On Tue, Aug 11, 2026 at 2:21 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 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 <[email protected]>
> ---
> 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

Reply via email to