mirror of
https://github.com/torvalds/linux.git
synced 2026-07-31 11:37:06 +02:00
gunyah: dbl: Porting doorbell driver pineapple
This is just snapshot of kalama's vm doorbell driver from
'commit 5beb98404269 ("Merge "i3c: i3c-master-msm-geni: Fix
IBI for invalid device OR handler"")'from msm-5.15 branch.
Change-Id: I6445cab51bc03d6a18ac11faa206979b659e5ef7
Signed-off-by: Peng Yang <quic_penyan@quicinc.com>
This commit is contained in:
parent
d41dcee41c
commit
4bd7265ed2
|
|
@ -24,4 +24,14 @@ 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.
|
||||
|
||||
endif
|
||||
|
|
|
|||
|
|
@ -1,2 +1,3 @@
|
|||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
obj-$(CONFIG_GH_VIRT_WATCHDOG)+= gh_virt_wdt.o
|
||||
obj-$(CONFIG_GH_DBL) += gh_dbl.o
|
||||
|
|
|
|||
712
drivers/virt/gunyah/gh_dbl.c
Normal file
712
drivers/virt/gunyah/gh_dbl.c
Normal 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 doorbell’s 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 object’s 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 doorbell’s 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 Doorbell’s 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 doorbell’s
|
||||
* 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");
|
||||
55
drivers/virt/gunyah/hcall_ctrl.h
Normal file
55
drivers/virt/gunyah/hcall_ctrl.h
Normal 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
|
||||
94
drivers/virt/gunyah/hcall_dbl.h
Normal file
94
drivers/virt/gunyah/hcall_dbl.h
Normal 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
|
||||
Loading…
Reference in New Issue
Block a user