diff options
Diffstat (limited to 'SOURCES/0001-ntsync.patch')
-rw-r--r-- | SOURCES/0001-ntsync.patch | 4995 |
1 files changed, 4995 insertions, 0 deletions
diff --git a/SOURCES/0001-ntsync.patch b/SOURCES/0001-ntsync.patch new file mode 100644 index 0000000..34a383a --- /dev/null +++ b/SOURCES/0001-ntsync.patch @@ -0,0 +1,4995 @@ +From 34529eb8302090ff79e04d95a6bb7f5fd48cecb6 Mon Sep 17 00:00:00 2001 +From: Elizabeth Figura <zfigura@codeweavers.com> +Date: Fri, 5 Mar 2021 10:50:45 -0600 +Subject: [PATCH 01/32] ntsync: Introduce the ntsync driver and character + device. + +ntsync uses a misc device as the simplest and least intrusive uAPI interface. + +Each file description on the device represents an isolated NT instance, intended +to correspond to a single NT virtual machine. +--- + drivers/misc/Kconfig | 9 ++++++++ + drivers/misc/Makefile | 1 + + drivers/misc/ntsync.c | 53 +++++++++++++++++++++++++++++++++++++++++++ + 3 files changed, 63 insertions(+) + create mode 100644 drivers/misc/ntsync.c + +diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig +index f37c4b8380ae..622eb26ac040 100644 +--- a/drivers/misc/Kconfig ++++ b/drivers/misc/Kconfig +@@ -504,6 +504,15 @@ config OPEN_DICE + measured boot flow. Userspace can use CDIs for remote attestation + and sealing. + ++config NTSYNC ++ tristate "NT synchronization primitive emulation" ++ help ++ This module provides kernel support for emulation of Windows NT ++ synchronization primitives. It is not a hardware driver. ++ ++ To compile this driver as a module, choose M here: the ++ module will be called ntsync. ++ + If unsure, say N. + + config VCPU_STALL_DETECTOR +diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile +index f2a4d1ff65d4..bd12e9a3b8c5 100644 +--- a/drivers/misc/Makefile ++++ b/drivers/misc/Makefile +@@ -59,6 +59,7 @@ obj-$(CONFIG_PVPANIC) += pvpanic/ + obj-$(CONFIG_UACCE) += uacce/ + obj-$(CONFIG_XILINX_SDFEC) += xilinx_sdfec.o + obj-$(CONFIG_HISI_HIKEY_USB) += hisi_hikey_usb.o ++obj-$(CONFIG_NTSYNC) += ntsync.o + obj-$(CONFIG_HI6421V600_IRQ) += hi6421v600-irq.o + obj-$(CONFIG_OPEN_DICE) += open-dice.o + obj-$(CONFIG_GP_PCI1XXXX) += mchp_pci1xxxx/ +diff --git a/drivers/misc/ntsync.c b/drivers/misc/ntsync.c +new file mode 100644 +index 000000000000..9424c6210e51 +--- /dev/null ++++ b/drivers/misc/ntsync.c +@@ -0,0 +1,53 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++/* ++ * ntsync.c - Kernel driver for NT synchronization primitives ++ * ++ * Copyright (C) 2021-2022 Elizabeth Figura ++ */ ++ ++#include <linux/fs.h> ++#include <linux/miscdevice.h> ++#include <linux/module.h> ++ ++#define NTSYNC_NAME "ntsync" ++ ++static int ntsync_char_open(struct inode *inode, struct file *file) ++{ ++ return nonseekable_open(inode, file); ++} ++ ++static int ntsync_char_release(struct inode *inode, struct file *file) ++{ ++ return 0; ++} ++ ++static long ntsync_char_ioctl(struct file *file, unsigned int cmd, ++ unsigned long parm) ++{ ++ switch (cmd) { ++ default: ++ return -ENOIOCTLCMD; ++ } ++} ++ ++static const struct file_operations ntsync_fops = { ++ .owner = THIS_MODULE, ++ .open = ntsync_char_open, ++ .release = ntsync_char_release, ++ .unlocked_ioctl = ntsync_char_ioctl, ++ .compat_ioctl = ntsync_char_ioctl, ++ .llseek = no_llseek, ++}; ++ ++static struct miscdevice ntsync_misc = { ++ .minor = MISC_DYNAMIC_MINOR, ++ .name = NTSYNC_NAME, ++ .fops = &ntsync_fops, ++}; ++ ++module_misc_device(ntsync_misc); ++ ++MODULE_AUTHOR("Elizabeth Figura"); ++MODULE_DESCRIPTION("Kernel driver for NT synchronization primitives"); ++MODULE_LICENSE("GPL"); ++MODULE_ALIAS("devname:" NTSYNC_NAME); +-- +2.43.0 + +From a375bffd8ab8d06b81e4b83c9f5577fdc97246f9 Mon Sep 17 00:00:00 2001 +From: Elizabeth Figura <zfigura@codeweavers.com> +Date: Fri, 5 Mar 2021 10:57:06 -0600 +Subject: [PATCH 02/32] ntsync: Reserve a minor device number and ioctl range. + +--- + Documentation/admin-guide/devices.txt | 3 ++- + Documentation/userspace-api/ioctl/ioctl-number.rst | 2 ++ + drivers/misc/ntsync.c | 3 ++- + include/linux/miscdevice.h | 1 + + 4 files changed, 7 insertions(+), 2 deletions(-) + +diff --git a/Documentation/admin-guide/devices.txt b/Documentation/admin-guide/devices.txt +index 839054923530..13841636ce92 100644 +--- a/Documentation/admin-guide/devices.txt ++++ b/Documentation/admin-guide/devices.txt +@@ -376,8 +376,9 @@ + 240 = /dev/userio Serio driver testing device + 241 = /dev/vhost-vsock Host kernel driver for virtio vsock + 242 = /dev/rfkill Turning off radio transmissions (rfkill) ++ 243 = /dev/ntsync NT synchronization primitive device + +- 243-254 Reserved for local use ++ 244-254 Reserved for local use + 255 Reserved for MISC_DYNAMIC_MINOR + + 11 char Raw keyboard device (Linux/SPARC only) +diff --git a/Documentation/userspace-api/ioctl/ioctl-number.rst b/Documentation/userspace-api/ioctl/ioctl-number.rst +index 4ea5b837399a..055482769b35 100644 +--- a/Documentation/userspace-api/ioctl/ioctl-number.rst ++++ b/Documentation/userspace-api/ioctl/ioctl-number.rst +@@ -375,6 +375,8 @@ Code Seq# Include File Comments + <mailto:thomas@winischhofer.net> + 0xF6 all LTTng Linux Trace Toolkit Next Generation + <mailto:mathieu.desnoyers@efficios.com> ++0xF7 00-1F uapi/linux/ntsync.h NT synchronization primitives ++ <mailto:wine-devel@winehq.org> + 0xF8 all arch/x86/include/uapi/asm/amd_hsmp.h AMD HSMP EPYC system management interface driver + <mailto:nchatrad@amd.com> + 0xFD all linux/dm-ioctl.h +diff --git a/drivers/misc/ntsync.c b/drivers/misc/ntsync.c +index 9424c6210e51..84b498e2b2d5 100644 +--- a/drivers/misc/ntsync.c ++++ b/drivers/misc/ntsync.c +@@ -40,7 +40,7 @@ static const struct file_operations ntsync_fops = { + }; + + static struct miscdevice ntsync_misc = { +- .minor = MISC_DYNAMIC_MINOR, ++ .minor = NTSYNC_MINOR, + .name = NTSYNC_NAME, + .fops = &ntsync_fops, + }; +@@ -51,3 +51,4 @@ MODULE_AUTHOR("Elizabeth Figura"); + MODULE_DESCRIPTION("Kernel driver for NT synchronization primitives"); + MODULE_LICENSE("GPL"); + MODULE_ALIAS("devname:" NTSYNC_NAME); ++MODULE_ALIAS_MISCDEV(NTSYNC_MINOR); +diff --git a/include/linux/miscdevice.h b/include/linux/miscdevice.h +index c0fea6ca5076..fe5d9366fdf7 100644 +--- a/include/linux/miscdevice.h ++++ b/include/linux/miscdevice.h +@@ -71,6 +71,7 @@ + #define USERIO_MINOR 240 + #define VHOST_VSOCK_MINOR 241 + #define RFKILL_MINOR 242 ++#define NTSYNC_MINOR 243 + #define MISC_DYNAMIC_MINOR 255 + + struct device; +-- +2.43.0 + +From a5f6ffc3056884b48bb9161bc1246d841d9ed961 Mon Sep 17 00:00:00 2001 +From: Elizabeth Figura <zfigura@codeweavers.com> +Date: Fri, 5 Mar 2021 11:15:39 -0600 +Subject: [PATCH 03/32] ntsync: Introduce NTSYNC_IOC_CREATE_SEM and + NTSYNC_IOC_DELETE. + +These correspond to the NT syscalls NtCreateSemaphore() and NtClose(). +Unlike those functions, however, these ioctls do not handle object names, or +lookup of existing objects, or handle reference counting, but simply create the +underlying primitive. The user space emulator is expected to implement those +functions if they are required. +--- + drivers/misc/ntsync.c | 117 ++++++++++++++++++++++++++++++++++++ + include/uapi/linux/ntsync.h | 25 ++++++++ + 2 files changed, 142 insertions(+) + create mode 100644 include/uapi/linux/ntsync.h + +diff --git a/drivers/misc/ntsync.c b/drivers/misc/ntsync.c +index 84b498e2b2d5..3287b94be351 100644 +--- a/drivers/misc/ntsync.c ++++ b/drivers/misc/ntsync.c +@@ -8,23 +8,140 @@ + #include <linux/fs.h> + #include <linux/miscdevice.h> + #include <linux/module.h> ++#include <linux/slab.h> ++#include <linux/xarray.h> ++#include <uapi/linux/ntsync.h> + + #define NTSYNC_NAME "ntsync" + ++enum ntsync_type { ++ NTSYNC_TYPE_SEM, ++}; ++ ++struct ntsync_obj { ++ struct rcu_head rhead; ++ struct kref refcount; ++ ++ enum ntsync_type type; ++ ++ union { ++ struct { ++ __u32 count; ++ __u32 max; ++ } sem; ++ } u; ++}; ++ ++struct ntsync_device { ++ struct xarray objects; ++}; ++ ++static void destroy_obj(struct kref *ref) ++{ ++ struct ntsync_obj *obj = container_of(ref, struct ntsync_obj, refcount); ++ ++ kfree_rcu(obj, rhead); ++} ++ ++static void put_obj(struct ntsync_obj *obj) ++{ ++ kref_put(&obj->refcount, destroy_obj); ++} ++ + static int ntsync_char_open(struct inode *inode, struct file *file) + { ++ struct ntsync_device *dev; ++ ++ dev = kzalloc(sizeof(*dev), GFP_KERNEL); ++ if (!dev) ++ return -ENOMEM; ++ ++ xa_init_flags(&dev->objects, XA_FLAGS_ALLOC); ++ ++ file->private_data = dev; + return nonseekable_open(inode, file); + } + + static int ntsync_char_release(struct inode *inode, struct file *file) + { ++ struct ntsync_device *dev = file->private_data; ++ struct ntsync_obj *obj; ++ unsigned long id; ++ ++ xa_for_each(&dev->objects, id, obj) ++ put_obj(obj); ++ ++ xa_destroy(&dev->objects); ++ ++ kfree(dev); ++ ++ return 0; ++} ++ ++static void init_obj(struct ntsync_obj *obj) ++{ ++ kref_init(&obj->refcount); ++} ++ ++static int ntsync_create_sem(struct ntsync_device *dev, void __user *argp) ++{ ++ struct ntsync_sem_args __user *user_args = argp; ++ struct ntsync_sem_args args; ++ struct ntsync_obj *sem; ++ __u32 id; ++ int ret; ++ ++ if (copy_from_user(&args, argp, sizeof(args))) ++ return -EFAULT; ++ ++ if (args.count > args.max) ++ return -EINVAL; ++ ++ sem = kzalloc(sizeof(*sem), GFP_KERNEL); ++ if (!sem) ++ return -ENOMEM; ++ ++ init_obj(sem); ++ sem->type = NTSYNC_TYPE_SEM; ++ sem->u.sem.count = args.count; ++ sem->u.sem.max = args.max; ++ ++ ret = xa_alloc(&dev->objects, &id, sem, xa_limit_32b, GFP_KERNEL); ++ if (ret < 0) { ++ kfree(sem); ++ return ret; ++ } ++ ++ return put_user(id, &user_args->sem); ++} ++ ++static int ntsync_delete(struct ntsync_device *dev, void __user *argp) ++{ ++ struct ntsync_obj *obj; ++ __u32 id; ++ ++ if (get_user(id, (__u32 __user *)argp)) ++ return -EFAULT; ++ ++ obj = xa_erase(&dev->objects, id); ++ if (!obj) ++ return -EINVAL; ++ ++ put_obj(obj); + return 0; + } + + static long ntsync_char_ioctl(struct file *file, unsigned int cmd, + unsigned long parm) + { ++ struct ntsync_device *dev = file->private_data; ++ void __user *argp = (void __user *)parm; ++ + switch (cmd) { ++ case NTSYNC_IOC_CREATE_SEM: ++ return ntsync_create_sem(dev, argp); ++ case NTSYNC_IOC_DELETE: ++ return ntsync_delete(dev, argp); + default: + return -ENOIOCTLCMD; + } +diff --git a/include/uapi/linux/ntsync.h b/include/uapi/linux/ntsync.h +new file mode 100644 +index 000000000000..d97afc138dcc +--- /dev/null ++++ b/include/uapi/linux/ntsync.h +@@ -0,0 +1,25 @@ ++/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ ++/* ++ * Kernel support for NT synchronization primitive emulation ++ * ++ * Copyright (C) 2021-2022 Elizabeth Figura ++ */ ++ ++#ifndef __LINUX_NTSYNC_H ++#define __LINUX_NTSYNC_H ++ ++#include <linux/types.h> ++ ++struct ntsync_sem_args { ++ __u32 sem; ++ __u32 count; ++ __u32 max; ++}; ++ ++#define NTSYNC_IOC_BASE 0xf7 ++ ++#define NTSYNC_IOC_CREATE_SEM _IOWR(NTSYNC_IOC_BASE, 0, \ ++ struct ntsync_sem_args) ++#define NTSYNC_IOC_DELETE _IOW (NTSYNC_IOC_BASE, 1, __u32) ++ ++#endif +-- +2.43.0 + +From 81b4a45e9f5ab9275e42e0edb6cbf6073c44d38e Mon Sep 17 00:00:00 2001 +From: Elizabeth Figura <zfigura@codeweavers.com> +Date: Fri, 5 Mar 2021 11:22:42 -0600 +Subject: [PATCH 04/32] ntsync: Introduce NTSYNC_IOC_PUT_SEM. + +This corresponds to the NT syscall NtReleaseSemaphore(). +--- + drivers/misc/ntsync.c | 76 +++++++++++++++++++++++++++++++++++++ + include/uapi/linux/ntsync.h | 2 + + 2 files changed, 78 insertions(+) + +diff --git a/drivers/misc/ntsync.c b/drivers/misc/ntsync.c +index 3287b94be351..d1c91c2a4f1a 100644 +--- a/drivers/misc/ntsync.c ++++ b/drivers/misc/ntsync.c +@@ -21,9 +21,11 @@ enum ntsync_type { + struct ntsync_obj { + struct rcu_head rhead; + struct kref refcount; ++ spinlock_t lock; + + enum ntsync_type type; + ++ /* The following fields are protected by the object lock. */ + union { + struct { + __u32 count; +@@ -36,6 +38,19 @@ struct ntsync_device { + struct xarray objects; + }; + ++static struct ntsync_obj *get_obj(struct ntsync_device *dev, __u32 id) ++{ ++ struct ntsync_obj *obj; ++ ++ rcu_read_lock(); ++ obj = xa_load(&dev->objects, id); ++ if (obj && !kref_get_unless_zero(&obj->refcount)) ++ obj = NULL; ++ rcu_read_unlock(); ++ ++ return obj; ++} ++ + static void destroy_obj(struct kref *ref) + { + struct ntsync_obj *obj = container_of(ref, struct ntsync_obj, refcount); +@@ -48,6 +63,18 @@ static void put_obj(struct ntsync_obj *obj) + kref_put(&obj->refcount, destroy_obj); + } + ++static struct ntsync_obj *get_obj_typed(struct ntsync_device *dev, __u32 id, ++ enum ntsync_type type) ++{ ++ struct ntsync_obj *obj = get_obj(dev, id); ++ ++ if (obj && obj->type != type) { ++ put_obj(obj); ++ return NULL; ++ } ++ return obj; ++} ++ + static int ntsync_char_open(struct inode *inode, struct file *file) + { + struct ntsync_device *dev; +@@ -81,6 +108,7 @@ static int ntsync_char_release(struct inode *inode, struct file *file) + static void init_obj(struct ntsync_obj *obj) + { + kref_init(&obj->refcount); ++ spin_lock_init(&obj->lock); + } + + static int ntsync_create_sem(struct ntsync_device *dev, void __user *argp) +@@ -131,6 +159,52 @@ static int ntsync_delete(struct ntsync_device *dev, void __user *argp) + return 0; + } + ++/* ++ * Actually change the semaphore state, returning -EOVERFLOW if it is made ++ * invalid. ++ */ ++static int put_sem_state(struct ntsync_obj *sem, __u32 count) ++{ ++ lockdep_assert_held(&sem->lock); ++ ++ if (sem->u.sem.count + count < sem->u.sem.count || ++ sem->u.sem.count + count > sem->u.sem.max) ++ return -EOVERFLOW; ++ ++ sem->u.sem.count += count; ++ return 0; ++} ++ ++static int ntsync_put_sem(struct ntsync_device *dev, void __user *argp) ++{ ++ struct ntsync_sem_args __user *user_args = argp; ++ struct ntsync_sem_args args; ++ struct ntsync_obj *sem; ++ __u32 prev_count; ++ int ret; ++ ++ if (copy_from_user(&args, argp, sizeof(args))) ++ return -EFAULT; ++ ++ sem = get_obj_typed(dev, args.sem, NTSYNC_TYPE_SEM); ++ if (!sem) ++ return -EINVAL; ++ ++ spin_lock(&sem->lock); ++ ++ prev_count = sem->u.sem.count; ++ ret = put_sem_state(sem, args.count); ++ ++ spin_unlock(&sem->lock); ++ ++ put_obj(sem); ++ ++ if (!ret && put_user(prev_count, &user_args->count)) ++ ret = -EFAULT; ++ ++ return ret; ++} ++ + static long ntsync_char_ioctl(struct file *file, unsigned int cmd, + unsigned long parm) + { +@@ -142,6 +216,8 @@ static long ntsync_char_ioctl(struct file *file, unsigned int cmd, + return ntsync_create_sem(dev, argp); + case NTSYNC_IOC_DELETE: + return ntsync_delete(dev, argp); ++ case NTSYNC_IOC_PUT_SEM: ++ return ntsync_put_sem(dev, argp); + default: + return -ENOIOCTLCMD; + } +diff --git a/include/uapi/linux/ntsync.h b/include/uapi/linux/ntsync.h +index d97afc138dcc..8c610d65f8ef 100644 +--- a/include/uapi/linux/ntsync.h ++++ b/include/uapi/linux/ntsync.h +@@ -21,5 +21,7 @@ struct ntsync_sem_args { + #define NTSYNC_IOC_CREATE_SEM _IOWR(NTSYNC_IOC_BASE, 0, \ + struct ntsync_sem_args) + #define NTSYNC_IOC_DELETE _IOW (NTSYNC_IOC_BASE, 1, __u32) ++#define NTSYNC_IOC_PUT_SEM _IOWR(NTSYNC_IOC_BASE, 2, \ ++ struct ntsync_sem_args) + + #endif +-- +2.43.0 + +From 68f1adf9a8e440dcb00b6665b8bc0f8aee275857 Mon Sep 17 00:00:00 2001 +From: Elizabeth Figura <zfigura@codeweavers.com> +Date: Fri, 5 Mar 2021 11:31:44 -0600 +Subject: [PATCH 05/32] ntsync: Introduce NTSYNC_IOC_WAIT_ANY. + +This corresponds to part of the functionality of the NT syscall +NtWaitForMultipleObjects(). Specifically, it implements the behaviour where +the third argument (wait_any) is TRUE, and it does not handle alertable waits. +Those features have been split out into separate patches to ease review. +--- + drivers/misc/ntsync.c | 229 ++++++++++++++++++++++++++++++++++++ + include/uapi/linux/ntsync.h | 13 ++ + 2 files changed, 242 insertions(+) + +diff --git a/drivers/misc/ntsync.c b/drivers/misc/ntsync.c +index d1c91c2a4f1a..2e8d3c2d51a4 100644 +--- a/drivers/misc/ntsync.c ++++ b/drivers/misc/ntsync.c +@@ -23,6 +23,8 @@ struct ntsync_obj { + struct kref refcount; + spinlock_t lock; + ++ struct list_head any_waiters; ++ + enum ntsync_type type; + + /* The following fields are protected by the object lock. */ +@@ -34,6 +36,28 @@ struct ntsync_obj { + } u; + }; + ++struct ntsync_q_entry { ++ struct list_head node; ++ struct ntsync_q *q; ++ struct ntsync_obj *obj; ++ __u32 index; ++}; ++ ++struct ntsync_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 ntsync_q_entry entries[]; ++}; ++ + struct ntsync_device { + struct xarray objects; + }; +@@ -109,6 +133,26 @@ static void init_obj(struct ntsync_obj *obj) + { + kref_init(&obj->refcount); + spin_lock_init(&obj->lock); ++ INIT_LIST_HEAD(&obj->any_waiters); ++} ++ ++static void try_wake_any_sem(struct ntsync_obj *sem) ++{ ++ struct ntsync_q_entry *entry; ++ ++ lockdep_assert_held(&sem->lock); ++ ++ list_for_each_entry(entry, &sem->any_waiters, node) { ++ struct ntsync_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 ntsync_create_sem(struct ntsync_device *dev, void __user *argp) +@@ -194,6 +238,8 @@ static int ntsync_put_sem(struct ntsync_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); + +@@ -205,6 +251,187 @@ static int ntsync_put_sem(struct ntsync_device *dev, void __user *argp) + return ret; + } + ++static int ntsync_schedule(const struct ntsync_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 ntsync_q structure, but do not queue us yet. ++ * Also, calculate the relative timeout. ++ */ ++static int setup_wait(struct ntsync_device *dev, ++ const struct ntsync_wait_args *args, ++ ktime_t *ret_timeout, struct ntsync_q **ret_q) ++{ ++ const __u32 count = args->count; ++ struct ntsync_q *q; ++ ktime_t timeout = 0; ++ __u32 *ids; ++ __u32 i, j; ++ ++ if (!args->owner || args->pad) ++ return -EINVAL; ++ ++ if (args->count > NTSYNC_MAX_WAIT_COUNT) ++ 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 ntsync_q_entry *entry = &q->entries[i]; ++ struct ntsync_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 ntsync_obj *obj) ++{ ++ switch (obj->type) { ++ case NTSYNC_TYPE_SEM: ++ try_wake_any_sem(obj); ++ break; ++ } ++} ++ ++static int ntsync_wait_any(struct ntsync_device *dev, void __user *argp) ++{ ++ struct ntsync_wait_args args; ++ struct ntsync_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 ntsync_q_entry *entry = &q->entries[i]; ++ struct ntsync_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 ntsync_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 = ntsync_schedule(q, args.timeout ? &timeout : NULL); ++ ++ /* and finally, unqueue */ ++ ++ for (i = 0; i < args.count; i++) { ++ struct ntsync_q_entry *entry = &q->entries[i]; ++ struct ntsync_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 ntsync_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 ntsync_char_ioctl(struct file *file, unsigned int cmd, + unsigned long parm) + { +@@ -218,6 +445,8 @@ static long ntsync_char_ioctl(struct file *file, unsigned int cmd, + return ntsync_delete(dev, argp); + case NTSYNC_IOC_PUT_SEM: + return ntsync_put_sem(dev, argp); ++ case NTSYNC_IOC_WAIT_ANY: ++ return ntsync_wait_any(dev, argp); + default: + return -ENOIOCTLCMD; + } +diff --git a/include/uapi/linux/ntsync.h b/include/uapi/linux/ntsync.h +index 8c610d65f8ef..10f07da7864e 100644 +--- a/include/uapi/linux/ntsync.h ++++ b/include/uapi/linux/ntsync.h +@@ -16,6 +16,17 @@ struct ntsync_sem_args { + __u32 max; + }; + ++struct ntsync_wait_args { ++ __u64 timeout; ++ __u64 objs; ++ __u32 count; ++ __u32 owner; ++ __u32 index; ++ __u32 pad; ++}; ++ ++#define NTSYNC_MAX_WAIT_COUNT 64 ++ + #define NTSYNC_IOC_BASE 0xf7 + + #define NTSYNC_IOC_CREATE_SEM _IOWR(NTSYNC_IOC_BASE, 0, \ +@@ -23,5 +34,7 @@ struct ntsync_sem_args { + #define NTSYNC_IOC_DELETE _IOW (NTSYNC_IOC_BASE, 1, __u32) + #define NTSYNC_IOC_PUT_SEM _IOWR(NTSYNC_IOC_BASE, 2, \ + struct ntsync_sem_args) ++#define NTSYNC_IOC_WAIT_ANY _IOWR(NTSYNC_IOC_BASE, 3, \ ++ struct ntsync_wait_args) + + #endif +-- +2.43.0 + +From ec146c6daf3cd94d70135965f3260f2bea90f305 Mon Sep 17 00:00:00 2001 +From: Elizabeth Figura <zfigura@codeweavers.com> +Date: Fri, 5 Mar 2021 11:36:09 -0600 +Subject: [PATCH 06/32] ntsync: Introduce NTSYNC_IOC_WAIT_ALL. + +This corresponds to part of the functionality of the NT syscall +NtWaitForMultipleObjects(). Specifically, it implements the behaviour where +the third argument (wait_any) is FALSE, and it does not yet handle alertable +waits. +--- + drivers/misc/ntsync.c | 241 ++++++++++++++++++++++++++++++++++-- + include/uapi/linux/ntsync.h | 2 + + 2 files changed, 235 insertions(+), 8 deletions(-) + +diff --git a/drivers/misc/ntsync.c b/drivers/misc/ntsync.c +index 2e8d3c2d51a4..2685363fae9e 100644 +--- a/drivers/misc/ntsync.c ++++ b/drivers/misc/ntsync.c +@@ -23,7 +23,34 @@ struct ntsync_obj { + struct kref refcount; + spinlock_t lock; + ++ /* ++ * any_waiters is protected by the object lock, but all_waiters is ++ * protected by the device wait_all_lock. ++ */ + struct list_head any_waiters; ++ struct list_head all_waiters; ++ ++ /* ++ * Hint describing how many tasks are queued on this object in a ++ * wait-all operation. ++ * ++ * Any time we do a wake, we may need to wake "all" waiters as well as ++ * "any" waiters. In order to atomically wake "all" waiters, we must ++ * lock all of the objects, and that means grabbing the wait_all_lock ++ * below (and, due to lock ordering rules, before locking this object). ++ * However, wait-all is a rare operation, and grabbing the wait-all ++ * lock for every wake would create unnecessary contention. Therefore we ++ * first check whether all_hint is zero, and, if it is, we skip trying ++ * to wake "all" waiters. ++ * ++ * This hint isn't protected by any lock. It might change during the ++ * course of a wake, but there's no meaningful race there; it's only a ++ * hint. ++ * ++ * Since wait requests must originate from user-space threads, we're ++ * limited here by PID_MAX_LIMIT, so there's no risk of saturation. ++ */ ++ atomic_t all_hint; + + enum ntsync_type type; + +@@ -54,11 +81,25 @@ struct ntsync_q { + */ + atomic_t signaled; + ++ bool all; + __u32 count; + struct ntsync_q_entry entries[]; + }; + + struct ntsync_device { ++ /* ++ * Wait-all operations must atomically grab all objects, and be totally ++ * ordered with respect to each other and wait-any operations. If one ++ * thread is trying to acquire several objects, another thread cannot ++ * touch the object at the same time. ++ * ++ * We achieve this by grabbing multiple object locks at the same time. ++ * However, this creates a lock ordering problem. To solve that problem, ++ * wait_all_lock is taken first whenever multiple objects must be locked ++ * at the same time. ++ */ ++ spinlock_t wait_all_lock; ++ + struct xarray objects; + }; + +@@ -107,6 +148,8 @@ static int ntsync_char_open(struct inode *inode, struct file *file) + if (!dev) + return -ENOMEM; + ++ spin_lock_init(&dev->wait_all_lock); ++ + xa_init_flags(&dev->objects, XA_FLAGS_ALLOC); + + file->private_data = dev; +@@ -132,8 +175,81 @@ static int ntsync_char_release(struct inode *inode, struct file *file) + static void init_obj(struct ntsync_obj *obj) + { + kref_init(&obj->refcount); ++ atomic_set(&obj->all_hint, 0); + spin_lock_init(&obj->lock); + INIT_LIST_HEAD(&obj->any_waiters); ++ INIT_LIST_HEAD(&obj->all_waiters); ++} ++ ++static bool is_signaled(struct ntsync_obj *obj, __u32 owner) ++{ ++ lockdep_assert_held(&obj->lock); ++ ++ switch (obj->type) { ++ case NTSYNC_TYPE_SEM: ++ return !!obj->u.sem.count; ++ } ++ ++ WARN(1, "bad object type %#x\n", obj->type); ++ return false; ++} ++ ++/* ++ * "locked_obj" is an optional pointer to an object which is already locked and ++ * should not be locked again. This is necessary so that changing an object's ++ * state and waking it can be a single atomic operation. ++ */ ++static void try_wake_all(struct ntsync_device *dev, struct ntsync_q *q, ++ struct ntsync_obj *locked_obj) ++{ ++ __u32 count = q->count; ++ bool can_wake = true; ++ __u32 i; ++ ++ lockdep_assert_held(&dev->wait_all_lock); ++ if (locked_obj) ++ lockdep_assert_held(&locked_obj->lock); ++ ++ for (i = 0; i < count; i++) { ++ if (q->entries[i].obj != locked_obj) ++ spin_lock_nest_lock(&q->entries[i].obj->lock, &dev->wait_all_lock); ++ } ++ ++ for (i = 0; i < count; i++) { ++ if (!is_signaled(q->entries[i].obj, q->owner)) { ++ can_wake = false; ++ break; ++ } ++ } ++ ++ if (can_wake && atomic_cmpxchg(&q->signaled, -1, 0) == -1) { ++ for (i = 0; i < count; i++) { ++ struct ntsync_obj *obj = q->entries[i].obj; ++ ++ switch (obj->type) { ++ case NTSYNC_TYPE_SEM: ++ obj->u.sem.count--; ++ break; ++ } ++ } ++ wake_up_process(q->task); ++ } ++ ++ for (i = 0; i < count; i++) { ++ if (q->entries[i].obj != locked_obj) ++ spin_unlock(&q->entries[i].obj->lock); ++ } ++} ++ ++static void try_wake_all_obj(struct ntsync_device *dev, struct ntsync_obj *obj) ++{ ++ struct ntsync_q_entry *entry; ++ ++ lockdep_assert_held(&dev->wait_all_lock); ++ lockdep_assert_held(&obj->lock); ++ ++ list_for_each_entry(entry, &obj->all_waiters, node) ++ try_wake_all(dev, entry->q, obj); + } + + static void try_wake_any_sem(struct ntsync_obj *sem) +@@ -234,14 +350,29 @@ static int ntsync_put_sem(struct ntsync_device *dev, void __user *argp) + if (!sem) + return -EINVAL; + +- spin_lock(&sem->lock); ++ if (atomic_read(&sem->all_hint) > 0) { ++ spin_lock(&dev->wait_all_lock); ++ spin_lock_nest_lock(&sem->lock, &dev->wait_all_lock); ++ ++ prev_count = sem->u.sem.count; ++ ret = put_sem_state(sem, args.count); ++ if (!ret) { ++ try_wake_all_obj(dev, sem); ++ try_wake_any_sem(sem); ++ } + +- prev_count = sem->u.sem.count; +- ret = put_sem_state(sem, args.count); +- if (!ret) +- try_wake_any_sem(sem); ++ spin_unlock(&sem->lock); ++ spin_unlock(&dev->wait_all_lock); ++ } else { ++ spin_lock(&sem->lock); + +- spin_unlock(&sem->lock); ++ prev_count = sem->u.sem.count; ++ ret = put_sem_state(sem, args.count); ++ if (!ret) ++ try_wake_any_sem(sem); ++ ++ spin_unlock(&sem->lock); ++ } + + put_obj(sem); + +@@ -278,7 +409,7 @@ static int ntsync_schedule(const struct ntsync_q *q, ktime_t *timeout) + * Also, calculate the relative timeout. + */ + static int setup_wait(struct ntsync_device *dev, +- const struct ntsync_wait_args *args, ++ const struct ntsync_wait_args *args, bool all, + ktime_t *ret_timeout, struct ntsync_q **ret_q) + { + const __u32 count = args->count; +@@ -321,6 +452,7 @@ static int setup_wait(struct ntsync_device *dev, + q->task = current; + q->owner = args->owner; + atomic_set(&q->signaled, -1); ++ q->all = all; + q->count = count; + + for (i = 0; i < count; i++) { +@@ -330,6 +462,16 @@ static int setup_wait(struct ntsync_device *dev, + if (!obj) + goto err; + ++ if (all) { ++ /* Check that the objects are all distinct. */ ++ for (j = 0; j < i; j++) { ++ if (obj == q->entries[j].obj) { ++ put_obj(obj); ++ goto err; ++ } ++ } ++ } ++ + entry->obj = obj; + entry->q = q; + entry->index = i; +@@ -370,7 +512,7 @@ static int ntsync_wait_any(struct ntsync_device *dev, void __user *argp) + if (copy_from_user(&args, argp, sizeof(args))) + return -EFAULT; + +- ret = setup_wait(dev, &args, &timeout, &q); ++ ret = setup_wait(dev, &args, false, &timeout, &q); + if (ret < 0) + return ret; + +@@ -432,6 +574,87 @@ static int ntsync_wait_any(struct ntsync_device *dev, void __user *argp) + return ret; + } + ++static int ntsync_wait_all(struct ntsync_device *dev, void __user *argp) ++{ ++ struct ntsync_wait_args args; ++ struct ntsync_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, true, &timeout, &q); ++ if (ret < 0) ++ return ret; ++ ++ /* queue ourselves */ ++ ++ spin_lock(&dev->wait_all_lock); ++ ++ for (i = 0; i < args.count; i++) { ++ struct ntsync_q_entry *entry = &q->entries[i]; ++ struct ntsync_obj *obj = entry->obj; ++ ++ atomic_inc(&obj->all_hint); ++ ++ /* ++ * obj->all_waiters is protected by dev->wait_all_lock rather ++ * than obj->lock, so there is no need to acquire it here. ++ */ ++ list_add_tail(&entry->node, &obj->all_waiters); ++ } ++ ++ /* check if we are already signaled */ ++ ++ try_wake_all(dev, q, NULL); ++ ++ spin_unlock(&dev->wait_all_lock); ++ ++ /* sleep */ ++ ++ ret = ntsync_schedule(q, args.timeout ? &timeout : NULL); ++ ++ /* and finally, unqueue */ ++ ++ spin_lock(&dev->wait_all_lock); ++ ++ for (i = 0; i < args.count; i++) { ++ struct ntsync_q_entry *entry = &q->entries[i]; ++ struct ntsync_obj *obj = entry->obj; ++ ++ /* ++ * obj->all_waiters is protected by dev->wait_all_lock rather ++ * than obj->lock, so there is no need to acquire it here. ++ */ ++ list_del(&entry->node); ++ ++ atomic_dec(&obj->all_hint); ++ ++ put_obj(obj); ++ } ++ ++ spin_unlock(&dev->wait_all_lock); ++ ++ signaled = atomic_read(&q->signaled); ++ if (signaled != -1) { ++ struct ntsync_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 ntsync_char_ioctl(struct file *file, unsigned int cmd, + unsigned long parm) + { +@@ -445,6 +668,8 @@ static long ntsync_char_ioctl(struct file *file, unsigned int cmd, + return ntsync_delete(dev, argp); + case NTSYNC_IOC_PUT_SEM: + return ntsync_put_sem(dev, argp); ++ case NTSYNC_IOC_WAIT_ALL: ++ return ntsync_wait_all(dev, argp); + case NTSYNC_IOC_WAIT_ANY: + return ntsync_wait_any(dev, argp); + default: +diff --git a/include/uapi/linux/ntsync.h b/include/uapi/linux/ntsync.h +index 10f07da7864e..a5bed5a39b21 100644 +--- a/include/uapi/linux/ntsync.h ++++ b/include/uapi/linux/ntsync.h +@@ -36,5 +36,7 @@ struct ntsync_wait_args { + struct ntsync_sem_args) + #define NTSYNC_IOC_WAIT_ANY _IOWR(NTSYNC_IOC_BASE, 3, \ + struct ntsync_wait_args) ++#define NTSYNC_IOC_WAIT_ALL _IOWR(NTSYNC_IOC_BASE, 4, \ ++ struct ntsync_wait_args) + + #endif +-- +2.43.0 + +From 965d8711ba4fbb0867c8baef95129f0ff62d9419 Mon Sep 17 00:00:00 2001 +From: Elizabeth Figura <zfigura@codeweavers.com> +Date: Fri, 5 Mar 2021 11:41:10 -0600 +Subject: [PATCH 07/32] ntsync: Introduce NTSYNC_IOC_CREATE_MUTEX. + +This corresponds to the NT syscall NtCreateMutant(). +--- + drivers/misc/ntsync.c | 72 +++++++++++++++++++++++++++++++++++++ + include/uapi/linux/ntsync.h | 8 +++++ + 2 files changed, 80 insertions(+) + +diff --git a/drivers/misc/ntsync.c b/drivers/misc/ntsync.c +index 2685363fae9e..d48f2ef41341 100644 +--- a/drivers/misc/ntsync.c ++++ b/drivers/misc/ntsync.c +@@ -16,6 +16,7 @@ + + enum ntsync_type { + NTSYNC_TYPE_SEM, ++ NTSYNC_TYPE_MUTEX, + }; + + struct ntsync_obj { +@@ -60,6 +61,10 @@ struct ntsync_obj { + __u32 count; + __u32 max; + } sem; ++ struct { ++ __u32 count; ++ __u32 owner; ++ } mutex; + } u; + }; + +@@ -188,6 +193,10 @@ static bool is_signaled(struct ntsync_obj *obj, __u32 owner) + switch (obj->type) { + case NTSYNC_TYPE_SEM: + return !!obj->u.sem.count; ++ case NTSYNC_TYPE_MUTEX: ++ if (obj->u.mutex.owner && obj->u.mutex.owner != owner) ++ return false; ++ return obj->u.mutex.count < UINT_MAX; + } + + WARN(1, "bad object type %#x\n", obj->type); +@@ -230,6 +239,10 @@ static void try_wake_all(struct ntsync_device *dev, struct ntsync_q *q, + case NTSYNC_TYPE_SEM: + obj->u.sem.count--; + break; ++ case NTSYNC_TYPE_MUTEX: ++ obj->u.mutex.count++; ++ obj->u.mutex.owner = q->owner; ++ break; + } + } + wake_up_process(q->task); +@@ -271,6 +284,28 @@ static void try_wake_any_sem(struct ntsync_obj *sem) + } + } + ++static void try_wake_any_mutex(struct ntsync_obj *mutex) ++{ ++ struct ntsync_q_entry *entry; ++ ++ lockdep_assert_held(&mutex->lock); ++ ++ list_for_each_entry(entry, &mutex->any_waiters, node) { ++ struct ntsync_q *q = entry->q; ++ ++ if (mutex->u.mutex.count == UINT_MAX) ++ break; ++ if (mutex->u.mutex.owner && mutex->u.mutex.owner != q->owner) ++ continue; ++ ++ if (atomic_cmpxchg(&q->signaled, -1, entry->index) == -1) { ++ mutex->u.mutex.count++; ++ mutex->u.mutex.owner = q->owner; ++ wake_up_process(q->task); ++ } ++ } ++} ++ + static int ntsync_create_sem(struct ntsync_device *dev, void __user *argp) + { + struct ntsync_sem_args __user *user_args = argp; +@@ -303,6 +338,38 @@ static int ntsync_create_sem(struct ntsync_device *dev, void __user *argp) + return put_user(id, &user_args->sem); + } + ++static int ntsync_create_mutex(struct ntsync_device *dev, void __user *argp) ++{ ++ struct ntsync_mutex_args __user *user_args = argp; ++ struct ntsync_mutex_args args; ++ struct ntsync_obj *mutex; ++ __u32 id; ++ int ret; ++ ++ if (copy_from_user(&args, argp, sizeof(args))) ++ return -EFAULT; ++ ++ if (!args.owner != !args.count) ++ return -EINVAL; ++ ++ mutex = kzalloc(sizeof(*mutex), GFP_KERNEL); ++ if (!mutex) ++ return -ENOMEM; ++ ++ init_obj(mutex); ++ mutex->type = NTSYNC_TYPE_MUTEX; ++ mutex->u.mutex.count = args.count; ++ mutex->u.mutex.owner = args.owner; ++ ++ ret = xa_alloc(&dev->objects, &id, mutex, xa_limit_32b, GFP_KERNEL); ++ if (ret < 0) { ++ kfree(mutex); ++ return ret; ++ } ++ ++ return put_user(id, &user_args->mutex); ++} ++ + static int ntsync_delete(struct ntsync_device *dev, void __user *argp) + { + struct ntsync_obj *obj; +@@ -497,6 +564,9 @@ static void try_wake_any_obj(struct ntsync_obj *obj) + case NTSYNC_TYPE_SEM: + try_wake_any_sem(obj); + break; ++ case NTSYNC_TYPE_MUTEX: ++ try_wake_any_mutex(obj); ++ break; + } + } + +@@ -662,6 +732,8 @@ static long ntsync_char_ioctl(struct file *file, unsigned int cmd, + void __user *argp = (void __user *)parm; + + switch (cmd) { ++ case NTSYNC_IOC_CREATE_MUTEX: ++ return ntsync_create_mutex(dev, argp); + case NTSYNC_IOC_CREATE_SEM: + return ntsync_create_sem(dev, argp); + case NTSYNC_IOC_DELETE: +diff --git a/include/uapi/linux/ntsync.h b/include/uapi/linux/ntsync.h +index a5bed5a39b21..26d1b3d4847f 100644 +--- a/include/uapi/linux/ntsync.h ++++ b/include/uapi/linux/ntsync.h +@@ -16,6 +16,12 @@ struct ntsync_sem_args { + __u32 max; + }; + ++struct ntsync_mutex_args { ++ __u32 mutex; ++ __u32 owner; ++ __u32 count; ++}; ++ + struct ntsync_wait_args { + __u64 timeout; + __u64 objs; +@@ -38,5 +44,7 @@ struct ntsync_wait_args { + struct ntsync_wait_args) + #define NTSYNC_IOC_WAIT_ALL _IOWR(NTSYNC_IOC_BASE, 4, \ + struct ntsync_wait_args) ++#define NTSYNC_IOC_CREATE_MUTEX _IOWR(NTSYNC_IOC_BASE, 5, \ ++ struct ntsync_mutex_args) + + #endif +-- +2.43.0 + +From b348f2e4e4054c20f0f1852ffaa90095e1f32415 Mon Sep 17 00:00:00 2001 +From: Elizabeth Figura <zfigura@codeweavers.com> +Date: Fri, 5 Mar 2021 11:44:41 -0600 +Subject: [PATCH 08/32] ntsync: Introduce NTSYNC_IOC_PUT_MUTEX. + +This corresponds to the NT syscall NtReleaseMutant(). +--- + drivers/misc/ntsync.c | 67 +++++++++++++++++++++++++++++++++++++ + include/uapi/linux/ntsync.h | 2 ++ + 2 files changed, 69 insertions(+) + +diff --git a/drivers/misc/ntsync.c b/drivers/misc/ntsync.c +index d48f2ef41341..28f43768d1c3 100644 +--- a/drivers/misc/ntsync.c ++++ b/drivers/misc/ntsync.c +@@ -449,6 +449,71 @@ static int ntsync_put_sem(struct ntsync_device *dev, void __user *argp) + return ret; + } + ++/* ++ * Actually change the mutex state, returning -EPERM if not the owner. ++ */ ++static int put_mutex_state(struct ntsync_obj *mutex, ++ const struct ntsync_mutex_args *args) ++{ ++ lockdep_assert_held(&mutex->lock); ++ ++ if (mutex->u.mutex.owner != args->owner) ++ return -EPERM; ++ ++ if (!--mutex->u.mutex.count) ++ mutex->u.mutex.owner = 0; ++ return 0; ++} ++ ++static int ntsync_put_mutex(struct ntsync_device *dev, void __user *argp) ++{ ++ struct ntsync_mutex_args __user *user_args = argp; ++ struct ntsync_mutex_args args; ++ struct ntsync_obj *mutex; ++ __u32 prev_count; ++ int ret; ++ ++ if (copy_from_user(&args, argp, sizeof(args))) ++ return -EFAULT; ++ if (!args.owner) ++ return -EINVAL; ++ ++ mutex = get_obj_typed(dev, args.mutex, NTSYNC_TYPE_MUTEX); ++ if (!mutex) ++ return -EINVAL; ++ ++ if (atomic_read(&mutex->all_hint) > 0) { ++ spin_lock(&dev->wait_all_lock); ++ spin_lock_nest_lock(&mutex->lock, &dev->wait_all_lock); ++ ++ prev_count = mutex->u.mutex.count; ++ ret = put_mutex_state(mutex, &args); ++ if (!ret) { ++ try_wake_all_obj(dev, mutex); ++ try_wake_any_mutex(mutex); ++ } ++ ++ spin_unlock(&mutex->lock); ++ spin_unlock(&dev->wait_all_lock); ++ } else { ++ spin_lock(&mutex->lock); ++ ++ prev_count = mutex->u.mutex.count; ++ ret = put_mutex_state(mutex, &args); ++ if (!ret) ++ try_wake_any_mutex(mutex); ++ ++ spin_unlock(&mutex->lock); ++ } ++ ++ put_obj(mutex); ++ ++ if (!ret && put_user(prev_count, &user_args->count)) ++ ret = -EFAULT; ++ ++ return ret; ++} ++ + static int ntsync_schedule(const struct ntsync_q *q, ktime_t *timeout) + { + int ret = 0; +@@ -738,6 +803,8 @@ static long ntsync_char_ioctl(struct file *file, unsigned int cmd, + return ntsync_create_sem(dev, argp); + case NTSYNC_IOC_DELETE: + return ntsync_delete(dev, argp); ++ case NTSYNC_IOC_PUT_MUTEX: ++ return ntsync_put_mutex(dev, argp); + case NTSYNC_IOC_PUT_SEM: + return ntsync_put_sem(dev, argp); + case NTSYNC_IOC_WAIT_ALL: +diff --git a/include/uapi/linux/ntsync.h b/include/uapi/linux/ntsync.h +index 26d1b3d4847f..2e44e7e77776 100644 +--- a/include/uapi/linux/ntsync.h ++++ b/include/uapi/linux/ntsync.h +@@ -46,5 +46,7 @@ struct ntsync_wait_args { + struct ntsync_wait_args) + #define NTSYNC_IOC_CREATE_MUTEX _IOWR(NTSYNC_IOC_BASE, 5, \ + struct ntsync_mutex_args) ++#define NTSYNC_IOC_PUT_MUTEX _IOWR(NTSYNC_IOC_BASE, 6, \ ++ struct ntsync_mutex_args) + + #endif +-- +2.43.0 + +From f042ca07e1744d2bb04942163e4e65fc6e624f41 Mon Sep 17 00:00:00 2001 +From: Elizabeth Figura <zfigura@codeweavers.com> +Date: Fri, 5 Mar 2021 11:46:46 -0600 +Subject: [PATCH 09/32] ntsync: Introduce NTSYNC_IOC_KILL_OWNER. + +This does not correspond to any NT syscall, but rather should be called by the +user-space NT emulator when a thread dies. It is responsible for marking any +mutexes owned by that thread as abandoned. +--- + drivers/misc/ntsync.c | 80 ++++++++++++++++++++++++++++++++++++- + include/uapi/linux/ntsync.h | 1 + + 2 files changed, 79 insertions(+), 2 deletions(-) + +diff --git a/drivers/misc/ntsync.c b/drivers/misc/ntsync.c +index 28f43768d1c3..1173c750c106 100644 +--- a/drivers/misc/ntsync.c ++++ b/drivers/misc/ntsync.c +@@ -64,6 +64,7 @@ struct ntsync_obj { + struct { + __u32 count; + __u32 owner; ++ bool ownerdead; + } mutex; + } u; + }; +@@ -87,6 +88,7 @@ struct ntsync_q { + atomic_t signaled; + + bool all; ++ bool ownerdead; + __u32 count; + struct ntsync_q_entry entries[]; + }; +@@ -240,6 +242,9 @@ static void try_wake_all(struct ntsync_device *dev, struct ntsync_q *q, + obj->u.sem.count--; + break; + case NTSYNC_TYPE_MUTEX: ++ if (obj->u.mutex.ownerdead) ++ q->ownerdead = true; ++ obj->u.mutex.ownerdead = false; + obj->u.mutex.count++; + obj->u.mutex.owner = q->owner; + break; +@@ -299,6 +304,9 @@ static void try_wake_any_mutex(struct ntsync_obj *mutex) + continue; + + if (atomic_cmpxchg(&q->signaled, -1, entry->index) == -1) { ++ if (mutex->u.mutex.ownerdead) ++ q->ownerdead = true; ++ mutex->u.mutex.ownerdead = false; + mutex->u.mutex.count++; + mutex->u.mutex.owner = q->owner; + wake_up_process(q->task); +@@ -514,6 +522,71 @@ static int ntsync_put_mutex(struct ntsync_device *dev, void __user *argp) + return ret; + } + ++/* ++ * Actually change the mutex state to mark its owner as dead. ++ */ ++static void put_mutex_ownerdead_state(struct ntsync_obj *mutex) ++{ ++ lockdep_assert_held(&mutex->lock); ++ ++ mutex->u.mutex.ownerdead = true; ++ mutex->u.mutex.owner = 0; ++ mutex->u.mutex.count = 0; ++} ++ ++static int ntsync_kill_owner(struct ntsync_device *dev, void __user *argp) ++{ ++ struct ntsync_obj *obj; ++ unsigned long id; ++ __u32 owner; ++ ++ if (get_user(owner, (__u32 __user *)argp)) ++ return -EFAULT; ++ if (!owner) ++ return -EINVAL; ++ ++ rcu_read_lock(); ++ ++ xa_for_each(&dev->objects, id, obj) { ++ if (!kref_get_unless_zero(&obj->refcount)) ++ continue; ++ ++ if (obj->type != NTSYNC_TYPE_MUTEX) { ++ put_obj(obj); ++ continue; ++ } ++ ++ if (atomic_read(&obj->all_hint) > 0) { ++ spin_lock(&dev->wait_all_lock); ++ spin_lock_nest_lock(&obj->lock, &dev->wait_all_lock); ++ ++ if (obj->u.mutex.owner == owner) { ++ put_mutex_ownerdead_state(obj); ++ try_wake_all_obj(dev, obj); ++ try_wake_any_mutex(obj); ++ } ++ ++ spin_unlock(&obj->lock); ++ spin_unlock(&dev->wait_all_lock); ++ } else { ++ spin_lock(&obj->lock); ++ ++ if (obj->u.mutex.owner == owner) { ++ put_mutex_ownerdead_state(obj); ++ try_wake_any_mutex(obj); ++ } ++ ++ spin_unlock(&obj->lock); ++ } ++ ++ put_obj(obj); ++ } ++ ++ rcu_read_unlock(); ++ ++ return 0; ++} ++ + static int ntsync_schedule(const struct ntsync_q *q, ktime_t *timeout) + { + int ret = 0; +@@ -585,6 +658,7 @@ static int setup_wait(struct ntsync_device *dev, + q->owner = args->owner; + atomic_set(&q->signaled, -1); + q->all = all; ++ q->ownerdead = false; + q->count = count; + + for (i = 0; i < count; i++) { +@@ -697,7 +771,7 @@ static int ntsync_wait_any(struct ntsync_device *dev, void __user *argp) + struct ntsync_wait_args __user *user_args = argp; + + /* even if we caught a signal, we need to communicate success */ +- ret = 0; ++ ret = q->ownerdead ? -EOWNERDEAD : 0; + + if (put_user(signaled, &user_args->index)) + ret = -EFAULT; +@@ -778,7 +852,7 @@ static int ntsync_wait_all(struct ntsync_device *dev, void __user *argp) + struct ntsync_wait_args __user *user_args = argp; + + /* even if we caught a signal, we need to communicate success */ +- ret = 0; ++ ret = q->ownerdead ? -EOWNERDEAD : 0; + + if (put_user(signaled, &user_args->index)) + ret = -EFAULT; +@@ -803,6 +877,8 @@ static long ntsync_char_ioctl(struct file *file, unsigned int cmd, + return ntsync_create_sem(dev, argp); + case NTSYNC_IOC_DELETE: + return ntsync_delete(dev, argp); ++ case NTSYNC_IOC_KILL_OWNER: ++ return ntsync_kill_owner(dev, argp); + case NTSYNC_IOC_PUT_MUTEX: + return ntsync_put_mutex(dev, argp); + case NTSYNC_IOC_PUT_SEM: +diff --git a/include/uapi/linux/ntsync.h b/include/uapi/linux/ntsync.h +index 2e44e7e77776..fec9a3993322 100644 +--- a/include/uapi/linux/ntsync.h ++++ b/include/uapi/linux/ntsync.h +@@ -48,5 +48,6 @@ struct ntsync_wait_args { + struct ntsync_mutex_args) + #define NTSYNC_IOC_PUT_MUTEX _IOWR(NTSYNC_IOC_BASE, 6, \ + struct ntsync_mutex_args) ++#define NTSYNC_IOC_KILL_OWNER _IOW (NTSYNC_IOC_BASE, 7, __u32) + + #endif +-- +2.43.0 + +From fc85bff90ef0348584fca122f96aa3ec1a5fe604 Mon Sep 17 00:00:00 2001 +From: Elizabeth Figura <zfigura@codeweavers.com> +Date: Fri, 5 Mar 2021 11:47:55 -0600 +Subject: [PATCH 10/32] ntsync: Introduce NTSYNC_IOC_READ_SEM. + +This corresponds to the NT syscall NtQuerySemaphore(). +--- + drivers/misc/ntsync.c | 29 +++++++++++++++++++++++++++++ + include/uapi/linux/ntsync.h | 2 ++ + 2 files changed, 31 insertions(+) + +diff --git a/drivers/misc/ntsync.c b/drivers/misc/ntsync.c +index 1173c750c106..70c60f294bb2 100644 +--- a/drivers/misc/ntsync.c ++++ b/drivers/misc/ntsync.c +@@ -522,6 +522,33 @@ static int ntsync_put_mutex(struct ntsync_device *dev, void __user *argp) + return ret; + } + ++static int ntsync_read_sem(struct ntsync_device *dev, void __user *argp) ++{ ++ struct ntsync_sem_args __user *user_args = argp; ++ struct ntsync_sem_args args; ++ struct ntsync_obj *sem; ++ __u32 id; ++ ++ if (get_user(id, &user_args->sem)) ++ return -EFAULT; ++ ++ sem = get_obj_typed(dev, id, NTSYNC_TYPE_SEM); ++ if (!sem) ++ return -EINVAL; ++ ++ args.sem = id; ++ spin_lock(&sem->lock); ++ args.count = sem->u.sem.count; ++ args.max = sem->u.sem.max; ++ spin_unlock(&sem->lock); ++ ++ put_obj(sem); ++ ++ if (copy_to_user(user_args, &args, sizeof(args))) ++ return -EFAULT; ++ return 0; ++} ++ + /* + * Actually change the mutex state to mark its owner as dead. + */ +@@ -883,6 +910,8 @@ static long ntsync_char_ioctl(struct file *file, unsigned int cmd, + return ntsync_put_mutex(dev, argp); + case NTSYNC_IOC_PUT_SEM: + return ntsync_put_sem(dev, argp); ++ case NTSYNC_IOC_READ_SEM: ++ return ntsync_read_sem(dev, argp); + case NTSYNC_IOC_WAIT_ALL: + return ntsync_wait_all(dev, argp); + case NTSYNC_IOC_WAIT_ANY: +diff --git a/include/uapi/linux/ntsync.h b/include/uapi/linux/ntsync.h +index fec9a3993322..b84a57c13d80 100644 +--- a/include/uapi/linux/ntsync.h ++++ b/include/uapi/linux/ntsync.h +@@ -49,5 +49,7 @@ struct ntsync_wait_args { + #define NTSYNC_IOC_PUT_MUTEX _IOWR(NTSYNC_IOC_BASE, 6, \ + struct ntsync_mutex_args) + #define NTSYNC_IOC_KILL_OWNER _IOW (NTSYNC_IOC_BASE, 7, __u32) ++#define NTSYNC_IOC_READ_SEM _IOWR(NTSYNC_IOC_BASE, 8, \ ++ struct ntsync_sem_args) + + #endif +-- +2.43.0 + +From 4acc2acbbcacf80485e7d443c0a2327d50879c64 Mon Sep 17 00:00:00 2001 +From: Elizabeth Figura <zfigura@codeweavers.com> +Date: Fri, 5 Mar 2021 11:48:10 -0600 +Subject: [PATCH 11/32] ntsync: Introduce NTSYNC_IOC_READ_MUTEX. + +This corresponds to the NT syscall NtQueryMutant(). +--- + drivers/misc/ntsync.c | 31 +++++++++++++++++++++++++++++++ + include/uapi/linux/ntsync.h | 2 ++ + 2 files changed, 33 insertions(+) + +diff --git a/drivers/misc/ntsync.c b/drivers/misc/ntsync.c +index 70c60f294bb2..88adb52bad93 100644 +--- a/drivers/misc/ntsync.c ++++ b/drivers/misc/ntsync.c +@@ -549,6 +549,35 @@ static int ntsync_read_sem(struct ntsync_device *dev, void __user *argp) + return 0; + } + ++static int ntsync_read_mutex(struct ntsync_device *dev, void __user *argp) ++{ ++ struct ntsync_mutex_args __user *user_args = argp; ++ struct ntsync_mutex_args args; ++ struct ntsync_obj *mutex; ++ __u32 id; ++ int ret; ++ ++ if (get_user(id, &user_args->mutex)) ++ return -EFAULT; ++ ++ mutex = get_obj_typed(dev, id, NTSYNC_TYPE_MUTEX); ++ if (!mutex) ++ return -EINVAL; ++ ++ args.mutex = id; ++ spin_lock(&mutex->lock); ++ args.count = mutex->u.mutex.count; ++ args.owner = mutex->u.mutex.owner; ++ ret = mutex->u.mutex.ownerdead ? -EOWNERDEAD : 0; ++ spin_unlock(&mutex->lock); ++ ++ put_obj(mutex); ++ ++ if (copy_to_user(user_args, &args, sizeof(args))) ++ return -EFAULT; ++ return ret; ++} ++ + /* + * Actually change the mutex state to mark its owner as dead. + */ +@@ -910,6 +939,8 @@ static long ntsync_char_ioctl(struct file *file, unsigned int cmd, + return ntsync_put_mutex(dev, argp); + case NTSYNC_IOC_PUT_SEM: + return ntsync_put_sem(dev, argp); ++ case NTSYNC_IOC_READ_MUTEX: ++ return ntsync_read_mutex(dev, argp); + case NTSYNC_IOC_READ_SEM: + return ntsync_read_sem(dev, argp); + case NTSYNC_IOC_WAIT_ALL: +diff --git a/include/uapi/linux/ntsync.h b/include/uapi/linux/ntsync.h +index b84a57c13d80..fa57f190c80e 100644 +--- a/include/uapi/linux/ntsync.h ++++ b/include/uapi/linux/ntsync.h +@@ -51,5 +51,7 @@ struct ntsync_wait_args { + #define NTSYNC_IOC_KILL_OWNER _IOW (NTSYNC_IOC_BASE, 7, __u32) + #define NTSYNC_IOC_READ_SEM _IOWR(NTSYNC_IOC_BASE, 8, \ + struct ntsync_sem_args) ++#define NTSYNC_IOC_READ_MUTEX _IOWR(NTSYNC_IOC_BASE, 9, \ ++ struct ntsync_mutex_args) + + #endif +-- +2.43.0 + +From 1bc3fa335b3c30797586eebbf533135ccddcb934 Mon Sep 17 00:00:00 2001 +From: Elizabeth Figura <zfigura@codeweavers.com> +Date: Wed, 19 Jan 2022 18:21:03 -0600 +Subject: [PATCH 12/32] ntsync: Introduce NTSYNC_IOC_CREATE_EVENT. + +This corresponds to the NT syscall NtCreateEvent(). +--- + drivers/misc/ntsync.c | 65 +++++++++++++++++++++++++++++++++++++ + include/uapi/linux/ntsync.h | 8 +++++ + 2 files changed, 73 insertions(+) + +diff --git a/drivers/misc/ntsync.c b/drivers/misc/ntsync.c +index 88adb52bad93..3778dbe617e9 100644 +--- a/drivers/misc/ntsync.c ++++ b/drivers/misc/ntsync.c +@@ -17,6 +17,7 @@ + enum ntsync_type { + NTSYNC_TYPE_SEM, + NTSYNC_TYPE_MUTEX, ++ NTSYNC_TYPE_EVENT, + }; + + struct ntsync_obj { +@@ -66,6 +67,10 @@ struct ntsync_obj { + __u32 owner; + bool ownerdead; + } mutex; ++ struct { ++ bool manual; ++ bool signaled; ++ } event; + } u; + }; + +@@ -199,6 +204,8 @@ static bool is_signaled(struct ntsync_obj *obj, __u32 owner) + if (obj->u.mutex.owner && obj->u.mutex.owner != owner) + return false; + return obj->u.mutex.count < UINT_MAX; ++ case NTSYNC_TYPE_EVENT: ++ return obj->u.event.signaled; + } + + WARN(1, "bad object type %#x\n", obj->type); +@@ -248,6 +255,10 @@ static void try_wake_all(struct ntsync_device *dev, struct ntsync_q *q, + obj->u.mutex.count++; + obj->u.mutex.owner = q->owner; + break; ++ case NTSYNC_TYPE_EVENT: ++ if (!obj->u.event.manual) ++ obj->u.event.signaled = false; ++ break; + } + } + wake_up_process(q->task); +@@ -314,6 +325,26 @@ static void try_wake_any_mutex(struct ntsync_obj *mutex) + } + } + ++static void try_wake_any_event(struct ntsync_obj *event) ++{ ++ struct ntsync_q_entry *entry; ++ ++ lockdep_assert_held(&event->lock); ++ ++ list_for_each_entry(entry, &event->any_waiters, node) { ++ struct ntsync_q *q = entry->q; ++ ++ if (!event->u.event.signaled) ++ break; ++ ++ if (atomic_cmpxchg(&q->signaled, -1, entry->index) == -1) { ++ if (!event->u.event.manual) ++ event->u.event.signaled = false; ++ wake_up_process(q->task); ++ } ++ } ++} ++ + static int ntsync_create_sem(struct ntsync_device *dev, void __user *argp) + { + struct ntsync_sem_args __user *user_args = argp; +@@ -378,6 +409,35 @@ static int ntsync_create_mutex(struct ntsync_device *dev, void __user *argp) + return put_user(id, &user_args->mutex); + } + ++static int ntsync_create_event(struct ntsync_device *dev, void __user *argp) ++{ ++ struct ntsync_event_args __user *user_args = argp; ++ struct ntsync_event_args args; ++ struct ntsync_obj *event; ++ __u32 id; ++ int ret; ++ ++ if (copy_from_user(&args, argp, sizeof(args))) ++ return -EFAULT; ++ ++ event = kzalloc(sizeof(*event), GFP_KERNEL); ++ if (!event) ++ return -ENOMEM; ++ ++ init_obj(event); ++ event->type = NTSYNC_TYPE_EVENT; ++ event->u.event.manual = args.manual; ++ event->u.event.signaled = args.signaled; ++ ++ ret = xa_alloc(&dev->objects, &id, event, xa_limit_32b, GFP_KERNEL); ++ if (ret < 0) { ++ kfree(event); ++ return ret; ++ } ++ ++ return put_user(id, &user_args->event); ++} ++ + static int ntsync_delete(struct ntsync_device *dev, void __user *argp) + { + struct ntsync_obj *obj; +@@ -762,6 +822,9 @@ static void try_wake_any_obj(struct ntsync_obj *obj) + case NTSYNC_TYPE_MUTEX: + try_wake_any_mutex(obj); + break; ++ case NTSYNC_TYPE_EVENT: ++ try_wake_any_event(obj); ++ break; + } + } + +@@ -927,6 +990,8 @@ static long ntsync_char_ioctl(struct file *file, unsigned int cmd, + void __user *argp = (void __user *)parm; + + switch (cmd) { ++ case NTSYNC_IOC_CREATE_EVENT: ++ return ntsync_create_event(dev, argp); + case NTSYNC_IOC_CREATE_MUTEX: + return ntsync_create_mutex(dev, argp); + case NTSYNC_IOC_CREATE_SEM: +diff --git a/include/uapi/linux/ntsync.h b/include/uapi/linux/ntsync.h +index fa57f190c80e..c7cbdeff32c9 100644 +--- a/include/uapi/linux/ntsync.h ++++ b/include/uapi/linux/ntsync.h +@@ -22,6 +22,12 @@ struct ntsync_mutex_args { + __u32 count; + }; + ++struct ntsync_event_args { ++ __u32 event; ++ __u32 manual; ++ __u32 signaled; ++}; ++ + struct ntsync_wait_args { + __u64 timeout; + __u64 objs; +@@ -53,5 +59,7 @@ struct ntsync_wait_args { + struct ntsync_sem_args) + #define NTSYNC_IOC_READ_MUTEX _IOWR(NTSYNC_IOC_BASE, 9, \ + struct ntsync_mutex_args) ++#define NTSYNC_IOC_CREATE_EVENT _IOWR(NTSYNC_IOC_BASE, 10, \ ++ struct ntsync_event_args) + + #endif +-- +2.43.0 + +From d4d9e2196dc13cf89b8181eb4eee86d4a26ef026 Mon Sep 17 00:00:00 2001 +From: Elizabeth Figura <zfigura@codeweavers.com> +Date: Wed, 19 Jan 2022 18:43:30 -0600 +Subject: [PATCH 13/32] ntsync: Introduce NTSYNC_IOC_SET_EVENT. + +This corresponds to the NT syscall NtSetEvent(). +--- + drivers/misc/ntsync.c | 45 +++++++++++++++++++++++++++++++++++++ + include/uapi/linux/ntsync.h | 2 ++ + 2 files changed, 47 insertions(+) + +diff --git a/drivers/misc/ntsync.c b/drivers/misc/ntsync.c +index 3778dbe617e9..cd6478bd93bd 100644 +--- a/drivers/misc/ntsync.c ++++ b/drivers/misc/ntsync.c +@@ -703,6 +703,49 @@ static int ntsync_kill_owner(struct ntsync_device *dev, void __user *argp) + return 0; + } + ++static int ntsync_set_event(struct ntsync_device *dev, void __user *argp) ++{ ++ struct ntsync_event_args __user *user_args = argp; ++ struct ntsync_event_args args; ++ struct ntsync_obj *event; ++ bool prev_state; ++ ++ if (copy_from_user(&args, argp, sizeof(args))) ++ return -EFAULT; ++ ++ event = get_obj_typed(dev, args.event, NTSYNC_TYPE_EVENT); ++ if (!event) ++ return -EINVAL; ++ ++ if (atomic_read(&event->all_hint) > 0) { ++ spin_lock(&dev->wait_all_lock); ++ spin_lock_nest_lock(&event->lock, &dev->wait_all_lock); ++ ++ prev_state = event->u.event.signaled; ++ event->u.event.signaled = true; ++ try_wake_all_obj(dev, event); ++ try_wake_any_event(event); ++ ++ spin_unlock(&event->lock); ++ spin_unlock(&dev->wait_all_lock); ++ } else { ++ spin_lock(&event->lock); ++ ++ prev_state = event->u.event.signaled; ++ event->u.event.signaled = true; ++ try_wake_any_event(event); ++ ++ spin_unlock(&event->lock); ++ } ++ ++ put_obj(event); ++ ++ if (put_user(prev_state, &user_args->signaled)) ++ return -EFAULT; ++ ++ return 0; ++} ++ + static int ntsync_schedule(const struct ntsync_q *q, ktime_t *timeout) + { + int ret = 0; +@@ -1008,6 +1051,8 @@ static long ntsync_char_ioctl(struct file *file, unsigned int cmd, + return ntsync_read_mutex(dev, argp); + case NTSYNC_IOC_READ_SEM: + return ntsync_read_sem(dev, argp); ++ case NTSYNC_IOC_SET_EVENT: ++ return ntsync_set_event(dev, argp); + case NTSYNC_IOC_WAIT_ALL: + return ntsync_wait_all(dev, argp); + case NTSYNC_IOC_WAIT_ANY: +diff --git a/include/uapi/linux/ntsync.h b/include/uapi/linux/ntsync.h +index c7cbdeff32c9..8a230f5d4b3f 100644 +--- a/include/uapi/linux/ntsync.h ++++ b/include/uapi/linux/ntsync.h +@@ -61,5 +61,7 @@ struct ntsync_wait_args { + struct ntsync_mutex_args) + #define NTSYNC_IOC_CREATE_EVENT _IOWR(NTSYNC_IOC_BASE, 10, \ + struct ntsync_event_args) ++#define NTSYNC_IOC_SET_EVENT _IOWR(NTSYNC_IOC_BASE, 11, \ ++ struct ntsync_event_args) + + #endif +-- +2.43.0 + +From 03224658123725d6b8a27b55cd103ad782ebad7b Mon Sep 17 00:00:00 2001 +From: Elizabeth Figura <zfigura@codeweavers.com> +Date: Wed, 19 Jan 2022 19:00:25 -0600 +Subject: [PATCH 14/32] ntsync: Introduce NTSYNC_IOC_RESET_EVENT. + +This corresponds to the NT syscall NtResetEvent(). +--- + drivers/misc/ntsync.c | 31 +++++++++++++++++++++++++++++++ + include/uapi/linux/ntsync.h | 2 ++ + 2 files changed, 33 insertions(+) + +diff --git a/drivers/misc/ntsync.c b/drivers/misc/ntsync.c +index cd6478bd93bd..151714dee0ff 100644 +--- a/drivers/misc/ntsync.c ++++ b/drivers/misc/ntsync.c +@@ -746,6 +746,35 @@ static int ntsync_set_event(struct ntsync_device *dev, void __user *argp) + return 0; + } + ++static int ntsync_reset_event(struct ntsync_device *dev, void __user *argp) ++{ ++ struct ntsync_event_args __user *user_args = argp; ++ struct ntsync_event_args args; ++ struct ntsync_obj *event; ++ bool prev_state; ++ ++ if (copy_from_user(&args, argp, sizeof(args))) ++ return -EFAULT; ++ ++ event = get_obj_typed(dev, args.event, NTSYNC_TYPE_EVENT); ++ if (!event) ++ return -EINVAL; ++ ++ spin_lock(&event->lock); ++ ++ prev_state = event->u.event.signaled; ++ event->u.event.signaled = false; ++ ++ spin_unlock(&event->lock); ++ ++ put_obj(event); ++ ++ if (put_user(prev_state, &user_args->signaled)) ++ return -EFAULT; ++ ++ return 0; ++} ++ + static int ntsync_schedule(const struct ntsync_q *q, ktime_t *timeout) + { + int ret = 0; +@@ -1051,6 +1080,8 @@ static long ntsync_char_ioctl(struct file *file, unsigned int cmd, + return ntsync_read_mutex(dev, argp); + case NTSYNC_IOC_READ_SEM: + return ntsync_read_sem(dev, argp); ++ case NTSYNC_IOC_RESET_EVENT: ++ return ntsync_reset_event(dev, argp); + case NTSYNC_IOC_SET_EVENT: + return ntsync_set_event(dev, argp); + case NTSYNC_IOC_WAIT_ALL: +diff --git a/include/uapi/linux/ntsync.h b/include/uapi/linux/ntsync.h +index 8a230f5d4b3f..806125856117 100644 +--- a/include/uapi/linux/ntsync.h ++++ b/include/uapi/linux/ntsync.h +@@ -63,5 +63,7 @@ struct ntsync_wait_args { + struct ntsync_event_args) + #define NTSYNC_IOC_SET_EVENT _IOWR(NTSYNC_IOC_BASE, 11, \ + struct ntsync_event_args) ++#define NTSYNC_IOC_RESET_EVENT _IOWR(NTSYNC_IOC_BASE, 12, \ ++ struct ntsync_event_args) + + #endif +-- +2.43.0 + +From 16daeb0c9b8d95ab27c0c807c49ddebb5dba52c0 Mon Sep 17 00:00:00 2001 +From: Elizabeth Figura <zfigura@codeweavers.com> +Date: Wed, 19 Jan 2022 19:10:12 -0600 +Subject: [PATCH 15/32] ntsync: Introduce NTSYNC_IOC_PULSE_EVENT. + +This corresponds to the NT syscall NtPulseEvent(). +--- + drivers/misc/ntsync.c | 11 +++++++++-- + include/uapi/linux/ntsync.h | 2 ++ + 2 files changed, 11 insertions(+), 2 deletions(-) + +diff --git a/drivers/misc/ntsync.c b/drivers/misc/ntsync.c +index 151714dee0ff..45405d01e11d 100644 +--- a/drivers/misc/ntsync.c ++++ b/drivers/misc/ntsync.c +@@ -703,7 +703,8 @@ static int ntsync_kill_owner(struct ntsync_device *dev, void __user *argp) + return 0; + } + +-static int ntsync_set_event(struct ntsync_device *dev, void __user *argp) ++static int ntsync_set_event(struct ntsync_device *dev, void __user *argp, ++ bool pulse) + { + struct ntsync_event_args __user *user_args = argp; + struct ntsync_event_args args; +@@ -725,6 +726,8 @@ static int ntsync_set_event(struct ntsync_device *dev, void __user *argp) + event->u.event.signaled = true; + try_wake_all_obj(dev, event); + try_wake_any_event(event); ++ if (pulse) ++ event->u.event.signaled = false; + + spin_unlock(&event->lock); + spin_unlock(&dev->wait_all_lock); +@@ -734,6 +737,8 @@ static int ntsync_set_event(struct ntsync_device *dev, void __user *argp) + prev_state = event->u.event.signaled; + event->u.event.signaled = true; + try_wake_any_event(event); ++ if (pulse) ++ event->u.event.signaled = false; + + spin_unlock(&event->lock); + } +@@ -1072,6 +1077,8 @@ static long ntsync_char_ioctl(struct file *file, unsigned int cmd, + return ntsync_delete(dev, argp); + case NTSYNC_IOC_KILL_OWNER: + return ntsync_kill_owner(dev, argp); ++ case NTSYNC_IOC_PULSE_EVENT: ++ return ntsync_set_event(dev, argp, true); + case NTSYNC_IOC_PUT_MUTEX: + return ntsync_put_mutex(dev, argp); + case NTSYNC_IOC_PUT_SEM: +@@ -1083,7 +1090,7 @@ static long ntsync_char_ioctl(struct file *file, unsigned int cmd, + case NTSYNC_IOC_RESET_EVENT: + return ntsync_reset_event(dev, argp); + case NTSYNC_IOC_SET_EVENT: +- return ntsync_set_event(dev, argp); ++ return ntsync_set_event(dev, argp, false); + case NTSYNC_IOC_WAIT_ALL: + return ntsync_wait_all(dev, argp); + case NTSYNC_IOC_WAIT_ANY: +diff --git a/include/uapi/linux/ntsync.h b/include/uapi/linux/ntsync.h +index 806125856117..30f56b4388c8 100644 +--- a/include/uapi/linux/ntsync.h ++++ b/include/uapi/linux/ntsync.h +@@ -65,5 +65,7 @@ struct ntsync_wait_args { + struct ntsync_event_args) + #define NTSYNC_IOC_RESET_EVENT _IOWR(NTSYNC_IOC_BASE, 12, \ + struct ntsync_event_args) ++#define NTSYNC_IOC_PULSE_EVENT _IOWR(NTSYNC_IOC_BASE, 13, \ ++ struct ntsync_event_args) + + #endif +-- +2.43.0 + +From c264fe3627e7032d88cd6d56f4ce6a2c2fbc30af Mon Sep 17 00:00:00 2001 +From: Elizabeth Figura <zfigura@codeweavers.com> +Date: Wed, 19 Jan 2022 19:14:00 -0600 +Subject: [PATCH 16/32] ntsync: Introduce NTSYNC_IOC_READ_EVENT. + +This corresponds to the NT syscall NtQueryEvent(). +--- + drivers/misc/ntsync.c | 29 +++++++++++++++++++++++++++++ + include/uapi/linux/ntsync.h | 2 ++ + 2 files changed, 31 insertions(+) + +diff --git a/drivers/misc/ntsync.c b/drivers/misc/ntsync.c +index 45405d01e11d..6821d9aa967d 100644 +--- a/drivers/misc/ntsync.c ++++ b/drivers/misc/ntsync.c +@@ -638,6 +638,33 @@ static int ntsync_read_mutex(struct ntsync_device *dev, void __user *argp) + return ret; + } + ++static int ntsync_read_event(struct ntsync_device *dev, void __user *argp) ++{ ++ struct ntsync_event_args __user *user_args = argp; ++ struct ntsync_event_args args; ++ struct ntsync_obj *event; ++ __u32 id; ++ ++ if (get_user(id, &user_args->event)) ++ return -EFAULT; ++ ++ event = get_obj_typed(dev, id, NTSYNC_TYPE_EVENT); ++ if (!event) ++ return -EINVAL; ++ ++ args.event = id; ++ spin_lock(&event->lock); ++ args.manual = event->u.event.manual; ++ args.signaled = event->u.event.signaled; ++ spin_unlock(&event->lock); ++ ++ put_obj(event); ++ ++ if (copy_to_user(user_args, &args, sizeof(args))) ++ return -EFAULT; ++ return 0; ++} ++ + /* + * Actually change the mutex state to mark its owner as dead. + */ +@@ -1083,6 +1110,8 @@ static long ntsync_char_ioctl(struct file *file, unsigned int cmd, + return ntsync_put_mutex(dev, argp); + case NTSYNC_IOC_PUT_SEM: + return ntsync_put_sem(dev, argp); ++ case NTSYNC_IOC_READ_EVENT: ++ return ntsync_read_event(dev, argp); + case NTSYNC_IOC_READ_MUTEX: + return ntsync_read_mutex(dev, argp); + case NTSYNC_IOC_READ_SEM: +diff --git a/include/uapi/linux/ntsync.h b/include/uapi/linux/ntsync.h +index 30f56b4388c8..86c28b909777 100644 +--- a/include/uapi/linux/ntsync.h ++++ b/include/uapi/linux/ntsync.h +@@ -67,5 +67,7 @@ struct ntsync_wait_args { + struct ntsync_event_args) + #define NTSYNC_IOC_PULSE_EVENT _IOWR(NTSYNC_IOC_BASE, 13, \ + struct ntsync_event_args) ++#define NTSYNC_IOC_READ_EVENT _IOWR(NTSYNC_IOC_BASE, 14, \ ++ struct ntsync_event_args) + + #endif +-- +2.43.0 + +From 198ddbebdbe3d062505bde44619da050eead04e8 Mon Sep 17 00:00:00 2001 +From: Elizabeth Figura <zfigura@codeweavers.com> +Date: Wed, 13 Apr 2022 20:02:39 -0500 +Subject: [PATCH 17/32] ntsync: Introduce alertable waits. + +This implements the "alertable" feature of NtWaitForMultipleObjects(). +Alerts are implemented using events; the user-space NT emulator is expected to +create an internal ntsync event for each thread and pass that event to wait +functions. +--- + drivers/misc/ntsync.c | 68 ++++++++++++++++++++++++++++++++----- + include/uapi/linux/ntsync.h | 2 +- + 2 files changed, 60 insertions(+), 10 deletions(-) + +diff --git a/drivers/misc/ntsync.c b/drivers/misc/ntsync.c +index 6821d9aa967d..6f4cb9a3ed5a 100644 +--- a/drivers/misc/ntsync.c ++++ b/drivers/misc/ntsync.c +@@ -840,10 +840,11 @@ static int setup_wait(struct ntsync_device *dev, + const __u32 count = args->count; + struct ntsync_q *q; + ktime_t timeout = 0; ++ __u32 total_count; + __u32 *ids; + __u32 i, j; + +- if (!args->owner || args->pad) ++ if (!args->owner) + return -EINVAL; + + if (args->count > NTSYNC_MAX_WAIT_COUNT) +@@ -860,7 +861,11 @@ static int setup_wait(struct ntsync_device *dev, + timeout = timespec64_to_ns(&to); + } + +- ids = kmalloc_array(count, sizeof(*ids), GFP_KERNEL); ++ total_count = count; ++ if (args->alert) ++ total_count++; ++ ++ ids = kmalloc_array(total_count, sizeof(*ids), GFP_KERNEL); + if (!ids) + return -ENOMEM; + if (copy_from_user(ids, u64_to_user_ptr(args->objs), +@@ -868,8 +873,10 @@ static int setup_wait(struct ntsync_device *dev, + kfree(ids); + return -EFAULT; + } ++ if (args->alert) ++ ids[count] = args->alert; + +- q = kmalloc(struct_size(q, entries, count), GFP_KERNEL); ++ q = kmalloc(struct_size(q, entries, total_count), GFP_KERNEL); + if (!q) { + kfree(ids); + return -ENOMEM; +@@ -881,7 +888,7 @@ static int setup_wait(struct ntsync_device *dev, + q->ownerdead = false; + q->count = count; + +- for (i = 0; i < count; i++) { ++ for (i = 0; i < total_count; i++) { + struct ntsync_q_entry *entry = &q->entries[i]; + struct ntsync_obj *obj = get_obj(dev, ids[i]); + +@@ -936,9 +943,9 @@ static int ntsync_wait_any(struct ntsync_device *dev, void __user *argp) + { + struct ntsync_wait_args args; + struct ntsync_q *q; ++ __u32 i, total_count; + ktime_t timeout; + int signaled; +- __u32 i; + int ret; + + if (copy_from_user(&args, argp, sizeof(args))) +@@ -948,9 +955,13 @@ static int ntsync_wait_any(struct ntsync_device *dev, void __user *argp) + if (ret < 0) + return ret; + ++ total_count = args.count; ++ if (args.alert) ++ total_count++; ++ + /* queue ourselves */ + +- for (i = 0; i < args.count; i++) { ++ for (i = 0; i < total_count; i++) { + struct ntsync_q_entry *entry = &q->entries[i]; + struct ntsync_obj *obj = entry->obj; + +@@ -959,9 +970,15 @@ static int ntsync_wait_any(struct ntsync_device *dev, void __user *argp) + spin_unlock(&obj->lock); + } + +- /* check if we are already signaled */ ++ /* ++ * Check if we are already signaled. ++ * ++ * Note that the API requires that normal objects are checked before ++ * the alert event. Hence we queue the alert event last, and check ++ * objects in order. ++ */ + +- for (i = 0; i < args.count; i++) { ++ for (i = 0; i < total_count; i++) { + struct ntsync_obj *obj = q->entries[i].obj; + + if (atomic_read(&q->signaled) != -1) +@@ -978,7 +995,7 @@ static int ntsync_wait_any(struct ntsync_device *dev, void __user *argp) + + /* and finally, unqueue */ + +- for (i = 0; i < args.count; i++) { ++ for (i = 0; i < total_count; i++) { + struct ntsync_q_entry *entry = &q->entries[i]; + struct ntsync_obj *obj = entry->obj; + +@@ -1038,6 +1055,14 @@ static int ntsync_wait_all(struct ntsync_device *dev, void __user *argp) + */ + list_add_tail(&entry->node, &obj->all_waiters); + } ++ if (args.alert) { ++ struct ntsync_q_entry *entry = &q->entries[args.count]; ++ struct ntsync_obj *obj = entry->obj; ++ ++ spin_lock_nest_lock(&obj->lock, &dev->wait_all_lock); ++ list_add_tail(&entry->node, &obj->any_waiters); ++ spin_unlock(&obj->lock); ++ } + + /* check if we are already signaled */ + +@@ -1045,6 +1070,21 @@ static int ntsync_wait_all(struct ntsync_device *dev, void __user *argp) + + spin_unlock(&dev->wait_all_lock); + ++ /* ++ * Check if the alert event is signaled, making sure to do so only ++ * after checking if the other objects are signaled. ++ */ ++ ++ if (args.alert) { ++ struct ntsync_obj *obj = q->entries[args.count].obj; ++ ++ if (atomic_read(&q->signaled) == -1) { ++ spin_lock(&obj->lock); ++ try_wake_any_obj(obj); ++ spin_unlock(&obj->lock); ++ } ++ } ++ + /* sleep */ + + ret = ntsync_schedule(q, args.timeout ? &timeout : NULL); +@@ -1067,6 +1107,16 @@ static int ntsync_wait_all(struct ntsync_device *dev, void __user *argp) + + put_obj(obj); + } ++ if (args.alert) { ++ struct ntsync_q_entry *entry = &q->entries[args.count]; ++ struct ntsync_obj *obj = entry->obj; ++ ++ spin_lock_nest_lock(&obj->lock, &dev->wait_all_lock); ++ list_del(&entry->node); ++ spin_unlock(&obj->lock); ++ ++ put_obj(obj); ++ } + + spin_unlock(&dev->wait_all_lock); + +diff --git a/include/uapi/linux/ntsync.h b/include/uapi/linux/ntsync.h +index 86c28b909777..e145b36a15ea 100644 +--- a/include/uapi/linux/ntsync.h ++++ b/include/uapi/linux/ntsync.h +@@ -34,7 +34,7 @@ struct ntsync_wait_args { + __u32 count; + __u32 owner; + __u32 index; +- __u32 pad; ++ __u32 alert; + }; + + #define NTSYNC_MAX_WAIT_COUNT 64 +-- +2.43.0 + +From 9208836580ed5e5aadfdfaf59dc8352ff6c4a07c Mon Sep 17 00:00:00 2001 +From: Elizabeth Figura <zfigura@codeweavers.com> +Date: Fri, 5 Mar 2021 12:06:23 -0600 +Subject: [PATCH 18/32] selftests: ntsync: Add some tests for semaphore state. + +--- + tools/testing/selftests/Makefile | 1 + + .../testing/selftests/drivers/ntsync/Makefile | 8 + + tools/testing/selftests/drivers/ntsync/config | 1 + + .../testing/selftests/drivers/ntsync/ntsync.c | 153 ++++++++++++++++++ + 4 files changed, 163 insertions(+) + create mode 100644 tools/testing/selftests/drivers/ntsync/Makefile + create mode 100644 tools/testing/selftests/drivers/ntsync/config + create mode 100644 tools/testing/selftests/drivers/ntsync/ntsync.c + +diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile +index 8247a7c69c36..40b7318a8e22 100644 +--- a/tools/testing/selftests/Makefile ++++ b/tools/testing/selftests/Makefile +@@ -15,6 +15,7 @@ TARGETS += cpu-hotplug + TARGETS += damon + TARGETS += dmabuf-heaps + TARGETS += drivers/dma-buf ++TARGETS += drivers/ntsync + TARGETS += drivers/s390x/uvdevice + TARGETS += drivers/net/bonding + TARGETS += drivers/net/team +diff --git a/tools/testing/selftests/drivers/ntsync/Makefile b/tools/testing/selftests/drivers/ntsync/Makefile +new file mode 100644 +index 000000000000..a34da5ccacf0 +--- /dev/null ++++ b/tools/testing/selftests/drivers/ntsync/Makefile +@@ -0,0 +1,8 @@ ++# SPDX-LICENSE-IDENTIFIER: GPL-2.0-only ++TEST_GEN_PROGS := ntsync ++ ++top_srcdir =../../../../.. ++CFLAGS += -I$(top_srcdir)/usr/include ++LDLIBS += -lpthread ++ ++include ../../lib.mk +diff --git a/tools/testing/selftests/drivers/ntsync/config b/tools/testing/selftests/drivers/ntsync/config +new file mode 100644 +index 000000000000..60539c826d06 +--- /dev/null ++++ b/tools/testing/selftests/drivers/ntsync/config +@@ -0,0 +1 @@ ++CONFIG_WINESYNC=y +diff --git a/tools/testing/selftests/drivers/ntsync/ntsync.c b/tools/testing/selftests/drivers/ntsync/ntsync.c +new file mode 100644 +index 000000000000..777c408e8f9e +--- /dev/null ++++ b/tools/testing/selftests/drivers/ntsync/ntsync.c +@@ -0,0 +1,153 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Various unit tests for the "ntsync" synchronization primitive driver. ++ * ++ * Copyright (C) 2021-2022 Elizabeth Figura ++ */ ++ ++#define _GNU_SOURCE ++#include <sys/ioctl.h> ++#include <sys/stat.h> ++#include <fcntl.h> ++#include <time.h> ++#include <pthread.h> ++#include <linux/ntsync.h> ++#include "../../kselftest_harness.h" ++ ++static int read_sem_state(int fd, __u32 sem, __u32 *count, __u32 *max) ++{ ++ struct ntsync_sem_args args; ++ int ret; ++ ++ args.sem = sem; ++ args.count = 0xdeadbeef; ++ args.max = 0xdeadbeef; ++ ret = ioctl(fd, NTSYNC_IOC_READ_SEM, &args); ++ *count = args.count; ++ *max = args.max; ++ return ret; ++} ++ ++#define check_sem_state(fd, sem, count, max) \ ++ ({ \ ++ __u32 __count, __max; \ ++ int ret = read_sem_state((fd), (sem), &__count, &__max); \ ++ EXPECT_EQ(0, ret); \ ++ EXPECT_EQ((count), __count); \ ++ EXPECT_EQ((max), __max); \ ++ }) ++ ++static int put_sem(int fd, __u32 sem, __u32 *count) ++{ ++ struct ntsync_sem_args args; ++ int ret; ++ ++ args.sem = sem; ++ args.count = *count; ++ ret = ioctl(fd, NTSYNC_IOC_PUT_SEM, &args); ++ *count = args.count; ++ return ret; ++} ++ ++static int wait_any(int fd, __u32 count, const __u32 *objs, __u32 owner, ++ __u32 *index) ++{ ++ struct ntsync_wait_args args = {0}; ++ struct timespec timeout; ++ int ret; ++ ++ clock_gettime(CLOCK_MONOTONIC, &timeout); ++ ++ args.timeout = (uintptr_t)&timeout; ++ args.count = count; ++ args.objs = (uintptr_t)objs; ++ args.owner = owner; ++ args.index = 0xdeadbeef; ++ ret = ioctl(fd, NTSYNC_IOC_WAIT_ANY, &args); ++ *index = args.index; ++ return ret; ++} ++ ++TEST(semaphore_state) ++{ ++ struct ntsync_sem_args sem_args; ++ struct timespec timeout; ++ __u32 count, index; ++ int fd, ret; ++ ++ clock_gettime(CLOCK_MONOTONIC, &timeout); ++ ++ fd = open("/dev/ntsync", O_CLOEXEC | O_RDONLY); ++ ASSERT_LE(0, fd); ++ ++ sem_args.count = 3; ++ sem_args.max = 2; ++ sem_args.sem = 0xdeadbeef; ++ ret = ioctl(fd, NTSYNC_IOC_CREATE_SEM, &sem_args); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EINVAL, errno); ++ ++ sem_args.count = 2; ++ sem_args.max = 2; ++ sem_args.sem = 0xdeadbeef; ++ ret = ioctl(fd, NTSYNC_IOC_CREATE_SEM, &sem_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_NE(0xdeadbeef, sem_args.sem); ++ check_sem_state(fd, sem_args.sem, 2, 2); ++ ++ count = 0; ++ ret = put_sem(fd, sem_args.sem, &count); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(2, count); ++ check_sem_state(fd, sem_args.sem, 2, 2); ++ ++ count = 1; ++ ret = put_sem(fd, sem_args.sem, &count); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EOVERFLOW, errno); ++ check_sem_state(fd, sem_args.sem, 2, 2); ++ ++ ret = wait_any(fd, 1, &sem_args.sem, 123, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, index); ++ check_sem_state(fd, sem_args.sem, 1, 2); ++ ++ ret = wait_any(fd, 1, &sem_args.sem, 123, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, index); ++ check_sem_state(fd, sem_args.sem, 0, 2); ++ ++ ret = wait_any(fd, 1, &sem_args.sem, 123, &index); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(ETIMEDOUT, errno); ++ ++ count = 3; ++ ret = put_sem(fd, sem_args.sem, &count); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EOVERFLOW, errno); ++ check_sem_state(fd, sem_args.sem, 0, 2); ++ ++ count = 2; ++ ret = put_sem(fd, sem_args.sem, &count); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, count); ++ check_sem_state(fd, sem_args.sem, 2, 2); ++ ++ ret = wait_any(fd, 1, &sem_args.sem, 123, &index); ++ EXPECT_EQ(0, ret); ++ ret = wait_any(fd, 1, &sem_args.sem, 123, &index); ++ EXPECT_EQ(0, ret); ++ ++ count = 1; ++ ret = put_sem(fd, sem_args.sem, &count); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, count); ++ check_sem_state(fd, sem_args.sem, 1, 2); ++ ++ ret = ioctl(fd, NTSYNC_IOC_DELETE, &sem_args.sem); ++ EXPECT_EQ(0, ret); ++ ++ close(fd); ++} ++ ++TEST_HARNESS_MAIN +-- +2.43.0 + +From 7f8e2b5dedf3fe0192bcf7924d651d33df8898a9 Mon Sep 17 00:00:00 2001 +From: Elizabeth Figura <zfigura@codeweavers.com> +Date: Fri, 5 Mar 2021 12:07:04 -0600 +Subject: [PATCH 19/32] selftests: ntsync: Add some tests for mutex state. + +--- + .../testing/selftests/drivers/ntsync/ntsync.c | 188 ++++++++++++++++++ + 1 file changed, 188 insertions(+) + +diff --git a/tools/testing/selftests/drivers/ntsync/ntsync.c b/tools/testing/selftests/drivers/ntsync/ntsync.c +index 777c408e8f9e..8390dbbb1dd5 100644 +--- a/tools/testing/selftests/drivers/ntsync/ntsync.c ++++ b/tools/testing/selftests/drivers/ntsync/ntsync.c +@@ -49,6 +49,42 @@ static int put_sem(int fd, __u32 sem, __u32 *count) + return ret; + } + ++static int read_mutex_state(int fd, __u32 mutex, __u32 *count, __u32 *owner) ++{ ++ struct ntsync_mutex_args args; ++ int ret; ++ ++ args.mutex = mutex; ++ args.count = 0xdeadbeef; ++ args.owner = 0xdeadbeef; ++ ret = ioctl(fd, NTSYNC_IOC_READ_MUTEX, &args); ++ *count = args.count; ++ *owner = args.owner; ++ return ret; ++} ++ ++#define check_mutex_state(fd, mutex, count, owner) \ ++ ({ \ ++ __u32 __count, __owner; \ ++ int ret = read_mutex_state((fd), (mutex), &__count, &__owner); \ ++ EXPECT_EQ(0, ret); \ ++ EXPECT_EQ((count), __count); \ ++ EXPECT_EQ((owner), __owner); \ ++ }) ++ ++static int put_mutex(int fd, __u32 mutex, __u32 owner, __u32 *count) ++{ ++ struct ntsync_mutex_args args; ++ int ret; ++ ++ args.mutex = mutex; ++ args.owner = owner; ++ args.count = 0xdeadbeef; ++ ret = ioctl(fd, NTSYNC_IOC_PUT_MUTEX, &args); ++ *count = args.count; ++ return ret; ++} ++ + static int wait_any(int fd, __u32 count, const __u32 *objs, __u32 owner, + __u32 *index) + { +@@ -150,4 +186,156 @@ TEST(semaphore_state) + close(fd); + } + ++TEST(mutex_state) ++{ ++ struct ntsync_mutex_args mutex_args; ++ __u32 mutex, owner, count, index; ++ struct timespec timeout; ++ int fd, ret; ++ ++ clock_gettime(CLOCK_MONOTONIC, &timeout); ++ ++ fd = open("/dev/ntsync", O_CLOEXEC | O_RDONLY); ++ ASSERT_LE(0, fd); ++ ++ mutex_args.owner = 123; ++ mutex_args.count = 0; ++ ret = ioctl(fd, NTSYNC_IOC_CREATE_MUTEX, &mutex_args); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EINVAL, errno); ++ ++ mutex_args.owner = 0; ++ mutex_args.count = 2; ++ ret = ioctl(fd, NTSYNC_IOC_CREATE_MUTEX, &mutex_args); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EINVAL, errno); ++ ++ mutex_args.owner = 123; ++ mutex_args.count = 2; ++ mutex_args.mutex = 0xdeadbeef; ++ ret = ioctl(fd, NTSYNC_IOC_CREATE_MUTEX, &mutex_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_NE(0xdeadbeef, mutex_args.mutex); ++ mutex = mutex_args.mutex; ++ check_mutex_state(fd, mutex, 2, 123); ++ ++ ret = put_mutex(fd, mutex, 0, &count); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EINVAL, errno); ++ ++ ret = put_mutex(fd, mutex, 456, &count); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EPERM, errno); ++ check_mutex_state(fd, mutex, 2, 123); ++ ++ ret = put_mutex(fd, mutex, 123, &count); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(2, count); ++ check_mutex_state(fd, mutex, 1, 123); ++ ++ ret = put_mutex(fd, mutex, 123, &count); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(1, count); ++ check_mutex_state(fd, mutex, 0, 0); ++ ++ ret = put_mutex(fd, mutex, 123, &count); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EPERM, errno); ++ ++ ret = wait_any(fd, 1, &mutex, 456, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, index); ++ check_mutex_state(fd, mutex, 1, 456); ++ ++ ret = wait_any(fd, 1, &mutex, 456, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, index); ++ check_mutex_state(fd, mutex, 2, 456); ++ ++ ret = put_mutex(fd, mutex, 456, &count); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(2, count); ++ check_mutex_state(fd, mutex, 1, 456); ++ ++ ret = wait_any(fd, 1, &mutex, 123, &index); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(ETIMEDOUT, errno); ++ ++ owner = 0; ++ ret = ioctl(fd, NTSYNC_IOC_KILL_OWNER, &owner); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EINVAL, errno); ++ ++ owner = 123; ++ ret = ioctl(fd, NTSYNC_IOC_KILL_OWNER, &owner); ++ EXPECT_EQ(0, ret); ++ check_mutex_state(fd, mutex, 1, 456); ++ ++ owner = 456; ++ ret = ioctl(fd, NTSYNC_IOC_KILL_OWNER, &owner); ++ EXPECT_EQ(0, ret); ++ ++ mutex_args.count = 0xdeadbeef; ++ mutex_args.owner = 0xdeadbeef; ++ ret = ioctl(fd, NTSYNC_IOC_READ_MUTEX, &mutex_args); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EOWNERDEAD, errno); ++ EXPECT_EQ(0, mutex_args.count); ++ EXPECT_EQ(0, mutex_args.owner); ++ ++ mutex_args.count = 0xdeadbeef; ++ mutex_args.owner = 0xdeadbeef; ++ ret = ioctl(fd, NTSYNC_IOC_READ_MUTEX, &mutex_args); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EOWNERDEAD, errno); ++ EXPECT_EQ(0, mutex_args.count); ++ EXPECT_EQ(0, mutex_args.owner); ++ ++ ret = wait_any(fd, 1, &mutex, 123, &index); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EOWNERDEAD, errno); ++ EXPECT_EQ(0, index); ++ check_mutex_state(fd, mutex, 1, 123); ++ ++ owner = 123; ++ ret = ioctl(fd, NTSYNC_IOC_KILL_OWNER, &owner); ++ EXPECT_EQ(0, ret); ++ ++ mutex_args.count = 0xdeadbeef; ++ mutex_args.owner = 0xdeadbeef; ++ ret = ioctl(fd, NTSYNC_IOC_READ_MUTEX, &mutex_args); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EOWNERDEAD, errno); ++ EXPECT_EQ(0, mutex_args.count); ++ EXPECT_EQ(0, mutex_args.owner); ++ ++ ret = wait_any(fd, 1, &mutex, 123, &index); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EOWNERDEAD, errno); ++ EXPECT_EQ(0, index); ++ check_mutex_state(fd, mutex, 1, 123); ++ ++ ret = ioctl(fd, NTSYNC_IOC_DELETE, &mutex); ++ EXPECT_EQ(0, ret); ++ ++ mutex_args.owner = 0; ++ mutex_args.count = 0; ++ mutex_args.mutex = 0xdeadbeef; ++ ret = ioctl(fd, NTSYNC_IOC_CREATE_MUTEX, &mutex_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_NE(0xdeadbeef, mutex_args.mutex); ++ mutex = mutex_args.mutex; ++ check_mutex_state(fd, mutex, 0, 0); ++ ++ ret = wait_any(fd, 1, &mutex, 123, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, index); ++ check_mutex_state(fd, mutex, 1, 123); ++ ++ ret = ioctl(fd, NTSYNC_IOC_DELETE, &mutex_args.mutex); ++ EXPECT_EQ(0, ret); ++ ++ close(fd); ++} ++ + TEST_HARNESS_MAIN +-- +2.43.0 + +From 596798d186edb9ca00396222e9dc0c43fcb40548 Mon Sep 17 00:00:00 2001 +From: Elizabeth Figura <zfigura@codeweavers.com> +Date: Fri, 5 Mar 2021 12:07:45 -0600 +Subject: [PATCH 20/32] selftests: ntsync: Add some tests for + WINESYNC_IOC_WAIT_ANY. + +--- + .../testing/selftests/drivers/ntsync/ntsync.c | 107 ++++++++++++++++++ + 1 file changed, 107 insertions(+) + +diff --git a/tools/testing/selftests/drivers/ntsync/ntsync.c b/tools/testing/selftests/drivers/ntsync/ntsync.c +index 8390dbbb1dd5..8ae930e7a601 100644 +--- a/tools/testing/selftests/drivers/ntsync/ntsync.c ++++ b/tools/testing/selftests/drivers/ntsync/ntsync.c +@@ -338,4 +338,111 @@ TEST(mutex_state) + close(fd); + } + ++TEST(test_wait_any) ++{ ++ struct ntsync_mutex_args mutex_args = {0}; ++ struct ntsync_wait_args wait_args = {0}; ++ struct ntsync_sem_args sem_args = {0}; ++ __u32 objs[2], owner, index; ++ struct timespec timeout; ++ int fd, ret; ++ ++ clock_gettime(CLOCK_MONOTONIC, &timeout); ++ ++ fd = open("/dev/ntsync", O_CLOEXEC | O_RDONLY); ++ ASSERT_LE(0, fd); ++ ++ sem_args.count = 2; ++ sem_args.max = 3; ++ sem_args.sem = 0xdeadbeef; ++ ret = ioctl(fd, NTSYNC_IOC_CREATE_SEM, &sem_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_NE(0xdeadbeef, sem_args.sem); ++ ++ mutex_args.owner = 0; ++ mutex_args.count = 0; ++ mutex_args.mutex = 0xdeadbeef; ++ ret = ioctl(fd, NTSYNC_IOC_CREATE_MUTEX, &mutex_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_NE(0xdeadbeef, mutex_args.mutex); ++ ++ objs[0] = sem_args.sem; ++ objs[1] = mutex_args.mutex; ++ ++ ret = wait_any(fd, 2, objs, 123, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, index); ++ check_sem_state(fd, sem_args.sem, 1, 3); ++ check_mutex_state(fd, mutex_args.mutex, 0, 0); ++ ++ ret = wait_any(fd, 2, objs, 123, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, index); ++ check_sem_state(fd, sem_args.sem, 0, 3); ++ check_mutex_state(fd, mutex_args.mutex, 0, 0); ++ ++ ret = wait_any(fd, 2, objs, 123, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(1, index); ++ check_sem_state(fd, sem_args.sem, 0, 3); ++ check_mutex_state(fd, mutex_args.mutex, 1, 123); ++ ++ sem_args.count = 1; ++ ret = ioctl(fd, NTSYNC_IOC_PUT_SEM, &sem_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, sem_args.count); ++ ++ ret = wait_any(fd, 2, objs, 123, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, index); ++ check_sem_state(fd, sem_args.sem, 0, 3); ++ check_mutex_state(fd, mutex_args.mutex, 1, 123); ++ ++ ret = wait_any(fd, 2, objs, 123, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(1, index); ++ check_sem_state(fd, sem_args.sem, 0, 3); ++ check_mutex_state(fd, mutex_args.mutex, 2, 123); ++ ++ ret = wait_any(fd, 2, objs, 456, &index); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(ETIMEDOUT, errno); ++ ++ owner = 123; ++ ret = ioctl(fd, NTSYNC_IOC_KILL_OWNER, &owner); ++ EXPECT_EQ(0, ret); ++ ++ ret = wait_any(fd, 2, objs, 456, &index); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EOWNERDEAD, errno); ++ EXPECT_EQ(1, index); ++ ++ ret = wait_any(fd, 2, objs, 456, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(1, index); ++ ++ /* test waiting on the same object twice */ ++ sem_args.count = 2; ++ ret = ioctl(fd, NTSYNC_IOC_PUT_SEM, &sem_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, sem_args.count); ++ ++ objs[0] = objs[1] = sem_args.sem; ++ ret = wait_any(fd, 2, objs, 456, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, wait_args.index); ++ check_sem_state(fd, sem_args.sem, 1, 3); ++ ++ ret = wait_any(fd, 0, NULL, 456, &index); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(ETIMEDOUT, errno); ++ ++ ret = ioctl(fd, NTSYNC_IOC_DELETE, &sem_args.sem); ++ EXPECT_EQ(0, ret); ++ ret = ioctl(fd, NTSYNC_IOC_DELETE, &mutex_args.mutex); ++ EXPECT_EQ(0, ret); ++ ++ close(fd); ++} ++ + TEST_HARNESS_MAIN +-- +2.43.0 + +From 4610258d640a5bd75b7482b92cb74c4ffba58b84 Mon Sep 17 00:00:00 2001 +From: Elizabeth Figura <zfigura@codeweavers.com> +Date: Fri, 5 Mar 2021 12:08:25 -0600 +Subject: [PATCH 21/32] selftests: ntsync: Add some tests for + WINESYNC_IOC_WAIT_ALL. + +--- + .../testing/selftests/drivers/ntsync/ntsync.c | 104 +++++++++++++++++- + 1 file changed, 101 insertions(+), 3 deletions(-) + +diff --git a/tools/testing/selftests/drivers/ntsync/ntsync.c b/tools/testing/selftests/drivers/ntsync/ntsync.c +index 8ae930e7a601..586e798d3228 100644 +--- a/tools/testing/selftests/drivers/ntsync/ntsync.c ++++ b/tools/testing/selftests/drivers/ntsync/ntsync.c +@@ -85,8 +85,8 @@ static int put_mutex(int fd, __u32 mutex, __u32 owner, __u32 *count) + return ret; + } + +-static int wait_any(int fd, __u32 count, const __u32 *objs, __u32 owner, +- __u32 *index) ++static int wait_objs(int fd, unsigned long request, __u32 count, ++ const __u32 *objs, __u32 owner, __u32 *index) + { + struct ntsync_wait_args args = {0}; + struct timespec timeout; +@@ -99,11 +99,23 @@ static int wait_any(int fd, __u32 count, const __u32 *objs, __u32 owner, + args.objs = (uintptr_t)objs; + args.owner = owner; + args.index = 0xdeadbeef; +- ret = ioctl(fd, NTSYNC_IOC_WAIT_ANY, &args); ++ ret = ioctl(fd, request, &args); + *index = args.index; + return ret; + } + ++static int wait_any(int fd, __u32 count, const __u32 *objs, ++ __u32 owner, __u32 *index) ++{ ++ return wait_objs(fd, NTSYNC_IOC_WAIT_ANY, count, objs, owner, index); ++} ++ ++static int wait_all(int fd, __u32 count, const __u32 *objs, ++ __u32 owner, __u32 *index) ++{ ++ return wait_objs(fd, NTSYNC_IOC_WAIT_ALL, count, objs, owner, index); ++} ++ + TEST(semaphore_state) + { + struct ntsync_sem_args sem_args; +@@ -445,4 +457,90 @@ TEST(test_wait_any) + close(fd); + } + ++TEST(test_wait_all) ++{ ++ struct ntsync_mutex_args mutex_args = {0}; ++ struct ntsync_sem_args sem_args = {0}; ++ __u32 objs[2], owner, index; ++ int fd, ret; ++ ++ fd = open("/dev/ntsync", O_CLOEXEC | O_RDONLY); ++ ASSERT_LE(0, fd); ++ ++ sem_args.count = 2; ++ sem_args.max = 3; ++ sem_args.sem = 0xdeadbeef; ++ ret = ioctl(fd, NTSYNC_IOC_CREATE_SEM, &sem_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_NE(0xdeadbeef, sem_args.sem); ++ ++ mutex_args.owner = 0; ++ mutex_args.count = 0; ++ mutex_args.mutex = 0xdeadbeef; ++ ret = ioctl(fd, NTSYNC_IOC_CREATE_MUTEX, &mutex_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_NE(0xdeadbeef, mutex_args.mutex); ++ ++ objs[0] = sem_args.sem; ++ objs[1] = mutex_args.mutex; ++ ++ ret = wait_all(fd, 2, objs, 123, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, index); ++ check_sem_state(fd, sem_args.sem, 1, 3); ++ check_mutex_state(fd, mutex_args.mutex, 1, 123); ++ ++ ret = wait_all(fd, 2, objs, 456, &index); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(ETIMEDOUT, errno); ++ check_sem_state(fd, sem_args.sem, 1, 3); ++ check_mutex_state(fd, mutex_args.mutex, 1, 123); ++ ++ ret = wait_all(fd, 2, objs, 123, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, index); ++ check_sem_state(fd, sem_args.sem, 0, 3); ++ check_mutex_state(fd, mutex_args.mutex, 2, 123); ++ ++ ret = wait_all(fd, 2, objs, 123, &index); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(ETIMEDOUT, errno); ++ check_sem_state(fd, sem_args.sem, 0, 3); ++ check_mutex_state(fd, mutex_args.mutex, 2, 123); ++ ++ sem_args.count = 3; ++ ret = ioctl(fd, NTSYNC_IOC_PUT_SEM, &sem_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, sem_args.count); ++ ++ ret = wait_all(fd, 2, objs, 123, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, index); ++ check_sem_state(fd, sem_args.sem, 2, 3); ++ check_mutex_state(fd, mutex_args.mutex, 3, 123); ++ ++ owner = 123; ++ ret = ioctl(fd, NTSYNC_IOC_KILL_OWNER, &owner); ++ EXPECT_EQ(0, ret); ++ ++ ret = wait_all(fd, 2, objs, 123, &index); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EOWNERDEAD, errno); ++ check_sem_state(fd, sem_args.sem, 1, 3); ++ check_mutex_state(fd, mutex_args.mutex, 1, 123); ++ ++ /* test waiting on the same object twice */ ++ objs[0] = objs[1] = sem_args.sem; ++ ret = wait_all(fd, 2, objs, 123, &index); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EINVAL, errno); ++ ++ ret = ioctl(fd, NTSYNC_IOC_DELETE, &sem_args.sem); ++ EXPECT_EQ(0, ret); ++ ret = ioctl(fd, NTSYNC_IOC_DELETE, &mutex_args.mutex); ++ EXPECT_EQ(0, ret); ++ ++ close(fd); ++} ++ + TEST_HARNESS_MAIN +-- +2.43.0 + +From a18ca868f284f8eec745aec2346b2ff138370473 Mon Sep 17 00:00:00 2001 +From: Elizabeth Figura <zfigura@codeweavers.com> +Date: Fri, 5 Mar 2021 12:08:54 -0600 +Subject: [PATCH 22/32] selftests: ntsync: Add some tests for invalid object + handling. + +--- + .../testing/selftests/drivers/ntsync/ntsync.c | 93 +++++++++++++++++++ + 1 file changed, 93 insertions(+) + +diff --git a/tools/testing/selftests/drivers/ntsync/ntsync.c b/tools/testing/selftests/drivers/ntsync/ntsync.c +index 586e798d3228..ac24a94f2518 100644 +--- a/tools/testing/selftests/drivers/ntsync/ntsync.c ++++ b/tools/testing/selftests/drivers/ntsync/ntsync.c +@@ -543,4 +543,97 @@ TEST(test_wait_all) + close(fd); + } + ++TEST(invalid_objects) ++{ ++ struct ntsync_mutex_args mutex_args = {0}; ++ struct ntsync_wait_args wait_args = {0}; ++ struct ntsync_sem_args sem_args = {0}; ++ __u32 objs[2] = {0}; ++ int fd, ret; ++ ++ fd = open("/dev/ntsync", O_CLOEXEC | O_RDONLY); ++ ASSERT_LE(0, fd); ++ ++ ret = ioctl(fd, NTSYNC_IOC_PUT_SEM, &sem_args); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EINVAL, errno); ++ ++ ret = ioctl(fd, NTSYNC_IOC_READ_SEM, &sem_args); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EINVAL, errno); ++ ++ ret = ioctl(fd, NTSYNC_IOC_PUT_MUTEX, &mutex_args); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EINVAL, errno); ++ ++ ret = ioctl(fd, NTSYNC_IOC_READ_MUTEX, &mutex_args); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EINVAL, errno); ++ ++ wait_args.objs = (uintptr_t)objs; ++ wait_args.count = 1; ++ ret = ioctl(fd, NTSYNC_IOC_WAIT_ANY, &wait_args); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EINVAL, errno); ++ ret = ioctl(fd, NTSYNC_IOC_WAIT_ALL, &wait_args); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EINVAL, errno); ++ ++ ret = ioctl(fd, NTSYNC_IOC_DELETE, &objs[0]); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EINVAL, errno); ++ ++ sem_args.max = 1; ++ ret = ioctl(fd, NTSYNC_IOC_CREATE_SEM, &sem_args); ++ EXPECT_EQ(0, ret); ++ ++ mutex_args.mutex = sem_args.sem; ++ ret = ioctl(fd, NTSYNC_IOC_PUT_MUTEX, &mutex_args); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EINVAL, errno); ++ ++ ret = ioctl(fd, NTSYNC_IOC_READ_MUTEX, &mutex_args); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EINVAL, errno); ++ ++ objs[0] = sem_args.sem; ++ objs[1] = sem_args.sem + 1; ++ wait_args.count = 2; ++ ret = ioctl(fd, NTSYNC_IOC_WAIT_ANY, &wait_args); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EINVAL, errno); ++ ret = ioctl(fd, NTSYNC_IOC_WAIT_ALL, &wait_args); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EINVAL, errno); ++ ++ objs[0] = sem_args.sem + 1; ++ objs[1] = sem_args.sem; ++ ret = ioctl(fd, NTSYNC_IOC_WAIT_ANY, &wait_args); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EINVAL, errno); ++ ret = ioctl(fd, NTSYNC_IOC_WAIT_ALL, &wait_args); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EINVAL, errno); ++ ++ ret = ioctl(fd, NTSYNC_IOC_DELETE, &sem_args.sem); ++ EXPECT_EQ(0, ret); ++ ++ ret = ioctl(fd, NTSYNC_IOC_CREATE_MUTEX, &mutex_args); ++ EXPECT_EQ(0, ret); ++ ++ sem_args.sem = mutex_args.mutex; ++ ret = ioctl(fd, NTSYNC_IOC_PUT_SEM, &sem_args); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EINVAL, errno); ++ ++ ret = ioctl(fd, NTSYNC_IOC_READ_SEM, &sem_args); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EINVAL, errno); ++ ++ ret = ioctl(fd, NTSYNC_IOC_DELETE, &mutex_args.mutex); ++ EXPECT_EQ(0, ret); ++ ++ close(fd); ++} ++ + TEST_HARNESS_MAIN +-- +2.43.0 + +From e49d10455ebe2a5d056523a225b8b179b1da932a Mon Sep 17 00:00:00 2001 +From: Elizabeth Figura <zfigura@codeweavers.com> +Date: Fri, 5 Mar 2021 12:09:32 -0600 +Subject: [PATCH 23/32] selftests: ntsync: Add some tests for wakeup signaling + with WINESYNC_IOC_WAIT_ANY. + +--- + .../testing/selftests/drivers/ntsync/ntsync.c | 154 ++++++++++++++++++ + 1 file changed, 154 insertions(+) + +diff --git a/tools/testing/selftests/drivers/ntsync/ntsync.c b/tools/testing/selftests/drivers/ntsync/ntsync.c +index ac24a94f2518..919cabc3ba5e 100644 +--- a/tools/testing/selftests/drivers/ntsync/ntsync.c ++++ b/tools/testing/selftests/drivers/ntsync/ntsync.c +@@ -636,4 +636,158 @@ TEST(invalid_objects) + close(fd); + } + ++struct wake_args ++{ ++ int fd; ++ __u32 obj; ++}; ++ ++struct wait_args ++{ ++ int fd; ++ unsigned long request; ++ struct ntsync_wait_args *args; ++ int ret; ++ int err; ++}; ++ ++static void *wait_thread(void *arg) ++{ ++ struct wait_args *args = arg; ++ ++ args->ret = ioctl(args->fd, args->request, args->args); ++ args->err = errno; ++ return NULL; ++} ++ ++static void get_abs_timeout(struct timespec *timeout, clockid_t clock, ++ unsigned int ms) ++{ ++ clock_gettime(clock, timeout); ++ timeout->tv_nsec += ms * 1000000; ++ timeout->tv_sec += (timeout->tv_nsec / 1000000000); ++ timeout->tv_nsec %= 1000000000; ++} ++ ++static int wait_for_thread(pthread_t thread, unsigned int ms) ++{ ++ struct timespec timeout; ++ get_abs_timeout(&timeout, CLOCK_REALTIME, ms); ++ return pthread_timedjoin_np(thread, NULL, &timeout); ++} ++ ++TEST(wake_any) ++{ ++ struct ntsync_mutex_args mutex_args = {0}; ++ struct ntsync_wait_args wait_args = {0}; ++ struct ntsync_sem_args sem_args = {0}; ++ struct wait_args thread_args; ++ __u32 objs[2], count, index; ++ struct timespec timeout; ++ pthread_t thread; ++ int fd, ret; ++ ++ fd = open("/dev/ntsync", O_CLOEXEC | O_RDONLY); ++ ASSERT_LE(0, fd); ++ ++ sem_args.count = 0; ++ sem_args.max = 3; ++ sem_args.sem = 0xdeadbeef; ++ ret = ioctl(fd, NTSYNC_IOC_CREATE_SEM, &sem_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_NE(0xdeadbeef, sem_args.sem); ++ ++ mutex_args.owner = 123; ++ mutex_args.count = 1; ++ mutex_args.mutex = 0xdeadbeef; ++ ret = ioctl(fd, NTSYNC_IOC_CREATE_MUTEX, &mutex_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_NE(0xdeadbeef, mutex_args.mutex); ++ ++ objs[0] = sem_args.sem; ++ objs[1] = mutex_args.mutex; ++ ++ /* test waking the semaphore */ ++ ++ get_abs_timeout(&timeout, CLOCK_MONOTONIC, 1000); ++ wait_args.timeout = (uintptr_t)&timeout; ++ wait_args.objs = (uintptr_t)objs; ++ wait_args.count = 2; ++ wait_args.owner = 456; ++ wait_args.index = 0xdeadbeef; ++ thread_args.fd = fd; ++ thread_args.args = &wait_args; ++ thread_args.request = NTSYNC_IOC_WAIT_ANY; ++ ret = pthread_create(&thread, NULL, wait_thread, &thread_args); ++ EXPECT_EQ(0, ret); ++ ++ ret = wait_for_thread(thread, 100); ++ EXPECT_EQ(ETIMEDOUT, ret); ++ ++ sem_args.count = 1; ++ ret = ioctl(fd, NTSYNC_IOC_PUT_SEM, &sem_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, sem_args.count); ++ check_sem_state(fd, sem_args.sem, 0, 3); ++ ++ ret = wait_for_thread(thread, 100); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, thread_args.ret); ++ EXPECT_EQ(0, wait_args.index); ++ ++ /* test waking the mutex */ ++ ++ /* first grab it again for owner 123 */ ++ ret = wait_any(fd, 1, &mutex_args.mutex, 123, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, index); ++ ++ get_abs_timeout(&timeout, CLOCK_MONOTONIC, 1000); ++ wait_args.owner = 456; ++ ret = pthread_create(&thread, NULL, wait_thread, &thread_args); ++ EXPECT_EQ(0, ret); ++ ++ ret = wait_for_thread(thread, 100); ++ EXPECT_EQ(ETIMEDOUT, ret); ++ ++ ret = put_mutex(fd, mutex_args.mutex, 123, &count); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(2, count); ++ ++ ret = pthread_tryjoin_np(thread, NULL); ++ EXPECT_EQ(EBUSY, ret); ++ ++ ret = put_mutex(fd, mutex_args.mutex, 123, &count); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(1, mutex_args.count); ++ check_mutex_state(fd, mutex_args.mutex, 1, 456); ++ ++ ret = wait_for_thread(thread, 100); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, thread_args.ret); ++ EXPECT_EQ(1, wait_args.index); ++ ++ /* delete an object while it's being waited on */ ++ ++ get_abs_timeout(&timeout, CLOCK_MONOTONIC, 200); ++ wait_args.owner = 123; ++ ret = pthread_create(&thread, NULL, wait_thread, &thread_args); ++ EXPECT_EQ(0, ret); ++ ++ ret = wait_for_thread(thread, 100); ++ EXPECT_EQ(ETIMEDOUT, ret); ++ ++ ret = ioctl(fd, NTSYNC_IOC_DELETE, &sem_args.sem); ++ EXPECT_EQ(0, ret); ++ ret = ioctl(fd, NTSYNC_IOC_DELETE, &mutex_args.mutex); ++ EXPECT_EQ(0, ret); ++ ++ ret = wait_for_thread(thread, 200); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(-1, thread_args.ret); ++ EXPECT_EQ(ETIMEDOUT, thread_args.err); ++ ++ close(fd); ++} ++ + TEST_HARNESS_MAIN +-- +2.43.0 + +From a9f683caedb447a30b28a3ebb3d8fc597fe26d73 Mon Sep 17 00:00:00 2001 +From: Elizabeth Figura <zfigura@codeweavers.com> +Date: Fri, 5 Mar 2021 12:09:36 -0600 +Subject: [PATCH 24/32] selftests: ntsync: Add some tests for wakeup signaling + with WINESYNC_IOC_WAIT_ALL. + +--- + .../testing/selftests/drivers/ntsync/ntsync.c | 102 ++++++++++++++++++ + 1 file changed, 102 insertions(+) + +diff --git a/tools/testing/selftests/drivers/ntsync/ntsync.c b/tools/testing/selftests/drivers/ntsync/ntsync.c +index 919cabc3ba5e..3dbae7c8ac1f 100644 +--- a/tools/testing/selftests/drivers/ntsync/ntsync.c ++++ b/tools/testing/selftests/drivers/ntsync/ntsync.c +@@ -790,4 +790,106 @@ TEST(wake_any) + close(fd); + } + ++TEST(wake_all) ++{ ++ struct ntsync_mutex_args mutex_args = {0}; ++ struct ntsync_wait_args wait_args = {0}; ++ struct ntsync_sem_args sem_args = {0}; ++ struct wait_args thread_args; ++ __u32 objs[2], count, index; ++ struct timespec timeout; ++ pthread_t thread; ++ int fd, ret; ++ ++ fd = open("/dev/ntsync", O_CLOEXEC | O_RDONLY); ++ ASSERT_LE(0, fd); ++ ++ sem_args.count = 0; ++ sem_args.max = 3; ++ sem_args.sem = 0xdeadbeef; ++ ret = ioctl(fd, NTSYNC_IOC_CREATE_SEM, &sem_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_NE(0xdeadbeef, sem_args.sem); ++ ++ mutex_args.owner = 123; ++ mutex_args.count = 1; ++ mutex_args.mutex = 0xdeadbeef; ++ ret = ioctl(fd, NTSYNC_IOC_CREATE_MUTEX, &mutex_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_NE(0xdeadbeef, mutex_args.mutex); ++ ++ objs[0] = sem_args.sem; ++ objs[1] = mutex_args.mutex; ++ ++ get_abs_timeout(&timeout, CLOCK_MONOTONIC, 1000); ++ wait_args.timeout = (uintptr_t)&timeout; ++ wait_args.objs = (uintptr_t)objs; ++ wait_args.count = 2; ++ wait_args.owner = 456; ++ thread_args.fd = fd; ++ thread_args.args = &wait_args; ++ thread_args.request = NTSYNC_IOC_WAIT_ALL; ++ ret = pthread_create(&thread, NULL, wait_thread, &thread_args); ++ EXPECT_EQ(0, ret); ++ ++ ret = wait_for_thread(thread, 100); ++ EXPECT_EQ(ETIMEDOUT, ret); ++ ++ sem_args.count = 1; ++ ret = ioctl(fd, NTSYNC_IOC_PUT_SEM, &sem_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, sem_args.count); ++ ++ ret = pthread_tryjoin_np(thread, NULL); ++ EXPECT_EQ(EBUSY, ret); ++ ++ check_sem_state(fd, sem_args.sem, 1, 3); ++ ++ ret = wait_any(fd, 1, &sem_args.sem, 123, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, index); ++ ++ ret = put_mutex(fd, mutex_args.mutex, 123, &count); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(1, count); ++ ++ ret = pthread_tryjoin_np(thread, NULL); ++ EXPECT_EQ(EBUSY, ret); ++ ++ check_mutex_state(fd, mutex_args.mutex, 0, 0); ++ ++ sem_args.count = 2; ++ ret = ioctl(fd, NTSYNC_IOC_PUT_SEM, &sem_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, sem_args.count); ++ check_sem_state(fd, sem_args.sem, 1, 3); ++ check_mutex_state(fd, mutex_args.mutex, 1, 456); ++ ++ ret = wait_for_thread(thread, 100); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, thread_args.ret); ++ ++ /* delete an object while it's being waited on */ ++ ++ get_abs_timeout(&timeout, CLOCK_MONOTONIC, 200); ++ wait_args.owner = 123; ++ ret = pthread_create(&thread, NULL, wait_thread, &thread_args); ++ EXPECT_EQ(0, ret); ++ ++ ret = wait_for_thread(thread, 100); ++ EXPECT_EQ(ETIMEDOUT, ret); ++ ++ ret = ioctl(fd, NTSYNC_IOC_DELETE, &sem_args.sem); ++ EXPECT_EQ(0, ret); ++ ret = ioctl(fd, NTSYNC_IOC_DELETE, &mutex_args.mutex); ++ EXPECT_EQ(0, ret); ++ ++ ret = wait_for_thread(thread, 200); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(-1, thread_args.ret); ++ EXPECT_EQ(ETIMEDOUT, thread_args.err); ++ ++ close(fd); ++} ++ + TEST_HARNESS_MAIN +-- +2.43.0 + +From f56885c1a183947d3b1f5e11a80ce23c7f150e33 Mon Sep 17 00:00:00 2001 +From: Elizabeth Figura <zfigura@codeweavers.com> +Date: Wed, 19 Jan 2022 19:34:47 -0600 +Subject: [PATCH 25/32] selftests: ntsync: Add some tests for manual-reset + event state. + +--- + .../testing/selftests/drivers/ntsync/ntsync.c | 92 +++++++++++++++++++ + 1 file changed, 92 insertions(+) + +diff --git a/tools/testing/selftests/drivers/ntsync/ntsync.c b/tools/testing/selftests/drivers/ntsync/ntsync.c +index 3dbae7c8ac1f..cbe4ed73ffb9 100644 +--- a/tools/testing/selftests/drivers/ntsync/ntsync.c ++++ b/tools/testing/selftests/drivers/ntsync/ntsync.c +@@ -85,6 +85,30 @@ static int put_mutex(int fd, __u32 mutex, __u32 owner, __u32 *count) + return ret; + } + ++static int read_event_state(int fd, __u32 event, __u32 *signaled, __u32 *manual) ++{ ++ struct ntsync_event_args args; ++ int ret; ++ ++ args.event = event; ++ args.signaled = 0xdeadbeef; ++ args.manual = 0xdeadbeef; ++ ret = ioctl(fd, NTSYNC_IOC_READ_EVENT, &args); ++ *signaled = args.signaled; ++ *manual = args.manual; ++ return ret; ++} ++ ++#define check_event_state(fd, event, signaled, manual) \ ++ ({ \ ++ __u32 __signaled, __manual; \ ++ int ret = read_event_state((fd), (event), \ ++ &__signaled, &__manual); \ ++ EXPECT_EQ(0, ret); \ ++ EXPECT_EQ((signaled), __signaled); \ ++ EXPECT_EQ((manual), __manual); \ ++ }) ++ + static int wait_objs(int fd, unsigned long request, __u32 count, + const __u32 *objs, __u32 owner, __u32 *index) + { +@@ -350,6 +374,74 @@ TEST(mutex_state) + close(fd); + } + ++TEST(manual_event_state) ++{ ++ struct ntsync_event_args event_args; ++ __u32 index; ++ int fd, ret; ++ ++ fd = open("/dev/ntsync", O_CLOEXEC | O_RDONLY); ++ ASSERT_LE(0, fd); ++ ++ event_args.manual = 1; ++ event_args.signaled = 0; ++ event_args.event = 0xdeadbeef; ++ ret = ioctl(fd, NTSYNC_IOC_CREATE_EVENT, &event_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_NE(0xdeadbeef, event_args.event); ++ check_event_state(fd, event_args.event, 0, 1); ++ ++ event_args.signaled = 0xdeadbeef; ++ ret = ioctl(fd, NTSYNC_IOC_SET_EVENT, &event_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, event_args.signaled); ++ check_event_state(fd, event_args.event, 1, 1); ++ ++ ret = ioctl(fd, NTSYNC_IOC_SET_EVENT, &event_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(1, event_args.signaled); ++ check_event_state(fd, event_args.event, 1, 1); ++ ++ ret = wait_any(fd, 1, &event_args.event, 123, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, index); ++ check_event_state(fd, event_args.event, 1, 1); ++ ++ event_args.signaled = 0xdeadbeef; ++ ret = ioctl(fd, NTSYNC_IOC_RESET_EVENT, &event_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(1, event_args.signaled); ++ check_event_state(fd, event_args.event, 0, 1); ++ ++ ret = ioctl(fd, NTSYNC_IOC_RESET_EVENT, &event_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, event_args.signaled); ++ check_event_state(fd, event_args.event, 0, 1); ++ ++ ret = wait_any(fd, 1, &event_args.event, 123, &index); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(ETIMEDOUT, errno); ++ ++ ret = ioctl(fd, NTSYNC_IOC_SET_EVENT, &event_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, event_args.signaled); ++ ++ ret = ioctl(fd, NTSYNC_IOC_PULSE_EVENT, &event_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(1, event_args.signaled); ++ check_event_state(fd, event_args.event, 0, 1); ++ ++ ret = ioctl(fd, NTSYNC_IOC_PULSE_EVENT, &event_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, event_args.signaled); ++ check_event_state(fd, event_args.event, 0, 1); ++ ++ ret = ioctl(fd, NTSYNC_IOC_DELETE, &event_args.event); ++ EXPECT_EQ(0, ret); ++ ++ close(fd); ++} ++ + TEST(test_wait_any) + { + struct ntsync_mutex_args mutex_args = {0}; +-- +2.43.0 + +From d1e2db12a317942c72505085459dad6d64c8926a Mon Sep 17 00:00:00 2001 +From: Elizabeth Figura <zfigura@codeweavers.com> +Date: Wed, 19 Jan 2022 19:45:39 -0600 +Subject: [PATCH 26/32] selftests: ntsync: Add some tests for auto-reset event + state. + +--- + .../testing/selftests/drivers/ntsync/ntsync.c | 59 +++++++++++++++++++ + 1 file changed, 59 insertions(+) + +diff --git a/tools/testing/selftests/drivers/ntsync/ntsync.c b/tools/testing/selftests/drivers/ntsync/ntsync.c +index cbe4ed73ffb9..c158c15fc03e 100644 +--- a/tools/testing/selftests/drivers/ntsync/ntsync.c ++++ b/tools/testing/selftests/drivers/ntsync/ntsync.c +@@ -442,6 +442,65 @@ TEST(manual_event_state) + close(fd); + } + ++TEST(auto_event_state) ++{ ++ struct ntsync_event_args event_args; ++ __u32 index; ++ int fd, ret; ++ ++ fd = open("/dev/ntsync", O_CLOEXEC | O_RDONLY); ++ ASSERT_LE(0, fd); ++ ++ event_args.manual = 0; ++ event_args.signaled = 1; ++ event_args.event = 0xdeadbeef; ++ ret = ioctl(fd, NTSYNC_IOC_CREATE_EVENT, &event_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_NE(0xdeadbeef, event_args.event); ++ ++ check_event_state(fd, event_args.event, 1, 0); ++ ++ event_args.signaled = 0xdeadbeef; ++ ret = ioctl(fd, NTSYNC_IOC_SET_EVENT, &event_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(1, event_args.signaled); ++ check_event_state(fd, event_args.event, 1, 0); ++ ++ ret = wait_any(fd, 1, &event_args.event, 123, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, index); ++ check_event_state(fd, event_args.event, 0, 0); ++ ++ event_args.signaled = 0xdeadbeef; ++ ret = ioctl(fd, NTSYNC_IOC_RESET_EVENT, &event_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, event_args.signaled); ++ check_event_state(fd, event_args.event, 0, 0); ++ ++ ret = wait_any(fd, 1, &event_args.event, 123, &index); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(ETIMEDOUT, errno); ++ ++ ret = ioctl(fd, NTSYNC_IOC_SET_EVENT, &event_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, event_args.signaled); ++ ++ ret = ioctl(fd, NTSYNC_IOC_PULSE_EVENT, &event_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(1, event_args.signaled); ++ check_event_state(fd, event_args.event, 0, 0); ++ ++ ret = ioctl(fd, NTSYNC_IOC_PULSE_EVENT, &event_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, event_args.signaled); ++ check_event_state(fd, event_args.event, 0, 0); ++ ++ ret = ioctl(fd, NTSYNC_IOC_DELETE, &event_args.event); ++ EXPECT_EQ(0, ret); ++ ++ close(fd); ++} ++ + TEST(test_wait_any) + { + struct ntsync_mutex_args mutex_args = {0}; +-- +2.43.0 + +From 011b5115949be3b87e3009ea5e4ede6708cf3ece Mon Sep 17 00:00:00 2001 +From: Elizabeth Figura <zfigura@codeweavers.com> +Date: Wed, 19 Jan 2022 21:00:50 -0600 +Subject: [PATCH 27/32] selftests: ntsync: Add some tests for wakeup signaling + with events. + +--- + .../testing/selftests/drivers/ntsync/ntsync.c | 152 +++++++++++++++++- + 1 file changed, 150 insertions(+), 2 deletions(-) + +diff --git a/tools/testing/selftests/drivers/ntsync/ntsync.c b/tools/testing/selftests/drivers/ntsync/ntsync.c +index c158c15fc03e..8f07577cf156 100644 +--- a/tools/testing/selftests/drivers/ntsync/ntsync.c ++++ b/tools/testing/selftests/drivers/ntsync/ntsync.c +@@ -610,6 +610,7 @@ TEST(test_wait_any) + + TEST(test_wait_all) + { ++ struct ntsync_event_args event_args = {0}; + struct ntsync_mutex_args mutex_args = {0}; + struct ntsync_sem_args sem_args = {0}; + __u32 objs[2], owner, index; +@@ -632,6 +633,11 @@ TEST(test_wait_all) + EXPECT_EQ(0, ret); + EXPECT_NE(0xdeadbeef, mutex_args.mutex); + ++ event_args.manual = true; ++ event_args.signaled = true; ++ ret = ioctl(fd, NTSYNC_IOC_CREATE_EVENT, &event_args); ++ EXPECT_EQ(0, ret); ++ + objs[0] = sem_args.sem; + objs[1] = mutex_args.mutex; + +@@ -680,6 +686,14 @@ TEST(test_wait_all) + check_sem_state(fd, sem_args.sem, 1, 3); + check_mutex_state(fd, mutex_args.mutex, 1, 123); + ++ objs[0] = sem_args.sem; ++ objs[1] = event_args.event; ++ ret = wait_all(fd, 2, objs, 123, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, index); ++ check_sem_state(fd, sem_args.sem, 0, 3); ++ check_event_state(fd, event_args.event, 1, 1); ++ + /* test waiting on the same object twice */ + objs[0] = objs[1] = sem_args.sem; + ret = wait_all(fd, 2, objs, 123, &index); +@@ -690,6 +704,8 @@ TEST(test_wait_all) + EXPECT_EQ(0, ret); + ret = ioctl(fd, NTSYNC_IOC_DELETE, &mutex_args.mutex); + EXPECT_EQ(0, ret); ++ ret = ioctl(fd, NTSYNC_IOC_DELETE, &event_args.event); ++ EXPECT_EQ(0, ret); + + close(fd); + } +@@ -829,6 +845,7 @@ static int wait_for_thread(pthread_t thread, unsigned int ms) + + TEST(wake_any) + { ++ struct ntsync_event_args event_args = {0}; + struct ntsync_mutex_args mutex_args = {0}; + struct ntsync_wait_args wait_args = {0}; + struct ntsync_sem_args sem_args = {0}; +@@ -918,10 +935,103 @@ TEST(wake_any) + EXPECT_EQ(0, thread_args.ret); + EXPECT_EQ(1, wait_args.index); + ++ /* test waking events */ ++ ++ event_args.manual = false; ++ event_args.signaled = false; ++ ret = ioctl(fd, NTSYNC_IOC_CREATE_EVENT, &event_args); ++ EXPECT_EQ(0, ret); ++ ++ objs[1] = event_args.event; ++ get_abs_timeout(&timeout, CLOCK_MONOTONIC, 1000); ++ ret = pthread_create(&thread, NULL, wait_thread, &thread_args); ++ EXPECT_EQ(0, ret); ++ ++ ret = wait_for_thread(thread, 100); ++ EXPECT_EQ(ETIMEDOUT, ret); ++ ++ ret = ioctl(fd, NTSYNC_IOC_SET_EVENT, &event_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, event_args.signaled); ++ check_event_state(fd, event_args.event, 0, 0); ++ ++ ret = wait_for_thread(thread, 100); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, thread_args.ret); ++ EXPECT_EQ(1, wait_args.index); ++ ++ get_abs_timeout(&timeout, CLOCK_MONOTONIC, 1000); ++ ret = pthread_create(&thread, NULL, wait_thread, &thread_args); ++ EXPECT_EQ(0, ret); ++ ++ ret = wait_for_thread(thread, 100); ++ EXPECT_EQ(ETIMEDOUT, ret); ++ ++ ret = ioctl(fd, NTSYNC_IOC_PULSE_EVENT, &event_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, event_args.signaled); ++ check_event_state(fd, event_args.event, 0, 0); ++ ++ ret = wait_for_thread(thread, 100); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, thread_args.ret); ++ EXPECT_EQ(1, wait_args.index); ++ ++ ret = ioctl(fd, NTSYNC_IOC_DELETE, &event_args.event); ++ EXPECT_EQ(0, ret); ++ ++ event_args.manual = true; ++ event_args.signaled = false; ++ ret = ioctl(fd, NTSYNC_IOC_CREATE_EVENT, &event_args); ++ EXPECT_EQ(0, ret); ++ ++ objs[1] = event_args.event; ++ get_abs_timeout(&timeout, CLOCK_MONOTONIC, 1000); ++ ret = pthread_create(&thread, NULL, wait_thread, &thread_args); ++ EXPECT_EQ(0, ret); ++ ++ ret = wait_for_thread(thread, 100); ++ EXPECT_EQ(ETIMEDOUT, ret); ++ ++ ret = ioctl(fd, NTSYNC_IOC_SET_EVENT, &event_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, event_args.signaled); ++ check_event_state(fd, event_args.event, 1, 1); ++ ++ ret = wait_for_thread(thread, 100); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, thread_args.ret); ++ EXPECT_EQ(1, wait_args.index); ++ ++ ret = ioctl(fd, NTSYNC_IOC_RESET_EVENT, &event_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(1, event_args.signaled); ++ ++ get_abs_timeout(&timeout, CLOCK_MONOTONIC, 1000); ++ ret = pthread_create(&thread, NULL, wait_thread, &thread_args); ++ EXPECT_EQ(0, ret); ++ ++ ret = wait_for_thread(thread, 100); ++ EXPECT_EQ(ETIMEDOUT, ret); ++ ++ ret = ioctl(fd, NTSYNC_IOC_PULSE_EVENT, &event_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, event_args.signaled); ++ check_event_state(fd, event_args.event, 0, 1); ++ ++ ret = wait_for_thread(thread, 100); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, thread_args.ret); ++ EXPECT_EQ(1, wait_args.index); ++ ++ ret = ioctl(fd, NTSYNC_IOC_DELETE, &event_args.event); ++ EXPECT_EQ(0, ret); ++ + /* delete an object while it's being waited on */ + + get_abs_timeout(&timeout, CLOCK_MONOTONIC, 200); + wait_args.owner = 123; ++ objs[1] = mutex_args.mutex; + ret = pthread_create(&thread, NULL, wait_thread, &thread_args); + EXPECT_EQ(0, ret); + +@@ -943,11 +1053,13 @@ TEST(wake_any) + + TEST(wake_all) + { ++ struct ntsync_event_args manual_event_args = {0}; ++ struct ntsync_event_args auto_event_args = {0}; + struct ntsync_mutex_args mutex_args = {0}; + struct ntsync_wait_args wait_args = {0}; + struct ntsync_sem_args sem_args = {0}; + struct wait_args thread_args; +- __u32 objs[2], count, index; ++ __u32 objs[4], count, index; + struct timespec timeout; + pthread_t thread; + int fd, ret; +@@ -969,13 +1081,25 @@ TEST(wake_all) + EXPECT_EQ(0, ret); + EXPECT_NE(0xdeadbeef, mutex_args.mutex); + ++ manual_event_args.manual = true; ++ manual_event_args.signaled = true; ++ ret = ioctl(fd, NTSYNC_IOC_CREATE_EVENT, &manual_event_args); ++ EXPECT_EQ(0, ret); ++ ++ auto_event_args.manual = false; ++ auto_event_args.signaled = true; ++ ret = ioctl(fd, NTSYNC_IOC_CREATE_EVENT, &auto_event_args); ++ EXPECT_EQ(0, ret); ++ + objs[0] = sem_args.sem; + objs[1] = mutex_args.mutex; ++ objs[2] = manual_event_args.event; ++ objs[3] = auto_event_args.event; + + get_abs_timeout(&timeout, CLOCK_MONOTONIC, 1000); + wait_args.timeout = (uintptr_t)&timeout; + wait_args.objs = (uintptr_t)objs; +- wait_args.count = 2; ++ wait_args.count = 4; + wait_args.owner = 456; + thread_args.fd = fd; + thread_args.args = &wait_args; +@@ -1009,12 +1133,32 @@ TEST(wake_all) + + check_mutex_state(fd, mutex_args.mutex, 0, 0); + ++ ret = ioctl(fd, NTSYNC_IOC_RESET_EVENT, &manual_event_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(1, manual_event_args.signaled); ++ + sem_args.count = 2; + ret = ioctl(fd, NTSYNC_IOC_PUT_SEM, &sem_args); + EXPECT_EQ(0, ret); + EXPECT_EQ(0, sem_args.count); ++ check_sem_state(fd, sem_args.sem, 2, 3); ++ ++ ret = ioctl(fd, NTSYNC_IOC_RESET_EVENT, &auto_event_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(1, auto_event_args.signaled); ++ ++ ret = ioctl(fd, NTSYNC_IOC_SET_EVENT, &manual_event_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, manual_event_args.signaled); ++ ++ ret = ioctl(fd, NTSYNC_IOC_SET_EVENT, &auto_event_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, auto_event_args.signaled); ++ + check_sem_state(fd, sem_args.sem, 1, 3); + check_mutex_state(fd, mutex_args.mutex, 1, 456); ++ check_event_state(fd, manual_event_args.event, 1, 1); ++ check_event_state(fd, auto_event_args.event, 0, 0); + + ret = wait_for_thread(thread, 100); + EXPECT_EQ(0, ret); +@@ -1034,6 +1178,10 @@ TEST(wake_all) + EXPECT_EQ(0, ret); + ret = ioctl(fd, NTSYNC_IOC_DELETE, &mutex_args.mutex); + EXPECT_EQ(0, ret); ++ ret = ioctl(fd, NTSYNC_IOC_DELETE, &manual_event_args.event); ++ EXPECT_EQ(0, ret); ++ ret = ioctl(fd, NTSYNC_IOC_DELETE, &auto_event_args.event); ++ EXPECT_EQ(0, ret); + + ret = wait_for_thread(thread, 200); + EXPECT_EQ(0, ret); +-- +2.43.0 + +From 74c862089a6975afeb0a11bfb6e1c632ae231419 Mon Sep 17 00:00:00 2001 +From: Elizabeth Figura <zfigura@codeweavers.com> +Date: Wed, 19 Jan 2022 21:06:22 -0600 +Subject: [PATCH 28/32] selftests: ntsync: Add some tests for invalid object + handling with events. + +--- + .../testing/selftests/drivers/ntsync/ntsync.c | 34 +++++++++++++++++++ + 1 file changed, 34 insertions(+) + +diff --git a/tools/testing/selftests/drivers/ntsync/ntsync.c b/tools/testing/selftests/drivers/ntsync/ntsync.c +index 8f07577cf156..01bd034d4467 100644 +--- a/tools/testing/selftests/drivers/ntsync/ntsync.c ++++ b/tools/testing/selftests/drivers/ntsync/ntsync.c +@@ -712,6 +712,7 @@ TEST(test_wait_all) + + TEST(invalid_objects) + { ++ struct ntsync_event_args event_args = {0}; + struct ntsync_mutex_args mutex_args = {0}; + struct ntsync_wait_args wait_args = {0}; + struct ntsync_sem_args sem_args = {0}; +@@ -737,6 +738,22 @@ TEST(invalid_objects) + EXPECT_EQ(-1, ret); + EXPECT_EQ(EINVAL, errno); + ++ ret = ioctl(fd, NTSYNC_IOC_SET_EVENT, &event_args); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EINVAL, errno); ++ ++ ret = ioctl(fd, NTSYNC_IOC_RESET_EVENT, &event_args); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EINVAL, errno); ++ ++ ret = ioctl(fd, NTSYNC_IOC_PULSE_EVENT, &event_args); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EINVAL, errno); ++ ++ ret = ioctl(fd, NTSYNC_IOC_READ_EVENT, &event_args); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EINVAL, errno); ++ + wait_args.objs = (uintptr_t)objs; + wait_args.count = 1; + ret = ioctl(fd, NTSYNC_IOC_WAIT_ANY, &wait_args); +@@ -763,6 +780,23 @@ TEST(invalid_objects) + EXPECT_EQ(-1, ret); + EXPECT_EQ(EINVAL, errno); + ++ event_args.event = sem_args.sem; ++ ret = ioctl(fd, NTSYNC_IOC_SET_EVENT, &event_args); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EINVAL, errno); ++ ++ ret = ioctl(fd, NTSYNC_IOC_RESET_EVENT, &event_args); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EINVAL, errno); ++ ++ ret = ioctl(fd, NTSYNC_IOC_PULSE_EVENT, &event_args); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EINVAL, errno); ++ ++ ret = ioctl(fd, NTSYNC_IOC_READ_EVENT, &event_args); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EINVAL, errno); ++ + objs[0] = sem_args.sem; + objs[1] = sem_args.sem + 1; + wait_args.count = 2; +-- +2.43.0 + +From 8550d7aec916d737b2e81f5916c25da69d0d3de0 Mon Sep 17 00:00:00 2001 +From: Elizabeth Figura <zfigura@codeweavers.com> +Date: Wed, 20 Apr 2022 18:08:37 -0500 +Subject: [PATCH 29/32] selftests: ntsync: Add tests for alertable waits. + +--- + .../testing/selftests/drivers/ntsync/ntsync.c | 189 +++++++++++++++++- + 1 file changed, 186 insertions(+), 3 deletions(-) + +diff --git a/tools/testing/selftests/drivers/ntsync/ntsync.c b/tools/testing/selftests/drivers/ntsync/ntsync.c +index 01bd034d4467..a58387d6744b 100644 +--- a/tools/testing/selftests/drivers/ntsync/ntsync.c ++++ b/tools/testing/selftests/drivers/ntsync/ntsync.c +@@ -110,7 +110,7 @@ static int read_event_state(int fd, __u32 event, __u32 *signaled, __u32 *manual) + }) + + static int wait_objs(int fd, unsigned long request, __u32 count, +- const __u32 *objs, __u32 owner, __u32 *index) ++ const __u32 *objs, __u32 owner, __u32 alert, __u32 *index) + { + struct ntsync_wait_args args = {0}; + struct timespec timeout; +@@ -123,6 +123,7 @@ static int wait_objs(int fd, unsigned long request, __u32 count, + args.objs = (uintptr_t)objs; + args.owner = owner; + args.index = 0xdeadbeef; ++ args.alert = alert; + ret = ioctl(fd, request, &args); + *index = args.index; + return ret; +@@ -131,13 +132,27 @@ static int wait_objs(int fd, unsigned long request, __u32 count, + static int wait_any(int fd, __u32 count, const __u32 *objs, + __u32 owner, __u32 *index) + { +- return wait_objs(fd, NTSYNC_IOC_WAIT_ANY, count, objs, owner, index); ++ return wait_objs(fd, NTSYNC_IOC_WAIT_ANY, count, objs, owner, 0, index); + } + + static int wait_all(int fd, __u32 count, const __u32 *objs, + __u32 owner, __u32 *index) + { +- return wait_objs(fd, NTSYNC_IOC_WAIT_ALL, count, objs, owner, index); ++ return wait_objs(fd, NTSYNC_IOC_WAIT_ALL, count, objs, owner, 0, index); ++} ++ ++static int wait_any_alert(int fd, __u32 count, const __u32 *objs, ++ __u32 owner, __u32 alert, __u32 *index) ++{ ++ return wait_objs(fd, NTSYNC_IOC_WAIT_ANY, ++ count, objs, owner, alert, index); ++} ++ ++static int wait_all_alert(int fd, __u32 count, const __u32 *objs, ++ __u32 owner, __u32 alert, __u32 *index) ++{ ++ return wait_objs(fd, NTSYNC_IOC_WAIT_ALL, ++ count, objs, owner, alert, index); + } + + TEST(semaphore_state) +@@ -1225,4 +1240,172 @@ TEST(wake_all) + close(fd); + } + ++TEST(alert_any) ++{ ++ struct ntsync_event_args event_args = {0}; ++ struct ntsync_sem_args sem_args = {0}; ++ __u32 objs[2], index; ++ int fd, ret; ++ ++ fd = open("/dev/ntsync", O_CLOEXEC | O_RDONLY); ++ ASSERT_LE(0, fd); ++ ++ sem_args.count = 0; ++ sem_args.max = 2; ++ sem_args.sem = 0xdeadbeef; ++ ret = ioctl(fd, NTSYNC_IOC_CREATE_SEM, &sem_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_NE(0xdeadbeef, sem_args.sem); ++ objs[0] = sem_args.sem; ++ ++ sem_args.count = 1; ++ sem_args.max = 2; ++ sem_args.sem = 0xdeadbeef; ++ ret = ioctl(fd, NTSYNC_IOC_CREATE_SEM, &sem_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_NE(0xdeadbeef, sem_args.sem); ++ objs[1] = sem_args.sem; ++ ++ event_args.manual = true; ++ event_args.signaled = true; ++ ret = ioctl(fd, NTSYNC_IOC_CREATE_EVENT, &event_args); ++ EXPECT_EQ(0, ret); ++ ++ ret = wait_any_alert(fd, 0, NULL, 123, event_args.event, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, index); ++ ++ ret = ioctl(fd, NTSYNC_IOC_RESET_EVENT, &event_args); ++ EXPECT_EQ(0, ret); ++ ++ ret = wait_any_alert(fd, 0, NULL, 123, event_args.event, &index); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(ETIMEDOUT, errno); ++ ++ ret = ioctl(fd, NTSYNC_IOC_SET_EVENT, &event_args); ++ EXPECT_EQ(0, ret); ++ ++ ret = wait_any_alert(fd, 2, objs, 123, event_args.event, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(1, index); ++ ++ ret = wait_any_alert(fd, 2, objs, 123, event_args.event, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(2, index); ++ ++ ret = ioctl(fd, NTSYNC_IOC_DELETE, &event_args.event); ++ EXPECT_EQ(0, ret); ++ ++ /* test with an auto-reset event */ ++ ++ event_args.manual = false; ++ event_args.signaled = true; ++ ret = ioctl(fd, NTSYNC_IOC_CREATE_EVENT, &event_args); ++ EXPECT_EQ(0, ret); ++ ++ sem_args.sem = objs[0]; ++ sem_args.count = 1; ++ ret = ioctl(fd, NTSYNC_IOC_PUT_SEM, &sem_args); ++ EXPECT_EQ(0, ret); ++ ++ ret = wait_any_alert(fd, 2, objs, 123, event_args.event, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, index); ++ ++ ret = wait_any_alert(fd, 2, objs, 123, event_args.event, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(2, index); ++ ++ ret = wait_any_alert(fd, 2, objs, 123, event_args.event, &index); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(ETIMEDOUT, errno); ++ ++ ret = ioctl(fd, NTSYNC_IOC_DELETE, &event_args.event); ++ EXPECT_EQ(0, ret); ++ ++ ret = ioctl(fd, NTSYNC_IOC_DELETE, &objs[0]); ++ EXPECT_EQ(0, ret); ++ ret = ioctl(fd, NTSYNC_IOC_DELETE, &objs[1]); ++ EXPECT_EQ(0, ret); ++ ++ close(fd); ++} ++ ++TEST(alert_all) ++{ ++ struct ntsync_event_args event_args = {0}; ++ struct ntsync_sem_args sem_args = {0}; ++ __u32 objs[2], index; ++ int fd, ret; ++ ++ fd = open("/dev/ntsync", O_CLOEXEC | O_RDONLY); ++ ASSERT_LE(0, fd); ++ ++ sem_args.count = 2; ++ sem_args.max = 2; ++ sem_args.sem = 0xdeadbeef; ++ ret = ioctl(fd, NTSYNC_IOC_CREATE_SEM, &sem_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_NE(0xdeadbeef, sem_args.sem); ++ objs[0] = sem_args.sem; ++ ++ sem_args.count = 1; ++ sem_args.max = 2; ++ sem_args.sem = 0xdeadbeef; ++ ret = ioctl(fd, NTSYNC_IOC_CREATE_SEM, &sem_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_NE(0xdeadbeef, sem_args.sem); ++ objs[1] = sem_args.sem; ++ ++ event_args.manual = true; ++ event_args.signaled = true; ++ ret = ioctl(fd, NTSYNC_IOC_CREATE_EVENT, &event_args); ++ EXPECT_EQ(0, ret); ++ ++ ret = wait_all_alert(fd, 2, objs, 123, event_args.event, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, index); ++ ++ ret = wait_all_alert(fd, 2, objs, 123, event_args.event, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(2, index); ++ ++ ret = ioctl(fd, NTSYNC_IOC_DELETE, &event_args.event); ++ EXPECT_EQ(0, ret); ++ ++ /* test with an auto-reset event */ ++ ++ event_args.manual = false; ++ event_args.signaled = true; ++ ret = ioctl(fd, NTSYNC_IOC_CREATE_EVENT, &event_args); ++ EXPECT_EQ(0, ret); ++ ++ sem_args.sem = objs[1]; ++ sem_args.count = 2; ++ ret = ioctl(fd, NTSYNC_IOC_PUT_SEM, &sem_args); ++ EXPECT_EQ(0, ret); ++ ++ ret = wait_all_alert(fd, 2, objs, 123, event_args.event, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, index); ++ ++ ret = wait_all_alert(fd, 2, objs, 123, event_args.event, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(2, index); ++ ++ ret = wait_all_alert(fd, 2, objs, 123, event_args.event, &index); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(ETIMEDOUT, errno); ++ ++ ret = ioctl(fd, NTSYNC_IOC_DELETE, &event_args.event); ++ EXPECT_EQ(0, ret); ++ ++ ret = ioctl(fd, NTSYNC_IOC_DELETE, &objs[0]); ++ EXPECT_EQ(0, ret); ++ ret = ioctl(fd, NTSYNC_IOC_DELETE, &objs[1]); ++ EXPECT_EQ(0, ret); ++ ++ close(fd); ++} ++ + TEST_HARNESS_MAIN +-- +2.43.0 + +From 96d776b0b301b8a9f0b2fc27e62ff8b26e407072 Mon Sep 17 00:00:00 2001 +From: Elizabeth Figura <zfigura@codeweavers.com> +Date: Wed, 20 Apr 2022 18:24:43 -0500 +Subject: [PATCH 30/32] selftests: ntsync: Add some tests for wakeup signaling + via alerts. + +--- + .../testing/selftests/drivers/ntsync/ntsync.c | 66 +++++++++++++++++++ + 1 file changed, 66 insertions(+) + +diff --git a/tools/testing/selftests/drivers/ntsync/ntsync.c b/tools/testing/selftests/drivers/ntsync/ntsync.c +index a58387d6744b..fda9f1c5e810 100644 +--- a/tools/testing/selftests/drivers/ntsync/ntsync.c ++++ b/tools/testing/selftests/drivers/ntsync/ntsync.c +@@ -1243,8 +1243,12 @@ TEST(wake_all) + TEST(alert_any) + { + struct ntsync_event_args event_args = {0}; ++ struct ntsync_wait_args wait_args = {0}; + struct ntsync_sem_args sem_args = {0}; ++ struct wait_args thread_args; ++ struct timespec timeout; + __u32 objs[2], index; ++ pthread_t thread; + int fd, ret; + + fd = open("/dev/ntsync", O_CLOEXEC | O_RDONLY); +@@ -1293,6 +1297,35 @@ TEST(alert_any) + EXPECT_EQ(0, ret); + EXPECT_EQ(2, index); + ++ /* test wakeup via alert */ ++ ++ ret = ioctl(fd, NTSYNC_IOC_RESET_EVENT, &event_args); ++ EXPECT_EQ(0, ret); ++ ++ get_abs_timeout(&timeout, CLOCK_MONOTONIC, 1000); ++ wait_args.timeout = (uintptr_t)&timeout; ++ wait_args.objs = (uintptr_t)objs; ++ wait_args.count = 2; ++ wait_args.owner = 123; ++ wait_args.index = 0xdeadbeef; ++ wait_args.alert = event_args.event; ++ thread_args.fd = fd; ++ thread_args.args = &wait_args; ++ thread_args.request = NTSYNC_IOC_WAIT_ANY; ++ ret = pthread_create(&thread, NULL, wait_thread, &thread_args); ++ EXPECT_EQ(0, ret); ++ ++ ret = wait_for_thread(thread, 100); ++ EXPECT_EQ(ETIMEDOUT, ret); ++ ++ ret = ioctl(fd, NTSYNC_IOC_SET_EVENT, &event_args); ++ EXPECT_EQ(0, ret); ++ ++ ret = wait_for_thread(thread, 100); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, thread_args.ret); ++ EXPECT_EQ(2, wait_args.index); ++ + ret = ioctl(fd, NTSYNC_IOC_DELETE, &event_args.event); + EXPECT_EQ(0, ret); + +@@ -1334,8 +1367,12 @@ TEST(alert_any) + TEST(alert_all) + { + struct ntsync_event_args event_args = {0}; ++ struct ntsync_wait_args wait_args = {0}; + struct ntsync_sem_args sem_args = {0}; ++ struct wait_args thread_args; ++ struct timespec timeout; + __u32 objs[2], index; ++ pthread_t thread; + int fd, ret; + + fd = open("/dev/ntsync", O_CLOEXEC | O_RDONLY); +@@ -1370,6 +1407,35 @@ TEST(alert_all) + EXPECT_EQ(0, ret); + EXPECT_EQ(2, index); + ++ /* test wakeup via alert */ ++ ++ ret = ioctl(fd, NTSYNC_IOC_RESET_EVENT, &event_args); ++ EXPECT_EQ(0, ret); ++ ++ get_abs_timeout(&timeout, CLOCK_MONOTONIC, 1000); ++ wait_args.timeout = (uintptr_t)&timeout; ++ wait_args.objs = (uintptr_t)objs; ++ wait_args.count = 2; ++ wait_args.owner = 123; ++ wait_args.index = 0xdeadbeef; ++ wait_args.alert = event_args.event; ++ thread_args.fd = fd; ++ thread_args.args = &wait_args; ++ thread_args.request = NTSYNC_IOC_WAIT_ALL; ++ ret = pthread_create(&thread, NULL, wait_thread, &thread_args); ++ EXPECT_EQ(0, ret); ++ ++ ret = wait_for_thread(thread, 100); ++ EXPECT_EQ(ETIMEDOUT, ret); ++ ++ ret = ioctl(fd, NTSYNC_IOC_SET_EVENT, &event_args); ++ EXPECT_EQ(0, ret); ++ ++ ret = wait_for_thread(thread, 100); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, thread_args.ret); ++ EXPECT_EQ(2, wait_args.index); ++ + ret = ioctl(fd, NTSYNC_IOC_DELETE, &event_args.event); + EXPECT_EQ(0, ret); + +-- +2.43.0 + +From 5ecf8efc0d4f019edcf4334b54314226bc30bd62 Mon Sep 17 00:00:00 2001 +From: Elizabeth Figura <zfigura@codeweavers.com> +Date: Fri, 5 Mar 2021 12:22:55 -0600 +Subject: [PATCH 31/32] maintainers: Add an entry for ntsync. + +--- + MAINTAINERS | 9 +++++++++ + 1 file changed, 9 insertions(+) + +diff --git a/MAINTAINERS b/MAINTAINERS +index a7c4cf8201e0..90e84b768c42 100644 +--- a/MAINTAINERS ++++ b/MAINTAINERS +@@ -15341,6 +15341,15 @@ T: git https://github.com/Paragon-Software-Group/linux-ntfs3.git + F: Documentation/filesystems/ntfs3.rst + F: fs/ntfs3/ + ++NTSYNC SYNCHRONIZATION PRIMITIVE DRIVER ++M: Elizabeth Figura <zfigura@codeweavers.com> ++L: wine-devel@winehq.org ++S: Supported ++F: Documentation/userspace-api/ntsync.rst ++F: drivers/misc/ntsync.c ++F: include/uapi/linux/ntsync.h ++F: tools/testing/selftests/drivers/ntsync/ ++ + NUBUS SUBSYSTEM + M: Finn Thain <fthain@linux-m68k.org> + L: linux-m68k@lists.linux-m68k.org +-- +2.43.0 + +From e4db1ebaa7b9b150bcf54a4d57699e746b4d5b75 Mon Sep 17 00:00:00 2001 +From: Peter Jung <admin@ptr1337.dev> +Date: Wed, 24 Jan 2024 17:37:06 +0100 +Subject: [PATCH 32/32] docs: ntsync: Add documentation for the ntsync uAPI. + +Signed-off-by: Peter Jung <admin@ptr1337.dev> +--- + Documentation/userspace-api/index.rst | 1 + + Documentation/userspace-api/ntsync.rst | 445 +++++++++++++++++++++++++ + 2 files changed, 446 insertions(+) + create mode 100644 Documentation/userspace-api/ntsync.rst + +diff --git a/Documentation/userspace-api/index.rst b/Documentation/userspace-api/index.rst +index 031df47a7c19..188bf15c4ecf 100644 +--- a/Documentation/userspace-api/index.rst ++++ b/Documentation/userspace-api/index.rst +@@ -33,6 +33,7 @@ place where this information is gathered. + sysfs-platform_profile + vduse + futex2 ++ ntsync + + .. only:: subproject and html + +diff --git a/Documentation/userspace-api/ntsync.rst b/Documentation/userspace-api/ntsync.rst +new file mode 100644 +index 000000000000..45d945b3e246 +--- /dev/null ++++ b/Documentation/userspace-api/ntsync.rst +@@ -0,0 +1,445 @@ ++=================================== ++NT synchronization primitive driver ++=================================== ++ ++This page documents the user-space API for the ntsync driver. ++ ++ntsync is a support driver for emulation of NT synchronization ++primitives by user-space NT emulators. It exists because implementation ++in user-space, using existing tools, cannot match Windows performance ++while offering accurate semantics. It is implemented entirely in ++software, and does not drive any hardware device. ++ ++This interface is meant as a compatibility tool only, and should not ++be used for general synchronization. Instead use generic, versatile ++interfaces such as futex(2) and poll(2). ++ ++Synchronization primitives ++========================== ++ ++The ntsync driver exposes three types of synchronization primitives: ++semaphores, mutexes, and events. ++ ++A semaphore holds a single volatile 32-bit counter, and a static ++32-bit integer denoting the maximum value. It is considered signaled ++when the counter is nonzero. The counter is decremented by one when a ++wait is satisfied. Both the initial and maximum count are established ++when the semaphore is created. ++ ++A mutex holds a volatile 32-bit recursion count, and a volatile 32-bit ++identifier denoting its owner. A mutex is considered signaled when its ++owner is zero (indicating that it is not owned). The recursion count ++is incremented when a wait is satisfied, and ownership is set to the ++given identifier. ++ ++A mutex also holds an internal flag denoting whether its previous ++owner has died; such a mutex is said to be inconsistent. Owner death ++is not tracked automatically based on thread death, but rather must be ++communicated using ``NTSYNC_IOC_KILL_OWNER``. An inconsistent mutex ++is inherently considered unowned. ++ ++Except for the "unowned" semantics of zero, the actual value of the ++owner identifier is not interpreted by the ntsync driver at all. The ++intended use is to store a thread identifier; however, the ntsync ++driver does not actually validate that a calling thread provides ++consistent or unique identifiers. ++ ++An event holds a volatile boolean state denoting whether it is ++signaled or not. There are two types of events, auto-reset and ++manual-reset. An auto-reset event is designaled when a wait is ++satisfied; a manual-reset event is not. The event type is specified ++when the event is created. ++ ++Unless specified otherwise, all operations on an object are atomic and ++totally ordered with respect to other operations on the same object. ++ ++Objects are represented by unsigned 32-bit integers. ++ ++Char device ++=========== ++ ++The ntsync driver creates a single char device /dev/ntsync. Each ++file description opened on the device represents a unique namespace. ++That is, objects created on one open file description are shared ++across all its individual descriptors, but are not shared with other ++open() calls on the same device. The same file description may be ++shared across multiple processes. ++ ++ioctl reference ++=============== ++ ++All operations on the device are done through ioctls. There are four ++structures used in ioctl calls:: ++ ++ struct ntsync_sem_args { ++ __u32 sem; ++ __u32 count; ++ __u32 max; ++ }; ++ ++ struct ntsync_mutex_args { ++ __u32 mutex; ++ __u32 owner; ++ __u32 count; ++ }; ++ ++ struct ntsync_event_args { ++ __u32 event; ++ __u32 signaled; ++ __u32 manual; ++ }; ++ ++ struct ntsync_wait_args { ++ __u64 timeout; ++ __u64 objs; ++ __u32 count; ++ __u32 owner; ++ __u32 index; ++ __u32 alert; ++ }; ++ ++Depending on the ioctl, members of the structure may be used as input, ++output, or not at all. All ioctls return 0 on success. ++ ++The ioctls are as follows: ++ ++.. c:macro:: NTSYNC_IOC_CREATE_SEM ++ ++ Create a semaphore object. Takes a pointer to struct ++ :c:type:`ntsync_sem_args`, which is used as follows: ++ ++ .. list-table:: ++ ++ * - ``sem`` ++ - On output, contains the identifier of the created semaphore. ++ * - ``count`` ++ - Initial count of the semaphore. ++ * - ``max`` ++ - Maximum count of the semaphore. ++ ++ Fails with ``EINVAL`` if ``count`` is greater than ``max``. ++ ++.. c:macro:: NTSYNC_IOC_CREATE_MUTEX ++ ++ Create a mutex object. Takes a pointer to struct ++ :c:type:`ntsync_mutex_args`, which is used as follows: ++ ++ .. list-table:: ++ ++ * - ``mutex`` ++ - On output, contains the identifier of the created mutex. ++ * - ``count`` ++ - Initial recursion count of the mutex. ++ * - ``owner`` ++ - Initial owner of the mutex. ++ ++ If ``owner`` is nonzero and ``count`` is zero, or if ``owner`` is ++ zero and ``count`` is nonzero, the function fails with ``EINVAL``. ++ ++.. c:macro:: NTSYNC_IOC_CREATE_EVENT ++ ++ Create an event object. Takes a pointer to struct ++ :c:type:`ntsync_event_args`, which is used as follows: ++ ++ .. list-table:: ++ ++ * - ``event`` ++ - On output, contains the identifier of the created event. ++ * - ``signaled`` ++ - If nonzero, the event is initially signaled, otherwise ++ nonsignaled. ++ * - ``manual`` ++ - If nonzero, the event is a manual-reset event, otherwise ++ auto-reset. ++ ++.. c:macro:: NTSYNC_IOC_DELETE ++ ++ Delete an object of any type. Takes an input-only pointer to a ++ 32-bit integer denoting the object to delete. ++ ++ Wait ioctls currently in progress are not interrupted, and behave as ++ if the object remains valid. ++ ++.. c:macro:: NTSYNC_IOC_PUT_SEM ++ ++ Post to a semaphore object. Takes a pointer to struct ++ :c:type:`ntsync_sem_args`, which is used as follows: ++ ++ .. list-table:: ++ ++ * - ``sem`` ++ - Semaphore object to post to. ++ * - ``count`` ++ - Count to add to the semaphore. On output, contains the ++ previous count of the semaphore. ++ * - ``max`` ++ - Not used. ++ ++ If adding ``count`` to the semaphore's current count would raise the ++ latter past the semaphore's maximum count, the ioctl fails with ++ ``EOVERFLOW`` and the semaphore is not affected. If raising the ++ semaphore's count causes it to become signaled, eligible threads ++ waiting on this semaphore will be woken and the semaphore's count ++ decremented appropriately. ++ ++.. c:macro:: NTSYNC_IOC_PUT_MUTEX ++ ++ Release a mutex object. Takes a pointer to struct ++ :c:type:`ntsync_mutex_args`, which is used as follows: ++ ++ .. list-table:: ++ ++ * - ``mutex`` ++ - Mutex object to release. ++ * - ``owner`` ++ - Mutex owner identifier. ++ * - ``count`` ++ - On output, contains the previous recursion count. ++ ++ If ``owner`` is zero, the ioctl fails with ``EINVAL``. If ``owner`` ++ is not the current owner of the mutex, the ioctl fails with ++ ``EPERM``. ++ ++ The mutex's count will be decremented by one. If decrementing the ++ mutex's count causes it to become zero, the mutex is marked as ++ unowned and signaled, and eligible threads waiting on it will be ++ woken as appropriate. ++ ++.. c:macro:: NTSYNC_IOC_SET_EVENT ++ ++ Signal an event object. Takes a pointer to struct ++ :c:type:`ntsync_event_args`, which is used as follows: ++ ++ .. list-table:: ++ ++ * - ``event`` ++ - Event object to set. ++ * - ``signaled`` ++ - On output, contains the previous state of the event. ++ * - ``manual`` ++ - Unused. ++ ++ Eligible threads will be woken, and auto-reset events will be ++ designaled appropriately. ++ ++.. c:macro:: NTSYNC_IOC_RESET_EVENT ++ ++ Designal an event object. Takes a pointer to struct ++ :c:type:`ntsync_event_args`, which is used as follows: ++ ++ .. list-table:: ++ ++ * - ``event`` ++ - Event object to reset. ++ * - ``signaled`` ++ - On output, contains the previous state of the event. ++ * - ``manual`` ++ - Unused. ++ ++.. c:macro:: NTSYNC_IOC_PULSE_EVENT ++ ++ Wake threads waiting on an event object without leaving it in a ++ signaled state. Takes a pointer to struct ++ :c:type:`ntsync_event_args`, which is used as follows: ++ ++ .. list-table:: ++ ++ * - ``event`` ++ - Event object to pulse. ++ * - ``signaled`` ++ - On output, contains the previous state of the event. ++ * - ``manual`` ++ - Unused. ++ ++ A pulse operation can be thought of as a set followed by a reset, ++ performed as a single atomic operation. If two threads are waiting ++ on an auto-reset event which is pulsed, only one will be woken. If ++ two threads are waiting a manual-reset event which is pulsed, both ++ will be woken. However, in both cases, the event will be unsignaled ++ afterwards, and a simultaneous read operation will always report the ++ event as unsignaled. ++ ++.. c:macro:: NTSYNC_IOC_READ_SEM ++ ++ Read the current state of a semaphore object. Takes a pointer to ++ struct :c:type:`ntsync_sem_args`, which is used as follows: ++ ++ .. list-table:: ++ ++ * - ``sem`` ++ - Semaphore object to read. ++ * - ``count`` ++ - On output, contains the current count of the semaphore. ++ * - ``max`` ++ - On output, contains the maximum count of the semaphore. ++ ++.. c:macro:: NTSYNC_IOC_READ_MUTEX ++ ++ Read the current state of a mutex object. Takes a pointer to struct ++ :c:type:`ntsync_mutex_args`, which is used as follows: ++ ++ .. list-table:: ++ ++ * - ``mutex`` ++ - Mutex object to read. ++ * - ``owner`` ++ - On output, contains the current owner of the mutex, or zero ++ if the mutex is not currently owned. ++ * - ``count`` ++ - On output, contains the current recursion count of the mutex. ++ ++ If the mutex is marked as inconsistent, the function fails with ++ ``EOWNERDEAD``. In this case, ``count`` and ``owner`` are set to ++ zero. ++ ++.. c:macro:: NTSYNC_IOC_READ_EVENT ++ ++ Read the current state of an event object. Takes a pointer to struct ++ :c:type:`ntsync_event_args`, which is used as follows: ++ ++ .. list-table:: ++ ++ * - ``event`` ++ - Event object. ++ * - ``signaled`` ++ - On output, contains the current state of the event. ++ * - ``manual`` ++ - On output, contains 1 if the event is a manual-reset event, ++ and 0 otherwise. ++ ++.. c:macro:: NTSYNC_IOC_KILL_OWNER ++ ++ Mark any mutexes owned by the given owner as unowned and ++ inconsistent. Takes an input-only pointer to a 32-bit integer ++ denoting the owner. If the owner is zero, the ioctl fails with ++ ``EINVAL``. ++ ++ For each mutex currently owned by the given owner, eligible threads ++ waiting on said mutex will be woken as appropriate (and such waits ++ will fail with ``EOWNERDEAD``, as described below). ++ ++ The operation as a whole is not atomic; however, the modification of ++ each mutex is atomic and totally ordered with respect to other ++ operations on the same mutex. ++ ++.. c:macro:: NTSYNC_IOC_WAIT_ANY ++ ++ Poll on any of a list of objects, atomically acquiring at most one. ++ Takes a pointer to struct :c:type:`ntsync_wait_args`, which is ++ used as follows: ++ ++ .. list-table:: ++ ++ * - ``timeout`` ++ - Optional pointer to a 64-bit struct :c:type:`timespec` ++ (specified as an integer so that the structure has the same ++ size regardless of architecture). The timeout is specified in ++ absolute format, as measured against the MONOTONIC clock. If ++ the timeout is equal to or earlier than the current time, the ++ function returns immediately without sleeping. If ``timeout`` ++ is zero, i.e. NULL, the function will sleep until an object ++ is signaled, and will not fail with ``ETIMEDOUT``. ++ * - ``objs`` ++ - Pointer to an array of ``count`` 32-bit object identifiers ++ (specified as an integer so that the structure has the same ++ size regardless of architecture). If any identifier is ++ invalid, the function fails with ``EINVAL``. ++ * - ``count`` ++ - Number of object identifiers specified in the ``objs`` array. ++ If greater than ``NTSYNC_MAX_WAIT_COUNT``, the function fails ++ with ``EINVAL``. ++ * - ``owner`` ++ - Mutex owner identifier. If any object in ``objs`` is a mutex, ++ the ioctl will attempt to acquire that mutex on behalf of ++ ``owner``. If ``owner`` is zero, the ioctl fails with ++ ``EINVAL``. ++ * - ``index`` ++ - On success, contains the index (into ``objs``) of the object ++ which was signaled. If ``alert`` was signaled instead, ++ this contains ``count``. ++ * - ``alert`` ++ - Optional event object identifier. If nonzero, this specifies ++ an "alert" event object which, if signaled, will terminate ++ the wait. If nonzero, the identifier must point to a valid ++ event. ++ ++ This function attempts to acquire one of the given objects. If ++ unable to do so, it sleeps until an object becomes signaled, ++ subsequently acquiring it, or the timeout expires. In the latter ++ case the ioctl fails with ``ETIMEDOUT``. The function only acquires ++ one object, even if multiple objects are signaled. ++ ++ A semaphore is considered to be signaled if its count is nonzero, ++ and is acquired by decrementing its count by one. A mutex is ++ considered to be signaled if it is unowned or if its owner matches ++ the ``owner`` argument, and is acquired by incrementing its ++ recursion count by one and setting its owner to the ``owner`` ++ argument. An auto-reset event is acquired by designaling it; a ++ manual-reset event is not affected by acquisition. ++ ++ Acquisition is atomic and totally ordered with respect to other ++ operations on the same object. If two wait operations (with ++ different ``owner`` identifiers) are queued on the same mutex, only ++ one is signaled. If two wait operations are queued on the same ++ semaphore, and a value of one is posted to it, only one is signaled. ++ The order in which threads are signaled is not specified. ++ ++ If an inconsistent mutex is acquired, the ioctl fails with ++ ``EOWNERDEAD``. Although this is a failure return, the function may ++ otherwise be considered successful. The mutex is marked as owned by ++ the given owner (with a recursion count of 1) and as no longer ++ inconsistent, and ``index`` is still set to the index of the mutex. ++ ++ The ``alert`` argument is an "extra" event which can terminate the ++ wait, independently of all other objects. If members of ``objs`` and ++ ``alert`` are both simultaneously signaled, a member of ``objs`` ++ will always be given priority and acquired first. Aside from this, ++ for "any" waits, there is no difference between passing an event as ++ this parameter, and passing it as an additional object at the end of ++ the ``objs`` array. For "all" waits, there is an additional ++ difference, as described below. ++ ++ It is valid to pass the same object more than once, including by ++ passing the same event in the ``objs`` array and in ``alert``. If a ++ wakeup occurs due to that object being signaled, ``index`` is set to ++ the lowest index corresponding to that object. ++ ++ The function may fail with ``EINTR`` if a signal is received. ++ ++.. c:macro:: NTSYNC_IOC_WAIT_ALL ++ ++ Poll on a list of objects, atomically acquiring all of them. Takes a ++ pointer to struct :c:type:`ntsync_wait_args`, which is used ++ identically to ``NTSYNC_IOC_WAIT_ANY``, except that ``index`` is ++ always filled with zero on success if not woken via alert. ++ ++ This function attempts to simultaneously acquire all of the given ++ objects. If unable to do so, it sleeps until all objects become ++ simultaneously signaled, subsequently acquiring them, or the timeout ++ expires. In the latter case the ioctl fails with ``ETIMEDOUT`` and ++ no objects are modified. ++ ++ Objects may become signaled and subsequently designaled (through ++ acquisition by other threads) while this thread is sleeping. Only ++ once all objects are simultaneously signaled does the ioctl acquire ++ them and return. The entire acquisition is atomic and totally ++ ordered with respect to other operations on any of the given ++ objects. ++ ++ If an inconsistent mutex is acquired, the ioctl fails with ++ ``EOWNERDEAD``. Similarly to ``NTSYNC_IOC_WAIT_ANY``, all objects ++ are nevertheless marked as acquired. Note that if multiple mutex ++ objects are specified, there is no way to know which were marked as ++ inconsistent. ++ ++ As with "any" waits, the ``alert`` argument is an "extra" event ++ which can terminate the wait. Critically, however, an "all" wait ++ will succeed if all members in ``objs`` are signaled, *or* if ++ ``alert`` is signaled. In the latter case ``index`` will be set to ++ ``count``. As with "any" waits, if both conditions are filled, the ++ former takes priority, and objects in ``objs`` will be acquired. ++ ++ Unlike ``NTSYNC_IOC_WAIT_ANY``, it is not valid to pass the same ++ object more than once, nor is it valid to pass the same object in ++ ``objs`` and in ``alert``. If this is attempted, the function fails ++ with ``EINVAL``. +-- +2.43.0 + |