From 7af086a1d397fcd9d2580bae8a267b97fedabe47 Mon Sep 17 00:00:00 2001 From: Raghavendra Rao Ananta Date: Thu, 2 Jan 2020 15:33:54 -0800 Subject: [PATCH] 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