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

Commit f2dc86b1 authored by Chris Lew's avatar Chris Lew
Browse files

soc: qcom: Add snapshot of qsee_ipc_irq_bridge driver



This snapshot is taken as of msm-4.9 'commit <34e1614ab73d>
("Merge "thermal: qpnp-adc-tm: Add read_status function for non-HC
peripheral"")'.

In addition, update copyright and remove wake properties from the
interrupt since this will be handled by the qsee_ipc_irq driver. Also
change of_get_property to of_property_read_string for names.

Change-Id: I36bbc36b3e87548020fe5a52422a5343d83f81cb
Signed-off-by: default avatarChris Lew <clew@codeaurora.org>
parent 09ddc60f
Loading
Loading
Loading
Loading
+30 −0
Original line number Diff line number Diff line
Qualcomm Technologies, Inc. Secure Execution Environment IPC Interrupt Bridge

[Root level node]
Required properties:
-compatible : should be "qcom,qsee-ipc-irq-bridge";

[Second level nodes]
qcom,qsee-ipc-irq-subsystem
Required properties:
-qcom,dev-name: the bridge device name
-interrupt: IPC interrupt line from remote subsystem to QSEE
-label : The name of this subsystem.

Required properties if interrupt type is IRQ_TYPE_LEVEL_HIGH[4]:
-qcom,rx-irq-clr : the register to clear the level triggered rx interrupt
-qcom,rx-irq-clr-mask : the bitmask to clear the rx interrupt

Example:

	qcom,qsee_ipc_irq_bridge {
		compatible = "qcom,qsee-ipc-irq-bridge";

		qcom,qsee-ipc-irq-spss {
			qcom,rx-irq-clr = <0x1d08008 0x4>;
			qcom,rx-irq-clr-mask = <0x2>;
			qcom,dev-name = "qsee_ipc_irq_spss";
			interrupts = <0 349 4>;
			label = "spss";
		};
	};
+10 −0
Original line number Diff line number Diff line
@@ -445,6 +445,16 @@ config QSEE_IPC_IRQ
	  Clients can use this driver to avoid adding common interrupt handling
	  code.

config QSEE_IPC_IRQ_BRIDGE
	tristate "QSEE IPC Interrupt Bridge"
	select QSEE_IPC_IRQ
	help
	  This module enables bridging an Inter-Processor Communication(IPC)
	  interrupt from a remote subsystem directed towards Qualcomm
	  Technologies, Inc. Secure Execution Environment(QSEE) to userspace.
	  The interrupt will be propagated through a character device that
	  userspace clients can poll on.

config QCOM_GLINK
	tristate "GLINK Probe Helper"
	depends on RPMSG_QCOM_GLINK_SMEM
+1 −0
Original line number Diff line number Diff line
@@ -57,6 +57,7 @@ ifdef CONFIG_MSM_SUBSYSTEM_RESTART
endif
obj-$(CONFIG_QCOM_EUD) += eud.o
obj-$(CONFIG_QSEE_IPC_IRQ) += qsee_ipc_irq.o
obj-$(CONFIG_QSEE_IPC_IRQ_BRIDGE) += qsee_ipc_irq_bridge.o
obj-$(CONFIG_QCOM_GLINK) += glink_probe.o
obj-$(CONFIG_QCOM_GLINK_PKT) += glink_pkt.o
obj-$(CONFIG_QCOM_QDSS_BRIDGE) += qdss_bridge.o
+618 −0
Original line number Diff line number Diff line
/* Copyright (c) 2016-2018, The Linux Foundation. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 and
 * only version 2 as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */

#include <linux/cdev.h>
#include <linux/interrupt.h>
#include <linux/ipc_logging.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/platform_device.h>
#include <linux/poll.h>
#include <linux/slab.h>
#include <soc/qcom/subsystem_notif.h>
#include <soc/qcom/subsystem_restart.h>

