* [PATCH v6 3/8] selftest: riscv: test vectorized user copy
[not found] <20260918215154.2481482-1-tchiu@tenstorrent.com>
@ 2026-09-18 21:51 ` Andy Chiu
2026-10-06 10:02 ` Paul Walmsley
2026-09-18 21:51 ` [PATCH v6 7/8] selftests: riscv: Extend vector tests for sigreturn and ptrace Andy Chiu
1 sibling, 1 reply; 3+ messages in thread
From: Andy Chiu @ 2026-09-18 21:51 UTC (permalink / raw)
To: Shuah Khan, Paul Walmsley, Palmer Dabbelt, Albert Ou,
Alexandre Ghiti, Nathan Chancellor, Nick Desaulniers,
Bill Wendling, Justin Stitt, linux-kselftest, linux-riscv, llvm
Cc: andybnac, Andy Chiu, dfustini, greentime.hu, Sergey Matyukevich,
Zong Li, Yong-Xuan Wang
Add a test that pushes a 1KiB pseudo-random stream through a pipe, so
write() and read() copy it in and out of the kernel on the vectorized
uaccess path, and compares the result against the original. Both buffers
shift every iteration to sweep all relative alignments, and 16 processes
per online CPU keep the vector unit contended. The compare is a byte at a
time loop through volatile pointers rather than memcmp(), keeping it scalar
and independent of the user vector state.
Assisted-by: Claude-Code:claude-opus-5
Signed-off-by: Andy Chiu <tchiu@tenstorrent.com>
---
Changelog v5:
- new patch since v5
---
tools/testing/selftests/riscv/vector/Makefile | 6 +-
.../selftests/riscv/vector/v_uaccess_stress.c | 221 ++++++++++++++++++
2 files changed, 226 insertions(+), 1 deletion(-)
create mode 100644 tools/testing/selftests/riscv/vector/v_uaccess_stress.c
diff --git a/tools/testing/selftests/riscv/vector/Makefile b/tools/testing/selftests/riscv/vector/Makefile
index 7e0017b3fb8b..0ed09b69dd8a 100644
--- a/tools/testing/selftests/riscv/vector/Makefile
+++ b/tools/testing/selftests/riscv/vector/Makefile
@@ -2,7 +2,8 @@
# Copyright (C) 2021 ARM Limited
# Originally tools/testing/arm64/abi/Makefile
-TEST_GEN_PROGS := v_initval vstate_prctl vstate_ptrace validate_v_ptrace
+TEST_GEN_PROGS := v_initval vstate_prctl vstate_ptrace validate_v_ptrace \
+ v_uaccess_stress
TEST_GEN_PROGS_EXTENDED := vstate_exec_nolibc v_exec_initval_nolibc
TEST_GEN_LIBS := v_helpers.c sys_hwprobe.c
@@ -36,4 +37,7 @@ $(OUTPUT)/vstate_ptrace: vstate_ptrace.c $(OUTPUT)/sys_hwprobe.o $(OUTPUT)/v_hel
$(OUTPUT)/validate_v_ptrace: validate_v_ptrace.c $(OUTPUT)/sys_hwprobe.o $(OUTPUT)/v_helpers.o
$(CC) -static -o $@ $(CFLAGS) $(LDFLAGS) $^
+$(OUTPUT)/v_uaccess_stress: v_uaccess_stress.c
+ $(CC) -static -o $@ $(CFLAGS) $(LDFLAGS) $^
+
EXTRA_CLEAN += $(TEST_GEN_OBJ)
diff --git a/tools/testing/selftests/riscv/vector/v_uaccess_stress.c b/tools/testing/selftests/riscv/vector/v_uaccess_stress.c
new file mode 100644
index 000000000000..d70e0387e246
--- /dev/null
+++ b/tools/testing/selftests/riscv/vector/v_uaccess_stress.c
@@ -0,0 +1,221 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Stress the kernel's user copy path from several processes at once.
+ *
+ * Each child pushes a 1KiB pseudo-random byte stream through a pipe: write()
+ * copies it from user space into the kernel (copy_from_user()) and read()
+ * copies it back out (copy_to_user()). The round trip is repeated 512 times
+ * per child, and every iteration the buffers are shifted so that the copies
+ * cover all of the source/destination alignment combinations.
+ *
+ * On RISC-V those copies may be serviced by kernel-mode vector routines, so
+ * the payload is generated and verified with plain scalar loads and stores
+ * only. The comparison goes through volatile pointers, which keeps both GCC
+ * and clang from turning the loop into a vectorised compare and keeps the
+ * check independent of the user vector state the kernel is supposed to
+ * preserve. memcmp() is avoided for the same reason: the libc version is free
+ * to use vector instructions.
+ */
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/wait.h>
+
+#include "kselftest_harness.h"
+
+#define ITERATIONS 32768
+#define STREAM_SIZE 1024
+#define MAX_SHIFT 16
+#define BUF_SIZE (STREAM_SIZE + MAX_SHIFT)
+
+#define CONCURRENCY 16
+
+/* xorshift64*, so the payload does not depend on any libc or kernel helper. */
+static unsigned long prng_state;
+
+static unsigned long prng_next(void)
+{
+ prng_state ^= prng_state >> 12;
+ prng_state ^= prng_state << 25;
+ prng_state ^= prng_state >> 27;
+
+ return prng_state * 2685821657736338717UL;
+}
+
+static void fill_random(unsigned char *buf, size_t len)
+{
+ size_t i;
+
+ for (i = 0; i < len; i++)
+ buf[i] = (unsigned char)(prng_next() >> 24);
+}
+
+/*
+ * Scalar, byte at a time comparison. Returns the offset of the first
+ * mismatching byte, or len when the two streams are identical.
+ */
+static size_t scalar_diff(const unsigned char *a, const unsigned char *b,
+ size_t len)
+{
+ const volatile unsigned char *va = a;
+ const volatile unsigned char *vb = b;
+ size_t i;
+
+ for (i = 0; i < len; i++) {
+ if (va[i] != vb[i])
+ break;
+ }
+
+ return i;
+}
+
+static ssize_t write_all(int fd, const unsigned char *buf, size_t len)
+{
+ size_t done = 0;
+
+ while (done < len) {
+ ssize_t ret = write(fd, buf + done, len - done);
+
+ if (ret < 0) {
+ if (errno == EINTR)
+ continue;
+ return -1;
+ }
+ done += ret;
+ }
+
+ return done;
+}
+
+static ssize_t read_all(int fd, unsigned char *buf, size_t len)
+{
+ size_t done = 0;
+
+ while (done < len) {
+ ssize_t ret = read(fd, buf + done, len - done);
+
+ if (ret < 0) {
+ if (errno == EINTR)
+ continue;
+ return -1;
+ }
+ if (ret == 0)
+ return -1;
+ done += ret;
+ }
+
+ return done;
+}
+
+/* Runs in the child. Returns the exit status to hand back to the parent. */
+static int child_loop(unsigned long seed)
+{
+ unsigned char src[BUF_SIZE], dst[BUF_SIZE];
+ int pipefd[2];
+ int i;
+
+ prng_state = seed;
+
+ if (pipe(pipefd)) {
+ fprintf(stderr, "pid %d: pipe() failed: %s\n",
+ getpid(), strerror(errno));
+ return 1;
+ }
+
+ for (i = 0; i < ITERATIONS; i++) {
+ /*
+ * Walk the source and destination through every relative
+ * alignment, including the case where both are equally
+ * misaligned.
+ */
+ unsigned char *in = src + (i % MAX_SHIFT);
+ unsigned char *out = dst + ((i / MAX_SHIFT) % MAX_SHIFT);
+ size_t off;
+
+ fill_random(in, STREAM_SIZE);
+ memset(out, 0, STREAM_SIZE);
+
+ if (write_all(pipefd[1], in, STREAM_SIZE) < 0) {
+ fprintf(stderr, "pid %d: iteration %d: write() failed: %s\n",
+ getpid(), i, strerror(errno));
+ return 1;
+ }
+
+ if (read_all(pipefd[0], out, STREAM_SIZE) < 0) {
+ fprintf(stderr, "pid %d: iteration %d: read() failed: %s\n",
+ getpid(), i, strerror(errno));
+ return 1;
+ }
+
+ off = scalar_diff(in, out, STREAM_SIZE);
+ if (off != STREAM_SIZE) {
+ fprintf(stderr,
+ "pid %d: iteration %d: mismatch at byte %zu of %d (in %p, out %p): expected 0x%02x, got 0x%02x\n",
+ getpid(), i, off, STREAM_SIZE, in, out,
+ in[off], out[off]);
+ return 1;
+ }
+ }
+
+ close(pipefd[0]);
+ close(pipefd[1]);
+
+ return 0;
+}
+
+static int nr_children(void)
+{
+ long online = sysconf(_SC_NPROCESSORS_ONLN);
+
+ return online * CONCURRENCY;
+}
+
+TEST(uaccess_round_trip)
+{
+ int nr = nr_children();
+ int failures = 0;
+ pid_t pids[nr];
+ int i;
+
+ ksft_print_msg("%d children, %d iterations of %d bytes each\n",
+ nr, ITERATIONS, STREAM_SIZE);
+
+ for (i = 0; i < nr; i++) {
+ pids[i] = fork();
+ ASSERT_LE(0, pids[i]) {
+ TH_LOG("fork() failed: %s", strerror(errno));
+ }
+
+ if (pids[i] == 0) {
+ /* A distinct, reproducible stream per child. */
+ _exit(child_loop(0x9e3779b97f4a7c15UL + i));
+ }
+ }
+
+ for (i = 0; i < nr; i++) {
+ int status;
+
+ if (waitpid(pids[i], &status, 0) < 0) {
+ TH_LOG("waitpid(%d) failed: %s", pids[i],
+ strerror(errno));
+ failures++;
+ continue;
+ }
+
+ if (WIFSIGNALED(status)) {
+ TH_LOG("child %d killed by signal %d", pids[i],
+ WTERMSIG(status));
+ failures++;
+ } else if (!WIFEXITED(status) || WEXITSTATUS(status)) {
+ TH_LOG("child %d failed", pids[i]);
+ failures++;
+ }
+ }
+
+ ASSERT_EQ(0, failures);
+}
+
+TEST_HARNESS_MAIN
--
2.43.0
^ permalink raw reply related [flat|nested] 3+ messages in thread
* [PATCH v6 7/8] selftests: riscv: Extend vector tests for sigreturn and ptrace
[not found] <20260918215154.2481482-1-tchiu@tenstorrent.com>
2026-09-18 21:51 ` [PATCH v6 3/8] selftest: riscv: test vectorized user copy Andy Chiu
@ 2026-09-18 21:51 ` Andy Chiu
1 sibling, 0 replies; 3+ messages in thread
From: Andy Chiu @ 2026-09-18 21:51 UTC (permalink / raw)
To: Shuah Khan, Paul Walmsley, Palmer Dabbelt, Albert Ou,
Alexandre Ghiti, linux-kselftest, linux-riscv
Cc: andybnac, Andy Chiu, dfustini, greentime.hu, Andrew Morton,
Bala-Vignesh-Reddy, Wei Yang, Yong-Xuan Wang
Add new test cases to verify the vector state restorations at syscall
stops for ptrace and signal interfaces. Specifically:
1. Signal handler should read all ones at syscall stop and modifying
context should success.
2. Ptrace should read all ones but any modification to NT_RISCV_VECTOR
is silently dropped.
Signed-off-by: Andy Chiu <tchiu@tenstorrent.com>
---
Changelog v6:
- reformat code to slience checkpatch
Changelog v4:
- remove timer dependency from the signal delivery (Sashiko)
- solve 2 memory leaks in ptrace (Sashiko)
Changelog v3:
- new patch since v3
---
.../selftests/riscv/sigreturn/sigreturn.c | 69 +++++++++++
.../selftests/riscv/vector/vstate_ptrace.c | 111 +++++++++++++++++-
2 files changed, 176 insertions(+), 4 deletions(-)
diff --git a/tools/testing/selftests/riscv/sigreturn/sigreturn.c b/tools/testing/selftests/riscv/sigreturn/sigreturn.c
index e10873d95fed..386367cf28be 100644
--- a/tools/testing/selftests/riscv/sigreturn/sigreturn.c
+++ b/tools/testing/selftests/riscv/sigreturn/sigreturn.c
@@ -7,6 +7,7 @@
#include "kselftest_harness.h"
#define RISCV_V_MAGIC 0x53465457
+#define END_MAGIC 0
#define DEFAULT_VALUE 2
#define SIGNAL_HANDLER_OVERRIDE 3
@@ -61,6 +62,74 @@ static int vector_sigreturn(int data, void (*handler)(int, siginfo_t *, void *))
return after_sigreturn;
}
+#define V_TEST_PATTERN_SIGNAL 0x98
+int nulled_val;
+static void sigalrm_handler(int sig, siginfo_t *info, void *vcontext)
+{
+ struct __riscv_extra_ext_header *ext;
+ struct __riscv_v_ext_state *v_state;
+ ucontext_t *context = vcontext;
+ struct __riscv_ctx_hdr *hdr;
+ uint8_t *ext_ptr;
+
+ /* Find the vector context */
+ ext = (void *)(&context->uc_mcontext.__fpregs);
+ ext_ptr = (uint8_t *)ext;
+ hdr = &ext->hdr;
+
+ while (hdr->magic != END_MAGIC) {
+ if (hdr->magic == RISCV_V_MAGIC) {
+ v_state = (struct __riscv_v_ext_state *)(hdr + 1);
+ /* Assume a valid datap */
+ nulled_val = *(int *)v_state->datap;
+ /* Fill all vector registers with magic pattern */
+ memset(v_state->datap, V_TEST_PATTERN_SIGNAL, v_state->vlenb * 32);
+ /*
+ * We must also set the vector configuration so that when
+ * userspace reads v0, it uses a valid element width (e8).
+ */
+ v_state->vl = v_state->vlenb;
+ v_state->vtype = 0; /* e8, m1, tu, mu */
+ break;
+ }
+ /* Move to the next extension header */
+ ext_ptr += hdr->size;
+ hdr = (struct __riscv_ctx_hdr *)ext_ptr;
+ }
+}
+
+TEST(test_signal_syscall_ucontext) {
+ struct sigaction sa;
+
+ /* Make sure we get V in ucontext by executing vsetvli */
+ asm volatile (".option push\n\t"
+ ".option arch, +v\n\t"
+ "vsetivli x0, 1, e32, m1, ta, ma\n\t"
+ ".option pop\n\t" : : :);
+
+ sa.sa_flags = SA_SIGINFO;
+ sa.sa_sigaction = sigalrm_handler;
+ sigemptyset(&sa.sa_mask);
+ if (sigaction(SIGALRM, &sa, NULL) == -1)
+ ksft_exit_fail_msg("Failed to register signal handler\n");
+
+ raise(SIGALRM);
+ /*
+ * If the kernel successfully parsed and restored our modified ucontext,
+ * v0 will contain V_TEST_PATTERN_SIGNAL.
+ */
+ unsigned char v0_val;
+
+ asm volatile(".option push\n\t"
+ ".option arch, +zve32x\n\t"
+ "vmv.x.s %0, v0\n\t"
+ ".option pop\n\t"
+ : "=r" (v0_val));
+
+ EXPECT_EQ(v0_val, V_TEST_PATTERN_SIGNAL);
+ EXPECT_EQ(nulled_val, -1);
+}
+
TEST(vector_restore)
{
int result;
diff --git a/tools/testing/selftests/riscv/vector/vstate_ptrace.c b/tools/testing/selftests/riscv/vector/vstate_ptrace.c
index 1479abc0c9cb..863a16f6e1a3 100644
--- a/tools/testing/selftests/riscv/vector/vstate_ptrace.c
+++ b/tools/testing/selftests/riscv/vector/vstate_ptrace.c
@@ -21,6 +21,41 @@ static long do_ptrace(enum __ptrace_request op, pid_t pid, long type, size_t siz
return ptrace(op, pid, type, &v_iovec);
}
+static int do_child_syscall_stop(void)
+{
+ int out;
+
+ if (ptrace(PTRACE_TRACEME, -1, NULL, NULL)) {
+ ksft_perror("PTRACE_TRACEME failed\n");
+ return EXIT_FAILURE;
+ }
+
+ raise(SIGSTOP);
+
+ asm volatile (".option push\n\t"
+ ".option arch, +v\n\t"
+ "vsetivli x0, 1, e32, m1, ta, ma\n\t"
+ "vmv.s.x v31, %[in]\n\t"
+ ".option pop\n\t"
+ :
+ : [in] "r" (child_set_val));
+
+ getpid();
+
+ asm volatile (".option push\n\t"
+ ".option arch, +v\n\t"
+ "vsetivli x0, 1, e32, m1, ta, ma\n\t"
+ "vmv.x.s %[out], v31\n\t"
+ ".option pop\n\t"
+ : [out] "=r" (out)
+ :);
+
+ if (out != -1)
+ return EXIT_FAILURE;
+
+ return EXIT_SUCCESS;
+}
+
static int do_child(void)
{
int out;
@@ -59,7 +94,7 @@ static void do_parent(pid_t child)
goto out;
} else if (WIFSTOPPED(status) && (WSTOPSIG(status) == SIGTRAP)) {
size_t size;
- void *data, *v31;
+ void *vctx, *v31;
struct __riscv_v_regset_state *v_regset_hdr;
struct user_regs_struct *gpreg;
@@ -73,9 +108,11 @@ static void do_parent(pid_t child)
goto out;
ksft_print_msg("vlenb %ld\n", v_regset_hdr->vlenb);
- data = realloc(data, size + v_regset_hdr->vlenb * 32);
- if (!data)
+ vctx = realloc(data, size + v_regset_hdr->vlenb * 32);
+ if (!vctx)
goto out;
+ data = vctx;
+
v_regset_hdr = (struct __riscv_v_regset_state *)data;
v31 = (void *)(data + size + v_regset_hdr->vlenb * 31);
size += v_regset_hdr->vlenb * 32;
@@ -109,11 +146,65 @@ static void do_parent(pid_t child)
free(data);
}
+static void do_parent_syscall_stop(pid_t child)
+{
+ int status;
+ void *data = NULL;
+
+ while (waitpid(child, &status, 0)) {
+ if (WIFEXITED(status)) {
+ ksft_test_result(WEXITSTATUS(status) == 0,
+ "SETREGSET vector at syscall stop\n");
+ goto out;
+ } else if (WIFSTOPPED(status) && (WSTOPSIG(status) == SIGSTOP)) {
+ /* Attach to the child at syscall stop */
+ ptrace(PTRACE_SYSCALL, child, NULL, NULL);
+ continue;
+ } else if (WIFSTOPPED(status) && (WSTOPSIG(status) == SIGTRAP)) {
+ size_t size;
+ void *vctx, *v31;
+ struct __riscv_v_regset_state *v_regset_hdr;
+
+ size = sizeof(*v_regset_hdr);
+ data = malloc(size);
+ if (!data)
+ goto out;
+ v_regset_hdr = (struct __riscv_v_regset_state *)data;
+
+ if (do_ptrace(PTRACE_GETREGSET, child, NT_RISCV_VECTOR, size, data))
+ goto out;
+
+ ksft_print_msg("vlenb %ld\n", v_regset_hdr->vlenb);
+ vctx = realloc(data, size + v_regset_hdr->vlenb * 32);
+ if (!vctx)
+ goto out;
+ data = vctx;
+
+ v_regset_hdr = (struct __riscv_v_regset_state *)data;
+ v31 = (void *)(data + size + v_regset_hdr->vlenb * 31);
+ size += v_regset_hdr->vlenb * 32;
+
+ if (do_ptrace(PTRACE_GETREGSET, child, NT_RISCV_VECTOR, size, data))
+ goto out;
+
+ ksft_test_result(*(int *)v31 == -1, "GETREGSET vector at syscall stop\n");
+
+ *(int *)v31 = parent_set_val;
+ if (do_ptrace(PTRACE_SETREGSET, child, NT_RISCV_VECTOR, size, data))
+ goto out;
+ }
+ ptrace(PTRACE_CONT, child, NULL, NULL);
+ }
+
+out:
+ free(data);
+}
+
int main(void)
{
pid_t child;
- ksft_set_plan(2);
+ ksft_set_plan(4);
if (!is_vector_supported() && !is_xtheadvector_supported())
ksft_exit_skip("Vector not supported\n");
@@ -130,5 +221,17 @@ int main(void)
do_parent(child);
+ parent_set_val = 0x53355457;
+ child_set_val = 0x49504F21;
+
+ child = fork();
+ if (child < 0)
+ ksft_exit_fail_msg("Fork failed %d\n", child);
+
+ if (!child)
+ return do_child_syscall_stop();
+
+ do_parent_syscall_stop(child);
+
ksft_finished();
}
--
2.43.0
^ permalink raw reply related [flat|nested] 3+ messages in thread
* Re: [PATCH v6 3/8] selftest: riscv: test vectorized user copy
2026-09-18 21:51 ` [PATCH v6 3/8] selftest: riscv: test vectorized user copy Andy Chiu
@ 2026-10-06 10:02 ` Paul Walmsley
0 siblings, 0 replies; 3+ messages in thread
From: Paul Walmsley @ 2026-10-06 10:02 UTC (permalink / raw)
To: Andy Chiu
Cc: Shuah Khan, Paul Walmsley, Palmer Dabbelt, Albert Ou,
Alexandre Ghiti, Nathan Chancellor, Nick Desaulniers,
Bill Wendling, Justin Stitt, linux-kselftest, linux-riscv, llvm,
andybnac, dfustini, greentime.hu, Sergey Matyukevich, Zong Li,
Yong-Xuan Wang
On Fri, 18 Sep 2026, Andy Chiu wrote:
> Add a test that pushes a 1KiB pseudo-random stream through a pipe, so
> write() and read() copy it in and out of the kernel on the vectorized
> uaccess path, and compares the result against the original. Both buffers
> shift every iteration to sweep all relative alignments, and 16 processes
> per online CPU keep the vector unit contended. The compare is a byte at a
> time loop through volatile pointers rather than memcmp(), keeping it scalar
> and independent of the user vector state.
>
> Assisted-by: Claude-Code:claude-opus-5
> Signed-off-by: Andy Chiu <tchiu@tenstorrent.com>
Thanks, queued for v7.4.
- Paul
^ permalink raw reply [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-10-06 10:02 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
[not found] <20260918215154.2481482-1-tchiu@tenstorrent.com>
2026-09-18 21:51 ` [PATCH v6 3/8] selftest: riscv: test vectorized user copy Andy Chiu
2026-10-06 10:02 ` Paul Walmsley
2026-09-18 21:51 ` [PATCH v6 7/8] selftests: riscv: Extend vector tests for sigreturn and ptrace Andy Chiu
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox