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 <[email protected]>
---
 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


Reply via email to