Merge "gunyah: msgq: Porting messageq driver to pineapple"

This commit is contained in:
qctecmdr 2022-08-12 17:16:29 -07:00 committed by Gerrit - the friendly Code Review server
commit fcd1028dbb
7 changed files with 1730 additions and 0 deletions

View File

@ -24,4 +24,24 @@ config GH_VIRT_WATCHDOG
actions such as setting the bark/bite time and also petting the
watchdog in the hypervisor.
config GH_DBL
tristate "Gunyah Doorbell driver"
help
Gunyah offers a simple inter VMs(Virtual Machines) communication
through the use of doorbell interrupts. A single doorbell instance
provides an unidirectional communication between two VMs and it acts
like either a source(Tx) or generate(Rx). Individual VMs make use of
these doorbells by calling send and/or a receive primitives exposed by
driver and trigger an interrupt to each other and exchange the data.
config GH_MSGQ
tristate "Gunyah Message Queue driver"
help
Gunyah 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

View File

@ -1,2 +1,4 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_GH_VIRT_WATCHDOG)+= gh_virt_wdt.o
obj-$(CONFIG_GH_DBL) += gh_dbl.o
obj-$(CONFIG_GH_MSGQ) += gh_msgq.o

View File

@ -0,0 +1,712 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2020-2021, The Linux Foundation. All rights reserved.
*
*/
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/spinlock.h>
#include <linux/interrupt.h>
#include <linux/gunyah/gh_dbl.h>
#include <linux/gunyah/gh_errno.h>
#include "hcall_dbl.h"
struct gh_dbl_desc {
enum gh_dbl_label label;
};
enum gh_dbl_dir {
GH_DBL_DIRECTION_TX,
GH_DBL_DIRECTION_RX
};
struct gh_dbl_cap_table {
struct gh_dbl_desc *client_desc;
spinlock_t cap_entry_lock;
gh_capid_t tx_cap_id;
int tx_reg_done;
gh_capid_t rx_cap_id;
int rx_irq;
int rx_reg_done;
const char *rx_irq_name;
dbl_rx_cb_t rx_callback;
void *rx_priv_data;
wait_queue_head_t cap_wq;
};
static bool gh_dbl_initialized;
static struct gh_dbl_cap_table gh_dbl_cap_table[GH_DBL_LABEL_MAX];
/**
* gh_dbl_validate_params - Validate doorbell common parameters
*/
static int gh_dbl_validate_params(struct gh_dbl_desc *client_desc,
enum gh_dbl_dir dir, const unsigned long flags)
{
struct gh_dbl_cap_table *cap_table_entry;
int ret;
if (IS_ERR_OR_NULL(client_desc))
return -EINVAL;
/* Check if the client has manipulated the label */
if (client_desc->label < 0 || client_desc->label >= GH_DBL_LABEL_MAX)
return -EINVAL;
cap_table_entry = &gh_dbl_cap_table[client_desc->label];
spin_lock(&cap_table_entry->cap_entry_lock);
if (cap_table_entry->client_desc != client_desc) {
spin_unlock(&cap_table_entry->cap_entry_lock);
pr_err("%s: Invalid client descriptor\n", __func__);
return -EINVAL;
}
/*
* rx_cap_id == NULL and tx_cap_id == NULL means TWO things
* either "gh_dbl_populate_cap_info()" call from RM is not over
* or
* There are no doorbell setup for Tx or Rx
*/
if (dir == GH_DBL_DIRECTION_RX) {
if (!cap_table_entry->rx_reg_done) {
ret = -EINVAL;
goto err;
}
if ((cap_table_entry->rx_cap_id == GH_CAPID_INVAL) &&
(flags & GH_DBL_NONBLOCK)) {
ret = -EAGAIN;
goto err;
}
spin_unlock(&cap_table_entry->cap_entry_lock);
if (wait_event_interruptible(cap_table_entry->cap_wq,
cap_table_entry->rx_cap_id != GH_CAPID_INVAL))
return -ERESTARTSYS;
} else {
if (!cap_table_entry->tx_reg_done) {
ret = -EINVAL;
goto err;
}
if ((cap_table_entry->tx_cap_id == GH_CAPID_INVAL) &&
(flags & GH_DBL_NONBLOCK)) {
ret = -EAGAIN;
goto err;
}
spin_unlock(&cap_table_entry->cap_entry_lock);
if (wait_event_interruptible(cap_table_entry->cap_wq,
cap_table_entry->tx_cap_id != GH_CAPID_INVAL))
return -ERESTARTSYS;
}
return 0;
err:
spin_unlock(&cap_table_entry->cap_entry_lock);
return ret;
}
/**
* gh_dbl_read_and_clean - Automatically read and clear the flags in doorbell
* @client_desc: client handle to indetify the doorbell object
* @clear_flags: clear the bits mentioned in the clear_flags
* @flags: Optional flags to pass to send the data. For the list of flags,
* see linux/gunyah/gh_dbl.h
*
* Reads and clears the flags of the Doorbell object. If there is a pending
* bound virtual interrupt, it will be de-asserted
*
* Returns:
* 0 on success, @clear_flags contains the doorbells previous unmasked flags
* before the @clear_flags were removed.
*/
int gh_dbl_read_and_clean(void *dbl_client_desc, gh_dbl_flags_t *clear_flags,
const unsigned long flags)
{
struct gh_dbl_cap_table *cap_table_entry;
struct gh_hcall_dbl_recv_resp recv_resp;
struct gh_dbl_desc *client_desc = dbl_client_desc;
int ret, gh_ret;
if (!clear_flags)
return -EINVAL;
ret = gh_dbl_validate_params(client_desc, GH_DBL_DIRECTION_RX, flags);
if (ret)
return ret;
cap_table_entry = &gh_dbl_cap_table[client_desc->label];
gh_ret = gh_hcall_dbl_recv(cap_table_entry->rx_cap_id,
*clear_flags, &recv_resp);
ret = gh_remap_error(gh_ret);
if (ret != 0)
pr_err("%s: Hypercall failed, ret = %d\n", __func__, gh_ret);
else
*clear_flags = recv_resp.old_flags;
return ret;
}
EXPORT_SYMBOL(gh_dbl_read_and_clean);
/**
* gh_dbl_set_mask - Set doorbell object mask
* @client_desc: client handle to indetify the doorbell object
* @enable_mask: The mask of flags that will cause an assertion of
* the doorbell's bound virtual interrupt
* @ack_mask: Controls which flags should be automatically cleared
* when the interrupt is asserted
* @flags: Optional flags to pass to send the data. For the list of flags,
* see linux/gunyah/gh_dbl.h
*
* Sets the Doorbell objects masks. A doorbell object has two masks
* which are configured by the receiver to control which flags it is
* interested in, and which flags if any should be automatically acknowledged.
*
* Returns:
* 0 on success
*/
int gh_dbl_set_mask(void *dbl_client_desc, gh_dbl_flags_t enable_mask,
gh_dbl_flags_t ack_mask, const unsigned long flags)
{
struct gh_dbl_cap_table *cap_table_entry;
struct gh_dbl_desc *client_desc = dbl_client_desc;
int ret, gh_ret;
ret = gh_dbl_validate_params(client_desc, GH_DBL_DIRECTION_RX, flags);
if (ret)
return ret;
cap_table_entry = &gh_dbl_cap_table[client_desc->label];
gh_ret = gh_hcall_dbl_mask(cap_table_entry->rx_cap_id,
enable_mask, ack_mask);
ret = gh_remap_error(gh_ret);
if (ret != 0)
pr_err("%s: Hypercall failed ret = %d\n", __func__, gh_ret);
return ret;
}
EXPORT_SYMBOL(gh_dbl_set_mask);
/**
* gh_dbl_send - Set flags in the doorbell
* @client_desc: client handle to indetify the doorbell object
* @newflags: flags to set in the doorbell. This flag along with enable_mask
* in the doorbell decide whehter to raise vIRQ are not.
* @flags: Optional flags to pass to send the data. For the list of flags,
* see linux/gunyah/gh_dbl.h
*
* Set flags in the doorbell. If following the send, the set of enabled flags
* as defined by the bitwise-AND of the doorbell flags with the EnableMask,
* is non-zero, any bound virtual interrupt will be asserted.
*
* Returns:
* 0 on success, @newflags contains the doorbells previous unmasked flags
* before the @newflags were added.
*/
int gh_dbl_send(void *dbl_client_desc, gh_dbl_flags_t *newflags,
unsigned long flags)
{
struct gh_dbl_cap_table *cap_table_entry;
struct gh_hcall_dbl_send_resp send_resp;
struct gh_dbl_desc *client_desc = dbl_client_desc;
int ret, gh_ret;
if (!newflags)
return -EINVAL;
ret = gh_dbl_validate_params(client_desc, GH_DBL_DIRECTION_TX, flags);
if (ret)
return ret;
cap_table_entry = &gh_dbl_cap_table[client_desc->label];
gh_ret = gh_hcall_dbl_send(cap_table_entry->tx_cap_id, *newflags,
&send_resp);
ret = gh_remap_error(gh_ret);
if (ret != 0)
pr_err("%s: Hypercall failed ret = %d\n", __func__, gh_ret);
else
*newflags = send_resp.old_flags;
return ret;
}
EXPORT_SYMBOL(gh_dbl_send);
/**
* gh_dbl_reset - clear all the flags of the doorbell and sets all bits in
* the Doorbells mask.
* @client_desc: client handle to indetify the doorbell object
* @flags: Optional flags to pass to send the data. For the list of flags,
* see linux/gunyah/gh_dbl.h
*
* Clears all the flags of the doorbell and sets all bits in the doorbells
* mask. If there is a pending bound virtual interrupt, it will be de-asserted.
*
* Returns:
* 0 on success
*/
int gh_dbl_reset(void *dbl_client_desc, const unsigned long flags)
{
struct gh_dbl_cap_table *cap_table_entry;
struct gh_dbl_desc *client_desc = dbl_client_desc;
int ret, gh_ret;
ret = gh_dbl_validate_params(client_desc, GH_DBL_DIRECTION_RX, flags);
if (ret)
return ret;
cap_table_entry = &gh_dbl_cap_table[client_desc->label];
gh_ret = gh_hcall_dbl_reset(cap_table_entry->rx_cap_id);
ret = gh_remap_error(gh_ret);
if (ret != 0)
pr_err("%s: Hypercall failed ret = %d\n", __func__, gh_ret);
return ret;
}
EXPORT_SYMBOL(gh_dbl_reset);
static irqreturn_t gh_dbl_rx_callback_thread(int irq, void *data)
{
struct gh_dbl_cap_table *cap_table_entry = data;
if (!cap_table_entry->rx_callback)
return IRQ_HANDLED;
cap_table_entry->rx_callback(irq, cap_table_entry->rx_priv_data);
return IRQ_HANDLED;
}
/**
* gh_dbl_tx_register: Register as a Tx client to use the doorbell
* @label: The label associated to the doorbell that the client wants
* to send a message to other VM.
*
* The function returns a descriptor for the clients to send a message.
* Else, returns -EBUSY if some other client is already registered
* to this label, and -EINVAL for invalid arguments. The caller should check
* the return value using IS_ERR_OR_NULL() and PTR_ERR() to extract the error
* code.
*/
void *gh_dbl_tx_register(enum gh_dbl_label label)
{
struct gh_dbl_cap_table *cap_table_entry;
struct gh_dbl_desc *client_desc;
int ret;
if (label < 0 || label >= GH_DBL_LABEL_MAX)
return ERR_PTR(-EINVAL);
if (!gh_dbl_initialized)
return ERR_PTR(-EPROBE_DEFER);
cap_table_entry = &gh_dbl_cap_table[label];
spin_lock(&cap_table_entry->cap_entry_lock);
/* Avoid multiple client Tx registration for the same doorbell */
if (cap_table_entry->tx_reg_done) {
ret = -EBUSY;
goto err;
}
if (cap_table_entry->client_desc) {
client_desc = cap_table_entry->client_desc;
} else {
client_desc = kzalloc(sizeof(*client_desc), GFP_ATOMIC);
if (!client_desc) {
ret = -ENOMEM;
goto err;
}
client_desc->label = label;
cap_table_entry->client_desc = client_desc;
}
cap_table_entry->tx_reg_done = 1;
pr_debug("%s: Registered Tx client for label: %d\n", __func__, label);
spin_unlock(&cap_table_entry->cap_entry_lock);
return client_desc;
err:
spin_unlock(&cap_table_entry->cap_entry_lock);
return ERR_PTR(ret);
}
EXPORT_SYMBOL(gh_dbl_tx_register);
/**
* gh_dbl_rx_register: Register as a Rx client to use the doorbell
* @label: The label associated to the doorbell that the client wants
* to read a message.
* @rx_cb: Callback of the client when there is a vIRQ on doorbell
* @priv: Private data of the driver
*
* The function returns a descriptor for the clients to receive a message.
* Else, returns -EBUSY if some other client is already registered
* to this label, and -EINVAL for invalid arguments. The caller should check
* the return value using IS_ERR_OR_NULL() and PTR_ERR() to extract the error
* code.
*/
void *gh_dbl_rx_register(enum gh_dbl_label label, dbl_rx_cb_t rx_cb, void *priv)
{
struct gh_dbl_cap_table *cap_table_entry;
struct gh_dbl_desc *client_desc;
int ret;
if (label < 0 || label >= GH_DBL_LABEL_MAX)
return ERR_PTR(-EINVAL);
if (!gh_dbl_initialized)
return ERR_PTR(-EPROBE_DEFER);
cap_table_entry = &gh_dbl_cap_table[label];
spin_lock(&cap_table_entry->cap_entry_lock);
/* Avoid multiple client Rx registration for the same doorbell */
if (cap_table_entry->rx_reg_done) {
ret = -EBUSY;
goto err;
}
if (cap_table_entry->client_desc) {
client_desc = cap_table_entry->client_desc;
} else {
client_desc = kzalloc(sizeof(*client_desc), GFP_ATOMIC);
if (!client_desc) {
ret = -ENOMEM;
goto err;
}
client_desc->label = label;
cap_table_entry->client_desc = client_desc;
}
cap_table_entry->rx_callback = rx_cb;
cap_table_entry->rx_priv_data = priv;
cap_table_entry->rx_reg_done = 1;
pr_debug("%s: Registered Rx client for label: %d\n", __func__, label);
spin_unlock(&cap_table_entry->cap_entry_lock);
return client_desc;
err:
pr_debug("%s: Registration for Rx client for label failed: %d\n",
__func__, label);
spin_unlock(&cap_table_entry->cap_entry_lock);
return ERR_PTR(ret);
}
EXPORT_SYMBOL(gh_dbl_rx_register);
/**
* gh_dbl_tx_unregister: Unregister Tx client to use the doorbell
* @client_desc: The descriptor that was passed via gh_dbl_tx_register() or
* gh_dbl_rx_register()
*
* The function returns 0 is the client was unregistered successfully. Else,
* -EINVAL for invalid arguments.
*/
int gh_dbl_tx_unregister(void *dbl_client_desc)
{
struct gh_dbl_desc *client_desc = dbl_client_desc;
struct gh_dbl_cap_table *cap_table_entry;
if (IS_ERR_OR_NULL(client_desc))
return -EINVAL;
/* Check if the client has manipulated the label */
if (client_desc->label < 0 || client_desc->label >= GH_DBL_LABEL_MAX)
return -EINVAL;
cap_table_entry = &gh_dbl_cap_table[client_desc->label];
spin_lock(&cap_table_entry->cap_entry_lock);
/* Is the client trying to free someone else's doorbell? */
if (cap_table_entry->client_desc != client_desc) {
pr_err("%s: Trying to free invalid client descriptor!\n",
__func__);
spin_unlock(&cap_table_entry->cap_entry_lock);
return -EINVAL;
}
pr_debug("%s: Unregistering client for label: %d\n",
__func__, client_desc->label);
/* Rx client still holding the "client_desc". Do not remove now. */
if (!cap_table_entry->rx_reg_done) {
cap_table_entry->client_desc = NULL;
kfree(client_desc);
} else {
pr_debug("%s: Rx client holding the client_desc.\n", __func__);
}
cap_table_entry->tx_reg_done = 0;
spin_unlock(&cap_table_entry->cap_entry_lock);
return 0;
}
EXPORT_SYMBOL(gh_dbl_tx_unregister);
/**
* gh_dbl_rx_unregister: Unregister Rx client to use the doorbell
* @client_desc: The descriptor that was passed via gh_dbl_tx_register() or
* gh_dbl_rx_register()
*
* The function returns 0 is the client was unregistered successfully. Else,
* -EINVAL for invalid arguments.
*/
int gh_dbl_rx_unregister(void *dbl_client_desc)
{
struct gh_dbl_desc *client_desc = dbl_client_desc;
struct gh_dbl_cap_table *cap_table_entry;
if (IS_ERR_OR_NULL(client_desc))
return -EINVAL;
/* Check if the client has manipulated the label */
if (client_desc->label < 0 || client_desc->label >= GH_DBL_LABEL_MAX)
return -EINVAL;
cap_table_entry = &gh_dbl_cap_table[client_desc->label];
spin_lock(&cap_table_entry->cap_entry_lock);
/* Is the client trying to free someone else's doorbell? */
if (cap_table_entry->client_desc != client_desc) {
pr_err("%s: Trying to free invalid client descriptor!\n",
__func__);
spin_unlock(&cap_table_entry->cap_entry_lock);
return -EINVAL;
}
pr_debug("%s: Unregistering client for label: %d\n", __func__,
client_desc->label);
/* Tx client still holding the "client_desc". Do not remove now.*/
if (!cap_table_entry->tx_reg_done) {
cap_table_entry->client_desc = NULL;
kfree(client_desc);
} else {
pr_debug("%s: Tx client holding the client_desc.\n", __func__);
}
cap_table_entry->rx_callback = NULL;
cap_table_entry->rx_priv_data = NULL;
cap_table_entry->rx_reg_done = 0;
spin_unlock(&cap_table_entry->cap_entry_lock);
return 0;
}
EXPORT_SYMBOL(gh_dbl_rx_unregister);
/**
* This API is called by RM driver to populate doorbell objects
*/
int gh_dbl_populate_cap_info(enum gh_dbl_label label, u64 cap_id,
int direction, int rx_irq)
{
struct gh_dbl_cap_table *cap_table_entry;
int ret = 0;
if (!gh_dbl_initialized)
return -EAGAIN;
if (label < 0 || label >= GH_DBL_LABEL_MAX) {
pr_err("%s: Invalid label passed\n", __func__);
return -EINVAL;
}
cap_table_entry = &gh_dbl_cap_table[label];
switch (direction) {
case GH_DBL_DIRECTION_TX:
/* No interrupt should associated with Tx doorbell*/
if (rx_irq > 0) {
pr_err("%s: No IRQ associated for Tx doorbell!\n",
__func__);
ret = -ENXIO;
goto err;
}
spin_lock(&cap_table_entry->cap_entry_lock);
cap_table_entry->tx_cap_id = cap_id;
spin_unlock(&cap_table_entry->cap_entry_lock);
wake_up_interruptible(&cap_table_entry->cap_wq);
pr_debug("%s: label: %d; tx_cap_id: %llu; dir: %d; rx_irq: %d\n",
__func__, label, cap_id, direction, rx_irq);
break;
case GH_DBL_DIRECTION_RX:
if (rx_irq <= 0) {
pr_err("%s: Invalid IRQ number for Rx doorbell\n",
__func__);
ret = -ENXIO;
goto err;
}
cap_table_entry->rx_irq = rx_irq;
ret = request_threaded_irq(cap_table_entry->rx_irq,
NULL,
gh_dbl_rx_callback_thread,
IRQF_ONESHOT | IRQF_TRIGGER_RISING,
cap_table_entry->rx_irq_name,
cap_table_entry);
if (ret < 0) {
pr_err("%s: IRQ registration failed\n", __func__);
goto err;
}
irq_set_irq_wake(rx_irq, 1);
spin_lock(&cap_table_entry->cap_entry_lock);
cap_table_entry->rx_cap_id = cap_id;
spin_unlock(&cap_table_entry->cap_entry_lock);
wake_up_interruptible(&cap_table_entry->cap_wq);
pr_debug("%s: label: %d; rx_cap_id: %llu; dir: %d; rx_irq: %d\n",
__func__, label, cap_id, direction, rx_irq);
break;
default:
pr_err("%s: Invalid direction(%d) for doorbell\n",
__func__, direction);
ret = -EINVAL;
}
err:
return ret;
}
EXPORT_SYMBOL(gh_dbl_populate_cap_info);
/**
* This API is called by RM driver to free up doorbell objects
*/
int gh_dbl_reset_cap_info(enum gh_dbl_label label, int direction, int *irq)
{
struct gh_dbl_cap_table *cap_table_entry;
int ret = 0;
if (!gh_dbl_initialized)
return -EAGAIN;
if (label < 0 || label >= GH_DBL_LABEL_MAX) {
pr_err("%s: Invalid label passed\n", __func__);
return -EINVAL;
}
if (!irq)
return -EINVAL;
cap_table_entry = &gh_dbl_cap_table[label];
spin_lock(&cap_table_entry->cap_entry_lock);
switch (direction) {
case GH_DBL_DIRECTION_TX:
*irq = 0;
cap_table_entry->tx_cap_id = GH_CAPID_INVAL;
break;
case GH_DBL_DIRECTION_RX:
if (!cap_table_entry->rx_irq) {
pr_err("%s: Rx IRQ not setup\n", __func__);
ret = -ENXIO;
goto err_unlock;
}
*irq = cap_table_entry->rx_irq;
cap_table_entry->rx_irq = 0;
cap_table_entry->rx_cap_id = GH_CAPID_INVAL;
break;
default:
pr_err("%s: Invalid direction(%d) for doorbell\n",
__func__, direction);
ret = -EINVAL;
}
err_unlock:
spin_unlock(&cap_table_entry->cap_entry_lock);
if (*irq && !ret)
free_irq(*irq, cap_table_entry);
return ret;
}
EXPORT_SYMBOL(gh_dbl_reset_cap_info);
static void gh_dbl_cleanup(int begin_idx)
{
struct gh_dbl_cap_table *cap_table_entry;
int i;
if (begin_idx >= GH_DBL_LABEL_MAX)
begin_idx = GH_DBL_LABEL_MAX - 1;
for (i = begin_idx; i >= 0; i--) {
cap_table_entry = &gh_dbl_cap_table[i];
kfree(cap_table_entry->rx_irq_name);
}
}
static int __init gh_dbl_init(void)
{
struct gh_dbl_cap_table *entry;
int ret;
int i;
for (i = 0; i < GH_DBL_LABEL_MAX; i++) {
entry = &gh_dbl_cap_table[i];
spin_lock_init(&entry->cap_entry_lock);
init_waitqueue_head(&entry->cap_wq);
entry->tx_cap_id = GH_CAPID_INVAL;
entry->rx_cap_id = GH_CAPID_INVAL;
entry->rx_irq_name = kasprintf(GFP_KERNEL, "gh_dbl_rx_%d", i);
if (!entry->rx_irq_name) {
ret = -ENOMEM;
goto err;
}
}
gh_dbl_initialized = true;
return 0;
err:
gh_dbl_cleanup(i);
return ret;
}
module_init(gh_dbl_init);
static void __exit gh_dbl_exit(void)
{
gh_dbl_cleanup(GH_DBL_LABEL_MAX - 1);
}
module_exit(gh_dbl_exit);
MODULE_DESCRIPTION("Qualcomm Technologies, Inc. Gunyah Doorbell Driver");
MODULE_LICENSE("GPL");

