coresight: byte-cntr: Add support for streaming interface for ETR

Add support for a streaming interface for TMC ETR to allow for continuous
log collection to secondary storage. An interrupt based mechanism is used
to stream out the data from the device. The streaming interface cannot be
used in conjunction with the traditional ETR read operation.

Change-Id: I9a78107273f295ecb219f89ec728aa7b9eea27e0
Signed-off-by: Satyajit Desai <sadesai@codeaurora.org>
diff --git a/drivers/hwtracing/coresight/Makefile b/drivers/hwtracing/coresight/Makefile
index 44e55ff..cb47ecd 100644
--- a/drivers/hwtracing/coresight/Makefile
+++ b/drivers/hwtracing/coresight/Makefile
@@ -5,7 +5,8 @@
 obj-$(CONFIG_OF) += of_coresight.o
 obj-$(CONFIG_CORESIGHT_LINK_AND_SINK_TMC) += coresight-tmc.o \
 					     coresight-tmc-etf.o \
-					     coresight-tmc-etr.o
+					     coresight-tmc-etr.o \
+					     coresight-byte-cntr.o
 obj-$(CONFIG_CORESIGHT_SINK_TPIU) += coresight-tpiu.o
 obj-$(CONFIG_CORESIGHT_SINK_ETBV10) += coresight-etb10.o
 obj-$(CONFIG_CORESIGHT_LINKS_AND_SINKS) += coresight-funnel.o \
