[PATCH v5 19/22] KVM: arm64: Support SDEI ioctl commands on vCPU

From: Gavin Shan
Date: Tue Mar 22 2022 - 04:11:06 EST


This supports ioctl commands on vCPU to manage the various object.
It's primarily used by VMM to accomplish migration. The ioctl
commands introduced by this are highlighted as below:

* KVM_SDEI_CMD_GET_VCPU_EVENT_COUNT
Return the total count of vCPU events, which have been queued
on the target vCPU.

* KVM_SDEI_CMD_GET_VCPU_EVENT
* KVM_SDEI_CMD_SET_VCPU_EVENT
Get or set vCPU events.

* KVM_SDEI_CMD_GET_VCPU_STATE
* KVM_SDEI_CMD_SET_VCPU_STATE
Get or set vCPU state.

* KVM_SDEI_CMD_INJECT_EVENT
Inject SDEI event.

Signed-off-by: Gavin Shan <gshan@xxxxxxxxxx>
---
arch/arm64/include/asm/kvm_sdei.h | 1 +
arch/arm64/include/uapi/asm/kvm_sdei_state.h | 9 +
arch/arm64/kvm/arm.c | 3 +
arch/arm64/kvm/sdei.c | 299 +++++++++++++++++++
4 files changed, 312 insertions(+)

