[PATCH v4 18/21] KVM: arm64: Support SDEI event injection

Gavin Shan
2021-08-15T00:16:39Z

This supports SDEI event injection by implementing kvm_sdei_inject().
It's called by kernel directly or VMM through ioctl command to inject
SDEI event to the specific vCPU.

Signed-off-by: Gavin Shan <gshan@redhat.com>
---
arch/arm64/include/asm/kvm_sdei.h | 2 +
arch/arm64/include/uapi/asm/kvm_sdei.h | 1 +
arch/arm64/kvm/sdei.c | 108 +++++++++++++++++++++++++
3 files changed, 111 insertions(+)

diff --git a/arch/arm64/include/asm/kvm_sdei.h b/arch/arm64/include/asm/kvm_sdei.h
index a997989bab77..51087fe971ba 100644
--- a/arch/arm64/include/asm/kvm_sdei.h
+++ b/arch/arm64/include/asm/kvm_sdei.h
@@ -124,6 +124,8 @@ void kvm_sdei_create_vcpu(struct kvm_vcpu *vcpu);
int kvm_sdei_hypercall(struct kvm_vcpu *vcpu);
int kvm_sdei_register_notifier(struct kvm *kvm, unsigned long num,
kvm_sdei_notifier notifier);
+int kvm_sdei_inject(struct kvm_vcpu *vcpu,
+ unsigned long num, bool immediate);
void kvm_sdei_deliver(struct kvm_vcpu *vcpu);
long kvm_sdei_vm_ioctl(struct kvm *kvm, unsigned long arg);
long kvm_sdei_vcpu_ioctl(struct kvm_vcpu *vcpu, unsigned long arg);
diff --git a/arch/arm64/include/uapi/asm/kvm_sdei.h b/arch/arm64/include/uapi/asm/kvm_sdei.h
index b916c3435646..f7a6b2b22b50 100644
--- a/arch/arm64/include/uapi/asm/kvm_sdei.h
+++ b/arch/arm64/include/uapi/asm/kvm_sdei.h
@@ -67,6 +67,7 @@ struct kvm_sdei_vcpu_state {
#define KVM_SDEI_CMD_SET_VEVENT 7
#define KVM_SDEI_CMD_GET_VCPU_STATE 8
#define KVM_SDEI_CMD_SET_VCPU_STATE 9
+#define KVM_SDEI_CMD_INJECT_EVENT 10

struct kvm_sdei_cmd {
__u32 cmd;
diff --git a/arch/arm64/kvm/sdei.c b/arch/arm64/kvm/sdei.c
index 79315b77f24b..7c2789cd1421 100644
--- a/arch/arm64/kvm/sdei.c
+++ b/arch/arm64/kvm/sdei.c
@@ -802,6 +802,111 @@ int kvm_sdei_register_notifier(struct kvm *kvm,
return ret;
}

+int kvm_sdei_inject(struct kvm_vcpu *vcpu,
+ unsigned long num,
+ bool immediate)
+{
+ struct kvm *kvm = vcpu->kvm;
+ struct kvm_sdei_kvm *ksdei = kvm->arch.sdei;
+ struct kvm_sdei_vcpu *vsdei = vcpu->arch.sdei;
+ struct kvm_sdei_event *kse = NULL;
+ struct kvm_sdei_kvm_event *kske = NULL;
+ struct kvm_sdei_vcpu_event *ksve = NULL;
+ int index, ret = 0;
+
+ /* Sanity check */
+ if (!(ksdei && vsdei)) {
+ ret = -EPERM;
+ goto out;
+ }
+
+ if (!kvm_sdei_is_valid_event_num(num)) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ /* Check the kvm event */
+ spin_lock(&ksdei->lock);
+ kske = kvm_sdei_find_kvm_event(kvm, num);
+ if (!kske) {
+ ret = -ENOENT;
+ goto unlock_kvm;
+ }
+
+ kse = kske->kse;
+ index = (kse->state.type == SDEI_EVENT_TYPE_PRIVATE) ?
+ vcpu->vcpu_idx : 0;
+ if (!(kvm_sdei_is_registered(kske, index) &&
+ kvm_sdei_is_enabled(kske, index))) {
+ ret = -EPERM;
+ goto unlock_kvm;
+ }
+
+ /* Check the vcpu state */
+ spin_lock(&vsdei->lock);
+ if (vsdei->state.masked) {
+ ret = -EPERM;
+ goto unlock_vcpu;
+ }
+
+ /* Check if the event can be delivered immediately */
+ if (immediate) {
+ if (kse->state.priority == SDEI_EVENT_PRIORITY_CRITICAL &&
+ !list_empty(&vsdei->critical_events)) {
+ ret = -ENOSPC;
+ goto unlock_vcpu;
+ }
+
+ if (kse->state.priority == SDEI_EVENT_PRIORITY_NORMAL &&
+ (!list_empty(&vsdei->critical_events) ||
+ !list_empty(&vsdei->normal_events))) {
+ ret = -ENOSPC;
+ goto unlock_vcpu;
+ }
+ }
+
+ /* Check if the vcpu event exists */
+ ksve = kvm_sdei_find_vcpu_event(vcpu, num);
+ if (ksve) {
+ kske->state.refcount++;
+ ksve->state.refcount++;
+ kvm_make_request(KVM_REQ_SDEI, vcpu);
+ goto unlock_vcpu;
+ }
+
+ /* Allocate vcpu event */
+ ksve = kzalloc(sizeof(*ksve), GFP_KERNEL);
+ if (!ksve) {
+ ret = -ENOMEM;
+ goto unlock_vcpu;
+ }
+
+ /*
+ * We should take lock to update KVM event state because its
+ * reference count might be zero. In that case, the KVM event
+ * could be destroyed.
+ */
+ kske->state.refcount++;
+ ksve->state.num = num;
+ ksve->state.refcount = 1;
+ ksve->kske = kske;
+ ksve->vcpu = vcpu;
+
+ if (kse->state.priority == SDEI_EVENT_PRIORITY_CRITICAL)
+ list_add_tail(&ksve->link, &vsdei->critical_events);
+ else
+ list_add_tail(&ksve->link, &vsdei->normal_events);
+
+ kvm_make_request(KVM_REQ_SDEI, vcpu);
+
+unlock_vcpu:
+ spin_unlock(&vsdei->lock);
+unlock_kvm:
+ spin_unlock(&ksdei->lock);
+out:
+ return ret;
+}
+
void kvm_sdei_deliver(struct kvm_vcpu *vcpu)
{
struct kvm *kvm = vcpu->kvm;
@@ -1317,6 +1422,9 @@ long kvm_sdei_vcpu_ioctl(struct kvm_vcpu *vcpu, unsigned long arg)
case KVM_SDEI_CMD_SET_VCPU_STATE:
ret = kvm_sdei_set_vcpu_state(vcpu, &cmd->ksv_state);
break;
+ case KVM_SDEI_CMD_INJECT_EVENT:
+ ret = kvm_sdei_inject(vcpu, cmd->num, false);
+ break;
default:
ret = -EINVAL;
}
--
2.23.0