|
@@ -0,0 +1,382 @@
|
|
|
|
+/*
|
|
|
|
+ * Copyright 2017 Red Hat
|
|
|
|
+ *
|
|
|
|
+ * 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.
|
|
|
|
+ *
|
|
|
|
+ * Authors:
|
|
|
|
+ *
|
|
|
|
+ */
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * DOC: Overview
|
|
|
|
+ *
|
|
|
|
+ * DRM synchronisation objects (syncobj) are a persistent objects,
|
|
|
|
+ * that contain an optional fence. The fence can be updated with a new
|
|
|
|
+ * fence, or be NULL.
|
|
|
|
+ *
|
|
|
|
+ * 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.
|
|
|
|
+ *
|
|
|
|
+ * Their primary use-case is to implement Vulkan fences and semaphores.
|
|
|
|
+ *
|
|
|
|
+ * syncobj have a kref reference count, but also have an optional file.
|
|
|
|
+ * The file is only created once the syncobj is exported.
|
|
|
|
+ * The file takes a reference on the kref.
|
|
|
|
+ */
|
|
|
|
+
|
|
|
|
+#include <drm/drmP.h>
|
|
|
|
+#include <linux/file.h>
|
|
|
|
+#include <linux/fs.h>
|
|
|
|
+#include <linux/anon_inodes.h>
|
|
|
|
+
|
|
|
|
+#include "drm_internal.h"
|
|
|
|
+#include <drm/drm_syncobj.h>
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * drm_syncobj_find - lookup and reference a sync object.
|
|
|
|
+ * @file_private: drm file private pointer
|
|
|
|
+ * @handle: sync object handle to lookup.
|
|
|
|
+ *
|
|
|
|
+ * Returns a reference to the syncobj pointed to by handle or NULL.
|
|
|
|
+ */
|
|
|
|
+struct drm_syncobj *drm_syncobj_find(struct drm_file *file_private,
|
|
|
|
+ u32 handle)
|
|
|
|
+{
|
|
|
|
+ struct drm_syncobj *syncobj;
|
|
|
|
+
|
|
|
|
+ spin_lock(&file_private->syncobj_table_lock);
|
|
|
|
+
|
|
|
|
+ /* Check if we currently have a reference on the object */
|
|
|
|
+ syncobj = idr_find(&file_private->syncobj_idr, handle);
|
|
|
|
+ if (syncobj)
|
|
|
|
+ drm_syncobj_get(syncobj);
|
|
|
|
+
|
|
|
|
+ spin_unlock(&file_private->syncobj_table_lock);
|
|
|
|
+
|
|
|
|
+ return syncobj;
|
|
|
|
+}
|
|
|
|
+EXPORT_SYMBOL(drm_syncobj_find);
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * drm_syncobj_replace_fence - replace fence in a sync object.
|
|
|
|
+ * @file_private: drm file private pointer.
|
|
|
|
+ * @syncobj: Sync object to replace fence in
|
|
|
|
+ * @fence: fence to install in sync file.
|
|
|
|
+ *
|
|
|
|
+ * This replaces the fence on a sync object.
|
|
|
|
+ */
|
|
|
|
+void drm_syncobj_replace_fence(struct drm_file *file_private,
|
|
|
|
+ struct drm_syncobj *syncobj,
|
|
|
|
+ struct dma_fence *fence)
|
|
|
|
+{
|
|
|
|
+ struct dma_fence *old_fence = NULL;
|
|
|
|
+
|
|
|
|
+ if (fence)
|
|
|
|
+ dma_fence_get(fence);
|
|
|
|
+ old_fence = xchg(&syncobj->fence, fence);
|
|
|
|
+
|
|
|
|
+ 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 *syncobj = drm_syncobj_find(file_private, handle);
|
|
|
|
+ int ret = 0;
|
|
|
|
+
|
|
|
|
+ if (!syncobj)
|
|
|
|
+ return -ENOENT;
|
|
|
|
+
|
|
|
|
+ *fence = dma_fence_get(syncobj->fence);
|
|
|
|
+ if (!*fence) {
|
|
|
|
+ ret = -EINVAL;
|
|
|
|
+ }
|
|
|
|
+ drm_syncobj_put(syncobj);
|
|
|
|
+ return ret;
|
|
|
|
+}
|
|
|
|
+EXPORT_SYMBOL(drm_syncobj_fence_get);
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * drm_syncobj_free - free a sync object.
|
|
|
|
+ * @kref: kref to free.
|
|
|
|
+ *
|
|
|
|
+ * Only to be called from kref_put in drm_syncobj_put.
|
|
|
|
+ */
|
|
|
|
+void drm_syncobj_free(struct kref *kref)
|
|
|
|
+{
|
|
|
|
+ struct drm_syncobj *syncobj = container_of(kref,
|
|
|
|
+ struct drm_syncobj,
|
|
|
|
+ refcount);
|
|
|
|
+ dma_fence_put(syncobj->fence);
|
|
|
|
+ kfree(syncobj);
|
|
|
|
+}
|
|
|
|
+EXPORT_SYMBOL(drm_syncobj_free);
|
|
|
|
+
|
|
|
|
+static int drm_syncobj_create(struct drm_file *file_private,
|
|
|
|
+ u32 *handle)
|
|
|
|
+{
|
|
|
|
+ int ret;
|
|
|
|
+ struct drm_syncobj *syncobj;
|
|
|
|
+
|
|
|
|
+ syncobj = kzalloc(sizeof(struct drm_syncobj), GFP_KERNEL);
|
|
|
|
+ if (!syncobj)
|
|
|
|
+ return -ENOMEM;
|
|
|
|
+
|
|
|
|
+ kref_init(&syncobj->refcount);
|
|
|
|
+
|
|
|
|
+ idr_preload(GFP_KERNEL);
|
|
|
|
+ spin_lock(&file_private->syncobj_table_lock);
|
|
|
|
+ ret = idr_alloc(&file_private->syncobj_idr, syncobj, 1, 0, GFP_NOWAIT);
|
|
|
|
+ spin_unlock(&file_private->syncobj_table_lock);
|
|
|
|
+
|
|
|
|
+ idr_preload_end();
|
|
|
|
+
|
|
|
|
+ if (ret < 0) {
|
|
|
|
+ drm_syncobj_put(syncobj);
|
|
|
|
+ return ret;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ *handle = ret;
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int drm_syncobj_destroy(struct drm_file *file_private,
|
|
|
|
+ u32 handle)
|
|
|
|
+{
|
|
|
|
+ struct drm_syncobj *syncobj;
|
|
|
|
+
|
|
|
|
+ spin_lock(&file_private->syncobj_table_lock);
|
|
|
|
+ syncobj = idr_remove(&file_private->syncobj_idr, handle);
|
|
|
|
+ spin_unlock(&file_private->syncobj_table_lock);
|
|
|
|
+
|
|
|
|
+ if (!syncobj)
|
|
|
|
+ return -EINVAL;
|
|
|
|
+
|
|
|
|
+ drm_syncobj_put(syncobj);
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int drm_syncobj_file_release(struct inode *inode, struct file *file)
|
|
|
|
+{
|
|
|
|
+ struct drm_syncobj *syncobj = file->private_data;
|
|
|
|
+
|
|
|
|
+ drm_syncobj_put(syncobj);
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static const struct file_operations drm_syncobj_file_fops = {
|
|
|
|
+ .release = drm_syncobj_file_release,
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+static int drm_syncobj_alloc_file(struct drm_syncobj *syncobj)
|
|
|
|
+{
|
|
|
|
+ struct file *file = anon_inode_getfile("syncobj_file",
|
|
|
|
+ &drm_syncobj_file_fops,
|
|
|
|
+ syncobj, 0);
|
|
|
|
+ if (IS_ERR(file))
|
|
|
|
+ return PTR_ERR(file);
|
|
|
|
+
|
|
|
|
+ drm_syncobj_get(syncobj);
|
|
|
|
+ if (cmpxchg(&syncobj->file, NULL, file)) {
|
|
|
|
+ /* lost the race */
|
|
|
|
+ fput(file);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int drm_syncobj_handle_to_fd(struct drm_file *file_private,
|
|
|
|
+ u32 handle, int *p_fd)
|
|
|
|
+{
|
|
|
|
+ struct drm_syncobj *syncobj = drm_syncobj_find(file_private, handle);
|
|
|
|
+ int ret;
|
|
|
|
+ int fd;
|
|
|
|
+
|
|
|
|
+ if (!syncobj)
|
|
|
|
+ return -EINVAL;
|
|
|
|
+
|
|
|
|
+ fd = get_unused_fd_flags(O_CLOEXEC);
|
|
|
|
+ if (fd < 0) {
|
|
|
|
+ drm_syncobj_put(syncobj);
|
|
|
|
+ return fd;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (!syncobj->file) {
|
|
|
|
+ ret = drm_syncobj_alloc_file(syncobj);
|
|
|
|
+ if (ret)
|
|
|
|
+ goto out_put_fd;
|
|
|
|
+ }
|
|
|
|
+ fd_install(fd, syncobj->file);
|
|
|
|
+ drm_syncobj_put(syncobj);
|
|
|
|
+ *p_fd = fd;
|
|
|
|
+ return 0;
|
|
|
|
+out_put_fd:
|
|
|
|
+ put_unused_fd(fd);
|
|
|
|
+ drm_syncobj_put(syncobj);
|
|
|
|
+ return ret;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static struct drm_syncobj *drm_syncobj_fdget(int fd)
|
|
|
|
+{
|
|
|
|
+ struct file *file = fget(fd);
|
|
|
|
+
|
|
|
|
+ if (!file)
|
|
|
|
+ return NULL;
|
|
|
|
+ if (file->f_op != &drm_syncobj_file_fops)
|
|
|
|
+ goto err;
|
|
|
|
+
|
|
|
|
+ return file->private_data;
|
|
|
|
+err:
|
|
|
|
+ fput(file);
|
|
|
|
+ return NULL;
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+static int drm_syncobj_fd_to_handle(struct drm_file *file_private,
|
|
|
|
+ int fd, u32 *handle)
|
|
|
|
+{
|
|
|
|
+ struct drm_syncobj *syncobj = drm_syncobj_fdget(fd);
|
|
|
|
+ int ret;
|
|
|
|
+
|
|
|
|
+ if (!syncobj)
|
|
|
|
+ return -EINVAL;
|
|
|
|
+
|
|
|
|
+ /* take a reference to put in the idr */
|
|
|
|
+ drm_syncobj_get(syncobj);
|
|
|
|
+
|
|
|
|
+ idr_preload(GFP_KERNEL);
|
|
|
|
+ spin_lock(&file_private->syncobj_table_lock);
|
|
|
|
+ ret = idr_alloc(&file_private->syncobj_idr, syncobj, 1, 0, GFP_NOWAIT);
|
|
|
|
+ spin_unlock(&file_private->syncobj_table_lock);
|
|
|
|
+ idr_preload_end();
|
|
|
|
+
|
|
|
|
+ if (ret < 0) {
|
|
|
|
+ fput(syncobj->file);
|
|
|
|
+ return ret;
|
|
|
|
+ }
|
|
|
|
+ *handle = ret;
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * 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
|
|
|
|
+ * of sync objects.
|
|
|
|
+ */
|
|
|
|
+void
|
|
|
|
+drm_syncobj_open(struct drm_file *file_private)
|
|
|
|
+{
|
|
|
|
+ idr_init(&file_private->syncobj_idr);
|
|
|
|
+ spin_lock_init(&file_private->syncobj_table_lock);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int
|
|
|
|
+drm_syncobj_release_handle(int id, void *ptr, void *data)
|
|
|
|
+{
|
|
|
|
+ struct drm_syncobj *syncobj = ptr;
|
|
|
|
+
|
|
|
|
+ drm_syncobj_put(syncobj);
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * 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.
|
|
|
|
+ *
|
|
|
|
+ * Releases any remaining references on objects by this filp.
|
|
|
|
+ */
|
|
|
|
+void
|
|
|
|
+drm_syncobj_release(struct drm_file *file_private)
|
|
|
|
+{
|
|
|
|
+ idr_for_each(&file_private->syncobj_idr,
|
|
|
|
+ &drm_syncobj_release_handle, file_private);
|
|
|
|
+ idr_destroy(&file_private->syncobj_idr);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+int
|
|
|
|
+drm_syncobj_create_ioctl(struct drm_device *dev, void *data,
|
|
|
|
+ struct drm_file *file_private)
|
|
|
|
+{
|
|
|
|
+ struct drm_syncobj_create *args = data;
|
|
|
|
+
|
|
|
|
+ if (!drm_core_check_feature(dev, DRIVER_SYNCOBJ))
|
|
|
|
+ return -ENODEV;
|
|
|
|
+
|
|
|
|
+ /* no valid flags yet */
|
|
|
|
+ if (args->flags)
|
|
|
|
+ return -EINVAL;
|
|
|
|
+
|
|
|
|
+ return drm_syncobj_create(file_private,
|
|
|
|
+ &args->handle);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+int
|
|
|
|
+drm_syncobj_destroy_ioctl(struct drm_device *dev, void *data,
|
|
|
|
+ struct drm_file *file_private)
|
|
|
|
+{
|
|
|
|
+ struct drm_syncobj_destroy *args = data;
|
|
|
|
+
|
|
|
|
+ if (!drm_core_check_feature(dev, DRIVER_SYNCOBJ))
|
|
|
|
+ return -ENODEV;
|
|
|
|
+
|
|
|
|
+ /* make sure padding is empty */
|
|
|
|
+ if (args->pad)
|
|
|
|
+ return -EINVAL;
|
|
|
|
+ return drm_syncobj_destroy(file_private, args->handle);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+int
|
|
|
|
+drm_syncobj_handle_to_fd_ioctl(struct drm_device *dev, void *data,
|
|
|
|
+ struct drm_file *file_private)
|
|
|
|
+{
|
|
|
|
+ struct drm_syncobj_handle *args = data;
|
|
|
|
+
|
|
|
|
+ if (!drm_core_check_feature(dev, DRIVER_SYNCOBJ))
|
|
|
|
+ return -ENODEV;
|
|
|
|
+
|
|
|
|
+ if (args->pad || args->flags)
|
|
|
|
+ return -EINVAL;
|
|
|
|
+
|
|
|
|
+ return drm_syncobj_handle_to_fd(file_private, args->handle,
|
|
|
|
+ &args->fd);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+int
|
|
|
|
+drm_syncobj_fd_to_handle_ioctl(struct drm_device *dev, void *data,
|
|
|
|
+ struct drm_file *file_private)
|
|
|
|
+{
|
|
|
|
+ struct drm_syncobj_handle *args = data;
|
|
|
|
+
|
|
|
|
+ if (!drm_core_check_feature(dev, DRIVER_SYNCOBJ))
|
|
|
|
+ return -ENODEV;
|
|
|
|
+
|
|
|
|
+ if (args->pad || args->flags)
|
|
|
|
+ return -EINVAL;
|
|
|
|
+
|
|
|
|
+ return drm_syncobj_fd_to_handle(file_private, args->fd,
|
|
|
|
+ &args->handle);
|
|
|
|
+}
|