From 7af086a1d397fcd9d2580bae8a267b97fedabe47 Mon Sep 17 00:00:00 2001 From: Raghavendra Rao Ananta Date: Thu, 2 Jan 2020 15:33:54 -0800 Subject: [PATCH 1/4] haven: Add Haven Resource Manager Driver Add Haven Resource Manager driver to talk to the Resource Manager Virtual Machine for using the services such as resource (IRQ/device) sharing, obtaining info about IPC/signalling mechanisms, getting notifications about other VM's health, and so on. Change-Id: Ibff0fa3a2fef12276c1e9d7037b8bec3eb2efab1 Signed-off-by: Raghavendra Rao Ananta Signed-off-by: Elliot Berman --- drivers/virt/haven/Kconfig | 13 + drivers/virt/haven/Makefile | 2 + drivers/virt/haven/hh_rm_core.c | 807 +++++++++++++++++++++++++ drivers/virt/haven/hh_rm_drv_private.h | 139 +++++ drivers/virt/haven/hh_rm_iface.c | 127 ++++ include/linux/haven/hh_rm_drv.h | 119 ++++ 6 files changed, 1207 insertions(+) create mode 100644 drivers/virt/haven/hh_rm_core.c create mode 100644 drivers/virt/haven/hh_rm_drv_private.h create mode 100644 drivers/virt/haven/hh_rm_iface.c create mode 100644 include/linux/haven/hh_rm_drv.h diff --git a/drivers/virt/haven/Kconfig b/drivers/virt/haven/Kconfig index 9dc44f3d1037..37a2eeeb9c31 100644 --- a/drivers/virt/haven/Kconfig +++ b/drivers/virt/haven/Kconfig @@ -24,4 +24,17 @@ 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. + endif diff --git a/drivers/virt/haven/Makefile b/drivers/virt/haven/Makefile index 68ab1b7f0809..fe0ae6120f19 100644 --- a/drivers/virt/haven/Makefile +++ b/drivers/virt/haven/Makefile @@ -1,2 +1,4 @@ # 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 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_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 From 165cd8f74b2747cb5ef3de7d8a67cad6d31348c3 Mon Sep 17 00:00:00 2001 From: Raghavendra Rao Ananta Date: Fri, 10 Jan 2020 16:24:50 -0800 Subject: [PATCH 2/4] arm64: defconfig: Enable Haven virtualization drivers Enable the virtualization drivers, such as Resource Manager driver, and Message Queue driver for Lahaina platform. Change-Id: I860b64a11c22590d5abd0bea87913e839a42393e Signed-off-by: Raghavendra Rao Ananta Signed-off-by: Elliot Berman --- arch/arm64/configs/vendor/lahaina_GKI.config | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/arch/arm64/configs/vendor/lahaina_GKI.config b/arch/arm64/configs/vendor/lahaina_GKI.config index e4663e17efb6..f5fb5e7e2850 100644 --- a/arch/arm64/configs/vendor/lahaina_GKI.config +++ b/arch/arm64/configs/vendor/lahaina_GKI.config @@ -118,6 +118,10 @@ 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_CFI_PERMISSIVE=y # CONFIG_SND_SOC_WCD9335 is not set # CONFIG_SLIM_QCOM_CTRL is not set From fbd0842e666f5f5c809eb85377265a525435e338 Mon Sep 17 00:00:00 2001 From: Murali Nalajala Date: Mon, 13 Jan 2020 22:19:20 -0800 Subject: [PATCH 3/4] haven: Add haven doorbell driver In haven one way to provide inter VMs(Virtual Machines) communication is through the use of doorbell interrupts. 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. Change-Id: I6d797d78cddc96a23680554e2b3fc1031446cbe5 Signed-off-by: Murali Nalajala Signed-off-by: Elliot Berman --- drivers/virt/haven/Kconfig | 10 + drivers/virt/haven/Makefile | 1 + drivers/virt/haven/hh_dbl.c | 621 +++++++++++++++++++++++++++++++++++ include/linux/haven/hh_dbl.h | 34 ++ 4 files changed, 666 insertions(+) create mode 100644 drivers/virt/haven/hh_dbl.c create mode 100644 include/linux/haven/hh_dbl.h 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 From f57d0c27d3ca4f1e4f36109b5cc122499f14dfea Mon Sep 17 00:00:00 2001 From: Elliot Berman Date: Mon, 3 Feb 2020 14:04:04 -0800 Subject: [PATCH 4/4] arm64: defconfig: Enable haven doorbell driver Enable doorbell driver for VMs to communicate each other over an IPC Change-Id: Id72d186ada0a81bcf7b6706834c2110518983d71 Signed-off-by: Murali Nalajala Signed-off-by: Elliot Berman --- arch/arm64/configs/vendor/lahaina_GKI.config | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm64/configs/vendor/lahaina_GKI.config b/arch/arm64/configs/vendor/lahaina_GKI.config index f5fb5e7e2850..c1fac404b3ee 100644 --- a/arch/arm64/configs/vendor/lahaina_GKI.config +++ b/arch/arm64/configs/vendor/lahaina_GKI.config @@ -122,6 +122,7 @@ 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