Skip to content

Commit

Permalink
winesync: Introduce WINESYNC_IOC_WAIT_ANY
Browse files Browse the repository at this point in the history
  • Loading branch information
Zebediah Figura authored and xanmod committed May 30, 2022
1 parent d98d162 commit bb79698
Show file tree
Hide file tree
Showing 2 changed files with 237 additions and 0 deletions.
226 changes: 226 additions & 0 deletions drivers/misc/winesync.c
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ struct winesync_obj {
struct kref refcount;
spinlock_t lock;

struct list_head any_waiters;

enum winesync_type type;

/* The following fields are protected by the object lock. */
Expand All @@ -34,6 +36,28 @@ struct winesync_obj {
} u;
};

struct winesync_q_entry {
struct list_head node;
struct winesync_q *q;
struct winesync_obj *obj;
__u32 index;
};

struct winesync_q {
struct task_struct *task;
__u32 owner;

/*
* Protected via atomic_cmpxchg(). Only the thread that wins the
* compare-and-swap may actually change object states and wake this
* task.
*/
atomic_t signaled;

__u32 count;
struct winesync_q_entry entries[];
};

struct winesync_device {
struct xarray objects;
};
Expand Down Expand Up @@ -109,6 +133,26 @@ static void init_obj(struct winesync_obj *obj)
{
kref_init(&obj->refcount);
spin_lock_init(&obj->lock);
INIT_LIST_HEAD(&obj->any_waiters);
}

static void try_wake_any_sem(struct winesync_obj *sem)
{
struct winesync_q_entry *entry;

lockdep_assert_held(&sem->lock);

list_for_each_entry(entry, &sem->any_waiters, node) {
struct winesync_q *q = entry->q;

if (!sem->u.sem.count)
break;

if (atomic_cmpxchg(&q->signaled, -1, entry->index) == -1) {
sem->u.sem.count--;
wake_up_process(q->task);
}
}
}

static int winesync_create_sem(struct winesync_device *dev, void __user *argp)
Expand Down Expand Up @@ -194,6 +238,8 @@ static int winesync_put_sem(struct winesync_device *dev, void __user *argp)

prev_count = sem->u.sem.count;
ret = put_sem_state(sem, args.count);
if (!ret)
try_wake_any_sem(sem);

spin_unlock(&sem->lock);

Expand All @@ -205,6 +251,184 @@ static int winesync_put_sem(struct winesync_device *dev, void __user *argp)
return ret;
}

static int winesync_schedule(const struct winesync_q *q, ktime_t *timeout)
{
int ret = 0;

do {
if (signal_pending(current)) {
ret = -ERESTARTSYS;
break;
}

set_current_state(TASK_INTERRUPTIBLE);
if (atomic_read(&q->signaled) != -1) {
ret = 0;
break;
}
ret = schedule_hrtimeout(timeout, HRTIMER_MODE_ABS);
} while (ret < 0);
__set_current_state(TASK_RUNNING);

return ret;
}

/*
* Allocate and initialize the winesync_q structure, but do not queue us yet.
* Also, calculate the relative timeout.
*/
static int setup_wait(struct winesync_device *dev,
const struct winesync_wait_args *args,
ktime_t *ret_timeout, struct winesync_q **ret_q)
{
const __u32 count = args->count;
struct winesync_q *q;
ktime_t timeout = 0;
__u32 *ids;
__u32 i, j;

if (!args->owner || args->pad)
return -EINVAL;

if (args->timeout) {
struct timespec64 to;

if (get_timespec64(&to, u64_to_user_ptr(args->timeout)))
return -EFAULT;
if (!timespec64_valid(&to))
return -EINVAL;

timeout = timespec64_to_ns(&to);
}

ids = kmalloc_array(count, sizeof(*ids), GFP_KERNEL);
if (!ids)
return -ENOMEM;
if (copy_from_user(ids, u64_to_user_ptr(args->objs),
array_size(count, sizeof(*ids)))) {
kfree(ids);
return -EFAULT;
}

q = kmalloc(struct_size(q, entries, count), GFP_KERNEL);
if (!q) {
kfree(ids);
return -ENOMEM;
}
q->task = current;
q->owner = args->owner;
atomic_set(&q->signaled, -1);
q->count = count;

for (i = 0; i < count; i++) {
struct winesync_q_entry *entry = &q->entries[i];
struct winesync_obj *obj = get_obj(dev, ids[i]);

if (!obj)
goto err;

entry->obj = obj;
entry->q = q;
entry->index = i;
}

kfree(ids);

*ret_q = q;
*ret_timeout = timeout;
return 0;

err:
for (j = 0; j < i; j++)
put_obj(q->entries[j].obj);
kfree(ids);
kfree(q);
return -EINVAL;
}

