haven: Add Haven Resource Manager Driver

Add Haven Resource Manager driver to talk to the Resource Manager
Virtual Machine for using the services such as resource (IRQ/device)
sharing, obtaining info about IPC/signalling mechanisms, getting
notifications about other VM's health, and so on.

Change-Id: Ibff0fa3a2fef12276c1e9d7037b8bec3eb2efab1
Signed-off-by: Raghavendra Rao Ananta <rananta@codeaurora.org>
Signed-off-by: Elliot Berman <eberman@codeaurora.org>
This commit is contained in:
Raghavendra Rao Ananta 2020-01-02 15:33:54 -08:00 • committed by Elliot Berman
commit 7af086a1d3
6 changed files with 1207 additions and 0 deletions

View file

@ -24,4 +24,17 @@ config HH_MSGQ
the services offered by the drivers is simply to send and receive
messages in a blocking manner.
config HH_RM_DRV
tristate "Haven Resource Manager driver"
help
The Haven Resource Manager driver is used to communicate with the
Resource Manager Virtual Machine (RM-VM). The RM-VM acts as a mediator
and provides numerous services to the other VMs running in the system,
such as notifying when a particular VM is up, resource (IRQ/device)
sharing between VMs, information about the IPC mechanisms, and so on.
The Resource Manager driver runs on the Virtual Machine and acts as an
interface to other driver in order to obtain the services provided by
the RM-VM.
endif

View file

@ -1,2 +1,4 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_HH_MSGQ) += hh_msgq.o
obj-$(CONFIG_HH_RM_DRV) += hh_rm_drv.o
hh_rm_drv-y := hh_rm_core.o hh_rm_iface.o

View file

