[PATCH v5 16/22] KVM: arm64: Support SDEI_EVENT_{COMPLETE,COMPLETE_AND_RESUME} hypercall

From: Gavin Shan
Date: Tue Mar 22 2022 - 04:10:29 EST


This supports SDEI_EVENT_{COMPLETE, COMPLETE_AND_RESUME} hypercall.
They are used by the guest to notify the completion of the SDEI
event in the handler. The executing context or registers are modified
according to the SDEI specification like below:

* x0 - x17, PC and PState are restored to what values we had in
the interrupted or preempted context.

* If it's SDEI_EVENT_COMPLETE_AND_RESUME hypercall, IRQ exception
is injected.

Signed-off-by: Gavin Shan <gshan@xxxxxxxxxx>
---
arch/arm64/include/asm/kvm_emulate.h | 1 +
arch/arm64/include/asm/kvm_host.h | 1 +
arch/arm64/kvm/inject_fault.c | 29 +++++++++++
arch/arm64/kvm/sdei.c | 76 +++++++++++++++++++++++++++-
4 files changed, 106 insertions(+), 1 deletion(-)

diff --git a/arch/arm64/include/asm/kvm_emulate.h b/arch/arm64/include/asm/kvm_emulate.h
index d62405ce3e6d..ca9de9f24923 100644
--- a/arch/arm64/include/asm/kvm_emulate.h
+++ b/arch/arm64/include/asm/kvm_emulate.h
@@ -37,6 +37,7 @@ bool kvm_condition_valid32(const struct kvm_vcpu *vcpu);
void kvm_skip_instr32(struct kvm_vcpu *vcpu);

void kvm_inject_undefined(struct kvm_vcpu *vcpu);
+void kvm_inject_irq(struct kvm_vcpu *vcpu);
void kvm_inject_vabt(struct kvm_vcpu *vcpu);
void kvm_inject_dabt(struct kvm_vcpu *vcpu, unsigned long addr);
void kvm_inject_pabt(struct kvm_vcpu *vcpu, unsigned long addr);
diff --git a/arch/arm64/include/asm/kvm_host.h b/arch/arm64/include/asm/kvm_host.h
index e2762d08ab1c..282913e1afb0 100644
--- a/arch/arm64/include/asm/kvm_host.h
+++ b/arch/arm64/include/asm/kvm_host.h
@@ -428,6 +428,7 @@ struct kvm_vcpu_arch {
#define KVM_ARM64_EXCEPT_AA32_UND (0 << 9)
#define KVM_ARM64_EXCEPT_AA32_IABT (1 << 9)
#define KVM_ARM64_EXCEPT_AA32_DABT (2 << 9)
+#define KVM_ARM64_EXCEPT_AA32_IRQ (3 << 9)
/* For AArch64: */
#define KVM_ARM64_EXCEPT_AA64_ELx_SYNC (0 << 9)
#define KVM_ARM64_EXCEPT_AA64_ELx_IRQ (1 << 9)
diff --git a/arch/arm64/kvm/inject_fault.c b/arch/arm64/kvm/inject_fault.c
index b47df73e98d7..c8a8791bdf28 100644
--- a/arch/arm64/kvm/inject_fault.c
+++ b/arch/arm64/kvm/inject_fault.c
@@ -66,6 +66,13 @@ static void inject_undef64(struct kvm_vcpu *vcpu)
vcpu_write_sys_reg(vcpu, esr, ESR_EL1);
}

+static void inject_irq64(struct kvm_vcpu *vcpu)
+{
+ vcpu->arch.flags |= (KVM_ARM64_EXCEPT_AA64_EL1 |
+ KVM_ARM64_EXCEPT_AA64_ELx_IRQ |
+ KVM_ARM64_PENDING_EXCEPTION);
+}
+
#define DFSR_FSC_EXTABT_LPAE 0x10
#define DFSR_FSC_EXTABT_nLPAE 0x08
#define DFSR_LPAE BIT(9)
@@ -77,6 +84,12 @@ static void inject_undef32(struct kvm_vcpu *vcpu)
KVM_ARM64_PENDING_EXCEPTION);
}

