From 4bd7265ed2fec89ab0a41c9e589d5a438b136311 Mon Sep 17 00:00:00 2001 From: Peng Yang Date: Sat, 2 Jul 2022 05:54:15 -0700 Subject: [PATCH 1/2] gunyah: dbl: Porting doorbell driver pineapple This is just snapshot of kalama's vm doorbell driver from 'commit 5beb98404269 ("Merge "i3c: i3c-master-msm-geni: Fix IBI for invalid device OR handler"")'from msm-5.15 branch. Change-Id: I6445cab51bc03d6a18ac11faa206979b659e5ef7 Signed-off-by: Peng Yang --- drivers/virt/gunyah/Kconfig | 10 + drivers/virt/gunyah/Makefile | 1 + drivers/virt/gunyah/gh_dbl.c | 712 +++++++++++++++++++++++++++++++ drivers/virt/gunyah/hcall_ctrl.h | 55 +++ drivers/virt/gunyah/hcall_dbl.h | 94 ++++ 5 files changed, 872 insertions(+) create mode 100644 drivers/virt/gunyah/gh_dbl.c create mode 100644 drivers/virt/gunyah/hcall_ctrl.h create mode 100644 drivers/virt/gunyah/hcall_dbl.h diff --git a/drivers/virt/gunyah/Kconfig b/drivers/virt/gunyah/Kconfig index e32f62a5bd34..9d89084b5551 100644 --- a/drivers/virt/gunyah/Kconfig +++ b/drivers/virt/gunyah/Kconfig @@ -24,4 +24,14 @@ config GH_VIRT_WATCHDOG actions such as setting the bark/bite time and also petting the watchdog in the hypervisor. +config GH_DBL + tristate "Gunyah Doorbell driver" + help + Gunyah offers a simple inter VMs(Virtual Machines) communication + through the use of doorbell interrupts. A single doorbell instance + provides an unidirectional communication between two VMs and it acts + like either a source(Tx) or generate(Rx). Individual VMs make use of + these doorbells by calling send and/or a receive primitives exposed by + driver and trigger an interrupt to each other and exchange the data. + endif diff --git a/drivers/virt/gunyah/Makefile b/drivers/virt/gunyah/Makefile index 79aaed4d2f24..e2dc37a26b4d 100644 --- a/drivers/virt/gunyah/Makefile +++ b/drivers/virt/gunyah/Makefile @@ -1,2 +1,3 @@ # SPDX-License-Identifier: GPL-2.0-only obj-$(CONFIG_GH_VIRT_WATCHDOG)+= gh_virt_wdt.o +obj-$(CONFIG_GH_DBL) += gh_dbl.o diff --git a/drivers/virt/gunyah/gh_dbl.c b/drivers/virt/gunyah/gh_dbl.c new file mode 100644 index 000000000000..d25b8c9be4a3 --- /dev/null +++ b/drivers/virt/gunyah/gh_dbl.c @@ -0,0 +1,712 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2020-2021, The Linux Foundation. All rights reserved. + * + */ + +#include +#include +#include +#include + +#include +#include +#include "hcall_dbl.h" + +struct gh_dbl_desc { + enum gh_dbl_label label; +}; + +enum gh_dbl_dir { + GH_DBL_DIRECTION_TX, + GH_DBL_DIRECTION_RX +}; + +struct gh_dbl_cap_table { + struct gh_dbl_desc *client_desc; + spinlock_t cap_entry_lock; + gh_capid_t tx_cap_id; + int tx_reg_done; + + gh_capid_t rx_cap_id; + int rx_irq; + int rx_reg_done; + const char *rx_irq_name; + dbl_rx_cb_t rx_callback; + void *rx_priv_data; + wait_queue_head_t cap_wq; +}; + +static bool gh_dbl_initialized; +static struct gh_dbl_cap_table gh_dbl_cap_table[GH_DBL_LABEL_MAX]; + +/** + * gh_dbl_validate_params - Validate doorbell common parameters + */ +static int gh_dbl_validate_params(struct gh_dbl_desc *client_desc, + enum gh_dbl_dir dir, const unsigned long flags) +{ + struct gh_dbl_cap_table *cap_table_entry; + int ret; + + if (IS_ERR_OR_NULL(client_desc)) + return -EINVAL; + + /* Check if the client has manipulated the label */ + if (client_desc->label < 0 || client_desc->label >= GH_DBL_LABEL_MAX) + return -EINVAL; + + cap_table_entry = &gh_dbl_cap_table[client_desc->label]; + + spin_lock(&cap_table_entry->cap_entry_lock); + + if (cap_table_entry->client_desc != client_desc) { + spin_unlock(&cap_table_entry->cap_entry_lock); + pr_err("%s: Invalid client descriptor\n", __func__); + return -EINVAL; + } + + /* + * rx_cap_id == NULL and tx_cap_id == NULL means TWO things + * either "gh_dbl_populate_cap_info()" call from RM is not over + * or + * There are no doorbell setup for Tx or Rx + */ + if (dir == GH_DBL_DIRECTION_RX) { + if (!cap_table_entry->rx_reg_done) { + ret = -EINVAL; + goto err; + } + + if ((cap_table_entry->rx_cap_id == GH_CAPID_INVAL) && + (flags & GH_DBL_NONBLOCK)) { + ret = -EAGAIN; + goto err; + } + + spin_unlock(&cap_table_entry->cap_entry_lock); + + if (wait_event_interruptible(cap_table_entry->cap_wq, + cap_table_entry->rx_cap_id != GH_CAPID_INVAL)) + return -ERESTARTSYS; + + } else { + if (!cap_table_entry->tx_reg_done) { + ret = -EINVAL; + goto err; + } + + if ((cap_table_entry->tx_cap_id == GH_CAPID_INVAL) && + (flags & GH_DBL_NONBLOCK)) { + ret = -EAGAIN; + goto err; + } + + spin_unlock(&cap_table_entry->cap_entry_lock); + + if (wait_event_interruptible(cap_table_entry->cap_wq, + cap_table_entry->tx_cap_id != GH_CAPID_INVAL)) + return -ERESTARTSYS; + + } + + return 0; +err: + spin_unlock(&cap_table_entry->cap_entry_lock); + return ret; +} + +/** + * gh_dbl_read_and_clean - Automatically read and clear the flags in doorbell + * @client_desc: client handle to indetify the doorbell object + * @clear_flags: clear the bits mentioned in the clear_flags + * @flags: Optional flags to pass to send the data. For the list of flags, + * see linux/gunyah/gh_dbl.h + * + * Reads and clears the flags of the Doorbell object. If there is a pending + * bound virtual interrupt, it will be de-asserted + * + * Returns: + * 0 on success, @clear_flags contains the doorbell’s previous unmasked flags + * before the @clear_flags were removed. + */ +int gh_dbl_read_and_clean(void *dbl_client_desc, gh_dbl_flags_t *clear_flags, + const unsigned long flags) +{ + struct gh_dbl_cap_table *cap_table_entry; + struct gh_hcall_dbl_recv_resp recv_resp; + struct gh_dbl_desc *client_desc = dbl_client_desc; + int ret, gh_ret; + + if (!clear_flags) + return -EINVAL; + + ret = gh_dbl_validate_params(client_desc, GH_DBL_DIRECTION_RX, flags); + if (ret) + return ret; + + cap_table_entry = &gh_dbl_cap_table[client_desc->label]; + + gh_ret = gh_hcall_dbl_recv(cap_table_entry->rx_cap_id, + *clear_flags, &recv_resp); + + ret = gh_remap_error(gh_ret); + if (ret != 0) + pr_err("%s: Hypercall failed, ret = %d\n", __func__, gh_ret); + else + *clear_flags = recv_resp.old_flags; + + return ret; +} +EXPORT_SYMBOL(gh_dbl_read_and_clean); + +/** + * gh_dbl_set_mask - Set doorbell object mask + * @client_desc: client handle to indetify the doorbell object + * @enable_mask: The mask of flags that will cause an assertion of + * the doorbell's bound virtual interrupt + * @ack_mask: Controls which flags should be automatically cleared + * when the interrupt is asserted + * @flags: Optional flags to pass to send the data. For the list of flags, + * see linux/gunyah/gh_dbl.h + * + * Sets the Doorbell object’s masks. A doorbell object has two masks + * which are configured by the receiver to control which flags it is + * interested in, and which flags if any should be automatically acknowledged. + * + * Returns: + * 0 on success + */ +int gh_dbl_set_mask(void *dbl_client_desc, gh_dbl_flags_t enable_mask, + gh_dbl_flags_t ack_mask, const unsigned long flags) +{ + struct gh_dbl_cap_table *cap_table_entry; + struct gh_dbl_desc *client_desc = dbl_client_desc; + int ret, gh_ret; + + ret = gh_dbl_validate_params(client_desc, GH_DBL_DIRECTION_RX, flags); + if (ret) + return ret; + + cap_table_entry = &gh_dbl_cap_table[client_desc->label]; + + gh_ret = gh_hcall_dbl_mask(cap_table_entry->rx_cap_id, + enable_mask, ack_mask); + + ret = gh_remap_error(gh_ret); + if (ret != 0) + pr_err("%s: Hypercall failed ret = %d\n", __func__, gh_ret); + + return ret; +} +EXPORT_SYMBOL(gh_dbl_set_mask); + +/** + * gh_dbl_send - Set flags in the doorbell + * @client_desc: client handle to indetify the doorbell object + * @newflags: flags to set in the doorbell. This flag along with enable_mask + * in the doorbell decide whehter to raise vIRQ are not. + * @flags: Optional flags to pass to send the data. For the list of flags, + * see linux/gunyah/gh_dbl.h + * + * Set flags in the doorbell. If following the send, the set of enabled flags + * as defined by the bitwise-AND of the doorbell flags with the EnableMask, + * is non-zero, any bound virtual interrupt will be asserted. + * + * Returns: + * 0 on success, @newflags contains the doorbell’s previous unmasked flags + * before the @newflags were added. + */ +int gh_dbl_send(void *dbl_client_desc, gh_dbl_flags_t *newflags, + unsigned long flags) +{ + struct gh_dbl_cap_table *cap_table_entry; + struct gh_hcall_dbl_send_resp send_resp; + struct gh_dbl_desc *client_desc = dbl_client_desc; + int ret, gh_ret; + + if (!newflags) + return -EINVAL; + + ret = gh_dbl_validate_params(client_desc, GH_DBL_DIRECTION_TX, flags); + if (ret) + return ret; + + cap_table_entry = &gh_dbl_cap_table[client_desc->label]; + + gh_ret = gh_hcall_dbl_send(cap_table_entry->tx_cap_id, *newflags, + &send_resp); + + ret = gh_remap_error(gh_ret); + if (ret != 0) + pr_err("%s: Hypercall failed ret = %d\n", __func__, gh_ret); + else + *newflags = send_resp.old_flags; + + return ret; +} +EXPORT_SYMBOL(gh_dbl_send); + +/** + * gh_dbl_reset - clear all the flags of the doorbell and sets all bits in + * the Doorbell’s mask. + * @client_desc: client handle to indetify the doorbell object + * @flags: Optional flags to pass to send the data. For the list of flags, + * see linux/gunyah/gh_dbl.h + * + * Clears all the flags of the doorbell and sets all bits in the doorbell’s + * mask. If there is a pending bound virtual interrupt, it will be de-asserted. + * + * Returns: + * 0 on success + */ +int gh_dbl_reset(void *dbl_client_desc, const unsigned long flags) +{ + struct gh_dbl_cap_table *cap_table_entry; + struct gh_dbl_desc *client_desc = dbl_client_desc; + int ret, gh_ret; + + ret = gh_dbl_validate_params(client_desc, GH_DBL_DIRECTION_RX, flags); + if (ret) + return ret; + + cap_table_entry = &gh_dbl_cap_table[client_desc->label]; + + gh_ret = gh_hcall_dbl_reset(cap_table_entry->rx_cap_id); + + ret = gh_remap_error(gh_ret); + if (ret != 0) + pr_err("%s: Hypercall failed ret = %d\n", __func__, gh_ret); + + return ret; +} +EXPORT_SYMBOL(gh_dbl_reset); + +static irqreturn_t gh_dbl_rx_callback_thread(int irq, void *data) +{ + struct gh_dbl_cap_table *cap_table_entry = data; + + if (!cap_table_entry->rx_callback) + return IRQ_HANDLED; + + cap_table_entry->rx_callback(irq, cap_table_entry->rx_priv_data); + return IRQ_HANDLED; +} + +/** + * gh_dbl_tx_register: Register as a Tx client to use the doorbell + * @label: The label associated to the doorbell that the client wants + * to send a message to other VM. + * + * The function returns a descriptor for the clients to send a message. + * Else, returns -EBUSY if some other client is already registered + * to this label, and -EINVAL for invalid arguments. The caller should check + * the return value using IS_ERR_OR_NULL() and PTR_ERR() to extract the error + * code. + */ +void *gh_dbl_tx_register(enum gh_dbl_label label) +{ + struct gh_dbl_cap_table *cap_table_entry; + struct gh_dbl_desc *client_desc; + int ret; + + if (label < 0 || label >= GH_DBL_LABEL_MAX) + return ERR_PTR(-EINVAL); + + if (!gh_dbl_initialized) + return ERR_PTR(-EPROBE_DEFER); + + cap_table_entry = &gh_dbl_cap_table[label]; + + spin_lock(&cap_table_entry->cap_entry_lock); + + /* Avoid multiple client Tx registration for the same doorbell */ + if (cap_table_entry->tx_reg_done) { + ret = -EBUSY; + goto err; + } + + if (cap_table_entry->client_desc) { + client_desc = cap_table_entry->client_desc; + } else { + client_desc = kzalloc(sizeof(*client_desc), GFP_ATOMIC); + if (!client_desc) { + ret = -ENOMEM; + goto err; + } + + client_desc->label = label; + cap_table_entry->client_desc = client_desc; + } + + cap_table_entry->tx_reg_done = 1; + + pr_debug("%s: Registered Tx client for label: %d\n", __func__, label); + + spin_unlock(&cap_table_entry->cap_entry_lock); + return client_desc; + +err: + spin_unlock(&cap_table_entry->cap_entry_lock); + return ERR_PTR(ret); +} +EXPORT_SYMBOL(gh_dbl_tx_register); + +/** + * gh_dbl_rx_register: Register as a Rx client to use the doorbell + * @label: The label associated to the doorbell that the client wants + * to read a message. + * @rx_cb: Callback of the client when there is a vIRQ on doorbell + * @priv: Private data of the driver + * + * The function returns a descriptor for the clients to receive a message. + * Else, returns -EBUSY if some other client is already registered + * to this label, and -EINVAL for invalid arguments. The caller should check + * the return value using IS_ERR_OR_NULL() and PTR_ERR() to extract the error + * code. + */ + +void *gh_dbl_rx_register(enum gh_dbl_label label, dbl_rx_cb_t rx_cb, void *priv) +{ + struct gh_dbl_cap_table *cap_table_entry; + struct gh_dbl_desc *client_desc; + int ret; + + if (label < 0 || label >= GH_DBL_LABEL_MAX) + return ERR_PTR(-EINVAL); + + if (!gh_dbl_initialized) + return ERR_PTR(-EPROBE_DEFER); + + cap_table_entry = &gh_dbl_cap_table[label]; + + spin_lock(&cap_table_entry->cap_entry_lock); + + /* Avoid multiple client Rx registration for the same doorbell */ + if (cap_table_entry->rx_reg_done) { + ret = -EBUSY; + goto err; + } + + if (cap_table_entry->client_desc) { + client_desc = cap_table_entry->client_desc; + } else { + client_desc = kzalloc(sizeof(*client_desc), GFP_ATOMIC); + if (!client_desc) { + ret = -ENOMEM; + goto err; + } + + client_desc->label = label; + cap_table_entry->client_desc = client_desc; + } + + cap_table_entry->rx_callback = rx_cb; + cap_table_entry->rx_priv_data = priv; + + cap_table_entry->rx_reg_done = 1; + + pr_debug("%s: Registered Rx client for label: %d\n", __func__, label); + + spin_unlock(&cap_table_entry->cap_entry_lock); + return client_desc; + +err: + pr_debug("%s: Registration for Rx client for label failed: %d\n", + __func__, label); + spin_unlock(&cap_table_entry->cap_entry_lock); + return ERR_PTR(ret); +} +EXPORT_SYMBOL(gh_dbl_rx_register); + +/** + * gh_dbl_tx_unregister: Unregister Tx client to use the doorbell + * @client_desc: The descriptor that was passed via gh_dbl_tx_register() or + * gh_dbl_rx_register() + * + * The function returns 0 is the client was unregistered successfully. Else, + * -EINVAL for invalid arguments. + */ +int gh_dbl_tx_unregister(void *dbl_client_desc) +{ + struct gh_dbl_desc *client_desc = dbl_client_desc; + struct gh_dbl_cap_table *cap_table_entry; + + if (IS_ERR_OR_NULL(client_desc)) + return -EINVAL; + + /* Check if the client has manipulated the label */ + if (client_desc->label < 0 || client_desc->label >= GH_DBL_LABEL_MAX) + return -EINVAL; + + cap_table_entry = &gh_dbl_cap_table[client_desc->label]; + + spin_lock(&cap_table_entry->cap_entry_lock); + + /* Is the client trying to free someone else's doorbell? */ + if (cap_table_entry->client_desc != client_desc) { + pr_err("%s: Trying to free invalid client descriptor!\n", + __func__); + spin_unlock(&cap_table_entry->cap_entry_lock); + return -EINVAL; + } + + pr_debug("%s: Unregistering client for label: %d\n", + __func__, client_desc->label); + + /* Rx client still holding the "client_desc". Do not remove now. */ + if (!cap_table_entry->rx_reg_done) { + cap_table_entry->client_desc = NULL; + kfree(client_desc); + } else { + pr_debug("%s: Rx client holding the client_desc.\n", __func__); + } + + cap_table_entry->tx_reg_done = 0; + spin_unlock(&cap_table_entry->cap_entry_lock); + + return 0; +} +EXPORT_SYMBOL(gh_dbl_tx_unregister); + +/** + * gh_dbl_rx_unregister: Unregister Rx client to use the doorbell + * @client_desc: The descriptor that was passed via gh_dbl_tx_register() or + * gh_dbl_rx_register() + * + * The function returns 0 is the client was unregistered successfully. Else, + * -EINVAL for invalid arguments. + */ +int gh_dbl_rx_unregister(void *dbl_client_desc) +{ + struct gh_dbl_desc *client_desc = dbl_client_desc; + struct gh_dbl_cap_table *cap_table_entry; + + if (IS_ERR_OR_NULL(client_desc)) + return -EINVAL; + + /* Check if the client has manipulated the label */ + if (client_desc->label < 0 || client_desc->label >= GH_DBL_LABEL_MAX) + return -EINVAL; + + cap_table_entry = &gh_dbl_cap_table[client_desc->label]; + + spin_lock(&cap_table_entry->cap_entry_lock); + + /* Is the client trying to free someone else's doorbell? */ + if (cap_table_entry->client_desc != client_desc) { + pr_err("%s: Trying to free invalid client descriptor!\n", + __func__); + spin_unlock(&cap_table_entry->cap_entry_lock); + return -EINVAL; + } + + pr_debug("%s: Unregistering client for label: %d\n", __func__, + client_desc->label); + + /* Tx client still holding the "client_desc". Do not remove now.*/ + if (!cap_table_entry->tx_reg_done) { + cap_table_entry->client_desc = NULL; + kfree(client_desc); + } else { + pr_debug("%s: Tx client holding the client_desc.\n", __func__); + } + + cap_table_entry->rx_callback = NULL; + cap_table_entry->rx_priv_data = NULL; + cap_table_entry->rx_reg_done = 0; + + spin_unlock(&cap_table_entry->cap_entry_lock); + + return 0; +} +EXPORT_SYMBOL(gh_dbl_rx_unregister); + +/** + * This API is called by RM driver to populate doorbell objects + */ +int gh_dbl_populate_cap_info(enum gh_dbl_label label, u64 cap_id, + int direction, int rx_irq) +{ + struct gh_dbl_cap_table *cap_table_entry; + int ret = 0; + + if (!gh_dbl_initialized) + return -EAGAIN; + + if (label < 0 || label >= GH_DBL_LABEL_MAX) { + pr_err("%s: Invalid label passed\n", __func__); + return -EINVAL; + } + + cap_table_entry = &gh_dbl_cap_table[label]; + + switch (direction) { + case GH_DBL_DIRECTION_TX: + /* No interrupt should associated with Tx doorbell*/ + if (rx_irq > 0) { + pr_err("%s: No IRQ associated for Tx doorbell!\n", + __func__); + ret = -ENXIO; + goto err; + } + + spin_lock(&cap_table_entry->cap_entry_lock); + cap_table_entry->tx_cap_id = cap_id; + spin_unlock(&cap_table_entry->cap_entry_lock); + + wake_up_interruptible(&cap_table_entry->cap_wq); + + pr_debug("%s: label: %d; tx_cap_id: %llu; dir: %d; rx_irq: %d\n", + __func__, label, cap_id, direction, rx_irq); + break; + case GH_DBL_DIRECTION_RX: + if (rx_irq <= 0) { + pr_err("%s: Invalid IRQ number for Rx doorbell\n", + __func__); + ret = -ENXIO; + goto err; + } + + cap_table_entry->rx_irq = rx_irq; + ret = request_threaded_irq(cap_table_entry->rx_irq, + NULL, + gh_dbl_rx_callback_thread, + IRQF_ONESHOT | IRQF_TRIGGER_RISING, + cap_table_entry->rx_irq_name, + cap_table_entry); + + if (ret < 0) { + pr_err("%s: IRQ registration failed\n", __func__); + goto err; + } + + irq_set_irq_wake(rx_irq, 1); + + spin_lock(&cap_table_entry->cap_entry_lock); + cap_table_entry->rx_cap_id = cap_id; + spin_unlock(&cap_table_entry->cap_entry_lock); + + wake_up_interruptible(&cap_table_entry->cap_wq); + + pr_debug("%s: label: %d; rx_cap_id: %llu; dir: %d; rx_irq: %d\n", + __func__, label, cap_id, direction, rx_irq); + break; + default: + pr_err("%s: Invalid direction(%d) for doorbell\n", + __func__, direction); + ret = -EINVAL; + } + +err: + return ret; +} +EXPORT_SYMBOL(gh_dbl_populate_cap_info); + +/** + * This API is called by RM driver to free up doorbell objects + */ +int gh_dbl_reset_cap_info(enum gh_dbl_label label, int direction, int *irq) +{ + struct gh_dbl_cap_table *cap_table_entry; + int ret = 0; + + if (!gh_dbl_initialized) + return -EAGAIN; + + if (label < 0 || label >= GH_DBL_LABEL_MAX) { + pr_err("%s: Invalid label passed\n", __func__); + return -EINVAL; + } + + if (!irq) + return -EINVAL; + + cap_table_entry = &gh_dbl_cap_table[label]; + + spin_lock(&cap_table_entry->cap_entry_lock); + + switch (direction) { + case GH_DBL_DIRECTION_TX: + *irq = 0; + cap_table_entry->tx_cap_id = GH_CAPID_INVAL; + break; + case GH_DBL_DIRECTION_RX: + if (!cap_table_entry->rx_irq) { + pr_err("%s: Rx IRQ not setup\n", __func__); + ret = -ENXIO; + goto err_unlock; + } + + *irq = cap_table_entry->rx_irq; + + cap_table_entry->rx_irq = 0; + cap_table_entry->rx_cap_id = GH_CAPID_INVAL; + break; + default: + pr_err("%s: Invalid direction(%d) for doorbell\n", + __func__, direction); + ret = -EINVAL; + } + +err_unlock: + spin_unlock(&cap_table_entry->cap_entry_lock); + + if (*irq && !ret) + free_irq(*irq, cap_table_entry); + + return ret; +} +EXPORT_SYMBOL(gh_dbl_reset_cap_info); + +static void gh_dbl_cleanup(int begin_idx) +{ + struct gh_dbl_cap_table *cap_table_entry; + int i; + + if (begin_idx >= GH_DBL_LABEL_MAX) + begin_idx = GH_DBL_LABEL_MAX - 1; + + for (i = begin_idx; i >= 0; i--) { + cap_table_entry = &gh_dbl_cap_table[i]; + kfree(cap_table_entry->rx_irq_name); + } +} + +static int __init gh_dbl_init(void) +{ + struct gh_dbl_cap_table *entry; + int ret; + int i; + + for (i = 0; i < GH_DBL_LABEL_MAX; i++) { + entry = &gh_dbl_cap_table[i]; + spin_lock_init(&entry->cap_entry_lock); + init_waitqueue_head(&entry->cap_wq); + entry->tx_cap_id = GH_CAPID_INVAL; + entry->rx_cap_id = GH_CAPID_INVAL; + entry->rx_irq_name = kasprintf(GFP_KERNEL, "gh_dbl_rx_%d", i); + if (!entry->rx_irq_name) { + ret = -ENOMEM; + goto err; + } + } + + gh_dbl_initialized = true; + + return 0; + +err: + gh_dbl_cleanup(i); + return ret; +} +module_init(gh_dbl_init); + +static void __exit gh_dbl_exit(void) +{ + gh_dbl_cleanup(GH_DBL_LABEL_MAX - 1); +} +module_exit(gh_dbl_exit); + +MODULE_DESCRIPTION("Qualcomm Technologies, Inc. Gunyah Doorbell Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/virt/gunyah/hcall_ctrl.h b/drivers/virt/gunyah/hcall_ctrl.h new file mode 100644 index 000000000000..6e2e2aba5427 --- /dev/null +++ b/drivers/virt/gunyah/hcall_ctrl.h @@ -0,0 +1,55 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2021, The Linux Foundation. All rights reserved. + */ +#ifndef __GH_HCALL_CTRL_H +#define __GH_HCALL_CTRL_H + +#include +#include + +#include +#include +#include + +struct gh_hcall_hyp_identify_resp { + u64 api_info; + u64 flags[3]; +}; + +static inline int gh_hcall_hyp_identify(struct gh_hcall_hyp_identify_resp *resp) +{ + struct gh_hcall_resp _resp = {0}; + + _gh_hcall(0x6000, + (struct gh_hcall_args){ 0 }, + &_resp); + + if (resp) { + resp->api_info = _resp.resp0; + resp->flags[0] = _resp.resp1; + resp->flags[1] = _resp.resp2; + resp->flags[2] = _resp.resp3; + } + + return 0; +} + +static inline int gh_hcall_trace_update_class_flags( + uint64_t set_flags, uint64_t clear_flags, + uint64_t *new_flags) +{ + int ret; + struct gh_hcall_resp _resp = {0}; + + ret = _gh_hcall(0x603f, + (struct gh_hcall_args){ set_flags, clear_flags, 0 }, + &_resp); + + if (!ret && new_flags) + *new_flags = _resp.resp1; + + return ret; +} + +#endif diff --git a/drivers/virt/gunyah/hcall_dbl.h b/drivers/virt/gunyah/hcall_dbl.h new file mode 100644 index 000000000000..6018e074e42e --- /dev/null +++ b/drivers/virt/gunyah/hcall_dbl.h @@ -0,0 +1,94 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2021, The Linux Foundation. All rights reserved. + */ +#ifndef __GH_HCALL_DBL_H +#define __GH_HCALL_DBL_H + +#include +#include + +#include +#include +#include + +static inline int gh_hcall_dbl_bind(gh_capid_t dbl_capid, gh_capid_t vic_capid, + gh_virq_handle_t virq_info) +{ + struct gh_hcall_resp _resp = {0}; + + return _gh_hcall(0x6010, + (struct gh_hcall_args){ dbl_capid, vic_capid, + virq_info }, + &_resp); +} + +static inline int gh_hcall_dbl_unbind(gh_capid_t dbl_capid) +{ + struct gh_hcall_resp _resp = {0}; + + return _gh_hcall(0x6011, (struct gh_hcall_args){ dbl_capid }, &_resp); +} + +struct gh_hcall_dbl_send_resp { + u64 old_flags; +}; + +static inline int gh_hcall_dbl_send(gh_capid_t dbl_capid, + gh_dbl_flags_t new_flags, + struct gh_hcall_dbl_send_resp *resp) +{ + int ret; + struct gh_hcall_resp _resp = {0}; + + ret = _gh_hcall(0x6012, + (struct gh_hcall_args){ dbl_capid, new_flags }, + &_resp); + + if (!ret && resp) + resp->old_flags = _resp.resp1; + + return ret; +} + +struct gh_hcall_dbl_recv_resp { + u64 old_flags; +}; + +static inline int gh_hcall_dbl_recv(gh_capid_t dbl_capid, + gh_dbl_flags_t clear_flags, + struct gh_hcall_dbl_recv_resp *resp) +{ + int ret; + struct gh_hcall_resp _resp = {0}; + + ret = _gh_hcall(0x6013, + (struct gh_hcall_args){ dbl_capid, clear_flags }, + &_resp); + + if (!ret && resp) + resp->old_flags = _resp.resp1; + + return ret; +} + +static inline int gh_hcall_dbl_reset(gh_capid_t dbl_capid) +{ + struct gh_hcall_resp _resp = {0}; + + return _gh_hcall(0x6014, (struct gh_hcall_args){ dbl_capid }, &_resp); +} + +static inline int gh_hcall_dbl_mask(gh_capid_t dbl_capid, + gh_dbl_flags_t enable_mask, + gh_dbl_flags_t ack_mask) +{ + struct gh_hcall_resp _resp = {0}; + + return _gh_hcall(0x6015, + (struct gh_hcall_args){ dbl_capid, enable_mask, + ack_mask }, + &_resp); +} + +#endif From 213f1c589fb1d0466358299a1d330a1dfb38660d Mon Sep 17 00:00:00 2001 From: Peng Yang Date: Sat, 2 Jul 2022 06:27:04 -0700 Subject: [PATCH 2/2] gunyah: msgq: Porting messageq driver to pineapple This is just snapshot of kalama's vm msgq driver from 'commit 5beb98404269 ("Merge "i3c: i3c-master-msm-geni: Fix IBI for invalid device OR handler"")'from msm-5.15 branch. Change-Id: I1eb3e01ebf7229f3c35bd8bc6782e80498a2db5e Signed-off-by: Peng Yang --- drivers/virt/gunyah/Kconfig | 10 + drivers/virt/gunyah/Makefile | 1 + drivers/virt/gunyah/gh_msgq.c | 716 +++++++++++++++++++++++++++++++ drivers/virt/gunyah/hcall_msgq.h | 131 ++++++ 4 files changed, 858 insertions(+) create mode 100644 drivers/virt/gunyah/gh_msgq.c create mode 100644 drivers/virt/gunyah/hcall_msgq.h diff --git a/drivers/virt/gunyah/Kconfig b/drivers/virt/gunyah/Kconfig index 9d89084b5551..d5f5decdd144 100644 --- a/drivers/virt/gunyah/Kconfig +++ b/drivers/virt/gunyah/Kconfig @@ -34,4 +34,14 @@ config GH_DBL these doorbells by calling send and/or a receive primitives exposed by driver and trigger an interrupt to each other and exchange the data. +config GH_MSGQ + tristate "Gunyah Message Queue driver" + help + Gunyah offers message-queues as one of the IPC mechanisms to + communicate among the Virtual Machines. The message queue drivers + runs on the Virtual machines to provide an interface to the clients + who wish to communicate to other clients on a different VM. Currently, + the services offered by the drivers is simply to send and receive + messages in a blocking manner. + endif diff --git a/drivers/virt/gunyah/Makefile b/drivers/virt/gunyah/Makefile index e2dc37a26b4d..df16ec8758b2 100644 --- a/drivers/virt/gunyah/Makefile +++ b/drivers/virt/gunyah/Makefile @@ -1,3 +1,4 @@ # SPDX-License-Identifier: GPL-2.0-only obj-$(CONFIG_GH_VIRT_WATCHDOG)+= gh_virt_wdt.o obj-$(CONFIG_GH_DBL) += gh_dbl.o +obj-$(CONFIG_GH_MSGQ) += gh_msgq.o diff --git a/drivers/virt/gunyah/gh_msgq.c b/drivers/virt/gunyah/gh_msgq.c new file mode 100644 index 000000000000..c2eed5434816 --- /dev/null +++ b/drivers/virt/gunyah/gh_msgq.c @@ -0,0 +1,716 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2020-2021, The Linux Foundation. All rights reserved. + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include "hcall_msgq.h" + +/* HVC call specific mask: 0 to 31 */ +#define GH_MSGQ_HVC_FLAGS_MASK GENMASK_ULL(31, 0) + +struct gh_msgq_cap_table; + +struct gh_msgq_desc { + int label; + struct gh_msgq_cap_table *cap_table; +}; + +struct gh_msgq_cap_table { + struct gh_msgq_desc *client_desc; + spinlock_t cap_entry_lock; + + gh_capid_t tx_cap_id; + gh_capid_t rx_cap_id; + int tx_irq; + int rx_irq; + const char *tx_irq_name; + const char *rx_irq_name; + spinlock_t tx_lock; + spinlock_t rx_lock; + + bool tx_full; + bool rx_empty; + wait_queue_head_t tx_wq; + wait_queue_head_t rx_wq; + + int label; + struct list_head entry; +}; + +static LIST_HEAD(gh_msgq_cap_list); +static DEFINE_SPINLOCK(gh_msgq_cap_list_lock); + +struct gh_msgq_cap_table *gh_msgq_alloc_entry(int label) +{ + int ret; + struct gh_msgq_cap_table *cap_table_entry = NULL; + + cap_table_entry = kzalloc(sizeof(struct gh_msgq_cap_table), GFP_ATOMIC); + if (!cap_table_entry) + return ERR_PTR(-ENOMEM); + + cap_table_entry->tx_cap_id = GH_CAPID_INVAL; + cap_table_entry->rx_cap_id = GH_CAPID_INVAL; + cap_table_entry->tx_full = false; + cap_table_entry->rx_empty = true; + cap_table_entry->label = label; + init_waitqueue_head(&cap_table_entry->tx_wq); + init_waitqueue_head(&cap_table_entry->rx_wq); + spin_lock_init(&cap_table_entry->tx_lock); + spin_lock_init(&cap_table_entry->rx_lock); + spin_lock_init(&cap_table_entry->cap_entry_lock); + + cap_table_entry->tx_irq_name = + kasprintf(GFP_ATOMIC, "gh_msgq_tx_%d", label); + if (!cap_table_entry->tx_irq_name) { + ret = -ENOMEM; + goto err; + } + + cap_table_entry->rx_irq_name = + kasprintf(GFP_ATOMIC, "gh_msgq_rx_%d", label); + if (!cap_table_entry->rx_irq_name) { + ret = -ENOMEM; + goto err; + } + + list_add(&cap_table_entry->entry, &gh_msgq_cap_list); + return cap_table_entry; +err: + kfree(cap_table_entry->tx_irq_name); + kfree(cap_table_entry->rx_irq_name); + kfree(cap_table_entry); + return ERR_PTR(ret); +} + +static irqreturn_t gh_msgq_rx_isr(int irq, void *dev) +{ + struct gh_msgq_cap_table *cap_table_entry = dev; + + spin_lock(&cap_table_entry->rx_lock); + cap_table_entry->rx_empty = false; + spin_unlock(&cap_table_entry->rx_lock); + + wake_up_interruptible(&cap_table_entry->rx_wq); + + return IRQ_HANDLED; +} + +static irqreturn_t gh_msgq_tx_isr(int irq, void *dev) +{ + struct gh_msgq_cap_table *cap_table_entry = dev; + + spin_lock(&cap_table_entry->tx_lock); + cap_table_entry->tx_full = false; + spin_unlock(&cap_table_entry->tx_lock); + + wake_up_interruptible(&cap_table_entry->tx_wq); + + return IRQ_HANDLED; +} + +static int __gh_msgq_recv(struct gh_msgq_cap_table *cap_table_entry, + void *buff, size_t buff_size, + size_t *recv_size, u64 rx_flags) +{ + struct gh_hcall_msgq_recv_resp resp = {}; + unsigned long flags; + int gh_ret; + int ret = 0; + + /* Discard the driver specific flags, and keep only HVC specifics */ + rx_flags &= GH_MSGQ_HVC_FLAGS_MASK; + + spin_lock_irqsave(&cap_table_entry->rx_lock, flags); + gh_ret = gh_hcall_msgq_recv(cap_table_entry->rx_cap_id, buff, + buff_size, &resp); + + switch (gh_ret) { + case GH_ERROR_OK: + *recv_size = resp.recv_size; + cap_table_entry->rx_empty = !resp.not_empty; + ret = 0; + break; + case GH_ERROR_MSGQUEUE_EMPTY: + cap_table_entry->rx_empty = true; + ret = -EAGAIN; + break; + default: + ret = gh_remap_error(gh_ret); + } + + spin_unlock_irqrestore(&cap_table_entry->rx_lock, flags); + + if (ret != 0 && ret != -EAGAIN) + pr_err("%s: Failed to recv from msgq. Hypercall error: %d\n", + __func__, gh_ret); + + return ret; +} + +/** + * gh_msgq_recv: Receive a message from the client running on a different VM + * @client_desc: 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 + * @recv_size: The actual amount of data that is copied into buff + * @flags: Optional flags to pass to receive the data. For the list of flags, + * see linux/gunyah/gh_msgq.h + * + * The function returns 0 if the data is successfully received and recv_size + * would contain the actual amount of data copied into buff. + * It returns -EINVAL if the caller passes invalid arguments, -EAGAIN + * if the message queue is not yet ready to communicate, and -EPERM if the + * caller doesn't have permissions to receive the data. In all these failure + * cases, recv_size is unmodified. + * + * Note: this function may sleep and should not be called from interrupt + * context + */ +int gh_msgq_recv(void *msgq_client_desc, + void *buff, size_t buff_size, + size_t *recv_size, unsigned long flags) +{ + struct gh_msgq_desc *client_desc = msgq_client_desc; + struct gh_msgq_cap_table *cap_table_entry; + int ret; + + if (!client_desc || !buff || !buff_size || !recv_size) + return -EINVAL; + + if (buff_size > GH_MSGQ_MAX_MSG_SIZE_BYTES) + return -E2BIG; + + if (client_desc->cap_table == NULL) + return -EAGAIN; + + cap_table_entry = client_desc->cap_table; + + spin_lock(&cap_table_entry->cap_entry_lock); + + if (cap_table_entry->client_desc != client_desc) { + pr_err("%s: Invalid client descriptor\n", __func__); + ret = -EINVAL; + goto err; + } + + if ((cap_table_entry->rx_cap_id == GH_CAPID_INVAL) && + (flags & GH_MSGQ_NONBLOCK)) { + pr_err_ratelimited( + "%s: Recv info for label %d not yet initialized\n", + __func__, client_desc->label); + ret = -EAGAIN; + goto err; + } + + spin_unlock(&cap_table_entry->cap_entry_lock); + + if (wait_event_interruptible(cap_table_entry->rx_wq, + cap_table_entry->rx_cap_id != GH_CAPID_INVAL)) + return -ERESTARTSYS; + + spin_lock(&cap_table_entry->cap_entry_lock); + + if (!cap_table_entry->rx_irq) { + pr_err_ratelimited("%s: Rx IRQ for label %d not yet setup\n", + __func__, client_desc->label); + ret = -EAGAIN; + goto err; + } + + spin_unlock(&cap_table_entry->cap_entry_lock); + + do { + if (cap_table_entry->rx_empty && (flags & GH_MSGQ_NONBLOCK)) + return -EAGAIN; + + if (wait_event_interruptible(cap_table_entry->rx_wq, + !cap_table_entry->rx_empty)) + return -ERESTARTSYS; + + ret = __gh_msgq_recv(cap_table_entry, buff, buff_size, + recv_size, flags); + } while (ret == -EAGAIN); + + if (!ret) + print_hex_dump_debug(__func__, DUMP_PREFIX_OFFSET, + 4, 1, buff, *recv_size, false); + + return ret; + +err: + spin_unlock(&cap_table_entry->cap_entry_lock); + return ret; +} +EXPORT_SYMBOL(gh_msgq_recv); + +static int __gh_msgq_send(struct gh_msgq_cap_table *cap_table_entry, + void *buff, size_t size, u64 tx_flags) +{ + struct gh_hcall_msgq_send_resp resp = {}; + unsigned long flags; + int gh_ret; + int ret = 0; + + /* Discard the driver specific flags, and keep only HVC specifics */ + tx_flags &= GH_MSGQ_HVC_FLAGS_MASK; + + print_hex_dump_debug("gh_msgq_send: ", DUMP_PREFIX_OFFSET, + 4, 1, buff, size, false); + + spin_lock_irqsave(&cap_table_entry->tx_lock, flags); + gh_ret = gh_hcall_msgq_send(cap_table_entry->tx_cap_id, + size, buff, tx_flags, &resp); + + switch (gh_ret) { + case GH_ERROR_OK: + cap_table_entry->tx_full = !resp.not_full; + ret = 0; + break; + case GH_ERROR_MSGQUEUE_FULL: + cap_table_entry->tx_full = true; + ret = -EAGAIN; + break; + default: + ret = gh_remap_error(gh_ret); + } + + spin_unlock_irqrestore(&cap_table_entry->tx_lock, flags); + + if (ret != 0 && ret != -EAGAIN) + pr_err("%s: Failed to send on msgq. Hypercall error: %d\n", + __func__, gh_ret); + + return ret; +} + +/** + * gh_msgq_send: Send a message to the client on a different VM + * @client_desc: The client descriptor that was obtained via gh_msgq_register() + * @buff: Pointer to the buffer that needs to be sent + * @size: The size of the buffer + * @flags: Optional flags to pass to send the data. For the list of flags, + * see linux/gunyah/gh_msgq.h + * + * The function returns -EINVAL if the caller passes invalid arguments, + * -EAGAIN if the message queue is not yet ready to communicate, and -EPERM if + * the caller doesn't have permissions to send the data. + * + */ +int gh_msgq_send(void *msgq_client_desc, + void *buff, size_t size, unsigned long flags) +{ + struct gh_msgq_desc *client_desc = msgq_client_desc; + struct gh_msgq_cap_table *cap_table_entry; + int ret; + + if (!client_desc || !buff || !size) + return -EINVAL; + + if (size > GH_MSGQ_MAX_MSG_SIZE_BYTES) + return -E2BIG; + + if (client_desc->cap_table == NULL) + return -EAGAIN; + + cap_table_entry = client_desc->cap_table; + + spin_lock(&cap_table_entry->cap_entry_lock); + + if (cap_table_entry->client_desc != client_desc) { + pr_err("%s: Invalid client descriptor\n", __func__); + ret = -EINVAL; + goto err; + } + + if ((cap_table_entry->tx_cap_id == GH_CAPID_INVAL) && + (flags & GH_MSGQ_NONBLOCK)) { + pr_err_ratelimited( + "%s: Send info for label %d not yet initialized\n", + __func__, client_desc->label); + ret = -EAGAIN; + goto err; + } + + spin_unlock(&cap_table_entry->cap_entry_lock); + + if (wait_event_interruptible(cap_table_entry->tx_wq, + cap_table_entry->tx_cap_id != GH_CAPID_INVAL)) + return -ERESTARTSYS; + + spin_lock(&cap_table_entry->cap_entry_lock); + + if (!cap_table_entry->tx_irq) { + pr_err_ratelimited("%s: Tx IRQ for label %d not yet setup\n", + __func__, client_desc->label); + ret = -EAGAIN; + goto err; + } + + spin_unlock(&cap_table_entry->cap_entry_lock); + + do { + if (cap_table_entry->tx_full && (flags & GH_MSGQ_NONBLOCK)) + return -EAGAIN; + + if (wait_event_interruptible(cap_table_entry->tx_wq, + !cap_table_entry->tx_full)) + return -ERESTARTSYS; + + ret = __gh_msgq_send(cap_table_entry, buff, size, flags); + } while (ret == -EAGAIN); + + return ret; +err: + spin_unlock(&cap_table_entry->cap_entry_lock); + return ret; +} +EXPORT_SYMBOL(gh_msgq_send); + +/** + * gh_msgq_register: Register as a client to the use the message queue + * @label: The label associated to the message queue that the client wants + * to communicate + * + * The function returns a descriptor for the clients to send and receive the + * messages. Else, returns -EBUSY if some other client is already regitsered + * to this label, and -EINVAL for invalid arguments. The caller should check + * the return value using IS_ERR_OR_NULL() and PTR_ERR() to extract the error + * code. + */ +void *gh_msgq_register(int label) +{ + struct gh_msgq_cap_table *cap_table_entry = NULL, *tmp_entry; + struct gh_msgq_desc *client_desc; + + if (label < 0) + return ERR_PTR(-EINVAL); + + spin_lock(&gh_msgq_cap_list_lock); + list_for_each_entry(tmp_entry, &gh_msgq_cap_list, entry) { + if (label == tmp_entry->label) { + cap_table_entry = tmp_entry; + break; + } + } + + if (cap_table_entry == NULL) { + cap_table_entry = gh_msgq_alloc_entry(label); + if (IS_ERR(cap_table_entry)) { + spin_unlock(&gh_msgq_cap_list_lock); + return cap_table_entry; + } + } + spin_unlock(&gh_msgq_cap_list_lock); + + spin_lock(&cap_table_entry->cap_entry_lock); + + /* Multiple clients cannot register to the same label (msgq) */ + if (cap_table_entry->client_desc) { + spin_unlock(&cap_table_entry->cap_entry_lock); + pr_err("%s: Client already exists for label %d\n", + __func__, label); + return ERR_PTR(-EBUSY); + } + + client_desc = kzalloc(sizeof(*client_desc), GFP_ATOMIC); + if (!client_desc) { + spin_unlock(&cap_table_entry->cap_entry_lock); + return ERR_PTR(-ENOMEM); + } + + client_desc->label = label; + client_desc->cap_table = cap_table_entry; + + cap_table_entry->client_desc = client_desc; + spin_unlock(&cap_table_entry->cap_entry_lock); + + pr_info("gh_msgq: Registered client for label: %d\n", label); + + return client_desc; +} +EXPORT_SYMBOL(gh_msgq_register); + +/** + * gh_msgq_unregister: Unregister as a client to the use the message queue + * @client_desc: The descriptor that was passed via gh_msgq_register() + * + * The function returns 0 is the client was unregistered successfully. Else, + * -EINVAL for invalid arguments. + */ +int gh_msgq_unregister(void *msgq_client_desc) +{ + struct gh_msgq_desc *client_desc = msgq_client_desc; + struct gh_msgq_cap_table *cap_table_entry; + + if (!client_desc) + return -EINVAL; + + cap_table_entry = client_desc->cap_table; + + spin_lock(&cap_table_entry->cap_entry_lock); + + /* Is the client trying to free someone else's msgq? */ + if (cap_table_entry->client_desc != client_desc) { + pr_err("%s: Trying to free invalid client descriptor!\n", + __func__); + spin_unlock(&cap_table_entry->cap_entry_lock); + return -EINVAL; + } + + cap_table_entry->client_desc = NULL; + + spin_unlock(&cap_table_entry->cap_entry_lock); + + pr_info("%s: Unregistered client for label: %d\n", + __func__, client_desc->label); + + kfree(client_desc); + + return 0; +} +EXPORT_SYMBOL(gh_msgq_unregister); + +int gh_msgq_populate_cap_info(int label, u64 cap_id, int direction, int irq) +{ + struct gh_msgq_cap_table *cap_table_entry = NULL, *tmp_entry; + int ret; + + if (label < 0) { + pr_err("%s: Invalid label passed\n", __func__); + return -EINVAL; + } + + if (irq < 0) { + pr_err("%s: Invalid IRQ number passed\n", __func__); + return -ENXIO; + } + + spin_lock(&gh_msgq_cap_list_lock); + list_for_each_entry(tmp_entry, &gh_msgq_cap_list, entry) { + if (label == tmp_entry->label) { + cap_table_entry = tmp_entry; + break; + } + } + + if (cap_table_entry == NULL) { + cap_table_entry = gh_msgq_alloc_entry(label); + if (IS_ERR(cap_table_entry)) { + spin_unlock(&gh_msgq_cap_list_lock); + return PTR_ERR(cap_table_entry); + } + } + spin_unlock(&gh_msgq_cap_list_lock); + + if (direction == GH_MSGQ_DIRECTION_TX) { + ret = request_irq(irq, gh_msgq_tx_isr, 0, + cap_table_entry->tx_irq_name, cap_table_entry); + if (ret < 0) + goto err; + + spin_lock(&cap_table_entry->cap_entry_lock); + cap_table_entry->tx_cap_id = cap_id; + cap_table_entry->tx_irq = irq; + spin_unlock(&cap_table_entry->cap_entry_lock); + + wake_up_interruptible(&cap_table_entry->tx_wq); + } else if (direction == GH_MSGQ_DIRECTION_RX) { + ret = request_irq(irq, gh_msgq_rx_isr, 0, + cap_table_entry->rx_irq_name, cap_table_entry); + if (ret < 0) + goto err; + + spin_lock(&cap_table_entry->cap_entry_lock); + cap_table_entry->rx_cap_id = cap_id; + cap_table_entry->rx_irq = irq; + spin_unlock(&cap_table_entry->cap_entry_lock); + + wake_up_interruptible(&cap_table_entry->rx_wq); + } else { + pr_err("%s: Invalid direction passed\n", __func__); + ret = -EINVAL; + goto err; + } + + irq_set_irq_wake(irq, 1); + + pr_debug( + "%s: label: %d; cap_id: %llu; dir: %d; irq: %d\n", + __func__, label, cap_id, direction, irq); + + return 0; + +err: + spin_lock(&gh_msgq_cap_list_lock); + list_del(&cap_table_entry->entry); + spin_unlock(&gh_msgq_cap_list_lock); + kfree(cap_table_entry->tx_irq_name); + kfree(cap_table_entry->rx_irq_name); + kfree(cap_table_entry); + return ret; +} +EXPORT_SYMBOL(gh_msgq_populate_cap_info); + +/** + * gh_msgq_reset_cap_info: Reset the msgq cap info + * @label: The label associated to the message queue that the client wants + * to communicate + * @direction: The direction of msgq + * @irq: The irq associated with the msgq + * + * The function resets all the msgq related info. + */ +int gh_msgq_reset_cap_info(enum gh_msgq_label label, int direction, int *irq) +{ + struct gh_msgq_cap_table *cap_table_entry = NULL, *tmp_entry; + int ret; + + if (label < 0) { + pr_err("%s: Invalid label passed\n", __func__); + return -EINVAL; + } + + if (!irq) + return -EINVAL; + + spin_lock(&gh_msgq_cap_list_lock); + list_for_each_entry(tmp_entry, &gh_msgq_cap_list, entry) { + if (label == tmp_entry->label) { + cap_table_entry = tmp_entry; + break; + } + } + spin_unlock(&gh_msgq_cap_list_lock); + + if (cap_table_entry == NULL) + return -EINVAL; + + if (direction == GH_MSGQ_DIRECTION_TX) { + if (!cap_table_entry->tx_irq) { + pr_err("%s: Tx IRQ not setup\n", __func__); + ret = -ENXIO; + goto err_unlock; + } + + *irq = cap_table_entry->tx_irq; + spin_lock(&cap_table_entry->cap_entry_lock); + cap_table_entry->tx_cap_id = GH_CAPID_INVAL; + cap_table_entry->tx_irq = 0; + spin_unlock(&cap_table_entry->cap_entry_lock); + } else if (direction == GH_MSGQ_DIRECTION_RX) { + if (!cap_table_entry->rx_irq) { + pr_err("%s: Rx IRQ not setup\n", __func__); + ret = -ENXIO; + goto err_unlock; + } + + *irq = cap_table_entry->rx_irq; + spin_lock(&cap_table_entry->cap_entry_lock); + cap_table_entry->rx_cap_id = GH_CAPID_INVAL; + cap_table_entry->rx_irq = 0; + spin_unlock(&cap_table_entry->cap_entry_lock); + } else { + pr_err("%s: Invalid direction passed\n", __func__); + ret = -EINVAL; + goto err_unlock; + } + + if (*irq) + free_irq(*irq, cap_table_entry); + + return 0; + +err_unlock: + return ret; +} +EXPORT_SYMBOL(gh_msgq_reset_cap_info); + +static int gh_msgq_probe_direction(struct platform_device *pdev, int label, + int direction, int idx) +{ + int irq, ret; + u64 capid; + + irq = platform_get_irq(pdev, idx); + if (irq < 0) { + dev_err(&pdev->dev, "Failed to get the IRQ%d. ret: %d\n", + idx, irq); + return irq; + } + + ret = of_property_read_u64_index(pdev->dev.of_node, "reg", idx, &capid); + if (ret) { + dev_err(&pdev->dev, "Failed to get capid[%d]\n", idx); + return ret; + } + + return gh_msgq_populate_cap_info(label, capid, direction, irq); +} + +int gh_msgq_probe(struct platform_device *pdev, int label) +{ + int ret, idx = 0; + struct device_node *node = pdev->dev.of_node; + bool duplex; + + duplex = of_property_read_bool(node, "qcom,is-full-duplex"); + + if (duplex || of_property_read_bool(node, "qcom,is-sender")) { + ret = gh_msgq_probe_direction(pdev, label, GH_MSGQ_DIRECTION_TX, + idx); + if (ret) + return ret; + idx++; + } + + if (duplex || of_property_read_bool(node, "qcom,is-receiver")) { + ret = gh_msgq_probe_direction(pdev, label, GH_MSGQ_DIRECTION_RX, + idx); + if (ret) + return ret; + } + + return 0; +} +EXPORT_SYMBOL(gh_msgq_probe); + +static void gh_msgq_cleanup(void) +{ + struct gh_msgq_cap_table *cap_table_entry; + struct gh_msgq_cap_table *temp; + + spin_lock(&gh_msgq_cap_list_lock); + list_for_each_entry_safe(cap_table_entry, temp, &gh_msgq_cap_list, entry) { + kfree(cap_table_entry->tx_irq_name); + kfree(cap_table_entry->rx_irq_name); + kfree(cap_table_entry); + } + spin_unlock(&gh_msgq_cap_list_lock); +} + +static int __init gh_msgq_init(void) +{ + return 0; +} +module_init(gh_msgq_init); + +static void __exit gh_msgq_exit(void) +{ + gh_msgq_cleanup(); +} +module_exit(gh_msgq_exit); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Qualcomm Technologies, Inc. Gunyah Message Queue Driver"); diff --git a/drivers/virt/gunyah/hcall_msgq.h b/drivers/virt/gunyah/hcall_msgq.h new file mode 100644 index 000000000000..efcc316baa37 --- /dev/null +++ b/drivers/virt/gunyah/hcall_msgq.h @@ -0,0 +1,131 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2021, The Linux Foundation. All rights reserved. + */ +#ifndef __GH_HCALL_MSGQ_H +#define __GH_HCALL_MSGQ_H + +#include +#include + +#include +#include +#include + +static inline int gh_hcall_msgq_bind_send(gh_capid_t msgq_capid, + gh_capid_t vic_capid, + gh_virq_handle_t virq_info) +{ + struct gh_hcall_resp _resp = {0}; + + return _gh_hcall(0x6017, + (struct gh_hcall_args){ msgq_capid, vic_capid, + virq_info }, + &_resp); +} + +static inline int gh_hcall_msgq_bind_recv(gh_capid_t msgq_capid, + gh_capid_t vic_capid, + gh_virq_handle_t virq_info) +{ + struct gh_hcall_resp _resp = {0}; + + return _gh_hcall(0x6018, + (struct gh_hcall_args){ msgq_capid, vic_capid, + virq_info }, + &_resp); +} + +static inline int gh_hcall_msgq_unbind_send(gh_capid_t msgq_capid) +{ + struct gh_hcall_resp _resp = {0}; + + return _gh_hcall(0x6019, (struct gh_hcall_args){ msgq_capid }, &_resp); +} + +static inline int gh_hcall_msgq_unbind_recv(gh_capid_t msgq_capid) +{ + struct gh_hcall_resp _resp = {0}; + + return _gh_hcall(0x601A, (struct gh_hcall_args){ msgq_capid }, &_resp); +} + +struct gh_hcall_msgq_send_resp { + bool not_full; +}; + +static inline int gh_hcall_msgq_send(gh_capid_t msgq_capid, size_t size, + void *data, u64 send_flags, + struct gh_hcall_msgq_send_resp *resp) +{ + int ret; + struct gh_hcall_resp _resp = {0}; + + ret = _gh_hcall(0x601B, + (struct gh_hcall_args){ msgq_capid, size, (unsigned long)data, + send_flags }, + &_resp); + + if (!ret && resp) + resp->not_full = _resp.resp1; + + return ret; +} + +struct gh_hcall_msgq_recv_resp { + size_t recv_size; + bool not_empty; +}; + +static inline int gh_hcall_msgq_recv(gh_capid_t msgq_capid, void *buffer, + size_t max_size, + struct gh_hcall_msgq_recv_resp *resp) +{ + int ret; + struct gh_hcall_resp _resp = {0}; + + ret = _gh_hcall(0x601C, + (struct gh_hcall_args){ msgq_capid, (unsigned long)buffer, + max_size }, + &_resp); + + if (!ret && resp) { + resp->recv_size = _resp.resp1; + resp->not_empty = _resp.resp2; + } + + return ret; +} + +static inline int gh_hcall_msgq_flush(gh_capid_t msgq_capid) +{ + struct gh_hcall_resp _resp = {0}; + + return _gh_hcall(0x601D, (struct gh_hcall_args){ msgq_capid }, &_resp); +} + +static inline int gh_hcall_msgq_configure_send(gh_capid_t msgq_capid, + long not_full_threshold, + long not_full_delay) +{ + struct gh_hcall_resp _resp = {0}; + + return _gh_hcall(0x601F, + (struct gh_hcall_args){ msgq_capid, not_full_threshold, + not_full_delay, -1 }, + &_resp); +} + +static inline int gh_hcall_msgq_configure_recv(gh_capid_t msgq_capid, + long not_empty_threshold, + long not_empty_delay) +{ + struct gh_hcall_resp _resp = {0}; + + return _gh_hcall(0x6020, + (struct gh_hcall_args){ msgq_capid, not_empty_threshold, + not_empty_delay, -1 }, + &_resp); +} + +#endif