#define MODULE_NAME "qsee_ipc_irq_bridge"
#define DEVICE_NAME MODULE_NAME
#define NUM_LOG_PAGES 4

#define QIIB_DBG(x...) do { \
	if (qiib_info->log_ctx) \
		ipc_log_string(qiib_info->log_ctx, x); \
	else \
		pr_debug(x); \
	} while (0)

#define QIIB_ERR(x...) do { \
	pr_err(x); \
	if (qiib_info->log_ctx) \
		ipc_log_string(qiib_info->log_ctx, x); \
	} while (0)

static void qiib_cleanup(void);

/**
 * qiib_dev - QSEE IPC IRQ bridge device
 * @dev_list:		qiib device list.
 * @i:			Index to this character device.
 * @dev_name:		Device node name used by the clients.
 * @cdev:		structure to the internal character device.
 * @devicep:		Pointer to the qiib class device structure.
 * @poll_wait_queue:	poll thread wait queue.
 * @irq_num:		IRQ number usd for this device.
 * @rx_irq_reset_reg:	Reference to the register to reset the rx irq
 *			line, if applicable.
 * @irq_mask:		Mask written to @rx_irq_reset_reg to clear the irq.
 * @irq_pending_count:	The number of IRQs pending.
 * @irq_pending_count_lock: Lock to protect @irq_pending_cont.
 * @ssr_name:		Name of the subsystem recognized by the SSR framework.
 * @nb:			SSR Notifier callback.
 * @notifier_handle:	SSR Notifier handle.
 * @in_reset:		Flag to check the SSR state.
 */
struct qiib_dev {
	struct list_head dev_list;
	uint32_t i;

	const char *dev_name;
	struct cdev cdev;
	struct device *devicep;

	wait_queue_head_t poll_wait_queue;

	uint32_t irq_line;
	void __iomem *rx_irq_reset_reg;
	uint32_t irq_mask;
	uint32_t irq_pending_count;
	spinlock_t irq_pending_count_lock;

	const char *ssr_name;
	struct notifier_block nb;
	void *notifier_handle;
	bool in_reset;
};

/**
 * qiib_driver_data - QSEE IPC IRQ bridge driver data
 * @list:		list of all nodes devices.
 * @list_lock:		lock to synchronize the @list access.
 * @nprots:		Number of device nodes.
 * @classp:		Pointer to the device class.
 * @dev_num:		qiib device number.
 * @log_ctx:		pointer to the ipc logging context.
 */
struct qiib_driver_data {
	struct list_head list;
	struct mutex list_lock;

	int nports;
	struct class *classp;
	dev_t dev_num;

	void *log_ctx;
};

static struct qiib_driver_data *qiib_info;

/**
 * qiib_driver_data_init() - Initialize the QIIB driver data.
 *
 * This function used to initialize the driver specific data
 * during the module init.
 *
 * Return:	0 for success, Standard Linux errors
 */
static int qiib_driver_data_init(void)
{
	qiib_info = kzalloc(sizeof(*qiib_info), GFP_KERNEL);
	if (!qiib_info)
		return -ENOMEM;

	INIT_LIST_HEAD(&qiib_info->list);
	mutex_init(&qiib_info->list_lock);

	qiib_info->log_ctx = ipc_log_context_create(NUM_LOG_PAGES,
						"qsee_ipc_irq_bridge", 0);
	if (!qiib_info->log_ctx)
		QIIB_ERR("%s: unable to create logging context\n", __func__);

	return 0;
}

/**
 * qiib_driver_data_deinit() - De-Initialize the QIIB driver data.
 *
 * This function used to de-initialize the driver specific data
 * during the module exit.
 */
static void qiib_driver_data_deinit(void)
{
	qiib_cleanup();
	if (!qiib_info->log_ctx)
		ipc_log_context_destroy(qiib_info->log_ctx);
	kfree(qiib_info);
	qiib_info = NULL;
}

