* [PATCH v8] KVM: selftests: riscv: Add lazy V extension enablement for guests
[not found] <20260722062804.738428-1-jinrui@haiwei.tech>
@ 2026-07-22 7:33 ` JinRui
2026-07-22 7:44 ` sashiko-bot
0 siblings, 1 reply; 2+ messages in thread
From: JinRui @ 2026-07-22 7:33 UTC (permalink / raw)
To: Anup Patel, Paolo Bonzini, Shuah Khan, Paul Walmsley,
Palmer Dabbelt, Albert Ou
Cc: Atish Patra, Alexandre Ghiti, 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 v8:
- Change 'ec' in route_exception() from int to unsigned long to
match regs->cause (unsigned long), preventing signed truncation
from bypassing the ec >= NR_EXCEPTIONS bounds check.
Changes in v7:
- Use unsigned int for vcpu_id, v_epc_capacity, and max_vcpus to
prevent negative-value out-of-bounds array access on v_epc[].
Changes in v6:
- Move struct handlers definition to the top of the file, before its
first use in vm_arch_vcpu_add().
Changes in v5:
- Allocate the per-vCPU epc table dynamically via a flexible array
member sized by KVM_CAP_MAX_VCPUS rather than a hardcoded constant,
making the concurrency fix safe for any vCPU count.
Changes in v4:
- Add vcpu_dump() in assert_on_unhandled_exception() to restore the
register dump lost when guest_unexp_trap() was removed.
Changes in v3:
- Move v_available from a host-side static variable into struct
handlers (Guest memory), fixing host-to-Guest synchronization.
Changes in v2:
- Add a v_available flag to prevent infinite exception loops on
hosts without the V extension.
- Use regs->status instead of reading the live sstatus CSR.
---
.../selftests/kvm/lib/riscv/processor.c | 111 +++++++++++++++---
1 file changed, 95 insertions(+), 16 deletions(-)
diff --git a/tools/testing/selftests/kvm/lib/riscv/processor.c b/tools/testing/selftests/kvm/lib/riscv/processor.c
index ded5429f3448..624f20f0f476 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;
@@ -297,14 +304,6 @@ void vcpu_arch_dump(FILE *stream, struct kvm_vcpu *vcpu, u8 indent)
" T3: 0x%016lx T4: 0x%016lx T5: 0x%016lx T6: 0x%016lx\n",
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 +347,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 +432,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 +454,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 +510,26 @@ 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_vcpus;
+ size_t size;
+
+ if (vm->handlers)
+ return;
+
+ max_vcpus = kvm_check_cap(KVM_CAP_MAX_VCPUS);
+ if (max_vcpus == 0)
+ max_vcpus = 512;
+
+ size = sizeof(struct handlers) + max_vcpus * 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_vcpus;
+ }
+
*(gva_t *)addr_gva2hva(vm, (gva_t)(&exception_handlers)) = vm->handlers;
}
--
2.43.0
^ permalink raw reply related [flat|nested] 2+ messages in thread
* Re: [PATCH v8] KVM: selftests: riscv: Add lazy V extension enablement for guests
2026-07-22 7:33 ` [PATCH v8] KVM: selftests: riscv: Add lazy V extension enablement for guests JinRui
@ 2026-07-22 7:44 ` sashiko-bot
0 siblings, 0 replies; 2+ messages in thread
From: sashiko-bot @ 2026-07-22 7:44 UTC (permalink / raw)
To: JinRui; +Cc: kvm
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] The `v_epc` array for tracking vector extension enablement is sized using `KVM_CAP_MAX_VCPUS` but indexed by `vcpu_id`. Since KVM allows `vcpu_id` up to `KVM_CAP_MAX_VCPU_ID` (which can be significantly larger than `KVM_CAP_MAX_VCPUS` for sparse topologies), tests using large vCPU IDs will silently bypass the lazy V extension enablement and fail with an unhandled exception.
--
--- Patch [1]: [PATCH v8] KVM: selftests: riscv: Add lazy V extension enablement for guests ---
commit fc8045a6835b1383f4809674a40465f3a99bc4f0
Author: jinrui <jinrui@haiwei.tech>
KVM: selftests: riscv: Add lazy V extension enablement for guests
This commit introduces lazy V extension enablement for RISC-V KVM selftests.
It replaces the default guest exception handler with the full vector table
and adds a check in route_exception() to lazily enable the V extension if
an EXC_INST_ILLEGAL is triggered by auto-vectorized instructions.
> diff --git a/tools/testing/selftests/kvm/lib/riscv/processor.c b/tools/testing/selftests/kvm/lib/riscv/processor.c
> index ded5429f34483..624f20f0f4766 100644
> --- a/tools/testing/selftests/kvm/lib/riscv/processor.c
> +++ b/tools/testing/selftests/kvm/lib/riscv/processor.c
[ ... ]
> @@ -432,6 +454,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;
> + }
> + }
> + }
> +
[ ... ]
> @@ -448,9 +510,26 @@ 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_vcpus;
> + size_t size;
> +
> + if (vm->handlers)
> + return;
> +
> + max_vcpus = kvm_check_cap(KVM_CAP_MAX_VCPUS);
> + if (max_vcpus == 0)
> + max_vcpus = 512;
> +
> + size = sizeof(struct handlers) + max_vcpus * sizeof(unsigned long);
[Severity: Medium]
Should this allocation use KVM_CAP_MAX_VCPU_ID instead of KVM_CAP_MAX_VCPUS?
The v_epc array is sized here using max_vcpus, but it is indexed by vcpu_id
when accessed in route_exception():
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;
Since KVM allows vcpu_id to go up to KVM_CAP_MAX_VCPU_ID, which can be
significantly larger than KVM_CAP_MAX_VCPUS for sparse topologies, tests
using large vCPU IDs might exceed this capacity. This would cause the bounds
check in route_exception() to fail, silently bypassing the lazy V extension
enablement and resulting in an unhandled exception for those tests.
> + 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_vcpus;
> + }
> +
> *(gva_t *)addr_gva2hva(vm, (gva_t)(&exception_handlers)) = vm->handlers;
> }
--
Sashiko AI review · https://sashiko.dev/#/patchset/33AEC00DC78613A1+20260722073344.771230-1-jinrui@haiwei.tech?part=1
^ permalink raw reply [flat|nested] 2+ messages in thread
end of thread, other threads:[~2026-07-22 7:44 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
[not found] <20260722062804.738428-1-jinrui@haiwei.tech>
2026-07-22 7:33 ` [PATCH v8] KVM: selftests: riscv: Add lazy V extension enablement for guests JinRui
2026-07-22 7:44 ` sashiko-bot
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox