Linux userland API discussions
 help / color / mirror / Atom feed
* [PATCHv3 bpf-next 5/7] selftests/bpf: Add uretprobe syscall call from user space test
From: Jiri Olsa @ 2024-04-21 19:42 UTC (permalink / raw)
  To: Steven Rostedt, Masami Hiramatsu, Oleg Nesterov,
	Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko
  Cc: linux-kernel, linux-trace-kernel, linux-api, x86, bpf, Song Liu,
	Yonghong Song, John Fastabend, Peter Zijlstra, Thomas Gleixner,
	Borislav Petkov (AMD), Ingo Molnar, Andy Lutomirski
In-Reply-To: <20240421194206.1010934-1-jolsa@kernel.org>

Adding test to verify that when called from outside of the
trampoline provided by kernel, the uretprobe syscall will cause
calling process to receive SIGILL signal and the attached bpf
program is no executed.

Signed-off-by: Jiri Olsa <jolsa@kernel.org>
---
 .../selftests/bpf/prog_tests/uprobe_syscall.c | 92 +++++++++++++++++++
 .../selftests/bpf/progs/uprobe_syscall_call.c | 15 +++
 2 files changed, 107 insertions(+)
 create mode 100644 tools/testing/selftests/bpf/progs/uprobe_syscall_call.c

diff --git a/tools/testing/selftests/bpf/prog_tests/uprobe_syscall.c b/tools/testing/selftests/bpf/prog_tests/uprobe_syscall.c
index 1a50cd35205d..9233210a4c33 100644
--- a/tools/testing/selftests/bpf/prog_tests/uprobe_syscall.c
+++ b/tools/testing/selftests/bpf/prog_tests/uprobe_syscall.c
@@ -7,7 +7,10 @@
 #include <unistd.h>
 #include <asm/ptrace.h>
 #include <linux/compiler.h>
+#include <linux/stringify.h>
+#include <sys/wait.h>
 #include "uprobe_syscall.skel.h"
+#include "uprobe_syscall_call.skel.h"
 
 __naked unsigned long uretprobe_regs_trigger(void)
 {
@@ -209,6 +212,85 @@ static void test_uretprobe_regs_change(void)
 	}
 }
 
+#ifndef __NR_uretprobe
+#define __NR_uretprobe 462
+#endif
+
+__naked unsigned long uretprobe_syscall_call_1(void)
+{
+	/*
+	 * Pretend we are uretprobe trampoline to trigger the return
+	 * probe invocation in order to verify we get SIGILL.
+	 */
+	asm volatile (
+		"pushq %rax\n"
+		"pushq %rcx\n"
+		"pushq %r11\n"
+		"movq $" __stringify(__NR_uretprobe) ", %rax\n"
+		"syscall\n"
+		"popq %r11\n"
+		"popq %rcx\n"
+		"retq\n"
+	);
+}
+
+__naked unsigned long uretprobe_syscall_call(void)
+{
+	asm volatile (
+		"call uretprobe_syscall_call_1\n"
+		"retq\n"
+	);
+}
+
+static void __test_uretprobe_syscall_call(void)
+{
+	struct uprobe_syscall_call *skel = NULL;
+	int err;
+
+	skel = uprobe_syscall_call__open_and_load();
+	if (!ASSERT_OK_PTR(skel, "uprobe_syscall_call__open_and_load"))
+		goto cleanup;
+
+	err = uprobe_syscall_call__attach(skel);
+	if (!ASSERT_OK(err, "uprobe_syscall_call__attach"))
+		goto cleanup;
+
+	uretprobe_syscall_call();
+
+cleanup:
+	uprobe_syscall_call__destroy(skel);
+}
+
+static void trace_pipe_cb(const char *str, void *data)
+{
+	if (strstr(str, "uretprobe called") != NULL)
+		(*(int *)data)++;
+}
+
+static void test_uretprobe_syscall_call(void)
+{
+	int pid, status, found = 0;
+
+	pid = fork();
+	if (!ASSERT_GE(pid, 0, "fork"))
+		return;
+
+	if (pid == 0) {
+		__test_uretprobe_syscall_call();
+		_exit(0);
+	}
+
+	waitpid(pid, &status, 0);
+
+	/* verify the child got killed with SIGILL */
+	ASSERT_EQ(WIFSIGNALED(status), 1, "WIFSIGNALED");
+	ASSERT_EQ(WTERMSIG(status), SIGILL, "WTERMSIG");
+
+	/* verify the uretprobe program wasn't called */
+	ASSERT_OK(read_trace_pipe_iter(trace_pipe_cb, &found, 1000),
+		 "read_trace_pipe_iter");
+	ASSERT_EQ(found, 0, "found");
+}
 #else
 static void test_uretprobe_regs_equal(void)
 {
@@ -219,6 +301,11 @@ static void test_uretprobe_regs_change(void)
 {
 	test__skip();
 }
+
+static void test_uretprobe_syscall_call(void)
+{
+	test__skip();
+}
 #endif
 
 void test_uprobe_syscall(void)
@@ -228,3 +315,8 @@ void test_uprobe_syscall(void)
 	if (test__start_subtest("uretprobe_regs_change"))
 		test_uretprobe_regs_change();
 }
+
+void serial_test_uprobe_syscall_call(void)
+{
+	test_uretprobe_syscall_call();
+}
diff --git a/tools/testing/selftests/bpf/progs/uprobe_syscall_call.c b/tools/testing/selftests/bpf/progs/uprobe_syscall_call.c
new file mode 100644
index 000000000000..5ea03bb47198
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/uprobe_syscall_call.c
@@ -0,0 +1,15 @@
+// SPDX-License-Identifier: GPL-2.0
+#include "vmlinux.h"
+#include <bpf/bpf_helpers.h>
+#include <string.h>
+
+struct pt_regs regs;
+
+char _license[] SEC("license") = "GPL";
+
+SEC("uretprobe//proc/self/exe:uretprobe_syscall_call")
+int uretprobe(struct pt_regs *regs)
+{
+	bpf_printk("uretprobe called");
+	return 0;
+}
-- 
2.44.0


^ permalink raw reply related

* [PATCHv3 bpf-next 4/7] selftests/bpf: Add uretprobe syscall test for regs changes
From: Jiri Olsa @ 2024-04-21 19:42 UTC (permalink / raw)
  To: Steven Rostedt, Masami Hiramatsu, Oleg Nesterov,
	Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko
  Cc: linux-kernel, linux-trace-kernel, linux-api, x86, bpf, Song Liu,
	Yonghong Song, John Fastabend, Peter Zijlstra, Thomas Gleixner,
	Borislav Petkov (AMD), Ingo Molnar, Andy Lutomirski
In-Reply-To: <20240421194206.1010934-1-jolsa@kernel.org>

Adding test that creates uprobe consumer on uretprobe which changes some
of the registers. Making sure the changed registers are propagated to the
user space when the ureptobe syscall trampoline is used on x86_64.

To be able to do this, adding support to bpf_testmod to create uprobe via
new attribute file:
  /sys/kernel/bpf_testmod_uprobe

This file is expecting file offset and creates related uprobe on current
process exe file and removes existing uprobe if offset is 0. The can be
only single uprobe at any time.

The uprobe has specific consumer that changes registers used in ureprobe
syscall trampoline and which are later checked in the test.

Acked-by: Andrii Nakryiko <andrii@kernel.org>
Signed-off-by: Jiri Olsa <jolsa@kernel.org>
---
 .../selftests/bpf/bpf_testmod/bpf_testmod.c   | 123 +++++++++++++++++-
 .../selftests/bpf/prog_tests/uprobe_syscall.c |  67 ++++++++++
 2 files changed, 189 insertions(+), 1 deletion(-)

diff --git a/tools/testing/selftests/bpf/bpf_testmod/bpf_testmod.c b/tools/testing/selftests/bpf/bpf_testmod/bpf_testmod.c
index 39ad96a18123..c832cbb42e74 100644
--- a/tools/testing/selftests/bpf/bpf_testmod/bpf_testmod.c
+++ b/tools/testing/selftests/bpf/bpf_testmod/bpf_testmod.c
@@ -10,6 +10,7 @@
 #include <linux/percpu-defs.h>
 #include <linux/sysfs.h>
 #include <linux/tracepoint.h>
+#include <linux/namei.h>
 #include "bpf_testmod.h"
 #include "bpf_testmod_kfunc.h"
 
@@ -343,6 +344,119 @@ static struct bin_attribute bin_attr_bpf_testmod_file __ro_after_init = {
 	.write = bpf_testmod_test_write,
 };
 
+/* bpf_testmod_uprobe sysfs attribute is so far enabled for x86_64 only,
+ * please see test_uretprobe_regs_change test
+ */
+#ifdef __x86_64__
+
+static int
+uprobe_ret_handler(struct uprobe_consumer *self, unsigned long func,
+		   struct pt_regs *regs)
+
+{
+	regs->ax  = 0x12345678deadbeef;
+	regs->cx  = 0x87654321feebdaed;
+	regs->r11 = (u64) -1;
+	return true;
+}
+
+struct testmod_uprobe {
+	struct path path;
+	loff_t offset;
+	struct uprobe_consumer consumer;
+};
+
+static DEFINE_MUTEX(testmod_uprobe_mutex);
+
+static struct testmod_uprobe uprobe = {
+	.consumer.ret_handler = uprobe_ret_handler,
+};
+
+static int testmod_register_uprobe(loff_t offset)
+{
+	int err = -EBUSY;
+
+	if (uprobe.offset)
+		return -EBUSY;
+
+	mutex_lock(&testmod_uprobe_mutex);
+
+	if (uprobe.offset)
+		goto out;
+
+	err = kern_path("/proc/self/exe", LOOKUP_FOLLOW, &uprobe.path);
+	if (err)
+		goto out;
+
+	err = uprobe_register_refctr(d_real_inode(uprobe.path.dentry),
+				     offset, 0, &uprobe.consumer);
+	if (err)
+		path_put(&uprobe.path);
+	else
+		uprobe.offset = offset;
+
+out:
+	mutex_unlock(&testmod_uprobe_mutex);
+	return err;
+}
+
+static void testmod_unregister_uprobe(void)
+{
+	mutex_lock(&testmod_uprobe_mutex);
+
+	if (uprobe.offset) {
+		uprobe_unregister(d_real_inode(uprobe.path.dentry),
+				  uprobe.offset, &uprobe.consumer);
+		uprobe.offset = 0;
+	}
+
+	mutex_unlock(&testmod_uprobe_mutex);
+}
+
+static ssize_t
+bpf_testmod_uprobe_write(struct file *file, struct kobject *kobj,
+			 struct bin_attribute *bin_attr,
+			 char *buf, loff_t off, size_t len)
+{
+	unsigned long offset;
+	int err;
+
+	if (kstrtoul(buf, 0, &offset))
+		return -EINVAL;
+
+	if (offset)
+		err = testmod_register_uprobe(offset);
+	else
+		testmod_unregister_uprobe();
+
+	return err ?: strlen(buf);
+}
+
+static struct bin_attribute bin_attr_bpf_testmod_uprobe_file __ro_after_init = {
+	.attr = { .name = "bpf_testmod_uprobe", .mode = 0666, },
+	.write = bpf_testmod_uprobe_write,
+};
+
+static int register_bpf_testmod_uprobe(void)
+{
+	return sysfs_create_bin_file(kernel_kobj, &bin_attr_bpf_testmod_uprobe_file);
+}
+
+static void unregister_bpf_testmod_uprobe(void)
+{
+	testmod_unregister_uprobe();
+	sysfs_remove_bin_file(kernel_kobj, &bin_attr_bpf_testmod_uprobe_file);
+}
+
+#else
+static int register_bpf_testmod_uprobe(void)
+{
+	return 0;
+}
+
+static void unregister_bpf_testmod_uprobe(void) { }
+#endif
+
 BTF_KFUNCS_START(bpf_testmod_common_kfunc_ids)
 BTF_ID_FLAGS(func, bpf_iter_testmod_seq_new, KF_ITER_NEW)
 BTF_ID_FLAGS(func, bpf_iter_testmod_seq_next, KF_ITER_NEXT | KF_RET_NULL)
@@ -650,7 +764,13 @@ static int bpf_testmod_init(void)
 		return ret;
 	if (bpf_fentry_test1(0) < 0)
 		return -EINVAL;
-	return sysfs_create_bin_file(kernel_kobj, &bin_attr_bpf_testmod_file);
+	ret = sysfs_create_bin_file(kernel_kobj, &bin_attr_bpf_testmod_file);
+	if (ret < 0)
+		return ret;
+	ret = register_bpf_testmod_uprobe();
+	if (ret < 0)
+		return ret;
+	return 0;
 }
 
 static void bpf_testmod_exit(void)
@@ -664,6 +784,7 @@ static void bpf_testmod_exit(void)
 		msleep(20);
 
 	sysfs_remove_bin_file(kernel_kobj, &bin_attr_bpf_testmod_file);
+	unregister_bpf_testmod_uprobe();
 }
 
 module_init(bpf_testmod_init);
diff --git a/tools/testing/selftests/bpf/prog_tests/uprobe_syscall.c b/tools/testing/selftests/bpf/prog_tests/uprobe_syscall.c
index 311ac19d8992..1a50cd35205d 100644
--- a/tools/testing/selftests/bpf/prog_tests/uprobe_syscall.c
+++ b/tools/testing/selftests/bpf/prog_tests/uprobe_syscall.c
@@ -149,15 +149,82 @@ static void test_uretprobe_regs_equal(void)
 cleanup:
 	uprobe_syscall__destroy(skel);
 }
+
+#define BPF_TESTMOD_UPROBE_TEST_FILE "/sys/kernel/bpf_testmod_uprobe"
+
+static int write_bpf_testmod_uprobe(unsigned long offset)
+{
+	size_t n, ret;
+	char buf[30];
+	int fd;
+
+	n = sprintf(buf, "%lu", offset);
+
+	fd = open(BPF_TESTMOD_UPROBE_TEST_FILE, O_WRONLY);
+	if (fd < 0)
+		return -errno;
+
+	ret = write(fd, buf, n);
+	close(fd);
+	return ret != n ? (int) ret : 0;
+}
+
+static void test_uretprobe_regs_change(void)
+{
+	struct pt_regs before = {}, after = {};
+	unsigned long *pb = (unsigned long *) &before;
+	unsigned long *pa = (unsigned long *) &after;
+	unsigned long cnt = sizeof(before)/sizeof(*pb);
+	unsigned int i, err, offset;
+
+	offset = get_uprobe_offset(uretprobe_regs_trigger);
+
+	err = write_bpf_testmod_uprobe(offset);
+	if (!ASSERT_OK(err, "register_uprobe"))
+		return;
+
+	uretprobe_regs(&before, &after);
+
+	err = write_bpf_testmod_uprobe(0);
+	if (!ASSERT_OK(err, "unregister_uprobe"))
+		return;
+
+	for (i = 0; i < cnt; i++) {
+		unsigned int offset = i * sizeof(unsigned long);
+
+		switch (offset) {
+		case offsetof(struct pt_regs, rax):
+			ASSERT_EQ(pa[i], 0x12345678deadbeef, "rax");
+			break;
+		case offsetof(struct pt_regs, rcx):
+			ASSERT_EQ(pa[i], 0x87654321feebdaed, "rcx");
+			break;
+		case offsetof(struct pt_regs, r11):
+			ASSERT_EQ(pa[i], (__u64) -1, "r11");
+			break;
+		default:
+			if (!ASSERT_EQ(pa[i], pb[i], "register before-after value check"))
+				fprintf(stdout, "failed register offset %u\n", offset);
+		}
+	}
+}
+
 #else
 static void test_uretprobe_regs_equal(void)
 {
 	test__skip();
 }
+
+static void test_uretprobe_regs_change(void)
+{
+	test__skip();
+}
 #endif
 
 void test_uprobe_syscall(void)
 {
 	if (test__start_subtest("uretprobe_regs_equal"))
 		test_uretprobe_regs_equal();
+	if (test__start_subtest("uretprobe_regs_change"))
+		test_uretprobe_regs_change();
 }
-- 
2.44.0