static void try_wake_any_obj(struct winesync_obj *obj)
{
switch (obj->type) {
case WINESYNC_TYPE_SEM:
try_wake_any_sem(obj);
break;
}
}

static int winesync_wait_any(struct winesync_device *dev, void __user *argp)
{
struct winesync_wait_args args;
struct winesync_q *q;
ktime_t timeout;
int signaled;
__u32 i;
int ret;

if (copy_from_user(&args, argp, sizeof(args)))
return -EFAULT;

ret = setup_wait(dev, &args, &timeout, &q);
if (ret < 0)
return ret;

/* queue ourselves */

for (i = 0; i < args.count; i++) {
struct winesync_q_entry *entry = &q->entries[i];
struct winesync_obj *obj = entry->obj;

spin_lock(&obj->lock);
list_add_tail(&entry->node, &obj->any_waiters);
spin_unlock(&obj->lock);
}

/* check if we are already signaled */

for (i = 0; i < args.count; i++) {
struct winesync_obj *obj = q->entries[i].obj;

if (atomic_read(&q->signaled) != -1)
break;

spin_lock(&obj->lock);
try_wake_any_obj(obj);
spin_unlock(&obj->lock);
}

/* sleep */

ret = winesync_schedule(q, args.timeout ? &timeout : NULL);

/* and finally, unqueue */

for (i = 0; i < args.count; i++) {
struct winesync_q_entry *entry = &q->entries[i];
struct winesync_obj *obj = entry->obj;

spin_lock(&obj->lock);
list_del(&entry->node);
spin_unlock(&obj->lock);

put_obj(obj);
}

signaled = atomic_read(&q->signaled);
if (signaled != -1) {
struct winesync_wait_args __user *user_args = argp;

/* even if we caught a signal, we need to communicate success */
ret = 0;

if (put_user(signaled, &user_args->index))
ret = -EFAULT;
} else if (!ret) {
ret = -ETIMEDOUT;
}

kfree(q);
return ret;
}

static long winesync_char_ioctl(struct file *file, unsigned int cmd,
unsigned long parm)
{
Expand All @@ -218,6 +442,8 @@ static long winesync_char_ioctl(struct file *file, unsigned int cmd,
return winesync_delete(dev, argp);
case WINESYNC_IOC_PUT_SEM:
return winesync_put_sem(dev, argp);
case WINESYNC_IOC_WAIT_ANY:
return winesync_wait_any(dev, argp);
default:
return -ENOSYS;
}
Expand Down
11 changes: 11 additions & 0 deletions include/uapi/linux/winesync.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,23 @@ struct winesync_sem_args {
__u32 max;
};

struct winesync_wait_args {
__u64 timeout;
__u64 objs;
__u32 count;
__u32 owner;
__u32 index;
__u32 pad;
};

#define WINESYNC_IOC_BASE 0xf7

#define WINESYNC_IOC_CREATE_SEM _IOWR(WINESYNC_IOC_BASE, 0, \
struct winesync_sem_args)
#define WINESYNC_IOC_DELETE _IOW (WINESYNC_IOC_BASE, 1, __u32)
#define WINESYNC_IOC_PUT_SEM _IOWR(WINESYNC_IOC_BASE, 2, \
struct winesync_sem_args)
#define WINESYNC_IOC_WAIT_ANY _IOWR(WINESYNC_IOC_BASE, 3, \
struct winesync_wait_args)

#endif

0 comments on commit bb79698

Please sign in to comment.