From 8d77ea1fdba49e9b9dc4ca9ec664bb7b67be0db5 Mon Sep 17 00:00:00 2001
From: Mike Bazov <mike@perception-point.io>
Date: Mon, 3 Sep 2018 13:07:05 +0300
Subject: [PATCH 3/4] coresight: tmc-etr: Introduce get_trace() sink operation

Introducing the get_trace() sink operation. This is merely
exposing the tmc_etr_get_sysfs_trace() functionality as a sink
operation, and also implementing for CS_MODE_API.

When using get_trace() with request CS_MODE_API, the trace
is fetched from the __current__ etr buffer, which is assumed to be
the CS_MODE_API buffer set by set_buffer(). The get_trace() can
be further exposed to kernel clients to easily get the trace
buffer and copy trace data from it.

In addition, the tmc-etf is also made aware of this sink op,
without supporting CS_MODE_API.

Signed-off-by: Mike Bazov <mike@perception-point.io>
---
 drivers/hwtracing/coresight/coresight-tmc-etf.c |  14 ++++
 drivers/hwtracing/coresight/coresight-tmc-etr.c | 100 +++++++++++++++++++++---
 include/linux/coresight.h                       |   3 +
 3 files changed, 108 insertions(+), 9 deletions(-)

diff --git a/drivers/hwtracing/coresight/coresight-tmc-etf.c b/drivers/hwtracing/coresight/coresight-tmc-etf.c
index 897dfc1bcbaa..656a786de808 100644
--- a/drivers/hwtracing/coresight/coresight-tmc-etf.c
+++ b/drivers/hwtracing/coresight/coresight-tmc-etf.c
@@ -120,6 +120,19 @@ ssize_t tmc_etb_get_sysfs_trace(struct tmc_drvdata *drvdata,
 	return actual;
 }
 