^ permalink raw reply related

* [PATCHv3 bpf-next 3/7] selftests/bpf: Add uretprobe syscall test for regs integrity
From: Jiri Olsa @ 2024-04-21 19:42 UTC (permalink / raw)
  To: Steven Rostedt, Masami Hiramatsu, Oleg Nesterov,
	Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko
  Cc: linux-kernel, linux-trace-kernel, linux-api, x86, bpf, Song Liu,
	Yonghong Song, John Fastabend, Peter Zijlstra, Thomas Gleixner,
	Borislav Petkov (AMD), Ingo Molnar, Andy Lutomirski
In-Reply-To: <20240421194206.1010934-1-jolsa@kernel.org>

Add uretprobe syscall test that compares register values before
and after the uretprobe is hit. It also compares the register
values seen from attached bpf program.

Acked-by: Andrii Nakryiko <andrii@kernel.org>
Signed-off-by: Jiri Olsa <jolsa@kernel.org>
---
 tools/include/linux/compiler.h                |   4 +
 .../selftests/bpf/prog_tests/uprobe_syscall.c | 163 ++++++++++++++++++
 .../selftests/bpf/progs/uprobe_syscall.c      |  15 ++
 3 files changed, 182 insertions(+)
 create mode 100644 tools/testing/selftests/bpf/prog_tests/uprobe_syscall.c
 create mode 100644 tools/testing/selftests/bpf/progs/uprobe_syscall.c

diff --git a/tools/include/linux/compiler.h b/tools/include/linux/compiler.h
index 8a63a9913495..6f7f22ac9da5 100644
--- a/tools/include/linux/compiler.h
+++ b/tools/include/linux/compiler.h
@@ -62,6 +62,10 @@
 #define __nocf_check __attribute__((nocf_check))
 #endif
 
+#ifndef __naked
+#define __naked __attribute__((__naked__))
+#endif
+
 /* Are two types/vars the same type (ignoring qualifiers)? */
 #ifndef __same_type
 # define __same_type(a, b) __builtin_types_compatible_p(typeof(a), typeof(b))
diff --git a/tools/testing/selftests/bpf/prog_tests/uprobe_syscall.c b/tools/testing/selftests/bpf/prog_tests/uprobe_syscall.c
new file mode 100644
index 000000000000..311ac19d8992
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/uprobe_syscall.c
@@ -0,0 +1,163 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <test_progs.h>
+
+#ifdef __x86_64__
+
+#include <unistd.h>
+#include <asm/ptrace.h>
+#include <linux/compiler.h>
+#include "uprobe_syscall.skel.h"
+
+__naked unsigned long uretprobe_regs_trigger(void)
+{
+	asm volatile (
+		"movq $0xdeadbeef, %rax\n"
+		"ret\n"
+	);
+}
+
+__naked void uretprobe_regs(struct pt_regs *before, struct pt_regs *after)
+{
+	asm volatile (
+		"movq %r15,   0(%rdi)\n"
+		"movq %r14,   8(%rdi)\n"
+		"movq %r13,  16(%rdi)\n"
+		"movq %r12,  24(%rdi)\n"
+		"movq %rbp,  32(%rdi)\n"
+		"movq %rbx,  40(%rdi)\n"
+		"movq %r11,  48(%rdi)\n"
+		"movq %r10,  56(%rdi)\n"
+		"movq  %r9,  64(%rdi)\n"
+		"movq  %r8,  72(%rdi)\n"
+		"movq %rax,  80(%rdi)\n"
+		"movq %rcx,  88(%rdi)\n"
+		"movq %rdx,  96(%rdi)\n"
+		"movq %rsi, 104(%rdi)\n"
+		"movq %rdi, 112(%rdi)\n"
+		"movq   $0, 120(%rdi)\n" /* orig_rax */
+		"movq   $0, 128(%rdi)\n" /* rip      */
+		"movq   $0, 136(%rdi)\n" /* cs       */
+		"pushf\n"
+		"pop %rax\n"
+		"movq %rax, 144(%rdi)\n" /* eflags   */
+		"movq %rsp, 152(%rdi)\n" /* rsp      */
+		"movq   $0, 160(%rdi)\n" /* ss       */
+
+		/* save 2nd argument */
+		"pushq %rsi\n"
+		"call uretprobe_regs_trigger\n"
+
+		/* save  return value and load 2nd argument pointer to rax */
+		"pushq %rax\n"
+		"movq 8(%rsp), %rax\n"
+
+		"movq %r15,   0(%rax)\n"
+		"movq %r14,   8(%rax)\n"
+		"movq %r13,  16(%rax)\n"
+		"movq %r12,  24(%rax)\n"
+		"movq %rbp,  32(%rax)\n"
+		"movq %rbx,  40(%rax)\n"
+		"movq %r11,  48(%rax)\n"
+		"movq %r10,  56(%rax)\n"
+		"movq  %r9,  64(%rax)\n"
+		"movq  %r8,  72(%rax)\n"
+		"movq %rcx,  88(%rax)\n"
+		"movq %rdx,  96(%rax)\n"
+		"movq %rsi, 104(%rax)\n"
+		"movq %rdi, 112(%rax)\n"
+		"movq   $0, 120(%rax)\n" /* orig_rax */
+		"movq   $0, 128(%rax)\n" /* rip      */
+		"movq   $0, 136(%rax)\n" /* cs       */
+
+		/* restore return value and 2nd argument */
+		"pop %rax\n"
+		"pop %rsi\n"
+
+		"movq %rax,  80(%rsi)\n"
+
+		"pushf\n"
+		"pop %rax\n"
+
+		"movq %rax, 144(%rsi)\n" /* eflags   */
+		"movq %rsp, 152(%rsi)\n" /* rsp      */
+		"movq   $0, 160(%rsi)\n" /* ss       */
+		"ret\n"
+);
+}
+
+static void test_uretprobe_regs_equal(void)
+{
+	struct uprobe_syscall *skel = NULL;
+	struct pt_regs before = {}, after = {};
+	unsigned long *pb = (unsigned long *) &before;
+	unsigned long *pa = (unsigned long *) &after;
+	unsigned long *pp;
+	unsigned int i, cnt;
+	int err;
+
+	skel = uprobe_syscall__open_and_load();
+	if (!ASSERT_OK_PTR(skel, "uprobe_syscall__open_and_load"))
+		goto cleanup;
+
+	err = uprobe_syscall__attach(skel);
+	if (!ASSERT_OK(err, "uprobe_syscall__attach"))
+		goto cleanup;
+
+	uretprobe_regs(&before, &after);
+
+	pp = (unsigned long *) &skel->bss->regs;
+	cnt = sizeof(before)/sizeof(*pb);
+
+	for (i = 0; i < cnt; i++) {
+		unsigned int offset = i * sizeof(unsigned long);
+
+		/*
+		 * Check register before and after uretprobe_regs_trigger call
+		 * that triggers the uretprobe.
+		 */
+		switch (offset) {
+		case offsetof(struct pt_regs, rax):
+			ASSERT_EQ(pa[i], 0xdeadbeef, "return value");
+			break;
+		default:
+			if (!ASSERT_EQ(pb[i], pa[i], "register before-after value check"))
+				fprintf(stdout, "failed register offset %u\n", offset);
+		}
+
+		/*
+		 * Check register seen from bpf program and register after
+		 * uretprobe_regs_trigger call
+		 */
+		switch (offset) {
+		/*
+		 * These values will be different (not set in uretprobe_regs),
+		 * we don't care.
+		 */
+		case offsetof(struct pt_regs, orig_rax):
+		case offsetof(struct pt_regs, rip):
+		case offsetof(struct pt_regs, cs):
+		case offsetof(struct pt_regs, rsp):
+		case offsetof(struct pt_regs, ss):
+			break;
+		default:
+			if (!ASSERT_EQ(pp[i], pa[i], "register prog-after value check"))
+				fprintf(stdout, "failed register offset %u\n", offset);
+		}
+	}
+
+cleanup:
+	uprobe_syscall__destroy(skel);
+}
+#else
+static void test_uretprobe_regs_equal(void)
+{
+	test__skip();
+}
+#endif
+
+void test_uprobe_syscall(void)
+{
+	if (test__start_subtest("uretprobe_regs_equal"))
+		test_uretprobe_regs_equal();
+}
diff --git a/tools/testing/selftests/bpf/progs/uprobe_syscall.c b/tools/testing/selftests/bpf/progs/uprobe_syscall.c
new file mode 100644
index 000000000000..8a4fa6c7ef59
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/uprobe_syscall.c
@@ -0,0 +1,15 @@
+// SPDX-License-Identifier: GPL-2.0
+#include "vmlinux.h"
+#include <bpf/bpf_helpers.h>
+#include <string.h>
+
+struct pt_regs regs;
+
+char _license[] SEC("license") = "GPL";
+
+SEC("uretprobe//proc/self/exe:uretprobe_regs_trigger")
+int uretprobe(struct pt_regs *ctx)
+{
+	__builtin_memcpy(&regs, ctx, sizeof(regs));
+	return 0;
+}
-- 
2.44.0


^ permalink raw reply related

* [PATCHv3 bpf-next 2/7] uprobe: Add uretprobe syscall to speed up return probe
From: Jiri Olsa @ 2024-04-21 19:42 UTC (permalink / raw)
  To: Steven Rostedt, Masami Hiramatsu, Oleg Nesterov,
	Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko
  Cc: linux-kernel, linux-trace-kernel, linux-api, x86, bpf, Song Liu,
	Yonghong Song, John Fastabend, Peter Zijlstra, Thomas Gleixner,
	Borislav Petkov (AMD), Ingo Molnar, Andy Lutomirski
In-Reply-To: <20240421194206.1010934-1-jolsa@kernel.org>

Adding uretprobe syscall instead of trap to speed up return probe.

At the moment the uretprobe setup/path is:

  - install entry uprobe

  - when the uprobe is hit, it overwrites probed function's return address
    on stack with address of the trampoline that contains breakpoint
    instruction

  - the breakpoint trap code handles the uretprobe consumers execution and
    jumps back to original return address

This patch replaces the above trampoline's breakpoint instruction with new
ureprobe syscall call. This syscall does exactly the same job as the trap
with some more extra work:

  - syscall trampoline must save original value for rax/r11/rcx registers
    on stack - rax is set to syscall number and r11/rcx are changed and
    used by syscall instruction

  - the syscall code reads the original values of those registers and
    restore those values in task's pt_regs area

  - only caller from trampoline exposed in '[uprobes]' is allowed,
    the process will receive SIGILL signal otherwise

Even with some extra work, using the uretprobes syscall shows speed
improvement (compared to using standard breakpoint):

  On Intel (11th Gen Intel(R) Core(TM) i7-1165G7 @ 2.80GHz)

  current:
    uretprobe-nop  :    1.498 ± 0.000M/s
    uretprobe-push :    1.448 ± 0.001M/s
    uretprobe-ret  :    0.816 ± 0.001M/s

  with the fix:
    uretprobe-nop  :    1.969 ± 0.002M/s  < 31% speed up
    uretprobe-push :    1.910 ± 0.000M/s  < 31% speed up
    uretprobe-ret  :    0.934 ± 0.000M/s  < 14% speed up

  On Amd (AMD Ryzen 7 5700U)

  current:
    uretprobe-nop  :    0.778 ± 0.001M/s
    uretprobe-push :    0.744 ± 0.001M/s
    uretprobe-ret  :    0.540 ± 0.001M/s

  with the fix:
    uretprobe-nop  :    0.860 ± 0.001M/s  < 10% speed up
    uretprobe-push :    0.818 ± 0.001M/s  < 10% speed up
    uretprobe-ret  :    0.578 ± 0.000M/s  <  7% speed up

The performance test spawns a thread that runs loop which triggers
uprobe with attached bpf program that increments the counter that
gets printed in results above.

The uprobe (and uretprobe) kind is determined by which instruction
is being patched with breakpoint instruction. That's also important
for uretprobes, because uprobe is installed for each uretprobe.

The performance test is part of bpf selftests:
  tools/testing/selftests/bpf/run_bench_uprobes.sh

Note at the moment uretprobe syscall is supported only for native
64-bit process, compat process still uses standard breakpoint.

Suggested-by: Andrii Nakryiko <andrii@kernel.org>
Signed-off-by: Oleg Nesterov <oleg@redhat.com>
Signed-off-by: Jiri Olsa <jolsa@kernel.org>
---
 arch/x86/kernel/uprobes.c | 115 ++++++++++++++++++++++++++++++++++++++
 include/linux/uprobes.h   |   3 +
 kernel/events/uprobes.c   |  24 +++++---
 3 files changed, 135 insertions(+), 7 deletions(-)

diff --git a/arch/x86/kernel/uprobes.c b/arch/x86/kernel/uprobes.c
index 6c07f6daaa22..81e6ee95784d 100644
--- a/arch/x86/kernel/uprobes.c
+++ b/arch/x86/kernel/uprobes.c
@@ -12,6 +12,7 @@
 #include <linux/ptrace.h>
 #include <linux/uprobes.h>
 #include <linux/uaccess.h>
+#include <linux/syscalls.h>
 
 #include <linux/kdebug.h>
 #include <asm/processor.h>
@@ -308,6 +309,120 @@ static int uprobe_init_insn(struct arch_uprobe *auprobe, struct insn *insn, bool
 }
 
 #ifdef CONFIG_X86_64
+
+asm (
+	".pushsection .rodata\n"
+	".global uretprobe_syscall_entry\n"
+	"uretprobe_syscall_entry:\n"
+	"pushq %rax\n"
+	"pushq %rcx\n"
+	"pushq %r11\n"
+	"movq $" __stringify(__NR_uretprobe) ", %rax\n"
+	"syscall\n"
+	".global uretprobe_syscall_check\n"
+	"uretprobe_syscall_check:\n"
+	"popq %r11\n"
+	"popq %rcx\n"
+
+	/* The uretprobe syscall replaces stored %rax value with final
+	 * return address, so we don't restore %rax in here and just
+	 * call ret.
+	 */
+	"retq\n"
+	".global uretprobe_syscall_end\n"
+	"uretprobe_syscall_end:\n"
+	".popsection\n"
+);
+
+extern u8 uretprobe_syscall_entry[];
+extern u8 uretprobe_syscall_check[];
+extern u8 uretprobe_syscall_end[];
+
+void *arch_uprobe_trampoline(unsigned long *psize)
+{
+	static uprobe_opcode_t insn = UPROBE_SWBP_INSN;
+	struct pt_regs *regs = task_pt_regs(current);
+
+	/*
+	 * At the moment the uretprobe syscall trampoline is supported
+	 * only for native 64-bit process, the compat process still uses
+	 * standard breakpoint.
+	 */
+	if (user_64bit_mode(regs)) {
+		*psize = uretprobe_syscall_end - uretprobe_syscall_entry;
+		return uretprobe_syscall_entry;
+	}
+
+	*psize = UPROBE_SWBP_INSN_SIZE;
+	return &insn;
+}
+
+static unsigned long trampoline_check_ip(void)
+{
+	unsigned long tramp = uprobe_get_trampoline_vaddr();
+
+	return tramp + (uretprobe_syscall_check - uretprobe_syscall_entry);
+}
+
+SYSCALL_DEFINE0(uretprobe)
+{
+	struct pt_regs *regs = task_pt_regs(current);
+	unsigned long err, ip, sp, r11_cx_ax[3];
+
+	if (regs->ip != trampoline_check_ip())
+		goto sigill;
+
+	err = copy_from_user(r11_cx_ax, (void __user *)regs->sp, sizeof(r11_cx_ax));
+	if (err)
+		goto sigill;
+
+	/* expose the "right" values of r11/cx/ax/sp to uprobe_consumer/s */
+	regs->r11 = r11_cx_ax[0];
+	regs->cx  = r11_cx_ax[1];
+	regs->ax  = r11_cx_ax[2];
+	regs->sp += sizeof(r11_cx_ax);
+	regs->orig_ax = -1;
+
+	ip = regs->ip;
+	sp = regs->sp;
+
+	uprobe_handle_trampoline(regs);
+
+	/*
+	 * uprobe_consumer has changed sp, we can do nothing,
+	 * just return via iret
+	 */
+	if (regs->sp != sp)
+		return regs->ax;
+	regs->sp -= sizeof(r11_cx_ax);
+
+	/* for the case uprobe_consumer has changed r11/cx */
+	r11_cx_ax[0] = regs->r11;
+	r11_cx_ax[1] = regs->cx;
+
+	/*
+	 * ax register is passed through as return value, so we can use
+	 * its space on stack for ip value and jump to it through the
+	 * trampoline's ret instruction
+	 */
+	r11_cx_ax[2] = regs->ip;
+	regs->ip = ip;
+
+	err = copy_to_user((void __user *)regs->sp, r11_cx_ax, sizeof(r11_cx_ax));
+	if (err)
+		goto sigill;
+
+	/* ensure sysret, see do_syscall_64() */
+	regs->r11 = regs->flags;
+	regs->cx  = regs->ip;
+
+	return regs->ax;
+
+sigill:
+	force_sig(SIGILL);
+	return -1;
+}
+
 /*
  * If arch_uprobe->insn doesn't use rip-relative addressing, return
  * immediately.  Otherwise, rewrite the instruction so that it accesses
diff --git a/include/linux/uprobes.h b/include/linux/uprobes.h
index f46e0ca0169c..b503fafb7fb3 100644
--- a/include/linux/uprobes.h
+++ b/include/linux/uprobes.h
@@ -138,6 +138,9 @@ extern bool arch_uretprobe_is_alive(struct return_instance *ret, enum rp_check c
 extern bool arch_uprobe_ignore(struct arch_uprobe *aup, struct pt_regs *regs);
 extern void arch_uprobe_copy_ixol(struct page *page, unsigned long vaddr,
 					 void *src, unsigned long len);
+extern void uprobe_handle_trampoline(struct pt_regs *regs);
+extern void *arch_uprobe_trampoline(unsigned long *psize);
+extern unsigned long uprobe_get_trampoline_vaddr(void);
 #else /* !CONFIG_UPROBES */
 struct uprobes_state {
 };
