From 46871b6fbeace552875b1d230fa4b69788339237 Mon Sep 17 00:00:00 2001 From: Andy Chiu Date: Thu, 21 May 2026 11:25:17 -0500 Subject: [PATCH 1/4] riscv: vector: refactor vector context operations Lift riscv_v_{enable,disable} out of __*vstate_{save,restore,discard} so that we can reuse some functions without repeatedly turning on/off vector. Also, refactor and document about the user context save in preempt_v to make code more readable. Signed-off-by: Andy Chiu Signed-off-by: Linux RISC-V bot --- arch/riscv/include/asm/kvm_vcpu_vector.h | 8 +++++-- arch/riscv/include/asm/vector.h | 15 +++++++------ arch/riscv/kernel/kernel_mode_vector.c | 27 +++++++++++++++++++----- arch/riscv/kvm/vcpu_vector.c | 8 +++---- 4 files changed, 40 insertions(+), 18 deletions(-) diff --git a/arch/riscv/include/asm/kvm_vcpu_vector.h b/arch/riscv/include/asm/kvm_vcpu_vector.h index 57a798a4cb0d7d5..e679869e2ba3470 100644 --- a/arch/riscv/include/asm/kvm_vcpu_vector.h +++ b/arch/riscv/include/asm/kvm_vcpu_vector.h @@ -16,14 +16,18 @@ #include #include -static __always_inline void __kvm_riscv_vector_save(struct kvm_cpu_context *context) +static __always_inline void kvm_riscv_vector_save(struct kvm_cpu_context *context) { + riscv_v_enable(); __riscv_v_vstate_save(&context->vector, context->vector.datap); + riscv_v_disable(); } -static __always_inline void __kvm_riscv_vector_restore(struct kvm_cpu_context *context) +static __always_inline void kvm_riscv_vector_restore(struct kvm_cpu_context *context) { + riscv_v_enable(); __riscv_v_vstate_restore(&context->vector, context->vector.datap); + riscv_v_disable(); } void kvm_riscv_vcpu_vector_reset(struct kvm_vcpu *vcpu); diff --git a/arch/riscv/include/asm/vector.h b/arch/riscv/include/asm/vector.h index 00cb9c0982b1aeb..45820cd900d6219 100644 --- a/arch/riscv/include/asm/vector.h +++ b/arch/riscv/include/asm/vector.h @@ -198,7 +198,6 @@ static inline void __riscv_v_vstate_save(struct __riscv_v_ext_state *save_to, { unsigned long vl; - riscv_v_enable(); __vstate_csr_save(save_to); if (has_xtheadvector()) { asm volatile ( @@ -227,7 +226,6 @@ static inline void __riscv_v_vstate_save(struct __riscv_v_ext_state *save_to, ".option pop\n\t" : "=&r" (vl) : "r" (datap) : "memory"); } - riscv_v_disable(); } static inline void __riscv_v_vstate_restore(struct __riscv_v_ext_state *restore_from, @@ -235,7 +233,6 @@ static inline void __riscv_v_vstate_restore(struct __riscv_v_ext_state *restore_ { unsigned long vl; - riscv_v_enable(); if (has_xtheadvector()) { asm volatile ( "mv t0, %0\n\t" @@ -264,14 +261,12 @@ static inline void __riscv_v_vstate_restore(struct __riscv_v_ext_state *restore_ : "=&r" (vl) : "r" (datap) : "memory"); } __vstate_csr_restore(restore_from); - riscv_v_disable(); } static inline void __riscv_v_vstate_discard(void) { unsigned long vl, vtype_inval = 1UL << (BITS_PER_LONG - 1); - riscv_v_enable(); if (has_xtheadvector()) asm volatile (THEAD_VSETVLI_T4X0E8M8D1 : : : "t4"); else @@ -291,14 +286,14 @@ static inline void __riscv_v_vstate_discard(void) "vsetvl %0, x0, %1\n\t" ".option pop\n\t" : "=&r" (vl) : "r" (vtype_inval)); - - riscv_v_disable(); } static inline void riscv_v_vstate_discard(struct pt_regs *regs) { if (riscv_v_vstate_query(regs)) { + riscv_v_enable(); __riscv_v_vstate_discard(); + riscv_v_disable(); __riscv_v_vstate_dirty(regs); } } @@ -307,7 +302,9 @@ static inline void riscv_v_vstate_save(struct __riscv_v_ext_state *vstate, struct pt_regs *regs) { if (__riscv_v_vstate_check(regs->status, DIRTY)) { + riscv_v_enable(); __riscv_v_vstate_save(vstate, vstate->datap); + riscv_v_disable(); __riscv_v_vstate_clean(regs); } } @@ -316,7 +313,9 @@ static inline void riscv_v_vstate_restore(struct __riscv_v_ext_state *vstate, struct pt_regs *regs) { if (riscv_v_vstate_query(regs)) { + riscv_v_enable(); __riscv_v_vstate_restore(vstate, vstate->datap); + riscv_v_disable(); __riscv_v_vstate_clean(regs); } } @@ -378,8 +377,10 @@ static inline void __switch_to_vector(struct task_struct *prev, prev->thread.riscv_v_flags |= RISCV_PREEMPT_V_IN_SCHEDULE; } if (riscv_preempt_v_dirty(prev)) { + riscv_v_enable(); __riscv_v_vstate_save(&prev->thread.kernel_vstate, prev->thread.kernel_vstate.datap); + riscv_v_disable(); riscv_preempt_v_clear_dirty(prev); } } else { diff --git a/arch/riscv/kernel/kernel_mode_vector.c b/arch/riscv/kernel/kernel_mode_vector.c index 99972a48e86bc42..b612793d69798fd 100644 --- a/arch/riscv/kernel/kernel_mode_vector.c +++ b/arch/riscv/kernel/kernel_mode_vector.c @@ -134,20 +134,35 @@ static int riscv_v_start_kernel_context(bool *is_nested) *is_nested = true; get_cpu_vector_context(); if (riscv_preempt_v_dirty(current)) { + riscv_v_enable(); __riscv_v_vstate_save(kvstate, kvstate->datap); + riscv_v_disable(); riscv_preempt_v_clear_dirty(current); } riscv_preempt_v_set_restore(current); return 0; } - /* Transfer the ownership of V from user to kernel, then save */ - riscv_v_start(RISCV_PREEMPT_V | RISCV_PREEMPT_V_DIRTY); + /* + * Skip saving user's context if it is not DIRTY. We would have to start KMV in "dirty" if + * this check is performed after KMV starts, to protect user's ctx. Then, we could waste + * time saving already "clean" context once KMV is started in "dirty". + */ if (__riscv_v_vstate_check(task_pt_regs(current)->status, DIRTY)) { - uvstate = ¤t->thread.vstate; - __riscv_v_vstate_save(uvstate, uvstate->datap); + /* Transfer the ownership of V from user to kernel, then save */ + riscv_v_start(RISCV_PREEMPT_V | RISCV_PREEMPT_V_DIRTY); + /* + * Calling the guarded version of vstate_save to make the code cleaner. Also, the + * vstate check within the call is necessary as context switch may happen between + * __riscv_v_vstate_check and riscv_v_start. In such case we are not supposed to + * save the context again. + */ + riscv_v_vstate_save(¤t->thread.vstate, task_pt_regs(current)); + riscv_preempt_v_clear_dirty(current); + return 0; } - riscv_preempt_v_clear_dirty(current); + + riscv_v_start(RISCV_PREEMPT_V); return 0; } @@ -180,7 +195,9 @@ asmlinkage void riscv_v_context_nesting_end(struct pt_regs *regs) depth = riscv_v_ctx_get_depth(); if (depth == 0) { if (riscv_preempt_v_restore(current)) { + riscv_v_enable(); __riscv_v_vstate_restore(vstate, vstate->datap); + riscv_v_disable(); __riscv_v_vstate_clean(regs); riscv_preempt_v_reset_flags(); } diff --git a/arch/riscv/kvm/vcpu_vector.c b/arch/riscv/kvm/vcpu_vector.c index 62d2fb77bb9b931..da6c6db846c11fb 100644 --- a/arch/riscv/kvm/vcpu_vector.c +++ b/arch/riscv/kvm/vcpu_vector.c @@ -46,7 +46,7 @@ void kvm_riscv_vcpu_guest_vector_save(struct kvm_cpu_context *cntx, { if ((cntx->sstatus & SR_VS) == SR_VS_DIRTY) { if (riscv_isa_extension_available(isa, v)) - __kvm_riscv_vector_save(cntx); + kvm_riscv_vector_save(cntx); kvm_riscv_vcpu_vector_clean(cntx); } } @@ -56,7 +56,7 @@ void kvm_riscv_vcpu_guest_vector_restore(struct kvm_cpu_context *cntx, { if ((cntx->sstatus & SR_VS) != SR_VS_OFF) { if (riscv_isa_extension_available(isa, v)) - __kvm_riscv_vector_restore(cntx); + kvm_riscv_vector_restore(cntx); kvm_riscv_vcpu_vector_clean(cntx); } } @@ -65,13 +65,13 @@ void kvm_riscv_vcpu_host_vector_save(struct kvm_cpu_context *cntx) { /* No need to check host sstatus as it can be modified outside */ if (!kvm_riscv_isa_check_host(V)) - __kvm_riscv_vector_save(cntx); + kvm_riscv_vector_save(cntx); } void kvm_riscv_vcpu_host_vector_restore(struct kvm_cpu_context *cntx) { if (!kvm_riscv_isa_check_host(V)) - __kvm_riscv_vector_restore(cntx); + kvm_riscv_vector_restore(cntx); } int kvm_riscv_vcpu_alloc_vector_context(struct kvm_vcpu *vcpu) From ec7291a3f23fb54555c2dfefd8ac126a545623f7 Mon Sep 17 00:00:00 2001 From: daichengrong Date: Thu, 21 May 2026 11:25:18 -0500 Subject: [PATCH 2/4] riscv: clarify vector state semantics on syscall and context switch The RISC-V vector specification states that executing a system call causes all caller-saved vector registers (v0-v31, vl, vtype) and vstart to become unspecified. Currently, after calling riscv_v_vstate_discard(), the vector state may still be marked as DIRTY, which can mislead the context switch logic into treating the registers as containing valid user data. This patch clarifies and tightens the kernel-side semantics: 1. On syscall entry, the kernel checks the vector state via sstatus and explicitly set it to INIT, indicating that the vector registers no longer contain meaningful user data. 2. During context switch, the vector state is saved only if the state is DIRTY. (no change) 3. On restore, if the state is INIT, the vector registers are treated as invalid and are not restored from memory. Instead, they are overwritten with a known initial value to avoid data leaakge. Signed-off-by: daichengrong Co-developed-by: Andy Chiu Signed-off-by: Andy Chiu Signed-off-by: Linux RISC-V bot --- arch/riscv/include/asm/vector.h | 32 ++++++++++++++++---------------- arch/riscv/kernel/vector.c | 2 +- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/arch/riscv/include/asm/vector.h b/arch/riscv/include/asm/vector.h index 45820cd900d6219..769f307798675be 100644 --- a/arch/riscv/include/asm/vector.h +++ b/arch/riscv/include/asm/vector.h @@ -95,7 +95,7 @@ static inline void riscv_v_vstate_off(struct pt_regs *regs) regs->status = __riscv_v_vstate_or(regs->status, OFF); } -static inline void riscv_v_vstate_on(struct pt_regs *regs) +static inline void riscv_v_vstate_init(struct pt_regs *regs) { regs->status = __riscv_v_vstate_or(regs->status, INITIAL); } @@ -288,16 +288,6 @@ static inline void __riscv_v_vstate_discard(void) : "=&r" (vl) : "r" (vtype_inval)); } -static inline void riscv_v_vstate_discard(struct pt_regs *regs) -{ - if (riscv_v_vstate_query(regs)) { - riscv_v_enable(); - __riscv_v_vstate_discard(); - riscv_v_disable(); - __riscv_v_vstate_dirty(regs); - } -} - static inline void riscv_v_vstate_save(struct __riscv_v_ext_state *vstate, struct pt_regs *regs) { @@ -312,20 +302,29 @@ static inline void riscv_v_vstate_save(struct __riscv_v_ext_state *vstate, static inline void riscv_v_vstate_restore(struct __riscv_v_ext_state *vstate, struct pt_regs *regs) { - if (riscv_v_vstate_query(regs)) { + if (__riscv_v_vstate_check(regs->status, INITIAL)) { + riscv_v_enable(); + __riscv_v_vstate_discard(); + riscv_v_disable(); + } else if (__riscv_v_vstate_check(regs->status, CLEAN)) { riscv_v_enable(); __riscv_v_vstate_restore(vstate, vstate->datap); riscv_v_disable(); - __riscv_v_vstate_clean(regs); } } static inline void riscv_v_vstate_set_restore(struct task_struct *task, struct pt_regs *regs) { - if (riscv_v_vstate_query(regs)) { + if (riscv_v_vstate_query(regs)) set_tsk_thread_flag(task, TIF_RISCV_V_DEFER_RESTORE); - riscv_v_vstate_on(regs); +} + +static inline void riscv_v_vstate_discard(struct pt_regs *regs) +{ + if (riscv_v_vstate_query(regs)) { + riscv_v_vstate_set_restore(current, regs); + riscv_v_vstate_init(regs); } } @@ -396,6 +395,7 @@ static inline void __switch_to_vector(struct task_struct *prev, riscv_preempt_v_set_restore(next); } } else { + /* VS is never DIRTY at this point, there's no need to alter vstate here */ riscv_v_vstate_set_restore(next, task_pt_regs(next)); } } @@ -421,7 +421,7 @@ static inline bool riscv_v_vstate_ctrl_user_allowed(void) { return false; } #define riscv_v_vstate_restore(vstate, regs) do {} while (0) #define __switch_to_vector(__prev, __next) do {} while (0) #define riscv_v_vstate_off(regs) do {} while (0) -#define riscv_v_vstate_on(regs) do {} while (0) +#define riscv_v_vstate_init(regs) do {} while (0) #define riscv_v_thread_free(tsk) do {} while (0) #define riscv_v_setup_ctx_cache() do {} while (0) #define riscv_v_thread_alloc(tsk) do {} while (0) diff --git a/arch/riscv/kernel/vector.c b/arch/riscv/kernel/vector.c index b112166d51e9f58..4eef51f6d43294a 100644 --- a/arch/riscv/kernel/vector.c +++ b/arch/riscv/kernel/vector.c @@ -221,7 +221,7 @@ bool riscv_v_first_use_handler(struct pt_regs *regs) return true; } - riscv_v_vstate_on(regs); + __riscv_v_vstate_clean(regs); riscv_v_vstate_set_restore(current, regs); return true; From 2e571065f261b3d5993e3d4f08451f8a4b99a929 Mon Sep 17 00:00:00 2001 From: Andy Chiu Date: Thu, 21 May 2026 11:25:19 -0500 Subject: [PATCH 3/4] riscv: vector: adjust ptrace and signal behavior for INITIAL state The last patch introduced the INITIAL vector state to avoid saving and restoring vector registers across syscall boundaries. However, this optimization did not fully account for the ptrace and signal handling interfaces. As a result, two issues emerged: 1. Ptrace reads at syscall stop could observe stale, non-nulled registers. 2. Modifications to the ucontext through signal interface during a syscall stop would be overwritten by the vector discaring macro. This patch introduces riscv_v_ucontext_save() to synchronize these paths with the INITIAL state: - Ptrace reads during a syscall stop now explicitly execute the hardware discard macro and return the discarded state to prevent data leaks. - Ptrace writes (PTRACE_SETREGSET) during a syscall stop are silently dropped (returning 0). Returning an error like EINVAL would break debbugers like GDB, which disables the optional regset on receiving such error. - Signal handling (rt_sigreturn) now honor user-space modifications to the vector context (for user-space thread schedulers). CC: Sergey Matyukevich CC: gdb@sourceware.org Signed-off-by: Andy Chiu Signed-off-by: Linux RISC-V bot --- arch/riscv/include/asm/vector.h | 2 ++ arch/riscv/kernel/ptrace.c | 13 ++++++------ arch/riscv/kernel/signal.c | 11 ++++++---- arch/riscv/kernel/vector.c | 37 +++++++++++++++++++++++++++++++++ 4 files changed, 53 insertions(+), 10 deletions(-) diff --git a/arch/riscv/include/asm/vector.h b/arch/riscv/include/asm/vector.h index 769f307798675be..8c1e64e0dd0b925 100644 --- a/arch/riscv/include/asm/vector.h +++ b/arch/riscv/include/asm/vector.h @@ -52,6 +52,7 @@ void riscv_v_thread_free(struct task_struct *tsk); void __init riscv_v_setup_ctx_cache(void); void riscv_v_thread_alloc(struct task_struct *tsk); void __init update_regset_vector_info(unsigned long size); +void riscv_v_ucontext_save(struct task_struct *tsk); static inline u32 riscv_v_flags(void) { @@ -427,6 +428,7 @@ static inline bool riscv_v_vstate_ctrl_user_allowed(void) { return false; } #define riscv_v_thread_alloc(tsk) do {} while (0) #define get_cpu_vector_context() do {} while (0) #define put_cpu_vector_context() do {} while (0) +#define riscv_v_ucontext_save(tsk) do {} while (0) #define riscv_v_vstate_set_restore(task, regs) do {} while (0) #endif /* CONFIG_RISCV_ISA_V */ diff --git a/arch/riscv/kernel/ptrace.c b/arch/riscv/kernel/ptrace.c index 793bcee46182829..097800876fb0eb9 100644 --- a/arch/riscv/kernel/ptrace.c +++ b/arch/riscv/kernel/ptrace.c @@ -109,11 +109,7 @@ static int riscv_vr_get(struct task_struct *target, * Ensure the vector registers have been saved to the memory before * copying them to membuf. */ - if (target == current) { - get_cpu_vector_context(); - riscv_v_vstate_save(¤t->thread.vstate, task_pt_regs(current)); - put_cpu_vector_context(); - } + riscv_v_ucontext_save(target); ptrace_vstate.vstart = vstate->vstart; ptrace_vstate.vl = vstate->vl; @@ -222,13 +218,18 @@ static int riscv_vr_set(struct task_struct *target, int ret; struct __riscv_v_ext_state *vstate = &target->thread.vstate; struct __riscv_v_regset_state ptrace_vstate; + struct pt_regs *regs = task_pt_regs(target); if (!(has_vector() || has_xtheadvector())) return -EINVAL; - if (!riscv_v_vstate_query(task_pt_regs(target))) + if (!riscv_v_vstate_query(regs)) return -ENODATA; + /* Silently drop the modification to tracee as no vreg lives across a syscall */ + if (__riscv_v_vstate_check(regs->status, INITIAL)) + return 0; + /* Copy rest of the vstate except datap */ ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf, &ptrace_vstate, 0, sizeof(struct __riscv_v_regset_state)); diff --git a/arch/riscv/kernel/signal.c b/arch/riscv/kernel/signal.c index 59784dc117e454e..0da352310b841bc 100644 --- a/arch/riscv/kernel/signal.c +++ b/arch/riscv/kernel/signal.c @@ -89,9 +89,7 @@ static long save_v_state(struct pt_regs *regs, void __user *sc_vec) /* datap is designed to be 16 byte aligned for better performance */ WARN_ON(!IS_ALIGNED((unsigned long)datap, 16)); - get_cpu_vector_context(); - riscv_v_vstate_save(¤t->thread.vstate, regs); - put_cpu_vector_context(); + riscv_v_ucontext_save(current); /* Copy everything of vstate but datap. */ err = __copy_to_user(&state->v_state, ¤t->thread.vstate, @@ -121,9 +119,14 @@ static long __restore_v_state(struct pt_regs *regs, void __user *sc_vec) /* * Mark the vstate as clean prior performing the actual copy, * to avoid getting the vstate incorrectly clobbered by the - * discarded vector state. + * discarded vector state. + * + * This also allows user to modify vregs through the signal + * interface at a syscall stop. e.g. to support user space + * context switching. */ riscv_v_vstate_set_restore(current, regs); + __riscv_v_vstate_clean(regs); /* Copy everything of __sc_riscv_v_state except datap. */ err = __copy_from_user(¤t->thread.vstate, &state->v_state, diff --git a/arch/riscv/kernel/vector.c b/arch/riscv/kernel/vector.c index 4eef51f6d43294a..6fd541f5d5cbbfd 100644 --- a/arch/riscv/kernel/vector.c +++ b/arch/riscv/kernel/vector.c @@ -29,6 +29,43 @@ static struct kmem_cache *riscv_v_kernel_cachep; unsigned long riscv_v_vsize __read_mostly; EXPORT_SYMBOL_GPL(riscv_v_vsize); +/* + * Context memory is not coherent to register when sstatus.vs is set to INITIAL. This function + * take the INITIAL state into consideration and reflect the nulled state into context memory. + * Assume the target task is not actively running when tsk != current + */ +void riscv_v_ucontext_save(struct task_struct *tsk) +{ + struct __riscv_v_ext_state *vstate = &tsk->thread.vstate; + struct pt_regs *regs = task_pt_regs(tsk); + + /* + * Do not set vstate as clean when it is INITIAL, otherwise we lose track of the nulled + * state in ptrace. + */ + if (tsk == current) { + get_cpu_vector_context(); + if (__riscv_v_vstate_check(regs->status, INITIAL)) { + riscv_v_enable(); + __riscv_v_vstate_discard(); + __riscv_v_vstate_save(vstate, vstate->datap); + riscv_v_disable(); + } else { + riscv_v_vstate_save(vstate, regs); + } + put_cpu_vector_context(); + } else if (__riscv_v_vstate_check(regs->status, INITIAL)) { + /* + * If we are not current and VS == INITIAL, null out the context memory for tsk + * using kernel mode vector. + */ + kernel_vector_begin(); + __riscv_v_vstate_discard(); + __riscv_v_vstate_save(vstate, vstate->datap); + kernel_vector_end(); + } +} + int riscv_v_setup_vsize(void) { unsigned long this_vsize; From 3a0be60731c1b2c7e8a250d040b4d74c8fc070aa Mon Sep 17 00:00:00 2001 From: Andy Chiu Date: Thu, 21 May 2026 11:25:20 -0500 Subject: [PATCH 4/4] selftests: riscv: extend vector tests for sigreturn and ptrace 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 Signed-off-by: Linux RISC-V bot --- .../selftests/riscv/sigreturn/sigreturn.c | 78 ++++++++++++++ .../selftests/riscv/vector/vstate_ptrace.c | 100 +++++++++++++++++- 2 files changed, 177 insertions(+), 1 deletion(-) diff --git a/tools/testing/selftests/riscv/sigreturn/sigreturn.c b/tools/testing/selftests/riscv/sigreturn/sigreturn.c index e10873d95fedb0e..c7d8cac80efbf6f 100644 --- a/tools/testing/selftests/riscv/sigreturn/sigreturn.c +++ b/tools/testing/selftests/riscv/sigreturn/sigreturn.c @@ -3,10 +3,12 @@ #include #include #include +#include #include #include "kselftest_harness.h" #define RISCV_V_MAGIC 0x53465457 +#define END_MAGIC 0 #define DEFAULT_VALUE 2 #define SIGNAL_HANDLER_OVERRIDE 3 @@ -61,6 +63,82 @@ 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) +{ + ucontext_t *context = vcontext; + struct __riscv_extra_ext_header *ext; + 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) { + struct __riscv_v_ext_state *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"); + + /* Setup a 10ms timer to unblock us from pause() */ + struct itimerval itv = {{0, 0}, {0, 10000}}; + + setitimer(ITIMER_REAL, &itv, NULL); + + pause(); + + /* + * 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 1479abc0c9cba40..7952885a04c71b5 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; @@ -109,11 +144,62 @@ 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); + } else if (WIFSTOPPED(status) && (WSTOPSIG(status) == SIGTRAP)) { + size_t size; + void *data, *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); + data = realloc(data, size + v_regset_hdr->vlenb * 32); + if (!data) + goto out; + 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 +216,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(); }