+ssize_t tmc_etf_get_trace(struct coresight_device *csdev, u32 mode,
+			  loff_t pos, size_t len, char **bufpp)
+{
+	struct tmc_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
+
+	switch (mode) {
+	case CS_MODE_SYSFS:
+		return tmc_etb_get_sysfs_trace(drvdata, pos, len, bufpp);
+	default:
+		return -EINVAL;
+	}
+}
+
 static int tmc_enable_etf_sink_sysfs(struct coresight_device *csdev)
 {
 	int ret = 0;
@@ -490,6 +503,7 @@ static const struct coresight_ops_sink tmc_etf_sink_ops = {
 	.alloc_buffer	= tmc_alloc_etf_buffer,
 	.free_buffer	= tmc_free_etf_buffer,
 	.update_buffer	= tmc_update_etf_buffer,
+	.get_trace      = tmc_etf_get_trace,
 };
 
 static const struct coresight_ops_link tmc_etf_link_ops = {
diff --git a/drivers/hwtracing/coresight/coresight-tmc-etr.c b/drivers/hwtracing/coresight/coresight-tmc-etr.c
index 6ffcaaf76680..f60ac8942c1f 100644
--- a/drivers/hwtracing/coresight/coresight-tmc-etr.c
+++ b/drivers/hwtracing/coresight/coresight-tmc-etr.c
@@ -37,6 +37,8 @@ struct etr_perf_buffer {
 	void			**pages;
 };
 
+static void tmc_etr_disable_hw(struct tmc_drvdata *drvdata);
+
 /* Convert the perf index to an offset within the ETR buffer */
 #define PERF_IDX2OFF(idx, buf)	((idx) % ((buf)->nr_pages << PAGE_SHIFT))
 
@@ -907,6 +909,7 @@ static void tmc_sync_etr_buf(struct tmc_drvdata *drvdata)
 	rrp = tmc_read_rrp(drvdata);
 	rwp = tmc_read_rwp(drvdata);
 	status = readl_relaxed(drvdata->base + TMC_STS);
+
 	etr_buf->full = status & TMC_STS_FULL;
 
 	WARN_ON(!etr_buf->ops || !etr_buf->ops->sync);
@@ -988,29 +991,91 @@ static void tmc_etr_enable_hw(struct tmc_drvdata *drvdata,
  * also updating the @bufpp on where to find it. Since the trace data
  * starts at anywhere in the buffer, depending on the RRP, we adjust the
  * @len returned to handle buffer wrapping around.
- *
- * We are protected here by drvdata->reading != 0, which ensures the
- * sysfs_buf stays alive.
  */
-ssize_t tmc_etr_get_sysfs_trace(struct tmc_drvdata *drvdata,
-				loff_t pos, size_t len, char **bufpp)
+static ssize_t etr_buf_get_trace(struct etr_buf *etr_buf,
+				 loff_t pos, size_t len, char **bufpp)
 {
 	s64 offset;
 	ssize_t actual = len;
-	struct etr_buf *etr_buf = drvdata->sysfs_buf;
+
+	if (!etr_buf)
+		return -EINVAL;
 
 	if (pos + actual > etr_buf->len)
 		actual = etr_buf->len - pos;
 	if (actual <= 0)
-		return actual;
+		return 0;
 
 	/* Compute the offset from which we read the data */
 	offset = etr_buf->offset + pos;
 	if (offset >= etr_buf->size)
 		offset -= etr_buf->size;
+
 	return tmc_etr_buf_get_data(etr_buf, offset, actual, bufpp);
 }
 
+/* We are protected here by drvdata->reading != 0, which ensures
+ * the sysfs_buf stays alive.
+ */
+ssize_t tmc_etr_get_sysfs_trace(struct tmc_drvdata *drvdata,
+				loff_t pos, size_t len, char **bufpp)
+{
+	return etr_buf_get_trace(drvdata->sysfs_buf, pos, len, bufpp);
+}
+
+/* It is the user's responsbility to keep the coresight session buffer
+ * valid.
+ */
+static ssize_t tmc_etr_get_api_trace(struct tmc_drvdata *drvdata,
+				     loff_t pos, size_t len, char **bufpp)
+{
+	ssize_t ret = 0;
+	unsigned long flags;
+
+	spin_lock_irqsave(&drvdata->spinlock, flags);
+
+	if (drvdata->mode != CS_MODE_API) {
+		ret = -EINVAL;
+		goto out;
+	}
+
+	if (drvdata->api_buf == NULL) {
+		ret = -ENOENT;
+		goto out;
+	}
+
+	/* Make sure to stop and sync the buffer if we're getting a trace
+	 * from a running session.
+	 */
+	tmc_etr_disable_hw(drvdata);
+
+	ret = etr_buf_get_trace(drvdata->api_buf, pos, len, bufpp);
+
+	/* Re-enable the trace session. Again, it is entirely up the user to
+	 * read the actual trace data before reusing the existing buffer, or,
+	 * perhaps, use a different buffer.
+	 */
+	tmc_etr_enable_hw(drvdata, drvdata->api_buf);
+out:
+	spin_unlock_irqrestore(&drvdata->spinlock, flags);
+	return ret;
+}
+
+ssize_t tmc_etr_get_trace(struct coresight_device *csdev, u32 mode,
+			  loff_t poss, size_t len, char **buffpp)
+{
+	struct tmc_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
+
+	switch (mode) {
+	case CS_MODE_SYSFS:
+		return tmc_etr_get_sysfs_trace(drvdata, poss, len, buffpp);
+	case CS_MODE_API:
+		return tmc_etr_get_api_trace(drvdata, poss, len, buffpp);
+	default:
+		return -EINVAL;
+	}
+}
+
 static struct etr_buf *
 tmc_etr_setup_sysfs_buf(struct tmc_drvdata *drvdata)
 {
@@ -1037,6 +1102,17 @@ static void tmc_etr_sync_sysfs_buf(struct tmc_drvdata *drvdata)
 	}
 }
 
+static void tmc_etr_sync_api_buf(struct tmc_drvdata *drvdata)
+{
+	struct etr_buf *etr_buf = drvdata->etr_buf;
+
+	/* We aren't certain there is indeed an etr buffer here, the user
+	 * might set it to NULL, he is controling the buffer.
+	 */
+	if (etr_buf && !WARN_ON(drvdata->api_buf != etr_buf))
+		tmc_sync_etr_buf(drvdata);
+}
+
 static void tmc_etr_disable_hw(struct tmc_drvdata *drvdata)
 {
 	CS_UNLOCK(drvdata->base);
@@ -1048,9 +1124,10 @@ static void tmc_etr_disable_hw(struct tmc_drvdata *drvdata)
 	 */
 	if (drvdata->mode == CS_MODE_SYSFS)
 		tmc_etr_sync_sysfs_buf(drvdata);
+	else if (drvdata->mode == CS_MODE_API)
+		tmc_etr_sync_api_buf(drvdata);
 
 	tmc_disable_hw(drvdata);
-
 	CS_LOCK(drvdata->base);
 
 	/* Disable CATU device if this ETR is connected to one */
@@ -1460,7 +1537,11 @@ void tmc_disable_etr_sink(struct coresight_device *csdev)
 
 	/* Disable the TMC only if it needs to */
 	if (drvdata->mode != CS_MODE_DISABLED) {
-		tmc_etr_disable_hw(drvdata);
+		/* Only disable the hardware if the mode isn't API,
+		 * or, if the mode is API, only if we have a valid buffer.
+		 */
+		if (drvdata->mode != CS_MODE_API || drvdata->api_buf != NULL)
+			tmc_etr_disable_hw(drvdata);
 		drvdata->mode = CS_MODE_DISABLED;
 	}
 
@@ -1476,6 +1557,7 @@ static const struct coresight_ops_sink tmc_etr_sink_ops = {
 	.update_buffer	= tmc_update_etr_buffer,
 	.set_buffer	= tmc_etr_set_buffer,
 	.free_buffer	= tmc_free_etr_buffer,
+	.get_trace      = tmc_etr_get_trace
 };
 
 const struct coresight_ops tmc_etr_cs_ops = {
diff --git a/include/linux/coresight.h b/include/linux/coresight.h
index eca2e166a7d9..342c71ae453e 100644
--- a/include/linux/coresight.h
+++ b/include/linux/coresight.h
@@ -187,6 +187,7 @@ struct coresight_device {
  * @free_buffer:	release memory allocated in @get_config.
  * @set_buffer:		set the trace buffer.
  * @update_buffer:	update buffer pointers after a trace session.
+ * @get_trace:	get a pointer to the trace data.
  */
 struct coresight_ops_sink {
 	int (*enable)(struct coresight_device *csdev, u32 mode, void *data);
@@ -200,6 +201,8 @@ struct coresight_ops_sink {
 	unsigned long (*update_buffer)(struct coresight_device *csdev,
 			      struct perf_output_handle *handle,
 			      void *sink_config);
+	ssize_t (*get_trace)(struct coresight_device *csdev, u32 mode,
+		      loff_t poss, size_t len, char **buf);
 };
 
 /**
-- 
2.16.2

