[RFC PATCH 7/7] RISC-V: KVM: add support for double trap exception

From: Clément Léger
Date: Thu Apr 18 2024 - 10:30:42 EST


When a double trap exception is generated from VS-mode, it should be
delivered to M-mode which might redirect it to S-mode. Currently, the
kvm double trap exception handling simply prints an error and returns
-EOPNOTSUPP to stop VM execution. In future, this might use KVM SBI SSE
extension implementation to actually send an SSE event to the guest VM.

Signed-off-by: Clément Léger <cleger@xxxxxxxxxxxx>
---
arch/riscv/include/asm/kvm_host.h | 7 ++++---
arch/riscv/include/uapi/asm/kvm.h | 1 +
arch/riscv/kvm/vcpu.c | 23 +++++++++------------
arch/riscv/kvm/vcpu_exit.c | 33 +++++++++++++++++++++++++------
arch/riscv/kvm/vcpu_insn.c | 15 +++++---------
arch/riscv/kvm/vcpu_onereg.c | 2 ++
arch/riscv/kvm/vcpu_sbi.c | 4 +---
arch/riscv/kvm/vcpu_switch.S | 19 +++++++++++++++---
8 files changed, 65 insertions(+), 39 deletions(-)

diff --git a/arch/riscv/include/asm/kvm_host.h b/arch/riscv/include/asm/kvm_host.h
index be60aaa07f57..1d699bf44c45 100644
--- a/arch/riscv/include/asm/kvm_host.h
+++ b/arch/riscv/include/asm/kvm_host.h
@@ -358,12 +358,13 @@ 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_redirect(struct kvm_vcpu *vcpu,
- struct kvm_cpu_trap *trap);
+int 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,
struct kvm_cpu_trap *trap);

-void __kvm_riscv_switch_to(struct kvm_vcpu_arch *vcpu_arch);
+void __kvm_riscv_switch_to(struct kvm_vcpu_arch *vcpu_arch,
+ struct kvm_cpu_trap *trap);

void kvm_riscv_vcpu_setup_isa(struct kvm_vcpu *vcpu);
unsigned long kvm_riscv_vcpu_num_regs(struct kvm_vcpu *vcpu);
diff --git a/arch/riscv/include/uapi/asm/kvm.h b/arch/riscv/include/uapi/asm/kvm.h
index fa3097da91c0..323f4e8380d2 100644
--- a/arch/riscv/include/uapi/asm/kvm.h
+++ b/arch/riscv/include/uapi/asm/kvm.h
@@ -166,6 +166,7 @@ enum KVM_RISCV_ISA_EXT_ID {
KVM_RISCV_ISA_EXT_ZVFH,
KVM_RISCV_ISA_EXT_ZVFHMIN,
KVM_RISCV_ISA_EXT_ZFA,
+ KVM_RISCV_ISA_EXT_SSDBLTRP,
KVM_RISCV_ISA_EXT_MAX,
};

diff --git a/arch/riscv/kvm/vcpu.c b/arch/riscv/kvm/vcpu.c
index 461ef60d4eda..89e663defe14 100644
--- a/arch/riscv/kvm/vcpu.c
+++ b/arch/riscv/kvm/vcpu.c
@@ -121,6 +121,8 @@ int kvm_arch_vcpu_create(struct kvm_vcpu *vcpu)
/* Setup reset state of shadow SSTATUS and HSTATUS CSRs */
cntx = &vcpu->arch.guest_reset_context;
cntx->sstatus = SR_SPP | SR_SPIE;
+ if (riscv_isa_extension_available(vcpu->arch.isa, SSDBLTRP))
+ cntx->sstatus |= SR_SDT;
cntx->hstatus = 0;
cntx->hstatus |= HSTATUS_VTW;
cntx->hstatus |= HSTATUS_SPVP;
@@ -579,6 +581,9 @@ void kvm_arch_vcpu_put(struct kvm_vcpu *vcpu)
csr->hvip = csr_read(CSR_HVIP);
csr->vsatp = csr_read(CSR_VSATP);
cfg->hedeleg = csr_read(CSR_HEDELEG);
+ cfg->henvcfg = csr_read(CSR_HENVCFG);
+ if (IS_ENABLED(CONFIG_32BIT))
+ cfg->henvcfg = csr_read(CSR_HENVCFGH) << 32;
}