diff --git a/drivers/hwtracing/coresight/coresight-byte-cntr.c b/drivers/hwtracing/coresight/coresight-byte-cntr.c
new file mode 100644
index 0000000..496738c
--- /dev/null
+++ b/drivers/hwtracing/coresight/coresight-byte-cntr.c
@@ -0,0 +1,374 @@
+/* Copyright (c) 2017, 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/interrupt.h>
+#include <linux/dma-mapping.h>
+#include <linux/fs.h>
+#include <linux/of_irq.h>
+#include <linux/moduleparam.h>
+#include <linux/delay.h>
+
+#include "coresight-byte-cntr.h"
+#include "coresight-priv.h"
+#include "coresight-tmc.h"
+
+static struct tmc_drvdata *tmcdrvdata;
+
+static void tmc_etr_read_bytes(struct byte_cntr *byte_cntr_data, loff_t *ppos,
+			       size_t bytes, size_t *len)
+{
+	if (*len >= bytes) {
+		atomic_dec(&byte_cntr_data->irq_cnt);
+		*len = bytes;
+	} else {
+		if (((uint32_t)*ppos % bytes) + *len > bytes)
+			*len = bytes - ((uint32_t)*ppos % bytes);
+		if ((*len + (uint32_t)*ppos) % bytes == 0)
+			atomic_dec(&byte_cntr_data->irq_cnt);
+	}
+}
+
+static void tmc_etr_sg_read_pos(loff_t *ppos,
+				size_t bytes, bool noirq, size_t *len,
+				char **bufpp)
+{
+	uint32_t rwp, i = 0;
+	uint32_t blk_num, sg_tbl_num, blk_num_loc, read_off;
+	uint32_t *virt_pte, *virt_st_tbl;
+	void *virt_blk;
+	phys_addr_t phys_pte;
+	int total_ents = DIV_ROUND_UP(tmcdrvdata->size, PAGE_SIZE);
+	int ents_per_pg = PAGE_SIZE/sizeof(uint32_t);
+
+	if (*len == 0)
+		return;
+
+	blk_num = *ppos / PAGE_SIZE;
+	read_off = *ppos % PAGE_SIZE;
+
+	virt_st_tbl = (uint32_t *)tmcdrvdata->vaddr;
+
+	/* Compute table index and block entry index within that table */
+	if (blk_num && (blk_num == (total_ents - 1)) &&
+	    !(blk_num % (ents_per_pg - 1))) {
+		sg_tbl_num = blk_num / ents_per_pg;
+		blk_num_loc = ents_per_pg - 1;
+	} else {
+		sg_tbl_num = blk_num / (ents_per_pg - 1);
+		blk_num_loc = blk_num % (ents_per_pg - 1);
+	}
+
+	for (i = 0; i < sg_tbl_num; i++) {
+		virt_pte = virt_st_tbl + (ents_per_pg - 1);
+		phys_pte = TMC_ETR_SG_ENT_TO_BLK(*virt_pte);
+		virt_st_tbl = (uint32_t *)phys_to_virt(phys_pte);
+	}
+
+	virt_pte = virt_st_tbl + blk_num_loc;
+	phys_pte = TMC_ETR_SG_ENT_TO_BLK(*virt_pte);
+	virt_blk = phys_to_virt(phys_pte);
+
+	*bufpp = (char *)(virt_blk + read_off);
+
+	if (noirq) {
+		rwp = readl_relaxed(tmcdrvdata->base + TMC_RWP);
+		tmc_etr_sg_rwp_pos(tmcdrvdata, rwp);
+		if (tmcdrvdata->sg_blk_num == blk_num &&
+		    rwp >= (phys_pte + read_off))
+			*len = rwp - phys_pte - read_off;
+		else if (tmcdrvdata->sg_blk_num > blk_num)
+			*len = PAGE_SIZE - read_off;
+		else
+			*len = bytes;
+	} else {
+
+		if (*len > (PAGE_SIZE - read_off))
+			*len = PAGE_SIZE - read_off;
+
+		if (*len >= (bytes - ((uint32_t)*ppos % bytes)))
+			*len = bytes - ((uint32_t)*ppos % bytes);
+
+		if ((*len + (uint32_t)*ppos) % bytes == 0)
+			atomic_dec(&tmcdrvdata->byte_cntr->irq_cnt);
+	}
+
+	/*
+	 * Invalidate cache range before reading. This will make sure that CPU
+	 * reads latest contents from DDR
+	 */
+	dmac_inv_range((void *)(*bufpp), (void *)(*bufpp) + *len);
+}
+
+static irqreturn_t etr_handler(int irq, void *data)
+{
+	struct byte_cntr *byte_cntr_data = data;
+
+	atomic_inc(&byte_cntr_data->irq_cnt);
+
+	wake_up(&byte_cntr_data->wq);
+
+	return IRQ_HANDLED;
+}
+
+static void tmc_etr_flush_bytes(loff_t *ppos, size_t bytes, size_t *len)
+{
+	uint32_t rwp = 0;
+
+	rwp = readl_relaxed(tmcdrvdata->base + TMC_RWP);
+
+	if (rwp >= (tmcdrvdata->paddr + *ppos)) {
+		if (bytes > (rwp - tmcdrvdata->paddr - *ppos))
+			*len = rwp - tmcdrvdata->paddr - *ppos;
+	}
+}
+
+static ssize_t tmc_etr_byte_cntr_read(struct file *fp, char __user *data,
+			       size_t len, loff_t *ppos)
+{
+	struct byte_cntr *byte_cntr_data = fp->private_data;
+	char *bufp;
+
+	if (!data)
+		return -EINVAL;
+
+	mutex_lock(&byte_cntr_data->byte_cntr_lock);
+	if (!byte_cntr_data->read_active)
+		goto err0;
+
+	if (byte_cntr_data->enable) {
+		if (!atomic_read(&byte_cntr_data->irq_cnt)) {
+			mutex_unlock(&byte_cntr_data->byte_cntr_lock);
+			if (wait_event_interruptible(byte_cntr_data->wq,
+				atomic_read(&byte_cntr_data->irq_cnt) > 0))
+				return -ERESTARTSYS;
+			mutex_lock(&byte_cntr_data->byte_cntr_lock);
+			if (!byte_cntr_data->read_active)
+				goto err0;
+		}
+		bufp = (char *)(tmcdrvdata->vaddr + *ppos);
+
+		if (tmcdrvdata->mem_type == TMC_ETR_MEM_TYPE_CONTIG)
+			tmc_etr_read_bytes(byte_cntr_data, ppos,
+					   byte_cntr_data->block_size, &len);
+		else
+			tmc_etr_sg_read_pos(ppos, byte_cntr_data->block_size, 0,
+					    &len, &bufp);
+
+	} else {
+		if (!atomic_read(&byte_cntr_data->irq_cnt)) {
+			if (tmcdrvdata->mem_type == TMC_ETR_MEM_TYPE_CONTIG)
+				tmc_etr_flush_bytes(ppos,
+						    byte_cntr_data->block_size,
+						    &len);
+			else
+				tmc_etr_sg_read_pos(ppos,
+						    byte_cntr_data->block_size,
+						    1,
+						    &len, &bufp);
+			if (!len)
+				goto err0;
+		} else {
+			if (tmcdrvdata->mem_type == TMC_ETR_MEM_TYPE_CONTIG)
+				tmc_etr_read_bytes(byte_cntr_data, ppos,
+						   byte_cntr_data->block_size,
+						   &len);
+			else
+				tmc_etr_sg_read_pos(ppos,
+						    byte_cntr_data->block_size,
+						    1,
+						    &len, &bufp);
+		}
+	}
+
+	if (copy_to_user(data, bufp, len)) {
+		mutex_unlock(&byte_cntr_data->byte_cntr_lock);
+		dev_dbg(tmcdrvdata->dev, "%s: copy_to_user failed\n", __func__);
+		return -EFAULT;
+	}
+
+	if (*ppos + len >= tmcdrvdata->size)
+		*ppos = 0;
+	else
+		*ppos += len;
+err0:
+	mutex_unlock(&byte_cntr_data->byte_cntr_lock);
+
+	return len;
+}
+
+void tmc_etr_byte_cntr_start(struct byte_cntr *byte_cntr_data)
+{
+	if (!byte_cntr_data)
+		return;
+
+	mutex_lock(&byte_cntr_data->byte_cntr_lock);
+
+	if (byte_cntr_data->block_size == 0) {
+		mutex_unlock(&byte_cntr_data->byte_cntr_lock);
+		return;
+	}
+
+	atomic_set(&byte_cntr_data->irq_cnt, 0);
+	byte_cntr_data->enable = true;
+	mutex_unlock(&byte_cntr_data->byte_cntr_lock);
+}
+EXPORT_SYMBOL(tmc_etr_byte_cntr_start);
+
+void tmc_etr_byte_cntr_stop(struct byte_cntr *byte_cntr_data)
+{
+	if (!byte_cntr_data)
+		return;
+
+	mutex_lock(&byte_cntr_data->byte_cntr_lock);
+	byte_cntr_data->enable = false;
+	coresight_csr_set_byte_cntr(0);
+	mutex_unlock(&byte_cntr_data->byte_cntr_lock);
+
+}
+EXPORT_SYMBOL(tmc_etr_byte_cntr_stop);
+
+
+static int tmc_etr_byte_cntr_release(struct inode *in, struct file *fp)
+{
+	struct byte_cntr *byte_cntr_data = fp->private_data;
+
+	mutex_lock(&byte_cntr_data->byte_cntr_lock);
+	byte_cntr_data->read_active = false;
+
+	coresight_csr_set_byte_cntr(0);
+	mutex_unlock(&byte_cntr_data->byte_cntr_lock);
+
+	return 0;
+}
+
+static int tmc_etr_byte_cntr_open(struct inode *in, struct file *fp)
+{
+	struct byte_cntr *byte_cntr_data =
+			container_of(in->i_cdev, struct byte_cntr, dev);
+
+	mutex_lock(&byte_cntr_data->byte_cntr_lock);
+
+	if (!tmcdrvdata->enable || !byte_cntr_data->block_size) {
+		mutex_unlock(&byte_cntr_data->byte_cntr_lock);
+		return -EINVAL;
+	}
+
+	coresight_csr_set_byte_cntr(byte_cntr_data->block_size);
+	fp->private_data = byte_cntr_data;
+	nonseekable_open(in, fp);
+	byte_cntr_data->enable = true;
+	byte_cntr_data->read_active = true;
+	mutex_unlock(&byte_cntr_data->byte_cntr_lock);
+
+	return 0;
+}
+
+static const struct file_operations byte_cntr_fops = {
+	.owner		= THIS_MODULE,
+	.open		= tmc_etr_byte_cntr_open,
+	.read		= tmc_etr_byte_cntr_read,
+	.release	= tmc_etr_byte_cntr_release,
+	.llseek		= no_llseek,
+};
+
+static int byte_cntr_register_chardev(struct byte_cntr *byte_cntr_data)
+{
+	int ret;
+	unsigned int baseminor = 0;
+	unsigned int count = 1;
+	struct device *device;
+	dev_t dev;
+
+	ret = alloc_chrdev_region(&dev, baseminor, count, "byte-cntr");
+	if (ret < 0) {
+		pr_err("alloc_chrdev_region failed %d\n", ret);
+		return ret;
+	}
+	cdev_init(&byte_cntr_data->dev, &byte_cntr_fops);
+
+	byte_cntr_data->dev.owner = THIS_MODULE;
+	byte_cntr_data->dev.ops = &byte_cntr_fops;
+
+	ret = cdev_add(&byte_cntr_data->dev, dev, 1);
+	if (ret)
+		goto exit_unreg_chrdev_region;
+
+	byte_cntr_data->driver_class = class_create(THIS_MODULE,
+						   "coresight-tmc-etr-stream");
+	if (IS_ERR(byte_cntr_data->driver_class)) {
+		ret = -ENOMEM;
+		pr_err("class_create failed %d\n", ret);
+		goto exit_unreg_chrdev_region;
+	}
+
+	device = device_create(byte_cntr_data->driver_class, NULL,
+			       byte_cntr_data->dev.dev, byte_cntr_data,
+			       "byte-cntr");
+
+	if (IS_ERR(device)) {
+		pr_err("class_device_create failed %d\n", ret);
+		ret = -ENOMEM;
+		goto exit_destroy_class;
+	}
+
+	return 0;
+
+exit_destroy_class:
+	class_destroy(byte_cntr_data->driver_class);
+exit_unreg_chrdev_region:
+	unregister_chrdev_region(byte_cntr_data->dev.dev, 1);
+	return ret;
+}
+
+struct byte_cntr *byte_cntr_init(struct amba_device *adev,
+				 struct tmc_drvdata *drvdata)
+{
+	struct device *dev = &adev->dev;
+	struct device_node *np = adev->dev.of_node;
+	int byte_cntr_irq;
+	int ret;
+	struct byte_cntr *byte_cntr_data;
+
+	byte_cntr_irq = of_irq_get_byname(np, "byte-cntr-irq");
+	if (byte_cntr_irq < 0)
+		return NULL;
+
+	byte_cntr_data = devm_kmalloc(dev, sizeof(*byte_cntr_data), GFP_KERNEL);
+	if (!byte_cntr_data)
+		return NULL;
+
+	ret = devm_request_irq(dev, byte_cntr_irq, etr_handler,
+			       IRQF_TRIGGER_RISING | IRQF_SHARED,
+			       "tmc-etr", byte_cntr_data);
+	if (ret) {
+		devm_kfree(dev, byte_cntr_data);
+		dev_err(dev, "Byte_cntr interrupt registration failed\n");
+		return NULL;
+	}
+
+	ret = byte_cntr_register_chardev(byte_cntr_data);
+	if (ret) {
+		devm_free_irq(dev, byte_cntr_irq, byte_cntr_data);
+		devm_kfree(dev, byte_cntr_data);
+		dev_err(dev, "Byte_cntr char dev registration failed\n");
+		return NULL;
+	}
+
+	tmcdrvdata = drvdata;
+	byte_cntr_data->block_size = 0;
+	byte_cntr_data->byte_cntr_irq = byte_cntr_irq;
+	atomic_set(&byte_cntr_data->irq_cnt, 0);
+	init_waitqueue_head(&byte_cntr_data->wq);
+	mutex_init(&byte_cntr_data->byte_cntr_lock);
+
+	return byte_cntr_data;
+}
+EXPORT_SYMBOL(byte_cntr_init);
diff --git a/drivers/hwtracing/coresight/coresight-byte-cntr.h b/drivers/hwtracing/coresight/coresight-byte-cntr.h
new file mode 100644
index 0000000..94e9089
--- /dev/null
+++ b/drivers/hwtracing/coresight/coresight-byte-cntr.h
@@ -0,0 +1,24 @@
+#ifndef _CORESIGHT_BYTE_CNTR_H
+#define _CORESIGHT_BYTE_CNTR_H
+#include <linux/cdev.h>
+#include <linux/amba/bus.h>
+#include <linux/wait.h>
+#include <linux/mutex.h>
+
+struct byte_cntr {
+	struct cdev		dev;
+	struct class		*driver_class;
+	bool			enable;
+	bool			read_active;
+	uint32_t		byte_cntr_value;
+	uint32_t		block_size;
+	int			byte_cntr_irq;
+	atomic_t		irq_cnt;
+	wait_queue_head_t	wq;
+	struct mutex		byte_cntr_lock;
+};
+
+extern void tmc_etr_byte_cntr_start(struct byte_cntr *byte_cntr_data);
+extern void tmc_etr_byte_cntr_stop(struct byte_cntr *byte_cntr_data);
+
+#endif
diff --git a/drivers/hwtracing/coresight/coresight-priv.h b/drivers/hwtracing/coresight/coresight-priv.h
index 3af358a..67e368d 100644
--- a/drivers/hwtracing/coresight/coresight-priv.h
+++ b/drivers/hwtracing/coresight/coresight-priv.h
@@ -153,12 +153,14 @@
 extern void msm_qdss_csr_disable_bam_to_usb(void);
 extern void msm_qdss_csr_disable_flush(void);
 extern int coresight_csr_hwctrl_set(uint64_t addr, uint32_t val);