@ -0,0 +1,807 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2020, The Linux Foundation. All rights reserved.
*
*/
#include <linux/of.h>
#include <linux/slab.h>
#include <linux/mutex.h>
#include <linux/module.h>
#include <linux/of_irq.h>
#include <linux/kthread.h>
#include <linux/notifier.h>
#include <linux/irqdomain.h>
#include <linux/workqueue.h>
#include <linux/completion.h>
#include <linux/mod_devicetable.h>
#include <linux/platform_device.h>
#include <dt-bindings/interrupt-controller/arm-gic.h>
#include <linux/haven/hh_msgq.h>
#include <linux/haven/hh_errno.h>
#include <linux/haven/hh_common.h>
#include "hh_rm_drv_private.h"
#define HH_RM_MAX_NUM_FRAGMENTS 62
#define GIC_V3_SPI_MAX 1019
#define HH_RM_NO_IRQ_ALLOC -1
#define HH_RM_MAX_MSG_SIZE_BYTES \
(HH_MSGQ_MAX_MSG_SIZE_BYTES - sizeof(struct hh_rm_rpc_hdr))
struct hh_rm_connection {
u16 seq;
void *recv_buff;
u32 reply_err_code;
size_t recv_buff_size;
struct completion seq_done;
u8 num_fragments;
u8 fragments_received;
void *current_recv_buff;
};
static struct task_struct *hh_rm_drv_recv_task;
static struct hh_msgq_desc *hh_rm_msgq_desc;
static DEFINE_MUTEX(hh_rm_call_idr_lock);
static DEFINE_IDR(hh_rm_call_idr);
static DEFINE_IDA(hh_rm_free_virq_ida);
static struct device_node *hh_rm_intc;
static u32 hh_rm_base_virq;
SRCU_NOTIFIER_HEAD_STATIC(hh_rm_notifier);
static void hh_rm_get_svm_res_work_fn(struct work_struct *work);
static DECLARE_WORK(hh_rm_get_svm_res_work, hh_rm_get_svm_res_work_fn);
static int hh_rm_populate_hyp_res(void);
static struct hh_rm_connection *hh_rm_alloc_connection(int seq)
{
struct hh_rm_connection *connection;
connection = kzalloc(sizeof(*connection), GFP_KERNEL);
if (!connection)
return ERR_PTR(-ENOMEM);
init_completion(&connection->seq_done);
if (seq > 0) {
connection->seq = seq;
return connection;
}
/* Allocate a new seq number for this connection */
if (mutex_lock_interruptible(&hh_rm_call_idr_lock)) {
kfree(connection);
return ERR_PTR(-ERESTARTSYS);
}
connection->seq = idr_alloc_cyclic(&hh_rm_call_idr, connection,
0, U16_MAX, GFP_KERNEL);
mutex_unlock(&hh_rm_call_idr_lock);
return connection;
}
static int
hh_rm_init_connection_buff(struct hh_rm_connection *connection,
void *recv_buff, size_t hdr_size,
size_t payload_size)
{
struct hh_rm_rpc_hdr *hdr = recv_buff;
/* Some of the 'reply' types doesn't contain any payload */
if (!payload_size)
return 0;
/* If the data is split into multiple fragments, allocate a large
* enough buffer to hold the payloads for all the fragments.
*/
connection->recv_buff = connection->current_recv_buff =
kzalloc((HH_MSGQ_MAX_MSG_SIZE_BYTES - hdr_size) *
(hdr->fragments + 1),
GFP_KERNEL);
if (!connection->recv_buff)
return -ENOMEM;
memcpy(connection->recv_buff, recv_buff + hdr_size, payload_size);
connection->current_recv_buff += payload_size;
connection->recv_buff_size = payload_size;
connection->num_fragments = hdr->fragments;
return 0;
}
int hh_rm_register_notifier(struct notifier_block *nb)
{
return srcu_notifier_chain_register(&hh_rm_notifier, nb);
}
EXPORT_SYMBOL(hh_rm_register_notifier);
int hh_rm_unregister_notifier(struct notifier_block *nb)
{
return srcu_notifier_chain_unregister(&hh_rm_notifier, nb);
}
EXPORT_SYMBOL(hh_rm_unregister_notifier);
static int hh_rm_process_notif_vm_status(void *recv_buff, size_t recv_buff_size)
{
struct hh_rm_notif_vm_status_payload *vm_status_payload;
vm_status_payload = recv_buff + sizeof(struct hh_rm_rpc_hdr);
/* The VM is now booting. Collect it's info and
* populate to other entities such as MessageQ and DBL
*/
if (vm_status_payload->vm_status == HH_RM_OS_STATUS_BOOT)
return hh_rm_populate_hyp_res();
return 0;
}
static struct hh_rm_connection *
hh_rm_wait_for_notif_fragments(void *recv_buff, size_t recv_buff_size)
{
struct hh_rm_rpc_hdr *hdr = recv_buff;
struct hh_rm_connection *connection;
size_t payload_size;
int ret = 0;
connection = hh_rm_alloc_connection(hdr->seq);
if (IS_ERR_OR_NULL(connection))
return connection;
payload_size = recv_buff_size - sizeof(*hdr);
ret = hh_rm_init_connection_buff(connection, recv_buff,
sizeof(*hdr), payload_size);
if (ret < 0)
goto out;
if (wait_for_completion_interruptible(&connection->seq_done)) {
ret = -ERESTARTSYS;
goto out;
}
return connection;
out:
kfree(connection);
return ERR_PTR(ret);
}
static int hh_rm_process_notif(void *recv_buff, size_t recv_buff_size)
{
struct hh_rm_connection *connection = NULL;
struct hh_rm_rpc_hdr *hdr = recv_buff;
u32 notification = hdr->msg_id;
void *payload = NULL;
int ret = 0;
pr_debug("Notification received from RM-VM: %x\n", notification);
if (recv_buff_size > sizeof(*hdr))
payload = recv_buff + sizeof(*hdr);
/* If the notification payload is split-up into
* fragments, wait until all them arrive.
*/
if (hdr->fragments) {
connection = hh_rm_wait_for_notif_fragments(recv_buff,
recv_buff_size);
if (IS_ERR_OR_NULL(connection))
return PTR_ERR(connection);
/* In the case of fragments, the complete payload
* is contained under connection->recv_buff
*/
payload = connection->recv_buff;
recv_buff_size = connection->recv_buff_size;
}
switch (notification) {
case HH_RM_NOTIF_VM_STATUS:
if (recv_buff_size != sizeof(*hdr) +
sizeof(struct hh_rm_notif_vm_status_payload)) {
pr_err("%s: Invalid size for VM_STATUS notif: %u\n",
__func__, recv_buff_size - sizeof(*hdr));
ret = -EINVAL;
goto err;
}
ret = hh_rm_process_notif_vm_status(recv_buff, recv_buff_size);
break;
case HH_RM_NOTIF_VM_IRQ_LENT:
if (recv_buff_size != sizeof(*hdr) +
sizeof(struct hh_rm_notif_vm_irq_lent_payload)) {
pr_err("%s: Invalid size for VM_IRQ_LENT notif: %u\n",
__func__, recv_buff_size - sizeof(*hdr));
ret = -EINVAL;
goto err;
}
break;
case HH_RM_NOTIF_VM_IRQ_RELEASED:
if (recv_buff_size != sizeof(*hdr) +
sizeof(struct hh_rm_notif_vm_irq_released_payload)) {
pr_err("%s: Invalid size for VM_IRQ_REL notif: %u\n",
__func__, recv_buff_size - sizeof(*hdr));
ret = -EINVAL;
goto err;
}
break;
case HH_RM_NOTIF_MEM_SHARED:
if (recv_buff_size < sizeof(*hdr) +
sizeof(struct hh_rm_notif_mem_shared_payload)) {
pr_err("%s: Invalid size for MEM_SHARED notif: %u\n",
__func__, recv_buff_size - sizeof(*hdr));
ret = -EINVAL;
goto err;
}
break;
case HH_RM_NOTIF_MEM_RELEASED:
if (recv_buff_size != sizeof(*hdr) +
sizeof(struct hh_rm_notif_mem_released_payload)) {
pr_err("%s: Invalid size for MEM_RELEASED notif: %u\n",
__func__, recv_buff_size - sizeof(*hdr));
ret = -EINVAL;
goto err;
}
break;
case HH_RM_NOTIF_VM_CONSOLE_CHARS:
if (recv_buff_size < sizeof(*hdr) +
sizeof(struct hh_rm_notif_vm_console_chars)) {
struct hh_rm_notif_vm_console_chars *console_chars;
u16 num_bytes;
console_chars = recv_buff + sizeof(*hdr);
num_bytes = console_chars->num_bytes;
if (sizeof(*hdr) + sizeof(*console_chars) + num_bytes !=
recv_buff_size) {
pr_err("%s: Invalid size for VM_CONSOLE_CHARS notify %u\n",
__func__, recv_buff_size - sizeof(*hdr));
ret = -EINVAL;
goto err;
}
}
break;
default:
pr_err("%s: Unknown notification received: %u\n", __func__,
notification);
ret = -EINVAL;
goto err;
}
srcu_notifier_call_chain(&hh_rm_notifier, notification, payload);
err:
if (connection) {
kfree(connection->recv_buff);
kfree(connection);
}
return ret;
}
static int hh_rm_process_rply(void *recv_buff, size_t recv_buff_size)
{
struct hh_rm_rpc_reply_hdr *reply_hdr = recv_buff;
struct hh_rm_rpc_hdr *hdr = recv_buff;
struct hh_rm_connection *connection;
size_t payload_size;
int ret = 0;
if (mutex_lock_interruptible(&hh_rm_call_idr_lock)) {
ret = -ERESTARTSYS;
goto out;
}
connection = idr_find(&hh_rm_call_idr, hdr->seq);
mutex_unlock(&hh_rm_call_idr_lock);
if (!connection || connection->seq != hdr->seq) {
pr_err("%s: Failed to get the connection info for seq: %d\n",
__func__, hdr->seq);
ret = -EINVAL;
goto out;
}
payload_size = recv_buff_size - sizeof(*reply_hdr);
ret = hh_rm_init_connection_buff(connection, recv_buff,
sizeof(*reply_hdr), payload_size);
if (ret < 0)
return ret;
connection->reply_err_code = reply_hdr->err_code;
/*
* If the data is composed of a single message, wakeup the
* receiver immediately.
*
* Else, if the data is split into multiple fragments, fill
* this buffer as and when the fragments arrive, and finally
* wakeup the receiver upon reception of the last fragment.
*/
if (!hdr->fragments)
complete(&connection->seq_done);
out:
return ret;
}
static int hh_rm_process_cont(void *recv_buff, size_t recv_buff_size)
{
struct hh_rm_rpc_hdr *hdr = recv_buff;
struct hh_rm_connection *connection;
size_t payload_size;
if (mutex_lock_interruptible(&hh_rm_call_idr_lock))
return -ERESTARTSYS;
connection = idr_find(&hh_rm_call_idr, hdr->seq);
mutex_unlock(&hh_rm_call_idr_lock);
/* The seq number should be the same for all the fragments */
if (!connection || connection->seq != hdr->seq) {
pr_err("%s: Failed to get the connection info for seq: %d\n",
__func__, hdr->seq);
return -EINVAL;
}
/*
* hdr->fragments preserves the value from the first 'reply/notif'
* message. For the sake of sanity, check if it's still intact.
*/
if (connection->num_fragments != hdr->fragments) {
pr_err("%s: Number of fragments mismatch for seq: %d\n",
__func__, hdr->seq);
return -EINVAL;
}
payload_size = recv_buff_size - sizeof(*hdr);
/* Keep appending the data to the previous fragment's end */
memcpy(connection->current_recv_buff,
recv_buff + sizeof(*hdr), payload_size);
connection->current_recv_buff += payload_size;
connection->recv_buff_size += payload_size;
connection->fragments_received++;
if (connection->fragments_received == connection->num_fragments)
complete(&connection->seq_done);
return 0;
}
struct hh_rm_msgq_data {
void *recv_buff;
size_t recv_buff_size;
struct work_struct recv_work;
};
static void hh_rm_process_recv_work(struct work_struct *work)
{
struct hh_rm_msgq_data *msgq_data;
struct hh_rm_rpc_hdr *hdr;
void *recv_buff;
msgq_data = container_of(work, struct hh_rm_msgq_data, recv_work);
recv_buff = hdr = msgq_data->recv_buff;
switch (hdr->type) {
case HH_RM_RPC_TYPE_NOTIF:
hh_rm_process_notif(recv_buff, msgq_data->recv_buff_size);
break;
case HH_RM_RPC_TYPE_RPLY:
hh_rm_process_rply(recv_buff, msgq_data->recv_buff_size);
break;
case HH_RM_RPC_TYPE_CONT:
hh_rm_process_cont(recv_buff, msgq_data->recv_buff_size);
break;
default:
pr_err("%s: Invalid message type (%d) received\n",
__func__, hdr->type);
}
/* All the processing functions would have trimmed-off the header
* and copied the data to connection->recv_buff. Hence, it's okay
* to release the original packet that arrived.
*/
kfree(recv_buff);
}
static int hh_rm_recv_task_fn(void *data)
{
struct hh_rm_msgq_data *msgq_data;
size_t recv_buff_size;
void *recv_buff;
int ret;
while (!kthread_should_stop()) {
/* Block until a new message is received */
ret = hh_msgq_recv(hh_rm_msgq_desc, &recv_buff,
&recv_buff_size, 0);
if (ret < 0) {
pr_err("%s: Failed to receive the message: %d\n",
__func__, ret);
continue;
} else if (recv_buff_size <= sizeof(struct hh_rm_rpc_hdr)) {
pr_err("%s: Invalid message size received\n", __func__);
kfree(recv_buff);
continue;
}
/* Process this message in the background, while leaving
* this thread to receive the next message.
*/
msgq_data = kzalloc(sizeof(*msgq_data), GFP_KERNEL);
if (!msgq_data) {
kfree(recv_buff);
continue;
}
msgq_data->recv_buff = recv_buff;
msgq_data->recv_buff_size = recv_buff_size;
INIT_WORK(&msgq_data->recv_work, hh_rm_process_recv_work);
schedule_work(&msgq_data->recv_work);
}
return 0;
}
static int hh_rm_send_request(u32 message_id,
const void *req_buff, size_t req_buff_size,
struct hh_rm_connection *connection)
{
size_t buff_size_remaining = req_buff_size;
const void *req_buff_curr = req_buff;
struct hh_rm_rpc_hdr *hdr;
unsigned long tx_flags;
u8 num_fragments = 0;
size_t payload_size;
void *send_buff;
int i, ret;
num_fragments = (req_buff_size + HH_RM_MAX_MSG_SIZE_BYTES - 1) /
HH_RM_MAX_MSG_SIZE_BYTES;
/* The above calculation also includes the count
* for the 'request' packet. Exclude it as the
* header needs to fill the num. of fragments to follow.
*/
num_fragments--;
if (num_fragments > HH_RM_MAX_NUM_FRAGMENTS) {
pr_err("%s: Limit exceeded for the number of fragments: %u\n",
__func__, num_fragments);
return -E2BIG;
}
/* Consider also the 'request' packet for the loop count */
for (i = 0; i <= num_fragments; i++) {
if (buff_size_remaining > HH_RM_MAX_MSG_SIZE_BYTES) {
payload_size = HH_RM_MAX_MSG_SIZE_BYTES;
buff_size_remaining -= payload_size;
} else {
payload_size = buff_size_remaining;
}
send_buff = kzalloc(sizeof(*hdr) + payload_size, GFP_KERNEL);
if (!send_buff)
return -ENOMEM;
hdr = send_buff;
hdr->version = HH_RM_RPC_HDR_VERSION_ONE;
hdr->hdr_words = HH_RM_RPC_HDR_WORDS;
hdr->type = i == 0 ? HH_RM_RPC_TYPE_REQ : HH_RM_RPC_TYPE_CONT;
hdr->fragments = num_fragments;
hdr->seq = connection->seq;
hdr->msg_id = message_id;
memcpy(send_buff + sizeof(*hdr), req_buff_curr, payload_size);
req_buff_curr += payload_size;
/* Force the last fragment (or the request type)
* to be sent immediately to the receiver
*/
tx_flags = (i == num_fragments) ? HH_MSGQ_TX_PUSH : 0;
ret = hh_msgq_send(hh_rm_msgq_desc, send_buff,
sizeof(*hdr) + payload_size, tx_flags);
if (ret) {
kfree(send_buff);
return ret;
}
}
return 0;
}
/**
* hh_rm_call: Achieve request-response type communication with RPC
* @message_id: The RM RPC message-id
* @req_buff: Request buffer that contains the payload
* @req_buff_size: Total size of the payload
* @resp_buff_size: Size of the response buffer
* @reply_err_code: Returns Haven standard error code for the response
*
* Make a request to the RM-VM and expect a reply back. For a successful
* response, the function returns the payload and its size for the response.
* Some of the reply types doesn't contain any payload, in which case, the
* caller would see a NULL returned. Hence, it's recommended that the caller
* first read the error code and then dereference the returned payload
* (if applicable). Also, the caller should kfree the returned pointer
* when done.
*/
void *hh_rm_call(hh_rm_msgid_t message_id,
void *req_buff, size_t req_buff_size,
size_t *resp_buff_size, int *reply_err_code)
{
struct hh_rm_connection *connection;
int req_ret;
void *ret;
if (!message_id || !req_buff || !resp_buff_size || !reply_err_code)
return ERR_PTR(-EINVAL);
connection = hh_rm_alloc_connection(-1);
if (IS_ERR_OR_NULL(connection))
return connection;
/* Send the request to the Resource Manager VM */
req_ret = hh_rm_send_request(message_id,
req_buff, req_buff_size,
connection);
if (req_ret < 0) {
ret = ERR_PTR(req_ret);
goto out;
}
/* Wait for response */
if (wait_for_completion_interruptible(&connection->seq_done)) {
ret = ERR_PTR(-ERESTARTSYS);
goto out;
}
*reply_err_code = connection->reply_err_code;
if (connection->reply_err_code) {
pr_err("%s: Reply for seq:%d failed with err: %d\n",
__func__, connection->seq, connection->reply_err_code);
ret = ERR_PTR(hh_remap_error(connection->reply_err_code));
goto out;
}
ret = connection->recv_buff;
*resp_buff_size = connection->recv_buff_size;
out:
kfree(connection);
return ret;
}
static int hh_rm_virq_to_linux_irq(u32 virq, u32 type, u32 trigger)
{
struct irq_fwspec fwspec = {};
fwspec.fwnode = of_node_to_fwnode(hh_rm_intc);
fwspec.param_count = 3;
fwspec.param[0] = type;
fwspec.param[1] = virq;
fwspec.param[2] = trigger;
return irq_create_fwspec_mapping(&fwspec);
}
static int hh_rm_get_irq(struct hh_vm_get_hyp_res_resp_entry *res_entry)
{
int ret, virq = res_entry->virq;
/* For resources, such as DBL source, there's no IRQ. The virq_handle
* wouldn't be defined for such cases. Hence ignore such cases
*/
if (!res_entry->virq_handle)
return 0;
/* Allocate and bind a new IRQ if RM-VM hasn't already done already */
if (virq == HH_RM_NO_IRQ_ALLOC) {
/* Get the next free vIRQ.
* Subtract 32 from the base virq to get the base SPI.
*/
ret = virq = ida_alloc_range(&hh_rm_free_virq_ida,
hh_rm_base_virq - 32,
GIC_V3_SPI_MAX, GFP_KERNEL);
if (ret < 0)
return ret;
/* Bind the vIRQ */
ret = hh_rm_vm_irq_accept(res_entry->virq_handle, virq);
if (ret < 0)
goto err;
}
return hh_rm_virq_to_linux_irq(virq, GIC_SPI, IRQ_TYPE_LEVEL_HIGH);
err:
ida_free(&hh_rm_free_virq_ida, virq);
return ret;
}
static int hh_rm_populate_hyp_res(void)
{
struct hh_vm_get_hyp_res_resp_entry *res_entries = NULL;
int linux_irq, ret = 0;
hh_capid_t cap_id;
hh_label_t label;
u32 n_res, i;
res_entries = hh_rm_vm_get_hyp_res(0, &n_res);
if (IS_ERR_OR_NULL(res_entries))
return PTR_ERR(res_entries);
for (i = 0; i < n_res; i++) {
ret = linux_irq = hh_rm_get_irq(&res_entries[i]);
if (ret < 0)
goto out;
cap_id = (u64) res_entries[i].cap_id_high << 32 |
res_entries[i].cap_id_low;
label = res_entries[i].resource_label;
/* Populate MessageQ & DBL's cap tables */
/* TODO: Handle DBL */
switch (res_entries[i].res_type) {
case HH_RM_RES_TYPE_MQ_TX:
ret = hh_msgq_populate_cap_info(label, cap_id,
HH_MSGQ_DIRECTION_TX, linux_irq);
break;
case HH_RM_RES_TYPE_MQ_RX:
ret = hh_msgq_populate_cap_info(label, cap_id,
HH_MSGQ_DIRECTION_RX, linux_irq);
break;
case HH_RM_RES_TYPE_DB_TX:
break;
case HH_RM_RES_TYPE_DB_RX:
break;
default:
pr_err("%s: Unknown resource type: %u\n",
__func__, res_entries[i].res_type);
ret = -EINVAL;
}
if (ret < 0)
goto out;
}
out:
kfree(res_entries);
return ret;
}
static void hh_rm_get_svm_res_work_fn(struct work_struct *work)
{
hh_rm_populate_hyp_res();
}
static const struct of_device_id hh_rm_drv_of_match[] = {
{ .compatible = "qcom,haven-resource-manager-1-0" },
{ }
};
static int hh_rm_drv_probe(struct platform_device *pdev)
{
struct resource *rm_tx_res, *rm_rx_res;
int tx_irq, rx_irq;
u32 owner_vmid;
int ret;
rm_tx_res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
"rm-tx-cap-id");
if (!rm_tx_res) {
dev_err(&pdev->dev, "Failed to get the Tx cap-id\n");
return -EINVAL;
}
rm_rx_res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
"rm-rx-cap-id");
if (!rm_rx_res) {
dev_err(&pdev->dev, "Failed to get the Rx cap-id\n");
return -EINVAL;
}
tx_irq = platform_get_irq_byname(pdev, "rm-tx-irq");
if (tx_irq < 0) {
dev_err(&pdev->dev, "Failed to get the Tx IRQ. ret: %d\n",
tx_irq);
return tx_irq;
}
rx_irq = platform_get_irq_byname(pdev, "rm-rx-irq");
if (rx_irq < 0) {
dev_err(&pdev->dev, "Failed to get the Rx IRQ. ret: %d\n",
rx_irq);
return rx_irq;
}
if (of_property_read_u32(pdev->dev.of_node, "qcom,virq-base",
&hh_rm_base_virq)) {
dev_err(&pdev->dev, "Failed to get the vIRQ base\n");
return -ENXIO;
}
hh_rm_intc = of_irq_find_parent(pdev->dev.of_node);
if (!hh_rm_intc) {
dev_err(&pdev->dev, "Failed to get the IRQ parent node\n");
return -ENXIO;
}
ret = hh_msgq_populate_cap_info(HH_MSGQ_LABEL_RM, rm_tx_res->start,
HH_MSGQ_DIRECTION_TX, tx_irq);
if (ret)
return ret;
ret = hh_msgq_populate_cap_info(HH_MSGQ_LABEL_RM, rm_rx_res->start,
HH_MSGQ_DIRECTION_RX, rx_irq);
if (ret)
return ret;
hh_rm_msgq_desc = hh_msgq_register(HH_MSGQ_LABEL_RM);
if (IS_ERR_OR_NULL(hh_rm_msgq_desc))
return PTR_ERR(hh_rm_msgq_desc);
/* As we don't have a callback for message reception yet,
* spawn a kthread and always listen to incoming messages.
*/
hh_rm_drv_recv_task = kthread_run(hh_rm_recv_task_fn,
NULL, "hh_rm_recv_task");
if (IS_ERR_OR_NULL(hh_rm_drv_recv_task)) {
ret = PTR_ERR(hh_rm_drv_recv_task);
goto err_recv_task;
}
/* If the node has a "qcom,owner-vmid" property, it means that it's
* a secondary-VM. Gain the info about it's resources here as there
* won't be any explicit notification from the primary-VM.
*/
if (!of_property_read_u32(pdev->dev.of_node, "qcom,owner-vmid",
&owner_vmid)) {
schedule_work(&hh_rm_get_svm_res_work);
}
return 0;
err_recv_task:
hh_msgq_unregister(hh_rm_msgq_desc);
return ret;
}
static int hh_rm_drv_remove(struct platform_device *pdev)
{
kthread_stop(hh_rm_drv_recv_task);
hh_msgq_unregister(hh_rm_msgq_desc);
idr_destroy(&hh_rm_call_idr);
return 0;
}
static struct platform_driver hh_rm_driver = {
.probe = hh_rm_drv_probe,
.remove = hh_rm_drv_remove,
.driver = {
.name = "hh_rm_driver",
.of_match_table = hh_rm_drv_of_match,
},
};
module_platform_driver(hh_rm_driver);
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("Qualcomm Technologies, Inc. Haven Resource Mgr. Driver");