+static void inject_irq32(struct kvm_vcpu *vcpu)
+{
+ vcpu->arch.flags |= (KVM_ARM64_EXCEPT_AA32_IRQ |
+ KVM_ARM64_PENDING_EXCEPTION);
+}
+
/*
* Modelled after TakeDataAbortException() and TakePrefetchAbortException
* pseudocode.
@@ -160,6 +173,22 @@ void kvm_inject_undefined(struct kvm_vcpu *vcpu)
inject_undef64(vcpu);
}

+/**
+ * kvm_inject_irq - inject an IRQ into the guest
+ * @vcpu: The vCPU in which to inject IRQ
+ *
+ * Inject IRQs to the target vCPU. It is assumed that this code is
+ * called from the VCPU thread and that the VCPU therefore is not
+ * currently executing guest code.
+ */
+void kvm_inject_irq(struct kvm_vcpu *vcpu)
+{
+ if (vcpu_el1_is_32bit(vcpu))
+ inject_irq32(vcpu);
+ else
+ inject_irq64(vcpu);
+}
+
void kvm_set_sei_esr(struct kvm_vcpu *vcpu, u64 esr)
{
vcpu_set_vsesr(vcpu, esr & ESR_ELx_ISS_MASK);
diff --git a/arch/arm64/kvm/sdei.c b/arch/arm64/kvm/sdei.c
index ba2ca65c871b..3019ac196e76 100644
--- a/arch/arm64/kvm/sdei.c
+++ b/arch/arm64/kvm/sdei.c
@@ -344,6 +344,78 @@ static unsigned long hypercall_context(struct kvm_vcpu *vcpu)
return ret;
}

+static unsigned long hypercall_complete(struct kvm_vcpu *vcpu, bool resume)
+{
+ struct kvm *kvm = vcpu->kvm;
+ struct kvm_sdei_kvm *ksdei = kvm->arch.sdei;
+ struct kvm_sdei_vcpu *vsdei = vcpu->arch.sdei;
+ struct kvm_sdei_exposed_event *exposed_event;
+ struct kvm_sdei_registered_event *registered_event;
+ struct kvm_sdei_vcpu_event *vcpu_event;
+ struct kvm_sdei_vcpu_regs_state *regs;
+ unsigned long ret = SDEI_SUCCESS;
+ int index;
+
+ spin_lock(&ksdei->lock);
+ spin_lock(&vsdei->lock);
+
+ if (vsdei->critical_event) {
+ vcpu_event = vsdei->critical_event;
+ regs = &vsdei->state.critical_regs;
+ vsdei->critical_event = NULL;
+ vsdei->state.critical_num = KVM_SDEI_INVALID_EVENT;
+ } else if (vsdei->normal_event) {
+ vcpu_event = vsdei->normal_event;
+ regs = &vsdei->state.normal_regs;
+ vsdei->normal_event = NULL;
+ vsdei->state.normal_num = KVM_SDEI_INVALID_EVENT;
+ } else {
+ ret = SDEI_DENIED;
+ goto unlock;
+ }
+
+ /* Restore registers: x0 -> x17, PC, PState */
+ for (index = 0; index < ARRAY_SIZE(regs->regs); index++)
+ vcpu_set_reg(vcpu, index, regs->regs[index]);
+
+ *vcpu_cpsr(vcpu) = regs->pstate;
+ *vcpu_pc(vcpu) = regs->pc;
+
+ /* Inject interrupt if needed */
+ if (resume)
+ kvm_inject_irq(vcpu);
+
+ /* Dereference the vcpu event and destroy it if needed */
+ vcpu_event->state.event_count--;
+ if (!vcpu_event->state.event_count)
+ remove_one_vcpu_event(vcpu, vcpu_event);
+
+ /*
+ * We need to check if the registered event is pending for
+ * unregistration. In that case, the registered event should
+ * be unregistered and destroyed if needed.
+ */
+ registered_event = vcpu_event->registered_event;
+ exposed_event = registered_event->exposed_event;
+ index = kvm_sdei_vcpu_index(vcpu, exposed_event);
+ if (kvm_sdei_is_unregister_pending(registered_event, index)) {
+ kvm_sdei_clear_enabled(registered_event, index);
+ kvm_sdei_clear_registered(registered_event, index);
+ if (kvm_sdei_none_registered(registered_event))
+ remove_one_registered_event(kvm, registered_event);
+ }
+
+ /* Make another request if we have any pending events */
+ if ((vsdei->critical_event_count + vsdei->normal_event_count) > 0)
+ kvm_make_request(KVM_REQ_SDEI, vcpu);
+
+unlock:
+ spin_unlock(&vsdei->lock);
+ spin_unlock(&ksdei->lock);
+
+ return ret;
+}
+
static unsigned long
unregister_one_event(struct kvm *kvm, struct kvm_vcpu *vcpu,
struct kvm_sdei_registered_event *registered_event)
@@ -864,8 +936,10 @@ int kvm_sdei_hypercall(struct kvm_vcpu *vcpu)
ret = hypercall_context(vcpu);
break;
case SDEI_1_0_FN_SDEI_EVENT_COMPLETE:
+ ret = hypercall_complete(vcpu, false);
+ break;
case SDEI_1_0_FN_SDEI_EVENT_COMPLETE_AND_RESUME:
- ret = SDEI_NOT_SUPPORTED;
+ ret = hypercall_complete(vcpu, true);
break;
case SDEI_1_0_FN_SDEI_EVENT_UNREGISTER:
ret = hypercall_unregister(vcpu);
--
2.23.0