+extern void coresight_csr_set_byte_cntr(uint32_t count);
 #else
 static inline void msm_qdss_csr_enable_bam_to_usb(void) {}
 static inline void msm_qdss_csr_disable_bam_to_usb(void) {}
 static inline void msm_qdss_csr_disable_flush(void) {}
 static inline int coresight_csr_hwctrl_set(uint64_t addr,	95
 					   uint32_t val) { return -EINVAL; }
+extern void coresight_csr_set_byte_cntr(uint32_t count) {}
 #endif
 
 #endif
diff --git a/drivers/hwtracing/coresight/coresight-tmc-etr.c b/drivers/hwtracing/coresight/coresight-tmc-etr.c
index 2a5a1bd..9e024ce 100644
--- a/drivers/hwtracing/coresight/coresight-tmc-etr.c
+++ b/drivers/hwtracing/coresight/coresight-tmc-etr.c
@@ -351,7 +351,7 @@
 	tmc_etr_sg_tbl_flush(vaddr, size);
 }
 
-static void tmc_etr_sg_rwp_pos(struct tmc_drvdata *drvdata, uint32_t rwp)
+void tmc_etr_sg_rwp_pos(struct tmc_drvdata *drvdata, uint32_t rwp)
 {
 	uint32_t i = 0, pte_n = 0, last_pte;
 	uint32_t *virt_st_tbl, *virt_pte;
@@ -403,6 +403,7 @@
 			break;
 	}
 }
