From 2036743631900e676a3e1df527d75944b310fd01 Mon Sep 17 00:00:00 2001 From: Nanotwerp Date: Sat, 11 May 2024 23:05:49 -0400 Subject: [PATCH] 6.9: update ntsync5 patch from v3 to v4 The ntsync5-v4 patch works fine, as long as Wine can detect the kernel headers. See [this](https://github.com/Frogging-Family/wine-tkg-git/issues/936#issuecomment-2105899240) and [this](https://github.com/Frogging-Family/wine-tkg-git/issues/936#issuecomment-2106099799). --- 6.9/0008-ntsync.patch | 6072 ++++++++++++++++++++++++++--------------- 1 file changed, 3921 insertions(+), 2151 deletions(-) diff --git a/6.9/0008-ntsync.patch b/6.9/0008-ntsync.patch index 31ab03b9..fda79584 100644 --- a/6.9/0008-ntsync.patch +++ b/6.9/0008-ntsync.patch @@ -1,42 +1,151 @@ -From 0b39251f3683488bf42636c97cfdd43108129d1c Mon Sep 17 00:00:00 2001 -From: Peter Jung -Date: Fri, 10 May 2024 13:14:28 +0200 -Subject: [PATCH 8/9] ntsync +From 4f45013020bdd5b3b31c4bbfba3ffd94ae7435a6 Mon Sep 17 00:00:00 2001 +From: Elizabeth Figura +Date: Thu, 28 Mar 2024 19:05:52 -0500 +Subject: [PATCH 01/30] ntsync: Introduce the ntsync driver and character + device. -Signed-off-by: Peter Jung +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. + +Signed-off-by: Elizabeth Figura +Link: https://lore.kernel.org/r/20240329000621.148791-2-zfigura@codeweavers.com +Signed-off-by: Greg Kroah-Hartman --- - Documentation/userspace-api/index.rst | 1 + - .../userspace-api/ioctl/ioctl-number.rst | 2 + - Documentation/userspace-api/ntsync.rst | 399 +++++ - MAINTAINERS | 9 + - drivers/misc/Kconfig | 9 + - drivers/misc/Makefile | 1 + - drivers/misc/ntsync.c | 1146 ++++++++++++++ - include/uapi/linux/ntsync.h | 62 + - tools/testing/selftests/Makefile | 1 + - .../testing/selftests/drivers/ntsync/Makefile | 8 + - tools/testing/selftests/drivers/ntsync/config | 1 + - .../testing/selftests/drivers/ntsync/ntsync.c | 1407 +++++++++++++++++ - 12 files changed, 3046 insertions(+) - create mode 100644 Documentation/userspace-api/ntsync.rst + drivers/misc/Kconfig | 11 +++++++++ + drivers/misc/Makefile | 1 + + drivers/misc/ntsync.c | 52 +++++++++++++++++++++++++++++++++++++++++++ + 3 files changed, 64 insertions(+) create mode 100644 drivers/misc/ntsync.c - create mode 100644 include/uapi/linux/ntsync.h - 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/Documentation/userspace-api/index.rst b/Documentation/userspace-api/index.rst -index afecfe3cc4a8..cf6331b1d39f 100644 ---- a/Documentation/userspace-api/index.rst -+++ b/Documentation/userspace-api/index.rst -@@ -49,6 +49,7 @@ Devices and I/O - dcdbas - vduse - isapnp -+ ntsync +diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig +index 4fb291f0bf7c..801ed229ed7d 100644 +--- a/drivers/misc/Kconfig ++++ b/drivers/misc/Kconfig +@@ -506,6 +506,17 @@ config OPEN_DICE + + If unsure, say N. - Everything else - =============== ++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 + tristate "Guest vCPU stall detector" + depends on OF && HAS_IOMEM +diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile +index ea6ea5bbbc9c..153a3f4837e8 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..bd76e653d83e +--- /dev/null ++++ b/drivers/misc/ntsync.c +@@ -0,0 +1,52 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++/* ++ * ntsync.c - Kernel driver for NT synchronization primitives ++ * ++ * Copyright (C) 2024 Elizabeth Figura ++ */ ++ ++#include ++#include ++#include ++ ++#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 = compat_ptr_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"); +-- +2.44.0 + + +From a404955aceef932dc3a61f8c10623c14cd52d151 Mon Sep 17 00:00:00 2001 +From: Elizabeth Figura +Date: Thu, 28 Mar 2024 19:05:53 -0500 +Subject: [PATCH 02/30] ntsync: Introduce NTSYNC_IOC_CREATE_SEM. + +This corresponds to the NT syscall NtCreateSemaphore(). + +Semaphores are one of three types of object to be implemented in this driver, +the others being mutexes and events. + +An NT semaphore contains a 32-bit counter, and is signaled and can be acquired +when the counter is nonzero. The counter has a maximum value which is specified +at creation time. The initial value of the semaphore is also specified at +creation time. There are no restrictions on the maximum and initial value. + +Each object is exposed as an file, to which any number of fds may be opened. +When all fds are closed, the object is deleted. + +Objects hold a pointer to the ntsync_device that created them. The device's +reference count is driven by struct file. + +Signed-off-by: Elizabeth Figura +Link: https://lore.kernel.org/r/20240329000621.148791-3-zfigura@codeweavers.com +Signed-off-by: Greg Kroah-Hartman +--- + .../userspace-api/ioctl/ioctl-number.rst | 2 + + drivers/misc/ntsync.c | 131 ++++++++++++++++++ + include/uapi/linux/ntsync.h | 21 +++ + 3 files changed, 154 insertions(+) + create mode 100644 include/uapi/linux/ntsync.h + diff --git a/Documentation/userspace-api/ioctl/ioctl-number.rst b/Documentation/userspace-api/ioctl/ioctl-number.rst index c472423412bf..a141e8e65c5d 100644 --- a/Documentation/userspace-api/ioctl/ioctl-number.rst @@ -50,529 +159,769 @@ index c472423412bf..a141e8e65c5d 100644 'O' 00-06 mtd/ubi-user.h UBI 'P' all linux/soundcard.h conflict! 'P' 60-6F sound/sscape_ioctl.h conflict! -diff --git a/Documentation/userspace-api/ntsync.rst b/Documentation/userspace-api/ntsync.rst -new file mode 100644 -index 000000000000..202c2350d3af ---- /dev/null -+++ b/Documentation/userspace-api/ntsync.rst -@@ -0,0 +1,399 @@ -+=================================== -+NT synchronization primitive driver -+=================================== -+ -+This page documents the user-space API for the ntsync driver. +diff --git a/drivers/misc/ntsync.c b/drivers/misc/ntsync.c +index bd76e653d83e..20158ec148bc 100644 +--- a/drivers/misc/ntsync.c ++++ b/drivers/misc/ntsync.c +@@ -5,26 +5,157 @@ + * Copyright (C) 2024 Elizabeth Figura + */ + ++#include ++#include + #include + #include + #include ++#include ++#include + + #define NTSYNC_NAME "ntsync" + ++enum ntsync_type { ++ NTSYNC_TYPE_SEM, ++}; + -+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. ++/* ++ * Individual synchronization primitives are represented by ++ * struct ntsync_obj, and each primitive is backed by a file. ++ * ++ * The whole namespace is represented by a struct ntsync_device also ++ * backed by a file. ++ * ++ * Both rely on struct file for reference counting. Individual ++ * ntsync_obj objects take a reference to the device when created. ++ */ + -+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). ++struct ntsync_obj { ++ enum ntsync_type type; + -+Synchronization primitives -+========================== ++ union { ++ struct { ++ __u32 count; ++ __u32 max; ++ } sem; ++ } u; + -+The ntsync driver exposes three types of synchronization primitives: -+semaphores, mutexes, and events. ++ struct file *file; ++ struct ntsync_device *dev; ++}; + -+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. ++struct ntsync_device { ++ struct file *file; ++}; + -+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. ++static int ntsync_obj_release(struct inode *inode, struct file *file) ++{ ++ struct ntsync_obj *obj = file->private_data; + -+A mutex also holds an internal flag denoting whether its previous owner -+has died; such a mutex is said to be abandoned. Owner death is not -+tracked automatically based on thread death, but rather must be -+communicated using ``NTSYNC_IOC_MUTEX_KILL``. An abandoned mutex is -+inherently considered unowned. ++ fput(obj->dev->file); ++ kfree(obj); + -+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. ++ return 0; ++} + -+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. ++static const struct file_operations ntsync_obj_fops = { ++ .owner = THIS_MODULE, ++ .release = ntsync_obj_release, ++ .llseek = no_llseek, ++}; + -+Unless specified otherwise, all operations on an object are atomic and -+totally ordered with respect to other operations on the same object. ++static struct ntsync_obj *ntsync_alloc_obj(struct ntsync_device *dev, ++ enum ntsync_type type) ++{ ++ struct ntsync_obj *obj; + -+Objects are represented by files. When all file descriptors to an -+object are closed, that object is deleted. ++ obj = kzalloc(sizeof(*obj), GFP_KERNEL); ++ if (!obj) ++ return NULL; ++ obj->type = type; ++ obj->dev = dev; ++ get_file(dev->file); + -+Char device -+=========== ++ return obj; ++} + -+The ntsync driver creates a single char device /dev/ntsync. Each file -+description opened on the device represents a unique instance intended -+to back an individual NT virtual machine. Objects created by one ntsync -+instance may only be used with other objects created by the same -+instance. ++static int ntsync_obj_get_fd(struct ntsync_obj *obj) ++{ ++ struct file *file; ++ int fd; + -+ioctl reference -+=============== ++ fd = get_unused_fd_flags(O_CLOEXEC); ++ if (fd < 0) ++ return fd; ++ file = anon_inode_getfile("ntsync", &ntsync_obj_fops, obj, O_RDWR); ++ if (IS_ERR(file)) { ++ put_unused_fd(fd); ++ return PTR_ERR(file); ++ } ++ obj->file = file; ++ fd_install(fd, file); + -+All operations on the device are done through ioctls. There are four -+structures used in ioctl calls:: ++ return fd; ++} + -+ struct ntsync_sem_args { -+ __u32 sem; -+ __u32 count; -+ __u32 max; -+ }; ++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; ++ int fd; + -+ struct ntsync_mutex_args { -+ __u32 mutex; -+ __u32 owner; -+ __u32 count; -+ }; ++ if (copy_from_user(&args, argp, sizeof(args))) ++ return -EFAULT; + -+ struct ntsync_event_args { -+ __u32 event; -+ __u32 signaled; -+ __u32 manual; -+ }; ++ if (args.count > args.max) ++ return -EINVAL; + -+ struct ntsync_wait_args { -+ __u64 timeout; -+ __u64 objs; -+ __u32 count; -+ __u32 owner; -+ __u32 index; -+ __u32 alert; -+ __u32 flags; -+ __u32 pad; -+ }; ++ sem = ntsync_alloc_obj(dev, NTSYNC_TYPE_SEM); ++ if (!sem) ++ return -ENOMEM; ++ sem->u.sem.count = args.count; ++ sem->u.sem.max = args.max; ++ fd = ntsync_obj_get_fd(sem); ++ if (fd < 0) { ++ kfree(sem); ++ return fd; ++ } + -+Depending on the ioctl, members of the structure may be used as input, -+output, or not at all. All ioctls return 0 on success. ++ return put_user(fd, &user_args->sem); ++} + -+The ioctls on the device file are as follows: + static int ntsync_char_open(struct inode *inode, struct file *file) + { ++ struct ntsync_device *dev; + -+.. c:macro:: NTSYNC_IOC_CREATE_SEM ++ dev = kzalloc(sizeof(*dev), GFP_KERNEL); ++ if (!dev) ++ return -ENOMEM; + -+ Create a semaphore object. Takes a pointer to struct -+ :c:type:`ntsync_sem_args`, which is used as follows: ++ file->private_data = dev; ++ dev->file = file; + return nonseekable_open(inode, file); + } + + static int ntsync_char_release(struct inode *inode, struct file *file) + { ++ struct ntsync_device *dev = file->private_data; + -+ .. list-table:: ++ kfree(dev); + -+ * - ``sem`` -+ - On output, contains a file descriptor to the created semaphore. -+ * - ``count`` -+ - Initial count of the semaphore. -+ * - ``max`` -+ - Maximum count of the semaphore. + 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; + -+ Fails with ``EINVAL`` if ``count`` is greater than ``max``. + switch (cmd) { ++ case NTSYNC_IOC_CREATE_SEM: ++ return ntsync_create_sem(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..6a4867a6c97b +--- /dev/null ++++ b/include/uapi/linux/ntsync.h +@@ -0,0 +1,21 @@ ++/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ ++/* ++ * Kernel support for NT synchronization primitive emulation ++ * ++ * Copyright (C) 2021-2022 Elizabeth Figura ++ */ + -+.. c:macro:: NTSYNC_IOC_CREATE_MUTEX ++#ifndef __LINUX_NTSYNC_H ++#define __LINUX_NTSYNC_H + -+ Create a mutex object. Takes a pointer to struct -+ :c:type:`ntsync_mutex_args`, which is used as follows: ++#include + -+ .. list-table:: ++struct ntsync_sem_args { ++ __u32 sem; ++ __u32 count; ++ __u32 max; ++}; + -+ * - ``mutex`` -+ - On output, contains a file descriptor to the created mutex. -+ * - ``count`` -+ - Initial recursion count of the mutex. -+ * - ``owner`` -+ - Initial owner of the mutex. ++#define NTSYNC_IOC_CREATE_SEM _IOWR('N', 0x80, struct ntsync_sem_args) + -+ If ``owner`` is nonzero and ``count`` is zero, or if ``owner`` is -+ zero and ``count`` is nonzero, the function fails with ``EINVAL``. ++#endif +-- +2.44.0 + + +From 97c158bb150b41342f7c36cb1817e734afc63bb7 Mon Sep 17 00:00:00 2001 +From: Elizabeth Figura +Date: Thu, 28 Mar 2024 19:05:54 -0500 +Subject: [PATCH 03/30] ntsync: Introduce NTSYNC_IOC_SEM_POST. + +This corresponds to the NT syscall NtReleaseSemaphore(). + +This increases the semaphore's internal counter by the given value, and returns +the previous value. If the counter would overflow the defined maximum, the +function instead fails and returns -EOVERFLOW. + +Signed-off-by: Elizabeth Figura +Link: https://lore.kernel.org/r/20240329000621.148791-4-zfigura@codeweavers.com +Signed-off-by: Greg Kroah-Hartman +--- + drivers/misc/ntsync.c | 72 +++++++++++++++++++++++++++++++++++-- + include/uapi/linux/ntsync.h | 2 ++ + 2 files changed, 71 insertions(+), 3 deletions(-) + +diff --git a/drivers/misc/ntsync.c b/drivers/misc/ntsync.c +index 20158ec148bc..3c2f743c58b0 100644 +--- a/drivers/misc/ntsync.c ++++ b/drivers/misc/ntsync.c +@@ -10,7 +10,9 @@ + #include + #include + #include ++#include + #include ++#include + #include + + #define NTSYNC_NAME "ntsync" +@@ -31,23 +33,70 @@ enum ntsync_type { + */ + + struct ntsync_obj { ++ spinlock_t lock; + -+.. c:macro:: NTSYNC_IOC_CREATE_EVENT + enum ntsync_type type; + ++ struct file *file; ++ struct ntsync_device *dev; + -+ Create an event object. Takes a pointer to struct -+ :c:type:`ntsync_event_args`, which is used as follows: ++ /* The following fields are protected by the object lock. */ + union { + struct { + __u32 count; + __u32 max; + } sem; + } u; +- +- struct file *file; +- struct ntsync_device *dev; + }; + + struct ntsync_device { + struct file *file; + }; + ++/* ++ * Actually change the semaphore state, returning -EOVERFLOW if it is made ++ * invalid. ++ */ ++static int post_sem_state(struct ntsync_obj *sem, __u32 count) ++{ ++ __u32 sum; + -+ .. list-table:: ++ lockdep_assert_held(&sem->lock); + -+ * - ``event`` -+ - On output, contains a file descriptor to 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. ++ if (check_add_overflow(sem->u.sem.count, count, &sum) || ++ sum > sem->u.sem.max) ++ return -EOVERFLOW; + -+The ioctls on the individual objects are as follows: ++ sem->u.sem.count = sum; ++ return 0; ++} + -+.. c:macro:: NTSYNC_IOC_SEM_POST ++static int ntsync_sem_post(struct ntsync_obj *sem, void __user *argp) ++{ ++ __u32 __user *user_args = argp; ++ __u32 prev_count; ++ __u32 args; ++ int ret; + -+ Post to a semaphore object. Takes a pointer to a 32-bit integer, -+ which on input holds the count to be added to the semaphore, and on -+ output contains its previous count. ++ if (copy_from_user(&args, argp, sizeof(args))) ++ return -EFAULT; + -+ If adding 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. ++ if (sem->type != NTSYNC_TYPE_SEM) ++ return -EINVAL; + -+.. c:macro:: NTSYNC_IOC_MUTEX_UNLOCK ++ spin_lock(&sem->lock); + -+ Release a mutex object. Takes a pointer to struct -+ :c:type:`ntsync_mutex_args`, which is used as follows: ++ prev_count = sem->u.sem.count; ++ ret = post_sem_state(sem, args); + -+ .. list-table:: ++ spin_unlock(&sem->lock); + -+ * - ``mutex`` -+ - Ignored. -+ * - ``owner`` -+ - Specifies the owner trying to release this mutex. -+ * - ``count`` -+ - On output, contains the previous recursion count. ++ if (!ret && put_user(prev_count, user_args)) ++ ret = -EFAULT; + -+ If ``owner`` is zero, the ioctl fails with ``EINVAL``. If ``owner`` -+ is not the current owner of the mutex, the ioctl fails with -+ ``EPERM``. ++ return ret; ++} + -+ 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. + static int ntsync_obj_release(struct inode *inode, struct file *file) + { + struct ntsync_obj *obj = file->private_data; +@@ -58,9 +107,25 @@ static int ntsync_obj_release(struct inode *inode, struct file *file) + return 0; + } + ++static long ntsync_obj_ioctl(struct file *file, unsigned int cmd, ++ unsigned long parm) ++{ ++ struct ntsync_obj *obj = file->private_data; ++ void __user *argp = (void __user *)parm; + -+.. c:macro:: NTSYNC_IOC_SET_EVENT ++ switch (cmd) { ++ case NTSYNC_IOC_SEM_POST: ++ return ntsync_sem_post(obj, argp); ++ default: ++ return -ENOIOCTLCMD; ++ } ++} + -+ Signal an event object. Takes a pointer to a 32-bit integer, which on -+ output contains the previous state of the event. + static const struct file_operations ntsync_obj_fops = { + .owner = THIS_MODULE, + .release = ntsync_obj_release, ++ .unlocked_ioctl = ntsync_obj_ioctl, ++ .compat_ioctl = compat_ptr_ioctl, + .llseek = no_llseek, + }; + +@@ -75,6 +140,7 @@ static struct ntsync_obj *ntsync_alloc_obj(struct ntsync_device *dev, + obj->type = type; + obj->dev = dev; + get_file(dev->file); ++ spin_lock_init(&obj->lock); + + return obj; + } +diff --git a/include/uapi/linux/ntsync.h b/include/uapi/linux/ntsync.h +index 6a4867a6c97b..dcfa38fdc93c 100644 +--- a/include/uapi/linux/ntsync.h ++++ b/include/uapi/linux/ntsync.h +@@ -18,4 +18,6 @@ struct ntsync_sem_args { + + #define NTSYNC_IOC_CREATE_SEM _IOWR('N', 0x80, struct ntsync_sem_args) + ++#define NTSYNC_IOC_SEM_POST _IOWR('N', 0x81, __u32) + -+ Eligible threads will be woken, and auto-reset events will be -+ designaled appropriately. + #endif +-- +2.44.0 + + +From 7ef774049a632535c7b8ffe80d364648ec5713c1 Mon Sep 17 00:00:00 2001 +From: Elizabeth Figura +Date: Mon, 29 Jan 2024 15:24:38 -0600 +Subject: [PATCH 04/30] 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. + +This patch therefore implements the wait/wake infrastructure which comprises the +core of ntsync's functionality. + +NTSYNC_IOC_WAIT_ANY is a vectored wait function similar to poll(). Unlike +poll(), it "consumes" objects when they are signaled. For semaphores, this means +decreasing one from the internal counter. At most one object can be consumed by +this function. + +This wait/wake model is fundamentally different from that used anywhere else in +the kernel, and for that reason ntsync does not use any existing infrastructure, +such as futexes, kernel mutexes or semaphores, or wait_event(). + +Up to 64 objects can be waited on at once. As soon as one is signaled, the +object with the lowest index is consumed, and that index is returned via the +"index" field. + +A timeout is supported. The timeout is passed as a u64 nanosecond value, which +represents absolute time measured against either the MONOTONIC or REALTIME clock +(controlled by the flags argument). If U64_MAX is passed, the ioctl waits +indefinitely. + +This ioctl validates that all objects belong to the relevant device. This is not +necessary for any technical reason related to NTSYNC_IOC_WAIT_ANY, but will be +necessary for NTSYNC_IOC_WAIT_ALL introduced in the following patch. + +Two u32s of padding are left in the ntsync_wait_args structure; one will be used +by a patch later in the series (which is split out to ease review). +--- + drivers/misc/ntsync.c | 250 ++++++++++++++++++++++++++++++++++++ + include/uapi/linux/ntsync.h | 16 +++ + 2 files changed, 266 insertions(+) + +diff --git a/drivers/misc/ntsync.c b/drivers/misc/ntsync.c +index 3c2f743c58b0..c6f84a5fc8c0 100644 +--- a/drivers/misc/ntsync.c ++++ b/drivers/misc/ntsync.c +@@ -6,11 +6,16 @@ + */ + + #include ++#include + #include + #include ++#include ++#include + #include + #include + #include ++#include ++#include + #include + #include + #include +@@ -30,6 +35,8 @@ enum ntsync_type { + * + * Both rely on struct file for reference counting. Individual + * ntsync_obj objects take a reference to the device when created. ++ * Wait operations take a reference to each object being waited on for ++ * the duration of the wait. + */ + + struct ntsync_obj { +@@ -47,12 +54,56 @@ struct ntsync_obj { + __u32 max; + } sem; + } u; + -+.. c:macro:: NTSYNC_IOC_RESET_EVENT ++ struct list_head any_waiters; ++}; + -+ Designal an event object. Takes a pointer to a 32-bit integer, which -+ on output contains the previous state of the event. ++struct ntsync_q_entry { ++ struct list_head node; ++ struct ntsync_q *q; ++ struct ntsync_obj *obj; ++ __u32 index; ++}; + -+.. c:macro:: NTSYNC_IOC_PULSE_EVENT ++struct ntsync_q { ++ struct task_struct *task; ++ __u32 owner; + -+ Wake threads waiting on an event object while leaving it in an -+ unsignaled state. Takes a pointer to a 32-bit integer, which on -+ output contains the previous state of the event. ++ /* ++ * Protected via atomic_try_cmpxchg(). Only the thread that wins the ++ * compare-and-swap may actually change object states and wake this ++ * task. ++ */ ++ atomic_t signaled; + -+ 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. ++ __u32 count; ++ struct ntsync_q_entry entries[]; + }; + + struct ntsync_device { + struct file *file; + }; + ++static void try_wake_any_sem(struct ntsync_obj *sem) ++{ ++ struct ntsync_q_entry *entry; + -+.. c:macro:: NTSYNC_IOC_READ_SEM ++ lockdep_assert_held(&sem->lock); + -+ Read the current state of a semaphore object. Takes a pointer to -+ struct :c:type:`ntsync_sem_args`, which is used as follows: ++ list_for_each_entry(entry, &sem->any_waiters, node) { ++ struct ntsync_q *q = entry->q; ++ int signaled = -1; + -+ .. list-table:: ++ if (!sem->u.sem.count) ++ break; + -+ * - ``sem`` -+ - Ignored. -+ * - ``count`` -+ - On output, contains the current count of the semaphore. -+ * - ``max`` -+ - On output, contains the maximum count of the semaphore. ++ if (atomic_try_cmpxchg(&q->signaled, &signaled, entry->index)) { ++ sem->u.sem.count--; ++ wake_up_process(q->task); ++ } ++ } ++} + -+.. c:macro:: NTSYNC_IOC_READ_MUTEX + /* + * Actually change the semaphore state, returning -EOVERFLOW if it is made + * invalid. +@@ -88,6 +139,8 @@ static int ntsync_sem_post(struct ntsync_obj *sem, void __user *argp) + + prev_count = sem->u.sem.count; + ret = post_sem_state(sem, args); ++ if (!ret) ++ try_wake_any_sem(sem); + + spin_unlock(&sem->lock); + +@@ -141,6 +194,7 @@ static struct ntsync_obj *ntsync_alloc_obj(struct ntsync_device *dev, + obj->dev = dev; + get_file(dev->file); + spin_lock_init(&obj->lock); ++ INIT_LIST_HEAD(&obj->any_waiters); + + return obj; + } +@@ -191,6 +245,200 @@ static int ntsync_create_sem(struct ntsync_device *dev, void __user *argp) + return put_user(fd, &user_args->sem); + } + ++static struct ntsync_obj *get_obj(struct ntsync_device *dev, int fd) ++{ ++ struct file *file = fget(fd); ++ struct ntsync_obj *obj; + -+ Read the current state of a mutex object. Takes a pointer to struct -+ :c:type:`ntsync_mutex_args`, which is used as follows: ++ if (!file) ++ return NULL; + -+ .. list-table:: ++ if (file->f_op != &ntsync_obj_fops) { ++ fput(file); ++ return NULL; ++ } + -+ * - ``mutex`` -+ - Ignored. -+ * - ``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. ++ obj = file->private_data; ++ if (obj->dev != dev) { ++ fput(file); ++ return NULL; ++ } + -+ If the mutex is marked as abandoned, the function fails with -+ ``EOWNERDEAD``. In this case, ``count`` and ``owner`` are set to -+ zero. ++ return obj; ++} + -+.. c:macro:: NTSYNC_IOC_READ_EVENT ++static void put_obj(struct ntsync_obj *obj) ++{ ++ fput(obj->file); ++} + -+ Read the current state of an event object. Takes a pointer to struct -+ :c:type:`ntsync_event_args`, which is used as follows: ++static int ntsync_schedule(const struct ntsync_q *q, const struct ntsync_wait_args *args) ++{ ++ ktime_t timeout = ns_to_ktime(args->timeout); ++ clockid_t clock = CLOCK_MONOTONIC; ++ ktime_t *timeout_ptr; ++ int ret = 0; + -+ .. list-table:: ++ timeout_ptr = (args->timeout == U64_MAX ? NULL : &timeout); + -+ * - ``event`` -+ - Ignored. -+ * - ``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. ++ if (args->flags & NTSYNC_WAIT_REALTIME) ++ clock = CLOCK_REALTIME; + -+.. c:macro:: NTSYNC_IOC_KILL_OWNER ++ do { ++ if (signal_pending(current)) { ++ ret = -ERESTARTSYS; ++ break; ++ } + -+ Mark a mutex as unowned and abandoned if it is owned by the given -+ owner. Takes an input-only pointer to a 32-bit integer denoting the -+ owner. If the owner is zero, the ioctl fails with ``EINVAL``. If the -+ owner does not own the mutex, the function fails with ``EPERM``. ++ set_current_state(TASK_INTERRUPTIBLE); ++ if (atomic_read(&q->signaled) != -1) { ++ ret = 0; ++ break; ++ } ++ ret = schedule_hrtimeout_range_clock(timeout_ptr, 0, HRTIMER_MODE_ABS, clock); ++ } while (ret < 0); ++ __set_current_state(TASK_RUNNING); + -+ Eligible threads waiting on the mutex will be woken as appropriate -+ (and such waits will fail with ``EOWNERDEAD``, as described below). ++ return ret; ++} + -+.. c:macro:: NTSYNC_IOC_WAIT_ANY ++/* ++ * Allocate and initialize the ntsync_q structure, but do not queue us yet. ++ */ ++static int setup_wait(struct ntsync_device *dev, ++ const struct ntsync_wait_args *args, ++ struct ntsync_q **ret_q) ++{ ++ const __u32 count = args->count; ++ int fds[NTSYNC_MAX_WAIT_COUNT]; ++ struct ntsync_q *q; ++ __u32 i, j; + -+ 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: ++ if (!args->owner) ++ return -EINVAL; + -+ .. list-table:: ++ if (args->pad || args->pad2 || (args->flags & ~NTSYNC_WAIT_REALTIME)) ++ return -EINVAL; + -+ * - ``timeout`` -+ - Absolute timeout in nanoseconds. If ``NTSYNC_WAIT_REALTIME`` -+ is set, the timeout is measured against the REALTIME clock; -+ otherwise it is 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 U64_MAX, the function will sleep until an object is -+ signaled, and will not fail with ``ETIMEDOUT``. -+ * - ``objs`` -+ - Pointer to an array of ``count`` file descriptors -+ (specified as an integer so that the structure has the same -+ size regardless of architecture). If any object is -+ invalid, the function fails with ``EINVAL``. -+ * - ``count`` -+ - Number of objects 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 file descriptor. 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. -+ * - ``flags`` -+ - Zero or more flags. Currently the only flag is -+ ``NTSYNC_WAIT_REALTIME``, which causes the timeout to be -+ measured against the REALTIME clock instead of MONOTONIC. -+ * - ``pad`` -+ - Unused, must be set to zero. ++ if (args->count > NTSYNC_MAX_WAIT_COUNT) ++ return -EINVAL; + -+ 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. ++ if (copy_from_user(fds, u64_to_user_ptr(args->objs), ++ array_size(count, sizeof(*fds)))) ++ return -EFAULT; + -+ 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. ++ q = kmalloc(struct_size(q, entries, count), GFP_KERNEL); ++ if (!q) ++ return -ENOMEM; ++ q->task = current; ++ q->owner = args->owner; ++ atomic_set(&q->signaled, -1); ++ q->count = count; + -+ 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. ++ for (i = 0; i < count; i++) { ++ struct ntsync_q_entry *entry = &q->entries[i]; ++ struct ntsync_obj *obj = get_obj(dev, fds[i]); + -+ If an abandoned 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 -+ abandoned, and ``index`` is still set to the index of the mutex. ++ if (!obj) ++ goto err; + -+ 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. ++ entry->obj = obj; ++ entry->q = q; ++ entry->index = i; ++ } + -+ 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. ++ *ret_q = q; ++ return 0; + -+ The function may fail with ``EINTR`` if a signal is received. ++err: ++ for (j = 0; j < i; j++) ++ put_obj(q->entries[j].obj); ++ kfree(q); ++ return -EINVAL; ++} + -+.. c:macro:: NTSYNC_IOC_WAIT_ALL ++static void try_wake_any_obj(struct ntsync_obj *obj) ++{ ++ switch (obj->type) { ++ case NTSYNC_TYPE_SEM: ++ try_wake_any_sem(obj); ++ break; ++ } ++} + -+ 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. ++static int ntsync_wait_any(struct ntsync_device *dev, void __user *argp) ++{ ++ struct ntsync_wait_args args; ++ struct ntsync_q *q; ++ int signaled; ++ __u32 i; ++ int ret; + -+ 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. ++ if (copy_from_user(&args, argp, sizeof(args))) ++ return -EFAULT; + -+ 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. ++ ret = setup_wait(dev, &args, &q); ++ if (ret < 0) ++ return ret; + -+ If an abandoned 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 -+ abandoned. ++ /* queue ourselves */ + -+ 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. ++ for (i = 0; i < args.count; i++) { ++ struct ntsync_q_entry *entry = &q->entries[i]; ++ struct ntsync_obj *obj = entry->obj; + -+ 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``. -diff --git a/MAINTAINERS b/MAINTAINERS -index ec0284125e8f..2f4be32edc17 100644 ---- a/MAINTAINERS -+++ b/MAINTAINERS -@@ -15720,6 +15720,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 -+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/ ++ spin_lock(&obj->lock); ++ list_add_tail(&entry->node, &obj->any_waiters); ++ spin_unlock(&obj->lock); ++ } + - NUBUS SUBSYSTEM - M: Finn Thain - L: linux-m68k@lists.linux-m68k.org -diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig -index 4fb291f0bf7c..bdd8a71bd853 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. ++ /* check if we are already signaled */ + -+ To compile this driver as a module, choose M here: the -+ module will be called ntsync. ++ for (i = 0; i < args.count; i++) { ++ struct ntsync_obj *obj = q->entries[i].obj; + - If unsure, say N. - - config VCPU_STALL_DETECTOR -diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile -index ea6ea5bbbc9c..153a3f4837e8 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..496704268603 ---- /dev/null -+++ b/drivers/misc/ntsync.c -@@ -0,0 +1,1146 @@ -+// SPDX-License-Identifier: GPL-2.0-only -+/* -+ * ntsync.c - Kernel driver for NT synchronization primitives -+ * -+ * Copyright (C) 2024 Elizabeth Figura -+ */ ++ if (atomic_read(&q->signaled) != -1) ++ break; + -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include ++ spin_lock(&obj->lock); ++ try_wake_any_obj(obj); ++ spin_unlock(&obj->lock); ++ } + -+#define NTSYNC_NAME "ntsync" ++ /* sleep */ + -+enum ntsync_type { -+ NTSYNC_TYPE_SEM, -+ NTSYNC_TYPE_MUTEX, -+ NTSYNC_TYPE_EVENT, -+}; ++ ret = ntsync_schedule(q, &args); + -+struct ntsync_obj { -+ spinlock_t lock; ++ /* and finally, unqueue */ + -+ enum ntsync_type type; ++ for (i = 0; i < args.count; i++) { ++ struct ntsync_q_entry *entry = &q->entries[i]; ++ struct ntsync_obj *obj = entry->obj; + -+ struct file *file; -+ struct ntsync_device *dev; ++ spin_lock(&obj->lock); ++ list_del(&entry->node); ++ spin_unlock(&obj->lock); + -+ /* The following fields are protected by the object lock. */ -+ union { -+ struct { -+ __u32 count; -+ __u32 max; -+ } sem; -+ struct { -+ __u32 count; -+ __u32 owner; -+ bool ownerdead; -+ } mutex; -+ struct { -+ bool manual; -+ bool signaled; -+ } event; -+ } u; ++ 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 int ntsync_char_open(struct inode *inode, struct file *file) + { + struct ntsync_device *dev; +@@ -222,6 +470,8 @@ static long ntsync_char_ioctl(struct file *file, unsigned int cmd, + switch (cmd) { + case NTSYNC_IOC_CREATE_SEM: + return ntsync_create_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 dcfa38fdc93c..60ad414b5552 100644 +--- a/include/uapi/linux/ntsync.h ++++ b/include/uapi/linux/ntsync.h +@@ -16,7 +16,23 @@ struct ntsync_sem_args { + __u32 max; + }; + ++#define NTSYNC_WAIT_REALTIME 0x1 ++ ++struct ntsync_wait_args { ++ __u64 timeout; ++ __u64 objs; ++ __u32 count; ++ __u32 owner; ++ __u32 index; ++ __u32 flags; ++ __u32 pad; ++ __u32 pad2; ++}; ++ ++#define NTSYNC_MAX_WAIT_COUNT 64 + + #define NTSYNC_IOC_CREATE_SEM _IOWR('N', 0x80, struct ntsync_sem_args) ++#define NTSYNC_IOC_WAIT_ANY _IOWR('N', 0x82, struct ntsync_wait_args) + + #define NTSYNC_IOC_SEM_POST _IOWR('N', 0x81, __u32) + +-- +2.44.0 + + +From ea8c5bd7439c5a37080ecb7ae6cbf9da54da2db9 Mon Sep 17 00:00:00 2001 +From: Elizabeth Figura +Date: Mon, 29 Jan 2024 17:23:07 -0600 +Subject: [PATCH 05/30] ntsync: Introduce NTSYNC_IOC_WAIT_ALL. + +This is similar to NTSYNC_IOC_WAIT_ANY, but waits until all of the objects are +simultaneously signaled, and then acquires all of them as a single atomic +operation. +--- + drivers/misc/ntsync.c | 243 ++++++++++++++++++++++++++++++++++-- + include/uapi/linux/ntsync.h | 1 + + 2 files changed, 236 insertions(+), 8 deletions(-) + +diff --git a/drivers/misc/ntsync.c b/drivers/misc/ntsync.c +index c6f84a5fc8c0..e914d626465a 100644 +--- a/drivers/misc/ntsync.c ++++ b/drivers/misc/ntsync.c +@@ -55,7 +55,34 @@ struct ntsync_obj { + } sem; + } u; + + /* + * 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 any_waiters; + struct list_head all_waiters; + + /* @@ -596,33 +945,19 @@ index 000000000000..496704268603 + * limited here by PID_MAX_LIMIT, so there's no risk of overflow. + */ + atomic_t all_hint; -+}; -+ -+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; -+ + }; + + struct ntsync_q_entry { +@@ -76,14 +103,100 @@ struct ntsync_q { + */ + atomic_t signaled; + + bool all; -+ bool ownerdead; -+ __u32 count; -+ struct ntsync_q_entry entries[]; -+}; -+ -+struct ntsync_device { + __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. @@ -636,9 +971,9 @@ index 000000000000..496704268603 + */ + spinlock_t wait_all_lock; + -+ struct file *file; -+}; -+ + struct file *file; + }; + +static bool is_signaled(struct ntsync_obj *obj, __u32 owner) +{ + lockdep_assert_held(&obj->lock); @@ -646,12 +981,6 @@ index 000000000000..496704268603 + 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; -+ case NTSYNC_TYPE_EVENT: -+ return obj->u.event.signaled; + } + + WARN(1, "bad object type %#x\n", obj->type); @@ -668,6 +997,7 @@ index 000000000000..496704268603 +{ + __u32 count = q->count; + bool can_wake = true; ++ int signaled = -1; + __u32 i; + + lockdep_assert_held(&dev->wait_all_lock); @@ -686,7 +1016,7 @@ index 000000000000..496704268603 + } + } + -+ if (can_wake && atomic_cmpxchg(&q->signaled, -1, 0) == -1) { ++ if (can_wake && atomic_try_cmpxchg(&q->signaled, &signaled, 0)) { + for (i = 0; i < count; i++) { + struct ntsync_obj *obj = q->entries[i].obj; + @@ -694,17 +1024,6 @@ index 000000000000..496704268603 + case NTSYNC_TYPE_SEM: + 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; -+ case NTSYNC_TYPE_EVENT: -+ if (!obj->u.event.manual) -+ obj->u.event.signaled = false; -+ break; + } + } + wake_up_process(q->task); @@ -727,25 +1046,297 @@ index 000000000000..496704268603 + try_wake_all(dev, entry->q, obj); +} + -+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; + static void try_wake_any_sem(struct ntsync_obj *sem) + { + struct ntsync_q_entry *entry; +@@ -124,6 +237,7 @@ static int post_sem_state(struct ntsync_obj *sem, __u32 count) + + static int ntsync_sem_post(struct ntsync_obj *sem, void __user *argp) + { ++ struct ntsync_device *dev = sem->dev; + __u32 __user *user_args = argp; + __u32 prev_count; + __u32 args; +@@ -135,14 +249,29 @@ static int ntsync_sem_post(struct ntsync_obj *sem, void __user *argp) + if (sem->type != NTSYNC_TYPE_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); + -+ if (atomic_cmpxchg(&q->signaled, -1, entry->index) == -1) { -+ sem->u.sem.count--; -+ wake_up_process(q->task); ++ prev_count = sem->u.sem.count; ++ ret = post_sem_state(sem, args); ++ if (!ret) { ++ try_wake_all_obj(dev, sem); ++ try_wake_any_sem(sem); ++ } + +- prev_count = sem->u.sem.count; +- ret = post_sem_state(sem, args); +- 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 = post_sem_state(sem, args); ++ if (!ret) ++ try_wake_any_sem(sem); ++ ++ spin_unlock(&sem->lock); ++ } + + if (!ret && put_user(prev_count, user_args)) + ret = -EFAULT; +@@ -195,6 +324,8 @@ static struct ntsync_obj *ntsync_alloc_obj(struct ntsync_device *dev, + get_file(dev->file); + spin_lock_init(&obj->lock); + INIT_LIST_HEAD(&obj->any_waiters); ++ INIT_LIST_HEAD(&obj->all_waiters); ++ atomic_set(&obj->all_hint, 0); + + return obj; + } +@@ -306,7 +437,7 @@ static int ntsync_schedule(const struct ntsync_q *q, const struct ntsync_wait_ar + * Allocate and initialize the ntsync_q structure, but do not queue us yet. + */ + static int setup_wait(struct ntsync_device *dev, +- const struct ntsync_wait_args *args, ++ const struct ntsync_wait_args *args, bool all, + struct ntsync_q **ret_q) + { + const __u32 count = args->count; +@@ -333,6 +464,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++) { +@@ -342,6 +474,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; +@@ -377,7 +519,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, &q); ++ ret = setup_wait(dev, &args, false, &q); + if (ret < 0) + return ret; + +@@ -439,6 +581,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; ++ int signaled; ++ __u32 i; ++ int ret; ++ ++ if (copy_from_user(&args, argp, sizeof(args))) ++ return -EFAULT; ++ ++ ret = setup_wait(dev, &args, true, &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 obj->lock ++ * 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); ++ ++ /* 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 int ntsync_char_open(struct inode *inode, struct file *file) + { + struct ntsync_device *dev; +@@ -447,6 +670,8 @@ static int ntsync_char_open(struct inode *inode, struct file *file) + if (!dev) + return -ENOMEM; + ++ spin_lock_init(&dev->wait_all_lock); ++ + file->private_data = dev; + dev->file = file; + return nonseekable_open(inode, file); +@@ -470,6 +695,8 @@ static long ntsync_char_ioctl(struct file *file, unsigned int cmd, + switch (cmd) { + case NTSYNC_IOC_CREATE_SEM: + return ntsync_create_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 60ad414b5552..83784d4438a1 100644 +--- a/include/uapi/linux/ntsync.h ++++ b/include/uapi/linux/ntsync.h +@@ -33,6 +33,7 @@ struct ntsync_wait_args { + + #define NTSYNC_IOC_CREATE_SEM _IOWR('N', 0x80, struct ntsync_sem_args) + #define NTSYNC_IOC_WAIT_ANY _IOWR('N', 0x82, struct ntsync_wait_args) ++#define NTSYNC_IOC_WAIT_ALL _IOWR('N', 0x83, struct ntsync_wait_args) + + #define NTSYNC_IOC_SEM_POST _IOWR('N', 0x81, __u32) + +-- +2.44.0 + + +From 2590ddb09493782883513b72632dd194e54c6f6f Mon Sep 17 00:00:00 2001 +From: Elizabeth Figura +Date: Fri, 5 Mar 2021 11:41:10 -0600 +Subject: [PATCH 06/30] ntsync: Introduce NTSYNC_IOC_CREATE_MUTEX. + +This corresponds to the NT syscall NtCreateMutant(). + +An NT mutex is recursive, with a 32-bit recursion counter. When acquired via +NtWaitForMultipleObjects(), the recursion counter is incremented by one. + +The OS records the thread which acquired it. However, in order to keep this +driver self-contained, the owning thread ID is managed by user-space, and passed +as a parameter to all relevant ioctls. + +The initial owner and recursion count, if any, are specified when the mutex is +created. +--- + drivers/misc/ntsync.c | 68 +++++++++++++++++++++++++++++++++++++ + include/uapi/linux/ntsync.h | 7 ++++ + 2 files changed, 75 insertions(+) + +diff --git a/drivers/misc/ntsync.c b/drivers/misc/ntsync.c +index e914d626465a..173513aeeacc 100644 +--- a/drivers/misc/ntsync.c ++++ b/drivers/misc/ntsync.c +@@ -24,6 +24,7 @@ + + enum ntsync_type { + NTSYNC_TYPE_SEM, ++ NTSYNC_TYPE_MUTEX, + }; + + /* +@@ -53,6 +54,10 @@ struct ntsync_obj { + __u32 count; + __u32 max; + } sem; ++ struct { ++ __u32 count; ++ __u32 owner; ++ } mutex; + } u; + + /* +@@ -132,6 +137,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); +@@ -175,6 +184,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); +@@ -217,6 +230,29 @@ 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; @@ -754,16 +1345,14 @@ index 000000000000..496704268603 + + list_for_each_entry(entry, &mutex->any_waiters, node) { + struct ntsync_q *q = entry->q; ++ int signaled = -1; + + 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) { -+ if (mutex->u.mutex.ownerdead) -+ q->ownerdead = true; -+ mutex->u.mutex.ownerdead = false; ++ if (atomic_try_cmpxchg(&q->signaled, &signaled, entry->index)) { + mutex->u.mutex.count++; + mutex->u.mutex.owner = q->owner; + wake_up_process(q->task); @@ -771,118 +1360,144 @@ index 000000000000..496704268603 + } +} + -+static void try_wake_any_event(struct ntsync_obj *event) + /* + * Actually change the semaphore state, returning -EOVERFLOW if it is made + * invalid. +@@ -376,6 +412,33 @@ static int ntsync_create_sem(struct ntsync_device *dev, void __user *argp) + return put_user(fd, &user_args->sem); + } + ++static int ntsync_create_mutex(struct ntsync_device *dev, void __user *argp) +{ -+ struct ntsync_q_entry *entry; -+ -+ lockdep_assert_held(&event->lock); ++ struct ntsync_mutex_args __user *user_args = argp; ++ struct ntsync_mutex_args args; ++ struct ntsync_obj *mutex; ++ int fd; + -+ list_for_each_entry(entry, &event->any_waiters, node) { -+ struct ntsync_q *q = entry->q; ++ if (copy_from_user(&args, argp, sizeof(args))) ++ return -EFAULT; + -+ if (!event->u.event.signaled) -+ break; ++ if (!args.owner != !args.count) ++ return -EINVAL; + -+ if (atomic_cmpxchg(&q->signaled, -1, entry->index) == -1) { -+ if (!event->u.event.manual) -+ event->u.event.signaled = false; -+ wake_up_process(q->task); -+ } ++ mutex = ntsync_alloc_obj(dev, NTSYNC_TYPE_MUTEX); ++ if (!mutex) ++ return -ENOMEM; ++ mutex->u.mutex.count = args.count; ++ mutex->u.mutex.owner = args.owner; ++ fd = ntsync_obj_get_fd(mutex); ++ if (fd < 0) { ++ kfree(mutex); ++ return fd; + } ++ ++ return put_user(fd, &user_args->mutex); +} + + static struct ntsync_obj *get_obj(struct ntsync_device *dev, int fd) + { + struct file *file = fget(fd); +@@ -505,6 +568,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; + } + } + +@@ -693,6 +759,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_WAIT_ALL: +diff --git a/include/uapi/linux/ntsync.h b/include/uapi/linux/ntsync.h +index 83784d4438a1..cd7841cdba49 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; ++}; ++ + #define NTSYNC_WAIT_REALTIME 0x1 + + struct ntsync_wait_args { +@@ -34,6 +40,7 @@ struct ntsync_wait_args { + #define NTSYNC_IOC_CREATE_SEM _IOWR('N', 0x80, struct ntsync_sem_args) + #define NTSYNC_IOC_WAIT_ANY _IOWR('N', 0x82, struct ntsync_wait_args) + #define NTSYNC_IOC_WAIT_ALL _IOWR('N', 0x83, struct ntsync_wait_args) ++#define NTSYNC_IOC_CREATE_MUTEX _IOWR('N', 0x84, struct ntsync_sem_args) + + #define NTSYNC_IOC_SEM_POST _IOWR('N', 0x81, __u32) + +-- +2.44.0 + + +From 55bf5867d4e02ec5d709341e434b5790312eddfc Mon Sep 17 00:00:00 2001 +From: Elizabeth Figura +Date: Mon, 29 Jan 2024 17:51:17 -0600 +Subject: [PATCH 07/30] ntsync: Introduce NTSYNC_IOC_MUTEX_UNLOCK. + +This corresponds to the NT syscall NtReleaseMutant(). + +This syscall decrements the mutex's recursion count by one, and returns the +previous value. If the mutex is not owned by the given owner ID, the function +instead fails and returns -EPERM. +--- + drivers/misc/ntsync.c | 64 +++++++++++++++++++++++++++++++++++++ + include/uapi/linux/ntsync.h | 1 + + 2 files changed, 65 insertions(+) + +diff --git a/drivers/misc/ntsync.c b/drivers/misc/ntsync.c +index 173513aeeacc..f7911ef78d5b 100644 +--- a/drivers/misc/ntsync.c ++++ b/drivers/misc/ntsync.c +@@ -315,6 +315,68 @@ static int ntsync_sem_post(struct ntsync_obj *sem, void __user *argp) + return ret; + } + +/* -+ * Actually change the semaphore state, returning -EOVERFLOW if it is made -+ * invalid. ++ * Actually change the mutex state, returning -EPERM if not the owner. + */ -+static int post_sem_state(struct ntsync_obj *sem, __u32 count) ++static int unlock_mutex_state(struct ntsync_obj *mutex, ++ const struct ntsync_mutex_args *args) +{ -+ __u32 sum; -+ -+ lockdep_assert_held(&sem->lock); ++ lockdep_assert_held(&mutex->lock); + -+ if (check_add_overflow(sem->u.sem.count, count, &sum) || -+ sum > sem->u.sem.max) -+ return -EOVERFLOW; ++ if (mutex->u.mutex.owner != args->owner) ++ return -EPERM; + -+ sem->u.sem.count = sum; ++ if (!--mutex->u.mutex.count) ++ mutex->u.mutex.owner = 0; + return 0; +} + -+static int ntsync_sem_post(struct ntsync_obj *sem, void __user *argp) ++static int ntsync_mutex_unlock(struct ntsync_obj *mutex, void __user *argp) +{ -+ struct ntsync_device *dev = sem->dev; -+ __u32 __user *user_args = argp; ++ struct ntsync_mutex_args __user *user_args = argp; ++ struct ntsync_device *dev = mutex->dev; ++ struct ntsync_mutex_args args; + __u32 prev_count; -+ __u32 args; + int ret; + + if (copy_from_user(&args, argp, sizeof(args))) + return -EFAULT; ++ if (!args.owner) ++ return -EINVAL; + -+ if (sem->type != NTSYNC_TYPE_SEM) -+ return -EINVAL; -+ -+ 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 = post_sem_state(sem, args); -+ if (!ret) { -+ try_wake_all_obj(dev, sem); -+ try_wake_any_sem(sem); -+ } -+ -+ spin_unlock(&sem->lock); -+ spin_unlock(&dev->wait_all_lock); -+ } else { -+ spin_lock(&sem->lock); -+ -+ prev_count = sem->u.sem.count; -+ ret = post_sem_state(sem, args); -+ if (!ret) -+ try_wake_any_sem(sem); -+ -+ spin_unlock(&sem->lock); -+ } -+ -+ if (!ret && put_user(prev_count, user_args)) -+ ret = -EFAULT; -+ -+ return ret; -+} -+ -+/* -+ * Actually change the mutex state, returning -EPERM if not the owner. -+ */ -+static int unlock_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_mutex_unlock(struct ntsync_obj *mutex, void __user *argp) -+{ -+ struct ntsync_mutex_args __user *user_args = argp; -+ struct ntsync_device *dev = mutex->dev; -+ struct ntsync_mutex_args args; -+ __u32 prev_count; -+ int ret; -+ -+ if (copy_from_user(&args, argp, sizeof(args))) -+ return -EFAULT; -+ if (!args.owner) -+ return -EINVAL; -+ -+ if (mutex->type != NTSYNC_TYPE_MUTEX) ++ if (mutex->type != NTSYNC_TYPE_MUTEX) + return -EINVAL; + + if (atomic_read(&mutex->all_hint) > 0) { @@ -915,6 +1530,95 @@ index 000000000000..496704268603 + return ret; +} + + static int ntsync_obj_release(struct inode *inode, struct file *file) + { + struct ntsync_obj *obj = file->private_data; +@@ -334,6 +396,8 @@ static long ntsync_obj_ioctl(struct file *file, unsigned int cmd, + switch (cmd) { + case NTSYNC_IOC_SEM_POST: + return ntsync_sem_post(obj, argp); ++ case NTSYNC_IOC_MUTEX_UNLOCK: ++ return ntsync_mutex_unlock(obj, argp); + default: + return -ENOIOCTLCMD; + } +diff --git a/include/uapi/linux/ntsync.h b/include/uapi/linux/ntsync.h +index cd7841cdba49..fa2c9f638d77 100644 +--- a/include/uapi/linux/ntsync.h ++++ b/include/uapi/linux/ntsync.h +@@ -43,5 +43,6 @@ struct ntsync_wait_args { + #define NTSYNC_IOC_CREATE_MUTEX _IOWR('N', 0x84, struct ntsync_sem_args) + + #define NTSYNC_IOC_SEM_POST _IOWR('N', 0x81, __u32) ++#define NTSYNC_IOC_MUTEX_UNLOCK _IOWR('N', 0x85, struct ntsync_mutex_args) + + #endif +-- +2.44.0 + + +From 85dac70b1aba269a28370ae206aa1943d3107c90 Mon Sep 17 00:00:00 2001 +From: Elizabeth Figura +Date: Mon, 29 Jan 2024 18:03:48 -0600 +Subject: [PATCH 08/30] ntsync: Introduce NTSYNC_IOC_MUTEX_KILL. + +This does not correspond to any NT syscall. Rather, when a thread dies, it +should be called by the NT emulator for each mutex. + +NT mutexes are robust (in the pthread sense). When an NT thread dies, any +mutexes it owned are immediately released. Acquisition of those mutexes by other +threads will return a special value indicating that the mutex was abandoned, +like EOWNERDEAD returned from pthread_mutex_lock(), and EOWNERDEAD is indeed +used here for that purpose. +--- + drivers/misc/ntsync.c | 71 +++++++++++++++++++++++++++++++++++-- + include/uapi/linux/ntsync.h | 1 + + 2 files changed, 70 insertions(+), 2 deletions(-) + +diff --git a/drivers/misc/ntsync.c b/drivers/misc/ntsync.c +index f7911ef78d5b..1e68f96bc2a6 100644 +--- a/drivers/misc/ntsync.c ++++ b/drivers/misc/ntsync.c +@@ -57,6 +57,7 @@ struct ntsync_obj { + struct { + __u32 count; + __u32 owner; ++ bool ownerdead; + } mutex; + } u; + +@@ -109,6 +110,7 @@ struct ntsync_q { + atomic_t signaled; + + bool all; ++ bool ownerdead; + __u32 count; + struct ntsync_q_entry entries[]; + }; +@@ -185,6 +187,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; +@@ -246,6 +251,9 @@ static void try_wake_any_mutex(struct ntsync_obj *mutex) + continue; + + if (atomic_try_cmpxchg(&q->signaled, &signaled, entry->index)) { ++ 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); +@@ -377,6 +385,62 @@ static int ntsync_mutex_unlock(struct ntsync_obj *mutex, void __user *argp) + return ret; + } + +/* + * Actually change the mutex state to mark its owner as dead, + * returning -EPERM if not the owner. @@ -971,7 +1675,252 @@ index 000000000000..496704268603 + return ret; +} + -+static int ntsync_event_set(struct ntsync_obj *event, void __user *argp, bool pulse) + static int ntsync_obj_release(struct inode *inode, struct file *file) + { + struct ntsync_obj *obj = file->private_data; +@@ -398,6 +462,8 @@ static long ntsync_obj_ioctl(struct file *file, unsigned int cmd, + return ntsync_sem_post(obj, argp); + case NTSYNC_IOC_MUTEX_UNLOCK: + return ntsync_mutex_unlock(obj, argp); ++ case NTSYNC_IOC_MUTEX_KILL: ++ return ntsync_mutex_kill(obj, argp); + default: + return -ENOIOCTLCMD; + } +@@ -592,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++) { +@@ -699,7 +766,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; +@@ -780,7 +847,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; +diff --git a/include/uapi/linux/ntsync.h b/include/uapi/linux/ntsync.h +index fa2c9f638d77..1bff8f19d6d9 100644 +--- a/include/uapi/linux/ntsync.h ++++ b/include/uapi/linux/ntsync.h +@@ -44,5 +44,6 @@ struct ntsync_wait_args { + + #define NTSYNC_IOC_SEM_POST _IOWR('N', 0x81, __u32) + #define NTSYNC_IOC_MUTEX_UNLOCK _IOWR('N', 0x85, struct ntsync_mutex_args) ++#define NTSYNC_IOC_MUTEX_KILL _IOW ('N', 0x86, __u32) + + #endif +-- +2.44.0 + + +From 32f9f3771c86c83e2658bc6b0eaf204af28c2469 Mon Sep 17 00:00:00 2001 +From: Elizabeth Figura +Date: Mon, 29 Jan 2024 18:24:59 -0600 +Subject: [PATCH 09/30] ntsync: Introduce NTSYNC_IOC_CREATE_EVENT. + +This correspond to the NT syscall NtCreateEvent(). + +An NT event holds a single bit of state denoting whether it is signaled or +unsignaled. + +There are two types of events: manual-reset and automatic-reset. When an +automatic-reset event is acquired via a wait function, its state is reset to +unsignaled. Manual-reset events are not affected by wait functions. + +Whether the event is manual-reset, and its initial state, are specified at +creation time. +--- + drivers/misc/ntsync.c | 61 +++++++++++++++++++++++++++++++++++++ + include/uapi/linux/ntsync.h | 7 +++++ + 2 files changed, 68 insertions(+) + +diff --git a/drivers/misc/ntsync.c b/drivers/misc/ntsync.c +index 1e68f96bc2a6..3e125c805c00 100644 +--- a/drivers/misc/ntsync.c ++++ b/drivers/misc/ntsync.c +@@ -25,6 +25,7 @@ + enum ntsync_type { + NTSYNC_TYPE_SEM, + NTSYNC_TYPE_MUTEX, ++ NTSYNC_TYPE_EVENT, + }; + + /* +@@ -59,6 +60,10 @@ struct ntsync_obj { + __u32 owner; + bool ownerdead; + } mutex; ++ struct { ++ bool manual; ++ bool signaled; ++ } event; + } u; + + /* +@@ -143,6 +148,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); +@@ -193,6 +200,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); +@@ -261,6 +272,27 @@ 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; ++ int signaled = -1; ++ ++ if (!event->u.event.signaled) ++ break; ++ ++ if (atomic_try_cmpxchg(&q->signaled, &signaled, entry->index)) { ++ if (!event->u.event.manual) ++ event->u.event.signaled = false; ++ wake_up_process(q->task); ++ } ++ } ++} ++ + /* + * Actually change the semaphore state, returning -EOVERFLOW if it is made + * invalid. +@@ -569,6 +601,30 @@ static int ntsync_create_mutex(struct ntsync_device *dev, void __user *argp) + return put_user(fd, &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; ++ int fd; ++ ++ if (copy_from_user(&args, argp, sizeof(args))) ++ return -EFAULT; ++ ++ event = ntsync_alloc_obj(dev, NTSYNC_TYPE_EVENT); ++ if (!event) ++ return -ENOMEM; ++ event->u.event.manual = args.manual; ++ event->u.event.signaled = args.signaled; ++ fd = ntsync_obj_get_fd(event); ++ if (fd < 0) { ++ kfree(event); ++ return fd; ++ } ++ ++ return put_user(fd, &user_args->event); ++} ++ + static struct ntsync_obj *get_obj(struct ntsync_device *dev, int fd) + { + struct file *file = fget(fd); +@@ -702,6 +758,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; + } + } + +@@ -890,6 +949,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 1bff8f19d6d9..0d133f2eaf0b 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; ++}; ++ + #define NTSYNC_WAIT_REALTIME 0x1 + + struct ntsync_wait_args { +@@ -41,6 +47,7 @@ struct ntsync_wait_args { + #define NTSYNC_IOC_WAIT_ANY _IOWR('N', 0x82, struct ntsync_wait_args) + #define NTSYNC_IOC_WAIT_ALL _IOWR('N', 0x83, struct ntsync_wait_args) + #define NTSYNC_IOC_CREATE_MUTEX _IOWR('N', 0x84, struct ntsync_sem_args) ++#define NTSYNC_IOC_CREATE_EVENT _IOWR('N', 0x87, struct ntsync_event_args) + + #define NTSYNC_IOC_SEM_POST _IOWR('N', 0x81, __u32) + #define NTSYNC_IOC_MUTEX_UNLOCK _IOWR('N', 0x85, struct ntsync_mutex_args) +-- +2.44.0 + + +From 1e59393a2eb34129f4d3da69c09fd4af08b7bbb9 Mon Sep 17 00:00:00 2001 +From: Elizabeth Figura +Date: Mon, 29 Jan 2024 18:31:46 -0600 +Subject: [PATCH 10/30] ntsync: Introduce NTSYNC_IOC_EVENT_SET. + +This corresponds to the NT syscall NtSetEvent(). + +This sets the event to the signaled state, and returns its previous state. +--- + drivers/misc/ntsync.c | 37 +++++++++++++++++++++++++++++++++++++ + include/uapi/linux/ntsync.h | 1 + + 2 files changed, 38 insertions(+) + +diff --git a/drivers/misc/ntsync.c b/drivers/misc/ntsync.c +index 3e125c805c00..69f359241cf6 100644 +--- a/drivers/misc/ntsync.c ++++ b/drivers/misc/ntsync.c +@@ -473,6 +473,41 @@ static int ntsync_mutex_kill(struct ntsync_obj *mutex, void __user *argp) + return ret; + } + ++static int ntsync_event_set(struct ntsync_obj *event, void __user *argp) +{ + struct ntsync_device *dev = event->dev; + __u32 prev_state; @@ -987,8 +1936,6 @@ index 000000000000..496704268603 + 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); @@ -998,8 +1945,6 @@ index 000000000000..496704268603 + 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); + } @@ -1010,6 +1955,54 @@ index 000000000000..496704268603 + return 0; +} + + static int ntsync_obj_release(struct inode *inode, struct file *file) + { + struct ntsync_obj *obj = file->private_data; +@@ -496,6 +531,8 @@ static long ntsync_obj_ioctl(struct file *file, unsigned int cmd, + return ntsync_mutex_unlock(obj, argp); + case NTSYNC_IOC_MUTEX_KILL: + return ntsync_mutex_kill(obj, argp); ++ case NTSYNC_IOC_EVENT_SET: ++ return ntsync_event_set(obj, argp); + default: + return -ENOIOCTLCMD; + } +diff --git a/include/uapi/linux/ntsync.h b/include/uapi/linux/ntsync.h +index 0d133f2eaf0b..65329d15a472 100644 +--- a/include/uapi/linux/ntsync.h ++++ b/include/uapi/linux/ntsync.h +@@ -52,5 +52,6 @@ struct ntsync_wait_args { + #define NTSYNC_IOC_SEM_POST _IOWR('N', 0x81, __u32) + #define NTSYNC_IOC_MUTEX_UNLOCK _IOWR('N', 0x85, struct ntsync_mutex_args) + #define NTSYNC_IOC_MUTEX_KILL _IOW ('N', 0x86, __u32) ++#define NTSYNC_IOC_EVENT_SET _IOR ('N', 0x88, __u32) + + #endif +-- +2.44.0 + + +From 55cb3749e614f641b25d650b6a8f9ae146032bc0 Mon Sep 17 00:00:00 2001 +From: Elizabeth Figura +Date: Mon, 29 Jan 2024 18:33:45 -0600 +Subject: [PATCH 11/30] ntsync: Introduce NTSYNC_IOC_EVENT_RESET. + +This corresponds to the NT syscall NtResetEvent(). + +This sets the event to the unsignaled state, and returns its previous state. +--- + drivers/misc/ntsync.c | 22 ++++++++++++++++++++++ + include/uapi/linux/ntsync.h | 1 + + 2 files changed, 23 insertions(+) + +diff --git a/drivers/misc/ntsync.c b/drivers/misc/ntsync.c +index 69f359241cf6..ae78425c87d1 100644 +--- a/drivers/misc/ntsync.c ++++ b/drivers/misc/ntsync.c +@@ -508,6 +508,26 @@ static int ntsync_event_set(struct ntsync_obj *event, void __user *argp) + return 0; + } + +static int ntsync_event_reset(struct ntsync_obj *event, void __user *argp) +{ + __u32 prev_state; @@ -1030,39 +2023,210 @@ index 000000000000..496704268603 + return 0; +} + -+static int ntsync_sem_read(struct ntsync_obj *sem, void __user *argp) -+{ -+ struct ntsync_sem_args __user *user_args = argp; -+ struct ntsync_sem_args args; -+ -+ if (sem->type != NTSYNC_TYPE_SEM) -+ return -EINVAL; -+ -+ args.sem = 0; -+ spin_lock(&sem->lock); -+ args.count = sem->u.sem.count; -+ args.max = sem->u.sem.max; -+ spin_unlock(&sem->lock); -+ -+ if (copy_to_user(user_args, &args, sizeof(args))) -+ return -EFAULT; -+ return 0; -+} -+ -+static int ntsync_mutex_read(struct ntsync_obj *mutex, void __user *argp) -+{ -+ struct ntsync_mutex_args __user *user_args = argp; -+ struct ntsync_mutex_args args; -+ int ret; -+ -+ if (mutex->type != NTSYNC_TYPE_MUTEX) -+ return -EINVAL; -+ -+ args.mutex = 0; -+ spin_lock(&mutex->lock); -+ args.count = mutex->u.mutex.count; -+ args.owner = mutex->u.mutex.owner; -+ ret = mutex->u.mutex.ownerdead ? -EOWNERDEAD : 0; + static int ntsync_obj_release(struct inode *inode, struct file *file) + { + struct ntsync_obj *obj = file->private_data; +@@ -533,6 +553,8 @@ static long ntsync_obj_ioctl(struct file *file, unsigned int cmd, + return ntsync_mutex_kill(obj, argp); + case NTSYNC_IOC_EVENT_SET: + return ntsync_event_set(obj, argp); ++ case NTSYNC_IOC_EVENT_RESET: ++ return ntsync_event_reset(obj, argp); + default: + return -ENOIOCTLCMD; + } +diff --git a/include/uapi/linux/ntsync.h b/include/uapi/linux/ntsync.h +index 65329d15a472..657542107328 100644 +--- a/include/uapi/linux/ntsync.h ++++ b/include/uapi/linux/ntsync.h +@@ -53,5 +53,6 @@ struct ntsync_wait_args { + #define NTSYNC_IOC_MUTEX_UNLOCK _IOWR('N', 0x85, struct ntsync_mutex_args) + #define NTSYNC_IOC_MUTEX_KILL _IOW ('N', 0x86, __u32) + #define NTSYNC_IOC_EVENT_SET _IOR ('N', 0x88, __u32) ++#define NTSYNC_IOC_EVENT_RESET _IOR ('N', 0x89, __u32) + + #endif +-- +2.44.0 + + +From 8883133dbfe8925630bb2bde5a3b9424ae633f6f Mon Sep 17 00:00:00 2001 +From: Elizabeth Figura +Date: Mon, 29 Jan 2024 18:36:30 -0600 +Subject: [PATCH 12/30] ntsync: Introduce NTSYNC_IOC_EVENT_PULSE. + +This corresponds to the NT syscall NtPulseEvent(). + +This wakes up any waiters as if the event had been set, but does not set the +event, instead resetting it if it had been signalled. Thus, for a manual-reset +event, all waiters are woken, whereas for an auto-reset event, at most one +waiter is woken. +--- + drivers/misc/ntsync.c | 10 ++++++++-- + include/uapi/linux/ntsync.h | 1 + + 2 files changed, 9 insertions(+), 2 deletions(-) + +diff --git a/drivers/misc/ntsync.c b/drivers/misc/ntsync.c +index ae78425c87d1..adba4657bf26 100644 +--- a/drivers/misc/ntsync.c ++++ b/drivers/misc/ntsync.c +@@ -473,7 +473,7 @@ static int ntsync_mutex_kill(struct ntsync_obj *mutex, void __user *argp) + return ret; + } + +-static int ntsync_event_set(struct ntsync_obj *event, void __user *argp) ++static int ntsync_event_set(struct ntsync_obj *event, void __user *argp, bool pulse) + { + struct ntsync_device *dev = event->dev; + __u32 prev_state; +@@ -489,6 +489,8 @@ static int ntsync_event_set(struct ntsync_obj *event, 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); +@@ -498,6 +500,8 @@ static int ntsync_event_set(struct ntsync_obj *event, 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); + } +@@ -552,9 +556,11 @@ static long ntsync_obj_ioctl(struct file *file, unsigned int cmd, + case NTSYNC_IOC_MUTEX_KILL: + return ntsync_mutex_kill(obj, argp); + case NTSYNC_IOC_EVENT_SET: +- return ntsync_event_set(obj, argp); ++ return ntsync_event_set(obj, argp, false); + case NTSYNC_IOC_EVENT_RESET: + return ntsync_event_reset(obj, argp); ++ case NTSYNC_IOC_EVENT_PULSE: ++ return ntsync_event_set(obj, argp, true); + default: + return -ENOIOCTLCMD; + } +diff --git a/include/uapi/linux/ntsync.h b/include/uapi/linux/ntsync.h +index 657542107328..57721f5d31ba 100644 +--- a/include/uapi/linux/ntsync.h ++++ b/include/uapi/linux/ntsync.h +@@ -54,5 +54,6 @@ struct ntsync_wait_args { + #define NTSYNC_IOC_MUTEX_KILL _IOW ('N', 0x86, __u32) + #define NTSYNC_IOC_EVENT_SET _IOR ('N', 0x88, __u32) + #define NTSYNC_IOC_EVENT_RESET _IOR ('N', 0x89, __u32) ++#define NTSYNC_IOC_EVENT_PULSE _IOR ('N', 0x8a, __u32) + + #endif +-- +2.44.0 + + +From 14b61e4201b7ac62cc3b3ffa7f5913f7b417a75a Mon Sep 17 00:00:00 2001 +From: Elizabeth Figura +Date: Mon, 29 Jan 2024 18:43:07 -0600 +Subject: [PATCH 13/30] ntsync: Introduce NTSYNC_IOC_SEM_READ. + +This corresponds to the NT syscall NtQuerySemaphore(). + +This returns the current count and maximum count of the semaphore. +--- + drivers/misc/ntsync.c | 21 +++++++++++++++++++++ + include/uapi/linux/ntsync.h | 1 + + 2 files changed, 22 insertions(+) + +diff --git a/drivers/misc/ntsync.c b/drivers/misc/ntsync.c +index adba4657bf26..961e8d241602 100644 +--- a/drivers/misc/ntsync.c ++++ b/drivers/misc/ntsync.c +@@ -532,6 +532,25 @@ static int ntsync_event_reset(struct ntsync_obj *event, void __user *argp) + return 0; + } + ++static int ntsync_sem_read(struct ntsync_obj *sem, void __user *argp) ++{ ++ struct ntsync_sem_args __user *user_args = argp; ++ struct ntsync_sem_args args; ++ ++ if (sem->type != NTSYNC_TYPE_SEM) ++ return -EINVAL; ++ ++ args.sem = 0; ++ spin_lock(&sem->lock); ++ args.count = sem->u.sem.count; ++ args.max = sem->u.sem.max; ++ spin_unlock(&sem->lock); ++ ++ if (copy_to_user(user_args, &args, sizeof(args))) ++ return -EFAULT; ++ return 0; ++} ++ + static int ntsync_obj_release(struct inode *inode, struct file *file) + { + struct ntsync_obj *obj = file->private_data; +@@ -551,6 +570,8 @@ static long ntsync_obj_ioctl(struct file *file, unsigned int cmd, + switch (cmd) { + case NTSYNC_IOC_SEM_POST: + return ntsync_sem_post(obj, argp); ++ case NTSYNC_IOC_SEM_READ: ++ return ntsync_sem_read(obj, argp); + case NTSYNC_IOC_MUTEX_UNLOCK: + return ntsync_mutex_unlock(obj, argp); + case NTSYNC_IOC_MUTEX_KILL: +diff --git a/include/uapi/linux/ntsync.h b/include/uapi/linux/ntsync.h +index 57721f5d31ba..e298400bf25a 100644 +--- a/include/uapi/linux/ntsync.h ++++ b/include/uapi/linux/ntsync.h +@@ -55,5 +55,6 @@ struct ntsync_wait_args { + #define NTSYNC_IOC_EVENT_SET _IOR ('N', 0x88, __u32) + #define NTSYNC_IOC_EVENT_RESET _IOR ('N', 0x89, __u32) + #define NTSYNC_IOC_EVENT_PULSE _IOR ('N', 0x8a, __u32) ++#define NTSYNC_IOC_SEM_READ _IOR ('N', 0x8b, struct ntsync_sem_args) + + #endif +-- +2.44.0 + + +From 2d006df7b511fb15778471748ea6e17172917770 Mon Sep 17 00:00:00 2001 +From: Elizabeth Figura +Date: Tue, 30 Jan 2024 11:37:15 -0600 +Subject: [PATCH 14/30] ntsync: Introduce NTSYNC_IOC_MUTEX_READ. + +This corresponds to the NT syscall NtQueryMutant(). + +This returns the recursion count, owner, and abandoned state of the mutex. +--- + drivers/misc/ntsync.c | 23 +++++++++++++++++++++++ + include/uapi/linux/ntsync.h | 1 + + 2 files changed, 24 insertions(+) + +diff --git a/drivers/misc/ntsync.c b/drivers/misc/ntsync.c +index 961e8d241602..bd043dccc9fa 100644 +--- a/drivers/misc/ntsync.c ++++ b/drivers/misc/ntsync.c +@@ -551,6 +551,27 @@ static int ntsync_sem_read(struct ntsync_obj *sem, void __user *argp) + return 0; + } + ++static int ntsync_mutex_read(struct ntsync_obj *mutex, void __user *argp) ++{ ++ struct ntsync_mutex_args __user *user_args = argp; ++ struct ntsync_mutex_args args; ++ int ret; ++ ++ if (mutex->type != NTSYNC_TYPE_MUTEX) ++ return -EINVAL; ++ ++ args.mutex = 0; ++ 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); + + if (copy_to_user(user_args, &args, sizeof(args))) @@ -1070,6 +2234,54 @@ index 000000000000..496704268603 + return ret; +} + + static int ntsync_obj_release(struct inode *inode, struct file *file) + { + struct ntsync_obj *obj = file->private_data; +@@ -576,6 +597,8 @@ static long ntsync_obj_ioctl(struct file *file, unsigned int cmd, + return ntsync_mutex_unlock(obj, argp); + case NTSYNC_IOC_MUTEX_KILL: + return ntsync_mutex_kill(obj, argp); ++ case NTSYNC_IOC_MUTEX_READ: ++ return ntsync_mutex_read(obj, argp); + case NTSYNC_IOC_EVENT_SET: + return ntsync_event_set(obj, argp, false); + case NTSYNC_IOC_EVENT_RESET: +diff --git a/include/uapi/linux/ntsync.h b/include/uapi/linux/ntsync.h +index e298400bf25a..797e8df10a3a 100644 +--- a/include/uapi/linux/ntsync.h ++++ b/include/uapi/linux/ntsync.h +@@ -56,5 +56,6 @@ struct ntsync_wait_args { + #define NTSYNC_IOC_EVENT_RESET _IOR ('N', 0x89, __u32) + #define NTSYNC_IOC_EVENT_PULSE _IOR ('N', 0x8a, __u32) + #define NTSYNC_IOC_SEM_READ _IOR ('N', 0x8b, struct ntsync_sem_args) ++#define NTSYNC_IOC_MUTEX_READ _IOR ('N', 0x8c, struct ntsync_mutex_args) + + #endif +-- +2.44.0 + + +From 33b7ebabf49ba08089976f40dd4f80ff73aaaeb8 Mon Sep 17 00:00:00 2001 +From: Elizabeth Figura +Date: Tue, 30 Jan 2024 11:39:45 -0600 +Subject: [PATCH 15/30] ntsync: Introduce NTSYNC_IOC_EVENT_READ. + +This corresponds to the NT syscall NtQueryEvent(). + +This returns the signaled state of the event and whether it is manual-reset. +--- + drivers/misc/ntsync.c | 21 +++++++++++++++++++++ + include/uapi/linux/ntsync.h | 1 + + 2 files changed, 22 insertions(+) + +diff --git a/drivers/misc/ntsync.c b/drivers/misc/ntsync.c +index bd043dccc9fa..a03c6fceb518 100644 +--- a/drivers/misc/ntsync.c ++++ b/drivers/misc/ntsync.c +@@ -572,6 +572,25 @@ static int ntsync_mutex_read(struct ntsync_obj *mutex, void __user *argp) + return ret; + } + +static int ntsync_event_read(struct ntsync_obj *event, void __user *argp) +{ + struct ntsync_event_args __user *user_args = argp; @@ -1089,1074 +2301,1309 @@ index 000000000000..496704268603 + return 0; +} + -+static int ntsync_obj_release(struct inode *inode, struct file *file) -+{ -+ struct ntsync_obj *obj = file->private_data; -+ -+ fput(obj->dev->file); -+ kfree(obj); -+ -+ return 0; -+} -+ -+static long ntsync_obj_ioctl(struct file *file, unsigned int cmd, -+ unsigned long parm) -+{ -+ struct ntsync_obj *obj = file->private_data; -+ void __user *argp = (void __user *)parm; -+ -+ switch (cmd) { -+ case NTSYNC_IOC_SEM_POST: -+ return ntsync_sem_post(obj, argp); -+ case NTSYNC_IOC_SEM_READ: -+ return ntsync_sem_read(obj, argp); -+ case NTSYNC_IOC_MUTEX_UNLOCK: -+ return ntsync_mutex_unlock(obj, argp); -+ case NTSYNC_IOC_MUTEX_KILL: -+ return ntsync_mutex_kill(obj, argp); -+ case NTSYNC_IOC_MUTEX_READ: -+ return ntsync_mutex_read(obj, argp); -+ case NTSYNC_IOC_EVENT_SET: -+ return ntsync_event_set(obj, argp, false); -+ case NTSYNC_IOC_EVENT_RESET: -+ return ntsync_event_reset(obj, argp); -+ case NTSYNC_IOC_EVENT_PULSE: -+ return ntsync_event_set(obj, argp, true); + static int ntsync_obj_release(struct inode *inode, struct file *file) + { + struct ntsync_obj *obj = file->private_data; +@@ -605,6 +624,8 @@ static long ntsync_obj_ioctl(struct file *file, unsigned int cmd, + return ntsync_event_reset(obj, argp); + case NTSYNC_IOC_EVENT_PULSE: + return ntsync_event_set(obj, argp, true); + case NTSYNC_IOC_EVENT_READ: + return ntsync_event_read(obj, argp); -+ default: -+ return -ENOIOCTLCMD; -+ } -+} -+ -+static const struct file_operations ntsync_obj_fops = { -+ .owner = THIS_MODULE, -+ .release = ntsync_obj_release, -+ .unlocked_ioctl = ntsync_obj_ioctl, -+ .compat_ioctl = compat_ptr_ioctl, -+ .llseek = no_llseek, -+}; + default: + return -ENOIOCTLCMD; + } +diff --git a/include/uapi/linux/ntsync.h b/include/uapi/linux/ntsync.h +index 797e8df10a3a..80f36de46a75 100644 +--- a/include/uapi/linux/ntsync.h ++++ b/include/uapi/linux/ntsync.h +@@ -57,5 +57,6 @@ struct ntsync_wait_args { + #define NTSYNC_IOC_EVENT_PULSE _IOR ('N', 0x8a, __u32) + #define NTSYNC_IOC_SEM_READ _IOR ('N', 0x8b, struct ntsync_sem_args) + #define NTSYNC_IOC_MUTEX_READ _IOR ('N', 0x8c, struct ntsync_mutex_args) ++#define NTSYNC_IOC_EVENT_READ _IOR ('N', 0x8d, struct ntsync_event_args) + + #endif +-- +2.44.0 + + +From eb784afa4cfb57002d095775c136f35ad5ec88de Mon Sep 17 00:00:00 2001 +From: Elizabeth Figura +Date: Wed, 13 Apr 2022 20:02:39 -0500 +Subject: [PATCH 16/30] ntsync: Introduce alertable waits. + +NT waits can optionally be made "alertable". This is a special channel for +thread wakeup that is mildly similar to SIGIO. A thread has an internal single +bit of "alerted" state, and if a thread is alerted while an alertable wait, the +wait will return a special value, consume the "alerted" state, and will not +consume any of its objects. + +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 a03c6fceb518..19fd70ac3f50 100644 +--- a/drivers/misc/ntsync.c ++++ b/drivers/misc/ntsync.c +@@ -819,25 +819,32 @@ static int setup_wait(struct ntsync_device *dev, + const struct ntsync_wait_args *args, bool all, + struct ntsync_q **ret_q) + { ++ int fds[NTSYNC_MAX_WAIT_COUNT + 1]; + const __u32 count = args->count; +- int fds[NTSYNC_MAX_WAIT_COUNT]; + struct ntsync_q *q; ++ __u32 total_count; + __u32 i, j; + + if (!args->owner) + return -EINVAL; + +- if (args->pad || args->pad2 || (args->flags & ~NTSYNC_WAIT_REALTIME)) ++ if (args->pad || (args->flags & ~NTSYNC_WAIT_REALTIME)) + return -EINVAL; + + if (args->count > NTSYNC_MAX_WAIT_COUNT) + return -EINVAL; + ++ total_count = count; ++ if (args->alert) ++ total_count++; + -+static struct ntsync_obj *ntsync_alloc_obj(struct ntsync_device *dev, -+ enum ntsync_type type) -+{ -+ struct ntsync_obj *obj; + if (copy_from_user(fds, u64_to_user_ptr(args->objs), + array_size(count, sizeof(*fds)))) + return -EFAULT; ++ if (args->alert) ++ fds[count] = args->alert; + +- q = kmalloc(struct_size(q, entries, count), GFP_KERNEL); ++ q = kmalloc(struct_size(q, entries, total_count), GFP_KERNEL); + if (!q) + return -ENOMEM; + q->task = current; +@@ -847,7 +854,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, fds[i]); + +@@ -897,9 +904,9 @@ static void try_wake_any_obj(struct ntsync_obj *obj) + static int ntsync_wait_any(struct ntsync_device *dev, void __user *argp) + { + struct ntsync_wait_args args; ++ __u32 i, total_count; + struct ntsync_q *q; + int signaled; +- __u32 i; + int ret; + + if (copy_from_user(&args, argp, sizeof(args))) +@@ -909,9 +916,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++; + -+ obj = kzalloc(sizeof(*obj), GFP_KERNEL); -+ if (!obj) -+ return NULL; -+ obj->type = type; -+ obj->dev = dev; -+ get_file(dev->file); -+ spin_lock_init(&obj->lock); -+ INIT_LIST_HEAD(&obj->any_waiters); -+ INIT_LIST_HEAD(&obj->all_waiters); -+ atomic_set(&obj->all_hint, 0); + /* 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; + +@@ -920,9 +931,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) +@@ -939,7 +956,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; + +@@ -999,6 +1016,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; + -+ return 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 */ + +@@ -1006,6 +1031,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. ++ */ + -+static int ntsync_obj_get_fd(struct ntsync_obj *obj) -+{ -+ struct file *file; -+ int fd; ++ if (args.alert) { ++ struct ntsync_obj *obj = q->entries[args.count].obj; + -+ fd = get_unused_fd_flags(O_CLOEXEC); -+ if (fd < 0) -+ return fd; -+ file = anon_inode_getfile("ntsync", &ntsync_obj_fops, obj, O_RDWR); -+ if (IS_ERR(file)) { -+ put_unused_fd(fd); -+ return PTR_ERR(file); ++ if (atomic_read(&q->signaled) == -1) { ++ spin_lock(&obj->lock); ++ try_wake_any_obj(obj); ++ spin_unlock(&obj->lock); ++ } + } -+ obj->file = file; -+ fd_install(fd, file); + -+ return fd; -+} + /* sleep */ + + ret = ntsync_schedule(q, &args); +@@ -1028,6 +1068,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; + -+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; -+ int fd; ++ spin_lock_nest_lock(&obj->lock, &dev->wait_all_lock); ++ list_del(&entry->node); ++ spin_unlock(&obj->lock); + -+ if (copy_from_user(&args, argp, sizeof(args))) -+ return -EFAULT; ++ put_obj(obj); ++ } + + spin_unlock(&dev->wait_all_lock); + +diff --git a/include/uapi/linux/ntsync.h b/include/uapi/linux/ntsync.h +index 80f36de46a75..f21dbac42164 100644 +--- a/include/uapi/linux/ntsync.h ++++ b/include/uapi/linux/ntsync.h +@@ -37,8 +37,8 @@ struct ntsync_wait_args { + __u32 owner; + __u32 index; + __u32 flags; ++ __u32 alert; + __u32 pad; +- __u32 pad2; + }; + + #define NTSYNC_MAX_WAIT_COUNT 64 +-- +2.44.0 + + +From b25aea93d59f6742461ca86fc8a60a2393bb1704 Mon Sep 17 00:00:00 2001 +From: Elizabeth Figura +Date: Fri, 5 Mar 2021 12:06:23 -0600 +Subject: [PATCH 17/30] selftests: ntsync: Add some tests for semaphore state. + +Wine has tests for its synchronization primitives, but these are more accessible +to kernel developers, and also allow us to test some edge cases that Wine does +not care about. + +This patch adds tests for semaphore-specific ioctls NTSYNC_IOC_SEM_POST and +NTSYNC_IOC_SEM_READ, and waiting on semaphores. +--- + tools/testing/selftests/Makefile | 1 + + .../selftests/drivers/ntsync/.gitignore | 1 + + .../testing/selftests/drivers/ntsync/Makefile | 7 + + tools/testing/selftests/drivers/ntsync/config | 1 + + .../testing/selftests/drivers/ntsync/ntsync.c | 149 ++++++++++++++++++ + 5 files changed, 159 insertions(+) + create mode 100644 tools/testing/selftests/drivers/ntsync/.gitignore + 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 e1504833654d..6f95206325e1 100644 +--- a/tools/testing/selftests/Makefile ++++ b/tools/testing/selftests/Makefile +@@ -16,6 +16,7 @@ TARGETS += damon + TARGETS += devices + 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/.gitignore b/tools/testing/selftests/drivers/ntsync/.gitignore +new file mode 100644 +index 000000000000..848573a3d3ea +--- /dev/null ++++ b/tools/testing/selftests/drivers/ntsync/.gitignore +@@ -0,0 +1 @@ ++ntsync +diff --git a/tools/testing/selftests/drivers/ntsync/Makefile b/tools/testing/selftests/drivers/ntsync/Makefile +new file mode 100644 +index 000000000000..dbf2b055c0b2 +--- /dev/null ++++ b/tools/testing/selftests/drivers/ntsync/Makefile +@@ -0,0 +1,7 @@ ++# SPDX-LICENSE-IDENTIFIER: GPL-2.0-only ++TEST_GEN_PROGS := ntsync + -+ if (args.count > args.max) -+ return -EINVAL; ++CFLAGS += $(KHDR_INCLUDES) ++LDLIBS += -lpthread + -+ sem = ntsync_alloc_obj(dev, NTSYNC_TYPE_SEM); -+ if (!sem) -+ return -ENOMEM; -+ sem->u.sem.count = args.count; -+ sem->u.sem.max = args.max; -+ fd = ntsync_obj_get_fd(sem); -+ if (fd < 0) { -+ kfree(sem); -+ return fd; -+ } ++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..1e145c6dfded +--- /dev/null ++++ b/tools/testing/selftests/drivers/ntsync/ntsync.c +@@ -0,0 +1,149 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Various unit tests for the "ntsync" synchronization primitive driver. ++ * ++ * Copyright (C) 2021-2022 Elizabeth Figura ++ */ + -+ return put_user(fd, &user_args->sem); -+} ++#define _GNU_SOURCE ++#include ++#include ++#include ++#include ++#include ++#include ++#include "../../kselftest_harness.h" + -+static int ntsync_create_mutex(struct ntsync_device *dev, void __user *argp) ++static int read_sem_state(int sem, __u32 *count, __u32 *max) +{ -+ struct ntsync_mutex_args __user *user_args = argp; -+ struct ntsync_mutex_args args; -+ struct ntsync_obj *mutex; -+ int fd; -+ -+ if (copy_from_user(&args, argp, sizeof(args))) -+ return -EFAULT; ++ struct ntsync_sem_args args; ++ int ret; + -+ if (!args.owner != !args.count) -+ return -EINVAL; ++ memset(&args, 0xcc, sizeof(args)); ++ ret = ioctl(sem, NTSYNC_IOC_SEM_READ, &args); ++ *count = args.count; ++ *max = args.max; ++ return ret; ++} + -+ mutex = ntsync_alloc_obj(dev, NTSYNC_TYPE_MUTEX); -+ if (!mutex) -+ return -ENOMEM; -+ mutex->u.mutex.count = args.count; -+ mutex->u.mutex.owner = args.owner; -+ fd = ntsync_obj_get_fd(mutex); -+ if (fd < 0) { -+ kfree(mutex); -+ return fd; -+ } ++#define check_sem_state(sem, count, max) \ ++ ({ \ ++ __u32 __count, __max; \ ++ int ret = read_sem_state((sem), &__count, &__max); \ ++ EXPECT_EQ(0, ret); \ ++ EXPECT_EQ((count), __count); \ ++ EXPECT_EQ((max), __max); \ ++ }) + -+ return put_user(fd, &user_args->mutex); ++static int post_sem(int sem, __u32 *count) ++{ ++ return ioctl(sem, NTSYNC_IOC_SEM_POST, count); +} + -+static int ntsync_create_event(struct ntsync_device *dev, void __user *argp) ++static int wait_any(int fd, __u32 count, const int *objs, __u32 owner, __u32 *index) +{ -+ struct ntsync_event_args __user *user_args = argp; -+ struct ntsync_event_args args; -+ struct ntsync_obj *event; -+ int fd; -+ -+ if (copy_from_user(&args, argp, sizeof(args))) -+ return -EFAULT; ++ struct ntsync_wait_args args = {0}; ++ struct timespec timeout; ++ int ret; + -+ event = ntsync_alloc_obj(dev, NTSYNC_TYPE_EVENT); -+ if (!event) -+ return -ENOMEM; -+ event->u.event.manual = args.manual; -+ event->u.event.signaled = args.signaled; -+ fd = ntsync_obj_get_fd(event); -+ if (fd < 0) { -+ kfree(event); -+ return fd; -+ } ++ clock_gettime(CLOCK_MONOTONIC, &timeout); + -+ return put_user(fd, &user_args->event); ++ args.timeout = timeout.tv_sec * 1000000000 + timeout.tv_nsec; ++ 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; +} + -+static struct ntsync_obj *get_obj(struct ntsync_device *dev, int fd) ++TEST(semaphore_state) +{ -+ struct file *file = fget(fd); -+ struct ntsync_obj *obj; -+ -+ if (file->f_op != &ntsync_obj_fops) { -+ fput(file); -+ return NULL; -+ } ++ struct ntsync_sem_args sem_args; ++ struct timespec timeout; ++ __u32 count, index; ++ int fd, ret, sem; + -+ obj = file->private_data; -+ if (obj->dev != dev) { -+ fput(file); -+ return NULL; -+ } ++ clock_gettime(CLOCK_MONOTONIC, &timeout); + -+ return obj; -+} ++ fd = open("/dev/ntsync", O_CLOEXEC | O_RDONLY); ++ ASSERT_LE(0, fd); + -+static void put_obj(struct ntsync_obj *obj) -+{ -+ fput(obj->file); -+} ++ 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); + -+static int ntsync_schedule(const struct ntsync_q *q, const struct ntsync_wait_args *args) -+{ -+ ktime_t timeout = ns_to_ktime(args->timeout); -+ clockid_t clock = CLOCK_MONOTONIC; -+ ktime_t *timeout_ptr; -+ int ret = 0; ++ 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); ++ sem = sem_args.sem; ++ check_sem_state(sem, 2, 2); + -+ timeout_ptr = (args->timeout == U64_MAX ? NULL : &timeout); ++ count = 0; ++ ret = post_sem(sem, &count); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(2, count); ++ check_sem_state(sem, 2, 2); + -+ if (args->flags & NTSYNC_WAIT_REALTIME) -+ clock = CLOCK_REALTIME; ++ count = 1; ++ ret = post_sem(sem, &count); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EOVERFLOW, errno); ++ check_sem_state(sem, 2, 2); + -+ do { -+ if (signal_pending(current)) { -+ ret = -ERESTARTSYS; -+ break; -+ } ++ ret = wait_any(fd, 1, &sem, 123, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, index); ++ check_sem_state(sem, 1, 2); + -+ set_current_state(TASK_INTERRUPTIBLE); -+ if (atomic_read(&q->signaled) != -1) { -+ ret = 0; -+ break; -+ } -+ ret = schedule_hrtimeout_range_clock(timeout_ptr, 0, HRTIMER_MODE_ABS, clock); -+ } while (ret < 0); -+ __set_current_state(TASK_RUNNING); ++ ret = wait_any(fd, 1, &sem, 123, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, index); ++ check_sem_state(sem, 0, 2); + -+ return ret; -+} ++ ret = wait_any(fd, 1, &sem, 123, &index); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(ETIMEDOUT, errno); + -+/* -+ * Allocate and initialize the ntsync_q structure, but do not queue us yet. -+ */ -+static int setup_wait(struct ntsync_device *dev, -+ const struct ntsync_wait_args *args, bool all, -+ struct ntsync_q **ret_q) -+{ -+ int fds[NTSYNC_MAX_WAIT_COUNT + 1]; -+ const __u32 count = args->count; -+ struct ntsync_q *q; -+ __u32 total_count; -+ __u32 i, j; -+ -+ if (!args->owner) -+ return -EINVAL; -+ -+ if (args->pad || (args->flags & ~NTSYNC_WAIT_REALTIME)) -+ return -EINVAL; -+ -+ if (args->count > NTSYNC_MAX_WAIT_COUNT) -+ return -EINVAL; -+ -+ total_count = count; -+ if (args->alert) -+ total_count++; -+ -+ if (copy_from_user(fds, u64_to_user_ptr(args->objs), -+ array_size(count, sizeof(*fds)))) -+ return -EFAULT; -+ if (args->alert) -+ fds[count] = args->alert; -+ -+ q = kmalloc(struct_size(q, entries, total_count), GFP_KERNEL); -+ if (!q) -+ return -ENOMEM; -+ q->task = current; -+ q->owner = args->owner; -+ atomic_set(&q->signaled, -1); -+ q->all = all; -+ q->ownerdead = false; -+ q->count = count; ++ count = 3; ++ ret = post_sem(sem, &count); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EOVERFLOW, errno); ++ check_sem_state(sem, 0, 2); + -+ for (i = 0; i < total_count; i++) { -+ struct ntsync_q_entry *entry = &q->entries[i]; -+ struct ntsync_obj *obj = get_obj(dev, fds[i]); ++ count = 2; ++ ret = post_sem(sem, &count); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, count); ++ check_sem_state(sem, 2, 2); + -+ if (!obj) -+ goto err; ++ ret = wait_any(fd, 1, &sem, 123, &index); ++ EXPECT_EQ(0, ret); ++ ret = wait_any(fd, 1, &sem, 123, &index); ++ EXPECT_EQ(0, ret); + -+ 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; -+ } -+ } -+ } ++ count = 1; ++ ret = post_sem(sem, &count); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, count); ++ check_sem_state(sem, 1, 2); + -+ entry->obj = obj; -+ entry->q = q; -+ entry->index = i; -+ } ++ count = ~0u; ++ ret = post_sem(sem, &count); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EOVERFLOW, errno); ++ check_sem_state(sem, 1, 2); + -+ *ret_q = q; -+ return 0; ++ close(sem); + -+err: -+ for (j = 0; j < i; j++) -+ put_obj(q->entries[j].obj); -+ kfree(q); -+ return -EINVAL; ++ close(fd); +} + -+static void try_wake_any_obj(struct ntsync_obj *obj) ++TEST_HARNESS_MAIN +-- +2.44.0 + + +From 63eb6bb9f3e43925057c4d50bf0b20ed1e42de33 Mon Sep 17 00:00:00 2001 +From: Elizabeth Figura +Date: Fri, 5 Mar 2021 12:07:04 -0600 +Subject: [PATCH 18/30] selftests: ntsync: Add some tests for mutex state. + +Test mutex-specific ioctls NTSYNC_IOC_MUTEX_UNLOCK and NTSYNC_IOC_MUTEX_READ, +and waiting on mutexes. +--- + .../testing/selftests/drivers/ntsync/ntsync.c | 196 ++++++++++++++++++ + 1 file changed, 196 insertions(+) + +diff --git a/tools/testing/selftests/drivers/ntsync/ntsync.c b/tools/testing/selftests/drivers/ntsync/ntsync.c +index 1e145c6dfded..7cd0f40594fd 100644 +--- a/tools/testing/selftests/drivers/ntsync/ntsync.c ++++ b/tools/testing/selftests/drivers/ntsync/ntsync.c +@@ -40,6 +40,39 @@ static int post_sem(int sem, __u32 *count) + return ioctl(sem, NTSYNC_IOC_SEM_POST, count); + } + ++static int read_mutex_state(int mutex, __u32 *count, __u32 *owner) +{ -+ switch (obj->type) { -+ case NTSYNC_TYPE_SEM: -+ try_wake_any_sem(obj); -+ break; -+ case NTSYNC_TYPE_MUTEX: -+ try_wake_any_mutex(obj); -+ break; -+ case NTSYNC_TYPE_EVENT: -+ try_wake_any_event(obj); -+ break; -+ } ++ struct ntsync_mutex_args args; ++ int ret; ++ ++ memset(&args, 0xcc, sizeof(args)); ++ ret = ioctl(mutex, NTSYNC_IOC_MUTEX_READ, &args); ++ *count = args.count; ++ *owner = args.owner; ++ return ret; +} + -+static int ntsync_wait_any(struct ntsync_device *dev, void __user *argp) ++#define check_mutex_state(mutex, count, owner) \ ++ ({ \ ++ __u32 __count, __owner; \ ++ int ret = read_mutex_state((mutex), &__count, &__owner); \ ++ EXPECT_EQ(0, ret); \ ++ EXPECT_EQ((count), __count); \ ++ EXPECT_EQ((owner), __owner); \ ++ }) ++ ++static int unlock_mutex(int mutex, __u32 owner, __u32 *count) +{ -+ struct ntsync_wait_args args; -+ __u32 i, total_count; -+ struct ntsync_q *q; -+ int signaled; ++ struct ntsync_mutex_args args; + int ret; + -+ if (copy_from_user(&args, argp, sizeof(args))) -+ return -EFAULT; -+ -+ ret = setup_wait(dev, &args, false, &q); -+ if (ret < 0) -+ return ret; ++ args.owner = owner; ++ args.count = 0xdeadbeef; ++ ret = ioctl(mutex, NTSYNC_IOC_MUTEX_UNLOCK, &args); ++ *count = args.count; ++ return ret; ++} + -+ total_count = args.count; -+ if (args.alert) -+ total_count++; + static int wait_any(int fd, __u32 count, const int *objs, __u32 owner, __u32 *index) + { + struct ntsync_wait_args args = {0}; +@@ -146,4 +179,167 @@ TEST(semaphore_state) + close(fd); + } + ++TEST(mutex_state) ++{ ++ struct ntsync_mutex_args mutex_args; ++ __u32 owner, count, index; ++ struct timespec timeout; ++ int fd, ret, mutex; + -+ /* queue ourselves */ ++ clock_gettime(CLOCK_MONOTONIC, &timeout); + -+ for (i = 0; i < total_count; i++) { -+ struct ntsync_q_entry *entry = &q->entries[i]; -+ struct ntsync_obj *obj = entry->obj; ++ fd = open("/dev/ntsync", O_CLOEXEC | O_RDONLY); ++ ASSERT_LE(0, fd); + -+ spin_lock(&obj->lock); -+ list_add_tail(&entry->node, &obj->any_waiters); -+ spin_unlock(&obj->lock); -+ } ++ 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); + -+ /* -+ * 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. -+ */ ++ 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); + -+ for (i = 0; i < total_count; i++) { -+ struct ntsync_obj *obj = q->entries[i].obj; ++ 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(mutex, 2, 123); + -+ if (atomic_read(&q->signaled) != -1) -+ break; ++ ret = unlock_mutex(mutex, 0, &count); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EINVAL, errno); + -+ spin_lock(&obj->lock); -+ try_wake_any_obj(obj); -+ spin_unlock(&obj->lock); -+ } ++ ret = unlock_mutex(mutex, 456, &count); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EPERM, errno); ++ check_mutex_state(mutex, 2, 123); + -+ /* sleep */ ++ ret = unlock_mutex(mutex, 123, &count); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(2, count); ++ check_mutex_state(mutex, 1, 123); + -+ ret = ntsync_schedule(q, &args); ++ ret = unlock_mutex(mutex, 123, &count); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(1, count); ++ check_mutex_state(mutex, 0, 0); + -+ /* and finally, unqueue */ ++ ret = unlock_mutex(mutex, 123, &count); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EPERM, errno); + -+ for (i = 0; i < total_count; i++) { -+ struct ntsync_q_entry *entry = &q->entries[i]; -+ struct ntsync_obj *obj = entry->obj; ++ ret = wait_any(fd, 1, &mutex, 456, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, index); ++ check_mutex_state(mutex, 1, 456); + -+ spin_lock(&obj->lock); -+ list_del(&entry->node); -+ spin_unlock(&obj->lock); ++ ret = wait_any(fd, 1, &mutex, 456, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, index); ++ check_mutex_state(mutex, 2, 456); + -+ put_obj(obj); -+ } ++ ret = unlock_mutex(mutex, 456, &count); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(2, count); ++ check_mutex_state(mutex, 1, 456); + -+ signaled = atomic_read(&q->signaled); -+ if (signaled != -1) { -+ struct ntsync_wait_args __user *user_args = argp; ++ ret = wait_any(fd, 1, &mutex, 123, &index); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(ETIMEDOUT, errno); + -+ /* even if we caught a signal, we need to communicate success */ -+ ret = q->ownerdead ? -EOWNERDEAD : 0; ++ owner = 0; ++ ret = ioctl(mutex, NTSYNC_IOC_MUTEX_KILL, &owner); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EINVAL, errno); + -+ if (put_user(signaled, &user_args->index)) -+ ret = -EFAULT; -+ } else if (!ret) { -+ ret = -ETIMEDOUT; -+ } ++ owner = 123; ++ ret = ioctl(mutex, NTSYNC_IOC_MUTEX_KILL, &owner); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EPERM, errno); ++ check_mutex_state(mutex, 1, 456); + -+ kfree(q); -+ return ret; -+} ++ owner = 456; ++ ret = ioctl(mutex, NTSYNC_IOC_MUTEX_KILL, &owner); ++ EXPECT_EQ(0, ret); + -+static int ntsync_wait_all(struct ntsync_device *dev, void __user *argp) -+{ -+ struct ntsync_wait_args args; -+ struct ntsync_q *q; -+ int signaled; -+ __u32 i; -+ int ret; ++ memset(&mutex_args, 0xcc, sizeof(mutex_args)); ++ ret = ioctl(mutex, NTSYNC_IOC_MUTEX_READ, &mutex_args); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EOWNERDEAD, errno); ++ EXPECT_EQ(0, mutex_args.count); ++ EXPECT_EQ(0, mutex_args.owner); + -+ if (copy_from_user(&args, argp, sizeof(args))) -+ return -EFAULT; ++ memset(&mutex_args, 0xcc, sizeof(mutex_args)); ++ ret = ioctl(mutex, NTSYNC_IOC_MUTEX_READ, &mutex_args); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EOWNERDEAD, errno); ++ EXPECT_EQ(0, mutex_args.count); ++ EXPECT_EQ(0, mutex_args.owner); + -+ ret = setup_wait(dev, &args, true, &q); -+ if (ret < 0) -+ return ret; ++ ret = wait_any(fd, 1, &mutex, 123, &index); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EOWNERDEAD, errno); ++ EXPECT_EQ(0, index); ++ check_mutex_state(mutex, 1, 123); + -+ /* queue ourselves */ ++ owner = 123; ++ ret = ioctl(mutex, NTSYNC_IOC_MUTEX_KILL, &owner); ++ EXPECT_EQ(0, ret); + -+ spin_lock(&dev->wait_all_lock); ++ memset(&mutex_args, 0xcc, sizeof(mutex_args)); ++ ret = ioctl(mutex, NTSYNC_IOC_MUTEX_READ, &mutex_args); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EOWNERDEAD, errno); ++ EXPECT_EQ(0, mutex_args.count); ++ EXPECT_EQ(0, mutex_args.owner); + -+ for (i = 0; i < args.count; i++) { -+ struct ntsync_q_entry *entry = &q->entries[i]; -+ struct ntsync_obj *obj = entry->obj; ++ ret = wait_any(fd, 1, &mutex, 123, &index); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EOWNERDEAD, errno); ++ EXPECT_EQ(0, index); ++ check_mutex_state(mutex, 1, 123); + -+ atomic_inc(&obj->all_hint); ++ close(mutex); + -+ /* -+ * obj->all_waiters is protected by dev->wait_all_lock rather -+ * than obj->lock, so there is no need to acquire obj->lock -+ * here. -+ */ -+ 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; ++ 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(mutex, 0, 0); + -+ spin_lock_nest_lock(&obj->lock, &dev->wait_all_lock); -+ list_add_tail(&entry->node, &obj->any_waiters); -+ spin_unlock(&obj->lock); -+ } ++ ret = wait_any(fd, 1, &mutex, 123, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, index); ++ check_mutex_state(mutex, 1, 123); + -+ /* check if we are already signaled */ ++ close(mutex); + -+ try_wake_all(dev, q, NULL); ++ mutex_args.owner = 123; ++ mutex_args.count = ~0u; ++ 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(mutex, ~0u, 123); + -+ spin_unlock(&dev->wait_all_lock); ++ ret = wait_any(fd, 1, &mutex, 123, &index); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(ETIMEDOUT, errno); + -+ /* -+ * Check if the alert event is signaled, making sure to do so only -+ * after checking if the other objects are signaled. -+ */ ++ close(mutex); + -+ if (args.alert) { -+ struct ntsync_obj *obj = q->entries[args.count].obj; ++ close(fd); ++} + -+ if (atomic_read(&q->signaled) == -1) { -+ spin_lock(&obj->lock); -+ try_wake_any_obj(obj); -+ spin_unlock(&obj->lock); -+ } -+ } + TEST_HARNESS_MAIN +-- +2.44.0 + + +From 6e4e7848352c4300df5ff4ee48d2d3a8bae4fd8c Mon Sep 17 00:00:00 2001 +From: Elizabeth Figura +Date: Fri, 5 Mar 2021 12:07:45 -0600 +Subject: [PATCH 19/30] selftests: ntsync: Add some tests for + NTSYNC_IOC_WAIT_ANY. + +Test basic synchronous functionality of NTSYNC_IOC_WAIT_ANY, when objects are +considered signaled or not signaled, and how they are affected by a successful +wait. +--- + .../testing/selftests/drivers/ntsync/ntsync.c | 119 ++++++++++++++++++ + 1 file changed, 119 insertions(+) + +diff --git a/tools/testing/selftests/drivers/ntsync/ntsync.c b/tools/testing/selftests/drivers/ntsync/ntsync.c +index 7cd0f40594fd..40ad8cbd3138 100644 +--- a/tools/testing/selftests/drivers/ntsync/ntsync.c ++++ b/tools/testing/selftests/drivers/ntsync/ntsync.c +@@ -342,4 +342,123 @@ TEST(mutex_state) + close(fd); + } + ++TEST(test_wait_any) ++{ ++ int objs[NTSYNC_MAX_WAIT_COUNT + 1], fd, ret; ++ struct ntsync_mutex_args mutex_args = {0}; ++ struct ntsync_sem_args sem_args = {0}; ++ __u32 owner, index, count, i; ++ struct timespec timeout; + -+ /* sleep */ ++ clock_gettime(CLOCK_MONOTONIC, &timeout); + -+ ret = ntsync_schedule(q, &args); ++ fd = open("/dev/ntsync", O_CLOEXEC | O_RDONLY); ++ ASSERT_LE(0, fd); + -+ /* and finally, unqueue */ ++ 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); + -+ spin_lock(&dev->wait_all_lock); ++ 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); + -+ for (i = 0; i < args.count; i++) { -+ struct ntsync_q_entry *entry = &q->entries[i]; -+ struct ntsync_obj *obj = entry->obj; ++ objs[0] = sem_args.sem; ++ objs[1] = mutex_args.mutex; + -+ /* -+ * 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); ++ ret = wait_any(fd, 2, objs, 123, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, index); ++ check_sem_state(sem_args.sem, 1, 3); ++ check_mutex_state(mutex_args.mutex, 0, 0); + -+ atomic_dec(&obj->all_hint); ++ ret = wait_any(fd, 2, objs, 123, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, index); ++ check_sem_state(sem_args.sem, 0, 3); ++ check_mutex_state(mutex_args.mutex, 0, 0); + -+ put_obj(obj); -+ } -+ if (args.alert) { -+ struct ntsync_q_entry *entry = &q->entries[args.count]; -+ struct ntsync_obj *obj = entry->obj; ++ ret = wait_any(fd, 2, objs, 123, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(1, index); ++ check_sem_state(sem_args.sem, 0, 3); ++ check_mutex_state(mutex_args.mutex, 1, 123); + -+ spin_lock_nest_lock(&obj->lock, &dev->wait_all_lock); -+ list_del(&entry->node); -+ spin_unlock(&obj->lock); ++ count = 1; ++ ret = post_sem(sem_args.sem, &count); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, count); + -+ put_obj(obj); -+ } ++ ret = wait_any(fd, 2, objs, 123, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, index); ++ check_sem_state(sem_args.sem, 0, 3); ++ check_mutex_state(mutex_args.mutex, 1, 123); + -+ spin_unlock(&dev->wait_all_lock); ++ ret = wait_any(fd, 2, objs, 123, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(1, index); ++ check_sem_state(sem_args.sem, 0, 3); ++ check_mutex_state(mutex_args.mutex, 2, 123); + -+ signaled = atomic_read(&q->signaled); -+ if (signaled != -1) { -+ struct ntsync_wait_args __user *user_args = argp; ++ ret = wait_any(fd, 2, objs, 456, &index); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(ETIMEDOUT, errno); + -+ /* even if we caught a signal, we need to communicate success */ -+ ret = q->ownerdead ? -EOWNERDEAD : 0; ++ owner = 123; ++ ret = ioctl(mutex_args.mutex, NTSYNC_IOC_MUTEX_KILL, &owner); ++ EXPECT_EQ(0, ret); + -+ if (put_user(signaled, &user_args->index)) -+ ret = -EFAULT; -+ } else if (!ret) { -+ ret = -ETIMEDOUT; -+ } ++ ret = wait_any(fd, 2, objs, 456, &index); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EOWNERDEAD, errno); ++ EXPECT_EQ(1, index); + -+ kfree(q); -+ return ret; -+} ++ ret = wait_any(fd, 2, objs, 456, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(1, index); + -+static int ntsync_char_open(struct inode *inode, struct file *file) -+{ -+ struct ntsync_device *dev; ++ /* test waiting on the same object twice */ ++ count = 2; ++ ret = post_sem(sem_args.sem, &count); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, count); + -+ dev = kzalloc(sizeof(*dev), GFP_KERNEL); -+ if (!dev) -+ return -ENOMEM; ++ objs[0] = objs[1] = sem_args.sem; ++ ret = wait_any(fd, 2, objs, 456, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, index); ++ check_sem_state(sem_args.sem, 1, 3); + -+ spin_lock_init(&dev->wait_all_lock); ++ ret = wait_any(fd, 0, NULL, 456, &index); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(ETIMEDOUT, errno); + -+ file->private_data = dev; -+ dev->file = file; -+ return nonseekable_open(inode, file); -+} ++ for (i = 0; i < NTSYNC_MAX_WAIT_COUNT + 1; ++i) ++ objs[i] = sem_args.sem; + -+static int ntsync_char_release(struct inode *inode, struct file *file) -+{ -+ struct ntsync_device *dev = file->private_data; ++ ret = wait_any(fd, NTSYNC_MAX_WAIT_COUNT, objs, 123, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, index); + -+ kfree(dev); ++ ret = wait_any(fd, NTSYNC_MAX_WAIT_COUNT + 1, objs, 123, &index); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EINVAL, errno); + -+ return 0; -+} ++ ret = wait_any(fd, -1, objs, 123, &index); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EINVAL, errno); + -+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; ++ close(sem_args.sem); ++ close(mutex_args.mutex); + -+ 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: -+ return ntsync_create_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: -+ return -ENOIOCTLCMD; -+ } ++ close(fd); +} + -+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 = compat_ptr_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"); -diff --git a/include/uapi/linux/ntsync.h b/include/uapi/linux/ntsync.h -new file mode 100644 -index 000000000000..074f26423426 ---- /dev/null -+++ b/include/uapi/linux/ntsync.h -@@ -0,0 +1,62 @@ -+/* 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 -+ -+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 manual; -+ __u32 signaled; -+}; -+ -+#define NTSYNC_WAIT_REALTIME 0x1 -+ -+struct ntsync_wait_args { -+ __u64 timeout; -+ __u64 objs; -+ __u32 count; -+ __u32 owner; -+ __u32 index; -+ __u32 alert; -+ __u32 flags; -+ __u32 pad; -+}; -+ -+#define NTSYNC_MAX_WAIT_COUNT 64 -+ -+#define NTSYNC_IOC_CREATE_SEM _IOWR('N', 0x80, struct ntsync_sem_args) -+#define NTSYNC_IOC_WAIT_ANY _IOWR('N', 0x82, struct ntsync_wait_args) -+#define NTSYNC_IOC_WAIT_ALL _IOWR('N', 0x83, struct ntsync_wait_args) -+#define NTSYNC_IOC_CREATE_MUTEX _IOWR('N', 0x84, struct ntsync_sem_args) -+#define NTSYNC_IOC_CREATE_EVENT _IOWR('N', 0x87, struct ntsync_event_args) -+ -+#define NTSYNC_IOC_SEM_POST _IOWR('N', 0x81, __u32) -+#define NTSYNC_IOC_MUTEX_UNLOCK _IOWR('N', 0x85, struct ntsync_mutex_args) -+#define NTSYNC_IOC_MUTEX_KILL _IOW ('N', 0x86, __u32) -+#define NTSYNC_IOC_EVENT_SET _IOR ('N', 0x88, __u32) -+#define NTSYNC_IOC_EVENT_RESET _IOR ('N', 0x89, __u32) -+#define NTSYNC_IOC_EVENT_PULSE _IOR ('N', 0x8a, __u32) -+#define NTSYNC_IOC_SEM_READ _IOR ('N', 0x8b, struct ntsync_sem_args) -+#define NTSYNC_IOC_MUTEX_READ _IOR ('N', 0x8c, struct ntsync_mutex_args) -+#define NTSYNC_IOC_EVENT_READ _IOR ('N', 0x8d, struct ntsync_event_args) -+ -+#endif -diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile -index e1504833654d..6f95206325e1 100644 ---- a/tools/testing/selftests/Makefile -+++ b/tools/testing/selftests/Makefile -@@ -16,6 +16,7 @@ TARGETS += damon - TARGETS += devices - 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 + TEST_HARNESS_MAIN +-- +2.44.0 + + +From 6eb8f2c3501f0bb0cc4514da86e10b6b5cfbacde Mon Sep 17 00:00:00 2001 +From: Elizabeth Figura +Date: Fri, 5 Mar 2021 12:08:25 -0600 +Subject: [PATCH 20/30] selftests: ntsync: Add some tests for + NTSYNC_IOC_WAIT_ALL. + +Test basic synchronous functionality of NTSYNC_IOC_WAIT_ALL, and when objects +are considered simultaneously signaled. +--- + .../testing/selftests/drivers/ntsync/ntsync.c | 99 ++++++++++++++++++- + 1 file changed, 97 insertions(+), 2 deletions(-) + diff --git a/tools/testing/selftests/drivers/ntsync/ntsync.c b/tools/testing/selftests/drivers/ntsync/ntsync.c -new file mode 100644 -index 000000000000..aba513c9af01 ---- /dev/null +index 40ad8cbd3138..c0f372167557 100644 +--- a/tools/testing/selftests/drivers/ntsync/ntsync.c +++ b/tools/testing/selftests/drivers/ntsync/ntsync.c -@@ -0,0 +1,1407 @@ -+// 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 -+#include -+#include -+#include -+#include -+#include -+#include "../../kselftest_harness.h" -+ -+static int read_sem_state(int sem, __u32 *count, __u32 *max) +@@ -73,7 +73,8 @@ static int unlock_mutex(int mutex, __u32 owner, __u32 *count) + return ret; + } + +-static int wait_any(int fd, __u32 count, const int *objs, __u32 owner, __u32 *index) ++static int wait_objs(int fd, unsigned long request, __u32 count, ++ const int *objs, __u32 owner, __u32 *index) + { + struct ntsync_wait_args args = {0}; + struct timespec timeout; +@@ -86,11 +87,21 @@ static int wait_any(int fd, __u32 count, const int *objs, __u32 owner, __u32 *in + 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 int *objs, __u32 owner, __u32 *index) +{ -+ struct ntsync_sem_args args; -+ int ret; -+ -+ memset(&args, 0xcc, sizeof(args)); -+ ret = ioctl(sem, NTSYNC_IOC_SEM_READ, &args); -+ *count = args.count; -+ *max = args.max; -+ return ret; ++ return wait_objs(fd, NTSYNC_IOC_WAIT_ANY, count, objs, owner, index); +} + -+#define check_sem_state(sem, count, max) \ -+ ({ \ -+ __u32 __count, __max; \ -+ int ret = read_sem_state((sem), &__count, &__max); \ -+ EXPECT_EQ(0, ret); \ -+ EXPECT_EQ((count), __count); \ -+ EXPECT_EQ((max), __max); \ -+ }) -+ -+static int post_sem(int sem, __u32 *count) ++static int wait_all(int fd, __u32 count, const int *objs, __u32 owner, __u32 *index) +{ -+ return ioctl(sem, NTSYNC_IOC_SEM_POST, count); ++ return wait_objs(fd, NTSYNC_IOC_WAIT_ALL, count, objs, owner, index); +} + -+static int read_mutex_state(int mutex, __u32 *count, __u32 *owner) + TEST(semaphore_state) + { + struct ntsync_sem_args sem_args; +@@ -461,4 +472,88 @@ TEST(test_wait_any) + close(fd); + } + ++TEST(test_wait_all) +{ -+ struct ntsync_mutex_args args; -+ int ret; ++ struct ntsync_mutex_args mutex_args = {0}; ++ struct ntsync_sem_args sem_args = {0}; ++ __u32 owner, index, count; ++ int objs[2], fd, ret; + -+ memset(&args, 0xcc, sizeof(args)); -+ ret = ioctl(mutex, NTSYNC_IOC_MUTEX_READ, &args); -+ *count = args.count; -+ *owner = args.owner; -+ return ret; -+} ++ fd = open("/dev/ntsync", O_CLOEXEC | O_RDONLY); ++ ASSERT_LE(0, fd); + -+#define check_mutex_state(mutex, count, owner) \ -+ ({ \ -+ __u32 __count, __owner; \ -+ int ret = read_mutex_state((mutex), &__count, &__owner); \ -+ EXPECT_EQ(0, ret); \ -+ EXPECT_EQ((count), __count); \ -+ EXPECT_EQ((owner), __owner); \ -+ }) ++ 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); + -+static int unlock_mutex(int mutex, __u32 owner, __u32 *count) -+{ -+ struct ntsync_mutex_args args; -+ int 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); + -+ args.owner = owner; -+ args.count = 0xdeadbeef; -+ ret = ioctl(mutex, NTSYNC_IOC_MUTEX_UNLOCK, &args); -+ *count = args.count; -+ return ret; -+} ++ objs[0] = sem_args.sem; ++ objs[1] = mutex_args.mutex; + -+static int read_event_state(int event, __u32 *signaled, __u32 *manual) -+{ -+ struct ntsync_event_args args; -+ int ret; ++ ret = wait_all(fd, 2, objs, 123, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, index); ++ check_sem_state(sem_args.sem, 1, 3); ++ check_mutex_state(mutex_args.mutex, 1, 123); + -+ memset(&args, 0xcc, sizeof(args)); -+ ret = ioctl(event, NTSYNC_IOC_EVENT_READ, &args); -+ *signaled = args.signaled; -+ *manual = args.manual; -+ return ret; -+} ++ ret = wait_all(fd, 2, objs, 456, &index); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(ETIMEDOUT, errno); ++ check_sem_state(sem_args.sem, 1, 3); ++ check_mutex_state(mutex_args.mutex, 1, 123); + -+#define check_event_state(event, signaled, manual) \ -+ ({ \ -+ __u32 __signaled, __manual; \ -+ int ret = read_event_state((event), &__signaled, &__manual); \ -+ EXPECT_EQ(0, ret); \ -+ EXPECT_EQ((signaled), __signaled); \ -+ EXPECT_EQ((manual), __manual); \ -+ }) ++ ret = wait_all(fd, 2, objs, 123, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, index); ++ check_sem_state(sem_args.sem, 0, 3); ++ check_mutex_state(mutex_args.mutex, 2, 123); + -+static int wait_objs(int fd, unsigned long request, __u32 count, -+ const int *objs, __u32 owner, int alert, __u32 *index) -+{ -+ struct ntsync_wait_args args = {0}; -+ struct timespec timeout; -+ int ret; ++ ret = wait_all(fd, 2, objs, 123, &index); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(ETIMEDOUT, errno); ++ check_sem_state(sem_args.sem, 0, 3); ++ check_mutex_state(mutex_args.mutex, 2, 123); + -+ clock_gettime(CLOCK_MONOTONIC, &timeout); ++ count = 3; ++ ret = post_sem(sem_args.sem, &count); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, count); + -+ args.timeout = timeout.tv_sec * 1000000000 + timeout.tv_nsec; -+ args.count = 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; -+} ++ ret = wait_all(fd, 2, objs, 123, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, index); ++ check_sem_state(sem_args.sem, 2, 3); ++ check_mutex_state(mutex_args.mutex, 3, 123); ++ ++ owner = 123; ++ ret = ioctl(mutex_args.mutex, NTSYNC_IOC_MUTEX_KILL, &owner); ++ EXPECT_EQ(0, ret); ++ ++ ret = wait_all(fd, 2, objs, 123, &index); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EOWNERDEAD, errno); ++ check_sem_state(sem_args.sem, 1, 3); ++ check_mutex_state(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); ++ ++ close(sem_args.sem); ++ close(mutex_args.mutex); + -+static int wait_any(int fd, __u32 count, const int *objs, __u32 owner, __u32 *index) -+{ -+ return wait_objs(fd, NTSYNC_IOC_WAIT_ANY, count, objs, owner, 0, index); ++ close(fd); +} + -+static int wait_all(int fd, __u32 count, const int *objs, __u32 owner, __u32 *index) + TEST_HARNESS_MAIN +-- +2.44.0 + + +From efef49bb0f418fc2b1044810e58ddac757ea2962 Mon Sep 17 00:00:00 2001 +From: Elizabeth Figura +Date: Fri, 5 Mar 2021 12:09:32 -0600 +Subject: [PATCH 21/30] selftests: ntsync: Add some tests for wakeup signaling + with WINESYNC_IOC_WAIT_ANY. + +Test contended "wait-for-any" waits, to make sure that scheduling and wakeup +logic works correctly. +--- + .../testing/selftests/drivers/ntsync/ntsync.c | 150 ++++++++++++++++++ + 1 file changed, 150 insertions(+) + +diff --git a/tools/testing/selftests/drivers/ntsync/ntsync.c b/tools/testing/selftests/drivers/ntsync/ntsync.c +index c0f372167557..993f5db23768 100644 +--- a/tools/testing/selftests/drivers/ntsync/ntsync.c ++++ b/tools/testing/selftests/drivers/ntsync/ntsync.c +@@ -556,4 +556,154 @@ TEST(test_wait_all) + close(fd); + } + ++struct wake_args { ++ int fd; ++ int obj; ++}; ++ ++struct wait_args { ++ int fd; ++ unsigned long request; ++ struct ntsync_wait_args *args; ++ int ret; ++ int err; ++}; ++ ++static void *wait_thread(void *arg) +{ -+ return wait_objs(fd, NTSYNC_IOC_WAIT_ALL, count, objs, owner, 0, index); ++ struct wait_args *args = arg; ++ ++ args->ret = ioctl(args->fd, args->request, args->args); ++ args->err = errno; ++ return NULL; +} + -+static int wait_any_alert(int fd, __u32 count, const int *objs, -+ __u32 owner, int alert, __u32 *index) ++static __u64 get_abs_timeout(unsigned int ms) +{ -+ return wait_objs(fd, NTSYNC_IOC_WAIT_ANY, -+ count, objs, owner, alert, index); ++ struct timespec timeout; ++ clock_gettime(CLOCK_MONOTONIC, &timeout); ++ return (timeout.tv_sec * 1000000000) + timeout.tv_nsec + (ms * 1000000); +} + -+static int wait_all_alert(int fd, __u32 count, const int *objs, -+ __u32 owner, int alert, __u32 *index) ++static int wait_for_thread(pthread_t thread, unsigned int ms) +{ -+ return wait_objs(fd, NTSYNC_IOC_WAIT_ALL, -+ count, objs, owner, alert, index); ++ struct timespec timeout; ++ ++ clock_gettime(CLOCK_REALTIME, &timeout); ++ timeout.tv_nsec += ms * 1000000; ++ timeout.tv_sec += (timeout.tv_nsec / 1000000000); ++ timeout.tv_nsec %= 1000000000; ++ return pthread_timedjoin_np(thread, NULL, &timeout); +} + -+TEST(semaphore_state) ++TEST(wake_any) +{ -+ struct ntsync_sem_args sem_args; -+ struct timespec timeout; ++ 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; ++ int objs[2], fd, ret; + __u32 count, index; -+ int fd, ret, sem; -+ -+ clock_gettime(CLOCK_MONOTONIC, &timeout); ++ pthread_t thread; + + 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.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); -+ sem = sem_args.sem; -+ check_sem_state(sem, 2, 2); + -+ count = 0; -+ ret = post_sem(sem, &count); ++ 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_EQ(2, count); -+ check_sem_state(sem, 2, 2); ++ EXPECT_NE(0xdeadbeef, mutex_args.mutex); + -+ count = 1; -+ ret = post_sem(sem, &count); -+ EXPECT_EQ(-1, ret); -+ EXPECT_EQ(EOVERFLOW, errno); -+ check_sem_state(sem, 2, 2); ++ objs[0] = sem_args.sem; ++ objs[1] = mutex_args.mutex; + -+ ret = wait_any(fd, 1, &sem, 123, &index); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(0, index); -+ check_sem_state(sem, 1, 2); ++ /* test waking the semaphore */ + -+ ret = wait_any(fd, 1, &sem, 123, &index); ++ wait_args.timeout = get_abs_timeout(1000); ++ 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); -+ EXPECT_EQ(0, index); -+ check_sem_state(sem, 0, 2); -+ -+ ret = wait_any(fd, 1, &sem, 123, &index); -+ EXPECT_EQ(-1, ret); -+ EXPECT_EQ(ETIMEDOUT, errno); + -+ count = 3; -+ ret = post_sem(sem, &count); -+ EXPECT_EQ(-1, ret); -+ EXPECT_EQ(EOVERFLOW, errno); -+ check_sem_state(sem, 0, 2); ++ ret = wait_for_thread(thread, 100); ++ EXPECT_EQ(ETIMEDOUT, ret); + -+ count = 2; -+ ret = post_sem(sem, &count); ++ count = 1; ++ ret = post_sem(sem_args.sem, &count); + EXPECT_EQ(0, ret); + EXPECT_EQ(0, count); -+ check_sem_state(sem, 2, 2); ++ check_sem_state(sem_args.sem, 0, 3); + -+ ret = wait_any(fd, 1, &sem, 123, &index); -+ EXPECT_EQ(0, ret); -+ ret = wait_any(fd, 1, &sem, 123, &index); ++ ret = wait_for_thread(thread, 100); + EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, thread_args.ret); ++ EXPECT_EQ(0, wait_args.index); + -+ count = 1; -+ ret = post_sem(sem, &count); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(0, count); -+ check_sem_state(sem, 1, 2); ++ /* test waking the mutex */ + -+ count = ~0u; -+ ret = post_sem(sem, &count); -+ EXPECT_EQ(-1, ret); -+ EXPECT_EQ(EOVERFLOW, errno); -+ check_sem_state(sem, 1, 2); ++ /* 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); + -+ close(sem); ++ wait_args.timeout = get_abs_timeout(1000); ++ wait_args.owner = 456; ++ ret = pthread_create(&thread, NULL, wait_thread, &thread_args); ++ EXPECT_EQ(0, ret); + -+ close(fd); -+} ++ ret = wait_for_thread(thread, 100); ++ EXPECT_EQ(ETIMEDOUT, ret); + -+TEST(mutex_state) -+{ -+ struct ntsync_mutex_args mutex_args; -+ __u32 owner, count, index; -+ struct timespec timeout; -+ int fd, ret, mutex; ++ ret = unlock_mutex(mutex_args.mutex, 123, &count); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(2, count); + -+ clock_gettime(CLOCK_MONOTONIC, &timeout); ++ ret = pthread_tryjoin_np(thread, NULL); ++ EXPECT_EQ(EBUSY, ret); + -+ fd = open("/dev/ntsync", O_CLOEXEC | O_RDONLY); -+ ASSERT_LE(0, fd); ++ ret = unlock_mutex(mutex_args.mutex, 123, &count); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(1, mutex_args.count); ++ check_mutex_state(mutex_args.mutex, 1, 456); + -+ 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); ++ ret = wait_for_thread(thread, 100); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, thread_args.ret); ++ EXPECT_EQ(1, wait_args.index); + -+ 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); ++ /* delete an object while it's being waited on */ + -+ mutex_args.owner = 123; -+ mutex_args.count = 2; -+ mutex_args.mutex = 0xdeadbeef; -+ ret = ioctl(fd, NTSYNC_IOC_CREATE_MUTEX, &mutex_args); ++ wait_args.timeout = get_abs_timeout(200); ++ wait_args.owner = 123; ++ ret = pthread_create(&thread, NULL, wait_thread, &thread_args); + EXPECT_EQ(0, ret); -+ EXPECT_NE(0xdeadbeef, mutex_args.mutex); -+ mutex = mutex_args.mutex; -+ check_mutex_state(mutex, 2, 123); + -+ ret = unlock_mutex(mutex, 0, &count); -+ EXPECT_EQ(-1, ret); -+ EXPECT_EQ(EINVAL, errno); ++ ret = wait_for_thread(thread, 100); ++ EXPECT_EQ(ETIMEDOUT, ret); + -+ ret = unlock_mutex(mutex, 456, &count); -+ EXPECT_EQ(-1, ret); -+ EXPECT_EQ(EPERM, errno); -+ check_mutex_state(mutex, 2, 123); ++ close(sem_args.sem); ++ close(mutex_args.mutex); + -+ ret = unlock_mutex(mutex, 123, &count); ++ ret = wait_for_thread(thread, 200); + EXPECT_EQ(0, ret); -+ EXPECT_EQ(2, count); -+ check_mutex_state(mutex, 1, 123); ++ EXPECT_EQ(-1, thread_args.ret); ++ EXPECT_EQ(ETIMEDOUT, thread_args.err); + -+ ret = unlock_mutex(mutex, 123, &count); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(1, count); -+ check_mutex_state(mutex, 0, 0); ++ close(fd); ++} + -+ ret = unlock_mutex(mutex, 123, &count); -+ EXPECT_EQ(-1, ret); -+ EXPECT_EQ(EPERM, errno); + TEST_HARNESS_MAIN +-- +2.44.0 + + +From 2348d7b12998b3ecea716025b028aea5ff6c331c Mon Sep 17 00:00:00 2001 +From: Elizabeth Figura +Date: Fri, 5 Mar 2021 12:09:36 -0600 +Subject: [PATCH 22/30] selftests: ntsync: Add some tests for wakeup signaling + with WINESYNC_IOC_WAIT_ALL. + +Test contended "wait-for-all" waits, to make sure that scheduling and wakeup +logic works correctly, and that the wait only exits once objects are all +simultaneously signaled. +--- + .../testing/selftests/drivers/ntsync/ntsync.c | 98 +++++++++++++++++++ + 1 file changed, 98 insertions(+) + +diff --git a/tools/testing/selftests/drivers/ntsync/ntsync.c b/tools/testing/selftests/drivers/ntsync/ntsync.c +index 993f5db23768..b77fb0b2c4b1 100644 +--- a/tools/testing/selftests/drivers/ntsync/ntsync.c ++++ b/tools/testing/selftests/drivers/ntsync/ntsync.c +@@ -706,4 +706,102 @@ 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; ++ int objs[2], fd, ret; ++ __u32 count, index; ++ pthread_t thread; + -+ ret = wait_any(fd, 1, &mutex, 456, &index); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(0, index); -+ check_mutex_state(mutex, 1, 456); ++ fd = open("/dev/ntsync", O_CLOEXEC | O_RDONLY); ++ ASSERT_LE(0, fd); + -+ ret = wait_any(fd, 1, &mutex, 456, &index); ++ 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_EQ(0, index); -+ check_mutex_state(mutex, 2, 456); ++ EXPECT_NE(0xdeadbeef, sem_args.sem); + -+ ret = unlock_mutex(mutex, 456, &count); ++ 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_EQ(2, count); -+ check_mutex_state(mutex, 1, 456); ++ EXPECT_NE(0xdeadbeef, mutex_args.mutex); + -+ ret = wait_any(fd, 1, &mutex, 123, &index); -+ EXPECT_EQ(-1, ret); -+ EXPECT_EQ(ETIMEDOUT, errno); ++ objs[0] = sem_args.sem; ++ objs[1] = mutex_args.mutex; + -+ owner = 0; -+ ret = ioctl(mutex, NTSYNC_IOC_MUTEX_KILL, &owner); -+ EXPECT_EQ(-1, ret); -+ EXPECT_EQ(EINVAL, errno); ++ wait_args.timeout = get_abs_timeout(1000); ++ 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); + -+ owner = 123; -+ ret = ioctl(mutex, NTSYNC_IOC_MUTEX_KILL, &owner); -+ EXPECT_EQ(-1, ret); -+ EXPECT_EQ(EPERM, errno); -+ check_mutex_state(mutex, 1, 456); ++ ret = wait_for_thread(thread, 100); ++ EXPECT_EQ(ETIMEDOUT, ret); + -+ owner = 456; -+ ret = ioctl(mutex, NTSYNC_IOC_MUTEX_KILL, &owner); ++ count = 1; ++ ret = post_sem(sem_args.sem, &count); + EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, count); + -+ memset(&mutex_args, 0xcc, sizeof(mutex_args)); -+ ret = ioctl(mutex, NTSYNC_IOC_MUTEX_READ, &mutex_args); -+ EXPECT_EQ(-1, ret); -+ EXPECT_EQ(EOWNERDEAD, errno); -+ EXPECT_EQ(0, mutex_args.count); -+ EXPECT_EQ(0, mutex_args.owner); ++ ret = pthread_tryjoin_np(thread, NULL); ++ EXPECT_EQ(EBUSY, ret); + -+ memset(&mutex_args, 0xcc, sizeof(mutex_args)); -+ ret = ioctl(mutex, NTSYNC_IOC_MUTEX_READ, &mutex_args); -+ EXPECT_EQ(-1, ret); -+ EXPECT_EQ(EOWNERDEAD, errno); -+ EXPECT_EQ(0, mutex_args.count); -+ EXPECT_EQ(0, mutex_args.owner); ++ check_sem_state(sem_args.sem, 1, 3); + -+ ret = wait_any(fd, 1, &mutex, 123, &index); -+ EXPECT_EQ(-1, ret); -+ EXPECT_EQ(EOWNERDEAD, errno); ++ ret = wait_any(fd, 1, &sem_args.sem, 123, &index); ++ EXPECT_EQ(0, ret); + EXPECT_EQ(0, index); -+ check_mutex_state(mutex, 1, 123); + -+ owner = 123; -+ ret = ioctl(mutex, NTSYNC_IOC_MUTEX_KILL, &owner); ++ ret = unlock_mutex(mutex_args.mutex, 123, &count); + EXPECT_EQ(0, ret); ++ EXPECT_EQ(1, count); + -+ memset(&mutex_args, 0xcc, sizeof(mutex_args)); -+ ret = ioctl(mutex, NTSYNC_IOC_MUTEX_READ, &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(mutex, 1, 123); ++ ret = pthread_tryjoin_np(thread, NULL); ++ EXPECT_EQ(EBUSY, ret); + -+ close(mutex); ++ check_mutex_state(mutex_args.mutex, 0, 0); + -+ mutex_args.owner = 0; -+ mutex_args.count = 0; -+ mutex_args.mutex = 0xdeadbeef; -+ ret = ioctl(fd, NTSYNC_IOC_CREATE_MUTEX, &mutex_args); ++ count = 2; ++ ret = post_sem(sem_args.sem, &count); + EXPECT_EQ(0, ret); -+ EXPECT_NE(0xdeadbeef, mutex_args.mutex); -+ mutex = mutex_args.mutex; -+ check_mutex_state(mutex, 0, 0); ++ EXPECT_EQ(0, count); ++ check_sem_state(sem_args.sem, 1, 3); ++ check_mutex_state(mutex_args.mutex, 1, 456); + -+ ret = wait_any(fd, 1, &mutex, 123, &index); ++ ret = wait_for_thread(thread, 100); + EXPECT_EQ(0, ret); -+ EXPECT_EQ(0, index); -+ check_mutex_state(mutex, 1, 123); ++ EXPECT_EQ(0, thread_args.ret); + -+ close(mutex); ++ /* delete an object while it's being waited on */ + -+ mutex_args.owner = 123; -+ mutex_args.count = ~0u; -+ mutex_args.mutex = 0xdeadbeef; -+ ret = ioctl(fd, NTSYNC_IOC_CREATE_MUTEX, &mutex_args); ++ wait_args.timeout = get_abs_timeout(200); ++ wait_args.owner = 123; ++ ret = pthread_create(&thread, NULL, wait_thread, &thread_args); + EXPECT_EQ(0, ret); -+ EXPECT_NE(0xdeadbeef, mutex_args.mutex); -+ mutex = mutex_args.mutex; -+ check_mutex_state(mutex, ~0u, 123); + -+ ret = wait_any(fd, 1, &mutex, 123, &index); -+ EXPECT_EQ(-1, ret); -+ EXPECT_EQ(ETIMEDOUT, errno); ++ ret = wait_for_thread(thread, 100); ++ EXPECT_EQ(ETIMEDOUT, ret); + -+ close(mutex); ++ close(sem_args.sem); ++ close(mutex_args.mutex); ++ ++ 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.44.0 + + +From 9ff68a61d9d128deb616a463e38486f0b37a8228 Mon Sep 17 00:00:00 2001 +From: Elizabeth Figura +Date: Wed, 19 Jan 2022 19:34:47 -0600 +Subject: [PATCH 23/30] selftests: ntsync: Add some tests for manual-reset + event state. + +Test event-specific ioctls NTSYNC_IOC_EVENT_SET, NTSYNC_IOC_EVENT_RESET, +NTSYNC_IOC_EVENT_PULSE, NTSYNC_IOC_EVENT_READ for manual-reset events, and +waiting on manual-reset events. +--- + .../testing/selftests/drivers/ntsync/ntsync.c | 89 +++++++++++++++++++ + 1 file changed, 89 insertions(+) + +diff --git a/tools/testing/selftests/drivers/ntsync/ntsync.c b/tools/testing/selftests/drivers/ntsync/ntsync.c +index b77fb0b2c4b1..b6481c2b85cc 100644 +--- a/tools/testing/selftests/drivers/ntsync/ntsync.c ++++ b/tools/testing/selftests/drivers/ntsync/ntsync.c +@@ -73,6 +73,27 @@ static int unlock_mutex(int mutex, __u32 owner, __u32 *count) + return ret; + } + ++static int read_event_state(int event, __u32 *signaled, __u32 *manual) ++{ ++ struct ntsync_event_args args; ++ int ret; ++ ++ memset(&args, 0xcc, sizeof(args)); ++ ret = ioctl(event, NTSYNC_IOC_EVENT_READ, &args); ++ *signaled = args.signaled; ++ *manual = args.manual; ++ return ret; ++} ++ ++#define check_event_state(event, signaled, manual) \ ++ ({ \ ++ __u32 __signaled, __manual; \ ++ int ret = read_event_state((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 int *objs, __u32 owner, __u32 *index) + { +@@ -353,6 +374,74 @@ TEST(mutex_state) + close(fd); + } + +TEST(manual_event_state) +{ + struct ntsync_event_args event_args; @@ -2225,6 +3672,34 @@ index 000000000000..aba513c9af01 + close(fd); +} + + TEST(test_wait_any) + { + int objs[NTSYNC_MAX_WAIT_COUNT + 1], fd, ret; +-- +2.44.0 + + +From 9890fb52d924fa1ab8d62b7ca875ec071f2f4a94 Mon Sep 17 00:00:00 2001 +From: Elizabeth Figura +Date: Wed, 19 Jan 2022 19:45:39 -0600 +Subject: [PATCH 24/30] selftests: ntsync: Add some tests for auto-reset event + state. + +Test event-specific ioctls NTSYNC_IOC_EVENT_SET, NTSYNC_IOC_EVENT_RESET, +NTSYNC_IOC_EVENT_PULSE, NTSYNC_IOC_EVENT_READ for auto-reset events, and +waiting on auto-reset events. +--- + .../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 b6481c2b85cc..12ccb4ec28e4 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; @@ -2284,405 +3759,553 @@ index 000000000000..aba513c9af01 + close(fd); +} + -+TEST(test_wait_any) -+{ -+ int objs[NTSYNC_MAX_WAIT_COUNT + 1], fd, ret; -+ struct ntsync_mutex_args mutex_args = {0}; -+ struct ntsync_sem_args sem_args = {0}; -+ __u32 owner, index, count, i; -+ struct timespec timeout; -+ -+ 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(sem_args.sem, 1, 3); -+ check_mutex_state(mutex_args.mutex, 0, 0); -+ -+ ret = wait_any(fd, 2, objs, 123, &index); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(0, index); -+ check_sem_state(sem_args.sem, 0, 3); -+ check_mutex_state(mutex_args.mutex, 0, 0); -+ -+ ret = wait_any(fd, 2, objs, 123, &index); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(1, index); -+ check_sem_state(sem_args.sem, 0, 3); -+ check_mutex_state(mutex_args.mutex, 1, 123); -+ -+ count = 1; -+ ret = post_sem(sem_args.sem, &count); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(0, count); -+ -+ ret = wait_any(fd, 2, objs, 123, &index); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(0, index); -+ check_sem_state(sem_args.sem, 0, 3); -+ check_mutex_state(mutex_args.mutex, 1, 123); -+ -+ ret = wait_any(fd, 2, objs, 123, &index); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(1, index); -+ check_sem_state(sem_args.sem, 0, 3); -+ check_mutex_state(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(mutex_args.mutex, NTSYNC_IOC_MUTEX_KILL, &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 */ -+ count = 2; -+ ret = post_sem(sem_args.sem, &count); + TEST(test_wait_any) + { + int objs[NTSYNC_MAX_WAIT_COUNT + 1], fd, ret; +-- +2.44.0 + + +From e4bb0c53c71cbddb5270b59cadf790172ba23c0e Mon Sep 17 00:00:00 2001 +From: Elizabeth Figura +Date: Wed, 19 Jan 2022 21:00:50 -0600 +Subject: [PATCH 25/30] selftests: ntsync: Add some tests for wakeup signaling + with events. + +Expand the contended wait tests, which previously only covered events and +semaphores, to cover events as well. +--- + .../testing/selftests/drivers/ntsync/ntsync.c | 151 +++++++++++++++++- + 1 file changed, 147 insertions(+), 4 deletions(-) + +diff --git a/tools/testing/selftests/drivers/ntsync/ntsync.c b/tools/testing/selftests/drivers/ntsync/ntsync.c +index 12ccb4ec28e4..5d17eff6a370 100644 +--- a/tools/testing/selftests/drivers/ntsync/ntsync.c ++++ b/tools/testing/selftests/drivers/ntsync/ntsync.c +@@ -622,6 +622,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 owner, index, count; +@@ -644,6 +645,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); -+ EXPECT_EQ(0, count); + -+ objs[0] = objs[1] = sem_args.sem; -+ ret = wait_any(fd, 2, objs, 456, &index); + objs[0] = sem_args.sem; + objs[1] = mutex_args.mutex; + +@@ -692,6 +698,14 @@ TEST(test_wait_all) + check_sem_state(sem_args.sem, 1, 3); + check_mutex_state(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(sem_args.sem, 1, 3); -+ -+ ret = wait_any(fd, 0, NULL, 456, &index); -+ EXPECT_EQ(-1, ret); -+ EXPECT_EQ(ETIMEDOUT, errno); ++ check_sem_state(sem_args.sem, 0, 3); ++ check_event_state(event_args.event, 1, 1); + -+ for (i = 0; i < NTSYNC_MAX_WAIT_COUNT + 1; ++i) -+ objs[i] = sem_args.sem; + /* test waiting on the same object twice */ + objs[0] = objs[1] = sem_args.sem; + ret = wait_all(fd, 2, objs, 123, &index); +@@ -700,6 +714,7 @@ TEST(test_wait_all) + + close(sem_args.sem); + close(mutex_args.mutex); ++ close(event_args.event); + + close(fd); + } +@@ -746,12 +761,13 @@ 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}; + struct wait_args thread_args; ++ __u32 count, index, signaled; + int objs[2], fd, ret; +- __u32 count, index; + pthread_t thread; + + fd = open("/dev/ntsync", O_CLOEXEC | O_RDONLY); +@@ -833,10 +849,101 @@ TEST(wake_any) + EXPECT_EQ(0, thread_args.ret); + EXPECT_EQ(1, wait_args.index); + ++ /* test waking events */ + -+ ret = wait_any(fd, NTSYNC_MAX_WAIT_COUNT, objs, 123, &index); ++ event_args.manual = false; ++ event_args.signaled = false; ++ ret = ioctl(fd, NTSYNC_IOC_CREATE_EVENT, &event_args); + EXPECT_EQ(0, ret); -+ EXPECT_EQ(0, index); + -+ ret = wait_any(fd, NTSYNC_MAX_WAIT_COUNT + 1, objs, 123, &index); -+ EXPECT_EQ(-1, ret); -+ EXPECT_EQ(EINVAL, errno); ++ objs[1] = event_args.event; ++ wait_args.timeout = get_abs_timeout(1000); ++ ret = pthread_create(&thread, NULL, wait_thread, &thread_args); ++ EXPECT_EQ(0, ret); + -+ ret = wait_any(fd, -1, objs, 123, &index); -+ EXPECT_EQ(-1, ret); -+ EXPECT_EQ(EINVAL, errno); ++ ret = wait_for_thread(thread, 100); ++ EXPECT_EQ(ETIMEDOUT, ret); + -+ close(sem_args.sem); -+ close(mutex_args.mutex); ++ ret = ioctl(event_args.event, NTSYNC_IOC_EVENT_SET, &signaled); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, signaled); ++ check_event_state(event_args.event, 0, 0); + -+ close(fd); -+} ++ ret = wait_for_thread(thread, 100); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, thread_args.ret); ++ EXPECT_EQ(1, wait_args.index); + -+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 owner, index, count; -+ int objs[2], fd, ret; ++ wait_args.timeout = get_abs_timeout(1000); ++ ret = pthread_create(&thread, NULL, wait_thread, &thread_args); ++ EXPECT_EQ(0, ret); + -+ fd = open("/dev/ntsync", O_CLOEXEC | O_RDONLY); -+ ASSERT_LE(0, fd); ++ ret = wait_for_thread(thread, 100); ++ EXPECT_EQ(ETIMEDOUT, ret); + -+ sem_args.count = 2; -+ sem_args.max = 3; -+ sem_args.sem = 0xdeadbeef; -+ ret = ioctl(fd, NTSYNC_IOC_CREATE_SEM, &sem_args); ++ ret = ioctl(event_args.event, NTSYNC_IOC_EVENT_PULSE, &signaled); + EXPECT_EQ(0, ret); -+ EXPECT_NE(0xdeadbeef, sem_args.sem); ++ EXPECT_EQ(0, signaled); ++ check_event_state(event_args.event, 0, 0); + -+ mutex_args.owner = 0; -+ mutex_args.count = 0; -+ mutex_args.mutex = 0xdeadbeef; -+ ret = ioctl(fd, NTSYNC_IOC_CREATE_MUTEX, &mutex_args); ++ ret = wait_for_thread(thread, 100); + EXPECT_EQ(0, ret); -+ EXPECT_NE(0xdeadbeef, mutex_args.mutex); ++ EXPECT_EQ(0, thread_args.ret); ++ EXPECT_EQ(1, wait_args.index); ++ ++ close(event_args.event); + + event_args.manual = true; -+ event_args.signaled = true; ++ event_args.signaled = false; + ret = ioctl(fd, NTSYNC_IOC_CREATE_EVENT, &event_args); + EXPECT_EQ(0, ret); + -+ objs[0] = sem_args.sem; -+ objs[1] = mutex_args.mutex; -+ -+ ret = wait_all(fd, 2, objs, 123, &index); ++ objs[1] = event_args.event; ++ wait_args.timeout = get_abs_timeout(1000); ++ ret = pthread_create(&thread, NULL, wait_thread, &thread_args); + EXPECT_EQ(0, ret); -+ EXPECT_EQ(0, index); -+ check_sem_state(sem_args.sem, 1, 3); -+ check_mutex_state(mutex_args.mutex, 1, 123); + -+ ret = wait_all(fd, 2, objs, 456, &index); -+ EXPECT_EQ(-1, ret); -+ EXPECT_EQ(ETIMEDOUT, errno); -+ check_sem_state(sem_args.sem, 1, 3); -+ check_mutex_state(mutex_args.mutex, 1, 123); ++ ret = wait_for_thread(thread, 100); ++ EXPECT_EQ(ETIMEDOUT, ret); + -+ ret = wait_all(fd, 2, objs, 123, &index); ++ ret = ioctl(event_args.event, NTSYNC_IOC_EVENT_SET, &signaled); + EXPECT_EQ(0, ret); -+ EXPECT_EQ(0, index); -+ check_sem_state(sem_args.sem, 0, 3); -+ check_mutex_state(mutex_args.mutex, 2, 123); -+ -+ ret = wait_all(fd, 2, objs, 123, &index); -+ EXPECT_EQ(-1, ret); -+ EXPECT_EQ(ETIMEDOUT, errno); -+ check_sem_state(sem_args.sem, 0, 3); -+ check_mutex_state(mutex_args.mutex, 2, 123); ++ EXPECT_EQ(0, signaled); ++ check_event_state(event_args.event, 1, 1); + -+ count = 3; -+ ret = post_sem(sem_args.sem, &count); ++ ret = wait_for_thread(thread, 100); + EXPECT_EQ(0, ret); -+ EXPECT_EQ(0, count); ++ EXPECT_EQ(0, thread_args.ret); ++ EXPECT_EQ(1, wait_args.index); + -+ ret = wait_all(fd, 2, objs, 123, &index); ++ ret = ioctl(event_args.event, NTSYNC_IOC_EVENT_RESET, &signaled); + EXPECT_EQ(0, ret); -+ EXPECT_EQ(0, index); -+ check_sem_state(sem_args.sem, 2, 3); -+ check_mutex_state(mutex_args.mutex, 3, 123); ++ EXPECT_EQ(1, signaled); + -+ owner = 123; -+ ret = ioctl(mutex_args.mutex, NTSYNC_IOC_MUTEX_KILL, &owner); ++ wait_args.timeout = get_abs_timeout(1000); ++ ret = pthread_create(&thread, NULL, wait_thread, &thread_args); + EXPECT_EQ(0, ret); + -+ ret = wait_all(fd, 2, objs, 123, &index); -+ EXPECT_EQ(-1, ret); -+ EXPECT_EQ(EOWNERDEAD, errno); -+ check_sem_state(sem_args.sem, 1, 3); -+ check_mutex_state(mutex_args.mutex, 1, 123); ++ ret = wait_for_thread(thread, 100); ++ EXPECT_EQ(ETIMEDOUT, ret); + -+ objs[0] = sem_args.sem; -+ objs[1] = event_args.event; -+ ret = wait_all(fd, 2, objs, 123, &index); ++ ret = ioctl(event_args.event, NTSYNC_IOC_EVENT_PULSE, &signaled); + EXPECT_EQ(0, ret); -+ EXPECT_EQ(0, index); -+ check_sem_state(sem_args.sem, 0, 3); -+ check_event_state(event_args.event, 1, 1); ++ EXPECT_EQ(0, signaled); ++ check_event_state(event_args.event, 0, 1); + -+ /* 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 = wait_for_thread(thread, 100); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, thread_args.ret); ++ EXPECT_EQ(1, wait_args.index); + -+ close(sem_args.sem); -+ close(mutex_args.mutex); + close(event_args.event); + -+ close(fd); -+} + /* delete an object while it's being waited on */ + + wait_args.timeout = get_abs_timeout(200); + wait_args.owner = 123; ++ objs[1] = mutex_args.mutex; + ret = pthread_create(&thread, NULL, wait_thread, &thread_args); + EXPECT_EQ(0, ret); + +@@ -856,12 +963,14 @@ 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; +- int objs[2], fd, ret; +- __u32 count, index; ++ __u32 count, index, signaled; ++ int objs[4], fd, ret; + pthread_t thread; + + fd = open("/dev/ntsync", O_CLOEXEC | O_RDONLY); +@@ -881,12 +990,24 @@ 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); + -+struct wake_args { -+ int fd; -+ int obj; -+}; ++ auto_event_args.manual = false; ++ auto_event_args.signaled = true; ++ ret = ioctl(fd, NTSYNC_IOC_CREATE_EVENT, &auto_event_args); ++ EXPECT_EQ(0, ret); + -+struct wait_args { -+ int fd; -+ unsigned long request; -+ struct ntsync_wait_args *args; -+ int ret; -+ int err; -+}; + objs[0] = sem_args.sem; + objs[1] = mutex_args.mutex; ++ objs[2] = manual_event_args.event; ++ objs[3] = auto_event_args.event; + + wait_args.timeout = get_abs_timeout(1000); + 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; +@@ -920,12 +1041,32 @@ TEST(wake_all) + + check_mutex_state(mutex_args.mutex, 0, 0); + ++ ret = ioctl(manual_event_args.event, NTSYNC_IOC_EVENT_RESET, &signaled); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(1, signaled); + -+static void *wait_thread(void *arg) -+{ -+ struct wait_args *args = arg; + count = 2; + ret = post_sem(sem_args.sem, &count); + EXPECT_EQ(0, ret); + EXPECT_EQ(0, count); ++ check_sem_state(sem_args.sem, 2, 3); + -+ args->ret = ioctl(args->fd, args->request, args->args); -+ args->err = errno; -+ return NULL; -+} ++ ret = ioctl(auto_event_args.event, NTSYNC_IOC_EVENT_RESET, &signaled); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(1, signaled); + -+static __u64 get_abs_timeout(unsigned int ms) -+{ -+ struct timespec timeout; -+ clock_gettime(CLOCK_MONOTONIC, &timeout); -+ return (timeout.tv_sec * 1000000000) + timeout.tv_nsec + (ms * 1000000); ++ ret = ioctl(manual_event_args.event, NTSYNC_IOC_EVENT_SET, &signaled); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, signaled); ++ ++ ret = ioctl(auto_event_args.event, NTSYNC_IOC_EVENT_SET, &signaled); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, signaled); ++ + check_sem_state(sem_args.sem, 1, 3); + check_mutex_state(mutex_args.mutex, 1, 456); ++ check_event_state(manual_event_args.event, 1, 1); ++ check_event_state(auto_event_args.event, 0, 0); + + ret = wait_for_thread(thread, 100); + EXPECT_EQ(0, ret); +@@ -943,6 +1084,8 @@ TEST(wake_all) + + close(sem_args.sem); + close(mutex_args.mutex); ++ close(manual_event_args.event); ++ close(auto_event_args.event); + + ret = wait_for_thread(thread, 200); + EXPECT_EQ(0, ret); +-- +2.44.0 + + +From 6eaed48903879da9d3676767f5501a895752ae39 Mon Sep 17 00:00:00 2001 +From: Elizabeth Figura +Date: Wed, 20 Apr 2022 18:08:37 -0500 +Subject: [PATCH 26/30] selftests: ntsync: Add tests for alertable waits. + +Test the "alert" functionality of NTSYNC_IOC_WAIT_ALL and NTSYNC_IOC_WAIT_ANY, +when a wait is woken with an alert and when it is woken by an object. +--- + .../testing/selftests/drivers/ntsync/ntsync.c | 179 +++++++++++++++++- + 1 file changed, 176 insertions(+), 3 deletions(-) + +diff --git a/tools/testing/selftests/drivers/ntsync/ntsync.c b/tools/testing/selftests/drivers/ntsync/ntsync.c +index 5d17eff6a370..5465a16d38b3 100644 +--- a/tools/testing/selftests/drivers/ntsync/ntsync.c ++++ b/tools/testing/selftests/drivers/ntsync/ntsync.c +@@ -95,7 +95,7 @@ static int read_event_state(int event, __u32 *signaled, __u32 *manual) + }) + + static int wait_objs(int fd, unsigned long request, __u32 count, +- const int *objs, __u32 owner, __u32 *index) ++ const int *objs, __u32 owner, int alert, __u32 *index) + { + struct ntsync_wait_args args = {0}; + struct timespec timeout; +@@ -108,6 +108,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; +@@ -115,12 +116,26 @@ static int wait_objs(int fd, unsigned long request, __u32 count, + + static int wait_any(int fd, __u32 count, const int *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 int *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_for_thread(pthread_t thread, unsigned int ms) ++static int wait_any_alert(int fd, __u32 count, const int *objs, ++ __u32 owner, int alert, __u32 *index) +{ -+ struct timespec timeout; -+ -+ clock_gettime(CLOCK_REALTIME, &timeout); -+ timeout.tv_nsec += ms * 1000000; -+ timeout.tv_sec += (timeout.tv_nsec / 1000000000); -+ timeout.tv_nsec %= 1000000000; -+ return pthread_timedjoin_np(thread, NULL, &timeout); ++ return wait_objs(fd, NTSYNC_IOC_WAIT_ANY, ++ count, objs, owner, alert, index); +} + -+TEST(wake_any) ++static int wait_all_alert(int fd, __u32 count, const int *objs, ++ __u32 owner, int alert, __u32 *index) ++{ ++ return wait_objs(fd, NTSYNC_IOC_WAIT_ALL, ++ count, objs, owner, alert, index); + } + + TEST(semaphore_state) +@@ -1095,4 +1110,162 @@ TEST(wake_all) + close(fd); + } + ++TEST(alert_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}; -+ struct wait_args thread_args; -+ __u32 count, index, signaled; ++ __u32 index, count, signaled; + int objs[2], fd, ret; -+ pthread_t thread; + + fd = open("/dev/ntsync", O_CLOEXEC | O_RDONLY); + ASSERT_LE(0, fd); + + sem_args.count = 0; -+ sem_args.max = 3; ++ 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; + -+ mutex_args.owner = 123; -+ mutex_args.count = 1; -+ mutex_args.mutex = 0xdeadbeef; -+ ret = ioctl(fd, NTSYNC_IOC_CREATE_MUTEX, &mutex_args); ++ 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, mutex_args.mutex); ++ EXPECT_NE(0xdeadbeef, sem_args.sem); ++ objs[1] = sem_args.sem; + -+ objs[0] = sem_args.sem; -+ objs[1] = mutex_args.mutex; ++ event_args.manual = true; ++ event_args.signaled = true; ++ ret = ioctl(fd, NTSYNC_IOC_CREATE_EVENT, &event_args); ++ EXPECT_EQ(0, ret); + -+ /* test waking the semaphore */ ++ ret = wait_any_alert(fd, 0, NULL, 123, event_args.event, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, index); + -+ wait_args.timeout = get_abs_timeout(1000); -+ 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); ++ ret = ioctl(event_args.event, NTSYNC_IOC_EVENT_RESET, &signaled); + EXPECT_EQ(0, ret); + -+ ret = wait_for_thread(thread, 100); -+ EXPECT_EQ(ETIMEDOUT, ret); ++ ret = wait_any_alert(fd, 0, NULL, 123, event_args.event, &index); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(ETIMEDOUT, errno); + -+ count = 1; -+ ret = post_sem(sem_args.sem, &count); ++ ret = ioctl(event_args.event, NTSYNC_IOC_EVENT_SET, &signaled); + EXPECT_EQ(0, ret); -+ EXPECT_EQ(0, count); -+ check_sem_state(sem_args.sem, 0, 3); + -+ ret = wait_for_thread(thread, 100); ++ ret = wait_any_alert(fd, 2, objs, 123, event_args.event, &index); + EXPECT_EQ(0, ret); -+ EXPECT_EQ(0, thread_args.ret); -+ EXPECT_EQ(0, wait_args.index); -+ -+ /* test waking the mutex */ ++ EXPECT_EQ(1, index); + -+ /* first grab it again for owner 123 */ -+ ret = wait_any(fd, 1, &mutex_args.mutex, 123, &index); ++ ret = wait_any_alert(fd, 2, objs, 123, event_args.event, &index); + EXPECT_EQ(0, ret); -+ EXPECT_EQ(0, index); ++ EXPECT_EQ(2, index); + -+ wait_args.timeout = get_abs_timeout(1000); -+ wait_args.owner = 456; -+ ret = pthread_create(&thread, NULL, wait_thread, &thread_args); -+ EXPECT_EQ(0, ret); ++ close(event_args.event); + -+ ret = wait_for_thread(thread, 100); -+ EXPECT_EQ(ETIMEDOUT, ret); ++ /* test with an auto-reset event */ + -+ ret = unlock_mutex(mutex_args.mutex, 123, &count); ++ event_args.manual = false; ++ event_args.signaled = true; ++ ret = ioctl(fd, NTSYNC_IOC_CREATE_EVENT, &event_args); + EXPECT_EQ(0, ret); -+ EXPECT_EQ(2, count); + -+ ret = pthread_tryjoin_np(thread, NULL); -+ EXPECT_EQ(EBUSY, ret); ++ count = 1; ++ ret = post_sem(objs[0], &count); ++ EXPECT_EQ(0, ret); + -+ ret = unlock_mutex(mutex_args.mutex, 123, &count); ++ ret = wait_any_alert(fd, 2, objs, 123, event_args.event, &index); + EXPECT_EQ(0, ret); -+ EXPECT_EQ(1, mutex_args.count); -+ check_mutex_state(mutex_args.mutex, 1, 456); ++ EXPECT_EQ(0, index); + -+ ret = wait_for_thread(thread, 100); ++ ret = wait_any_alert(fd, 2, objs, 123, event_args.event, &index); + EXPECT_EQ(0, ret); -+ EXPECT_EQ(0, thread_args.ret); -+ EXPECT_EQ(1, wait_args.index); ++ EXPECT_EQ(2, index); + -+ /* test waking events */ ++ ret = wait_any_alert(fd, 2, objs, 123, event_args.event, &index); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(ETIMEDOUT, errno); + -+ event_args.manual = false; -+ event_args.signaled = false; -+ ret = ioctl(fd, NTSYNC_IOC_CREATE_EVENT, &event_args); ++ close(event_args.event); ++ ++ close(objs[0]); ++ close(objs[1]); ++ ++ close(fd); ++} ++ ++TEST(alert_all) ++{ ++ struct ntsync_event_args event_args = {0}; ++ struct ntsync_sem_args sem_args = {0}; ++ __u32 index, count, signaled; ++ int objs[2], 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; + -+ objs[1] = event_args.event; -+ wait_args.timeout = get_abs_timeout(1000); -+ ret = pthread_create(&thread, NULL, wait_thread, &thread_args); ++ 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; + -+ ret = wait_for_thread(thread, 100); -+ EXPECT_EQ(ETIMEDOUT, ret); ++ event_args.manual = true; ++ event_args.signaled = true; ++ ret = ioctl(fd, NTSYNC_IOC_CREATE_EVENT, &event_args); ++ EXPECT_EQ(0, ret); + -+ ret = ioctl(event_args.event, NTSYNC_IOC_EVENT_SET, &signaled); ++ ret = wait_all_alert(fd, 2, objs, 123, event_args.event, &index); + EXPECT_EQ(0, ret); -+ EXPECT_EQ(0, signaled); -+ check_event_state(event_args.event, 0, 0); ++ EXPECT_EQ(0, index); + -+ ret = wait_for_thread(thread, 100); ++ ret = wait_all_alert(fd, 2, objs, 123, event_args.event, &index); + EXPECT_EQ(0, ret); -+ EXPECT_EQ(0, thread_args.ret); -+ EXPECT_EQ(1, wait_args.index); ++ EXPECT_EQ(2, index); + -+ wait_args.timeout = get_abs_timeout(1000); -+ ret = pthread_create(&thread, NULL, wait_thread, &thread_args); ++ close(event_args.event); ++ ++ /* 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); + -+ ret = wait_for_thread(thread, 100); -+ EXPECT_EQ(ETIMEDOUT, ret); ++ count = 2; ++ ret = post_sem(objs[1], &count); ++ EXPECT_EQ(0, ret); + -+ ret = ioctl(event_args.event, NTSYNC_IOC_EVENT_PULSE, &signaled); ++ ret = wait_all_alert(fd, 2, objs, 123, event_args.event, &index); + EXPECT_EQ(0, ret); -+ EXPECT_EQ(0, signaled); -+ check_event_state(event_args.event, 0, 0); ++ EXPECT_EQ(0, index); + -+ ret = wait_for_thread(thread, 100); ++ ret = wait_all_alert(fd, 2, objs, 123, event_args.event, &index); + EXPECT_EQ(0, ret); -+ EXPECT_EQ(0, thread_args.ret); -+ EXPECT_EQ(1, wait_args.index); ++ EXPECT_EQ(2, index); ++ ++ ret = wait_all_alert(fd, 2, objs, 123, event_args.event, &index); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(ETIMEDOUT, errno); + + close(event_args.event); + -+ event_args.manual = true; -+ event_args.signaled = false; -+ ret = ioctl(fd, NTSYNC_IOC_CREATE_EVENT, &event_args); ++ close(objs[0]); ++ close(objs[1]); ++ ++ close(fd); ++} ++ + TEST_HARNESS_MAIN +-- +2.44.0 + + +From a1cc0ad80763ec9a8b17698ed754fdd311f00a1d Mon Sep 17 00:00:00 2001 +From: Elizabeth Figura +Date: Wed, 20 Apr 2022 18:24:43 -0500 +Subject: [PATCH 27/30] selftests: ntsync: Add some tests for wakeup signaling + via alerts. + +Expand the alert tests to cover alerting a thread mid-wait, to test that the +relevant scheduling logic works correctly. +--- + .../testing/selftests/drivers/ntsync/ntsync.c | 62 +++++++++++++++++++ + 1 file changed, 62 insertions(+) + +diff --git a/tools/testing/selftests/drivers/ntsync/ntsync.c b/tools/testing/selftests/drivers/ntsync/ntsync.c +index 5465a16d38b3..968874d7e325 100644 +--- a/tools/testing/selftests/drivers/ntsync/ntsync.c ++++ b/tools/testing/selftests/drivers/ntsync/ntsync.c +@@ -1113,9 +1113,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}; + __u32 index, count, signaled; ++ struct wait_args thread_args; + int objs[2], fd, ret; ++ pthread_t thread; + + fd = open("/dev/ntsync", O_CLOEXEC | O_RDONLY); + ASSERT_LE(0, fd); +@@ -1163,6 +1166,34 @@ TEST(alert_any) + EXPECT_EQ(0, ret); + EXPECT_EQ(2, index); + ++ /* test wakeup via alert */ ++ ++ ret = ioctl(event_args.event, NTSYNC_IOC_EVENT_RESET, &signaled); + EXPECT_EQ(0, ret); + -+ objs[1] = event_args.event; + wait_args.timeout = get_abs_timeout(1000); ++ 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); + @@ -2691,488 +4314,635 @@ index 000000000000..aba513c9af01 + + ret = ioctl(event_args.event, NTSYNC_IOC_EVENT_SET, &signaled); + EXPECT_EQ(0, ret); -+ EXPECT_EQ(0, signaled); -+ check_event_state(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); ++ EXPECT_EQ(2, wait_args.index); ++ + close(event_args.event); + + /* test with an auto-reset event */ +@@ -1199,9 +1230,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; + __u32 index, count, signaled; + int objs[2], fd, ret; ++ pthread_t thread; + + fd = open("/dev/ntsync", O_CLOEXEC | O_RDONLY); + ASSERT_LE(0, fd); +@@ -1235,6 +1269,34 @@ TEST(alert_all) + EXPECT_EQ(0, ret); + EXPECT_EQ(2, index); + ++ /* test wakeup via alert */ + + ret = ioctl(event_args.event, NTSYNC_IOC_EVENT_RESET, &signaled); + EXPECT_EQ(0, ret); -+ EXPECT_EQ(1, signaled); + + wait_args.timeout = get_abs_timeout(1000); ++ 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(event_args.event, NTSYNC_IOC_EVENT_PULSE, &signaled); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(0, signaled); -+ check_event_state(event_args.event, 0, 1); ++ ret = ioctl(event_args.event, NTSYNC_IOC_EVENT_SET, &signaled); ++ EXPECT_EQ(0, ret); + + ret = wait_for_thread(thread, 100); + EXPECT_EQ(0, ret); + EXPECT_EQ(0, thread_args.ret); -+ EXPECT_EQ(1, wait_args.index); ++ EXPECT_EQ(2, wait_args.index); + -+ close(event_args.event); + close(event_args.event); + + /* test with an auto-reset event */ +-- +2.44.0 + + +From 0bf047dadeaf42f7a4e55394a311a6da94edcb1e Mon Sep 17 00:00:00 2001 +From: Elizabeth Figura +Date: Tue, 13 Feb 2024 19:59:49 -0600 +Subject: [PATCH 28/30] selftests: ntsync: Add a stress test for contended + waits. + +Test a more realistic usage pattern, and one with heavy contention, in order to +actually exercise ntsync's internal synchronization. + +This test has several threads in a tight loop acquiring a mutex, modifying some +shared data, and then releasing the mutex. At the end we check if the data is +consistent. +--- + .../testing/selftests/drivers/ntsync/ntsync.c | 74 +++++++++++++++++++ + 1 file changed, 74 insertions(+) + +diff --git a/tools/testing/selftests/drivers/ntsync/ntsync.c b/tools/testing/selftests/drivers/ntsync/ntsync.c +index 968874d7e325..5fa2c9a0768c 100644 +--- a/tools/testing/selftests/drivers/ntsync/ntsync.c ++++ b/tools/testing/selftests/drivers/ntsync/ntsync.c +@@ -1330,4 +1330,78 @@ TEST(alert_all) + close(fd); + } + ++#define STRESS_LOOPS 10000 ++#define STRESS_THREADS 4 + -+ /* delete an object while it's being waited on */ ++static unsigned int stress_counter; ++static int stress_device, stress_start_event, stress_mutex; + -+ wait_args.timeout = get_abs_timeout(200); -+ wait_args.owner = 123; -+ objs[1] = mutex_args.mutex; -+ ret = pthread_create(&thread, NULL, wait_thread, &thread_args); -+ EXPECT_EQ(0, ret); ++static void *stress_thread(void *arg) ++{ ++ struct ntsync_wait_args wait_args = {0}; ++ __u32 index, count, i; ++ int ret; + -+ ret = wait_for_thread(thread, 100); -+ EXPECT_EQ(ETIMEDOUT, ret); ++ wait_args.timeout = UINT64_MAX; ++ wait_args.count = 1; ++ wait_args.objs = (uintptr_t)&stress_start_event; ++ wait_args.owner = gettid(); ++ wait_args.index = 0xdeadbeef; + -+ close(sem_args.sem); -+ close(mutex_args.mutex); ++ ioctl(stress_device, NTSYNC_IOC_WAIT_ANY, &wait_args); + -+ ret = wait_for_thread(thread, 200); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(-1, thread_args.ret); -+ EXPECT_EQ(ETIMEDOUT, thread_args.err); ++ wait_args.objs = (uintptr_t)&stress_mutex; + -+ close(fd); ++ for (i = 0; i < STRESS_LOOPS; ++i) { ++ ioctl(stress_device, NTSYNC_IOC_WAIT_ANY, &wait_args); ++ ++ ++stress_counter; ++ ++ unlock_mutex(stress_mutex, wait_args.owner, &count); ++ } ++ ++ return NULL; +} + -+TEST(wake_all) ++TEST(stress_wait) +{ -+ 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 count, index, signaled; -+ int objs[4], fd, ret; -+ pthread_t thread; ++ struct ntsync_event_args event_args; ++ struct ntsync_mutex_args mutex_args; ++ pthread_t threads[STRESS_THREADS]; ++ __u32 signaled, i; ++ int ret; + -+ fd = open("/dev/ntsync", O_CLOEXEC | O_RDONLY); -+ ASSERT_LE(0, fd); ++ stress_device = open("/dev/ntsync", O_CLOEXEC | O_RDONLY); ++ ASSERT_LE(0, stress_device); + -+ sem_args.count = 0; -+ sem_args.max = 3; -+ sem_args.sem = 0xdeadbeef; -+ ret = ioctl(fd, NTSYNC_IOC_CREATE_SEM, &sem_args); ++ mutex_args.owner = 0; ++ mutex_args.count = 0; ++ ret = ioctl(stress_device, NTSYNC_IOC_CREATE_MUTEX, &mutex_args); + EXPECT_EQ(0, ret); -+ EXPECT_NE(0xdeadbeef, sem_args.sem); ++ stress_mutex = mutex_args.mutex; + -+ mutex_args.owner = 123; -+ mutex_args.count = 1; -+ mutex_args.mutex = 0xdeadbeef; -+ ret = ioctl(fd, NTSYNC_IOC_CREATE_MUTEX, &mutex_args); ++ event_args.manual = 1; ++ event_args.signaled = 0; ++ ret = ioctl(stress_device, NTSYNC_IOC_CREATE_EVENT, &event_args); + EXPECT_EQ(0, ret); -+ EXPECT_NE(0xdeadbeef, mutex_args.mutex); ++ stress_start_event = event_args.event; + -+ manual_event_args.manual = true; -+ manual_event_args.signaled = true; -+ ret = ioctl(fd, NTSYNC_IOC_CREATE_EVENT, &manual_event_args); -+ EXPECT_EQ(0, ret); ++ for (i = 0; i < STRESS_THREADS; ++i) ++ pthread_create(&threads[i], NULL, stress_thread, NULL); + -+ auto_event_args.manual = false; -+ auto_event_args.signaled = true; -+ ret = ioctl(fd, NTSYNC_IOC_CREATE_EVENT, &auto_event_args); ++ ret = ioctl(stress_start_event, NTSYNC_IOC_EVENT_SET, &signaled); + 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; ++ for (i = 0; i < STRESS_THREADS; ++i) { ++ ret = pthread_join(threads[i], NULL); ++ EXPECT_EQ(0, ret); ++ } + -+ wait_args.timeout = get_abs_timeout(1000); -+ wait_args.objs = (uintptr_t)objs; -+ wait_args.count = 4; -+ 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); ++ EXPECT_EQ(STRESS_LOOPS * STRESS_THREADS, stress_counter); + -+ ret = wait_for_thread(thread, 100); -+ EXPECT_EQ(ETIMEDOUT, ret); ++ close(stress_start_event); ++ close(stress_mutex); ++ close(stress_device); ++} + -+ count = 1; -+ ret = post_sem(sem_args.sem, &count); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(0, count); + TEST_HARNESS_MAIN +-- +2.44.0 + + +From 3b3ccb123a9d07034e5c94e1d72b0f08bd29d05c Mon Sep 17 00:00:00 2001 +From: Elizabeth Figura +Date: Fri, 5 Mar 2021 12:22:55 -0600 +Subject: [PATCH 29/30] maintainers: Add an entry for ntsync. + +Add myself as maintainer, supported by CodeWeavers. +--- + MAINTAINERS | 9 +++++++++ + 1 file changed, 9 insertions(+) + +diff --git a/MAINTAINERS b/MAINTAINERS +index ec0284125e8f..2f4be32edc17 100644 +--- a/MAINTAINERS ++++ b/MAINTAINERS +@@ -15720,6 +15720,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 ++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/ + -+ ret = pthread_tryjoin_np(thread, NULL); -+ EXPECT_EQ(EBUSY, ret); + NUBUS SUBSYSTEM + M: Finn Thain + L: linux-m68k@lists.linux-m68k.org +-- +2.44.0 + + +From cd6bd9c2c55b19e0e699e1eba30e313bb936a25e Mon Sep 17 00:00:00 2001 +From: Elizabeth Figura +Date: Fri, 5 Mar 2021 11:50:49 -0600 +Subject: [PATCH 30/30] docs: ntsync: Add documentation for the ntsync uAPI. + +Add an overall explanation of the driver architecture, and complete and precise +specification for its intended behaviour. + +Reviewed-by: Bagas Sanjaya +--- + Documentation/userspace-api/index.rst | 1 + + Documentation/userspace-api/ntsync.rst | 399 +++++++++++++++++++++++++ + 2 files changed, 400 insertions(+) + create mode 100644 Documentation/userspace-api/ntsync.rst + +diff --git a/Documentation/userspace-api/index.rst b/Documentation/userspace-api/index.rst +index afecfe3cc4a8..d5745a500fa7 100644 +--- a/Documentation/userspace-api/index.rst ++++ b/Documentation/userspace-api/index.rst +@@ -62,6 +62,7 @@ Everything else + vduse + futex2 + perf_ring_buffer ++ 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..202c2350d3af +--- /dev/null ++++ b/Documentation/userspace-api/ntsync.rst +@@ -0,0 +1,399 @@ ++=================================== ++NT synchronization primitive driver ++=================================== + -+ check_sem_state(sem_args.sem, 1, 3); ++This page documents the user-space API for the ntsync driver. + -+ ret = wait_any(fd, 1, &sem_args.sem, 123, &index); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(0, index); ++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. + -+ ret = unlock_mutex(mutex_args.mutex, 123, &count); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(1, count); ++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). + -+ ret = pthread_tryjoin_np(thread, NULL); -+ EXPECT_EQ(EBUSY, ret); ++Synchronization primitives ++========================== + -+ check_mutex_state(mutex_args.mutex, 0, 0); ++The ntsync driver exposes three types of synchronization primitives: ++semaphores, mutexes, and events. + -+ ret = ioctl(manual_event_args.event, NTSYNC_IOC_EVENT_RESET, &signaled); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(1, signaled); ++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. + -+ count = 2; -+ ret = post_sem(sem_args.sem, &count); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(0, count); -+ check_sem_state(sem_args.sem, 2, 3); ++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. + -+ ret = ioctl(auto_event_args.event, NTSYNC_IOC_EVENT_RESET, &signaled); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(1, signaled); ++A mutex also holds an internal flag denoting whether its previous owner ++has died; such a mutex is said to be abandoned. Owner death is not ++tracked automatically based on thread death, but rather must be ++communicated using ``NTSYNC_IOC_MUTEX_KILL``. An abandoned mutex is ++inherently considered unowned. + -+ ret = ioctl(manual_event_args.event, NTSYNC_IOC_EVENT_SET, &signaled); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(0, signaled); ++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. + -+ ret = ioctl(auto_event_args.event, NTSYNC_IOC_EVENT_SET, &signaled); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(0, signaled); ++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. + -+ check_sem_state(sem_args.sem, 1, 3); -+ check_mutex_state(mutex_args.mutex, 1, 456); -+ check_event_state(manual_event_args.event, 1, 1); -+ check_event_state(auto_event_args.event, 0, 0); ++Unless specified otherwise, all operations on an object are atomic and ++totally ordered with respect to other operations on the same object. + -+ ret = wait_for_thread(thread, 100); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(0, thread_args.ret); ++Objects are represented by files. When all file descriptors to an ++object are closed, that object is deleted. + -+ /* delete an object while it's being waited on */ ++Char device ++=========== + -+ wait_args.timeout = get_abs_timeout(200); -+ wait_args.owner = 123; -+ ret = pthread_create(&thread, NULL, wait_thread, &thread_args); -+ EXPECT_EQ(0, ret); ++The ntsync driver creates a single char device /dev/ntsync. Each file ++description opened on the device represents a unique instance intended ++to back an individual NT virtual machine. Objects created by one ntsync ++instance may only be used with other objects created by the same ++instance. + -+ ret = wait_for_thread(thread, 100); -+ EXPECT_EQ(ETIMEDOUT, ret); ++ioctl reference ++=============== + -+ close(sem_args.sem); -+ close(mutex_args.mutex); -+ close(manual_event_args.event); -+ close(auto_event_args.event); ++All operations on the device are done through ioctls. There are four ++structures used in ioctl calls:: + -+ ret = wait_for_thread(thread, 200); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(-1, thread_args.ret); -+ EXPECT_EQ(ETIMEDOUT, thread_args.err); ++ struct ntsync_sem_args { ++ __u32 sem; ++ __u32 count; ++ __u32 max; ++ }; + -+ close(fd); -+} ++ struct ntsync_mutex_args { ++ __u32 mutex; ++ __u32 owner; ++ __u32 count; ++ }; + -+TEST(alert_any) -+{ -+ struct ntsync_event_args event_args = {0}; -+ struct ntsync_wait_args wait_args = {0}; -+ struct ntsync_sem_args sem_args = {0}; -+ __u32 index, count, signaled; -+ struct wait_args thread_args; -+ int objs[2], fd, ret; -+ pthread_t thread; ++ struct ntsync_event_args { ++ __u32 event; ++ __u32 signaled; ++ __u32 manual; ++ }; + -+ fd = open("/dev/ntsync", O_CLOEXEC | O_RDONLY); -+ ASSERT_LE(0, fd); ++ struct ntsync_wait_args { ++ __u64 timeout; ++ __u64 objs; ++ __u32 count; ++ __u32 owner; ++ __u32 index; ++ __u32 alert; ++ __u32 flags; ++ __u32 pad; ++ }; + -+ 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; ++Depending on the ioctl, members of the structure may be used as input, ++output, or not at all. All ioctls return 0 on success. + -+ 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; ++The ioctls on the device file are as follows: + -+ event_args.manual = true; -+ event_args.signaled = true; -+ ret = ioctl(fd, NTSYNC_IOC_CREATE_EVENT, &event_args); -+ EXPECT_EQ(0, ret); ++.. c:macro:: NTSYNC_IOC_CREATE_SEM + -+ ret = wait_any_alert(fd, 0, NULL, 123, event_args.event, &index); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(0, index); ++ Create a semaphore object. Takes a pointer to struct ++ :c:type:`ntsync_sem_args`, which is used as follows: + -+ ret = ioctl(event_args.event, NTSYNC_IOC_EVENT_RESET, &signaled); -+ EXPECT_EQ(0, ret); ++ .. list-table:: + -+ ret = wait_any_alert(fd, 0, NULL, 123, event_args.event, &index); -+ EXPECT_EQ(-1, ret); -+ EXPECT_EQ(ETIMEDOUT, errno); ++ * - ``sem`` ++ - On output, contains a file descriptor to the created semaphore. ++ * - ``count`` ++ - Initial count of the semaphore. ++ * - ``max`` ++ - Maximum count of the semaphore. + -+ ret = ioctl(event_args.event, NTSYNC_IOC_EVENT_SET, &signaled); -+ EXPECT_EQ(0, ret); ++ Fails with ``EINVAL`` if ``count`` is greater than ``max``. + -+ ret = wait_any_alert(fd, 2, objs, 123, event_args.event, &index); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(1, index); ++.. c:macro:: NTSYNC_IOC_CREATE_MUTEX + -+ ret = wait_any_alert(fd, 2, objs, 123, event_args.event, &index); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(2, index); ++ Create a mutex object. Takes a pointer to struct ++ :c:type:`ntsync_mutex_args`, which is used as follows: + -+ /* test wakeup via alert */ ++ .. list-table:: + -+ ret = ioctl(event_args.event, NTSYNC_IOC_EVENT_RESET, &signaled); -+ EXPECT_EQ(0, ret); ++ * - ``mutex`` ++ - On output, contains a file descriptor to the created mutex. ++ * - ``count`` ++ - Initial recursion count of the mutex. ++ * - ``owner`` ++ - Initial owner of the mutex. + -+ wait_args.timeout = get_abs_timeout(1000); -+ 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); ++ If ``owner`` is nonzero and ``count`` is zero, or if ``owner`` is ++ zero and ``count`` is nonzero, the function fails with ``EINVAL``. + -+ ret = wait_for_thread(thread, 100); -+ EXPECT_EQ(ETIMEDOUT, ret); ++.. c:macro:: NTSYNC_IOC_CREATE_EVENT + -+ ret = ioctl(event_args.event, NTSYNC_IOC_EVENT_SET, &signaled); -+ EXPECT_EQ(0, ret); ++ Create an event object. Takes a pointer to struct ++ :c:type:`ntsync_event_args`, which is used as follows: + -+ ret = wait_for_thread(thread, 100); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(0, thread_args.ret); -+ EXPECT_EQ(2, wait_args.index); ++ .. list-table:: + -+ close(event_args.event); ++ * - ``event`` ++ - On output, contains a file descriptor to 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. + -+ /* test with an auto-reset event */ ++The ioctls on the individual objects are as follows: + -+ event_args.manual = false; -+ event_args.signaled = true; -+ ret = ioctl(fd, NTSYNC_IOC_CREATE_EVENT, &event_args); -+ EXPECT_EQ(0, ret); ++.. c:macro:: NTSYNC_IOC_SEM_POST + -+ count = 1; -+ ret = post_sem(objs[0], &count); -+ EXPECT_EQ(0, ret); ++ Post to a semaphore object. Takes a pointer to a 32-bit integer, ++ which on input holds the count to be added to the semaphore, and on ++ output contains its previous count. + -+ ret = wait_any_alert(fd, 2, objs, 123, event_args.event, &index); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(0, index); ++ If adding 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. + -+ ret = wait_any_alert(fd, 2, objs, 123, event_args.event, &index); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(2, index); ++.. c:macro:: NTSYNC_IOC_MUTEX_UNLOCK + -+ ret = wait_any_alert(fd, 2, objs, 123, event_args.event, &index); -+ EXPECT_EQ(-1, ret); -+ EXPECT_EQ(ETIMEDOUT, errno); ++ Release a mutex object. Takes a pointer to struct ++ :c:type:`ntsync_mutex_args`, which is used as follows: + -+ close(event_args.event); ++ .. list-table:: + -+ close(objs[0]); -+ close(objs[1]); ++ * - ``mutex`` ++ - Ignored. ++ * - ``owner`` ++ - Specifies the owner trying to release this mutex. ++ * - ``count`` ++ - On output, contains the previous recursion count. + -+ close(fd); -+} ++ If ``owner`` is zero, the ioctl fails with ``EINVAL``. If ``owner`` ++ is not the current owner of the mutex, the ioctl fails with ++ ``EPERM``. + -+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; -+ __u32 index, count, signaled; -+ int objs[2], fd, ret; -+ pthread_t thread; ++ 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. + -+ fd = open("/dev/ntsync", O_CLOEXEC | O_RDONLY); -+ ASSERT_LE(0, fd); ++.. c:macro:: NTSYNC_IOC_SET_EVENT + -+ 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; ++ Signal an event object. Takes a pointer to a 32-bit integer, which on ++ output contains the previous state of the event. + -+ 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; ++ Eligible threads will be woken, and auto-reset events will be ++ designaled appropriately. + -+ event_args.manual = true; -+ event_args.signaled = true; -+ ret = ioctl(fd, NTSYNC_IOC_CREATE_EVENT, &event_args); -+ EXPECT_EQ(0, ret); ++.. c:macro:: NTSYNC_IOC_RESET_EVENT + -+ ret = wait_all_alert(fd, 2, objs, 123, event_args.event, &index); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(0, index); ++ Designal an event object. Takes a pointer to a 32-bit integer, which ++ on output contains the previous state of the event. + -+ ret = wait_all_alert(fd, 2, objs, 123, event_args.event, &index); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(2, index); ++.. c:macro:: NTSYNC_IOC_PULSE_EVENT + -+ /* test wakeup via alert */ ++ Wake threads waiting on an event object while leaving it in an ++ unsignaled state. Takes a pointer to a 32-bit integer, which on ++ output contains the previous state of the event. + -+ ret = ioctl(event_args.event, NTSYNC_IOC_EVENT_RESET, &signaled); -+ EXPECT_EQ(0, ret); ++ 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. + -+ wait_args.timeout = get_abs_timeout(1000); -+ 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); ++.. c:macro:: NTSYNC_IOC_READ_SEM + -+ ret = wait_for_thread(thread, 100); -+ EXPECT_EQ(ETIMEDOUT, ret); ++ Read the current state of a semaphore object. Takes a pointer to ++ struct :c:type:`ntsync_sem_args`, which is used as follows: + -+ ret = ioctl(event_args.event, NTSYNC_IOC_EVENT_SET, &signaled); -+ EXPECT_EQ(0, ret); ++ .. list-table:: + -+ ret = wait_for_thread(thread, 100); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(0, thread_args.ret); -+ EXPECT_EQ(2, wait_args.index); ++ * - ``sem`` ++ - Ignored. ++ * - ``count`` ++ - On output, contains the current count of the semaphore. ++ * - ``max`` ++ - On output, contains the maximum count of the semaphore. + -+ close(event_args.event); ++.. c:macro:: NTSYNC_IOC_READ_MUTEX + -+ /* test with an auto-reset event */ ++ Read the current state of a mutex object. Takes a pointer to struct ++ :c:type:`ntsync_mutex_args`, which is used as follows: + -+ event_args.manual = false; -+ event_args.signaled = true; -+ ret = ioctl(fd, NTSYNC_IOC_CREATE_EVENT, &event_args); -+ EXPECT_EQ(0, ret); ++ .. list-table:: + -+ count = 2; -+ ret = post_sem(objs[1], &count); -+ EXPECT_EQ(0, ret); ++ * - ``mutex`` ++ - Ignored. ++ * - ``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. + -+ ret = wait_all_alert(fd, 2, objs, 123, event_args.event, &index); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(0, index); ++ If the mutex is marked as abandoned, the function fails with ++ ``EOWNERDEAD``. In this case, ``count`` and ``owner`` are set to ++ zero. + -+ ret = wait_all_alert(fd, 2, objs, 123, event_args.event, &index); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(2, index); ++.. c:macro:: NTSYNC_IOC_READ_EVENT + -+ ret = wait_all_alert(fd, 2, objs, 123, event_args.event, &index); -+ EXPECT_EQ(-1, ret); -+ EXPECT_EQ(ETIMEDOUT, errno); ++ Read the current state of an event object. Takes a pointer to struct ++ :c:type:`ntsync_event_args`, which is used as follows: + -+ close(event_args.event); ++ .. list-table:: + -+ close(objs[0]); -+ close(objs[1]); ++ * - ``event`` ++ - Ignored. ++ * - ``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. + -+ close(fd); -+} ++.. c:macro:: NTSYNC_IOC_KILL_OWNER + -+#define STRESS_LOOPS 10000 -+#define STRESS_THREADS 4 ++ Mark a mutex as unowned and abandoned if it is owned by the given ++ owner. Takes an input-only pointer to a 32-bit integer denoting the ++ owner. If the owner is zero, the ioctl fails with ``EINVAL``. If the ++ owner does not own the mutex, the function fails with ``EPERM``. + -+static unsigned int stress_counter; -+static int stress_device, stress_start_event, stress_mutex; ++ Eligible threads waiting on the mutex will be woken as appropriate ++ (and such waits will fail with ``EOWNERDEAD``, as described below). + -+static void *stress_thread(void *arg) -+{ -+ struct ntsync_wait_args wait_args = {0}; -+ __u32 index, count, i; -+ int ret; ++.. c:macro:: NTSYNC_IOC_WAIT_ANY + -+ wait_args.timeout = UINT64_MAX; -+ wait_args.count = 1; -+ wait_args.objs = (uintptr_t)&stress_start_event; -+ wait_args.owner = gettid(); -+ wait_args.index = 0xdeadbeef; ++ 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: + -+ ioctl(stress_device, NTSYNC_IOC_WAIT_ANY, &wait_args); ++ .. list-table:: + -+ wait_args.objs = (uintptr_t)&stress_mutex; ++ * - ``timeout`` ++ - Absolute timeout in nanoseconds. If ``NTSYNC_WAIT_REALTIME`` ++ is set, the timeout is measured against the REALTIME clock; ++ otherwise it is 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 U64_MAX, the function will sleep until an object is ++ signaled, and will not fail with ``ETIMEDOUT``. ++ * - ``objs`` ++ - Pointer to an array of ``count`` file descriptors ++ (specified as an integer so that the structure has the same ++ size regardless of architecture). If any object is ++ invalid, the function fails with ``EINVAL``. ++ * - ``count`` ++ - Number of objects 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 file descriptor. 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. ++ * - ``flags`` ++ - Zero or more flags. Currently the only flag is ++ ``NTSYNC_WAIT_REALTIME``, which causes the timeout to be ++ measured against the REALTIME clock instead of MONOTONIC. ++ * - ``pad`` ++ - Unused, must be set to zero. + -+ for (i = 0; i < STRESS_LOOPS; ++i) { -+ ioctl(stress_device, NTSYNC_IOC_WAIT_ANY, &wait_args); ++ 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. + -+ ++stress_counter; ++ 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. + -+ unlock_mutex(stress_mutex, wait_args.owner, &count); -+ } ++ 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. + -+ return NULL; -+} ++ If an abandoned 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 ++ abandoned, and ``index`` is still set to the index of the mutex. + -+TEST(stress_wait) -+{ -+ struct ntsync_event_args event_args; -+ struct ntsync_mutex_args mutex_args; -+ pthread_t threads[STRESS_THREADS]; -+ __u32 signaled, i; -+ int ret; ++ 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. + -+ stress_device = open("/dev/ntsync", O_CLOEXEC | O_RDONLY); -+ ASSERT_LE(0, stress_device); ++ 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. + -+ mutex_args.owner = 0; -+ mutex_args.count = 0; -+ ret = ioctl(stress_device, NTSYNC_IOC_CREATE_MUTEX, &mutex_args); -+ EXPECT_EQ(0, ret); -+ stress_mutex = mutex_args.mutex; ++ The function may fail with ``EINTR`` if a signal is received. + -+ event_args.manual = 1; -+ event_args.signaled = 0; -+ ret = ioctl(stress_device, NTSYNC_IOC_CREATE_EVENT, &event_args); -+ EXPECT_EQ(0, ret); -+ stress_start_event = event_args.event; ++.. c:macro:: NTSYNC_IOC_WAIT_ALL + -+ for (i = 0; i < STRESS_THREADS; ++i) -+ pthread_create(&threads[i], NULL, stress_thread, NULL); ++ 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. + -+ ret = ioctl(stress_start_event, NTSYNC_IOC_EVENT_SET, &signaled); -+ EXPECT_EQ(0, ret); ++ 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. + -+ for (i = 0; i < STRESS_THREADS; ++i) { -+ ret = pthread_join(threads[i], NULL); -+ EXPECT_EQ(0, ret); -+ } ++ 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. + -+ EXPECT_EQ(STRESS_LOOPS * STRESS_THREADS, stress_counter); ++ If an abandoned 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 ++ abandoned. + -+ close(stress_start_event); -+ close(stress_mutex); -+ close(stress_device); -+} ++ 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. + -+TEST_HARNESS_MAIN ++ 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.45.0 - +2.44.0