/**
 * qiib_restart_notifier_cb() - SSR restart notifier callback function
 * @this:	Notifier block used by the SSR framework
 * @code:	The SSR code for which stage of restart is occurring
 * @data:	Structure containing private data - not used here.
 *
 * This function is a callback for the SSR framework. From here we initiate
 * our handling of SSR.
 *
 * Return: Status of SSR handling
 */
static int qiib_restart_notifier_cb(struct notifier_block *this,
				  unsigned long code,
				  void *data)
{
	struct qiib_dev *devp = container_of(this, struct qiib_dev, nb);

	if (code == SUBSYS_BEFORE_SHUTDOWN) {
		QIIB_DBG("%s: %s: subsystem restart for %s\n", __func__,
				"SUBSYS_BEFORE_SHUTDOWN",
				devp->ssr_name);
		devp->in_reset = true;
		wake_up_interruptible(&devp->poll_wait_queue);
	} else if (code == SUBSYS_AFTER_POWERUP) {
		QIIB_DBG("%s: %s: subsystem restart for %s\n", __func__,
				"SUBSYS_AFTER_POWERUP",
				devp->ssr_name);
		devp->in_reset = false;
	}
	return NOTIFY_DONE;
}

/**
 * qiib_poll() - poll() syscall for the qiib device
 * @file:	Pointer to the file structure.
 * @wait:	pointer to Poll table.
 *
 * This function is used to poll on the qiib device when
 * userspace client do a poll() system call. All input arguments are
 * validated by the virtual file system before calling this function.
 *
 * Return: POLLIN for interrupt intercepted case and POLLRDHUP for SSR.
 */
static unsigned int qiib_poll(struct file *file, poll_table *wait)
{
	struct qiib_dev *devp = file->private_data;
	unsigned int mask = 0;
	unsigned long flags;

	if (!devp) {
		QIIB_ERR("%s on NULL device\n", __func__);
		return POLLERR;
	}

	if (devp->in_reset)
		return POLLRDHUP;

	poll_wait(file, &devp->poll_wait_queue, wait);
	spin_lock_irqsave(&devp->irq_pending_count_lock, flags);
	if (devp->irq_pending_count) {
		mask |= POLLIN;
		QIIB_DBG("%s set POLLIN on [%s] count[%d]\n",
					__func__, devp->dev_name,
					devp->irq_pending_count);
		devp->irq_pending_count = 0;
	}
	spin_unlock_irqrestore(&devp->irq_pending_count_lock, flags);

	if (devp->in_reset) {
		mask |= POLLRDHUP;
		QIIB_DBG("%s set POLLRDHUP on [%s] count[%d]\n",
					__func__, devp->dev_name,
					devp->irq_pending_count);
	}
	return mask;
}

/**
 * qiib_open() - open() syscall for the qiib device
 * @inode:	Pointer to the inode structure.
 * @file:	Pointer to the file structure.
 *
 * This function is used to open the qiib device when
 * userspace client do a open() system call. All input arguments are
 * validated by the virtual file system before calling this function.
 *
 * Return:	0 for success, Standard Linux errors
 */
static int qiib_open(struct inode *inode, struct file *file)
{
	struct qiib_dev *devp = NULL;

	devp = container_of(inode->i_cdev, struct qiib_dev, cdev);
	if (!devp) {
		QIIB_ERR("%s on NULL device\n", __func__);
		return -EINVAL;
	}
	file->private_data = devp;
	QIIB_DBG("%s on [%s]\n", __func__, devp->dev_name);
	return 0;
}

/**
 * qiib_release() - release operation on qiibdevice
 * @inode:	Pointer to the inode structure.
 * @file:	Pointer to the file structure.
 *
 * This function is used to release the qiib device when
 * userspace client do a close() system call. All input arguments are
 * validated by the virtual file system before calling this function.
 */
