433 lines
10 KiB
C
433 lines
10 KiB
C
/* 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;
|
|
};
|
|
#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;
|
|
|
|
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->state->orig_fence == sync_pt->fence) {
|
|
|
|
/*
|
|
* 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;
|
|
}
|
|
/* clear the pointer, since it will be freed soon */
|
|
pt->state->orig_fence = NULL;
|
|
}
|
|
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");
|
|
|