[PATCH v5 18/22] KVM: arm64: Support SDEI ioctl commands on VM

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


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

* KVM_SDEI_CMD_GET_VERSION
Retrieve the version of current implementation. It's different
from the version of the followed SDEI specification. This version
is used to indicates what functionalities documented in the SDEI
specification have been supported or not supported.

* KVM_SDEI_CMD_GET_EXPOSED_EVENT_COUNT
Return the total count of exposed events.

* KVM_SDEI_CMD_GET_EXPOSED_EVENT
* KVM_SDEI_CMD_SET_EXPOSED_EVENT
Get or set exposed event

* KVM_SDEI_CMD_GET_REGISTERED_EVENT_COUNT
Return the total count of registered events.

* KVM_SDEI_CMD_GET_REGISTERED_EVENT
* KVM_SDEI_CMD_SET_REGISTERED_EVENT
Get or set registered 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 | 20 ++
arch/arm64/kvm/arm.c | 3 +
arch/arm64/kvm/sdei.c | 302 +++++++++++++++++++
include/uapi/linux/kvm.h | 3 +
5 files changed, 329 insertions(+)

diff --git a/arch/arm64/include/asm/kvm_sdei.h b/arch/arm64/include/asm/kvm_sdei.h
index 2480ec0e9824..64f00cc79162 100644
--- a/arch/arm64/include/asm/kvm_sdei.h
+++ b/arch/arm64/include/asm/kvm_sdei.h
@@ -179,6 +179,7 @@ int kvm_sdei_inject_event(struct kvm_vcpu *vcpu,
unsigned long num, bool immediate);
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);
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 b14844230117..2bd6d11627bc 100644
--- a/arch/arm64/include/uapi/asm/kvm_sdei_state.h
+++ b/arch/arm64/include/uapi/asm/kvm_sdei_state.h
@@ -68,5 +68,25 @@ struct kvm_sdei_vcpu_state {
struct kvm_sdei_vcpu_regs_state normal_regs;
};

+#define KVM_SDEI_CMD_GET_VERSION 0
+#define KVM_SDEI_CMD_GET_EXPOSED_EVENT_COUNT 1
+#define KVM_SDEI_CMD_GET_EXPOSED_EVENT 2
+#define KVM_SDEI_CMD_SET_EXPOSED_EVENT 3
+#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
+
+struct kvm_sdei_cmd {
+ __u32 cmd;
+ union {
+ __u32 version;
+ __u32 count;
+ };
+ union {
+ struct kvm_sdei_exposed_event_state *exposed_event_state;
+ struct kvm_sdei_registered_event_state *registered_event_state;
+ };
+};
+
#endif /* !__ASSEMBLY__ */
#endif /* _UAPI__ASM_KVM_SDEI_STATE_H */
diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c
index 00c136a6e8df..ebfd504a1c08 100644
--- a/arch/arm64/kvm/arm.c
+++ b/arch/arm64/kvm/arm.c
@@ -1465,6 +1465,9 @@ long kvm_arch_vm_ioctl(struct file *filp,
return -EFAULT;
return kvm_vm_ioctl_mte_copy_tags(kvm, &copy_tags);
}
+ case KVM_ARM_SDEI_COMMAND: {
+ return kvm_sdei_vm_ioctl(kvm, arg);
+ }
default:
return -EINVAL;
}
diff --git a/arch/arm64/kvm/sdei.c b/arch/arm64/kvm/sdei.c
index 9f1959653318..d9cf494990a9 100644
--- a/arch/arm64/kvm/sdei.c
+++ b/arch/arm64/kvm/sdei.c
@@ -1265,6 +1265,308 @@ void kvm_sdei_create_vcpu(struct kvm_vcpu *vcpu)
vcpu->arch.sdei = vsdei;
}

