haven: Add haven doorbell driver

In haven one way to provide inter VMs(Virtual Machines)
communication is through the use of doorbell interrupts.
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.

Change-Id: I6d797d78cddc96a23680554e2b3fc1031446cbe5
Signed-off-by: Murali Nalajala <mnalajal@codeaurora.org>
Signed-off-by: Elliot Berman <eberman@codeaurora.org>
This commit is contained in:
Murali Nalajala 2020-01-13 22:19:20 -08:00 • committed by Elliot Berman
commit fbd0842e66
4 changed files with 666 additions and 0 deletions

View file

@ -37,4 +37,14 @@ config HH_RM_DRV
interface to other driver in order to obtain the services provided by
the RM-VM.
config HH_DBL
tristate "Haven Doorbell driver"
help
Haven 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

View file

@ -2,3 +2,4 @@
obj-$(CONFIG_HH_MSGQ) += hh_msgq.o
obj-$(CONFIG_HH_RM_DRV) += hh_rm_drv.o
hh_rm_drv-y := hh_rm_core.o hh_rm_iface.o
obj-$(CONFIG_HH_DBL) += hh_dbl.o

621
drivers/virt/haven/hh_dbl.c Normal file
View file

@ -0,0 +1,621 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2020, The Linux Foundation. All rights reserved.
*
*/
#include <linux/slab.h>
#include <linux/mutex.h>
#include <linux/module.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;
struct mutex 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;
};
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)
{
struct hh_dbl_cap_table *cap_table_entry;
if (!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];
if (cap_table_entry->client_desc != client_desc) {
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_cap_id ||
!cap_table_entry->rx_reg_done) {
pr_err("%s: label: %d; rx_cap_id: %llu; dir: %d rx_done: %d\n",
__func__, client_desc->label,
cap_table_entry->rx_cap_id,
dir, cap_table_entry->rx_reg_done);
return -EAGAIN;
}
} else {
if (!cap_table_entry->tx_cap_id ||
!cap_table_entry->tx_reg_done) {
pr_err("%s: label: %d; tx_cap_id: %llu; dir: %d tx_done: %d\n",
__func__, client_desc->label,
cap_table_entry->tx_cap_id,
dir, cap_table_entry->tx_reg_done);
return -EAGAIN;
}
}
return 0;
}
/**
* 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
*
* 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 hh_dbl_read_and_clean(void *dbl_client_desc, hh_dbl_flags_t *clear_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);
if (ret)
return ret;
cap_table_entry = &hh_dbl_cap_table[client_desc->label];
if (mutex_lock_interruptible(&cap_table_entry->cap_entry_lock))
return -ERESTARTSYS;
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;
mutex_unlock(&cap_table_entry->cap_entry_lock);
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
*
* 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 hh_dbl_set_mask(void *dbl_client_desc, hh_dbl_flags_t enable_mask,
hh_dbl_flags_t ack_mask)
{
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);
if (ret)
return ret;
cap_table_entry = &hh_dbl_cap_table[client_desc->label];
if (mutex_lock_interruptible(&cap_table_entry->cap_entry_lock))
return -ERESTARTSYS;
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);
mutex_unlock(&cap_table_entry->cap_entry_lock);
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.
*
* 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 hh_dbl_send(void *dbl_client_desc, hh_dbl_flags_t *newflags)
{
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);
if (ret)
return ret;
cap_table_entry = &hh_dbl_cap_table[client_desc->label];
if (mutex_lock_interruptible(&cap_table_entry->cap_entry_lock))
return -ERESTARTSYS;
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", hh_ret);
else
*newflags = send_resp.old_flags;
mutex_unlock(&cap_table_entry->cap_entry_lock);
return ret;
}
EXPORT_SYMBOL(hh_dbl_send);
/**
* hh_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
*
* 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 hh_dbl_reset(void *dbl_client_desc)
{
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);
if (ret)
return ret;
cap_table_entry = &hh_dbl_cap_table[client_desc->label];
if (mutex_lock_interruptible(&cap_table_entry->cap_entry_lock))
return -ERESTARTSYS;
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);
mutex_unlock(&cap_table_entry->cap_entry_lock);
return ret;
}
EXPORT_SYMBOL(hh_dbl_reset);
static irqreturn_t hh_dbl_rx_callback_thread(int irq, void *rx_priv_data)
{
struct hh_dbl_cap_table *cap_table_entry;
cap_table_entry = container_of(rx_priv_data,
struct hh_dbl_cap_table, rx_priv_data);
if (!cap_table_entry->rx_callback)
return IRQ_HANDLED;
cap_table_entry->rx_callback(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);
cap_table_entry = &hh_dbl_cap_table[label];
if (mutex_lock_interruptible(&cap_table_entry->cap_entry_lock))
return ERR_PTR(-ERESTARTSYS);
/* 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_KERNEL);
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);
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_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);
cap_table_entry = &hh_dbl_cap_table[label];
if (mutex_lock_interruptible(&cap_table_entry->cap_entry_lock))
return ERR_PTR(-ERESTARTSYS);
/* 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_KERNEL);
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;
ret = request_threaded_irq(cap_table_entry->rx_irq,
NULL,
hh_dbl_rx_callback_thread,
0,
cap_table_entry->rx_irq_name,
priv);
if (ret < 0) {
pr_err("%s: IRQ registration failed\n", __func__);
cap_table_entry->rx_callback = NULL;
cap_table_entry->rx_priv_data = NULL;
goto err;
}
cap_table_entry->rx_reg_done = 1;
pr_debug("%s: Registered Rx client for label: %d\n", __func__, label);
mutex_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);
mutex_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 (!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];
if (mutex_lock_interruptible(&cap_table_entry->cap_entry_lock))
return -ERESTARTSYS;
/* 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__);
mutex_unlock(&cap_table_entry->cap_entry_lock);
return -EINVAL;
}
/* 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;
mutex_unlock(&cap_table_entry->cap_entry_lock);
pr_debug("%s: Unregistered client for label: %d\n",
__func__, client_desc->label);
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 (!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];
if (mutex_lock_interruptible(&cap_table_entry->cap_entry_lock))
return -ERESTARTSYS;
/* 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__);
mutex_unlock(&cap_table_entry->cap_entry_lock);
return -EINVAL;
}
/* 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;
mutex_unlock(&cap_table_entry->cap_entry_lock);
pr_debug("%s: Unregistered client for label: %d\n", __func__,
client_desc->label);
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 (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;
}
cap_table_entry->tx_cap_id = cap_id;
pr_debug("%s: label: %d; tx_cap_id: %llu; dir: %d; rx_irq: %d\n",
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_cap_id = cap_id;
cap_table_entry->rx_irq = rx_irq;
pr_debug("%s: label: %d; rx_cap_id: %llu; dir: %d; rx_irq: %d\n",
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);
mutex_destroy(&cap_table_entry->cap_entry_lock);
}
}
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];
mutex_init(&entry->cap_entry_lock);
entry->rx_irq_name = kasprintf(GFP_KERNEL, "hh_dbl_rx_%d", i);
if (!entry->rx_irq_name) {
ret = -ENOMEM;
goto err;
}
}
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");

View file

@ -0,0 +1,34 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2020, The Linux Foundation. All rights reserved.
*
*/
#ifndef __HH_DBL_H
#define __HH_DBL_H
#include "hh_common.h"
typedef void (*dbl_rx_cb_t)(void *priv_data);
enum hh_dbl_label {
HH_DBL_TUI_LABEL,
HH_DBL_LABEL_MAX
};
void *hh_dbl_tx_register(enum hh_dbl_label label);
void *hh_dbl_rx_register(enum hh_dbl_label label, dbl_rx_cb_t rx_cb,
void *priv);
int hh_dbl_tx_unregister(void *dbl_client_desc);
int hh_dbl_rx_unregister(void *dbl_client_desc);
int hh_dbl_send(void *dbl_client_desc, uint64_t *newflags);
int hh_dbl_set_mask(void *dbl_client_desc, hh_dbl_flags_t enable_mask,
hh_dbl_flags_t ack_mask);
int hh_dbl_read_and_clean(void *dbl_client_desc, hh_dbl_flags_t *clear_flags);
int hh_dbl_reset(void *dbl_client_desc);
int hh_dbl_populate_cap_info(enum hh_dbl_label label, u64 cap_id,
int direction, int rx_irq);
#endif