View file

@ -0,0 +1,139 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2020, The Linux Foundation. All rights reserved.
*/
#ifndef __HH_RM_DRV_PRIVATE_H
#define __HH_RM_DRV_PRIVATE_H
#include <linux/types.h>
#include <linux/haven/hh_rm_drv.h>
#include <linux/haven/hh_common.h>
/* Resource Manager Header */
struct hh_rm_rpc_hdr {
u8 version:4,
hdr_words:4;
u8 type:2,
fragments:6;
u16 seq;
u32 msg_id;
} __packed;
/* Standard reply header */
struct hh_rm_rpc_reply_hdr {
struct hh_rm_rpc_hdr rpc_hdr;
u32 err_code;
} __packed;
/* RPC Header versions */
#define HH_RM_RPC_HDR_VERSION_ONE 0x1
/* RPC Header words */
#define HH_RM_RPC_HDR_WORDS 0x2
/* RPC Message types */
#define HH_RM_RPC_TYPE_CONT 0x0
#define HH_RM_RPC_TYPE_REQ 0x1
#define HH_RM_RPC_TYPE_RPLY 0x2
#define HH_RM_RPC_TYPE_NOTIF 0x3
/* RPC Message IDs */
/* Call type Message IDs that has a request/reply pattern */
/* Message IDs: Informative */
#define HH_RM_RPC_MSG_ID_CALL_GET_IDENT 0x00000001
#define HH_RM_RPC_MSG_ID_CALL_GET_FEATURES 0x00000002
/* Message IDs: Memory management */
#define HH_RM_RPC_MSG_ID_CALL_MEM_ACCEPT 0x51000011
#define HH_RM_RPC_MSG_ID_CALL_MEM_LEND 0x51000012
#define HH_RM_RPC_MSG_ID_CALL_MEM_SHARE 0x51000013
#define HH_RM_RPC_MSG_ID_CALL_MEM_RELEASE 0x51000014
#define HH_RM_RPC_MSG_ID_CALL_MEM_RECLAIM 0x51000015
#define HH_RM_RPC_MSG_ID_CALL_MEM_NOTIFY 0x51000017
/* Message IDs: extensions for hyp-assign */
#define HH_RM_RPC_MSG_ID_CALL_MEM_QCOM_LOOKUP_SGL 0x5100001A
/* Message IDs: VM Management */
#define HH_RM_RPC_MSG_ID_CALL_VM_ALLOCATE 0x56000001
#define HH_RM_RPC_MSG_ID_CALL_VM_START 0x56000004
/* Message IDs: VM Query */
#define HH_RM_RPC_MSG_ID_CALL_VM_GET_STATE 0x56000017
#define HH_RM_RPC_MSG_ID_CALL_VM_GET_HYP_RESOURCES 0x56000020
#define HH_RM_RPC_MSG_ID_CALL_VM_LOOKUP_HYP_CAPIDS 0x56000021
#define HH_RM_RPC_MSG_ID_CALL_VM_LOOKUP_HYP_IRQS 0X56000022
/* Message IDs: VM Configuration */
#define HH_RM_RPC_MSG_ID_CALL_VM_IRQ_ACCEPT 0x56000050
#define HH_RM_RPC_MSG_ID_CALL_VM_IRQ_LEND 0x56000051
#define HH_RM_RPC_MSG_ID_CALL_VM_IRQ_RELEASE 0x56000052
#define HH_RM_RPC_MSG_ID_CALL_VM_IRQ_RECLAIM 0x56000053
#define HH_RM_RPC_MSG_ID_CALL_VM_IRQ_NOTIFY 0x56000054
#define HH_RM_RPC_MSG_ID_CALL_VM_IRQ_UNMAP 0x56000055
/* Message IDs: VM Services */
#define HH_RM_RPC_MSG_ID_CALL_VM_SET_STATUS 0x56000080
#define HH_RM_RPC_MSG_ID_CALL_VM_CONSOLE_OPEN 0x56000081
#define HH_RM_RPC_MSG_ID_CALL_VM_CONSOLE_CLOSE 0x56000082
#define HH_RM_RPC_MSG_ID_CALL_VM_CONSOLE_WRITE 0x56000083
#define HH_RM_RPC_MSG_ID_CALL_VM_CONSOLE_FLUSH 0x56000084
/* Message IDs: VM-Host Query */
#define HH_RM_RPC_MSG_ID_CALL_VM_HOST_GET_TYPE 0x560000A0
/* End Call type Message IDs */
/* End RPC Message IDs */
/* Message ID headers */
/* Call: VM_GET_HYP_RESOURCES */
#define HH_RM_RES_TYPE_DB_TX 0
#define HH_RM_RES_TYPE_DB_RX 1
#define HH_RM_RES_TYPE_MQ_TX 2
#define HH_RM_RES_TYPE_MQ_RX 3
struct hh_vm_get_hyp_res_req_payload {
u16 vmid;
u16 reserved;
} __packed;
struct hh_vm_get_hyp_res_resp_entry {
u8 res_type;
u8 reserved;
u16 partner_vmid;
u32 resource_handle;
u32 resource_label;
u32 cap_id_low;
u32 cap_id_high;
u32 virq_handle;
u32 virq;
} __packed;
struct hh_vm_get_hyp_res_resp_payload {
u32 n_resource_entries;
struct hh_vm_get_hyp_res_resp_entry resp_entries[];
} __packed;
/* Call: VM_IRQ_ACCEPT */
struct hh_vm_irq_accept_req_payload {
hh_virq_handle_t virq_handle;
s32 virq;
} __packed;
struct hh_vm_irq_accept_resp_payload {
s32 virq;
} __packed;
/* End Message ID headers */
/* Common function declerations */
void *hh_rm_call(hh_rm_msgid_t message_id,
void *req_buff, size_t req_buff_size,
size_t *resp_buff_size, int *reply_err_code);
struct hh_vm_get_hyp_res_resp_entry *
hh_rm_vm_get_hyp_res(hh_vmid_t vmid, u32 *out_n_entries);
int hh_msgq_populate_cap_info(enum hh_msgq_label label, u64 cap_id,
int direction, int irq);
#endif /* __HH_RM_DRV_PRIVATE_H */

