diff mbox series

[2/3] soc: qcom: Add AOSS QMP communication driver

Message ID 20181112080557.22698-3-bjorn.andersson@linaro.org (mailing list archive)
State Not Applicable, archived
Delegated to: Andy Gross
Headers show
Series Qualcomm AOSS QMP side channel binding and driver | expand

Commit Message

Bjorn Andersson Nov. 12, 2018, 8:05 a.m. UTC
The AOSS QMP driver is used to communicate with the AOSS for certain
side-channel requests, that are not enabled through the RPMh interface.

The communication is a very simple synchronous mechanism of messages
being written in message RAM and a doorbell in the AOSS is rung. As the
AOSS has processed the message length is cleared and an interrupt is
fired by the AOSS as acknowledgment.

Signed-off-by: Bjorn Andersson <bjorn.andersson@linaro.org>
---
 drivers/soc/qcom/Kconfig          |   7 +
 drivers/soc/qcom/Makefile         |   1 +
 drivers/soc/qcom/aoss-qmp.c       | 313 ++++++++++++++++++++++++++++++
 include/linux/soc/qcom/aoss-qmp.h |  12 ++
 4 files changed, 333 insertions(+)
 create mode 100644 drivers/soc/qcom/aoss-qmp.c
 create mode 100644 include/linux/soc/qcom/aoss-qmp.h

Comments

Bjorn Andersson Dec. 26, 2018, 8:28 p.m. UTC | #1
On Tue 20 Nov 04:22 PST 2018, Arun Kumar Neelakantam wrote:

Thanks for the review Arun.

> On 11/12/2018 1:35 PM, Bjorn Andersson wrote:
[..]
> > +int qmp_send(struct qmp *qmp, const void *data, size_t len)
> > +{
> > +	int ret;
> > +
> > +	if (WARN_ON(len + sizeof(u32) > qmp->size)) {
> > +		dev_err(qmp->dev, "message too long\n");
> > +		return -EINVAL;
> > +	}
> > +
> > +	if (WARN_ON(len % sizeof(u32))) {
> > +		dev_err(qmp->dev, "message not 32-bit aligned\n");
> > +		return -EINVAL;
> > +	}
> > +
> > +	mutex_lock(&qmp->tx_lock);
> > +
> > +	if (!qmp_message_empty(qmp)) {
> > +		dev_err(qmp->dev, "mailbox left busy\n");
> > +		ret = -EINVAL;
> should it be -EBUSY ?

That makes more sense.

> And qmp_messge_empty will be done either by remote if it process the data
> else by this driver in TIMEOUT case, so does we need this check for every TX
> ? I think we can just reset to Zero once in open time.

Didn't think about that, should we really make the QMP link ready again
when we get a timeout? Can we expect that the firmware of the remote
side is ready to serve future messages?


Should we keep this check and remove the writel() below?

> > +		goto out_unlock;
> > +	}
> > +
> > +	/* The message RAM only implements 32-bit accesses */
> > +	__iowrite32_copy(qmp->msgram + qmp->offset + sizeof(u32),
> > +			 data, len / sizeof(u32));
> > +	writel(len, qmp->msgram + qmp->offset);
> > +	qmp_kick(qmp);
> > +
> > +	ret = wait_event_interruptible_timeout(qmp->event,
> > +					       qmp_message_empty(qmp), HZ);
> > +	if (!ret) {
> > +		dev_err(qmp->dev, "ucore did not ack channel\n");
> > +		ret = -ETIMEDOUT;
> > +
> > +		writel(0, qmp->msgram + qmp->offset);
> > +	} else {
> > +		ret = 0;
> > +	}
> > +
> > +out_unlock:
> > +	mutex_unlock(&qmp->tx_lock);
> > +
> > +	return ret;
> > +}

