Re: [PATCH v9] KVM: selftests: riscv: Add lazy V extension enablement for guests

Anup Patel <[email protected]>
Newsgroups org.infradead.lists.linux-riscv,org.infradead.lists.kvm-riscv,org.kernel.vger.kvm,org.kernel.vger.linux-kernel,org.kernel.vger.linux-kselftest
Message-ID <CAAhSdy0K5H23AC9X_OA-cj6wppcw7fWPEHQZFzkTR+Rjnv6jBw@mail.gmail.com>
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

_______________________________________________
linux-riscv mailing list
[email protected]
http://lists.infradead.org/mailman/listinfo/linux-riscv
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.