View file

@ -0,0 +1,127 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2020, The Linux Foundation. All rights reserved.
*
*/
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/haven/hh_msgq.h>
#include <linux/haven/hh_common.h>
#include "hh_rm_drv_private.h"
/**
* hh_rm_vm_get_hyp_res: Get info about a series of resources for this VM
* @vmid: vmid whose info is needed. Pass 0 for self
* @n_entries: The number of the resource entries that's returned to the caller
*
* The function returns an array of type 'struct hh_vm_get_hyp_res_resp_entry',
* in which each entry specifies info about a particular resource. The number
* of entries in the array is returned by 'n_entries'. The caller must kfree
* the returned pointer when done.
*
* The function encodes the error codes via ERR_PTR. Hence, the caller is
* responsible to check it with IS_ERR_OR_NULL().
*/
struct hh_vm_get_hyp_res_resp_entry *
hh_rm_vm_get_hyp_res(hh_vmid_t vmid, u32 *n_entries)
{
struct hh_vm_get_hyp_res_resp_payload *resp_payload;
struct hh_vm_get_hyp_res_req_payload req_payload = {
.vmid = vmid
};
struct hh_vm_get_hyp_res_resp_entry *resp_entries;
size_t resp_payload_size, resp_entries_size;
int err, reply_err_code;
if (!n_entries)
return ERR_PTR(-EINVAL);
resp_payload = hh_rm_call(HH_RM_RPC_MSG_ID_CALL_VM_GET_HYP_RESOURCES,
&req_payload, sizeof(req_payload),
&resp_payload_size, &reply_err_code);
if (reply_err_code || IS_ERR_OR_NULL(resp_payload)) {
err = PTR_ERR(resp_payload);
pr_err("%s: GET_HYP_RESOURCES failed with err: %d\n",
__func__, err);
return ERR_PTR(err);
}
/* The response payload should contain all the resource entries */
if (resp_payload_size < sizeof(*n_entries) ||
resp_payload_size != sizeof(*n_entries) +
(resp_payload->n_resource_entries * sizeof(*resp_entries))) {
pr_err("%s: Invalid size received for GET_HYP_RESOURCES: %u\n",
__func__, resp_payload_size);
resp_entries = ERR_PTR(-EINVAL);
goto out;
}
resp_entries_size = sizeof(*resp_entries) *
resp_payload->n_resource_entries;
resp_entries = kmemdup(resp_payload->resp_entries, resp_entries_size,
GFP_KERNEL);
if (!resp_entries) {
resp_entries = ERR_PTR(-ENOMEM);
goto out;
}
*n_entries = resp_payload->n_resource_entries;
out:
kfree(resp_payload);
return resp_entries;
}
/**
* hh_rm_vm_irq_accept: Bind the virq number to the supplied virq_handle
* @virq_handle: The virtual IRQ handle (for example, obtained via
* call to hh_rm_get_hyp_resources())
* @virq: The virtual IRQ number to bind to. Note that this is the virtual
* GIC IRQ number and not the linux IRQ number. Pass -1 here if the
* caller wants the Resource Manager VM to allocate a number
*
* If provided -1 for virq, the function returns the new IRQ number, else
* the one that was already provided.
*
* The function encodes the error codes via ERR_PTR. Hence, the caller is
* responsible to check it with IS_ERR_OR_NULL().
*/
int hh_rm_vm_irq_accept(hh_virq_handle_t virq_handle, int virq)
{
struct hh_vm_irq_accept_resp_payload *resp_payload;
struct hh_vm_irq_accept_req_payload req_payload;
size_t resp_payload_size;
int ret, reply_err_code;
/* -1 is valid for virq if requesting for a new number */
if (virq < -1)
return -EINVAL;
req_payload.virq_handle = virq_handle;
req_payload.virq = virq;
resp_payload = hh_rm_call(HH_RM_RPC_MSG_ID_CALL_VM_IRQ_ACCEPT,
&req_payload, sizeof(req_payload),
&resp_payload_size, &reply_err_code);
if (reply_err_code || IS_ERR_OR_NULL(resp_payload)) {
ret = PTR_ERR(resp_payload);
pr_err("%s: VM_IRQ_ACCEPT failed with err: %d\n",
__func__, ret);
return ret;
}
if (virq == -1 && resp_payload_size != sizeof(*resp_payload)) {
pr_err("%s: Invalid size received for VM_IRQ_ACCEPT: %u\n",
__func__, resp_payload_size);
ret = -EINVAL;
goto out;
}
ret = virq == -1 ? resp_payload->virq : virq;
out:
kfree(resp_payload);
return ret;
}

