Kernel KVM virtualization development
 help / color / mirror / Atom feed
* [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