diff --git a/arch/arm64/configs/vendor/lahaina_GKI.config b/arch/arm64/configs/vendor/lahaina_GKI.config index b28707a99ffe..abf60754da1c 100644 --- a/arch/arm64/configs/vendor/lahaina_GKI.config +++ b/arch/arm64/configs/vendor/lahaina_GKI.config @@ -121,6 +121,11 @@ CONFIG_MSM_CDSP_LOADER=m CONFIG_UIO_MSM_SHAREDMEM=m CONFIG_QCOM_SOCINFO=m CONFIG_BTFM_SLIM=m +CONFIG_VIRT_DRIVERS=y +CONFIG_HAVEN_DRIVERS=y +CONFIG_HH_MSGQ=m +CONFIG_HH_RM_DRV=m +CONFIG_HH_DBL=m CONFIG_CFI_PERMISSIVE=y # CONFIG_SND_SOC_WCD9335 is not set # CONFIG_SLIM_QCOM_CTRL is not set diff --git a/drivers/virt/haven/Kconfig b/drivers/virt/haven/Kconfig index 9dc44f3d1037..29370773efa9 100644 --- a/drivers/virt/haven/Kconfig +++ b/drivers/virt/haven/Kconfig @@ -24,4 +24,27 @@ config HH_MSGQ the services offered by the drivers is simply to send and receive messages in a blocking manner. +config HH_RM_DRV + tristate "Haven Resource Manager driver" + help + The Haven Resource Manager driver is used to communicate with the + Resource Manager Virtual Machine (RM-VM). The RM-VM acts as a mediator + and provides numerous services to the other VMs running in the system, + such as notifying when a particular VM is up, resource (IRQ/device) + sharing between VMs, information about the IPC mechanisms, and so on. + + The Resource Manager driver runs on the Virtual Machine and acts as an + 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 68ab1b7f0809..a1f9b15a438f 100644 --- a/drivers/virt/haven/Makefile +++ b/drivers/virt/haven/Makefile @@ -1,2 +1,5 @@ # SPDX-License-Identifier: GPL-2.0-only 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/drivers/virt/haven/hh_rm_core.c b/drivers/virt/haven/hh_rm_core.c new file mode 100644 index 000000000000..6be1b9e17924 --- /dev/null +++ b/drivers/virt/haven/hh_rm_core.c @@ -0,0 +1,807 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "hh_rm_drv_private.h" + +#define HH_RM_MAX_NUM_FRAGMENTS 62 + +#define GIC_V3_SPI_MAX 1019 + +#define HH_RM_NO_IRQ_ALLOC -1 + +#define HH_RM_MAX_MSG_SIZE_BYTES \ + (HH_MSGQ_MAX_MSG_SIZE_BYTES - sizeof(struct hh_rm_rpc_hdr)) + +struct hh_rm_connection { + u16 seq; + void *recv_buff; + u32 reply_err_code; + size_t recv_buff_size; + + struct completion seq_done; + + u8 num_fragments; + u8 fragments_received; + void *current_recv_buff; +}; + +static struct task_struct *hh_rm_drv_recv_task; +static struct hh_msgq_desc *hh_rm_msgq_desc; + +static DEFINE_MUTEX(hh_rm_call_idr_lock); +static DEFINE_IDR(hh_rm_call_idr); + +static DEFINE_IDA(hh_rm_free_virq_ida); +static struct device_node *hh_rm_intc; +static u32 hh_rm_base_virq; + +SRCU_NOTIFIER_HEAD_STATIC(hh_rm_notifier); + +static void hh_rm_get_svm_res_work_fn(struct work_struct *work); +static DECLARE_WORK(hh_rm_get_svm_res_work, hh_rm_get_svm_res_work_fn); + +static int hh_rm_populate_hyp_res(void); + +static struct hh_rm_connection *hh_rm_alloc_connection(int seq) +{ + struct hh_rm_connection *connection; + + connection = kzalloc(sizeof(*connection), GFP_KERNEL); + if (!connection) + return ERR_PTR(-ENOMEM); + + init_completion(&connection->seq_done); + + if (seq > 0) { + connection->seq = seq; + return connection; + } + + /* Allocate a new seq number for this connection */ + if (mutex_lock_interruptible(&hh_rm_call_idr_lock)) { + kfree(connection); + return ERR_PTR(-ERESTARTSYS); + } + + connection->seq = idr_alloc_cyclic(&hh_rm_call_idr, connection, + 0, U16_MAX, GFP_KERNEL); + mutex_unlock(&hh_rm_call_idr_lock); + + return connection; +} + +static int +hh_rm_init_connection_buff(struct hh_rm_connection *connection, + void *recv_buff, size_t hdr_size, + size_t payload_size) +{ + struct hh_rm_rpc_hdr *hdr = recv_buff; + + /* Some of the 'reply' types doesn't contain any payload */ + if (!payload_size) + return 0; + + /* If the data is split into multiple fragments, allocate a large + * enough buffer to hold the payloads for all the fragments. + */ + connection->recv_buff = connection->current_recv_buff = + kzalloc((HH_MSGQ_MAX_MSG_SIZE_BYTES - hdr_size) * + (hdr->fragments + 1), + GFP_KERNEL); + if (!connection->recv_buff) + return -ENOMEM; + + memcpy(connection->recv_buff, recv_buff + hdr_size, payload_size); + connection->current_recv_buff += payload_size; + connection->recv_buff_size = payload_size; + connection->num_fragments = hdr->fragments; + + return 0; +} + +int hh_rm_register_notifier(struct notifier_block *nb) +{ + return srcu_notifier_chain_register(&hh_rm_notifier, nb); +} +EXPORT_SYMBOL(hh_rm_register_notifier); + +int hh_rm_unregister_notifier(struct notifier_block *nb) +{ + return srcu_notifier_chain_unregister(&hh_rm_notifier, nb); +} +EXPORT_SYMBOL(hh_rm_unregister_notifier); + +static int hh_rm_process_notif_vm_status(void *recv_buff, size_t recv_buff_size) +{ + struct hh_rm_notif_vm_status_payload *vm_status_payload; + + vm_status_payload = recv_buff + sizeof(struct hh_rm_rpc_hdr); + + /* The VM is now booting. Collect it's info and + * populate to other entities such as MessageQ and DBL + */ + if (vm_status_payload->vm_status == HH_RM_OS_STATUS_BOOT) + return hh_rm_populate_hyp_res(); + + return 0; +} + +static struct hh_rm_connection * +hh_rm_wait_for_notif_fragments(void *recv_buff, size_t recv_buff_size) +{ + struct hh_rm_rpc_hdr *hdr = recv_buff; + struct hh_rm_connection *connection; + size_t payload_size; + int ret = 0; + + connection = hh_rm_alloc_connection(hdr->seq); + if (IS_ERR_OR_NULL(connection)) + return connection; + + payload_size = recv_buff_size - sizeof(*hdr); + + ret = hh_rm_init_connection_buff(connection, recv_buff, + sizeof(*hdr), payload_size); + if (ret < 0) + goto out; + + if (wait_for_completion_interruptible(&connection->seq_done)) { + ret = -ERESTARTSYS; + goto out; + } + + return connection; + +out: + kfree(connection); + return ERR_PTR(ret); +} + +static int hh_rm_process_notif(void *recv_buff, size_t recv_buff_size) +{ + struct hh_rm_connection *connection = NULL; + struct hh_rm_rpc_hdr *hdr = recv_buff; + u32 notification = hdr->msg_id; + void *payload = NULL; + int ret = 0; + + pr_debug("Notification received from RM-VM: %x\n", notification); + + if (recv_buff_size > sizeof(*hdr)) + payload = recv_buff + sizeof(*hdr); + + /* If the notification payload is split-up into + * fragments, wait until all them arrive. + */ + if (hdr->fragments) { + connection = hh_rm_wait_for_notif_fragments(recv_buff, + recv_buff_size); + if (IS_ERR_OR_NULL(connection)) + return PTR_ERR(connection); + + /* In the case of fragments, the complete payload + * is contained under connection->recv_buff + */ + payload = connection->recv_buff; + recv_buff_size = connection->recv_buff_size; + } + + switch (notification) { + case HH_RM_NOTIF_VM_STATUS: + if (recv_buff_size != sizeof(*hdr) + + sizeof(struct hh_rm_notif_vm_status_payload)) { + pr_err("%s: Invalid size for VM_STATUS notif: %u\n", + __func__, recv_buff_size - sizeof(*hdr)); + ret = -EINVAL; + goto err; + } + + ret = hh_rm_process_notif_vm_status(recv_buff, recv_buff_size); + break; + case HH_RM_NOTIF_VM_IRQ_LENT: + if (recv_buff_size != sizeof(*hdr) + + sizeof(struct hh_rm_notif_vm_irq_lent_payload)) { + pr_err("%s: Invalid size for VM_IRQ_LENT notif: %u\n", + __func__, recv_buff_size - sizeof(*hdr)); + ret = -EINVAL; + goto err; + } + break; + case HH_RM_NOTIF_VM_IRQ_RELEASED: + if (recv_buff_size != sizeof(*hdr) + + sizeof(struct hh_rm_notif_vm_irq_released_payload)) { + pr_err("%s: Invalid size for VM_IRQ_REL notif: %u\n", + __func__, recv_buff_size - sizeof(*hdr)); + ret = -EINVAL; + goto err; + } + break; + case HH_RM_NOTIF_MEM_SHARED: + if (recv_buff_size < sizeof(*hdr) + + sizeof(struct hh_rm_notif_mem_shared_payload)) { + pr_err("%s: Invalid size for MEM_SHARED notif: %u\n", + __func__, recv_buff_size - sizeof(*hdr)); + ret = -EINVAL; + goto err; + } + break; + case HH_RM_NOTIF_MEM_RELEASED: + if (recv_buff_size != sizeof(*hdr) + + sizeof(struct hh_rm_notif_mem_released_payload)) { + pr_err("%s: Invalid size for MEM_RELEASED notif: %u\n", + __func__, recv_buff_size - sizeof(*hdr)); + ret = -EINVAL; + goto err; + } + break; + case HH_RM_NOTIF_VM_CONSOLE_CHARS: + if (recv_buff_size < sizeof(*hdr) + + sizeof(struct hh_rm_notif_vm_console_chars)) { + struct hh_rm_notif_vm_console_chars *console_chars; + u16 num_bytes; + + console_chars = recv_buff + sizeof(*hdr); + num_bytes = console_chars->num_bytes; + + if (sizeof(*hdr) + sizeof(*console_chars) + num_bytes != + recv_buff_size) { + pr_err("%s: Invalid size for VM_CONSOLE_CHARS notify %u\n", + __func__, recv_buff_size - sizeof(*hdr)); + ret = -EINVAL; + goto err; + } + } + break; + default: + pr_err("%s: Unknown notification received: %u\n", __func__, + notification); + ret = -EINVAL; + goto err; + } + + srcu_notifier_call_chain(&hh_rm_notifier, notification, payload); + +err: + if (connection) { + kfree(connection->recv_buff); + kfree(connection); + } + + return ret; +} + +static int hh_rm_process_rply(void *recv_buff, size_t recv_buff_size) +{ + struct hh_rm_rpc_reply_hdr *reply_hdr = recv_buff; + struct hh_rm_rpc_hdr *hdr = recv_buff; + struct hh_rm_connection *connection; + size_t payload_size; + int ret = 0; + + if (mutex_lock_interruptible(&hh_rm_call_idr_lock)) { + ret = -ERESTARTSYS; + goto out; + } + + connection = idr_find(&hh_rm_call_idr, hdr->seq); + mutex_unlock(&hh_rm_call_idr_lock); + + if (!connection || connection->seq != hdr->seq) { + pr_err("%s: Failed to get the connection info for seq: %d\n", + __func__, hdr->seq); + ret = -EINVAL; + goto out; + } + + payload_size = recv_buff_size - sizeof(*reply_hdr); + + ret = hh_rm_init_connection_buff(connection, recv_buff, + sizeof(*reply_hdr), payload_size); + if (ret < 0) + return ret; + + connection->reply_err_code = reply_hdr->err_code; + + /* + * If the data is composed of a single message, wakeup the + * receiver immediately. + * + * Else, if the data is split into multiple fragments, fill + * this buffer as and when the fragments arrive, and finally + * wakeup the receiver upon reception of the last fragment. + */ + if (!hdr->fragments) + complete(&connection->seq_done); +out: + return ret; +} + +static int hh_rm_process_cont(void *recv_buff, size_t recv_buff_size) +{ + struct hh_rm_rpc_hdr *hdr = recv_buff; + struct hh_rm_connection *connection; + size_t payload_size; + + if (mutex_lock_interruptible(&hh_rm_call_idr_lock)) + return -ERESTARTSYS; + + connection = idr_find(&hh_rm_call_idr, hdr->seq); + mutex_unlock(&hh_rm_call_idr_lock); + + /* The seq number should be the same for all the fragments */ + if (!connection || connection->seq != hdr->seq) { + pr_err("%s: Failed to get the connection info for seq: %d\n", + __func__, hdr->seq); + return -EINVAL; + } + + /* + * hdr->fragments preserves the value from the first 'reply/notif' + * message. For the sake of sanity, check if it's still intact. + */ + if (connection->num_fragments != hdr->fragments) { + pr_err("%s: Number of fragments mismatch for seq: %d\n", + __func__, hdr->seq); + return -EINVAL; + } + + payload_size = recv_buff_size - sizeof(*hdr); + + /* Keep appending the data to the previous fragment's end */ + memcpy(connection->current_recv_buff, + recv_buff + sizeof(*hdr), payload_size); + connection->current_recv_buff += payload_size; + connection->recv_buff_size += payload_size; + + connection->fragments_received++; + if (connection->fragments_received == connection->num_fragments) + complete(&connection->seq_done); + + return 0; +} + +struct hh_rm_msgq_data { + void *recv_buff; + size_t recv_buff_size; + struct work_struct recv_work; +}; + +static void hh_rm_process_recv_work(struct work_struct *work) +{ + struct hh_rm_msgq_data *msgq_data; + struct hh_rm_rpc_hdr *hdr; + void *recv_buff; + + msgq_data = container_of(work, struct hh_rm_msgq_data, recv_work); + recv_buff = hdr = msgq_data->recv_buff; + + switch (hdr->type) { + case HH_RM_RPC_TYPE_NOTIF: + hh_rm_process_notif(recv_buff, msgq_data->recv_buff_size); + break; + case HH_RM_RPC_TYPE_RPLY: + hh_rm_process_rply(recv_buff, msgq_data->recv_buff_size); + break; + case HH_RM_RPC_TYPE_CONT: + hh_rm_process_cont(recv_buff, msgq_data->recv_buff_size); + break; + default: + pr_err("%s: Invalid message type (%d) received\n", + __func__, hdr->type); + } + + /* All the processing functions would have trimmed-off the header + * and copied the data to connection->recv_buff. Hence, it's okay + * to release the original packet that arrived. + */ + kfree(recv_buff); +} + +static int hh_rm_recv_task_fn(void *data) +{ + struct hh_rm_msgq_data *msgq_data; + size_t recv_buff_size; + void *recv_buff; + int ret; + + while (!kthread_should_stop()) { + /* Block until a new message is received */ + ret = hh_msgq_recv(hh_rm_msgq_desc, &recv_buff, + &recv_buff_size, 0); + if (ret < 0) { + pr_err("%s: Failed to receive the message: %d\n", + __func__, ret); + continue; + } else if (recv_buff_size <= sizeof(struct hh_rm_rpc_hdr)) { + pr_err("%s: Invalid message size received\n", __func__); + kfree(recv_buff); + continue; + } + + /* Process this message in the background, while leaving + * this thread to receive the next message. + */ + msgq_data = kzalloc(sizeof(*msgq_data), GFP_KERNEL); + if (!msgq_data) { + kfree(recv_buff); + continue; + } + + msgq_data->recv_buff = recv_buff; + msgq_data->recv_buff_size = recv_buff_size; + INIT_WORK(&msgq_data->recv_work, hh_rm_process_recv_work); + + schedule_work(&msgq_data->recv_work); + } + + return 0; +} + +static int hh_rm_send_request(u32 message_id, + const void *req_buff, size_t req_buff_size, + struct hh_rm_connection *connection) +{ + size_t buff_size_remaining = req_buff_size; + const void *req_buff_curr = req_buff; + struct hh_rm_rpc_hdr *hdr; + unsigned long tx_flags; + u8 num_fragments = 0; + size_t payload_size; + void *send_buff; + int i, ret; + + num_fragments = (req_buff_size + HH_RM_MAX_MSG_SIZE_BYTES - 1) / + HH_RM_MAX_MSG_SIZE_BYTES; + + /* The above calculation also includes the count + * for the 'request' packet. Exclude it as the + * header needs to fill the num. of fragments to follow. + */ + num_fragments--; + + if (num_fragments > HH_RM_MAX_NUM_FRAGMENTS) { + pr_err("%s: Limit exceeded for the number of fragments: %u\n", + __func__, num_fragments); + return -E2BIG; + } + + /* Consider also the 'request' packet for the loop count */ + for (i = 0; i <= num_fragments; i++) { + if (buff_size_remaining > HH_RM_MAX_MSG_SIZE_BYTES) { + payload_size = HH_RM_MAX_MSG_SIZE_BYTES; + buff_size_remaining -= payload_size; + } else { + payload_size = buff_size_remaining; + } + + send_buff = kzalloc(sizeof(*hdr) + payload_size, GFP_KERNEL); + if (!send_buff) + return -ENOMEM; + + hdr = send_buff; + hdr->version = HH_RM_RPC_HDR_VERSION_ONE; + hdr->hdr_words = HH_RM_RPC_HDR_WORDS; + hdr->type = i == 0 ? HH_RM_RPC_TYPE_REQ : HH_RM_RPC_TYPE_CONT; + hdr->fragments = num_fragments; + hdr->seq = connection->seq; + hdr->msg_id = message_id; + + memcpy(send_buff + sizeof(*hdr), req_buff_curr, payload_size); + req_buff_curr += payload_size; + + /* Force the last fragment (or the request type) + * to be sent immediately to the receiver + */ + tx_flags = (i == num_fragments) ? HH_MSGQ_TX_PUSH : 0; + + ret = hh_msgq_send(hh_rm_msgq_desc, send_buff, + sizeof(*hdr) + payload_size, tx_flags); + if (ret) { + kfree(send_buff); + return ret; + } + } + + return 0; +} + +/** + * hh_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_buff_size: Size of the response buffer + * @reply_err_code: Returns Haven standard error code for the response + * + * Make a request to the RM-VM and expect a reply back. For a successful + * response, the function returns the payload and its size for the response. + * Some of the reply types doesn't contain any payload, in which case, the + * caller would see a NULL returned. Hence, it's recommended that the caller + * first read the error code and then dereference the returned payload + * (if applicable). Also, the caller should kfree the returned pointer + * when done. + */ +void *hh_rm_call(hh_rm_msgid_t message_id, + void *req_buff, size_t req_buff_size, + size_t *resp_buff_size, int *reply_err_code) +{ + struct hh_rm_connection *connection; + int req_ret; + void *ret; + + if (!message_id || !req_buff || !resp_buff_size || !reply_err_code) + return ERR_PTR(-EINVAL); + + connection = hh_rm_alloc_connection(-1); + if (IS_ERR_OR_NULL(connection)) + return connection; + + /* Send the request to the Resource Manager VM */ + req_ret = hh_rm_send_request(message_id, + req_buff, req_buff_size, + connection); + if (req_ret < 0) { + ret = ERR_PTR(req_ret); + goto out; + } + + /* Wait for response */ + if (wait_for_completion_interruptible(&connection->seq_done)) { + ret = ERR_PTR(-ERESTARTSYS); + goto out; + } + + *reply_err_code = connection->reply_err_code; + if (connection->reply_err_code) { + pr_err("%s: Reply for seq:%d failed with err: %d\n", + __func__, connection->seq, connection->reply_err_code); + ret = ERR_PTR(hh_remap_error(connection->reply_err_code)); + goto out; + } + + ret = connection->recv_buff; + *resp_buff_size = connection->recv_buff_size; + +out: + kfree(connection); + return ret; +} + +static int hh_rm_virq_to_linux_irq(u32 virq, u32 type, u32 trigger) +{ + struct irq_fwspec fwspec = {}; + + fwspec.fwnode = of_node_to_fwnode(hh_rm_intc); + fwspec.param_count = 3; + fwspec.param[0] = type; + fwspec.param[1] = virq; + fwspec.param[2] = trigger; + + return irq_create_fwspec_mapping(&fwspec); +} + +static int hh_rm_get_irq(struct hh_vm_get_hyp_res_resp_entry *res_entry) +{ + int ret, virq = res_entry->virq; + + /* For resources, such as DBL source, there's no IRQ. The virq_handle + * wouldn't be defined for such cases. Hence ignore such cases + */ + if (!res_entry->virq_handle) + return 0; + + /* Allocate and bind a new IRQ if RM-VM hasn't already done already */ + if (virq == HH_RM_NO_IRQ_ALLOC) { + /* Get the next free vIRQ. + * Subtract 32 from the base virq to get the base SPI. + */ + ret = virq = ida_alloc_range(&hh_rm_free_virq_ida, + hh_rm_base_virq - 32, + GIC_V3_SPI_MAX, GFP_KERNEL); + if (ret < 0) + return ret; + + /* Bind the vIRQ */ + ret = hh_rm_vm_irq_accept(res_entry->virq_handle, virq); + if (ret < 0) + goto err; + } + + return hh_rm_virq_to_linux_irq(virq, GIC_SPI, IRQ_TYPE_LEVEL_HIGH); + +err: + ida_free(&hh_rm_free_virq_ida, virq); + return ret; +} + +static int hh_rm_populate_hyp_res(void) +{ + struct hh_vm_get_hyp_res_resp_entry *res_entries = NULL; + int linux_irq, ret = 0; + hh_capid_t cap_id; + hh_label_t label; + u32 n_res, i; + + res_entries = hh_rm_vm_get_hyp_res(0, &n_res); + if (IS_ERR_OR_NULL(res_entries)) + return PTR_ERR(res_entries); + + for (i = 0; i < n_res; i++) { + ret = linux_irq = hh_rm_get_irq(&res_entries[i]); + if (ret < 0) + goto out; + + cap_id = (u64) res_entries[i].cap_id_high << 32 | + res_entries[i].cap_id_low; + label = res_entries[i].resource_label; + + /* Populate MessageQ & DBL's cap tables */ + /* TODO: Handle DBL */ + switch (res_entries[i].res_type) { + case HH_RM_RES_TYPE_MQ_TX: + ret = hh_msgq_populate_cap_info(label, cap_id, + HH_MSGQ_DIRECTION_TX, linux_irq); + break; + case HH_RM_RES_TYPE_MQ_RX: + ret = hh_msgq_populate_cap_info(label, cap_id, + HH_MSGQ_DIRECTION_RX, linux_irq); + break; + case HH_RM_RES_TYPE_DB_TX: + break; + case HH_RM_RES_TYPE_DB_RX: + break; + default: + pr_err("%s: Unknown resource type: %u\n", + __func__, res_entries[i].res_type); + ret = -EINVAL; + } + + if (ret < 0) + goto out; + } + +out: + kfree(res_entries); + return ret; +} + +static void hh_rm_get_svm_res_work_fn(struct work_struct *work) +{ + hh_rm_populate_hyp_res(); +} + +static const struct of_device_id hh_rm_drv_of_match[] = { + { .compatible = "qcom,haven-resource-manager-1-0" }, + { } +}; + +static int hh_rm_drv_probe(struct platform_device *pdev) +{ + struct resource *rm_tx_res, *rm_rx_res; + int tx_irq, rx_irq; + u32 owner_vmid; + int ret; + + rm_tx_res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "rm-tx-cap-id"); + if (!rm_tx_res) { + dev_err(&pdev->dev, "Failed to get the Tx cap-id\n"); + return -EINVAL; + } + + rm_rx_res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "rm-rx-cap-id"); + if (!rm_rx_res) { + dev_err(&pdev->dev, "Failed to get the Rx cap-id\n"); + return -EINVAL; + } + + tx_irq = platform_get_irq_byname(pdev, "rm-tx-irq"); + if (tx_irq < 0) { + dev_err(&pdev->dev, "Failed to get the Tx IRQ. ret: %d\n", + tx_irq); + return tx_irq; + } + + rx_irq = platform_get_irq_byname(pdev, "rm-rx-irq"); + if (rx_irq < 0) { + dev_err(&pdev->dev, "Failed to get the Rx IRQ. ret: %d\n", + rx_irq); + return rx_irq; + } + + if (of_property_read_u32(pdev->dev.of_node, "qcom,virq-base", + &hh_rm_base_virq)) { + dev_err(&pdev->dev, "Failed to get the vIRQ base\n"); + return -ENXIO; + } + + hh_rm_intc = of_irq_find_parent(pdev->dev.of_node); + if (!hh_rm_intc) { + dev_err(&pdev->dev, "Failed to get the IRQ parent node\n"); + return -ENXIO; + } + + ret = hh_msgq_populate_cap_info(HH_MSGQ_LABEL_RM, rm_tx_res->start, + HH_MSGQ_DIRECTION_TX, tx_irq); + if (ret) + return ret; + + ret = hh_msgq_populate_cap_info(HH_MSGQ_LABEL_RM, rm_rx_res->start, + HH_MSGQ_DIRECTION_RX, rx_irq); + if (ret) + return ret; + + hh_rm_msgq_desc = hh_msgq_register(HH_MSGQ_LABEL_RM); + if (IS_ERR_OR_NULL(hh_rm_msgq_desc)) + return PTR_ERR(hh_rm_msgq_desc); + + /* As we don't have a callback for message reception yet, + * spawn a kthread and always listen to incoming messages. + */ + hh_rm_drv_recv_task = kthread_run(hh_rm_recv_task_fn, + NULL, "hh_rm_recv_task"); + if (IS_ERR_OR_NULL(hh_rm_drv_recv_task)) { + ret = PTR_ERR(hh_rm_drv_recv_task); + goto err_recv_task; + } + + /* If the node has a "qcom,owner-vmid" property, it means that it's + * a secondary-VM. Gain the info about it's resources here as there + * won't be any explicit notification from the primary-VM. + */ + if (!of_property_read_u32(pdev->dev.of_node, "qcom,owner-vmid", + &owner_vmid)) { + schedule_work(&hh_rm_get_svm_res_work); + } + + return 0; + +err_recv_task: + hh_msgq_unregister(hh_rm_msgq_desc); + return ret; +} + +static int hh_rm_drv_remove(struct platform_device *pdev) +{ + kthread_stop(hh_rm_drv_recv_task); + hh_msgq_unregister(hh_rm_msgq_desc); + idr_destroy(&hh_rm_call_idr); + + return 0; +} + +static struct platform_driver hh_rm_driver = { + .probe = hh_rm_drv_probe, + .remove = hh_rm_drv_remove, + .driver = { + .name = "hh_rm_driver", + .of_match_table = hh_rm_drv_of_match, + }, +}; + +module_platform_driver(hh_rm_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Qualcomm Technologies, Inc. Haven Resource Mgr. Driver"); diff --git a/drivers/virt/haven/hh_rm_drv_private.h b/drivers/virt/haven/hh_rm_drv_private.h new file mode 100644 index 000000000000..2c830c23a1a9 --- /dev/null +++ b/drivers/virt/haven/hh_rm_drv_private.h @@ -0,0 +1,139 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ + +#ifndef __HH_RM_DRV_PRIVATE_H +#define __HH_RM_DRV_PRIVATE_H + +#include + +#include +#include + +/* Resource Manager Header */ +struct hh_rm_rpc_hdr { + u8 version:4, + hdr_words:4; + u8 type:2, + fragments:6; + u16 seq; + u32 msg_id; +} __packed; + +/* Standard reply header */ +struct hh_rm_rpc_reply_hdr { + struct hh_rm_rpc_hdr rpc_hdr; + u32 err_code; +} __packed; + +/* RPC Header versions */ +#define HH_RM_RPC_HDR_VERSION_ONE 0x1 + +/* RPC Header words */ +#define HH_RM_RPC_HDR_WORDS 0x2 + +/* RPC Message types */ +#define HH_RM_RPC_TYPE_CONT 0x0 +#define HH_RM_RPC_TYPE_REQ 0x1 +#define HH_RM_RPC_TYPE_RPLY 0x2 +#define HH_RM_RPC_TYPE_NOTIF 0x3 + +/* RPC Message IDs */ +/* Call type Message IDs that has a request/reply pattern */ +/* Message IDs: Informative */ +#define HH_RM_RPC_MSG_ID_CALL_GET_IDENT 0x00000001 +#define HH_RM_RPC_MSG_ID_CALL_GET_FEATURES 0x00000002 + +/* Message IDs: Memory management */ +#define HH_RM_RPC_MSG_ID_CALL_MEM_ACCEPT 0x51000011 +#define HH_RM_RPC_MSG_ID_CALL_MEM_LEND 0x51000012 +#define HH_RM_RPC_MSG_ID_CALL_MEM_SHARE 0x51000013 +#define HH_RM_RPC_MSG_ID_CALL_MEM_RELEASE 0x51000014 +#define HH_RM_RPC_MSG_ID_CALL_MEM_RECLAIM 0x51000015 +#define HH_RM_RPC_MSG_ID_CALL_MEM_NOTIFY 0x51000017 + +/* Message IDs: extensions for hyp-assign */ +#define HH_RM_RPC_MSG_ID_CALL_MEM_QCOM_LOOKUP_SGL 0x5100001A + +/* Message IDs: VM Management */ +#define HH_RM_RPC_MSG_ID_CALL_VM_ALLOCATE 0x56000001 +#define HH_RM_RPC_MSG_ID_CALL_VM_START 0x56000004 + +/* Message IDs: VM Query */ +#define HH_RM_RPC_MSG_ID_CALL_VM_GET_STATE 0x56000017 +#define HH_RM_RPC_MSG_ID_CALL_VM_GET_HYP_RESOURCES 0x56000020 +#define HH_RM_RPC_MSG_ID_CALL_VM_LOOKUP_HYP_CAPIDS 0x56000021 +#define HH_RM_RPC_MSG_ID_CALL_VM_LOOKUP_HYP_IRQS 0X56000022 + +/* Message IDs: VM Configuration */ +#define HH_RM_RPC_MSG_ID_CALL_VM_IRQ_ACCEPT 0x56000050 +#define HH_RM_RPC_MSG_ID_CALL_VM_IRQ_LEND 0x56000051 +#define HH_RM_RPC_MSG_ID_CALL_VM_IRQ_RELEASE 0x56000052 +#define HH_RM_RPC_MSG_ID_CALL_VM_IRQ_RECLAIM 0x56000053 +#define HH_RM_RPC_MSG_ID_CALL_VM_IRQ_NOTIFY 0x56000054 +#define HH_RM_RPC_MSG_ID_CALL_VM_IRQ_UNMAP 0x56000055 + +/* Message IDs: VM Services */ +#define HH_RM_RPC_MSG_ID_CALL_VM_SET_STATUS 0x56000080 +#define HH_RM_RPC_MSG_ID_CALL_VM_CONSOLE_OPEN 0x56000081 +#define HH_RM_RPC_MSG_ID_CALL_VM_CONSOLE_CLOSE 0x56000082 +#define HH_RM_RPC_MSG_ID_CALL_VM_CONSOLE_WRITE 0x56000083 +#define HH_RM_RPC_MSG_ID_CALL_VM_CONSOLE_FLUSH 0x56000084 + +/* Message IDs: VM-Host Query */ +#define HH_RM_RPC_MSG_ID_CALL_VM_HOST_GET_TYPE 0x560000A0 + +/* End Call type Message IDs */ +/* End RPC Message IDs */ + +/* Message ID headers */ +/* Call: VM_GET_HYP_RESOURCES */ +#define HH_RM_RES_TYPE_DB_TX 0 +#define HH_RM_RES_TYPE_DB_RX 1 +#define HH_RM_RES_TYPE_MQ_TX 2 +#define HH_RM_RES_TYPE_MQ_RX 3 + +struct hh_vm_get_hyp_res_req_payload { + u16 vmid; + u16 reserved; +} __packed; + +struct hh_vm_get_hyp_res_resp_entry { + u8 res_type; + u8 reserved; + u16 partner_vmid; + u32 resource_handle; + u32 resource_label; + u32 cap_id_low; + u32 cap_id_high; + u32 virq_handle; + u32 virq; +} __packed; + +struct hh_vm_get_hyp_res_resp_payload { + u32 n_resource_entries; + struct hh_vm_get_hyp_res_resp_entry resp_entries[]; +} __packed; + +/* Call: VM_IRQ_ACCEPT */ +struct hh_vm_irq_accept_req_payload { + hh_virq_handle_t virq_handle; + s32 virq; +} __packed; + +struct hh_vm_irq_accept_resp_payload { + s32 virq; +} __packed; + +/* End Message ID headers */ + +/* Common function declerations */ +void *hh_rm_call(hh_rm_msgid_t message_id, + void *req_buff, size_t req_buff_size, + size_t *resp_buff_size, int *reply_err_code); +struct hh_vm_get_hyp_res_resp_entry * +hh_rm_vm_get_hyp_res(hh_vmid_t vmid, u32 *out_n_entries); +int hh_msgq_populate_cap_info(enum hh_msgq_label label, u64 cap_id, + int direction, int irq); +#endif /* __HH_RM_DRV_PRIVATE_H */ diff --git a/drivers/virt/haven/hh_rm_iface.c b/drivers/virt/haven/hh_rm_iface.c new file mode 100644 index 000000000000..a18747957fcd --- /dev/null +++ b/drivers/virt/haven/hh_rm_iface.c @@ -0,0 +1,127 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + * + */ + +#include +#include + +#include +#include + +#include "hh_rm_drv_private.h" + +/** + * hh_rm_vm_get_hyp_res: Get info about a series of resources for this VM + * @vmid: vmid whose info is needed. Pass 0 for self + * @n_entries: The number of the resource entries that's returned to the caller + * + * The function returns an array of type 'struct hh_vm_get_hyp_res_resp_entry', + * in which each entry specifies info about a particular resource. The number + * of entries in the array is returned by 'n_entries'. The caller must kfree + * the returned pointer when done. + * + * The function encodes the error codes via ERR_PTR. Hence, the caller is + * responsible to check it with IS_ERR_OR_NULL(). + */ +struct hh_vm_get_hyp_res_resp_entry * +hh_rm_vm_get_hyp_res(hh_vmid_t vmid, u32 *n_entries) +{ + struct hh_vm_get_hyp_res_resp_payload *resp_payload; + struct hh_vm_get_hyp_res_req_payload req_payload = { + .vmid = vmid + }; + struct hh_vm_get_hyp_res_resp_entry *resp_entries; + size_t resp_payload_size, resp_entries_size; + int err, reply_err_code; + + if (!n_entries) + return ERR_PTR(-EINVAL); + + resp_payload = hh_rm_call(HH_RM_RPC_MSG_ID_CALL_VM_GET_HYP_RESOURCES, + &req_payload, sizeof(req_payload), + &resp_payload_size, &reply_err_code); + if (reply_err_code || IS_ERR_OR_NULL(resp_payload)) { + err = PTR_ERR(resp_payload); + pr_err("%s: GET_HYP_RESOURCES failed with err: %d\n", + __func__, err); + return ERR_PTR(err); + } + + /* The response payload should contain all the resource entries */ + if (resp_payload_size < sizeof(*n_entries) || + resp_payload_size != sizeof(*n_entries) + + (resp_payload->n_resource_entries * sizeof(*resp_entries))) { + pr_err("%s: Invalid size received for GET_HYP_RESOURCES: %u\n", + __func__, resp_payload_size); + resp_entries = ERR_PTR(-EINVAL); + goto out; + } + + resp_entries_size = sizeof(*resp_entries) * + resp_payload->n_resource_entries; + resp_entries = kmemdup(resp_payload->resp_entries, resp_entries_size, + GFP_KERNEL); + if (!resp_entries) { + resp_entries = ERR_PTR(-ENOMEM); + goto out; + } + + *n_entries = resp_payload->n_resource_entries; + +out: + kfree(resp_payload); + return resp_entries; +} + +/** + * hh_rm_vm_irq_accept: Bind the virq number to the supplied virq_handle + * @virq_handle: The virtual IRQ handle (for example, obtained via + * call to hh_rm_get_hyp_resources()) + * @virq: The virtual IRQ number to bind to. Note that this is the virtual + * GIC IRQ number and not the linux IRQ number. Pass -1 here if the + * caller wants the Resource Manager VM to allocate a number + * + * If provided -1 for virq, the function returns the new IRQ number, else + * the one that was already provided. + * + * The function encodes the error codes via ERR_PTR. Hence, the caller is + * responsible to check it with IS_ERR_OR_NULL(). + */ +int hh_rm_vm_irq_accept(hh_virq_handle_t virq_handle, int virq) +{ + struct hh_vm_irq_accept_resp_payload *resp_payload; + struct hh_vm_irq_accept_req_payload req_payload; + size_t resp_payload_size; + int ret, reply_err_code; + + /* -1 is valid for virq if requesting for a new number */ + if (virq < -1) + return -EINVAL; + + req_payload.virq_handle = virq_handle; + req_payload.virq = virq; + + resp_payload = hh_rm_call(HH_RM_RPC_MSG_ID_CALL_VM_IRQ_ACCEPT, + &req_payload, sizeof(req_payload), + &resp_payload_size, &reply_err_code); + if (reply_err_code || IS_ERR_OR_NULL(resp_payload)) { + ret = PTR_ERR(resp_payload); + pr_err("%s: VM_IRQ_ACCEPT failed with err: %d\n", + __func__, ret); + return ret; + } + + if (virq == -1 && resp_payload_size != sizeof(*resp_payload)) { + pr_err("%s: Invalid size received for VM_IRQ_ACCEPT: %u\n", + __func__, resp_payload_size); + ret = -EINVAL; + goto out; + } + + ret = virq == -1 ? resp_payload->virq : virq; +out: + kfree(resp_payload); + return ret; +} 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 diff --git a/include/linux/haven/hh_rm_drv.h b/include/linux/haven/hh_rm_drv.h new file mode 100644 index 000000000000..57a4a20b7351 --- /dev/null +++ b/include/linux/haven/hh_rm_drv.h @@ -0,0 +1,119 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + * + */ + +#ifndef __HH_RM_DRV_H +#define __HH_RM_DRV_H + +#include + +#include "hh_common.h" + +/* Notification type Message IDs */ +/* Memory APIs */ +#define HH_RM_NOTIF_MEM_SHARED 0x51100011 +#define HH_RM_NOTIF_MEM_RELEASED 0x51100012 + +#define HH_RM_MEM_TYPE_NORMAL 0 +#define HH_RM_MEM_TYPE_IO 1 + +#define HH_RM_TRANS_TYPE_DONATE 0 +#define HH_RM_TRANS_TYPE_LEND 1 +#define HH_RM_TRANS_TYPE_SHARE 2 + +#define HH_RM_ACL_X BIT(0) +#define HH_RM_ACL_W BIT(1) +#define HH_RM_ACL_R BIT(2) + +struct hh_rm_mem_shared_acl_entry; +struct hh_rm_mem_shared_sgl_entry; +struct hh_rm_mem_shared_attr_entry; + +struct hh_rm_notif_mem_shared_payload { + u32 mem_handle; + u8 mem_type; + u8 trans_type; + u8 flags; + u8 reserved1; + u16 owner_vmid; + u16 reserved2; + u32 label; + /* TODO: How to arrange multiple variable length struct arrays? */ +} __packed; + +struct hh_rm_mem_shared_acl_entry { + u16 acl_vmid; + u8 acl_rights; + u8 reserved; +} __packed; + +struct hh_rm_mem_shared_sgl_entry { + u32 sgl_size_low; + u32 sgl_size_high; +} __packed; + +struct hh_rm_mem_shared_attr_entry { + u16 attributes; + u16 attributes_vmid; +} __packed; + +struct hh_rm_notif_mem_released_payload { + u32 mem_handle; + u16 participant_vmid; + u16 reserved; +} __packed; + +/* VM APIs */ +#define HH_RM_NOTIF_VM_STATUS 0x56100008 +#define HH_RM_NOTIF_VM_IRQ_LENT 0x56100011 +#define HH_RM_NOTIF_VM_IRQ_RELEASED 0x56100012 + +#define HH_RM_VM_STATUS_NO_STATE 0 +#define HH_RM_VM_STATUS_RUNNING 1 +#define HH_RM_VM_STATUS_PAUSED 2 +#define HH_RM_VM_STATUS_SHUTDOWN 3 +#define HH_RM_VM_STATUS_SHUTOFF 4 +#define HH_RM_VM_STATUS_CRASHED 5 + +#define HH_RM_OS_STATUS_NONE 0 +#define HH_RM_OS_STATUS_EARLY_BOOT 1 +#define HH_RM_OS_STATUS_BOOT 2 +#define HH_RM_OS_STATUS_INIT 3 +#define HH_RM_OS_STATUS_RUN 4 + +struct hh_rm_notif_vm_status_payload { + u16 vmid; + u16 reserved; + u8 vm_status; + u8 os_status; + u16 app_status; +} __packed; + +struct hh_rm_notif_vm_irq_lent_payload { + u16 owner_vmid; + u32 virq_handle; + u32 virq_label; +} __packed; + +struct hh_rm_notif_vm_irq_released_payload { + u32 virq_handle; +} __packed; + +/* VM Services */ +#define HH_RM_NOTIF_VM_CONSOLE_CHARS 0X56100080 + +struct hh_rm_notif_vm_console_chars { + u16 vmid; + u16 num_bytes; + u8 bytes[0]; +} __packed; + +/* End Notification type APIs */ + +int hh_rm_register_notifier(struct notifier_block *nb); +int hh_rm_unregister_notifier(struct notifier_block *nb); +int hh_rm_vm_irq_accept(hh_virq_handle_t virq_handle, int virq); + +#endif