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

Commit 3bdb50e5 authored by qctecmdr's avatar qctecmdr Committed by Gerrit - the friendly Code Review server
Browse files

Merge "arm64: defconfig: Enable haven doorbell driver"

parents 81c4256b f57d0c27
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -121,6 +121,11 @@ CONFIG_MSM_CDSP_LOADER=m
CONFIG_UIO_MSM_SHAREDMEM=m
CONFIG_QCOM_SOCINFO=m
CONFIG_BTFM_SLIM=m
CONFIG_VIRT_DRIVERS=y
CONFIG_HAVEN_DRIVERS=y
CONFIG_HH_MSGQ=m
CONFIG_HH_RM_DRV=m
CONFIG_HH_DBL=m
CONFIG_CFI_PERMISSIVE=y
# CONFIG_SND_SOC_WCD9335 is not set
# CONFIG_SLIM_QCOM_CTRL is not set
+23 −0
Original line number Diff line number Diff line
@@ -24,4 +24,27 @@ config HH_MSGQ
	  the services offered by the drivers is simply to send and receive
	  messages in a blocking manner.

config HH_RM_DRV
	tristate "Haven Resource Manager driver"
	help
	  The Haven Resource Manager driver is used to communicate with the
	  Resource Manager Virtual Machine (RM-VM). The RM-VM acts as a mediator
	  and provides numerous services to the other VMs running in the system,
	  such as notifying when a particular VM is up, resource (IRQ/device)
	  sharing between VMs, information about the IPC mechanisms, and so on.

	  The Resource Manager driver runs on the Virtual Machine and acts as an
	  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
+3 −0
Original line number Diff line number Diff line
# SPDX-License-Identifier: GPL-2.0-only
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");
+807 −0

File added.

Preview size limit exceeded, changes collapsed.

Loading