Regards,
Bjorn
Arun Kumar Neelakantam Jan. 3, 2019, 5:48 p.m. UTC | #2
On 12/27/2018 1:58 AM, Bjorn Andersson wrote:
> On Tue 20 Nov 04:22 PST 2018, Arun Kumar Neelakantam wrote:
>
> Thanks for the review Arun.
>
>> On 11/12/2018 1:35 PM, Bjorn Andersson wrote:
> [..]
>>> +int qmp_send(struct qmp *qmp, const void *data, size_t len)
>>> +{
>>> +	int ret;
>>> +
>>> +	if (WARN_ON(len + sizeof(u32) > qmp->size)) {
>>> +		dev_err(qmp->dev, "message too long\n");
>>> +		return -EINVAL;
>>> +	}
>>> +
>>> +	if (WARN_ON(len % sizeof(u32))) {
>>> +		dev_err(qmp->dev, "message not 32-bit aligned\n");
>>> +		return -EINVAL;
>>> +	}
>>> +
>>> +	mutex_lock(&qmp->tx_lock);
>>> +
>>> +	if (!qmp_message_empty(qmp)) {
>>> +		dev_err(qmp->dev, "mailbox left busy\n");
>>> +		ret = -EINVAL;
>> should it be -EBUSY ?
> That makes more sense.
>
>> And qmp_messge_empty will be done either by remote if it process the data
>> else by this driver in TIMEOUT case, so does we need this check for every TX
>> ? I think we can just reset to Zero once in open time.
> Didn't think about that, should we really make the QMP link ready again
> when we get a timeout? Can we expect that the firmware of the remote
> side is ready to serve future messages?
>
>
> Should we keep this check and remove the writel() below?
I prefer we can just remove this check and keep writel() below same as 
down stream.
>
>>> +		goto out_unlock;
>>> +	}
>>> +
>>> +	/* The message RAM only implements 32-bit accesses */
>>> +	__iowrite32_copy(qmp->msgram + qmp->offset + sizeof(u32),
>>> +			 data, len / sizeof(u32));
>>> +	writel(len, qmp->msgram + qmp->offset);
>>> +	qmp_kick(qmp);
>>> +
>>> +	ret = wait_event_interruptible_timeout(qmp->event,
>>> +					       qmp_message_empty(qmp), HZ);
>>> +	if (!ret) {
>>> +		dev_err(qmp->dev, "ucore did not ack channel\n");
>>> +		ret = -ETIMEDOUT;
>>> +
>>> +		writel(0, qmp->msgram + qmp->offset);
>>> +	} else {
>>> +		ret = 0;
>>> +	}
>>> +
>>> +out_unlock:
>>> +	mutex_unlock(&qmp->tx_lock);
>>> +
>>> +	return ret;
>>> +}
> Regards,
> Bjorn
diff mbox series

Patch

diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig
index a51458022d21..ba08fc00d7f5 100644
--- a/drivers/soc/qcom/Kconfig
+++ b/drivers/soc/qcom/Kconfig
@@ -3,6 +3,13 @@ 
 #
 menu "Qualcomm SoC drivers"
 
+config QCOM_AOSS_QMP
+	tristate "Qualcomm AOSS Messaging Driver"
+	help
+	  This driver provides the means for communicating with the
+	  micro-controller in the AOSS, using QMP, to control certain resource
+	  that are not exposed through RPMh.
+
 config QCOM_COMMAND_DB
 	bool "Qualcomm Command DB"
 	depends on ARCH_QCOM || COMPILE_TEST
diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile
index 67cb85d0373c..d0d7fdc94d9a 100644
--- a/drivers/soc/qcom/Makefile
+++ b/drivers/soc/qcom/Makefile
@@ -1,5 +1,6 @@ 
 # SPDX-License-Identifier: GPL-2.0
 CFLAGS_rpmh-rsc.o := -I$(src)
+obj-$(CONFIG_QCOM_AOSS_QMP) +=	aoss-qmp.o
 obj-$(CONFIG_QCOM_GENI_SE) +=	qcom-geni-se.o
 obj-$(CONFIG_QCOM_COMMAND_DB) += cmd-db.o
 obj-$(CONFIG_QCOM_GLINK_SSR) +=	glink_ssr.o
