Implement mos_gem_bo_wait

InflightList is based on the version in mesa (except converted to C++),
because it's thread-safe and the most optimized.

Change-Id: Ia0b08c49e2cdc4e3aba0e387745f14121e9f18ac
diff --git a/media_driver/linux/common/os/magma/inflight_list.cpp b/media_driver/linux/common/os/magma/inflight_list.cpp
new file mode 100644
index 0000000..4af26e3
--- /dev/null
+++ b/media_driver/linux/common/os/magma/inflight_list.cpp
@@ -0,0 +1,170 @@
+/*
+ * Copyright © 2019 Google, LLC
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#include "inflight_list.h"
+#include <time.h>
+
+static uint64_t gettime_ns(void)
+{
+   struct timespec current;
+   clock_gettime(CLOCK_MONOTONIC, &current);
+   constexpr uint64_t kNsecPerSec =  1000000000;
+   return static_cast<uint64_t>(current.tv_sec * kNsecPerSec + current.tv_nsec);
+}
+
+static uint64_t get_relative_timeout(uint64_t abs_timeout)
+{
+   uint64_t now = gettime_ns();
+
+   if (abs_timeout < now)
+      return 0;
+   return abs_timeout - now;
+}
+
+static magma_status_t wait_notification_channel(magma_handle_t channel, int64_t timeout_ns)
+{
+   magma_poll_item_t item = {
+       .handle = channel,
+       .type = MAGMA_POLL_TYPE_HANDLE,
+       .condition = MAGMA_POLL_CONDITION_READABLE,
+   };
+   return magma_poll(&item, 1, timeout_ns);
+}
+
+InflightList::InflightList()
+{
+   wait_ = wait_notification_channel;
+   read_ = magma_read_notification_channel2;
+}
+
+InflightList::~InflightList()
+{
+}
+
+void InflightList::add( uint64_t buffer_id)
+{
+   assert(buffer_id != 0);
+   buffers_.push_back(buffer_id);
+}
+
+bool InflightList::remove(uint64_t buffer_id)
+{
+   auto it = std::find(buffers_.begin(), buffers_.end(), buffer_id);
+   if (it == buffers_.end()) {
+      return false;
+   }
+   buffers_.erase(it);
+   return true;
+}
+
+bool InflightList::is_inflight(uint64_t buffer_id)
+{
+   return std::find(buffers_.begin(), buffers_.end(), buffer_id) != buffers_.end();
+}
+
+bool InflightList::TryUpdate(magma_connection_t connection)
+{
+   if (!mutex_.try_lock()) {
+      return false;
+   }
+
+   update(connection);
+
+   mutex_.unlock();
+
+   return true;
+}
+
+magma_status_t InflightList::WaitForBuffer(magma_connection_t connection,
+                                           magma_handle_t notification_channel, uint64_t buffer_id,
+                                           uint64_t timeout_ns)
+{
+   // Calculate deadline before potentially blocking on the mutex
+   uint64_t start = gettime_ns();
+   uint64_t deadline = start + timeout_ns;
+
+   std::lock_guard<std::mutex> lock(mutex_);
+
+   magma_status_t status = MAGMA_STATUS_OK;
+
+   if (is_inflight(buffer_id)) {
+
+      while (true) {
+         // First pass optimization: optimistically try reading the notification channel;
+         // may avoid an unnecessary wait.
+         update(connection);
+
+         if (!is_inflight(buffer_id))
+            break;
+
+         if (timeout_ns == 0) {
+            // Optimization: don't bother making the wait system call since the notification
+            // channel was just drained.
+            status = MAGMA_STATUS_TIMED_OUT;
+            break;
+         }
+
+         status = wait_(notification_channel, get_relative_timeout(deadline));
+         if (status != MAGMA_STATUS_OK) {
+            break;
+         }
+      }
+   }
+
+   return status;
+}
+
+void InflightList::AddAndUpdate(magma_connection_t connection,
+                               struct magma_exec_resource* resources, uint32_t count)
+{
+   std::lock_guard<std::mutex> lock(mutex_);
+
+   for (uint32_t i = 0; i < count; i++) {
+      add(resources[i].buffer_id);
+   }
+
+   update(connection);
+}
+
+void InflightList::update(magma_connection_t connection)
+{
+   uint64_t bytes_available = 0;
+   magma_bool_t more_data = false;
+   while (true) {
+      magma_status_t status =
+          read_(connection, notification_buffer, sizeof(notification_buffer),
+                      &bytes_available, &more_data);
+      if (status != MAGMA_STATUS_OK) {
+         return;
+      }
+      if (bytes_available == 0)
+         return;
+      assert(bytes_available % sizeof(uint64_t) == 0);
+      for (uint32_t i = 0; i < bytes_available / sizeof(uint64_t); i++) {
+         assert(is_inflight(notification_buffer[i]));
+         remove(notification_buffer[i]);
+      }
+      if (!more_data)
+         return;
+   }
+}
diff --git a/media_driver/linux/common/os/magma/inflight_list.h b/media_driver/linux/common/os/magma/inflight_list.h
new file mode 100644
index 0000000..f652462
--- /dev/null
+++ b/media_driver/linux/common/os/magma/inflight_list.h
@@ -0,0 +1,78 @@
+/*
+ * Copyright © 2019 Google, LLC
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#ifndef INFLIGHT_LIST_H
+#define INFLIGHT_LIST_H
+
+#include <assert.h>
+#include <magma.h>
+#include <stdbool.h>
+#include <stdio.h>
+
+#include <deque>
+#include <mutex>
+
+// A convenience utility for maintaining a list of inflight command buffers,
+// by reading completed buffer ids from the magma notification channel.
+
+typedef magma_status_t (*wait_notification_channel_t)(magma_handle_t channel, int64_t timeout_ns);
+
+typedef magma_status_t (*read_notification_channel_t)(magma_connection_t connection, void* buffer,
+                                                      uint64_t buffer_size,
+                                                      uint64_t* buffer_size_out,
+                                                      magma_bool_t* more_data_out);
+
+class InflightList {
+  public:
+   InflightList();
+   ~InflightList();
+
+   void add(uint64_t buffer_id);
+   bool remove(uint64_t buffer_id);
+
+   bool is_inflight(uint64_t buffer_id);
+
+   void update(magma_connection_t connection);
+
+   // Returns true if the list lock was obtained. Threadsafe.
+   bool TryUpdate(magma_connection_t connection);
+
+   // Wait for the given |buffer_id| to be removed from the inflight list. Threadsafe.
+   magma_status_t WaitForBuffer(magma_connection_t connection,
+                                magma_handle_t notification_channel, uint64_t buffer_id,
+                                uint64_t timeout_ns);
+
+   // Adds the given buffers to the inflight list and services the notification channel. Threadsafe.
+   void AddAndUpdate(magma_connection_t connection,
+                     struct magma_exec_resource* resources, uint32_t count);
+
+ private:
+   wait_notification_channel_t wait_;
+   read_notification_channel_t read_;
+
+   std::deque<uint64_t> buffers_;
+   std::mutex mutex_;
+   uint64_t notification_buffer[4096 / sizeof(uint64_t)];
+};
+
+#endif // INFLIGHT_LIST_H
diff --git a/media_driver/linux/common/os/magma/media_srcs.cmake b/media_driver/linux/common/os/magma/media_srcs.cmake
index 73f9e46..01d3296 100644
--- a/media_driver/linux/common/os/magma/media_srcs.cmake
+++ b/media_driver/linux/common/os/magma/media_srcs.cmake
@@ -24,6 +24,7 @@
     ${CMAKE_CURRENT_LIST_DIR}/mos_bufmgr_magma.cpp
     ${CMAKE_CURRENT_LIST_DIR}/mos_bufmgr_stub.c
     ${CMAKE_CURRENT_LIST_DIR}/simple_allocator.cpp
+    ${CMAKE_CURRENT_LIST_DIR}/inflight_list.cpp
     ${CMAKE_CURRENT_LIST_DIR}/../i915/mos_bufmgr_api.c
 )
 
diff --git a/media_driver/linux/common/os/magma/mos_bufmgr_magma.cpp b/media_driver/linux/common/os/magma/mos_bufmgr_magma.cpp
index af79ea6..0a2cd4e 100644
--- a/media_driver/linux/common/os/magma/mos_bufmgr_magma.cpp
+++ b/media_driver/linux/common/os/magma/mos_bufmgr_magma.cpp
@@ -25,6 +25,7 @@
 #include <zircon/process.h>
 #include <zircon/syscalls.h>
 
+#include "inflight_list.h"
 #include "mos_bufmgr.h"
 #include "mos_bufmgr_priv.h"
 #include "simple_allocator.h"
@@ -53,6 +54,7 @@
     ~MagmaBufMgr();
 
     magma_connection_t connection() const { return connection_; }
+    magma_handle_t notification_handle() const { return notification_handle_; }
 
     uint32_t device_id() const { return device_id_; }
 
@@ -68,11 +70,15 @@
 
     MagmaBo* CreateBo(const char *name, unsigned long size);
 
+    InflightList* inflight_list() { return &inflight_list_;}
+
 private:
     magma_connection_t connection_ {};
+    magma_handle_t notification_handle_{};
     uint32_t device_id_;
     std::mutex allocator_mutex_;
     std::unique_ptr<SimpleAllocator> allocator_ __attribute__((__guarded_by__(allocator_mutex_)));
+    InflightList inflight_list_;
 };
 
 
@@ -413,6 +419,8 @@
 MagmaBufMgr::MagmaBufMgr(magma_connection_t connection, uint32_t device_id) :
     connection_(connection), device_id_(device_id) {
 
+    notification_handle_ = magma_get_notification_channel_handle(connection_);
+
     mos_bufmgr::bo_alloc = bufmgr_bo_alloc;
     mos_bufmgr::bo_alloc_for_render = bufmgr_bo_alloc_for_render;
     mos_bufmgr::bo_alloc_tiled = bufmgr_bo_alloc_tiled;
@@ -569,6 +577,7 @@
     constexpr uint32_t kExpectedFlags = I915_EXEC_BSD|I915_EXEC_BSD_RING1;
 
     auto bo = static_cast<MagmaBo*>(mos_linux_bo);
+    auto bufmgr = static_cast<MagmaBufMgr*>(ctx->bufmgr);
 
     LOG_VERBOSE("mos_gem_bo_context_exec2 bo %lu used %d context_id %u num_cliprects %d DR4 %d flags 0x%x kExpectedFlags 0x%x",
         bo->id(), used, ctx->ctx_id, num_cliprects, DR4, flags, kExpectedFlags);
@@ -589,6 +598,14 @@
         .flags = kMagmaIntelGenCommandBufferForVideo,
     };
 
+   // Add to inflight list first to avoid race with any other thread reading completions from the
+   // notification channel, in case this thread is preempted just after sending the command buffer
+   // and the completion happens quickly.
+   bufmgr->inflight_list()->AddAndUpdate(
+                             bufmgr->connection(), resources.data(),
+                             resources.size());
+
+
     magma_status_t status = magma_execute_command_buffer_with_resources2(bo->magma_bufmgr()->connection(),
         ctx->ctx_id,
         &command_buffer,
@@ -622,3 +639,19 @@
     *prime_fd = handle;
     return 0;
 }
+
+int mos_gem_bo_wait(struct mos_linux_bo *mos_linux_bo, int64_t timeout_ns)
+{
+    auto bo = static_cast<MagmaBo*>(mos_linux_bo);
+    auto bufmgr = bo->magma_bufmgr();
+    magma_status_t status = bufmgr->inflight_list()->WaitForBuffer(
+        bufmgr->connection(),
+        bufmgr->notification_handle(),
+        bo->id(),
+        timeout_ns);
+    if (status != MAGMA_STATUS_OK) {
+        LOG_VERBOSE("WaitForbuffer failed: %d", status);
+        return -1;
+    }
+    return 0;
+}
diff --git a/media_driver/linux/common/os/magma/mos_bufmgr_stub.c b/media_driver/linux/common/os/magma/mos_bufmgr_stub.c
index b0aeb9c..ff88479 100644
--- a/media_driver/linux/common/os/magma/mos_bufmgr_stub.c
+++ b/media_driver/linux/common/os/magma/mos_bufmgr_stub.c
@@ -33,12 +33,6 @@
         fflush(stderr); \
 } while (0)
 
-int mos_gem_bo_wait(struct mos_linux_bo *bo, int64_t timeout_ns)
-{
-    LOG_VERBOSE("mos_gem_bo_wait unimplemented");
-    return 0;
-}
-
 void mos_bufmgr_gem_enable_reuse(struct mos_bufmgr *bufmgr)
 {
     LOG_VERBOSE("mos_bufmgr_gem_enable_reuse unimplemented");