diff --git a/kernel/events/uprobes.c b/kernel/events/uprobes.c
index e4834d23e1d1..c550449d66be 100644
--- a/kernel/events/uprobes.c
+++ b/kernel/events/uprobes.c
@@ -1474,11 +1474,20 @@ static int xol_add_vma(struct mm_struct *mm, struct xol_area *area)
 	return ret;
 }
 
+void * __weak arch_uprobe_trampoline(unsigned long *psize)
+{
+	static uprobe_opcode_t insn = UPROBE_SWBP_INSN;
+
+	*psize = UPROBE_SWBP_INSN_SIZE;
+	return &insn;
+}
+
 static struct xol_area *__create_xol_area(unsigned long vaddr)
 {
 	struct mm_struct *mm = current->mm;
-	uprobe_opcode_t insn = UPROBE_SWBP_INSN;
+	unsigned long insns_size;
 	struct xol_area *area;
+	void *insns;
 
 	area = kmalloc(sizeof(*area), GFP_KERNEL);
 	if (unlikely(!area))
@@ -1502,7 +1511,8 @@ static struct xol_area *__create_xol_area(unsigned long vaddr)
 	/* Reserve the 1st slot for get_trampoline_vaddr() */
 	set_bit(0, area->bitmap);
 	atomic_set(&area->slot_count, 1);
-	arch_uprobe_copy_ixol(area->pages[0], 0, &insn, UPROBE_SWBP_INSN_SIZE);
+	insns = arch_uprobe_trampoline(&insns_size);
+	arch_uprobe_copy_ixol(area->pages[0], 0, insns, insns_size);
 
 	if (!xol_add_vma(mm, area))
 		return area;
@@ -1827,7 +1837,7 @@ void uprobe_copy_process(struct task_struct *t, unsigned long flags)
  *
  * Returns -1 in case the xol_area is not allocated.
  */
-static unsigned long get_trampoline_vaddr(void)
+unsigned long uprobe_get_trampoline_vaddr(void)
 {
 	struct xol_area *area;
 	unsigned long trampoline_vaddr = -1;
@@ -1878,7 +1888,7 @@ static void prepare_uretprobe(struct uprobe *uprobe, struct pt_regs *regs)
 	if (!ri)
 		return;
 
-	trampoline_vaddr = get_trampoline_vaddr();
+	trampoline_vaddr = uprobe_get_trampoline_vaddr();
 	orig_ret_vaddr = arch_uretprobe_hijack_return_addr(trampoline_vaddr, regs);
 	if (orig_ret_vaddr == -1)
 		goto fail;
@@ -2123,7 +2133,7 @@ static struct return_instance *find_next_ret_chain(struct return_instance *ri)
 	return ri;
 }
 
-static void handle_trampoline(struct pt_regs *regs)
+void uprobe_handle_trampoline(struct pt_regs *regs)
 {
 	struct uprobe_task *utask;
 	struct return_instance *ri, *next;
@@ -2187,8 +2197,8 @@ static void handle_swbp(struct pt_regs *regs)
 	int is_swbp;
 
 	bp_vaddr = uprobe_get_swbp_addr(regs);
-	if (bp_vaddr == get_trampoline_vaddr())
-		return handle_trampoline(regs);
+	if (bp_vaddr == uprobe_get_trampoline_vaddr())
+		return uprobe_handle_trampoline(regs);
 
 	uprobe = find_active_uprobe(bp_vaddr, &is_swbp);
 	if (!uprobe) {
-- 
2.44.0


^ permalink raw reply related

* [PATCHv3 bpf-next 1/7] uprobe: Wire up uretprobe system call
From: Jiri Olsa @ 2024-04-21 19:42 UTC (permalink / raw)
  To: Steven Rostedt, Masami Hiramatsu, Oleg Nesterov,
	Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko
  Cc: linux-kernel, linux-trace-kernel, linux-api, x86, bpf, Song Liu,
	Yonghong Song, John Fastabend, Peter Zijlstra, Thomas Gleixner,
	Borislav Petkov (AMD), Ingo Molnar, Andy Lutomirski
In-Reply-To: <20240421194206.1010934-1-jolsa@kernel.org>

Wiring up uretprobe system call, which comes in following changes.
We need to do the wiring before, because the uretprobe implementation
needs the syscall number.

Note at the moment uretprobe syscall is supported only for native
64-bit process.

Signed-off-by: Jiri Olsa <jolsa@kernel.org>
---
 arch/x86/entry/syscalls/syscall_64.tbl | 1 +
 include/linux/syscalls.h               | 2 ++
 include/uapi/asm-generic/unistd.h      | 5 ++++-
 kernel/sys_ni.c                        | 2 ++
 4 files changed, 9 insertions(+), 1 deletion(-)

diff --git a/arch/x86/entry/syscalls/syscall_64.tbl b/arch/x86/entry/syscalls/syscall_64.tbl
index 7e8d46f4147f..af0a33ab06ee 100644
--- a/arch/x86/entry/syscalls/syscall_64.tbl
+++ b/arch/x86/entry/syscalls/syscall_64.tbl
@@ -383,6 +383,7 @@
 459	common	lsm_get_self_attr	sys_lsm_get_self_attr
 460	common	lsm_set_self_attr	sys_lsm_set_self_attr
 461	common	lsm_list_modules	sys_lsm_list_modules
+462	64	uretprobe		sys_uretprobe
 
 #
 # Due to a historical design error, certain syscalls are numbered differently
diff --git a/include/linux/syscalls.h b/include/linux/syscalls.h
index e619ac10cd23..5318e0e76799 100644
--- a/include/linux/syscalls.h
+++ b/include/linux/syscalls.h
@@ -972,6 +972,8 @@ asmlinkage long sys_lsm_list_modules(u64 *ids, u32 *size, u32 flags);
 /* x86 */
 asmlinkage long sys_ioperm(unsigned long from, unsigned long num, int on);
 
+asmlinkage long sys_uretprobe(void);
+
 /* pciconfig: alpha, arm, arm64, ia64, sparc */
 asmlinkage long sys_pciconfig_read(unsigned long bus, unsigned long dfn,
 				unsigned long off, unsigned long len,
diff --git a/include/uapi/asm-generic/unistd.h b/include/uapi/asm-generic/unistd.h
index 75f00965ab15..8a747cd1d735 100644
--- a/include/uapi/asm-generic/unistd.h
+++ b/include/uapi/asm-generic/unistd.h
@@ -842,8 +842,11 @@ __SYSCALL(__NR_lsm_set_self_attr, sys_lsm_set_self_attr)
 #define __NR_lsm_list_modules 461
 __SYSCALL(__NR_lsm_list_modules, sys_lsm_list_modules)
 
+#define __NR_uretprobe 462
+__SYSCALL(__NR_uretprobe, sys_uretprobe)
+
 #undef __NR_syscalls
-#define __NR_syscalls 462
+#define __NR_syscalls 463
 
 /*
  * 32 bit systems traditionally used different
diff --git a/kernel/sys_ni.c b/kernel/sys_ni.c
index faad00cce269..be6195e0d078 100644
--- a/kernel/sys_ni.c
+++ b/kernel/sys_ni.c
@@ -391,3 +391,5 @@ COND_SYSCALL(setuid16);
 
 /* restartable sequence */
 COND_SYSCALL(rseq);
+
+COND_SYSCALL(uretprobe);
-- 
2.44.0


^ permalink raw reply related

* [PATCHv3 bpf-next 0/7] uprobe: uretprobe speed up
From: Jiri Olsa @ 2024-04-21 19:41 UTC (permalink / raw)
  To: Steven Rostedt, Masami Hiramatsu, Oleg Nesterov,
	Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko
  Cc: linux-kernel, linux-trace-kernel, linux-api, x86, bpf, Song Liu,
	Yonghong Song, John Fastabend, Peter Zijlstra, Thomas Gleixner,
	Borislav Petkov (AMD), Ingo Molnar, Andy Lutomirski

hi,
as part of the effort on speeding up the uprobes [0] coming with
return uprobe optimization by using syscall instead of the trap
on the uretprobe trampoline.

The speed up depends on instruction type that uprobe is installed
and depends on specific HW type, please check patch 1 for details.

Patches 1-6 are based on bpf-next/master, but path 1 and 2 are
apply-able on linux-trace.git tree probes/for-next branch.
Patch 7 is based on man-pages master.

v3 changes:
  - added source ip check if the uretprobe syscall is called from
    trampoline and sending SIGILL to process if it's not
  - keep x86 compat process to use standard breakpoint
  - split syscall wiring into separate change
  - ran ltp and syzkaller locally, no issues found [Masami]
  - building uprobe_compat binary in selftests which breaks
    CI atm because of missing 32-bit delve packages, I will
    need to fix that in separate changes once this is acked
  - added man page change
  - there were several changes so I removed acks [Oleg Andrii]

Also available at:
  https://git.kernel.org/pub/scm/linux/kernel/git/jolsa/perf.git
  uretprobe_syscall

thanks,
jirka


Notes to check list items in Documentation/process/adding-syscalls.rst:

- System Call Alternatives
  New syscall seems like the best way in here, becase we need
  just to quickly enter kernel with no extra arguments processing,
  which we'd need to do if we decided to use another syscall.

- Designing the API: Planning for Extension
  The uretprobe syscall is very specific and most likely won't be
  extended in the future.

  At the moment it does not take any arguments and even if it does
  in future, it's allowed to be called only from trampoline prepared
  by kernel, so there'll be no broken user.

- Designing the API: Other Considerations
  N/A because uretprobe syscall does not return reference to kernel
  object.

- Proposing the API
  Wiring up of the uretprobe system call si in separate change,
  selftests and man page changes are part of the patchset.

- Generic System Call Implementation
  There's no CONFIG option for the new functionality because it
  keeps the same behaviour from the user POV.

- x86 System Call Implementation
  It's 64-bit syscall only.

- Compatibility System Calls (Generic)
  N/A uretprobe syscall has no arguments and is not supported
  for compat processes.

- Compatibility System Calls (x86)
  N/A uretprobe syscall is not supported for compat processes.

- System Calls Returning Elsewhere
  N/A.

- Other Details
  N/A.

- Testing
  Adding new bpf selftests and ran ltp on top of this change.

- Man Page
  Attached.

- Do not call System Calls in the Kernel
  N/A.


[0] https://lore.kernel.org/bpf/ZeCXHKJ--iYYbmLj@krava/
---
Jiri Olsa (6):
      uprobe: Wire up uretprobe system call
      uprobe: Add uretprobe syscall to speed up return probe
      selftests/bpf: Add uretprobe syscall test for regs integrity
      selftests/bpf: Add uretprobe syscall test for regs changes
      selftests/bpf: Add uretprobe syscall call from user space test
      selftests/bpf: Add uretprobe compat test

 arch/x86/entry/syscalls/syscall_64.tbl                    |   1 +
 arch/x86/kernel/uprobes.c                                 | 115 ++++++++++++++++++++++++++++++
 include/linux/syscalls.h                                  |   2 +
 include/linux/uprobes.h                                   |   3 +
 include/uapi/asm-generic/unistd.h                         |   5 +-
 kernel/events/uprobes.c                                   |  24 +++++--
 kernel/sys_ni.c                                           |   2 +
 tools/include/linux/compiler.h                            |   4 ++
 tools/testing/selftests/bpf/.gitignore                    |   1 +
 tools/testing/selftests/bpf/Makefile                      |   6 +-
 tools/testing/selftests/bpf/bpf_testmod/bpf_testmod.c     | 123 +++++++++++++++++++++++++++++++-
 tools/testing/selftests/bpf/prog_tests/uprobe_syscall.c   | 362 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 tools/testing/selftests/bpf/progs/uprobe_syscall.c        |  15 ++++
 tools/testing/selftests/bpf/progs/uprobe_syscall_call.c   |  15 ++++
 tools/testing/selftests/bpf/progs/uprobe_syscall_compat.c |  13 ++++
 15 files changed, 681 insertions(+), 10 deletions(-)
 create mode 100644 tools/testing/selftests/bpf/prog_tests/uprobe_syscall.c
 create mode 100644 tools/testing/selftests/bpf/progs/uprobe_syscall.c
 create mode 100644 tools/testing/selftests/bpf/progs/uprobe_syscall_call.c
 create mode 100644 tools/testing/selftests/bpf/progs/uprobe_syscall_compat.c


Jiri Olsa (1):
      man2: Add uretprobe syscall page

 man2/uretprobe.2 | 40 ++++++++++++++++++++++++++++++++++++++++
 1 file changed, 40 insertions(+)
 create mode 100644 man2/uretprobe.2

^ permalink raw reply

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

On Friday, 19 April 2024 11:16:11 CDT Peter Zijlstra wrote:
> On Tue, Apr 16, 2024 at 05:18:56PM -0500, Elizabeth Figura wrote:
> > 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.)
> 
> So I've tried using that wine build with lutris, and I can't get it to
> start EGS or anything else.
> 
> I even added a printk to the ntsync driver for every open, to see if it
> gets that far, but I'm not even getting that :/

That's odd, it works for me, both as a standalone build and with
lutris...

Does /dev/ntsync exist (module is loaded) and have nonzero permissions?
I forgot to mention that's necessary, sorry.

Otherwise I can try to look at an strace, or a Wine debug log. I don't
think there's an easy way to get the latter with Lutris, but something
like `WINEDEBUG=+all ./wine winecfg 2>log` should work.



^ permalink raw reply

* Re: [PATCH v4 02/27] ntsync: Introduce NTSYNC_IOC_WAIT_ALL.
From: Peter Zijlstra @ 2024-04-19 16:28 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: <20240418093511.GQ40213@noisy.programming.kicks-ass.net>

On Thu, Apr 18, 2024 at 11:35:11AM +0200, Peter Zijlstra wrote:
> On Wed, Apr 17, 2024 at 03:03:05PM -0500, Elizabeth Figura wrote:
> 
> > Ach. I wrote this with the idea that the race isn't meaningful, but
> > looking at it again you're right—there is a harmful race here.
> > 
> > I think it should be fixable by moving the atomic_read inside the lock,
> > though.
> 
> Right, I've ended up with the (as yet untested) below. I'll see if I can
> find time later to actually test things.

Latest hackery... I tried testing this but I'm not having luck using the
patched wine as per the other email.

---
--- a/drivers/misc/ntsync.c
+++ b/drivers/misc/ntsync.c
@@ -18,6 +18,7 @@
 #include <linux/sched/signal.h>
 #include <linux/slab.h>
 #include <linux/spinlock.h>
+#include <linux/mutex.h>
 #include <uapi/linux/ntsync.h>
 
 #define NTSYNC_NAME	"ntsync"
@@ -43,6 +44,7 @@ enum ntsync_type {
 
 struct ntsync_obj {
 	spinlock_t lock;
+	int dev_locked;
 
 	enum ntsync_type type;
 
@@ -132,14 +134,107 @@ struct ntsync_device {
 	 * wait_all_lock is taken first whenever multiple objects must be locked
 	 * at the same time.
 	 */
-	spinlock_t wait_all_lock;
+	struct mutex wait_all_lock;
 
 	struct file *file;
 };
 
+/*
+ * Single objects are locked using obj->lock.
+ *
+ * Multiple objects are 'locked' while holding dev->wait_all_lock to avoid lock
+ * order issues. In this case however, single objects are not locked by holding
+ * obj->lock, but by setting obj->dev_locked.
+ *
+ * This means that in order to lock a single object, the sequence is slightly
+ * more complicated than usual. Specifically it needs to check obj->dev_locked
+ * after acquiring obj->lock, if set, it needs to drop the lock and acquire
+ * dev->wait_all_lock in order to serialize against the multi-object operation.
+ */
+
+static void dev_lock_obj(struct ntsync_device *dev, struct ntsync_obj *obj)
+{
+	lockdep_assert_held(&dev->wait_all_lock);
+	lockdep_assert(obj->dev == dev);
+	spin_lock(&obj->lock);
+	/*
+	 * By setting obj->dev_locked inside obj->lock, it is ensured that
+	 * anyone holding obj->lock must see the value.
+	 */
+	obj->dev_locked = 1;
+	spin_unlock(&obj->lock);
+}
+
+static void dev_unlock_obj(struct ntsync_device *dev, struct ntsync_obj *obj)
+{
+	lockdep_assert_held(&dev->wait_all_lock);
+	lockdep_assert(obj->dev == dev);
+	spin_lock(&obj->lock);
+	obj->dev_locked = 0;
+	spin_unlock(&obj->lock);
+}
+
+static void obj_lock(struct ntsync_obj *obj)
+{
+	struct ntsync_device *dev = obj->dev;
+
+	for (;;) {
+		spin_lock(&obj->lock);
+		if (likely(!obj->dev_locked))
+			break;
+
+		spin_unlock(&obj->lock);
+		mutex_lock(&dev->wait_all_lock);
+		spin_lock(&obj->lock);
+		/*
+		 * obj->dev_locked should be set and released under the same
+		 * wait_all_lock section, since we now own this lock, it should
+		 * be clear.
+		 */
+		lockdep_assert(!obj->dev_locked);
+		spin_unlock(&obj->lock);
+		mutex_unlock(&dev->wait_all_lock);
+	}
+}
+
+static void obj_unlock(struct ntsync_obj *obj)
+{
+	spin_unlock(&obj->lock);
+}
+
+static bool ntsync_lock_obj(struct ntsync_device *dev, struct ntsync_obj *obj)
+{
+	bool all;
+
+	obj_lock(obj);
+	all = atomic_read(&obj->all_hint);
+	if (unlikely(all)) {
+		obj_unlock(obj);
+		mutex_lock(&dev->wait_all_lock);
+		dev_lock_obj(dev, obj);
+	}
+
+	return all;
+}
+
+static void ntsync_unlock_obj(struct ntsync_device *dev, struct ntsync_obj *obj, bool all)
+{
+	if (all) {
+		dev_unlock_obj(dev, obj);
+		mutex_unlock(&dev->wait_all_lock);
+	} else {
+		obj_unlock(obj);
+	}
+}
+
+#define ntsync_assert_held(obj) \
+	lockdep_assert((lockdep_is_held(&(obj)->lock) != LOCK_STATE_NOT_HELD) || \
+		       ((lockdep_is_held(&(obj)->dev->wait_all_lock) != LOCK_STATE_NOT_HELD) && \
+			(obj)->dev_locked))
+
 static bool is_signaled(struct ntsync_obj *obj, __u32 owner)
 {
-	lockdep_assert_held(&obj->lock);
+	ntsync_assert_held(obj);
 
 	switch (obj->type) {
 	case NTSYNC_TYPE_SEM:
@@ -171,11 +266,11 @@ static void try_wake_all(struct ntsync_d
 
 	lockdep_assert_held(&dev->wait_all_lock);
 	if (locked_obj)
-		lockdep_assert_held(&locked_obj->lock);
+		lockdep_assert(locked_obj->dev_locked);
 
 	for (i = 0; i < count; i++) {
 		if (q->entries[i].obj != locked_obj)
-			spin_lock_nest_lock(&q->entries[i].obj->lock, &dev->wait_all_lock);
+			dev_lock_obj(dev, q->entries[i].obj);
 	}
 
 	for (i = 0; i < count; i++) {
@@ -211,7 +306,7 @@ static void try_wake_all(struct ntsync_d
 
 	for (i = 0; i < count; i++) {
 		if (q->entries[i].obj != locked_obj)
-			spin_unlock(&q->entries[i].obj->lock);
+			dev_unlock_obj(dev, q->entries[i].obj);
 	}
 }
 
@@ -220,7 +315,7 @@ static void try_wake_all_obj(struct ntsy
 	struct ntsync_q_entry *entry;
 
 	lockdep_assert_held(&dev->wait_all_lock);
-	lockdep_assert_held(&obj->lock);
+	lockdep_assert(obj->dev_locked);
 
 	list_for_each_entry(entry, &obj->all_waiters, node)
 		try_wake_all(dev, entry->q, obj);
@@ -230,7 +325,8 @@ static void try_wake_any_sem(struct ntsy
 {
 	struct ntsync_q_entry *entry;
 
-	lockdep_assert_held(&sem->lock);
+	ntsync_assert_held(sem);
+	lockdep_assert(sem->type == NTSYNC_TYPE_SEM);
 
 	list_for_each_entry(entry, &sem->any_waiters, node) {
 		struct ntsync_q *q = entry->q;
@@ -250,7 +346,8 @@ static void try_wake_any_mutex(struct nt
 {
 	struct ntsync_q_entry *entry;
 
-	lockdep_assert_held(&mutex->lock);
+	ntsync_assert_held(mutex);
+	lockdep_assert(mutex->type == NTSYNC_TYPE_MUTEX);
 
 	list_for_each_entry(entry, &mutex->any_waiters, node) {
 		struct ntsync_q *q = entry->q;
@@ -276,7 +373,8 @@ static void try_wake_any_event(struct nt
 {
 	struct ntsync_q_entry *entry;
 
-	lockdep_assert_held(&event->lock);
+	ntsync_assert_held(event);
+	lockdep_assert(event->type == NTSYNC_TYPE_EVENT);
 
 	list_for_each_entry(entry, &event->any_waiters, node) {
 		struct ntsync_q *q = entry->q;
@@ -302,6 +400,7 @@ static int post_sem_state(struct ntsync_
 	__u32 sum;
 
 	lockdep_assert_held(&sem->lock);
+	lockdep_assert(sem->type == NTSYNC_TYPE_SEM);
 
 	if (check_add_overflow(sem->u.sem.count, count, &sum) ||
 	    sum > sem->u.sem.max)
@@ -317,6 +416,7 @@ static int ntsync_sem_post(struct ntsync
 	__u32 __user *user_args = argp;
 	__u32 prev_count;
 	__u32 args;
+	bool all;
 	int ret;
 
 	if (copy_from_user(&args, argp, sizeof(args)))
@@ -325,30 +425,18 @@ static int ntsync_sem_post(struct ntsync
 	if (sem->type != NTSYNC_TYPE_SEM)
 		return -EINVAL;
 
-	if (atomic_read(&sem->all_hint) > 0) {
-		spin_lock(&dev->wait_all_lock);
-		spin_lock_nest_lock(&sem->lock, &dev->wait_all_lock);
-
-		prev_count = sem->u.sem.count;
-		ret = post_sem_state(sem, args);
-		if (!ret) {
-			try_wake_all_obj(dev, sem);
-			try_wake_any_sem(sem);
-		}
-
-		spin_unlock(&sem->lock);
-		spin_unlock(&dev->wait_all_lock);
-	} else {
-		spin_lock(&sem->lock);
+	all = ntsync_lock_obj(dev, sem);
 
-		prev_count = sem->u.sem.count;
-		ret = post_sem_state(sem, args);
-		if (!ret)
-			try_wake_any_sem(sem);
-
-		spin_unlock(&sem->lock);
+	prev_count = sem->u.sem.count;
+	ret = post_sem_state(sem, args);
+	if (!ret) {
+		if (all)
+			try_wake_all_obj(dev, sem);
+		try_wake_any_sem(sem);
 	}
 
+	ntsync_unlock_obj(dev, sem, all);
+
 	if (!ret && put_user(prev_count, user_args))
 		ret = -EFAULT;
 
@@ -377,6 +465,7 @@ static int ntsync_mutex_unlock(struct nt
 	struct ntsync_device *dev = mutex->dev;
 	struct ntsync_mutex_args args;
 	__u32 prev_count;
+	bool all;
 	int ret;
 
 	if (copy_from_user(&args, argp, sizeof(args)))
@@ -387,30 +476,18 @@ static int ntsync_mutex_unlock(struct nt
 	if (mutex->type != NTSYNC_TYPE_MUTEX)
 		return -EINVAL;
 
-	if (atomic_read(&mutex->all_hint) > 0) {
-		spin_lock(&dev->wait_all_lock);
-		spin_lock_nest_lock(&mutex->lock, &dev->wait_all_lock);
-
-		prev_count = mutex->u.mutex.count;
-		ret = unlock_mutex_state(mutex, &args);
-		if (!ret) {
-			try_wake_all_obj(dev, mutex);
-			try_wake_any_mutex(mutex);
-		}
+	all = ntsync_lock_obj(dev, mutex);
 
-		spin_unlock(&mutex->lock);
-		spin_unlock(&dev->wait_all_lock);
-	} else {
-		spin_lock(&mutex->lock);
-
-		prev_count = mutex->u.mutex.count;
-		ret = unlock_mutex_state(mutex, &args);
-		if (!ret)
-			try_wake_any_mutex(mutex);
-
-		spin_unlock(&mutex->lock);
+	prev_count = mutex->u.mutex.count;
+	ret = unlock_mutex_state(mutex, &args);
+	if (!ret) {
+		if (all)
+			try_wake_all_obj(dev, mutex);
+		try_wake_any_mutex(mutex);
 	}
 
+	ntsync_unlock_obj(dev, mutex, all);
+
 	if (!ret && put_user(prev_count, &user_args->count))
 		ret = -EFAULT;
 
@@ -438,6 +515,7 @@ static int ntsync_mutex_kill(struct ntsy
 {
 	struct ntsync_device *dev = mutex->dev;
 	__u32 owner;
+	bool all;
 	int ret;
 
 	if (get_user(owner, (__u32 __user *)argp))
@@ -448,28 +526,17 @@ static int ntsync_mutex_kill(struct ntsy
 	if (mutex->type != NTSYNC_TYPE_MUTEX)
 		return -EINVAL;
 
-	if (atomic_read(&mutex->all_hint) > 0) {
-		spin_lock(&dev->wait_all_lock);
-		spin_lock_nest_lock(&mutex->lock, &dev->wait_all_lock);
+	all = ntsync_lock_obj(dev, mutex);
 
-		ret = kill_mutex_state(mutex, owner);
-		if (!ret) {
+	ret = kill_mutex_state(mutex, owner);
+	if (!ret) {
+		if (all)
 			try_wake_all_obj(dev, mutex);
-			try_wake_any_mutex(mutex);
-		}
-
-		spin_unlock(&mutex->lock);
-		spin_unlock(&dev->wait_all_lock);
-	} else {
-		spin_lock(&mutex->lock);
-
-		ret = kill_mutex_state(mutex, owner);
-		if (!ret)
-			try_wake_any_mutex(mutex);
-
-		spin_unlock(&mutex->lock);
+		try_wake_any_mutex(mutex);
 	}
 
+	ntsync_unlock_obj(dev, mutex, all);
+
 	return ret;
 }
 
@@ -477,34 +544,22 @@ static int ntsync_event_set(struct ntsyn
 {
 	struct ntsync_device *dev = event->dev;
 	__u32 prev_state;
+	bool all;
 
 	if (event->type != NTSYNC_TYPE_EVENT)
 		return -EINVAL;
 
-	if (atomic_read(&event->all_hint) > 0) {
-		spin_lock(&dev->wait_all_lock);
-		spin_lock_nest_lock(&event->lock, &dev->wait_all_lock);
+	all = ntsync_lock_obj(dev, event);
 
-		prev_state = event->u.event.signaled;
-		event->u.event.signaled = true;
+	prev_state = event->u.event.signaled;
+	event->u.event.signaled = true;
+	if (all)
 		try_wake_all_obj(dev, event);
-		try_wake_any_event(event);
-		if (pulse)
-			event->u.event.signaled = false;
+	try_wake_any_event(event);
+	if (pulse)
+		event->u.event.signaled = false;
 
-		spin_unlock(&event->lock);
-		spin_unlock(&dev->wait_all_lock);
-	} else {
-		spin_lock(&event->lock);
-
-		prev_state = event->u.event.signaled;
-		event->u.event.signaled = true;
-		try_wake_any_event(event);
-		if (pulse)
-			event->u.event.signaled = false;
-
-		spin_unlock(&event->lock);
-	}
+	ntsync_unlock_obj(dev, event, all);
 
 	if (put_user(prev_state, (__u32 __user *)argp))
 		return -EFAULT;
@@ -984,7 +1039,7 @@ static int ntsync_wait_all(struct ntsync
 
 	/* queue ourselves */
 
-	spin_lock(&dev->wait_all_lock);
+	mutex_lock(&dev->wait_all_lock);
 
 	for (i = 0; i < args.count; i++) {
 		struct ntsync_q_entry *entry = &q->entries[i];
@@ -1004,7 +1059,7 @@ static int ntsync_wait_all(struct ntsync
 
 	try_wake_all(dev, q, NULL);
 
-	spin_unlock(&dev->wait_all_lock);
+	mutex_unlock(&dev->wait_all_lock);
 
 	/* sleep */
 
@@ -1012,7 +1067,7 @@ static int ntsync_wait_all(struct ntsync
 
 	/* and finally, unqueue */
 
-	spin_lock(&dev->wait_all_lock);
+	mutex_lock(&dev->wait_all_lock);
 
 	for (i = 0; i < args.count; i++) {
 		struct ntsync_q_entry *entry = &q->entries[i];
@@ -1029,7 +1084,7 @@ static int ntsync_wait_all(struct ntsync
 		put_obj(obj);
 	}
 
-	spin_unlock(&dev->wait_all_lock);
+	mutex_unlock(&dev->wait_all_lock);
 
 	signaled = atomic_read(&q->signaled);
 	if (signaled != -1) {
@@ -1056,7 +1111,7 @@ static int ntsync_char_open(struct inode
 	if (!dev)
 		return -ENOMEM;
 
-	spin_lock_init(&dev->wait_all_lock);
+	mutex_init(&dev->wait_all_lock);
 
 	file->private_data = dev;
 	dev->file = file;

^ permalink raw reply

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

On Tue, Apr 16, 2024 at 05:18:56PM -0500, Elizabeth Figura wrote:
> 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.)

So I've tried using that wine build with lutris, and I can't get it to
start EGS or anything else.

I even added a printk to the ntsync driver for every open, to see if it
gets that far, but I'm not even getting that :/


^ permalink raw reply

* Re: [PATCH v5 2/3] VT: Add KDFONTINFO ioctl
From: Alexey Gladkov @ 2024-04-18 10:45 UTC (permalink / raw)
  To: Helge Deller
  Cc: Greg Kroah-Hartman, Jiri Slaby, LKML, kbd, linux-api, linux-fbdev,
	linux-serial
In-Reply-To: <9019dc74-35ad-43d7-8763-cea3da93e9c1@gmx.de>

On Wed, Apr 17, 2024 at 09:31:53PM +0200, Helge Deller wrote:
> On 4/17/24 19:37, Alexey Gladkov wrote:
> > Each driver has its own restrictions on font size. There is currently no
> > way to understand what the requirements are. The new ioctl allows
> > userspace to get the minimum and maximum font size values.
> >
> > Acked-by: Helge Deller <deller@gmx.de>
> > Signed-off-by: Alexey Gladkov <legion@kernel.org>
> > ---
> >   drivers/tty/vt/vt.c       | 24 ++++++++++++++++++++++++
> >   drivers/tty/vt/vt_ioctl.c | 13 +++++++++++++
> >   include/linux/console.h   |  3 +++
> >   include/linux/vt_kern.h   |  1 +
> >   include/uapi/linux/kd.h   | 14 ++++++++++++++
> >   5 files changed, 55 insertions(+)
> >
> > diff --git a/drivers/tty/vt/vt.c b/drivers/tty/vt/vt.c
> > index 9b5b98dfc8b4..e8db0e9ea674 100644
> > --- a/drivers/tty/vt/vt.c
> > +++ b/drivers/tty/vt/vt.c
> > @@ -4851,6 +4851,30 @@ int con_font_op(struct vc_data *vc, struct console_font_op *op)
> >   	return -ENOSYS;
> >   }
> >
> > +int con_font_info(struct vc_data *vc, struct console_font_info *info)
> > +{
> > +	int rc;
> > +
> > +	info->min_height = 0;
> > +	info->max_height = max_font_height;
> > +
> > +	info->min_width = 0;
> > +	info->max_width = max_font_width;
> > +
> > +	info->flags = KD_FONT_INFO_FLAG_LOW_SIZE | KD_FONT_INFO_FLAG_HIGH_SIZE;
> > +
> > +	console_lock();
> > +	if (vc->vc_mode != KD_TEXT)
> > +		rc = -EINVAL;
> > +	else if (vc->vc_sw->con_font_info)
> > +		rc = vc->vc_sw->con_font_info(vc, info);
> > +	else
> > +		rc = -ENOSYS;
> > +	console_unlock();
> > +
> > +	return rc;
> > +}
> > +
> >   /*
> >    *	Interface exported to selection and vcs.
> >    */
> > diff --git a/drivers/tty/vt/vt_ioctl.c b/drivers/tty/vt/vt_ioctl.c
> > index 4b91072f3a4e..9a2f8081f650 100644
> > --- a/drivers/tty/vt/vt_ioctl.c
> > +++ b/drivers/tty/vt/vt_ioctl.c
> > @@ -479,6 +479,19 @@ static int vt_k_ioctl(struct tty_struct *tty, unsigned int cmd,
> >   		break;
> >   	}
> >
> > +	case KDFONTINFO: {
> > +		struct console_font_info fnt_info;
> > +
> > +		memset(&fnt_info, 0, sizeof(fnt_info));
> > +
> > +		ret = con_font_info(vc, &fnt_info);
> > +		if (ret)
> > +			return ret;
> > +		if (copy_to_user(up, &fnt_info, sizeof(fnt_info)))
> > +			return -EFAULT;
> > +		break;
> > +	}
> > +
> >   	default:
> >   		return -ENOIOCTLCMD;
> >   	}
> > diff --git a/include/linux/console.h b/include/linux/console.h
> > index 31a8f5b85f5d..4b798322aa01 100644
> > --- a/include/linux/console.h
> > +++ b/include/linux/console.h
> > @@ -21,6 +21,7 @@
> >   #include <linux/vesa.h>
> >
> >   struct vc_data;
> > +struct console_font_info;
> >   struct console_font_op;
> >   struct console_font;
> >   struct module;
> > @@ -102,6 +103,8 @@ struct consw {
> >   	bool	(*con_switch)(struct vc_data *vc);
> >   	bool	(*con_blank)(struct vc_data *vc, enum vesa_blank_mode blank,
> >   			     bool mode_switch);
> > +	int	(*con_font_info)(struct vc_data *vc,
> > +				 struct console_font_info *info);
> >   	int	(*con_font_set)(struct vc_data *vc,
> >   				const struct console_font *font,
> >   				unsigned int vpitch, unsigned int flags);
> > diff --git a/include/linux/vt_kern.h b/include/linux/vt_kern.h
> > index d008c3d0a9bb..383b3a4f6113 100644
> > --- a/include/linux/vt_kern.h
> > +++ b/include/linux/vt_kern.h
> > @@ -33,6 +33,7 @@ void do_blank_screen(int entering_gfx);
> >   void do_unblank_screen(int leaving_gfx);
> >   void poke_blanked_console(void);
> >   int con_font_op(struct vc_data *vc, struct console_font_op *op);
> > +int con_font_info(struct vc_data *vc, struct console_font_info *info);
> >   int con_set_cmap(unsigned char __user *cmap);
> >   int con_get_cmap(unsigned char __user *cmap);
> >   void scrollback(struct vc_data *vc);
> > diff --git a/include/uapi/linux/kd.h b/include/uapi/linux/kd.h
> > index 8ddb2219a84b..68b715ad4d5c 100644
> > --- a/include/uapi/linux/kd.h
> > +++ b/include/uapi/linux/kd.h
> > @@ -185,6 +185,20 @@ struct console_font {
> >
> >   #define KD_FONT_FLAG_DONT_RECALC 	1	/* Don't recalculate hw charcell size [compat] */
> >
> > +/* font information */
> > +
> > +#define KD_FONT_INFO_FLAG_LOW_SIZE	_BITUL(0) /* 256 */
> > +#define KD_FONT_INFO_FLAG_HIGH_SIZE	_BITUL(1) /* 512 */
> 
> Do we really need those bits?
> You set a default min/max font size in con_font_info() above,
> and all drivers can override those values.
> So, there are always min/max sizes available.

These bits are not about the minimum and maximum glyph size, but about the
number of glyphs in the font.

Maybe this is an overkill, but sticon has this check:

if ((w < 6) || (h < 6) || (w > 32) || (h > 32) || (vpitch != 32)
    || (op->charcount != 256 && op->charcount != 512))

[ to be honest, I don’t know why this driver doesn’t accept a glyph of
width 4 ]

I thought it would be worth fixing the maximum number of requirements in
the drivers since I started adding a new ioctl.

> > +struct console_font_info {
> > +	__u32  flags;			/* KD_FONT_INFO_FLAG_* */
> 
> One space too much in front of "flags" ?

No problem. I will fix.

> 
> > +	__u32 min_width, min_height;	/* minimal font size */
> > +	__u32 max_width, max_height;	/* maximum font size */
> > +	__u32 reserved[5];		/* This field is reserved for future use. Must be 0. */
> > +};
> > +
> > +#define KDFONTINFO	_IOR(KD_IOCTL_BASE, 0x73, struct console_font_info)
> > +
> >   /* note: 0x4B00-0x4B4E all have had a value at some time;
> >      don't reuse for the time being */
> >   /* note: 0x4B60-0x4B6D, 0x4B70-0x4B72 used above */
> 

-- 
Rgrds, legion


^ permalink raw reply

* Re: [PATCH v5 2/3] VT: Add KDFONTINFO ioctl
From: Alexey Gladkov @ 2024-04-18 10:27 UTC (permalink / raw)
  To: Greg Kroah-Hartman
  Cc: Jiri Slaby, LKML, kbd, linux-api, linux-fbdev, linux-serial,
	Helge Deller
In-Reply-To: <2024041830-feisty-gristle-5fd0@gregkh>

On Thu, Apr 18, 2024 at 08:18:33AM +0200, Greg Kroah-Hartman wrote:
> On Wed, Apr 17, 2024 at 07:37:36PM +0200, Alexey Gladkov wrote:
> > Each driver has its own restrictions on font size. There is currently no
> > way to understand what the requirements are. The new ioctl allows
> > userspace to get the minimum and maximum font size values.
> 
> Is there any userspace code that uses this yet that we can point to
> here?

Yes. I have a code that uses this. It waits for this ioctl to appear in
the kernel.

https://git.kernel.org/pub/scm/linux/kernel/git/legion/kbd.git/commit/?h=kdfontinfo-v1&id=e2ad0117ca8e46cedd8668934db7b04e9054d5d7

> I know tty ioctls are woefully undocumented, but could there be some
> documentation here?

Yes, this is a big problem for this interface. The ioctl_console(2)
describes PIO_FONT/PIO_FONTX, which is no longer supported, but does not
describe KDFONTOP at all, which is exactly used by userspace.

My TODO has a task to fix this.

But I would suggest creating documentation in the kernel because life
shows that man-page is far behind what is implemented.

> > 
> > Acked-by: Helge Deller <deller@gmx.de>
> > Signed-off-by: Alexey Gladkov <legion@kernel.org>
> > ---
> >  drivers/tty/vt/vt.c       | 24 ++++++++++++++++++++++++
> >  drivers/tty/vt/vt_ioctl.c | 13 +++++++++++++
> >  include/linux/console.h   |  3 +++
> >  include/linux/vt_kern.h   |  1 +
> >  include/uapi/linux/kd.h   | 14 ++++++++++++++
> >  5 files changed, 55 insertions(+)
> > 
> > diff --git a/drivers/tty/vt/vt.c b/drivers/tty/vt/vt.c
> > index 9b5b98dfc8b4..e8db0e9ea674 100644
> > --- a/drivers/tty/vt/vt.c
> > +++ b/drivers/tty/vt/vt.c
> > @@ -4851,6 +4851,30 @@ int con_font_op(struct vc_data *vc, struct console_font_op *op)
> >  	return -ENOSYS;
> >  }
> >  
> > +int con_font_info(struct vc_data *vc, struct console_font_info *info)
> > +{
> > +	int rc;
> > +
> > +	info->min_height = 0;
> > +	info->max_height = max_font_height;
> > +
> > +	info->min_width = 0;
> > +	info->max_width = max_font_width;
> > +
> > +	info->flags = KD_FONT_INFO_FLAG_LOW_SIZE | KD_FONT_INFO_FLAG_HIGH_SIZE;
> > +
> > +	console_lock();
> > +	if (vc->vc_mode != KD_TEXT)
> > +		rc = -EINVAL;
> > +	else if (vc->vc_sw->con_font_info)
> > +		rc = vc->vc_sw->con_font_info(vc, info);
> > +	else
> > +		rc = -ENOSYS;
> > +	console_unlock();
> > +
> > +	return rc;
> > +}
> > +
> >  /*
> >   *	Interface exported to selection and vcs.
> >   */
> > diff --git a/drivers/tty/vt/vt_ioctl.c b/drivers/tty/vt/vt_ioctl.c
> > index 4b91072f3a4e..9a2f8081f650 100644
> > --- a/drivers/tty/vt/vt_ioctl.c
> > +++ b/drivers/tty/vt/vt_ioctl.c
> > @@ -479,6 +479,19 @@ static int vt_k_ioctl(struct tty_struct *tty, unsigned int cmd,
> >  		break;
> >  	}
> >  
> > +	case KDFONTINFO: {
> > +		struct console_font_info fnt_info;
> > +
> > +		memset(&fnt_info, 0, sizeof(fnt_info));
> > +
> > +		ret = con_font_info(vc, &fnt_info);
> 
> Shouldn't con_font_info() memset it first?  No need to do it in the
> caller.
> 
> > +		if (ret)
> > +			return ret;
> > +		if (copy_to_user(up, &fnt_info, sizeof(fnt_info)))
> > +			return -EFAULT;
> > +		break;
> > +	}
> > +
> >  	default:
> >  		return -ENOIOCTLCMD;
> >  	}
> > diff --git a/include/linux/console.h b/include/linux/console.h
> > index 31a8f5b85f5d..4b798322aa01 100644
> > --- a/include/linux/console.h
> > +++ b/include/linux/console.h
> > @@ -21,6 +21,7 @@
> >  #include <linux/vesa.h>
> >  
> >  struct vc_data;
> > +struct console_font_info;
> >  struct console_font_op;
> >  struct console_font;
> >  struct module;
> > @@ -102,6 +103,8 @@ struct consw {
> >  	bool	(*con_switch)(struct vc_data *vc);
> >  	bool	(*con_blank)(struct vc_data *vc, enum vesa_blank_mode blank,
> >  			     bool mode_switch);
> > +	int	(*con_font_info)(struct vc_data *vc,
> > +				 struct console_font_info *info);
> 
> To make the names more obvious, how about:
> 	con_font_info_get()?
> 
> >  	int	(*con_font_set)(struct vc_data *vc,
> >  				const struct console_font *font,
> >  				unsigned int vpitch, unsigned int flags);
> > diff --git a/include/linux/vt_kern.h b/include/linux/vt_kern.h
> > index d008c3d0a9bb..383b3a4f6113 100644
> > --- a/include/linux/vt_kern.h
> > +++ b/include/linux/vt_kern.h
> > @@ -33,6 +33,7 @@ void do_blank_screen(int entering_gfx);
> >  void do_unblank_screen(int leaving_gfx);
> >  void poke_blanked_console(void);
> >  int con_font_op(struct vc_data *vc, struct console_font_op *op);
> > +int con_font_info(struct vc_data *vc, struct console_font_info *info);
> >  int con_set_cmap(unsigned char __user *cmap);
> >  int con_get_cmap(unsigned char __user *cmap);
> >  void scrollback(struct vc_data *vc);
> > diff --git a/include/uapi/linux/kd.h b/include/uapi/linux/kd.h
> > index 8ddb2219a84b..68b715ad4d5c 100644
> > --- a/include/uapi/linux/kd.h
> > +++ b/include/uapi/linux/kd.h
> > @@ -185,6 +185,20 @@ struct console_font {
> >  
> >  #define KD_FONT_FLAG_DONT_RECALC 	1	/* Don't recalculate hw charcell size [compat] */
> >  
> > +/* font information */
> > +
> > +#define KD_FONT_INFO_FLAG_LOW_SIZE	_BITUL(0) /* 256 */
> > +#define KD_FONT_INFO_FLAG_HIGH_SIZE	_BITUL(1) /* 512 */
> 
> I don't understand why bit 0 and bit 1 have those comments after them.
> That's confusing (i.e. bit 0 is NOT 256...)
> 
> > +
> > +struct console_font_info {
> > +	__u32  flags;			/* KD_FONT_INFO_FLAG_* */
> 
> Why are there flags if you are only setting these 2 values?  What are
> the flags for?
> 
> If this is going to be a "multiplexed" type of structure, then make it a
> union?  Or maybe we are totally over thinking this whole thing.
> 
> All you want is the min/max font size of the console, right?  So perhaps
> the whole structure is just:
> 
> > +	__u32 min_width, min_height;	/* minimal font size */
> > +	__u32 max_width, max_height;	/* maximum font size */
> 
> Those 4 variables?  Why have anything else here at all?  For any new
> thing you wish to discover, have it be a new ioctl?
> 
> > +	__u32 reserved[5];		/* This field is reserved for future use. Must be 0. */
> 
> I understand the "must be 0" but this is a read-only structure, so
> saying "it will be set to 0" might be better?"  Or something like that?
> 
> > +};
> > +
> > +#define KDFONTINFO	_IOR(KD_IOCTL_BASE, 0x73, struct console_font_info)
> 
> As mentioned above how about KDFONTINFOGET?
> 
> thanks,
> 
> greg k-h
> 

-- 
Rgrds, legion


^ permalink raw reply

* Re: [PATCH v4 02/27] ntsync: Introduce NTSYNC_IOC_WAIT_ALL.
From: Peter Zijlstra @ 2024-04-18  9:35 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: <3479054.QJadu78ljV@camazotz>

On Wed, Apr 17, 2024 at 03:03:05PM -0500, Elizabeth Figura wrote:

> Ach. I wrote this with the idea that the race isn't meaningful, but
> looking at it again you're right—there is a harmful race here.
> 
> I think it should be fixable by moving the atomic_read inside the lock,
> though.

Right, I've ended up with the (as yet untested) below. I'll see if I can
find time later to actually test things.

---

--- a/drivers/misc/ntsync.c
+++ b/drivers/misc/ntsync.c
@@ -18,6 +18,7 @@
 #include <linux/sched/signal.h>
 #include <linux/slab.h>
 #include <linux/spinlock.h>
+#include <linux/mutex.h>
 #include <uapi/linux/ntsync.h>
 
 #define NTSYNC_NAME	"ntsync"
@@ -43,6 +44,7 @@ enum ntsync_type {
 
 struct ntsync_obj {
 	spinlock_t lock;
+	int dev_locked;
 
 	enum ntsync_type type;
 
@@ -132,7 +134,7 @@ struct ntsync_device {
 	 * wait_all_lock is taken first whenever multiple objects must be locked
 	 * at the same time.
 	 */
-	spinlock_t wait_all_lock;
+	struct mutex wait_all_lock;
 
 	struct file *file;
 };
@@ -157,6 +159,56 @@ static bool is_signaled(struct ntsync_ob
 }
 
 /*
+ * XXX write coherent comment on the locking
+ */
+
+static void dev_lock_obj(struct ntsync_device *dev, struct ntsync_obj *obj)
+{
+	lockdep_assert_held(&dev->wait_all_lock);
+	WARN_ON_ONCE(obj->dev != dev);
+	spin_lock(&obj->lock);
+	obj->dev_locked = 1;
+	spin_unlock(&obj->lock);
+}
+
+static void dev_unlock_obj(struct ntsync_device *dev, struct ntsync_obj *obj)
+{
+	lockdep_assert_held(&dev->wait_all_lock);
+	WARN_ON_ONCE(obj->dev != dev);
+	spin_lock(&obj->lock);
+	obj->dev_locked = 0;
+	spin_unlock(&obj->lock);
+}
+
+static void obj_lock(struct ntsync_obj *obj)
+{
+	struct ntsync_device *dev = obj->dev;
+
+	for (;;) {
+		spin_lock(&obj->lock);
+		if (likely(!obj->dev_locked))
+			break;
+
+		spin_unlock(&obj->lock);
+		mutex_lock(&dev->wait_all_lock);
+		spin_lock(&obj->lock);
+		/*
+		 * obj->dev_locked should be set and released under the same
+		 * wait_all_lock section, since we now own this lock, it should
+		 * be clear.
+		 */
+		WARN_ON_ONCE(obj->dev_locked);
+		spin_unlock(&obj->lock);
+		mutex_unlock(&dev->wait_all_lock);
+	}
+}
+
+static void obj_unlock(struct ntsync_obj *obj)
+{
+	spin_unlock(&obj->lock);
+}
+
+/*
  * "locked_obj" is an optional pointer to an object which is already locked and
  * should not be locked again. This is necessary so that changing an object's
  * state and waking it can be a single atomic operation.
@@ -175,7 +227,7 @@ static void try_wake_all(struct ntsync_d
 
 	for (i = 0; i < count; i++) {
 		if (q->entries[i].obj != locked_obj)
-			spin_lock_nest_lock(&q->entries[i].obj->lock, &dev->wait_all_lock);
+			dev_lock_obj(dev, q->entries[i].obj);
 	}
 
 	for (i = 0; i < count; i++) {
@@ -211,7 +263,7 @@ static void try_wake_all(struct ntsync_d
 
 	for (i = 0; i < count; i++) {
 		if (q->entries[i].obj != locked_obj)
-			spin_unlock(&q->entries[i].obj->lock);
+			dev_unlock_obj(dev, q->entries[i].obj);
 	}
 }
 
@@ -231,6 +283,7 @@ static void try_wake_any_sem(struct ntsy
 	struct ntsync_q_entry *entry;
 
 	lockdep_assert_held(&sem->lock);
+	WARN_ON_ONCE(sem->type != NTSYNC_TYPE_SEM);
 
 	list_for_each_entry(entry, &sem->any_waiters, node) {
 		struct ntsync_q *q = entry->q;
@@ -251,6 +304,7 @@ static void try_wake_any_mutex(struct nt
 	struct ntsync_q_entry *entry;
 
 	lockdep_assert_held(&mutex->lock);
+	WARN_ON_ONCE(mutex->type != NTSYNC_TYPE_MUTEX);
 
 	list_for_each_entry(entry, &mutex->any_waiters, node) {
 		struct ntsync_q *q = entry->q;
@@ -302,6 +356,7 @@ static int post_sem_state(struct ntsync_
 	__u32 sum;
 
 	lockdep_assert_held(&sem->lock);
+	WARN_ON_ONCE(sem->type != NTSYNC_TYPE_SEM);
 
 	if (check_add_overflow(sem->u.sem.count, count, &sum) ||
 	    sum > sem->u.sem.max)
@@ -316,6 +371,7 @@ static int ntsync_sem_post(struct ntsync
 	struct ntsync_device *dev = sem->dev;
 	__u32 __user *user_args = argp;
 	__u32 prev_count;
+	bool all = false;
 	__u32 args;
 	int ret;
 
@@ -325,28 +381,27 @@ static int ntsync_sem_post(struct ntsync
 	if (sem->type != NTSYNC_TYPE_SEM)
 		return -EINVAL;
 
-	if (atomic_read(&sem->all_hint) > 0) {
-		spin_lock(&dev->wait_all_lock);
-		spin_lock_nest_lock(&sem->lock, &dev->wait_all_lock);
-
-		prev_count = sem->u.sem.count;
-		ret = post_sem_state(sem, args);
-		if (!ret) {
+	obj_lock(sem);
+	all = atomic_read(&sem->all_hint);
+	if (unlikely(all)) {
+		obj_unlock(sem);
+		mutex_lock(&dev->wait_all_lock);
+		dev_lock_obj(dev, sem);
+	}
+
+	prev_count = sem->u.sem.count;
+	ret = post_sem_state(sem, args);
+	if (!ret) {
+		if (all)
 			try_wake_all_obj(dev, sem);
-			try_wake_any_sem(sem);
-		}
+		try_wake_any_sem(sem);
+	}
 
-		spin_unlock(&sem->lock);
-		spin_unlock(&dev->wait_all_lock);
+	if (all) {
+		dev_unlock_obj(dev, sem);
+		mutex_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);
+		obj_unlock(sem);
 	}
 
 	if (!ret && put_user(prev_count, user_args))
@@ -376,6 +431,7 @@ static int ntsync_mutex_unlock(struct nt
 	struct ntsync_mutex_args __user *user_args = argp;
 	struct ntsync_device *dev = mutex->dev;
 	struct ntsync_mutex_args args;
+	bool all = false;
 	__u32 prev_count;
 	int ret;
 
@@ -387,28 +443,27 @@ static int ntsync_mutex_unlock(struct nt
 	if (mutex->type != NTSYNC_TYPE_MUTEX)
 		return -EINVAL;
 
-	if (atomic_read(&mutex->all_hint) > 0) {
-		spin_lock(&dev->wait_all_lock);
-		spin_lock_nest_lock(&mutex->lock, &dev->wait_all_lock);
-
-		prev_count = mutex->u.mutex.count;
-		ret = unlock_mutex_state(mutex, &args);
-		if (!ret) {
+	obj_lock(mutex);
+	all = atomic_read(&mutex->all_hint);
+	if (unlikely(all)) {
+		obj_unlock(mutex);
+		mutex_lock(&dev->wait_all_lock);
+		dev_lock_obj(dev, mutex);
+	}
+
+	prev_count = mutex->u.mutex.count;
+	ret = unlock_mutex_state(mutex, &args);
+	if (!ret) {
+		if (all)
 			try_wake_all_obj(dev, mutex);
-			try_wake_any_mutex(mutex);
-		}
+		try_wake_any_mutex(mutex);
+	}
 
-		spin_unlock(&mutex->lock);
-		spin_unlock(&dev->wait_all_lock);
+	if (all) {
+		dev_unlock_obj(dev, mutex);
+		mutex_unlock(&dev->wait_all_lock);
 	} else {
-		spin_lock(&mutex->lock);
-
-		prev_count = mutex->u.mutex.count;
-		ret = unlock_mutex_state(mutex, &args);
-		if (!ret)
-			try_wake_any_mutex(mutex);
-
-		spin_unlock(&mutex->lock);
+		obj_unlock(mutex);
 	}
 
 	if (!ret && put_user(prev_count, &user_args->count))
@@ -437,6 +492,7 @@ static int kill_mutex_state(struct ntsyn
 static int ntsync_mutex_kill(struct ntsync_obj *mutex, void __user *argp)
 {
 	struct ntsync_device *dev = mutex->dev;
+	bool all = false;
 	__u32 owner;
 	int ret;
 
@@ -448,26 +504,26 @@ static int ntsync_mutex_kill(struct ntsy
 	if (mutex->type != NTSYNC_TYPE_MUTEX)
 		return -EINVAL;
 
-	if (atomic_read(&mutex->all_hint) > 0) {
-		spin_lock(&dev->wait_all_lock);
-		spin_lock_nest_lock(&mutex->lock, &dev->wait_all_lock);
+	obj_lock(mutex);
+	all = atomic_read(&mutex->all_hint);
+	if (unlikely(all)) {
+		obj_unlock(mutex);
+		mutex_lock(&dev->wait_all_lock);
+		dev_lock_obj(dev, mutex);
+	}
 
-		ret = kill_mutex_state(mutex, owner);
-		if (!ret) {
+	ret = kill_mutex_state(mutex, owner);
+	if (!ret) {
+		if (all)
 			try_wake_all_obj(dev, mutex);
-			try_wake_any_mutex(mutex);
-		}
+		try_wake_any_mutex(mutex);
+	}
 
-		spin_unlock(&mutex->lock);
-		spin_unlock(&dev->wait_all_lock);
+	if (all) {
+		dev_unlock_obj(dev, mutex);
+		mutex_unlock(&dev->wait_all_lock);
 	} else {
-		spin_lock(&mutex->lock);
-
-		ret = kill_mutex_state(mutex, owner);
-		if (!ret)
-			try_wake_any_mutex(mutex);
-
-		spin_unlock(&mutex->lock);
+		obj_unlock(mutex);
 	}
 
 	return ret;
@@ -477,35 +533,35 @@ static int ntsync_event_set(struct ntsyn
 {
 	struct ntsync_device *dev = event->dev;
 	__u32 prev_state;
+	bool all = false;
 
 	if (event->type != NTSYNC_TYPE_EVENT)
 		return -EINVAL;
 
-	if (atomic_read(&event->all_hint) > 0) {
-		spin_lock(&dev->wait_all_lock);
-		spin_lock_nest_lock(&event->lock, &dev->wait_all_lock);
+	obj_lock(event);
+	all = atomic_read(&event->all_hint);
+	if (unlikely(all)) {
+		obj_unlock(event);
+		mutex_lock(&dev->wait_all_lock);
+		dev_lock_obj(dev, event);
+	}
 
-		prev_state = event->u.event.signaled;
-		event->u.event.signaled = true;
+	prev_state = event->u.event.signaled;
+	event->u.event.signaled = true;
+	if (all)
 		try_wake_all_obj(dev, event);
-		try_wake_any_event(event);
-		if (pulse)
-			event->u.event.signaled = false;
-
-		spin_unlock(&event->lock);
-		spin_unlock(&dev->wait_all_lock);
+	try_wake_any_event(event);
+	if (pulse)
+		event->u.event.signaled = false;
+
+	if (all) {
+		dev_unlock_obj(dev, event);
+		mutex_unlock(&dev->wait_all_lock);
 	} else {
-		spin_lock(&event->lock);
-
-		prev_state = event->u.event.signaled;
-		event->u.event.signaled = true;
-		try_wake_any_event(event);
-		if (pulse)
-			event->u.event.signaled = false;
-
-		spin_unlock(&event->lock);
+		obj_unlock(event);
 	}
 
+
 	if (put_user(prev_state, (__u32 __user *)argp))
 		return -EFAULT;
 
@@ -984,7 +1040,7 @@ static int ntsync_wait_all(struct ntsync
 
 	/* queue ourselves */
 
-	spin_lock(&dev->wait_all_lock);
+	mutex_lock(&dev->wait_all_lock);
 
 	for (i = 0; i < args.count; i++) {
 		struct ntsync_q_entry *entry = &q->entries[i];
@@ -1004,7 +1060,7 @@ static int ntsync_wait_all(struct ntsync
 
 	try_wake_all(dev, q, NULL);
 
-	spin_unlock(&dev->wait_all_lock);
+	mutex_unlock(&dev->wait_all_lock);
 
 	/* sleep */
 
@@ -1012,7 +1068,7 @@ static int ntsync_wait_all(struct ntsync
 
 	/* and finally, unqueue */
 
-	spin_lock(&dev->wait_all_lock);
+	mutex_lock(&dev->wait_all_lock);
 
 	for (i = 0; i < args.count; i++) {
 		struct ntsync_q_entry *entry = &q->entries[i];
@@ -1029,7 +1085,7 @@ static int ntsync_wait_all(struct ntsync
 		put_obj(obj);
 	}
 
-	spin_unlock(&dev->wait_all_lock);
+	mutex_unlock(&dev->wait_all_lock);
 
 	signaled = atomic_read(&q->signaled);
 	if (signaled != -1) {
@@ -1056,7 +1112,7 @@ static int ntsync_char_open(struct inode
 	if (!dev)
 		return -ENOMEM;
 
-	spin_lock_init(&dev->wait_all_lock);
+	mutex_init(&dev->wait_all_lock);
 
 	file->private_data = dev;
 	dev->file = file;

^ permalink raw reply

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

On Wed, Apr 17, 2024 at 07:37:35PM +0200, Alexey Gladkov wrote:
> 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(-)

This is a nice cleanup, thanks for doing it, I'll just take this one
change now if you don't object.

thanks,

greg k-h

^ permalink raw reply

* Re: [PATCH v5 2/3] VT: Add KDFONTINFO ioctl
From: Greg Kroah-Hartman @ 2024-04-18  6:18 UTC (permalink / raw)
  To: Alexey Gladkov
  Cc: Jiri Slaby, LKML, kbd, linux-api, linux-fbdev, linux-serial,
	Helge Deller
In-Reply-To: <bd2755e7b6ebe9b49e82d04d9908a176e0fe2f15.1713375378.git.legion@kernel.org>

On Wed, Apr 17, 2024 at 07:37:36PM +0200, Alexey Gladkov wrote:
> Each driver has its own restrictions on font size. There is currently no
> way to understand what the requirements are. The new ioctl allows
> userspace to get the minimum and maximum font size values.

Is there any userspace code that uses this yet that we can point to
here?

I know tty ioctls are woefully undocumented, but could there be some
documentation here?

> 
> Acked-by: Helge Deller <deller@gmx.de>
> Signed-off-by: Alexey Gladkov <legion@kernel.org>
> ---
>  drivers/tty/vt/vt.c       | 24 ++++++++++++++++++++++++
>  drivers/tty/vt/vt_ioctl.c | 13 +++++++++++++
>  include/linux/console.h   |  3 +++
>  include/linux/vt_kern.h   |  1 +
>  include/uapi/linux/kd.h   | 14 ++++++++++++++
>  5 files changed, 55 insertions(+)
> 
> diff --git a/drivers/tty/vt/vt.c b/drivers/tty/vt/vt.c
> index 9b5b98dfc8b4..e8db0e9ea674 100644
> --- a/drivers/tty/vt/vt.c
> +++ b/drivers/tty/vt/vt.c
> @@ -4851,6 +4851,30 @@ int con_font_op(struct vc_data *vc, struct console_font_op *op)
>  	return -ENOSYS;
>  }
>  
> +int con_font_info(struct vc_data *vc, struct console_font_info *info)
> +{
> +	int rc;
> +
> +	info->min_height = 0;
> +	info->max_height = max_font_height;
> +
> +	info->min_width = 0;
> +	info->max_width = max_font_width;
> +
> +	info->flags = KD_FONT_INFO_FLAG_LOW_SIZE | KD_FONT_INFO_FLAG_HIGH_SIZE;
> +
> +	console_lock();
> +	if (vc->vc_mode != KD_TEXT)
> +		rc = -EINVAL;
> +	else if (vc->vc_sw->con_font_info)
> +		rc = vc->vc_sw->con_font_info(vc, info);
> +	else
> +		rc = -ENOSYS;
> +	console_unlock();
> +
> +	return rc;
> +}
> +
>  /*
>   *	Interface exported to selection and vcs.
>   */
> diff --git a/drivers/tty/vt/vt_ioctl.c b/drivers/tty/vt/vt_ioctl.c
> index 4b91072f3a4e..9a2f8081f650 100644
> --- a/drivers/tty/vt/vt_ioctl.c
> +++ b/drivers/tty/vt/vt_ioctl.c
> @@ -479,6 +479,19 @@ static int vt_k_ioctl(struct tty_struct *tty, unsigned int cmd,
>  		break;
>  	}
>  
> +	case KDFONTINFO: {
> +		struct console_font_info fnt_info;
> +
> +		memset(&fnt_info, 0, sizeof(fnt_info));
> +
> +		ret = con_font_info(vc, &fnt_info);

Shouldn't con_font_info() memset it first?  No need to do it in the
caller.

> +		if (ret)
> +			return ret;
> +		if (copy_to_user(up, &fnt_info, sizeof(fnt_info)))
> +			return -EFAULT;
> +		break;
> +	}
> +
>  	default:
>  		return -ENOIOCTLCMD;
>  	}
> diff --git a/include/linux/console.h b/include/linux/console.h
> index 31a8f5b85f5d..4b798322aa01 100644
> --- a/include/linux/console.h
> +++ b/include/linux/console.h
> @@ -21,6 +21,7 @@
>  #include <linux/vesa.h>
>  
>  struct vc_data;
> +struct console_font_info;
>  struct console_font_op;
>  struct console_font;
>  struct module;
> @@ -102,6 +103,8 @@ struct consw {
>  	bool	(*con_switch)(struct vc_data *vc);
>  	bool	(*con_blank)(struct vc_data *vc, enum vesa_blank_mode blank,
>  			     bool mode_switch);
> +	int	(*con_font_info)(struct vc_data *vc,
> +				 struct console_font_info *info);

To make the names more obvious, how about:
	con_font_info_get()?

>  	int	(*con_font_set)(struct vc_data *vc,
>  				const struct console_font *font,
>  				unsigned int vpitch, unsigned int flags);
> diff --git a/include/linux/vt_kern.h b/include/linux/vt_kern.h
> index d008c3d0a9bb..383b3a4f6113 100644
> --- a/include/linux/vt_kern.h
> +++ b/include/linux/vt_kern.h
> @@ -33,6 +33,7 @@ void do_blank_screen(int entering_gfx);
>  void do_unblank_screen(int leaving_gfx);
>  void poke_blanked_console(void);
>  int con_font_op(struct vc_data *vc, struct console_font_op *op);
> +int con_font_info(struct vc_data *vc, struct console_font_info *info);
>  int con_set_cmap(unsigned char __user *cmap);
>  int con_get_cmap(unsigned char __user *cmap);
>  void scrollback(struct vc_data *vc);
> diff --git a/include/uapi/linux/kd.h b/include/uapi/linux/kd.h
> index 8ddb2219a84b..68b715ad4d5c 100644
> --- a/include/uapi/linux/kd.h
> +++ b/include/uapi/linux/kd.h
> @@ -185,6 +185,20 @@ struct console_font {
>  
>  #define KD_FONT_FLAG_DONT_RECALC 	1	/* Don't recalculate hw charcell size [compat] */
>  
> +/* font information */
> +
> +#define KD_FONT_INFO_FLAG_LOW_SIZE	_BITUL(0) /* 256 */
> +#define KD_FONT_INFO_FLAG_HIGH_SIZE	_BITUL(1) /* 512 */

I don't understand why bit 0 and bit 1 have those comments after them.
That's confusing (i.e. bit 0 is NOT 256...)

> +
> +struct console_font_info {
> +	__u32  flags;			/* KD_FONT_INFO_FLAG_* */

Why are there flags if you are only setting these 2 values?  What are
the flags for?

If this is going to be a "multiplexed" type of structure, then make it a
union?  Or maybe we are totally over thinking this whole thing.

All you want is the min/max font size of the console, right?  So perhaps
the whole structure is just:

> +	__u32 min_width, min_height;	/* minimal font size */
> +	__u32 max_width, max_height;	/* maximum font size */

Those 4 variables?  Why have anything else here at all?  For any new
thing you wish to discover, have it be a new ioctl?

> +	__u32 reserved[5];		/* This field is reserved for future use. Must be 0. */

I understand the "must be 0" but this is a read-only structure, so
saying "it will be set to 0" might be better?"  Or something like that?

> +};
> +
> +#define KDFONTINFO	_IOR(KD_IOCTL_BASE, 0x73, struct console_font_info)

As mentioned above how about KDFONTINFOGET?

thanks,

greg k-h

^ permalink raw reply

* Re: [PATCH v4 02/27] ntsync: Introduce NTSYNC_IOC_WAIT_ALL.
From: Elizabeth Figura @ 2024-04-17 20:03 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: <20240417113703.GL30852@noisy.programming.kicks-ass.net>

On Wednesday, 17 April 2024 06:37:03 CDT Peter Zijlstra wrote:
> 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.

Ach. I wrote this with the idea that the race isn't meaningful, but
looking at it again you're right—there is a harmful race here.

I think it should be fixable by moving the atomic_read inside the lock,
though.



^ permalink raw reply

* Re: [PATCH v4 00/30] NT synchronization primitive driver
From: Elizabeth Figura @ 2024-04-17 20:02 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: <20240417100132.GK30852@noisy.programming.kicks-ass.net>

On Wednesday, 17 April 2024 05:01:32 CDT Peter Zijlstra wrote:
> > 
> > ===================================
> > 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.

Uncontended, yes, or needs-wakeup (I'm not sure what the difference
between the two is?)

> > 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.

I mostly figured it'd be simplest to leave the driver completely
agnostic, but I don't think there's any reason we can't use the real
TID for most calls.

> > 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.

It's what I'm trying to define there, a single bit of state that's
either contended or not. It acts broadly like an eventfd, in that you
can wake (write) or wait (read), but without the distinction of having
different nonzero values in the internal counter.

You could also think of it as a semaphore with a maximum count of one.
However, unlike a semaphore it also supports the "pulse" operation, and
you can also have "manual-reset" events that *don't* change state when
you wait on them (no equivalent for regular semaphores).

> > 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.

I think that would have been fine, and I could still change it
accordingly. The reason I didn't do that was that [1] advises against
it (although I don't know why).

[1] https://docs.kernel.org/driver-api/ioctl.html#return-code

> > 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.

No, NT calls it "release" (and the operation on a mutex is also
"release" rather than "unlock".) I called it "post" after POSIX
semaphores, on the idea that it'd be more familiar to a Unix developer
(and shorter). I see I was wrong, so I'll rename it to "release".

> > 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?

Right. We could replace owner with current everywhere except for 
NTSYNC_IOC_KILL_OWNER.

> >   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?

Yes, exactly.

> > 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*

Yep :D

This one is terrible, and it's the only one that Microsoft has come out
and explicitly said "don't use this". Supposedly their kernel is even
coded such that if a waiting thread gets hit by an interrupt that the
pulse will go unnoticed, although I've tried to reproduce this in
practice and been unsuccessful.

But of course it's terrible regardless, because you never know if your
thread is waiting or not. In practice it seems to usually be used on a
timer, though, so that part doesn't matter as much.

> > 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.

I have no idea what it's intended for. Actually it's not even exposed
as a documented API, only an undocumented one. But it does work, and
applications use it.

> > 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.

Unfortunately yes.

> > 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.

Yes, mostly so I could reuse the existing structures.

On the other hand if there's no reason not to return fds from the
create ioctls, then we could just remove those members.

> > 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.

Exactly.

> > 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?

I don't think there's any particular reason.

> >      * 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.

Yeah. Again the advice about "only return zero from an ioctl", too.

Although we could also use a bit in the return value (which is also
kind of what NT does).

> >   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.

There's no documented guarantee in either case, but when testing in
controlled well-ordered environments, NtWaitForMultipleObjects() always
acquires the lowest index first, and I think wakes are FIFO. I'm not
really sure why I specified the guarantee for the former but not the
latter.

> >   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.

It's definitely legal in NT to pass the same object more than once (in
wait-for-any, not wait-for-all though), and it's definitely the case
that (at least in controlled well-ordered environments) the lowest
index is acquired first. I don't know of any application that
definitely depends on either of these, and they're not documented
behaviours, but Wine has implemented those behaviours and it would make
me nervous to break them here.

The part about passing the same event in "alert" and "objs" is not part
of NT exactly (in NT the "alert" isn't even an event; it's a special
bit of thread state; we just use an event for simplicity). I think I
specified it just to avoid coding an extra check (since it should Just
Work), while also making it clear that case was considered.

> >   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?

Even disregarding wait-for-all, futexes aren't really good enough. Wait
operations need to consume state, and while we could put that state in
user space (and in fact we *do* have an out of tree patch set that kind
of does this) that requires all that state to be shared across
processes, which is a problem since we want processes to be isolated
unless they explicitly share objects with each other. There's not
a scalable way to achieve this, especially since you can share objects
lazily.

You also cannot do NtPulseEvent() this way. The aforementioned patch
set badly emulates it and it does in practice break any application
that uses it. Similarly there are some applications that do a weird
"fake pulse" where they set and then immediately reset an event from
the same thread, and expect that to always wake.



^ permalink raw reply

* Re: [PATCH v5 2/3] VT: Add KDFONTINFO ioctl
From: Helge Deller @ 2024-04-17 19:31 UTC (permalink / raw)
  To: Alexey Gladkov, Greg Kroah-Hartman, Jiri Slaby
  Cc: LKML, kbd, linux-api, linux-fbdev, linux-serial
In-Reply-To: <bd2755e7b6ebe9b49e82d04d9908a176e0fe2f15.1713375378.git.legion@kernel.org>

On 4/17/24 19:37, Alexey Gladkov wrote:
> Each driver has its own restrictions on font size. There is currently no
> way to understand what the requirements are. The new ioctl allows
> userspace to get the minimum and maximum font size values.
>
> Acked-by: Helge Deller <deller@gmx.de>
> Signed-off-by: Alexey Gladkov <legion@kernel.org>
> ---
>   drivers/tty/vt/vt.c       | 24 ++++++++++++++++++++++++
>   drivers/tty/vt/vt_ioctl.c | 13 +++++++++++++
>   include/linux/console.h   |  3 +++
>   include/linux/vt_kern.h   |  1 +
>   include/uapi/linux/kd.h   | 14 ++++++++++++++
>   5 files changed, 55 insertions(+)
>
> diff --git a/drivers/tty/vt/vt.c b/drivers/tty/vt/vt.c
> index 9b5b98dfc8b4..e8db0e9ea674 100644
> --- a/drivers/tty/vt/vt.c
> +++ b/drivers/tty/vt/vt.c
> @@ -4851,6 +4851,30 @@ int con_font_op(struct vc_data *vc, struct console_font_op *op)
>   	return -ENOSYS;
>   }
>
> +int con_font_info(struct vc_data *vc, struct console_font_info *info)
> +{
> +	int rc;
> +
> +	info->min_height = 0;
> +	info->max_height = max_font_height;
> +
> +	info->min_width = 0;
> +	info->max_width = max_font_width;
> +
> +	info->flags = KD_FONT_INFO_FLAG_LOW_SIZE | KD_FONT_INFO_FLAG_HIGH_SIZE;
> +
> +	console_lock();
> +	if (vc->vc_mode != KD_TEXT)
> +		rc = -EINVAL;
> +	else if (vc->vc_sw->con_font_info)
> +		rc = vc->vc_sw->con_font_info(vc, info);
> +	else
> +		rc = -ENOSYS;
> +	console_unlock();
> +
> +	return rc;
> +}
> +
>   /*
>    *	Interface exported to selection and vcs.
>    */
> diff --git a/drivers/tty/vt/vt_ioctl.c b/drivers/tty/vt/vt_ioctl.c
> index 4b91072f3a4e..9a2f8081f650 100644
> --- a/drivers/tty/vt/vt_ioctl.c
> +++ b/drivers/tty/vt/vt_ioctl.c
> @@ -479,6 +479,19 @@ static int vt_k_ioctl(struct tty_struct *tty, unsigned int cmd,
>   		break;
>   	}
>
> +	case KDFONTINFO: {
> +		struct console_font_info fnt_info;
> +
> +		memset(&fnt_info, 0, sizeof(fnt_info));
> +
> +		ret = con_font_info(vc, &fnt_info);
> +		if (ret)
> +			return ret;
> +		if (copy_to_user(up, &fnt_info, sizeof(fnt_info)))
> +			return -EFAULT;
> +		break;
> +	}
> +
>   	default:
>   		return -ENOIOCTLCMD;
>   	}
> diff --git a/include/linux/console.h b/include/linux/console.h
> index 31a8f5b85f5d..4b798322aa01 100644
> --- a/include/linux/console.h
> +++ b/include/linux/console.h
> @@ -21,6 +21,7 @@
>   #include <linux/vesa.h>
>
>   struct vc_data;
> +struct console_font_info;
>   struct console_font_op;
>   struct console_font;
>   struct module;
> @@ -102,6 +103,8 @@ struct consw {
>   	bool	(*con_switch)(struct vc_data *vc);
>   	bool	(*con_blank)(struct vc_data *vc, enum vesa_blank_mode blank,
>   			     bool mode_switch);
> +	int	(*con_font_info)(struct vc_data *vc,
> +				 struct console_font_info *info);
>   	int	(*con_font_set)(struct vc_data *vc,
>   				const struct console_font *font,
>   				unsigned int vpitch, unsigned int flags);
> diff --git a/include/linux/vt_kern.h b/include/linux/vt_kern.h
> index d008c3d0a9bb..383b3a4f6113 100644
> --- a/include/linux/vt_kern.h
> +++ b/include/linux/vt_kern.h
> @@ -33,6 +33,7 @@ void do_blank_screen(int entering_gfx);
>   void do_unblank_screen(int leaving_gfx);
>   void poke_blanked_console(void);
>   int con_font_op(struct vc_data *vc, struct console_font_op *op);
> +int con_font_info(struct vc_data *vc, struct console_font_info *info);
>   int con_set_cmap(unsigned char __user *cmap);
>   int con_get_cmap(unsigned char __user *cmap);
>   void scrollback(struct vc_data *vc);
> diff --git a/include/uapi/linux/kd.h b/include/uapi/linux/kd.h
> index 8ddb2219a84b..68b715ad4d5c 100644
> --- a/include/uapi/linux/kd.h
> +++ b/include/uapi/linux/kd.h
> @@ -185,6 +185,20 @@ struct console_font {
>
>   #define KD_FONT_FLAG_DONT_RECALC 	1	/* Don't recalculate hw charcell size [compat] */
>
> +/* font information */
> +
> +#define KD_FONT_INFO_FLAG_LOW_SIZE	_BITUL(0) /* 256 */
> +#define KD_FONT_INFO_FLAG_HIGH_SIZE	_BITUL(1) /* 512 */

Do we really need those bits?
You set a default min/max font size in con_font_info() above,
and all drivers can override those values.
So, there are always min/max sizes available.

> +struct console_font_info {
> +	__u32  flags;			/* KD_FONT_INFO_FLAG_* */

One space too much in front of "flags" ?

> +	__u32 min_width, min_height;	/* minimal font size */
> +	__u32 max_width, max_height;	/* maximum font size */
> +	__u32 reserved[5];		/* This field is reserved for future use. Must be 0. */
> +};
> +
> +#define KDFONTINFO	_IOR(KD_IOCTL_BASE, 0x73, struct console_font_info)
> +
>   /* note: 0x4B00-0x4B4E all have had a value at some time;
>      don't reuse for the time being */
>   /* note: 0x4B60-0x4B6D, 0x4B70-0x4B72 used above */


^ permalink raw reply

* [PATCH v5 3/3] VT: Allow to get max font width and height
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, Helge Deller
In-Reply-To: <cover.1713375378.git.legion@kernel.org>

The Console drivers has more restrictive font size limits than vt_ioctl.
This leads to errors that are difficult to handle. If a font whose size
is not supported is used, an EINVAL error will be returned, which is
also returned in case of errors in the font itself. At the moment there
is no way to understand what font sizes the current console driver
supports.

To solve this problem, we need to transfer information about the
supported font to userspace from the console driver.

Acked-by: Helge Deller <deller@gmx.de>
Signed-off-by: Alexey Gladkov <legion@kernel.org>
---
 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 ++++++++++++++++
 4 files changed, 76 insertions(+), 7 deletions(-)

diff --git a/drivers/video/console/newport_con.c b/drivers/video/console/newport_con.c
index a51cfc1d560e..6167f45326ac 100644
--- a/drivers/video/console/newport_con.c
+++ b/drivers/video/console/newport_con.c
@@ -33,6 +33,9 @@
 
 #define NEWPORT_LEN	0x10000
 
+#define NEWPORT_MAX_FONT_WIDTH 8
+#define NEWPORT_MAX_FONT_HEIGHT 16
+
 #define FONT_DATA ((unsigned char *)font_vga_8x16.data)
 
 static unsigned char *font_data[MAX_NR_CONSOLES];
@@ -328,8 +331,8 @@ static void newport_init(struct vc_data *vc, bool init)
 {
 	int cols, rows;
 
-	cols = newport_xsize / 8;
-	rows = newport_ysize / 16;
+	cols = newport_xsize / NEWPORT_MAX_FONT_WIDTH;
+	rows = newport_ysize / NEWPORT_MAX_FONT_HEIGHT;
 	vc->vc_can_do_color = 1;
 	if (init) {
 		vc->vc_cols = cols;
@@ -507,8 +510,8 @@ static int newport_set_font(int unit, const struct console_font *op,
 
 	/* ladis: when I grow up, there will be a day... and more sizes will
 	 * be supported ;-) */
-	if ((w != 8) || (h != 16) || (vpitch != 32)
-	    || (op->charcount != 256 && op->charcount != 512))
+	if ((w != NEWPORT_MAX_FONT_WIDTH) || (h != NEWPORT_MAX_FONT_HEIGHT) ||
+	    (vpitch != 32) || (op->charcount != 256 && op->charcount != 512))
 		return -EINVAL;
 
 	if (!(new_data = kmalloc(FONT_EXTRA_WORDS * sizeof(int) + size,
@@ -570,6 +573,15 @@ static int newport_font_default(struct vc_data *vc, struct console_font *op,
 	return newport_set_def_font(vc->vc_num, op);
 }
 
+static int newport_font_info(struct vc_data *vc, struct console_font_info *info)
+{
+	info->min_width = info->max_width = NEWPORT_MAX_FONT_WIDTH;
+	info->min_height = info->max_height = NEWPORT_MAX_FONT_HEIGHT;
+	info->flags = KD_FONT_INFO_FLAG_LOW_SIZE | KD_FONT_INFO_FLAG_HIGH_SIZE;
+
+	return 0;
+}
+
 static int newport_font_set(struct vc_data *vc, const struct console_font *font,
 			    unsigned int vpitch, unsigned int flags)
 {
@@ -689,6 +701,7 @@ const struct consw newport_con = {
 	.con_scroll	  = newport_scroll,
 	.con_switch	  = newport_switch,
 	.con_blank	  = newport_blank,
+	.con_font_info	  = newport_font_info,
 	.con_font_set	  = newport_font_set,
 	.con_font_default = newport_font_default,
 	.con_save_screen  = newport_save_screen
diff --git a/drivers/video/console/sticon.c b/drivers/video/console/sticon.c
index 4c7b4959a1aa..490e6b266a31 100644
--- a/drivers/video/console/sticon.c
+++ b/drivers/video/console/sticon.c
@@ -56,6 +56,11 @@
 #define BLANK 0
 static int vga_is_gfx;
 
+#define STICON_MIN_FONT_WIDTH 6
+#define STICON_MIN_FONT_HEIGHT 6
+#define STICON_MAX_FONT_WIDTH 32
+#define STICON_MAX_FONT_HEIGHT 32
+
 #define STI_DEF_FONT	sticon_sti->font
 
 /* borrowed from fbcon.c */
@@ -166,8 +171,10 @@ static int sticon_set_font(struct vc_data *vc, const struct console_font *op,
 	struct sti_cooked_font *cooked_font;
 	unsigned char *data = op->data, *p;
 
-	if ((w < 6) || (h < 6) || (w > 32) || (h > 32) || (vpitch != 32)
-	    || (op->charcount != 256 && op->charcount != 512))
+	if (!in_range(w, STICON_MIN_FONT_WIDTH, STICON_MAX_FONT_WIDTH) ||
+	    !in_range(h, STICON_MIN_FONT_HEIGHT, STICON_MAX_FONT_HEIGHT) ||
+	    (vpitch != 32) ||
+	    (op->charcount != 256 && op->charcount != 512))
 		return -EINVAL;
 	pitch = ALIGN(w, 8) / 8;
 	bpc = pitch * h;
@@ -260,6 +267,19 @@ static int sticon_font_set(struct vc_data *vc, const struct console_font *font,
 	return sticon_set_font(vc, font, vpitch);
 }
 
+static int sticon_font_info(struct vc_data *vc, struct console_font_info *info)
+{
+	info->min_width = STICON_MIN_FONT_WIDTH;
+	info->min_height = STICON_MIN_FONT_HEIGHT;
+
+	info->max_width = STICON_MAX_FONT_WIDTH;
+	info->max_height = STICON_MAX_FONT_HEIGHT;
+
+	info->flags = KD_FONT_INFO_FLAG_LOW_SIZE | KD_FONT_INFO_FLAG_HIGH_SIZE;
+
+	return 0;
+}
+
 static void sticon_init(struct vc_data *c, bool init)
 {
     struct sti_struct *sti = sticon_sti;
@@ -356,6 +376,7 @@ static const struct consw sti_con = {
 	.con_scroll		= sticon_scroll,
 	.con_switch		= sticon_switch,
 	.con_blank		= sticon_blank,
+	.con_font_info		= sticon_font_info,
 	.con_font_set		= sticon_font_set,
 	.con_font_default	= sticon_font_default,
 	.con_build_attr		= sticon_build_attr,
diff --git a/drivers/video/console/vgacon.c b/drivers/video/console/vgacon.c
index 7597f04b0dc7..b5465e555fdc 100644
--- a/drivers/video/console/vgacon.c
+++ b/drivers/video/console/vgacon.c
@@ -61,6 +61,10 @@ static struct vgastate vgastate;
 #define BLANK 0x0020
 
 #define VGA_FONTWIDTH       8   /* VGA does not support fontwidths != 8 */
+
+#define VGACON_MAX_FONT_WIDTH VGA_FONTWIDTH
+#define VGACON_MAX_FONT_HEIGHT 32
+
 /*
  *  Interface used by the world
  */
@@ -1039,6 +1043,19 @@ static int vgacon_adjust_height(struct vc_data *vc, unsigned fontheight)
 	return 0;
 }
 
+static int vgacon_font_info(struct vc_data *vc, struct console_font_info *info)
+{
+	info->min_width = VGACON_MAX_FONT_WIDTH;
+	info->min_height = 0;
+
+	info->max_width = VGACON_MAX_FONT_WIDTH;
+	info->max_height = VGACON_MAX_FONT_HEIGHT;
+
+	info->flags = KD_FONT_INFO_FLAG_LOW_SIZE | KD_FONT_INFO_FLAG_HIGH_SIZE;
+
+	return 0;
+}
+
 static int vgacon_font_set(struct vc_data *c, const struct console_font *font,
 			   unsigned int vpitch, unsigned int flags)
 {
@@ -1048,7 +1065,8 @@ static int vgacon_font_set(struct vc_data *c, const struct console_font *font,
 	if (vga_video_type < VIDEO_TYPE_EGAM)
 		return -EINVAL;
 
-	if (font->width != VGA_FONTWIDTH || font->height > 32 || vpitch != 32 ||
+	if (font->width != VGACON_MAX_FONT_WIDTH ||
+	    font->height > VGACON_MAX_FONT_HEIGHT || vpitch != 32 ||
 	    (charcount != 256 && charcount != 512))
 		return -EINVAL;
 
@@ -1201,6 +1219,7 @@ const struct consw vga_con = {
 	.con_scroll = vgacon_scroll,
 	.con_switch = vgacon_switch,
 	.con_blank = vgacon_blank,
+	.con_font_info = vgacon_font_info,
 	.con_font_set = vgacon_font_set,
 	.con_font_get = vgacon_font_get,
 	.con_resize = vgacon_resize,
diff --git a/drivers/video/fbdev/core/fbcon.c b/drivers/video/fbdev/core/fbcon.c
index fcabc668e9fb..b54031da49fd 100644
--- a/drivers/video/fbdev/core/fbcon.c
+++ b/drivers/video/fbdev/core/fbcon.c
@@ -2452,6 +2452,21 @@ static int fbcon_do_set_font(struct vc_data *vc, int w, int h, int charcount,
 	return ret;
 }
 
+
+static int fbcon_font_info(struct vc_data *vc, struct console_font_info *info)
+{
+	info->min_width = 0;
+	info->min_height = 0;
+
+	info->max_width = FB_MAX_BLIT_WIDTH;
+	info->max_height = FB_MAX_BLIT_HEIGHT;
+
+	info->flags = KD_FONT_INFO_FLAG_LOW_SIZE | KD_FONT_INFO_FLAG_HIGH_SIZE;
+
+	return 0;
+}
+
+
 /*
  *  User asked to set font; we are guaranteed that charcount does not exceed 512
  *  but lets not assume that, since charcount of 512 is small for unicode support.
@@ -3127,6 +3142,7 @@ static const struct consw fb_con = {
 	.con_scroll 		= fbcon_scroll,
 	.con_switch 		= fbcon_switch,
 	.con_blank 		= fbcon_blank,
+	.con_font_info		= fbcon_font_info,
 	.con_font_set 		= fbcon_set_font,
 	.con_font_get 		= fbcon_get_font,
 	.con_font_default	= fbcon_set_def_font,
-- 
2.44.0


^ permalink raw reply related

* [PATCH v5 2/3] VT: Add KDFONTINFO ioctl
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, Helge Deller
In-Reply-To: <cover.1713375378.git.legion@kernel.org>

Each driver has its own restrictions on font size. There is currently no
way to understand what the requirements are. The new ioctl allows
userspace to get the minimum and maximum font size values.

Acked-by: Helge Deller <deller@gmx.de>
Signed-off-by: Alexey Gladkov <legion@kernel.org>
---
 drivers/tty/vt/vt.c       | 24 ++++++++++++++++++++++++
 drivers/tty/vt/vt_ioctl.c | 13 +++++++++++++
 include/linux/console.h   |  3 +++
 include/linux/vt_kern.h   |  1 +
 include/uapi/linux/kd.h   | 14 ++++++++++++++
 5 files changed, 55 insertions(+)

diff --git a/drivers/tty/vt/vt.c b/drivers/tty/vt/vt.c
index 9b5b98dfc8b4..e8db0e9ea674 100644
--- a/drivers/tty/vt/vt.c
+++ b/drivers/tty/vt/vt.c
@@ -4851,6 +4851,30 @@ int con_font_op(struct vc_data *vc, struct console_font_op *op)
 	return -ENOSYS;
 }
 
+int con_font_info(struct vc_data *vc, struct console_font_info *info)
+{
+	int rc;
+
+	info->min_height = 0;
+	info->max_height = max_font_height;
+
+	info->min_width = 0;
+	info->max_width = max_font_width;
+
+	info->flags = KD_FONT_INFO_FLAG_LOW_SIZE | KD_FONT_INFO_FLAG_HIGH_SIZE;
+
+	console_lock();
+	if (vc->vc_mode != KD_TEXT)
+		rc = -EINVAL;
+	else if (vc->vc_sw->con_font_info)
+		rc = vc->vc_sw->con_font_info(vc, info);
+	else
+		rc = -ENOSYS;
+	console_unlock();
+
+	return rc;
+}
+
 /*
  *	Interface exported to selection and vcs.
  */
diff --git a/drivers/tty/vt/vt_ioctl.c b/drivers/tty/vt/vt_ioctl.c
index 4b91072f3a4e..9a2f8081f650 100644
--- a/drivers/tty/vt/vt_ioctl.c
+++ b/drivers/tty/vt/vt_ioctl.c
@@ -479,6 +479,19 @@ static int vt_k_ioctl(struct tty_struct *tty, unsigned int cmd,
 		break;
 	}
 
+	case KDFONTINFO: {
+		struct console_font_info fnt_info;
+
+		memset(&fnt_info, 0, sizeof(fnt_info));
+
+		ret = con_font_info(vc, &fnt_info);
+		if (ret)
+			return ret;
+		if (copy_to_user(up, &fnt_info, sizeof(fnt_info)))
+			return -EFAULT;
+		break;
+	}
+
 	default:
 		return -ENOIOCTLCMD;
 	}
diff --git a/include/linux/console.h b/include/linux/console.h
index 31a8f5b85f5d..4b798322aa01 100644
--- a/include/linux/console.h
+++ b/include/linux/console.h
@@ -21,6 +21,7 @@
 #include <linux/vesa.h>
 
 struct vc_data;
+struct console_font_info;
 struct console_font_op;
 struct console_font;
 struct module;
@@ -102,6 +103,8 @@ struct consw {
 	bool	(*con_switch)(struct vc_data *vc);
 	bool	(*con_blank)(struct vc_data *vc, enum vesa_blank_mode blank,
 			     bool mode_switch);
+	int	(*con_font_info)(struct vc_data *vc,
+				 struct console_font_info *info);
 	int	(*con_font_set)(struct vc_data *vc,
 				const struct console_font *font,
 				unsigned int vpitch, unsigned int flags);
diff --git a/include/linux/vt_kern.h b/include/linux/vt_kern.h
index d008c3d0a9bb..383b3a4f6113 100644
--- a/include/linux/vt_kern.h
+++ b/include/linux/vt_kern.h
@@ -33,6 +33,7 @@ void do_blank_screen(int entering_gfx);
 void do_unblank_screen(int leaving_gfx);
 void poke_blanked_console(void);
 int con_font_op(struct vc_data *vc, struct console_font_op *op);
+int con_font_info(struct vc_data *vc, struct console_font_info *info);
 int con_set_cmap(unsigned char __user *cmap);
 int con_get_cmap(unsigned char __user *cmap);
 void scrollback(struct vc_data *vc);
diff --git a/include/uapi/linux/kd.h b/include/uapi/linux/kd.h
index 8ddb2219a84b..68b715ad4d5c 100644
--- a/include/uapi/linux/kd.h
+++ b/include/uapi/linux/kd.h
@@ -185,6 +185,20 @@ struct console_font {
 
 #define KD_FONT_FLAG_DONT_RECALC 	1	/* Don't recalculate hw charcell size [compat] */
 
+/* font information */
+
+#define KD_FONT_INFO_FLAG_LOW_SIZE	_BITUL(0) /* 256 */
+#define KD_FONT_INFO_FLAG_HIGH_SIZE	_BITUL(1) /* 512 */
+
+struct console_font_info {
+	__u32  flags;			/* KD_FONT_INFO_FLAG_* */
+	__u32 min_width, min_height;	/* minimal font size */
+	__u32 max_width, max_height;	/* maximum font size */
+	__u32 reserved[5];		/* This field is reserved for future use. Must be 0. */
+};
+
+#define KDFONTINFO	_IOR(KD_IOCTL_BASE, 0x73, struct console_font_info)
+
 /* note: 0x4B00-0x4B4E all have had a value at some time;
    don't reuse for the time being */
 /* note: 0x4B60-0x4B6D, 0x4B70-0x4B72 used above */
-- 
2.44.0


^ permalink raw reply related

* [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

* [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

* 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

* 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 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  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


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