[PATCH v3] RISC-V: KVM: Add kvm-riscv.wfi_trap_policy to control VS-mode WFI trapping
Anup Patel
anup at brainfault.org
Fri Oct 2 21:41:40 PDT 2026
On Wed, Aug 5, 2026 at 12:22 PM Yuhang.Chen <yhchen312 at gmail.com> wrote:
>
> 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 <zhouquan at iscas.ac.cn>
> Signed-off-by: Quan Zhou <zhouquan at iscas.ac.cn>
> Signed-off-by: Yuhang.Chen <yhchen312 at gmail.com>
> Reported-by: kernel test robot <lkp at intel.com>
> Closes: https://lore.kernel.org/oe-kbuild-all/202608051354.KYOXP5po-lkp@intel.com/
>
> ---
> 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/20260709115610.287420-1-yhchen312@gmail.com/
> v2: https://lore.kernel.org/all/20260804024707.2400404-1-yhchen312@gmail.com/
> ---
> .../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);
FYI, module parameters can also be passed as kernel parameter when the
module is built-in. There is no reason why WFI trap-policy will not work as when
KVM RISC-V is a plugabble module.
Convert this to module_param.
> +#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
>
Regards,
Anup
More information about the linux-riscv
mailing list