+EXPORT_SYMBOL(tmc_etr_sg_rwp_pos);
 
 static void tmc_etr_mem_reset(struct tmc_drvdata *drvdata)
 {
@@ -831,6 +832,8 @@
 	drvdata->sticky_enable = true;
 out:
 	spin_unlock_irqrestore(&drvdata->spinlock, flags);
+	if (drvdata->out_mode == TMC_ETR_OUT_MODE_MEM)
+		tmc_etr_byte_cntr_start(drvdata->byte_cntr);
 	mutex_unlock(&drvdata->mem_lock);
 
 	if (!ret)
@@ -919,6 +922,7 @@
 	if (drvdata->out_mode == TMC_ETR_OUT_MODE_MEM) {
 		coresight_cti_unmap_trigin(drvdata->cti_reset, 2, 0);
 		coresight_cti_unmap_trigout(drvdata->cti_flush, 3, 0);
+		tmc_etr_byte_cntr_stop(drvdata->byte_cntr);
 	}
 out:
 	mutex_unlock(&drvdata->mem_lock);
@@ -990,6 +994,11 @@
 		goto out;
 	}
 
+	if (!drvdata->byte_cntr || drvdata->byte_cntr->enable) {
+		ret = -EINVAL;
+		goto out;
+	}
+
 	/* Disable the TMC if need be */
 	if (val == CS_MODE_SYSFS)
 		tmc_etr_disable_hw(drvdata);
