* [PATCH v3 0/5] linux-user/riscv: add vector state to signal context
@ 2026-03-21 14:13 Nicholas Piggin
2026-03-21 14:13 ` [PATCH v3 1/5] tests/tcg/riscv64: Add a user signal handling test Nicholas Piggin
` (5 more replies)
0 siblings, 6 replies; 13+ messages in thread
From: Nicholas Piggin @ 2026-03-21 14:13 UTC (permalink / raw)
To: qemu-riscv
Cc: Nicholas Piggin, qemu-devel, Laurent Vivier, Pierrick Bouvier,
Palmer Dabbelt, Alistair Francis, Weiwei Li,
Daniel Henrique Barboza, Liu Zhiwei, Richard Henderson,
Joel Stanley
Changes in v3:
- Fixed up a bunch of good review comments that Richard made against
the v1 series here https://lore.kernel.org/all/06afcbdc-28e2-4d13-81d4-26fd257ed8a3@linaro.org/#r
in particular:
- Restoring vl/vtype state using CPU accessors rather than stuffing the
values into the registers directly.
- Fix ABI types.
- Fix some confusion with host/guest pointer mapping.
- Fix error handling to SIGSEGV.
- Style fixes.
- Fixed build issue without riscv intrinsics header.
- Add some more test cases.
Changes in v2:
- Fix 32-bit compile
- Moved sizeof_rt_sigframe change to its correct patch (patch 2 is the
one that extends the size of rt_sigframe, patch 3 is adding new
extended state to the frame which is added dynamically).
This series adds vector state to the linux-user signal handler,
and adds a basic signal handling test case. As a sanity check, I
also verified the signal handling test works in the same way when
run under a real Linux kernel.
Thanks,
Nick
Nicholas Piggin (5):
tests/tcg/riscv64: Add a user signal handling test
linux-user/riscv: Allow restore_sigcontext to return error
linux-user/riscv: Add extended state to sigcontext
linux-user/riscv: Add vector state to signal context
tests/tcg/riscv64: Add vector state to signal test
linux-user/riscv/signal.c | 281 +++++++++++-
linux-user/riscv/vdso-asmoffset.h | 4 +-
target/riscv/cpu.h | 4 +
target/riscv/csr.c | 7 +-
target/riscv/vector_helper.c | 19 +-
tests/tcg/riscv64/Makefile.target | 5 +
tests/tcg/riscv64/test-signal-handling.c | 517 +++++++++++++++++++++++
7 files changed, 807 insertions(+), 30 deletions(-)
create mode 100644 tests/tcg/riscv64/test-signal-handling.c
--
2.51.0
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH v3 1/5] tests/tcg/riscv64: Add a user signal handling test
2026-03-21 14:13 [PATCH v3 0/5] linux-user/riscv: add vector state to signal context Nicholas Piggin
@ 2026-03-21 14:13 ` Nicholas Piggin
2026-03-25 3:07 ` Alistair Francis
2026-03-21 14:13 ` [PATCH v3 2/5] linux-user/riscv: Allow restore_sigcontext to return error Nicholas Piggin
` (4 subsequent siblings)
5 siblings, 1 reply; 13+ messages in thread
From: Nicholas Piggin @ 2026-03-21 14:13 UTC (permalink / raw)
To: qemu-riscv
Cc: Nicholas Piggin, qemu-devel, Laurent Vivier, Pierrick Bouvier,
Palmer Dabbelt, Alistair Francis, Weiwei Li,
Daniel Henrique Barboza, Liu Zhiwei, Richard Henderson,
Joel Stanley
Add a few basic signal handling tests for user emulation.
Signed-off-by: Nicholas Piggin <npiggin@gmail.com>
---
tests/tcg/riscv64/Makefile.target | 5 +
tests/tcg/riscv64/test-signal-handling.c | 303 +++++++++++++++++++++++
2 files changed, 308 insertions(+)
create mode 100644 tests/tcg/riscv64/test-signal-handling.c
diff --git a/tests/tcg/riscv64/Makefile.target b/tests/tcg/riscv64/Makefile.target
index 4da5b9a3b3..f318891396 100644
--- a/tests/tcg/riscv64/Makefile.target
+++ b/tests/tcg/riscv64/Makefile.target
@@ -18,3 +18,8 @@ TESTS += test-fcvtmod
test-fcvtmod: CFLAGS += -march=rv64imafdc
test-fcvtmod: LDFLAGS += -static
run-test-fcvtmod: QEMU_OPTS += -cpu rv64,d=true,zfa=true
+
+# Test signal handling.
+TESTS += test-signal-handling
+test-signal-handling: CFLAGS += -march=rv64gc
+run-test-signal-handling: QEMU_OPTS += -cpu rv64
diff --git a/tests/tcg/riscv64/test-signal-handling.c b/tests/tcg/riscv64/test-signal-handling.c
new file mode 100644
index 0000000000..c202503382
--- /dev/null
+++ b/tests/tcg/riscv64/test-signal-handling.c
@@ -0,0 +1,303 @@
+/*
+ * Test for linux-user signal handling.
+ *
+ * This ensures that integer and fp register values are
+ * saved as expected in the sigcontext, created by a SIGILL.
+ *
+ * TODO: Register restore is not explicitly verified, except
+ * for advancing pc, and the restoring of registers that were
+ * clobbered by the compiler in the signal handler.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+#define _GNU_SOURCE
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <execinfo.h>
+#include <unistd.h>
+#include <assert.h>
+#include <sys/mman.h>
+#include <ucontext.h>
+#include <asm/sigcontext.h>
+
+/*
+ * This horrible hack seems to be required when including
+ * signal.h and asm/sigcontext.h, to prevent sigcontext
+ * redefinition by bits/sigcontext.h :(
+ *
+ * bits/sigcontext.h does not have the extended state or
+ * RISCV_V_MAGIC, etc. It could have just been introduced
+ * as a new type.
+ */
+#define _BITS_SIGCONTEXT_H 1
+#include <signal.h>
+
+static uint64_t *initial_gvalues;
+static uint64_t *final_gvalues;
+static uint64_t *signal_gvalues;
+static double *initial_fvalues;
+static double *final_fvalues;
+static double *signal_fvalues;
+
+extern unsigned long unimp_addr[];
+
+static bool got_signal = false;
+
+#define BT_BUF_SIZE 100
+
+static void *find_callchain_root(void)
+{
+ int nptrs;
+ void *buffer[BT_BUF_SIZE];
+
+ nptrs = backtrace(buffer, BT_BUF_SIZE);
+
+ return buffer[nptrs - 1];
+}
+
+static void *callchain_root;
+
+static void ILL_handler(int signo, siginfo_t *info, void *context)
+{
+ ucontext_t *uc = context;
+ struct sigcontext *sc = (struct sigcontext *)&uc->uc_mcontext;
+
+ got_signal = true;
+
+ assert(unimp_addr == info->si_addr);
+ assert(sc->sc_regs.pc == (unsigned long)info->si_addr);
+
+ /* Ensure stack unwind through the signal frame is not broken */
+ assert(callchain_root == find_callchain_root());
+
+ for (int i = 0; i < 31; i++) {
+ ((uint64_t *)signal_gvalues)[i] = ((unsigned long *)&sc->sc_regs.ra)[i];
+ }
+
+ for (int i = 0; i < 32; i++) {
+ ((uint64_t *)signal_fvalues)[i] = sc->sc_fpregs.d.f[i];
+ }
+ /* Test sc->sc_fpregs.d.fcsr ? */
+
+ sc->sc_regs.pc += 4;
+}
+
+static void init_test(void)
+{
+ int i;
+
+ callchain_root = find_callchain_root();
+
+ initial_gvalues = malloc(8 * 31);
+ memset(initial_gvalues, 0, 8 * 31);
+ final_gvalues = malloc(8 * 31);
+ memset(final_gvalues, 0, 8 * 31);
+ signal_gvalues = malloc(8 * 31);
+ memset(signal_gvalues, 0, 8 * 31);
+
+ initial_fvalues = malloc(8 * 32);
+ memset(initial_fvalues, 0, 8 * 32);
+ for (i = 0; i < 32 ; i++) {
+ initial_fvalues[i] = 3.142 * (i + 1);
+ }
+ final_fvalues = malloc(8 * 32);
+ memset(final_fvalues, 0, 8 * 32);
+ signal_fvalues = malloc(8 * 32);
+ memset(signal_fvalues, 0, 8 * 32);
+}
+
+static void run_test(void)
+{
+ asm volatile(
+ /* Save initial values from gp registers */
+ "mv t0, %[initial_gvalues]\n\t"
+ "sd x1, 0x0(t0)\n\t"
+ "sd x2, 0x8(t0)\n\t"
+ "sd x3, 0x10(t0)\n\t"
+ "sd x4, 0x18(t0)\n\t"
+ "sd x5, 0x20(t0)\n\t"
+ "sd x6, 0x28(t0)\n\t"
+ "sd x7, 0x30(t0)\n\t"
+ "sd x8, 0x38(t0)\n\t"
+ "sd x9, 0x40(t0)\n\t"
+ "sd x10, 0x48(t0)\n\t"
+ "sd x11, 0x50(t0)\n\t"
+ "sd x12, 0x58(t0)\n\t"
+ "sd x13, 0x60(t0)\n\t"
+ "sd x14, 0x68(t0)\n\t"
+ "sd x15, 0x70(t0)\n\t"
+ "sd x16, 0x78(t0)\n\t"
+ "sd x17, 0x80(t0)\n\t"
+ "sd x18, 0x88(t0)\n\t"
+ "sd x19, 0x90(t0)\n\t"
+ "sd x20, 0x98(t0)\n\t"
+ "sd x21, 0xa0(t0)\n\t"
+ "sd x22, 0xa8(t0)\n\t"
+ "sd x23, 0xb0(t0)\n\t"
+ "sd x24, 0xb8(t0)\n\t"
+ "sd x25, 0xc0(t0)\n\t"
+ "sd x26, 0xc8(t0)\n\t"
+ "sd x27, 0xd0(t0)\n\t"
+ "sd x28, 0xd8(t0)\n\t"
+ "sd x29, 0xe0(t0)\n\t"
+ "sd x30, 0xe8(t0)\n\t"
+ "sd x31, 0xf0(t0)\n\t"
+ /* Load initial values into float registers */
+ "mv t0, %[initial_fvalues]\n\t"
+ "fld f0, 0x0(t0)\n\t"
+ "fld f1, 0x8(t0)\n\t"
+ "fld f2, 0x10(t0)\n\t"
+ "fld f3, 0x18(t0)\n\t"
+ "fld f4, 0x20(t0)\n\t"
+ "fld f5, 0x28(t0)\n\t"
+ "fld f6, 0x30(t0)\n\t"
+ "fld f7, 0x38(t0)\n\t"
+ "fld f8, 0x40(t0)\n\t"
+ "fld f9, 0x48(t0)\n\t"
+ "fld f10, 0x50(t0)\n\t"
+ "fld f11, 0x58(t0)\n\t"
+ "fld f12, 0x60(t0)\n\t"
+ "fld f13, 0x68(t0)\n\t"
+ "fld f14, 0x70(t0)\n\t"
+ "fld f15, 0x78(t0)\n\t"
+ "fld f16, 0x80(t0)\n\t"
+ "fld f17, 0x88(t0)\n\t"
+ "fld f18, 0x90(t0)\n\t"
+ "fld f19, 0x98(t0)\n\t"
+ "fld f20, 0xa0(t0)\n\t"
+ "fld f21, 0xa8(t0)\n\t"
+ "fld f22, 0xb0(t0)\n\t"
+ "fld f23, 0xb8(t0)\n\t"
+ "fld f24, 0xc0(t0)\n\t"
+ "fld f25, 0xc8(t0)\n\t"
+ "fld f26, 0xd0(t0)\n\t"
+ "fld f27, 0xd8(t0)\n\t"
+ "fld f28, 0xe0(t0)\n\t"
+ "fld f29, 0xe8(t0)\n\t"
+ "fld f30, 0xf0(t0)\n\t"
+ "fld f31, 0xf8(t0)\n\t"
+ /* Trigger the SIGILL */
+".global unimp_addr\n\t"
+"unimp_addr:\n\t"
+ "unimp\n\t"
+ "nop\n\t"
+ /* Save final values from gp registers */
+ "mv t0, %[final_gvalues]\n\t"
+ "sd x1, 0x0(t0)\n\t"
+ "sd x2, 0x8(t0)\n\t"
+ "sd x3, 0x10(t0)\n\t"
+ "sd x4, 0x18(t0)\n\t"
+ "sd x5, 0x20(t0)\n\t"
+ "sd x6, 0x28(t0)\n\t"
+ "sd x7, 0x30(t0)\n\t"
+ "sd x8, 0x38(t0)\n\t"
+ "sd x9, 0x40(t0)\n\t"
+ "sd x10, 0x48(t0)\n\t"
+ "sd x11, 0x50(t0)\n\t"
+ "sd x12, 0x58(t0)\n\t"
+ "sd x13, 0x60(t0)\n\t"
+ "sd x14, 0x68(t0)\n\t"
+ "sd x15, 0x70(t0)\n\t"
+ "sd x16, 0x78(t0)\n\t"
+ "sd x17, 0x80(t0)\n\t"
+ "sd x18, 0x88(t0)\n\t"
+ "sd x19, 0x90(t0)\n\t"
+ "sd x20, 0x98(t0)\n\t"
+ "sd x21, 0xa0(t0)\n\t"
+ "sd x22, 0xa8(t0)\n\t"
+ "sd x23, 0xb0(t0)\n\t"
+ "sd x24, 0xb8(t0)\n\t"
+ "sd x25, 0xc0(t0)\n\t"
+ "sd x26, 0xc8(t0)\n\t"
+ "sd x27, 0xd0(t0)\n\t"
+ "sd x28, 0xd8(t0)\n\t"
+ "sd x29, 0xe0(t0)\n\t"
+ "sd x30, 0xe8(t0)\n\t"
+ "sd x31, 0xf0(t0)\n\t"
+ /* Save final values from float registers */
+ "mv t0, %[final_fvalues]\n\t"
+ "fsd f0, 0x0(t0)\n\t"
+ "fsd f1, 0x8(t0)\n\t"
+ "fsd f2, 0x10(t0)\n\t"
+ "fsd f3, 0x18(t0)\n\t"
+ "fsd f4, 0x20(t0)\n\t"
+ "fsd f5, 0x28(t0)\n\t"
+ "fsd f6, 0x30(t0)\n\t"
+ "fsd f7, 0x38(t0)\n\t"
+ "fsd f8, 0x40(t0)\n\t"
+ "fsd f9, 0x48(t0)\n\t"
+ "fsd f10, 0x50(t0)\n\t"
+ "fsd f11, 0x58(t0)\n\t"
+ "fsd f12, 0x60(t0)\n\t"
+ "fsd f13, 0x68(t0)\n\t"
+ "fsd f14, 0x70(t0)\n\t"
+ "fsd f15, 0x78(t0)\n\t"
+ "fsd f16, 0x80(t0)\n\t"
+ "fsd f17, 0x88(t0)\n\t"
+ "fsd f18, 0x90(t0)\n\t"
+ "fsd f19, 0x98(t0)\n\t"
+ "fsd f20, 0xa0(t0)\n\t"
+ "fsd f21, 0xa8(t0)\n\t"
+ "fsd f22, 0xb0(t0)\n\t"
+ "fsd f23, 0xb8(t0)\n\t"
+ "fsd f24, 0xc0(t0)\n\t"
+ "fsd f25, 0xc8(t0)\n\t"
+ "fsd f26, 0xd0(t0)\n\t"
+ "fsd f27, 0xd8(t0)\n\t"
+ "fsd f28, 0xe0(t0)\n\t"
+ "fsd f29, 0xe8(t0)\n\t"
+ "fsd f30, 0xf0(t0)\n\t"
+ "fsd f31, 0xf8(t0)\n\t"
+ : "=m" (initial_gvalues),
+ "=m" (final_gvalues),
+ "=m" (final_fvalues)
+ : "m" (initial_fvalues),
+ [initial_gvalues] "r" (initial_gvalues),
+ [initial_fvalues] "r" (initial_fvalues),
+ [final_gvalues] "r" (final_gvalues),
+ [final_fvalues] "r" (final_fvalues)
+ : "t0",
+ "f0", "f1", "f2", "f3", "f4", "f5", "f6", "f7",
+ "f8", "f9", "f10", "f11", "f12", "f13", "f14", "f15",
+ "f16", "f17", "f18", "f19", "f20", "f21", "f22", "f23",
+ "f24", "f25", "f26", "f27", "f28", "f29", "f30", "f31");
+
+ assert(got_signal);
+
+ /*
+ * x4 / t0 is used in the asm so it has to be handled specially
+ * and is not a simple equality.
+ */
+ assert(initial_gvalues[4] == (unsigned long)initial_gvalues);
+ assert(signal_gvalues[4] == (unsigned long)initial_fvalues);
+ assert(final_gvalues[4] == (unsigned long)final_gvalues);
+ initial_gvalues[4] = final_gvalues[4] = signal_gvalues[4] = 0;
+
+ /*
+ * Ensure registers match before, inside, and after signal
+ * handler.
+ */
+ assert(!memcmp(initial_gvalues, final_gvalues, 8 * 31));
+ assert(!memcmp(initial_gvalues, signal_gvalues, 8 * 31));
+ assert(!memcmp(initial_fvalues, final_fvalues, 8 * 32));
+ assert(!memcmp(initial_fvalues, signal_fvalues, 8 * 32));
+}
+
+int main(void)
+{
+ struct sigaction act = { 0 };
+
+ act.sa_flags = SA_SIGINFO;
+ act.sa_sigaction = &ILL_handler;
+ if (sigaction(SIGILL, &act, NULL) == -1) {
+ perror("sigaction");
+ exit(EXIT_FAILURE);
+ }
+
+ init_test();
+
+ run_test();
+}
--
2.51.0
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH v3 2/5] linux-user/riscv: Allow restore_sigcontext to return error
2026-03-21 14:13 [PATCH v3 0/5] linux-user/riscv: add vector state to signal context Nicholas Piggin
2026-03-21 14:13 ` [PATCH v3 1/5] tests/tcg/riscv64: Add a user signal handling test Nicholas Piggin
@ 2026-03-21 14:13 ` Nicholas Piggin
2026-03-25 3:07 ` Alistair Francis
2026-03-21 14:13 ` [PATCH v3 3/5] linux-user/riscv: Add extended state to sigcontext Nicholas Piggin
` (3 subsequent siblings)
5 siblings, 1 reply; 13+ messages in thread
From: Nicholas Piggin @ 2026-03-21 14:13 UTC (permalink / raw)
To: qemu-riscv
Cc: Nicholas Piggin, qemu-devel, Laurent Vivier, Pierrick Bouvier,
Palmer Dabbelt, Alistair Francis, Weiwei Li,
Daniel Henrique Barboza, Liu Zhiwei, Richard Henderson,
Joel Stanley
Linux can return error from restore_sigcontext in some conditions, which
forces a SIGSEGV. Plumb through this error handling which will be used
by the next change.
Suggested-by: Richard Henderson <richard.henderson@linaro.org>
Signed-off-by: Nicholas Piggin <npiggin@gmail.com>
---
linux-user/riscv/signal.c | 13 +++++++++----
1 file changed, 9 insertions(+), 4 deletions(-)
diff --git a/linux-user/riscv/signal.c b/linux-user/riscv/signal.c
index 22b1b8149f..ece276f85f 100644
--- a/linux-user/riscv/signal.c
+++ b/linux-user/riscv/signal.c
@@ -145,7 +145,7 @@ badframe:
force_sig(TARGET_SIGSEGV);
}
-static void restore_sigcontext(CPURISCVState *env, struct target_sigcontext *sc)
+static bool restore_sigcontext(CPURISCVState *env, struct target_sigcontext *sc)
{
int i;
@@ -161,9 +161,11 @@ static void restore_sigcontext(CPURISCVState *env, struct target_sigcontext *sc)
uint32_t fcsr;
__get_user(fcsr, &sc->fcsr);
riscv_csr_write(env, CSR_FCSR, fcsr);
+
+ return true;
}
-static void restore_ucontext(CPURISCVState *env, struct target_ucontext *uc)
+static bool restore_ucontext(CPURISCVState *env, struct target_ucontext *uc)
{
sigset_t blocked;
target_sigset_t target_set;
@@ -177,7 +179,7 @@ static void restore_ucontext(CPURISCVState *env, struct target_ucontext *uc)
target_to_host_sigset_internal(&blocked, &target_set);
set_sigmask(&blocked);
- restore_sigcontext(env, &uc->uc_mcontext);
+ return restore_sigcontext(env, &uc->uc_mcontext);
}
long do_rt_sigreturn(CPURISCVState *env)
@@ -191,7 +193,10 @@ long do_rt_sigreturn(CPURISCVState *env)
goto badframe;
}
- restore_ucontext(env, &frame->uc);
+ if (!restore_ucontext(env, &frame->uc)) {
+ goto badframe;
+ }
+
target_restore_altstack(&frame->uc.uc_stack, env);
unlock_user_struct(frame, frame_addr, 0);
--
2.51.0
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH v3 3/5] linux-user/riscv: Add extended state to sigcontext
2026-03-21 14:13 [PATCH v3 0/5] linux-user/riscv: add vector state to signal context Nicholas Piggin
2026-03-21 14:13 ` [PATCH v3 1/5] tests/tcg/riscv64: Add a user signal handling test Nicholas Piggin
2026-03-21 14:13 ` [PATCH v3 2/5] linux-user/riscv: Allow restore_sigcontext to return error Nicholas Piggin
@ 2026-03-21 14:13 ` Nicholas Piggin
2026-03-25 3:14 ` Alistair Francis
2026-03-21 14:13 ` [PATCH v3 4/5] linux-user/riscv: Add vector state to signal context Nicholas Piggin
` (2 subsequent siblings)
5 siblings, 1 reply; 13+ messages in thread
From: Nicholas Piggin @ 2026-03-21 14:13 UTC (permalink / raw)
To: qemu-riscv
Cc: Nicholas Piggin, qemu-devel, Laurent Vivier, Pierrick Bouvier,
Palmer Dabbelt, Alistair Francis, Weiwei Li,
Daniel Henrique Barboza, Liu Zhiwei, Richard Henderson,
Joel Stanley
Linux/riscv has extended the sigcontext with padding and an
extended state structure that can save various optional
features like vector in a flexible format. Update the
linux-user signal handling to this new structure.
Signed-off-by: Nicholas Piggin <npiggin@gmail.com>
---
linux-user/riscv/signal.c | 93 +++++++++++++++++++++++++++----
linux-user/riscv/vdso-asmoffset.h | 4 +-
2 files changed, 85 insertions(+), 12 deletions(-)
diff --git a/linux-user/riscv/signal.c b/linux-user/riscv/signal.c
index ece276f85f..e20b9ac177 100644
--- a/linux-user/riscv/signal.c
+++ b/linux-user/riscv/signal.c
@@ -31,14 +31,43 @@
The code below is qemu re-implementation of arch/riscv/kernel/signal.c */
-struct target_sigcontext {
+struct target_gp_state {
abi_long pc;
abi_long gpr[31]; /* x0 is not present, so all offsets must be -1 */
+};
+
+struct target_fp_state {
uint64_t fpr[32];
uint32_t fcsr;
+};
+
+/* The Magic number for signal context frame header. */
+#define END_MAGIC 0x0
+
+/* The size of END signal context header. */
+#define END_HDR_SIZE 0x0
+
+struct target_ctx_hdr {
+ uint32_t magic;
+ uint32_t size;
+};
+
+struct target_extra_ext_header {
+ uint32_t __padding[129] __attribute__((aligned(16)));
+ uint32_t reserved;
+ struct target_ctx_hdr hdr;
+};
+
+struct target_sigcontext {
+ struct target_gp_state sc_regs;
+ union {
+ struct target_fp_state sc_fpregs;
+ struct target_extra_ext_header sc_extdesc;
+ };
}; /* cf. riscv-linux:arch/riscv/include/uapi/asm/ptrace.h */
-QEMU_BUILD_BUG_ON(offsetof(struct target_sigcontext, fpr) != offsetof_freg0);
+QEMU_BUILD_BUG_ON(offsetof(struct target_sigcontext, sc_fpregs.fpr) !=
+ offsetof_freg0);
struct target_ucontext {
abi_ulong uc_flags;
@@ -79,19 +108,26 @@ static abi_ulong get_sigframe(struct target_sigaction *ka,
static void setup_sigcontext(struct target_sigcontext *sc, CPURISCVState *env)
{
+ struct target_ctx_hdr *hdr;
int i;
- __put_user(env->pc, &sc->pc);
+ __put_user(env->pc, &sc->sc_regs.pc);
for (i = 1; i < 32; i++) {
- __put_user(env->gpr[i], &sc->gpr[i - 1]);
+ __put_user(env->gpr[i], &sc->sc_regs.gpr[i - 1]);
}
for (i = 0; i < 32; i++) {
- __put_user(env->fpr[i], &sc->fpr[i]);
+ __put_user(env->fpr[i], &sc->sc_fpregs.fpr[i]);
}
uint32_t fcsr = riscv_csr_read(env, CSR_FCSR);
- __put_user(fcsr, &sc->fcsr);
+ __put_user(fcsr, &sc->sc_fpregs.fcsr);
+
+ __put_user(0, &sc->sc_extdesc.reserved);
+
+ hdr = &sc->sc_extdesc.hdr;
+ __put_user(END_MAGIC, &hdr->magic);
+ __put_user(END_HDR_SIZE, &hdr->size);
}
static void setup_ucontext(struct target_ucontext *uc,
@@ -147,21 +183,58 @@ badframe:
static bool restore_sigcontext(CPURISCVState *env, struct target_sigcontext *sc)
{
+ struct target_ctx_hdr *hdr;
+ uint32_t rsv, magic, size;
int i;
- __get_user(env->pc, &sc->pc);
+ __get_user(env->pc, &sc->sc_regs.pc);
for (i = 1; i < 32; ++i) {
- __get_user(env->gpr[i], &sc->gpr[i - 1]);
+ __get_user(env->gpr[i], &sc->sc_regs.gpr[i - 1]);
}
for (i = 0; i < 32; ++i) {
- __get_user(env->fpr[i], &sc->fpr[i]);
+ __get_user(env->fpr[i], &sc->sc_fpregs.fpr[i]);
}
uint32_t fcsr;
- __get_user(fcsr, &sc->fcsr);
+ __get_user(fcsr, &sc->sc_fpregs.fcsr);
riscv_csr_write(env, CSR_FCSR, fcsr);
+ hdr = &sc->sc_extdesc.hdr;
+ __get_user(rsv, &sc->sc_extdesc.reserved);
+ if (rsv != 0) {
+ qemu_log_mask(LOG_GUEST_ERROR, "signal: sigcontext reserved field is "
+ "non-zero\n");
+ return false;
+ }
+
+ __get_user(magic, &hdr->magic);
+ while (magic != END_MAGIC) {
+ switch (magic) {
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR, "signal: unknown extended state in "
+ "sigcontext, magic=0x%08x\n", magic);
+ return false;
+ }
+
+ __get_user(size, &hdr->size);
+ if (size == 0) {
+ qemu_log_mask(LOG_GUEST_ERROR, "signal: extended state in "
+ "sigcontext has size 0\n");
+ return false;
+ }
+
+ hdr = (void *)hdr + size;
+ __get_user(magic, &hdr->magic);
+ }
+
+ __get_user(size, &hdr->size);
+ if (size != END_HDR_SIZE) {
+ qemu_log_mask(LOG_GUEST_ERROR, "signal: extended state end header has "
+ "size=%u (should be 0)\n", size);
+ return false;
+ }
+
return true;
}
diff --git a/linux-user/riscv/vdso-asmoffset.h b/linux-user/riscv/vdso-asmoffset.h
index 123902ef61..92e8ac10ab 100644
--- a/linux-user/riscv/vdso-asmoffset.h
+++ b/linux-user/riscv/vdso-asmoffset.h
@@ -1,9 +1,9 @@
#ifdef TARGET_ABI32
-# define sizeof_rt_sigframe 0x2b0
+# define sizeof_rt_sigframe 0x3b0
# define offsetof_uc_mcontext 0x120
# define offsetof_freg0 0x80
#else
-# define sizeof_rt_sigframe 0x340
+# define sizeof_rt_sigframe 0x440
# define offsetof_uc_mcontext 0x130
# define offsetof_freg0 0x100
#endif
--
2.51.0
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH v3 4/5] linux-user/riscv: Add vector state to signal context
2026-03-21 14:13 [PATCH v3 0/5] linux-user/riscv: add vector state to signal context Nicholas Piggin
` (2 preceding siblings ...)
2026-03-21 14:13 ` [PATCH v3 3/5] linux-user/riscv: Add extended state to sigcontext Nicholas Piggin
@ 2026-03-21 14:13 ` Nicholas Piggin
2026-03-25 3:45 ` Alistair Francis
2026-03-21 14:13 ` [PATCH v3 5/5] tests/tcg/riscv64: Add vector state to signal test Nicholas Piggin
2026-03-25 4:45 ` [PATCH v3 0/5] linux-user/riscv: add vector state to signal context Alistair Francis
5 siblings, 1 reply; 13+ messages in thread
From: Nicholas Piggin @ 2026-03-21 14:13 UTC (permalink / raw)
To: qemu-riscv
Cc: Nicholas Piggin, qemu-devel, Laurent Vivier, Pierrick Bouvier,
Palmer Dabbelt, Alistair Francis, Weiwei Li,
Daniel Henrique Barboza, Liu Zhiwei, Richard Henderson,
Joel Stanley
This enables vector state to be saved and restored across signals.
Signed-off-by: Nicholas Piggin <npiggin@gmail.com>
---
linux-user/riscv/signal.c | 175 +++++++++++++++++++++++++++++++++--
target/riscv/cpu.h | 4 +
target/riscv/csr.c | 7 +-
target/riscv/vector_helper.c | 19 +++-
4 files changed, 191 insertions(+), 14 deletions(-)
diff --git a/linux-user/riscv/signal.c b/linux-user/riscv/signal.c
index e20b9ac177..2e1a1a5027 100644
--- a/linux-user/riscv/signal.c
+++ b/linux-user/riscv/signal.c
@@ -41,7 +41,17 @@ struct target_fp_state {
uint32_t fcsr;
};
+struct target_v_ext_state {
+ abi_ulong vstart;
+ abi_ulong vl;
+ abi_ulong vtype;
+ abi_ulong vcsr;
+ abi_ulong vlenb;
+ abi_ptr datap;
+};
+
/* The Magic number for signal context frame header. */
+#define RISCV_V_MAGIC 0x53465457
#define END_MAGIC 0x0
/* The size of END signal context header. */
@@ -106,6 +116,130 @@ static abi_ulong get_sigframe(struct target_sigaction *ka,
return sp;
}
+static unsigned int get_v_state_hdr_size(CPURISCVState *env)
+{
+ return sizeof(struct target_ctx_hdr) +
+ sizeof(struct target_v_ext_state);
+}
+
+static unsigned int get_v_state_data_size(CPURISCVState *env)
+{
+ RISCVCPU *cpu = env_archcpu(env);
+ return cpu->cfg.vlenb * 32;
+}
+
+static struct target_ctx_hdr *save_v_state(CPURISCVState *env,
+ struct target_ctx_hdr *hdr)
+{
+ RISCVCPU *cpu = env_archcpu(env);
+ target_ulong vlenb = cpu->cfg.vlenb;
+ uint32_t riscv_v_sc_size = get_v_state_hdr_size(env) +
+ get_v_state_data_size(env);
+ struct target_v_ext_state *vs;
+ abi_ulong vcsr;
+ abi_ptr datap;
+ void *host_datap;
+
+#ifdef CONFIG_DEBUG_REMAP
+ /*
+ * The host pointers are derived from lock_user, not g2h, so
+ * h2g can not be used when CONFIG_DEBUG_REMAP=y.
+ */
+ qemu_log_mask(LOG_UNIMP, "signal: sigcontext can not save V state "
+ "when CONFIG_DEBUG_REMAP=y\n");
+ return hdr;
+#endif
+
+ vs = (struct target_v_ext_state *)(hdr + 1);
+ vcsr = riscv_csr_read(env, CSR_VCSR);
+ host_datap = (vs + 1);
+ datap = h2g(host_datap);
+
+ __put_user(RISCV_V_MAGIC, &hdr->magic);
+ __put_user(riscv_v_sc_size, &hdr->size);
+
+ __put_user(env->vstart, &vs->vstart);
+ __put_user(env->vl, &vs->vl);
+ __put_user(env->vtype, &vs->vtype);
+ __put_user(vcsr, &vs->vcsr);
+ __put_user(vlenb, &vs->vlenb);
+ __put_user(datap, &vs->datap);
+
+ for (int i = 0; i < 32; i++) {
+ for (int j = 0; j < vlenb; j += 8) {
+ size_t idx = (i * vlenb + j);
+ __put_user(env->vreg[idx / 8],
+ (uint64_t *)(host_datap + idx));
+ }
+ }
+
+ return (void *)hdr + riscv_v_sc_size;
+}
+
+static bool restore_v_state(CPURISCVState *env,
+ struct target_ctx_hdr *hdr)
+{
+ RISCVCPU *cpu = env_archcpu(env);
+ target_ulong vlenb;
+ target_ulong vcsr, vl, vtype, vstart;
+ struct target_v_ext_state *vs;
+ uint32_t size;
+ abi_ptr datap;
+ void *host_datap;
+
+ if (!riscv_has_ext(env, RVV)) {
+ qemu_log_mask(LOG_GUEST_ERROR, "signal: sigcontext has V state but "
+ "CPU does not support V extension\n");
+ return false;
+ }
+
+ __get_user(size, &hdr->size);
+ if (size < get_v_state_hdr_size(env)) {
+ qemu_log_mask(LOG_GUEST_ERROR, "signal: sigcontext V state header "
+ "size is too small (%u)\n", size);
+ return false;
+ }
+
+ vs = (struct target_v_ext_state *)(hdr + 1);
+
+ __get_user(vstart, &vs->vstart);
+ __get_user(vl, &vs->vl);
+ __get_user(vtype, &vs->vtype);
+ __get_user(vcsr, &vs->vcsr);
+
+ riscv_cpu_set_vstart(env, vstart);
+ riscv_cpu_vsetvl(env, vl, vtype, 0);
+ riscv_csr_write(env, CSR_VCSR, vcsr);
+
+ __get_user(vlenb, &vs->vlenb);
+
+ if (vlenb != cpu->cfg.vlenb) {
+ qemu_log_mask(LOG_GUEST_ERROR, "signal: sigcontext has invalid "
+ "vlenb\n");
+ return false;
+ }
+
+ __get_user(datap, &vs->datap);
+
+ host_datap = lock_user(VERIFY_READ, datap, vlenb * 32, true);
+ if (!host_datap) {
+ qemu_log_mask(LOG_GUEST_ERROR, "signal: sigcontext has V state but "
+ "datap pointer is invalid\n");
+ return false;
+ }
+
+ for (int i = 0; i < 32; i++) {
+ for (int j = 0; j < vlenb; j += 8) {
+ size_t idx = (i * vlenb + j);
+ __get_user(env->vreg[idx / 8],
+ (uint64_t *)(host_datap + idx));
+ }
+ }
+ unlock_user(host_datap, datap, 0);
+
+ return true;
+}
+
static void setup_sigcontext(struct target_sigcontext *sc, CPURISCVState *env)
{
struct target_ctx_hdr *hdr;
@@ -126,6 +260,9 @@ static void setup_sigcontext(struct target_sigcontext *sc, CPURISCVState *env)
__put_user(0, &sc->sc_extdesc.reserved);
hdr = &sc->sc_extdesc.hdr;
+ if (riscv_has_ext(env, RVV)) {
+ hdr = save_v_state(env, hdr);
+ }
__put_user(END_MAGIC, &hdr->magic);
__put_user(END_HDR_SIZE, &hdr->size);
}
@@ -152,17 +289,24 @@ void setup_rt_frame(int sig, struct target_sigaction *ka,
{
abi_ulong frame_addr;
struct target_rt_sigframe *frame;
+ size_t frame_size = sizeof(*frame);
- frame_addr = get_sigframe(ka, env, sizeof(*frame));
+ if (riscv_has_ext(env, RVV)) {
+ frame_size += get_v_state_hdr_size(env) +
+ get_v_state_data_size(env);
+ }
+
+ frame_addr = get_sigframe(ka, env, frame_size);
trace_user_setup_rt_frame(env, frame_addr);
- if (!lock_user_struct(VERIFY_WRITE, frame, frame_addr, 0)) {
+ frame = lock_user(VERIFY_WRITE, frame_addr, frame_size, 0);
+ if (!frame) {
goto badframe;
}
setup_ucontext(&frame->uc, env, set);
frame->info = *info;
- unlock_user_struct(frame, frame_addr, 1);
+ unlock_user(frame, frame_addr, frame_size);
env->pc = ka->_sa_handler;
env->gpr[xSP] = frame_addr;
@@ -174,7 +318,7 @@ void setup_rt_frame(int sig, struct target_sigaction *ka,
return;
badframe:
- unlock_user_struct(frame, frame_addr, 1);
+ unlock_user(frame, frame_addr, frame_size);
if (sig == TARGET_SIGSEGV) {
ka->_sa_handler = TARGET_SIG_DFL;
}
@@ -211,6 +355,11 @@ static bool restore_sigcontext(CPURISCVState *env, struct target_sigcontext *sc)
__get_user(magic, &hdr->magic);
while (magic != END_MAGIC) {
switch (magic) {
+ case RISCV_V_MAGIC:
+ if (!restore_v_state(env, hdr)) {
+ return false;
+ }
+ break;
default:
qemu_log_mask(LOG_GUEST_ERROR, "signal: unknown extended state in "
"sigcontext, magic=0x%08x\n", magic);
@@ -258,11 +407,23 @@ static bool restore_ucontext(CPURISCVState *env, struct target_ucontext *uc)
long do_rt_sigreturn(CPURISCVState *env)
{
struct target_rt_sigframe *frame;
+ size_t frame_size = sizeof(*frame);
abi_ulong frame_addr;
+ if (riscv_has_ext(env, RVV)) {
+ /*
+ * userspace may have set up a discontiguous V state data area,
+ * so need to map that region separately once the address is
+ * known, from datap.
+ */
+ frame_size += get_v_state_hdr_size(env);
+ }
+
frame_addr = env->gpr[xSP];
trace_user_do_sigreturn(env, frame_addr);
- if (!lock_user_struct(VERIFY_READ, frame, frame_addr, 1)) {
+
+ frame = lock_user(VERIFY_READ, frame_addr, frame_size, 1);
+ if (!frame) {
goto badframe;
}
@@ -272,11 +433,11 @@ long do_rt_sigreturn(CPURISCVState *env)
target_restore_altstack(&frame->uc.uc_stack, env);
- unlock_user_struct(frame, frame_addr, 0);
+ unlock_user(frame, frame_addr, 0);
return -QEMU_ESIGRETURN;
badframe:
- unlock_user_struct(frame, frame_addr, 0);
+ unlock_user(frame, frame_addr, 0);
force_sig(TARGET_SIGSEGV);
return 0;
}
diff --git a/target/riscv/cpu.h b/target/riscv/cpu.h
index 35d1f6362c..e1eca79197 100644
--- a/target/riscv/cpu.h
+++ b/target/riscv/cpu.h
@@ -668,6 +668,10 @@ G_NORETURN void riscv_raise_exception(CPURISCVState *env,
target_ulong riscv_cpu_get_fflags(CPURISCVState *env);
void riscv_cpu_set_fflags(CPURISCVState *env, target_ulong);
+void riscv_cpu_set_vstart(CPURISCVState *env, target_ulong val);
+target_ulong riscv_cpu_vsetvl(CPURISCVState *env, target_ulong s1,
+ target_ulong s2, target_ulong x0);
+
#ifndef CONFIG_USER_ONLY
void cpu_set_exception_base(int vp_index, target_ulong address);
#endif
diff --git a/target/riscv/csr.c b/target/riscv/csr.c
index 5064483917..8a6fd11fb5 100644
--- a/target/riscv/csr.c
+++ b/target/riscv/csr.c
@@ -991,11 +991,8 @@ static RISCVException write_vstart(CPURISCVState *env, int csrno,
#if !defined(CONFIG_USER_ONLY)
env->mstatus |= MSTATUS_VS;
#endif
- /*
- * The vstart CSR is defined to have only enough writable bits
- * to hold the largest element index, i.e. lg2(VLEN) bits.
- */
- env->vstart = val & ~(~0ULL << ctzl(riscv_cpu_cfg(env)->vlenb << 3));
+ riscv_cpu_set_vstart(env, val);
+
return RISCV_EXCP_NONE;
}
diff --git a/target/riscv/vector_helper.c b/target/riscv/vector_helper.c
index caa8dd9c12..bceefe019b 100644
--- a/target/riscv/vector_helper.c
+++ b/target/riscv/vector_helper.c
@@ -33,8 +33,17 @@
#include "vector_internals.h"
#include <math.h>
-target_ulong HELPER(vsetvl)(CPURISCVState *env, target_ulong s1,
- target_ulong s2, target_ulong x0)
+void riscv_cpu_set_vstart(CPURISCVState *env, target_ulong val)
+{
+ /*
+ * The vstart CSR is defined to have only enough writable bits
+ * to hold the largest element index, i.e. lg2(VLEN) bits.
+ */
+ env->vstart = val & ~(~0ULL << ctzl(riscv_cpu_cfg(env)->vlenb << 3));
+}
+
+target_ulong riscv_cpu_vsetvl(CPURISCVState *env, target_ulong s1,
+ target_ulong s2, target_ulong x0)
{
int vlmax, vl;
RISCVCPU *cpu = env_archcpu(env);
@@ -99,6 +108,12 @@ target_ulong HELPER(vsetvl)(CPURISCVState *env, target_ulong s1,
return vl;
}
+target_ulong HELPER(vsetvl)(CPURISCVState *env, target_ulong s1,
+ target_ulong s2, target_ulong x0)
+{
+ return riscv_cpu_vsetvl(env, s1, s2, x0);
+}
+
/*
* Get the maximum number of elements can be operated.
*
--
2.51.0
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH v3 5/5] tests/tcg/riscv64: Add vector state to signal test
2026-03-21 14:13 [PATCH v3 0/5] linux-user/riscv: add vector state to signal context Nicholas Piggin
` (3 preceding siblings ...)
2026-03-21 14:13 ` [PATCH v3 4/5] linux-user/riscv: Add vector state to signal context Nicholas Piggin
@ 2026-03-21 14:13 ` Nicholas Piggin
2026-03-25 3:49 ` Alistair Francis
2026-03-25 4:45 ` [PATCH v3 0/5] linux-user/riscv: add vector state to signal context Alistair Francis
5 siblings, 1 reply; 13+ messages in thread
From: Nicholas Piggin @ 2026-03-21 14:13 UTC (permalink / raw)
To: qemu-riscv
Cc: Nicholas Piggin, qemu-devel, Laurent Vivier, Pierrick Bouvier,
Palmer Dabbelt, Alistair Francis, Weiwei Li,
Daniel Henrique Barboza, Liu Zhiwei, Richard Henderson,
Joel Stanley
Signed-off-by: Nicholas Piggin <npiggin@gmail.com>
---
tests/tcg/riscv64/Makefile.target | 4 +-
tests/tcg/riscv64/test-signal-handling.c | 226 ++++++++++++++++++++++-
2 files changed, 222 insertions(+), 8 deletions(-)
diff --git a/tests/tcg/riscv64/Makefile.target b/tests/tcg/riscv64/Makefile.target
index f318891396..86b6889a3d 100644
--- a/tests/tcg/riscv64/Makefile.target
+++ b/tests/tcg/riscv64/Makefile.target
@@ -21,5 +21,5 @@ run-test-fcvtmod: QEMU_OPTS += -cpu rv64,d=true,zfa=true
# Test signal handling.
TESTS += test-signal-handling
-test-signal-handling: CFLAGS += -march=rv64gc
-run-test-signal-handling: QEMU_OPTS += -cpu rv64
+test-signal-handling: CFLAGS += -march=rv64gcv
+run-test-signal-handling: QEMU_OPTS += -cpu rv64,v=on
diff --git a/tests/tcg/riscv64/test-signal-handling.c b/tests/tcg/riscv64/test-signal-handling.c
index c202503382..b71fa6ee87 100644
--- a/tests/tcg/riscv64/test-signal-handling.c
+++ b/tests/tcg/riscv64/test-signal-handling.c
@@ -19,10 +19,27 @@
#include <execinfo.h>
#include <unistd.h>
#include <assert.h>
+#include <sys/auxv.h>
+#include <elf.h>
#include <sys/mman.h>
#include <ucontext.h>
#include <asm/sigcontext.h>
+#ifdef __riscv_v_intrinsic
+#include <riscv_vector.h>
+#else
+static inline unsigned long __riscv_vlenb(void)
+{
+ unsigned long vlenb;
+ __asm__ __volatile__ ("csrr %0, vlenb" : "=r" (vlenb));
+ return vlenb;
+}
+#endif
+
+#ifndef COMPAT_HWCAP_ISA_V
+#define COMPAT_HWCAP_ISA_V (1 << ('V' - 'A'))
+#endif
+
/*
* This horrible hack seems to be required when including
* signal.h and asm/sigcontext.h, to prevent sigcontext
@@ -41,6 +58,10 @@ static uint64_t *signal_gvalues;
static double *initial_fvalues;
static double *final_fvalues;
static double *signal_fvalues;
+static size_t vlenb;
+static uint8_t *initial_vvalues;
+static uint8_t *final_vvalues;
+static uint8_t *signal_vvalues;
extern unsigned long unimp_addr[];
@@ -64,6 +85,8 @@ static void ILL_handler(int signo, siginfo_t *info, void *context)
{
ucontext_t *uc = context;
struct sigcontext *sc = (struct sigcontext *)&uc->uc_mcontext;
+ struct __riscv_ctx_hdr *sc_ext = &sc->sc_extdesc.hdr;
+ bool found_v = false;
got_signal = true;
@@ -82,12 +105,48 @@ static void ILL_handler(int signo, siginfo_t *info, void *context)
}
/* Test sc->sc_fpregs.d.fcsr ? */
+ assert(sc->sc_extdesc.reserved == 0);
+ while (sc_ext->magic != END_MAGIC) {
+ assert(sc_ext->size != 0);
+
+ if (sc_ext->magic == RISCV_V_MAGIC) {
+ struct __sc_riscv_v_state *sc_v_state =
+ (struct __sc_riscv_v_state *)(sc_ext + 1);
+ struct __riscv_v_ext_state *v_state = &sc_v_state->v_state;
+
+ found_v = true;
+
+ assert(getauxval(AT_HWCAP) & COMPAT_HWCAP_ISA_V);
+
+ assert(v_state->vlenb == vlenb);
+ assert(v_state->vtype == 0xc0); /* vma, vta */
+ assert(v_state->vl == vlenb);
+ assert(v_state->vstart == 0);
+ assert(v_state->vcsr == 0);
+
+ uint64_t *vregs = v_state->datap;
+ for (int i = 0; i < 32; i++) {
+ for (int j = 0; j < vlenb; j += 8) {
+ size_t idx = (i * vlenb + j) / 8;
+ ((uint64_t *)signal_vvalues)[idx] = vregs[idx];
+ }
+ }
+ }
+
+ sc_ext = (void *)sc_ext + sc_ext->size;
+ }
+
+ assert(sc_ext->size == 0);
+ if (getauxval(AT_HWCAP) & COMPAT_HWCAP_ISA_V) {
+ assert(found_v);
+ }
+
sc->sc_regs.pc += 4;
}
static void init_test(void)
{
- int i;
+ int i, j;
callchain_root = find_callchain_root();
@@ -107,6 +166,19 @@ static void init_test(void)
memset(final_fvalues, 0, 8 * 32);
signal_fvalues = malloc(8 * 32);
memset(signal_fvalues, 0, 8 * 32);
+
+ vlenb = __riscv_vlenb();
+ initial_vvalues = malloc(vlenb * 32);
+ memset(initial_vvalues, 0, vlenb * 32);
+ for (i = 0; i < 32 ; i++) {
+ for (j = 0; j < vlenb; j++) {
+ initial_vvalues[i * vlenb + j] = i * vlenb + j;
+ }
+ }
+ final_vvalues = malloc(vlenb * 32);
+ memset(final_vvalues, 0, vlenb * 32);
+ signal_vvalues = malloc(vlenb * 32);
+ memset(signal_vvalues, 0, vlenb * 32);
}
static void run_test(void)
@@ -179,6 +251,72 @@ static void run_test(void)
"fld f29, 0xe8(t0)\n\t"
"fld f30, 0xf0(t0)\n\t"
"fld f31, 0xf8(t0)\n\t"
+ /* Load initial values into vector registers */
+ "mv t0, %[initial_vvalues]\n\t"
+ "vsetvli x0,%[vlenb],e8,m1,ta,ma\n\t"
+ "vle8.v v0, (t0)\n\t"
+ "add t0, t0, %[vlenb]\n\t"
+ "vle8.v v1, (t0)\n\t"
+ "add t0, t0, %[vlenb]\n\t"
+ "vle8.v v2, (t0)\n\t"
+ "add t0, t0, %[vlenb]\n\t"
+ "vle8.v v3, (t0)\n\t"
+ "add t0, t0, %[vlenb]\n\t"
+ "vle8.v v4, (t0)\n\t"
+ "add t0, t0, %[vlenb]\n\t"
+ "vle8.v v5, (t0)\n\t"
+ "add t0, t0, %[vlenb]\n\t"
+ "vle8.v v6, (t0)\n\t"
+ "add t0, t0, %[vlenb]\n\t"
+ "vle8.v v7, (t0)\n\t"
+ "add t0, t0, %[vlenb]\n\t"
+ "vle8.v v8, (t0)\n\t"
+ "add t0, t0, %[vlenb]\n\t"
+ "vle8.v v9, (t0)\n\t"
+ "add t0, t0, %[vlenb]\n\t"
+ "vle8.v v10, (t0)\n\t"
+ "add t0, t0, %[vlenb]\n\t"
+ "vle8.v v11, (t0)\n\t"
+ "add t0, t0, %[vlenb]\n\t"
+ "vle8.v v12, (t0)\n\t"
+ "add t0, t0, %[vlenb]\n\t"
+ "vle8.v v13, (t0)\n\t"
+ "add t0, t0, %[vlenb]\n\t"
+ "vle8.v v14, (t0)\n\t"
+ "add t0, t0, %[vlenb]\n\t"
+ "vle8.v v15, (t0)\n\t"
+ "add t0, t0, %[vlenb]\n\t"
+ "vle8.v v16, (t0)\n\t"
+ "add t0, t0, %[vlenb]\n\t"
+ "vle8.v v17, (t0)\n\t"
+ "add t0, t0, %[vlenb]\n\t"
+ "vle8.v v18, (t0)\n\t"
+ "add t0, t0, %[vlenb]\n\t"
+ "vle8.v v19, (t0)\n\t"
+ "add t0, t0, %[vlenb]\n\t"
+ "vle8.v v20, (t0)\n\t"
+ "add t0, t0, %[vlenb]\n\t"
+ "vle8.v v21, (t0)\n\t"
+ "add t0, t0, %[vlenb]\n\t"
+ "vle8.v v22, (t0)\n\t"
+ "add t0, t0, %[vlenb]\n\t"
+ "vle8.v v23, (t0)\n\t"
+ "add t0, t0, %[vlenb]\n\t"
+ "vle8.v v24, (t0)\n\t"
+ "add t0, t0, %[vlenb]\n\t"
+ "vle8.v v25, (t0)\n\t"
+ "add t0, t0, %[vlenb]\n\t"
+ "vle8.v v26, (t0)\n\t"
+ "add t0, t0, %[vlenb]\n\t"
+ "vle8.v v27, (t0)\n\t"
+ "add t0, t0, %[vlenb]\n\t"
+ "vle8.v v28, (t0)\n\t"
+ "add t0, t0, %[vlenb]\n\t"
+ "vle8.v v29, (t0)\n\t"
+ "add t0, t0, %[vlenb]\n\t"
+ "vle8.v v30, (t0)\n\t"
+ "add t0, t0, %[vlenb]\n\t"
+ "vle8.v v31, (t0)\n\t"
/* Trigger the SIGILL */
".global unimp_addr\n\t"
"unimp_addr:\n\t"
@@ -251,19 +389,93 @@ static void run_test(void)
"fsd f29, 0xe8(t0)\n\t"
"fsd f30, 0xf0(t0)\n\t"
"fsd f31, 0xf8(t0)\n\t"
+ /* Save final values from vector registers */
+ "mv t0, %[final_vvalues]\n\t"
+ "vse8.v v0, (t0)\n\t"
+ "add t0, t0, %[vlenb]\n\t"
+ "vse8.v v1, (t0)\n\t"
+ "add t0, t0, %[vlenb]\n\t"
+ "vse8.v v2, (t0)\n\t"
+ "add t0, t0, %[vlenb]\n\t"
+ "vse8.v v3, (t0)\n\t"
+ "add t0, t0, %[vlenb]\n\t"
+ "vse8.v v4, (t0)\n\t"
+ "add t0, t0, %[vlenb]\n\t"
+ "vse8.v v5, (t0)\n\t"
+ "add t0, t0, %[vlenb]\n\t"
+ "vse8.v v6, (t0)\n\t"
+ "add t0, t0, %[vlenb]\n\t"
+ "vse8.v v7, (t0)\n\t"
+ "add t0, t0, %[vlenb]\n\t"
+ "vse8.v v8, (t0)\n\t"
+ "add t0, t0, %[vlenb]\n\t"
+ "vse8.v v9, (t0)\n\t"
+ "add t0, t0, %[vlenb]\n\t"
+ "vse8.v v10, (t0)\n\t"
+ "add t0, t0, %[vlenb]\n\t"
+ "vse8.v v11, (t0)\n\t"
+ "add t0, t0, %[vlenb]\n\t"
+ "vse8.v v12, (t0)\n\t"
+ "add t0, t0, %[vlenb]\n\t"
+ "vse8.v v13, (t0)\n\t"
+ "add t0, t0, %[vlenb]\n\t"
+ "vse8.v v14, (t0)\n\t"
+ "add t0, t0, %[vlenb]\n\t"
+ "vse8.v v15, (t0)\n\t"
+ "add t0, t0, %[vlenb]\n\t"
+ "vse8.v v16, (t0)\n\t"
+ "add t0, t0, %[vlenb]\n\t"
+ "vse8.v v17, (t0)\n\t"
+ "add t0, t0, %[vlenb]\n\t"
+ "vse8.v v18, (t0)\n\t"
+ "add t0, t0, %[vlenb]\n\t"
+ "vse8.v v19, (t0)\n\t"
+ "add t0, t0, %[vlenb]\n\t"
+ "vse8.v v20, (t0)\n\t"
+ "add t0, t0, %[vlenb]\n\t"
+ "vse8.v v21, (t0)\n\t"
+ "add t0, t0, %[vlenb]\n\t"
+ "vse8.v v22, (t0)\n\t"
+ "add t0, t0, %[vlenb]\n\t"
+ "vse8.v v23, (t0)\n\t"
+ "add t0, t0, %[vlenb]\n\t"
+ "vse8.v v24, (t0)\n\t"
+ "add t0, t0, %[vlenb]\n\t"
+ "vse8.v v25, (t0)\n\t"
+ "add t0, t0, %[vlenb]\n\t"
+ "vse8.v v26, (t0)\n\t"
+ "add t0, t0, %[vlenb]\n\t"
+ "vse8.v v27, (t0)\n\t"
+ "add t0, t0, %[vlenb]\n\t"
+ "vse8.v v28, (t0)\n\t"
+ "add t0, t0, %[vlenb]\n\t"
+ "vse8.v v29, (t0)\n\t"
+ "add t0, t0, %[vlenb]\n\t"
+ "vse8.v v30, (t0)\n\t"
+ "add t0, t0, %[vlenb]\n\t"
+ "vse8.v v31, (t0)\n\t"
: "=m" (initial_gvalues),
"=m" (final_gvalues),
- "=m" (final_fvalues)
- : "m" (initial_fvalues),
+ "=m" (final_fvalues),
+ "=m" (final_vvalues)
+ : [vlenb] "r" (vlenb),
+ "m" (initial_fvalues),
+ "m" (initial_vvalues),
[initial_gvalues] "r" (initial_gvalues),
[initial_fvalues] "r" (initial_fvalues),
+ [initial_vvalues] "r" (initial_vvalues),
[final_gvalues] "r" (final_gvalues),
- [final_fvalues] "r" (final_fvalues)
+ [final_fvalues] "r" (final_fvalues),
+ [final_vvalues] "r" (final_vvalues)
: "t0",
"f0", "f1", "f2", "f3", "f4", "f5", "f6", "f7",
"f8", "f9", "f10", "f11", "f12", "f13", "f14", "f15",
"f16", "f17", "f18", "f19", "f20", "f21", "f22", "f23",
- "f24", "f25", "f26", "f27", "f28", "f29", "f30", "f31");
+ "f24", "f25", "f26", "f27", "f28", "f29", "f30", "f31",
+ "v0", "v1", "v2", "v3", "v4", "v5", "v6", "v7",
+ "v8", "v9", "v10", "v11", "v12", "v13", "v14", "v15",
+ "v16", "v17", "v18", "v19", "v20", "v21", "v22", "v23",
+ "v24", "v25", "v26", "v27", "v28", "v29", "v30", "v31");
assert(got_signal);
@@ -272,7 +484,7 @@ static void run_test(void)
* and is not a simple equality.
*/
assert(initial_gvalues[4] == (unsigned long)initial_gvalues);
- assert(signal_gvalues[4] == (unsigned long)initial_fvalues);
+ assert(signal_gvalues[4] == (unsigned long)initial_vvalues + 31 * vlenb);
assert(final_gvalues[4] == (unsigned long)final_gvalues);
initial_gvalues[4] = final_gvalues[4] = signal_gvalues[4] = 0;
@@ -284,6 +496,8 @@ static void run_test(void)
assert(!memcmp(initial_gvalues, signal_gvalues, 8 * 31));
assert(!memcmp(initial_fvalues, final_fvalues, 8 * 32));
assert(!memcmp(initial_fvalues, signal_fvalues, 8 * 32));
+ assert(!memcmp(initial_vvalues, signal_vvalues, vlenb * 32));
+ assert(!memcmp(initial_vvalues, final_vvalues, vlenb * 32));
}
int main(void)
--
2.51.0
^ permalink raw reply related [flat|nested] 13+ messages in thread
* Re: [PATCH v3 1/5] tests/tcg/riscv64: Add a user signal handling test
2026-03-21 14:13 ` [PATCH v3 1/5] tests/tcg/riscv64: Add a user signal handling test Nicholas Piggin
@ 2026-03-25 3:07 ` Alistair Francis
0 siblings, 0 replies; 13+ messages in thread
From: Alistair Francis @ 2026-03-25 3:07 UTC (permalink / raw)
To: Nicholas Piggin
Cc: qemu-riscv, qemu-devel, Laurent Vivier, Pierrick Bouvier,
Palmer Dabbelt, Alistair Francis, Weiwei Li,
Daniel Henrique Barboza, Liu Zhiwei, Richard Henderson,
Joel Stanley
On Sun, Mar 22, 2026 at 12:16 AM Nicholas Piggin <npiggin@gmail.com> wrote:
>
> Add a few basic signal handling tests for user emulation.
>
> Signed-off-by: Nicholas Piggin <npiggin@gmail.com>
Reviewed-by: Alistair Francis <alistair.francis@wdc.com>
Alistair
> ---
> tests/tcg/riscv64/Makefile.target | 5 +
> tests/tcg/riscv64/test-signal-handling.c | 303 +++++++++++++++++++++++
> 2 files changed, 308 insertions(+)
> create mode 100644 tests/tcg/riscv64/test-signal-handling.c
>
> diff --git a/tests/tcg/riscv64/Makefile.target b/tests/tcg/riscv64/Makefile.target
> index 4da5b9a3b3..f318891396 100644
> --- a/tests/tcg/riscv64/Makefile.target
> +++ b/tests/tcg/riscv64/Makefile.target
> @@ -18,3 +18,8 @@ TESTS += test-fcvtmod
> test-fcvtmod: CFLAGS += -march=rv64imafdc
> test-fcvtmod: LDFLAGS += -static
> run-test-fcvtmod: QEMU_OPTS += -cpu rv64,d=true,zfa=true
> +
> +# Test signal handling.
> +TESTS += test-signal-handling
> +test-signal-handling: CFLAGS += -march=rv64gc
> +run-test-signal-handling: QEMU_OPTS += -cpu rv64
> diff --git a/tests/tcg/riscv64/test-signal-handling.c b/tests/tcg/riscv64/test-signal-handling.c
> new file mode 100644
> index 0000000000..c202503382
> --- /dev/null
> +++ b/tests/tcg/riscv64/test-signal-handling.c
> @@ -0,0 +1,303 @@
> +/*
> + * Test for linux-user signal handling.
> + *
> + * This ensures that integer and fp register values are
> + * saved as expected in the sigcontext, created by a SIGILL.
> + *
> + * TODO: Register restore is not explicitly verified, except
> + * for advancing pc, and the restoring of registers that were
> + * clobbered by the compiler in the signal handler.
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + */
> +#define _GNU_SOURCE
> +#include <stdlib.h>
> +#include <stdint.h>
> +#include <string.h>
> +#include <stdbool.h>
> +#include <stdio.h>
> +#include <execinfo.h>
> +#include <unistd.h>
> +#include <assert.h>
> +#include <sys/mman.h>
> +#include <ucontext.h>
> +#include <asm/sigcontext.h>
> +
> +/*
> + * This horrible hack seems to be required when including
> + * signal.h and asm/sigcontext.h, to prevent sigcontext
> + * redefinition by bits/sigcontext.h :(
> + *
> + * bits/sigcontext.h does not have the extended state or
> + * RISCV_V_MAGIC, etc. It could have just been introduced
> + * as a new type.
> + */
> +#define _BITS_SIGCONTEXT_H 1
> +#include <signal.h>
> +
> +static uint64_t *initial_gvalues;
> +static uint64_t *final_gvalues;
> +static uint64_t *signal_gvalues;
> +static double *initial_fvalues;
> +static double *final_fvalues;
> +static double *signal_fvalues;
> +
> +extern unsigned long unimp_addr[];
> +
> +static bool got_signal = false;
> +
> +#define BT_BUF_SIZE 100
> +
> +static void *find_callchain_root(void)
> +{
> + int nptrs;
> + void *buffer[BT_BUF_SIZE];
> +
> + nptrs = backtrace(buffer, BT_BUF_SIZE);
> +
> + return buffer[nptrs - 1];
> +}
> +
> +static void *callchain_root;
> +
> +static void ILL_handler(int signo, siginfo_t *info, void *context)
> +{
> + ucontext_t *uc = context;
> + struct sigcontext *sc = (struct sigcontext *)&uc->uc_mcontext;
> +
> + got_signal = true;
> +
> + assert(unimp_addr == info->si_addr);
> + assert(sc->sc_regs.pc == (unsigned long)info->si_addr);
> +
> + /* Ensure stack unwind through the signal frame is not broken */
> + assert(callchain_root == find_callchain_root());
> +
> + for (int i = 0; i < 31; i++) {
> + ((uint64_t *)signal_gvalues)[i] = ((unsigned long *)&sc->sc_regs.ra)[i];
> + }
> +
> + for (int i = 0; i < 32; i++) {
> + ((uint64_t *)signal_fvalues)[i] = sc->sc_fpregs.d.f[i];
> + }
> + /* Test sc->sc_fpregs.d.fcsr ? */
> +
> + sc->sc_regs.pc += 4;
> +}
> +
> +static void init_test(void)
> +{
> + int i;
> +
> + callchain_root = find_callchain_root();
> +
> + initial_gvalues = malloc(8 * 31);
> + memset(initial_gvalues, 0, 8 * 31);
> + final_gvalues = malloc(8 * 31);
> + memset(final_gvalues, 0, 8 * 31);
> + signal_gvalues = malloc(8 * 31);
> + memset(signal_gvalues, 0, 8 * 31);
> +
> + initial_fvalues = malloc(8 * 32);
> + memset(initial_fvalues, 0, 8 * 32);
> + for (i = 0; i < 32 ; i++) {
> + initial_fvalues[i] = 3.142 * (i + 1);
> + }
> + final_fvalues = malloc(8 * 32);
> + memset(final_fvalues, 0, 8 * 32);
> + signal_fvalues = malloc(8 * 32);
> + memset(signal_fvalues, 0, 8 * 32);
> +}
> +
> +static void run_test(void)
> +{
> + asm volatile(
> + /* Save initial values from gp registers */
> + "mv t0, %[initial_gvalues]\n\t"
> + "sd x1, 0x0(t0)\n\t"
> + "sd x2, 0x8(t0)\n\t"
> + "sd x3, 0x10(t0)\n\t"
> + "sd x4, 0x18(t0)\n\t"
> + "sd x5, 0x20(t0)\n\t"
> + "sd x6, 0x28(t0)\n\t"
> + "sd x7, 0x30(t0)\n\t"
> + "sd x8, 0x38(t0)\n\t"
> + "sd x9, 0x40(t0)\n\t"
> + "sd x10, 0x48(t0)\n\t"
> + "sd x11, 0x50(t0)\n\t"
> + "sd x12, 0x58(t0)\n\t"
> + "sd x13, 0x60(t0)\n\t"
> + "sd x14, 0x68(t0)\n\t"
> + "sd x15, 0x70(t0)\n\t"
> + "sd x16, 0x78(t0)\n\t"
> + "sd x17, 0x80(t0)\n\t"
> + "sd x18, 0x88(t0)\n\t"
> + "sd x19, 0x90(t0)\n\t"
> + "sd x20, 0x98(t0)\n\t"
> + "sd x21, 0xa0(t0)\n\t"
> + "sd x22, 0xa8(t0)\n\t"
> + "sd x23, 0xb0(t0)\n\t"
> + "sd x24, 0xb8(t0)\n\t"
> + "sd x25, 0xc0(t0)\n\t"
> + "sd x26, 0xc8(t0)\n\t"
> + "sd x27, 0xd0(t0)\n\t"
> + "sd x28, 0xd8(t0)\n\t"
> + "sd x29, 0xe0(t0)\n\t"
> + "sd x30, 0xe8(t0)\n\t"
> + "sd x31, 0xf0(t0)\n\t"
> + /* Load initial values into float registers */
> + "mv t0, %[initial_fvalues]\n\t"
> + "fld f0, 0x0(t0)\n\t"
> + "fld f1, 0x8(t0)\n\t"
> + "fld f2, 0x10(t0)\n\t"
> + "fld f3, 0x18(t0)\n\t"
> + "fld f4, 0x20(t0)\n\t"
> + "fld f5, 0x28(t0)\n\t"
> + "fld f6, 0x30(t0)\n\t"
> + "fld f7, 0x38(t0)\n\t"
> + "fld f8, 0x40(t0)\n\t"
> + "fld f9, 0x48(t0)\n\t"
> + "fld f10, 0x50(t0)\n\t"
> + "fld f11, 0x58(t0)\n\t"
> + "fld f12, 0x60(t0)\n\t"
> + "fld f13, 0x68(t0)\n\t"
> + "fld f14, 0x70(t0)\n\t"
> + "fld f15, 0x78(t0)\n\t"
> + "fld f16, 0x80(t0)\n\t"
> + "fld f17, 0x88(t0)\n\t"
> + "fld f18, 0x90(t0)\n\t"
> + "fld f19, 0x98(t0)\n\t"
> + "fld f20, 0xa0(t0)\n\t"
> + "fld f21, 0xa8(t0)\n\t"
> + "fld f22, 0xb0(t0)\n\t"
> + "fld f23, 0xb8(t0)\n\t"
> + "fld f24, 0xc0(t0)\n\t"
> + "fld f25, 0xc8(t0)\n\t"
> + "fld f26, 0xd0(t0)\n\t"
> + "fld f27, 0xd8(t0)\n\t"
> + "fld f28, 0xe0(t0)\n\t"
> + "fld f29, 0xe8(t0)\n\t"
> + "fld f30, 0xf0(t0)\n\t"
> + "fld f31, 0xf8(t0)\n\t"
> + /* Trigger the SIGILL */
> +".global unimp_addr\n\t"
> +"unimp_addr:\n\t"
> + "unimp\n\t"
> + "nop\n\t"
> + /* Save final values from gp registers */
> + "mv t0, %[final_gvalues]\n\t"
> + "sd x1, 0x0(t0)\n\t"
> + "sd x2, 0x8(t0)\n\t"
> + "sd x3, 0x10(t0)\n\t"
> + "sd x4, 0x18(t0)\n\t"
> + "sd x5, 0x20(t0)\n\t"
> + "sd x6, 0x28(t0)\n\t"
> + "sd x7, 0x30(t0)\n\t"
> + "sd x8, 0x38(t0)\n\t"
> + "sd x9, 0x40(t0)\n\t"
> + "sd x10, 0x48(t0)\n\t"
> + "sd x11, 0x50(t0)\n\t"
> + "sd x12, 0x58(t0)\n\t"
> + "sd x13, 0x60(t0)\n\t"
> + "sd x14, 0x68(t0)\n\t"
> + "sd x15, 0x70(t0)\n\t"
> + "sd x16, 0x78(t0)\n\t"
> + "sd x17, 0x80(t0)\n\t"
> + "sd x18, 0x88(t0)\n\t"
> + "sd x19, 0x90(t0)\n\t"
> + "sd x20, 0x98(t0)\n\t"
> + "sd x21, 0xa0(t0)\n\t"
> + "sd x22, 0xa8(t0)\n\t"
> + "sd x23, 0xb0(t0)\n\t"
> + "sd x24, 0xb8(t0)\n\t"
> + "sd x25, 0xc0(t0)\n\t"
> + "sd x26, 0xc8(t0)\n\t"
> + "sd x27, 0xd0(t0)\n\t"
> + "sd x28, 0xd8(t0)\n\t"
> + "sd x29, 0xe0(t0)\n\t"
> + "sd x30, 0xe8(t0)\n\t"
> + "sd x31, 0xf0(t0)\n\t"
> + /* Save final values from float registers */
> + "mv t0, %[final_fvalues]\n\t"
> + "fsd f0, 0x0(t0)\n\t"
> + "fsd f1, 0x8(t0)\n\t"
> + "fsd f2, 0x10(t0)\n\t"
> + "fsd f3, 0x18(t0)\n\t"
> + "fsd f4, 0x20(t0)\n\t"
> + "fsd f5, 0x28(t0)\n\t"
> + "fsd f6, 0x30(t0)\n\t"
> + "fsd f7, 0x38(t0)\n\t"
> + "fsd f8, 0x40(t0)\n\t"
> + "fsd f9, 0x48(t0)\n\t"
> + "fsd f10, 0x50(t0)\n\t"
> + "fsd f11, 0x58(t0)\n\t"
> + "fsd f12, 0x60(t0)\n\t"
> + "fsd f13, 0x68(t0)\n\t"
> + "fsd f14, 0x70(t0)\n\t"
> + "fsd f15, 0x78(t0)\n\t"
> + "fsd f16, 0x80(t0)\n\t"
> + "fsd f17, 0x88(t0)\n\t"
> + "fsd f18, 0x90(t0)\n\t"
> + "fsd f19, 0x98(t0)\n\t"
> + "fsd f20, 0xa0(t0)\n\t"
> + "fsd f21, 0xa8(t0)\n\t"
> + "fsd f22, 0xb0(t0)\n\t"
> + "fsd f23, 0xb8(t0)\n\t"
> + "fsd f24, 0xc0(t0)\n\t"
> + "fsd f25, 0xc8(t0)\n\t"
> + "fsd f26, 0xd0(t0)\n\t"
> + "fsd f27, 0xd8(t0)\n\t"
> + "fsd f28, 0xe0(t0)\n\t"
> + "fsd f29, 0xe8(t0)\n\t"
> + "fsd f30, 0xf0(t0)\n\t"
> + "fsd f31, 0xf8(t0)\n\t"
> + : "=m" (initial_gvalues),
> + "=m" (final_gvalues),
> + "=m" (final_fvalues)
> + : "m" (initial_fvalues),
> + [initial_gvalues] "r" (initial_gvalues),
> + [initial_fvalues] "r" (initial_fvalues),
> + [final_gvalues] "r" (final_gvalues),
> + [final_fvalues] "r" (final_fvalues)
> + : "t0",
> + "f0", "f1", "f2", "f3", "f4", "f5", "f6", "f7",
> + "f8", "f9", "f10", "f11", "f12", "f13", "f14", "f15",
> + "f16", "f17", "f18", "f19", "f20", "f21", "f22", "f23",
> + "f24", "f25", "f26", "f27", "f28", "f29", "f30", "f31");
> +
> + assert(got_signal);
> +
> + /*
> + * x4 / t0 is used in the asm so it has to be handled specially
> + * and is not a simple equality.
> + */
> + assert(initial_gvalues[4] == (unsigned long)initial_gvalues);
> + assert(signal_gvalues[4] == (unsigned long)initial_fvalues);
> + assert(final_gvalues[4] == (unsigned long)final_gvalues);
> + initial_gvalues[4] = final_gvalues[4] = signal_gvalues[4] = 0;
> +
> + /*
> + * Ensure registers match before, inside, and after signal
> + * handler.
> + */
> + assert(!memcmp(initial_gvalues, final_gvalues, 8 * 31));
> + assert(!memcmp(initial_gvalues, signal_gvalues, 8 * 31));
> + assert(!memcmp(initial_fvalues, final_fvalues, 8 * 32));
> + assert(!memcmp(initial_fvalues, signal_fvalues, 8 * 32));
> +}
> +
> +int main(void)
> +{
> + struct sigaction act = { 0 };
> +
> + act.sa_flags = SA_SIGINFO;
> + act.sa_sigaction = &ILL_handler;
> + if (sigaction(SIGILL, &act, NULL) == -1) {
> + perror("sigaction");
> + exit(EXIT_FAILURE);
> + }
> +
> + init_test();
> +
> + run_test();
> +}
> --
> 2.51.0
>
>
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH v3 2/5] linux-user/riscv: Allow restore_sigcontext to return error
2026-03-21 14:13 ` [PATCH v3 2/5] linux-user/riscv: Allow restore_sigcontext to return error Nicholas Piggin
@ 2026-03-25 3:07 ` Alistair Francis
0 siblings, 0 replies; 13+ messages in thread
From: Alistair Francis @ 2026-03-25 3:07 UTC (permalink / raw)
To: Nicholas Piggin
Cc: qemu-riscv, qemu-devel, Laurent Vivier, Pierrick Bouvier,
Palmer Dabbelt, Alistair Francis, Weiwei Li,
Daniel Henrique Barboza, Liu Zhiwei, Richard Henderson,
Joel Stanley
On Sun, Mar 22, 2026 at 12:16 AM Nicholas Piggin <npiggin@gmail.com> wrote:
>
> Linux can return error from restore_sigcontext in some conditions, which
> forces a SIGSEGV. Plumb through this error handling which will be used
> by the next change.
>
> Suggested-by: Richard Henderson <richard.henderson@linaro.org>
> Signed-off-by: Nicholas Piggin <npiggin@gmail.com>
Reviewed-by: Alistair Francis <alistair.francis@wdc.com>
Alistair
> ---
> linux-user/riscv/signal.c | 13 +++++++++----
> 1 file changed, 9 insertions(+), 4 deletions(-)
>
> diff --git a/linux-user/riscv/signal.c b/linux-user/riscv/signal.c
> index 22b1b8149f..ece276f85f 100644
> --- a/linux-user/riscv/signal.c
> +++ b/linux-user/riscv/signal.c
> @@ -145,7 +145,7 @@ badframe:
> force_sig(TARGET_SIGSEGV);
> }
>
> -static void restore_sigcontext(CPURISCVState *env, struct target_sigcontext *sc)
> +static bool restore_sigcontext(CPURISCVState *env, struct target_sigcontext *sc)
> {
> int i;
>
> @@ -161,9 +161,11 @@ static void restore_sigcontext(CPURISCVState *env, struct target_sigcontext *sc)
> uint32_t fcsr;
> __get_user(fcsr, &sc->fcsr);
> riscv_csr_write(env, CSR_FCSR, fcsr);
> +
> + return true;
> }
>
> -static void restore_ucontext(CPURISCVState *env, struct target_ucontext *uc)
> +static bool restore_ucontext(CPURISCVState *env, struct target_ucontext *uc)
> {
> sigset_t blocked;
> target_sigset_t target_set;
> @@ -177,7 +179,7 @@ static void restore_ucontext(CPURISCVState *env, struct target_ucontext *uc)
> target_to_host_sigset_internal(&blocked, &target_set);
> set_sigmask(&blocked);
>
> - restore_sigcontext(env, &uc->uc_mcontext);
> + return restore_sigcontext(env, &uc->uc_mcontext);
> }
>
> long do_rt_sigreturn(CPURISCVState *env)
> @@ -191,7 +193,10 @@ long do_rt_sigreturn(CPURISCVState *env)
> goto badframe;
> }
>
> - restore_ucontext(env, &frame->uc);
> + if (!restore_ucontext(env, &frame->uc)) {
> + goto badframe;
> + }
> +
> target_restore_altstack(&frame->uc.uc_stack, env);
>
> unlock_user_struct(frame, frame_addr, 0);
> --
> 2.51.0
>
>
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH v3 3/5] linux-user/riscv: Add extended state to sigcontext
2026-03-21 14:13 ` [PATCH v3 3/5] linux-user/riscv: Add extended state to sigcontext Nicholas Piggin
@ 2026-03-25 3:14 ` Alistair Francis
0 siblings, 0 replies; 13+ messages in thread
From: Alistair Francis @ 2026-03-25 3:14 UTC (permalink / raw)
To: Nicholas Piggin
Cc: qemu-riscv, qemu-devel, Laurent Vivier, Pierrick Bouvier,
Palmer Dabbelt, Alistair Francis, Weiwei Li,
Daniel Henrique Barboza, Liu Zhiwei, Richard Henderson,
Joel Stanley
On Sun, Mar 22, 2026 at 12:16 AM Nicholas Piggin <npiggin@gmail.com> wrote:
>
> Linux/riscv has extended the sigcontext with padding and an
> extended state structure that can save various optional
> features like vector in a flexible format. Update the
> linux-user signal handling to this new structure.
>
> Signed-off-by: Nicholas Piggin <npiggin@gmail.com>
> ---
> linux-user/riscv/signal.c | 93 +++++++++++++++++++++++++++----
> linux-user/riscv/vdso-asmoffset.h | 4 +-
> 2 files changed, 85 insertions(+), 12 deletions(-)
>
> diff --git a/linux-user/riscv/signal.c b/linux-user/riscv/signal.c
> index ece276f85f..e20b9ac177 100644
> --- a/linux-user/riscv/signal.c
> +++ b/linux-user/riscv/signal.c
> @@ -31,14 +31,43 @@
>
> The code below is qemu re-implementation of arch/riscv/kernel/signal.c */
>
> -struct target_sigcontext {
> +struct target_gp_state {
> abi_long pc;
> abi_long gpr[31]; /* x0 is not present, so all offsets must be -1 */
> +};
> +
> +struct target_fp_state {
> uint64_t fpr[32];
> uint32_t fcsr;
> +};
> +
> +/* The Magic number for signal context frame header. */
> +#define END_MAGIC 0x0
> +
> +/* The size of END signal context header. */
> +#define END_HDR_SIZE 0x0
> +
> +struct target_ctx_hdr {
> + uint32_t magic;
> + uint32_t size;
> +};
> +
> +struct target_extra_ext_header {
> + uint32_t __padding[129] __attribute__((aligned(16)));
> + uint32_t reserved;
> + struct target_ctx_hdr hdr;
> +};
> +
> +struct target_sigcontext {
> + struct target_gp_state sc_regs;
> + union {
> + struct target_fp_state sc_fpregs;
> + struct target_extra_ext_header sc_extdesc;
> + };
> }; /* cf. riscv-linux:arch/riscv/include/uapi/asm/ptrace.h */
I would argue that arch/riscv/include/uapi/asm/sigcontext.h is a
better pointer, but both work
Reviewed-by: Alistair Francis <alistair.francis@wdc.com>
Alistair
>
> -QEMU_BUILD_BUG_ON(offsetof(struct target_sigcontext, fpr) != offsetof_freg0);
> +QEMU_BUILD_BUG_ON(offsetof(struct target_sigcontext, sc_fpregs.fpr) !=
> + offsetof_freg0);
>
> struct target_ucontext {
> abi_ulong uc_flags;
> @@ -79,19 +108,26 @@ static abi_ulong get_sigframe(struct target_sigaction *ka,
>
> static void setup_sigcontext(struct target_sigcontext *sc, CPURISCVState *env)
> {
> + struct target_ctx_hdr *hdr;
> int i;
>
> - __put_user(env->pc, &sc->pc);
> + __put_user(env->pc, &sc->sc_regs.pc);
>
> for (i = 1; i < 32; i++) {
> - __put_user(env->gpr[i], &sc->gpr[i - 1]);
> + __put_user(env->gpr[i], &sc->sc_regs.gpr[i - 1]);
> }
> for (i = 0; i < 32; i++) {
> - __put_user(env->fpr[i], &sc->fpr[i]);
> + __put_user(env->fpr[i], &sc->sc_fpregs.fpr[i]);
> }
>
> uint32_t fcsr = riscv_csr_read(env, CSR_FCSR);
> - __put_user(fcsr, &sc->fcsr);
> + __put_user(fcsr, &sc->sc_fpregs.fcsr);
> +
> + __put_user(0, &sc->sc_extdesc.reserved);
> +
> + hdr = &sc->sc_extdesc.hdr;
> + __put_user(END_MAGIC, &hdr->magic);
> + __put_user(END_HDR_SIZE, &hdr->size);
> }
>
> static void setup_ucontext(struct target_ucontext *uc,
> @@ -147,21 +183,58 @@ badframe:
>
> static bool restore_sigcontext(CPURISCVState *env, struct target_sigcontext *sc)
> {
> + struct target_ctx_hdr *hdr;
> + uint32_t rsv, magic, size;
> int i;
>
> - __get_user(env->pc, &sc->pc);
> + __get_user(env->pc, &sc->sc_regs.pc);
>
> for (i = 1; i < 32; ++i) {
> - __get_user(env->gpr[i], &sc->gpr[i - 1]);
> + __get_user(env->gpr[i], &sc->sc_regs.gpr[i - 1]);
> }
> for (i = 0; i < 32; ++i) {
> - __get_user(env->fpr[i], &sc->fpr[i]);
> + __get_user(env->fpr[i], &sc->sc_fpregs.fpr[i]);
> }
>
> uint32_t fcsr;
> - __get_user(fcsr, &sc->fcsr);
> + __get_user(fcsr, &sc->sc_fpregs.fcsr);
> riscv_csr_write(env, CSR_FCSR, fcsr);
>
> + hdr = &sc->sc_extdesc.hdr;
> + __get_user(rsv, &sc->sc_extdesc.reserved);
> + if (rsv != 0) {
> + qemu_log_mask(LOG_GUEST_ERROR, "signal: sigcontext reserved field is "
> + "non-zero\n");
> + return false;
> + }
> +
> + __get_user(magic, &hdr->magic);
> + while (magic != END_MAGIC) {
> + switch (magic) {
> + default:
> + qemu_log_mask(LOG_GUEST_ERROR, "signal: unknown extended state in "
> + "sigcontext, magic=0x%08x\n", magic);
> + return false;
> + }
> +
> + __get_user(size, &hdr->size);
> + if (size == 0) {
> + qemu_log_mask(LOG_GUEST_ERROR, "signal: extended state in "
> + "sigcontext has size 0\n");
> + return false;
> + }
> +
> + hdr = (void *)hdr + size;
> + __get_user(magic, &hdr->magic);
> + }
> +
> + __get_user(size, &hdr->size);
> + if (size != END_HDR_SIZE) {
> + qemu_log_mask(LOG_GUEST_ERROR, "signal: extended state end header has "
> + "size=%u (should be 0)\n", size);
> + return false;
> + }
> +
> return true;
> }
>
> diff --git a/linux-user/riscv/vdso-asmoffset.h b/linux-user/riscv/vdso-asmoffset.h
> index 123902ef61..92e8ac10ab 100644
> --- a/linux-user/riscv/vdso-asmoffset.h
> +++ b/linux-user/riscv/vdso-asmoffset.h
> @@ -1,9 +1,9 @@
> #ifdef TARGET_ABI32
> -# define sizeof_rt_sigframe 0x2b0
> +# define sizeof_rt_sigframe 0x3b0
> # define offsetof_uc_mcontext 0x120
> # define offsetof_freg0 0x80
> #else
> -# define sizeof_rt_sigframe 0x340
> +# define sizeof_rt_sigframe 0x440
> # define offsetof_uc_mcontext 0x130
> # define offsetof_freg0 0x100
> #endif
> --
> 2.51.0
>
>
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH v3 4/5] linux-user/riscv: Add vector state to signal context
2026-03-21 14:13 ` [PATCH v3 4/5] linux-user/riscv: Add vector state to signal context Nicholas Piggin
@ 2026-03-25 3:45 ` Alistair Francis
0 siblings, 0 replies; 13+ messages in thread
From: Alistair Francis @ 2026-03-25 3:45 UTC (permalink / raw)
To: Nicholas Piggin
Cc: qemu-riscv, qemu-devel, Laurent Vivier, Pierrick Bouvier,
Palmer Dabbelt, Alistair Francis, Weiwei Li,
Daniel Henrique Barboza, Liu Zhiwei, Richard Henderson,
Joel Stanley
On Sun, Mar 22, 2026 at 12:16 AM Nicholas Piggin <npiggin@gmail.com> wrote:
>
> This enables vector state to be saved and restored across signals.
>
> Signed-off-by: Nicholas Piggin <npiggin@gmail.com>
Reviewed-by: Alistair Francis <alistair.francis@wdc.com>
Alistair
> ---
> linux-user/riscv/signal.c | 175 +++++++++++++++++++++++++++++++++--
> target/riscv/cpu.h | 4 +
> target/riscv/csr.c | 7 +-
> target/riscv/vector_helper.c | 19 +++-
> 4 files changed, 191 insertions(+), 14 deletions(-)
>
> diff --git a/linux-user/riscv/signal.c b/linux-user/riscv/signal.c
> index e20b9ac177..2e1a1a5027 100644
> --- a/linux-user/riscv/signal.c
> +++ b/linux-user/riscv/signal.c
> @@ -41,7 +41,17 @@ struct target_fp_state {
> uint32_t fcsr;
> };
>
> +struct target_v_ext_state {
> + abi_ulong vstart;
> + abi_ulong vl;
> + abi_ulong vtype;
> + abi_ulong vcsr;
> + abi_ulong vlenb;
> + abi_ptr datap;
> +};
> +
> /* The Magic number for signal context frame header. */
> +#define RISCV_V_MAGIC 0x53465457
> #define END_MAGIC 0x0
>
> /* The size of END signal context header. */
> @@ -106,6 +116,130 @@ static abi_ulong get_sigframe(struct target_sigaction *ka,
> return sp;
> }
>
> +static unsigned int get_v_state_hdr_size(CPURISCVState *env)
> +{
> + return sizeof(struct target_ctx_hdr) +
> + sizeof(struct target_v_ext_state);
> +}
> +
> +static unsigned int get_v_state_data_size(CPURISCVState *env)
> +{
> + RISCVCPU *cpu = env_archcpu(env);
> + return cpu->cfg.vlenb * 32;
> +}
> +
> +static struct target_ctx_hdr *save_v_state(CPURISCVState *env,
> + struct target_ctx_hdr *hdr)
> +{
> + RISCVCPU *cpu = env_archcpu(env);
> + target_ulong vlenb = cpu->cfg.vlenb;
> + uint32_t riscv_v_sc_size = get_v_state_hdr_size(env) +
> + get_v_state_data_size(env);
> + struct target_v_ext_state *vs;
> + abi_ulong vcsr;
> + abi_ptr datap;
> + void *host_datap;
> +
> +#ifdef CONFIG_DEBUG_REMAP
> + /*
> + * The host pointers are derived from lock_user, not g2h, so
> + * h2g can not be used when CONFIG_DEBUG_REMAP=y.
> + */
> + qemu_log_mask(LOG_UNIMP, "signal: sigcontext can not save V state "
> + "when CONFIG_DEBUG_REMAP=y\n");
> + return hdr;
> +#endif
> +
> + vs = (struct target_v_ext_state *)(hdr + 1);
> + vcsr = riscv_csr_read(env, CSR_VCSR);
> + host_datap = (vs + 1);
> + datap = h2g(host_datap);
> +
> + __put_user(RISCV_V_MAGIC, &hdr->magic);
> + __put_user(riscv_v_sc_size, &hdr->size);
> +
> + __put_user(env->vstart, &vs->vstart);
> + __put_user(env->vl, &vs->vl);
> + __put_user(env->vtype, &vs->vtype);
> + __put_user(vcsr, &vs->vcsr);
> + __put_user(vlenb, &vs->vlenb);
> + __put_user(datap, &vs->datap);
> +
> + for (int i = 0; i < 32; i++) {
> + for (int j = 0; j < vlenb; j += 8) {
> + size_t idx = (i * vlenb + j);
> + __put_user(env->vreg[idx / 8],
> + (uint64_t *)(host_datap + idx));
> + }
> + }
> +
> + return (void *)hdr + riscv_v_sc_size;
> +}
> +
> +static bool restore_v_state(CPURISCVState *env,
> + struct target_ctx_hdr *hdr)
> +{
> + RISCVCPU *cpu = env_archcpu(env);
> + target_ulong vlenb;
> + target_ulong vcsr, vl, vtype, vstart;
> + struct target_v_ext_state *vs;
> + uint32_t size;
> + abi_ptr datap;
> + void *host_datap;
> +
> + if (!riscv_has_ext(env, RVV)) {
> + qemu_log_mask(LOG_GUEST_ERROR, "signal: sigcontext has V state but "
> + "CPU does not support V extension\n");
> + return false;
> + }
> +
> + __get_user(size, &hdr->size);
> + if (size < get_v_state_hdr_size(env)) {
> + qemu_log_mask(LOG_GUEST_ERROR, "signal: sigcontext V state header "
> + "size is too small (%u)\n", size);
> + return false;
> + }
> +
> + vs = (struct target_v_ext_state *)(hdr + 1);
> +
> + __get_user(vstart, &vs->vstart);
> + __get_user(vl, &vs->vl);
> + __get_user(vtype, &vs->vtype);
> + __get_user(vcsr, &vs->vcsr);
> +
> + riscv_cpu_set_vstart(env, vstart);
> + riscv_cpu_vsetvl(env, vl, vtype, 0);
> + riscv_csr_write(env, CSR_VCSR, vcsr);
> +
> + __get_user(vlenb, &vs->vlenb);
> +
> + if (vlenb != cpu->cfg.vlenb) {
> + qemu_log_mask(LOG_GUEST_ERROR, "signal: sigcontext has invalid "
> + "vlenb\n");
> + return false;
> + }
> +
> + __get_user(datap, &vs->datap);
> +
> + host_datap = lock_user(VERIFY_READ, datap, vlenb * 32, true);
> + if (!host_datap) {
> + qemu_log_mask(LOG_GUEST_ERROR, "signal: sigcontext has V state but "
> + "datap pointer is invalid\n");
> + return false;
> + }
> +
> + for (int i = 0; i < 32; i++) {
> + for (int j = 0; j < vlenb; j += 8) {
> + size_t idx = (i * vlenb + j);
> + __get_user(env->vreg[idx / 8],
> + (uint64_t *)(host_datap + idx));
> + }
> + }
> + unlock_user(host_datap, datap, 0);
> +
> + return true;
> +}
> +
> static void setup_sigcontext(struct target_sigcontext *sc, CPURISCVState *env)
> {
> struct target_ctx_hdr *hdr;
> @@ -126,6 +260,9 @@ static void setup_sigcontext(struct target_sigcontext *sc, CPURISCVState *env)
> __put_user(0, &sc->sc_extdesc.reserved);
>
> hdr = &sc->sc_extdesc.hdr;
> + if (riscv_has_ext(env, RVV)) {
> + hdr = save_v_state(env, hdr);
> + }
> __put_user(END_MAGIC, &hdr->magic);
> __put_user(END_HDR_SIZE, &hdr->size);
> }
> @@ -152,17 +289,24 @@ void setup_rt_frame(int sig, struct target_sigaction *ka,
> {
> abi_ulong frame_addr;
> struct target_rt_sigframe *frame;
> + size_t frame_size = sizeof(*frame);
>
> - frame_addr = get_sigframe(ka, env, sizeof(*frame));
> + if (riscv_has_ext(env, RVV)) {
> + frame_size += get_v_state_hdr_size(env) +
> + get_v_state_data_size(env);
> + }
> +
> + frame_addr = get_sigframe(ka, env, frame_size);
> trace_user_setup_rt_frame(env, frame_addr);
>
> - if (!lock_user_struct(VERIFY_WRITE, frame, frame_addr, 0)) {
> + frame = lock_user(VERIFY_WRITE, frame_addr, frame_size, 0);
> + if (!frame) {
> goto badframe;
> }
>
> setup_ucontext(&frame->uc, env, set);
> frame->info = *info;
> - unlock_user_struct(frame, frame_addr, 1);
> + unlock_user(frame, frame_addr, frame_size);
>
> env->pc = ka->_sa_handler;
> env->gpr[xSP] = frame_addr;
> @@ -174,7 +318,7 @@ void setup_rt_frame(int sig, struct target_sigaction *ka,
> return;
>
> badframe:
> - unlock_user_struct(frame, frame_addr, 1);
> + unlock_user(frame, frame_addr, frame_size);
> if (sig == TARGET_SIGSEGV) {
> ka->_sa_handler = TARGET_SIG_DFL;
> }
> @@ -211,6 +355,11 @@ static bool restore_sigcontext(CPURISCVState *env, struct target_sigcontext *sc)
> __get_user(magic, &hdr->magic);
> while (magic != END_MAGIC) {
> switch (magic) {
> + case RISCV_V_MAGIC:
> + if (!restore_v_state(env, hdr)) {
> + return false;
> + }
> + break;
> default:
> qemu_log_mask(LOG_GUEST_ERROR, "signal: unknown extended state in "
> "sigcontext, magic=0x%08x\n", magic);
> @@ -258,11 +407,23 @@ static bool restore_ucontext(CPURISCVState *env, struct target_ucontext *uc)
> long do_rt_sigreturn(CPURISCVState *env)
> {
> struct target_rt_sigframe *frame;
> + size_t frame_size = sizeof(*frame);
> abi_ulong frame_addr;
>
> + if (riscv_has_ext(env, RVV)) {
> + /*
> + * userspace may have set up a discontiguous V state data area,
> + * so need to map that region separately once the address is
> + * known, from datap.
> + */
> + frame_size += get_v_state_hdr_size(env);
> + }
> +
> frame_addr = env->gpr[xSP];
> trace_user_do_sigreturn(env, frame_addr);
> - if (!lock_user_struct(VERIFY_READ, frame, frame_addr, 1)) {
> +
> + frame = lock_user(VERIFY_READ, frame_addr, frame_size, 1);
> + if (!frame) {
> goto badframe;
> }
>
> @@ -272,11 +433,11 @@ long do_rt_sigreturn(CPURISCVState *env)
>
> target_restore_altstack(&frame->uc.uc_stack, env);
>
> - unlock_user_struct(frame, frame_addr, 0);
> + unlock_user(frame, frame_addr, 0);
> return -QEMU_ESIGRETURN;
>
> badframe:
> - unlock_user_struct(frame, frame_addr, 0);
> + unlock_user(frame, frame_addr, 0);
> force_sig(TARGET_SIGSEGV);
> return 0;
> }
> diff --git a/target/riscv/cpu.h b/target/riscv/cpu.h
> index 35d1f6362c..e1eca79197 100644
> --- a/target/riscv/cpu.h
> +++ b/target/riscv/cpu.h
> @@ -668,6 +668,10 @@ G_NORETURN void riscv_raise_exception(CPURISCVState *env,
> target_ulong riscv_cpu_get_fflags(CPURISCVState *env);
> void riscv_cpu_set_fflags(CPURISCVState *env, target_ulong);
>
> +void riscv_cpu_set_vstart(CPURISCVState *env, target_ulong val);
> +target_ulong riscv_cpu_vsetvl(CPURISCVState *env, target_ulong s1,
> + target_ulong s2, target_ulong x0);
> +
> #ifndef CONFIG_USER_ONLY
> void cpu_set_exception_base(int vp_index, target_ulong address);
> #endif
> diff --git a/target/riscv/csr.c b/target/riscv/csr.c
> index 5064483917..8a6fd11fb5 100644
> --- a/target/riscv/csr.c
> +++ b/target/riscv/csr.c
> @@ -991,11 +991,8 @@ static RISCVException write_vstart(CPURISCVState *env, int csrno,
> #if !defined(CONFIG_USER_ONLY)
> env->mstatus |= MSTATUS_VS;
> #endif
> - /*
> - * The vstart CSR is defined to have only enough writable bits
> - * to hold the largest element index, i.e. lg2(VLEN) bits.
> - */
> - env->vstart = val & ~(~0ULL << ctzl(riscv_cpu_cfg(env)->vlenb << 3));
> + riscv_cpu_set_vstart(env, val);
> +
> return RISCV_EXCP_NONE;
> }
>
> diff --git a/target/riscv/vector_helper.c b/target/riscv/vector_helper.c
> index caa8dd9c12..bceefe019b 100644
> --- a/target/riscv/vector_helper.c
> +++ b/target/riscv/vector_helper.c
> @@ -33,8 +33,17 @@
> #include "vector_internals.h"
> #include <math.h>
>
> -target_ulong HELPER(vsetvl)(CPURISCVState *env, target_ulong s1,
> - target_ulong s2, target_ulong x0)
> +void riscv_cpu_set_vstart(CPURISCVState *env, target_ulong val)
> +{
> + /*
> + * The vstart CSR is defined to have only enough writable bits
> + * to hold the largest element index, i.e. lg2(VLEN) bits.
> + */
> + env->vstart = val & ~(~0ULL << ctzl(riscv_cpu_cfg(env)->vlenb << 3));
> +}
> +
> +target_ulong riscv_cpu_vsetvl(CPURISCVState *env, target_ulong s1,
> + target_ulong s2, target_ulong x0)
> {
> int vlmax, vl;
> RISCVCPU *cpu = env_archcpu(env);
> @@ -99,6 +108,12 @@ target_ulong HELPER(vsetvl)(CPURISCVState *env, target_ulong s1,
> return vl;
> }
>
> +target_ulong HELPER(vsetvl)(CPURISCVState *env, target_ulong s1,
> + target_ulong s2, target_ulong x0)
> +{
> + return riscv_cpu_vsetvl(env, s1, s2, x0);
> +}
> +
> /*
> * Get the maximum number of elements can be operated.
> *
> --
> 2.51.0
>
>
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH v3 5/5] tests/tcg/riscv64: Add vector state to signal test
2026-03-21 14:13 ` [PATCH v3 5/5] tests/tcg/riscv64: Add vector state to signal test Nicholas Piggin
@ 2026-03-25 3:49 ` Alistair Francis
0 siblings, 0 replies; 13+ messages in thread
From: Alistair Francis @ 2026-03-25 3:49 UTC (permalink / raw)
To: Nicholas Piggin
Cc: qemu-riscv, qemu-devel, Laurent Vivier, Pierrick Bouvier,
Palmer Dabbelt, Alistair Francis, Weiwei Li,
Daniel Henrique Barboza, Liu Zhiwei, Richard Henderson,
Joel Stanley
On Sun, Mar 22, 2026 at 12:16 AM Nicholas Piggin <npiggin@gmail.com> wrote:
>
> Signed-off-by: Nicholas Piggin <npiggin@gmail.com>
Reviewed-by: Alistair Francis <alistair.francis@wdc.com>
Alistair
> ---
> tests/tcg/riscv64/Makefile.target | 4 +-
> tests/tcg/riscv64/test-signal-handling.c | 226 ++++++++++++++++++++++-
> 2 files changed, 222 insertions(+), 8 deletions(-)
>
> diff --git a/tests/tcg/riscv64/Makefile.target b/tests/tcg/riscv64/Makefile.target
> index f318891396..86b6889a3d 100644
> --- a/tests/tcg/riscv64/Makefile.target
> +++ b/tests/tcg/riscv64/Makefile.target
> @@ -21,5 +21,5 @@ run-test-fcvtmod: QEMU_OPTS += -cpu rv64,d=true,zfa=true
>
> # Test signal handling.
> TESTS += test-signal-handling
> -test-signal-handling: CFLAGS += -march=rv64gc
> -run-test-signal-handling: QEMU_OPTS += -cpu rv64
> +test-signal-handling: CFLAGS += -march=rv64gcv
> +run-test-signal-handling: QEMU_OPTS += -cpu rv64,v=on
> diff --git a/tests/tcg/riscv64/test-signal-handling.c b/tests/tcg/riscv64/test-signal-handling.c
> index c202503382..b71fa6ee87 100644
> --- a/tests/tcg/riscv64/test-signal-handling.c
> +++ b/tests/tcg/riscv64/test-signal-handling.c
> @@ -19,10 +19,27 @@
> #include <execinfo.h>
> #include <unistd.h>
> #include <assert.h>
> +#include <sys/auxv.h>
> +#include <elf.h>
> #include <sys/mman.h>
> #include <ucontext.h>
> #include <asm/sigcontext.h>
>
> +#ifdef __riscv_v_intrinsic
> +#include <riscv_vector.h>
> +#else
> +static inline unsigned long __riscv_vlenb(void)
> +{
> + unsigned long vlenb;
> + __asm__ __volatile__ ("csrr %0, vlenb" : "=r" (vlenb));
> + return vlenb;
> +}
> +#endif
> +
> +#ifndef COMPAT_HWCAP_ISA_V
> +#define COMPAT_HWCAP_ISA_V (1 << ('V' - 'A'))
> +#endif
> +
> /*
> * This horrible hack seems to be required when including
> * signal.h and asm/sigcontext.h, to prevent sigcontext
> @@ -41,6 +58,10 @@ static uint64_t *signal_gvalues;
> static double *initial_fvalues;
> static double *final_fvalues;
> static double *signal_fvalues;
> +static size_t vlenb;
> +static uint8_t *initial_vvalues;
> +static uint8_t *final_vvalues;
> +static uint8_t *signal_vvalues;
>
> extern unsigned long unimp_addr[];
>
> @@ -64,6 +85,8 @@ static void ILL_handler(int signo, siginfo_t *info, void *context)
> {
> ucontext_t *uc = context;
> struct sigcontext *sc = (struct sigcontext *)&uc->uc_mcontext;
> + struct __riscv_ctx_hdr *sc_ext = &sc->sc_extdesc.hdr;
> + bool found_v = false;
>
> got_signal = true;
>
> @@ -82,12 +105,48 @@ static void ILL_handler(int signo, siginfo_t *info, void *context)
> }
> /* Test sc->sc_fpregs.d.fcsr ? */
>
> + assert(sc->sc_extdesc.reserved == 0);
> + while (sc_ext->magic != END_MAGIC) {
> + assert(sc_ext->size != 0);
> +
> + if (sc_ext->magic == RISCV_V_MAGIC) {
> + struct __sc_riscv_v_state *sc_v_state =
> + (struct __sc_riscv_v_state *)(sc_ext + 1);
> + struct __riscv_v_ext_state *v_state = &sc_v_state->v_state;
> +
> + found_v = true;
> +
> + assert(getauxval(AT_HWCAP) & COMPAT_HWCAP_ISA_V);
> +
> + assert(v_state->vlenb == vlenb);
> + assert(v_state->vtype == 0xc0); /* vma, vta */
> + assert(v_state->vl == vlenb);
> + assert(v_state->vstart == 0);
> + assert(v_state->vcsr == 0);
> +
> + uint64_t *vregs = v_state->datap;
> + for (int i = 0; i < 32; i++) {
> + for (int j = 0; j < vlenb; j += 8) {
> + size_t idx = (i * vlenb + j) / 8;
> + ((uint64_t *)signal_vvalues)[idx] = vregs[idx];
> + }
> + }
> + }
> +
> + sc_ext = (void *)sc_ext + sc_ext->size;
> + }
> +
> + assert(sc_ext->size == 0);
> + if (getauxval(AT_HWCAP) & COMPAT_HWCAP_ISA_V) {
> + assert(found_v);
> + }
> +
> sc->sc_regs.pc += 4;
> }
>
> static void init_test(void)
> {
> - int i;
> + int i, j;
>
> callchain_root = find_callchain_root();
>
> @@ -107,6 +166,19 @@ static void init_test(void)
> memset(final_fvalues, 0, 8 * 32);
> signal_fvalues = malloc(8 * 32);
> memset(signal_fvalues, 0, 8 * 32);
> +
> + vlenb = __riscv_vlenb();
> + initial_vvalues = malloc(vlenb * 32);
> + memset(initial_vvalues, 0, vlenb * 32);
> + for (i = 0; i < 32 ; i++) {
> + for (j = 0; j < vlenb; j++) {
> + initial_vvalues[i * vlenb + j] = i * vlenb + j;
> + }
> + }
> + final_vvalues = malloc(vlenb * 32);
> + memset(final_vvalues, 0, vlenb * 32);
> + signal_vvalues = malloc(vlenb * 32);
> + memset(signal_vvalues, 0, vlenb * 32);
> }
>
> static void run_test(void)
> @@ -179,6 +251,72 @@ static void run_test(void)
> "fld f29, 0xe8(t0)\n\t"
> "fld f30, 0xf0(t0)\n\t"
> "fld f31, 0xf8(t0)\n\t"
> + /* Load initial values into vector registers */
> + "mv t0, %[initial_vvalues]\n\t"
> + "vsetvli x0,%[vlenb],e8,m1,ta,ma\n\t"
> + "vle8.v v0, (t0)\n\t"
> + "add t0, t0, %[vlenb]\n\t"
> + "vle8.v v1, (t0)\n\t"
> + "add t0, t0, %[vlenb]\n\t"
> + "vle8.v v2, (t0)\n\t"
> + "add t0, t0, %[vlenb]\n\t"
> + "vle8.v v3, (t0)\n\t"
> + "add t0, t0, %[vlenb]\n\t"
> + "vle8.v v4, (t0)\n\t"
> + "add t0, t0, %[vlenb]\n\t"
> + "vle8.v v5, (t0)\n\t"
> + "add t0, t0, %[vlenb]\n\t"
> + "vle8.v v6, (t0)\n\t"
> + "add t0, t0, %[vlenb]\n\t"
> + "vle8.v v7, (t0)\n\t"
> + "add t0, t0, %[vlenb]\n\t"
> + "vle8.v v8, (t0)\n\t"
> + "add t0, t0, %[vlenb]\n\t"
> + "vle8.v v9, (t0)\n\t"
> + "add t0, t0, %[vlenb]\n\t"
> + "vle8.v v10, (t0)\n\t"
> + "add t0, t0, %[vlenb]\n\t"
> + "vle8.v v11, (t0)\n\t"
> + "add t0, t0, %[vlenb]\n\t"
> + "vle8.v v12, (t0)\n\t"
> + "add t0, t0, %[vlenb]\n\t"
> + "vle8.v v13, (t0)\n\t"
> + "add t0, t0, %[vlenb]\n\t"
> + "vle8.v v14, (t0)\n\t"
> + "add t0, t0, %[vlenb]\n\t"
> + "vle8.v v15, (t0)\n\t"
> + "add t0, t0, %[vlenb]\n\t"
> + "vle8.v v16, (t0)\n\t"
> + "add t0, t0, %[vlenb]\n\t"
> + "vle8.v v17, (t0)\n\t"
> + "add t0, t0, %[vlenb]\n\t"
> + "vle8.v v18, (t0)\n\t"
> + "add t0, t0, %[vlenb]\n\t"
> + "vle8.v v19, (t0)\n\t"
> + "add t0, t0, %[vlenb]\n\t"
> + "vle8.v v20, (t0)\n\t"
> + "add t0, t0, %[vlenb]\n\t"
> + "vle8.v v21, (t0)\n\t"
> + "add t0, t0, %[vlenb]\n\t"
> + "vle8.v v22, (t0)\n\t"
> + "add t0, t0, %[vlenb]\n\t"
> + "vle8.v v23, (t0)\n\t"
> + "add t0, t0, %[vlenb]\n\t"
> + "vle8.v v24, (t0)\n\t"
> + "add t0, t0, %[vlenb]\n\t"
> + "vle8.v v25, (t0)\n\t"
> + "add t0, t0, %[vlenb]\n\t"
> + "vle8.v v26, (t0)\n\t"
> + "add t0, t0, %[vlenb]\n\t"
> + "vle8.v v27, (t0)\n\t"
> + "add t0, t0, %[vlenb]\n\t"
> + "vle8.v v28, (t0)\n\t"
> + "add t0, t0, %[vlenb]\n\t"
> + "vle8.v v29, (t0)\n\t"
> + "add t0, t0, %[vlenb]\n\t"
> + "vle8.v v30, (t0)\n\t"
> + "add t0, t0, %[vlenb]\n\t"
> + "vle8.v v31, (t0)\n\t"
> /* Trigger the SIGILL */
> ".global unimp_addr\n\t"
> "unimp_addr:\n\t"
> @@ -251,19 +389,93 @@ static void run_test(void)
> "fsd f29, 0xe8(t0)\n\t"
> "fsd f30, 0xf0(t0)\n\t"
> "fsd f31, 0xf8(t0)\n\t"
> + /* Save final values from vector registers */
> + "mv t0, %[final_vvalues]\n\t"
> + "vse8.v v0, (t0)\n\t"
> + "add t0, t0, %[vlenb]\n\t"
> + "vse8.v v1, (t0)\n\t"
> + "add t0, t0, %[vlenb]\n\t"
> + "vse8.v v2, (t0)\n\t"
> + "add t0, t0, %[vlenb]\n\t"
> + "vse8.v v3, (t0)\n\t"
> + "add t0, t0, %[vlenb]\n\t"
> + "vse8.v v4, (t0)\n\t"
> + "add t0, t0, %[vlenb]\n\t"
> + "vse8.v v5, (t0)\n\t"
> + "add t0, t0, %[vlenb]\n\t"
> + "vse8.v v6, (t0)\n\t"
> + "add t0, t0, %[vlenb]\n\t"
> + "vse8.v v7, (t0)\n\t"
> + "add t0, t0, %[vlenb]\n\t"
> + "vse8.v v8, (t0)\n\t"
> + "add t0, t0, %[vlenb]\n\t"
> + "vse8.v v9, (t0)\n\t"
> + "add t0, t0, %[vlenb]\n\t"
> + "vse8.v v10, (t0)\n\t"
> + "add t0, t0, %[vlenb]\n\t"
> + "vse8.v v11, (t0)\n\t"
> + "add t0, t0, %[vlenb]\n\t"
> + "vse8.v v12, (t0)\n\t"
> + "add t0, t0, %[vlenb]\n\t"
> + "vse8.v v13, (t0)\n\t"
> + "add t0, t0, %[vlenb]\n\t"
> + "vse8.v v14, (t0)\n\t"
> + "add t0, t0, %[vlenb]\n\t"
> + "vse8.v v15, (t0)\n\t"
> + "add t0, t0, %[vlenb]\n\t"
> + "vse8.v v16, (t0)\n\t"
> + "add t0, t0, %[vlenb]\n\t"
> + "vse8.v v17, (t0)\n\t"
> + "add t0, t0, %[vlenb]\n\t"
> + "vse8.v v18, (t0)\n\t"
> + "add t0, t0, %[vlenb]\n\t"
> + "vse8.v v19, (t0)\n\t"
> + "add t0, t0, %[vlenb]\n\t"
> + "vse8.v v20, (t0)\n\t"
> + "add t0, t0, %[vlenb]\n\t"
> + "vse8.v v21, (t0)\n\t"
> + "add t0, t0, %[vlenb]\n\t"
> + "vse8.v v22, (t0)\n\t"
> + "add t0, t0, %[vlenb]\n\t"
> + "vse8.v v23, (t0)\n\t"
> + "add t0, t0, %[vlenb]\n\t"
> + "vse8.v v24, (t0)\n\t"
> + "add t0, t0, %[vlenb]\n\t"
> + "vse8.v v25, (t0)\n\t"
> + "add t0, t0, %[vlenb]\n\t"
> + "vse8.v v26, (t0)\n\t"
> + "add t0, t0, %[vlenb]\n\t"
> + "vse8.v v27, (t0)\n\t"
> + "add t0, t0, %[vlenb]\n\t"
> + "vse8.v v28, (t0)\n\t"
> + "add t0, t0, %[vlenb]\n\t"
> + "vse8.v v29, (t0)\n\t"
> + "add t0, t0, %[vlenb]\n\t"
> + "vse8.v v30, (t0)\n\t"
> + "add t0, t0, %[vlenb]\n\t"
> + "vse8.v v31, (t0)\n\t"
> : "=m" (initial_gvalues),
> "=m" (final_gvalues),
> - "=m" (final_fvalues)
> - : "m" (initial_fvalues),
> + "=m" (final_fvalues),
> + "=m" (final_vvalues)
> + : [vlenb] "r" (vlenb),
> + "m" (initial_fvalues),
> + "m" (initial_vvalues),
> [initial_gvalues] "r" (initial_gvalues),
> [initial_fvalues] "r" (initial_fvalues),
> + [initial_vvalues] "r" (initial_vvalues),
> [final_gvalues] "r" (final_gvalues),
> - [final_fvalues] "r" (final_fvalues)
> + [final_fvalues] "r" (final_fvalues),
> + [final_vvalues] "r" (final_vvalues)
> : "t0",
> "f0", "f1", "f2", "f3", "f4", "f5", "f6", "f7",
> "f8", "f9", "f10", "f11", "f12", "f13", "f14", "f15",
> "f16", "f17", "f18", "f19", "f20", "f21", "f22", "f23",
> - "f24", "f25", "f26", "f27", "f28", "f29", "f30", "f31");
> + "f24", "f25", "f26", "f27", "f28", "f29", "f30", "f31",
> + "v0", "v1", "v2", "v3", "v4", "v5", "v6", "v7",
> + "v8", "v9", "v10", "v11", "v12", "v13", "v14", "v15",
> + "v16", "v17", "v18", "v19", "v20", "v21", "v22", "v23",
> + "v24", "v25", "v26", "v27", "v28", "v29", "v30", "v31");
>
> assert(got_signal);
>
> @@ -272,7 +484,7 @@ static void run_test(void)
> * and is not a simple equality.
> */
> assert(initial_gvalues[4] == (unsigned long)initial_gvalues);
> - assert(signal_gvalues[4] == (unsigned long)initial_fvalues);
> + assert(signal_gvalues[4] == (unsigned long)initial_vvalues + 31 * vlenb);
> assert(final_gvalues[4] == (unsigned long)final_gvalues);
> initial_gvalues[4] = final_gvalues[4] = signal_gvalues[4] = 0;
>
> @@ -284,6 +496,8 @@ static void run_test(void)
> assert(!memcmp(initial_gvalues, signal_gvalues, 8 * 31));
> assert(!memcmp(initial_fvalues, final_fvalues, 8 * 32));
> assert(!memcmp(initial_fvalues, signal_fvalues, 8 * 32));
> + assert(!memcmp(initial_vvalues, signal_vvalues, vlenb * 32));
> + assert(!memcmp(initial_vvalues, final_vvalues, vlenb * 32));
> }
>
> int main(void)
> --
> 2.51.0
>
>
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH v3 0/5] linux-user/riscv: add vector state to signal context
2026-03-21 14:13 [PATCH v3 0/5] linux-user/riscv: add vector state to signal context Nicholas Piggin
` (4 preceding siblings ...)
2026-03-21 14:13 ` [PATCH v3 5/5] tests/tcg/riscv64: Add vector state to signal test Nicholas Piggin
@ 2026-03-25 4:45 ` Alistair Francis
2026-03-26 6:22 ` Nicholas Piggin
5 siblings, 1 reply; 13+ messages in thread
From: Alistair Francis @ 2026-03-25 4:45 UTC (permalink / raw)
To: Nicholas Piggin
Cc: qemu-riscv, qemu-devel, Laurent Vivier, Pierrick Bouvier,
Palmer Dabbelt, Alistair Francis, Weiwei Li,
Daniel Henrique Barboza, Liu Zhiwei, Richard Henderson,
Joel Stanley
On Sun, Mar 22, 2026 at 12:16 AM Nicholas Piggin <npiggin@gmail.com> wrote:
>
> Changes in v3:
> - Fixed up a bunch of good review comments that Richard made against
> the v1 series here https://lore.kernel.org/all/06afcbdc-28e2-4d13-81d4-26fd257ed8a3@linaro.org/#r
> in particular:
> - Restoring vl/vtype state using CPU accessors rather than stuffing the
> values into the registers directly.
> - Fix ABI types.
> - Fix some confusion with host/guest pointer mapping.
> - Fix error handling to SIGSEGV.
> - Style fixes.
> - Fixed build issue without riscv intrinsics header.
> - Add some more test cases.
>
> Changes in v2:
> - Fix 32-bit compile
> - Moved sizeof_rt_sigframe change to its correct patch (patch 2 is the
> one that extends the size of rt_sigframe, patch 3 is adding new
> extended state to the frame which is added dynamically).
>
> This series adds vector state to the linux-user signal handler,
> and adds a basic signal handling test case. As a sanity check, I
> also verified the signal handling test works in the same way when
> run under a real Linux kernel.
>
> Thanks,
> Nick
>
> Nicholas Piggin (5):
> tests/tcg/riscv64: Add a user signal handling test
> linux-user/riscv: Allow restore_sigcontext to return error
> linux-user/riscv: Add extended state to sigcontext
> linux-user/riscv: Add vector state to signal context
> tests/tcg/riscv64: Add vector state to signal test
Can you rebase this on the latest master branch?
Alistair
>
> linux-user/riscv/signal.c | 281 +++++++++++-
> linux-user/riscv/vdso-asmoffset.h | 4 +-
> target/riscv/cpu.h | 4 +
> target/riscv/csr.c | 7 +-
> target/riscv/vector_helper.c | 19 +-
> tests/tcg/riscv64/Makefile.target | 5 +
> tests/tcg/riscv64/test-signal-handling.c | 517 +++++++++++++++++++++++
> 7 files changed, 807 insertions(+), 30 deletions(-)
> create mode 100644 tests/tcg/riscv64/test-signal-handling.c
>
> --
> 2.51.0
>
>
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH v3 0/5] linux-user/riscv: add vector state to signal context
2026-03-25 4:45 ` [PATCH v3 0/5] linux-user/riscv: add vector state to signal context Alistair Francis
@ 2026-03-26 6:22 ` Nicholas Piggin
0 siblings, 0 replies; 13+ messages in thread
From: Nicholas Piggin @ 2026-03-26 6:22 UTC (permalink / raw)
To: Alistair Francis
Cc: qemu-riscv, qemu-devel, Laurent Vivier, Pierrick Bouvier,
Palmer Dabbelt, Alistair Francis, Weiwei Li,
Daniel Henrique Barboza, Liu Zhiwei, Richard Henderson,
Joel Stanley
On Wed, Mar 25, 2026 at 02:45:24PM +1000, Alistair Francis wrote:
> On Sun, Mar 22, 2026 at 12:16 AM Nicholas Piggin <npiggin@gmail.com> wrote:
> >
> > Changes in v3:
> > - Fixed up a bunch of good review comments that Richard made against
> > the v1 series here https://lore.kernel.org/all/06afcbdc-28e2-4d13-81d4-26fd257ed8a3@linaro.org/#r
> > in particular:
> > - Restoring vl/vtype state using CPU accessors rather than stuffing the
> > values into the registers directly.
> > - Fix ABI types.
> > - Fix some confusion with host/guest pointer mapping.
> > - Fix error handling to SIGSEGV.
> > - Style fixes.
> > - Fixed build issue without riscv intrinsics header.
> > - Add some more test cases.
> >
> > Changes in v2:
> > - Fix 32-bit compile
> > - Moved sizeof_rt_sigframe change to its correct patch (patch 2 is the
> > one that extends the size of rt_sigframe, patch 3 is adding new
> > extended state to the frame which is added dynamically).
> >
> > This series adds vector state to the linux-user signal handler,
> > and adds a basic signal handling test case. As a sanity check, I
> > also verified the signal handling test works in the same way when
> > run under a real Linux kernel.
> >
> > Thanks,
> > Nick
> >
> > Nicholas Piggin (5):
> > tests/tcg/riscv64: Add a user signal handling test
> > linux-user/riscv: Allow restore_sigcontext to return error
> > linux-user/riscv: Add extended state to sigcontext
> > linux-user/riscv: Add vector state to signal context
> > tests/tcg/riscv64: Add vector state to signal test
>
> Can you rebase this on the latest master branch?
Will do. I'll give a few more days then send out updates. Thanks
for the reviews.
Thanks,
Nick
^ permalink raw reply [flat|nested] 13+ messages in thread
end of thread, other threads:[~2026-03-26 6:23 UTC | newest]
Thread overview: 13+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-03-21 14:13 [PATCH v3 0/5] linux-user/riscv: add vector state to signal context Nicholas Piggin
2026-03-21 14:13 ` [PATCH v3 1/5] tests/tcg/riscv64: Add a user signal handling test Nicholas Piggin
2026-03-25 3:07 ` Alistair Francis
2026-03-21 14:13 ` [PATCH v3 2/5] linux-user/riscv: Allow restore_sigcontext to return error Nicholas Piggin
2026-03-25 3:07 ` Alistair Francis
2026-03-21 14:13 ` [PATCH v3 3/5] linux-user/riscv: Add extended state to sigcontext Nicholas Piggin
2026-03-25 3:14 ` Alistair Francis
2026-03-21 14:13 ` [PATCH v3 4/5] linux-user/riscv: Add vector state to signal context Nicholas Piggin
2026-03-25 3:45 ` Alistair Francis
2026-03-21 14:13 ` [PATCH v3 5/5] tests/tcg/riscv64: Add vector state to signal test Nicholas Piggin
2026-03-25 3:49 ` Alistair Francis
2026-03-25 4:45 ` [PATCH v3 0/5] linux-user/riscv: add vector state to signal context Alistair Francis
2026-03-26 6:22 ` Nicholas Piggin
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox