Linux userland API discussions
 help / color / mirror / Atom feed
* [PATCH v4 25/27] selftests: ntsync: Add a stress test for contended waits.
From: Elizabeth Figura @ 2024-04-16  1:08 UTC (permalink / raw)
  To: Arnd Bergmann, Greg Kroah-Hartman, Jonathan Corbet, Shuah Khan
  Cc: linux-kernel, linux-api, wine-devel, André Almeida,
	Wolfram Sang, Arkadiusz Hiler, Peter Zijlstra, Andy Lutomirski,
	linux-doc, linux-kselftest, Randy Dunlap, Ingo Molnar,
	Will Deacon, Waiman Long, Boqun Feng, Elizabeth Figura
In-Reply-To: <20240416010837.333694-1-zfigura@codeweavers.com>

Test a more realistic usage pattern, and one with heavy contention, in order to
actually exercise ntsync's internal synchronization.

This test has several threads in a tight loop acquiring a mutex, modifying some
shared data, and then releasing the mutex. At the end we check if the data is
consistent.

Signed-off-by: Elizabeth Figura <zfigura@codeweavers.com>
---
 .../testing/selftests/drivers/ntsync/ntsync.c | 74 +++++++++++++++++++
 1 file changed, 74 insertions(+)

diff --git a/tools/testing/selftests/drivers/ntsync/ntsync.c b/tools/testing/selftests/drivers/ntsync/ntsync.c
index 968874d7e325..5fa2c9a0768c 100644
--- a/tools/testing/selftests/drivers/ntsync/ntsync.c
+++ b/tools/testing/selftests/drivers/ntsync/ntsync.c
@@ -1330,4 +1330,78 @@ TEST(alert_all)
 	close(fd);
 }
 
+#define STRESS_LOOPS 10000
+#define STRESS_THREADS 4
+
+static unsigned int stress_counter;
+static int stress_device, stress_start_event, stress_mutex;
+
+static void *stress_thread(void *arg)
+{
+	struct ntsync_wait_args wait_args = {0};
+	__u32 index, count, i;
+	int ret;
+
+	wait_args.timeout = UINT64_MAX;
+	wait_args.count = 1;
+	wait_args.objs = (uintptr_t)&stress_start_event;
+	wait_args.owner = gettid();
+	wait_args.index = 0xdeadbeef;
+
+	ioctl(stress_device, NTSYNC_IOC_WAIT_ANY, &wait_args);
+
+	wait_args.objs = (uintptr_t)&stress_mutex;
+
+	for (i = 0; i < STRESS_LOOPS; ++i) {
+		ioctl(stress_device, NTSYNC_IOC_WAIT_ANY, &wait_args);
+
+		++stress_counter;
+
+		unlock_mutex(stress_mutex, wait_args.owner, &count);
+	}
+
+	return NULL;
+}
+
+TEST(stress_wait)
+{
+	struct ntsync_event_args event_args;
+	struct ntsync_mutex_args mutex_args;
+	pthread_t threads[STRESS_THREADS];
+	__u32 signaled, i;
+	int ret;
+
+	stress_device = open("/dev/ntsync", O_CLOEXEC | O_RDONLY);
+	ASSERT_LE(0, stress_device);
+
+	mutex_args.owner = 0;
+	mutex_args.count = 0;
+	ret = ioctl(stress_device, NTSYNC_IOC_CREATE_MUTEX, &mutex_args);
+	EXPECT_EQ(0, ret);
+	stress_mutex = mutex_args.mutex;
+
+	event_args.manual = 1;
+	event_args.signaled = 0;
+	ret = ioctl(stress_device, NTSYNC_IOC_CREATE_EVENT, &event_args);
+	EXPECT_EQ(0, ret);
+	stress_start_event = event_args.event;
+
+	for (i = 0; i < STRESS_THREADS; ++i)
+		pthread_create(&threads[i], NULL, stress_thread, NULL);
+
+	ret = ioctl(stress_start_event, NTSYNC_IOC_EVENT_SET, &signaled);
+	EXPECT_EQ(0, ret);
+
+	for (i = 0; i < STRESS_THREADS; ++i) {
+		ret = pthread_join(threads[i], NULL);
+		EXPECT_EQ(0, ret);
+	}
+
+	EXPECT_EQ(STRESS_LOOPS * STRESS_THREADS, stress_counter);
+
+	close(stress_start_event);
+	close(stress_mutex);
+	close(stress_device);
+}
+
 TEST_HARNESS_MAIN
-- 
2.43.0


^ permalink raw reply related

* [PATCH v4 24/27] selftests: ntsync: Add some tests for wakeup signaling via alerts.
From: Elizabeth Figura @ 2024-04-16  1:08 UTC (permalink / raw)
  To: Arnd Bergmann, Greg Kroah-Hartman, Jonathan Corbet, Shuah Khan
  Cc: linux-kernel, linux-api, wine-devel, André Almeida,
	Wolfram Sang, Arkadiusz Hiler, Peter Zijlstra, Andy Lutomirski,
	linux-doc, linux-kselftest, Randy Dunlap, Ingo Molnar,
	Will Deacon, Waiman Long, Boqun Feng, Elizabeth Figura
In-Reply-To: <20240416010837.333694-1-zfigura@codeweavers.com>

Expand the alert tests to cover alerting a thread mid-wait, to test that the
relevant scheduling logic works correctly.

Signed-off-by: Elizabeth Figura <zfigura@codeweavers.com>
---
 .../testing/selftests/drivers/ntsync/ntsync.c | 62 +++++++++++++++++++
 1 file changed, 62 insertions(+)

diff --git a/tools/testing/selftests/drivers/ntsync/ntsync.c b/tools/testing/selftests/drivers/ntsync/ntsync.c
index 5465a16d38b3..968874d7e325 100644
--- a/tools/testing/selftests/drivers/ntsync/ntsync.c
+++ b/tools/testing/selftests/drivers/ntsync/ntsync.c
@@ -1113,9 +1113,12 @@ TEST(wake_all)
 TEST(alert_any)
 {
 	struct ntsync_event_args event_args = {0};
+	struct ntsync_wait_args wait_args = {0};
 	struct ntsync_sem_args sem_args = {0};
 	__u32 index, count, signaled;
+	struct wait_args thread_args;
 	int objs[2], fd, ret;
+	pthread_t thread;
 
 	fd = open("/dev/ntsync", O_CLOEXEC | O_RDONLY);
 	ASSERT_LE(0, fd);
@@ -1163,6 +1166,34 @@ TEST(alert_any)
 	EXPECT_EQ(0, ret);
 	EXPECT_EQ(2, index);
 
+	/* test wakeup via alert */
+
+	ret = ioctl(event_args.event, NTSYNC_IOC_EVENT_RESET, &signaled);
+	EXPECT_EQ(0, ret);
+
+	wait_args.timeout = get_abs_timeout(1000);
+	wait_args.objs = (uintptr_t)objs;
+	wait_args.count = 2;
+	wait_args.owner = 123;
+	wait_args.index = 0xdeadbeef;
+	wait_args.alert = event_args.event;
+	thread_args.fd = fd;
+	thread_args.args = &wait_args;
+	thread_args.request = NTSYNC_IOC_WAIT_ANY;
+	ret = pthread_create(&thread, NULL, wait_thread, &thread_args);
+	EXPECT_EQ(0, ret);
+
+	ret = wait_for_thread(thread, 100);
+	EXPECT_EQ(ETIMEDOUT, ret);
+
+	ret = ioctl(event_args.event, NTSYNC_IOC_EVENT_SET, &signaled);
+	EXPECT_EQ(0, ret);
+
+	ret = wait_for_thread(thread, 100);
+	EXPECT_EQ(0, ret);
+	EXPECT_EQ(0, thread_args.ret);
+	EXPECT_EQ(2, wait_args.index);
+
 	close(event_args.event);
 
 	/* test with an auto-reset event */
@@ -1199,9 +1230,12 @@ TEST(alert_any)
 TEST(alert_all)
 {
 	struct ntsync_event_args event_args = {0};
+	struct ntsync_wait_args wait_args = {0};
 	struct ntsync_sem_args sem_args = {0};
+	struct wait_args thread_args;
 	__u32 index, count, signaled;
 	int objs[2], fd, ret;
+	pthread_t thread;
 
 	fd = open("/dev/ntsync", O_CLOEXEC | O_RDONLY);
 	ASSERT_LE(0, fd);
@@ -1235,6 +1269,34 @@ TEST(alert_all)
 	EXPECT_EQ(0, ret);
 	EXPECT_EQ(2, index);
 
+	/* test wakeup via alert */
+
+	ret = ioctl(event_args.event, NTSYNC_IOC_EVENT_RESET, &signaled);
+	EXPECT_EQ(0, ret);
+
+	wait_args.timeout = get_abs_timeout(1000);
+	wait_args.objs = (uintptr_t)objs;
+	wait_args.count = 2;
+	wait_args.owner = 123;
+	wait_args.index = 0xdeadbeef;
+	wait_args.alert = event_args.event;
+	thread_args.fd = fd;
+	thread_args.args = &wait_args;
+	thread_args.request = NTSYNC_IOC_WAIT_ALL;
+	ret = pthread_create(&thread, NULL, wait_thread, &thread_args);
+	EXPECT_EQ(0, ret);
+
+	ret = wait_for_thread(thread, 100);
+	EXPECT_EQ(ETIMEDOUT, ret);
+
+	ret = ioctl(event_args.event, NTSYNC_IOC_EVENT_SET, &signaled);
+	EXPECT_EQ(0, ret);
+
+	ret = wait_for_thread(thread, 100);
+	EXPECT_EQ(0, ret);
+	EXPECT_EQ(0, thread_args.ret);
+	EXPECT_EQ(2, wait_args.index);
+
 	close(event_args.event);
 
 	/* test with an auto-reset event */
-- 
2.43.0


^ permalink raw reply related

* [PATCH v4 23/27] selftests: ntsync: Add tests for alertable waits.
From: Elizabeth Figura @ 2024-04-16  1:08 UTC (permalink / raw)
  To: Arnd Bergmann, Greg Kroah-Hartman, Jonathan Corbet, Shuah Khan
  Cc: linux-kernel, linux-api, wine-devel, André Almeida,
	Wolfram Sang, Arkadiusz Hiler, Peter Zijlstra, Andy Lutomirski,
	linux-doc, linux-kselftest, Randy Dunlap, Ingo Molnar,
	Will Deacon, Waiman Long, Boqun Feng, Elizabeth Figura
In-Reply-To: <20240416010837.333694-1-zfigura@codeweavers.com>

Test the "alert" functionality of NTSYNC_IOC_WAIT_ALL and NTSYNC_IOC_WAIT_ANY,
when a wait is woken with an alert and when it is woken by an object.

Signed-off-by: Elizabeth Figura <zfigura@codeweavers.com>
---
 .../testing/selftests/drivers/ntsync/ntsync.c | 179 +++++++++++++++++-
 1 file changed, 176 insertions(+), 3 deletions(-)

diff --git a/tools/testing/selftests/drivers/ntsync/ntsync.c b/tools/testing/selftests/drivers/ntsync/ntsync.c
index 5d17eff6a370..5465a16d38b3 100644
--- a/tools/testing/selftests/drivers/ntsync/ntsync.c
+++ b/tools/testing/selftests/drivers/ntsync/ntsync.c
@@ -95,7 +95,7 @@ static int read_event_state(int event, __u32 *signaled, __u32 *manual)
 	})
 
 static int wait_objs(int fd, unsigned long request, __u32 count,
-		     const int *objs, __u32 owner, __u32 *index)
+		     const int *objs, __u32 owner, int alert, __u32 *index)
 {
 	struct ntsync_wait_args args = {0};
 	struct timespec timeout;
@@ -108,6 +108,7 @@ static int wait_objs(int fd, unsigned long request, __u32 count,
 	args.objs = (uintptr_t)objs;
 	args.owner = owner;
 	args.index = 0xdeadbeef;
+	args.alert = alert;
 	ret = ioctl(fd, request, &args);
 	*index = args.index;
 	return ret;
@@ -115,12 +116,26 @@ static int wait_objs(int fd, unsigned long request, __u32 count,
 
 static int wait_any(int fd, __u32 count, const int *objs, __u32 owner, __u32 *index)
 {
-	return wait_objs(fd, NTSYNC_IOC_WAIT_ANY, count, objs, owner, index);
+	return wait_objs(fd, NTSYNC_IOC_WAIT_ANY, count, objs, owner, 0, index);
 }
 
 static int wait_all(int fd, __u32 count, const int *objs, __u32 owner, __u32 *index)
 {
-	return wait_objs(fd, NTSYNC_IOC_WAIT_ALL, count, objs, owner, index);
+	return wait_objs(fd, NTSYNC_IOC_WAIT_ALL, count, objs, owner, 0, index);
+}
+
+static int wait_any_alert(int fd, __u32 count, const int *objs,
+			  __u32 owner, int alert, __u32 *index)
+{
+	return wait_objs(fd, NTSYNC_IOC_WAIT_ANY,
+			 count, objs, owner, alert, index);
+}
+
+static int wait_all_alert(int fd, __u32 count, const int *objs,
+			  __u32 owner, int alert, __u32 *index)
+{
+	return wait_objs(fd, NTSYNC_IOC_WAIT_ALL,
+			 count, objs, owner, alert, index);
 }
 
 TEST(semaphore_state)
@@ -1095,4 +1110,162 @@ TEST(wake_all)
 	close(fd);
 }
 
+TEST(alert_any)
+{
+	struct ntsync_event_args event_args = {0};
+	struct ntsync_sem_args sem_args = {0};
+	__u32 index, count, signaled;
+	int objs[2], fd, ret;
+
+	fd = open("/dev/ntsync", O_CLOEXEC | O_RDONLY);
+	ASSERT_LE(0, fd);
+
+	sem_args.count = 0;
+	sem_args.max = 2;
+	sem_args.sem = 0xdeadbeef;
+	ret = ioctl(fd, NTSYNC_IOC_CREATE_SEM, &sem_args);
+	EXPECT_EQ(0, ret);
+	EXPECT_NE(0xdeadbeef, sem_args.sem);
+	objs[0] = sem_args.sem;
+
+	sem_args.count = 1;
+	sem_args.max = 2;
+	sem_args.sem = 0xdeadbeef;
+	ret = ioctl(fd, NTSYNC_IOC_CREATE_SEM, &sem_args);
+	EXPECT_EQ(0, ret);
+	EXPECT_NE(0xdeadbeef, sem_args.sem);
+	objs[1] = sem_args.sem;
+
+	event_args.manual = true;
+	event_args.signaled = true;
+	ret = ioctl(fd, NTSYNC_IOC_CREATE_EVENT, &event_args);
+	EXPECT_EQ(0, ret);
+
+	ret = wait_any_alert(fd, 0, NULL, 123, event_args.event, &index);
+	EXPECT_EQ(0, ret);
+	EXPECT_EQ(0, index);
+
+	ret = ioctl(event_args.event, NTSYNC_IOC_EVENT_RESET, &signaled);
+	EXPECT_EQ(0, ret);
+
+	ret = wait_any_alert(fd, 0, NULL, 123, event_args.event, &index);
+	EXPECT_EQ(-1, ret);
+	EXPECT_EQ(ETIMEDOUT, errno);
+
+	ret = ioctl(event_args.event, NTSYNC_IOC_EVENT_SET, &signaled);
+	EXPECT_EQ(0, ret);
+
+	ret = wait_any_alert(fd, 2, objs, 123, event_args.event, &index);
+	EXPECT_EQ(0, ret);
+	EXPECT_EQ(1, index);
+
+	ret = wait_any_alert(fd, 2, objs, 123, event_args.event, &index);
+	EXPECT_EQ(0, ret);
+	EXPECT_EQ(2, index);
+
+	close(event_args.event);
+
+	/* test with an auto-reset event */
+
+	event_args.manual = false;
+	event_args.signaled = true;
+	ret = ioctl(fd, NTSYNC_IOC_CREATE_EVENT, &event_args);
+	EXPECT_EQ(0, ret);
+
+	count = 1;
+	ret = post_sem(objs[0], &count);
+	EXPECT_EQ(0, ret);
+
+	ret = wait_any_alert(fd, 2, objs, 123, event_args.event, &index);
+	EXPECT_EQ(0, ret);
+	EXPECT_EQ(0, index);
+
+	ret = wait_any_alert(fd, 2, objs, 123, event_args.event, &index);
+	EXPECT_EQ(0, ret);
+	EXPECT_EQ(2, index);
+
+	ret = wait_any_alert(fd, 2, objs, 123, event_args.event, &index);
+	EXPECT_EQ(-1, ret);
+	EXPECT_EQ(ETIMEDOUT, errno);
+
+	close(event_args.event);
+
+	close(objs[0]);
+	close(objs[1]);
+
+	close(fd);
+}
+
+TEST(alert_all)
+{
+	struct ntsync_event_args event_args = {0};
+	struct ntsync_sem_args sem_args = {0};
+	__u32 index, count, signaled;
+	int objs[2], fd, ret;
+
+	fd = open("/dev/ntsync", O_CLOEXEC | O_RDONLY);
+	ASSERT_LE(0, fd);
+
+	sem_args.count = 2;
+	sem_args.max = 2;
+	sem_args.sem = 0xdeadbeef;
+	ret = ioctl(fd, NTSYNC_IOC_CREATE_SEM, &sem_args);
+	EXPECT_EQ(0, ret);
+	EXPECT_NE(0xdeadbeef, sem_args.sem);
+	objs[0] = sem_args.sem;
+
+	sem_args.count = 1;
+	sem_args.max = 2;
+	sem_args.sem = 0xdeadbeef;
+	ret = ioctl(fd, NTSYNC_IOC_CREATE_SEM, &sem_args);
+	EXPECT_EQ(0, ret);
+	EXPECT_NE(0xdeadbeef, sem_args.sem);
+	objs[1] = sem_args.sem;
+
+	event_args.manual = true;
+	event_args.signaled = true;
+	ret = ioctl(fd, NTSYNC_IOC_CREATE_EVENT, &event_args);
+	EXPECT_EQ(0, ret);
+
+	ret = wait_all_alert(fd, 2, objs, 123, event_args.event, &index);
+	EXPECT_EQ(0, ret);
+	EXPECT_EQ(0, index);
+
+	ret = wait_all_alert(fd, 2, objs, 123, event_args.event, &index);
+	EXPECT_EQ(0, ret);
+	EXPECT_EQ(2, index);
+
+	close(event_args.event);
+
+	/* test with an auto-reset event */
+
+	event_args.manual = false;
+	event_args.signaled = true;
+	ret = ioctl(fd, NTSYNC_IOC_CREATE_EVENT, &event_args);
+	EXPECT_EQ(0, ret);
+
+	count = 2;
+	ret = post_sem(objs[1], &count);
+	EXPECT_EQ(0, ret);
+
+	ret = wait_all_alert(fd, 2, objs, 123, event_args.event, &index);
+	EXPECT_EQ(0, ret);
+	EXPECT_EQ(0, index);
+
+	ret = wait_all_alert(fd, 2, objs, 123, event_args.event, &index);
+	EXPECT_EQ(0, ret);
+	EXPECT_EQ(2, index);
+
+	ret = wait_all_alert(fd, 2, objs, 123, event_args.event, &index);
+	EXPECT_EQ(-1, ret);
+	EXPECT_EQ(ETIMEDOUT, errno);
+
+	close(event_args.event);
+
+	close(objs[0]);
+	close(objs[1]);
+
+	close(fd);
+}
+
 TEST_HARNESS_MAIN
-- 
2.43.0


^ permalink raw reply related

* [PATCH v4 26/27] maintainers: Add an entry for ntsync.
From: Elizabeth Figura @ 2024-04-16  1:08 UTC (permalink / raw)
  To: Arnd Bergmann, Greg Kroah-Hartman, Jonathan Corbet, Shuah Khan
  Cc: linux-kernel, linux-api, wine-devel, André Almeida,
	Wolfram Sang, Arkadiusz Hiler, Peter Zijlstra, Andy Lutomirski,
	linux-doc, linux-kselftest, Randy Dunlap, Ingo Molnar,
	Will Deacon, Waiman Long, Boqun Feng, Elizabeth Figura
In-Reply-To: <20240416010837.333694-1-zfigura@codeweavers.com>

Add myself as maintainer, supported by CodeWeavers.

Signed-off-by: Elizabeth Figura <zfigura@codeweavers.com>
---
 MAINTAINERS | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index 41a013dfebbc..09ae011a8d91 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -15728,6 +15728,15 @@ T:	git https://github.com/Paragon-Software-Group/linux-ntfs3.git
 F:	Documentation/filesystems/ntfs3.rst
 F:	fs/ntfs3/
 
+NTSYNC SYNCHRONIZATION PRIMITIVE DRIVER
+M:	Elizabeth Figura <zfigura@codeweavers.com>
+L:	wine-devel@winehq.org
+S:	Supported
+F:	Documentation/userspace-api/ntsync.rst
+F:	drivers/misc/ntsync.c
+F:	include/uapi/linux/ntsync.h
+F:	tools/testing/selftests/drivers/ntsync/
+
 NUBUS SUBSYSTEM
 M:	Finn Thain <fthain@linux-m68k.org>
 L:	linux-m68k@lists.linux-m68k.org
-- 
2.43.0


^ permalink raw reply related

* [PATCH v4 27/27] docs: ntsync: Add documentation for the ntsync uAPI.
From: Elizabeth Figura @ 2024-04-16  1:08 UTC (permalink / raw)
  To: Arnd Bergmann, Greg Kroah-Hartman, Jonathan Corbet, Shuah Khan
  Cc: linux-kernel, linux-api, wine-devel, André Almeida,
	Wolfram Sang, Arkadiusz Hiler, Peter Zijlstra, Andy Lutomirski,
	linux-doc, linux-kselftest, Randy Dunlap, Ingo Molnar,
	Will Deacon, Waiman Long, Boqun Feng, Elizabeth Figura,
	Bagas Sanjaya
In-Reply-To: <20240416010837.333694-1-zfigura@codeweavers.com>

Add an overall explanation of the driver architecture, and complete and precise
specification for its intended behaviour.

Reviewed-by: Bagas Sanjaya <bagasdotme@gmail.com>
Signed-off-by: Elizabeth Figura <zfigura@codeweavers.com>
---
 Documentation/userspace-api/index.rst  |   1 +
 Documentation/userspace-api/ntsync.rst | 399 +++++++++++++++++++++++++
 2 files changed, 400 insertions(+)
 create mode 100644 Documentation/userspace-api/ntsync.rst

diff --git a/Documentation/userspace-api/index.rst b/Documentation/userspace-api/index.rst
index afecfe3cc4a8..d5745a500fa7 100644
--- a/Documentation/userspace-api/index.rst
+++ b/Documentation/userspace-api/index.rst
@@ -62,6 +62,7 @@ Everything else
    vduse
    futex2
    perf_ring_buffer
+   ntsync
 
 .. only::  subproject and html
 
diff --git a/Documentation/userspace-api/ntsync.rst b/Documentation/userspace-api/ntsync.rst
new file mode 100644
index 000000000000..202c2350d3af
--- /dev/null
+++ b/Documentation/userspace-api/ntsync.rst
@@ -0,0 +1,399 @@
+===================================
+NT synchronization primitive driver
+===================================
+
+This page documents the user-space API for the ntsync driver.
+
+ntsync is a support driver for emulation of NT synchronization
+primitives by user-space NT emulators. It exists because implementation
+in user-space, using existing tools, cannot match Windows performance
+while offering accurate semantics. It is implemented entirely in
+software, and does not drive any hardware device.
+
+This interface is meant as a compatibility tool only, and should not
+be used for general synchronization. Instead use generic, versatile
+interfaces such as futex(2) and poll(2).
+
+Synchronization primitives
+==========================
+
+The ntsync driver exposes three types of synchronization primitives:
+semaphores, mutexes, and events.
+
+A semaphore holds a single volatile 32-bit counter, and a static 32-bit
+integer denoting the maximum value. It is considered signaled when the
+counter is nonzero. The counter is decremented by one when a wait is
+satisfied. Both the initial and maximum count are established when the
+semaphore is created.
+
+A mutex holds a volatile 32-bit recursion count, and a volatile 32-bit
+identifier denoting its owner. A mutex is considered signaled when its
+owner is zero (indicating that it is not owned). The recursion count is
+incremented when a wait is satisfied, and ownership is set to the given
+identifier.
+
+A mutex also holds an internal flag denoting whether its previous owner
+has died; such a mutex is said to be abandoned. Owner death is not
+tracked automatically based on thread death, but rather must be
+communicated using ``NTSYNC_IOC_MUTEX_KILL``. An abandoned mutex is
+inherently considered unowned.
+
+Except for the "unowned" semantics of zero, the actual value of the
+owner identifier is not interpreted by the ntsync driver at all. The
+intended use is to store a thread identifier; however, the ntsync
+driver does not actually validate that a calling thread provides
+consistent or unique identifiers.
+
+An event holds a volatile boolean state denoting whether it is signaled
+or not. There are two types of events, auto-reset and manual-reset. An
+auto-reset event is designaled when a wait is satisfied; a manual-reset
+event is not. The event type is specified when the event is created.
+
+Unless specified otherwise, all operations on an object are atomic and
+totally ordered with respect to other operations on the same object.
+
+Objects are represented by files. When all file descriptors to an
+object are closed, that object is deleted.
+
+Char device
+===========
+
+The ntsync driver creates a single char device /dev/ntsync. Each file
+description opened on the device represents a unique instance intended
+to back an individual NT virtual machine. Objects created by one ntsync
+instance may only be used with other objects created by the same
+instance.
+
+ioctl reference
+===============
+
+All operations on the device are done through ioctls. There are four
+structures used in ioctl calls::
+
+   struct ntsync_sem_args {
+   	__u32 sem;
+   	__u32 count;
+   	__u32 max;
+   };
+
+   struct ntsync_mutex_args {
+   	__u32 mutex;
+   	__u32 owner;
+   	__u32 count;
+   };
+
+   struct ntsync_event_args {
+   	__u32 event;
+   	__u32 signaled;
+   	__u32 manual;
+   };
+
+   struct ntsync_wait_args {
+   	__u64 timeout;
+   	__u64 objs;
+   	__u32 count;
+   	__u32 owner;
+   	__u32 index;
+   	__u32 alert;
+   	__u32 flags;
+   	__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.
+
+The ioctls on the device file are as follows:
+
+.. c:macro:: NTSYNC_IOC_CREATE_SEM
+
+  Create a semaphore object. Takes a pointer to struct
+  :c:type:`ntsync_sem_args`, which is used as follows:
+
+  .. list-table::
+
+     * - ``sem``
+       - On output, contains a file descriptor to the created semaphore.
+     * - ``count``
+       - Initial count of the semaphore.
+     * - ``max``
+       - Maximum count of the semaphore.
+
+  Fails with ``EINVAL`` if ``count`` is greater than ``max``.
+
+.. c:macro:: NTSYNC_IOC_CREATE_MUTEX
+
+  Create a mutex object. Takes a pointer to struct
+  :c:type:`ntsync_mutex_args`, which is used as follows:
+
+  .. list-table::
+
+     * - ``mutex``
+       - On output, contains a file descriptor to the created mutex.
+     * - ``count``
+       - Initial recursion count of the mutex.
+     * - ``owner``
+       - Initial owner of the mutex.
+
+  If ``owner`` is nonzero and ``count`` is zero, or if ``owner`` is
+  zero and ``count`` is nonzero, the function fails with ``EINVAL``.
+
+.. c:macro:: NTSYNC_IOC_CREATE_EVENT
+
+  Create an event object. Takes a pointer to struct
+  :c:type:`ntsync_event_args`, which is used as follows:
+
+  .. list-table::
+
+     * - ``event``
+       - On output, contains a file descriptor to the created event.
+     * - ``signaled``
+       - If nonzero, the event is initially signaled, otherwise
+         nonsignaled.
+     * - ``manual``
+       - If nonzero, the event is a manual-reset event, otherwise
+         auto-reset.
+
+The ioctls on the individual objects are as follows:
+
+.. c:macro:: NTSYNC_IOC_SEM_POST
+
+  Post to a semaphore object. Takes a pointer to a 32-bit integer,
+  which on input holds the count to be added to the semaphore, and on
+  output contains its previous count.
+
+  If adding to the semaphore's current count would raise the latter
+  past the semaphore's maximum count, the ioctl fails with
+  ``EOVERFLOW`` and the semaphore is not affected. If raising the
+  semaphore's count causes it to become signaled, eligible threads
+  waiting on this semaphore will be woken and the semaphore's count
+  decremented appropriately.
+
+.. c:macro:: NTSYNC_IOC_MUTEX_UNLOCK
+
+  Release a mutex object. Takes a pointer to struct
+  :c:type:`ntsync_mutex_args`, which is used as follows:
+
+  .. list-table::
+
+     * - ``mutex``
+       - Ignored.
+     * - ``owner``
+       - Specifies the owner trying to release this mutex.
+     * - ``count``
+       - On output, contains the previous recursion count.
+
+  If ``owner`` is zero, the ioctl fails with ``EINVAL``. If ``owner``
+  is not the current owner of the mutex, the ioctl fails with
+  ``EPERM``.
+
+  The mutex's count will be decremented by one. If decrementing the
+  mutex's count causes it to become zero, the mutex is marked as
+  unowned and signaled, and eligible threads waiting on it will be
+  woken as appropriate.
+
+.. c:macro:: NTSYNC_IOC_SET_EVENT
+
+  Signal an event object. Takes a pointer to a 32-bit integer, which on
+  output contains the previous state of the event.
+
+  Eligible threads will be woken, and auto-reset events will be
+  designaled appropriately.
+
+.. c:macro:: NTSYNC_IOC_RESET_EVENT
+
+  Designal an event object. Takes a pointer to a 32-bit integer, which
+  on output contains the previous state of the event.
+
+.. c:macro:: NTSYNC_IOC_PULSE_EVENT
+
+  Wake threads waiting on an event object while leaving it in an
+  unsignaled state. Takes a pointer to a 32-bit integer, which on
+  output contains the previous state of the event.
+
+  A pulse operation can be thought of as a set followed by a reset,
+  performed as a single atomic operation. If two threads are waiting on
+  an auto-reset event which is pulsed, only one will be woken. If two
+  threads are waiting a manual-reset event which is pulsed, both will
+  be woken. However, in both cases, the event will be unsignaled
+  afterwards, and a simultaneous read operation will always report the
+  event as unsignaled.
+
+.. c:macro:: NTSYNC_IOC_READ_SEM
+
+  Read the current state of a semaphore object. Takes a pointer to
+  struct :c:type:`ntsync_sem_args`, which is used as follows:
+
+  .. list-table::
+
+     * - ``sem``
+       - Ignored.
+     * - ``count``
+       - On output, contains the current count of the semaphore.
+     * - ``max``
+       - On output, contains the maximum count of the semaphore.
+
+.. c:macro:: NTSYNC_IOC_READ_MUTEX
+
+  Read the current state of a mutex object. Takes a pointer to struct
+  :c:type:`ntsync_mutex_args`, which is used as follows:
+
+  .. list-table::
+
+     * - ``mutex``
+       - Ignored.
+     * - ``owner``
+       - On output, contains the current owner of the mutex, or zero
+         if the mutex is not currently owned.
+     * - ``count``
+       - On output, contains the current recursion count of the mutex.
+
+  If the mutex is marked as abandoned, the function fails with
+  ``EOWNERDEAD``. In this case, ``count`` and ``owner`` are set to
+  zero.
+
+.. c:macro:: NTSYNC_IOC_READ_EVENT
+
+  Read the current state of an event object. Takes a pointer to struct
+  :c:type:`ntsync_event_args`, which is used as follows:
+
+  .. list-table::
+
+     * - ``event``
+       - Ignored.
+     * - ``signaled``
+       - On output, contains the current state of the event.
+     * - ``manual``
+       - On output, contains 1 if the event is a manual-reset event,
+         and 0 otherwise.
+
+.. c:macro:: NTSYNC_IOC_KILL_OWNER
+
+  Mark a mutex as unowned and abandoned if it is owned by the given
+  owner. Takes an input-only pointer to a 32-bit integer denoting the
+  owner. If the owner is zero, the ioctl fails with ``EINVAL``. If the
+  owner does not own the mutex, the function fails with ``EPERM``.
+
+  Eligible threads waiting on the mutex will be woken as appropriate
+  (and such waits will fail with ``EOWNERDEAD``, as described below).
+
+.. c:macro:: NTSYNC_IOC_WAIT_ANY
+
+  Poll on any of a list of objects, atomically acquiring at most one.
+  Takes a pointer to struct :c:type:`ntsync_wait_args`, which is
+  used as follows:
+
+  .. list-table::
+
+     * - ``timeout``
+       - Absolute timeout in nanoseconds. If ``NTSYNC_WAIT_REALTIME``
+         is set, the timeout is measured against the REALTIME clock;
+         otherwise it is measured against the MONOTONIC clock. If the
+         timeout is equal to or earlier than the current time, the
+         function returns immediately without sleeping. If ``timeout``
+         is U64_MAX, the function will sleep until an object is
+         signaled, and will not fail with ``ETIMEDOUT``.
+     * - ``objs``
+       - Pointer to an array of ``count`` file descriptors
+         (specified as an integer so that the structure has the same
+         size regardless of architecture). If any object is
+         invalid, the function fails with ``EINVAL``.
+     * - ``count``
+       - Number of objects specified in the ``objs`` array.
+         If greater than ``NTSYNC_MAX_WAIT_COUNT``, the function fails
+         with ``EINVAL``.
+     * - ``owner``
+       - Mutex owner identifier. If any object in ``objs`` is a mutex,
+         the ioctl will attempt to acquire that mutex on behalf of
+         ``owner``. If ``owner`` is zero, the ioctl fails with
+         ``EINVAL``.
+     * - ``index``
+       - On success, contains the index (into ``objs``) of the object
+         which was signaled. If ``alert`` was signaled instead,
+         this contains ``count``.
+     * - ``alert``
+       - Optional event object file descriptor. If nonzero, this
+         specifies an "alert" event object which, if signaled, will
+         terminate the wait. If nonzero, the identifier must point to a
+         valid event.
+     * - ``flags``
+       - Zero or more flags. Currently the only flag is
+         ``NTSYNC_WAIT_REALTIME``, which causes the timeout to be
+         measured against the REALTIME clock instead of MONOTONIC.
+     * - ``pad``
+       - Unused, must be set to zero.
+
+  This function attempts to acquire one of the given objects. If unable
+  to do so, it sleeps until an object becomes signaled, subsequently
+  acquiring it, or the timeout expires. In the latter case the ioctl
+  fails with ``ETIMEDOUT``. The function only acquires one object, even
+  if multiple objects are signaled.
+
+  A semaphore is considered to be signaled if its count is nonzero, and
+  is acquired by decrementing its count by one. A mutex is considered
+  to be signaled if it is unowned or if its owner matches the ``owner``
+  argument, and is acquired by incrementing its recursion count by one
+  and setting its owner to the ``owner`` argument. An auto-reset event
+  is acquired by designaling it; a manual-reset event is not affected
+  by acquisition.
+
+  Acquisition is atomic and totally ordered with respect to other
+  operations on the same object. If two wait operations (with different
+  ``owner`` identifiers) are queued on the same mutex, only one is
+  signaled. If two wait operations are queued on the same semaphore,
+  and a value of one is posted to it, only one is signaled. The order
+  in which threads are signaled is not specified.
+
+  If an abandoned mutex is acquired, the ioctl fails with
+  ``EOWNERDEAD``. Although this is a failure return, the function may
+  otherwise be considered successful. The mutex is marked as owned by
+  the given owner (with a recursion count of 1) and as no longer
+  abandoned, and ``index`` is still set to the index of the mutex.
+
+  The ``alert`` argument is an "extra" event which can terminate the
+  wait, independently of all other objects. If members of ``objs`` and
+  ``alert`` are both simultaneously signaled, a member of ``objs`` will
+  always be given priority and acquired first.
+
+  It is valid to pass the same object more than once, including by
+  passing the same event in the ``objs`` array and in ``alert``. If a
+  wakeup occurs due to that object being signaled, ``index`` is set to
+  the lowest index corresponding to that object.
+
+  The function may fail with ``EINTR`` if a signal is received.
+
+.. c:macro:: NTSYNC_IOC_WAIT_ALL
+
+  Poll on a list of objects, atomically acquiring all of them. Takes a
+  pointer to struct :c:type:`ntsync_wait_args`, which is used
+  identically to ``NTSYNC_IOC_WAIT_ANY``, except that ``index`` is
+  always filled with zero on success if not woken via alert.
+
+  This function attempts to simultaneously acquire all of the given
+  objects. If unable to do so, it sleeps until all objects become
+  simultaneously signaled, subsequently acquiring them, or the timeout
+  expires. In the latter case the ioctl fails with ``ETIMEDOUT`` and no
+  objects are modified.
+
+  Objects may become signaled and subsequently designaled (through
+  acquisition by other threads) while this thread is sleeping. Only
+  once all objects are simultaneously signaled does the ioctl acquire
+  them and return. The entire acquisition is atomic and totally ordered
+  with respect to other operations on any of the given objects.
+
+  If an abandoned mutex is acquired, the ioctl fails with
+  ``EOWNERDEAD``. Similarly to ``NTSYNC_IOC_WAIT_ANY``, all objects are
+  nevertheless marked as acquired. Note that if multiple mutex objects
+  are specified, there is no way to know which were marked as
+  abandoned.
+
+  As with "any" waits, the ``alert`` argument is an "extra" event which
+  can terminate the wait. Critically, however, an "all" wait will
+  succeed if all members in ``objs`` are signaled, *or* if ``alert`` is
+  signaled. In the latter case ``index`` will be set to ``count``. As
+  with "any" waits, if both conditions are filled, the former takes
+  priority, and objects in ``objs`` will be acquired.
+
+  Unlike ``NTSYNC_IOC_WAIT_ANY``, it is not valid to pass the same
+  object more than once, nor is it valid to pass the same object in
+  ``objs`` and in ``alert``. If this is attempted, the function fails
+  with ``EINVAL``.
-- 
2.43.0


^ permalink raw reply related

* Re: [PATCH v4 27/27] docs: ntsync: Add documentation for the ntsync uAPI.
From: Randy Dunlap @ 2024-04-16  2:13 UTC (permalink / raw)
  To: Elizabeth Figura, Arnd Bergmann, Greg Kroah-Hartman,
	Jonathan Corbet, Shuah Khan
  Cc: linux-kernel, linux-api, wine-devel, André Almeida,
	Wolfram Sang, Arkadiusz Hiler, Peter Zijlstra, Andy Lutomirski,
	linux-doc, linux-kselftest, Ingo Molnar, Will Deacon, Waiman Long,
	Boqun Feng, Bagas Sanjaya
In-Reply-To: <20240416010837.333694-28-zfigura@codeweavers.com>



On 4/15/24 6:08 PM, Elizabeth Figura wrote:
> Add an overall explanation of the driver architecture, and complete and precise
> specification for its intended behaviour.
> 
> Reviewed-by: Bagas Sanjaya <bagasdotme@gmail.com>
> Signed-off-by: Elizabeth Figura <zfigura@codeweavers.com>

Tested-by: Randy Dunlap <rdunlap@infradead.org>

Thanks.

> ---
>  Documentation/userspace-api/index.rst  |   1 +
>  Documentation/userspace-api/ntsync.rst | 399 +++++++++++++++++++++++++
>  2 files changed, 400 insertions(+)
>  create mode 100644 Documentation/userspace-api/ntsync.rst

-- 
#Randy
https://people.kernel.org/tglx/notes-about-netiquette
https://subspace.kernel.org/etiquette.html

^ permalink raw reply

* Re: [PATCH v4 00/30] NT synchronization primitive driver
From: Peter Zijlstra @ 2024-04-16  8:14 UTC (permalink / raw)
  To: Elizabeth Figura
  Cc: Arnd Bergmann, Greg Kroah-Hartman, Jonathan Corbet, Shuah Khan,
	linux-kernel, linux-api, wine-devel, André Almeida,
	Wolfram Sang, Arkadiusz Hiler, Andy Lutomirski, linux-doc,
	linux-kselftest, Randy Dunlap, Ingo Molnar, Will Deacon,
	Waiman Long, Boqun Feng
In-Reply-To: <20240416010837.333694-1-zfigura@codeweavers.com>

On Mon, Apr 15, 2024 at 08:08:10PM -0500, Elizabeth Figura wrote:
> This patch series implements a new char misc driver, /dev/ntsync, which is used
> to implement Windows NT synchronization primitives.

This patch series does not apply to anything I have at hand. Nor does it
state anything explicit to put it on top of.


> Hence I would like to request review from someone familiar with locking to make
> sure that the usage of low-level kernel primitives is correct and that the wait
> queues work as intended, and to that end I've CC'd the locking maintainers.

I am sadly very limited atm, but I'll try and read through it. If only I
could apply...



> == Patches ==
> 
> The intended semantics of the patches are broadly intended to match those of the
> corresponding Windows functions. For those not already familiar with the Windows
> functions (or their undocumented behaviour), patch 27/27 provides a detailed
> specification, and individual patches also include a brief description of the
> API they are implementing.
> 
> The patches making use of this driver in Wine can be retrieved or browsed here:
> 
>     https://repo.or.cz/wine/zf.git/shortlog/refs/heads/ntsync5

I don't support GE has it in his builds? Last time I tried, building
Wine was a bit of a pain.


> Some aspects of the implementation may deserve particular comment:
> 
> * In the interest of performance, each object is governed only by a single
>   spinlock. However, NTSYNC_IOC_WAIT_ALL requires that the state of multiple
>   objects be changed as a single atomic operation. In order to achieve this, we
>   first take a device-wide lock ("wait_all_lock") any time we are going to lock
>   more than one object at a time.
> 
>   The maximum number of objects that can be used in a vectored wait, and
>   therefore the maximum that can be locked simultaneously, is 64. This number is
>   NT's own limit.
> 
>   The acquisition of multiple spinlocks will degrade performance. This is a
>   conscious choice, however. Wait-for-all is known to be a very rare operation
>   in practice, especially with counts that approach the maximum, and it is the
>   intent of the ntsync driver to optimize wait-for-any at the expense of
>   wait-for-all as much as possible.

Per the example of percpu-rwsem, it would be possible to create a
mutex-spinlock hybrid scheme, where single locks are spinlocks while
held, but can block when the global thing is pending. And the global
lock is always mutex like.

If all that is worth it, I don't know. Nesting 64 spinlocks doesn't give
me warm and fuzzy feelings though.

^ permalink raw reply

* Re: [PATCH v7] posix-timers: add clock_compare system call
From: Sagi Maimon @ 2024-04-16  8:39 UTC (permalink / raw)
  To: Mahesh Bandewar (महेश बंडेवार)
  Cc: Thomas Gleixner, richardcochran, luto, mingo, bp, dave.hansen,
	x86, hpa, arnd, geert, peterz, hannes, sohil.mehta,
	rick.p.edgecombe, nphamcs, palmer, keescook, legion, mark.rutland,
	mszeredi, casey, reibax, davem, brauner, linux-kernel, linux-api,
	linux-arch, netdev
In-Reply-To: <CAF2d9jg0X_HKSZbiwPTEXdmrhY49D1zfT3Q4xzNAqv1z+TYXtA@mail.gmail.com>

Thanks

On Mon, Apr 15, 2024 at 8:23 PM Mahesh Bandewar (महेश बंडेवार)
<maheshb@google.com> wrote:
>
> On Sun, Apr 14, 2024 at 5:22 AM Sagi Maimon <maimon.sagi@gmail.com> wrote:
> >
> > On Thu, Apr 11, 2024 at 7:34 PM Mahesh Bandewar (महेश बंडेवार)
> > <maheshb@google.com> wrote:
> > >
> > > On Thu, Apr 11, 2024 at 12:11 AM Sagi Maimon <maimon.sagi@gmail.com> wrote:
> > > >
> > > > Hi Mahesh
> > > > What is the status of your patch?
> > > > if your patch is upstreamed , then it will have all I need.
> > > > But, If not , I will upstream my patch.
> > > > BR,
> > > >
> > > Hi Sagi,
> > >
> > > If you want to pursue the syscall option, then those are tangential
> > > and please go ahead. (I cannot stop you!)
> > > I'm interested in getting the "tight sandwich timestamps" that
> > > gettimex64() ioctl offers and I would want enhancements to
> > > gettimex64() done the way it was discussed in the later half of this
> > > thread. If you want to sign-up for that please let me know.
> > Hi Mahesh
> > I do need to modify the  PTP_SYS_OFFSET_EXTENDED ioctl for cases which
> > gettimex64
> > not supported by the driver (look at Thomas suggestion), but I need
> > your changes in ptp_read_system_prets.
> > I like to add my changes above your changes, so we won't do duplicate work.
> > please show me your latest patch and the status of it
> > Once you have upstream yours , I will add my changes on the next patch.
>
> OK, in that case let me post the patch since your changes would need
> pieces from it.
>
> thanks,
> --mahesh..
>
> > BR
> > Sagi
> >
> > >
> > > thanks,
> > > --mahesh..
> > >
> > >
> > > > On Thu, Apr 11, 2024 at 5:56 AM Mahesh Bandewar (महेश बंडेवार)
> > > > <maheshb@google.com> wrote:
> > > > >
> > > > > On Wed, Apr 3, 2024 at 6:48 AM Thomas Gleixner <tglx@linutronix.de> wrote:
> > > > > >
> > > > > > On Tue, Apr 02 2024 at 16:37, Mahesh Bandewar (महेश बंडेवार) wrote:
> > > > > > > On Tue, Apr 2, 2024 at 3:37 PM Thomas Gleixner <tglx@linutronix.de> wrote:
> > > > > > > The modification that you have proposed (in a couple of posts back)
> > > > > > > would work but it's still not ideal since the pre/post ts are not
> > > > > > > close enough as they are currently  (properly implemented!)
> > > > > > > gettimex64() would have. The only way to do that would be to have
> > > > > > > another ioctl as I have proposed which is a superset of current
> > > > > > > gettimex64 and pre-post collection is the closest possible.
> > > > > >
> > > > > > Errm. What I posted as sketch _is_ using gettimex64() with the extra
> > > > > > twist of the flag vs. a clockid (which is an implementation detail) and
> > > > > > the difference that I carry the information in ptp_system_timestamp
> > > > > > instead of needing a new argument clockid to all existing callbacks
> > > > > > because the modification to ptp_read_prets() and postts() will just be
> > > > > > sufficient, no?
> > > > > >
> > > > > OK, that makes sense.
> > > > >
> > > > > > For the case where the driver does not provide gettimex64() then the
> > > > > > extension of the original offset ioctl is still providing a better
> > > > > > mechanism than the proposed syscall.
> > > > > >
> > > > > > I also clearly said that all drivers should be converted over to
> > > > > > gettimex64().
> > > > > >
> > > > > I agree. Honestly that should have been mandatory and
> > > > > ptp_register_clock() should fail otherwise! Probably should have been
> > > > > part of gettimex64 implementation :(
> > > > >
> > > > > I don't think we can do anything other than just hoping all driver
> > > > > implementations include gettimex64 implementation.
> > > > >
> > > > > > > Having said that, the 'flag' modification proposal is a good backup
> > > > > > > for the drivers that don't have good implementation (close enough but
> > > > > > > not ideal). Also, you don't need a new ioctl-op. So if we really want
> > > > > > > precision, I believe, we need a new ioctl op (with supporting
> > > > > > > implementation similar to the mlx4 code above). but we want to save
> > > > > > > the new ioctl-op and have less precision then proposed modification
> > > > > > > would work fine.
> > > > > >
> > > > > > I disagree. The existing gettimex64() is good enough if the driver
> > > > > > implements it correctly today. If not then those drivers need to be
> > > > > > fixed independent of this.
> > > > > >
> > > > > > So assumed that a driver does:
> > > > > >
> > > > > > gettimex64()
> > > > > >    ptp_prets(sts);
> > > > > >    read_clock();
> > > > > >    ptp_postts(sts);
> > > > > >
> > > > > > today then having:
> > > > > >
> > > > > > static inline void ptp_read_system_prets(struct ptp_system_timestamp *sts)
> > > > > > {
> > > > > >         if (sts) {
> > > > > >                 if (sts->flags & PTP_SYS_OFFSET_MONO_RAW)
> > > > > >                         ktime_get_raw_ts64(&sts->pre_ts);
> > > > > >                 else
> > > > > >                         ktime_get_real_ts64(&sts->pre_ts);
> > > > > >         }
> > > > > > }
> > > > > >
> > > > > > static inline void ptp_read_system_postts(struct ptp_system_timestamp *sts)
> > > > > > {
> > > > > >         if (sts) {
> > > > > >                 if (sts->flags & PTP_SYS_OFFSET_MONO_RAW)
> > > > > >                         ktime_get_raw_ts64(&sts->post_ts);
> > > > > >                 else
> > > > > >                         ktime_get_real_ts64(&sts->post_ts);
> > > > > >         }
> > > > > > }
> > > > > >
> > > > > > or
> > > > > >
> > > > > > static inline void ptp_read_system_prets(struct ptp_system_timestamp *sts)
> > > > > > {
> > > > > >         if (sts) {
> > > > > >                 switch (sts->clockid) {
> > > > > >                 case CLOCK_MONOTONIC_RAW:
> > > > > >                         time_get_raw_ts64(&sts->pre_ts);
> > > > > >                         break;
> > > > > >                 case CLOCK_REALTIME:
> > > > > >                         ktime_get_real_ts64(&sts->pre_ts);
> > > > > >                         break;
> > > > > >                 }
> > > > > >         }
> > > > > > }
> > > > > >
> > > > > > static inline void ptp_read_system_postts(struct ptp_system_timestamp *sts)
> > > > > > {
> > > > > >         if (sts) {
> > > > > >                 switch (sts->clockid) {
> > > > > >                 case CLOCK_MONOTONIC_RAW:
> > > > > >                         time_get_raw_ts64(&sts->post_ts);
> > > > > >                         break;
> > > > > >                 case CLOCK_REALTIME:
> > > > > >                         ktime_get_real_ts64(&sts->post_ts);
> > > > > >                         break;
> > > > > >                 }
> > > > > >         }
> > > > > > }
> > > > > >
> > > > > > is doing the exact same thing as your proposal but without touching any
> > > > > > driver which implements gettimex64() correctly at all.
> > > > > >
> > > > > I see. Yes, this makes sense.
> > > > >
> > > > > > While your proposal requires to touch every single driver for no reason,
> > > > > > no?
> > > > > >
> > > > > > It is just an implementation detail whether you use a flag or a
> > > > > > clockid. You can carry the clockid for the clocks which actually can be
> > > > > > read in that context in a reserved field of PTP_SYS_OFFSET_EXTENDED:
> > > > > >
> > > > > > struct ptp_sys_offset_extended {
> > > > > >         unsigned int    n_samples; /* Desired number of measurements. */
> > > > > >         clockid_t       clockid;
> > > > > >         unsigned int    rsv[2];    /* Reserved for future use. */
> > > > > > };
> > > > > >
> > > > > > and in the IOCTL:
> > > > > >
> > > > > >         if (extoff->clockid != CLOCK_MONOTONIC_RAW)
> > > > > >                 return -EINVAL;
> > > > > >
> > > > > >         sts.clockid = extoff->clockid;
> > > > > >
> > > > > > and it all just works, no?
> > > > > >
> > > > > Yes, this should work. However, I didn't check if struct
> > > > > ptp_system_timestamp is used in some other context.
> > > > >
> > > > > > I have no problem to decide that PTP_SYS_OFFSET will not get this
> > > > > > treatment and the drivers have to be converted over to
> > > > > > PTP_SYS_OFFSET_EXTENDED.
> > > > > >
> > > > > > But adding yet another callback just to carry a clockid as argument is a
> > > > > > more than pointless exercise as I demonstrated.
> > > > > >
> > > > > Agreed. As I said, I thought we cannot change the gettimex64() without
> > > > > breaking the compatibility but the fact that CLOCK_REALTIME is "0"
> > > > > works well for the backward compatibility case.
> > > > >
> > > > > I can spin up an updated patch/series that updates gettimex64
> > > > > implementation instead of adding a new ioctl-op If you all agree.
> > > > >
> > > > > thanks,
> > > > > --mahesh..
> > > > >
> > > > > > Thanks,
> > > > > >
> > > > > >         tglx

^ permalink raw reply

* Re: [PATCH v4 00/30] NT synchronization primitive driver
From: Greg Kroah-Hartman @ 2024-04-16  8:49 UTC (permalink / raw)
  To: Peter Zijlstra
  Cc: Elizabeth Figura, Arnd Bergmann, Jonathan Corbet, Shuah Khan,
	linux-kernel, linux-api, wine-devel, André Almeida,
	Wolfram Sang, Arkadiusz Hiler, Andy Lutomirski, linux-doc,
	linux-kselftest, Randy Dunlap, Ingo Molnar, Will Deacon,
	Waiman Long, Boqun Feng
In-Reply-To: <20240416081421.GB31647@noisy.programming.kicks-ass.net>

On Tue, Apr 16, 2024 at 10:14:21AM +0200, Peter Zijlstra wrote:
> On Mon, Apr 15, 2024 at 08:08:10PM -0500, Elizabeth Figura wrote:
> > This patch series implements a new char misc driver, /dev/ntsync, which is used
> > to implement Windows NT synchronization primitives.
> 
> This patch series does not apply to anything I have at hand. Nor does it
> state anything explicit to put it on top of.

Should work on linux-next as I took a few of the original commits from
the last series already.

thanks,

greg k-h

^ permalink raw reply

* Re: [PATCH v4 00/30] NT synchronization primitive driver
From: Peter Zijlstra @ 2024-04-16 15:50 UTC (permalink / raw)
  To: Elizabeth Figura
  Cc: Arnd Bergmann, Greg Kroah-Hartman, Jonathan Corbet, Shuah Khan,
	linux-kernel, linux-api, wine-devel, André Almeida,
	Wolfram Sang, Arkadiusz Hiler, Andy Lutomirski, linux-doc,
	linux-kselftest, Randy Dunlap, Ingo Molnar, Will Deacon,
	Waiman Long, Boqun Feng
In-Reply-To: <20240416081421.GB31647@noisy.programming.kicks-ass.net>

On Tue, Apr 16, 2024 at 10:14:21AM +0200, Peter Zijlstra wrote:

> > Some aspects of the implementation may deserve particular comment:
> > 
> > * In the interest of performance, each object is governed only by a single
> >   spinlock. However, NTSYNC_IOC_WAIT_ALL requires that the state of multiple
> >   objects be changed as a single atomic operation. In order to achieve this, we
> >   first take a device-wide lock ("wait_all_lock") any time we are going to lock
> >   more than one object at a time.
> > 
> >   The maximum number of objects that can be used in a vectored wait, and
> >   therefore the maximum that can be locked simultaneously, is 64. This number is
> >   NT's own limit.

AFAICT:

	spin_lock(&dev->wait_all_lock);
	  list_for_each_entry(entry, &obj->all_waiters, node)
	    for (i=0; i<count; i++)
	      spin_lock_nest_lock(q->entries[i].obj->lock, &dev->wait_all_lock);

Where @count <= NTSYNC_MAX_WAIT_COUNT.

So while this nests at most 65 spinlocks, there is no actual bound on
the amount of nested lock sections in total. That is, all_waiters list
can be grown without limits.

Can we pretty please make wait_all_lock a mutex ?

> >   The acquisition of multiple spinlocks will degrade performance. This is a
> >   conscious choice, however. Wait-for-all is known to be a very rare operation
> >   in practice, especially with counts that approach the maximum, and it is the
> >   intent of the ntsync driver to optimize wait-for-any at the expense of
> >   wait-for-all as much as possible.

Typical sane usage is a good guide for performance, but you must not
forget about malicious userspace and what they can do on purpose to mess
you up.


Anyway, let me stare more at all this....

^ permalink raw reply

* Re: [PATCH v4 00/30] NT synchronization primitive driver
From: Peter Zijlstra @ 2024-04-16 15:53 UTC (permalink / raw)
  To: Elizabeth Figura
  Cc: Arnd Bergmann, Greg Kroah-Hartman, Jonathan Corbet, Shuah Khan,
	linux-kernel, linux-api, wine-devel, André Almeida,
	Wolfram Sang, Arkadiusz Hiler, Andy Lutomirski, linux-doc,
	linux-kselftest, Randy Dunlap, Ingo Molnar, Will Deacon,
	Waiman Long, Boqun Feng
In-Reply-To: <20240416155014.GB12673@noisy.programming.kicks-ass.net>

On Tue, Apr 16, 2024 at 05:50:14PM +0200, Peter Zijlstra wrote:
> On Tue, Apr 16, 2024 at 10:14:21AM +0200, Peter Zijlstra wrote:
> 
> > > Some aspects of the implementation may deserve particular comment:
> > > 
> > > * In the interest of performance, each object is governed only by a single
> > >   spinlock. However, NTSYNC_IOC_WAIT_ALL requires that the state of multiple
> > >   objects be changed as a single atomic operation. In order to achieve this, we
> > >   first take a device-wide lock ("wait_all_lock") any time we are going to lock
> > >   more than one object at a time.
> > > 
> > >   The maximum number of objects that can be used in a vectored wait, and
> > >   therefore the maximum that can be locked simultaneously, is 64. This number is
> > >   NT's own limit.
> 
> AFAICT:
> 
> 	spin_lock(&dev->wait_all_lock);
> 	  list_for_each_entry(entry, &obj->all_waiters, node)
> 	    for (i=0; i<count; i++)
> 	      spin_lock_nest_lock(q->entries[i].obj->lock, &dev->wait_all_lock);
> 
> Where @count <= NTSYNC_MAX_WAIT_COUNT.
> 
> So while this nests at most 65 spinlocks, there is no actual bound on
> the amount of nested lock sections in total. That is, all_waiters list
> can be grown without limits.
> 
> Can we pretty please make wait_all_lock a mutex ?

Hurmph, it's worse, you do that list walk while holding some obj->lock
spinlokc too. Still need to figure out how all that works....

^ permalink raw reply

* Re: [PATCH v4 00/30] NT synchronization primitive driver
From: Peter Zijlstra @ 2024-04-16 16:05 UTC (permalink / raw)
  To: Elizabeth Figura
  Cc: Arnd Bergmann, Greg Kroah-Hartman, Jonathan Corbet, Shuah Khan,
	linux-kernel, linux-api, wine-devel, André Almeida,
	Wolfram Sang, Arkadiusz Hiler, Andy Lutomirski, linux-doc,
	linux-kselftest, Randy Dunlap, Ingo Molnar, Will Deacon,
	Waiman Long, Boqun Feng
In-Reply-To: <20240416010837.333694-1-zfigura@codeweavers.com>

On Mon, Apr 15, 2024 at 08:08:10PM -0500, Elizabeth Figura wrote:

> The intended semantics of the patches are broadly intended to match those of the
> corresponding Windows functions. For those not already familiar with the Windows
> functions (or their undocumented behaviour), patch 27/27 provides a detailed
> specification, and individual patches also include a brief description of the
> API they are implementing.

You happen to have a readable copy of patch 27 around? RST is utter
garbage to read :/

^ permalink raw reply

* Re: [PATCH v4 00/30] NT synchronization primitive driver
From: Peter Zijlstra @ 2024-04-16 16:19 UTC (permalink / raw)
  To: Elizabeth Figura
  Cc: Arnd Bergmann, Greg Kroah-Hartman, Jonathan Corbet, Shuah Khan,
	linux-kernel, linux-api, wine-devel, André Almeida,
	Wolfram Sang, Arkadiusz Hiler, Andy Lutomirski, linux-doc,
	linux-kselftest, Randy Dunlap, Ingo Molnar, Will Deacon,
	Waiman Long, Boqun Feng
In-Reply-To: <20240416155345.GC12673@noisy.programming.kicks-ass.net>

On Tue, Apr 16, 2024 at 05:53:45PM +0200, Peter Zijlstra wrote:
> On Tue, Apr 16, 2024 at 05:50:14PM +0200, Peter Zijlstra wrote:
> > On Tue, Apr 16, 2024 at 10:14:21AM +0200, Peter Zijlstra wrote:
> > 
> > > > Some aspects of the implementation may deserve particular comment:
> > > > 
> > > > * In the interest of performance, each object is governed only by a single
> > > >   spinlock. However, NTSYNC_IOC_WAIT_ALL requires that the state of multiple
> > > >   objects be changed as a single atomic operation. In order to achieve this, we
> > > >   first take a device-wide lock ("wait_all_lock") any time we are going to lock
> > > >   more than one object at a time.
> > > > 
> > > >   The maximum number of objects that can be used in a vectored wait, and
> > > >   therefore the maximum that can be locked simultaneously, is 64. This number is
> > > >   NT's own limit.
> > 
> > AFAICT:
> > 
> > 	spin_lock(&dev->wait_all_lock);
> > 	  list_for_each_entry(entry, &obj->all_waiters, node)
> > 	    for (i=0; i<count; i++)
> > 	      spin_lock_nest_lock(q->entries[i].obj->lock, &dev->wait_all_lock);
> > 
> > Where @count <= NTSYNC_MAX_WAIT_COUNT.
> > 
> > So while this nests at most 65 spinlocks, there is no actual bound on
> > the amount of nested lock sections in total. That is, all_waiters list
> > can be grown without limits.
> > 
> > Can we pretty please make wait_all_lock a mutex ?
> 
> Hurmph, it's worse, you do that list walk while holding some obj->lock
> spinlokc too. Still need to figure out how all that works....

So the point of having that other lock around is so that things like:

	try_wake_all_obj(dev, sem)
	try_wake_any_sem(sem)

are done under the same lock?

Where I seem to note that both those functions do that same list
iteration.

Can't you write things like:

static void try_wake_all_obj(struct nysync_device *dev,
			     struct ntsync_obj *obj,
			     void (*wake_obj)(struct ntsync_obj *obj))
{
	list_for_each_entry(entry, &obj->all_waiters, node) {
		spin_lock(&obj->lock);
		try_wake_all(dev, event->q, obj);
		wake_obj(obj);
		spin_unlock(&obj->lock);
	}
}

And then instead of the above, write:

	try_wake_all_obj(dev, sem, wake_sem);

[[ Also, should not something like try_wake_any_sem -- wake_sem in the
   above -- have something like:

	WARN_ON_ONCE(sem->type != NTSYNC_TYPE_SEM);
]]

  

^ permalink raw reply

* Re: [PATCH v4 00/30] NT synchronization primitive driver
From: Elizabeth Figura @ 2024-04-16 21:18 UTC (permalink / raw)
  To: Peter Zijlstra
  Cc: Arnd Bergmann, Greg Kroah-Hartman, Jonathan Corbet, Shuah Khan,
	linux-kernel, linux-api, wine-devel, André Almeida,
	Wolfram Sang, Arkadiusz Hiler, Andy Lutomirski, linux-doc,
	linux-kselftest, Randy Dunlap, Ingo Molnar, Will Deacon,
	Waiman Long, Boqun Feng
In-Reply-To: <20240416161917.GD12673@noisy.programming.kicks-ass.net>

On Tuesday, 16 April 2024 11:19:17 CDT Peter Zijlstra wrote:
> On Tue, Apr 16, 2024 at 05:53:45PM +0200, Peter Zijlstra wrote:
> > On Tue, Apr 16, 2024 at 05:50:14PM +0200, Peter Zijlstra wrote:
> > > On Tue, Apr 16, 2024 at 10:14:21AM +0200, Peter Zijlstra wrote:
> > > > > Some aspects of the implementation may deserve particular comment:
> > > > > 
> > > > > * In the interest of performance, each object is governed only by a
> > > > > single
> > > > > 
> > > > >   spinlock. However, NTSYNC_IOC_WAIT_ALL requires that the state of
> > > > >   multiple
> > > > >   objects be changed as a single atomic operation. In order to
> > > > >   achieve this, we first take a device-wide lock ("wait_all_lock")
> > > > >   any time we are going to lock more than one object at a time.
> > > > >   
> > > > >   The maximum number of objects that can be used in a vectored wait,
> > > > >   and
> > > > >   therefore the maximum that can be locked simultaneously, is 64.
> > > > >   This number is NT's own limit.
> > > 
> > > AFAICT:
> > > 	spin_lock(&dev->wait_all_lock);
> > > 	
> > > 	  list_for_each_entry(entry, &obj->all_waiters, node)
> > > 	  
> > > 	    for (i=0; i<count; i++)
> > > 	    
> > > 	      spin_lock_nest_lock(q->entries[i].obj->lock,
> > > 	      &dev->wait_all_lock);
> > > 
> > > Where @count <= NTSYNC_MAX_WAIT_COUNT.
> > > 
> > > So while this nests at most 65 spinlocks, there is no actual bound on
> > > the amount of nested lock sections in total. That is, all_waiters list
> > > can be grown without limits.
> > > 
> > > Can we pretty please make wait_all_lock a mutex ?

That should be fine, at least.

> > Hurmph, it's worse, you do that list walk while holding some obj->lock
> > spinlokc too. Still need to figure out how all that works....
> 
> So the point of having that other lock around is so that things like:
> 
> 	try_wake_all_obj(dev, sem)
> 	try_wake_any_sem(sem)
> 
> are done under the same lock?

The point of having the other lock around is that try_wake_all() needs to lock 
multiple objects at the same time. It's a way of avoiding lock inversion.

Consider task A does a wait-for-all on objects X, Y, Z. Then task B signals Y, 
so we do try_wake_all_obj() on Y, which does try_wake_all() on A's queue 
entry; that needs to check X and Z and consume the state of all three objects 
atomically. Another task could be trying to signal Z at the same time and 
could hit a task waiting on Z, Y, X, and that causes inversion.

The simple and easy way to implement everything is just to have a global lock 
on the whole device, but this is kind of known to be a performance bottleneck 
(this was NT's BKL, and they ditched it starting with Vista or 7 or 
something).

Instead we use a lock per object, and normally in the wait-for-any case we 
only ever need to grab one lock at a time, but when we need to do a wait-for-
all we need to lock multiple objects at once, and we grab the outer lock to 
avoid potential lock inversion.

> Where I seem to note that both those functions do that same list
> iteration.

Over different lists. I don't know if there's a better way to name things to 
make that clearer.

There's the "any" wait queue, which tasks which do a wait-for-any add 
themselves to, and the "all" wait queue, which tasks that do a wait-for-all 
add themselves to. Signaling an object could potentially wake up either one, 
but checking whether a task is eligible is a different process.



^ permalink raw reply

* Re: [PATCH v4 00/30] NT synchronization primitive driver
From: Elizabeth Figura @ 2024-04-16 21:18 UTC (permalink / raw)
  To: Peter Zijlstra
  Cc: Arnd Bergmann, Greg Kroah-Hartman, Jonathan Corbet, Shuah Khan,
	linux-kernel, linux-api, wine-devel, André Almeida,
	Wolfram Sang, Arkadiusz Hiler, Andy Lutomirski, linux-doc,
	linux-kselftest, Randy Dunlap, Ingo Molnar, Will Deacon,
	Waiman Long, Boqun Feng
In-Reply-To: <20240416160553.GA30707@noisy.programming.kicks-ass.net>

On Tuesday, 16 April 2024 11:05:53 CDT Peter Zijlstra wrote:
> On Mon, Apr 15, 2024 at 08:08:10PM -0500, Elizabeth Figura wrote:
> > The intended semantics of the patches are broadly intended to match those
> > of the corresponding Windows functions. For those not already familiar
> > with the Windows functions (or their undocumented behaviour), patch 27/27
> > provides a detailed specification, and individual patches also include a
> > brief description of the API they are implementing.
> 
> You happen to have a readable copy of patch 27 around? RST is utter
> garbage to read :/

An HTML copy should be available here now (thanks Arek):

    https://ivyl.gg/ntsync/ntsync.html

Let me know if that's good enough or if I should try to render it into plain 
text somehow.



^ permalink raw reply

* Re: [PATCH v4 00/30] NT synchronization primitive driver
From: Elizabeth Figura @ 2024-04-16 21:18 UTC (permalink / raw)
  To: Peter Zijlstra
  Cc: Arnd Bergmann, Greg Kroah-Hartman, Jonathan Corbet, Shuah Khan,
	linux-kernel, linux-api, wine-devel, André Almeida,
	Wolfram Sang, Arkadiusz Hiler, Andy Lutomirski, linux-doc,
	linux-kselftest, Randy Dunlap, Ingo Molnar, Will Deacon,
	Waiman Long, Boqun Feng
In-Reply-To: <20240416081421.GB31647@noisy.programming.kicks-ass.net>

On Tuesday, 16 April 2024 03:14:21 CDT Peter Zijlstra wrote:
> On Mon, Apr 15, 2024 at 08:08:10PM -0500, Elizabeth Figura wrote:
> > This patch series implements a new char misc driver, /dev/ntsync, which is
> > used to implement Windows NT synchronization primitives.
> 
> This patch series does not apply to anything I have at hand. Nor does it
> state anything explicit to put it on top of.

It was written to apply against the 'char-misc-next' branch of gregkh/char-
misc.git. I'll make a note of that next time, sorry for the inconvenience.

> > Hence I would like to request review from someone familiar with locking to
> > make sure that the usage of low-level kernel primitives is correct and
> > that the wait queues work as intended, and to that end I've CC'd the
> > locking maintainers.
> I am sadly very limited atm, but I'll try and read through it. If only I
> could apply...
> 
> > == Patches ==
> > 
> > The intended semantics of the patches are broadly intended to match those
> > of the corresponding Windows functions. For those not already familiar
> > with the Windows functions (or their undocumented behaviour), patch 27/27
> > provides a detailed specification, and individual patches also include a
> > brief description of the API they are implementing.
> > 
> > The patches making use of this driver in Wine can be retrieved or browsed 
here:
> >     https://repo.or.cz/wine/zf.git/shortlog/refs/heads/ntsync5
> 
> I don't support GE has it in his builds? Last time I tried, building
> Wine was a bit of a pain.

It doesn't seem so. I tried to build a GE-compatible ntsync build, uploaded 
here (thanks Arek for hosting):

    https://f002.backblazeb2.com/file/wine-ntsync/ntsync-wine.tar.xz

> > Some aspects of the implementation may deserve particular comment:
> > 
> > * In the interest of performance, each object is governed only by a single
> > 
> >   spinlock. However, NTSYNC_IOC_WAIT_ALL requires that the state of
> >   multiple
> >   objects be changed as a single atomic operation. In order to achieve
> >   this, we first take a device-wide lock ("wait_all_lock") any time we
> >   are going to lock more than one object at a time.
> >   
> >   The maximum number of objects that can be used in a vectored wait, and
> >   therefore the maximum that can be locked simultaneously, is 64. This
> >   number is NT's own limit.
> >   
> >   The acquisition of multiple spinlocks will degrade performance. This is
> >   a
> >   conscious choice, however. Wait-for-all is known to be a very rare
> >   operation in practice, especially with counts that approach the
> >   maximum, and it is the intent of the ntsync driver to optimize
> >   wait-for-any at the expense of wait-for-all as much as possible.
> 
> Per the example of percpu-rwsem, it would be possible to create a
> mutex-spinlock hybrid scheme, where single locks are spinlocks while
> held, but can block when the global thing is pending. And the global
> lock is always mutex like.
> 
> If all that is worth it, I don't know. Nesting 64 spinlocks doesn't give
> me warm and fuzzy feelings though.

Is the concern about poor performance when ntsync is in use, or is nesting a 
lot of spinlocks like that something that could cause problems for unrelated 
tasks? I'm not familiar enough with the scheduler to know if this can be 
abused.

I think we don't care about performance problems within Wine, at least. FWIW, 
the scheme here is actually similar to what Windows does (as described by one 
of their kernel engineers), although slightly different. NT nests spinlocks as 
well, but instead of using the outer lock like our "wait_all_lock" to prevent 
lock inversion, they instead sort the inner locks (by address, I assume).

If there's deeper problems... I can look into (ab)using a rwlock for this 
purpose, at least for now.

In any case making wait_all_lock into a sleeping mutex instead of a spinlock 
should be fine. I'll rerun performance tests but I don't expect it to cause 
any problems.



^ permalink raw reply

* Re: [PATCH v4 00/30] NT synchronization primitive driver
From: Elizabeth Figura @ 2024-04-16 22:18 UTC (permalink / raw)
  To: Peter Zijlstra, wine-devel
  Cc: Arnd Bergmann, Greg Kroah-Hartman, Jonathan Corbet, Shuah Khan,
	linux-kernel, linux-api, wine-devel, André Almeida,
	Wolfram Sang, Andy Lutomirski, linux-doc, linux-kselftest,
	Randy Dunlap, Ingo Molnar, Will Deacon, Waiman Long, Boqun Feng,
	Elizabeth Figura
In-Reply-To: <4340072.ejJDZkT8p0@terabithia>

On Tuesday, 16 April 2024 16:18:24 CDT Elizabeth Figura wrote:
> On Tuesday, 16 April 2024 03:14:21 CDT Peter Zijlstra wrote:
> > I don't support GE has it in his builds? Last time I tried, building
> > Wine was a bit of a pain.
> 
> It doesn't seem so. I tried to build a GE-compatible ntsync build, uploaded
> here (thanks Arek for hosting):
> 
>     https://f002.backblazeb2.com/file/wine-ntsync/ntsync-wine.tar.xz

Oops, the initial version I uploaded had broken paths. Should be fixed now.

(It's also broken on an unpatched kernel unless explicitly disabled with 
WINE_DISABLE_FAST_SYNC=1. Not sure what I messed up there—it should fall back 
cleanly—but hopefully shouldn't be too important for testing.)



^ permalink raw reply

* Re: [PATCH v4 00/30] NT synchronization primitive driver
From: Peter Zijlstra @ 2024-04-17  5:21 UTC (permalink / raw)
  To: Elizabeth Figura
  Cc: Arnd Bergmann, Greg Kroah-Hartman, Jonathan Corbet, Shuah Khan,
	linux-kernel, linux-api, wine-devel, André Almeida,
	Wolfram Sang, Arkadiusz Hiler, Andy Lutomirski, linux-doc,
	linux-kselftest, Randy Dunlap, Ingo Molnar, Will Deacon,
	Waiman Long, Boqun Feng
In-Reply-To: <23472492.6Emhk5qWAg@terabithia>

On Tue, Apr 16, 2024 at 04:18:17PM -0500, Elizabeth Figura wrote:

> Over different lists. I don't know if there's a better way to name things to 
> make that clearer.

D'oh, reading hard. I'll stare more.

^ permalink raw reply

* Re: [PATCH v4 00/30] NT synchronization primitive driver
From: Peter Zijlstra @ 2024-04-17  5:22 UTC (permalink / raw)
  To: Elizabeth Figura
  Cc: Arnd Bergmann, Greg Kroah-Hartman, Jonathan Corbet, Shuah Khan,
	linux-kernel, linux-api, wine-devel, André Almeida,
	Wolfram Sang, Arkadiusz Hiler, Andy Lutomirski, linux-doc,
	linux-kselftest, Randy Dunlap, Ingo Molnar, Will Deacon,
	Waiman Long, Boqun Feng
In-Reply-To: <2602449.Lt9SDvczpP@terabithia>

On Tue, Apr 16, 2024 at 04:18:19PM -0500, Elizabeth Figura wrote:

> Let me know if that's good enough or if I should try to render it into plain 
> text somehow.

Plain text is much preferred. I'm more of a text editor kinda guy --
being a programmer and all that.
> 
> 

^ permalink raw reply

* Re: [PATCH v4 00/30] NT synchronization primitive driver
From: Peter Zijlstra @ 2024-04-17  5:24 UTC (permalink / raw)
  To: Elizabeth Figura
  Cc: Arnd Bergmann, Greg Kroah-Hartman, Jonathan Corbet, Shuah Khan,
	linux-kernel, linux-api, wine-devel, André Almeida,
	Wolfram Sang, Arkadiusz Hiler, Andy Lutomirski, linux-doc,
	linux-kselftest, Randy Dunlap, Ingo Molnar, Will Deacon,
	Waiman Long, Boqun Feng
In-Reply-To: <4340072.ejJDZkT8p0@terabithia>

On Tue, Apr 16, 2024 at 04:18:24PM -0500, Elizabeth Figura wrote:

> Is the concern about poor performance when ntsync is in use, or is nesting a 
> lot of spinlocks like that something that could cause problems for unrelated 
> tasks? I'm not familiar enough with the scheduler to know if this can be 
> abused.

The problem is keeping preemption disabled for potentially a fairly long
time. By doing that you potentially affect the performance of other tasks.

^ permalink raw reply

* Re: [PATCH v4 00/30] NT synchronization primitive driver
From: Elizabeth Figura @ 2024-04-17  6:05 UTC (permalink / raw)
  To: Peter Zijlstra
  Cc: Arnd Bergmann, Greg Kroah-Hartman, Jonathan Corbet, Shuah Khan,
	linux-kernel, linux-api, wine-devel, André Almeida,
	Wolfram Sang, Arkadiusz Hiler, Andy Lutomirski, linux-doc,
	linux-kselftest, Randy Dunlap, Ingo Molnar, Will Deacon,
	Waiman Long, Boqun Feng
In-Reply-To: <20240417052218.GI30852@noisy.programming.kicks-ass.net>

On Wednesday, 17 April 2024 00:22:18 CDT Peter Zijlstra wrote:
> On Tue, Apr 16, 2024 at 04:18:19PM -0500, Elizabeth Figura wrote:
> > Let me know if that's good enough or if I should try to render it into
> > plain text somehow.
> 
> Plain text is much preferred. I'm more of a text editor kinda guy --
> being a programmer and all that.

I can certainly sympathize with that ;-)

Here's a (slightly ad-hoc) simplification of the patch into text form inlined 
into this message; hopefully it's readable enough.


===================================
NT synchronization primitive driver
===================================

This page documents the user-space API for the ntsync driver.

ntsync is a support driver for emulation of NT synchronization
primitives by user-space NT emulators. It exists because implementation
in user-space, using existing tools, cannot match Windows performance
while offering accurate semantics. It is implemented entirely in
software, and does not drive any hardware device.

This interface is meant as a compatibility tool only, and should not
be used for general synchronization. Instead use generic, versatile
interfaces such as futex(2) and poll(2).

Synchronization primitives
==========================

The ntsync driver exposes three types of synchronization primitives:
semaphores, mutexes, and events.

A semaphore holds a single volatile 32-bit counter, and a static 32-bit
integer denoting the maximum value. It is considered signaled when the
counter is nonzero. The counter is decremented by one when a wait is
satisfied. Both the initial and maximum count are established when the
semaphore is created.

A mutex holds a volatile 32-bit recursion count, and a volatile 32-bit
identifier denoting its owner. A mutex is considered signaled when its
owner is zero (indicating that it is not owned). The recursion count is
incremented when a wait is satisfied, and ownership is set to the given
identifier.

A mutex also holds an internal flag denoting whether its previous owner
has died; such a mutex is said to be abandoned. Owner death is not
tracked automatically based on thread death, but rather must be
communicated using NTSYNC_IOC_MUTEX_KILL. An abandoned mutex is
inherently considered unowned.

Except for the "unowned" semantics of zero, the actual value of the
owner identifier is not interpreted by the ntsync driver at all. The
intended use is to store a thread identifier; however, the ntsync
driver does not actually validate that a calling thread provides
consistent or unique identifiers.

An event holds a volatile boolean state denoting whether it is signaled
or not. There are two types of events, auto-reset and manual-reset. An
auto-reset event is designaled when a wait is satisfied; a manual-reset
event is not. The event type is specified when the event is created.

Unless specified otherwise, all operations on an object are atomic and
totally ordered with respect to other operations on the same object.

Objects are represented by files. When all file descriptors to an
object are closed, that object is deleted.

Char device
===========

The ntsync driver creates a single char device /dev/ntsync. Each file
description opened on the device represents a unique instance intended
to back an individual NT virtual machine. Objects created by one ntsync
instance may only be used with other objects created by the same
instance.

ioctl reference
===============

All operations on the device are done through ioctls. There are four
structures used in ioctl calls::

   struct ntsync_sem_args {
       __u32 sem;
       __u32 count;
       __u32 max;
   };

   struct ntsync_mutex_args {
       __u32 mutex;
       __u32 owner;
       __u32 count;
   };

   struct ntsync_event_args {
       __u32 event;
       __u32 signaled;
       __u32 manual;
   };

   struct ntsync_wait_args {
       __u64 timeout;
       __u64 objs;
       __u32 count;
       __u32 owner;
       __u32 index;
       __u32 alert;
       __u32 flags;
       __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.

The ioctls on the device file are as follows:

.. NTSYNC_IOC_CREATE_SEM

  Create a semaphore object. Takes a pointer to struct ntsync_sem_args,
  which is used as follows:

     * sem:   On output, contains a file descriptor to the created semaphore.
     * count: Initial count of the semaphore.
     * max:   Maximum count of the semaphore.

  Fails with EINVAL if `count` is greater than `max`.

.. NTSYNC_IOC_CREATE_MUTEX

  Create a mutex object. Takes a pointer to struct ntsync_mutex_args,
  which is used as follows:

     * mutex: On output, contains a file descriptor to the created mutex.
     * count: Initial recursion count of the mutex.
     * owner: Initial owner of the mutex.

  If ``owner`` is nonzero and ``count`` is zero, or if ``owner`` is zero
  and ``count`` is nonzero, the function fails with EINVAL.

.. NTSYNC_IOC_CREATE_EVENT

  Create an event object. Takes a pointer to struct ntsync_event_args,
  which is used as follows:

     * event:    On output, contains a file descriptor to the created event.
     * signaled: If nonzero, the event is initially signaled, otherwise
                 nonsignaled.
     * manual:   If nonzero, the event is a manual-reset event, otherwise
                 auto-reset.

The ioctls on the individual objects are as follows:

.. NTSYNC_IOC_SEM_POST

  Post to a semaphore object. Takes a pointer to a 32-bit integer,
  which on input holds the count to be added to the semaphore, and on
  output contains its previous count.

  If adding to the semaphore's current count would raise the latter
  past the semaphore's maximum count, the ioctl fails with
  EOVERFLOW and the semaphore is not affected. If raising the
  semaphore's count causes it to become signaled, eligible threads
  waiting on this semaphore will be woken and the semaphore's count
  decremented appropriately.

.. NTSYNC_IOC_MUTEX_UNLOCK

  Release a mutex object. Takes a pointer to struct ntsync_mutex_args,
  which is used as follows:

     * mutex: Ignored.
     * owner: Specifies the owner trying to release this mutex.
     * count: On output, contains the previous recursion count.

  If ``owner`` is zero, the ioctl fails with EINVAL. If ``owner``
  is not the current owner of the mutex, the ioctl fails with
  EPERM.

  The mutex's count will be decremented by one. If decrementing the
  mutex's count causes it to become zero, the mutex is marked as
  unowned and signaled, and eligible threads waiting on it will be
  woken as appropriate.

.. NTSYNC_IOC_SET_EVENT

  Signal an event object. Takes a pointer to a 32-bit integer, which on
  output contains the previous state of the event.

  Eligible threads will be woken, and auto-reset events will be
  designaled appropriately.

.. NTSYNC_IOC_RESET_EVENT

  Designal an event object. Takes a pointer to a 32-bit integer, which
  on output contains the previous state of the event.

.. NTSYNC_IOC_PULSE_EVENT

  Wake threads waiting on an event object while leaving it in an
  unsignaled state. Takes a pointer to a 32-bit integer, which on
  output contains the previous state of the event.

  A pulse operation can be thought of as a set followed by a reset,
  performed as a single atomic operation. If two threads are waiting on
  an auto-reset event which is pulsed, only one will be woken. If two
  threads are waiting a manual-reset event which is pulsed, both will
  be woken. However, in both cases, the event will be unsignaled
  afterwards, and a simultaneous read operation will always report the
  event as unsignaled.

.. NTSYNC_IOC_READ_SEM

  Read the current state of a semaphore object. Takes a pointer to
  struct ntsync_sem_args, which is used as follows:

     * sem:   Ignored.
     * count: On output, contains the current count of the semaphore.
     * max:   On output, contains the maximum count of the semaphore.

.. NTSYNC_IOC_READ_MUTEX

  Read the current state of a mutex object. Takes a pointer to struct
  ntsync_mutex_args, which is used as follows:

     * mutex: Ignored.
     * owner: On output, contains the current owner of the mutex, or zero
              if the mutex is not currently owned.
     * count: On output, contains the current recursion count of the mutex.

  If the mutex is marked as abandoned, the function fails with
  EOWNERDEAD. In this case, ``count`` and ``owner`` are set to zero.

.. NTSYNC_IOC_READ_EVENT

  Read the current state of an event object. Takes a pointer to struct
  ntsync_event_args, which is used as follows:

     * event:    Ignored.
     * signaled: On output, contains the current state of the event.
     * manual:   On output, contains 1 if the event is a manual-reset event,
                 and 0 otherwise.

.. NTSYNC_IOC_KILL_OWNER

  Mark a mutex as unowned and abandoned if it is owned by the given
  owner. Takes an input-only pointer to a 32-bit integer denoting the
  owner. If the owner is zero, the ioctl fails with EINVAL. If the
  owner does not own the mutex, the function fails with EPERM.

  Eligible threads waiting on the mutex will be woken as appropriate
  (and such waits will fail with EOWNERDEAD, as described below).

.. NTSYNC_IOC_WAIT_ANY

  Poll on any of a list of objects, atomically acquiring at most one.
  Takes a pointer to struct ntsync_wait_args, which is used as follows:

     * timeout: Absolute timeout in nanoseconds. If NTSYNC_WAIT_REALTIME
                is set, the timeout is measured against the REALTIME
                clock; otherwise it is measured against the MONOTONIC
                clock. If the timeout is equal to or earlier than the
                current time, the function returns immediately without
                sleeping. If ``timeout`` is U64_MAX, the function will
                sleep until an object is signaled, and will not fail
                with ETIMEDOUT.

     * objs:    Pointer to an array of ``count`` file descriptors
                (specified as an integer so that the structure has the
                same size regardless of architecture). If any object is
                invalid, the function fails with EINVAL.

     * count:   Number of objects specified in the ``objs`` array. If
                greater than NTSYNC_MAX_WAIT_COUNT, the function fails
                with EINVAL.

     * owner:   Mutex owner identifier. If any object in ``objs`` is a
                mutex, the ioctl will attempt to acquire that mutex on
                behalf of ``owner``. If ``owner`` is zero, the ioctl
                fails with EINVAL.

     * index:   On success, contains the index (into ``objs``) of the
                object which was signaled. If ``alert`` was signaled
                instead, this contains ``count``.

     * alert:   Optional event object file descriptor. If nonzero, this
                specifies an "alert" event object which, if signaled,
                will terminate the wait. If nonzero, the identifier must
                point to a valid event.

     * flags:   Zero or more flags. Currently the only flag is
                NTSYNC_WAIT_REALTIME, which causes the timeout to be
                measured against the REALTIME clock instead of
                MONOTONIC.

     * pad:     Unused, must be set to zero.

  This function attempts to acquire one of the given objects. If unable
  to do so, it sleeps until an object becomes signaled, subsequently
  acquiring it, or the timeout expires. In the latter case the ioctl
  fails with ETIMEDOUT. The function only acquires one object, even if
  multiple objects are signaled.

  A semaphore is considered to be signaled if its count is nonzero, and
  is acquired by decrementing its count by one. A mutex is considered
  to be signaled if it is unowned or if its owner matches the ``owner``
  argument, and is acquired by incrementing its recursion count by one
  and setting its owner to the ``owner`` argument. An auto-reset event
  is acquired by designaling it; a manual-reset event is not affected
  by acquisition.

  Acquisition is atomic and totally ordered with respect to other
  operations on the same object. If two wait operations (with different
  ``owner`` identifiers) are queued on the same mutex, only one is
  signaled. If two wait operations are queued on the same semaphore,
  and a value of one is posted to it, only one is signaled. The order
  in which threads are signaled is not specified.

  If an abandoned mutex is acquired, the ioctl fails with
  EOWNERDEAD. Although this is a failure return, the function may
  otherwise be considered successful. The mutex is marked as owned by
  the given owner (with a recursion count of 1) and as no longer
  abandoned, and ``index`` is still set to the index of the mutex.

  The ``alert`` argument is an "extra" event which can terminate the
  wait, independently of all other objects. If members of ``objs`` and
  ``alert`` are both simultaneously signaled, a member of ``objs`` will
  always be given priority and acquired first.

  It is valid to pass the same object more than once, including by
  passing the same event in the ``objs`` array and in ``alert``. If a
  wakeup occurs due to that object being signaled, ``index`` is set to
  the lowest index corresponding to that object.

  The function may fail with EINTR if a signal is received.

.. NTSYNC_IOC_WAIT_ALL

  Poll on a list of objects, atomically acquiring all of them. Takes a
  pointer to struct ntsync_wait_args, which is used identically to
  NTSYNC_IOC_WAIT_ANY, except that ``index`` is always filled with zero
  on success if not woken via alert.

  This function attempts to simultaneously acquire all of the given
  objects. If unable to do so, it sleeps until all objects become
  simultaneously signaled, subsequently acquiring them, or the timeout
  expires. In the latter case the ioctl fails with ETIMEDOUT and no
  objects are modified.

  Objects may become signaled and subsequently designaled (through
  acquisition by other threads) while this thread is sleeping. Only
  once all objects are simultaneously signaled does the ioctl acquire
  them and return. The entire acquisition is atomic and totally ordered
  with respect to other operations on any of the given objects.

  If an abandoned mutex is acquired, the ioctl fails with
  EOWNERDEAD. Similarly to NTSYNC_IOC_WAIT_ANY, all objects are
  nevertheless marked as acquired. Note that if multiple mutex objects
  are specified, there is no way to know which were marked as
  abandoned.

  As with "any" waits, the ``alert`` argument is an "extra" event which
  can terminate the wait. Critically, however, an "all" wait will
  succeed if all members in ``objs`` are signaled, *or* if ``alert`` is
  signaled. In the latter case ``index`` will be set to ``count``. As
  with "any" waits, if both conditions are filled, the former takes
  priority, and objects in ``objs`` will be acquired.

  Unlike NTSYNC_IOC_WAIT_ANY, it is not valid to pass the same
  object more than once, nor is it valid to pass the same object in
  ``objs`` and in ``alert``. If this is attempted, the function fails
  with EINVAL.



^ permalink raw reply

* Re: [PATCH v4 00/30] NT synchronization primitive driver
From: Peter Zijlstra @ 2024-04-17 10:01 UTC (permalink / raw)
  To: Elizabeth Figura
  Cc: Arnd Bergmann, Greg Kroah-Hartman, Jonathan Corbet, Shuah Khan,
	linux-kernel, linux-api, wine-devel, André Almeida,
	Wolfram Sang, Arkadiusz Hiler, Andy Lutomirski, linux-doc,
	linux-kselftest, Randy Dunlap, Ingo Molnar, Will Deacon,
	Waiman Long, Boqun Feng
In-Reply-To: <1856855.atdPhlSkOF@terabithia>

On Wed, Apr 17, 2024 at 01:05:47AM -0500, Elizabeth Figura wrote:

> Here's a (slightly ad-hoc) simplification of the patch into text form inlined 
> into this message; hopefully it's readable enough.

Thanks!

Still needed:

 s/\`\`/"/g
 s/\.\.\ //g

But then it's readable

> 
> ===================================
> NT synchronization primitive driver
> ===================================
> 
> This page documents the user-space API for the ntsync driver.
> 
> ntsync is a support driver for emulation of NT synchronization
> primitives by user-space NT emulators. It exists because implementation
> in user-space, using existing tools, cannot match Windows performance
> while offering accurate semantics. It is implemented entirely in
> software, and does not drive any hardware device.
> 
> This interface is meant as a compatibility tool only, and should not
> be used for general synchronization. Instead use generic, versatile
> interfaces such as futex(2) and poll(2).
> 
> Synchronization primitives
> ==========================
> 
> The ntsync driver exposes three types of synchronization primitives:
> semaphores, mutexes, and events.
> 
> A semaphore holds a single volatile 32-bit counter, and a static 32-bit
> integer denoting the maximum value. It is considered signaled when the
> counter is nonzero. The counter is decremented by one when a wait is
> satisfied. Both the initial and maximum count are established when the
> semaphore is created.
> 
> A mutex holds a volatile 32-bit recursion count, and a volatile 32-bit
> identifier denoting its owner. A mutex is considered signaled when its
> owner is zero (indicating that it is not owned). The recursion count is
> incremented when a wait is satisfied, and ownership is set to the given
> identifier.

'signaled' is used twice now but not defined. For both Semaphore and
Mutex this seems to indicate uncontended? Edit: seems to be needs-wakeup
more than uncontended.

> A mutex also holds an internal flag denoting whether its previous owner
> has died; such a mutex is said to be abandoned. Owner death is not
> tracked automatically based on thread death, but rather must be
> communicated using NTSYNC_IOC_MUTEX_KILL. An abandoned mutex is
> inherently considered unowned.
> 
> Except for the "unowned" semantics of zero, the actual value of the
> owner identifier is not interpreted by the ntsync driver at all. The
> intended use is to store a thread identifier; however, the ntsync
> driver does not actually validate that a calling thread provides
> consistent or unique identifiers.

Why not verify it? Seems simple enough to put in a TID check, esp. if NT
mandates the same.

> An event holds a volatile boolean state denoting whether it is signaled
> or not. There are two types of events, auto-reset and manual-reset. An
> auto-reset event is designaled when a wait is satisfied; a manual-reset
> event is not. The event type is specified when the event is created.

But what is an event? I'm familiar with semaphores and mutexes, but less
so with events.

> Unless specified otherwise, all operations on an object are atomic and
> totally ordered with respect to other operations on the same object.
> 
> Objects are represented by files. When all file descriptors to an
> object are closed, that object is deleted.
> 
> Char device
> ===========
> 
> The ntsync driver creates a single char device /dev/ntsync. Each file
> description opened on the device represents a unique instance intended
> to back an individual NT virtual machine. Objects created by one ntsync
> instance may only be used with other objects created by the same
> instance.
> 
> ioctl reference
> ===============
> 
> All operations on the device are done through ioctls. There are four
> structures used in ioctl calls::
> 
>    struct ntsync_sem_args {
>        __u32 sem;
>        __u32 count;
>        __u32 max;
>    };
> 
>    struct ntsync_mutex_args {
>        __u32 mutex;
>        __u32 owner;
>        __u32 count;
>    };
> 
>    struct ntsync_event_args {
>        __u32 event;
>        __u32 signaled;
>        __u32 manual;
>    };
> 
>    struct ntsync_wait_args {
>        __u64 timeout;
>        __u64 objs;
>        __u32 count;
>        __u32 owner;
>        __u32 index;
>        __u32 alert;
>        __u32 flags;
>        __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.
> 
> The ioctls on the device file are as follows:
> 
> NTSYNC_IOC_CREATE_SEM
> 
>   Create a semaphore object. Takes a pointer to struct ntsync_sem_args,
>   which is used as follows:
> 
>      * sem:   On output, contains a file descriptor to the created semaphore.
>      * count: Initial count of the semaphore.
>      * max:   Maximum count of the semaphore.
> 
>   Fails with EINVAL if `count` is greater than `max`.

So the implication is that @count and @max are input argument and as
such should be set before calling the ioctl()?

It would not have been weird to have the ioctl() return the fd on
success I suppose, instead of mixing input and output arguments like
this, but whatever, this works.

> NTSYNC_IOC_CREATE_MUTEX
> 
>   Create a mutex object. Takes a pointer to struct ntsync_mutex_args,
>   which is used as follows:
> 
>      * mutex: On output, contains a file descriptor to the created mutex.
>      * count: Initial recursion count of the mutex.
>      * owner: Initial owner of the mutex.
> 
>   If "owner" is nonzero and "count" is zero, or if "owner" is zero
>   and "count" is nonzero, the function fails with EINVAL.
> 
> NTSYNC_IOC_CREATE_EVENT
> 
>   Create an event object. Takes a pointer to struct ntsync_event_args,
>   which is used as follows:
> 
>      * event:    On output, contains a file descriptor to the created event.
>      * signaled: If nonzero, the event is initially signaled, otherwise
>                  nonsignaled.
>      * manual:   If nonzero, the event is a manual-reset event, otherwise
>                  auto-reset.
> 

Still mystified as to what event actually is, perhaps more clues
below...

> The ioctls on the individual objects are as follows:
> 
> NTSYNC_IOC_SEM_POST
> 
>   Post to a semaphore object. Takes a pointer to a 32-bit integer,
>   which on input holds the count to be added to the semaphore, and on
>   output contains its previous count.
> 
>   If adding to the semaphore's current count would raise the latter
>   past the semaphore's maximum count, the ioctl fails with
>   EOVERFLOW and the semaphore is not affected. If raising the
>   semaphore's count causes it to become signaled, eligible threads
>   waiting on this semaphore will be woken and the semaphore's count
>   decremented appropriately.

Urg, so this is the traditional V (vrijgeven per Dijkstra, release in
English), but now 'conveniently' called POST, such that it can be
readily confused with the P operation (passering, or passing) which it
is not.

Glorious :-/

You're of course going to tell me NT did this and you can't help this
naming foible.

> NTSYNC_IOC_MUTEX_UNLOCK
> 
>   Release a mutex object. Takes a pointer to struct ntsync_mutex_args,
>   which is used as follows:
> 
>      * mutex: Ignored.
>      * owner: Specifies the owner trying to release this mutex.
>      * count: On output, contains the previous recursion count.
> 
>   If "owner" is zero, the ioctl fails with EINVAL. If "owner"
>   is not the current owner of the mutex, the ioctl fails with
>   EPERM.

ISTR you having written elsewhere that NT actually demands mutexes to be
strictly per thread, which for the above would mandate @owner to be
current, no?

>   The mutex's count will be decremented by one. If decrementing the
>   mutex's count causes it to become zero, the mutex is marked as
>   unowned and signaled, and eligible threads waiting on it will be
>   woken as appropriate.
> 
> NTSYNC_IOC_SET_EVENT
> 
>   Signal an event object. Takes a pointer to a 32-bit integer, which on
>   output contains the previous state of the event.
> 
>   Eligible threads will be woken, and auto-reset events will be
>   designaled appropriately.

Hmm, so the event thing is like a simple wait-wake scheme? Where the
'signaled' bit is used as the wakeup state?

> NTSYNC_IOC_RESET_EVENT
> 
>   Designal an event object. Takes a pointer to a 32-bit integer, which
>   on output contains the previous state of the event.
> 
> NTSYNC_IOC_PULSE_EVENT
> 
>   Wake threads waiting on an event object while leaving it in an
>   unsignaled state. Takes a pointer to a 32-bit integer, which on
>   output contains the previous state of the event.
> 
>   A pulse operation can be thought of as a set followed by a reset,
>   performed as a single atomic operation. If two threads are waiting on
>   an auto-reset event which is pulsed, only one will be woken. If two
>   threads are waiting a manual-reset event which is pulsed, both will
>   be woken. However, in both cases, the event will be unsignaled
>   afterwards, and a simultaneous read operation will always report the
>   event as unsignaled.

*groan*

> NTSYNC_IOC_READ_SEM
> 
>   Read the current state of a semaphore object. Takes a pointer to
>   struct ntsync_sem_args, which is used as follows:
> 
>      * sem:   Ignored.
>      * count: On output, contains the current count of the semaphore.
>      * max:   On output, contains the maximum count of the semaphore.

This seems inherently racy -- what is the intended purpose of this
interface?

Specifically the moment a value is returned, either P or V operations
can change it, rendering the (as yet unused) return value incorrect.

> NTSYNC_IOC_READ_MUTEX
> 
>   Read the current state of a mutex object. Takes a pointer to struct
>   ntsync_mutex_args, which is used as follows:
> 
>      * mutex: Ignored.
>      * owner: On output, contains the current owner of the mutex, or zero
>               if the mutex is not currently owned.
>      * count: On output, contains the current recursion count of the mutex.
> 
>   If the mutex is marked as abandoned, the function fails with
>   EOWNERDEAD. In this case, "count" and "owner" are set to zero.

Another questionable interface. I suspect you're going to be telling me
NT has them so you have to have them, but urgh.

> NTSYNC_IOC_READ_EVENT
> 
>   Read the current state of an event object. Takes a pointer to struct
>   ntsync_event_args, which is used as follows:
> 
>      * event:    Ignored.
>      * signaled: On output, contains the current state of the event.
>      * manual:   On output, contains 1 if the event is a manual-reset event,
>                  and 0 otherwise.

I can't help but notice all those @sem, @mutex, @event 'output' members
being unused except for create. Seems like a waste to have them.

> NTSYNC_IOC_KILL_OWNER
> 
>   Mark a mutex as unowned and abandoned if it is owned by the given
>   owner. Takes an input-only pointer to a 32-bit integer denoting the
>   owner. If the owner is zero, the ioctl fails with EINVAL. If the
>   owner does not own the mutex, the function fails with EPERM.
> 
>   Eligible threads waiting on the mutex will be woken as appropriate
>   (and such waits will fail with EOWNERDEAD, as described below).

Wine will use this when it detects a thread exit I suppose.

> NTSYNC_IOC_WAIT_ANY
> 
>   Poll on any of a list of objects, atomically acquiring at most one.
>   Takes a pointer to struct ntsync_wait_args, which is used as follows:
> 
>      * timeout: Absolute timeout in nanoseconds. If NTSYNC_WAIT_REALTIME
>                 is set, the timeout is measured against the REALTIME
>                 clock; otherwise it is measured against the MONOTONIC
>                 clock. If the timeout is equal to or earlier than the
>                 current time, the function returns immediately without
>                 sleeping. If "timeout" is U64_MAX, the function will
>                 sleep until an object is signaled, and will not fail
>                 with ETIMEDOUT.
> 
>      * objs:    Pointer to an array of "count" file descriptors
>                 (specified as an integer so that the structure has the
>                 same size regardless of architecture). If any object is
>                 invalid, the function fails with EINVAL.
> 
>      * count:   Number of objects specified in the "objs" array. If
>                 greater than NTSYNC_MAX_WAIT_COUNT, the function fails
>                 with EINVAL.
> 
>      * owner:   Mutex owner identifier. If any object in "objs" is a
>                 mutex, the ioctl will attempt to acquire that mutex on
>                 behalf of "owner". If "owner" is zero, the ioctl
>                 fails with EINVAL.

Again, should that not be current? That is, why not maintain the NT
invariant and mandates TIDs and avoid the arguments in both cases?

>      * index:   On success, contains the index (into "objs") of the
>                 object which was signaled. If "alert" was signaled
>                 instead, this contains "count".

Could be the actual return value, no? Edit: no it cannot be because
-EOWNERDEAD case below.

> 
>      * alert:   Optional event object file descriptor. If nonzero, this
>                 specifies an "alert" event object which, if signaled,
>                 will terminate the wait. If nonzero, the identifier must
>                 point to a valid event.
> 
>      * flags:   Zero or more flags. Currently the only flag is
>                 NTSYNC_WAIT_REALTIME, which causes the timeout to be
>                 measured against the REALTIME clock instead of
>                 MONOTONIC.
> 
>      * pad:     Unused, must be set to zero.
> 
>   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.

Any guarantee as to which will be acquired in case multiple are
available? [A]

>   A semaphore is considered to be signaled if its count is nonzero, and
>   is acquired by decrementing its count by one. A mutex is considered
>   to be signaled if it is unowned or if its owner matches the "owner"
>   argument, and is acquired by incrementing its recursion count by one
>   and setting its owner to the "owner" argument. An auto-reset event
>   is acquired by designaling it; a manual-reset event is not affected
>   by acquisition.
> 
>   Acquisition is atomic and totally ordered with respect to other
>   operations on the same object. If two wait operations (with different
>   "owner" identifiers) are queued on the same mutex, only one is
>   signaled. If two wait operations are queued on the same semaphore,
>   and a value of one is posted to it, only one is signaled. The order
>   in which threads are signaled is not specified.

Note that you do list the lack of guarantee here, but not above. I
suspect both cases are similar and guarantee nothing.

>   If an abandoned mutex is acquired, the ioctl fails with
>   EOWNERDEAD. Although this is a failure return, the function may
>   otherwise be considered successful. The mutex is marked as owned by
>   the given owner (with a recursion count of 1) and as no longer
>   abandoned, and "index" is still set to the index of the mutex.

Aaah, I see, this does indeed preclude @index from being the return
value.

>   The "alert" argument is an "extra" event which can terminate the
>   wait, independently of all other objects. If members of "objs" and
>   "alert" are both simultaneously signaled, a member of "objs" will
>   always be given priority and acquired first.
> 
>   It is valid to pass the same object more than once, including by
>   passing the same event in the "objs" array and in "alert". If a
>   wakeup occurs due to that object being signaled, "index" is set to
>   the lowest index corresponding to that object.

Urgh, is this an actual guarantee? This almost seems to imply that at
[A] above we can indeed guarantee the lowest indexed object is acquired
first.

>   The function may fail with EINTR if a signal is received.

In which case @index must be disregarded since nothing will be acquired,
right?

So far nothing really weird, and I'm thinking futexes should be able to
do all this, no?

> NTSYNC_IOC_WAIT_ALL
> 
>   Poll on a list of objects, atomically acquiring all of them. Takes a
>   pointer to struct ntsync_wait_args, which is used identically to
>   NTSYNC_IOC_WAIT_ANY, except that "index" is always filled with zero
>   on success if not woken via alert.

Whee, and this is the one weird operation that you're all struggling to
emulate, right? The atomic multi-acquire is 'hard' to do with futexes.

>   This function attempts to simultaneously acquire all of the given
>   objects. If unable to do so, it sleeps until all objects become
>   simultaneously signaled, subsequently acquiring them, or the timeout
>   expires. In the latter case the ioctl fails with ETIMEDOUT and no
>   objects are modified.
> 
>   Objects may become signaled and subsequently designaled (through
>   acquisition by other threads) while this thread is sleeping. Only
>   once all objects are simultaneously signaled does the ioctl acquire
>   them and return. The entire acquisition is atomic and totally ordered
>   with respect to other operations on any of the given objects.
> 
>   If an abandoned mutex is acquired, the ioctl fails with
>   EOWNERDEAD. Similarly to NTSYNC_IOC_WAIT_ANY, all objects are
>   nevertheless marked as acquired. Note that if multiple mutex objects
>   are specified, there is no way to know which were marked as
>   abandoned.
> 
>   As with "any" waits, the "alert" argument is an "extra" event which
>   can terminate the wait. Critically, however, an "all" wait will
>   succeed if all members in "objs" are signaled, *or* if "alert" is
>   signaled. In the latter case "index" will be set to "count". As
>   with "any" waits, if both conditions are filled, the former takes
>   priority, and objects in "objs" will be acquired.
> 
>   Unlike NTSYNC_IOC_WAIT_ANY, it is not valid to pass the same
>   object more than once, nor is it valid to pass the same object in
>   "objs" and in "alert". If this is attempted, the function fails
>   with EINVAL.

OK, this all was helpful, I'll go stare at the code again.

Thanks!

^ permalink raw reply

* Re: [PATCH v4 02/27] ntsync: Introduce NTSYNC_IOC_WAIT_ALL.
From: Peter Zijlstra @ 2024-04-17 11:37 UTC (permalink / raw)
  To: Elizabeth Figura
  Cc: Arnd Bergmann, Greg Kroah-Hartman, Jonathan Corbet, Shuah Khan,
	linux-kernel, linux-api, wine-devel, André Almeida,
	Wolfram Sang, Arkadiusz Hiler, Andy Lutomirski, linux-doc,
	linux-kselftest, Randy Dunlap, Ingo Molnar, Will Deacon,
	Waiman Long, Boqun Feng
In-Reply-To: <20240416010837.333694-3-zfigura@codeweavers.com>

On Mon, Apr 15, 2024 at 08:08:12PM -0500, Elizabeth Figura wrote:
> +	if (atomic_read(&sem->all_hint) > 0) {
> +		spin_lock(&dev->wait_all_lock);
> +		spin_lock_nest_lock(&sem->lock, &dev->wait_all_lock);
>  
> +		prev_count = sem->u.sem.count;
> +		ret = post_sem_state(sem, args);
> +		if (!ret) {
> +			try_wake_all_obj(dev, sem);
> +			try_wake_any_sem(sem);
> +		}
>  
> +		spin_unlock(&sem->lock);
> +		spin_unlock(&dev->wait_all_lock);
> +	} else {
> +		spin_lock(&sem->lock);
> +
> +		prev_count = sem->u.sem.count;
> +		ret = post_sem_state(sem, args);
> +		if (!ret)
> +			try_wake_any_sem(sem);
> +
> +		spin_unlock(&sem->lock);
> +	}
>  
>  	if (!ret && put_user(prev_count, user_args))
>  		ret = -EFAULT;

vs.

> +	/* queue ourselves */
> +
> +	spin_lock(&dev->wait_all_lock);
> +
> +	for (i = 0; i < args.count; i++) {
> +		struct ntsync_q_entry *entry = &q->entries[i];
> +		struct ntsync_obj *obj = entry->obj;
> +
> +		atomic_inc(&obj->all_hint);
> +
> +		/*
> +		 * obj->all_waiters is protected by dev->wait_all_lock rather
> +		 * than obj->lock, so there is no need to acquire obj->lock
> +		 * here.
> +		 */
> +		list_add_tail(&entry->node, &obj->all_waiters);
> +	}

This looks racy, consider:

	atomic_read(all_hints) /* 0 */

				spin_lock(wait_all_lock)
				atomic_inc(all_hint)	/* 1 */
				list_add_tail()

	spin_lock(sem->lock)
	/* try_wake_all_obj() missing */




I've not yet thought about if this is harmful or not, but if not, it
definitely needs a comment.

Anyway, I need a break, maybe more this evening.



^ permalink raw reply

* [PATCH v5 0/3] VT: Add ability to get font requirements
From: Alexey Gladkov @ 2024-04-17 17:37 UTC (permalink / raw)
  To: Greg Kroah-Hartman, Jiri Slaby
  Cc: LKML, kbd, linux-api, linux-fbdev, linux-serial
In-Reply-To: <cover.1712080158.git.legion@kernel.org>

We now have KD_FONT_OP_SET_TALL, but in fact such large fonts cannot be
loaded. No console driver supports tall fonts. Unfortunately, userspace
cannot distinguish the lack of support in the driver from errors in the
font itself. In all cases, EINVAL will be returned.

This patchset adds a separate ioctl to obtain the font parameters
supported by the console driver.

v5:
* Use data types that are compatible with the uapi structure.
* Use _IOR to define a new ioctl as required for new ioctls.

v4:
* Rebased on v6.9-rc1 and conflicts have been fixed.
* Do not copy KDFONTINFO data from the userspace.
* Header include/uapi/linux/kd.h uses _IOC macros to define ioctls.

v3:
* Added the use of the in_range macro.
* Squashed the commits that add ioctl to console divers.

v2:
* Instead of the KDFONTOP extension, a new ioctl has been added to
  obtain font information.

Alexey Gladkov (3):
  VT: Use macros to define ioctls
  VT: Add KDFONTINFO ioctl
  VT: Allow to get max font width and height

 drivers/tty/vt/vt.c                 |  24 ++++++
 drivers/tty/vt/vt_ioctl.c           |  13 ++++
 drivers/video/console/newport_con.c |  21 +++++-
 drivers/video/console/sticon.c      |  25 ++++++-
 drivers/video/console/vgacon.c      |  21 +++++-
 drivers/video/fbdev/core/fbcon.c    |  16 ++++
 include/linux/console.h             |   3 +
 include/linux/vt_kern.h             |   1 +
 include/uapi/linux/kd.h             | 110 ++++++++++++++++------------
 9 files changed, 180 insertions(+), 54 deletions(-)

-- 
2.44.0


^ permalink raw reply

* [PATCH v5 1/3] VT: Use macros to define ioctls
From: Alexey Gladkov @ 2024-04-17 17:37 UTC (permalink / raw)
  To: Greg Kroah-Hartman, Jiri Slaby
  Cc: LKML, kbd, linux-api, linux-fbdev, linux-serial
In-Reply-To: <cover.1713375378.git.legion@kernel.org>

All other headers use _IOC() macros to describe ioctls for a long time
now. This header is stuck in the last century.

Simply use the _IO() macro. No other changes.

Signed-off-by: Alexey Gladkov <legion@kernel.org>
---
 include/uapi/linux/kd.h | 96 +++++++++++++++++++++--------------------
 1 file changed, 49 insertions(+), 47 deletions(-)

diff --git a/include/uapi/linux/kd.h b/include/uapi/linux/kd.h
index 6b384065c013..8ddb2219a84b 100644
--- a/include/uapi/linux/kd.h
+++ b/include/uapi/linux/kd.h
@@ -5,60 +5,61 @@
 #include <linux/compiler.h>
 
 /* 0x4B is 'K', to avoid collision with termios and vt */
+#define KD_IOCTL_BASE	'K'
 
-#define GIO_FONT	0x4B60	/* gets font in expanded form */
-#define PIO_FONT	0x4B61	/* use font in expanded form */
+#define GIO_FONT	_IO(KD_IOCTL_BASE, 0x60)	/* gets font in expanded form */
+#define PIO_FONT	_IO(KD_IOCTL_BASE, 0x61)	/* use font in expanded form */
 
-#define GIO_FONTX	0x4B6B	/* get font using struct consolefontdesc */
-#define PIO_FONTX	0x4B6C	/* set font using struct consolefontdesc */
+#define GIO_FONTX	_IO(KD_IOCTL_BASE, 0x6B)	/* get font using struct consolefontdesc */
+#define PIO_FONTX	_IO(KD_IOCTL_BASE, 0x6C)	/* set font using struct consolefontdesc */
 struct consolefontdesc {
 	unsigned short charcount;	/* characters in font (256 or 512) */
 	unsigned short charheight;	/* scan lines per character (1-32) */
 	char __user *chardata;		/* font data in expanded form */
 };
 
-#define PIO_FONTRESET   0x4B6D	/* reset to default font */
+#define PIO_FONTRESET	_IO(KD_IOCTL_BASE, 0x6D)	/* reset to default font */
 
-#define GIO_CMAP	0x4B70	/* gets colour palette on VGA+ */
-#define PIO_CMAP	0x4B71	/* sets colour palette on VGA+ */
+#define GIO_CMAP	_IO(KD_IOCTL_BASE, 0x70)	/* gets colour palette on VGA+ */
+#define PIO_CMAP	_IO(KD_IOCTL_BASE, 0x71)	/* sets colour palette on VGA+ */
 
-#define KIOCSOUND	0x4B2F	/* start sound generation (0 for off) */
-#define KDMKTONE	0x4B30	/* generate tone */
+#define KIOCSOUND	_IO(KD_IOCTL_BASE, 0x2F)	/* start sound generation (0 for off) */
+#define KDMKTONE	_IO(KD_IOCTL_BASE, 0x30)	/* generate tone */
 
-#define KDGETLED	0x4B31	/* return current led state */
-#define KDSETLED	0x4B32	/* set led state [lights, not flags] */
+#define KDGETLED	_IO(KD_IOCTL_BASE, 0x31)	/* return current led state */
+#define KDSETLED	_IO(KD_IOCTL_BASE, 0x32)	/* set led state [lights, not flags] */
 #define 	LED_SCR		0x01	/* scroll lock led */
 #define 	LED_NUM		0x02	/* num lock led */
 #define 	LED_CAP		0x04	/* caps lock led */
 
-#define KDGKBTYPE	0x4B33	/* get keyboard type */
+#define KDGKBTYPE	_IO(KD_IOCTL_BASE, 0x33)	/* get keyboard type */
 #define 	KB_84		0x01
 #define 	KB_101		0x02 	/* this is what we always answer */
 #define 	KB_OTHER	0x03
 
-#define KDADDIO		0x4B34	/* add i/o port as valid */
-#define KDDELIO		0x4B35	/* del i/o port as valid */
-#define KDENABIO	0x4B36	/* enable i/o to video board */
-#define KDDISABIO	0x4B37	/* disable i/o to video board */
+#define KDADDIO		_IO(KD_IOCTL_BASE, 0x34)	/* add i/o port as valid */
+#define KDDELIO		_IO(KD_IOCTL_BASE, 0x35)	/* del i/o port as valid */
+#define KDENABIO	_IO(KD_IOCTL_BASE, 0x36)	/* enable i/o to video board */
+#define KDDISABIO	_IO(KD_IOCTL_BASE, 0x37)	/* disable i/o to video board */
 
-#define KDSETMODE	0x4B3A	/* set text/graphics mode */
+#define KDSETMODE	_IO(KD_IOCTL_BASE, 0x3A)	/* set text/graphics mode */
 #define		KD_TEXT		0x00
 #define		KD_GRAPHICS	0x01
 #define		KD_TEXT0	0x02	/* obsolete */
 #define		KD_TEXT1	0x03	/* obsolete */
-#define KDGETMODE	0x4B3B	/* get current mode */
+#define KDGETMODE	_IO(KD_IOCTL_BASE, 0x3B)	/* get current mode */
 
-#define KDMAPDISP	0x4B3C	/* map display into address space */
-#define KDUNMAPDISP	0x4B3D	/* unmap display from address space */
+#define KDMAPDISP	_IO(KD_IOCTL_BASE, 0x3C)	/* map display into address space */
+#define KDUNMAPDISP	_IO(KD_IOCTL_BASE, 0x3D)	/* unmap display from address space */
 
 typedef char scrnmap_t;
 #define		E_TABSZ		256
-#define GIO_SCRNMAP	0x4B40	/* get screen mapping from kernel */
-#define PIO_SCRNMAP	0x4B41	/* put screen mapping table in kernel */
-#define GIO_UNISCRNMAP  0x4B69	/* get full Unicode screen mapping */
-#define PIO_UNISCRNMAP  0x4B6A  /* set full Unicode screen mapping */
+#define GIO_SCRNMAP	_IO(KD_IOCTL_BASE, 0x40)	/* get screen mapping from kernel */
+#define PIO_SCRNMAP	_IO(KD_IOCTL_BASE, 0x41)	/* put screen mapping table in kernel */
+#define GIO_UNISCRNMAP	_IO(KD_IOCTL_BASE, 0x69)	/* get full Unicode screen mapping */
+#define PIO_UNISCRNMAP	_IO(KD_IOCTL_BASE, 0x6A)	/* set full Unicode screen mapping */
 
-#define GIO_UNIMAP	0x4B66	/* get unicode-to-font mapping from kernel */
+#define GIO_UNIMAP	_IO(KD_IOCTL_BASE, 0x66)	/* get unicode-to-font mapping from kernel */
 struct unipair {
 	unsigned short unicode;
 	unsigned short fontpos;
@@ -67,8 +68,8 @@ struct unimapdesc {
 	unsigned short entry_ct;
 	struct unipair __user *entries;
 };
-#define PIO_UNIMAP	0x4B67	/* put unicode-to-font mapping in kernel */
-#define PIO_UNIMAPCLR	0x4B68	/* clear table, possibly advise hash algorithm */
+#define PIO_UNIMAP	_IO(KD_IOCTL_BASE, 0x67)	/* put unicode-to-font mapping in kernel */
+#define PIO_UNIMAPCLR	_IO(KD_IOCTL_BASE, 0x68)	/* clear table, possibly advise hash algorithm */
 struct unimapinit {
 	unsigned short advised_hashsize;  /* 0 if no opinion */
 	unsigned short advised_hashstep;  /* 0 if no opinion */
@@ -83,19 +84,19 @@ struct unimapinit {
 #define		K_MEDIUMRAW	0x02
 #define		K_UNICODE	0x03
 #define		K_OFF		0x04
-#define KDGKBMODE	0x4B44	/* gets current keyboard mode */
-#define KDSKBMODE	0x4B45	/* sets current keyboard mode */
+#define KDGKBMODE	_IO(KD_IOCTL_BASE, 0x44)	/* gets current keyboard mode */
+#define KDSKBMODE	_IO(KD_IOCTL_BASE, 0x45)	/* sets current keyboard mode */
 
 #define		K_METABIT	0x03
 #define		K_ESCPREFIX	0x04
-#define KDGKBMETA	0x4B62	/* gets meta key handling mode */
-#define KDSKBMETA	0x4B63	/* sets meta key handling mode */
+#define KDGKBMETA	_IO(KD_IOCTL_BASE, 0x62)	/* gets meta key handling mode */
+#define KDSKBMETA	_IO(KD_IOCTL_BASE, 0x63)	/* sets meta key handling mode */
 
 #define		K_SCROLLLOCK	0x01
 #define		K_NUMLOCK	0x02
 #define		K_CAPSLOCK	0x04
-#define	KDGKBLED	0x4B64	/* get led flags (not lights) */
-#define	KDSKBLED	0x4B65	/* set led flags (not lights) */
+#define	KDGKBLED	_IO(KD_IOCTL_BASE, 0x64)	/* get led flags (not lights) */
+#define	KDSKBLED	_IO(KD_IOCTL_BASE, 0x65)	/* set led flags (not lights) */
 
 struct kbentry {
 	unsigned char kb_table;
@@ -107,15 +108,15 @@ struct kbentry {
 #define		K_ALTTAB	0x02
 #define		K_ALTSHIFTTAB	0x03
 
-#define KDGKBENT	0x4B46	/* gets one entry in translation table */
-#define KDSKBENT	0x4B47	/* sets one entry in translation table */
+#define KDGKBENT	_IO(KD_IOCTL_BASE, 0x46)	/* gets one entry in translation table */
+#define KDSKBENT	_IO(KD_IOCTL_BASE, 0x47)	/* sets one entry in translation table */
 
 struct kbsentry {
 	unsigned char kb_func;
 	unsigned char kb_string[512];
 };
-#define KDGKBSENT	0x4B48	/* gets one function key string entry */
-#define KDSKBSENT	0x4B49	/* sets one function key string entry */
+#define KDGKBSENT	_IO(KD_IOCTL_BASE, 0x48)	/* gets one function key string entry */
+#define KDSKBSENT	_IO(KD_IOCTL_BASE, 0x49)	/* sets one function key string entry */
 
 struct kbdiacr {
         unsigned char diacr, base, result;
@@ -124,8 +125,8 @@ struct kbdiacrs {
         unsigned int kb_cnt;    /* number of entries in following array */
 	struct kbdiacr kbdiacr[256];    /* MAX_DIACR from keyboard.h */
 };
-#define KDGKBDIACR      0x4B4A  /* read kernel accent table */
-#define KDSKBDIACR      0x4B4B  /* write kernel accent table */
+#define KDGKBDIACR	_IO(KD_IOCTL_BASE, 0x4A)  /* read kernel accent table */
+#define KDSKBDIACR	_IO(KD_IOCTL_BASE, 0x4B)  /* write kernel accent table */
 
 struct kbdiacruc {
 	unsigned int diacr, base, result;
@@ -134,16 +135,16 @@ struct kbdiacrsuc {
         unsigned int kb_cnt;    /* number of entries in following array */
 	struct kbdiacruc kbdiacruc[256];    /* MAX_DIACR from keyboard.h */
 };
-#define KDGKBDIACRUC    0x4BFA  /* read kernel accent table - UCS */
-#define KDSKBDIACRUC    0x4BFB  /* write kernel accent table - UCS */
+#define KDGKBDIACRUC	_IO(KD_IOCTL_BASE, 0xFA)  /* read kernel accent table - UCS */
+#define KDSKBDIACRUC	_IO(KD_IOCTL_BASE, 0xFB)  /* write kernel accent table - UCS */
 
 struct kbkeycode {
 	unsigned int scancode, keycode;
 };
-#define KDGETKEYCODE	0x4B4C	/* read kernel keycode table entry */
-#define KDSETKEYCODE	0x4B4D	/* write kernel keycode table entry */
+#define KDGETKEYCODE	_IO(KD_IOCTL_BASE, 0x4C)	/* read kernel keycode table entry */
+#define KDSETKEYCODE	_IO(KD_IOCTL_BASE, 0x4D)	/* write kernel keycode table entry */
 
-#define KDSIGACCEPT	0x4B4E	/* accept kbd generated signals */
+#define KDSIGACCEPT	_IO(KD_IOCTL_BASE, 0x4E)	/* accept kbd generated signals */
 
 struct kbd_repeat {
 	int delay;	/* in msec; <= 0: don't change */
@@ -151,10 +152,11 @@ struct kbd_repeat {
 			/* earlier this field was misnamed "rate" */
 };
 
-#define KDKBDREP        0x4B52  /* set keyboard delay/repeat rate;
-				 * actually used values are returned */
+#define KDKBDREP	_IO(KD_IOCTL_BASE, 0x52)	/* set keyboard delay/repeat rate;
+							 * actually used values are returned
+							 */
 
-#define KDFONTOP	0x4B72	/* font operations */
+#define KDFONTOP	_IO(KD_IOCTL_BASE, 0x72)	/* font operations */
 
 struct console_font_op {
 	unsigned int op;	/* operation code KD_FONT_OP_* */
-- 
2.44.0


^ permalink raw reply related


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox