android_kernel_xiaomi_sm8350/drivers/virt/haven/hh_dbl.c

657 lines
17 KiB
C
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2020, The Linux Foundation. All rights reserved.
*
*/
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/spinlock.h>
#include <linux/interrupt.h>
#include <linux/haven/hh_dbl.h>
#include <linux/haven/hh_errno.h>
#include <linux/haven/hcall.h>
struct hh_dbl_desc {
enum hh_dbl_label label;
};
enum hh_dbl_dir {
HH_DBL_DIRECTION_TX,
HH_DBL_DIRECTION_RX
};
struct hh_dbl_cap_table {
struct hh_dbl_desc *client_desc;
spinlock_t cap_entry_lock;
hh_capid_t tx_cap_id;
int tx_reg_done;
hh_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 hh_dbl_initialized;
static struct hh_dbl_cap_table hh_dbl_cap_table[HH_DBL_LABEL_MAX];
/**
* hh_dbl_validate_params - Validate doorbell common parameters
*/
static int hh_dbl_validate_params(struct hh_dbl_desc *client_desc,
enum hh_dbl_dir dir, const unsigned long flags)
{
struct hh_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 >= HH_DBL_LABEL_MAX)
return -EINVAL;
cap_table_entry = &hh_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 "hh_dbl_populate_cap_info()" call from RM is not over
* or
* There are no doorbell setup for Tx or Rx
*/
if (dir == HH_DBL_DIRECTION_RX) {
if (!cap_table_entry->rx_reg_done) {
ret = -EINVAL;
goto err;
}
if ((cap_table_entry->rx_cap_id == HH_CAPID_INVAL) &&
(flags & HH_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 != HH_CAPID_INVAL))
return -ERESTARTSYS;
} else {
if (!cap_table_entry->tx_reg_done) {
ret = -EINVAL;
goto err;
}
if ((cap_table_entry->tx_cap_id == HH_CAPID_INVAL) &&
(flags & HH_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 != HH_CAPID_INVAL))
return -ERESTARTSYS;
}
return 0;
err:
spin_unlock(&cap_table_entry->cap_entry_lock);
return ret;
}
/**
* hh_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/haven/hh_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 hh_dbl_read_and_clean(void *dbl_client_desc, hh_dbl_flags_t *clear_flags,
const unsigned long flags)
{
struct hh_dbl_cap_table *cap_table_entry;
struct hh_hcall_dbl_recv_resp recv_resp;
struct hh_dbl_desc *client_desc = dbl_client_desc;
int ret, hh_ret;
if (!clear_flags)
return -EINVAL;
ret = hh_dbl_validate_params(client_desc, HH_DBL_DIRECTION_RX, flags);
if (ret)
return ret;
cap_table_entry = &hh_dbl_cap_table[client_desc->label];
hh_ret = hh_hcall_dbl_recv(cap_table_entry->rx_cap_id,
*clear_flags, &recv_resp);
ret = hh_remap_error(hh_ret);
if (ret != 0)
pr_err("%s: Hypercall failed, ret = %d\n", __func__, hh_ret);
else
*clear_flags = recv_resp.old_flags;
return ret;
}
EXPORT_SYMBOL(hh_dbl_read_and_clean);
/**
* hh_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/haven/hh_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 hh_dbl_set_mask(void *dbl_client_desc, hh_dbl_flags_t enable_mask,
hh_dbl_flags_t ack_mask, const unsigned long flags)
{
struct hh_dbl_cap_table *cap_table_entry;
struct hh_dbl_desc *client_desc = dbl_client_desc;
int ret, hh_ret;
ret = hh_dbl_validate_params(client_desc, HH_DBL_DIRECTION_RX, flags);
if (ret)
return ret;
cap_table_entry = &hh_dbl_cap_table[client_desc->label];
hh_ret = hh_hcall_dbl_mask(cap_table_entry->rx_cap_id,
enable_mask, ack_mask);
ret = hh_remap_error(hh_ret);
if (ret != 0)
pr_err("%s: Hypercall failed ret = %d\n", __func__, hh_ret);
return ret;
}
EXPORT_SYMBOL(hh_dbl_set_mask);
/**
* hh_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/haven/hh_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 hh_dbl_send(void *dbl_client_desc, hh_dbl_flags_t *newflags,
unsigned long flags)
{
struct hh_dbl_cap_table *cap_table_entry;
struct hh_hcall_dbl_send_resp send_resp;
struct hh_dbl_desc *client_desc = dbl_client_desc;
int ret, hh_ret;
if (!newflags)
return -EINVAL;
ret = hh_dbl_validate_params(client_desc, HH_DBL_DIRECTION_TX, flags);
if (ret)
return ret;
cap_table_entry = &hh_dbl_cap_table[client_desc->label];
hh_ret = hh_hcall_dbl_send(cap_table_entry->tx_cap_id, *newflags,
&send_resp);
ret = hh_remap_error(hh_ret);
if (ret != 0)
pr_err("%s: Hypercall failed ret = %d\n", __func__, hh_ret);
else
*newflags = send_resp.old_flags;
return ret;
}
EXPORT_SYMBOL(hh_dbl_send);
/**
* hh_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/haven/hh_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 hh_dbl_reset(void *dbl_client_desc, const unsigned long flags)
{
struct hh_dbl_cap_table *cap_table_entry;
struct hh_dbl_desc *client_desc = dbl_client_desc;
int ret, hh_ret;
ret = hh_dbl_validate_params(client_desc, HH_DBL_DIRECTION_RX, flags);
if (ret)
return ret;
cap_table_entry = &hh_dbl_cap_table[client_desc->label];
hh_ret = hh_hcall_dbl_reset(cap_table_entry->rx_cap_id);
ret = hh_remap_error(hh_ret);
if (ret != 0)
pr_err("%s: Hypercall failed ret = %d\n", __func__, hh_ret);
return ret;
}
EXPORT_SYMBOL(hh_dbl_reset);
static irqreturn_t hh_dbl_rx_callback_thread(int irq, void *data)
{
struct hh_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;
}
/**
* hh_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 *hh_dbl_tx_register(enum hh_dbl_label label)
{
struct hh_dbl_cap_table *cap_table_entry;
struct hh_dbl_desc *client_desc;
int ret;
if (label < 0 || label >= HH_DBL_LABEL_MAX)
return ERR_PTR(-EINVAL);
if (!hh_dbl_initialized)
return ERR_PTR(-EPROBE_DEFER);
cap_table_entry = &hh_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(hh_dbl_tx_register);
/**
* hh_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 receieve 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 *hh_dbl_rx_register(enum hh_dbl_label label, dbl_rx_cb_t rx_cb, void *priv)
{
struct hh_dbl_cap_table *cap_table_entry;
struct hh_dbl_desc *client_desc;
int ret;
if (label < 0 || label >= HH_DBL_LABEL_MAX)
return ERR_PTR(-EINVAL);
if (!hh_dbl_initialized)
return ERR_PTR(-EPROBE_DEFER);
cap_table_entry = &hh_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(hh_dbl_rx_register);
/**
* hh_dbl_tx_unregister: Unregister Tx client to use the doorbell
* @client_desc: The descriptor that was passed via hh_dbl_tx_register() or
* hh_dbl_rx_register()
*
* The function returns 0 is the client was unregistered successfully. Else,
* -EINVAL for invalid arguments.
*/
int hh_dbl_tx_unregister(void *dbl_client_desc)
{
struct hh_dbl_desc *client_desc = dbl_client_desc;
struct hh_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 >= HH_DBL_LABEL_MAX)
return -EINVAL;
cap_table_entry = &hh_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(hh_dbl_tx_unregister);
/**
* hh_dbl_rx_unregister: Unregister Rx client to use the doorbell
* @client_desc: The descriptor that was passed via hh_dbl_tx_register() or
* hh_dbl_rx_register()
*
* The function returns 0 is the client was unregistered successfully. Else,
* -EINVAL for invalid arguments.
*/
int hh_dbl_rx_unregister(void *dbl_client_desc)
{
struct hh_dbl_desc *client_desc = dbl_client_desc;
struct hh_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 >= HH_DBL_LABEL_MAX)
return -EINVAL;
cap_table_entry = &hh_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(hh_dbl_rx_unregister);
/**
* This API is called by RM driver to populate doorbell objects
*/
int hh_dbl_populate_cap_info(enum hh_dbl_label label, u64 cap_id,
int direction, int rx_irq)
{
struct hh_dbl_cap_table *cap_table_entry;
int ret = 0;
if (!hh_dbl_initialized)
return -EAGAIN;
if (label < 0 || label >= HH_DBL_LABEL_MAX) {
pr_err("%s: Invalid label passed\n", __func__);
return -EINVAL;
}
cap_table_entry = &hh_dbl_cap_table[label];
switch (direction) {
case HH_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 HH_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,
hh_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(hh_dbl_populate_cap_info);
static void hh_dbl_cleanup(int begin_idx)
{
struct hh_dbl_cap_table *cap_table_entry;
int i;
if (begin_idx >= HH_DBL_LABEL_MAX)
begin_idx = HH_DBL_LABEL_MAX - 1;
for (i = begin_idx; i >= 0; i--) {
cap_table_entry = &hh_dbl_cap_table[i];
kfree(cap_table_entry->rx_irq_name);
}
}
static int __init hh_dbl_init(void)
{
struct hh_dbl_cap_table *entry;
int ret;
int i;
for (i = 0; i < HH_DBL_LABEL_MAX; i++) {
entry = &hh_dbl_cap_table[i];
spin_lock_init(&entry->cap_entry_lock);
init_waitqueue_head(&entry->cap_wq);
entry->tx_cap_id = HH_CAPID_INVAL;
entry->rx_cap_id = HH_CAPID_INVAL;
entry->rx_irq_name = kasprintf(GFP_KERNEL, "hh_dbl_rx_%d", i);
if (!entry->rx_irq_name) {
ret = -ENOMEM;
goto err;
}
}
hh_dbl_initialized = true;
return 0;
err:
hh_dbl_cleanup(i);
return ret;
}
module_init(hh_dbl_init);
static void __exit hh_dbl_exit(void)
{
hh_dbl_cleanup(HH_DBL_LABEL_MAX - 1);
}
module_exit(hh_dbl_exit);
MODULE_DESCRIPTION("Qualcomm Technologies, Inc. Haven Doorbell Driver");
MODULE_LICENSE("GPL v2");