3128 lines
86 KiB
Diff
3128 lines
86 KiB
Diff
|
diff --git a/Documentation/admin-guide/devices.txt b/Documentation/admin-guide/devices.txt
|
||
|
index 63fd4e6a014b..dc9a30b5f1a2 100644
|
||
|
--- a/Documentation/admin-guide/devices.txt
|
||
|
+++ b/Documentation/admin-guide/devices.txt
|
||
|
@@ -376,8 +376,9 @@
|
||
|
240 = /dev/userio Serio driver testing device
|
||
|
241 = /dev/vhost-vsock Host kernel driver for virtio vsock
|
||
|
242 = /dev/rfkill Turning off radio transmissions (rfkill)
|
||
|
+ 243 = /dev/winesync Wine synchronization primitive device
|
||
|
|
||
|
- 243-254 Reserved for local use
|
||
|
+ 244-254 Reserved for local use
|
||
|
255 Reserved for MISC_DYNAMIC_MINOR
|
||
|
|
||
|
11 char Raw keyboard device (Linux/SPARC only)
|
||
|
diff --git a/Documentation/userspace-api/index.rst b/Documentation/userspace-api/index.rst
|
||
|
index d29b020e5622..d2ddbf9ad8cb 100644
|
||
|
--- a/Documentation/userspace-api/index.rst
|
||
|
+++ b/Documentation/userspace-api/index.rst
|
||
|
@@ -25,6 +25,7 @@ place where this information is gathered.
|
||
|
iommu
|
||
|
media/index
|
||
|
sysfs-platform_profile
|
||
|
+ winesync
|
||
|
|
||
|
.. only:: subproject and html
|
||
|
|
||
|
diff --git a/Documentation/userspace-api/ioctl/ioctl-number.rst b/Documentation/userspace-api/ioctl/ioctl-number.rst
|
||
|
index 599bd4493944..825c6b2d2623 100644
|
||
|
--- a/Documentation/userspace-api/ioctl/ioctl-number.rst
|
||
|
+++ b/Documentation/userspace-api/ioctl/ioctl-number.rst
|
||
|
@@ -366,6 +366,8 @@ Code Seq# Include File Comments
|
||
|
<mailto:thomas@winischhofer.net>
|
||
|
0xF6 all LTTng Linux Trace Toolkit Next Generation
|
||
|
<mailto:mathieu.desnoyers@efficios.com>
|
||
|
+0xF7 00-0F uapi/linux/winesync.h Wine synchronization primitives
|
||
|
+ <mailto:wine-devel@winehq.org>
|
||
|
0xFD all linux/dm-ioctl.h
|
||
|
0xFE all linux/isst_if.h
|
||
|
==== ===== ======================================================= ================================================================
|
||
|
diff --git a/Documentation/userspace-api/winesync.rst b/Documentation/userspace-api/winesync.rst
|
||
|
new file mode 100644
|
||
|
index 000000000000..751c70f1ffce
|
||
|
--- /dev/null
|
||
|
+++ b/Documentation/userspace-api/winesync.rst
|
||
|
@@ -0,0 +1,373 @@
|
||
|
+=====================================
|
||
|
+Wine synchronization primitive driver
|
||
|
+=====================================
|
||
|
+
|
||
|
+This page documents the user-space API for the winesync driver.
|
||
|
+
|
||
|
+winesync is a support driver for emulation of NT synchronization
|
||
|
+primitives by the Wine project. It exists because implementation in
|
||
|
+user-space, using existing tools, cannot satisfy performance,
|
||
|
+correctness, and security constraints. It is implemented entirely in
|
||
|
+software, and does not drive any hardware device.
|
||
|
+
|
||
|
+This interface is meant as a compatibility tool only and should not be
|
||
|
+used for general synchronization; instead use generic, versatile
|
||
|
+interfaces such as futex(2) and poll(2).
|
||
|
+
|
||
|
+Synchronization primitives
|
||
|
+==========================
|
||
|
+
|
||
|
+The winesync driver exposes two types of synchronization primitives,
|
||
|
+semaphores and mutexes.
|
||
|
+
|
||
|
+A semaphore holds a single volatile 32-bit counter, and a static
|
||
|
+32-bit integer denoting the maximum value. It is considered signaled
|
||
|
+when the counter is nonzero. The counter is decremented by one when a
|
||
|
+wait is satisfied. Both the initial and maximum count are established
|
||
|
+when the semaphore is created.
|
||
|
+
|
||
|
+A mutex holds a volatile 32-bit recursion count, and a volatile 32-bit
|
||
|
+identifier denoting its owner. The latter is intended to identify the
|
||
|
+thread holding the mutex; however, it is not actually validated
|
||
|
+against earlier calls made by the same thread. A mutex is considered
|
||
|
+signaled when its owner is zero (indicating that it is not owned). The
|
||
|
+recursion count is incremented when a wait is satisfied, and ownership
|
||
|
+is set to the given identifier. A mutex also holds an internal flag
|
||
|
+denoting whether its previous owner has died; such a mutex is said to
|
||
|
+be inconsistent. Owner death is not tracked automatically based on
|
||
|
+thread death, but rather must be communicated using
|
||
|
+``WINESYNC_IOC_KILL_OWNER``.
|
||
|
+
|
||
|
+Objects are represented by signed 32-bit integers. A valid object
|
||
|
+identifier will always be nonnegative.
|
||
|
+
|
||
|
+Char device
|
||
|
+===========
|
||
|
+
|
||
|
+The winesync driver creates a single char device /dev/winesync. Each
|
||
|
+file description opened on the device represents a unique namespace.
|
||
|
+That is, objects created on one open file description are shared
|
||
|
+across all its individual descriptors, but are not shared with other
|
||
|
+open() calls on the same device.
|
||
|
+
|
||
|
+ioctl reference
|
||
|
+===============
|
||
|
+
|
||
|
+All operations on the device are done through ioctls. There are three
|
||
|
+structures used in ioctl calls::
|
||
|
+
|
||
|
+ struct winesync_sem_args {
|
||
|
+ __s32 sem;
|
||
|
+ __u32 count;
|
||
|
+ __u32 max;
|
||
|
+ __u32 flags;
|
||
|
+ };
|
||
|
+
|
||
|
+ struct winesync_mutex_args {
|
||
|
+ __s32 mutex;
|
||
|
+ __u32 owner;
|
||
|
+ __u32 count;
|
||
|
+ };
|
||
|
+
|
||
|
+ struct winesync_wait_args {
|
||
|
+ __u64 timeout;
|
||
|
+ __u64 objs;
|
||
|
+ __u32 count;
|
||
|
+ __u32 owner;
|
||
|
+ __u32 index;
|
||
|
+ __u32 pad;
|
||
|
+ };
|
||
|
+
|
||
|
+Depending on the ioctl, members of the structure may be used as input,
|
||
|
+output, or not at all.
|
||
|
+
|
||
|
+All ioctls return 0 on success, and -1 on error, in which case `errno`
|
||
|
+will be set to a nonzero error code.
|
||
|
+
|
||
|
+The ioctls are as follows:
|
||
|
+
|
||
|
+.. c:macro:: WINESYNC_IOC_CREATE_SEM
|
||
|
+
|
||
|
+ Create a semaphore object. Takes a pointer to struct
|
||
|
+ :c:type:`winesync_sem_args`, which is used as follows:
|
||
|
+
|
||
|
+ ``count`` and ``max`` are input-only arguments, denoting the
|
||
|
+ initial and maximum count of the semaphore.
|
||
|
+
|
||
|
+ ``flags`` is an input-only argument, which specifies additional
|
||
|
+ flags modifying the behaviour of the semaphore. There is only one
|
||
|
+ flag defined, ``WINESYNC_SEM_GETONWAIT``. If present, wait
|
||
|
+ operations on this semaphore will acquire it, decrementing its
|
||
|
+ count by one; otherwise, wait operations will not affect the
|
||
|
+ semaphore's state.
|
||
|
+
|
||
|
+ ``sem`` is an output-only argument, which will be filled with the
|
||
|
+ allocated identifier if successful.
|
||
|
+
|
||
|
+ Fails with ``EINVAL`` if ``count`` is greater than ``max``, or
|
||
|
+ ``ENOMEM`` if not enough memory is available.
|
||
|
+
|
||
|
+.. c:macro:: WINESYNC_IOC_CREATE_MUTEX
|
||
|
+
|
||
|
+ Create a mutex object. Takes a pointer to struct
|
||
|
+ :c:type:`winesync_mutex_args`, which is used as follows:
|
||
|
+
|
||
|
+ ``owner`` is an input-only argument denoting the initial owner of
|
||
|
+ the mutex.
|
||
|
+
|
||
|
+ ``count`` is an input-only argument denoting the initial recursion
|
||
|
+ count of the mutex. If ``owner`` is nonzero and ``count`` is zero,
|
||
|
+ or if ``owner`` is zero and ``count`` is nonzero, the function
|
||
|
+ fails with ``EINVAL``.
|
||
|
+
|
||
|
+ ``mutex`` is an output-only argument, which will be filled with
|
||
|
+ the allocated identifier if successful.
|
||
|
+
|
||
|
+ Fails with ``ENOMEM`` if not enough memory is available.
|
||
|
+
|
||
|
+.. c:macro:: WINESYNC_IOC_DELETE
|
||
|
+
|
||
|
+ Delete an object of any type. Takes an input-only pointer to a
|
||
|
+ 32-bit integer denoting the object to delete. Fails with ``EINVAL``
|
||
|
+ if the object is not valid. Further ioctls attempting to use the
|
||
|
+ object return ``EINVAL``, unless the object identifier is reused.
|
||
|
+ However, wait ioctls currently in progress are not interrupted, and
|
||
|
+ behave as if the object remains valid.
|
||
|
+
|
||
|
+.. c:macro:: WINESYNC_IOC_PUT_SEM
|
||
|
+
|
||
|
+ Post to a semaphore object. Takes a pointer to struct
|
||
|
+ :c:type:`winesync_sem_args`, which is used as follows:
|
||
|
+
|
||
|
+ ``sem`` is an input-only argument denoting the semaphore object.
|
||
|
+ If ``sem`` is not a valid semaphore object, the ioctl fails with
|
||
|
+ ``EINVAL``.
|
||
|
+
|
||
|
+ ``count`` contains on input the count to add to the semaphore, and
|
||
|
+ on output is filled with its previous count.
|
||
|
+
|
||
|
+ ``max`` and ``flags`` are not used.
|
||
|
+
|
||
|
+ The operation is atomic and totally ordered with respect to other
|
||
|
+ operations on the same semaphore. If adding ``count`` to the
|
||
|
+ semaphore's current count would raise the latter past the
|
||
|
+ semaphore's maximum count, the ioctl fails with ``EOVERFLOW`` and
|
||
|
+ the semaphore is not affected. If raising the semaphore's count
|
||
|
+ causes it to become signaled, eligible threads waiting on this
|
||
|
+ semaphore will be woken and the semaphore's count decremented
|
||
|
+ appropriately.
|
||
|
+
|
||
|
+.. c:macro:: WINESYNC_IOC_PULSE_SEM
|
||
|
+
|
||
|
+ This operation is identical to ``WINESYNC_IOC_PUT_SEM``, with one
|
||
|
+ notable exception: the semaphore is always left in an *unsignaled*
|
||
|
+ state, regardless of the initial count or the count added by the
|
||
|
+ ioctl. That is, the count after a pulse operation will always be
|
||
|
+ zero. The entire operation is atomic.
|
||
|
+
|
||
|
+ Hence, if the semaphore was created with the
|
||
|
+ ``WINESYNC_SEM_GETONWAIT`` flag set, and an unsignaled semaphore is
|
||
|
+ "pulsed" with a count of 2, at most two eligible threads (i.e.
|
||
|
+ threads not otherwise constrained due to ``WINESYNC_IOC_WAIT_ALL``)
|
||
|
+ will be woken up, and any others will remain sleeping. If less than
|
||
|
+ two eligible threads are waiting on the semaphore, all of them will
|
||
|
+ be woken up, and the semaphore's count will remain at zero. On the
|
||
|
+ other hand, if the semaphore was created without the
|
||
|
+ ``WINESYNC_SEM_GETONWAIT``, all eligible threads will be woken up,
|
||
|
+ making ``count`` effectively redundant. In either case, a
|
||
|
+ simultaneous ``WINESYNC_IOC_READ_SEM`` ioctl from another thread
|
||
|
+ will always report a count of zero.
|
||
|
+
|
||
|
+ If adding ``count`` to the semaphore's current count would raise the
|
||
|
+ latter past the semaphore's maximum count, the ioctl fails with
|
||
|
+ ``EOVERFLOW``. However, in this case the semaphore's count will
|
||
|
+ still be reset to zero.
|
||
|
+
|
||
|
+.. c:macro:: WINESYNC_IOC_GET_SEM
|
||
|
+
|
||
|
+ Attempt to acquire a semaphore object. Takes an input-only pointer
|
||
|
+ to a 32-bit integer denoting the semaphore to acquire.
|
||
|
+
|
||
|
+ This operation does not block. If the semaphore's count was zero, it
|
||
|
+ fails with ``EWOULDBLOCK``. Otherwise, the semaphore's count is
|
||
|
+ decremented by one. The behaviour of this operation is unaffected by
|
||
|
+ whether the semaphore was created with the
|
||
|
+ ``WINESYNC_SEM_GETONWAIT`` flag set.
|
||
|
+
|
||
|
+ The operation is atomic and totally ordered with respect to other
|
||
|
+ operations on the same semaphore.
|
||
|
+
|
||
|
+.. c:macro:: WINESYNC_IOC_PUT_MUTEX
|
||
|
+
|
||
|
+ Release a mutex object. Takes a pointer to struct
|
||
|
+ :c:type:`winesync_mutex_args`, which is used as follows:
|
||
|
+
|
||
|
+ ``mutex`` is an input-only argument denoting the mutex object. If
|
||
|
+ ``mutex`` is not a valid mutex object, the ioctl fails with
|
||
|
+ ``EINVAL``.
|
||
|
+
|
||
|
+ ``owner`` is an input-only argument denoting the mutex owner.
|
||
|
+ ``owner`` must be nonzero, else the ioctl fails with ``EINVAL``.
|
||
|
+ If ``owner`` is not the current owner of the mutex, the ioctl
|
||
|
+ fails with ``EPERM``.
|
||
|
+
|
||
|
+ ``count`` is an output-only argument which will be filled on
|
||
|
+ success with the mutex's previous recursion count.
|
||
|
+
|
||
|
+ The mutex's count will be decremented by one. The operation is
|
||
|
+ atomic and totally ordered with respect to other operations on the
|
||
|
+ same mutex. If decrementing the mutex's count causes it to become
|
||
|
+ zero, the mutex is marked as unowned and signaled, and eligible
|
||
|
+ threads waiting on it will be woken as appropriate.
|
||
|
+
|
||
|
+.. c:macro:: WINESYNC_IOC_READ_SEM
|
||
|
+
|
||
|
+ Read the current state of a semaphore object. Takes a pointer to
|
||
|
+ struct :c:type:`winesync_sem_args`, which is used as follows:
|
||
|
+
|
||
|
+ ``sem`` is an input-only argument denoting the semaphore object.
|
||
|
+ If ``sem`` is not a valid semaphore object, the ioctl fails with
|
||
|
+ ``EINVAL``.
|
||
|
+
|
||
|
+ ``count`` and ``max`` are output-only arguments, which will be
|
||
|
+ filled with the current and maximum count of the given semaphore.
|
||
|
+
|
||
|
+ ``flags`` is an output-only argument, which will be filled with
|
||
|
+ the flags used to create the semaphore.
|
||
|
+
|
||
|
+ The operation is atomic and totally ordered with respect to other
|
||
|
+ operations on the same semaphore.
|
||
|
+
|
||
|
+.. c:macro:: WINESYNC_IOC_READ_MUTEX
|
||
|
+
|
||
|
+ Read the current state of a mutex object. Takes a pointer to struct
|
||
|
+ :c:type:`winesync_mutex_args`, which is used as follows:
|
||
|
+
|
||
|
+ ``mutex`` is an input-only argument denoting the mutex object. If
|
||
|
+ ``mutex`` is not a valid mutex object, the ioctl fails with
|
||
|
+ ``EINVAL``.
|
||
|
+
|
||
|
+ ``count`` and ``owner`` are output-only arguments, which will be
|
||
|
+ filled with the current recursion count and owner of the given
|
||
|
+ mutex. If the mutex is not owned, both ``count`` and ``owner`` are
|
||
|
+ set to zero.
|
||
|
+
|
||
|
+ If the mutex is marked as inconsistent, the function fails with
|
||
|
+ ``EOWNERDEAD``.
|
||
|
+
|
||
|
+ The operation is atomic and totally ordered with respect to other
|
||
|
+ operations on the same mutex.
|
||
|
+
|
||
|
+.. c:macro:: WINESYNC_IOC_KILL_OWNER
|
||
|
+
|
||
|
+ Mark any mutexes owned by the given identifier as unowned and
|
||
|
+ inconsistent. Takes an input-only pointer to a 32-bit integer
|
||
|
+ denoting the owner. If the owner is zero, the ioctl fails with
|
||
|
+ ``EINVAL``.
|
||
|
+
|
||
|
+.. c:macro:: WINESYNC_IOC_WAIT_ANY
|
||
|
+
|
||
|
+ Poll on any of a list of objects, atomically acquiring (at most)
|
||
|
+ one. Takes a pointer to struct :c:type:`winesync_wait_args`, which
|
||
|
+ is used as follows:
|
||
|
+
|
||
|
+ ``timeout`` is an optional input-only pointer to a 64-bit struct
|
||
|
+ :c:type:`timespec` (specified as an integer so that the structure
|
||
|
+ has the same size regardless of architecture). The timeout is
|
||
|
+ specified in absolute format, as measured against the MONOTONIC
|
||
|
+ clock. If the timeout is equal to or earlier than the current
|
||
|
+ time, the function returns immediately without sleeping. If
|
||
|
+ ``timeout`` is zero, i.e. NULL, the function will sleep until an
|
||
|
+ object is signaled, and will not fail with ``ETIMEDOUT``.
|
||
|
+
|
||
|
+ ``objs`` is a input-only pointer to an array of ``count`` 32-bit
|
||
|
+ object identifiers (specified as an integer so that the structure
|
||
|
+ has the same size regardless of architecture). If any identifier
|
||
|
+ is invalid, the function fails with ``EINVAL``.
|
||
|
+
|
||
|
+ ``count`` is an input-only argument denoting the number of
|
||
|
+ elements in ``objs``.
|
||
|
+
|
||
|
+ ``owner`` is an input-only argument denoting the 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`` is an output-only argument which, if the ioctl is
|
||
|
+ successful, is filled with the index of the object actually
|
||
|
+ signaled.
|
||
|
+
|
||
|
+ ``pad`` is unused, and exists to keep a consistent structure size.
|
||
|
+
|
||
|
+ This function attempts to acquire one of the given objects. If
|
||
|
+ unable to do so, it sleeps until an object becomes signaled,
|
||
|
+ subsequently acquiring it, or the timeout expires. In the latter
|
||
|
+ case the ioctl fails with ``ETIMEDOUT``. The function only acquires
|
||
|
+ one object, even if multiple objects are signaled.
|
||
|
+
|
||
|
+ A semaphore is considered to be signaled if its count is nonzero. It
|
||
|
+ is acquired by decrementing its count by one if the
|
||
|
+ ``WINESYNC_SEM_GETONWAIT`` flag was used to create it; otherwise no
|
||
|
+ operation is done to acquire the semaphore. 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.
|
||
|
+
|
||
|
+ 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 (which was not created with the ``WINESYNC_SEM_GETONWAIT``
|
||
|
+ flag set), and a value of one is posted to it, only one is signaled.
|
||
|
+ The order in which threads are signaled is not guaranteed.
|
||
|
+
|
||
|
+ (If two wait operations are queued on the same semaphore, and the
|
||
|
+ semaphore was created with the ``WINESYNC_SEM_GETONWAIT`` flag set,
|
||
|
+ and a value of one is posted to it, both threads are signaled, and
|
||
|
+ the semaphore retains a count of one.)
|
||
|
+
|
||
|
+ If an inconsistent mutex is acquired, the ioctl fails with
|
||
|
+ ``EOWNERDEAD``. Although this is a failure return, the function may
|
||
|
+ otherwise be considered successful. The mutex is marked as owned by
|
||
|
+ the given owner (with a recursion count of 1) and as no longer
|
||
|
+ inconsistent. ``index`` is still set to the index of the mutex.
|
||
|
+
|
||
|
+ Unlike ``WINESYNC_IOC_WAIT_ALL``, it is valid to pass the same
|
||
|
+ object more than once. If a wakeup occurs due to that object being
|
||
|
+ signaled, ``index`` is set to the index of the first instance of the
|
||
|
+ object.
|
||
|
+
|
||
|
+ Fails with ``ENOMEM`` if not enough memory is available, or
|
||
|
+ ``EINTR`` if a signal is received.
|
||
|
+
|
||
|
+.. c:macro:: WINESYNC_IOC_WAIT_ALL
|
||
|
+
|
||
|
+ Poll on a list of objects, atomically acquiring all of them. Takes a
|
||
|
+ pointer to struct :c:type:`winesync_wait_args`, which is used
|
||
|
+ identically to ``WINESYNC_IOC_WAIT_ANY``, except that ``index`` is
|
||
|
+ unused.
|
||
|
+
|
||
|
+ This function attempts to simultaneously acquire all of the given
|
||
|
+ objects. If unable to do so, it sleeps until all objects become
|
||
|
+ simultaneously signaled, subsequently acquiring them, or the timeout
|
||
|
+ expires. In the latter case the ioctl fails with ``ETIMEDOUT`` and
|
||
|
+ no objects are modified.
|
||
|
+
|
||
|
+ Objects may become signaled and subsequently designaled (through
|
||
|
+ acquisition by other threads) while this thread is sleeping. Only
|
||
|
+ once all objects are simultaneously signaled does the ioctl return.
|
||
|
+ The acquisition is atomic and totally ordered with respect to other
|
||
|
+ operations on any of the given objects.
|
||
|
+
|
||
|
+ If an inconsistent mutex is acquired, the ioctl fails with
|
||
|
+ ``EOWNERDEAD``. Similarly to ``WINESYNC_IOC_WAIT_ANY``, all objects
|
||
|
+ are nevertheless marked as acquired. Note that if multiple mutex
|
||
|
+ objects are specified, there is no way to know which were marked as
|
||
|
+ inconsistent.
|
||
|
+
|
||
|
+ Unlike ``WINESYNC_IOC_WAIT_ALL``, it is not valid to pass the same
|
||
|
+ object more than once. If this is attempted, the function fails with
|
||
|
+ ``EINVAL``.
|
||
|
+
|
||
|
+ Fails with ``ENOMEM`` if not enough memory is available, or
|
||
|
+ ``EINTR`` if a signal is received.
|
||
|
diff --git a/MAINTAINERS b/MAINTAINERS
|
||
|
index 9450e052f1b1..d33e317759bf 100644
|
||
|
--- a/MAINTAINERS
|
||
|
+++ b/MAINTAINERS
|
||
|
@@ -19351,6 +19351,15 @@ M: David Härdeman <david@hardeman.nu>
|
||
|
S: Maintained
|
||
|
F: drivers/media/rc/winbond-cir.c
|
||
|
|
||
|
+WINESYNC SYNCHRONIZATION PRIMITIVE DRIVER
|
||
|
+M: Zebediah Figura <zfigura@codeweavers.com>
|
||
|
+L: wine-devel@winehq.org
|
||
|
+S: Supported
|
||
|
+F: Documentation/userspace-api/winesync.rst
|
||
|
+F: drivers/misc/winesync.c
|
||
|
+F: include/uapi/linux/winesync.c
|
||
|
+F: tools/testing/selftests/drivers/winesync/
|
||
|
+
|
||
|
WINSYSTEMS EBC-C384 WATCHDOG DRIVER
|
||
|
M: William Breathitt Gray <vilhelm.gray@gmail.com>
|
||
|
L: linux-watchdog@vger.kernel.org
|
||
|
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
|
||
|
index f532c59bb59b..496f20d69914 100644
|
||
|
--- a/drivers/misc/Kconfig
|
||
|
+++ b/drivers/misc/Kconfig
|
||
|
@@ -445,6 +445,17 @@ config HISI_HIKEY_USB
|
||
|
switching between the dual-role USB-C port and the USB-A host ports
|
||
|
using only one USB controller.
|
||
|
|
||
|
+config WINESYNC
|
||
|
+ tristate "Synchronization primitives for Wine"
|
||
|
+ help
|
||
|
+ This module provides kernel support for synchronization primitives
|
||
|
+ used by Wine. It is not a hardware driver.
|
||
|
+
|
||
|
+ To compile this driver as a module, choose M here: the
|
||
|
+ module will be called winesync.
|
||
|
+
|
||
|
+ If unsure, say N.
|
||
|
+
|
||
|
source "drivers/misc/c2port/Kconfig"
|
||
|
source "drivers/misc/eeprom/Kconfig"
|
||
|
source "drivers/misc/cb710/Kconfig"
|
||
|
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
|
||
|
index 99b6f15a3c70..327c9628c479 100644
|
||
|
--- a/drivers/misc/Makefile
|
||
|
+++ b/drivers/misc/Makefile
|
||
|
@@ -56,3 +56,4 @@ obj-$(CONFIG_HABANA_AI) += habanalabs/
|
||
|
obj-$(CONFIG_UACCE) += uacce/
|
||
|
obj-$(CONFIG_XILINX_SDFEC) += xilinx_sdfec.o
|
||
|
obj-$(CONFIG_HISI_HIKEY_USB) += hisi_hikey_usb.o
|
||
|
+obj-$(CONFIG_WINESYNC) += winesync.o
|
||
|
diff --git a/drivers/misc/winesync.c b/drivers/misc/winesync.c
|
||
|
new file mode 100644
|
||
|
index 000000000000..ff5749206aa9
|
||
|
--- /dev/null
|
||
|
+++ b/drivers/misc/winesync.c
|
||
|
@@ -0,0 +1,1047 @@
|
||
|
+// SPDX-License-Identifier: GPL-2.0-only
|
||
|
+/*
|
||
|
+ * winesync.c - Kernel driver for Wine synchronization primitives
|
||
|
+ *
|
||
|
+ * Copyright (C) 2021 Zebediah Figura
|
||
|
+ */
|
||
|
+
|
||
|
+#include <linux/fs.h>
|
||
|
+#include <linux/idr.h>
|
||
|
+#include <linux/miscdevice.h>
|
||
|
+#include <linux/module.h>
|
||
|
+#include <linux/mutex.h>
|
||
|
+#include <linux/slab.h>
|
||
|
+#include <uapi/linux/winesync.h>
|
||
|
+
|
||
|
+#define WINESYNC_NAME "winesync"
|
||
|
+
|
||
|
+enum winesync_type {
|
||
|
+ WINESYNC_TYPE_SEM,
|
||
|
+ WINESYNC_TYPE_MUTEX,
|
||
|
+};
|
||
|
+
|
||
|
+struct winesync_obj {
|
||
|
+ struct kref refcount;
|
||
|
+ spinlock_t lock;
|
||
|
+
|
||
|
+ /*
|
||
|
+ * any_waiters is protected by the object lock, but all_waiters is
|
||
|
+ * protected by the device wait_all_lock.
|
||
|
+ */
|
||
|
+ struct list_head any_waiters;
|
||
|
+ struct list_head all_waiters;
|
||
|
+
|
||
|
+ /*
|
||
|
+ * Hint describing how many tasks are queued on this object in a
|
||
|
+ * wait-all operation.
|
||
|
+ *
|
||
|
+ * Any time we do a wake, we may need to wake "all" waiters as well as
|
||
|
+ * "any" waiters. In order to atomically wake "all" waiters, we must
|
||
|
+ * lock all of the objects, and that means grabbing the wait_all_lock
|
||
|
+ * below (and, due to lock ordering rules, before locking this object).
|
||
|
+ * However, wait-all is a rare operation, and grabbing the wait-all
|
||
|
+ * lock for every wake would create unnecessary contention. Therefore we
|
||
|
+ * first check whether all_hint is one, and, if it is, we skip trying
|
||
|
+ * to wake "all" waiters.
|
||
|
+ *
|
||
|
+ * This "refcount" isn't protected by any lock. It might change during
|
||
|
+ * the course of a wake, but there's no meaningful race there; it's only
|
||
|
+ * a hint.
|
||
|
+ *
|
||
|
+ * Use refcount_t rather than atomic_t to take advantage of saturation.
|
||
|
+ * This does mean that the "no waiters" case is signified by all_hint
|
||
|
+ * being one, rather than zero (otherwise we would get spurious
|
||
|
+ * warnings).
|
||
|
+ */
|
||
|
+ refcount_t all_hint;
|
||
|
+
|
||
|
+ enum winesync_type type;
|
||
|
+
|
||
|
+ /* The following fields are protected by the object lock. */
|
||
|
+ union {
|
||
|
+ struct {
|
||
|
+ __u32 count;
|
||
|
+ __u32 max;
|
||
|
+ __u32 flags;
|
||
|
+ } sem;
|
||
|
+ struct {
|
||
|
+ __u32 count;
|
||
|
+ __u32 owner;
|
||
|
+ bool ownerdead;
|
||
|
+ } mutex;
|
||
|
+ } u;
|
||
|
+};
|
||
|
+
|
||
|
+struct winesync_q_entry {
|
||
|
+ struct list_head node;
|
||
|
+ struct winesync_q *q;
|
||
|
+ struct winesync_obj *obj;
|
||
|
+ __u32 index;
|
||
|
+};
|
||
|
+
|
||
|
+struct winesync_q {
|
||
|
+ struct task_struct *task;
|
||
|
+ __u32 owner;
|
||
|
+
|
||
|
+ /*
|
||
|
+ * Protected via atomic_cmpxchg(). Only the thread that wins the
|
||
|
+ * compare-and-swap may actually change object states and wake this
|
||
|
+ * task.
|
||
|
+ */
|
||
|
+ atomic_t signaled;
|
||
|
+
|
||
|
+ bool all;
|
||
|
+ bool ownerdead;
|
||
|
+ __u32 count;
|
||
|
+ struct winesync_q_entry entries[];
|
||
|
+};
|
||
|
+
|
||
|
+struct winesync_device {
|
||
|
+ /*
|
||
|
+ * Wait-all operations must atomically grab all objects, and be totally
|
||
|
+ * ordered with respect to each other and wait-any operations. If one
|
||
|
+ * thread is trying to acquire several objects, another thread cannot
|
||
|
+ * touch the object at the same time.
|
||
|
+ *
|
||
|
+ * We achieve this by grabbing multiple object locks at the same time.
|
||
|
+ * However, this creates a lock ordering problem. To solve that problem,
|
||
|
+ * wait_all_lock is taken first whenever multiple objects must be locked
|
||
|
+ * at the same time.
|
||
|
+ */
|
||
|
+ spinlock_t wait_all_lock;
|
||
|
+ struct mutex table_lock;
|
||
|
+ struct idr objects;
|
||
|
+};
|
||
|
+
|
||
|
+static struct winesync_obj *get_obj(struct winesync_device *dev, int id)
|
||
|
+{
|
||
|
+ struct winesync_obj *obj;
|
||
|
+
|
||
|
+ rcu_read_lock();
|
||
|
+ obj = idr_find(&dev->objects, id);
|
||
|
+ if (obj && !kref_get_unless_zero(&obj->refcount))
|
||
|
+ obj = NULL;
|
||
|
+ rcu_read_unlock();
|
||
|
+
|
||
|
+ return obj;
|
||
|
+}
|
||
|
+
|
||
|
+static void destroy_obj(struct kref *ref)
|
||
|
+{
|
||
|
+ struct winesync_obj *obj;
|
||
|
+
|
||
|
+ obj = container_of(ref, struct winesync_obj, refcount);
|
||
|
+ kfree(obj);
|
||
|
+}
|
||
|
+
|
||
|
+static void put_obj(struct winesync_obj *obj)
|
||
|
+{
|
||
|
+ kref_put(&obj->refcount, destroy_obj);
|
||
|
+}
|
||
|
+
|
||
|
+static int winesync_char_open(struct inode *inode, struct file *file)
|
||
|
+{
|
||
|
+ struct winesync_device *dev;
|
||
|
+
|
||
|
+ dev = kzalloc(sizeof(*dev), GFP_KERNEL);
|
||
|
+ if (!dev)
|
||
|
+ return -ENOMEM;
|
||
|
+
|
||
|
+ idr_init(&dev->objects);
|
||
|
+ spin_lock_init(&dev->wait_all_lock);
|
||
|
+ mutex_init(&dev->table_lock);
|
||
|
+
|
||
|
+ file->private_data = dev;
|
||
|
+ return nonseekable_open(inode, file);
|
||
|
+}
|
||
|
+
|
||
|
+static int winesync_char_release(struct inode *inode, struct file *file)
|
||
|
+{
|
||
|
+ struct winesync_device *dev = file->private_data;
|
||
|
+ struct winesync_obj *obj;
|
||
|
+ int id;
|
||
|
+
|
||
|
+ mutex_lock(&dev->table_lock);
|
||
|
+ idr_for_each_entry(&dev->objects, obj, id) {
|
||
|
+ idr_remove(&dev->objects, id);
|
||
|
+ synchronize_rcu();
|
||
|
+ put_obj(obj);
|
||
|
+ }
|
||
|
+ mutex_unlock(&dev->table_lock);
|
||
|
+
|
||
|
+ kfree(dev);
|
||
|
+
|
||
|
+ return 0;
|
||
|
+}
|
||
|
+
|
||
|
+static void winesync_init_obj(struct winesync_obj *obj)
|
||
|
+{
|
||
|
+ kref_init(&obj->refcount);
|
||
|
+ refcount_set(&obj->all_hint, 1);
|
||
|
+ spin_lock_init(&obj->lock);
|
||
|
+ INIT_LIST_HEAD(&obj->any_waiters);
|
||
|
+ INIT_LIST_HEAD(&obj->all_waiters);
|
||
|
+}
|
||
|
+
|
||
|
+static bool is_signaled(struct winesync_obj *obj, __u32 owner)
|
||
|
+{
|
||
|
+ lockdep_assert_held(&obj->lock);
|
||
|
+
|
||
|
+ switch (obj->type) {
|
||
|
+ case WINESYNC_TYPE_SEM:
|
||
|
+ return !!obj->u.sem.count;
|
||
|
+ case WINESYNC_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);
|
||
|
+ return false;
|
||
|
+}
|
||
|
+
|
||
|
+/*
|
||
|
+ * "locked_obj" is an optional pointer to an object which is already locked and
|
||
|
+ * should not be locked again. This is necessary so that changing an object's
|
||
|
+ * state and waking it can be a single atomic operation.
|
||
|
+ */
|
||
|
+static void try_wake_all(struct winesync_device *dev, struct winesync_q *q,
|
||
|
+ struct winesync_obj *locked_obj)
|
||
|
+{
|
||
|
+ __u32 count = q->count;
|
||
|
+ bool can_wake = true;
|
||
|
+ __u32 i;
|
||
|
+
|
||
|
+ lockdep_assert_held(&dev->wait_all_lock);
|
||
|
+ if (locked_obj)
|
||
|
+ lockdep_assert_held(&locked_obj->lock);
|
||
|
+
|
||
|
+ for (i = 0; i < count; i++) {
|
||
|
+ if (q->entries[i].obj != locked_obj)
|
||
|
+ spin_lock(&q->entries[i].obj->lock);
|
||
|
+ }
|
||
|
+
|
||
|
+ for (i = 0; i < count; i++) {
|
||
|
+ if (!is_signaled(q->entries[i].obj, q->owner)) {
|
||
|
+ can_wake = false;
|
||
|
+ break;
|
||
|
+ }
|
||
|
+ }
|
||
|
+
|
||
|
+ if (can_wake && atomic_cmpxchg(&q->signaled, -1, 0) == -1) {
|
||
|
+ for (i = 0; i < count; i++) {
|
||
|
+ struct winesync_obj *obj = q->entries[i].obj;
|
||
|
+
|
||
|
+ switch (obj->type) {
|
||
|
+ case WINESYNC_TYPE_SEM:
|
||
|
+ if (obj->u.sem.flags & WINESYNC_SEM_GETONWAIT)
|
||
|
+ obj->u.sem.count--;
|
||
|
+ break;
|
||
|
+ case WINESYNC_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;
|
||
|
+ }
|
||
|
+ }
|
||
|
+ wake_up_process(q->task);
|
||
|
+ }
|
||
|
+
|
||
|
+ for (i = 0; i < count; i++) {
|
||
|
+ if (q->entries[i].obj != locked_obj)
|
||
|
+ spin_unlock(&q->entries[i].obj->lock);
|
||
|
+ }
|
||
|
+}
|
||
|
+
|
||
|
+static void try_wake_all_obj(struct winesync_device *dev,
|
||
|
+ struct winesync_obj *obj)
|
||
|
+{
|
||
|
+ struct winesync_q_entry *entry;
|
||
|
+
|
||
|
+ lockdep_assert_held(&dev->wait_all_lock);
|
||
|
+ lockdep_assert_held(&obj->lock);
|
||
|
+
|
||
|
+ list_for_each_entry(entry, &obj->all_waiters, node)
|
||
|
+ try_wake_all(dev, entry->q, obj);
|
||
|
+}
|
||
|
+
|
||
|
+static void try_wake_any_sem(struct winesync_obj *sem)
|
||
|
+{
|
||
|
+ struct winesync_q_entry *entry;
|
||
|
+
|
||
|
+ lockdep_assert_held(&sem->lock);
|
||
|
+
|
||
|
+ list_for_each_entry(entry, &sem->any_waiters, node) {
|
||
|
+ struct winesync_q *q = entry->q;
|
||
|
+
|
||
|
+ if (!sem->u.sem.count)
|
||
|
+ break;
|
||
|
+
|
||
|
+ if (atomic_cmpxchg(&q->signaled, -1, entry->index) == -1) {
|
||
|
+ if (sem->u.sem.flags & WINESYNC_SEM_GETONWAIT)
|
||
|
+ sem->u.sem.count--;
|
||
|
+ wake_up_process(q->task);
|
||
|
+ }
|
||
|
+ }
|
||
|
+}
|
||
|
+
|
||
|
+static void try_wake_any_mutex(struct winesync_obj *mutex)
|
||
|
+{
|
||
|
+ struct winesync_q_entry *entry;
|
||
|
+
|
||
|
+ lockdep_assert_held(&mutex->lock);
|
||
|
+
|
||
|
+ list_for_each_entry(entry, &mutex->any_waiters, node) {
|
||
|
+ struct winesync_q *q = entry->q;
|
||
|
+
|
||
|
+ if (mutex->u.mutex.count == UINT_MAX)
|
||
|
+ break;
|
||
|
+ if (mutex->u.mutex.owner && mutex->u.mutex.owner != q->owner)
|
||
|
+ continue;
|
||
|
+
|
||
|
+ if (atomic_cmpxchg(&q->signaled, -1, entry->index) == -1) {
|
||
|
+ 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);
|
||
|
+ }
|
||
|
+ }
|
||
|
+}
|
||
|
+
|
||
|
+static int winesync_create_sem(struct winesync_device *dev, void __user *argp)
|
||
|
+{
|
||
|
+ struct winesync_sem_args __user *user_args = argp;
|
||
|
+ struct winesync_sem_args args;
|
||
|
+ struct winesync_obj *sem;
|
||
|
+ int ret;
|
||
|
+
|
||
|
+ if (copy_from_user(&args, argp, sizeof(args)))
|
||
|
+ return -EFAULT;
|
||
|
+
|
||
|
+ if (args.count > args.max)
|
||
|
+ return -EINVAL;
|
||
|
+
|
||
|
+ if (args.flags & ~WINESYNC_SEM_GETONWAIT)
|
||
|
+ return -EINVAL;
|
||
|
+
|
||
|
+ sem = kzalloc(sizeof(*sem), GFP_KERNEL);
|
||
|
+ if (!sem)
|
||
|
+ return -ENOMEM;
|
||
|
+
|
||
|
+ winesync_init_obj(sem);
|
||
|
+ sem->type = WINESYNC_TYPE_SEM;
|
||
|
+ sem->u.sem.count = args.count;
|
||
|
+ sem->u.sem.max = args.max;
|
||
|
+ sem->u.sem.flags = args.flags;
|
||
|
+
|
||
|
+ mutex_lock(&dev->table_lock);
|
||
|
+ ret = idr_alloc(&dev->objects, sem, 0, 0, GFP_KERNEL);
|
||
|
+ mutex_unlock(&dev->table_lock);
|
||
|
+
|
||
|
+ if (ret < 0) {
|
||
|
+ kfree(sem);
|
||
|
+ return ret;
|
||
|
+ }
|
||
|
+
|
||
|
+ return put_user(ret, &user_args->sem);
|
||
|
+}
|
||
|
+
|
||
|
+static int winesync_create_mutex(struct winesync_device *dev, void __user *argp)
|
||
|
+{
|
||
|
+ struct winesync_mutex_args __user *user_args = argp;
|
||
|
+ struct winesync_mutex_args args;
|
||
|
+ struct winesync_obj *mutex;
|
||
|
+ int ret;
|
||
|
+
|
||
|
+ if (copy_from_user(&args, argp, sizeof(args)))
|
||
|
+ return -EFAULT;
|
||
|
+
|
||
|
+ if (!args.owner != !args.count)
|
||
|
+ return -EINVAL;
|
||
|
+
|
||
|
+ mutex = kzalloc(sizeof(*mutex), GFP_KERNEL);
|
||
|
+ if (!mutex)
|
||
|
+ return -ENOMEM;
|
||
|
+
|
||
|
+ winesync_init_obj(mutex);
|
||
|
+ mutex->type = WINESYNC_TYPE_MUTEX;
|
||
|
+ mutex->u.mutex.count = args.count;
|
||
|
+ mutex->u.mutex.owner = args.owner;
|
||
|
+
|
||
|
+ mutex_lock(&dev->table_lock);
|
||
|
+ ret = idr_alloc(&dev->objects, mutex, 0, 0, GFP_KERNEL);
|
||
|
+ mutex_unlock(&dev->table_lock);
|
||
|
+
|
||
|
+ if (ret < 0) {
|
||
|
+ kfree(mutex);
|
||
|
+ return ret;
|
||
|
+ }
|
||
|
+
|
||
|
+ return put_user(ret, &user_args->mutex);
|
||
|
+}
|
||
|
+
|
||
|
+static int winesync_delete(struct winesync_device *dev, void __user *argp)
|
||
|
+{
|
||
|
+ struct winesync_obj *obj;
|
||
|
+ __s32 id;
|
||
|
+
|
||
|
+ if (get_user(id, (__s32 __user *)argp))
|
||
|
+ return -EFAULT;
|
||
|
+
|
||
|
+ mutex_lock(&dev->table_lock);
|
||
|
+ obj = idr_remove(&dev->objects, id);
|
||
|
+ mutex_unlock(&dev->table_lock);
|
||
|
+
|
||
|
+ if (!obj)
|
||
|
+ return -EINVAL;
|
||
|
+
|
||
|
+ put_obj(obj);
|
||
|
+ return 0;
|
||
|
+}
|
||
|
+
|
||
|
+static int winesync_get_sem(struct winesync_device *dev, void __user *argp)
|
||
|
+{
|
||
|
+ struct winesync_obj *sem;
|
||
|
+ int ret = -EWOULDBLOCK;
|
||
|
+ __s32 id;
|
||
|
+
|
||
|
+ if (get_user(id, (__s32 __user *)argp))
|
||
|
+ return -EFAULT;
|
||
|
+
|
||
|
+ sem = get_obj(dev, id);
|
||
|
+ if (!sem)
|
||
|
+ return -EINVAL;
|
||
|
+ if (sem->type != WINESYNC_TYPE_SEM) {
|
||
|
+ put_obj(sem);
|
||
|
+ return -EINVAL;
|
||
|
+ }
|
||
|
+
|
||
|
+ spin_lock(&sem->lock);
|
||
|
+
|
||
|
+ if (sem->u.sem.count) {
|
||
|
+ /*
|
||
|
+ * Decrement the semaphore's count, regardless of whether it
|
||
|
+ * has the WINESYNC_SEM_GETONWAIT flag set.
|
||
|
+ */
|
||
|
+ sem->u.sem.count--;
|
||
|
+ ret = 0;
|
||
|
+ }
|
||
|
+
|
||
|
+ spin_unlock(&sem->lock);
|
||
|
+
|
||
|
+ put_obj(sem);
|
||
|
+
|
||
|
+ return ret;
|
||
|
+}
|
||
|
+
|
||
|
+/*
|
||
|
+ * Actually change the semaphore state, returning -EOVERFLOW if it is made
|
||
|
+ * invalid.
|
||
|
+ */
|
||
|
+static int put_sem_state(struct winesync_obj *sem, __u32 count)
|
||
|
+{
|
||
|
+ lockdep_assert_held(&sem->lock);
|
||
|
+
|
||
|
+ if (sem->u.sem.count + count < sem->u.sem.count ||
|
||
|
+ sem->u.sem.count + count > sem->u.sem.max)
|
||
|
+ return -EOVERFLOW;
|
||
|
+
|
||
|
+ sem->u.sem.count += count;
|
||
|
+ return 0;
|
||
|
+}
|
||
|
+
|
||
|
+static int winesync_put_sem(struct winesync_device *dev, void __user *argp,
|
||
|
+ bool pulse)
|
||
|
+{
|
||
|
+ struct winesync_sem_args __user *user_args = argp;
|
||
|
+ struct winesync_sem_args args;
|
||
|
+ struct winesync_obj *sem;
|
||
|
+ __u32 prev_count;
|
||
|
+ int ret;
|
||
|
+
|
||
|
+ if (copy_from_user(&args, argp, sizeof(args)))
|
||
|
+ return -EFAULT;
|
||
|
+
|
||
|
+ sem = get_obj(dev, args.sem);
|
||
|
+ if (!sem)
|
||
|
+ return -EINVAL;
|
||
|
+ if (sem->type != WINESYNC_TYPE_SEM) {
|
||
|
+ put_obj(sem);
|
||
|
+ return -EINVAL;
|
||
|
+ }
|
||
|
+
|
||
|
+ if (refcount_read(&sem->all_hint) > 1) {
|
||
|
+ spin_lock(&dev->wait_all_lock);
|
||
|
+ spin_lock(&sem->lock);
|
||
|
+
|
||
|
+ prev_count = sem->u.sem.count;
|
||
|
+ ret = put_sem_state(sem, args.count);
|
||
|
+ if (!ret) {
|
||
|
+ try_wake_all_obj(dev, sem);
|
||
|
+ try_wake_any_sem(sem);
|
||
|
+ }
|
||
|
+
|
||
|
+ if (pulse)
|
||
|
+ sem->u.sem.count = 0;
|
||
|
+
|
||
|
+ spin_unlock(&sem->lock);
|
||
|
+ spin_unlock(&dev->wait_all_lock);
|
||
|
+ } else {
|
||
|
+ spin_lock(&sem->lock);
|
||
|
+
|
||
|
+ prev_count = sem->u.sem.count;
|
||
|
+ ret = put_sem_state(sem, args.count);
|
||
|
+ if (!ret)
|
||
|
+ try_wake_any_sem(sem);
|
||
|
+
|
||
|
+ if (pulse)
|
||
|
+ sem->u.sem.count = 0;
|
||
|
+
|
||
|
+ spin_unlock(&sem->lock);
|
||
|
+ }
|
||
|
+
|
||
|
+ put_obj(sem);
|
||
|
+
|
||
|
+ if (!ret && put_user(prev_count, &user_args->count))
|
||
|
+ ret = -EFAULT;
|
||
|
+
|
||
|
+ return ret;
|
||
|
+}
|
||
|
+
|
||
|
+/*
|
||
|
+ * Actually change the mutex state, returning -EPERM if not the owner.
|
||
|
+ */
|
||
|
+static int put_mutex_state(struct winesync_obj *mutex,
|
||
|
+ const struct winesync_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 winesync_put_mutex(struct winesync_device *dev, void __user *argp)
|
||
|
+{
|
||
|
+ struct winesync_mutex_args __user *user_args = argp;
|
||
|
+ struct winesync_mutex_args args;
|
||
|
+ struct winesync_obj *mutex;
|
||
|
+ __u32 prev_count;
|
||
|
+ int ret;
|
||
|
+
|
||
|
+ if (copy_from_user(&args, argp, sizeof(args)))
|
||
|
+ return -EFAULT;
|
||
|
+ if (!args.owner)
|
||
|
+ return -EINVAL;
|
||
|
+
|
||
|
+ mutex = get_obj(dev, args.mutex);
|
||
|
+ if (!mutex)
|
||
|
+ return -EINVAL;
|
||
|
+ if (mutex->type != WINESYNC_TYPE_MUTEX) {
|
||
|
+ put_obj(mutex);
|
||
|
+ return -EINVAL;
|
||
|
+ }
|
||
|
+
|
||
|
+ if (refcount_read(&mutex->all_hint) > 1) {
|
||
|
+ spin_lock(&dev->wait_all_lock);
|
||
|
+ spin_lock(&mutex->lock);
|
||
|
+
|
||
|
+ prev_count = mutex->u.mutex.count;
|
||
|
+ ret = put_mutex_state(mutex, &args);
|
||
|
+ if (!ret) {
|
||
|
+ try_wake_all_obj(dev, mutex);
|
||
|
+ try_wake_any_mutex(mutex);
|
||
|
+ }
|
||
|
+
|
||
|
+ spin_unlock(&mutex->lock);
|
||
|
+ spin_unlock(&dev->wait_all_lock);
|
||
|
+ } else {
|
||
|
+ spin_lock(&mutex->lock);
|
||
|
+
|
||
|
+ prev_count = mutex->u.mutex.count;
|
||
|
+ ret = put_mutex_state(mutex, &args);
|
||
|
+ if (!ret)
|
||
|
+ try_wake_any_mutex(mutex);
|
||
|
+
|
||
|
+ spin_unlock(&mutex->lock);
|
||
|
+ }
|
||
|
+
|
||
|
+ put_obj(mutex);
|
||
|
+
|
||
|
+ if (!ret && put_user(prev_count, &user_args->count))
|
||
|
+ ret = -EFAULT;
|
||
|
+
|
||
|
+ return ret;
|
||
|
+}
|
||
|
+
|
||
|
+static int winesync_read_sem(struct winesync_device *dev, void __user *argp)
|
||
|
+{
|
||
|
+ struct winesync_sem_args __user *user_args = argp;
|
||
|
+ struct winesync_sem_args args;
|
||
|
+ struct winesync_obj *sem;
|
||
|
+ __s32 id;
|
||
|
+
|
||
|
+ if (get_user(id, &user_args->sem))
|
||
|
+ return -EFAULT;
|
||
|
+
|
||
|
+ sem = get_obj(dev, id);
|
||
|
+ if (!sem)
|
||
|
+ return -EINVAL;
|
||
|
+ if (sem->type != WINESYNC_TYPE_SEM) {
|
||
|
+ put_obj(sem);
|
||
|
+ return -EINVAL;
|
||
|
+ }
|
||
|
+
|
||
|
+ args.sem = id;
|
||
|
+ spin_lock(&sem->lock);
|
||
|
+ args.count = sem->u.sem.count;
|
||
|
+ args.max = sem->u.sem.max;
|
||
|
+ args.flags = sem->u.sem.flags;
|
||
|
+ spin_unlock(&sem->lock);
|
||
|
+
|
||
|
+ put_obj(sem);
|
||
|
+
|
||
|
+ if (copy_to_user(user_args, &args, sizeof(args)))
|
||
|
+ return -EFAULT;
|
||
|
+ return 0;
|
||
|
+}
|
||
|
+
|
||
|
+static int winesync_read_mutex(struct winesync_device *dev, void __user *argp)
|
||
|
+{
|
||
|
+ struct winesync_mutex_args __user *user_args = argp;
|
||
|
+ struct winesync_mutex_args args;
|
||
|
+ struct winesync_obj *mutex;
|
||
|
+ __s32 id;
|
||
|
+ int ret;
|
||
|
+
|
||
|
+ if (get_user(id, &user_args->mutex))
|
||
|
+ return -EFAULT;
|
||
|
+
|
||
|
+ mutex = get_obj(dev, id);
|
||
|
+ if (!mutex)
|
||
|
+ return -EINVAL;
|
||
|
+ if (mutex->type != WINESYNC_TYPE_MUTEX) {
|
||
|
+ put_obj(mutex);
|
||
|
+ return -EINVAL;
|
||
|
+ }
|
||
|
+
|
||
|
+ args.mutex = id;
|
||
|
+ spin_lock(&mutex->lock);
|
||
|
+ args.count = mutex->u.mutex.count;
|
||
|
+ args.owner = mutex->u.mutex.owner;
|
||
|
+ ret = mutex->u.mutex.ownerdead ? -EOWNERDEAD : 0;
|
||
|
+ spin_unlock(&mutex->lock);
|
||
|
+
|
||
|
+ put_obj(mutex);
|
||
|
+
|
||
|
+ if (copy_to_user(user_args, &args, sizeof(args)))
|
||
|
+ return -EFAULT;
|
||
|
+ return ret;
|
||
|
+}
|
||
|
+
|
||
|
+/*
|
||
|
+ * Actually change the mutex state to mark its owner as dead.
|
||
|
+ */
|
||
|
+static void put_mutex_ownerdead_state(struct winesync_obj *mutex)
|
||
|
+{
|
||
|
+ lockdep_assert_held(&mutex->lock);
|
||
|
+
|
||
|
+ mutex->u.mutex.ownerdead = true;
|
||
|
+ mutex->u.mutex.owner = 0;
|
||
|
+ mutex->u.mutex.count = 0;
|
||
|
+}
|
||
|
+
|
||
|
+static int winesync_kill_owner(struct winesync_device *dev, void __user *argp)
|
||
|
+{
|
||
|
+ struct winesync_obj *obj;
|
||
|
+ __u32 owner;
|
||
|
+ int id;
|
||
|
+
|
||
|
+ if (get_user(owner, (__u32 __user *)argp))
|
||
|
+ return -EFAULT;
|
||
|
+ if (!owner)
|
||
|
+ return -EINVAL;
|
||
|
+
|
||
|
+ rcu_read_lock();
|
||
|
+
|
||
|
+ idr_for_each_entry(&dev->objects, obj, id) {
|
||
|
+ if (!kref_get_unless_zero(&obj->refcount))
|
||
|
+ continue;
|
||
|
+
|
||
|
+ if (obj->type != WINESYNC_TYPE_MUTEX) {
|
||
|
+ put_obj(obj);
|
||
|
+ continue;
|
||
|
+ }
|
||
|
+
|
||
|
+ if (refcount_read(&obj->all_hint) > 1) {
|
||
|
+ spin_lock(&dev->wait_all_lock);
|
||
|
+ spin_lock(&obj->lock);
|
||
|
+
|
||
|
+ if (obj->u.mutex.owner == owner) {
|
||
|
+ put_mutex_ownerdead_state(obj);
|
||
|
+ try_wake_all_obj(dev, obj);
|
||
|
+ try_wake_any_mutex(obj);
|
||
|
+ }
|
||
|
+
|
||
|
+ spin_unlock(&obj->lock);
|
||
|
+ spin_unlock(&dev->wait_all_lock);
|
||
|
+ } else {
|
||
|
+ spin_lock(&obj->lock);
|
||
|
+
|
||
|
+ if (obj->u.mutex.owner == owner) {
|
||
|
+ put_mutex_ownerdead_state(obj);
|
||
|
+ try_wake_any_mutex(obj);
|
||
|
+ }
|
||
|
+
|
||
|
+ spin_unlock(&obj->lock);
|
||
|
+ }
|
||
|
+
|
||
|
+ put_obj(obj);
|
||
|
+ }
|
||
|
+
|
||
|
+ rcu_read_unlock();
|
||
|
+
|
||
|
+ return 0;
|
||
|
+}
|
||
|
+
|
||
|
+static int winesync_schedule(const struct winesync_q *q, ktime_t *timeout)
|
||
|
+{
|
||
|
+ int ret = 0;
|
||
|
+
|
||
|
+ do {
|
||
|
+ if (signal_pending(current)) {
|
||
|
+ ret = -ERESTARTSYS;
|
||
|
+ break;
|
||
|
+ }
|
||
|
+
|
||
|
+ set_current_state(TASK_INTERRUPTIBLE);
|
||
|
+ if (atomic_read(&q->signaled) != -1) {
|
||
|
+ ret = 0;
|
||
|
+ break;
|
||
|
+ }
|
||
|
+ ret = schedule_hrtimeout(timeout, HRTIMER_MODE_ABS);
|
||
|
+ } while (ret < 0);
|
||
|
+ __set_current_state(TASK_RUNNING);
|
||
|
+
|
||
|
+ return ret;
|
||
|
+}
|
||
|
+
|
||
|
+/*
|
||
|
+ * Allocate and initialize most of the winesync_q structure, but do not queue us
|
||
|
+ * yet. Also, calculate the relative timeout in jiffies.
|
||
|
+ */
|
||
|
+static int setup_wait(struct winesync_device *dev,
|
||
|
+ const struct winesync_wait_args *args, bool all,
|
||
|
+ ktime_t *ret_timeout, struct winesync_q **ret_q)
|
||
|
+{
|
||
|
+ const __u32 count = args->count;
|
||
|
+ struct winesync_q *q;
|
||
|
+ ktime_t timeout = 0;
|
||
|
+ __s32 *ids;
|
||
|
+ __u32 i, j;
|
||
|
+
|
||
|
+ if (args->timeout) {
|
||
|
+ struct timespec64 to;
|
||
|
+
|
||
|
+ if (get_timespec64(&to, u64_to_user_ptr(args->timeout)))
|
||
|
+ return -EFAULT;
|
||
|
+ if (!timespec64_valid(&to))
|
||
|
+ return -EINVAL;
|
||
|
+
|
||
|
+ timeout = timespec64_to_ns(&to);
|
||
|
+ }
|
||
|
+
|
||
|
+ ids = kmalloc_array(args->count, sizeof(*ids), GFP_KERNEL);
|
||
|
+ if (!ids)
|
||
|
+ return -ENOMEM;
|
||
|
+ if (copy_from_user(ids, u64_to_user_ptr(args->objs),
|
||
|
+ array_size(args->count, sizeof(*ids)))) {
|
||
|
+ kfree(ids);
|
||
|
+ return -EFAULT;
|
||
|
+ }
|
||
|
+
|
||
|
+ q = kmalloc(struct_size(q, entries, count), GFP_KERNEL);
|
||
|
+ if (!q) {
|
||
|
+ kfree(ids);
|
||
|
+ return -ENOMEM;
|
||
|
+ }
|
||
|
+ q->task = current;
|
||
|
+ q->owner = args->owner;
|
||
|
+ atomic_set(&q->signaled, -1);
|
||
|
+ q->all = all;
|
||
|
+ q->ownerdead = false;
|
||
|
+ q->count = count;
|
||
|
+
|
||
|
+ for (i = 0; i < count; i++) {
|
||
|
+ struct winesync_q_entry *entry = &q->entries[i];
|
||
|
+ struct winesync_obj *obj = get_obj(dev, ids[i]);
|
||
|
+
|
||
|
+ if (!obj)
|
||
|
+ goto err;
|
||
|
+
|
||
|
+ 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;
|
||
|
+ }
|
||
|
+
|
||
|
+ kfree(ids);
|
||
|
+
|
||
|
+ *ret_q = q;
|
||
|
+ *ret_timeout = timeout;
|
||
|
+ return 0;
|
||
|
+
|
||
|
+err:
|
||
|
+ for (j = 0; j < i; j++)
|
||
|
+ put_obj(q->entries[j].obj);
|
||
|
+ kfree(ids);
|
||
|
+ kfree(q);
|
||
|
+ return -EINVAL;
|
||
|
+}
|
||
|
+
|
||
|
+static void try_wake_any_obj(struct winesync_obj *obj)
|
||
|
+{
|
||
|
+ switch (obj->type) {
|
||
|
+ case WINESYNC_TYPE_SEM:
|
||
|
+ try_wake_any_sem(obj);
|
||
|
+ break;
|
||
|
+ case WINESYNC_TYPE_MUTEX:
|
||
|
+ try_wake_any_mutex(obj);
|
||
|
+ break;
|
||
|
+ }
|
||
|
+}
|
||
|
+
|
||
|
+static int winesync_wait_any(struct winesync_device *dev, void __user *argp)
|
||
|
+{
|
||
|
+ struct winesync_wait_args args;
|
||
|
+ struct winesync_q *q;
|
||
|
+ ktime_t timeout;
|
||
|
+ __u32 i;
|
||
|
+ int ret;
|
||
|
+
|
||
|
+ if (copy_from_user(&args, argp, sizeof(args)))
|
||
|
+ return -EFAULT;
|
||
|
+ if (!args.owner)
|
||
|
+ return -EINVAL;
|
||
|
+
|
||
|
+ ret = setup_wait(dev, &args, false, &timeout, &q);
|
||
|
+ if (ret < 0)
|
||
|
+ return ret;
|
||
|
+
|
||
|
+ /* queue ourselves */
|
||
|
+
|
||
|
+ for (i = 0; i < args.count; i++) {
|
||
|
+ struct winesync_q_entry *entry = &q->entries[i];
|
||
|
+ struct winesync_obj *obj = q->entries[i].obj;
|
||
|
+
|
||
|
+ spin_lock(&obj->lock);
|
||
|
+ list_add_tail(&entry->node, &obj->any_waiters);
|
||
|
+ spin_unlock(&obj->lock);
|
||
|
+ }
|
||
|
+
|
||
|
+ /* check if we are already signaled */
|
||
|
+
|
||
|
+ for (i = 0; i < args.count; i++) {
|
||
|
+ struct winesync_obj *obj = q->entries[i].obj;
|
||
|
+
|
||
|
+ if (atomic_read(&q->signaled) != -1)
|
||
|
+ break;
|
||
|
+
|
||
|
+ spin_lock(&obj->lock);
|
||
|
+ try_wake_any_obj(obj);
|
||
|
+ spin_unlock(&obj->lock);
|
||
|
+ }
|
||
|
+
|
||
|
+ /* sleep */
|
||
|
+
|
||
|
+ ret = winesync_schedule(q, args.timeout ? &timeout : NULL);
|
||
|
+
|
||
|
+ /* and finally, unqueue */
|
||
|
+
|
||
|
+ for (i = 0; i < args.count; i++) {
|
||
|
+ struct winesync_obj *obj = q->entries[i].obj;
|
||
|
+
|
||
|
+ spin_lock(&obj->lock);
|
||
|
+ list_del(&q->entries[i].node);
|
||
|
+ spin_unlock(&obj->lock);
|
||
|
+
|
||
|
+ put_obj(obj);
|
||
|
+ }
|
||
|
+
|
||
|
+ if (atomic_read(&q->signaled) != -1) {
|
||
|
+ struct winesync_wait_args __user *user_args = argp;
|
||
|
+
|
||
|
+ /* even if we caught a signal, we need to communicate success */
|
||
|
+ ret = q->ownerdead ? -EOWNERDEAD : 0;
|
||
|
+
|
||
|
+ if (put_user(atomic_read(&q->signaled), &user_args->index))
|
||
|
+ ret = -EFAULT;
|
||
|
+ } else if (!ret) {
|
||
|
+ ret = -ETIMEDOUT;
|
||
|
+ }
|
||
|
+
|
||
|
+ kfree(q);
|
||
|
+ return ret;
|
||
|
+}
|
||
|
+
|
||
|
+static int winesync_wait_all(struct winesync_device *dev, void __user *argp)
|
||
|
+{
|
||
|
+ struct winesync_wait_args args;
|
||
|
+ struct winesync_q *q;
|
||
|
+ ktime_t timeout;
|
||
|
+ __u32 i;
|
||
|
+ int ret;
|
||
|
+
|
||
|
+ if (copy_from_user(&args, argp, sizeof(args)))
|
||
|
+ return -EFAULT;
|
||
|
+ if (!args.owner)
|
||
|
+ return -EINVAL;
|
||
|
+
|
||
|
+ ret = setup_wait(dev, &args, true, &timeout, &q);
|
||
|
+ if (ret < 0)
|
||
|
+ return ret;
|
||
|
+
|
||
|
+ /* queue ourselves */
|
||
|
+
|
||
|
+ spin_lock(&dev->wait_all_lock);
|
||
|
+
|
||
|
+ for (i = 0; i < args.count; i++) {
|
||
|
+ struct winesync_q_entry *entry = &q->entries[i];
|
||
|
+ struct winesync_obj *obj = q->entries[i].obj;
|
||
|
+
|
||
|
+ refcount_inc(&obj->all_hint);
|
||
|
+
|
||
|
+ /*
|
||
|
+ * obj->all_waiters is protected by dev->wait_all_lock rather
|
||
|
+ * than obj->lock, so there is no need to acquire it here.
|
||
|
+ */
|
||
|
+ list_add_tail(&entry->node, &obj->all_waiters);
|
||
|
+ }
|
||
|
+
|
||
|
+ /* check if we are already signaled */
|
||
|
+
|
||
|
+ try_wake_all(dev, q, NULL);
|
||
|
+
|
||
|
+ spin_unlock(&dev->wait_all_lock);
|
||
|
+
|
||
|
+ /* sleep */
|
||
|
+
|
||
|
+ ret = winesync_schedule(q, args.timeout ? &timeout : NULL);
|
||
|
+
|
||
|
+ /* and finally, unqueue */
|
||
|
+
|
||
|
+ spin_lock(&dev->wait_all_lock);
|
||
|
+
|
||
|
+ for (i = 0; i < args.count; i++) {
|
||
|
+ struct winesync_q_entry *entry = &q->entries[i];
|
||
|
+ struct winesync_obj *obj = q->entries[i].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);
|
||
|
+
|
||
|
+ refcount_dec(&obj->all_hint);
|
||
|
+
|
||
|
+ put_obj(obj);
|
||
|
+ }
|
||
|
+
|
||
|
+ spin_unlock(&dev->wait_all_lock);
|
||
|
+
|
||
|
+ if (atomic_read(&q->signaled) != -1) {
|
||
|
+ /* even if we caught a signal, we need to communicate success */
|
||
|
+ ret = q->ownerdead ? -EOWNERDEAD : 0;
|
||
|
+ } else if (!ret) {
|
||
|
+ ret = -ETIMEDOUT;
|
||
|
+ }
|
||
|
+
|
||
|
+ kfree(q);
|
||
|
+ return ret;
|
||
|
+}
|
||
|
+
|
||
|
+static long winesync_char_ioctl(struct file *file, unsigned int cmd,
|
||
|
+ unsigned long parm)
|
||
|
+{
|
||
|
+ struct winesync_device *dev = file->private_data;
|
||
|
+ void __user *argp = (void __user *)parm;
|
||
|
+
|
||
|
+ switch (cmd) {
|
||
|
+ case WINESYNC_IOC_CREATE_SEM:
|
||
|
+ return winesync_create_sem(dev, argp);
|
||
|
+ case WINESYNC_IOC_CREATE_MUTEX:
|
||
|
+ return winesync_create_mutex(dev, argp);
|
||
|
+ case WINESYNC_IOC_DELETE:
|
||
|
+ return winesync_delete(dev, argp);
|
||
|
+ case WINESYNC_IOC_GET_SEM:
|
||
|
+ return winesync_get_sem(dev, argp);
|
||
|
+ case WINESYNC_IOC_PUT_SEM:
|
||
|
+ return winesync_put_sem(dev, argp, false);
|
||
|
+ case WINESYNC_IOC_PULSE_SEM:
|
||
|
+ return winesync_put_sem(dev, argp, true);
|
||
|
+ case WINESYNC_IOC_PUT_MUTEX:
|
||
|
+ return winesync_put_mutex(dev, argp);
|
||
|
+ case WINESYNC_IOC_READ_SEM:
|
||
|
+ return winesync_read_sem(dev, argp);
|
||
|
+ case WINESYNC_IOC_READ_MUTEX:
|
||
|
+ return winesync_read_mutex(dev, argp);
|
||
|
+ case WINESYNC_IOC_KILL_OWNER:
|
||
|
+ return winesync_kill_owner(dev, argp);
|
||
|
+ case WINESYNC_IOC_WAIT_ANY:
|
||
|
+ return winesync_wait_any(dev, argp);
|
||
|
+ case WINESYNC_IOC_WAIT_ALL:
|
||
|
+ return winesync_wait_all(dev, argp);
|
||
|
+ default:
|
||
|
+ return -ENOSYS;
|
||
|
+ }
|
||
|
+}
|
||
|
+
|
||
|
+static const struct file_operations winesync_fops = {
|
||
|
+ .owner = THIS_MODULE,
|
||
|
+ .open = winesync_char_open,
|
||
|
+ .release = winesync_char_release,
|
||
|
+ .unlocked_ioctl = winesync_char_ioctl,
|
||
|
+ .compat_ioctl = winesync_char_ioctl,
|
||
|
+ .llseek = no_llseek,
|
||
|
+};
|
||
|
+
|
||
|
+static struct miscdevice winesync_misc = {
|
||
|
+ .minor = WINESYNC_MINOR,
|
||
|
+ .name = WINESYNC_NAME,
|
||
|
+ .fops = &winesync_fops,
|
||
|
+};
|
||
|
+
|
||
|
+static int __init winesync_init(void)
|
||
|
+{
|
||
|
+ return misc_register(&winesync_misc);
|
||
|
+}
|
||
|
+
|
||
|
+static void __exit winesync_exit(void)
|
||
|
+{
|
||
|
+ misc_deregister(&winesync_misc);
|
||
|
+}
|
||
|
+
|
||
|
+module_init(winesync_init);
|
||
|
+module_exit(winesync_exit);
|
||
|
+
|
||
|
+MODULE_AUTHOR("Zebediah Figura");
|
||
|
+MODULE_DESCRIPTION("Kernel driver for Wine synchronization primitives");
|
||
|
+MODULE_LICENSE("GPL");
|
||
|
+MODULE_ALIAS("devname:" WINESYNC_NAME);
|
||
|
+MODULE_ALIAS_MISCDEV(WINESYNC_MINOR);
|
||
|
diff --git a/include/linux/miscdevice.h b/include/linux/miscdevice.h
|
||
|
index 0676f18093f9..350aecfcfb29 100644
|
||
|
--- a/include/linux/miscdevice.h
|
||
|
+++ b/include/linux/miscdevice.h
|
||
|
@@ -71,6 +71,7 @@
|
||
|
#define USERIO_MINOR 240
|
||
|
#define VHOST_VSOCK_MINOR 241
|
||
|
#define RFKILL_MINOR 242
|
||
|
+#define WINESYNC_MINOR 243
|
||
|
#define MISC_DYNAMIC_MINOR 255
|
||
|
|
||
|
struct device;
|
||
|
diff --git a/include/uapi/linux/winesync.h b/include/uapi/linux/winesync.h
|
||
|
new file mode 100644
|
||
|
index 000000000000..efc591795249
|
||
|
--- /dev/null
|
||
|
+++ b/include/uapi/linux/winesync.h
|
||
|
@@ -0,0 +1,61 @@
|
||
|
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
|
||
|
+/*
|
||
|
+ * Kernel support for Wine synchronization primitives
|
||
|
+ *
|
||
|
+ * Copyright (C) 2021 Zebediah Figura
|
||
|
+ */
|
||
|
+
|
||
|
+#ifndef __LINUX_WINESYNC_H
|
||
|
+#define __LINUX_WINESYNC_H
|
||
|
+
|
||
|
+#include <linux/types.h>
|
||
|
+
|
||
|
+#define WINESYNC_SEM_GETONWAIT 1
|
||
|
+
|
||
|
+struct winesync_sem_args {
|
||
|
+ __s32 sem;
|
||
|
+ __u32 count;
|
||
|
+ __u32 max;
|
||
|
+ __u32 flags;
|
||
|
+};
|
||
|
+
|
||
|
+struct winesync_mutex_args {
|
||
|
+ __s32 mutex;
|
||
|
+ __u32 owner;
|
||
|
+ __u32 count;
|
||
|
+};
|
||
|
+
|
||
|
+struct winesync_wait_args {
|
||
|
+ __u64 timeout;
|
||
|
+ __u64 objs;
|
||
|
+ __u32 count;
|
||
|
+ __u32 owner;
|
||
|
+ __u32 index;
|
||
|
+ __u32 pad;
|
||
|
+};
|
||
|
+
|
||
|
+#define WINESYNC_IOC_BASE 0xf7
|
||
|
+
|
||
|
+#define WINESYNC_IOC_CREATE_SEM _IOWR(WINESYNC_IOC_BASE, 0, \
|
||
|
+ struct winesync_sem_args)
|
||
|
+#define WINESYNC_IOC_DELETE _IOW (WINESYNC_IOC_BASE, 1, __s32)
|
||
|
+#define WINESYNC_IOC_PUT_SEM _IOWR(WINESYNC_IOC_BASE, 2, \
|
||
|
+ struct winesync_sem_args)
|
||
|
+#define WINESYNC_IOC_WAIT_ANY _IOWR(WINESYNC_IOC_BASE, 3, \
|
||
|
+ struct winesync_wait_args)
|
||
|
+#define WINESYNC_IOC_WAIT_ALL _IOW (WINESYNC_IOC_BASE, 4, \
|
||
|
+ struct winesync_wait_args)
|
||
|
+#define WINESYNC_IOC_CREATE_MUTEX _IOWR(WINESYNC_IOC_BASE, 5, \
|
||
|
+ struct winesync_mutex_args)
|
||
|
+#define WINESYNC_IOC_PUT_MUTEX _IOWR(WINESYNC_IOC_BASE, 6, \
|
||
|
+ struct winesync_mutex_args)
|
||
|
+#define WINESYNC_IOC_KILL_OWNER _IOW (WINESYNC_IOC_BASE, 7, __u32)
|
||
|
+#define WINESYNC_IOC_READ_SEM _IOWR(WINESYNC_IOC_BASE, 8, \
|
||
|
+ struct winesync_sem_args)
|
||
|
+#define WINESYNC_IOC_READ_MUTEX _IOWR(WINESYNC_IOC_BASE, 9, \
|
||
|
+ struct winesync_mutex_args)
|
||
|
+#define WINESYNC_IOC_GET_SEM _IOW (WINESYNC_IOC_BASE, 10, __s32)
|
||
|
+#define WINESYNC_IOC_PULSE_SEM _IOWR(WINESYNC_IOC_BASE, 11, \
|
||
|
+ struct winesync_sem_args)
|
||
|
+
|
||
|
+#endif
|
||
|
diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile
|
||
|
index 6c575cf34a71..7f43a50ee19e 100644
|
||
|
--- a/tools/testing/selftests/Makefile
|
||
|
+++ b/tools/testing/selftests/Makefile
|
||
|
@@ -9,6 +9,7 @@ TARGETS += core
|
||
|
TARGETS += cpufreq
|
||
|
TARGETS += cpu-hotplug
|
||
|
TARGETS += drivers/dma-buf
|
||
|
+TARGETS += drivers/winesync
|
||
|
TARGETS += efivarfs
|
||
|
TARGETS += exec
|
||
|
TARGETS += filesystems
|
||
|
diff --git a/tools/testing/selftests/drivers/winesync/Makefile b/tools/testing/selftests/drivers/winesync/Makefile
|
||
|
new file mode 100644
|
||
|
index 000000000000..43b39fdeea10
|
||
|
--- /dev/null
|
||
|
+++ b/tools/testing/selftests/drivers/winesync/Makefile
|
||
|
@@ -0,0 +1,8 @@
|
||
|
+# SPDX-LICENSE-IDENTIFIER: GPL-2.0-only
|
||
|
+TEST_GEN_PROGS := winesync
|
||
|
+
|
||
|
+top_srcdir =../../../../..
|
||
|
+CFLAGS += -I$(top_srcdir)/usr/include
|
||
|
+LDLIBS += -lpthread
|
||
|
+
|
||
|
+include ../../lib.mk
|
||
|
diff --git a/tools/testing/selftests/drivers/winesync/config b/tools/testing/selftests/drivers/winesync/config
|
||
|
new file mode 100644
|
||
|
index 000000000000..60539c826d06
|
||
|
--- /dev/null
|
||
|
+++ b/tools/testing/selftests/drivers/winesync/config
|
||
|
@@ -0,0 +1 @@
|
||
|
+CONFIG_WINESYNC=y
|
||
|
diff --git a/tools/testing/selftests/drivers/winesync/winesync.c b/tools/testing/selftests/drivers/winesync/winesync.c
|
||
|
new file mode 100644
|
||
|
index 000000000000..52373fcd5c8c
|
||
|
--- /dev/null
|
||
|
+++ b/tools/testing/selftests/drivers/winesync/winesync.c
|
||
|
@@ -0,0 +1,1486 @@
|
||
|
+// SPDX-License-Identifier: GPL-2.0-or-later
|
||
|
+/*
|
||
|
+ * Various unit tests for the "winesync" synchronization primitive driver.
|
||
|
+ *
|
||
|
+ * Copyright (C) 2021 Zebediah Figura
|
||
|
+ */
|
||
|
+
|
||
|
+#define _GNU_SOURCE
|
||
|
+#include <sys/ioctl.h>
|
||
|
+#include <sys/stat.h>
|
||
|
+#include <fcntl.h>
|
||
|
+#include <time.h>
|
||
|
+#include <pthread.h>
|
||
|
+#include <linux/winesync.h>
|
||
|
+#include "../../kselftest_harness.h"
|
||
|
+
|
||
|
+TEST(semaphore_state)
|
||
|
+{
|
||
|
+ struct winesync_wait_args wait_args;
|
||
|
+ struct winesync_sem_args sem_args;
|
||
|
+ struct timespec timeout;
|
||
|
+ int fd, ret;
|
||
|
+
|
||
|
+ clock_gettime(CLOCK_MONOTONIC, &timeout);
|
||
|
+
|
||
|
+ fd = open("/dev/winesync", O_CLOEXEC | O_RDONLY);
|
||
|
+ ASSERT_LE(0, fd);
|
||
|
+
|
||
|
+ sem_args.count = 3;
|
||
|
+ sem_args.max = 2;
|
||
|
+ sem_args.sem = 0xdeadbeef;
|
||
|
+ sem_args.flags = 0;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_CREATE_SEM, &sem_args);
|
||
|
+ EXPECT_EQ(-1, ret);
|
||
|
+ EXPECT_EQ(EINVAL, errno);
|
||
|
+
|
||
|
+ sem_args.count = 2;
|
||
|
+ sem_args.max = 2;
|
||
|
+ sem_args.sem = 0xdeadbeef;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_CREATE_SEM, &sem_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_NE(0xdeadbeef, sem_args.sem);
|
||
|
+
|
||
|
+ sem_args.count = 0xdeadbeef;
|
||
|
+ sem_args.max = 0xdeadbeef;
|
||
|
+ sem_args.flags = 0xdeadbeef;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_READ_SEM, &sem_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(2, sem_args.count);
|
||
|
+ EXPECT_EQ(2, sem_args.max);
|
||
|
+ EXPECT_EQ(0, sem_args.flags);
|
||
|
+
|
||
|
+ sem_args.count = 0;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_PUT_SEM, &sem_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(2, sem_args.count);
|
||
|
+
|
||
|
+ sem_args.count = 0xdeadbeef;
|
||
|
+ sem_args.max = 0xdeadbeef;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_READ_SEM, &sem_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(2, sem_args.count);
|
||
|
+ EXPECT_EQ(2, sem_args.max);
|
||
|
+
|
||
|
+ sem_args.count = 1;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_PUT_SEM, &sem_args);
|
||
|
+ EXPECT_EQ(-1, ret);
|
||
|
+ EXPECT_EQ(EOVERFLOW, errno);
|
||
|
+
|
||
|
+ sem_args.count = 0xdeadbeef;
|
||
|
+ sem_args.max = 0xdeadbeef;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_READ_SEM, &sem_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(2, sem_args.count);
|
||
|
+ EXPECT_EQ(2, sem_args.max);
|
||
|
+
|
||
|
+ wait_args.timeout = (uintptr_t)&timeout;
|
||
|
+ wait_args.objs = (uintptr_t)&sem_args.sem;
|
||
|
+ wait_args.count = 1;
|
||
|
+ wait_args.owner = 123;
|
||
|
+ wait_args.index = 0xdeadbeef;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_WAIT_ANY, &wait_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(0, wait_args.index);
|
||
|
+
|
||
|
+ sem_args.count = 0xdeadbeef;
|
||
|
+ sem_args.max = 0xdeadbeef;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_READ_SEM, &sem_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(2, sem_args.count);
|
||
|
+ EXPECT_EQ(2, sem_args.max);
|
||
|
+
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_GET_SEM, &sem_args.sem);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+
|
||
|
+ sem_args.count = 0xdeadbeef;
|
||
|
+ sem_args.max = 0xdeadbeef;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_READ_SEM, &sem_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(1, sem_args.count);
|
||
|
+ EXPECT_EQ(2, sem_args.max);
|
||
|
+
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_GET_SEM, &sem_args.sem);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+
|
||
|
+ sem_args.count = 0xdeadbeef;
|
||
|
+ sem_args.max = 0xdeadbeef;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_READ_SEM, &sem_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(0, sem_args.count);
|
||
|
+ EXPECT_EQ(2, sem_args.max);
|
||
|
+
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_GET_SEM, &sem_args.sem);
|
||
|
+ EXPECT_EQ(-1, ret);
|
||
|
+ EXPECT_EQ(EWOULDBLOCK, errno);
|
||
|
+
|
||
|
+ sem_args.count = 3;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_PUT_SEM, &sem_args);
|
||
|
+ EXPECT_EQ(-1, ret);
|
||
|
+ EXPECT_EQ(EOVERFLOW, errno);
|
||
|
+
|
||
|
+ sem_args.count = 0xdeadbeef;
|
||
|
+ sem_args.max = 0xdeadbeef;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_READ_SEM, &sem_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(0, sem_args.count);
|
||
|
+ EXPECT_EQ(2, sem_args.max);
|
||
|
+
|
||
|
+ sem_args.count = 2;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_PUT_SEM, &sem_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(0, sem_args.count);
|
||
|
+
|
||
|
+ sem_args.count = 0xdeadbeef;
|
||
|
+ sem_args.max = 0xdeadbeef;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_READ_SEM, &sem_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(2, sem_args.count);
|
||
|
+ EXPECT_EQ(2, sem_args.max);
|
||
|
+
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_GET_SEM, &sem_args.sem);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_GET_SEM, &sem_args.sem);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+
|
||
|
+ sem_args.count = 1;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_PUT_SEM, &sem_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(0, sem_args.count);
|
||
|
+
|
||
|
+ sem_args.count = 0xdeadbeef;
|
||
|
+ sem_args.max = 0xdeadbeef;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_READ_SEM, &sem_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(1, sem_args.count);
|
||
|
+ EXPECT_EQ(2, sem_args.max);
|
||
|
+
|
||
|
+ /* Test PULSE. */
|
||
|
+
|
||
|
+ sem_args.count = 2;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_PULSE_SEM, &sem_args);
|
||
|
+ EXPECT_EQ(-1, ret);
|
||
|
+ EXPECT_EQ(EOVERFLOW, errno);
|
||
|
+
|
||
|
+ sem_args.count = 0xdeadbeef;
|
||
|
+ sem_args.max = 0xdeadbeef;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_READ_SEM, &sem_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(0, sem_args.count);
|
||
|
+ EXPECT_EQ(2, sem_args.max);
|
||
|
+
|
||
|
+ sem_args.count = 1;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_PUT_SEM, &sem_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(0, sem_args.count);
|
||
|
+
|
||
|
+ sem_args.count = 1;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_PULSE_SEM, &sem_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(1, sem_args.count);
|
||
|
+
|
||
|
+ sem_args.count = 0xdeadbeef;
|
||
|
+ sem_args.max = 0xdeadbeef;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_READ_SEM, &sem_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(0, sem_args.count);
|
||
|
+ EXPECT_EQ(2, sem_args.max);
|
||
|
+
|
||
|
+ sem_args.count = 1;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_PULSE_SEM, &sem_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(0, sem_args.count);
|
||
|
+
|
||
|
+ sem_args.count = 0xdeadbeef;
|
||
|
+ sem_args.max = 0xdeadbeef;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_READ_SEM, &sem_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(0, sem_args.count);
|
||
|
+ EXPECT_EQ(2, sem_args.max);
|
||
|
+
|
||
|
+ sem_args.count = 2;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_PULSE_SEM, &sem_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(0, sem_args.count);
|
||
|
+
|
||
|
+ sem_args.count = 0xdeadbeef;
|
||
|
+ sem_args.max = 0xdeadbeef;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_READ_SEM, &sem_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(0, sem_args.count);
|
||
|
+ EXPECT_EQ(2, sem_args.max);
|
||
|
+
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_DELETE, &sem_args.sem);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+
|
||
|
+ close(fd);
|
||
|
+}
|
||
|
+
|
||
|
+TEST(semaphore_state_getonwait)
|
||
|
+{
|
||
|
+ struct winesync_wait_args wait_args;
|
||
|
+ struct winesync_sem_args sem_args;
|
||
|
+ struct timespec timeout;
|
||
|
+ int fd, ret;
|
||
|
+
|
||
|
+ clock_gettime(CLOCK_MONOTONIC, &timeout);
|
||
|
+
|
||
|
+ fd = open("/dev/winesync", O_CLOEXEC | O_RDONLY);
|
||
|
+ ASSERT_LE(0, fd);
|
||
|
+
|
||
|
+ sem_args.count = 3;
|
||
|
+ sem_args.max = 2;
|
||
|
+ sem_args.sem = 0xdeadbeef;
|
||
|
+ sem_args.flags = WINESYNC_SEM_GETONWAIT;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_CREATE_SEM, &sem_args);
|
||
|
+ EXPECT_EQ(-1, ret);
|
||
|
+ EXPECT_EQ(EINVAL, errno);
|
||
|
+
|
||
|
+ sem_args.count = 2;
|
||
|
+ sem_args.max = 2;
|
||
|
+ sem_args.sem = 0xdeadbeef;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_CREATE_SEM, &sem_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_NE(0xdeadbeef, sem_args.sem);
|
||
|
+
|
||
|
+ sem_args.count = 0xdeadbeef;
|
||
|
+ sem_args.max = 0xdeadbeef;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_READ_SEM, &sem_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(2, sem_args.count);
|
||
|
+ EXPECT_EQ(2, sem_args.max);
|
||
|
+
|
||
|
+ sem_args.count = 0;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_PUT_SEM, &sem_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(2, sem_args.count);
|
||
|
+
|
||
|
+ sem_args.count = 0xdeadbeef;
|
||
|
+ sem_args.max = 0xdeadbeef;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_READ_SEM, &sem_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(2, sem_args.count);
|
||
|
+ EXPECT_EQ(2, sem_args.max);
|
||
|
+
|
||
|
+ sem_args.count = 1;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_PUT_SEM, &sem_args);
|
||
|
+ EXPECT_EQ(-1, ret);
|
||
|
+ EXPECT_EQ(EOVERFLOW, errno);
|
||
|
+
|
||
|
+ sem_args.count = 0xdeadbeef;
|
||
|
+ sem_args.max = 0xdeadbeef;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_READ_SEM, &sem_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(2, sem_args.count);
|
||
|
+ EXPECT_EQ(2, sem_args.max);
|
||
|
+
|
||
|
+ wait_args.timeout = (uintptr_t)&timeout;
|
||
|
+ wait_args.objs = (uintptr_t)&sem_args.sem;
|
||
|
+ wait_args.count = 1;
|
||
|
+ wait_args.owner = 123;
|
||
|
+ wait_args.index = 0xdeadbeef;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_WAIT_ANY, &wait_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(0, wait_args.index);
|
||
|
+
|
||
|
+ sem_args.count = 0xdeadbeef;
|
||
|
+ sem_args.max = 0xdeadbeef;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_READ_SEM, &sem_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(1, sem_args.count);
|
||
|
+ EXPECT_EQ(2, sem_args.max);
|
||
|
+
|
||
|
+ wait_args.index = 0xdeadbeef;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_WAIT_ANY, &wait_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(0, wait_args.index);
|
||
|
+
|
||
|
+ sem_args.count = 0xdeadbeef;
|
||
|
+ sem_args.max = 0xdeadbeef;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_READ_SEM, &sem_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(0, sem_args.count);
|
||
|
+ EXPECT_EQ(2, sem_args.max);
|
||
|
+
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_WAIT_ANY, &wait_args);
|
||
|
+ EXPECT_EQ(-1, ret);
|
||
|
+ EXPECT_EQ(ETIMEDOUT, errno);
|
||
|
+
|
||
|
+ sem_args.count = 3;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_PUT_SEM, &sem_args);
|
||
|
+ EXPECT_EQ(-1, ret);
|
||
|
+ EXPECT_EQ(EOVERFLOW, errno);
|
||
|
+
|
||
|
+ sem_args.count = 0xdeadbeef;
|
||
|
+ sem_args.max = 0xdeadbeef;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_READ_SEM, &sem_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(0, sem_args.count);
|
||
|
+ EXPECT_EQ(2, sem_args.max);
|
||
|
+
|
||
|
+ sem_args.count = 2;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_PUT_SEM, &sem_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(0, sem_args.count);
|
||
|
+
|
||
|
+ sem_args.count = 0xdeadbeef;
|
||
|
+ sem_args.max = 0xdeadbeef;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_READ_SEM, &sem_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(2, sem_args.count);
|
||
|
+ EXPECT_EQ(2, sem_args.max);
|
||
|
+
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_WAIT_ANY, &wait_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_WAIT_ANY, &wait_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+
|
||
|
+ sem_args.count = 1;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_PUT_SEM, &sem_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(0, sem_args.count);
|
||
|
+
|
||
|
+ sem_args.count = 0xdeadbeef;
|
||
|
+ sem_args.max = 0xdeadbeef;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_READ_SEM, &sem_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(1, sem_args.count);
|
||
|
+ EXPECT_EQ(2, sem_args.max);
|
||
|
+
|
||
|
+ /* Test GET. */
|
||
|
+
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_GET_SEM, &sem_args.sem);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+
|
||
|
+ sem_args.count = 0xdeadbeef;
|
||
|
+ sem_args.max = 0xdeadbeef;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_READ_SEM, &sem_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(0, sem_args.count);
|
||
|
+ EXPECT_EQ(2, sem_args.max);
|
||
|
+
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_GET_SEM, &sem_args.sem);
|
||
|
+ EXPECT_EQ(-1, ret);
|
||
|
+ EXPECT_EQ(EWOULDBLOCK, errno);
|
||
|
+
|
||
|
+ sem_args.count = 2;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_PUT_SEM, &sem_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(0, sem_args.count);
|
||
|
+
|
||
|
+ sem_args.count = 0xdeadbeef;
|
||
|
+ sem_args.max = 0xdeadbeef;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_READ_SEM, &sem_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(2, sem_args.count);
|
||
|
+ EXPECT_EQ(2, sem_args.max);
|
||
|
+
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_GET_SEM, &sem_args.sem);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+
|
||
|
+ sem_args.count = 0xdeadbeef;
|
||
|
+ sem_args.max = 0xdeadbeef;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_READ_SEM, &sem_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(1, sem_args.count);
|
||
|
+ EXPECT_EQ(2, sem_args.max);
|
||
|
+
|
||
|
+ /* Test PULSE. */
|
||
|
+
|
||
|
+ sem_args.count = 2;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_PULSE_SEM, &sem_args);
|
||
|
+ EXPECT_EQ(-1, ret);
|
||
|
+ EXPECT_EQ(EOVERFLOW, errno);
|
||
|
+
|
||
|
+ sem_args.count = 0xdeadbeef;
|
||
|
+ sem_args.max = 0xdeadbeef;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_READ_SEM, &sem_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(0, sem_args.count);
|
||
|
+ EXPECT_EQ(2, sem_args.max);
|
||
|
+
|
||
|
+ sem_args.count = 1;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_PUT_SEM, &sem_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(0, sem_args.count);
|
||
|
+
|
||
|
+ sem_args.count = 1;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_PULSE_SEM, &sem_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(1, sem_args.count);
|
||
|
+
|
||
|
+ sem_args.count = 0xdeadbeef;
|
||
|
+ sem_args.max = 0xdeadbeef;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_READ_SEM, &sem_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(0, sem_args.count);
|
||
|
+ EXPECT_EQ(2, sem_args.max);
|
||
|
+
|
||
|
+ sem_args.count = 1;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_PULSE_SEM, &sem_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(0, sem_args.count);
|
||
|
+
|
||
|
+ sem_args.count = 0xdeadbeef;
|
||
|
+ sem_args.max = 0xdeadbeef;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_READ_SEM, &sem_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(0, sem_args.count);
|
||
|
+ EXPECT_EQ(2, sem_args.max);
|
||
|
+
|
||
|
+ sem_args.count = 2;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_PULSE_SEM, &sem_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(0, sem_args.count);
|
||
|
+
|
||
|
+ sem_args.count = 0xdeadbeef;
|
||
|
+ sem_args.max = 0xdeadbeef;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_READ_SEM, &sem_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(0, sem_args.count);
|
||
|
+ EXPECT_EQ(2, sem_args.max);
|
||
|
+
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_DELETE, &sem_args.sem);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+
|
||
|
+ close(fd);
|
||
|
+}
|
||
|
+
|
||
|
+TEST(mutex_state)
|
||
|
+{
|
||
|
+ struct winesync_wait_args wait_args;
|
||
|
+ struct winesync_mutex_args mutex_args;
|
||
|
+ struct timespec timeout;
|
||
|
+ __u32 owner;
|
||
|
+ int fd, ret;
|
||
|
+
|
||
|
+ clock_gettime(CLOCK_MONOTONIC, &timeout);
|
||
|
+
|
||
|
+ fd = open("/dev/winesync", O_CLOEXEC | O_RDONLY);
|
||
|
+ ASSERT_LE(0, fd);
|
||
|
+
|
||
|
+ mutex_args.owner = 123;
|
||
|
+ mutex_args.count = 0;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_CREATE_MUTEX, &mutex_args);
|
||
|
+ EXPECT_EQ(-1, ret);
|
||
|
+ EXPECT_EQ(EINVAL, errno);
|
||
|
+
|
||
|
+ mutex_args.owner = 0;
|
||
|
+ mutex_args.count = 2;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_CREATE_MUTEX, &mutex_args);
|
||
|
+ EXPECT_EQ(-1, ret);
|
||
|
+ EXPECT_EQ(EINVAL, errno);
|
||
|
+
|
||
|
+ mutex_args.owner = 123;
|
||
|
+ mutex_args.count = 2;
|
||
|
+ mutex_args.mutex = 0xdeadbeef;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_CREATE_MUTEX, &mutex_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_NE(0xdeadbeef, mutex_args.mutex);
|
||
|
+
|
||
|
+ mutex_args.count = 0xdeadbeef;
|
||
|
+ mutex_args.owner = 0xdeadbeef;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_READ_MUTEX, &mutex_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(2, mutex_args.count);
|
||
|
+ EXPECT_EQ(123, mutex_args.owner);
|
||
|
+
|
||
|
+ mutex_args.count = 0xdeadbeef;
|
||
|
+ mutex_args.owner = 0;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_PUT_MUTEX, &mutex_args);
|
||
|
+ EXPECT_EQ(-1, ret);
|
||
|
+ EXPECT_EQ(EINVAL, errno);
|
||
|
+
|
||
|
+ mutex_args.count = 0xdeadbeef;
|
||
|
+ mutex_args.owner = 456;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_PUT_MUTEX, &mutex_args);
|
||
|
+ EXPECT_EQ(-1, ret);
|
||
|
+ EXPECT_EQ(EPERM, errno);
|
||
|
+
|
||
|
+ mutex_args.count = 0xdeadbeef;
|
||
|
+ mutex_args.owner = 0xdeadbeef;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_READ_MUTEX, &mutex_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(2, mutex_args.count);
|
||
|
+ EXPECT_EQ(123, mutex_args.owner);
|
||
|
+
|
||
|
+ mutex_args.count = 0xdeadbeef;
|
||
|
+ mutex_args.owner = 123;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_PUT_MUTEX, &mutex_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(2, mutex_args.count);
|
||
|
+
|
||
|
+ mutex_args.count = 0xdeadbeef;
|
||
|
+ mutex_args.owner = 0xdeadbeef;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_READ_MUTEX, &mutex_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(1, mutex_args.count);
|
||
|
+ EXPECT_EQ(123, mutex_args.owner);
|
||
|
+
|
||
|
+ mutex_args.count = 0xdeadbeef;
|
||
|
+ mutex_args.owner = 123;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_PUT_MUTEX, &mutex_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(1, mutex_args.count);
|
||
|
+
|
||
|
+ mutex_args.count = 0xdeadbeef;
|
||
|
+ mutex_args.owner = 0xdeadbeef;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_READ_MUTEX, &mutex_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(0, mutex_args.count);
|
||
|
+ EXPECT_EQ(0, mutex_args.owner);
|
||
|
+
|
||
|
+ mutex_args.count = 0xdeadbeef;
|
||
|
+ mutex_args.owner = 123;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_PUT_MUTEX, &mutex_args);
|
||
|
+ EXPECT_EQ(-1, ret);
|
||
|
+ EXPECT_EQ(EPERM, errno);
|
||
|
+
|
||
|
+ wait_args.timeout = (uintptr_t)&timeout;
|
||
|
+ wait_args.objs = (uintptr_t)&mutex_args.mutex;
|
||
|
+ wait_args.count = 1;
|
||
|
+ wait_args.owner = 456;
|
||
|
+ wait_args.index = 0xdeadbeef;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_WAIT_ANY, &wait_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(0, wait_args.index);
|
||
|
+
|
||
|
+ mutex_args.count = 0xdeadbeef;
|
||
|
+ mutex_args.owner = 0xdeadbeef;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_READ_MUTEX, &mutex_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(1, mutex_args.count);
|
||
|
+ EXPECT_EQ(456, mutex_args.owner);
|
||
|
+
|
||
|
+ wait_args.owner = 456;
|
||
|
+ wait_args.index = 0xdeadbeef;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_WAIT_ANY, &wait_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(0, wait_args.index);
|
||
|
+
|
||
|
+ mutex_args.count = 0xdeadbeef;
|
||
|
+ mutex_args.owner = 0xdeadbeef;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_READ_MUTEX, &mutex_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(2, mutex_args.count);
|
||
|
+ EXPECT_EQ(456, mutex_args.owner);
|
||
|
+
|
||
|
+ mutex_args.count = 0xdeadbeef;
|
||
|
+ mutex_args.owner = 456;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_PUT_MUTEX, &mutex_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(2, mutex_args.count);
|
||
|
+
|
||
|
+ mutex_args.count = 0xdeadbeef;
|
||
|
+ mutex_args.owner = 0xdeadbeef;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_READ_MUTEX, &mutex_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(1, mutex_args.count);
|
||
|
+ EXPECT_EQ(456, mutex_args.owner);
|
||
|
+
|
||
|
+ wait_args.owner = 123;
|
||
|
+ wait_args.index = 0xdeadbeef;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_WAIT_ANY, &wait_args);
|
||
|
+ EXPECT_EQ(-1, ret);
|
||
|
+ EXPECT_EQ(ETIMEDOUT, errno);
|
||
|
+
|
||
|
+ owner = 0;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_KILL_OWNER, &owner);
|
||
|
+ EXPECT_EQ(-1, ret);
|
||
|
+ EXPECT_EQ(EINVAL, errno);
|
||
|
+
|
||
|
+ owner = 123;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_KILL_OWNER, &owner);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+
|
||
|
+ mutex_args.count = 0xdeadbeef;
|
||
|
+ mutex_args.owner = 0xdeadbeef;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_READ_MUTEX, &mutex_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(1, mutex_args.count);
|
||
|
+ EXPECT_EQ(456, mutex_args.owner);
|
||
|
+
|
||
|
+ owner = 456;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_KILL_OWNER, &owner);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+
|
||
|
+ mutex_args.count = 0xdeadbeef;
|
||
|
+ mutex_args.owner = 0xdeadbeef;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_READ_MUTEX, &mutex_args);
|
||
|
+ EXPECT_EQ(-1, ret);
|
||
|
+ EXPECT_EQ(EOWNERDEAD, errno);
|
||
|
+ EXPECT_EQ(0, mutex_args.count);
|
||
|
+ EXPECT_EQ(0, mutex_args.owner);
|
||
|
+
|
||
|
+ mutex_args.count = 0xdeadbeef;
|
||
|
+ mutex_args.owner = 0xdeadbeef;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_READ_MUTEX, &mutex_args);
|
||
|
+ EXPECT_EQ(-1, ret);
|
||
|
+ EXPECT_EQ(EOWNERDEAD, errno);
|
||
|
+ EXPECT_EQ(0, mutex_args.count);
|
||
|
+ EXPECT_EQ(0, mutex_args.owner);
|
||
|
+
|
||
|
+ wait_args.owner = 123;
|
||
|
+ wait_args.index = 0xdeadbeef;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_WAIT_ANY, &wait_args);
|
||
|
+ EXPECT_EQ(-1, ret);
|
||
|
+ EXPECT_EQ(EOWNERDEAD, errno);
|
||
|
+ EXPECT_EQ(0, wait_args.index);
|
||
|
+
|
||
|
+ mutex_args.count = 0xdeadbeef;
|
||
|
+ mutex_args.owner = 0xdeadbeef;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_READ_MUTEX, &mutex_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(1, mutex_args.count);
|
||
|
+ EXPECT_EQ(123, mutex_args.owner);
|
||
|
+
|
||
|
+ owner = 123;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_KILL_OWNER, &owner);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+
|
||
|
+ mutex_args.count = 0xdeadbeef;
|
||
|
+ mutex_args.owner = 0xdeadbeef;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_READ_MUTEX, &mutex_args);
|
||
|
+ EXPECT_EQ(-1, ret);
|
||
|
+ EXPECT_EQ(EOWNERDEAD, errno);
|
||
|
+ EXPECT_EQ(0, mutex_args.count);
|
||
|
+ EXPECT_EQ(0, mutex_args.owner);
|
||
|
+
|
||
|
+ wait_args.owner = 123;
|
||
|
+ wait_args.index = 0xdeadbeef;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_WAIT_ANY, &wait_args);
|
||
|
+ EXPECT_EQ(-1, ret);
|
||
|
+ EXPECT_EQ(EOWNERDEAD, errno);
|
||
|
+ EXPECT_EQ(0, wait_args.index);
|
||
|
+
|
||
|
+ mutex_args.count = 0xdeadbeef;
|
||
|
+ mutex_args.owner = 0xdeadbeef;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_READ_MUTEX, &mutex_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(1, mutex_args.count);
|
||
|
+ EXPECT_EQ(123, mutex_args.owner);
|
||
|
+
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_DELETE, &mutex_args.mutex);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+
|
||
|
+ mutex_args.owner = 0;
|
||
|
+ mutex_args.count = 0;
|
||
|
+ mutex_args.mutex = 0xdeadbeef;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_CREATE_MUTEX, &mutex_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_NE(0xdeadbeef, mutex_args.mutex);
|
||
|
+
|
||
|
+ mutex_args.count = 0xdeadbeef;
|
||
|
+ mutex_args.owner = 0xdeadbeef;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_READ_MUTEX, &mutex_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(0, mutex_args.count);
|
||
|
+ EXPECT_EQ(0, mutex_args.owner);
|
||
|
+
|
||
|
+ wait_args.owner = 123;
|
||
|
+ wait_args.index = 0xdeadbeef;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_WAIT_ANY, &wait_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(0, wait_args.index);
|
||
|
+
|
||
|
+ mutex_args.count = 0xdeadbeef;
|
||
|
+ mutex_args.owner = 0xdeadbeef;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_READ_MUTEX, &mutex_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(1, mutex_args.count);
|
||
|
+ EXPECT_EQ(123, mutex_args.owner);
|
||
|
+
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_DELETE, &mutex_args.mutex);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+
|
||
|
+ close(fd);
|
||
|
+}
|
||
|
+
|
||
|
+TEST(wait_any)
|
||
|
+{
|
||
|
+ struct winesync_mutex_args mutex_args = {0};
|
||
|
+ struct winesync_wait_args wait_args = {0};
|
||
|
+ struct winesync_sem_args sem_args = {0};
|
||
|
+ struct timespec timeout;
|
||
|
+ __s32 objs[2];
|
||
|
+ __u32 owner;
|
||
|
+ int fd, ret;
|
||
|
+
|
||
|
+ clock_gettime(CLOCK_MONOTONIC, &timeout);
|
||
|
+
|
||
|
+ fd = open("/dev/winesync", O_CLOEXEC | O_RDONLY);
|
||
|
+ ASSERT_LE(0, fd);
|
||
|
+
|
||
|
+ sem_args.count = 2;
|
||
|
+ sem_args.max = 3;
|
||
|
+ sem_args.sem = 0xdeadbeef;
|
||
|
+ sem_args.flags = WINESYNC_SEM_GETONWAIT;
|
||
|
+ ret = ioctl(fd, WINESYNC_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, WINESYNC_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;
|
||
|
+
|
||
|
+ wait_args.timeout = (uintptr_t)&timeout;
|
||
|
+ wait_args.objs = (uintptr_t)objs;
|
||
|
+ wait_args.count = 2;
|
||
|
+ wait_args.owner = 123;
|
||
|
+ wait_args.index = 0xdeadbeef;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_WAIT_ANY, &wait_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(0, wait_args.index);
|
||
|
+ EXPECT_EQ((uintptr_t)objs, wait_args.objs);
|
||
|
+ EXPECT_EQ(2, wait_args.count);
|
||
|
+ EXPECT_EQ(123, wait_args.owner);
|
||
|
+
|
||
|
+ sem_args.count = 0xdeadbeef;
|
||
|
+ sem_args.max = 0xdeadbeef;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_READ_SEM, &sem_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(1, sem_args.count);
|
||
|
+ EXPECT_EQ(3, sem_args.max);
|
||
|
+
|
||
|
+ mutex_args.count = 0xdeadbeef;
|
||
|
+ mutex_args.owner = 0xdeadbeef;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_READ_MUTEX, &mutex_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(0, mutex_args.count);
|
||
|
+ EXPECT_EQ(0, mutex_args.owner);
|
||
|
+
|
||
|
+ wait_args.owner = 123;
|
||
|
+ wait_args.index = 0xdeadbeef;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_WAIT_ANY, &wait_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(0, wait_args.index);
|
||
|
+
|
||
|
+ sem_args.count = 0xdeadbeef;
|
||
|
+ sem_args.max = 0xdeadbeef;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_READ_SEM, &sem_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(0, sem_args.count);
|
||
|
+ EXPECT_EQ(3, sem_args.max);
|
||
|
+
|
||
|
+ mutex_args.count = 0xdeadbeef;
|
||
|
+ mutex_args.owner = 0xdeadbeef;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_READ_MUTEX, &mutex_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(0, mutex_args.count);
|
||
|
+ EXPECT_EQ(0, mutex_args.owner);
|
||
|
+
|
||
|
+ wait_args.owner = 123;
|
||
|
+ wait_args.index = 0xdeadbeef;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_WAIT_ANY, &wait_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(1, wait_args.index);
|
||
|
+
|
||
|
+ sem_args.count = 0xdeadbeef;
|
||
|
+ sem_args.max = 0xdeadbeef;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_READ_SEM, &sem_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(0, sem_args.count);
|
||
|
+ EXPECT_EQ(3, sem_args.max);
|
||
|
+
|
||
|
+ mutex_args.count = 0xdeadbeef;
|
||
|
+ mutex_args.owner = 0xdeadbeef;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_READ_MUTEX, &mutex_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(1, mutex_args.count);
|
||
|
+ EXPECT_EQ(123, mutex_args.owner);
|
||
|
+
|
||
|
+ sem_args.count = 1;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_PUT_SEM, &sem_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(0, sem_args.count);
|
||
|
+
|
||
|
+ wait_args.owner = 123;
|
||
|
+ wait_args.index = 0xdeadbeef;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_WAIT_ANY, &wait_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(0, wait_args.index);
|
||
|
+
|
||
|
+ sem_args.count = 0xdeadbeef;
|
||
|
+ sem_args.max = 0xdeadbeef;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_READ_SEM, &sem_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(0, sem_args.count);
|
||
|
+ EXPECT_EQ(3, sem_args.max);
|
||
|
+
|
||
|
+ mutex_args.count = 0xdeadbeef;
|
||
|
+ mutex_args.owner = 0xdeadbeef;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_READ_MUTEX, &mutex_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(1, mutex_args.count);
|
||
|
+ EXPECT_EQ(123, mutex_args.owner);
|
||
|
+
|
||
|
+ wait_args.owner = 123;
|
||
|
+ wait_args.index = 0xdeadbeef;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_WAIT_ANY, &wait_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(1, wait_args.index);
|
||
|
+
|
||
|
+ sem_args.count = 0xdeadbeef;
|
||
|
+ sem_args.max = 0xdeadbeef;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_READ_SEM, &sem_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(0, sem_args.count);
|
||
|
+ EXPECT_EQ(3, sem_args.max);
|
||
|
+
|
||
|
+ mutex_args.count = 0xdeadbeef;
|
||
|
+ mutex_args.owner = 0xdeadbeef;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_READ_MUTEX, &mutex_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(2, mutex_args.count);
|
||
|
+ EXPECT_EQ(123, mutex_args.owner);
|
||
|
+
|
||
|
+ wait_args.owner = 456;
|
||
|
+ wait_args.index = 0xdeadbeef;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_WAIT_ANY, &wait_args);
|
||
|
+ EXPECT_EQ(-1, ret);
|
||
|
+ EXPECT_EQ(ETIMEDOUT, errno);
|
||
|
+
|
||
|
+ owner = 123;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_KILL_OWNER, &owner);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+
|
||
|
+ wait_args.owner = 456;
|
||
|
+ wait_args.index = 0xdeadbeef;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_WAIT_ANY, &wait_args);
|
||
|
+ EXPECT_EQ(-1, ret);
|
||
|
+ EXPECT_EQ(EOWNERDEAD, errno);
|
||
|
+ EXPECT_EQ(1, wait_args.index);
|
||
|
+
|
||
|
+ wait_args.owner = 456;
|
||
|
+ wait_args.index = 0xdeadbeef;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_WAIT_ANY, &wait_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(1, wait_args.index);
|
||
|
+
|
||
|
+ /* test waiting on the same object twice */
|
||
|
+ sem_args.count = 2;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_PUT_SEM, &sem_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(0, sem_args.count);
|
||
|
+
|
||
|
+ objs[0] = objs[1] = sem_args.sem;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_WAIT_ANY, &wait_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(0, wait_args.index);
|
||
|
+
|
||
|
+ sem_args.count = 0xdeadbeef;
|
||
|
+ sem_args.max = 0xdeadbeef;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_READ_SEM, &sem_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(1, sem_args.count);
|
||
|
+ EXPECT_EQ(3, sem_args.max);
|
||
|
+
|
||
|
+ wait_args.count = 0;
|
||
|
+ wait_args.objs = (uintptr_t)NULL;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_WAIT_ANY, &wait_args);
|
||
|
+ EXPECT_EQ(-1, ret);
|
||
|
+ EXPECT_EQ(ETIMEDOUT, errno);
|
||
|
+
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_DELETE, &sem_args.sem);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_DELETE, &mutex_args.mutex);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+
|
||
|
+ close(fd);
|
||
|
+}
|
||
|
+
|
||
|
+TEST(wait_all)
|
||
|
+{
|
||
|
+ struct winesync_mutex_args mutex_args = {0};
|
||
|
+ struct winesync_wait_args wait_args = {0};
|
||
|
+ struct winesync_sem_args sem_args = {0};
|
||
|
+ struct timespec timeout;
|
||
|
+ __s32 objs[2];
|
||
|
+ __u32 owner;
|
||
|
+ int fd, ret;
|
||
|
+
|
||
|
+ clock_gettime(CLOCK_MONOTONIC, &timeout);
|
||
|
+
|
||
|
+ fd = open("/dev/winesync", O_CLOEXEC | O_RDONLY);
|
||
|
+ ASSERT_LE(0, fd);
|
||
|
+
|
||
|
+ sem_args.count = 2;
|
||
|
+ sem_args.max = 3;
|
||
|
+ sem_args.sem = 0xdeadbeef;
|
||
|
+ sem_args.flags = WINESYNC_SEM_GETONWAIT;
|
||
|
+ ret = ioctl(fd, WINESYNC_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, WINESYNC_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;
|
||
|
+
|
||
|
+ wait_args.timeout = (uintptr_t)&timeout;
|
||
|
+ wait_args.objs = (uintptr_t)objs;
|
||
|
+ wait_args.count = 2;
|
||
|
+ wait_args.owner = 123;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_WAIT_ALL, &wait_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ((uintptr_t)objs, wait_args.objs);
|
||
|
+ EXPECT_EQ(2, wait_args.count);
|
||
|
+ EXPECT_EQ(123, wait_args.owner);
|
||
|
+
|
||
|
+ sem_args.count = 0xdeadbeef;
|
||
|
+ sem_args.max = 0xdeadbeef;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_READ_SEM, &sem_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(1, sem_args.count);
|
||
|
+ EXPECT_EQ(3, sem_args.max);
|
||
|
+
|
||
|
+ mutex_args.count = 0xdeadbeef;
|
||
|
+ mutex_args.owner = 0xdeadbeef;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_READ_MUTEX, &mutex_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(1, mutex_args.count);
|
||
|
+ EXPECT_EQ(123, mutex_args.owner);
|
||
|
+
|
||
|
+ wait_args.owner = 456;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_WAIT_ALL, &wait_args);
|
||
|
+ EXPECT_EQ(-1, ret);
|
||
|
+ EXPECT_EQ(ETIMEDOUT, errno);
|
||
|
+
|
||
|
+ sem_args.count = 0xdeadbeef;
|
||
|
+ sem_args.max = 0xdeadbeef;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_READ_SEM, &sem_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(1, sem_args.count);
|
||
|
+ EXPECT_EQ(3, sem_args.max);
|
||
|
+
|
||
|
+ mutex_args.count = 0xdeadbeef;
|
||
|
+ mutex_args.owner = 0xdeadbeef;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_READ_MUTEX, &mutex_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(1, mutex_args.count);
|
||
|
+ EXPECT_EQ(123, mutex_args.owner);
|
||
|
+
|
||
|
+ wait_args.owner = 123;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_WAIT_ALL, &wait_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+
|
||
|
+ sem_args.count = 0xdeadbeef;
|
||
|
+ sem_args.max = 0xdeadbeef;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_READ_SEM, &sem_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(0, sem_args.count);
|
||
|
+ EXPECT_EQ(3, sem_args.max);
|
||
|
+
|
||
|
+ mutex_args.count = 0xdeadbeef;
|
||
|
+ mutex_args.owner = 0xdeadbeef;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_READ_MUTEX, &mutex_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(2, mutex_args.count);
|
||
|
+ EXPECT_EQ(123, mutex_args.owner);
|
||
|
+
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_WAIT_ALL, &wait_args);
|
||
|
+ EXPECT_EQ(-1, ret);
|
||
|
+ EXPECT_EQ(ETIMEDOUT, errno);
|
||
|
+
|
||
|
+ sem_args.count = 0xdeadbeef;
|
||
|
+ sem_args.max = 0xdeadbeef;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_READ_SEM, &sem_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(0, sem_args.count);
|
||
|
+ EXPECT_EQ(3, sem_args.max);
|
||
|
+
|
||
|
+ mutex_args.count = 0xdeadbeef;
|
||
|
+ mutex_args.owner = 0xdeadbeef;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_READ_MUTEX, &mutex_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(2, mutex_args.count);
|
||
|
+ EXPECT_EQ(123, mutex_args.owner);
|
||
|
+
|
||
|
+ sem_args.count = 3;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_PUT_SEM, &sem_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(0, sem_args.count);
|
||
|
+
|
||
|
+ owner = 123;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_KILL_OWNER, &owner);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_WAIT_ALL, &wait_args);
|
||
|
+ EXPECT_EQ(-1, ret);
|
||
|
+ EXPECT_EQ(EOWNERDEAD, errno);
|
||
|
+
|
||
|
+ sem_args.count = 0xdeadbeef;
|
||
|
+ sem_args.max = 0xdeadbeef;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_READ_SEM, &sem_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(2, sem_args.count);
|
||
|
+ EXPECT_EQ(3, sem_args.max);
|
||
|
+
|
||
|
+ mutex_args.count = 0xdeadbeef;
|
||
|
+ mutex_args.owner = 0xdeadbeef;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_READ_MUTEX, &mutex_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(1, mutex_args.count);
|
||
|
+ EXPECT_EQ(123, mutex_args.owner);
|
||
|
+
|
||
|
+ /* test waiting on the same object twice */
|
||
|
+ objs[0] = objs[1] = sem_args.sem;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_WAIT_ALL, &wait_args);
|
||
|
+ EXPECT_EQ(-1, ret);
|
||
|
+ EXPECT_EQ(EINVAL, errno);
|
||
|
+
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_DELETE, &sem_args.sem);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_DELETE, &mutex_args.mutex);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+
|
||
|
+ close(fd);
|
||
|
+}
|
||
|
+
|
||
|
+TEST(invalid_objects)
|
||
|
+{
|
||
|
+ struct winesync_mutex_args mutex_args = {0};
|
||
|
+ struct winesync_wait_args wait_args = {0};
|
||
|
+ struct winesync_sem_args sem_args = {0};
|
||
|
+ __s32 objs[2] = {0};
|
||
|
+ int fd, ret;
|
||
|
+
|
||
|
+ fd = open("/dev/winesync", O_CLOEXEC | O_RDONLY);
|
||
|
+ ASSERT_LE(0, fd);
|
||
|
+
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_PUT_SEM, &sem_args);
|
||
|
+ EXPECT_EQ(-1, ret);
|
||
|
+ EXPECT_EQ(EINVAL, errno);
|
||
|
+
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_PULSE_SEM, &sem_args);
|
||
|
+ EXPECT_EQ(-1, ret);
|
||
|
+ EXPECT_EQ(EINVAL, errno);
|
||
|
+
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_READ_SEM, &sem_args);
|
||
|
+ EXPECT_EQ(-1, ret);
|
||
|
+ EXPECT_EQ(EINVAL, errno);
|
||
|
+
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_GET_SEM, &sem_args.sem);
|
||
|
+ EXPECT_EQ(-1, ret);
|
||
|
+ EXPECT_EQ(EINVAL, errno);
|
||
|
+
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_PUT_MUTEX, &mutex_args);
|
||
|
+ EXPECT_EQ(-1, ret);
|
||
|
+ EXPECT_EQ(EINVAL, errno);
|
||
|
+
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_READ_MUTEX, &mutex_args);
|
||
|
+ EXPECT_EQ(-1, ret);
|
||
|
+ EXPECT_EQ(EINVAL, errno);
|
||
|
+
|
||
|
+ wait_args.objs = (uintptr_t)objs;
|
||
|
+ wait_args.count = 1;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_WAIT_ANY, &wait_args);
|
||
|
+ EXPECT_EQ(-1, ret);
|
||
|
+ EXPECT_EQ(EINVAL, errno);
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_WAIT_ALL, &wait_args);
|
||
|
+ EXPECT_EQ(-1, ret);
|
||
|
+ EXPECT_EQ(EINVAL, errno);
|
||
|
+
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_DELETE, &objs[0]);
|
||
|
+ EXPECT_EQ(-1, ret);
|
||
|
+ EXPECT_EQ(EINVAL, errno);
|
||
|
+
|
||
|
+ sem_args.max = 1;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_CREATE_SEM, &sem_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+
|
||
|
+ mutex_args.mutex = sem_args.sem;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_PUT_MUTEX, &mutex_args);
|
||
|
+ EXPECT_EQ(-1, ret);
|
||
|
+ EXPECT_EQ(EINVAL, errno);
|
||
|
+
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_READ_MUTEX, &mutex_args);
|
||
|
+ EXPECT_EQ(-1, ret);
|
||
|
+ EXPECT_EQ(EINVAL, errno);
|
||
|
+
|
||
|
+ objs[0] = sem_args.sem;
|
||
|
+ objs[1] = sem_args.sem + 1;
|
||
|
+ wait_args.count = 2;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_WAIT_ANY, &wait_args);
|
||
|
+ EXPECT_EQ(-1, ret);
|
||
|
+ EXPECT_EQ(EINVAL, errno);
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_WAIT_ALL, &wait_args);
|
||
|
+ EXPECT_EQ(-1, ret);
|
||
|
+ EXPECT_EQ(EINVAL, errno);
|
||
|
+
|
||
|
+ objs[0] = sem_args.sem + 1;
|
||
|
+ objs[1] = sem_args.sem;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_WAIT_ANY, &wait_args);
|
||
|
+ EXPECT_EQ(-1, ret);
|
||
|
+ EXPECT_EQ(EINVAL, errno);
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_WAIT_ALL, &wait_args);
|
||
|
+ EXPECT_EQ(-1, ret);
|
||
|
+ EXPECT_EQ(EINVAL, errno);
|
||
|
+
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_DELETE, &sem_args.sem);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_CREATE_MUTEX, &mutex_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+
|
||
|
+ sem_args.sem = mutex_args.mutex;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_PUT_SEM, &sem_args);
|
||
|
+ EXPECT_EQ(-1, ret);
|
||
|
+ EXPECT_EQ(EINVAL, errno);
|
||
|
+
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_READ_SEM, &sem_args);
|
||
|
+ EXPECT_EQ(-1, ret);
|
||
|
+ EXPECT_EQ(EINVAL, errno);
|
||
|
+
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_DELETE, &mutex_args.mutex);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+
|
||
|
+ close(fd);
|
||
|
+}
|
||
|
+
|
||
|
+struct wake_args
|
||
|
+{
|
||
|
+ int fd;
|
||
|
+ __s32 obj;
|
||
|
+};
|
||
|
+
|
||
|
+struct wait_args
|
||
|
+{
|
||
|
+ int fd;
|
||
|
+ unsigned long request;
|
||
|
+ struct winesync_wait_args *args;
|
||
|
+ int ret;
|
||
|
+ int err;
|
||
|
+};
|
||
|
+
|
||
|
+static void *wait_thread(void *arg)
|
||
|
+{
|
||
|
+ struct wait_args *args = arg;
|
||
|
+
|
||
|
+ args->ret = ioctl(args->fd, args->request, args->args);
|
||
|
+ args->err = errno;
|
||
|
+ return NULL;
|
||
|
+}
|
||
|
+
|
||
|
+static void get_abs_timeout(struct timespec *timeout, clockid_t clock,
|
||
|
+ unsigned int ms)
|
||
|
+{
|
||
|
+ clock_gettime(clock, timeout);
|
||
|
+ timeout->tv_nsec += ms * 1000000;
|
||
|
+ timeout->tv_sec += (timeout->tv_nsec / 1000000000);
|
||
|
+ timeout->tv_nsec %= 1000000000;
|
||
|
+}
|
||
|
+
|
||
|
+static int wait_for_thread(pthread_t thread, unsigned int ms)
|
||
|
+{
|
||
|
+ struct timespec timeout;
|
||
|
+ get_abs_timeout(&timeout, CLOCK_REALTIME, ms);
|
||
|
+ return pthread_timedjoin_np(thread, NULL, &timeout);
|
||
|
+}
|
||
|
+
|
||
|
+TEST(wake_any)
|
||
|
+{
|
||
|
+ struct winesync_mutex_args mutex_args = {0};
|
||
|
+ struct winesync_wait_args wait_args = {0};
|
||
|
+ struct winesync_sem_args sem_args = {0};
|
||
|
+ struct wait_args thread_args;
|
||
|
+ struct timespec timeout;
|
||
|
+ pthread_t thread;
|
||
|
+ __s32 objs[2];
|
||
|
+ __u32 owner;
|
||
|
+ int fd, ret;
|
||
|
+
|
||
|
+ fd = open("/dev/winesync", O_CLOEXEC | O_RDONLY);
|
||
|
+ ASSERT_LE(0, fd);
|
||
|
+
|
||
|
+ sem_args.count = 0;
|
||
|
+ sem_args.max = 3;
|
||
|
+ sem_args.sem = 0xdeadbeef;
|
||
|
+ sem_args.flags = WINESYNC_SEM_GETONWAIT;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_CREATE_SEM, &sem_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_NE(0xdeadbeef, sem_args.sem);
|
||
|
+
|
||
|
+ mutex_args.owner = 123;
|
||
|
+ mutex_args.count = 1;
|
||
|
+ mutex_args.mutex = 0xdeadbeef;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_CREATE_MUTEX, &mutex_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_NE(0xdeadbeef, mutex_args.mutex);
|
||
|
+
|
||
|
+ objs[0] = sem_args.sem;
|
||
|
+ objs[1] = mutex_args.mutex;
|
||
|
+
|
||
|
+ /* test waking the semaphore */
|
||
|
+
|
||
|
+ get_abs_timeout(&timeout, CLOCK_MONOTONIC, 1000);
|
||
|
+ wait_args.timeout = (uintptr_t)&timeout;
|
||
|
+ wait_args.objs = (uintptr_t)objs;
|
||
|
+ wait_args.count = 2;
|
||
|
+ wait_args.owner = 456;
|
||
|
+ wait_args.index = 0xdeadbeef;
|
||
|
+ thread_args.fd = fd;
|
||
|
+ thread_args.args = &wait_args;
|
||
|
+ thread_args.request = WINESYNC_IOC_WAIT_ANY;
|
||
|
+ ret = pthread_create(&thread, NULL, wait_thread, &thread_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+
|
||
|
+ ret = wait_for_thread(thread, 100);
|
||
|
+ EXPECT_EQ(ETIMEDOUT, ret);
|
||
|
+
|
||
|
+ sem_args.count = 1;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_PUT_SEM, &sem_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(0, sem_args.count);
|
||
|
+
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_READ_SEM, &sem_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(0, sem_args.count);
|
||
|
+
|
||
|
+ ret = wait_for_thread(thread, 100);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(0, thread_args.ret);
|
||
|
+ EXPECT_EQ(0, wait_args.index);
|
||
|
+
|
||
|
+ /* test waking the semaphore via pulse */
|
||
|
+
|
||
|
+ get_abs_timeout(&timeout, CLOCK_MONOTONIC, 1000);
|
||
|
+ wait_args.owner = 456;
|
||
|
+ ret = pthread_create(&thread, NULL, wait_thread, &thread_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+
|
||
|
+ ret = wait_for_thread(thread, 100);
|
||
|
+ EXPECT_EQ(ETIMEDOUT, ret);
|
||
|
+
|
||
|
+ sem_args.count = 2;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_PULSE_SEM, &sem_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(0, sem_args.count);
|
||
|
+
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_READ_SEM, &sem_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(0, sem_args.count);
|
||
|
+
|
||
|
+ ret = wait_for_thread(thread, 100);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(0, thread_args.ret);
|
||
|
+ EXPECT_EQ(0, wait_args.index);
|
||
|
+
|
||
|
+ /* test waking the mutex */
|
||
|
+
|
||
|
+ /* first grab it again for owner 123 */
|
||
|
+ wait_args.owner = 123;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_WAIT_ANY, &wait_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(1, wait_args.index);
|
||
|
+
|
||
|
+ get_abs_timeout(&timeout, CLOCK_MONOTONIC, 1000);
|
||
|
+ wait_args.owner = 456;
|
||
|
+ ret = pthread_create(&thread, NULL, wait_thread, &thread_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+
|
||
|
+ ret = wait_for_thread(thread, 100);
|
||
|
+ EXPECT_EQ(ETIMEDOUT, ret);
|
||
|
+
|
||
|
+ mutex_args.owner = 123;
|
||
|
+ mutex_args.count = 0xdeadbeef;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_PUT_MUTEX, &mutex_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(2, mutex_args.count);
|
||
|
+
|
||
|
+ ret = pthread_tryjoin_np(thread, NULL);
|
||
|
+ EXPECT_EQ(EBUSY, ret);
|
||
|
+
|
||
|
+ mutex_args.owner = 123;
|
||
|
+ mutex_args.count = 0xdeadbeef;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_PUT_MUTEX, &mutex_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(1, mutex_args.count);
|
||
|
+
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_READ_MUTEX, &mutex_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(1, mutex_args.count);
|
||
|
+ EXPECT_EQ(456, mutex_args.owner);
|
||
|
+
|
||
|
+ ret = wait_for_thread(thread, 100);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(0, thread_args.ret);
|
||
|
+ EXPECT_EQ(1, wait_args.index);
|
||
|
+
|
||
|
+ /* delete an object while it's being waited on */
|
||
|
+
|
||
|
+ get_abs_timeout(&timeout, CLOCK_MONOTONIC, 200);
|
||
|
+ wait_args.owner = 123;
|
||
|
+ ret = pthread_create(&thread, NULL, wait_thread, &thread_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+
|
||
|
+ ret = wait_for_thread(thread, 100);
|
||
|
+ EXPECT_EQ(ETIMEDOUT, ret);
|
||
|
+
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_DELETE, &sem_args.sem);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_DELETE, &mutex_args.mutex);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+
|
||
|
+ ret = wait_for_thread(thread, 200);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(-1, thread_args.ret);
|
||
|
+ EXPECT_EQ(ETIMEDOUT, thread_args.err);
|
||
|
+
|
||
|
+ close(fd);
|
||
|
+}
|
||
|
+
|
||
|
+TEST(wake_all)
|
||
|
+{
|
||
|
+ struct winesync_wait_args wait_args = {0}, wait_args2 = {0};
|
||
|
+ struct winesync_mutex_args mutex_args = {0};
|
||
|
+ struct winesync_sem_args sem_args = {0};
|
||
|
+ struct timespec timeout, timeout2;
|
||
|
+ struct wait_args thread_args;
|
||
|
+ pthread_t thread;
|
||
|
+ __s32 objs[2];
|
||
|
+ __u32 owner;
|
||
|
+ int fd, ret;
|
||
|
+
|
||
|
+ fd = open("/dev/winesync", O_CLOEXEC | O_RDONLY);
|
||
|
+ ASSERT_LE(0, fd);
|
||
|
+
|
||
|
+ sem_args.count = 0;
|
||
|
+ sem_args.max = 3;
|
||
|
+ sem_args.sem = 0xdeadbeef;
|
||
|
+ sem_args.flags = WINESYNC_SEM_GETONWAIT;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_CREATE_SEM, &sem_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_NE(0xdeadbeef, sem_args.sem);
|
||
|
+
|
||
|
+ mutex_args.owner = 123;
|
||
|
+ mutex_args.count = 1;
|
||
|
+ mutex_args.mutex = 0xdeadbeef;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_CREATE_MUTEX, &mutex_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_NE(0xdeadbeef, mutex_args.mutex);
|
||
|
+
|
||
|
+ objs[0] = sem_args.sem;
|
||
|
+ objs[1] = mutex_args.mutex;
|
||
|
+
|
||
|
+ get_abs_timeout(&timeout, CLOCK_MONOTONIC, 1000);
|
||
|
+ wait_args.timeout = (uintptr_t)&timeout;
|
||
|
+ wait_args.objs = (uintptr_t)objs;
|
||
|
+ wait_args.count = 2;
|
||
|
+ wait_args.owner = 456;
|
||
|
+ thread_args.fd = fd;
|
||
|
+ thread_args.args = &wait_args;
|
||
|
+ thread_args.request = WINESYNC_IOC_WAIT_ALL;
|
||
|
+ ret = pthread_create(&thread, NULL, wait_thread, &thread_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+
|
||
|
+ ret = wait_for_thread(thread, 100);
|
||
|
+ EXPECT_EQ(ETIMEDOUT, ret);
|
||
|
+
|
||
|
+ sem_args.count = 1;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_PUT_SEM, &sem_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(0, sem_args.count);
|
||
|
+
|
||
|
+ ret = pthread_tryjoin_np(thread, NULL);
|
||
|
+ EXPECT_EQ(EBUSY, ret);
|
||
|
+
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_READ_SEM, &sem_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(1, sem_args.count);
|
||
|
+
|
||
|
+ get_abs_timeout(&timeout2, CLOCK_MONOTONIC, 0);
|
||
|
+ wait_args2.timeout = (uintptr_t)&timeout2;
|
||
|
+ wait_args2.objs = (uintptr_t)&sem_args.sem;
|
||
|
+ wait_args2.count = 1;
|
||
|
+ wait_args2.owner = 123;
|
||
|
+ wait_args2.index = 0xdeadbeef;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_WAIT_ANY, &wait_args2);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(0, wait_args2.index);
|
||
|
+
|
||
|
+ mutex_args.owner = 123;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_PUT_MUTEX, &mutex_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(1, mutex_args.count);
|
||
|
+
|
||
|
+ ret = pthread_tryjoin_np(thread, NULL);
|
||
|
+ EXPECT_EQ(EBUSY, ret);
|
||
|
+
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_READ_MUTEX, &mutex_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(0, mutex_args.count);
|
||
|
+ EXPECT_EQ(0, mutex_args.owner);
|
||
|
+
|
||
|
+ sem_args.count = 1;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_PUT_SEM, &sem_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(0, sem_args.count);
|
||
|
+
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_READ_SEM, &sem_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(0, sem_args.count);
|
||
|
+
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_READ_MUTEX, &mutex_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(1, mutex_args.count);
|
||
|
+ EXPECT_EQ(456, mutex_args.owner);
|
||
|
+
|
||
|
+ ret = wait_for_thread(thread, 100);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(0, thread_args.ret);
|
||
|
+
|
||
|
+ /* test waking the semaphore via pulse */
|
||
|
+
|
||
|
+ get_abs_timeout(&timeout, CLOCK_MONOTONIC, 1000);
|
||
|
+ wait_args.owner = 456;
|
||
|
+ ret = pthread_create(&thread, NULL, wait_thread, &thread_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+
|
||
|
+ ret = wait_for_thread(thread, 100);
|
||
|
+ EXPECT_EQ(ETIMEDOUT, ret);
|
||
|
+
|
||
|
+ sem_args.count = 1;
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_PULSE_SEM, &sem_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(0, sem_args.count);
|
||
|
+
|
||
|
+ ret = wait_for_thread(thread, 100);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(0, thread_args.ret);
|
||
|
+
|
||
|
+ /* delete an object while it's being waited on */
|
||
|
+
|
||
|
+ get_abs_timeout(&timeout, CLOCK_MONOTONIC, 200);
|
||
|
+ wait_args.owner = 123;
|
||
|
+ ret = pthread_create(&thread, NULL, wait_thread, &thread_args);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+
|
||
|
+ ret = wait_for_thread(thread, 100);
|
||
|
+ EXPECT_EQ(ETIMEDOUT, ret);
|
||
|
+
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_DELETE, &sem_args.sem);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ ret = ioctl(fd, WINESYNC_IOC_DELETE, &mutex_args.mutex);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+
|
||
|
+ ret = wait_for_thread(thread, 200);
|
||
|
+ EXPECT_EQ(0, ret);
|
||
|
+ EXPECT_EQ(-1, thread_args.ret);
|
||
|
+ EXPECT_EQ(ETIMEDOUT, thread_args.err);
|
||
|
+
|
||
|
+ close(fd);
|
||
|
+}
|
||
|
+
|
||
|
+TEST_HARNESS_MAIN
|