static int qiib_release(struct inode *inode, struct file *file)
{
	struct qiib_dev *devp = file->private_data;

	if (!devp) {
		QIIB_ERR("%s on NULL device\n", __func__);
		return -EINVAL;
	}

	QIIB_DBG("%s on [%s]\n", __func__, devp->dev_name);
	return 0;
}

static const struct file_operations qiib_fops = {
	.owner = THIS_MODULE,
	.open = qiib_open,
	.release = qiib_release,
	.poll = qiib_poll,
};

/**
 * qiib_add_device() - Initialize qiib device and add cdev
 * @devp:	pointer to the qiib device.
 * @i:		index of the qiib device.
 *
 * Return:	0 for success, Standard Linux errors
 */
static int qiib_add_device(struct qiib_dev *devp, int i)
{
	int ret = 0;

	devp->i = i;
	init_waitqueue_head(&devp->poll_wait_queue);
	spin_lock_init(&devp->irq_pending_count_lock);

	cdev_init(&devp->cdev, &qiib_fops);
	devp->cdev.owner = THIS_MODULE;

	ret = cdev_add(&devp->cdev, qiib_info->dev_num + i, 1);
	if (IS_ERR_VALUE((unsigned long)ret)) {
		QIIB_ERR("%s: cdev_add() failed for dev [%s] ret:%i\n",
			__func__, devp->dev_name, ret);
		return ret;
	}

	devp->devicep = device_create(qiib_info->classp,
			      NULL,
			      (qiib_info->dev_num + i),
			      NULL,
			      devp->dev_name);

	if (IS_ERR_OR_NULL(devp->devicep)) {
		QIIB_ERR("%s: device_create() failed for dev [%s]\n",
			__func__, devp->dev_name);
		ret = -ENOMEM;
		cdev_del(&devp->cdev);
		return ret;
	}

	mutex_lock(&qiib_info->list_lock);
	list_add(&devp->dev_list, &qiib_info->list);
	mutex_unlock(&qiib_info->list_lock);

	return ret;
}

static irqreturn_t qiib_irq_handler(int irq, void *priv)
{
	struct qiib_dev *devp = priv;
	unsigned long flags;

	spin_lock_irqsave(&devp->irq_pending_count_lock, flags);
	devp->irq_pending_count++;
	spin_unlock_irqrestore(&devp->irq_pending_count_lock, flags);
	wake_up_interruptible(&devp->poll_wait_queue);

	if (devp->rx_irq_reset_reg)
		writel_relaxed(devp->irq_mask, devp->rx_irq_reset_reg);

	QIIB_DBG("%s name[%s] pend_count[%d]\n", __func__,
				devp->dev_name, devp->irq_pending_count);

	return IRQ_HANDLED;
}

/**
 * qiib_parse_node() - parse node from device tree binding
 * @node:	pointer to device tree node
 * @devp:	pointer to the qiib device
 *
 * Return:	0 on success, -ENODEV on failure.
 */