+static long vm_ioctl_get_exposed_event(struct kvm *kvm,
+ struct kvm_sdei_cmd *cmd)
+{
+ struct kvm_sdei_kvm *ksdei = kvm->arch.sdei;
+ struct kvm_sdei_exposed_event *exposed_event;
+ struct kvm_sdei_exposed_event_state *state;
+ void __user *user_state = (void __user *)(cmd->exposed_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(exposed_event, &ksdei->exposed_events, link) {
+ state[i++] = exposed_event->state;
+ if (!--count)
+ break;
+ }
+
+ if (copy_to_user(user_state, state, sizeof(*state) * cmd->count))
+ ret = -EFAULT;
+
+ kfree(state);
+ return ret;
+}
+
+static long vm_ioctl_set_exposed_event(struct kvm *kvm,
+ struct kvm_sdei_cmd *cmd)
+{
+ struct kvm_sdei_kvm *ksdei = kvm->arch.sdei;
+ struct kvm_sdei_exposed_event *exposed_event;
+ struct kvm_sdei_exposed_event_state *state;
+ void __user *user_state = (void __user *)(cmd->exposed_event_state);
+ unsigned int i, j;
+ long ret = 0;
+
+ if (!cmd->count)
+ return 0;
+
+ if ((ksdei->exposed_event_count + cmd->count) > KVM_SDEI_MAX_EVENTS)
+ return -ERANGE;
+
+ 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;
+ }
+
+ for (i = 0; i < cmd->count; i++) {
+ if (!kvm_sdei_is_supported(state[i].num)) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ if (!kvm_sdei_is_shared(state[i].type) &&
+ !kvm_sdei_is_private(state[i].type)) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ if (!kvm_sdei_is_critical(state[i].priority) &&
+ !kvm_sdei_is_normal(state[i].priority)) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ /*
+ * Check if the event has been exposed. The notifier is
+ * allowed to be changed.
+ */
+ exposed_event = find_exposed_event(kvm, state[i].num);
+ if (exposed_event &&
+ (state[i].num != exposed_event->state.num ||
+ state[i].type != exposed_event->state.type ||
+ state[i].signaled != exposed_event->state.signaled ||
+ state[i].priority != exposed_event->state.priority)) {
+ ret = -EEXIST;
+ goto out;
+ }
+
+ /* Avoid the duplicated event */
+ for (j = 0; j < cmd->count; j++) {
+ if (i != j && state[i].num == state[j].num) {
+ ret = -EINVAL;
+ goto out;
+ }
+ }
+ }
+
+ for (i = 0; i < cmd->count; i++) {
+ exposed_event = find_exposed_event(kvm, state[i].num);
+ if (exposed_event) {
+ exposed_event->state = state[i];
+ continue;
+ }
+
+ exposed_event = kzalloc(sizeof(*exposed_event),
+ GFP_KERNEL_ACCOUNT);
+ if (!exposed_event) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ exposed_event->state = state[i];
+ exposed_event->kvm = kvm;
+
+ ksdei->exposed_event_count++;
+ list_add_tail(&exposed_event->link, &ksdei->exposed_events);
+ }
+
+out:
+ kfree(state);
+ return ret;
+}
+
+static long vm_ioctl_get_registered_event(struct kvm *kvm,
+ struct kvm_sdei_cmd *cmd)
+{
+ struct kvm_sdei_kvm *ksdei = kvm->arch.sdei;
+ struct kvm_sdei_registered_event *registered_event;
+ struct kvm_sdei_registered_event_state *state;
+ void __user *user_state = (void __user *)(cmd->registered_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(registered_event,
+ &ksdei->registered_events, link) {
+ state[i++] = registered_event->state;
+ if (!--count)
+ break;
+ }
+
+ if (copy_to_user(user_state, state, sizeof(*state) * cmd->count))
+ ret = -EFAULT;
+
+ kfree(state);
+ return ret;
+}
+
+static long vm_ioctl_set_registered_event(struct kvm *kvm,
+ struct kvm_sdei_cmd *cmd)
+{
+ struct kvm_sdei_kvm *ksdei = kvm->arch.sdei;
+ struct kvm_sdei_exposed_event *exposed_event;
+ struct kvm_sdei_registered_event *registered_event;
+ struct kvm_sdei_registered_event_state *state;
+ void __user *user_state = (void __user *)(cmd->registered_event_state);
+ unsigned int i, j;
+ long ret = 0;
+
+ if (!cmd->count)
+ return 0;
+
+ if ((ksdei->registered_event_count + cmd->count) > KVM_SDEI_MAX_EVENTS)
+ return -ERANGE;
+
+ 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;
+ }
+
+ for (i = 0; i < cmd->count; i++) {
+ if (!kvm_sdei_is_supported(state[i].num)) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ if (state[i].route_mode != SDEI_EVENT_REGISTER_RM_ANY &&
+ state[i].route_mode != SDEI_EVENT_REGISTER_RM_PE) {
+ 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 = -EEXIST;
+ goto out;
+ }
+
+ /* Avoid the duplicated event */
+ for (j = 0; j < cmd->count; j++) {
+ if (i != j && state[i].num == state[j].num) {
+ ret = -EINVAL;
+ goto out;
+ }
+ }
+ }
+
+ for (i = 0; i < cmd->count; i++) {
+ registered_event = kzalloc(sizeof(*registered_event),
+ GFP_KERNEL_ACCOUNT);
+ if (!registered_event) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ exposed_event = find_exposed_event(kvm, state[i].num);
+ registered_event->state = state[i];
+ registered_event->kvm = kvm;
+ registered_event->exposed_event = exposed_event;
+
+ ksdei->registered_event_count++;
+ exposed_event->registered_event_count++;
+ list_add_tail(&registered_event->link,
+ &ksdei->registered_events);
+ }
+
+out:
+ kfree(state);
+ return ret;
+}
+
+long kvm_sdei_vm_ioctl(struct kvm *kvm, unsigned long arg)
+{
+ struct kvm_sdei_kvm *ksdei = kvm->arch.sdei;
+ struct kvm_sdei_cmd *cmd;
+ void __user *argp = (void __user *)arg;
+ long ret = 0;
+
+ if (!ksdei)
+ return -EPERM;
+
+ cmd = kzalloc(sizeof(*cmd), GFP_KERNEL_ACCOUNT);
+ if (!cmd)
+ return -ENOMEM;
+
+ if (copy_from_user(cmd, argp, sizeof(*cmd))) {
+ ret = -EFAULT;
+ goto out;
+ }
+
+ spin_lock(&ksdei->lock);
+
+ switch (cmd->cmd) {
+ case KVM_SDEI_CMD_GET_VERSION:
+ cmd->version = (1 << 16); /* v1.0.0 */
+ if (copy_to_user(argp, cmd, sizeof(*cmd)))
+ ret = -EFAULT;
+ break;
+ case KVM_SDEI_CMD_GET_EXPOSED_EVENT_COUNT:
+ cmd->count = ksdei->exposed_event_count;
+ if (copy_to_user(argp, cmd, sizeof(*cmd)))
+ ret = -EFAULT;
+ break;
+ case KVM_SDEI_CMD_GET_EXPOSED_EVENT:
+ ret = vm_ioctl_get_exposed_event(kvm, cmd);
+ break;
+ case KVM_SDEI_CMD_SET_EXPOSED_EVENT:
+ ret = vm_ioctl_set_exposed_event(kvm, cmd);
+ break;
+ case KVM_SDEI_CMD_GET_REGISTERED_EVENT_COUNT:
+ cmd->count = ksdei->registered_event_count;
+ if (copy_to_user(argp, cmd, sizeof(*cmd)))
+ ret = -EFAULT;
+ break;
+ case KVM_SDEI_CMD_GET_REGISTERED_EVENT:
+ ret = vm_ioctl_get_registered_event(kvm, cmd);
+ break;
+ case KVM_SDEI_CMD_SET_REGISTERED_EVENT:
+ ret = vm_ioctl_set_registered_event(kvm, cmd);
+ break;
+ default:
+ ret = -EINVAL;
+ }
+
+ spin_unlock(&ksdei->lock);
+out:
+
+ kfree(cmd);
+ return ret;
+}
+
void kvm_sdei_destroy_vcpu(struct kvm_vcpu *vcpu)
{
struct kvm *kvm = vcpu->kvm;
diff --git a/include/uapi/linux/kvm.h b/include/uapi/linux/kvm.h
index 507ee1f2aa96..2d11c909ec42 100644
--- a/include/uapi/linux/kvm.h
+++ b/include/uapi/linux/kvm.h
@@ -2049,4 +2049,7 @@ struct kvm_stats_desc {
/* Available with KVM_CAP_XSAVE2 */
#define KVM_GET_XSAVE2 _IOR(KVMIO, 0xcf, struct kvm_xsave)

+/* Available with KVM_CAP_ARM_SDEI */
+#define KVM_ARM_SDEI_COMMAND _IOWR(KVMIO, 0xd0, struct kvm_sdei_cmd)
+
#endif /* __LINUX_KVM_H */
--
2.23.0