mirror of
https://github.com/BobTheBlinker/android_kernel_motorola_sm6375.git
synced 2026-10-12 07:37:31 -04:00
Merge "haven: Add Haven Message Queue driver"
This commit is contained in:
commit
0390237bf3
11 changed files with 1033 additions and 0 deletions
57
arch/arm64/include/asm/haven/hcall.h
Normal file
57
arch/arm64/include/asm/haven/hcall.h
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Copyright (c) 2020, The Linux Foundation. All rights reserved.
|
||||
*/
|
||||
#ifndef __ASM_HH_HCALL_H
|
||||
#define __ASM_HH_HCALL_H
|
||||
|
||||
#include <linux/types.h>
|
||||
|
||||
#include <linux/haven/hcall_common.h>
|
||||
|
||||
/**
|
||||
* _hh_hcall: Performs an AArch64-specific call into hypervisor using Haven ABI
|
||||
* @hcall_num: Hypercall function ID to invoke
|
||||
* @args: Hypercall argument registers
|
||||
* @resp: Pointer to location to store response
|
||||
*/
|
||||
static inline int _hh_hcall(const hh_hcall_fnid_t hcall_num,
|
||||
const struct hh_hcall_args args,
|
||||
struct hh_hcall_resp *resp)
|
||||
{
|
||||
uint64_t _x18;
|
||||
|
||||
register uint64_t _x0 asm("x0") = args.arg0;
|
||||
register uint64_t _x1 asm("x1") = args.arg1;
|
||||
register uint64_t _x2 asm("x2") = args.arg2;
|
||||
register uint64_t _x3 asm("x3") = args.arg3;
|
||||
register uint64_t _x4 asm("x4") = args.arg4;
|
||||
register uint64_t _x5 asm("x5") = args.arg5;
|
||||
register uint64_t _x6 asm("x6") = args.arg6;
|
||||
register uint64_t _x7 asm("x7") = args.arg7;
|
||||
|
||||
asm volatile (
|
||||
"str x18, [%[_x18]]\n"
|
||||
"hvc %[num]\n"
|
||||
"ldr x18, [%[_x18]]\n"
|
||||
"str xzr, [%[_x18]]\n"
|
||||
: "+r"(_x0), "+r"(_x1), "+r"(_x2), "+r"(_x3), "+r"(_x4),
|
||||
"+r"(_x5), "+r"(_x6), "+r"(_x7)
|
||||
: [num] "i" (hcall_num), [_x18] "r"(&_x18)
|
||||
: "x9", "x10", "x11", "x12", "x13", "x14", "x15", "x16", "x17",
|
||||
"memory"
|
||||
);
|
||||
|
||||
resp->resp0 = _x0;
|
||||
resp->resp1 = _x1;
|
||||
resp->resp2 = _x2;
|
||||
resp->resp3 = _x3;
|
||||
resp->resp4 = _x4;
|
||||
resp->resp5 = _x5;
|
||||
resp->resp6 = _x6;
|
||||
resp->resp7 = _x7;
|
||||
|
||||
return _x0;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -32,4 +32,5 @@ config FSL_HV_MANAGER
|
|||
partition shuts down.
|
||||
|
||||
source "drivers/virt/vboxguest/Kconfig"
|
||||
source "drivers/virt/haven/Kconfig"
|
||||
endif
|
||||
|
|
|
|||
|
|
@ -5,3 +5,4 @@
|
|||
|
||||
obj-$(CONFIG_FSL_HV_MANAGER) += fsl_hypervisor.o
|
||||
obj-y += vboxguest/
|
||||
obj-y += haven/
|
||||
|
|
|
|||
27
drivers/virt/haven/Kconfig
Normal file
27
drivers/virt/haven/Kconfig
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
|
||||
menuconfig HAVEN_DRIVERS
|
||||
bool "Haven Virtualization drivers"
|
||||
help
|
||||
The Haven drivers are the helper interfaces that runs on the
|
||||
virtual machines that provides support such as memory/device
|
||||
sharing, IRQ sharing, IPC/signalling mechanisms, and so on.
|
||||
|
||||
Say Y here to enable the drivers needed to work on Haven
|
||||
virtualization environment.
|
||||
|
||||
If you say N, all options in this submenu will be skipped and disabled.
|
||||
|
||||
if HAVEN_DRIVERS
|
||||
|
||||
config HH_MSGQ
|
||||
tristate "Haven Message Queue driver"
|
||||
help
|
||||
Haven offers message-queues as one of the IPC mechanisms to
|
||||
communicate among the Virtual Machines. The message queue drivers
|
||||
runs on the Virtual machines to provide an interface to the clients
|
||||
who wish to communicate to other clients on a different VM. Currently,
|
||||
the services offered by the drivers is simply to send and receive
|
||||
messages in a blocking manner.
|
||||
|
||||
endif
|
||||
2
drivers/virt/haven/Makefile
Normal file
2
drivers/virt/haven/Makefile
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
obj-$(CONFIG_HH_MSGQ) += hh_msgq.o
|
||||
523
drivers/virt/haven/hh_msgq.c
Normal file
523
drivers/virt/haven/hh_msgq.c
Normal file
|
|
@ -0,0 +1,523 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (c) 2020, The Linux Foundation. All rights reserved.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/slab.h>
|
||||
#include <linux/wait.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/interrupt.h>
|
||||
|
||||
#include <linux/haven/hcall.h>
|
||||
#include <linux/haven/hh_msgq.h>
|
||||
#include <linux/haven/hh_errno.h>
|
||||
|
||||
/* HVC call specific mask: 0 to 31 */
|
||||
#define HH_MSGQ_HVC_FLAGS_MASK GENMASK_ULL(31, 0)
|
||||
|
||||
struct hh_msgq_desc {
|
||||
enum hh_msgq_label label;
|
||||
};
|
||||
|
||||
struct hh_msgq_cap_table {
|
||||
struct hh_msgq_desc *client_desc;
|
||||
struct mutex cap_entry_lock;
|
||||
|
||||
hh_capid_t tx_cap_id;
|
||||
hh_capid_t rx_cap_id;
|
||||
int tx_irq;
|
||||
int rx_irq;
|
||||
const char *tx_irq_name;
|
||||
const char *rx_irq_name;
|
||||
spinlock_t tx_lock;
|
||||
spinlock_t rx_lock;
|
||||
|
||||
bool tx_full;
|
||||
bool rx_empty;
|
||||
wait_queue_head_t tx_wq;
|
||||
wait_queue_head_t rx_wq;
|
||||
};
|
||||
|
||||
static struct hh_msgq_cap_table hh_msgq_cap_table[HH_MSGQ_LABEL_MAX];
|
||||
|
||||
static irqreturn_t hh_msgq_rx_isr(int irq, void *dev)
|
||||
{
|
||||
struct hh_msgq_cap_table *cap_table_entry = dev;
|
||||
|
||||
spin_lock(&cap_table_entry->rx_lock);
|
||||
cap_table_entry->rx_empty = false;
|
||||
spin_unlock(&cap_table_entry->rx_lock);
|
||||
|
||||
wake_up_interruptible(&cap_table_entry->rx_wq);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static irqreturn_t hh_msgq_tx_isr(int irq, void *dev)
|
||||
{
|
||||
struct hh_msgq_cap_table *cap_table_entry = dev;
|
||||
|
||||
spin_lock(&cap_table_entry->tx_lock);
|
||||
cap_table_entry->tx_full = false;
|
||||
spin_unlock(&cap_table_entry->tx_lock);
|
||||
|
||||
wake_up_interruptible(&cap_table_entry->tx_wq);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int __hh_msgq_recv(struct hh_msgq_cap_table *cap_table_entry,
|
||||
void *buff, size_t *size, u64 rx_flags)
|
||||
{
|
||||
struct hh_hcall_msgq_recv_resp resp = {};
|
||||
unsigned long flags;
|
||||
int hh_ret;
|
||||
int ret = 0;
|
||||
|
||||
/* Discard the driver specific flags, and keep only HVC specifics */
|
||||
rx_flags &= HH_MSGQ_HVC_FLAGS_MASK;
|
||||
|
||||
spin_lock_irqsave(&cap_table_entry->rx_lock, flags);
|
||||
hh_ret = hh_hcall_msgq_recv(cap_table_entry->rx_cap_id, buff,
|
||||
HH_MSGQ_MAX_MSG_SIZE_BYTES, &resp);
|
||||
|
||||
switch (hh_ret) {
|
||||
case HH_ERROR_OK:
|
||||
*size = resp.recv_size;
|
||||
ret = 0;
|
||||
break;
|
||||
case HH_ERROR_MSGQUEUE_EMPTY:
|
||||
cap_table_entry->rx_empty = true;
|
||||
ret = -EAGAIN;
|
||||
break;
|
||||
default:
|
||||
ret = hh_remap_error(hh_ret);
|
||||
}
|
||||
|
||||
spin_unlock_irqrestore(&cap_table_entry->rx_lock, flags);
|
||||
|
||||
if (ret != 0 && ret != -EAGAIN)
|
||||
pr_err("%s: Failed to recv the message. Error: %d\n",
|
||||
__func__, hh_ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* hh_msgq_recv: Receive a message from the client running on a different VM
|
||||
* @client_desc: The client descriptor that was obtained via hh_msgq_register()
|
||||
* @buff: Pointer to the buffer where the received data must be placed. Note
|
||||
* that the caller is responsible to free the data contained in buff
|
||||
* @size: The size of the buffer received
|
||||
* @flags: Optional flags to pass to receive the data. For the list of flags,
|
||||
* see linux/haven/hh_msgq.h
|
||||
*
|
||||
* The function returns -EINVAL if the caller passes invalid arguments, -EAGAIN
|
||||
* if the message queue is not yet ready to communicate, and -EPERM if the
|
||||
* caller doesn't have permissions to receive the data. 0 is the data is
|
||||
* successfully received.
|
||||
*/
|
||||
int hh_msgq_recv(void *msgq_client_desc,
|
||||
void **buff, size_t *size, unsigned long flags)
|
||||
{
|
||||
struct hh_msgq_desc *client_desc = msgq_client_desc;
|
||||
struct hh_msgq_cap_table *cap_table_entry;
|
||||
int ret;
|
||||
|
||||
if (!client_desc || !(*buff) || !size)
|
||||
return -EINVAL;
|
||||
|
||||
cap_table_entry = &hh_msgq_cap_table[client_desc->label];
|
||||
|
||||
if (mutex_lock_interruptible(&cap_table_entry->cap_entry_lock))
|
||||
return -ERESTARTSYS;
|
||||
|
||||
if (cap_table_entry->client_desc != client_desc) {
|
||||
pr_err("%s: Invalid client descriptor\n", __func__);
|
||||
ret = -EINVAL;
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (!cap_table_entry->rx_cap_id) {
|
||||
pr_err("%s: Recv info for label %d not yet initialized\n",
|
||||
__func__, client_desc->label);
|
||||
ret = -EAGAIN;
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (!cap_table_entry->rx_irq) {
|
||||
pr_err("%s: Rx IRQ for label %d not yet setup\n",
|
||||
__func__, client_desc->label);
|
||||
ret = -EAGAIN;
|
||||
goto err;
|
||||
}
|
||||
|
||||
mutex_unlock(&cap_table_entry->cap_entry_lock);
|
||||
|
||||
*buff = kzalloc(HH_MSGQ_MAX_MSG_SIZE_BYTES, GFP_KERNEL);
|
||||
if (!(*buff))
|
||||
return -ENOMEM;
|
||||
|
||||
do {
|
||||
if (cap_table_entry->rx_empty && (flags & HH_MSGQ_NONBLOCK)) {
|
||||
ret = -EAGAIN;
|
||||
goto buff_free;
|
||||
}
|
||||
|
||||
if (wait_event_interruptible(cap_table_entry->rx_wq,
|
||||
!cap_table_entry->rx_empty)) {
|
||||
ret = -ERESTARTSYS;
|
||||
goto buff_free;
|
||||
}
|
||||
|
||||
ret = __hh_msgq_recv(cap_table_entry, *buff, size, flags);
|
||||
} while (ret == -EAGAIN);
|
||||
|
||||
buff_free:
|
||||
if (ret < 0) {
|
||||
kfree(*buff);
|
||||
*buff = NULL;
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
||||
err:
|
||||
mutex_unlock(&cap_table_entry->cap_entry_lock);
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL(hh_msgq_recv);
|
||||
|
||||
static int __hh_msgq_send(struct hh_msgq_cap_table *cap_table_entry,
|
||||
void *buff, size_t size, u64 tx_flags)
|
||||
{
|
||||
struct hh_hcall_msgq_send_resp resp = {};
|
||||
unsigned long flags;
|
||||
int hh_ret;
|
||||
int ret = 0;
|
||||
|
||||
/* Discard the driver specific flags, and keep only HVC specifics */
|
||||
tx_flags &= HH_MSGQ_HVC_FLAGS_MASK;
|
||||
|
||||
spin_lock_irqsave(&cap_table_entry->tx_lock, flags);
|
||||
hh_ret = hh_hcall_msgq_send(cap_table_entry->tx_cap_id,
|
||||
size, buff, tx_flags, &resp);
|
||||
|
||||
switch (hh_ret) {
|
||||
case HH_ERROR_MSGQUEUE_FULL:
|
||||
cap_table_entry->tx_full = true;
|
||||
ret = -EAGAIN;
|
||||
break;
|
||||
default:
|
||||
ret = hh_remap_error(hh_ret);
|
||||
}
|
||||
|
||||
spin_unlock_irqrestore(&cap_table_entry->tx_lock, flags);
|
||||
|
||||
if (ret != 0 && ret != -EAGAIN)
|
||||
pr_err("%s: Failed to send the message. Error: %d\n",
|
||||
__func__, hh_ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* hh_msgq_send: Send a message to the client on a different VM
|
||||
* @client_desc: The client descriptor that was obtained via hh_msgq_register()
|
||||
* @buff: Pointer to the buffer that needs to be sent. The buffer should be
|
||||
* dynamically allocated via kmalloc/kzalloc.
|
||||
* @size: The size of the buffer
|
||||
* @flags: Optional flags to pass to send the data. For the list of flags,
|
||||
* see linux/haven/hh_msgq.h
|
||||
*
|
||||
* The function would free the buffer upon success, and returns 0. The caller
|
||||
* should not be referencing the buffer anymore.
|
||||
* On the other hand, it returns -EINVAL if the caller passes invalid arguments,
|
||||
* -EAGAIN if the message queue is not yet ready to communicate, and -EPERM if
|
||||
* the caller doesn't have permissions to send the data.
|
||||
*
|
||||
*/
|
||||
int hh_msgq_send(void *msgq_client_desc,
|
||||
void *buff, size_t size, unsigned long flags)
|
||||
{
|
||||
struct hh_msgq_desc *client_desc = msgq_client_desc;
|
||||
struct hh_msgq_cap_table *cap_table_entry;
|
||||
int ret;
|
||||
|
||||
if (!client_desc || !buff || !size)
|
||||
return -EINVAL;
|
||||
|
||||
if (size > HH_MSGQ_MAX_MSG_SIZE_BYTES)
|
||||
return -E2BIG;
|
||||
|
||||
cap_table_entry = &hh_msgq_cap_table[client_desc->label];
|
||||
|
||||
if (mutex_lock_interruptible(&cap_table_entry->cap_entry_lock))
|
||||
return -ERESTARTSYS;
|
||||
|
||||
if (cap_table_entry->client_desc != client_desc) {
|
||||
pr_err("%s: Invalid client descriptor\n", __func__);
|
||||
ret = -EINVAL;
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (!cap_table_entry->tx_cap_id) {
|
||||
pr_err("%s: Send info for label %d not yet initialized\n",
|
||||
__func__, client_desc->label);
|
||||
ret = -EAGAIN;
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (!cap_table_entry->tx_irq) {
|
||||
pr_err("%s: Tx IRQ for label %d not yet setup\n",
|
||||
__func__, client_desc->label);
|
||||
ret = -EAGAIN;
|
||||
goto err;
|
||||
}
|
||||
|
||||
mutex_unlock(&cap_table_entry->cap_entry_lock);
|
||||
|
||||
do {
|
||||
if (cap_table_entry->tx_full && (flags & HH_MSGQ_NONBLOCK)) {
|
||||
ret = -EAGAIN;
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (wait_event_interruptible(cap_table_entry->tx_wq,
|
||||
!cap_table_entry->tx_full))
|
||||
return -ERESTARTSYS;
|
||||
|
||||
ret = __hh_msgq_send(cap_table_entry, buff, size, flags);
|
||||
} while (ret == -EAGAIN);
|
||||
|
||||
/* If the send is success, hypervisor should not be holding any
|
||||
* references to 'buff', and hence, can be released.
|
||||
*/
|
||||
if (!ret)
|
||||
kfree(buff);
|
||||
|
||||
return ret;
|
||||
err:
|
||||
mutex_unlock(&cap_table_entry->cap_entry_lock);
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL(hh_msgq_send);
|
||||
|
||||
/**
|
||||
* hh_msgq_register: Register as a client to the use the message queue
|
||||
* @label: The label associated to the message queue that the client wants
|
||||
* to communicate
|
||||
*
|
||||
* The function returns a descriptor for the clients to send and receive the
|
||||
* messages. Else, returns -EBUSY if some other client is already regitsered
|
||||
* to this label, and -EINVAL for invalid arguments. The caller should check
|
||||
* the return value using IS_ERR_OR_NULL() and PTR_ERR() to extract the error
|
||||
* code.
|
||||
*/
|
||||
void *hh_msgq_register(enum hh_msgq_label label)
|
||||
{
|
||||
struct hh_msgq_cap_table *cap_table_entry;
|
||||
struct hh_msgq_desc *client_desc;
|
||||
int ret;
|
||||
|
||||
if (label < 0 || label >= HH_MSGQ_LABEL_MAX)
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
||||
cap_table_entry = &hh_msgq_cap_table[label];
|
||||
|
||||
if (mutex_lock_interruptible(&cap_table_entry->cap_entry_lock))
|
||||
return ERR_PTR(-ERESTARTSYS);
|
||||
|
||||
/* Multiple clients cannot register to the same label (msgq) */
|
||||
if (cap_table_entry->client_desc) {
|
||||
ret = -EBUSY;
|
||||
goto err;
|
||||
}
|
||||
|
||||
client_desc = kzalloc(sizeof(*client_desc), GFP_KERNEL);
|
||||
if (!client_desc) {
|
||||
ret = ENOMEM;
|
||||
goto err;
|
||||
}
|
||||
|
||||
client_desc->label = label;
|
||||
|
||||
cap_table_entry->client_desc = client_desc;
|
||||
|
||||
pr_info("hh_msgq: Registered client for label: %d\n", label);
|
||||
|
||||
mutex_unlock(&cap_table_entry->cap_entry_lock);
|
||||
return client_desc;
|
||||
|
||||
err:
|
||||
mutex_unlock(&cap_table_entry->cap_entry_lock);
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
EXPORT_SYMBOL(hh_msgq_register);
|
||||
|
||||
/**
|
||||
* hh_msgq_unregister: Unregister as a client to the use the message queue
|
||||
* @client_desc: The descriptor that was passed via hh_msgq_register()
|
||||
*
|
||||
* The function returns 0 is the client was unregistered successfully. Else,
|
||||
* -EINVAL for invalid arguments.
|
||||
*/
|
||||
int hh_msgq_unregister(void *msgq_client_desc)
|
||||
{
|
||||
struct hh_msgq_desc *client_desc = msgq_client_desc;
|
||||
struct hh_msgq_cap_table *cap_table_entry;
|
||||
|
||||
if (!client_desc)
|
||||
return -EINVAL;
|
||||
|
||||
cap_table_entry = &hh_msgq_cap_table[client_desc->label];
|
||||
|
||||
if (mutex_lock_interruptible(&cap_table_entry->cap_entry_lock))
|
||||
return -ERESTARTSYS;
|
||||
|
||||
/* Is the client trying to free someone else's msgq? */
|
||||
if (cap_table_entry->client_desc != client_desc) {
|
||||
pr_err("%s: Trying to free invalid client descriptor!\n",
|
||||
__func__);
|
||||
mutex_unlock(&cap_table_entry->cap_entry_lock);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
cap_table_entry->client_desc = NULL;
|
||||
|
||||
mutex_unlock(&cap_table_entry->cap_entry_lock);
|
||||
|
||||
pr_info("%s: Unregistered client for label: %d\n",
|
||||
__func__, client_desc->label);
|
||||
|
||||
kfree(client_desc);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(hh_msgq_unregister);
|
||||
|
||||
int hh_msgq_populate_cap_info(enum hh_msgq_label label, u64 cap_id,
|
||||
int direction, int irq)
|
||||
{
|
||||
struct hh_msgq_cap_table *cap_table_entry;
|
||||
int ret;
|
||||
|
||||
if (label < 0 || label >= HH_MSGQ_LABEL_MAX) {
|
||||
pr_err("%s: Invalid label passed\n", __func__);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (irq < 0) {
|
||||
pr_err("%s: Invalid IRQ number passed\n", __func__);
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
cap_table_entry = &hh_msgq_cap_table[label];
|
||||
|
||||
if (mutex_lock_interruptible(&cap_table_entry->cap_entry_lock))
|
||||
return -ERESTARTSYS;
|
||||
|
||||
if (direction == HH_MSGQ_DIRECTION_TX) {
|
||||
ret = request_irq(irq, hh_msgq_tx_isr, 0,
|
||||
cap_table_entry->tx_irq_name, cap_table_entry);
|
||||
if (ret < 0)
|
||||
goto err;
|
||||
|
||||
cap_table_entry->tx_irq = irq;
|
||||
cap_table_entry->tx_cap_id = cap_id;
|
||||
} else if (direction == HH_MSGQ_DIRECTION_RX) {
|
||||
ret = request_irq(irq, hh_msgq_rx_isr, 0,
|
||||
cap_table_entry->rx_irq_name, cap_table_entry);
|
||||
if (ret < 0)
|
||||
goto err;
|
||||
|
||||
cap_table_entry->rx_cap_id = cap_id;
|
||||
cap_table_entry->rx_irq = irq;
|
||||
} else {
|
||||
pr_err("%s: Invalid direction passed\n", __func__);
|
||||
ret = -EINVAL;
|
||||
goto err;
|
||||
}
|
||||
|
||||
mutex_unlock(&cap_table_entry->cap_entry_lock);
|
||||
|
||||
pr_debug(
|
||||
"%s: label: %d; cap_id: %llu; dir: %d; irq: %d\n",
|
||||
__func__, label, cap_id, direction, irq);
|
||||
|
||||
return 0;
|
||||
|
||||
err:
|
||||
mutex_unlock(&cap_table_entry->cap_entry_lock);
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL(hh_msgq_populate_cap_info);
|
||||
|
||||
static void hh_msgq_cleanup(int begin_idx)
|
||||
{
|
||||
struct hh_msgq_cap_table *cap_table_entry;
|
||||
int i;
|
||||
|
||||
if (begin_idx >= HH_MSGQ_LABEL_MAX)
|
||||
begin_idx = HH_MSGQ_LABEL_MAX - 1;
|
||||
|
||||
for (i = begin_idx; i >= 0; i--) {
|
||||
cap_table_entry = &hh_msgq_cap_table[i];
|
||||
|
||||
kfree(cap_table_entry->tx_irq_name);
|
||||
kfree(cap_table_entry->rx_irq_name);
|
||||
|
||||
mutex_destroy(&cap_table_entry->cap_entry_lock);
|
||||
}
|
||||
}
|
||||
|
||||
static int __init hh_msgq_init(void)
|
||||
{
|
||||
struct hh_msgq_cap_table *cap_table_entry;
|
||||
int ret;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < HH_MSGQ_LABEL_MAX; i++) {
|
||||
cap_table_entry = &hh_msgq_cap_table[i];
|
||||
|
||||
cap_table_entry->tx_full = false;
|
||||
cap_table_entry->rx_empty = true;
|
||||
init_waitqueue_head(&cap_table_entry->tx_wq);
|
||||
init_waitqueue_head(&cap_table_entry->rx_wq);
|
||||
spin_lock_init(&cap_table_entry->tx_lock);
|
||||
spin_lock_init(&cap_table_entry->rx_lock);
|
||||
mutex_init(&cap_table_entry->cap_entry_lock);
|
||||
|
||||
cap_table_entry->tx_irq_name = kasprintf(GFP_KERNEL,
|
||||
"hh_msgq_tx_%d", i);
|
||||
if (!cap_table_entry->tx_irq_name) {
|
||||
ret = -ENOMEM;
|
||||
goto err;
|
||||
}
|
||||
|
||||
cap_table_entry->rx_irq_name = kasprintf(GFP_KERNEL,
|
||||
"hh_msgq_rx_%d", i);
|
||||
if (!cap_table_entry->rx_irq_name) {
|
||||
ret = -ENOMEM;
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err:
|
||||
hh_msgq_cleanup(i);
|
||||
return ret;
|
||||
}
|
||||
module_init(hh_msgq_init);
|
||||
|
||||
static void __exit hh_msgq_exit(void)
|
||||
{
|
||||
hh_msgq_cleanup(HH_MSGQ_LABEL_MAX - 1);
|
||||
}
|
||||
module_exit(hh_msgq_exit);
|
||||
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_DESCRIPTION("Qualcomm Technologies, Inc. Haven Message Queue Driver");
|
||||
273
include/linux/haven/hcall.h
Normal file
273
include/linux/haven/hcall.h
Normal file
|
|
@ -0,0 +1,273 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Copyright (c) 2020, The Linux Foundation. All rights reserved.
|
||||
*/
|
||||
#ifndef __HH_HCALL_H
|
||||
#define __HH_HCALL_H
|
||||
|
||||
#include <linux/err.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
#include <linux/haven/hcall_common.h>
|
||||
#include <linux/haven/hh_common.h>
|
||||
#include <asm/haven/hcall.h>
|
||||
|
||||
struct hh_hcall_hyp_identify_resp {
|
||||
u64 api_info;
|
||||
u64 flags[3];
|
||||
};
|
||||
|
||||
static inline int hh_hcall_hyp_identify(struct hh_hcall_hyp_identify_resp *resp)
|
||||
{
|
||||
int ret;
|
||||
struct hh_hcall_resp _resp = {0};
|
||||
|
||||
ret = _hh_hcall(0x6000,
|
||||
(struct hh_hcall_args){ 0 },
|
||||
&_resp);
|
||||
|
||||
if (!ret && resp) {
|
||||
resp->api_info = _resp.resp0;
|
||||
resp->flags[0] = _resp.resp1;
|
||||
resp->flags[1] = _resp.resp2;
|
||||
resp->flags[2] = _resp.resp3;
|
||||
}
|
||||
|
||||
return ret < 0 ? ret : 0;
|
||||
}
|
||||
|
||||
static inline int hh_hcall_dbl_bind(hh_capid_t dbl_capid, hh_capid_t vic_capid,
|
||||
hh_virq_handle_t virq_info)
|
||||
{
|
||||
int ret;
|
||||
struct hh_hcall_resp _resp = {0};
|
||||
|
||||
ret = _hh_hcall(0x6010,
|
||||
(struct hh_hcall_args){ dbl_capid, vic_capid, virq_info },
|
||||
&_resp);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline int hh_hcall_dbl_unbind(hh_capid_t dbl_capid)
|
||||
{
|
||||
int ret;
|
||||
struct hh_hcall_resp _resp = {0};
|
||||
|
||||
ret = _hh_hcall(0x6011,
|
||||
(struct hh_hcall_args){ dbl_capid },
|
||||
&_resp);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
struct hh_hcall_dbl_send_resp {
|
||||
u64 old_flags;
|
||||
};
|
||||
|
||||
static inline int hh_hcall_dbl_send(hh_capid_t dbl_capid,
|
||||
hh_dbl_flags_t new_flags,
|
||||
struct hh_hcall_dbl_send_resp *resp)
|
||||
{
|
||||
int ret;
|
||||
struct hh_hcall_resp _resp = {0};
|
||||
|
||||
ret = _hh_hcall(0x6012,
|
||||
(struct hh_hcall_args){ dbl_capid, new_flags },
|
||||
&_resp);
|
||||
|
||||
if (!ret && resp)
|
||||
resp->old_flags = _resp.resp1;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
struct hh_hcall_dbl_recv_resp {
|
||||
u64 old_flags;
|
||||
};
|
||||
|
||||
static inline int hh_hcall_dbl_recv(hh_capid_t dbl_capid,
|
||||
hh_dbl_flags_t clear_flags,
|
||||
struct hh_hcall_dbl_recv_resp *resp)
|
||||
{
|
||||
int ret;
|
||||
struct hh_hcall_resp _resp = {0};
|
||||
|
||||
ret = _hh_hcall(0x6013,
|
||||
(struct hh_hcall_args){ dbl_capid, clear_flags },
|
||||
&_resp);
|
||||
|
||||
if (!ret && resp)
|
||||
resp->old_flags = _resp.resp1;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline int hh_hcall_dbl_reset(hh_capid_t dbl_capid)
|
||||
{
|
||||
int ret;
|
||||
struct hh_hcall_resp _resp = {0};
|
||||
|
||||
ret = _hh_hcall(0x6014,
|
||||
(struct hh_hcall_args){ dbl_capid },
|
||||
&_resp);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline int hh_hcall_dbl_mask(hh_capid_t dbl_capid,
|
||||
hh_dbl_flags_t enable_mask,
|
||||
hh_dbl_flags_t ack_mask)
|
||||
{
|
||||
int ret;
|
||||
struct hh_hcall_resp _resp = {0};
|
||||
|
||||
ret = _hh_hcall(0x6015,
|
||||
(struct hh_hcall_args){ dbl_capid, enable_mask, ack_mask },
|
||||
&_resp);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline int hh_hcall_msgq_bind_send(hh_capid_t msgq_capid,
|
||||
hh_capid_t vic_capid,
|
||||
hh_virq_handle_t virq_info)
|
||||
{
|
||||
int ret;
|
||||
struct hh_hcall_resp _resp = {0};
|
||||
|
||||
ret = _hh_hcall(0x6017,
|
||||
(struct hh_hcall_args){ msgq_capid, vic_capid, virq_info },
|
||||
&_resp);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline int hh_hcall_msgq_bind_recv(hh_capid_t msgq_capid,
|
||||
hh_capid_t vic_capid,
|
||||
hh_virq_handle_t virq_info)
|
||||
{
|
||||
int ret;
|
||||
struct hh_hcall_resp _resp = {0};
|
||||
|
||||
ret = _hh_hcall(0x6018,
|
||||
(struct hh_hcall_args){ msgq_capid, vic_capid, virq_info },
|
||||
&_resp);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline int hh_hcall_msgq_unbind_send(hh_capid_t msgq_capid)
|
||||
{
|
||||
int ret;
|
||||
struct hh_hcall_resp _resp = {0};
|
||||
|
||||
ret = _hh_hcall(0x6019,
|
||||
(struct hh_hcall_args){ msgq_capid },
|
||||
&_resp);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline int hh_hcall_msgq_unbind_recv(hh_capid_t msgq_capid)
|
||||
{
|
||||
int ret;
|
||||
struct hh_hcall_resp _resp = {0};
|
||||
|
||||
ret = _hh_hcall(0x601A,
|
||||
(struct hh_hcall_args){ msgq_capid },
|
||||
&_resp);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
struct hh_hcall_msgq_send_resp {
|
||||
bool not_full;
|
||||
};
|
||||
|
||||
static inline int hh_hcall_msgq_send(hh_capid_t msgq_capid, size_t size,
|
||||
void *data, u64 send_flags,
|
||||
struct hh_hcall_msgq_send_resp *resp)
|
||||
{
|
||||
int ret;
|
||||
struct hh_hcall_resp _resp = {0};
|
||||
|
||||
ret = _hh_hcall(0x601B,
|
||||
(struct hh_hcall_args){ msgq_capid, size, (unsigned long)data,
|
||||
send_flags },
|
||||
&_resp);
|
||||
|
||||
if (resp)
|
||||
resp->not_full = _resp.resp1;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
struct hh_hcall_msgq_recv_resp {
|
||||
size_t recv_size;
|
||||
bool not_empty;
|
||||
};
|
||||
|
||||
static inline int hh_hcall_msgq_recv(hh_capid_t msgq_capid, void *buffer,
|
||||
size_t max_size,
|
||||
struct hh_hcall_msgq_recv_resp *resp)
|
||||
{
|
||||
int ret;
|
||||
struct hh_hcall_resp _resp = {0};
|
||||
|
||||
ret = _hh_hcall(0x601C,
|
||||
(struct hh_hcall_args){ msgq_capid, (unsigned long)buffer,
|
||||
max_size },
|
||||
&_resp);
|
||||
|
||||
if (!ret && resp) {
|
||||
resp->recv_size = _resp.resp1;
|
||||
resp->not_empty = _resp.resp2;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline int hh_hcall_msgq_flush(hh_capid_t msgq_capid)
|
||||
{
|
||||
int ret;
|
||||
struct hh_hcall_resp _resp = {0};
|
||||
|
||||
ret = _hh_hcall(0x601D,
|
||||
(struct hh_hcall_args){ msgq_capid },
|
||||
&_resp);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline int hh_hcall_msgq_configure_send(hh_capid_t msgq_capid,
|
||||
long not_full_threshold,
|
||||
long not_full_delay)
|
||||
{
|
||||
int ret;
|
||||
struct hh_hcall_resp _resp = {0};
|
||||
|
||||
ret = _hh_hcall(0x601F,
|
||||
(struct hh_hcall_args){ msgq_capid, not_full_threshold,
|
||||
not_full_delay, -1 },
|
||||
&_resp);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline int hh_hcall_msgq_configure_recv(hh_capid_t msgq_capid,
|
||||
long not_empty_threshold,
|
||||
long not_empty_delay)
|
||||
{
|
||||
int ret;
|
||||
struct hh_hcall_resp _resp = {0};
|
||||
|
||||
ret = _hh_hcall(0x6020,
|
||||
(struct hh_hcall_args){ msgq_capid, not_empty_threshold,
|
||||
not_empty_delay, -1 },
|
||||
&_resp);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#endif
|
||||
18
include/linux/haven/hcall_common.h
Normal file
18
include/linux/haven/hcall_common.h
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Copyright (c) 2020, The Linux Foundation. All rights reserved.
|
||||
*/
|
||||
#ifndef __HH_HCALL_COMMON_H
|
||||
#define __HH_HCALL_COMMON_H
|
||||
|
||||
struct hh_hcall_args {
|
||||
unsigned long arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7;
|
||||
};
|
||||
|
||||
struct hh_hcall_resp {
|
||||
unsigned long resp0, resp1, resp2, resp3, resp4, resp5, resp6, resp7;
|
||||
};
|
||||
|
||||
typedef u16 hh_hcall_fnid_t;
|
||||
|
||||
#endif
|
||||
18
include/linux/haven/hh_common.h
Normal file
18
include/linux/haven/hh_common.h
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Copyright (c) 2020, The Linux Foundation. All rights reserved.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef __HH_COMMON_H
|
||||
#define __HH_COMMON_H
|
||||
|
||||
/* Common Haven types */
|
||||
typedef u16 hh_vmid_t;
|
||||
typedef u32 hh_rm_msgid_t;
|
||||
typedef u32 hh_virq_handle_t;
|
||||
typedef u32 hh_label_t;
|
||||
typedef u64 hh_capid_t;
|
||||
typedef u64 hh_dbl_flags_t;
|
||||
|
||||
#endif
|
||||
76
include/linux/haven/hh_errno.h
Normal file
76
include/linux/haven/hh_errno.h
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Copyright (c) 2020, The Linux Foundation. All rights reserved.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef __HH_ERRNO_H
|
||||
#define __HH_ERRNO_H
|
||||
|
||||
#include <linux/errno.h>
|
||||
|
||||
#define HH_ERROR_OK 0
|
||||
#define HH_ERROR_UNIMPLEMENTED -1
|
||||
|
||||
#define HH_ERROR_ARG_INVAL 1
|
||||
#define HH_ERROR_ARG_SIZE 2
|
||||
#define HH_ERROR_ARG_ALIGN 3
|
||||
|
||||
#define HH_ERROR_NOMEM 10
|
||||
|
||||
#define HH_ERROR_ADDR_OVFL 20
|
||||
#define HH_ERROR_ADDR_UNFL 21
|
||||
#define HH_ERROR_ADDR_INVAL 22
|
||||
|
||||
#define HH_ERROR_DENIED 30
|
||||
#define HH_ERROR_BUSY 31
|
||||
#define HH_ERROR_IDLE 32
|
||||
|
||||
#define HH_ERROR_IRQ_BOUND 40
|
||||
#define HH_ERROR_IRQ_UNBOUND 41
|
||||
|
||||
#define HH_ERROR_CSPACE_CAP_NULL 50
|
||||
#define HH_ERROR_CSPACE_CAP_REVOKED 51
|
||||
#define HH_ERROR_CSPACE_WRONG_OBJ_TYPE 52
|
||||
#define HH_ERROR_CSPACE_INSUF_RIGHTS 53
|
||||
#define HH_ERROR_CSPACE_FULL 54
|
||||
|
||||
/* TODO: Get the correct values for the MSGQ error codes */
|
||||
#define HH_ERROR_MSGQUEUE_FULL 60
|
||||
#define HH_ERROR_MSGQUEUE_EMPTY 61
|
||||
|
||||
static inline int hh_remap_error(int hh_error)
|
||||
{
|
||||
switch (hh_error) {
|
||||
case HH_ERROR_OK:
|
||||
return 0;
|
||||
case HH_ERROR_NOMEM:
|
||||
return -ENOMEM;
|
||||
case HH_ERROR_DENIED:
|
||||
case HH_ERROR_CSPACE_CAP_NULL:
|
||||
case HH_ERROR_CSPACE_CAP_REVOKED:
|
||||
case HH_ERROR_CSPACE_WRONG_OBJ_TYPE:
|
||||
case HH_ERROR_CSPACE_INSUF_RIGHTS:
|
||||
case HH_ERROR_CSPACE_FULL:
|
||||
return -EACCES;
|
||||
case HH_ERROR_BUSY:
|
||||
case HH_ERROR_IDLE:
|
||||
return -EBUSY;
|
||||
case HH_ERROR_IRQ_BOUND:
|
||||
case HH_ERROR_IRQ_UNBOUND:
|
||||
case HH_ERROR_MSGQUEUE_FULL:
|
||||
case HH_ERROR_MSGQUEUE_EMPTY:
|
||||
return -EPERM;
|
||||
case HH_ERROR_UNIMPLEMENTED:
|
||||
case HH_ERROR_ARG_INVAL:
|
||||
case HH_ERROR_ARG_SIZE:
|
||||
case HH_ERROR_ARG_ALIGN:
|
||||
case HH_ERROR_ADDR_OVFL:
|
||||
case HH_ERROR_ADDR_UNFL:
|
||||
case HH_ERROR_ADDR_INVAL:
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
37
include/linux/haven/hh_msgq.h
Normal file
37
include/linux/haven/hh_msgq.h
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Copyright (c) 2020, The Linux Foundation. All rights reserved.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef __HH_MSGQ_H
|
||||
#define __HH_MSGQ_H
|
||||
|
||||
#include <linux/types.h>
|
||||
|
||||
#include "hh_common.h"
|
||||
|
||||
enum hh_msgq_label {
|
||||
HH_MSGQ_LABEL_RM,
|
||||
HH_MSGQ_LABEL_MAX
|
||||
};
|
||||
|
||||
#define HH_MSGQ_MAX_MSG_SIZE_BYTES 240
|
||||
|
||||
#define HH_MSGQ_DIRECTION_TX 0
|
||||
#define HH_MSGQ_DIRECTION_RX 1
|
||||
|
||||
/* Possible flags to pass for Tx or Rx */
|
||||
#define HH_MSGQ_TX_PUSH BIT(0)
|
||||
#define HH_MSGQ_NONBLOCK BIT(32)
|
||||
|
||||
void *hh_msgq_register(enum hh_msgq_label label);
|
||||
int hh_msgq_unregister(void *msgq_client_desc);
|
||||
int hh_msgq_send(void *msgq_client_desc,
|
||||
void *buff, size_t size, unsigned long flags);
|
||||
int hh_msgq_recv(void *msgq_client_desc,
|
||||
void **buff, size_t *size, unsigned long flags);
|
||||
|
||||
int hh_msgq_populate_cap_info(enum hh_msgq_label label, u64 cap_id,
|
||||
int direction, int irq);
|
||||
#endif
|
||||
Loading…
Reference in a new issue