| /* Copyright (c) 2014-2016, The Linux Foundation. All rights reserved. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 and |
| * only version 2 as published by the Free Software Foundation. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| */ |
| #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
| |
| #include <linux/kernel.h> |
| #include <linux/file.h> |
| #include <linux/fs.h> |
| #include <linux/miscdevice.h> |
| #include <linux/module.h> |
| #include <linux/syscalls.h> |
| #include <linux/uaccess.h> |
| #include <linux/slab.h> |
| #include <linux/list.h> |
| #include <linux/mutex.h> |
| #include <linux/sync.h> |
| #include <linux/oneshot_sync.h> |
| |
| /** |
| * struct oneshot_sync_timeline - a userspace signaled, out of order, timeline |
| * @obj: base sync timeline |
| * @lock: spinlock to guard other members |
| * @state_list: list of oneshot_sync_states. |
| * @id: next id for points creating oneshot_sync_pts |
| */ |
| struct oneshot_sync_timeline { |
| struct sync_timeline obj; |
| spinlock_t lock; |
| struct list_head state_list; |
| unsigned int id; |
| }; |
| |
| #define to_oneshot_timeline(_p) \ |
| container_of((_p), struct oneshot_sync_timeline, obj) |
| |
| /** |
| * struct oneshot_sync_state - signal state for a group of oneshot points |
| * @refcount: reference count for this structure. |
| * @signaled: is this signaled or not? |
| * @id: identifier for this state |
| * @orig_fence: fence used to create this state, no is reference count held. |
| * @timeline: back pointer to the timeline. |
| */ |
| struct oneshot_sync_state { |
| struct kref refcount; |
| struct list_head node; |
| bool signaled; |
| unsigned int id; |
| struct sync_fence *orig_fence; |
| struct oneshot_sync_timeline *timeline; |
| }; |
| |
| /** |
| * struct oneshot_sync_pt |
| * @sync_pt: base sync point structure |
| * @state: reference counted pointer to the state of this pt |
| */ |
| struct oneshot_sync_pt { |
| struct sync_pt sync_pt; |
| struct oneshot_sync_state *state; |
| bool dup; |
| }; |
| #define to_oneshot_pt(_p) container_of((_p), struct oneshot_sync_pt, sync_pt) |
| |
| static void oneshot_state_destroy(struct kref *ref) |
| { |
| struct oneshot_sync_state *state = |
| container_of(ref, struct oneshot_sync_state, refcount); |
| |
| spin_lock(&state->timeline->lock); |
| list_del(&state->node); |
| spin_unlock(&state->timeline->lock); |
| |
| kfree(state); |
| } |
| |
| static void oneshot_state_put(struct oneshot_sync_state *state) |
| { |
| kref_put(&state->refcount, oneshot_state_destroy); |
| } |
| |
| static struct oneshot_sync_pt * |
| oneshot_pt_create(struct oneshot_sync_timeline *timeline) |
| { |
| struct oneshot_sync_pt *pt = NULL; |
| |
| pt = (struct oneshot_sync_pt *)sync_pt_create(&timeline->obj, |
| sizeof(*pt)); |
| if (pt == NULL) |
| return NULL; |
| |
| pt->state = kzalloc(sizeof(struct oneshot_sync_state), GFP_KERNEL); |
| if (pt->state == NULL) |
| goto error; |
| |
| kref_init(&pt->state->refcount); |
| pt->state->signaled = false; |
| pt->state->timeline = timeline; |
| |
| spin_lock(&timeline->lock); |
| /* assign an id to the state, which could be shared by several pts. */ |
| pt->state->id = ++(timeline->id); |
| /* add this pt to the list of pts that can be signaled by userspace */ |
| list_add_tail(&pt->state->node, &timeline->state_list); |
| spin_unlock(&timeline->lock); |
| |
| return pt; |
| error: |
| if (pt) |
| sync_pt_free(&pt->sync_pt); |
| return NULL; |
| } |
| |
| static struct sync_pt *oneshot_pt_dup(struct sync_pt *sync_pt) |
| { |
| struct oneshot_sync_pt *out_pt; |
| struct oneshot_sync_pt *pt = to_oneshot_pt(sync_pt); |
| |
| if (!kref_get_unless_zero(&pt->state->refcount)) |
| return NULL; |
| |
| out_pt = (struct oneshot_sync_pt *) |
| sync_pt_create(sync_pt->parent, sizeof(*out_pt)); |
| |
| if (out_pt == NULL) { |
| oneshot_state_put(pt->state); |
| return NULL; |
| } |
| out_pt->state = pt->state; |
| out_pt->dup = true; |
| |
| return &out_pt->sync_pt; |
| } |
| |
| static int oneshot_pt_has_signaled(struct sync_pt *sync_pt) |
| { |
| struct oneshot_sync_pt *pt = to_oneshot_pt(sync_pt); |
| |
| return pt->state->signaled; |
| } |
| |
| static int oneshot_pt_compare(struct sync_pt *a, struct sync_pt *b) |
| { |
| struct oneshot_sync_pt *pt_a = to_oneshot_pt(a); |
| struct oneshot_sync_pt *pt_b = to_oneshot_pt(b); |
| /* |
| * since oneshot sync points are order-independent, |
| * return an arbitrary order which just happens to |
| * prevent sync.c from collapsing the points. |
| */ |
| return (pt_a->state == pt_b->state) ? 0 : 1; |
| } |
| |
| static void oneshot_pt_free(struct sync_pt *sync_pt) |
| { |
| struct oneshot_sync_pt *pt = to_oneshot_pt(sync_pt); |
| |
| struct oneshot_sync_timeline *timeline = sync_pt->parent ? |
| to_oneshot_timeline(sync_pt->parent) : NULL; |
| |
| if (timeline != NULL) { |
| spin_lock(&timeline->lock); |
| /* |
| * If this is the original pt (and fence), signal to avoid |
| * deadlock. Unfornately, we can't signal the timeline here |
| * safely, so there could be a delay until the pt's |
| * state change is noticed. |
| */ |
| |
| if (pt->dup == false) { |
| /* |
| * If the original pt goes away, force it signaled to |
| * avoid deadlock. |
| */ |
| if (!pt->state->signaled) { |
| pr_debug("id %d: fence closed before signal.\n", |
| pt->state->id); |
| pt->state->signaled = true; |
| } |
| } |
| spin_unlock(&timeline->lock); |
| } |
| oneshot_state_put(pt->state); |
| } |
| |
| static void oneshot_pt_value_str(struct sync_pt *sync_pt, char *str, int size) |
| { |
| struct oneshot_sync_pt *pt = to_oneshot_pt(sync_pt); |
| |
| snprintf(str, size, "%u", pt->state->id); |
| } |
| |
| static struct sync_timeline_ops oneshot_timeline_ops = { |
| .driver_name = "oneshot", |
| .dup = oneshot_pt_dup, |
| .has_signaled = oneshot_pt_has_signaled, |
| .compare = oneshot_pt_compare, |
| .free_pt = oneshot_pt_free, |
| .pt_value_str = oneshot_pt_value_str, |
| }; |
| |
| struct oneshot_sync_timeline *oneshot_timeline_create(const char *name) |
| { |
| struct oneshot_sync_timeline *timeline = NULL; |
| static const char *default_name = "oneshot-timeline"; |
| |
| if (name == NULL) |
| name = default_name; |
| |
| timeline = (struct oneshot_sync_timeline *) |
| sync_timeline_create(&oneshot_timeline_ops, |
| sizeof(*timeline), |
| name); |
| |
| if (timeline == NULL) |
| return NULL; |
| |
| INIT_LIST_HEAD(&timeline->state_list); |
| spin_lock_init(&timeline->lock); |
| |
| return timeline; |
| } |
| EXPORT_SYMBOL(oneshot_timeline_create); |
| |
| void oneshot_timeline_destroy(struct oneshot_sync_timeline *timeline) |
| { |
| if (timeline) |
| sync_timeline_destroy(&timeline->obj); |
| } |
| EXPORT_SYMBOL(oneshot_timeline_destroy); |
| |
| struct sync_fence *oneshot_fence_create(struct oneshot_sync_timeline *timeline, |
| const char *name) |
| { |
| struct sync_fence *fence = NULL; |
| struct oneshot_sync_pt *pt = NULL; |
| |
| pt = oneshot_pt_create(timeline); |
| if (pt == NULL) |
| return NULL; |
| |
| fence = sync_fence_create(name, &pt->sync_pt); |
| if (fence == NULL) { |
| sync_pt_free(&pt->sync_pt); |
| return NULL; |
| } |
| |
| pt->state->orig_fence = fence; |
| |
| return fence; |
| } |
| EXPORT_SYMBOL(oneshot_fence_create); |
| |
| int oneshot_fence_signal(struct oneshot_sync_timeline *timeline, |
| struct sync_fence *fence) |
| { |
| int ret = -EINVAL; |
| struct oneshot_sync_state *state = NULL; |
| bool signaled = false; |
| |
| if (timeline == NULL || fence == NULL) |
| return -EINVAL; |
| |
| spin_lock(&timeline->lock); |
| list_for_each_entry(state, &timeline->state_list, node) { |
| /* |
| * If we have the point from this fence on our list, |
| * this is is the original fence we created, so signal it. |
| */ |
| if (state->orig_fence == fence) { |
| /* ignore attempts to signal multiple times */ |
| if (!state->signaled) { |
| state->signaled = true; |
| signaled = true; |
| } |
| ret = 0; |
| break; |
| } |
| } |
| spin_unlock(&timeline->lock); |
| if (ret == -EINVAL) |
| pr_debug("fence: %pK not from this timeline\n", fence); |
| |
| if (signaled) |
| sync_timeline_signal(&timeline->obj); |
| return ret; |
| } |
| EXPORT_SYMBOL(oneshot_fence_signal); |
| |
| #ifdef CONFIG_ONESHOT_SYNC_USER |
| |
| static int oneshot_open(struct inode *inode, struct file *file) |
| { |
| struct oneshot_sync_timeline *timeline = NULL; |
| char name[32]; |
| char task_comm[TASK_COMM_LEN]; |
| |
| get_task_comm(task_comm, current); |
| snprintf(name, sizeof(name), "%s-oneshot", task_comm); |
| |
| timeline = oneshot_timeline_create(name); |
| if (timeline == NULL) |
| return -ENOMEM; |
| |
| file->private_data = timeline; |
| return 0; |
| } |
| |
| static int oneshot_release(struct inode *inode, struct file *file) |
| { |
| struct oneshot_sync_timeline *timeline = file->private_data; |
| |
| oneshot_timeline_destroy(timeline); |
| |
| return 0; |
| } |
| |
| static long oneshot_ioctl_fence_create(struct oneshot_sync_timeline *timeline, |
| unsigned long arg) |
| { |
| struct oneshot_sync_create_fence param; |
| int ret = -ENOMEM; |
| struct sync_fence *fence = NULL; |
| int fd = get_unused_fd(); |
| |
| if (fd < 0) |
| return fd; |
| |
| if (copy_from_user(¶m, (void __user *)arg, sizeof(param))) { |
| ret = -EFAULT; |
| goto out; |
| } |
| |
| fence = oneshot_fence_create(timeline, param.name); |
| if (fence == NULL) { |
| ret = -ENOMEM; |
| goto out; |
| } |
| |
| param.fence_fd = fd; |
| |
| if (copy_to_user((void __user *)arg, ¶m, sizeof(param))) { |
| ret = -EFAULT; |
| goto out; |
| } |
| |
| sync_fence_install(fence, fd); |
| ret = 0; |
| out: |
| if (ret) { |
| if (fence) |
| sync_fence_put(fence); |
| put_unused_fd(fd); |
| } |
| return ret; |
| } |
| |
| |
| |
| static long oneshot_ioctl_fence_signal(struct oneshot_sync_timeline *timeline, |
| unsigned long arg) |
| { |
| int ret = -EINVAL; |
| int fd = -1; |
| struct sync_fence *fence = NULL; |
| |
| if (get_user(fd, (int __user *)arg)) |
| return -EFAULT; |
| |
| fence = sync_fence_fdget(fd); |
| if (fence == NULL) |
| return -EBADF; |
| |
| ret = oneshot_fence_signal(timeline, fence); |
| sync_fence_put(fence); |
| |
| return ret; |
| } |
| |
| static long oneshot_ioctl(struct file *file, unsigned int cmd, |
| unsigned long arg) |
| { |
| struct oneshot_sync_timeline *timeline = file->private_data; |
| |
| switch (cmd) { |
| case ONESHOT_SYNC_IOC_CREATE_FENCE: |
| return oneshot_ioctl_fence_create(timeline, arg); |
| |
| case ONESHOT_SYNC_IOC_SIGNAL_FENCE: |
| return oneshot_ioctl_fence_signal(timeline, arg); |
| |
| default: |
| return -ENOTTY; |
| } |
| } |
| |
| static const struct file_operations oneshot_fops = { |
| .owner = THIS_MODULE, |
| .open = oneshot_open, |
| .release = oneshot_release, |
| .unlocked_ioctl = oneshot_ioctl, |
| .compat_ioctl = oneshot_ioctl, |
| }; |
| static struct miscdevice oneshot_dev = { |
| .minor = MISC_DYNAMIC_MINOR, |
| .name = "oneshot_sync", |
| .fops = &oneshot_fops, |
| }; |
| |
| static int __init oneshot_init(void) |
| { |
| return misc_register(&oneshot_dev); |
| } |
| |
| static void __exit oneshot_remove(void) |
| { |
| misc_deregister(&oneshot_dev); |
| } |
| |
| module_init(oneshot_init); |
| module_exit(oneshot_remove); |
| |
| #endif /* CONFIG_ONESHOT_SYNC_USER */ |
| MODULE_LICENSE("GPL v2"); |
| |