static int qiib_parse_node(struct device_node *node, struct qiib_dev *devp)
{
	char *key;
	const char *subsys_name;
	const char *dev_name;
	uint32_t irqtype;
	uint32_t irq_clear[2];
	struct irq_data *irqtype_data;
	int ret = -ENODEV;

	key = "qcom,dev-name";
	ret = of_property_read_string(node, key, &dev_name);
	if (ret) {
		QIIB_ERR("%s: missing key: %s\n", __func__, key);
		goto missing_key;
	}
	QIIB_DBG("%s: %s = %s\n", __func__, key, dev_name);

	key = "interrupts";
	devp->irq_line = irq_of_parse_and_map(node, 0);
	if (!devp->irq_line) {
		QIIB_ERR("%s: missing key: %s\n", __func__, key);
		goto missing_key;
	}
	QIIB_DBG("%s: %s = %d\n", __func__, key, devp->irq_line);

	irqtype_data = irq_get_irq_data(devp->irq_line);
	if (!irqtype_data) {
		QIIB_ERR("%s: get irqdata fail:%d\n", __func__, devp->irq_line);
		goto missing_key;
	}
	irqtype = irqd_get_trigger_type(irqtype_data);
	QIIB_DBG("%s: irqtype = %d\n", __func__, irqtype);

	key = "label";
	ret = of_property_read_string(node, key, &subsys_name);
	if (ret) {
		QIIB_ERR("%s: missing key: %s\n", __func__, key);
		goto missing_key;
	}
	QIIB_DBG("%s: %s = %s\n", __func__, key, subsys_name);

	if (irqtype & IRQF_TRIGGER_HIGH) {
		key = "qcom,rx-irq-clr-mask";
		ret = of_property_read_u32(node, key, &devp->irq_mask);
		if (ret) {
			QIIB_ERR("%s: missing key: %s\n", __func__, key);
			ret = -ENODEV;
			goto missing_key;
		}
		QIIB_DBG("%s: %s = %d\n", __func__, key, devp->irq_mask);

		key = "qcom,rx-irq-clr";
		ret = of_property_read_u32_array(node, key, irq_clear,
							ARRAY_SIZE(irq_clear));
		if (ret) {
			QIIB_ERR("%s: missing key: %s\n", __func__, key);
			ret = -ENODEV;
			goto missing_key;
		}

		devp->rx_irq_reset_reg = ioremap_nocache(irq_clear[0],
								irq_clear[1]);
		if (!devp->rx_irq_reset_reg) {
			QIIB_ERR("%s: unable to map rx reset reg\n", __func__);
			ret = -ENOMEM;
			goto missing_key;
		}
	}

	devp->dev_name = dev_name;
	devp->ssr_name = subsys_name;
	devp->nb.notifier_call = qiib_restart_notifier_cb;

	devp->notifier_handle = subsys_notif_register_notifier(devp->ssr_name,
								&devp->nb);
	if (IS_ERR_OR_NULL(devp->notifier_handle)) {
		QIIB_ERR("%s: Could not register SSR notifier cb\n", __func__);
		ret = -EINVAL;
		goto ssr_reg_fail;
	}

	ret = request_irq(devp->irq_line, qiib_irq_handler, irqtype,
			devp->dev_name, devp);
	if (ret < 0) {
		QIIB_ERR("%s: request_irq() failed on %d\n", __func__,
				devp->irq_line);
		goto req_irq_fail;
	}

	return ret;

req_irq_fail:
	subsys_notif_unregister_notifier(devp->notifier_handle,	&devp->nb);
ssr_reg_fail:
	if (devp->rx_irq_reset_reg) {
		iounmap(devp->rx_irq_reset_reg);
		devp->rx_irq_reset_reg = NULL;
	}
missing_key:
	return ret;
}

/**
 * qiib_cleanup - cleanup all the resources
 *
 * This function remove all the memory and unregister
 * the char device region.
 */
static void qiib_cleanup(void)
{
	struct qiib_dev *devp;
	struct qiib_dev *index;

	mutex_lock(&qiib_info->list_lock);
	list_for_each_entry_safe(devp, index, &qiib_info->list, dev_list) {
		cdev_del(&devp->cdev);
		list_del(&devp->dev_list);
		device_destroy(qiib_info->classp,
			       MKDEV(MAJOR(qiib_info->dev_num), devp->i));
		if (devp->notifier_handle)
			subsys_notif_unregister_notifier(devp->notifier_handle,
								&devp->nb);
		kfree(devp);
	}
	mutex_unlock(&qiib_info->list_lock);

	if (!IS_ERR_OR_NULL(qiib_info->classp))
		class_destroy(qiib_info->classp);

	unregister_chrdev_region(MAJOR(qiib_info->dev_num), qiib_info->nports);
}

/**
 * qiib_alloc_chrdev_region() - allocate the char device region
 *
 * This function allocate memory for qiib character-device region and
 * create the class.
 */