diff --git a/drivers/hwtracing/coresight/coresight-tmc.c b/drivers/hwtracing/coresight/coresight-tmc.c
index b97ebb8..6f13eb3 100644
--- a/drivers/hwtracing/coresight/coresight-tmc.c
+++ b/drivers/hwtracing/coresight/coresight-tmc.c
@@ -459,6 +459,42 @@
 }
 static DEVICE_ATTR_RW(mem_type);
 
+static ssize_t block_size_show(struct device *dev,
+			     struct device_attribute *attr,
+			     char *buf)
+{
+	struct tmc_drvdata *drvdata = dev_get_drvdata(dev->parent);
+	uint32_t val = 0;
+
+	if (drvdata->byte_cntr)
+		val = drvdata->byte_cntr->block_size;
+
+	return scnprintf(buf, PAGE_SIZE, "%d\n",
+			val);
+}
+
+static ssize_t block_size_store(struct device *dev,
+			      struct device_attribute *attr,
+			      const char *buf,
+			      size_t size)
+{
+	struct tmc_drvdata *drvdata = dev_get_drvdata(dev->parent);
+	unsigned long val;
+
+	if (kstrtoul(buf, 0, &val))
+		return -EINVAL;
+
+	if (!drvdata->byte_cntr)
+		return -EINVAL;
+
+	mutex_lock(&drvdata->byte_cntr->byte_cntr_lock);
+	drvdata->byte_cntr->block_size = val * 8;
+	mutex_unlock(&drvdata->byte_cntr->byte_cntr_lock);
+
+	return size;
+}
+static DEVICE_ATTR_RW(block_size);
+
 static struct attribute *coresight_tmc_etf_attrs[] = {
 	&dev_attr_trigger_cntr.attr,
 	NULL,
@@ -470,6 +506,7 @@
 	&dev_attr_trigger_cntr.attr,
 	&dev_attr_out_mode.attr,
 	&dev_attr_available_out_modes.attr,
+	&dev_attr_block_size.attr,
 	NULL,
 };
 
