[PATCH v21 2/8] coresight: tmc: add create/clean functions for etr_buf_list
Jie Gan
jie.gan at oss.qualcomm.com
Mon Nov 2 01:46:30 PST 2026
Introduce functions for creating and inserting or removing the
etr_buf_node to/from the etr_buf_list.
The byte-cntr functionality requires two etr_buf to receive trace data.
The active etr_buf collects the trace data from source device, while the
byte-cntr reading function accesses the deactivated etr_buf after is
has been filled and synced, transferring data to the userspace.
Reviewed-by: Mike Leach <mike.leach at arm.com>
Signed-off-by: Jie Gan <jie.gan at oss.qualcomm.com>
---
drivers/hwtracing/coresight/coresight-tmc-core.c | 1 +
drivers/hwtracing/coresight/coresight-tmc-etr.c | 168 ++++++++++++++++++++++-
drivers/hwtracing/coresight/coresight-tmc.h | 18 +++
3 files changed, 185 insertions(+), 2 deletions(-)
diff --git a/drivers/hwtracing/coresight/coresight-tmc-core.c b/drivers/hwtracing/coresight/coresight-tmc-core.c
index bc5a133ada3e..bc7dd676da47 100644
--- a/drivers/hwtracing/coresight/coresight-tmc-core.c
+++ b/drivers/hwtracing/coresight/coresight-tmc-core.c
@@ -835,6 +835,7 @@ static int __tmc_probe(struct device *dev, struct resource *res)
idr_init(&drvdata->idr);
mutex_init(&drvdata->idr_mutex);
dev_list = "tmc_etr";
+ INIT_LIST_HEAD(&drvdata->etr_buf_list);
break;
case TMC_CONFIG_TYPE_ETF:
desc.groups = coresight_etf_groups;
diff --git a/drivers/hwtracing/coresight/coresight-tmc-etr.c b/drivers/hwtracing/coresight/coresight-tmc-etr.c
index 361a433e6f0c..eee98a9269df 100644
--- a/drivers/hwtracing/coresight/coresight-tmc-etr.c
+++ b/drivers/hwtracing/coresight/coresight-tmc-etr.c
@@ -1268,7 +1268,9 @@ static struct etr_buf *tmc_etr_get_sysfs_buffer(struct coresight_device *csdev)
raw_spin_lock_irqsave(&drvdata->spinlock, flags);
}
- if (drvdata->reading || coresight_get_mode(csdev) == CS_MODE_PERF) {
+ /* The sysfs_buf is owned by the etr_buf_list, don't replace it */
+ if (drvdata->reading || coresight_get_mode(csdev) == CS_MODE_PERF ||
+ !list_empty(&drvdata->etr_buf_list)) {
ret = -EBUSY;
goto out;
}
@@ -1918,6 +1920,167 @@ const struct coresight_ops tmc_etr_cs_ops = {
.panic_ops = &tmc_etr_sync_ops,
};
+/**
+ * tmc_clean_etr_buf_list - clean the etr_buf_list.
+ * @drvdata: driver data of the TMC device.
+ *
+ * Remove all nodes from @drvdata->etr_buf_list and free their buffers.
+ * If a node holds the live sysfs_buf and the sink is enabled in sysfs mode, the
+ * node is removed but the buffer is not freed; ownership stays with
+ * drvdata->sysfs_buf.
+ *
+ * Locking: must not be called with @drvdata->spinlock held. The spinlock is
+ * taken internally to serialise the @drvdata->sysfs_buf accesses and the
+ * removal of the nodes against the ETR sink enable/disable paths and the
+ * generic sysfs read. There must be only one owner of the list at a time.
+ * Must be called from process context: buffers are freed with the lock
+ * released.
+ */
+void tmc_clean_etr_buf_list(struct tmc_drvdata *drvdata)
+{
+ struct etr_buf_node *nd, *next;
+ unsigned long flags;
+
+ list_for_each_entry_safe(nd, next, &drvdata->etr_buf_list, link) {
+ raw_spin_lock_irqsave(&drvdata->spinlock, flags);
+ if (nd->sysfs_buf == drvdata->sysfs_buf) {
+ if (coresight_get_mode(drvdata->csdev) == CS_MODE_SYSFS)
+ /*
+ * The sink is still enabled in sysfs mode. Keep
+ * the live buffer owned by drvdata->sysfs_buf
+ * and only drop the list's reference to it.
+ */
+ nd->sysfs_buf = NULL;
+ else
+ /* Free the buffer below through nd->sysfs_buf */
+ drvdata->sysfs_buf = NULL;
+ }
+ list_del(&nd->link);
+ raw_spin_unlock_irqrestore(&drvdata->spinlock, flags);
+
+ /* Free the buffer (NULL is ignored) and the node out of the lock */
+ tmc_etr_free_sysfs_buf(nd->sysfs_buf);
+ kfree(nd);
+ }
+}
+EXPORT_SYMBOL_GPL(tmc_clean_etr_buf_list);
+
+/**
+ * tmc_create_etr_buf_list - create a list to manage the etr_buf_node.
+ * @drvdata: driver data of the TMC device.
+ * @num_nodes: number of nodes want to create with the list.
+ *
+ * Locking: must not be called with @drvdata->spinlock held. The spinlock is
+ * taken internally to serialise the @drvdata->sysfs_buf accesses and every
+ * update of @drvdata->etr_buf_list against the ETR sink enable/disable paths
+ * and the generic sysfs read. There must be only one owner of the list at a
+ * time. Must be called from process context: buffers and nodes are allocated
+ * with the lock released.
+ *
+ * Return: 0 upon success, else the error number: -EBUSY if a generic sysfs
+ * read is in progress, -EINVAL if the sink is not enabled in sysfs mode or
+ * the ETR is in the reserved buffer mode, or the error of the allocation.
+ */
+int tmc_create_etr_buf_list(struct tmc_drvdata *drvdata, int num_nodes)
+{
+ struct etr_buf_node *new_node;
+ struct etr_buf *sysfs_buf;
+ unsigned long flags;
+ int i = 0, ret = 0;
+
+ /* We don't need a list if there is only one node */
+ if (num_nodes < 2)
+ return -EINVAL;
+
+ /*
+ * All the etr_buf of the reserved mode are mapped to the same reserved
+ * region, so they can't be used as separate buffers of the list.
+ */
+ if (drvdata->etr_mode == ETR_MODE_RESRV)
+ return -EINVAL;
+
+ /*
+ * We expect that sysfs_buf in drvdata has already been allocated.
+ * Wrap the live sysfs_buf into the first node so the captured trace
+ * data is preserved. Take the sysfs_buf and add the node under the
+ * lock, so that the ETR sink enable path sees a non-empty list and
+ * doesn't replace the buffer.
+ */
+ new_node = kzalloc_obj(*new_node, GFP_KERNEL);
+ if (!new_node)
+ return -ENOMEM;
+
+ raw_spin_lock_irqsave(&drvdata->spinlock, flags);
+ /* The sysfs_buf is in use by a sysfs read session */
+ if (drvdata->reading) {
+ raw_spin_unlock_irqrestore(&drvdata->spinlock, flags);
+ kfree(new_node);
+ return -EBUSY;
+ }
+
+ /*
+ * The sink enable path reuses or frees the sysfs_buf of a sink which
+ * isn't enabled, so only wrap it while the sink is enabled in sysfs mode.
+ */
+ if (coresight_get_mode(drvdata->csdev) != CS_MODE_SYSFS) {
+ raw_spin_unlock_irqrestore(&drvdata->spinlock, flags);
+ kfree(new_node);
+ return -EINVAL;
+ }
+
+ if (drvdata->sysfs_buf) {
+ new_node->sysfs_buf = drvdata->sysfs_buf;
+ new_node->is_free = false;
+ list_add(&new_node->link, &drvdata->etr_buf_list);
+ new_node = NULL;
+ i++;
+ }
+ raw_spin_unlock_irqrestore(&drvdata->spinlock, flags);
+ /* Free the node if there is no live sysfs_buf to wrap */
+ kfree(new_node);
+
+ while (i < num_nodes) {
+ new_node = kzalloc_obj(*new_node, GFP_KERNEL);
+ if (!new_node) {
+ ret = -ENOMEM;
+ break;
+ }
+
+ /* Allocate the buffer with the lock released */
+ sysfs_buf = tmc_alloc_etr_buf(drvdata, drvdata->size, 0, cpu_to_node(0), NULL);
+ if (IS_ERR(sysfs_buf)) {
+ kfree(new_node);
+ ret = PTR_ERR(sysfs_buf);
+ break;
+ }
+
+ new_node->sysfs_buf = sysfs_buf;
+ /*
+ * The drvdata->sysfs_buf write and the list update need the
+ * spinlock, to serialise against the ETR sink enable/disable
+ * paths.
+ */
+ raw_spin_lock_irqsave(&drvdata->spinlock, flags);
+ /* We don't have an available sysfs_buf in drvdata, set one up */
+ if (!drvdata->sysfs_buf) {
+ drvdata->sysfs_buf = sysfs_buf;
+ new_node->is_free = false;
+ } else {
+ new_node->is_free = true;
+ }
+ list_add_tail(&new_node->link, &drvdata->etr_buf_list);
+ raw_spin_unlock_irqrestore(&drvdata->spinlock, flags);
+ i++;
+ }
+
+ /* Clean the list if there is an error */
+ if (ret)
+ tmc_clean_etr_buf_list(drvdata);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(tmc_create_etr_buf_list);
+
int tmc_read_prepare_etr(struct tmc_drvdata *drvdata)
{
int ret = 0;
@@ -1928,7 +2091,8 @@ int tmc_read_prepare_etr(struct tmc_drvdata *drvdata)
return -EINVAL;
raw_spin_lock_irqsave(&drvdata->spinlock, flags);
- if (drvdata->reading) {
+ /* The sysfs_buf is owned by the etr_buf_list */
+ if (drvdata->reading || !list_empty(&drvdata->etr_buf_list)) {
ret = -EBUSY;
goto out;
}
diff --git a/drivers/hwtracing/coresight/coresight-tmc.h b/drivers/hwtracing/coresight/coresight-tmc.h
index 319a354ede9f..7c604b9f92e5 100644
--- a/drivers/hwtracing/coresight/coresight-tmc.h
+++ b/drivers/hwtracing/coresight/coresight-tmc.h
@@ -208,6 +208,20 @@ struct tmc_resrv_buf {
s64 len;
};
+/**
+ * struct etr_buf_node - node of the etr_buf_list
+ * @sysfs_buf: Allocated sysfs_buf.
+ * @is_free: Indicates whether the buffer is free to choose.
+ * @pos: Offset to the start of the buffer.
+ * @link: list_head of the node.
+ */
+struct etr_buf_node {
+ struct etr_buf *sysfs_buf;
+ bool is_free;
+ loff_t pos;
+ struct list_head link;
+};
+
/**
* struct tmc_drvdata - specifics associated to an TMC component
* @atclk: optional clock for the core parts of the TMC.
@@ -245,6 +259,7 @@ struct tmc_resrv_buf {
* (after crash) by default.
* @crash_mdata: Reserved memory for storing tmc crash metadata.
* Used by ETR/ETF.
+ * @etr_buf_list: List that is used to manage allocated etr_buf.
*/
struct tmc_drvdata {
struct clk *atclk;
@@ -275,6 +290,7 @@ struct tmc_drvdata {
struct etr_buf *perf_buf;
struct tmc_resrv_buf resrv_buf;
struct tmc_resrv_buf crash_mdata;
+ struct list_head etr_buf_list;
};
struct etr_buf_operations {
@@ -447,5 +463,7 @@ struct etr_buf *tmc_etr_get_buffer(struct coresight_device *csdev,
enum cs_mode mode,
struct coresight_path *path);
extern const struct attribute_group coresight_etr_group;
+void tmc_clean_etr_buf_list(struct tmc_drvdata *drvdata);
+int tmc_create_etr_buf_list(struct tmc_drvdata *drvdata, int num_nodes);
#endif
--
2.34.1
More information about the linux-arm-kernel
mailing list