View File

@ -0,0 +1,716 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2020-2021, The Linux Foundation. All rights reserved.
*
*/
#include <linux/slab.h>
#include <linux/wait.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/spinlock.h>
#include <linux/interrupt.h>
#include <linux/ratelimit.h>
#include <linux/gunyah/gh_msgq.h>
#include <linux/gunyah/gh_errno.h>
#include "hcall_msgq.h"
/* HVC call specific mask: 0 to 31 */
#define GH_MSGQ_HVC_FLAGS_MASK GENMASK_ULL(31, 0)
struct gh_msgq_cap_table;
struct gh_msgq_desc {
int label;
struct gh_msgq_cap_table *cap_table;
};
struct gh_msgq_cap_table {
struct gh_msgq_desc *client_desc;
spinlock_t cap_entry_lock;
gh_capid_t tx_cap_id;
gh_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;
int label;
struct list_head entry;
};
static LIST_HEAD(gh_msgq_cap_list);
static DEFINE_SPINLOCK(gh_msgq_cap_list_lock);
struct gh_msgq_cap_table *gh_msgq_alloc_entry(int label)
{
int ret;
struct gh_msgq_cap_table *cap_table_entry = NULL;
cap_table_entry = kzalloc(sizeof(struct gh_msgq_cap_table), GFP_ATOMIC);
if (!cap_table_entry)
return ERR_PTR(-ENOMEM);
cap_table_entry->tx_cap_id = GH_CAPID_INVAL;
cap_table_entry->rx_cap_id = GH_CAPID_INVAL;
cap_table_entry->tx_full = false;
cap_table_entry->rx_empty = true;
cap_table_entry->label = label;
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);
spin_lock_init(&cap_table_entry->cap_entry_lock);
cap_table_entry->tx_irq_name =
kasprintf(GFP_ATOMIC, "gh_msgq_tx_%d", label);
if (!cap_table_entry->tx_irq_name) {
ret = -ENOMEM;
goto err;
}
cap_table_entry->rx_irq_name =
kasprintf(GFP_ATOMIC, "gh_msgq_rx_%d", label);
if (!cap_table_entry->rx_irq_name) {
ret = -ENOMEM;
goto err;
}
list_add(&cap_table_entry->entry, &gh_msgq_cap_list);
return cap_table_entry;
err:
kfree(cap_table_entry->tx_irq_name);
kfree(cap_table_entry->rx_irq_name);
kfree(cap_table_entry);
return ERR_PTR(ret);
}
static irqreturn_t gh_msgq_rx_isr(int irq, void *dev)
{
struct gh_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 gh_msgq_tx_isr(int irq, void *dev)
{
struct gh_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 __gh_msgq_recv(struct gh_msgq_cap_table *cap_table_entry,
void *buff, size_t buff_size,
size_t *recv_size, u64 rx_flags)
{
struct gh_hcall_msgq_recv_resp resp = {};
unsigned long flags;
int gh_ret;
int ret = 0;
/* Discard the driver specific flags, and keep only HVC specifics */
rx_flags &= GH_MSGQ_HVC_FLAGS_MASK;
spin_lock_irqsave(&cap_table_entry->rx_lock, flags);
gh_ret = gh_hcall_msgq_recv(cap_table_entry->rx_cap_id, buff,
buff_size, &resp);
switch (gh_ret) {
case GH_ERROR_OK:
*recv_size = resp.recv_size;
cap_table_entry->rx_empty = !resp.not_empty;
ret = 0;
break;
case GH_ERROR_MSGQUEUE_EMPTY:
cap_table_entry->rx_empty = true;
ret = -EAGAIN;
break;
default:
ret = gh_remap_error(gh_ret);
}
spin_unlock_irqrestore(&cap_table_entry->rx_lock, flags);
if (ret != 0 && ret != -EAGAIN)
pr_err("%s: Failed to recv from msgq. Hypercall error: %d\n",
__func__, gh_ret);
return ret;
}
/**
* gh_msgq_recv: Receive a message from the client running on a different VM
* @client_desc: The client descriptor that was obtained via gh_msgq_register()
* @buff: Pointer to the buffer where the received data must be placed
* @buff_size: The size of the buffer space available
* @recv_size: The actual amount of data that is copied into buff
* @flags: Optional flags to pass to receive the data. For the list of flags,
* see linux/gunyah/gh_msgq.h
*
* The function returns 0 if the data is successfully received and recv_size
* would contain the actual amount of data copied into buff.
* 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 receive the data. In all these failure
* cases, recv_size is unmodified.
*
* Note: this function may sleep and should not be called from interrupt
* context
*/
int gh_msgq_recv(void *msgq_client_desc,
void *buff, size_t buff_size,
size_t *recv_size, unsigned long flags)
{
struct gh_msgq_desc *client_desc = msgq_client_desc;
struct gh_msgq_cap_table *cap_table_entry;
int ret;
if (!client_desc || !buff || !buff_size || !recv_size)
return -EINVAL;
if (buff_size > GH_MSGQ_MAX_MSG_SIZE_BYTES)
return -E2BIG;
if (client_desc->cap_table == NULL)
return -EAGAIN;
cap_table_entry = client_desc->cap_table;
spin_lock(&cap_table_entry->cap_entry_lock);
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 == GH_CAPID_INVAL) &&
(flags & GH_MSGQ_NONBLOCK)) {
pr_err_ratelimited(
"%s: Recv info for label %d not yet initialized\n",
__func__, client_desc->label);
ret = -EAGAIN;
goto err;
}
spin_unlock(&cap_table_entry->cap_entry_lock);
if (wait_event_interruptible(cap_table_entry->rx_wq,
cap_table_entry->rx_cap_id != GH_CAPID_INVAL))
return -ERESTARTSYS;
spin_lock(&cap_table_entry->cap_entry_lock);
if (!cap_table_entry->rx_irq) {
pr_err_ratelimited("%s: Rx IRQ for label %d not yet setup\n",
__func__, client_desc->label);
ret = -EAGAIN;
goto err;
}
spin_unlock(&cap_table_entry->cap_entry_lock);
do {
if (cap_table_entry->rx_empty && (flags & GH_MSGQ_NONBLOCK))
return -EAGAIN;
if (wait_event_interruptible(cap_table_entry->rx_wq,
!cap_table_entry->rx_empty))
return -ERESTARTSYS;
ret = __gh_msgq_recv(cap_table_entry, buff, buff_size,
recv_size, flags);
} while (ret == -EAGAIN);
if (!ret)
print_hex_dump_debug(__func__, DUMP_PREFIX_OFFSET,
4, 1, buff, *recv_size, false);
return ret;
err:
spin_unlock(&cap_table_entry->cap_entry_lock);
return ret;
}
EXPORT_SYMBOL(gh_msgq_recv);
static int __gh_msgq_send(struct gh_msgq_cap_table *cap_table_entry,
void *buff, size_t size, u64 tx_flags)
{
struct gh_hcall_msgq_send_resp resp = {};
unsigned long flags;
int gh_ret;
int ret = 0;
/* Discard the driver specific flags, and keep only HVC specifics */
tx_flags &= GH_MSGQ_HVC_FLAGS_MASK;
print_hex_dump_debug("gh_msgq_send: ", DUMP_PREFIX_OFFSET,
4, 1, buff, size, false);
spin_lock_irqsave(&cap_table_entry->tx_lock, flags);
gh_ret = gh_hcall_msgq_send(cap_table_entry->tx_cap_id,
size, buff, tx_flags, &resp);
switch (gh_ret) {
case GH_ERROR_OK:
cap_table_entry->tx_full = !resp.not_full;
ret = 0;
break;
case GH_ERROR_MSGQUEUE_FULL:
cap_table_entry->tx_full = true;
ret = -EAGAIN;
break;
default:
ret = gh_remap_error(gh_ret);
}
spin_unlock_irqrestore(&cap_table_entry->tx_lock, flags);
if (ret != 0 && ret != -EAGAIN)
pr_err("%s: Failed to send on msgq. Hypercall error: %d\n",
__func__, gh_ret);
return ret;
}
/**
* gh_msgq_send: Send a message to the client on a different VM
* @client_desc: The client descriptor that was obtained via gh_msgq_register()
* @buff: Pointer to the buffer that needs to be sent
* @size: The size of the buffer
* @flags: Optional flags to pass to send the data. For the list of flags,
* see linux/gunyah/gh_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 send the data.
*
*/
int gh_msgq_send(void *msgq_client_desc,
void *buff, size_t size, unsigned long flags)
{
struct gh_msgq_desc *client_desc = msgq_client_desc;
struct gh_msgq_cap_table *cap_table_entry;
int ret;
if (!client_desc || !buff || !size)
return -EINVAL;
if (size > GH_MSGQ_MAX_MSG_SIZE_BYTES)
return -E2BIG;
if (client_desc->cap_table == NULL)
return -EAGAIN;
cap_table_entry = client_desc->cap_table;
spin_lock(&cap_table_entry->cap_entry_lock);
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 == GH_CAPID_INVAL) &&
(flags & GH_MSGQ_NONBLOCK)) {
pr_err_ratelimited(
"%s: Send info for label %d not yet initialized\n",
__func__, client_desc->label);
ret = -EAGAIN;
goto err;
}
spin_unlock(&cap_table_entry->cap_entry_lock);
if (wait_event_interruptible(cap_table_entry->tx_wq,
cap_table_entry->tx_cap_id != GH_CAPID_INVAL))
return -ERESTARTSYS;
spin_lock(&cap_table_entry->cap_entry_lock);
if (!cap_table_entry->tx_irq) {
pr_err_ratelimited("%s: Tx IRQ for label %d not yet setup\n",
__func__, client_desc->label);
ret = -EAGAIN;
goto err;
}
spin_unlock(&cap_table_entry->cap_entry_lock);
do {
if (cap_table_entry->tx_full && (flags & GH_MSGQ_NONBLOCK))
return -EAGAIN;
if (wait_event_interruptible(cap_table_entry->tx_wq,
!cap_table_entry->tx_full))
return -ERESTARTSYS;
ret = __gh_msgq_send(cap_table_entry, buff, size, flags);
} while (ret == -EAGAIN);
return ret;
err:
spin_unlock(&cap_table_entry->cap_entry_lock);
return ret;
}
EXPORT_SYMBOL(gh_msgq_send);
/**
* gh_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 *gh_msgq_register(int label)
{
struct gh_msgq_cap_table *cap_table_entry = NULL, *tmp_entry;
struct gh_msgq_desc *client_desc;
if (label < 0)
return ERR_PTR(-EINVAL);
spin_lock(&gh_msgq_cap_list_lock);
list_for_each_entry(tmp_entry, &gh_msgq_cap_list, entry) {
if (label == tmp_entry->label) {
cap_table_entry = tmp_entry;
break;
}
}
if (cap_table_entry == NULL) {
cap_table_entry = gh_msgq_alloc_entry(label);
if (IS_ERR(cap_table_entry)) {
spin_unlock(&gh_msgq_cap_list_lock);
return cap_table_entry;
}
}
spin_unlock(&gh_msgq_cap_list_lock);
spin_lock(&cap_table_entry->cap_entry_lock);
/* Multiple clients cannot register to the same label (msgq) */
if (cap_table_entry->client_desc) {
spin_unlock(&cap_table_entry->cap_entry_lock);
pr_err("%s: Client already exists for label %d\n",
__func__, label);
return ERR_PTR(-EBUSY);
}
client_desc = kzalloc(sizeof(*client_desc), GFP_ATOMIC);
if (!client_desc) {
spin_unlock(&cap_table_entry->cap_entry_lock);
return ERR_PTR(-ENOMEM);
}
client_desc->label = label;
client_desc->cap_table = cap_table_entry;
cap_table_entry->client_desc = client_desc;
spin_unlock(&cap_table_entry->cap_entry_lock);
pr_info("gh_msgq: Registered client for label: %d\n", label);
return client_desc;
}
EXPORT_SYMBOL(gh_msgq_register);
/**
* gh_msgq_unregister: Unregister as a client to the use the message queue
* @client_desc: The descriptor that was passed via gh_msgq_register()
*
* The function returns 0 is the client was unregistered successfully. Else,
* -EINVAL for invalid arguments.
*/
int gh_msgq_unregister(void *msgq_client_desc)
{
struct gh_msgq_desc *client_desc = msgq_client_desc;
struct gh_msgq_cap_table *cap_table_entry;
if (!client_desc)
return -EINVAL;
cap_table_entry = client_desc->cap_table;
spin_lock(&cap_table_entry->cap_entry_lock);
/* 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__);
spin_unlock(&cap_table_entry->cap_entry_lock);
return -EINVAL;
}
cap_table_entry->client_desc = NULL;
spin_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(gh_msgq_unregister);
int gh_msgq_populate_cap_info(int label, u64 cap_id, int direction, int irq)
{
struct gh_msgq_cap_table *cap_table_entry = NULL, *tmp_entry;
int ret;
if (label < 0) {
pr_err("%s: Invalid label passed\n", __func__);
return -EINVAL;
}
if (irq < 0) {
pr_err("%s: Invalid IRQ number passed\n", __func__);
return -ENXIO;
}
spin_lock(&gh_msgq_cap_list_lock);
list_for_each_entry(tmp_entry, &gh_msgq_cap_list, entry) {
if (label == tmp_entry->label) {
cap_table_entry = tmp_entry;
break;
}
}
if (cap_table_entry == NULL) {
cap_table_entry = gh_msgq_alloc_entry(label);
if (IS_ERR(cap_table_entry)) {
spin_unlock(&gh_msgq_cap_list_lock);
return PTR_ERR(cap_table_entry);
}
}
spin_unlock(&gh_msgq_cap_list_lock);
if (direction == GH_MSGQ_DIRECTION_TX) {
ret = request_irq(irq, gh_msgq_tx_isr, 0,
cap_table_entry->tx_irq_name, cap_table_entry);
if (ret < 0)
goto err;
spin_lock(&cap_table_entry->cap_entry_lock);
cap_table_entry->tx_cap_id = cap_id;
cap_table_entry->tx_irq = irq;
spin_unlock(&cap_table_entry->cap_entry_lock);
wake_up_interruptible(&cap_table_entry->tx_wq);
} else if (direction == GH_MSGQ_DIRECTION_RX) {
ret = request_irq(irq, gh_msgq_rx_isr, 0,
cap_table_entry->rx_irq_name, cap_table_entry);
if (ret < 0)
goto err;
spin_lock(&cap_table_entry->cap_entry_lock);
cap_table_entry->rx_cap_id = cap_id;
cap_table_entry->rx_irq = irq;
spin_unlock(&cap_table_entry->cap_entry_lock);
wake_up_interruptible(&cap_table_entry->rx_wq);
} else {
pr_err("%s: Invalid direction passed\n", __func__);
ret = -EINVAL;
goto err;
}
irq_set_irq_wake(irq, 1);
pr_debug(
"%s: label: %d; cap_id: %llu; dir: %d; irq: %d\n",
__func__, label, cap_id, direction, irq);
return 0;
err:
spin_lock(&gh_msgq_cap_list_lock);
list_del(&cap_table_entry->entry);
spin_unlock(&gh_msgq_cap_list_lock);
kfree(cap_table_entry->tx_irq_name);
kfree(cap_table_entry->rx_irq_name);
kfree(cap_table_entry);
return ret;
}
EXPORT_SYMBOL(gh_msgq_populate_cap_info);
/**
* gh_msgq_reset_cap_info: Reset the msgq cap info
* @label: The label associated to the message queue that the client wants
* to communicate
* @direction: The direction of msgq
* @irq: The irq associated with the msgq
*
* The function resets all the msgq related info.
*/
int gh_msgq_reset_cap_info(enum gh_msgq_label label, int direction, int *irq)
{
struct gh_msgq_cap_table *cap_table_entry = NULL, *tmp_entry;
int ret;
if (label < 0) {
pr_err("%s: Invalid label passed\n", __func__);
return -EINVAL;
}
if (!irq)
return -EINVAL;
spin_lock(&gh_msgq_cap_list_lock);
list_for_each_entry(tmp_entry, &gh_msgq_cap_list, entry) {
if (label == tmp_entry->label) {
cap_table_entry = tmp_entry;
break;
}
}
spin_unlock(&gh_msgq_cap_list_lock);
if (cap_table_entry == NULL)
return -EINVAL;
if (direction == GH_MSGQ_DIRECTION_TX) {
if (!cap_table_entry->tx_irq) {
pr_err("%s: Tx IRQ not setup\n", __func__);
ret = -ENXIO;
goto err_unlock;
}
*irq = cap_table_entry->tx_irq;
spin_lock(&cap_table_entry->cap_entry_lock);
cap_table_entry->tx_cap_id = GH_CAPID_INVAL;
cap_table_entry->tx_irq = 0;
spin_unlock(&cap_table_entry->cap_entry_lock);
} else if (direction == GH_MSGQ_DIRECTION_RX) {
if (!cap_table_entry->rx_irq) {
pr_err("%s: Rx IRQ not setup\n", __func__);
ret = -ENXIO;
goto err_unlock;
}
*irq = cap_table_entry->rx_irq;
spin_lock(&cap_table_entry->cap_entry_lock);
cap_table_entry->rx_cap_id = GH_CAPID_INVAL;
cap_table_entry->rx_irq = 0;
spin_unlock(&cap_table_entry->cap_entry_lock);
} else {
pr_err("%s: Invalid direction passed\n", __func__);
ret = -EINVAL;
goto err_unlock;
}
if (*irq)
free_irq(*irq, cap_table_entry);
return 0;
err_unlock:
return ret;
}
EXPORT_SYMBOL(gh_msgq_reset_cap_info);
static int gh_msgq_probe_direction(struct platform_device *pdev, int label,
int direction, int idx)
{
int irq, ret;
u64 capid;
irq = platform_get_irq(pdev, idx);
if (irq < 0) {
dev_err(&pdev->dev, "Failed to get the IRQ%d. ret: %d\n",
idx, irq);
return irq;
}
ret = of_property_read_u64_index(pdev->dev.of_node, "reg", idx, &capid);
if (ret) {
dev_err(&pdev->dev, "Failed to get capid[%d]\n", idx);
return ret;
}
return gh_msgq_populate_cap_info(label, capid, direction, irq);
}
int gh_msgq_probe(struct platform_device *pdev, int label)
{
int ret, idx = 0;
struct device_node *node = pdev->dev.of_node;
bool duplex;
duplex = of_property_read_bool(node, "qcom,is-full-duplex");
if (duplex || of_property_read_bool(node, "qcom,is-sender")) {
ret = gh_msgq_probe_direction(pdev, label, GH_MSGQ_DIRECTION_TX,
idx);
if (ret)
return ret;
idx++;
}
if (duplex || of_property_read_bool(node, "qcom,is-receiver")) {
ret = gh_msgq_probe_direction(pdev, label, GH_MSGQ_DIRECTION_RX,
idx);
if (ret)
return ret;
}
return 0;
}
EXPORT_SYMBOL(gh_msgq_probe);
static void gh_msgq_cleanup(void)
{
struct gh_msgq_cap_table *cap_table_entry;
struct gh_msgq_cap_table *temp;
spin_lock(&gh_msgq_cap_list_lock);
list_for_each_entry_safe(cap_table_entry, temp, &gh_msgq_cap_list, entry) {
kfree(cap_table_entry->tx_irq_name);
kfree(cap_table_entry->rx_irq_name);
kfree(cap_table_entry);
}
spin_unlock(&gh_msgq_cap_list_lock);
}
static int __init gh_msgq_init(void)
{
return 0;
}
module_init(gh_msgq_init);
static void __exit gh_msgq_exit(void)
{
gh_msgq_cleanup();
}
module_exit(gh_msgq_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Qualcomm Technologies, Inc. Gunyah Message Queue Driver");

View File

@ -0,0 +1,55 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2021, The Linux Foundation. All rights reserved.
*/
#ifndef __GH_HCALL_CTRL_H
#define __GH_HCALL_CTRL_H
#include <linux/err.h>
#include <linux/types.h>
#include <linux/gunyah/hcall_common.h>
#include <linux/gunyah/gh_common.h>
#include <asm/gunyah/hcall.h>
struct gh_hcall_hyp_identify_resp {
u64 api_info;
u64 flags[3];
};
static inline int gh_hcall_hyp_identify(struct gh_hcall_hyp_identify_resp *resp)
{
struct gh_hcall_resp _resp = {0};
_gh_hcall(0x6000,
(struct gh_hcall_args){ 0 },
&_resp);
if (resp) {
resp->api_info = _resp.resp0;
resp->flags[0] = _resp.resp1;
resp->flags[1] = _resp.resp2;
resp->flags[2] = _resp.resp3;
}
return 0;
}
static inline int gh_hcall_trace_update_class_flags(
uint64_t set_flags, uint64_t clear_flags,
uint64_t *new_flags)
{
int ret;
struct gh_hcall_resp _resp = {0};
ret = _gh_hcall(0x603f,
(struct gh_hcall_args){ set_flags, clear_flags, 0 },
&_resp);
if (!ret && new_flags)
*new_flags = _resp.resp1;
return ret;
}
#endif

View File

@ -0,0 +1,94 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2021, The Linux Foundation. All rights reserved.
*/
#ifndef __GH_HCALL_DBL_H
#define __GH_HCALL_DBL_H
#include <linux/err.h>
#include <linux/types.h>
#include <linux/gunyah/hcall_common.h>
#include <linux/gunyah/gh_common.h>
#include <asm/gunyah/hcall.h>
static inline int gh_hcall_dbl_bind(gh_capid_t dbl_capid, gh_capid_t vic_capid,
gh_virq_handle_t virq_info)
{
struct gh_hcall_resp _resp = {0};
return _gh_hcall(0x6010,
(struct gh_hcall_args){ dbl_capid, vic_capid,
virq_info },
&_resp);
}
static inline int gh_hcall_dbl_unbind(gh_capid_t dbl_capid)
{
struct gh_hcall_resp _resp = {0};
return _gh_hcall(0x6011, (struct gh_hcall_args){ dbl_capid }, &_resp);
}
struct gh_hcall_dbl_send_resp {
u64 old_flags;
};
static inline int gh_hcall_dbl_send(gh_capid_t dbl_capid,
gh_dbl_flags_t new_flags,
struct gh_hcall_dbl_send_resp *resp)
{
int ret;
struct gh_hcall_resp _resp = {0};
ret = _gh_hcall(0x6012,
(struct gh_hcall_args){ dbl_capid, new_flags },
&_resp);
if (!ret && resp)
resp->old_flags = _resp.resp1;
return ret;
}
struct gh_hcall_dbl_recv_resp {
u64 old_flags;
};
static inline int gh_hcall_dbl_recv(gh_capid_t dbl_capid,
gh_dbl_flags_t clear_flags,
struct gh_hcall_dbl_recv_resp *resp)
{
int ret;
struct gh_hcall_resp _resp = {0};
ret = _gh_hcall(0x6013,
(struct gh_hcall_args){ dbl_capid, clear_flags },
&_resp);
if (!ret && resp)
resp->old_flags = _resp.resp1;
return ret;
}
static inline int gh_hcall_dbl_reset(gh_capid_t dbl_capid)
{
struct gh_hcall_resp _resp = {0};
return _gh_hcall(0x6014, (struct gh_hcall_args){ dbl_capid }, &_resp);
}
static inline int gh_hcall_dbl_mask(gh_capid_t dbl_capid,
gh_dbl_flags_t enable_mask,
gh_dbl_flags_t ack_mask)
{
struct gh_hcall_resp _resp = {0};
return _gh_hcall(0x6015,
(struct gh_hcall_args){ dbl_capid, enable_mask,
ack_mask },
&_resp);
}
#endif

View File

@ -0,0 +1,131 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2021, The Linux Foundation. All rights reserved.
*/
#ifndef __GH_HCALL_MSGQ_H
#define __GH_HCALL_MSGQ_H
#include <linux/err.h>
#include <linux/types.h>
#include <linux/gunyah/hcall_common.h>
#include <linux/gunyah/gh_common.h>
#include <asm/gunyah/hcall.h>
static inline int gh_hcall_msgq_bind_send(gh_capid_t msgq_capid,
gh_capid_t vic_capid,
gh_virq_handle_t virq_info)
{
struct gh_hcall_resp _resp = {0};
return _gh_hcall(0x6017,
(struct gh_hcall_args){ msgq_capid, vic_capid,
virq_info },
&_resp);
}
static inline int gh_hcall_msgq_bind_recv(gh_capid_t msgq_capid,
gh_capid_t vic_capid,
gh_virq_handle_t virq_info)
{
struct gh_hcall_resp _resp = {0};
return _gh_hcall(0x6018,
(struct gh_hcall_args){ msgq_capid, vic_capid,
virq_info },
&_resp);
}
static inline int gh_hcall_msgq_unbind_send(gh_capid_t msgq_capid)
{
struct gh_hcall_resp _resp = {0};
return _gh_hcall(0x6019, (struct gh_hcall_args){ msgq_capid }, &_resp);
}
static inline int gh_hcall_msgq_unbind_recv(gh_capid_t msgq_capid)
{
struct gh_hcall_resp _resp = {0};
return _gh_hcall(0x601A, (struct gh_hcall_args){ msgq_capid }, &_resp);
}
struct gh_hcall_msgq_send_resp {
bool not_full;
};
static inline int gh_hcall_msgq_send(gh_capid_t msgq_capid, size_t size,
void *data, u64 send_flags,
struct gh_hcall_msgq_send_resp *resp)
{
int ret;
struct gh_hcall_resp _resp = {0};
ret = _gh_hcall(0x601B,
(struct gh_hcall_args){ msgq_capid, size, (unsigned long)data,
send_flags },
&_resp);
if (!ret && resp)
resp->not_full = _resp.resp1;
return ret;
}
struct gh_hcall_msgq_recv_resp {
size_t recv_size;
bool not_empty;
};
static inline int gh_hcall_msgq_recv(gh_capid_t msgq_capid, void *buffer,
size_t max_size,
struct gh_hcall_msgq_recv_resp *resp)
{
int ret;
struct gh_hcall_resp _resp = {0};
ret = _gh_hcall(0x601C,
(struct gh_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 gh_hcall_msgq_flush(gh_capid_t msgq_capid)
{
struct gh_hcall_resp _resp = {0};
return _gh_hcall(0x601D, (struct gh_hcall_args){ msgq_capid }, &_resp);
}
static inline int gh_hcall_msgq_configure_send(gh_capid_t msgq_capid,
long not_full_threshold,
long not_full_delay)
{
struct gh_hcall_resp _resp = {0};
return _gh_hcall(0x601F,
(struct gh_hcall_args){ msgq_capid, not_full_threshold,
not_full_delay, -1 },
&_resp);
}
static inline int gh_hcall_msgq_configure_recv(gh_capid_t msgq_capid,
long not_empty_threshold,
long not_empty_delay)
{
struct gh_hcall_resp _resp = {0};
return _gh_hcall(0x6020,
(struct gh_hcall_args){ msgq_capid, not_empty_threshold,
not_empty_delay, -1 },
&_resp);
}
#endif