Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit fbd0842e authored by Murali Nalajala's avatar Murali Nalajala Committed by Elliot Berman
Browse files

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: default avatarMurali Nalajala <mnalajal@codeaurora.org>
Signed-off-by: default avatarElliot Berman <eberman@codeaurora.org>
parent 165cd8f7
Loading
Loading
Loading
Loading
+10 −0
Original line number Diff line number Diff line
@@ -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
+1 −0
Original line number Diff line number Diff line
@@ -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 −0
Original line number Diff line number Diff line
// 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");
+34 −0
Original line number Diff line number Diff line
/* 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