diff --git a/drivers/hwtracing/coresight/Makefile b/drivers/hwtracing/coresight/Makefile index 8ab0d43daf23..82457dd69c1e 100644 --- a/drivers/hwtracing/coresight/Makefile +++ b/drivers/hwtracing/coresight/Makefile @@ -9,7 +9,7 @@ coresight-y := coresight-core.o coresight-etm-perf.o coresight-platform.o \ coresight-syscfg-configfs.o obj-$(CONFIG_CORESIGHT_LINK_AND_SINK_TMC) += coresight-tmc.o coresight-tmc-y := coresight-tmc-core.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 000000000000..3433e433a390 --- /dev/null +++ b/drivers/hwtracing/coresight/coresight-byte-cntr.c @@ -0,0 +1,344 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2021 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "coresight-priv.h" +#include "coresight-byte-cntr.h" +#include "coresight-common.h" + +#define CSR_BYTECNTVAL (0x06C) + +static void tmc_etr_read_bytes(struct byte_cntr *byte_cntr_data, loff_t *ppos, + size_t bytes, size_t *len, char **bufp) +{ + struct tmc_drvdata *tmcdrvdata = byte_cntr_data->tmcdrvdata; + struct etr_buf *etr_buf = tmcdrvdata->sysfs_buf; + size_t actual; + + if (*len >= bytes) + *len = bytes; + else if (((uint32_t)*ppos % bytes) + *len > bytes) + *len = bytes - ((uint32_t)*ppos % bytes); + + actual = tmc_etr_buf_get_data(etr_buf, *ppos, *len, bufp); + *len = actual; + if (actual == bytes || (actual + (uint32_t)*ppos) % bytes == 0) + atomic_dec(&byte_cntr_data->irq_cnt); +} + + +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(struct byte_cntr *byte_cntr_data, loff_t *ppos, + size_t bytes, size_t *len) +{ + uint32_t rwp = 0; + struct tmc_drvdata *tmcdrvdata = byte_cntr_data->tmcdrvdata; + dma_addr_t paddr = tmcdrvdata->sysfs_buf->hwaddr; + + rwp = readl_relaxed(tmcdrvdata->base + TMC_RWP); + + if (rwp >= (paddr + *ppos)) { + if (bytes > (rwp - paddr - *ppos)) + *len = rwp - 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; + struct tmc_drvdata *tmcdrvdata = byte_cntr_data->tmcdrvdata; + char *bufp; + int ret = 0; + + if (!data) + return -EINVAL; + + mutex_lock(&byte_cntr_data->byte_cntr_lock); + if (!byte_cntr_data->read_active) { + ret = -EINVAL; + 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 + || !byte_cntr_data->enable)) + return -ERESTARTSYS; + mutex_lock(&byte_cntr_data->byte_cntr_lock); + if (!byte_cntr_data->read_active) { + ret = -EINVAL; + goto err0; + } + + } + + tmc_etr_read_bytes(byte_cntr_data, ppos, + byte_cntr_data->block_size, &len, &bufp); + + } else { + if (!atomic_read(&byte_cntr_data->irq_cnt)) { + tmc_etr_flush_bytes(byte_cntr_data, ppos, byte_cntr_data->block_size, + &len); + if (!len) { + ret = -EINVAL; + goto err0; + } + } else { + tmc_etr_read_bytes(byte_cntr_data, ppos, + byte_cntr_data->block_size, + &len, &bufp); + } + } + + if (copy_to_user(data, bufp, len)) { + mutex_unlock(&byte_cntr_data->byte_cntr_lock); + dev_dbg(&tmcdrvdata->csdev->dev, + "%s: copy_to_user failed\n", __func__); + return -EFAULT; + } + + if (*ppos + len >= tmcdrvdata->size) + *ppos = 0; + else + *ppos += len; + + goto out; + +err0: + mutex_unlock(&byte_cntr_data->byte_cntr_lock); + return ret; +out: + 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 + || byte_cntr_data->read_active) { + 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; + byte_cntr_data->read_active = false; + atomic_set(&byte_cntr_data->irq_cnt, 0); + wake_up(&byte_cntr_data->wq); + coresight_csr_set_byte_cntr(byte_cntr_data->csr, + byte_cntr_data->irqctrl_offset, 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; + + atomic_set(&byte_cntr_data->irq_cnt, 0); + coresight_csr_set_byte_cntr(byte_cntr_data->csr, + byte_cntr_data->irqctrl_offset, 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); + struct tmc_drvdata *tmcdrvdata = byte_cntr_data->tmcdrvdata; + + mutex_lock(&byte_cntr_data->byte_cntr_lock); + + if (tmcdrvdata->mode != CS_MODE_SYSFS || + !byte_cntr_data->block_size) { + mutex_unlock(&byte_cntr_data->byte_cntr_lock); + return -EINVAL; + } + + /* IRQ is a '8- byte' counter and to observe interrupt at + * 'block_size' bytes of data + */ + + coresight_csr_set_byte_cntr(byte_cntr_data->csr, byte_cntr_data->irqctrl_offset, + (byte_cntr_data->block_size) / 8); + + 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_data->name); + 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, + byte_cntr_data->class_name); + 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_data->name); + + 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_kzalloc(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, + dev_name(dev), byte_cntr_data); + if (ret) { + dev_err(dev, "Byte_cntr interrupt registration failed\n"); + return NULL; + } + + ret = of_property_read_u32(dev->of_node, "csr-irqctrl-offset", + &byte_cntr_data->irqctrl_offset); + + if (ret) { + dev_dbg(dev, "Get byte cntr csr irqctrl offset failed\n"); + byte_cntr_data->irqctrl_offset = CSR_BYTECNTVAL; + } + + ret = device_property_read_string(dev, "byte-cntr-name", &byte_cntr_data->name); + if (ret) { + dev_dbg(dev, "Get byte cntr name failed\n"); + byte_cntr_data->name = "byte-cntr"; + } + + ret = device_property_read_string(dev, "byte-cntr-class-name", + &byte_cntr_data->class_name); + if (ret) { + dev_dbg(dev, "Get byte cntr class name failed\n"); + byte_cntr_data->class_name = "coresight-tmc-etr-stream"; + } + + ret = byte_cntr_register_chardev(byte_cntr_data); + if (ret) { + dev_err(dev, "Byte_cntr char dev registration failed\n"); + return NULL; + } + + byte_cntr_data->byte_cntr_irq = byte_cntr_irq; + byte_cntr_data->csr = drvdata->csr; + byte_cntr_data->tmcdrvdata = drvdata; + 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; +} + +void byte_cntr_remove(struct byte_cntr *byte_cntr_data) +{ + device_destroy(byte_cntr_data->driver_class, + byte_cntr_data->dev.dev); + class_destroy(byte_cntr_data->driver_class); + unregister_chrdev_region(byte_cntr_data->dev.dev, 1); +} diff --git a/drivers/hwtracing/coresight/coresight-byte-cntr.h b/drivers/hwtracing/coresight/coresight-byte-cntr.h new file mode 100644 index 000000000000..470fb73dcfdd --- /dev/null +++ b/drivers/hwtracing/coresight/coresight-byte-cntr.h @@ -0,0 +1,35 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2021 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#ifndef _CORESIGHT_BYTE_CNTR_H +#define _CORESIGHT_BYTE_CNTR_H +#include +#include +#include +#include +#include "coresight-tmc.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; + struct coresight_csr *csr; + struct tmc_drvdata *tmcdrvdata; + const char *name; + const char *class_name; + int irqctrl_offset; +}; + +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-core.c b/drivers/hwtracing/coresight/coresight-core.c index 96a83cf25bad..2eb6dfa3f4f4 100644 --- a/drivers/hwtracing/coresight/coresight-core.c +++ b/drivers/hwtracing/coresight/coresight-core.c @@ -670,20 +670,6 @@ int coresight_enable_path(struct list_head *path, u32 mode, void *sink_data) goto out; } -static struct coresight_device *coresight_get_source(struct list_head *path) -{ - struct coresight_device *csdev; - - if (!path) - return NULL; - - csdev = list_first_entry(path, struct coresight_node, link)->csdev; - if (csdev->type != CORESIGHT_DEV_TYPE_SOURCE) - return NULL; - - return csdev; -} - struct coresight_device *coresight_get_sink(struct list_head *path) { struct coresight_device *csdev; diff --git a/drivers/hwtracing/coresight/coresight-tmc-core.c b/drivers/hwtracing/coresight/coresight-tmc-core.c index 9cb87a1ca476..11fc1e3b3163 100644 --- a/drivers/hwtracing/coresight/coresight-tmc-core.c +++ b/drivers/hwtracing/coresight/coresight-tmc-core.c @@ -348,9 +348,51 @@ static ssize_t buffer_size_store(struct device *dev, static DEVICE_ATTR_RW(buffer_size); +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; + + if (val && val < 4096) { + pr_err("Assign minimum block size of 4096 bytes\n"); + return -EINVAL; + } + + mutex_lock(&drvdata->byte_cntr->byte_cntr_lock); + drvdata->byte_cntr->block_size = val; + mutex_unlock(&drvdata->byte_cntr->byte_cntr_lock); + + return size; +} +static DEVICE_ATTR_RW(block_size); + static struct attribute *coresight_tmc_attrs[] = { &dev_attr_trigger_cntr.attr, &dev_attr_buffer_size.attr, + &dev_attr_block_size.attr, NULL, }; @@ -528,6 +570,9 @@ static int tmc_probe(struct amba_device *adev, const struct amba_id *id) if (!of_property_read_u32(dev->of_node, "csr-atid-offset", &drvdata->atid_offset)) coresight_set_csr_ops(&csr_atid_ops); + + drvdata->byte_cntr = byte_cntr_init(adev, drvdata); + break; case TMC_CONFIG_TYPE_ETF: desc.type = CORESIGHT_DEV_TYPE_LINKSINK; @@ -606,6 +651,10 @@ static void tmc_remove(struct amba_device *adev) * handler to this device is closed. */ + if (drvdata->config_type == TMC_CONFIG_TYPE_ETR + && drvdata->byte_cntr) + byte_cntr_remove(drvdata->byte_cntr); + coresight_remove_csr_ops(); misc_deregister(&drvdata->miscdev); coresight_unregister(drvdata->csdev); diff --git a/drivers/hwtracing/coresight/coresight-tmc-etr.c b/drivers/hwtracing/coresight/coresight-tmc-etr.c index 90c69e4df4fd..f9d2dd5938eb 100644 --- a/drivers/hwtracing/coresight/coresight-tmc-etr.c +++ b/drivers/hwtracing/coresight/coresight-tmc-etr.c @@ -938,7 +938,7 @@ static void tmc_free_etr_buf(struct etr_buf *etr_buf) * Returns: The size of the linear data available @pos, with *bufpp * updated to point to the buffer. */ -static ssize_t tmc_etr_buf_get_data(struct etr_buf *etr_buf, +ssize_t tmc_etr_buf_get_data(struct etr_buf *etr_buf, u64 offset, size_t len, char **bufpp) { /* Adjust the length to limit this transaction to end of buffer */ @@ -1235,8 +1235,10 @@ static int tmc_enable_etr_sink_sysfs(struct coresight_device *csdev) if (free_buf) tmc_etr_free_sysfs_buf(free_buf); - if (!ret) + if (!ret) { + tmc_etr_byte_cntr_start(drvdata->byte_cntr); dev_dbg(&csdev->dev, "TMC-ETR enabled\n"); + } return ret; } @@ -1706,6 +1708,7 @@ static int tmc_disable_etr_sink(struct coresight_device *csdev) drvdata->perf_buf = NULL; spin_unlock_irqrestore(&drvdata->spinlock, flags); + tmc_etr_byte_cntr_stop(drvdata->byte_cntr); dev_dbg(&csdev->dev, "TMC-ETR disabled\n"); return 0; @@ -1748,6 +1751,11 @@ int tmc_read_prepare_etr(struct tmc_drvdata *drvdata) goto out; } + if (drvdata->byte_cntr && drvdata->byte_cntr->enable) { + ret = -EINVAL; + goto out; + } + /* Disable the TMC if we are trying to read from a running session. */ if (drvdata->mode == CS_MODE_SYSFS) __tmc_etr_disable_hw(drvdata); diff --git a/drivers/hwtracing/coresight/coresight-tmc.h b/drivers/hwtracing/coresight/coresight-tmc.h index fe291513f1b8..7162fd12a02f 100644 --- a/drivers/hwtracing/coresight/coresight-tmc.h +++ b/drivers/hwtracing/coresight/coresight-tmc.h @@ -13,6 +13,7 @@ #include #include #include "coresight-priv.h" +#include "coresight-byte-cntr.h" #define TMC_RSZ 0x004 #define TMC_STS 0x00c @@ -212,6 +213,7 @@ struct tmc_drvdata { struct mutex idr_mutex; struct etr_buf *sysfs_buf; struct etr_buf *perf_buf; + struct byte_cntr *byte_cntr; struct coresight_csr *csr; const char *csr_name; u32 atid_offset; @@ -273,10 +275,15 @@ extern const struct coresight_ops tmc_etf_cs_ops; ssize_t tmc_etb_get_sysfs_trace(struct tmc_drvdata *drvdata, loff_t pos, size_t len, char **bufpp); +ssize_t tmc_etr_buf_get_data(struct etr_buf *etr_buf, + u64 offset, size_t len, char **bufpp); /* ETR functions */ int tmc_read_prepare_etr(struct tmc_drvdata *drvdata); int tmc_read_unprepare_etr(struct tmc_drvdata *drvdata); void tmc_etr_disable_hw(struct tmc_drvdata *drvdata); +struct byte_cntr *byte_cntr_init(struct amba_device *adev, + struct tmc_drvdata *drvdata); +void byte_cntr_remove(struct byte_cntr *byte_cntr); extern const struct coresight_ops tmc_etr_cs_ops; extern const struct csr_set_atid_op csr_atid_ops; ssize_t tmc_etr_get_sysfs_trace(struct tmc_drvdata *drvdata,