@@ -8745,6 +8745,7 @@ F: Documentation/virt/gunyah/
F: arch/arm64/gunyah/
F: drivers/virt/gunyah/
F: include/asm-generic/gunyah.h
+F: include/linux/gunyah.h
HABANALABS PCI DRIVER
M: Oded Gabbay <ogabbay@kernel.org>
@@ -26,6 +26,8 @@
| ((fn) & GH_CALL_FUNCTION_NUM_MASK))
#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.
@@ -67,5 +69,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");
@@ -1,2 +1,2 @@
-gunyah-y += sysfs.o
+gunyah-y += sysfs.o msgq.o
obj-$(CONFIG_GUNYAH) += gunyah.o
new file mode 100644
@@ -0,0 +1,192 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
+ */
+
+#include <linux/interrupt.h>
+#include <linux/gunyah.h>
+#include <linux/printk.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/wait.h>
+
+static irqreturn_t gh_msgq_irq_handler(int irq, void *data)
+{
+ struct gunyah_msgq *msgq = data;
+ void (*on_ready)(struct gunyah_msgq *msgq);
+
+ spin_lock(&msgq->lock);
+ msgq->ready = true;
+ on_ready = msgq->on_ready;
+ spin_unlock(&msgq->lock);
+
+ if (on_ready)
+ on_ready(msgq);
+
+ wake_up_interruptible_all(&msgq->wq);
+
+ return IRQ_HANDLED;
+}
+
+static int __gh_msgq_send(struct gunyah_msgq *msgq, void *buff, size_t size, u64 tx_flags)
+{
+ unsigned long flags, gh_err;
+ ssize_t ret;
+ bool ready;
+
+ spin_lock_irqsave(&msgq->lock, flags);
+ gh_err = gh_hypercall_msgq_send(msgq->ghdev.capid, size, (uintptr_t)buff, tx_flags, &ready);
+ switch (gh_err) {
+ case GH_ERROR_OK:
+ ret = 0;
+ msgq->ready = ready;
+ break;
+ case GH_ERROR_MSGQUEUE_FULL:
+ ret = -EAGAIN;
+ msgq->ready = false;
+ break;
+ default:
+ ret = gh_remap_error(gh_err);
+ break;
+ }
+ spin_unlock_irqrestore(&msgq->lock, flags);
+
+ return ret;
+}
+
+/**
+ * gh_msgq_send() - Send a message to the client running on a different VM
+ * @client: The client descriptor that was obtained via gh_msgq_register()
+ * @buff: Pointer to the buffer where the received data must be placed
+ * @buff_size: The size of the buffer space available
+ * @flags: Optional flags to pass to receive the data. For the list of flags,
+ * see linux/gunyah/gh_msgq.h
+ *
+ * Returns: The number of bytes copied to buff. <0 if there was an error.
+ *
+ * Note: this function may sleep and should not be called from interrupt context
+ */
+ssize_t gh_msgq_send(struct gunyah_msgq *msgq, void *buff, size_t size, const unsigned long flags)
+{
+ ssize_t ret;
+ u64 tx_flags = 0;
+
+ if (unlikely(msgq->ghdev.type != GUNYAH_DEVICE_TYPE_MSGQ_TX))
+ return -EINVAL;
+
+ if (flags & GH_MSGQ_TX_PUSH)
+ tx_flags |= GH_HYPERCALL_MSGQ_TX_FLAGS_PUSH;
+
+ do {
+ ret = __gh_msgq_send(msgq, buff, size, tx_flags);
+
+ if (ret == -EAGAIN) {
+ if (flags & GH_MSGQ_NONBLOCK)
+ goto out;
+ if (wait_event_interruptible(msgq->wq, msgq->ready))
+ ret = -ERESTARTSYS;
+ }
+ } while (ret == -EAGAIN);
+
+out:
+ return ret;
+}
+EXPORT_SYMBOL_GPL(gh_msgq_send);
+
+static ssize_t __gh_msgq_recv(struct gunyah_msgq *msgq, void *buff, size_t size)
+{
+ unsigned long flags, gh_err;
+ size_t recv_size;
+ ssize_t ret;
+ bool ready;
+
+ spin_lock_irqsave(&msgq->lock, flags);
+
+ gh_err = gh_hypercall_msgq_recv(msgq->ghdev.capid, (uintptr_t)buff, size,
+ &recv_size, &ready);
+ switch (gh_err) {
+ case GH_ERROR_OK:
+ ret = recv_size;
+ msgq->ready = ready;
+ break;
+ case GH_ERROR_MSGQUEUE_EMPTY:
+ ret = -EAGAIN;
+ msgq->ready = false;
+ break;
+ default:
+ ret = gh_remap_error(gh_err);
+ break;
+ }
+ spin_unlock_irqrestore(&msgq->lock, flags);
+
+ return ret;
+}
+
+/**
+ * gh_msgq_recv() - Receive a message from the client running on a different VM
+ * @client: The client descriptor that was obtained via gh_msgq_register()
+ * @buff: Pointer to the buffer where the received data must be placed
+ * @buff_size: The size of the buffer space available
+ * @flags: Optional flags to pass to receive the data. For the list of flags,
+ * see linux/gunyah/gh_msgq.h
+ *
+ * Returns: The number of bytes copied to buff. <0 if there was an error.
+ *
+ * Note: this function may sleep and should not be called from interrupt context
+ */
+ssize_t gh_msgq_recv(struct gunyah_msgq *msgq, void *buff, size_t size, const unsigned long flags)
+{
+ ssize_t ret;
+
+ if (unlikely(msgq->ghdev.type != GUNYAH_DEVICE_TYPE_MSGQ_RX))
+ return -EINVAL;
+
+ do {
+ ret = __gh_msgq_recv(msgq, buff, size);
+
+ if (ret == -EAGAIN) {
+ if (flags & GH_MSGQ_NONBLOCK)
+ goto out;
+ if (wait_event_interruptible(msgq->wq, msgq->ready))
+ ret = -ERESTARTSYS;
+ }
+ } while (ret == -EAGAIN);
+
+out:
+ return ret;
+}
+EXPORT_SYMBOL_GPL(gh_msgq_recv);
+
+struct gunyah_msgq *__gunyah_msgq_alloc(enum gunyah_device_type type, u64 capid, int irq)
+{
+ struct gunyah_msgq *msgq;
+ int ret;
+
+ msgq = kzalloc(sizeof(*msgq), GFP_KERNEL);
+ if (!msgq)
+ return NULL;
+
+ msgq->ghdev.type = type;
+ msgq->ghdev.capid = capid;
+ msgq->ghdev.irq = irq;
+
+ msgq->ready = true; /* Assume we can use the message queue right away */
+ init_waitqueue_head(&msgq->wq);
+ spin_lock_init(&msgq->lock);
+
+ ret = request_irq(msgq->ghdev.irq, gh_msgq_irq_handler, 0, "gh_msgq", msgq);
+ if (WARN(ret, "Failed to request message queue irq %d: %d\n", irq, ret)) {
+ kfree(msgq);
+ return NULL;
+ }
+
+ return msgq;
+}
+EXPORT_SYMBOL_GPL(__gunyah_msgq_alloc);
+
+void gunyah_msgq_free(struct gunyah_msgq *msgq)
+{
+ free_irq(msgq->ghdev.irq, msgq);
+ kfree(msgq);
+}
+EXPORT_SYMBOL_GPL(gunyah_msgq_free);
@@ -107,4 +107,9 @@ struct gh_hypercall_hyp_identify_resp {
void gh_hypercall_get_uid(u32 *uid);
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
new file mode 100644
@@ -0,0 +1,71 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
+ */
+
+#ifndef _GUNYAH_H
+#define _GUNYAH_H
+
+#include <asm-generic/gunyah.h>
+
+/* Follows resource manager's resource types for VM_GET_HYP_RESOURCES */
+enum gunyah_device_type {
+ GUNYAH_DEVICE_TYPE_BELL_TX = 0,
+ GUNYAH_DEVICE_TYPE_BELL_RX = 1,
+ GUNYAH_DEVICE_TYPE_MSGQ_TX = 2,
+ GUNYAH_DEVICE_TYPE_MSGQ_RX = 3,
+ GUNYAH_DEVICE_TYPE_VCPU = 4,
+};
+
+struct gunyah_device {
+ enum gunyah_device_type type;
+ u64 capid;
+ int irq;
+};
+
+/**
+ * Gunyah Message Queues
+ */
+
+struct gunyah_msgq {
+ struct gunyah_device ghdev;
+
+ /* Set by the message queue client */
+ void (*on_ready)(struct gunyah_msgq *msgq);
+ void *data;
+
+ /* msgq private */
+ bool ready;
+ wait_queue_head_t wq;
+ spinlock_t lock;
+};
+
+#define GH_MSGQ_MAX_MSG_SIZE 1024
+
+/* Possible flags to pass for Tx or Rx */
+#define GH_MSGQ_TX_PUSH BIT(0)
+#define GH_MSGQ_NONBLOCK BIT(32)
+
+static inline struct gunyah_msgq * __must_check to_gunyah_msgq(struct gunyah_device *ghdev)
+{
+ if (ghdev->type != GUNYAH_DEVICE_TYPE_BELL_TX && ghdev->type != GUNYAH_DEVICE_TYPE_BELL_RX)
+ return NULL;
+ return container_of(ghdev, struct gunyah_msgq, ghdev);
+}
+
+ssize_t gh_msgq_send(struct gunyah_msgq *msgq, void *buff, size_t size, const unsigned long flags);
+ssize_t gh_msgq_recv(struct gunyah_msgq *msgq, void *buff, size_t size, const unsigned long flags);
+struct gunyah_msgq *__gunyah_msgq_alloc(enum gunyah_device_type type, u64 capid, int irq);
+void gunyah_msgq_free(struct gunyah_msgq *msgq);
+
+static inline struct gunyah_msgq *gunyah_msgq_tx_alloc(u64 capid, int irq)
+{
+ return __gunyah_msgq_alloc(GUNYAH_DEVICE_TYPE_BELL_TX, capid, irq);
+}
+
+static inline struct gunyah_msgq *gunyah_msgq_rx_alloc(u64 capid, int irq)
+{
+ return __gunyah_msgq_alloc(GUNYAH_DEVICE_TYPE_BELL_RX, capid, irq);
+}
+
+#endif
Gunyah message queues are unidirectional pipelines to communicate between 2 virtual machines, but are typically paired to allow bidirectional communication. The intended use case is for small control messages between 2 VMs, as they support a maximum of 1024 bytes. Signed-off-by: Elliot Berman <quic_eberman@quicinc.com> --- MAINTAINERS | 1 + arch/arm64/gunyah/hypercall.c | 33 ++++++ drivers/virt/gunyah/Makefile | 2 +- drivers/virt/gunyah/msgq.c | 192 ++++++++++++++++++++++++++++++++++ include/asm-generic/gunyah.h | 5 + include/linux/gunyah.h | 71 +++++++++++++ 6 files changed, 303 insertions(+), 1 deletion(-) create mode 100644 drivers/virt/gunyah/msgq.c create mode 100644 include/linux/gunyah.h