diff --git a/drivers/soc/qcom/aoss-qmp.c b/drivers/soc/qcom/aoss-qmp.c
new file mode 100644
index 000000000000..acc5677a06ed
--- /dev/null
+++ b/drivers/soc/qcom/aoss-qmp.c
@@ -0,0 +1,313 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2018, Linaro Ltd
+ */
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/mailbox_client.h>
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/soc/qcom/aoss-qmp.h>
+#include <linux/wait.h>
+
+#define QMP_DESC_MAGIC			0x0
+#define QMP_DESC_VERSION		0x4
+#define QMP_DESC_FEATURES		0x8
+
+#define QMP_DESC_UCORE_LINK_STATE	0xc
+#define QMP_DESC_UCORE_LINK_STATE_ACK	0x10
+#define QMP_DESC_UCORE_CH_STATE		0x14
+#define QMP_DESC_UCORE_CH_STATE_ACK	0x18
+#define QMP_DESC_UCORE_MBOX_SIZE	0x1c
+#define QMP_DESC_UCORE_MBOX_OFFSET	0x20
+
+#define QMP_DESC_MCORE_LINK_STATE	0x24
+#define QMP_DESC_MCORE_LINK_STATE_ACK	0x28
+#define QMP_DESC_MCORE_CH_STATE		0x2c
+#define QMP_DESC_MCORE_CH_STATE_ACK	0x30
+#define QMP_DESC_MCORE_MBOX_SIZE	0x34
+#define QMP_DESC_MCORE_MBOX_OFFSET	0x38
+
+#define QMP_STATE_UP	0x0000ffff
+#define QMP_STATE_DOWN	0xffff0000
+
+#define QMP_MAGIC	0x4d41494c
+#define QMP_VERSION	1
+
+/**
+ * struct qmp - driver state for QMP implementation
+ * @msgram: iomem referencing the message RAM used for communication
+ * @dev: reference to QMP device
+ * @mbox_client: mailbox client used to ring the doorbell on transmit
+ * @mbox_chan: mailbox channel used to ring the doorbell on transmit
+ * @offset: offset within @msgram where messages should be written
+ * @size: maximum size of the messages to be transmitted
+ * @event: wait_queue for synchronization with the IRQ
+ * @tx_lock: provides syncrhonization between multiple callers of qmp_send()
+ */
+struct qmp {
+	void __iomem *msgram;
+	struct device *dev;
+
+	struct mbox_client mbox_client;
+	struct mbox_chan *mbox_chan;
+
+	size_t offset;
+	size_t size;
+
+	wait_queue_head_t event;
+
+	struct mutex tx_lock;
+};
+
+static void qmp_kick(struct qmp *qmp)
+{
+	mbox_send_message(qmp->mbox_chan, NULL);
+	mbox_client_txdone(qmp->mbox_chan, 0);
+}
+
+static bool qmp_magic_valid(struct qmp *qmp)
+{
+	return readl(qmp->msgram + QMP_DESC_MAGIC) == QMP_MAGIC;
+}
+
+static bool qmp_link_acked(struct qmp *qmp)
+{
+	return readl(qmp->msgram + QMP_DESC_MCORE_LINK_STATE_ACK) == QMP_STATE_UP;
+}
+
+static bool qmp_mcore_channel_acked(struct qmp *qmp)
+{
+	return readl(qmp->msgram + QMP_DESC_MCORE_CH_STATE_ACK) == QMP_STATE_UP;
+}
+
+static bool qmp_ucore_channel_up(struct qmp *qmp)
+{
+	return readl(qmp->msgram + QMP_DESC_UCORE_CH_STATE) == QMP_STATE_UP;
+}
+
+static int qmp_open(struct qmp *qmp)
+{
+	int ret;
+	u32 val;
+
+	ret = wait_event_timeout(qmp->event, qmp_magic_valid(qmp), HZ);
+	if (!ret) {
+		dev_err(qmp->dev, "QMP magic doesn't match\n");
+		return -ETIMEDOUT;
+	}
+
+	val = readl(qmp->msgram + QMP_DESC_VERSION);
+	if (val != QMP_VERSION) {
+		dev_err(qmp->dev, "unsupported QMP version %d\n", val);
+		return -EINVAL;
+	}
+
+	qmp->offset = readl(qmp->msgram + QMP_DESC_MCORE_MBOX_OFFSET);
+	qmp->size = readl(qmp->msgram + QMP_DESC_MCORE_MBOX_SIZE);
+	if (!qmp->size) {
+		dev_err(qmp->dev, "invalid mailbox size 0x%zx\n", qmp->size);
+		return -EINVAL;
+	}
+
+	/* Ack remote core's link state */
+	val = readl(qmp->msgram + QMP_DESC_UCORE_LINK_STATE);
+	writel(val, qmp->msgram + QMP_DESC_UCORE_LINK_STATE_ACK);
+
+	/* Set local core's link state to up */
+	writel(QMP_STATE_UP, qmp->msgram + QMP_DESC_MCORE_LINK_STATE);
+
+	qmp_kick(qmp);
+
+	ret = wait_event_timeout(qmp->event, qmp_link_acked(qmp), HZ);
+	if (!ret) {
+		dev_err(qmp->dev, "ucore didn't ack link\n");
+		goto timeout_close_link;
+	}
+
+	writel(QMP_STATE_UP, qmp->msgram + QMP_DESC_MCORE_CH_STATE);
+
+	ret = wait_event_timeout(qmp->event, qmp_ucore_channel_up(qmp), HZ);
+	if (!ret) {
+		dev_err(qmp->dev, "ucore didn't open channel\n");
+		goto timeout_close_channel;
+	}
+
+	/* Ack remote core's channel state */
+	val = readl(qmp->msgram + QMP_DESC_UCORE_CH_STATE);
+	writel(val, qmp->msgram + QMP_DESC_UCORE_CH_STATE_ACK);
+
+	qmp_kick(qmp);
+
+	ret = wait_event_timeout(qmp->event, qmp_mcore_channel_acked(qmp), HZ);
+	if (!ret) {
+		dev_err(qmp->dev, "ucore didn't ack channel\n");
+		goto timeout_close_channel;
+	}
+
+	return 0;
+
+timeout_close_channel:
+	writel(QMP_STATE_DOWN, qmp->msgram + QMP_DESC_MCORE_CH_STATE);
+
+timeout_close_link:
+	writel(QMP_STATE_DOWN, qmp->msgram + QMP_DESC_MCORE_LINK_STATE);
+	qmp_kick(qmp);
+
+	return -ETIMEDOUT;
+}
+
+static void qmp_close(struct qmp *qmp)
+{
+	writel(QMP_STATE_DOWN, qmp->msgram + QMP_DESC_MCORE_CH_STATE);
+	writel(QMP_STATE_DOWN, qmp->msgram + QMP_DESC_MCORE_LINK_STATE);
+	qmp_kick(qmp);
+}
+
+static irqreturn_t qmp_intr(int irq, void *data)
+{
+	struct qmp *qmp = data;
+
+	wake_up_interruptible_all(&qmp->event);
+
+	return IRQ_HANDLED;
+}
+
+static bool qmp_message_empty(struct qmp *qmp)
+{
+	return readl(qmp->msgram + qmp->offset) == 0;
+}
+
+/**
+ * qmp_send() - send a message to the AOSS
+ * @qmp: qmp context
+ * @data: message to be sent
+ * @len: length of the message
+ *
+ * Transmit @data to AOSS and wait for the AOSS to acknowledge the message.
+ * @len must be a multiple of 4 and not longer than the mailbox size. Access is
+ * synchronized by this implementation.
+ *
+ * Return: 0 on success, negative errno on failure
+ */
+int qmp_send(struct qmp *qmp, const void *data, size_t len)
+{
+	int ret;
+
+	if (WARN_ON(len + sizeof(u32) > qmp->size)) {
+		dev_err(qmp->dev, "message too long\n");
+		return -EINVAL;
+	}
+
+	if (WARN_ON(len % sizeof(u32))) {
+		dev_err(qmp->dev, "message not 32-bit aligned\n");
+		return -EINVAL;
+	}
+
+	mutex_lock(&qmp->tx_lock);
+
+	if (!qmp_message_empty(qmp)) {
+		dev_err(qmp->dev, "mailbox left busy\n");
+		ret = -EINVAL;
+		goto out_unlock;
+	}
+
+	/* The message RAM only implements 32-bit accesses */
+	__iowrite32_copy(qmp->msgram + qmp->offset + sizeof(u32),
+			 data, len / sizeof(u32));
+	writel(len, qmp->msgram + qmp->offset);
+	qmp_kick(qmp);
+
+	ret = wait_event_interruptible_timeout(qmp->event,
+					       qmp_message_empty(qmp), HZ);
+	if (!ret) {
+		dev_err(qmp->dev, "ucore did not ack channel\n");
+		ret = -ETIMEDOUT;
+
+		writel(0, qmp->msgram + qmp->offset);
+	} else {
+		ret = 0;
+	}
+
+out_unlock:
+	mutex_unlock(&qmp->tx_lock);
+
+	return ret;
+}
+EXPORT_SYMBOL(qmp_send);
+
+static int qmp_probe(struct platform_device *pdev)
+{
+	struct resource *res;
+	struct qmp *qmp;
+	int irq;
+	int ret;
+
+	qmp = devm_kzalloc(&pdev->dev, sizeof(*qmp), GFP_KERNEL);
+	if (!qmp)
+		return -ENOMEM;
+
+	qmp->dev = &pdev->dev;
+	init_waitqueue_head(&qmp->event);
+	mutex_init(&qmp->tx_lock);
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	qmp->msgram = devm_ioremap_resource(&pdev->dev, res);
+	if (IS_ERR(qmp->msgram))
+		return PTR_ERR(qmp->msgram);
+
+	qmp->mbox_client.dev = &pdev->dev;
+	qmp->mbox_client.knows_txdone = true;
+	qmp->mbox_chan = mbox_request_channel(&qmp->mbox_client, 0);
+	if (IS_ERR(qmp->mbox_chan)) {
+		dev_err(&pdev->dev, "failed to acquire ipc mailbox\n");
+		return PTR_ERR(qmp->mbox_chan);
+	}
+
+	irq = platform_get_irq(pdev, 0);
+	ret = devm_request_irq(&pdev->dev, irq, qmp_intr, IRQF_ONESHOT,
+			       "aoss-qmp", qmp);
+	if (ret < 0) {
+		dev_err(&pdev->dev, "failed to request interrupt\n");
+		return ret;
+	}
+
+	ret = qmp_open(qmp);
+	if (ret < 0)
+		return ret;
+
+	platform_set_drvdata(pdev, qmp);
+
+	return of_platform_populate(pdev->dev.of_node, NULL, NULL, &pdev->dev);
+}
+
+static int qmp_remove(struct platform_device *pdev)
+{
+	struct qmp *qmp = platform_get_drvdata(pdev);
+
+	of_platform_depopulate(&pdev->dev);
+
+	qmp_close(qmp);
+
+	return 0;
+}
+
+static const struct of_device_id qmp_dt_match[] = {
+	{ .compatible = "qcom,sdm845-aoss-qmp", },
+	{}
+};
+MODULE_DEVICE_TABLE(of, qmp_dt_match);
+
+static struct platform_driver qmp_driver = {
+	.driver = {
+		.name		= "aoss_qmp",
+		.of_match_table	= qmp_dt_match,
+	},
+	.probe = qmp_probe,
+	.remove	= qmp_remove,
+};
+module_platform_driver(qmp_driver);
+
+MODULE_DESCRIPTION("Qualcomm AOSS QMP driver");
+MODULE_LICENSE("GPL v2");
diff --git a/include/linux/soc/qcom/aoss-qmp.h b/include/linux/soc/qcom/aoss-qmp.h
new file mode 100644
index 000000000000..32ccaa091a9f
--- /dev/null
+++ b/include/linux/soc/qcom/aoss-qmp.h
@@ -0,0 +1,12 @@ 
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2018, Linaro Ltd
+ */
+#ifndef __AOP_QMP_H__
+#define __AOP_QMP_H__
+
+struct qmp;
+
+int qmp_send(struct qmp *qmp, const void *data, size_t len);
+
+#endif