static int qiib_alloc_chrdev_region(void)
{
	int ret;

	ret = alloc_chrdev_region(&qiib_info->dev_num,
			       0,
			       qiib_info->nports,
			       DEVICE_NAME);
	if (IS_ERR_VALUE((unsigned long)ret)) {
		QIIB_ERR("%s: alloc_chrdev_region() failed ret:%i\n",
			__func__, ret);
		return ret;
	}

	qiib_info->classp = class_create(THIS_MODULE, DEVICE_NAME);
	if (IS_ERR(qiib_info->classp)) {
		QIIB_ERR("%s: class_create() failed ENOMEM\n", __func__);
		ret = -ENOMEM;
		unregister_chrdev_region(MAJOR(qiib_info->dev_num),
						qiib_info->nports);
		return ret;
	}

	return 0;
}

static int qsee_ipc_irq_bridge_probe(struct platform_device *pdev)
{
	int ret;
	struct device_node *node;
	struct qiib_dev *devp;
	int i = 0;

	qiib_info->nports = of_get_available_child_count(pdev->dev.of_node);
	if (!qiib_info->nports) {
		QIIB_ERR("%s:Fail nports = %d\n", __func__, qiib_info->nports);
		return -EINVAL;
	}

	ret = qiib_alloc_chrdev_region();
	if (ret) {
		QIIB_ERR("%s: chrdev_region allocation failed ret:%i\n",
			__func__, ret);
		return ret;
	}

	for_each_available_child_of_node(pdev->dev.of_node, node) {
		devp = kzalloc(sizeof(*devp), GFP_KERNEL);
		if (IS_ERR_OR_NULL(devp)) {
			QIIB_ERR("%s:Allocation failed id:%d\n", __func__, i);
			ret = -ENOMEM;
			goto error;
		}

		ret = qiib_parse_node(node, devp);
		if (ret) {
			QIIB_ERR("%s:qiib_parse_node failed %d\n", __func__, i);
			kfree(devp);
			goto error;
		}

		ret = qiib_add_device(devp, i);
		if (ret < 0) {
			QIIB_ERR("%s: add [%s] device failed ret=%d\n",
					__func__, devp->dev_name, ret);
			kfree(devp);
			goto error;
		}
		i++;
	}

	QIIB_DBG("%s: Driver Initialized.\n", __func__);
	return 0;

error:
	qiib_cleanup();
	return ret;
}

static int qsee_ipc_irq_bridge_remove(struct platform_device *pdev)
{
	qiib_cleanup();
	return 0;
}

static const struct of_device_id qsee_ipc_irq_bridge_match_table[] = {
	{ .compatible = "qcom,qsee-ipc-irq-bridge" },
	{},
};

static struct platform_driver qsee_ipc_irq_bridge_driver = {
	.probe = qsee_ipc_irq_bridge_probe,
	.remove = qsee_ipc_irq_bridge_remove,
	.driver = {
		.name = MODULE_NAME,
		.owner = THIS_MODULE,
		.of_match_table = qsee_ipc_irq_bridge_match_table,
	 },
};

static int __init qsee_ipc_irq_bridge_init(void)
{
	int ret;

	ret = qiib_driver_data_init();
	if (ret) {
		QIIB_ERR("%s: driver data init failed %d\n",
			__func__, ret);
		return ret;
	}

	ret = platform_driver_register(&qsee_ipc_irq_bridge_driver);
	if (ret) {
		QIIB_ERR("%s: platform driver register failed %d\n",
			__func__, ret);
		return ret;
	}

	return 0;
}
module_init(qsee_ipc_irq_bridge_init);

static void __exit qsee_ipc_irq_bridge_exit(void)
{
	platform_driver_unregister(&qsee_ipc_irq_bridge_driver);
	qiib_driver_data_deinit();
}
module_exit(qsee_ipc_irq_bridge_exit);
MODULE_DESCRIPTION("QSEE IPC interrupt bridge");
MODULE_LICENSE("GPL v2");