drm/syncobj: Add a syncobj_array_find helper
[linux-2.6-microblaze.git] / drivers / gpu / drm / drm_syncobj.c
index 789ba0b..15e74ca 100644 (file)
@@ -1,5 +1,7 @@
 /*
  * Copyright 2017 Red Hat
+ * Parts ported from amdgpu (fence wait code).
+ * Copyright 2016 Advanced Micro Devices, Inc.
  *
  * Permission is hereby granted, free of charge, to any person obtaining a
  * copy of this software and associated documentation files (the "Software"),
@@ -31,6 +33,9 @@
  * that contain an optional fence. The fence can be updated with a new
  * fence, or be NULL.
  *
+ * syncobj's can be waited upon, where it will wait for the underlying
+ * fence.
+ *
  * syncobj's can be export to fd's and back, these fd's are opaque and
  * have no other use case, except passing the syncobj between processes.
  *
@@ -46,6 +51,7 @@
 #include <linux/fs.h>
 #include <linux/anon_inodes.h>
 #include <linux/sync_file.h>
+#include <linux/sched/signal.h>
 
 #include "drm_internal.h"
 #include <drm/drm_syncobj.h>
@@ -75,6 +81,75 @@ struct drm_syncobj *drm_syncobj_find(struct drm_file *file_private,
 }
 EXPORT_SYMBOL(drm_syncobj_find);
 
+static void drm_syncobj_add_callback_locked(struct drm_syncobj *syncobj,
+                                           struct drm_syncobj_cb *cb,
+                                           drm_syncobj_func_t func)
+{
+       cb->func = func;
+       list_add_tail(&cb->node, &syncobj->cb_list);
+}
+
+static int drm_syncobj_fence_get_or_add_callback(struct drm_syncobj *syncobj,
+                                                struct dma_fence **fence,
+                                                struct drm_syncobj_cb *cb,
+                                                drm_syncobj_func_t func)
+{
+       int ret;
+
+       *fence = drm_syncobj_fence_get(syncobj);
+       if (*fence)
+               return 1;
+
+       spin_lock(&syncobj->lock);
+       /* We've already tried once to get a fence and failed.  Now that we
+        * have the lock, try one more time just to be sure we don't add a
+        * callback when a fence has already been set.
+        */
+       if (syncobj->fence) {
+               *fence = dma_fence_get(syncobj->fence);
+               ret = 1;
+       } else {
+               *fence = NULL;
+               drm_syncobj_add_callback_locked(syncobj, cb, func);
+               ret = 0;
+       }
+       spin_unlock(&syncobj->lock);
+
+       return ret;
+}
+
+/**
+ * drm_syncobj_add_callback - adds a callback to syncobj::cb_list
+ * @syncobj: Sync object to which to add the callback
+ * @cb: Callback to add
+ * @func: Func to use when initializing the drm_syncobj_cb struct
+ *
+ * This adds a callback to be called next time the fence is replaced
+ */
+void drm_syncobj_add_callback(struct drm_syncobj *syncobj,
+                             struct drm_syncobj_cb *cb,
+                             drm_syncobj_func_t func)
+{
+       spin_lock(&syncobj->lock);
+       drm_syncobj_add_callback_locked(syncobj, cb, func);
+       spin_unlock(&syncobj->lock);
+}
+EXPORT_SYMBOL(drm_syncobj_add_callback);
+
+/**
+ * drm_syncobj_add_callback - removes a callback to syncobj::cb_list
+ * @syncobj: Sync object from which to remove the callback
+ * @cb: Callback to remove
+ */
+void drm_syncobj_remove_callback(struct drm_syncobj *syncobj,
+                                struct drm_syncobj_cb *cb)
+{
+       spin_lock(&syncobj->lock);
+       list_del_init(&cb->node);
+       spin_unlock(&syncobj->lock);
+}
+EXPORT_SYMBOL(drm_syncobj_remove_callback);
+
 /**
  * drm_syncobj_replace_fence - replace fence in a sync object.
  * @syncobj: Sync object to replace fence in
@@ -86,18 +161,75 @@ void drm_syncobj_replace_fence(struct drm_syncobj *syncobj,
                               struct dma_fence *fence)
 {
        struct dma_fence *old_fence;
+       struct drm_syncobj_cb *cur, *tmp;
 
        if (fence)
                dma_fence_get(fence);
-       old_fence = xchg(&syncobj->fence, fence);
+
+       spin_lock(&syncobj->lock);
+
+       old_fence = syncobj->fence;
+       syncobj->fence = fence;
+
+       if (fence != old_fence) {
+               list_for_each_entry_safe(cur, tmp, &syncobj->cb_list, node) {
+                       list_del_init(&cur->node);
+                       cur->func(syncobj, cur);
+               }
+       }
+
+       spin_unlock(&syncobj->lock);
 
        dma_fence_put(old_fence);
 }
 EXPORT_SYMBOL(drm_syncobj_replace_fence);
 
-int drm_syncobj_fence_get(struct drm_file *file_private,
-                         u32 handle,
-                         struct dma_fence **fence)
+struct drm_syncobj_null_fence {
+       struct dma_fence base;
+       spinlock_t lock;
+};
+
+static const char *drm_syncobj_null_fence_get_name(struct dma_fence *fence)
+{
+        return "syncobjnull";
+}
+
+static bool drm_syncobj_null_fence_enable_signaling(struct dma_fence *fence)
+{
+    dma_fence_enable_sw_signaling(fence);
+    return !dma_fence_is_signaled(fence);
+}
+
+static const struct dma_fence_ops drm_syncobj_null_fence_ops = {
+       .get_driver_name = drm_syncobj_null_fence_get_name,
+       .get_timeline_name = drm_syncobj_null_fence_get_name,
+       .enable_signaling = drm_syncobj_null_fence_enable_signaling,
+       .wait = dma_fence_default_wait,
+       .release = NULL,
+};
+
+static int drm_syncobj_assign_null_handle(struct drm_syncobj *syncobj)
+{
+       struct drm_syncobj_null_fence *fence;
+       fence = kzalloc(sizeof(*fence), GFP_KERNEL);
+       if (fence == NULL)
+               return -ENOMEM;
+
+       spin_lock_init(&fence->lock);
+       dma_fence_init(&fence->base, &drm_syncobj_null_fence_ops,
+                      &fence->lock, 0, 0);
+       dma_fence_signal(&fence->base);
+
+       drm_syncobj_replace_fence(syncobj, &fence->base);
+
+       dma_fence_put(&fence->base);
+
+       return 0;
+}
+
+int drm_syncobj_find_fence(struct drm_file *file_private,
+                          u32 handle,
+                          struct dma_fence **fence)
 {
        struct drm_syncobj *syncobj = drm_syncobj_find(file_private, handle);
        int ret = 0;
@@ -105,14 +237,14 @@ int drm_syncobj_fence_get(struct drm_file *file_private,
        if (!syncobj)
                return -ENOENT;
 
-       *fence = dma_fence_get(syncobj->fence);
+       *fence = drm_syncobj_fence_get(syncobj);
        if (!*fence) {
                ret = -EINVAL;
        }
        drm_syncobj_put(syncobj);
        return ret;
 }
-EXPORT_SYMBOL(drm_syncobj_fence_get);
+EXPORT_SYMBOL(drm_syncobj_find_fence);
 
 /**
  * drm_syncobj_free - free a sync object.
@@ -125,13 +257,13 @@ void drm_syncobj_free(struct kref *kref)
        struct drm_syncobj *syncobj = container_of(kref,
                                                   struct drm_syncobj,
                                                   refcount);
-       dma_fence_put(syncobj->fence);
+       drm_syncobj_replace_fence(syncobj, NULL);
        kfree(syncobj);
 }
 EXPORT_SYMBOL(drm_syncobj_free);
 
 static int drm_syncobj_create(struct drm_file *file_private,
-                             u32 *handle)
+                             u32 *handle, uint32_t flags)
 {
        int ret;
        struct drm_syncobj *syncobj;
@@ -141,6 +273,16 @@ static int drm_syncobj_create(struct drm_file *file_private,
                return -ENOMEM;
 
        kref_init(&syncobj->refcount);
+       INIT_LIST_HEAD(&syncobj->cb_list);
+       spin_lock_init(&syncobj->lock);
+
+       if (flags & DRM_SYNCOBJ_CREATE_SIGNALED) {
+               ret = drm_syncobj_assign_null_handle(syncobj);
+               if (ret < 0) {
+                       drm_syncobj_put(syncobj);
+                       return ret;
+               }
+       }
 
        idr_preload(GFP_KERNEL);
        spin_lock(&file_private->syncobj_table_lock);
@@ -307,7 +449,7 @@ int drm_syncobj_export_sync_file(struct drm_file *file_private,
        if (fd < 0)
                return fd;
 
-       ret = drm_syncobj_fence_get(file_private, handle, &fence);
+       ret = drm_syncobj_find_fence(file_private, handle, &fence);
        if (ret)
                goto err_put_fd;
 
@@ -330,7 +472,6 @@ err_put_fd:
 }
 /**
  * drm_syncobj_open - initalizes syncobj file-private structures at devnode open time
- * @dev: drm_device which is being opened by userspace
  * @file_private: drm file-private structure to set up
  *
  * Called at device open time, sets up the structure for handling refcounting
@@ -354,7 +495,6 @@ drm_syncobj_release_handle(int id, void *ptr, void *data)
 
 /**
  * drm_syncobj_release - release file-private sync object resources
- * @dev: drm_device which is being closed by userspace
  * @file_private: drm file-private structure to clean up
  *
  * Called at close time when the filp is going away.
@@ -379,11 +519,11 @@ drm_syncobj_create_ioctl(struct drm_device *dev, void *data,
                return -ENODEV;
 
        /* no valid flags yet */
