blob: d8c8db61d00f4910e6990c96e6fb1d284c874196 [file] [log] [blame]
/* 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(&param, (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, &param, 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");