On Thu, Aug 13, 2026 at 3:59 PM JinRui <[email protected]> wrote: > > From: jinrui <[email protected]> > > 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 <[email protected]>
LGTM. Reviewed-by: Anup Patel <[email protected]> Queued this patch for Linux-7.4 Thanks, Anup > --- > 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 Regards, Anup

