Gunyah is a Type-1 hypervisor independent of any
high-level OS kernel, and runs in a higher CPU privilege level. It does
not depend on any lower-privileged OS kernel/code for its core
functionality. This increases its security and can support a much smaller
trusted computing base than a Type-2 hypervisor.
Gunyah is an open source hypervisor. The source repo is available at
https://github.com/quic/gunyah-hypervisor.
The diagram below shows the architecture.
::
VM A VM B
+-----+ +-----+ | +-----+ +-----+ +-----+
| | | | | | | | | | |
EL0 | APP | | APP | | | APP | | APP | | APP |
| | | | | | | | | | |
+-----+ +-----+ | +-----+ +-----+ +-----+
---------------------|-------------------------
+--------------+ | +----------------------+
| | | | |
EL1 | Linux Kernel | | |Linux kernel/Other OS | ...
| | | | |
+--------------+ | +----------------------+
--------hvc/smc------|------hvc/smc------------
+----------------------------------------+
| |
EL2 | Gunyah Hypervisor |
| |
+----------------------------------------+
Gunyah provides these following features.
- Threads and Scheduling: The scheduler schedules virtual CPUs (VCPUs) on
physical CPUs and enables time-sharing of the CPUs.
- Memory Management: Gunyah tracks memory ownership and use of all memory
under its control. Memory partitioning between VMs is a fundamental
security feature.
- Interrupt Virtualization: All interrupts are handled in the hypervisor
and routed to the assigned VM.
- Inter-VM Communication: There are several different mechanisms provided
for communicating between VMs.
- Device Virtualization: Para-virtualization of devices is supported using
inter-VM communication. Low level system features and devices such as
interrupt controllers are supported with emulation where required.
This series adds the basic framework for detecting that Linux is running
under Gunyah as a virtual machine, communication with the Gunyah Resource
Manager, and a virtual machine manager capable of launching virtual machines.
Patches 21-28 are presently intended to be submitted separately and are included
for initial RFC. These patches introudce "VM function" framework to expose further
interfaces to interact with Gunyah Virtual Machines. With all 28 patches, it is
possible to create a Gunyah VM supporting virtio.
Changes in v8:
- Treat VM manager as a library of RM
- Add patches 21-28 as RFC to support proxy-scheduled vCPUs and necessary bits to support virtio
from Gunyah userspace
Changes in v7: https://lore.kernel.org/all/[email protected]/
- Refactor to remove gunyah RM bus
- Refactor allow multiple RM device instances
- Bump UAPI to start at 0x0
- Refactor QCOM SCM's platform hooks to allow CONFIG_QCOM_SCM=Y/CONFIG_GUNYAH=M combinations
Changes in v6: https://lore.kernel.org/all/[email protected]/
- *Replace gunyah-console with gunyah VM Manager*
- Move include/asm-generic/gunyah.h into include/linux/gunyah.h
- s/gunyah_msgq/gh_msgq/
- Minor tweaks and documentation tidying based on comments from Jiri, Greg, Arnd, Dmitry, and Bagas.
Changes in v5: https://lore.kernel.org/all/[email protected]/
- Dropped sysfs nodes
- Switch from aux bus to Gunyah RM bus for the subdevices
- Cleaning up RM console
Changes in v4: https://lore.kernel.org/all/[email protected]/
- Tidied up documentation throughout based on questions/feedback received
- Switched message queue implementation to use mailboxes
- Renamed "gunyah_device" as "gunyah_resource"
Changes in v3: https://lore.kernel.org/all/[email protected]/
- /Maintained/Supported/ in MAINTAINERS
- Tidied up documentation throughout based on questions/feedback received
- Moved hypercalls into arch/arm64/gunyah/; following hyper-v's implementation
- Drop opaque typedefs
- Move sysfs nodes under /sys/hypervisor/gunyah/
- Moved Gunyah console driver to drivers/tty/
- Reworked gunyah_device design to drop the Gunyah bus.
Changes in v2: https://lore.kernel.org/all/[email protected]/
- DT bindings clean up
- Switch hypercalls to follow SMCCC
v1: https://lore.kernel.org/all/[email protected]/
Elliot Berman (28):
docs: gunyah: Introduce Gunyah Hypervisor
dt-bindings: Add binding for gunyah hypervisor
gunyah: Common types and error codes for Gunyah hypercalls
arm64: smccc: Include alternative-macros.h
virt: gunyah: Add hypercalls to identify Gunyah
virt: gunyah: Identify hypervisor version
mailbox: Allow direct registration to a channel
virt: gunyah: msgq: Add hypercalls to send and receive messages
mailbox: Add Gunyah message queue mailbox
gunyah: rsc_mgr: Add resource manager RPC core
gunyah: rsc_mgr: Add VM lifecycle RPC
gunyah: vm_mgr: Introduce basic VM Manager
gunyah: rsc_mgr: Add RPC for sharing memory
gunyah: vm_mgr: Add/remove user memory regions
gunyah: vm_mgr: Add ioctls to support basic non-proxy VM boot
samples: Add sample userspace Gunyah VM Manager
gunyah: rsc_mgr: Add platform ops on mem_lend/mem_reclaim
firmware: qcom_scm: Use fixed width src vm bitmap
firmware: qcom_scm: Register Gunyah platform ops
docs: gunyah: Document Gunyah VM Manager
virt: gunyah: Translate gh_rm_hyp_resource into gunyah_resource
gunyah: vm_mgr: Add framework to add VM Functions
virt: gunyah: Add resource tickets
virt: gunyah: Add IO handlers
virt: gunyah: Add proxy-scheduled vCPUs
virt: gunyah: Add hypercalls for sending doorbell
virt: gunyah: Add irqfd interface
virt: gunyah: Add ioeventfd
.../bindings/firmware/gunyah-hypervisor.yaml | 82 ++
.../userspace-api/ioctl/ioctl-number.rst | 1 +
Documentation/virt/gunyah/index.rst | 115 +++
Documentation/virt/gunyah/message-queue.rst | 64 ++
Documentation/virt/gunyah/vm-manager.rst | 187 ++++
Documentation/virt/index.rst | 1 +
MAINTAINERS | 13 +
arch/arm64/Kbuild | 1 +
arch/arm64/gunyah/Makefile | 1 +
arch/arm64/gunyah/gunyah_hypercall.c | 157 ++++
arch/arm64/include/asm/gunyah.h | 23 +
drivers/firmware/Kconfig | 2 +
drivers/firmware/qcom_scm.c | 107 ++-
drivers/mailbox/Kconfig | 10 +
drivers/mailbox/Makefile | 2 +
drivers/mailbox/gunyah-msgq.c | 229 +++++
drivers/mailbox/mailbox.c | 96 ++-
drivers/mailbox/omap-mailbox.c | 18 +-
drivers/mailbox/pcc.c | 18 +-
drivers/misc/fastrpc.c | 6 +-
drivers/net/wireless/ath/ath10k/qmi.c | 4 +-
drivers/remoteproc/qcom_q6v5_mss.c | 8 +-
drivers/soc/qcom/rmtfs_mem.c | 2 +-
drivers/virt/Kconfig | 1 +
drivers/virt/Makefile | 1 +
drivers/virt/gunyah/Kconfig | 73 ++
drivers/virt/gunyah/Makefile | 10 +
drivers/virt/gunyah/gunyah.c | 46 +
drivers/virt/gunyah/gunyah_ioeventfd.c | 109 +++
drivers/virt/gunyah/gunyah_irqfd.c | 180 ++++
drivers/virt/gunyah/gunyah_platform_hooks.c | 63 ++
drivers/virt/gunyah/gunyah_vcpu.c | 350 ++++++++
drivers/virt/gunyah/rsc_mgr.c | 795 ++++++++++++++++++
drivers/virt/gunyah/rsc_mgr.h | 124 +++
drivers/virt/gunyah/rsc_mgr_rpc.c | 428 ++++++++++
drivers/virt/gunyah/vm_mgr.c | 594 +++++++++++++
drivers/virt/gunyah/vm_mgr.h | 76 ++
drivers/virt/gunyah/vm_mgr_mm.c | 245 ++++++
include/linux/arm-smccc.h | 1 +
include/linux/gunyah.h | 186 ++++
include/linux/gunyah_rsc_mgr.h | 136 +++
include/linux/gunyah_vm_mgr.h | 107 +++
include/linux/mailbox_client.h | 1 +
include/linux/qcom_scm.h | 2 +-
include/uapi/linux/gunyah.h | 114 +++
samples/Kconfig | 10 +
samples/Makefile | 1 +
samples/gunyah/.gitignore | 2 +
samples/gunyah/Makefile | 6 +
samples/gunyah/gunyah_vmm.c | 270 ++++++
samples/gunyah/sample_vm.dts | 69 ++
51 files changed, 5075 insertions(+), 72 deletions(-)
create mode 100644 Documentation/devicetree/bindings/firmware/gunyah-hypervisor.yaml
create mode 100644 Documentation/virt/gunyah/index.rst
create mode 100644 Documentation/virt/gunyah/message-queue.rst
create mode 100644 Documentation/virt/gunyah/vm-manager.rst
create mode 100644 arch/arm64/gunyah/Makefile
create mode 100644 arch/arm64/gunyah/gunyah_hypercall.c
create mode 100644 arch/arm64/include/asm/gunyah.h
create mode 100644 drivers/mailbox/gunyah-msgq.c
create mode 100644 drivers/virt/gunyah/Kconfig
create mode 100644 drivers/virt/gunyah/Makefile
create mode 100644 drivers/virt/gunyah/gunyah.c
create mode 100644 drivers/virt/gunyah/gunyah_ioeventfd.c
create mode 100644 drivers/virt/gunyah/gunyah_irqfd.c
create mode 100644 drivers/virt/gunyah/gunyah_platform_hooks.c
create mode 100644 drivers/virt/gunyah/gunyah_vcpu.c
create mode 100644 drivers/virt/gunyah/rsc_mgr.c
create mode 100644 drivers/virt/gunyah/rsc_mgr.h
create mode 100644 drivers/virt/gunyah/rsc_mgr_rpc.c
create mode 100644 drivers/virt/gunyah/vm_mgr.c
create mode 100644 drivers/virt/gunyah/vm_mgr.h
create mode 100644 drivers/virt/gunyah/vm_mgr_mm.c
create mode 100644 include/linux/gunyah.h
create mode 100644 include/linux/gunyah_rsc_mgr.h
create mode 100644 include/linux/gunyah_vm_mgr.h
create mode 100644 include/uapi/linux/gunyah.h
create mode 100644 samples/gunyah/.gitignore
create mode 100644 samples/gunyah/Makefile
create mode 100644 samples/gunyah/gunyah_vmm.c
create mode 100644 samples/gunyah/sample_vm.dts
base-commit: 830b3c68c1fb1e9176028d02ef86f3cf76aa2476
--
2.25.1
Add remaining ioctls to support non-proxy VM boot:
- Gunyah Resource Manager uses the VM's devicetree to configure the
virtual machine. The location of the devicetree in the guest's
virtual memory can be declared via the SET_DTB_CONFIG ioctl.
- Trigger start of the virtual machine with VM_START ioctl.
Co-developed-by: Prakruthi Deepak Heragu <[email protected]>
Signed-off-by: Prakruthi Deepak Heragu <[email protected]>
Signed-off-by: Elliot Berman <[email protected]>
---
drivers/virt/gunyah/vm_mgr.c | 92 ++++++++++++++++++++++++++++++++-
drivers/virt/gunyah/vm_mgr.h | 9 ++++
drivers/virt/gunyah/vm_mgr_mm.c | 24 +++++++++
include/uapi/linux/gunyah.h | 8 +++
4 files changed, 132 insertions(+), 1 deletion(-)
diff --git a/drivers/virt/gunyah/vm_mgr.c b/drivers/virt/gunyah/vm_mgr.c
index a7220ad41669..a96cb7f73754 100644
--- a/drivers/virt/gunyah/vm_mgr.c
+++ b/drivers/virt/gunyah/vm_mgr.c
@@ -8,7 +8,7 @@
#include <linux/anon_inodes.h>
#include <linux/file.h>
#include <linux/gunyah_rsc_mgr.h>
-#include <linux/miscdevice.h>
+#include <linux/mm.h>
#include <linux/module.h>
#include <uapi/linux/gunyah.h>
@@ -35,10 +35,79 @@ static __must_check struct gunyah_vm *gunyah_vm_alloc(struct gh_rm_rpc *rm)
mutex_init(&ghvm->mm_lock);
INIT_LIST_HEAD(&ghvm->memory_mappings);
+ init_rwsem(&ghvm->status_lock);
return ghvm;
}
+static int gh_vm_start(struct gunyah_vm *ghvm)
+{
+ struct gunyah_vm_memory_mapping *mapping;
+ u64 dtb_offset;
+ u32 mem_handle;
+ int ret;
+
+ down_write(&ghvm->status_lock);
+ if (ghvm->vm_status != GH_RM_VM_STATUS_NO_STATE) {
+ up_write(&ghvm->status_lock);
+ return 0;
+ }
+
+ mapping = gh_vm_mem_mapping_find_mapping(ghvm,
+ ghvm->dtb_config.gpa, ghvm->dtb_config.size);
+ if (!mapping) {
+ pr_warn("Failed to find the memory_handle for DTB\n");
+ ret = -EINVAL;
+ goto err;
+ }
+
+ mem_handle = mapping->parcel.mem_handle;
+ dtb_offset = ghvm->dtb_config.gpa - mapping->guest_phys_addr;
+
+ ret = gh_rm_vm_configure(ghvm->rm, ghvm->vmid, ghvm->auth, mem_handle,
+ 0, 0, dtb_offset, ghvm->dtb_config.size);
+ if (ret) {
+ pr_warn("Failed to configure VM: %d\n", ret);
+ goto err;
+ }
+
+ ret = gh_rm_vm_init(ghvm->rm, ghvm->vmid);
+ if (ret) {
+ pr_warn("Failed to initialize VM: %d\n", ret);
+ goto err;
+ }
+
+ ret = gh_rm_vm_start(ghvm->rm, ghvm->vmid);
+ if (ret) {
+ pr_warn("Failed to start VM: %d\n", ret);
+ goto err;
+ }
+
+ ghvm->vm_status = GH_RM_VM_STATUS_READY;
+
+ up_write(&ghvm->status_lock);
+ return ret;
+err:
+ ghvm->vm_status = GH_RM_VM_STATUS_INIT_FAILED;
+ up_write(&ghvm->status_lock);
+ return ret;
+}
+
+static void gh_vm_stop(struct gunyah_vm *ghvm)
+{
+ int ret;
+
+ down_write(&ghvm->status_lock);
+ if (ghvm->vm_status == GH_RM_VM_STATUS_READY) {
+ ret = gh_rm_vm_stop(ghvm->rm, ghvm->vmid);
+ if (ret)
+ pr_warn("Failed to stop VM: %d\n", ret);
+ }
+
+ ghvm->vm_status = GH_RM_VM_STATUS_EXITED;
+ up_write(&ghvm->status_lock);
+}
+
static long gh_vm_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
struct gunyah_vm *ghvm = filp->private_data;
@@ -82,6 +151,25 @@ static long gh_vm_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
}
break;
}
+ case GH_VM_SET_DTB_CONFIG: {
+ struct gh_vm_dtb_config dtb_config;
+
+ r = -EFAULT;
+ if (copy_from_user(&dtb_config, argp, sizeof(dtb_config)))
+ break;
+
+ dtb_config.size = PAGE_ALIGN(dtb_config.size);
+ ghvm->dtb_config = dtb_config;
+
+ r = 0;
+ break;
+ }
+ case GH_VM_START: {
+ r = gh_vm_start(ghvm);
+ if (r)
+ r = -EINVAL;
+ break;
+ }
default:
r = -ENOTTY;
break;
@@ -95,6 +183,8 @@ static int gh_vm_release(struct inode *inode, struct file *filp)
struct gunyah_vm *ghvm = filp->private_data;
struct gunyah_vm_memory_mapping *mapping, *tmp;
+ gh_vm_stop(ghvm);
+
list_for_each_entry_safe(mapping, tmp, &ghvm->memory_mappings, list) {
gh_vm_mem_mapping_reclaim(ghvm, mapping);
}
diff --git a/drivers/virt/gunyah/vm_mgr.h b/drivers/virt/gunyah/vm_mgr.h
index 955856c1310b..8dcba9f14b5c 100644
--- a/drivers/virt/gunyah/vm_mgr.h
+++ b/drivers/virt/gunyah/vm_mgr.h
@@ -10,6 +10,7 @@
#include <linux/list.h>
#include <linux/miscdevice.h>
#include <linux/mutex.h>
+#include <linux/rwsem.h>
#include <uapi/linux/gunyah.h>
@@ -40,6 +41,12 @@ struct gunyah_vm {
u16 vmid;
struct gh_rm_rpc *rm;
+ enum gh_rm_vm_auth_mechanism auth;
+ struct gh_vm_dtb_config dtb_config;
+
+ enum gh_rm_vm_status vm_status;
+ struct rw_semaphore status_lock;
+
struct mutex mm_lock;
struct list_head memory_mappings;
};
@@ -48,5 +55,7 @@ struct gunyah_vm_memory_mapping *gh_vm_mem_mapping_alloc(struct gunyah_vm *ghvm,
struct gh_userspace_memory_region *region);
void gh_vm_mem_mapping_reclaim(struct gunyah_vm *ghvm, struct gunyah_vm_memory_mapping *mapping);
struct gunyah_vm_memory_mapping *gh_vm_mem_mapping_find(struct gunyah_vm *ghvm, u32 label);
+struct gunyah_vm_memory_mapping *gh_vm_mem_mapping_find_mapping(struct gunyah_vm *ghvm,
+ u64 gpa, u32 size);
#endif
diff --git a/drivers/virt/gunyah/vm_mgr_mm.c b/drivers/virt/gunyah/vm_mgr_mm.c
index 800d35da14ba..82d8e567b1a8 100644
--- a/drivers/virt/gunyah/vm_mgr_mm.c
+++ b/drivers/virt/gunyah/vm_mgr_mm.c
@@ -53,6 +53,30 @@ void gh_vm_mem_mapping_reclaim(struct gunyah_vm *ghvm, struct gunyah_vm_memory_m
mutex_unlock(&ghvm->mm_lock);
}
+struct gunyah_vm_memory_mapping *gh_vm_mem_mapping_find_mapping(struct gunyah_vm *ghvm,
+ u64 gpa, u32 size)
+{
+ struct gunyah_vm_memory_mapping *mapping = NULL;
+ int ret;
+
+ ret = mutex_lock_interruptible(&ghvm->mm_lock);
+ if (ret)
+ return ERR_PTR(ret);
+
+ list_for_each_entry(mapping, &ghvm->memory_mappings, list) {
+ if (gpa >= mapping->guest_phys_addr &&
+ (gpa + size <= mapping->guest_phys_addr +
+ (mapping->npages << PAGE_SHIFT))) {
+ goto unlock;
+ }
+ }
+
+ mapping = NULL;
+unlock:
+ mutex_unlock(&ghvm->mm_lock);
+ return mapping;
+}
+
struct gunyah_vm_memory_mapping *gh_vm_mem_mapping_find(struct gunyah_vm *ghvm, u32 label)
{
struct gunyah_vm_memory_mapping *mapping;
diff --git a/include/uapi/linux/gunyah.h b/include/uapi/linux/gunyah.h
index 574f33b198d0..36359ad2175e 100644
--- a/include/uapi/linux/gunyah.h
+++ b/include/uapi/linux/gunyah.h
@@ -42,4 +42,12 @@ struct gh_userspace_memory_region {
#define GH_VM_SET_USER_MEM_REGION _IOW(GH_IOCTL_TYPE, 0x1, \
struct gh_userspace_memory_region)
+struct gh_vm_dtb_config {
+ __u64 gpa;
+ __u64 size;
+};
+#define GH_VM_SET_DTB_CONFIG _IOW(GH_IOCTL_TYPE, 0x2, struct gh_vm_dtb_config)
+
+#define GH_VM_START _IO(GH_IOCTL_TYPE, 0x3)
+
#endif
--
2.25.1
Add architecture-independent standard error codes, types, and macros for
Gunyah hypercalls.
Reviewed-by: Dmitry Baryshkov <[email protected]>
Signed-off-by: Elliot Berman <[email protected]>
---
MAINTAINERS | 1 +
include/linux/gunyah.h | 74 ++++++++++++++++++++++++++++++++++++++++++
2 files changed, 75 insertions(+)
create mode 100644 include/linux/gunyah.h
diff --git a/MAINTAINERS b/MAINTAINERS
index f32320a9efa4..74e76e0ab14d 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -8941,6 +8941,7 @@ L: [email protected]
S: Supported
F: Documentation/devicetree/bindings/firmware/gunyah-hypervisor.yaml
F: Documentation/virt/gunyah/
+F: include/linux/gunyah.h
HABANALABS PCI DRIVER
M: Oded Gabbay <[email protected]>
diff --git a/include/linux/gunyah.h b/include/linux/gunyah.h
new file mode 100644
index 000000000000..824e20a11d27
--- /dev/null
+++ b/include/linux/gunyah.h
@@ -0,0 +1,74 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
+ */
+
+#ifndef _GUNYAH_H
+#define _GUNYAH_H
+
+#include <linux/types.h>
+#include <linux/errno.h>
+
+/* Common Gunyah macros */
+#define GH_CAPID_INVAL U64_MAX
+#define GH_VMID_ROOT_VM 0xff
+
+#define GH_ERROR_OK 0
+
+#define GH_ERROR_UNIMPLEMENTED -1
+#define GH_ERROR_RETRY -2
+
+#define GH_ERROR_ARG_INVAL 1
+#define GH_ERROR_ARG_SIZE 2
+#define GH_ERROR_ARG_ALIGN 3
+
+#define GH_ERROR_NOMEM 10
+
+#define GH_ERROR_ADDR_OVFL 20
+#define GH_ERROR_ADDR_UNFL 21
+#define GH_ERROR_ADDR_INVAL 22
+
+#define GH_ERROR_DENIED 30
+#define GH_ERROR_BUSY 31
+#define GH_ERROR_IDLE 32
+
+#define GH_ERROR_IRQ_BOUND 40
+#define GH_ERROR_IRQ_UNBOUND 41
+
+#define GH_ERROR_CSPACE_CAP_NULL 50
+#define GH_ERROR_CSPACE_CAP_REVOKED 51
+#define GH_ERROR_CSPACE_WRONG_OBJ_TYPE 52
+#define GH_ERROR_CSPACE_INSUF_RIGHTS 53
+#define GH_ERROR_CSPACE_FULL 54
+
+#define GH_ERROR_MSGQUEUE_EMPTY 60
+#define GH_ERROR_MSGQUEUE_FULL 61
+
+static inline int gh_remap_error(int gh_error)
+{
+ switch (gh_error) {
+ case GH_ERROR_OK:
+ return 0;
+ case GH_ERROR_NOMEM:
+ return -ENOMEM;
+ case GH_ERROR_DENIED:
+ case GH_ERROR_CSPACE_CAP_NULL:
+ case GH_ERROR_CSPACE_CAP_REVOKED:
+ case GH_ERROR_CSPACE_WRONG_OBJ_TYPE:
+ case GH_ERROR_CSPACE_INSUF_RIGHTS:
+ case GH_ERROR_CSPACE_FULL:
+ return -EACCES;
+ case GH_ERROR_BUSY:
+ case GH_ERROR_IDLE:
+ return -EBUSY;
+ case GH_ERROR_IRQ_BOUND:
+ case GH_ERROR_IRQ_UNBOUND:
+ case GH_ERROR_MSGQUEUE_FULL:
+ case GH_ERROR_MSGQUEUE_EMPTY:
+ return -EPERM;
+ default:
+ return -EINVAL;
+ }
+}
+
+#endif
--
2.25.1
Gunyah is an open-source Type-1 hypervisor developed by Qualcomm. It
does not depend on any lower-privileged OS/kernel code for its core
functionality. This increases its security and can support a smaller
trusted computing based when compared to Type-2 hypervisors.
Add documentation describing the Gunyah hypervisor and the main
components of the Gunyah hypervisor which are of interest to Linux
virtualization development.
Reviewed-by: Bagas Sanjaya <[email protected]>
Signed-off-by: Elliot Berman <[email protected]>
---
Documentation/virt/gunyah/index.rst | 114 ++++++++++++++++++++
Documentation/virt/gunyah/message-queue.rst | 56 ++++++++++
Documentation/virt/index.rst | 1 +
MAINTAINERS | 7 ++
4 files changed, 178 insertions(+)
create mode 100644 Documentation/virt/gunyah/index.rst
create mode 100644 Documentation/virt/gunyah/message-queue.rst
diff --git a/Documentation/virt/gunyah/index.rst b/Documentation/virt/gunyah/index.rst
new file mode 100644
index 000000000000..fbadbdd24da7
--- /dev/null
+++ b/Documentation/virt/gunyah/index.rst
@@ -0,0 +1,114 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+=================
+Gunyah Hypervisor
+=================
+
+.. toctree::
+ :maxdepth: 1
+
+ message-queue
+
+Gunyah is a Type-1 hypervisor which is independent of any OS kernel, and runs in
+a higher CPU privilege level. It does not depend on any lower-privileged operating system
+for its core functionality. This increases its security and can support a much smaller
+trusted computing base than a Type-2 hypervisor.
+
+Gunyah is an open source hypervisor. The source repo is available at
+https://github.com/quic/gunyah-hypervisor.
+
+Gunyah provides these following features.
+
+- Scheduling:
+
+ A scheduler for virtual CPUs (vCPUs) on physical CPUs and enables time-sharing
+ of the CPUs. Gunyah supports two models of scheduling:
+
+ 1. "Behind the back" scheduling in which Gunyah hypervisor schedules vCPUS on its own.
+ 2. "Proxy" scheduling in which a delegated VM can donate part of one of its vCPU slice
+ to another VM's vCPU via a hypercall.
+
+- Memory Management:
+
+ APIs handling memory, abstracted as objects, limiting direct use of physical
+ addresses. Memory ownership and usage tracking of all memory under its control.
+ Memory partitioning between VMs is a fundamental security feature.
+
+- Interrupt Virtualization:
+
+ Uses CPU hardware interrupt virtualization capabilities. Interrupts are handled
+ in the hypervisor and routed to the assigned VM.
+
+- Inter-VM Communication:
+
+ There are several different mechanisms provided for communicating between VMs.
+
+- Virtual platform:
+
+ Architectural devices such as interrupt controllers and CPU timers are directly provided
+ by the hypervisor as well as core virtual platform devices and system APIs such as ARM PSCI.
+
+- Device Virtualization:
+
+ Para-virtualization of devices is supported using inter-VM communication.
+
+Architectures supported
+=======================
+AArch64 with a GIC
+
+Resources and Capabilities
+==========================
+
+Some services or resources provided by the Gunyah hypervisor are described to a virtual machine by
+capability IDs. For instance, inter-VM communication is performed with doorbells and message queues.
+Gunyah allows access to manipulate that doorbell via the capability ID. These devices are described
+in Linux as a struct gunyah_resource.
+
+High level management of these resources is performed by the resource manager VM. RM informs a
+guest VM about resources it can access through either the device tree or via guest-initiated RPC.
+
+For each virtual machine, Gunyah maintains a table of resources which can be accessed by that VM.
+An entry in this table is called a "capability" and VMs can only access resources via this
+capability table. Hence, virtual Gunyah devices are referenced by a "capability IDs" and not a
+"resource IDs". A VM can have multiple capability IDs mapping to the same resource. If 2 VMs have
+access to the same resource, they may not be using the same capability ID to access that resource
+since the tables are independent per VM.
+
+Resource Manager
+================
+
+The resource manager (RM) is a privileged application VM supporting the Gunyah Hypervisor.
+It provides policy enforcement aspects of the virtualization system. The resource manager can
+be treated as an extension of the Hypervisor but is separated to its own partition to ensure
+that the hypervisor layer itself remains small and secure and to maintain a separation of policy
+and mechanism in the platform. On arm64, RM runs at NS-EL1 similar to other virtual machines.
+
+Communication with the resource manager from each guest VM happens with message-queue.rst. Details
+about the specific messages can be found in drivers/virt/gunyah/rsc_mgr.c
+
+::
+
+ +-------+ +--------+ +--------+
+ | RM | | VM_A | | VM_B |
+ +-.-.-.-+ +---.----+ +---.----+
+ | | | |
+ +-.-.-----------.------------.----+
+ | | \==========/ | |
+ | \========================/ |
+ | Gunyah |
+ +---------------------------------+
+
+The source for the resource manager is available at https://github.com/quic/gunyah-resource-manager.
+
+The resource manager provides the following features:
+
+- VM lifecycle management: allocating a VM, starting VMs, destruction of VMs
+- VM access control policy, including memory sharing and lending
+- Interrupt routing configuration
+- Forwarding of system-level events (e.g. VM shutdown) to owner VM
+
+When booting a virtual machine which uses a devicetree, resource manager overlays a
+/hypervisor node. This node can let Linux know it is running as a Gunyah guest VM,
+how to communicate with resource manager, and basic description and capabilities of
+this VM. See Documentation/devicetree/bindings/firmware/gunyah-hypervisor.yaml for a description
+of this node.
diff --git a/Documentation/virt/gunyah/message-queue.rst b/Documentation/virt/gunyah/message-queue.rst
new file mode 100644
index 000000000000..be4ab289236a
--- /dev/null
+++ b/Documentation/virt/gunyah/message-queue.rst
@@ -0,0 +1,56 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+Message Queues
+==============
+Message queue is a simple low-capacity IPC channel between two VMs. It is
+intended for sending small control and configuration messages. Each message
+queue object is unidirectional, so a full-duplex IPC channel requires a pair of
+objects.
+
+Messages can be up to 240 bytes in length. Longer messages require a further
+protocol on top of the message queue messages themselves. For instance, communication
+with the resource manager adds a header field for sending longer messages via multiple
+message fragments.
+
+The diagram below shows how message queue works. A typical configuration involves
+2 message queues. Message queue 1 allows VM_A to send messages to VM_B. Message
+queue 2 allows VM_B to send messages to VM_A.
+
+1. VM_A sends a message of up to 240 bytes in length. It raises a hypercall
+ with the message to inform the hypervisor to add the message to
+ message queue 1's queue.
+
+2. Gunyah raises the corresponding interrupt for VM_B when any of these happens:
+
+ a. gh_msgq_send has PUSH flag. Queue is immediately flushed. This is the typical case.
+ b. Explicility with gh_msgq_push command from VM_A.
+ c. Message queue has reached a threshold depth.
+
+3. VM_B calls gh_msgq_recv and Gunyah copies message to requested buffer.
+
+For VM_B to send a message to VM_A, the process is identical, except that hypercalls
+reference message queue 2's capability ID. Each message queue has its own independent
+vIRQ: two TX message queues will have two vIRQs (and two capability IDs).
+
+::
+
+ +---------------+ +-----------------+ +---------------+
+ | VM_A | |Gunyah hypervisor| | VM_B |
+ | | | | | |
+ | | | | | |
+ | | Tx | | | |
+ | |-------->| | Rx vIRQ | |
+ |gh_msgq_send() | Tx vIRQ |Message queue 1 |-------->|gh_msgq_recv() |
+ | |<------- | | | |
+ | | | | | |
+ | Message Queue | | | | Message Queue |
+ | driver | | | | driver |
+ | | | | | |
+ | | | | | |
+ | | | | Tx | |
+ | | Rx vIRQ | |<--------| |
+ |gh_msgq_recv() |<--------|Message queue 2 | Tx vIRQ |gh_msgq_send() |
+ | | | |-------->| |
+ | | | | | |
+ | | | | | |
+ +---------------+ +-----------------+ +---------------+
diff --git a/Documentation/virt/index.rst b/Documentation/virt/index.rst
index 2f1cffa87b1b..418d540f5484 100644
--- a/Documentation/virt/index.rst
+++ b/Documentation/virt/index.rst
@@ -15,6 +15,7 @@ Linux Virtualization Support
acrn/index
coco/sev-guest
hyperv/index
+ gunyah/index
.. only:: html and subproject
diff --git a/MAINTAINERS b/MAINTAINERS
index 886d3f69ee64..1dd8f58d6e01 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -8934,6 +8934,13 @@ L: [email protected]
S: Maintained
F: block/partitions/efi.*
+GUNYAH HYPERVISOR DRIVER
+M: Elliot Berman <[email protected]>
+M: Murali Nalajala <[email protected]>
+L: [email protected]
+S: Supported
+F: Documentation/virt/gunyah/
+
HABANALABS PCI DRIVER
M: Oded Gabbay <[email protected]>
S: Supported
--
2.25.1
Add hypercalls to send and receive messages on a Gunyah message queue.
Signed-off-by: Elliot Berman <[email protected]>
---
arch/arm64/gunyah/gunyah_hypercall.c | 33 ++++++++++++++++++++++++++++
include/linux/gunyah.h | 5 +++++
2 files changed, 38 insertions(+)
diff --git a/arch/arm64/gunyah/gunyah_hypercall.c b/arch/arm64/gunyah/gunyah_hypercall.c
index 0beb3123d650..d28600909be4 100644
--- a/arch/arm64/gunyah/gunyah_hypercall.c
+++ b/arch/arm64/gunyah/gunyah_hypercall.c
@@ -26,6 +26,8 @@
GH_FN_ID(GH_CALL_TYPE_HYPERCALL, fn))
#define GH_HYPERCALL_HYP_IDENTIFY GH_HYPERCALL(0x0000)
+#define GH_HYPERCALL_MSGQ_SEND GH_HYPERCALL(0x001B)
+#define GH_HYPERCALL_MSGQ_RECV GH_HYPERCALL(0x001C)
/**
* gh_hypercall_get_uid() - Returns a UID when running under a Gunyah hypervisor
@@ -65,5 +67,36 @@ void gh_hypercall_hyp_identify(struct gh_hypercall_hyp_identify_resp *hyp_identi
}
EXPORT_SYMBOL_GPL(gh_hypercall_hyp_identify);
+int gh_hypercall_msgq_send(u64 capid, size_t size, uintptr_t buff, int tx_flags, bool *ready)
+{
+ struct arm_smccc_res res;
+
+ arm_smccc_1_1_hvc(GH_HYPERCALL_MSGQ_SEND, capid, size, buff, tx_flags, 0, &res);
+
+ if (res.a0)
+ return res.a0;
+
+ *ready = res.a1;
+
+ return res.a0;
+}
+EXPORT_SYMBOL_GPL(gh_hypercall_msgq_send);
+
+int gh_hypercall_msgq_recv(u64 capid, uintptr_t buff, size_t size, size_t *recv_size, bool *ready)
+{
+ struct arm_smccc_res res;
+
+ arm_smccc_1_1_hvc(GH_HYPERCALL_MSGQ_RECV, capid, buff, size, 0, &res);
+
+ if (res.a0)
+ return res.a0;
+
+ *recv_size = res.a1;
+ *ready = res.a2;
+
+ return res.a0;
+}
+EXPORT_SYMBOL_GPL(gh_hypercall_msgq_recv);
+
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Gunyah Hypervisor Hypercalls");
diff --git a/include/linux/gunyah.h b/include/linux/gunyah.h
index 166156f69df9..c863cac4a3cf 100644
--- a/include/linux/gunyah.h
+++ b/include/linux/gunyah.h
@@ -102,4 +102,9 @@ static inline u16 gh_api_version(void)
void gh_hypercall_get_uid(u32 uid[4]);
void gh_hypercall_hyp_identify(struct gh_hypercall_hyp_identify_resp *hyp_identity);
+#define GH_HYPERCALL_MSGQ_TX_FLAGS_PUSH BIT(0)
+
+int gh_hypercall_msgq_send(u64 capid, size_t size, uintptr_t buff, int tx_flags, bool *ready);
+int gh_hypercall_msgq_recv(u64 capid, uintptr_t buff, size_t size, size_t *recv_size, bool *ready);
+
#endif
--
2.25.1
Some VM functions need to acquire Gunyah resources. For instance, Gunyah
vCPUs are exposed to the host as a resource. The Gunyah vCPU function
will register a resource ticket and be able to interact with the
hypervisor once the resource ticket is filled.
Signed-off-by: Elliot Berman <[email protected]>
---
drivers/virt/gunyah/vm_mgr.c | 93 ++++++++++++++++++++++++++++++++++-
drivers/virt/gunyah/vm_mgr.h | 5 ++
include/linux/gunyah.h | 1 +
include/linux/gunyah_vm_mgr.h | 14 ++++++
4 files changed, 112 insertions(+), 1 deletion(-)
diff --git a/drivers/virt/gunyah/vm_mgr.c b/drivers/virt/gunyah/vm_mgr.c
index a5bda4d6613b..3328b84fa681 100644
--- a/drivers/virt/gunyah/vm_mgr.c
+++ b/drivers/virt/gunyah/vm_mgr.c
@@ -115,6 +115,56 @@ static long gh_vm_add_function(struct gunyah_vm *ghvm, struct gunyah_vm_function
return r;
}
+int ghvm_add_resource_ticket(struct gunyah_vm *ghvm, struct gunyah_vm_resource_ticket *ticket)
+{
+ struct gunyah_vm_resource_ticket *iter;
+ struct gunyah_resource *ghrsc;
+ int ret = 0;
+
+ mutex_lock(&ghvm->resources_lock);
+ list_for_each_entry(iter, &ghvm->resource_tickets, list) {
+ if (iter->resource_type == ticket->resource_type && iter->label == ticket->label) {
+ ret = -EEXIST;
+ goto out;
+ }
+ }
+
+ if (!try_module_get(ticket->owner)) {
+ ret = -ENODEV;
+ goto out;
+ }
+
+ list_add(&ticket->list, &ghvm->resource_tickets);
+ INIT_LIST_HEAD(&ticket->resources);
+
+ list_for_each_entry(ghrsc, &ghvm->resources, list) {
+ if (ghrsc->type == ticket->resource_type && ghrsc->rm_label == ticket->label) {
+ if (!ticket->populate(ticket, ghrsc))
+ list_move(&ghrsc->list, &ticket->resources);
+ }
+ }
+out:
+ mutex_unlock(&ghvm->resources_lock);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(ghvm_add_resource_ticket);
+
+void ghvm_remove_resource_ticket(struct gunyah_vm *ghvm, struct gunyah_vm_resource_ticket *ticket)
+{
+ struct gunyah_resource *ghrsc;
+
+ mutex_lock(&ghvm->resources_lock);
+ list_for_each_entry(ghrsc, &ticket->resources, list) {
+ ticket->unpopulate(ticket, ghrsc);
+ list_move(&ghrsc->list, &ghvm->resources);
+ }
+
+ module_put(ticket->owner);
+ list_del(&ticket->list);
+ mutex_unlock(&ghvm->resources_lock);
+}
+EXPORT_SYMBOL_GPL(ghvm_remove_resource_ticket);
+
static void ghvm_put(struct kref *kref)
{
struct gunyah_vm *ghvm = container_of(kref, struct gunyah_vm, kref);
@@ -156,17 +206,41 @@ static __must_check struct gunyah_vm *gunyah_vm_alloc(struct gh_rm_rpc *rm)
INIT_LIST_HEAD(&ghvm->memory_mappings);
init_rwsem(&ghvm->status_lock);
kref_init(&ghvm->kref);
+ mutex_init(&ghvm->resources_lock);
+ INIT_LIST_HEAD(&ghvm->resources);
+ INIT_LIST_HEAD(&ghvm->resource_tickets);
INIT_LIST_HEAD(&ghvm->functions);
return ghvm;
}
+static void gh_vm_add_resource(struct gunyah_vm *ghvm, struct gunyah_resource *ghrsc)
+{
+ struct gunyah_vm_resource_ticket *ticket;
+
+ mutex_lock(&ghvm->resources_lock);
+ list_for_each_entry(ticket, &ghvm->resource_tickets, list) {
+ if (ghrsc->type == ticket->resource_type && ghrsc->rm_label == ticket->label) {
+ if (!ticket->populate(ticket, ghrsc)) {
+ list_add(&ghrsc->list, &ticket->resources);
+ goto found;
+ }
+ }
+ }
+ list_add(&ghrsc->list, &ghvm->resources);
+found:
+ mutex_unlock(&ghvm->resources_lock);
+}
+
static int gh_vm_start(struct gunyah_vm *ghvm)
{
struct gunyah_vm_memory_mapping *mapping;
u64 dtb_offset;
u32 mem_handle;
- int ret;
+ ssize_t num_hyp_resources;
+ struct gunyah_resource *ghrsc;
+ struct gh_rm_hyp_resource *resources;
+ int ret, i;
down_write(&ghvm->status_lock);
if (ghvm->vm_status != GH_RM_VM_STATUS_NO_STATE) {
@@ -198,6 +272,23 @@ static int gh_vm_start(struct gunyah_vm *ghvm)
goto err;
}
+ num_hyp_resources = gh_rm_get_hyp_resources(ghvm->rm, ghvm->vmid, &resources);
+ if (num_hyp_resources < 0) {
+ pr_warn("Failed to get hypervisor resources for VM: %ld\n", num_hyp_resources);
+ ret = -EINVAL;
+ goto err;
+ }
+
+ for (i = 0; i < num_hyp_resources; i++) {
+ ghrsc = gh_rm_alloc_resource(ghvm->rm, &resources[i]);
+ if (!ghrsc) {
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ gh_vm_add_resource(ghvm, ghrsc);
+ }
+
ret = gh_rm_vm_start(ghvm->rm, ghvm->vmid);
if (ret) {
pr_warn("Failed to start VM: %d\n", ret);
diff --git a/drivers/virt/gunyah/vm_mgr.h b/drivers/virt/gunyah/vm_mgr.h
index 2c4fd240792a..8a71f97960bc 100644
--- a/drivers/virt/gunyah/vm_mgr.h
+++ b/drivers/virt/gunyah/vm_mgr.h
@@ -7,6 +7,7 @@
#define _GH_PRIV_VM_MGR_H
#include <linux/gunyah_rsc_mgr.h>
+#include <linux/gunyah_vm_mgr.h>
#include <linux/list.h>
#include <linux/kref.h>
#include <linux/miscdevice.h>
@@ -53,6 +54,10 @@ struct gunyah_vm {
struct mutex mm_lock;
struct list_head memory_mappings;
+ struct mutex resources_lock;
+ struct list_head resources;
+ struct list_head resource_tickets;
+
struct list_head functions;
};
diff --git a/include/linux/gunyah.h b/include/linux/gunyah.h
index 8cb6af88c75d..af69a3479025 100644
--- a/include/linux/gunyah.h
+++ b/include/linux/gunyah.h
@@ -28,6 +28,7 @@ struct gunyah_resource {
int irq;
/* To help allocator in resource manager */
+ struct list_head list;
u32 rm_label;
};
diff --git a/include/linux/gunyah_vm_mgr.h b/include/linux/gunyah_vm_mgr.h
index 4bb06f100ae5..3e6e6e0ab7f0 100644
--- a/include/linux/gunyah_vm_mgr.h
+++ b/include/linux/gunyah_vm_mgr.h
@@ -65,4 +65,18 @@ void gunyah_vm_function_unregister(struct gunyah_vm_function_driver *f);
} \
module_exit(_name##_mod_exit)
+struct gunyah_vm_resource_ticket {
+ struct list_head list;
+ struct list_head resources;
+ enum gunyah_resource_type resource_type;
+ u32 label;
+
+ struct module *owner;
+ int (*populate)(struct gunyah_vm_resource_ticket *ticket, struct gunyah_resource *ghrsc);
+ void (*unpopulate)(struct gunyah_vm_resource_ticket *ticket, struct gunyah_resource *ghrsc);
+};
+
+int ghvm_add_resource_ticket(struct gunyah_vm *ghvm, struct gunyah_vm_resource_ticket *ticket);
+void ghvm_remove_resource_ticket(struct gunyah_vm *ghvm, struct gunyah_vm_resource_ticket *ticket);
+
#endif
--
2.25.1
Introduce a framework for Gunyah userspace to install VM functions. VM
functions are optional interfaces to the virtual machine. vCPUs,
ioeventfs, and irqfds are examples of such VM functions and are
implemented in subsequent patches.
A generic framework is implemented instead of individual ioctls to
create vCPUs, irqfds, etc., in order to simplify the VM manager core
implementation and allow dynamic loading of VM function modules.
Signed-off-by: Elliot Berman <[email protected]>
---
Documentation/virt/gunyah/vm-manager.rst | 18 +++
drivers/virt/gunyah/vm_mgr.c | 145 ++++++++++++++++++++++-
drivers/virt/gunyah/vm_mgr.h | 5 +
include/linux/gunyah_vm_mgr.h | 68 +++++++++++
include/uapi/linux/gunyah.h | 12 ++
5 files changed, 247 insertions(+), 1 deletion(-)
create mode 100644 include/linux/gunyah_vm_mgr.h
diff --git a/Documentation/virt/gunyah/vm-manager.rst b/Documentation/virt/gunyah/vm-manager.rst
index 62513af09cbf..d929f8f20b3b 100644
--- a/Documentation/virt/gunyah/vm-manager.rst
+++ b/Documentation/virt/gunyah/vm-manager.rst
@@ -17,6 +17,24 @@ sharing userspace memory with a VM is done via the GH_VM_SET_USER_MEM_REGION
ioctl. The VM itself is configured to use the memory region via the
devicetree.
+Gunyah Functions
+================
+
+Components of a Gunyah VM's configuration that need kernel configuration are
+called "functions" and are built on top of a framework. Functions are identified
+by a string and have some argument(s) to configure them. They are typically
+created by the `GH_VM_ADD_FUNCTION` ioctl.
+
+Functions typically will always do at least one of these operations:
+
+1. Create resource ticket(s). Resource tickets allow a function to register
+ itself as the client for a Gunyah resource (e.g. doorbell or vCPU) and
+ the function is given the pointer to the `struct gunyah_resource` when the
+ VM is starting.
+
+2. Register IO handler(s). IO handlers allow a function to handle stage-2 faults
+ from the virtual machine.
+
Sample Userspace VMM
====================
diff --git a/drivers/virt/gunyah/vm_mgr.c b/drivers/virt/gunyah/vm_mgr.c
index a96cb7f73754..a5bda4d6613b 100644
--- a/drivers/virt/gunyah/vm_mgr.c
+++ b/drivers/virt/gunyah/vm_mgr.c
@@ -15,6 +15,125 @@
#include "vm_mgr.h"
+static DEFINE_MUTEX(functions_lock);
+static LIST_HEAD(functions);
+
+static struct gunyah_vm_function_driver *__find_function(const char name[GUNYAH_FUNCTION_NAME_SIZE])
+ __must_hold(functions_lock)
+{
+ struct gunyah_vm_function_driver *iter, *drv = NULL;
+
+ list_for_each_entry(iter, &functions, list) {
+ if (!strncmp(iter->name, name, GUNYAH_FUNCTION_NAME_SIZE)) {
+ drv = iter;
+ break;
+ }
+ }
+
+ return drv;
+}
+
+int gunyah_vm_function_register(struct gunyah_vm_function_driver *drv)
+{
+ int ret = 0;
+
+ mutex_lock(&functions_lock);
+ if (__find_function(drv->name)) {
+ ret = -EEXIST;
+ goto out;
+ }
+
+ INIT_LIST_HEAD(&drv->instances);
+ list_add(&drv->list, &functions);
+out:
+ mutex_unlock(&functions_lock);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(gunyah_vm_function_register);
+
+static void gh_vm_remove_function(struct gunyah_vm_function *f)
+ __must_hold(functions_lock)
+{
+ f->drv->release(f);
+ list_del(&f->vm_list);
+ list_del(&f->drv_list);
+ module_put(f->drv->mod);
+}
+
+void gunyah_vm_function_unregister(struct gunyah_vm_function_driver *drv)
+{
+ struct gunyah_vm_function *f, *iter;
+
+ mutex_lock(&functions_lock);
+ list_for_each_entry_safe(f, iter, &drv->instances, drv_list)
+ gh_vm_remove_function(f);
+ list_del(&drv->list);
+ mutex_unlock(&functions_lock);
+}
+EXPORT_SYMBOL_GPL(gunyah_vm_function_unregister);
+
+static long gh_vm_add_function(struct gunyah_vm *ghvm, struct gunyah_vm_function *f)
+{
+ long r = 0;
+
+ mutex_lock(&functions_lock);
+ f->drv = __find_function(f->fn.name);
+ if (!f->drv) {
+ mutex_unlock(&functions_lock);
+ r = request_module("ghfunc:%s", f->fn.name);
+ if (r)
+ return r;
+
+ mutex_lock(&functions_lock);
+ f->drv = __find_function(f->fn.name);
+ }
+
+ if (!f->drv) {
+ r = -ENOENT;
+ goto out;
+ }
+
+ if (!try_module_get(f->drv->mod)) {
+ r = -ENOENT;
+ f->drv = NULL;
+ goto out;
+ }
+
+ f->ghvm = ghvm;
+ f->rm = ghvm->rm;
+
+ r = f->drv->bind(f);
+ if (r < 0) {
+ module_put(f->drv->mod);
+ goto out;
+ }
+
+ list_add(&f->vm_list, &ghvm->functions);
+ list_add(&f->drv_list, &f->drv->instances);
+out:
+ mutex_unlock(&functions_lock);
+ return r;
+}
+
+static void ghvm_put(struct kref *kref)
+{
+ struct gunyah_vm *ghvm = container_of(kref, struct gunyah_vm, kref);
+
+ kfree(ghvm);
+}
+
+int __must_check get_gunyah_vm(struct gunyah_vm *ghvm)
+{
+ return kref_get_unless_zero(&ghvm->kref);
+}
+EXPORT_SYMBOL_GPL(get_gunyah_vm);
+
+void put_gunyah_vm(struct gunyah_vm *ghvm)
+{
+ kref_put(&ghvm->kref, ghvm_put);
+}
+EXPORT_SYMBOL_GPL(put_gunyah_vm);
+
static __must_check struct gunyah_vm *gunyah_vm_alloc(struct gh_rm_rpc *rm)
{
struct gunyah_vm *ghvm;
@@ -36,6 +155,8 @@ static __must_check struct gunyah_vm *gunyah_vm_alloc(struct gh_rm_rpc *rm)
mutex_init(&ghvm->mm_lock);
INIT_LIST_HEAD(&ghvm->memory_mappings);
init_rwsem(&ghvm->status_lock);
+ kref_init(&ghvm->kref);
+ INIT_LIST_HEAD(&ghvm->functions);
return ghvm;
}
@@ -170,6 +291,23 @@ static long gh_vm_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
r = -EINVAL;
break;
}
+ case GH_VM_ADD_FUNCTION: {
+ struct gunyah_vm_function *f;
+
+ r = -ENOMEM;
+ f = kzalloc(sizeof(*f), GFP_KERNEL);
+ if (!f)
+ break;
+
+ r = -EFAULT;
+ if (copy_from_user(&f->fn, argp, sizeof(f->fn)))
+ break;
+
+ r = gh_vm_add_function(ghvm, f);
+ if (r < 0)
+ kfree(f);
+ break;
+ }
default:
r = -ENOTTY;
break;
@@ -182,14 +320,19 @@ static int gh_vm_release(struct inode *inode, struct file *filp)
{
struct gunyah_vm *ghvm = filp->private_data;
struct gunyah_vm_memory_mapping *mapping, *tmp;
+ struct gunyah_vm_function *f, *iter;
gh_vm_stop(ghvm);
+ list_for_each_entry_safe(f, iter, &ghvm->functions, vm_list) {
+ gh_vm_remove_function(f);
+ }
list_for_each_entry_safe(mapping, tmp, &ghvm->memory_mappings, list) {
gh_vm_mem_mapping_reclaim(ghvm, mapping);
}
+
put_gh_rm(ghvm->rm);
- kfree(ghvm);
+ put_gunyah_vm(ghvm);
return 0;
}
diff --git a/drivers/virt/gunyah/vm_mgr.h b/drivers/virt/gunyah/vm_mgr.h
index 8dcba9f14b5c..2c4fd240792a 100644
--- a/drivers/virt/gunyah/vm_mgr.h
+++ b/drivers/virt/gunyah/vm_mgr.h
@@ -8,6 +8,7 @@
#include <linux/gunyah_rsc_mgr.h>
#include <linux/list.h>
+#include <linux/kref.h>
#include <linux/miscdevice.h>
#include <linux/mutex.h>
#include <linux/rwsem.h>
@@ -47,8 +48,12 @@ struct gunyah_vm {
enum gh_rm_vm_status vm_status;
struct rw_semaphore status_lock;
+ struct kref kref;
+
struct mutex mm_lock;
struct list_head memory_mappings;
+
+ struct list_head functions;
};
struct gunyah_vm_memory_mapping *gh_vm_mem_mapping_alloc(struct gunyah_vm *ghvm,
diff --git a/include/linux/gunyah_vm_mgr.h b/include/linux/gunyah_vm_mgr.h
new file mode 100644
index 000000000000..4bb06f100ae5
--- /dev/null
+++ b/include/linux/gunyah_vm_mgr.h
@@ -0,0 +1,68 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
+ */
+
+#ifndef _GUNYAH_VM_MGR_H
+#define _GUNYAH_VM_MGR_H
+
+#include <linux/compiler_types.h>
+#include <linux/gunyah.h>
+#include <linux/gunyah_rsc_mgr.h>
+#include <linux/list.h>
+#include <linux/mod_devicetable.h>
+#include <linux/notifier.h>
+
+#include <uapi/linux/gunyah.h>
+
+struct gunyah_vm;
+
+int __must_check get_gunyah_vm(struct gunyah_vm *ghvm);
+void put_gunyah_vm(struct gunyah_vm *ghvm);
+
+struct gunyah_vm_function;
+struct gunyah_vm_function_driver {
+ const char name[GUNYAH_FUNCTION_NAME_SIZE];
+ struct module *mod;
+ long (*bind)(struct gunyah_vm_function *f);
+ void (*release)(struct gunyah_vm_function *f);
+ struct list_head list;
+ struct list_head instances;
+};
+
+struct gunyah_vm_function {
+ struct gh_vm_function fn;
+ struct gunyah_vm *ghvm;
+ struct gh_rm_rpc *rm;
+ struct gunyah_vm_function_driver *drv;
+ void *data;
+ struct list_head vm_list;
+ struct list_head drv_list;
+};
+
+int gunyah_vm_function_register(struct gunyah_vm_function_driver *f);
+void gunyah_vm_function_unregister(struct gunyah_vm_function_driver *f);
+
+#define DECLARE_GUNYAH_VM_FUNCTION(_name, _bind, _release) \
+ static struct gunyah_vm_function_driver _name = { \
+ .name = __stringify(_name), \
+ .mod = THIS_MODULE, \
+ .bind = _bind, \
+ .release = _release, \
+ }; \
+ MODULE_ALIAS("ghfunc:"__stringify(_name))
+
+#define DECLARE_GUNYAH_VM_FUNCTION_INIT(_name, _bind, _release) \
+ DECLARE_GUNYAH_VM_FUNCTION(_name, _bind, _release); \
+ static int __init _name##_mod_init(void) \
+ { \
+ return gunyah_vm_function_register(&(_name)); \
+ } \
+ module_init(_name##_mod_init); \
+ static void __exit _name##_mod_exit(void) \
+ { \
+ gunyah_vm_function_unregister(&(_name)); \
+ } \
+ module_exit(_name##_mod_exit)
+
+#endif
diff --git a/include/uapi/linux/gunyah.h b/include/uapi/linux/gunyah.h
index 36359ad2175e..40db32aeb012 100644
--- a/include/uapi/linux/gunyah.h
+++ b/include/uapi/linux/gunyah.h
@@ -50,4 +50,16 @@ struct gh_vm_dtb_config {
#define GH_VM_START _IO(GH_IOCTL_TYPE, 0x3)
+#define GUNYAH_FUNCTION_NAME_SIZE 32
+#define GUNYAH_FUNCTION_MAX_ARG_SIZE 1024
+
+struct gh_vm_function {
+ char name[GUNYAH_FUNCTION_NAME_SIZE];
+ union {
+ char data[GUNYAH_FUNCTION_MAX_ARG_SIZE];
+ };
+};
+
+#define GH_VM_ADD_FUNCTION _IOW(GH_IOCTL_TYPE, 0x4, struct gh_vm_function)
+
#endif
--
2.25.1
Enable support for creating irqfds which can raise an interrupt on a
Gunyah virtual machine. irqfds are exposed to userspace as a Gunyah VM
function with the name "irqfd". If the VM devicetree is not configured
to create a doorbell with the corresponding label, userspace will still
be able to assert the eventfd but no interrupt will be raised on the
guest.
Co-developed-by: Prakruthi Deepak Heragu <[email protected]>
Signed-off-by: Prakruthi Deepak Heragu <[email protected]>
Signed-off-by: Elliot Berman <[email protected]>
---
Documentation/virt/gunyah/vm-manager.rst | 22 +++
drivers/virt/gunyah/Kconfig | 10 ++
drivers/virt/gunyah/Makefile | 1 +
drivers/virt/gunyah/gunyah_irqfd.c | 180 +++++++++++++++++++++++
include/linux/gunyah.h | 5 +
include/uapi/linux/gunyah.h | 11 +-
6 files changed, 228 insertions(+), 1 deletion(-)
create mode 100644 drivers/virt/gunyah/gunyah_irqfd.c
diff --git a/Documentation/virt/gunyah/vm-manager.rst b/Documentation/virt/gunyah/vm-manager.rst
index d11267d59802..b6cf8db826b8 100644
--- a/Documentation/virt/gunyah/vm-manager.rst
+++ b/Documentation/virt/gunyah/vm-manager.rst
@@ -142,3 +142,25 @@ The vcpu type will register with the VM Manager to expect to control
vCPU number `vcpu_id`. It returns a file descriptor allowing interaction with
the vCPU. See the Gunyah vCPU API description sections for interacting with
the Gunyah vCPU file descriptors.
+
+Type: "irqfd"
+^^^^^^^^^^^^^
+
+::
+
+ struct gh_fn_irqfd_arg {
+ __u32 fd;
+ __u32 label;
+ #define GH_IRQFD_LEVEL (1UL << 0)
+ #define GH_IRQFD_DEASSIGN (1UL << 1)
+ __u32 flags;
+ };
+
+Allows setting an eventfd to directly trigger a guest interrupt.
+irqfd.fd specifies the file descriptor to use as the eventfd.
+irqfd.label corresponds to the doorbell label used in the guest VM's devicetree.
+The irqfd is removed using the GH_IRQFD_DEASSIGN flag and specifying at least
+the irqfd.label.
+
+GH_IRQFD_LEVEL configures the corresponding doorbell to behave like a level
+triggered interrupt.
diff --git a/drivers/virt/gunyah/Kconfig b/drivers/virt/gunyah/Kconfig
index f9551cd5dfe4..42842e755a04 100644
--- a/drivers/virt/gunyah/Kconfig
+++ b/drivers/virt/gunyah/Kconfig
@@ -49,5 +49,15 @@ config GUNYAH_VCPU
can schedule the guest VM's vCPUs instead of using Gunyah's scheduler.
VMMs can also handle stage 2 faults of the vCPUs.
+ Say Y/M here if unsure and you want to support Gunyah VMMs.
+
+config GUNYAH_IRQFD
+ tristate "Gunyah irqfd interface"
+ depends on GUNYAH_RESOURCE_MANAGER
+ depends on GUNYAH_VM_MANAGER
+ help
+ Enable kernel support for creating irqfds which can raise an interrupt
+ on Gunyah virtual machine.
+
Say Y/M here if unsure and you want to support Gunyah VMMs.
endif
diff --git a/drivers/virt/gunyah/Makefile b/drivers/virt/gunyah/Makefile
index debdd7e4d9a1..065eeb060597 100644
--- a/drivers/virt/gunyah/Makefile
+++ b/drivers/virt/gunyah/Makefile
@@ -6,3 +6,4 @@ gunyah_rsc_mgr-$(CONFIG_GUNYAH_VM_MANAGER) += vm_mgr.o vm_mgr_mm.o
obj-$(CONFIG_GUNYAH_RESOURCE_MANAGER) += gunyah_rsc_mgr.o
obj-$(CONFIG_GUNYAH_VCPU) += gunyah_vcpu.o
+obj-$(CONFIG_GUNYAH_IRQFD) += gunyah_irqfd.o
diff --git a/drivers/virt/gunyah/gunyah_irqfd.c b/drivers/virt/gunyah/gunyah_irqfd.c
new file mode 100644
index 000000000000..34ec389323e2
--- /dev/null
+++ b/drivers/virt/gunyah/gunyah_irqfd.c
@@ -0,0 +1,180 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
+ */
+
+#include <linux/eventfd.h>
+#include <linux/file.h>
+#include <linux/fs.h>
+#include <linux/gunyah.h>
+#include <linux/gunyah_vm_mgr.h>
+#include <linux/kref.h>
+#include <linux/module.h>
+#include <linux/poll.h>
+#include <linux/printk.h>
+
+#include <uapi/linux/gunyah.h>
+
+struct gunyah_irqfd {
+ struct gunyah_resource *ghrsc;
+ struct gunyah_vm_resource_ticket ticket;
+ struct gunyah_vm_function *f;
+
+ struct kref kref;
+ bool level;
+
+ struct eventfd_ctx *ctx;
+ wait_queue_entry_t wait;
+ poll_table pt;
+ struct fd fd;
+ struct work_struct shutdown_work;
+};
+
+static void gh_irqfd_cleanup(struct kref *kref)
+{
+ struct gunyah_irqfd *irqfd = container_of(kref, struct gunyah_irqfd, kref);
+
+ kfree(irqfd);
+}
+
+
+static void irqfd_shutdown(struct work_struct *work)
+{
+ struct gunyah_irqfd *irqfd = container_of(work, struct gunyah_irqfd, shutdown_work);
+ u64 isr;
+
+ if (irqfd->ctx) {
+ eventfd_ctx_remove_wait_queue(irqfd->ctx, &irqfd->wait, &isr);
+ eventfd_ctx_put(irqfd->ctx);
+ fdput(irqfd->fd);
+ irqfd->ctx = NULL;
+ irqfd->fd.file = NULL;
+ }
+
+ kref_put(&irqfd->kref, gh_irqfd_cleanup);
+}
+
+static int irqfd_wakeup(wait_queue_entry_t *wait, unsigned int mode, int sync, void *key)
+{
+ struct gunyah_irqfd *irqfd = container_of(wait, struct gunyah_irqfd, wait);
+ __poll_t flags = key_to_poll(key);
+ u64 enable_mask = GH_DBL_NONBLOCK;
+ u64 old_flags;
+ int ret = 0;
+
+ if (flags & EPOLLIN) {
+ if (irqfd->ghrsc) {
+ ret = gh_hypercall_dbl_send(irqfd->ghrsc->capid, enable_mask, &old_flags);
+ if (ret)
+ pr_err("Failed to assert irq %d\n", irqfd->f->fn.irqfd.label);
+ }
+ }
+
+ return 0;
+}
+
+static void irqfd_ptable_queue_proc(struct file *file, wait_queue_head_t *wqh, poll_table *pt)
+{
+ struct gunyah_irqfd *irq_ctx = container_of(pt, struct gunyah_irqfd, pt);
+
+ add_wait_queue(wqh, &irq_ctx->wait);
+}
+
+static int gunyah_irqfd_populate(struct gunyah_vm_resource_ticket *ticket,
+ struct gunyah_resource *ghrsc)
+{
+ struct gunyah_irqfd *irqfd = container_of(ticket, struct gunyah_irqfd, ticket);
+ u64 enable_mask = GH_DBL_NONBLOCK;
+ u64 ack_mask = ~0;
+ int ret = 0;
+
+ irqfd->ghrsc = ghrsc;
+ if (irqfd->level) {
+ ret = gh_hypercall_dbl_set_mask(irqfd->ghrsc->capid, enable_mask, ack_mask);
+ if (ret)
+ pr_warn("irq %d couldn't be set as level triggered. Might cause IRQ storm if asserted\n",
+ irqfd->f->fn.irqfd.label);
+ }
+ kref_get(&irqfd->kref);
+
+ return 0;
+}
+
+static void gunyah_irqfd_unpopulate(struct gunyah_vm_resource_ticket *ticket,
+ struct gunyah_resource *ghrsc)
+{
+ struct gunyah_irqfd *irqfd = container_of(ticket, struct gunyah_irqfd, ticket);
+
+ queue_work(system_wq, &irqfd->shutdown_work);
+ irqfd->ghrsc = NULL;
+ kref_put(&irqfd->kref, gh_irqfd_cleanup);
+}
+
+static long gunyah_irqfd_bind(struct gunyah_vm_function *f)
+{
+ __poll_t events;
+ struct gunyah_irqfd *irqfd;
+ long r;
+
+ irqfd = kzalloc(sizeof(*irqfd), GFP_KERNEL);
+ if (!irqfd)
+ return -ENOMEM;
+
+ irqfd->f = f;
+ f->data = irqfd;
+
+ irqfd->fd = fdget(f->fn.irqfd.fd);
+ if (!irqfd->fd.file) {
+ r = -EBADF;
+ goto err_free;
+ }
+
+ irqfd->ctx = eventfd_ctx_fileget(irqfd->fd.file);
+ if (IS_ERR(irqfd->ctx)) {
+ r = PTR_ERR(irqfd->ctx);
+ goto err_fdput;
+ }
+
+ if (f->fn.irqfd.flags & GH_IRQFD_LEVEL)
+ irqfd->level = true;
+
+ irqfd->ticket.resource_type = GUNYAH_RESOURCE_TYPE_BELL_TX;
+ irqfd->ticket.label = f->fn.irqfd.label;
+ irqfd->ticket.owner = THIS_MODULE;
+ irqfd->ticket.populate = gunyah_irqfd_populate;
+ irqfd->ticket.unpopulate = gunyah_irqfd_unpopulate;
+
+ r = ghvm_add_resource_ticket(f->ghvm, &irqfd->ticket);
+ if (r)
+ goto err_ctx;
+
+ init_waitqueue_func_entry(&irqfd->wait, irqfd_wakeup);
+ INIT_WORK(&irqfd->shutdown_work, irqfd_shutdown);
+ init_poll_funcptr(&irqfd->pt, irqfd_ptable_queue_proc);
+ kref_init(&irqfd->kref);
+
+ events = vfs_poll(irqfd->fd.file, &irqfd->pt);
+ if (events & EPOLLIN)
+ pr_warn("Premature injection of interrupt\n");
+
+ return 0;
+err_ctx:
+ eventfd_ctx_put(irqfd->ctx);
+err_fdput:
+ fdput(irqfd->fd);
+err_free:
+ kfree(irqfd);
+ return r;
+}
+
+static void gunyah_irqfd_release(struct gunyah_vm_function *f)
+{
+ struct gunyah_irqfd *irqfd = f->data;
+
+ /* unpopulate will trigger clean up of the eventfd */
+ ghvm_remove_resource_ticket(irqfd->f->ghvm, &irqfd->ticket);
+}
+
+DECLARE_GUNYAH_VM_FUNCTION_INIT(irqfd, gunyah_irqfd_bind, gunyah_irqfd_release);
+MODULE_DESCRIPTION("Gunyah irqfds");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/gunyah.h b/include/linux/gunyah.h
index 2c2b498a792d..db5910adc658 100644
--- a/include/linux/gunyah.h
+++ b/include/linux/gunyah.h
@@ -32,6 +32,11 @@ struct gunyah_resource {
u32 rm_label;
};
+/**
+ * Gunyah Doorbells
+ */
+#define GH_DBL_NONBLOCK BIT(32)
+
/**
* Gunyah Message Queues
*/
diff --git a/include/uapi/linux/gunyah.h b/include/uapi/linux/gunyah.h
index ccc0e6d0f985..5eab212c7bf5 100644
--- a/include/uapi/linux/gunyah.h
+++ b/include/uapi/linux/gunyah.h
@@ -57,10 +57,19 @@ struct gh_fn_vcpu_arg {
__u32 vcpu_id;
};
+struct gh_fn_irqfd_arg {
+ __u32 fd;
+ __u32 label;
+#define GH_IRQFD_LEVEL (1UL << 0)
+#define GH_IRQFD_DEASSIGN (1UL << 1)
+ __u32 flags;
+};
+
struct gh_vm_function {
char name[GUNYAH_FUNCTION_NAME_SIZE];
union {
- struct gh_device_vcpu_arg vcpu;
+ struct gh_fn_vcpu_arg vcpu;
+ struct gh_fn_irqfd_arg irqfd;
char data[GUNYAH_FUNCTION_MAX_ARG_SIZE];
};
};
--
2.25.1
When launching a virtual machine, Gunyah userspace allocates memory for
the guest and informs Gunyah about these memory regions through
SET_USER_MEMORY_REGION ioctl.
Co-developed-by: Prakruthi Deepak Heragu <[email protected]>
Signed-off-by: Prakruthi Deepak Heragu <[email protected]>
Signed-off-by: Elliot Berman <[email protected]>
---
drivers/virt/gunyah/Makefile | 2 +-
drivers/virt/gunyah/vm_mgr.c | 45 +++++++
drivers/virt/gunyah/vm_mgr.h | 27 ++++
drivers/virt/gunyah/vm_mgr_mm.c | 221 ++++++++++++++++++++++++++++++++
include/uapi/linux/gunyah.h | 22 ++++
5 files changed, 316 insertions(+), 1 deletion(-)
create mode 100644 drivers/virt/gunyah/vm_mgr_mm.c
diff --git a/drivers/virt/gunyah/Makefile b/drivers/virt/gunyah/Makefile
index e817ae6deddc..9d6f900ab7b0 100644
--- a/drivers/virt/gunyah/Makefile
+++ b/drivers/virt/gunyah/Makefile
@@ -1,5 +1,5 @@
obj-$(CONFIG_GUNYAH) += gunyah.o
gunyah_rsc_mgr-y += rsc_mgr.o rsc_mgr_rpc.o
-gunyah_rsc_mgr-$(CONFIG_GUNYAH_VM_MANAGER) += vm_mgr.o
+gunyah_rsc_mgr-$(CONFIG_GUNYAH_VM_MANAGER) += vm_mgr.o vm_mgr_mm.o
obj-$(CONFIG_GUNYAH_RESOURCE_MANAGER) += gunyah_rsc_mgr.o
diff --git a/drivers/virt/gunyah/vm_mgr.c b/drivers/virt/gunyah/vm_mgr.c
index 9c796e5edcf1..a7220ad41669 100644
--- a/drivers/virt/gunyah/vm_mgr.c
+++ b/drivers/virt/gunyah/vm_mgr.c
@@ -33,14 +33,55 @@ static __must_check struct gunyah_vm *gunyah_vm_alloc(struct gh_rm_rpc *rm)
ghvm->vmid = vmid;
ghvm->rm = rm;
+ mutex_init(&ghvm->mm_lock);
+ INIT_LIST_HEAD(&ghvm->memory_mappings);
+
return ghvm;
}
static long gh_vm_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
+ struct gunyah_vm *ghvm = filp->private_data;
+ void __user *argp = (void __user *)arg;
long r;
switch (cmd) {
+ case GH_VM_SET_USER_MEM_REGION: {
+ struct gunyah_vm_memory_mapping *mapping;
+ struct gh_userspace_memory_region region;
+
+ r = -EFAULT;
+ if (copy_from_user(®ion, argp, sizeof(region)))
+ break;
+
+ r = -EINVAL;
+ /* All other flag bits are reserved for future use */
+ if (region.flags & ~(GH_MEM_ALLOW_READ | GH_MEM_ALLOW_WRITE | GH_MEM_ALLOW_EXEC |
+ GH_MEM_LENT))
+ break;
+
+
+ if (region.memory_size) {
+ r = 0;
+ mapping = gh_vm_mem_mapping_alloc(ghvm, ®ion);
+ if (IS_ERR(mapping)) {
+ r = PTR_ERR(mapping);
+ break;
+ }
+ } else {
+ mapping = gh_vm_mem_mapping_find(ghvm, region.label);
+ if (IS_ERR(mapping)) {
+ r = PTR_ERR(mapping);
+ break;
+ }
+ r = 0;
+ if (!mapping)
+ break;
+ gh_vm_mem_mapping_reclaim(ghvm, mapping);
+ kfree(mapping);
+ }
+ break;
+ }
default:
r = -ENOTTY;
break;
@@ -52,7 +93,11 @@ static long gh_vm_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
static int gh_vm_release(struct inode *inode, struct file *filp)
{
struct gunyah_vm *ghvm = filp->private_data;
+ struct gunyah_vm_memory_mapping *mapping, *tmp;
+ list_for_each_entry_safe(mapping, tmp, &ghvm->memory_mappings, list) {
+ gh_vm_mem_mapping_reclaim(ghvm, mapping);
+ }
put_gh_rm(ghvm->rm);
kfree(ghvm);
return 0;
diff --git a/drivers/virt/gunyah/vm_mgr.h b/drivers/virt/gunyah/vm_mgr.h
index 84e3d1a8571d..955856c1310b 100644
--- a/drivers/virt/gunyah/vm_mgr.h
+++ b/drivers/virt/gunyah/vm_mgr.h
@@ -7,6 +7,9 @@
#define _GH_PRIV_VM_MGR_H
#include <linux/gunyah_rsc_mgr.h>
+#include <linux/list.h>
+#include <linux/miscdevice.h>
+#include <linux/mutex.h>
#include <uapi/linux/gunyah.h>
@@ -17,9 +20,33 @@ static inline long gh_dev_vm_mgr_ioctl(struct gh_rm_rpc *rm, unsigned int cmd, u
{ return -ENOIOCTLCMD; }
#endif
+enum gunyah_vm_mem_share_type {
+ VM_MEM_SHARE,
+ VM_MEM_LEND,
+};
+
+struct gunyah_vm_memory_mapping {
+ struct list_head list;
+ enum gunyah_vm_mem_share_type share_type;
+ struct gh_rm_mem_parcel parcel;
+
+ __u64 guest_phys_addr;
+ __u32 mem_size;
+ struct page **pages;
+ unsigned long npages;
+};
+
struct gunyah_vm {
u16 vmid;
struct gh_rm_rpc *rm;
+
+ struct mutex mm_lock;
+ struct list_head memory_mappings;
};
+struct gunyah_vm_memory_mapping *gh_vm_mem_mapping_alloc(struct gunyah_vm *ghvm,
+ struct gh_userspace_memory_region *region);
+void gh_vm_mem_mapping_reclaim(struct gunyah_vm *ghvm, struct gunyah_vm_memory_mapping *mapping);
+struct gunyah_vm_memory_mapping *gh_vm_mem_mapping_find(struct gunyah_vm *ghvm, u32 label);
+
#endif
diff --git a/drivers/virt/gunyah/vm_mgr_mm.c b/drivers/virt/gunyah/vm_mgr_mm.c
new file mode 100644
index 000000000000..800d35da14ba
--- /dev/null
+++ b/drivers/virt/gunyah/vm_mgr_mm.c
@@ -0,0 +1,221 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
+ */
+
+#define pr_fmt(fmt) "gh_vm_mgr: " fmt
+
+#include <linux/gunyah_rsc_mgr.h>
+#include <linux/mm.h>
+
+#include <uapi/linux/gunyah.h>
+
+#include "vm_mgr.h"
+
+static inline bool page_contiguous(phys_addr_t p, phys_addr_t t)
+{
+ return t - p == PAGE_SIZE;
+}
+
+static struct gunyah_vm_memory_mapping *__gh_vm_mem_mapping_find(struct gunyah_vm *ghvm, u32 label)
+{
+ struct gunyah_vm_memory_mapping *mapping;
+
+
+ list_for_each_entry(mapping, &ghvm->memory_mappings, list)
+ if (mapping->parcel.label == label)
+ return mapping;
+
+ return NULL;
+}
+
+void gh_vm_mem_mapping_reclaim(struct gunyah_vm *ghvm, struct gunyah_vm_memory_mapping *mapping)
+{
+ int i, ret = 0;
+
+ if (mapping->parcel.mem_handle != GH_MEM_HANDLE_INVAL) {
+ ret = gh_rm_mem_reclaim(ghvm->rm, &mapping->parcel);
+ if (ret)
+ pr_warn("Failed to reclaim memory parcel for label %d: %d\n",
+ mapping->parcel.label, ret);
+ }
+
+ if (!ret)
+ for (i = 0; i < mapping->npages; i++)
+ unpin_user_page(mapping->pages[i]);
+
+ kfree(mapping->pages);
+ kfree(mapping->parcel.acl_entries);
+ kfree(mapping->parcel.mem_entries);
+
+ mutex_lock(&ghvm->mm_lock);
+ list_del(&mapping->list);
+ mutex_unlock(&ghvm->mm_lock);
+}
+
+struct gunyah_vm_memory_mapping *gh_vm_mem_mapping_find(struct gunyah_vm *ghvm, u32 label)
+{
+ struct gunyah_vm_memory_mapping *mapping;
+ int ret;
+
+ ret = mutex_lock_interruptible(&ghvm->mm_lock);
+ if (ret)
+ return ERR_PTR(ret);
+ mapping = __gh_vm_mem_mapping_find(ghvm, label);
+ mutex_unlock(&ghvm->mm_lock);
+ return mapping;
+}
+
+struct gunyah_vm_memory_mapping *gh_vm_mem_mapping_alloc(struct gunyah_vm *ghvm,
+ struct gh_userspace_memory_region *region)
+{
+ phys_addr_t curr_page, prev_page;
+ struct gunyah_vm_memory_mapping *mapping, *tmp_mapping;
+ struct gh_rm_mem_entry *mem_entries;
+ int i, j, pinned, ret = 0;
+ struct gh_rm_mem_parcel *parcel;
+
+ if (!region->memory_size || !PAGE_ALIGNED(region->memory_size) ||
+ !PAGE_ALIGNED(region->userspace_addr))
+ return ERR_PTR(-EINVAL);
+
+ ret = mutex_lock_interruptible(&ghvm->mm_lock);
+ if (ret)
+ return ERR_PTR(ret);
+ mapping = __gh_vm_mem_mapping_find(ghvm, region->label);
+ if (mapping) {
+ ret = -EEXIST;
+ goto unlock;
+ }
+
+ mapping = kzalloc(sizeof(*mapping), GFP_KERNEL);
+ if (!mapping) {
+ ret = -ENOMEM;
+ goto unlock;
+ }
+
+ mapping->parcel.label = region->label;
+ mapping->guest_phys_addr = region->guest_phys_addr;
+ mapping->npages = region->memory_size >> PAGE_SHIFT;
+ parcel = &mapping->parcel;
+ parcel->mem_handle = GH_MEM_HANDLE_INVAL; /* to be filled later by mem_share/mem_lend */
+ parcel->mem_type = GH_RM_MEM_TYPE_NORMAL;
+
+ /* Check for overlap */
+ list_for_each_entry(tmp_mapping, &ghvm->memory_mappings, list) {
+ if (!((mapping->guest_phys_addr + (mapping->npages << PAGE_SHIFT) <=
+ tmp_mapping->guest_phys_addr) ||
+ (mapping->guest_phys_addr >=
+ tmp_mapping->guest_phys_addr + (tmp_mapping->npages << PAGE_SHIFT)))) {
+ ret = -EEXIST;
+ goto unlock;
+ }
+ }
+
+ list_add(&mapping->list, &ghvm->memory_mappings);
+unlock:
+ mutex_unlock(&ghvm->mm_lock);
+ if (ret)
+ goto free_mapping;
+
+ mapping->pages = kcalloc(mapping->npages, sizeof(*mapping->pages), GFP_KERNEL);
+ if (!mapping->pages) {
+ ret = -ENOMEM;
+ goto reclaim;
+ }
+
+ pinned = pin_user_pages_fast(region->userspace_addr, mapping->npages,
+ FOLL_WRITE | FOLL_LONGTERM, mapping->pages);
+ if (pinned < 0) {
+ ret = pinned;
+ goto reclaim;
+ } else if (pinned != mapping->npages) {
+ ret = -EFAULT;
+ mapping->npages = pinned; /* update npages for reclaim */
+ goto reclaim;
+ }
+
+ if (region->flags & GH_MEM_LENT) {
+ parcel->n_acl_entries = 1;
+ mapping->share_type = VM_MEM_LEND;
+ } else {
+ parcel->n_acl_entries = 2;
+ mapping->share_type = VM_MEM_SHARE;
+ }
+ parcel->acl_entries = kcalloc(parcel->n_acl_entries,
+ sizeof(*parcel->acl_entries),
+ GFP_KERNEL);
+ if (!parcel->acl_entries) {
+ ret = -ENOMEM;
+ goto reclaim;
+ }
+
+ parcel->acl_entries[0].vmid = ghvm->vmid;
+ if (region->flags & GH_MEM_ALLOW_READ)
+ parcel->acl_entries[0].perms |= GH_RM_ACL_R;
+ if (region->flags & GH_MEM_ALLOW_WRITE)
+ parcel->acl_entries[0].perms |= GH_RM_ACL_W;
+ if (region->flags & GH_MEM_ALLOW_EXEC)
+ parcel->acl_entries[0].perms |= GH_RM_ACL_X;
+
+ if (mapping->share_type == VM_MEM_SHARE) {
+ ret = gh_rm_get_vmid(ghvm->rm, &parcel->acl_entries[1].vmid);
+ if (ret)
+ goto reclaim;
+ /* Host assumed to have all these permissions. Gunyah will not
+ * grant new permissions if host actually had less than RWX
+ */
+ parcel->acl_entries[1].perms |= GH_RM_ACL_R | GH_RM_ACL_W | GH_RM_ACL_X;
+ }
+
+ mem_entries = kcalloc(mapping->npages, sizeof(*mem_entries), GFP_KERNEL);
+ if (!mem_entries) {
+ ret = -ENOMEM;
+ goto reclaim;
+ }
+
+ // reduce number of entries by combining contiguous pages into single memory entry
+ prev_page = mem_entries[0].ipa_base = page_to_phys(mapping->pages[0]);
+ mem_entries[0].size = PAGE_SIZE;
+ for (i = 1, j = 0; i < mapping->npages; i++) {
+ curr_page = page_to_phys(mapping->pages[i]);
+ if (page_contiguous(prev_page, curr_page)) {
+ mem_entries[j].size += PAGE_SIZE;
+ } else {
+ j++;
+ mem_entries[j].ipa_base = curr_page;
+ mem_entries[j].size = PAGE_SIZE;
+ }
+
+ prev_page = curr_page;
+ }
+
+ parcel->n_mem_entries = j + 1;
+ parcel->mem_entries = kmemdup(mem_entries, sizeof(*mem_entries) * parcel->n_mem_entries,
+ GFP_KERNEL);
+ kfree(mem_entries);
+ if (!parcel->mem_entries) {
+ ret = -ENOMEM;
+ goto reclaim;
+ }
+
+ switch (mapping->share_type) {
+ case VM_MEM_LEND:
+ ret = gh_rm_mem_lend(ghvm->rm, parcel);
+ break;
+ case VM_MEM_SHARE:
+ ret = gh_rm_mem_share(ghvm->rm, parcel);
+ break;
+ }
+ if (ret > 0)
+ ret = -EINVAL;
+ if (ret)
+ goto reclaim;
+
+ return mapping;
+reclaim:
+ gh_vm_mem_mapping_reclaim(ghvm, mapping);
+free_mapping:
+ kfree(mapping);
+ return ERR_PTR(ret);
+}
diff --git a/include/uapi/linux/gunyah.h b/include/uapi/linux/gunyah.h
index 88a40d6e0b96..574f33b198d0 100644
--- a/include/uapi/linux/gunyah.h
+++ b/include/uapi/linux/gunyah.h
@@ -20,4 +20,26 @@
*/
#define GH_CREATE_VM _IO(GH_IOCTL_TYPE, 0x0) /* Returns a Gunyah VM fd */
+/*
+ * ioctls for VM fds
+ */
+struct gh_userspace_memory_region {
+ __u32 label;
+#define GH_MEM_ALLOW_READ (1UL << 0)
+#define GH_MEM_ALLOW_WRITE (1UL << 1)
+#define GH_MEM_ALLOW_EXEC (1UL << 2)
+/*
+ * The guest will be lent the memory instead of shared.
+ * In other words, the guest has exclusive access to the memory region and the host loses access.
+ */
+#define GH_MEM_LENT (1UL << 3)
+ __u32 flags;
+ __u64 guest_phys_addr;
+ __u64 memory_size;
+ __u64 userspace_addr;
+};
+
+#define GH_VM_SET_USER_MEM_REGION _IOW(GH_IOCTL_TYPE, 0x1, \
+ struct gh_userspace_memory_region)
+
#endif
--
2.25.1
Export the version of Gunyah which is reported via the hyp_identify
hypercall. Increments of the major API version indicate possibly
backwards incompatible changes.
Export the hypervisor identity so that Gunyah drivers can act according
to the major API version.
Signed-off-by: Elliot Berman <[email protected]>
---
drivers/virt/Makefile | 1 +
drivers/virt/gunyah/Makefile | 1 +
drivers/virt/gunyah/gunyah.c | 46 ++++++++++++++++++++++++++++++++++++
include/linux/gunyah.h | 6 +++++
4 files changed, 54 insertions(+)
create mode 100644 drivers/virt/gunyah/Makefile
create mode 100644 drivers/virt/gunyah/gunyah.c
diff --git a/drivers/virt/Makefile b/drivers/virt/Makefile
index 093674e05c40..92b7e5311548 100644
--- a/drivers/virt/Makefile
+++ b/drivers/virt/Makefile
@@ -11,3 +11,4 @@ obj-$(CONFIG_NITRO_ENCLAVES) += nitro_enclaves/
obj-$(CONFIG_ACRN_HSM) += acrn/
obj-$(CONFIG_EFI_SECRET) += coco/efi_secret/
obj-$(CONFIG_SEV_GUEST) += coco/sev-guest/
+obj-y += gunyah/
diff --git a/drivers/virt/gunyah/Makefile b/drivers/virt/gunyah/Makefile
new file mode 100644
index 000000000000..2ac4ee64b89d
--- /dev/null
+++ b/drivers/virt/gunyah/Makefile
@@ -0,0 +1 @@
+obj-$(CONFIG_GUNYAH) += gunyah.o
diff --git a/drivers/virt/gunyah/gunyah.c b/drivers/virt/gunyah/gunyah.c
new file mode 100644
index 000000000000..c34c9046fc08
--- /dev/null
+++ b/drivers/virt/gunyah/gunyah.c
@@ -0,0 +1,46 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
+ */
+
+#define pr_fmt(fmt) "gunyah: " fmt
+
+#include <linux/gunyah.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/printk.h>
+
+struct gh_hypercall_hyp_identify_resp gunyah_api;
+EXPORT_SYMBOL_GPL(gunyah_api);
+
+static const uint32_t gunyah_known_uuids[][4] = {
+ {0x19bd54bd, 0x0b37571b, 0x946f609b, 0x54539de6}, /* QC_HYP (Qualcomm's build) */
+ {0x673d5f14, 0x9265ce36, 0xa4535fdb, 0xc1d58fcd}, /* GUNYAH (open source build) */
+};
+
+static int __init gunyah_init(void)
+{
+ u32 uid[4];
+ int i;
+
+ gh_hypercall_get_uid(uid);
+
+ for (i = 0; i < ARRAY_SIZE(gunyah_known_uuids); i++)
+ if (!memcmp(uid, gunyah_known_uuids[i], sizeof(uid)))
+ break;
+
+ if (i == ARRAY_SIZE(gunyah_known_uuids))
+ return -ENODEV;
+
+ gh_hypercall_hyp_identify(&gunyah_api);
+
+ pr_info("Running under Gunyah hypervisor %llx/v%u\n",
+ FIELD_GET(GH_API_INFO_VARIANT_MASK, gunyah_api.api_info),
+ gh_api_version());
+
+ return 0;
+}
+arch_initcall(gunyah_init);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Gunyah Hypervisor Driver");
diff --git a/include/linux/gunyah.h b/include/linux/gunyah.h
index 2765d2b40198..166156f69df9 100644
--- a/include/linux/gunyah.h
+++ b/include/linux/gunyah.h
@@ -92,6 +92,12 @@ struct gh_hypercall_hyp_identify_resp {
u64 api_info;
u64 flags[3];
};
+extern struct gh_hypercall_hyp_identify_resp gunyah_api;
+
+static inline u16 gh_api_version(void)
+{
+ return FIELD_GET(GH_API_INFO_API_VERSION_MASK, gunyah_api.api_info);
+}
void gh_hypercall_get_uid(u32 uid[4]);
void gh_hypercall_hyp_identify(struct gh_hypercall_hyp_identify_resp *hyp_identity);
--
2.25.1
The resource manager is a special virtual machine which is always
running on a Gunyah system. It provides APIs for creating and destroying
VMs, secure memory management, sharing/lending of memory between VMs,
and setup of inter-VM communication. Calls to the resource manager are
made via message queues.
This patch implements the basic probing and RPC mechanism to make those
API calls. Request/response calls can be made with gh_rm_call.
Drivers can also register to notifications pushed by RM via
gh_rm_register_notifier
Specific API calls that resource manager supports will be implemented in
subsequent patches.
Signed-off-by: Elliot Berman <[email protected]>
---
MAINTAINERS | 2 +-
drivers/virt/gunyah/Kconfig | 14 +
drivers/virt/gunyah/Makefile | 3 +
drivers/virt/gunyah/rsc_mgr.c | 584 +++++++++++++++++++++++++++++++++
drivers/virt/gunyah/rsc_mgr.h | 36 ++
include/linux/gunyah_rsc_mgr.h | 18 +
6 files changed, 656 insertions(+), 1 deletion(-)
create mode 100644 drivers/virt/gunyah/rsc_mgr.c
create mode 100644 drivers/virt/gunyah/rsc_mgr.h
create mode 100644 include/linux/gunyah_rsc_mgr.h
diff --git a/MAINTAINERS b/MAINTAINERS
index 667480bfd387..bb315385e155 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -8944,7 +8944,7 @@ F: Documentation/virt/gunyah/
F: arch/arm64/gunyah/
F: drivers/mailbox/gunyah-msgq.c
F: drivers/virt/gunyah/
-F: include/linux/gunyah.h
+F: include/linux/gunyah*.h
HABANALABS PCI DRIVER
M: Oded Gabbay <[email protected]>
diff --git a/drivers/virt/gunyah/Kconfig b/drivers/virt/gunyah/Kconfig
index 127156a678a6..4a6189dedab2 100644
--- a/drivers/virt/gunyah/Kconfig
+++ b/drivers/virt/gunyah/Kconfig
@@ -10,3 +10,17 @@ config GUNYAH
Say Y/M here to enable the drivers needed to interact in a Gunyah
virtual environment.
+
+if GUNYAH
+config GUNYAH_RESOURCE_MANAGER
+ tristate "Gunyah Resource Manager"
+ depends on MAILBOX
+ select GUNYAH_MESSAGE_QUEUES
+ help
+ The resource manager (RM) is a privileged application VM supporting
+ the Gunyah Hypervisor. Enable this driver to support communicating
+ with Gunyah RM. This is typically required for a VM running under
+ Gunyah wanting to have Gunyah-awareness.
+
+ Say Y/M here if unsure.
+endif
diff --git a/drivers/virt/gunyah/Makefile b/drivers/virt/gunyah/Makefile
index 2ac4ee64b89d..e7cf59b9e64e 100644
--- a/drivers/virt/gunyah/Makefile
+++ b/drivers/virt/gunyah/Makefile
@@ -1 +1,4 @@
obj-$(CONFIG_GUNYAH) += gunyah.o
+
+gunyah_rsc_mgr-y += rsc_mgr.o
+obj-$(CONFIG_GUNYAH_RESOURCE_MANAGER) += gunyah_rsc_mgr.o
diff --git a/drivers/virt/gunyah/rsc_mgr.c b/drivers/virt/gunyah/rsc_mgr.c
new file mode 100644
index 000000000000..6b281576f5af
--- /dev/null
+++ b/drivers/virt/gunyah/rsc_mgr.c
@@ -0,0 +1,584 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
+ */
+
+#include <linux/of.h>
+#include <linux/slab.h>
+#include <linux/mutex.h>
+#include <linux/sched.h>
+#include <linux/gunyah.h>
+#include <linux/module.h>
+#include <linux/of_irq.h>
+#include <linux/kthread.h>
+#include <linux/notifier.h>
+#include <linux/workqueue.h>
+#include <linux/completion.h>
+#include <linux/gunyah_rsc_mgr.h>
+#include <linux/platform_device.h>
+
+#include "rsc_mgr.h"
+
+/* Resource Manager Header */
+struct gh_rm_rpc_hdr {
+#define RM_RPC_HDR_VERSION_ONE 0x1
+#define RM_RPC_API_VERSION_MASK GENMASK(3, 0)
+
+#define RM_RPC_HDR_WORDS 0x2
+#define RM_RPC_HEADER_WORDS_MASK GENMASK(7, 4)
+ u8 api;
+
+#define RM_RPC_TYPE_CONT 0x0
+#define RM_RPC_TYPE_REQ 0x1
+#define RM_RPC_TYPE_RPLY 0x2
+#define RM_RPC_TYPE_NOTIF 0x3
+#define RM_RPC_TYPE_MASK GENMASK(1, 0)
+
+#define GH_RM_MAX_NUM_FRAGMENTS 62
+#define RM_RPC_FRAGMENTS_MASK GENMASK(7, 2)
+ u8 type;
+ __le16 seq;
+ __le32 msg_id;
+} __packed;
+
+/* Standard reply header */
+struct gh_rm_rpc_reply_hdr {
+ struct gh_rm_rpc_hdr rpc_hdr;
+ u32 err_code;
+} __packed;
+
+#define GH_RM_MAX_MSG_SIZE (GH_MSGQ_MAX_MSG_SIZE - sizeof(struct gh_rm_rpc_hdr))
+
+/**
+ * struct gh_rm_connection - Represents a complete message from resource manager
+ * @payload: Combined payload of all the fragments (msg headers stripped off).
+ * @size: Size of the payload.
+ * @ret: Linux return code, set in case there was an error processing connection
+ * @msg_id: Message ID from the header.
+ * @type: RM_RPC_TYPE_RPLY or RM_RPC_TYPE_NOTIF.
+ * @num_fragments: total number of fragments expected to be received.
+ * @fragments_received: fragments received so far.
+ * @rm_error: For request/reply sequences with standard replies.
+ * @seq: Sequence ID for the main message.
+ * @seq_done: Signals caller that the RM reply has been received
+ */
+struct gh_rm_connection {
+ void *payload;
+ size_t size;
+ int ret;
+ u32 msg_id;
+ u8 type;
+
+ u8 num_fragments;
+ u8 fragments_received;
+
+ /* only for req/reply sequence */
+ u32 rm_error;
+ u16 seq;
+ struct completion seq_done;
+};
+
+struct gh_rm_notif_complete {
+ struct gh_rm_connection *conn;
+ struct work_struct work;
+};
+
+struct gh_rm_rpc {
+ struct device *dev;
+ struct gunyah_resource tx_ghrsc, rx_ghrsc;
+ struct gh_msgq msgq;
+ struct mbox_client msgq_client;
+ struct gh_rm_connection *active_rx_connection;
+ int last_tx_ret;
+
+ struct idr call_idr;
+ struct mutex call_idr_lock;
+
+ struct mutex send_lock;
+
+ struct work_struct recv_work;
+};
+
+static struct gh_rm_connection *gh_rm_alloc_connection(u32 msg_id, u8 type)
+{
+ struct gh_rm_connection *connection;
+
+ connection = kzalloc(sizeof(*connection), GFP_KERNEL);
+ if (!connection)
+ return NULL;
+
+ connection->type = type;
+ connection->msg_id = msg_id;
+
+ return connection;
+}
+
+static int gh_rm_init_connection_payload(struct gh_rm_connection *connection, void *msg,
+ size_t hdr_size, size_t msg_size)
+{
+ struct gh_rm_rpc_hdr *hdr = msg;
+ size_t max_buf_size, payload_size;
+
+ if (hdr_size > msg_size)
+ return -EINVAL;
+
+ payload_size = msg_size - hdr_size;
+
+ connection->num_fragments = FIELD_GET(RM_RPC_FRAGMENTS_MASK, hdr->type);
+ connection->fragments_received = 0;
+
+ /* There's not going to be any payload, no need to allocate buffer. */
+ if (!payload_size && !connection->num_fragments)
+ return 0;
+
+ /*
+ * maximum payload size is GH_MSGQ_MAX_MSG_SIZE - hdr_size
+ * and can received (hdr->fragments + 1) of those
+ */
+ max_buf_size = (GH_MSGQ_MAX_MSG_SIZE - hdr_size) * (connection->num_fragments + 1);
+
+ connection->payload = kzalloc(max_buf_size, GFP_KERNEL);
+ if (!connection->payload)
+ return -ENOMEM;
+
+ memcpy(connection->payload, msg + hdr_size, payload_size);
+ connection->size = payload_size;
+ return 0;
+}
+
+static void gh_rm_notif_work(struct work_struct *work)
+{
+ struct gh_rm_notif_complete *notif = container_of(work, struct gh_rm_notif_complete, work);
+ struct gh_rm_connection *connection = notif->conn;
+
+ /* No users of notifications, yet. */
+
+ kfree(connection->payload);
+ kfree(connection);
+ kfree(notif);
+}
+
+static struct gh_rm_connection *gh_rm_process_notif(struct gh_rm_rpc *rsc_mgr,
+ void *msg, size_t msg_size)
+{
+ struct gh_rm_rpc_hdr *hdr = msg;
+ struct gh_rm_connection *connection;
+
+ connection = gh_rm_alloc_connection(le32_to_cpu(hdr->msg_id),
+ FIELD_GET(RM_RPC_TYPE_MASK, hdr->type));
+ if (!connection) {
+ dev_err(rsc_mgr->dev, "Failed to alloc connection for notification, dropping.\n");
+ return NULL;
+ }
+
+ if (gh_rm_init_connection_payload(connection, msg, sizeof(*hdr), msg_size)) {
+ dev_err(rsc_mgr->dev, "Failed to alloc connection buffer for notification, dropping.\n");
+ kfree(connection);
+ return NULL;
+ }
+
+ return connection;
+}
+
+static struct gh_rm_connection *gh_rm_process_rply(struct gh_rm_rpc *rsc_mgr,
+ void *msg, size_t msg_size)
+{
+ struct gh_rm_rpc_reply_hdr *reply_hdr = msg;
+ struct gh_rm_rpc_hdr *hdr = msg;
+ struct gh_rm_connection *connection;
+ u16 seq_id = le16_to_cpu(hdr->seq);
+
+ if (mutex_lock_interruptible(&rsc_mgr->call_idr_lock))
+ return ERR_PTR(-ERESTARTSYS);
+
+ connection = idr_find(&rsc_mgr->call_idr, seq_id);
+ mutex_unlock(&rsc_mgr->call_idr_lock);
+
+ if (!connection) {
+ dev_err(rsc_mgr->dev, "Failed to find connection for sequence %u\n", seq_id);
+ return NULL;
+ }
+ if (connection->msg_id != le32_to_cpu(hdr->msg_id)) {
+ dev_err(rsc_mgr->dev, "Reply for sequence %u expected msg_id: %x but got %x\n",
+ seq_id, connection->msg_id, le32_to_cpu(hdr->msg_id));
+ /*
+ * Don't complete connection and error the client, maybe
+ * resource manager will send us the expected reply sequence soon.
+ */
+ return NULL;
+ }
+
+ if (gh_rm_init_connection_payload(connection, msg, sizeof(*reply_hdr), msg_size)) {
+ dev_err(rsc_mgr->dev, "Failed to alloc connection buffer for sequence %d\n",
+ seq_id);
+ /* Send connection complete and error the client. */
+ connection->ret = -ENOMEM;
+ complete(&connection->seq_done);
+ return NULL;
+ }
+
+ connection->rm_error = reply_hdr->err_code;
+ return connection;
+}
+
+static void gh_rm_process_cont(struct gh_rm_rpc *rsc_mgr, struct gh_rm_connection *connection,
+ void *msg, size_t msg_size)
+{
+ struct gh_rm_rpc_hdr *hdr = msg;
+ size_t payload_size = msg_size - sizeof(*hdr);
+
+ /*
+ * hdr->fragments and hdr->msg_id preserves the value from first reply
+ * or notif message. To detect mishandling, check it's still intact.
+ */
+ if (connection->msg_id != le32_to_cpu(hdr->msg_id))
+ dev_err(rsc_mgr->dev, "Appending mismatched continuation with id %d to connection with id %d\n",
+ le32_to_cpu(hdr->msg_id), connection->msg_id);
+ if (connection->num_fragments != FIELD_GET(RM_RPC_FRAGMENTS_MASK, hdr->type))
+ dev_err(rsc_mgr->dev, "Number of fragments mismatch for seq: %d\n",
+ le16_to_cpu(hdr->seq));
+
+ memcpy(connection->payload + connection->size, msg + sizeof(*hdr), payload_size);
+ connection->size += payload_size;
+ connection->fragments_received++;
+}
+
+static bool gh_rm_complete_connection(struct gh_rm_rpc *rsc_mgr,
+ struct gh_rm_connection *connection)
+{
+ struct gh_rm_notif_complete *notif_work;
+
+ if (!connection)
+ return false;
+
+ if (connection->fragments_received != connection->num_fragments)
+ return false;
+
+ switch (connection->type) {
+ case RM_RPC_TYPE_RPLY:
+ complete(&connection->seq_done);
+ break;
+ case RM_RPC_TYPE_NOTIF:
+ notif_work = kzalloc(sizeof(*notif_work), GFP_KERNEL);
+ if (notif_work == NULL)
+ break;
+
+ notif_work->conn = connection;
+ INIT_WORK(¬if_work->work, gh_rm_notif_work);
+
+ schedule_work(¬if_work->work);
+ break;
+ default:
+ dev_err(rsc_mgr->dev, "Invalid message type (%d) received\n", connection->type);
+ break;
+ }
+
+ return true;
+}
+
+static void gh_rm_abort_connection(struct gh_rm_connection *connection)
+{
+ switch (connection->type) {
+ case RM_RPC_TYPE_RPLY:
+ connection->ret = -EIO;
+ complete(&connection->seq_done);
+ break;
+ case RM_RPC_TYPE_NOTIF:
+ fallthrough;
+ default:
+ kfree(connection->payload);
+ kfree(connection);
+ }
+}
+
+static void gh_rm_msgq_rx_data(struct mbox_client *cl, void *mssg)
+{
+ struct gh_rm_rpc *rsc_mgr = container_of(cl, struct gh_rm_rpc, msgq_client);
+ struct gh_msgq_rx_data *rx_data = mssg;
+ void *msg = rx_data->data;
+ size_t msg_size = rx_data->length;
+ struct gh_rm_rpc_hdr *hdr;
+
+ if (msg_size <= sizeof(struct gh_rm_rpc_hdr)) {
+ dev_err(rsc_mgr->dev, "Incomplete message size: %ld is too small\n", msg_size);
+ return;
+ }
+
+ hdr = msg;
+ switch (FIELD_GET(RM_RPC_TYPE_MASK, hdr->type)) {
+ case RM_RPC_TYPE_NOTIF:
+ if (rsc_mgr->active_rx_connection) {
+ /* Not possible per protocol. Do something better than BUG_ON */
+ dev_err(rsc_mgr->dev, "Received start of new notification without finishing existing message series.\n");
+ gh_rm_abort_connection(rsc_mgr->active_rx_connection);
+ }
+ rsc_mgr->active_rx_connection = gh_rm_process_notif(rsc_mgr, msg, msg_size);
+ break;
+ case RM_RPC_TYPE_RPLY:
+ if (rsc_mgr->active_rx_connection) {
+ /* Not possible per protocol. Do something better than BUG_ON */
+ dev_err(rsc_mgr->dev, "Received start of new reply without finishing existing message series.\n");
+ gh_rm_abort_connection(rsc_mgr->active_rx_connection);
+ }
+ rsc_mgr->active_rx_connection = gh_rm_process_rply(rsc_mgr, msg, msg_size);
+ break;
+ case RM_RPC_TYPE_CONT:
+ if (!rsc_mgr->active_rx_connection) {
+ dev_err(rsc_mgr->dev, "Received a continuation message without receiving initial message\n");
+ break;
+ }
+ gh_rm_process_cont(rsc_mgr, rsc_mgr->active_rx_connection, msg, msg_size);
+ break;
+ default:
+ dev_err(rsc_mgr->dev, "Invalid message type (%lu) received\n",
+ FIELD_GET(RM_RPC_TYPE_MASK, hdr->type));
+ return;
+ }
+
+ if (gh_rm_complete_connection(rsc_mgr, rsc_mgr->active_rx_connection))
+ rsc_mgr->active_rx_connection = NULL;
+}
+
+static void gh_rm_msgq_tx_done(struct mbox_client *cl, void *mssg, int r)
+{
+ struct gh_rm_rpc *rsc_mgr = container_of(cl, struct gh_rm_rpc, msgq_client);
+
+ kfree(mssg);
+ rsc_mgr->last_tx_ret = r;
+}
+
+static int gh_rm_send_request(struct gh_rm_rpc *rsc_mgr, u32 message_id,
+ const void *req_buff, size_t req_buff_size,
+ struct gh_rm_connection *connection)
+{
+ size_t buff_size_remaining = req_buff_size;
+ const void *req_buff_curr = req_buff;
+ struct gh_rm_rpc_hdr *hdr;
+ u32 cont_fragments = 0;
+ size_t payload_size;
+ struct gh_msgq_tx_data *msg;
+ int i, ret;
+
+ if (req_buff_size)
+ cont_fragments = (req_buff_size - 1) / GH_RM_MAX_MSG_SIZE;
+
+ if (WARN(cont_fragments > GH_RM_MAX_NUM_FRAGMENTS,
+ "Limit exceeded for the number of fragments: %u\n", cont_fragments))
+ return -E2BIG;
+
+ ret = mutex_lock_interruptible(&rsc_mgr->send_lock);
+ if (ret)
+ return ret;
+
+ /* Consider also the 'request' packet for the loop count */
+ for (i = 0; i <= cont_fragments; i++) {
+ if (buff_size_remaining > GH_RM_MAX_MSG_SIZE) {
+ payload_size = GH_RM_MAX_MSG_SIZE;
+ buff_size_remaining -= payload_size;
+ } else {
+ payload_size = buff_size_remaining;
+ }
+
+ msg = kzalloc(sizeof(*msg) + GH_MSGQ_MAX_MSG_SIZE, GFP_KERNEL);
+ if (!msg) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ /* Fill header */
+ hdr = (struct gh_rm_rpc_hdr *)msg->data;
+ hdr->api = FIELD_PREP(RM_RPC_API_VERSION_MASK, RM_RPC_HDR_VERSION_ONE) |
+ FIELD_PREP(RM_RPC_HEADER_WORDS_MASK, RM_RPC_HDR_WORDS);
+ hdr->type = FIELD_PREP(RM_RPC_TYPE_MASK,
+ i == 0 ? RM_RPC_TYPE_REQ : RM_RPC_TYPE_CONT) |
+ FIELD_PREP(RM_RPC_FRAGMENTS_MASK, cont_fragments);
+ hdr->seq = cpu_to_le16(connection->seq);
+ hdr->msg_id = cpu_to_le32(message_id);
+
+ /* Copy payload */
+ memcpy(msg->data + sizeof(*hdr), req_buff_curr, payload_size);
+ req_buff_curr += payload_size;
+
+ /* Force the last fragment to immediately alert the receiver */
+ msg->push = i == cont_fragments;
+ msg->length = sizeof(*hdr) + payload_size;
+
+ ret = mbox_send_message(gh_msgq_chan(&rsc_mgr->msgq), msg);
+ if (ret < 0) {
+ kfree(msg);
+ break;
+ }
+
+ if (rsc_mgr->last_tx_ret) {
+ ret = rsc_mgr->last_tx_ret;
+ break;
+ }
+ }
+
+out:
+ mutex_unlock(&rsc_mgr->send_lock);
+ return ret < 0 ? ret : 0;
+}
+
+/**
+ * gh_rm_call: Achieve request-response type communication with RPC
+ * @message_id: The RM RPC message-id
+ * @req_buff: Request buffer that contains the payload
+ * @req_buff_size: Total size of the payload
+ * @resp_buf: Pointer to a response buffer
+ * @resp_buff_size: Size of the response buffer
+ *
+ * Make a request to the RM-VM and wait for reply back. For a successful
+ * response, the function returns the payload. The size of the payload is set in
+ * resp_buff_size. The resp_buf should be freed by the caller.
+ *
+ * Context: Process context. Will sleep waiting for reply.
+ * Return: >0 is standard reply error from RM. <0 on internal error.
+ */
+int gh_rm_call(struct gh_rm_rpc *rsc_mgr, u32 message_id, void *req_buff, size_t req_buff_size,
+ void **resp_buf, size_t *resp_buff_size)
+{
+ struct gh_rm_connection *connection;
+ int ret;
+
+ /* messaged_id 0 is reserved */
+ if (!message_id)
+ return -EINVAL;
+
+ if (!rsc_mgr)
+ return -EPROBE_DEFER;
+
+ connection = gh_rm_alloc_connection(message_id, RM_RPC_TYPE_RPLY);
+ if (!connection)
+ return -ENOMEM;
+
+ init_completion(&connection->seq_done);
+
+ /* Allocate a new seq number for this connection */
+ if (mutex_lock_interruptible(&rsc_mgr->call_idr_lock)) {
+ kfree(connection);
+ return -ERESTARTSYS;
+ }
+ connection->seq = idr_alloc_cyclic(&rsc_mgr->call_idr, connection, 0, U16_MAX, GFP_KERNEL);
+ mutex_unlock(&rsc_mgr->call_idr_lock);
+
+ /* Send the request to the Resource Manager */
+ ret = gh_rm_send_request(rsc_mgr, message_id, req_buff, req_buff_size, connection);
+ if (ret < 0)
+ goto out;
+
+ /* Wait for response */
+ ret = wait_for_completion_interruptible(&connection->seq_done);
+ if (ret)
+ goto out;
+
+ if (connection->ret) {
+ ret = connection->ret;
+ kfree(connection->payload);
+ goto out;
+ }
+
+ if (connection->rm_error) {
+ ret = connection->rm_error;
+ kfree(connection->payload);
+ goto out;
+ }
+
+ *resp_buf = connection->payload;
+ *resp_buff_size = connection->size;
+
+out:
+ mutex_lock(&rsc_mgr->call_idr_lock);
+ idr_remove(&rsc_mgr->call_idr, connection->seq);
+ mutex_unlock(&rsc_mgr->call_idr_lock);
+
+ kfree(connection);
+ return ret;
+}
+
+static int gh_msgq_platform_probe_direction(struct platform_device *pdev,
+ u8 gh_type, int idx, struct gunyah_resource *ghrsc)
+{
+ int ret;
+ struct device_node *node = pdev->dev.of_node;
+
+ ghrsc->type = gh_type;
+
+ ghrsc->irq = platform_get_irq(pdev, idx);
+ if (ghrsc->irq < 0) {
+ dev_err(&pdev->dev, "Failed to get irq%d: %d\n", idx, ghrsc->irq);
+ return ghrsc->irq;
+ }
+
+ ret = of_property_read_u64_index(node, "reg", idx, &ghrsc->capid);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to get capid%d: %d\n", idx, ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int gh_rm_drv_probe(struct platform_device *pdev)
+{
+ struct gh_rm_rpc *rsc_mgr;
+ int ret;
+
+ rsc_mgr = devm_kzalloc(&pdev->dev, sizeof(*rsc_mgr), GFP_KERNEL);
+ if (!rsc_mgr)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, rsc_mgr);
+ rsc_mgr->dev = &pdev->dev;
+
+ mutex_init(&rsc_mgr->call_idr_lock);
+ idr_init(&rsc_mgr->call_idr);
+ mutex_init(&rsc_mgr->send_lock);
+
+ ret = gh_msgq_platform_probe_direction(pdev, GUNYAH_RESOURCE_TYPE_MSGQ_TX, 0,
+ &rsc_mgr->tx_ghrsc);
+ if (ret)
+ return ret;
+
+ ret = gh_msgq_platform_probe_direction(pdev, GUNYAH_RESOURCE_TYPE_MSGQ_RX, 1,
+ &rsc_mgr->rx_ghrsc);
+ if (ret)
+ return ret;
+
+ rsc_mgr->msgq_client.dev = &pdev->dev;
+ rsc_mgr->msgq_client.tx_block = true;
+ rsc_mgr->msgq_client.rx_callback = gh_rm_msgq_rx_data;
+ rsc_mgr->msgq_client.tx_done = gh_rm_msgq_tx_done;
+
+ return gh_msgq_init(&pdev->dev, &rsc_mgr->msgq, &rsc_mgr->msgq_client,
+ &rsc_mgr->tx_ghrsc, &rsc_mgr->rx_ghrsc);
+}
+
+static int gh_rm_drv_remove(struct platform_device *pdev)
+{
+ struct gh_rm_rpc *rm = platform_get_drvdata(pdev);
+
+ mbox_free_channel(gh_msgq_chan(&rm->msgq));
+ gh_msgq_remove(&rm->msgq);
+
+ return 0;
+}
+
+static const struct of_device_id gh_rm_of_match[] = {
+ { .compatible = "gunyah-resource-manager" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, gh_rm_of_match);
+
+static struct platform_driver gh_rm_driver = {
+ .probe = gh_rm_drv_probe,
+ .remove = gh_rm_drv_remove,
+ .driver = {
+ .name = "gh_rsc_mgr",
+ .of_match_table = gh_rm_of_match,
+ },
+};
+module_platform_driver(gh_rm_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Gunyah Resource Manager Driver");
diff --git a/drivers/virt/gunyah/rsc_mgr.h b/drivers/virt/gunyah/rsc_mgr.h
new file mode 100644
index 000000000000..b5bb36a7a4cc
--- /dev/null
+++ b/drivers/virt/gunyah/rsc_mgr.h
@@ -0,0 +1,36 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
+ */
+#ifndef __GH_RSC_MGR_PRIV_H
+#define __GH_RSC_MGR_PRIV_H
+
+#include <linux/gunyah.h>
+#include <linux/types.h>
+
+/* RM Error codes */
+#define GH_RM_ERROR_OK 0x0
+#define GH_RM_ERROR_UNIMPLEMENTED 0xFFFFFFFF
+#define GH_RM_ERROR_NOMEM 0x1
+#define GH_RM_ERROR_NORESOURCE 0x2
+#define GH_RM_ERROR_DENIED 0x3
+#define GH_RM_ERROR_INVALID 0x4
+#define GH_RM_ERROR_BUSY 0x5
+#define GH_RM_ERROR_ARGUMENT_INVALID 0x6
+#define GH_RM_ERROR_HANDLE_INVALID 0x7
+#define GH_RM_ERROR_VALIDATE_FAILED 0x8
+#define GH_RM_ERROR_MAP_FAILED 0x9
+#define GH_RM_ERROR_MEM_INVALID 0xA
+#define GH_RM_ERROR_MEM_INUSE 0xB
+#define GH_RM_ERROR_MEM_RELEASED 0xC
+#define GH_RM_ERROR_VMID_INVALID 0xD
+#define GH_RM_ERROR_LOOKUP_FAILED 0xE
+#define GH_RM_ERROR_IRQ_INVALID 0xF
+#define GH_RM_ERROR_IRQ_INUSE 0x10
+#define GH_RM_ERROR_IRQ_RELEASED 0x11
+
+struct gh_rm_rpc;
+int gh_rm_call(struct gh_rm_rpc *rsc_mgr, u32 message_id, void *req_buff, size_t req_buff_size,
+ void **resp_buf, size_t *resp_buff_size);
+
+#endif
diff --git a/include/linux/gunyah_rsc_mgr.h b/include/linux/gunyah_rsc_mgr.h
new file mode 100644
index 000000000000..b4f55c19954b
--- /dev/null
+++ b/include/linux/gunyah_rsc_mgr.h
@@ -0,0 +1,18 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
+ */
+
+#ifndef _GUNYAH_RSC_MGR_H
+#define _GUNYAH_RSC_MGR_H
+
+#include <linux/list.h>
+#include <linux/notifier.h>
+#include <linux/gunyah.h>
+
+#define GH_VMID_INVAL U16_MAX
+
+/* Gunyah recognizes VMID0 as an alias to the current VM's ID */
+#define GH_VMID_SELF 0
+
+#endif
--
2.25.1
Gunyah VM manager is a kernel moduel which exposes an interface to
Gunyah userspace to load, run, and interact with other Gunyah virtual
machines. The interface is a character device at /dev/gunyah.
Add a basic VM manager driver. Upcoming patches will add more ioctls
into this driver.
Co-developed-by: Prakruthi Deepak Heragu <[email protected]>
Signed-off-by: Prakruthi Deepak Heragu <[email protected]>
Signed-off-by: Elliot Berman <[email protected]>
---
.../userspace-api/ioctl/ioctl-number.rst | 1 +
drivers/virt/gunyah/Kconfig | 11 ++
drivers/virt/gunyah/Makefile | 1 +
drivers/virt/gunyah/rsc_mgr.c | 47 ++++++-
drivers/virt/gunyah/vm_mgr.c | 115 ++++++++++++++++++
drivers/virt/gunyah/vm_mgr.h | 25 ++++
include/linux/gunyah_rsc_mgr.h | 3 +
include/uapi/linux/gunyah.h | 23 ++++
8 files changed, 225 insertions(+), 1 deletion(-)
create mode 100644 drivers/virt/gunyah/vm_mgr.c
create mode 100644 drivers/virt/gunyah/vm_mgr.h
create mode 100644 include/uapi/linux/gunyah.h
diff --git a/Documentation/userspace-api/ioctl/ioctl-number.rst b/Documentation/userspace-api/ioctl/ioctl-number.rst
index 5f81e2a24a5c..81456c34c85d 100644
--- a/Documentation/userspace-api/ioctl/ioctl-number.rst
+++ b/Documentation/userspace-api/ioctl/ioctl-number.rst
@@ -136,6 +136,7 @@ Code Seq# Include File Comments
'F' DD video/sstfb.h conflict!
'G' 00-3F drivers/misc/sgi-gru/grulib.h conflict!
'G' 00-0F xen/gntalloc.h, xen/gntdev.h conflict!
+'G' 00-0f linux/gunyah.h conflict!
'H' 00-7F linux/hiddev.h conflict!
'H' 00-0F linux/hidraw.h conflict!
'H' 01 linux/mei.h conflict!
diff --git a/drivers/virt/gunyah/Kconfig b/drivers/virt/gunyah/Kconfig
index 4a6189dedab2..5e5a329c9866 100644
--- a/drivers/virt/gunyah/Kconfig
+++ b/drivers/virt/gunyah/Kconfig
@@ -23,4 +23,15 @@ config GUNYAH_RESOURCE_MANAGER
Gunyah wanting to have Gunyah-awareness.
Say Y/M here if unsure.
+
+config GUNYAH_VM_MANAGER
+ bool "Manage Gunyah Virtual Machines"
+ depends on MAILBOX
+ depends on GUNYAH_RESOURCE_MANAGER
+ help
+ Gunyah VM manager exposes an interface to Gunyah userspace to load,
+ run, and interact with other Gunyah virtual machines. This option is
+ required to launch other virtual machines.
+
+ Say Y here if unsure and you want to support Gunyah VMMs.
endif
diff --git a/drivers/virt/gunyah/Makefile b/drivers/virt/gunyah/Makefile
index 5caa05267a58..e817ae6deddc 100644
--- a/drivers/virt/gunyah/Makefile
+++ b/drivers/virt/gunyah/Makefile
@@ -1,4 +1,5 @@
obj-$(CONFIG_GUNYAH) += gunyah.o
gunyah_rsc_mgr-y += rsc_mgr.o rsc_mgr_rpc.o
+gunyah_rsc_mgr-$(CONFIG_GUNYAH_VM_MANAGER) += vm_mgr.o
obj-$(CONFIG_GUNYAH_RESOURCE_MANAGER) += gunyah_rsc_mgr.o
diff --git a/drivers/virt/gunyah/rsc_mgr.c b/drivers/virt/gunyah/rsc_mgr.c
index 6b281576f5af..49d7939bbe36 100644
--- a/drivers/virt/gunyah/rsc_mgr.c
+++ b/drivers/virt/gunyah/rsc_mgr.c
@@ -16,8 +16,10 @@
#include <linux/completion.h>
#include <linux/gunyah_rsc_mgr.h>
#include <linux/platform_device.h>
+#include <linux/miscdevice.h>
#include "rsc_mgr.h"
+#include "vm_mgr.h"
/* Resource Manager Header */
struct gh_rm_rpc_hdr {
@@ -96,6 +98,7 @@ struct gh_rm_rpc {
struct mutex send_lock;
+ struct miscdevice miscdev;
struct work_struct recv_work;
};
@@ -496,6 +499,33 @@ int gh_rm_call(struct gh_rm_rpc *rsc_mgr, u32 message_id, void *req_buff, size_t
return ret;
}
+void get_gh_rm(struct gh_rm_rpc *rm)
+{
+ get_device(rm->dev);
+}
+EXPORT_SYMBOL_GPL(get_gh_rm);
+
+void put_gh_rm(struct gh_rm_rpc *rm)
+{
+ put_device(rm->dev);
+}
+EXPORT_SYMBOL_GPL(put_gh_rm);
+
+static long gh_dev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
+{
+ struct miscdevice *miscdev = filp->private_data;
+ struct gh_rm_rpc *rm = container_of(miscdev, struct gh_rm_rpc, miscdev);
+
+ return gh_dev_vm_mgr_ioctl(rm, cmd, arg);
+}
+
+static const struct file_operations gh_dev_fops = {
+ .owner = THIS_MODULE,
+ .unlocked_ioctl = gh_dev_ioctl,
+ .compat_ioctl = compat_ptr_ioctl,
+ .llseek = noop_llseek,
+};
+
static int gh_msgq_platform_probe_direction(struct platform_device *pdev,
u8 gh_type, int idx, struct gunyah_resource *ghrsc)
{
@@ -550,14 +580,29 @@ static int gh_rm_drv_probe(struct platform_device *pdev)
rsc_mgr->msgq_client.rx_callback = gh_rm_msgq_rx_data;
rsc_mgr->msgq_client.tx_done = gh_rm_msgq_tx_done;
- return gh_msgq_init(&pdev->dev, &rsc_mgr->msgq, &rsc_mgr->msgq_client,
+ ret = gh_msgq_init(&pdev->dev, &rsc_mgr->msgq, &rsc_mgr->msgq_client,
&rsc_mgr->tx_ghrsc, &rsc_mgr->rx_ghrsc);
+
+ rsc_mgr->miscdev.name = "gunyah";
+ rsc_mgr->miscdev.minor = MISC_DYNAMIC_MINOR;
+ rsc_mgr->miscdev.fops = &gh_dev_fops;
+
+ ret = misc_register(&rsc_mgr->miscdev);
+ if (ret)
+ goto err_msgq;
+
+ return 0;
+err_msgq:
+ mbox_free_channel(gh_msgq_chan(&rsc_mgr->msgq));
+ gh_msgq_remove(&rsc_mgr->msgq);
+ return ret;
}
static int gh_rm_drv_remove(struct platform_device *pdev)
{
struct gh_rm_rpc *rm = platform_get_drvdata(pdev);
+ misc_deregister(&rm->miscdev);
mbox_free_channel(gh_msgq_chan(&rm->msgq));
gh_msgq_remove(&rm->msgq);
diff --git a/drivers/virt/gunyah/vm_mgr.c b/drivers/virt/gunyah/vm_mgr.c
new file mode 100644
index 000000000000..9c796e5edcf1
--- /dev/null
+++ b/drivers/virt/gunyah/vm_mgr.c
@@ -0,0 +1,115 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
+ */
+
+#define pr_fmt(fmt) "gh_vm_mgr: " fmt
+
+#include <linux/anon_inodes.h>
+#include <linux/file.h>
+#include <linux/gunyah_rsc_mgr.h>
+#include <linux/miscdevice.h>
+#include <linux/module.h>
+
+#include <uapi/linux/gunyah.h>
+
+#include "vm_mgr.h"
+
+static __must_check struct gunyah_vm *gunyah_vm_alloc(struct gh_rm_rpc *rm)
+{
+ struct gunyah_vm *ghvm;
+ int vmid;
+
+ vmid = gh_rm_alloc_vmid(rm, 0);
+ if (vmid < 0)
+ return ERR_PTR(vmid);
+
+ ghvm = kzalloc(sizeof(*ghvm), GFP_KERNEL);
+ if (!ghvm)
+ return ghvm;
+
+ get_gh_rm(rm);
+
+ ghvm->vmid = vmid;
+ ghvm->rm = rm;
+
+ return ghvm;
+}
+
+static long gh_vm_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
+{
+ long r;
+
+ switch (cmd) {
+ default:
+ r = -ENOTTY;
+ break;
+ }
+
+ return r;
+}
+
+static int gh_vm_release(struct inode *inode, struct file *filp)
+{
+ struct gunyah_vm *ghvm = filp->private_data;
+
+ put_gh_rm(ghvm->rm);
+ kfree(ghvm);
+ return 0;
+}
+
+static const struct file_operations gh_vm_fops = {
+ .unlocked_ioctl = gh_vm_ioctl,
+ .release = gh_vm_release,
+ .compat_ioctl = compat_ptr_ioctl,
+ .llseek = noop_llseek,
+};
+
+static long gh_dev_ioctl_create_vm(struct gh_rm_rpc *rm, unsigned long arg)
+{
+ struct gunyah_vm *ghvm;
+ struct file *file;
+ int fd, err;
+
+ /* arg reserved for future use. */
+ if (arg)
+ return -EINVAL;
+
+ ghvm = gunyah_vm_alloc(rm);
+ if (IS_ERR_OR_NULL(ghvm))
+ return PTR_ERR(ghvm) ? : -ENOMEM;
+
+ fd = get_unused_fd_flags(O_CLOEXEC);
+ if (fd < 0) {
+ err = fd;
+ goto err_destroy_vm;
+ }
+
+ file = anon_inode_getfile("gunyah-vm", &gh_vm_fops, ghvm, O_RDWR);
+ if (IS_ERR(file)) {
+ err = PTR_ERR(file);
+ goto err_put_fd;
+ }
+
+ fd_install(fd, file);
+
+ return fd;
+
+err_put_fd:
+ put_unused_fd(fd);
+err_destroy_vm:
+ kfree(ghvm);
+ return err;
+}
+
+long gh_dev_vm_mgr_ioctl(struct gh_rm_rpc *rm, unsigned int cmd, unsigned long arg)
+{
+ switch (cmd) {
+ case GH_CREATE_VM:
+ return gh_dev_ioctl_create_vm(rm, arg);
+ default:
+ return -ENOIOCTLCMD;
+ }
+}
+EXPORT_SYMBOL_GPL(gh_dev_vm_mgr_ioctl);
+
diff --git a/drivers/virt/gunyah/vm_mgr.h b/drivers/virt/gunyah/vm_mgr.h
new file mode 100644
index 000000000000..84e3d1a8571d
--- /dev/null
+++ b/drivers/virt/gunyah/vm_mgr.h
@@ -0,0 +1,25 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
+ */
+
+#ifndef _GH_PRIV_VM_MGR_H
+#define _GH_PRIV_VM_MGR_H
+
+#include <linux/gunyah_rsc_mgr.h>
+
+#include <uapi/linux/gunyah.h>
+
+#if IS_ENABLED(CONFIG_GUNYAH_VM_MANAGER)
+long gh_dev_vm_mgr_ioctl(struct gh_rm_rpc *rm, unsigned int cmd, unsigned long arg);
+#else
+static inline long gh_dev_vm_mgr_ioctl(struct gh_rm_rpc *rm, unsigned int cmd, unsigned long arg)
+ { return -ENOIOCTLCMD; }
+#endif
+
+struct gunyah_vm {
+ u16 vmid;
+ struct gh_rm_rpc *rm;
+};
+
+#endif
diff --git a/include/linux/gunyah_rsc_mgr.h b/include/linux/gunyah_rsc_mgr.h
index eb94b48c41de..9c566436687d 100644
--- a/include/linux/gunyah_rsc_mgr.h
+++ b/include/linux/gunyah_rsc_mgr.h
@@ -17,6 +17,9 @@
struct gh_rm_rpc;
+void get_gh_rm(struct gh_rm_rpc *rm);
+void put_gh_rm(struct gh_rm_rpc *rm);
+
enum gh_rm_vm_status {
GH_RM_VM_STATUS_NO_STATE = 0,
GH_RM_VM_STATUS_INIT = 1,
diff --git a/include/uapi/linux/gunyah.h b/include/uapi/linux/gunyah.h
new file mode 100644
index 000000000000..88a40d6e0b96
--- /dev/null
+++ b/include/uapi/linux/gunyah.h
@@ -0,0 +1,23 @@
+/* SPDX-License-Identifier: GPL-2.0-only WITH Linux-syscall-note */
+/*
+ * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
+ */
+
+#ifndef _UAPI_LINUX_GUNYAH
+#define _UAPI_LINUX_GUNYAH
+
+/*
+ * Userspace interface for /dev/gunyah - gunyah based virtual machine
+ */
+
+#include <linux/types.h>
+#include <linux/ioctl.h>
+
+#define GH_IOCTL_TYPE 'G'
+
+/*
+ * ioctls for /dev/gunyah fds:
+ */
+#define GH_CREATE_VM _IO(GH_IOCTL_TYPE, 0x0) /* Returns a Gunyah VM fd */
+
+#endif
--
2.25.1
On Qualcomm platforms, there is a firmware entity which controls access
to physical pages. In order to share memory with another VM, this entity
needs to be informed that the guest VM should have access to the memory.
Co-developed-by: Prakruthi Deepak Heragu <[email protected]>
Signed-off-by: Prakruthi Deepak Heragu <[email protected]>
Signed-off-by: Elliot Berman <[email protected]>
---
drivers/virt/gunyah/Kconfig | 4 ++
drivers/virt/gunyah/Makefile | 1 +
drivers/virt/gunyah/gunyah_platform_hooks.c | 63 +++++++++++++++++++++
drivers/virt/gunyah/rsc_mgr.h | 3 +
drivers/virt/gunyah/rsc_mgr_rpc.c | 7 ++-
include/linux/gunyah_rsc_mgr.h | 14 +++++
6 files changed, 91 insertions(+), 1 deletion(-)
create mode 100644 drivers/virt/gunyah/gunyah_platform_hooks.c
diff --git a/drivers/virt/gunyah/Kconfig b/drivers/virt/gunyah/Kconfig
index 5e5a329c9866..e92318ec8026 100644
--- a/drivers/virt/gunyah/Kconfig
+++ b/drivers/virt/gunyah/Kconfig
@@ -11,11 +11,15 @@ config GUNYAH
Say Y/M here to enable the drivers needed to interact in a Gunyah
virtual environment.
+config GUNYAH_PLATFORM_HOOKS
+ tristate
+
if GUNYAH
config GUNYAH_RESOURCE_MANAGER
tristate "Gunyah Resource Manager"
depends on MAILBOX
select GUNYAH_MESSAGE_QUEUES
+ select GUNYAH_PLATFORM_HOOKS
help
The resource manager (RM) is a privileged application VM supporting
the Gunyah Hypervisor. Enable this driver to support communicating
diff --git a/drivers/virt/gunyah/Makefile b/drivers/virt/gunyah/Makefile
index 9d6f900ab7b0..e321c009898f 100644
--- a/drivers/virt/gunyah/Makefile
+++ b/drivers/virt/gunyah/Makefile
@@ -1,4 +1,5 @@
obj-$(CONFIG_GUNYAH) += gunyah.o
+obj-$(CONFIG_GUNYAH_PLATFORM_HOOKS) += gunyah_platform_hooks.o
gunyah_rsc_mgr-y += rsc_mgr.o rsc_mgr_rpc.o
gunyah_rsc_mgr-$(CONFIG_GUNYAH_VM_MANAGER) += vm_mgr.o vm_mgr_mm.o
diff --git a/drivers/virt/gunyah/gunyah_platform_hooks.c b/drivers/virt/gunyah/gunyah_platform_hooks.c
new file mode 100644
index 000000000000..36cc02c75963
--- /dev/null
+++ b/drivers/virt/gunyah/gunyah_platform_hooks.c
@@ -0,0 +1,63 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
+ */
+
+#include <linux/module.h>
+#include <linux/rwsem.h>
+#include <linux/gunyah_rsc_mgr.h>
+
+#include "rsc_mgr.h"
+
+static struct gunyah_rm_platform_ops *rm_platform_ops;
+static DECLARE_RWSEM(rm_platform_ops_lock);
+
+int gh_rm_platform_pre_mem_share(struct gh_rm_rpc *rm, struct gh_rm_mem_parcel *mem_parcel)
+{
+ int ret = 0;
+
+ down_read(&rm_platform_ops_lock);
+ if (rm_platform_ops && rm_platform_ops->pre_mem_share)
+ ret = rm_platform_ops->pre_mem_share(rm, mem_parcel);
+ up_read(&rm_platform_ops_lock);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(gh_rm_platform_pre_mem_share);
+
+int gh_rm_platform_post_mem_reclaim(struct gh_rm_rpc *rm, struct gh_rm_mem_parcel *mem_parcel)
+{
+ int ret = 0;
+
+ down_read(&rm_platform_ops_lock);
+ if (rm_platform_ops && rm_platform_ops->post_mem_reclaim)
+ ret = rm_platform_ops->post_mem_reclaim(rm, mem_parcel);
+ up_read(&rm_platform_ops_lock);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(gh_rm_platform_post_mem_reclaim);
+
+int gh_rm_register_platform_ops(struct gunyah_rm_platform_ops *platform_ops)
+{
+ int ret = 0;
+
+ down_write(&rm_platform_ops_lock);
+ if (!rm_platform_ops)
+ rm_platform_ops = platform_ops;
+ else
+ ret = -EEXIST;
+ up_write(&rm_platform_ops_lock);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(gh_rm_register_platform_ops);
+
+void gh_rm_unregister_platform_ops(struct gunyah_rm_platform_ops *platform_ops)
+{
+ down_write(&rm_platform_ops_lock);
+ if (rm_platform_ops == platform_ops)
+ rm_platform_ops = NULL;
+ up_write(&rm_platform_ops_lock);
+}
+EXPORT_SYMBOL_GPL(gh_rm_unregister_platform_ops);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Gunyah Platform Hooks");
diff --git a/drivers/virt/gunyah/rsc_mgr.h b/drivers/virt/gunyah/rsc_mgr.h
index e068b5b65d2b..69554602c889 100644
--- a/drivers/virt/gunyah/rsc_mgr.h
+++ b/drivers/virt/gunyah/rsc_mgr.h
@@ -33,6 +33,9 @@ struct gh_rm_rpc;
int gh_rm_call(struct gh_rm_rpc *rsc_mgr, u32 message_id, void *req_buff, size_t req_buff_size,
void **resp_buf, size_t *resp_buff_size);
+int gh_rm_platform_pre_mem_share(struct gh_rm_rpc *rm, struct gh_rm_mem_parcel *mem_parcel);
+int gh_rm_platform_post_mem_reclaim(struct gh_rm_rpc *rm, struct gh_rm_mem_parcel *mem_parcel);
+
/* Message IDs: Memory Management */
#define GH_RM_RPC_MEM_LEND 0x51000012
#define GH_RM_RPC_MEM_SHARE 0x51000013
diff --git a/drivers/virt/gunyah/rsc_mgr_rpc.c b/drivers/virt/gunyah/rsc_mgr_rpc.c
index 2c9aded7cc09..e81a0dc798e1 100644
--- a/drivers/virt/gunyah/rsc_mgr_rpc.c
+++ b/drivers/virt/gunyah/rsc_mgr_rpc.c
@@ -112,6 +112,10 @@ static int gh_rm_mem_lend_common(struct gh_rm_rpc *rm, u32 message_id, struct gh
if (initial_n_mem_entries > GH_RM_MAX_MEM_ENTRIES)
initial_n_mem_entries = GH_RM_MAX_MEM_ENTRIES;
+ ret = gh_rm_platform_pre_mem_share(rm, p);
+ if (ret)
+ return ret;
+
/* The format of the message goes:
* request header
* ACL entries (which VMs get what kind of access to this memory parcel)
@@ -155,6 +159,7 @@ static int gh_rm_mem_lend_common(struct gh_rm_rpc *rm, u32 message_id, struct gh
if (resp_size != sizeof(__le32)) {
ret = -EIO;
+ gh_rm_platform_post_mem_reclaim(rm, p);
goto out;
}
@@ -224,7 +229,7 @@ int gh_rm_mem_reclaim(struct gh_rm_rpc *rm, struct gh_rm_mem_parcel *parcel)
return ret;
kfree(resp);
- return ret;
+ return gh_rm_platform_post_mem_reclaim(rm, parcel);
}
EXPORT_SYMBOL_GPL(gh_rm_mem_reclaim);
diff --git a/include/linux/gunyah_rsc_mgr.h b/include/linux/gunyah_rsc_mgr.h
index 0b5b236fd76f..be434bcf9b4e 100644
--- a/include/linux/gunyah_rsc_mgr.h
+++ b/include/linux/gunyah_rsc_mgr.h
@@ -115,4 +115,18 @@ ssize_t gh_rm_get_hyp_resources(struct gh_rm_rpc *rm, u16 vmid,
struct gh_rm_hyp_resource **resources);
int gh_rm_get_vmid(struct gh_rm_rpc *rm, u16 *vmid);
+struct gunyah_rm_platform_ops {
+ int (*pre_mem_share)(struct gh_rm_rpc *rm, struct gh_rm_mem_parcel *mem_parcel);
+ int (*post_mem_reclaim)(struct gh_rm_rpc *rm, struct gh_rm_mem_parcel *mem_parcel);
+};
+
+#if IS_ENABLED(CONFIG_GUNYAH_PLATFORM_HOOKS)
+int gh_rm_register_platform_ops(struct gunyah_rm_platform_ops *platform_ops);
+void gh_rm_unregister_platform_ops(struct gunyah_rm_platform_ops *platform_ops);
+#else
+static inline int gh_rm_register_platform_ops(struct gunyah_rm_platform_ops *platform_ops)
+ { return 0; }
+static inline void gh_rm_unregister_platform_ops(struct gunyah_rm_platform_ops *platform_ops) { }
+#endif
+
#endif
--
2.25.1
Add a sample Gunyah VMM capable of launching a non-proxy scheduled VM.
Signed-off-by: Elliot Berman <[email protected]>
---
MAINTAINERS | 1 +
samples/Kconfig | 10 ++
samples/Makefile | 1 +
samples/gunyah/.gitignore | 2 +
samples/gunyah/Makefile | 6 +
samples/gunyah/gunyah_vmm.c | 270 +++++++++++++++++++++++++++++++++++
samples/gunyah/sample_vm.dts | 69 +++++++++
7 files changed, 359 insertions(+)
create mode 100644 samples/gunyah/.gitignore
create mode 100644 samples/gunyah/Makefile
create mode 100644 samples/gunyah/gunyah_vmm.c
create mode 100644 samples/gunyah/sample_vm.dts
diff --git a/MAINTAINERS b/MAINTAINERS
index bb315385e155..176272eb9009 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -8945,6 +8945,7 @@ F: arch/arm64/gunyah/
F: drivers/mailbox/gunyah-msgq.c
F: drivers/virt/gunyah/
F: include/linux/gunyah*.h
+F: samples/gunyah/
HABANALABS PCI DRIVER
M: Oded Gabbay <[email protected]>
diff --git a/samples/Kconfig b/samples/Kconfig
index 0d81c00289ee..197c8a3610e7 100644
--- a/samples/Kconfig
+++ b/samples/Kconfig
@@ -263,6 +263,16 @@ config SAMPLE_CORESIGHT_SYSCFG
This demonstrates how a user may create their own CoreSight
configurations and easily load them into the system at runtime.
+config SAMPLE_GUNYAH
+ bool "Build example Gunyah Virtual Machine Manager"
+ depends on CC_CAN_LINK && HEADERS_INSTALL
+ depends on GUNYAH_VM_MANAGER
+ help
+ Build an example Gunyah VMM userspace program capable of launching
+ a basic virtual machine under the Gunyah hypervisor.
+ This demonstrates how to create a virtual machine under the Gunyah
+ hypervisor.
+
source "samples/rust/Kconfig"
endif # SAMPLES
diff --git a/samples/Makefile b/samples/Makefile
index 9832ef3f8fcb..2600bd4b82f8 100644
--- a/samples/Makefile
+++ b/samples/Makefile
@@ -36,3 +36,4 @@ obj-$(CONFIG_DEBUG_KMEMLEAK_TEST) += kmemleak/
obj-$(CONFIG_SAMPLE_CORESIGHT_SYSCFG) += coresight/
obj-$(CONFIG_SAMPLE_FPROBE) += fprobe/
obj-$(CONFIG_SAMPLES_RUST) += rust/
+obj-$(CONFIG_SAMPLE_GUNYAH) += gunyah/
diff --git a/samples/gunyah/.gitignore b/samples/gunyah/.gitignore
new file mode 100644
index 000000000000..adc7d1589fde
--- /dev/null
+++ b/samples/gunyah/.gitignore
@@ -0,0 +1,2 @@
+# SPDX-License-Identifier: GPL-2.0
+/gunyah_vmm
diff --git a/samples/gunyah/Makefile b/samples/gunyah/Makefile
new file mode 100644
index 000000000000..faf14f9bb337
--- /dev/null
+++ b/samples/gunyah/Makefile
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+userprogs-always-y += gunyah_vmm
+dtb-y += sample_vm.dtb
+
+userccflags += -I usr/include
diff --git a/samples/gunyah/gunyah_vmm.c b/samples/gunyah/gunyah_vmm.c
new file mode 100644
index 000000000000..ec74393c864e
--- /dev/null
+++ b/samples/gunyah/gunyah_vmm.c
@@ -0,0 +1,270 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <getopt.h>
+#include <limits.h>
+#include <stdint.h>
+#include <fcntl.h>
+#include <string.h>
+#include <sys/sysmacros.h>
+#define __USE_GNU
+#include <sys/mman.h>
+
+#include <linux/gunyah.h>
+
+struct vm_config {
+ int image_fd;
+ int dtb_fd;
+ int ramdisk_fd;
+
+ uint64_t guest_base;
+ uint64_t guest_size;
+
+ uint64_t image_offset;
+ off_t image_size;
+ uint64_t dtb_offset;
+ off_t dtb_size;
+ uint64_t ramdisk_offset;
+ off_t ramdisk_size;
+};
+
+static struct option options[] = {
+ { "help", no_argument, NULL, 'h' },
+ { "image", required_argument, NULL, 'i' },
+ { "dtb", required_argument, NULL, 'd' },
+ { "ramdisk", optional_argument, NULL, 'r' },
+ { "base", optional_argument, NULL, 'B' },
+ { "size", optional_argument, NULL, 'S' },
+ { "image_offset", optional_argument, NULL, 'I' },
+ { "dtb_offset", optional_argument, NULL, 'D' },
+ { "ramdisk_offset", optional_argument, NULL, 'R' },
+ { }
+};
+
+static void print_help(char *cmd)
+{
+ printf("gunyah_vmm, a sample tool to launch Gunyah VMs\n"
+ "Usage: %s <options>\n"
+ " --help, -h this menu\n"
+ " --image, -i <image> VM image file to load (e.g. a kernel Image) [Required]\n"
+ " --dtb, -d <dtb> Devicetree to load [Required]\n"
+ " --ramdisk, -r <ramdisk> Ramdisk to load\n"
+ " --base, -B <address> Set the base address of guest's memory [Default: 0x80000000]\n"
+ " --size, -S <number> The number of bytes large to make the guest's memory [Default: 0x6400000 (100 MB)]\n"
+ " --image_offset, -I <number> Offset into guest memory to load the VM image file [Default: 0x10000]\n"
+ " --dtb_offset, -D <number> Offset into guest memory to load the DTB [Default: 0]\n"
+ " --ramdisk_offset, -R <number> Offset into guest memory to load a ramdisk [Default: 0x4600000]\n"
+ , cmd);
+}
+
+int main(int argc, char **argv)
+{
+ int gunyah_fd, vm_fd, guest_fd;
+ struct gh_userspace_memory_region guest_mem_desc = { 0 };
+ struct gh_vm_dtb_config dtb_config = { 0 };
+ char *guest_mem;
+ struct vm_config config = {
+ /* Defaults good enough to boot static kernel and a basic ramdisk */
+ .ramdisk_fd = -1,
+ .guest_base = 0x80000000,
+ .guest_size = 104857600, /* 100 MB */
+ .image_offset = 0,
+ .dtb_offset = 0x45f0000,
+ .ramdisk_offset = 0x4600000, /* put at +70MB (30MB for ramdisk) */
+ };
+ struct stat st;
+ int opt, optidx, ret = 0;
+ long int l;
+
+ while ((opt = getopt_long(argc, argv, "hi:d:r:B:S:I:D:R:c:", options, &optidx)) != -1) {
+ switch (opt) {
+ case 'i':
+ config.image_fd = open(optarg, O_RDONLY | O_CLOEXEC);
+ if (config.image_fd < 0) {
+ perror("Failed to open image");
+ return -1;
+ }
+ if (stat(optarg, &st) < 0) {
+ perror("Failed to stat image");
+ return -1;
+ }
+ config.image_size = st.st_size;
+ break;
+ case 'd':
+ config.dtb_fd = open(optarg, O_RDONLY | O_CLOEXEC);
+ if (config.dtb_fd < 0) {
+ perror("Failed to open dtb");
+ return -1;
+ }
+ if (stat(optarg, &st) < 0) {
+ perror("Failed to stat dtb");
+ return -1;
+ }
+ config.dtb_size = st.st_size;
+ break;
+ case 'r':
+ config.ramdisk_fd = open(optarg, O_RDONLY | O_CLOEXEC);
+ if (config.ramdisk_fd < 0) {
+ perror("Failed to open ramdisk");
+ return -1;
+ }
+ if (stat(optarg, &st) < 0) {
+ perror("Failed to stat ramdisk");
+ return -1;
+ }
+ config.ramdisk_size = st.st_size;
+ break;
+ case 'B':
+ l = strtol(optarg, NULL, 0);
+ if (l == LONG_MIN) {
+ perror("Failed to parse base address");
+ return -1;
+ }
+ config.guest_base = l;
+ break;
+ case 'S':
+ l = strtol(optarg, NULL, 0);
+ if (l == LONG_MIN) {
+ perror("Failed to parse memory size");
+ return -1;
+ }
+ config.guest_size = l;
+ break;
+ case 'I':
+ l = strtol(optarg, NULL, 0);
+ if (l == LONG_MIN) {
+ perror("Failed to parse image offset");
+ return -1;
+ }
+ config.image_offset = l;
+ break;
+ case 'D':
+ l = strtol(optarg, NULL, 0);
+ if (l == LONG_MIN) {
+ perror("Failed to parse dtb offset");
+ return -1;
+ }
+ config.dtb_offset = l;
+ break;
+ case 'R':
+ l = strtol(optarg, NULL, 0);
+ if (l == LONG_MIN) {
+ perror("Failed to parse ramdisk offset");
+ return -1;
+ }
+ config.ramdisk_offset = l;
+ break;
+ case 'h':
+ print_help(argv[0]);
+ return 0;
+ default:
+ print_help(argv[0]);
+ return -1;
+ }
+ }
+
+ if (!config.image_fd || !config.dtb_fd) {
+ print_help(argv[0]);
+ return -1;
+ }
+
+ if (config.image_offset + config.image_size > config.guest_size) {
+ fprintf(stderr, "Image offset and size puts it outside guest memory. Make image smaller or increase guest memory size.\n");
+ return -1;
+ }
+
+ if (config.dtb_offset + config.dtb_size > config.guest_size) {
+ fprintf(stderr, "DTB offset and size puts it outside guest memory. Make dtb smaller or increase guest memory size.\n");
+ return -1;
+ }
+
+ if (config.ramdisk_fd == -1 &&
+ config.ramdisk_offset + config.ramdisk_size > config.guest_size) {
+ fprintf(stderr, "Ramdisk offset and size puts it outside guest memory. Make ramdisk smaller or increase guest memory size.\n");
+ return -1;
+ }
+
+ gunyah_fd = open("/dev/gunyah", O_RDWR | O_CLOEXEC);
+ if (gunyah_fd < 0) {
+ perror("Failed to open /dev/gunyah");
+ return -1;
+ }
+
+ vm_fd = ioctl(gunyah_fd, GH_CREATE_VM, 0);
+ if (vm_fd < 0) {
+ perror("Failed to create vm");
+ return -1;
+ }
+
+ guest_fd = memfd_create("guest_memory", MFD_CLOEXEC);
+ if (guest_fd < 0) {
+ perror("Failed to create guest memfd");
+ return -1;
+ }
+
+ if (ftruncate(guest_fd, config.guest_size) < 0) {
+ perror("Failed to grow guest memory");
+ return -1;
+ }
+
+ guest_mem = mmap(NULL, config.guest_size, PROT_READ | PROT_WRITE, MAP_SHARED, guest_fd, 0);
+ if (guest_mem == MAP_FAILED) {
+ perror("Not enough memory");
+ return -1;
+ }
+
+ if (read(config.image_fd, guest_mem + config.image_offset, config.image_size) < 0) {
+ perror("Failed to read image into guest memory");
+ return -1;
+ }
+
+ if (read(config.dtb_fd, guest_mem + config.dtb_offset, config.dtb_size) < 0) {
+ perror("Failed to read dtb into guest memory");
+ return -1;
+ }
+
+ if (config.ramdisk_fd > 0 &&
+ read(config.ramdisk_fd, guest_mem + config.ramdisk_offset,
+ config.ramdisk_size) < 0) {
+ perror("Failed to read ramdisk into guest memory");
+ return -1;
+ }
+
+ guest_mem_desc.label = 0;
+ guest_mem_desc.flags = GH_MEM_ALLOW_READ | GH_MEM_ALLOW_WRITE | GH_MEM_ALLOW_EXEC;
+ guest_mem_desc.guest_phys_addr = config.guest_base;
+ guest_mem_desc.memory_size = config.guest_size;
+ guest_mem_desc.userspace_addr = (__u64)guest_mem;
+
+ if (ioctl(vm_fd, GH_VM_SET_USER_MEM_REGION, &guest_mem_desc) < 0) {
+ perror("Failed to register guest memory with VM");
+ return -1;
+ }
+
+ dtb_config.gpa = config.guest_base + config.dtb_offset;
+ dtb_config.size = config.dtb_size;
+ if (ioctl(vm_fd, GH_VM_SET_DTB_CONFIG, &dtb_config) < 0) {
+ perror("Failed to set DTB configuration for VM");
+ return -1;
+ }
+
+ ret = ioctl(vm_fd, GH_VM_START);
+ if (ret) {
+ perror("GH_VM_START failed");
+ return -1;
+ }
+
+ while (1)
+ sleep(10);
+
+ return 0;
+}
diff --git a/samples/gunyah/sample_vm.dts b/samples/gunyah/sample_vm.dts
new file mode 100644
index 000000000000..1c410d58c298
--- /dev/null
+++ b/samples/gunyah/sample_vm.dts
@@ -0,0 +1,69 @@
+// SPDX-License-Identifier: BSD-3-Clause
+/*
+ * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
+ */
+
+/dts-v1/;
+
+/ {
+ #address-cells = <2>;
+ #size-cells = <2>;
+ interrupt-parent = <&intc>;
+
+ chosen {
+ bootargs = "nokaslr";
+ };
+
+ cpus {
+ #address-cells = <0x2>;
+ #size-cells = <0>;
+
+ cpu@0 {
+ device_type = "cpu";
+ compatible = "arm,armv8";
+ reg = <0 0>;
+ };
+ };
+
+ intc: interrupt-controller@3FFF0000 {
+ compatible = "arm,gic-v3";
+ #interrupt-cells = <3>;
+ #address-cells = <2>;
+ #size-cells = <2>;
+ interrupt-controller;
+ reg = <0 0x3FFF0000 0 0x10000>,
+ <0 0x3FFD0000 0 0x20000>;
+ };
+
+ timer {
+ compatible = "arm,armv8-timer";
+ always-on;
+ interrupts = <1 13 0x108>,
+ <1 14 0x108>,
+ <1 11 0x108>,
+ <1 10 0x108>;
+ clock-frequency = <19200000>;
+ };
+
+ gunyah-vm-config {
+ image-name = "linux_vm_0";
+ os-type = "linux";
+
+ memory {
+ #address-cells = <2>;
+ #size-cells = <2>;
+
+ base-address = <0 0x80000000>;
+ };
+
+ interrupts {
+ config = <&intc>;
+ };
+
+ vcpus {
+ affinity-map = < 0 >;
+ sched-priority = < (-1) >;
+ sched-timeslice = < 2000 >;
+ };
+ };
+};
--
2.25.1
Gunyah allows host virtual machines to schedule guest virtual machines
and handle their MMIO accesses. vCPUs are presented to the host as a
Gunyah resource and represented to userspace as a Gunyah VM function.
Creating the vcpu VM function will create a file descriptor that:
- can run an ioctl: GH_VCPU_RUN to schedule the guest vCPU until the
next interrupt occurs on the host or when the guest vCPU can no
longer be run.
- can be mmap'd to share a gh_vcpu_run structure which can look up the
reason why GH_VCPU_RUN returned and provide return values for MMIO
access.
Co-developed-by: Prakruthi Deepak Heragu <[email protected]>
Signed-off-by: Prakruthi Deepak Heragu <[email protected]>
Signed-off-by: Elliot Berman <[email protected]>
---
Documentation/virt/gunyah/vm-manager.rst | 30 +-
arch/arm64/gunyah/gunyah_hypercall.c | 28 ++
drivers/virt/gunyah/Kconfig | 12 +
drivers/virt/gunyah/Makefile | 2 +
drivers/virt/gunyah/gunyah_vcpu.c | 350 +++++++++++++++++++++++
drivers/virt/gunyah/vm_mgr.c | 25 ++
drivers/virt/gunyah/vm_mgr.h | 1 +
include/linux/gunyah.h | 7 +
include/uapi/linux/gunyah.h | 30 ++
9 files changed, 483 insertions(+), 2 deletions(-)
create mode 100644 drivers/virt/gunyah/gunyah_vcpu.c
diff --git a/Documentation/virt/gunyah/vm-manager.rst b/Documentation/virt/gunyah/vm-manager.rst
index d929f8f20b3b..d11267d59802 100644
--- a/Documentation/virt/gunyah/vm-manager.rst
+++ b/Documentation/virt/gunyah/vm-manager.rst
@@ -5,8 +5,7 @@ Virtual Machine Manager
=======================
The Gunyah Virtual Machine Manager is a Linux driver to support launching
-virtual machines using Gunyah. It presently supports launching non-proxy
-scheduled Linux-like virtual machines.
+virtual machines using Gunyah.
Except for some basic information about the location of initial binaries,
most of the configuration about a Gunyah virtual machine is described in the
@@ -116,3 +115,30 @@ GH_VM_START
~~~~~~~~~~~
This ioctl starts the VM.
+
+GH_VM_ADD_FUNCTION
+~~~~~~~~~~~~~~~~~~
+
+This ioctl registers a Gunyah VM function with the VM manager. The VM function
+is described with a `type` string and some arguments for that type. Typically,
+the function is added before the VM starts, but the function doesn't "operate"
+until the VM starts with GH_VM_START: e.g. vCPU ioclts will all return an error
+until the VM starts because the vCPUs don't exist until the VM is started. This
+allows the VMM to set up all the kernel functionality needed for the VM *before*
+the VM starts.
+
+The possible types are documented below:
+
+Type: "vcpu"
+^^^^^^^^^^^^
+
+::
+
+ struct gh_fn_vcpu_arg {
+ __u32 vcpu_id;
+ };
+
+The vcpu type will register with the VM Manager to expect to control
+vCPU number `vcpu_id`. It returns a file descriptor allowing interaction with
+the vCPU. See the Gunyah vCPU API description sections for interacting with
+the Gunyah vCPU file descriptors.
diff --git a/arch/arm64/gunyah/gunyah_hypercall.c b/arch/arm64/gunyah/gunyah_hypercall.c
index d28600909be4..dc42a4e6bd7c 100644
--- a/arch/arm64/gunyah/gunyah_hypercall.c
+++ b/arch/arm64/gunyah/gunyah_hypercall.c
@@ -28,6 +28,7 @@
#define GH_HYPERCALL_HYP_IDENTIFY GH_HYPERCALL(0x0000)
#define GH_HYPERCALL_MSGQ_SEND GH_HYPERCALL(0x001B)
#define GH_HYPERCALL_MSGQ_RECV GH_HYPERCALL(0x001C)
+#define GH_HYPERCALL_VCPU_RUN GH_HYPERCALL(0x0065)
/**
* gh_hypercall_get_uid() - Returns a UID when running under a Gunyah hypervisor
@@ -98,5 +99,32 @@ int gh_hypercall_msgq_recv(u64 capid, uintptr_t buff, size_t size, size_t *recv_
}
EXPORT_SYMBOL_GPL(gh_hypercall_msgq_recv);
+int gh_hypercall_vcpu_run(u64 capid, u64 *resume_data, struct gh_hypercall_vcpu_run_resp *resp)
+{
+ struct arm_smccc_1_2_regs args = {
+ .a0 = GH_HYPERCALL_VCPU_RUN,
+ .a1 = capid,
+ .a2 = resume_data[0],
+ .a3 = resume_data[1],
+ .a4 = resume_data[2],
+ /* C language says this will be implictly zero. Gunyah requires 0, so be explicit */
+ .a5 = 0,
+ };
+ struct arm_smccc_1_2_regs res;
+
+ arm_smccc_1_2_hvc(&args, &res);
+
+ if (res.a0)
+ return res.a0;
+
+ resp->state = res.a1;
+ resp->state_data[0] = res.a2;
+ resp->state_data[1] = res.a3;
+ resp->state_data[2] = res.a4;
+
+ return res.a0;
+}
+EXPORT_SYMBOL_GPL(gh_hypercall_vcpu_run);
+
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Gunyah Hypervisor Hypercalls");
diff --git a/drivers/virt/gunyah/Kconfig b/drivers/virt/gunyah/Kconfig
index e92318ec8026..f9551cd5dfe4 100644
--- a/drivers/virt/gunyah/Kconfig
+++ b/drivers/virt/gunyah/Kconfig
@@ -38,4 +38,16 @@ config GUNYAH_VM_MANAGER
required to launch other virtual machines.
Say Y here if unsure and you want to support Gunyah VMMs.
+
+config GUNYAH_VCPU
+ tristate "Runnable Gunyah vCPUs"
+ depends on GUNYAH_RESOURCE_MANAGER
+ depends on GUNYAH_VM_MANAGER
+ help
+ Enable kernel support for host-scheduled vCPUs running under Gunyah.
+ When selecting this option, userspace virtual machine managers (VMM)
+ can schedule the guest VM's vCPUs instead of using Gunyah's scheduler.
+ VMMs can also handle stage 2 faults of the vCPUs.
+
+ Say Y/M here if unsure and you want to support Gunyah VMMs.
endif
diff --git a/drivers/virt/gunyah/Makefile b/drivers/virt/gunyah/Makefile
index e321c009898f..debdd7e4d9a1 100644
--- a/drivers/virt/gunyah/Makefile
+++ b/drivers/virt/gunyah/Makefile
@@ -4,3 +4,5 @@ obj-$(CONFIG_GUNYAH_PLATFORM_HOOKS) += gunyah_platform_hooks.o
gunyah_rsc_mgr-y += rsc_mgr.o rsc_mgr_rpc.o
gunyah_rsc_mgr-$(CONFIG_GUNYAH_VM_MANAGER) += vm_mgr.o vm_mgr_mm.o
obj-$(CONFIG_GUNYAH_RESOURCE_MANAGER) += gunyah_rsc_mgr.o
+
+obj-$(CONFIG_GUNYAH_VCPU) += gunyah_vcpu.o
diff --git a/drivers/virt/gunyah/gunyah_vcpu.c b/drivers/virt/gunyah/gunyah_vcpu.c
new file mode 100644
index 000000000000..618a6e103de7
--- /dev/null
+++ b/drivers/virt/gunyah/gunyah_vcpu.c
@@ -0,0 +1,350 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
+ */
+
+#include <linux/anon_inodes.h>
+#include <linux/file.h>
+#include <linux/gunyah.h>
+#include <linux/gunyah_vm_mgr.h>
+#include <linux/interrupt.h>
+#include <linux/kref.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/wait.h>
+
+#include "vm_mgr.h"
+
+#include <uapi/linux/gunyah.h>
+
+#define MAX_VCPU_NAME 20 /* gh-vcpu:u32_max+NUL */
+
+struct gunyah_vcpu {
+ struct gunyah_resource *rsc;
+ struct gunyah_vm_resource_ticket ticket;
+ struct gunyah_vm_function *f;
+ struct gunyah_vm *ghvm;
+
+ bool handle_mmio;
+ struct gh_vcpu_run *vcpu_run;
+
+ struct kref kref;
+ struct completion ready;
+ struct mutex run_lock;
+};
+
+/* VCPU is ready to run */
+#define GH_VCPU_STATE_READY 0
+/* VCPU is sleeping until an interrupt arrives */
+#define GH_VCPU_STATE_EXPECTS_WAKEUP 1
+/* VCPU is powered off */
+#define GH_VCPU_STATE_POWERED_OFF 2
+/* VCPU is blocked in EL2 for unspecified reason */
+#define GH_VCPU_STATE_BLOCKED 3
+/* VCPU has returned for MMIO READ */
+#define GH_VCPU_ADDRSPACE_VMMIO_READ 4
+/* VCPU has returned for MMIO WRITE */
+#define GH_VCPU_ADDRSPACE_VMMIO_WRITE 5
+
+static void vcpu_release(struct kref *kref)
+{
+ struct gunyah_vcpu *vcpu = container_of(kref, struct gunyah_vcpu, kref);
+
+ kfree(vcpu);
+}
+
+/**
+ * When hypervisor wants us to schedule vCPU again, it gives us an interrupt
+ */
+static irqreturn_t gh_vcpu_irq_handler(int irq, void *data)
+{
+ struct gunyah_vcpu *vcpu = data;
+
+ complete(&vcpu->ready);
+ return IRQ_HANDLED;
+}
+
+static void gh_handle_mmio_return(struct gunyah_vcpu *vcpu, u64 *state)
+{
+ if (!vcpu->vcpu_run->mmio.is_write)
+ memcpy(&state[0], vcpu->vcpu_run->mmio.data, vcpu->vcpu_run->mmio.len);
+
+ vcpu->handle_mmio = false;
+ vcpu->vcpu_run->exit_reason = GH_VM_EXIT_UNKNOWN;
+}
+
+static bool gh_handle_mmio(struct gunyah_vcpu *vcpu,
+ struct gh_hypercall_vcpu_run_resp *vcpu_run_resp)
+{
+ int ret = 0;
+
+ if (vcpu_run_resp->state == GH_VCPU_ADDRSPACE_VMMIO_READ) {
+ vcpu->vcpu_run->mmio.is_write = 0;
+ vcpu->vcpu_run->exit_reason = GH_VM_EXIT_MMIO;
+ } else { /* GH_VCPU_ADDRSPACE_VMMIO_WRITE case */
+ ret = gh_vm_mgr_mmio_write(vcpu->f->ghvm, vcpu_run_resp->state_data[0],
+ vcpu_run_resp->state_data[1], vcpu_run_resp->state_data[2]);
+ if (!ret)
+ return true;
+
+ vcpu->vcpu_run->mmio.is_write = 1;
+ memcpy(vcpu->vcpu_run->mmio.data, &vcpu_run_resp->state_data[2],
+ vcpu_run_resp->state_data[1]);
+ }
+
+ vcpu->vcpu_run->mmio.phys_addr = vcpu_run_resp->state_data[0];
+ vcpu->vcpu_run->mmio.len = vcpu_run_resp->state_data[1];
+ vcpu->vcpu_run->exit_reason = GH_VM_EXIT_MMIO;
+
+ return false;
+}
+
+/**
+ * gh_vcpu_run() - Request Gunyah to begin scheduling this vCPU.
+ * @vcpu: The client descriptor that was obtained via gunyah_vcpu_alloc()
+ */
+static int gh_vcpu_run(struct gunyah_vcpu *vcpu)
+{
+ struct gh_hypercall_vcpu_run_resp vcpu_run_resp;
+ u64 state_data[3] = { 0 };
+ int ret = 0;
+
+ ret = gh_vm_ensure_started(vcpu->ghvm);
+ if (ret)
+ return ret;
+
+ if (mutex_lock_interruptible(&vcpu->run_lock))
+ return -ERESTARTSYS;
+
+ if (!vcpu->rsc || !vcpu->f) {
+ ret = -ENODEV;
+ goto out;
+ }
+ /* Last exit reason was EXIT_MMIO. Userspace has filled in the data, now we need to tell
+ * Gunyah about the response.
+ */
+ if (vcpu->handle_mmio)
+ gh_handle_mmio_return(vcpu, state_data);
+
+ while (!ret && !signal_pending(current) && !vcpu->vcpu_run->immediate_exit) {
+ ret = gh_hypercall_vcpu_run(vcpu->rsc->capid, state_data, &vcpu_run_resp);
+ if (ret == GH_ERROR_OK) {
+ switch (vcpu_run_resp.state) {
+ case GH_VCPU_STATE_READY:
+ if (need_resched())
+ schedule();
+ break;
+ case GH_VCPU_STATE_EXPECTS_WAKEUP:
+ case GH_VCPU_STATE_POWERED_OFF:
+ ret = wait_for_completion_interruptible(&vcpu->ready);
+ /* reinitialize completion before next VCPU_RUN. If we reinitialize
+ * after the VCPU_RUN, interrupt may have already come before
+ * we can re-initialize and then waiting for an interrupt that
+ * was already handled.
+ */
+ reinit_completion(&vcpu->ready);
+ break;
+ case GH_VCPU_STATE_BLOCKED:
+ schedule();
+ break;
+ case GH_VCPU_ADDRSPACE_VMMIO_READ:
+ case GH_VCPU_ADDRSPACE_VMMIO_WRITE:
+ if (!gh_handle_mmio(vcpu, &vcpu_run_resp)) {
+ vcpu->handle_mmio = true;
+ goto out;
+ }
+ break;
+ default:
+ pr_warn_ratelimited("Unknown vCPU state: %llx\n",
+ vcpu_run_resp.state);
+ schedule();
+ break;
+ }
+ } else if (ret == GH_ERROR_RETRY) {
+ schedule();
+ ret = 0;
+ } else
+ ret = gh_remap_error(ret);
+ }
+
+out:
+ mutex_unlock(&vcpu->run_lock);
+
+ if (signal_pending(current))
+ return -ERESTARTSYS;
+
+ return ret;
+}
+
+static long gh_vcpu_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
+{
+ struct gunyah_vcpu *vcpu = filp->private_data;
+ long ret = -EINVAL;
+
+ switch (cmd) {
+ case GH_VCPU_RUN:
+ ret = gh_vcpu_run(vcpu);
+ break;
+ case GH_VCPU_MMAP_SIZE:
+ ret = PAGE_SIZE;
+ break;
+ default:
+ break;
+ }
+ return ret;
+}
+
+static int gh_vcpu_release(struct inode *inode, struct file *filp)
+{
+ struct gunyah_vcpu *vcpu = filp->private_data;
+
+ kref_put(&vcpu->kref, vcpu_release);
+ return 0;
+}
+
+static vm_fault_t gh_vcpu_fault(struct vm_fault *vmf)
+{
+ struct gunyah_vcpu *vcpu = vmf->vma->vm_file->private_data;
+ struct page *page = NULL;
+
+ if (vmf->pgoff == 0)
+ page = virt_to_page(vcpu->vcpu_run);
+
+ get_page(page);
+ vmf->page = page;
+ return 0;
+}
+
+static const struct vm_operations_struct gh_vcpu_ops = {
+ .fault = gh_vcpu_fault,
+};
+
+static int gh_vcpu_mmap(struct file *file, struct vm_area_struct *vma)
+{
+ vma->vm_ops = &gh_vcpu_ops;
+ return 0;
+}
+
+static const struct file_operations gh_vcpu_fops = {
+ .unlocked_ioctl = gh_vcpu_ioctl,
+ .release = gh_vcpu_release,
+ .llseek = noop_llseek,
+ .mmap = gh_vcpu_mmap,
+};
+
+static int gunyah_vcpu_populate(struct gunyah_vm_resource_ticket *ticket,
+ struct gunyah_resource *ghrsc)
+{
+ struct gunyah_vcpu *vcpu = container_of(ticket, struct gunyah_vcpu, ticket);
+ int ret;
+
+ mutex_lock(&vcpu->run_lock);
+ vcpu->rsc = ghrsc;
+
+ init_completion(&vcpu->ready);
+ mutex_unlock(&vcpu->run_lock);
+
+ ret = request_irq(vcpu->rsc->irq, gh_vcpu_irq_handler, IRQF_TRIGGER_RISING, "gh_vcpu",
+ vcpu);
+ if (WARN(ret, "Failed to request vcpu irq %d: %d", vcpu->rsc->irq, ret))
+ return ret;
+
+ return ret;
+}
+
+static void gunyah_vcpu_unpopulate(struct gunyah_vm_resource_ticket *ticket,
+ struct gunyah_resource *ghrsc)
+{
+ struct gunyah_vcpu *vcpu = container_of(ticket, struct gunyah_vcpu, ticket);
+
+ mutex_lock(&vcpu->run_lock);
+ free_irq(vcpu->rsc->irq, vcpu);
+ vcpu->rsc = NULL;
+ mutex_unlock(&vcpu->run_lock);
+}
+
+static long gunyah_vcpu_bind(struct gunyah_vm_function *f)
+{
+ struct gunyah_vcpu *vcpu;
+ char name[MAX_VCPU_NAME];
+ struct file *file;
+ struct page *page;
+ int fd;
+ long r;
+
+ vcpu = kzalloc(sizeof(*vcpu), GFP_KERNEL);
+ if (!vcpu)
+ return -ENOMEM;
+
+ vcpu->f = f;
+ f->data = vcpu;
+ mutex_init(&vcpu->run_lock);
+ kref_init(&vcpu->kref);
+
+ page = alloc_page(GFP_KERNEL | __GFP_ZERO);
+ if (!page) {
+ r = -ENOMEM;
+ goto err_destroy_vcpu;
+ }
+ vcpu->vcpu_run = page_address(page);
+
+ vcpu->ticket.resource_type = GUNYAH_RESOURCE_TYPE_VCPU;
+ vcpu->ticket.label = f->fn.vcpu.vcpu_id;
+ vcpu->ticket.owner = THIS_MODULE;
+ vcpu->ticket.populate = gunyah_vcpu_populate;
+ vcpu->ticket.unpopulate = gunyah_vcpu_unpopulate;
+
+ r = ghvm_add_resource_ticket(f->ghvm, &vcpu->ticket);
+ if (r)
+ goto err_destroy_page;
+
+ fd = get_unused_fd_flags(O_CLOEXEC);
+ if (fd < 0) {
+ r = fd;
+ goto err_remove_vcpu;
+ }
+
+ if (!get_gunyah_vm(f->ghvm)) {
+ r = -ENODEV;
+ goto err_put_fd;
+ }
+ vcpu->ghvm = f->ghvm;
+
+ kref_get(&vcpu->kref);
+ snprintf(name, sizeof(name), "gh-vcpu:%d", vcpu->ticket.label);
+ file = anon_inode_getfile(name, &gh_vcpu_fops, vcpu, O_RDWR);
+ if (IS_ERR(file)) {
+ r = PTR_ERR(file);
+ goto err_put_ghvm;
+ }
+
+ fd_install(fd, file);
+
+ return fd;
+err_put_ghvm:
+ put_gunyah_vm(vcpu->ghvm);
+err_put_fd:
+ put_unused_fd(fd);
+err_remove_vcpu:
+ ghvm_remove_resource_ticket(f->ghvm, &vcpu->ticket);
+err_destroy_page:
+ free_page((unsigned long)vcpu->vcpu_run);
+err_destroy_vcpu:
+ kfree(vcpu);
+ return r;
+}
+
+static void gunyah_vcpu_release(struct gunyah_vm_function *f)
+{
+ struct gunyah_vcpu *vcpu = f->data;
+
+ ghvm_remove_resource_ticket(vcpu->f->ghvm, &vcpu->ticket);
+ vcpu->f = NULL;
+
+ kref_put(&vcpu->kref, vcpu_release);
+}
+
+DECLARE_GUNYAH_VM_FUNCTION_INIT(vcpu, gunyah_vcpu_bind, gunyah_vcpu_release);
+MODULE_DESCRIPTION("Gunyah vCPU Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/virt/gunyah/vm_mgr.c b/drivers/virt/gunyah/vm_mgr.c
index dcca255bc11f..415d2b164457 100644
--- a/drivers/virt/gunyah/vm_mgr.c
+++ b/drivers/virt/gunyah/vm_mgr.c
@@ -390,6 +390,31 @@ static int gh_vm_start(struct gunyah_vm *ghvm)
return ret;
}
+int gh_vm_ensure_started(struct gunyah_vm *ghvm)
+{
+ int ret;
+
+retry:
+ ret = down_read_interruptible(&ghvm->status_lock);
+ if (ret)
+ return ret;
+
+ if (unlikely(ghvm->vm_status == GH_RM_VM_STATUS_NO_STATE)) {
+ up_read(&ghvm->status_lock);
+ ret = gh_vm_start(ghvm);
+ if (ret)
+ return ret;
+ goto retry;
+ }
+
+ if (unlikely(ghvm->vm_status != GH_RM_VM_STATUS_READY))
+ ret = -ENODEV;
+
+ up_read(&ghvm->status_lock);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(gh_vm_ensure_started);
+
static void gh_vm_stop(struct gunyah_vm *ghvm)
{
int ret;
diff --git a/drivers/virt/gunyah/vm_mgr.h b/drivers/virt/gunyah/vm_mgr.h
index 46ba2e530be2..cd45fa3dc2c2 100644
--- a/drivers/virt/gunyah/vm_mgr.h
+++ b/drivers/virt/gunyah/vm_mgr.h
@@ -70,6 +70,7 @@ struct gunyah_vm_memory_mapping *gh_vm_mem_mapping_find(struct gunyah_vm *ghvm,
struct gunyah_vm_memory_mapping *gh_vm_mem_mapping_find_mapping(struct gunyah_vm *ghvm,
u64 gpa, u32 size);
+int gh_vm_ensure_started(struct gunyah_vm *ghvm);
int gh_vm_mgr_mmio_write(struct gunyah_vm *ghvm, u64 addr, u32 len, u64 data);
#endif
diff --git a/include/linux/gunyah.h b/include/linux/gunyah.h
index af69a3479025..42160b4de834 100644
--- a/include/linux/gunyah.h
+++ b/include/linux/gunyah.h
@@ -168,4 +168,11 @@ void gh_hypercall_hyp_identify(struct gh_hypercall_hyp_identify_resp *hyp_identi
int gh_hypercall_msgq_send(u64 capid, size_t size, uintptr_t buff, int tx_flags, bool *ready);
int gh_hypercall_msgq_recv(u64 capid, uintptr_t buff, size_t size, size_t *recv_size, bool *ready);
+struct gh_hypercall_vcpu_run_resp {
+ u64 state;
+ u64 state_data[3];
+};
+
+int gh_hypercall_vcpu_run(u64 capid, u64 *resume_data, struct gh_hypercall_vcpu_run_resp *resp);
+
#endif
diff --git a/include/uapi/linux/gunyah.h b/include/uapi/linux/gunyah.h
index 40db32aeb012..ccc0e6d0f985 100644
--- a/include/uapi/linux/gunyah.h
+++ b/include/uapi/linux/gunyah.h
@@ -53,13 +53,43 @@ struct gh_vm_dtb_config {
#define GUNYAH_FUNCTION_NAME_SIZE 32
#define GUNYAH_FUNCTION_MAX_ARG_SIZE 1024
+struct gh_fn_vcpu_arg {
+ __u32 vcpu_id;
+};
+
struct gh_vm_function {
char name[GUNYAH_FUNCTION_NAME_SIZE];
union {
+ struct gh_device_vcpu_arg vcpu;
char data[GUNYAH_FUNCTION_MAX_ARG_SIZE];
};
};
#define GH_VM_ADD_FUNCTION _IOW(GH_IOCTL_TYPE, 0x4, struct gh_vm_function)
+/* for GH_VCPU_RUN, returned by mmap(vcpu_fd, offset=0) */
+struct gh_vcpu_run {
+ /* in */
+ __u8 immediate_exit;
+ __u8 padding1[7];
+
+ /* out */
+#define GH_VM_EXIT_UNKNOWN 0
+#define GH_VM_EXIT_MMIO 1
+ __u32 exit_reason;
+
+ union {
+ /* GH_VM_EXIT_MMIO */
+ struct {
+ __u64 phys_addr;
+ __u8 data[8];
+ __u32 len;
+ __u8 is_write;
+ } mmio;
+ };
+};
+
+#define GH_VCPU_RUN _IO(GH_IOCTL_TYPE, 0x5)
+#define GH_VCPU_MMAP_SIZE _IO(GH_IOCTL_TYPE, 0x6)
+
#endif
--
2.25.1
Gunyah doorbells allow two virtual machines to signal each other using
interrupts. Add the hypercalls needed to assert the interrupt.
Signed-off-by: Elliot Berman <[email protected]>
---
arch/arm64/gunyah/gunyah_hypercall.c | 27 +++++++++++++++++++++++++++
include/linux/gunyah.h | 3 +++
2 files changed, 30 insertions(+)
diff --git a/arch/arm64/gunyah/gunyah_hypercall.c b/arch/arm64/gunyah/gunyah_hypercall.c
index dc42a4e6bd7c..00c5f91b96fd 100644
--- a/arch/arm64/gunyah/gunyah_hypercall.c
+++ b/arch/arm64/gunyah/gunyah_hypercall.c
@@ -26,6 +26,8 @@
GH_FN_ID(GH_CALL_TYPE_HYPERCALL, fn))
#define GH_HYPERCALL_HYP_IDENTIFY GH_HYPERCALL(0x0000)
+#define GH_HYPERCALL_DBL_SEND GH_HYPERCALL(0x0012)
+#define GH_HYPERCALL_DBL_SET_MASK GH_HYPERCALL(0x0015)
#define GH_HYPERCALL_MSGQ_SEND GH_HYPERCALL(0x001B)
#define GH_HYPERCALL_MSGQ_RECV GH_HYPERCALL(0x001C)
#define GH_HYPERCALL_VCPU_RUN GH_HYPERCALL(0x0065)
@@ -68,6 +70,31 @@ void gh_hypercall_hyp_identify(struct gh_hypercall_hyp_identify_resp *hyp_identi
}
EXPORT_SYMBOL_GPL(gh_hypercall_hyp_identify);
+int gh_hypercall_dbl_send(u64 capid, u64 new_flags, u64 *old_flags)
+{
+ struct arm_smccc_res res;
+
+ arm_smccc_1_1_hvc(GH_HYPERCALL_DBL_SEND, capid, new_flags, 0, &res);
+
+ if (res.a0)
+ return res.a0;
+
+ *old_flags = res.a1;
+
+ return res.a0;
+}
+EXPORT_SYMBOL_GPL(gh_hypercall_dbl_send);
+
+int gh_hypercall_dbl_set_mask(u64 capid, u64 enable_mask, u64 ack_mask)
+{
+ struct arm_smccc_res res;
+
+ arm_smccc_1_1_hvc(GH_HYPERCALL_DBL_SET_MASK, capid, enable_mask, ack_mask, 0, &res);
+
+ return res.a0;
+}
+EXPORT_SYMBOL_GPL(gh_hypercall_dbl_set_mask);
+
int gh_hypercall_msgq_send(u64 capid, size_t size, uintptr_t buff, int tx_flags, bool *ready)
{
struct arm_smccc_res res;
diff --git a/include/linux/gunyah.h b/include/linux/gunyah.h
index 42160b4de834..2c2b498a792d 100644
--- a/include/linux/gunyah.h
+++ b/include/linux/gunyah.h
@@ -163,6 +163,9 @@ static inline u16 gh_api_version(void)
void gh_hypercall_get_uid(u32 uid[4]);
void gh_hypercall_hyp_identify(struct gh_hypercall_hyp_identify_resp *hyp_identity);
+int gh_hypercall_dbl_send(u64 capid, u64 new_flags, u64 *old_flags);
+int gh_hypercall_dbl_set_mask(u64 capid, u64 enable_mask, u64 ack_mask);
+
#define GH_HYPERCALL_MSGQ_TX_FLAGS_PUSH BIT(0)
int gh_hypercall_msgq_send(u64 capid, size_t size, uintptr_t buff, int tx_flags, bool *ready);
--
2.25.1
Add framework for VM functions to handle stage-2 write faults from Gunyah
guest virtual machines. IO handlers have a range of addresses which they
apply to. Optionally, they may apply to only when the value written
matches the IO handler's value.
Co-developed-by: Prakruthi Deepak Heragu <[email protected]>
Signed-off-by: Prakruthi Deepak Heragu <[email protected]>
Signed-off-by: Elliot Berman <[email protected]>
---
drivers/virt/gunyah/vm_mgr.c | 85 +++++++++++++++++++++++++++++++++++
drivers/virt/gunyah/vm_mgr.h | 4 ++
include/linux/gunyah_vm_mgr.h | 25 +++++++++++
3 files changed, 114 insertions(+)
diff --git a/drivers/virt/gunyah/vm_mgr.c b/drivers/virt/gunyah/vm_mgr.c
index 3328b84fa681..dcca255bc11f 100644
--- a/drivers/virt/gunyah/vm_mgr.c
+++ b/drivers/virt/gunyah/vm_mgr.c
@@ -165,6 +165,91 @@ void ghvm_remove_resource_ticket(struct gunyah_vm *ghvm, struct gunyah_vm_resour
}
EXPORT_SYMBOL_GPL(ghvm_remove_resource_ticket);
+static inline bool gh_vm_io_handler_matches(struct gunyah_vm_io_handler *io_hdlr, u64 addr,
+ u64 len, u64 data)
+{
+ u64 mask = BIT_ULL(io_hdlr->len * BITS_PER_BYTE) - 1;
+
+ if (io_hdlr->addr != addr)
+ return false;
+
+ if (!io_hdlr->datamatch)
+ return true;
+
+ if (io_hdlr->len != len)
+ return false;
+
+ return (data & mask) == (io_hdlr->data & mask);
+}
+
+static struct gunyah_vm_io_handler *gh_vm_mgr_find_io_hdlr(struct gunyah_vm *ghvm, u64 addr,
+ u64 len, u64 data)
+{
+ struct gunyah_vm_io_handler *io_hdlr = NULL;
+ struct rb_node *root = NULL;
+
+ root = ghvm->mmio_handler_root.rb_node;
+ while (root) {
+ io_hdlr = rb_entry(root, struct gunyah_vm_io_handler, node);
+ if (addr < io_hdlr->addr)
+ root = root->rb_left;
+ else if (addr > io_hdlr->addr)
+ root = root->rb_right;
+ else if (gh_vm_io_handler_matches(io_hdlr, addr, len, data))
+ return io_hdlr;
+ }
+ return NULL;
+}
+
+int gh_vm_mgr_mmio_write(struct gunyah_vm *ghvm, u64 addr, u32 len, u64 data)
+{
+ struct gunyah_vm_io_handler *io_hdlr = NULL;
+
+ io_hdlr = gh_vm_mgr_find_io_hdlr(ghvm, addr, len, data);
+ if (!io_hdlr)
+ return -ENODEV;
+
+ if (io_hdlr->ops && io_hdlr->ops->write)
+ return io_hdlr->ops->write(io_hdlr, addr, len, data);
+
+ return -ENODEV;
+}
+EXPORT_SYMBOL_GPL(gh_vm_mgr_mmio_write);
+
+int gh_vm_mgr_add_io_handler(struct gunyah_vm *ghvm, struct gunyah_vm_io_handler *io_hdlr)
+{
+ struct rb_node **root, *parent = NULL;
+
+ if (io_hdlr->datamatch &&
+ (!io_hdlr->len || io_hdlr->len > (sizeof(io_hdlr->data) * BITS_PER_BYTE)))
+ return -EINVAL;
+
+ root = &ghvm->mmio_handler_root.rb_node;
+ while (*root) {
+ struct gunyah_vm_io_handler *curr = rb_entry(*root, struct gunyah_vm_io_handler,
+ node);
+
+ parent = *root;
+ if (io_hdlr->addr < curr->addr)
+ root = &((*root)->rb_left);
+ else if (io_hdlr->addr > curr->addr)
+ root = &((*root)->rb_right);
+ else
+ return -EEXIST;
+ }
+
+ rb_link_node(&io_hdlr->node, parent, root);
+ rb_insert_color(&io_hdlr->node, &ghvm->mmio_handler_root);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(gh_vm_mgr_add_io_handler);
+
+void gh_vm_mgr_remove_io_handler(struct gunyah_vm *ghvm, struct gunyah_vm_io_handler *io_hdlr)
+{
+ rb_erase(&io_hdlr->node, &ghvm->mmio_handler_root);
+}
+EXPORT_SYMBOL_GPL(gh_vm_mgr_remove_io_handler);
+
static void ghvm_put(struct kref *kref)
{
struct gunyah_vm *ghvm = container_of(kref, struct gunyah_vm, kref);
diff --git a/drivers/virt/gunyah/vm_mgr.h b/drivers/virt/gunyah/vm_mgr.h
index 8a71f97960bc..46ba2e530be2 100644
--- a/drivers/virt/gunyah/vm_mgr.h
+++ b/drivers/virt/gunyah/vm_mgr.h
@@ -58,6 +58,8 @@ struct gunyah_vm {
struct list_head resources;
struct list_head resource_tickets;
+ struct rb_root mmio_handler_root;
+
struct list_head functions;
};
@@ -68,4 +70,6 @@ struct gunyah_vm_memory_mapping *gh_vm_mem_mapping_find(struct gunyah_vm *ghvm,
struct gunyah_vm_memory_mapping *gh_vm_mem_mapping_find_mapping(struct gunyah_vm *ghvm,
u64 gpa, u32 size);
+int gh_vm_mgr_mmio_write(struct gunyah_vm *ghvm, u64 addr, u32 len, u64 data);
+
#endif
diff --git a/include/linux/gunyah_vm_mgr.h b/include/linux/gunyah_vm_mgr.h
index 3e6e6e0ab7f0..5b17cb54ba01 100644
--- a/include/linux/gunyah_vm_mgr.h
+++ b/include/linux/gunyah_vm_mgr.h
@@ -79,4 +79,29 @@ struct gunyah_vm_resource_ticket {
int ghvm_add_resource_ticket(struct gunyah_vm *ghvm, struct gunyah_vm_resource_ticket *ticket);
void ghvm_remove_resource_ticket(struct gunyah_vm *ghvm, struct gunyah_vm_resource_ticket *ticket);
+/*
+ * gunyah_vm_io_handler contains the info about an io device and its associated
+ * addr and the ops associated with the io device.
+ */
+struct gunyah_vm_io_handler {
+ struct rb_node node;
+ u64 addr;
+
+ bool datamatch;
+ u8 len;
+ u64 data;
+ struct gunyah_vm_io_handler_ops *ops;
+};
+
+/*
+ * gunyah_vm_io_handler_ops contains function pointers associated with an iodevice.
+ */
+struct gunyah_vm_io_handler_ops {
+ int (*read)(struct gunyah_vm_io_handler *io_dev, u64 addr, u32 len, u64 data);
+ int (*write)(struct gunyah_vm_io_handler *io_dev, u64 addr, u32 len, u64 data);
+};
+
+int gh_vm_mgr_add_io_handler(struct gunyah_vm *ghvm, struct gunyah_vm_io_handler *io_dev);
+void gh_vm_mgr_remove_io_handler(struct gunyah_vm *ghvm, struct gunyah_vm_io_handler *io_dev);
+
#endif
--
2.25.1
Allow userspace to attach an ioeventfd to an mmio address within the guest.
Co-developed-by: Prakruthi Deepak Heragu <[email protected]>
Signed-off-by: Prakruthi Deepak Heragu <[email protected]>
Signed-off-by: Elliot Berman <[email protected]>
---
Documentation/virt/gunyah/vm-manager.rst | 21 +++++
drivers/virt/gunyah/Kconfig | 10 +++
drivers/virt/gunyah/Makefile | 1 +
drivers/virt/gunyah/gunyah_ioeventfd.c | 109 +++++++++++++++++++++++
include/uapi/linux/gunyah.h | 10 +++
5 files changed, 151 insertions(+)
create mode 100644 drivers/virt/gunyah/gunyah_ioeventfd.c
diff --git a/Documentation/virt/gunyah/vm-manager.rst b/Documentation/virt/gunyah/vm-manager.rst
index b6cf8db826b8..19ec859a7912 100644
--- a/Documentation/virt/gunyah/vm-manager.rst
+++ b/Documentation/virt/gunyah/vm-manager.rst
@@ -164,3 +164,24 @@ the irqfd.label.
GH_IRQFD_LEVEL configures the corresponding doorbell to behave like a level
triggered interrupt.
+
+Type: "ioeventfd"
+^^^^^^^^^^^^^^^^^
+
+::
+
+ struct gh_fn_ioeventfd_arg {
+ __u64 datamatch;
+ __u64 addr; /* legal mmio address */
+ __u32 len; /* 1, 2, 4, or 8 bytes */
+ __s32 fd;
+ #define GH_IOEVENTFD_DATAMATCH (1UL << 0)
+ __u32 flags;
+ };
+
+Attaches an ioeventfd to a legal mmio address within the guest. A guest write
+in the registered address will signal the provided event instead of triggering
+an exit on the GH_VCPU_RUN ioctl.
+
+If datamatch flag is set, the event will be signaled only if the written value
+to the registered address is equal to datamatch in struct gh_fn_ioeventfd_arg.
diff --git a/drivers/virt/gunyah/Kconfig b/drivers/virt/gunyah/Kconfig
index 42842e755a04..06f1082f98f0 100644
--- a/drivers/virt/gunyah/Kconfig
+++ b/drivers/virt/gunyah/Kconfig
@@ -59,5 +59,15 @@ config GUNYAH_IRQFD
Enable kernel support for creating irqfds which can raise an interrupt
on Gunyah virtual machine.
+ Say Y/M here if unsure and you want to support Gunyah VMMs.
+
+config GUNYAH_IOEVENTFD
+ tristate "Gunyah ioeventfd interface"
+ depends on GUNYAH_RESOURCE_MANAGER
+ depends on GUNYAH_VM_MANAGER
+ help
+ Enable kernel support for creating ioeventfds which can alert userspace
+ when a Gunyah virtual machine accesses a memory address.
+
Say Y/M here if unsure and you want to support Gunyah VMMs.
endif
diff --git a/drivers/virt/gunyah/Makefile b/drivers/virt/gunyah/Makefile
index 065eeb060597..bcffb99eb28f 100644
--- a/drivers/virt/gunyah/Makefile
+++ b/drivers/virt/gunyah/Makefile
@@ -7,3 +7,4 @@ obj-$(CONFIG_GUNYAH_RESOURCE_MANAGER) += gunyah_rsc_mgr.o
obj-$(CONFIG_GUNYAH_VCPU) += gunyah_vcpu.o
obj-$(CONFIG_GUNYAH_IRQFD) += gunyah_irqfd.o
+obj-$(CONFIG_GUNYAH_IOEVENTFD) += gunyah_ioeventfd.o
diff --git a/drivers/virt/gunyah/gunyah_ioeventfd.c b/drivers/virt/gunyah/gunyah_ioeventfd.c
new file mode 100644
index 000000000000..5f43057eb768
--- /dev/null
+++ b/drivers/virt/gunyah/gunyah_ioeventfd.c
@@ -0,0 +1,109 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
+ */
+
+#include <linux/eventfd.h>
+#include <linux/file.h>
+#include <linux/fs.h>
+#include <linux/gunyah.h>
+#include <linux/gunyah_vm_mgr.h>
+#include <linux/module.h>
+#include <linux/printk.h>
+
+#include <uapi/linux/gunyah.h>
+
+struct gunyah_ioeventfd {
+ struct gunyah_vm_function *f;
+ struct gunyah_vm_io_handler io_handler;
+
+ struct eventfd_ctx *ctx;
+};
+
+static int gh_write_ioeventfd(struct gunyah_vm_io_handler *io_dev, u64 addr, u32 len, u64 data)
+{
+ struct gunyah_ioeventfd *iofd = container_of(io_dev, struct gunyah_ioeventfd, io_handler);
+
+ eventfd_signal(iofd->ctx, 1);
+ return 0;
+}
+
+static struct gunyah_vm_io_handler_ops io_ops = {
+ .write = gh_write_ioeventfd,
+};
+
+static long gunyah_ioeventfd_bind(struct gunyah_vm_function *f)
+{
+ struct eventfd_ctx *ctx = NULL;
+ struct gunyah_ioeventfd *iofd;
+ const struct gh_fn_ioeventfd_arg *args = &f->fn.ioeventfd;
+ int ret;
+
+ /* must be natural-word sized, or 0 to ignore length */
+ switch (args->len) {
+ case 0:
+ case 1:
+ case 2:
+ case 4:
+ case 8:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* check for range overflow */
+ if (args->addr + args->len < args->addr)
+ return -EINVAL;
+
+ /* ioeventfd with no length can't be combined with DATAMATCH */
+ if (!args->len && (args->flags & GH_IOEVENTFD_DATAMATCH))
+ return -EINVAL;
+
+ ctx = eventfd_ctx_fdget(args->fd);
+ if (IS_ERR(ctx))
+ return PTR_ERR(ctx);
+
+ iofd = kzalloc(sizeof(*iofd), GFP_KERNEL);
+ if (!iofd) {
+ ret = -ENOMEM;
+ goto err_eventfd;
+ }
+
+ f->data = iofd;
+ iofd->f = f;
+
+ iofd->ctx = ctx;
+
+ if (args->flags & GH_IOEVENTFD_DATAMATCH) {
+ iofd->io_handler.datamatch = true;
+ iofd->io_handler.len = args->len;
+ iofd->io_handler.data = args->datamatch;
+ }
+ iofd->io_handler.addr = args->addr;
+ iofd->io_handler.ops = &io_ops;
+
+ ret = gh_vm_mgr_add_io_handler(f->ghvm, &iofd->io_handler);
+ if (ret)
+ goto err_io_dev_add;
+
+ return 0;
+
+err_io_dev_add:
+ kfree(iofd);
+err_eventfd:
+ eventfd_ctx_put(ctx);
+ return ret;
+}
+
+static void gunyah_ioeventfd_release(struct gunyah_vm_function *f)
+{
+ struct gunyah_ioeventfd *iofd = f->data;
+
+ eventfd_ctx_put(iofd->ctx);
+ gh_vm_mgr_remove_io_handler(iofd->f->ghvm, &iofd->io_handler);
+ kfree(iofd);
+}
+
+DECLARE_GUNYAH_VM_FUNCTION_INIT(ioeventfd, gunyah_ioeventfd_bind, gunyah_ioeventfd_release);
+MODULE_DESCRIPTION("Gunyah ioeventfds");
+MODULE_LICENSE("GPL");
diff --git a/include/uapi/linux/gunyah.h b/include/uapi/linux/gunyah.h
index 5eab212c7bf5..5da9fb933bae 100644
--- a/include/uapi/linux/gunyah.h
+++ b/include/uapi/linux/gunyah.h
@@ -65,11 +65,21 @@ struct gh_fn_irqfd_arg {
__u32 flags;
};
+struct gh_fn_ioeventfd_arg {
+ __u64 datamatch;
+ __u64 addr; /* legal mmio address */
+ __u32 len; /* 1, 2, 4, or 8 bytes; or 0 to ignore length */
+ __s32 fd;
+#define GH_IOEVENTFD_DATAMATCH (1UL << 0)
+ __u32 flags;
+};
+
struct gh_vm_function {
char name[GUNYAH_FUNCTION_NAME_SIZE];
union {
struct gh_fn_vcpu_arg vcpu;
struct gh_fn_irqfd_arg irqfd;
+ struct gh_fn_ioeventfd_arg ioeventfd;
char data[GUNYAH_FUNCTION_MAX_ARG_SIZE];
};
};
--
2.25.1
Add Gunyah Resource Manager RPC to launch an unauthenticated VM.
Signed-off-by: Elliot Berman <[email protected]>
---
drivers/virt/gunyah/Makefile | 2 +-
drivers/virt/gunyah/rsc_mgr.h | 41 ++++++
drivers/virt/gunyah/rsc_mgr_rpc.c | 224 ++++++++++++++++++++++++++++++
include/linux/gunyah_rsc_mgr.h | 50 +++++++
4 files changed, 316 insertions(+), 1 deletion(-)
create mode 100644 drivers/virt/gunyah/rsc_mgr_rpc.c
diff --git a/drivers/virt/gunyah/Makefile b/drivers/virt/gunyah/Makefile
index e7cf59b9e64e..5caa05267a58 100644
--- a/drivers/virt/gunyah/Makefile
+++ b/drivers/virt/gunyah/Makefile
@@ -1,4 +1,4 @@
obj-$(CONFIG_GUNYAH) += gunyah.o
-gunyah_rsc_mgr-y += rsc_mgr.o
+gunyah_rsc_mgr-y += rsc_mgr.o rsc_mgr_rpc.o
obj-$(CONFIG_GUNYAH_RESOURCE_MANAGER) += gunyah_rsc_mgr.o
diff --git a/drivers/virt/gunyah/rsc_mgr.h b/drivers/virt/gunyah/rsc_mgr.h
index b5bb36a7a4cc..1f9e3c38038e 100644
--- a/drivers/virt/gunyah/rsc_mgr.h
+++ b/drivers/virt/gunyah/rsc_mgr.h
@@ -33,4 +33,45 @@ struct gh_rm_rpc;
int gh_rm_call(struct gh_rm_rpc *rsc_mgr, u32 message_id, void *req_buff, size_t req_buff_size,
void **resp_buf, size_t *resp_buff_size);
+/* Message IDs: VM Management */
+#define GH_RM_RPC_VM_ALLOC_VMID 0x56000001
+#define GH_RM_RPC_VM_DEALLOC_VMID 0x56000002
+#define GH_RM_RPC_VM_START 0x56000004
+#define GH_RM_RPC_VM_STOP 0x56000005
+#define GH_RM_RPC_VM_CONFIG_IMAGE 0x56000009
+#define GH_RM_RPC_VM_INIT 0x5600000B
+#define GH_RM_RPC_VM_GET_HYP_RESOURCES 0x56000020
+#define GH_RM_RPC_VM_GET_VMID 0x56000024
+
+/* Call: CONSOLE_OPEN, CONSOLE_CLOSE, CONSOLE_FLUSH */
+struct gh_vm_common_vmid_req {
+ __le16 vmid;
+ __le16 reserved0;
+} __packed;
+
+/* Call: VM_STOP */
+struct gh_vm_stop_req {
+ __le16 vmid;
+ u8 flags;
+ u8 reserved;
+ __le32 stop_reason;
+} __packed;
+
+/* Call: VM_CONFIG_IMAGE */
+struct gh_vm_config_image_req {
+ __le16 vmid;
+ __le16 auth_mech;
+ __le32 mem_handle;
+ __le64 image_offset;
+ __le64 image_size;
+ __le64 dtb_offset;
+ __le64 dtb_size;
+} __packed;
+
+/* Call: GET_HYP_RESOURCES */
+struct gh_vm_get_hyp_resources_resp {
+ __le32 n_entries;
+ struct gh_rm_hyp_resource entries[];
+} __packed;
+
#endif
diff --git a/drivers/virt/gunyah/rsc_mgr_rpc.c b/drivers/virt/gunyah/rsc_mgr_rpc.c
new file mode 100644
index 000000000000..8d9c1c635a27
--- /dev/null
+++ b/drivers/virt/gunyah/rsc_mgr_rpc.c
@@ -0,0 +1,224 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
+ */
+
+#include <linux/gunyah_rsc_mgr.h>
+
+#include "rsc_mgr.h"
+
+/*
+ * Several RM calls take only a VMID as a parameter and give only standard
+ * response back. Deduplicate boilerplate code by using this common call.
+ */
+static int gh_rm_common_vmid_call(struct gh_rm_rpc *rm, u32 message_id, u16 vmid)
+{
+ void *resp = NULL;
+ struct gh_vm_common_vmid_req req_payload = {
+ .vmid = cpu_to_le16(vmid),
+ };
+ size_t resp_size;
+ int ret;
+
+ ret = gh_rm_call(rm, message_id, &req_payload, sizeof(req_payload), &resp, &resp_size);
+ if (!ret)
+ kfree(resp);
+
+ WARN_ON(!ret && resp_size);
+
+ return ret;
+}
+
+/**
+ * gh_rm_alloc_vmid() - Allocate a new VM in Gunyah. Returns the VM identifier.
+ * @vmid: Use GH_VMID_INVAL to dynamically allocate a VM. A reserved VMID can also be requested
+ * for a special-purpose platform-defined VM.
+ *
+ * Returns - the allocated VMID or negative value on error
+ */
+int gh_rm_alloc_vmid(struct gh_rm_rpc *rm, u16 vmid)
+{
+ void *resp;
+ struct gh_vm_common_vmid_req req_payload = {
+ .vmid = cpu_to_le16(vmid),
+ };
+ struct gh_vm_common_vmid_req *resp_payload;
+ size_t resp_size;
+ int ret;
+
+ if (vmid == GH_VMID_INVAL)
+ vmid = 0;
+
+ ret = gh_rm_call(rm, GH_RM_RPC_VM_ALLOC_VMID, &req_payload, sizeof(req_payload), &resp,
+ &resp_size);
+ if (ret)
+ return ret;
+
+ if (!vmid) {
+ if (resp_size != sizeof(*resp_payload)) {
+ ret = -EINVAL;
+ } else {
+ resp_payload = resp;
+ ret = resp_payload->vmid;
+ }
+ }
+ kfree(resp);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(gh_rm_alloc_vmid);
+
+/**
+ * gh_rm_dealloc_vmid() - Dispose the VMID
+ * @vmid: VM identifier
+ */
+int gh_rm_dealloc_vmid(struct gh_rm_rpc *rm, u16 vmid)
+{
+ return gh_rm_common_vmid_call(rm, GH_RM_RPC_VM_DEALLOC_VMID, vmid);
+}
+EXPORT_SYMBOL_GPL(gh_rm_dealloc_vmid);
+
+/**
+ * gh_rm_vm_start() - Move the VM into "ready to run" state
+ * @vmid: VM identifier
+ *
+ * On VMs which use proxy scheduling, vcpu_run is needed to actually run the VM.
+ * On VMs which use Gunyah's scheduling, the vCPUs start executing in accordance with Gunyah
+ * scheduling policies.
+ */
+int gh_rm_vm_start(struct gh_rm_rpc *rm, u16 vmid)
+{
+ return gh_rm_common_vmid_call(rm, GH_RM_RPC_VM_START, vmid);
+}
+EXPORT_SYMBOL_GPL(gh_rm_vm_start);
+
+/**
+ * gh_rm_vm_stop() - Send a request to Resource Manager VM to stop a VM.
+ * @vmid: VM identifier
+ */
+int gh_rm_vm_stop(struct gh_rm_rpc *rm, u16 vmid)
+{
+ struct gh_vm_stop_req req_payload = {
+ .vmid = cpu_to_le16(vmid),
+ };
+ void *resp;
+ size_t resp_size;
+ int ret;
+
+ ret = gh_rm_call(rm, GH_RM_RPC_VM_STOP, &req_payload, sizeof(req_payload),
+ &resp, &resp_size);
+ if (ret)
+ return ret;
+ kfree(resp);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(gh_rm_vm_stop);
+
+int gh_rm_vm_configure(struct gh_rm_rpc *rm, u16 vmid, enum gh_rm_vm_auth_mechanism auth_mechanism,
+ u32 mem_handle, u64 image_offset, u64 image_size, u64 dtb_offset, u64 dtb_size)
+{
+ struct gh_vm_config_image_req req_payload = { 0 };
+ void *resp;
+ size_t resp_size;
+ int ret;
+
+ req_payload.vmid = cpu_to_le16(vmid);
+ req_payload.auth_mech = cpu_to_le32(auth_mechanism);
+ req_payload.mem_handle = cpu_to_le32(mem_handle);
+ req_payload.image_offset = cpu_to_le64(image_offset);
+ req_payload.image_size = cpu_to_le64(image_size);
+ req_payload.dtb_offset = cpu_to_le64(dtb_offset);
+ req_payload.dtb_size = cpu_to_le64(dtb_size);
+
+ ret = gh_rm_call(rm, GH_RM_RPC_VM_CONFIG_IMAGE, &req_payload, sizeof(req_payload),
+ &resp, &resp_size);
+ if (ret)
+ return ret;
+ kfree(resp);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(gh_rm_vm_configure);
+
+/**
+ * gh_rm_vm_init() - Move the VM to initialized state.
+ * @vmid: VM identifier
+ *
+ * RM will allocate needed resources for the VM. After gh_rm_vm_init, gh_rm_get_hyp_resources()
+ * can be called to learn of the capabilities we can use with the new VM.
+ */
+int gh_rm_vm_init(struct gh_rm_rpc *rm, u16 vmid)
+{
+ return gh_rm_common_vmid_call(rm, GH_RM_RPC_VM_INIT, vmid);
+}
+EXPORT_SYMBOL_GPL(gh_rm_vm_init);
+
+/**
+ * gh_rm_get_hyp_resources() - Retrieve hypervisor resources (capabilities) associated with a VM
+ * @vmid: VMID of the other VM to get the resources of
+ * @resources: Set by gh_rm_get_hyp_resources and contains the returned hypervisor resources.
+ *
+ * Return: >=0 value indicates the number of gh_rm_hyp_resource entries filled into *resources
+ */
+ssize_t gh_rm_get_hyp_resources(struct gh_rm_rpc *rm, u16 vmid,
+ struct gh_rm_hyp_resource **resources)
+{
+ struct gh_vm_get_hyp_resources_resp *resp;
+ size_t resp_size;
+ int ret;
+ struct gh_vm_common_vmid_req req_payload = {
+ .vmid = cpu_to_le16(vmid),
+ };
+
+ ret = gh_rm_call(rm, GH_RM_RPC_VM_GET_HYP_RESOURCES,
+ &req_payload, sizeof(req_payload),
+ (void **)&resp, &resp_size);
+ if (ret)
+ return ret;
+
+ if (resp_size < sizeof(*resp) ||
+ (sizeof(*resp->entries) && (resp->n_entries > U32_MAX / sizeof(*resp->entries))) ||
+ (resp_size != sizeof(*resp) + (resp->n_entries * sizeof(*resp->entries)))) {
+ ret = -EIO;
+ goto out;
+ }
+
+ *resources = kmemdup(resp->entries, (resp->n_entries * sizeof(*resp->entries)), GFP_KERNEL);
+ ret = resp->n_entries;
+
+out:
+ kfree(resp);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(gh_rm_get_hyp_resources);
+
+/**
+ * gh_rm_get_vmid() - Retrieve VMID of this virtual machine
+ * @vmid: Filled with the VMID of this VM
+ */
+int gh_rm_get_vmid(struct gh_rm_rpc *rm, u16 *vmid)
+{
+ static u16 cached_vmid = GH_VMID_INVAL;
+ void *resp;
+ size_t resp_size;
+ int ret;
+ int payload = 0;
+
+ if (cached_vmid != GH_VMID_INVAL) {
+ *vmid = cached_vmid;
+ return 0;
+ }
+
+ ret = gh_rm_call(rm, GH_RM_RPC_VM_GET_VMID, &payload, sizeof(payload), &resp, &resp_size);
+ if (ret)
+ return ret;
+
+ if (resp_size != sizeof(*vmid))
+ return -EIO;
+ *vmid = *(u16 *)resp;
+ kfree(resp);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(gh_rm_get_vmid);
diff --git a/include/linux/gunyah_rsc_mgr.h b/include/linux/gunyah_rsc_mgr.h
index b4f55c19954b..eb94b48c41de 100644
--- a/include/linux/gunyah_rsc_mgr.h
+++ b/include/linux/gunyah_rsc_mgr.h
@@ -15,4 +15,54 @@
/* Gunyah recognizes VMID0 as an alias to the current VM's ID */
#define GH_VMID_SELF 0
+struct gh_rm_rpc;
+
+enum gh_rm_vm_status {
+ GH_RM_VM_STATUS_NO_STATE = 0,
+ GH_RM_VM_STATUS_INIT = 1,
+ GH_RM_VM_STATUS_READY = 2,
+ GH_RM_VM_STATUS_RUNNING = 3,
+ GH_RM_VM_STATUS_PAUSED = 4,
+ GH_RM_VM_STATUS_LOAD = 5,
+ GH_RM_VM_STATUS_AUTH = 6,
+ GH_RM_VM_STATUS_INIT_FAILED = 8,
+ GH_RM_VM_STATUS_EXITED = 9,
+ GH_RM_VM_STATUS_RESETTING = 10,
+ GH_RM_VM_STATUS_RESET = 11,
+};
+
+/* RPC Calls */
+int gh_rm_alloc_vmid(struct gh_rm_rpc *rm, u16 vmid);
+int gh_rm_dealloc_vmid(struct gh_rm_rpc *rm, u16 vmid);
+int gh_rm_vm_start(struct gh_rm_rpc *rm, u16 vmid);
+int gh_rm_vm_stop(struct gh_rm_rpc *rm, u16 vmid);
+
+enum gh_rm_vm_auth_mechanism {
+ GH_RM_VM_AUTH_NONE = 0,
+ GH_RM_VM_AUTH_QCOM_PIL_ELF = 1,
+ GH_RM_VM_AUTH_QCOM_ANDROID_PVM = 2,
+};
+
+int gh_rm_vm_configure(struct gh_rm_rpc *rm, u16 vmid, enum gh_rm_vm_auth_mechanism auth_mechanism,
+ u32 mem_handle, u64 image_offset, u64 image_size,
+ u64 dtb_offset, u64 dtb_size);
+int gh_rm_vm_init(struct gh_rm_rpc *rm, u16 vmid);
+
+struct gh_rm_hyp_resource {
+ u8 type;
+ u8 reserved;
+ __le16 partner_vmid;
+ __le32 resource_handle;
+ __le32 resource_label;
+ __le64 cap_id;
+ __le32 virq_handle;
+ __le32 virq;
+ __le64 base;
+ __le64 size;
+} __packed;
+
+ssize_t gh_rm_get_hyp_resources(struct gh_rm_rpc *rm, u16 vmid,
+ struct gh_rm_hyp_resource **resources);
+int gh_rm_get_vmid(struct gh_rm_rpc *rm, u16 *vmid);
+
#endif
--
2.25.1
Gunyah message queues are a unidirectional inter-VM pipe for messages up
to 1024 bytes. This driver supports pairing a receiver message queue and
a transmitter message queue to expose a single mailbox channel.
Signed-off-by: Elliot Berman <[email protected]>
---
Documentation/virt/gunyah/message-queue.rst | 8 +
MAINTAINERS | 1 +
drivers/mailbox/Kconfig | 10 +
drivers/mailbox/Makefile | 2 +
drivers/mailbox/gunyah-msgq.c | 229 ++++++++++++++++++++
include/linux/gunyah.h | 61 +++++-
6 files changed, 309 insertions(+), 2 deletions(-)
create mode 100644 drivers/mailbox/gunyah-msgq.c
diff --git a/Documentation/virt/gunyah/message-queue.rst b/Documentation/virt/gunyah/message-queue.rst
index be4ab289236a..9245dbe6dd8a 100644
--- a/Documentation/virt/gunyah/message-queue.rst
+++ b/Documentation/virt/gunyah/message-queue.rst
@@ -54,3 +54,11 @@ vIRQ: two TX message queues will have two vIRQs (and two capability IDs).
| | | | | |
| | | | | |
+---------------+ +-----------------+ +---------------+
+
+Gunyah message queues are exposed as mailboxes. To create the mailbox, create
+a mbox_client and call `gh_msgq_init`. On receipt of the RX_READY interrupt,
+all messages in the RX message queue are read and pushed via the `rx_callback`
+of the registered mbox_client.
+
+.. kernel-doc:: drivers/mailbox/gunyah-msgq.c
+ :identifiers: gh_msgq_init
diff --git a/MAINTAINERS b/MAINTAINERS
index 36698df6b0e5..667480bfd387 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -8942,6 +8942,7 @@ S: Supported
F: Documentation/devicetree/bindings/firmware/gunyah-hypervisor.yaml
F: Documentation/virt/gunyah/
F: arch/arm64/gunyah/
+F: drivers/mailbox/gunyah-msgq.c
F: drivers/virt/gunyah/
F: include/linux/gunyah.h
diff --git a/drivers/mailbox/Kconfig b/drivers/mailbox/Kconfig
index 05d6fae800e3..baf9451c5f04 100644
--- a/drivers/mailbox/Kconfig
+++ b/drivers/mailbox/Kconfig
@@ -41,6 +41,16 @@ config IMX_MBOX
help
Mailbox implementation for i.MX Messaging Unit (MU).
+config GUNYAH_MESSAGE_QUEUES
+ tristate "Gunyah Message Queue Mailbox"
+ depends on GUNYAH
+ help
+ Mailbox implementation for Gunyah Message Queues. Gunyah message queues
+ are an IPC mechanism to pass short messages between virtual machines
+ running under the Gunyah hypervisor.
+
+ Say Y here if you run Linux as a Gunyah virtual machine.
+
config PLATFORM_MHU
tristate "Platform MHU Mailbox"
depends on OF
diff --git a/drivers/mailbox/Makefile b/drivers/mailbox/Makefile
index fc9376117111..5f929bb55e9a 100644
--- a/drivers/mailbox/Makefile
+++ b/drivers/mailbox/Makefile
@@ -55,6 +55,8 @@ obj-$(CONFIG_MTK_CMDQ_MBOX) += mtk-cmdq-mailbox.o
obj-$(CONFIG_ZYNQMP_IPI_MBOX) += zynqmp-ipi-mailbox.o
+obj-$(CONFIG_GUNYAH) += gunyah-msgq.o
+
obj-$(CONFIG_SUN6I_MSGBOX) += sun6i-msgbox.o
obj-$(CONFIG_SPRD_MBOX) += sprd-mailbox.o
diff --git a/drivers/mailbox/gunyah-msgq.c b/drivers/mailbox/gunyah-msgq.c
new file mode 100644
index 000000000000..d5a324e85428
--- /dev/null
+++ b/drivers/mailbox/gunyah-msgq.c
@@ -0,0 +1,229 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
+ */
+
+#include <linux/mailbox_controller.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/gunyah.h>
+#include <linux/printk.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/wait.h>
+
+#define mbox_chan_to_msgq(chan) (container_of(chan->mbox, struct gh_msgq, mbox))
+
+static inline bool gh_msgq_has_tx(struct gh_msgq *msgq)
+{
+ return msgq->tx_ghrsc->type == GUNYAH_RESOURCE_TYPE_MSGQ_TX;
+}
+
+static inline bool gh_msgq_has_rx(struct gh_msgq *msgq)
+{
+ return msgq->rx_ghrsc->type == GUNYAH_RESOURCE_TYPE_MSGQ_RX;
+}
+
+static irqreturn_t gh_msgq_rx_irq_handler(int irq, void *data)
+{
+ struct gh_msgq *msgq = data;
+ struct gh_msgq_rx_data rx_data;
+ unsigned long gh_err;
+ ssize_t ret;
+ bool ready = false;
+
+ do {
+ gh_err = gh_hypercall_msgq_recv(msgq->rx_ghrsc->capid,
+ (uintptr_t)&rx_data.data, sizeof(rx_data.data),
+ &rx_data.length, &ready);
+ if (gh_err == GH_ERROR_OK) {
+ mbox_chan_received_data(gh_msgq_chan(msgq), &rx_data);
+ } else if (GH_ERROR_MSGQUEUE_EMPTY) {
+ break;
+ } else {
+ pr_warn("Failed to receive data from msgq for %s: %ld\n",
+ msgq->mbox.dev ? dev_name(msgq->mbox.dev) : "", ret);
+ break;
+ }
+ } while (ready);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t gh_msgq_tx_irq_handler(int irq, void *data)
+{
+ struct gh_msgq *msgq = data;
+
+ mbox_chan_txdone(gh_msgq_chan(msgq), 0);
+
+ return IRQ_HANDLED;
+}
+
+static void gh_msgq_txdone_tasklet(struct tasklet_struct *tasklet)
+{
+ struct gh_msgq *msgq = container_of(tasklet, struct gh_msgq, txdone_tasklet);
+
+ mbox_chan_txdone(gh_msgq_chan(msgq), msgq->last_status);
+}
+
+static int gh_msgq_send_data(struct mbox_chan *chan, void *data)
+{
+ struct gh_msgq *msgq = mbox_chan_to_msgq(chan);
+ struct gh_msgq_tx_data *msgq_data = data;
+ u64 tx_flags = 0;
+ unsigned long ret;
+ bool ready;
+
+ if (msgq_data->push)
+ tx_flags |= GH_HYPERCALL_MSGQ_TX_FLAGS_PUSH;
+
+ ret = gh_hypercall_msgq_send(msgq->tx_ghrsc->capid, msgq_data->length,
+ (uintptr_t)msgq_data->data, tx_flags, &ready);
+
+ /**
+ * unlikely because Linux tracks state of msgq and should not try to
+ * send message when msgq is full.
+ */
+ if (unlikely(ret == GH_ERROR_MSGQUEUE_FULL))
+ return -EAGAIN;
+
+ /**
+ * Propagate all other errors to client. If we return error to mailbox
+ * framework, then no other messages can be sent and nobody will know
+ * to retry this message.
+ */
+ msgq->last_status = gh_remap_error(ret);
+
+ /**
+ * This message was successfully sent, but message queue isn't ready to
+ * receive more messages because it's now full. Mailbox framework
+ * requires that we only report that message was transmitted only when
+ * we're ready to transmit another message. We'll get that in the form
+ * of tx IRQ once the other side starts to drain the msgq.
+ */
+ if (ret == GH_ERROR_OK && !ready)
+ return 0;
+
+ /**
+ * We can send more messages. Mailbox framework requires that tx done
+ * happens asynchronously to sending the message. Gunyah message queues
+ * tell us right away on the hypercall return whether we can send more
+ * messages. To work around this, defer the txdone to a tasklet.
+ */
+ tasklet_schedule(&msgq->txdone_tasklet);
+
+ return 0;
+}
+
+struct mbox_chan_ops gh_msgq_ops = {
+ .send_data = gh_msgq_send_data,
+};
+
+/**
+ * gh_msgq_init() - Initialize a Gunyah message queue with an mbox_client
+ * @parent: optional, device parent used for the mailbox controller
+ * @msgq: Pointer to the gh_msgq to initialize
+ * @cl: A mailbox client to bind to the mailbox channel that the message queue creates
+ * @tx_ghrsc: optional, the transmission side of the message queue
+ * @rx_ghrsc: optional, the receiving side of the message queue
+ *
+ * At least one of tx_ghrsc and rx_ghrsc should be not NULL. Most message queue use cases come with
+ * a pair of message queues to facilitiate bidirectional communication. When tx_ghrsc is set,
+ * the client can send messages with mbox_send_message(gh_msgq_chan(msgq), msg). When rx_ghrsc
+ * is set, the mbox_client should register an .rx_callback() and the message queue driver will
+ * push all available messages upon receiving the RX ready interrupt. The messages should be
+ * consumed or copied by the client right away as the gh_msgq_rx_data will be replaced/destroyed
+ * after the callback.
+ *
+ * Returns - 0 on success, negative otherwise
+ */
+int gh_msgq_init(struct device *parent, struct gh_msgq *msgq, struct mbox_client *cl,
+ struct gunyah_resource *tx_ghrsc, struct gunyah_resource *rx_ghrsc)
+{
+ int ret;
+
+ /* Must have at least a tx_ghrsc or rx_ghrsc and that they are the right device types */
+ if ((!tx_ghrsc && !rx_ghrsc) ||
+ (tx_ghrsc && tx_ghrsc->type != GUNYAH_RESOURCE_TYPE_MSGQ_TX) ||
+ (rx_ghrsc && rx_ghrsc->type != GUNYAH_RESOURCE_TYPE_MSGQ_RX))
+ return -EINVAL;
+
+ msgq->tx_ghrsc = tx_ghrsc;
+ msgq->rx_ghrsc = rx_ghrsc;
+
+ msgq->mbox.dev = parent;
+ msgq->mbox.ops = &gh_msgq_ops;
+ msgq->mbox.num_chans = 1;
+ msgq->mbox.chans = kcalloc(msgq->mbox.num_chans, sizeof(*msgq->mbox.chans), GFP_KERNEL);
+ if (!msgq->mbox.chans)
+ return -ENOMEM;
+ msgq->mbox.txdone_irq = true;
+
+ if (gh_msgq_has_tx(msgq)) {
+ ret = request_irq(msgq->tx_ghrsc->irq, gh_msgq_tx_irq_handler, 0, "gh_msgq_tx",
+ msgq);
+ if (ret)
+ goto err_chans;
+ }
+
+ if (gh_msgq_has_rx(msgq)) {
+ ret = request_threaded_irq(msgq->rx_ghrsc->irq, NULL, gh_msgq_rx_irq_handler,
+ IRQF_ONESHOT, "gh_msgq_rx", msgq);
+ if (ret)
+ goto err_tx_irq;
+ }
+
+ tasklet_setup(&msgq->txdone_tasklet, gh_msgq_txdone_tasklet);
+
+ ret = mbox_controller_register(&msgq->mbox);
+ if (ret)
+ goto err_rx_irq;
+
+ ret = mbox_bind_client(gh_msgq_chan(msgq), cl);
+ if (ret)
+ goto err_mbox;
+
+ return 0;
+err_mbox:
+ mbox_controller_unregister(&msgq->mbox);
+err_rx_irq:
+ if (gh_msgq_has_rx(msgq))
+ free_irq(msgq->rx_ghrsc->irq, msgq);
+err_tx_irq:
+ if (gh_msgq_has_tx(msgq))
+ free_irq(msgq->tx_ghrsc->irq, msgq);
+err_chans:
+ kfree(msgq->mbox.chans);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(gh_msgq_init);
+
+void gh_msgq_remove(struct gh_msgq *msgq)
+{
+ mbox_controller_unregister(&msgq->mbox);
+
+ if (gh_msgq_has_rx(msgq))
+ free_irq(msgq->rx_ghrsc->irq, msgq);
+
+ if (gh_msgq_has_tx(msgq))
+ free_irq(msgq->tx_ghrsc->irq, msgq);
+
+ kfree(msgq->mbox.chans);
+}
+EXPORT_SYMBOL_GPL(gh_msgq_remove);
+
+
+static int __init gh_msgq_init_module(void)
+{
+ if (gh_api_version() != GUNYAH_API_V1) {
+ pr_warn("Unrecognized gunyah version: %u. Currently supported: %d\n",
+ gh_api_version(), GUNYAH_API_V1);
+ return -ENODEV;
+ }
+
+ return 0;
+}
+module_init(gh_msgq_init_module);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Gunyah Message Queue Driver");
diff --git a/include/linux/gunyah.h b/include/linux/gunyah.h
index c863cac4a3cf..e317d7ac938f 100644
--- a/include/linux/gunyah.h
+++ b/include/linux/gunyah.h
@@ -7,10 +7,67 @@
#define _GUNYAH_H
#include <linux/bitfield.h>
-#include <linux/types.h>
#include <linux/errno.h>
+#include <linux/interrupt.h>
+#include <linux/mailbox_controller.h>
+#include <linux/mailbox_client.h>
+#include <linux/types.h>
+
+/* Follows resource manager's resource types for VM_GET_HYP_RESOURCES */
+enum gunyah_resource_type {
+ GUNYAH_RESOURCE_TYPE_BELL_TX = 0,
+ GUNYAH_RESOURCE_TYPE_BELL_RX = 1,
+ GUNYAH_RESOURCE_TYPE_MSGQ_TX = 2,
+ GUNYAH_RESOURCE_TYPE_MSGQ_RX = 3,
+ GUNYAH_RESOURCE_TYPE_VCPU = 4,
+};
+
+struct gunyah_resource {
+ enum gunyah_resource_type type;
+ u64 capid;
+ int irq;
+};
+
+/**
+ * Gunyah Message Queues
+ */
+
+#define GH_MSGQ_MAX_MSG_SIZE 240
+
+struct gh_msgq_tx_data {
+ size_t length;
+ bool push;
+ char data[];
+};
+
+struct gh_msgq_rx_data {
+ size_t length;
+ char data[GH_MSGQ_MAX_MSG_SIZE];
+};
+
+struct gh_msgq {
+ struct gunyah_resource *tx_ghrsc;
+ struct gunyah_resource *rx_ghrsc;
+
+ /* msgq private */
+ int last_status;
+ struct mbox_controller mbox;
+ struct tasklet_struct txdone_tasklet;
+};
+
+
+int gh_msgq_init(struct device *parent, struct gh_msgq *msgq, struct mbox_client *cl,
+ struct gunyah_resource *tx_ghrsc, struct gunyah_resource *rx_ghrsc);
+void gh_msgq_remove(struct gh_msgq *msgq);
+
+static inline struct mbox_chan *gh_msgq_chan(struct gh_msgq *msgq)
+{
+ return &msgq->mbox.chans[0];
+}
+
+/******************************************************************************/
+/* Common arch-independent macros and definitions for Gunyah hypercalls */
-/* Common Gunyah macros */
#define GH_CAPID_INVAL U64_MAX
#define GH_VMID_ROOT_VM 0xff
--
2.25.1
Add hypercalls to identify when Linux is running a virtual machine under
Gunyah.
There are two calls to help identify Gunyah:
1. gh_hypercall_get_uid() returns a UID when running under a Gunyah
hypervisor.
2. gh_hypercall_hyp_identify() returns build information and a set of
feature flags that are supported by Gunyah.
Signed-off-by: Elliot Berman <[email protected]>
---
MAINTAINERS | 2 +
arch/arm64/Kbuild | 1 +
arch/arm64/gunyah/Makefile | 1 +
arch/arm64/gunyah/gunyah_hypercall.c | 69 ++++++++++++++++++++++++++++
drivers/virt/Kconfig | 1 +
drivers/virt/gunyah/Kconfig | 12 +++++
include/linux/gunyah.h | 25 ++++++++++
7 files changed, 111 insertions(+)
create mode 100644 arch/arm64/gunyah/Makefile
create mode 100644 arch/arm64/gunyah/gunyah_hypercall.c
create mode 100644 drivers/virt/gunyah/Kconfig
diff --git a/MAINTAINERS b/MAINTAINERS
index 74e76e0ab14d..36698df6b0e5 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -8941,6 +8941,8 @@ L: [email protected]
S: Supported
F: Documentation/devicetree/bindings/firmware/gunyah-hypervisor.yaml
F: Documentation/virt/gunyah/
+F: arch/arm64/gunyah/
+F: drivers/virt/gunyah/
F: include/linux/gunyah.h
HABANALABS PCI DRIVER
diff --git a/arch/arm64/Kbuild b/arch/arm64/Kbuild
index 5bfbf7d79c99..e4847ba0e3c9 100644
--- a/arch/arm64/Kbuild
+++ b/arch/arm64/Kbuild
@@ -3,6 +3,7 @@ obj-y += kernel/ mm/ net/
obj-$(CONFIG_KVM) += kvm/
obj-$(CONFIG_XEN) += xen/
obj-$(subst m,y,$(CONFIG_HYPERV)) += hyperv/
+obj-$(CONFIG_GUNYAH) += gunyah/
obj-$(CONFIG_CRYPTO) += crypto/
# for cleaning
diff --git a/arch/arm64/gunyah/Makefile b/arch/arm64/gunyah/Makefile
new file mode 100644
index 000000000000..9fbc720b6fb6
--- /dev/null
+++ b/arch/arm64/gunyah/Makefile
@@ -0,0 +1 @@
+obj-$(CONFIG_GUNYAH) += gunyah_hypercall.o
diff --git a/arch/arm64/gunyah/gunyah_hypercall.c b/arch/arm64/gunyah/gunyah_hypercall.c
new file mode 100644
index 000000000000..0beb3123d650
--- /dev/null
+++ b/arch/arm64/gunyah/gunyah_hypercall.c
@@ -0,0 +1,69 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
+ */
+
+#include <linux/arm-smccc.h>
+#include <linux/module.h>
+#include <linux/gunyah.h>
+
+#define GH_CALL_TYPE_PLATFORM_CALL 0
+#define GH_CALL_TYPE_HYPERCALL 2
+#define GH_CALL_TYPE_SERVICE 3
+#define GH_CALL_TYPE_SHIFT 14
+#define GH_CALL_FUNCTION_NUM_MASK 0x3fff
+
+#define GH_FN_ID(type, num) ((type) << GH_CALL_TYPE_SHIFT | ((num) & GH_CALL_FUNCTION_NUM_MASK))
+
+#define GH_SERVICE(fn) ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, ARM_SMCCC_SMC_32, \
+ ARM_SMCCC_OWNER_VENDOR_HYP, \
+ GH_FN_ID(GH_CALL_TYPE_SERVICE, fn))
+
+#define GH_HYPERCALL_CALL_UID GH_SERVICE(0x3f01)
+
+#define GH_HYPERCALL(fn) ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, ARM_SMCCC_SMC_64, \
+ ARM_SMCCC_OWNER_VENDOR_HYP, \
+ GH_FN_ID(GH_CALL_TYPE_HYPERCALL, fn))
+
+#define GH_HYPERCALL_HYP_IDENTIFY GH_HYPERCALL(0x0000)
+
+/**
+ * gh_hypercall_get_uid() - Returns a UID when running under a Gunyah hypervisor
+ * @uid: An array of 4 u32's (u32 uid[4];)
+ *
+ * Caller should compare the resulting UID to a list of known Gunyah UIDs to
+ * confirm that Linux is running as a guest of Gunyah.
+ */
+void gh_hypercall_get_uid(u32 uid[4])
+{
+ struct arm_smccc_res res;
+
+ arm_smccc_1_1_hvc(GH_HYPERCALL_CALL_UID, &res);
+
+ uid[0] = res.a0;
+ uid[1] = res.a1;
+ uid[2] = res.a2;
+ uid[3] = res.a3;
+}
+EXPORT_SYMBOL_GPL(gh_hypercall_get_uid);
+
+/**
+ * gh_hypercall_hyp_identify() - Returns build information and feature flags
+ * supported by Gunyah.
+ * @hyp_identity: filled by the hypercall with the API info and feature flags.
+ */
+void gh_hypercall_hyp_identify(struct gh_hypercall_hyp_identify_resp *hyp_identity)
+{
+ struct arm_smccc_res res;
+
+ arm_smccc_1_1_hvc(GH_HYPERCALL_HYP_IDENTIFY, &res);
+
+ hyp_identity->api_info = res.a0;
+ hyp_identity->flags[0] = res.a1;
+ hyp_identity->flags[1] = res.a2;
+ hyp_identity->flags[2] = res.a3;
+}
+EXPORT_SYMBOL_GPL(gh_hypercall_hyp_identify);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Gunyah Hypervisor Hypercalls");
diff --git a/drivers/virt/Kconfig b/drivers/virt/Kconfig
index 87ef258cec64..259dc2be6cad 100644
--- a/drivers/virt/Kconfig
+++ b/drivers/virt/Kconfig
@@ -52,4 +52,5 @@ source "drivers/virt/coco/efi_secret/Kconfig"
source "drivers/virt/coco/sev-guest/Kconfig"
+source "drivers/virt/gunyah/Kconfig"
endif
diff --git a/drivers/virt/gunyah/Kconfig b/drivers/virt/gunyah/Kconfig
new file mode 100644
index 000000000000..127156a678a6
--- /dev/null
+++ b/drivers/virt/gunyah/Kconfig
@@ -0,0 +1,12 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+config GUNYAH
+ tristate "Gunyah Virtualization drivers"
+ depends on ARM64
+ help
+ The Gunyah drivers are the helper interfaces that runs in a guest VM
+ such as basic inter-VM IPC and signaling mechanisms, and higher level
+ services such as memory/device sharing, IRQ sharing, and so on.
+
+ Say Y/M here to enable the drivers needed to interact in a Gunyah
+ virtual environment.
diff --git a/include/linux/gunyah.h b/include/linux/gunyah.h
index 824e20a11d27..2765d2b40198 100644
--- a/include/linux/gunyah.h
+++ b/include/linux/gunyah.h
@@ -6,6 +6,7 @@
#ifndef _GUNYAH_H
#define _GUNYAH_H
+#include <linux/bitfield.h>
#include <linux/types.h>
#include <linux/errno.h>
@@ -71,4 +72,28 @@ static inline int gh_remap_error(int gh_error)
}
}
+#define GUNYAH_API_V1 1
+
+#define GH_API_INFO_API_VERSION_MASK GENMASK_ULL(13, 0)
+#define GH_API_INFO_BIG_ENDIAN BIT_ULL(14)
+#define GH_API_INFO_IS_64BIT BIT_ULL(15)
+#define GH_API_INFO_VARIANT_MASK GENMASK_ULL(63, 56)
+
+#define GH_IDENTIFY_PARTITION_CSPACE BIT_ULL(0)
+#define GH_IDENTIFY_DOORBELL BIT_ULL(1)
+#define GH_IDENTIFY_MSGQUEUE BIT_ULL(2)
+#define GH_IDENTIFY_VIC BIT_ULL(3)
+#define GH_IDENTIFY_VPM BIT_ULL(4)
+#define GH_IDENTIFY_VCPU BIT_ULL(5)
+#define GH_IDENTIFY_MEMEXTENT BIT_ULL(6)
+#define GH_IDENTIFY_TRACE_CTRL BIT_ULL(7)
+
+struct gh_hypercall_hyp_identify_resp {
+ u64 api_info;
+ u64 flags[3];
+};
+
+void gh_hypercall_get_uid(u32 uid[4]);
+void gh_hypercall_hyp_identify(struct gh_hypercall_hyp_identify_resp *hyp_identity);
+
#endif
--
2.25.1
When booting a Gunyah virtual machine, the host VM may gain capabilities
to interact with resources for the guest virtual machine. Examples of
such resources are vCPUs or message queues. To use those resources, we
need to translate the RM response into a gunyah_resource structure which
are useful to Linux drivers. Presently, Linux drivers need only to know
the type of resource, the capability ID, and an interrupt.
On ARM64 systems, the interrupt reported by Gunyah is the GIC interrupt
ID number and always a SPI.
Signed-off-by: Elliot Berman <[email protected]>
---
arch/arm64/include/asm/gunyah.h | 23 +++++
drivers/virt/gunyah/rsc_mgr.c | 168 +++++++++++++++++++++++++++++++-
include/linux/gunyah.h | 3 +
include/linux/gunyah_rsc_mgr.h | 4 +
4 files changed, 197 insertions(+), 1 deletion(-)
create mode 100644 arch/arm64/include/asm/gunyah.h
diff --git a/arch/arm64/include/asm/gunyah.h b/arch/arm64/include/asm/gunyah.h
new file mode 100644
index 000000000000..64cfb964efee
--- /dev/null
+++ b/arch/arm64/include/asm/gunyah.h
@@ -0,0 +1,23 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
+ */
+#ifndef __ASM_GUNYAH_H_
+#define __ASM_GUNYAH_H_
+
+#include <linux/irq.h>
+#include <dt-bindings/interrupt-controller/arm-gic.h>
+
+static inline int arch_gh_fill_irq_fwspec_params(u32 virq, struct irq_fwspec *fwspec)
+{
+ if (virq < 32 || virq > 1019)
+ return -EINVAL;
+
+ fwspec->param_count = 3;
+ fwspec->param[0] = GIC_SPI;
+ fwspec->param[1] = virq - 32;
+ fwspec->param[2] = IRQ_TYPE_EDGE_RISING;
+ return 0;
+}
+
+#endif
diff --git a/drivers/virt/gunyah/rsc_mgr.c b/drivers/virt/gunyah/rsc_mgr.c
index 49d7939bbe36..1010e725f1bd 100644
--- a/drivers/virt/gunyah/rsc_mgr.c
+++ b/drivers/virt/gunyah/rsc_mgr.c
@@ -18,6 +18,8 @@
#include <linux/platform_device.h>
#include <linux/miscdevice.h>
+#include <asm/gunyah.h>
+
#include "rsc_mgr.h"
#include "vm_mgr.h"
@@ -99,9 +101,145 @@ struct gh_rm_rpc {
struct mutex send_lock;
struct miscdevice miscdev;
+ struct irq_domain *irq_domain;
+
struct work_struct recv_work;
};
+struct gh_irq_chip_data {
+ u32 gh_virq;
+};
+
+static struct irq_chip gh_rm_irq_chip = {
+ .name = "Gunyah",
+ .irq_enable = irq_chip_enable_parent,
+ .irq_disable = irq_chip_disable_parent,
+ .irq_ack = irq_chip_ack_parent,
+ .irq_mask = irq_chip_mask_parent,
+ .irq_mask_ack = irq_chip_mask_ack_parent,
+ .irq_unmask = irq_chip_unmask_parent,
+ .irq_eoi = irq_chip_eoi_parent,
+ .irq_set_affinity = irq_chip_set_affinity_parent,
+ .irq_set_type = irq_chip_set_type_parent,
+ .irq_set_wake = irq_chip_set_wake_parent,
+ .irq_set_vcpu_affinity = irq_chip_set_vcpu_affinity_parent,
+ .irq_retrigger = irq_chip_retrigger_hierarchy,
+ .irq_get_irqchip_state = irq_chip_get_parent_state,
+ .irq_set_irqchip_state = irq_chip_set_parent_state,
+ .flags = IRQCHIP_SET_TYPE_MASKED |
+ IRQCHIP_SKIP_SET_WAKE |
+ IRQCHIP_MASK_ON_SUSPEND,
+};
+
+static int gh_rm_irq_domain_alloc(struct irq_domain *d, unsigned int virq, unsigned int nr_irqs,
+ void *arg)
+{
+ struct irq_fwspec parent_fwspec;
+ struct gh_irq_chip_data *chip_data, *spec = arg;
+ u32 gh_virq = spec->gh_virq;
+ struct gh_rm_rpc *rsc_mgr = d->host_data;
+ int ret;
+
+ if (nr_irqs != 1 || gh_virq == U32_MAX)
+ return -EINVAL;
+
+ chip_data = kzalloc(sizeof(*chip_data), GFP_KERNEL);
+ if (!chip_data)
+ return -ENOMEM;
+
+ chip_data->gh_virq = gh_virq;
+
+ ret = irq_domain_set_hwirq_and_chip(d, virq, chip_data->gh_virq, &gh_rm_irq_chip,
+ chip_data);
+ if (ret)
+ return ret;
+
+ parent_fwspec.fwnode = d->parent->fwnode;
+ ret = arch_gh_fill_irq_fwspec_params(chip_data->gh_virq, &parent_fwspec);
+ if (ret) {
+ dev_err(rsc_mgr->dev, "virq translation failed %u: %d\n", chip_data->gh_virq, ret);
+ goto err_free_irq_data;
+ }
+
+ ret = irq_domain_alloc_irqs_parent(d, virq, nr_irqs, &parent_fwspec);
+ if (ret)
+ goto err_free_irq_data;
+
+ return ret;
+err_free_irq_data:
+ kfree(chip_data);
+ return ret;
+}
+
+static void gh_rm_irq_domain_free_single(struct irq_domain *d, unsigned int virq)
+{
+ struct irq_data *irq_data;
+ struct gh_irq_chip_data *chip_data;
+
+ irq_data = irq_domain_get_irq_data(d, virq);
+ if (!irq_data)
+ return;
+
+ chip_data = irq_data->chip_data;
+
+ kfree(chip_data);
+ irq_data->chip_data = NULL;
+}
+
+static void gh_rm_irq_domain_free(struct irq_domain *d, unsigned int virq, unsigned int nr_irqs)
+{
+ unsigned int i;
+
+ for (i = 0; i < nr_irqs; i++)
+ gh_rm_irq_domain_free_single(d, virq);
+}
+
+static const struct irq_domain_ops gh_rm_irq_domain_ops = {
+ .alloc = gh_rm_irq_domain_alloc,
+ .free = gh_rm_irq_domain_free,
+};
+
+struct gunyah_resource *gh_rm_alloc_resource(struct gh_rm_rpc *rm,
+ struct gh_rm_hyp_resource *hyp_resource)
+{
+ struct gunyah_resource *ghrsc;
+
+ ghrsc = kzalloc(sizeof(*ghrsc), GFP_KERNEL);
+ if (!ghrsc)
+ return NULL;
+
+ ghrsc->type = hyp_resource->type;
+ ghrsc->capid = le64_to_cpu(hyp_resource->cap_id);
+ ghrsc->irq = IRQ_NOTCONNECTED;
+ ghrsc->rm_label = le32_to_cpu(hyp_resource->resource_label);
+ /* Gunyah doesn't send us virq_handle anymore and will always give us an interrupt
+ * that's ready to be used. Defensively check that it's not set.
+ */
+ if (hyp_resource->virq_handle) {
+ pr_warn_once("Unexpected virq handle.\n");
+ } else if (hyp_resource->virq && le32_to_cpu(hyp_resource->virq) != U32_MAX) {
+ struct gh_irq_chip_data irq_data = {
+ .gh_virq = le32_to_cpu(hyp_resource->virq),
+ };
+
+ ghrsc->irq = irq_domain_alloc_irqs(rm->irq_domain, 1, NUMA_NO_NODE, &irq_data);
+ if (ghrsc->irq < 0) {
+ pr_err("Failed to allocate interrupt: %d\n", ghrsc->irq);
+ ghrsc->irq = IRQ_NOTCONNECTED;
+ }
+ }
+
+ return ghrsc;
+}
+EXPORT_SYMBOL_GPL(gh_rm_alloc_resource);
+
+void gh_rm_free_resource(struct gunyah_resource *ghrsc)
+{
+ irq_dispose_mapping(ghrsc->irq);
+ kfree(ghrsc);
+}
+EXPORT_SYMBOL_GPL(gh_rm_free_resource);
+
static struct gh_rm_connection *gh_rm_alloc_connection(u32 msg_id, u8 type)
{
struct gh_rm_connection *connection;
@@ -552,6 +690,8 @@ static int gh_msgq_platform_probe_direction(struct platform_device *pdev,
static int gh_rm_drv_probe(struct platform_device *pdev)
{
struct gh_rm_rpc *rsc_mgr;
+ struct device_node *parent_irq_node;
+ struct irq_domain *parent_irq_domain;
int ret;
rsc_mgr = devm_kzalloc(&pdev->dev, sizeof(*rsc_mgr), GFP_KERNEL);
@@ -583,15 +723,40 @@ static int gh_rm_drv_probe(struct platform_device *pdev)
ret = gh_msgq_init(&pdev->dev, &rsc_mgr->msgq, &rsc_mgr->msgq_client,
&rsc_mgr->tx_ghrsc, &rsc_mgr->rx_ghrsc);
+ parent_irq_node = of_irq_find_parent(pdev->dev.of_node);
+ if (!parent_irq_node) {
+ dev_err(&pdev->dev, "Failed to find interrupt parent of resource manager\n");
+ ret = -ENODEV;
+ goto err_msgq;
+ }
+
+ parent_irq_domain = irq_find_host(parent_irq_node);
+ if (!parent_irq_domain) {
+ dev_err(&pdev->dev, "Failed to find interrupt parent domain of resource manager\n");
+ ret = -ENODEV;
+ goto err_msgq;
+ }
+
+ rsc_mgr->irq_domain = irq_domain_add_hierarchy(parent_irq_domain, 0, 0, pdev->dev.of_node,
+ &gh_rm_irq_domain_ops, NULL);
+ if (!rsc_mgr->irq_domain) {
+ dev_err(&pdev->dev, "Failed to add irq domain\n");
+ ret = -ENODEV;
+ goto err_msgq;
+ }
+ rsc_mgr->irq_domain->host_data = rsc_mgr;
+
rsc_mgr->miscdev.name = "gunyah";
rsc_mgr->miscdev.minor = MISC_DYNAMIC_MINOR;
rsc_mgr->miscdev.fops = &gh_dev_fops;
ret = misc_register(&rsc_mgr->miscdev);
if (ret)
- goto err_msgq;
+ goto err_irq_domain;
return 0;
+err_irq_domain:
+ irq_domain_remove(rsc_mgr->irq_domain);
err_msgq:
mbox_free_channel(gh_msgq_chan(&rsc_mgr->msgq));
gh_msgq_remove(&rsc_mgr->msgq);
@@ -603,6 +768,7 @@ static int gh_rm_drv_remove(struct platform_device *pdev)
struct gh_rm_rpc *rm = platform_get_drvdata(pdev);
misc_deregister(&rm->miscdev);
+ irq_domain_remove(rm->irq_domain);
mbox_free_channel(gh_msgq_chan(&rm->msgq));
gh_msgq_remove(&rm->msgq);
diff --git a/include/linux/gunyah.h b/include/linux/gunyah.h
index e317d7ac938f..8cb6af88c75d 100644
--- a/include/linux/gunyah.h
+++ b/include/linux/gunyah.h
@@ -26,6 +26,9 @@ struct gunyah_resource {
enum gunyah_resource_type type;
u64 capid;
int irq;
+
+ /* To help allocator in resource manager */
+ u32 rm_label;
};
/**
diff --git a/include/linux/gunyah_rsc_mgr.h b/include/linux/gunyah_rsc_mgr.h
index be434bcf9b4e..eee993a33971 100644
--- a/include/linux/gunyah_rsc_mgr.h
+++ b/include/linux/gunyah_rsc_mgr.h
@@ -115,6 +115,10 @@ ssize_t gh_rm_get_hyp_resources(struct gh_rm_rpc *rm, u16 vmid,
struct gh_rm_hyp_resource **resources);
int gh_rm_get_vmid(struct gh_rm_rpc *rm, u16 *vmid);
+struct gunyah_resource *gh_rm_alloc_resource(struct gh_rm_rpc *rm,
+ struct gh_rm_hyp_resource *hyp_resource);
+void gh_rm_free_resource(struct gunyah_resource *ghrsc);
+
struct gunyah_rm_platform_ops {
int (*pre_mem_share)(struct gh_rm_rpc *rm, struct gh_rm_mem_parcel *mem_parcel);
int (*post_mem_reclaim)(struct gh_rm_rpc *rm, struct gh_rm_mem_parcel *mem_parcel);
--
2.25.1
Gunyah resource manager provides API to manipulate stage 2 page tables.
Manipulations are represented as a memory parcel. Memory parcels
describe a list of memory regions (intermediate physical address and
size), a list of new permissions for VMs, and the memory type (DDR or
MMIO). Memory parcels are uniquely identified by a handle allocated by
Gunyah. There are a few types of memory parcel sharing which Gunyah
supports:
- Sharing: the guest and host VM both have access
- Lending: only the guest has access; host VM loses access
- Donating: Permanently lent (not reclaimed even if guest shuts down)
Memory parcels that have been shared or lent can be reclaimed by the
host via an additional call. The reclaim operation restores the original
access the host VM had to the memory parcel and removes the access to
other VM.
One point to note that memory parcels don't describe where in the guest
VM the memory parcel should reside. The guest VM must accept the memory
parcel either explicitly via a "gh_rm_mem_accept" call (not introduced
here) or be configured to accept it automatically at boot. As the guest
VM accepts the memory parcel, it also mentions the IPA it wants to place
memory parcel.
Co-developed-by: Prakruthi Deepak Heragu <[email protected]>
Signed-off-by: Prakruthi Deepak Heragu <[email protected]>
Signed-off-by: Elliot Berman <[email protected]>
---
drivers/virt/gunyah/rsc_mgr.h | 44 +++++++
drivers/virt/gunyah/rsc_mgr_rpc.c | 199 ++++++++++++++++++++++++++++++
include/linux/gunyah_rsc_mgr.h | 47 +++++++
3 files changed, 290 insertions(+)
diff --git a/drivers/virt/gunyah/rsc_mgr.h b/drivers/virt/gunyah/rsc_mgr.h
index 1f9e3c38038e..e068b5b65d2b 100644
--- a/drivers/virt/gunyah/rsc_mgr.h
+++ b/drivers/virt/gunyah/rsc_mgr.h
@@ -33,6 +33,12 @@ struct gh_rm_rpc;
int gh_rm_call(struct gh_rm_rpc *rsc_mgr, u32 message_id, void *req_buff, size_t req_buff_size,
void **resp_buf, size_t *resp_buff_size);
+/* Message IDs: Memory Management */
+#define GH_RM_RPC_MEM_LEND 0x51000012
+#define GH_RM_RPC_MEM_SHARE 0x51000013
+#define GH_RM_RPC_MEM_RECLAIM 0x51000015
+#define GH_RM_RPC_MEM_APPEND 0x51000018
+
/* Message IDs: VM Management */
#define GH_RM_RPC_VM_ALLOC_VMID 0x56000001
#define GH_RM_RPC_VM_DEALLOC_VMID 0x56000002
@@ -49,6 +55,44 @@ struct gh_vm_common_vmid_req {
__le16 reserved0;
} __packed;
+/* Call: MEM_LEND, MEM_SHARE */
+struct gh_mem_share_req_header {
+ u8 mem_type;
+ u8 reserved0;
+#define GH_MEM_SHARE_REQ_FLAGS_APPEND BIT(1)
+ u8 flags;
+ u8 reserved1;
+ __le32 label;
+} __packed;
+
+struct gh_mem_share_req_acl_section {
+ __le32 n_entries;
+ struct gh_rm_mem_acl_entry entries[];
+};
+
+struct gh_mem_share_req_mem_section {
+ __le16 n_entries;
+ __le16 reserved0;
+ struct gh_rm_mem_entry entries[];
+};
+
+/* Call: MEM_RELEASE */
+struct gh_mem_release_req {
+ __le32 mem_handle;
+ u8 flags;
+ __le16 reserved0;
+ u8 reserved1;
+} __packed;
+
+/* Call: MEM_APPEND */
+struct gh_mem_append_req_header {
+ __le32 mem_handle;
+#define GH_MEM_APPEND_REQ_FLAGS_END BIT(0)
+ u8 flags;
+ __le16 reserved0;
+ u8 reserved1;
+} __packed;
+
/* Call: VM_STOP */
struct gh_vm_stop_req {
__le16 vmid;
diff --git a/drivers/virt/gunyah/rsc_mgr_rpc.c b/drivers/virt/gunyah/rsc_mgr_rpc.c
index 8d9c1c635a27..2c9aded7cc09 100644
--- a/drivers/virt/gunyah/rsc_mgr_rpc.c
+++ b/drivers/virt/gunyah/rsc_mgr_rpc.c
@@ -7,6 +7,8 @@
#include "rsc_mgr.h"
+#define GH_RM_MAX_MEM_ENTRIES 512
+
/*
* Several RM calls take only a VMID as a parameter and give only standard
* response back. Deduplicate boilerplate code by using this common call.
@@ -29,6 +31,203 @@ static int gh_rm_common_vmid_call(struct gh_rm_rpc *rm, u32 message_id, u16 vmid
return ret;
}
+static int _gh_rm_mem_append(struct gh_rm_rpc *rm, u32 mem_handle, bool end_append,
+ struct gh_rm_mem_entry *mem_entries, size_t n_mem_entries)
+{
+ size_t msg_size = 0;
+ void *msg;
+ struct gh_mem_append_req_header *req_header;
+ struct gh_mem_share_req_mem_section *mem_section;
+ void *resp;
+ size_t resp_size;
+ int ret;
+
+ msg_size += sizeof(struct gh_mem_append_req_header);
+ msg_size += offsetof(struct gh_mem_share_req_mem_section, entries[n_mem_entries]);
+
+ msg = kzalloc(msg_size, GFP_KERNEL);
+ if (!msg)
+ return -ENOMEM;
+
+ req_header = msg;
+ mem_section = (void *)req_header + sizeof(struct gh_mem_append_req_header);
+
+ req_header->mem_handle = mem_handle;
+ if (end_append)
+ req_header->flags |= GH_MEM_APPEND_REQ_FLAGS_END;
+
+ mem_section->n_entries = n_mem_entries;
+ memcpy(mem_section->entries, mem_entries, sizeof(*mem_entries) * n_mem_entries);
+
+ ret = gh_rm_call(rm, GH_RM_RPC_MEM_APPEND, msg, msg_size, &resp, &resp_size);
+ if (ret)
+ return ret;
+ kfree(resp);
+
+ return ret;
+}
+
+static int gh_rm_mem_append(struct gh_rm_rpc *rm, u32 mem_handle, bool allow_append,
+ struct gh_rm_mem_entry *mem_entries, size_t n_mem_entries)
+{
+ bool end_append;
+ size_t n;
+ int ret = 0;
+
+ while (n_mem_entries) {
+ if (n_mem_entries > GH_RM_MAX_MEM_ENTRIES) {
+ end_append = false;
+ n = GH_RM_MAX_MEM_ENTRIES;
+ } else {
+ end_append = !allow_append;
+ n = n_mem_entries;
+ }
+
+ ret = _gh_rm_mem_append(rm, mem_handle, end_append, mem_entries, n);
+ if (ret)
+ break;
+
+ mem_entries += n;
+ n_mem_entries -= n;
+ }
+
+ return ret;
+}
+
+static int gh_rm_mem_lend_common(struct gh_rm_rpc *rm, u32 message_id, struct gh_rm_mem_parcel *p)
+{
+ size_t msg_size = 0, initial_n_mem_entries = p->n_mem_entries;
+ void *msg, *resp;
+ struct gh_mem_share_req_header *req_header;
+ struct gh_mem_share_req_acl_section *acl_section;
+ struct gh_mem_share_req_mem_section *mem_section;
+ u32 *mem_attr_section;
+ size_t resp_size;
+ int ret;
+
+ if (!p->acl_entries || !p->n_acl_entries || !p->mem_entries || !p->n_mem_entries ||
+ p->n_acl_entries > U8_MAX)
+ return -EINVAL;
+
+ if (initial_n_mem_entries > GH_RM_MAX_MEM_ENTRIES)
+ initial_n_mem_entries = GH_RM_MAX_MEM_ENTRIES;
+
+ /* The format of the message goes:
+ * request header
+ * ACL entries (which VMs get what kind of access to this memory parcel)
+ * Memory entries (list of memory regions to share)
+ * Memory attributes (currently unused, we'll hard-code the size to 0)
+ */
+ msg_size += sizeof(struct gh_mem_share_req_header);
+ msg_size += offsetof(struct gh_mem_share_req_acl_section, entries[p->n_acl_entries]);
+ msg_size += offsetof(struct gh_mem_share_req_mem_section, entries[initial_n_mem_entries]);
+ msg_size += sizeof(u32); /* for memory attributes, currently unused */
+
+ msg = kzalloc(msg_size, GFP_KERNEL);
+ if (!msg)
+ return -ENOMEM;
+
+ req_header = msg;
+ acl_section = (void *)req_header + sizeof(*req_header);
+ mem_section = (void *)acl_section + offsetof(struct gh_mem_share_req_acl_section,
+ entries[p->n_acl_entries]);
+ mem_attr_section = (void *)mem_section + offsetof(struct gh_mem_share_req_mem_section,
+ entries[initial_n_mem_entries]);
+
+ req_header->mem_type = p->mem_type;
+ if (initial_n_mem_entries != p->n_mem_entries)
+ req_header->flags |= GH_MEM_SHARE_REQ_FLAGS_APPEND;
+ req_header->label = cpu_to_le32(p->label);
+
+ acl_section->n_entries = p->n_acl_entries;
+ memcpy(acl_section->entries, p->acl_entries, sizeof(*(p->acl_entries)) * p->n_acl_entries);
+
+ mem_section->n_entries = initial_n_mem_entries;
+ memcpy(mem_section->entries, p->mem_entries,
+ sizeof(*(p->mem_entries)) * initial_n_mem_entries);
+
+ /* Set n_entries for memory attribute section to 0 */
+ *mem_attr_section = 0;
+
+ ret = gh_rm_call(rm, message_id, msg, msg_size, &resp, &resp_size);
+ if (ret)
+ return ret;
+
+ if (resp_size != sizeof(__le32)) {
+ ret = -EIO;
+ goto out;
+ }
+
+ p->mem_handle = le32_to_cpu(*(__le32 *)resp);
+
+ if (initial_n_mem_entries != p->n_mem_entries) {
+ ret = gh_rm_mem_append(rm, p->mem_handle, false,
+ &p->mem_entries[initial_n_mem_entries],
+ p->n_mem_entries - initial_n_mem_entries);
+ if (ret)
+ gh_rm_mem_reclaim(rm, p);
+ }
+
+out:
+ kfree(resp);
+ return ret;
+}
+
+/**
+ * gh_rm_mem_lend() - Lend memory to other virtual machines.
+ * @parcel: Package the memory information of the memory to be lent.
+ *
+ * Returns - 0 on success; negative value on failure
+ *
+ * Lending removes Linux's access to the memory while the memory parcel is lent.
+ */
+int gh_rm_mem_lend(struct gh_rm_rpc *rm, struct gh_rm_mem_parcel *parcel)
+{
+ return gh_rm_mem_lend_common(rm, GH_RM_RPC_MEM_LEND, parcel);
+}
+EXPORT_SYMBOL_GPL(gh_rm_mem_lend);
+
+
+/**
+ * gh_rm_mem_share() - Share memory with other virtual machines.
+ * @parcel: Package the memory information of the memory to be shared.
+ *
+ * Returns - 0 on success; negative value on failure
+ *
+ * Sharing keeps Linux's access to the memory while the memory parcel is shared.
+ */
+int gh_rm_mem_share(struct gh_rm_rpc *rm, struct gh_rm_mem_parcel *parcel)
+{
+ return gh_rm_mem_lend_common(rm, GH_RM_RPC_MEM_SHARE, parcel);
+}
+EXPORT_SYMBOL_GPL(gh_rm_mem_share);
+
+/**
+ * gh_rm_mem_reclaim() - Reclaim a memory parcel
+ * @parcel: Package the memory information of the memory to be reclaimed.
+ *
+ * Returns - 0 on success; negative value on failure
+ *
+ * RM maps the associated memory back into the stage-2 page tables of the owner VM.
+ */
+int gh_rm_mem_reclaim(struct gh_rm_rpc *rm, struct gh_rm_mem_parcel *parcel)
+{
+ struct gh_mem_release_req req = {
+ .mem_handle = cpu_to_le32(parcel->mem_handle),
+ };
+ size_t resp_size;
+ void *resp;
+ int ret;
+
+ ret = gh_rm_call(rm, GH_RM_RPC_MEM_RECLAIM, &req, sizeof(req), &resp, &resp_size);
+ if (ret)
+ return ret;
+ kfree(resp);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(gh_rm_mem_reclaim);
+
/**
* gh_rm_alloc_vmid() - Allocate a new VM in Gunyah. Returns the VM identifier.
* @vmid: Use GH_VMID_INVAL to dynamically allocate a VM. A reserved VMID can also be requested
diff --git a/include/linux/gunyah_rsc_mgr.h b/include/linux/gunyah_rsc_mgr.h
index 9c566436687d..0b5b236fd76f 100644
--- a/include/linux/gunyah_rsc_mgr.h
+++ b/include/linux/gunyah_rsc_mgr.h
@@ -11,6 +11,7 @@
#include <linux/gunyah.h>
#define GH_VMID_INVAL U16_MAX
+#define GH_MEM_HANDLE_INVAL U32_MAX
/* Gunyah recognizes VMID0 as an alias to the current VM's ID */
#define GH_VMID_SELF 0
@@ -34,7 +35,53 @@ enum gh_rm_vm_status {
GH_RM_VM_STATUS_RESET = 11,
};
+struct gh_rm_mem_acl_entry {
+ __le16 vmid;
+#define GH_RM_ACL_X BIT(0)
+#define GH_RM_ACL_W BIT(1)
+#define GH_RM_ACL_R BIT(2)
+ u8 perms;
+ u8 reserved;
+} __packed;
+
+struct gh_rm_mem_entry {
+ __le64 ipa_base;
+ __le64 size;
+} __packed;
+
+enum gh_rm_mem_type {
+ GH_RM_MEM_TYPE_NORMAL = 0,
+ GH_RM_MEM_TYPE_IO = 1,
+};
+
+/*
+ * struct gh_rm_mem_parcel - Package info about memory to be lent/shared/donated/reclaimed
+ * @mem_type: The type of memory: normal (DDR) or IO
+ * @label: An client-specified identifier which can be used by the other VMs to identify the purpose
+ * of the memory parcel.
+ * @acl_entries: An array of access control entries. Each entry specifies a VM and what access
+ * is allowed for the memory parcel.
+ * @n_acl_entries: Count of the number of entries in the `acl_entries` array.
+ * @mem_entries: An list of regions to be associated with the memory parcel. Addresses should be
+ * (intermediate) physical addresses from Linux's perspective.
+ * @n_mem_entries: Count of the number of entries in the `mem_entries` array.
+ * @mem_handle: On success, filled with memory handle that RM allocates for this memory parcel
+ */
+struct gh_rm_mem_parcel {
+ enum gh_rm_mem_type mem_type;
+ u32 label;
+ size_t n_acl_entries;
+ struct gh_rm_mem_acl_entry *acl_entries;
+ size_t n_mem_entries;
+ struct gh_rm_mem_entry *mem_entries;
+ u32 mem_handle;
+};
+
/* RPC Calls */
+int gh_rm_mem_lend(struct gh_rm_rpc *rm, struct gh_rm_mem_parcel *parcel);
+int gh_rm_mem_share(struct gh_rm_rpc *rm, struct gh_rm_mem_parcel *parcel);
+int gh_rm_mem_reclaim(struct gh_rm_rpc *rm, struct gh_rm_mem_parcel *parcel);
+
int gh_rm_alloc_vmid(struct gh_rm_rpc *rm, u16 vmid);
int gh_rm_dealloc_vmid(struct gh_rm_rpc *rm, u16 vmid);
int gh_rm_vm_start(struct gh_rm_rpc *rm, u16 vmid);
--
2.25.1
Document the ioctls and usage of Gunyah VM Manager driver.
Signed-off-by: Elliot Berman <[email protected]>
---
Documentation/virt/gunyah/index.rst | 1 +
Documentation/virt/gunyah/vm-manager.rst | 100 +++++++++++++++++++++++
2 files changed, 101 insertions(+)
create mode 100644 Documentation/virt/gunyah/vm-manager.rst
diff --git a/Documentation/virt/gunyah/index.rst b/Documentation/virt/gunyah/index.rst
index fbadbdd24da7..9019a03b6f3e 100644
--- a/Documentation/virt/gunyah/index.rst
+++ b/Documentation/virt/gunyah/index.rst
@@ -7,6 +7,7 @@ Gunyah Hypervisor
.. toctree::
:maxdepth: 1
+ vm-manager
message-queue
Gunyah is a Type-1 hypervisor which is independent of any OS kernel, and runs in
diff --git a/Documentation/virt/gunyah/vm-manager.rst b/Documentation/virt/gunyah/vm-manager.rst
new file mode 100644
index 000000000000..62513af09cbf
--- /dev/null
+++ b/Documentation/virt/gunyah/vm-manager.rst
@@ -0,0 +1,100 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+=======================
+Virtual Machine Manager
+=======================
+
+The Gunyah Virtual Machine Manager is a Linux driver to support launching
+virtual machines using Gunyah. It presently supports launching non-proxy
+scheduled Linux-like virtual machines.
+
+Except for some basic information about the location of initial binaries,
+most of the configuration about a Gunyah virtual machine is described in the
+VM's devicetree. The devicetree is generated by userspace. Interacting with the
+virtual machine is still done via the kernel and VM configuration requires some
+of the corresponding functionality to be set up in the kernel. For instance,
+sharing userspace memory with a VM is done via the GH_VM_SET_USER_MEM_REGION
+ioctl. The VM itself is configured to use the memory region via the
+devicetree.
+
+Sample Userspace VMM
+====================
+
+A sample userspace VMM is included in samples/gunyah/ along with a minimal
+devicetree that can be used to launch a VM. To build this sample, enable
+CONFIG_SAMPLE_GUNYAH.
+
+IOCTLs and userspace VMM flows
+==============================
+
+The kernel exposes a char device interface at /dev/gunyah.
+
+To create a VM, use the GH_CREATE_VM ioctl. A successful call will return a
+"Gunyah VM" file descriptor.
+
+/dev/gunyah API Descriptions
+----------------------------
+
+GH_CREATE_VM
+~~~~~~~~~~~~
+
+Creates a Gunyah VM. The argument is reserved for future use and must be 0.
+
+Gunyah VM API Descriptions
+--------------------------
+
+GH_VM_SET_USER_MEM_REGION
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+::
+
+ struct gh_userspace_memory_region {
+ __u32 label;
+ __u32 flags;
+ __u64 guest_phys_addr;
+ __u64 memory_size;
+ __u64 userspace_addr;
+ };
+
+This ioctl allows the user to create or delete a memory parcel for a guest
+virtual machine. Each memory region is uniquely identified by a label;
+attempting to create two regions with the same label is not allowed.
+
+While VMM is guest-agnostic and allows runtime addition of memory regions,
+Linux guest virtual machines do not support accepting memory regions at runtime.
+Thus, memory regions should be provided before starting the VM and the VM must
+be configured to accept these at boot-up.
+
+The guest physical address is used by Linux kernel to check that the requested
+user regions do not overlap and to help find the corresponding memory region
+for calls like GH_VM_SET_DTB_CONFIG.
+
+To delete a memory region, call GH_VM_SET_USER_MEM_REGION with label set to the
+desired region and memory_size set to 0.
+
+The flags field of gh_userspace_memory_region accepts the following bits. All
+other bits must be 0 and are reserved for future use. The ioctl will return
+-EINVAL if an unsupported bit is detected.
+
+ - GH_MEM_ALLOW_READ/GH_MEM_ALLOW_WRITE/GH_MEM_ALLOW_EXEC sets read/write/exec
+ permissions for the guest, respectively.
+ - GH_MEM_LENT means that the memory will be unmapped from the host and be
+ unaccessible by the host while the guest has the region.
+
+GH_VM_SET_DTB_CONFIG
+~~~~~~~~~~~~~~~~~~~~
+
+::
+
+ struct gh_vm_dtb_config {
+ __u64 gpa;
+ __u64 size;
+ };
+
+This ioctl sets the location of the VM's devicetree blob and is used by Gunyah
+Resource Manager to allocate resources.
+
+GH_VM_START
+~~~~~~~~~~~
+
+This ioctl starts the VM.
--
2.25.1
Hi Elliot,
I love your patch! Perhaps something to improve:
[auto build test WARNING on 830b3c68c1fb1e9176028d02ef86f3cf76aa2476]
url: https://github.com/intel-lab-lkp/linux/commits/Elliot-Berman/Drivers-for-gunyah-hypervisor/20221220-073053
base: 830b3c68c1fb1e9176028d02ef86f3cf76aa2476
patch link: https://lore.kernel.org/r/20221219225850.2397345-28-quic_eberman%40quicinc.com
patch subject: [PATCH v8 27/28] virt: gunyah: Add irqfd interface
config: arm64-allyesconfig
compiler: aarch64-linux-gcc (GCC) 12.1.0
reproduce (this is a W=1 build):
wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross
chmod +x ~/bin/make.cross
# https://github.com/intel-lab-lkp/linux/commit/158304cc06a0a3021e5103a3a84fb2b1dfc6f703
git remote add linux-review https://github.com/intel-lab-lkp/linux
git fetch --no-tags linux-review Elliot-Berman/Drivers-for-gunyah-hypervisor/20221220-073053
git checkout 158304cc06a0a3021e5103a3a84fb2b1dfc6f703
# save the config file
mkdir build_dir && cp config build_dir/.config
COMPILER_INSTALL_PATH=$HOME/0day COMPILER=gcc-12.1.0 make.cross W=1 O=build_dir ARCH=arm64 olddefconfig
COMPILER_INSTALL_PATH=$HOME/0day COMPILER=gcc-12.1.0 make.cross W=1 O=build_dir ARCH=arm64 SHELL=/bin/bash drivers/virt/gunyah/
If you fix the issue, kindly add following tag where applicable
| Reported-by: kernel test robot <[email protected]>
All warnings (new ones prefixed by >>):
>> drivers/virt/gunyah/gunyah_vcpu.c:58: warning: This comment starts with '/**', but isn't a kernel-doc comment. Refer Documentation/doc-guide/kernel-doc.rst
* When hypervisor wants us to schedule vCPU again, it gives us an interrupt
vim +58 drivers/virt/gunyah/gunyah_vcpu.c
b4c9ba3b6fa553a Elliot Berman 2022-12-19 56
b4c9ba3b6fa553a Elliot Berman 2022-12-19 57 /**
b4c9ba3b6fa553a Elliot Berman 2022-12-19 @58 * When hypervisor wants us to schedule vCPU again, it gives us an interrupt
b4c9ba3b6fa553a Elliot Berman 2022-12-19 59 */
b4c9ba3b6fa553a Elliot Berman 2022-12-19 60 static irqreturn_t gh_vcpu_irq_handler(int irq, void *data)
b4c9ba3b6fa553a Elliot Berman 2022-12-19 61 {
b4c9ba3b6fa553a Elliot Berman 2022-12-19 62 struct gunyah_vcpu *vcpu = data;
b4c9ba3b6fa553a Elliot Berman 2022-12-19 63
b4c9ba3b6fa553a Elliot Berman 2022-12-19 64 complete(&vcpu->ready);
b4c9ba3b6fa553a Elliot Berman 2022-12-19 65 return IRQ_HANDLED;
b4c9ba3b6fa553a Elliot Berman 2022-12-19 66 }
b4c9ba3b6fa553a Elliot Berman 2022-12-19 67
--
0-DAY CI Kernel Test Service
https://01.org/lkp
* Elliot Berman <[email protected]> [2022-12-19 14:58:30]:
> +static inline bool gh_msgq_has_tx(struct gh_msgq *msgq)
> +{
Consider possibility that msgq->tx_ghrc can be NULL?
> + return msgq->tx_ghrsc->type == GUNYAH_RESOURCE_TYPE_MSGQ_TX;
> +}
> +
> +static inline bool gh_msgq_has_rx(struct gh_msgq *msgq)
> +{
Consider possibility that msgq->rx_ghrc can be NULL?
> + return msgq->rx_ghrsc->type == GUNYAH_RESOURCE_TYPE_MSGQ_RX;
> +}
> +
> +static irqreturn_t gh_msgq_rx_irq_handler(int irq, void *data)
> +{
> + struct gh_msgq *msgq = data;
> + struct gh_msgq_rx_data rx_data;
> + unsigned long gh_err;
> + ssize_t ret;
> + bool ready = false;
> +
> + do {
> + gh_err = gh_hypercall_msgq_recv(msgq->rx_ghrsc->capid,
> + (uintptr_t)&rx_data.data, sizeof(rx_data.data),
> + &rx_data.length, &ready);
> + if (gh_err == GH_ERROR_OK) {
> + mbox_chan_received_data(gh_msgq_chan(msgq), &rx_data);
> + } else if (GH_ERROR_MSGQUEUE_EMPTY) {
gh_err == GH_ERROR_MSGQUEUE_EMPTY
> + break;
> + } else {
> + pr_warn("Failed to receive data from msgq for %s: %ld\n",
> + msgq->mbox.dev ? dev_name(msgq->mbox.dev) : "", ret);
> + break;
> + }
> + } while (ready);
> +
> + return IRQ_HANDLED;
> +}
On 1/1/2023 10:13 PM, Srivatsa Vaddagiri wrote:
> * Elliot Berman <[email protected]> [2022-12-19 14:58:30]:
>
>> +static inline bool gh_msgq_has_tx(struct gh_msgq *msgq)
>> +{
>
> Consider possibility that msgq->tx_ghrc can be NULL?
>
>> + return msgq->tx_ghrsc->type == GUNYAH_RESOURCE_TYPE_MSGQ_TX;
>> +}
>> +
>> +static inline bool gh_msgq_has_rx(struct gh_msgq *msgq)
>> +{
>
> Consider possibility that msgq->rx_ghrc can be NULL?
>
>> + return msgq->rx_ghrsc->type == GUNYAH_RESOURCE_TYPE_MSGQ_RX;
>> +}
>> +
>> +static irqreturn_t gh_msgq_rx_irq_handler(int irq, void *data)
>> +{
>> + struct gh_msgq *msgq = data;
>> + struct gh_msgq_rx_data rx_data;
>> + unsigned long gh_err;
>> + ssize_t ret;
>> + bool ready = false;
>> +
>> + do {
>> + gh_err = gh_hypercall_msgq_recv(msgq->rx_ghrsc->capid,
>> + (uintptr_t)&rx_data.data, sizeof(rx_data.data),
>> + &rx_data.length, &ready);
>> + if (gh_err == GH_ERROR_OK) {
>> + mbox_chan_received_data(gh_msgq_chan(msgq), &rx_data);
>> + } else if (GH_ERROR_MSGQUEUE_EMPTY) {
>
> gh_err == GH_ERROR_MSGQUEUE_EMPTY
>
Applied the 3 comments, thanks!
* Elliot Berman <[email protected]> [2022-12-19 14:58:32]:
> +/* Call: CONSOLE_OPEN, CONSOLE_CLOSE, CONSOLE_FLUSH */
I think this struct is used by other calls as well?
Also CONSOLE_** functions are not yet introduced in this patch
> +struct gh_vm_common_vmid_req {
> + __le16 vmid;
> + __le16 reserved0;
> +} __packed;
[snip]
> +int gh_rm_alloc_vmid(struct gh_rm_rpc *rm, u16 vmid)
> +{
> + void *resp;
> + struct gh_vm_common_vmid_req req_payload = {
> + .vmid = cpu_to_le16(vmid),
> + };
> + struct gh_vm_common_vmid_req *resp_payload;
> + size_t resp_size;
> + int ret;
> +
> + if (vmid == GH_VMID_INVAL)
> + vmid = 0;
> +
> + ret = gh_rm_call(rm, GH_RM_RPC_VM_ALLOC_VMID, &req_payload, sizeof(req_payload), &resp,
> + &resp_size);
> + if (ret)
> + return ret;
> +
> + if (!vmid) {
> + if (resp_size != sizeof(*resp_payload)) {
> + ret = -EINVAL;
> + } else {
> + resp_payload = resp;
> + ret = resp_payload->vmid;
Do we need a le_to_cpu() wrapper on the response here?
> +int gh_rm_vm_stop(struct gh_rm_rpc *rm, u16 vmid)
> +{
> + struct gh_vm_stop_req req_payload = {
> + .vmid = cpu_to_le16(vmid),
> + };
> + void *resp;
> + size_t resp_size;
> + int ret;
> +
> + ret = gh_rm_call(rm, GH_RM_RPC_VM_STOP, &req_payload, sizeof(req_payload),
> + &resp, &resp_size);
> + if (ret)
> + return ret;
> + kfree(resp);
Why not use gh_rm_common_vmid_call() here as well?
return gh_rm_common_vmid_call(rm, GH_RM_RPC_VM_STOP, vmid);
> +
> + return ret;
> +}
> +EXPORT_SYMBOL_GPL(gh_rm_vm_stop);
> +
[snip]
> +ssize_t gh_rm_get_hyp_resources(struct gh_rm_rpc *rm, u16 vmid,
> + struct gh_rm_hyp_resource **resources)
> +{
> + struct gh_vm_get_hyp_resources_resp *resp;
> + size_t resp_size;
> + int ret;
> + struct gh_vm_common_vmid_req req_payload = {
> + .vmid = cpu_to_le16(vmid),
> + };
> +
> + ret = gh_rm_call(rm, GH_RM_RPC_VM_GET_HYP_RESOURCES,
> + &req_payload, sizeof(req_payload),
> + (void **)&resp, &resp_size);
> + if (ret)
> + return ret;
> +
> + if (resp_size < sizeof(*resp) ||
> + (sizeof(*resp->entries) && (resp->n_entries > U32_MAX / sizeof(*resp->entries))) ||
> + (resp_size != sizeof(*resp) + (resp->n_entries * sizeof(*resp->entries)))) {
> + ret = -EIO;
> + goto out;
> + }
> +
> + *resources = kmemdup(resp->entries, (resp->n_entries * sizeof(*resp->entries)), GFP_KERNEL);
Consider NULL return value from kmemdup
> + ret = resp->n_entries;
> +
> +out:
> + kfree(resp);
> + return ret;
> +}
> +EXPORT_SYMBOL_GPL(gh_rm_get_hyp_resources);
> +
> +/**
> + * gh_rm_get_vmid() - Retrieve VMID of this virtual machine
> + * @vmid: Filled with the VMID of this VM
> + */
> +int gh_rm_get_vmid(struct gh_rm_rpc *rm, u16 *vmid)
> +{
> + static u16 cached_vmid = GH_VMID_INVAL;
> + void *resp;
> + size_t resp_size;
> + int ret;
> + int payload = 0;
> +
> + if (cached_vmid != GH_VMID_INVAL) {
> + *vmid = cached_vmid;
> + return 0;
> + }
> +
> + ret = gh_rm_call(rm, GH_RM_RPC_VM_GET_VMID, &payload, sizeof(payload), &resp, &resp_size);
> + if (ret)
> + return ret;
> +
> + if (resp_size != sizeof(*vmid))
kfree(resp) in this case?
> + return -EIO;
> + *vmid = *(u16 *)resp;
Do we need a le_to_cpu() wrapper on the response?
Also update cached_vmid in success case.
> + kfree(resp);
> +
> + return ret;
> +}
* Elliot Berman <[email protected]> [2022-12-19 14:58:33]:
> +config GUNYAH_VM_MANAGER
Any reason why this needs to be a separate config? IOW CONFIG_GUNYAH should
enable VM management functionality also.
> @@ -550,14 +580,29 @@ static int gh_rm_drv_probe(struct platform_device *pdev)
> rsc_mgr->msgq_client.rx_callback = gh_rm_msgq_rx_data;
> rsc_mgr->msgq_client.tx_done = gh_rm_msgq_tx_done;
>
> - return gh_msgq_init(&pdev->dev, &rsc_mgr->msgq, &rsc_mgr->msgq_client,
> + ret = gh_msgq_init(&pdev->dev, &rsc_mgr->msgq, &rsc_mgr->msgq_client,
> &rsc_mgr->tx_ghrsc, &rsc_mgr->rx_ghrsc);
Bail on error here.
[snip]
> +static __must_check struct gunyah_vm *gunyah_vm_alloc(struct gh_rm_rpc *rm)
> +{
> + struct gunyah_vm *ghvm;
> + int vmid;
> +
> + vmid = gh_rm_alloc_vmid(rm, 0);
> + if (vmid < 0)
> + return ERR_PTR(vmid);
> +
> + ghvm = kzalloc(sizeof(*ghvm), GFP_KERNEL);
> + if (!ghvm)
dealloc_vmid here (as well as few other error paths)?
> + return ghvm;
> +
> + get_gh_rm(rm);
On 1/8/2023 11:13 PM, Srivatsa Vaddagiri wrote:
> * Elliot Berman <[email protected]> [2022-12-19 14:58:32]:
>
>> +/* Call: CONSOLE_OPEN, CONSOLE_CLOSE, CONSOLE_FLUSH */
>
> I think this struct is used by other calls as well?
> Also CONSOLE_** functions are not yet introduced in this patch
>
>> +struct gh_vm_common_vmid_req {
>> + __le16 vmid;
>> + __le16 reserved0;
>> +} __packed;
>
> [snip]
>
>> +int gh_rm_alloc_vmid(struct gh_rm_rpc *rm, u16 vmid)
>> +{
>> + void *resp;
>> + struct gh_vm_common_vmid_req req_payload = {
>> + .vmid = cpu_to_le16(vmid),
>> + };
>> + struct gh_vm_common_vmid_req *resp_payload;
>> + size_t resp_size;
>> + int ret;
>> +
>> + if (vmid == GH_VMID_INVAL)
>> + vmid = 0;
>> +
>> + ret = gh_rm_call(rm, GH_RM_RPC_VM_ALLOC_VMID, &req_payload, sizeof(req_payload), &resp,
>> + &resp_size);
>> + if (ret)
>> + return ret;
>> +
>> + if (!vmid) {
>> + if (resp_size != sizeof(*resp_payload)) {
>> + ret = -EINVAL;
>> + } else {
>> + resp_payload = resp;
>> + ret = resp_payload->vmid;
>
> Do we need a le_to_cpu() wrapper on the response here?
>
>> +int gh_rm_vm_stop(struct gh_rm_rpc *rm, u16 vmid)
>> +{
>> + struct gh_vm_stop_req req_payload = {
>> + .vmid = cpu_to_le16(vmid),
>> + };
>> + void *resp;
>> + size_t resp_size;
>> + int ret;
>> +
>> + ret = gh_rm_call(rm, GH_RM_RPC_VM_STOP, &req_payload, sizeof(req_payload),
>> + &resp, &resp_size);
>> + if (ret)
>> + return ret;
>> + kfree(resp);
>
> Why not use gh_rm_common_vmid_call() here as well?
>
> return gh_rm_common_vmid_call(rm, GH_RM_RPC_VM_STOP, vmid);
>
>> +
>> + return ret;
>> +}
>> +EXPORT_SYMBOL_GPL(gh_rm_vm_stop);
>> +
>
> [snip]
>
>> +ssize_t gh_rm_get_hyp_resources(struct gh_rm_rpc *rm, u16 vmid,
>> + struct gh_rm_hyp_resource **resources)
>> +{
>> + struct gh_vm_get_hyp_resources_resp *resp;
>> + size_t resp_size;
>> + int ret;
>> + struct gh_vm_common_vmid_req req_payload = {
>> + .vmid = cpu_to_le16(vmid),
>> + };
>> +
>> + ret = gh_rm_call(rm, GH_RM_RPC_VM_GET_HYP_RESOURCES,
>> + &req_payload, sizeof(req_payload),
>> + (void **)&resp, &resp_size);
>> + if (ret)
>> + return ret;
>> +
>> + if (resp_size < sizeof(*resp) ||
>> + (sizeof(*resp->entries) && (resp->n_entries > U32_MAX / sizeof(*resp->entries))) ||
>> + (resp_size != sizeof(*resp) + (resp->n_entries * sizeof(*resp->entries)))) {
>> + ret = -EIO;
>> + goto out;
>> + }
>> +
>> + *resources = kmemdup(resp->entries, (resp->n_entries * sizeof(*resp->entries)), GFP_KERNEL);
>
> Consider NULL return value from kmemdup
>
>> + ret = resp->n_entries;
>> +
>> +out:
>> + kfree(resp);
>> + return ret;
>> +}
>> +EXPORT_SYMBOL_GPL(gh_rm_get_hyp_resources);
>> +
>> +/**
>> + * gh_rm_get_vmid() - Retrieve VMID of this virtual machine
>> + * @vmid: Filled with the VMID of this VM
>> + */
>> +int gh_rm_get_vmid(struct gh_rm_rpc *rm, u16 *vmid)
>> +{
>> + static u16 cached_vmid = GH_VMID_INVAL;
>> + void *resp;
>> + size_t resp_size;
>> + int ret;
>> + int payload = 0;
>> +
>> + if (cached_vmid != GH_VMID_INVAL) {
>> + *vmid = cached_vmid;
>> + return 0;
>> + }
>> +
>> + ret = gh_rm_call(rm, GH_RM_RPC_VM_GET_VMID, &payload, sizeof(payload), &resp, &resp_size);
>> + if (ret)
>> + return ret;
>> +
>> + if (resp_size != sizeof(*vmid))
>
> kfree(resp) in this case?
>
>> + return -EIO;
>> + *vmid = *(u16 *)resp;
>
> Do we need a le_to_cpu() wrapper on the response?
> Also update cached_vmid in success case.
>
>> + kfree(resp);
>> +
>> + return ret;
>> +}
Applied all these.
Thanks,
Elliot
On 1/9/2023 1:05 AM, Srivatsa Vaddagiri wrote:
> * Elliot Berman <[email protected]> [2022-12-19 14:58:33]:
>
>> +config GUNYAH_VM_MANAGER
>
> Any reason why this needs to be a separate config? IOW CONFIG_GUNYAH should
> enable VM management functionality also.
>
>> @@ -550,14 +580,29 @@ static int gh_rm_drv_probe(struct platform_device *pdev)
>> rsc_mgr->msgq_client.rx_callback = gh_rm_msgq_rx_data;
>> rsc_mgr->msgq_client.tx_done = gh_rm_msgq_tx_done;
>>
>> - return gh_msgq_init(&pdev->dev, &rsc_mgr->msgq, &rsc_mgr->msgq_client,
>> + ret = gh_msgq_init(&pdev->dev, &rsc_mgr->msgq, &rsc_mgr->msgq_client,
>> &rsc_mgr->tx_ghrsc, &rsc_mgr->rx_ghrsc);
>
> Bail on error here.
>
> [snip]
>
>> +static __must_check struct gunyah_vm *gunyah_vm_alloc(struct gh_rm_rpc *rm)
>> +{
>> + struct gunyah_vm *ghvm;
>> + int vmid;
>> +
>> + vmid = gh_rm_alloc_vmid(rm, 0);
>> + if (vmid < 0)
>> + return ERR_PTR(vmid);
>> +
>> + ghvm = kzalloc(sizeof(*ghvm), GFP_KERNEL);
>> + if (!ghvm)
>
> dealloc_vmid here (as well as few other error paths)?
>
>> + return ghvm;
>> +
>> + get_gh_rm(rm);
Applied all of these.
Thanks,
Elliot
On 12/19/22 4:58 PM, Elliot Berman wrote:
> Add architecture-independent standard error codes, types, and macros for
> Gunyah hypercalls.
>
> Reviewed-by: Dmitry Baryshkov <[email protected]>
> Signed-off-by: Elliot Berman <[email protected]>
> ---
> MAINTAINERS | 1 +
> include/linux/gunyah.h | 74 ++++++++++++++++++++++++++++++++++++++++++
> 2 files changed, 75 insertions(+)
> create mode 100644 include/linux/gunyah.h
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index f32320a9efa4..74e76e0ab14d 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -8941,6 +8941,7 @@ L: [email protected]
> S: Supported
> F: Documentation/devicetree/bindings/firmware/gunyah-hypervisor.yaml
> F: Documentation/virt/gunyah/
> +F: include/linux/gunyah.h
>
> HABANALABS PCI DRIVER
> M: Oded Gabbay <[email protected]>
> diff --git a/include/linux/gunyah.h b/include/linux/gunyah.h
> new file mode 100644
> index 000000000000..824e20a11d27
> --- /dev/null
> +++ b/include/linux/gunyah.h
> @@ -0,0 +1,74 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
> + */
> +
> +#ifndef _GUNYAH_H
Maybe use _LINUX_GUNYAH_H?
> +#define _GUNYAH_H
> +
> +#include <linux/types.h>
Why is types.h included?
U64_MAX is defined in <linux/limits.h> (in case that's why).
Otherwise this just defines a fixed API exposed by Gunyah
so there's not much more for me to comment on.
-Alex
> +#include <linux/errno.h>
> +
> +/* Common Gunyah macros */
> +#define GH_CAPID_INVAL U64_MAX
> +#define GH_VMID_ROOT_VM 0xff
> +
> +#define GH_ERROR_OK 0
> +
> +#define GH_ERROR_UNIMPLEMENTED -1
> +#define GH_ERROR_RETRY -2
> +
> +#define GH_ERROR_ARG_INVAL 1
> +#define GH_ERROR_ARG_SIZE 2
> +#define GH_ERROR_ARG_ALIGN 3
> +
> +#define GH_ERROR_NOMEM 10
> +
> +#define GH_ERROR_ADDR_OVFL 20
> +#define GH_ERROR_ADDR_UNFL 21
> +#define GH_ERROR_ADDR_INVAL 22
> +
> +#define GH_ERROR_DENIED 30
> +#define GH_ERROR_BUSY 31
> +#define GH_ERROR_IDLE 32
> +
> +#define GH_ERROR_IRQ_BOUND 40
> +#define GH_ERROR_IRQ_UNBOUND 41
> +
> +#define GH_ERROR_CSPACE_CAP_NULL 50
> +#define GH_ERROR_CSPACE_CAP_REVOKED 51
> +#define GH_ERROR_CSPACE_WRONG_OBJ_TYPE 52
> +#define GH_ERROR_CSPACE_INSUF_RIGHTS 53
> +#define GH_ERROR_CSPACE_FULL 54
> +
> +#define GH_ERROR_MSGQUEUE_EMPTY 60
> +#define GH_ERROR_MSGQUEUE_FULL 61
> +
> +static inline int gh_remap_error(int gh_error)
> +{
> + switch (gh_error) {
> + case GH_ERROR_OK:
> + return 0;
> + case GH_ERROR_NOMEM:
> + return -ENOMEM;
> + case GH_ERROR_DENIED:
> + case GH_ERROR_CSPACE_CAP_NULL:
> + case GH_ERROR_CSPACE_CAP_REVOKED:
> + case GH_ERROR_CSPACE_WRONG_OBJ_TYPE:
> + case GH_ERROR_CSPACE_INSUF_RIGHTS:
> + case GH_ERROR_CSPACE_FULL:
> + return -EACCES;
> + case GH_ERROR_BUSY:
> + case GH_ERROR_IDLE:
> + return -EBUSY;
> + case GH_ERROR_IRQ_BOUND:
> + case GH_ERROR_IRQ_UNBOUND:
> + case GH_ERROR_MSGQUEUE_FULL:
> + case GH_ERROR_MSGQUEUE_EMPTY:
> + return -EPERM;
> + default:
> + return -EINVAL;
> + }
> +}
> +
> +#endif
On 12/19/22 4:58 PM, Elliot Berman wrote:
> Add hypercalls to identify when Linux is running a virtual machine under
> Gunyah.
>
> There are two calls to help identify Gunyah:
>
> 1. gh_hypercall_get_uid() returns a UID when running under a Gunyah
> hypervisor.
> 2. gh_hypercall_hyp_identify() returns build information and a set of
> feature flags that are supported by Gunyah.
The first is a "service", while the second is a "hypercall".
Can you explain the distinction? The sentence at the top
refers to both as "hypercalls".
>
> Signed-off-by: Elliot Berman <[email protected]>
> ---
> MAINTAINERS | 2 +
> arch/arm64/Kbuild | 1 +
> arch/arm64/gunyah/Makefile | 1 +
> arch/arm64/gunyah/gunyah_hypercall.c | 69 ++++++++++++++++++++++++++++
> drivers/virt/Kconfig | 1 +
> drivers/virt/gunyah/Kconfig | 12 +++++
> include/linux/gunyah.h | 25 ++++++++++
> 7 files changed, 111 insertions(+)
> create mode 100644 arch/arm64/gunyah/Makefile
> create mode 100644 arch/arm64/gunyah/gunyah_hypercall.c
> create mode 100644 drivers/virt/gunyah/Kconfig
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 74e76e0ab14d..36698df6b0e5 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -8941,6 +8941,8 @@ L: [email protected]
> S: Supported
> F: Documentation/devicetree/bindings/firmware/gunyah-hypervisor.yaml
> F: Documentation/virt/gunyah/
> +F: arch/arm64/gunyah/
> +F: drivers/virt/gunyah/
> F: include/linux/gunyah.h
>
> HABANALABS PCI DRIVER
> diff --git a/arch/arm64/Kbuild b/arch/arm64/Kbuild
> index 5bfbf7d79c99..e4847ba0e3c9 100644
> --- a/arch/arm64/Kbuild
> +++ b/arch/arm64/Kbuild
> @@ -3,6 +3,7 @@ obj-y += kernel/ mm/ net/
> obj-$(CONFIG_KVM) += kvm/
> obj-$(CONFIG_XEN) += xen/
> obj-$(subst m,y,$(CONFIG_HYPERV)) += hyperv/
> +obj-$(CONFIG_GUNYAH) += gunyah/
> obj-$(CONFIG_CRYPTO) += crypto/
>
> # for cleaning
> diff --git a/arch/arm64/gunyah/Makefile b/arch/arm64/gunyah/Makefile
> new file mode 100644
> index 000000000000..9fbc720b6fb6
> --- /dev/null
> +++ b/arch/arm64/gunyah/Makefile
> @@ -0,0 +1 @@
> +obj-$(CONFIG_GUNYAH) += gunyah_hypercall.o
> diff --git a/arch/arm64/gunyah/gunyah_hypercall.c b/arch/arm64/gunyah/gunyah_hypercall.c
> new file mode 100644
> index 000000000000..0beb3123d650
> --- /dev/null
> +++ b/arch/arm64/gunyah/gunyah_hypercall.c
> @@ -0,0 +1,69 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
> + */
> +
> +#include <linux/arm-smccc.h>
> +#include <linux/module.h>
> +#include <linux/gunyah.h>
> +
> +#define GH_CALL_TYPE_PLATFORM_CALL 0
> +#define GH_CALL_TYPE_HYPERCALL 2
> +#define GH_CALL_TYPE_SERVICE 3
> +#define GH_CALL_TYPE_SHIFT 14
> +#define GH_CALL_FUNCTION_NUM_MASK 0x3fff
A FN_ID is a 32-bit value. Are all 18 high-order bits considered
part of the call type? It might be good to specify that explicitly
by defining a mask for it.
> +
> +#define GH_FN_ID(type, num) ((type) << GH_CALL_TYPE_SHIFT | ((num) & GH_CALL_FUNCTION_NUM_MASK))
> +
Is there any need for the endianness of these values to be specified?
Does Gunyah operate with a well-defined endianness? Is there any
chance a VM can run with an endianness different from Gunyah? I
see that the arm_smcc_* structures are defined without endianness.
(Sorry if these are dumb questions.)
> +#define GH_SERVICE(fn) ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, ARM_SMCCC_SMC_32, \
> + ARM_SMCCC_OWNER_VENDOR_HYP, \
> + GH_FN_ID(GH_CALL_TYPE_SERVICE, fn))
> +
> +#define GH_HYPERCALL_CALL_UID GH_SERVICE(0x3f01)
Perhaps 0x3f01 could be defined symbolically.
However if this is the only place it's ever used, doing so
doesn't add much value (meaning, just do it the way you did).
> +
> +#define GH_HYPERCALL(fn) ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, ARM_SMCCC_SMC_64, \
> + ARM_SMCCC_OWNER_VENDOR_HYP, \
> + GH_FN_ID(GH_CALL_TYPE_HYPERCALL, fn))
> +
> +#define GH_HYPERCALL_HYP_IDENTIFY GH_HYPERCALL(0x0000)
Will there be a growing set of well-known hypervisor call functions?
Perhaps 0x0000 should be defined symbolically. (Or not if it's only
used here.)
> +
> +/**
> + * gh_hypercall_get_uid() - Returns a UID when running under a Gunyah hypervisor
> + * @uid: An array of 4 u32's (u32 uid[4];)
> + *
> + * Caller should compare the resulting UID to a list of known Gunyah UIDs to
> + * confirm that Linux is running as a guest of Gunyah.
I presume that, if the returned UID isn't well-known, then no other
Gunyah-related calls are meaningful. Is that correct?
> + */
> +void gh_hypercall_get_uid(u32 uid[4])
> +{
> + struct arm_smccc_res res;
> +
> + arm_smccc_1_1_hvc(GH_HYPERCALL_CALL_UID, &res);
> +
> + uid[0] = res.a0;
> + uid[1] = res.a1;
> + uid[2] = res.a2;
> + uid[3] = res.a3;
I see in the definition of struct arm_smccc_res that the four
fields are unsigned long values. That differs from the u32
array passed as argument. Are the resource IDs guaranteed to
be four 32-bit values? I personally prefer being explicit
about the upper 32-bits being discarded (though some don't
agree with that convention).
> +}
> +EXPORT_SYMBOL_GPL(gh_hypercall_get_uid);
> +
> +/**
> + * gh_hypercall_hyp_identify() - Returns build information and feature flags
> + * supported by Gunyah.
> + * @hyp_identity: filled by the hypercall with the API info and feature flags.
> + */
> +void gh_hypercall_hyp_identify(struct gh_hypercall_hyp_identify_resp *hyp_identity)
> +{
> + struct arm_smccc_res res;
> +
> + arm_smccc_1_1_hvc(GH_HYPERCALL_HYP_IDENTIFY, &res);
> +
> + hyp_identity->api_info = res.a0;
> + hyp_identity->flags[0] = res.a1;
> + hyp_identity->flags[1] = res.a2;
> + hyp_identity->flags[2] = res.a3;
> +}
> +EXPORT_SYMBOL_GPL(gh_hypercall_hyp_identify);
> +
> +MODULE_LICENSE("GPL");
> +MODULE_DESCRIPTION("Gunyah Hypervisor Hypercalls");
> diff --git a/drivers/virt/Kconfig b/drivers/virt/Kconfig
> index 87ef258cec64..259dc2be6cad 100644
> --- a/drivers/virt/Kconfig
> +++ b/drivers/virt/Kconfig
> @@ -52,4 +52,5 @@ source "drivers/virt/coco/efi_secret/Kconfig"
>
> source "drivers/virt/coco/sev-guest/Kconfig"
>
> +source "drivers/virt/gunyah/Kconfig"
> endif
> diff --git a/drivers/virt/gunyah/Kconfig b/drivers/virt/gunyah/Kconfig
> new file mode 100644
> index 000000000000..127156a678a6
> --- /dev/null
> +++ b/drivers/virt/gunyah/Kconfig
> @@ -0,0 +1,12 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +
> +config GUNYAH
Maybe config QCOM_GUNYAH? Will this ever run on hardware
other than Qualcomm's?
> + tristate "Gunyah Virtualization drivers"
> + depends on ARM64
> + help
> + The Gunyah drivers are the helper interfaces that runs in a guest VM
s/runs/run/
> + such as basic inter-VM IPC and signaling mechanisms, and higher level
> + services such as memory/device sharing, IRQ sharing, and so on.
> +
> + Say Y/M here to enable the drivers needed to interact in a Gunyah
> + virtual environment.
> diff --git a/include/linux/gunyah.h b/include/linux/gunyah.h
> index 824e20a11d27..2765d2b40198 100644
> --- a/include/linux/gunyah.h
> +++ b/include/linux/gunyah.h
> @@ -6,6 +6,7 @@
> #ifndef _GUNYAH_H
> #define _GUNYAH_H
>
> +#include <linux/bitfield.h>
> #include <linux/types.h>
> #include <linux/errno.h>
>
> @@ -71,4 +72,28 @@ static inline int gh_remap_error(int gh_error)
> }
> }
>
> +#define GUNYAH_API_V1 1
> +
> +#define GH_API_INFO_API_VERSION_MASK GENMASK_ULL(13, 0)
> +#define GH_API_INFO_BIG_ENDIAN BIT_ULL(14)
> +#define GH_API_INFO_IS_64BIT BIT_ULL(15)
> +#define GH_API_INFO_VARIANT_MASK GENMASK_ULL(63, 56)
> +
How are the GH_IDENTIFY bits below used? Are they encoded
in the three 64-bit flags fields in the response structure?
Does that mean only the first of those three is (currently)
used?
> +#define GH_IDENTIFY_PARTITION_CSPACE BIT_ULL(0)
> +#define GH_IDENTIFY_DOORBELL BIT_ULL(1)
> +#define GH_IDENTIFY_MSGQUEUE BIT_ULL(2)
> +#define GH_IDENTIFY_VIC BIT_ULL(3)
> +#define GH_IDENTIFY_VPM BIT_ULL(4)
> +#define GH_IDENTIFY_VCPU BIT_ULL(5)
> +#define GH_IDENTIFY_MEMEXTENT BIT_ULL(6)
> +#define GH_IDENTIFY_TRACE_CTRL BIT_ULL(7)
> +
> +struct gh_hypercall_hyp_identify_resp {
> + u64 api_info;
> + u64 flags[3];
> +};
Again I'll ask about endianness. This is a response coming *from*
Gunyah. Is it guaranteed to use the same byte order convention as
the running operating system (Linux) guest?
-Alex
> +
> +void gh_hypercall_get_uid(u32 uid[4]);
> +void gh_hypercall_hyp_identify(struct gh_hypercall_hyp_identify_resp *hyp_identity);
> +
> #endif
On 12/19/22 4:58 PM, Elliot Berman wrote:
> Gunyah message queues are a unidirectional inter-VM pipe for messages up
s/Gunyah message queues are/A Gunyah message queue is/
> to 1024 bytes. This driver supports pairing a receiver message queue and
> a transmitter message queue to expose a single mailbox channel.
>
> Signed-off-by: Elliot Berman <[email protected]>
> ---
> Documentation/virt/gunyah/message-queue.rst | 8 +
> MAINTAINERS | 1 +
> drivers/mailbox/Kconfig | 10 +
> drivers/mailbox/Makefile | 2 +
> drivers/mailbox/gunyah-msgq.c | 229 ++++++++++++++++++++
> include/linux/gunyah.h | 61 +++++-
> 6 files changed, 309 insertions(+), 2 deletions(-)
> create mode 100644 drivers/mailbox/gunyah-msgq.c
>
> diff --git a/Documentation/virt/gunyah/message-queue.rst b/Documentation/virt/gunyah/message-queue.rst
> index be4ab289236a..9245dbe6dd8a 100644
> --- a/Documentation/virt/gunyah/message-queue.rst
> +++ b/Documentation/virt/gunyah/message-queue.rst
> @@ -54,3 +54,11 @@ vIRQ: two TX message queues will have two vIRQs (and two capability IDs).
> | | | | | |
> | | | | | |
> +---------------+ +-----------------+ +---------------+
> +
> +Gunyah message queues are exposed as mailboxes. To create the mailbox, create
> +a mbox_client and call `gh_msgq_init`. On receipt of the RX_READY interrupt,
> +all messages in the RX message queue are read and pushed via the `rx_callback`
> +of the registered mbox_client.
> +
> +.. kernel-doc:: drivers/mailbox/gunyah-msgq.c
> + :identifiers: gh_msgq_init
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 36698df6b0e5..667480bfd387 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -8942,6 +8942,7 @@ S: Supported
> F: Documentation/devicetree/bindings/firmware/gunyah-hypervisor.yaml
> F: Documentation/virt/gunyah/
> F: arch/arm64/gunyah/
> +F: drivers/mailbox/gunyah-msgq.c
> F: drivers/virt/gunyah/
> F: include/linux/gunyah.h
>
> diff --git a/drivers/mailbox/Kconfig b/drivers/mailbox/Kconfig
> index 05d6fae800e3..baf9451c5f04 100644
> --- a/drivers/mailbox/Kconfig
> +++ b/drivers/mailbox/Kconfig
> @@ -41,6 +41,16 @@ config IMX_MBOX
> help
> Mailbox implementation for i.MX Messaging Unit (MU).
>
> +config GUNYAH_MESSAGE_QUEUES
Is Gunyah useful without message queues? Are Gunyah message
queues useful without the rest of Gunyah? Are there other
possible message queue implementations? My point is that
maybe this isn't a necessary kernel config option.
> + tristate "Gunyah Message Queue Mailbox"
> + depends on GUNYAH
> + help
> + Mailbox implementation for Gunyah Message Queues. Gunyah message queues
> + are an IPC mechanism to pass short messages between virtual machines
> + running under the Gunyah hypervisor.
> +
> + Say Y here if you run Linux as a Gunyah virtual machine.
> +
> config PLATFORM_MHU
> tristate "Platform MHU Mailbox"
> depends on OF
> diff --git a/drivers/mailbox/Makefile b/drivers/mailbox/Makefile
> index fc9376117111..5f929bb55e9a 100644
> --- a/drivers/mailbox/Makefile
> +++ b/drivers/mailbox/Makefile
> @@ -55,6 +55,8 @@ obj-$(CONFIG_MTK_CMDQ_MBOX) += mtk-cmdq-mailbox.o
>
> obj-$(CONFIG_ZYNQMP_IPI_MBOX) += zynqmp-ipi-mailbox.o
>
> +obj-$(CONFIG_GUNYAH) += gunyah-msgq.o
Assuming you keep the Kconfig option, should this be
referring to CONFIG_GUNYAH_MESSAGE_QUEUES?
> +
> obj-$(CONFIG_SUN6I_MSGBOX) += sun6i-msgbox.o
>
> obj-$(CONFIG_SPRD_MBOX) += sprd-mailbox.o
> diff --git a/drivers/mailbox/gunyah-msgq.c b/drivers/mailbox/gunyah-msgq.c
> new file mode 100644
> index 000000000000..d5a324e85428
> --- /dev/null
> +++ b/drivers/mailbox/gunyah-msgq.c
> @@ -0,0 +1,229 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
> + */
> +
> +#include <linux/mailbox_controller.h>
> +#include <linux/module.h>
> +#include <linux/interrupt.h>
> +#include <linux/gunyah.h>
> +#include <linux/printk.h>
> +#include <linux/init.h>
> +#include <linux/slab.h>
> +#include <linux/wait.h>
> +
> +#define mbox_chan_to_msgq(chan) (container_of(chan->mbox, struct gh_msgq, mbox))
> +
> +static inline bool gh_msgq_has_tx(struct gh_msgq *msgq)
> +{
> + return msgq->tx_ghrsc->type == GUNYAH_RESOURCE_TYPE_MSGQ_TX;
I think this was pointed out elsewhere; I think msgq->tx_ghrsc
can be null. Same for msgq->rx_ghrsc below.
> +}
> +
> +static inline bool gh_msgq_has_rx(struct gh_msgq *msgq)
> +{
> + return msgq->rx_ghrsc->type == GUNYAH_RESOURCE_TYPE_MSGQ_RX;
Is there any chance that a message queue would be created whose
type was anything other than MSGQ_RX? It's possible that there
is some generic resource code that needs this type field, but
in this case I don't see why it's important.
And... If that's the case, I think it's perhaps clearer to
just replace calls to gs_msgq_has_rx(msgq) with:
if (msgq->rx_ghrsc)
/* there is an RX message queue */
> +}
> +
> +static irqreturn_t gh_msgq_rx_irq_handler(int irq, void *data)
> +{
> + struct gh_msgq *msgq = data;
> + struct gh_msgq_rx_data rx_data;
> + unsigned long gh_err;
> + ssize_t ret;
> + bool ready = false;
> +
> + do {
Maybe: bool ready = true;
while (ready) {
> + gh_err = gh_hypercall_msgq_recv(msgq->rx_ghrsc->capid,
> + (uintptr_t)&rx_data.data, sizeof(rx_data.data),
> + &rx_data.length, &ready);
> + if (gh_err == GH_ERROR_OK) {
> + mbox_chan_received_data(gh_msgq_chan(msgq), &rx_data);
Maybe continue;
}
if (gh_err != GH_ERROR_MSGQUEUE_EMPTY)
pr_warn(...);
break;
> + } else if (GH_ERROR_MSGQUEUE_EMPTY) {
> + break;
> + } else {
> + pr_warn("Failed to receive data from msgq for %s: %ld\n",
s/%ld/%zd/
> + msgq->mbox.dev ? dev_name(msgq->mbox.dev) : "", ret);
> + break;
> + }
> + } while (ready);
> +
> + return IRQ_HANDLED;
> +}
> +
> +static irqreturn_t gh_msgq_tx_irq_handler(int irq, void *data)
> +{
> + struct gh_msgq *msgq = data;
> +
> + mbox_chan_txdone(gh_msgq_chan(msgq), 0);
> +
> + return IRQ_HANDLED;
> +}
> +
> +static void gh_msgq_txdone_tasklet(struct tasklet_struct *tasklet)
> +{
> + struct gh_msgq *msgq = container_of(tasklet, struct gh_msgq, txdone_tasklet);
> +
> + mbox_chan_txdone(gh_msgq_chan(msgq), msgq->last_status);
> +}
> +
> +static int gh_msgq_send_data(struct mbox_chan *chan, void *data)
> +{
> + struct gh_msgq *msgq = mbox_chan_to_msgq(chan);
> + struct gh_msgq_tx_data *msgq_data = data;
> + u64 tx_flags = 0;
> + unsigned long ret;
> + bool ready;
> +
> + if (msgq_data->push)
> + tx_flags |= GH_HYPERCALL_MSGQ_TX_FLAGS_PUSH;
> +
I probably missed something, but where is gh_hypercall_msgq_send()
defined? I'm interested in what the ready flag is used for.
(Can the return value encode that?)
> + ret = gh_hypercall_msgq_send(msgq->tx_ghrsc->capid, msgq_data->length,
> + (uintptr_t)msgq_data->data, tx_flags, &ready);
> +
> + /**
> + * unlikely because Linux tracks state of msgq and should not try to
> + * send message when msgq is full.
> + */
Is it just unlikely, or is it impossible?
> + if (unlikely(ret == GH_ERROR_MSGQUEUE_FULL))
> + return -EAGAIN;
> +
> + /**
> + * Propagate all other errors to client. If we return error to mailbox
> + * framework, then no other messages can be sent and nobody will know
> + * to retry this message.
> + */
> + msgq->last_status = gh_remap_error(ret);
> +
> + /**
> + * This message was successfully sent, but message queue isn't ready to
> + * receive more messages because it's now full. Mailbox framework
> + * requires that we only report that message was transmitted only when
s/only // (for one of them)
> + * we're ready to transmit another message. We'll get that in the form
> + * of tx IRQ once the other side starts to drain the msgq.
> + */
> + if (ret == GH_ERROR_OK && !ready)
> + return 0;
> +
> + /**
> + * We can send more messages. Mailbox framework requires that tx done
> + * happens asynchronously to sending the message. Gunyah message queues
> + * tell us right away on the hypercall return whether we can send more
> + * messages. To work around this, defer the txdone to a tasklet.
> + */
> + tasklet_schedule(&msgq->txdone_tasklet);
> +
> + return 0;
> +}
> +
> +struct mbox_chan_ops gh_msgq_ops = {
> + .send_data = gh_msgq_send_data,
> +};
> +
> +/**
> + * gh_msgq_init() - Initialize a Gunyah message queue with an mbox_client
> + * @parent: optional, device parent used for the mailbox controller
> + * @msgq: Pointer to the gh_msgq to initialize
> + * @cl: A mailbox client to bind to the mailbox channel that the message queue creates
> + * @tx_ghrsc: optional, the transmission side of the message queue
> + * @rx_ghrsc: optional, the receiving side of the message queue
> + *
> + * At least one of tx_ghrsc and rx_ghrsc should be not NULL. Most message queue use cases come with
> + * a pair of message queues to facilitiate bidirectional communication. When tx_ghrsc is set,
s/facilitiate/facilitate/
> + * the client can send messages with mbox_send_message(gh_msgq_chan(msgq), msg). When rx_ghrsc
> + * is set, the mbox_client should register an .rx_callback() and the message queue driver will
> + * push all available messages upon receiving the RX ready interrupt. The messages should be
> + * consumed or copied by the client right away as the gh_msgq_rx_data will be replaced/destroyed
> + * after the callback.
> + *
> + * Returns - 0 on success, negative otherwise
> + */
> +int gh_msgq_init(struct device *parent, struct gh_msgq *msgq, struct mbox_client *cl,
> + struct gunyah_resource *tx_ghrsc, struct gunyah_resource *rx_ghrsc)
> +{
> + int ret;
> +
> + /* Must have at least a tx_ghrsc or rx_ghrsc and that they are the right device types */
> + if ((!tx_ghrsc && !rx_ghrsc) ||
> + (tx_ghrsc && tx_ghrsc->type != GUNYAH_RESOURCE_TYPE_MSGQ_TX) ||
> + (rx_ghrsc && rx_ghrsc->type != GUNYAH_RESOURCE_TYPE_MSGQ_RX))
The internal parenthesis pairs above aren't really required.
> + return -EINVAL;
> +
> + msgq->tx_ghrsc = tx_ghrsc;
> + msgq->rx_ghrsc = rx_ghrsc;
> +
> + msgq->mbox.dev = parent;
> + msgq->mbox.ops = &gh_msgq_ops;
> + msgq->mbox.num_chans = 1;
> + msgq->mbox.chans = kcalloc(msgq->mbox.num_chans, sizeof(*msgq->mbox.chans), GFP_KERNEL);
> + if (!msgq->mbox.chans)
> + return -ENOMEM;
> + msgq->mbox.txdone_irq = true;
> +
> + if (gh_msgq_has_tx(msgq)) {
Maybe if (tx_ghrsc) {
> + ret = request_irq(msgq->tx_ghrsc->irq, gh_msgq_tx_irq_handler, 0, "gh_msgq_tx",
> + msgq);
> + if (ret)
> + goto err_chans;
> + }
> +
> + if (gh_msgq_has_rx(msgq)) {
Maybe if (rx_ghrsc) {
> + ret = request_threaded_irq(msgq->rx_ghrsc->irq, NULL, gh_msgq_rx_irq_handler,
> + IRQF_ONESHOT, "gh_msgq_rx", msgq);
> + if (ret)
> + goto err_tx_irq;
> + }
> +
> + tasklet_setup(&msgq->txdone_tasklet, gh_msgq_txdone_tasklet);
> +
> + ret = mbox_controller_register(&msgq->mbox);
> + if (ret)
> + goto err_rx_irq;
> +
> + ret = mbox_bind_client(gh_msgq_chan(msgq), cl);
> + if (ret)
> + goto err_mbox;
> +
> + return 0;
> +err_mbox:
> + mbox_controller_unregister(&msgq->mbox);
> +err_rx_irq:
> + if (gh_msgq_has_rx(msgq))
if (rx_ghrsc)
> + free_irq(msgq->rx_ghrsc->irq, msgq);
> +err_tx_irq:
> + if (gh_msgq_has_tx(msgq))
if (tx_ghrsc)
> + free_irq(msgq->tx_ghrsc->irq, msgq);
> +err_chans:
> + kfree(msgq->mbox.chans);
> + return ret;
> +}
> +EXPORT_SYMBOL_GPL(gh_msgq_init);
> +
> +void gh_msgq_remove(struct gh_msgq *msgq)
> +{
> + mbox_controller_unregister(&msgq->mbox);
> +
> + if (gh_msgq_has_rx(msgq))
> + free_irq(msgq->rx_ghrsc->irq, msgq);
> +
> + if (gh_msgq_has_tx(msgq))
> + free_irq(msgq->tx_ghrsc->irq, msgq);
> +
> + kfree(msgq->mbox.chans);
> +}
> +EXPORT_SYMBOL_GPL(gh_msgq_remove);
> +
> +
> +static int __init gh_msgq_init_module(void)
> +{
> + if (gh_api_version() != GUNYAH_API_V1) {
> + pr_warn("Unrecognized gunyah version: %u. Currently supported: %d\n",
> + gh_api_version(), GUNYAH_API_V1);
> + return -ENODEV;
> + }
> +
> + return 0;
> +}
> +module_init(gh_msgq_init_module);
> +
> +MODULE_LICENSE("GPL");
> +MODULE_DESCRIPTION("Gunyah Message Queue Driver");
> diff --git a/include/linux/gunyah.h b/include/linux/gunyah.h
> index c863cac4a3cf..e317d7ac938f 100644
> --- a/include/linux/gunyah.h
> +++ b/include/linux/gunyah.h
> @@ -7,10 +7,67 @@
> #define _GUNYAH_H
>
> #include <linux/bitfield.h>
> -#include <linux/types.h>
> #include <linux/errno.h>
> +#include <linux/interrupt.h>
> +#include <linux/mailbox_controller.h>
> +#include <linux/mailbox_client.h>
> +#include <linux/types.h>
> +
> +/* Follows resource manager's resource types for VM_GET_HYP_RESOURCES */
> +enum gunyah_resource_type {
> + GUNYAH_RESOURCE_TYPE_BELL_TX = 0,
> + GUNYAH_RESOURCE_TYPE_BELL_RX = 1,
> + GUNYAH_RESOURCE_TYPE_MSGQ_TX = 2,
> + GUNYAH_RESOURCE_TYPE_MSGQ_RX = 3,
> + GUNYAH_RESOURCE_TYPE_VCPU = 4,
> +};
> +
> +struct gunyah_resource {
> + enum gunyah_resource_type type;
> + u64 capid;
> + int irq;
> +};
> +
> +/**
> + * Gunyah Message Queues
> + */
> +
> +#define GH_MSGQ_MAX_MSG_SIZE 240
> +
> +struct gh_msgq_tx_data {
> + size_t length;
> + bool push;
> + char data[];
> +};
> +
> +struct gh_msgq_rx_data {
> + size_t length;
> + char data[GH_MSGQ_MAX_MSG_SIZE];
> +};
> +
> +struct gh_msgq {
> + struct gunyah_resource *tx_ghrsc;
> + struct gunyah_resource *rx_ghrsc;
> +
> + /* msgq private */
> + int last_status;
Maybe note that last_status is an errno (and not GH_ERROR_*)
-Alex
> + struct mbox_controller mbox;
> + struct tasklet_struct txdone_tasklet;
> +};
> +
> +
> +int gh_msgq_init(struct device *parent, struct gh_msgq *msgq, struct mbox_client *cl,
> + struct gunyah_resource *tx_ghrsc, struct gunyah_resource *rx_ghrsc);
> +void gh_msgq_remove(struct gh_msgq *msgq);
> +
> +static inline struct mbox_chan *gh_msgq_chan(struct gh_msgq *msgq)
> +{
> + return &msgq->mbox.chans[0];
> +}
> +
> +/******************************************************************************/
> +/* Common arch-independent macros and definitions for Gunyah hypercalls */
>
> -/* Common Gunyah macros */
> #define GH_CAPID_INVAL U64_MAX
> #define GH_VMID_ROOT_VM 0xff
>
On 12/19/22 4:58 PM, Elliot Berman wrote:
> Gunyah is a Type-1 hypervisor independent of any
> high-level OS kernel, and runs in a higher CPU privilege level. It does
> not depend on any lower-privileged OS kernel/code for its core
> functionality. This increases its security and can support a much smaller
> trusted computing base than a Type-2 hypervisor.
>
> Gunyah is an open source hypervisor. The source repo is available at
> https://github.com/quic/gunyah-hypervisor.
Can you provide any history about the hypervisor code itself?
Was it publicly reviewed? Has it been reviewed by anyone in
the Linux kernel community, who might have some useful input
on it?
> The diagram below shows the architecture.
>
> ::
>
> VM A VM B
> +-----+ +-----+ | +-----+ +-----+ +-----+
> | | | | | | | | | | |
> EL0 | APP | | APP | | | APP | | APP | | APP |
> | | | | | | | | | | |
> +-----+ +-----+ | +-----+ +-----+ +-----+
> ---------------------|-------------------------
> +--------------+ | +----------------------+
> | | | | |
> EL1 | Linux Kernel | | |Linux kernel/Other OS | ...
> | | | | |
> +--------------+ | +----------------------+
> --------hvc/smc------|------hvc/smc------------
> +----------------------------------------+
> | |
> EL2 | Gunyah Hypervisor |
> | |
> +----------------------------------------+
>
> Gunyah provides these following features.
>
> - Threads and Scheduling: The scheduler schedules virtual CPUs (VCPUs) on
> physical CPUs and enables time-sharing of the CPUs.
> - Memory Management: Gunyah tracks memory ownership and use of all memory
> under its control. Memory partitioning between VMs is a fundamental
> security feature.
> - Interrupt Virtualization: All interrupts are handled in the hypervisor
> and routed to the assigned VM.
> - Inter-VM Communication: There are several different mechanisms provided
> for communicating between VMs.
> - Device Virtualization: Para-virtualization of devices is supported using
> inter-VM communication. Low level system features and devices such as
> interrupt controllers are supported with emulation where required.
>
> This series adds the basic framework for detecting that Linux is running
> under Gunyah as a virtual machine, communication with the Gunyah Resource
> Manager, and a virtual machine manager capable of launching virtual machines.
>
> Patches 21-28 are presently intended to be submitted separately and are included
> for initial RFC. These patches introudce "VM function" framework to expose further
> interfaces to interact with Gunyah Virtual Machines. With all 28 patches, it is
> possible to create a Gunyah VM supporting virtio.
I'm not a virtualization expert, and it's likely some of my
comments demonstrate that...
You're going to need to update your copyright dates to include
2023 now.
I haven't looked at the earlier reviews; perhaps the RFC stuff
was requested. I'm sure it's useful to see that but it doesn't
seem directly helpful if your goal is to get this code upstream.
At this point I've reviewed only through patch 9. I currently
have smallish blocks of time and I don't want to hold back my
feedback for too long.
-Alex
> Changes in v8:
> - Treat VM manager as a library of RM
> - Add patches 21-28 as RFC to support proxy-scheduled vCPUs and necessary bits to support virtio
> from Gunyah userspace
>
> Changes in v7: https://lore.kernel.org/all/[email protected]/
> - Refactor to remove gunyah RM bus
> - Refactor allow multiple RM device instances
> - Bump UAPI to start at 0x0
> - Refactor QCOM SCM's platform hooks to allow CONFIG_QCOM_SCM=Y/CONFIG_GUNYAH=M combinations
>
> Changes in v6: https://lore.kernel.org/all/[email protected]/
> - *Replace gunyah-console with gunyah VM Manager*
> - Move include/asm-generic/gunyah.h into include/linux/gunyah.h
> - s/gunyah_msgq/gh_msgq/
> - Minor tweaks and documentation tidying based on comments from Jiri, Greg, Arnd, Dmitry, and Bagas.
>
> Changes in v5: https://lore.kernel.org/all/[email protected]/
> - Dropped sysfs nodes
> - Switch from aux bus to Gunyah RM bus for the subdevices
> - Cleaning up RM console
>
> Changes in v4: https://lore.kernel.org/all/[email protected]/
> - Tidied up documentation throughout based on questions/feedback received
> - Switched message queue implementation to use mailboxes
> - Renamed "gunyah_device" as "gunyah_resource"
>
> Changes in v3: https://lore.kernel.org/all/[email protected]/
> - /Maintained/Supported/ in MAINTAINERS
> - Tidied up documentation throughout based on questions/feedback received
> - Moved hypercalls into arch/arm64/gunyah/; following hyper-v's implementation
> - Drop opaque typedefs
> - Move sysfs nodes under /sys/hypervisor/gunyah/
> - Moved Gunyah console driver to drivers/tty/
> - Reworked gunyah_device design to drop the Gunyah bus.
>
> Changes in v2: https://lore.kernel.org/all/[email protected]/
> - DT bindings clean up
> - Switch hypercalls to follow SMCCC
>
> v1: https://lore.kernel.org/all/[email protected]/
>
> Elliot Berman (28):
> docs: gunyah: Introduce Gunyah Hypervisor
> dt-bindings: Add binding for gunyah hypervisor
> gunyah: Common types and error codes for Gunyah hypercalls
> arm64: smccc: Include alternative-macros.h
> virt: gunyah: Add hypercalls to identify Gunyah
> virt: gunyah: Identify hypervisor version
> mailbox: Allow direct registration to a channel
> virt: gunyah: msgq: Add hypercalls to send and receive messages
> mailbox: Add Gunyah message queue mailbox
> gunyah: rsc_mgr: Add resource manager RPC core
> gunyah: rsc_mgr: Add VM lifecycle RPC
> gunyah: vm_mgr: Introduce basic VM Manager
> gunyah: rsc_mgr: Add RPC for sharing memory
> gunyah: vm_mgr: Add/remove user memory regions
> gunyah: vm_mgr: Add ioctls to support basic non-proxy VM boot
> samples: Add sample userspace Gunyah VM Manager
> gunyah: rsc_mgr: Add platform ops on mem_lend/mem_reclaim
> firmware: qcom_scm: Use fixed width src vm bitmap
> firmware: qcom_scm: Register Gunyah platform ops
> docs: gunyah: Document Gunyah VM Manager
> virt: gunyah: Translate gh_rm_hyp_resource into gunyah_resource
> gunyah: vm_mgr: Add framework to add VM Functions
> virt: gunyah: Add resource tickets
> virt: gunyah: Add IO handlers
> virt: gunyah: Add proxy-scheduled vCPUs
> virt: gunyah: Add hypercalls for sending doorbell
> virt: gunyah: Add irqfd interface
> virt: gunyah: Add ioeventfd
>
> .../bindings/firmware/gunyah-hypervisor.yaml | 82 ++
> .../userspace-api/ioctl/ioctl-number.rst | 1 +
> Documentation/virt/gunyah/index.rst | 115 +++
> Documentation/virt/gunyah/message-queue.rst | 64 ++
> Documentation/virt/gunyah/vm-manager.rst | 187 ++++
> Documentation/virt/index.rst | 1 +
> MAINTAINERS | 13 +
> arch/arm64/Kbuild | 1 +
> arch/arm64/gunyah/Makefile | 1 +
> arch/arm64/gunyah/gunyah_hypercall.c | 157 ++++
> arch/arm64/include/asm/gunyah.h | 23 +
> drivers/firmware/Kconfig | 2 +
> drivers/firmware/qcom_scm.c | 107 ++-
> drivers/mailbox/Kconfig | 10 +
> drivers/mailbox/Makefile | 2 +
> drivers/mailbox/gunyah-msgq.c | 229 +++++
> drivers/mailbox/mailbox.c | 96 ++-
> drivers/mailbox/omap-mailbox.c | 18 +-
> drivers/mailbox/pcc.c | 18 +-
> drivers/misc/fastrpc.c | 6 +-
> drivers/net/wireless/ath/ath10k/qmi.c | 4 +-
> drivers/remoteproc/qcom_q6v5_mss.c | 8 +-
> drivers/soc/qcom/rmtfs_mem.c | 2 +-
> drivers/virt/Kconfig | 1 +
> drivers/virt/Makefile | 1 +
> drivers/virt/gunyah/Kconfig | 73 ++
> drivers/virt/gunyah/Makefile | 10 +
> drivers/virt/gunyah/gunyah.c | 46 +
> drivers/virt/gunyah/gunyah_ioeventfd.c | 109 +++
> drivers/virt/gunyah/gunyah_irqfd.c | 180 ++++
> drivers/virt/gunyah/gunyah_platform_hooks.c | 63 ++
> drivers/virt/gunyah/gunyah_vcpu.c | 350 ++++++++
> drivers/virt/gunyah/rsc_mgr.c | 795 ++++++++++++++++++
> drivers/virt/gunyah/rsc_mgr.h | 124 +++
> drivers/virt/gunyah/rsc_mgr_rpc.c | 428 ++++++++++
> drivers/virt/gunyah/vm_mgr.c | 594 +++++++++++++
> drivers/virt/gunyah/vm_mgr.h | 76 ++
> drivers/virt/gunyah/vm_mgr_mm.c | 245 ++++++
> include/linux/arm-smccc.h | 1 +
> include/linux/gunyah.h | 186 ++++
> include/linux/gunyah_rsc_mgr.h | 136 +++
> include/linux/gunyah_vm_mgr.h | 107 +++
> include/linux/mailbox_client.h | 1 +
> include/linux/qcom_scm.h | 2 +-
> include/uapi/linux/gunyah.h | 114 +++
> samples/Kconfig | 10 +
> samples/Makefile | 1 +
> samples/gunyah/.gitignore | 2 +
> samples/gunyah/Makefile | 6 +
> samples/gunyah/gunyah_vmm.c | 270 ++++++
> samples/gunyah/sample_vm.dts | 69 ++
> 51 files changed, 5075 insertions(+), 72 deletions(-)
> create mode 100644 Documentation/devicetree/bindings/firmware/gunyah-hypervisor.yaml
> create mode 100644 Documentation/virt/gunyah/index.rst
> create mode 100644 Documentation/virt/gunyah/message-queue.rst
> create mode 100644 Documentation/virt/gunyah/vm-manager.rst
> create mode 100644 arch/arm64/gunyah/Makefile
> create mode 100644 arch/arm64/gunyah/gunyah_hypercall.c
> create mode 100644 arch/arm64/include/asm/gunyah.h
> create mode 100644 drivers/mailbox/gunyah-msgq.c
> create mode 100644 drivers/virt/gunyah/Kconfig
> create mode 100644 drivers/virt/gunyah/Makefile
> create mode 100644 drivers/virt/gunyah/gunyah.c
> create mode 100644 drivers/virt/gunyah/gunyah_ioeventfd.c
> create mode 100644 drivers/virt/gunyah/gunyah_irqfd.c
> create mode 100644 drivers/virt/gunyah/gunyah_platform_hooks.c
> create mode 100644 drivers/virt/gunyah/gunyah_vcpu.c
> create mode 100644 drivers/virt/gunyah/rsc_mgr.c
> create mode 100644 drivers/virt/gunyah/rsc_mgr.h
> create mode 100644 drivers/virt/gunyah/rsc_mgr_rpc.c
> create mode 100644 drivers/virt/gunyah/vm_mgr.c
> create mode 100644 drivers/virt/gunyah/vm_mgr.h
> create mode 100644 drivers/virt/gunyah/vm_mgr_mm.c
> create mode 100644 include/linux/gunyah.h
> create mode 100644 include/linux/gunyah_rsc_mgr.h
> create mode 100644 include/linux/gunyah_vm_mgr.h
> create mode 100644 include/uapi/linux/gunyah.h
> create mode 100644 samples/gunyah/.gitignore
> create mode 100644 samples/gunyah/Makefile
> create mode 100644 samples/gunyah/gunyah_vmm.c
> create mode 100644 samples/gunyah/sample_vm.dts
>
>
> base-commit: 830b3c68c1fb1e9176028d02ef86f3cf76aa2476
On 12/19/22 4:58 PM, Elliot Berman wrote:
> Export the version of Gunyah which is reported via the hyp_identify
> hypercall. Increments of the major API version indicate possibly
> backwards incompatible changes.
>
> Export the hypervisor identity so that Gunyah drivers can act according
> to the major API version.
>
> Signed-off-by: Elliot Berman <[email protected]>
> ---
> drivers/virt/Makefile | 1 +
> drivers/virt/gunyah/Makefile | 1 +
> drivers/virt/gunyah/gunyah.c | 46 ++++++++++++++++++++++++++++++++++++
> include/linux/gunyah.h | 6 +++++
> 4 files changed, 54 insertions(+)
> create mode 100644 drivers/virt/gunyah/Makefile
> create mode 100644 drivers/virt/gunyah/gunyah.c
>
> diff --git a/drivers/virt/Makefile b/drivers/virt/Makefile
> index 093674e05c40..92b7e5311548 100644
> --- a/drivers/virt/Makefile
> +++ b/drivers/virt/Makefile
> @@ -11,3 +11,4 @@ obj-$(CONFIG_NITRO_ENCLAVES) += nitro_enclaves/
> obj-$(CONFIG_ACRN_HSM) += acrn/
> obj-$(CONFIG_EFI_SECRET) += coco/efi_secret/
> obj-$(CONFIG_SEV_GUEST) += coco/sev-guest/
> +obj-y += gunyah/
> diff --git a/drivers/virt/gunyah/Makefile b/drivers/virt/gunyah/Makefile
> new file mode 100644
> index 000000000000..2ac4ee64b89d
> --- /dev/null
> +++ b/drivers/virt/gunyah/Makefile
> @@ -0,0 +1 @@
> +obj-$(CONFIG_GUNYAH) += gunyah.o
> diff --git a/drivers/virt/gunyah/gunyah.c b/drivers/virt/gunyah/gunyah.c
> new file mode 100644
> index 000000000000..c34c9046fc08
> --- /dev/null
> +++ b/drivers/virt/gunyah/gunyah.c
> @@ -0,0 +1,46 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
> + */
> +
> +#define pr_fmt(fmt) "gunyah: " fmt
> +
> +#include <linux/gunyah.h>
> +#include <linux/init.h>
> +#include <linux/module.h>
> +#include <linux/printk.h>
> +
> +struct gh_hypercall_hyp_identify_resp gunyah_api;
> +EXPORT_SYMBOL_GPL(gunyah_api);
> +
> +static const uint32_t gunyah_known_uuids[][4] = {
> + {0x19bd54bd, 0x0b37571b, 0x946f609b, 0x54539de6}, /* QC_HYP (Qualcomm's build) */
> + {0x673d5f14, 0x9265ce36, 0xa4535fdb, 0xc1d58fcd}, /* GUNYAH (open source build) */
> +};
> +
> +static int __init gunyah_init(void)
> +{
> + u32 uid[4];
> + int i;
> +
> + gh_hypercall_get_uid(uid);
> +
> + for (i = 0; i < ARRAY_SIZE(gunyah_known_uuids); i++)
> + if (!memcmp(uid, gunyah_known_uuids[i], sizeof(uid)))
> + break;
> +
> + if (i == ARRAY_SIZE(gunyah_known_uuids))
> + return -ENODEV;
> +
> + gh_hypercall_hyp_identify(&gunyah_api);
> +
> + pr_info("Running under Gunyah hypervisor %llx/v%u\n",
> + FIELD_GET(GH_API_INFO_VARIANT_MASK, gunyah_api.api_info),
> + gh_api_version());
> +
> + return 0;
> +}
> +arch_initcall(gunyah_init);
> +
> +MODULE_LICENSE("GPL");
> +MODULE_DESCRIPTION("Gunyah Hypervisor Driver");
> diff --git a/include/linux/gunyah.h b/include/linux/gunyah.h
> index 2765d2b40198..166156f69df9 100644
> --- a/include/linux/gunyah.h
> +++ b/include/linux/gunyah.h
> @@ -92,6 +92,12 @@ struct gh_hypercall_hyp_identify_resp {
> u64 api_info;
> u64 flags[3];
> };
> +extern struct gh_hypercall_hyp_identify_resp gunyah_api;
Must this global variable be exposed? Can't you hide it--as
well as its interpretation--inside "gunyah.c"?
> +
> +static inline u16 gh_api_version(void)
> +{
> + return FIELD_GET(GH_API_INFO_API_VERSION_MASK, gunyah_api.api_info);
> +}
If you don't make the above function inline, can you hide the
definition of gunyah_api?
-Alex
> void gh_hypercall_get_uid(u32 uid[4]);
> void gh_hypercall_hyp_identify(struct gh_hypercall_hyp_identify_resp *hyp_identity);
On 12/19/22 4:58 PM, Elliot Berman wrote:
> Gunyah is an open-source Type-1 hypervisor developed by Qualcomm. It
> does not depend on any lower-privileged OS/kernel code for its core
> functionality. This increases its security and can support a smaller
> trusted computing based when compared to Type-2 hypervisors.
>
> Add documentation describing the Gunyah hypervisor and the main
> components of the Gunyah hypervisor which are of interest to Linux
> virtualization development.
>
> Reviewed-by: Bagas Sanjaya <[email protected]>
> Signed-off-by: Elliot Berman <[email protected]>
> ---
> Documentation/virt/gunyah/index.rst | 114 ++++++++++++++++++++
> Documentation/virt/gunyah/message-queue.rst | 56 ++++++++++
> Documentation/virt/index.rst | 1 +
> MAINTAINERS | 7 ++
> 4 files changed, 178 insertions(+)
> create mode 100644 Documentation/virt/gunyah/index.rst
> create mode 100644 Documentation/virt/gunyah/message-queue.rst
>
> diff --git a/Documentation/virt/gunyah/index.rst b/Documentation/virt/gunyah/index.rst
> new file mode 100644
> index 000000000000..fbadbdd24da7
> --- /dev/null
> +++ b/Documentation/virt/gunyah/index.rst
> @@ -0,0 +1,114 @@
> +.. SPDX-License-Identifier: GPL-2.0
> +
> +=================
> +Gunyah Hypervisor
> +=================
> +
> +.. toctree::
> + :maxdepth: 1
> +
> + message-queue
> +
> +Gunyah is a Type-1 hypervisor which is independent of any OS kernel, and runs in
> +a higher CPU privilege level. It does not depend on any lower-privileged operating system
> +for its core functionality. This increases its security and can support a much smaller
> +trusted computing base than a Type-2 hypervisor.
> +
> +Gunyah is an open source hypervisor. The source repo is available at
> +https://github.com/quic/gunyah-hypervisor.
> +
> +Gunyah provides these following features.
> +
> +- Scheduling:
> +
> + A scheduler for virtual CPUs (vCPUs) on physical CPUs and enables time-sharing
s/and enables/enables/ (?)
> + of the CPUs. Gunyah supports two models of scheduling:
> +
> + 1. "Behind the back" scheduling in which Gunyah hypervisor schedules vCPUS on its own.
> + 2. "Proxy" scheduling in which a delegated VM can donate part of one of its vCPU slice
> + to another VM's vCPU via a hypercall.
> +
> +- Memory Management:
> +
> + APIs handling memory, abstracted as objects, limiting direct use of physical
> + addresses. Memory ownership and usage tracking of all memory under its control.
> + Memory partitioning between VMs is a fundamental security feature.
> +
> +- Interrupt Virtualization:
> +
> + Uses CPU hardware interrupt virtualization capabilities. Interrupts are handled
> + in the hypervisor and routed to the assigned VM.
> +
> +- Inter-VM Communication:
> +
> + There are several different mechanisms provided for communicating between VMs.
> +
> +- Virtual platform:
> +
> + Architectural devices such as interrupt controllers and CPU timers are directly provided
> + by the hypervisor as well as core virtual platform devices and system APIs such as ARM PSCI.
> +
> +- Device Virtualization:
> +
> + Para-virtualization of devices is supported using inter-VM communication.
> +
> +Architectures supported
> +=======================
> +AArch64 with a GIC
> +
> +Resources and Capabilities
> +==========================
> +
> +Some services or resources provided by the Gunyah hypervisor are described to a virtual machine by
> +capability IDs. For instance, inter-VM communication is performed with doorbells and message queues.
> +Gunyah allows access to manipulate that doorbell via the capability ID. These devices are described
s/devices/resources/
> +in Linux as a struct gunyah_resource.
> +
> +High level management of these resources is performed by the resource manager VM. RM informs a
s/resource manager VM/resource manager VM (RM)/
> +guest VM about resources it can access through either the device tree or via guest-initiated RPC.
> +
> +For each virtual machine, Gunyah maintains a table of resources which can be accessed by that VM.
> +An entry in this table is called a "capability" and VMs can only access resources via this
> +capability table. Hence, virtual Gunyah devices are referenced by a "capability IDs" and not a
s/devices/resources/
s/and not a/and not/
> +"resource IDs". A VM can have multiple capability IDs mapping to the same resource. If 2 VMs have
> +access to the same resource, they may not be using the same capability ID to access that resource
Does "may not be using the same capability ID" mean they "shall not",
or "are permitted not to"?
> +since the tables are independent per VM.
> +
> +Resource Manager
> +================
> +
> +The resource manager (RM) is a privileged application VM supporting the Gunyah Hypervisor.
> +It provides policy enforcement aspects of the virtualization system. The resource manager can
> +be treated as an extension of the Hypervisor but is separated to its own partition to ensure
> +that the hypervisor layer itself remains small and secure and to maintain a separation of policy
> +and mechanism in the platform. On arm64, RM runs at NS-EL1 similar to other virtual machines.
This only runs on arm64, right? Maybe "RM runs at arm64 NS-EL1..."
> +
> +Communication with the resource manager from each guest VM happens with message-queue.rst. Details
Is "message-queue.rst" supposed to be a reference to that other document?
> +about the specific messages can be found in drivers/virt/gunyah/rsc_mgr.c
> +
> +::
> +
> + +-------+ +--------+ +--------+
> + | RM | | VM_A | | VM_B |
> + +-.-.-.-+ +---.----+ +---.----+
> + | | | |
> + +-.-.-----------.------------.----+
> + | | \==========/ | |
> + | \========================/ |
> + | Gunyah |
> + +---------------------------------+
> +
> +The source for the resource manager is available at https://github.com/quic/gunyah-resource-manager.
> +
> +The resource manager provides the following features:
> +
> +- VM lifecycle management: allocating a VM, starting VMs, destruction of VMs
> +- VM access control policy, including memory sharing and lending
> +- Interrupt routing configuration
> +- Forwarding of system-level events (e.g. VM shutdown) to owner VM
> +
> +When booting a virtual machine which uses a devicetree, resource manager overlays a
"When booting Linux in a virtual machine..." ?
> +/hypervisor node. This node can let Linux know it is running as a Gunyah guest VM,
> +how to communicate with resource manager, and basic description and capabilities of
> +this VM. See Documentation/devicetree/bindings/firmware/gunyah-hypervisor.yaml for a description
> +of this node.
> diff --git a/Documentation/virt/gunyah/message-queue.rst b/Documentation/virt/gunyah/message-queue.rst
> new file mode 100644
> index 000000000000..be4ab289236a
> --- /dev/null
> +++ b/Documentation/virt/gunyah/message-queue.rst
> @@ -0,0 +1,56 @@
> +.. SPDX-License-Identifier: GPL-2.0
> +
> +Message Queues
> +==============
> +Message queue is a simple low-capacity IPC channel between two VMs. It is
> +intended for sending small control and configuration messages. Each message
> +queue object is unidirectional, so a full-duplex IPC channel requires a pair of
> +objects.
The wording here makes it seem like "message queue" might be
distinct from a "message queue object" but I think they're the
same thing (right?).
> +
> +Messages can be up to 240 bytes in length. Longer messages require a further
> +protocol on top of the message queue messages themselves. For instance, communication
> +with the resource manager adds a header field for sending longer messages via multiple
> +message fragments.
> +
> +The diagram below shows how message queue works. A typical configuration involves
> +2 message queues. Message queue 1 allows VM_A to send messages to VM_B. Message
> +queue 2 allows VM_B to send messages to VM_A.
> +
> +1. VM_A sends a message of up to 240 bytes in length. It raises a hypercall
> + with the message to inform the hypervisor to add the message to
> + message queue 1's queue.
> +
> +2. Gunyah raises the corresponding interrupt for VM_B when any of these happens:
> +
> + a. gh_msgq_send has PUSH flag. Queue is immediately flushed. This is the typical case.
> + b. Explicility with gh_msgq_push command from VM_A.
> + c. Message queue has reached a threshold depth.
> +
> +3. VM_B calls gh_msgq_recv and Gunyah copies message to requested buffer.
So VM_B *responds* to the Rx vIRQ by calling gh_msgq_recv() and
supplying a buffer in which Gunyah copies the message content?
I guess my point is, can VM_B post a receive buffer in advance of
a message Rx vIRQ being delivered?
You don't describe what a Tx vIRQ does. When does it fire?
-Alex
> +For VM_B to send a message to VM_A, the process is identical, except that hypercalls
> +reference message queue 2's capability ID. Each message queue has its own independent
> +vIRQ: two TX message queues will have two vIRQs (and two capability IDs).
> +
> +::
> +
> + +---------------+ +-----------------+ +---------------+
> + | VM_A | |Gunyah hypervisor| | VM_B |
> + | | | | | |
> + | | | | | |
> + | | Tx | | | |
> + | |-------->| | Rx vIRQ | |
> + |gh_msgq_send() | Tx vIRQ |Message queue 1 |-------->|gh_msgq_recv() |
> + | |<------- | | | |
> + | | | | | |
> + | Message Queue | | | | Message Queue |
> + | driver | | | | driver |
> + | | | | | |
> + | | | | | |
> + | | | | Tx | |
> + | | Rx vIRQ | |<--------| |
> + |gh_msgq_recv() |<--------|Message queue 2 | Tx vIRQ |gh_msgq_send() |
> + | | | |-------->| |
> + | | | | | |
> + | | | | | |
> + +---------------+ +-----------------+ +---------------+
> diff --git a/Documentation/virt/index.rst b/Documentation/virt/index.rst
> index 2f1cffa87b1b..418d540f5484 100644
> --- a/Documentation/virt/index.rst
> +++ b/Documentation/virt/index.rst
> @@ -15,6 +15,7 @@ Linux Virtualization Support
> acrn/index
> coco/sev-guest
> hyperv/index
> + gunyah/index
>
> .. only:: html and subproject
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 886d3f69ee64..1dd8f58d6e01 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -8934,6 +8934,13 @@ L: [email protected]
> S: Maintained
> F: block/partitions/efi.*
>
> +GUNYAH HYPERVISOR DRIVER
> +M: Elliot Berman <[email protected]>
> +M: Murali Nalajala <[email protected]>
> +L: [email protected]
> +S: Supported
> +F: Documentation/virt/gunyah/
> +
> HABANALABS PCI DRIVER
> M: Oded Gabbay <[email protected]>
> S: Supported
On 1/9/2023 1:34 PM, Alex Elder wrote:
> On 12/19/22 4:58 PM, Elliot Berman wrote:
>> Gunyah is an open-source Type-1 hypervisor developed by Qualcomm. It
>> does not depend on any lower-privileged OS/kernel code for its core
>> functionality. This increases its security and can support a smaller
>> trusted computing based when compared to Type-2 hypervisors.
>>
>> Add documentation describing the Gunyah hypervisor and the main
>> components of the Gunyah hypervisor which are of interest to Linux
>> virtualization development.
>>
>> Reviewed-by: Bagas Sanjaya <[email protected]>
>> Signed-off-by: Elliot Berman <[email protected]>
>> ---
>> Documentation/virt/gunyah/index.rst | 114 ++++++++++++++++++++
>> Documentation/virt/gunyah/message-queue.rst | 56 ++++++++++
>> Documentation/virt/index.rst | 1 +
>> MAINTAINERS | 7 ++
>> 4 files changed, 178 insertions(+)
>> create mode 100644 Documentation/virt/gunyah/index.rst
>> create mode 100644 Documentation/virt/gunyah/message-queue.rst
>>
>> diff --git a/Documentation/virt/gunyah/index.rst
>> b/Documentation/virt/gunyah/index.rst
>> new file mode 100644
>> index 000000000000..fbadbdd24da7
>> --- /dev/null
>> +++ b/Documentation/virt/gunyah/index.rst
>> @@ -0,0 +1,114 @@
>> +.. SPDX-License-Identifier: GPL-2.0
>> +
>> +=================
>> +Gunyah Hypervisor
>> +=================
>> +
>> +.. toctree::
>> + :maxdepth: 1
>> +
>> + message-queue
>> +
>> +Gunyah is a Type-1 hypervisor which is independent of any OS kernel,
>> and runs in
>> +a higher CPU privilege level. It does not depend on any
>> lower-privileged operating system
>> +for its core functionality. This increases its security and can
>> support a much smaller
>> +trusted computing base than a Type-2 hypervisor.
>> +
>> +Gunyah is an open source hypervisor. The source repo is available at
>> +https://github.com/quic/gunyah-hypervisor.
>> +
>> +Gunyah provides these following features.
>> +
>> +- Scheduling:
>> +
>> + A scheduler for virtual CPUs (vCPUs) on physical CPUs and enables
>> time-sharing
>
> s/and enables/enables/ (?)
>
>> + of the CPUs. Gunyah supports two models of scheduling:
>> +
>> + 1. "Behind the back" scheduling in which Gunyah hypervisor
>> schedules vCPUS on its own.
>> + 2. "Proxy" scheduling in which a delegated VM can donate part of
>> one of its vCPU slice
>> + to another VM's vCPU via a hypercall.
>> +
>> +- Memory Management:
>> +
>> + APIs handling memory, abstracted as objects, limiting direct useof
>> physical
>> + addresses. Memory ownership and usage tracking of all memory under
>> its control.
>> + Memory partitioning between VMs is a fundamental security feature.
>> +
>> +- Interrupt Virtualization:
>> +
>> + Uses CPU hardware interrupt virtualization capabilities. Interrupts
>> are handled
>> + in the hypervisor and routed to the assigned VM.
>> +
>> +- Inter-VM Communication:
>> +
>> + There are several different mechanisms provided for communicating
>> between VMs.
>> +
>> +- Virtual platform:
>> +
>> + Architectural devices such as interrupt controllers and CPU timers
>> are directly provided
>> + by the hypervisor as well as core virtual platform devices and
>> system APIs such as ARM PSCI.
>> +
>> +- Device Virtualization:
>> +
>> + Para-virtualization of devices is supported using inter-VM
>> communication.
>> +
>> +Architectures supported
>> +=======================
>> +AArch64 with a GIC
>> +
>> +Resources and Capabilities
>> +==========================
>> +
>> +Some services or resources provided by the Gunyah hypervisor are
>> described to a virtual machine by
>> +capability IDs. For instance, inter-VM communication is performed
>> with doorbells and message queues.
>> +Gunyah allows access to manipulate that doorbell via the capability
>> ID. These devices are described
>
> s/devices/resources/
>
>> +in Linux as a struct gunyah_resource.
>> +
>> +High level management of these resources is performed by the resource
>> manager VM. RM informs a
>
> s/resource manager VM/resource manager VM (RM)/
>
>> +guest VM about resources it can access through either the device tree
>> or via guest-initiated RPC.
>> +
>> +For each virtual machine, Gunyah maintains a table of resources which
>> can be accessed by that VM.
>> +An entry in this table is called a "capability" and VMs can only
>> access resources via this
>> +capability table. Hence, virtual Gunyah devices are referenced by a
>> "capability IDs" and not a
>
> s/devices/resources/
> s/and not a/and not/
>
>> +"resource IDs". A VM can have multiple capability IDs mapping to the
>> same resource. If 2 VMs have
>> +access to the same resource, they may not be using the same
>> capability ID to access that resource
>
> Does "may not be using the same capability ID" mean they "shall not",
> or "are permitted not to"?
>
"are permitted not to". I'll say "might not" instead of "may not".
>> +since the tables are independent per VM.
>> +
>> +Resource Manager
>> +================
>> +
>> +The resource manager (RM) is a privileged application VM supporting
>> the Gunyah Hypervisor.
>> +It provides policy enforcement aspects of the virtualization system.
>> The resource manager can
>> +be treated as an extension of the Hypervisor but is separated to its
>> own partition to ensure
>> +that the hypervisor layer itself remains small and secure and to
>> maintain a separation of policy
>> +and mechanism in the platform. On arm64, RM runs at NS-EL1 similar to
>> other virtual machines.
>
> This only runs on arm64, right? Maybe "RM runs at arm64 NS-EL1..."
>> +
>> +Communication with the resource manager from each guest VM happens
>> with message-queue.rst. Details
>
> Is "message-queue.rst" supposed to be a reference to that other document?
>
Yes. Sphinx will generate hyperlink to that document. It's in the same
directory as this document.
>> +about the specific messages can be found in
>> drivers/virt/gunyah/rsc_mgr.c
>> +
>> +::
>> +
>> + +-------+ +--------+ +--------+
>> + | RM | | VM_A | | VM_B |
>> + +-.-.-.-+ +---.----+ +---.----+
>> + | | | |
>> + +-.-.-----------.------------.----+
>> + | | \==========/ | |
>> + | \========================/ |
>> + | Gunyah |
>> + +---------------------------------+
>> +
>> +The source for the resource manager is available at
>> https://github.com/quic/gunyah-resource-manager.
>> +
>> +The resource manager provides the following features:
>> +
>> +- VM lifecycle management: allocating a VM, starting VMs, destruction
>> of VMs
>> +- VM access control policy, including memory sharing and lending
>> +- Interrupt routing configuration
>> +- Forwarding of system-level events (e.g. VM shutdown) to owner VM
>> +
>> +When booting a virtual machine which uses a devicetree, resource
>> manager overlays a
>
> "When booting Linux in a virtual machine..." ?
>
>> +/hypervisor node. This node can let Linux know it is running as a
>> Gunyah guest VM,
>> +how to communicate with resource manager, and basic description and
>> capabilities of
>> +this VM. See
>> Documentation/devicetree/bindings/firmware/gunyah-hypervisor.yaml for
>> a description
>> +of this node.
>> diff --git a/Documentation/virt/gunyah/message-queue.rst
>> b/Documentation/virt/gunyah/message-queue.rst
>> new file mode 100644
>> index 000000000000..be4ab289236a
>> --- /dev/null
>> +++ b/Documentation/virt/gunyah/message-queue.rst
>> @@ -0,0 +1,56 @@
>> +.. SPDX-License-Identifier: GPL-2.0
>> +
>> +Message Queues
>> +==============
>> +Message queue is a simple low-capacity IPC channel between two VMs.
>> It is
>> +intended for sending small control and configuration messages. Each
>> message
>> +queue object is unidirectional, so a full-duplex IPC channel requires
>> a pair of
>> +objects.
>
> The wording here makes it seem like "message queue" might be
> distinct from a "message queue object" but I think they're the
> same thing (right?).
>
Yes, they are the same. Removed the "object" to make it more concise.
>> +
>> +Messages can be up to 240 bytes in length. Longer messages require a
>> further
>> +protocol on top of the message queue messages themselves. For
>> instance, communication
>> +with the resource manager adds a header field for sending longer
>> messages via multiple
>> +message fragments.
>> +
>> +The diagram below shows how message queue works. A typical
>> configuration involves
>> +2 message queues. Message queue 1 allows VM_A to send messages to
>> VM_B. Message
>> +queue 2 allows VM_B to send messages to VM_A.
>> +
>> +1. VM_A sends a message of up to 240 bytes in length. It raises a
>> hypercall
>> + with the message to inform the hypervisor to add the message to
>> + message queue 1's queue.
>> +
>> +2. Gunyah raises the corresponding interrupt for VM_B when any of
>> these happens:
(edited above line to explicitly call out this is the Rx vIRQ)
>> +
>> + a. gh_msgq_send has PUSH flag. Queue is immediately flushed. This
>> is the typical case.
>> + b. Explicility with gh_msgq_push command from VM_A.
>> + c. Message queue has reached a threshold depth.
>> +
>> +3. VM_B calls gh_msgq_recv and Gunyah copies message to requested
>> buffer.
>
> So VM_B *responds* to the Rx vIRQ by calling gh_msgq_recv() and
> supplying a buffer in which Gunyah copies the message content?
>
> I guess my point is, can VM_B post a receive buffer in advance of
> a message Rx vIRQ being delivered?
Yes, that is possible.
>
> You don't describe what a Tx vIRQ does. When does it fire?
Good catch! I've added a 4th point:
4. Gunyah buffers messages in the queue. If the queue became full when
VM_A added a message,
the return values for gh_msgq_send() include a flag that indicates
the queue is full.
Once VM_B receives the message and, thus, there is space in the
queue, Gunyah
will raise the Tx vIRQ on VM_A to indicate it can continue sending
messages.
Thanks,
Elliot
On 1/9/2023 1:34 PM, Alex Elder wrote:
> On 12/19/22 4:58 PM, Elliot Berman wrote:
>> Gunyah is a Type-1 hypervisor independent of any
>> high-level OS kernel, and runs in a higher CPU privilege level. It does
>> not depend on any lower-privileged OS kernel/code for its core
>> functionality. This increases its security and can support a much smaller
>> trusted computing base than a Type-2 hypervisor.
>>
>> Gunyah is an open source hypervisor. The source repo is available at
>> https://github.com/quic/gunyah-hypervisor.
>
> Can you provide any history about the hypervisor code itself?
> Was it publicly reviewed? Has it been reviewed by anyone in
> the Linux kernel community, who might have some useful input
> on it?
>
This is Gunyah's first interaction with wider public community. Gunyah
has been deployed in devices for past few generation of Qualcomm
Technolgoies, Inc. (mobile) chipsets.
Thanks,
Elliot
On 1/9/2023 1:34 PM, Alex Elder wrote:
> On 12/19/22 4:58 PM, Elliot Berman wrote:
>> Gunyah message queues are a unidirectional inter-VM pipe for messages up
>
> s/Gunyah message queues are/A Gunyah message queue is/
>
>> to 1024 bytes. This driver supports pairing a receiver message queue and
>> a transmitter message queue to expose a single mailbox channel.
>>
>> Signed-off-by: Elliot Berman <[email protected]>
>> ---
>> Documentation/virt/gunyah/message-queue.rst | 8 +
>> MAINTAINERS | 1 +
>> drivers/mailbox/Kconfig | 10 +
>> drivers/mailbox/Makefile | 2 +
>> drivers/mailbox/gunyah-msgq.c | 229 ++++++++++++++++++++
>> include/linux/gunyah.h | 61 +++++-
>> 6 files changed, 309 insertions(+), 2 deletions(-)
>> create mode 100644 drivers/mailbox/gunyah-msgq.c
>>
>> diff --git a/Documentation/virt/gunyah/message-queue.rst
>> b/Documentation/virt/gunyah/message-queue.rst
>> index be4ab289236a..9245dbe6dd8a 100644
>> --- a/Documentation/virt/gunyah/message-queue.rst
>> +++ b/Documentation/virt/gunyah/message-queue.rst
>> @@ -54,3 +54,11 @@ vIRQ: two TX message queues will have two vIRQs
>> (and two capability IDs).
>> | | | |
>> | |
>> | | | |
>> | |
>> +---------------+ +-----------------+
>> +---------------+
>> +
>> +Gunyah message queues are exposed as mailboxes. To create the
>> mailbox, create
>> +a mbox_client and call `gh_msgq_init`. On receipt of the RX_READY
>> interrupt,
>> +all messages in the RX message queue are read and pushed via the
>> `rx_callback`
>> +of the registered mbox_client.
>> +
>> +.. kernel-doc:: drivers/mailbox/gunyah-msgq.c
>> + :identifiers: gh_msgq_init
>> diff --git a/MAINTAINERS b/MAINTAINERS
>> index 36698df6b0e5..667480bfd387 100644
>> --- a/MAINTAINERS
>> +++ b/MAINTAINERS
>> @@ -8942,6 +8942,7 @@ S: Supported
>> F: Documentation/devicetree/bindings/firmware/gunyah-hypervisor.yaml
>> F: Documentation/virt/gunyah/
>> F: arch/arm64/gunyah/
>> +F: drivers/mailbox/gunyah-msgq.c
>> F: drivers/virt/gunyah/
>> F: include/linux/gunyah.h
>> diff --git a/drivers/mailbox/Kconfig b/drivers/mailbox/Kconfig
>> index 05d6fae800e3..baf9451c5f04 100644
>> --- a/drivers/mailbox/Kconfig
>> +++ b/drivers/mailbox/Kconfig
>> @@ -41,6 +41,16 @@ config IMX_MBOX
>> help
>> Mailbox implementation for i.MX Messaging Unit (MU).
>> +config GUNYAH_MESSAGE_QUEUES
>
> Is Gunyah useful without message queues? Are Gunyah message
> queues useful without the rest of Gunyah? Are there other
> possible message queue implementations? My point is that
> maybe this isn't a necessary kernel config option.
>
I can drop this.
>> + tristate "Gunyah Message Queue Mailbox"
>> + depends on GUNYAH
>> + help
>> + Mailbox implementation for Gunyah Message Queues. Gunyah
>> message queues
>> + are an IPC mechanism to pass short messages between virtual
>> machines
>> + running under the Gunyah hypervisor.
>> +
>> + Say Y here if you run Linux as a Gunyah virtual machine.
>> +
>> config PLATFORM_MHU
>> tristate "Platform MHU Mailbox"
>> depends on OF
>> diff --git a/drivers/mailbox/Makefile b/drivers/mailbox/Makefile
>> index fc9376117111..5f929bb55e9a 100644
>> --- a/drivers/mailbox/Makefile
>> +++ b/drivers/mailbox/Makefile
>> @@ -55,6 +55,8 @@ obj-$(CONFIG_MTK_CMDQ_MBOX) += mtk-cmdq-mailbox.o
>> obj-$(CONFIG_ZYNQMP_IPI_MBOX) += zynqmp-ipi-mailbox.o
>> +obj-$(CONFIG_GUNYAH) += gunyah-msgq.o
>
> Assuming you keep the Kconfig option, should this be
> referring to CONFIG_GUNYAH_MESSAGE_QUEUES?
>
>> +
>> obj-$(CONFIG_SUN6I_MSGBOX) += sun6i-msgbox.o
>> obj-$(CONFIG_SPRD_MBOX) += sprd-mailbox.o
>> diff --git a/drivers/mailbox/gunyah-msgq.c
>> b/drivers/mailbox/gunyah-msgq.c
>> new file mode 100644
>> index 000000000000..d5a324e85428
>> --- /dev/null
>> +++ b/drivers/mailbox/gunyah-msgq.c
>> @@ -0,0 +1,229 @@
>> +// SPDX-License-Identifier: GPL-2.0-only
>> +/*
>> + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights
>> reserved.
>> + */
>> +
>> +#include <linux/mailbox_controller.h>
>> +#include <linux/module.h>
>> +#include <linux/interrupt.h>
>> +#include <linux/gunyah.h>
>> +#include <linux/printk.h>
>> +#include <linux/init.h>
>> +#include <linux/slab.h>
>> +#include <linux/wait.h>
>> +
>> +#define mbox_chan_to_msgq(chan) (container_of(chan->mbox, struct
>> gh_msgq, mbox))
>> +
>> +static inline bool gh_msgq_has_tx(struct gh_msgq *msgq)
>> +{
>> + return msgq->tx_ghrsc->type == GUNYAH_RESOURCE_TYPE_MSGQ_TX;
>
> I think this was pointed out elsewhere; I think msgq->tx_ghrsc
> can be null. Same for msgq->rx_ghrsc below.
>
>> +}
>> +
>> +static inline bool gh_msgq_has_rx(struct gh_msgq *msgq)
>> +{
>> + return msgq->rx_ghrsc->type == GUNYAH_RESOURCE_TYPE_MSGQ_RX;
>
> Is there any chance that a message queue would be created whose
> type was anything other than MSGQ_RX? It's possible that there
> is some generic resource code that needs this type field, but
> in this case I don't see why it's important.
>
> And... If that's the case, I think it's perhaps clearer to
> just replace calls to gs_msgq_has_rx(msgq) with:
>
> if (msgq->rx_ghrsc)
> /* there is an RX message queue */
>
Yep, that's what I've done for v9.
>> +}
>> +
>> +static irqreturn_t gh_msgq_rx_irq_handler(int irq, void *data)
>> +{
>> + struct gh_msgq *msgq = data;
>> + struct gh_msgq_rx_data rx_data;
>> + unsigned long gh_err;
>> + ssize_t ret;
>> + bool ready = false;
>> +
>> + do {
>
> Maybe: bool ready = true;
>
> while (ready) {
>> + gh_err = gh_hypercall_msgq_recv(msgq->rx_ghrsc->capid,
>> + (uintptr_t)&rx_data.data, sizeof(rx_data.data),
>> + &rx_data.length, &ready);
>> + if (gh_err == GH_ERROR_OK) {
>> + mbox_chan_received_data(gh_msgq_chan(msgq), &rx_data);
>
> Maybe continue;
> }
>
> if (gh_err != GH_ERROR_MSGQUEUE_EMPTY)
> pr_warn(...);
>
> break;
>
>> + } else if (GH_ERROR_MSGQUEUE_EMPTY) {
>> + break;
>> + } else {
>> + pr_warn("Failed to receive data from msgq for %s: %ld\n",
>
> s/%ld/%zd/
>
>> + msgq->mbox.dev ? dev_name(msgq->mbox.dev) : "", ret);
>> + break;
>> + }
>> + } while (ready);
>> +
>> + return IRQ_HANDLED;
>> +}
>> +
>> +static irqreturn_t gh_msgq_tx_irq_handler(int irq, void *data)
>> +{
>> + struct gh_msgq *msgq = data;
>> +
>> + mbox_chan_txdone(gh_msgq_chan(msgq), 0);
>> +
>> + return IRQ_HANDLED;
>> +}
>> +
>> +static void gh_msgq_txdone_tasklet(struct tasklet_struct *tasklet)
>> +{
>> + struct gh_msgq *msgq = container_of(tasklet, struct gh_msgq,
>> txdone_tasklet);
>> +
>> + mbox_chan_txdone(gh_msgq_chan(msgq), msgq->last_status);
>> +}
>> +
>> +static int gh_msgq_send_data(struct mbox_chan *chan, void *data)
>> +{
>> + struct gh_msgq *msgq = mbox_chan_to_msgq(chan);
>> + struct gh_msgq_tx_data *msgq_data = data;
>> + u64 tx_flags = 0;
>> + unsigned long ret;
>> + bool ready;
>> +
>> + if (msgq_data->push)
>> + tx_flags |= GH_HYPERCALL_MSGQ_TX_FLAGS_PUSH;
>> +
>
> I probably missed something, but where is gh_hypercall_msgq_send()
> defined? I'm interested in what the ready flag is used for.
> (Can the return value encode that?)
>
It's in arch/arm64/gunyah/gunyah_hypercall.c. The return values of
encode whether more data can be received (ready flag).
>> + ret = gh_hypercall_msgq_send(msgq->tx_ghrsc->capid,
>> msgq_data->length,
>> + (uintptr_t)msgq_data->data, tx_flags, &ready);
>> +
>> + /**
>> + * unlikely because Linux tracks state of msgq and should not try to
>> + * send message when msgq is full.
>> + */
>
> Is it just unlikely, or is it impossible?
>
This would require multiple mailbox controllers interacting with the
same message queue.
The only way I can think this is possible is if the Gunyah drivers are
unloaded when the message queue is full; drivers are then re-loaded
before the receiver processes the messages. The initial internal state
of the message queue controller assumes that there is space in the
message queue. We would get a Tx vIRQ once space becomes available and
the message would then be attempted to sent again. Since there's a safe
flow to recover from a inconsistent internal state and it's very
unlikely to start in that state, I don't think we need to add calls to
check if the message queue is full during initialization.
>> + if (unlikely(ret == GH_ERROR_MSGQUEUE_FULL))
>> + return -EAGAIN;
>> +
>> + /**
>> + * Propagate all other errors to client. If we return error to
>> mailbox
>> + * framework, then no other messages can be sent and nobody will
>> know
>> + * to retry this message.
>> + */
>> + msgq->last_status = gh_remap_error(ret);
>> +
>> + /**
>> + * This message was successfully sent, but message queue isn't
>> ready to
>> + * receive more messages because it's now full.Mailbox framework
>> + * requires that we only report that message was transmitted only
>> when
>
> s/only // (for one of them)
>
>> + * we're ready to transmit another message. We'll get that in the
>> form
>> + * of tx IRQ once the other side starts to drain the msgq.
>> + */
>> + if (ret == GH_ERROR_OK && !ready)
>> + return 0;
>> +
>> + /**
>> + * We can send more messages. Mailbox frameworkrequires that tx
>> done
>> + * happens asynchronously to sending the message. Gunyah message
>> queues
>> + * tell us right away on the hypercall return whether we can send
>> more
>> + * messages. To work around this, defer the txdone to a tasklet.
>> + */
>> + tasklet_schedule(&msgq->txdone_tasklet);
>> +
>> + return 0;
>> +}
>> +
>> +struct mbox_chan_ops gh_msgq_ops = {
>> + .send_data = gh_msgq_send_data,
>> +};
>> +
>> +/**
>> + * gh_msgq_init() - Initialize a Gunyah message queue with an
>> mbox_client
>> + * @parent: optional, device parent used for the mailbox controller
>> + * @msgq: Pointer to the gh_msgq to initialize
>> + * @cl: A mailbox client to bind to the mailbox channel that the
>> message queue creates
>> + * @tx_ghrsc: optional, the transmission side of the message queue
>> + * @rx_ghrsc: optional, the receiving side of the message queue
>> + *
>> + * At least one of tx_ghrsc and rx_ghrsc should be not NULL. Most
>> message queue use cases come with
>> + * a pair of message queues to facilitiate bidirectional
>> communication. When tx_ghrsc is set,
>
> s/facilitiate/facilitate/
>
>> + * the client can send messages with
>> mbox_send_message(gh_msgq_chan(msgq), msg). When rx_ghrsc
>> + * is set, the mbox_client should register an .rx_callback() and the
>> message queue driver will
>> + * push all available messages upon receiving the RX ready interrupt.
>> The messages should be
>> + * consumed or copied by the client right away as the gh_msgq_rx_data
>> will be replaced/destroyed
>> + * after the callback.
>> + *
>> + * Returns - 0 on success, negative otherwise
>> + */
>> +int gh_msgq_init(struct device *parent, struct gh_msgq *msgq, struct
>> mbox_client *cl,
>> + struct gunyah_resource *tx_ghrsc, struct gunyah_resource
>> *rx_ghrsc)
>> +{
>> + int ret;
>> +
>> + /* Must have at least a tx_ghrsc or rx_ghrsc and that they are
>> the right device types */
>> + if ((!tx_ghrsc && !rx_ghrsc) ||
>> + (tx_ghrsc && tx_ghrsc->type != GUNYAH_RESOURCE_TYPE_MSGQ_TX) ||
>> + (rx_ghrsc && rx_ghrsc->type != GUNYAH_RESOURCE_TYPE_MSGQ_RX))
>
> The internal parenthesis pairs above aren't really required.
>
>
I think it's cleaner/easier to read this way.
>> + return -EINVAL;
>> +
>> + msgq->tx_ghrsc = tx_ghrsc;
>> + msgq->rx_ghrsc = rx_ghrsc;
>> +
>> + msgq->mbox.dev = parent;
>> + msgq->mbox.ops = &gh_msgq_ops;
>> + msgq->mbox.num_chans = 1;
>> + msgq->mbox.chans = kcalloc(msgq->mbox.num_chans,
>> sizeof(*msgq->mbox.chans), GFP_KERNEL);
>> + if (!msgq->mbox.chans)
>> + return -ENOMEM;
>> + msgq->mbox.txdone_irq = true;
>> +
>> + if (gh_msgq_has_tx(msgq)) {
>
> Maybe if (tx_ghrsc) {
>
>> + ret = request_irq(msgq->tx_ghrsc->irq,
>> gh_msgq_tx_irq_handler, 0, "gh_msgq_tx",
>> + msgq);
>> + if (ret)
>> + gotoerr_chans;
>> + }
>> +
>> + if (gh_msgq_has_rx(msgq)) {
>
> Maybe if (rx_ghrsc) {
>
>> + ret = request_threaded_irq(msgq->rx_ghrsc->irq, NULL,
>> gh_msgq_rx_irq_handler,
>> + IRQF_ONESHOT, "gh_msgq_rx", msgq);
>> + if (ret)
>> + gotoerr_tx_irq;
>> + }
>> +
>> + tasklet_setup(&msgq->txdone_tasklet, gh_msgq_txdone_tasklet);
>> +
>> + ret = mbox_controller_register(&msgq->mbox);
>> + if (ret)
>> + goto err_rx_irq;
>> +
>> + ret = mbox_bind_client(gh_msgq_chan(msgq), cl);
>> + if (ret)
>> + goto err_mbox;
>> +
>> + return 0;
>> +err_mbox:
>> + mbox_controller_unregister(&msgq->mbox);
>> +err_rx_irq:
>> + if (gh_msgq_has_rx(msgq))
>
> if (rx_ghrsc)
>
>> + free_irq(msgq->rx_ghrsc->irq, msgq);
>> +err_tx_irq:
>> + if (gh_msgq_has_tx(msgq))
>
> if (tx_ghrsc)
>
Applied for the suggestions above.
>> + free_irq(msgq->tx_ghrsc->irq, msgq);
>> +err_chans:
>> + kfree(msgq->mbox.chans);
>> + return ret;
>> +}
>> +EXPORT_SYMBOL_GPL(gh_msgq_init);
>> +
>> +void gh_msgq_remove(struct gh_msgq *msgq)
>> +{
>> + mbox_controller_unregister(&msgq->mbox);
>> +
>> + if (gh_msgq_has_rx(msgq))
>> + free_irq(msgq->rx_ghrsc->irq, msgq);
>> +
>> + if (gh_msgq_has_tx(msgq))
>> + free_irq(msgq->tx_ghrsc->irq, msgq);
>> +
>> + kfree(msgq->mbox.chans);
>> +}
>> +EXPORT_SYMBOL_GPL(gh_msgq_remove);
>> +
>> +
>> +static int __init gh_msgq_init_module(void)
>> +{
>> + if (gh_api_version() != GUNYAH_API_V1) {
>> + pr_warn("Unrecognized gunyahversion: %u. Currently
>> supported: %d\n",
>> + gh_api_version(), GUNYAH_API_V1);
>> + return -ENODEV;
>> + }
>> +
>> + return 0;
>> +}
>> +module_init(gh_msgq_init_module);
>> +
>> +MODULE_LICENSE("GPL");
>> +MODULE_DESCRIPTION("Gunyah Message Queue Driver");
>> diff --git a/include/linux/gunyah.h b/include/linux/gunyah.h
>> index c863cac4a3cf..e317d7ac938f 100644
>> --- a/include/linux/gunyah.h
>> +++ b/include/linux/gunyah.h
>> @@ -7,10 +7,67 @@
>> #define _GUNYAH_H
>> #include <linux/bitfield.h>
>> -#include <linux/types.h>
>> #include <linux/errno.h>
>> +#include <linux/interrupt.h>
>> +#include <linux/mailbox_controller.h>
>> +#include <linux/mailbox_client.h>
>> +#include <linux/types.h>
>> +
>> +/* Follows resource manager's resource types for VM_GET_HYP_RESOURCES */
>> +enum gunyah_resource_type {
>> + GUNYAH_RESOURCE_TYPE_BELL_TX = 0,
>> + GUNYAH_RESOURCE_TYPE_BELL_RX = 1,
>> + GUNYAH_RESOURCE_TYPE_MSGQ_TX = 2,
>> + GUNYAH_RESOURCE_TYPE_MSGQ_RX = 3,
>> + GUNYAH_RESOURCE_TYPE_VCPU = 4,
>> +};
>> +
>> +struct gunyah_resource {
>> + enum gunyah_resource_type type;
>> + u64 capid;
>> + int irq;
>> +};
>> +
>> +/**
>> + * Gunyah Message Queues
>> + */
>> +
>> +#define GH_MSGQ_MAX_MSG_SIZE 240
>> +
>> +struct gh_msgq_tx_data {
>> + size_t length;
>> + bool push;
>> + char data[];
>> +};
>> +
>> +struct gh_msgq_rx_data {
>> + size_t length;
>> + char data[GH_MSGQ_MAX_MSG_SIZE];
>> +};
>> +
>> +struct gh_msgq {
>> + struct gunyah_resource *tx_ghrsc;
>> + struct gunyah_resource *rx_ghrsc;
>> +
>> + /* msgq private */
>> + int last_status;
>
> Maybe note that last_status is an errno (and not GH_ERROR_*)
>
Done, renamed to "last_ret".
> -Alex
>
>> + struct mbox_controller mbox;
>> + struct tasklet_struct txdone_tasklet;
>> +};
>> +
>> +
>> +int gh_msgq_init(struct device *parent, struct gh_msgq *msgq, struct
>> mbox_client *cl,
>> + struct gunyah_resource *tx_ghrsc, struct gunyah_resource
>> *rx_ghrsc);
>> +void gh_msgq_remove(struct gh_msgq *msgq);
>> +
>> +static inline struct mbox_chan *gh_msgq_chan(struct gh_msgq *msgq)
>> +{
>> + return &msgq->mbox.chans[0];
>> +}
>> +
>> +/******************************************************************************/
>> +/* Common arch-independent macros and definitions for Gunyah
>> hypercalls */
>> -/* Common Gunyah macros */
>> #define GH_CAPID_INVAL U64_MAX
>> #define GH_VMID_ROOT_VM 0xff
>
On 1/9/2023 1:34 PM, Alex Elder wrote:
> On 12/19/22 4:58 PM, Elliot Berman wrote:
>> Add hypercalls to identify when Linux is running a virtual machine under
>> Gunyah.
>>
>> There are two calls to help identify Gunyah:
>>
>> 1. gh_hypercall_get_uid() returns a UID when running under a Gunyah
>> hypervisor.
>> 2. gh_hypercall_hyp_identify() returns build information and a set of
>> feature flags that are supported by Gunyah.
>
> The first is a "service", while the second is a "hypercall".
> Can you explain the distinction? The sentence at the top
> refers to both as "hypercalls".
>
I learned more details about this to answer your question. "get_uid()"
is a standardized call that is ARM_SMCCC_VENDOR_HYP_CALL_UID_FUNC_ID
defined in include/arm-smccc.h. I'll use that.
>>
>> Signed-off-by: Elliot Berman <[email protected]>
>> ---
>> MAINTAINERS | 2 +
>> arch/arm64/Kbuild | 1 +
>> arch/arm64/gunyah/Makefile | 1 +
>> arch/arm64/gunyah/gunyah_hypercall.c | 69 ++++++++++++++++++++++++++++
>> drivers/virt/Kconfig | 1 +
>> drivers/virt/gunyah/Kconfig | 12 +++++
>> include/linux/gunyah.h | 25 ++++++++++
>> 7 files changed, 111 insertions(+)
>> create mode 100644 arch/arm64/gunyah/Makefile
>> create mode 100644 arch/arm64/gunyah/gunyah_hypercall.c
>> create mode 100644 drivers/virt/gunyah/Kconfig
>>
>> diff --git a/MAINTAINERS b/MAINTAINERS
>> index 74e76e0ab14d..36698df6b0e5 100644
>> --- a/MAINTAINERS
>> +++ b/MAINTAINERS
>> @@ -8941,6 +8941,8 @@ L: [email protected]
>> S: Supported
>> F: Documentation/devicetree/bindings/firmware/gunyah-hypervisor.yaml
>> F: Documentation/virt/gunyah/
>> +F: arch/arm64/gunyah/
>> +F: drivers/virt/gunyah/
>> F: include/linux/gunyah.h
>> HABANALABS PCI DRIVER
>> diff --git a/arch/arm64/Kbuild b/arch/arm64/Kbuild
>> index 5bfbf7d79c99..e4847ba0e3c9 100644
>> --- a/arch/arm64/Kbuild
>> +++ b/arch/arm64/Kbuild
>> @@ -3,6 +3,7 @@ obj-y += kernel/ mm/ net/
>> obj-$(CONFIG_KVM) += kvm/
>> obj-$(CONFIG_XEN) += xen/
>> obj-$(subst m,y,$(CONFIG_HYPERV)) += hyperv/
>> +obj-$(CONFIG_GUNYAH) += gunyah/
>> obj-$(CONFIG_CRYPTO) += crypto/
>> # for cleaning
>> diff --git a/arch/arm64/gunyah/Makefile b/arch/arm64/gunyah/Makefile
>> new file mode 100644
>> index 000000000000..9fbc720b6fb6
>> --- /dev/null
>> +++ b/arch/arm64/gunyah/Makefile
>> @@ -0,0 +1 @@
>> +obj-$(CONFIG_GUNYAH) += gunyah_hypercall.o
>> diff --git a/arch/arm64/gunyah/gunyah_hypercall.c
>> b/arch/arm64/gunyah/gunyah_hypercall.c
>> new file mode 100644
>> index 000000000000..0beb3123d650
>> --- /dev/null
>> +++ b/arch/arm64/gunyah/gunyah_hypercall.c
>> @@ -0,0 +1,69 @@
>> +// SPDX-License-Identifier: GPL-2.0-only
>> +/*
>> + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights
>> reserved.
>> + */
>> +
>> +#include <linux/arm-smccc.h>
>> +#include <linux/module.h>
>> +#include <linux/gunyah.h>
>> +
>> +#define GH_CALL_TYPE_PLATFORM_CALL 0
>> +#define GH_CALL_TYPE_HYPERCALL 2
>> +#define GH_CALL_TYPE_SERVICE 3
>> +#define GH_CALL_TYPE_SHIFT 14
>> +#define GH_CALL_FUNCTION_NUM_MASK 0x3fff
>
> A FN_ID is a 32-bit value. Are all 18 high-order bits considered
> part of the call type? It might be good to specify that explicitly
> by defining a mask for it.
>
With above in mind, I decided to simplify the macros and drop the TYPE
field.
>> +
>> +#define GH_FN_ID(type, num) ((type) << GH_CALL_TYPE_SHIFT | ((num)
>> & GH_CALL_FUNCTION_NUM_MASK))
>> +
>
> Is there any need for the endianness of these values to be specified?
> Does Gunyah operate with a well-defined endianness? Is there any
> chance a VM can run with an endianness different from Gunyah? I
> see that the arm_smcc_* structures are defined without endianness.
> (Sorry if these are dumb questions.)
>
All of the data transfers for hypercalls happen via registers, so
endianness doesn't have impact here (there is no "low address" in a
register).
>> +#define GH_SERVICE(fn) ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL,
>> ARM_SMCCC_SMC_32, \
>> + ARM_SMCCC_OWNER_VENDOR_HYP, \
>> + GH_FN_ID(GH_CALL_TYPE_SERVICE, fn))
>> +
>> +#define GH_HYPERCALL_CALL_UID GH_SERVICE(0x3f01)
>
> Perhaps 0x3f01 could be defined symbolically.
>
> However if this is the only place it's ever used, doing so
> doesn't add much value (meaning, just do it the way you did).
>
>> +
>> +#define GH_HYPERCALL(fn) ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL,
>> ARM_SMCCC_SMC_64, \
>> + ARM_SMCCC_OWNER_VENDOR_HYP, \
>> + GH_FN_ID(GH_CALL_TYPE_HYPERCALL, fn))
>> +
>> +#define GH_HYPERCALL_HYP_IDENTIFY GH_HYPERCALL(0x0000)
>
> Will there be a growing set of well-known hypervisor call functions?
> Perhaps 0x0000 should be defined symbolically. (Or not if it's only
> used here.)
>
Yes, we would add growing set of well-known hypercalls. 0x0000 would
only be used here.
>> +
>> +/**
>> + * gh_hypercall_get_uid() - Returns a UID when running under a Gunyah
>> hypervisor
>> + * @uid: An array of 4 u32's (u32 uid[4];)
>> + *
>> + * Caller should compare the resulting UID to a list of known Gunyah
>> UIDs to
>> + * confirm that Linux is running as a guest of Gunyah.
>
> I presume that, if the returned UID isn't well-known, then no other
> Gunyah-related calls are meaningful. Is that correct?
>
That's correct.
>> + */
>> +void gh_hypercall_get_uid(u32 uid[4])
>> +{
>> + struct arm_smccc_res res;
>> +
>> + arm_smccc_1_1_hvc(GH_HYPERCALL_CALL_UID, &res);
>> +
>> + uid[0] = res.a0;
>> + uid[1] = res.a1;
>> + uid[2] = res.a2;
>> + uid[3] = res.a3;
>
> I see in the definition of struct arm_smccc_res that the four
> fields are unsigned long values. That differs from the u32
> array passed as argument. Are the resource IDs guaranteed to
> be four 32-bit values? I personally prefer being explicit
> about the upper 32-bits being discarded (though some don't
> agree with that convention).
>
Done.
>> +}
>> +EXPORT_SYMBOL_GPL(gh_hypercall_get_uid);
>> +
>> +/**
>> + * gh_hypercall_hyp_identify() - Returns build information and
>> feature flags
>> + * supported by Gunyah.
>> + * @hyp_identity: filled by the hypercall with the API info and
>> feature flags.
>> + */
>> +void gh_hypercall_hyp_identify(struct gh_hypercall_hyp_identify_resp
>> *hyp_identity)
>> +{
>> + struct arm_smccc_res res;
>> +
>> + arm_smccc_1_1_hvc(GH_HYPERCALL_HYP_IDENTIFY, &res);
>> +
>> + hyp_identity->api_info = res.a0;
>> + hyp_identity->flags[0] = res.a1;
>> + hyp_identity->flags[1] = res.a2;
>> + hyp_identity->flags[2] = res.a3;
>> +}
>> +EXPORT_SYMBOL_GPL(gh_hypercall_hyp_identify);
>> +
>> +MODULE_LICENSE("GPL");
>> +MODULE_DESCRIPTION("Gunyah Hypervisor Hypercalls");
>> diff --git a/drivers/virt/Kconfig b/drivers/virt/Kconfig
>> index 87ef258cec64..259dc2be6cad 100644
>> --- a/drivers/virt/Kconfig
>> +++ b/drivers/virt/Kconfig
>> @@ -52,4 +52,5 @@ source "drivers/virt/coco/efi_secret/Kconfig"
>> source "drivers/virt/coco/sev-guest/Kconfig"
>> +source "drivers/virt/gunyah/Kconfig"
>> endif
>> diff --git a/drivers/virt/gunyah/Kconfig b/drivers/virt/gunyah/Kconfig
>> new file mode 100644
>> index 000000000000..127156a678a6
>> --- /dev/null
>> +++ b/drivers/virt/gunyah/Kconfig
>> @@ -0,0 +1,12 @@
>> +# SPDX-License-Identifier: GPL-2.0-only
>> +
>> +config GUNYAH
>
> Maybe config QCOM_GUNYAH? Will this ever run on hardware
> other than Qualcomm's?
>
Yes, Gunyah can run on other hardware. We have support for QEMU and
other hardware support is anticipated.
>> + tristate "Gunyah Virtualization drivers"
>> + depends on ARM64
>> + help
>> + The Gunyah drivers are the helper interfaces that runs in a
>> guest VM
>
> s/runs/run/
>
>> + such as basic inter-VM IPC and signalingmechanisms, and higher
>> level
>> + services such as memory/device sharing, IRQ sharing, and so on.
>> +
>> + Say Y/M here to enable the drivers needed to interact in a Gunyah
>> + virtual environment.
>> diff --git a/include/linux/gunyah.h b/include/linux/gunyah.h
>> index 824e20a11d27..2765d2b40198 100644
>> --- a/include/linux/gunyah.h
>> +++ b/include/linux/gunyah.h
>> @@ -6,6 +6,7 @@
>> #ifndef _GUNYAH_H
>> #define _GUNYAH_H
>> +#include <linux/bitfield.h>
>> #include <linux/types.h>
>> #include <linux/errno.h>
>> @@ -71,4 +72,28 @@ static inline int gh_remap_error(int gh_error)
>> }
>> }
>> +#define GUNYAH_API_V1 1
>> +
>> +#define GH_API_INFO_API_VERSION_MASK GENMASK_ULL(13, 0)
>> +#define GH_API_INFO_BIG_ENDIAN BIT_ULL(14)
>> +#define GH_API_INFO_IS_64BIT BIT_ULL(15)
>> +#define GH_API_INFO_VARIANT_MASK GENMASK_ULL(63, 56)
>> +
>
> How are the GH_IDENTIFY bits below used? Are they encoded
> in the three 64-bit flags fields in the response structure?
> Does that mean only the first of those three is (currently)
> used?
>
That's correct.
>> +#define GH_IDENTIFY_PARTITION_CSPACE BIT_ULL(0)
>> +#define GH_IDENTIFY_DOORBELL BIT_ULL(1)
>> +#define GH_IDENTIFY_MSGQUEUE BIT_ULL(2)
>> +#define GH_IDENTIFY_VIC BIT_ULL(3)
>> +#define GH_IDENTIFY_VPM BIT_ULL(4)
>> +#define GH_IDENTIFY_VCPU BIT_ULL(5)
>> +#define GH_IDENTIFY_MEMEXTENT BIT_ULL(6)
>> +#define GH_IDENTIFY_TRACE_CTRL BIT_ULL(7)
>> +
>> +struct gh_hypercall_hyp_identify_resp {
>> + u64 api_info;
>> + u64 flags[3];
>> +};
>
> Again I'll ask about endianness. This is a response coming *from*
> Gunyah. Is it guaranteed to use the same byte order convention as
> the running operating system (Linux) guest?
>
Yes, that guarantee is there.
Thanks,
Elliot
On 1/9/2023 1:34 PM, Alex Elder wrote:
> On 12/19/22 4:58 PM, Elliot Berman wrote:
>> Export the version of Gunyah which is reported via the hyp_identify
>> hypercall. Increments of the major API version indicate possibly
>> backwards incompatible changes.
>>
>> Export the hypervisor identity so that Gunyah drivers can act according
>> to the major API version.
>>
>> Signed-off-by: Elliot Berman <[email protected]>
>> ---
>> drivers/virt/Makefile | 1 +
>> drivers/virt/gunyah/Makefile | 1 +
>> drivers/virt/gunyah/gunyah.c | 46 ++++++++++++++++++++++++++++++++++++
>> include/linux/gunyah.h | 6 +++++
>> 4 files changed, 54 insertions(+)
>> create mode 100644 drivers/virt/gunyah/Makefile
>> create mode 100644 drivers/virt/gunyah/gunyah.c
>>
>> diff --git a/drivers/virt/Makefile b/drivers/virt/Makefile
>> index 093674e05c40..92b7e5311548 100644
>> --- a/drivers/virt/Makefile
>> +++ b/drivers/virt/Makefile
>> @@ -11,3 +11,4 @@ obj-$(CONFIG_NITRO_ENCLAVES) += nitro_enclaves/
>> obj-$(CONFIG_ACRN_HSM) += acrn/
>> obj-$(CONFIG_EFI_SECRET) += coco/efi_secret/
>> obj-$(CONFIG_SEV_GUEST) += coco/sev-guest/
>> +obj-y += gunyah/
>> diff --git a/drivers/virt/gunyah/Makefile b/drivers/virt/gunyah/Makefile
>> new file mode 100644
>> index 000000000000..2ac4ee64b89d
>> --- /dev/null
>> +++ b/drivers/virt/gunyah/Makefile
>> @@ -0,0 +1 @@
>> +obj-$(CONFIG_GUNYAH) += gunyah.o
>> diff --git a/drivers/virt/gunyah/gunyah.c b/drivers/virt/gunyah/gunyah.c
>> new file mode 100644
>> index 000000000000..c34c9046fc08
>> --- /dev/null
>> +++ b/drivers/virt/gunyah/gunyah.c
>> @@ -0,0 +1,46 @@
>> +// SPDX-License-Identifier: GPL-2.0-only
>> +/*
>> + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights
>> reserved.
>> + */
>> +
>> +#define pr_fmt(fmt) "gunyah: " fmt
>> +
>> +#include <linux/gunyah.h>
>> +#include <linux/init.h>
>> +#include <linux/module.h>
>> +#include <linux/printk.h>
>> +
>> +struct gh_hypercall_hyp_identify_resp gunyah_api;
>> +EXPORT_SYMBOL_GPL(gunyah_api);
>> +
>> +static const uint32_t gunyah_known_uuids[][4] = {
>> + {0x19bd54bd, 0x0b37571b, 0x946f609b, 0x54539de6}, /*QC_HYP
>> (Qualcomm's build) */
>> + {0x673d5f14, 0x9265ce36, 0xa4535fdb, 0xc1d58fcd}, /*GUNYAH (open
>> source build) */
>> +};
>> +
>> +static int __init gunyah_init(void)
>> +{
>> + u32 uid[4];
>> + int i;
>> +
>> + gh_hypercall_get_uid(uid);
>> +
>> + for (i = 0; i < ARRAY_SIZE(gunyah_known_uuids); i++)
>> + if (!memcmp(uid, gunyah_known_uuids[i], sizeof(uid)))
>> + break;
>> +
>> + if (i == ARRAY_SIZE(gunyah_known_uuids))
>> + return -ENODEV;
>> +
>> + gh_hypercall_hyp_identify(&gunyah_api);
>> +
>> + pr_info("Running under Gunyah hypervisor %llx/v%u\n",
>> + FIELD_GET(GH_API_INFO_VARIANT_MASK, gunyah_api.api_info),
>> + gh_api_version());
>> +
>> + return 0;
>> +}
>> +arch_initcall(gunyah_init);
>> +
>> +MODULE_LICENSE("GPL");
>> +MODULE_DESCRIPTION("Gunyah Hypervisor Driver");
>> diff --git a/include/linux/gunyah.h b/include/linux/gunyah.h
>> index 2765d2b40198..166156f69df9 100644
>> --- a/include/linux/gunyah.h
>> +++ b/include/linux/gunyah.h
>> @@ -92,6 +92,12 @@ struct gh_hypercall_hyp_identify_resp {
>> u64 api_info;
>> u64 flags[3];
>> };
>> +extern struct gh_hypercall_hyp_identify_resp gunyah_api;
>
> Must this global variable be exposed? Can't you hide it--as
> well as its interpretation--inside "gunyah.c"?
> >> +
>> +static inline u16 gh_api_version(void)
>> +{
>> + return FIELD_GET(GH_API_INFO_API_VERSION_MASK, gunyah_api.api_info);
>> +}
>
> If you don't make the above function inline, can you hide the
> definition of gunyah_api?
>
This seems like a good idea to me. I'm thinking to have the following
functions:
enum gh_api_feature {
GH_API_FEATURE_DOORBELL,
GH_API_FEATURE_MSGQUEUE,
GH_API_FEATURE_VIC,
GH_API_FEATURE_VPM,
GH_API_FEATURE_VCPU,
GH_API_FEATURE_MEMEXTENT,
GH_API_FEATURE_TRACE_CTRL,
};
u16 gh_api_version(void);
bool gh_api_has_feature(enum gh_api_feature feature);
Thanks,
Elliot
On 1/9/2023 1:34 PM, Alex Elder wrote:
> On 12/19/22 4:58 PM, Elliot Berman wrote:
>> Add architecture-independent standard error codes, types, and macros for
>> Gunyah hypercalls.
>>
>> Reviewed-by: Dmitry Baryshkov <[email protected]>
>> Signed-off-by: Elliot Berman <[email protected]>
>> ---
>> MAINTAINERS | 1 +
>> include/linux/gunyah.h | 74 ++++++++++++++++++++++++++++++++++++++++++
>> 2 files changed, 75 insertions(+)
>> create mode 100644 include/linux/gunyah.h
>>
>> diff --git a/MAINTAINERS b/MAINTAINERS
>> index f32320a9efa4..74e76e0ab14d 100644
>> --- a/MAINTAINERS
>> +++ b/MAINTAINERS
>> @@ -8941,6 +8941,7 @@ L: [email protected]
>> S: Supported
>> F: Documentation/devicetree/bindings/firmware/gunyah-hypervisor.yaml
>> F: Documentation/virt/gunyah/
>> +F: include/linux/gunyah.h
>> HABANALABS PCI DRIVER
>> M: Oded Gabbay <[email protected]>
>> diff --git a/include/linux/gunyah.h b/include/linux/gunyah.h
>> new file mode 100644
>> index 000000000000..824e20a11d27
>> --- /dev/null
>> +++ b/include/linux/gunyah.h
>> @@ -0,0 +1,74 @@
>> +/* SPDX-License-Identifier: GPL-2.0-only */
>> +/*
>> + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights
>> reserved.
>> + */
>> +
>> +#ifndef _GUNYAH_H
>
> Maybe use _LINUX_GUNYAH_H?
>
Ack.
>> +#define _GUNYAH_H
>> +
>> +#include <linux/types.h>
>
> Why is types.h included?
>
> U64_MAX is defined in <linux/limits.h> (in case that's why).
Ack.
>
> Otherwise this just defines a fixed API exposed by Gunyah
> so there's not much more for me to comment on.
>
> -Alex
>
>> +#include <linux/errno.h>
>> +
>> +/* Common Gunyah macros */
>> +#define GH_CAPID_INVAL U64_MAX
>> +#define GH_VMID_ROOT_VM 0xff
>> +
>> +#define GH_ERROR_OK 0
>> +
>> +#define GH_ERROR_UNIMPLEMENTED -1
>> +#define GH_ERROR_RETRY -2
>> +
>> +#define GH_ERROR_ARG_INVAL 1
>> +#define GH_ERROR_ARG_SIZE 2
>> +#define GH_ERROR_ARG_ALIGN 3
>> +
>> +#define GH_ERROR_NOMEM 10
>> +
>> +#define GH_ERROR_ADDR_OVFL 20
>> +#define GH_ERROR_ADDR_UNFL 21
>> +#define GH_ERROR_ADDR_INVAL 22
>> +
>> +#define GH_ERROR_DENIED 30
>> +#define GH_ERROR_BUSY 31
>> +#define GH_ERROR_IDLE 32
>> +
>> +#define GH_ERROR_IRQ_BOUND 40
>> +#define GH_ERROR_IRQ_UNBOUND 41
>> +
>> +#define GH_ERROR_CSPACE_CAP_NULL 50
>> +#define GH_ERROR_CSPACE_CAP_REVOKED 51
>> +#define GH_ERROR_CSPACE_WRONG_OBJ_TYPE 52
>> +#define GH_ERROR_CSPACE_INSUF_RIGHTS 53
>> +#define GH_ERROR_CSPACE_FULL 54
>> +
>> +#define GH_ERROR_MSGQUEUE_EMPTY 60
>> +#define GH_ERROR_MSGQUEUE_FULL 61
>> +
>> +static inline int gh_remap_error(int gh_error)
>> +{
>> + switch (gh_error) {
>> + case GH_ERROR_OK:
>> + return 0;
>> + case GH_ERROR_NOMEM:
>> + return -ENOMEM;
>> + case GH_ERROR_DENIED:
>> + case GH_ERROR_CSPACE_CAP_NULL:
>> + case GH_ERROR_CSPACE_CAP_REVOKED:
>> + case GH_ERROR_CSPACE_WRONG_OBJ_TYPE:
>> + case GH_ERROR_CSPACE_INSUF_RIGHTS:
>> + case GH_ERROR_CSPACE_FULL:
>> + return -EACCES;
>> + case GH_ERROR_BUSY:
>> + case GH_ERROR_IDLE:
>> + return -EBUSY;
>> + case GH_ERROR_IRQ_BOUND:
>> + case GH_ERROR_IRQ_UNBOUND:
>> + case GH_ERROR_MSGQUEUE_FULL:
>> + case GH_ERROR_MSGQUEUE_EMPTY:
>> + return -EPERM;
>> + default:
>> + return -EINVAL;
>> + }
>> +}
>> +
>> +#endif
>
On 1/9/2023 1:34 PM, Alex Elder wrote:
> On 12/19/22 4:58 PM, Elliot Berman wrote:
>> Gunyah is a Type-1 hypervisor independent of any
>> high-level OS kernel, and runs in a higher CPU privilege level. It does
>> not depend on any lower-privileged OS kernel/code for its core
>> functionality. This increases its security and can support a much smaller
>> trusted computing base than a Type-2 hypervisor.
>>
>> Gunyah is an open source hypervisor. The source repo is available at
>> https://github.com/quic/gunyah-hypervisor.
>
> Can you provide any history about the hypervisor code itself?
> Was it publicly reviewed? Has it been reviewed by anyone in
> the Linux kernel community, who might have some useful input
> on it?
>
>> The diagram below shows the architecture.
>>
>> ::
>>
>> VM A VM B
>> +-----+ +-----+ | +-----+ +-----+ +-----+
>> | | | | | | | | | | |
>> EL0 | APP | | APP | | | APP | | APP | | APP |
>> | | | | | | | | | | |
>> +-----+ +-----+ | +-----+ +-----+ +-----+
>> ---------------------|-------------------------
>> +--------------+ | +----------------------+
>> | | | | |
>> EL1 | Linux Kernel | | |Linux kernel/Other OS | ...
>> | | | | |
>> +--------------+ | +----------------------+
>> --------hvc/smc------|------hvc/smc------------
>> +----------------------------------------+
>> | |
>> EL2 | Gunyah Hypervisor |
>> | |
>> +----------------------------------------+
>>
>> Gunyah provides these following features.
>>
>> - Threads and Scheduling: The scheduler schedules virtual CPUs (VCPUs) on
>> physical CPUs and enables time-sharing of the CPUs.
>> - Memory Management: Gunyah tracks memory ownership and use of all memory
>> under its control. Memory partitioning between VMs is a fundamental
>> security feature.
>> - Interrupt Virtualization: All interrupts are handled in the hypervisor
>> and routed to the assigned VM.
>> - Inter-VM Communication: There are several different mechanisms provided
>> for communicating between VMs.
>> - Device Virtualization: Para-virtualization of devices is supported
>> using
>> inter-VM communication. Low level system features and devices such as
>> interrupt controllers are supported with emulation where required.
>>
>> This series adds the basic framework for detecting that Linux is running
>> under Gunyah as a virtual machine, communication with the Gunyah Resource
>> Manager, and a virtual machine manager capable of launching virtual
>> machines.
>>
>> Patches 21-28 are presently intended to be submitted separately and
>> are included
>> for initial RFC. These patches introudce "VM function" framework to
>> expose further
>> interfaces to interact with Gunyah Virtual Machines. With all 28
>> patches, it is
>> possible to create a Gunyah VM supporting virtio.
>
> I'm not a virtualization expert, and it's likely some of my
> comments demonstrate that...
>
> You're going to need to update your copyright dates to include
> 2023 now.
>
> I haven't looked at the earlier reviews; perhaps the RFC stuff
> was requested. I'm sure it's useful to see that but it doesn't
> seem directly helpful if your goal is to get this code upstream.
>
Right, the RFC patches were requested. Do you have a recommendation for
sharing those later patches? I understand it's best practice not to post
too many patches. The logical split was to have 1-20 go in first, and
the remaining patches submitted later.
> At this point I've reviewed only through patch 9. I currently
> have smallish blocks of time and I don't want to hold back my
> feedback for too long.
>
> -Alex
>
>> Changes in v8:
>> - Treat VM manager as a library of RM
>> - Add patches 21-28 as RFC to support proxy-scheduled vCPUs and
>> necessary bits to support virtio
>> from Gunyah userspace
>>
>> Changes in v7:
>> https://lore.kernel.org/all/[email protected]/
>> - Refactor to remove gunyah RM bus
>> - Refactor allow multiple RM device instances
>> - Bump UAPI to start at 0x0
>> - Refactor QCOM SCM's platform hooks to allow
>> CONFIG_QCOM_SCM=Y/CONFIG_GUNYAH=M combinations
>>
>> Changes in v6:
>> https://lore.kernel.org/all/[email protected]/
>> - *Replace gunyah-console with gunyah VM Manager*
>> - Move include/asm-generic/gunyah.h into include/linux/gunyah.h
>> - s/gunyah_msgq/gh_msgq/
>> - Minor tweaks and documentation tidying based on comments from
>> Jiri, Greg, Arnd, Dmitry, and Bagas.
>>
>> Changes in v5:
>> https://lore.kernel.org/all/[email protected]/
>> - Dropped sysfs nodes
>> - Switch from aux bus to Gunyah RM bus for the subdevices
>> - Cleaning up RM console
>>
>> Changes in v4:
>> https://lore.kernel.org/all/[email protected]/
>> - Tidied up documentation throughout based on questions/feedback
>> received
>> - Switched message queue implementation to use mailboxes
>> - Renamed "gunyah_device" as "gunyah_resource"
>>
>> Changes in v3:
>> https://lore.kernel.org/all/[email protected]/
>> - /Maintained/Supported/ in MAINTAINERS
>> - Tidied up documentation throughout based on questions/feedback
>> received
>> - Moved hypercalls into arch/arm64/gunyah/; following hyper-v's
>> implementation
>> - Drop opaque typedefs
>> - Move sysfs nodes under /sys/hypervisor/gunyah/
>> - Moved Gunyah console driver to drivers/tty/
>> - Reworked gunyah_device design to drop the Gunyah bus.
>>
>> Changes in v2:
>> https://lore.kernel.org/all/[email protected]/
>> - DT bindings clean up
>> - Switch hypercalls to follow SMCCC
>>
>> v1:
>> https://lore.kernel.org/all/[email protected]/
>>
>> Elliot Berman (28):
>> docs: gunyah: Introduce Gunyah Hypervisor
>> dt-bindings: Add binding for gunyah hypervisor
>> gunyah: Common types and error codes for Gunyah hypercalls
>> arm64: smccc: Include alternative-macros.h
>> virt: gunyah: Add hypercalls to identify Gunyah
>> virt: gunyah: Identify hypervisor version
>> mailbox: Allow direct registration to a channel
>> virt: gunyah: msgq: Add hypercalls to send and receive messages
>> mailbox: Add Gunyah message queue mailbox
>> gunyah: rsc_mgr: Add resource manager RPC core
>> gunyah: rsc_mgr: Add VM lifecycle RPC
>> gunyah: vm_mgr: Introduce basic VM Manager
>> gunyah: rsc_mgr: Add RPC for sharing memory
>> gunyah: vm_mgr: Add/remove user memory regions
>> gunyah: vm_mgr: Add ioctls to support basic non-proxy VM boot
>> samples: Add sample userspace Gunyah VM Manager
>> gunyah: rsc_mgr: Add platform ops on mem_lend/mem_reclaim
>> firmware: qcom_scm: Use fixed width src vm bitmap
>> firmware: qcom_scm: Register Gunyah platform ops
>> docs: gunyah: Document Gunyah VM Manager
>> virt: gunyah: Translate gh_rm_hyp_resource into gunyah_resource
>> gunyah: vm_mgr: Add framework to add VM Functions
>> virt: gunyah: Add resource tickets
>> virt: gunyah: Add IO handlers
>> virt: gunyah: Add proxy-scheduled vCPUs
>> virt: gunyah: Add hypercalls for sending doorbell
>> virt: gunyah: Add irqfd interface
>> virt: gunyah: Add ioeventfd
>>
>> .../bindings/firmware/gunyah-hypervisor.yaml | 82 ++
>> .../userspace-api/ioctl/ioctl-number.rst | 1 +
>> Documentation/virt/gunyah/index.rst | 115 +++
>> Documentation/virt/gunyah/message-queue.rst | 64 ++
>> Documentation/virt/gunyah/vm-manager.rst | 187 ++++
>> Documentation/virt/index.rst | 1 +
>> MAINTAINERS | 13 +
>> arch/arm64/Kbuild | 1 +
>> arch/arm64/gunyah/Makefile | 1 +
>> arch/arm64/gunyah/gunyah_hypercall.c | 157 ++++
>> arch/arm64/include/asm/gunyah.h | 23 +
>> drivers/firmware/Kconfig | 2 +
>> drivers/firmware/qcom_scm.c | 107 ++-
>> drivers/mailbox/Kconfig | 10 +
>> drivers/mailbox/Makefile | 2 +
>> drivers/mailbox/gunyah-msgq.c | 229 +++++
>> drivers/mailbox/mailbox.c | 96 ++-
>> drivers/mailbox/omap-mailbox.c | 18 +-
>> drivers/mailbox/pcc.c | 18 +-
>> drivers/misc/fastrpc.c | 6 +-
>> drivers/net/wireless/ath/ath10k/qmi.c | 4 +-
>> drivers/remoteproc/qcom_q6v5_mss.c | 8 +-
>> drivers/soc/qcom/rmtfs_mem.c | 2 +-
>> drivers/virt/Kconfig | 1 +
>> drivers/virt/Makefile | 1 +
>> drivers/virt/gunyah/Kconfig | 73 ++
>> drivers/virt/gunyah/Makefile | 10 +
>> drivers/virt/gunyah/gunyah.c | 46 +
>> drivers/virt/gunyah/gunyah_ioeventfd.c | 109 +++
>> drivers/virt/gunyah/gunyah_irqfd.c | 180 ++++
>> drivers/virt/gunyah/gunyah_platform_hooks.c | 63 ++
>> drivers/virt/gunyah/gunyah_vcpu.c | 350 ++++++++
>> drivers/virt/gunyah/rsc_mgr.c | 795 ++++++++++++++++++
>> drivers/virt/gunyah/rsc_mgr.h | 124 +++
>> drivers/virt/gunyah/rsc_mgr_rpc.c | 428 ++++++++++
>> drivers/virt/gunyah/vm_mgr.c | 594 +++++++++++++
>> drivers/virt/gunyah/vm_mgr.h | 76 ++
>> drivers/virt/gunyah/vm_mgr_mm.c | 245 ++++++
>> include/linux/arm-smccc.h | 1 +
>> include/linux/gunyah.h | 186 ++++
>> include/linux/gunyah_rsc_mgr.h | 136 +++
>> include/linux/gunyah_vm_mgr.h | 107 +++
>> include/linux/mailbox_client.h | 1 +
>> include/linux/qcom_scm.h | 2 +-
>> include/uapi/linux/gunyah.h | 114 +++
>> samples/Kconfig | 10 +
>> samples/Makefile | 1 +
>> samples/gunyah/.gitignore | 2 +
>> samples/gunyah/Makefile | 6 +
>> samples/gunyah/gunyah_vmm.c | 270 ++++++
>> samples/gunyah/sample_vm.dts | 69 ++
>> 51 files changed, 5075 insertions(+), 72 deletions(-)
>> create mode 100644
>> Documentation/devicetree/bindings/firmware/gunyah-hypervisor.yaml
>> create mode 100644 Documentation/virt/gunyah/index.rst
>> create mode 100644 Documentation/virt/gunyah/message-queue.rst
>> create mode 100644 Documentation/virt/gunyah/vm-manager.rst
>> create mode 100644 arch/arm64/gunyah/Makefile
>> create mode 100644 arch/arm64/gunyah/gunyah_hypercall.c
>> create mode 100644 arch/arm64/include/asm/gunyah.h
>> create mode 100644 drivers/mailbox/gunyah-msgq.c
>> create mode 100644 drivers/virt/gunyah/Kconfig
>> create mode 100644 drivers/virt/gunyah/Makefile
>> create mode 100644 drivers/virt/gunyah/gunyah.c
>> create mode 100644 drivers/virt/gunyah/gunyah_ioeventfd.c
>> create mode 100644 drivers/virt/gunyah/gunyah_irqfd.c
>> create mode 100644 drivers/virt/gunyah/gunyah_platform_hooks.c
>> create mode 100644 drivers/virt/gunyah/gunyah_vcpu.c
>> create mode 100644 drivers/virt/gunyah/rsc_mgr.c
>> create mode 100644 drivers/virt/gunyah/rsc_mgr.h
>> create mode 100644 drivers/virt/gunyah/rsc_mgr_rpc.c
>> create mode 100644 drivers/virt/gunyah/vm_mgr.c
>> create mode 100644 drivers/virt/gunyah/vm_mgr.h
>> create mode 100644 drivers/virt/gunyah/vm_mgr_mm.c
>> create mode 100644 include/linux/gunyah.h
>> create mode 100644 include/linux/gunyah_rsc_mgr.h
>> create mode 100644 include/linux/gunyah_vm_mgr.h
>> create mode 100644 include/uapi/linux/gunyah.h
>> create mode 100644 samples/gunyah/.gitignore
>> create mode 100644 samples/gunyah/Makefile
>> create mode 100644 samples/gunyah/gunyah_vmm.c
>> create mode 100644 samples/gunyah/sample_vm.dts
>>
>>
>> base-commit: 830b3c68c1fb1e9176028d02ef86f3cf76aa2476
>
* Elliot Berman <[email protected]> [2022-12-19 14:58:34]:
> +static int _gh_rm_mem_append(struct gh_rm_rpc *rm, u32 mem_handle, bool end_append,
> + struct gh_rm_mem_entry *mem_entries, size_t n_mem_entries)
> +{
> + size_t msg_size = 0;
> + void *msg;
> + struct gh_mem_append_req_header *req_header;
> + struct gh_mem_share_req_mem_section *mem_section;
> + void *resp;
> + size_t resp_size;
> + int ret;
> +
> + msg_size += sizeof(struct gh_mem_append_req_header);
> + msg_size += offsetof(struct gh_mem_share_req_mem_section, entries[n_mem_entries]);
> +
> + msg = kzalloc(msg_size, GFP_KERNEL);
> + if (!msg)
> + return -ENOMEM;
> +
> + req_header = msg;
> + mem_section = (void *)req_header + sizeof(struct gh_mem_append_req_header);
> +
> + req_header->mem_handle = mem_handle;
cpu_to_le(mem_handle);
> + if (end_append)
> + req_header->flags |= GH_MEM_APPEND_REQ_FLAGS_END;
> +
> + mem_section->n_entries = n_mem_entries;
> + memcpy(mem_section->entries, mem_entries, sizeof(*mem_entries) * n_mem_entries);
> +
> + ret = gh_rm_call(rm, GH_RM_RPC_MEM_APPEND, msg, msg_size, &resp, &resp_size);
> + if (ret)
kfree(msg);
> + return ret;
> + kfree(resp);
kfree(msg);
> +
> + return ret;
> +}
> +
> +static int gh_rm_mem_append(struct gh_rm_rpc *rm, u32 mem_handle, bool allow_append,
Is there any scenario where allow_append will be 'true'? As of this patch
atleast, its always 'false', so maybe just avoid this input argument?
> +static int gh_rm_mem_lend_common(struct gh_rm_rpc *rm, u32 message_id, struct gh_rm_mem_parcel *p)
> +{
[snip]
> + acl_section->n_entries = p->n_acl_entries;
cpu_to_le32(p->n_acl_entries);
> + memcpy(acl_section->entries, p->acl_entries, sizeof(*(p->acl_entries)) * p->n_acl_entries);
> +
> + mem_section->n_entries = initial_n_mem_entries;
cpu_to_le32(initial_n_mem_entries);
> + memcpy(mem_section->entries, p->mem_entries,
> + sizeof(*(p->mem_entries)) * initial_n_mem_entries);
> +
> + /* Set n_entries for memory attribute section to 0 */
> + *mem_attr_section = 0;
> +
> + ret = gh_rm_call(rm, message_id, msg, msg_size, &resp, &resp_size);
> + if (ret)
kfree(msg)
> + return ret;
> +
> + if (resp_size != sizeof(__le32)) {
> + ret = -EIO;
> + goto out;
> + }
> +
> + p->mem_handle = le32_to_cpu(*(__le32 *)resp);
> +
> + if (initial_n_mem_entries != p->n_mem_entries) {
> + ret = gh_rm_mem_append(rm, p->mem_handle, false,
> + &p->mem_entries[initial_n_mem_entries],
> + p->n_mem_entries - initial_n_mem_entries);
> + if (ret)
> + gh_rm_mem_reclaim(rm, p);
p->mem_handle = GH_MEM_HANDLE_INVAL in this case?
> + }
> +
> +out:
kfree(msg) ?
On 1/10/23 11:56 AM, Elliot Berman wrote:
>
>
> On 1/9/2023 1:34 PM, Alex Elder wrote:
>> On 12/19/22 4:58 PM, Elliot Berman wrote:
>>> Export the version of Gunyah which is reported via the hyp_identify
>>> hypercall. Increments of the major API version indicate possibly
>>> backwards incompatible changes.
>>>
>>> Export the hypervisor identity so that Gunyah drivers can act according
>>> to the major API version.
>>>
>>> Signed-off-by: Elliot Berman <[email protected]>
>>> ---
>>> drivers/virt/Makefile | 1 +
>>> drivers/virt/gunyah/Makefile | 1 +
>>> drivers/virt/gunyah/gunyah.c | 46 ++++++++++++++++++++++++++++++++++++
>>> include/linux/gunyah.h | 6 +++++
>>> 4 files changed, 54 insertions(+)
>>> create mode 100644 drivers/virt/gunyah/Makefile
>>> create mode 100644 drivers/virt/gunyah/gunyah.c
>>>
>>> diff --git a/drivers/virt/Makefile b/drivers/virt/Makefile
>>> index 093674e05c40..92b7e5311548 100644
>>> --- a/drivers/virt/Makefile
>>> +++ b/drivers/virt/Makefile
>>> @@ -11,3 +11,4 @@ obj-$(CONFIG_NITRO_ENCLAVES) += nitro_enclaves/
>>> obj-$(CONFIG_ACRN_HSM) += acrn/
>>> obj-$(CONFIG_EFI_SECRET) += coco/efi_secret/
>>> obj-$(CONFIG_SEV_GUEST) += coco/sev-guest/
>>> +obj-y += gunyah/
>>> diff --git a/drivers/virt/gunyah/Makefile b/drivers/virt/gunyah/Makefile
>>> new file mode 100644
>>> index 000000000000..2ac4ee64b89d
>>> --- /dev/null
>>> +++ b/drivers/virt/gunyah/Makefile
>>> @@ -0,0 +1 @@
>>> +obj-$(CONFIG_GUNYAH) += gunyah.o
>>> diff --git a/drivers/virt/gunyah/gunyah.c b/drivers/virt/gunyah/gunyah.c
>>> new file mode 100644
>>> index 000000000000..c34c9046fc08
>>> --- /dev/null
>>> +++ b/drivers/virt/gunyah/gunyah.c
>>> @@ -0,0 +1,46 @@
>>> +// SPDX-License-Identifier: GPL-2.0-only
>>> +/*
>>> + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights
>>> reserved.
>>> + */
>>> +
>>> +#define pr_fmt(fmt) "gunyah: " fmt
>>> +
>>> +#include <linux/gunyah.h>
>>> +#include <linux/init.h>
>>> +#include <linux/module.h>
>>> +#include <linux/printk.h>
>>> +
>>> +struct gh_hypercall_hyp_identify_resp gunyah_api;
>>> +EXPORT_SYMBOL_GPL(gunyah_api);
>>> +
>>> +static const uint32_t gunyah_known_uuids[][4] = {
>>> + {0x19bd54bd, 0x0b37571b, 0x946f609b, 0x54539de6}, /*QC_HYP
>>> (Qualcomm's build) */
>>> + {0x673d5f14, 0x9265ce36, 0xa4535fdb, 0xc1d58fcd}, /*GUNYAH (open
>>> source build) */
>>> +};
>>> +
>>> +static int __init gunyah_init(void)
>>> +{
>>> + u32 uid[4];
>>> + int i;
>>> +
>>> + gh_hypercall_get_uid(uid);
>>> +
>>> + for (i = 0; i < ARRAY_SIZE(gunyah_known_uuids); i++)
>>> + if (!memcmp(uid, gunyah_known_uuids[i], sizeof(uid)))
>>> + break;
>>> +
>>> + if (i == ARRAY_SIZE(gunyah_known_uuids))
>>> + return -ENODEV;
>>> +
>>> + gh_hypercall_hyp_identify(&gunyah_api);
>>> +
>>> + pr_info("Running under Gunyah hypervisor %llx/v%u\n",
>>> + FIELD_GET(GH_API_INFO_VARIANT_MASK, gunyah_api.api_info),
>>> + gh_api_version());
>>> +
>>> + return 0;
>>> +}
>>> +arch_initcall(gunyah_init);
>>> +
>>> +MODULE_LICENSE("GPL");
>>> +MODULE_DESCRIPTION("Gunyah Hypervisor Driver");
>>> diff --git a/include/linux/gunyah.h b/include/linux/gunyah.h
>>> index 2765d2b40198..166156f69df9 100644
>>> --- a/include/linux/gunyah.h
>>> +++ b/include/linux/gunyah.h
>>> @@ -92,6 +92,12 @@ struct gh_hypercall_hyp_identify_resp {
>>> u64 api_info;
>>> u64 flags[3];
>>> };
>>> +extern struct gh_hypercall_hyp_identify_resp gunyah_api;
>>
>> Must this global variable be exposed? Can't you hide it--as
>> well as its interpretation--inside "gunyah.c"?
>> >> +
>>> +static inline u16 gh_api_version(void)
>>> +{
>>> + return FIELD_GET(GH_API_INFO_API_VERSION_MASK,
>>> gunyah_api.api_info);
>>> +}
>>
>> If you don't make the above function inline, can you hide the
>> definition of gunyah_api?
>>
>
> This seems like a good idea to me. I'm thinking to have the following
> functions:
>
> enum gh_api_feature {
> GH_API_FEATURE_DOORBELL,
> GH_API_FEATURE_MSGQUEUE,
> GH_API_FEATURE_VIC,
> GH_API_FEATURE_VPM,
> GH_API_FEATURE_VCPU,
> GH_API_FEATURE_MEMEXTENT,
> GH_API_FEATURE_TRACE_CTRL,
> };
My point wasn't necessarily to expose all of that. Already
"gunyah.h" defines the bit positions in which these things
are recorded in the identify response. My main point was
about (not) exposing that global variable.
I support your suggestion of abstracting the feature
interface that way, but I suggest waiting until it's
actually needed before you add it. I don't see any
code that uses GH_IDENTIFY_PARTITION_CSPACE (and the
rest) currently.
Oh, and interestingly enough I see GH_API_INFO_BIG_ENDIAN
defined in the API info...
-Alex
>
> u16 gh_api_version(void);
> bool gh_api_has_feature(enum gh_api_feature feature);
>
> Thanks,
> Elliot
On 1/10/23 11:56 AM, Elliot Berman wrote:
>> Is there any need for the endianness of these values to be specified?
>> Does Gunyah operate with a well-defined endianness? Is there any
>> chance a VM can run with an endianness different from Gunyah? I
>> see that the arm_smcc_* structures are defined without endianness.
>> (Sorry if these are dumb questions.)
>>
>
> All of the data transfers for hypercalls happen via registers, so
> endianness doesn't have impact here (there is no "low address" in a
> register).
I don't believe that is technically true. Practically speaking,
it's probably almost *always* little-endian. But for example,
here:
https://developer.arm.com/documentation/102376/0100/Alignment-and-endianness
it says:
Endianness
In Armv8-A, instruction fetches are always treated as
little-endian.
For data accesses, it is IMPLEMENTATION DEFINED whether
both little-endian and big-endian are supported. And if
only one is supported, it is IMPLEMENTATION DEFINED
which one is supported.
For processors that support both big-endian and
little-endian, endianness is configured per Exception
level.
Perhaps that last sentence doesn't apply to HVC exceptions
but to me it *sounds* like it's at least possible for a VM
to be running with an endianness that differs from the
hypervisor (perhaps not other VMs though.)
This is not an area of expertise of mine, so I would love
for someone who knows more to correct me if I'm wrong.
It's likely to be fine as-is, but (other than the work to
do it and get it right) it doesn't hurt to specify it and
do the conversions as data is passes to/from the hypervisor.
-Alex
On 1/10/23 12:16 PM, Elliot Berman wrote:
>>> + ret = gh_hypercall_msgq_send(msgq->tx_ghrsc->capid,
>>> msgq_data->length,
>>> + (uintptr_t)msgq_data->data, tx_flags, &ready);
>>> +
>>> + /**
>>> + * unlikely because Linux tracks state of msgq and should not
>>> try to
>>> + * send message when msgq is full.
>>> + */
>>
>> Is it just unlikely, or is it impossible?
>>
>
> This would require multiple mailbox controllers interacting with the
> same message queue.
>
> The only way I can think this is possible is if the Gunyah drivers are
> unloaded when the message queue is full; drivers are then re-loaded
> before the receiver processes the messages. The initial internal state
> of the message queue controller assumes that there is space in the
> message queue. We would get a Tx vIRQ once space becomes available and
> the message would then be attempted to sent again. Since there's a safe
> flow to recover from a inconsistent internal state and it's very
> unlikely to start in that state, I don't think we need to add calls to
> check if the message queue is full during initialization.
First, your explanation is appreciated but more than what I
was looking for... I just mentioned it because if it were
impossible, then there's no point in having this code handle
something that literally can't happen.
But as far as your explanation, I *hope* if the Gunyah drivers
are unloaded, everything gets fully cleaned up before that
completes. There should be no in-flight activity, or
any "previous generation" messages that could be processed.
In any case, I think my question is answered. Thanks.
-Alex
On 1/10/23 11:56 AM, Elliot Berman wrote:
>>> There are two calls to help identify Gunyah:
>>>
>>> 1. gh_hypercall_get_uid() returns a UID when running under a Gunyah
>>> hypervisor.
>>> 2. gh_hypercall_hyp_identify() returns build information and a set of
>>> feature flags that are supported by Gunyah.
>>
>> The first is a "service", while the second is a "hypercall".
>> Can you explain the distinction? The sentence at the top
>> refers to both as "hypercalls".
>>
>
> I learned more details about this to answer your question. "get_uid()"
> is a standardized call that is ARM_SMCCC_VENDOR_HYP_CALL_UID_FUNC_ID
> defined in include/arm-smccc.h. I'll use that.
You didn't really explain the distinction between hypercall
and service in Gunyah. Both are encoded as "vendor specific
hypervisor service calls" according to the SVCCC specification.
I haven't found where ARM_SMCCC_VENDOR_HYP_CALL_UID_FUNC_ID
gets handled, but I accept your answer that it's basically
a standard call. The "get UID" is the only one that's defined
as a Gunyah "service"; the rest are hypercalls.
It's not a big deal, I just noticed the difference and was
curious about it.
-Alex
On 1/10/23 3:47 PM, Elliot Berman wrote:
>>
>> I haven't looked at the earlier reviews; perhaps the RFC stuff
>> was requested. I'm sure it's useful to see that but it doesn't
>> seem directly helpful if your goal is to get this code upstream.
>>
>
> Right, the RFC patches were requested. Do you have a recommendation for
> sharing those later patches? I understand it's best practice not to post
> too many patches. The logical split was to have 1-20 go in first, and
> the remaining patches submitted later.
If they're RFC they should be tagged "RFC".
I do think it's easier for reviewers if you can divide up the
code into a few smaller series, so reviewing each (sub-)series
isn't such an overwhelming thing to consider. I've started
looking (IN GREAT DETAIL) the RPC core code, and haven't gone
much past that, so I don't have any guidance about how things
could be structured. (I do appreciate that the early patches
were small, and built things up gradually.)
I personally try to keep my series to closer to 5-10 patches,
though the maintainer(s) involved need to agree to accept the
smaller series before the full functionality gets enabled when
it's all accepted.
You'll get (lots) more feedback from me on the remaining patches,
eventually. If you decide to re-spin things soon I'd like to know
your plan so I can review the latest when it's available.
-Alex
On 12/19/22 4:58 PM, Elliot Berman wrote:
> The resource manager is a special virtual machine which is always
> running on a Gunyah system. It provides APIs for creating and destroying
> VMs, secure memory management, sharing/lending of memory between VMs,
> and setup of inter-VM communication. Calls to the resource manager are
> made via message queues.
I mentioned in an earlier message that I did a really thorough
review of this. Actually I haven't reviewed the interrupt part
yet, but I'll get to that. I want to get my comments out now.
I found one bug, but I'd guess it's not likely to occur (though
user space can trigger it so it's serious). You'll see it below.
> This patch implements the basic probing and RPC mechanism to make those
> API calls. Request/response calls can be made with gh_rm_call.
> Drivers can also register to notifications pushed by RM via
> gh_rm_register_notifier
Is there a human-readable document that explains the RPC
protocol and formats? (It would provide a way to compare
the implementation with the intended design.)
Here's what I see:
It appears that each sent message is delivered exactly once,
and messages arrive in order and are assumed to be intact.
A Gunyah message holds a 240 byte maximum payload, while a
resource manager RPC message (carried in a Gunyah message)
has a 232 byte maximum payload.
An RPC "connection" is either a notification message (only
*from* the resource manager) or a request/response message
pair sharing the same sequence ID (with the request only
going *to* the resource manager). An RPC "connection"
consists of a single RPC message (of any of the three types)
followed by 0-62 "fragment" messages, where the payload for
each message is concatenated by the receiver into a single
buffer (up to 63 * 232 = 14616 bytes in size).
An RPC header contains a sequence ID that's unique for
each active "connection" to the resource manager (and
ignored for notifications). It also contains a message
ID supplied by the user of the resource manager interface
(e.g., a VM); the message ID in a response must match the
message ID in its matching request.
> Specific API calls that resource manager supports will be implemented in
> subsequent patches.
>
> Signed-off-by: Elliot Berman <[email protected]>
> ---
> MAINTAINERS | 2 +-
> drivers/virt/gunyah/Kconfig | 14 +
> drivers/virt/gunyah/Makefile | 3 +
> drivers/virt/gunyah/rsc_mgr.c | 584 +++++++++++++++++++++++++++++++++
> drivers/virt/gunyah/rsc_mgr.h | 36 ++
> include/linux/gunyah_rsc_mgr.h | 18 +
> 6 files changed, 656 insertions(+), 1 deletion(-)
> create mode 100644 drivers/virt/gunyah/rsc_mgr.c
> create mode 100644 drivers/virt/gunyah/rsc_mgr.h
> create mode 100644 include/linux/gunyah_rsc_mgr.h
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 667480bfd387..bb315385e155 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -8944,7 +8944,7 @@ F: Documentation/virt/gunyah/
> F: arch/arm64/gunyah/
> F: drivers/mailbox/gunyah-msgq.c
> F: drivers/virt/gunyah/
> -F: include/linux/gunyah.h
> +F: include/linux/gunyah*.h
>
> HABANALABS PCI DRIVER
> M: Oded Gabbay <[email protected]>
> diff --git a/drivers/virt/gunyah/Kconfig b/drivers/virt/gunyah/Kconfig
> index 127156a678a6..4a6189dedab2 100644
> --- a/drivers/virt/gunyah/Kconfig
> +++ b/drivers/virt/gunyah/Kconfig
> @@ -10,3 +10,17 @@ config GUNYAH
>
> Say Y/M here to enable the drivers needed to interact in a Gunyah
> virtual environment.
> +
> +if GUNYAH
> +config GUNYAH_RESOURCE_MANAGER
Is Gunyah useful without the resource manager?
There might be good reasons to have different kernel config
options for parts of Gunyah (like, perhaps you want to allow
a *different* resource manager to be used). But if, overall,
a component is fundamentally required for Gunyah to operate,
I don't think it's helpful to configure it separately.
This comment applies to all your config options. I won't
mention it again, but maybe you can consider it, and for
anything that's pretty fundamental, don't make it an option.
> + tristate "Gunyah Resource Manager"
> + depends on MAILBOX
> + select GUNYAH_MESSAGE_QUEUES
> + help
> + The resource manager (RM) is a privileged application VM supporting
> + the Gunyah Hypervisor. Enable this driver to support communicating
> + with Gunyah RM. This is typically required for a VM running under
> + Gunyah wanting to have Gunyah-awareness.
> +
> + Say Y/M here if unsure.
> +endif
> diff --git a/drivers/virt/gunyah/Makefile b/drivers/virt/gunyah/Makefile
> index 2ac4ee64b89d..e7cf59b9e64e 100644
> --- a/drivers/virt/gunyah/Makefile
> +++ b/drivers/virt/gunyah/Makefile
> @@ -1 +1,4 @@
> obj-$(CONFIG_GUNYAH) += gunyah.o
> +
> +gunyah_rsc_mgr-y += rsc_mgr.o
> +obj-$(CONFIG_GUNYAH_RESOURCE_MANAGER) += gunyah_rsc_mgr.o
> diff --git a/drivers/virt/gunyah/rsc_mgr.c b/drivers/virt/gunyah/rsc_mgr.c
> new file mode 100644
> index 000000000000..6b281576f5af
> --- /dev/null
> +++ b/drivers/virt/gunyah/rsc_mgr.c
> @@ -0,0 +1,584 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
> + */
> +
> +#include <linux/of.h>
> +#include <linux/slab.h>
> +#include <linux/mutex.h>
> +#include <linux/sched.h>
> +#include <linux/gunyah.h>
> +#include <linux/module.h>
> +#include <linux/of_irq.h>
> +#include <linux/kthread.h>
> +#include <linux/notifier.h>
> +#include <linux/workqueue.h>
> +#include <linux/completion.h>
> +#include <linux/gunyah_rsc_mgr.h>
> +#include <linux/platform_device.h>
> +
> +#include "rsc_mgr.h"
> +
> +/* Resource Manager Header */
> +struct gh_rm_rpc_hdr {
This is a style thing, but I prefer *not* having struct
definitions with related value definitions interleaved.
My preference would be to define the values above the
struct definitions. A comment can indicate which field
a given group of definitions is associated with. And
if it's ambiguous, the symbol names can further tie
them together.
> +#define RM_RPC_HDR_VERSION_ONE 0x1
I don't think HEADER_VERSION_ONE is any more understandable
than just "1" if that's its value. Unless you're envisioning
a more complex versioning scheme, using 1, 2, 3 (etc.) for
the versions is cleaner than VERSION_ONE, VERSION_TWO,
VERSION_THREE, etc.
> +#define RM_RPC_API_VERSION_MASK GENMASK(3, 0)
> +
> +#define RM_RPC_HDR_WORDS 0x2
Here's another case where it might just be simpler to open-code
the numeric value in the code. Even better, I believe this
represents the number of words in an RPC header. That is better
expressed as:
(sizeof(struct gh_rm_rpc_hdr) / sizeof(u32))
At this point it seems there *is* only one version. Even so,
the point of this (currently anyway) is to define the Gunyah
RPC API that *this driver* supports. That doesn't change for
any message header, so maybe you could just define something
like this:
#define RM_RPC_API_VERSION FIELD_PREP(RM_RPC_API_VERSION_MASK, 1)
#define RM_RPC_HDR_WORDS FIELD_PREP(RM_RPC_HEADER_WORDS_MASK,
(sizeof(struct gh_rm_rpc_hdr) /
sizeof(u32))
#define RM_RPC_API (RM_RPC_API_VERSION | RM_RPC_HDR_WORDS)
Then you can use RM_RPC_API in gh_rm_send_request() to fill in
the api field of the RPC message header. You could also check
it in gh_rm_msgq_rx_data() (something you don't currently do).
This might look a little ugly, but it simplifies the code where
it's used.
> +#define RM_RPC_HEADER_WORDS_MASK GENMASK(7, 4)
> + u8 api;
> +
> +#define RM_RPC_TYPE_CONT 0x0
> +#define RM_RPC_TYPE_REQ 0x1
s/REQ/REQUEST/ (maybe)
Also there is a missing tab between the name and its value.
> +#define RM_RPC_TYPE_RPLY 0x2
s/RPLY/REPLY/
> +#define RM_RPC_TYPE_NOTIF 0x3
> +#define RM_RPC_TYPE_MASK GENMASK(1, 0)
> +
> +#define GH_RM_MAX_NUM_FRAGMENTS 62
> +#define RM_RPC_FRAGMENTS_MASK GENMASK(7, 2)
> + u8 type;
> + __le16 seq;
> + __le32 msg_id;
> +} __packed;
> +
> +/* Standard reply header */
> +struct gh_rm_rpc_reply_hdr {
> + struct gh_rm_rpc_hdr rpc_hdr;
> + u32 err_code;
Why is endianness represented in the sequence numbe r and
message id in the header, but not in the error code here?
Is the error code set by local software or something?
The values that can land in err_code are defined in
"rsc_mgr.h" defined at the end of this patch. Some
examples are GH_RM_ERROR_OK and GH_RM_ERROR_NOMEM.
Please indicate that somehow, for example:
u32 err_code; /* GH_RM_ERROR_* */
These values are (almost) always positive. But I see
two problems:
- If non-zero, this value is returned by gh_rm_call(), and
that function is expected to return a *negative* value
on error.
- RM_ERROR_UNIMPLEMENTED is defined as 0xffffffff; that
value, when interpreted as a signed int, is -1, which
if interpreted as an errno is EPERM. That is at best
a misleading result if returned by gh_rm_call().
> +} __packed;
> +
> +#define GH_RM_MAX_MSG_SIZE (GH_MSGQ_MAX_MSG_SIZE - sizeof(struct gh_rm_rpc_hdr))
> +
> +/**
> + * struct gh_rm_connection - Represents a complete message from resource manager
> + * @payload: Combined payload of all the fragments (msg headers stripped off).
> + * @size: Size of the payload.
The size is the number of bytes of payload received *so far* (in
case there are fragments). It is not the size of the payload
until it has been completely received.
> + * @ret: Linux return code, set in case there was an error processing connection
> + * @msg_id: Message ID from the header.
> + * @type: RM_RPC_TYPE_RPLY or RM_RPC_TYPE_NOTIF.
> + * @num_fragments: total number of fragments expected to be received.
> + * @fragments_received: fragments received so far.
> + * @rm_error: For request/reply sequences with standard replies.
> + * @seq: Sequence ID for the main message.
> + * @seq_done: Signals caller that the RM reply has been received
> + */
> +struct gh_rm_connection {
> + void *payload;
> + size_t size;
> + int ret;
> + u32 msg_id;
The msg_id field is actually opaque from this driver's perspective.
The only thing that matters is if a response message ID *matches*
a request message ID. It could be maintained in __le32 byte order
(as received).
> + u8 type;
> +
> + u8 num_fragments;
> + u8 fragments_received;
> +
The next three fields are (as the comment says) only used for
request/reply sequences. You could put them in a structure,
then have that structure share space in a union with a work
structure used for notification messages. That could replace
struct gh_rm_notif_complete (which would become unnecessary).
> + /* only for req/reply sequence */
> + u32 rm_error;
> + u16 seq;
> + struct completion seq_done;
> +};
> +
> +struct gh_rm_notif_complete {
> + struct gh_rm_connection *conn;
> + struct work_struct work;
> +};
> +
> +struct gh_rm_rpc {
I actually think this structure should just be called "gh_rm".
Yes, it's the RPC interface for the resource manager, but this
interface (which happens to use RPC) abstractly represents the
resource manager *itself*.
> + struct device *dev;
> + struct gunyah_resource tx_ghrsc, rx_ghrsc;
> + struct gh_msgq msgq;
> + struct mbox_client msgq_client;
> + struct gh_rm_connection *active_rx_connection;
> + int last_tx_ret;
> +
> + struct idr call_idr;
> + struct mutex call_idr_lock;
> +
> + struct mutex send_lock;
> +
> + struct work_struct recv_work;
> +};
> +
> +static struct gh_rm_connection *gh_rm_alloc_connection(u32 msg_id, u8 type)
> +{
> + struct gh_rm_connection *connection;
> +
> + connection = kzalloc(sizeof(*connection), GFP_KERNEL);
> + if (!connection)
> + return NULL;
> +
> + connection->type = type;
> + connection->msg_id = msg_id;
> +
> + return connection;
> +}
> +
> +static int gh_rm_init_connection_payload(struct gh_rm_connection *connection, void *msg,
> + size_t hdr_size, size_t msg_size)
> +{
> + struct gh_rm_rpc_hdr *hdr = msg;
> + size_t max_buf_size, payload_size;
> +
> + if (hdr_size > msg_size)
> + return -EINVAL;
> +
> + payload_size = msg_size - hdr_size;
The maximum payload size is 14616 bytes. The msg_size comes
from the resource manager--which I guess is trustworthy. But
you're already checking for a short message above, so you
might as well check for a message that's too big as well.
(Or just decide not to check either...)
> + connection->num_fragments = FIELD_GET(RM_RPC_FRAGMENTS_MASK, hdr->type);
Here too, num_fragments had better not be more than
GH_RM_MAX_NUM_FRAGMENTS (= 62).
> + connection->fragments_received = 0;
> +
> + /* There's not going to be any payload, no need to allocate buffer. */
> + if (!payload_size && !connection->num_fragments)
> + return 0;
> +
> + /*
> + * maximum payload size is GH_MSGQ_MAX_MSG_SIZE - hdr_size
> + * and can received (hdr->fragments + 1) of those
> + */
> + max_buf_size = (GH_MSGQ_MAX_MSG_SIZE - hdr_size) * (connection->num_fragments + 1);
> +
> + connection->payload = kzalloc(max_buf_size, GFP_KERNEL);
> + if (!connection->payload)
> + return -ENOMEM;
> +
> + memcpy(connection->payload, msg + hdr_size, payload_size);
> + connection->size = payload_size;
> + return 0;
> +}
> +
> +static void gh_rm_notif_work(struct work_struct *work)
> +{
> + struct gh_rm_notif_complete *notif = container_of(work, struct gh_rm_notif_complete, work);
> + struct gh_rm_connection *connection = notif->conn;
> +
> + /* No users of notifications, yet. */
> +
> + kfree(connection->payload);
> + kfree(connection);
> + kfree(notif);
> +}
> +
> +static struct gh_rm_connection *gh_rm_process_notif(struct gh_rm_rpc *rsc_mgr,
Here you use "rsc_mgr" as the symbol having this type, while
elsewhere you use "rm". Pick one, and use it consistenly (I
suggest using "rm" to represent a "struct gh_rm").
> + void *msg, size_t msg_size)
> +{
> + struct gh_rm_rpc_hdr *hdr = msg;
> + struct gh_rm_connection *connection;
> +
> + connection = gh_rm_alloc_connection(le32_to_cpu(hdr->msg_id),
We already know the header type is RM_RPC_TYPE_NOTIF. Better
to state that explicitly here rather than decoding what's in
the header (again).
> + FIELD_GET(RM_RPC_TYPE_MASK, hdr->type));
> + if (!connection) {
> + dev_err(rsc_mgr->dev, "Failed to alloc connection for notification, dropping.\n");
> + return NULL;
> + }
> +
> + if (gh_rm_init_connection_payload(connection, msg, sizeof(*hdr), msg_size)) {
This could conceivably return -EINVAL as well (which is not an
allocation failure, despite what the message says). The same
thing is done in gh_rm_process_rply(). (If the resource manager
is as trusted as it should be, and the hardware is not broken,
-EINVAL should never get returned...)
> + dev_err(rsc_mgr->dev, "Failed to alloc connection buffer for notification, dropping.\n");
> + kfree(connection);
> + return NULL;
> + }
> +
> + return connection;
> +}
> +
> +static struct gh_rm_connection *gh_rm_process_rply(struct gh_rm_rpc *rsc_mgr,
> + void *msg, size_t msg_size)
> +{
> + struct gh_rm_rpc_reply_hdr *reply_hdr = msg;
> + struct gh_rm_rpc_hdr *hdr = msg;
Please use:
struct gh_rm_rpc_hdr *hdr = &msg->rpc_hdr;
> + struct gh_rm_connection *connection;
> + u16 seq_id = le16_to_cpu(hdr->seq);
> +
> + if (mutex_lock_interruptible(&rsc_mgr->call_idr_lock))
> + return ERR_PTR(-ERESTARTSYS);
THIS IS A BUG.
If this call gets interrupted, we'll return the ERESTARTSYS pointer.
That's non-null.
But the return value from gh_rm_process_rply() is blindly assigned
to the rsc_mgr->active_rx_connection pointer in gh_rm_msgq_rx_data()
if the RPC message type is RM_RPC_TYPE_RPLY.
Thereafter, if that pointer is non-null, it's assumed to refer
to a valid memory location.
I'm not sure what the right handling is. You could save the
return value and check it with IS_ERR_OR_NULL(). Perhaps you
could return NULL here too, but it's actually not clear to me
you can simply ignore this situation and have the result be
correct.
> +
> + connection = idr_find(&rsc_mgr->call_idr, seq_id);
> + mutex_unlock(&rsc_mgr->call_idr_lock);
> +
> + if (!connection) {
> + dev_err(rsc_mgr->dev, "Failed to find connection for sequence %u\n", seq_id);
> + return NULL;
> + }
> + if (connection->msg_id != le32_to_cpu(hdr->msg_id)) {
> + dev_err(rsc_mgr->dev, "Reply for sequence %u expected msg_id: %x but got %x\n",
> + seq_id, connection->msg_id, le32_to_cpu(hdr->msg_id));
> + /*
> + * Don't complete connection and error the client, maybe
> + * resource manager will send us the expected reply sequence soon.
> + */
> + return NULL;
> + }
> +
> + if (gh_rm_init_connection_payload(connection, msg, sizeof(*reply_hdr), msg_size)) {
> + dev_err(rsc_mgr->dev, "Failed to alloc connection buffer for sequence %d\n",
> + seq_id);
> + /* Send connection complete and error the client. */
> + connection->ret = -ENOMEM;
> + complete(&connection->seq_done);
> + return NULL;
> + }
> +
> + connection->rm_error = reply_hdr->err_code;
> + return connection;
Since you're already passing in the resource manager as an argument,
you could take care of assigning its ->connection pointer here as well.
Doing it this way *might* clean up the caller a bit (but sometimes
you have to try it to see).
> +}
> +
> +static void gh_rm_process_cont(struct gh_rm_rpc *rsc_mgr, struct gh_rm_connection *connection,
> + void *msg, size_t msg_size)
> +{
> + struct gh_rm_rpc_hdr *hdr = msg;
> + size_t payload_size = msg_size - sizeof(*hdr);
Maybe:
void *payload = hdr + 1;
Here too, the message size is coming from the resource manager.
But it seems prudent to ensure msg_size is >= sizeof(*hdr)
and <= GH_MSGQ_MAXMSG_SIZE.
> +
> + /*
> + * hdr->fragments and hdr->msg_id preserves the value from first reply
> + * or notif message. To detect mishandling, check it's still intact.
> + */
> + if (connection->msg_id != le32_to_cpu(hdr->msg_id))
> + dev_err(rsc_mgr->dev, "Appending mismatched continuation with id %d to connection with id %d\n",
> + le32_to_cpu(hdr->msg_id), connection->msg_id);
> + if (connection->num_fragments != FIELD_GET(RM_RPC_FRAGMENTS_MASK, hdr->type))
> + dev_err(rsc_mgr->dev, "Number of fragments mismatch for seq: %d\n",
> + le16_to_cpu(hdr->seq));
The sequencer ID is irrelevant on a notification "connection".
Maybe you should report the message type too?
You copy the contents into the receive buffer anyway?
I think you should drop the remainder of the "connection"
if you see *any* corruption like this. (I would argue
this is a bug as well, though maybe it "never happens.")
> +
> + memcpy(connection->payload + connection->size, msg + sizeof(*hdr), payload_size);
> + connection->size += payload_size;
> + connection->fragments_received++;
> +}
> +
> +static bool gh_rm_complete_connection(struct gh_rm_rpc *rsc_mgr,
> + struct gh_rm_connection *connection)
This is called in one place, and it is always passed the active RX
connection pointer. If true is returned, that pointer is nulled.
You could drop the second argument and just do the assignment
here instead. (With the above-mentioned bug, this is also a
place where the non-null ERESTARTSYS pointer gets dererferenced.)
> +{
> + struct gh_rm_notif_complete *notif_work;
> +
> + if (!connection)
> + return false;
> +
> + if (connection->fragments_received != connection->num_fragments)
> + return false;
> +
> + switch (connection->type) {
> + case RM_RPC_TYPE_RPLY:
> + complete(&connection->seq_done);
> + break;
> + case RM_RPC_TYPE_NOTIF:
> + notif_work = kzalloc(sizeof(*notif_work), GFP_KERNEL);
> + if (notif_work == NULL)
> + break;
Is there any signficant consequence possible from quietly
ignoring a notification from the resource manager? Should
this situation be reported at least?
If you embedded the work structure in the connection structure
as I mentioned earlier, you could avoid this ever being a
problem.
> +
> + notif_work->conn = connection;
> + INIT_WORK(¬if_work->work, gh_rm_notif_work);
> +
> + schedule_work(¬if_work->work);
> + break;
> + default:
> + dev_err(rsc_mgr->dev, "Invalid message type (%d) received\n", connection->type);
> + break;
> + }
> +
> + return true;
> +}
> +
> +static void gh_rm_abort_connection(struct gh_rm_connection *connection)
Although both callers immediately overwrite the rm->active_rx_connection
pointer after this call, you could pass the resource manager pointer
and make it NULL after the abort, so the old pointer is *gone*.
> +{
> + switch (connection->type) {
> + case RM_RPC_TYPE_RPLY:
> + connection->ret = -EIO;
> + complete(&connection->seq_done);
> + break;
> + case RM_RPC_TYPE_NOTIF:
> + fallthrough;
The case statement isn't really valuable here. You could
just do:
if (connection->type == RM_RPC_TYPE_RPLY) {
... complete
} else {
... free
}
(Fine as-is though.)
> + default:
> + kfree(connection->payload);
> + kfree(connection);
> + }
> +}
> +
> +static void gh_rm_msgq_rx_data(struct mbox_client *cl, void *mssg)
> +{
> + struct gh_rm_rpc *rsc_mgr = container_of(cl, struct gh_rm_rpc, msgq_client);
> + struct gh_msgq_rx_data *rx_data = mssg;
> + void *msg = rx_data->data;
> + size_t msg_size = rx_data->length;
> + struct gh_rm_rpc_hdr *hdr;
> +
> + if (msg_size <= sizeof(struct gh_rm_rpc_hdr)) {
> + dev_err(rsc_mgr->dev, "Incomplete message size: %ld is too small\n", msg_size);
> + return;
> + }
This is received data, so as long as you're verifying the message
size you should also ensure it's within the maximum allowed. You
should also verify the header api byte is correct.
> +
> + hdr = msg;
> + switch (FIELD_GET(RM_RPC_TYPE_MASK, hdr->type)) {
> + case RM_RPC_TYPE_NOTIF:
Here and in the other cases below, I think it's cleaner if you
move the check of the active_rx_connection poiner (whether it's
NULL or not) into the called functions. That way they're all
checking based on their own requirements rather than having
that logic be here.
> + if (rsc_mgr->active_rx_connection) {
> + /* Not possible per protocol. Do something better than BUG_ON */
> + dev_err(rsc_mgr->dev, "Received start of new notification without finishing existing message series.\n");
> + gh_rm_abort_connection(rsc_mgr->active_rx_connection);
> + }
> + rsc_mgr->active_rx_connection = gh_rm_process_notif(rsc_mgr, msg, msg_size);
> + break;
> + case RM_RPC_TYPE_RPLY:
> + if (rsc_mgr->active_rx_connection) {
> + /* Not possible per protocol. Do something better than BUG_ON */
> + dev_err(rsc_mgr->dev, "Received start of new reply without finishing existing message series.\n");
> + gh_rm_abort_connection(rsc_mgr->active_rx_connection);
> + }
> + rsc_mgr->active_rx_connection = gh_rm_process_rply(rsc_mgr, msg, msg_size);
> + break;
> + case RM_RPC_TYPE_CONT:
> + if (!rsc_mgr->active_rx_connection) {
> + dev_err(rsc_mgr->dev, "Received a continuation message without receiving initial message\n");
> + break;
> + }
> + gh_rm_process_cont(rsc_mgr, rsc_mgr->active_rx_connection, msg, msg_size);
> + break;
> + default:
> + dev_err(rsc_mgr->dev, "Invalid message type (%lu) received\n",
> + FIELD_GET(RM_RPC_TYPE_MASK, hdr->type));
> + return;
> + }
> +
> + if (gh_rm_complete_connection(rsc_mgr, rsc_mgr->active_rx_connection))
> + rsc_mgr->active_rx_connection = NULL;
> +}
> +
> +static void gh_rm_msgq_tx_done(struct mbox_client *cl, void *mssg, int r)
> +{
> + struct gh_rm_rpc *rsc_mgr = container_of(cl, struct gh_rm_rpc, msgq_client);
> +
> + kfree(mssg);
> + rsc_mgr->last_tx_ret = r;
> +}
> +
It is allowable to pass a null req_buff pointer here. The handling in
that case is a little different, and I think it's important to both
state that a null pointer is OK, and to explain what it means (or
how its processing is different).
> +static int gh_rm_send_request(struct gh_rm_rpc *rsc_mgr, u32 message_id,
> + const void *req_buff, size_t req_buff_size,
> + struct gh_rm_connection *connection)
> +{
> + size_t buff_size_remaining = req_buff_size;
> + const void *req_buff_curr = req_buff;
> + struct gh_rm_rpc_hdr *hdr;
> + u32 cont_fragments = 0;
> + size_t payload_size;
> + struct gh_msgq_tx_data *msg;
> + int i, ret;
> +
> + if (req_buff_size)
> + cont_fragments = (req_buff_size - 1) / GH_RM_MAX_MSG_SIZE;
> +
> + if (WARN(cont_fragments > GH_RM_MAX_NUM_FRAGMENTS,
> + "Limit exceeded for the number of fragments: %u\n", cont_fragments))
> + return -E2BIG;
You could just do:
if (WARN(req_buff_size > GH_RM_MAX_MSG_SIZE * GHRM_MAX_NUM_FRAGMENTS,
...
And then your loop below could just do:
u8 type = RM_RPC_TYPE_REQ;
while (req_buff_size) {
payload_size = max(buff_size_remaining,
GH_RM_MAX_MSG_SIZE);
req_buff_size -= payload_size;
...
msg->push = !req_buff_size;
...
type = RM_RPC_TYPE_CONT;
}
> +
> + ret = mutex_lock_interruptible(&rsc_mgr->send_lock);
> + if (ret)
> + return ret;
> +
> + /* Consider also the 'request' packet for the loop count */
> + for (i = 0; i <= cont_fragments; i++) {
> + if (buff_size_remaining > GH_RM_MAX_MSG_SIZE) {
> + payload_size = GH_RM_MAX_MSG_SIZE;
> + buff_size_remaining -= payload_size;
> + } else {
> + payload_size = buff_size_remaining;
> + }
> +
> + msg = kzalloc(sizeof(*msg) + GH_MSGQ_MAX_MSG_SIZE, GFP_KERNEL);
Use:
msg = kzalloc(sizeof(*msg) + payload_size, GFP_KERNEL);
But even better:
msg = kzalloc(struct_size(msg, data, payload_size), GFP_KERNEL);
OR... since these buffers are always 256 bytes apiece, you could
create a slab cache so they're allocated efficiently.
> + if (!msg) {
> + ret = -ENOMEM;
> + goto out;
> + }
> +
> + /* Fill header */
> + hdr = (struct gh_rm_rpc_hdr *)msg->data;
> + hdr->api = FIELD_PREP(RM_RPC_API_VERSION_MASK, RM_RPC_HDR_VERSION_ONE) |
> + FIELD_PREP(RM_RPC_HEADER_WORDS_MASK, RM_RPC_HDR_WORDS);
I suggested defining this api value at the top, since it's
constant for a given build.
> + hdr->type = FIELD_PREP(RM_RPC_TYPE_MASK,
> + i == 0 ? RM_RPC_TYPE_REQ : RM_RPC_TYPE_CONT) |
> + FIELD_PREP(RM_RPC_FRAGMENTS_MASK, cont_fragments);
I think FIELD_PREP() (and u32_encode_bits()) can look a
little cumbersome. I think this would be more readable:
u8 type = i ? RM_RPC_TYPE_CONT : RM_RPC_TYPE_REQ;
hdr->type = FIELD_PREP(RM_RPC_TYPE_MASK, type);
hdr->type |= FIELD_PREP(RM_RPC_FRAGMENTS_MASK, cont_fragments);
> + hdr->seq = cpu_to_le16(connection->seq);
> + hdr->msg_id = cpu_to_le32(message_id);
> +
> + /* Copy payload */
Maybe:
void *payload = hdr + 1;
...
memcpy(payload, req_buff_curr, payload_size);
> + memcpy(msg->data + sizeof(*hdr), req_buff_curr, payload_size);
> + req_buff_curr += payload_size;
> +
> + /* Force the last fragment to immediately alert the receiver */
> + msg->push = i == cont_fragments;
> + msg->length = sizeof(*hdr) + payload_size;
> +
> + ret = mbox_send_message(gh_msgq_chan(&rsc_mgr->msgq), msg);
> + if (ret < 0) {
> + kfree(msg);
> + break;
> + }
> +
> + if (rsc_mgr->last_tx_ret) {
> + ret = rsc_mgr->last_tx_ret;
> + break;
> + }
> + }
> +
> +out:
> + mutex_unlock(&rsc_mgr->send_lock);
> + return ret < 0 ? ret : 0;
> +}
> +
> +/**
> + * gh_rm_call: Achieve request-response type communication with RPC
* @rsc_mgr: ...
> + * @message_id: The RM RPC message-id
> + * @req_buff: Request buffer that contains the payload
> + * @req_buff_size: Total size of the payload
> + * @resp_buf: Pointer to a response buffer
> + * @resp_buff_size: Size of the response buffer
> + *
> + * Make a request to the RM-VM and wait for reply back. For a successful
> + * response, the function returns the payload. The size of the payload is set in
> + * resp_buff_size. The resp_buf should be freed by the caller.
> + *
> + * Context: Process context. Will sleep waiting for reply.
> + * Return: >0 is standard reply error from RM. <0 on internal error.
> + */
> +int gh_rm_call(struct gh_rm_rpc *rsc_mgr, u32 message_id, void *req_buff, size_t req_buff_size,
> + void **resp_buf, size_t *resp_buff_size)
> +{
> + struct gh_rm_connection *connection;
> + int ret;
> +
> + /* messaged_id 0 is reserved */
> + if (!message_id)
> + return -EINVAL;
> +
Maybe check for null rsc_mgr first.
> + if (!rsc_mgr)
> + return -EPROBE_DEFER;
> +
> + connection = gh_rm_alloc_connection(message_id, RM_RPC_TYPE_RPLY);
> + if (!connection)
> + return -ENOMEM;
> +
> + init_completion(&connection->seq_done);
> +
> + /* Allocate a new seq number for this connection */
> + if (mutex_lock_interruptible(&rsc_mgr->call_idr_lock)) {
Maybe use the goto ... pattern here.
> + kfree(connection);
> + return -ERESTARTSYS;
> + }
> + connection->seq = idr_alloc_cyclic(&rsc_mgr->call_idr, connection, 0, U16_MAX, GFP_KERNEL);
Note that idr_alloc_cyclic() *can* return an error.
> + mutex_unlock(&rsc_mgr->call_idr_lock);
> +
> + /* Send the request to the Resource Manager */
> + ret = gh_rm_send_request(rsc_mgr, message_id, req_buff, req_buff_size, connection);
> + if (ret < 0)
> + goto out;
> +
> + /* Wait for response */
> + ret = wait_for_completion_interruptible(&connection->seq_done);
> + if (ret)
> + goto out;
> +
> + if (connection->ret) {
> + ret = connection->ret;
I note that this is a negative errno. (You do explain
that non-zero return means error above.)
> + kfree(connection->payload);
> + goto out;
> + }
> +
> + if (connection->rm_error) {
> + ret = connection->rm_error;
While this is positive resource manager error (with one exception
I mentioned earlier).
> + kfree(connection->payload);
> + goto out;
> + }
> +
> + *resp_buf = connection->payload;
> + *resp_buff_size = connection->size;
> +
> +out:
> + mutex_lock(&rsc_mgr->call_idr_lock);
Why is the mutex_lock() here not interruptible? Or rather, if
you can do a non-interruptible wait here, why not above?
> + idr_remove(&rsc_mgr->call_idr, connection->seq);
> + mutex_unlock(&rsc_mgr->call_idr_lock);
> +
> + kfree(connection);
> + return ret;
> +}
> +
> +static int gh_msgq_platform_probe_direction(struct platform_device *pdev,
> + u8 gh_type, int idx, struct gunyah_resource *ghrsc)
I think I might call this gh_msgq_platform_probe_msgq() and pass a
Boolean indicating whether it's being called for the RX (versus TX)
message queue. Then deduce the gh_type and the index from that
argument.
-Alex
> +{
> + int ret;
> + struct device_node *node = pdev->dev.of_node;
> +
> + ghrsc->type = gh_type;
> +
> + ghrsc->irq = platform_get_irq(pdev, idx);
> + if (ghrsc->irq < 0) {
> + dev_err(&pdev->dev, "Failed to get irq%d: %d\n", idx, ghrsc->irq);
> + return ghrsc->irq;
> + }
> +
> + ret = of_property_read_u64_index(node, "reg", idx, &ghrsc->capid);
> + if (ret) {
> + dev_err(&pdev->dev, "Failed to get capid%d: %d\n", idx, ret);
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static int gh_rm_drv_probe(struct platform_device *pdev)
> +{
> + struct gh_rm_rpc *rsc_mgr;
> + int ret;
> +
> + rsc_mgr = devm_kzalloc(&pdev->dev, sizeof(*rsc_mgr), GFP_KERNEL);
> + if (!rsc_mgr)
> + return -ENOMEM;
> +
> + platform_set_drvdata(pdev, rsc_mgr);
> + rsc_mgr->dev = &pdev->dev;
> +
> + mutex_init(&rsc_mgr->call_idr_lock);
> + idr_init(&rsc_mgr->call_idr);
> + mutex_init(&rsc_mgr->send_lock);
> +
> + ret = gh_msgq_platform_probe_direction(pdev, GUNYAH_RESOURCE_TYPE_MSGQ_TX, 0,
> + &rsc_mgr->tx_ghrsc);
> + if (ret)
> + return ret;
> +
> + ret = gh_msgq_platform_probe_direction(pdev, GUNYAH_RESOURCE_TYPE_MSGQ_RX, 1,
> + &rsc_mgr->rx_ghrsc);
> + if (ret)
> + return ret;
> +
> + rsc_mgr->msgq_client.dev = &pdev->dev;
> + rsc_mgr->msgq_client.tx_block = true;
> + rsc_mgr->msgq_client.rx_callback = gh_rm_msgq_rx_data;
> + rsc_mgr->msgq_client.tx_done = gh_rm_msgq_tx_done;
> +
> + return gh_msgq_init(&pdev->dev, &rsc_mgr->msgq, &rsc_mgr->msgq_client,
> + &rsc_mgr->tx_ghrsc, &rsc_mgr->rx_ghrsc);
> +}
> +
> +static int gh_rm_drv_remove(struct platform_device *pdev)
> +{
> + struct gh_rm_rpc *rm = platform_get_drvdata(pdev);
> +
> + mbox_free_channel(gh_msgq_chan(&rm->msgq));
> + gh_msgq_remove(&rm->msgq);
> +
> + return 0;
> +}
> +
> +static const struct of_device_id gh_rm_of_match[] = {
> + { .compatible = "gunyah-resource-manager" },
> + {}
> +};
> +MODULE_DEVICE_TABLE(of, gh_rm_of_match);
> +
> +static struct platform_driver gh_rm_driver = {
> + .probe = gh_rm_drv_probe,
> + .remove = gh_rm_drv_remove,
> + .driver = {
> + .name = "gh_rsc_mgr",
> + .of_match_table = gh_rm_of_match,
> + },
> +};
> +module_platform_driver(gh_rm_driver);
> +
> +MODULE_LICENSE("GPL");
> +MODULE_DESCRIPTION("Gunyah Resource Manager Driver");
> diff --git a/drivers/virt/gunyah/rsc_mgr.h b/drivers/virt/gunyah/rsc_mgr.h
> new file mode 100644
> index 000000000000..b5bb36a7a4cc
> --- /dev/null
> +++ b/drivers/virt/gunyah/rsc_mgr.h
> @@ -0,0 +1,36 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
> + */
> +#ifndef __GH_RSC_MGR_PRIV_H
> +#define __GH_RSC_MGR_PRIV_H
> +
> +#include <linux/gunyah.h>
> +#include <linux/types.h>
> +
> +/* RM Error codes */
> +#define GH_RM_ERROR_OK 0x0
> +#define GH_RM_ERROR_UNIMPLEMENTED 0xFFFFFFFF
> +#define GH_RM_ERROR_NOMEM 0x1
> +#define GH_RM_ERROR_NORESOURCE 0x2
> +#define GH_RM_ERROR_DENIED 0x3
> +#define GH_RM_ERROR_INVALID 0x4
> +#define GH_RM_ERROR_BUSY 0x5
> +#define GH_RM_ERROR_ARGUMENT_INVALID 0x6
> +#define GH_RM_ERROR_HANDLE_INVALID 0x7
> +#define GH_RM_ERROR_VALIDATE_FAILED 0x8
> +#define GH_RM_ERROR_MAP_FAILED 0x9
> +#define GH_RM_ERROR_MEM_INVALID 0xA
> +#define GH_RM_ERROR_MEM_INUSE 0xB
> +#define GH_RM_ERROR_MEM_RELEASED 0xC
> +#define GH_RM_ERROR_VMID_INVALID 0xD
> +#define GH_RM_ERROR_LOOKUP_FAILED 0xE
> +#define GH_RM_ERROR_IRQ_INVALID 0xF
> +#define GH_RM_ERROR_IRQ_INUSE 0x10
> +#define GH_RM_ERROR_IRQ_RELEASED 0x11
> +
> +struct gh_rm_rpc;
> +int gh_rm_call(struct gh_rm_rpc *rsc_mgr, u32 message_id, void *req_buff, size_t req_buff_size,
> + void **resp_buf, size_t *resp_buff_size);
> +
> +#endif
> diff --git a/include/linux/gunyah_rsc_mgr.h b/include/linux/gunyah_rsc_mgr.h
> new file mode 100644
> index 000000000000..b4f55c19954b
> --- /dev/null
> +++ b/include/linux/gunyah_rsc_mgr.h
> @@ -0,0 +1,18 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
> + */
> +
> +#ifndef _GUNYAH_RSC_MGR_H
> +#define _GUNYAH_RSC_MGR_H
> +
> +#include <linux/list.h>
> +#include <linux/notifier.h>
> +#include <linux/gunyah.h>
> +
> +#define GH_VMID_INVAL U16_MAX
> +
> +/* Gunyah recognizes VMID0 as an alias to the current VM's ID */
> +#define GH_VMID_SELF 0
> +
> +#endif
On 1/17/2023 11:20 AM, Alex Elder wrote:
> On 1/10/23 11:56 AM, Elliot Berman wrote:
>>>> There are two calls to help identify Gunyah:
>>>>
>>>> 1. gh_hypercall_get_uid() returns a UID when running under a Gunyah
>>>> hypervisor.
>>>> 2. gh_hypercall_hyp_identify() returns build information and a set of
>>>> feature flags that are supported by Gunyah.
>>>
>>> The first is a "service", while the second is a "hypercall".
>>> Can you explain the distinction? The sentence at the top
>>> refers to both as "hypercalls".
>>>
>>
>> I learned more details about this to answer your question. "get_uid()"
>> is a standardized call that is ARM_SMCCC_VENDOR_HYP_CALL_UID_FUNC_ID
>> defined in include/arm-smccc.h. I'll use that.
>
> You didn't really explain the distinction between hypercall
> and service in Gunyah. Both are encoded as "vendor specific
> hypervisor service calls" according to the SVCCC specification.
> I haven't found where ARM_SMCCC_VENDOR_HYP_CALL_UID_FUNC_ID
> gets handled, but I accept your answer that it's basically
> a standard call. The "get UID" is the only one that's defined
> as a Gunyah "service"; the rest are hypercalls.
>
This came from a Gunyah implementation detail that separates standard
(service) calls from the non-standard calls. I was following the
distinction from Gunyah code not realizing that it's actually a
standardized call that Linux would already have support for.
Thanks,
Elliot
> It's not a big deal, I just noticed the difference and was
> curious about it.
>
> -Alex
On 1/17/2023 11:21 AM, Alex Elder wrote:
> On 1/10/23 11:56 AM, Elliot Berman wrote:
>>
>>
>> On 1/9/2023 1:34 PM, Alex Elder wrote:
>>> On 12/19/22 4:58 PM, Elliot Berman wrote:
>>>> Export the version of Gunyah which is reported via the hyp_identify
>>>> hypercall. Increments of the major API version indicate possibly
>>>> backwards incompatible changes.
>>>>
>>>> Export the hypervisor identity so that Gunyah drivers can act according
>>>> to the major API version.
>>>>
>>>> Signed-off-by: Elliot Berman <[email protected]>
>>>> ---
>>>> drivers/virt/Makefile | 1 +
>>>> drivers/virt/gunyah/Makefile | 1 +
>>>> drivers/virt/gunyah/gunyah.c | 46
>>>> ++++++++++++++++++++++++++++++++++++
>>>> include/linux/gunyah.h | 6 +++++
>>>> 4 files changed, 54 insertions(+)
>>>> create mode 100644 drivers/virt/gunyah/Makefile
>>>> create mode 100644 drivers/virt/gunyah/gunyah.c
>>>>
>>>> diff --git a/drivers/virt/Makefile b/drivers/virt/Makefile
>>>> index 093674e05c40..92b7e5311548 100644
>>>> --- a/drivers/virt/Makefile
>>>> +++ b/drivers/virt/Makefile
>>>> @@ -11,3 +11,4 @@ obj-$(CONFIG_NITRO_ENCLAVES) += nitro_enclaves/
>>>> obj-$(CONFIG_ACRN_HSM) += acrn/
>>>> obj-$(CONFIG_EFI_SECRET) += coco/efi_secret/
>>>> obj-$(CONFIG_SEV_GUEST) += coco/sev-guest/
>>>> +obj-y += gunyah/
>>>> diff --git a/drivers/virt/gunyah/Makefile
>>>> b/drivers/virt/gunyah/Makefile
>>>> new file mode 100644
>>>> index 000000000000..2ac4ee64b89d
>>>> --- /dev/null
>>>> +++ b/drivers/virt/gunyah/Makefile
>>>> @@ -0,0 +1 @@
>>>> +obj-$(CONFIG_GUNYAH) += gunyah.o
>>>> diff --git a/drivers/virt/gunyah/gunyah.c
>>>> b/drivers/virt/gunyah/gunyah.c
>>>> new file mode 100644
>>>> index 000000000000..c34c9046fc08
>>>> --- /dev/null
>>>> +++ b/drivers/virt/gunyah/gunyah.c
>>>> @@ -0,0 +1,46 @@
>>>> +// SPDX-License-Identifier: GPL-2.0-only
>>>> +/*
>>>> + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights
>>>> reserved.
>>>> + */
>>>> +
>>>> +#define pr_fmt(fmt) "gunyah: " fmt
>>>> +
>>>> +#include <linux/gunyah.h>
>>>> +#include <linux/init.h>
>>>> +#include <linux/module.h>
>>>> +#include <linux/printk.h>
>>>> +
>>>> +struct gh_hypercall_hyp_identify_resp gunyah_api;
>>>> +EXPORT_SYMBOL_GPL(gunyah_api);
>>>> +
>>>> +static const uint32_t gunyah_known_uuids[][4] = {
>>>> + {0x19bd54bd, 0x0b37571b, 0x946f609b, 0x54539de6}, /*QC_HYP
>>>> (Qualcomm's build) */
>>>> + {0x673d5f14, 0x9265ce36, 0xa4535fdb, 0xc1d58fcd}, /*GUNYAH
>>>> (open source build) */
>>>> +};
>>>> +
>>>> +static int __init gunyah_init(void)
>>>> +{
>>>> + u32 uid[4];
>>>> + int i;
>>>> +
>>>> + gh_hypercall_get_uid(uid);
>>>> +
>>>> + for (i = 0; i < ARRAY_SIZE(gunyah_known_uuids); i++)
>>>> + if (!memcmp(uid, gunyah_known_uuids[i], sizeof(uid)))
>>>> + break;
>>>> +
>>>> + if (i == ARRAY_SIZE(gunyah_known_uuids))
>>>> + return -ENODEV;
>>>> +
>>>> + gh_hypercall_hyp_identify(&gunyah_api);
>>>> +
>>>> + pr_info("Running under Gunyah hypervisor %llx/v%u\n",
>>>> + FIELD_GET(GH_API_INFO_VARIANT_MASK, gunyah_api.api_info),
>>>> + gh_api_version());
>>>> +
>>>> + return 0;
>>>> +}
>>>> +arch_initcall(gunyah_init);
>>>> +
>>>> +MODULE_LICENSE("GPL");
>>>> +MODULE_DESCRIPTION("Gunyah Hypervisor Driver");
>>>> diff --git a/include/linux/gunyah.h b/include/linux/gunyah.h
>>>> index 2765d2b40198..166156f69df9 100644
>>>> --- a/include/linux/gunyah.h
>>>> +++ b/include/linux/gunyah.h
>>>> @@ -92,6 +92,12 @@ struct gh_hypercall_hyp_identify_resp {
>>>> u64 api_info;
>>>> u64 flags[3];
>>>> };
>>>> +extern struct gh_hypercall_hyp_identify_resp gunyah_api;
>>>
>>> Must this global variable be exposed? Can't you hide it--as
>>> well as its interpretation--inside "gunyah.c"?
>>> >> +
>>>> +static inline u16 gh_api_version(void)
>>>> +{
>>>> + return FIELD_GET(GH_API_INFO_API_VERSION_MASK,
>>>> gunyah_api.api_info);
>>>> +}
>>>
>>> If you don't make the above function inline, can you hide the
>>> definition of gunyah_api?
>>>
>>
>> This seems like a good idea to me. I'm thinking to have the following
>> functions:
>>
>> enum gh_api_feature {
>> GH_API_FEATURE_DOORBELL,
>> GH_API_FEATURE_MSGQUEUE,
>> GH_API_FEATURE_VIC,
>> GH_API_FEATURE_VPM,
>> GH_API_FEATURE_VCPU,
>> GH_API_FEATURE_MEMEXTENT,
>> GH_API_FEATURE_TRACE_CTRL,
>> };
>
> My point wasn't necessarily to expose all of that. Already
> "gunyah.h" defines the bit positions in which these things
> are recorded in the identify response. My main point was
> about (not) exposing that global variable.
>
> I support your suggestion of abstracting the feature
> interface that way, but I suggest waiting until it's
> actually needed before you add it. I don't see any
> code that uses GH_IDENTIFY_PARTITION_CSPACE (and the
> rest) currently.
Sure, I will do that.
>
> Oh, and interestingly enough I see GH_API_INFO_BIG_ENDIAN
> defined in the API info...
>
> -Alex
>
>>
>> u16 gh_api_version(void);
>> bool gh_api_has_feature(enum gh_api_feature feature);
>>
>> Thanks,
>> Elliot
>
On 12/19/22 4:58 PM, Elliot Berman wrote:
> Add Gunyah Resource Manager RPC to launch an unauthenticated VM.
I have a number of general comments on your patch, and there
are a few bugs that you will need to address.
> Signed-off-by: Elliot Berman <[email protected]>
> ---
> drivers/virt/gunyah/Makefile | 2 +-
> drivers/virt/gunyah/rsc_mgr.h | 41 ++++++
> drivers/virt/gunyah/rsc_mgr_rpc.c | 224 ++++++++++++++++++++++++++++++
> include/linux/gunyah_rsc_mgr.h | 50 +++++++
> 4 files changed, 316 insertions(+), 1 deletion(-)
> create mode 100644 drivers/virt/gunyah/rsc_mgr_rpc.c
>
> diff --git a/drivers/virt/gunyah/Makefile b/drivers/virt/gunyah/Makefile
> index e7cf59b9e64e..5caa05267a58 100644
> --- a/drivers/virt/gunyah/Makefile
> +++ b/drivers/virt/gunyah/Makefile
> @@ -1,4 +1,4 @@
> obj-$(CONFIG_GUNYAH) += gunyah.o
>
> -gunyah_rsc_mgr-y += rsc_mgr.o
> +gunyah_rsc_mgr-y += rsc_mgr.o rsc_mgr_rpc.o
> obj-$(CONFIG_GUNYAH_RESOURCE_MANAGER) += gunyah_rsc_mgr.o
> diff --git a/drivers/virt/gunyah/rsc_mgr.h b/drivers/virt/gunyah/rsc_mgr.h
> index b5bb36a7a4cc..1f9e3c38038e 100644
> --- a/drivers/virt/gunyah/rsc_mgr.h
> +++ b/drivers/virt/gunyah/rsc_mgr.h
> @@ -33,4 +33,45 @@ struct gh_rm_rpc;
> int gh_rm_call(struct gh_rm_rpc *rsc_mgr, u32 message_id, void *req_buff, size_t req_buff_size,
> void **resp_buf, size_t *resp_buff_size);
>
> +/* Message IDs: VM Management */
> +#define GH_RM_RPC_VM_ALLOC_VMID 0x56000001
These values very clearly appear to be encoded. Like the top
byte (0x56) might be defining the resource type and bytes below
it are the request ID? If that is truly the case I think it
is useful to lay out that format, and perhaps define the values
based on the components that they are comprised of.
> +#define GH_RM_RPC_VM_DEALLOC_VMID 0x56000002
> +#define GH_RM_RPC_VM_START 0x56000004
> +#define GH_RM_RPC_VM_STOP 0x56000005
> +#define GH_RM_RPC_VM_CONFIG_IMAGE 0x56000009
> +#define GH_RM_RPC_VM_INIT 0x5600000B
> +#define GH_RM_RPC_VM_GET_HYP_RESOURCES 0x56000020
> +#define GH_RM_RPC_VM_GET_VMID 0x56000024
> +
> +/* Call: CONSOLE_OPEN, CONSOLE_CLOSE, CONSOLE_FLUSH */
The above comment is nonsense. None of these symbols (or
similar) are defined, anywhere. For the current patch, this
structure is used for VM_ALLOC_VMID, VM_GET_HYP_RESOURCES,
and via gh_rm_common_vmid_call(), VM_DEALLOC_VMID, VM_START,
and VM_INIT. It's possible you'll use it for other requests
in subsequent patches.
Here you are defining a single "common" structure that's
used for several message types. I think there are legitimate
reasons for that. However...
In general, I think I'd rather see every message type laid
out separately. It becomes a little long, here, where you
would define them, but in the code that uses them it allows
some simple patterns to be used in message handler functions
that I think improve understandability and perhaps correctness.
I'll provide some examples below to try to make my case.
> +struct gh_vm_common_vmid_req {
> + __le16 vmid;
> + __le16 reserved0;
If the above is really reserved (and unused), its
endianness doesn't matter. (Not really a big deal.)
> +} __packed;
The above structure defines the format of a *request*
message. But it is *also* the format of a VM_ALLOC_VMID
*response* message. So the name of the type does not
match the way it's used. As a reviewer, this seems off.
> +/* Call: VM_STOP */
> +struct gh_vm_stop_req {
> + __le16 vmid;
> + u8 flags;
I don't believe you specify what the valid values this
"flags" field can hold. If not, I think you should (and
if it's currently not used/needed, say that).
> + u8 reserved;
> + __le32 stop_reason;
Same thing here. This field is defined but from what
I can tell, it is not currently used. What values
*could* it take on? Why is it currently ignored?
> +} __packed;
> +
> +/* Call: VM_CONFIG_IMAGE */
> +struct gh_vm_config_image_req {
> + __le16 vmid;
> + __le16 auth_mech;
> + __le32 mem_handle;
> + __le64 image_offset;
> + __le64 image_size;
> + __le64 dtb_offset;
> + __le64 dtb_size;
> +} __packed;
> +
> +/* Call: GET_HYP_RESOURCES */
> +struct gh_vm_get_hyp_resources_resp {
> + __le32 n_entries;
> + struct gh_rm_hyp_resource entries[];
This header file should include <linux/gunyah_rsrc_mgr.h> to get
the required definition of struct gh_rm_hyp_resource.
> +} __packed;
> +
> #endif
> diff --git a/drivers/virt/gunyah/rsc_mgr_rpc.c b/drivers/virt/gunyah/rsc_mgr_rpc.c
> new file mode 100644
> index 000000000000..8d9c1c635a27
> --- /dev/null
> +++ b/drivers/virt/gunyah/rsc_mgr_rpc.c
> @@ -0,0 +1,224 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
> + */
> +
> +#include <linux/gunyah_rsc_mgr.h>
> +
> +#include "rsc_mgr.h"
> +
> +/*
> + * Several RM calls take only a VMID as a parameter and give only standard
> + * response back. Deduplicate boilerplate code by using this common call.
In other words, the response message is empty for the requests that use
this function.
> + */
> +static int gh_rm_common_vmid_call(struct gh_rm_rpc *rm, u32 message_id, u16 vmid)
> +{
> + void *resp = NULL;
No need to set this to NULL if you ignore its value
when the gh_rm_call() is unsuccessful.
> + struct gh_vm_common_vmid_req req_payload = {
> + .vmid = cpu_to_le16(vmid),
> + };
> + size_t resp_size;
> + int ret;
> +
> + ret = gh_rm_call(rm, message_id, &req_payload, sizeof(req_payload), &resp, &resp_size);
Here and in all other calls to gh_rm_call() here (and added later),
you need to handle the return value differently. (THIS IS A BUG.)
There are three classes of return value from gh_rm_call():
- Zero (success)
- Negative errno
- Positive Gunyah resource manager error code
In practice, it might be nice to distinguish Gunyah error codes from
Linux errnos, but I think the easiest solution might be to have a
function that maps Gunyah codes to comparable errno values, and have
gh_rm_call() use it so its return value is always 0 or negative.
> + if (!ret)
> + kfree(resp);
> +
> + WARN_ON(!ret && resp_size);
You should have well-defined behavior when the response message
is empty. Will the response pointer be valid or not? I suggest
that a 0 length response message be guaranteed to return a null
pointer if the return value indicates success. And a non-zero
length should be guaranteed to be valid. There should be no
need for callers to issue warnings like this.
So the above would then just be (note this follows a pattern
similar to used by handlers later in the file):
if (ret)
return ret;
if (resp_size) /* (this is optional) */
kfree(resp);
return 0;
> +
> + return ret;
> +}
> +
> +/**
> + * gh_rm_alloc_vmid() - Allocate a new VM in Gunyah. Returns the VM identifier.
> + * @vmid: Use GH_VMID_INVAL to dynamically allocate a VM. A reserved VMID can also be requested
> + * for a special-purpose platform-defined VM.
> + *
> + * Returns - the allocated VMID or negative value on error
Currently (based on a comment I made above) this statement
is not correct. But I think what's described is the proper
return convention. It's probably worth mentioning that it
will never return GH_VMID_SELF or GH_VMID_INVAL.
> + */
> +int gh_rm_alloc_vmid(struct gh_rm_rpc *rm, u16 vmid)
> +{
> + void *resp;
> + struct gh_vm_common_vmid_req req_payload = {
> + .vmid = cpu_to_le16(vmid),
> + };
> + struct gh_vm_common_vmid_req *resp_payload;
> + size_t resp_size;
> + int ret;
> +
What is the meaning of this call if you pass GH_VMID_SELF?
Is that defined to be valid? The code allows it, and if
so it treats it as equivalent to passing GH_VMID_INVAL.
> + if (vmid == GH_VMID_INVAL)
> + vmid = 0;
> +
> + ret = gh_rm_call(rm, GH_RM_RPC_VM_ALLOC_VMID, &req_payload, sizeof(req_payload), &resp,
> + &resp_size);
This *can* return a positive Gunyah resource manager error ID.
So in this case the caller would (erroneously) assume it was
a valid VMID.
You need to fix this.
> + if (ret)
> + return ret;
> +
> + if (!vmid) {
> + if (resp_size != sizeof(*resp_payload)) {
> + ret = -EINVAL;
EINVAL means "invalid argument". I *think* the right thing
to return would be -EBADMSG. In any case I don't really
think -EINVAL is appropriate.
> + } else {
> + resp_payload = resp;
> + ret = resp_payload->vmid;
This is a bug. The vmid is a little-endian value, so you need
to use:
ret = le16_to_cpu(resp_payload->vmid);
> + }
> + }
> + kfree(resp);
> +
> + return ret;
> +}
> +EXPORT_SYMBOL_GPL(gh_rm_alloc_vmid);
> +
> +/**
> + * gh_rm_dealloc_vmid() - Dispose the VMID
> + * @vmid: VM identifier
* Returns: ... (here and throughout)
> + */
> +int gh_rm_dealloc_vmid(struct gh_rm_rpc *rm, u16 vmid)
> +{
> + return gh_rm_common_vmid_call(rm, GH_RM_RPC_VM_DEALLOC_VMID, vmid);
> +}
> +EXPORT_SYMBOL_GPL(gh_rm_dealloc_vmid);
> +
> +/**
> + * gh_rm_vm_start() - Move the VM into "ready to run" state
> + * @vmid: VM identifier
> + *
> + * On VMs which use proxy scheduling, vcpu_run is needed to actually run the VM.
> + * On VMs which use Gunyah's scheduling, the vCPUs start executing in accordance with Gunyah
> + * scheduling policies.
> + */
> +int gh_rm_vm_start(struct gh_rm_rpc *rm, u16 vmid)
> +{
> + return gh_rm_common_vmid_call(rm, GH_RM_RPC_VM_START, vmid);
> +}
> +EXPORT_SYMBOL_GPL(gh_rm_vm_start);
> +
> +/**
> + * gh_rm_vm_stop() - Send a request to Resource Manager VM to stop a VM.
> + * @vmid: VM identifier
> + */
> +int gh_rm_vm_stop(struct gh_rm_rpc *rm, u16 vmid)
> +{
> + struct gh_vm_stop_req req_payload = {
> + .vmid = cpu_to_le16(vmid),
This structure has a flags and a stop_reason field. As I mentioned
earlier, I think you ought to explain what values this might have,
or perhaps explain why they're not used.
> + };
> + void *resp;
> + size_t resp_size;
> + int ret;
> +
> + ret = gh_rm_call(rm, GH_RM_RPC_VM_STOP, &req_payload, sizeof(req_payload),
> + &resp, &resp_size);
> + if (ret)
> + return ret;
Is the response in this case in fact empty? You are discarding
it, but because you don't specify how every request/response is
structured, we can only guess what's contained in the response
(if there is one). This is a reason why I prefer having every
message structure shown explicitly, even if some are identical.
It makes it very clear to the reader what the protocol actually
looks like. And unlike what you do in gh_rm_common_vmid_call(),
you ignore the resp_size, which could be checked if we knew its
expected size.
> + kfree(resp);
> +
> + return ret;
> +}
> +EXPORT_SYMBOL_GPL(gh_rm_vm_stop);
> +
> +int gh_rm_vm_configure(struct gh_rm_rpc *rm, u16 vmid, enum gh_rm_vm_auth_mechanism auth_mechanism,
> + u32 mem_handle, u64 image_offset, u64 image_size, u64 dtb_offset, u64 dtb_size)
> +{
> + struct gh_vm_config_image_req req_payload = { 0 };
> + void *resp;
> + size_t resp_size;
> + int ret;
> +
> + req_payload.vmid = cpu_to_le16(vmid);
> + req_payload.auth_mech = cpu_to_le32(auth_mechanism);
> + req_payload.mem_handle = cpu_to_le32(mem_handle);
> + req_payload.image_offset = cpu_to_le64(image_offset);
> + req_payload.image_size = cpu_to_le64(image_size);
> + req_payload.dtb_offset = cpu_to_le64(dtb_offset);
> + req_payload.dtb_size = cpu_to_le64(dtb_size);
> +
> + ret = gh_rm_call(rm, GH_RM_RPC_VM_CONFIG_IMAGE, &req_payload, sizeof(req_payload),
> + &resp, &resp_size);
> + if (ret)
> + return ret;
> + kfree(resp);
> +
> + return ret;
> +}
> +EXPORT_SYMBOL_GPL(gh_rm_vm_configure);
> +
> +/**
> + * gh_rm_vm_init() - Move the VM to initialized state.
> + * @vmid: VM identifier
> + *
> + * RM will allocate needed resources for the VM. After gh_rm_vm_init, gh_rm_get_hyp_resources()
> + * can be called to learn of the capabilities we can use with the new VM.
> + */
> +int gh_rm_vm_init(struct gh_rm_rpc *rm, u16 vmid)
> +{
> + return gh_rm_common_vmid_call(rm, GH_RM_RPC_VM_INIT, vmid);
> +}
> +EXPORT_SYMBOL_GPL(gh_rm_vm_init);
> +
> +/**
> + * gh_rm_get_hyp_resources() - Retrieve hypervisor resources (capabilities) associated with a VM
> + * @vmid: VMID of the other VM to get the resources of
> + * @resources: Set by gh_rm_get_hyp_resources and contains the returned hypervisor resources.
> + *
> + * Return: >=0 value indicates the number of gh_rm_hyp_resource entries filled into *resources
> + */
> +ssize_t gh_rm_get_hyp_resources(struct gh_rm_rpc *rm, u16 vmid,
> + struct gh_rm_hyp_resource **resources)
First, this function can return a positive Gunyah resource manager
error as well, which is a problem.
But this function is also pretty different from the rest, returning
the number of elements in the array whose address is returned.
I don't know why you don't just return the actual response
buffer (rather than duplicating most of it), and let the
caller use its embedded n_entries field to determine its
length. Either way it's not beautiful I guess, but the
kmemdup() call seems unneccesary and adds a possible
additional error condition.
> +{
> + struct gh_vm_get_hyp_resources_resp *resp;
> + size_t resp_size;
> + int ret;
> + struct gh_vm_common_vmid_req req_payload = {
> + .vmid = cpu_to_le16(vmid),
> + };
> +
> + ret = gh_rm_call(rm, GH_RM_RPC_VM_GET_HYP_RESOURCES,
> + &req_payload, sizeof(req_payload),
> + (void **)&resp, &resp_size);
> + if (ret)
> + return ret;
> +
> + if (resp_size < sizeof(*resp) ||
> + (sizeof(*resp->entries) && (resp->n_entries > U32_MAX / sizeof(*resp->entries))) ||
> + (resp_size != sizeof(*resp) + (resp->n_entries * sizeof(*resp->entries)))) {
> + ret = -EIO;
> + goto out;
> + }
> +
> + *resources = kmemdup(resp->entries, (resp->n_entries * sizeof(*resp->entries)), GFP_KERNEL);
If *resources is NULL you should return -ENOMEM.
> + ret = resp->n_entries;
> +
> +out:
> + kfree(resp);
> + return ret;
> +}
> +EXPORT_SYMBOL_GPL(gh_rm_get_hyp_resources);
> +
> +/**
> + * gh_rm_get_vmid() - Retrieve VMID of this virtual machine
> + * @vmid: Filled with the VMID of this VM
> + */
> +int gh_rm_get_vmid(struct gh_rm_rpc *rm, u16 *vmid)
> +{
> + static u16 cached_vmid = GH_VMID_INVAL;
This cached VMID seems like a good idea, but you don't actually use it.
> + void *resp;
> + size_t resp_size;
> + int ret;
> + int payload = 0;
In fact the payload has type __le16 (or at least I assume so).
I don't actually know what this payload represents. Is it a
VMID (which would I presume in this case be GH_VMID_SELF)?
How big is it, actually?
If you were to define each request and response, I would hope
each would be a packed structure, so in this case:
struct gh_rm_get_vmid_request {
__le16 vmid;
u16 reserved; /* ??? */
} __packed;
> +
> + if (cached_vmid != GH_VMID_INVAL) {
> + *vmid = cached_vmid;
> + return 0;
> + }
> +
> + ret = gh_rm_call(rm, GH_RM_RPC_VM_GET_VMID, &payload, sizeof(payload), &resp, &resp_size);
> + if (ret)
> + return ret;
> +
> + if (resp_size != sizeof(*vmid))
> + return -EIO;
> + *vmid = *(u16 *)resp;
This is a bug. The response is little endian, so you need to do:
*vmid = le16_to_32(resp);
> + kfree(resp);
> +
> + return ret;
> +}
> +EXPORT_SYMBOL_GPL(gh_rm_get_vmid);
> diff --git a/include/linux/gunyah_rsc_mgr.h b/include/linux/gunyah_rsc_mgr.h
> index b4f55c19954b..eb94b48c41de 100644
> --- a/include/linux/gunyah_rsc_mgr.h
> +++ b/include/linux/gunyah_rsc_mgr.h
> @@ -15,4 +15,54 @@
> /* Gunyah recognizes VMID0 as an alias to the current VM's ID */
> #define GH_VMID_SELF 0
Please be sure to use this symbol in place of 0 when that's
what you mean. In gh_rm_get_vmid() it looked like *maybe*
that's what was being passed as an argument in the request
payload--and if so, that's a case I'd like to see GH_VMID_SELF.
-Alex
>
> +struct gh_rm_rpc;
> +
> +enum gh_rm_vm_status {
> + GH_RM_VM_STATUS_NO_STATE = 0,
> + GH_RM_VM_STATUS_INIT = 1,
> + GH_RM_VM_STATUS_READY = 2,
> + GH_RM_VM_STATUS_RUNNING = 3,
> + GH_RM_VM_STATUS_PAUSED = 4,
> + GH_RM_VM_STATUS_LOAD = 5,
> + GH_RM_VM_STATUS_AUTH = 6,
> + GH_RM_VM_STATUS_INIT_FAILED = 8,
> + GH_RM_VM_STATUS_EXITED = 9,
> + GH_RM_VM_STATUS_RESETTING = 10,
> + GH_RM_VM_STATUS_RESET = 11,
> +};
> +
> +/* RPC Calls */
> +int gh_rm_alloc_vmid(struct gh_rm_rpc *rm, u16 vmid);
> +int gh_rm_dealloc_vmid(struct gh_rm_rpc *rm, u16 vmid);
> +int gh_rm_vm_start(struct gh_rm_rpc *rm, u16 vmid);
> +int gh_rm_vm_stop(struct gh_rm_rpc *rm, u16 vmid);
> +
> +enum gh_rm_vm_auth_mechanism {
> + GH_RM_VM_AUTH_NONE = 0,
> + GH_RM_VM_AUTH_QCOM_PIL_ELF = 1,
> + GH_RM_VM_AUTH_QCOM_ANDROID_PVM = 2,
> +};
> +
> +int gh_rm_vm_configure(struct gh_rm_rpc *rm, u16 vmid, enum gh_rm_vm_auth_mechanism auth_mechanism,
> + u32 mem_handle, u64 image_offset, u64 image_size,
> + u64 dtb_offset, u64 dtb_size);
> +int gh_rm_vm_init(struct gh_rm_rpc *rm, u16 vmid);
> +
> +struct gh_rm_hyp_resource {
> + u8 type;
> + u8 reserved;
> + __le16 partner_vmid;
> + __le32 resource_handle;
> + __le32 resource_label;
> + __le64 cap_id;
> + __le32 virq_handle;
> + __le32 virq;
> + __le64 base;
> + __le64 size;
> +} __packed;
> +
> +ssize_t gh_rm_get_hyp_resources(struct gh_rm_rpc *rm, u16 vmid,
> + struct gh_rm_hyp_resource **resources);
> +int gh_rm_get_vmid(struct gh_rm_rpc *rm, u16 *vmid);
> +
> #endif
On 1/18/2023 10:26 AM, Alex Elder wrote:
> On 12/19/22 4:58 PM, Elliot Berman wrote:
>> Add Gunyah Resource Manager RPC to launch an unauthenticated VM.
>
> I have a number of general comments on your patch, and there
> are a few bugs that you will need to address.
>
>> Signed-off-by: Elliot Berman <[email protected]>
>> ---
>> drivers/virt/gunyah/Makefile | 2 +-
>> drivers/virt/gunyah/rsc_mgr.h | 41 ++++++
>> drivers/virt/gunyah/rsc_mgr_rpc.c | 224 ++++++++++++++++++++++++++++++
>> include/linux/gunyah_rsc_mgr.h | 50 +++++++
>> 4 files changed, 316 insertions(+), 1 deletion(-)
>> create mode 100644 drivers/virt/gunyah/rsc_mgr_rpc.c
>>
>> diff --git a/drivers/virt/gunyah/Makefile b/drivers/virt/gunyah/Makefile
>> index e7cf59b9e64e..5caa05267a58 100644
>> --- a/drivers/virt/gunyah/Makefile
>> +++ b/drivers/virt/gunyah/Makefile
>> @@ -1,4 +1,4 @@
>> obj-$(CONFIG_GUNYAH) += gunyah.o
>> -gunyah_rsc_mgr-y += rsc_mgr.o
>> +gunyah_rsc_mgr-y += rsc_mgr.o rsc_mgr_rpc.o
>> obj-$(CONFIG_GUNYAH_RESOURCE_MANAGER) += gunyah_rsc_mgr.o
>> diff --git a/drivers/virt/gunyah/rsc_mgr.h
>> b/drivers/virt/gunyah/rsc_mgr.h
>> index b5bb36a7a4cc..1f9e3c38038e 100644
>> --- a/drivers/virt/gunyah/rsc_mgr.h
>> +++ b/drivers/virt/gunyah/rsc_mgr.h
>> @@ -33,4 +33,45 @@ struct gh_rm_rpc;
>> int gh_rm_call(struct gh_rm_rpc *rsc_mgr, u32 message_id, void
>> *req_buff, size_t req_buff_size,
>> void **resp_buf, size_t *resp_buff_size);
>> +/* Message IDs: VM Management */
>> +#define GH_RM_RPC_VM_ALLOC_VMID 0x56000001
>
> These values very clearly appear to be encoded. Like the top
> byte (0x56) might be defining the resource type and bytes below
> it are the request ID? If that is truly the case I think it
> is useful to lay out that format, and perhaps define the values
> based on the components that they are comprised of.
>
Gunyah doesn't have any formal encoding scheme. It's separated out into
logical regions, but there is no meaning for the "0x56" except that it
mostly has functions related to VM management.
>> +#define GH_RM_RPC_VM_DEALLOC_VMID 0x56000002
>> +#define GH_RM_RPC_VM_START 0x56000004
>> +#define GH_RM_RPC_VM_STOP 0x56000005
>> +#define GH_RM_RPC_VM_CONFIG_IMAGE 0x56000009
>> +#define GH_RM_RPC_VM_INIT 0x5600000B
>> +#define GH_RM_RPC_VM_GET_HYP_RESOURCES 0x56000020
>> +#define GH_RM_RPC_VM_GET_VMID 0x56000024
>> +
>> +/* Call: CONSOLE_OPEN, CONSOLE_CLOSE, CONSOLE_FLUSH */
>
> The above comment is nonsense. None of these symbols (or
> similar) are defined, anywhere. For the current patch, this
> structure is used for VM_ALLOC_VMID, VM_GET_HYP_RESOURCES,
> and via gh_rm_common_vmid_call(), VM_DEALLOC_VMID, VM_START,
> and VM_INIT. It's possible you'll use it for other requests
> in subsequent patches.
>
> Here you are defining a single "common" structure that's
> used for several message types. I think there are legitimate
> reasons for that. However...
>
> In general, I think I'd rather see every message type laid
> out separately. It becomes a little long, here, where you
> would define them, but in the code that uses them it allows
> some simple patterns to be used in message handler functions
> that I think improve understandability and perhaps correctness.
> I'll provide some examples below to try to make my case.
>
>> +struct gh_vm_common_vmid_req {
>> + __le16 vmid;
>> + __le16 reserved0;
>
> If the above is really reserved (and unused), its
> endianness doesn't matter. (Not really a big deal.)
>
>> +} __packed;
>
> The above structure defines the format of a *request*
> message. But it is *also* the format of a VM_ALLOC_VMID
> *response* message. So the name of the type does not
> match the way it's used. As a reviewer, this seems off.
>
Yes, I see the point here. I've created separate struct for the
VM_ALLOC_VMID response message. I'd still like to keep the
"gh_vm_common_vmid_req" for
>> +/* Call: VM_STOP */
>> +struct gh_vm_stop_req {
>> + __le16 vmid;
>> + u8 flags;
>
> I don't believe you specify what the valid values this
> "flags" field can hold. If not, I think you should (and
> if it's currently not used/needed, say that).
>
>> + u8 reserved;
>> + __le32 stop_reason;
>
> Same thing here. This field is defined but from what
> I can tell, it is not currently used. What values
> *could* it take on? Why is it currently ignored?
>
Neither of the fields are currently used, so I've not defined them.
Flags has the following bit:
// Forcibly shutdown VM without sending a VM_SHUTDOWN notification
#define GH_VM_STOP_FORCE_SHUTDOWN BIT(1)
stop_reason is a code that is passed to the VM in the VM_SHUTDOWN
notification.
>> +} __packed;
>> +
>> +/* Call: VM_CONFIG_IMAGE */
>> +struct gh_vm_config_image_req {
>> + __le16 vmid;
>> + __le16 auth_mech;
>> + __le32 mem_handle;
>> + __le64 image_offset;
>> + __le64 image_size;
>> + __le64 dtb_offset;
>> + __le64 dtb_size;
>> +} __packed;
>> +
>> +/* Call: GET_HYP_RESOURCES */
>> +struct gh_vm_get_hyp_resources_resp {
>> + __le32 n_entries;
>> + struct gh_rm_hyp_resource entries[];
>
> This header file should include <linux/gunyah_rsrc_mgr.h> to get
> the required definition of struct gh_rm_hyp_resource.
>
Done.
>> +} __packed;
>> +
>> #endif
>> diff --git a/drivers/virt/gunyah/rsc_mgr_rpc.c
>> b/drivers/virt/gunyah/rsc_mgr_rpc.c
>> new file mode 100644
>> index 000000000000..8d9c1c635a27
>> --- /dev/null
>> +++ b/drivers/virt/gunyah/rsc_mgr_rpc.c
>> @@ -0,0 +1,224 @@
>> +// SPDX-License-Identifier: GPL-2.0-only
>> +/*
>> + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights
>> reserved.
>> + */
>> +
>> +#include <linux/gunyah_rsc_mgr.h>
>> +
>> +#include "rsc_mgr.h"
>> +
>> +/*
>> + * Several RM calls take only a VMID as a parameter and give only
>> standard
>> + * response back. Deduplicate boilerplate code by using this common
>> call.
>
> In other words, the response message is empty for the requests that use
> this function.
>
>> + */
>> +static int gh_rm_common_vmid_call(struct gh_rm_rpc *rm, u32
>> message_id, u16 vmid)
>> +{
>> + void *resp = NULL;
>
> No need to set this to NULL if you ignore its value
> when the gh_rm_call() is unsuccessful.
>
Done.
>> + struct gh_vm_common_vmid_req req_payload = {
>> + .vmid = cpu_to_le16(vmid),
>> + };
>> + size_t resp_size;
>> + int ret;
>> +
>> + ret = gh_rm_call(rm, message_id, &req_payload,
>> sizeof(req_payload), &resp, &resp_size);
>
> Here and in all other calls to gh_rm_call() here (and added later),
> you need to handle the return value differently. (THIS IS A BUG.)
>
> There are three classes of return value from gh_rm_call():
> - Zero (success)
> - Negative errno
> - Positive Gunyah resource manager error code
>
> In practice, it might be nice to distinguish Gunyah error codes from
> Linux errnos, but I think the easiest solution might be to have a
> function that maps Gunyah codes to comparable errno values, and have
> gh_rm_call() use it so its return value is always 0 or negative.
>
>> + if (!ret)
>> + kfree(resp);
>> +
>> + WARN_ON(!ret && resp_size);
>
> You should have well-defined behavior when the response message
> is empty. Will the response pointer be valid or not? I suggest
> that a 0 length response message be guaranteed to return a null
> pointer if the return value indicates success. And a non-zero
> length should be guaranteed to be valid. There should be no
> need for callers to issue warnings like this.
>
> So the above would then just be (note this follows a pattern
> similar to used by handlers later in the file):
>
> if (ret)
> return ret;
>
> if (resp_size) /* (this is optional) */
> kfree(resp);
>
> return 0;
>
>
Done, I'll clean up the error flow here.
>> +
>> + return ret;
>> +}
>> +
>> +/**
>> + * gh_rm_alloc_vmid() - Allocate a new VM in Gunyah. Returns the VM
>> identifier.
>> + * @vmid: Use GH_VMID_INVAL to dynamically allocate a VM. A reserved
>> VMID can also be requested
>> + * for a special-purpose platform-defined VM.
>> + *
>> + * Returns - the allocated VMID or negative value on error
>
> Currently (based on a comment I made above) this statement
> is not correct. But I think what's described is the proper
> return convention. It's probably worth mentioning that it
> will never return GH_VMID_SELF or GH_VMID_INVAL.
>
>> + */
>> +int gh_rm_alloc_vmid(struct gh_rm_rpc *rm, u16 vmid)
>> +{
>> + void *resp;
>> + struct gh_vm_common_vmid_req req_payload = {
>> + .vmid = cpu_to_le16(vmid),
>> + };
>> + struct gh_vm_common_vmid_req *resp_payload;
>> + size_t resp_size;
>> + int ret;
>> +
>
> What is the meaning of this call if you pass GH_VMID_SELF?
> Is that defined to be valid? The code allows it, and if
> so it treats it as equivalent to passing GH_VMID_INVAL.
>
I've updated the docstring to allow for GH_VMID_SELF and 0 to all be
equivalent.
>> + if (vmid == GH_VMID_INVAL)
>> + vmid = 0;
>> +
>> + ret = gh_rm_call(rm, GH_RM_RPC_VM_ALLOC_VMID, &req_payload,
>> sizeof(req_payload), &resp,
>> + &resp_size);
>
> This *can* return a positive Gunyah resource manager error ID.
> So in this case the caller would (erroneously) assume it was
> a valid VMID.
>
> You need to fix this.
>
>> + if (ret)
>> + return ret;
>> +
>> + if (!vmid) {
>> + if (resp_size != sizeof(*resp_payload)) {
>> + ret = -EINVAL;
>
> EINVAL means "invalid argument". I *think* the right thing
> to return would be -EBADMSG. In any case I don't really
> think -EINVAL is appropriate.
>
>> + } else {
>> + resp_payload = resp;
>> + ret = resp_payload->vmid;
>
> This is a bug. The vmid is a little-endian value, so you need
> to use:
>
> ret = le16_to_cpu(resp_payload->vmid);
>
>> + }
>> + }
>> + kfree(resp);
>> +
>> + return ret;
>> +}
>> +EXPORT_SYMBOL_GPL(gh_rm_alloc_vmid);
>> +
>> +/**
>> + * gh_rm_dealloc_vmid() - Dispose the VMID
>> + * @vmid: VM identifier
>
> * Returns: ... (here and throughout)
>
>> + */
>> +int gh_rm_dealloc_vmid(struct gh_rm_rpc *rm, u16 vmid)
>> +{
>> + return gh_rm_common_vmid_call(rm, GH_RM_RPC_VM_DEALLOC_VMID, vmid);
>> +}
>> +EXPORT_SYMBOL_GPL(gh_rm_dealloc_vmid);
>> +
>> +/**
>> + * gh_rm_vm_start() - Move the VM into "ready to run" state
>> + * @vmid: VM identifier
>> + *
>> + * On VMs which use proxy scheduling, vcpu_run is needed to actually
>> run the VM.
>> + * On VMs which use Gunyah's scheduling, the vCPUs start executing in
>> accordance with Gunyah
>> + * scheduling policies.
>> + */
>> +int gh_rm_vm_start(struct gh_rm_rpc *rm, u16 vmid)
>> +{
>> + return gh_rm_common_vmid_call(rm, GH_RM_RPC_VM_START, vmid);
>> +}
>> +EXPORT_SYMBOL_GPL(gh_rm_vm_start);
>> +
>> +/**
>> + * gh_rm_vm_stop() - Send a request to Resource Manager VM to stop a VM.
>> + * @vmid: VM identifier
>> + */
>> +int gh_rm_vm_stop(struct gh_rm_rpc *rm, u16 vmid)
>> +{
>> + struct gh_vm_stop_req req_payload = {
>> + .vmid = cpu_to_le16(vmid),
>
> This structure has a flags and a stop_reason field. As I mentioned
> earlier, I think you ought to explain what values this might have,
> or perhaps explain why they're not used.
>
>> + };
>> + void *resp;
>> + size_t resp_size;
>> + int ret;
>> +
>> + ret = gh_rm_call(rm, GH_RM_RPC_VM_STOP, &req_payload,
>> sizeof(req_payload),
>> + &resp, &resp_size);
>> + if (ret)
>> + return ret;
>
> Is the response in this case in fact empty? You are discarding
> it, but because you don't specify how every request/response is
> structured, we can only guess what's contained in the response
> (if there is one). This is a reason why I prefer having every
> message structure shown explicitly, even if some are identical.
> It makes it very clear to the reader what the protocol actually
> looks like. And unlike what you do in gh_rm_common_vmid_call(),
> you ignore the resp_size, which could be checked if we knew its
> expected size.
>
The response is empty, I've added the resp_size check.
>> + kfree(resp);
>> +
>> + return ret;
>> +}
>> +EXPORT_SYMBOL_GPL(gh_rm_vm_stop);
>> +
>> +int gh_rm_vm_configure(struct gh_rm_rpc *rm, u16 vmid, enum
>> gh_rm_vm_auth_mechanism auth_mechanism,
>> + u32 mem_handle, u64 image_offset, u64 image_size, u64
>> dtb_offset, u64 dtb_size)
>> +{
>> + struct gh_vm_config_image_req req_payload = { 0 };
>> + void *resp;
>> + size_t resp_size;
>> + int ret;
>> +
>> + req_payload.vmid = cpu_to_le16(vmid);
>> + req_payload.auth_mech = cpu_to_le32(auth_mechanism);
>> + req_payload.mem_handle = cpu_to_le32(mem_handle);
>> + req_payload.image_offset = cpu_to_le64(image_offset);
>> + req_payload.image_size = cpu_to_le64(image_size);
>> + req_payload.dtb_offset = cpu_to_le64(dtb_offset);
>> + req_payload.dtb_size = cpu_to_le64(dtb_size);
>> +
>> + ret = gh_rm_call(rm, GH_RM_RPC_VM_CONFIG_IMAGE, &req_payload,
>> sizeof(req_payload),
>> + &resp, &resp_size);
>> + if (ret)
>> + return ret;
>> + kfree(resp);
>> +
>> + return ret;
>> +}
>> +EXPORT_SYMBOL_GPL(gh_rm_vm_configure);
>> +
>> +/**
>> + * gh_rm_vm_init() - Move the VM to initialized state.
>> + * @vmid: VM identifier
>> + *
>> + * RM will allocate needed resources for the VM. After gh_rm_vm_init,
>> gh_rm_get_hyp_resources()
>> + * can be called to learn of the capabilities we can use with the new
>> VM.
>> + */
>> +int gh_rm_vm_init(struct gh_rm_rpc *rm, u16 vmid)
>> +{
>> + return gh_rm_common_vmid_call(rm, GH_RM_RPC_VM_INIT,vmid);
>> +}
>> +EXPORT_SYMBOL_GPL(gh_rm_vm_init);
>> +
>> +/**
>> + * gh_rm_get_hyp_resources() - Retrieve hypervisor resources
>> (capabilities) associated with a VM
>> + * @vmid: VMID of the other VM to get the resources of
>> + * @resources: Set by gh_rm_get_hyp_resources and contains the
>> returned hypervisor resources.
>> + *
>> + * Return: >=0 value indicates the number of gh_rm_hyp_resource
>> entries filled into *resources
>> + */
>> +ssize_t gh_rm_get_hyp_resources(struct gh_rm_rpc *rm, u16 vmid,
>> + struct gh_rm_hyp_resource **resources)
>
> First, this function can return a positive Gunyah resource manager
> error as well, which is a problem.
>
> But this function is also pretty different from the rest, returning
> the number of elements in the array whose address is returned.
>
> I don't know why you don't just return the actual response
> buffer (rather than duplicating most of it), and let the
> caller use its embedded n_entries field to determine its
> length. Either way it's not beautiful I guess, but the
> kmemdup() call seems unneccesary and adds a possible
> additional error condition.
>
I can return the response buffer. It arbitrarily felt like a better idea
at the time, but returning the actual response is also a clean approach.
>> +{
>> + struct gh_vm_get_hyp_resources_resp *resp;
>> + size_t resp_size;
>> + int ret;
>> + struct gh_vm_common_vmid_req req_payload = {
>> + .vmid = cpu_to_le16(vmid),
>> + };
>> +
>> + ret = gh_rm_call(rm, GH_RM_RPC_VM_GET_HYP_RESOURCES,
>> + &req_payload, sizeof(req_payload),
>> + (void **)&resp, &resp_size);
>> + if (ret)
>> + return ret;
>> +
>> + if (resp_size < sizeof(*resp) ||
>> + (sizeof(*resp->entries) && (resp->n_entries > U32_MAX /
>> sizeof(*resp->entries))) ||
>> + (resp_size != sizeof(*resp) + (resp->n_entries *
>> sizeof(*resp->entries)))) {
>> + ret = -EIO;
>> + goto out;
>> + }
>> +
>> + *resources = kmemdup(resp->entries, (resp->n_entries *
>> sizeof(*resp->entries)), GFP_KERNEL);
>
> If *resources is NULL you should return -ENOMEM.
>
>> + ret = resp->n_entries;
>> +
>> +out:
>> + kfree(resp);
>> + return ret;
>> +}
>> +EXPORT_SYMBOL_GPL(gh_rm_get_hyp_resources);
>> +
>> +/**
>> + * gh_rm_get_vmid() - Retrieve VMID of this virtual machine
>> + * @vmid: Filled with the VMID of this VM
>> + */
>> +int gh_rm_get_vmid(struct gh_rm_rpc *rm, u16 *vmid)
>> +{
>> + static u16 cached_vmid = GH_VMID_INVAL;
>
> This cached VMID seems like a good idea, but you don't actually use it.
>
>> + void *resp;
>> + size_t resp_size;
>> + int ret;
>> + int payload = 0;
>
> In fact the payload has type __le16 (or at least I assume so).
> I don't actually know what this payload represents. Is it a
> VMID (which would I presume in this case be GH_VMID_SELF)?
> How big is it, actually?
>
> If you were to define each request and response, I would hope
> each would be a packed structure, so in this case:
>
> struct gh_rm_get_vmid_request {
> __le16 vmid;
> u16 reserved; /* ??? */
> } __packed;
>
The payload here is a dummy payload and not used due to some earlier
limitations in RM's handling of messages. This brought up the point
again, and they have fixed the limitation. I should've commented that it
is empty payload without meaning. In v9, I'll simply remove the payload
entirely.
>> +
>> + if (cached_vmid != GH_VMID_INVAL) {
>> + *vmid = cached_vmid;
>> + return 0;
>> + }
>> +
>> + ret = gh_rm_call(rm, GH_RM_RPC_VM_GET_VMID, &payload,
>> sizeof(payload), &resp, &resp_size);
>> + if (ret)
>> + return ret;
>> +
>> + if (resp_size != sizeof(*vmid))
>> + return -EIO;
>> + *vmid = *(u16 *)resp;
>
> This is a bug. The response is little endian, so you need to do:
>
> *vmid = le16_to_32(resp);
>
>> + kfree(resp);
>> +
>> + return ret;
>> +}
>> +EXPORT_SYMBOL_GPL(gh_rm_get_vmid);
>> diff --git a/include/linux/gunyah_rsc_mgr.h
>> b/include/linux/gunyah_rsc_mgr.h
>> index b4f55c19954b..eb94b48c41de 100644
>> --- a/include/linux/gunyah_rsc_mgr.h
>> +++ b/include/linux/gunyah_rsc_mgr.h
>> @@ -15,4 +15,54 @@
>> /* Gunyah recognizes VMID0 as an alias to the current VM's ID */
>> #define GH_VMID_SELF 0
>
> Please be sure to use this symbol in place of 0 when that's
> what you mean. In gh_rm_get_vmid() it looked like *maybe*
> that's what was being passed as an argument in the request
> payload--and if so, that's a case I'd like to see GH_VMID_SELF.
>
> -Alex
>
>> +struct gh_rm_rpc;
>> +
>> +enum gh_rm_vm_status {
>> + GH_RM_VM_STATUS_NO_STATE = 0,
>> + GH_RM_VM_STATUS_INIT = 1,
>> + GH_RM_VM_STATUS_READY = 2,
>> + GH_RM_VM_STATUS_RUNNING = 3,
>> + GH_RM_VM_STATUS_PAUSED = 4,
>> + GH_RM_VM_STATUS_LOAD = 5,
>> + GH_RM_VM_STATUS_AUTH = 6,
>> + GH_RM_VM_STATUS_INIT_FAILED = 8,
>> + GH_RM_VM_STATUS_EXITED = 9,
>> + GH_RM_VM_STATUS_RESETTING = 10,
>> + GH_RM_VM_STATUS_RESET = 11,
>> +};
>> +
>> +/* RPC Calls */
>> +int gh_rm_alloc_vmid(struct gh_rm_rpc *rm, u16 vmid);
>> +int gh_rm_dealloc_vmid(struct gh_rm_rpc *rm, u16 vmid);
>> +int gh_rm_vm_start(struct gh_rm_rpc *rm, u16 vmid);
>> +int gh_rm_vm_stop(struct gh_rm_rpc *rm, u16 vmid);
>> +
>> +enum gh_rm_vm_auth_mechanism {
>> + GH_RM_VM_AUTH_NONE = 0,
>> + GH_RM_VM_AUTH_QCOM_PIL_ELF = 1,
>> + GH_RM_VM_AUTH_QCOM_ANDROID_PVM =2,
>> +};
>> +
>> +int gh_rm_vm_configure(struct gh_rm_rpc *rm, u16 vmid, enum
>> gh_rm_vm_auth_mechanism auth_mechanism,
>> + u32 mem_handle, u64 image_offset, u64 image_size,
>> + u64 dtb_offset, u64 dtb_size);
>> +int gh_rm_vm_init(struct gh_rm_rpc *rm, u16 vmid);
>> +
>> +struct gh_rm_hyp_resource {
>> + u8 type;
>> + u8 reserved;
>> + __le16 partner_vmid;
>> + __le32 resource_handle;
>> + __le32 resource_label;
>> + __le64 cap_id;
>> + __le32 virq_handle;
>> + __le32 virq;
>> + __le64 base;
>> + __le64 size;
>> +} __packed;
>> +
>> +ssize_t gh_rm_get_hyp_resources(struct gh_rm_rpc *rm, u16 vmid,
>> + struct gh_rm_hyp_resource **resources);
>> +int gh_rm_get_vmid(struct gh_rm_rpc *rm, u16 *vmid);
>> +
>> #endif
>
* Elliot Berman <[email protected]> [2022-12-19 14:58:35]:
> static int gh_vm_release(struct inode *inode, struct file *filp)
> {
> struct gunyah_vm *ghvm = filp->private_data;
> + struct gunyah_vm_memory_mapping *mapping, *tmp;
>
> + list_for_each_entry_safe(mapping, tmp, &ghvm->memory_mappings, list) {
> + gh_vm_mem_mapping_reclaim(ghvm, mapping);
kfree(mapping) also?
> + }
> put_gh_rm(ghvm->rm);
> kfree(ghvm);
> return 0;
[snip]
> +struct gunyah_vm_memory_mapping {
> + struct list_head list;
> + enum gunyah_vm_mem_share_type share_type;
> + struct gh_rm_mem_parcel parcel;
> +
> + __u64 guest_phys_addr;
> + __u32 mem_size;
'gh_userspace_memory_region' allows 64-bit mem_size, so perhaps make it 64-bit
here as well?
> +struct gunyah_vm_memory_mapping *gh_vm_mem_mapping_alloc(struct gunyah_vm *ghvm,
> + struct gh_userspace_memory_region *region)
> +{
> + phys_addr_t curr_page, prev_page;
> + struct gunyah_vm_memory_mapping *mapping, *tmp_mapping;
> + struct gh_rm_mem_entry *mem_entries;
> + int i, j, pinned, ret = 0;
> + struct gh_rm_mem_parcel *parcel;
> +
> + if (!region->memory_size || !PAGE_ALIGNED(region->memory_size) ||
> + !PAGE_ALIGNED(region->userspace_addr))
> + return ERR_PTR(-EINVAL);
> +
> + ret = mutex_lock_interruptible(&ghvm->mm_lock);
> + if (ret)
> + return ERR_PTR(ret);
> + mapping = __gh_vm_mem_mapping_find(ghvm, region->label);
> + if (mapping) {
> + ret = -EEXIST;
> + goto unlock;
We should avoid kfree(mapping) in this case?
> + }
> +
> + mapping = kzalloc(sizeof(*mapping), GFP_KERNEL);
> + if (!mapping) {
> + ret = -ENOMEM;
> + goto unlock;
> + }
> +
> + mapping->parcel.label = region->label;
> + mapping->guest_phys_addr = region->guest_phys_addr;
> + mapping->npages = region->memory_size >> PAGE_SHIFT;
> + parcel = &mapping->parcel;
> + parcel->mem_handle = GH_MEM_HANDLE_INVAL; /* to be filled later by mem_share/mem_lend */
> + parcel->mem_type = GH_RM_MEM_TYPE_NORMAL;
> +
> + /* Check for overlap */
> + list_for_each_entry(tmp_mapping, &ghvm->memory_mappings, list) {
> + if (!((mapping->guest_phys_addr + (mapping->npages << PAGE_SHIFT) <=
> + tmp_mapping->guest_phys_addr) ||
> + (mapping->guest_phys_addr >=
> + tmp_mapping->guest_phys_addr + (tmp_mapping->npages << PAGE_SHIFT)))) {
> + ret = -EEXIST;
> + goto unlock;
> + }
> + }
> +
> + list_add(&mapping->list, &ghvm->memory_mappings);
I think we need to either avoid adding to the list this early (until all steps
below are successfull) or maintain some additional state in 'mapping' to
indicate that its work in progress. Consider the race condition for example when
multiple threads call SET_USER_MEM_REGION ioctl on same label (one with size > 0
and other with size = 0), which can lead to unpleasant outcome AFAICS.
> +unlock:
> + mutex_unlock(&ghvm->mm_lock);
> + if (ret)
> + goto free_mapping;
> +
> + mapping->pages = kcalloc(mapping->npages, sizeof(*mapping->pages), GFP_KERNEL);
> + if (!mapping->pages) {
> + ret = -ENOMEM;
> + goto reclaim;
Can you check this error path? We seem to call unpin_user_page() in this path,
which is not correct.
> + }
> +
> + pinned = pin_user_pages_fast(region->userspace_addr, mapping->npages,
> + FOLL_WRITE | FOLL_LONGTERM, mapping->pages);
> + if (pinned < 0) {
> + ret = pinned;
> + goto reclaim;
Same comment as above
> + parcel->acl_entries[0].vmid = ghvm->vmid;
cpu_to_le() wrapper missing here and few other places in this function. Pls check.
On 1/17/2023 2:33 PM, Alex Elder wrote:
> On 12/19/22 4:58 PM, Elliot Berman wrote:
>> The resource manager is a special virtual machine which is always
>> running on a Gunyah system. It provides APIs for creating and destroying
>> VMs, secure memory management, sharing/lending of memory between VMs,
>> and setup of inter-VM communication. Calls to the resource manager are
>> made via message queues.
>
> I mentioned in an earlier message that I did a really thorough
> review of this. Actually I haven't reviewed the interrupt part
> yet, but I'll get to that. I want to get my comments out now.
>
> I found one bug, but I'd guess it's not likely to occur (though
> user space can trigger it so it's serious). You'll see it below.
>
>> This patch implements the basic probing and RPC mechanism to make those
>> API calls. Request/response calls can be made with gh_rm_call.
>> Drivers can also register to notifications pushed by RM via
>> gh_rm_register_notifier
>
> Is there a human-readable document that explains the RPC
> protocol and formats? (It would provide a way to compare
> the implementation with the intended design.)
>
> Here's what I see:
>
> It appears that each sent message is delivered exactly once,
> and messages arrive in order and are assumed to be intact.
>
> A Gunyah message holds a 240 byte maximum payload, while a
> resource manager RPC message (carried in a Gunyah message)
> has a 232 byte maximum payload.
>
> An RPC "connection" is either a notification message (only
> *from* the resource manager) or a request/response message
> pair sharing the same sequence ID (with the request only
> going *to* the resource manager). An RPC "connection"
> consists of a single RPC message (of any of the three types)
> followed by 0-62 "fragment" messages, where the payload for
> each message is concatenated by the receiver into a single
> buffer (up to 63 * 232 = 14616 bytes in size).
>
> An RPC header contains a sequence ID that's unique for
> each active "connection" to the resource manager (and
> ignored for notifications). It also contains a message
> ID supplied by the user of the resource manager interface
> (e.g., a VM); the message ID in a response must match the
> message ID in its matching request.
>
This is the correct understanding. There is a document, I will work with
the right folks to see how we make it available.
>> Specific API calls that resource manager supports will be implemented in
>> subsequent patches.
>>
>> Signed-off-by: Elliot Berman <[email protected]>
>> ---
>> MAINTAINERS | 2 +-
>> drivers/virt/gunyah/Kconfig | 14 +
>> drivers/virt/gunyah/Makefile | 3 +
>> drivers/virt/gunyah/rsc_mgr.c | 584 +++++++++++++++++++++++++++++++++
>> drivers/virt/gunyah/rsc_mgr.h | 36 ++
>> include/linux/gunyah_rsc_mgr.h | 18 +
>> 6 files changed, 656 insertions(+), 1 deletion(-)
>> create mode 100644 drivers/virt/gunyah/rsc_mgr.c
>> create mode 100644 drivers/virt/gunyah/rsc_mgr.h
>> create mode 100644 include/linux/gunyah_rsc_mgr.h
>>
>> diff --git a/MAINTAINERS b/MAINTAINERS
>> index 667480bfd387..bb315385e155 100644
>> --- a/MAINTAINERS
>> +++ b/MAINTAINERS
>> @@ -8944,7 +8944,7 @@ F: Documentation/virt/gunyah/
>> F: arch/arm64/gunyah/
>> F: drivers/mailbox/gunyah-msgq.c
>> F: drivers/virt/gunyah/
>> -F: include/linux/gunyah.h
>> +F: include/linux/gunyah*.h
>> HABANALABS PCI DRIVER
>> M: Oded Gabbay <[email protected]>
>> diff --git a/drivers/virt/gunyah/Kconfig b/drivers/virt/gunyah/Kconfig
>> index 127156a678a6..4a6189dedab2 100644
>> --- a/drivers/virt/gunyah/Kconfig
>> +++ b/drivers/virt/gunyah/Kconfig
>> @@ -10,3 +10,17 @@ config GUNYAH
>> Say Y/M here to enable the drivers needed to interact in a Gunyah
>> virtual environment.
>> +
>> +if GUNYAH
>> +config GUNYAH_RESOURCE_MANAGER
>
> Is Gunyah useful without the resource manager?
>
> There might be good reasons to have different kernel config
> options for parts of Gunyah (like, perhaps you want to allow
> a *different* resource manager to be used). But if, overall,
> a component is fundamentally required for Gunyah to operate,
> I don't think it's helpful to configure it separately.
>
> This comment applies to all your config options. I won't
> mention it again, but maybe you can consider it, and for
> anything that's pretty fundamental, don't make it an option.
>
I've removed GUNYAH_MESSAGE_QUEUES, GUNYAH_RESOURCE_MANAGER, and
GUNYAH_VM_MANAGER.
These config options remain:
- GUNYAH (Patch 5)
- GUNYAH_PLATFORM_HOOKS (Patch 17)
- SAMPLE_GUNYAH (Patch 16)
From the RFC patches, I'm planning on keeping all the options
introduced there since they are not fundamental to launching a VM.
- GUNYAH_VCPU (Patch 25)
- GUNYAH_IRQFD (Patch 27)
- GUNYAH_IOEVENTFD (Patch 28)
>> + tristate "Gunyah Resource Manager"
>> + depends on MAILBOX
>> + select GUNYAH_MESSAGE_QUEUES
>> + help
>> + The resource manager (RM) is a privileged application VM
>> supporting
>> + the Gunyah Hypervisor. Enable this driver to support communicating
>> + with Gunyah RM. This is typically required for a VM running under
>> + Gunyah wanting to have Gunyah-awareness.
>> +
>> + Say Y/M here if unsure.
>> +endif
>> diff --git a/drivers/virt/gunyah/Makefile b/drivers/virt/gunyah/Makefile
>> index 2ac4ee64b89d..e7cf59b9e64e 100644
>> --- a/drivers/virt/gunyah/Makefile
>> +++ b/drivers/virt/gunyah/Makefile
>> @@ -1 +1,4 @@
>> obj-$(CONFIG_GUNYAH) += gunyah.o
>> +
>> +gunyah_rsc_mgr-y += rsc_mgr.o
>> +obj-$(CONFIG_GUNYAH_RESOURCE_MANAGER) += gunyah_rsc_mgr.o
>> diff --git a/drivers/virt/gunyah/rsc_mgr.c
>> b/drivers/virt/gunyah/rsc_mgr.c
>> new file mode 100644
>> index 000000000000..6b281576f5af
>> --- /dev/null
>> +++ b/drivers/virt/gunyah/rsc_mgr.c
>> @@ -0,0 +1,584 @@
>> +// SPDX-License-Identifier: GPL-2.0-only
>> +/*
>> + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights
>> reserved.
>> + */
>> +
>> +#include <linux/of.h>
>> +#include <linux/slab.h>
>> +#include <linux/mutex.h>
>> +#include <linux/sched.h>
>> +#include <linux/gunyah.h>
>> +#include <linux/module.h>
>> +#include <linux/of_irq.h>
>> +#include <linux/kthread.h>
>> +#include <linux/notifier.h>
>> +#include <linux/workqueue.h>
>> +#include <linux/completion.h>
>> +#include <linux/gunyah_rsc_mgr.h>
>> +#include <linux/platform_device.h>
>> +
>> +#include "rsc_mgr.h"
>> +
>> +/* Resource Manager Header */
>> +struct gh_rm_rpc_hdr {
>
> This is a style thing, but I prefer *not* having struct
> definitions with related value definitions interleaved.
> My preference would be to define the values above the
> struct definitions. A comment can indicate which field
> a given group of definitions is associated with. And
> if it's ambiguous, the symbol names can further tie
> them together.
>
Done.
>> +#define RM_RPC_HDR_VERSION_ONE 0x1
>
> I don't think HEADER_VERSION_ONE is any more understandable
> than just "1" if that's its value. Unless you're envisioning
> a more complex versioning scheme, using 1, 2, 3 (etc.) for
> the versions is cleaner than VERSION_ONE, VERSION_TWO,
> VERSION_THREE, etc.
> >> +#define RM_RPC_API_VERSION_MASK GENMASK(3, 0)
>> +
>> +#define RM_RPC_HDR_WORDS 0x2
>
> Here's another case where it might just be simpler to open-code
> the numeric value in the code. Even better, I believe this
> represents the number of words in an RPC header. That is better
> expressed as:
> (sizeof(struct gh_rm_rpc_hdr) / sizeof(u32))
>
> At this point it seems there *is* only one version. Even so,
> the point of this (currently anyway) is to define the Gunyah
> RPC API that *this driver* supports. That doesn't change for
> any message header, so maybe you could just define something
> like this:
>
> #define RM_RPC_API_VERSION FIELD_PREP(RM_RPC_API_VERSION_MASK, 1)
> #define RM_RPC_HDR_WORDS FIELD_PREP(RM_RPC_HEADER_WORDS_MASK,
> (sizeof(structgh_rm_rpc_hdr) /
> sizeof(u32))
> #define RM_RPC_API (RM_RPC_API_VERSION | RM_RPC_HDR_WORDS)
>
> Then you can use RM_RPC_API in gh_rm_send_request() to fill in
> the api field of the RPC message header. You could also check
> it in gh_rm_msgq_rx_data() (something you don't currently do).
> This might look a little ugly, but it simplifies the code where
> it's used.
>
I like this approach, it doesn't seem uglier to me.
>> +#define RM_RPC_HEADER_WORDS_MASK GENMASK(7, 4)
>> + u8 api;
>> +
>> +#define RM_RPC_TYPE_CONT 0x0
>> +#define RM_RPC_TYPE_REQ 0x1
>
> s/REQ/REQUEST/ (maybe)
>
> Also there is a missing tab between the name and its value.
>
>> +#define RM_RPC_TYPE_RPLY 0x2
>
> s/RPLY/REPLY/
>
>> +#define RM_RPC_TYPE_NOTIF 0x3
>> +#define RM_RPC_TYPE_MASK GENMASK(1, 0)
>> +
>> +#define GH_RM_MAX_NUM_FRAGMENTS 62
>> +#define RM_RPC_FRAGMENTS_MASK GENMASK(7, 2)
>> + u8 type;
>> + __le16 seq;
>> + __le32 msg_id;
>> +} __packed;
>> +
>> +/* Standard reply header */
>> +struct gh_rm_rpc_reply_hdr {
>> + struct gh_rm_rpc_hdr rpc_hdr;
>> + u32 err_code;
>
> Why is endianness represented in the sequence numbe r and
> message id in the header, but not in the error code here?
> Is the error code set by local software or something?
>
I had missed adding this in the explicit le32_to_cpu conversions, it's
added now.
> The values that can land in err_code are defined in
> "rsc_mgr.h" defined at the end of this patch. Some
> examples are GH_RM_ERROR_OK and GH_RM_ERROR_NOMEM.
> Please indicate that somehow, for example:
>
> u32 err_code; /* GH_RM_ERROR_**/
>
Done.
> These values are (almost) always positive. But I see
> two problems:
> - If non-zero, this value is returned by gh_rm_call(), and
> that function is expected to return a *negative* value
> on error.
> - RM_ERROR_UNIMPLEMENTED is defined as 0xffffffff; that
> value, when interpreted as a signed int, is -1, which
> if interpreted as an errno is EPERM. That is at best
> a misleading result if returned by gh_rm_call().
>
Done. I've added retry logic for GH_ERROR_RETRY (so far never returned
by resource manager, but it's in the spec).
>> +} __packed;
>> +
>> +#define GH_RM_MAX_MSG_SIZE (GH_MSGQ_MAX_MSG_SIZE - sizeof(struct
>> gh_rm_rpc_hdr))
>> +
>> +/**
>> + * struct gh_rm_connection - Represents a complete message from
>> resource manager
>> + * @payload: Combined payload of all the fragments (msg headers
>> stripped off).
>> + * @size: Size of the payload.
>
> The size is the number of bytes of payload received *so far* (in
> case there are fragments). It is not the size of the payload
> until it has been completely received.
>
I've updated the comment to mention this.
>> + * @ret: Linux return code, set in case there was an error processing
>> connection
>> + * @msg_id: Message ID from the header.
>> + * @type: RM_RPC_TYPE_RPLY or RM_RPC_TYPE_NOTIF.
>> + * @num_fragments: total number of fragments expected to be received.
>> + * @fragments_received: fragments received so far.
>> + * @rm_error: For request/reply sequences with standard replies.
>> + * @seq: Sequence ID for the main message.
>> + * @seq_done: Signals caller that the RM reply has been received
>> + */
>> +struct gh_rm_connection {
>> + void *payload;
>> + size_t size;
>> + int ret;
>> + u32 msg_id;
>
> The msg_id field is actually opaque from this driver's perspective.
> The only thing that matters is if a response message ID *matches*
> a request message ID. It could be maintained in __le32 byte order
> (as received).
>
Done.
>> + u8 type;
>> +
>> + u8 num_fragments;
>> + u8 fragments_received;
>> +
>
> The next three fields are (as the comment says) only used for
> request/reply sequences. You could put them in a structure,
> then have that structure share space in a union with a work
> structure used for notification messages. That could replace
> struct gh_rm_notif_complete (which would become unnecessary).
>
Done.
>> + /* only for req/reply sequence */
>> + u32 rm_error;
>> + u16 seq;
>> + struct completion seq_done;
>> +};
>> +
>> +struct gh_rm_notif_complete {
>> + struct gh_rm_connection *conn;
>> + struct work_struct work;
>> +};
>> +
>> +struct gh_rm_rpc {
>
> I actually think this structure should just be called "gh_rm".
> Yes, it's the RPC interface for the resource manager, but this
> interface (which happens to use RPC) abstractly represents the
> resource manager *itself*.
>
Done.
>> + struct device *dev;
>> + struct gunyah_resource tx_ghrsc, rx_ghrsc;
>> + struct gh_msgq msgq;
>> + struct mbox_client msgq_client;
>> + struct gh_rm_connection *active_rx_connection;
>> + int last_tx_ret;
>> +
>> + struct idr call_idr;
>> + struct mutex call_idr_lock;
>> +
>> + struct mutex send_lock;
>> +
>> + struct work_struct recv_work;
>> +};
>> +
>> +static struct gh_rm_connection *gh_rm_alloc_connection(u32 msg_id, u8
>> type)
>> +{
>> + struct gh_rm_connection *connection;
>> +
>> + connection = kzalloc(sizeof(*connection), GFP_KERNEL);
>> + if (!connection)
>> + return NULL;
>> +
>> + connection->type = type;
>> + connection->msg_id = msg_id;
>> +
>> + return connection;
>> +}
>> +
>> +static int gh_rm_init_connection_payload(struct gh_rm_connection
>> *connection, void *msg,
>> + size_t hdr_size, size_t msg_size)
>> +{
>> + struct gh_rm_rpc_hdr *hdr = msg;
>> + size_t max_buf_size, payload_size;
>> +
>> + if (hdr_size > msg_size)
>> + return -EINVAL;
>> +
>> + payload_size = msg_size - hdr_size;
>
> The maximum payload size is 14616 bytes. The msg_size comes
> from the resource manager--which I guess is trustworthy. But
> you're already checking for a short message above, so you
> might as well check for a message that's too big as well.
> (Or just decide not to check either...)
>
Done.
>> + connection->num_fragments = FIELD_GET(RM_RPC_FRAGMENTS_MASK,
>> hdr->type);
>
> Here too, num_fragments had better not be more than
> GH_RM_MAX_NUM_FRAGMENTS (= 62).
>
>> + connection->fragments_received = 0;
>> +
>> + /* There's not going to be any payload, no need to allocate
>> buffer. */
>> + if (!payload_size && !connection->num_fragments)
>> + return 0;
>> +
>> + /*
>> + * maximum payload size is GH_MSGQ_MAX_MSG_SIZE- hdr_size
>> + * and can received (hdr->fragments + 1) of those
>> + */
>> + max_buf_size = (GH_MSGQ_MAX_MSG_SIZE - hdr_size) *
>> (connection->num_fragments + 1);
>> +
>> + connection->payload = kzalloc(max_buf_size, GFP_KERNEL);
>> + if (!connection->payload)
>> + return -ENOMEM;
>> +
>> + memcpy(connection->payload, msg + hdr_size, payload_size);
>> + connection->size = payload_size;
>> + return 0;
>> +}
>> +
>> +static void gh_rm_notif_work(struct work_struct *work)
>> +{
>> + struct gh_rm_notif_complete *notif = container_of(work, struct
>> gh_rm_notif_complete, work);
>> + struct gh_rm_connection *connection = notif->conn;
>> +
>> + /* No users of notifications, yet. */
>> +
>> + kfree(connection->payload);
>> + kfree(connection);
>> + kfree(notif);
>> +}
>> +
>> +static struct gh_rm_connection *gh_rm_process_notif(struct gh_rm_rpc
>> *rsc_mgr,
>
> Here you use "rsc_mgr" as the symbol having this type, while
> elsewhere you use "rm". Pick one, and use it consistenly (I
> suggest using "rm" to represent a "struct gh_rm").
>
Done
>> + void *msg, size_t msg_size)
>> +{
>> + struct gh_rm_rpc_hdr *hdr = msg;
>> + struct gh_rm_connection *connection;
>> +
>> + connection = gh_rm_alloc_connection(le32_to_cpu(hdr->msg_id),
>
> We already know the header type is RM_RPC_TYPE_NOTIF. Better
> to state that explicitly here rather than decoding what's in
> the header (again).
>
Done.
>> + FIELD_GET(RM_RPC_TYPE_MASK, hdr->type));
>> + if (!connection) {
>> + dev_err(rsc_mgr->dev, "Failed to alloc connection for
>> notification, dropping.\n");
>> + return NULL;
>> + }
>> +
>> + if (gh_rm_init_connection_payload(connection, msg, sizeof(*hdr),
>> msg_size)) {
>
> This could conceivably return -EINVAL as well (which is not an
> allocation failure, despite what the message says). The same
> thing is done in gh_rm_process_rply(). (If the resource manager
> is as trusted as it should be, and the hardware is not broken,
> -EINVAL should never get returned...)
>
Updated the message to allow for -EINVAL.
>> + dev_err(rsc_mgr->dev, "Failed to alloc connection buffer for
>> notification, dropping.\n");
>> + kfree(connection);
>> + return NULL;
>> + }
>> +
>> + return connection;
>> +}
>> +
>> +static struct gh_rm_connection *gh_rm_process_rply(struct gh_rm_rpc
>> *rsc_mgr,
>> + void *msg, size_t msg_size)
>> +{
>> + struct gh_rm_rpc_reply_hdr *reply_hdr = msg;
>> + struct gh_rm_rpc_hdr *hdr = msg;
>
> Please use:
>
> struct gh_rm_rpc_hdr *hdr = &msg->rpc_hdr;
>
Done.
>> + struct gh_rm_connection *connection;
>> + u16 seq_id = le16_to_cpu(hdr->seq);
>> +
>> + if (mutex_lock_interruptible(&rsc_mgr->call_idr_lock))
>> + return ERR_PTR(-ERESTARTSYS);
>
> THIS IS A BUG.
>
> If this call gets interrupted, we'll return the ERESTARTSYS pointer.
> That's non-null.
>
> But the return value from gh_rm_process_rply() is blindly assigned
> to the rsc_mgr->active_rx_connection pointer in gh_rm_msgq_rx_data()
> if the RPC message type is RM_RPC_TYPE_RPLY.
>
> Thereafter, if that pointer is non-null, it's assumed to refer
> to a valid memory location.
>
> I'm not sure what the right handling is. You could save the
> return value and check it with IS_ERR_OR_NULL(). Perhaps you
> could return NULL here too, but it's actually not clear to me
> you can simply ignore this situation and have the result be
> correct.
>
I don't need interruptible lock here anyway as this function is now only
called from threaded irq context.
>> +
>> + connection = idr_find(&rsc_mgr->call_idr, seq_id);
>> + mutex_unlock(&rsc_mgr->call_idr_lock);
>> +
>> + if (!connection) {
>> + dev_err(rsc_mgr->dev, "Failed to find connection for sequence
>> %u\n", seq_id);
>> + return NULL;
>> + }
>> + if (connection->msg_id != le32_to_cpu(hdr->msg_id)) {
>> + dev_err(rsc_mgr->dev, "Replyfor sequence %u expected msg_id:
>> %x but got %x\n",
>> + seq_id, connection->msg_id, le32_to_cpu(hdr->msg_id));
>> + /*
>> + * Don't complete connection and error the client, maybe
>> + * resource manager will send us the expected reply sequence
>> soon.
>> + */
>> + return NULL;
>> + }
>> +
>> + if (gh_rm_init_connection_payload(connection, msg,
>> sizeof(*reply_hdr), msg_size)) {
>> + dev_err(rsc_mgr->dev, "Failed to alloc connection buffer for
>> sequence %d\n",
>> + seq_id);
>> + /* Send connection complete and error the client. */
>> + connection->ret = -ENOMEM;
>> + complete(&connection->seq_done);
>> + return NULL;
>> + }
>> +
>> + connection->rm_error = reply_hdr->err_code;
>> + return connection;
>
> Since you're already passing in the resource manager as an argument,
> you could take care of assigning its ->connection pointer here as well.
> Doing it this way *might* clean up the caller a bit (but sometimes
> you have to try it to see).
>
In the current implementation, all of the handling
"active_rx_connection" is done just in gh_rm_msgq_rx_data, which I think
makes it easier to track how it's being used. If it's spread it out to
gh_rm_process_*, I think it gets harder to follow if someone is new to
the code.
>> +}
>> +
>> +static void gh_rm_process_cont(struct gh_rm_rpc *rsc_mgr, struct
>> gh_rm_connection *connection,
>> + void *msg, size_t msg_size)
>> +{
>> + struct gh_rm_rpc_hdr *hdr = msg;
>> + size_t payload_size = msg_size - sizeof(*hdr);
>
> Maybe:
> void *payload = hdr + 1;
>
> Here too, the message size is coming from the resource manager.
> But it seems prudent to ensure msg_size is >= sizeof(*hdr)
> and <= GH_MSGQ_MAXMSG_SIZE.
>
The max msg_size comes from hypervisor, but I will add a check that all
the messages we receive are <= GH_MSGQ_MAX_MSG_SIZE.
>> +
>> + /*
>> + * hdr->fragments and hdr->msg_id preserves thevalue from first
>> reply
>> + * or notif message. To detect mishandling, check it's still intact.
>> + */
>> + if (connection->msg_id != le32_to_cpu(hdr->msg_id))
>> + dev_err(rsc_mgr->dev, "Appending mismatched continuation with
>> id %d to connection with id %d\n",
>> + le32_to_cpu(hdr->msg_id), connection->msg_id);
>> + if (connection->num_fragments != FIELD_GET(RM_RPC_FRAGMENTS_MASK,
>> hdr->type))
>> + dev_err(rsc_mgr->dev, "Number of fragments mismatch for seq:
>> %d\n",
>> + le16_to_cpu(hdr->seq));
>
> The sequencer ID is irrelevant on a notification "connection".
> Maybe you should report the message type too?
>
> You copy the contents into the receive buffer anyway?
>
> I think you should drop the remainder of the "connection"
> if you see *any* corruption like this. (I would argue
> this is a bug as well, though maybe it "never happens.")
>
This is currently a case of "never happens". I think dropping remainder
of the connection when this corruption happens is fine approach, too.
I'll do that.
>> +
>> + memcpy(connection->payload + connection->size, msg +
>> sizeof(*hdr), payload_size);
>> + connection->size += payload_size;
>> + connection->fragments_received++;
>> +}
>> +
>> +static bool gh_rm_complete_connection(struct gh_rm_rpc *rsc_mgr,
>> + struct gh_rm_connection *connection)
>
> This is called in one place, and it is always passed the active RX
> connection pointer. If true is returned, that pointer is nulled.
> You could drop the second argument and just do the assignment
> here instead. (With the above-mentioned bug, this is also a
> place where the non-null ERESTARTSYS pointer gets dererferenced.)
>
Same as above comment about tracking active_rx_connection.
>> +{
>> + struct gh_rm_notif_complete *notif_work;
>> +
>> + if (!connection)
>> + return false;
>> +
>> + if (connection->fragments_received != connection->num_fragments)
>> + return false;
>> +
>> + switch (connection->type) {
>> + case RM_RPC_TYPE_RPLY:
>> + complete(&connection->seq_done);
>> + break;
>> + case RM_RPC_TYPE_NOTIF:
>> + notif_work = kzalloc(sizeof(*notif_work), GFP_KERNEL);
>> + if (notif_work == NULL)
>> + break;
>
> Is there any signficant consequence possible from quietly
> ignoring a notification from the resource manager? Should
> this situation be reported at least?
>
> If you embedded the work structure in the connection structure
> as I mentioned earlier, you could avoid this ever being a
> problem.
>
Done by embedding the work structure in connection structure.
>> +
>> + notif_work->conn = connection;
>> + INIT_WORK(¬if_work->work,gh_rm_notif_work);
>> +
>> + schedule_work(¬if_work->work);
>> + break;
>> + default:
>> + dev_err(rsc_mgr->dev, "Invalid message type (%d) received\n",
>> connection->type);
>> + break;
>> + }
>> +
>> + return true;
>> +}
>> +
>> +static void gh_rm_abort_connection(struct gh_rm_connection *connection)
>
> Although both callers immediately overwrite the rm->active_rx_connection
> pointer after this call, you could pass the resource manager pointer
> and make it NULL after the abort, so the old pointer is *gone*.
>
>> +{
>> + switch (connection->type) {
>> + case RM_RPC_TYPE_RPLY:
>> + connection->ret = -EIO;
>> + complete(&connection->seq_done);
>> + break;
>> + case RM_RPC_TYPE_NOTIF:
>> + fallthrough;
>
> The case statement isn't really valuable here. You could
> just do:
>
> if (connection->type == RM_RPC_TYPE_RPLY) {
> ... complete
> } else {
> ... free
> }
>
> (Fine as-is though.)
>
>> + default:
>> + kfree(connection->payload);
>> + kfree(connection);
>> + }
>> +}
>> +
>> +static void gh_rm_msgq_rx_data(struct mbox_client *cl, void *mssg)
>> +{
>> + struct gh_rm_rpc *rsc_mgr = container_of(cl, struct gh_rm_rpc,
>> msgq_client);
>> + struct gh_msgq_rx_data *rx_data = mssg;
>> + void *msg = rx_data->data;
>> + size_t msg_size = rx_data->length;
>> + struct gh_rm_rpc_hdr *hdr;
>> +
>> + if (msg_size <= sizeof(struct gh_rm_rpc_hdr)) {
>> + dev_err(rsc_mgr->dev, "Incomplete message size: %ld is too
>> small\n", msg_size);
>> + return;
>> + }
>
> This is received data, so as long as you're verifying the message
> size you should also ensure it's within the maximum allowed. You
> should also verify the header api byte is correct.
>
Done.
>> +
>> + hdr = msg;
>> + switch (FIELD_GET(RM_RPC_TYPE_MASK, hdr->type)) {
>> + case RM_RPC_TYPE_NOTIF:
>
> Here and in the other cases below, I think it's cleaner if you
> move the check of the active_rx_connection poiner (whether it's
> NULL or not) into the called functions. That way they're all
> checking based on their own requirements rather than having
> that logic be here.
>
>> + if (rsc_mgr->active_rx_connection) {
>> + /* Not possible per protocol. Do something better than
>> BUG_ON */
>> + dev_err(rsc_mgr->dev, "Received start of new notification
>> without finishing existing message series.\n");
>> + gh_rm_abort_connection(rsc_mgr->active_rx_connection);
>> + }
>> + rsc_mgr->active_rx_connection = gh_rm_process_notif(rsc_mgr,
>> msg, msg_size);
>> + break;
>> + case RM_RPC_TYPE_RPLY:
>> + if (rsc_mgr->active_rx_connection) {
>> + /* Not possible per protocol. Do something better than
>> BUG_ON */
>> + dev_err(rsc_mgr->dev, "Received start of new reply
>> without finishing existing message series.\n");
>> + gh_rm_abort_connection(rsc_mgr->active_rx_connection);
>> + }
>> + rsc_mgr->active_rx_connection = gh_rm_process_rply(rsc_mgr,
>> msg, msg_size);
>> + break;
>> + case RM_RPC_TYPE_CONT:
>> + if (!rsc_mgr->active_rx_connection) {
>> + dev_err(rsc_mgr->dev, "Received a continuation message
>> without receiving initial message\n");
>> + break;
>> + }
>> + gh_rm_process_cont(rsc_mgr, rsc_mgr->active_rx_connection,
>> msg, msg_size);
>> + break;
>> + default:
>> + dev_err(rsc_mgr->dev, "Invalid message type (%lu) received\n",
>> + FIELD_GET(RM_RPC_TYPE_MASK, hdr->type));
>> + return;
>> + }
>> +
>> + if (gh_rm_complete_connection(rsc_mgr,
>> rsc_mgr->active_rx_connection))
>> + rsc_mgr->active_rx_connection = NULL;
>> +}
>> +
>> +static void gh_rm_msgq_tx_done(struct mbox_client *cl, void *mssg,
>> int r)
>> +{
>> + struct gh_rm_rpc *rsc_mgr = container_of(cl, struct gh_rm_rpc,
>> msgq_client);
>> +
>> + kfree(mssg);
>> + rsc_mgr->last_tx_ret = r;
>> +}
>> +
>
> It is allowable to pass a null req_buff pointer here. The handling in
> that case is a little different, and I think it's important to both
> state that a null pointer is OK, and to explain what it means (or
> how its processing is different).
>
Done.
>> +static int gh_rm_send_request(struct gh_rm_rpc *rsc_mgr, u32 message_id,
>> + const void *req_buff, size_t req_buff_size,
>> + struct gh_rm_connection *connection)
>> +{
>> + size_t buff_size_remaining = req_buff_size;
>> + const void *req_buff_curr = req_buff;
>> + struct gh_rm_rpc_hdr *hdr;
>> + u32 cont_fragments = 0;
>> + size_t payload_size;
>> + struct gh_msgq_tx_data *msg;
>> + int i, ret;
>> +
>> + if (req_buff_size)
>> + cont_fragments = (req_buff_size - 1) / GH_RM_MAX_MSG_SIZE;
>> +
>> + if (WARN(cont_fragments > GH_RM_MAX_NUM_FRAGMENTS,
>> + "Limit exceeded for the number of fragments: %u\n",
>> cont_fragments))
>> + return -E2BIG;
>
> You could just do:
>
> if (WARN(req_buff_size > GH_RM_MAX_MSG_SIZE * GHRM_MAX_NUM_FRAGMENTS,
> ...
>
> And then your loop below could just do:
>
> u8 type = RM_RPC_TYPE_REQ;
>
> while (req_buff_size) {
> payload_size = max(buff_size_remaining,
> GH_RM_MAX_MSG_SIZE);
>
> req_buff_size -= payload_size;
> ...
> msg->push = !req_buff_size;
> ...
> type = RM_RPC_TYPE_CONT;
> }
>
Done.
>> +
>> + ret = mutex_lock_interruptible(&rsc_mgr->send_lock);
>> + if (ret)
>> + return ret;
>> +
>> + /* Consider also the 'request' packet for the loop count */
>> + for (i = 0; i <= cont_fragments; i++) {
>> + if (buff_size_remaining > GH_RM_MAX_MSG_SIZE) {
>> + payload_size = GH_RM_MAX_MSG_SIZE;
>> + buff_size_remaining -= payload_size;
>> + } else {
>> + payload_size = buff_size_remaining;
>> + }
>> +
>> + msg = kzalloc(sizeof(*msg)+ GH_MSGQ_MAX_MSG_SIZE, GFP_KERNEL);
>
> Use:
> msg = kzalloc(sizeof(*msg) + payload_size, GFP_KERNEL);
>
> But even better:
> msg = kzalloc(struct_size(msg, data, payload_size),GFP_KERNEL);
>
> OR... since these buffers are always 256 bytes apiece, you could
> create a slab cache so they're allocated efficiently.
>
Will switch to kmem_cache.
>> + if (!msg) {
>> + ret = -ENOMEM;
>> + gotoout;
>> + }
>> +
>> + /* Fill header */
>> + hdr = (struct gh_rm_rpc_hdr *)msg->data;
>> + hdr->api = FIELD_PREP(RM_RPC_API_VERSION_MASK,
>> RM_RPC_HDR_VERSION_ONE) |
>> + FIELD_PREP(RM_RPC_HEADER_WORDS_MASK, RM_RPC_HDR_WORDS);
>
> I suggested defining this api value at the top, since it's
> constant for a given build.
>
Ack.
>> + hdr->type = FIELD_PREP(RM_RPC_TYPE_MASK,
>> + i == 0 ? RM_RPC_TYPE_REQ : RM_RPC_TYPE_CONT) |
>> + FIELD_PREP(RM_RPC_FRAGMENTS_MASK, cont_fragments);
>
> I think FIELD_PREP() (and u32_encode_bits()) can look a
> little cumbersome. I think this would be more readable:
>
> u8 type = i ? RM_RPC_TYPE_CONT : RM_RPC_TYPE_REQ;
>
> hdr->type = FIELD_PREP(RM_RPC_TYPE_MASK, type);
> hdr->type |= FIELD_PREP(RM_RPC_FRAGMENTS_MASK, cont_fragments);
>
>
>> + hdr->seq = cpu_to_le16(connection->seq);
>> + hdr->msg_id = cpu_to_le32(message_id);
>> +
>> + /* Copy payload */
>
> Maybe:
> void *payload = hdr + 1;
> ...
> memcpy(payload, req_buff_curr, payload_size);
>
>> + memcpy(msg->data + sizeof(*hdr), req_buff_curr, payload_size);
>> + req_buff_curr += payload_size;
>> +
>> + /* Force the last fragment to immediately alert the receiver */
>> + msg->push = i == cont_fragments;
>> + msg->length = sizeof(*hdr)+ payload_size;
>> +
>> + ret = mbox_send_message(gh_msgq_chan(&rsc_mgr->msgq), msg);
>> + if (ret < 0) {
>> + kfree(msg);
>> + break;
>> + }
>> +
>> + if (rsc_mgr->last_tx_ret) {
>> + ret = rsc_mgr->last_tx_ret;
>> + break;
>> + }
>> + }
>> +
>> +out:
>> + mutex_unlock(&rsc_mgr->send_lock);
>> + return ret < 0 ? ret : 0;
>> +}
>> +
>> +/**
>> + * gh_rm_call: Achieve request-response type communication with RPC
>
> * @rsc_mgr: ...
>
>> + * @message_id: The RM RPC message-id
>> + * @req_buff: Request buffer that contains the payload
>> + * @req_buff_size: Total size of the payload
>> + * @resp_buf: Pointer to a response buffer
>> + * @resp_buff_size: Size of the response buffer
>> + *
>> + * Make a request to the RM-VM and wait for reply back. For a successful
>> + * response, the function returns the payload. The size of the
>> payload is set in
>> + * resp_buff_size. The resp_buf should be freed by the caller.
>> + *
>> + * Context: Process context. Will sleep waiting for reply.
>> + * Return: >0 is standard reply error from RM. <0 on internal error.
>> + */
>> +int gh_rm_call(struct gh_rm_rpc *rsc_mgr, u32 message_id, void
>> *req_buff, size_t req_buff_size,
>> + void **resp_buf, size_t *resp_buff_size)
>> +{
>> + struct gh_rm_connection *connection;
>> + int ret;
>> +
>> + /* messaged_id 0 is reserved */
>> + if (!message_id)
>> + return -EINVAL;
>> +
>
> Maybe check for null rsc_mgr first.
>
Every caller is guaranteed to have valid rm pointer.
>> + if (!rsc_mgr)
>> + return -EPROBE_DEFER;
>> +
>> + connection = gh_rm_alloc_connection(message_id, RM_RPC_TYPE_RPLY);
>> + if (!connection)
>> + return -ENOMEM;
>> +
>> + init_completion(&connection->seq_done);
>> +
>> + /* Allocate a new seq number for this connection */
>> + if (mutex_lock_interruptible(&rsc_mgr->call_idr_lock)) {
>
> Maybe use the goto ... pattern here.
>
>> + kfree(connection);
>> + return -ERESTARTSYS;
>> + }
>> + connection->seq = idr_alloc_cyclic(&rsc_mgr->call_idr,
>> connection, 0, U16_MAX, GFP_KERNEL);
>
> Note that idr_alloc_cyclic() *can* return an error.
>
Done.
>> + mutex_unlock(&rsc_mgr->call_idr_lock);
>> +
>> + /* Send the request to the Resource Manager */
>> + ret = gh_rm_send_request(rsc_mgr, message_id, req_buff,
>> req_buff_size, connection);
>> + if (ret < 0)
>> + goto out;
>> +
>> + /* Wait for response */
>> + ret = wait_for_completion_interruptible(&connection->seq_done);
>> + if (ret)
>> + goto out;
>> +
>> + if (connection->ret) {
>> + ret = connection->ret;
>
> I note that this is a negative errno. (You do explain
> that non-zero return means error above.)
>
>> + kfree(connection->payload);
>> + goto out;
>> + }
>> +
>> + if (connection->rm_error) {
>> + ret = connection->rm_error;
>
> While this is positive resource manager error (with one exception
> I mentioned earlier).
>
>> + kfree(connection->payload);
>> + goto out;
>> + }
>> +
>> + *resp_buf = connection->payload;
>> + *resp_buff_size = connection->size;
>> +
>> +out:
>> + mutex_lock(&rsc_mgr->call_idr_lock);
>
> Why is the mutex_lock() here not interruptible? Or rather, if
> you can do a non-interruptible wait here, why not above?
>
I've removed the interruptible call_idr_lock because all the sections
which use are quick.
>> + idr_remove(&rsc_mgr->call_idr, connection->seq);
>> + mutex_unlock(&rsc_mgr->call_idr_lock);
>> +
>> + kfree(connection);
>> + return ret;
>> +}
>> +
>> +static int gh_msgq_platform_probe_direction(struct platform_device
>> *pdev,
>> + u8 gh_type, int idx, struct gunyah_resource *ghrsc)
>
> I think I might call this gh_msgq_platform_probe_msgq() and pass a
> Boolean indicating whether it's being called for the RX (versus TX)
> message queue. Then deduce the gh_type and the index from that
> argument.
>
Done.
> -Alex
>
>> +{
>> + int ret;
>> + struct device_node *node = pdev->dev.of_node;
>> +
>> + ghrsc->type = gh_type;
>> +
>> + ghrsc->irq = platform_get_irq(pdev, idx);
>> + if (ghrsc->irq < 0) {
>> + dev_err(&pdev->dev, "Failed to get irq%d: %d\n", idx,
>> ghrsc->irq);
>> + return ghrsc->irq;
>> + }
>> +
>> + ret = of_property_read_u64_index(node, "reg", idx,&ghrsc->capid);
>> + if (ret) {
>> + dev_err(&pdev->dev, "Failed to get capid%d: %d\n", idx, ret);
>> + return ret;
>> + }
>> +
>> + return 0;
>> +}
>> +
>> +static int gh_rm_drv_probe(struct platform_device *pdev)
>> +{
>> + struct gh_rm_rpc *rsc_mgr;
>> + int ret;
>> +
>> + rsc_mgr = devm_kzalloc(&pdev->dev, sizeof(*rsc_mgr), GFP_KERNEL);
>> + if (!rsc_mgr)
>> + return -ENOMEM;
>> +
>> + platform_set_drvdata(pdev, rsc_mgr);
>> + rsc_mgr->dev = &pdev->dev;
>> +
>> + mutex_init(&rsc_mgr->call_idr_lock);
>> + idr_init(&rsc_mgr->call_idr);
>> + mutex_init(&rsc_mgr->send_lock);
>> +
>> + ret = gh_msgq_platform_probe_direction(pdev,
>> GUNYAH_RESOURCE_TYPE_MSGQ_TX, 0,
>> + &rsc_mgr->tx_ghrsc);
>> + if (ret)
>> + return ret;
>> +
>> + ret = gh_msgq_platform_probe_direction(pdev,
>> GUNYAH_RESOURCE_TYPE_MSGQ_RX, 1,
>> + &rsc_mgr->rx_ghrsc);
>> + if (ret)
>> + return ret;
>> +
>> + rsc_mgr->msgq_client.dev = &pdev->dev;
>> + rsc_mgr->msgq_client.tx_block = true;
>> + rsc_mgr->msgq_client.rx_callback = gh_rm_msgq_rx_data;
>> + rsc_mgr->msgq_client.tx_done = gh_rm_msgq_tx_done;
>> +
>> + return gh_msgq_init(&pdev->dev, &rsc_mgr->msgq,
>> &rsc_mgr->msgq_client,
>> + &rsc_mgr->tx_ghrsc, &rsc_mgr->rx_ghrsc);
>> +}
>> +
>> +static int gh_rm_drv_remove(struct platform_device *pdev)
>> +{
>> + struct gh_rm_rpc *rm = platform_get_drvdata(pdev);
>> +
>> + mbox_free_channel(gh_msgq_chan(&rm->msgq));
>> + gh_msgq_remove(&rm->msgq);
>> +
>> + return 0;
>> +}
>> +
>> +static const struct of_device_id gh_rm_of_match[] = {
>> + { .compatible = "gunyah-resource-manager" },
>> + {}
>> +};
>> +MODULE_DEVICE_TABLE(of, gh_rm_of_match);
>> +
>> +static struct platform_driver gh_rm_driver = {
>> + .probe = gh_rm_drv_probe,
>> + .remove = gh_rm_drv_remove,
>> + .driver = {
>> + .name = "gh_rsc_mgr",
>> + .of_match_table = gh_rm_of_match,
>> + },
>> +};
>> +module_platform_driver(gh_rm_driver);
>> +
>> +MODULE_LICENSE("GPL");
>> +MODULE_DESCRIPTION("Gunyah Resource Manager Driver");
>> diff --git a/drivers/virt/gunyah/rsc_mgr.h
>> b/drivers/virt/gunyah/rsc_mgr.h
>> new file mode 100644
>> index 000000000000..b5bb36a7a4cc
>> --- /dev/null
>> +++ b/drivers/virt/gunyah/rsc_mgr.h
>> @@ -0,0 +1,36 @@
>> +/* SPDX-License-Identifier: GPL-2.0-only */
>> +/*
>> + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights
>> reserved.
>> + */
>> +#ifndef __GH_RSC_MGR_PRIV_H
>> +#define __GH_RSC_MGR_PRIV_H
>> +
>> +#include <linux/gunyah.h>
>> +#include <linux/types.h>
>> +
>> +/* RM Error codes */
>> +#define GH_RM_ERROR_OK 0x0
>> +#define GH_RM_ERROR_UNIMPLEMENTED 0xFFFFFFFF
>> +#define GH_RM_ERROR_NOMEM 0x1
>> +#define GH_RM_ERROR_NORESOURCE 0x2
>> +#define GH_RM_ERROR_DENIED 0x3
>> +#define GH_RM_ERROR_INVALID 0x4
>> +#define GH_RM_ERROR_BUSY 0x5
>> +#define GH_RM_ERROR_ARGUMENT_INVALID 0x6
>> +#define GH_RM_ERROR_HANDLE_INVALID 0x7
>> +#define GH_RM_ERROR_VALIDATE_FAILED 0x8
>> +#define GH_RM_ERROR_MAP_FAILED 0x9
>> +#define GH_RM_ERROR_MEM_INVALID 0xA
>> +#define GH_RM_ERROR_MEM_INUSE 0xB
>> +#define GH_RM_ERROR_MEM_RELEASED 0xC
>> +#define GH_RM_ERROR_VMID_INVALID 0xD
>> +#define GH_RM_ERROR_LOOKUP_FAILED 0xE
>> +#define GH_RM_ERROR_IRQ_INVALID 0xF
>> +#define GH_RM_ERROR_IRQ_INUSE 0x10
>> +#define GH_RM_ERROR_IRQ_RELEASED 0x11
>> +
>> +struct gh_rm_rpc;
>> +int gh_rm_call(struct gh_rm_rpc *rsc_mgr, u32 message_id, void
>> *req_buff, size_t req_buff_size,
>> + void **resp_buf, size_t *resp_buff_size);
>> +
>> +#endif
>> diff --git a/include/linux/gunyah_rsc_mgr.h
>> b/include/linux/gunyah_rsc_mgr.h
>> new file mode 100644
>> index 000000000000..b4f55c19954b
>> --- /dev/null
>> +++ b/include/linux/gunyah_rsc_mgr.h
>> @@ -0,0 +1,18 @@
>> +/* SPDX-License-Identifier: GPL-2.0-only */
>> +/*
>> + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights
>> reserved.
>> + */
>> +
>> +#ifndef _GUNYAH_RSC_MGR_H
>> +#define _GUNYAH_RSC_MGR_H
>> +
>> +#include <linux/list.h>
>> +#include <linux/notifier.h>
>> +#include <linux/gunyah.h>
>> +
>> +#define GH_VMID_INVAL U16_MAX
>> +
>> +/* Gunyah recognizes VMID0 as an alias to the current VM's ID */
>> +#define GH_VMID_SELF 0
>> +
>> +#endif
>