[Mesa-dev] [PATCH 3/3] anv: Add a userspace timeline implementation based on BOs

Jason Ekstrand jason at jlekstrand.net
Fri Oct 26 18:40:21 UTC 2018


This allows us to implement non-exportable timeline semaphores with BOs
that will work on as old a kernel as we like.
---
 src/intel/vulkan/anv_private.h |  23 ++++
 src/intel/vulkan/anv_queue.c   | 187 +++++++++++++++++++++++++++++++++
 2 files changed, 210 insertions(+)

diff --git a/src/intel/vulkan/anv_private.h b/src/intel/vulkan/anv_private.h
index a3ab971b6fd..0078efb3677 100644
--- a/src/intel/vulkan/anv_private.h
+++ b/src/intel/vulkan/anv_private.h
@@ -2283,6 +2283,29 @@ enum anv_semaphore_type {
    ANV_SEMAPHORE_TYPE_DRM_SYNCOBJ,
 };
 
+struct anv_bo_time_point {
+   struct list_head link;
+
+   /* True if some thread is actively waiting on the BO */
+   int waiting;
+
+   uint64_t serial;
+   struct anv_bo bo;
+};
+
+struct anv_bo_timeline {
+   uint64_t highest_past;
+   uint64_t highest_pending;
+
+   struct list_head time_points;
+   struct list_head free_time_points;
+};
+
+VkResult
+anv_bo_timeline_get_signal_bo_locked(struct anv_device *device,
+                                     struct anv_bo_timeline *timeline,
+                                     uint64_t serial, struct anv_bo **bo_out);
+
 struct anv_semaphore_impl {
    enum anv_semaphore_type type;
 
diff --git a/src/intel/vulkan/anv_queue.c b/src/intel/vulkan/anv_queue.c
index 0b4324cb9d4..dbf15963784 100644
--- a/src/intel/vulkan/anv_queue.c
+++ b/src/intel/vulkan/anv_queue.c
@@ -918,6 +918,193 @@ VkResult anv_GetFenceFdKHR(
 
 // Queue semaphore functions
 
+static void
+anv_bo_timeline_init(struct anv_bo_timeline *timeline)
+{
+   timeline->highest_past = 0;
+   timeline->highest_pending = 0;
+   list_inithead(&timeline->time_points);
+   list_inithead(&timeline->free_time_points);
+}
+
+static void
+anv_bo_timeline_finish(struct anv_device *device,
+                       struct anv_bo_timeline *timeline)
+{
+   list_for_each_entry_safe(struct anv_bo_time_point, tp,
+                            &timeline->time_points, link) {
+      anv_gem_close(device, tp->bo.gem_handle);
+      vk_free(&device->alloc, tp);
+   }
+
+   list_for_each_entry_safe(struct anv_bo_time_point, tp,
+                            &timeline->free_time_points, link) {
+      anv_gem_close(device, tp->bo.gem_handle);
+      vk_free(&device->alloc, tp);
+   }
+}
+
+static VkResult
+anv_bo_timeline_gc_locked(struct anv_device *device,
+                          struct anv_bo_timeline *timeline)
+{
+   list_for_each_entry_safe(struct anv_bo_time_point, time_point,
+                            &timeline->time_points, link) {
+      /* If someone is waiting on this time point, consider it busy and don't
+       * try to recycle it.  There's a slim possibility that it's no longer
+       * busy by the time we look at it but we would be recycling it out from
+       * under a waiter and that can lead to weird races.
+       *
+       * We walk the list in-order so if this time point is still busy so
+       * is every following time point
+       */
+      assert(time_point->waiting >= 0);
+      if (time_point->waiting)
+         return VK_SUCCESS;
+
+      VkResult result = anv_device_bo_busy(device, &time_point->bo);
+      if (result == VK_NOT_READY) {
+         /* We walk the list in-order so if this time point is still busy so
+          * is every following time point
+          */
+         return VK_SUCCESS;
+      } else if (result != VK_SUCCESS) {
+         return result;
+      }
+
+      /* If we got here, the BO isn't busy; recycle it. */
+      assert(timeline->highest_past < time_point->serial);
+      timeline->highest_past = time_point->serial;
+      list_del(&time_point->link);
+      list_addtail(&time_point->link, &timeline->free_time_points);
+   }
+
+   return VK_SUCCESS;
+}
+
+VkResult
+anv_bo_timeline_get_signal_bo_locked(struct anv_device *device,
+                                     struct anv_bo_timeline *timeline,
+                                     uint64_t serial, struct anv_bo **bo_out)
+{
+   /* Garbage collect in case there's an idle BO we can re-use */
+   anv_bo_timeline_gc_locked(device, timeline);
+
+   /* Timelines must always increase */
+   assert(serial > timeline->highest_pending);
+
+   struct anv_bo_time_point *time_point;
+   if (list_empty(&timeline->free_time_points)) {
+      time_point = vk_zalloc(&device->alloc, sizeof(*time_point), 8,
+                    VK_SYSTEM_ALLOCATION_SCOPE_DEVICE);
+      if (!time_point)
+         return vk_error(VK_ERROR_OUT_OF_HOST_MEMORY);
+
+      VkResult result = anv_bo_init_new(&time_point->bo, device, 4096);
+      if (result != VK_SUCCESS) {
+         vk_free(&device->alloc, time_point);
+         return result;
+      }
+   } else {
+      time_point = list_first_entry(&timeline->free_time_points,
+                            struct anv_bo_time_point, link);
+      list_del(&time_point->link);
+   }
+
+   assert(!time_point->waiting);
+   time_point->serial = serial;
+   list_addtail(&time_point->link, &timeline->time_points);
+   timeline->highest_pending = serial;
+
+   *bo_out = &time_point->bo;
+   return VK_SUCCESS;
+}
+
+static VkResult
+anv_bo_timeline_wait_locked(struct anv_device *device,
+                            struct anv_bo_timeline *timeline,
+                            uint64_t serial, uint64_t abs_timeout_ns)
+{
+   /* Wait on the queue_submit condition variable until the timeline has a
+    * time point pending that's at least as high as serial.
+    */
+   while (timeline->highest_pending < serial) {
+      struct timespec abstime = {
+         .tv_sec = abs_timeout_ns / NSEC_PER_SEC,
+         .tv_nsec = abs_timeout_ns % NSEC_PER_SEC,
+      };
+
+      int ret = pthread_cond_timedwait(&device->queue_submit,
+                                       &device->mutex, &abstime);
+      assert(ret != EINVAL);
+      if (gettime_ns() >= abs_timeout_ns &&
+          timeline->highest_pending < serial)
+         return VK_TIMEOUT;
+   }
+
+   while (1) {
+      VkResult result = anv_bo_timeline_gc_locked(device, timeline);
+      if (result != VK_SUCCESS)
+         return result;
+
+      if (timeline->highest_past >= serial)
+         return VK_SUCCESS;
+
+      /* If we got here, our earliest time point has a busy BO */
+      struct anv_bo_time_point *time_point =
+         list_first_entry(&timeline->time_points,
+                          struct anv_bo_time_point, link);
+
+      /* Drop the lock while we wait. */
+      time_point->waiting++;
+      pthread_mutex_unlock(&device->mutex);
+
+      result = anv_device_wait(device, &time_point->bo,
+                               anv_get_relative_timeout(abs_timeout_ns));
+
+      /* Pick the mutex back up */
+      pthread_mutex_lock(&device->mutex);
+      time_point->waiting--;
+
+      /* This covers both VK_TIMEOUT and VK_ERROR_DEVICE_LOST */
+      if (result != VK_SUCCESS)
+         return result;
+   }
+}
+
+static VkResult
+anv_bo_timeline_highest_past(struct anv_device *device,
+                             struct anv_bo_timeline *timeline,
+                             uint64_t *serial)
+{
+   pthread_mutex_lock(&device->mutex);
+
+   VkResult result = anv_bo_timeline_gc_locked(device, timeline);
+   uint64_t highest_past = timeline->highest_past;
+
+   pthread_mutex_unlock(&device->mutex);
+
+   if (result == VK_SUCCESS)
+      *serial = highest_past;
+
+   return result;
+}
+
+static VkResult
+anv_bo_timeline_wait(struct anv_device *device,
+                     struct anv_bo_timeline *timeline,
+                     uint64_t serial, uint64_t abs_timeout_ns)
+{
+   pthread_mutex_lock(&device->mutex);
+
+   VkResult result = anv_bo_timeline_wait_locked(device, timeline, serial,
+                                                 abs_timeout_ns);
+
+   pthread_mutex_unlock(&device->mutex);
+
+   return result;
+}
+
 VkResult anv_CreateSemaphore(
     VkDevice                                    _device,
     const VkSemaphoreCreateInfo*                pCreateInfo,
-- 
2.19.1



More information about the mesa-dev mailing list