View file

@ -0,0 +1,119 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2020, The Linux Foundation. All rights reserved.
*
*/
#ifndef __HH_RM_DRV_H
#define __HH_RM_DRV_H
#include <linux/types.h>
#include "hh_common.h"
/* Notification type Message IDs */
/* Memory APIs */
#define HH_RM_NOTIF_MEM_SHARED 0x51100011
#define HH_RM_NOTIF_MEM_RELEASED 0x51100012
#define HH_RM_MEM_TYPE_NORMAL 0
#define HH_RM_MEM_TYPE_IO 1
#define HH_RM_TRANS_TYPE_DONATE 0
#define HH_RM_TRANS_TYPE_LEND 1
#define HH_RM_TRANS_TYPE_SHARE 2
#define HH_RM_ACL_X BIT(0)
#define HH_RM_ACL_W BIT(1)
#define HH_RM_ACL_R BIT(2)
struct hh_rm_mem_shared_acl_entry;
struct hh_rm_mem_shared_sgl_entry;
struct hh_rm_mem_shared_attr_entry;
struct hh_rm_notif_mem_shared_payload {
u32 mem_handle;
u8 mem_type;
u8 trans_type;
u8 flags;
u8 reserved1;
u16 owner_vmid;
u16 reserved2;
u32 label;
/* TODO: How to arrange multiple variable length struct arrays? */
} __packed;
struct hh_rm_mem_shared_acl_entry {
u16 acl_vmid;
u8 acl_rights;
u8 reserved;
} __packed;
struct hh_rm_mem_shared_sgl_entry {
u32 sgl_size_low;
u32 sgl_size_high;
} __packed;
struct hh_rm_mem_shared_attr_entry {
u16 attributes;
u16 attributes_vmid;
} __packed;
struct hh_rm_notif_mem_released_payload {
u32 mem_handle;
u16 participant_vmid;
u16 reserved;
} __packed;
/* VM APIs */
#define HH_RM_NOTIF_VM_STATUS 0x56100008
#define HH_RM_NOTIF_VM_IRQ_LENT 0x56100011
#define HH_RM_NOTIF_VM_IRQ_RELEASED 0x56100012
#define HH_RM_VM_STATUS_NO_STATE 0
#define HH_RM_VM_STATUS_RUNNING 1
#define HH_RM_VM_STATUS_PAUSED 2
#define HH_RM_VM_STATUS_SHUTDOWN 3
#define HH_RM_VM_STATUS_SHUTOFF 4
#define HH_RM_VM_STATUS_CRASHED 5
#define HH_RM_OS_STATUS_NONE 0
#define HH_RM_OS_STATUS_EARLY_BOOT 1
#define HH_RM_OS_STATUS_BOOT 2
#define HH_RM_OS_STATUS_INIT 3
#define HH_RM_OS_STATUS_RUN 4
struct hh_rm_notif_vm_status_payload {
u16 vmid;
u16 reserved;
u8 vm_status;
u8 os_status;
u16 app_status;
} __packed;
struct hh_rm_notif_vm_irq_lent_payload {
u16 owner_vmid;
u32 virq_handle;
u32 virq_label;
} __packed;
struct hh_rm_notif_vm_irq_released_payload {
u32 virq_handle;
} __packed;
/* VM Services */
#define HH_RM_NOTIF_VM_CONSOLE_CHARS 0X56100080
struct hh_rm_notif_vm_console_chars {
u16 vmid;
u16 num_bytes;
u8 bytes[0];
} __packed;
/* End Notification type APIs */
int hh_rm_register_notifier(struct notifier_block *nb);
int hh_rm_unregister_notifier(struct notifier_block *nb);
int hh_rm_vm_irq_accept(hh_virq_handle_t virq_handle, int virq);
#endif