[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <20260120080013.2153519-16-anup.patel@oss.qualcomm.com>
Date: Tue, 20 Jan 2026 13:30:01 +0530
From: Anup Patel <anup.patel@....qualcomm.com>
To: Paolo Bonzini <pbonzini@...hat.com>, Atish Patra <atish.patra@...ux.dev>
Cc: Palmer Dabbelt <palmer@...belt.com>, Paul Walmsley <pjw@...nel.org>,
Alexandre Ghiti <alex@...ti.fr>, Shuah Khan <shuah@...nel.org>,
Anup Patel <anup@...infault.org>,
Andrew Jones <andrew.jones@....qualcomm.com>,
kvm-riscv@...ts.infradead.org, kvm@...r.kernel.org,
linux-riscv@...ts.infradead.org, linux-kernel@...r.kernel.org,
linux-kselftest@...r.kernel.org,
Anup Patel <anup.patel@....qualcomm.com>
Subject: [PATCH 15/27] RISC-V: KVM: Extend trap redirection for nested virtualization
The L0/host hypervisor must always redirect traps to the L1/guest
hypervisor so extend KVM RISC-V to perform the necessary nested
world-switch when redirecting traps.
Signed-off-by: Anup Patel <anup.patel@....qualcomm.com>
---
arch/riscv/include/asm/kvm_host.h | 3 +
arch/riscv/include/asm/kvm_vcpu_nested.h | 12 ++
arch/riscv/kvm/vcpu_exit.c | 28 +++-
arch/riscv/kvm/vcpu_nested.c | 162 +++++++++++++++++++++++
4 files changed, 201 insertions(+), 4 deletions(-)
diff --git a/arch/riscv/include/asm/kvm_host.h b/arch/riscv/include/asm/kvm_host.h
index 3b58953eb4eb..c510564a09a2 100644
--- a/arch/riscv/include/asm/kvm_host.h
+++ b/arch/riscv/include/asm/kvm_host.h
@@ -289,6 +289,9 @@ unsigned long kvm_riscv_vcpu_unpriv_read(struct kvm_vcpu *vcpu,
bool read_insn,
unsigned long guest_addr,
struct kvm_cpu_trap *trap);
+void kvm_riscv_vcpu_trap_smode_redirect(struct kvm_vcpu *vcpu,
+ struct kvm_cpu_trap *trap,
+ bool prev_priv);
void kvm_riscv_vcpu_trap_redirect(struct kvm_vcpu *vcpu,
struct kvm_cpu_trap *trap);
int kvm_riscv_vcpu_exit(struct kvm_vcpu *vcpu, struct kvm_run *run,
diff --git a/arch/riscv/include/asm/kvm_vcpu_nested.h b/arch/riscv/include/asm/kvm_vcpu_nested.h
index 4234c6e81bb6..6bfb67702610 100644
--- a/arch/riscv/include/asm/kvm_vcpu_nested.h
+++ b/arch/riscv/include/asm/kvm_vcpu_nested.h
@@ -75,6 +75,18 @@ void kvm_riscv_vcpu_nested_swtlb_reset(struct kvm_vcpu *vcpu);
int kvm_riscv_vcpu_nested_swtlb_init(struct kvm_vcpu *vcpu);
void kvm_riscv_vcpu_nested_swtlb_deinit(struct kvm_vcpu *vcpu);
+enum kvm_vcpu_nested_set_virt_event {
+ NESTED_SET_VIRT_EVENT_TRAP = 0,
+ NESTED_SET_VIRT_EVENT_SRET
+};
+
+void kvm_riscv_vcpu_nested_set_virt(struct kvm_vcpu *vcpu,
+ enum kvm_vcpu_nested_set_virt_event event,
+ bool virt, bool spvp, bool gva);
+void kvm_riscv_vcpu_nested_trap_redirect(struct kvm_vcpu *vcpu,
+ struct kvm_cpu_trap *trap,
+ bool prev_priv);
+
void kvm_riscv_vcpu_nested_reset(struct kvm_vcpu *vcpu);
int kvm_riscv_vcpu_nested_init(struct kvm_vcpu *vcpu);
void kvm_riscv_vcpu_nested_deinit(struct kvm_vcpu *vcpu);
diff --git a/arch/riscv/kvm/vcpu_exit.c b/arch/riscv/kvm/vcpu_exit.c
index 4f63548e582f..aeec4c4eee06 100644
--- a/arch/riscv/kvm/vcpu_exit.c
+++ b/arch/riscv/kvm/vcpu_exit.c
@@ -149,19 +149,21 @@ unsigned long kvm_riscv_vcpu_unpriv_read(struct kvm_vcpu *vcpu,
}
/**
- * kvm_riscv_vcpu_trap_redirect -- Redirect trap to Guest
+ * kvm_riscv_vcpu_trap_smode_redirect -- Redirect S-mode trap to Guest
*
* @vcpu: The VCPU pointer
* @trap: Trap details
+ * @prev_priv: Previous privilege mode (true: S-mode, false: U-mode)
*/
-void kvm_riscv_vcpu_trap_redirect(struct kvm_vcpu *vcpu,
- struct kvm_cpu_trap *trap)
+void kvm_riscv_vcpu_trap_smode_redirect(struct kvm_vcpu *vcpu,
+ struct kvm_cpu_trap *trap,
+ bool prev_priv)
{
unsigned long vsstatus = ncsr_read(CSR_VSSTATUS);
/* Change Guest SSTATUS.SPP bit */
vsstatus &= ~SR_SPP;
- if (vcpu->arch.guest_context.sstatus & SR_SPP)
+ if (prev_priv)
vsstatus |= SR_SPP;
/* Change Guest SSTATUS.SPIE bit */
@@ -187,6 +189,24 @@ void kvm_riscv_vcpu_trap_redirect(struct kvm_vcpu *vcpu,
vcpu->arch.guest_context.sstatus |= SR_SPP;
}
+/**
+ * kvm_riscv_vcpu_trap_redirect -- Redirect HS-mode trap to Guest
+ *
+ * @vcpu: The VCPU pointer
+ * @trap: Trap details
+ */
+void kvm_riscv_vcpu_trap_redirect(struct kvm_vcpu *vcpu,
+ struct kvm_cpu_trap *trap)
+{
+ bool prev_priv = (vcpu->arch.guest_context.sstatus & SR_SPP) ? true : false;
+
+ /* Update Guest nested state */
+ kvm_riscv_vcpu_nested_trap_redirect(vcpu, trap, prev_priv);
+
+ /* Update Guest supervisor state */
+ kvm_riscv_vcpu_trap_smode_redirect(vcpu, trap, prev_priv);
+}
+
static inline int vcpu_redirect(struct kvm_vcpu *vcpu, struct kvm_cpu_trap *trap)
{
int ret = -EFAULT;
diff --git a/arch/riscv/kvm/vcpu_nested.c b/arch/riscv/kvm/vcpu_nested.c
index 3c30d35b3b39..214206fc28bb 100644
--- a/arch/riscv/kvm/vcpu_nested.c
+++ b/arch/riscv/kvm/vcpu_nested.c
@@ -3,13 +3,175 @@
* Copyright (c) 2026 Qualcomm Technologies, Inc.
*/
+#include <linux/smp.h>
#include <linux/kvm_host.h>
+#include <asm/kvm_nacl.h>
+#include <asm/kvm_mmu.h>
DEFINE_STATIC_KEY_FALSE(kvm_riscv_nested_available);
static bool __read_mostly enable_nested_virt;
module_param(enable_nested_virt, bool, 0644);
+void kvm_riscv_vcpu_nested_set_virt(struct kvm_vcpu *vcpu,
+ enum kvm_vcpu_nested_set_virt_event event,
+ bool virt, bool spvp, bool gva)
+{
+ struct kvm_vcpu_nested *ns = &vcpu->arch.nested;
+ struct kvm_vcpu_nested_csr *nsc = &ns->csr;
+ unsigned long tmp, sr_fs_vs_mask = 0;
+ int cpu;
+
+ /* If H-extension is not available for VCPU then do nothing */
+ if (!riscv_isa_extension_available(vcpu->arch.isa, h))
+ return;
+
+ /* Grab the CPU to ensure we remain on same CPU */
+ cpu = get_cpu();
+
+ /* Skip hardware CSR update if no change in virt state */
+ if (virt == ns->virt)
+ goto skip_csr_update;
+
+ /* Update config CSRs (aka hedeleg, hideleg, henvcfg, and hstateeX) */
+ kvm_riscv_vcpu_config_load(vcpu, virt);
+
+ /* Update time delta */
+ kvm_riscv_vcpu_update_timedelta(vcpu, virt);
+
+ /* Update G-stage page table */
+ kvm_riscv_mmu_update_hgatp(vcpu, virt);
+
+ /* Swap hardware vs<xyz> CSRs except vsie and vsstatus */
+ nsc->vstvec = ncsr_swap(CSR_VSTVEC, nsc->vstvec);
+ nsc->vsscratch = ncsr_swap(CSR_VSSCRATCH, nsc->vsscratch);
+ nsc->vsepc = ncsr_swap(CSR_VSEPC, nsc->vsepc);
+ nsc->vscause = ncsr_swap(CSR_VSCAUSE, nsc->vscause);
+ nsc->vstval = ncsr_swap(CSR_VSTVAL, nsc->vstval);
+ nsc->vsatp = ncsr_swap(CSR_VSATP, nsc->vsatp);
+
+ /* Update vsstatus CSR */
+ if (riscv_isa_extension_available(vcpu->arch.isa, f) ||
+ riscv_isa_extension_available(vcpu->arch.isa, d))
+ sr_fs_vs_mask |= SR_FS;
+ if (riscv_isa_extension_available(vcpu->arch.isa, v))
+ sr_fs_vs_mask |= SR_VS;
+ if (virt) {
+ /*
+ * Update vsstatus in following manner:
+ * 1) Swap hardware vsstatus (i.e. virtual-HS mode sstatus) with
+ * vsstatus in nested virtualization context (i.e. virtual-VS
+ * mode sstatus)
+ * 2) Swap host sstatus.[FS|VS] (i.e. HS mode sstatus.[FS|VS])
+ * with the vsstatus.[FS|VS] saved in nested virtualization
+ * context (i.e. virtual-HS mode sstatus.[FS|VS])
+ */
+ nsc->vsstatus = ncsr_swap(CSR_VSSTATUS, nsc->vsstatus);
+ tmp = vcpu->arch.guest_context.sstatus & sr_fs_vs_mask;
+ vcpu->arch.guest_context.sstatus &= ~sr_fs_vs_mask;
+ vcpu->arch.guest_context.sstatus |= (nsc->vsstatus & sr_fs_vs_mask);
+ nsc->vsstatus &= ~sr_fs_vs_mask;
+ nsc->vsstatus |= tmp;
+ } else {
+ /*
+ * Update vsstatus in following manner:
+ * 1) Swap host sstatus.[FS|VS] (i.e. virtual-HS mode sstatus.[FS|VS])
+ * with vsstatus.[FS|VS] saved in the nested virtualization
+ * context (i.e. HS mode sstatus.[FS|VS])
+ * 2) Swap hardware vsstatus (i.e. virtual-VS mode sstatus) with
+ * vsstatus in nested virtualization context (i.e. virtual-HS
+ * mode sstatus)
+ */
+ tmp = vcpu->arch.guest_context.sstatus & sr_fs_vs_mask;
+ vcpu->arch.guest_context.sstatus &= ~sr_fs_vs_mask;
+ vcpu->arch.guest_context.sstatus |= (nsc->vsstatus & sr_fs_vs_mask);
+ nsc->vsstatus &= ~sr_fs_vs_mask;
+ nsc->vsstatus |= tmp;
+ nsc->vsstatus = ncsr_swap(CSR_VSSTATUS, nsc->vsstatus);
+ }
+
+skip_csr_update:
+ if (event != NESTED_SET_VIRT_EVENT_SRET) {
+ /* Update guest hstatus.SPV bit */
+ nsc->hstatus &= ~HSTATUS_SPV;
+ nsc->hstatus |= (ns->virt) ? HSTATUS_SPV : 0;
+
+ /* Update guest hstatus.SPVP bit */
+ if (ns->virt) {
+ nsc->hstatus &= ~HSTATUS_SPVP;
+ if (spvp)
+ nsc->hstatus |= HSTATUS_SPVP;
+ }
+
+ /* Update guest hstatus.GVA bit */
+ if (event == NESTED_SET_VIRT_EVENT_TRAP) {
+ nsc->hstatus &= ~HSTATUS_GVA;
+ nsc->hstatus |= (gva) ? HSTATUS_GVA : 0;
+ }
+ }
+
+ /* Update host SRET trapping */
+ vcpu->arch.guest_context.hstatus &= ~HSTATUS_VTSR;
+ if (virt) {
+ if (nsc->hstatus & HSTATUS_VTSR)
+ vcpu->arch.guest_context.hstatus |= HSTATUS_VTSR;
+ } else {
+ if (nsc->hstatus & HSTATUS_SPV)
+ vcpu->arch.guest_context.hstatus |= HSTATUS_VTSR;
+ }
+
+ /* Update host VM trapping */
+ vcpu->arch.guest_context.hstatus &= ~HSTATUS_VTVM;
+ if (virt && (nsc->hstatus & HSTATUS_VTVM))
+ vcpu->arch.guest_context.hstatus |= HSTATUS_VTVM;
+
+ /* Update virt flag */
+ ns->virt = virt;
+
+ /* Release CPU */
+ put_cpu();
+}
+
+void kvm_riscv_vcpu_nested_trap_redirect(struct kvm_vcpu *vcpu,
+ struct kvm_cpu_trap *trap,
+ bool prev_priv)
+{
+ bool gva;
+
+ /* Do nothing if H-extension is not available for VCPU */
+ if (!riscv_isa_extension_available(vcpu->arch.isa, h))
+ return;
+
+ /* Determine GVA bit state */
+ gva = false;
+ switch (trap->scause) {
+ case EXC_INST_MISALIGNED:
+ case EXC_INST_ACCESS:
+ case EXC_LOAD_MISALIGNED:
+ case EXC_LOAD_ACCESS:
+ case EXC_STORE_MISALIGNED:
+ case EXC_STORE_ACCESS:
+ case EXC_INST_PAGE_FAULT:
+ case EXC_LOAD_PAGE_FAULT:
+ case EXC_STORE_PAGE_FAULT:
+ case EXC_INST_GUEST_PAGE_FAULT:
+ case EXC_LOAD_GUEST_PAGE_FAULT:
+ case EXC_STORE_GUEST_PAGE_FAULT:
+ gva = true;
+ break;
+ default:
+ break;
+ }
+
+ /* Update Guest HTVAL and HTINST */
+ vcpu->arch.nested.csr.htval = trap->htval;
+ vcpu->arch.nested.csr.htinst = trap->htinst;
+
+ /* Turn-off nested virtualization for virtual-HS mode */
+ kvm_riscv_vcpu_nested_set_virt(vcpu, NESTED_SET_VIRT_EVENT_TRAP,
+ false, prev_priv, gva);
+}
+
void kvm_riscv_vcpu_nested_reset(struct kvm_vcpu *vcpu)
{
struct kvm_vcpu_nested *ns = &vcpu->arch.nested;
--
2.43.0
Powered by blists - more mailing lists