-       if (args->flags)
+       if (args->flags & ~DRM_SYNCOBJ_CREATE_SIGNALED)
                return -EINVAL;
 
        return drm_syncobj_create(file_private,
-                                 &args->handle);
+                                 &args->handle, args->flags);
 }
 
 int
@@ -449,3 +589,299 @@ drm_syncobj_fd_to_handle_ioctl(struct drm_device *dev, void *data,
        return drm_syncobj_fd_to_handle(file_private, args->fd,
                                        &args->handle);
 }
+
+struct syncobj_wait_entry {
+       struct task_struct *task;
+       struct dma_fence *fence;
+       struct dma_fence_cb fence_cb;
+       struct drm_syncobj_cb syncobj_cb;
+};
+
+static void syncobj_wait_fence_func(struct dma_fence *fence,
+                                   struct dma_fence_cb *cb)
+{
+       struct syncobj_wait_entry *wait =
+               container_of(cb, struct syncobj_wait_entry, fence_cb);
+
+       wake_up_process(wait->task);
+}
+
+static void syncobj_wait_syncobj_func(struct drm_syncobj *syncobj,
+                                     struct drm_syncobj_cb *cb)
+{
+       struct syncobj_wait_entry *wait =
+               container_of(cb, struct syncobj_wait_entry, syncobj_cb);
+
+       /* This happens inside the syncobj lock */
+       wait->fence = dma_fence_get(syncobj->fence);
+       wake_up_process(wait->task);
+}
+
+static signed long drm_syncobj_array_wait_timeout(struct drm_syncobj **syncobjs,
+                                                 uint32_t count,
+                                                 uint32_t flags,
+                                                 signed long timeout,
+                                                 uint32_t *idx)
+{
+       struct syncobj_wait_entry *entries;
+       struct dma_fence *fence;
+       signed long ret;
+       uint32_t signaled_count, i;
+
+       entries = kcalloc(count, sizeof(*entries), GFP_KERNEL);
+       if (!entries)
+               return -ENOMEM;
+
+       /* Walk the list of sync objects and initialize entries.  We do
+        * this up-front so that we can properly return -EINVAL if there is
+        * a syncobj with a missing fence and then never have the chance of
+        * returning -EINVAL again.
+        */
+       signaled_count = 0;
+       for (i = 0; i < count; ++i) {
+               entries[i].task = current;
+               entries[i].fence = drm_syncobj_fence_get(syncobjs[i]);
+               if (!entries[i].fence) {
+                       if (flags & DRM_SYNCOBJ_WAIT_FLAGS_WAIT_FOR_SUBMIT) {
+                               continue;
+                       } else {
+                               ret = -EINVAL;
+                               goto cleanup_entries;
+                       }
+               }
+
+               if (dma_fence_is_signaled(entries[i].fence)) {
+                       if (signaled_count == 0 && idx)
+                               *idx = i;
+                       signaled_count++;
+               }
+       }
+
+       /* Initialize ret to the max of timeout and 1.  That way, the
+        * default return value indicates a successful wait and not a
+        * timeout.
+        */
+       ret = max_t(signed long, timeout, 1);
+
+       if (signaled_count == count ||
+           (signaled_count > 0 &&
+            !(flags & DRM_SYNCOBJ_WAIT_FLAGS_WAIT_ALL)))
+               goto cleanup_entries;
+
+       /* There's a very annoying laxness in the dma_fence API here, in
+        * that backends are not required to automatically report when a
+        * fence is signaled prior to fence->ops->enable_signaling() being
+        * called.  So here if we fail to match signaled_count, we need to
+        * fallthough and try a 0 timeout wait!
+        */
+
+       if (flags & DRM_SYNCOBJ_WAIT_FLAGS_WAIT_FOR_SUBMIT) {
+               for (i = 0; i < count; ++i) {
+                       drm_syncobj_fence_get_or_add_callback(syncobjs[i],
+                                                             &entries[i].fence,
+                                                             &entries[i].syncobj_cb,
+                                                             syncobj_wait_syncobj_func);
+               }
+       }
+
+       do {
+               set_current_state(TASK_INTERRUPTIBLE);
+
+               signaled_count = 0;
+               for (i = 0; i < count; ++i) {
+                       fence = entries[i].fence;
+                       if (!fence)
+                               continue;
+
+                       if (dma_fence_is_signaled(fence) ||
+                           (!entries[i].fence_cb.func &&
+                            dma_fence_add_callback(fence,
+                                                   &entries[i].fence_cb,
+                                                   syncobj_wait_fence_func))) {
+                               /* The fence has been signaled */
+                               if (flags & DRM_SYNCOBJ_WAIT_FLAGS_WAIT_ALL) {
+                                       signaled_count++;
+                               } else {
+                                       if (idx)
+                                               *idx = i;
+                                       goto done_waiting;
+                               }
+                       }
+               }
+
+               if (signaled_count == count)
+                       goto done_waiting;
+
+               if (timeout == 0) {
+                       /* If we are doing a 0 timeout wait and we got
+                        * here, then we just timed out.
+                        */
+                       ret = 0;
+                       goto done_waiting;
+               }
+
+               ret = schedule_timeout(ret);
+
+               if (ret > 0 && signal_pending(current))
+                       ret = -ERESTARTSYS;
+       } while (ret > 0);
+
+done_waiting:
+       __set_current_state(TASK_RUNNING);
+
+cleanup_entries:
+       for (i = 0; i < count; ++i) {
+               if (entries[i].syncobj_cb.func)
+                       drm_syncobj_remove_callback(syncobjs[i],
+                                                   &entries[i].syncobj_cb);
+               if (entries[i].fence_cb.func)
+                       dma_fence_remove_callback(entries[i].fence,
+                                                 &entries[i].fence_cb);
+               dma_fence_put(entries[i].fence);
+       }
+       kfree(entries);
+
+       return ret;
+}
+
+/**
+ * drm_timeout_abs_to_jiffies - calculate jiffies timeout from absolute value
+ *
+ * @timeout_nsec: timeout nsec component in ns, 0 for poll
+ *
+ * Calculate the timeout in jiffies from an absolute time in sec/nsec.
+ */
+static signed long drm_timeout_abs_to_jiffies(int64_t timeout_nsec)
+{
+       ktime_t abs_timeout, now;
+       u64 timeout_ns, timeout_jiffies64;
+
+       /* make 0 timeout means poll - absolute 0 doesn't seem valid */
+       if (timeout_nsec == 0)
+               return 0;
+
+       abs_timeout = ns_to_ktime(timeout_nsec);
+       now = ktime_get();
+
+       if (!ktime_after(abs_timeout, now))
+               return 0;
+
+       timeout_ns = ktime_to_ns(ktime_sub(abs_timeout, now));
+
+       timeout_jiffies64 = nsecs_to_jiffies64(timeout_ns);
+       /*  clamp timeout to avoid infinite timeout */
+       if (timeout_jiffies64 >= MAX_SCHEDULE_TIMEOUT - 1)
+               return MAX_SCHEDULE_TIMEOUT - 1;
+
+       return timeout_jiffies64 + 1;
+}
+
+static int drm_syncobj_array_wait(struct drm_device *dev,
+                                 struct drm_file *file_private,
+                                 struct drm_syncobj_wait *wait,
+                                 struct drm_syncobj **syncobjs)
+{
+       signed long timeout = drm_timeout_abs_to_jiffies(wait->timeout_nsec);
+       signed long ret = 0;
+       uint32_t first = ~0;
+
+       ret = drm_syncobj_array_wait_timeout(syncobjs,
+                                            wait->count_handles,
+                                            wait->flags,
+                                            timeout, &first);
+       if (ret < 0)
+               return ret;
+
+       wait->first_signaled = first;
+       if (ret == 0)
+               return -ETIME;
+       return 0;
+}
+
+static int drm_syncobj_array_find(struct drm_file *file_private,
+                                 void *user_handles, uint32_t count_handles,
+                                 struct drm_syncobj ***syncobjs_out)
+{
+       uint32_t i, *handles;
+       struct drm_syncobj **syncobjs;
+       int ret;
+
+       handles = kmalloc_array(count_handles, sizeof(*handles), GFP_KERNEL);
+       if (handles == NULL)
+               return -ENOMEM;
+
+       if (copy_from_user(handles, user_handles,
+                          sizeof(uint32_t) * count_handles)) {
+               ret = -EFAULT;
+               goto err_free_handles;
+       }
+
+       syncobjs = kmalloc_array(count_handles, sizeof(*syncobjs), GFP_KERNEL);
+       if (syncobjs == NULL) {
+               ret = -ENOMEM;
+               goto err_free_handles;
+       }
+
+       for (i = 0; i < count_handles; i++) {
+               syncobjs[i] = drm_syncobj_find(file_private, handles[i]);
+               if (!syncobjs[i]) {
+                       ret = -ENOENT;
+                       goto err_put_syncobjs;
+               }
+       }
+
+       kfree(handles);
+       *syncobjs_out = syncobjs;
+       return 0;
+
+err_put_syncobjs:
+       while (i-- > 0)
+               drm_syncobj_put(syncobjs[i]);
+       kfree(syncobjs);
+err_free_handles:
+       kfree(handles);
+
+       return ret;
+}
+
+static void drm_syncobj_array_free(struct drm_syncobj **syncobjs,
+                                  uint32_t count)
+{
+       uint32_t i;
+       for (i = 0; i < count; i++)
+               drm_syncobj_put(syncobjs[i]);
+       kfree(syncobjs);
+}
+
+int
+drm_syncobj_wait_ioctl(struct drm_device *dev, void *data,
+                      struct drm_file *file_private)
+{
+       struct drm_syncobj_wait *args = data;
+       struct drm_syncobj **syncobjs;
+       int ret = 0;
+
+       if (!drm_core_check_feature(dev, DRIVER_SYNCOBJ))
+               return -ENODEV;
+
+       if (args->flags & ~(DRM_SYNCOBJ_WAIT_FLAGS_WAIT_ALL |
+                           DRM_SYNCOBJ_WAIT_FLAGS_WAIT_FOR_SUBMIT))
+               return -EINVAL;
+
+       if (args->count_handles == 0)
+               return -EINVAL;
+
+       ret = drm_syncobj_array_find(file_private,
+                                    u64_to_user_ptr(args->handles),
+                                    args->count_handles,
+                                    &syncobjs);
+       if (ret < 0)
+               return ret;
+
+       ret = drm_syncobj_array_wait(dev, file_private,
+                                    args, syncobjs);
+
+       drm_syncobj_array_free(syncobjs, args->count_handles);
+
+       return ret;
+}