diff --git a/drivers/virt/gunyah/Kconfig b/drivers/virt/gunyah/Kconfig index d5f5decdd144..e8ac775665e4 100644 --- a/drivers/virt/gunyah/Kconfig +++ b/drivers/virt/gunyah/Kconfig @@ -44,4 +44,17 @@ config GH_MSGQ the services offered by the drivers is simply to send and receive messages in a blocking manner. +config GH_RM_DRV + tristate "Gunyah Resource Manager driver" + help + The Gunyah 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/gunyah/Makefile b/drivers/virt/gunyah/Makefile index df16ec8758b2..2e7548bf8948 100644 --- a/drivers/virt/gunyah/Makefile +++ b/drivers/virt/gunyah/Makefile @@ -2,3 +2,5 @@ obj-$(CONFIG_GH_VIRT_WATCHDOG)+= gh_virt_wdt.o obj-$(CONFIG_GH_DBL) += gh_dbl.o obj-$(CONFIG_GH_MSGQ) += gh_msgq.o +obj-$(CONFIG_GH_RM_DRV) += gh_rm_drv.o +gh_rm_drv-y += gh_rm_core.o gh_rm_iface.o diff --git a/drivers/virt/gunyah/gh_rm_core.c b/drivers/virt/gunyah/gh_rm_core.c new file mode 100644 index 000000000000..3b878293e875 --- /dev/null +++ b/drivers/virt/gunyah/gh_rm_core.c @@ -0,0 +1,1616 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2020-2021, The Linux Foundation. All rights reserved. + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "gh_rm_drv_private.h" + +#define GH_RM_MAX_NUM_FRAGMENTS 62 + +#define GH_RM_NO_IRQ_ALLOC -1 + +#define GH_RM_MAX_MSG_SIZE_BYTES \ + (GH_MSGQ_MAX_MSG_SIZE_BYTES - sizeof(struct gh_rm_rpc_hdr)) + +struct gh_rm_connection { + u32 msg_id; + u16 seq; + u8 type; + 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; +}; + +struct gh_rm_notif_validate { + void *recv_buff; + void *payload; + size_t recv_buff_size; + struct gh_rm_connection *conn; + struct work_struct validate_work; +}; +const static struct { + enum gh_vm_names val; + const char *image_name; + const char *vm_name; +} vm_name_map[] = { + {GH_PRIMARY_VM, "pvm", ""}, + {GH_TRUSTED_VM, "trustedvm", "qcom,trustedvm"}, + {GH_CPUSYS_VM, "cpusys_vm", "qcom,cpusysvm"}, + {GH_OEM_VM, "oem_vm", "qcom,oemvm"}, +}; + +static struct task_struct *gh_rm_drv_recv_task; +static struct gh_msgq_desc *gh_rm_msgq_desc; +static gh_virtio_mmio_cb_t gh_virtio_mmio_fn; +static gh_wdog_manage_cb_t gh_wdog_manage_fn; +static gh_vcpu_affinity_set_cb_t gh_vcpu_affinity_set_fn; +static gh_vcpu_affinity_reset_cb_t gh_vcpu_affinity_reset_fn; +static gh_vpm_grp_set_cb_t gh_vpm_grp_set_fn; +static gh_vpm_grp_reset_cb_t gh_vpm_grp_reset_fn; +static gh_all_res_populated_cb_t gh_all_res_populated_fn; + +static DEFINE_MUTEX(gh_rm_call_idr_lock); +static DEFINE_MUTEX(gh_virtio_mmio_fn_lock); +static DEFINE_IDR(gh_rm_call_idr); +static DEFINE_MUTEX(gh_rm_send_lock); + +static struct device_node *gh_rm_intc; +static struct irq_domain *gh_rm_irq_domain; +static u32 gh_rm_base_virq; + +SRCU_NOTIFIER_HEAD_STATIC(gh_rm_notifier); + +/* non-static: used by gh_rm_iface */ +bool gh_rm_core_initialized; + +static void gh_rm_get_svm_res_work_fn(struct work_struct *work); +static DECLARE_WORK(gh_rm_get_svm_res_work, gh_rm_get_svm_res_work_fn); + +enum gh_vm_names gh_get_image_name(const char *str) +{ + int vmid; + + for (vmid = 0; vmid < ARRAY_SIZE(vm_name_map); ++vmid) { + if (!strcmp(str, vm_name_map[vmid].image_name)) + return vm_name_map[vmid].val; + } + pr_err("Can find vm index for image name %s\n", str); + return GH_VM_MAX; +} +EXPORT_SYMBOL(gh_get_image_name); + +enum gh_vm_names gh_get_vm_name(const char *str) +{ + int vmid; + + for (vmid = 0; vmid < ARRAY_SIZE(vm_name_map); ++vmid) { + if (!strcmp(str, vm_name_map[vmid].vm_name)) + return vm_name_map[vmid].val; + } + pr_err("Can find vm index for vm name %s\n", str); + return GH_VM_MAX; +} +EXPORT_SYMBOL(gh_get_vm_name); + +static struct gh_rm_connection *gh_rm_alloc_connection(u32 msg_id, + bool needed) +{ + struct gh_rm_connection *connection; + + connection = kzalloc(sizeof(*connection), GFP_KERNEL); + if (!connection) + return ERR_PTR(-ENOMEM); + + if (needed) + init_completion(&connection->seq_done); + + connection->msg_id = msg_id; + + return connection; +} + +static int +gh_rm_init_connection_buff(struct gh_rm_connection *connection, + void *recv_buff, size_t hdr_size, + size_t payload_size) +{ + struct gh_rm_rpc_hdr *hdr = recv_buff; + size_t max_buf_size; + + /* Some of the 'reply' types doesn't contain any payload */ + if (!payload_size) + return 0; + + max_buf_size = (GH_MSGQ_MAX_MSG_SIZE_BYTES - hdr_size) * + (hdr->fragments + 1); + + if (payload_size > max_buf_size) { + pr_err("%s: Payload size exceeds max buff size\n", __func__); + return -EINVAL; + } + + /* 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(max_buf_size, 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; + connection->type = hdr->type; + + return 0; +} + +int gh_rm_register_notifier(struct notifier_block *nb) +{ + return srcu_notifier_chain_register(&gh_rm_notifier, nb); +} +EXPORT_SYMBOL(gh_rm_register_notifier); + +int gh_rm_unregister_notifier(struct notifier_block *nb) +{ + return srcu_notifier_chain_unregister(&gh_rm_notifier, nb); +} +EXPORT_SYMBOL(gh_rm_unregister_notifier); + +static int +gh_rm_validate_vm_exited_notif(struct gh_rm_rpc_hdr *hdr, + void *payload, size_t recv_buff_size) +{ + struct gh_rm_notif_vm_exited_payload *vm_exited_payload; + size_t min_buff_sz = sizeof(*hdr) + sizeof(*vm_exited_payload); + + if (recv_buff_size < min_buff_sz) + return -EINVAL; + + vm_exited_payload = payload; + + switch (vm_exited_payload->exit_type) { + case GH_RM_VM_EXIT_TYPE_VM_EXIT: + if ((vm_exited_payload->exit_reason_size != + MAX_EXIT_REASON_SIZE) || + (recv_buff_size != min_buff_sz + + sizeof(struct gh_vm_exit_reason_vm_exit))) { + pr_err("%s: Invalid size for type VM_EXIT: %u\n", + __func__, recv_buff_size - sizeof(*hdr)); + return -EINVAL; + } + break; + case GH_RM_VM_EXIT_TYPE_WDT_BITE: + break; + case GH_RM_VM_EXIT_TYPE_HYP_ERROR: + break; + case GH_RM_VM_EXIT_TYPE_ASYNC_EXT_ABORT: + break; + case GH_RM_VM_EXIT_TYPE_VM_STOP_FORCED: + break; + default: + if (gh_arch_validate_vm_exited_notif(recv_buff_size, + sizeof(*hdr), vm_exited_payload)) { + pr_err("%s: Unknown exit type: %u\n", __func__, + vm_exited_payload->exit_type); + return -EINVAL; + } + } + + return 0; +} + +static struct gh_rm_connection * +gh_rm_wait_for_notif_fragments(void *recv_buff, size_t recv_buff_size) +{ + struct gh_rm_rpc_hdr *hdr = recv_buff; + struct gh_rm_connection *connection; + bool seq_done_needed = false; + size_t payload_size; + int ret = 0; + + connection = gh_rm_alloc_connection(hdr->msg_id, seq_done_needed); + if (IS_ERR_OR_NULL(connection)) + return connection; + + payload_size = recv_buff_size - sizeof(*hdr); + + ret = gh_rm_init_connection_buff(connection, recv_buff, + sizeof(*hdr), payload_size); + if (ret < 0) + goto out; + return connection; + +out: + kfree(connection); + return ERR_PTR(ret); +} + +static void gh_rm_validate_notif(struct work_struct *work) +{ + struct gh_rm_connection *connection = NULL; + struct gh_rm_notif_validate *validate_work; + void *recv_buff; + size_t recv_buff_size; + void *payload; + struct gh_rm_rpc_hdr *hdr; + u32 notification; + + validate_work = container_of(work, struct gh_rm_notif_validate, + validate_work); + recv_buff = validate_work->recv_buff; + recv_buff_size = validate_work->recv_buff_size; + payload = validate_work->payload; + connection = validate_work->conn; + hdr = recv_buff; + notification = hdr->msg_id; + pr_debug("Notification received from RM-VM: %x\n", notification); + + switch (notification) { + case GH_RM_NOTIF_VM_STATUS: + if (recv_buff_size != sizeof(*hdr) + + sizeof(struct gh_rm_notif_vm_status_payload)) { + pr_err("%s: Invalid size for VM_STATUS notif: %u\n", + __func__, recv_buff_size - sizeof(*hdr)); + goto err; + } + break; + case GH_RM_NOTIF_VM_EXITED: + if (gh_rm_validate_vm_exited_notif(hdr, + payload, recv_buff_size)) + goto err; + break; + case GH_RM_NOTIF_VM_SHUTDOWN: + if (recv_buff_size != sizeof(*hdr) + + sizeof(struct gh_rm_notif_vm_shutdown_payload)) { + pr_err("%s: Invalid size for VM_SHUTDOWN notif: %u\n", + __func__, recv_buff_size - sizeof(*hdr)); + goto err; + } + break; + case GH_RM_NOTIF_VM_IRQ_LENT: + if (recv_buff_size != sizeof(*hdr) + + sizeof(struct gh_rm_notif_vm_irq_lent_payload)) { + pr_err("%s: Invalid size for VM_IRQ_LENT notif: %u\n", + __func__, recv_buff_size - sizeof(*hdr)); + goto err; + } + break; + case GH_RM_NOTIF_VM_IRQ_RELEASED: + if (recv_buff_size != sizeof(*hdr) + + sizeof(struct gh_rm_notif_vm_irq_released_payload)) { + pr_err("%s: Invalid size for VM_IRQ_REL notif: %u\n", + __func__, recv_buff_size - sizeof(*hdr)); + goto err; + } + break; + case GH_RM_NOTIF_VM_IRQ_ACCEPTED: + if (recv_buff_size != sizeof(*hdr) + + sizeof(struct gh_rm_notif_vm_irq_accepted_payload)) { + pr_err("%s: Invalid size for VM_IRQ_ACCEPTED notif: %u\n", + __func__, recv_buff_size - sizeof(*hdr)); + goto err; + } + break; + case GH_RM_NOTIF_MEM_SHARED: + if (recv_buff_size < sizeof(*hdr) + + sizeof(struct gh_rm_notif_mem_shared_payload)) { + pr_err("%s: Invalid size for MEM_SHARED notif: %u\n", + __func__, recv_buff_size - sizeof(*hdr)); + goto err; + } + break; + case GH_RM_NOTIF_MEM_RELEASED: + if (recv_buff_size != sizeof(*hdr) + + sizeof(struct gh_rm_notif_mem_released_payload)) { + pr_err("%s: Invalid size for MEM_RELEASED notif: %u\n", + __func__, recv_buff_size - sizeof(*hdr)); + goto err; + } + break; + case GH_RM_NOTIF_MEM_ACCEPTED: + if (recv_buff_size != sizeof(*hdr) + + sizeof(struct gh_rm_notif_mem_accepted_payload)) { + pr_err("%s: Invalid size for MEM_ACCEPTED notif: %u\n", + __func__, recv_buff_size - sizeof(*hdr)); + goto err; + } + break; + case GH_RM_NOTIF_VM_CONSOLE_CHARS: + if (recv_buff_size < sizeof(*hdr) + + sizeof(struct gh_rm_notif_vm_console_chars)) { + struct gh_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)); + goto err; + } + } + break; + default: + pr_err("%s: Unknown notification received: %u\n", __func__, + notification); + goto err; + } + + srcu_notifier_call_chain(&gh_rm_notifier, notification, payload); +err: + kfree(recv_buff); + kfree(connection); + kfree(validate_work); +} + +static +struct gh_rm_connection *gh_rm_process_notif(void *recv_buff, size_t recv_buff_size) +{ + struct gh_rm_connection *connection = NULL; + struct gh_rm_notif_validate *validate_work; + struct gh_rm_rpc_hdr *hdr = recv_buff; + void *payload = NULL; + + 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 = gh_rm_wait_for_notif_fragments(recv_buff, + recv_buff_size); + return connection; + } + + /* Validate the notification received if there are no more + * fragments to follow. + */ + validate_work = kzalloc(sizeof(*validate_work), GFP_KERNEL); + if (validate_work == NULL) + return ERR_PTR(-ENOMEM); + validate_work->recv_buff = recv_buff; + validate_work->recv_buff_size = recv_buff_size; + validate_work->payload = payload; + validate_work->conn = connection; + INIT_WORK(&validate_work->validate_work, gh_rm_validate_notif); + + schedule_work(&validate_work->validate_work); + return connection; +} + +static +struct gh_rm_connection *gh_rm_process_rply(void *recv_buff, size_t recv_buff_size) +{ + struct gh_rm_rpc_reply_hdr *reply_hdr = recv_buff; + struct gh_rm_rpc_hdr *hdr = recv_buff; + struct gh_rm_connection *connection; + size_t payload_size; + int ret = 0; + + if (mutex_lock_interruptible(&gh_rm_call_idr_lock)) { + ret = -ERESTARTSYS; + return ERR_PTR(ret); + } + + connection = idr_find(&gh_rm_call_idr, hdr->seq); + mutex_unlock(&gh_rm_call_idr_lock); + + if (!connection || connection->seq != hdr->seq || + connection->msg_id != hdr->msg_id) { + pr_err("%s: Failed to get the connection info for seq: %d\n", + __func__, hdr->seq); + ret = -EINVAL; + return ERR_PTR(ret); + } + + payload_size = recv_buff_size - sizeof(*reply_hdr); + + ret = gh_rm_init_connection_buff(connection, recv_buff, + sizeof(*reply_hdr), payload_size); + if (ret < 0) + return ERR_PTR(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); + + /* 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); + return connection; +} + +static int gh_rm_process_cont(struct gh_rm_connection *connection, + void *recv_buff, size_t recv_buff_size) +{ + struct gh_rm_notif_validate *validate_work; + struct gh_rm_rpc_hdr *hdr = recv_buff; + size_t payload_size; + + if (!connection) { + pr_err("%s: not processing a fragmented connection\n", + __func__); + return -EINVAL; + } + + if (connection->msg_id != hdr->msg_id) { + pr_err("%s: got message id %x when expecting %x\n", + __func__, hdr->msg_id, connection->msg_id); + } + + /* + * 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; + + if (++connection->fragments_received == + connection->num_fragments) { + switch (connection->type) { + case GH_RM_RPC_TYPE_RPLY: + complete(&connection->seq_done); + /* 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); + break; + case GH_RM_RPC_TYPE_NOTIF: + validate_work = kzalloc(sizeof(*validate_work), + GFP_KERNEL); + if (validate_work == NULL) + return -ENOMEM; + validate_work->recv_buff = recv_buff; + validate_work->recv_buff_size = + connection->recv_buff_size; + validate_work->payload = connection->recv_buff; + validate_work->conn = connection; + INIT_WORK(&validate_work->validate_work, + gh_rm_validate_notif); + + schedule_work(&validate_work->validate_work); + break; + default: + pr_err("%s: Invalid message type (%d) received\n", + __func__, hdr->type); + } + } + + return 0; +} + +static int gh_rm_recv_task_fn(void *data) +{ + struct gh_rm_connection *connection = NULL; + struct gh_rm_rpc_hdr *hdr = NULL; + size_t recv_buff_size; + void *recv_buff; + int ret; + + while (!kthread_should_stop()) { + recv_buff = kzalloc(GH_MSGQ_MAX_MSG_SIZE_BYTES, GFP_KERNEL); + if (!recv_buff) + continue; + + /* Block until a new message is received */ + ret = gh_msgq_recv(gh_rm_msgq_desc, recv_buff, + GH_MSGQ_MAX_MSG_SIZE_BYTES, + &recv_buff_size, 0); + if (ret < 0) { + pr_err("%s: Failed to receive the message: %d\n", + __func__, ret); + kfree(recv_buff); + continue; + } else if (recv_buff_size <= sizeof(struct gh_rm_rpc_hdr)) { + pr_err("%s: Invalid message size received\n", __func__); + kfree(recv_buff); + continue; + } + + hdr = recv_buff; + switch (hdr->type) { + case GH_RM_RPC_TYPE_NOTIF: + connection = gh_rm_process_notif(recv_buff, + recv_buff_size); + break; + case GH_RM_RPC_TYPE_RPLY: + connection = gh_rm_process_rply(recv_buff, + recv_buff_size); + break; + case GH_RM_RPC_TYPE_CONT: + gh_rm_process_cont(connection, recv_buff, + recv_buff_size); + break; + default: + pr_err("%s: Invalid message type (%d) received\n", + __func__, hdr->type); + } + print_hex_dump_debug("gh_rm_recv: ", DUMP_PREFIX_OFFSET, + 4, 1, recv_buff, recv_buff_size, false); + } + + return 0; +} + +static int gh_rm_send_request(u32 message_id, + const void *req_buff, size_t req_buff_size, + struct gh_rm_connection *connection) +{ + size_t buff_size_remaining = req_buff_size; + const void *req_buff_curr = req_buff; + struct gh_rm_rpc_hdr *hdr; + unsigned long tx_flags; + u32 num_fragments = 0; + size_t payload_size; + void *send_buff; + int i, ret; + + num_fragments = (req_buff_size + GH_RM_MAX_MSG_SIZE_BYTES - 1) / + GH_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 > GH_RM_MAX_NUM_FRAGMENTS) { + pr_err("%s: Limit exceeded for the number of fragments: %u\n", + __func__, num_fragments); + return -E2BIG; + } + + if (mutex_lock_interruptible(&gh_rm_send_lock)) + return -ERESTARTSYS; + + /* Consider also the 'request' packet for the loop count */ + for (i = 0; i <= num_fragments; i++) { + if (buff_size_remaining > GH_RM_MAX_MSG_SIZE_BYTES) { + payload_size = GH_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) { + mutex_unlock(&gh_rm_send_lock); + return -ENOMEM; + } + + hdr = send_buff; + hdr->version = GH_RM_RPC_HDR_VERSION_ONE; + hdr->hdr_words = GH_RM_RPC_HDR_WORDS; + hdr->type = i == 0 ? GH_RM_RPC_TYPE_REQ : GH_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) ? GH_MSGQ_TX_PUSH : 0; + + /* delay sending console characters to RM */ + if (message_id == GH_RM_RPC_MSG_ID_CALL_VM_CONSOLE_WRITE || + message_id == GH_RM_RPC_MSG_ID_CALL_VM_CONSOLE_FLUSH) + udelay(800); + + ret = gh_msgq_send(gh_rm_msgq_desc, send_buff, + sizeof(*hdr) + payload_size, tx_flags); + + /* + * In the case of a success, the hypervisor would have consumed + * the buffer. While in the case of a failure, we are going to + * quit anyways. Hence, free the buffer regardless of the + * return value. + */ + kfree(send_buff); + + if (ret) { + mutex_unlock(&gh_rm_send_lock); + return ret; + } + } + + mutex_unlock(&gh_rm_send_lock); + return 0; +} + +/** + * gh_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 Gunyah 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 *gh_rm_call(gh_rm_msgid_t message_id, + void *req_buff, size_t req_buff_size, + size_t *resp_buff_size, int *reply_err_code) +{ + struct gh_rm_connection *connection; + bool seq_done_needed = true; + int req_ret; + void *ret; + + if (!message_id || !req_buff || !resp_buff_size || !reply_err_code) + return ERR_PTR(-EINVAL); + + connection = gh_rm_alloc_connection(message_id, seq_done_needed); + if (IS_ERR_OR_NULL(connection)) + return connection; + + /* Allocate a new seq number for this connection */ + if (mutex_lock_interruptible(&gh_rm_call_idr_lock)) { + kfree(connection); + return ERR_PTR(-ERESTARTSYS); + } + + connection->seq = idr_alloc_cyclic(&gh_rm_call_idr, connection, + 0, U16_MAX, GFP_KERNEL); + mutex_unlock(&gh_rm_call_idr_lock); + + pr_debug("%s TX msg_id: %x\n", __func__, message_id); + print_hex_dump_debug("@"__stringify(__func__)" TX: ", DUMP_PREFIX_OFFSET, 4, 1, + req_buff, req_buff_size, false); + /* Send the request to the Resource Manager VM */ + req_ret = gh_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 */ + wait_for_completion(&connection->seq_done); + + *reply_err_code = connection->reply_err_code; + if (connection->reply_err_code) { + pr_err("%s: Reply for seq:%d failed with RM err: %d\n", + __func__, connection->seq, connection->reply_err_code); + ret = ERR_PTR(gh_remap_error(connection->reply_err_code)); + kfree(connection->recv_buff); + goto out; + } + + print_hex_dump_debug("@"__stringify(__func__)" RX: ", DUMP_PREFIX_OFFSET, 4, 1, + connection->recv_buff, connection->recv_buff_size, + false); + + mutex_lock(&gh_rm_call_idr_lock); + idr_remove(&gh_rm_call_idr, connection->seq); + mutex_unlock(&gh_rm_call_idr_lock); + + ret = connection->recv_buff; + *resp_buff_size = connection->recv_buff_size; + +out: + kfree(connection); + return ret; +} + +/** + * gh_rm_virq_to_irq: Get a Linux IRQ from a Gunyah-compatible vIRQ + * @virq: Gunyah-compatible vIRQ + * @type: IRQ trigger type (IRQ_TYPE_EDGE_RISING) + * + * Returns the mapped Linux IRQ# at Gunyah's IRQ domain (i.e. GIC SPI) + */ +int gh_rm_virq_to_irq(u32 virq, u32 type) +{ + return gh_get_irq(virq, type, of_node_to_fwnode(gh_rm_intc)); +} +EXPORT_SYMBOL(gh_rm_virq_to_irq); + +/** + * gh_rm_irq_to_virq: Get a Gunyah-compatible vIRQ from a Linux IRQ + * @irq: Linux-assigned IRQ# + * @virq: out value where Gunyah-compatible vIRQ is stored + * + * Returns 0 upon success, -EINVAL if the Linux IRQ could not be mapped to + * a Gunyah vIRQ (i.e., the IRQ does not correspond to any GIC-level IRQ) + */ +int gh_rm_irq_to_virq(int irq, u32 *virq) +{ + struct irq_data *irq_data; + + irq_data = irq_domain_get_irq_data(gh_rm_irq_domain, irq); + if (!irq_data) + return -EINVAL; + + if (virq) + *virq = irq_data->hwirq; + + return 0; +} +EXPORT_SYMBOL(gh_rm_irq_to_virq); + +static int gh_rm_get_irq(struct gh_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 && !virq) || virq == U32_MAX) + return 0; + + /* Allocate and bind a new IRQ if RM-VM hasn't already done already */ + if (virq == GH_RM_NO_IRQ_ALLOC) { + ret = virq = gh_get_virq(gh_rm_base_virq, virq); + if (ret < 0) + return ret; + + /* Bind the vIRQ */ + ret = gh_rm_vm_irq_accept(res_entry->virq_handle, virq); + if (ret < 0) { + pr_err("%s: IRQ accept failed: %d\n", + __func__, ret); + gh_put_virq(virq); + return ret; + } + } + + return gh_rm_virq_to_irq(virq, IRQ_TYPE_EDGE_RISING); +} + +/** + * gh_rm_get_vm_id_info: Query Resource Manager VM to get vm identification info. + * @vmid: The vmid of VM whose id information needs to be queried. + * + * The function encodes the error codes via ERR_PTR. Hence, the caller is + * responsible to check it with IS_ERR_OR_NULL(). + */ +int gh_rm_get_vm_id_info(gh_vmid_t vmid) +{ + struct gh_vm_get_id_resp_entry *id_entries = NULL, *entry; + struct gh_vm_property vm_prop = {0}; + void *info = NULL; + int ret = 0; + u32 n_id, i; + enum gh_vm_names vm_name; + + id_entries = gh_rm_vm_get_id(vmid, &n_id); + if (IS_ERR_OR_NULL(id_entries)) + return PTR_ERR(id_entries); + + pr_debug("%s: %d Info are associated with vmid %d\n", + __func__, n_id, vmid); + + entry = id_entries; + for (i = 0; i < n_id; i++) { + pr_debug("%s: idx:%d id_type %d reserved %d id_size %d\n", + __func__, i, + entry->id_type, + entry->reserved, + entry->id_size); + + info = kzalloc(entry->id_size % 4 ? entry->id_size + 1 : + entry->id_size, + GFP_KERNEL); + + if (!info) { + ret = -ENOMEM; + break; + } + + memcpy(info, entry->id_info, entry->id_size); + + pr_debug("%s: idx:%d id_info %s\n", __func__, i, info); + switch (entry->id_type) { + case GH_RM_ID_TYPE_GUID: + vm_prop.guid = info; + break; + case GH_RM_ID_TYPE_URI: + vm_prop.uri = info; + break; + case GH_RM_ID_TYPE_NAME: + vm_prop.name = info; + break; + case GH_RM_ID_TYPE_SIGN_AUTH: + vm_prop.sign_auth = info; + break; + default: + pr_err("%s: Unknown id type: %u\n", + __func__, entry->id_type); + ret = -EINVAL; + kfree(info); + } + entry = (void *)entry + sizeof(*entry) + + round_up(entry->id_size, 4); + } + + if (!ret) { + vm_prop.vmid = vmid; + if (vm_prop.name) + vm_name = gh_get_vm_name(vm_prop.name); + else + vm_name = GH_VM_MAX; + if (vm_name == GH_VM_MAX) { + pr_err("Invalid vm name %s of VMID %d\n", vm_prop.name, + vmid); + ret = -EINVAL; + } else { + ret = gh_update_vm_prop_table(vm_name, &vm_prop); + } + } + + kfree(id_entries); + return ret; +} +EXPORT_SYMBOL(gh_rm_get_vm_id_info); + +/** + * gh_rm_populate_hyp_res: Query Resource Manager VM to get hyp resources. + * @vmid: The vmid of resources to be queried. + * + * The function encodes the error codes via ERR_PTR. Hence, the caller is + * responsible to check it with IS_ERR_OR_NULL(). + */ +int gh_rm_populate_hyp_res(gh_vmid_t vmid, const char *vm_name) +{ + struct gh_vm_get_hyp_res_resp_entry *res_entries = NULL; + int linux_irq, ret = 0; + gh_capid_t cap_id; + gh_label_t label; + u32 n_res, i; + u64 base = 0, size = 0; + + res_entries = gh_rm_vm_get_hyp_res(vmid, &n_res); + if (IS_ERR_OR_NULL(res_entries)) + return PTR_ERR(res_entries); + + pr_debug("%s: %d Resources are associated with vmid %d\n", + __func__, n_res, vmid); + + for (i = 0; i < n_res; i++) { + pr_debug("%s: idx:%d res_entries.res_type = 0x%x, res_entries.partner_vmid = 0x%x, res_entries.resource_handle = 0x%x, res_entries.resource_label = 0x%x, res_entries.cap_id_low = 0x%x, res_entries.cap_id_high = 0x%x, res_entries.virq_handle = 0x%x, res_entries.virq = 0x%x res_entries.base_high = 0x%x, res_entries.base_low = 0x%x, res_entries.size_high = 0x%x, res_entries.size_low = 0x%x\n", + __func__, i, + res_entries[i].res_type, + res_entries[i].partner_vmid, + res_entries[i].resource_handle, + res_entries[i].resource_label, + res_entries[i].cap_id_low, + res_entries[i].cap_id_high, + res_entries[i].virq_handle, + res_entries[i].virq, + res_entries[i].base_high, + res_entries[i].base_low, + res_entries[i].size_high, + res_entries[i].size_low); + + ret = linux_irq = gh_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; + base = (u64) res_entries[i].base_high << 32 | + res_entries[i].base_low; + size = (u64) res_entries[i].size_high << 32 | + res_entries[i].size_low; + label = res_entries[i].resource_label; + + /* Populate MessageQ, DBL and vCPUs cap tables */ + do { + switch (res_entries[i].res_type) { + case GH_RM_RES_TYPE_MQ_TX: + ret = gh_msgq_populate_cap_info(label, cap_id, + GH_MSGQ_DIRECTION_TX, linux_irq); + break; + case GH_RM_RES_TYPE_MQ_RX: + ret = gh_msgq_populate_cap_info(label, cap_id, + GH_MSGQ_DIRECTION_RX, linux_irq); + break; + case GH_RM_RES_TYPE_VCPU: + if (gh_vcpu_affinity_set_fn) + ret = (*gh_vcpu_affinity_set_fn)(vmid, label, + cap_id, linux_irq); + break; + case GH_RM_RES_TYPE_DB_TX: + ret = gh_dbl_populate_cap_info(label, cap_id, + GH_MSGQ_DIRECTION_TX, linux_irq); + break; + case GH_RM_RES_TYPE_DB_RX: + ret = gh_dbl_populate_cap_info(label, cap_id, + GH_MSGQ_DIRECTION_RX, linux_irq); + break; + case GH_RM_RES_TYPE_VPMGRP: + if (gh_vpm_grp_set_fn) + ret = (*gh_vpm_grp_set_fn)(vmid, cap_id, linux_irq); + break; + case GH_RM_RES_TYPE_VIRTIO_MMIO: + mutex_lock(&gh_virtio_mmio_fn_lock); + if (!gh_virtio_mmio_fn) { + mutex_unlock(&gh_virtio_mmio_fn_lock); + break; + } + + ret = (*gh_virtio_mmio_fn)(vmid, vm_name, label, + cap_id, linux_irq, base, size); + mutex_unlock(&gh_virtio_mmio_fn_lock); + break; + case GH_RM_RES_TYPE_WATCHDOG: + if (gh_wdog_manage_fn) + ret = (*gh_wdog_manage_fn)(vmid, cap_id, true); + break; + default: + pr_err("%s: Unknown resource type: %u\n", + __func__, res_entries[i].res_type); + ret = -EINVAL; + } + } while (ret == -EAGAIN); + + if (ret < 0) + goto out; + } + + if (gh_all_res_populated_fn) + (*gh_all_res_populated_fn)(vmid, true); +out: + kfree(res_entries); + return ret; +} +EXPORT_SYMBOL(gh_rm_populate_hyp_res); + +static void +gh_rm_put_irq(struct gh_vm_get_hyp_res_resp_entry *res_entry, int irq) +{ + if (!gh_put_irq(irq)) + gh_rm_vm_irq_release(res_entry->virq_handle); + +} + +/** + * gh_rm_unpopulate_hyp_res: Unpopulate the resources that we got from + * gh_rm_populate_hyp_res(). + * @vmid: The vmid of resources to be queried. + * @vm_name: The name of the VM + * + * Returns 0 on success and a negative error code upon failure. + */ +int gh_rm_unpopulate_hyp_res(gh_vmid_t vmid, const char *vm_name) +{ + struct gh_vm_get_hyp_res_resp_entry *res_entries = NULL; + gh_label_t label; + u32 n_res, i; + int ret = 0, irq = -1; + gh_capid_t cap_id; + + res_entries = gh_rm_vm_get_hyp_res(vmid, &n_res); + if (IS_ERR_OR_NULL(res_entries)) + return PTR_ERR(res_entries); + + for (i = 0; i < n_res; i++) { + label = res_entries[i].resource_label; + cap_id = (u64) res_entries[i].cap_id_high << 32 | + res_entries[i].cap_id_low; + + switch (res_entries[i].res_type) { + case GH_RM_RES_TYPE_MQ_TX: + ret = gh_msgq_reset_cap_info(label, + GH_MSGQ_DIRECTION_TX, &irq); + break; + case GH_RM_RES_TYPE_MQ_RX: + ret = gh_msgq_reset_cap_info(label, + GH_MSGQ_DIRECTION_RX, &irq); + break; + case GH_RM_RES_TYPE_DB_TX: + ret = gh_dbl_reset_cap_info(label, + GH_RM_RES_TYPE_DB_TX, &irq); + break; + case GH_RM_RES_TYPE_DB_RX: + ret = gh_dbl_reset_cap_info(label, + GH_RM_RES_TYPE_DB_RX, &irq); + break; + case GH_RM_RES_TYPE_VCPU: + if (gh_vcpu_affinity_reset_fn) + ret = (*gh_vcpu_affinity_reset_fn)(vmid, + label, cap_id, &irq); + break; + case GH_RM_RES_TYPE_VIRTIO_MMIO: + /* Virtio cleanup is handled in gh_virtio_mmio_exit() */ + break; + case GH_RM_RES_TYPE_VPMGRP: + if (gh_vpm_grp_reset_fn) + ret = (*gh_vpm_grp_reset_fn)(vmid, &irq); + break; + case GH_RM_RES_TYPE_WATCHDOG: + if (gh_wdog_manage_fn) + ret = (*gh_wdog_manage_fn)(vmid, cap_id, false); + break; + default: + pr_err("%s: Unknown resource type: %u\n", + __func__, res_entries[i].res_type); + ret = -EINVAL; + } + + if (ret < 0) + goto out; + + if (irq >= 0) + gh_rm_put_irq(&res_entries[i], irq); + } + + if (gh_all_res_populated_fn) + (*gh_all_res_populated_fn)(vmid, false); +out: + kfree(res_entries); + return ret; +} +EXPORT_SYMBOL(gh_rm_unpopulate_hyp_res); + +/** + * gh_rm_set_virtio_mmio_cb: Set callback that handles virtio MMIO resource + * @fnptr: Pointer to callback function + * + * gh_rm_populate_hyp_res() queries RM-VM for all resources assigned to a VM and + * as part of that response RM-VM will indicate resources assigned exclusively + * to handle virtio communication between the two VMs. @fnptr callback is + * invoked providing details of the virtio resource allocated for a particular + * virtio device. @fnptr is expected to initialize additional state based on the + * information provided. + * + * This function returns these values: + * 0 -> indicates success + * -EINVAL -> Indicates invalid input argument + * -EBUSY -> Indicates that a callback is already set + */ +int gh_rm_set_virtio_mmio_cb(gh_virtio_mmio_cb_t fnptr) +{ + if (!fnptr) + return -EINVAL; + + mutex_lock(&gh_virtio_mmio_fn_lock); + if (gh_virtio_mmio_fn) { + mutex_unlock(&gh_virtio_mmio_fn_lock); + return -EBUSY; + } + + gh_virtio_mmio_fn = fnptr; + mutex_unlock(&gh_virtio_mmio_fn_lock); + + return 0; +} +EXPORT_SYMBOL(gh_rm_set_virtio_mmio_cb); + +/** + * gh_rm_unset_virtio_mmio_cb: Unset callback that handles virtio MMIO resource + */ +void gh_rm_unset_virtio_mmio_cb(void) +{ + mutex_lock(&gh_virtio_mmio_fn_lock); + gh_virtio_mmio_fn = NULL; + mutex_unlock(&gh_virtio_mmio_fn_lock); +} +EXPORT_SYMBOL(gh_rm_unset_virtio_mmio_cb); + +/** + * gh_rm_set_wdog_manage_cb: Set callback that handles wdog resource + * @fnptr: Pointer to callback function + * + * @fnptr callback is invoked providing details of the wdog resource. + * + * This function returns these values: + * 0 -> indicates success + * -EINVAL -> Indicates invalid input argument + * -EBUSY -> Indicates that a callback is already set + */ +int gh_rm_set_wdog_manage_cb(gh_wdog_manage_cb_t fnptr) +{ + if (!fnptr) + return -EINVAL; + + if (gh_wdog_manage_fn) + return -EBUSY; + + gh_wdog_manage_fn = fnptr; + + return 0; +} +EXPORT_SYMBOL(gh_rm_set_wdog_manage_cb); + +/** + * gh_rm_set_vcpu_affinity_cb: Set callback that handles vcpu affinity + * @fnptr: Pointer to callback function + * + * @fnptr callback is invoked providing details of the vcpu resource. + * + * This function returns these values: + * 0 -> indicates success + * -EINVAL -> Indicates invalid input argument + * -EBUSY -> Indicates that a callback is already set + */ +int gh_rm_set_vcpu_affinity_cb(gh_vcpu_affinity_set_cb_t fnptr) +{ + if (!fnptr) + return -EINVAL; + + if (gh_vcpu_affinity_set_fn) + return -EBUSY; + + gh_vcpu_affinity_set_fn = fnptr; + + return 0; +} +EXPORT_SYMBOL(gh_rm_set_vcpu_affinity_cb); + +/** + * gh_rm_reset_vcpu_affinity_cb: Reset callback that handles vcpu affinity + * @fnptr: Pointer to callback function + * + * @fnptr callback is invoked providing details of the vcpu resource. + * + * This function returns these values: + * 0 -> indicates success + * -EINVAL -> Indicates invalid input argument + * -EBUSY -> Indicates that a callback is already set + */ +int gh_rm_reset_vcpu_affinity_cb(gh_vcpu_affinity_reset_cb_t fnptr) +{ + if (!fnptr) + return -EINVAL; + + if (gh_vcpu_affinity_reset_fn) + return -EBUSY; + + gh_vcpu_affinity_reset_fn = fnptr; + + return 0; +} +EXPORT_SYMBOL(gh_rm_reset_vcpu_affinity_cb); + +/** + * gh_rm_set_vpm_grp_cb: Set callback that handles vpm grp state + * @fnptr: Pointer to callback function + * + * @fnptr callback is invoked providing details of the vcpu grp state IRQ. + * + * This function returns these values: + * 0 -> indicates success + * -EINVAL -> Indicates invalid input argument + * -EBUSY -> Indicates that a callback is already set + */ +int gh_rm_set_vpm_grp_cb(gh_vpm_grp_set_cb_t fnptr) +{ + if (!fnptr) + return -EINVAL; + + if (gh_vpm_grp_set_fn) + return -EBUSY; + + gh_vpm_grp_set_fn = fnptr; + + return 0; +} +EXPORT_SYMBOL(gh_rm_set_vpm_grp_cb); + +/** + * gh_rm_reset_vpm_grp_cb: Reset callback that handles vpm grp state + * @fnptr: Pointer to callback function + * + * @fnptr callback is invoked providing details of the vcpu grp state IRQ. + * + * This function returns these values: + * 0 -> indicates success + * -EINVAL -> Indicates invalid input argument + * -EBUSY -> Indicates that a callback is already set + */ +int gh_rm_reset_vpm_grp_cb(gh_vpm_grp_reset_cb_t fnptr) +{ + if (!fnptr) + return -EINVAL; + + if (gh_vpm_grp_reset_fn) + return -EBUSY; + + gh_vpm_grp_reset_fn = fnptr; + + return 0; +} +EXPORT_SYMBOL(gh_rm_reset_vpm_grp_cb); + +/** + * gh_rm_all_res_populated_cb: Set callback that handles all res populated + * @fnptr: Pointer to callback function + * + * @fnptr callback is invoked after all resources are populated/un-pupulated. + * + * This function returns these values: + * 0 -> indicates success + * -EINVAL -> Indicates invalid input argument + * -EBUSY -> Indicates that a callback is already set + */ +int gh_rm_all_res_populated_cb(gh_all_res_populated_cb_t fnptr) +{ + if (!fnptr) + return -EINVAL; + + if (gh_all_res_populated_fn) + return -EBUSY; + + gh_all_res_populated_fn = fnptr; + + return 0; +} +EXPORT_SYMBOL(gh_rm_all_res_populated_cb); + +static void gh_rm_get_svm_res_work_fn(struct work_struct *work) +{ + gh_vmid_t vmid; + int ret; + + ret = gh_rm_get_vmid(GH_PRIMARY_VM, &vmid); + if (ret) + pr_err("%s: Unable to get VMID for VM label %d\n", + __func__, GH_PRIMARY_VM); + else + gh_rm_populate_hyp_res(vmid, NULL); +} + +static int gh_vm_status_nb_handler(struct notifier_block *this, + unsigned long cmd, void *data) +{ + struct gh_rm_notif_vm_status_payload *vm_status_payload = data; + struct gh_vminfo vm_info = {0}; + enum gh_vm_names vm_name; + u8 vm_status = vm_status_payload->vm_status; + int ret; + + if (cmd != GH_RM_NOTIF_VM_STATUS) + return NOTIFY_DONE; + + switch (vm_status) { + case GH_RM_VM_STATUS_READY: + pr_err("vm(%d) is ready\n", vm_status_payload->vmid); + ret = gh_rm_get_vm_id_info(vm_status_payload->vmid); + if (ret < 0) { + pr_err("Failed to get vmid info for vmid = %d ret = %d\n", + vm_status_payload->vmid, ret); + return NOTIFY_DONE; + } + ret = gh_rm_get_vm_name(vm_status_payload->vmid, &vm_name); + if (ret < 0) { + pr_err("Failed to get vm name for vmid = %d ret = %d\n", + vm_status_payload->vmid, ret); + return NOTIFY_DONE; + } + ret = gh_rm_get_vminfo(vm_name, &vm_info); + if (ret < 0) + pr_err("Failed to get vminfo of vmname = %s\n", vm_name); + ret = gh_rm_populate_hyp_res(vm_status_payload->vmid, + vm_info.name); + if (ret < 0) { + pr_err("Failed to get hyp resources for vmid = %d vmname = %s ret = %d\n", + vm_status_payload->vmid, vm_name, ret); + return NOTIFY_DONE; + } + break; + case GH_RM_VM_STATUS_RUNNING: + pr_err("vm(%d) started running\n", vm_status_payload->vmid); + break; + default: + pr_err("Unknown notification receieved for vmid = %d vm_status = %d\n", + vm_status_payload->vmid, vm_status); + } + + return NOTIFY_DONE; +} + + +static struct notifier_block gh_vm_status_nb = { + .notifier_call = gh_vm_status_nb_handler +}; + + +static void gh_vm_check_peer(struct device *dev, struct device_node *rm_root) +{ + int peers_cnt, ret, i; + const char **peers_array = NULL; + const char *peer, *peer_data; + gh_vmid_t vmid; + enum gh_vm_names vm_name_index; + struct gh_vminfo vm_info; + uuid_t vm_guid; + + peers_cnt = of_property_count_strings(rm_root, "qcom,peers"); + peers_array = kcalloc(peers_cnt, sizeof(char *), GFP_KERNEL); + if (!peers_array) { + dev_err(dev, "Failed to allocate memory\n"); + ret = -ENOMEM; + return; + } + + ret = of_property_read_string_array(rm_root, "qcom,peers", peers_array, + peers_cnt); + if (ret < 0) { + dev_err(dev, "Failed to find qcom,peers\n"); + ret = -ENODEV; + goto out; + } + + for (i = 0; i < peers_cnt; i++) { + peer = peers_array[i]; + if (peer == NULL) + continue; + if (strnstr(peer, "vm-name:", strlen("vm-name:")) != NULL) { + peer_data = peer + strlen("vm-name:"); + dev_dbg(dev, "Trying to lookup name %s\n", peer_data); + ret = gh_rm_vm_lookup(GH_VM_LOOKUP_NAME, peer_data, + strlen(peer_data), &vmid); + } else if (strnstr(peer, "vm-uri:", strlen("vm-uri:")) != + NULL) { + peer_data = peer + strlen("vm-uri:"); + dev_dbg(dev, "Trying to lookup uri %s\n", peer_data); + ret = gh_rm_vm_lookup(GH_VM_LOOKUP_URI, peer_data, + strlen(peer_data), &vmid); + } else if (strnstr(peer, "vm-guid:", strlen("vm-guid:")) != + NULL) { + peer_data = peer + strlen("vm-guid:"); + dev_dbg(dev, "Trying to lookup guid %s\n", peer_data); + ret = uuid_parse(peer_data, &vm_guid); + if (ret != 0) + dev_err(dev, "Invalid GUID:%s\n", + peer + strlen("vm-guid:")); + else + ret = gh_rm_vm_lookup(GH_VM_LOOKUP_GUID, + (char *)&vm_guid, + sizeof(vm_guid), &vmid); + } else { + dev_err(dev, "Unknown peer type:%s\n", peer); + continue; + } + if (ret < 0) { + dev_err(dev, + "lookup %s failed, VM is not running ret=%d\n", + peer, ret); + continue; + } + ret = gh_rm_get_vm_id_info(vmid); + if (ret < 0) { + dev_err(dev, + "Failed to get vmid info for vmid = %d ret = %d\n", + vmid, ret); + continue; + } + ret = gh_rm_get_vm_name(vmid, &vm_name_index); + if (ret < 0) { + dev_err(dev, + "Failed to get vmid info for vmid = %d ret = %d\n", + vmid, ret); + continue; + } + gh_rm_get_vminfo(vm_name_index, &vm_info); + ret = gh_rm_populate_hyp_res(vmid, vm_info.name); + if (ret < 0) { + dev_err(dev, + "Failed to get hyp resources for vmid = %d ret = %d\n", + vmid, ret); + continue; + } + } +out: + kfree(peers_array); +} + +static int gh_vm_probe(struct device *dev, struct device_node *hyp_root) +{ + struct device_node *node; + struct gh_vm_property temp_property = {0}; + int vmid, owner_vmid, ret; + const char *vm_name; + enum gh_vm_names vm_name_index; + + + gh_init_vm_prop_table(); + + node = of_find_compatible_node(hyp_root, NULL, "qcom,gunyah-vm-id-1.0"); + if (IS_ERR_OR_NULL(node)) { + node = of_find_compatible_node(hyp_root, NULL, "qcom,haven-vm-id-1.0"); + if (IS_ERR_OR_NULL(node)) { + dev_err(dev, "Could not find vm-id node\n"); + return -ENODEV; + } + } + + ret = of_property_read_u32(node, "qcom,vmid", &vmid); + if (ret) { + dev_err(dev, "Could not read vmid: %d\n", ret); + return ret; + } + + ret = of_property_read_u32(node, "qcom,owner-vmid", &owner_vmid); + if (ret) { + /* We must be GH_PRIMARY_VM */ + temp_property.vmid = vmid; + gh_update_vm_prop_table(GH_PRIMARY_VM, &temp_property); + gh_rm_core_initialized = true; + } else { + ret = of_property_read_string(node, "qcom,image-name", + &vm_name); + if (ret) { + /* Just for compatible, if image-name cannot be found */ + /* Assume we are trusted VM */ + dev_dbg(dev, + "Could not find qcom,image-name assume we are trustedvm\n"); + vm_name_index = GH_TRUSTED_VM; + } else { + vm_name_index = gh_get_vm_name(vm_name); + if (vm_name_index == GH_VM_MAX) { + dev_dbg(dev, + "Could not find vm_name:%s assume we are trustedvm\n", + vm_name); + vm_name_index = GH_TRUSTED_VM; + } else { + dev_dbg(dev, "VM name index is %d\n", + vm_name_index); + } + } + temp_property.vmid = vmid; + gh_update_vm_prop_table(vm_name_index, &temp_property); + temp_property.vmid = owner_vmid; + gh_update_vm_prop_table(GH_PRIMARY_VM, &temp_property); + + /* check peer to see if any VM has been bootup */ + gh_vm_check_peer(dev, node); + gh_rm_register_notifier(&gh_vm_status_nb); + gh_rm_core_initialized = true; + /* Query RM for available resources */ + schedule_work(&gh_rm_get_svm_res_work); + } + + return 0; +} + +static const struct of_device_id gh_rm_drv_of_match[] = { + { .compatible = "qcom,resource-manager-1-0" }, + { } +}; + +static int gh_rm_drv_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *node = dev->of_node; + int ret; + + ret = gh_msgq_probe(pdev, GH_MSGQ_LABEL_RM); + if (ret) { + dev_err(dev, "Failed to probe message queue: %d\n", ret); + return ret; + } + + if (of_property_read_u32(node, "qcom,free-irq-start", + &gh_rm_base_virq)) { + dev_err(dev, "Failed to get the vIRQ base\n"); + return -ENXIO; + } + + gh_rm_intc = of_irq_find_parent(node); + if (!gh_rm_intc) { + dev_err(dev, "Failed to get the IRQ parent node\n"); + return -ENXIO; + } + gh_rm_irq_domain = irq_find_host(gh_rm_intc); + if (!gh_rm_irq_domain) { + dev_err(dev, "Failed to get IRQ domain associated with RM\n"); + return -ENXIO; + } + + gh_rm_msgq_desc = gh_msgq_register(GH_MSGQ_LABEL_RM); + if (IS_ERR_OR_NULL(gh_rm_msgq_desc)) + return PTR_ERR(gh_rm_msgq_desc); + + /* As we don't have a callback for message reception yet, + * spawn a kthread and always listen to incoming messages. + */ + gh_rm_drv_recv_task = kthread_run(gh_rm_recv_task_fn, + NULL, "gh_rm_recv_task"); + if (IS_ERR_OR_NULL(gh_rm_drv_recv_task)) { + ret = PTR_ERR(gh_rm_drv_recv_task); + goto err_recv_task; + } + + /* Probe the vmid */ + ret = gh_vm_probe(dev, node->parent); + if (ret < 0 && ret != -ENODEV) + goto err_recv_task; + + return 0; + +err_recv_task: + gh_msgq_unregister(gh_rm_msgq_desc); + return ret; +} + +static int gh_rm_drv_remove(struct platform_device *pdev) +{ + kthread_stop(gh_rm_drv_recv_task); + gh_msgq_unregister(gh_rm_msgq_desc); + idr_destroy(&gh_rm_call_idr); + + return 0; +} + +static struct platform_driver gh_rm_driver = { + .probe = gh_rm_drv_probe, + .remove = gh_rm_drv_remove, + .driver = { + .name = "gh_rm_driver", + .of_match_table = gh_rm_drv_of_match, + }, +}; + +module_platform_driver(gh_rm_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Qualcomm Technologies, Inc. Gunyah Resource Mgr. Driver"); diff --git a/drivers/virt/gunyah/gh_rm_drv_private.h b/drivers/virt/gunyah/gh_rm_drv_private.h new file mode 100644 index 000000000000..e4d0bdedb9cb --- /dev/null +++ b/drivers/virt/gunyah/gh_rm_drv_private.h @@ -0,0 +1,461 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2020-2021, The Linux Foundation. All rights reserved. + */ + +#ifndef __GH_RM_DRV_PRIVATE_H +#define __GH_RM_DRV_PRIVATE_H + +#include + +#include +#include +#include + +extern bool gh_rm_core_initialized; + +/* Resource Manager Header */ +struct gh_rm_rpc_hdr { + u8 version:4, + hdr_words:4; + u8 type:2, + fragments:6; + u16 seq; + u32 msg_id; +} __packed; + +/* Standard reply header */ +struct gh_rm_rpc_reply_hdr { + struct gh_rm_rpc_hdr rpc_hdr; + u32 err_code; +} __packed; + +/* VM specific properties to be cached */ +struct gh_vm_property { + gh_vmid_t vmid; + u8 *guid; + char *uri; + char *name; + char *sign_auth; +}; + +/* RPC Header versions */ +#define GH_RM_RPC_HDR_VERSION_ONE 0x1 + +/* RPC Header words */ +#define GH_RM_RPC_HDR_WORDS 0x2 + +/* RPC Message types */ +#define GH_RM_RPC_TYPE_CONT 0x0 +#define GH_RM_RPC_TYPE_REQ 0x1 +#define GH_RM_RPC_TYPE_RPLY 0x2 +#define GH_RM_RPC_TYPE_NOTIF 0x3 + +/* RPC Message IDs */ +/* Call type Message IDs that has a request/reply pattern */ +/* Message IDs: Informative */ +#define GH_RM_RPC_MSG_ID_CALL_GET_IDENT 0x00000001 +#define GH_RM_RPC_MSG_ID_CALL_GET_FEATURES 0x00000002 + +/* Message IDs: Memory management */ +#define GH_RM_RPC_MSG_ID_CALL_MEM_DONATE 0x51000010 +#define GH_RM_RPC_MSG_ID_CALL_MEM_ACCEPT 0x51000011 +#define GH_RM_RPC_MSG_ID_CALL_MEM_LEND 0x51000012 +#define GH_RM_RPC_MSG_ID_CALL_MEM_SHARE 0x51000013 +#define GH_RM_RPC_MSG_ID_CALL_MEM_RELEASE 0x51000014 +#define GH_RM_RPC_MSG_ID_CALL_MEM_RECLAIM 0x51000015 +#define GH_RM_RPC_MSG_ID_CALL_MEM_NOTIFY 0x51000017 +#define GH_RM_RPC_MSG_ID_CALL_MEM_APPEND 0x51000018 + +/* Message IDs: extensions for hyp-assign */ +#define GH_RM_RPC_MSG_ID_CALL_MEM_QCOM_LOOKUP_SGL 0x5100001A + +/* Message IDs: VM Management */ +#define GH_RM_RPC_MSG_ID_CALL_VM_ALLOCATE 0x56000001 +#define GH_RM_RPC_MSG_ID_CALL_VM_DEALLOCATE 0x56000002 +#define GH_RM_RPC_MSG_ID_CALL_VM_START 0x56000004 +#define GH_RM_RPC_MSG_ID_CALL_VM_STOP 0x56000005 +#define GH_RM_RPC_MSG_ID_CALL_VM_RESET 0x56000006 +#define GH_RM_RPC_MSG_ID_CALL_VM_CONFIG_IMAGE 0x56000009 +#define GH_RM_RPC_MSG_ID_CALL_VM_AUTH_IMAGE 0x5600000A +#define GH_RM_RPC_MSG_ID_CALL_VM_INIT 0x5600000B + +/* Message IDs: VM Query */ +#define GH_RM_RPC_MSG_ID_CALL_VM_GET_ID 0x56000010 +#define GH_RM_RPC_MSG_ID_CALL_VM_LOOKUP_URI 0x56000011 +#define GH_RM_RPC_MSG_ID_CALL_VM_LOOKUP_GUID 0x56000012 +#define GH_RM_RPC_MSG_ID_CALL_VM_LOOKUP_NAME 0x56000013 +#define GH_RM_RPC_MSG_ID_CALL_VM_GET_STATE 0x56000017 +#define GH_RM_RPC_MSG_ID_CALL_VM_GET_HYP_RESOURCES 0x56000020 +#define GH_RM_RPC_MSG_ID_CALL_VM_LOOKUP_HYP_CAPIDS 0x56000021 +#define GH_RM_RPC_MSG_ID_CALL_VM_LOOKUP_HYP_IRQS 0X56000022 + +/* Message IDs: vRTC Configuration */ +#define GH_RM_RPC_MSG_ID_CALL_VM_SET_TIME_BASE 0x56000030 + +/* Message IDs: VM Configuration */ +#define GH_RM_RPC_MSG_ID_CALL_VM_IRQ_ACCEPT 0x56000050 +#define GH_RM_RPC_MSG_ID_CALL_VM_IRQ_LEND 0x56000051 +#define GH_RM_RPC_MSG_ID_CALL_VM_IRQ_RELEASE 0x56000052 +#define GH_RM_RPC_MSG_ID_CALL_VM_IRQ_RECLAIM 0x56000053 +#define GH_RM_RPC_MSG_ID_CALL_VM_IRQ_NOTIFY 0x56000054 +#define GH_RM_RPC_MSG_ID_CALL_VM_IRQ_UNMAP 0x56000055 + +/* Message IDs: VM Services */ +#define GH_RM_RPC_MSG_ID_CALL_VM_SET_STATUS 0x56000080 +#define GH_RM_RPC_MSG_ID_CALL_VM_CONSOLE_OPEN 0x56000081 +#define GH_RM_RPC_MSG_ID_CALL_VM_CONSOLE_CLOSE 0x56000082 +#define GH_RM_RPC_MSG_ID_CALL_VM_CONSOLE_WRITE 0x56000083 +#define GH_RM_RPC_MSG_ID_CALL_VM_CONSOLE_FLUSH 0x56000084 + +/* Message IDs: VM-Host Query */ +#define GH_RM_RPC_MSG_ID_CALL_VM_HOST_GET_TYPE 0x560000A0 + +/* End Call type Message IDs */ +/* End RPC Message IDs */ + +/* Call: VM_ALLOCATE */ +struct gh_vm_allocate_req_payload { + gh_vmid_t vmid; + u16 reserved; +} __packed; + +struct gh_vm_allocate_resp_payload { + u32 vmid; +} __packed; + +/* Call: VM_DEALLOCATE */ +struct gh_vm_deallocate_req_payload { + gh_vmid_t vmid; + u16 reserved; +} __packed; + +/* Call: VM_CONFIG_IMAGE */ +struct gh_vm_config_image_req_payload { + gh_vmid_t vmid; + u16 auth_mech; + u32 mem_handle; + u32 image_offset_low; + u32 image_offset_high; + u32 image_size_low; + u32 image_size_high; + u32 dtb_offset_low; + u32 dtb_offset_high; + u32 dtb_size_low; + u32 dtb_size_high; +} __packed; + +/* Call: VM_AUTH_IMAGE */ +struct gh_vm_auth_image_req_payload_hdr { + gh_vmid_t vmid; + u16 num_auth_params; +} __packed; + +/* Call: VM_INIT */ +struct gh_vm_init_req_payload { + gh_vmid_t vmid; + u16 reserved; +} __packed; + +/* Call: VM_START */ +struct gh_vm_start_req_payload { + gh_vmid_t vmid; + u16 reserved; +} __packed; + +struct gh_vm_start_resp_payload { + u32 response; +} __packed; + +/* Call: VM_STOP */ +struct gh_vm_stop_req_payload { + gh_vmid_t vmid; + u8 flags; + u8 reserved; + u32 stop_reason; +} __packed; + +/* Call: VM_RESET */ +struct gh_vm_reset_req_payload { + gh_vmid_t vmid; + u16 reserved; +} __packed; + +/* Call: VM_SET_STATUS */ +struct gh_vm_set_status_req_payload { + u8 vm_status; + u8 os_status; + u16 app_status; +} __packed; + +/* Call: VM_GET_STATE */ +struct gh_vm_get_state_req_payload { + gh_vmid_t vmid; + u16 reserved; +} __packed; + +struct gh_vm_get_state_resp_payload { + u8 vm_status; + u8 os_status; + u16 app_status; +} __packed; + +/* Call: CONSOLE_OPEN, CONSOLE_CLOSE, CONSOLE_FLUSH */ +struct gh_vm_console_common_req_payload { + gh_vmid_t vmid; + u16 reserved0; +} __packed; + +/* Call: CONSOLE_WRITE */ +struct gh_vm_console_write_req_payload { + gh_vmid_t vmid; + u16 num_bytes; + u8 data[0]; +} __packed; + +/* Call: GET_ID */ +#define GH_RM_ID_TYPE_GUID 0 +#define GH_RM_ID_TYPE_URI 1 +#define GH_RM_ID_TYPE_NAME 2 +#define GH_RM_ID_TYPE_SIGN_AUTH 3 + +struct gh_vm_get_id_req_payload { + gh_vmid_t vmid; + u16 reserved; +} __packed; + +struct gh_vm_get_id_resp_entry { + u8 id_type; + u8 reserved; + u16 id_size; + char id_info[]; +} __packed; + +struct gh_vm_get_id_resp_payload { + u32 n_id_entries; + struct gh_vm_get_id_resp_entry resp_entries[]; +} __packed; + +enum gh_vm_lookup_type { + GH_VM_LOOKUP_NAME, + GH_VM_LOOKUP_URI, + GH_VM_LOOKUP_GUID, +}; + +struct gh_vm_lookup_char_req_payload { + u16 size; + u16 reserved; + char data[]; +} __packed; + +struct gh_vm_lookup_resp_entry { + u16 vmid; + u16 reserved; +} __packed; + +struct gh_vm_lookup_resp_payload { + u32 n_id_entries; + struct gh_vm_lookup_resp_entry resp_entries[]; +} __packed; + +/* Message ID headers */ +/* Call: VM_GET_HYP_RESOURCES */ +#define GH_RM_RES_TYPE_DB_TX 0 +#define GH_RM_RES_TYPE_DB_RX 1 +#define GH_RM_RES_TYPE_MQ_TX 2 +#define GH_RM_RES_TYPE_MQ_RX 3 +#define GH_RM_RES_TYPE_VCPU 4 +#define GH_RM_RES_TYPE_VPMGRP 5 +#define GH_RM_RES_TYPE_VIRTIO_MMIO 6 +#define GH_RM_RES_TYPE_WATCHDOG 8 + +struct gh_vm_get_hyp_res_req_payload { + gh_vmid_t vmid; + u16 reserved; +} __packed; + +struct gh_vm_get_hyp_res_resp_entry { + u8 res_type; + u8 reserved; + gh_vmid_t partner_vmid; + u32 resource_handle; + u32 resource_label; + u32 cap_id_low; + u32 cap_id_high; + u32 virq_handle; + u32 virq; + u32 base_low; + u32 base_high; + u32 size_low; + u32 size_high; +} __packed; + +struct gh_vm_get_hyp_res_resp_payload { + u32 n_resource_entries; + struct gh_vm_get_hyp_res_resp_entry resp_entries[]; +} __packed; + +/* Call: VM_SET_TIME_BASE */ +struct gh_vm_set_time_base_req_payload { + gh_vmid_t vmid; + u8 reserved0; + u8 reserved1; + u32 time_base_low; + u32 time_base_high; + u32 arch_timer_ref_low; + u32 arch_timer_ref_high; +} __packed; + +/* Call: VM_IRQ_ACCEPT */ +struct gh_vm_irq_accept_req_payload { + gh_virq_handle_t virq_handle; + s32 virq; +} __packed; + +struct gh_vm_irq_accept_resp_payload { + s32 virq; +} __packed; + +/* Call: VM_IRQ_LEND */ +struct gh_vm_irq_lend_req_payload { + gh_vmid_t vmid; + u16 reserved; + s32 virq; + s32 label; +} __packed; + +struct gh_vm_irq_lend_resp_payload { + gh_virq_handle_t virq; +} __packed; + +/* Call: VM_IRQ_NOTIFY */ +#define GH_VM_IRQ_NOTIFY_FLAGS_LENT BIT(0) +#define GH_VM_IRQ_NOTIFY_FLAGS_RELEASED BIT(1) +#define GH_VM_IRQ_NOTIFY_FLAGS_ACCEPTED BIT(2) + +/* Call: VM_IRQ_RELEASE */ +struct gh_vm_irq_release_req_payload { + gh_virq_handle_t virq_handle; +} __packed; + +/* Call: VM_IRQ_RECLAIM */ +struct gh_vm_irq_reclaim_req_payload { + gh_virq_handle_t virq_handle; +} __packed; + +struct gh_vm_irq_notify_req_payload { + gh_virq_handle_t virq; + u8 flags; + u8 reserved0; + u16 reserved1; + struct __packed { + u16 num_vmids; + u16 reserved; + struct __packed { + gh_vmid_t vmid; + u16 reserved; + } vmids[0]; + } optional[0]; +} __packed; + +/* Call: MEM_QCOM_LOOKUP_SGL */ +/* + * Split up the whole payload into a header and several trailing structs + * to simplify allocation and treatment of packets with multiple flexible + * array members. + */ +struct gh_mem_qcom_lookup_sgl_req_payload_hdr { + u32 mem_type:8; + u32 reserved:24; + gh_label_t label; +} __packed; + +struct gh_mem_qcom_lookup_sgl_resp_payload { + gh_memparcel_handle_t memparcel_handle; +} __packed; + +/* Call: MEM_RELEASE/MEM_RECLAIM */ +struct gh_mem_release_req_payload { + gh_memparcel_handle_t memparcel_handle; + u32 flags:8; + u32 reserved:24; +} __packed; + +/* + * Call: MEM_ACCEPT + * + * Split up the whole payload into a header and several trailing structs + * to simplify allocation and treatment of packets with multiple flexible + * array members. + */ +struct gh_mem_accept_req_payload_hdr { + gh_memparcel_handle_t memparcel_handle; + u8 mem_type; + u8 trans_type; + u8 flags; + u8 reserved1; + u32 validate_label; +} __packed; + +struct gh_mem_accept_resp_payload { + u16 n_sgl_entries; + u16 reserved; +} __packed; + +/* + * Call: MEM_LEND/MEM_SHARE + * + * Split up the whole payload into a header and several trailing structs + * to simplify allocation and treatment of packets with multiple flexible + * array members. + */ +struct gh_mem_share_req_payload_hdr { + u8 mem_type; + u8 reserved1; + u8 flags; + u8 reserved2; + u32 label; +} __packed; + +struct gh_mem_share_resp_payload { + gh_memparcel_handle_t memparcel_handle; +} __packed; + +/* + * Call: MEM_APPEND + * + * Split up the whole payload into a header and several trailing structs + * to simplify allocation and treatment of packets with multiple flexible + * array members. + */ +struct gh_mem_append_req_payload_hdr { + gh_memparcel_handle_t memparcel_handle; + u32 flags:8; + u32 reserved:24; +} __packed; + +/* Call: MEM_NOTIFY */ +struct gh_mem_notify_req_payload { + gh_memparcel_handle_t memparcel_handle; + u32 flags:8; + u32 reserved1:24; + gh_label_t mem_info_tag; +} __packed; + +/* End Message ID headers */ + +/* Common function declerations */ +void gh_init_vm_prop_table(void); +int gh_update_vm_prop_table(enum gh_vm_names vm_name, + struct gh_vm_property *vm_prop); +void *gh_rm_call(gh_rm_msgid_t message_id, + void *req_buff, size_t req_buff_size, + size_t *resp_buff_size, int *reply_err_code); +struct gh_vm_get_id_resp_entry * +gh_rm_vm_get_id(gh_vmid_t vmid, u32 *out_n_entries); +int gh_rm_vm_lookup(enum gh_vm_lookup_type type, const void *name, + size_t size, gh_vmid_t *vmid); +struct gh_vm_get_hyp_res_resp_entry * +gh_rm_vm_get_hyp_res(gh_vmid_t vmid, u32 *out_n_entries); +int gh_msgq_populate_cap_info(int label, u64 cap_id, int direction, int irq); +#endif /* __GH_RM_DRV_PRIVATE_H */ diff --git a/drivers/virt/gunyah/gh_rm_iface.c b/drivers/virt/gunyah/gh_rm_iface.c new file mode 100644 index 000000000000..ff4fce8e7086 --- /dev/null +++ b/drivers/virt/gunyah/gh_rm_iface.c @@ -0,0 +1,2283 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2020-2021, The Linux Foundation. All rights reserved. + * + */ + +#include +#include +#include + +#include +#include +#include + +#include "gh_rm_drv_private.h" + +#define GH_RM_MEM_RELEASE_VALID_FLAGS GH_RM_MEM_RELEASE_CLEAR +#define GH_RM_MEM_RECLAIM_VALID_FLAGS GH_RM_MEM_RECLAIM_CLEAR +#define GH_RM_MEM_ACCEPT_VALID_FLAGS\ + (GH_RM_MEM_ACCEPT_VALIDATE_SANITIZED |\ + GH_RM_MEM_ACCEPT_VALIDATE_ACL_ATTRS |\ + GH_RM_MEM_ACCEPT_VALIDATE_LABEL |\ + GH_RM_MEM_ACCEPT_MAP_IPA_CONTIGUOUS |\ + GH_RM_MEM_ACCEPT_DONE) +#define GH_RM_MEM_SHARE_VALID_FLAGS GH_RM_MEM_SHARE_SANITIZE +#define GH_RM_MEM_LEND_VALID_FLAGS GH_RM_MEM_LEND_SANITIZE +#define GH_RM_MEM_DONATE_VALID_FLAGS GH_RM_MEM_DONATE_SANITIZE +#define GH_RM_MEM_NOTIFY_VALID_FLAGS\ + (GH_RM_MEM_NOTIFY_RECIPIENT_SHARED |\ + GH_RM_MEM_NOTIFY_OWNER_RELEASED | GH_RM_MEM_NOTIFY_OWNER_ACCEPTED) + +#define GH_RM_MEM_APPEND_VALID_FLAGS GH_RM_MEM_APPEND_END + +/* Maximum number of sgl entries supported by lend/share/donate/append/notify calls */ +#define GH_RM_MEM_MAX_SGL_ENTRIES 512 + +static DEFINE_SPINLOCK(gh_vm_table_lock); +static struct gh_vm_property gh_vm_table[GH_VM_MAX]; + +void gh_init_vm_prop_table(void) +{ + size_t vm_name; + + spin_lock(&gh_vm_table_lock); + + gh_vm_table[GH_SELF_VM].vmid = 0; + + for (vm_name = GH_SELF_VM + 1; vm_name < GH_VM_MAX; vm_name++) { + gh_vm_table[vm_name].vmid = GH_VMID_INVAL; + gh_vm_table[vm_name].guid = NULL; + gh_vm_table[vm_name].uri = NULL; + gh_vm_table[vm_name].name = NULL; + gh_vm_table[vm_name].sign_auth = NULL; + } + + spin_unlock(&gh_vm_table_lock); +} + +int gh_update_vm_prop_table(enum gh_vm_names vm_name, + struct gh_vm_property *vm_prop) +{ + if (!vm_prop) + return -EINVAL; + + if (vm_prop->vmid < 0 || vm_name < GH_SELF_VM || vm_name > GH_VM_MAX) + return -EINVAL; + + spin_lock(&gh_vm_table_lock); + if (gh_vm_table[vm_name].guid || gh_vm_table[vm_name].uri || + gh_vm_table[vm_name].name || gh_vm_table[vm_name].sign_auth) { + spin_unlock(&gh_vm_table_lock); + return -EEXIST; + } + if (vm_prop->vmid) + gh_vm_table[vm_name].vmid = vm_prop->vmid; + + if (vm_prop->guid) + gh_vm_table[vm_name].guid = vm_prop->guid; + + if (vm_prop->uri) + gh_vm_table[vm_name].uri = vm_prop->uri; + + if (vm_prop->name) + gh_vm_table[vm_name].name = vm_prop->name; + + if (vm_prop->sign_auth) + gh_vm_table[vm_name].sign_auth = vm_prop->sign_auth; + spin_unlock(&gh_vm_table_lock); + + return 0; +} + +void gh_reset_vm_prop_table_entry(gh_vmid_t vmid) +{ + size_t vm_name; + + spin_lock(&gh_vm_table_lock); + + for (vm_name = GH_SELF_VM + 1; vm_name < GH_VM_MAX; vm_name++) { + if (vmid == gh_vm_table[vm_name].vmid) { + gh_vm_table[vm_name].vmid = GH_VMID_INVAL; + gh_vm_table[vm_name].uri = NULL; + gh_vm_table[vm_name].guid = NULL; + gh_vm_table[vm_name].name = NULL; + gh_vm_table[vm_name].sign_auth = NULL; + break; + } + } + + spin_unlock(&gh_vm_table_lock); +} + +/** + * gh_rm_get_vmid: Translate VM name to vmid + * @vm_name: VM name to lookup + * @vmid: out pointer to store found vmid if VM is ofund + * + * If gh_rm_core has not yet probed, returns -EPROBE_DEFER. + * If no VM is known to RM with the supplied name, returns -EINVAL. + * Returns 0 on success. + */ +int gh_rm_get_vmid(enum gh_vm_names vm_name, gh_vmid_t *vmid) +{ + gh_vmid_t _vmid; + int ret = 0; + + if (vm_name < GH_SELF_VM || vm_name > GH_VM_MAX) + return -EINVAL; + + + spin_lock(&gh_vm_table_lock); + + _vmid = gh_vm_table[vm_name].vmid; + if (!gh_rm_core_initialized) { + ret = -EPROBE_DEFER; + goto out; + } + + if (!_vmid && vm_name != GH_SELF_VM) { + ret = -EINVAL; + goto out; + } + + if (vmid) + *vmid = _vmid; + +out: + spin_unlock(&gh_vm_table_lock); + return ret; +} +EXPORT_SYMBOL(gh_rm_get_vmid); + +/** + * gh_rm_get_vm_name: Translate vmid to vm name + * @vmid: vmid to lookup + * @vm_name: out pointer to store found VM name if vmid is found + * + * If no VM is known to RM with the supplied VMID, -EINVAL is returned. + * 0 otherwise. + */ +int gh_rm_get_vm_name(gh_vmid_t vmid, enum gh_vm_names *vm_name) +{ + enum gh_vm_names i; + + spin_lock(&gh_vm_table_lock); + + for (i = 0; i < GH_VM_MAX; i++) + if (gh_vm_table[i].vmid == vmid) { + if (vm_name) + *vm_name = i; + spin_unlock(&gh_vm_table_lock); + return 0; + } + + spin_unlock(&gh_vm_table_lock); + + return -EINVAL; +} +EXPORT_SYMBOL(gh_rm_get_vm_name); + +/** + * gh_rm_get_vminfo: Obtain Vm related info with vm name + * @vm_name: VM name to lookup + * @vm: out pointer to store id information about VM + * + * If no VM is known to RM with the supplied name, -EINVAL is returned. + * 0 otherwise. + */ +int gh_rm_get_vminfo(enum gh_vm_names vm_name, struct gh_vminfo *vm) +{ + if (!vm) + return -EINVAL; + + spin_lock(&gh_vm_table_lock); + if (vm_name < GH_SELF_VM || vm_name > GH_VM_MAX) { + spin_unlock(&gh_vm_table_lock); + return -EINVAL; + } + + vm->guid = gh_vm_table[vm_name].guid; + vm->uri = gh_vm_table[vm_name].uri; + vm->name = gh_vm_table[vm_name].name; + vm->sign_auth = gh_vm_table[vm_name].sign_auth; + + spin_unlock(&gh_vm_table_lock); + + return 0; +} +EXPORT_SYMBOL(gh_rm_get_vminfo); + +/** + * gh_rm_vm_get_id: Get identification info about a 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 gh_vm_get_id_resp_entry', + * in which each entry specifies identification info about the vm. 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 gh_vm_get_id_resp_entry * +gh_rm_vm_get_id(gh_vmid_t vmid, u32 *n_entries) +{ + struct gh_vm_get_id_resp_payload *resp_payload; + struct gh_vm_get_id_req_payload req_payload = { + .vmid = vmid + }; + struct gh_vm_get_id_resp_entry *resp_entries, *temp_entry; + size_t resp_payload_size, resp_entries_size = 0; + int err, reply_err_code, i; + + if (!n_entries) + return ERR_PTR(-EINVAL); + + resp_payload = gh_rm_call(GH_RM_RPC_MSG_ID_CALL_VM_GET_ID, + &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_ID failed with err: %d\n", + __func__, err); + return ERR_PTR(err); + } + + /* The response payload should contain all the resource entries */ + temp_entry = resp_payload->resp_entries; + for (i = 0; i < resp_payload->n_id_entries; i++) { + resp_entries_size += + sizeof(*temp_entry) + round_up(temp_entry->id_size, 4); + temp_entry = (void *)temp_entry + sizeof(*temp_entry) + + round_up(temp_entry->id_size, 4); + } + if (resp_entries_size != resp_payload_size - sizeof(*n_entries)) { + pr_err("%s: Invalid size received for GET_ID: %u expect %u\n", + __func__, resp_payload_size, resp_entries_size); + resp_entries = ERR_PTR(-EINVAL); + goto out; + } + 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_id_entries; + +out: + kfree(resp_payload); + return resp_entries; +} + +static int gh_rm_vm_lookup_name_uri(gh_rm_msgid_t msg_id, const char *data, + size_t size, gh_vmid_t *vmid) +{ + struct gh_vm_lookup_resp_payload *resp_payload; + struct gh_vm_lookup_char_req_payload *req_payload; + size_t resp_payload_size, req_payload_size; + int reply_err_code; + int ret = 0; + + if (!data || !vmid) + return -EINVAL; + + req_payload_size = sizeof(*req_payload) + round_up(size, 4); + req_payload = kzalloc(req_payload_size, GFP_KERNEL); + + if (!req_payload) + return -ENOMEM; + + req_payload->size = size; + memcpy(req_payload->data, data, size); + + resp_payload = gh_rm_call(msg_id, req_payload, req_payload_size, + &resp_payload_size, &reply_err_code); + + if (reply_err_code || IS_ERR_OR_NULL(resp_payload)) { + ret = PTR_ERR(resp_payload); + pr_err("%s: lookup name/uri failed with err: %d\n", __func__, (int)ret); + goto out; + } + + if (resp_payload->n_id_entries == 1) { + *vmid = resp_payload->resp_entries->vmid; + } else if (resp_payload->n_id_entries == 0) { + pr_err("%s: No VMID found from lookup %s\n", __func__, data); + ret = -EINVAL; + } else { + pr_err("%s: More than one VMID received from lookup %s\n", + __func__, data); + ret = -EINVAL; + } + + kfree(resp_payload); +out: + kfree(req_payload); + return ret; +} + +static int gh_rm_vm_lookup_guid(const u8 *data, gh_vmid_t *vmid) +{ + struct gh_vm_lookup_resp_payload *resp_payload; + size_t resp_payload_size; + int reply_err_code; + int ret = 0; + + if (!data || !vmid) + return -EINVAL; + + resp_payload = + gh_rm_call(GH_RM_RPC_MSG_ID_CALL_VM_LOOKUP_GUID, (void *)data, + 16, &resp_payload_size, &reply_err_code); + + if (reply_err_code || IS_ERR_OR_NULL(resp_payload)) { + ret = PTR_ERR(resp_payload); + pr_err("%s: lookup guid failed with err: %d\n", __func__, (int)ret); + return ret; + } + + if (resp_payload->n_id_entries == 1) { + *vmid = resp_payload->resp_entries->vmid; + } else if (resp_payload->n_id_entries == 0) { + pr_err("%s: No VMID found from lookup %pUB\n", __func__, data); + ret = -EINVAL; + } else { + pr_err("%s: More than one VMID received from lookup: %pUB\n", + __func__, data); + ret = -EINVAL; + } + + kfree(resp_payload); + return ret; +} + +/** + * gh_rm_vm_lookup: Get vmid from name + * @type: which type of property need to lookup + * @data: name/uri/guid whose vmid is needed + * @size: data size + * @vmid: vmid return to caller + * + */ +int gh_rm_vm_lookup(enum gh_vm_lookup_type type, const void *data, size_t size, + gh_vmid_t *vmid) +{ + int ret = 0; + + switch (type) { + case GH_VM_LOOKUP_NAME: + ret = gh_rm_vm_lookup_name_uri( + GH_RM_RPC_MSG_ID_CALL_VM_LOOKUP_NAME, + (const char *)data, size, vmid); + break; + case GH_VM_LOOKUP_URI: + ret = gh_rm_vm_lookup_name_uri( + GH_RM_RPC_MSG_ID_CALL_VM_LOOKUP_URI, (const char *)data, + size, vmid); + break; + case GH_VM_LOOKUP_GUID: + if (size != 16) { + pr_err("Invalid GUID size=%d\n", size); + ret = -EINVAL; + } else + ret = gh_rm_vm_lookup_guid((const u8 *)data, vmid); + break; + default: + pr_err("Invalid lookup type=%d\n", type); + break; + } + + return ret; +} + +/** + * gh_rm_vm_get_status: Get the status of a particular VM + * @vmid: The vmid of tehe VM. Pass 0 for self. + * + * The function returns a pointer to gh_vm_status containing + * the status of the VM for the requested vmid. The caller + * must kfree the memory when done reading the contents. + * + * The function encodes the error codes via ERR_PTR. Hence, the + * caller is responsible to check it with IS_ERR_OR_NULL(). + */ +struct gh_vm_status *gh_rm_vm_get_status(gh_vmid_t vmid) +{ + struct gh_vm_get_state_req_payload req_payload = { + .vmid = vmid, + }; + struct gh_vm_get_state_resp_payload *resp_payload; + struct gh_vm_status *gh_vm_status; + int err, reply_err_code = 0; + size_t resp_payload_size; + + resp_payload = gh_rm_call(GH_RM_RPC_MSG_ID_CALL_VM_GET_STATE, + &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: Failed to call VM_GET_STATE: %d\n", + __func__, err); + if (resp_payload) { + gh_vm_status = ERR_PTR(err); + goto out; + } + return ERR_PTR(err); + } + + if (resp_payload_size != sizeof(*resp_payload)) { + pr_err("%s: Invalid size received for VM_GET_STATE: %u\n", + __func__, resp_payload_size); + gh_vm_status = ERR_PTR(-EINVAL); + goto out; + } + + gh_vm_status = kmemdup(resp_payload, resp_payload_size, GFP_KERNEL); + if (!gh_vm_status) + gh_vm_status = ERR_PTR(-ENOMEM); + +out: + kfree(resp_payload); + return gh_vm_status; +} +EXPORT_SYMBOL(gh_rm_vm_get_status); + +/** + * gh_rm_vm_set_status: Set the status of this VM + * @gh_vm_status: The status to set + * + * The function sets this VM's status as per gh_vm_status. + * It returns 0 upon success and a negative error code + * upon failure. + */ +int gh_rm_vm_set_status(struct gh_vm_status gh_vm_status) +{ + struct gh_vm_set_status_req_payload req_payload = { + .vm_status = gh_vm_status.vm_status, + .os_status = gh_vm_status.os_status, + .app_status = gh_vm_status.app_status, + }; + size_t resp_payload_size; + int reply_err_code = 0; + void *resp; + + resp = gh_rm_call(GH_RM_RPC_MSG_ID_CALL_VM_SET_STATUS, + &req_payload, sizeof(req_payload), + &resp_payload_size, &reply_err_code); + if (IS_ERR(resp)) { + pr_err("%s: Failed to call VM_SET_STATUS: %d\n", + __func__, PTR_ERR(resp)); + return PTR_ERR(resp); + } + + if (reply_err_code) { + pr_err("%s: VM_SET_STATUS returned error: %d\n", + __func__, reply_err_code); + return reply_err_code; + } + + if (resp_payload_size) { + pr_err("%s: Invalid size received for VM_SET_STATUS: %u\n", + __func__, resp_payload_size); + return -EINVAL; + } + + return 0; +} +EXPORT_SYMBOL(gh_rm_vm_set_status); + +/** + * gh_rm_vm_set_vm_status: Set the vm status + * @vm_status: The vm_status to set + * + * The function returns 0 on success and a negative + * error code upon failure. + */ +int gh_rm_vm_set_vm_status(u8 vm_status) +{ + int ret = 0; + struct gh_vm_status *gh_vm_status; + + gh_vm_status = gh_rm_vm_get_status(0); + if (IS_ERR_OR_NULL(gh_vm_status)) + return PTR_ERR(gh_vm_status); + + gh_vm_status->vm_status = vm_status; + ret = gh_rm_vm_set_status(*gh_vm_status); + + kfree(gh_vm_status); + + return ret; +} +EXPORT_SYMBOL(gh_rm_vm_set_vm_status); + +/** + * gh_rm_vm_set_os_status: Set the OS status. Once the VM starts booting, + * there are various stages during the boot up that status of the + * Operating System can be set like early_boot, boot, init, run etc. + * @os_status: The os_status to set + * + * The function returns 0 on success and a negative + * error code upon failure. + */ +int gh_rm_vm_set_os_status(u8 os_status) +{ + int ret = 0; + struct gh_vm_status *gh_vm_status; + + gh_vm_status = gh_rm_vm_get_status(0); + if (IS_ERR_OR_NULL(gh_vm_status)) + return PTR_ERR(gh_vm_status); + + gh_vm_status->os_status = os_status; + ret = gh_rm_vm_set_status(*gh_vm_status); + + kfree(gh_vm_status); + + return ret; +} +EXPORT_SYMBOL(gh_rm_vm_set_os_status); + +/** + * gh_rm_vm_set_app_status: Set the app status + * @app_status: The app_status to set + * + * The function returns 0 on success and a negative + * error code upon failure. + */ +int gh_rm_vm_set_app_status(u16 app_status) +{ + int ret = 0; + struct gh_vm_status *gh_vm_status; + + gh_vm_status = gh_rm_vm_get_status(0); + if (IS_ERR_OR_NULL(gh_vm_status)) + return PTR_ERR(gh_vm_status); + + gh_vm_status->app_status = app_status; + ret = gh_rm_vm_set_status(*gh_vm_status); + + kfree(gh_vm_status); + + return ret; +} +EXPORT_SYMBOL(gh_rm_vm_set_app_status); + +/** + * gh_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 gh_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 gh_vm_get_hyp_res_resp_entry * +gh_rm_vm_get_hyp_res(gh_vmid_t vmid, u32 *n_entries) +{ + struct gh_vm_get_hyp_res_resp_payload *resp_payload; + struct gh_vm_get_hyp_res_req_payload req_payload = { + .vmid = vmid + }; + struct gh_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 = gh_rm_call(GH_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) || + (sizeof(*resp_entries) && + (resp_payload->n_resource_entries > U32_MAX / sizeof(*resp_entries))) || + (sizeof(*n_entries) > (U32_MAX - + (resp_payload->n_resource_entries * sizeof(*resp_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; +} + +/** + * gh_rm_vm_irq_notify: Notify an IRQ to another VM + * @vmids: VMs to notify the handle about + * @num_vmids: number of VMs to notify the handle about + * @flags: notification reason + * @virq_handle: Response handle which RM will accept from the other VM to take + * the lent interrupt + */ +static int gh_rm_vm_irq_notify(const gh_vmid_t *vmids, unsigned int num_vmids, + u16 flags, gh_virq_handle_t virq_handle) +{ + void *resp; + struct gh_vm_irq_notify_req_payload *req_payload; + size_t resp_payload_size, req_payload_size; + int ret = 0, reply_err_code; + unsigned int i; + + + if (!(flags & GH_VM_IRQ_NOTIFY_FLAGS_LENT) && num_vmids) + return -EINVAL; + + if (num_vmids > U16_MAX) + return -EINVAL; + + req_payload_size = sizeof(*req_payload); + if (flags & GH_VM_IRQ_NOTIFY_FLAGS_LENT) + req_payload_size += sizeof(*(req_payload->optional)) + + (sizeof(req_payload->optional->vmids[0]) * num_vmids); + req_payload = kzalloc(req_payload_size, GFP_KERNEL); + + if (!req_payload) + return -ENOMEM; + + req_payload->virq = virq_handle; + req_payload->flags = flags; + if (flags & GH_VM_IRQ_NOTIFY_FLAGS_LENT) { + req_payload->optional[0].num_vmids = num_vmids; + for (i = 0; i < num_vmids; i++) + req_payload->optional[0].vmids[i].vmid = vmids[i]; + } + + + resp = gh_rm_call(GH_RM_RPC_MSG_ID_CALL_VM_IRQ_NOTIFY, + req_payload, req_payload_size, + &resp_payload_size, &reply_err_code); + kfree(req_payload); + if (IS_ERR(resp)) { + pr_err("%s: Unable to send IRQ_NOTIFY to RM: %d\n", __func__, + PTR_ERR(resp)); + return PTR_ERR(resp); + } + + if (reply_err_code) { + pr_err("%s: IRQ_NOTIFY returned error: %d\n", __func__, + reply_err_code); + return reply_err_code; + } + + if (resp_payload_size) { + pr_err("%s: Invalid size received for IRQ_NOTIFY: %u\n", + __func__, resp_payload_size); + ret = -EINVAL; + } + + return ret; +} + +/** + * gh_rm_vm_irq_lend: Lend an IRQ to another VM + * @vmid: VM to lend the interrupt to + * @virq: Virtual IRQ number to lend + * @label: Label to give to VM so it may know how to associate the interrupt + * @virq_handle: Response handle which RM will accept from the other VM to take + * the lent interrupt + */ +int gh_rm_vm_irq_lend(gh_vmid_t vmid, int virq, int label, + gh_virq_handle_t *virq_handle) +{ + struct gh_vm_irq_lend_resp_payload *resp_payload; + struct gh_vm_irq_lend_req_payload req_payload = {0}; + size_t resp_payload_size; + int ret = 0, reply_err_code; + + req_payload.vmid = vmid; + req_payload.virq = virq; + req_payload.label = label; + + resp_payload = gh_rm_call(GH_RM_RPC_MSG_ID_CALL_VM_IRQ_LEND, + &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_LEND failed with err: %d\n", + __func__, ret); + return ret; + } + + if (resp_payload_size != sizeof(*resp_payload)) { + pr_err("%s: Invalid size received for VM_IRQ_LEND: %u\n", + __func__, resp_payload_size); + ret = -EINVAL; + goto out; + } + + if (virq_handle) + *virq_handle = resp_payload->virq; +out: + kfree(resp_payload); + return ret; +} +EXPORT_SYMBOL(gh_rm_vm_irq_lend); + +/** + * gh_rm_vm_irq_lend_notify: Lend an IRQ to a VM and notify the VM about it + * @vmid: VM to lend interrupt to + * @virq: Virtual IRQ number to lend + * @label: Label to give to VM so it may know how to associate the interrupt + * @virq_handle: vIRQ handle generated by hypervisor to reperesent the interrupt + * which can be used later to know when the interrupt has been + * released + * + * This function performs interrupt sharing flow for "HLOS" described in + * Resource Manager High Level Design Sec. 3.3.3. + */ +int gh_rm_vm_irq_lend_notify(gh_vmid_t vmid, gh_virq_handle_t virq_handle) +{ + return gh_rm_vm_irq_notify(&vmid, 1, GH_VM_IRQ_NOTIFY_FLAGS_LENT, + virq_handle); +} +EXPORT_SYMBOL(gh_rm_vm_irq_lend_notify); + +/** + * gh_rm_vm_irq_release: Return a lent IRQ + * @virq_handle: IRQ handle to be released + */ +int gh_rm_vm_irq_release(gh_virq_handle_t virq_handle) +{ + struct gh_vm_irq_release_req_payload req_payload = {0}; + void *resp; + int ret = 0, reply_err_code; + size_t resp_payload_size; + + req_payload.virq_handle = virq_handle; + + resp = gh_rm_call(GH_RM_RPC_MSG_ID_CALL_VM_IRQ_RELEASE, + &req_payload, sizeof(req_payload), + &resp_payload_size, &reply_err_code); + + if (IS_ERR(resp)) { + pr_err("%s: Unable to send IRQ_RELEASE to RM: %d\n", __func__, + PTR_ERR(resp)); + return PTR_ERR(resp); + } + + if (reply_err_code) { + pr_err("%s: IRQ_RELEASE returned error: %d\n", __func__, + reply_err_code); + return reply_err_code; + } + + if (resp_payload_size) { + pr_err("%s: Invalid size received for IRQ_RELEASE: %u\n", + __func__, resp_payload_size); + ret = -EINVAL; + } + + return ret; +} +EXPORT_SYMBOL(gh_rm_vm_irq_release); + +/** + * gh_rm_vm_irq_release_notify: Release IRQ back to a VM and notify that it has + * been released. + * @vmid: VM to release interrupt to + * @virq_handle: Virtual IRQ handle to release + */ +int gh_rm_vm_irq_release_notify(gh_vmid_t vmid, gh_virq_handle_t virq_handle) +{ + return gh_rm_vm_irq_notify(NULL, 0, GH_VM_IRQ_NOTIFY_FLAGS_RELEASED, + virq_handle); +} +EXPORT_SYMBOL(gh_rm_vm_irq_release_notify); + +/** + * gh_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 gh_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 gh_rm_vm_irq_accept(gh_virq_handle_t virq_handle, int virq) +{ + struct gh_vm_irq_accept_resp_payload *resp_payload; + struct gh_vm_irq_accept_req_payload req_payload = {0}; + 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 = gh_rm_call(GH_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; +} +EXPORT_SYMBOL(gh_rm_vm_irq_accept); + +/** + * gh_rm_vm_irq_release_notify: Release IRQ back to a VM and notify that it has + * been released. + * @vmid: VM to release interrupt to + * @virq_handle: Virtual IRQ handle to release + */ +int gh_rm_vm_irq_accept_notify(gh_vmid_t vmid, gh_virq_handle_t virq_handle) +{ + return gh_rm_vm_irq_notify(NULL, 0, GH_VM_IRQ_NOTIFY_FLAGS_ACCEPTED, + virq_handle); +} +EXPORT_SYMBOL(gh_rm_vm_irq_accept_notify); + +/** + * gh_rm_vm_irq_reclaim: Return a lent IRQ + * @virq_handle: IRQ handle to be reclaimed + */ +int gh_rm_vm_irq_reclaim(gh_virq_handle_t virq_handle) +{ + struct gh_vm_irq_reclaim_req_payload req_payload = {0}; + void *resp; + int ret = 0, reply_err_code; + size_t resp_payload_size; + + req_payload.virq_handle = virq_handle; + + resp = gh_rm_call(GH_RM_RPC_MSG_ID_CALL_VM_IRQ_RECLAIM, + &req_payload, sizeof(req_payload), + &resp_payload_size, &reply_err_code); + + if (IS_ERR(resp)) { + pr_err("%s: Unable to send IRQ_RELEASE to RM: %d\n", __func__, + PTR_ERR(resp)); + return PTR_ERR(resp); + } + + if (reply_err_code) { + pr_err("%s: IRQ_RELEASE returned error: %d\n", __func__, + reply_err_code); + return reply_err_code; + } + + if (resp_payload_size) { + pr_err("%s: Invalid size received for IRQ_RELEASE: %u\n", + __func__, resp_payload_size); + ret = -EINVAL; + } + + return ret; +} +EXPORT_SYMBOL(gh_rm_vm_irq_reclaim); + +/** + * gh_rm_vm_alloc_vmid: Return a vmid associated with the vm loaded into + * memory. This call should be called only during + initialization. + * @vm_name: The enum value of the vm that has been loaded. + * @vmid: Value of vmid read from DT. If not present in DT using 0. + * + * The function encodes the error codes via ERR_PTR. Hence, the caller is + * responsible to check it with IS_ERR_OR_NULL(). + */ +int gh_rm_vm_alloc_vmid(enum gh_vm_names vm_name, int *vmid) +{ + struct gh_vm_allocate_resp_payload *resp_payload; + struct gh_vm_allocate_req_payload req_payload = {0}; + size_t resp_payload_size; + struct gh_vm_property vm_prop = {0}; + int err, reply_err_code; + + /* Look up for the vm_name<->vmid pair if already present. + * If so, return. + */ + if (vm_name < GH_SELF_VM || vm_name > GH_VM_MAX) + return -EINVAL; + + spin_lock(&gh_vm_table_lock); + if (gh_vm_table[vm_name].vmid != GH_VMID_INVAL || + vm_name == GH_SELF_VM) { + pr_err("%s: VM_ALLOCATE already called for this VM\n", + __func__); + spin_unlock(&gh_vm_table_lock); + return -EINVAL; + } + spin_unlock(&gh_vm_table_lock); + + req_payload.vmid = *vmid; + + resp_payload = gh_rm_call(GH_RM_RPC_MSG_ID_CALL_VM_ALLOCATE, + &req_payload, sizeof(req_payload), + &resp_payload_size, &reply_err_code); + if (reply_err_code || IS_ERR(resp_payload)) { + err = PTR_ERR(resp_payload); + pr_err("%s: VM_ALLOCATE failed with err: %d\n", + __func__, err); + return err; + } + + if (resp_payload && + (resp_payload_size != sizeof(*resp_payload))) { + pr_err("%s: Invalid size received for VM_ALLOCATE: %u\n", + __func__, resp_payload_size); + kfree(resp_payload); + return -EINVAL; + } + + if (resp_payload) + *vmid = resp_payload->vmid; + + vm_prop.vmid = *vmid; + err = gh_update_vm_prop_table(vm_name, &vm_prop); + + if (err) { + pr_err("%s: Invalid vmid sent for updating table: %d\n", + __func__, vm_prop.vmid); + return -EINVAL; + } + + kfree(resp_payload); + return 0; +} +EXPORT_SYMBOL(gh_rm_vm_alloc_vmid); + +/** + * gh_rm_vm_dealloc_vmid: Deallocate an already allocated vmid + * @vmid: The vmid to deallocate. + * + * The function returns 0 on success and a negative error code + * upon failure. + */ +int gh_rm_vm_dealloc_vmid(gh_vmid_t vmid) +{ + struct gh_vm_deallocate_req_payload req_payload = { + .vmid = vmid, + }; + size_t resp_payload_size; + int err, reply_err_code; + void *resp; + + resp = gh_rm_call(GH_RM_RPC_MSG_ID_CALL_VM_DEALLOCATE, + &req_payload, sizeof(req_payload), + &resp_payload_size, &reply_err_code); + if (reply_err_code || IS_ERR(resp)) { + err = reply_err_code; + pr_err("%s: VM_DEALLOCATE failed with err: %d\n", + __func__, err); + return err; + } + + if (resp_payload_size) { + pr_err("%s: Invalid size received for VM_DEALLOCATE: %u\n", + __func__, resp_payload_size); + kfree(resp); + return -EINVAL; + } + + gh_reset_vm_prop_table_entry(vmid); + + return 0; +} +EXPORT_SYMBOL(gh_rm_vm_dealloc_vmid); + +/** + * gh_rm_vm_config_image: Configure the VM properties + * @vmid: The vmid of VM configure. + * @auth_mech: The kind of authentication mechanism based on VM image + * @mem_handle: The handle to the memory lent/donated + * @image_offset: Start addr of image relative to memparcel + * @image_size: Size of image relative to start addr + * @dtb_offset: Base addr of dtb image relative to memparcel + * @dtb_size: Size of dtb relative to memparcel + * + * The function returns 0 on success and a negative error code + * upon failure. + */ +int gh_rm_vm_config_image(gh_vmid_t vmid, u16 auth_mech, u32 mem_handle, + u64 image_offset, u64 image_size, u64 dtb_offset, u64 dtb_size) +{ + struct gh_vm_config_image_req_payload req_payload = { + .vmid = vmid, + .auth_mech = auth_mech, + .mem_handle = mem_handle, + .image_offset_low = image_offset, + .image_offset_high = image_offset >> 32, + .image_size_low = image_size, + .image_size_high = image_size >> 32, + .dtb_offset_low = dtb_offset, + .dtb_offset_high = dtb_offset >> 32, + .dtb_size_low = dtb_size, + .dtb_size_high = dtb_size >> 32, + }; + size_t resp_payload_size; + int err, reply_err_code = 0; + void *resp; + + resp = gh_rm_call(GH_RM_RPC_MSG_ID_CALL_VM_CONFIG_IMAGE, + &req_payload, sizeof(req_payload), + &resp_payload_size, &reply_err_code); + + if (IS_ERR(resp)) { + pr_err("%s: Unable to send VM_CONFIG_IMAGE to RM: %d\n", __func__, + PTR_ERR(resp)); + return PTR_ERR(resp); + } + + if (reply_err_code) { + err = reply_err_code; + pr_err("%s: VM_CONFIG_IMAGE failed with err: %d\n", + __func__, err); + return err; + } + + if (resp_payload_size) { + pr_err("%s: Invalid size received for VM_CONFIG_IMAGE: %u\n", + __func__, resp_payload_size); + kfree(resp); + return -EINVAL; + } + + return 0; +} +EXPORT_SYMBOL(gh_rm_vm_config_image); + +/** + * gh_rm_vm_auth_image: Request to authenticate the VM + * @vmid: The vmid of VM to authenticate. + * @n_entries: NUmber of auth_param entries + * @entry: Pointer to gh_vm_auth_param_entry structures + * + * The function returns 0 on success and a negative error code + * upon failure. + */ +int gh_rm_vm_auth_image(gh_vmid_t vmid, ssize_t n_entries, + struct gh_vm_auth_param_entry *entry) +{ + struct gh_vm_auth_image_req_payload_hdr *req_payload; + struct gh_vm_auth_param_entry *dest_entry; + size_t resp_payload_size; + size_t req_payload_size; + int err, reply_err_code = 0, n_entry; + void *req_buf; + void *resp; + + req_payload_size = sizeof(*req_payload) + n_entries*sizeof(*entry); + + req_buf = kzalloc(req_payload_size, GFP_KERNEL); + if (!req_buf) + return -ENOMEM; + + req_payload = req_buf; + req_payload->vmid = vmid; + req_payload->num_auth_params = n_entries; + + dest_entry = req_buf + sizeof(*req_payload); + for (n_entry = 0; n_entry < n_entries; n_entry++) { + dest_entry[n_entry].auth_param_type = entry[n_entry].auth_param_type; + dest_entry[n_entry].auth_param = entry[n_entry].auth_param; + } + + resp = gh_rm_call(GH_RM_RPC_MSG_ID_CALL_VM_AUTH_IMAGE, + req_buf, req_payload_size, + &resp_payload_size, &reply_err_code); + + if (IS_ERR(resp)) { + pr_err("%s: Unable to send VM_AUTH_IMAGE to RM: %d\n", __func__, + PTR_ERR(resp)); + return PTR_ERR(resp); + } + + if (reply_err_code) { + err = reply_err_code; + pr_err("%s: VM_AUTH_IMAGE failed with err: %d\n", + __func__, err); + kfree(req_buf); + return err; + } + + if (resp_payload_size) { + pr_err("%s: Invalid size received for VM_AUTH_IMAGE: %u\n", + __func__, resp_payload_size); + kfree(resp); + kfree(req_buf); + return -EINVAL; + } + + kfree(req_buf); + return 0; +} +EXPORT_SYMBOL(gh_rm_vm_auth_image); + +/** + * gh_rm_vm_init: Request to allocate resources of the VM + * @vmid: The vmid of VM to initialize. + * + * The function returns 0 on success and a negative error code + * upon failure. + */ +int gh_rm_vm_init(gh_vmid_t vmid) +{ + struct gh_vm_init_req_payload req_payload = { + .vmid = vmid, + }; + size_t resp_payload_size; + int err, reply_err_code = 0; + void *resp; + + resp = gh_rm_call(GH_RM_RPC_MSG_ID_CALL_VM_INIT, + &req_payload, sizeof(req_payload), + &resp_payload_size, &reply_err_code); + + if (IS_ERR(resp)) { + pr_err("%s: Unable to send VM_INIT to RM: %d\n", __func__, + PTR_ERR(resp)); + return PTR_ERR(resp); + } + + if (reply_err_code) { + err = reply_err_code; + pr_err("%s: VM_INIT failed with err: %d\n", + __func__, err); + return err; + } + + if (resp_payload_size) { + pr_err("%s: Invalid size received for VM_INIT: %u\n", + __func__, resp_payload_size); + kfree(resp); + return -EINVAL; + } + + return 0; +} +EXPORT_SYMBOL(gh_rm_vm_init); + +/** + * gh_rm_vm_start: Send a request to Resource Manager VM to start a VM. + * @vmid: The vmid of the vm to be started. + * + * The function encodes the error codes via ERR_PTR. Hence, the caller is + * responsible to check it with IS_ERR_OR_NULL(). + */ +int gh_rm_vm_start(int vmid) +{ + struct gh_vm_start_resp_payload *resp_payload; + struct gh_vm_start_req_payload req_payload = {0}; + size_t resp_payload_size; + int reply_err_code = 0; + + req_payload.vmid = (gh_vmid_t) vmid; + + resp_payload = gh_rm_call(GH_RM_RPC_MSG_ID_CALL_VM_START, + &req_payload, sizeof(req_payload), + &resp_payload_size, &reply_err_code); + if (reply_err_code) { + pr_err("%s: VM_START failed with err: %d\n", + __func__, reply_err_code); + return reply_err_code; + } + + if (resp_payload_size) { + pr_err("%s: Invalid size received for VM_START: %u\n", + __func__, resp_payload_size); + return -EINVAL; + } + + return 0; +} +EXPORT_SYMBOL(gh_rm_vm_start); + +/** + * gh_rm_vm_stop: Send a request to Resource Manager VM to stop a VM. + * @vmid: The vmid of the vm to be stopped. + * + * The function encodes the error codes via ERR_PTR. Hence, the caller is + * responsible to check it with IS_ERR_OR_NULL(). + */ +int gh_rm_vm_stop(gh_vmid_t vmid, u32 stop_reason, u8 flags) +{ + struct gh_vm_stop_req_payload req_payload = {0}; + size_t resp_payload_size; + int err, reply_err_code; + void *resp; + + if (stop_reason >= GH_VM_STOP_MAX) { + pr_err("%s: Invalid stop reason provided for VM_STOP\n", + __func__); + return -EINVAL; + } + + req_payload.vmid = vmid; + req_payload.stop_reason = stop_reason; + req_payload.flags = flags; + + resp = gh_rm_call(GH_RM_RPC_MSG_ID_CALL_VM_STOP, + &req_payload, sizeof(req_payload), + &resp_payload_size, &reply_err_code); + if (reply_err_code || IS_ERR(resp)) { + err = reply_err_code; + pr_err("%s: VM_STOP failed with err: %d\n", __func__, err); + return err; + } + + if (resp_payload_size) { + pr_err("%s: Invalid size received for VM_STOP: %u\n", + __func__, resp_payload_size); + kfree(resp); + return -EINVAL; + } + + return 0; +} +EXPORT_SYMBOL(gh_rm_vm_stop); + +/** + * gh_rm_vm_reset: Send a request to Resource Manager VM to free up all + * resources used by the VM. + * @vmid: The vmid of the vm to be cleaned up. + * + * The function returns 0 on success and a negative error code + * upon failure. + */ +int gh_rm_vm_reset(gh_vmid_t vmid) +{ + struct gh_vm_reset_req_payload req_payload = { + .vmid = vmid, + }; + size_t resp_payload_size; + int err, reply_err_code; + void *resp; + + resp = gh_rm_call(GH_RM_RPC_MSG_ID_CALL_VM_RESET, + &req_payload, sizeof(req_payload), + &resp_payload_size, &reply_err_code); + if (reply_err_code || IS_ERR(resp)) { + err = reply_err_code; + pr_err("%s: VM_RESET failed with err: %d\n", + __func__, err); + return err; + } + + if (resp_payload_size) { + pr_err("%s: Invalid size received for VM_RESET: %u\n", + __func__, resp_payload_size); + kfree(resp); + return -EINVAL; + } + + return 0; +} +EXPORT_SYMBOL(gh_rm_vm_reset); + +/** + * gh_rm_console_open: Open a console with a VM + * @vmid: The vmid of the vm to be started. + */ +int gh_rm_console_open(gh_vmid_t vmid) +{ + void *resp; + struct gh_vm_console_common_req_payload req_payload = {0}; + size_t resp_payload_size; + int reply_err_code = 0; + + req_payload.vmid = vmid; + + resp = gh_rm_call(GH_RM_RPC_MSG_ID_CALL_VM_CONSOLE_OPEN, + &req_payload, sizeof(req_payload), + &resp_payload_size, &reply_err_code); + if (IS_ERR(resp)) { + pr_err("%s: Unable to send CONSOLE_OPEN to RM: %d\n", __func__, + PTR_ERR(resp)); + return PTR_ERR(resp); + } + + if (reply_err_code) { + pr_err("%s: CONSOLE_OPEN returned error: %d\n", __func__, + reply_err_code); + return reply_err_code; + } + + if (resp_payload_size) { + pr_err("%s: Invalid size received for CONSOLE_OPEN: %u\n", + __func__, resp_payload_size); + return -EINVAL; + } + + return 0; +} +EXPORT_SYMBOL(gh_rm_console_open); + +/** + * gh_rm_console_close: Close a console with a VM + * @vmid: The vmid of the vm whose console to close. + */ +int gh_rm_console_close(gh_vmid_t vmid) +{ + void *resp; + struct gh_vm_console_common_req_payload req_payload = {0}; + size_t resp_payload_size; + int reply_err_code = 0; + + req_payload.vmid = vmid; + + resp = gh_rm_call(GH_RM_RPC_MSG_ID_CALL_VM_CONSOLE_CLOSE, + &req_payload, sizeof(req_payload), + &resp_payload_size, &reply_err_code); + if (IS_ERR(resp)) { + pr_err("%s: Unable to send CONSOLE_CLOSE to RM: %d\n", __func__, + PTR_ERR(resp)); + return PTR_ERR(resp); + } + + if (reply_err_code) { + pr_err("%s: CONSOLE_CLOSE returned error: %d\n", __func__, + reply_err_code); + return reply_err_code; + } + + if (resp_payload_size) { + pr_err("%s: Invalid size received for CONSOLE_CLOSE: %u\n", + __func__, resp_payload_size); + return -EINVAL; + } + + return 0; +} +EXPORT_SYMBOL(gh_rm_console_close); + +/** + * gh_rm_console_write: Write to a VM's console + * @vmid: The vmid of the vm whose console to write to. + * @buf: Buffer to write to the VM's console + * @size: Size of the buffer + */ +int gh_rm_console_write(gh_vmid_t vmid, const char *buf, size_t size) +{ + void *resp; + struct gh_vm_console_write_req_payload *req_payload; + size_t resp_payload_size; + int reply_err_code = 0; + size_t req_payload_size = sizeof(*req_payload) + size; + + if (size < 1 || size > (U32_MAX - sizeof(*req_payload))) + return -EINVAL; + + req_payload = kzalloc(req_payload_size, GFP_KERNEL); + + if (!req_payload) + return -ENOMEM; + + req_payload->vmid = vmid; + req_payload->num_bytes = size; + memcpy(req_payload->data, buf, size); + + resp = gh_rm_call(GH_RM_RPC_MSG_ID_CALL_VM_CONSOLE_WRITE, + req_payload, req_payload_size, + &resp_payload_size, &reply_err_code); + kfree(req_payload); + + if (IS_ERR(resp)) { + pr_err("%s: Unable to send CONSOLE_WRITE to RM: %d\n", __func__, + PTR_ERR(resp)); + return PTR_ERR(resp); + } + + if (reply_err_code) { + pr_err("%s: CONSOLE_WRITE returned error: %d\n", __func__, + reply_err_code); + return reply_err_code; + } + + if (resp_payload_size) { + pr_err("%s: Invalid size received for CONSOLE_WRITE: %u\n", + __func__, resp_payload_size); + return -EINVAL; + } + + return 0; +} +EXPORT_SYMBOL(gh_rm_console_write); + +/** + * gh_rm_console_flush: Flush a console with a VM + * @vmid: The vmid of the vm whose console to flush + */ +int gh_rm_console_flush(gh_vmid_t vmid) +{ + void *resp; + struct gh_vm_console_common_req_payload req_payload = {0}; + size_t resp_payload_size; + int reply_err_code = 0; + + req_payload.vmid = vmid; + + resp = gh_rm_call(GH_RM_RPC_MSG_ID_CALL_VM_CONSOLE_FLUSH, + &req_payload, sizeof(req_payload), + &resp_payload_size, &reply_err_code); + + if (IS_ERR(resp)) { + pr_err("%s: Unable to send CONSOLE_FLUSH to RM: %d\n", __func__, + PTR_ERR(resp)); + return PTR_ERR(resp); + } + + if (reply_err_code) { + pr_err("%s: CONSOLE_FLUSH returned error: %d\n", __func__, + reply_err_code); + return reply_err_code; + } + + if (resp_payload_size) { + pr_err("%s: Invalid size received for CONSOLE_FLUSH: %u\n", + __func__, resp_payload_size); + return -EINVAL; + } + + return 0; +} +EXPORT_SYMBOL(gh_rm_console_flush); + +static void gh_rm_populate_acl_desc(struct gh_acl_desc *dst_desc, + struct gh_acl_desc *src_desc) +{ + u32 n_acl_entries = src_desc ? src_desc->n_acl_entries : 0; + unsigned int i; + + dst_desc->n_acl_entries = n_acl_entries; + for (i = 0; i < n_acl_entries; i++) { + dst_desc->acl_entries[i].vmid = src_desc->acl_entries[i].vmid; + dst_desc->acl_entries[i].perms = src_desc->acl_entries[i].perms; + } +} + +static void gh_rm_populate_sgl_desc(struct gh_sgl_desc *dst_desc, + struct gh_sgl_desc *src_desc, + u16 reserved_param) +{ + u32 n_sgl_entries; + + n_sgl_entries = min_t(u32, GH_RM_MEM_MAX_SGL_ENTRIES, + src_desc ? src_desc->n_sgl_entries : 0); + + dst_desc->n_sgl_entries = n_sgl_entries; + dst_desc->reserved = reserved_param; + if (n_sgl_entries) + memcpy(dst_desc->sgl_entries, src_desc->sgl_entries, + sizeof(*dst_desc->sgl_entries) * n_sgl_entries); +} + +static void gh_rm_populate_mem_attr_desc(struct gh_mem_attr_desc *dst_desc, + struct gh_mem_attr_desc *src_desc) +{ + u32 n_mem_attr_entries = src_desc ? src_desc->n_mem_attr_entries : 0; + + dst_desc->n_mem_attr_entries = n_mem_attr_entries; + if (n_mem_attr_entries) + memcpy(dst_desc->attr_entries, src_desc->attr_entries, + sizeof(*dst_desc->attr_entries) * n_mem_attr_entries); +} + +/* + * Only first GH_RM_MEM_MAX_SGL_ENTRIES are added to req_buf. + */ +static void gh_rm_populate_mem_request(void *req_buf, u32 fn_id, + struct gh_acl_desc *src_acl_desc, + struct gh_sgl_desc *src_sgl_desc, + u16 reserved_param, + struct gh_mem_attr_desc *src_mem_attrs) +{ + struct gh_acl_desc *dst_acl_desc; + struct gh_sgl_desc *dst_sgl_desc; + struct gh_mem_attr_desc *dst_mem_attrs; + size_t req_hdr_size, req_acl_size, req_sgl_size; + u32 n_acl_entries = src_acl_desc ? src_acl_desc->n_acl_entries : 0; + u32 n_sgl_entries; + + n_sgl_entries = min_t(u32, GH_RM_MEM_MAX_SGL_ENTRIES, + src_sgl_desc ? src_sgl_desc->n_sgl_entries : 0); + + switch (fn_id) { + case GH_RM_RPC_MSG_ID_CALL_MEM_LEND: + case GH_RM_RPC_MSG_ID_CALL_MEM_SHARE: + case GH_RM_RPC_MSG_ID_CALL_MEM_DONATE: + req_hdr_size = sizeof(struct gh_mem_share_req_payload_hdr); + break; + case GH_RM_RPC_MSG_ID_CALL_MEM_QCOM_LOOKUP_SGL: + req_hdr_size = + sizeof(struct gh_mem_qcom_lookup_sgl_req_payload_hdr); + break; + case GH_RM_RPC_MSG_ID_CALL_MEM_ACCEPT: + req_hdr_size = + sizeof(struct gh_mem_accept_req_payload_hdr); + break; + default: + return; + } + + req_acl_size = offsetof(struct gh_acl_desc, acl_entries[n_acl_entries]); + req_sgl_size = offsetof(struct gh_sgl_desc, sgl_entries[n_sgl_entries]); + + dst_acl_desc = req_buf + req_hdr_size; + dst_sgl_desc = req_buf + req_hdr_size + req_acl_size; + dst_mem_attrs = req_buf + req_hdr_size + req_acl_size + req_sgl_size; + + gh_rm_populate_acl_desc(dst_acl_desc, src_acl_desc); + gh_rm_populate_sgl_desc(dst_sgl_desc, src_sgl_desc, reserved_param); + gh_rm_populate_mem_attr_desc(dst_mem_attrs, src_mem_attrs); +} + +static void *gh_rm_alloc_mem_request_buf(u32 fn_id, size_t n_acl_entries, + size_t n_sgl_entries, + size_t n_mem_attr_entries, + size_t *req_payload_size_ptr) +{ + size_t req_acl_size, req_sgl_size, req_mem_attr_size, req_payload_size; + void *req_buf; + + + switch (fn_id) { + case GH_RM_RPC_MSG_ID_CALL_MEM_LEND: + case GH_RM_RPC_MSG_ID_CALL_MEM_SHARE: + case GH_RM_RPC_MSG_ID_CALL_MEM_DONATE: + req_payload_size = sizeof(struct gh_mem_share_req_payload_hdr); + break; + case GH_RM_RPC_MSG_ID_CALL_MEM_QCOM_LOOKUP_SGL: + req_payload_size = + sizeof(struct gh_mem_qcom_lookup_sgl_req_payload_hdr); + break; + case GH_RM_RPC_MSG_ID_CALL_MEM_ACCEPT: + req_payload_size = + sizeof(struct gh_mem_accept_req_payload_hdr); + break; + default: + return ERR_PTR(-EINVAL); + } + + req_acl_size = offsetof(struct gh_acl_desc, acl_entries[n_acl_entries]); + req_sgl_size = offsetof(struct gh_sgl_desc, sgl_entries[n_sgl_entries]); + req_mem_attr_size = offsetof(struct gh_mem_attr_desc, + attr_entries[n_mem_attr_entries]); + req_payload_size += req_acl_size + req_sgl_size + req_mem_attr_size; + + req_buf = kzalloc(req_payload_size, GFP_KERNEL); + if (!req_buf) + return ERR_PTR(-ENOMEM); + + *req_payload_size_ptr = req_payload_size; + return req_buf; +} + +/** + * gh_rm_mem_qcom_lookup_sgl: Look up the handle for a memparcel by its sg-list + * @mem_type: The type of memory associated with the memparcel (i.e. normal or + * I/O) + * @label: The label to assign to the memparcel + * @acl_desc: Describes the number of ACL entries and VMID and permission pairs + * for the memparcel + * @sgl_desc: Describes the number of SG-List entries and the SG-List for the + * memory associated with the memparcel + * @mem_attr_desc: Describes the number of memory attribute entries and the + * memory attribute and VMID pairs for the memparcel. This + * parameter is currently optional, as this function is meant + * to be used in conjunction with hyp_assign_[phys/table], which + * does not provide memory attributes + * @handle: Pointer to where the memparcel handle should be stored + * + * On success, the function will return 0 and populate the memory referenced by + * @handle with the memparcel handle. Otherwise, a negative number will be + * returned. + */ +int gh_rm_mem_qcom_lookup_sgl(u8 mem_type, gh_label_t label, + struct gh_acl_desc *acl_desc, + struct gh_sgl_desc *sgl_desc, + struct gh_mem_attr_desc *mem_attr_desc, + gh_memparcel_handle_t *handle) +{ + struct gh_mem_qcom_lookup_sgl_req_payload_hdr *req_payload_hdr; + struct gh_mem_qcom_lookup_sgl_resp_payload *resp_payload; + size_t req_payload_size, resp_size; + void *req_buf; + unsigned int n_mem_attr_entries = 0; + u32 fn_id = GH_RM_RPC_MSG_ID_CALL_MEM_QCOM_LOOKUP_SGL; + int ret = 0, gh_ret; + + if ((mem_type != GH_RM_MEM_TYPE_NORMAL && + mem_type != GH_RM_MEM_TYPE_IO) || !acl_desc || + !acl_desc->n_acl_entries || !sgl_desc || + !sgl_desc->n_sgl_entries || !handle || (mem_attr_desc && + !mem_attr_desc->n_mem_attr_entries)) + return -EINVAL; + + if (mem_attr_desc) + n_mem_attr_entries = mem_attr_desc->n_mem_attr_entries; + + req_buf = gh_rm_alloc_mem_request_buf(fn_id, acl_desc->n_acl_entries, + sgl_desc->n_sgl_entries, + n_mem_attr_entries, + &req_payload_size); + if (IS_ERR(req_buf)) + return PTR_ERR(req_buf); + + req_payload_hdr = req_buf; + req_payload_hdr->mem_type = mem_type; + req_payload_hdr->label = label; + gh_rm_populate_mem_request(req_buf, fn_id, acl_desc, sgl_desc, 0, + mem_attr_desc); + + resp_payload = gh_rm_call(fn_id, req_buf, req_payload_size, &resp_size, + &gh_ret); + if (gh_ret || IS_ERR(resp_payload)) { + ret = PTR_ERR(resp_payload); + pr_err("%s failed with err: %d\n", __func__, ret); + goto err_rm_call; + } + + if (resp_size != sizeof(*resp_payload)) { + ret = -EINVAL; + pr_err("%s invalid size received %u\n", __func__, resp_size); + goto err_resp_size; + } + + *handle = resp_payload->memparcel_handle; + +err_resp_size: + kfree(resp_payload); +err_rm_call: + kfree(req_buf); + return ret; +} +EXPORT_SYMBOL(gh_rm_mem_qcom_lookup_sgl); + +static int gh_rm_mem_release_helper(u32 fn_id, gh_memparcel_handle_t handle, + u8 flags) +{ + struct gh_mem_release_req_payload req_payload = {}; + void *resp; + size_t resp_size; + int ret, gh_ret; + + if ((fn_id == GH_RM_RPC_MSG_ID_CALL_MEM_RELEASE) && + (flags & ~GH_RM_MEM_RELEASE_VALID_FLAGS)) + return -EINVAL; + else if ((fn_id == GH_RM_RPC_MSG_ID_CALL_MEM_RECLAIM) && + (flags & ~GH_RM_MEM_RECLAIM_VALID_FLAGS)) + return -EINVAL; + + req_payload.memparcel_handle = handle; + req_payload.flags = flags; + + resp = gh_rm_call(fn_id, &req_payload, sizeof(req_payload), &resp_size, + &gh_ret); + if (gh_ret) { + ret = PTR_ERR(resp); + pr_err("%s failed with err: %d\n", __func__, ret); + return ret; + } + + return 0; +} + +/** + * gh_rm_mem_release: Release a handle representing memory. This results in + * the RM unmapping the associated memory from the stage-2 + * page-tables of the current VM + * @handle: The memparcel handle associated with the memory + * @flags: Bitmask of values to influence the behavior of the RM when it unmaps + * the memory. + * + * On success, the function will return 0. Otherwise, a negative number will be + * returned. + */ +int gh_rm_mem_release(gh_memparcel_handle_t handle, u8 flags) +{ + return gh_rm_mem_release_helper(GH_RM_RPC_MSG_ID_CALL_MEM_RELEASE, + handle, flags); +} +EXPORT_SYMBOL(gh_rm_mem_release); + +/** + * gh_rm_mem_reclaim: Reclaim a memory represented by a handle. This results in + * the RM mapping the associated memory into the stage-2 + * page-tables of the owner VM + * @handle: The memparcel handle associated with the memory + * @flags: Bitmask of values to influence the behavior of the RM when it unmaps + * the memory. + * + * On success, the function will return 0. Otherwise, a negative number will be + * returned. + */ +int gh_rm_mem_reclaim(gh_memparcel_handle_t handle, u8 flags) +{ + return gh_rm_mem_release_helper(GH_RM_RPC_MSG_ID_CALL_MEM_RECLAIM, + handle, flags); +} +EXPORT_SYMBOL(gh_rm_mem_reclaim); + +/** + * gh_rm_mem_accept: Accept a handle representing memory. This results in + * the RM mapping the associated memory from the stage-2 + * page-tables of a VM + * @handle: The memparcel handle associated with the memory + * @mem_type: The type of memory associated with the memparcel (i.e. normal or + * I/O) + * @trans_type: The type of memory transfer + * @flags: Bitmask of values to influence the behavior of the RM when it maps + * the memory + * @label: The label to validate against the label maintained by the RM + * @acl_desc: Describes the number of ACL entries and VMID and permission + * pairs that the resource manager should validate against for AC + * regarding the memparcel + * @sgl_desc: Describes the number of SG-List entries as well as + * where the memory should be mapped in the IPA space of the VM + * denoted by @map_vmid. If this parameter is left NULL, then the + * RM will map the memory at an arbitrary location + * @mem_attr_desc: Describes the number of memory attribute entries and the + * memory attribute and VMID pairs that the RM should validate + * against regarding the memparcel. + * @map_vmid: The VMID which RM will map the memory for. VMID 0 corresponds + * to mapping the memory for the current VM + * + * + * On success, the function will return a pointer to an sg-list to convey where + * the memory has been mapped. If the @sgl_desc parameter was not NULL, then the + * return value will be a pointer to the same SG-List. Otherwise, the return + * value will be a pointer to a newly allocated SG-List. After the SG-List is + * no longer needed, the caller must free the table. On a failure, a negative + * number will be returned. + */ +struct gh_sgl_desc *gh_rm_mem_accept(gh_memparcel_handle_t handle, u8 mem_type, + u8 trans_type, u8 flags, gh_label_t label, + struct gh_acl_desc *acl_desc, + struct gh_sgl_desc *sgl_desc, + struct gh_mem_attr_desc *mem_attr_desc, + u16 map_vmid) +{ + struct gh_mem_accept_req_payload_hdr *req_payload_hdr; + struct gh_sgl_desc *ret_sgl; + struct gh_mem_accept_resp_payload *resp_payload; + void *req_buf; + size_t req_payload_size, resp_payload_size; + u16 req_sgl_entries = 0, req_mem_attr_entries = 0; + u32 req_acl_entries = 0; + int gh_ret; + u32 fn_id = GH_RM_RPC_MSG_ID_CALL_MEM_ACCEPT; + + if ((mem_type != GH_RM_MEM_TYPE_NORMAL && + mem_type != GH_RM_MEM_TYPE_IO) || + (trans_type != GH_RM_TRANS_TYPE_DONATE && + trans_type != GH_RM_TRANS_TYPE_LEND && + trans_type != GH_RM_TRANS_TYPE_SHARE) || + (flags & ~GH_RM_MEM_ACCEPT_VALID_FLAGS)) + return ERR_PTR(-EINVAL); + + if (flags & GH_RM_MEM_ACCEPT_VALIDATE_ACL_ATTRS && + (!acl_desc || !acl_desc->n_acl_entries) && + (!mem_attr_desc || !mem_attr_desc->n_mem_attr_entries)) + return ERR_PTR(-EINVAL); + + if (flags & GH_RM_MEM_ACCEPT_VALIDATE_ACL_ATTRS) { + if (acl_desc) + req_acl_entries = acl_desc->n_acl_entries; + if (mem_attr_desc) + req_mem_attr_entries = + mem_attr_desc->n_mem_attr_entries; + } + + if (sgl_desc) + req_sgl_entries = sgl_desc->n_sgl_entries; + + req_buf = gh_rm_alloc_mem_request_buf(fn_id, req_acl_entries, + req_sgl_entries, + req_mem_attr_entries, + &req_payload_size); + if (IS_ERR(req_buf)) + return req_buf; + + req_payload_hdr = req_buf; + req_payload_hdr->memparcel_handle = handle; + req_payload_hdr->mem_type = mem_type; + req_payload_hdr->trans_type = trans_type; + req_payload_hdr->flags = flags; + if (flags & GH_RM_MEM_ACCEPT_VALIDATE_LABEL) + req_payload_hdr->validate_label = label; + gh_rm_populate_mem_request(req_buf, fn_id, acl_desc, sgl_desc, map_vmid, + mem_attr_desc); + + resp_payload = gh_rm_call(fn_id, req_buf, req_payload_size, + &resp_payload_size, &gh_ret); + if (gh_ret || IS_ERR(resp_payload)) { + ret_sgl = ERR_CAST(resp_payload); + pr_err("%s failed with error: %d\n", __func__, + PTR_ERR(resp_payload)); + goto err_rm_call; + } + + + if (sgl_desc) { + ret_sgl = sgl_desc; + } else { + size_t size; + + size = offsetof(struct gh_sgl_desc, sgl_entries[resp_payload->n_sgl_entries]); + ret_sgl = kvmalloc(size, GFP_KERNEL); + if (!ret_sgl) + ret_sgl = ERR_PTR(-ENOMEM); + + memcpy(ret_sgl, resp_payload, size); + kfree(resp_payload); + } + +err_rm_call: + kfree(req_buf); + return ret_sgl; +} +EXPORT_SYMBOL(gh_rm_mem_accept); + +/** + * gh_rm_mem_append: Append additional memory to an existing handle. + * @handle: Memparcel handle from a previous gh_rm_mem_lend/share/donate call, which + * had GH_RM_MEM_*_APPEND flag set. + * @flags: + * @sgl_entries: List of physical memory to append. + * @n_sgl_entries: + * + * flags must include GH_RM_MEM_APPEND_END on the last call to append. + * in case of error on a partially constructed handle, the caller should call + * gh_rm_mem_reclaim. + */ +int gh_rm_mem_append(gh_memparcel_handle_t handle, u8 flags, + struct gh_sgl_entry *sgl_entries, u32 n_sgl_entries) +{ + int gh_ret, ret = 0; + size_t req_payload_size, resp_payload_size; + void *req_buf, *resp_payload; + struct gh_mem_append_req_payload_hdr *req_hdr; + struct gh_sgl_desc *req_sgl_desc; + + if ((flags & ~GH_RM_MEM_APPEND_VALID_FLAGS) || + n_sgl_entries > GH_RM_MEM_MAX_SGL_ENTRIES) + return -EINVAL; + + req_payload_size = sizeof(*req_hdr); + req_payload_size += offsetof(struct gh_sgl_desc, sgl_entries[n_sgl_entries]); + req_buf = kmalloc(req_payload_size, GFP_KERNEL); + if (!req_buf) + return -ENOMEM; + + req_hdr = req_buf; + req_sgl_desc = req_buf + sizeof(*req_hdr); + + req_hdr->memparcel_handle = handle; + req_hdr->flags = flags; + req_sgl_desc->n_sgl_entries = n_sgl_entries; + memcpy(req_sgl_desc->sgl_entries, sgl_entries, + sizeof(*sgl_entries) * n_sgl_entries); + + resp_payload = gh_rm_call(GH_RM_RPC_MSG_ID_CALL_MEM_APPEND, + req_buf, req_payload_size, + &resp_payload_size, &gh_ret); + if (gh_ret || IS_ERR(resp_payload)) { + ret = PTR_ERR(resp_payload); + pr_err("%s failed with error: %d\n", __func__, + PTR_ERR(resp_payload)); + goto free_req_buf; + } + + if (resp_payload_size) { + ret = -EINVAL; + pr_err("%s: Invalid size received: %u\n", + __func__, resp_payload_size); + } + + kfree(resp_payload); +free_req_buf: + kfree(req_buf); + return ret; +} +EXPORT_SYMBOL(gh_rm_mem_append); + + +static int gh_rm_mem_share_lend_helper(u32 fn_id, u8 mem_type, u8 flags, + gh_label_t label, + struct gh_acl_desc *acl_desc, + struct gh_sgl_desc *sgl_desc, + struct gh_mem_attr_desc *mem_attr_desc, + gh_memparcel_handle_t *handle) +{ + struct gh_mem_share_req_payload_hdr *req_payload_hdr; + struct gh_mem_share_resp_payload *resp_payload; + void *req_buf; + size_t req_payload_size, resp_payload_size; + u16 req_sgl_entries, req_acl_entries, req_mem_attr_entries = 0; + int gh_ret, ret = 0; + int idx, next; + + if ((mem_type != GH_RM_MEM_TYPE_NORMAL && + mem_type != GH_RM_MEM_TYPE_IO) || + ((fn_id == GH_RM_RPC_MSG_ID_CALL_MEM_SHARE) && + (flags & ~GH_RM_MEM_SHARE_VALID_FLAGS)) || + ((fn_id == GH_RM_RPC_MSG_ID_CALL_MEM_LEND) && + (flags & ~GH_RM_MEM_LEND_VALID_FLAGS)) || + ((fn_id == GH_RM_RPC_MSG_ID_CALL_MEM_DONATE) && + (flags & ~GH_RM_MEM_DONATE_VALID_FLAGS)) || !acl_desc || + (acl_desc && !acl_desc->n_acl_entries) || !sgl_desc || + (sgl_desc && !sgl_desc->n_sgl_entries) || + (mem_attr_desc && !mem_attr_desc->n_mem_attr_entries) || !handle) + return -EINVAL; + + req_acl_entries = acl_desc->n_acl_entries; + req_sgl_entries = sgl_desc->n_sgl_entries; + if (req_sgl_entries > GH_RM_MEM_MAX_SGL_ENTRIES) { + flags |= GH_RM_MEM_LEND_APPEND; + req_sgl_entries = GH_RM_MEM_MAX_SGL_ENTRIES; + } + + if (mem_attr_desc) + req_mem_attr_entries = mem_attr_desc->n_mem_attr_entries; + + req_buf = gh_rm_alloc_mem_request_buf(fn_id, req_acl_entries, + req_sgl_entries, + req_mem_attr_entries, + &req_payload_size); + if (IS_ERR(req_buf)) + return PTR_ERR(req_buf); + + req_payload_hdr = req_buf; + req_payload_hdr->mem_type = mem_type; + req_payload_hdr->flags = flags; + req_payload_hdr->label = label; + gh_rm_populate_mem_request(req_buf, fn_id, acl_desc, sgl_desc, 0, + mem_attr_desc); + + resp_payload = gh_rm_call(fn_id, req_buf, req_payload_size, + &resp_payload_size, &gh_ret); + if (gh_ret || IS_ERR(resp_payload)) { + ret = PTR_ERR(resp_payload); + pr_err("%s failed with error: %d\n", __func__, + PTR_ERR(resp_payload)); + goto err_rm_call; + } + + if (resp_payload_size != sizeof(*resp_payload)) { + ret = -EINVAL; + goto err_resp_size; + } + + for (idx = req_sgl_entries; idx < sgl_desc->n_sgl_entries; idx = next) { + u8 append_flags = 0; + + next = min_t(u32, idx + GH_RM_MEM_MAX_SGL_ENTRIES, sgl_desc->n_sgl_entries); + if (next == sgl_desc->n_sgl_entries) + append_flags |= GH_RM_MEM_APPEND_END; + + ret = gh_rm_mem_append(resp_payload->memparcel_handle, append_flags, + sgl_desc->sgl_entries + idx, next - idx); + if (ret) + goto err_mem_append; + } + + *handle = resp_payload->memparcel_handle; + kfree(resp_payload); + kfree(req_buf); + return 0; + +err_mem_append: + gh_rm_mem_reclaim(resp_payload->memparcel_handle, 0); +err_resp_size: + kfree(resp_payload); +err_rm_call: + kfree(req_buf); + return ret; +} + +/** + * gh_rm_mem_share: Share memory with other VM(s) without excluding the owner + * @mem_type: The type of memory being shared (i.e. normal or I/O) + * @flags: Bitmask of values to influence the behavior of the RM when it shares + * the memory + * @label: The label to assign to the memparcel that the RM will create + * @acl_desc: Describes the number of ACL entries and VMID and permission + * pairs that the resource manager should consider when sharing the + * memory + * @sgl_desc: Describes the number of SG-List entries as well as + * the location of the memory in the IPA space of the owner + * @mem_attr_desc: Describes the number of memory attribute entries and the + * memory attribute and VMID pairs that the RM should consider + * when sharing the memory + * @handle: Pointer to where the memparcel handle should be stored + + * On success, the function will return 0 and populate the memory referenced by + * @handle with the memparcel handle. Otherwise, a negative number will be + * returned. + */ +int gh_rm_mem_share(u8 mem_type, u8 flags, gh_label_t label, + struct gh_acl_desc *acl_desc, struct gh_sgl_desc *sgl_desc, + struct gh_mem_attr_desc *mem_attr_desc, + gh_memparcel_handle_t *handle) +{ + return gh_rm_mem_share_lend_helper(GH_RM_RPC_MSG_ID_CALL_MEM_SHARE, + mem_type, flags, label, acl_desc, + sgl_desc, mem_attr_desc, handle); +} +EXPORT_SYMBOL(gh_rm_mem_share); + +/** + * gh_rm_mem_lend: Lend memory to other VM(s)--excluding the owner + * @mem_type: The type of memory being lent (i.e. normal or I/O) + * @flags: Bitmask of values to influence the behavior of the RM when it lends + * the memory + * @label: The label to assign to the memparcel that the RM will create + * @acl_desc: Describes the number of ACL entries and VMID and permission + * pairs that the resource manager should consider when lending the + * memory + * @sgl_desc: Describes the number of SG-List entries as well as + * the location of the memory in the IPA space of the owner + * @mem_attr_desc: Describes the number of memory attribute entries and the + * memory attribute and VMID pairs that the RM should consider + * when lending the memory + * @handle: Pointer to where the memparcel handle should be stored + + * On success, the function will return 0 and populate the memory referenced by + * @handle with the memparcel handle. Otherwise, a negative number will be + * returned. + */ +int gh_rm_mem_lend(u8 mem_type, u8 flags, gh_label_t label, + struct gh_acl_desc *acl_desc, struct gh_sgl_desc *sgl_desc, + struct gh_mem_attr_desc *mem_attr_desc, + gh_memparcel_handle_t *handle) +{ + return gh_rm_mem_share_lend_helper(GH_RM_RPC_MSG_ID_CALL_MEM_LEND, + mem_type, flags, label, acl_desc, + sgl_desc, mem_attr_desc, handle); +} +EXPORT_SYMBOL(gh_rm_mem_lend); + +/** + * gh_rm_mem_donate: Donate memory to a single VM. + * @mem_type: The type of memory being lent (i.e. normal or I/O) + * @flags: Bitmask of values to influence the behavior of the RM when it lends + * the memory + * @label: The label to assign to the memparcel that the RM will create + * @acl_desc: Describes the number of ACL entries and VMID and permission + * pairs that the resource manager should consider when lending the + * memory + * @sgl_desc: Describes the number of SG-List entries as well as + * the location of the memory in the IPA space of the owner + * @mem_attr_desc: Describes the number of memory attribute entries and the + * memory attribute and VMID pairs that the RM should consider + * when lending the memory + * @handle: Pointer to where the memparcel handle should be stored + + * On success, the function will return 0 and populate the memory referenced by + * @handle with the memparcel handle. Otherwise, a negative number will be + * returned. + * + * Restrictions: + * Only to or from HLOS. + * non-HLOS VM must only donate memory which was previously donated to them by + * HLOS. + * Physically contiguous. + * If Lend or Share operates on a sgl entry which contains memory which + * originated from donate, that sgl entry must be entirely contained within + * that donate operation. + */ +int gh_rm_mem_donate(u8 mem_type, u8 flags, gh_label_t label, + struct gh_acl_desc *acl_desc, struct gh_sgl_desc *sgl_desc, + struct gh_mem_attr_desc *mem_attr_desc, + gh_memparcel_handle_t *handle) +{ + if (sgl_desc->n_sgl_entries != 1) { + pr_err("%s: Physically contiguous memory required\n", __func__); + return -EINVAL; + } + + if (acl_desc->n_acl_entries != 1) { + pr_err("%s: Donate requires single destination VM\n", __func__); + return -EINVAL; + } + + if (acl_desc->acl_entries[0].perms != (GH_RM_ACL_X | GH_RM_ACL_W | GH_RM_ACL_R)) { + pr_err("%s: Invalid permission argument\n", __func__); + return -EINVAL; + } + + return gh_rm_mem_share_lend_helper(GH_RM_RPC_MSG_ID_CALL_MEM_DONATE, + mem_type, flags, label, acl_desc, + sgl_desc, mem_attr_desc, handle); +} +EXPORT_SYMBOL(gh_rm_mem_donate); + +/** + * gh_rm_mem_notify: Notify VMs about a change in state with respect to a + * memparcel + * @handle: The handle of the memparcel for which a notification should be sent + * out + * @flags: Flags to determine if the notification is for notifying that memory + * has been shared to another VM, or that a VM has released memory + * @mem_info_tag: A 32-bit value that is attached to the + * MEM_SHARED/MEM_RELEASED/MEM_ACCEPTED notifications to aid in + * distinguishing different resources from one another. + * @vmid_desc: A list of VMIDs to notify that memory has been shared with them. + * This parameter should only be non-NULL if other VMs are being + * notified (i.e. it is invalid to specify this parameter when the + * operation is a release notification) + * + * On success, the function will return 0. Otherwise, a negative number will be + * returned. + */ +int gh_rm_mem_notify(gh_memparcel_handle_t handle, u8 flags, + gh_label_t mem_info_tag, + struct gh_notify_vmid_desc *vmid_desc) +{ + struct gh_mem_notify_req_payload *req_payload_hdr; + struct gh_notify_vmid_desc *dst_vmid_desc; + void *req_buf, *resp_payload; + size_t n_vmid_entries = 0, req_vmid_desc_size = 0, req_payload_size; + size_t resp_size; + unsigned int i; + int ret = 0, gh_ret; + + if ((flags & ~GH_RM_MEM_NOTIFY_VALID_FLAGS) || + ((flags & GH_RM_MEM_NOTIFY_RECIPIENT_SHARED) && (!vmid_desc || + (vmid_desc && + !vmid_desc->n_vmid_entries))) || + ((flags & (GH_RM_MEM_NOTIFY_OWNER_RELEASED | + GH_RM_MEM_NOTIFY_OWNER_ACCEPTED)) && vmid_desc) || + (hweight8(flags) != 1)) + return -EINVAL; + + if (flags & GH_RM_MEM_NOTIFY_RECIPIENT_SHARED) { + n_vmid_entries = vmid_desc->n_vmid_entries; + req_vmid_desc_size = offsetof(struct gh_notify_vmid_desc, + vmid_entries[n_vmid_entries]); + } + + req_payload_size = sizeof(*req_payload_hdr) + req_vmid_desc_size; + req_buf = kzalloc(req_payload_size, GFP_KERNEL); + if (!req_buf) + return -ENOMEM; + + req_payload_hdr = req_buf; + req_payload_hdr->memparcel_handle = handle; + req_payload_hdr->flags = flags; + req_payload_hdr->mem_info_tag = mem_info_tag; + + if (flags & GH_RM_MEM_NOTIFY_RECIPIENT_SHARED) { + dst_vmid_desc = req_buf + sizeof(*req_payload_hdr); + dst_vmid_desc->n_vmid_entries = n_vmid_entries; + for (i = 0; i < n_vmid_entries; i++) + dst_vmid_desc->vmid_entries[i].vmid = + vmid_desc->vmid_entries[i].vmid; + } + + resp_payload = gh_rm_call(GH_RM_RPC_MSG_ID_CALL_MEM_NOTIFY, req_buf, + req_payload_size, &resp_size, &gh_ret); + if (gh_ret) { + ret = PTR_ERR(resp_payload); + pr_err("%s failed with err: %d\n", __func__, ret); + } + + kfree(req_buf); + return ret; +} +EXPORT_SYMBOL(gh_rm_mem_notify); + +/** + * gh_rm_vm_set_time_base: Send a request to Resource Manager VM to set time base. + * @vmid: The vmid of the vm to be started. + * + * The function encodes the error codes via ERR_PTR. Hence, the caller is + * responsible to check it with IS_ERR_OR_NULL(). + */ +int gh_rm_vm_set_time_base(gh_vmid_t vmid) +{ + struct gh_vm_set_time_base_req_payload req_payload = {0}; + size_t resp_payload_size; + struct timespec64 ts_ref; + u64 ts_ns_ref; + u64 qtime_ref; + int reply_err_code = 0; + void *resp; + + req_payload.vmid = (gh_vmid_t) vmid; + + local_irq_disable(); + ktime_get_real_ts64(&ts_ref); + qtime_ref = arch_timer_read_counter(); + local_irq_enable(); + + ts_ns_ref = timespec64_to_ns(&ts_ref); + req_payload.time_base_low = (u32) ts_ns_ref; + req_payload.time_base_high = (u32) (ts_ns_ref >> 32); + req_payload.arch_timer_ref_low = (u32) qtime_ref; + req_payload.arch_timer_ref_high = (u32) (qtime_ref >> 32); + + resp = gh_rm_call(GH_RM_RPC_MSG_ID_CALL_VM_SET_TIME_BASE, + &req_payload, sizeof(req_payload), + &resp_payload_size, &reply_err_code); + if (reply_err_code) { + pr_err("%s: VM_SET_TIME_BASE failed with err: %d\n", + __func__, reply_err_code); + return reply_err_code; + } + + if (resp_payload_size) { + pr_err("%s: Invalid size received for VM_SET_TIME_BASE: %u\n", + __func__, resp_payload_size); + return -EINVAL; + } + + return 0; +} +EXPORT_SYMBOL(gh_rm_vm_set_time_base); diff --git a/include/linux/gunyah/gh_rm_drv.h b/include/linux/gunyah/gh_rm_drv.h index 557656797fc8..3fa8683fb62f 100644 --- a/include/linux/gunyah/gh_rm_drv.h +++ b/include/linux/gunyah/gh_rm_drv.h @@ -314,6 +314,7 @@ struct notifier_block; typedef int (*gh_virtio_mmio_cb_t)(gh_vmid_t peer, const char *vm_name, gh_label_t label, gh_capid_t cap_id, int linux_irq, u64 base, u64 size); +typedef int (*gh_wdog_manage_cb_t)(gh_vmid_t vmid, gh_capid_t cap_id, bool populate); typedef int (*gh_vcpu_affinity_set_cb_t)(gh_vmid_t vmid, gh_label_t label, gh_capid_t cap_id, int linux_irq); typedef int (*gh_vcpu_affinity_reset_cb_t)(gh_vmid_t vmid, gh_label_t label, @@ -346,6 +347,7 @@ int gh_rm_vm_irq_reclaim(gh_virq_handle_t virq_handle); int gh_rm_set_virtio_mmio_cb(gh_virtio_mmio_cb_t fnptr); void gh_rm_unset_virtio_mmio_cb(void); +int gh_rm_set_wdog_manage_cb(gh_wdog_manage_cb_t fnptr); int gh_rm_set_vcpu_affinity_cb(gh_vcpu_affinity_set_cb_t fnptr); int gh_rm_reset_vcpu_affinity_cb(gh_vcpu_affinity_reset_cb_t fnptr); int gh_rm_set_vpm_grp_cb(gh_vpm_grp_set_cb_t fnptr); @@ -661,6 +663,11 @@ static inline void gh_rm_unset_virtio_mmio_cb(void) } +static inline int gh_rm_set_wdog_manage_cb(gh_wdog_manage_cb_t fnptr) +{ + return -EINVAL; +} + static inline int gh_rm_set_vcpu_affinity_cb(gh_vcpu_affinity_set_cb_t fnptr) { return -EINVAL;