[PATCH v3] RISC-V: KVM: Add kvm-riscv.wfi_trap_policy to control VS-mode WFI trapping
"Yuhang.Chen" <[email protected]> Wed, 5 Aug 2026 14:51:32 +0800
| Newsgroups | org.infradead.lists.kvm-riscv,dev.linux.lists.oe-kbuild-all,org.infradead.lists.linux-riscv,org.kernel.vger.kvm,org.kernel.vger.linux-doc,org.kernel.vger.linux-kernel |
|---|---|
| Message-ID | <[email protected]> |
Add a kernel command-line option, kvm-riscv.wfi_trap_policy=trap|auto,
that controls whether a WFI executed by a VS-mode guest traps into KVM
(HS-mode) or executes natively.
HSTATUS.VTW governs VS-mode WFI: when set, the WFI traps into KVM, which
blocks the vCPU through kvm_vcpu_halt() and releases the CPU to other
runnable tasks; when clear, the guest runs WFI natively. Because RISC-V
WFI is only a hint (it may be a no-op on some implementations), the
policy is re-evaluated before each guest entry rather than fixed once
at reset.
trap : always trap VS-mode WFI into KVM (HSTATUS.VTW=1). This is the
default and preserves the previous unconditional behavior.
auto : clear HSTATUS.VTW so the guest runs WFI natively only when the
vCPU is the sole runnable task on the current CPU; otherwise
keep trapping. When the vCPU is alone, skipping the
virtual-instruction exit cannot starve another task, and on
hardware that honors WFI the hart blocks until a VS-mode
interrupt. The policy is re-evaluated before every guest entry,
so when another task becomes runnable the next entry traps
again and KVM blocks the vCPU through kvm_vcpu_halt(), yielding
the CPU. The vCPU therefore never monopolizes the CPU the way
an unconditional native WFI would: it either blocks through
kvm_vcpu_halt(), or runs WFI natively only when no other task
needs the CPU.
Measured on QEMU TCG (-smp 1, -cpu max): wfi_exit_stat delta and guest
wake count over a 3 s window. "busy" adds a CPU-bound competitor that
shares the vCPU's CPU so that single_task_running() reports false:
policy busy exits wakes cpu% note
------ ---- ----- ----- ---- ------------------------
trap off 286 285 6.5 default; no regression
trap on 291 290 101.0 trap is unconditional
auto off 4 287 5.0 sole task: native WFI
auto on 285 284 101.5 competitor -> traps
With "auto", WFI exits drop to ~0 when the vCPU is the only runnable
task, and rise back to the trap level as soon as a competitor appears,
which is the desired dynamic behavior. Host CPU stays low in the
sole-task case; the ~101% in the busy cases is the forked competitor,
not the vCPU.
Assisted-by: YuanSheng:deepseek-v4-pro
Co-developed-by: Quan Zhou <[email protected]>
Signed-off-by: Quan Zhou <[email protected]>
Signed-off-by: Yuhang.Chen <[email protected]>
Reported-by: kernel test robot <[email protected]>
Closes: https://lore.kernel.org/oe-kbuild-all/[email protected]/
---
Changes in v3:
- Guard the early_param() parser with #ifndef MODULE. RISC-V KVM is
tristate and may be built as a module (CONFIG_KVM=m), but
early_param() is only defined for built-in code, so v2 failed to
compile as a module. With CONFIG_KVM=m the policy now keeps its
default (trap) value, which is safe and regression-free; the rest of
the policy logic is unaffected. The kernel-parameters.txt entry now
documents that the option is only honored when KVM is built in.
- Re-evaluate the WFI trap policy before each guest entry instead of
only in kvm_arch_vcpu_load(). In v2 the policy was checked once when
the vCPU was loaded, so between two kvm_arch_vcpu_load() calls
HSTATUS.VTW could go stale: if a task of lower or equal priority woke
on the vCPU's CPU while the guest was running, the run loop would
re-enter the guest without re-checking and without preempting, and a
native WFI could then halt the hart and delay the woken task until the
next timer tick. Checking before every entry closes this window,
because a wakeup arrives through a host interrupt that forces a VM
exit, and the next entry re-evaluates and traps.
Changes in v2:
- Drop the "notrap" mode, which cleared HSTATUS.VTW unconditionally and
so never trapped: the vCPU never reached kvm_vcpu_halt() and could
stay busy even while idle.
- Add the "auto" mode, which clears HSTATUS.VTW only when the vCPU is
the sole runnable task (single_task_running()) and otherwise traps.
The policy is applied dynamically from kvm_arch_vcpu_load() instead of
once at reset, so a vCPU that stops being the sole runnable task
switches back to trapping.
- Update the kernel-parameters.txt entry for trap/auto.
v1: https://lore.kernel.org/all/[email protected]/
v2: https://lore.kernel.org/all/[email protected]/
---
.../admin-guide/kernel-parameters.txt | 20 ++++++
arch/riscv/kvm/vcpu.c | 72 +++++++++++++++++++
2 files changed, 92 insertions(+)
diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt
index b5493a7f8f22..2ab9eb335e60 100644
--- a/Documentation/admin-guide/kernel-parameters.txt
+++ b/Documentation/admin-guide/kernel-parameters.txt
@@ -3254,6 +3254,26 @@ Kernel parameters
notrap: clear WFI instruction trap
+ kvm-riscv.wfi_trap_policy=
+ [KVM,RISCV] Control when to set the WFI instruction
+ trap (HSTATUS.VTW) for KVM VMs. The policy is
+ re-evaluated before each guest entry, not only at
+ reset, since RISC-V WFI is only a hint.
+
+ trap: always trap VS-mode WFI into KVM (HSTATUS.VTW=1)
+
+ auto: trap unless the vCPU is the only runnable task on
+ the current CPU, in which case clear the trap
+ (HSTATUS.VTW=0) and let the guest execute WFI
+ natively
+
+ Defaults to trap, preserving the previous unconditional
+ behavior.
+
+ Only honored when KVM is built into the kernel
+ (CONFIG_KVM=y); as a module (CONFIG_KVM=m) the policy
+ stays at its default (trap).
+
kvm_cma_resv_ratio=n [PPC,EARLY]
Reserves given percentage from system memory area for
contiguous memory allocation for KVM hash pagetable
diff --git a/arch/riscv/kvm/vcpu.c b/arch/riscv/kvm/vcpu.c
index cf6e231e76e2..8efeb74699cc 100644
--- a/arch/riscv/kvm/vcpu.c
+++ b/arch/riscv/kvm/vcpu.c
@@ -12,8 +12,10 @@
#include <linux/kdebug.h>
#include <linux/module.h>
#include <linux/percpu.h>
+#include <linux/string.h>
#include <linux/vmalloc.h>
#include <linux/sched/signal.h>
+#include <linux/sched/stat.h>
#include <linux/fs.h>
#include <linux/kvm_host.h>
#include <asm/cacheflush.h>
@@ -26,6 +28,67 @@
static DEFINE_PER_CPU(struct kvm_vcpu *, kvm_former_vcpu);
+/*
+ * WFI trap policy for VS-mode guests, controllable through the
+ * kvm-riscv.wfi_trap_policy= kernel command-line option.
+ */
+enum kvm_riscv_wfi_trap_policy {
+ KVM_RISCV_WFI_TRAP, /* Always trap VS-mode WFI into KVM */
+ KVM_RISCV_WFI_AUTO, /* Trap unless the vCPU is the only runnable task */
+};
+
+static enum kvm_riscv_wfi_trap_policy kvm_riscv_wfi_trap_policy __read_mostly =
+ KVM_RISCV_WFI_TRAP;
+
+/*
+ * RISC-V KVM is tristate and may be built as a module, but early_param() is
+ * only defined for built-in code (see <linux/init.h>). Guard the command-line
+ * parser accordingly: when CONFIG_KVM=m the policy simply keeps its default
+ * (trap) value, which is the safe, regression-free behavior.
+ */
+#ifndef MODULE
+static int __init early_kvm_riscv_wfi_trap_policy_cfg(char *arg)
+{
+ if (!arg)
+ return -EINVAL;
+
+ if (strcmp(arg, "trap") == 0) {
+ kvm_riscv_wfi_trap_policy = KVM_RISCV_WFI_TRAP;
+ return 0;
+ }
+
+ if (strcmp(arg, "auto") == 0) {
+ kvm_riscv_wfi_trap_policy = KVM_RISCV_WFI_AUTO;
+ return 0;
+ }
+
+ return -EINVAL;
+}
+early_param("kvm-riscv.wfi_trap_policy", early_kvm_riscv_wfi_trap_policy_cfg);
+#endif
+
+static bool kvm_riscv_vcpu_wfi_should_trap(struct kvm_vcpu *vcpu)
+{
+ switch (kvm_riscv_wfi_trap_policy) {
+ case KVM_RISCV_WFI_AUTO:
+ /* Native WFI only when the vCPU is the sole runnable task. */
+ return !single_task_running();
+ case KVM_RISCV_WFI_TRAP:
+ default:
+ return true;
+ }
+}
+
+static void kvm_riscv_vcpu_update_wfi_trap(struct kvm_vcpu *vcpu)
+{
+ struct kvm_cpu_context *cntx = &vcpu->arch.guest_context;
+
+ if (kvm_riscv_vcpu_wfi_should_trap(vcpu))
+ cntx->hstatus |= HSTATUS_VTW;
+ else
+ cntx->hstatus &= ~HSTATUS_VTW;
+}
+
const struct kvm_stats_desc kvm_vcpu_stats_desc[] = {
KVM_GENERIC_VCPU_STATS(),
STATS_DESC_COUNTER(VCPU, ecall_exit_stat),
@@ -73,6 +136,7 @@ static void kvm_riscv_vcpu_context_reset(struct kvm_vcpu *vcpu,
/* Setup reset state of shadow SSTATUS and HSTATUS CSRs */
cntx->sstatus = SR_SPP | SR_SPIE;
+ /* Trap VS-mode WFI by default; the run loop reapplies the policy before each entry. */
cntx->hstatus |= HSTATUS_VTW;
cntx->hstatus |= HSTATUS_SPVP;
cntx->hstatus |= HSTATUS_SPV;
@@ -936,6 +1000,14 @@ int kvm_arch_vcpu_ioctl_run(struct kvm_vcpu *vcpu)
*/
kvm_riscv_local_tlb_sanitize(vcpu);
+ /*
+ * Re-evaluate the WFI trap policy for this entry so that
+ * HSTATUS.VTW tracks the current runnable-task count and
+ * cannot go stale across guest entries (which would let a
+ * native WFI halt the CPU while another task is runnable).
+ */
+ kvm_riscv_vcpu_update_wfi_trap(vcpu);
+
trace_kvm_entry(vcpu);
guest_timing_enter_irqoff();
--
2.34.1
--
kvm-riscv mailing list
[email protected]
http://lists.infradead.org/mailman/listinfo/kvm-riscv