diff --git a/arch/riscv/include/asm/kvm_vcpu_vector.h b/arch/riscv/include/asm/kvm_vcpu_vector.h index 57a798a4cb0d7d..e679869e2ba347 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 00cb9c0982b1ae..8c1e64e0dd0b92 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) { @@ -95,7 +96,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); } @@ -198,7 +199,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 +227,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 +234,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 +262,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,23 +287,15 @@ 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_vstate_discard(); - __riscv_v_vstate_dirty(regs); - } } 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); } } @@ -315,18 +303,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_vstate_clean(regs); + riscv_v_disable(); } } 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); } } @@ -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 { @@ -395,6 +396,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)); } } @@ -420,12 +422,13 @@ 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) #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/kernel_mode_vector.c b/arch/riscv/kernel/kernel_mode_vector.c index 99972a48e86bc4..b612793d69798f 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/kernel/ptrace.c b/arch/riscv/kernel/ptrace.c index 793bcee4618282..097800876fb0eb 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 59784dc117e454..0da352310b841b 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 b112166d51e9f5..6fd541f5d5cbbf 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; @@ -221,7 +258,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; diff --git a/arch/riscv/kvm/vcpu_vector.c b/arch/riscv/kvm/vcpu_vector.c index 62d2fb77bb9b93..da6c6db846c11f 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) diff --git a/tools/testing/selftests/riscv/sigreturn/sigreturn.c b/tools/testing/selftests/riscv/sigreturn/sigreturn.c index e10873d95fedb0..c7d8cac80efbf6 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 1479abc0c9cba4..7952885a04c71b 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(); }