* [PATCH v9] KVM: selftests: riscv: Add lazy V extension enablement for guests [not found] <20260722073344.771230-1-jinrui@haiwei.tech> @ 2026-08-11 8:50 ` JinRui 2026-08-12 17:26 ` Anup Patel 2026-08-13 9:03 ` [PATCH v10] " JinRui 0 siblings, 2 replies; 9+ messages in thread From: JinRui @ 2026-08-11 8:50 UTC (permalink / raw) To: anup, pbonzini, shuah, paul.walmsley, palmer, aou Cc: atish.patra, alex, sashiko-bot, kvm, kvm-riscv, linux-riscv, linux-kselftest, linux-kernel, jinrui From: jinrui <jinrui@haiwei.tech> When the cross-compiler defaults to an -march that includes the V (vector) extension, -O2 auto-vectorization generates vector instructions (e.g. vsetvli, vadd.vv) in Guest binary code. If the Guest executes a vector instruction while sstatus.VS is Off, an EXC_INST_ILLEGAL (scause=2) is raised. KVM's hedeleg delegates this exception to the Guest, but the selftest has no way to handle it as a bare-metal program, causing all Guest tests to fail. In contrast, a real OS kernel handles this via riscv_v_first_use_handler(), which detects the vector instruction, sets sstatus.VS to Initial, and srets to re-execute. Fix this with four changes in processor.c: 1. Delete the now-unused guest_unexp_trap() handler, which is replaced by the full exception vector table. 2. In vm_arch_vcpu_add(), notify KVM that the Guest is allowed to use the V extension via __vcpu_set_reg(V, 1) (best-effort, silently ignores errors on hardware without V). Also replace the raw stvec handler with the full exception vector table, which provides save_context/restore_context for safe lazy enablement. 3. In route_exception(), add a lazy V enablement check that runs before any test-registered handler. When the cause is a non-IRQ EXC_INST_ILLEGAL and sstatus.VS is Off, set VS to Initial and return so the faulting instruction is re-executed via sret. Track the epc per-vCPU via a flexible array member to avoid infinite loops on hardware without V and eliminate races between concurrent vCPUs. 4. Make vm_init_vector_tables() idempotent by checking vm->handlers before allocation, so tests that manually call it (ebreak_test, arch_timer, sbi_pmu_test) do not leak memory. The check runs before test-registered EXC_INST_ILLEGAL handlers (e.g. sbi_pmu_test) to ensure V enablement takes priority. Tested on a riscv64 host with KVM enabled: all 20 KVM selftest binaries pass (arch_timer, ebreak_test, steal_time, get-reg-list, sbi_pmu_test, etc.). Signed-off-by: jinrui <jinrui@haiwei.tech> --- Changes in v9: - Size the per-vCPU epc array by KVM_CAP_MAX_VCPU_ID instead of KVM_CAP_MAX_VCPUS, since vcpu_id may be sparse and exceed the vCPU count (found by sashiko-bot review). .../selftests/kvm/lib/riscv/processor.c | 115 +++++++++++++++--- 1 file changed, 100 insertions(+), 15 deletions(-) diff --git a/tools/testing/selftests/kvm/lib/riscv/processor.c b/tools/testing/selftests/kvm/lib/riscv/processor.c index ded5429f3..05e20ef40 100644 --- a/tools/testing/selftests/kvm/lib/riscv/processor.c +++ b/tools/testing/selftests/kvm/lib/riscv/processor.c @@ -17,6 +17,13 @@ static gva_t exception_handlers; +struct handlers { + exception_handler_fn exception_handlers[NR_VECTORS][NR_EXCEPTIONS]; + bool v_available; + unsigned int v_epc_capacity; + unsigned long v_epc[]; +}; + bool __vcpu_has_ext(struct kvm_vcpu *vcpu, u64 ext) { unsigned long value = 0; @@ -298,13 +305,6 @@ void vcpu_arch_dump(FILE *stream, struct kvm_vcpu *vcpu, u8 indent) core.regs.t3, core.regs.t4, core.regs.t5, core.regs.t6); } -static void __aligned(16) guest_unexp_trap(void) -{ - sbi_ecall(KVM_RISCV_SELFTESTS_SBI_EXT, - KVM_RISCV_SELFTESTS_SBI_UNEXP, - 0, 0, 0, 0, 0, 0); -} - void vcpu_arch_set_entry_point(struct kvm_vcpu *vcpu, void *guest_code) { vcpu_set_reg(vcpu, RISCV_CORE_REG(regs.pc), (unsigned long)guest_code); @@ -348,8 +348,33 @@ struct kvm_vcpu *vm_arch_vcpu_add(struct kvm_vm *vm, u32 vcpu_id) /* Setup sscratch for guest_get_vcpuid() */ vcpu_set_reg(vcpu, RISCV_GENERAL_CSR_REG(sscratch), vcpu_id); - /* Setup default exception vector of guest */ - vcpu_set_reg(vcpu, RISCV_GENERAL_CSR_REG(stvec), (unsigned long)guest_unexp_trap); + /* + * Enable the V (vector) extension in KVM so that the compiler can + * safely generate vector instructions (e.g. via -O2 auto- + * vectorization). Silently ignore errors; the test will still work + * without V. + */ + __vcpu_set_reg(vcpu, RISCV_ISA_EXT_REG(KVM_RISCV_ISA_EXT_V), 1); + + /* + * Use the full exception vector table (which provides lazy V + * extension enablement for EXC_INST_ILLEGAL in route_exception) + * as the default exception handler. vm_init_vector_tables() is + * idempotent; tests that call it again will get a no-op. + */ + vm_init_vector_tables(vm); + vcpu_init_vector_tables(vcpu); + + /* + * Record V extension availability in the handlers struct so that + * route_exception() (called from Guest context) can check it + * without relying on a host-side global variable. + */ + { + struct handlers *h = addr_gva2hva(vm, vm->handlers); + + h->v_available = __vcpu_has_isa_ext(vcpu, KVM_RISCV_ISA_EXT_V); + } return vcpu; } @@ -408,19 +433,17 @@ void assert_on_unhandled_exception(struct kvm_vcpu *vcpu) struct ucall uc; if (get_ucall(vcpu, &uc) == UCALL_UNHANDLED) { + vcpu_dump(stderr, vcpu, 2); TEST_FAIL("Unexpected exception (vector:0x%lx, ec:0x%lx)", uc.args[0], uc.args[1]); } } -struct handlers { - exception_handler_fn exception_handlers[NR_VECTORS][NR_EXCEPTIONS]; -}; - void route_exception(struct pt_regs *regs) { struct handlers *handlers = (struct handlers *)exception_handlers; - int vector = 0, ec; + int vector = 0; + unsigned long ec; ec = regs->cause & ~CAUSE_IRQ_FLAG; if (ec >= NR_EXCEPTIONS) @@ -432,6 +455,46 @@ void route_exception(struct pt_regs *regs) ec = 0; } + /* + * Handle V (vector) extension lazy enablement before any + * registered handler. The compiler's default march may include + * V, and auto-vectorization generates vector instructions that + * trigger EXC_INST_ILLEGAL when VS (Vector Status) in sstatus + * is Off. Enable VS to Initial and re-execute the faulting + * instruction, mimicking what a real OS kernel does. + * + * This check runs before any test-registered handler, so tests + * that install their own EXC_INST_ILLEGAL handler (e.g. + * sbi_pmu_test) are not affected. + */ + if (!(regs->cause & CAUSE_IRQ_FLAG) && ec == EXC_INST_ILLEGAL) { + /* + * If KVM supports the V extension for this Guest and VS + * (Vector Status) is Off in the saved sstatus, set it to + * Initial and re-execute the faulting instruction. + * + * Use regs->status (saved at exception entry) rather than + * reading the live CSR to avoid a TOCTOU race. + * + * Track the epc per-vCPU to avoid an infinite loop when + * V is disabled or the hardware rejects the VS change. + * Using a per-vCPU array avoids races between concurrent + * vCPUs that would occur with a single shared (epc, vcpu) + * pair. + */ + if (handlers && handlers->v_available && !(regs->status & SR_VS)) { + unsigned int vcpu_id; + + asm volatile("csrr %0, sscratch" : "=r" (vcpu_id)); + if (vcpu_id < handlers->v_epc_capacity && + handlers->v_epc[vcpu_id] != regs->epc) { + handlers->v_epc[vcpu_id] = regs->epc; + regs->status |= SR_VS_INITIAL; + return; + } + } + } + if (handlers && handlers->exception_handlers[vector][ec]) return handlers->exception_handlers[vector][ec](regs); @@ -448,9 +511,31 @@ void vcpu_init_vector_tables(struct kvm_vcpu *vcpu) void vm_init_vector_tables(struct kvm_vm *vm) { - vm->handlers = __vm_alloc(vm, sizeof(struct handlers), vm->page_size, + unsigned int max_vcpu_id; + size_t size; + + if (vm->handlers) + return; + + max_vcpu_id = kvm_check_cap(KVM_CAP_MAX_VCPU_ID); + if (max_vcpu_id == 0) + max_vcpu_id = 512; + + /* + * vcpu_id may be sparse and ranges from 0 to KVM_CAP_MAX_VCPU_ID + * (which can be much larger than KVM_CAP_MAX_VCPUS), so size the + * per-vCPU epc array accordingly. + */ + size = sizeof(struct handlers) + (max_vcpu_id + 1) * sizeof(unsigned long); + vm->handlers = __vm_alloc(vm, size, vm->page_size, MEM_REGION_DATA); + { + struct handlers *h = addr_gva2hva(vm, vm->handlers); + + h->v_epc_capacity = max_vcpu_id + 1; + } + *(gva_t *)addr_gva2hva(vm, (gva_t)(&exception_handlers)) = vm->handlers; } -- 2.53.0 ^ permalink raw reply related [flat|nested] 9+ messages in thread
* Re: [PATCH v9] KVM: selftests: riscv: Add lazy V extension enablement for guests 2026-08-11 8:50 ` [PATCH v9] KVM: selftests: riscv: Add lazy V extension enablement for guests JinRui @ 2026-08-12 17:26 ` Anup Patel 2026-08-13 9:03 ` [PATCH v10] " JinRui 1 sibling, 0 replies; 9+ messages in thread From: Anup Patel @ 2026-08-12 17:26 UTC (permalink / raw) To: JinRui Cc: pbonzini, shuah, paul.walmsley, palmer, aou, atish.patra, alex, sashiko-bot, kvm, kvm-riscv, linux-riscv, linux-kselftest, linux-kernel On Tue, Aug 11, 2026 at 2:21 PM JinRui <jinrui@haiwei.tech> wrote: > > From: jinrui <jinrui@haiwei.tech> > > When the cross-compiler defaults to an -march that includes the V > (vector) extension, -O2 auto-vectorization generates vector > instructions (e.g. vsetvli, vadd.vv) in Guest binary code. If the > Guest executes a vector instruction while sstatus.VS is Off, an > EXC_INST_ILLEGAL (scause=2) is raised. KVM's hedeleg delegates this > exception to the Guest, but the selftest has no way to handle it as a > bare-metal program, causing all Guest tests to fail. > > In contrast, a real OS kernel handles this via riscv_v_first_use_handler(), > which detects the vector instruction, sets sstatus.VS to Initial, and > srets to re-execute. > > Fix this with four changes in processor.c: > > 1. Delete the now-unused guest_unexp_trap() handler, which is replaced > by the full exception vector table. > > 2. In vm_arch_vcpu_add(), notify KVM that the Guest is allowed to use > the V extension via __vcpu_set_reg(V, 1) (best-effort, silently > ignores errors on hardware without V). Also replace the raw stvec > handler with the full exception vector table, which provides > save_context/restore_context for safe lazy enablement. > > 3. In route_exception(), add a lazy V enablement check that runs > before any test-registered handler. When the cause is a non-IRQ > EXC_INST_ILLEGAL and sstatus.VS is Off, set VS to Initial and > return so the faulting instruction is re-executed via sret. > Track the epc per-vCPU via a flexible array member to avoid > infinite loops on hardware without V and eliminate races between > concurrent vCPUs. > > 4. Make vm_init_vector_tables() idempotent by checking vm->handlers > before allocation, so tests that manually call it (ebreak_test, > arch_timer, sbi_pmu_test) do not leak memory. > > The check runs before test-registered EXC_INST_ILLEGAL handlers > (e.g. sbi_pmu_test) to ensure V enablement takes priority. > > Tested on a riscv64 host with KVM enabled: all 20 KVM selftest > binaries pass (arch_timer, ebreak_test, steal_time, get-reg-list, > sbi_pmu_test, etc.). > > Signed-off-by: jinrui <jinrui@haiwei.tech> > --- > Changes in v9: > - Size the per-vCPU epc array by KVM_CAP_MAX_VCPU_ID instead of > KVM_CAP_MAX_VCPUS, since vcpu_id may be sparse and exceed the vCPU > count (found by sashiko-bot review). > > .../selftests/kvm/lib/riscv/processor.c | 115 +++++++++++++++--- > 1 file changed, 100 insertions(+), 15 deletions(-) > > diff --git a/tools/testing/selftests/kvm/lib/riscv/processor.c b/tools/testing/selftests/kvm/lib/riscv/processor.c > index ded5429f3..05e20ef40 100644 > --- a/tools/testing/selftests/kvm/lib/riscv/processor.c > +++ b/tools/testing/selftests/kvm/lib/riscv/processor.c > @@ -17,6 +17,13 @@ > > static gva_t exception_handlers; > > +struct handlers { > + exception_handler_fn exception_handlers[NR_VECTORS][NR_EXCEPTIONS]; > + bool v_available; > + unsigned int v_epc_capacity; > + unsigned long v_epc[]; > +}; > + > bool __vcpu_has_ext(struct kvm_vcpu *vcpu, u64 ext) > { > unsigned long value = 0; > @@ -298,13 +305,6 @@ void vcpu_arch_dump(FILE *stream, struct kvm_vcpu *vcpu, u8 indent) > core.regs.t3, core.regs.t4, core.regs.t5, core.regs.t6); > } > > -static void __aligned(16) guest_unexp_trap(void) > -{ > - sbi_ecall(KVM_RISCV_SELFTESTS_SBI_EXT, > - KVM_RISCV_SELFTESTS_SBI_UNEXP, > - 0, 0, 0, 0, 0, 0); > -} > - > void vcpu_arch_set_entry_point(struct kvm_vcpu *vcpu, void *guest_code) > { > vcpu_set_reg(vcpu, RISCV_CORE_REG(regs.pc), (unsigned long)guest_code); > @@ -348,8 +348,33 @@ struct kvm_vcpu *vm_arch_vcpu_add(struct kvm_vm *vm, u32 vcpu_id) > /* Setup sscratch for guest_get_vcpuid() */ > vcpu_set_reg(vcpu, RISCV_GENERAL_CSR_REG(sscratch), vcpu_id); > > - /* Setup default exception vector of guest */ > - vcpu_set_reg(vcpu, RISCV_GENERAL_CSR_REG(stvec), (unsigned long)guest_unexp_trap); > + /* > + * Enable the V (vector) extension in KVM so that the compiler can > + * safely generate vector instructions (e.g. via -O2 auto- > + * vectorization). Silently ignore errors; the test will still work > + * without V. > + */ > + __vcpu_set_reg(vcpu, RISCV_ISA_EXT_REG(KVM_RISCV_ISA_EXT_V), 1); > + > + /* > + * Use the full exception vector table (which provides lazy V > + * extension enablement for EXC_INST_ILLEGAL in route_exception) > + * as the default exception handler. vm_init_vector_tables() is > + * idempotent; tests that call it again will get a no-op. > + */ > + vm_init_vector_tables(vm); > + vcpu_init_vector_tables(vcpu); > + > + /* > + * Record V extension availability in the handlers struct so that > + * route_exception() (called from Guest context) can check it > + * without relying on a host-side global variable. > + */ > + { > + struct handlers *h = addr_gva2hva(vm, vm->handlers); > + > + h->v_available = __vcpu_has_isa_ext(vcpu, KVM_RISCV_ISA_EXT_V); > + } > > return vcpu; > } > @@ -408,19 +433,17 @@ void assert_on_unhandled_exception(struct kvm_vcpu *vcpu) > struct ucall uc; > > if (get_ucall(vcpu, &uc) == UCALL_UNHANDLED) { > + vcpu_dump(stderr, vcpu, 2); > TEST_FAIL("Unexpected exception (vector:0x%lx, ec:0x%lx)", > uc.args[0], uc.args[1]); > } > } > > -struct handlers { > - exception_handler_fn exception_handlers[NR_VECTORS][NR_EXCEPTIONS]; > -}; > - > void route_exception(struct pt_regs *regs) > { > struct handlers *handlers = (struct handlers *)exception_handlers; > - int vector = 0, ec; > + int vector = 0; > + unsigned long ec; > > ec = regs->cause & ~CAUSE_IRQ_FLAG; > if (ec >= NR_EXCEPTIONS) > @@ -432,6 +455,46 @@ void route_exception(struct pt_regs *regs) > ec = 0; > } > > + /* > + * Handle V (vector) extension lazy enablement before any > + * registered handler. The compiler's default march may include > + * V, and auto-vectorization generates vector instructions that > + * trigger EXC_INST_ILLEGAL when VS (Vector Status) in sstatus > + * is Off. Enable VS to Initial and re-execute the faulting > + * instruction, mimicking what a real OS kernel does. > + * > + * This check runs before any test-registered handler, so tests > + * that install their own EXC_INST_ILLEGAL handler (e.g. > + * sbi_pmu_test) are not affected. > + */ > + if (!(regs->cause & CAUSE_IRQ_FLAG) && ec == EXC_INST_ILLEGAL) { > + /* > + * If KVM supports the V extension for this Guest and VS > + * (Vector Status) is Off in the saved sstatus, set it to > + * Initial and re-execute the faulting instruction. > + * > + * Use regs->status (saved at exception entry) rather than > + * reading the live CSR to avoid a TOCTOU race. > + * > + * Track the epc per-vCPU to avoid an infinite loop when > + * V is disabled or the hardware rejects the VS change. > + * Using a per-vCPU array avoids races between concurrent > + * vCPUs that would occur with a single shared (epc, vcpu) > + * pair. > + */ > + if (handlers && handlers->v_available && !(regs->status & SR_VS)) { Treating all illegal exceptions as V instruction traps is not the right the way. We may have genuinely unexpected traps for which guest_unexp_trap() should be called. > + unsigned int vcpu_id; > + > + asm volatile("csrr %0, sscratch" : "=r" (vcpu_id)); > + if (vcpu_id < handlers->v_epc_capacity && > + handlers->v_epc[vcpu_id] != regs->epc) { > + handlers->v_epc[vcpu_id] = regs->epc; > + regs->status |= SR_VS_INITIAL; > + return; > + } > + } > + } > + > if (handlers && handlers->exception_handlers[vector][ec]) > return handlers->exception_handlers[vector][ec](regs); > > @@ -448,9 +511,31 @@ void vcpu_init_vector_tables(struct kvm_vcpu *vcpu) > > void vm_init_vector_tables(struct kvm_vm *vm) > { > - vm->handlers = __vm_alloc(vm, sizeof(struct handlers), vm->page_size, > + unsigned int max_vcpu_id; > + size_t size; > + > + if (vm->handlers) > + return; > + > + max_vcpu_id = kvm_check_cap(KVM_CAP_MAX_VCPU_ID); > + if (max_vcpu_id == 0) > + max_vcpu_id = 512; > + > + /* > + * vcpu_id may be sparse and ranges from 0 to KVM_CAP_MAX_VCPU_ID > + * (which can be much larger than KVM_CAP_MAX_VCPUS), so size the > + * per-vCPU epc array accordingly. > + */ > + size = sizeof(struct handlers) + (max_vcpu_id + 1) * sizeof(unsigned long); > + vm->handlers = __vm_alloc(vm, size, vm->page_size, > MEM_REGION_DATA); > > + { > + struct handlers *h = addr_gva2hva(vm, vm->handlers); > + > + h->v_epc_capacity = max_vcpu_id + 1; > + } > + > *(gva_t *)addr_gva2hva(vm, (gva_t)(&exception_handlers)) = vm->handlers; > } > > -- > 2.53.0 > Regards, Anup ^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v10] KVM: selftests: riscv: Add lazy V extension enablement for guests 2026-08-11 8:50 ` [PATCH v9] KVM: selftests: riscv: Add lazy V extension enablement for guests JinRui 2026-08-12 17:26 ` Anup Patel @ 2026-08-13 9:03 ` JinRui 2026-08-13 9:13 ` sashiko-bot 2026-08-13 9:37 ` [PATCH v11] " JinRui 1 sibling, 2 replies; 9+ messages in thread From: JinRui @ 2026-08-13 9:03 UTC (permalink / raw) To: anup, pbonzini, shuah, paul.walmsley, palmer, aou Cc: atish.patra, alex, sashiko-bot, kvm, kvm-riscv, linux-riscv, linux-kselftest, linux-kernel, jinrui From: jinrui <jinrui@haiwei.tech> When the cross-compiler defaults to an -march that includes the V (vector) extension, -O2 auto-vectorization generates vector instructions (e.g. vsetvli, vadd.vv) in guest code. Executing such an instruction with sstatus.VS Off raises EXC_INST_ILLEGAL (scause=2); KVM's hedeleg forwards it to the guest, but the bare-metal selftest cannot handle it, so all guest tests fail. A real kernel handles this via riscv_v_first_use_handler(), which enables V and re-executes the instruction. Fix it in processor.c: 1. Delete the now-unused guest_unexp_trap() handler, replaced by the full exception vector table. 2. In vm_arch_vcpu_add(), advertise V to KVM via __vcpu_set_reg(V, 1) (best-effort, errors ignored on hardware without V) and install the full exception vector table instead of a raw stvec handler. 3. In route_exception(), decode the faulting instruction (stval) with insn_is_vector() and, when it is a vector instruction while sstatus.VS is Off, set VS to Initial and sret to re-execute it, before any test-registered handler. Genuinely illegal instructions still reach the unexpected-exception path. 4. Make vm_init_vector_tables() idempotent by checking vm->handlers before allocating, so tests that call it directly (ebreak_test, arch_timer, sbi_pmu_test) do not leak memory. Tested on a riscv64 host with KVM enabled. Signed-off-by: jinrui <jinrui@haiwei.tech> --- Changes in v10: - Decode the faulting instruction (insn_is_vector()) and only lazily enable V for actual vector instructions, so genuinely illegal instructions still reach the unexpected-exception path (Anup). - Drop the per-vCPU v_epc[] guard and KVM_CAP_MAX_VCPU_ID sizing, as they are redundant with v_available and the sstatus.VS check. .../selftests/kvm/include/riscv/processor.h | 13 +++ .../selftests/kvm/lib/riscv/processor.c | 79 ++++++++++++++++--- 2 files changed, 79 insertions(+), 13 deletions(-) diff --git a/tools/testing/selftests/kvm/include/riscv/processor.h b/tools/testing/selftests/kvm/include/riscv/processor.h index e3acf2ae9881..685baefebdb1 100644 --- a/tools/testing/selftests/kvm/include/riscv/processor.h +++ b/tools/testing/selftests/kvm/include/riscv/processor.h @@ -25,6 +25,19 @@ #define GET_RM(insn) (((insn) & INSN_MASK_FUNCT3) >> INSN_SHIFT_FUNCT3) #define GET_CSR_NUM(insn) (((insn) & INSN_CSR_MASK) >> INSN_CSR_SHIFT) +/* Vector (V) instruction decoding, matching arch/riscv/include/asm/insn.h */ +#define RV_INSN_OPCODE_MASK 0x7f +#define RVG_OPCODE_SYSTEM 0x73 +#define RVV_OPCODE_VECTOR 0x57 +#define RVV_OPCODE_VL 0x07 +#define RVV_OPCODE_VS 0x27 +#define RVV_VL_VS_WIDTH_8 0 +#define RVV_VL_VS_WIDTH_16 5 +#define RVV_VL_VS_WIDTH_32 6 +#define RVV_VL_VS_WIDTH_64 7 +#define RVV_EXTRACT_VL_VS_WIDTH(insn) (((insn) >> 12) & 0x7) +#define RVG_EXTRACT_SYSTEM_CSR(insn) (((insn) >> 20) & 0xfff) + static inline u64 __kvm_reg_id(u64 type, u64 subtype, u64 idx, u64 size) { return KVM_REG_RISCV | type | subtype | idx | size; diff --git a/tools/testing/selftests/kvm/lib/riscv/processor.c b/tools/testing/selftests/kvm/lib/riscv/processor.c index ded5429f3448..ffd84212c9b0 100644 --- a/tools/testing/selftests/kvm/lib/riscv/processor.c +++ b/tools/testing/selftests/kvm/lib/riscv/processor.c @@ -17,6 +17,11 @@ static gva_t exception_handlers; +struct handlers { + exception_handler_fn exception_handlers[NR_VECTORS][NR_EXCEPTIONS]; + bool v_available; +}; + bool __vcpu_has_ext(struct kvm_vcpu *vcpu, u64 ext) { unsigned long value = 0; @@ -298,13 +303,6 @@ void vcpu_arch_dump(FILE *stream, struct kvm_vcpu *vcpu, u8 indent) core.regs.t3, core.regs.t4, core.regs.t5, core.regs.t6); } -static void __aligned(16) guest_unexp_trap(void) -{ - sbi_ecall(KVM_RISCV_SELFTESTS_SBI_EXT, - KVM_RISCV_SELFTESTS_SBI_UNEXP, - 0, 0, 0, 0, 0, 0); -} - void vcpu_arch_set_entry_point(struct kvm_vcpu *vcpu, void *guest_code) { vcpu_set_reg(vcpu, RISCV_CORE_REG(regs.pc), (unsigned long)guest_code); @@ -348,8 +346,22 @@ struct kvm_vcpu *vm_arch_vcpu_add(struct kvm_vm *vm, u32 vcpu_id) /* Setup sscratch for guest_get_vcpuid() */ vcpu_set_reg(vcpu, RISCV_GENERAL_CSR_REG(sscratch), vcpu_id); - /* Setup default exception vector of guest */ - vcpu_set_reg(vcpu, RISCV_GENERAL_CSR_REG(stvec), (unsigned long)guest_unexp_trap); + /* + * Advertise V to KVM so -O2 auto-vectorization in guest code is valid; + * ignore errors since the tests work without V too. Use the full + * exception vector table (which lazily enables V in route_exception()) + * as the default handler; vm_init_vector_tables() is idempotent. + */ + __vcpu_set_reg(vcpu, RISCV_ISA_EXT_REG(KVM_RISCV_ISA_EXT_V), 1); + vm_init_vector_tables(vm); + vcpu_init_vector_tables(vcpu); + + /* Record V availability for route_exception(), which runs in guest context. */ + { + struct handlers *h = addr_gva2hva(vm, vm->handlers); + + h->v_available = __vcpu_has_isa_ext(vcpu, KVM_RISCV_ISA_EXT_V); + } return vcpu; } @@ -408,19 +420,43 @@ void assert_on_unhandled_exception(struct kvm_vcpu *vcpu) struct ucall uc; if (get_ucall(vcpu, &uc) == UCALL_UNHANDLED) { + vcpu_dump(stderr, vcpu, 2); TEST_FAIL("Unexpected exception (vector:0x%lx, ec:0x%lx)", uc.args[0], uc.args[1]); } } -struct handlers { - exception_handler_fn exception_handlers[NR_VECTORS][NR_EXCEPTIONS]; -}; +static bool insn_is_vector(u32 insn) +{ + u32 opcode = insn & RV_INSN_OPCODE_MASK; + u32 width, csr; + + /* All V-related instructions are 4-byte, i.e. not compressed. */ + if ((insn & 0x3) != 0x3) + return false; + + switch (opcode) { + case RVV_OPCODE_VECTOR: + return true; + case RVV_OPCODE_VL: + case RVV_OPCODE_VS: + width = RVV_EXTRACT_VL_VS_WIDTH(insn); + return width == RVV_VL_VS_WIDTH_8 || width == RVV_VL_VS_WIDTH_16 || + width == RVV_VL_VS_WIDTH_32 || width == RVV_VL_VS_WIDTH_64; + case RVG_OPCODE_SYSTEM: + csr = RVG_EXTRACT_SYSTEM_CSR(insn); + return (csr >= CSR_VSTART && csr <= CSR_VCSR) || + (csr >= CSR_VL && csr <= CSR_VLENB); + } + + return false; +} void route_exception(struct pt_regs *regs) { struct handlers *handlers = (struct handlers *)exception_handlers; - int vector = 0, ec; + int vector = 0; + unsigned long ec; ec = regs->cause & ~CAUSE_IRQ_FLAG; if (ec >= NR_EXCEPTIONS) @@ -432,6 +468,20 @@ void route_exception(struct pt_regs *regs) ec = 0; } + /* + * Lazily enable V on the first vector instruction: if the faulting + * instruction (stval) decodes as vector while VS is off, set VS to + * Initial and re-execute it, like the kernel's + * riscv_v_first_use_handler(). Genuinely illegal instructions fall + * through to the unexpected-exception path. + */ + if (!(regs->cause & CAUSE_IRQ_FLAG) && ec == EXC_INST_ILLEGAL && + handlers && handlers->v_available && !(regs->status & SR_VS) && + insn_is_vector((u32)regs->badaddr)) { + regs->status |= SR_VS_INITIAL; + return; + } + if (handlers && handlers->exception_handlers[vector][ec]) return handlers->exception_handlers[vector][ec](regs); @@ -448,6 +498,9 @@ void vcpu_init_vector_tables(struct kvm_vcpu *vcpu) void vm_init_vector_tables(struct kvm_vm *vm) { + if (vm->handlers) + return; + vm->handlers = __vm_alloc(vm, sizeof(struct handlers), vm->page_size, MEM_REGION_DATA); -- 2.53.0 ^ permalink raw reply related [flat|nested] 9+ messages in thread
* Re: [PATCH v10] KVM: selftests: riscv: Add lazy V extension enablement for guests 2026-08-13 9:03 ` [PATCH v10] " JinRui @ 2026-08-13 9:13 ` sashiko-bot 2026-08-13 9:37 ` [PATCH v11] " JinRui 1 sibling, 0 replies; 9+ messages in thread From: sashiko-bot @ 2026-08-13 9:13 UTC (permalink / raw) To: JinRui; +Cc: kvm Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider: - [Medium] Lazy V extension enablement fails on hardware that does not populate stval for illegal instructions. - [Medium] Storing a per-vCPU V-extension availability flag in a VM-wide shared structure causes test failures in mixed-vCPU VMs. -- --- Patch [1]: [PATCH v10] KVM: selftests: riscv: Add lazy V extension enablement for guests --- commit a24ebca96066b2892004c26d282cf14c6162da61 Author: jinrui <jinrui@haiwei.tech> KVM: selftests: riscv: Add lazy V extension enablement for guests This commit adds lazy V extension enablement for guests in KVM RISC-V selftests to handle auto-vectorized instructions when sstatus.VS is off. It registers a full exception vector table, advertises V to KVM, and lazily enables V in route_exception(). > diff --git a/tools/testing/selftests/kvm/lib/riscv/processor.c b/tools/testing/selftests/kvm/lib/riscv/processor.c > index ded5429f34483..ffd84212c9b0d 100644 > --- a/tools/testing/selftests/kvm/lib/riscv/processor.c > +++ b/tools/testing/selftests/kvm/lib/riscv/processor.c [ ... ] > @@ -348,8 +346,22 @@ struct kvm_vcpu *vm_arch_vcpu_add(struct kvm_vm *vm, u32 vcpu_id) > /* Setup sscratch for guest_get_vcpuid() */ > vcpu_set_reg(vcpu, RISCV_GENERAL_CSR_REG(sscratch), vcpu_id); > > - /* Setup default exception vector of guest */ > - vcpu_set_reg(vcpu, RISCV_GENERAL_CSR_REG(stvec), (unsigned long)guest_unexp_trap); > + /* > + * Advertise V to KVM so -O2 auto-vectorization in guest code is valid; > + * ignore errors since the tests work without V too. Use the full > + * exception vector table (which lazily enables V in route_exception()) > + * as the default handler; vm_init_vector_tables() is idempotent. > + */ > + __vcpu_set_reg(vcpu, RISCV_ISA_EXT_REG(KVM_RISCV_ISA_EXT_V), 1); > + vm_init_vector_tables(vm); > + vcpu_init_vector_tables(vcpu); > + > + /* Record V availability for route_exception(), which runs in guest context. */ > + { > + struct handlers *h = addr_gva2hva(vm, vm->handlers); > + > + h->v_available = __vcpu_has_isa_ext(vcpu, KVM_RISCV_ISA_EXT_V); [Severity: Medium] Does this overwrite a shared VM-wide state with a per-vCPU capability? The handlers structure is allocated once per VM in vm_init_vector_tables(). If a VM is created with a mix of vCPUs, and the last added vCPU has the V extension, h->v_available will be true globally for all vCPUs. If a non-V vCPU encounters an illegal instruction that happens to decode as a vector instruction, could route_exception() see h->v_available as true and continually set SR_VS_INITIAL, causing an infinite exception loop? > + } > > return vcpu; > } [ ... ] > @@ -432,6 +468,20 @@ void route_exception(struct pt_regs *regs) > ec = 0; > } > > + /* > + * Lazily enable V on the first vector instruction: if the faulting > + * instruction (stval) decodes as vector while VS is off, set VS to > + * Initial and re-execute it, like the kernel's > + * riscv_v_first_use_handler(). Genuinely illegal instructions fall > + * through to the unexpected-exception path. > + */ > + if (!(regs->cause & CAUSE_IRQ_FLAG) && ec == EXC_INST_ILLEGAL && > + handlers && handlers->v_available && !(regs->status & SR_VS) && > + insn_is_vector((u32)regs->badaddr)) { [Severity: Medium] Does this assume hardware always populates stval (which is mapped to regs->badaddr) for illegal instruction exceptions? If a test executes on hardware that does not populate stval and leaves it as 0, insn_is_vector() would evaluate to false. Would this cause genuinely auto-vectorized instructions to unexpectedly fall through to kvm_exit_unexpected_exception() instead of being lazily enabled? Should this fall back to reading the instruction directly from guest memory at regs->epc when badaddr is 0? > + regs->status |= SR_VS_INITIAL; > + return; > + } -- Sashiko AI review · https://sashiko.dev/#/patchset/733FC2542EECADE1+20260813090306.3756434-1-jinrui@haiwei.tech?part=1 ^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v11] KVM: selftests: riscv: Add lazy V extension enablement for guests 2026-08-13 9:03 ` [PATCH v10] " JinRui 2026-08-13 9:13 ` sashiko-bot @ 2026-08-13 9:37 ` JinRui 2026-08-13 9:46 ` sashiko-bot 2026-08-13 9:56 ` [PATCH v12] " JinRui 1 sibling, 2 replies; 9+ messages in thread From: JinRui @ 2026-08-13 9:37 UTC (permalink / raw) To: anup, pbonzini, shuah, paul.walmsley, palmer, aou Cc: atish.patra, alex, sashiko-bot, kvm, kvm-riscv, linux-riscv, linux-kselftest, linux-kernel, jinrui From: jinrui <jinrui@haiwei.tech> When the cross-compiler defaults to an -march that includes the V (vector) extension, -O2 auto-vectorization generates vector instructions (e.g. vsetvli, vadd.vv) in guest code. Executing such an instruction with sstatus.VS Off raises EXC_INST_ILLEGAL (scause=2); KVM's hedeleg forwards it to the guest, but the bare-metal selftest cannot handle it, so all guest tests fail. A real kernel handles this via riscv_v_first_use_handler(), which enables V and re-executes the instruction. Fix it in processor.c: 1. Delete the now-unused guest_unexp_trap() handler, replaced by the full exception vector table. 2. In vm_arch_vcpu_add(), advertise V to KVM via __vcpu_set_reg(V, 1) (best-effort, errors ignored on hardware without V) and install the full exception vector table instead of a raw stvec handler. 3. In route_exception(), decode the faulting instruction (stval) with insn_is_vector() and, when it is a vector instruction while sstatus.VS is Off, set VS to Initial and sret to re-execute it, before any test-registered handler. Genuinely illegal instructions still reach the unexpected-exception path. 4. Make vm_init_vector_tables() idempotent by checking vm->handlers before allocating, so tests that call it directly (ebreak_test, arch_timer, sbi_pmu_test) do not leak memory. Tested on a riscv64 host with KVM enabled. Signed-off-by: jinrui <jinrui@haiwei.tech> --- Changes in v11: - Fall back to reading the faulting instruction from epc when stval does not hold it, matching riscv_v_first_use_handler() (Sashiko review). - Reword a comment to avoid a checkpatch false positive. .../selftests/kvm/include/riscv/processor.h | 13 +++ .../selftests/kvm/lib/riscv/processor.c | 90 ++++++++++++++++--- 2 files changed, 90 insertions(+), 13 deletions(-) diff --git a/tools/testing/selftests/kvm/include/riscv/processor.h b/tools/testing/selftests/kvm/include/riscv/processor.h index e3acf2ae9881..685baefebdb1 100644 --- a/tools/testing/selftests/kvm/include/riscv/processor.h +++ b/tools/testing/selftests/kvm/include/riscv/processor.h @@ -25,6 +25,19 @@ #define GET_RM(insn) (((insn) & INSN_MASK_FUNCT3) >> INSN_SHIFT_FUNCT3) #define GET_CSR_NUM(insn) (((insn) & INSN_CSR_MASK) >> INSN_CSR_SHIFT) +/* Vector (V) instruction decoding, matching arch/riscv/include/asm/insn.h */ +#define RV_INSN_OPCODE_MASK 0x7f +#define RVG_OPCODE_SYSTEM 0x73 +#define RVV_OPCODE_VECTOR 0x57 +#define RVV_OPCODE_VL 0x07 +#define RVV_OPCODE_VS 0x27 +#define RVV_VL_VS_WIDTH_8 0 +#define RVV_VL_VS_WIDTH_16 5 +#define RVV_VL_VS_WIDTH_32 6 +#define RVV_VL_VS_WIDTH_64 7 +#define RVV_EXTRACT_VL_VS_WIDTH(insn) (((insn) >> 12) & 0x7) +#define RVG_EXTRACT_SYSTEM_CSR(insn) (((insn) >> 20) & 0xfff) + static inline u64 __kvm_reg_id(u64 type, u64 subtype, u64 idx, u64 size) { return KVM_REG_RISCV | type | subtype | idx | size; diff --git a/tools/testing/selftests/kvm/lib/riscv/processor.c b/tools/testing/selftests/kvm/lib/riscv/processor.c index ded5429f3448..c4171aae221d 100644 --- a/tools/testing/selftests/kvm/lib/riscv/processor.c +++ b/tools/testing/selftests/kvm/lib/riscv/processor.c @@ -17,6 +17,11 @@ static gva_t exception_handlers; +struct handlers { + exception_handler_fn exception_handlers[NR_VECTORS][NR_EXCEPTIONS]; + bool v_available; +}; + bool __vcpu_has_ext(struct kvm_vcpu *vcpu, u64 ext) { unsigned long value = 0; @@ -298,13 +303,6 @@ void vcpu_arch_dump(FILE *stream, struct kvm_vcpu *vcpu, u8 indent) core.regs.t3, core.regs.t4, core.regs.t5, core.regs.t6); } -static void __aligned(16) guest_unexp_trap(void) -{ - sbi_ecall(KVM_RISCV_SELFTESTS_SBI_EXT, - KVM_RISCV_SELFTESTS_SBI_UNEXP, - 0, 0, 0, 0, 0, 0); -} - void vcpu_arch_set_entry_point(struct kvm_vcpu *vcpu, void *guest_code) { vcpu_set_reg(vcpu, RISCV_CORE_REG(regs.pc), (unsigned long)guest_code); @@ -348,8 +346,26 @@ struct kvm_vcpu *vm_arch_vcpu_add(struct kvm_vm *vm, u32 vcpu_id) /* Setup sscratch for guest_get_vcpuid() */ vcpu_set_reg(vcpu, RISCV_GENERAL_CSR_REG(sscratch), vcpu_id); - /* Setup default exception vector of guest */ - vcpu_set_reg(vcpu, RISCV_GENERAL_CSR_REG(stvec), (unsigned long)guest_unexp_trap); + /* + * Advertise V to KVM so -O2 auto-vectorization in guest code is valid; + * ignore errors since the tests work without V too. Use the full + * exception vector table (which lazily enables V in route_exception()) + * as the default handler; vm_init_vector_tables() is idempotent. + */ + __vcpu_set_reg(vcpu, RISCV_ISA_EXT_REG(KVM_RISCV_ISA_EXT_V), 1); + vm_init_vector_tables(vm); + vcpu_init_vector_tables(vcpu); + + /* + * Record V availability for route_exception(), which runs in guest + * context. V is enabled uniformly for every vCPU, so this is a + * VM-wide property. + */ + { + struct handlers *h = addr_gva2hva(vm, vm->handlers); + + h->v_available = __vcpu_has_isa_ext(vcpu, KVM_RISCV_ISA_EXT_V); + } return vcpu; } @@ -408,19 +424,43 @@ void assert_on_unhandled_exception(struct kvm_vcpu *vcpu) struct ucall uc; if (get_ucall(vcpu, &uc) == UCALL_UNHANDLED) { + vcpu_dump(stderr, vcpu, 2); TEST_FAIL("Unexpected exception (vector:0x%lx, ec:0x%lx)", uc.args[0], uc.args[1]); } } -struct handlers { - exception_handler_fn exception_handlers[NR_VECTORS][NR_EXCEPTIONS]; -}; +static bool insn_is_vector(u32 insn) +{ + u32 opcode = insn & RV_INSN_OPCODE_MASK; + u32 width, csr; + + /* All V-related instructions are 4-byte, i.e. not compressed. */ + if ((insn & 0x3) != 0x3) + return false; + + switch (opcode) { + case RVV_OPCODE_VECTOR: + return true; + case RVV_OPCODE_VL: + case RVV_OPCODE_VS: + width = RVV_EXTRACT_VL_VS_WIDTH(insn); + return width == RVV_VL_VS_WIDTH_8 || width == RVV_VL_VS_WIDTH_16 || + width == RVV_VL_VS_WIDTH_32 || width == RVV_VL_VS_WIDTH_64; + case RVG_OPCODE_SYSTEM: + csr = RVG_EXTRACT_SYSTEM_CSR(insn); + return (csr >= CSR_VSTART && csr <= CSR_VCSR) || + (csr >= CSR_VL && csr <= CSR_VLENB); + } + + return false; +} void route_exception(struct pt_regs *regs) { struct handlers *handlers = (struct handlers *)exception_handlers; - int vector = 0, ec; + int vector = 0; + unsigned long ec; ec = regs->cause & ~CAUSE_IRQ_FLAG; if (ec >= NR_EXCEPTIONS) @@ -432,6 +472,27 @@ void route_exception(struct pt_regs *regs) ec = 0; } + /* + * Lazily enable V on the first vector instruction: if the faulting + * instruction decodes as vector while VS is off, set VS to Initial + * and re-execute it, like the kernel's riscv_v_first_use_handler(). + * Genuinely illegal instructions continue to the unexpected-exception + * path. + */ + if (!(regs->cause & CAUSE_IRQ_FLAG) && ec == EXC_INST_ILLEGAL && + handlers && handlers->v_available && !(regs->status & SR_VS)) { + u32 insn = (u32)regs->badaddr; + + /* stval is not guaranteed to hold the faulting instruction */ + if (!insn) + insn = *(u32 *)regs->epc; + + if (insn_is_vector(insn)) { + regs->status |= SR_VS_INITIAL; + return; + } + } + if (handlers && handlers->exception_handlers[vector][ec]) return handlers->exception_handlers[vector][ec](regs); @@ -448,6 +509,9 @@ void vcpu_init_vector_tables(struct kvm_vcpu *vcpu) void vm_init_vector_tables(struct kvm_vm *vm) { + if (vm->handlers) + return; + vm->handlers = __vm_alloc(vm, sizeof(struct handlers), vm->page_size, MEM_REGION_DATA); -- 2.53.0 ^ permalink raw reply related [flat|nested] 9+ messages in thread
* Re: [PATCH v11] KVM: selftests: riscv: Add lazy V extension enablement for guests 2026-08-13 9:37 ` [PATCH v11] " JinRui @ 2026-08-13 9:46 ` sashiko-bot 2026-08-13 9:56 ` [PATCH v12] " JinRui 1 sibling, 0 replies; 9+ messages in thread From: sashiko-bot @ 2026-08-13 9:46 UTC (permalink / raw) To: JinRui; +Cc: kvm Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Medium] Directly reading a 32-bit instruction from `regs->epc` can trigger an unhandled Misaligned Load or Load Page Fault exception in the guest. -- --- Patch [1]: [PATCH v11] KVM: selftests: riscv: Add lazy V extension enablement for guests --- commit 53e28f775e49a3c6cc2ff1526d5a5089a4c1c737 Author: jinrui <jinrui@haiwei.tech> KVM: selftests: riscv: Add lazy V extension enablement for guests This commit adds lazy enablement of the V (vector) extension in KVM RISC-V guest selftests to handle compiler auto-vectorization. It updates the exception routing to decode faulting instructions and enable the V extension if a vector instruction is executed while VS is off. > diff --git a/tools/testing/selftests/kvm/lib/riscv/processor.c b/tools/testing/selftests/kvm/lib/riscv/processor.c > --- a/tools/testing/selftests/kvm/lib/riscv/processor.c > +++ b/tools/testing/selftests/kvm/lib/riscv/processor.c [ ... ] > @@ -432,6 +472,27 @@ void route_exception(struct pt_regs *regs) > ec = 0; > } > > + /* > + * Lazily enable V on the first vector instruction: if the faulting > + * instruction decodes as vector while VS is off, set VS to Initial > + * and re-execute it, like the kernel's riscv_v_first_use_handler(). > + * Genuinely illegal instructions continue to the unexpected-exception > + * path. > + */ > + if (!(regs->cause & CAUSE_IRQ_FLAG) && ec == EXC_INST_ILLEGAL && > + handlers && handlers->v_available && !(regs->status & SR_VS)) { > + u32 insn = (u32)regs->badaddr; > + > + /* stval is not guaranteed to hold the faulting instruction */ > + if (!insn) > + insn = *(u32 *)regs->epc; [Severity: Medium] Can reading a full 32-bit value directly from regs->epc trigger an unhandled misaligned load or load page fault in the guest? If the faulting instruction is a compressed 16-bit instruction at a 2-byte aligned address, this 32-bit read will be unaligned and might trap on hardware that does not support unaligned accesses. Additionally, if the 16-bit instruction sits exactly at the end of a mapped page and the next page is unmapped, reading 32 bits could trigger a page fault instead of correctly handling the illegal instruction exception. > + > + if (insn_is_vector(insn)) { > + regs->status |= SR_VS_INITIAL; > + return; > + } > + } -- Sashiko AI review · https://sashiko.dev/#/patchset/EF5308EB97ADC8DE+20260813093751.3817371-1-jinrui@haiwei.tech?part=1 ^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v12] KVM: selftests: riscv: Add lazy V extension enablement for guests 2026-08-13 9:37 ` [PATCH v11] " JinRui 2026-08-13 9:46 ` sashiko-bot @ 2026-08-13 9:56 ` JinRui 2026-08-13 10:04 ` sashiko-bot 2026-08-13 10:29 ` [PATCH v13] " JinRui 1 sibling, 2 replies; 9+ messages in thread From: JinRui @ 2026-08-13 9:56 UTC (permalink / raw) To: anup, pbonzini, shuah, paul.walmsley, palmer, aou Cc: atish.patra, alex, sashiko-bot, kvm, kvm-riscv, linux-riscv, linux-kselftest, linux-kernel, jinrui From: jinrui <jinrui@haiwei.tech> When the cross-compiler defaults to an -march that includes the V (vector) extension, -O2 auto-vectorization generates vector instructions (e.g. vsetvli, vadd.vv) in guest code. Executing such an instruction with sstatus.VS Off raises EXC_INST_ILLEGAL (scause=2); KVM's hedeleg forwards it to the guest, but the bare-metal selftest cannot handle it, so all guest tests fail. A real kernel handles this via riscv_v_first_use_handler(), which enables V and re-executes the instruction. Fix it in processor.c: 1. Delete the now-unused guest_unexp_trap() handler, replaced by the full exception vector table. 2. In vm_arch_vcpu_add(), advertise V to KVM via __vcpu_set_reg(V, 1) (best-effort, errors ignored on hardware without V) and install the full exception vector table instead of a raw stvec handler. 3. In route_exception(), decode the faulting instruction (stval) with insn_is_vector() and, when it is a vector instruction while sstatus.VS is Off, set VS to Initial and sret to re-execute it, before any test-registered handler. Genuinely illegal instructions still reach the unexpected-exception path. 4. Make vm_init_vector_tables() idempotent by checking vm->handlers before allocating, so tests that call it directly (ebreak_test, arch_timer, sbi_pmu_test) do not leak memory. Tested on a riscv64 host with KVM enabled. Signed-off-by: jinrui <jinrui@haiwei.tech> --- Changes in v12: - Read a 16-bit halfword first and only load the full 32-bit instruction when it is not compressed, avoiding an unaligned or cross-page access in the stval fallback (Sashiko review). .../selftests/kvm/include/riscv/processor.h | 13 +++ .../selftests/kvm/lib/riscv/processor.c | 100 +++++++++++++++--- 2 files changed, 100 insertions(+), 13 deletions(-) diff --git a/tools/testing/selftests/kvm/include/riscv/processor.h b/tools/testing/selftests/kvm/include/riscv/processor.h index e3acf2ae9881..685baefebdb1 100644 --- a/tools/testing/selftests/kvm/include/riscv/processor.h +++ b/tools/testing/selftests/kvm/include/riscv/processor.h @@ -25,6 +25,19 @@ #define GET_RM(insn) (((insn) & INSN_MASK_FUNCT3) >> INSN_SHIFT_FUNCT3) #define GET_CSR_NUM(insn) (((insn) & INSN_CSR_MASK) >> INSN_CSR_SHIFT) +/* Vector (V) instruction decoding, matching arch/riscv/include/asm/insn.h */ +#define RV_INSN_OPCODE_MASK 0x7f +#define RVG_OPCODE_SYSTEM 0x73 +#define RVV_OPCODE_VECTOR 0x57 +#define RVV_OPCODE_VL 0x07 +#define RVV_OPCODE_VS 0x27 +#define RVV_VL_VS_WIDTH_8 0 +#define RVV_VL_VS_WIDTH_16 5 +#define RVV_VL_VS_WIDTH_32 6 +#define RVV_VL_VS_WIDTH_64 7 +#define RVV_EXTRACT_VL_VS_WIDTH(insn) (((insn) >> 12) & 0x7) +#define RVG_EXTRACT_SYSTEM_CSR(insn) (((insn) >> 20) & 0xfff) + static inline u64 __kvm_reg_id(u64 type, u64 subtype, u64 idx, u64 size) { return KVM_REG_RISCV | type | subtype | idx | size; diff --git a/tools/testing/selftests/kvm/lib/riscv/processor.c b/tools/testing/selftests/kvm/lib/riscv/processor.c index ded5429f3448..e677137d5e44 100644 --- a/tools/testing/selftests/kvm/lib/riscv/processor.c +++ b/tools/testing/selftests/kvm/lib/riscv/processor.c @@ -17,6 +17,11 @@ static gva_t exception_handlers; +struct handlers { + exception_handler_fn exception_handlers[NR_VECTORS][NR_EXCEPTIONS]; + bool v_available; +}; + bool __vcpu_has_ext(struct kvm_vcpu *vcpu, u64 ext) { unsigned long value = 0; @@ -298,13 +303,6 @@ void vcpu_arch_dump(FILE *stream, struct kvm_vcpu *vcpu, u8 indent) core.regs.t3, core.regs.t4, core.regs.t5, core.regs.t6); } -static void __aligned(16) guest_unexp_trap(void) -{ - sbi_ecall(KVM_RISCV_SELFTESTS_SBI_EXT, - KVM_RISCV_SELFTESTS_SBI_UNEXP, - 0, 0, 0, 0, 0, 0); -} - void vcpu_arch_set_entry_point(struct kvm_vcpu *vcpu, void *guest_code) { vcpu_set_reg(vcpu, RISCV_CORE_REG(regs.pc), (unsigned long)guest_code); @@ -348,8 +346,26 @@ struct kvm_vcpu *vm_arch_vcpu_add(struct kvm_vm *vm, u32 vcpu_id) /* Setup sscratch for guest_get_vcpuid() */ vcpu_set_reg(vcpu, RISCV_GENERAL_CSR_REG(sscratch), vcpu_id); - /* Setup default exception vector of guest */ - vcpu_set_reg(vcpu, RISCV_GENERAL_CSR_REG(stvec), (unsigned long)guest_unexp_trap); + /* + * Advertise V to KVM so -O2 auto-vectorization in guest code is valid; + * ignore errors since the tests work without V too. Use the full + * exception vector table (which lazily enables V in route_exception()) + * as the default handler; vm_init_vector_tables() is idempotent. + */ + __vcpu_set_reg(vcpu, RISCV_ISA_EXT_REG(KVM_RISCV_ISA_EXT_V), 1); + vm_init_vector_tables(vm); + vcpu_init_vector_tables(vcpu); + + /* + * Record V availability for route_exception(), which runs in guest + * context. V is enabled uniformly for every vCPU, so this is a + * VM-wide property. + */ + { + struct handlers *h = addr_gva2hva(vm, vm->handlers); + + h->v_available = __vcpu_has_isa_ext(vcpu, KVM_RISCV_ISA_EXT_V); + } return vcpu; } @@ -408,19 +424,43 @@ void assert_on_unhandled_exception(struct kvm_vcpu *vcpu) struct ucall uc; if (get_ucall(vcpu, &uc) == UCALL_UNHANDLED) { + vcpu_dump(stderr, vcpu, 2); TEST_FAIL("Unexpected exception (vector:0x%lx, ec:0x%lx)", uc.args[0], uc.args[1]); } } -struct handlers { - exception_handler_fn exception_handlers[NR_VECTORS][NR_EXCEPTIONS]; -}; +static bool insn_is_vector(u32 insn) +{ + u32 opcode = insn & RV_INSN_OPCODE_MASK; + u32 width, csr; + + /* All V-related instructions are 4-byte, i.e. not compressed. */ + if ((insn & 0x3) != 0x3) + return false; + + switch (opcode) { + case RVV_OPCODE_VECTOR: + return true; + case RVV_OPCODE_VL: + case RVV_OPCODE_VS: + width = RVV_EXTRACT_VL_VS_WIDTH(insn); + return width == RVV_VL_VS_WIDTH_8 || width == RVV_VL_VS_WIDTH_16 || + width == RVV_VL_VS_WIDTH_32 || width == RVV_VL_VS_WIDTH_64; + case RVG_OPCODE_SYSTEM: + csr = RVG_EXTRACT_SYSTEM_CSR(insn); + return (csr >= CSR_VSTART && csr <= CSR_VCSR) || + (csr >= CSR_VL && csr <= CSR_VLENB); + } + + return false; +} void route_exception(struct pt_regs *regs) { struct handlers *handlers = (struct handlers *)exception_handlers; - int vector = 0, ec; + int vector = 0; + unsigned long ec; ec = regs->cause & ~CAUSE_IRQ_FLAG; if (ec >= NR_EXCEPTIONS) @@ -432,6 +472,37 @@ void route_exception(struct pt_regs *regs) ec = 0; } + /* + * Lazily enable V on the first vector instruction: if the faulting + * instruction decodes as vector while VS is off, set VS to Initial + * and re-execute it, like the kernel's riscv_v_first_use_handler(). + * Genuinely illegal instructions continue to the unexpected-exception + * path. + */ + if (!(regs->cause & CAUSE_IRQ_FLAG) && ec == EXC_INST_ILLEGAL && + handlers && handlers->v_available && !(regs->status & SR_VS)) { + u32 insn = (u32)regs->badaddr; + + /* + * stval is not guaranteed to hold the faulting instruction. + * Vector instructions are always 32-bit, so read a 16-bit + * halfword first and only load the full 32-bit instruction when + * it is not compressed; this avoids an unaligned or cross-page + * access on a compressed instruction. + */ + if (!insn) { + u16 half = *(u16 *)regs->epc; + + if ((half & 0x3) == 0x3) + insn = *(u32 *)regs->epc; + } + + if (insn_is_vector(insn)) { + regs->status |= SR_VS_INITIAL; + return; + } + } + if (handlers && handlers->exception_handlers[vector][ec]) return handlers->exception_handlers[vector][ec](regs); @@ -448,6 +519,9 @@ void vcpu_init_vector_tables(struct kvm_vcpu *vcpu) void vm_init_vector_tables(struct kvm_vm *vm) { + if (vm->handlers) + return; + vm->handlers = __vm_alloc(vm, sizeof(struct handlers), vm->page_size, MEM_REGION_DATA); -- 2.53.0 ^ permalink raw reply related [flat|nested] 9+ messages in thread
* Re: [PATCH v12] KVM: selftests: riscv: Add lazy V extension enablement for guests 2026-08-13 9:56 ` [PATCH v12] " JinRui @ 2026-08-13 10:04 ` sashiko-bot 2026-08-13 10:29 ` [PATCH v13] " JinRui 1 sibling, 0 replies; 9+ messages in thread From: sashiko-bot @ 2026-08-13 10:04 UTC (permalink / raw) To: JinRui; +Cc: kvm Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Medium] Unaligned 32-bit memory access when decoding 32-bit instructions -- --- Patch [1]: [PATCH v12] KVM: selftests: riscv: Add lazy V extension enablement for guests --- commit cffe485ad3478e658853f5e74fe97f4c03d20bf6 Author: jinrui <jinrui@haiwei.tech> KVM: selftests: riscv: Add lazy V extension enablement for guests This commit introduces lazy enablement of the RISC-V vector (V) extension for guest tests. It updates the exception routing to decode faulting instructions and re-execute vector instructions when the VS status is off. > diff --git a/tools/testing/selftests/kvm/lib/riscv/processor.c b/tools/testing/selftests/kvm/lib/riscv/processor.c > index ded5429f34483..e677137d5e448 100644 > --- a/tools/testing/selftests/kvm/lib/riscv/processor.c > +++ b/tools/testing/selftests/kvm/lib/riscv/processor.c [ ... ] > @@ -432,6 +472,37 @@ void route_exception(struct pt_regs *regs) > ec = 0; > } > > + /* > + * Lazily enable V on the first vector instruction: if the faulting > + * instruction decodes as vector while VS is off, set VS to Initial > + * and re-execute it, like the kernel's riscv_v_first_use_handler(). > + * Genuinely illegal instructions continue to the unexpected-exception > + * path. > + */ > + if (!(regs->cause & CAUSE_IRQ_FLAG) && ec == EXC_INST_ILLEGAL && > + handlers && handlers->v_available && !(regs->status & SR_VS)) { > + u32 insn = (u32)regs->badaddr; > + > + /* > + * stval is not guaranteed to hold the faulting instruction. > + * Vector instructions are always 32-bit, so read a 16-bit > + * halfword first and only load the full 32-bit instruction when > + * it is not compressed; this avoids an unaligned or cross-page > + * access on a compressed instruction. > + */ > + if (!insn) { > + u16 half = *(u16 *)regs->epc; > + > + if ((half & 0x3) == 0x3) > + insn = *(u32 *)regs->epc; [Severity: Medium] Will this result in an unaligned 32-bit memory access on certain hardware? If a 32-bit vector instruction is placed at a 2-byte aligned address (which is valid when the RISC-V 'C' compressed extension is enabled), regs->epc will only be 2-byte aligned. Casting a potentially 2-byte aligned address to a u32 pointer is undefined behavior in C, and the compiler typically emits a standard 32-bit word load instruction. On strict-alignment RISC-V hardware, won't this trap and crash the selftest guest with a 'Load Address Misaligned' exception? > + } > + > + if (insn_is_vector(insn)) { > + regs->status |= SR_VS_INITIAL; > + return; > + } > + } > + -- Sashiko AI review · https://sashiko.dev/#/patchset/7C0512E558D1614D+20260813095615.3843757-1-jinrui@haiwei.tech?part=1 ^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v13] KVM: selftests: riscv: Add lazy V extension enablement for guests 2026-08-13 9:56 ` [PATCH v12] " JinRui 2026-08-13 10:04 ` sashiko-bot @ 2026-08-13 10:29 ` JinRui 1 sibling, 0 replies; 9+ messages in thread From: JinRui @ 2026-08-13 10:29 UTC (permalink / raw) To: anup, pbonzini, shuah, paul.walmsley, palmer, aou Cc: atish.patra, alex, sashiko-bot, kvm, kvm-riscv, linux-riscv, linux-kselftest, linux-kernel, jinrui From: jinrui <jinrui@haiwei.tech> When the cross-compiler defaults to an -march that includes the V (vector) extension, -O2 auto-vectorization generates vector instructions (e.g. vsetvli, vadd.vv) in guest code. Executing such an instruction with sstatus.VS Off raises EXC_INST_ILLEGAL (scause=2); KVM's hedeleg forwards it to the guest, but the bare-metal selftest cannot handle it, so all guest tests fail. A real kernel handles this via riscv_v_first_use_handler(), which enables V and re-executes the instruction. Fix it in processor.c: 1. Delete the now-unused guest_unexp_trap() handler, replaced by the full exception vector table. 2. In vm_arch_vcpu_add(), advertise V to KVM via __vcpu_set_reg(V, 1) (best-effort, errors ignored on hardware without V) and install the full exception vector table instead of a raw stvec handler. 3. In route_exception(), decode the faulting instruction (stval) with insn_is_vector() and, when it is a vector instruction while sstatus.VS is Off, set VS to Initial and sret to re-execute it, before any test-registered handler. Genuinely illegal instructions still reach the unexpected-exception path. 4. Make vm_init_vector_tables() idempotent by checking vm->handlers before allocating, so tests that call it directly (ebreak_test, arch_timer, sbi_pmu_test) do not leak memory. Tested on a riscv64 host with KVM enabled. Signed-off-by: jinrui <jinrui@haiwei.tech> --- Changes in v13: - Assemble the 32-bit instruction from two 16-bit halfword reads instead of a single 32-bit load, since with IALIGN=16 a 32-bit instruction can start on a 2-byte boundary (Sashiko review). .../selftests/kvm/include/riscv/processor.h | 13 +++ .../selftests/kvm/lib/riscv/processor.c | 99 ++++++++++++++++--- 2 files changed, 99 insertions(+), 13 deletions(-) diff --git a/tools/testing/selftests/kvm/include/riscv/processor.h b/tools/testing/selftests/kvm/include/riscv/processor.h index e3acf2ae9881..685baefebdb1 100644 --- a/tools/testing/selftests/kvm/include/riscv/processor.h +++ b/tools/testing/selftests/kvm/include/riscv/processor.h @@ -25,6 +25,19 @@ #define GET_RM(insn) (((insn) & INSN_MASK_FUNCT3) >> INSN_SHIFT_FUNCT3) #define GET_CSR_NUM(insn) (((insn) & INSN_CSR_MASK) >> INSN_CSR_SHIFT) +/* Vector (V) instruction decoding, matching arch/riscv/include/asm/insn.h */ +#define RV_INSN_OPCODE_MASK 0x7f +#define RVG_OPCODE_SYSTEM 0x73 +#define RVV_OPCODE_VECTOR 0x57 +#define RVV_OPCODE_VL 0x07 +#define RVV_OPCODE_VS 0x27 +#define RVV_VL_VS_WIDTH_8 0 +#define RVV_VL_VS_WIDTH_16 5 +#define RVV_VL_VS_WIDTH_32 6 +#define RVV_VL_VS_WIDTH_64 7 +#define RVV_EXTRACT_VL_VS_WIDTH(insn) (((insn) >> 12) & 0x7) +#define RVG_EXTRACT_SYSTEM_CSR(insn) (((insn) >> 20) & 0xfff) + static inline u64 __kvm_reg_id(u64 type, u64 subtype, u64 idx, u64 size) { return KVM_REG_RISCV | type | subtype | idx | size; diff --git a/tools/testing/selftests/kvm/lib/riscv/processor.c b/tools/testing/selftests/kvm/lib/riscv/processor.c index ded5429f3448..d00ac997291c 100644 --- a/tools/testing/selftests/kvm/lib/riscv/processor.c +++ b/tools/testing/selftests/kvm/lib/riscv/processor.c @@ -17,6 +17,11 @@ static gva_t exception_handlers; +struct handlers { + exception_handler_fn exception_handlers[NR_VECTORS][NR_EXCEPTIONS]; + bool v_available; +}; + bool __vcpu_has_ext(struct kvm_vcpu *vcpu, u64 ext) { unsigned long value = 0; @@ -298,13 +303,6 @@ void vcpu_arch_dump(FILE *stream, struct kvm_vcpu *vcpu, u8 indent) core.regs.t3, core.regs.t4, core.regs.t5, core.regs.t6); } -static void __aligned(16) guest_unexp_trap(void) -{ - sbi_ecall(KVM_RISCV_SELFTESTS_SBI_EXT, - KVM_RISCV_SELFTESTS_SBI_UNEXP, - 0, 0, 0, 0, 0, 0); -} - void vcpu_arch_set_entry_point(struct kvm_vcpu *vcpu, void *guest_code) { vcpu_set_reg(vcpu, RISCV_CORE_REG(regs.pc), (unsigned long)guest_code); @@ -348,8 +346,26 @@ struct kvm_vcpu *vm_arch_vcpu_add(struct kvm_vm *vm, u32 vcpu_id) /* Setup sscratch for guest_get_vcpuid() */ vcpu_set_reg(vcpu, RISCV_GENERAL_CSR_REG(sscratch), vcpu_id); - /* Setup default exception vector of guest */ - vcpu_set_reg(vcpu, RISCV_GENERAL_CSR_REG(stvec), (unsigned long)guest_unexp_trap); + /* + * Advertise V to KVM so -O2 auto-vectorization in guest code is valid; + * ignore errors since the tests work without V too. Use the full + * exception vector table (which lazily enables V in route_exception()) + * as the default handler; vm_init_vector_tables() is idempotent. + */ + __vcpu_set_reg(vcpu, RISCV_ISA_EXT_REG(KVM_RISCV_ISA_EXT_V), 1); + vm_init_vector_tables(vm); + vcpu_init_vector_tables(vcpu); + + /* + * Record V availability for route_exception(), which runs in guest + * context. V is enabled uniformly for every vCPU, so this is a + * VM-wide property. + */ + { + struct handlers *h = addr_gva2hva(vm, vm->handlers); + + h->v_available = __vcpu_has_isa_ext(vcpu, KVM_RISCV_ISA_EXT_V); + } return vcpu; } @@ -408,19 +424,43 @@ void assert_on_unhandled_exception(struct kvm_vcpu *vcpu) struct ucall uc; if (get_ucall(vcpu, &uc) == UCALL_UNHANDLED) { + vcpu_dump(stderr, vcpu, 2); TEST_FAIL("Unexpected exception (vector:0x%lx, ec:0x%lx)", uc.args[0], uc.args[1]); } } -struct handlers { - exception_handler_fn exception_handlers[NR_VECTORS][NR_EXCEPTIONS]; -}; +static bool insn_is_vector(u32 insn) +{ + u32 opcode = insn & RV_INSN_OPCODE_MASK; + u32 width, csr; + + /* All V-related instructions are 4-byte, i.e. not compressed. */ + if ((insn & 0x3) != 0x3) + return false; + + switch (opcode) { + case RVV_OPCODE_VECTOR: + return true; + case RVV_OPCODE_VL: + case RVV_OPCODE_VS: + width = RVV_EXTRACT_VL_VS_WIDTH(insn); + return width == RVV_VL_VS_WIDTH_8 || width == RVV_VL_VS_WIDTH_16 || + width == RVV_VL_VS_WIDTH_32 || width == RVV_VL_VS_WIDTH_64; + case RVG_OPCODE_SYSTEM: + csr = RVG_EXTRACT_SYSTEM_CSR(insn); + return (csr >= CSR_VSTART && csr <= CSR_VCSR) || + (csr >= CSR_VL && csr <= CSR_VLENB); + } + + return false; +} void route_exception(struct pt_regs *regs) { struct handlers *handlers = (struct handlers *)exception_handlers; - int vector = 0, ec; + int vector = 0; + unsigned long ec; ec = regs->cause & ~CAUSE_IRQ_FLAG; if (ec >= NR_EXCEPTIONS) @@ -432,6 +472,36 @@ void route_exception(struct pt_regs *regs) ec = 0; } + /* + * Lazily enable V on the first vector instruction: if the faulting + * instruction decodes as vector while VS is off, set VS to Initial + * and re-execute it, like the kernel's riscv_v_first_use_handler(). + * Genuinely illegal instructions continue to the unexpected-exception + * path. + */ + if (!(regs->cause & CAUSE_IRQ_FLAG) && ec == EXC_INST_ILLEGAL && + handlers && handlers->v_available && !(regs->status & SR_VS)) { + u32 insn = (u32)regs->badaddr; + + /* + * stval is not guaranteed to hold the faulting instruction. + * Vector instructions are always 32-bit, so check the first + * 16-bit halfword and, for a 32-bit instruction, assemble it + * from two halfwords to avoid an unaligned 32-bit access. + */ + if (!insn) { + u16 half = *(u16 *)regs->epc; + + if ((half & 0x3) == 0x3) + insn = (u32)half | ((u32)*(u16 *)(regs->epc + 2) << 16); + } + + if (insn_is_vector(insn)) { + regs->status |= SR_VS_INITIAL; + return; + } + } + if (handlers && handlers->exception_handlers[vector][ec]) return handlers->exception_handlers[vector][ec](regs); @@ -448,6 +518,9 @@ void vcpu_init_vector_tables(struct kvm_vcpu *vcpu) void vm_init_vector_tables(struct kvm_vm *vm) { + if (vm->handlers) + return; + vm->handlers = __vm_alloc(vm, sizeof(struct handlers), vm->page_size, MEM_REGION_DATA); -- 2.53.0 ^ permalink raw reply related [flat|nested] 9+ messages in thread
end of thread, other threads:[~2026-08-13 10:29 UTC | newest]
Thread overview: 9+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
[not found] <20260722073344.771230-1-jinrui@haiwei.tech>
2026-08-11 8:50 ` [PATCH v9] KVM: selftests: riscv: Add lazy V extension enablement for guests JinRui
2026-08-12 17:26 ` Anup Patel
2026-08-13 9:03 ` [PATCH v10] " JinRui
2026-08-13 9:13 ` sashiko-bot
2026-08-13 9:37 ` [PATCH v11] " JinRui
2026-08-13 9:46 ` sashiko-bot
2026-08-13 9:56 ` [PATCH v12] " JinRui
2026-08-13 10:04 ` sashiko-bot
2026-08-13 10:29 ` [PATCH v13] " JinRui
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox