diff --git a/drivers/virt/haven/Kconfig b/drivers/virt/haven/Kconfig index 37a2eeeb9c31..29370773efa9 100644 --- a/drivers/virt/haven/Kconfig +++ b/drivers/virt/haven/Kconfig @@ -37,4 +37,14 @@ config HH_RM_DRV interface to other driver in order to obtain the services provided by the RM-VM. +config HH_DBL + tristate "Haven Doorbell driver" + help + Haven 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/haven/Makefile b/drivers/virt/haven/Makefile index fe0ae6120f19..a1f9b15a438f 100644 --- a/drivers/virt/haven/Makefile +++ b/drivers/virt/haven/Makefile @@ -2,3 +2,4 @@ obj-$(CONFIG_HH_MSGQ) += hh_msgq.o obj-$(CONFIG_HH_RM_DRV) += hh_rm_drv.o hh_rm_drv-y := hh_rm_core.o hh_rm_iface.o +obj-$(CONFIG_HH_DBL) += hh_dbl.o diff --git a/drivers/virt/haven/hh_dbl.c b/drivers/virt/haven/hh_dbl.c new file mode 100644 index 000000000000..eb6c0b806190 --- /dev/null +++ b/drivers/virt/haven/hh_dbl.c @@ -0,0 +1,621 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + * + */ + +#include +#include +#include +#include + +#include +#include +#include + +struct hh_dbl_desc { + enum hh_dbl_label label; +}; + +enum hh_dbl_dir { + HH_DBL_DIRECTION_TX, + HH_DBL_DIRECTION_RX +}; + +struct hh_dbl_cap_table { + struct hh_dbl_desc *client_desc; + struct mutex cap_entry_lock; + hh_capid_t tx_cap_id; + int tx_reg_done; + + hh_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; +}; + +static struct hh_dbl_cap_table hh_dbl_cap_table[HH_DBL_LABEL_MAX]; + +/** + * hh_dbl_validate_params - Validate doorbell common parameters + */ +static int hh_dbl_validate_params(struct hh_dbl_desc *client_desc, + enum hh_dbl_dir dir) +{ + struct hh_dbl_cap_table *cap_table_entry; + + if (!client_desc) + return -EINVAL; + + /* Check if the client has manipulated the label */ + if (client_desc->label < 0 || client_desc->label >= HH_DBL_LABEL_MAX) + return -EINVAL; + + cap_table_entry = &hh_dbl_cap_table[client_desc->label]; + + if (cap_table_entry->client_desc != client_desc) { + pr_err("%s: Invalid client descriptor\n", __func__); + return -EINVAL; + } + + /* + * rx_cap_id == NULL and tx_cap_id == NULL means TWO things + * either "hh_dbl_populate_cap_info()" call from RM is not over + * or + * There are no doorbell setup for Tx or Rx + */ + if (dir == HH_DBL_DIRECTION_RX) { + if (!cap_table_entry->rx_cap_id || + !cap_table_entry->rx_reg_done) { + pr_err("%s: label: %d; rx_cap_id: %llu; dir: %d rx_done: %d\n", + __func__, client_desc->label, + cap_table_entry->rx_cap_id, + dir, cap_table_entry->rx_reg_done); + return -EAGAIN; + } + } else { + if (!cap_table_entry->tx_cap_id || + !cap_table_entry->tx_reg_done) { + pr_err("%s: label: %d; tx_cap_id: %llu; dir: %d tx_done: %d\n", + __func__, client_desc->label, + cap_table_entry->tx_cap_id, + dir, cap_table_entry->tx_reg_done); + return -EAGAIN; + } + } + + return 0; +} + +/** + * hh_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 + * + * 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 hh_dbl_read_and_clean(void *dbl_client_desc, hh_dbl_flags_t *clear_flags) +{ + struct hh_dbl_cap_table *cap_table_entry; + struct hh_hcall_dbl_recv_resp recv_resp; + struct hh_dbl_desc *client_desc = dbl_client_desc; + int ret, hh_ret; + + if (!clear_flags) + return -EINVAL; + + ret = hh_dbl_validate_params(client_desc, HH_DBL_DIRECTION_RX); + if (ret) + return ret; + + cap_table_entry = &hh_dbl_cap_table[client_desc->label]; + + if (mutex_lock_interruptible(&cap_table_entry->cap_entry_lock)) + return -ERESTARTSYS; + + hh_ret = hh_hcall_dbl_recv(cap_table_entry->rx_cap_id, + *clear_flags, &recv_resp); + + ret = hh_remap_error(hh_ret); + if (ret != 0) + pr_err("%s: Hypercall failed, ret = %d\n", __func__, hh_ret); + else + *clear_flags = recv_resp.old_flags; + + mutex_unlock(&cap_table_entry->cap_entry_lock); + + return ret; +} +EXPORT_SYMBOL(hh_dbl_read_and_clean); + +/** + * hh_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 + * + * 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 hh_dbl_set_mask(void *dbl_client_desc, hh_dbl_flags_t enable_mask, + hh_dbl_flags_t ack_mask) +{ + struct hh_dbl_cap_table *cap_table_entry; + struct hh_dbl_desc *client_desc = dbl_client_desc; + int ret, hh_ret; + + ret = hh_dbl_validate_params(client_desc, HH_DBL_DIRECTION_RX); + if (ret) + return ret; + + cap_table_entry = &hh_dbl_cap_table[client_desc->label]; + + if (mutex_lock_interruptible(&cap_table_entry->cap_entry_lock)) + return -ERESTARTSYS; + + hh_ret = hh_hcall_dbl_mask(cap_table_entry->rx_cap_id, + enable_mask, ack_mask); + + ret = hh_remap_error(hh_ret); + if (ret != 0) + pr_err("%s: Hypercall failed ret = %d\n", __func__, hh_ret); + + mutex_unlock(&cap_table_entry->cap_entry_lock); + + return ret; +} +EXPORT_SYMBOL(hh_dbl_set_mask); + +/** + * hh_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. + * + * 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 hh_dbl_send(void *dbl_client_desc, hh_dbl_flags_t *newflags) +{ + struct hh_dbl_cap_table *cap_table_entry; + struct hh_hcall_dbl_send_resp send_resp; + struct hh_dbl_desc *client_desc = dbl_client_desc; + int ret, hh_ret; + + if (!newflags) + return -EINVAL; + + ret = hh_dbl_validate_params(client_desc, HH_DBL_DIRECTION_TX); + if (ret) + return ret; + + cap_table_entry = &hh_dbl_cap_table[client_desc->label]; + + if (mutex_lock_interruptible(&cap_table_entry->cap_entry_lock)) + return -ERESTARTSYS; + + hh_ret = hh_hcall_dbl_send(cap_table_entry->tx_cap_id, *newflags, + &send_resp); + + ret = hh_remap_error(hh_ret); + if (ret != 0) + pr_err("%s: Hypercall failed ret = %d\n", hh_ret); + else + *newflags = send_resp.old_flags; + + mutex_unlock(&cap_table_entry->cap_entry_lock); + + return ret; +} +EXPORT_SYMBOL(hh_dbl_send); + +/** + * hh_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 + * + * 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 hh_dbl_reset(void *dbl_client_desc) +{ + struct hh_dbl_cap_table *cap_table_entry; + struct hh_dbl_desc *client_desc = dbl_client_desc; + int ret, hh_ret; + + ret = hh_dbl_validate_params(client_desc, HH_DBL_DIRECTION_RX); + if (ret) + return ret; + + cap_table_entry = &hh_dbl_cap_table[client_desc->label]; + + if (mutex_lock_interruptible(&cap_table_entry->cap_entry_lock)) + return -ERESTARTSYS; + + hh_ret = hh_hcall_dbl_reset(cap_table_entry->rx_cap_id); + + ret = hh_remap_error(hh_ret); + if (ret != 0) + pr_err("%s: Hypercall failed ret = %d\n", __func__, hh_ret); + + mutex_unlock(&cap_table_entry->cap_entry_lock); + + return ret; +} +EXPORT_SYMBOL(hh_dbl_reset); + +static irqreturn_t hh_dbl_rx_callback_thread(int irq, void *rx_priv_data) +{ + struct hh_dbl_cap_table *cap_table_entry; + + cap_table_entry = container_of(rx_priv_data, + struct hh_dbl_cap_table, rx_priv_data); + + if (!cap_table_entry->rx_callback) + return IRQ_HANDLED; + + cap_table_entry->rx_callback(rx_priv_data); + return IRQ_HANDLED; +} + +/** + * hh_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 *hh_dbl_tx_register(enum hh_dbl_label label) +{ + struct hh_dbl_cap_table *cap_table_entry; + struct hh_dbl_desc *client_desc; + int ret; + + if (label < 0 || label >= HH_DBL_LABEL_MAX) + return ERR_PTR(-EINVAL); + + cap_table_entry = &hh_dbl_cap_table[label]; + + if (mutex_lock_interruptible(&cap_table_entry->cap_entry_lock)) + return ERR_PTR(-ERESTARTSYS); + + /* 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_KERNEL); + 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); + + mutex_unlock(&cap_table_entry->cap_entry_lock); + return client_desc; + +err: + mutex_unlock(&cap_table_entry->cap_entry_lock); + return ERR_PTR(ret); +} +EXPORT_SYMBOL(hh_dbl_tx_register); + +/** + * hh_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 receieve 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 *hh_dbl_rx_register(enum hh_dbl_label label, dbl_rx_cb_t rx_cb, void *priv) +{ + struct hh_dbl_cap_table *cap_table_entry; + struct hh_dbl_desc *client_desc; + int ret; + + if (label < 0 || label >= HH_DBL_LABEL_MAX) + return ERR_PTR(-EINVAL); + + cap_table_entry = &hh_dbl_cap_table[label]; + + if (mutex_lock_interruptible(&cap_table_entry->cap_entry_lock)) + return ERR_PTR(-ERESTARTSYS); + + /* 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_KERNEL); + 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; + + ret = request_threaded_irq(cap_table_entry->rx_irq, + NULL, + hh_dbl_rx_callback_thread, + 0, + cap_table_entry->rx_irq_name, + priv); + + if (ret < 0) { + pr_err("%s: IRQ registration failed\n", __func__); + cap_table_entry->rx_callback = NULL; + cap_table_entry->rx_priv_data = NULL; + goto err; + } + + cap_table_entry->rx_reg_done = 1; + + pr_debug("%s: Registered Rx client for label: %d\n", __func__, label); + + mutex_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); + mutex_unlock(&cap_table_entry->cap_entry_lock); + return ERR_PTR(ret); +} +EXPORT_SYMBOL(hh_dbl_rx_register); + +/** + * hh_dbl_tx_unregister: Unregister Tx client to use the doorbell + * @client_desc: The descriptor that was passed via hh_dbl_tx_register() or + * hh_dbl_rx_register() + * + * The function returns 0 is the client was unregistered successfully. Else, + * -EINVAL for invalid arguments. + */ +int hh_dbl_tx_unregister(void *dbl_client_desc) +{ + struct hh_dbl_desc *client_desc = dbl_client_desc; + struct hh_dbl_cap_table *cap_table_entry; + + if (!client_desc) + return -EINVAL; + + /* Check if the client has manipulated the label */ + if (client_desc->label < 0 || client_desc->label >= HH_DBL_LABEL_MAX) + return -EINVAL; + + cap_table_entry = &hh_dbl_cap_table[client_desc->label]; + + if (mutex_lock_interruptible(&cap_table_entry->cap_entry_lock)) + return -ERESTARTSYS; + + /* 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__); + mutex_unlock(&cap_table_entry->cap_entry_lock); + return -EINVAL; + } + + /* 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; + mutex_unlock(&cap_table_entry->cap_entry_lock); + + pr_debug("%s: Unregistered client for label: %d\n", + __func__, client_desc->label); + + return 0; +} +EXPORT_SYMBOL(hh_dbl_tx_unregister); + +/** + * hh_dbl_rx_unregister: Unregister Rx client to use the doorbell + * @client_desc: The descriptor that was passed via hh_dbl_tx_register() or + * hh_dbl_rx_register() + * + * The function returns 0 is the client was unregistered successfully. Else, + * -EINVAL for invalid arguments. + */ +int hh_dbl_rx_unregister(void *dbl_client_desc) +{ + struct hh_dbl_desc *client_desc = dbl_client_desc; + struct hh_dbl_cap_table *cap_table_entry; + + if (!client_desc) + return -EINVAL; + + /* Check if the client has manipulated the label */ + if (client_desc->label < 0 || client_desc->label >= HH_DBL_LABEL_MAX) + return -EINVAL; + + cap_table_entry = &hh_dbl_cap_table[client_desc->label]; + + if (mutex_lock_interruptible(&cap_table_entry->cap_entry_lock)) + return -ERESTARTSYS; + + /* 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__); + mutex_unlock(&cap_table_entry->cap_entry_lock); + return -EINVAL; + } + + /* 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; + + mutex_unlock(&cap_table_entry->cap_entry_lock); + + pr_debug("%s: Unregistered client for label: %d\n", __func__, + client_desc->label); + + return 0; +} +EXPORT_SYMBOL(hh_dbl_rx_unregister); + +/** + * This API is called by RM driver to populate doorbell objects + */ +int hh_dbl_populate_cap_info(enum hh_dbl_label label, u64 cap_id, + int direction, int rx_irq) +{ + struct hh_dbl_cap_table *cap_table_entry; + int ret = 0; + + if (label < 0 || label >= HH_DBL_LABEL_MAX) { + pr_err("%s: Invalid label passed\n", __func__); + return -EINVAL; + } + + cap_table_entry = &hh_dbl_cap_table[label]; + + switch (direction) { + case HH_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; + } + cap_table_entry->tx_cap_id = cap_id; + pr_debug("%s: label: %d; tx_cap_id: %llu; dir: %d; rx_irq: %d\n", + label, cap_id, direction, rx_irq); + break; + case HH_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_cap_id = cap_id; + cap_table_entry->rx_irq = rx_irq; + pr_debug("%s: label: %d; rx_cap_id: %llu; dir: %d; rx_irq: %d\n", + 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(hh_dbl_populate_cap_info); + +static void hh_dbl_cleanup(int begin_idx) +{ + struct hh_dbl_cap_table *cap_table_entry; + int i; + + if (begin_idx >= HH_DBL_LABEL_MAX) + begin_idx = HH_DBL_LABEL_MAX - 1; + + for (i = begin_idx; i >= 0; i--) { + cap_table_entry = &hh_dbl_cap_table[i]; + kfree(cap_table_entry->rx_irq_name); + mutex_destroy(&cap_table_entry->cap_entry_lock); + } +} + +static int __init hh_dbl_init(void) +{ + struct hh_dbl_cap_table *entry; + int ret; + int i; + + for (i = 0; i < HH_DBL_LABEL_MAX; i++) { + entry = &hh_dbl_cap_table[i]; + mutex_init(&entry->cap_entry_lock); + entry->rx_irq_name = kasprintf(GFP_KERNEL, "hh_dbl_rx_%d", i); + if (!entry->rx_irq_name) { + ret = -ENOMEM; + goto err; + } + } + + return 0; + +err: + hh_dbl_cleanup(i); + return ret; +} +module_init(hh_dbl_init); + +static void __exit hh_dbl_exit(void) +{ + hh_dbl_cleanup(HH_DBL_LABEL_MAX - 1); +} +module_exit(hh_dbl_exit); + +MODULE_DESCRIPTION("Qualcomm Technologies, Inc. Haven Doorbell Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/include/linux/haven/hh_dbl.h b/include/linux/haven/hh_dbl.h new file mode 100644 index 000000000000..7ad444fa7b22 --- /dev/null +++ b/include/linux/haven/hh_dbl.h @@ -0,0 +1,34 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + * + */ + +#ifndef __HH_DBL_H +#define __HH_DBL_H + +#include "hh_common.h" + +typedef void (*dbl_rx_cb_t)(void *priv_data); + +enum hh_dbl_label { + HH_DBL_TUI_LABEL, + HH_DBL_LABEL_MAX +}; + +void *hh_dbl_tx_register(enum hh_dbl_label label); +void *hh_dbl_rx_register(enum hh_dbl_label label, dbl_rx_cb_t rx_cb, + void *priv); + +int hh_dbl_tx_unregister(void *dbl_client_desc); +int hh_dbl_rx_unregister(void *dbl_client_desc); + +int hh_dbl_send(void *dbl_client_desc, uint64_t *newflags); +int hh_dbl_set_mask(void *dbl_client_desc, hh_dbl_flags_t enable_mask, + hh_dbl_flags_t ack_mask); +int hh_dbl_read_and_clean(void *dbl_client_desc, hh_dbl_flags_t *clear_flags); +int hh_dbl_reset(void *dbl_client_desc); +int hh_dbl_populate_cap_info(enum hh_dbl_label label, u64 cap_id, + int direction, int rx_irq); + +#endif