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@xxxxxxxxxxx> --- 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 diff --git a/MAINTAINERS b/MAINTAINERS index c774bbcdb348..444891e02546 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -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@xxxxxxxxxx> diff --git a/arch/arm64/gunyah/hypercall.c b/arch/arm64/gunyah/hypercall.c index 5b08c9d80de0..042cca31879e 100644 --- a/arch/arm64/gunyah/hypercall.c +++ b/arch/arm64/gunyah/hypercall.c @@ -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"); diff --git a/drivers/virt/gunyah/Makefile b/drivers/virt/gunyah/Makefile index e15f16c17142..3c7efd2220c1 100644 --- a/drivers/virt/gunyah/Makefile +++ b/drivers/virt/gunyah/Makefile @@ -1,2 +1,2 @@ -gunyah-y += sysfs.o +gunyah-y += sysfs.o msgq.o obj-$(CONFIG_GUNYAH) += gunyah.o diff --git a/drivers/virt/gunyah/msgq.c b/drivers/virt/gunyah/msgq.c new file mode 100644 index 000000000000..05c3d90ac1ed --- /dev/null +++ b/drivers/virt/gunyah/msgq.c @@ -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); diff --git a/include/asm-generic/gunyah.h b/include/asm-generic/gunyah.h index 86eb59e203ef..43915faea704 100644 --- a/include/asm-generic/gunyah.h +++ b/include/asm-generic/gunyah.h @@ -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 diff --git a/include/linux/gunyah.h b/include/linux/gunyah.h new file mode 100644 index 000000000000..57e83b0d78cf --- /dev/null +++ b/include/linux/gunyah.h @@ -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 -- 2.25.1