@@ -1591,4 +1591,9 @@ void kvm_set_vm_id_reg(struct kvm *kvm, u32 reg, u64 val);
#define kvm_has_s1poe(k) \
(kvm_has_feat((k), ID_AA64MMFR3_EL1, S1POE, IMP))
+#ifndef CONFIG_KVM_ARM
+#define __KVM_HAVE_ARCH_VM_FREE
+void kvm_arch_free_vm(struct kvm *kvm);
+#endif
+
#endif /* __ARM64_KVM_HOST_H__ */
@@ -13,6 +13,12 @@
#include <asm/kvm_mmu.h>
#include <linux/perf_event.h>
+#include <linux/gunyah_rsc_mgr.h>
+#include <linux/gunyah.h>
+
+#undef pr_fmt
+#define pr_fmt(fmt) "gunyah: " fmt
+
static enum kvm_mode kvm_mode = KVM_MODE_DEFAULT;
enum kvm_mode kvm_get_mode(void)
@@ -338,12 +344,6 @@ void kvm_arch_create_vm_debugfs(struct kvm *kvm)
{
}
-void kvm_arch_destroy_vm(struct kvm *kvm)
-{
- kvm_destroy_vcpus(kvm);
- return;
-}
-
long kvm_arch_dev_ioctl(struct file *filp,
unsigned int ioctl, unsigned long arg)
{
@@ -788,7 +788,189 @@ int kvm_cpu_has_pending_timer(struct kvm_vcpu *vcpu)
return -EINVAL;
}
+static int gunyah_vm_rm_notification_status(struct gunyah_vm *ghvm, void *data)
+{
+ struct gunyah_rm_vm_status_payload *payload = data;
+
+ if (le16_to_cpu(payload->vmid) != ghvm->vmid)
+ return NOTIFY_OK;
+
+ /* All other state transitions are synchronous to a corresponding RM call */
+ if (payload->vm_status == GUNYAH_RM_VM_STATUS_RESET) {
+ down_write(&ghvm->status_lock);
+ ghvm->vm_status = payload->vm_status;
+ up_write(&ghvm->status_lock);
+ wake_up(&ghvm->vm_status_wait);
+ }
+
+ return NOTIFY_DONE;
+}
+
+static int gunyah_vm_rm_notification_exited(struct gunyah_vm *ghvm, void *data)
+{
+ struct gunyah_rm_vm_exited_payload *payload = data;
+
+ if (le16_to_cpu(payload->vmid) != ghvm->vmid)
+ return NOTIFY_OK;
+
+ down_write(&ghvm->status_lock);
+ ghvm->vm_status = GUNYAH_RM_VM_STATUS_EXITED;
+ up_write(&ghvm->status_lock);
+ wake_up(&ghvm->vm_status_wait);
+
+ return NOTIFY_DONE;
+}
+
+static int gunyah_vm_rm_notification(struct notifier_block *nb,
+ unsigned long action, void *data)
+{
+ struct gunyah_vm *ghvm = container_of(nb, struct gunyah_vm, nb);
+
+ switch (action) {
+ case GUNYAH_RM_NOTIFICATION_VM_STATUS:
+ return gunyah_vm_rm_notification_status(ghvm, data);
+ case GUNYAH_RM_NOTIFICATION_VM_EXITED:
+ return gunyah_vm_rm_notification_exited(ghvm, data);
+ default:
+ return NOTIFY_OK;
+ }
+}
+
+static void gunyah_vm_stop(struct gunyah_vm *ghvm)
+{
+ int ret;
+
+ if (ghvm->vm_status == GUNYAH_RM_VM_STATUS_RUNNING) {
+ ret = gunyah_rm_vm_stop(ghvm->rm, ghvm->vmid);
+ if (ret)
+ pr_warn("Failed to stop VM: %d\n", ret);
+ }
+
+ wait_event(ghvm->vm_status_wait,
+ ghvm->vm_status != GUNYAH_RM_VM_STATUS_RUNNING);
+}
+
+static int gunyah_vm_start(struct gunyah_vm *ghvm)
+{
+ int ret;
+
+ down_write(&ghvm->status_lock);
+ if (ghvm->vm_status != GUNYAH_RM_VM_STATUS_NO_STATE) {
+ up_write(&ghvm->status_lock);
+ return 0;
+ }
+
+ ghvm->nb.notifier_call = gunyah_vm_rm_notification;
+ ret = gunyah_rm_notifier_register(ghvm->rm, &ghvm->nb);
+ if (ret)
+ goto err;
+
+ ret = gunyah_rm_alloc_vmid(ghvm->rm, 0);
+ if (ret < 0) {
+ gunyah_rm_notifier_unregister(ghvm->rm, &ghvm->nb);
+ goto err;
+ }
+ ghvm->vmid = ret;
+ ghvm->vm_status = GUNYAH_RM_VM_STATUS_LOAD;
+
+ ret = gunyah_rm_vm_configure(ghvm->rm, ghvm->vmid, ghvm->auth, 0, 0, 0, 0, 0);
+ if (ret) {
+ pr_warn("Failed to configure VM: %d\n", ret);
+ goto err;
+ }
+
+ ret = gunyah_rm_vm_init(ghvm->rm, ghvm->vmid);
+ if (ret) {
+ ghvm->vm_status = GUNYAH_RM_VM_STATUS_INIT_FAILED;
+ pr_warn("Failed to initialize VM: %d\n", ret);
+ goto err;
+ }
+ ghvm->vm_status = GUNYAH_RM_VM_STATUS_READY;
+
+ ret = gunyah_rm_vm_start(ghvm->rm, ghvm->vmid);
+ if (ret) {
+ pr_warn("Failed to start VM: %d\n", ret);
+ goto err;
+ }
+
+ ghvm->vm_status = GUNYAH_RM_VM_STATUS_RUNNING;
+ up_write(&ghvm->status_lock);
+ return 0;
+err:
+ up_write(&ghvm->status_lock);
+ return ret;
+}
+
+static struct gunyah_vm *gunyah_vm_alloc(struct gunyah_rm *rm)
+{
+ struct gunyah_vm *ghvm;
+
+ ghvm = kzalloc(sizeof(*ghvm), GFP_KERNEL);
+ if (!ghvm)
+ return ERR_PTR(-ENOMEM);
+
+ ghvm->vmid = GUNYAH_VMID_INVAL;
+ ghvm->rm = rm;
+
+ init_rwsem(&ghvm->status_lock);
+ init_waitqueue_head(&ghvm->vm_status_wait);
+ ghvm->vm_status = GUNYAH_RM_VM_STATUS_NO_STATE;
+
+ return ghvm;
+}
+
+static void gunyah_destroy_vm(struct gunyah_vm *ghvm)
+{
+ int ret;
+
+ /**
+ * We might race with a VM exit notification, but that's ok:
+ * gh_rm_vm_stop() will just return right away.
+ */
+ if (ghvm->vm_status == GUNYAH_RM_VM_STATUS_RUNNING)
+ gunyah_vm_stop(ghvm);
+
+ if (ghvm->vm_status == GUNYAH_RM_VM_STATUS_EXITED ||
+ ghvm->vm_status == GUNYAH_RM_VM_STATUS_READY ||
+ ghvm->vm_status == GUNYAH_RM_VM_STATUS_INIT_FAILED) {
+ ret = gunyah_rm_vm_reset(ghvm->rm, ghvm->vmid);
+ if (!ret)
+ wait_event(ghvm->vm_status_wait,
+ ghvm->vm_status == GUNYAH_RM_VM_STATUS_RESET);
+ else
+ pr_warn("Failed to reset the vm: %d\n", ret);
+ }
+
+ if (ghvm->vm_status > GUNYAH_RM_VM_STATUS_NO_STATE) {
+ gunyah_rm_notifier_unregister(ghvm->rm, &ghvm->nb);
+ ret = gunyah_rm_dealloc_vmid(ghvm->rm, ghvm->vmid);
+ if (ret)
+ pr_warn("Failed to deallocate vmid: %d\n", ret);
+ }
+}
+
struct kvm *kvm_arch_alloc_vm(void)
{
- return NULL;
+ struct gunyah_vm *ghvm;
+
+ ghvm = gunyah_vm_alloc(gunyah_rm);
+ if (IS_ERR(ghvm))
+ return NULL;
+
+ return &ghvm->kvm;
+}
+
+void kvm_arch_destroy_vm(struct kvm *kvm)
+{
+ struct gunyah_vm *ghvm = kvm_to_gunyah(kvm);
+
+ kvm_destroy_vcpus(kvm);
+ gunyah_destroy_vm(ghvm);
+}
+
+void kvm_arch_free_vm(struct kvm *kvm)
+{
+ struct gunyah_vm *ghvm = kvm_to_gunyah(kvm);
+
+ kfree(ghvm);
}
@@ -3,8 +3,8 @@
* Copyright (c) 2022-2024 Qualcomm Innovation Center, Inc. All rights reserved.
*/
+#include <linux/slab.h>
#include <linux/error-injection.h>
-
#include <linux/gunyah_rsc_mgr.h>
/* Message IDs: VM Management */
@@ -11,6 +11,12 @@
#include <linux/interrupt.h>
#include <linux/limits.h>
#include <linux/types.h>
+#include <linux/kvm_host.h>
+
+#include <linux/gunyah_rsc_mgr.h>
+
+#define kvm_to_gunyah(kvm_ptr) \
+ container_of(kvm_ptr, struct gunyah_vm, kvm)
/* Matches resource manager's resource types for VM_GET_HYP_RESOURCES RPC */
enum gunyah_resource_type {
@@ -31,6 +37,32 @@ struct gunyah_resource {
unsigned int irq;
};
+/**
+ * struct gunyah_vm - Main representation of a Gunyah Virtual machine
+ memory shared with the guest.
+ * @vmid: Gunyah's VMID for this virtual machine
+ * @kvm: kvm instance for this VM
+ * @rm: Pointer to the resource manager struct to make RM calls
+ * @nb: Notifier block for RM notifications
+ * @vm_status: Current state of the VM, as last reported by RM
+ * @vm_status_wait: Wait queue for status @vm_status changes
+ * @status_lock: Serializing state transitions
+ * @auth: Authentication mechanism to be used by resource manager when
+ * launching the VM
+ */
+struct gunyah_vm {
+ u16 vmid;
+ struct kvm kvm;
+ struct gunyah_rm *rm;
+
+ struct notifier_block nb;
+ enum gunyah_rm_vm_status vm_status;
+ wait_queue_head_t vm_status_wait;
+ struct rw_semaphore status_lock;
+
+ enum gunyah_rm_vm_auth_mechanism auth;
+};
+
/******************************************************************************/
/* Common arch-independent definitions for Gunyah hypercalls */
#define GUNYAH_CAPID_INVAL U64_MAX
This patch hooks up Gunyah virtual machine lifecycle management with the KVM backend by implementing the kvm_arch_alloc_vm(), kvm_arch_destroy_vm(), and kvm_arch_free_vm() hooks. The Gunyah VM management logic—VMID allocation, configuration, initialization, start/stop, teardown, and notifier handling—is based on the implementation introduced in [1], authored by Elliot Berman and Prakruthi Deepak Heragu. The original code added a special ioctl interface to support userspace initialization of guest VMs. This patch reuses the same logic, but ported to KVM, allowing to use KVM's ioctl interface to create Gunyah-based guests. [1] Commit: 532788ce71c9 ("gunyah: vm_mgr: Add VM start/stop") Link: https://lore.kernel.org/lkml/20240222-gunyah-v17-10-1e9da6763d38@quicinc.com/ Co-developed-by: Elliot Berman <quic_eberman@quicinc.com> Co-developed-by: Prakruthi Deepak Heragu <quic_pheragu@quicinc.com> Signed-off-by: Karim Manaouil <karim.manaouil@linaro.org> --- arch/arm64/include/asm/kvm_host.h | 5 + arch/arm64/kvm/gunyah.c | 196 ++++++++++++++++++++++++++++-- drivers/virt/gunyah/rsc_mgr_rpc.c | 2 +- include/linux/gunyah.h | 32 +++++ 4 files changed, 227 insertions(+), 8 deletions(-)