static void kvm_riscv_check_vcpu_requests(struct kvm_vcpu *vcpu)
@@ -670,11 +675,12 @@ static __always_inline void kvm_riscv_vcpu_swap_in_host_state(struct kvm_vcpu *v
* This must be noinstr as instrumentation may make use of RCU, and this is not
* safe during the EQS.
*/
-static void noinstr kvm_riscv_vcpu_enter_exit(struct kvm_vcpu *vcpu)
+static void noinstr kvm_riscv_vcpu_enter_exit(struct kvm_vcpu *vcpu,
+ struct kvm_cpu_trap *trap)
{
kvm_riscv_vcpu_swap_in_guest_state(vcpu);
guest_state_enter_irqoff();
- __kvm_riscv_switch_to(&vcpu->arch);
+ __kvm_riscv_switch_to(&vcpu->arch, trap);
vcpu->arch.last_exit_cpu = vcpu->cpu;
guest_state_exit_irqoff();
kvm_riscv_vcpu_swap_in_host_state(vcpu);
@@ -789,22 +795,11 @@ int kvm_arch_vcpu_ioctl_run(struct kvm_vcpu *vcpu)

guest_timing_enter_irqoff();

- kvm_riscv_vcpu_enter_exit(vcpu);
+ kvm_riscv_vcpu_enter_exit(vcpu, &trap);

vcpu->mode = OUTSIDE_GUEST_MODE;
vcpu->stat.exits++;

- /*
- * Save SCAUSE, STVAL, HTVAL, and HTINST because we might
- * get an interrupt between __kvm_riscv_switch_to() and
- * local_irq_enable() which can potentially change CSRs.
- */
- trap.sepc = vcpu->arch.guest_context.sepc;
- trap.scause = csr_read(CSR_SCAUSE);
- trap.stval = csr_read(CSR_STVAL);
- trap.htval = csr_read(CSR_HTVAL);
- trap.htinst = csr_read(CSR_HTINST);
-
/* Syncup interrupts state with HW */
kvm_riscv_vcpu_sync_interrupts(vcpu);

diff --git a/arch/riscv/kvm/vcpu_exit.c b/arch/riscv/kvm/vcpu_exit.c
index 2415722c01b8..892c6df97eaf 100644
--- a/arch/riscv/kvm/vcpu_exit.c
+++ b/arch/riscv/kvm/vcpu_exit.c
@@ -126,17 +126,34 @@ unsigned long kvm_riscv_vcpu_unpriv_read(struct kvm_vcpu *vcpu,
return val;
}

+static int kvm_riscv_double_trap(struct kvm_vcpu *vcpu,
+ struct kvm_cpu_trap *trap)
+{
+ pr_err("Guest double trap");
+ /* TODO: Implement SSE support */
+
+ return -EOPNOTSUPP;
+}
+
/**
* kvm_riscv_vcpu_trap_redirect -- Redirect 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)
+int kvm_riscv_vcpu_trap_redirect(struct kvm_vcpu *vcpu,
+ struct kvm_cpu_trap *trap)
{
unsigned long vsstatus = csr_read(CSR_VSSTATUS);

+ if (riscv_isa_extension_available(vcpu->arch.isa, SSDBLTRP)) {
+ if (vsstatus & SR_SDT)
+ return kvm_riscv_double_trap(vcpu, trap);
+
+ /* Set Double Trap bit to enable double trap detection */
+ vsstatus |= SR_SDT;
+ }
+
/* Change Guest SSTATUS.SPP bit */
vsstatus &= ~SR_SPP;
if (vcpu->arch.guest_context.sstatus & SR_SPP)
@@ -163,6 +180,8 @@ void kvm_riscv_vcpu_trap_redirect(struct kvm_vcpu *vcpu,

/* Set Guest privilege mode to supervisor */
vcpu->arch.guest_context.sstatus |= SR_SPP;
+
+ return 1;
}

/*
@@ -185,10 +204,8 @@ int kvm_riscv_vcpu_exit(struct kvm_vcpu *vcpu, struct kvm_run *run,
case EXC_INST_ILLEGAL:
case EXC_LOAD_MISALIGNED:
case EXC_STORE_MISALIGNED:
- if (vcpu->arch.guest_context.hstatus & HSTATUS_SPV) {
- kvm_riscv_vcpu_trap_redirect(vcpu, trap);
- ret = 1;
- }
+ if (vcpu->arch.guest_context.hstatus & HSTATUS_SPV)
+ ret = kvm_riscv_vcpu_trap_redirect(vcpu, trap);
break;
case EXC_VIRTUAL_INST_FAULT:
if (vcpu->arch.guest_context.hstatus & HSTATUS_SPV)
@@ -204,6 +221,10 @@ int kvm_riscv_vcpu_exit(struct kvm_vcpu *vcpu, struct kvm_run *run,
if (vcpu->arch.guest_context.hstatus & HSTATUS_SPV)
ret = kvm_riscv_vcpu_sbi_ecall(vcpu, run);
break;
+ case EXC_DOUBLE_TRAP:
+ if (vcpu->arch.guest_context.hstatus & HSTATUS_SPV)
+ ret = kvm_riscv_double_trap(vcpu, trap);
+ break;
default:
break;
}
diff --git a/arch/riscv/kvm/vcpu_insn.c b/arch/riscv/kvm/vcpu_insn.c
index 7a6abed41bc1..050e811204f2 100644
--- a/arch/riscv/kvm/vcpu_insn.c
+++ b/arch/riscv/kvm/vcpu_insn.c
@@ -159,9 +159,8 @@ static int truly_illegal_insn(struct kvm_vcpu *vcpu, struct kvm_run *run,
utrap.stval = insn;
utrap.htval = 0;
utrap.htinst = 0;
- kvm_riscv_vcpu_trap_redirect(vcpu, &utrap);

- return 1;
+ return kvm_riscv_vcpu_trap_redirect(vcpu, &utrap);
}

static int truly_virtual_insn(struct kvm_vcpu *vcpu, struct kvm_run *run,
@@ -175,9 +174,8 @@ static int truly_virtual_insn(struct kvm_vcpu *vcpu, struct kvm_run *run,
utrap.stval = insn;
utrap.htval = 0;
utrap.htinst = 0;
- kvm_riscv_vcpu_trap_redirect(vcpu, &utrap);

- return 1;
+ return kvm_riscv_vcpu_trap_redirect(vcpu, &utrap);
}

/**
@@ -422,8 +420,7 @@ int kvm_riscv_vcpu_virtual_insn(struct kvm_vcpu *vcpu, struct kvm_run *run,
&utrap);
if (utrap.scause) {
utrap.sepc = ct->sepc;
- kvm_riscv_vcpu_trap_redirect(vcpu, &utrap);
- return 1;
+ return kvm_riscv_vcpu_trap_redirect(vcpu, &utrap);
}
}
if (INSN_IS_16BIT(insn))
@@ -478,8 +475,7 @@ int kvm_riscv_vcpu_mmio_load(struct kvm_vcpu *vcpu, struct kvm_run *run,
if (utrap.scause) {
/* Redirect trap if we failed to read instruction */
utrap.sepc = ct->sepc;
- kvm_riscv_vcpu_trap_redirect(vcpu, &utrap);
- return 1;
+ return kvm_riscv_vcpu_trap_redirect(vcpu, &utrap);
}
insn_len = INSN_LEN(insn);
}
@@ -604,8 +600,7 @@ int kvm_riscv_vcpu_mmio_store(struct kvm_vcpu *vcpu, struct kvm_run *run,
if (utrap.scause) {
/* Redirect trap if we failed to read instruction */
utrap.sepc = ct->sepc;
- kvm_riscv_vcpu_trap_redirect(vcpu, &utrap);
- return 1;
+ return kvm_riscv_vcpu_trap_redirect(vcpu, &utrap);
}
insn_len = INSN_LEN(insn);
}
diff --git a/arch/riscv/kvm/vcpu_onereg.c b/arch/riscv/kvm/vcpu_onereg.c
index 5f7355e96008..fece0043871c 100644
--- a/arch/riscv/kvm/vcpu_onereg.c
+++ b/arch/riscv/kvm/vcpu_onereg.c
@@ -36,6 +36,7 @@ static const unsigned long kvm_isa_ext_arr[] = {
/* Multi letter extensions (alphabetically sorted) */
KVM_ISA_EXT_ARR(SMSTATEEN),
KVM_ISA_EXT_ARR(SSAIA),
+ KVM_ISA_EXT_ARR(SSDBLTRP),
KVM_ISA_EXT_ARR(SSTC),
KVM_ISA_EXT_ARR(SVINVAL),
KVM_ISA_EXT_ARR(SVNAPOT),
@@ -153,6 +154,7 @@ static bool kvm_riscv_vcpu_isa_disable_allowed(unsigned long ext)
case KVM_RISCV_ISA_EXT_ZVKSED:
case KVM_RISCV_ISA_EXT_ZVKSH:
case KVM_RISCV_ISA_EXT_ZVKT:
+ case KVM_RISCV_ISA_EXT_SSDBLTRP:
return false;
/* Extensions which can be disabled using Smstateen */
case KVM_RISCV_ISA_EXT_SSAIA:
diff --git a/arch/riscv/kvm/vcpu_sbi.c b/arch/riscv/kvm/vcpu_sbi.c
index 76901f0f34b7..b839d578dc26 100644
--- a/arch/riscv/kvm/vcpu_sbi.c
+++ b/arch/riscv/kvm/vcpu_sbi.c
@@ -456,10 +456,8 @@ int kvm_riscv_vcpu_sbi_ecall(struct kvm_vcpu *vcpu, struct kvm_run *run)

/* Handle special error cases i.e trap, exit or userspace forward */
if (sbi_ret.utrap->scause) {
- /* No need to increment sepc or exit ioctl loop */
- ret = 1;
sbi_ret.utrap->sepc = cp->sepc;
- kvm_riscv_vcpu_trap_redirect(vcpu, sbi_ret.utrap);
+ ret = kvm_riscv_vcpu_trap_redirect(vcpu, sbi_ret.utrap);
next_sepc = false;
goto ecall_done;
}
diff --git a/arch/riscv/kvm/vcpu_switch.S b/arch/riscv/kvm/vcpu_switch.S
index 0c26189aa01c..94d5eb9da788 100644
--- a/arch/riscv/kvm/vcpu_switch.S
+++ b/arch/riscv/kvm/vcpu_switch.S
@@ -154,7 +154,6 @@ SYM_FUNC_START(__kvm_riscv_switch_to)
REG_L t2, (KVM_ARCH_HOST_SSCRATCH)(a0)
REG_L t3, (KVM_ARCH_HOST_SCOUNTEREN)(a0)
REG_L t4, (KVM_ARCH_HOST_HSTATUS)(a0)
- REG_L t5, (KVM_ARCH_HOST_SSTATUS)(a0)

/* Save Guest SEPC */
csrr t0, CSR_SEPC
@@ -171,8 +170,8 @@ SYM_FUNC_START(__kvm_riscv_switch_to)
/* Save Guest and Restore Host HSTATUS */
csrrw t4, CSR_HSTATUS, t4

- /* Save Guest and Restore Host SSTATUS */
- csrrw t5, CSR_SSTATUS, t5
+ /* Save Guest SSTATUS */
+ csrr t5, CSR_SSTATUS

/* Store Guest CSR values */
REG_S t0, (KVM_ARCH_GUEST_SEPC)(a0)
@@ -206,6 +205,20 @@ SYM_FUNC_START(__kvm_riscv_switch_to)
REG_L s10, (KVM_ARCH_HOST_S10)(a0)
REG_L s11, (KVM_ARCH_HOST_S11)(a0)

+ csrr t1, CSR_SCAUSE
+ csrr t2, CSR_STVAL
+ csrr t3, CSR_HTVAL
+ csrr t4, CSR_HTINST
+ REG_S t0, (KVM_ARCH_TRAP_SEPC)(a1)
+ REG_S t1, (KVM_ARCH_TRAP_SCAUSE)(a1)
+ REG_S t2, (KVM_ARCH_TRAP_STVAL)(a1)
+ REG_S t3, (KVM_ARCH_TRAP_HTVAL)(a1)
+ REG_S t4, (KVM_ARCH_TRAP_HTINST)(a1)
+
+ /* Restore Host SSTATUS */
+ REG_L t5, (KVM_ARCH_HOST_SSTATUS)(a0)
+ csrw CSR_SSTATUS, t5
+
/* Return to C code */
ret
SYM_FUNC_END(__kvm_riscv_switch_to)
--
2.43.0