@@ -587,6 +624,8 @@
 		desc.groups = coresight_tmc_etr_groups;
 		desc.subtype.sink_subtype = CORESIGHT_DEV_SUBTYPE_SINK_BUFFER;
 
+		drvdata->byte_cntr = byte_cntr_init(adev, drvdata);
+
 		ret = tmc_etr_bam_init(adev, drvdata);
 		if (ret)
 			goto out;
diff --git a/drivers/hwtracing/coresight/coresight-tmc.h b/drivers/hwtracing/coresight/coresight-tmc.h
index 6643adc..fe6bc76 100644
--- a/drivers/hwtracing/coresight/coresight-tmc.h
+++ b/drivers/hwtracing/coresight/coresight-tmc.h
@@ -27,6 +27,8 @@
 #include <linux/usb/usb_qdss.h>
 #include <linux/coresight-cti.h>
 
+#include "coresight-byte-cntr.h"
+
 #define TMC_RSZ			0x004
 #define TMC_STS			0x00c
 #define TMC_RRD			0x010
@@ -167,7 +169,7 @@
 	bool			enable;
 	char			*buf;
 	dma_addr_t		paddr;
-	void __iomem		*vaddr;
+	void			*vaddr;
 	u32			size;
 	u32			len;
 	local_t			mode;
@@ -187,6 +189,7 @@
 	bool			sticky_enable;
 	struct coresight_cti	*cti_flush;
 	struct coresight_cti	*cti_reset;
+	struct byte_cntr	*byte_cntr;
 };
 
 /* Generic functions */
@@ -214,5 +217,9 @@
 		  struct usb_qdss_ch *ch);
 int tmc_etr_bam_init(struct amba_device *adev,
 		     struct tmc_drvdata *drvdata);
+extern struct byte_cntr *byte_cntr_init(struct amba_device *adev,
+					struct tmc_drvdata *drvdata);
+extern void tmc_etr_sg_rwp_pos(struct tmc_drvdata *drvdata, uint32_t rwp);
+
 extern const struct coresight_ops tmc_etr_cs_ops;
 #endif