diff --git a/drivers/gpu/host1x-fence/dev.c b/drivers/gpu/host1x-fence/dev.c index ea26e97c..844f57a2 100644 --- a/drivers/gpu/host1x-fence/dev.c +++ b/drivers/gpu/host1x-fence/dev.c @@ -13,6 +13,8 @@ #include #include #include +#include +#include #include #include "include/uapi/linux/host1x-fence.h" @@ -170,6 +172,203 @@ put_fence: return err; } +struct host1x_pollfd { + struct kref ref; + struct mutex lock; + + struct dma_fence *fence; + wait_queue_head_t wq; + struct dma_fence_cb callback; + bool callback_set; +}; + +static int host1x_pollfd_release(struct inode *inode, struct file *file) +{ + struct host1x_pollfd *pollfd = file->private_data; + int err; + + mutex_lock(&pollfd->lock); + + if (pollfd->fence) { + if (pollfd->callback_set) { + err = dma_fence_remove_callback(pollfd->fence, &pollfd->callback); + if (err) { + /* callback could be executing concurrently */ + /* + * take fence lock. once we acquire the lock we know + * callbacks have finished. + */ + spin_lock_irq(pollfd->fence->lock); + spin_unlock_irq(pollfd->fence->lock); + } else { + host1x_fence_cancel(pollfd->fence); + } + } + dma_fence_put(pollfd->fence); + } + + mutex_unlock(&pollfd->lock); + + return 0; +} + +static unsigned int host1x_pollfd_poll(struct file *file, poll_table *wait) +{ + struct host1x_pollfd *pollfd = file->private_data; + unsigned int mask = 0; + + poll_wait(file, &pollfd->wq, wait); + + mutex_lock(&pollfd->lock); + + if (pollfd->fence && dma_fence_is_signaled(pollfd->fence)) { + mask = POLLPRI | POLLIN; + dma_fence_put(pollfd->fence); + pollfd->fence = NULL; + } + + mutex_unlock(&pollfd->lock); + + return mask; +} + +static const struct file_operations host1x_pollfd_ops = { + .release = host1x_pollfd_release, + .poll = host1x_pollfd_poll, +}; + +static int dev_file_ioctl_create_pollfd(struct host1x *host1x, void __user *data) +{ + struct host1x_create_pollfd args; + struct host1x_pollfd *pollfd; + unsigned long copy_err; + struct file *file; + int fd, err; + + copy_err = copy_from_user(&args, data, sizeof(args)); + if (copy_err) + return -EFAULT; + + pollfd = kzalloc(sizeof(*pollfd), GFP_KERNEL); + if (!pollfd) + return -ENOMEM; + + file = anon_inode_getfile("host1x_pollfd", &host1x_pollfd_ops, pollfd, 0); + if (IS_ERR(file)) { + err = PTR_ERR(file); + goto free_pollfd; + } + + init_waitqueue_head(&pollfd->wq); + mutex_init(&pollfd->lock); + kref_init(&pollfd->ref); + + fd = get_unused_fd_flags(O_CLOEXEC); + if (fd < 0) { + err = fd; + goto put_file; + } + + fd_install(fd, file); + + args.fd = fd; + + copy_err = copy_to_user(data, &args, sizeof(args)); + if (copy_err) + return -EFAULT; + + return 0; + +put_file: + fput(file); +free_pollfd: + kfree(pollfd); + + return err; +} + +static void host1x_pollfd_callback(struct dma_fence *fence, struct dma_fence_cb *cb) +{ + struct host1x_pollfd *pollfd = container_of(cb, struct host1x_pollfd, callback); + + wake_up_all(&pollfd->wq); +} + +static int dev_file_ioctl_trigger_pollfd(struct host1x *host1x, void __user *data) +{ + struct host1x_trigger_pollfd args; + struct host1x_syncpt *syncpt; + struct host1x_pollfd *pollfd; + struct dma_fence *fence; + unsigned long copy_err; + struct file *file; + int err; + + copy_err = copy_from_user(&args, data, sizeof(args)); + if (copy_err) + return -EFAULT; + + file = fget(args.fd); + if (!file) + return -EINVAL; + + if (file->f_op != &host1x_pollfd_ops) { + err = -EINVAL; + goto put_file; + } + + pollfd = file->private_data; + + syncpt = host1x_syncpt_get_by_id_noref(host1x, args.id); + if (!syncpt) { + err = -EINVAL; + goto put_file; + } + + mutex_lock(&pollfd->lock); + + if (pollfd->fence) { + mutex_unlock(&pollfd->lock); + err = -EBUSY; + goto put_file; + } + + fence = host1x_fence_create(syncpt, args.threshold, false); + if (IS_ERR(fence)) { + mutex_unlock(&pollfd->lock); + err = PTR_ERR(fence); + goto put_file; + } + + pollfd->fence = fence; + + pollfd->callback_set = false; + err = dma_fence_add_callback(fence, &pollfd->callback, host1x_pollfd_callback); + if (err == -ENOENT) { + /* + * We don't free the fence here -- it will be done from the poll + * handler. This way the logic is same whether the through callback + * or this shortcut. + */ + wake_up_all(&pollfd->wq); + } else if (err != 0) { + mutex_unlock(&pollfd->lock); + goto put_fence; + } + pollfd->callback_set = true; + + mutex_unlock(&pollfd->lock); + + return 0; + +put_fence: + dma_fence_put(fence); +put_file: + fput(file); + + return err; +} + static long dev_file_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { @@ -181,6 +380,14 @@ static long dev_file_ioctl(struct file *file, unsigned int cmd, err = dev_file_ioctl_create_fence(file->private_data, data); break; + case HOST1X_IOCTL_CREATE_POLLFD: + err = dev_file_ioctl_create_pollfd(file->private_data, data); + break; + + case HOST1X_IOCTL_TRIGGER_POLLFD: + err = dev_file_ioctl_trigger_pollfd(file->private_data, data); + break; + case HOST1X_IOCTL_FENCE_EXTRACT: err = dev_file_ioctl_fence_extract(file->private_data, data); break; diff --git a/drivers/gpu/host1x-fence/include/uapi/linux/host1x-fence.h b/drivers/gpu/host1x-fence/include/uapi/linux/host1x-fence.h index ce68c022..ad01bc4a 100644 --- a/drivers/gpu/host1x-fence/include/uapi/linux/host1x-fence.h +++ b/drivers/gpu/host1x-fence/include/uapi/linux/host1x-fence.h @@ -72,8 +72,22 @@ struct host1x_fence_extract { __u32 reserved[2]; }; +struct host1x_create_pollfd { + __s32 fd; + __u32 reserved; +}; + +struct host1x_trigger_pollfd { + __s32 fd; + __u32 id; + __u32 threshold; + __u32 reserved; +}; + #define HOST1X_IOCTL_CREATE_FENCE _IOWR('X', 0x02, struct host1x_create_fence) #define HOST1X_IOCTL_FENCE_EXTRACT _IOWR('X', 0x05, struct host1x_fence_extract) +#define HOST1X_IOCTL_CREATE_POLLFD _IOWR('X', 0x10, struct host1x_create_pollfd) +#define HOST1X_IOCTL_TRIGGER_POLLFD _IOWR('X', 0x11, struct host1x_trigger_pollfd) #if defined(__cplusplus) }