diff --git a/arch/arm64/include/asm/kvm_sdei.h b/arch/arm64/include/asm/kvm_sdei.h
index 64f00cc79162..ea4f222cf73d 100644
--- a/arch/arm64/include/asm/kvm_sdei.h
+++ b/arch/arm64/include/asm/kvm_sdei.h
@@ -180,6 +180,7 @@ int kvm_sdei_inject_event(struct kvm_vcpu *vcpu,
int kvm_sdei_cancel_event(struct kvm_vcpu *vcpu, unsigned long num);
void kvm_sdei_deliver_event(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);
void kvm_sdei_destroy_vcpu(struct kvm_vcpu *vcpu);
void kvm_sdei_destroy_vm(struct kvm *kvm);

diff --git a/arch/arm64/include/uapi/asm/kvm_sdei_state.h b/arch/arm64/include/uapi/asm/kvm_sdei_state.h
index 2bd6d11627bc..149451c5584f 100644
--- a/arch/arm64/include/uapi/asm/kvm_sdei_state.h
+++ b/arch/arm64/include/uapi/asm/kvm_sdei_state.h
@@ -75,6 +75,12 @@ struct kvm_sdei_vcpu_state {
#define KVM_SDEI_CMD_GET_REGISTERED_EVENT_COUNT 4
#define KVM_SDEI_CMD_GET_REGISTERED_EVENT 5
#define KVM_SDEI_CMD_SET_REGISTERED_EVENT 6
+#define KVM_SDEI_CMD_GET_VCPU_EVENT_COUNT 7
+#define KVM_SDEI_CMD_GET_VCPU_EVENT 8
+#define KVM_SDEI_CMD_SET_VCPU_EVENT 9
+#define KVM_SDEI_CMD_GET_VCPU_STATE 10
+#define KVM_SDEI_CMD_SET_VCPU_STATE 11
+#define KVM_SDEI_CMD_INJECT_EVENT 12

struct kvm_sdei_cmd {
__u32 cmd;
@@ -85,6 +91,9 @@ struct kvm_sdei_cmd {
union {
struct kvm_sdei_exposed_event_state *exposed_event_state;
struct kvm_sdei_registered_event_state *registered_event_state;
+ struct kvm_sdei_vcpu_event_state *vcpu_event_state;
+ struct kvm_sdei_vcpu_state *vcpu_state;
+ __u64 num;
};
};

diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c
index ebfd504a1c08..3f532e1c4a95 100644
--- a/arch/arm64/kvm/arm.c
+++ b/arch/arm64/kvm/arm.c
@@ -1387,6 +1387,9 @@ long kvm_arch_vcpu_ioctl(struct file *filp,

return kvm_arm_vcpu_finalize(vcpu, what);
}
+ case KVM_ARM_SDEI_COMMAND: {
+ return kvm_sdei_vcpu_ioctl(vcpu, arg);
+ }
default:
r = -EINVAL;
}
diff --git a/arch/arm64/kvm/sdei.c b/arch/arm64/kvm/sdei.c
index d9cf494990a9..06895ac73c24 100644
--- a/arch/arm64/kvm/sdei.c
+++ b/arch/arm64/kvm/sdei.c
@@ -1567,6 +1567,305 @@ long kvm_sdei_vm_ioctl(struct kvm *kvm, unsigned long arg)
return ret;
}

+static long vcpu_ioctl_get_vcpu_event(struct kvm_vcpu *vcpu,
+ struct kvm_sdei_cmd *cmd)
+{
+ struct kvm_sdei_vcpu *vsdei = vcpu->arch.sdei;
+ struct kvm_sdei_vcpu_event *vcpu_event;
+ struct kvm_sdei_vcpu_event_state *state;
+ void __user *user_state = (void __user *)(cmd->vcpu_event_state);
+ unsigned int count, i;
+ long ret = 0;
+
+ if (!cmd->count)
+ return 0;
+
+ state = kcalloc(cmd->count, sizeof(*state), GFP_KERNEL_ACCOUNT);
+ if (!state)
+ return -ENOMEM;
+
+ i = 0;
+ count = cmd->count;
+ list_for_each_entry(vcpu_event, &vsdei->critical_events, link) {
+ state[i++] = vcpu_event->state;
+ if (!--count)
+ break;
+ }
+
+ if (count) {
+ list_for_each_entry(vcpu_event, &vsdei->normal_events, link) {
+ state[i++] = vcpu_event->state;
+ if (!--count)
+ break;
+ }
+ }
+
+ if (copy_to_user(user_state, state, sizeof(*state) * cmd->count))
+ ret = -EFAULT;
+
+ kfree(state);
+ return ret;
+}
+
+static long vcpu_ioctl_set_vcpu_event(struct kvm_vcpu *vcpu,
+ struct kvm_sdei_cmd *cmd)
+{
+ struct kvm *kvm = vcpu->kvm;
+ 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_event_state *state;
+ void __user *user_state = (void __user *)(cmd->vcpu_event_state);
+ unsigned int vcpu_event_count, i, j;
+ long ret = 0;
+
+ if (!cmd->count)
+ return 0;
+
+ state = kcalloc(cmd->count, sizeof(*state), GFP_KERNEL_ACCOUNT);
+ if (!state)
+ return -ENOMEM;
+
+ if (copy_from_user(state, user_state, sizeof(*state) * cmd->count)) {
+ ret = -EFAULT;
+ goto out;
+ }
+
+ vcpu_event_count = vsdei->critical_event_count +
+ vsdei->normal_event_count;
+ for (i = 0; i < cmd->count; i++) {
+ if (!kvm_sdei_is_supported(state[i].num)) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ /* Check if the event has been exposed */
+ exposed_event = find_exposed_event(kvm, state[i].num);
+ if (!exposed_event) {
+ ret = -ENOENT;
+ goto out;
+ }
+
+ /* Check if the event has been registered */
+ registered_event = find_registered_event(kvm, state[i].num);
+ if (!registered_event) {
+ ret = -ENOENT;
+ goto out;
+ }
+
+ /*
+ * Calculate the total count of the vcpu event instances.
+ * We needn't a new vcpu event instance if it is existing
+ * or a duplicated event.
+ */
+ vcpu_event = find_vcpu_event(vcpu, state[i].num);
+ if (vcpu_event)
+ continue;
+
+ for (j = 0; j < cmd->count; j++) {
+ if (j != i && state[j].num == state[i].num)
+ break;
+ }
+
+ if (j >= cmd->count || i < j)
+ vcpu_event_count++;
+ }
+
+ /*
+ * Check if the required count of vcpu event instances exceeds
+ * the limit.
+ */
+ if (vcpu_event_count > KVM_SDEI_MAX_EVENTS) {
+ ret = -ERANGE;
+ goto out;
+ }
+
+ for (i = 0; i < cmd->count; i++) {
+ /* The vcpu event might have been existing */
+ vcpu_event = find_vcpu_event(vcpu, state[i].num);
+ if (vcpu_event) {
+ vcpu_event->state.event_count += state[i].event_count;
+ continue;
+ }
+
+ vcpu_event = kzalloc(sizeof(*vcpu_event), GFP_KERNEL_ACCOUNT);
+ if (!vcpu_event) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ registered_event = find_registered_event(kvm, state[i].num);
+ exposed_event = registered_event->exposed_event;
+
+ vcpu_event->state = state[i];
+ vcpu_event->registered_event = registered_event;
+ vcpu_event->vcpu = vcpu;
+
+ registered_event->vcpu_event_count++;
+ if (kvm_sdei_is_critical(exposed_event->state.priority)) {
+ list_add_tail(&vcpu_event->link,
+ &vsdei->critical_events);
+ vsdei->critical_event_count++;
+ } else {
+ list_add_tail(&vcpu_event->link,
+ &vsdei->normal_events);
+ vsdei->normal_event_count++;
+ }
+ }
+
+out:
+ kfree(state);
+ return ret;
+}
+
+static long vcpu_ioctl_set_vcpu_state(struct kvm_vcpu *vcpu,
+ struct kvm_sdei_cmd *cmd)
+{
+ struct kvm_sdei_vcpu *vsdei = vcpu->arch.sdei;
+ struct kvm_sdei_vcpu_event *critical_vcpu_event = NULL;
+ struct kvm_sdei_vcpu_event *normal_vcpu_event = NULL;
+ struct kvm_sdei_vcpu_state *state;
+ void __user *user_state = (void __user *)(cmd->vcpu_state);
+ long ret = 0;
+
+ state = kzalloc(sizeof(*state), GFP_KERNEL_ACCOUNT);
+ if (!state)
+ return -ENOMEM;
+
+ if (copy_from_user(state, user_state, sizeof(*state))) {
+ ret = -EFAULT;
+ goto out;
+ }
+
+ if (kvm_sdei_is_supported(state->critical_num)) {
+ critical_vcpu_event = find_vcpu_event(vcpu,
+ state->critical_num);
+ if (!critical_vcpu_event) {
+ ret = -EINVAL;
+ goto out;
+ }
+ }
+
+ if (kvm_sdei_is_supported(state->normal_num)) {
+ normal_vcpu_event = find_vcpu_event(vcpu, state->normal_num);
+ if (!normal_vcpu_event) {
+ ret = -EINVAL;
+ goto out;
+ }
+ }
+
+ vsdei->state = *state;
+ vsdei->critical_event = critical_vcpu_event;
+ vsdei->normal_event = normal_vcpu_event;
+
+ /*
+ * To deliver the vCPU events if we don't have a valid handler
+ * running. Otherwise, the vCPU events should be delivered when
+ * the running handler is completed.
+ */
+ if (!vsdei->critical_event && !vsdei->normal_event &&
+ (vsdei->critical_event_count + vsdei->normal_event_count) > 0)
+ kvm_make_request(KVM_REQ_SDEI, vcpu);
+
+out:
+ kfree(state);
+ return ret;
+}
+
+static long vcpu_ioctl_inject_event(struct kvm_vcpu *vcpu,
+ struct kvm_sdei_cmd *cmd)
+{
+ struct kvm *kvm = vcpu->kvm;
+ struct kvm_sdei_vcpu *vsdei = vcpu->arch.sdei;
+ struct kvm_sdei_exposed_event *exposed_event;
+ struct kvm_sdei_registered_event *registered_event;
+ int index;
+
+ if (!kvm_sdei_is_supported(cmd->num))
+ return -EINVAL;
+
+ registered_event = find_registered_event(kvm, cmd->num);
+ if (!registered_event)
+ return -ENOENT;
+
+ exposed_event = registered_event->exposed_event;
+ index = kvm_sdei_vcpu_index(vcpu, exposed_event);
+ if (!kvm_sdei_is_registered(registered_event, index) ||
+ !kvm_sdei_is_enabled(registered_event, index) ||
+ kvm_sdei_is_unregister_pending(registered_event, index))
+ return -EPERM;
+
+ if (vsdei->state.masked)
+ return -EPERM;
+
+ return do_inject_event(vcpu, registered_event, false);
+}
+
+long kvm_sdei_vcpu_ioctl(struct kvm_vcpu *vcpu, unsigned long arg)
+{
+ struct kvm *kvm = vcpu->kvm;
+ struct kvm_sdei_kvm *ksdei = kvm->arch.sdei;
+ struct kvm_sdei_vcpu *vsdei = vcpu->arch.sdei;
+ struct kvm_sdei_cmd *cmd = NULL;
+ void __user *argp = (void __user *)arg;
+ long ret = 0;
+
+ if (!(ksdei && vsdei)) {
+ ret = -EPERM;
+ goto out;
+ }
+
+ cmd = kzalloc(sizeof(*cmd), GFP_KERNEL_ACCOUNT);
+ if (!cmd) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ if (copy_from_user(cmd, argp, sizeof(*cmd))) {
+ ret = -EFAULT;
+ goto out;
+ }
+
+ spin_lock(&ksdei->lock);
+ spin_lock(&vsdei->lock);
+
+ switch (cmd->cmd) {
+ case KVM_SDEI_CMD_GET_VCPU_EVENT_COUNT:
+ cmd->count = vsdei->critical_event_count +
+ vsdei->normal_event_count;
+ if (copy_to_user(argp, cmd, sizeof(*cmd)))
+ ret = -EFAULT;
+ break;
+ case KVM_SDEI_CMD_GET_VCPU_EVENT:
+ ret = vcpu_ioctl_get_vcpu_event(vcpu, cmd);
+ break;
+ case KVM_SDEI_CMD_SET_VCPU_EVENT:
+ ret = vcpu_ioctl_set_vcpu_event(vcpu, cmd);
+ break;
+ case KVM_SDEI_CMD_GET_VCPU_STATE:
+ if (copy_to_user(cmd->vcpu_state, &vsdei->state,
+ sizeof(vsdei->state)))
+ ret = -EFAULT;
+ break;
+ case KVM_SDEI_CMD_SET_VCPU_STATE:
+ ret = vcpu_ioctl_set_vcpu_state(vcpu, cmd);
+ break;
+ case KVM_SDEI_CMD_INJECT_EVENT:
+ ret = vcpu_ioctl_inject_event(vcpu, cmd);
+ break;
+ default:
+ ret = -EINVAL;
+ }
+
+ spin_unlock(&vsdei->lock);
+ spin_unlock(&ksdei->lock);
+
+out:
+ kfree(cmd);
+ return ret;
+}
+
void kvm_sdei_destroy_vcpu(struct kvm_vcpu *vcpu)
{
struct kvm *kvm = vcpu->kvm;
--
2.23.0