mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [RFC PATCH 00/24] Add Linux RISC-V trace support via the gtrace framework
@ 2026-10-23  3:14 Zane Leung
  2026-10-23  3:14 ` [PATCH 01/24] gtrace: Move connection release into component unregister Zane Leung
                   ` (23 more replies)
  0 siblings, 24 replies; 25+ messages in thread
From: Zane Leung @ 2026-10-23  3:14 UTC (permalink / raw)
  To: gregkh, palmer, pjw, alexander.shishkin, suzuki.poulose,
	mchitale, anup, irogers
  Cc: mike.leach, james.clark, leo.yan, peterz, mingo, namhyung,
	mark.rutland, jolsa, adrian.hunter, alex, atish.patra,
	andrew.jones, sunilvl, liangzhen, zhuangqiubin, coresight,
	linux-riscv, linux-kernel

This series adds Linux RISC-V trace support via the generic hardware trace
(gtrace) framework [1].  The RISC-V trace encoder, funnel, ATB bridge, and
timestamp components are implemented as gtrace components, and the CoreSight
funnel and TMC (ETB/ETF/ETR) are likewise re-implemented as gtrace components
so that they can be composed with the RISC-V components into a single trace path.

This work supersedes the earlier CoreSight-based RFC [2]. Instead of forcing
the RISC-V trace IP to conform to the CoreSight device, this series shifts
the topology onto the gtrace framework to achieve cleaner architectural
decoupling and shared data paths.

The series includes:

- gtrace sysfs support, exposing per-component attributes (control,
  features, and timestamp) for configuration and validation
- gtrace component drivers for the RISC-V trace encoder, funnel, ATB
  bridge, and timestamp components
- gtrace component drivers for the CoreSight dynamic funnel and TMC
  (ETB/ETF/ETR), built on top of the CoreSight platform core APIs
  (CLAIM/device registration) re-implemented as gtrace components
- perf PMU support: gtrace event format attributes, per-event configuration
  parsing, and an AUX flag marking CoreSight-formatted trace data
- perf tooling: a Nexus (N-Trace) decoder and a gtrace AUX decoder that
  synthesizes branch samples, including machine-mode instruction access

Verification on K3 SoC:
The device tree follows [1], with only the compatible properties changed for
the gtrace components.

# perf list pmu
  gtrace//                                          [Kernel PMU event]

# perf record -e gtrace// --per-thread uname
[ perf record: Woken up 1 times to write data ]
[ perf record: Captured and wrote 1.946 MB perf.data ]

# perf script
uname  767863          1                          branches:  ffffffff80d18b04 gtrace_poll_bit+0x34 ([kernel.kallsyms]) => ffffffff80d18b12 gtrace_poll_bit+0x42 ([kernel.kallsyms])
uname  767863          1                          branches:  ffffffff80d18b4e gtrace_poll_bit+0x7e ([kernel.kallsyms]) => ffffffff80d18b6c gtrace_poll_bit+0x9c ([kernel.kallsyms])
uname  767863          1                          branches:  ffffffff80d1c17a rvtrace_encoder_start+0x112 ([kernel.kallsyms]) => ffffffff80d1c196 rvtrace_encoder_start+0x12e ([kernel.kallsyms])
uname  767863          1                          branches:  ffffffff80d18782 gtrace_path_start+0xbe ([kernel.kallsyms]) => ffffffff80d18702 gtrace_path_start+0x3e ([kernel.kallsyms])
uname  767863          1                          branches:  ffffffff80d18712 gtrace_path_start+0x4e ([kernel.kallsyms]) => ffffffff80d18724 gtrace_path_start+0x60 ([kernel.kallsyms])
...

In per-cpu mode, correlating trace data with the running task requires
the encoder to emit a context ID.  This depends on writing the task PID
to the RISC-V scontext CSR on context switch [3].

[1] https://lore.kernel.org/all/20260810152223.3946743-1-mayuresh.chitale@oss.qualcomm.com/
[2] https://lore.kernel.org/all/20260414034153.3272485-1-liangzhen@linux.spacemit.com/
[3] https://lore.kernel.org/all/20240329-dev-maxh-lin-452-6-9-v1-0-1534f93b94a7@sifive.com/

Zane Leung (24):
  gtrace: Move connection release into component unregister
  gtrace: Initial implementation of gtrace sysfs framework
  gtrace: Drop trace format field and helper function
  gtrace: Add RISC-V trace encoder sysfs support
  gtrace: Introduce some PMU format attributes
  gtrace: Replace owner PID with perf_event pointer in gtrace_path
  gtrace: Parse RISC-V Trace encoder config from perf event
  gtrace: add perf symlink support for gtrace components
  gtrace: Add port-level start/stop driver callbacks
  gtrace: Add RISC-V trace funnel driver
  gtrace: Add RISC-V trace timestamp support
  gtrace: Add RISC-V trace ATB bridge driver
  gtrace: Add CoreSight platform core APIs
  gtrace: Add CoreSight funnel driver
  gtrace: Only copy AUX data from the path sink
  perf: Flag whether gtrace AUX data carries CoreSight framing
  gtrace: Unify default sink selection across CPUs in perf setup
  gtrace: Add sink-specific buffer alloc/free callbacks
  gtrace: Add setup_sysfs_buf callback to setup sysfs sink buffer
  gtrace: Add CoreSight TMC driver support
  perf tools: riscv: making gtrace PMU listable
  perf tools: Add Nexus RISC-V Trace decoder
  perf tools: gtrace: Record and parse trace encoder metadata
  perf tools: gtrace: Add AUX trace decoder and branch sample synthesis

 .../hwtracing/coresight/coresight-tmc-etf.c   |    1 -
 drivers/hwtracing/gtrace/Kconfig              |   70 +
 drivers/hwtracing/gtrace/Makefile             |   12 +-
 drivers/hwtracing/gtrace/coresight-funnel.c   |  430 ++++
 drivers/hwtracing/gtrace/coresight-platform.c |  279 +++
 drivers/hwtracing/gtrace/coresight-tmc-core.c | 1111 ++++++++++
 drivers/hwtracing/gtrace/coresight-tmc-etf.c  |  573 +++++
 drivers/hwtracing/gtrace/coresight-tmc-etr.c  | 1888 +++++++++++++++++
 drivers/hwtracing/gtrace/coresight-tmc.h      |  469 ++++
 drivers/hwtracing/gtrace/coresight.h          |  212 ++
 drivers/hwtracing/gtrace/gtrace-core.c        |  165 +-
 drivers/hwtracing/gtrace/gtrace-perf.c        |  122 +-
 drivers/hwtracing/gtrace/gtrace-sysfs.c       |  452 ++++
 drivers/hwtracing/gtrace/rvtrace-atbbridge.c  |  153 ++
 ...trace-encoder.c => rvtrace-encoder-core.c} |  182 +-
 .../hwtracing/gtrace/rvtrace-encoder-sysfs.c  |  323 +++
 drivers/hwtracing/gtrace/rvtrace-encoder.h    |   61 +
 drivers/hwtracing/gtrace/rvtrace-funnel.c     |  220 ++
 drivers/hwtracing/gtrace/rvtrace-funnel.h     |   29 +
 drivers/hwtracing/gtrace/rvtrace-platform.c   |   26 +-
 drivers/hwtracing/gtrace/rvtrace-ramsink.c    |   11 +-
 drivers/hwtracing/gtrace/rvtrace-timestamp.c  |  336 +++
 drivers/hwtracing/gtrace/rvtrace-timestamp.h  |   44 +
 drivers/hwtracing/gtrace/rvtrace.h            |    5 +
 include/linux/gtrace.h                        |  161 +-
 include/uapi/linux/perf_event.h               |    1 +
 tools/arch/riscv/include/asm/insn.h           |  645 ++++++
 tools/include/uapi/linux/perf_event.h         |    1 +
 tools/perf/arch/riscv/util/Build              |    1 +
 tools/perf/arch/riscv/util/auxtrace.c         |  203 +-
 tools/perf/arch/riscv/util/pmu.c              |   19 +
 tools/perf/builtin-script.c                   |    4 +
 tools/perf/util/Build                         |    1 +
 tools/perf/util/gtrace-decoder.c              | 1283 ++++++++++-
 tools/perf/util/gtrace.h                      |   30 +-
 tools/perf/util/rvtrace-decoder/Build         |    2 +
 .../util/rvtrace-decoder/rvtrace-decoder.c    |  423 ++++
 .../util/rvtrace-decoder/rvtrace-decoder.h    |  122 ++
 .../rvtrace-decoder/rvtrace-ntrace-decoder.c  |  910 ++++++++
 .../rvtrace-decoder/rvtrace-ntrace-decoder.h  |  124 ++
 tools/perf/util/symbol_conf.h                 |    2 +
 41 files changed, 10926 insertions(+), 180 deletions(-)
 create mode 100644 drivers/hwtracing/gtrace/coresight-funnel.c
 create mode 100644 drivers/hwtracing/gtrace/coresight-platform.c
 create mode 100644 drivers/hwtracing/gtrace/coresight-tmc-core.c
 create mode 100644 drivers/hwtracing/gtrace/coresight-tmc-etf.c
 create mode 100644 drivers/hwtracing/gtrace/coresight-tmc-etr.c
 create mode 100644 drivers/hwtracing/gtrace/coresight-tmc.h
 create mode 100644 drivers/hwtracing/gtrace/coresight.h
 create mode 100644 drivers/hwtracing/gtrace/gtrace-sysfs.c
 create mode 100644 drivers/hwtracing/gtrace/rvtrace-atbbridge.c
 rename drivers/hwtracing/gtrace/{rvtrace-encoder.c => rvtrace-encoder-core.c} (55%)
 create mode 100644 drivers/hwtracing/gtrace/rvtrace-encoder-sysfs.c
 create mode 100644 drivers/hwtracing/gtrace/rvtrace-encoder.h
 create mode 100644 drivers/hwtracing/gtrace/rvtrace-funnel.c
 create mode 100644 drivers/hwtracing/gtrace/rvtrace-funnel.h
 create mode 100644 drivers/hwtracing/gtrace/rvtrace-timestamp.c
 create mode 100644 drivers/hwtracing/gtrace/rvtrace-timestamp.h
 create mode 100644 tools/arch/riscv/include/asm/insn.h
 create mode 100644 tools/perf/arch/riscv/util/pmu.c
 create mode 100644 tools/perf/util/rvtrace-decoder/Build
 create mode 100644 tools/perf/util/rvtrace-decoder/rvtrace-decoder.c
 create mode 100644 tools/perf/util/rvtrace-decoder/rvtrace-decoder.h
 create mode 100644 tools/perf/util/rvtrace-decoder/rvtrace-ntrace-decoder.c
 create mode 100644 tools/perf/util/rvtrace-decoder/rvtrace-ntrace-decoder.h

-- 
2.34.1


^ permalink raw reply	[flat|nested] 25+ messages in thread

* [PATCH 01/24] gtrace: Move connection release into component unregister
  2026-10-23  3:14 [RFC PATCH 00/24] Add Linux RISC-V trace support via the gtrace framework Zane Leung
@ 2026-10-23  3:14 ` Zane Leung
  2026-10-23  3:14 ` [PATCH 02/24] gtrace: Initial implementation of gtrace sysfs framework Zane Leung
                   ` (22 subsequent siblings)
  23 siblings, 0 replies; 25+ messages in thread
From: Zane Leung @ 2026-10-23  3:14 UTC (permalink / raw)
  To: gregkh, palmer, pjw, alexander.shishkin, suzuki.poulose,
	mchitale, anup, irogers
  Cc: mike.leach, james.clark, leo.yan, peterz, mingo, namhyung,
	mark.rutland, jolsa, adrian.hunter, alex, atish.patra,
	andrew.jones, sunilvl, liangzhen, zhuangqiubin, coresight,
	linux-riscv, linux-kernel

Add gtrace_release_outconns() and call it from
gtrace_unregister_component() so the output connection fwnode
references are released in the core instead of being duplicated
in every platform driver's remove callback.

Signed-off-by: Zane Leung <liangzhen@linux.spacemit.com>
---
 drivers/hwtracing/gtrace/gtrace-core.c      | 15 +++++++++++++++
 drivers/hwtracing/gtrace/rvtrace-platform.c |  9 ---------
 2 files changed, 15 insertions(+), 9 deletions(-)

diff --git a/drivers/hwtracing/gtrace/gtrace-core.c b/drivers/hwtracing/gtrace/gtrace-core.c
index 034987237abc..5704e426abe8 100644
--- a/drivers/hwtracing/gtrace/gtrace-core.c
+++ b/drivers/hwtracing/gtrace/gtrace-core.c
@@ -479,10 +479,25 @@ struct gtrace_component *gtrace_register_component(struct gtrace_component_id *i
 }
 EXPORT_SYMBOL_GPL(gtrace_register_component);
 
+static void gtrace_release_outconns(struct gtrace_component *comp)
+{
+	struct gtrace_platform_data *pdata = comp->pdata;
+	struct gtrace_connection *conn;
+	int i;
+
+	for (i = 0; i < pdata->nr_outconns; i++) {
+		conn = pdata->outconns[i];
+		if (conn && conn->dest_fwnode)
+			fwnode_handle_put(conn->dest_fwnode);
+	}
+}
+
 void gtrace_unregister_component(struct gtrace_component *comp)
 {
 	struct gtrace_component *c;
 
+	gtrace_release_outconns(comp);
+
 	mutex_lock(&gtrace_mutex);
 
 	if (comp->pdata->bound_cpu >= 0) {
diff --git a/drivers/hwtracing/gtrace/rvtrace-platform.c b/drivers/hwtracing/gtrace/rvtrace-platform.c
index a5655e983e51..189ff0b8bc18 100644
--- a/drivers/hwtracing/gtrace/rvtrace-platform.c
+++ b/drivers/hwtracing/gtrace/rvtrace-platform.c
@@ -141,15 +141,6 @@ static int rvtrace_platform_probe(struct platform_device *pdev)
 static void rvtrace_platform_remove(struct platform_device *pdev)
 {
 	struct gtrace_component *comp = platform_get_drvdata(pdev);
-	struct gtrace_platform_data *pdata = comp->pdata;
-	struct gtrace_connection *conn;
-	int i;
-
-	for (i = 0; i < pdata->nr_outconns; i++) {
-		conn = pdata->outconns[i];
-		if (conn && conn->dest_fwnode)
-			fwnode_handle_put(conn->dest_fwnode);
-	}
 
 	gtrace_unregister_component(comp);
 }
-- 
2.34.1


^ permalink raw reply	[flat|nested] 25+ messages in thread

* [PATCH 02/24] gtrace: Initial implementation of gtrace sysfs framework
  2026-10-23  3:14 [RFC PATCH 00/24] Add Linux RISC-V trace support via the gtrace framework Zane Leung
  2026-10-23  3:14 ` [PATCH 01/24] gtrace: Move connection release into component unregister Zane Leung
@ 2026-10-23  3:14 ` Zane Leung
  2026-10-23  3:14 ` [PATCH 03/24] gtrace: Drop trace format field and helper function Zane Leung
                   ` (21 subsequent siblings)
  23 siblings, 0 replies; 25+ messages in thread
From: Zane Leung @ 2026-10-23  3:14 UTC (permalink / raw)
  To: gregkh, palmer, pjw, alexander.shishkin, suzuki.poulose,
	mchitale, anup, irogers
  Cc: mike.leach, james.clark, leo.yan, peterz, mingo, namhyung,
	mark.rutland, jolsa, adrian.hunter, alex, atish.patra,
	andrew.jones, sunilvl, liangzhen, zhuangqiubin, coresight,
	linux-riscv, linux-kernel

Add per-component sysfs attribute groups (enable_sink, enable_source,
label) and a connections/ group with nr_links and bidirectional out:X /
in:Y symlinks. Drivers can attach extra groups via pdata->groups; they
are registered and cleaned up inside gtrace_sysfs_add/remove.

Signed-off-by: Zane Leung <liangzhen@linux.spacemit.com>
---
 drivers/hwtracing/gtrace/Makefile       |   2 +-
 drivers/hwtracing/gtrace/gtrace-core.c  |  47 ++-
 drivers/hwtracing/gtrace/gtrace-sysfs.c | 440 ++++++++++++++++++++++++
 include/linux/gtrace.h                  | 104 ++++++
 4 files changed, 583 insertions(+), 10 deletions(-)
 create mode 100644 drivers/hwtracing/gtrace/gtrace-sysfs.c

diff --git a/drivers/hwtracing/gtrace/Makefile b/drivers/hwtracing/gtrace/Makefile
index 33274a1ed0b8..ff79a49bdb5e 100644
--- a/drivers/hwtracing/gtrace/Makefile
+++ b/drivers/hwtracing/gtrace/Makefile
@@ -1,7 +1,7 @@
 # SPDX-License-Identifier: GPL-2.0
 
 obj-$(CONFIG_GTRACE) += gtrace.o
-gtrace-y := gtrace-core.o gtrace-perf.o
+gtrace-y := gtrace-core.o gtrace-perf.o gtrace-sysfs.o
 gtrace-$(CONFIG_OF) += gtrace-of.o
 
 obj-$(CONFIG_RVTRACE) += rvtrace-platform.o
diff --git a/drivers/hwtracing/gtrace/gtrace-core.c b/drivers/hwtracing/gtrace/gtrace-core.c
index 5704e426abe8..cae0a7fa820f 100644
--- a/drivers/hwtracing/gtrace/gtrace-core.c
+++ b/drivers/hwtracing/gtrace/gtrace-core.c
@@ -17,7 +17,7 @@
 #define GTRACE_POLL_TIMEOUT_US_DEFAULT	10
 
 /* Mutex to serialize component registration/unregistration */
-static DEFINE_MUTEX(gtrace_mutex);
+DEFINE_MUTEX(gtrace_mutex);
 
 /* Per-CPU source instances */
 static DEFINE_PER_CPU(struct gtrace_component *, gtrace_cpu_source_comp);
@@ -36,11 +36,20 @@ struct gtrace_comp_priv {
 	pid_t owner;
 	bool ready;
 	bool visited;
+	enum gtrace_component_mode mode;
 };
 
 #define to_gtrace_comp_priv(__comp)	\
 	container_of_const(__comp, struct gtrace_comp_priv, comp)
 
+enum gtrace_component_mode gtrace_get_mode(struct gtrace_component *comp)
+{
+	struct gtrace_comp_priv *cpriv = to_gtrace_comp_priv(comp);
+
+	return cpriv->mode;
+}
+EXPORT_SYMBOL_GPL(gtrace_get_mode);
+
 /* Component type based id generator */
 struct gtrace_type_idx {
 	/* Lock to protect the type ID generator */
@@ -463,6 +472,12 @@ struct gtrace_component *gtrace_register_component(struct gtrace_component_id *i
 		goto err_out_unlock;
 	}
 
+	ret = gtrace_sysfs_add(comp);
+	if (ret < 0) {
+		device_unregister(&comp->dev);
+		goto err_out_unlock;
+	}
+
 	if (comp->pdata->bound_cpu >= 0) {
 		gtrace_get_component(comp);
 		per_cpu(gtrace_cpu_source_comp, comp->pdata->bound_cpu) = comp;
@@ -507,6 +522,7 @@ void gtrace_unregister_component(struct gtrace_component *comp)
 	}
 
 	gtrace_cleanup_inconns_from_outconns(comp);
+	gtrace_sysfs_remove(comp);
 	device_unregister(&comp->dev);
 
 	mutex_unlock(&gtrace_mutex);
@@ -636,7 +652,8 @@ static void gtrace_release_path_nodes(struct gtrace_path *path)
 	}
 }
 
-static int __gtrace_comp_start(struct gtrace_component *comp, pid_t owner)
+static int __gtrace_comp_start(struct gtrace_component *comp,
+			       struct gtrace_path *path)
 {
 	struct gtrace_comp_priv *cpriv = to_gtrace_comp_priv(comp);
 	const struct gtrace_driver *gtdrv = to_gtrace_driver(comp->dev.driver);
@@ -648,17 +665,21 @@ static int __gtrace_comp_start(struct gtrace_component *comp, pid_t owner)
 
 	raw_spin_lock_irqsave(&cpriv->lock, flags);
 	if (cpriv->start_count) {
-		if (cpriv->owner != owner) {
+		if (cpriv->owner != path->owner || cpriv->mode != path->mode) {
 			ret = -EBUSY;
 			goto out;
 		}
 	} else {
 		if (gtdrv->start) {
+			comp->path = path;
 			ret = gtdrv->start(comp);
-			if (ret)
+			if (ret) {
+				comp->path = NULL;
 				goto out;
+			}
 		}
-		cpriv->owner = owner;
+		cpriv->owner = path->owner;
+		cpriv->mode = path->mode;
 	}
 
 	cpriv->start_count++;
@@ -667,7 +688,8 @@ static int __gtrace_comp_start(struct gtrace_component *comp, pid_t owner)
 	return ret;
 }
 
-static int __gtrace_comp_stop(struct gtrace_component *comp)
+static int __gtrace_comp_stop(struct gtrace_component *comp,
+			      struct gtrace_path *path)
 {
 	struct gtrace_comp_priv *cpriv = to_gtrace_comp_priv(comp);
 	const struct gtrace_driver *gtdrv = to_gtrace_driver(comp->dev.driver);
@@ -680,6 +702,11 @@ static int __gtrace_comp_stop(struct gtrace_component *comp)
 		goto out;
 	}
 
+	if (cpriv->mode != path->mode) {
+		ret = -EBUSY;
+		goto out;
+	}
+
 	cpriv->start_count--;
 	if (!cpriv->start_count) {
 		if (!gtdrv) {
@@ -689,6 +716,8 @@ static int __gtrace_comp_stop(struct gtrace_component *comp)
 
 		if (gtdrv->stop)
 			ret = gtdrv->stop(comp);
+		comp->path = NULL;
+		cpriv->mode = GTRACE_COMPONENT_MODE_DISABLED;
 	}
 out:
 	raw_spin_unlock_irqrestore(&cpriv->lock, flags);
@@ -701,7 +730,7 @@ int gtrace_path_start(struct gtrace_path *path)
 	int ret = 0;
 
 	list_for_each_entry_reverse(node, &path->comp_list, head) {
-		ret = __gtrace_comp_start(node->comp, path->owner);
+		ret = __gtrace_comp_start(node->comp, path);
 		if (ret)
 			break;
 	}
@@ -709,7 +738,7 @@ int gtrace_path_start(struct gtrace_path *path)
 	/* If a component failed to start, stop all the components that were started before it */
 	if (ret)
 		list_for_each_entry_continue(node, &path->comp_list, head)
-			__gtrace_comp_stop(node->comp);
+			__gtrace_comp_stop(node->comp, path);
 
 	return ret;
 }
@@ -721,7 +750,7 @@ int gtrace_path_stop(struct gtrace_path *path)
 	int ret = 0, err;
 
 	list_for_each_entry(node, &path->comp_list, head) {
-		err = __gtrace_comp_stop(node->comp);
+		err = __gtrace_comp_stop(node->comp, path);
 		if (err && !ret)
 			ret = err;
 	}
diff --git a/drivers/hwtracing/gtrace/gtrace-sysfs.c b/drivers/hwtracing/gtrace/gtrace-sysfs.c
new file mode 100644
index 000000000000..5f4f3fcb56a4
--- /dev/null
+++ b/drivers/hwtracing/gtrace/gtrace-sysfs.c
@@ -0,0 +1,440 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2026 Spacemit Technologies, Inc.
+ *
+ * Based on drivers/hwtracing/coresight/coresight-sysfs.c
+ */
+
+#include <linux/device.h>
+#include <linux/gtrace.h>
+#include <linux/property.h>
+
+ssize_t gtrace_simple_show_pair(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct gtrace_component *comp = to_gtrace_component(dev);
+	struct gtrace_pair_attribute *gtrace_attr =
+		container_of(attr, struct gtrace_pair_attribute, attr);
+	u64 val;
+
+	val = gtrace_relaxed_read_pair(comp->pdata, gtrace_attr->lo_off, gtrace_attr->hi_off);
+	return sysfs_emit(buf, "0x%llx\n", val);
+}
+EXPORT_SYMBOL_GPL(gtrace_simple_show_pair);
+
+ssize_t gtrace_simple_show32(struct device *dev,
+			     struct device_attribute *attr, char *buf)
+{
+	struct gtrace_component *comp = to_gtrace_component(dev);
+	struct gtrace_off_attribute *gtrace_attr =
+		container_of(attr, struct gtrace_off_attribute, attr);
+	u32 val;
+
+	val = gtrace_relaxed_read32(comp->pdata, gtrace_attr->off);
+	return sysfs_emit(buf, "0x%x\n", val);
+}
+EXPORT_SYMBOL_GPL(gtrace_simple_show32);
+
+/*
+ * gtrace_find_activated_sysfs_sink - find the first sink activated via
+ * walks outconns forward from @comp.
+ */
+static struct gtrace_component *
+gtrace_find_activated_sysfs_sink(struct gtrace_component *comp)
+{
+	int i;
+	struct gtrace_component *sink = NULL;
+	struct gtrace_platform_data *pdata = comp->pdata;
+
+	if (comp->sysfs_sink_activated)
+		return comp;
+
+	for (i = 0; i < pdata->nr_outconns; i++) {
+		struct gtrace_component *dest = pdata->outconns[i]->dest_comp;
+
+		if (dest)
+			sink = gtrace_find_activated_sysfs_sink(dest);
+		if (sink)
+			return sink;
+	}
+
+	return NULL;
+}
+
+/** gtrace_validate_source_sysfs - make sure a source has the right credentials to
+ *  be used via sysfs.
+ *  @comp:	the component structure for a source.
+ *  @function:	the function this was called from.
+ *
+ * Assumes the gtrace_mutex is held.
+ */
+static int gtrace_validate_source_sysfs(struct gtrace_component *comp,
+					const char *function)
+{
+	if (!gtrace_is_source(comp->pdata)) {
+		dev_err(&comp->dev, "component is not a trace source in %s\n", function);
+		return -EINVAL;
+	}
+
+	if (!cpu_online(comp->pdata->bound_cpu))
+		return -ENODEV;
+
+	return 0;
+}
+
+int gtrace_enable_sysfs(struct gtrace_component *comp)
+{
+	struct gtrace_component *sink;
+	struct gtrace_path *path;
+	int ret;
+
+	mutex_lock(&gtrace_mutex);
+	ret = gtrace_validate_source_sysfs(comp, __func__);
+	if (ret)
+		goto out;
+
+	/* mode == SYSFS implies that it's already enabled. */
+	if (gtrace_get_mode(comp) == GTRACE_COMPONENT_MODE_SYSFS)
+		goto out;
+
+	sink = gtrace_find_activated_sysfs_sink(comp);
+
+	/*
+	 * gtrace_create_path() acquires gtrace_mutex internally via
+	 * gtrace_walk_output_components(). Release it here first to
+	 * avoid a deadlock.
+	 */
+	mutex_unlock(&gtrace_mutex);
+
+	path = gtrace_create_path(comp, sink, GTRACE_COMPONENT_MODE_SYSFS);
+	if (IS_ERR(path)) {
+		pr_err("building path(s) failed\n");
+		return PTR_ERR(path);
+	}
+
+	mutex_lock(&gtrace_mutex);
+
+	ret = gtrace_path_start(path);
+	if (ret)
+		gtrace_destroy_path(path);
+
+out:
+	mutex_unlock(&gtrace_mutex);
+	return ret;
+}
+EXPORT_SYMBOL_GPL(gtrace_enable_sysfs);
+
+int gtrace_disable_sysfs(struct gtrace_component *comp)
+{
+	struct gtrace_path *path;
+	int ret;
+
+	mutex_lock(&gtrace_mutex);
+
+	ret = gtrace_validate_source_sysfs(comp, __func__);
+	if (ret)
+		goto out;
+
+	path = comp->path;
+
+	if (gtrace_get_mode(comp) != GTRACE_COMPONENT_MODE_SYSFS)
+		goto out;
+
+	ret = gtrace_path_stop(path);
+	if (ret)
+		pr_err("Error. Tracing not stopped\n");
+
+	gtrace_destroy_path(path);
+out:
+	mutex_unlock(&gtrace_mutex);
+	return ret;
+}
+EXPORT_SYMBOL_GPL(gtrace_disable_sysfs);
+
+static ssize_t enable_sink_show(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct gtrace_component *comp = to_gtrace_component(dev);
+
+	guard(mutex)(&gtrace_mutex);
+
+	return sysfs_emit(buf, "%u\n", comp->sysfs_sink_activated);
+}
+
+static ssize_t enable_sink_store(struct device *dev,
+				 struct device_attribute *attr,
+				 const char *buf, size_t size)
+{
+	int ret;
+	unsigned long val;
+	struct gtrace_component *comp = to_gtrace_component(dev);
+
+	ret = kstrtoul(buf, 10, &val);
+	if (ret)
+		return ret;
+
+	comp->sysfs_sink_activated = !!val;
+
+	return size;
+}
+static DEVICE_ATTR_RW(enable_sink);
+
+static ssize_t enable_source_show(struct device *dev,
+				  struct device_attribute *attr, char *buf)
+{
+	struct gtrace_component *comp = to_gtrace_component(dev);
+
+	guard(mutex)(&gtrace_mutex);
+	return sysfs_emit(buf, "%u\n",
+			  gtrace_get_mode(comp) == GTRACE_COMPONENT_MODE_SYSFS);
+}
+
+static ssize_t enable_source_store(struct device *dev,
+				   struct device_attribute *attr,
+				   const char *buf, size_t size)
+{
+	int ret = 0;
+	unsigned long val;
+	struct gtrace_component *comp = to_gtrace_component(dev);
+
+	ret = kstrtoul(buf, 10, &val);
+	if (ret)
+		return ret;
+
+
+	/*
+	 * gtrace hotplug callbacks in core layer control a activated path
+	 * from its source to sink. Taking hotplug lock here protects a race
+	 * condition with hotplug callbacks.
+	 */
+	guard(cpus_read_lock)();
+
+	if (val) {
+		ret = gtrace_enable_sysfs(comp);
+		if (ret)
+			return ret;
+	} else {
+		ret = gtrace_disable_sysfs(comp);
+		if (ret)
+			return ret;
+	}
+
+	return size;
+}
+static DEVICE_ATTR_RW(enable_source);
+
+static ssize_t label_show(struct device *dev,
+			  struct device_attribute *attr, char *buf)
+{
+	const char *str;
+	int ret;
+
+	ret = fwnode_property_read_string(dev_fwnode(dev), "label", &str);
+	if (ret)
+		return ret;
+
+	return sysfs_emit(buf, "%s\n", str);
+}
+static DEVICE_ATTR_RO(label);
+
+static umode_t label_is_visible(struct kobject *kobj,
+				struct attribute *attr, int n)
+{
+	struct device *dev = kobj_to_dev(kobj);
+
+	if (attr == &dev_attr_label.attr) {
+		if (fwnode_property_present(dev_fwnode(dev), "label"))
+			return attr->mode;
+		else
+			return 0;
+	}
+
+	return attr->mode;
+}
+
+static struct attribute *gtrace_sink_attrs[] = {
+	&dev_attr_enable_sink.attr,
+	&dev_attr_label.attr,
+	NULL,
+};
+
+static const struct attribute_group gtrace_sink_group = {
+	.attrs = gtrace_sink_attrs,
+	.is_visible = label_is_visible,
+};
+
+
+static struct attribute *gtrace_source_attrs[] = {
+	&dev_attr_enable_source.attr,
+	&dev_attr_label.attr,
+	NULL,
+};
+
+static const struct attribute_group gtrace_source_group = {
+	.attrs = gtrace_source_attrs,
+	.is_visible = label_is_visible,
+};
+
+static struct attribute *gtrace_link_attrs[] = {
+	&dev_attr_label.attr,
+	NULL,
+};
+
+static const struct attribute_group gtrace_link_group = {
+	.attrs = gtrace_link_attrs,
+	.is_visible = label_is_visible,
+};
+
+static ssize_t nr_links_show(struct device *dev,
+			     struct device_attribute *attr,
+			     char *buf)
+{
+	struct gtrace_component *comp = to_gtrace_component(dev);
+
+	return sysfs_emit(buf, "%u\n", comp->pdata->nr_inconns + comp->pdata->nr_outconns);
+}
+static DEVICE_ATTR_RO(nr_links);
+
+static struct attribute *gtrace_conns_attrs[] = {
+	&dev_attr_nr_links.attr,
+	NULL,
+};
+
+static struct attribute_group gtrace_conns_group = {
+	.attrs = gtrace_conns_attrs,
+	.name = "connections",
+};
+
+static int gtrace_make_links(struct gtrace_component *comp)
+{
+	struct gtrace_platform_data *pdata = comp->pdata;
+	struct gtrace_connection *conn;
+	char *outs, *ins;
+	int i, ret;
+
+	for (i = 0; i < pdata->nr_outconns; i++) {
+		conn = pdata->outconns[i];
+		if (!conn->dest_comp)
+			continue;
+
+		outs = devm_kasprintf(pdata->dev, GFP_KERNEL,
+				      "out:%d", conn->src_port);
+		ins  = devm_kasprintf(&conn->dest_comp->dev, GFP_KERNEL,
+				      "in:%d", conn->dest_port);
+		if (!outs || !ins)
+			return -ENOMEM;
+
+		ret = sysfs_add_link_to_group(&comp->dev.kobj,
+					      "connections",
+					      &conn->dest_comp->dev.kobj, outs);
+		if (ret)
+			return ret;
+
+		ret = sysfs_add_link_to_group(&conn->dest_comp->dev.kobj,
+					      "connections",
+					      &comp->dev.kobj, ins);
+		if (ret) {
+			sysfs_remove_link_from_group(&comp->dev.kobj,
+						     "connections", outs);
+			return ret;
+		}
+	}
+	return 0;
+}
+
+static void gtrace_remove_links(struct gtrace_component *comp)
+{
+	struct gtrace_platform_data *pdata = comp->pdata;
+	struct gtrace_connection *conn;
+	char buf[32];
+	int i;
+
+	for (i = 0; i < pdata->nr_outconns; i++) {
+		conn = pdata->outconns[i];
+		if (!conn->dest_comp)
+			continue;
+
+		snprintf(buf, sizeof(buf), "out:%d", conn->src_port);
+		sysfs_remove_link_from_group(&comp->dev.kobj,
+					     "connections", buf);
+
+		snprintf(buf, sizeof(buf), "in:%d", conn->dest_port);
+		sysfs_remove_link_from_group(&conn->dest_comp->dev.kobj,
+					     "connections", buf);
+	}
+}
+
+int gtrace_sysfs_add(struct gtrace_component *comp)
+{
+	const struct attribute_group *grp;
+	int ret;
+
+	if (gtrace_is_sink(comp->pdata) || comp->pdata->is_linksink)
+		grp = &gtrace_sink_group;
+	else if (gtrace_is_source(comp->pdata))
+		grp = &gtrace_source_group;
+	else
+		grp = &gtrace_link_group;
+
+	ret = sysfs_create_group(&comp->dev.kobj, grp);
+	if (ret) {
+		dev_err(&comp->dev, "failed to create sysfs group: %d\n", ret);
+		goto out;
+	}
+
+	ret = sysfs_create_group(&comp->dev.kobj, &gtrace_conns_group);
+	if (ret) {
+		dev_err(&comp->dev, "failed to create connections sysfs group: %d\n", ret);
+		goto out_remove_group;
+	}
+
+	ret = gtrace_make_links(comp);
+	if (ret) {
+		dev_err(&comp->dev, "failed to make sysfs links: %d\n", ret);
+		goto out_remove_conns;
+	}
+
+	if (comp->pdata->groups) {
+		ret = device_add_groups(&comp->dev, comp->pdata->groups);
+		if (ret) {
+			dev_err(&comp->dev, "failed to create sysfs component groups: %d\n", ret);
+			goto out_remove_links;
+		}
+	}
+
+	return ret;
+
+out_remove_links:
+	gtrace_remove_links(comp);
+out_remove_conns:
+	sysfs_remove_group(&comp->dev.kobj, &gtrace_conns_group);
+out_remove_group:
+	sysfs_remove_group(&comp->dev.kobj, grp);
+out:
+	return ret;
+}
+EXPORT_SYMBOL_GPL(gtrace_sysfs_add);
+
+void gtrace_sysfs_remove(struct gtrace_component *comp)
+{
+	mutex_lock(&gtrace_mutex);
+	if (comp->path) {
+		gtrace_path_stop(comp->path);
+		gtrace_destroy_path(comp->path);
+		comp->path = NULL;
+	}
+	mutex_unlock(&gtrace_mutex);
+
+	if (comp->pdata->groups)
+		device_remove_groups(&comp->dev, comp->pdata->groups);
+	gtrace_remove_links(comp);
+	sysfs_remove_group(&comp->dev.kobj, &gtrace_conns_group);
+
+	if (gtrace_is_sink(comp->pdata) || comp->pdata->is_linksink)
+		sysfs_remove_group(&comp->dev.kobj, &gtrace_sink_group);
+	else if (gtrace_is_source(comp->pdata))
+		sysfs_remove_group(&comp->dev.kobj, &gtrace_source_group);
+	else
+		sysfs_remove_group(&comp->dev.kobj, &gtrace_link_group);
+}
+EXPORT_SYMBOL_GPL(gtrace_sysfs_remove);
diff --git a/include/linux/gtrace.h b/include/linux/gtrace.h
index da8ba3779193..9945303d2713 100644
--- a/include/linux/gtrace.h
+++ b/include/linux/gtrace.h
@@ -14,6 +14,8 @@
 #include <linux/property.h>
 #include <linux/types.h>
 
+extern struct mutex gtrace_mutex;
+
 /*
  * Global list of trace component types across all architectures. Each backend
  * uses the entries relevant to it.
@@ -29,6 +31,8 @@ enum gtrace_component_type {
 
 /* Supported usage modes for trace components */
 enum gtrace_component_mode {
+	GTRACE_COMPONENT_MODE_DISABLED,
+	GTRACE_COMPONENT_MODE_SYSFS,
 	GTRACE_COMPONENT_MODE_PERF,
 	GTRACE_COMPONENT_MODE_MAX
 };
@@ -97,6 +101,9 @@ struct gtrace_hw_ops {
  * @inconns:     Array of pointers to input connections.
  * @nr_outconns: Number of output connections.
  * @outconns:    Array of pointers to output connections.
+ * @is_linksink: A component that can serve as both a link and a sink.
+ * @groups:      operations specific to this component. These will end up in
+ *               the component's sysfs sub-directory.
  */
 struct gtrace_platform_data {
 	struct device *dev;
@@ -138,6 +145,13 @@ struct gtrace_platform_data {
 	 */
 	unsigned int nr_outconns;
 	struct gtrace_connection **outconns;
+
+	/*
+	 * True for components like ETF that can operate as both a link and
+	 * a sink.
+	 */
+	bool is_linksink;
+	const struct attribute_group **groups;
 };
 
 static inline u32 gtrace_read32(struct gtrace_platform_data *pdata, u32 offset)
@@ -156,6 +170,28 @@ static inline u32 gtrace_relaxed_read32(struct gtrace_platform_data *pdata, u32
 	return pdata->read(pdata, offset, true);
 }
 
+static inline u64 gtrace_read_pair(struct gtrace_platform_data *pdata,
+				   u32 lo_offset, u32 hi_offset)
+{
+	if (likely(pdata->io_mem))
+		return readl(pdata->base + lo_offset) |
+			((u64)readl(pdata->base + hi_offset) << 32);
+
+	return pdata->read(pdata, lo_offset, false) |
+		((u64)pdata->read(pdata, hi_offset, false) << 32);
+}
+
+static inline u64 gtrace_relaxed_read_pair(struct gtrace_platform_data *pdata,
+					   u32 lo_offset, u32 hi_offset)
+{
+	if (likely(pdata->io_mem))
+		return readl_relaxed(pdata->base + lo_offset) |
+			((u64)readl_relaxed(pdata->base + hi_offset) << 32);
+
+	return pdata->read(pdata, lo_offset, true) |
+		((u64)pdata->read(pdata, hi_offset, true) << 32);
+}
+
 static inline void gtrace_write32(struct gtrace_platform_data *pdata, u32 val, u32 offset)
 {
 	if (likely(pdata->io_mem))
@@ -173,6 +209,30 @@ static inline void gtrace_relaxed_write32(struct gtrace_platform_data *pdata,
 		pdata->write(pdata, val, offset, true);
 }
 
+static inline void gtrace_relaxed_write_pair(struct gtrace_platform_data *pdata,
+					     u64 val, u32 lo_offset, u32 hi_offset)
+{
+	if (likely(pdata->io_mem)) {
+		writel_relaxed((u32)val, pdata->base + lo_offset);
+		writel_relaxed((u32)(val >> 32), pdata->base + hi_offset);
+	} else {
+		pdata->write(pdata, (u32)val, lo_offset, true);
+		pdata->write(pdata, (u32)(val >> 32), hi_offset, true);
+	}
+}
+
+static inline void gtrace_write_pair(struct gtrace_platform_data *pdata,
+				     u64 val, u32 lo_offset, u32 hi_offset)
+{
+	if (likely(pdata->io_mem)) {
+		writel((u32)val, pdata->base + lo_offset);
+		writel((u32)(val >> 32), pdata->base + hi_offset);
+	} else {
+		pdata->write(pdata, (u32)val, lo_offset, false);
+		pdata->write(pdata, (u32)(val >> 32), hi_offset, false);
+	}
+}
+
 static inline bool gtrace_is_source(struct gtrace_platform_data *pdata)
 {
 	return !pdata->nr_inconns ? true : false;
@@ -200,11 +260,18 @@ struct gtrace_component_id {
  * @pdata:    Pointer to underlying platform data.
  * @id:       Details to match the component.
  * @dev:      Device instance.
+ * @path:     Used for sysfs sources to get path, and for ETF link/sink detection.
+ * @sysfs_sink_activated: When set, this sink is preferred over other
+ *                        reachable sinks when a source walks forward
+ *                        looking for a sysfs trace target (e.g. stop
+ *                        at ETF instead of ETR).
  */
 struct gtrace_component {
 	struct gtrace_platform_data *pdata;
 	struct gtrace_component_id id;
 	struct device dev;
+	struct gtrace_path *path;
+	bool sysfs_sink_activated;
 };
 
 #define to_gtrace_component(__dev)	container_of_const(__dev, struct gtrace_component, dev)
@@ -329,4 +396,41 @@ static inline void gtrace_unregister_driver(struct gtrace_driver *gtdrv)
 int gtrace_perf_init(void);
 void gtrace_perf_exit(void);
 
+enum gtrace_component_mode gtrace_get_mode(struct gtrace_component *comp);
+
+struct gtrace_pair_attribute {
+	struct device_attribute attr;
+	u32 lo_off;
+	u32 hi_off;
+};
+
+struct gtrace_off_attribute {
+	struct device_attribute attr;
+	u32 off;
+};
+
+ssize_t gtrace_simple_show32(struct device *dev, struct device_attribute *attr, char *buf);
+ssize_t gtrace_simple_show_pair(struct device *dev, struct device_attribute *attr, char *buf);
+
+#define gtrace_simple_reg32(name, offset)				\
+	(&((struct gtrace_off_attribute[]) {				\
+	   {								\
+		__ATTR(name, 0444, gtrace_simple_show32, NULL),		\
+		offset							\
+	   }								\
+	})[0].attr.attr)
+
+#define gtrace_simple_reg64(name, lo_off, hi_off)			\
+	(&((struct gtrace_pair_attribute[]) {				\
+	   {								\
+		__ATTR(name, 0444, gtrace_simple_show_pair, NULL),	\
+		lo_off, hi_off						\
+	   }								\
+	})[0].attr.attr)
+
+int gtrace_enable_sysfs(struct gtrace_component *comp);
+int gtrace_disable_sysfs(struct gtrace_component *comp);
+int gtrace_sysfs_add(struct gtrace_component *comp);
+void gtrace_sysfs_remove(struct gtrace_component *comp);
+
 #endif /* __LINUX_GTRACE_H__ */
-- 
2.34.1


^ permalink raw reply	[flat|nested] 25+ messages in thread

* [PATCH 03/24] gtrace: Drop trace format field and helper function
  2026-10-23  3:14 [RFC PATCH 00/24] Add Linux RISC-V trace support via the gtrace framework Zane Leung
  2026-10-23  3:14 ` [PATCH 01/24] gtrace: Move connection release into component unregister Zane Leung
  2026-10-23  3:14 ` [PATCH 02/24] gtrace: Initial implementation of gtrace sysfs framework Zane Leung
@ 2026-10-23  3:14 ` Zane Leung
  2026-10-23  3:14 ` [PATCH 04/24] gtrace: Add RISC-V trace encoder sysfs support Zane Leung
                   ` (20 subsequent siblings)
  23 siblings, 0 replies; 25+ messages in thread
From: Zane Leung @ 2026-10-23  3:14 UTC (permalink / raw)
  To: gregkh, palmer, pjw, alexander.shishkin, suzuki.poulose,
	mchitale, anup, irogers
  Cc: mike.leach, james.clark, leo.yan, peterz, mingo, namhyung,
	mark.rutland, jolsa, adrian.hunter, alex, atish.patra,
	andrew.jones, sunilvl, liangzhen, zhuangqiubin, coresight,
	linux-riscv, linux-kernel

Remove the format field from struct gtrace_platform_data and drop
the associated gtrace_path_aux_format() helper function.

Signed-off-by: Zane Leung <liangzhen@linux.spacemit.com>
---
 drivers/hwtracing/gtrace/gtrace-perf.c      | 15 --------
 drivers/hwtracing/gtrace/rvtrace-encoder.c  | 39 ---------------------
 drivers/hwtracing/gtrace/rvtrace-platform.c |  3 +-
 drivers/hwtracing/gtrace/rvtrace-ramsink.c  |  1 -
 include/linux/gtrace.h                      |  3 --
 5 files changed, 1 insertion(+), 60 deletions(-)

diff --git a/drivers/hwtracing/gtrace/gtrace-perf.c b/drivers/hwtracing/gtrace/gtrace-perf.c
index 928f0405c6c1..56713c8ee850 100644
--- a/drivers/hwtracing/gtrace/gtrace-perf.c
+++ b/drivers/hwtracing/gtrace/gtrace-perf.c
@@ -167,20 +167,6 @@ static void *gtrace_setup_aux(struct perf_event *event, void **pages,
 	return NULL;
 }
 
-static u64 gtrace_path_aux_format(struct gtrace_path *path)
-{
-	struct gtrace_component *src = gtrace_path_source(path);
-
-	switch (src->pdata->format) {
-	case GTRACE_FORMAT_ETRACE:
-		return PERF_AUX_FLAG_GTRACE_FORMAT_ETRACE;
-	case GTRACE_FORMAT_NTRACE:
-		return PERF_AUX_FLAG_GTRACE_FORMAT_NTRACE;
-	default:
-		return PERF_AUX_FLAG_GTRACE_FORMAT_UNKNOWN;
-	}
-}
-
 static void gtrace_event_read(struct perf_event *event)
 {
 }
@@ -295,7 +281,6 @@ static void gtrace_event_stop(struct perf_event *event, int mode)
 			return;
 		ret = gtrace_path_copyto_auxbuf(path, &event_data->buf, &size, &format);
 		WARN_ON_ONCE(ret);
-		format = gtrace_path_aux_format(path);
 		if (READ_ONCE(handle->event)) {
 			/* Tag the AUX data with the source's trace format. */
 			perf_aux_output_flag(handle, format);
diff --git a/drivers/hwtracing/gtrace/rvtrace-encoder.c b/drivers/hwtracing/gtrace/rvtrace-encoder.c
index 1f06b1a2b412..f24780b1ca1e 100644
--- a/drivers/hwtracing/gtrace/rvtrace-encoder.c
+++ b/drivers/hwtracing/gtrace/rvtrace-encoder.c
@@ -53,38 +53,11 @@ static void rvtrace_encoder_set_srcid(struct gtrace_platform_data *pdata, u32 sr
 	gtrace_write32(pdata, val, RVTRACE_COMPONENT_CTRL_OFFSET);
 }
 
-/* Set requested trace format and return 0 if hardware supports that format */
-static int rvtrace_encoder_set_format(struct gtrace_platform_data *pdata)
-{
-	u32 val, fmt;
-
-	switch (pdata->format) {
-	case GTRACE_FORMAT_ETRACE:
-		fmt = RVTRACE_COMPONENT_CTRL_FORMAT_ETRACE;
-		break;
-	case GTRACE_FORMAT_NTRACE:
-		fmt = RVTRACE_COMPONENT_CTRL_FORMAT_NTRACE;
-		break;
-	default:
-		return -EINVAL;
-	}
-
-	val = gtrace_read32(pdata, RVTRACE_COMPONENT_CTRL_OFFSET);
-	val &= ~(RVTRACE_COMPONENT_CTRL_FORMAT_MASK << RVTRACE_COMPONENT_CTRL_FORMAT_SHIFT);
-	val |= fmt << RVTRACE_COMPONENT_CTRL_FORMAT_SHIFT;
-	gtrace_write32(pdata, val, RVTRACE_COMPONENT_CTRL_OFFSET);
-
-	val = gtrace_read32(pdata, RVTRACE_COMPONENT_CTRL_OFFSET);
-	val = (val >> RVTRACE_COMPONENT_CTRL_FORMAT_SHIFT) & RVTRACE_COMPONENT_CTRL_FORMAT_MASK;
-	return val == fmt ? 0 : -EOPNOTSUPP;
-}
-
 static int rvtrace_encoder_probe(struct gtrace_component *comp)
 {
 	struct gtrace_platform_data *pdata = comp->pdata;
 	struct rvtrace_encoder_priv *priv;
 	u32 val, hw_srcbits, srcbits;
-	int ret;
 
 	if (pdata->bound_cpu < 0) {
 		dev_err(&comp->dev, "cpu property missing. Please fix firmware.\n");
@@ -95,12 +68,6 @@ static int rvtrace_encoder_probe(struct gtrace_component *comp)
 	if (!priv)
 		return -ENOMEM;
 
-	ret = rvtrace_encoder_set_format(pdata);
-	if (ret) {
-		dev_err(&comp->dev, "failed to set format %d\n", pdata->format);
-		return ret;
-	}
-
 	/* Check if trTeInhibitSrc is hardwired to 1 */
 	val = gtrace_read32(pdata, RVTRACE_COMPONENT_CTRL_OFFSET);
 	val &= ~BIT(RVTRACE_COMPONENT_CTRL_INHIBITSRC_SHIFT);
@@ -144,12 +111,6 @@ static int rvtrace_encoder_start(struct gtrace_component *comp)
 	if (priv->srcbits)
 		rvtrace_encoder_set_srcid(pdata, priv->srcbits);
 
-	ret = rvtrace_encoder_set_format(pdata);
-	if (ret) {
-		dev_err(&comp->dev, "failed to set format %d\n", pdata->format);
-		return ret;
-	}
-
 	ret = gtrace_enable_component(comp);
 	if (ret) {
 		dev_err(&comp->dev, "failed to enable encoder.\n");
diff --git a/drivers/hwtracing/gtrace/rvtrace-platform.c b/drivers/hwtracing/gtrace/rvtrace-platform.c
index 189ff0b8bc18..e5c1f47388a2 100644
--- a/drivers/hwtracing/gtrace/rvtrace-platform.c
+++ b/drivers/hwtracing/gtrace/rvtrace-platform.c
@@ -78,7 +78,6 @@ static int rvtrace_platform_probe(struct platform_device *pdev)
 	pdata->dev = dev;
 	pdata->impid = RVTRACE_COMPONENT_IMPID_UNKNOWN;
 	pdata->hw_ops = &rvtrace_hw_ops;
-	pdata->format = (uintptr_t)device_get_match_data(dev);
 
 	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 	if (!res)
@@ -146,7 +145,7 @@ static void rvtrace_platform_remove(struct platform_device *pdev)
 }
 
 static const struct of_device_id rvtrace_platform_match[] = {
-	{ .compatible = "riscv,trace-component", .data = (void *)GTRACE_FORMAT_ETRACE },
+	{ .compatible = "riscv,trace-component" },
 	{}
 };
 
diff --git a/drivers/hwtracing/gtrace/rvtrace-ramsink.c b/drivers/hwtracing/gtrace/rvtrace-ramsink.c
index 67b6779aacee..9588c32c5b13 100644
--- a/drivers/hwtracing/gtrace/rvtrace-ramsink.c
+++ b/drivers/hwtracing/gtrace/rvtrace-ramsink.c
@@ -161,7 +161,6 @@ static size_t rvtrace_ramsink_copyto_auxbuf(struct gtrace_component *comp,
 	dst.cur = buf->pos;
 	src.base = priv->va;
 	src.len = priv->size;
-	*format = PERF_AUX_FLAG_GTRACE_FORMAT_ETRACE;
 	wp = rvtrace_ramsink_read_pair(comp->pdata, RVTRACE_RAMSINK_WPLOW_OFF);
 	wrap = wp & RVTRACE_RAMSINK_WPLOW_WRAP;
 	wp &= ~RVTRACE_RAMSINK_WPLOW_WRAP;
diff --git a/include/linux/gtrace.h b/include/linux/gtrace.h
index 9945303d2713..0a017f1f888d 100644
--- a/include/linux/gtrace.h
+++ b/include/linux/gtrace.h
@@ -96,7 +96,6 @@ struct gtrace_hw_ops {
  * @bound_cpu:   CPU to which the component is bound, or -1 if unbound. For a
  *               source component bound to a CPU this must not be -1.
  * @control_poll_timeout_usecs: Delay in usecs when polling control bits.
- * @format:      Trace format selected by the platform driver.
  * @nr_inconns:  Number of input connections.
  * @inconns:     Array of pointers to input connections.
  * @nr_outconns: Number of output connections.
@@ -128,8 +127,6 @@ struct gtrace_platform_data {
 	/* Delay in microseconds when polling control register bits */
 	int control_poll_timeout_usecs;
 
-	enum gtrace_format format;
-
 	/*
 	 * Platform driver must only populate empty pointer array without
 	 * any actual input connections.
-- 
2.34.1


^ permalink raw reply	[flat|nested] 25+ messages in thread

* [PATCH 04/24] gtrace: Add RISC-V trace encoder sysfs support
  2026-10-23  3:14 [RFC PATCH 00/24] Add Linux RISC-V trace support via the gtrace framework Zane Leung
                   ` (2 preceding siblings ...)
  2026-10-23  3:14 ` [PATCH 03/24] gtrace: Drop trace format field and helper function Zane Leung
@ 2026-10-23  3:14 ` Zane Leung
  2026-10-23  3:14 ` [PATCH 05/24] gtrace: Introduce some PMU format attributes Zane Leung
                   ` (19 subsequent siblings)
  23 siblings, 0 replies; 25+ messages in thread
From: Zane Leung @ 2026-10-23  3:14 UTC (permalink / raw)
  To: gregkh, palmer, pjw, alexander.shishkin, suzuki.poulose,
	mchitale, anup, irogers
  Cc: mike.leach, james.clark, leo.yan, peterz, mingo, namhyung,
	mark.rutland, jolsa, adrian.hunter, alex, atish.patra,
	andrew.jones, sunilvl, liangzhen, zhuangqiubin, coresight,
	linux-riscv, linux-kernel

Add sysfs attribute support for RISC-V trace encoder components in
the platform driver. The encoder attribute groups are now assigned to
pdata->groups based on component type during registration, enabling
encoder configuration through sysfs interfaces.

Signed-off-by: Zane Leung <liangzhen@linux.spacemit.com>
---
 drivers/hwtracing/gtrace/Makefile             |   1 +
 ...trace-encoder.c => rvtrace-encoder-core.c} | 117 +++++--
 .../hwtracing/gtrace/rvtrace-encoder-sysfs.c  | 290 ++++++++++++++++++
 drivers/hwtracing/gtrace/rvtrace-encoder.h    |  53 ++++
 drivers/hwtracing/gtrace/rvtrace-platform.c   |   8 +
 drivers/hwtracing/gtrace/rvtrace.h            |   3 +
 6 files changed, 438 insertions(+), 34 deletions(-)
 rename drivers/hwtracing/gtrace/{rvtrace-encoder.c => rvtrace-encoder-core.c} (60%)
 create mode 100644 drivers/hwtracing/gtrace/rvtrace-encoder-sysfs.c
 create mode 100644 drivers/hwtracing/gtrace/rvtrace-encoder.h

diff --git a/drivers/hwtracing/gtrace/Makefile b/drivers/hwtracing/gtrace/Makefile
index ff79a49bdb5e..87cb5add78d4 100644
--- a/drivers/hwtracing/gtrace/Makefile
+++ b/drivers/hwtracing/gtrace/Makefile
@@ -6,4 +6,5 @@ gtrace-$(CONFIG_OF) += gtrace-of.o
 
 obj-$(CONFIG_RVTRACE) += rvtrace-platform.o
 obj-$(CONFIG_RVTRACE_ENCODER) += rvtrace-encoder.o
+rvtrace-encoder-y := rvtrace-encoder-core.o rvtrace-encoder-sysfs.o
 obj-$(CONFIG_RVTRACE_RAMSINK) += rvtrace-ramsink.o
diff --git a/drivers/hwtracing/gtrace/rvtrace-encoder.c b/drivers/hwtracing/gtrace/rvtrace-encoder-core.c
similarity index 60%
rename from drivers/hwtracing/gtrace/rvtrace-encoder.c
rename to drivers/hwtracing/gtrace/rvtrace-encoder-core.c
index f24780b1ca1e..01925ebdfbde 100644
--- a/drivers/hwtracing/gtrace/rvtrace-encoder.c
+++ b/drivers/hwtracing/gtrace/rvtrace-encoder-core.c
@@ -10,28 +10,13 @@
 #include <linux/minmax.h>
 #include <linux/slab.h>
 #include <linux/types.h>
+#include <linux/bitfield.h>
 #include "rvtrace.h"
+#include "rvtrace-encoder.h"
 
-#define RVTRACE_COMPONENT_CTRL_ITRACE_SHIFT	2
-#define RVTRACE_COMPONENT_CTRL_INSTMODE_MASK	0x7
-#define RVTRACE_COMPONENT_CTRL_INSTMODE_SHIFT	4
-#define RVTRACE_COMPONENT_CTRL_INSTMODE_OPIT	0x6
-#define RVTRACE_COMPONENT_CTRL_INHIBITSRC_SHIFT	15
-#define RVTRACE_COMPONENT_CTRL_FORMAT_MASK	0x7
-#define RVTRACE_COMPONENT_CTRL_FORMAT_SHIFT	24
-#define RVTRACE_COMPONENT_CTRL_FORMAT_ETRACE	0x0
-#define RVTRACE_COMPONENT_CTRL_FORMAT_NTRACE	0x1
-
-#define RVTRACE_ENCODER_INSTFEAT_OFFSET		0x8
-#define RVTRACE_ENCODER_INSTFEAT_SRCID_MASK	0xfff
-#define RVTRACE_ENCODER_INSTFEAT_SRCID_SHIFT	16
-#define RVTRACE_ENCODER_INSTFEAT_SRCBITS_MASK	0xf
-#define RVTRACE_ENCODER_INSTFEAT_SRCBITS_SHIFT	28
-#define RVTRACE_ENCODER_INSTFEAT_SRCBITS_MAX	12
-
-struct rvtrace_encoder_priv {
-	u32 srcbits;
-};
+static int boot_enable;
+module_param(boot_enable, int, 0444);
+MODULE_PARM_DESC(boot_enable, "Enable tracing on boot");
 
 /*
  * Set the srcid and clear trTeInhibitSrc bit so that the emitted trace messages carry
@@ -42,10 +27,10 @@ static void rvtrace_encoder_set_srcid(struct gtrace_platform_data *pdata, u32 sr
 	u32 val;
 
 	val = gtrace_read32(pdata, RVTRACE_ENCODER_INSTFEAT_OFFSET);
-	val &= ~(RVTRACE_ENCODER_INSTFEAT_SRCID_MASK << RVTRACE_ENCODER_INSTFEAT_SRCID_SHIFT);
-	val &= ~(RVTRACE_ENCODER_INSTFEAT_SRCBITS_MASK << RVTRACE_ENCODER_INSTFEAT_SRCBITS_SHIFT);
-	val |= pdata->bound_cpu << RVTRACE_ENCODER_INSTFEAT_SRCID_SHIFT;
-	val |= srcbits << RVTRACE_ENCODER_INSTFEAT_SRCBITS_SHIFT;
+	val &= ~RVTRACE_ENCODER_INSTFEAT_SRCID_MASK;
+	val &= ~RVTRACE_ENCODER_INSTFEAT_SRCBITS_MASK;
+	val |= FIELD_PREP(RVTRACE_ENCODER_INSTFEAT_SRCBITS_MASK, srcbits);
+	val |= FIELD_PREP(RVTRACE_ENCODER_INSTFEAT_SRCID_MASK, pdata->bound_cpu);
 	gtrace_write32(pdata, val, RVTRACE_ENCODER_INSTFEAT_OFFSET);
 
 	val = gtrace_read32(pdata, RVTRACE_COMPONENT_CTRL_OFFSET);
@@ -53,11 +38,32 @@ static void rvtrace_encoder_set_srcid(struct gtrace_platform_data *pdata, u32 sr
 	gtrace_write32(pdata, val, RVTRACE_COMPONENT_CTRL_OFFSET);
 }
 
+static void rvtrace_encoder_get_syncmode_hw(struct gtrace_platform_data *pdata,
+					    bool *syncmode_hw)
+{
+	u32 ctrl, val;
+	enum rvtrace_encoder_syncmode mode;
+
+	ctrl = gtrace_read32(pdata, RVTRACE_COMPONENT_CTRL_OFFSET);
+
+	for (mode = RVTRACE_ENCODER_SYNCMODE_OFF; mode < RVTRACE_ENCODER_SYNCMODE_MAX; mode++) {
+		val = ctrl & ~RVTRACE_COMPONENT_CTRL_SYNCMODE_MASK;
+		val |= FIELD_PREP(RVTRACE_COMPONENT_CTRL_SYNCMODE_MASK, mode);
+		gtrace_write32(pdata, val, RVTRACE_COMPONENT_CTRL_OFFSET);
+		val = gtrace_read32(pdata, RVTRACE_COMPONENT_CTRL_OFFSET);
+		val = FIELD_GET(RVTRACE_COMPONENT_CTRL_SYNCMODE_MASK, val);
+		syncmode_hw[mode] = (mode == val);
+	}
+
+	gtrace_write32(pdata, ctrl, RVTRACE_COMPONENT_CTRL_OFFSET);
+}
+
 static int rvtrace_encoder_probe(struct gtrace_component *comp)
 {
 	struct gtrace_platform_data *pdata = comp->pdata;
 	struct rvtrace_encoder_priv *priv;
 	u32 val, hw_srcbits, srcbits;
+	int ret;
 
 	if (pdata->bound_cpu < 0) {
 		dev_err(&comp->dev, "cpu property missing. Please fix firmware.\n");
@@ -68,6 +74,28 @@ static int rvtrace_encoder_probe(struct gtrace_component *comp)
 	if (!priv)
 		return -ENOMEM;
 
+	/* Get trTeFormat */
+	val = gtrace_read32(pdata, RVTRACE_COMPONENT_CTRL_OFFSET);
+	priv->format = FIELD_GET(RVTRACE_COMPONENT_CTRL_FORMAT_MASK, val);
+	if (priv->format > GTRACE_FORMAT_NTRACE) {
+		dev_err(&comp->dev, "unsupported trace format %d\n", priv->format);
+		return -EINVAL;
+	}
+
+	/* Get trTeInstSyncMode */
+	priv->syncmode = FIELD_GET(RVTRACE_COMPONENT_CTRL_SYNCMODE_MASK, val);
+	rvtrace_encoder_get_syncmode_hw(pdata, priv->syncmode_hw);
+
+	/* Check if trTeContext is hardwired to 0 */
+	val |= BIT(RVTRACE_COMPONENT_CTRL_CONTEXT_SHIFT);
+	gtrace_write32(pdata, val, RVTRACE_COMPONENT_CTRL_OFFSET);
+	val = gtrace_read32(pdata, RVTRACE_COMPONENT_CTRL_OFFSET);
+	if (val & BIT(RVTRACE_COMPONENT_CTRL_CONTEXT_SHIFT)) {
+		priv->has_context = true;
+		val &= ~BIT(RVTRACE_COMPONENT_CTRL_CONTEXT_SHIFT);
+		gtrace_write32(pdata, val, RVTRACE_COMPONENT_CTRL_OFFSET);
+	}
+
 	/* Check if trTeInhibitSrc is hardwired to 1 */
 	val = gtrace_read32(pdata, RVTRACE_COMPONENT_CTRL_OFFSET);
 	val &= ~BIT(RVTRACE_COMPONENT_CTRL_INHIBITSRC_SHIFT);
@@ -76,14 +104,16 @@ static int rvtrace_encoder_probe(struct gtrace_component *comp)
 	if (!(val & BIT(RVTRACE_COMPONENT_CTRL_INHIBITSRC_SHIFT))) {
 		/* Probe how many bits can trTeSrcID span in the emitted Trace messages */
 		val = gtrace_read32(pdata, RVTRACE_ENCODER_INSTFEAT_OFFSET);
-		val |= (RVTRACE_ENCODER_INSTFEAT_SRCBITS_MASK <<
-			RVTRACE_ENCODER_INSTFEAT_SRCBITS_SHIFT);
-		gtrace_write32(pdata, val, RVTRACE_ENCODER_INSTFEAT_OFFSET);
-		val = gtrace_read32(pdata, RVTRACE_ENCODER_INSTFEAT_OFFSET);
-		hw_srcbits = (val >> RVTRACE_ENCODER_INSTFEAT_SRCBITS_SHIFT) &
-			     RVTRACE_ENCODER_INSTFEAT_SRCBITS_MASK;
+		hw_srcbits = RVTRACE_ENCODER_INSTFEAT_SRCBITS_MAX;
+		do {
+			val &= ~RVTRACE_ENCODER_INSTFEAT_SRCBITS_MASK;
+			val |= FIELD_PREP(RVTRACE_ENCODER_INSTFEAT_SRCBITS_MASK, hw_srcbits);
+			gtrace_write32(pdata, val, RVTRACE_ENCODER_INSTFEAT_OFFSET);
+			val = gtrace_read32(pdata, RVTRACE_ENCODER_INSTFEAT_OFFSET);
+			if (FIELD_GET(RVTRACE_ENCODER_INSTFEAT_SRCBITS_MASK, val) == hw_srcbits)
+				break;
+		} while (--hw_srcbits);
 		/* Determine if there are sufficient bits to hold max cpus */
-		hw_srcbits = min_t(u32, hw_srcbits, RVTRACE_ENCODER_INSTFEAT_SRCBITS_MAX);
 		srcbits = min_t(u32, hw_srcbits, order_base_2(nr_cpu_ids));
 		if (srcbits && pdata->bound_cpu < BIT(srcbits)) {
 			rvtrace_encoder_set_srcid(pdata, srcbits);
@@ -98,6 +128,15 @@ static int rvtrace_encoder_probe(struct gtrace_component *comp)
 		gtrace_write32(pdata, val, RVTRACE_COMPONENT_CTRL_OFFSET);
 	}
 	dev_set_drvdata(&comp->dev, priv);
+
+	if (boot_enable) {
+		ret = gtrace_enable_sysfs(comp);
+		if (ret) {
+			dev_err(&comp->dev, "failed to enable tracing on boot.\n");
+			return -EINVAL;
+		}
+		priv->boot_enable = true;
+	}
 	return 0;
 }
 
@@ -117,10 +156,20 @@ static int rvtrace_encoder_start(struct gtrace_component *comp)
 		return ret;
 	}
 
-	/* set mode */
 	val = gtrace_read32(pdata, RVTRACE_COMPONENT_CTRL_OFFSET);
-	val &= ~(RVTRACE_COMPONENT_CTRL_INSTMODE_MASK << RVTRACE_COMPONENT_CTRL_INSTMODE_SHIFT);
-	val |= (RVTRACE_COMPONENT_CTRL_INSTMODE_OPIT << RVTRACE_COMPONENT_CTRL_INSTMODE_SHIFT);
+	/* set mode */
+	val &= ~RVTRACE_COMPONENT_CTRL_INSTMODE_MASK;
+	val |= FIELD_PREP(RVTRACE_COMPONENT_CTRL_INSTMODE_MASK,
+		RVTRACE_COMPONENT_CTRL_INSTMODE_OPIT);
+	/* set context */
+	val &= ~BIT(RVTRACE_COMPONENT_CTRL_CONTEXT_SHIFT);
+	val |= FIELD_PREP(BIT(RVTRACE_COMPONENT_CTRL_CONTEXT_SHIFT), !!priv->context);
+	/* set syncmode */
+	val &= ~RVTRACE_COMPONENT_CTRL_SYNCMODE_MASK;
+	val |= FIELD_PREP(RVTRACE_COMPONENT_CTRL_SYNCMODE_MASK, priv->syncmode);
+	/* set syncmax */
+	val &= ~RVTRACE_COMPONENT_CTRL_SYNCMAX_MASK;
+	val |= FIELD_PREP(RVTRACE_COMPONENT_CTRL_SYNCMAX_MASK, priv->syncmax);
 	gtrace_write32(pdata, val, RVTRACE_COMPONENT_CTRL_OFFSET);
 
 	val = gtrace_read32(pdata, RVTRACE_COMPONENT_CTRL_OFFSET);
diff --git a/drivers/hwtracing/gtrace/rvtrace-encoder-sysfs.c b/drivers/hwtracing/gtrace/rvtrace-encoder-sysfs.c
new file mode 100644
index 000000000000..e973d0e43036
--- /dev/null
+++ b/drivers/hwtracing/gtrace/rvtrace-encoder-sysfs.c
@@ -0,0 +1,290 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2026 Qualcomm Technologies, Inc.
+ */
+
+#include <linux/device.h>
+#include <linux/gtrace.h>
+#include <linux/types.h>
+#include "rvtrace.h"
+#include "rvtrace-encoder.h"
+
+struct rvtrace_component_reg {
+	struct gtrace_component *comp;
+	u32 offset;
+	u32 data;
+};
+
+static void do_smp_cross_read(void *data)
+{
+	struct rvtrace_component_reg *reg = data;
+
+	reg->data = gtrace_relaxed_read32(reg->comp->pdata, reg->offset);
+}
+
+static u32 rvtrace_cross_read(struct gtrace_component *comp, u32 offset)
+{
+	struct gtrace_platform_data *pdata = comp->pdata;
+	struct rvtrace_component_reg reg;
+
+	reg.comp = comp;
+	reg.offset = offset;
+	/*
+	 * smp cross call ensures the CPU will be powered up before
+	 * accessing the RISC-V trace core registers
+	 */
+	smp_call_function_single(pdata->bound_cpu, do_smp_cross_read, &reg, 1);
+	return reg.data;
+}
+
+static u32 rvtrace_attr_to_offset(struct device_attribute *attr)
+{
+	struct dev_ext_attribute *eattr;
+
+	eattr = container_of(attr, struct dev_ext_attribute, attr);
+	return (u32)(unsigned long)eattr->var;
+}
+
+static ssize_t rvtrace_reg_show(struct device *dev,
+				struct device_attribute *attr,
+				char *buf)
+{
+	u32 val, offset;
+	struct gtrace_component *comp = to_gtrace_component(dev);
+
+	offset = rvtrace_attr_to_offset(attr);
+
+	val = rvtrace_cross_read(comp, offset);
+
+	return scnprintf(buf, PAGE_SIZE, "0x%x\n", val);
+}
+
+/*
+ * Macro to set an RO ext attribute with offset and show function.
+ */
+#define rvtrace_reg_showfn(name, offset, showfn) (	    \
+	&((struct dev_ext_attribute[]) {		    \
+	   {						    \
+		__ATTR(name, 0444, showfn, NULL),	    \
+		(void *)(unsigned long)offset		    \
+	   }						    \
+	})[0].attr.attr					    \
+	)
+
+/* macro using the default rvtrace_reg_show function */
+#define rvtrace_reg(name, offset)	\
+	rvtrace_reg_showfn(name, offset, rvtrace_reg_show)
+
+static ssize_t cpu_show(struct device *dev,
+			struct device_attribute *attr, char *buf)
+{
+	struct gtrace_component *comp = to_gtrace_component(dev);
+
+	return sysfs_emit(buf, "%d\n", comp->pdata->bound_cpu);
+}
+static DEVICE_ATTR_RO(cpu);
+
+static struct attribute *rvtrace_encoder_attrs[] = {
+	&dev_attr_cpu.attr,
+	NULL,
+};
+
+static struct attribute *rvtrace_encoder_mgmt_attrs[] = {
+	rvtrace_reg(control, RVTRACE_COMPONENT_CTRL_OFFSET),
+	rvtrace_reg(features, RVTRACE_ENCODER_INSTFEAT_OFFSET),
+	NULL,
+};
+
+static ssize_t format_show(struct device *dev,
+			   struct device_attribute *attr, char *buf)
+{
+	struct gtrace_component *comp = to_gtrace_component(dev);
+	struct rvtrace_encoder_priv *priv = dev_get_drvdata(&comp->dev);
+
+	return sysfs_emit(buf, "%u\n", priv->format);
+}
+static DEVICE_ATTR_RO(format);
+
+static ssize_t has_context_show(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct gtrace_component *comp = to_gtrace_component(dev);
+	struct rvtrace_encoder_priv *priv = dev_get_drvdata(&comp->dev);
+
+	return sysfs_emit(buf, "%u\n", priv->has_context);
+}
+static DEVICE_ATTR_RO(has_context);
+
+static ssize_t context_show(struct device *dev,
+			    struct device_attribute *attr, char *buf)
+{
+	struct gtrace_component *comp = to_gtrace_component(dev);
+	struct rvtrace_encoder_priv *priv = dev_get_drvdata(&comp->dev);
+
+	return sysfs_emit(buf, "%u\n", priv->context);
+}
+
+static ssize_t context_store(struct device *dev,
+			     struct device_attribute *attr,
+			     const char *buf, size_t size)
+{
+	int ret;
+	unsigned long val;
+	struct gtrace_component *comp = to_gtrace_component(dev);
+	struct rvtrace_encoder_priv *priv = dev_get_drvdata(&comp->dev);
+
+	if (!priv->has_context)
+		return -EINVAL;
+
+	ret = kstrtoul(buf, 10, &val);
+	if (ret)
+		return ret;
+
+	priv->context = !!val;
+
+	return size;
+}
+static DEVICE_ATTR_RW(context);
+
+static const char *const syncmodes_str[] = {
+	[RVTRACE_ENCODER_SYNCMODE_OFF]		= "off",
+	[RVTRACE_ENCODER_SYNCMODE_MESSAGES]	= "messages",
+	[RVTRACE_ENCODER_SYNCMODE_CLOCK]	= "clock",
+	[RVTRACE_ENCODER_SYNCMODE_INSTRUCTION]	= "instruction",
+};
+
+static ssize_t syncmodes_available_show(struct device *dev,
+					struct device_attribute *attr, char *buf)
+{
+	struct gtrace_component *comp = to_gtrace_component(dev);
+	struct rvtrace_encoder_priv *priv = dev_get_drvdata(&comp->dev);
+	ssize_t size = 0;
+	enum rvtrace_encoder_syncmode mode;
+
+	for (mode = RVTRACE_ENCODER_SYNCMODE_OFF; mode < RVTRACE_ENCODER_SYNCMODE_MAX; mode++) {
+		if (priv->syncmode_hw[mode])
+			size += sysfs_emit_at(buf, size, "%s ",
+				syncmodes_str[mode]);
+	}
+
+	size += sysfs_emit_at(buf, size, "\n");
+	return size;
+}
+static DEVICE_ATTR_RO(syncmodes_available);
+
+static ssize_t syncmode_preferred_show(struct device *dev,
+				       struct device_attribute *attr, char *buf)
+{
+	struct gtrace_component *comp = to_gtrace_component(dev);
+	struct rvtrace_encoder_priv *priv = dev_get_drvdata(&comp->dev);
+
+	return sysfs_emit(buf, "%s\n", syncmodes_str[priv->syncmode]);
+}
+
+static ssize_t syncmode_preferred_store(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t size)
+{
+	struct gtrace_component *comp = to_gtrace_component(dev);
+	struct rvtrace_encoder_priv *priv = dev_get_drvdata(&comp->dev);
+
+	if (sysfs_streq(buf, syncmodes_str[RVTRACE_ENCODER_SYNCMODE_OFF]) &&
+			priv->syncmode_hw[RVTRACE_ENCODER_SYNCMODE_OFF])
+		priv->syncmode = RVTRACE_ENCODER_SYNCMODE_OFF;
+	else if (sysfs_streq(buf, syncmodes_str[RVTRACE_ENCODER_SYNCMODE_MESSAGES]) &&
+			priv->syncmode_hw[RVTRACE_ENCODER_SYNCMODE_MESSAGES])
+		priv->syncmode = RVTRACE_ENCODER_SYNCMODE_MESSAGES;
+	else if (sysfs_streq(buf, syncmodes_str[RVTRACE_ENCODER_SYNCMODE_CLOCK]) &&
+			priv->syncmode_hw[RVTRACE_ENCODER_SYNCMODE_CLOCK])
+		priv->syncmode = RVTRACE_ENCODER_SYNCMODE_CLOCK;
+	else if (sysfs_streq(buf, syncmodes_str[RVTRACE_ENCODER_SYNCMODE_INSTRUCTION]) &&
+			priv->syncmode_hw[RVTRACE_ENCODER_SYNCMODE_INSTRUCTION])
+		priv->syncmode = RVTRACE_ENCODER_SYNCMODE_INSTRUCTION;
+	else
+		return -EINVAL;
+	return size;
+}
+static DEVICE_ATTR_RW(syncmode_preferred);
+
+static ssize_t syncmax_show(struct device *dev,
+			    struct device_attribute *attr, char *buf)
+{
+	struct gtrace_component *comp = to_gtrace_component(dev);
+	struct rvtrace_encoder_priv *priv = dev_get_drvdata(&comp->dev);
+
+	return sysfs_emit(buf, "%u\n", priv->syncmax);
+}
+
+static ssize_t syncmax_store(struct device *dev,
+			     struct device_attribute *attr,
+			     const char *buf, size_t size)
+{
+	struct gtrace_component *comp = to_gtrace_component(dev);
+	struct rvtrace_encoder_priv *priv = dev_get_drvdata(&comp->dev);
+	unsigned long val;
+	int ret;
+
+	ret = kstrtoul(buf, 0, &val);
+	if (ret)
+		return ret;
+
+	if (val > RVTRACE_COMPONENT_CTRL_SYNCMAX_MAX)
+		return -EINVAL;
+
+	priv->syncmax = val;
+	return size;
+}
+static DEVICE_ATTR_RW(syncmax);
+
+static struct attribute *rvtrace_encoder_control_attrs[] = {
+	&dev_attr_format.attr,
+	&dev_attr_has_context.attr,
+	&dev_attr_context.attr,
+	&dev_attr_syncmodes_available.attr,
+	&dev_attr_syncmode_preferred.attr,
+	&dev_attr_syncmax.attr,
+	NULL,
+};
+
+static ssize_t srcbits_show(struct device *dev,
+			    struct device_attribute *attr, char *buf)
+{
+	struct gtrace_component *comp = to_gtrace_component(dev);
+	struct rvtrace_encoder_priv *priv = dev_get_drvdata(&comp->dev);
+
+	return sysfs_emit(buf, "%u\n", priv->srcbits);
+}
+static DEVICE_ATTR_RO(srcbits);
+
+static struct attribute *rvtrace_encoder_features_attrs[] = {
+	&dev_attr_srcbits.attr,
+	NULL,
+};
+
+static const struct attribute_group rvtrace_encoder_group = {
+	.attrs = rvtrace_encoder_attrs,
+};
+
+static const struct attribute_group rvtrace_encoder_mgmt_group = {
+	.attrs = rvtrace_encoder_mgmt_attrs,
+	.name = "mgmt",
+};
+
+static const struct attribute_group rvtrace_encoder_control_group = {
+	.attrs = rvtrace_encoder_control_attrs,
+	.name = "control",
+};
+
+static const struct attribute_group rvtrace_encoder_features_group = {
+	.attrs = rvtrace_encoder_features_attrs,
+	.name = "features",
+};
+
+const struct attribute_group *rvtrace_encoder_groups[] = {
+	&rvtrace_encoder_group,
+	&rvtrace_encoder_mgmt_group,
+	&rvtrace_encoder_control_group,
+	&rvtrace_encoder_features_group,
+	NULL,
+};
+EXPORT_SYMBOL_GPL(rvtrace_encoder_groups);
diff --git a/drivers/hwtracing/gtrace/rvtrace-encoder.h b/drivers/hwtracing/gtrace/rvtrace-encoder.h
new file mode 100644
index 000000000000..733d44c0e641
--- /dev/null
+++ b/drivers/hwtracing/gtrace/rvtrace-encoder.h
@@ -0,0 +1,53 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#ifndef __RVTRACE_ENCODER_H__
+#define __RVTRACE_ENCODER_H__
+
+#define RVTRACE_COMPONENT_CTRL_ITRACE_SHIFT     2
+#define RVTRACE_COMPONENT_CTRL_INSTMODE_MASK	GENMASK(6, 4)
+#define RVTRACE_COMPONENT_CTRL_INSTMODE_OPIT	0x6
+#define RVTRACE_COMPONENT_CTRL_CONTEXT_SHIFT	9
+#define RVTRACE_COMPONENT_CTRL_INHIBITSRC_SHIFT	15
+#define RVTRACE_COMPONENT_CTRL_SYNCMODE_MASK	GENMASK(17, 16)
+#define RVTRACE_COMPONENT_CTRL_SYNCMAX_MASK	GENMASK(23, 20)
+#define RVTRACE_COMPONENT_CTRL_SYNCMAX_MAX	15
+#define RVTRACE_COMPONENT_CTRL_FORMAT_MASK	GENMASK(26, 24)
+#define RVTRACE_COMPONENT_CTRL_FORMAT_ETRACE	0x0
+#define RVTRACE_COMPONENT_CTRL_FORMAT_NTRACE	0x1
+
+#define RVTRACE_ENCODER_INSTFEAT_OFFSET		0x8
+#define RVTRACE_ENCODER_INSTFEAT_SRCID_MASK	GENMASK(27, 16)
+#define RVTRACE_ENCODER_INSTFEAT_SRCBITS_MASK	GENMASK(31, 28)
+#define RVTRACE_ENCODER_INSTFEAT_SRCBITS_MAX	12
+
+enum rvtrace_encoder_syncmode {
+	RVTRACE_ENCODER_SYNCMODE_OFF,
+	RVTRACE_ENCODER_SYNCMODE_MESSAGES,
+	RVTRACE_ENCODER_SYNCMODE_CLOCK,
+	RVTRACE_ENCODER_SYNCMODE_INSTRUCTION,
+	RVTRACE_ENCODER_SYNCMODE_MAX,
+};
+
+/**
+ * struct rvtrace_encoder_priv - Private driver data for the rvtrace encoder.
+ * @syncmode_hw: Hardware capability for synchronization modes.
+ * @syncmode:    Current operational synchronization mode configured in hardware.
+ * @syncmax:     Maximum interval value allowed for synchronization.
+ * @srcbits:     Width of source bits allocated for trace generation.
+ * @format:      Trace format type used by this encoder compinent.
+ * @has_context: Hardware flag for scontext/mcontext and privilege tracking.
+ * @context:     Current configuration for scontext/mcontext and privilege tracking.
+ * @boot_enable: Flag to automatically enable tracing on boot.
+ */
+struct rvtrace_encoder_priv {
+	bool syncmode_hw[RVTRACE_ENCODER_SYNCMODE_MAX];
+	u32 syncmode;
+	u32 syncmax;
+	u32 srcbits;
+	enum gtrace_format format;
+	bool has_context;
+	bool context;
+	bool boot_enable;
+};
+
+#endif/* __RVTRACE_ENCODER_H__ */
diff --git a/drivers/hwtracing/gtrace/rvtrace-platform.c b/drivers/hwtracing/gtrace/rvtrace-platform.c
index e5c1f47388a2..64496a638359 100644
--- a/drivers/hwtracing/gtrace/rvtrace-platform.c
+++ b/drivers/hwtracing/gtrace/rvtrace-platform.c
@@ -129,6 +129,14 @@ static int rvtrace_platform_probe(struct platform_device *pdev)
 	id.type = gtype;
 	id.version = rvtrace_component_mkversion(major, minor);
 
+	switch (gtype) {
+	case GTRACE_RVTRACE_ENCODER:
+		pdata->groups = rvtrace_encoder_groups;
+		break;
+	default:
+		pdata->groups = NULL;
+	}
+
 	comp = gtrace_register_component(&id, rvtrace_type_name(gtype), pdata);
 	if (IS_ERR(comp))
 		return PTR_ERR(comp);
diff --git a/drivers/hwtracing/gtrace/rvtrace.h b/drivers/hwtracing/gtrace/rvtrace.h
index e6be1ce303cc..fee8e2e4f42a 100644
--- a/drivers/hwtracing/gtrace/rvtrace.h
+++ b/drivers/hwtracing/gtrace/rvtrace.h
@@ -118,4 +118,7 @@ static inline int rvtrace_comp_poll_empty(struct gtrace_component *comp)
 
 extern const struct gtrace_hw_ops rvtrace_hw_ops;
 
+/* Sysfs attribute groups for RISC-V trace components */
+extern const struct attribute_group *rvtrace_encoder_groups[];
+
 #endif /* __LINUX_RVTRACE_H__ */
-- 
2.34.1


^ permalink raw reply	[flat|nested] 25+ messages in thread

* [PATCH 05/24] gtrace: Introduce some PMU format attributes
  2026-10-23  3:14 [RFC PATCH 00/24] Add Linux RISC-V trace support via the gtrace framework Zane Leung
                   ` (3 preceding siblings ...)
  2026-10-23  3:14 ` [PATCH 04/24] gtrace: Add RISC-V trace encoder sysfs support Zane Leung
@ 2026-10-23  3:14 ` Zane Leung
  2026-10-23  3:14 ` [PATCH 06/24] gtrace: Replace owner PID with perf_event pointer in gtrace_path Zane Leung
                   ` (18 subsequent siblings)
  23 siblings, 0 replies; 25+ messages in thread
From: Zane Leung @ 2026-10-23  3:14 UTC (permalink / raw)
  To: gregkh, palmer, pjw, alexander.shishkin, suzuki.poulose,
	mchitale, anup, irogers
  Cc: mike.leach, james.clark, leo.yan, peterz, mingo, namhyung,
	mark.rutland, jolsa, adrian.hunter, alex, atish.patra,
	andrew.jones, sunilvl, liangzhen, zhuangqiubin, coresight,
	linux-riscv, linux-kernel

Add new PMU format attributes to support timestamp, context, and
synchronization controls. This allows userspace tools like perf to
pass detailed hardware configurations down to the gtrace framework.

Signed-off-by: Zane Leung <liangzhen@linux.spacemit.com>
---
 drivers/hwtracing/gtrace/gtrace-perf.c | 12 ++++++++++++
 include/linux/gtrace.h                 |  7 +++++++
 2 files changed, 19 insertions(+)

diff --git a/drivers/hwtracing/gtrace/gtrace-perf.c b/drivers/hwtracing/gtrace/gtrace-perf.c
index 56713c8ee850..703c12b95665 100644
--- a/drivers/hwtracing/gtrace/gtrace-perf.c
+++ b/drivers/hwtracing/gtrace/gtrace-perf.c
@@ -323,9 +323,21 @@ static void gtrace_event_del(struct perf_event *event, int mode)
 }
 
 PMU_FORMAT_ATTR(event, "config:0-0");
+PMU_FORMAT_ATTR(context, "config:1-1");
+PMU_FORMAT_ATTR(syncmode, "config:2-3");
+PMU_FORMAT_ATTR(syncmax, "config:4-7");
+PMU_FORMAT_ATTR(timestamp, "config:8-8");
+PMU_FORMAT_ATTR(tsmode, "config:9-11");
+PMU_FORMAT_ATTR(tsprescale, "config:12-13");
 
 static struct attribute *gtrace_pmu_formats_attr[] = {
 	&format_attr_event.attr,
+	&format_attr_context.attr,
+	&format_attr_syncmode.attr,
+	&format_attr_syncmax.attr,
+	&format_attr_timestamp.attr,
+	&format_attr_tsmode.attr,
+	&format_attr_tsprescale.attr,
 	NULL,
 };
 
diff --git a/include/linux/gtrace.h b/include/linux/gtrace.h
index 0a017f1f888d..76b1aa9cfe79 100644
--- a/include/linux/gtrace.h
+++ b/include/linux/gtrace.h
@@ -14,6 +14,13 @@
 #include <linux/property.h>
 #include <linux/types.h>
 
+#define GTRACE_PMU_ATTR_CFG_CONTEXT_SHIFT	1
+#define GTRACE_PMU_ATTR_CFG_SYNCMODE_MASK	GENMASK(3, 2)
+#define GTRACE_PMU_ATTR_CFG_SYNCMAX_MASK	GENMASK(7, 4)
+#define GTRACE_PMU_ATTR_CFG_TIMESTAMP_SHIFT	8
+#define GTRACE_PMU_ATTR_CFG_TSMODE_MASK		GENMASK(11, 9)
+#define GTRACE_PMU_ATTR_CFG_TSPRESCALE_MASK	GENMASK(13, 12)
+
 extern struct mutex gtrace_mutex;
 
 /*
-- 
2.34.1


^ permalink raw reply	[flat|nested] 25+ messages in thread

* [PATCH 06/24] gtrace: Replace owner PID with perf_event pointer in gtrace_path
  2026-10-23  3:14 [RFC PATCH 00/24] Add Linux RISC-V trace support via the gtrace framework Zane Leung
                   ` (4 preceding siblings ...)
  2026-10-23  3:14 ` [PATCH 05/24] gtrace: Introduce some PMU format attributes Zane Leung
@ 2026-10-23  3:14 ` Zane Leung
  2026-10-23  3:14 ` [PATCH 07/24] gtrace: Parse RISC-V Trace encoder config from perf event Zane Leung
                   ` (17 subsequent siblings)
  23 siblings, 0 replies; 25+ messages in thread
From: Zane Leung @ 2026-10-23  3:14 UTC (permalink / raw)
  To: gregkh, palmer, pjw, alexander.shishkin, suzuki.poulose,
	mchitale, anup, irogers
  Cc: mike.leach, james.clark, leo.yan, peterz, mingo, namhyung,
	mark.rutland, jolsa, adrian.hunter, alex, atish.patra,
	andrew.jones, sunilvl, liangzhen, zhuangqiubin, coresight,
	linux-riscv, linux-kernel

Replace 'pid_t owner' with 'struct perf_event *event' in struct gtrace_path.
This allows component drivers to directly access the perf_event for PMU
configuration.

Signed-off-by: Zane Leung <liangzhen@linux.spacemit.com>
---
 drivers/hwtracing/gtrace/gtrace-core.c | 12 ++++++++++--
 drivers/hwtracing/gtrace/gtrace-perf.c |  2 +-
 include/linux/gtrace.h                 |  2 +-
 3 files changed, 12 insertions(+), 4 deletions(-)

diff --git a/drivers/hwtracing/gtrace/gtrace-core.c b/drivers/hwtracing/gtrace/gtrace-core.c
index cae0a7fa820f..3f55d8b7302d 100644
--- a/drivers/hwtracing/gtrace/gtrace-core.c
+++ b/drivers/hwtracing/gtrace/gtrace-core.c
@@ -13,6 +13,7 @@
 #include <linux/spinlock.h>
 #include <linux/xarray.h>
 #include <linux/gtrace.h>
+#include <linux/perf_event.h>
 
 #define GTRACE_POLL_TIMEOUT_US_DEFAULT	10
 
@@ -665,10 +666,16 @@ static int __gtrace_comp_start(struct gtrace_component *comp,
 
 	raw_spin_lock_irqsave(&cpriv->lock, flags);
 	if (cpriv->start_count) {
-		if (cpriv->owner != path->owner || cpriv->mode != path->mode) {
+		if (cpriv->mode != path->mode) {
 			ret = -EBUSY;
 			goto out;
 		}
+		if (cpriv->mode == GTRACE_COMPONENT_MODE_PERF) {
+			if (cpriv->owner != task_pid_nr(path->event->owner)) {
+				ret = -EBUSY;
+				goto out;
+			}
+		}
 	} else {
 		if (gtdrv->start) {
 			comp->path = path;
@@ -678,8 +685,9 @@ static int __gtrace_comp_start(struct gtrace_component *comp,
 				goto out;
 			}
 		}
-		cpriv->owner = path->owner;
 		cpriv->mode = path->mode;
+		if (cpriv->mode == GTRACE_COMPONENT_MODE_PERF)
+			cpriv->owner = task_pid_nr(path->event->owner);
 	}
 
 	cpriv->start_count++;
diff --git a/drivers/hwtracing/gtrace/gtrace-perf.c b/drivers/hwtracing/gtrace/gtrace-perf.c
index 703c12b95665..2fcfbae9acf6 100644
--- a/drivers/hwtracing/gtrace/gtrace-perf.c
+++ b/drivers/hwtracing/gtrace/gtrace-perf.c
@@ -137,7 +137,7 @@ static void *gtrace_setup_aux(struct perf_event *event, void **pages,
 			continue;
 		}
 
-		path->owner = task_pid_nr(event->owner);
+		path->event = event;
 		*gtrace_event_cpu_path_ptr(event_data, cpu) = path;
 	}
 
diff --git a/include/linux/gtrace.h b/include/linux/gtrace.h
index 76b1aa9cfe79..3d769b405030 100644
--- a/include/linux/gtrace.h
+++ b/include/linux/gtrace.h
@@ -325,7 +325,7 @@ struct gtrace_path {
 	enum gtrace_component_mode	mode;
 	u32				trace_id;
 #define GTRACE_INVALID_TRACE_ID	0
-	pid_t				owner;
+	struct perf_event		*event;
 };
 
 struct gtrace_component *gtrace_path_source(struct gtrace_path *path);
-- 
2.34.1


^ permalink raw reply	[flat|nested] 25+ messages in thread

* [PATCH 07/24] gtrace: Parse RISC-V Trace encoder config from perf event
  2026-10-23  3:14 [RFC PATCH 00/24] Add Linux RISC-V trace support via the gtrace framework Zane Leung
                   ` (5 preceding siblings ...)
  2026-10-23  3:14 ` [PATCH 06/24] gtrace: Replace owner PID with perf_event pointer in gtrace_path Zane Leung
@ 2026-10-23  3:14 ` Zane Leung
  2026-10-23  3:14 ` [PATCH 08/24] gtrace: add perf symlink support for gtrace components Zane Leung
                   ` (16 subsequent siblings)
  23 siblings, 0 replies; 25+ messages in thread
From: Zane Leung @ 2026-10-23  3:14 UTC (permalink / raw)
  To: gregkh, palmer, pjw, alexander.shishkin, suzuki.poulose,
	mchitale, anup, irogers
  Cc: mike.leach, james.clark, leo.yan, peterz, mingo, namhyung,
	mark.rutland, jolsa, adrian.hunter, alex, atish.patra,
	andrew.jones, sunilvl, liangzhen, zhuangqiubin, coresight,
	linux-riscv, linux-kernel

Parse the hardware trace encoder configurations (timestamp, context,
syncmode, and syncmax) from perf event attributes when operating
in GTRACE_COMPONENT_MODE_PERF.

Signed-off-by: Zane Leung <liangzhen@linux.spacemit.com>
---
 .../hwtracing/gtrace/rvtrace-encoder-core.c   | 20 +++++++++++++++++++
 1 file changed, 20 insertions(+)

diff --git a/drivers/hwtracing/gtrace/rvtrace-encoder-core.c b/drivers/hwtracing/gtrace/rvtrace-encoder-core.c
index 01925ebdfbde..817535ff9558 100644
--- a/drivers/hwtracing/gtrace/rvtrace-encoder-core.c
+++ b/drivers/hwtracing/gtrace/rvtrace-encoder-core.c
@@ -140,13 +140,33 @@ static int rvtrace_encoder_probe(struct gtrace_component *comp)
 	return 0;
 }
 
+static void rvtrace_encoder_parse_event_config(struct rvtrace_encoder_priv *priv,
+					       struct perf_event *event)
+{
+	u64 config = event->attr.config;
+	enum rvtrace_encoder_syncmode syncmode;
+
+	if (priv->has_context)
+		priv->context = config & BIT(GTRACE_PMU_ATTR_CFG_CONTEXT_SHIFT);
+
+	syncmode = FIELD_GET(GTRACE_PMU_ATTR_CFG_SYNCMODE_MASK, config);
+	if (priv->syncmode_hw[syncmode]) {
+		priv->syncmode = syncmode;
+		priv->syncmax = FIELD_GET(GTRACE_PMU_ATTR_CFG_SYNCMAX_MASK, config);
+	}
+}
+
 static int rvtrace_encoder_start(struct gtrace_component *comp)
 {
 	struct rvtrace_encoder_priv *priv = dev_get_drvdata(&comp->dev);
 	struct gtrace_platform_data *pdata = comp->pdata;
+	struct gtrace_path *path = comp->path;
 	int ret;
 	u32 val;
 
+	if (path->mode == GTRACE_COMPONENT_MODE_PERF)
+		rvtrace_encoder_parse_event_config(priv, path->event);
+
 	if (priv->srcbits)
 		rvtrace_encoder_set_srcid(pdata, priv->srcbits);
 
-- 
2.34.1



^ permalink raw reply	[flat|nested] 25+ messages in thread

* [PATCH 08/24] gtrace: add perf symlink support for gtrace components
  2026-10-23  3:14 [RFC PATCH 00/24] Add Linux RISC-V trace support via the gtrace framework Zane Leung
                   ` (6 preceding siblings ...)
  2026-10-23  3:14 ` [PATCH 07/24] gtrace: Parse RISC-V Trace encoder config from perf event Zane Leung
@ 2026-10-23  3:14 ` Zane Leung
  2026-10-23  3:14 ` [PATCH 09/24] gtrace: Add port-level start/stop driver callbacks Zane Leung
                   ` (15 subsequent siblings)
  23 siblings, 0 replies; 25+ messages in thread
From: Zane Leung @ 2026-10-23  3:14 UTC (permalink / raw)
  To: gregkh, palmer, pjw, alexander.shishkin, suzuki.poulose,
	mchitale, anup, irogers
  Cc: mike.leach, james.clark, leo.yan, peterz, mingo, namhyung,
	mark.rutland, jolsa, adrian.hunter, alex, atish.patra,
	andrew.jones, sunilvl, liangzhen, zhuangqiubin, coresight,
	linux-riscv, linux-kernel

Implement gtrace_perf_symlink() to let RISC-V Trace encoder
link themselves to the gtrace PMU directory as "cpuX" based
on their bound CPU.
---
 drivers/hwtracing/gtrace/gtrace-perf.c        | 29 +++++++++++++++++--
 .../hwtracing/gtrace/rvtrace-encoder-core.c   | 10 +++++++
 include/linux/gtrace.h                        |  2 ++
 3 files changed, 39 insertions(+), 2 deletions(-)

diff --git a/drivers/hwtracing/gtrace/gtrace-perf.c b/drivers/hwtracing/gtrace/gtrace-perf.c
index 2fcfbae9acf6..604b673a29d5 100644
--- a/drivers/hwtracing/gtrace/gtrace-perf.c
+++ b/drivers/hwtracing/gtrace/gtrace-perf.c
@@ -23,6 +23,7 @@
 #define GTRACE_PMU_NAME "gtrace"
 static struct pmu gtrace_pmu;
 static struct workqueue_struct *gtrace_wq;
+static bool gtrace_perf_up;
 
 /**
  * struct gtrace_event_data - generic hardware trace perf event data
@@ -351,6 +352,27 @@ static const struct attribute_group *gtrace_pmu_attr_groups[] = {
 	NULL,
 };
 
+int gtrace_perf_symlink(struct gtrace_component *comp, bool link)
+{
+	char entry[sizeof("cpu9999999")];
+	int ret = 0, cpu = comp->pdata->bound_cpu;
+	struct device *pmu_dev = gtrace_pmu.dev;
+	struct device *comp_dev = &comp->dev;
+
+	sprintf(entry, "cpu%d", cpu);
+
+	if (!gtrace_perf_up)
+		return -EPROBE_DEFER;
+
+	if (link)
+		ret = sysfs_create_link(&pmu_dev->kobj, &comp_dev->kobj, entry);
+	else
+		sysfs_remove_link(&pmu_dev->kobj, entry);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(gtrace_perf_symlink);
+
 int __init gtrace_perf_init(void)
 {
 	int ret;
@@ -373,10 +395,13 @@ int __init gtrace_perf_init(void)
 		return -ENOMEM;
 
 	ret = perf_pmu_register(&gtrace_pmu, GTRACE_PMU_NAME, -1);
-	if (ret)
+	if (ret) {
 		destroy_workqueue(gtrace_wq);
+		return ret;
+	}
 
-	return ret;
+	gtrace_perf_up = true;
+	return 0;
 }
 
 void __exit gtrace_perf_exit(void)
diff --git a/drivers/hwtracing/gtrace/rvtrace-encoder-core.c b/drivers/hwtracing/gtrace/rvtrace-encoder-core.c
index 817535ff9558..17a3dde1314c 100644
--- a/drivers/hwtracing/gtrace/rvtrace-encoder-core.c
+++ b/drivers/hwtracing/gtrace/rvtrace-encoder-core.c
@@ -129,6 +129,10 @@ static int rvtrace_encoder_probe(struct gtrace_component *comp)
 	}
 	dev_set_drvdata(&comp->dev, priv);
 
+	ret = gtrace_perf_symlink(comp, true);
+	if (ret)
+		return ret;
+
 	if (boot_enable) {
 		ret = gtrace_enable_sysfs(comp);
 		if (ret) {
@@ -140,6 +144,11 @@ static int rvtrace_encoder_probe(struct gtrace_component *comp)
 	return 0;
 }
 
+static void rvtrace_encoder_remove(struct gtrace_component *comp)
+{
+	gtrace_perf_symlink(comp, false);
+}
+
 static void rvtrace_encoder_parse_event_config(struct rvtrace_encoder_priv *priv,
 					       struct perf_event *event)
 {
@@ -240,6 +249,7 @@ static const struct gtrace_component_id rvtrace_encoder_ids[] = {
 static struct gtrace_driver rvtrace_encoder_driver = {
 	.id_table = rvtrace_encoder_ids,
 	.probe = rvtrace_encoder_probe,
+	.remove = rvtrace_encoder_remove,
 	.start = rvtrace_encoder_start,
 	.stop = rvtrace_encoder_stop,
 	.driver = {
diff --git a/include/linux/gtrace.h b/include/linux/gtrace.h
index 3d769b405030..4bbe5ac835ec 100644
--- a/include/linux/gtrace.h
+++ b/include/linux/gtrace.h
@@ -437,4 +437,6 @@ int gtrace_disable_sysfs(struct gtrace_component *comp);
 int gtrace_sysfs_add(struct gtrace_component *comp);
 void gtrace_sysfs_remove(struct gtrace_component *comp);
 
+int gtrace_perf_symlink(struct gtrace_component *comp, bool link);
+
 #endif /* __LINUX_GTRACE_H__ */
-- 
2.34.1


^ permalink raw reply	[flat|nested] 25+ messages in thread

* [PATCH 09/24] gtrace: Add port-level start/stop driver callbacks
  2026-10-23  3:14 [RFC PATCH 00/24] Add Linux RISC-V trace support via the gtrace framework Zane Leung
                   ` (7 preceding siblings ...)
  2026-10-23  3:14 ` [PATCH 08/24] gtrace: add perf symlink support for gtrace components Zane Leung
@ 2026-10-23  3:14 ` Zane Leung
  2026-10-23  3:14 ` [PATCH 10/24] gtrace: Add RISC-V trace funnel driver Zane Leung
                   ` (14 subsequent siblings)
  23 siblings, 0 replies; 25+ messages in thread
From: Zane Leung @ 2026-10-23  3:14 UTC (permalink / raw)
  To: gregkh, palmer, pjw, alexander.shishkin, suzuki.poulose,
	mchitale, anup, irogers
  Cc: mike.leach, james.clark, leo.yan, peterz, mingo, namhyung,
	mark.rutland, jolsa, adrian.hunter, alex, atish.patra,
	andrew.jones, sunilvl, liangzhen, zhuangqiubin, coresight,
	linux-riscv, linux-kernel

Add start_port() and stop_port() callbacks to gtrace_driver so
multi-port link components like funnel and replicator can enable a
specific input/output port the first time a path uses it, tracked by
the per-connection src_refcnt/dest_refcnt fields.

Signed-off-by: Zane Leung <liangzhen@linux.spacemit.com>
---
 drivers/hwtracing/gtrace/gtrace-core.c | 66 ++++++++++++++++++++------
 include/linux/gtrace.h                 | 12 +++++
 2 files changed, 64 insertions(+), 14 deletions(-)

diff --git a/drivers/hwtracing/gtrace/gtrace-core.c b/drivers/hwtracing/gtrace/gtrace-core.c
index 3f55d8b7302d..231d06218d8e 100644
--- a/drivers/hwtracing/gtrace/gtrace-core.c
+++ b/drivers/hwtracing/gtrace/gtrace-core.c
@@ -654,7 +654,9 @@ static void gtrace_release_path_nodes(struct gtrace_path *path)
 }
 
 static int __gtrace_comp_start(struct gtrace_component *comp,
-			       struct gtrace_path *path)
+			       struct gtrace_path *path,
+			       struct gtrace_connection *inconn,
+			       struct gtrace_connection *outconn)
 {
 	struct gtrace_comp_priv *cpriv = to_gtrace_comp_priv(comp);
 	const struct gtrace_driver *gtdrv = to_gtrace_driver(comp->dev.driver);
@@ -690,6 +692,12 @@ static int __gtrace_comp_start(struct gtrace_component *comp,
 			cpriv->owner = task_pid_nr(path->event->owner);
 	}
 
+	if (gtdrv->start_port) {
+		ret = gtdrv->start_port(comp, inconn, outconn);
+		if (ret)
+			goto out;
+	}
+
 	cpriv->start_count++;
 out:
 	raw_spin_unlock_irqrestore(&cpriv->lock, flags);
@@ -697,7 +705,9 @@ static int __gtrace_comp_start(struct gtrace_component *comp,
 }
 
 static int __gtrace_comp_stop(struct gtrace_component *comp,
-			      struct gtrace_path *path)
+			      struct gtrace_path *path,
+			      struct gtrace_connection *inconn,
+			      struct gtrace_connection *outconn)
 {
 	struct gtrace_comp_priv *cpriv = to_gtrace_comp_priv(comp);
 	const struct gtrace_driver *gtdrv = to_gtrace_driver(comp->dev.driver);
@@ -715,13 +725,16 @@ static int __gtrace_comp_stop(struct gtrace_component *comp,
 		goto out;
 	}
 
+	if (!gtdrv) {
+		ret = -ENODEV;
+		goto out;
+	}
+
+	if (gtdrv->stop_port)
+		gtdrv->stop_port(comp, inconn, outconn);
+
 	cpriv->start_count--;
 	if (!cpriv->start_count) {
-		if (!gtdrv) {
-			ret = -ENODEV;
-			goto out;
-		}
-
 		if (gtdrv->stop)
 			ret = gtdrv->stop(comp);
 		comp->path = NULL;
@@ -734,19 +747,36 @@ static int __gtrace_comp_stop(struct gtrace_component *comp,
 
 int gtrace_path_start(struct gtrace_path *path)
 {
-	struct gtrace_path_node *node;
+	struct gtrace_path_node *node, *prev;
+	struct gtrace_connection *inconn;
 	int ret = 0;
 
 	list_for_each_entry_reverse(node, &path->comp_list, head) {
-		ret = __gtrace_comp_start(node->comp, path);
+		if (node->comp == gtrace_path_source(path)) {
+			inconn = NULL;
+		} else {
+			prev = list_prev_entry(node, head);
+			inconn = prev->conn;
+		}
+
+		ret = __gtrace_comp_start(node->comp, path, inconn, node->conn);
 		if (ret)
 			break;
 	}
 
 	/* If a component failed to start, stop all the components that were started before it */
-	if (ret)
-		list_for_each_entry_continue(node, &path->comp_list, head)
-			__gtrace_comp_stop(node->comp, path);
+	if (ret) {
+		list_for_each_entry_continue(node, &path->comp_list, head) {
+			if (node->comp == gtrace_path_source(path)) {
+				inconn = NULL;
+			} else {
+				prev = list_prev_entry(node, head);
+				inconn = prev->conn;
+			}
+
+			__gtrace_comp_stop(node->comp, path, inconn, node->conn);
+		}
+	}
 
 	return ret;
 }
@@ -754,11 +784,19 @@ EXPORT_SYMBOL_GPL(gtrace_path_start);
 
 int gtrace_path_stop(struct gtrace_path *path)
 {
-	struct gtrace_path_node *node;
+	struct gtrace_path_node *node, *prev;
+	struct gtrace_connection *inconn;
 	int ret = 0, err;
 
 	list_for_each_entry(node, &path->comp_list, head) {
-		err = __gtrace_comp_stop(node->comp, path);
+		if (node->comp == gtrace_path_source(path)) {
+			inconn = NULL;
+		} else {
+			prev = list_prev_entry(node, head);
+			inconn = prev->conn;
+		}
+
+		err = __gtrace_comp_stop(node->comp, path, inconn, node->conn);
 		if (err && !ret)
 			ret = err;
 	}
diff --git a/include/linux/gtrace.h b/include/linux/gtrace.h
index 4bbe5ac835ec..ee60585ef65c 100644
--- a/include/linux/gtrace.h
+++ b/include/linux/gtrace.h
@@ -55,9 +55,11 @@ enum gtrace_format {
  * @src_port:    A connection's source port number.
  * @src_fwnode:  Source component's fwnode handle.
  * @src_comp:    Source component's pointer.
+ * @src_refcnt:  Number of paths currently using this connection's source port.
  * @dest_port:   A connection's destination port number.
  * @dest_fwnode: Destination component's fwnode handle.
  * @dest_comp:   Destination component's pointer.
+ * @dest_refcnt: Number of paths currently routing data into the destination port.
  */
 struct gtrace_connection {
 	int src_port;
@@ -66,6 +68,8 @@ struct gtrace_connection {
 	struct fwnode_handle *dest_fwnode;
 	struct gtrace_component *src_comp;
 	struct gtrace_component *dest_comp;
+	u32 src_refcnt;
+	u32 dest_refcnt;
 };
 
 struct gtrace_platform_data;
@@ -363,6 +367,8 @@ int gtrace_path_copyto_auxbuf(struct gtrace_path *path,
  *                 via @format (see PERF_AUX_FLAG_PMU_FORMAT_TYPE_MASK).
  * @start:         Callback to start tracing.
  * @stop:          Callback to stop tracing.
+ * @start_port:    Callback to start a specific component port.
+ * @stop_port:     Callback to stop a specific component port.
  * @probe:         Driver probe() function.
  * @remove:        Driver remove() function.
  * @get_trace_id:  Get/allocate a trace ID.
@@ -376,6 +382,12 @@ struct gtrace_driver {
 						 u64 *format);
 	int			(*start)(struct gtrace_component *comp);
 	int			(*stop)(struct gtrace_component *comp);
+	int			(*start_port)(struct gtrace_component *comp,
+					      struct gtrace_connection *inconn,
+					      struct gtrace_connection *outconn);
+	void			(*stop_port)(struct gtrace_component *comp,
+					     struct gtrace_connection *inconn,
+					     struct gtrace_connection *outconn);
 	int			(*probe)(struct gtrace_component *comp);
 	void			(*remove)(struct gtrace_component *comp);
 	int			(*get_trace_id)(struct gtrace_component *comp,
-- 
2.34.1


^ permalink raw reply	[flat|nested] 25+ messages in thread

* [PATCH 10/24] gtrace: Add RISC-V trace funnel driver
  2026-10-23  3:14 [RFC PATCH 00/24] Add Linux RISC-V trace support via the gtrace framework Zane Leung
                   ` (8 preceding siblings ...)
  2026-10-23  3:14 ` [PATCH 09/24] gtrace: Add port-level start/stop driver callbacks Zane Leung
@ 2026-10-23  3:14 ` Zane Leung
  2026-10-23  3:14 ` [PATCH 11/24] gtrace: Add RISC-V trace timestamp support Zane Leung
                   ` (13 subsequent siblings)
  23 siblings, 0 replies; 25+ messages in thread
From: Zane Leung @ 2026-10-23  3:14 UTC (permalink / raw)
  To: gregkh, palmer, pjw, alexander.shishkin, suzuki.poulose,
	mchitale, anup, irogers
  Cc: mike.leach, james.clark, leo.yan, peterz, mingo, namhyung,
	mark.rutland, jolsa, adrian.hunter, alex, atish.patra,
	andrew.jones, sunilvl, liangzhen, zhuangqiubin, coresight,
	linux-riscv, linux-kernel

Add initial implementation of RISC-V Trace funnel driver. The funnel
is defined in the RISC-V Trace Control Interface specification.

Signed-off-by: Zane Leung <liangzhen@linux.spacemit.com>
---
 drivers/hwtracing/gtrace/Kconfig            |  14 ++
 drivers/hwtracing/gtrace/Makefile           |   1 +
 drivers/hwtracing/gtrace/rvtrace-funnel.c   | 165 ++++++++++++++++++++
 drivers/hwtracing/gtrace/rvtrace-funnel.h   |  21 +++
 drivers/hwtracing/gtrace/rvtrace-platform.c |   3 +
 drivers/hwtracing/gtrace/rvtrace.h          |   1 +
 6 files changed, 205 insertions(+)
 create mode 100644 drivers/hwtracing/gtrace/rvtrace-funnel.c
 create mode 100644 drivers/hwtracing/gtrace/rvtrace-funnel.h

diff --git a/drivers/hwtracing/gtrace/Kconfig b/drivers/hwtracing/gtrace/Kconfig
index e5b62d65e2fd..f6026965c54d 100644
--- a/drivers/hwtracing/gtrace/Kconfig
+++ b/drivers/hwtracing/gtrace/Kconfig
@@ -57,3 +57,17 @@ config RVTRACE_RAMSINK
 
 	  To compile this driver as a module, choose M here: the module
 	  will be called rvtrace-ramsink.
+
+config RVTRACE_FUNNEL
+	tristate "RISC-V Trace Funnel driver"
+	depends on RVTRACE
+	default y
+	help
+	  This driver provides support for the RISC-V trace funnel component
+	  which is defined by the RISC-V Trace Control Interface specification.
+	  The funnel merges trace data from multiple input ports into a single
+	  output stream. It is a link component in the trace path between
+	  sources and sinks.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called rvtrace-funnel.
diff --git a/drivers/hwtracing/gtrace/Makefile b/drivers/hwtracing/gtrace/Makefile
index 87cb5add78d4..9c99d917b9f4 100644
--- a/drivers/hwtracing/gtrace/Makefile
+++ b/drivers/hwtracing/gtrace/Makefile
@@ -8,3 +8,4 @@ obj-$(CONFIG_RVTRACE) += rvtrace-platform.o
 obj-$(CONFIG_RVTRACE_ENCODER) += rvtrace-encoder.o
 rvtrace-encoder-y := rvtrace-encoder-core.o rvtrace-encoder-sysfs.o
 obj-$(CONFIG_RVTRACE_RAMSINK) += rvtrace-ramsink.o
+obj-$(CONFIG_RVTRACE_FUNNEL) += rvtrace-funnel.o
diff --git a/drivers/hwtracing/gtrace/rvtrace-funnel.c b/drivers/hwtracing/gtrace/rvtrace-funnel.c
new file mode 100644
index 000000000000..941cd3360395
--- /dev/null
+++ b/drivers/hwtracing/gtrace/rvtrace-funnel.c
@@ -0,0 +1,165 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2026 Spacemit Technologies, Inc.
+ */
+
+#include <linux/device.h>
+#include <linux/gtrace.h>
+#include <linux/types.h>
+#include "rvtrace.h"
+#include "rvtrace-funnel.h"
+
+static int rvtrace_funnel_start(struct gtrace_component *comp)
+{
+	int ret;
+
+	ret = gtrace_enable_component(comp);
+	if (ret)
+		dev_err(&comp->dev, "failed to start rvtrace funnel.\n");
+
+	return ret;
+}
+
+static int rvtrace_funnel_stop(struct gtrace_component *comp)
+{
+	struct rvtrace_funnel_priv *priv = dev_get_drvdata(&comp->dev);
+	int ret;
+
+	ret = gtrace_disable_component(comp);
+	if (ret) {
+		dev_err(&comp->dev, "failed to stop rvtrace funnel.\n");
+		return ret;
+	}
+
+	if (priv->disinput_mask)
+		gtrace_write32(comp->pdata, RVTRACE_FUNNEL_DISINPUT_MASK,
+				RVTRACE_FUNNEL_DISINPUT_OFFSET);
+
+	return 0;
+}
+
+static int _rvtrace_funnel_start_port(struct gtrace_component *comp, int port)
+{
+	struct gtrace_platform_data *pdata = comp->pdata;
+	struct rvtrace_funnel_priv *priv = dev_get_drvdata(&comp->dev);
+	u32 disinput;
+	int ret;
+
+	if (!(priv->disinput_mask & BIT(port)))
+		return 0;
+
+	disinput = gtrace_read32(pdata, RVTRACE_FUNNEL_DISINPUT_OFFSET);
+	disinput &= ~BIT(port);
+	gtrace_write32(pdata, disinput, RVTRACE_FUNNEL_DISINPUT_OFFSET);
+	ret = gtrace_poll_bit(pdata, RVTRACE_FUNNEL_DISINPUT_OFFSET,
+			      port, 0, comp->pdata->control_poll_timeout_usecs);
+	if (ret)
+		dev_err(&comp->dev, "failed to enable rvtrace funnel inport %d.\n", port);
+
+	return ret;
+}
+
+static int rvtrace_funnel_start_port(struct gtrace_component *comp,
+				     struct gtrace_connection *in,
+				     struct gtrace_connection *out)
+{
+	int ret = 0;
+
+	if (in->dest_refcnt == 0)
+		ret = _rvtrace_funnel_start_port(comp, in->dest_port);
+	if (!ret)
+		in->dest_refcnt++;
+
+	return ret;
+}
+
+static void rvtrace_funnel_stop_port(struct gtrace_component *comp,
+				     struct gtrace_connection *in,
+				     struct gtrace_connection *out)
+{
+	/*
+	 * Setting trFunnelEnable to 0 flushes any queued trace data to output.
+	 * Due to this flush behavior of the RISC-V trace funnel during component
+	 * disablement, we do not disable individual ports here. Instead, we
+	 * defer it and disable all ports at once when the entire component is
+	 * stopped. For now, we only need to decrement the reference count.
+	 */
+	--in->dest_refcnt;
+}
+
+static ssize_t disinput_show(struct device *dev,
+                            struct device_attribute *attr, char *buf)
+{
+	u32 val;
+	struct gtrace_component *comp = to_gtrace_component(dev);
+
+	val = gtrace_read32(comp->pdata, RVTRACE_FUNNEL_DISINPUT_OFFSET);
+
+	return scnprintf(buf, PAGE_SIZE, "0x%x\n", val);
+}
+static DEVICE_ATTR_RO(disinput);
+
+static struct attribute *rvtrace_funnel_attrs[] = {
+	&dev_attr_disinput.attr,
+	NULL,
+};
+
+static struct attribute_group rvtrace_funnel_group = {
+	.attrs = rvtrace_funnel_attrs,
+};
+
+const struct attribute_group *rvtrace_funnel_groups[] = {
+	&rvtrace_funnel_group,
+	NULL,
+};
+EXPORT_SYMBOL_GPL(rvtrace_funnel_groups);
+
+static int rvtrace_funnel_probe(struct gtrace_component *comp)
+{
+	struct gtrace_platform_data *pdata = comp->pdata;
+	struct rvtrace_funnel_priv *priv;
+
+	priv = devm_kzalloc(&comp->dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	gtrace_write32(pdata, RVTRACE_FUNNEL_DISINPUT_MASK, RVTRACE_FUNNEL_DISINPUT_OFFSET);
+	priv->disinput_mask = gtrace_read32(pdata, RVTRACE_FUNNEL_DISINPUT_OFFSET);
+	dev_set_drvdata(&comp->dev, priv);
+	return 0;
+}
+
+static const struct gtrace_component_id rvtrace_funnel_ids[] = {
+	{ .type = GTRACE_RVTRACE_FUNNEL,
+	  .version = rvtrace_component_mkversion(1, 0), },
+	{},
+};
+
+static struct gtrace_driver rvtrace_funnel_driver = {
+	.id_table = rvtrace_funnel_ids,
+	.start = rvtrace_funnel_start,
+	.stop = rvtrace_funnel_stop,
+	.start_port = rvtrace_funnel_start_port,
+	.stop_port = rvtrace_funnel_stop_port,
+	.probe = rvtrace_funnel_probe,
+	.driver = {
+		.name = "rvtrace-funnel",
+	},
+};
+
+static int __init rvtrace_funnel_init(void)
+{
+	return gtrace_register_driver(&rvtrace_funnel_driver);
+}
+
+static void __exit rvtrace_funnel_exit(void)
+{
+	gtrace_unregister_driver(&rvtrace_funnel_driver);
+}
+
+module_init(rvtrace_funnel_init);
+module_exit(rvtrace_funnel_exit);
+
+MODULE_AUTHOR("Zane Leung");
+MODULE_DESCRIPTION("RISC-V Trace Funnel Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hwtracing/gtrace/rvtrace-funnel.h b/drivers/hwtracing/gtrace/rvtrace-funnel.h
new file mode 100644
index 000000000000..41547c8971ba
--- /dev/null
+++ b/drivers/hwtracing/gtrace/rvtrace-funnel.h
@@ -0,0 +1,21 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2026 Spacemit Technologies, Inc.
+ */
+
+#ifndef __RVTRACE_HENNEL_H__
+#define __RVTRACE_HENNEL_H__
+
+#define RVTRACE_FUNNEL_DISINPUT_OFFSET	0x008
+#define RVTRACE_FUNNEL_DISINPUT_MASK	0xffff
+
+/**
+ * struct rvtrace_encoder_priv - Private driver data for the rvtrace funnel
+ * @disinput_mask: Bitmask of inputs supporting disable.
+ */
+
+struct rvtrace_funnel_priv {
+	u32 disinput_mask;
+};
+
+#endif /* __RVTRACE_ENCODER_H__ */
diff --git a/drivers/hwtracing/gtrace/rvtrace-platform.c b/drivers/hwtracing/gtrace/rvtrace-platform.c
index 64496a638359..ad8daa06e13e 100644
--- a/drivers/hwtracing/gtrace/rvtrace-platform.c
+++ b/drivers/hwtracing/gtrace/rvtrace-platform.c
@@ -133,6 +133,9 @@ static int rvtrace_platform_probe(struct platform_device *pdev)
 	case GTRACE_RVTRACE_ENCODER:
 		pdata->groups = rvtrace_encoder_groups;
 		break;
+	case GTRACE_RVTRACE_FUNNEL:
+		pdata->groups = rvtrace_funnel_groups;
+		break;
 	default:
 		pdata->groups = NULL;
 	}
diff --git a/drivers/hwtracing/gtrace/rvtrace.h b/drivers/hwtracing/gtrace/rvtrace.h
index fee8e2e4f42a..a345ba110267 100644
--- a/drivers/hwtracing/gtrace/rvtrace.h
+++ b/drivers/hwtracing/gtrace/rvtrace.h
@@ -120,5 +120,6 @@ extern const struct gtrace_hw_ops rvtrace_hw_ops;
 
 /* Sysfs attribute groups for RISC-V trace components */
 extern const struct attribute_group *rvtrace_encoder_groups[];
+extern const struct attribute_group *rvtrace_funnel_groups[];
 
 #endif /* __LINUX_RVTRACE_H__ */
-- 
2.34.1


^ permalink raw reply	[flat|nested] 25+ messages in thread

* [PATCH 11/24] gtrace: Add RISC-V trace timestamp support
  2026-10-23  3:14 [RFC PATCH 00/24] Add Linux RISC-V trace support via the gtrace framework Zane Leung
                   ` (9 preceding siblings ...)
  2026-10-23  3:14 ` [PATCH 10/24] gtrace: Add RISC-V trace funnel driver Zane Leung
@ 2026-10-23  3:14 ` Zane Leung
  2026-10-23  3:14 ` [PATCH 12/24] gtrace: Add RISC-V trace ATB bridge driver Zane Leung
                   ` (12 subsequent siblings)
  23 siblings, 0 replies; 25+ messages in thread
From: Zane Leung @ 2026-10-23  3:14 UTC (permalink / raw)
  To: gregkh, palmer, pjw, alexander.shishkin, suzuki.poulose,
	mchitale, anup, irogers
  Cc: mike.leach, james.clark, leo.yan, peterz, mingo, namhyung,
	mark.rutland, jolsa, adrian.hunter, alex, atish.patra,
	andrew.jones, sunilvl, liangzhen, zhuangqiubin, coresight,
	linux-riscv, linux-kernel

Add a shared timestamp component for the RISC-V trace encoder and
funnel.

Signed-off-by: Zane Leung <liangzhen@linux.spacemit.com>
---
 drivers/hwtracing/gtrace/Makefile             |   3 +-
 .../hwtracing/gtrace/rvtrace-encoder-core.c   |  16 +-
 .../hwtracing/gtrace/rvtrace-encoder-sysfs.c  |  33 ++
 drivers/hwtracing/gtrace/rvtrace-encoder.h    |   8 +
 drivers/hwtracing/gtrace/rvtrace-funnel.c     |  55 +++
 drivers/hwtracing/gtrace/rvtrace-funnel.h     |   8 +
 drivers/hwtracing/gtrace/rvtrace-timestamp.c  | 336 ++++++++++++++++++
 drivers/hwtracing/gtrace/rvtrace-timestamp.h  |  44 +++
 8 files changed, 500 insertions(+), 3 deletions(-)
 create mode 100644 drivers/hwtracing/gtrace/rvtrace-timestamp.c
 create mode 100644 drivers/hwtracing/gtrace/rvtrace-timestamp.h

diff --git a/drivers/hwtracing/gtrace/Makefile b/drivers/hwtracing/gtrace/Makefile
index 9c99d917b9f4..b60d3cfe5248 100644
--- a/drivers/hwtracing/gtrace/Makefile
+++ b/drivers/hwtracing/gtrace/Makefile
@@ -4,7 +4,8 @@ obj-$(CONFIG_GTRACE) += gtrace.o
 gtrace-y := gtrace-core.o gtrace-perf.o gtrace-sysfs.o
 gtrace-$(CONFIG_OF) += gtrace-of.o
 
-obj-$(CONFIG_RVTRACE) += rvtrace-platform.o
+obj-$(CONFIG_RVTRACE) += rvtrace.o
+rvtrace-y := rvtrace-platform.o rvtrace-timestamp.o
 obj-$(CONFIG_RVTRACE_ENCODER) += rvtrace-encoder.o
 rvtrace-encoder-y := rvtrace-encoder-core.o rvtrace-encoder-sysfs.o
 obj-$(CONFIG_RVTRACE_RAMSINK) += rvtrace-ramsink.o
diff --git a/drivers/hwtracing/gtrace/rvtrace-encoder-core.c b/drivers/hwtracing/gtrace/rvtrace-encoder-core.c
index 17a3dde1314c..c0541fee6b93 100644
--- a/drivers/hwtracing/gtrace/rvtrace-encoder-core.c
+++ b/drivers/hwtracing/gtrace/rvtrace-encoder-core.c
@@ -127,6 +127,10 @@ static int rvtrace_encoder_probe(struct gtrace_component *comp)
 		val |= BIT(RVTRACE_COMPONENT_CTRL_INHIBITSRC_SHIFT);
 		gtrace_write32(pdata, val, RVTRACE_COMPONENT_CTRL_OFFSET);
 	}
+
+	priv->has_timestamp = rvtrace_timestamp_reset(pdata) == 0;
+	if (priv->has_timestamp)
+		rvtrace_timestamp_init(pdata, &priv->ts_priv);
 	dev_set_drvdata(&comp->dev, priv);
 
 	ret = gtrace_perf_symlink(comp, true);
@@ -155,6 +159,8 @@ static void rvtrace_encoder_parse_event_config(struct rvtrace_encoder_priv *priv
 	u64 config = event->attr.config;
 	enum rvtrace_encoder_syncmode syncmode;
 
+	if (priv->has_timestamp)
+		priv->ts_ctrl = config & BIT(GTRACE_PMU_ATTR_CFG_TIMESTAMP_SHIFT);
 	if (priv->has_context)
 		priv->context = config & BIT(GTRACE_PMU_ATTR_CFG_CONTEXT_SHIFT);
 
@@ -170,11 +176,17 @@ static int rvtrace_encoder_start(struct gtrace_component *comp)
 	struct rvtrace_encoder_priv *priv = dev_get_drvdata(&comp->dev);
 	struct gtrace_platform_data *pdata = comp->pdata;
 	struct gtrace_path *path = comp->path;
+	struct perf_event *event = NULL;
 	int ret;
 	u32 val;
 
-	if (path->mode == GTRACE_COMPONENT_MODE_PERF)
-		rvtrace_encoder_parse_event_config(priv, path->event);
+	if (path->mode == GTRACE_COMPONENT_MODE_PERF) {
+		event = path->event;
+		rvtrace_encoder_parse_event_config(priv, event);
+	}
+
+	if (priv->has_timestamp)
+		rvtrace_timestamp_setup(pdata, &priv->ts_priv, event, priv->ts_ctrl);
 
 	if (priv->srcbits)
 		rvtrace_encoder_set_srcid(pdata, priv->srcbits);
diff --git a/drivers/hwtracing/gtrace/rvtrace-encoder-sysfs.c b/drivers/hwtracing/gtrace/rvtrace-encoder-sysfs.c
index e973d0e43036..638759be2987 100644
--- a/drivers/hwtracing/gtrace/rvtrace-encoder-sysfs.c
+++ b/drivers/hwtracing/gtrace/rvtrace-encoder-sysfs.c
@@ -84,8 +84,40 @@ static ssize_t cpu_show(struct device *dev,
 }
 static DEVICE_ATTR_RO(cpu);
 
+static ssize_t ts_ctrl_show(struct device *dev,
+			    struct device_attribute *attr, char *buf)
+{
+	struct gtrace_component *comp = to_gtrace_component(dev);
+	struct rvtrace_encoder_priv *priv = dev_get_drvdata(&comp->dev);
+
+	return sysfs_emit(buf, "%u\n", priv->ts_ctrl);
+}
+
+static ssize_t ts_ctrl_store(struct device *dev,
+			     struct device_attribute *attr,
+			     const char *buf, size_t size)
+{
+	int ret;
+	unsigned long val;
+	struct gtrace_component *comp = to_gtrace_component(dev);
+	struct rvtrace_encoder_priv *priv = dev_get_drvdata(&comp->dev);
+
+	if (!priv->has_timestamp)
+		return -EINVAL;
+
+	ret = kstrtoul(buf, 10, &val);
+	if (ret)
+		return ret;
+
+	priv->ts_ctrl = !!val;
+
+	return size;
+}
+static DEVICE_ATTR_RW(ts_ctrl);
+
 static struct attribute *rvtrace_encoder_attrs[] = {
 	&dev_attr_cpu.attr,
+	&dev_attr_ts_ctrl.attr,
 	NULL,
 };
 
@@ -285,6 +317,7 @@ const struct attribute_group *rvtrace_encoder_groups[] = {
 	&rvtrace_encoder_mgmt_group,
 	&rvtrace_encoder_control_group,
 	&rvtrace_encoder_features_group,
+	&rvtrace_timestamp_group,
 	NULL,
 };
 EXPORT_SYMBOL_GPL(rvtrace_encoder_groups);
diff --git a/drivers/hwtracing/gtrace/rvtrace-encoder.h b/drivers/hwtracing/gtrace/rvtrace-encoder.h
index 733d44c0e641..579567dabb3a 100644
--- a/drivers/hwtracing/gtrace/rvtrace-encoder.h
+++ b/drivers/hwtracing/gtrace/rvtrace-encoder.h
@@ -3,6 +3,8 @@
 #ifndef __RVTRACE_ENCODER_H__
 #define __RVTRACE_ENCODER_H__
 
+#include "rvtrace-timestamp.h"
+
 #define RVTRACE_COMPONENT_CTRL_ITRACE_SHIFT     2
 #define RVTRACE_COMPONENT_CTRL_INSTMODE_MASK	GENMASK(6, 4)
 #define RVTRACE_COMPONENT_CTRL_INSTMODE_OPIT	0x6
@@ -38,6 +40,9 @@ enum rvtrace_encoder_syncmode {
  * @has_context: Hardware flag for scontext/mcontext and privilege tracking.
  * @context:     Current configuration for scontext/mcontext and privilege tracking.
  * @boot_enable: Flag to automatically enable tracing on boot.
+ * @has_timestamp: Hardware flag indicating if timestamp generation is supported.
+ * @ts_ctrl:       Current configuration state of timestamp control.
+ * @ts_priv:       Private configuration data specific to the timestamp unit.
  */
 struct rvtrace_encoder_priv {
 	bool syncmode_hw[RVTRACE_ENCODER_SYNCMODE_MAX];
@@ -48,6 +53,9 @@ struct rvtrace_encoder_priv {
 	bool has_context;
 	bool context;
 	bool boot_enable;
+	bool has_timestamp;
+	bool ts_ctrl;
+	struct rvtrace_timestamp_priv ts_priv;
 };
 
 #endif/* __RVTRACE_ENCODER_H__ */
diff --git a/drivers/hwtracing/gtrace/rvtrace-funnel.c b/drivers/hwtracing/gtrace/rvtrace-funnel.c
index 941cd3360395..c4b3396568f7 100644
--- a/drivers/hwtracing/gtrace/rvtrace-funnel.c
+++ b/drivers/hwtracing/gtrace/rvtrace-funnel.c
@@ -9,10 +9,28 @@
 #include "rvtrace.h"
 #include "rvtrace-funnel.h"
 
+static void rvtrace_funnel_parse_event_config(struct rvtrace_funnel_priv *priv,
+					      struct perf_event *event)
+{
+	u64 config = event->attr.config;
+
+	priv->ts_ctrl = config & BIT(GTRACE_PMU_ATTR_CFG_TIMESTAMP_SHIFT);
+}
+
 static int rvtrace_funnel_start(struct gtrace_component *comp)
 {
+	struct gtrace_path *path = comp->path;
+	struct rvtrace_funnel_priv *priv = dev_get_drvdata(&comp->dev);
+	struct perf_event *event = NULL;
 	int ret;
 
+	if (path->mode == GTRACE_COMPONENT_MODE_PERF) {
+		event = path->event;
+		rvtrace_funnel_parse_event_config(priv, event);
+	}
+	if (priv->has_timestamp)
+		rvtrace_timestamp_setup(comp->pdata, &priv->ts_priv, event, priv->ts_ctrl);
+
 	ret = gtrace_enable_component(comp);
 	if (ret)
 		dev_err(&comp->dev, "failed to start rvtrace funnel.\n");
@@ -99,8 +117,40 @@ static ssize_t disinput_show(struct device *dev,
 }
 static DEVICE_ATTR_RO(disinput);
 
+static ssize_t ts_ctrl_show(struct device *dev,
+			    struct device_attribute *attr, char *buf)
+{
+	struct gtrace_component *comp = to_gtrace_component(dev);
+	struct rvtrace_funnel_priv *priv = dev_get_drvdata(&comp->dev);
+
+	return sysfs_emit(buf, "%u\n", priv->ts_ctrl);
+}
+
+static ssize_t ts_ctrl_store(struct device *dev,
+			     struct device_attribute *attr,
+			     const char *buf, size_t size)
+{
+	int ret;
+	unsigned long val;
+	struct gtrace_component *comp = to_gtrace_component(dev);
+	struct rvtrace_funnel_priv *priv = dev_get_drvdata(&comp->dev);
+
+	if (!priv->has_timestamp)
+		return -EINVAL;
+
+	ret = kstrtoul(buf, 10, &val);
+	if (ret)
+		return ret;
+
+	priv->ts_ctrl = !!val;
+
+	return size;
+}
+static DEVICE_ATTR_RW(ts_ctrl);
+
 static struct attribute *rvtrace_funnel_attrs[] = {
 	&dev_attr_disinput.attr,
+	&dev_attr_ts_ctrl.attr,
 	NULL,
 };
 
@@ -110,6 +160,7 @@ static struct attribute_group rvtrace_funnel_group = {
 
 const struct attribute_group *rvtrace_funnel_groups[] = {
 	&rvtrace_funnel_group,
+	&rvtrace_timestamp_group,
 	NULL,
 };
 EXPORT_SYMBOL_GPL(rvtrace_funnel_groups);
@@ -123,6 +174,10 @@ static int rvtrace_funnel_probe(struct gtrace_component *comp)
 	if (!priv)
 		return -ENOMEM;
 
+	priv->has_timestamp = rvtrace_timestamp_reset(pdata) == 0;
+	if (priv->has_timestamp)
+		rvtrace_timestamp_init(pdata, &priv->ts_priv);
+
 	gtrace_write32(pdata, RVTRACE_FUNNEL_DISINPUT_MASK, RVTRACE_FUNNEL_DISINPUT_OFFSET);
 	priv->disinput_mask = gtrace_read32(pdata, RVTRACE_FUNNEL_DISINPUT_OFFSET);
 	dev_set_drvdata(&comp->dev, priv);
diff --git a/drivers/hwtracing/gtrace/rvtrace-funnel.h b/drivers/hwtracing/gtrace/rvtrace-funnel.h
index 41547c8971ba..dddab5db8bef 100644
--- a/drivers/hwtracing/gtrace/rvtrace-funnel.h
+++ b/drivers/hwtracing/gtrace/rvtrace-funnel.h
@@ -6,16 +6,24 @@
 #ifndef __RVTRACE_HENNEL_H__
 #define __RVTRACE_HENNEL_H__
 
+#include "rvtrace-timestamp.h"
+
 #define RVTRACE_FUNNEL_DISINPUT_OFFSET	0x008
 #define RVTRACE_FUNNEL_DISINPUT_MASK	0xffff
 
 /**
  * struct rvtrace_encoder_priv - Private driver data for the rvtrace funnel
  * @disinput_mask: Bitmask of inputs supporting disable.
+ * @has_timestamp: Hardware flag indicating if timestamp generation is supported.
+ * @ts_ctrl:       Current configuration state of timestamp control.
+ * @ts_priv:       Private configuration data specific to the timestamp unit.
  */
 
 struct rvtrace_funnel_priv {
 	u32 disinput_mask;
+	bool has_timestamp;
+	bool ts_ctrl;
+	struct rvtrace_timestamp_priv ts_priv;
 };
 
 #endif /* __RVTRACE_ENCODER_H__ */
diff --git a/drivers/hwtracing/gtrace/rvtrace-timestamp.c b/drivers/hwtracing/gtrace/rvtrace-timestamp.c
new file mode 100644
index 000000000000..0ec7436add16
--- /dev/null
+++ b/drivers/hwtracing/gtrace/rvtrace-timestamp.c
@@ -0,0 +1,336 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright(C) 2026 Spacemit Technologies, Inc.
+ */
+
+#include <linux/device.h>
+#include <linux/gtrace.h>
+#include <linux/bitfield.h>
+#include "rvtrace.h"
+#include "rvtrace-timestamp.h"
+#include "rvtrace-encoder.h"
+#include "rvtrace-funnel.h"
+
+int rvtrace_timestamp_reset(struct gtrace_platform_data *pdata)
+{
+	gtrace_write32(pdata, 0, RVTRACE_TIMESTAMP_CTRL_OFFSET);
+	gtrace_write32(pdata, RVTRACE_COMPONENT_CTRL_ACTIVE_MASK, RVTRACE_TIMESTAMP_CTRL_OFFSET);
+	return gtrace_poll_bit(pdata, RVTRACE_TIMESTAMP_CTRL_OFFSET,
+			       RVTRACE_COMPONENT_CTRL_ACTIVE_SHIFT, 1,
+			       pdata->control_poll_timeout_usecs);
+}
+EXPORT_SYMBOL_GPL(rvtrace_timestamp_reset);
+
+void rvtrace_timestamp_init(struct gtrace_platform_data *pdata,
+			    struct rvtrace_timestamp_priv *priv)
+{
+	u32 ctrl, val;
+	enum rvtrace_timestamp_mode mode;
+
+	ctrl = gtrace_read32(pdata, RVTRACE_TIMESTAMP_CTRL_OFFSET);
+	priv->mode = FIELD_GET(RVTRACE_TIMESTAMP_MODE_MASK, ctrl);
+
+	/* get timestamp mode hw */
+	for (mode = RVTRACE_TIMESTAMP_MODE_NONE; mode < RVTRACE_TIMESTAMP_MODE_MAX; mode++) {
+		val = ctrl & ~RVTRACE_TIMESTAMP_MODE_MASK;
+		val |= FIELD_PREP(RVTRACE_TIMESTAMP_MODE_MASK, mode);
+		gtrace_write32(pdata, val, RVTRACE_TIMESTAMP_CTRL_OFFSET);
+		val = gtrace_read32(pdata, RVTRACE_TIMESTAMP_CTRL_OFFSET);
+		val = FIELD_GET(RVTRACE_TIMESTAMP_MODE_MASK, val);
+		priv->mode_hw[mode] = (mode == val);
+	}
+
+	gtrace_write32(pdata, ctrl, RVTRACE_TIMESTAMP_CTRL_OFFSET);
+}
+EXPORT_SYMBOL_GPL(rvtrace_timestamp_init);
+
+static bool rvtrace_timestamp_is_internal(struct rvtrace_timestamp_priv *priv)
+{
+	return (priv->mode == RVTRACE_TIMESTAMP_MODE_INTERNAL_SYSTEM ||
+		priv->mode == RVTRACE_TIMESTAMP_MODE_INTERNAL_CORE);
+}
+
+static void rvtrace_timestamp_parse_event_config(struct rvtrace_timestamp_priv *priv,
+						 struct perf_event *event)
+{
+	u64 config = event->attr.config;
+
+	if (priv->mode_hw[FIELD_GET(GTRACE_PMU_ATTR_CFG_TSMODE_MASK, config)])
+		priv->mode = FIELD_GET(GTRACE_PMU_ATTR_CFG_TSMODE_MASK, config);
+	if (rvtrace_timestamp_is_internal(priv))
+		priv->prescale = FIELD_GET(GTRACE_PMU_ATTR_CFG_TSPRESCALE_MASK, config);
+}
+
+void rvtrace_timestamp_setup(struct gtrace_platform_data *pdata,
+			     struct rvtrace_timestamp_priv *priv,
+			     struct perf_event *event,
+			     bool enable)
+{
+	u32 val;
+
+	if (event)
+		rvtrace_timestamp_parse_event_config(priv, event);
+
+	val = gtrace_read32(pdata, RVTRACE_TIMESTAMP_CTRL_OFFSET);
+	val &= ~RVTRACE_TIMESTAMP_MODE_MASK;
+	val &= ~RVTRACE_TIMESTAMP_PRESCALE_MASK;
+	val |= FIELD_PREP(RVTRACE_TIMESTAMP_MODE_MASK, priv->mode);
+	val |= FIELD_PREP(RVTRACE_TIMESTAMP_PRESCALE_MASK, priv->prescale);
+
+	val &= ~BIT(RVTRACE_TIMESTAMP_COUNT_SHIFT);
+	val &= ~BIT(RVTRACE_TIMESTAMP_ENABLE_SHIFT);
+	if (enable) {
+		val |= BIT(RVTRACE_TIMESTAMP_COUNT_SHIFT);
+		val |= BIT(RVTRACE_TIMESTAMP_ENABLE_SHIFT);
+	}
+
+	gtrace_write32(pdata, val, RVTRACE_TIMESTAMP_CTRL_OFFSET);
+}
+EXPORT_SYMBOL_GPL(rvtrace_timestamp_setup);
+
+static struct rvtrace_timestamp_priv *get_timestamp_priv(struct gtrace_component *comp)
+{
+	enum gtrace_component_type gtype = comp->id.type;
+
+	if (gtype == GTRACE_RVTRACE_ENCODER) {
+		struct rvtrace_encoder_priv *priv = dev_get_drvdata(&comp->dev);
+		return &priv->ts_priv;
+	} else if (gtype == GTRACE_RVTRACE_FUNNEL) {
+		struct rvtrace_funnel_priv *priv = dev_get_drvdata(&comp->dev);
+		return &priv->ts_priv;
+	}
+
+	return NULL;
+}
+
+static struct gtrace_component *rvtrace_find_funnel(struct gtrace_component *comp)
+{
+	struct gtrace_platform_data *pdata = comp->pdata;
+	struct gtrace_connection *conn = NULL;
+	int i;
+
+	if (comp->id.type == GTRACE_RVTRACE_FUNNEL)
+		return comp;
+
+	if (!pdata->nr_outconns)
+		return NULL;
+
+	for (i = 0; i < pdata->nr_outconns; i++) {
+		conn = pdata->outconns[i];
+		if (conn && conn->dest_comp)
+			break;
+	}
+
+	if (!conn)
+		return NULL;
+
+	return rvtrace_find_funnel(conn->dest_comp);
+}
+
+static struct gtrace_component *rvtrace_timestamp_get_source(struct gtrace_component *comp)
+{
+	struct gtrace_component *dest = comp;
+
+	if (dest->id.type == GTRACE_RVTRACE_ENCODER) {
+		struct rvtrace_encoder_priv *priv = dev_get_drvdata(&dest->dev);
+
+		if (priv && priv->has_timestamp)
+			return dest;
+
+		dest = rvtrace_find_funnel(dest);
+		if (!dest)
+			return NULL;
+	}
+
+	if (dest->id.type == GTRACE_RVTRACE_FUNNEL) {
+		struct rvtrace_funnel_priv *priv = dev_get_drvdata(&dest->dev);
+
+		if (priv && priv->has_timestamp)
+			return dest;
+	}
+
+	return NULL;
+}
+
+static ssize_t source_show(struct device *dev,
+			   struct device_attribute *attr, char *buf)
+{
+	struct gtrace_component *comp = to_gtrace_component(dev);
+
+	comp = rvtrace_timestamp_get_source(comp);
+	if (!comp)
+		return -EINVAL;
+
+	return sysfs_emit(buf, "%s\n", dev_name(&comp->dev));
+}
+static DEVICE_ATTR_RO(source);
+
+static const char * const modes_str[] = {
+	[RVTRACE_TIMESTAMP_MODE_NONE] = "none",
+	[RVTRACE_TIMESTAMP_MODE_EXTERNAL] = "external",
+	[RVTRACE_TIMESTAMP_MODE_INTERNAL_SYSTEM] = "internal_system",
+	[RVTRACE_TIMESTAMP_MODE_INTERNAL_CORE] = "internal_core",
+	[RVTRACE_TIMESTAMP_MODE_SHARED] = "shared",
+};
+
+static ssize_t modes_available_show(struct device *dev,
+				    struct device_attribute *attr, char *buf)
+{
+	struct gtrace_component *comp = to_gtrace_component(dev);
+	struct rvtrace_timestamp_priv *priv;
+	enum rvtrace_timestamp_mode mode;
+	ssize_t size = 0;
+
+	comp = rvtrace_timestamp_get_source(comp);
+	if (!comp)
+		return -EINVAL;
+
+	priv = get_timestamp_priv(comp);
+
+	for (mode = RVTRACE_TIMESTAMP_MODE_NONE; mode < RVTRACE_TIMESTAMP_MODE_MAX; mode++) {
+		if (priv->mode_hw[mode])
+			size += sysfs_emit_at(buf, size, "%s ", modes_str[mode]);
+	}
+
+	size += sysfs_emit_at(buf, size, "\n");
+	return size;
+}
+static DEVICE_ATTR_RO(modes_available);
+
+static ssize_t mode_preferred_show(struct device *dev,
+				   struct device_attribute *attr, char *buf)
+{
+	struct gtrace_component *comp = to_gtrace_component(dev);
+	struct rvtrace_timestamp_priv *priv;
+
+	comp = rvtrace_timestamp_get_source(comp);
+	if (!comp)
+		return -EINVAL;
+
+	priv = get_timestamp_priv(comp);
+
+	return sysfs_emit(buf, "%s\n", modes_str[priv->mode]);
+}
+
+static ssize_t mode_preferred_store(struct device *dev,
+				    struct device_attribute *attr,
+				    const char *buf, size_t size)
+{
+	struct gtrace_component *comp = to_gtrace_component(dev);
+	struct rvtrace_timestamp_priv *priv;
+
+	comp = rvtrace_timestamp_get_source(comp);
+	if (!comp)
+		return -EINVAL;
+
+	priv = get_timestamp_priv(comp);
+
+	if (sysfs_streq(buf, modes_str[RVTRACE_TIMESTAMP_MODE_NONE]) &&
+			priv->mode_hw[RVTRACE_TIMESTAMP_MODE_NONE])
+		priv->mode = RVTRACE_TIMESTAMP_MODE_NONE;
+	else if (sysfs_streq(buf, modes_str[RVTRACE_TIMESTAMP_MODE_EXTERNAL]) &&
+			priv->mode_hw[RVTRACE_TIMESTAMP_MODE_EXTERNAL])
+		priv->mode = RVTRACE_TIMESTAMP_MODE_EXTERNAL;
+	else if (sysfs_streq(buf, modes_str[RVTRACE_TIMESTAMP_MODE_INTERNAL_SYSTEM]) &&
+			priv->mode_hw[RVTRACE_TIMESTAMP_MODE_INTERNAL_SYSTEM])
+		priv->mode = RVTRACE_TIMESTAMP_MODE_INTERNAL_SYSTEM;
+	else if (sysfs_streq(buf, modes_str[RVTRACE_TIMESTAMP_MODE_INTERNAL_CORE]) &&
+			priv->mode_hw[RVTRACE_TIMESTAMP_MODE_INTERNAL_CORE])
+		priv->mode = RVTRACE_TIMESTAMP_MODE_INTERNAL_CORE;
+	else if (sysfs_streq(buf, modes_str[RVTRACE_TIMESTAMP_MODE_SHARED]) &&
+			priv->mode_hw[RVTRACE_TIMESTAMP_MODE_SHARED])
+		priv->mode = RVTRACE_TIMESTAMP_MODE_SHARED;
+	else
+		return -EINVAL;
+	return size;
+}
+static DEVICE_ATTR_RW(mode_preferred);
+
+static ssize_t prescale_show(struct device *dev,
+			     struct device_attribute *attr, char *buf)
+{
+	struct gtrace_component *comp = to_gtrace_component(dev);
+	struct rvtrace_timestamp_priv *priv;
+
+	comp = rvtrace_timestamp_get_source(comp);
+	if (!comp)
+		return -EINVAL;
+
+	priv = get_timestamp_priv(comp);
+
+	return sysfs_emit(buf, "%u\n", priv->prescale);
+}
+
+static ssize_t prescale_store(struct device *dev,
+			      struct device_attribute *attr,
+			      const char *buf, size_t size)
+{
+	struct gtrace_component *comp = to_gtrace_component(dev);
+	struct rvtrace_timestamp_priv *priv;
+	unsigned long val;
+	int ret;
+
+	comp = rvtrace_timestamp_get_source(comp);
+	if (!comp)
+		return -EINVAL;
+
+	ret = kstrtoul(buf, 0, &val);
+	if (ret)
+		return ret;
+
+	if (val > RVTRACE_TIMESTAMP_PRESCALE_MAX)
+		return -EINVAL;
+
+	priv = get_timestamp_priv(comp);
+	if (!rvtrace_timestamp_is_internal(priv))
+		return -EINVAL;
+
+	priv->prescale = val;
+
+	return size;
+}
+static DEVICE_ATTR_RW(prescale);
+
+static ssize_t control_show(struct device *dev,
+			    struct device_attribute *attr, char *buf)
+{
+	u32 val;
+	struct gtrace_component *comp = to_gtrace_component(dev);
+
+	comp = rvtrace_timestamp_get_source(comp);
+	if (!comp)
+		return -EINVAL;
+
+	val = gtrace_read32(comp->pdata, RVTRACE_TIMESTAMP_CTRL_OFFSET);
+
+	return sysfs_emit(buf, "0x%x\n", val);
+}
+static DEVICE_ATTR_RO(control);
+
+static struct attribute *rvtrace_timestamp_attrs[] = {
+	&dev_attr_source.attr,
+	&dev_attr_modes_available.attr,
+	&dev_attr_mode_preferred.attr,
+	&dev_attr_prescale.attr,
+	&dev_attr_control.attr,
+	NULL,
+};
+
+static umode_t timestamp_attr_is_visible(struct kobject *kobj,
+					 struct attribute *attr, int idx)
+{
+	struct device *dev = kobj_to_dev(kobj);
+	struct gtrace_component *comp = to_gtrace_component(dev);
+
+	if (rvtrace_timestamp_get_source(comp))
+		return attr->mode;
+
+	return 0;
+}
+
+struct attribute_group rvtrace_timestamp_group = {
+	.attrs = rvtrace_timestamp_attrs,
+	.name = "timestamp",
+	.is_visible = timestamp_attr_is_visible,
+};
+EXPORT_SYMBOL_GPL(rvtrace_timestamp_group);
diff --git a/drivers/hwtracing/gtrace/rvtrace-timestamp.h b/drivers/hwtracing/gtrace/rvtrace-timestamp.h
new file mode 100644
index 000000000000..75924d397996
--- /dev/null
+++ b/drivers/hwtracing/gtrace/rvtrace-timestamp.h
@@ -0,0 +1,44 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright(C) 2026 Spacemit Technologies, Inc.
+ */
+
+#ifndef __RVTRACE_TIMESTAMP_H__
+#define __RVTRACE_TIMESTAMP_H__
+
+#include <linux/types.h>
+
+#define RVTRACE_TIMESTAMP_CTRL_OFFSET		0x040
+#define RVTRACE_TIMESTAMP_COUNT_SHIFT		1
+#define RVTRACE_TIMESTAMP_MODE_MASK		GENMASK(6, 4)
+#define RVTRACE_TIMESTAMP_PRESCALE_MASK		GENMASK(9, 8)
+#define RVTRACE_TIMESTAMP_PRESCALE_MAX		3
+#define RVTRACE_TIMESTAMP_ENABLE_SHIFT		15
+#define RVTRACE_TIMESTAMP_WIDTH_MASK		GENMASK(29, 24)
+
+enum rvtrace_timestamp_mode {
+	RVTRACE_TIMESTAMP_MODE_NONE,
+	RVTRACE_TIMESTAMP_MODE_EXTERNAL,
+	RVTRACE_TIMESTAMP_MODE_INTERNAL_SYSTEM,
+	RVTRACE_TIMESTAMP_MODE_INTERNAL_CORE,
+	RVTRACE_TIMESTAMP_MODE_SHARED,
+	RVTRACE_TIMESTAMP_MODE_MAX
+};
+
+struct rvtrace_timestamp_priv {
+	bool mode_hw[RVTRACE_TIMESTAMP_MODE_MAX];
+	enum rvtrace_timestamp_mode mode;
+	u32 prescale;
+};
+
+int rvtrace_timestamp_reset(struct gtrace_platform_data *pdata);
+void rvtrace_timestamp_init(struct gtrace_platform_data *pdata,
+			    struct rvtrace_timestamp_priv *priv);
+void rvtrace_timestamp_setup(struct gtrace_platform_data *pdata,
+			     struct rvtrace_timestamp_priv *priv,
+			     struct perf_event *event,
+			     bool enable);
+
+extern struct attribute_group rvtrace_timestamp_group;
+
+#endif/* __RVTRACE_TIMESTAMP_H__ */
-- 
2.34.1


^ permalink raw reply	[flat|nested] 25+ messages in thread

* [PATCH 12/24] gtrace: Add RISC-V trace ATB bridge driver
  2026-10-23  3:14 [RFC PATCH 00/24] Add Linux RISC-V trace support via the gtrace framework Zane Leung
                   ` (10 preceding siblings ...)
  2026-10-23  3:14 ` [PATCH 11/24] gtrace: Add RISC-V trace timestamp support Zane Leung
@ 2026-10-23  3:14 ` Zane Leung
  2026-10-23  3:14 ` [PATCH 13/24] gtrace: Add CoreSight platform core APIs Zane Leung
                   ` (11 subsequent siblings)
  23 siblings, 0 replies; 25+ messages in thread
From: Zane Leung @ 2026-10-23  3:14 UTC (permalink / raw)
  To: gregkh, palmer, pjw, alexander.shishkin, suzuki.poulose,
	mchitale, anup, irogers
  Cc: mike.leach, james.clark, leo.yan, peterz, mingo, namhyung,
	mark.rutland, jolsa, adrian.hunter, alex, atish.patra,
	andrew.jones, sunilvl, liangzhen, zhuangqiubin, coresight,
	linux-riscv, linux-kernel

Add initial implementation of RISC-V Trace atbbridge driver. The
atbbridge is defined in the RISC-V Trace Control Interface specification.

Signed-off-by: Zane Leung <liangzhen@linux.spacemit.com>
---
 drivers/hwtracing/gtrace/Kconfig             |  15 ++
 drivers/hwtracing/gtrace/Makefile            |   1 +
 drivers/hwtracing/gtrace/rvtrace-atbbridge.c | 153 +++++++++++++++++++
 drivers/hwtracing/gtrace/rvtrace-platform.c  |   3 +
 drivers/hwtracing/gtrace/rvtrace.h           |   1 +
 5 files changed, 173 insertions(+)
 create mode 100644 drivers/hwtracing/gtrace/rvtrace-atbbridge.c

diff --git a/drivers/hwtracing/gtrace/Kconfig b/drivers/hwtracing/gtrace/Kconfig
index f6026965c54d..3af47d7c3ce3 100644
--- a/drivers/hwtracing/gtrace/Kconfig
+++ b/drivers/hwtracing/gtrace/Kconfig
@@ -71,3 +71,18 @@ config RVTRACE_FUNNEL
 
 	  To compile this driver as a module, choose M here: the module
 	  will be called rvtrace-funnel.
+
+config RVTRACE_ATBBRIDGE
+	tristate "RISC-V Trace ATB Bridge driver"
+	depends on RVTRACE
+	default y
+	help
+	  This driver provides support for the RISC-V trace ATB bridge
+	  component which is defined by the RISC-V Trace Control Interface
+	  specification. The ATB bridge converts the RISC-V trace stream
+	  to the ARM Advanced Trace Bus (ATB) protocol, allowing RISC-V
+	  trace sources to feed into ARM CoreSight trace components. It is
+	  a link component in the trace path.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called rvtrace-atbbridge.
diff --git a/drivers/hwtracing/gtrace/Makefile b/drivers/hwtracing/gtrace/Makefile
index b60d3cfe5248..3f1dcb5664a2 100644
--- a/drivers/hwtracing/gtrace/Makefile
+++ b/drivers/hwtracing/gtrace/Makefile
@@ -10,3 +10,4 @@ obj-$(CONFIG_RVTRACE_ENCODER) += rvtrace-encoder.o
 rvtrace-encoder-y := rvtrace-encoder-core.o rvtrace-encoder-sysfs.o
 obj-$(CONFIG_RVTRACE_RAMSINK) += rvtrace-ramsink.o
 obj-$(CONFIG_RVTRACE_FUNNEL) += rvtrace-funnel.o
+obj-$(CONFIG_RVTRACE_ATBBRIDGE) += rvtrace-atbbridge.o
diff --git a/drivers/hwtracing/gtrace/rvtrace-atbbridge.c b/drivers/hwtracing/gtrace/rvtrace-atbbridge.c
new file mode 100644
index 000000000000..92a59ed80a11
--- /dev/null
+++ b/drivers/hwtracing/gtrace/rvtrace-atbbridge.c
@@ -0,0 +1,153 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2026 Spacemit Technologies, Inc.
+ */
+
+#include <linux/device.h>
+#include <linux/gtrace.h>
+#include <linux/idr.h>
+#include <linux/types.h>
+#include <linux/bitfield.h>
+#include "rvtrace.h"
+
+#define RVTRACE_ATBBRIDGE_CTRL_ATID_MASK    GENMASK(14, 8)
+#define RVTRACE_ATBBRIDGE_ATID_MIN	    0x1
+#define RVTRACE_ATBBRIDGE_ATID_MAX	    0x6f
+
+static DEFINE_IDA(rvtrace_atbbridge_atid_ida);
+
+struct rvtrace_atbbridge_priv {
+	int atid;
+};
+
+static int rvtrace_atbbridge_start(struct gtrace_component *comp)
+{
+	int ret;
+
+	ret = gtrace_enable_component(comp);
+	if (ret)
+		dev_err(&comp->dev, "failed to start atbbridge.\n");
+
+	return ret;
+}
+
+static int rvtrace_atbbridge_stop(struct gtrace_component *comp)
+{
+	int ret;
+
+	ret = gtrace_disable_component(comp);
+	if (ret)
+		dev_err(&comp->dev, "failed to stop atbbridge.\n");
+
+	return ret;
+}
+
+static ssize_t cpu_show(struct device *dev,
+			struct device_attribute *attr, char *buf)
+{
+	struct gtrace_component *comp = to_gtrace_component(dev);
+
+	return sysfs_emit(buf, "%d\n", comp->pdata->bound_cpu);
+}
+static DEVICE_ATTR_RO(cpu);
+
+static ssize_t atid_show(struct device *dev,
+			 struct device_attribute *attr, char *buf)
+{
+	struct gtrace_component *comp = to_gtrace_component(dev);
+	struct rvtrace_atbbridge_priv *priv = dev_get_drvdata(&comp->dev);
+
+	return sysfs_emit(buf, "%u\n", priv->atid);
+}
+static DEVICE_ATTR_RO(atid);
+
+static struct attribute *rvtrace_atbbridge_attrs[] = {
+	&dev_attr_cpu.attr,
+	&dev_attr_atid.attr,
+	NULL,
+};
+
+static const struct attribute_group rvtrace_atbbridge_group = {
+	.attrs = rvtrace_atbbridge_attrs,
+};
+
+const struct attribute_group *rvtrace_atbbridge_groups[] = {
+	&rvtrace_atbbridge_group,
+	NULL,
+};
+EXPORT_SYMBOL_GPL(rvtrace_atbbridge_groups);
+
+static void rvtrace_atbbridge_cleanup(struct gtrace_component *comp)
+{
+	struct rvtrace_atbbridge_priv *priv = dev_get_drvdata(&comp->dev);
+
+	ida_free(&rvtrace_atbbridge_atid_ida, priv->atid);
+}
+
+static int rvtrace_atbbridge_probe(struct gtrace_component *comp)
+{
+	struct rvtrace_atbbridge_priv *priv;
+	u32 val;
+
+	priv = devm_kzalloc(&comp->dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	priv->atid = ida_alloc_range(&rvtrace_atbbridge_atid_ida,
+				     RVTRACE_ATBBRIDGE_ATID_MIN,
+				     RVTRACE_ATBBRIDGE_ATID_MAX,
+				     GFP_KERNEL);
+	if (priv->atid < 0) {
+		dev_err(&comp->dev, "failed to allocate ATID.\n");
+		return priv->atid;
+	}
+
+	val = gtrace_read32(comp->pdata, RVTRACE_COMPONENT_CTRL_OFFSET);
+	val &= ~RVTRACE_ATBBRIDGE_CTRL_ATID_MASK;
+	val |= FIELD_PREP(RVTRACE_ATBBRIDGE_CTRL_ATID_MASK, priv->atid);
+	gtrace_write32(comp->pdata, val, RVTRACE_COMPONENT_CTRL_OFFSET);
+
+	dev_set_drvdata(&comp->dev, priv);
+
+	return 0;
+}
+
+static void rvtrace_atbbridge_remove(struct gtrace_component *comp)
+{
+	rvtrace_atbbridge_cleanup(comp);
+}
+
+static const struct gtrace_component_id rvtrace_atbbridge_ids[] = {
+	{ .type = GTRACE_RVTRACE_ATBBRIDGE,
+	  .version = rvtrace_component_mkversion(1, 0), },
+	{},
+};
+
+static struct gtrace_driver rvtrace_atbbridge_driver = {
+	.id_table = rvtrace_atbbridge_ids,
+	.probe = rvtrace_atbbridge_probe,
+	.remove = rvtrace_atbbridge_remove,
+	.start = rvtrace_atbbridge_start,
+	.stop = rvtrace_atbbridge_stop,
+	.driver = {
+		.name = "rvtrace-atbbridge",
+	},
+};
+
+static int __init rvtrace_atbbridge_init(void)
+{
+	return gtrace_register_driver(&rvtrace_atbbridge_driver);
+}
+
+static void __exit rvtrace_atbbridge_exit(void)
+{
+	gtrace_unregister_driver(&rvtrace_atbbridge_driver);
+	ida_destroy(&rvtrace_atbbridge_atid_ida);
+}
+
+module_init(rvtrace_atbbridge_init);
+module_exit(rvtrace_atbbridge_exit);
+
+MODULE_AUTHOR("Zane Leung");
+MODULE_DESCRIPTION("RISC-V Trace ATB Bridge Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hwtracing/gtrace/rvtrace-platform.c b/drivers/hwtracing/gtrace/rvtrace-platform.c
index ad8daa06e13e..9bd315867082 100644
--- a/drivers/hwtracing/gtrace/rvtrace-platform.c
+++ b/drivers/hwtracing/gtrace/rvtrace-platform.c
@@ -136,6 +136,9 @@ static int rvtrace_platform_probe(struct platform_device *pdev)
 	case GTRACE_RVTRACE_FUNNEL:
 		pdata->groups = rvtrace_funnel_groups;
 		break;
+	case GTRACE_RVTRACE_ATBBRIDGE:
+		pdata->groups = rvtrace_atbbridge_groups;
+		break;
 	default:
 		pdata->groups = NULL;
 	}
diff --git a/drivers/hwtracing/gtrace/rvtrace.h b/drivers/hwtracing/gtrace/rvtrace.h
index a345ba110267..5cbe3405e274 100644
--- a/drivers/hwtracing/gtrace/rvtrace.h
+++ b/drivers/hwtracing/gtrace/rvtrace.h
@@ -121,5 +121,6 @@ extern const struct gtrace_hw_ops rvtrace_hw_ops;
 /* Sysfs attribute groups for RISC-V trace components */
 extern const struct attribute_group *rvtrace_encoder_groups[];
 extern const struct attribute_group *rvtrace_funnel_groups[];
+extern const struct attribute_group *rvtrace_atbbridge_groups[];
 
 #endif /* __LINUX_RVTRACE_H__ */
-- 
2.34.1


^ permalink raw reply	[flat|nested] 25+ messages in thread

* [PATCH 13/24] gtrace: Add CoreSight platform core APIs
  2026-10-23  3:14 [RFC PATCH 00/24] Add Linux RISC-V trace support via the gtrace framework Zane Leung
                   ` (11 preceding siblings ...)
  2026-10-23  3:14 ` [PATCH 12/24] gtrace: Add RISC-V trace ATB bridge driver Zane Leung
@ 2026-10-23  3:14 ` Zane Leung
  2026-10-23  3:14 ` [PATCH 14/24] gtrace: Add CoreSight funnel driver Zane Leung
                   ` (10 subsequent siblings)
  23 siblings, 0 replies; 25+ messages in thread
From: Zane Leung @ 2026-10-23  3:14 UTC (permalink / raw)
  To: gregkh, palmer, pjw, alexander.shishkin, suzuki.poulose,
	mchitale, anup, irogers
  Cc: mike.leach, james.clark, leo.yan, peterz, mingo, namhyung,
	mark.rutland, jolsa, adrian.hunter, alex, atish.patra,
	andrew.jones, sunilvl, liangzhen, zhuangqiubin, coresight,
	linux-riscv, linux-kernel

Add CoreSight infrastructure for the gtrace framework, providing driver
registration helper, clock management, and other core integration APIs.

Signed-off-by: Zane Leung <liangzhen@linux.spacemit.com>
---
 drivers/hwtracing/gtrace/Kconfig              |  15 +
 drivers/hwtracing/gtrace/Makefile             |   1 +
 drivers/hwtracing/gtrace/coresight-platform.c | 279 ++++++++++++++++++
 drivers/hwtracing/gtrace/coresight.h          | 212 +++++++++++++
 include/linux/gtrace.h                        |   7 +
 5 files changed, 514 insertions(+)
 create mode 100644 drivers/hwtracing/gtrace/coresight-platform.c
 create mode 100644 drivers/hwtracing/gtrace/coresight.h

diff --git a/drivers/hwtracing/gtrace/Kconfig b/drivers/hwtracing/gtrace/Kconfig
index 3af47d7c3ce3..5dce4474db7c 100644
--- a/drivers/hwtracing/gtrace/Kconfig
+++ b/drivers/hwtracing/gtrace/Kconfig
@@ -86,3 +86,18 @@ config RVTRACE_ATBBRIDGE
 
 	  To compile this driver as a module, choose M here: the module
 	  will be called rvtrace-atbbridge.
+
+config GTRACE_CORESIGHT
+	tristate "CoreSight Trace Support"
+	depends on RISCV
+	depends on GTRACE
+	select ARM_AMBA
+	help
+	  This provides the CoreSight backend for the generic hardware trace
+	  (gtrace) framework. It provides the CoreSight claim protocol,
+	  component and driver registration helpers, and clock management
+	  used by the individual CoreSight component drivers (TMC, funnel,
+	  etc.) to register CoreSight hardware with the gtrace core.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called coresight.
diff --git a/drivers/hwtracing/gtrace/Makefile b/drivers/hwtracing/gtrace/Makefile
index 3f1dcb5664a2..0833a40c397c 100644
--- a/drivers/hwtracing/gtrace/Makefile
+++ b/drivers/hwtracing/gtrace/Makefile
@@ -11,3 +11,4 @@ rvtrace-encoder-y := rvtrace-encoder-core.o rvtrace-encoder-sysfs.o
 obj-$(CONFIG_RVTRACE_RAMSINK) += rvtrace-ramsink.o
 obj-$(CONFIG_RVTRACE_FUNNEL) += rvtrace-funnel.o
 obj-$(CONFIG_RVTRACE_ATBBRIDGE) += rvtrace-atbbridge.o
+obj-$(CONFIG_GTRACE_CORESIGHT) += coresight-platform.o
diff --git a/drivers/hwtracing/gtrace/coresight-platform.c b/drivers/hwtracing/gtrace/coresight-platform.c
new file mode 100644
index 000000000000..f5a472e86a32
--- /dev/null
+++ b/drivers/hwtracing/gtrace/coresight-platform.c
@@ -0,0 +1,279 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * CoreSight platform backend shared functions for the gtrace framework.
+ * Derived from drivers/hwtracing/coresight/coresight-core.c.
+ *
+ * Copyright (c) 2012, The Linux Foundation. All rights reserved.
+ */
+
+#include <linux/acpi.h>
+#include <linux/bitfield.h>
+#include <linux/build_bug.h>
+#include <linux/cpu_pm.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/device.h>
+#include <linux/io.h>
+#include <linux/err.h>
+#include <linux/export.h>
+#include <linux/slab.h>
+#include <linux/stringhash.h>
+#include <linux/clk.h>
+#include <linux/gtrace.h>
+#include <linux/property.h>
+#include <linux/delay.h>
+#include <linux/pm_runtime.h>
+#include <linux/panic_notifier.h>
+
+#include "coresight.h"
+
+/*
+ * When losing synchronisation a new barrier packet needs to be inserted at the
+ * beginning of the data collected in a buffer.  That way the decoder knows that
+ * it needs to look for another sync sequence.
+ */
+const u32 coresight_barrier_pkt[4] = {0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff};
+EXPORT_SYMBOL_GPL(coresight_barrier_pkt);
+
+static u32 coresight_read_claim_tags_unlocked(struct gtrace_component *comp)
+{
+	return FIELD_GET(CORESIGHT_CLAIM_MASK,
+			gtrace_relaxed_read32(comp->pdata, CORESIGHT_CLAIMCLR));
+}
+
+static void coresight_set_self_claim_tag_unlocked(struct gtrace_component *comp)
+{
+	gtrace_relaxed_write32(comp->pdata, CORESIGHT_CLAIM_SELF_HOSTED,
+			       CORESIGHT_CLAIMSET);
+	/* Make sure everyone has seen this */
+	mb();
+}
+
+void coresight_clear_self_claim_tag(struct gtrace_platform_data *pdata)
+{
+	if (pdata->io_mem)
+		CS_UNLOCK(pdata->base);
+	coresight_clear_self_claim_tag_unlocked(pdata);
+	if (pdata->io_mem)
+		CS_LOCK(pdata->base);
+}
+EXPORT_SYMBOL_GPL(coresight_clear_self_claim_tag);
+
+void coresight_clear_self_claim_tag_unlocked(struct gtrace_platform_data *pdata)
+{
+	gtrace_relaxed_write32(pdata, CORESIGHT_CLAIM_SELF_HOSTED,
+			       CORESIGHT_CLAIMCLR);
+	/* Make sure everyone has seen this */
+	mb();
+}
+EXPORT_SYMBOL_GPL(coresight_clear_self_claim_tag_unlocked);
+
+/*
+ * coresight_claim_device_unlocked : Claim the device for self-hosted usage
+ * to prevent an external tool from touching this device. As per PSCI
+ * standards, section "Preserving the execution context" => "Debug and Trace
+ * save and Restore", DBGCLAIM[1] is reserved for Self-hosted debug/trace and
+ * DBGCLAIM[0] is reserved for external tools.
+ *
+ * Called with CS_UNLOCKed for the component.
+ * Returns : 0 on success
+ */
+int coresight_claim_device_unlocked(struct gtrace_component *comp)
+{
+	u32 tag;
+	struct gtrace_platform_data *pdata;
+
+	if (WARN_ON(!comp))
+		return -EINVAL;
+
+	pdata = comp->pdata;
+	tag = coresight_read_claim_tags_unlocked(comp);
+
+	switch (tag) {
+	case CORESIGHT_CLAIM_FREE:
+		coresight_set_self_claim_tag_unlocked(comp);
+		if (coresight_read_claim_tags_unlocked(comp) == CORESIGHT_CLAIM_SELF_HOSTED)
+			return 0;
+
+		/* There was a race setting the tag, clean up and fail */
+		coresight_clear_self_claim_tag_unlocked(pdata);
+		dev_dbg(&comp->dev, "Busy: Couldn't set self claim tag");
+		return -EBUSY;
+
+	case CORESIGHT_CLAIM_EXTERNAL:
+		/* External debug is an expected state, so log and report BUSY */
+		dev_dbg(&comp->dev, "Busy: Claimed by external debugger");
+		return -EBUSY;
+
+	default:
+	case CORESIGHT_CLAIM_SELF_HOSTED:
+	case CORESIGHT_CLAIM_INVALID:
+		dev_err_once(&comp->dev, "Invalid claim tag state: %x", tag);
+		return -EBUSY;
+	}
+}
+EXPORT_SYMBOL_GPL(coresight_claim_device_unlocked);
+
+int coresight_claim_device(struct gtrace_component *comp)
+{
+	int rc;
+
+	if (WARN_ON(!comp))
+		return -EINVAL;
+
+	CS_UNLOCK(comp->pdata->base);
+	rc = coresight_claim_device_unlocked(comp);
+	CS_LOCK(comp->pdata->base);
+
+	return rc;
+}
+EXPORT_SYMBOL_GPL(coresight_claim_device);
+
+/*
+ * coresight_disclaim_device_unlocked : Clear the claim tag for the device.
+ * Called with CS_UNLOCKed for the component.
+ */
+void coresight_disclaim_device_unlocked(struct gtrace_component *comp)
+{
+	if (WARN_ON(!comp))
+		return;
+
+	if (coresight_read_claim_tags_unlocked(comp) == CORESIGHT_CLAIM_SELF_HOSTED)
+		coresight_clear_self_claim_tag_unlocked(comp->pdata);
+	else
+		/*
+		 * The external agent may have not honoured our claim
+		 * and has manipulated it. Or something else has seriously
+		 * gone wrong in our driver.
+		 */
+		dev_WARN_ONCE(&comp->dev, 1, "External agent took claim tag");
+}
+EXPORT_SYMBOL_GPL(coresight_disclaim_device_unlocked);
+
+void coresight_disclaim_device(struct gtrace_component *comp)
+{
+	if (WARN_ON(!comp))
+		return;
+
+	CS_UNLOCK(comp->pdata->base);
+	coresight_disclaim_device_unlocked(comp);
+	CS_LOCK(comp->pdata->base);
+}
+EXPORT_SYMBOL_GPL(coresight_disclaim_device);
+
+/*
+ * Attempt to find and enable programming clock (pclk) and trace clock (atclk)
+ * for the given device.
+ *
+ * For ACPI devices, clocks are controlled by firmware, so bail out early in
+ * this case. Also, skip enabling pclk if the clock is managed by the AMBA
+ * bus driver instead.
+ *
+ * atclk is an optional clock, it will be only enabled when it is existed.
+ * Otherwise, a NULL pointer will be returned to caller.
+ *
+ * Returns: '0' on Success; Error code otherwise.
+ */
+int coresight_get_enable_clocks(struct device *dev, struct clk **pclk,
+				struct clk **atclk)
+{
+	WARN_ON(!pclk);
+
+	if (has_acpi_companion(dev))
+		return 0;
+
+	if (!dev_is_amba(dev)) {
+		/*
+		 * "apb_pclk" is the default clock name for an Arm Primecell
+		 * peripheral, while "apb" is used only by the CTCU driver.
+		 *
+		 * For easier maintenance, CoreSight drivers should use
+		 * "apb_pclk" as the programming clock name.
+		 */
+		*pclk = devm_clk_get_optional_enabled(dev, "apb_pclk");
+		if (!*pclk)
+			*pclk = devm_clk_get_optional_enabled(dev, "apb");
+		if (IS_ERR(*pclk))
+			return PTR_ERR(*pclk);
+	}
+
+	/* Initialization of atclk is skipped if it is a NULL pointer. */
+	if (atclk) {
+		*atclk = devm_clk_get_optional_enabled(dev, "atclk");
+		if (IS_ERR(*atclk))
+			return PTR_ERR(*atclk);
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(coresight_get_enable_clocks);
+
+struct gtrace_platform_data *
+coresight_get_platform_data(struct device *dev)
+{
+	int ret;
+	struct gtrace_platform_data *pdata = NULL;
+
+	pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
+	if (!pdata) {
+		ret = -ENOMEM;
+		goto err_out;
+	}
+
+	pdata->dev = dev;
+	pdata->control_poll_timeout_usecs = 100;
+
+	ret = gtrace_of_parse_outconns(pdata);
+	if (ret) {
+		dev_err_probe(pdata->dev, ret, "failed to parse output connections\n");
+		goto err_out;
+	}
+
+	ret = gtrace_of_parse_inconns(pdata);
+	if (ret) {
+		dev_err_probe(pdata->dev, ret, "failed to parse input connections\n");
+		goto err_out;
+	}
+
+	return pdata;
+err_out:
+	return ERR_PTR(ret);
+}
+EXPORT_SYMBOL_GPL(coresight_get_platform_data);
+
+int coresight_init_driver_with_owner(const char *drv, struct amba_driver *amba_drv,
+				     struct platform_driver *pdev_drv, struct module *owner,
+				     const char *mod_name)
+{
+	int ret;
+
+	ret = __amba_driver_register(amba_drv, owner);
+	if (ret) {
+		pr_err("%s: error registering AMBA driver\n", drv);
+		return ret;
+	}
+
+	ret = __platform_driver_register(pdev_drv, owner, mod_name);
+	if (!ret)
+		return 0;
+
+	pr_err("%s: error registering platform driver\n", drv);
+	amba_driver_unregister(amba_drv);
+	return ret;
+}
+EXPORT_SYMBOL_GPL(coresight_init_driver_with_owner);
+
+void coresight_remove_driver(struct amba_driver *amba_drv,
+			     struct platform_driver *pdev_drv)
+{
+	platform_driver_unregister(pdev_drv);
+	amba_driver_unregister(amba_drv);
+}
+EXPORT_SYMBOL_GPL(coresight_remove_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Pratik Patel <pratikp@codeaurora.org>");
+MODULE_AUTHOR("Mathieu Poirier <mathieu.poirier@linaro.org>");
+MODULE_AUTHOR("Zane Leung <liangzhen@linux.spacemit.com>");
+MODULE_DESCRIPTION("CoreSight backend for the generic trace framework");
diff --git a/drivers/hwtracing/gtrace/coresight.h b/drivers/hwtracing/gtrace/coresight.h
new file mode 100644
index 000000000000..f369911daace
--- /dev/null
+++ b/drivers/hwtracing/gtrace/coresight.h
@@ -0,0 +1,212 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * CoreSight component register layout and helpers for the gtrace framework.
+ * Derived from drivers/hwtracing/coresight/coresight-priv.h and
+ * include/linux/coresight.h.
+ *
+ * Copyright (c) 2012, The Linux Foundation. All rights reserved.
+ *
+ * CoreSight component register layout and helpers for the CoreSight backend
+ * of the generic trace (gtrace) framework. These definitions are specific to
+ * the CoreSight architecture and must not be used by the generic core.
+ */
+
+#ifndef __LINUX_CORESIGHT_H__
+#define __LINUX_CORESIGHT_H__
+
+#include <linux/gtrace.h>
+#include <linux/types.h>
+#include <linux/amba/bus.h>
+
+/*
+ * Coresight management registers (0xf00-0xfcc)
+ * 0xfa0 - 0xfa4: Management	registers in PFTv1.0
+ *		  Trace		registers in PFTv1.1
+ */
+#define CORESIGHT_ITCTRL	0xf00
+#define CORESIGHT_CLAIMSET	0xfa0
+#define CORESIGHT_CLAIMCLR	0xfa4
+#define CORESIGHT_LAR		0xfb0
+#define CORESIGHT_LSR		0xfb4
+#define CORESIGHT_DEVARCH	0xfbc
+#define CORESIGHT_AUTHSTATUS	0xfb8
+#define CORESIGHT_DEVID		0xfc8
+#define CORESIGHT_DEVTYPE	0xfcc
+
+/*
+ * Coresight device CLAIM protocol.
+ * See PSCI - ARM DEN 0022D, Section: 6.8.1 Debug and Trace save and restore.
+ */
+#define CORESIGHT_CLAIM_MASK		GENMASK(1, 0)
+#define CORESIGHT_CLAIM_FREE		0
+#define CORESIGHT_CLAIM_EXTERNAL	1
+#define CORESIGHT_CLAIM_SELF_HOSTED	2
+#define CORESIGHT_CLAIM_INVALID		3
+
+/* Peripheral id registers (0xFD0-0xFEC) */
+#define CORESIGHT_PERIPHIDR4	0xfd0
+#define CORESIGHT_PERIPHIDR5	0xfd4
+#define CORESIGHT_PERIPHIDR6	0xfd8
+#define CORESIGHT_PERIPHIDR7	0xfdC
+#define CORESIGHT_PERIPHIDR0	0xfe0
+#define CORESIGHT_PERIPHIDR1	0xfe4
+#define CORESIGHT_PERIPHIDR2	0xfe8
+#define CORESIGHT_PERIPHIDR3	0xfeC
+/* Component id registers (0xFF0-0xFFC) */
+#define CORESIGHT_COMPIDR0	0xff0
+#define CORESIGHT_COMPIDR1	0xff4
+#define CORESIGHT_COMPIDR2	0xff8
+#define CORESIGHT_COMPIDR3	0xffC
+
+#define CORESIGHT_UNLOCK	0xc5acce55
+
+#define BMVAL(val, lsb, msb)    ((val & GENMASK(msb, lsb)) >> lsb)
+
+extern const u32 coresight_barrier_pkt[4];
+#define CORESIGHT_BARRIER_PKT_SIZE (sizeof(coresight_barrier_pkt))
+
+#define CORESIGHT_COMPONENT_ID_VERSION	1
+
+static inline void coresight_insert_barrier_packet(void *buf)
+{
+	if (buf)
+		memcpy(buf, coresight_barrier_pkt, CORESIGHT_BARRIER_PKT_SIZE);
+}
+
+static inline void CS_LOCK(void __iomem *addr)
+{
+	do {
+		/* Wait for things to settle */
+		mb();
+		writel_relaxed(0x0, addr + CORESIGHT_LAR);
+	} while (0);
+}
+
+static inline void CS_UNLOCK(void __iomem *addr)
+{
+	do {
+		writel_relaxed(CORESIGHT_UNLOCK, addr + CORESIGHT_LAR);
+		/* Make sure everyone has seen this */
+		mb();
+	} while (0);
+}
+
+#define CORESIGHT_CIDRn(i)	(0xFF0 + ((i) * 4))
+
+static inline u32 coresight_get_cid(void __iomem *base)
+{
+	u32 i, cid = 0;
+
+	for (i = 0; i < 4; i++)
+		cid |= readl(base + CORESIGHT_CIDRn(i)) << (i * 8);
+
+	return cid;
+}
+
+static inline bool is_coresight_device(void __iomem *base)
+{
+	u32 cid = coresight_get_cid(base);
+
+	return cid == CORESIGHT_CID;
+}
+
+#define CORESIGHT_PIDRn(i)	(0xFE0 + ((i) * 4))
+
+static inline u32 coresight_get_pid(struct gtrace_platform_data *pdata)
+{
+	u32 i, pid = 0;
+
+	for (i = 0; i < 4; i++)
+		pid |= gtrace_relaxed_read32(pdata, CORESIGHT_PIDRn(i)) << (i * 8);
+
+	return pid;
+}
+
+/*
+ * Macros and inline functions to handle CoreSight UCI data and driver
+ * private data in AMBA ID table entries, and extract data values.
+ */
+
+/* coresight AMBA ID, no UCI, no driver data: id table entry */
+#define CS_AMBA_ID(pid)			\
+	{				\
+		.id	= pid,		\
+		.mask	= 0x000fffff,	\
+	}
+
+/* coresight AMBA ID, UCI with driver data only: id table entry. */
+#define CS_AMBA_ID_DATA(pid, dval)				\
+	{							\
+		.id	= pid,					\
+		.mask	= 0x000fffff,				\
+		.data	=  (void *)&(struct amba_cs_uci_id)	\
+			{				\
+				.data = (void *)dval,	\
+			}				\
+	}
+
+/* coresight AMBA ID, full UCI structure: id table entry. */
+#define __CS_AMBA_UCI_ID(pid, m, uci_ptr)	\
+	{					\
+		.id	= pid,			\
+		.mask	= m,			\
+		.data	= (void *)uci_ptr	\
+	}
+#define CS_AMBA_UCI_ID(pid, uci)	__CS_AMBA_UCI_ID(pid, 0x000fffff, uci)
+/*
+ * PIDR2[JEDEC], BIT(3) must be 1 (Read As One) to indicate that rest of the
+ * PIDR1, PIDR2 DES_* fields follow JEDEC encoding for the designer. Use that
+ * as a match value for blanket matching all devices in the given CoreSight
+ * device type and architecture.
+ */
+#define PIDR2_JEDEC			BIT(3)
+#define PID_PIDR2_JEDEC			(PIDR2_JEDEC << 16)
+/*
+ * Match all PIDs in a given CoreSight device type and architecture, defined
+ * by the uci.
+ */
+#define CS_AMBA_MATCH_ALL_UCI(uci)					\
+	__CS_AMBA_UCI_ID(PID_PIDR2_JEDEC, PID_PIDR2_JEDEC, uci)
+
+/* extract the data value from a UCI structure given amba_id pointer. */
+static inline void *coresight_get_uci_data(const struct amba_id *id)
+{
+	struct amba_cs_uci_id *uci_id = id->data;
+
+	if (!uci_id)
+		return NULL;
+
+	return uci_id->data;
+}
+
+static inline void *coresight_get_uci_data_from_amba(const struct amba_id *table, u32 pid)
+{
+	while (table->mask) {
+		if ((pid & table->mask) == table->id)
+			return coresight_get_uci_data(table);
+		table++;
+	};
+	return NULL;
+}
+
+int coresight_claim_device(struct gtrace_component *comp);
+int coresight_claim_device_unlocked(struct gtrace_component *comp);
+void coresight_clear_self_claim_tag(struct gtrace_platform_data *pdata);
+void coresight_clear_self_claim_tag_unlocked(struct gtrace_platform_data *pdata);
+void coresight_disclaim_device(struct gtrace_component *comp);
+void coresight_disclaim_device_unlocked(struct gtrace_component *comp);
+
+struct gtrace_platform_data *coresight_get_platform_data(struct device *dev);
+int coresight_get_enable_clocks(struct device *dev, struct clk **pclk,
+				struct clk **atclk);
+
+int coresight_init_driver_with_owner(const char *drv, struct amba_driver *amba_drv,
+				     struct platform_driver *pdev_drv, struct module *owner,
+				     const char *mod_name);
+#define coresight_init_driver(drv, amba_drv, pdev_drv) \
+	coresight_init_driver_with_owner(drv, amba_drv, pdev_drv, THIS_MODULE, KBUILD_MODNAME)
+
+void coresight_remove_driver(struct amba_driver *amba_drv,
+			     struct platform_driver *pdev_drv);
+
+#endif /* __LINUX_CORESIGHT_H__ */
diff --git a/include/linux/gtrace.h b/include/linux/gtrace.h
index ee60585ef65c..e9210b0a73b5 100644
--- a/include/linux/gtrace.h
+++ b/include/linux/gtrace.h
@@ -33,6 +33,13 @@ enum gtrace_component_type {
 	GTRACE_RVTRACE_RAMSINK,
 	GTRACE_RVTRACE_PIBSINK,
 	GTRACE_RVTRACE_ATBBRIDGE,
+	GTRACE_CORESIGHT_FUNNEL,
+	GTRACE_CORESIGHT_REPLICATOR,
+	GTRACE_CORESIGHT_TMC_ETB,
+	GTRACE_CORESIGHT_TMC_ETR,
+	GTRACE_CORESIGHT_TMC_ETF,
+	GTRACE_CORESIGHT_ETB,
+	GTRACE_CORESIGHT_TPIU,
 	GTRACE_COMPONENT_TYPE_MAX
 };
 
-- 
2.34.1


^ permalink raw reply	[flat|nested] 25+ messages in thread

* [PATCH 14/24] gtrace: Add CoreSight funnel driver
  2026-10-23  3:14 [RFC PATCH 00/24] Add Linux RISC-V trace support via the gtrace framework Zane Leung
                   ` (12 preceding siblings ...)
  2026-10-23  3:14 ` [PATCH 13/24] gtrace: Add CoreSight platform core APIs Zane Leung
@ 2026-10-23  3:14 ` Zane Leung
  2026-10-23  3:14 ` [PATCH 15/24] gtrace: Only copy AUX data from the path sink Zane Leung
                   ` (9 subsequent siblings)
  23 siblings, 0 replies; 25+ messages in thread
From: Zane Leung @ 2026-10-23  3:14 UTC (permalink / raw)
  To: gregkh, palmer, pjw, alexander.shishkin, suzuki.poulose,
	mchitale, anup, irogers
  Cc: mike.leach, james.clark, leo.yan, peterz, mingo, namhyung,
	mark.rutland, jolsa, adrian.hunter, alex, atish.patra,
	andrew.jones, sunilvl, liangzhen, zhuangqiubin, coresight,
	linux-riscv, linux-kernel

Add a CoreSight funnel driver for the gtrace framework, derived from
drivers/hwtracing/coresight/coresight-funnel.c.

Signed-off-by: Zane Leung <liangzhen@linux.spacemit.com>
---
 drivers/hwtracing/gtrace/Kconfig            |  13 +
 drivers/hwtracing/gtrace/Makefile           |   1 +
 drivers/hwtracing/gtrace/coresight-funnel.c | 430 ++++++++++++++++++++
 3 files changed, 444 insertions(+)
 create mode 100644 drivers/hwtracing/gtrace/coresight-funnel.c

diff --git a/drivers/hwtracing/gtrace/Kconfig b/drivers/hwtracing/gtrace/Kconfig
index 5dce4474db7c..081af9c0c670 100644
--- a/drivers/hwtracing/gtrace/Kconfig
+++ b/drivers/hwtracing/gtrace/Kconfig
@@ -101,3 +101,16 @@ config GTRACE_CORESIGHT
 
 	  To compile this driver as a module, choose M here: the module
 	  will be called coresight.
+
+config CORESIGHT_FUNNEL
+	tristate "CoreSight Trace Funnel driver"
+	depends on GTRACE_CORESIGHT
+	default y
+	help
+	  This driver provides support for the CoreSight funnel component.
+	  The funnel merges trace data from multiple input ports into a single
+	  output stream. It supports both static funnels (platform bus,
+	  arm,coresight-static-funnel) and dynamic funnels (AMBA bus).
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called coresight-funnel.
diff --git a/drivers/hwtracing/gtrace/Makefile b/drivers/hwtracing/gtrace/Makefile
index 0833a40c397c..d2745c961a75 100644
--- a/drivers/hwtracing/gtrace/Makefile
+++ b/drivers/hwtracing/gtrace/Makefile
@@ -12,3 +12,4 @@ obj-$(CONFIG_RVTRACE_RAMSINK) += rvtrace-ramsink.o
 obj-$(CONFIG_RVTRACE_FUNNEL) += rvtrace-funnel.o
 obj-$(CONFIG_RVTRACE_ATBBRIDGE) += rvtrace-atbbridge.o
 obj-$(CONFIG_GTRACE_CORESIGHT) += coresight-platform.o
+obj-$(CONFIG_CORESIGHT_FUNNEL) += coresight-funnel.o
diff --git a/drivers/hwtracing/gtrace/coresight-funnel.c b/drivers/hwtracing/gtrace/coresight-funnel.c
new file mode 100644
index 000000000000..e7a3f185da56
--- /dev/null
+++ b/drivers/hwtracing/gtrace/coresight-funnel.c
@@ -0,0 +1,430 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2011-2012, The Linux Foundation. All rights reserved.
+ *
+ * Description: CoreSight Funnel driver for the gtrace framework.
+ * Derived from drivers/hwtracing/coresight/coresight-funnel.c.
+ */
+
+#include <linux/acpi.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/fs.h>
+#include <linux/slab.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/amba/bus.h>
+#include <linux/clk.h>
+
+#include "coresight.h"
+
+#define FUNNEL_FUNCTL		0x000
+#define FUNNEL_PRICTL		0x004
+
+#define FUNNEL_HOLDTIME_MASK	0xf00
+#define FUNNEL_HOLDTIME_SHFT	0x8
+#define FUNNEL_HOLDTIME		(0x7 << FUNNEL_HOLDTIME_SHFT)
+#define FUNNEL_ENSx_MASK	0xff
+
+/**
+ * struct funnel_drvdata - specifics associated to a funnel component
+ * @base:	memory mapped base address for this component.
+ * @atclk:	optional clock for the core parts of the funnel.
+ * @pclk:	APB clock if present, otherwise NULL
+ * @comp:	component vitals needed by the framework.
+ * @priority:	port selection order.
+ */
+struct funnel_drvdata {
+	void __iomem		*base;
+	struct clk		*atclk;
+	struct clk		*pclk;
+	struct gtrace_component *comp;
+	unsigned long		priority;
+};
+
+static int dynamic_funnel_enable_hw(struct funnel_drvdata *drvdata, int port)
+{
+	u32 functl;
+	int rc = 0;
+	struct gtrace_component *comp = drvdata->comp;
+
+	CS_UNLOCK(drvdata->base);
+
+	functl = readl_relaxed(drvdata->base + FUNNEL_FUNCTL);
+	/* Claim the device only when we enable the first slave */
+	if (!(functl & FUNNEL_ENSx_MASK)) {
+		rc = coresight_claim_device_unlocked(comp);
+		if (rc)
+			goto done;
+	}
+
+	functl &= ~FUNNEL_HOLDTIME_MASK;
+	functl |= FUNNEL_HOLDTIME;
+	functl |= (1 << port);
+	writel_relaxed(functl, drvdata->base + FUNNEL_FUNCTL);
+	writel_relaxed(drvdata->priority, drvdata->base + FUNNEL_PRICTL);
+done:
+	CS_LOCK(drvdata->base);
+	return rc;
+}
+
+static int funnel_enable(struct gtrace_component *comp,
+			 struct gtrace_connection *in,
+			 struct gtrace_connection *out)
+{
+	int rc = 0;
+	struct funnel_drvdata *drvdata = dev_get_drvdata(comp->dev.parent);
+	bool first_enable = false;
+
+	if (in->dest_refcnt == 0) {
+		if (drvdata->base)
+			rc = dynamic_funnel_enable_hw(drvdata, in->dest_port);
+		if (!rc)
+			first_enable = true;
+	}
+	if (!rc)
+		in->dest_refcnt++;
+
+	if (first_enable)
+		dev_dbg(&comp->dev, "FUNNEL inport %d enabled\n",
+			in->dest_port);
+	return rc;
+}
+
+static void dynamic_funnel_disable_hw(struct funnel_drvdata *drvdata,
+				      int inport)
+{
+	u32 functl;
+	struct gtrace_component *comp = drvdata->comp;
+
+	CS_UNLOCK(drvdata->base);
+
+	functl = readl_relaxed(drvdata->base + FUNNEL_FUNCTL);
+	functl &= ~(1 << inport);
+	writel_relaxed(functl, drvdata->base + FUNNEL_FUNCTL);
+
+	/* Disclaim the device if none of the slaves are now active */
+	if (!(functl & FUNNEL_ENSx_MASK))
+		coresight_disclaim_device_unlocked(comp);
+
+	CS_LOCK(drvdata->base);
+}
+
+static void funnel_disable(struct gtrace_component *comp,
+			   struct gtrace_connection *in,
+			   struct gtrace_connection *out)
+{
+	struct funnel_drvdata *drvdata = dev_get_drvdata(comp->dev.parent);
+	bool last_disable = false;
+
+	if (--in->dest_refcnt == 0) {
+		if (drvdata->base)
+			dynamic_funnel_disable_hw(drvdata, in->dest_port);
+		last_disable = true;
+	}
+
+	if (last_disable)
+		dev_dbg(&comp->dev, "FUNNEL inport %d disabled\n",
+			in->dest_port);
+}
+
+static ssize_t priority_show(struct device *dev,
+			     struct device_attribute *attr, char *buf)
+{
+	struct funnel_drvdata *drvdata = dev_get_drvdata(dev->parent);
+	unsigned long val = drvdata->priority;
+
+	return sprintf(buf, "%#lx\n", val);
+}
+
+static ssize_t priority_store(struct device *dev,
+			      struct device_attribute *attr,
+			      const char *buf, size_t size)
+{
+	int ret;
+	unsigned long val;
+	struct funnel_drvdata *drvdata = dev_get_drvdata(dev->parent);
+
+	ret = kstrtoul(buf, 16, &val);
+	if (ret)
+		return ret;
+
+	drvdata->priority = val;
+	return size;
+}
+static DEVICE_ATTR_RW(priority);
+
+static u32 get_funnel_ctrl_hw(struct funnel_drvdata *drvdata)
+{
+	u32 functl;
+
+	CS_UNLOCK(drvdata->base);
+	functl = readl_relaxed(drvdata->base + FUNNEL_FUNCTL);
+	CS_LOCK(drvdata->base);
+
+	return functl;
+}
+
+static ssize_t funnel_ctrl_show(struct device *dev,
+			     struct device_attribute *attr, char *buf)
+{
+	u32 val;
+	struct funnel_drvdata *drvdata = dev_get_drvdata(dev->parent);
+
+	pm_runtime_get_sync(dev->parent);
+
+	val = get_funnel_ctrl_hw(drvdata);
+
+	pm_runtime_put(dev->parent);
+
+	return sprintf(buf, "%#x\n", val);
+}
+static DEVICE_ATTR_RO(funnel_ctrl);
+
+static struct attribute *coresight_funnel_attrs[] = {
+	&dev_attr_funnel_ctrl.attr,
+	&dev_attr_priority.attr,
+	NULL,
+};
+ATTRIBUTE_GROUPS(coresight_funnel);
+
+static int funnel_probe(struct device *dev, struct resource *res)
+{
+	void __iomem *base;
+	struct gtrace_platform_data *pdata = NULL;
+	struct funnel_drvdata *drvdata;
+	struct gtrace_component_id id = {
+		.type = GTRACE_CORESIGHT_FUNNEL,
+		.version = CORESIGHT_COMPONENT_ID_VERSION,
+	};
+	int ret;
+
+	if (is_of_node(dev_fwnode(dev)) &&
+	    of_device_is_compatible(dev->of_node, "arm,coresight-funnel"))
+		dev_warn_once(dev, "Uses OBSOLETE CoreSight funnel binding\n");
+
+	drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL);
+	if (!drvdata)
+		return -ENOMEM;
+
+	ret = coresight_get_enable_clocks(dev, &drvdata->pclk, &drvdata->atclk);
+	if (ret)
+		return ret;
+
+	pdata = coresight_get_platform_data(dev);
+	if (IS_ERR(pdata))
+		return PTR_ERR(pdata);
+
+	/*
+	 * Map the device base for dynamic-funnel, which has been
+	 * validated by AMBA core.
+	 */
+	if (res) {
+		base = devm_ioremap_resource(dev, res);
+		if (IS_ERR(base))
+			return PTR_ERR(base);
+		drvdata->base = base;
+		pdata->groups = coresight_funnel_groups;
+		pdata->io_mem = true;
+		pdata->base = base;
+		coresight_clear_self_claim_tag(pdata);
+	}
+
+	dev_set_drvdata(dev, drvdata);
+
+	drvdata->comp = gtrace_register_component(&id, "cs_funnel", pdata);
+	if (IS_ERR(drvdata->comp))
+		return PTR_ERR(drvdata->comp);
+
+	return 0;
+}
+
+static void funnel_remove(struct device *dev)
+{
+	struct funnel_drvdata *drvdata = dev_get_drvdata(dev);
+
+	gtrace_unregister_component(drvdata->comp);
+}
+
+#ifdef CONFIG_PM
+static int funnel_runtime_suspend(struct device *dev)
+{
+	struct funnel_drvdata *drvdata = dev_get_drvdata(dev);
+
+	clk_disable_unprepare(drvdata->atclk);
+	clk_disable_unprepare(drvdata->pclk);
+
+	return 0;
+}
+
+static int funnel_runtime_resume(struct device *dev)
+{
+	struct funnel_drvdata *drvdata = dev_get_drvdata(dev);
+	int ret;
+
+	ret = clk_prepare_enable(drvdata->pclk);
+	if (ret)
+		return ret;
+
+	ret = clk_prepare_enable(drvdata->atclk);
+	if (ret)
+		clk_disable_unprepare(drvdata->pclk);
+
+	return ret;
+}
+#endif
+
+static const struct dev_pm_ops funnel_dev_pm_ops = {
+	SET_RUNTIME_PM_OPS(funnel_runtime_suspend, funnel_runtime_resume, NULL)
+};
+
+static int funnel_platform_probe(struct platform_device *pdev)
+{
+	struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	int ret;
+
+	pm_runtime_get_noresume(&pdev->dev);
+	pm_runtime_set_active(&pdev->dev);
+	pm_runtime_enable(&pdev->dev);
+
+	ret = funnel_probe(&pdev->dev, res);
+	pm_runtime_put(&pdev->dev);
+	if (ret)
+		pm_runtime_disable(&pdev->dev);
+
+	return ret;
+}
+
+static void funnel_platform_remove(struct platform_device *pdev)
+{
+	struct funnel_drvdata *drvdata = dev_get_drvdata(&pdev->dev);
+
+	if (WARN_ON(!drvdata))
+		return;
+
+	funnel_remove(&pdev->dev);
+	pm_runtime_disable(&pdev->dev);
+}
+
+static const struct of_device_id funnel_match[] = {
+	{.compatible = "arm,coresight-static-funnel"},
+	{}
+};
+
+MODULE_DEVICE_TABLE(of, funnel_match);
+
+#ifdef CONFIG_ACPI
+static const struct acpi_device_id funnel_acpi_ids[] = {
+	{"ARMHC9FE", 0, 0, 0}, /* ARM Coresight Static Funnel */
+	{"ARMHC9FF", 0, 0, 0}, /* ARM CoreSight Dynamic Funnel */
+	{},
+};
+MODULE_DEVICE_TABLE(acpi, funnel_acpi_ids);
+#endif
+
+static struct platform_driver funnel_driver = {
+	.probe		= funnel_platform_probe,
+	.remove		= funnel_platform_remove,
+	.driver		= {
+		.name   = "coresight-funnel",
+		/* THIS_MODULE is taken care of by platform_driver_register() */
+		.of_match_table = funnel_match,
+		.acpi_match_table = ACPI_PTR(funnel_acpi_ids),
+		.pm	= &funnel_dev_pm_ops,
+		.suppress_bind_attrs = true,
+	},
+};
+
+static int dynamic_funnel_probe(struct amba_device *adev,
+				const struct amba_id *id)
+{
+	int ret;
+
+	ret = funnel_probe(&adev->dev, &adev->res);
+	if (!ret)
+		pm_runtime_put(&adev->dev);
+
+	return ret;
+}
+
+static void dynamic_funnel_remove(struct amba_device *adev)
+{
+	funnel_remove(&adev->dev);
+}
+
+static const struct amba_id dynamic_funnel_ids[] = {
+	{
+		.id     = 0x000bb908,
+		.mask   = 0x000fffff,
+	},
+	{
+		/* Coresight SoC-600 */
+		.id     = 0x000bb9eb,
+		.mask   = 0x000fffff,
+	},
+	{ 0, 0, NULL },
+};
+
+MODULE_DEVICE_TABLE(amba, dynamic_funnel_ids);
+
+static struct amba_driver dynamic_funnel_driver = {
+	.drv = {
+		.name	= "coresight-dynamic-funnel",
+		.pm	= &funnel_dev_pm_ops,
+		.suppress_bind_attrs = true,
+	},
+	.probe		= dynamic_funnel_probe,
+	.remove		= dynamic_funnel_remove,
+	.id_table	= dynamic_funnel_ids,
+};
+
+static const struct gtrace_component_id coresight_funnel_ids[] = {
+	{ .type = GTRACE_CORESIGHT_FUNNEL,
+	  .version = CORESIGHT_COMPONENT_ID_VERSION, },
+	{},
+};
+
+static struct gtrace_driver coresight_funnel_driver = {
+	.id_table = coresight_funnel_ids,
+	.start_port = funnel_enable,
+	.stop_port = funnel_disable,
+	.driver = {
+		.name = "coresight-funnel",
+	},
+};
+
+static int __init funnel_init(void)
+{
+	int ret;
+
+	ret = gtrace_register_driver(&coresight_funnel_driver);
+	if (ret)
+		return ret;
+
+	ret = coresight_init_driver("funnel", &dynamic_funnel_driver, &funnel_driver);
+	if (ret) {
+		gtrace_unregister_driver(&coresight_funnel_driver);
+		return ret;
+	}
+
+	return 0;
+}
+
+static void __exit funnel_exit(void)
+{
+	coresight_remove_driver(&dynamic_funnel_driver, &funnel_driver);
+	gtrace_unregister_driver(&coresight_funnel_driver);
+}
+
+module_init(funnel_init);
+module_exit(funnel_exit);
+
+MODULE_AUTHOR("Pratik Patel <pratikp@codeaurora.org>");
+MODULE_AUTHOR("Mathieu Poirier <mathieu.poirier@linaro.org>");
+MODULE_AUTHOR("Zane Leung <liangzhen@linux.spacemit.com>");
+MODULE_DESCRIPTION("Arm CoreSight Funnel Driver");
+MODULE_LICENSE("GPL v2");
-- 
2.34.1


^ permalink raw reply	[flat|nested] 25+ messages in thread

* [PATCH 15/24] gtrace: Only copy AUX data from the path sink
  2026-10-23  3:14 [RFC PATCH 00/24] Add Linux RISC-V trace support via the gtrace framework Zane Leung
                   ` (13 preceding siblings ...)
  2026-10-23  3:14 ` [PATCH 14/24] gtrace: Add CoreSight funnel driver Zane Leung
@ 2026-10-23  3:14 ` Zane Leung
  2026-10-23  3:14 ` [PATCH 16/24] perf: Flag whether gtrace AUX data carries CoreSight framing Zane Leung
                   ` (8 subsequent siblings)
  23 siblings, 0 replies; 25+ messages in thread
From: Zane Leung @ 2026-10-23  3:14 UTC (permalink / raw)
  To: gregkh, palmer, pjw, alexander.shishkin, suzuki.poulose,
	mchitale, anup, irogers
  Cc: mike.leach, james.clark, leo.yan, peterz, mingo, namhyung,
	mark.rutland, jolsa, adrian.hunter, alex, atish.patra,
	andrew.jones, sunilvl, liangzhen, zhuangqiubin, coresight,
	linux-riscv, linux-kernel

gtrace_path_copyto_auxbuf() walked the path and copied from the first
component implementing copyto_auxbuf(). For a linksink like ETF used as
a link, this picked the wrong component. Call the sink's callback
directly via gtrace_path_sink() and pass the perf handle so the sink
can set AUX flags itself.

Signed-off-by: Zane Leung <liangzhen@linux.spacemit.com>
---
 drivers/hwtracing/gtrace/gtrace-core.c     | 39 ++++++++++------------
 drivers/hwtracing/gtrace/gtrace-perf.c     | 13 ++------
 drivers/hwtracing/gtrace/rvtrace-ramsink.c | 10 ++++--
 include/linux/gtrace.h                     | 10 +++---
 4 files changed, 34 insertions(+), 38 deletions(-)

diff --git a/drivers/hwtracing/gtrace/gtrace-core.c b/drivers/hwtracing/gtrace/gtrace-core.c
index 231d06218d8e..a42b1844da29 100644
--- a/drivers/hwtracing/gtrace/gtrace-core.c
+++ b/drivers/hwtracing/gtrace/gtrace-core.c
@@ -806,14 +806,14 @@ int gtrace_path_stop(struct gtrace_path *path)
 EXPORT_SYMBOL_GPL(gtrace_path_stop);
 
 int gtrace_path_copyto_auxbuf(struct gtrace_path *path,
+			      struct perf_output_handle *handle,
 			      struct gtrace_perf_auxbuf *buf,
-			      size_t *bytes_copied, u64 *format)
+			      size_t *bytes_copied)
 {
-	struct gtrace_comp_priv *sink = to_gtrace_comp_priv(gtrace_path_sink(path));
-	const struct gtrace_driver *gtdrv;
-	struct gtrace_component *comp;
-	struct gtrace_path_node *node;
-	int ret = -EOPNOTSUPP;
+	struct gtrace_component *sink = gtrace_path_sink(path);
+	struct gtrace_comp_priv *spriv = to_gtrace_comp_priv(sink);
+	const struct gtrace_driver *sinkdrv = to_gtrace_driver(sink->dev.driver);
+	int ret = 0;
 	unsigned long flags;
 
 	/*
@@ -823,25 +823,20 @@ int gtrace_path_copyto_auxbuf(struct gtrace_path *path,
 	 * sink's lock also stops another path from starting the sink before copy
 	 * completes.
 	 */
-	raw_spin_lock_irqsave(&sink->lock, flags);
-	if (sink->start_count) {
-		*bytes_copied = 0;
-		ret = 0;
-		goto out;
-	}
-
-	list_for_each_entry(node, &path->comp_list, head) {
-		comp = node->comp;
-		gtdrv = to_gtrace_driver(comp->dev.driver);
-		if (!gtdrv || !gtdrv->copyto_auxbuf)
-			continue;
+	raw_spin_lock_irqsave(&spriv->lock, flags);
+	if (!spriv->start_count) {
+		if (!sinkdrv) {
+			ret = -ENODEV;
+			goto out;
+		}
 
-		*bytes_copied = gtdrv->copyto_auxbuf(comp, buf, format);
-		ret = 0;
-		break;
+		if (sinkdrv->copyto_auxbuf) {
+			*bytes_copied = sinkdrv->copyto_auxbuf(sink, handle, buf);
+			ret = 0;
+		}
 	}
 out:
-	raw_spin_unlock_irqrestore(&sink->lock, flags);
+	raw_spin_unlock_irqrestore(&spriv->lock, flags);
 	return ret;
 }
 EXPORT_SYMBOL_GPL(gtrace_path_copyto_auxbuf);
diff --git a/drivers/hwtracing/gtrace/gtrace-perf.c b/drivers/hwtracing/gtrace/gtrace-perf.c
index 604b673a29d5..3c8b620df5a8 100644
--- a/drivers/hwtracing/gtrace/gtrace-perf.c
+++ b/drivers/hwtracing/gtrace/gtrace-perf.c
@@ -243,7 +243,6 @@ static void gtrace_event_stop(struct perf_event *event, int mode)
 {
 	struct gtrace_ctxt *ctxt = this_cpu_ptr(&gtrace_ctxt);
 	struct perf_output_handle *handle = &ctxt->handle;
-	u64 format = PERF_AUX_FLAG_GTRACE_FORMAT_UNKNOWN;
 	struct gtrace_event_data *event_data;
 	int ret, cpu = smp_processor_id();
 	struct gtrace_path *path;
@@ -280,17 +279,11 @@ static void gtrace_event_stop(struct perf_event *event, int mode)
 	if (handle->event && (mode & PERF_EF_UPDATE)) {
 		if (WARN_ON_ONCE(handle->event != event))
 			return;
-		ret = gtrace_path_copyto_auxbuf(path, &event_data->buf, &size, &format);
+		ret = gtrace_path_copyto_auxbuf(path, handle, &event_data->buf, &size);
 		WARN_ON_ONCE(ret);
-		if (READ_ONCE(handle->event)) {
-			/* Tag the AUX data with the source's trace format. */
-			perf_aux_output_flag(handle, format);
-			if (size > handle->size) {
-				size = handle->size;
-				perf_aux_output_flag(handle, PERF_AUX_FLAG_TRUNCATED);
-			}
+		if (READ_ONCE(handle->event))
 			perf_aux_output_end(handle, size);
-		} else
+		else
 			WARN_ON(size);
 	}
 
diff --git a/drivers/hwtracing/gtrace/rvtrace-ramsink.c b/drivers/hwtracing/gtrace/rvtrace-ramsink.c
index 9588c32c5b13..e0826d3f0d60 100644
--- a/drivers/hwtracing/gtrace/rvtrace-ramsink.c
+++ b/drivers/hwtracing/gtrace/rvtrace-ramsink.c
@@ -11,6 +11,7 @@
 #include <linux/minmax.h>
 #include <linux/types.h>
 #include <linux/sizes.h>
+#include <linux/perf_event.h>
 #include <uapi/linux/perf_event.h>
 #include "rvtrace.h"
 
@@ -147,8 +148,8 @@ static void tbuf_to_pbuf_copy(struct trace_buf *src, struct trace_buf *dst, size
 }
 
 static size_t rvtrace_ramsink_copyto_auxbuf(struct gtrace_component *comp,
-					    struct gtrace_perf_auxbuf *buf,
-					    u64 *format)
+					    struct perf_output_handle *handle,
+					    struct gtrace_perf_auxbuf *buf)
 {
 	struct rvtrace_ramsink_priv *priv = dev_get_drvdata(&comp->dev);
 	struct trace_buf src, dst;
@@ -181,6 +182,11 @@ static size_t rvtrace_ramsink_copyto_auxbuf(struct gtrace_component *comp,
 		priv->prev_wp = wp;
 	}
 
+	if (bytes > handle->size) {
+		bytes = handle->size;
+		perf_aux_output_flag(handle, PERF_AUX_FLAG_TRUNCATED);
+	}
+
 	tbuf_to_pbuf_copy(&src, &dst, bytes);
 	dev_dbg(&comp->dev, "Copied %zu bytes\n", bytes);
 	return bytes;
diff --git a/include/linux/gtrace.h b/include/linux/gtrace.h
index e9210b0a73b5..39ae13701615 100644
--- a/include/linux/gtrace.h
+++ b/include/linux/gtrace.h
@@ -13,6 +13,7 @@
 #include <linux/platform_device.h>
 #include <linux/property.h>
 #include <linux/types.h>
+#include <linux/perf_event.h>
 
 #define GTRACE_PMU_ATTR_CFG_CONTEXT_SHIFT	1
 #define GTRACE_PMU_ATTR_CFG_SYNCMODE_MASK	GENMASK(3, 2)
@@ -363,15 +364,16 @@ struct gtrace_perf_auxbuf {
 };
 
 int gtrace_path_copyto_auxbuf(struct gtrace_path *path,
+			      struct perf_output_handle *handle,
 			      struct gtrace_perf_auxbuf *buf,
-			      size_t *bytes_copied, u64 *format);
+			      size_t *bytes_copied);
 
 /**
  * struct gtrace_driver - Representation of a trace driver.
  * @id_table:      Table to match components handled by the driver.
  * @copyto_auxbuf: Callback to copy data into perf AUX buffer. The driver
  *                 reports the PMU specific trace format of the copied data
- *                 via @format (see PERF_AUX_FLAG_PMU_FORMAT_TYPE_MASK).
+ *                 via @handle (see PERF_AUX_FLAG_PMU_FORMAT_TYPE_MASK).
  * @start:         Callback to start tracing.
  * @stop:          Callback to stop tracing.
  * @start_port:    Callback to start a specific component port.
@@ -385,8 +387,8 @@ int gtrace_path_copyto_auxbuf(struct gtrace_path *path,
 struct gtrace_driver {
 	const struct gtrace_component_id *id_table;
 	size_t			(*copyto_auxbuf)(struct gtrace_component *comp,
-						 struct gtrace_perf_auxbuf *buf,
-						 u64 *format);
+						 struct perf_output_handle *handle,
+						 struct gtrace_perf_auxbuf *buf);
 	int			(*start)(struct gtrace_component *comp);
 	int			(*stop)(struct gtrace_component *comp);
 	int			(*start_port)(struct gtrace_component *comp,
-- 
2.34.1


^ permalink raw reply	[flat|nested] 25+ messages in thread

* [PATCH 16/24] perf: Flag whether gtrace AUX data carries CoreSight framing
  2026-10-23  3:14 [RFC PATCH 00/24] Add Linux RISC-V trace support via the gtrace framework Zane Leung
                   ` (14 preceding siblings ...)
  2026-10-23  3:14 ` [PATCH 15/24] gtrace: Only copy AUX data from the path sink Zane Leung
@ 2026-10-23  3:14 ` Zane Leung
  2026-10-23  3:14 ` [PATCH 17/24] gtrace: Unify default sink selection across CPUs in perf setup Zane Leung
                   ` (7 subsequent siblings)
  23 siblings, 0 replies; 25+ messages in thread
From: Zane Leung @ 2026-10-23  3:14 UTC (permalink / raw)
  To: gregkh, palmer, pjw, alexander.shishkin, suzuki.poulose,
	mchitale, anup, irogers
  Cc: mike.leach, james.clark, leo.yan, peterz, mingo, namhyung,
	mark.rutland, jolsa, adrian.hunter, alex, atish.patra,
	andrew.jones, sunilvl, liangzhen, zhuangqiubin, coresight,
	linux-riscv, linux-kernel

Define PERF_AUX_FLAG_GTRACE_FORMAT_CORESIGHT for the gtrace format
which reports whether the data has been wrapped in CoreSight frame
formatting by a CoreSight sink.

Signed-off-by: Zane Leung <liangzhen@linux.spacemit.com>
---
 include/uapi/linux/perf_event.h       | 1 +
 tools/include/uapi/linux/perf_event.h | 1 +
 2 files changed, 2 insertions(+)

diff --git a/include/uapi/linux/perf_event.h b/include/uapi/linux/perf_event.h
index 2ac00a409a04..9d9ed2adc1d4 100644
--- a/include/uapi/linux/perf_event.h
+++ b/include/uapi/linux/perf_event.h
@@ -1315,6 +1315,7 @@ enum perf_callchain_context {
 #define PERF_AUX_FLAG_GTRACE_FORMAT_UNKNOWN	 0x0000
 #define PERF_AUX_FLAG_GTRACE_FORMAT_ETRACE	 0x0100 /* RISC-V E-trace format */
 #define PERF_AUX_FLAG_GTRACE_FORMAT_NTRACE	 0x0200 /* RISC-V N-Trace format */
+#define PERF_AUX_FLAG_GTRACE_FORMAT_CORESIGHT	 0x0300 /* Trace with CoreSight formatted frames */
 
 #define PERF_FLAG_FD_NO_GROUP			(1UL << 0)
 #define PERF_FLAG_FD_OUTPUT			(1UL << 1)
diff --git a/tools/include/uapi/linux/perf_event.h b/tools/include/uapi/linux/perf_event.h
index 2ac00a409a04..9d9ed2adc1d4 100644
--- a/tools/include/uapi/linux/perf_event.h
+++ b/tools/include/uapi/linux/perf_event.h
@@ -1315,6 +1315,7 @@ enum perf_callchain_context {
 #define PERF_AUX_FLAG_GTRACE_FORMAT_UNKNOWN	 0x0000
 #define PERF_AUX_FLAG_GTRACE_FORMAT_ETRACE	 0x0100 /* RISC-V E-trace format */
 #define PERF_AUX_FLAG_GTRACE_FORMAT_NTRACE	 0x0200 /* RISC-V N-Trace format */
+#define PERF_AUX_FLAG_GTRACE_FORMAT_CORESIGHT	 0x0300 /* Trace with CoreSight formatted frames */
 
 #define PERF_FLAG_FD_NO_GROUP			(1UL << 0)
 #define PERF_FLAG_FD_OUTPUT			(1UL << 1)
-- 
2.34.1


^ permalink raw reply	[flat|nested] 25+ messages in thread

* [PATCH 17/24] gtrace: Unify default sink selection across CPUs in perf setup
  2026-10-23  3:14 [RFC PATCH 00/24] Add Linux RISC-V trace support via the gtrace framework Zane Leung
                   ` (15 preceding siblings ...)
  2026-10-23  3:14 ` [PATCH 16/24] perf: Flag whether gtrace AUX data carries CoreSight framing Zane Leung
@ 2026-10-23  3:14 ` Zane Leung
  2026-10-23  3:14 ` [PATCH 18/24] gtrace: Add sink-specific buffer alloc/free callbacks Zane Leung
                   ` (6 subsequent siblings)
  23 siblings, 0 replies; 25+ messages in thread
From: Zane Leung @ 2026-10-23  3:14 UTC (permalink / raw)
  To: gregkh, palmer, pjw, alexander.shishkin, suzuki.poulose,
	mchitale, anup, irogers
  Cc: mike.leach, james.clark, leo.yan, peterz, mingo, namhyung,
	mark.rutland, jolsa, adrian.hunter, alex, atish.patra,
	andrew.jones, sunilvl, liangzhen, zhuangqiubin, coresight,
	linux-riscv, linux-kernel

In gtrace_setup_aux(), remember the sink found for the first CPU and
pass it to gtrace_create_path() for the remaining CPUs so they share a
compatible default sink. Clear a CPU from the mask when no path can be
built for it and abort when no sink is reachable at all.
---
 drivers/hwtracing/gtrace/gtrace-perf.c | 17 +++++++++++++++--
 1 file changed, 15 insertions(+), 2 deletions(-)

diff --git a/drivers/hwtracing/gtrace/gtrace-perf.c b/drivers/hwtracing/gtrace/gtrace-perf.c
index 3c8b620df5a8..9b874daa807f 100644
--- a/drivers/hwtracing/gtrace/gtrace-perf.c
+++ b/drivers/hwtracing/gtrace/gtrace-perf.c
@@ -109,6 +109,7 @@ static void *gtrace_setup_aux(struct perf_event *event, void **pages,
 			       int nr_pages, bool overwrite)
 {
 	struct gtrace_event_data *event_data = NULL;
+	struct gtrace_component *sink = NULL;
 	struct page **pagelist;
 	int cpu = event->cpu, i;
 	cpumask_t *mask;
@@ -120,7 +121,7 @@ static void *gtrace_setup_aux(struct perf_event *event, void **pages,
 	INIT_WORK(&event_data->work, free_event_data);
 	mask = &event_data->mask;
 	/*
-	 * Create the path for each CPU in the mask. In case of any failure skip the CPU
+	 * Create the path for each CPU in the mask. In case of any failure clear the CPU
 	 */
 	for_each_cpu(cpu, mask) {
 		struct gtrace_component *src;
@@ -132,16 +133,28 @@ static void *gtrace_setup_aux(struct perf_event *event, void **pages,
 			continue;
 		}
 
-		path = gtrace_create_path(src, NULL, GTRACE_COMPONENT_MODE_PERF);
+		path = gtrace_create_path(src, sink, GTRACE_COMPONENT_MODE_PERF);
 		if (IS_ERR(path)) {
 			cpumask_clear_cpu(cpu, mask);
 			continue;
 		}
 
+		/*
+		 * The first found sink is saved here and passed to
+		 * gtrace_create_path() to check whether the remaining sources
+		 * have a compatible default sink.
+		 */
+		if (!sink)
+			sink = gtrace_path_sink(path);
+
 		path->event = event;
 		*gtrace_event_cpu_path_ptr(event_data, cpu) = path;
 	}
 
+	/* no sink found for any CPU - cannot trace */
+	if (!sink)
+		goto err;
+
 	/* If we don't have any CPUs ready for tracing, abort */
 	cpu = cpumask_first(&event_data->mask);
 	if (cpu >= nr_cpu_ids)
-- 
2.34.1


^ permalink raw reply	[flat|nested] 25+ messages in thread

* [PATCH 18/24] gtrace: Add sink-specific buffer alloc/free callbacks
  2026-10-23  3:14 [RFC PATCH 00/24] Add Linux RISC-V trace support via the gtrace framework Zane Leung
                   ` (16 preceding siblings ...)
  2026-10-23  3:14 ` [PATCH 17/24] gtrace: Unify default sink selection across CPUs in perf setup Zane Leung
@ 2026-10-23  3:14 ` Zane Leung
  2026-10-23  3:15 ` [PATCH 19/24] gtrace: Add setup_sysfs_buf callback to setup sysfs sink buffer Zane Leung
                   ` (5 subsequent siblings)
  23 siblings, 0 replies; 25+ messages in thread
From: Zane Leung @ 2026-10-23  3:14 UTC (permalink / raw)
  To: gregkh, palmer, pjw, alexander.shishkin, suzuki.poulose,
	mchitale, anup, irogers
  Cc: mike.leach, james.clark, leo.yan, peterz, mingo, namhyung,
	mark.rutland, jolsa, adrian.hunter, alex, atish.patra,
	andrew.jones, sunilvl, liangzhen, zhuangqiubin, coresight,
	linux-riscv, linux-kernel

Extend gtrace_driver with alloc_buf() and free_buf() so a sink can
allocate its own buffer (e.g. an ETR SG table) during perf setup and
release it when the event data is freed. Store the resulting buffer in
gtrace_perf_auxbuf along with the event pid and snapshot mode.

Signed-off-by: Zane Leung <liangzhen@linux.spacemit.com>
---
 drivers/hwtracing/gtrace/gtrace-perf.c | 34 ++++++++++++++++++++++++++
 include/linux/gtrace.h                 | 12 +++++++++
 2 files changed, 46 insertions(+)

diff --git a/drivers/hwtracing/gtrace/gtrace-perf.c b/drivers/hwtracing/gtrace/gtrace-perf.c
index 9b874daa807f..e05a833aa6a4 100644
--- a/drivers/hwtracing/gtrace/gtrace-perf.c
+++ b/drivers/hwtracing/gtrace/gtrace-perf.c
@@ -86,6 +86,27 @@ static struct gtrace_path **gtrace_event_cpu_path_ptr(struct gtrace_event_data *
 	return per_cpu_ptr(data->path, cpu);
 }
 
+static void free_sink_buffer(struct gtrace_event_data *event_data)
+{
+	int cpu;
+	cpumask_t *mask = &event_data->mask;
+	struct gtrace_path *path;
+	struct gtrace_component *sink;
+	struct gtrace_driver *sinkdrv;
+
+	if (!event_data->buf.buf)
+		return;
+
+	if (WARN_ON(cpumask_empty(mask)))
+		return;
+
+	cpu = cpumask_first(mask);
+	path = *gtrace_event_cpu_path_ptr(event_data, cpu);
+	sink = gtrace_path_sink(path);
+	sinkdrv = to_gtrace_driver(sink->dev.driver);
+	sinkdrv->free_buf(event_data->buf.buf);
+}
+
 static void free_event_data(struct work_struct *work)
 {
 	struct gtrace_event_data *event_data;
@@ -95,6 +116,7 @@ static void free_event_data(struct work_struct *work)
 
 	event_data = container_of(work, struct gtrace_event_data, work);
 	mask = &event_data->mask;
+	free_sink_buffer(event_data);
 	for_each_cpu(cpu, mask) {
 		path = *gtrace_event_cpu_path_ptr(event_data, cpu);
 		gtrace_destroy_path(path);
@@ -110,6 +132,7 @@ static void *gtrace_setup_aux(struct perf_event *event, void **pages,
 {
 	struct gtrace_event_data *event_data = NULL;
 	struct gtrace_component *sink = NULL;
+	struct gtrace_driver *sinkdrv = NULL;
 	struct page **pagelist;
 	int cpu = event->cpu, i;
 	cpumask_t *mask;
@@ -175,6 +198,17 @@ static void *gtrace_setup_aux(struct perf_event *event, void **pages,
 	event_data->buf.nr_pages = nr_pages;
 	event_data->buf.length = nr_pages * PAGE_SIZE;
 	event_data->buf.pos = 0;
+	event_data->buf.snapshot = overwrite;
+	event_data->buf.pages = pages;
+
+	sinkdrv = to_gtrace_driver(sink->dev.driver);
+	if (sinkdrv && sinkdrv->alloc_buf) {
+		event_data->buf.buf = sinkdrv->alloc_buf(sink, event, pages,
+							 nr_pages, overwrite);
+		if (!event_data->buf.buf)
+			goto err;
+	}
+
 	return event_data;
 err:
 	gtrace_free_aux(event_data);
diff --git a/include/linux/gtrace.h b/include/linux/gtrace.h
index 39ae13701615..a3d125882ec9 100644
--- a/include/linux/gtrace.h
+++ b/include/linux/gtrace.h
@@ -355,12 +355,18 @@ int gtrace_path_stop(struct gtrace_path *path);
  * @nr_pages: Number of pages of the AUX buffer.
  * @base:     Start address of AUX buffer.
  * @pos:      Position in the AUX buffer to commit traced data.
+ * @snapshot: Is this run in snapshot mode.
+ * @pages:    a handle to the ring buffer.
+ * @buf:      Optional sink-specific buffer.
  */
 struct gtrace_perf_auxbuf {
 	size_t length;
 	int nr_pages;
 	void *base;
 	long pos;
+	bool snapshot;
+	void **pages;
+	void *buf;
 };
 
 int gtrace_path_copyto_auxbuf(struct gtrace_path *path,
@@ -371,6 +377,8 @@ int gtrace_path_copyto_auxbuf(struct gtrace_path *path,
 /**
  * struct gtrace_driver - Representation of a trace driver.
  * @id_table:      Table to match components handled by the driver.
+ * @alloc_buf:     Callback to alloc sink-specific buffer.
+ * @free_buf:      Callback to free sink-specific buffer.
  * @copyto_auxbuf: Callback to copy data into perf AUX buffer. The driver
  *                 reports the PMU specific trace format of the copied data
  *                 via @handle (see PERF_AUX_FLAG_PMU_FORMAT_TYPE_MASK).
@@ -386,6 +394,10 @@ int gtrace_path_copyto_auxbuf(struct gtrace_path *path,
  */
 struct gtrace_driver {
 	const struct gtrace_component_id *id_table;
+	void			*(*alloc_buf)(struct gtrace_component *comp,
+					     struct perf_event *event, void **pages,
+					     int nr_pages, bool overwrite);
+	void			(*free_buf)(void *buf);
 	size_t			(*copyto_auxbuf)(struct gtrace_component *comp,
 						 struct perf_output_handle *handle,
 						 struct gtrace_perf_auxbuf *buf);
-- 
2.34.1


^ permalink raw reply	[flat|nested] 25+ messages in thread

* [PATCH 19/24] gtrace: Add setup_sysfs_buf callback to setup sysfs sink buffer
  2026-10-23  3:14 [RFC PATCH 00/24] Add Linux RISC-V trace support via the gtrace framework Zane Leung
                   ` (17 preceding siblings ...)
  2026-10-23  3:14 ` [PATCH 18/24] gtrace: Add sink-specific buffer alloc/free callbacks Zane Leung
@ 2026-10-23  3:15 ` Zane Leung
  2026-10-23  3:15 ` [PATCH 20/24] gtrace: Add CoreSight TMC driver support Zane Leung
                   ` (4 subsequent siblings)
  23 siblings, 0 replies; 25+ messages in thread
From: Zane Leung @ 2026-10-23  3:15 UTC (permalink / raw)
  To: gregkh, palmer, pjw, alexander.shishkin, suzuki.poulose,
	mchitale, anup, irogers
  Cc: mike.leach, james.clark, leo.yan, peterz, mingo, namhyung,
	mark.rutland, jolsa, adrian.hunter, alex, atish.patra,
	andrew.jones, sunilvl, liangzhen, zhuangqiubin, coresight,
	linux-riscv, linux-kernel

Add a setup_sysfs_buf() callback to gtrace_driver and invoke it from
gtrace_enable_sysfs() before the path is started, so the sink can
allocate its sysfs trace buffer outside the spinlock held by the core
start path.

Signed-off-by: Zane Leung <liangzhen@linux.spacemit.com>
---
 drivers/hwtracing/gtrace/gtrace-sysfs.c | 12 ++++++++++++
 include/linux/gtrace.h                  |  2 ++
 2 files changed, 14 insertions(+)

diff --git a/drivers/hwtracing/gtrace/gtrace-sysfs.c b/drivers/hwtracing/gtrace/gtrace-sysfs.c
index 5f4f3fcb56a4..23b0a21ef9c0 100644
--- a/drivers/hwtracing/gtrace/gtrace-sysfs.c
+++ b/drivers/hwtracing/gtrace/gtrace-sysfs.c
@@ -85,6 +85,7 @@ static int gtrace_validate_source_sysfs(struct gtrace_component *comp,
 int gtrace_enable_sysfs(struct gtrace_component *comp)
 {
 	struct gtrace_component *sink;
+	struct gtrace_driver *sinkdrv;
 	struct gtrace_path *path;
 	int ret;
 
@@ -114,6 +115,17 @@ int gtrace_enable_sysfs(struct gtrace_component *comp)
 
 	mutex_lock(&gtrace_mutex);
 
+	if (!sink)
+		sink = gtrace_path_sink(path);
+	sinkdrv = to_gtrace_driver(sink->dev.driver);
+	if (sinkdrv && sinkdrv->setup_sysfs_buf) {
+		ret = sinkdrv->setup_sysfs_buf(sink);
+		if (ret) {
+			gtrace_destroy_path(path);
+			goto out;
+		}
+	}
+
 	ret = gtrace_path_start(path);
 	if (ret)
 		gtrace_destroy_path(path);
diff --git a/include/linux/gtrace.h b/include/linux/gtrace.h
index a3d125882ec9..0957d0490c57 100644
--- a/include/linux/gtrace.h
+++ b/include/linux/gtrace.h
@@ -377,6 +377,7 @@ int gtrace_path_copyto_auxbuf(struct gtrace_path *path,
 /**
  * struct gtrace_driver - Representation of a trace driver.
  * @id_table:      Table to match components handled by the driver.
+ * @setup_sysfs_buf: Callback to setup sink-specific sysfs buffer.
  * @alloc_buf:     Callback to alloc sink-specific buffer.
  * @free_buf:      Callback to free sink-specific buffer.
  * @copyto_auxbuf: Callback to copy data into perf AUX buffer. The driver
@@ -394,6 +395,7 @@ int gtrace_path_copyto_auxbuf(struct gtrace_path *path,
  */
 struct gtrace_driver {
 	const struct gtrace_component_id *id_table;
+	int			(*setup_sysfs_buf)(struct gtrace_component *comp);
 	void			*(*alloc_buf)(struct gtrace_component *comp,
 					     struct perf_event *event, void **pages,
 					     int nr_pages, bool overwrite);
-- 
2.34.1


^ permalink raw reply	[flat|nested] 25+ messages in thread

* [PATCH 20/24] gtrace: Add CoreSight TMC driver support
  2026-10-23  3:14 [RFC PATCH 00/24] Add Linux RISC-V trace support via the gtrace framework Zane Leung
                   ` (18 preceding siblings ...)
  2026-10-23  3:15 ` [PATCH 19/24] gtrace: Add setup_sysfs_buf callback to setup sysfs sink buffer Zane Leung
@ 2026-10-23  3:15 ` Zane Leung
  2026-10-23  3:15 ` [PATCH 21/24] perf tools: riscv: making gtrace PMU listable Zane Leung
                   ` (3 subsequent siblings)
  23 siblings, 0 replies; 25+ messages in thread
From: Zane Leung @ 2026-10-23  3:15 UTC (permalink / raw)
  To: gregkh, palmer, pjw, alexander.shishkin, suzuki.poulose,
	mchitale, anup, irogers
  Cc: mike.leach, james.clark, leo.yan, peterz, mingo, namhyung,
	mark.rutland, jolsa, adrian.hunter, alex, atish.patra,
	andrew.jones, sunilvl, liangzhen, zhuangqiubin, coresight,
	linux-riscv, linux-kernel

Add CoreSight Trace Memory Controller (TMC) driver as part of the
gtrace framework. ETB and ETF share one driver (ETF additionally
works as a link), while ETR uses system memory and provides perf
buffer alloc/free callbacks.

Signed-off-by: Zane Leung <liangzhen@linux.spacemit.com>
---
 .../hwtracing/coresight/coresight-tmc-etf.c   |    1 -
 drivers/hwtracing/gtrace/Kconfig              |   13 +
 drivers/hwtracing/gtrace/Makefile             |    2 +
 drivers/hwtracing/gtrace/coresight-tmc-core.c | 1111 ++++++++++
 drivers/hwtracing/gtrace/coresight-tmc-etf.c  |  573 +++++
 drivers/hwtracing/gtrace/coresight-tmc-etr.c  | 1888 +++++++++++++++++
 drivers/hwtracing/gtrace/coresight-tmc.h      |  469 ++++
 7 files changed, 4056 insertions(+), 1 deletion(-)
 create mode 100644 drivers/hwtracing/gtrace/coresight-tmc-core.c
 create mode 100644 drivers/hwtracing/gtrace/coresight-tmc-etf.c
 create mode 100644 drivers/hwtracing/gtrace/coresight-tmc-etr.c
 create mode 100644 drivers/hwtracing/gtrace/coresight-tmc.h

diff --git a/drivers/hwtracing/coresight/coresight-tmc-etf.c b/drivers/hwtracing/coresight/coresight-tmc-etf.c
index 8882b1c4cdc0..11082fd249eb 100644
--- a/drivers/hwtracing/coresight/coresight-tmc-etf.c
+++ b/drivers/hwtracing/coresight/coresight-tmc-etf.c
@@ -80,7 +80,6 @@ static void tmc_etb_dump_hw(struct tmc_drvdata *drvdata)
 
 	if (lost)
 		coresight_insert_barrier_packet(drvdata->buf);
-	return;
 }
 
 static void __tmc_etb_disable_hw(struct tmc_drvdata *drvdata)
diff --git a/drivers/hwtracing/gtrace/Kconfig b/drivers/hwtracing/gtrace/Kconfig
index 081af9c0c670..83ee442ad860 100644
--- a/drivers/hwtracing/gtrace/Kconfig
+++ b/drivers/hwtracing/gtrace/Kconfig
@@ -114,3 +114,16 @@ config CORESIGHT_FUNNEL
 
 	  To compile this driver as a module, choose M here: the module
 	  will be called coresight-funnel.
+
+config CORESIGHT_TMC
+	tristate "CoreSight generic TMC driver"
+	depends on GTRACE_CORESIGHT
+	default y
+	help
+	  This enables support for the CoreSight Trace Memory Controller
+	  (TMC) driver. Depending on its configuration the device can act
+	  as an embedded trace buffer (ETB), an embedded trace FIFO (ETF)
+	  or a trace router (ETR) that stores trace data in system memory.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called coresight-tmc.
diff --git a/drivers/hwtracing/gtrace/Makefile b/drivers/hwtracing/gtrace/Makefile
index d2745c961a75..e995ba8e7fc1 100644
--- a/drivers/hwtracing/gtrace/Makefile
+++ b/drivers/hwtracing/gtrace/Makefile
@@ -13,3 +13,5 @@ obj-$(CONFIG_RVTRACE_FUNNEL) += rvtrace-funnel.o
 obj-$(CONFIG_RVTRACE_ATBBRIDGE) += rvtrace-atbbridge.o
 obj-$(CONFIG_GTRACE_CORESIGHT) += coresight-platform.o
 obj-$(CONFIG_CORESIGHT_FUNNEL) += coresight-funnel.o
+obj-$(CONFIG_CORESIGHT_TMC) += coresight-tmc.o
+coresight-tmc-y := coresight-tmc-core.o coresight-tmc-etf.o coresight-tmc-etr.o
diff --git a/drivers/hwtracing/gtrace/coresight-tmc-core.c b/drivers/hwtracing/gtrace/coresight-tmc-core.c
new file mode 100644
index 000000000000..26572f1d3b6f
--- /dev/null
+++ b/drivers/hwtracing/gtrace/coresight-tmc-core.c
@@ -0,0 +1,1111 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2012, The Linux Foundation. All rights reserved.
+ *
+ * Description: CoreSight Trace Memory Controller driver
+ * Derived from drivers/hwtracing/coresight/coresight-tmc-core.c
+ */
+
+#include <linux/acpi.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/device.h>
+#include <linux/idr.h>
+#include <linux/io.h>
+#include <linux/iommu.h>
+#include <linux/err.h>
+#include <linux/fs.h>
+#include <linux/miscdevice.h>
+#include <linux/mutex.h>
+#include <linux/property.h>
+#include <linux/uaccess.h>
+#include <linux/slab.h>
+#include <linux/dma-mapping.h>
+#include <linux/spinlock.h>
+#include <linux/pm_runtime.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_reserved_mem.h>
+#include <linux/gtrace.h>
+#include <linux/amba/bus.h>
+#include <linux/platform_device.h>
+
+#include "coresight.h"
+#include "coresight-tmc.h"
+
+int tmc_wait_for_tmcready(struct tmc_drvdata *drvdata)
+{
+	struct gtrace_component *comp = drvdata->comp;
+	struct gtrace_platform_data *pdata = comp->pdata;
+
+	/* Ensure formatter, unformatter and hardware fifo are empty */
+	if (gtrace_poll_bit(pdata, TMC_STS, TMC_STS_TMCREADY_BIT, 1,
+			    pdata->control_poll_timeout_usecs)) {
+		dev_err(&comp->dev,
+			"timeout while waiting for TMC to be Ready\n");
+		return -EBUSY;
+	}
+	return 0;
+}
+
+void tmc_flush_and_stop(struct tmc_drvdata *drvdata)
+{
+	struct gtrace_component *comp = drvdata->comp;
+	struct gtrace_platform_data *pdata = comp->pdata;
+	u32 ffcr;
+
+	ffcr = readl_relaxed(drvdata->base + TMC_FFCR);
+	ffcr |= TMC_FFCR_STOP_ON_FLUSH;
+	writel_relaxed(ffcr, drvdata->base + TMC_FFCR);
+	ffcr |= BIT(TMC_FFCR_FLUSHMAN_BIT);
+	writel_relaxed(ffcr, drvdata->base + TMC_FFCR);
+	/* Ensure flush completes */
+	if (gtrace_poll_bit(pdata, TMC_FFCR, TMC_FFCR_FLUSHMAN_BIT, 0,
+			    pdata->control_poll_timeout_usecs)) {
+		dev_err(&comp->dev,
+		"timeout while waiting for completion of Manual Flush\n");
+	}
+
+	tmc_wait_for_tmcready(drvdata);
+}
+
+void tmc_enable_hw(struct tmc_drvdata *drvdata)
+{
+	writel_relaxed(TMC_CTL_CAPT_EN, drvdata->base + TMC_CTL);
+}
+
+void tmc_disable_hw(struct tmc_drvdata *drvdata)
+{
+	writel_relaxed(0x0, drvdata->base + TMC_CTL);
+}
+
+u32 tmc_get_memwidth_mask(struct tmc_drvdata *drvdata)
+{
+	u32 mask = 0;
+
+	/*
+	 * When moving RRP or an offset address forward, the new values must
+	 * be byte-address aligned to the width of the trace memory databus
+	 * _and_ to a frame boundary (16 byte), whichever is the biggest. For
+	 * example, for 32-bit, 64-bit and 128-bit wide trace memory, the four
+	 * LSBs must be 0s. For 256-bit wide trace memory, the five LSBs must
+	 * be 0s.
+	 */
+	switch (drvdata->memwidth) {
+	case TMC_MEM_INTF_WIDTH_32BITS:
+	case TMC_MEM_INTF_WIDTH_64BITS:
+	case TMC_MEM_INTF_WIDTH_128BITS:
+		mask = GENMASK(31, 4);
+		break;
+	case TMC_MEM_INTF_WIDTH_256BITS:
+		mask = GENMASK(31, 5);
+		break;
+	}
+
+	return mask;
+}
+
+static bool is_tmc_crashdata_valid(struct tmc_drvdata *drvdata)
+{
+	struct tmc_crash_metadata *mdata;
+
+	if (!tmc_has_reserved_buffer(drvdata) ||
+	    !tmc_has_crash_mdata_buffer(drvdata))
+		return false;
+
+	mdata = drvdata->crash_mdata.vaddr;
+
+	/* Check version match */
+	if (mdata->version != CS_CRASHDATA_VERSION)
+		return false;
+
+	/* Check for valid metadata */
+	if (!mdata->valid) {
+		dev_dbg(&drvdata->comp->dev,
+			"Data invalid in tmc crash metadata\n");
+		return false;
+	}
+
+	/*
+	 * Buffer address given by metadata for retrieval of trace data
+	 * from previous boot is expected to be same as the reserved
+	 * trace buffer memory region provided through DTS
+	 */
+	if (drvdata->resrv_buf.paddr != mdata->trace_paddr) {
+		dev_dbg(&drvdata->comp->dev,
+			"Trace buffer address of previous boot invalid\n");
+		return false;
+	}
+
+	/* Check data integrity of metadata */
+	if (mdata->crc32_mdata != find_crash_metadata_crc(mdata)) {
+		dev_err(&drvdata->comp->dev,
+			"CRC mismatch in tmc crash metadata\n");
+		return false;
+	}
+	/* Check data integrity of tracedata */
+	if (mdata->crc32_tdata != find_crash_tracedata_crc(drvdata, mdata)) {
+		dev_err(&drvdata->comp->dev,
+			"CRC mismatch in tmc crash tracedata\n");
+		return false;
+	}
+
+	return true;
+}
+
+static inline ssize_t tmc_get_resvbuf_trace(struct tmc_drvdata *drvdata,
+					  loff_t pos, size_t len, char **bufpp)
+{
+	s64 offset;
+	ssize_t actual = len;
+	struct tmc_resrv_buf *rbuf = &drvdata->resrv_buf;
+
+	if (pos + actual > rbuf->len)
+		actual = rbuf->len - pos;
+	if (actual <= 0)
+		return 0;
+
+	/* Compute the offset from which we read the data */
+	offset = rbuf->offset + pos;
+	if (offset >= rbuf->size)
+		offset -= rbuf->size;
+
+	/* Adjust the length to limit this transaction to end of buffer */
+	actual = (actual < (rbuf->size - offset)) ?
+		actual : rbuf->size - offset;
+
+	*bufpp = (char *)rbuf->vaddr + offset;
+
+	return actual;
+}
+
+static int tmc_prepare_crashdata(struct tmc_drvdata *drvdata)
+{
+	char *bufp;
+	ssize_t len;
+	u32 status, size;
+	u64 rrp, rwp, dba;
+	struct tmc_resrv_buf *rbuf;
+	struct tmc_crash_metadata *mdata;
+
+	mdata = drvdata->crash_mdata.vaddr;
+	rbuf = &drvdata->resrv_buf;
+
+	rrp = mdata->tmc_rrp;
+	rwp = mdata->tmc_rwp;
+	dba = mdata->tmc_dba;
+	status = mdata->tmc_sts;
+	size = mdata->tmc_ram_size << 2;
+
+	/* Sync the buffer pointers */
+	rbuf->offset = rrp - dba;
+	if (status & TMC_STS_FULL)
+		rbuf->len = size;
+	else
+		rbuf->len = rwp - rrp;
+
+	/* Additional sanity checks for validating metadata */
+	if ((rbuf->offset > size) ||
+	    (rbuf->len > size)) {
+		dev_dbg(&drvdata->comp->dev,
+			"Offset and length invalid in tmc crash metadata\n");
+		return -EINVAL;
+	}
+
+	if (status & TMC_STS_FULL) {
+		len = tmc_get_resvbuf_trace(drvdata, 0x0,
+					    CORESIGHT_BARRIER_PKT_SIZE, &bufp);
+		if (len >= CORESIGHT_BARRIER_PKT_SIZE) {
+			coresight_insert_barrier_packet(bufp);
+			/* Recalculate crc */
+			mdata->crc32_tdata = find_crash_tracedata_crc(drvdata,
+								      mdata);
+			mdata->crc32_mdata = find_crash_metadata_crc(mdata);
+		}
+	}
+
+	return 0;
+}
+
+static int tmc_read_prepare(struct tmc_drvdata *drvdata)
+{
+	int ret = 0;
+
+	switch (drvdata->config_type) {
+	case TMC_CONFIG_TYPE_ETB:
+	case TMC_CONFIG_TYPE_ETF:
+		ret = tmc_read_prepare_etb(drvdata);
+		break;
+	case TMC_CONFIG_TYPE_ETR:
+		ret = tmc_read_prepare_etr(drvdata);
+		break;
+	default:
+		ret = -EINVAL;
+	}
+
+	if (!ret)
+		dev_dbg(&drvdata->comp->dev, "TMC read start\n");
+
+	return ret;
+}
+
+static int tmc_read_unprepare(struct tmc_drvdata *drvdata)
+{
+	int ret = 0;
+
+	switch (drvdata->config_type) {
+	case TMC_CONFIG_TYPE_ETB:
+	case TMC_CONFIG_TYPE_ETF:
+		ret = tmc_read_unprepare_etb(drvdata);
+		break;
+	case TMC_CONFIG_TYPE_ETR:
+		ret = tmc_read_unprepare_etr(drvdata);
+		break;
+	default:
+		ret = -EINVAL;
+	}
+
+	if (!ret)
+		dev_dbg(&drvdata->comp->dev, "TMC read end\n");
+
+	return ret;
+}
+
+static int tmc_open(struct inode *inode, struct file *file)
+{
+	int ret;
+	struct tmc_drvdata *drvdata = container_of(file->private_data,
+						   struct tmc_drvdata, miscdev);
+
+	ret = tmc_read_prepare(drvdata);
+	if (ret)
+		return ret;
+
+	nonseekable_open(inode, file);
+
+	dev_dbg(&drvdata->comp->dev, "%s: successfully opened\n", __func__);
+	return 0;
+}
+
+static ssize_t tmc_get_sysfs_trace(struct tmc_drvdata *drvdata, loff_t pos, size_t len,
+				   char **bufpp)
+{
+	switch (drvdata->config_type) {
+	case TMC_CONFIG_TYPE_ETB:
+	case TMC_CONFIG_TYPE_ETF:
+		return tmc_etb_get_sysfs_trace(drvdata, pos, len, bufpp);
+	case TMC_CONFIG_TYPE_ETR:
+		return tmc_etr_get_sysfs_trace(drvdata, pos, len, bufpp);
+	}
+
+	return -EINVAL;
+}
+
+static ssize_t tmc_read(struct file *file, char __user *data, size_t len,
+			loff_t *ppos)
+{
+	char *bufp;
+	ssize_t actual;
+	struct tmc_drvdata *drvdata = container_of(file->private_data,
+						   struct tmc_drvdata, miscdev);
+	actual = tmc_get_sysfs_trace(drvdata, *ppos, len, &bufp);
+	if (actual <= 0)
+		return 0;
+
+	if (copy_to_user(data, bufp, actual)) {
+		dev_dbg(&drvdata->comp->dev,
+			"%s: copy_to_user failed\n", __func__);
+		return -EFAULT;
+	}
+
+	*ppos += actual;
+	dev_dbg(&drvdata->comp->dev, "%zu bytes copied\n", actual);
+
+	return actual;
+}
+
+static int tmc_release(struct inode *inode, struct file *file)
+{
+	int ret;
+	struct tmc_drvdata *drvdata = container_of(file->private_data,
+						   struct tmc_drvdata, miscdev);
+
+	ret = tmc_read_unprepare(drvdata);
+	if (ret)
+		return ret;
+
+	dev_dbg(&drvdata->comp->dev, "%s: released\n", __func__);
+	return 0;
+}
+
+static const struct file_operations tmc_fops = {
+	.owner		= THIS_MODULE,
+	.open		= tmc_open,
+	.read		= tmc_read,
+	.release	= tmc_release,
+};
+
+static int tmc_crashdata_open(struct inode *inode, struct file *file)
+{
+	int err = 0;
+	unsigned long flags;
+	struct tmc_resrv_buf *rbuf;
+	struct tmc_crash_metadata *mdata;
+	struct tmc_drvdata *drvdata = container_of(file->private_data,
+						   struct tmc_drvdata,
+						   crashdev);
+
+	mdata = drvdata->crash_mdata.vaddr;
+	rbuf = &drvdata->resrv_buf;
+
+	raw_spin_lock_irqsave(&drvdata->spinlock, flags);
+	if (mdata->valid)
+		rbuf->reading = true;
+	else
+		err = -ENOENT;
+	raw_spin_unlock_irqrestore(&drvdata->spinlock, flags);
+	if (err)
+		goto exit;
+
+	nonseekable_open(inode, file);
+	dev_dbg(&drvdata->comp->dev, "%s: successfully opened\n", __func__);
+exit:
+	return err;
+}
+
+static ssize_t tmc_crashdata_read(struct file *file, char __user *data,
+				  size_t len, loff_t *ppos)
+{
+	char *bufp;
+	ssize_t actual;
+	struct tmc_drvdata *drvdata = container_of(file->private_data,
+						   struct tmc_drvdata,
+						   crashdev);
+
+	actual = tmc_get_resvbuf_trace(drvdata, *ppos, len, &bufp);
+	if (actual <= 0)
+		return 0;
+
+	if (copy_to_user(data, bufp, actual)) {
+		dev_dbg(&drvdata->comp->dev,
+			"%s: copy_to_user failed\n", __func__);
+		return -EFAULT;
+	}
+
+	*ppos += actual;
+	dev_dbg(&drvdata->comp->dev, "%zu bytes copied\n", actual);
+
+	return actual;
+}
+
+static int tmc_crashdata_release(struct inode *inode, struct file *file)
+{
+	unsigned long flags;
+	struct tmc_resrv_buf *rbuf;
+	struct tmc_drvdata *drvdata = container_of(file->private_data,
+						   struct tmc_drvdata,
+						   crashdev);
+
+	rbuf = &drvdata->resrv_buf;
+	raw_spin_lock_irqsave(&drvdata->spinlock, flags);
+	rbuf->reading = false;
+	raw_spin_unlock_irqrestore(&drvdata->spinlock, flags);
+
+	dev_dbg(&drvdata->comp->dev, "%s: released\n", __func__);
+	return 0;
+}
+
+static const struct file_operations tmc_crashdata_fops = {
+	.owner		= THIS_MODULE,
+	.open		= tmc_crashdata_open,
+	.read		= tmc_crashdata_read,
+	.release	= tmc_crashdata_release,
+};
+
+static enum tmc_mem_intf_width tmc_get_memwidth(u32 devid)
+{
+	enum tmc_mem_intf_width memwidth;
+
+	/*
+	 * Excerpt from the TRM:
+	 *
+	 * DEVID::MEMWIDTH[10:8]
+	 * 0x2 Memory interface databus is 32 bits wide.
+	 * 0x3 Memory interface databus is 64 bits wide.
+	 * 0x4 Memory interface databus is 128 bits wide.
+	 * 0x5 Memory interface databus is 256 bits wide.
+	 */
+	switch (BMVAL(devid, 8, 10)) {
+	case 0x2:
+		memwidth = TMC_MEM_INTF_WIDTH_32BITS;
+		break;
+	case 0x3:
+		memwidth = TMC_MEM_INTF_WIDTH_64BITS;
+		break;
+	case 0x4:
+		memwidth = TMC_MEM_INTF_WIDTH_128BITS;
+		break;
+	case 0x5:
+		memwidth = TMC_MEM_INTF_WIDTH_256BITS;
+		break;
+	default:
+		memwidth = 0;
+	}
+
+	return memwidth;
+}
+
+static struct attribute *coresight_tmc_mgmt_attrs[] = {
+	gtrace_simple_reg32(rsz, TMC_RSZ),
+	gtrace_simple_reg32(sts, TMC_STS),
+	gtrace_simple_reg64(rrp, TMC_RRP, TMC_RRPHI),
+	gtrace_simple_reg64(rwp, TMC_RWP, TMC_RWPHI),
+	gtrace_simple_reg32(trg, TMC_TRG),
+	gtrace_simple_reg32(ctl, TMC_CTL),
+	gtrace_simple_reg32(ffsr, TMC_FFSR),
+	gtrace_simple_reg32(ffcr, TMC_FFCR),
+	gtrace_simple_reg32(mode, TMC_MODE),
+	gtrace_simple_reg32(pscr, TMC_PSCR),
+	gtrace_simple_reg32(devid, CORESIGHT_DEVID),
+	gtrace_simple_reg64(dba, TMC_DBALO, TMC_DBAHI),
+	gtrace_simple_reg32(axictl, TMC_AXICTL),
+	gtrace_simple_reg32(authstatus, TMC_AUTHSTATUS),
+	NULL,
+};
+
+static ssize_t trigger_cntr_show(struct device *dev,
+				 struct device_attribute *attr, char *buf)
+{
+	struct tmc_drvdata *drvdata = dev_get_drvdata(dev->parent);
+	unsigned long val = drvdata->trigger_cntr;
+
+	return sprintf(buf, "%#lx\n", val);
+}
+
+static ssize_t trigger_cntr_store(struct device *dev,
+			     struct device_attribute *attr,
+			     const char *buf, size_t size)
+{
+	int ret;
+	unsigned long val;
+	struct tmc_drvdata *drvdata = dev_get_drvdata(dev->parent);
+
+	ret = kstrtoul(buf, 16, &val);
+	if (ret)
+		return ret;
+
+	drvdata->trigger_cntr = val;
+	return size;
+}
+static DEVICE_ATTR_RW(trigger_cntr);
+
+static ssize_t buffer_size_show(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct tmc_drvdata *drvdata = dev_get_drvdata(dev->parent);
+
+	return sprintf(buf, "%#x\n", drvdata->size);
+}
+
+static ssize_t buffer_size_store(struct device *dev,
+				 struct device_attribute *attr,
+				 const char *buf, size_t size)
+{
+	int ret;
+	unsigned long val;
+	struct tmc_drvdata *drvdata = dev_get_drvdata(dev->parent);
+
+	/* Only permitted for TMC-ETRs */
+	if (drvdata->config_type != TMC_CONFIG_TYPE_ETR)
+		return -EPERM;
+
+	ret = kstrtoul(buf, 0, &val);
+	if (ret)
+		return ret;
+	/* The buffer size should be page aligned */
+	if (val & (PAGE_SIZE - 1))
+		return -EINVAL;
+	drvdata->size = val;
+	return size;
+}
+
+static DEVICE_ATTR_RW(buffer_size);
+
+static ssize_t stop_on_flush_show(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct tmc_drvdata *drvdata = dev_get_drvdata(dev->parent);
+
+	return sprintf(buf, "%#x\n", drvdata->stop_on_flush);
+}
+
+static ssize_t stop_on_flush_store(struct device *dev,
+				 struct device_attribute *attr,
+				 const char *buf, size_t size)
+{
+	int ret;
+	u8 val;
+	struct tmc_drvdata *drvdata = dev_get_drvdata(dev->parent);
+
+	ret = kstrtou8(buf, 0, &val);
+	if (ret)
+		return ret;
+	if (val)
+		drvdata->stop_on_flush = true;
+	else
+		drvdata->stop_on_flush = false;
+
+	return size;
+}
+
+static DEVICE_ATTR_RW(stop_on_flush);
+
+
+static struct attribute *coresight_tmc_attrs[] = {
+	&dev_attr_trigger_cntr.attr,
+	&dev_attr_buffer_size.attr,
+	&dev_attr_stop_on_flush.attr,
+	NULL,
+};
+
+static const struct attribute_group coresight_tmc_group = {
+	.attrs = coresight_tmc_attrs,
+};
+
+static const struct attribute_group coresight_tmc_mgmt_group = {
+	.attrs = coresight_tmc_mgmt_attrs,
+	.name = "mgmt",
+};
+
+static const struct attribute_group *coresight_etf_groups[] = {
+	&coresight_tmc_group,
+	&coresight_tmc_mgmt_group,
+	NULL,
+};
+
+static const struct attribute_group *coresight_etr_groups[] = {
+	&coresight_etr_group,
+	&coresight_tmc_group,
+	&coresight_tmc_mgmt_group,
+	NULL,
+};
+
+static bool tmc_etr_can_use_sg(struct device *dev)
+{
+	int ret;
+	u8 val_u8;
+
+	/*
+	 * Presence of the property 'arm,scatter-gather' is checked
+	 * on the platform for the feature support, rather than its
+	 * value.
+	 */
+	if (is_of_node(dev->fwnode)) {
+		return fwnode_property_present(dev->fwnode, "arm,scatter-gather");
+	} else if (is_acpi_device_node(dev->fwnode)) {
+		/*
+		 * TMC_DEVID_NOSCAT test in tmc_etr_setup_caps(), has already ensured
+		 * this property is only checked for Coresight SoC 400 TMC configured
+		 * as ETR.
+		 */
+		ret = fwnode_property_read_u8(dev->fwnode, "arm-armhc97c-sg-enable", &val_u8);
+		if (!ret)
+			return !!val_u8;
+
+		if (fwnode_property_present(dev->fwnode, "arm,scatter-gather")) {
+			pr_warn_once("Deprecated ACPI property - arm,scatter-gather\n");
+			return true;
+		}
+	}
+	return false;
+}
+
+static bool tmc_etr_has_non_secure_access(struct tmc_drvdata *drvdata)
+{
+	u32 auth = readl_relaxed(drvdata->base + TMC_AUTHSTATUS);
+
+	return (auth & TMC_AUTH_NSID_MASK) == 0x3;
+}
+
+static const struct amba_id tmc_ids[];
+
+static int of_tmc_get_reserved_resource_by_name(struct device *dev,
+						const char *name,
+						struct resource *res)
+{
+	int rc = -ENODEV;
+
+	rc = of_reserved_mem_region_to_resource_byname(dev->of_node, name, res);
+	if (rc < 0)
+		return rc;
+
+	if (res->start == 0 || resource_size(res) == 0)
+		rc = -ENODEV;
+
+	return rc;
+}
+
+static void tmc_get_reserved_region(struct device *parent)
+{
+	struct tmc_drvdata *drvdata = dev_get_drvdata(parent);
+	struct resource res;
+
+	if (of_tmc_get_reserved_resource_by_name(parent, "tracedata", &res))
+		return;
+
+	drvdata->resrv_buf.vaddr = memremap(res.start,
+						resource_size(&res),
+						MEMREMAP_WC);
+	if (IS_ERR_OR_NULL(drvdata->resrv_buf.vaddr)) {
+		dev_err(parent, "Reserved trace buffer mapping failed\n");
+		return;
+	}
+
+	drvdata->resrv_buf.paddr = res.start;
+	drvdata->resrv_buf.size  = resource_size(&res);
+
+	if (of_tmc_get_reserved_resource_by_name(parent, "metadata", &res))
+		return;
+
+	drvdata->crash_mdata.vaddr = memremap(res.start,
+					       resource_size(&res),
+					       MEMREMAP_WC);
+	if (IS_ERR_OR_NULL(drvdata->crash_mdata.vaddr)) {
+		dev_err(parent, "Metadata memory mapping failed\n");
+		return;
+	}
+
+	drvdata->crash_mdata.paddr = res.start;
+	drvdata->crash_mdata.size  = resource_size(&res);
+}
+
+/* Detect and initialise the capabilities of a TMC ETR */
+static int tmc_etr_setup_caps(struct device *parent, u32 devid,
+			      struct gtrace_platform_data *pdata)
+{
+	int rc;
+	u32 tmc_pid, dma_mask = 0;
+	struct tmc_drvdata *drvdata = dev_get_drvdata(parent);
+	void *dev_caps;
+
+	if (!tmc_etr_has_non_secure_access(drvdata))
+		return -EACCES;
+
+	tmc_pid = coresight_get_pid(pdata);
+	dev_caps = coresight_get_uci_data_from_amba(tmc_ids, tmc_pid);
+
+	/* Set the unadvertised capabilities */
+	tmc_etr_init_caps(drvdata, (u32)(unsigned long)dev_caps);
+
+	if (!(devid & TMC_DEVID_NOSCAT) && tmc_etr_can_use_sg(parent))
+		tmc_etr_set_cap(drvdata, TMC_ETR_SG);
+
+	/* Check if the AXI address width is available */
+	if (devid & TMC_DEVID_AXIAW_VALID)
+		dma_mask = ((devid >> TMC_DEVID_AXIAW_SHIFT) &
+				TMC_DEVID_AXIAW_MASK);
+
+	/*
+	 * Unless specified in the device configuration, ETR uses a 40-bit
+	 * AXI master in place of the embedded SRAM of ETB/ETF.
+	 */
+	switch (dma_mask) {
+	case 32:
+	case 40:
+	case 44:
+	case 48:
+	case 52:
+		dev_info(parent, "Detected dma mask %dbits\n", dma_mask);
+		break;
+	default:
+		dma_mask = 40;
+	}
+
+	rc = dma_set_mask_and_coherent(parent, DMA_BIT_MASK(dma_mask));
+	if (rc)
+		dev_err(parent, "Failed to setup DMA mask: %d\n", rc);
+	return rc;
+}
+
+static u32 tmc_etr_get_default_buffer_size(struct device *dev)
+{
+	u32 size;
+
+	if (fwnode_property_read_u32(dev->fwnode, "arm,buffer-size", &size))
+		size = SZ_1M;
+	return size;
+}
+
+static u32 tmc_etr_get_max_burst_size(struct device *dev)
+{
+	u32 burst_size;
+
+	if (fwnode_property_read_u32(dev->fwnode, "arm,max-burst-size",
+				     &burst_size))
+		return TMC_AXICTL_WR_BURST_16;
+
+	/* Only permissible values are 0 to 15 */
+	if (burst_size > 0xF)
+		burst_size = TMC_AXICTL_WR_BURST_16;
+
+	return burst_size;
+}
+
+static void register_crash_dev_interface(struct tmc_drvdata *drvdata,
+					 const char *name)
+{
+	drvdata->crashdev.name =
+		devm_kasprintf(&drvdata->comp->dev, GFP_KERNEL, "%s_%s", "crash", name);
+	drvdata->crashdev.minor = MISC_DYNAMIC_MINOR;
+	drvdata->crashdev.fops = &tmc_crashdata_fops;
+	if (misc_register(&drvdata->crashdev)) {
+		dev_dbg(&drvdata->comp->dev,
+			"Failed to setup user interface for crashdata\n");
+		drvdata->crashdev.fops = NULL;
+	} else
+		dev_info(&drvdata->comp->dev,
+			"Valid crash tracedata found\n");
+}
+
+static int __tmc_probe(struct device *dev, struct resource *res)
+{
+	int ret = 0;
+	u32 devid;
+	void __iomem *base;
+	struct gtrace_platform_data *pdata = NULL;
+	struct tmc_drvdata *drvdata;
+	struct gtrace_component_id id = {
+		.version = CORESIGHT_COMPONENT_ID_VERSION,
+	};
+	const char *comp_name = NULL;
+
+	drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL);
+	if (!drvdata)
+		return -ENOMEM;
+
+	dev_set_drvdata(dev, drvdata);
+
+	ret = coresight_get_enable_clocks(dev, &drvdata->pclk, &drvdata->atclk);
+	if (ret)
+		return ret;
+
+	ret = -ENOMEM;
+
+	/* Validity for the resource is already checked by the AMBA core */
+	base = devm_ioremap_resource(dev, res);
+	if (IS_ERR(base)) {
+		ret = PTR_ERR(base);
+		goto out;
+	}
+
+	drvdata->base = base;
+
+	raw_spin_lock_init(&drvdata->spinlock);
+
+	devid = readl_relaxed(drvdata->base + CORESIGHT_DEVID);
+	drvdata->config_type = BMVAL(devid, 6, 7);
+	drvdata->memwidth = tmc_get_memwidth(devid);
+	drvdata->etr_mode = ETR_MODE_AUTO;
+
+	if (drvdata->config_type == TMC_CONFIG_TYPE_ETR) {
+		drvdata->size = tmc_etr_get_default_buffer_size(dev);
+		drvdata->max_burst_size = tmc_etr_get_max_burst_size(dev);
+	} else {
+		drvdata->size = readl_relaxed(drvdata->base + TMC_RSZ) * 4;
+	}
+
+	tmc_get_reserved_region(dev);
+
+	pdata = coresight_get_platform_data(dev);
+	if (IS_ERR(pdata)) {
+		ret = PTR_ERR(pdata);
+		goto out;
+	}
+	pdata->io_mem = true;
+	pdata->base = base;
+
+	switch (drvdata->config_type) {
+	case TMC_CONFIG_TYPE_ETB:
+		pdata->groups = coresight_etf_groups;
+		id.type = GTRACE_CORESIGHT_TMC_ETB;
+		comp_name = "tmc_etb";
+		break;
+	case TMC_CONFIG_TYPE_ETR:
+		pdata->groups = coresight_etr_groups;
+		id.type = GTRACE_CORESIGHT_TMC_ETR;
+		ret = tmc_etr_setup_caps(dev, devid, pdata);
+		if (ret)
+			goto out;
+		idr_init(&drvdata->idr);
+		mutex_init(&drvdata->idr_mutex);
+		comp_name = "tmc_etr";
+		break;
+	case TMC_CONFIG_TYPE_ETF:
+		pdata->is_linksink = true;
+		pdata->groups = coresight_etf_groups;
+		id.type = GTRACE_CORESIGHT_TMC_ETF;
+		comp_name = "tmc_etf";
+		break;
+	default:
+		pr_err("%u: Unsupported TMC config\n", drvdata->config_type);
+		ret = -EINVAL;
+		goto out;
+	}
+
+	coresight_clear_self_claim_tag(pdata);
+
+	drvdata->comp = gtrace_register_component(&id, comp_name, pdata);
+	if (IS_ERR(drvdata->comp)) {
+		ret = PTR_ERR(drvdata->comp);
+		goto out;
+	}
+
+	drvdata->miscdev.name = dev_name(&drvdata->comp->dev);
+	drvdata->miscdev.minor = MISC_DYNAMIC_MINOR;
+	drvdata->miscdev.fops = &tmc_fops;
+	ret = misc_register(&drvdata->miscdev);
+	if (ret) {
+		gtrace_unregister_component(drvdata->comp);
+		goto out;
+	}
+
+out:
+	if (is_tmc_crashdata_valid(drvdata) &&
+	    !tmc_prepare_crashdata(drvdata))
+		register_crash_dev_interface(drvdata, dev_name(&drvdata->comp->dev));
+	return ret;
+}
+
+static int tmc_probe(struct amba_device *adev, const struct amba_id *id)
+{
+	int ret;
+
+	ret = __tmc_probe(&adev->dev, &adev->res);
+	if (!ret)
+		pm_runtime_put(&adev->dev);
+
+	return ret;
+}
+
+static void tmc_shutdown(struct amba_device *adev)
+{
+	unsigned long flags;
+	struct tmc_drvdata *drvdata = amba_get_drvdata(adev);
+
+	raw_spin_lock_irqsave(&drvdata->spinlock, flags);
+
+	if (gtrace_get_mode(drvdata->comp) == GTRACE_COMPONENT_MODE_DISABLED)
+		goto out;
+
+	if (drvdata->config_type == TMC_CONFIG_TYPE_ETR)
+		tmc_etr_disable_hw(drvdata);
+
+	/*
+	 * We do not care about coresight unregister here unlike remove
+	 * callback which is required for making coresight modular since
+	 * the system is going down after this.
+	 */
+out:
+	raw_spin_unlock_irqrestore(&drvdata->spinlock, flags);
+}
+
+static void __tmc_remove(struct device *dev)
+{
+	struct tmc_drvdata *drvdata = dev_get_drvdata(dev);
+
+	/*
+	 * Since misc_open() holds a refcount on the f_ops, which is
+	 * etb fops in this case, device is there until last file
+	 * handler to this device is closed.
+	 */
+	misc_deregister(&drvdata->miscdev);
+	if (drvdata->crashdev.fops)
+		misc_deregister(&drvdata->crashdev);
+	gtrace_unregister_component(drvdata->comp);
+}
+
+static void tmc_remove(struct amba_device *adev)
+{
+	__tmc_remove(&adev->dev);
+}
+
+static const struct amba_id tmc_ids[] = {
+	CS_AMBA_ID(0x000bb961),
+	/* Coresight SoC 600 TMC-ETR/ETS */
+	CS_AMBA_ID_DATA(0x000bb9e8, (unsigned long)CORESIGHT_SOC_600_ETR_CAPS),
+	/* Coresight SoC 600 TMC-ETB */
+	CS_AMBA_ID(0x000bb9e9),
+	/* Coresight SoC 600 TMC-ETF */
+	CS_AMBA_ID(0x000bb9ea),
+	{ 0, 0, NULL },
+};
+
+MODULE_DEVICE_TABLE(amba, tmc_ids);
+
+static struct amba_driver tmc_driver = {
+	.drv = {
+		.name   = "coresight-tmc",
+		.suppress_bind_attrs = true,
+	},
+	.probe		= tmc_probe,
+	.shutdown	= tmc_shutdown,
+	.remove		= tmc_remove,
+	.id_table	= tmc_ids,
+};
+
+static int tmc_platform_probe(struct platform_device *pdev)
+{
+	struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	int ret = 0;
+
+	pm_runtime_get_noresume(&pdev->dev);
+	pm_runtime_set_active(&pdev->dev);
+	pm_runtime_enable(&pdev->dev);
+
+	ret = __tmc_probe(&pdev->dev, res);
+	pm_runtime_put(&pdev->dev);
+	if (ret)
+		pm_runtime_disable(&pdev->dev);
+
+	return ret;
+}
+
+static void tmc_platform_remove(struct platform_device *pdev)
+{
+	struct tmc_drvdata *drvdata = dev_get_drvdata(&pdev->dev);
+
+	if (WARN_ON(!drvdata))
+		return;
+
+	__tmc_remove(&pdev->dev);
+	pm_runtime_disable(&pdev->dev);
+}
+
+#ifdef CONFIG_PM
+static int tmc_runtime_suspend(struct device *dev)
+{
+	struct tmc_drvdata *drvdata = dev_get_drvdata(dev);
+
+	clk_disable_unprepare(drvdata->atclk);
+	clk_disable_unprepare(drvdata->pclk);
+
+	return 0;
+}
+
+static int tmc_runtime_resume(struct device *dev)
+{
+	struct tmc_drvdata *drvdata = dev_get_drvdata(dev);
+	int ret;
+
+	ret = clk_prepare_enable(drvdata->pclk);
+	if (ret)
+		return ret;
+
+	ret = clk_prepare_enable(drvdata->atclk);
+	if (ret)
+		clk_disable_unprepare(drvdata->pclk);
+
+	return ret;
+}
+#endif
+
+static const struct dev_pm_ops tmc_dev_pm_ops = {
+	SET_RUNTIME_PM_OPS(tmc_runtime_suspend, tmc_runtime_resume, NULL)
+};
+
+#ifdef CONFIG_ACPI
+static const struct acpi_device_id tmc_acpi_ids[] = {
+	{"ARMHC501", 0, 0, 0}, /* ARM CoreSight ETR */
+	{"ARMHC97C", 0, 0, 0}, /* ARM CoreSight SoC-400 TMC, SoC-600 ETF/ETB */
+	{},
+};
+MODULE_DEVICE_TABLE(acpi, tmc_acpi_ids);
+#endif
+
+static struct platform_driver tmc_platform_driver = {
+	.probe	= tmc_platform_probe,
+	.remove = tmc_platform_remove,
+	.driver	= {
+		.name			= "coresight-tmc-platform",
+		.acpi_match_table	= ACPI_PTR(tmc_acpi_ids),
+		.suppress_bind_attrs	= true,
+		.pm			= &tmc_dev_pm_ops,
+	},
+};
+
+static const struct gtrace_component_id tmc_etr_ids[] = {
+	{ .type = GTRACE_CORESIGHT_TMC_ETR,
+	  .version = CORESIGHT_COMPONENT_ID_VERSION, },
+	{},
+};
+
+static struct gtrace_driver tmc_etr_driver = {
+	.id_table = tmc_etr_ids,
+	.setup_sysfs_buf = tmc_etr_setup_sysfs_buf,
+	.alloc_buf = tmc_alloc_etr_buffer,
+	.free_buf = tmc_free_etr_buffer,
+	.copyto_auxbuf = tmc_update_etr_buffer,
+	.start = tmc_enable_etr_sink,
+	.stop = tmc_disable_etr_sink,
+	.driver = {
+		.name = "coresight-tmc-etr"
+	},
+};
+
+static const struct gtrace_component_id tmc_etf_ids[] = {
+	{ .type = GTRACE_CORESIGHT_TMC_ETB,
+	  .version = CORESIGHT_COMPONENT_ID_VERSION, },
+	{ .type = GTRACE_CORESIGHT_TMC_ETF,
+	  .version = CORESIGHT_COMPONENT_ID_VERSION, },
+	{},
+};
+
+static struct gtrace_driver tmc_etf_driver = {
+	.id_table = tmc_etf_ids,
+	.setup_sysfs_buf = tmc_etf_setup_sysfs_buf,
+	.copyto_auxbuf = tmc_update_etf_buffer,
+	.start = tmc_enable_etf,
+	.stop = tmc_disable_etf,
+	.driver = {
+		.name = "coresight-tmc-etf"
+	},
+};
+
+static int __init tmc_init(void)
+{
+	int ret;
+
+	ret = gtrace_register_driver(&tmc_etr_driver);
+	if (ret)
+		return ret;
+
+	ret = gtrace_register_driver(&tmc_etf_driver);
+	if (ret)
+		goto err_etr;
+
+	ret = coresight_init_driver("tmc", &tmc_driver, &tmc_platform_driver);
+	if (ret)
+		goto err_etf;
+
+	return 0;
+
+err_etf:
+	gtrace_unregister_driver(&tmc_etf_driver);
+err_etr:
+	gtrace_unregister_driver(&tmc_etr_driver);
+	return ret;
+}
+
+static void __exit tmc_exit(void)
+{
+	coresight_remove_driver(&tmc_driver, &tmc_platform_driver);
+	gtrace_unregister_driver(&tmc_etf_driver);
+	gtrace_unregister_driver(&tmc_etr_driver);
+}
+module_init(tmc_init);
+module_exit(tmc_exit);
+
+MODULE_AUTHOR("Pratik Patel <pratikp@codeaurora.org>");
+MODULE_AUTHOR("Zane Leung <liangzhen@linux.spacemit.com>");
+MODULE_DESCRIPTION("Arm CoreSight Trace Memory Controller driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/hwtracing/gtrace/coresight-tmc-etf.c b/drivers/hwtracing/gtrace/coresight-tmc-etf.c
new file mode 100644
index 000000000000..cb35a398bf28
--- /dev/null
+++ b/drivers/hwtracing/gtrace/coresight-tmc-etf.c
@@ -0,0 +1,573 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright(C) 2016 Linaro Limited. All rights reserved.
+ * Author: Mathieu Poirier <mathieu.poirier@linaro.org>
+ * Derived from drivers/hwtracing/coresight/coresight-tmc-etf.c
+ */
+
+#include <linux/atomic.h>
+#include <linux/circ_buf.h>
+#include <linux/gtrace.h>
+#include <linux/perf_event.h>
+#include <linux/slab.h>
+#include "coresight.h"
+#include "coresight-tmc.h"
+
+static int __tmc_etb_enable_hw(struct tmc_drvdata *drvdata)
+{
+	int rc = 0;
+	u32 ffcr;
+
+	CS_UNLOCK(drvdata->base);
+
+	/* Wait for TMCSReady bit to be set */
+	rc = tmc_wait_for_tmcready(drvdata);
+	if (rc) {
+		dev_err(&drvdata->comp->dev,
+			"Failed to enable: TMC not ready\n");
+		CS_LOCK(drvdata->base);
+		return rc;
+	}
+
+	writel_relaxed(TMC_MODE_CIRCULAR_BUFFER, drvdata->base + TMC_MODE);
+
+	ffcr = TMC_FFCR_EN_FMT | TMC_FFCR_EN_TI | TMC_FFCR_FON_FLIN |
+		TMC_FFCR_FON_TRIG_EVT | TMC_FFCR_TRIGON_TRIGIN;
+	if (drvdata->stop_on_flush)
+		ffcr |= TMC_FFCR_STOP_ON_FLUSH;
+	writel_relaxed(ffcr, drvdata->base + TMC_FFCR);
+
+	writel_relaxed(drvdata->trigger_cntr, drvdata->base + TMC_TRG);
+	tmc_enable_hw(drvdata);
+
+	CS_LOCK(drvdata->base);
+	return rc;
+}
+
+static int tmc_etb_enable_hw(struct tmc_drvdata *drvdata)
+{
+	int rc = coresight_claim_device(drvdata->comp);
+
+	if (rc)
+		return rc;
+
+	rc = __tmc_etb_enable_hw(drvdata);
+	if (rc)
+		coresight_disclaim_device(drvdata->comp);
+	return rc;
+}
+
+static void tmc_etb_dump_hw(struct tmc_drvdata *drvdata)
+{
+	char *bufp;
+	u32 read_data, lost;
+
+	/* Check if the buffer wrapped around. */
+	lost = readl_relaxed(drvdata->base + TMC_STS) & TMC_STS_FULL;
+	bufp = drvdata->buf;
+	drvdata->len = 0;
+	while (1) {
+		read_data = readl_relaxed(drvdata->base + TMC_RRD);
+		if (read_data == 0xFFFFFFFF)
+			break;
+		memcpy(bufp, &read_data, 4);
+		bufp += 4;
+		drvdata->len += 4;
+	}
+
+	if (lost)
+		coresight_insert_barrier_packet(drvdata->buf);
+	return;
+}
+
+static void __tmc_etb_disable_hw(struct tmc_drvdata *drvdata)
+{
+	CS_UNLOCK(drvdata->base);
+
+	tmc_flush_and_stop(drvdata);
+	/*
+	 * When operating in sysFS mode the content of the buffer needs to be
+	 * read before the TMC is disabled.
+	 */
+	if (gtrace_get_mode(drvdata->comp) == GTRACE_COMPONENT_MODE_SYSFS)
+		tmc_etb_dump_hw(drvdata);
+	tmc_disable_hw(drvdata);
+
+	CS_LOCK(drvdata->base);
+}
+
+static void tmc_etb_disable_hw(struct tmc_drvdata *drvdata)
+{
+	__tmc_etb_disable_hw(drvdata);
+	coresight_disclaim_device(drvdata->comp);
+}
+
+static int __tmc_etf_enable_hw(struct tmc_drvdata *drvdata)
+{
+	int rc = 0;
+
+	CS_UNLOCK(drvdata->base);
+
+	/* Wait for TMCSReady bit to be set */
+	rc = tmc_wait_for_tmcready(drvdata);
+	if (rc) {
+		dev_err(&drvdata->comp->dev,
+			"Failed to enable : TMC is not ready\n");
+		CS_LOCK(drvdata->base);
+		return rc;
+	}
+
+	writel_relaxed(TMC_MODE_HARDWARE_FIFO, drvdata->base + TMC_MODE);
+	writel_relaxed(TMC_FFCR_EN_FMT | TMC_FFCR_EN_TI,
+		       drvdata->base + TMC_FFCR);
+	writel_relaxed(0x0, drvdata->base + TMC_BUFWM);
+	tmc_enable_hw(drvdata);
+
+	CS_LOCK(drvdata->base);
+	return rc;
+}
+
+static int tmc_etf_enable_hw(struct tmc_drvdata *drvdata)
+{
+	int rc = coresight_claim_device(drvdata->comp);
+
+	if (rc)
+		return rc;
+
+	rc = __tmc_etf_enable_hw(drvdata);
+	if (rc)
+		coresight_disclaim_device(drvdata->comp);
+	return rc;
+}
+
+static void tmc_etf_disable_hw(struct tmc_drvdata *drvdata)
+{
+	struct gtrace_component *comp = drvdata->comp;
+
+	CS_UNLOCK(drvdata->base);
+
+	tmc_flush_and_stop(drvdata);
+	tmc_disable_hw(drvdata);
+	coresight_disclaim_device_unlocked(comp);
+	CS_LOCK(drvdata->base);
+}
+
+/*
+ * Return the available trace data in the buffer from @pos, with
+ * a maximum limit of @len, updating the @bufpp on where to
+ * find it.
+ */
+ssize_t tmc_etb_get_sysfs_trace(struct tmc_drvdata *drvdata,
+				loff_t pos, size_t len, char **bufpp)
+{
+	ssize_t actual = len;
+
+	/* Adjust the len to available size @pos */
+	if (pos + actual > drvdata->len)
+		actual = drvdata->len - pos;
+	if (actual > 0)
+		*bufpp = drvdata->buf + pos;
+	return actual;
+}
+
+int tmc_etf_setup_sysfs_buf(struct gtrace_component *comp)
+{
+	int ret = 0;
+	bool used = false;
+	char *buf = NULL;
+	unsigned long flags;
+	struct tmc_drvdata *drvdata = dev_get_drvdata(comp->dev.parent);
+
+	/*
+	 * If we don't have a buffer release the lock and allocate memory.
+	 * Otherwise keep the lock and move along.
+	 */
+	raw_spin_lock_irqsave(&drvdata->spinlock, flags);
+	if (!drvdata->buf) {
+		raw_spin_unlock_irqrestore(&drvdata->spinlock, flags);
+
+		/* Allocating the memory here while outside of the spinlock */
+		buf = kzalloc(drvdata->size, GFP_KERNEL);
+		if (!buf)
+			return -ENOMEM;
+
+		/* Let's try again */
+		raw_spin_lock_irqsave(&drvdata->spinlock, flags);
+	}
+
+	if (drvdata->reading) {
+		ret = -EBUSY;
+		goto out;
+	}
+
+	/*
+	 * If drvdata::buf isn't NULL, memory was allocated for a previous
+	 * trace run but wasn't read.  If so simply zero-out the memory.
+	 * Otherwise use the memory allocated above.
+	 *
+	 * The memory is freed when users read the buffer using the
+	 * /dev/xyz.{etf|etb} interface.  See tmc_read_unprepare_etf() for
+	 * details.
+	 */
+	if (drvdata->buf) {
+		memset(drvdata->buf, 0, drvdata->size);
+	} else {
+		used = true;
+		drvdata->buf = buf;
+	}
+out:
+	raw_spin_unlock_irqrestore(&drvdata->spinlock, flags);
+
+	/* Free memory outside the spinlock if need be */
+	if (!used)
+		kfree(buf);
+
+	return ret;
+}
+
+int tmc_enable_etf(struct gtrace_component *comp)
+{
+	int ret = 0;
+	struct gtrace_path *path = comp->path;
+	struct tmc_drvdata *drvdata = dev_get_drvdata(comp->dev.parent);
+
+	if (WARN_ON(!path))
+		return -EINVAL;
+
+	if (drvdata->reading)
+		return -EBUSY;
+
+	drvdata->is_sink = gtrace_path_sink(path);
+	if (drvdata->is_sink)
+		ret = tmc_etb_enable_hw(drvdata);
+	else
+		ret = tmc_etf_enable_hw(drvdata);
+
+	if (!ret)
+		dev_dbg(&comp->dev, "TMC-ETB/ETF enabled\n");
+
+	return ret;
+}
+
+int tmc_disable_etf(struct gtrace_component *comp)
+{
+	struct gtrace_path *path = comp->path;
+	struct tmc_drvdata *drvdata = dev_get_drvdata(comp->dev.parent);
+
+	if (WARN_ON(!path))
+		return -EINVAL;
+
+	if (drvdata->reading)
+		return -EBUSY;
+
+	if (drvdata->is_sink)
+		tmc_etb_disable_hw(drvdata);
+	else
+		tmc_etf_disable_hw(drvdata);
+
+	dev_dbg(&comp->dev, "TMC-ETB/ETF disabled\n");
+	return 0;
+}
+
+unsigned long tmc_update_etf_buffer(struct gtrace_component *comp,
+				    struct perf_output_handle *handle,
+				    struct gtrace_perf_auxbuf *buf)
+{
+	bool lost = false;
+	int i, cur;
+	const u32 *barrier;
+	u32 *buf_ptr;
+	u64 read_ptr, write_ptr;
+	u32 status;
+	unsigned long offset, to_read = 0, flags;
+	struct tmc_drvdata *drvdata = dev_get_drvdata(comp->dev.parent);
+	struct perf_event *event = handle->event;
+
+	/* This shouldn't happen */
+	if (WARN_ON_ONCE(gtrace_get_mode(comp) != GTRACE_COMPONENT_MODE_PERF))
+		return 0;
+
+	raw_spin_lock_irqsave(&drvdata->spinlock, flags);
+
+	CS_UNLOCK(drvdata->base);
+
+	tmc_flush_and_stop(drvdata);
+
+	read_ptr = tmc_read_rrp(drvdata);
+	write_ptr = tmc_read_rwp(drvdata);
+
+	/*
+	 * Get a hold of the status register and see if a wrap around
+	 * has occurred.  If so adjust things accordingly.
+	 */
+	status = readl_relaxed(drvdata->base + TMC_STS);
+	if (status & TMC_STS_FULL) {
+		lost = true;
+		to_read = drvdata->size;
+	} else {
+		to_read = CIRC_CNT(write_ptr, read_ptr, drvdata->size);
+	}
+
+	/*
+	 * The TMC RAM buffer may be bigger than the space available in the
+	 * perf ring buffer (handle->size).  If so advance the RRP so that we
+	 * get the latest trace data.  In snapshot mode none of that matters
+	 * since we are expected to clobber stale data in favour of the latest
+	 * traces.
+	 */
+	if (!buf->snapshot && to_read > handle->size) {
+		u32 mask = tmc_get_memwidth_mask(drvdata);
+
+		/*
+		 * Make sure the new size is aligned in accordance with the
+		 * requirement explained in function tmc_get_memwidth_mask().
+		 */
+		to_read = handle->size & mask;
+		/* Move the RAM read pointer up */
+		read_ptr = (write_ptr + drvdata->size) - to_read;
+		/* Make sure we are still within our limits */
+		if (read_ptr > (drvdata->size - 1))
+			read_ptr -= drvdata->size;
+		/* Tell the HW */
+		tmc_write_rrp(drvdata, read_ptr);
+		lost = true;
+	}
+
+	/*
+	 * Don't set the TRUNCATED flag in snapshot mode because 1) the
+	 * captured buffer is expected to be truncated and 2) a full buffer
+	 * prevents the event from being re-enabled by the perf core,
+	 * resulting in stale data being send to user space.
+	 */
+	if (!buf->snapshot && lost)
+		perf_aux_output_flag(handle, PERF_AUX_FLAG_TRUNCATED);
+
+	cur = buf->pos / PAGE_SIZE;
+	offset = buf->pos % PAGE_SIZE;
+	barrier = coresight_barrier_pkt;
+
+	/* for every byte to read */
+	for (i = 0; i < to_read; i += 4) {
+		buf_ptr = buf->pages[cur] + offset;
+		*buf_ptr = readl_relaxed(drvdata->base + TMC_RRD);
+
+		if (lost && i < CORESIGHT_BARRIER_PKT_SIZE) {
+			*buf_ptr = *barrier;
+			barrier++;
+		}
+
+		offset += 4;
+		if (offset >= PAGE_SIZE) {
+			offset = 0;
+			cur++;
+			/* wrap around at the end of the buffer */
+			cur &= buf->nr_pages - 1;
+		}
+	}
+
+	/*
+	 * In snapshot mode we simply increment the head by the number of byte
+	 * that were written.  User space will figure out how many bytes to get
+	 * from the AUX buffer based on the position of the head.
+	 */
+	if (buf->snapshot)
+		handle->head += to_read;
+
+	/*
+	 * CS_LOCK() contains mb() so it can ensure visibility of the AUX trace
+	 * data before the aux_head is updated via perf_aux_output_end(), which
+	 * is expected by the perf ring buffer.
+	 */
+	CS_LOCK(drvdata->base);
+
+	/*
+	 * If the event is active, it is triggered during an AUX pause.
+	 * Re-enable the sink so that it is ready when AUX resume is invoked.
+	 */
+	if (!event->hw.state)
+		__tmc_etb_enable_hw(drvdata);
+
+	/* set the GTRACE_FORMAT_CORESIGHT flag */
+	perf_aux_output_flag(handle, PERF_AUX_FLAG_GTRACE_FORMAT_CORESIGHT);
+
+	return to_read;
+}
+
+int tmc_panic_sync_etf(struct gtrace_component *comp)
+{
+	u32 val;
+	struct tmc_crash_metadata *mdata;
+	struct tmc_drvdata *drvdata = dev_get_drvdata(comp->dev.parent);
+
+	mdata = (struct tmc_crash_metadata *)drvdata->crash_mdata.vaddr;
+
+	/* Make sure we have valid reserved memory */
+	if (!tmc_has_reserved_buffer(drvdata) ||
+	    !tmc_has_crash_mdata_buffer(drvdata))
+		return 0;
+
+	tmc_crashdata_set_invalid(drvdata);
+
+	CS_UNLOCK(drvdata->base);
+
+	/* Proceed only if ETF is enabled or configured as sink */
+	val = readl(drvdata->base + TMC_CTL);
+	if (!(val & TMC_CTL_CAPT_EN))
+		goto out;
+	val = readl(drvdata->base + TMC_MODE);
+	if (val != TMC_MODE_CIRCULAR_BUFFER)
+		goto out;
+
+	val = readl(drvdata->base + TMC_FFSR);
+	/* Do manual flush and stop only if its not auto-stopped */
+	if (!(val & TMC_FFSR_FT_STOPPED)) {
+		dev_dbg(&comp->dev,
+			 "%s: Triggering manual flush\n", __func__);
+		tmc_flush_and_stop(drvdata);
+	} else
+		tmc_wait_for_tmcready(drvdata);
+
+	/* Sync registers from hardware to metadata region */
+	mdata->tmc_sts = readl(drvdata->base + TMC_STS);
+	mdata->tmc_mode = readl(drvdata->base + TMC_MODE);
+	mdata->tmc_ffcr = readl(drvdata->base + TMC_FFCR);
+	mdata->tmc_ffsr = readl(drvdata->base + TMC_FFSR);
+
+	/* Sync Internal SRAM to reserved trace buffer region */
+	drvdata->buf = drvdata->resrv_buf.vaddr;
+	tmc_etb_dump_hw(drvdata);
+	/* Store as per RSZ register convention */
+	mdata->tmc_ram_size = drvdata->len >> 2;
+
+	/* Other fields for processing trace buffer reads */
+	mdata->tmc_rrp = 0;
+	mdata->tmc_dba = 0;
+	mdata->tmc_rwp = drvdata->len;
+	mdata->trace_paddr = drvdata->resrv_buf.paddr;
+
+	mdata->version = CS_CRASHDATA_VERSION;
+
+	/*
+	 * Make sure all previous writes are ordered,
+	 * before we mark valid
+	 */
+	smp_wmb();
+	mdata->valid = true;
+	/*
+	 * Below order need to maintained, since crc of metadata
+	 * is dependent on first
+	 */
+	mdata->crc32_tdata = find_crash_tracedata_crc(drvdata, mdata);
+	mdata->crc32_mdata = find_crash_metadata_crc(mdata);
+
+	tmc_disable_hw(drvdata);
+
+	dev_dbg(&comp->dev, "%s: success\n", __func__);
+out:
+	CS_UNLOCK(drvdata->base);
+	return 0;
+}
+
+int tmc_read_prepare_etb(struct tmc_drvdata *drvdata)
+{
+	enum tmc_mode mode;
+	int ret = 0;
+	unsigned long flags;
+
+	/* config types are set a boot time and never change */
+	if (WARN_ON_ONCE(drvdata->config_type != TMC_CONFIG_TYPE_ETB &&
+			 drvdata->config_type != TMC_CONFIG_TYPE_ETF))
+		return -EINVAL;
+
+	raw_spin_lock_irqsave(&drvdata->spinlock, flags);
+
+	if (drvdata->reading) {
+		ret = -EBUSY;
+		goto out;
+	}
+
+	/* Don't interfere if operated from Perf */
+	if (gtrace_get_mode(drvdata->comp) == GTRACE_COMPONENT_MODE_PERF) {
+		ret = -EINVAL;
+		goto out;
+	}
+
+	/* If drvdata::buf is NULL the trace data has been read already */
+	if (drvdata->buf == NULL) {
+		ret = -EINVAL;
+		goto out;
+	}
+
+	/* Disable the TMC if need be */
+	if (gtrace_get_mode(drvdata->comp) == GTRACE_COMPONENT_MODE_SYSFS) {
+		/* There is no point in reading a TMC in HW FIFO mode */
+		mode = readl_relaxed(drvdata->base + TMC_MODE);
+		if (mode != TMC_MODE_CIRCULAR_BUFFER) {
+			ret = -EINVAL;
+			goto out;
+		}
+		__tmc_etb_disable_hw(drvdata);
+	}
+
+	drvdata->reading = true;
+out:
+	raw_spin_unlock_irqrestore(&drvdata->spinlock, flags);
+
+	return ret;
+}
+
+int tmc_read_unprepare_etb(struct tmc_drvdata *drvdata)
+{
+	char *buf = NULL;
+	enum tmc_mode mode;
+	unsigned long flags;
+
+	/* config types are set a boot time and never change */
+	if (WARN_ON_ONCE(drvdata->config_type != TMC_CONFIG_TYPE_ETB &&
+			 drvdata->config_type != TMC_CONFIG_TYPE_ETF))
+		return -EINVAL;
+
+	raw_spin_lock_irqsave(&drvdata->spinlock, flags);
+
+	/* Re-enable the TMC if need be */
+	if (gtrace_get_mode(drvdata->comp) == GTRACE_COMPONENT_MODE_SYSFS) {
+		/* There is no point in reading a TMC in HW FIFO mode */
+		mode = readl_relaxed(drvdata->base + TMC_MODE);
+		if (mode != TMC_MODE_CIRCULAR_BUFFER) {
+			raw_spin_unlock_irqrestore(&drvdata->spinlock, flags);
+			return -EINVAL;
+		}
+		/*
+		 * The trace run will continue with the same allocated trace
+		 * buffer. As such zero-out the buffer so that we don't end
+		 * up with stale data.
+		 *
+		 * Since the tracer is still enabled drvdata::buf
+		 * can't be NULL.
+		 */
+		memset(drvdata->buf, 0, drvdata->size);
+		/*
+		 * Ignore failures to enable the TMC to make sure, we don't
+		 * leave the TMC in a "reading" state.
+		 */
+		__tmc_etb_enable_hw(drvdata);
+	} else {
+		/*
+		 * The ETB/ETF is not tracing and the buffer was just read.
+		 * As such prepare to free the trace buffer.
+		 */
+		buf = drvdata->buf;
+		drvdata->buf = NULL;
+	}
+
+	drvdata->reading = false;
+	raw_spin_unlock_irqrestore(&drvdata->spinlock, flags);
+
+	/*
+	 * Free allocated memory outside of the spinlock.  There is no need
+	 * to assert the validity of 'buf' since calling kfree(NULL) is safe.
+	 */
+	kfree(buf);
+
+	return 0;
+}
diff --git a/drivers/hwtracing/gtrace/coresight-tmc-etr.c b/drivers/hwtracing/gtrace/coresight-tmc-etr.c
new file mode 100644
index 000000000000..11b7df8ae1b2
--- /dev/null
+++ b/drivers/hwtracing/gtrace/coresight-tmc-etr.c
@@ -0,0 +1,1888 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright(C) 2016 Linaro Limited. All rights reserved.
+ * Author: Mathieu Poirier <mathieu.poirier@linaro.org>
+ * Derived from drivers/hwtracing/coresight/coresight-tmc-etr.c
+ */
+
+#include <linux/atomic.h>
+#include <linux/gtrace.h>
+#include <linux/dma-mapping.h>
+#include <linux/iommu.h>
+#include <linux/idr.h>
+#include <linux/mutex.h>
+#include <linux/refcount.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+#include <linux/vmalloc.h>
+#include "coresight.h"
+#include "coresight-tmc.h"
+
+struct etr_flat_buf {
+	struct device	*dev;
+	dma_addr_t	daddr;
+	void		*vaddr;
+	size_t		size;
+};
+
+struct etr_buf_hw {
+	bool	has_iommu;
+	bool	has_etr_sg;
+	bool	has_catu;
+	bool	has_resrv;
+};
+
+/* Convert the perf index to an offset within the ETR buffer */
+#define PERF_IDX2OFF(idx, buf)		\
+		((idx) % ((unsigned long)(buf)->nr_pages << PAGE_SHIFT))
+
+/* Lower limit for ETR hardware buffer */
+#define TMC_ETR_PERF_MIN_BUF_SIZE	SZ_1M
+
+/*
+ * The TMC ETR SG has a page size of 4K. The SG table contains pointers
+ * to 4KB buffers. However, the OS may use a PAGE_SIZE different from
+ * 4K (i.e, 16KB or 64KB). This implies that a single OS page could
+ * contain more than one SG buffer and tables.
+ *
+ * A table entry has the following format:
+ *
+ * ---Bit31------------Bit4-------Bit1-----Bit0--
+ * |     Address[39:12]    | SBZ |  Entry Type  |
+ * ----------------------------------------------
+ *
+ * Address: Bits [39:12] of a physical page address. Bits [11:0] are
+ *	    always zero.
+ *
+ * Entry type:
+ *	b00 - Reserved.
+ *	b01 - Last entry in the tables, points to 4K page buffer.
+ *	b10 - Normal entry, points to 4K page buffer.
+ *	b11 - Link. The address points to the base of next table.
+ */
+
+typedef u32 sgte_t;
+
+#define ETR_SG_PAGE_SHIFT		12
+#define ETR_SG_PAGE_SIZE		(1UL << ETR_SG_PAGE_SHIFT)
+#define ETR_SG_PAGES_PER_SYSPAGE	(PAGE_SIZE / ETR_SG_PAGE_SIZE)
+#define ETR_SG_PTRS_PER_PAGE		(ETR_SG_PAGE_SIZE / sizeof(sgte_t))
+#define ETR_SG_PTRS_PER_SYSPAGE		(PAGE_SIZE / sizeof(sgte_t))
+
+#define ETR_SG_ET_MASK			0x3
+#define ETR_SG_ET_LAST			0x1
+#define ETR_SG_ET_NORMAL		0x2
+#define ETR_SG_ET_LINK			0x3
+
+#define ETR_SG_ADDR_SHIFT		4
+
+#define ETR_SG_ENTRY(addr, type) \
+	((sgte_t)((((addr) >> ETR_SG_PAGE_SHIFT) << ETR_SG_ADDR_SHIFT) | \
+		((type) & ETR_SG_ET_MASK)))
+
+#define ETR_SG_ADDR(entry) \
+	(((dma_addr_t)(entry) >> ETR_SG_ADDR_SHIFT) << ETR_SG_PAGE_SHIFT)
+#define ETR_SG_ET(entry)		((entry) & ETR_SG_ET_MASK)
+
+/*
+ * struct etr_sg_table : ETR SG Table
+ * @sg_table:		Generic SG Table holding the data/table pages.
+ * @hwaddr:		hwaddress used by the TMC, which is the base
+ *			address of the table.
+ */
+struct etr_sg_table {
+	struct tmc_sg_table	*sg_table;
+	dma_addr_t		hwaddr;
+};
+
+/*
+ * tmc_etr_sg_table_entries: Total number of table entries required to map
+ * @nr_pages system pages.
+ *
+ * We need to map @nr_pages * ETR_SG_PAGES_PER_SYSPAGE data pages.
+ * Each TMC page can map (ETR_SG_PTRS_PER_PAGE - 1) buffer pointers,
+ * with the last entry pointing to another page of table entries.
+ * If we spill over to a new page for mapping 1 entry, we could as
+ * well replace the link entry of the previous page with the last entry.
+ */
+static unsigned long __attribute_const__
+tmc_etr_sg_table_entries(int nr_pages)
+{
+	unsigned long nr_sgpages = nr_pages * ETR_SG_PAGES_PER_SYSPAGE;
+	unsigned long nr_sglinks = nr_sgpages / (ETR_SG_PTRS_PER_PAGE - 1);
+	/*
+	 * If we spill over to a new page for 1 entry, we could as well
+	 * make it the LAST entry in the previous page, skipping the Link
+	 * address.
+	 */
+	if (nr_sglinks && (nr_sgpages % (ETR_SG_PTRS_PER_PAGE - 1) < 2))
+		nr_sglinks--;
+	return nr_sgpages + nr_sglinks;
+}
+
+/*
+ * tmc_pages_get_offset:  Go through all the pages in the tmc_pages
+ * and map the device address @addr to an offset within the virtual
+ * contiguous buffer.
+ */
+static long
+tmc_pages_get_offset(struct tmc_pages *tmc_pages, dma_addr_t addr)
+{
+	int i;
+	dma_addr_t page_start;
+
+	for (i = 0; i < tmc_pages->nr_pages; i++) {
+		page_start = tmc_pages->daddrs[i];
+		if (addr >= page_start && addr < (page_start + PAGE_SIZE))
+			return (long)i * PAGE_SIZE + (addr - page_start);
+	}
+
+	return -EINVAL;
+}
+
+/*
+ * tmc_pages_free : Unmap and free the pages used by tmc_pages.
+ * If the pages were not allocated in tmc_pages_alloc(), we would
+ * simply drop the refcount.
+ */
+static void tmc_pages_free(struct tmc_pages *tmc_pages,
+			   struct device *dev, enum dma_data_direction dir)
+{
+	int i;
+	struct device *real_dev = dev->parent;
+
+	for (i = 0; i < tmc_pages->nr_pages; i++) {
+		if (tmc_pages->daddrs && tmc_pages->daddrs[i])
+			dma_unmap_page(real_dev, tmc_pages->daddrs[i],
+					 PAGE_SIZE, dir);
+		if (tmc_pages->pages && tmc_pages->pages[i])
+			__free_page(tmc_pages->pages[i]);
+	}
+
+	kfree(tmc_pages->pages);
+	kfree(tmc_pages->daddrs);
+	tmc_pages->pages = NULL;
+	tmc_pages->daddrs = NULL;
+	tmc_pages->nr_pages = 0;
+}
+
+/*
+ * tmc_pages_alloc : Allocate and map pages for a given @tmc_pages.
+ * If @pages is not NULL, the list of page virtual addresses are
+ * used as the data pages. The pages are then dma_map'ed for @dev
+ * with dma_direction @dir.
+ *
+ * Returns 0 upon success, else the error number.
+ */
+static int tmc_pages_alloc(struct tmc_pages *tmc_pages,
+			   struct device *dev, int node,
+			   enum dma_data_direction dir, void **pages)
+{
+	int i, nr_pages;
+	dma_addr_t paddr;
+	struct page *page;
+	struct device *real_dev = dev->parent;
+
+	nr_pages = tmc_pages->nr_pages;
+	tmc_pages->daddrs = kzalloc_objs(*tmc_pages->daddrs, nr_pages);
+	if (!tmc_pages->daddrs)
+		return -ENOMEM;
+	tmc_pages->pages = kzalloc_objs(*tmc_pages->pages, nr_pages);
+	if (!tmc_pages->pages) {
+		kfree(tmc_pages->daddrs);
+		tmc_pages->daddrs = NULL;
+		return -ENOMEM;
+	}
+
+	for (i = 0; i < nr_pages; i++) {
+		if (pages && pages[i]) {
+			page = virt_to_page(pages[i]);
+			/* Hold a refcount on the page */
+			get_page(page);
+		} else {
+			page = alloc_pages_node(node,
+						GFP_KERNEL | __GFP_ZERO, 0);
+			if (!page)
+				goto err;
+		}
+		paddr = dma_map_page(real_dev, page, 0, PAGE_SIZE, dir);
+		if (dma_mapping_error(real_dev, paddr))
+			goto err;
+		tmc_pages->daddrs[i] = paddr;
+		tmc_pages->pages[i] = page;
+	}
+	return 0;
+err:
+	tmc_pages_free(tmc_pages, dev, dir);
+	return -ENOMEM;
+}
+
+static long
+tmc_sg_get_data_page_offset(struct tmc_sg_table *sg_table, dma_addr_t addr)
+{
+	return tmc_pages_get_offset(&sg_table->data_pages, addr);
+}
+
+static void tmc_free_table_pages(struct tmc_sg_table *sg_table)
+{
+	if (sg_table->table_vaddr)
+		vunmap(sg_table->table_vaddr);
+	tmc_pages_free(&sg_table->table_pages, sg_table->dev, DMA_TO_DEVICE);
+}
+
+static void tmc_free_data_pages(struct tmc_sg_table *sg_table)
+{
+	if (sg_table->data_vaddr)
+		vunmap(sg_table->data_vaddr);
+	tmc_pages_free(&sg_table->data_pages, sg_table->dev, DMA_FROM_DEVICE);
+}
+
+void tmc_free_sg_table(struct tmc_sg_table *sg_table)
+{
+	tmc_free_table_pages(sg_table);
+	tmc_free_data_pages(sg_table);
+	kfree(sg_table);
+}
+EXPORT_SYMBOL_GPL(tmc_free_sg_table);
+
+/*
+ * Alloc pages for the table. Since this will be used by the device,
+ * allocate the pages closer to the device (i.e, dev_to_node(dev)
+ * rather than the CPU node).
+ */
+static int tmc_alloc_table_pages(struct tmc_sg_table *sg_table)
+{
+	int rc;
+	struct tmc_pages *table_pages = &sg_table->table_pages;
+
+	rc = tmc_pages_alloc(table_pages, sg_table->dev,
+			     dev_to_node(sg_table->dev),
+			     DMA_TO_DEVICE, NULL);
+	if (rc)
+		return rc;
+	sg_table->table_vaddr = vmap(table_pages->pages,
+				     table_pages->nr_pages,
+				     VM_MAP,
+				     PAGE_KERNEL);
+	if (!sg_table->table_vaddr)
+		rc = -ENOMEM;
+	else
+		sg_table->table_daddr = table_pages->daddrs[0];
+	return rc;
+}
+
+static int tmc_alloc_data_pages(struct tmc_sg_table *sg_table, void **pages)
+{
+	int rc;
+
+	/* Allocate data pages on the node requested by the caller */
+	rc = tmc_pages_alloc(&sg_table->data_pages,
+			     sg_table->dev, sg_table->node,
+			     DMA_FROM_DEVICE, pages);
+	if (!rc) {
+		sg_table->data_vaddr = vmap(sg_table->data_pages.pages,
+					    sg_table->data_pages.nr_pages,
+					    VM_MAP,
+					    PAGE_KERNEL);
+		if (!sg_table->data_vaddr)
+			rc = -ENOMEM;
+	}
+	return rc;
+}
+
+/*
+ * tmc_alloc_sg_table: Allocate and setup dma pages for the TMC SG table
+ * and data buffers. TMC writes to the data buffers and reads from the SG
+ * Table pages.
+ *
+ * @dev		- Coresight device to which page should be DMA mapped.
+ * @node	- Numa node for mem allocations
+ * @nr_tpages	- Number of pages for the table entries.
+ * @nr_dpages	- Number of pages for Data buffer.
+ * @pages	- Optional list of virtual address of pages.
+ */
+struct tmc_sg_table *tmc_alloc_sg_table(struct device *dev,
+					int node,
+					int nr_tpages,
+					int nr_dpages,
+					void **pages)
+{
+	long rc;
+	struct tmc_sg_table *sg_table;
+
+	sg_table = kzalloc_obj(*sg_table);
+	if (!sg_table)
+		return ERR_PTR(-ENOMEM);
+	sg_table->data_pages.nr_pages = nr_dpages;
+	sg_table->table_pages.nr_pages = nr_tpages;
+	sg_table->node = node;
+	sg_table->dev = dev;
+
+	rc  = tmc_alloc_data_pages(sg_table, pages);
+	if (!rc)
+		rc = tmc_alloc_table_pages(sg_table);
+	if (rc) {
+		tmc_free_sg_table(sg_table);
+		return ERR_PTR(rc);
+	}
+
+	return sg_table;
+}
+EXPORT_SYMBOL_GPL(tmc_alloc_sg_table);
+
+/*
+ * tmc_sg_table_sync_data_range: Sync the data buffer written
+ * by the device from @offset up to a @size bytes.
+ */
+void tmc_sg_table_sync_data_range(struct tmc_sg_table *table,
+				  u64 offset, u64 size)
+{
+	int i, index, start;
+	int npages = DIV_ROUND_UP(size, PAGE_SIZE);
+	struct device *real_dev = table->dev->parent;
+	struct tmc_pages *data = &table->data_pages;
+
+	start = offset >> PAGE_SHIFT;
+	for (i = start; i < (start + npages); i++) {
+		index = i % data->nr_pages;
+		dma_sync_single_for_cpu(real_dev, data->daddrs[index],
+					PAGE_SIZE, DMA_FROM_DEVICE);
+	}
+}
+EXPORT_SYMBOL_GPL(tmc_sg_table_sync_data_range);
+
+/* tmc_sg_sync_table: Sync the page table */
+void tmc_sg_table_sync_table(struct tmc_sg_table *sg_table)
+{
+	int i;
+	struct device *real_dev = sg_table->dev->parent;
+	struct tmc_pages *table_pages = &sg_table->table_pages;
+
+	for (i = 0; i < table_pages->nr_pages; i++)
+		dma_sync_single_for_device(real_dev, table_pages->daddrs[i],
+					   PAGE_SIZE, DMA_TO_DEVICE);
+}
+EXPORT_SYMBOL_GPL(tmc_sg_table_sync_table);
+
+/*
+ * tmc_sg_table_get_data: Get the buffer pointer for data @offset
+ * in the SG buffer. The @bufpp is updated to point to the buffer.
+ * Returns :
+ *	the length of linear data available at @offset.
+ *	or
+ *	<= 0 if no data is available.
+ */
+ssize_t tmc_sg_table_get_data(struct tmc_sg_table *sg_table,
+			      u64 offset, size_t len, char **bufpp)
+{
+	size_t size;
+	int pg_idx = offset >> PAGE_SHIFT;
+	int pg_offset = offset & (PAGE_SIZE - 1);
+	struct tmc_pages *data_pages = &sg_table->data_pages;
+
+	size = tmc_sg_table_buf_size(sg_table);
+	if (offset >= size)
+		return -EINVAL;
+
+	/* Make sure we don't go beyond the end */
+	len = (len < (size - offset)) ? len : size - offset;
+	/* Respect the page boundaries */
+	len = (len < (PAGE_SIZE - pg_offset)) ? len : (PAGE_SIZE - pg_offset);
+	if (len > 0)
+		*bufpp = page_address(data_pages->pages[pg_idx]) + pg_offset;
+	return len;
+}
+EXPORT_SYMBOL_GPL(tmc_sg_table_get_data);
+
+#ifdef ETR_SG_DEBUG
+/* Map a dma address to virtual address */
+static unsigned long
+tmc_sg_daddr_to_vaddr(struct tmc_sg_table *sg_table,
+		      dma_addr_t addr, bool table)
+{
+	long offset;
+	unsigned long base;
+	struct tmc_pages *tmc_pages;
+
+	if (table) {
+		tmc_pages = &sg_table->table_pages;
+		base = (unsigned long)sg_table->table_vaddr;
+	} else {
+		tmc_pages = &sg_table->data_pages;
+		base = (unsigned long)sg_table->data_vaddr;
+	}
+
+	offset = tmc_pages_get_offset(tmc_pages, addr);
+	if (offset < 0)
+		return 0;
+	return base + offset;
+}
+
+/* Dump the given sg_table */
+static void tmc_etr_sg_table_dump(struct etr_sg_table *etr_table)
+{
+	sgte_t *ptr;
+	int i = 0;
+	dma_addr_t addr;
+	struct tmc_sg_table *sg_table = etr_table->sg_table;
+
+	ptr = (sgte_t *)tmc_sg_daddr_to_vaddr(sg_table,
+					      etr_table->hwaddr, true);
+	while (ptr) {
+		addr = ETR_SG_ADDR(*ptr);
+		switch (ETR_SG_ET(*ptr)) {
+		case ETR_SG_ET_NORMAL:
+			dev_dbg(sg_table->dev,
+				"%05d: %p\t:[N] 0x%llx\n", i, ptr, addr);
+			ptr++;
+			break;
+		case ETR_SG_ET_LINK:
+			dev_dbg(sg_table->dev,
+				"%05d: *** %p\t:{L} 0x%llx ***\n",
+				 i, ptr, addr);
+			ptr = (sgte_t *)tmc_sg_daddr_to_vaddr(sg_table,
+							      addr, true);
+			break;
+		case ETR_SG_ET_LAST:
+			dev_dbg(sg_table->dev,
+				"%05d: ### %p\t:[L] 0x%llx ###\n",
+				 i, ptr, addr);
+			return;
+		default:
+			dev_dbg(sg_table->dev,
+				"%05d: xxx %p\t:[INVALID] 0x%llx xxx\n",
+				 i, ptr, addr);
+			return;
+		}
+		i++;
+	}
+	dev_dbg(sg_table->dev, "******* End of Table *****\n");
+}
+#else
+static void tmc_etr_sg_table_dump(struct etr_sg_table *etr_table) {}
+#endif
+
+/*
+ * Populate the SG Table page table entries from table/data
+ * pages allocated. Each Data page has ETR_SG_PAGES_PER_SYSPAGE SG pages.
+ * So does a Table page. So we keep track of indices of the tables
+ * in each system page and move the pointers accordingly.
+ */
+#define INC_IDX_ROUND(idx, size) ((idx) = ((idx) + 1) % (size))
+static void tmc_etr_sg_table_populate(struct etr_sg_table *etr_table)
+{
+	dma_addr_t paddr;
+	int i, type, nr_entries;
+	int tpidx = 0; /* index to the current system table_page */
+	int sgtidx = 0;	/* index to the sg_table within the current syspage */
+	int sgtentry = 0; /* the entry within the sg_table */
+	int dpidx = 0; /* index to the current system data_page */
+	int spidx = 0; /* index to the SG page within the current data page */
+	sgte_t *ptr; /* pointer to the table entry to fill */
+	struct tmc_sg_table *sg_table = etr_table->sg_table;
+	dma_addr_t *table_daddrs = sg_table->table_pages.daddrs;
+	dma_addr_t *data_daddrs = sg_table->data_pages.daddrs;
+
+	nr_entries = tmc_etr_sg_table_entries(sg_table->data_pages.nr_pages);
+	/*
+	 * Use the contiguous virtual address of the table to update entries.
+	 */
+	ptr = sg_table->table_vaddr;
+	/*
+	 * Fill all the entries, except the last entry to avoid special
+	 * checks within the loop.
+	 */
+	for (i = 0; i < nr_entries - 1; i++) {
+		if (sgtentry == ETR_SG_PTRS_PER_PAGE - 1) {
+			/*
+			 * Last entry in a sg_table page is a link address to
+			 * the next table page. If this sg_table is the last
+			 * one in the system page, it links to the first
+			 * sg_table in the next system page. Otherwise, it
+			 * links to the next sg_table page within the system
+			 * page.
+			 */
+			if (sgtidx == ETR_SG_PAGES_PER_SYSPAGE - 1) {
+				paddr = table_daddrs[tpidx + 1];
+			} else {
+				paddr = table_daddrs[tpidx] +
+					(ETR_SG_PAGE_SIZE * (sgtidx + 1));
+			}
+			type = ETR_SG_ET_LINK;
+		} else {
+			/*
+			 * Update the indices to the data_pages to point to the
+			 * next sg_page in the data buffer.
+			 */
+			type = ETR_SG_ET_NORMAL;
+			paddr = data_daddrs[dpidx] + spidx * ETR_SG_PAGE_SIZE;
+			if (!INC_IDX_ROUND(spidx, ETR_SG_PAGES_PER_SYSPAGE))
+				dpidx++;
+		}
+		*ptr++ = ETR_SG_ENTRY(paddr, type);
+		/*
+		 * Move to the next table pointer, moving the table page index
+		 * if necessary
+		 */
+		if (!INC_IDX_ROUND(sgtentry, ETR_SG_PTRS_PER_PAGE)) {
+			if (!INC_IDX_ROUND(sgtidx, ETR_SG_PAGES_PER_SYSPAGE))
+				tpidx++;
+		}
+	}
+
+	/* Set up the last entry, which is always a data pointer */
+	paddr = data_daddrs[dpidx] + spidx * ETR_SG_PAGE_SIZE;
+	*ptr++ = ETR_SG_ENTRY(paddr, ETR_SG_ET_LAST);
+}
+
+/*
+ * tmc_init_etr_sg_table: Allocate a TMC ETR SG table, data buffer of @size and
+ * populate the table.
+ *
+ * @dev		- Device pointer for the TMC
+ * @node	- NUMA node where the memory should be allocated
+ * @size	- Total size of the data buffer
+ * @pages	- Optional list of page virtual address
+ */
+static struct etr_sg_table *
+tmc_init_etr_sg_table(struct device *dev, int node,
+		      unsigned long size, void **pages)
+{
+	int nr_entries, nr_tpages;
+	int nr_dpages = size >> PAGE_SHIFT;
+	struct tmc_sg_table *sg_table;
+	struct etr_sg_table *etr_table;
+
+	etr_table = kzalloc_obj(*etr_table);
+	if (!etr_table)
+		return ERR_PTR(-ENOMEM);
+	nr_entries = tmc_etr_sg_table_entries(nr_dpages);
+	nr_tpages = DIV_ROUND_UP(nr_entries, ETR_SG_PTRS_PER_SYSPAGE);
+
+	sg_table = tmc_alloc_sg_table(dev, node, nr_tpages, nr_dpages, pages);
+	if (IS_ERR(sg_table)) {
+		kfree(etr_table);
+		return ERR_CAST(sg_table);
+	}
+
+	etr_table->sg_table = sg_table;
+	/* TMC should use table base address for DBA */
+	etr_table->hwaddr = sg_table->table_daddr;
+	tmc_etr_sg_table_populate(etr_table);
+	/* Sync the table pages for the HW */
+	tmc_sg_table_sync_table(sg_table);
+	tmc_etr_sg_table_dump(etr_table);
+
+	return etr_table;
+}
+
+/*
+ * tmc_etr_alloc_flat_buf: Allocate a contiguous DMA buffer.
+ */
+static int tmc_etr_alloc_flat_buf(struct tmc_drvdata *drvdata,
+				  struct etr_buf *etr_buf, int node,
+				  void **pages)
+{
+	struct etr_flat_buf *flat_buf;
+	struct device *real_dev = drvdata->comp->dev.parent;
+
+	/* We cannot reuse existing pages for flat buf */
+	if (pages)
+		return -EINVAL;
+
+	flat_buf = kzalloc_obj(*flat_buf);
+	if (!flat_buf)
+		return -ENOMEM;
+
+	flat_buf->vaddr = dma_alloc_noncoherent(real_dev, etr_buf->size,
+						&flat_buf->daddr,
+						DMA_FROM_DEVICE,
+						GFP_KERNEL | __GFP_NOWARN);
+	if (!flat_buf->vaddr) {
+		kfree(flat_buf);
+		return -ENOMEM;
+	}
+
+	flat_buf->size = etr_buf->size;
+	flat_buf->dev = &drvdata->comp->dev;
+	etr_buf->hwaddr = flat_buf->daddr;
+	etr_buf->mode = ETR_MODE_FLAT;
+	etr_buf->private = flat_buf;
+	return 0;
+}
+
+static void tmc_etr_free_flat_buf(struct etr_buf *etr_buf)
+{
+	struct etr_flat_buf *flat_buf = etr_buf->private;
+
+	if (flat_buf && flat_buf->daddr) {
+		struct device *real_dev = flat_buf->dev->parent;
+
+		dma_free_noncoherent(real_dev, etr_buf->size,
+				     flat_buf->vaddr, flat_buf->daddr,
+				     DMA_FROM_DEVICE);
+	}
+	kfree(flat_buf);
+}
+
+static void tmc_etr_sync_flat_buf(struct etr_buf *etr_buf, u64 rrp, u64 rwp)
+{
+	struct etr_flat_buf *flat_buf = etr_buf->private;
+	struct device *real_dev = flat_buf->dev->parent;
+
+	/*
+	 * Adjust the buffer to point to the beginning of the trace data
+	 * and update the available trace data.
+	 */
+	etr_buf->offset = rrp - etr_buf->hwaddr;
+	if (etr_buf->full)
+		etr_buf->len = etr_buf->size;
+	else
+		etr_buf->len = rwp - rrp;
+
+	/*
+	 * The driver always starts tracing at the beginning of the buffer,
+	 * the only reason why we would get a wrap around is when the buffer
+	 * is full.  Sync the entire buffer in one go for this case.
+	 */
+	if (etr_buf->offset + etr_buf->len > etr_buf->size)
+		dma_sync_single_for_cpu(real_dev, flat_buf->daddr,
+					etr_buf->size, DMA_FROM_DEVICE);
+	else
+		dma_sync_single_for_cpu(real_dev,
+					flat_buf->daddr + etr_buf->offset,
+					etr_buf->len, DMA_FROM_DEVICE);
+}
+
+static ssize_t tmc_etr_get_data_flat_buf(struct etr_buf *etr_buf,
+					 u64 offset, size_t len, char **bufpp)
+{
+	struct etr_flat_buf *flat_buf = etr_buf->private;
+
+	*bufpp = (char *)flat_buf->vaddr + offset;
+	/*
+	 * tmc_etr_buf_get_data already adjusts the length to handle
+	 * buffer wrapping around.
+	 */
+	return len;
+}
+
+static const struct etr_buf_operations etr_flat_buf_ops = {
+	.alloc = tmc_etr_alloc_flat_buf,
+	.free = tmc_etr_free_flat_buf,
+	.sync = tmc_etr_sync_flat_buf,
+	.get_data = tmc_etr_get_data_flat_buf,
+};
+
+/*
+ * tmc_etr_alloc_resrv_buf: Allocate a contiguous DMA buffer from reserved region.
+ */
+static int tmc_etr_alloc_resrv_buf(struct tmc_drvdata *drvdata,
+				  struct etr_buf *etr_buf, int node,
+				  void **pages)
+{
+	struct etr_flat_buf *resrv_buf;
+	struct device *real_dev = drvdata->comp->dev.parent;
+
+	/* We cannot reuse existing pages for resrv buf */
+	if (pages)
+		return -EINVAL;
+
+	resrv_buf = kzalloc_obj(*resrv_buf);
+	if (!resrv_buf)
+		return -ENOMEM;
+
+	resrv_buf->daddr = dma_map_resource(real_dev, drvdata->resrv_buf.paddr,
+					   drvdata->resrv_buf.size,
+					   DMA_FROM_DEVICE, 0);
+	if (dma_mapping_error(real_dev, resrv_buf->daddr)) {
+		dev_err(real_dev, "failed to map source buffer address\n");
+		kfree(resrv_buf);
+		return -ENOMEM;
+	}
+
+	resrv_buf->vaddr = drvdata->resrv_buf.vaddr;
+	resrv_buf->size = etr_buf->size = drvdata->resrv_buf.size;
+	resrv_buf->dev = &drvdata->comp->dev;
+	etr_buf->hwaddr = resrv_buf->daddr;
+	etr_buf->mode = ETR_MODE_RESRV;
+	etr_buf->private = resrv_buf;
+	return 0;
+}
+
+static void tmc_etr_free_resrv_buf(struct etr_buf *etr_buf)
+{
+	struct etr_flat_buf *resrv_buf = etr_buf->private;
+
+	if (resrv_buf && resrv_buf->daddr) {
+		struct device *real_dev = resrv_buf->dev->parent;
+
+		dma_unmap_resource(real_dev, resrv_buf->daddr,
+				resrv_buf->size, DMA_FROM_DEVICE, 0);
+	}
+	kfree(resrv_buf);
+}
+
+static void tmc_etr_sync_resrv_buf(struct etr_buf *etr_buf, u64 rrp, u64 rwp)
+{
+	/*
+	 * Adjust the buffer to point to the beginning of the trace data
+	 * and update the available trace data.
+	 */
+	etr_buf->offset = rrp - etr_buf->hwaddr;
+	if (etr_buf->full)
+		etr_buf->len = etr_buf->size;
+	else
+		etr_buf->len = rwp - rrp;
+}
+
+static const struct etr_buf_operations etr_resrv_buf_ops = {
+	.alloc = tmc_etr_alloc_resrv_buf,
+	.free = tmc_etr_free_resrv_buf,
+	.sync = tmc_etr_sync_resrv_buf,
+	.get_data = tmc_etr_get_data_flat_buf,
+};
+
+/*
+ * tmc_etr_alloc_sg_buf: Allocate an SG buf @etr_buf. Setup the parameters
+ * appropriately.
+ */
+static int tmc_etr_alloc_sg_buf(struct tmc_drvdata *drvdata,
+				struct etr_buf *etr_buf, int node,
+				void **pages)
+{
+	struct etr_sg_table *etr_table;
+	struct device *dev = &drvdata->comp->dev;
+
+	etr_table = tmc_init_etr_sg_table(dev, node,
+					  etr_buf->size, pages);
+	if (IS_ERR(etr_table))
+		return -ENOMEM;
+	etr_buf->hwaddr = etr_table->hwaddr;
+	etr_buf->mode = ETR_MODE_ETR_SG;
+	etr_buf->private = etr_table;
+	return 0;
+}
+
+static void tmc_etr_free_sg_buf(struct etr_buf *etr_buf)
+{
+	struct etr_sg_table *etr_table = etr_buf->private;
+
+	if (etr_table) {
+		tmc_free_sg_table(etr_table->sg_table);
+		kfree(etr_table);
+	}
+}
+
+static ssize_t tmc_etr_get_data_sg_buf(struct etr_buf *etr_buf, u64 offset,
+				       size_t len, char **bufpp)
+{
+	struct etr_sg_table *etr_table = etr_buf->private;
+
+	return tmc_sg_table_get_data(etr_table->sg_table, offset, len, bufpp);
+}
+
+static void tmc_etr_sync_sg_buf(struct etr_buf *etr_buf, u64 rrp, u64 rwp)
+{
+	long r_offset, w_offset;
+	struct etr_sg_table *etr_table = etr_buf->private;
+	struct tmc_sg_table *table = etr_table->sg_table;
+
+	/* Convert hw address to offset in the buffer */
+	r_offset = tmc_sg_get_data_page_offset(table, rrp);
+	if (r_offset < 0) {
+		dev_warn(table->dev,
+			 "Unable to map RRP %llx to offset\n", rrp);
+		etr_buf->len = 0;
+		return;
+	}
+
+	w_offset = tmc_sg_get_data_page_offset(table, rwp);
+	if (w_offset < 0) {
+		dev_warn(table->dev,
+			 "Unable to map RWP %llx to offset\n", rwp);
+		etr_buf->len = 0;
+		return;
+	}
+
+	etr_buf->offset = r_offset;
+	if (etr_buf->full)
+		etr_buf->len = etr_buf->size;
+	else
+		etr_buf->len = ((w_offset < r_offset) ? etr_buf->size : 0) +
+				w_offset - r_offset;
+	tmc_sg_table_sync_data_range(table, r_offset, etr_buf->len);
+}
+
+static const struct etr_buf_operations etr_sg_buf_ops = {
+	.alloc = tmc_etr_alloc_sg_buf,
+	.free = tmc_etr_free_sg_buf,
+	.sync = tmc_etr_sync_sg_buf,
+	.get_data = tmc_etr_get_data_sg_buf,
+};
+
+/*
+ * TMC ETR could be connected to a CATU device, which can provide address
+ * translation service. This is represented by the Output port of the TMC
+ * (ETR) connected to the input port of the CATU.
+ *
+ * Returns	: gtrace_component ptr for the CATU device if a CATU is found.
+ *		: NULL otherwise.
+ */
+struct gtrace_component *
+tmc_etr_get_catu_device(struct tmc_drvdata *drvdata)
+{
+	return NULL;
+}
+EXPORT_SYMBOL_GPL(tmc_etr_get_catu_device);
+
+static const struct etr_buf_operations *etr_buf_ops[] = {
+	[ETR_MODE_FLAT] = &etr_flat_buf_ops,
+	[ETR_MODE_ETR_SG] = &etr_sg_buf_ops,
+	[ETR_MODE_CATU] = NULL,
+	[ETR_MODE_RESRV] = &etr_resrv_buf_ops
+};
+
+void tmc_etr_set_catu_ops(const struct etr_buf_operations *catu)
+{
+	etr_buf_ops[ETR_MODE_CATU] = catu;
+}
+EXPORT_SYMBOL_GPL(tmc_etr_set_catu_ops);
+
+void tmc_etr_remove_catu_ops(void)
+{
+	etr_buf_ops[ETR_MODE_CATU] = NULL;
+}
+EXPORT_SYMBOL_GPL(tmc_etr_remove_catu_ops);
+
+static int tmc_etr_mode_alloc_buf(int mode, struct tmc_drvdata *drvdata, struct etr_buf *etr_buf,
+				  int node, void **pages)
+{
+	int rc = -EINVAL;
+
+	switch (mode) {
+	case ETR_MODE_FLAT:
+	case ETR_MODE_ETR_SG:
+	case ETR_MODE_CATU:
+	case ETR_MODE_RESRV:
+		if (etr_buf_ops[mode] && etr_buf_ops[mode]->alloc)
+			rc = etr_buf_ops[mode]->alloc(drvdata, etr_buf,
+						      node, pages);
+		if (!rc)
+			etr_buf->ops = etr_buf_ops[mode];
+		return rc;
+	default:
+		return -EINVAL;
+	}
+}
+
+static void get_etr_buf_hw(struct device *dev, struct etr_buf_hw *buf_hw)
+{
+	struct tmc_drvdata *drvdata = dev_get_drvdata(dev->parent);
+
+	buf_hw->has_iommu = iommu_get_domain_for_dev(dev->parent);
+	buf_hw->has_etr_sg = tmc_etr_has_cap(drvdata, TMC_ETR_SG);
+	buf_hw->has_catu = !!tmc_etr_get_catu_device(drvdata);
+	buf_hw->has_resrv = tmc_has_reserved_buffer(drvdata);
+}
+
+static bool etr_can_use_flat_mode(struct etr_buf_hw *buf_hw, ssize_t etr_buf_size)
+{
+	bool has_sg = buf_hw->has_catu || buf_hw->has_etr_sg;
+
+	return !has_sg || buf_hw->has_iommu || etr_buf_size < SZ_1M;
+}
+
+/*
+ * tmc_alloc_etr_buf: Allocate a buffer use by ETR.
+ * @drvdata	: ETR device details.
+ * @size	: size of the requested buffer.
+ * @flags	: Required properties for the buffer.
+ * @node	: Node for memory allocations.
+ * @pages	: An optional list of pages.
+ */
+static struct etr_buf *tmc_alloc_etr_buf(struct tmc_drvdata *drvdata,
+					 ssize_t size, int flags,
+					 int node, void **pages)
+{
+	int rc = -ENOMEM;
+	struct etr_buf *etr_buf;
+	struct etr_buf_hw buf_hw;
+	struct device *dev = &drvdata->comp->dev;
+
+	get_etr_buf_hw(dev, &buf_hw);
+	etr_buf = kzalloc_obj(*etr_buf);
+	if (!etr_buf)
+		return ERR_PTR(-ENOMEM);
+
+	etr_buf->size = size;
+
+	/* If there is user directive for buffer mode, try that first */
+	if (drvdata->etr_mode != ETR_MODE_AUTO)
+		rc = tmc_etr_mode_alloc_buf(drvdata->etr_mode, drvdata,
+					    etr_buf, node, pages);
+
+	/*
+	 * If we have to use an existing list of pages, we cannot reliably
+	 * use a contiguous DMA memory (even if we have an IOMMU). Otherwise,
+	 * we use the contiguous DMA memory if at least one of the following
+	 * conditions is true:
+	 *  a) The ETR cannot use Scatter-Gather.
+	 *  b) we have a backing IOMMU
+	 *  c) The requested memory size is smaller (< 1M).
+	 *
+	 * Fallback to available mechanisms.
+	 *
+	 */
+	if (rc && !pages && etr_can_use_flat_mode(&buf_hw, size))
+		rc = tmc_etr_mode_alloc_buf(ETR_MODE_FLAT, drvdata,
+					    etr_buf, node, pages);
+	if (rc && buf_hw.has_etr_sg)
+		rc = tmc_etr_mode_alloc_buf(ETR_MODE_ETR_SG, drvdata,
+					    etr_buf, node, pages);
+	if (rc && buf_hw.has_catu)
+		rc = tmc_etr_mode_alloc_buf(ETR_MODE_CATU, drvdata,
+					    etr_buf, node, pages);
+	if (rc) {
+		kfree(etr_buf);
+		return ERR_PTR(rc);
+	}
+
+	/*
+	 * Keep a reference to the ETR this buffer has been allocated for
+	 * in order to have access to the IDR in tmc_free_etr_buffer().
+	 */
+	etr_buf->drvdata = drvdata;
+	/* per-thread buffers are not shared and never enter the idr */
+	etr_buf->pid = -1;
+
+	refcount_set(&etr_buf->refcount, 1);
+	dev_dbg(dev, "allocated buffer of size %ldKB in mode %d\n",
+		(unsigned long)size >> 10, etr_buf->mode);
+	return etr_buf;
+}
+
+static void tmc_free_etr_buf(struct etr_buf *etr_buf)
+{
+	WARN_ON(!etr_buf->ops || !etr_buf->ops->free);
+	etr_buf->ops->free(etr_buf);
+	kfree(etr_buf);
+}
+
+/*
+ * tmc_etr_buf_get_data: Get the pointer the trace data at @offset
+ * with a maximum of @len bytes.
+ * 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,
+				    u64 offset, size_t len, char **bufpp)
+{
+	/* Adjust the length to limit this transaction to end of buffer */
+	len = (len < (etr_buf->size - offset)) ? len : etr_buf->size - offset;
+
+	return etr_buf->ops->get_data(etr_buf, (u64)offset, len, bufpp);
+}
+
+static s64
+tmc_etr_buf_insert_barrier_packet(struct etr_buf *etr_buf, u64 offset)
+{
+	ssize_t len;
+	char *bufp;
+
+	len = tmc_etr_buf_get_data(etr_buf, offset,
+				   CORESIGHT_BARRIER_PKT_SIZE, &bufp);
+	if (WARN_ON(len < 0 || len < CORESIGHT_BARRIER_PKT_SIZE))
+		return -EINVAL;
+	coresight_insert_barrier_packet(bufp);
+	return offset + CORESIGHT_BARRIER_PKT_SIZE;
+}
+
+/*
+ * tmc_sync_etr_buf: Sync the trace buffer availability with drvdata.
+ * Makes sure the trace data is synced to the memory for consumption.
+ * @etr_buf->offset will hold the offset to the beginning of the trace data
+ * within the buffer, with @etr_buf->len bytes to consume.
+ */
+static void tmc_sync_etr_buf(struct tmc_drvdata *drvdata)
+{
+	struct etr_buf *etr_buf = drvdata->etr_buf;
+	u64 rrp, rwp;
+	u32 status;
+
+	rrp = tmc_read_rrp(drvdata);
+	rwp = tmc_read_rwp(drvdata);
+	status = readl_relaxed(drvdata->base + TMC_STS);
+
+	/*
+	 * If there were memory errors in the session, truncate the
+	 * buffer.
+	 */
+	if (WARN_ON_ONCE(status & TMC_STS_MEMERR)) {
+		dev_dbg(&drvdata->comp->dev,
+			"tmc memory error detected, truncating buffer\n");
+		etr_buf->len = 0;
+		etr_buf->full = false;
+		return;
+	}
+
+	etr_buf->full = !!(status & TMC_STS_FULL);
+
+	WARN_ON(!etr_buf->ops || !etr_buf->ops->sync);
+
+	etr_buf->ops->sync(etr_buf, rrp, rwp);
+}
+
+static int __tmc_etr_enable_hw(struct tmc_drvdata *drvdata)
+{
+	u32 axictl, sts, ffcr;
+	struct etr_buf *etr_buf = drvdata->etr_buf;
+	int rc = 0;
+
+	CS_UNLOCK(drvdata->base);
+
+	/* Wait for TMCSReady bit to be set */
+	rc = tmc_wait_for_tmcready(drvdata);
+	if (rc) {
+		dev_err(&drvdata->comp->dev,
+			"Failed to enable : TMC not ready\n");
+		CS_LOCK(drvdata->base);
+		return rc;
+	}
+
+	writel_relaxed(etr_buf->size / 4, drvdata->base + TMC_RSZ);
+	writel_relaxed(TMC_MODE_CIRCULAR_BUFFER, drvdata->base + TMC_MODE);
+
+	axictl = readl_relaxed(drvdata->base + TMC_AXICTL);
+	axictl &= ~TMC_AXICTL_CLEAR_MASK;
+	axictl |= TMC_AXICTL_PROT_CTL_B1;
+	axictl |= TMC_AXICTL_WR_BURST(drvdata->max_burst_size);
+	axictl |= TMC_AXICTL_AXCACHE_OS;
+
+	if (tmc_etr_has_cap(drvdata, TMC_ETR_AXI_ARCACHE)) {
+		axictl &= ~TMC_AXICTL_ARCACHE_MASK;
+		axictl |= TMC_AXICTL_ARCACHE_OS;
+	}
+
+	if (etr_buf->mode == ETR_MODE_ETR_SG)
+		axictl |= TMC_AXICTL_SCT_GAT_MODE;
+
+	writel_relaxed(axictl, drvdata->base + TMC_AXICTL);
+	tmc_write_dba(drvdata, etr_buf->hwaddr);
+	/*
+	 * If the TMC pointers must be programmed before the session,
+	 * we have to set it properly (i.e, RRP/RWP to base address and
+	 * STS to "not full").
+	 */
+	if (tmc_etr_has_cap(drvdata, TMC_ETR_SAVE_RESTORE)) {
+		tmc_write_rrp(drvdata, etr_buf->hwaddr);
+		tmc_write_rwp(drvdata, etr_buf->hwaddr);
+		sts = readl_relaxed(drvdata->base + TMC_STS) & ~TMC_STS_FULL;
+		writel_relaxed(sts, drvdata->base + TMC_STS);
+	}
+
+	ffcr = TMC_FFCR_EN_FMT | TMC_FFCR_EN_TI | TMC_FFCR_FON_FLIN |
+		TMC_FFCR_FON_TRIG_EVT | TMC_FFCR_TRIGON_TRIGIN;
+	if (drvdata->stop_on_flush)
+		ffcr |= TMC_FFCR_STOP_ON_FLUSH;
+	writel_relaxed(ffcr, drvdata->base + TMC_FFCR);
+
+	writel_relaxed(drvdata->trigger_cntr, drvdata->base + TMC_TRG);
+	tmc_enable_hw(drvdata);
+
+	CS_LOCK(drvdata->base);
+	return rc;
+}
+
+static int tmc_etr_enable_hw(struct tmc_drvdata *drvdata,
+			     struct etr_buf *etr_buf)
+{
+	int rc;
+
+	/* Callers should provide an appropriate buffer for use */
+	if (WARN_ON(!etr_buf))
+		return -EINVAL;
+
+	if ((etr_buf->mode == ETR_MODE_ETR_SG) &&
+	    WARN_ON(!tmc_etr_has_cap(drvdata, TMC_ETR_SG)))
+		return -EINVAL;
+
+	if (WARN_ON(drvdata->etr_buf))
+		return -EBUSY;
+
+	rc = coresight_claim_device(drvdata->comp);
+	if (!rc) {
+		drvdata->etr_buf = etr_buf;
+		rc = __tmc_etr_enable_hw(drvdata);
+		if (rc) {
+			drvdata->etr_buf = NULL;
+			coresight_disclaim_device(drvdata->comp);
+		}
+	}
+
+	return rc;
+}
+
+/*
+ * Return the available trace data in the buffer (starts at etr_buf->offset,
+ * limited by etr_buf->len) from @pos, with a maximum limit of @len,
+ * 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)
+{
+	s64 offset;
+	ssize_t actual = len;
+	struct etr_buf *etr_buf = drvdata->sysfs_buf;
+
+	if (pos + actual > etr_buf->len)
+		actual = etr_buf->len - pos;
+	if (actual <= 0)
+		return actual;
+
+	/* 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);
+}
+
+static struct etr_buf *
+tmc_etr_alloc_sysfs_buf(struct tmc_drvdata *drvdata)
+{
+	return tmc_alloc_etr_buf(drvdata, drvdata->size,
+				 0, cpu_to_node(0), NULL);
+}
+
+static void
+tmc_etr_free_sysfs_buf(struct etr_buf *buf)
+{
+	if (buf)
+		tmc_free_etr_buf(buf);
+}
+
+static void tmc_etr_sync_sysfs_buf(struct tmc_drvdata *drvdata)
+{
+	struct etr_buf *etr_buf = drvdata->etr_buf;
+
+	if (WARN_ON(drvdata->sysfs_buf != etr_buf)) {
+		tmc_etr_free_sysfs_buf(drvdata->sysfs_buf);
+		drvdata->sysfs_buf = NULL;
+	} else {
+		tmc_sync_etr_buf(drvdata);
+		/*
+		 * Insert barrier packets at the beginning, if there was
+		 * an overflow.
+		 */
+		if (etr_buf->full)
+			tmc_etr_buf_insert_barrier_packet(etr_buf,
+							  etr_buf->offset);
+	}
+	/* Reset the ETR buf used by hardware */
+	drvdata->etr_buf = NULL;
+}
+
+static void __tmc_etr_disable_hw(struct tmc_drvdata *drvdata)
+{
+	CS_UNLOCK(drvdata->base);
+
+	tmc_flush_and_stop(drvdata);
+	/*
+	 * When operating in sysFS mode the content of the buffer needs to be
+	 * read before the TMC is disabled.
+	 */
+	if (gtrace_get_mode(drvdata->comp) == GTRACE_COMPONENT_MODE_SYSFS)
+		tmc_etr_sync_sysfs_buf(drvdata);
+
+	tmc_disable_hw(drvdata);
+
+	CS_LOCK(drvdata->base);
+
+}
+
+void tmc_etr_disable_hw(struct tmc_drvdata *drvdata)
+{
+	__tmc_etr_disable_hw(drvdata);
+	coresight_disclaim_device(drvdata->comp);
+}
+
+int tmc_etr_setup_sysfs_buf(struct gtrace_component *comp)
+{
+	int ret = 0;
+	unsigned long flags;
+	struct tmc_drvdata *drvdata = dev_get_drvdata(comp->dev.parent);
+	struct etr_buf *sysfs_buf = NULL, *new_buf = NULL, *free_buf = NULL;
+
+	/*
+	 * If we are enabling the ETR from disabled state, we need to make
+	 * sure we have a buffer with the right size. The etr_buf is not reset
+	 * immediately after we stop the tracing in SYSFS mode as we wait for
+	 * the user to collect the data. We may be able to reuse the existing
+	 * buffer, provided the size matches. Any allocation has to be done
+	 * with the lock released.
+	 */
+	raw_spin_lock_irqsave(&drvdata->spinlock, flags);
+
+	sysfs_buf = READ_ONCE(drvdata->sysfs_buf);
+	if (!sysfs_buf || (sysfs_buf->size != drvdata->size)) {
+		raw_spin_unlock_irqrestore(&drvdata->spinlock, flags);
+
+		/* Allocate memory with the locks released */
+		free_buf = new_buf = tmc_etr_alloc_sysfs_buf(drvdata);
+		if (IS_ERR(new_buf))
+			return PTR_ERR(new_buf);
+
+		/* Let's try again */
+		raw_spin_lock_irqsave(&drvdata->spinlock, flags);
+	}
+
+	if (drvdata->reading) {
+		ret = -EBUSY;
+		goto out;
+	}
+
+	/*
+	 * If we don't have a buffer or it doesn't match the requested size,
+	 * use the buffer allocated above. Otherwise reuse the existing buffer.
+	 */
+	sysfs_buf = READ_ONCE(drvdata->sysfs_buf);
+	if (!sysfs_buf || (new_buf && sysfs_buf->size != new_buf->size)) {
+		free_buf = sysfs_buf;
+		drvdata->sysfs_buf = new_buf;
+	}
+
+out:
+	raw_spin_unlock_irqrestore(&drvdata->spinlock, flags);
+
+	/* Free memory outside the spinlock if need be */
+	if (free_buf)
+		tmc_etr_free_sysfs_buf(free_buf);
+	return ret;
+}
+
+struct etr_buf *tmc_etr_get_buffer(struct gtrace_component *comp)
+{
+	struct gtrace_path *path = comp->path;
+	struct tmc_drvdata *drvdata = dev_get_drvdata(comp->dev.parent);
+	struct etr_buf *etr_buf;
+
+	switch (path->mode) {
+	case GTRACE_COMPONENT_MODE_SYSFS:
+		etr_buf = drvdata->sysfs_buf;
+		break;
+	case GTRACE_COMPONENT_MODE_PERF:
+		etr_buf = drvdata->perf_buf;
+		break;
+	default:
+		return ERR_PTR(-EINVAL);
+	}
+
+	WARN_ON(!etr_buf);
+
+	return etr_buf;
+}
+EXPORT_SYMBOL_GPL(tmc_etr_get_buffer);
+
+/*
+ * alloc_etr_buf: Allocate ETR buffer for use by perf.
+ * Allocate the largest possible size, scaling down the size by half until it
+ * reaches a minimum limit (1M), beyond which we give up.
+ */
+static struct etr_buf *
+alloc_etr_buf(struct tmc_drvdata *drvdata, struct perf_event *event,
+	      int nr_pages, void **pages, bool snapshot)
+{
+	int node;
+	struct etr_buf *etr_buf;
+	ssize_t size;
+
+	node = (event->cpu == -1) ? NUMA_NO_NODE : cpu_to_node(event->cpu);
+
+	/* Use the minimum limit if the required size is smaller */
+	size = (ssize_t)nr_pages << PAGE_SHIFT;
+	size = max_t(ssize_t, size, TMC_ETR_PERF_MIN_BUF_SIZE);
+
+	/*
+	 * Try to allocate the required size for this ETR, if failed scale
+	 * down until we hit the minimum limit.
+	 */
+	do {
+		etr_buf = tmc_alloc_etr_buf(drvdata, size, 0, node, NULL);
+		if (!IS_ERR(etr_buf))
+			return etr_buf;
+		size /= 2;
+	} while (size >= TMC_ETR_PERF_MIN_BUF_SIZE);
+
+	return ERR_PTR(-ENOMEM);
+}
+
+static struct etr_buf *
+get_perf_etr_buf_cpu_wide(struct tmc_drvdata *drvdata,
+			  struct perf_event *event, int nr_pages,
+			  void **pages, bool snapshot)
+{
+	int ret;
+	pid_t pid = task_pid_nr(event->owner);
+	struct etr_buf *etr_buf;
+
+retry:
+	/*
+	 * An gtrace_perf_auxbuf is associated with an event and holds a reference
+	 * to the AUX ring buffer that was created for that event.  In CPU-wide
+	 * N:1 mode multiple events (one per CPU), each with its own AUX ring
+	 * buffer, share a sink.  As such an gtrace_perf_auxbuf is created for each
+	 * event but a single etr_buf associated with the ETR is shared between
+	 * them.  The last event in a trace session will copy the content of the
+	 * etr_buf to its AUX ring buffer.  Ring buffer associated to other
+	 * events are simply not used an freed as events are destoyed.  We still
+	 * need to allocate a ring buffer for each event since we don't know
+	 * which event will be last.
+	 */
+
+	/*
+	 * The first thing to do here is check if an etr_buf has already been
+	 * allocated for this session.  If so it is shared with this event,
+	 * otherwise it is created.
+	 */
+	mutex_lock(&drvdata->idr_mutex);
+	etr_buf = idr_find(&drvdata->idr, pid);
+	if (etr_buf) {
+		refcount_inc(&etr_buf->refcount);
+		mutex_unlock(&drvdata->idr_mutex);
+		return etr_buf;
+	}
+
+	/* If we made it here no buffer has been allocated, do so now. */
+	mutex_unlock(&drvdata->idr_mutex);
+
+	etr_buf = alloc_etr_buf(drvdata, event, nr_pages, pages, snapshot);
+	if (IS_ERR(etr_buf))
+		return etr_buf;
+
+	/* Now that we have a buffer, add it to the IDR. */
+	mutex_lock(&drvdata->idr_mutex);
+	ret = idr_alloc(&drvdata->idr, etr_buf, pid, pid + 1, GFP_KERNEL);
+	mutex_unlock(&drvdata->idr_mutex);
+
+	/* Another event with this session ID has allocated this buffer. */
+	if (ret == -ENOSPC) {
+		tmc_free_etr_buf(etr_buf);
+		goto retry;
+	}
+
+	/* The IDR can't allocate room for a new session, abandon ship. */
+	if (ret == -ENOMEM) {
+		tmc_free_etr_buf(etr_buf);
+		return ERR_PTR(ret);
+	}
+
+	/*
+	 * Keep a reference to the ETR this buffer has been allocated for
+	 * in order to have access to the IDR in tmc_free_etr_buffer().
+	 */
+	etr_buf->drvdata = drvdata;
+	/* cpu-wide buffers are shared and tracked in the idr by session pid */
+	etr_buf->pid = pid;
+
+	return etr_buf;
+}
+
+static struct etr_buf *
+get_perf_etr_buf_per_thread(struct tmc_drvdata *drvdata,
+			    struct perf_event *event, int nr_pages,
+			    void **pages, bool snapshot)
+{
+	/*
+	 * In per-thread mode the etr_buf isn't shared, so just go ahead
+	 * with memory allocation.
+	 */
+	return alloc_etr_buf(drvdata, event, nr_pages, pages, snapshot);
+}
+
+static struct etr_buf *
+get_perf_etr_buf(struct tmc_drvdata *drvdata, struct perf_event *event,
+		 int nr_pages, void **pages, bool snapshot)
+{
+	if (event->cpu == -1)
+		return get_perf_etr_buf_per_thread(drvdata, event, nr_pages,
+						   pages, snapshot);
+
+	return get_perf_etr_buf_cpu_wide(drvdata, event, nr_pages,
+					 pages, snapshot);
+}
+
+void *tmc_alloc_etr_buffer(struct gtrace_component *comp,
+			   struct perf_event *event, void **pages,
+			   int nr_pages, bool snapshot)
+{
+	int node;
+	struct etr_buf *etr_buf;
+	struct tmc_drvdata *drvdata = dev_get_drvdata(comp->dev.parent);
+
+	node = (event->cpu == -1) ? NUMA_NO_NODE : cpu_to_node(event->cpu);
+
+	etr_buf = get_perf_etr_buf(drvdata, event, nr_pages, pages, snapshot);
+	if (IS_ERR(etr_buf))
+		return NULL;
+
+	drvdata->perf_buf = etr_buf;
+
+	return etr_buf;
+}
+
+void tmc_free_etr_buffer(void *config)
+{
+	struct etr_buf *buf = NULL, *etr_buf = config;
+	struct tmc_drvdata *drvdata;
+
+	drvdata = etr_buf->drvdata;
+
+	mutex_lock(&drvdata->idr_mutex);
+	/* If we are not the last one to use the buffer, don't touch it. */
+	if (!refcount_dec_and_test(&etr_buf->refcount)) {
+		mutex_unlock(&drvdata->idr_mutex);
+		return;
+	}
+
+	/* only shared buffers are present in the idr */
+	if (etr_buf->pid != -1) {
+		/* We are the last one, remove from the IDR and free the buffer. */
+		buf = idr_remove(&drvdata->idr, etr_buf->pid);
+
+	}
+	mutex_unlock(&drvdata->idr_mutex);
+
+	/*
+	 * Something went very wrong if the buffer associated with this ID
+	 * is not the same in the IDR.  Leak to avoid use after free.
+	 */
+	if (buf && WARN_ON(buf != etr_buf))
+		return;
+
+	tmc_free_etr_buf(etr_buf);
+	/* Reset perf specific data */
+	drvdata->perf_buf = NULL;
+}
+
+/*
+ * tmc_etr_sync_perf_buffer: Copy the actual trace data from the hardware
+ * buffer to the perf ring buffer.
+ */
+static void tmc_etr_sync_perf_buffer(struct gtrace_perf_auxbuf *etr_perf,
+				     unsigned long head,
+				     unsigned long src_offset,
+				     unsigned long to_copy)
+{
+	long bytes;
+	long pg_idx, pg_offset;
+	char **dst_pages, *src_buf;
+	struct etr_buf *etr_buf = etr_perf->buf;
+
+	head = PERF_IDX2OFF(head, etr_perf);
+	pg_idx = head >> PAGE_SHIFT;
+	pg_offset = head & (PAGE_SIZE - 1);
+	dst_pages = (char **)etr_perf->pages;
+
+	while (to_copy > 0) {
+		/*
+		 * In one iteration, we can copy minimum of :
+		 *  1) what is available in the source buffer,
+		 *  2) what is available in the source buffer, before it
+		 *     wraps around.
+		 *  3) what is available in the destination page.
+		 * in one iteration.
+		 */
+		if (src_offset >= etr_buf->size)
+			src_offset -= etr_buf->size;
+		bytes = tmc_etr_buf_get_data(etr_buf, src_offset, to_copy,
+					     &src_buf);
+		if (WARN_ON_ONCE(bytes <= 0))
+			break;
+		bytes = min(bytes, (long)(PAGE_SIZE - pg_offset));
+
+		memcpy(dst_pages[pg_idx] + pg_offset, src_buf, bytes);
+
+		to_copy -= bytes;
+
+		/* Move destination pointers */
+		pg_offset += bytes;
+		if (pg_offset == PAGE_SIZE) {
+			pg_offset = 0;
+			if (++pg_idx == etr_perf->nr_pages)
+				pg_idx = 0;
+		}
+
+		/* Move source pointers */
+		src_offset += bytes;
+	}
+}
+
+/*
+ * tmc_update_etr_buffer : Update the perf ring buffer with the
+ * available trace data. We use software double buffering at the moment.
+ *
+ * TODO: Add support for reusing the perf ring buffer.
+ */
+unsigned long
+tmc_update_etr_buffer(struct gtrace_component *comp,
+		      struct perf_output_handle *handle,
+		      struct gtrace_perf_auxbuf *etr_perf)
+{
+	bool lost = false;
+	unsigned long offset, size = 0;
+	struct tmc_drvdata *drvdata = dev_get_drvdata(comp->dev.parent);
+	struct etr_buf *etr_buf = etr_perf->buf;
+	struct perf_event *event = handle->event;
+
+	if (WARN_ON(drvdata->perf_buf != etr_buf)) {
+		lost = true;
+		goto out;
+	}
+
+	CS_UNLOCK(drvdata->base);
+
+	tmc_flush_and_stop(drvdata);
+	tmc_sync_etr_buf(drvdata);
+
+	CS_LOCK(drvdata->base);
+
+	lost = etr_buf->full;
+	offset = etr_buf->offset;
+	size = etr_buf->len;
+
+	/*
+	 * The ETR buffer may be bigger than the space available in the
+	 * perf ring buffer (handle->size).  If so advance the offset so that we
+	 * get the latest trace data.  In snapshot mode none of that matters
+	 * since we are expected to clobber stale data in favour of the latest
+	 * traces.
+	 */
+	if (!etr_perf->snapshot && size > handle->size) {
+		u32 mask = tmc_get_memwidth_mask(drvdata);
+
+		/*
+		 * Make sure the new size is aligned in accordance with the
+		 * requirement explained in function tmc_get_memwidth_mask().
+		 */
+		size = handle->size & mask;
+		offset = etr_buf->offset + etr_buf->len - size;
+
+		if (offset >= etr_buf->size)
+			offset -= etr_buf->size;
+		lost = true;
+	}
+
+	/* Insert barrier packets at the beginning, if there was an overflow */
+	if (lost)
+		tmc_etr_buf_insert_barrier_packet(etr_buf, offset);
+	tmc_etr_sync_perf_buffer(etr_perf, handle->head, offset, size);
+
+	/*
+	 * In snapshot mode we simply increment the head by the number of byte
+	 * that were written.  User space will figure out how many bytes to get
+	 * from the AUX buffer based on the position of the head.
+	 */
+	if (etr_perf->snapshot)
+		handle->head += size;
+
+	/*
+	 * Ensure that the AUX trace data is visible before the aux_head
+	 * is updated via perf_aux_output_end(), as expected by the
+	 * perf ring buffer.
+	 */
+	smp_wmb();
+
+	/*
+	 * If the event is active, it is triggered during an AUX pause.
+	 * Re-enable the sink so that it is ready when AUX resume is invoked.
+	 */
+	if (!event->hw.state)
+		__tmc_etr_enable_hw(drvdata);
+
+	/* Reset the ETR buf used by hardware */
+	if (event->hw.state == PERF_HES_STOPPED)
+		drvdata->etr_buf = NULL;
+
+	/* set the GTRACE_FORMAT_CORESIGHT flag */
+	perf_aux_output_flag(handle, PERF_AUX_FLAG_GTRACE_FORMAT_CORESIGHT);
+out:
+	/*
+	 * Don't set the TRUNCATED flag in snapshot mode because 1) the
+	 * captured buffer is expected to be truncated and 2) a full buffer
+	 * prevents the event from being re-enabled by the perf core,
+	 * resulting in stale data being send to user space.
+	 */
+	if (!etr_perf->snapshot && lost)
+		perf_aux_output_flag(handle, PERF_AUX_FLAG_TRUNCATED);
+	return size;
+}
+
+int tmc_enable_etr_sink(struct gtrace_component *comp)
+{
+	struct tmc_drvdata *drvdata = dev_get_drvdata(comp->dev.parent);
+	struct etr_buf *etr_buf;
+	int ret;
+
+	etr_buf = tmc_etr_get_buffer(comp);
+	if (IS_ERR(etr_buf))
+		return PTR_ERR(etr_buf);
+
+	ret = tmc_etr_enable_hw(drvdata, etr_buf);
+	if (!ret)
+		dev_dbg(&comp->dev, "TMC-ETR enabled\n");
+
+	return ret;
+}
+
+int tmc_disable_etr_sink(struct gtrace_component *comp)
+{
+	struct tmc_drvdata *drvdata = dev_get_drvdata(comp->dev.parent);
+
+	if (drvdata->reading)
+		return -EBUSY;
+
+	tmc_etr_disable_hw(drvdata);
+
+	dev_dbg(&comp->dev, "TMC-ETR disabled\n");
+	return 0;
+}
+
+int tmc_panic_sync_etr(struct gtrace_component *comp)
+{
+	u32 val;
+	struct tmc_crash_metadata *mdata;
+	struct tmc_drvdata *drvdata = dev_get_drvdata(comp->dev.parent);
+
+	mdata = (struct tmc_crash_metadata *)drvdata->crash_mdata.vaddr;
+
+	if (!drvdata->etr_buf)
+		return 0;
+
+	/* Being in RESRV mode implies valid reserved memory as well */
+	if (drvdata->etr_buf->mode != ETR_MODE_RESRV)
+		return 0;
+
+	if (!tmc_has_crash_mdata_buffer(drvdata))
+		return 0;
+
+	CS_UNLOCK(drvdata->base);
+
+	/* Proceed only if ETR is enabled */
+	val = readl(drvdata->base + TMC_CTL);
+	if (!(val & TMC_CTL_CAPT_EN))
+		goto out;
+
+	val = readl(drvdata->base + TMC_FFSR);
+	/* Do manual flush and stop only if its not auto-stopped */
+	if (!(val & TMC_FFSR_FT_STOPPED)) {
+		dev_dbg(&comp->dev,
+			 "%s: Triggering manual flush\n", __func__);
+		tmc_flush_and_stop(drvdata);
+	} else
+		tmc_wait_for_tmcready(drvdata);
+
+	/* Sync registers from hardware to metadata region */
+	mdata->tmc_ram_size = readl(drvdata->base + TMC_RSZ);
+	mdata->tmc_sts = readl(drvdata->base + TMC_STS);
+	mdata->tmc_mode = readl(drvdata->base + TMC_MODE);
+	mdata->tmc_ffcr = readl(drvdata->base + TMC_FFCR);
+	mdata->tmc_ffsr = readl(drvdata->base + TMC_FFSR);
+	mdata->tmc_rrp = tmc_read_rrp(drvdata);
+	mdata->tmc_rwp = tmc_read_rwp(drvdata);
+	mdata->tmc_dba = tmc_read_dba(drvdata);
+	mdata->trace_paddr = drvdata->resrv_buf.paddr;
+	mdata->version = CS_CRASHDATA_VERSION;
+
+	/*
+	 * Make sure all previous writes are ordered,
+	 * before we mark valid
+	 */
+	smp_wmb();
+	mdata->valid = true;
+	/*
+	 * Below order need to maintained, since crc of metadata
+	 * is dependent on first
+	 */
+	mdata->crc32_tdata = find_crash_tracedata_crc(drvdata, mdata);
+	mdata->crc32_mdata = find_crash_metadata_crc(mdata);
+
+	tmc_disable_hw(drvdata);
+
+	dev_dbg(&comp->dev, "%s: success\n", __func__);
+out:
+	CS_UNLOCK(drvdata->base);
+
+	return 0;
+}
+
+int tmc_read_prepare_etr(struct tmc_drvdata *drvdata)
+{
+	int ret = 0;
+	unsigned long flags;
+
+	/* config types are set a boot time and never change */
+	if (WARN_ON_ONCE(drvdata->config_type != TMC_CONFIG_TYPE_ETR))
+		return -EINVAL;
+
+	raw_spin_lock_irqsave(&drvdata->spinlock, flags);
+	if (drvdata->reading) {
+		ret = -EBUSY;
+		goto out;
+	}
+
+	/*
+	 * We can safely allow reads even if the ETR is operating in PERF mode,
+	 * since the sysfs session is captured in mode specific data.
+	 * If drvdata::sysfs_data is NULL the trace data has been read already.
+	 */
+	if (!drvdata->sysfs_buf) {
+		ret = -EINVAL;
+		goto out;
+	}
+
+	/* Disable the TMC if we are trying to read from a running session. */
+	if (gtrace_get_mode(drvdata->comp) == GTRACE_COMPONENT_MODE_SYSFS)
+		__tmc_etr_disable_hw(drvdata);
+
+	drvdata->reading = true;
+out:
+	raw_spin_unlock_irqrestore(&drvdata->spinlock, flags);
+
+	return ret;
+}
+
+int tmc_read_unprepare_etr(struct tmc_drvdata *drvdata)
+{
+	unsigned long flags;
+	struct etr_buf *sysfs_buf = NULL;
+
+	/* config types are set a boot time and never change */
+	if (WARN_ON_ONCE(drvdata->config_type != TMC_CONFIG_TYPE_ETR))
+		return -EINVAL;
+
+	raw_spin_lock_irqsave(&drvdata->spinlock, flags);
+
+	/* RE-enable the TMC if need be */
+	if (gtrace_get_mode(drvdata->comp) == GTRACE_COMPONENT_MODE_SYSFS) {
+		/*
+		 * The trace run will continue with the same allocated trace
+		 * buffer. Since the tracer is still enabled drvdata::buf can't
+		 * be NULL.
+		 */
+		__tmc_etr_enable_hw(drvdata);
+	} else {
+		/*
+		 * The ETR is not tracing and the buffer was just read.
+		 * As such prepare to free the trace buffer.
+		 */
+		sysfs_buf = drvdata->sysfs_buf;
+		drvdata->sysfs_buf = NULL;
+	}
+
+	drvdata->reading = false;
+	raw_spin_unlock_irqrestore(&drvdata->spinlock, flags);
+
+	/* Free allocated memory out side of the spinlock */
+	if (sysfs_buf)
+		tmc_etr_free_sysfs_buf(sysfs_buf);
+
+	return 0;
+}
+
+static const char *const buf_modes_str[] = {
+	[ETR_MODE_FLAT]		= "flat",
+	[ETR_MODE_ETR_SG]	= "tmc-sg",
+	[ETR_MODE_CATU]		= "catu",
+	[ETR_MODE_RESRV]	= "resrv",
+	[ETR_MODE_AUTO]		= "auto",
+};
+
+static ssize_t buf_modes_available_show(struct device *dev,
+					    struct device_attribute *attr, char *buf)
+{
+	struct etr_buf_hw buf_hw;
+	ssize_t size = 0;
+
+	get_etr_buf_hw(dev, &buf_hw);
+	size += sysfs_emit(buf, "%s ", buf_modes_str[ETR_MODE_AUTO]);
+	size += sysfs_emit_at(buf, size, "%s ", buf_modes_str[ETR_MODE_FLAT]);
+	if (buf_hw.has_etr_sg)
+		size += sysfs_emit_at(buf, size, "%s ", buf_modes_str[ETR_MODE_ETR_SG]);
+
+	if (buf_hw.has_catu)
+		size += sysfs_emit_at(buf, size, "%s ", buf_modes_str[ETR_MODE_CATU]);
+
+	if (buf_hw.has_resrv)
+		size += sysfs_emit_at(buf, size, "%s ", buf_modes_str[ETR_MODE_RESRV]);
+
+	size += sysfs_emit_at(buf, size, "\n");
+	return size;
+}
+static DEVICE_ATTR_RO(buf_modes_available);
+
+static ssize_t buf_mode_preferred_show(struct device *dev,
+					 struct device_attribute *attr, char *buf)
+{
+	struct tmc_drvdata *drvdata = dev_get_drvdata(dev->parent);
+
+	return sysfs_emit(buf, "%s\n", buf_modes_str[drvdata->etr_mode]);
+}
+
+static int buf_mode_set_resrv(struct tmc_drvdata *drvdata)
+{
+	int err = -EBUSY;
+	unsigned long flags;
+	struct tmc_resrv_buf *rbuf;
+
+	rbuf = &drvdata->resrv_buf;
+
+	/* Ensure there are no active crashdata read sessions */
+	raw_spin_lock_irqsave(&drvdata->spinlock, flags);
+	if (!rbuf->reading) {
+		tmc_crashdata_set_invalid(drvdata);
+		rbuf->len = 0;
+		drvdata->etr_mode = ETR_MODE_RESRV;
+		err = 0;
+	}
+	raw_spin_unlock_irqrestore(&drvdata->spinlock, flags);
+	return err;
+}
+
+static ssize_t buf_mode_preferred_store(struct device *dev,
+					  struct device_attribute *attr,
+					  const char *buf, size_t size)
+{
+	struct tmc_drvdata *drvdata = dev_get_drvdata(dev->parent);
+	struct etr_buf_hw buf_hw;
+
+	get_etr_buf_hw(dev, &buf_hw);
+	if (sysfs_streq(buf, buf_modes_str[ETR_MODE_FLAT]))
+		drvdata->etr_mode = ETR_MODE_FLAT;
+	else if (sysfs_streq(buf, buf_modes_str[ETR_MODE_ETR_SG]) && buf_hw.has_etr_sg)
+		drvdata->etr_mode = ETR_MODE_ETR_SG;
+	else if (sysfs_streq(buf, buf_modes_str[ETR_MODE_CATU]) && buf_hw.has_catu)
+		drvdata->etr_mode = ETR_MODE_CATU;
+	else if (sysfs_streq(buf, buf_modes_str[ETR_MODE_RESRV]) && buf_hw.has_resrv)
+		return buf_mode_set_resrv(drvdata) ? : size;
+	else if (sysfs_streq(buf, buf_modes_str[ETR_MODE_AUTO]))
+		drvdata->etr_mode = ETR_MODE_AUTO;
+	else
+		return -EINVAL;
+	return size;
+}
+static DEVICE_ATTR_RW(buf_mode_preferred);
+
+static struct attribute *coresight_etr_attrs[] = {
+	&dev_attr_buf_modes_available.attr,
+	&dev_attr_buf_mode_preferred.attr,
+	NULL,
+};
+
+const struct attribute_group coresight_etr_group = {
+	.attrs = coresight_etr_attrs,
+};
diff --git a/drivers/hwtracing/gtrace/coresight-tmc.h b/drivers/hwtracing/gtrace/coresight-tmc.h
new file mode 100644
index 000000000000..78281e04f243
--- /dev/null
+++ b/drivers/hwtracing/gtrace/coresight-tmc.h
@@ -0,0 +1,469 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright(C) 2015 Linaro Limited. All rights reserved.
+ * Author: Mathieu Poirier <mathieu.poirier@linaro.org>
+ * Derived from drivers/hwtracing/coresight/coresight-tmc.h
+ */
+
+#ifndef _CORESIGHT_TMC_H
+#define _CORESIGHT_TMC_H
+
+#include <linux/dma-mapping.h>
+#include <linux/idr.h>
+#include <linux/miscdevice.h>
+#include <linux/mutex.h>
+#include <linux/refcount.h>
+#include <linux/crc32.h>
+
+#define TMC_RSZ			0x004
+#define TMC_STS			0x00c
+#define TMC_RRD			0x010
+#define TMC_RRP			0x014
+#define TMC_RWP			0x018
+#define TMC_TRG			0x01c
+#define TMC_CTL			0x020
+#define TMC_RWD			0x024
+#define TMC_MODE		0x028
+#define TMC_LBUFLEVEL		0x02c
+#define TMC_CBUFLEVEL		0x030
+#define TMC_BUFWM		0x034
+#define TMC_RRPHI		0x038
+#define TMC_RWPHI		0x03c
+#define TMC_AXICTL		0x110
+#define TMC_DBALO		0x118
+#define TMC_DBAHI		0x11c
+#define TMC_FFSR		0x300
+#define TMC_FFCR		0x304
+#define TMC_PSCR		0x308
+#define TMC_ITMISCOP0		0xee0
+#define TMC_ITTRFLIN		0xee8
+#define TMC_ITATBDATA0		0xeec
+#define TMC_ITATBCTR2		0xef0
+#define TMC_ITATBCTR1		0xef4
+#define TMC_ITATBCTR0		0xef8
+#define TMC_AUTHSTATUS		0xfb8
+
+/* register description */
+/* TMC_CTL - 0x020 */
+#define TMC_CTL_CAPT_EN		BIT(0)
+/* TMC_STS - 0x00C */
+#define TMC_STS_TMCREADY_BIT	2
+#define TMC_STS_FULL		BIT(0)
+#define TMC_STS_TRIGGERED	BIT(1)
+#define TMC_STS_MEMERR		BIT(5)
+/*
+ * TMC_AXICTL - 0x110
+ *
+ * TMC AXICTL format for SoC-400
+ *	Bits [0-1]	: ProtCtrlBit0-1
+ *	Bits [2-5]	: CacheCtrlBits 0-3 (AXCACHE)
+ *	Bit  6		: Reserved
+ *	Bit  7		: ScatterGatherMode
+ *	Bits [8-11]	: WrBurstLen
+ *	Bits [12-31]	: Reserved.
+ * TMC AXICTL format for SoC-600, as above except:
+ *	Bits [2-5]	: AXI WCACHE
+ *	Bits [16-19]	: AXI RCACHE
+ *	Bits [20-31]	: Reserved
+ */
+#define TMC_AXICTL_CLEAR_MASK 0xfbf
+#define TMC_AXICTL_ARCACHE_MASK (0xf << 16)
+
+#define TMC_AXICTL_PROT_CTL_B0	BIT(0)
+#define TMC_AXICTL_PROT_CTL_B1	BIT(1)
+#define TMC_AXICTL_SCT_GAT_MODE	BIT(7)
+#define TMC_AXICTL_WR_BURST(v)	(((v) & 0xf) << 8)
+#define TMC_AXICTL_WR_BURST_16	0xf
+/* Write-back Read and Write-allocate */
+#define TMC_AXICTL_AXCACHE_OS	(0xf << 2)
+#define TMC_AXICTL_ARCACHE_OS	(0xf << 16)
+
+/* TMC_FFSR - 0x300 */
+#define TMC_FFSR_FT_STOPPED	BIT(1)
+
+/* TMC_FFCR - 0x304 */
+#define TMC_FFCR_FLUSHMAN_BIT	6
+#define TMC_FFCR_EN_FMT		BIT(0)
+#define TMC_FFCR_EN_TI		BIT(1)
+#define TMC_FFCR_FON_FLIN	BIT(4)
+#define TMC_FFCR_FON_TRIG_EVT	BIT(5)
+#define TMC_FFCR_TRIGON_TRIGIN	BIT(8)
+#define TMC_FFCR_STOP_ON_FLUSH	BIT(12)
+
+
+#define TMC_DEVID_NOSCAT	BIT(24)
+
+#define TMC_DEVID_AXIAW_VALID	BIT(16)
+#define TMC_DEVID_AXIAW_SHIFT	17
+#define TMC_DEVID_AXIAW_MASK	0x7f
+
+#define TMC_AUTH_NSID_MASK	GENMASK(1, 0)
+
+/* Major version 1 Minor version 0 */
+#define CS_CRASHDATA_VERSION	(1 << 16)
+
+enum tmc_config_type {
+	TMC_CONFIG_TYPE_ETB,
+	TMC_CONFIG_TYPE_ETR,
+	TMC_CONFIG_TYPE_ETF,
+};
+
+enum tmc_mode {
+	TMC_MODE_CIRCULAR_BUFFER,
+	TMC_MODE_SOFTWARE_FIFO,
+	TMC_MODE_HARDWARE_FIFO,
+};
+
+enum tmc_mem_intf_width {
+	TMC_MEM_INTF_WIDTH_32BITS	= 1,
+	TMC_MEM_INTF_WIDTH_64BITS	= 2,
+	TMC_MEM_INTF_WIDTH_128BITS	= 4,
+	TMC_MEM_INTF_WIDTH_256BITS	= 8,
+};
+
+/* TMC ETR Capability bit definitions */
+#define TMC_ETR_SG			(0x1U << 0)
+/* ETR has separate read/write cache encodings */
+#define TMC_ETR_AXI_ARCACHE		(0x1U << 1)
+/*
+ * TMC_ETR_SAVE_RESTORE - Values of RRP/RWP/STS.Full are
+ * retained when TMC leaves Disabled state, allowing us to continue
+ * the tracing from a point where we stopped. This also implies that
+ * the RRP/RWP/STS.Full should always be programmed to the correct
+ * value. Unfortunately this is not advertised by the hardware,
+ * so we have to rely on PID of the IP to detect the functionality.
+ */
+#define TMC_ETR_SAVE_RESTORE		(0x1U << 2)
+
+/* Coresight SoC-600 TMC-ETR unadvertised capabilities */
+#define CORESIGHT_SOC_600_ETR_CAPS	\
+	(TMC_ETR_SAVE_RESTORE | TMC_ETR_AXI_ARCACHE)
+
+/* TMC metadata region for ETR and ETF configurations */
+struct tmc_crash_metadata {
+	uint32_t crc32_mdata;	/* crc of metadata */
+	uint32_t crc32_tdata;	/* crc of tracedata */
+	uint32_t version;	/* 31:16 Major version, 15:0 Minor version */
+	uint32_t valid;         /* Indicate if this ETF/ETR was enabled */
+	uint32_t tmc_ram_size;  /* Ram Size register */
+	uint32_t tmc_sts;       /* Status register */
+	uint32_t tmc_mode;	/* Mode register */
+	uint32_t tmc_ffcr;	/* Formatter and flush control register */
+	uint32_t tmc_ffsr;	/* Formatter and flush status register */
+	uint32_t reserved32;
+	uint64_t tmc_rrp;       /* Ram Read pointer register */
+	uint64_t tmc_rwp;       /* Ram Write pointer register */
+	uint64_t tmc_dba;	/* Data buffer address register */
+	uint64_t trace_paddr;	/* Phys address of trace buffer */
+	uint64_t reserved64[3];
+};
+
+enum etr_mode {
+	ETR_MODE_FLAT,		/* Uses contiguous flat buffer */
+	ETR_MODE_ETR_SG,	/* Uses in-built TMC ETR SG mechanism */
+	ETR_MODE_CATU,		/* Use SG mechanism in CATU */
+	ETR_MODE_RESRV,		/* Use reserved region contiguous buffer */
+	ETR_MODE_AUTO,		/* Use the default mechanism */
+};
+
+struct tmc_drvdata;
+struct etr_buf_operations;
+
+/**
+ * struct etr_buf - Details of the buffer used by ETR
+ * drvdata	; The ETR drvdaga this buffer has been allocated for.
+ * @pid		; The PID of the session owner that etr_buf belongs to.
+ * refcount	; Number of sources currently using this etr_buf.
+ * @mode	: Mode of the ETR buffer, contiguous, Scatter Gather etc.
+ * @full	: Trace data overflow
+ * @size	: Size of the buffer.
+ * @hwaddr	: Address to be programmed in the TMC:DBA{LO,HI}
+ * @offset	: Offset of the trace data in the buffer for consumption.
+ * @len		: Available trace data @buf (may round up to the beginning).
+ * @ops		: ETR buffer operations for the mode.
+ * @private	: Backend specific information for the buf
+ */
+struct etr_buf {
+	struct tmc_drvdata		*drvdata;
+	pid_t				pid;
+	refcount_t			refcount;
+	enum etr_mode			mode;
+	bool				full;
+	ssize_t				size;
+	dma_addr_t			hwaddr;
+	unsigned long			offset;
+	s64				len;
+	const struct etr_buf_operations	*ops;
+	void				*private;
+};
+
+/**
+ * @paddr	: Start address of reserved memory region.
+ * @vaddr	: Corresponding CPU virtual address.
+ * @size	: Size of reserved memory region.
+ * @offset	: Offset of the trace data in the buffer for consumption.
+ * @reading	: Flag to indicate if reading is active
+ * @len	: Available trace data @buf (may round up to the beginning).
+ */
+struct tmc_resrv_buf {
+	phys_addr_t     paddr;
+	void		*vaddr;
+	size_t		size;
+	unsigned long	offset;
+	bool		reading;
+	s64		len;
+};
+
+/**
+ * struct tmc_drvdata - specifics associated to an TMC component
+ * @atclk:	optional clock for the core parts of the TMC.
+ * @pclk:	APB clock if present, otherwise NULL
+ * @base:	memory mapped base address for this component.
+ * @comp:	component vitals needed by the framework.
+ * @miscdev:	specifics to handle "/dev/xyz.tmc" entry.
+ * @crashdev:	specifics to handle "/dev/crash_tmc_xyz" entry for reading
+ *		crash tracedata.
+ * @spinlock:	only one at a time pls.
+ * @reading:	buffer's in the reading through "/dev/xyz.tmc" entry
+ * @stop_on_flush: Stop on flush trigger user configuration.
+ * @is_sink:	True if the ETF is configured as a sink.
+ * @buf:	Snapshot of the trace data for ETF/ETB.
+ * @etr_buf:	details of buffer used in TMC-ETR
+ * @len:	size of the available trace for ETF/ETB.
+ * @size:	trace buffer size for this TMC (common for all modes).
+ * @max_burst_size: The maximum burst size that can be initiated by
+ *		TMC-ETR on AXI bus.
+ * @config_type: TMC variant, must be of type @tmc_config_type.
+ * @memwidth:	width of the memory interface databus, in bytes.
+ * @trigger_cntr: amount of words to store after a trigger.
+ * @etr_caps:	Bitmask of capabilities of the TMC ETR, inferred from the
+ *		device configuration register (DEVID)
+ * @etr_mode:	User preferred mode of the ETR device, default auto mode.
+ * @idr:	Holds etr_bufs allocated for this ETR.
+ * @idr_mutex:	Access serialisation for idr.
+ * @sysfs_buf:	SYSFS buffer for ETR.
+ * @perf_buf:	PERF buffer for ETR.
+ * @resrv_buf:  Used by ETR as hardware trace buffer and for trace data
+ *		retention (after crash) only when ETR_MODE_RESRV buffer
+ *		mode is enabled. Used by ETF for trace data retention
+ *		(after crash) by default.
+ * @crash_mdata: Reserved memory for storing tmc crash metadata.
+ *		 Used by ETR/ETF.
+ */
+struct tmc_drvdata {
+	struct clk		*atclk;
+	struct clk		*pclk;
+	void __iomem		*base;
+	struct gtrace_component	*comp;
+	struct miscdevice	miscdev;
+	struct miscdevice	crashdev;
+	raw_spinlock_t		spinlock;
+	bool			reading;
+	bool			stop_on_flush;
+	bool			is_sink;
+	union {
+		char		*buf;		/* TMC ETB */
+		struct etr_buf	*etr_buf;	/* TMC ETR */
+	};
+	u32			len;
+	u32			size;
+	u32			max_burst_size;
+	enum tmc_config_type	config_type;
+	enum tmc_mem_intf_width	memwidth;
+	u32			trigger_cntr;
+	u32			etr_caps;
+	enum etr_mode		etr_mode;
+	struct idr		idr;
+	struct mutex		idr_mutex;
+	struct etr_buf		*sysfs_buf;
+	struct etr_buf		*perf_buf;
+	struct tmc_resrv_buf	resrv_buf;
+	struct tmc_resrv_buf	crash_mdata;
+};
+
+struct etr_buf_operations {
+	int (*alloc)(struct tmc_drvdata *drvdata, struct etr_buf *etr_buf,
+		     int node, void **pages);
+	void (*sync)(struct etr_buf *etr_buf, u64 rrp, u64 rwp);
+	ssize_t (*get_data)(struct etr_buf *etr_buf, u64 offset, size_t len,
+			    char **bufpp);
+	void (*free)(struct etr_buf *etr_buf);
+};
+
+/**
+ * struct tmc_pages - Collection of pages used for SG.
+ * @nr_pages:		Number of pages in the list.
+ * @daddrs:		Array of DMA'able page address.
+ * @pages:		Array pages for the buffer.
+ */
+struct tmc_pages {
+	int nr_pages;
+	dma_addr_t	*daddrs;
+	struct page	**pages;
+};
+
+/*
+ * struct tmc_sg_table - Generic SG table for TMC
+ * @dev:		Device for DMA allocations
+ * @table_vaddr:	Contiguous Virtual address for PageTable
+ * @data_vaddr:		Contiguous Virtual address for Data Buffer
+ * @table_daddr:	DMA address of the PageTable base
+ * @node:		Node for Page allocations
+ * @table_pages:	List of pages & dma address for Table
+ * @data_pages:		List of pages & dma address for Data
+ */
+struct tmc_sg_table {
+	struct device *dev;
+	void *table_vaddr;
+	void *data_vaddr;
+	dma_addr_t table_daddr;
+	int node;
+	struct tmc_pages table_pages;
+	struct tmc_pages data_pages;
+};
+
+/* Generic functions */
+int tmc_wait_for_tmcready(struct tmc_drvdata *drvdata);
+void tmc_flush_and_stop(struct tmc_drvdata *drvdata);
+void tmc_enable_hw(struct tmc_drvdata *drvdata);
+void tmc_disable_hw(struct tmc_drvdata *drvdata);
+u32 tmc_get_memwidth_mask(struct tmc_drvdata *drvdata);
+int tmc_read_prepare_crashdata(struct tmc_drvdata *drvdata);
+
+/* ETB/ETF functions */
+int tmc_read_prepare_etb(struct tmc_drvdata *drvdata);
+int tmc_read_unprepare_etb(struct tmc_drvdata *drvdata);
+int tmc_etf_setup_sysfs_buf(struct gtrace_component *comp);
+unsigned long tmc_update_etf_buffer(struct gtrace_component *comp,
+				    struct perf_output_handle *handle,
+				    struct gtrace_perf_auxbuf *buf);
+int tmc_enable_etf(struct gtrace_component *comp);
+int tmc_disable_etf(struct gtrace_component *comp);
+int tmc_panic_sync_etf(struct gtrace_component *comp);
+
+ssize_t tmc_etb_get_sysfs_trace(struct tmc_drvdata *drvdata,
+				loff_t pos, 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);
+int tmc_etr_setup_sysfs_buf(struct gtrace_component *comp);
+void *tmc_alloc_etr_buffer(struct gtrace_component *comp,
+			   struct perf_event *event, void **pages,
+			   int nr_pages, bool snapshot);
+void tmc_free_etr_buffer(void *config);
+unsigned long
+tmc_update_etr_buffer(struct gtrace_component *comp,
+		      struct perf_output_handle *handle,
+		      struct gtrace_perf_auxbuf *buf);
+int tmc_enable_etr_sink(struct gtrace_component *comp);
+int tmc_disable_etr_sink(struct gtrace_component *comp);
+int tmc_panic_sync_etr(struct gtrace_component *comp);
+ssize_t tmc_etr_get_sysfs_trace(struct tmc_drvdata *drvdata,
+				loff_t pos, size_t len, char **bufpp);
+
+
+#define TMC_REG_PAIR(name, lo_off, hi_off)				\
+static inline u64							\
+tmc_read_##name(struct tmc_drvdata *drvdata)				\
+{									\
+	return gtrace_relaxed_read_pair(drvdata->comp->pdata, lo_off, hi_off); \
+}									\
+static inline void							\
+tmc_write_##name(struct tmc_drvdata *drvdata, u64 val)			\
+{									\
+	gtrace_relaxed_write_pair(drvdata->comp->pdata, val, lo_off, hi_off); \
+}
+
+TMC_REG_PAIR(rrp, TMC_RRP, TMC_RRPHI)
+TMC_REG_PAIR(rwp, TMC_RWP, TMC_RWPHI)
+TMC_REG_PAIR(dba, TMC_DBALO, TMC_DBAHI)
+
+/* Initialise the caps from unadvertised static capabilities of the device */
+static inline void tmc_etr_init_caps(struct tmc_drvdata *drvdata, u32 dev_caps)
+{
+	WARN_ON(drvdata->etr_caps);
+	drvdata->etr_caps = dev_caps;
+}
+
+static inline void tmc_etr_set_cap(struct tmc_drvdata *drvdata, u32 cap)
+{
+	drvdata->etr_caps |= cap;
+}
+
+static inline bool tmc_etr_has_cap(struct tmc_drvdata *drvdata, u32 cap)
+{
+	return !!(drvdata->etr_caps & cap);
+}
+
+struct tmc_sg_table *tmc_alloc_sg_table(struct device *dev,
+					int node,
+					int nr_tpages,
+					int nr_dpages,
+					void **pages);
+void tmc_free_sg_table(struct tmc_sg_table *sg_table);
+void tmc_sg_table_sync_table(struct tmc_sg_table *sg_table);
+void tmc_sg_table_sync_data_range(struct tmc_sg_table *table,
+				  u64 offset, u64 size);
+ssize_t tmc_sg_table_get_data(struct tmc_sg_table *sg_table,
+			      u64 offset, size_t len, char **bufpp);
+
+static inline unsigned long
+tmc_sg_table_buf_size(struct tmc_sg_table *sg_table)
+{
+	return (unsigned long)sg_table->data_pages.nr_pages << PAGE_SHIFT;
+}
+
+static inline bool tmc_has_reserved_buffer(struct tmc_drvdata *drvdata)
+{
+	if (drvdata->resrv_buf.vaddr &&
+	    drvdata->resrv_buf.size)
+		return true;
+	return false;
+}
+
+static inline bool tmc_has_crash_mdata_buffer(struct tmc_drvdata *drvdata)
+{
+	if (drvdata->crash_mdata.vaddr &&
+	    drvdata->crash_mdata.size)
+		return true;
+	return false;
+}
+
+static inline void tmc_crashdata_set_invalid(struct tmc_drvdata *drvdata)
+{
+	struct tmc_crash_metadata *mdata;
+
+	mdata = (struct tmc_crash_metadata *)drvdata->crash_mdata.vaddr;
+
+	if (tmc_has_crash_mdata_buffer(drvdata))
+		mdata->valid = false;
+}
+
+static inline uint32_t find_crash_metadata_crc(struct tmc_crash_metadata *md)
+{
+	unsigned long crc_size;
+
+	crc_size = sizeof(struct tmc_crash_metadata) -
+		offsetof(struct tmc_crash_metadata, crc32_tdata);
+	return crc32_le(0, (void *)&md->crc32_tdata, crc_size);
+}
+
+static inline uint32_t find_crash_tracedata_crc(struct tmc_drvdata *drvdata,
+						struct tmc_crash_metadata *md)
+{
+	unsigned long crc_size;
+
+	/* Take CRC of configured buffer size to keep it simple */
+	crc_size = md->tmc_ram_size << 2;
+	return crc32_le(0, (void *)drvdata->resrv_buf.vaddr, crc_size);
+}
+
+struct gtrace_component *tmc_etr_get_catu_device(struct tmc_drvdata *drvdata);
+
+void tmc_etr_set_catu_ops(const struct etr_buf_operations *catu);
+void tmc_etr_remove_catu_ops(void);
+struct etr_buf *tmc_etr_get_buffer(struct gtrace_component *comp);
+extern const struct attribute_group coresight_etr_group;
+
+#endif
-- 
2.34.1


^ permalink raw reply	[flat|nested] 25+ messages in thread

* [PATCH 21/24] perf tools: riscv: making gtrace PMU listable
  2026-10-23  3:14 [RFC PATCH 00/24] Add Linux RISC-V trace support via the gtrace framework Zane Leung
                   ` (19 preceding siblings ...)
  2026-10-23  3:15 ` [PATCH 20/24] gtrace: Add CoreSight TMC driver support Zane Leung
@ 2026-10-23  3:15 ` Zane Leung
  2026-10-23  3:15 ` [PATCH 22/24] perf tools: Add Nexus RISC-V Trace decoder Zane Leung
                   ` (2 subsequent siblings)
  23 siblings, 0 replies; 25+ messages in thread
From: Zane Leung @ 2026-10-23  3:15 UTC (permalink / raw)
  To: gregkh, palmer, pjw, alexander.shishkin, suzuki.poulose,
	mchitale, anup, irogers
  Cc: mike.leach, james.clark, leo.yan, peterz, mingo, namhyung,
	mark.rutland, jolsa, adrian.hunter, alex, atish.patra,
	andrew.jones, sunilvl, liangzhen, zhuangqiubin, coresight,
	linux-riscv, linux-kernel

This commit enhances the RISC-V performance tools by allowing the
gtrace PMU to be listed and selected.

Signed-off-by: Zane Leung <liangzhen@linux.spacemit.com>
---
 tools/perf/arch/riscv/util/Build |  1 +
 tools/perf/arch/riscv/util/pmu.c | 19 +++++++++++++++++++
 2 files changed, 20 insertions(+)
 create mode 100644 tools/perf/arch/riscv/util/pmu.c

diff --git a/tools/perf/arch/riscv/util/Build b/tools/perf/arch/riscv/util/Build
index e07d5525ece6..57991c43e8f0 100644
--- a/tools/perf/arch/riscv/util/Build
+++ b/tools/perf/arch/riscv/util/Build
@@ -1,2 +1,3 @@
 perf-util-y += header.o
 perf-util-y += auxtrace.o
+perf-util-y += pmu.o
diff --git a/tools/perf/arch/riscv/util/pmu.c b/tools/perf/arch/riscv/util/pmu.c
new file mode 100644
index 000000000000..8cb8c82182ac
--- /dev/null
+++ b/tools/perf/arch/riscv/util/pmu.c
@@ -0,0 +1,19 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2026 Spacemit Technologies, Inc.
+ */
+
+#include <string.h>
+#include <linux/perf_event.h>
+
+#include "../../../util/pmu.h"
+
+#define GTRACE_PMU_NAME "gtrace"
+
+void perf_pmu__arch_init(struct perf_pmu *pmu)
+{
+	if (!strcmp(pmu->name, GTRACE_PMU_NAME)) {
+		pmu->auxtrace = true;
+		pmu->selectable = true;
+	}
+}
-- 
2.34.1


^ permalink raw reply	[flat|nested] 25+ messages in thread

* [PATCH 22/24] perf tools: Add Nexus RISC-V Trace decoder
  2026-10-23  3:14 [RFC PATCH 00/24] Add Linux RISC-V trace support via the gtrace framework Zane Leung
                   ` (20 preceding siblings ...)
  2026-10-23  3:15 ` [PATCH 21/24] perf tools: riscv: making gtrace PMU listable Zane Leung
@ 2026-10-23  3:15 ` Zane Leung
  2026-10-23  3:15 ` [PATCH 23/24] perf tools: gtrace: Record and parse trace encoder metadata Zane Leung
  2026-10-23  3:15 ` [PATCH 24/24] perf tools: gtrace: Add AUX trace decoder and branch sample synthesis Zane Leung
  23 siblings, 0 replies; 25+ messages in thread
From: Zane Leung @ 2026-10-23  3:15 UTC (permalink / raw)
  To: gregkh, palmer, pjw, alexander.shishkin, suzuki.poulose,
	mchitale, anup, irogers
  Cc: mike.leach, james.clark, leo.yan, peterz, mingo, namhyung,
	mark.rutland, jolsa, adrian.hunter, alex, atish.patra,
	andrew.jones, sunilvl, liangzhen, zhuangqiubin, coresight,
	linux-riscv, linux-kernel

Add support for RISC-V Nexus Trace decoder based on the reference
implementation from the RISC-V Nexus Trace specification. This includes
a CoreSight frame deformatter to remove the trace formatter overhead.

Signed-off-by: Zane Leung <liangzhen@linux.spacemit.com>
---
 tools/arch/riscv/include/asm/insn.h           | 645 +++++++++++++
 tools/perf/util/Build                         |   1 +
 tools/perf/util/rvtrace-decoder/Build         |   2 +
 .../util/rvtrace-decoder/rvtrace-decoder.c    | 423 ++++++++
 .../util/rvtrace-decoder/rvtrace-decoder.h    | 122 +++
 .../rvtrace-decoder/rvtrace-ntrace-decoder.c  | 910 ++++++++++++++++++
 .../rvtrace-decoder/rvtrace-ntrace-decoder.h  | 124 +++
 7 files changed, 2227 insertions(+)
 create mode 100644 tools/arch/riscv/include/asm/insn.h
 create mode 100644 tools/perf/util/rvtrace-decoder/Build
 create mode 100644 tools/perf/util/rvtrace-decoder/rvtrace-decoder.c
 create mode 100644 tools/perf/util/rvtrace-decoder/rvtrace-decoder.h
 create mode 100644 tools/perf/util/rvtrace-decoder/rvtrace-ntrace-decoder.c
 create mode 100644 tools/perf/util/rvtrace-decoder/rvtrace-ntrace-decoder.h

diff --git a/tools/arch/riscv/include/asm/insn.h b/tools/arch/riscv/include/asm/insn.h
new file mode 100644
index 000000000000..6c020afe926b
--- /dev/null
+++ b/tools/arch/riscv/include/asm/insn.h
@@ -0,0 +1,645 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2020 SiFive
+ */
+
+#ifndef _ASM_RISCV_INSN_H
+#define _ASM_RISCV_INSN_H
+
+#include <linux/bits.h>
+
+#define RV_INSN_FUNCT3_MASK	GENMASK(14, 12)
+#define RV_INSN_FUNCT3_OPOFF	12
+#define RV_INSN_OPCODE_MASK	GENMASK(6, 0)
+#define RV_INSN_OPCODE_OPOFF	0
+#define RV_INSN_FUNCT12_OPOFF	20
+
+#define RV_ENCODE_FUNCT3(f_)	(RVG_FUNCT3_##f_ << RV_INSN_FUNCT3_OPOFF)
+#define RV_ENCODE_FUNCT12(f_)	(RVG_FUNCT12_##f_ << RV_INSN_FUNCT12_OPOFF)
+
+/* The bit field of immediate value in I-type instruction */
+#define RV_I_IMM_SIGN_OPOFF	31
+#define RV_I_IMM_11_0_OPOFF	20
+#define RV_I_IMM_SIGN_OFF	12
+#define RV_I_IMM_11_0_OFF	0
+#define RV_I_IMM_11_0_MASK	GENMASK(11, 0)
+
+/* The bit field of immediate value in J-type instruction */
+#define RV_J_IMM_SIGN_OPOFF	31
+#define RV_J_IMM_10_1_OPOFF	21
+#define RV_J_IMM_11_OPOFF	20
+#define RV_J_IMM_19_12_OPOFF	12
+#define RV_J_IMM_SIGN_OFF	20
+#define RV_J_IMM_10_1_OFF	1
+#define RV_J_IMM_11_OFF		11
+#define RV_J_IMM_19_12_OFF	12
+#define RV_J_IMM_10_1_MASK	GENMASK(9, 0)
+#define RV_J_IMM_11_MASK	GENMASK(0, 0)
+#define RV_J_IMM_19_12_MASK	GENMASK(7, 0)
+
+/*
+ * U-type IMMs contain the upper 20bits [31:20] of an immediate with
+ * the rest filled in by zeros, so no shifting required. Similarly,
+ * bit31 contains the signed state, so no sign extension necessary.
+ */
+#define RV_U_IMM_SIGN_OPOFF	31
+#define RV_U_IMM_31_12_OPOFF	0
+#define RV_U_IMM_31_12_MASK	GENMASK(31, 12)
+
+/* The bit field of immediate value in B-type instruction */
+#define RV_B_IMM_SIGN_OPOFF	31
+#define RV_B_IMM_10_5_OPOFF	25
+#define RV_B_IMM_4_1_OPOFF	8
+#define RV_B_IMM_11_OPOFF	7
+#define RV_B_IMM_SIGN_OFF	12
+#define RV_B_IMM_10_5_OFF	5
+#define RV_B_IMM_4_1_OFF	1
+#define RV_B_IMM_11_OFF		11
+#define RV_B_IMM_10_5_MASK	GENMASK(5, 0)
+#define RV_B_IMM_4_1_MASK	GENMASK(3, 0)
+#define RV_B_IMM_11_MASK	GENMASK(0, 0)
+
+/* The register offset in RVG instruction */
+#define RVG_RS1_OPOFF		15
+#define RVG_RS2_OPOFF		20
+#define RVG_RD_OPOFF		7
+#define RVG_RS1_MASK		GENMASK(4, 0)
+#define RVG_RS2_MASK		GENMASK(4, 0)
+#define RVG_RD_MASK		GENMASK(4, 0)
+
+/* The bit field of immediate value in RVC J instruction */
+#define RVC_J_IMM_SIGN_OPOFF	12
+#define RVC_J_IMM_4_OPOFF	11
+#define RVC_J_IMM_9_8_OPOFF	9
+#define RVC_J_IMM_10_OPOFF	8
+#define RVC_J_IMM_6_OPOFF	7
+#define RVC_J_IMM_7_OPOFF	6
+#define RVC_J_IMM_3_1_OPOFF	3
+#define RVC_J_IMM_5_OPOFF	2
+#define RVC_J_IMM_SIGN_OFF	11
+#define RVC_J_IMM_4_OFF		4
+#define RVC_J_IMM_9_8_OFF	8
+#define RVC_J_IMM_10_OFF	10
+#define RVC_J_IMM_6_OFF		6
+#define RVC_J_IMM_7_OFF		7
+#define RVC_J_IMM_3_1_OFF	1
+#define RVC_J_IMM_5_OFF		5
+#define RVC_J_IMM_4_MASK	GENMASK(0, 0)
+#define RVC_J_IMM_9_8_MASK	GENMASK(1, 0)
+#define RVC_J_IMM_10_MASK	GENMASK(0, 0)
+#define RVC_J_IMM_6_MASK	GENMASK(0, 0)
+#define RVC_J_IMM_7_MASK	GENMASK(0, 0)
+#define RVC_J_IMM_3_1_MASK	GENMASK(2, 0)
+#define RVC_J_IMM_5_MASK	GENMASK(0, 0)
+
+/* The bit field of immediate value in RVC B instruction */
+#define RVC_B_IMM_SIGN_OPOFF	12
+#define RVC_B_IMM_4_3_OPOFF	10
+#define RVC_B_IMM_7_6_OPOFF	5
+#define RVC_B_IMM_2_1_OPOFF	3
+#define RVC_B_IMM_5_OPOFF	2
+#define RVC_B_IMM_SIGN_OFF	8
+#define RVC_B_IMM_4_3_OFF	3
+#define RVC_B_IMM_7_6_OFF	6
+#define RVC_B_IMM_2_1_OFF	1
+#define RVC_B_IMM_5_OFF		5
+#define RVC_B_IMM_4_3_MASK	GENMASK(1, 0)
+#define RVC_B_IMM_7_6_MASK	GENMASK(1, 0)
+#define RVC_B_IMM_2_1_MASK	GENMASK(1, 0)
+#define RVC_B_IMM_5_MASK	GENMASK(0, 0)
+
+#define RVC_INSN_FUNCT4_MASK	GENMASK(15, 12)
+#define RVC_INSN_FUNCT4_OPOFF	12
+#define RVC_INSN_FUNCT3_MASK	GENMASK(15, 13)
+#define RVC_INSN_FUNCT3_OPOFF	13
+#define RVC_INSN_J_RS1_MASK	GENMASK(11, 7)
+#define RVC_INSN_J_RS2_MASK	GENMASK(6, 2)
+#define RVC_INSN_OPCODE_MASK	GENMASK(1, 0)
+#define RVC_ENCODE_FUNCT3(f_)	(RVC_FUNCT3_##f_ << RVC_INSN_FUNCT3_OPOFF)
+#define RVC_ENCODE_FUNCT4(f_)	(RVC_FUNCT4_##f_ << RVC_INSN_FUNCT4_OPOFF)
+
+/* The register offset in RVC op=C0 instruction */
+#define RVC_C0_RS1_OPOFF	7
+#define RVC_C0_RS2_OPOFF	2
+#define RVC_C0_RD_OPOFF		2
+
+/* The register offset in RVC op=C1 instruction */
+#define RVC_C1_RS1_OPOFF	7
+#define RVC_C1_RS2_OPOFF	2
+#define RVC_C1_RD_OPOFF		7
+
+/* The register offset in RVC op=C2 instruction */
+#define RVC_C2_RS1_OPOFF	7
+#define RVC_C2_RS2_OPOFF	2
+#define RVC_C2_RD_OPOFF		7
+#define RVC_C2_RS1_MASK		GENMASK(4, 0)
+
+/* parts of opcode for RVG*/
+#define RVG_OPCODE_FENCE	0x0f
+#define RVG_OPCODE_AUIPC	0x17
+#define RVG_OPCODE_BRANCH	0x63
+#define RVG_OPCODE_JALR		0x67
+#define RVG_OPCODE_JAL		0x6f
+#define RVG_OPCODE_SYSTEM	0x73
+#define RVG_SYSTEM_CSR_OFF	20
+#define RVG_SYSTEM_CSR_MASK	GENMASK(12, 0)
+
+/* parts of opcode for RVF, RVD and RVQ */
+#define RVFDQ_FL_FS_WIDTH_OFF	12
+#define RVFDQ_FL_FS_WIDTH_MASK	GENMASK(2, 0)
+#define RVFDQ_FL_FS_WIDTH_W	2
+#define RVFDQ_FL_FS_WIDTH_D	3
+#define RVFDQ_LS_FS_WIDTH_Q	4
+#define RVFDQ_OPCODE_FL		0x07
+#define RVFDQ_OPCODE_FS		0x27
+
+/* parts of opcode for RVV */
+#define RVV_OPCODE_VECTOR	0x57
+#define RVV_VL_VS_WIDTH_8	0
+#define RVV_VL_VS_WIDTH_16	5
+#define RVV_VL_VS_WIDTH_32	6
+#define RVV_VL_VS_WIDTH_64	7
+#define RVV_OPCODE_VL		RVFDQ_OPCODE_FL
+#define RVV_OPCODE_VS		RVFDQ_OPCODE_FS
+
+/* parts of opcode for RVC*/
+#define RVC_OPCODE_C0		0x0
+#define RVC_OPCODE_C1		0x1
+#define RVC_OPCODE_C2		0x2
+
+/* parts of funct3 code for I, M, A extension*/
+#define RVG_FUNCT3_JALR		0x0
+#define RVG_FUNCT3_BEQ		0x0
+#define RVG_FUNCT3_BNE		0x1
+#define RVG_FUNCT3_BLT		0x4
+#define RVG_FUNCT3_BGE		0x5
+#define RVG_FUNCT3_BLTU		0x6
+#define RVG_FUNCT3_BGEU		0x7
+
+/* parts of funct3 code for C extension*/
+#define RVC_FUNCT3_C_BEQZ	0x6
+#define RVC_FUNCT3_C_BNEZ	0x7
+#define RVC_FUNCT3_C_J		0x5
+#define RVC_FUNCT3_C_JAL	0x1
+#define RVC_FUNCT4_C_JR		0x8
+#define RVC_FUNCT4_C_JALR	0x9
+#define RVC_FUNCT4_C_EBREAK	0x9
+
+#define RVG_FUNCT12_ECALL	0x0
+#define RVG_FUNCT12_EBREAK	0x1
+#define RVG_FUNCT12_SRET	0x102
+#define RVG_FUNCT12_MRET	0x303
+
+#define RVG_MATCH_AUIPC		(RVG_OPCODE_AUIPC)
+#define RVG_MATCH_JALR		(RV_ENCODE_FUNCT3(JALR) | RVG_OPCODE_JALR)
+#define RVG_MATCH_JAL		(RVG_OPCODE_JAL)
+#define RVG_MATCH_FENCE		(RVG_OPCODE_FENCE)
+#define RVG_MATCH_BEQ		(RV_ENCODE_FUNCT3(BEQ) | RVG_OPCODE_BRANCH)
+#define RVG_MATCH_BNE		(RV_ENCODE_FUNCT3(BNE) | RVG_OPCODE_BRANCH)
+#define RVG_MATCH_BLT		(RV_ENCODE_FUNCT3(BLT) | RVG_OPCODE_BRANCH)
+#define RVG_MATCH_BGE		(RV_ENCODE_FUNCT3(BGE) | RVG_OPCODE_BRANCH)
+#define RVG_MATCH_BLTU		(RV_ENCODE_FUNCT3(BLTU) | RVG_OPCODE_BRANCH)
+#define RVG_MATCH_BGEU		(RV_ENCODE_FUNCT3(BGEU) | RVG_OPCODE_BRANCH)
+#define RVG_MATCH_ECALL		(RV_ENCODE_FUNCT12(EBREAK) | RVG_OPCODE_SYSTEM)
+#define RVG_MATCH_EBREAK	(RV_ENCODE_FUNCT12(EBREAK) | RVG_OPCODE_SYSTEM)
+#define RVG_MATCH_SRET		(RV_ENCODE_FUNCT12(SRET) | RVG_OPCODE_SYSTEM)
+#define RVG_MATCH_MRET		(RV_ENCODE_FUNCT12(MRET) | RVG_OPCODE_SYSTEM)
+#define RVC_MATCH_C_BEQZ	(RVC_ENCODE_FUNCT3(C_BEQZ) | RVC_OPCODE_C1)
+#define RVC_MATCH_C_BNEZ	(RVC_ENCODE_FUNCT3(C_BNEZ) | RVC_OPCODE_C1)
+#define RVC_MATCH_C_J		(RVC_ENCODE_FUNCT3(C_J) | RVC_OPCODE_C1)
+#define RVC_MATCH_C_JAL		(RVC_ENCODE_FUNCT3(C_JAL) | RVC_OPCODE_C1)
+#define RVC_MATCH_C_JR		(RVC_ENCODE_FUNCT4(C_JR) | RVC_OPCODE_C2)
+#define RVC_MATCH_C_JALR	(RVC_ENCODE_FUNCT4(C_JALR) | RVC_OPCODE_C2)
+#define RVC_MATCH_C_EBREAK	(RVC_ENCODE_FUNCT4(C_EBREAK) | RVC_OPCODE_C2)
+
+#define RVG_MASK_AUIPC		(RV_INSN_OPCODE_MASK)
+#define RVG_MASK_JALR		(RV_INSN_FUNCT3_MASK | RV_INSN_OPCODE_MASK)
+#define RVG_MASK_JAL		(RV_INSN_OPCODE_MASK)
+#define RVG_MASK_FENCE		(RV_INSN_OPCODE_MASK)
+#define RVC_MASK_C_JALR		(RVC_INSN_FUNCT4_MASK | RVC_INSN_J_RS2_MASK | RVC_INSN_OPCODE_MASK)
+#define RVC_MASK_C_JR		(RVC_INSN_FUNCT4_MASK | RVC_INSN_J_RS2_MASK | RVC_INSN_OPCODE_MASK)
+#define RVC_MASK_C_JAL		(RVC_INSN_FUNCT3_MASK | RVC_INSN_OPCODE_MASK)
+#define RVC_MASK_C_J		(RVC_INSN_FUNCT3_MASK | RVC_INSN_OPCODE_MASK)
+#define RVG_MASK_BEQ		(RV_INSN_FUNCT3_MASK | RV_INSN_OPCODE_MASK)
+#define RVG_MASK_BNE		(RV_INSN_FUNCT3_MASK | RV_INSN_OPCODE_MASK)
+#define RVG_MASK_BLT		(RV_INSN_FUNCT3_MASK | RV_INSN_OPCODE_MASK)
+#define RVG_MASK_BGE		(RV_INSN_FUNCT3_MASK | RV_INSN_OPCODE_MASK)
+#define RVG_MASK_BLTU		(RV_INSN_FUNCT3_MASK | RV_INSN_OPCODE_MASK)
+#define RVG_MASK_BGEU		(RV_INSN_FUNCT3_MASK | RV_INSN_OPCODE_MASK)
+#define RVC_MASK_C_BEQZ		(RVC_INSN_FUNCT3_MASK | RVC_INSN_OPCODE_MASK)
+#define RVC_MASK_C_BNEZ		(RVC_INSN_FUNCT3_MASK | RVC_INSN_OPCODE_MASK)
+#define RVC_MASK_C_EBREAK	0xffff
+#define RVG_MASK_EBREAK		0xffffffff
+#define RVG_MASK_ECALL		0xffffffff
+#define RVG_MASK_SRET		0xffffffff
+#define RVG_MASK_MRET		0xffffffff
+
+#define __INSN_LENGTH_MASK	_UL(0x3)
+#define __INSN_LENGTH_GE_32	_UL(0x3)
+#define __INSN_OPCODE_MASK	_UL(0x7F)
+#define __INSN_BRANCH_OPCODE	_UL(RVG_OPCODE_BRANCH)
+
+#define __RISCV_INSN_FUNCS(name, mask, val)				\
+static __always_inline bool riscv_insn_is_##name(u32 code)		\
+{									\
+	BUILD_BUG_ON(~(mask) & (val));					\
+	return (code & (mask)) == (val);				\
+}									\
+
+#if __riscv_xlen == 32
+/* C.JAL is an RV32C-only instruction */
+__RISCV_INSN_FUNCS(c_jal, RVC_MASK_C_JAL, RVC_MATCH_C_JAL)
+#else
+#define riscv_insn_is_c_jal(opcode) 0
+#endif
+__RISCV_INSN_FUNCS(auipc, RVG_MASK_AUIPC, RVG_MATCH_AUIPC)
+__RISCV_INSN_FUNCS(jalr, RVG_MASK_JALR, RVG_MATCH_JALR)
+__RISCV_INSN_FUNCS(jal, RVG_MASK_JAL, RVG_MATCH_JAL)
+__RISCV_INSN_FUNCS(c_j, RVC_MASK_C_J, RVC_MATCH_C_J)
+__RISCV_INSN_FUNCS(beq, RVG_MASK_BEQ, RVG_MATCH_BEQ)
+__RISCV_INSN_FUNCS(bne, RVG_MASK_BNE, RVG_MATCH_BNE)
+__RISCV_INSN_FUNCS(blt, RVG_MASK_BLT, RVG_MATCH_BLT)
+__RISCV_INSN_FUNCS(bge, RVG_MASK_BGE, RVG_MATCH_BGE)
+__RISCV_INSN_FUNCS(bltu, RVG_MASK_BLTU, RVG_MATCH_BLTU)
+__RISCV_INSN_FUNCS(bgeu, RVG_MASK_BGEU, RVG_MATCH_BGEU)
+__RISCV_INSN_FUNCS(c_beqz, RVC_MASK_C_BEQZ, RVC_MATCH_C_BEQZ)
+__RISCV_INSN_FUNCS(c_bnez, RVC_MASK_C_BNEZ, RVC_MATCH_C_BNEZ)
+__RISCV_INSN_FUNCS(c_ebreak, RVC_MASK_C_EBREAK, RVC_MATCH_C_EBREAK)
+__RISCV_INSN_FUNCS(ebreak, RVG_MASK_EBREAK, RVG_MATCH_EBREAK)
+__RISCV_INSN_FUNCS(ecall, RVG_MASK_EBREAK, RVG_MATCH_EBREAK)
+__RISCV_INSN_FUNCS(sret, RVG_MASK_SRET, RVG_MATCH_SRET)
+__RISCV_INSN_FUNCS(mret, RVG_MASK_MRET, RVG_MATCH_MRET)
+__RISCV_INSN_FUNCS(fence, RVG_MASK_FENCE, RVG_MATCH_FENCE);
+
+/* special case to catch _any_ system instruction */
+static __always_inline bool riscv_insn_is_system(u32 code)
+{
+	return (code & RV_INSN_OPCODE_MASK) == RVG_OPCODE_SYSTEM;
+}
+
+/* special case to catch _any_ branch instruction */
+static __always_inline bool riscv_insn_is_branch(u32 code)
+{
+	return (code & RV_INSN_OPCODE_MASK) == RVG_OPCODE_BRANCH;
+}
+
+static __always_inline bool riscv_insn_is_c_jr(u32 code)
+{
+	return (code & RVC_MASK_C_JR) == RVC_MATCH_C_JR &&
+	       (code & RVC_INSN_J_RS1_MASK) != 0;
+}
+
+static __always_inline bool riscv_insn_is_c_jalr(u32 code)
+{
+	return (code & RVC_MASK_C_JALR) == RVC_MATCH_C_JALR &&
+	       (code & RVC_INSN_J_RS1_MASK) != 0;
+}
+
+#define INSN_MATCH_LB		0x3
+#define INSN_MASK_LB		0x707f
+#define INSN_MATCH_LH		0x1003
+#define INSN_MASK_LH		0x707f
+#define INSN_MATCH_LW		0x2003
+#define INSN_MASK_LW		0x707f
+#define INSN_MATCH_LD		0x3003
+#define INSN_MASK_LD		0x707f
+#define INSN_MATCH_LBU		0x4003
+#define INSN_MASK_LBU		0x707f
+#define INSN_MATCH_LHU		0x5003
+#define INSN_MASK_LHU		0x707f
+#define INSN_MATCH_LWU		0x6003
+#define INSN_MASK_LWU		0x707f
+#define INSN_MATCH_SB		0x23
+#define INSN_MASK_SB		0x707f
+#define INSN_MATCH_SH		0x1023
+#define INSN_MASK_SH		0x707f
+#define INSN_MATCH_SW		0x2023
+#define INSN_MASK_SW		0x707f
+#define INSN_MATCH_SD		0x3023
+#define INSN_MASK_SD		0x707f
+
+#define INSN_MATCH_C_LD		0x6000
+#define INSN_MASK_C_LD		0xe003
+#define INSN_MATCH_C_SD		0xe000
+#define INSN_MASK_C_SD		0xe003
+#define INSN_MATCH_C_LW		0x4000
+#define INSN_MASK_C_LW		0xe003
+#define INSN_MATCH_C_SW		0xc000
+#define INSN_MASK_C_SW		0xe003
+#define INSN_MATCH_C_LDSP	0x6002
+#define INSN_MASK_C_LDSP	0xe003
+#define INSN_MATCH_C_SDSP	0xe002
+#define INSN_MASK_C_SDSP	0xe003
+#define INSN_MATCH_C_LWSP	0x4002
+#define INSN_MASK_C_LWSP	0xe003
+#define INSN_MATCH_C_SWSP	0xc002
+#define INSN_MASK_C_SWSP	0xe003
+
+#define INSN_OPCODE_MASK	0x007c
+#define INSN_OPCODE_SHIFT	2
+#define INSN_OPCODE_SYSTEM	28
+
+#define INSN_MASK_WFI		0xffffffff
+#define INSN_MATCH_WFI		0x10500073
+
+#define INSN_MASK_WRS		0xffffffff
+#define INSN_MATCH_WRS		0x00d00073
+
+#define INSN_MATCH_CSRRW	0x1073
+#define INSN_MASK_CSRRW		0x707f
+#define INSN_MATCH_CSRRS	0x2073
+#define INSN_MASK_CSRRS		0x707f
+#define INSN_MATCH_CSRRC	0x3073
+#define INSN_MASK_CSRRC		0x707f
+#define INSN_MATCH_CSRRWI	0x5073
+#define INSN_MASK_CSRRWI	0x707f
+#define INSN_MATCH_CSRRSI	0x6073
+#define INSN_MASK_CSRRSI	0x707f
+#define INSN_MATCH_CSRRCI	0x7073
+#define INSN_MASK_CSRRCI	0x707f
+
+#define INSN_MATCH_FLW		0x2007
+#define INSN_MASK_FLW		0x707f
+#define INSN_MATCH_FLD		0x3007
+#define INSN_MASK_FLD		0x707f
+#define INSN_MATCH_FLQ		0x4007
+#define INSN_MASK_FLQ		0x707f
+#define INSN_MATCH_FSW		0x2027
+#define INSN_MASK_FSW		0x707f
+#define INSN_MATCH_FSD		0x3027
+#define INSN_MASK_FSD		0x707f
+#define INSN_MATCH_FSQ		0x4027
+#define INSN_MASK_FSQ		0x707f
+
+#define INSN_MATCH_C_FLD	0x2000
+#define INSN_MASK_C_FLD		0xe003
+#define INSN_MATCH_C_FLW	0x6000
+#define INSN_MASK_C_FLW		0xe003
+#define INSN_MATCH_C_FSD	0xa000
+#define INSN_MASK_C_FSD		0xe003
+#define INSN_MATCH_C_FSW	0xe000
+#define INSN_MASK_C_FSW		0xe003
+#define INSN_MATCH_C_FLDSP	0x2002
+#define INSN_MASK_C_FLDSP	0xe003
+#define INSN_MATCH_C_FSDSP	0xa002
+#define INSN_MASK_C_FSDSP	0xe003
+#define INSN_MATCH_C_FLWSP	0x6002
+#define INSN_MASK_C_FLWSP	0xe003
+#define INSN_MATCH_C_FSWSP	0xe002
+#define INSN_MASK_C_FSWSP	0xe003
+
+#define INSN_MATCH_C_LHU		0x8400
+#define INSN_MASK_C_LHU			0xfc43
+#define INSN_MATCH_C_LH			0x8440
+#define INSN_MASK_C_LH			0xfc43
+#define INSN_MATCH_C_SH			0x8c00
+#define INSN_MASK_C_SH			0xfc43
+
+#define INSN_16BIT_MASK		0x3
+#define INSN_IS_16BIT(insn)	(((insn) & INSN_16BIT_MASK) != INSN_16BIT_MASK)
+#define INSN_LEN(insn)		(INSN_IS_16BIT(insn) ? 2 : 4)
+
+#define SHIFT_RIGHT(x, y)		\
+	((y) < 0 ? ((x) << -(y)) : ((x) >> (y)))
+
+#define REG_MASK			\
+	((1 << (5 + LOG_REGBYTES)) - (1 << LOG_REGBYTES))
+
+#define REG_OFFSET(insn, pos)		\
+	(SHIFT_RIGHT((insn), (pos) - LOG_REGBYTES) & REG_MASK)
+
+#define REG_PTR(insn, pos, regs)	\
+	((ulong *)((ulong)(regs) + REG_OFFSET(insn, pos)))
+
+#define GET_RS1(insn, regs)	(*REG_PTR(insn, SH_RS1, regs))
+#define GET_RS2(insn, regs)	(*REG_PTR(insn, SH_RS2, regs))
+#define GET_RS1S(insn, regs)	(*REG_PTR(RVC_RS1S(insn), 0, regs))
+#define GET_RS2S(insn, regs)	(*REG_PTR(RVC_RS2S(insn), 0, regs))
+#define GET_RS2C(insn, regs)	(*REG_PTR(insn, SH_RS2C, regs))
+#define GET_SP(regs)		(*REG_PTR(2, 0, regs))
+#define SET_RD(insn, regs, val)	(*REG_PTR(insn, SH_RD, regs) = (val))
+#define IMM_I(insn)		((s32)(insn) >> 20)
+#define IMM_S(insn)		(((s32)(insn) >> 25 << 5) | \
+				 (s32)(((insn) >> 7) & 0x1f))
+
+#define SH_RD			7
+#define SH_RS1			15
+#define SH_RS2			20
+#define SH_RS2C			2
+#define MASK_RX			0x1f
+
+#if defined(CONFIG_64BIT)
+#define LOG_REGBYTES		3
+#else
+#define LOG_REGBYTES		2
+#endif
+
+#define MASK_FUNCT3		0x7000
+
+#define GET_FUNCT3(insn)	(((insn) >> 12) & 7)
+
+#define RV_IMM_SIGN(x)		(-(((x) >> 31) & 1))
+#define RVC_IMM_SIGN(x)		(-(((x) >> 12) & 1))
+#define RV_X_MASK(X, s, mask)	(((X) >> (s)) & (mask))
+#define RV_X(X, s, n)		RV_X_MASK(X, s, ((1 << (n)) - 1))
+#define RVC_LW_IMM(x)		((RV_X(x, 6, 1) << 2) | \
+				 (RV_X(x, 10, 3) << 3) | \
+				 (RV_X(x, 5, 1) << 6))
+#define RVC_LD_IMM(x)		((RV_X(x, 10, 3) << 3) | \
+				 (RV_X(x, 5, 2) << 6))
+#define RVC_LWSP_IMM(x)		((RV_X(x, 4, 3) << 2) | \
+				 (RV_X(x, 12, 1) << 5) | \
+				 (RV_X(x, 2, 2) << 6))
+#define RVC_LDSP_IMM(x)		((RV_X(x, 5, 2) << 3) | \
+				 (RV_X(x, 12, 1) << 5) | \
+				 (RV_X(x, 2, 3) << 6))
+#define RVC_SWSP_IMM(x)		((RV_X(x, 9, 4) << 2) | \
+				 (RV_X(x, 7, 2) << 6))
+#define RVC_SDSP_IMM(x)		((RV_X(x, 10, 3) << 3) | \
+				 (RV_X(x, 7, 3) << 6))
+#define RVC_RS1S(insn)		(8 + RV_X(insn, SH_RD, 3))
+#define RVC_RS2S(insn)		(8 + RV_X(insn, SH_RS2C, 3))
+#define RVC_RS2(insn)		RV_X(insn, SH_RS2C, 5)
+#define RVC_X(X, s, mask)	RV_X_MASK(X, s, mask)
+
+#define RV_EXTRACT_FUNCT3(x) \
+	({typeof(x) x_ = (x); \
+	(RV_X_MASK(x_, RV_INSN_FUNCT3_OPOFF, \
+		   RV_INSN_FUNCT3_MASK >> RV_INSN_FUNCT3_OPOFF)); })
+
+#define RV_EXTRACT_RS1_REG(x) \
+	({typeof(x) x_ = (x); \
+	(RV_X_MASK(x_, RVG_RS1_OPOFF, RVG_RS1_MASK)); })
+
+#define RV_EXTRACT_RS2_REG(x) \
+	({typeof(x) x_ = (x); \
+	(RV_X_MASK(x_, RVG_RS2_OPOFF, RVG_RS2_MASK)); })
+
+#define RV_EXTRACT_RD_REG(x) \
+	({typeof(x) x_ = (x); \
+	(RV_X_MASK(x_, RVG_RD_OPOFF, RVG_RD_MASK)); })
+
+#define RV_EXTRACT_UTYPE_IMM(x) \
+	({typeof(x) x_ = (x); \
+	(RV_X_MASK(x_, RV_U_IMM_31_12_OPOFF, RV_U_IMM_31_12_MASK)); })
+
+#define RV_EXTRACT_JTYPE_IMM(x) \
+	({typeof(x) x_ = (x); \
+	(RV_X_MASK(x_, RV_J_IMM_10_1_OPOFF, RV_J_IMM_10_1_MASK) << RV_J_IMM_10_1_OFF) | \
+	(RV_X_MASK(x_, RV_J_IMM_11_OPOFF, RV_J_IMM_11_MASK) << RV_J_IMM_11_OFF) | \
+	(RV_X_MASK(x_, RV_J_IMM_19_12_OPOFF, RV_J_IMM_19_12_MASK) << RV_J_IMM_19_12_OFF) | \
+	(RV_IMM_SIGN(x_) << RV_J_IMM_SIGN_OFF); })
+
+#define RV_EXTRACT_ITYPE_IMM(x) \
+	({typeof(x) x_ = (x); \
+	(RV_X_MASK(x_, RV_I_IMM_11_0_OPOFF, RV_I_IMM_11_0_MASK)) | \
+	(RV_IMM_SIGN(x_) << RV_I_IMM_SIGN_OFF); })
+
+#define RV_EXTRACT_BTYPE_IMM(x) \
+	({typeof(x) x_ = (x); \
+	(RV_X_MASK(x_, RV_B_IMM_4_1_OPOFF, RV_B_IMM_4_1_MASK) << RV_B_IMM_4_1_OFF) | \
+	(RV_X_MASK(x_, RV_B_IMM_10_5_OPOFF, RV_B_IMM_10_5_MASK) << RV_B_IMM_10_5_OFF) | \
+	(RV_X_MASK(x_, RV_B_IMM_11_OPOFF, RV_B_IMM_11_MASK) << RV_B_IMM_11_OFF) | \
+	(RV_IMM_SIGN(x_) << RV_B_IMM_SIGN_OFF); })
+
+#define RVC_EXTRACT_C2_RS1_REG(x) \
+	({typeof(x) x_ = (x); \
+	(RV_X_MASK(x_, RVC_C2_RS1_OPOFF, RVC_C2_RS1_MASK)); })
+
+#define RVC_EXTRACT_JTYPE_IMM(x) \
+	({typeof(x) x_ = (x); \
+	(RVC_X(x_, RVC_J_IMM_3_1_OPOFF, RVC_J_IMM_3_1_MASK) << RVC_J_IMM_3_1_OFF) | \
+	(RVC_X(x_, RVC_J_IMM_4_OPOFF, RVC_J_IMM_4_MASK) << RVC_J_IMM_4_OFF) | \
+	(RVC_X(x_, RVC_J_IMM_5_OPOFF, RVC_J_IMM_5_MASK) << RVC_J_IMM_5_OFF) | \
+	(RVC_X(x_, RVC_J_IMM_6_OPOFF, RVC_J_IMM_6_MASK) << RVC_J_IMM_6_OFF) | \
+	(RVC_X(x_, RVC_J_IMM_7_OPOFF, RVC_J_IMM_7_MASK) << RVC_J_IMM_7_OFF) | \
+	(RVC_X(x_, RVC_J_IMM_9_8_OPOFF, RVC_J_IMM_9_8_MASK) << RVC_J_IMM_9_8_OFF) | \
+	(RVC_X(x_, RVC_J_IMM_10_OPOFF, RVC_J_IMM_10_MASK) << RVC_J_IMM_10_OFF) | \
+	(RVC_IMM_SIGN(x_) << RVC_J_IMM_SIGN_OFF); })
+
+#define RVC_EXTRACT_BTYPE_IMM(x) \
+	({typeof(x) x_ = (x); \
+	(RVC_X(x_, RVC_B_IMM_2_1_OPOFF, RVC_B_IMM_2_1_MASK) << RVC_B_IMM_2_1_OFF) | \
+	(RVC_X(x_, RVC_B_IMM_4_3_OPOFF, RVC_B_IMM_4_3_MASK) << RVC_B_IMM_4_3_OFF) | \
+	(RVC_X(x_, RVC_B_IMM_5_OPOFF, RVC_B_IMM_5_MASK) << RVC_B_IMM_5_OFF) | \
+	(RVC_X(x_, RVC_B_IMM_7_6_OPOFF, RVC_B_IMM_7_6_MASK) << RVC_B_IMM_7_6_OFF) | \
+	(RVC_IMM_SIGN(x_) << RVC_B_IMM_SIGN_OFF); })
+
+#define RVG_EXTRACT_SYSTEM_CSR(x) \
+	({typeof(x) x_ = (x); RV_X_MASK(x_, RVG_SYSTEM_CSR_OFF, RVG_SYSTEM_CSR_MASK); })
+
+#define RVFDQ_EXTRACT_FL_FS_WIDTH(x) \
+	({typeof(x) x_ = (x); RV_X_MASK(x_, RVFDQ_FL_FS_WIDTH_OFF, \
+				   RVFDQ_FL_FS_WIDTH_MASK); })
+
+#define RVV_EXTRACT_VL_VS_WIDTH(x) RVFDQ_EXTRACT_FL_FS_WIDTH(x)
+
+/*
+ * Get the immediate from a J-type instruction.
+ *
+ * @insn: instruction to process
+ * Return: immediate
+ */
+static inline s32 riscv_insn_extract_jtype_imm(u32 insn)
+{
+	return RV_EXTRACT_JTYPE_IMM(insn);
+}
+
+/*
+ * Update a J-type instruction with an immediate value.
+ *
+ * @insn: pointer to the jtype instruction
+ * @imm: the immediate to insert into the instruction
+ */
+static inline void riscv_insn_insert_jtype_imm(u32 *insn, s32 imm)
+{
+	/* drop the old IMMs, all jal IMM bits sit at 31:12 */
+	*insn &= ~GENMASK(31, 12);
+	*insn |= (RV_X_MASK(imm, RV_J_IMM_10_1_OFF, RV_J_IMM_10_1_MASK) << RV_J_IMM_10_1_OPOFF) |
+		 (RV_X_MASK(imm, RV_J_IMM_11_OFF, RV_J_IMM_11_MASK) << RV_J_IMM_11_OPOFF) |
+		 (RV_X_MASK(imm, RV_J_IMM_19_12_OFF, RV_J_IMM_19_12_MASK) << RV_J_IMM_19_12_OPOFF) |
+		 (RV_X_MASK(imm, RV_J_IMM_SIGN_OFF, 1) << RV_J_IMM_SIGN_OPOFF);
+}
+
+/*
+ * Put together one immediate from a U-type and I-type instruction pair.
+ *
+ * The U-type contains an upper immediate, meaning bits[31:12] with [11:0]
+ * being zero, while the I-type contains a 12bit immediate.
+ * Combined these can encode larger 32bit values and are used for example
+ * in auipc + jalr pairs to allow larger jumps.
+ *
+ * @utype_insn: instruction containing the upper immediate
+ * @itype_insn: instruction
+ * Return: combined immediate
+ */
+static inline s32 riscv_insn_extract_utype_itype_imm(u32 utype_insn, u32 itype_insn)
+{
+	s32 imm;
+
+	imm = RV_EXTRACT_UTYPE_IMM(utype_insn);
+	imm += RV_EXTRACT_ITYPE_IMM(itype_insn);
+
+	return imm;
+}
+
+/*
+ * Update a set of two instructions (U-type + I-type) with an immediate value.
+ *
+ * Used for example in auipc+jalrs pairs the U-type instructions contains
+ * a 20bit upper immediate representing bits[31:12], while the I-type
+ * instruction contains a 12bit immediate representing bits[11:0].
+ *
+ * This also takes into account that both separate immediates are
+ * considered as signed values, so if the I-type immediate becomes
+ * negative (BIT(11) set) the U-type part gets adjusted.
+ *
+ * @utype_insn: pointer to the utype instruction of the pair
+ * @itype_insn: pointer to the itype instruction of the pair
+ * @imm: the immediate to insert into the two instructions
+ */
+static inline void riscv_insn_insert_utype_itype_imm(u32 *utype_insn, u32 *itype_insn, s32 imm)
+{
+	/* drop possible old IMM values */
+	*utype_insn &= ~(RV_U_IMM_31_12_MASK);
+	*itype_insn &= ~(RV_I_IMM_11_0_MASK << RV_I_IMM_11_0_OPOFF);
+
+	/* add the adapted IMMs */
+	*utype_insn |= (imm & RV_U_IMM_31_12_MASK) + ((imm & BIT(11)) << 1);
+	*itype_insn |= ((imm & RV_I_IMM_11_0_MASK) << RV_I_IMM_11_0_OPOFF);
+}
+
+/*
+ * Get the immediate from a B-type instruction.
+ *
+ * @insn: instruction to process
+ * Return: immediate
+ */
+static inline s32 riscv_insn_extract_btype_imm(u32 insn)
+{
+	return RV_EXTRACT_BTYPE_IMM(insn);
+}
+
+/*
+ * Get the immediate from a RVC B-type instruction.
+ *
+ * @insn: instruction to process
+ * Return: immediate
+ */
+static inline s32 riscv_insn_extract_cbtype_imm(u32 insn)
+{
+	return RVC_EXTRACT_BTYPE_IMM(insn);
+}
+
+/*
+ * Get the immediate from a RVC J-type instruction.
+ *
+ * @insn: instruction to process
+ * Return: immediate
+ */
+static inline s32 riscv_insn_extract_cjtype_imm(u16 insn)
+{
+	return RVC_EXTRACT_JTYPE_IMM(insn);
+}
+
+#endif /* _ASM_RISCV_INSN_H */
diff --git a/tools/perf/util/Build b/tools/perf/util/Build
index d25140a0d64f..342b4f75457b 100644
--- a/tools/perf/util/Build
+++ b/tools/perf/util/Build
@@ -150,6 +150,7 @@ perf-util-y += cs-etm-decoder/
 endif
 perf-util-y += cs-etm-base.o
 perf-util-y += gtrace-decoder.o
+perf-util-y += rvtrace-decoder/
 
 perf-util-y += parse-branch-options.o
 perf-util-y += parse-regs-options.o
diff --git a/tools/perf/util/rvtrace-decoder/Build b/tools/perf/util/rvtrace-decoder/Build
new file mode 100644
index 000000000000..dc447eec372a
--- /dev/null
+++ b/tools/perf/util/rvtrace-decoder/Build
@@ -0,0 +1,2 @@
+perf-util-y += rvtrace-decoder.o
+perf-util-y += rvtrace-ntrace-decoder.o
diff --git a/tools/perf/util/rvtrace-decoder/rvtrace-decoder.c b/tools/perf/util/rvtrace-decoder/rvtrace-decoder.c
new file mode 100644
index 000000000000..e4b54fe8f51a
--- /dev/null
+++ b/tools/perf/util/rvtrace-decoder/rvtrace-decoder.c
@@ -0,0 +1,423 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2020 IAR Systems AB.
+ * Copyright (c) 2026 Spacemit Technologies, Inc.
+ */
+
+#include <linux/err.h>
+#include <linux/zalloc.h>
+#include <linux/types.h>
+#include <errno.h>
+#include <api/fs/fs.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "../debug.h"
+#include "../intlist.h"
+#include "rvtrace-decoder.h"
+
+#define BUFFER_SIZE	1024
+
+/*
+ * Shared by the per-source staging buffer used during CoreSight frame
+ * de-formatting and by the contiguous de-formatted output.
+ */
+static int rvtrace_buf_append(struct rvtrace_defmt_buf *b, const void *src, size_t len)
+{
+	if (b->size + len > b->capacity) {
+		size_t new_cap = b->capacity ? b->capacity : BUFFER_SIZE;
+		unsigned char *p;
+
+		while (new_cap < b->size + len)
+			new_cap *= 2;
+
+		p = realloc(b->buf, new_cap);
+		if (!p)
+			return -ENOMEM;
+		b->buf = p;
+		b->capacity = new_cap;
+	}
+	memcpy(b->buf + b->size, src, len);
+	b->size += len;
+	return 0;
+}
+
+static struct rvtrace_defmt_buf *rvtrace_get_buf(struct rvtrace_defmt_buf defmt_bufs[],
+						 unsigned char id)
+{
+	if (defmt_bufs[id].buf != NULL)
+		return &defmt_bufs[id];
+
+	defmt_bufs[id].buf = (unsigned char *)malloc(BUFFER_SIZE);
+	if (!defmt_bufs[id].buf)
+		return NULL;
+
+	defmt_bufs[id].size = 0;
+	defmt_bufs[id].capacity = BUFFER_SIZE;
+
+	return &defmt_bufs[id];
+}
+
+static int rvtrace_defmt_buf_append(struct rvtrace_defmt_buf *defmt_buf,
+				    struct rvtrace_defmt_buf *out, unsigned char data)
+{
+	int err;
+
+	err = rvtrace_buf_append(defmt_buf, &data, 1);
+	if (err)
+		return err;
+
+	/* A Nexus message ends when the MSEO bits of the last byte are '11'. */
+	if ((data & 0x3) == 0x3) {
+		err = rvtrace_buf_append(out, defmt_buf->buf, defmt_buf->size);
+		if (err) {
+			pr_err("Encoder: failed to write nexus data\n");
+			return err;
+		}
+		defmt_buf->size = 0;
+	}
+
+	return 0;
+}
+
+static size_t skip_frame_syncs(const unsigned char *buf, size_t len, size_t *fsync_count)
+{
+	const uint32_t FSYNC_PATTERN = 0x7FFFFFFF;    /* LE host pattern for Frame SYNC */
+	size_t skipped_bytes = 0;
+
+	while (skipped_bytes + 4 <= len) {
+		if (*((uint32_t *)(buf + skipped_bytes)) == FSYNC_PATTERN) {
+			skipped_bytes += 4;
+			(*fsync_count)++;
+		} else {
+			break;
+		}
+	}
+
+	return skipped_bytes;
+}
+
+/* remove coresight formatter frame */
+static int rvtrace_data_defmt(struct rvtrace_defmt_buf defmt_bufs[],
+			      struct rvtrace_defmt_buf *out,
+			      const unsigned char *buf, size_t len)
+{
+	int err;
+	uint8_t flag_bit;
+	unsigned char data = 0;
+	int cur_id = -1, old_id = -1, new_id = -1;
+
+	/* 16 bytes is output by coresight trace formatter */
+	while (len >= 16) {
+		/* some linux drivers (e.g. for perf) will insert FSYNCS to pad or differentiate
+		 * between blocks of aligned data, always in frame aligned complete 16 byte frames.
+		 * we need to skip past these frames, resetting as we go.
+		 */
+		size_t fsync_count = 0;
+		size_t fsync_bytes = skip_frame_syncs(buf, len, &fsync_count);
+
+		if (fsync_bytes > 0) {
+			if (fsync_count % 4 == 0) {
+				cur_id = -1;
+				old_id = -1;
+				new_id = -1;
+			} else {
+				pr_err("Incorrect FSYNC reset pattern\n");
+				return -EINVAL;
+			}
+			buf += fsync_bytes;
+			len -= fsync_bytes;
+			continue;
+		}
+
+		flag_bit = 0x1;
+		for (int i = 0; i < 15; i += 2) {
+			if (buf[i] & 0x1) {
+				/* it's id */
+				old_id = new_id;
+				new_id = buf[i] >> 1; /* get new_id */
+				cur_id = (flag_bit & buf[15]) ? old_id : new_id;
+			} else {
+				/* it's data */
+				data = buf[i] | ((flag_bit & buf[15]) ? 0x1 : 0x0);
+			}
+
+			if (IS_VALID_ID(cur_id)) {
+				struct rvtrace_defmt_buf *defmt_buf =
+					rvtrace_get_buf(defmt_bufs, cur_id);
+
+				if (!defmt_buf)
+					return -ENOMEM;
+
+				/* it's data */
+				if ((buf[i] & 0x1) == 0) {
+					err = rvtrace_defmt_buf_append(defmt_buf, out, data);
+					if (err)
+						return err;
+				}
+
+				/* buf[i+1] is alway data when i != 14 */
+				if (i != 14) {
+					err = rvtrace_defmt_buf_append(defmt_buf, out,
+								       buf[i + 1]);
+					if (err)
+						return err;
+				}
+			}
+
+			if (cur_id != new_id)
+				cur_id = new_id;
+
+			flag_bit <<= 1;
+		}
+		buf += 16;
+		len -= 16;
+	}
+	return 0;
+}
+
+/*
+ * Common processing for both dump and decode: de-format the raw AUX buffer
+ * into a contiguous Nexus byte stream (in memory), then dispatch to the
+ * format specific parser based on 'type'.
+ */
+static int rvtrace_decode(struct rvtrace_decoder *decoder,
+			  enum rvtrace_decode_type type,
+			  const unsigned char *buf, size_t len)
+{
+	int err;
+	const unsigned char *data = buf;
+	size_t size = len;
+	struct rvtrace_defmt_buf out = { 0 };
+
+	if (decoder->coresight_formatted) {
+		err = rvtrace_data_defmt(decoder->defmt_bufs, &out, buf, len);
+		if (err) {
+			pr_err("Encoder: failed to remove coresight formatter frame\n");
+			free(out.buf);
+			return err;
+		}
+		data = out.buf;
+		size = out.size;
+	}
+
+	switch (decoder->format) {
+	case GTRACE_FORMAT_NTRACE:
+		err = rvtrace_ntrace_decode(decoder, type, data, size);
+		break;
+	case GTRACE_FORMAT_ETRACE:
+	default:
+		err = -EINVAL;
+		pr_err("unsupported trace format %d\n", decoder->format);
+		break;
+	}
+
+	free(out.buf);
+	return err;
+}
+
+static int rvtrace_init_packet_buffer(struct rvtrace_packet_buffer *packet_buffer)
+{
+	if (!packet_buffer->packets) {
+		packet_buffer->packets = calloc(BUFFER_SIZE, sizeof(struct rvtrace_packet));
+		if (!packet_buffer->packets)
+			return -ENOMEM;
+	}
+
+	packet_buffer->size = 0;
+	packet_buffer->capacity = BUFFER_SIZE;
+
+	return 0;
+}
+
+static void rvtrace_free_packet_buffer(struct rvtrace_packet_buffer *packet_buffer)
+{
+	free(packet_buffer->packets);
+}
+
+int rvtrace_packet_buffer_append(struct rvtrace_packet_buffer *packet_buffer,
+				 struct rvtrace_packet packet)
+{
+	if (packet_buffer->size == packet_buffer->capacity) {
+		packet_buffer->capacity *= 2;
+		packet_buffer->packets = realloc(packet_buffer->packets,
+			sizeof(struct rvtrace_packet) * packet_buffer->capacity);
+		if (!packet_buffer->packets)
+			return -ENOMEM;
+	}
+	packet_buffer->packets[packet_buffer->size++] = packet;
+
+	return 0;
+}
+
+int rvtrace_init_stack(struct rvtrace_stack *stack)
+{
+	if (!stack->data) {
+		stack->data = (u64 *)malloc(sizeof(u64) * BUFFER_SIZE);
+		if (!stack->data)
+			return -ENOMEM;
+	}
+
+	stack->top = -1;
+	stack->capacity = BUFFER_SIZE;
+
+	return 0;
+}
+
+int rvtrace_stack_push(struct rvtrace_stack *stack, u64 value)
+{
+	if (stack->top == stack->capacity - 1) {
+		stack->capacity *= 2;
+		stack->data = (u64 *)realloc(stack->data, sizeof(u64) * stack->capacity);
+		if (!stack->data)
+			return -ENOMEM;
+	}
+	stack->data[++stack->top] = value;
+
+	return 0;
+}
+
+int rvtrace_stack_pop(struct rvtrace_stack *stack)
+{
+	u64 value;
+
+	if (stack->top == -1)
+		return 1;   /* Empty */
+
+	value = stack->data[stack->top--];
+	return value;
+}
+
+static void rvtrace_free_stack(struct rvtrace_stack *stack)
+{
+	free(stack->data);
+}
+
+struct rvtrace_src_context *rvtrace_get_src_context(struct rvtrace_decoder *decoder, int srcid)
+{
+	struct int_node *node;
+	struct rvtrace_src_context *ctx;
+
+	node = intlist__find(decoder->src_contexts, srcid);
+	if (node)
+		return (struct rvtrace_src_context *)node->priv;
+
+	ctx = zalloc(sizeof(struct rvtrace_src_context));
+	if (!ctx)
+		return NULL;
+
+	ctx->srcid = srcid;
+	ctx->nexdeco_pc = 1;
+	ctx->nexdeco_lastaddr = 1;
+	ctx->prv = 0;
+	ctx->v = 0;
+	ctx->timestamp = 0;
+	ctx->context = -1;
+	ctx->resourcefull_icnt = 0;
+
+	if (rvtrace_init_stack(&ctx->stack)) {
+		free(ctx);
+		return NULL;
+	}
+
+	/* Add to RB Tree */
+	node = intlist__findnew(decoder->src_contexts, srcid);
+	if (!node) {
+		rvtrace_free_stack(&ctx->stack);
+		free(ctx);
+		return NULL;
+	}
+
+	node->priv = ctx;
+
+	return ctx;
+}
+
+static void rvtrace_free_src_contexts(struct intlist *src_contexts)
+{
+	struct int_node *node;
+
+	intlist__for_each_entry(node, src_contexts) {
+		struct rvtrace_src_context *ctx = (struct rvtrace_src_context *)node->priv;
+
+		rvtrace_free_stack(&ctx->stack);
+		free(ctx);
+	}
+	intlist__delete(src_contexts);
+}
+
+struct rvtrace_decoder *rvtrace_decoder_new(struct rvtrace_decoder_params *params)
+{
+	int err;
+	struct rvtrace_decoder *decoder;
+
+	if (!params)
+		return NULL;
+
+	decoder = zalloc(sizeof(struct rvtrace_decoder));
+	if (!decoder)
+		return NULL;
+
+	decoder->data = params->data;
+	decoder->coresight_formatted = params->coresight_formatted;
+	decoder->format = params->format;
+	decoder->srcbits = params->srcbits;
+	decoder->mem_access = params->mem_access;
+	decoder->decode = rvtrace_decode;
+	decoder->current_src = -1;
+	decoder->src_contexts = intlist__new(NULL);
+	if (!decoder->src_contexts)
+		goto err_out_free_decoder;
+
+	err = rvtrace_init_packet_buffer(&decoder->packet_buffer);
+	if (err)
+		goto err_out_free_context;
+
+	return decoder;
+
+err_out_free_context:
+	rvtrace_free_src_contexts(decoder->src_contexts);
+err_out_free_decoder:
+	zfree(&decoder);
+	return NULL;
+}
+
+void rvtrace_decoder_free(struct rvtrace_decoder *decoder)
+{
+	for (int i = 0; i < MAX_ID; ++i)
+		free(decoder->defmt_bufs[i].buf);
+
+	rvtrace_free_packet_buffer(&decoder->packet_buffer);
+	rvtrace_free_src_contexts(decoder->src_contexts);
+	zfree(&decoder);
+}
+
+int rvtrace_decoder_reset(struct rvtrace_decoder *decoder)
+{
+	int err;
+	struct int_node *node;
+	struct rvtrace_src_context *ctx;
+
+	if (decoder->src_contexts) {
+		intlist__for_each_entry(node, decoder->src_contexts) {
+			ctx = (struct rvtrace_src_context *)node->priv;
+			ctx->nexdeco_pc = 1;
+			ctx->nexdeco_lastaddr = 1;
+			ctx->prv = 0;
+			ctx->v = 0;
+			ctx->timestamp = 0;
+			ctx->context = -1;
+			ctx->resourcefull_icnt = 0;
+			rvtrace_init_stack(&ctx->stack);
+		}
+	}
+	decoder->current_src_ctx = NULL;
+	decoder->current_src = -1;
+
+	err = rvtrace_init_packet_buffer(&decoder->packet_buffer);
+	if (err)
+		return err;
+
+	return 0;
+}
diff --git a/tools/perf/util/rvtrace-decoder/rvtrace-decoder.h b/tools/perf/util/rvtrace-decoder/rvtrace-decoder.h
new file mode 100644
index 000000000000..27c27d5fc3b8
--- /dev/null
+++ b/tools/perf/util/rvtrace-decoder/rvtrace-decoder.h
@@ -0,0 +1,122 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2026 Spacemit Technologies, Inc.
+ */
+
+#ifndef INCLUDE__RVTRACE_DECODER_H__
+#define INCLUDE__RVTRACE_DECODER_H__
+
+#include "../gtrace.h"
+#include "rvtrace-ntrace-decoder.h"
+
+#define MAX_ID        112
+
+/* check an ID is in the valid range */
+#define IS_VALID_ID(id)	    \
+	((id > 0) && (id < MAX_ID))
+
+struct rvtrace_defmt_buf {
+	unsigned char *buf;
+	size_t size;
+	size_t capacity;
+};
+
+struct rvtrace_stack {
+	u64 *data;
+	int top;
+	int capacity;
+};
+
+enum rvtrace_sample_type {
+	RVTRACE_EMPTY,
+	RVTRACE_RANGE,
+	RVTRACE_LOSS,
+	RVTRACE_ERROR,
+};
+
+enum riscv_privilege_mode {
+	RISCV_PRIV_USER_MODE,
+	RISCV_PRIV_SUPERVISOR_MODE,
+	RISCV_PRIV_MACHINE_MODE = 3,
+};
+
+typedef u32 (*rvtrace_mem_access_type)(void *, u64, enum riscv_privilege_mode,
+				       int, size_t, u8 *);
+
+struct rvtrace_packet {
+	enum rvtrace_sample_type sample_type;
+	u64 start_addr;
+	u64 end_addr;
+	u32 insn_cnt;
+	int cpu;
+	enum riscv_privilege_mode prv;
+	bool v;
+	int context;
+	u64 timestamp;
+};
+
+struct rvtrace_packet_buffer {
+	struct rvtrace_packet *packets;
+	int size;
+	int capacity;
+};
+
+struct rvtrace_src_context {
+	int srcid;
+	u64 nexdeco_pc;
+	u64 nexdeco_lastaddr;
+	u64 current_pc;
+	u64 timestamp;
+	enum riscv_privilege_mode prv;
+	bool v;
+	int context;
+	int resourcefull_icnt;
+	struct rvtrace_stack stack;
+};
+
+struct rvtrace_decoder_params {
+	void *data;
+	bool coresight_formatted;
+	enum gtrace_format format;
+	u32 srcbits;
+	rvtrace_mem_access_type mem_access;
+};
+
+/* What the decoder should do with the de-formatted trace stream. */
+enum rvtrace_decode_type {
+	RVTRACE_DECODE_MSG,	/* print the Nexus messages */
+	RVTRACE_DECODE_INSN,	/* reconstruct instruction trace packets */
+};
+
+struct rvtrace_decoder {
+	void *data;
+	bool coresight_formatted;
+	enum gtrace_format format;
+	u32 srcbits;
+	rvtrace_mem_access_type mem_access;
+	int (*decode)(struct rvtrace_decoder *decoder, enum rvtrace_decode_type type,
+		      const unsigned char *buf, size_t size);
+	struct rvtrace_defmt_buf defmt_bufs[MAX_ID];
+	int current_src;
+	struct intlist *src_contexts;
+	struct rvtrace_src_context *current_src_ctx;
+	struct rvtrace_packet_buffer packet_buffer;
+	struct rvtrace_ntrace_msg msg;		/* most recently parsed message */
+	struct rvtrace_ntrace_msg saved_msg;	/* previous message, for RepeatBranch replay */
+};
+
+int rvtrace_packet_buffer_append(struct rvtrace_packet_buffer *packet_buffer,
+				 struct rvtrace_packet packet);
+int rvtrace_init_stack(struct rvtrace_stack *stack);
+int rvtrace_stack_push(struct rvtrace_stack *stack, u64 value);
+int rvtrace_stack_pop(struct rvtrace_stack *stack);
+struct rvtrace_src_context *
+rvtrace_get_src_context(struct rvtrace_decoder *decoder, int srcid);
+struct rvtrace_decoder *rvtrace_decoder_new(struct rvtrace_decoder_params *params);
+void rvtrace_decoder_free(struct rvtrace_decoder *decoder);
+int rvtrace_decoder_reset(struct rvtrace_decoder *decoder);
+
+int rvtrace_ntrace_decode(struct rvtrace_decoder *decoder, enum rvtrace_decode_type type,
+			  const unsigned char *buf, size_t size);
+
+#endif
diff --git a/tools/perf/util/rvtrace-decoder/rvtrace-ntrace-decoder.c b/tools/perf/util/rvtrace-decoder/rvtrace-ntrace-decoder.c
new file mode 100644
index 000000000000..f72e5baa323e
--- /dev/null
+++ b/tools/perf/util/rvtrace-decoder/rvtrace-ntrace-decoder.c
@@ -0,0 +1,910 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2020 IAR Systems AB.
+ * Copyright (c) 2026 Spacemit Technologies, Inc.
+ */
+
+#include <linux/err.h>
+#include <linux/zalloc.h>
+#include <linux/types.h>
+#include <errno.h>
+#include <api/fs/fs.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "../debug.h"
+#include "../color.h"
+#include "../intlist.h"
+#include "rvtrace-decoder.h"
+#include "rvtrace-ntrace-decoder.h"
+#include "../../../arch/riscv/include/asm/insn.h"
+
+#define RV_REGNO_ZERO   0
+#define RV_REGNO_RA	1
+
+/* Definition of Nexus Messages (subset applicable to RISC-V PC trace). */
+const struct rvtrace_ntrace_msg_field_def rvtrace_ntrace_msg_defs[] = {
+	NEXM_BEG(Ownership),
+	  NEXM_FLD_PAR(SRC),
+	  NEXM_FLD(FORMAT),
+	  NEXM_FLD(PRV),
+	  NEXM_FLD(V),
+	  NEXM_VAR(CONTEXT),
+	  NEXM_VAR(TSTAMP),
+	NEXM_END(),
+
+	NEXM_BEG(DirectBranch),
+	  NEXM_FLD_PAR(SRC),
+	  NEXM_VAR(ICNT),
+	  NEXM_VAR(TSTAMP),
+	NEXM_END(),
+
+	NEXM_BEG(IndirectBranch),
+	  NEXM_FLD_PAR(SRC),
+	  NEXM_FLD(BTYPE),
+	  NEXM_VAR(ICNT),
+	  NEXM_ADR(UADDR),
+	  NEXM_VAR(TSTAMP),
+	NEXM_END(),
+
+	NEXM_BEG(Error),
+	  NEXM_FLD_PAR(SRC),
+	  NEXM_FLD(ETYPE),
+	  NEXM_VAR(PAD),
+	  NEXM_VAR(TSTAMP),
+	NEXM_END(),
+
+	NEXM_BEG(ProgTraceSync),
+	  NEXM_FLD_PAR(SRC),
+	  NEXM_FLD(SYNC),
+	  NEXM_VAR(ICNT),
+	  NEXM_ADR(FADDR),
+	  NEXM_VAR(TSTAMP),
+	NEXM_END(),
+
+	NEXM_BEG(DirectBranchSync),
+	  NEXM_FLD_PAR(SRC),
+	  NEXM_FLD(SYNC),
+	  NEXM_VAR(ICNT),
+	  NEXM_ADR(FADDR),
+	  NEXM_VAR(TSTAMP),
+	NEXM_END(),
+
+	NEXM_BEG(IndirectBranchSync),
+	  NEXM_FLD_PAR(SRC),
+	  NEXM_FLD(SYNC),
+	  NEXM_FLD(BTYPE),
+	  NEXM_VAR(ICNT),
+	  NEXM_ADR(FADDR),
+	  NEXM_VAR(TSTAMP),
+	NEXM_END(),
+
+	NEXM_BEG(ResourceFull),
+	  NEXM_FLD_PAR(SRC),
+	  NEXM_FLD(RCODE),
+	  NEXM_VAR(RDATA),
+	  NEXM_VAR(HREPEAT),
+	  NEXM_VAR(TSTAMP),
+	NEXM_END(),
+
+	NEXM_BEG(IndirectBranchHist),
+	  NEXM_FLD_PAR(SRC),
+	  NEXM_FLD(BTYPE),
+	  NEXM_VAR(ICNT),
+	  NEXM_ADR(UADDR),
+	  NEXM_VAR(HIST),
+	  NEXM_VAR(TSTAMP),
+	NEXM_END(),
+
+	NEXM_BEG(IndirectBranchHistSync),
+	  NEXM_FLD_PAR(SRC),
+	  NEXM_FLD(SYNC),
+	  NEXM_FLD(BTYPE),
+	  NEXM_VAR(ICNT),
+	  NEXM_ADR(FADDR),
+	  NEXM_VAR(HIST),
+	  NEXM_VAR(TSTAMP),
+	NEXM_END(),
+
+	NEXM_BEG(RepeatBranch),
+	  NEXM_FLD_PAR(SRC),
+	  NEXM_VAR(BCNT),
+	  NEXM_VAR(TSTAMP),
+	NEXM_END(),
+
+	NEXM_BEG(ProgTraceCorrelation),
+	  NEXM_FLD_PAR(SRC),
+	  NEXM_FLD(EVCODE),
+	  NEXM_FLD(CDF),
+	  NEXM_VAR(ICNT),
+	  NEXM_VAR(HIST),
+	  NEXM_VAR(TSTAMP),
+	NEXM_END(),
+
+	{ NULL, 0 } /* end marker */
+};
+
+/*
+ * Look up the rvtrace_ntrace_msg_defs entry index of a message header whose low 8 bits
+ * match 'tcode'.  Returns the index, or -1 if the TCODE is not defined.
+ */
+static int rvtrace_ntrace_find_tcode(unsigned int tcode)
+{
+	for (int d = 0; rvtrace_ntrace_msg_defs[d].def != 0; d++) {
+		if ((rvtrace_ntrace_msg_defs[d].def & NEXM_DEF_MSG) == 0)
+			continue;
+		if ((rvtrace_ntrace_msg_defs[d].def & 0xff) == tcode)
+			return d;
+	}
+	return -1;
+}
+
+/*
+ * Return the bit width of the fixed-size field at rvtrace_ntrace_msg_defs[def]. Fields
+ * marked NEXM_DEF_PARAM take their width from the decoder's srcbits.
+ */
+static int rvtrace_ntrace_field_size(struct rvtrace_decoder *decoder, int def)
+{
+	int size = rvtrace_ntrace_msg_defs[def].def & 0xff;
+
+	if (rvtrace_ntrace_msg_defs[def].def & NEXM_DEF_PARAM)
+		size = decoder->srcbits;
+	return size;
+}
+
+/*
+ * Feed the byte stream one byte at a time. The parser accumulates
+ * MDO/MSEO bits into 'msg' and returns:
+ *
+ *   1   a complete message has been parsed into *msg
+ *   0   more bytes are needed (or an idle/partial byte was consumed)
+ */
+static int rvtrace_ntrace_msg_parse(struct rvtrace_decoder *decoder,
+				    struct rvtrace_ntrace_msg_parser *p,
+				    struct rvtrace_ntrace_msg *msg,
+				    unsigned char byte, unsigned char prev_byte)
+{
+	unsigned int mdo = byte >> 2;
+	unsigned int mseo = byte & 0x3;
+
+	pr_debug3("0x%02x ", byte);
+	for (int b = 0x80; b != 0; b >>= 1) {
+		if (b == 0x2)
+			pr_debug3("_");
+		pr_debug3("%d", !!(byte & b));
+	}
+	pr_debug3("\n");
+
+	if (p->resync) {
+		if ((prev_byte & 0x3) == 0x3) { /* MSEO==3: message end */
+			p->resync = false;
+			p->field_def = -1;
+			p->field_bits = 0;
+			p->field_val = 0;
+		}
+		return 0;
+	}
+
+	/* Skip long runs of idle bytes (0xFF). */
+	if (byte == 0xFF && prev_byte == 0xFF)
+		return 0;
+
+	if (mseo == 0x2) {
+		pr_err("ERROR: MSEO='10' is not allowed\n");
+		p->resync = true;
+		return 0;
+	}
+
+	/* Message header: the first byte's MDO is the TCODE. */
+	if (p->field_def < 0) {
+		if (mseo == 0x3)
+			return 0;   /* skip idle */
+
+		if (mseo != 0x0) {
+			pr_err("ERROR: Message must start from MSEO='00'\n");
+			p->resync = true;
+			return 0;
+		}
+
+		p->field_def = rvtrace_ntrace_find_tcode(mdo);
+		if (p->field_def < 0) {
+			pr_err("ERROR: Message with TCODE=%d is not defined for RISC-V\n", mdo);
+			p->resync = true;
+			return 0;
+		}
+
+		msg->field_pos = p->field_def;
+		msg->tcode = mdo;
+		msg->nfields = 0;
+		msg->fields[msg->nfields++] = mdo;
+
+		p->field_def++;
+		p->field_bits = 0;
+		p->field_val = 0;
+		return 0;
+	}
+
+	/* Accumulate this byte's MDO into the current field value. */
+	p->field_val |= (u64)mdo << p->field_bits;
+	p->field_bits += 6;
+
+	/* Emit as many complete fixed-size fields as we now have bits for. */
+	while ((rvtrace_ntrace_msg_defs[p->field_def].def & NEXM_DEF_FLD) &&
+	       p->field_bits >= rvtrace_ntrace_field_size(decoder, p->field_def)) {
+		int width = rvtrace_ntrace_field_size(decoder, p->field_def);
+
+		msg->fields[msg->nfields++] = p->field_val & ((1ULL << width) - 1);
+		p->field_def++;
+		p->field_val >>= width;
+		p->field_bits -= width;
+	}
+
+	if (mseo == 0x0)
+		return 0;
+
+	if (rvtrace_ntrace_msg_defs[p->field_def].def & NEXM_DEF_VAR) {
+		/* Variable-size field: the whole remaining value. */
+		msg->fields[msg->nfields++] = p->field_val;
+
+		if (rvtrace_ntrace_msg_defs[p->field_def].def & NEXM_DEF_ADR)
+			msg->addr_bits = p->field_bits;
+
+		if (mseo == 3) {
+			p->field_def = -1;
+			p->field_bits = 0;
+			p->field_val = 0;
+			return 1;	/* complete message */
+		}
+		p->field_def++;
+		p->field_bits = 0;
+		p->field_val = 0;
+		return 0;
+	}
+
+	if (p->field_bits > 0) {
+		pr_err("Decode: Not enough bits for non-variable field\n");
+		p->resync = true;
+	}
+	return 0;
+}
+
+static int rvtrace_ntrace_msg_sprint(char *buf, size_t size,
+				     struct rvtrace_ntrace_msg *msg, int msgs, int bytes)
+{
+	int len;
+
+	len = snprintf(buf, size, "MSG #%d +%d - %s TCODE[6]=%d",
+		       msgs, bytes,
+		       rvtrace_ntrace_msg_defs[msg->field_pos].name, msg->tcode);
+
+	for (int i = 1; i < (int)msg->nfields && len < (int)size; i++)
+		len += snprintf(buf + len, size - len, " %s=0x%llx",
+				rvtrace_ntrace_msg_defs[msg->field_pos + i].name,
+				(unsigned long long)msg->fields[i]);
+
+	return len;
+}
+
+static int handle_error_msg(struct rvtrace_src_context *ctx, const char *err)
+{
+	pr_err("\nERROR: %s\n", err);
+	ctx->nexdeco_pc = 1; /* 1 means, that last address is unknown */
+	rvtrace_init_stack(&ctx->stack);
+	return -EINVAL;
+}
+
+static int rvtrace_ntrace_insn_info_get(struct rvtrace_decoder *decoder, u8 *info, u64 *dest)
+{
+	u32 insn;
+	struct rvtrace_src_context *ctx = decoder->current_src_ctx;
+	u64 addr = ctx->nexdeco_pc;
+
+	if (!decoder->mem_access(decoder->data, addr, ctx->prv, ctx->context,
+				 sizeof(insn), (u8 *)&insn))
+		return -EINVAL;
+
+	*info = INFO_LINEAR;
+	if ((insn & (__INSN_LENGTH_MASK)) != (__INSN_LENGTH_GE_32)) {
+		if (riscv_insn_is_c_beqz(insn) || riscv_insn_is_c_bnez(insn)) {
+			*info = INFO_BRANCH;
+			*dest = addr + riscv_insn_extract_cbtype_imm(insn);
+		} else if (riscv_insn_is_c_j(insn)) {
+			/* c.j offset */
+			*info = INFO_JUMP;
+			*dest = addr + riscv_insn_extract_cjtype_imm(insn);
+		} else if (riscv_insn_is_c_jr(insn)) {
+			*info = INFO_JUMP | INFO_INDIRECT;
+			/* ret => c.jr x1 */
+			if (RVC_EXTRACT_C2_RS1_REG(insn) == RV_REGNO_RA)
+				*info |= INFO_RET;
+		} else if (riscv_insn_is_c_jalr(insn)) {
+			*info = INFO_JUMP | INFO_CALL | INFO_INDIRECT;
+		} else {
+			*info = INFO_LINEAR;
+		}
+	} else {
+		if (riscv_insn_is_branch(insn)) {
+			*info = INFO_BRANCH;
+			*dest = addr + riscv_insn_extract_btype_imm(insn);
+		} else if (riscv_insn_is_jalr(insn)) {
+			*info = INFO_JUMP | INFO_INDIRECT;
+			if (RV_EXTRACT_RD_REG(insn) == RV_REGNO_ZERO
+					&& riscv_insn_extract_jtype_imm(insn) == 0) {
+				/* ret => jalr x0,x1,0 */
+				if (RV_EXTRACT_RS1_REG(insn) == RV_REGNO_RA)
+					*info |= INFO_RET;
+				else
+				/* jr rs1 => jalr x0,rs1,0 */
+					*info |= INFO_CALL;
+			}
+		} else if (riscv_insn_is_jal(insn)) {
+			*info = INFO_JUMP;
+			/* j offset => jal x0,offset */
+			if (RV_EXTRACT_RD_REG(insn) != RV_REGNO_ZERO)
+				*info |= INFO_CALL;
+			*dest = addr + riscv_insn_extract_jtype_imm(insn);
+		} else if (riscv_insn_is_sret(insn) || riscv_insn_is_mret(insn)) {
+			*info = INFO_JUMP | INFO_INDIRECT | INFO_RET;
+		} else if (riscv_insn_is_ecall(insn)) {
+			*info = INFO_JUMP | INFO_INDIRECT | INFO_CALL;
+		} else {
+			*info = INFO_LINEAR;
+		}
+		*info |= INFO_4;
+	}
+
+	pr_debug2(".nexdeco_pc=0x%lx .insn=0x%0*x\n", addr,
+		  (*info & INFO_4) ? 8 : 4,
+		  (*info & INFO_4) ? insn : (u16)insn);
+	return 0;
+}
+
+static int rvtrace_ntrace_emit_icnt(struct rvtrace_decoder *decoder, int n, u32 hist)
+{
+	u64 a = 0;
+	u32 hist_mask;
+	u8 info;
+	int done_icnt = 0; /* Number of done instruction count */
+	struct rvtrace_src_context *ctx = decoder->current_src_ctx;
+
+	if (ctx->nexdeco_pc & 1)
+		return 0;  /* Not synchronized ... */
+
+	pr_debug2(".PC=0x%lX, EmitICNT(n=%d,hist=0x%x)\n", ctx->nexdeco_pc, n, hist);
+
+	/*
+	 * Adjust ICNT by what was handled by ResourceFull message[s]
+	 * before this message (message with normal ICNT field).
+	 * NOTE: ctx->resourcefull_icnt maybe positive or negative!
+	 */
+	if (n >= 0 && ctx->resourcefull_icnt != 0) {
+		n += ctx->resourcefull_icnt;
+		if (n < 0)
+			return handle_error_msg(ctx, "ICNT adjustment ERROR");
+
+		ctx->resourcefull_icnt = 0;    /* Make adjustment 'consumed' */
+	}
+
+	hist_mask = 0;  /* MSB is first in history, so we need sliding mask */
+	if (hist != 0) {
+		if (hist & (1 << (sizeof(u32) * 8 - 1))) {
+			hist_mask = (1 << (sizeof(u32) * 8 - 2));
+		} else {
+			hist_mask = 0x1;
+			while (hist_mask <= hist)
+				hist_mask <<= 1;
+			hist_mask >>= 2;
+		}
+	}
+
+	while (n != 0) {
+		ctx->current_pc = ctx->nexdeco_pc;
+		if (rvtrace_ntrace_insn_info_get(decoder, &info, &a))
+			return handle_error_msg(ctx, "failed to get insn info");
+
+		if (n > 0) {
+			n -= (info & INFO_4) ? 2 : 1;
+			if (n < 0)
+				return handle_error_msg(ctx, "ICNT too small");
+			done_icnt += 1;
+		}
+
+		if (info & INFO_CALL) {
+			u64 ret = ctx->nexdeco_pc + ((info & INFO_4) ? 4 : 2);
+
+			if (rvtrace_stack_push(&ctx->stack, ret))
+				return handle_error_msg(ctx, "failed to push value to stack");
+		}
+
+		if (info & INFO_INDIRECT) { /* Cannot continue over indirect... */
+			if (info & INFO_RET) {
+				u64 ret = rvtrace_stack_pop(&ctx->stack);
+
+				ctx->nexdeco_pc = ret;
+				if (n != 0) {
+					if (ret == 1)
+						return handle_error_msg(ctx,
+							"Not enough entries on callstack");
+					continue;
+				}
+			}
+
+			if (n > 0)
+				return handle_error_msg(ctx,
+							"indirect address encountered in ICNT");
+
+			break;
+		}
+
+		if (info & INFO_BRANCH) {
+			if (hist == 0) {
+				/* This is calling as DirectBranch */
+				if (n == 0)
+					info |= INFO_JUMP;  /* Force PC change below */
+			} else {
+				if (hist_mask & hist)
+					info |= INFO_JUMP;  /* Force PC change below */
+				hist_mask >>= 1;
+				if (hist_mask == 0 && n < 0)
+					n = 0;
+			}
+		}
+
+		if (info & INFO_JUMP)
+			ctx->nexdeco_pc = a;   /* Direct jump/call/branch */
+		else if (info & INFO_4)
+			ctx->nexdeco_pc += 4;
+		else
+			ctx->nexdeco_pc += 2;
+
+	}
+
+	return done_icnt;
+}
+
+static u64 rvtrace_ntrace_field_get(struct rvtrace_decoder *decoder, const char *name)
+{
+	struct rvtrace_ntrace_msg *msg = &decoder->msg;
+
+	for (int d = msg->field_pos; rvtrace_ntrace_msg_defs[d].def != 1; d++) {
+		if (strcmp(rvtrace_ntrace_msg_defs[d].name, name) == 0) {
+			int fi = d - msg->field_pos;
+
+			if (fi < (int)msg->nfields)
+				return msg->fields[fi];
+			return 0;
+		}
+	}
+	return 0;
+}
+
+#define NEX_FLDGET(n) rvtrace_ntrace_field_get(decoder, #n)
+
+/*
+ * Switch the active source context to 'new_src', allocating it on first use.
+ * Returns 0 on success, negative on error.
+ */
+static int rvtrace_ntrace_switch_src(struct rvtrace_decoder *decoder, int new_src)
+{
+	if (new_src == decoder->current_src)
+		return 0;
+
+	decoder->current_src_ctx = rvtrace_get_src_context(decoder, new_src);
+	if (!decoder->current_src_ctx)
+		return -ENOMEM;
+
+	decoder->current_src = new_src;
+	return 0;
+}
+
+/*
+ * Fill 'packet' from the active source context and append it to the decoder's
+ * packet buffer.  'ret' is the instruction count produced by emit_icnt.
+ */
+static int rvtrace_ntrace_emit_packet(struct rvtrace_decoder *decoder, int tcode,
+				u64 start_addr, int ret, int src)
+{
+	struct rvtrace_packet packet;
+
+	if (ret >= 0) {
+		packet.sample_type = (tcode == NEXUS_TCODE_Error) ?
+					RVTRACE_LOSS :
+					(ret ? RVTRACE_RANGE : RVTRACE_EMPTY);
+		packet.start_addr = start_addr;
+		packet.end_addr = decoder->current_src_ctx->current_pc;
+		packet.insn_cnt = ret;
+		packet.cpu = src;
+		packet.prv = decoder->current_src_ctx->prv;
+		packet.v = decoder->current_src_ctx->v;
+		packet.context = decoder->current_src_ctx->context;
+		packet.timestamp = decoder->current_src_ctx->timestamp;
+	} else {
+		packet.sample_type = RVTRACE_ERROR;
+	}
+
+	return rvtrace_packet_buffer_append(&decoder->packet_buffer, packet);
+}
+
+/* Reconstruct an address from the transmitted F-ADDR/U-ADDR field */
+static u64 rvtrace_ntrace_calculate_addr(struct rvtrace_decoder *decoder,
+				   u64 fu_addr, int full, u64 prev_addr)
+{
+	u32 bits = decoder->msg.addr_bits;
+
+	fu_addr <<= 1; /* LSB bit is never sent */
+
+	/*
+	 * After shifting the LSB back in, the most significant transmitted bit
+	 * sits at position 'bits'.  Extend it (0 or 1) all the way to bit 63.
+	 * This is sign-extension in effect, but the spec notes it is NOT a sign
+	 * bit -- it is simply the value of the skipped identical bits.
+	 */
+	if (bits && bits < 64 && (fu_addr & (1ULL << bits)))
+		fu_addr |= ~0ULL << bits;
+
+	/* Update (NEW or XOR) */
+	if (!full) {
+		if (prev_addr & 1) /* Not Sync */
+			fu_addr = prev_addr;
+		else
+			fu_addr ^= prev_addr;
+	}
+	return fu_addr;
+}
+
+/*
+ * Per-message handler result.  Each handler fills these and the dispatcher
+ * emits a single packet from them.
+ */
+struct rvtrace_ntrace_result {
+	int icnt;	/* instruction count from emit_icnt() */
+	u64 start_addr;
+	int src;
+};
+
+/* Common prologue: switch to the message's source context. */
+static int rvtrace_ntrace_handler_prologue(struct rvtrace_decoder *decoder,
+				     struct rvtrace_ntrace_result *res)
+{
+	res->src = NEX_FLDGET(SRC);
+	return rvtrace_ntrace_switch_src(decoder, res->src);
+}
+
+/*
+ * Common F-ADDR/U-ADDR handling: reconstruct the address and make it the
+ * current PC.  'full' selects full (sync) vs XOR (compressed) update.
+ */
+static void rvtrace_ntrace_update_addr(struct rvtrace_decoder *decoder, u64 addr, int full)
+{
+	struct rvtrace_src_context *ctx = decoder->current_src_ctx;
+
+	ctx->nexdeco_lastaddr = rvtrace_ntrace_calculate_addr(decoder, addr, full,
+							ctx->nexdeco_lastaddr);
+	pr_debug2(".nexdeco_lastaddr=0x%lx\n", ctx->nexdeco_lastaddr);
+	ctx->nexdeco_pc = ctx->nexdeco_lastaddr;
+}
+
+static int rvtrace_ntrace_handle_ownership(struct rvtrace_decoder *decoder,
+				     struct rvtrace_ntrace_result *res)
+{
+	int ret = rvtrace_ntrace_handler_prologue(decoder, res);
+
+	if (ret)
+		return ret;
+
+	decoder->current_src_ctx->prv = NEX_FLDGET(PRV);
+	decoder->current_src_ctx->v = NEX_FLDGET(V);
+	if (NEX_FLDGET(FORMAT) && NEX_FLDGET(CONTEXT))
+		decoder->current_src_ctx->context = NEX_FLDGET(CONTEXT);
+	decoder->current_src_ctx->timestamp += NEX_FLDGET(TSTAMP);
+	return 0;
+}
+
+static int rvtrace_ntrace_handle_direct_branch(struct rvtrace_decoder *decoder,
+					 struct rvtrace_ntrace_result *res)
+{
+	int ret = rvtrace_ntrace_handler_prologue(decoder, res);
+
+	if (ret)
+		return ret;
+
+	res->start_addr = decoder->current_src_ctx->nexdeco_pc;
+	res->icnt = rvtrace_ntrace_emit_icnt(decoder, NEX_FLDGET(ICNT), 0x0);
+	decoder->current_src_ctx->timestamp += NEX_FLDGET(TSTAMP);
+	return 0;
+}
+
+static int rvtrace_ntrace_handle_indirect_branch(struct rvtrace_decoder *decoder,
+					   struct rvtrace_ntrace_result *res)
+{
+	int ret = rvtrace_ntrace_handler_prologue(decoder, res);
+
+	if (ret)
+		return ret;
+
+	res->start_addr = decoder->current_src_ctx->nexdeco_pc;
+	res->icnt = rvtrace_ntrace_emit_icnt(decoder, NEX_FLDGET(ICNT), 0x0);
+
+	rvtrace_ntrace_update_addr(decoder, NEX_FLDGET(UADDR), 0);
+	decoder->current_src_ctx->timestamp += NEX_FLDGET(TSTAMP);
+	return 0;
+}
+
+static int rvtrace_ntrace_handle_prog_trace_sync(struct rvtrace_decoder *decoder,
+					   struct rvtrace_ntrace_result *res)
+{
+	int ret = rvtrace_ntrace_handler_prologue(decoder, res);
+
+	if (ret)
+		return ret;
+
+	res->start_addr = decoder->current_src_ctx->nexdeco_pc;
+	res->icnt = rvtrace_ntrace_emit_icnt(decoder, NEX_FLDGET(ICNT), 0x0);
+
+	rvtrace_ntrace_update_addr(decoder, NEX_FLDGET(FADDR), 1);
+	decoder->current_src_ctx->timestamp = NEX_FLDGET(TSTAMP);
+	return 0;
+}
+
+static int rvtrace_ntrace_handle_direct_branch_sync(struct rvtrace_decoder *decoder,
+					      struct rvtrace_ntrace_result *res)
+{
+	int ret = rvtrace_ntrace_handler_prologue(decoder, res);
+
+	if (ret)
+		return ret;
+
+	res->start_addr = decoder->current_src_ctx->nexdeco_pc;
+	res->icnt = rvtrace_ntrace_emit_icnt(decoder, NEX_FLDGET(ICNT), 0x0);
+
+	rvtrace_ntrace_update_addr(decoder, NEX_FLDGET(FADDR), 1);
+	decoder->current_src_ctx->timestamp = NEX_FLDGET(TSTAMP);
+	return 0;
+}
+
+static int rvtrace_ntrace_handle_indirect_branch_sync(struct rvtrace_decoder *decoder,
+						struct rvtrace_ntrace_result *res)
+{
+	int ret = rvtrace_ntrace_handler_prologue(decoder, res);
+
+	if (ret)
+		return ret;
+
+	res->start_addr = decoder->current_src_ctx->nexdeco_pc;
+	res->icnt = rvtrace_ntrace_emit_icnt(decoder, NEX_FLDGET(ICNT), 0x0);
+
+	rvtrace_ntrace_update_addr(decoder, NEX_FLDGET(FADDR), 1);
+	decoder->current_src_ctx->timestamp = NEX_FLDGET(TSTAMP);
+	return 0;
+}
+
+static int rvtrace_ntrace_handle_indirect_branch_hist(struct rvtrace_decoder *decoder,
+						struct rvtrace_ntrace_result *res)
+{
+	int ret = rvtrace_ntrace_handler_prologue(decoder, res);
+
+	if (ret)
+		return ret;
+
+	res->start_addr = decoder->current_src_ctx->nexdeco_pc;
+	res->icnt = rvtrace_ntrace_emit_icnt(decoder, NEX_FLDGET(ICNT), NEX_FLDGET(HIST));
+
+	rvtrace_ntrace_update_addr(decoder, NEX_FLDGET(UADDR), 0);
+	decoder->current_src_ctx->timestamp += NEX_FLDGET(TSTAMP);
+	return 0;
+}
+
+static int rvtrace_ntrace_handle_indirect_branch_hist_sync(struct rvtrace_decoder *decoder,
+						     struct rvtrace_ntrace_result *res)
+{
+	int ret = rvtrace_ntrace_handler_prologue(decoder, res);
+
+	if (ret)
+		return ret;
+
+	res->start_addr = decoder->current_src_ctx->nexdeco_pc;
+	res->icnt = rvtrace_ntrace_emit_icnt(decoder, NEX_FLDGET(ICNT), NEX_FLDGET(HIST));
+
+	rvtrace_ntrace_update_addr(decoder, NEX_FLDGET(FADDR), 1);
+	decoder->current_src_ctx->timestamp = NEX_FLDGET(TSTAMP);
+	return 0;
+}
+
+static int rvtrace_ntrace_handle_resource_full(struct rvtrace_decoder *decoder,
+					 struct rvtrace_ntrace_result *res)
+{
+	int ret = rvtrace_ntrace_handler_prologue(decoder, res);
+	int rcode = NEX_FLDGET(RCODE);
+	int hrepeat = 0;
+
+	if (ret)
+		return ret;
+
+	if (rcode == 2)
+		hrepeat = NEX_FLDGET(HREPEAT);
+
+	res->start_addr = decoder->current_src_ctx->nexdeco_pc;
+	decoder->current_src_ctx->timestamp += NEX_FLDGET(TSTAMP);
+	if (rcode == 1 || rcode == 2) {
+		int rdata = NEX_FLDGET(RDATA);
+
+		if (rdata > 1) {
+			do {
+				/* ICNT is unknown (-1), what will process only HIST bits */
+				res->icnt = rvtrace_ntrace_emit_icnt(decoder, -1, rdata);
+				if (res->icnt < 0)
+					break;
+
+				/* Consume, so next time ICNT will be adjusted */
+				decoder->current_src_ctx->resourcefull_icnt -= res->icnt;
+				hrepeat--;
+			} while (hrepeat > 0);
+		}
+	} else if (rcode == 0) {
+		decoder->current_src_ctx->resourcefull_icnt += NEX_FLDGET(RDATA);
+	}
+	return 0;
+}
+
+static int rvtrace_ntrace_handle_prog_trace_correlation(struct rvtrace_decoder *decoder,
+						  struct rvtrace_ntrace_result *res)
+{
+	int ret = rvtrace_ntrace_handler_prologue(decoder, res);
+	int hist = 0;
+
+	if (ret)
+		return ret;
+
+	if (NEX_FLDGET(CDF) == 1)
+		hist = NEX_FLDGET(HIST);
+
+	res->start_addr = decoder->current_src_ctx->nexdeco_pc;
+	res->icnt = rvtrace_ntrace_emit_icnt(decoder, NEX_FLDGET(ICNT), hist);
+	decoder->current_src_ctx->timestamp += NEX_FLDGET(TSTAMP);
+	return 0;
+}
+
+static int rvtrace_ntrace_handle_error(struct rvtrace_decoder *decoder,
+				 struct rvtrace_ntrace_result *res)
+{
+	int ret = rvtrace_ntrace_handler_prologue(decoder, res);
+
+	if (ret)
+		return ret;
+
+	decoder->current_src_ctx->timestamp += NEX_FLDGET(TSTAMP);
+	return 0;
+}
+
+static int rvtrace_ntrace_msg_handle(struct rvtrace_decoder *decoder)
+{
+	struct rvtrace_ntrace_result res = { 0 };
+	int tcode = decoder->msg.tcode;
+	int err;
+
+	switch (tcode) {
+	case NEXUS_TCODE_Ownership:
+		err = rvtrace_ntrace_handle_ownership(decoder, &res);
+		break;
+	case NEXUS_TCODE_DirectBranch:
+		err = rvtrace_ntrace_handle_direct_branch(decoder, &res);
+		break;
+	case NEXUS_TCODE_IndirectBranch:
+		err = rvtrace_ntrace_handle_indirect_branch(decoder, &res);
+		break;
+	case NEXUS_TCODE_ProgTraceSync:
+		err = rvtrace_ntrace_handle_prog_trace_sync(decoder, &res);
+		break;
+	case NEXUS_TCODE_DirectBranchSync:
+		err = rvtrace_ntrace_handle_direct_branch_sync(decoder, &res);
+		break;
+	case NEXUS_TCODE_IndirectBranchSync:
+		err = rvtrace_ntrace_handle_indirect_branch_sync(decoder, &res);
+		break;
+	case NEXUS_TCODE_IndirectBranchHist:
+		err = rvtrace_ntrace_handle_indirect_branch_hist(decoder, &res);
+		break;
+	case NEXUS_TCODE_IndirectBranchHistSync:
+		err = rvtrace_ntrace_handle_indirect_branch_hist_sync(decoder, &res);
+		break;
+	case NEXUS_TCODE_ResourceFull:
+		err = rvtrace_ntrace_handle_resource_full(decoder, &res);
+		break;
+	case NEXUS_TCODE_ProgTraceCorrelation:
+		err = rvtrace_ntrace_handle_prog_trace_correlation(decoder, &res);
+		break;
+	case NEXUS_TCODE_Error:
+		err = rvtrace_ntrace_handle_error(decoder, &res);
+		break;
+	case NEXUS_TCODE_RepeatBranch:  /* Handled differently! */
+	default:
+		return -EINVAL;
+	}
+
+	if (err)
+		return err;
+
+	return rvtrace_ntrace_emit_packet(decoder, tcode, res.start_addr, res.icnt, res.src);
+}
+
+/*
+ * Decode the N-Trace byte stream.
+ *
+ *   RVTRACE_DECODE_MSG  - print the messages
+ *   RVTRACE_DECODE_INSN - reconstruct instruction flow
+ */
+int rvtrace_ntrace_decode(struct rvtrace_decoder *decoder, enum rvtrace_decode_type type,
+			  const unsigned char *buf, size_t size)
+{
+	const char *color = PERF_COLOR_BLUE;
+	struct rvtrace_ntrace_msg_parser p = { .field_def = -1 };
+	struct rvtrace_ntrace_msg *msg = &decoder->msg;
+	struct {
+		int msgs;
+		int bytes;
+		int idles;
+		int errors;
+		int invalid;
+	} stat = { 0 };
+	unsigned char prev_byte = 0;
+	bool was_resync = false;
+	size_t pos;
+	int err;
+
+	msg->nfields = 0;
+
+	for (pos = 0; pos < size; pos++) {
+		unsigned char byte = buf[pos];
+		char line[256];
+		int done = rvtrace_ntrace_msg_parse(decoder, &p, msg, byte, prev_byte);
+		int cnt = 1;
+
+		prev_byte = byte;
+		stat.bytes++;
+
+		if (p.resync && !was_resync)
+			stat.invalid++;
+		was_resync = p.resync;
+
+		if (done <= 0)
+			continue;
+
+		stat.msgs++;
+		if (msg->tcode == NEXUS_TCODE_Error)
+			stat.errors++;
+
+		rvtrace_ntrace_msg_sprint(line, sizeof(line), msg, stat.msgs, stat.bytes);
+		if (type == RVTRACE_DECODE_MSG) {
+			color_fprintf(stdout, color, "%s\n", line);
+			continue;
+		}
+		pr_debug2("%s\n", line);
+
+		if (msg->tcode == NEXUS_TCODE_RepeatBranch) {
+			cnt = msg->fields[1];
+			*msg = decoder->saved_msg;
+		} else {
+			/* Save previous message fields */
+			decoder->saved_msg = *msg;
+		}
+
+		while (cnt-- > 0) {
+			err = rvtrace_ntrace_msg_handle(decoder);
+			if (err)
+				return err;
+		}
+	}
+
+	if (type == RVTRACE_DECODE_MSG) {
+		color_fprintf(stdout, color,
+			      "\nN-Trace: %d bytes, %d messages, %d error messages, %d invalid messages",
+			      stat.bytes, stat.msgs, stat.errors, stat.invalid);
+	} else {
+		pr_debug2("\nN-Trace: %d bytes, %d messages, %d errors, %d invalid\n",
+			  stat.bytes, stat.msgs, stat.errors, stat.invalid);
+	}
+
+	return 0;
+}
+
diff --git a/tools/perf/util/rvtrace-decoder/rvtrace-ntrace-decoder.h b/tools/perf/util/rvtrace-decoder/rvtrace-ntrace-decoder.h
new file mode 100644
index 000000000000..4575e631ba5e
--- /dev/null
+++ b/tools/perf/util/rvtrace-decoder/rvtrace-ntrace-decoder.h
@@ -0,0 +1,124 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2020 IAR Systems AB.
+ * Copyright (c) 2026 Spacemit Technologies, Inc.
+ */
+
+#ifndef INCLUDE__RVTRACE_NTRACE_DECODER_H__
+#define INCLUDE__RVTRACE_NTRACE_DECODER_H__
+
+#include <stdint.h> // For uint32_t and uint64_t
+#include <stdbool.h>
+#include <linux/types.h>
+
+/****************************************************************************/
+/* Instruction information */
+#define INFO_LINEAR   0x1   // Linear (plain instruction or not taken BRANCH)
+#define INFO_4        0x2   // If not 4, it must be 2 on RISC-V
+//                    0x4   // Reserved (for exception or so ...)
+#define INFO_INDIRECT 0x8   // Possible for most types above
+#define INFO_BRANCH   0x10  // Always direct on RISC-V (may have LINEAR too)
+#define INFO_JUMP     0x20  // Direct or indirect
+#define INFO_CALL     0x40  // Direct or indirect (and always a jump)
+#define INFO_RET      0x80  // Return (always indirect and always a jump)
+
+/****************************************************************************/
+
+/****************************************************************************/
+/* Nexus specific values (based on Nexus Standard PDF) */
+
+#define NEXUS_FLDSIZE_TCODE                       6 // This is standard
+
+/* Nexus TCODE values applicable to RISC-V */
+#define NEXUS_TCODE_Ownership                     2
+#define NEXUS_TCODE_DirectBranch                  3
+#define NEXUS_TCODE_IndirectBranch                4
+#define NEXUS_TCODE_Error                         8
+#define NEXUS_TCODE_ProgTraceSync                 9
+#define NEXUS_TCODE_DirectBranchSync              11
+#define NEXUS_TCODE_IndirectBranchSync            12
+#define NEXUS_TCODE_ResourceFull                  27
+#define NEXUS_TCODE_IndirectBranchHist            28
+#define NEXUS_TCODE_IndirectBranchHistSync        29
+#define NEXUS_TCODE_RepeatBranch                  30
+#define NEXUS_TCODE_ProgTraceCorrelation          33
+
+/* End of standard values */
+/****************************************************************************/
+
+/****************************************************************************/
+/* RISC-V Nexus Trace related values (most 'recommended' by Nexus) */
+
+//  Sizes of fields:
+#define NEXUS_FLDSIZE_BTYPE     2 // Branch type
+#define NEXUS_FLDSIZE_SYNC      4 // Synchronization code
+#define NEXUS_FLDSIZE_ETYPE     4 // Error type
+#define NEXUS_FLDSIZE_EVCODE    4 // Event code (correlation)
+#define NEXUS_FLDSIZE_CDF       2
+#define NEXUS_FLDSIZE_RCODE     4 // Resource full code size
+//  from PROCESS fields:
+#define NEXUS_FLDSIZE_FORMAT    2
+#define NEXUS_FLDSIZE_PRV       2
+#define NEXUS_FLDSIZE_V         1
+
+#define NEXUS_PAR_SIZE_SRC      0 // SRC field size is defined by parameter #0
+
+#define NEXUS_HIST_BITS         31 // Number of valid HIST bits
+
+// Address skipping (on RISC-V LSB of PC is always 0, so it is not encoded)
+#define NEXUS_PARAM_AddrSkip    1
+#define NEXUS_PARAM_AddrUnit    1
+
+// End of RISC-V related values
+/****************************************************************************/
+
+/*
+ * Field definition flags packed into rvtrace_ntrace_msg_field_def.def.  The
+ * low 8 bits of a fixed field carry its bit width; bit 7 marks a
+ * runtime-sized (parameter) field such as SRC.
+ */
+#define NEXM_DEF_MSG	0x100	/* message header: low bits are the TCODE */
+#define NEXM_DEF_FLD	0x200	/* fixed-size field */
+#define NEXM_DEF_VAR	0x400	/* variable-size field */
+#define NEXM_DEF_ADR	0x800	/* address field (variable + addr semantics) */
+#define NEXM_DEF_PARAM	0x80	/* field size is a runtime parameter */
+
+// Macros to define Nexus Messages (NEXM_...)
+//  NOTE: These macros refer to 'NEXUS_TCODE_...' and 'NEXUS_FLDSIZE_...'
+//  It is also possible to NOT do it, but it provides less flexibility.
+//
+//                          name   def (marker | value)
+#define NEXM_BEG(n)         {#n,    NEXM_DEF_MSG | (NEXUS_TCODE_##n)   }
+#define   NEXM_FLD(n)       {#n,    NEXM_DEF_FLD | (NEXUS_FLDSIZE_##n) }
+#define   NEXM_FLD_PAR(n)   {#n,    NEXM_DEF_FLD | NEXM_DEF_PARAM      }
+#define   NEXM_VAR(n)       {#n,    NEXM_DEF_VAR                       }
+#define   NEXM_ADR(n)       {#n,    NEXM_DEF_VAR | NEXM_DEF_ADR        }
+#define NEXM_END()          {NULL,  1                                  }
+
+#define MSGFIELDS_MAX 10
+
+struct rvtrace_ntrace_msg_field_def {
+	const char *name;   /* Name of message/field */
+	int def;	    /* Definition of field (see NEXM_... above) */
+};
+
+extern const struct rvtrace_ntrace_msg_field_def rvtrace_ntrace_msg_defs[];
+
+/* A single decoded N-Trace message */
+struct rvtrace_ntrace_msg {
+	u32 tcode;
+	u32 field_pos;
+	u32 nfields;
+	u32 addr_bits;	/* transmitted bit width of the last F-ADDR/U-ADDR field */
+	u64 fields[MSGFIELDS_MAX];
+};
+
+/* Bit-level N-Trace message parser state */
+struct rvtrace_ntrace_msg_parser {
+	int field_def;		/* index into rvtrace_ntrace_msg_defs of the current field */
+	int field_bits;		/* bits accumulated for the current field */
+	u64 field_val;		/* accumulated value of the current field */
+	bool resync;		/* skip bytes until the next MSEO==3 terminator */
+};
+
+#endif
-- 
2.34.1


^ permalink raw reply	[flat|nested] 25+ messages in thread

* [PATCH 23/24] perf tools: gtrace: Record and parse trace encoder metadata
  2026-10-23  3:14 [RFC PATCH 00/24] Add Linux RISC-V trace support via the gtrace framework Zane Leung
                   ` (21 preceding siblings ...)
  2026-10-23  3:15 ` [PATCH 22/24] perf tools: Add Nexus RISC-V Trace decoder Zane Leung
@ 2026-10-23  3:15 ` Zane Leung
  2026-10-23  3:15 ` [PATCH 24/24] perf tools: gtrace: Add AUX trace decoder and branch sample synthesis Zane Leung
  23 siblings, 0 replies; 25+ messages in thread
From: Zane Leung @ 2026-10-23  3:15 UTC (permalink / raw)
  To: gregkh, palmer, pjw, alexander.shishkin, suzuki.poulose,
	mchitale, anup, irogers
  Cc: mike.leach, james.clark, leo.yan, peterz, mingo, namhyung,
	mark.rutland, jolsa, adrian.hunter, alex, atish.patra,
	andrew.jones, sunilvl, liangzhen, zhuangqiubin, coresight,
	linux-riscv, linux-kernel

Collect the read-only trace encoder control, features and timestamp
registers per CPU into the AUXTRACE_INFO metadata and validate the
requested context, syncmode, syncmax and timestamp options against the
hardware before recording. Parse the metadata back in the decoder.

Signed-off-by: Zane Leung <liangzhen@linux.spacemit.com>
---
 tools/perf/arch/riscv/util/auxtrace.c | 203 +++++++++++++++++++++++++-
 tools/perf/util/gtrace-decoder.c      |  89 ++++++++++-
 tools/perf/util/gtrace.h              |  30 +++-
 3 files changed, 311 insertions(+), 11 deletions(-)

diff --git a/tools/perf/arch/riscv/util/auxtrace.c b/tools/perf/arch/riscv/util/auxtrace.c
index 882632511d09..c11c5c34cbc8 100644
--- a/tools/perf/arch/riscv/util/auxtrace.c
+++ b/tools/perf/arch/riscv/util/auxtrace.c
@@ -9,6 +9,7 @@
 #include <linux/bitops.h>
 #include <linux/log2.h>
 #include <linux/zalloc.h>
+#include <linux/bitfield.h>
 #include <errno.h>
 #include <limits.h>
 #include <stdlib.h>
@@ -39,28 +40,217 @@ struct rvtrace_recording {
 	struct evlist *evlist;
 };
 
+static const char * const metadata_encoder_ro[] = {
+	[RVTRACE_ENCODER_FORMAT]    = "control/format",
+	[RVTRACE_ENCODER_CONTEXT]   = "control/has_context",
+	[RVTRACE_ENCODER_SRCBITS]   = "features/srcbits",
+	[RVTRACE_ENCODER_TIMESTAMP] = "timestamp/source",
+};
+
 static size_t rvtrace_info_priv_size(struct auxtrace_record *itr __maybe_unused,
 				     struct evlist *evlist __maybe_unused)
 {
-	return GTRACE_AUXTRACE_PRIV_SIZE;
+	int encoder;
+	struct perf_cpu_map *event_cpus = evlist->core.user_requested_cpus;
+	struct perf_cpu_map *intersect_cpus;
+
+	if (!perf_cpu_map__has_any_cpu(event_cpus)) {
+		/* cpu map is not "any" CPU , we have specific CPUs to work with */
+		struct perf_cpu_map *online_cpus = perf_cpu_map__new_online_cpus();
+
+		intersect_cpus = perf_cpu_map__intersect(event_cpus, online_cpus);
+		perf_cpu_map__put(online_cpus);
+	} else {
+		/* Event can be "any" CPU so count all online CPUs. */
+		intersect_cpus = perf_cpu_map__new_online_cpus();
+	}
+
+	encoder = perf_cpu_map__nr(intersect_cpus);
+	perf_cpu_map__put(intersect_cpus);
+
+	return (GTRACE_HEADER_SIZE + encoder * RVTRACE_ENCODER_PRIV_SIZE);
+
+}
+
+static bool gtrace_pmu_path_exists(struct perf_pmu *pmu, struct perf_cpu cpu, const char *path)
+{
+	char pmu_path[PATH_MAX];
+
+	/* Get RO metadata from sysfs */
+	snprintf(pmu_path, PATH_MAX, "cpu%d/%s", cpu.cpu, path);
+
+	return perf_pmu__file_exists(pmu, pmu_path);
+}
+
+static int rvtrace_get_ro(struct perf_pmu *pmu, struct perf_cpu cpu, const char *path, __u64 *val)
+{
+	char pmu_path[PATH_MAX];
+	int scan;
+
+	/* Get RO metadata from sysfs */
+	snprintf(pmu_path, PATH_MAX, "cpu%d/%s", cpu.cpu, path);
+
+	scan = perf_pmu__scan_file(pmu, pmu_path, "%llx", val);
+	if (scan != 1) {
+		pr_err("%s: error reading: %s\n", __func__, pmu_path);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static void rvtrace_get_metadata(struct perf_cpu cpu, u32 *offset,
+				 struct auxtrace_record *itr,
+				 struct perf_record_auxtrace_info *info)
+{
+	struct rvtrace_recording *ptr = container_of(itr, struct rvtrace_recording, itr);
+	struct perf_pmu *rvtrace_pmu = ptr->rvtrace_pmu;
+	bool timestamp_exists;
+
+	info->priv[*offset + RVTRACE_ENCODER_CPU] = cpu.cpu;
+	info->priv[*offset + RVTRACE_ENCODER_NR_TRC_PARAMS] = RVTRACE_ENCODER_NR_TRC_PARAMS_LENGTH;
+
+	/* Get read-only information from sysFS */
+	rvtrace_get_ro(rvtrace_pmu, cpu, metadata_encoder_ro[RVTRACE_ENCODER_FORMAT],
+		       &info->priv[*offset + RVTRACE_ENCODER_FORMAT]);
+	rvtrace_get_ro(rvtrace_pmu, cpu, metadata_encoder_ro[RVTRACE_ENCODER_CONTEXT],
+		       &info->priv[*offset + RVTRACE_ENCODER_CONTEXT]);
+	rvtrace_get_ro(rvtrace_pmu, cpu, metadata_encoder_ro[RVTRACE_ENCODER_SRCBITS],
+		       &info->priv[*offset + RVTRACE_ENCODER_SRCBITS]);
+
+	timestamp_exists = gtrace_pmu_path_exists(rvtrace_pmu, cpu,
+			metadata_encoder_ro[RVTRACE_ENCODER_TIMESTAMP]);
+	info->priv[*offset + RVTRACE_ENCODER_TIMESTAMP] = timestamp_exists ? 1 : 0;
+
+	/* Where the next CPU entry should start from */
+	*offset += RVTRACE_ENCODER_PRIV_MAX;
 }
 
 static int rvtrace_info_fill(struct auxtrace_record *itr,
 			     struct perf_session *session __maybe_unused,
 			     struct perf_record_auxtrace_info *auxtrace_info, size_t priv_size)
 {
+	unsigned int i;
+	u32 offset;
+	u64 nr_cpu, type;
+	struct perf_cpu_map *cpu_map;
+	struct perf_cpu_map *event_cpus = evlist__core(session->evlist)->user_requested_cpus;
+	struct perf_cpu_map *online_cpus = perf_cpu_map__new_online_cpus();
 	struct rvtrace_recording *ptr = container_of(itr, struct rvtrace_recording, itr);
 	struct perf_pmu *rvtrace_pmu = ptr->rvtrace_pmu;
+	struct perf_cpu cpu;
+
+	if (priv_size != rvtrace_info_priv_size(itr, session->evlist))
+		return -EINVAL;
 
-	if (priv_size != GTRACE_AUXTRACE_PRIV_SIZE)
+	if (!evlist__core(session->evlist)->nr_mmaps)
 		return -EINVAL;
 
+	if (perf_cpu_map__has_any_cpu(event_cpus)) {
+		cpu_map = online_cpus;
+	} else {
+		perf_cpu_map__for_each_cpu(cpu, i, event_cpus) {
+			if (!perf_cpu_map__has(online_cpus, cpu))
+				return -EINVAL;
+		}
+
+		cpu_map = event_cpus;
+	}
+
+	nr_cpu = perf_cpu_map__nr(cpu_map);
+	type = rvtrace_pmu->type;
+
 	auxtrace_info->type = PERF_AUXTRACE_GTRACE;
-	auxtrace_info->priv[0] = rvtrace_pmu->type;
+	auxtrace_info->priv[GTRACE_PMU_TYPE_CPUS] = type << 32;
+	auxtrace_info->priv[GTRACE_PMU_TYPE_CPUS] |= nr_cpu;
+
+	offset = GTRACE_HEADER_MAX;
+
+	perf_cpu_map__for_each_cpu(cpu, i, cpu_map) {
+		assert(offset < priv_size);
+		rvtrace_get_metadata(cpu, &offset, itr, auxtrace_info);
+	}
+
+	perf_cpu_map__put(online_cpus);
+
+	return 0;
+}
+
+static int rvtrace_encoder_validate_context(struct perf_pmu *rvtrace_pmu, struct evsel *evsel,
+					    struct perf_cpu cpu)
+{
+	int err;
+	u64 ctxt;
+	__u64 context;
+
+	evsel__get_config_val(evsel, "context", &ctxt);
+
+	if (!ctxt)
+		return 0;
+
+	err = rvtrace_get_ro(rvtrace_pmu, cpu,
+			     metadata_encoder_ro[RVTRACE_ENCODER_CONTEXT], &context);
+	if (err)
+		return err;
+
+	if (!context) {
+		pr_err("context not supported");
+		return -EINVAL;
+	}
 
 	return 0;
 }
 
+static int rvtrace_encoder_validate_timestamp(struct perf_pmu *rvtrace_pmu, struct evsel *evsel,
+					      struct perf_cpu cpu)
+{
+	u64 ts;
+
+	evsel__get_config_val(evsel, "timestamp", &ts);
+
+	if (!ts)
+		return 0;
+
+	if (!gtrace_pmu_path_exists(rvtrace_pmu, cpu,
+		    metadata_encoder_ro[RVTRACE_ENCODER_TIMESTAMP])) {
+		pr_err("timestamp not supported");
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int rvtrace_validate_config(struct perf_pmu *rvtrace_pmu, struct evsel *evsel)
+{
+	unsigned int idx;
+	int err = 0;
+	struct perf_cpu_map *event_cpus = evlist__core(evsel->evlist)->user_requested_cpus;
+	struct perf_cpu_map *intersect_cpus;
+	struct perf_cpu cpu;
+
+	if (!perf_cpu_map__has_any_cpu(event_cpus)) {
+		struct perf_cpu_map *online_cpus = perf_cpu_map__new_online_cpus();
+
+		intersect_cpus = perf_cpu_map__intersect(event_cpus, online_cpus);
+		perf_cpu_map__put(online_cpus);
+	} else {
+		intersect_cpus = perf_cpu_map__new_online_cpus();
+	}
+
+	perf_cpu_map__for_each_cpu_skip_any(cpu, idx, intersect_cpus) {
+		err = rvtrace_encoder_validate_context(rvtrace_pmu, evsel, cpu);
+		if (err)
+			break;
+
+		err = rvtrace_encoder_validate_timestamp(rvtrace_pmu, evsel, cpu);
+		if (err)
+			break;
+	}
+
+	perf_cpu_map__put(intersect_cpus);
+	return err;
+}
+
 static int rvtrace_set_auxtrace_mmap_page(struct record_opts *opts)
 {
 	bool privileged = perf_event_paranoid_check(-1);
@@ -127,6 +317,9 @@ static int rvtrace_recording_options(struct auxtrace_record *itr, struct evlist
 	evlist__to_front(evlist, rvtrace_evsel);
 	evsel__set_sample_bit(rvtrace_evsel, TIME);
 
+	if (opts->sample_time_set)
+		evsel__set_config_if_unset(rvtrace_evsel, "timestamp", 1);
+
 	/* Add dummy event to keep tracking */
 	err = parse_event(evlist, "dummy:u");
 	if (err)
@@ -139,6 +332,10 @@ static int rvtrace_recording_options(struct auxtrace_record *itr, struct evlist
 	tracking_evsel->core.attr.sample_period = 1;
 	evsel__set_sample_bit(tracking_evsel, TIME);
 
+	err = rvtrace_validate_config(rvtrace_pmu, rvtrace_evsel);
+	if (err)
+		return err;
+
 	return 0;
 }
 
diff --git a/tools/perf/util/gtrace-decoder.c b/tools/perf/util/gtrace-decoder.c
index c25fbfc61928..d70cb7cd399f 100644
--- a/tools/perf/util/gtrace-decoder.c
+++ b/tools/perf/util/gtrace-decoder.c
@@ -12,6 +12,7 @@
 #include "evlist.h"
 #include "session.h"
 #include "gtrace.h"
+#include "debug.h"
 
 struct gtrace_decoder {
 	struct auxtrace auxtrace;
@@ -19,6 +20,8 @@ struct gtrace_decoder {
 	struct perf_session *session;
 	struct machine *machine;
 	u32 pmu_type;
+	int num_cpu;
+	u64 **metadata;
 };
 
 static int gtrace_process_event(struct perf_session *session __maybe_unused,
@@ -52,7 +55,10 @@ static void gtrace_free(struct perf_session *session)
 					    auxtrace);
 
 	session->auxtrace = NULL;
-	free(ptr);
+	for (int i = 0; i < ptr->num_cpu; i++)
+		zfree(&ptr->metadata[i]);
+	zfree(&ptr->metadata);
+	zfree(&ptr);
 }
 
 static bool gtrace_evsel_is_auxtrace(struct perf_session *session,
@@ -64,24 +70,88 @@ static bool gtrace_evsel_is_auxtrace(struct perf_session *session,
 	return evsel->core.attr.type == ptr->pmu_type;
 }
 
+static const char * const gtrace_global_header_fmts[] = {
+	[GTRACE_PMU_TYPE_CPUS]		    = "	    PMU type/num cpus	    %llx\n",
+};
+
+static const char * const rvtrace_encoder_priv_fmts[] = {
+	[RVTRACE_ENCODER_CPU]		    = "	    CPU			    %lld\n",
+	[RVTRACE_ENCODER_NR_TRC_PARAMS]	    = "	    NR_TRC_PARAMS	    %lld\n",
+	[RVTRACE_ENCODER_FORMAT]	    = "	    FORMAT		    %lld\n",
+	[RVTRACE_ENCODER_CONTEXT]	    = "	    CONTEXT		    %lld\n",
+	[RVTRACE_ENCODER_SRCBITS]	    = "	    SRCBITS		    %lld\n",
+	[RVTRACE_ENCODER_TIMESTAMP]	    = "	    TIMESTAMP		    %lld\n",
+};
+
+static void gtrace_print_auxtrace_info(u64 *val, int num_cpu)
+{
+	int i, j, cpu = 0, nr_params = 0, fmt_offset = 0;
+
+	for (i = 0; i < GTRACE_HEADER_MAX; i++)
+		fprintf(stdout, gtrace_global_header_fmts[i], val[i]);
+
+	for (i = GTRACE_HEADER_MAX; cpu < num_cpu; cpu++) {
+		fprintf(stdout, rvtrace_encoder_priv_fmts[RVTRACE_ENCODER_CPU], val[i++]);
+		nr_params = val[i++];
+		fmt_offset = RVTRACE_ENCODER_FORMAT;
+		for (j = fmt_offset; j < nr_params + fmt_offset; j++, i++)
+			fprintf(stdout, rvtrace_encoder_priv_fmts[j], val[i]);
+	}
+}
+
 int gtrace__process_auxtrace_info(union perf_event *event,
 				   struct perf_session *session)
 {
 	struct perf_record_auxtrace_info *auxtrace_info = &event->auxtrace_info;
 	struct gtrace_decoder *ptr;
-
-	if (auxtrace_info->header.size < GTRACE_AUXTRACE_PRIV_SIZE +
-	    sizeof(struct perf_record_auxtrace_info))
+	u64 *priv;
+	u64 **metadata;
+	int num_cpu;
+	int priv_size;
+	int err;
+	int i;
+
+	priv = (u64 *) auxtrace_info->priv;
+	num_cpu = priv[GTRACE_PMU_TYPE_CPUS] & 0xffffffff;
+	if (dump_trace)
+		gtrace_print_auxtrace_info(priv, num_cpu);
+
+	priv_size = auxtrace_info->header.size - sizeof(struct perf_event_header) -
+		    INFO_HEADER_SIZE - GTRACE_HEADER_SIZE;
+	if (num_cpu <= 0 || priv_size <= 0 ||
+	    num_cpu > priv_size / (int)sizeof(u64))
 		return -EINVAL;
 
-	ptr = zalloc(sizeof(*ptr));
-	if (!ptr)
+	metadata = zalloc(sizeof(*metadata) * num_cpu);
+	if (!metadata)
 		return -ENOMEM;
 
+	i = GTRACE_HEADER_SIZE;
+
+	for (int j = 0; j < num_cpu; j++) {
+		metadata[j] = zalloc(sizeof(*metadata[j]) * RVTRACE_ENCODER_PRIV_MAX);
+		if (!metadata[j]) {
+			err = -ENOMEM;
+			goto err_free_metadata;
+		}
+
+		for (int k = 0; k < RVTRACE_ENCODER_PRIV_MAX; k++)
+			metadata[j][k] = priv[i + k];
+		i += RVTRACE_ENCODER_PRIV_MAX;
+	}
+
+	ptr = zalloc(sizeof(*ptr));
+	if (!ptr) {
+		err = -ENOMEM;
+		goto err_free_metadata;
+	}
+
 	ptr->session = session;
 	ptr->machine = &session->machines.host;
 	ptr->auxtrace_type = auxtrace_info->type;
-	ptr->pmu_type = auxtrace_info->priv[0];
+	ptr->pmu_type = (unsigned int) ((priv[GTRACE_PMU_TYPE_CPUS] >> 32) & 0xffffffff);
+	ptr->num_cpu = num_cpu;
+	ptr->metadata = metadata;
 
 	ptr->auxtrace.process_event = gtrace_process_event;
 	ptr->auxtrace.process_auxtrace_event = gtrace_process_auxtrace_event;
@@ -92,4 +162,9 @@ int gtrace__process_auxtrace_info(union perf_event *event,
 	session->auxtrace = &ptr->auxtrace;
 
 	return 0;
+
+err_free_metadata:
+	for (int j = 0; j < num_cpu; j++)
+		zfree(&metadata);
+	return err;
 }
diff --git a/tools/perf/util/gtrace.h b/tools/perf/util/gtrace.h
index 02db4f2ff9f5..76006e30e7c3 100644
--- a/tools/perf/util/gtrace.h
+++ b/tools/perf/util/gtrace.h
@@ -11,7 +11,35 @@
 
 struct perf_session;
 
-#define GTRACE_AUXTRACE_PRIV_SIZE      sizeof(u64)
+/* Supported trace protocol formats */
+enum gtrace_format {
+	GTRACE_FORMAT_ETRACE,
+	GTRACE_FORMAT_NTRACE,
+};
+
+enum {
+	GTRACE_PMU_TYPE_CPUS,
+	GTRACE_HEADER_MAX,
+};
+
+/* RISC-V Trace Encoder metadata */
+enum {
+	RVTRACE_ENCODER_CPU,
+	RVTRACE_ENCODER_NR_TRC_PARAMS,
+	RVTRACE_ENCODER_FORMAT,
+	RVTRACE_ENCODER_CONTEXT,
+	RVTRACE_ENCODER_SRCBITS,
+	RVTRACE_ENCODER_TIMESTAMP,
+	RVTRACE_ENCODER_PRIV_MAX,
+};
+
+#define RVTRACE_ENCODER_NR_TRC_PARAMS_LENGTH (RVTRACE_ENCODER_PRIV_MAX - RVTRACE_ENCODER_FORMAT)
+
+#define GTRACE_HEADER_SIZE             (GTRACE_HEADER_MAX * sizeof(u64))
+#define RVTRACE_ENCODER_PRIV_SIZE      (RVTRACE_ENCODER_PRIV_MAX * sizeof(u64))
+
+#define INFO_HEADER_SIZE (sizeof(((struct perf_record_auxtrace_info *)0)->type) + \
+			  sizeof(((struct perf_record_auxtrace_info *)0)->reserved__))
 
 int gtrace__process_auxtrace_info(union perf_event *event, struct perf_session *session);
 #endif
-- 
2.34.1


^ permalink raw reply	[flat|nested] 25+ messages in thread

* [PATCH 24/24] perf tools: gtrace: Add AUX trace decoder and branch sample synthesis
  2026-10-23  3:14 [RFC PATCH 00/24] Add Linux RISC-V trace support via the gtrace framework Zane Leung
                   ` (22 preceding siblings ...)
  2026-10-23  3:15 ` [PATCH 23/24] perf tools: gtrace: Record and parse trace encoder metadata Zane Leung
@ 2026-10-23  3:15 ` Zane Leung
  23 siblings, 0 replies; 25+ messages in thread
From: Zane Leung @ 2026-10-23  3:15 UTC (permalink / raw)
  To: gregkh, palmer, pjw, alexander.shishkin, suzuki.poulose,
	mchitale, anup, irogers
  Cc: mike.leach, james.clark, leo.yan, peterz, mingo, namhyung,
	mark.rutland, jolsa, adrian.hunter, alex, atish.patra,
	andrew.jones, sunilvl, liangzhen, zhuangqiubin, coresight,
	linux-riscv, linux-kernel

Implement the gtrace auxtrace decoder: queue AUX buffers, run timeless or
timestamped decoding, and synthesize branch samples from decoded trace
packets.  Support machine-mode instruction access via new '-m/--firmware'
and '--firmware-base' options.
---
 tools/perf/builtin-script.c      |    4 +
 tools/perf/util/gtrace-decoder.c | 1206 +++++++++++++++++++++++++++++-
 tools/perf/util/symbol_conf.h    |    2 +
 3 files changed, 1197 insertions(+), 15 deletions(-)

diff --git a/tools/perf/builtin-script.c b/tools/perf/builtin-script.c
index ad8ca08ceb5f..d1173c80b6fa 100644
--- a/tools/perf/builtin-script.c
+++ b/tools/perf/builtin-script.c
@@ -4088,6 +4088,10 @@ int cmd_script(int argc, const char **argv)
 		   "file", "vmlinux pathname"),
 	OPT_STRING(0, "kallsyms", &symbol_conf.kallsyms_name,
 		   "file", "kallsyms pathname"),
+	OPT_STRING('m', "firmware", &symbol_conf.firmware_name,
+		   "file", "machine mode firmware binary pathname"),
+	OPT_STRING(0, "firmware-base", &symbol_conf.firmware_base,
+		   "address", "base address for machine mode firmware (e.g., 0x100000000)"),
 	OPT_BOOLEAN('G', "hide-call-graph", &no_callchain,
 		    "When printing symbols do not display call chain"),
 	OPT_CALLBACK(0, "symfs", NULL, "directory[,layout]", SYMFS_HELP,
diff --git a/tools/perf/util/gtrace-decoder.c b/tools/perf/util/gtrace-decoder.c
index d70cb7cd399f..b2973f7ad3fe 100644
--- a/tools/perf/util/gtrace-decoder.c
+++ b/tools/perf/util/gtrace-decoder.c
@@ -13,6 +13,24 @@
 #include "session.h"
 #include "gtrace.h"
 #include "debug.h"
+#include "color.h"
+#include <linux/bitfield.h>
+#include "intlist.h"
+#include "machine.h"
+#include "map.h"
+#include "thread.h"
+#include "thread_map.h"
+#include "thread-stack.h"
+#include "util.h"
+#include "dso.h"
+#include "maps.h"
+#include "symbol.h"
+#include "addr_location.h"
+#include "util/synthetic-events.h"
+#include <fcntl.h>
+#include <unistd.h>
+#include "rvtrace-decoder/rvtrace-decoder.h"
+#include "../../arch/riscv/include/asm/insn.h"
 
 struct gtrace_decoder {
 	struct auxtrace auxtrace;
@@ -22,31 +40,242 @@ struct gtrace_decoder {
 	u32 pmu_type;
 	int num_cpu;
 	u64 **metadata;
+	struct auxtrace_queues queues;
+	struct auxtrace_heap heap;
+	struct itrace_synth_opts synth_opts;
+	struct dso *firmware_dso;
+	struct maps *firmware_maps;
+	bool timeless_decoding;
+	bool per_thread_decoding;
+	bool data_queued;
+	u64 latest_kernel_timestamp;
+	u64 branches_sample_type;
+	u64 branches_id;
 };
 
-static int gtrace_process_event(struct perf_session *session __maybe_unused,
-				 union perf_event *event __maybe_unused,
-				 struct perf_sample *sample __maybe_unused,
-				 const struct perf_tool *tool __maybe_unused)
+enum coresight_format {
+	UNSET,
+	FORMATTED,
+	UNFORMATTED
+};
+
+struct gtrace_queue {
+	struct gtrace_decoder *ptr;
+	struct thread *thread;
+	struct rvtrace_decoder *decoder;
+	struct auxtrace_buffer *buffer;
+	union perf_event *event_buf;
+	unsigned int queue_nr;
+	u64 offset;
+	enum coresight_format cs_format;
+};
+
+static int gtrace_process_timeless_queues(struct gtrace_decoder *ptr,
+					  pid_t tid);
+static int gtrace_process_timestamped_queues(struct gtrace_decoder *ptr);
+
+static int gtrace_process_itrace_start(struct gtrace_decoder *ptr,
+				       union perf_event *event)
 {
+	struct thread *th;
+
+	if (ptr->timeless_decoding)
+		return 0;
+
+	/*
+	 * Add the tid/pid to the log so that we can get a match when
+	 * we get a contextID from the decoder.
+	 */
+	th = machine__findnew_thread(ptr->machine,
+				     event->itrace_start.pid,
+				     event->itrace_start.tid);
+	if (!th)
+		return -ENOMEM;
+
+	thread__put(th);
+
 	return 0;
 }
 
-static int gtrace_process_auxtrace_event(struct perf_session *session __maybe_unused,
-					  union perf_event *event __maybe_unused,
-					  const struct perf_tool *tool __maybe_unused)
+static int gtrace_process_switch_cpu_wide(struct gtrace_decoder *ptr,
+					  union perf_event *event)
 {
+	struct thread *th;
+	bool out = event->header.misc & PERF_RECORD_MISC_SWITCH_OUT;
+
+	if (ptr->timeless_decoding)
+		return 0;
+
+	if (!out)
+		return 0;
+
+	th = machine__findnew_thread(&ptr->session->machines.host,
+				     event->context_switch.next_prev_pid,
+				     event->context_switch.next_prev_tid);
+
+	if (!th)
+		return -ENOMEM;
+
+	thread__put(th);
+
 	return 0;
 }
 
-static int gtrace_flush(struct perf_session *session __maybe_unused,
-			 const struct perf_tool *tool __maybe_unused)
+static int gtrace_process_event(struct perf_session *session,
+				 union perf_event *event,
+				 struct perf_sample *sample,
+				 const struct perf_tool *tool)
 {
+	struct gtrace_decoder *ptr = container_of(session->auxtrace, struct gtrace_decoder,
+					    auxtrace);
+	if (dump_trace)
+		return 0;
+
+	if (!tool->ordered_events) {
+		pr_err("GTRACE requires ordered events\n");
+		return -EINVAL;
+	}
+
+	switch (event->header.type) {
+	case PERF_RECORD_EXIT:
+		if (ptr->per_thread_decoding && ptr->timeless_decoding)
+			return gtrace_process_timeless_queues(ptr, event->fork.tid);
+		break;
+
+	case PERF_RECORD_ITRACE_START:
+		return gtrace_process_itrace_start(ptr, event);
+
+	case PERF_RECORD_SWITCH_CPU_WIDE:
+		return gtrace_process_switch_cpu_wide(ptr, event);
+
+	case PERF_RECORD_AUX:
+		if (sample->time && (sample->time != (u64)-1))
+			ptr->latest_kernel_timestamp = sample->time;
+		break;
+
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+static void gtrace_dump_event(struct gtrace_queue *gtraceq,
+			      struct auxtrace_buffer *buffer)
+{
+	int ret;
+	const char *color = PERF_COLOR_BLUE;
+	struct rvtrace_decoder *decoder = gtraceq->decoder;
+
+	color_fprintf(stdout, color,
+		      ". ... Trace Encoder Trace data: size %#zx bytes\n",
+		      buffer->size);
+
+	ret = decoder->decode(gtraceq->decoder, RVTRACE_DECODE_MSG,
+			      buffer->data, buffer->size);
+	if (ret)
+		color_fprintf(stdout, color, " Bad packet!\n");
+}
+
+static void dump_queued_data(struct gtrace_decoder *ptr,
+			     struct perf_record_auxtrace *event)
+{
+	struct auxtrace_buffer *buf;
+	unsigned int i;
+	/*
+	 * Find all buffers with same reference in the queues and dump them.
+	 * This is because the queues can contain multiple entries of the same
+	 * buffer that were split on aux records.
+	 */
+	for (i = 0; i < ptr->queues.nr_queues; ++i)
+		list_for_each_entry(buf, &ptr->queues.queue_array[i].head, list)
+			if (buf->reference == event->reference)
+				gtrace_dump_event(ptr->queues.queue_array[i].priv, buf);
+}
+
+static int gtrace_process_auxtrace_event(struct perf_session *session,
+					 union perf_event *event,
+					 const struct perf_tool *tool __maybe_unused)
+{
+	struct gtrace_decoder *ptr = container_of(session->auxtrace, struct gtrace_decoder,
+					    auxtrace);
+
+	if (!ptr->data_queued) {
+		struct auxtrace_buffer *buffer;
+		off_t  data_offset;
+		int fd = perf_data__fd(session->data);
+		bool is_pipe = perf_data__is_pipe(session->data);
+		int err;
+		int idx = event->auxtrace.idx;
+
+		if (is_pipe)
+			data_offset = 0;
+		else {
+			data_offset = lseek(fd, 0, SEEK_CUR);
+			if (data_offset == -1)
+				return -errno;
+		}
+
+		err = auxtrace_queues__add_event(&ptr->queues, session,
+						 event, data_offset, &buffer);
+		if (err)
+			return err;
+
+		if (dump_trace)
+			if (auxtrace_buffer__get_data(buffer, fd)) {
+				gtrace_dump_event(ptr->queues.queue_array[idx].priv, buffer);
+				auxtrace_buffer__put_data(buffer);
+			}
+	} else if (dump_trace)
+		dump_queued_data(ptr, &event->auxtrace);
+
 	return 0;
 }
 
-static void gtrace_free_events(struct perf_session *session __maybe_unused)
+static int gtrace_flush(struct perf_session *session,
+			 const struct perf_tool *tool)
 {
+	struct gtrace_decoder *ptr = container_of(session->auxtrace, struct gtrace_decoder,
+					    auxtrace);
+	if (dump_trace)
+		return 0;
+
+	if (!tool->ordered_events)
+		return -EINVAL;
+
+	if (ptr->timeless_decoding)
+		return gtrace_process_timeless_queues(ptr, -1);
+
+	return gtrace_process_timestamped_queues(ptr);
+}
+
+static void gtrace_free_queue(void *priv)
+{
+	struct gtrace_queue *gtraceq = priv;
+
+	if (!gtraceq)
+		return;
+
+	if (gtraceq->decoder)
+		rvtrace_decoder_free(gtraceq->decoder);
+	thread__zput(gtraceq->thread);
+	zfree(&gtraceq->event_buf);
+	free(gtraceq);
+}
+
+static void gtrace_free_events(struct perf_session *session)
+{
+	unsigned int i;
+	struct gtrace_decoder *ptr = container_of(session->auxtrace, struct gtrace_decoder,
+					    auxtrace);
+	struct auxtrace_queues *queues = &ptr->queues;
+
+	for (i = 0; i < queues->nr_queues; i++) {
+		gtrace_free_queue(queues->queue_array[i].priv);
+		queues->queue_array[i].priv = NULL;
+	}
+
+	auxtrace_queues__free(queues);
 }
 
 static void gtrace_free(struct perf_session *session)
@@ -54,6 +283,7 @@ static void gtrace_free(struct perf_session *session)
 	struct gtrace_decoder *ptr = container_of(session->auxtrace, struct gtrace_decoder,
 					    auxtrace);
 
+	gtrace_free_events(session);
 	session->auxtrace = NULL;
 	for (int i = 0; i < ptr->num_cpu; i++)
 		zfree(&ptr->metadata[i]);
@@ -70,6 +300,643 @@ static bool gtrace_evsel_is_auxtrace(struct perf_session *session,
 	return evsel->core.attr.type == ptr->pmu_type;
 }
 
+static void gtrace_set_thread(struct gtrace_queue *gtraceq,
+			      pid_t tid)
+{
+	struct gtrace_decoder *ptr = gtraceq->ptr;
+
+	if (tid != -1) {
+		thread__zput(gtraceq->thread);
+		gtraceq->thread = machine__find_thread(ptr->machine, -1, tid);
+	}
+
+	/* Couldn't find a known thread */
+	if (!gtraceq->thread)
+		gtraceq->thread = machine__idle_thread(ptr->machine);
+}
+
+/*
+ * Register firmware ELF PT_LOAD segments as maps in the machine's kernel maps,
+ * so that perf script can resolve symbols for M-mode (machine mode) samples.
+ *
+ * base is the absolute physical load address of the firmware (from --firmware-base).
+ * p_paddr in the ELF is the relative address within the firmware image.
+ *
+ *   map->start = base + p_paddr
+ *   map->end   = base + p_paddr + p_memsz
+ *   map->pgoff = p_offset
+ *
+ * map__map_ip(paddr) = paddr - start + pgoff = p_offset + (paddr - base - p_paddr)
+ */
+static int gtrace_register_firmware_maps(struct gtrace_decoder *ptr,
+					 struct dso *dso, u64 base)
+{
+	Elf64_Ehdr ehdr;
+	Elf64_Phdr phdr;
+	int fd, i, ret = 0;
+	const char *filename = dso__long_name(dso);
+
+	fd = open(filename, O_RDONLY);
+	if (fd < 0)
+		return -1;
+
+	if (pread(fd, &ehdr, sizeof(ehdr), 0) != sizeof(ehdr)) {
+		ret = -1;
+		goto out;
+	}
+
+	for (i = 0; i < ehdr.e_phnum; i++) {
+		struct map *map;
+
+		if (pread(fd, &phdr, sizeof(phdr),
+			  ehdr.e_phoff + (u64)i * ehdr.e_phentsize) != sizeof(phdr))
+			continue;
+
+		if (phdr.p_type != PT_LOAD || phdr.p_memsz == 0)
+			continue;
+
+		map = map__new2(base + phdr.p_paddr, dso);
+		if (!map) {
+			ret = -ENOMEM;
+			goto out;
+		}
+
+		map__set_end(map, base + phdr.p_paddr + phdr.p_memsz);
+		map__set_pgoff(map, phdr.p_offset);
+
+		pr_debug("gtrace: machine code map [%#"PRIx64"-%#"PRIx64"] pgoff=%#"PRIx64" %s\n",
+			 base + phdr.p_paddr, base + phdr.p_paddr + phdr.p_memsz,
+			 (u64)phdr.p_offset, dso__long_name(dso));
+
+		ret = maps__insert(ptr->firmware_maps, map);
+		map__put(map);
+		if (ret)
+			goto out;
+	}
+out:
+	close(fd);
+	return ret;
+}
+
+static int gtrace_insert_map_cb(struct map *map, void *data)
+{
+	struct maps *kmaps = data;
+
+	maps__insert(kmaps, map);
+	return 0;
+}
+
+static u32 gtrace_machine_mode_access(struct gtrace_queue *gtraceq, u64 address,
+				       size_t size, u8 *buffer)
+{
+	struct machine *machine = gtraceq->ptr->machine;
+	struct map *map;
+	struct dso *dso;
+	u64 offset;
+	int len;
+
+	if (!gtraceq->ptr->firmware_dso) {
+		ui__warning_once("RISC-V Trace: Machine mode code binary not provided.\n"
+				 "              Failed to read address 0x%lx.\n"
+				 "              Use option '-m /path/to/firmware --firmware-base 0xBASE'\n"
+				 "              to specify the binary and its base address.\n",
+				 address);
+		return 0;
+	}
+
+	map = maps__find(gtraceq->ptr->firmware_maps, address);
+	if (!map)
+		return 0;
+
+	/*
+	 * Insert firmware maps into kernel maps for symbol resolution.
+	 * Re-insert if kcore loading has removed them (kcore calls
+	 * maps__remove_maps which clears all non-kernel maps).
+	 */
+	if (!maps__find(machine__kernel_maps(machine), address)) {
+		maps__for_each_map(gtraceq->ptr->firmware_maps,
+				   gtrace_insert_map_cb,
+				   machine__kernel_maps(machine));
+		map__load(map);
+	}
+
+	dso = map__dso(map);
+	offset = map__map_ip(map, address);
+	len = dso__data_read_offset(dso, machine, offset, buffer, size);
+	map__put(map);
+	return len > 0 ? len : 0;
+}
+
+static u32 gtrace_mem_access(void *data, u64 address, enum riscv_privilege_mode prv,
+			     int context, size_t size, u8 *buffer)
+{
+	u8 cpumode;
+	u64 offset;
+	int len;
+	struct machine *machine;
+	struct addr_location al;
+	struct dso *dso;
+	int ret = 0;
+	struct gtrace_queue *gtraceq = data;
+
+	if (!gtraceq)
+		goto out;
+
+	addr_location__init(&al);
+
+	/* If the riscv_privilege_mode is machine mode, access physical address by /dev/mem */
+	if (prv == RISCV_PRIV_MACHINE_MODE)
+		return gtrace_machine_mode_access(gtraceq, address, size, buffer);
+
+	machine = gtraceq->ptr->machine;
+	if (address >= machine__kernel_start(machine))
+		cpumode = PERF_RECORD_MISC_KERNEL;
+	else
+		cpumode = PERF_RECORD_MISC_USER;
+
+	if (thread__tid(gtraceq->thread) != context)
+		gtrace_set_thread(gtraceq, context);
+
+	if (!thread__find_map(gtraceq->thread, cpumode, address, &al))
+		goto out;
+
+	dso = map__dso(al.map);
+	if (!dso)
+		goto out;
+
+	if (dso__data(dso)->status == DSO_DATA_STATUS_ERROR &&
+	    dso__data_status_seen(dso, DSO_DATA_STATUS_SEEN_ITRACE))
+		goto out;
+
+	offset = map__map_ip(al.map, address);
+
+	map__load(al.map);
+
+	len = dso__data_read_offset(dso, machine, offset, buffer, size);
+	if (len <= 0) {
+		ui__warning_once("GTRACE: Missing DSO. Use 'perf archive' or debuginfod to export data from the traced system.\n"
+				 "                     Enable CONFIG_PROC_KCORE or use option '-k /path/to/vmlinux' for kernel symbols.\n");
+		if (!dso__auxtrace_warned(dso)) {
+			pr_err("RISC-V Trace: Debug data not found for address %#"PRIx64" in %s\n",
+				address,
+				dso__long_name(dso) ? dso__long_name(dso) : "Unknown");
+			dso__set_auxtrace_warned(dso);
+		}
+		goto out;
+	}
+	ret = len;
+out:
+	addr_location__exit(&al);
+	return ret;
+}
+
+static u8 gtrace_cpu_mode(enum riscv_privilege_mode prv)
+{
+	u8 cpumode;
+
+	switch (prv) {
+	case RISCV_PRIV_USER_MODE:
+		cpumode = PERF_RECORD_MISC_USER;
+		break;
+	case RISCV_PRIV_SUPERVISOR_MODE:
+	case RISCV_PRIV_MACHINE_MODE:
+	default:
+		cpumode = PERF_RECORD_MISC_KERNEL;
+		break;
+	}
+	return cpumode;
+}
+
+static void gtrace_synth_copy_insn(struct gtrace_queue *gtraceq,
+				   struct rvtrace_packet *packet,
+				   struct perf_sample *sample)
+{
+	int ret;
+	u32 insn;
+
+	ret = gtrace_mem_access(gtraceq, sample->ip, packet->prv, packet->context,
+				sizeof(insn), (u8 *)&insn);
+	if (!ret)
+		return;
+
+	sample->insn_len = ((insn & __INSN_LENGTH_MASK) == __INSN_LENGTH_GE_32) ? 4 : 2;
+	memcpy(sample->insn, &insn, sample->insn_len);
+}
+
+static inline u64 gtrace_resolve_sample_time(struct gtrace_queue *gtraceq,
+					     struct rvtrace_packet *packet)
+{
+	struct gtrace_decoder *ptr = gtraceq->ptr;
+
+	if (!ptr->timeless_decoding)
+		return packet->timestamp;
+	else
+		return ptr->latest_kernel_timestamp;
+}
+
+static int gtrace_synth_branch_sample(struct gtrace_queue *gtraceq,
+				      struct rvtrace_packet *packet)
+{
+	int ret = 0;
+	struct gtrace_decoder *ptr = gtraceq->ptr;
+	struct perf_sample sample;
+	union perf_event *event = gtraceq->event_buf;
+
+	perf_sample__init(&sample, /*all=*/true);
+	event->sample.header.type = PERF_RECORD_SAMPLE;
+	event->sample.header.misc = gtrace_cpu_mode(packet->prv);
+	event->sample.header.size = sizeof(struct perf_event_header);
+
+	if (thread__tid(gtraceq->thread) != packet->context)
+		gtrace_set_thread(gtraceq, packet->context);
+
+	/* Set time field based on rvtrace auxtrace config. */
+	sample.time = gtrace_resolve_sample_time(gtraceq, packet);
+
+	sample.ip = packet->start_addr;
+	sample.pid = thread__pid(gtraceq->thread);
+	sample.tid = thread__tid(gtraceq->thread);
+	sample.addr = packet->end_addr;
+	sample.insn_cnt = packet->insn_cnt;
+	sample.id = gtraceq->ptr->branches_id;
+	sample.stream_id = gtraceq->ptr->branches_id;
+	sample.period = 1;
+	sample.cpu = packet->cpu;
+	sample.flags = 0;
+	sample.cpumode = event->sample.header.misc;
+
+	gtrace_synth_copy_insn(gtraceq, packet, &sample);
+
+	ret = perf_session__deliver_synth_event(ptr->session, event, &sample);
+	if (ret)
+		pr_err(
+		"RISC-V Trace: failed to deliver instruction event, error %d\n",
+		ret);
+	perf_sample__exit(&sample);
+
+	return ret;
+}
+
+static int gtrace_synth_events(struct gtrace_decoder *ptr,
+			       struct perf_session *session)
+{
+	struct evlist *evlist = session->evlist;
+	struct evsel *evsel;
+	struct perf_event_attr attr;
+	bool found = false;
+	u64 id;
+	int err;
+
+	evlist__for_each_entry(evlist, evsel) {
+		if (evsel->core.attr.type == ptr->pmu_type) {
+			found = true;
+			break;
+		}
+	}
+
+	if (!found) {
+		pr_debug("No selected events with RISC-V Trace data\n");
+		return 0;
+	}
+
+	memset(&attr, 0, sizeof(struct perf_event_attr));
+	attr.size = sizeof(struct perf_event_attr);
+	attr.type = PERF_TYPE_HARDWARE;
+	attr.sample_type = evsel->core.attr.sample_type & PERF_SAMPLE_MASK;
+	attr.sample_type |= PERF_SAMPLE_IP | PERF_SAMPLE_TID |
+			    PERF_SAMPLE_PERIOD;
+	if (ptr->timeless_decoding)
+		attr.sample_type &= ~(u64)PERF_SAMPLE_TIME;
+	else
+		attr.sample_type |= PERF_SAMPLE_TIME;
+
+	attr.exclude_user = evsel->core.attr.exclude_user;
+	attr.exclude_kernel = evsel->core.attr.exclude_kernel;
+	attr.exclude_hv = evsel->core.attr.exclude_hv;
+	attr.exclude_host = evsel->core.attr.exclude_host;
+	attr.exclude_guest = evsel->core.attr.exclude_guest;
+	attr.sample_id_all = evsel->core.attr.sample_id_all;
+	attr.read_format = evsel->core.attr.read_format;
+
+	/* create new id val to be a fixed offset from evsel id */
+	id = evsel->core.id[0] + 1000000000;
+	if (!id)
+		id = 1;
+
+	if (ptr->synth_opts.branches) {
+		attr.config = PERF_COUNT_HW_BRANCH_INSTRUCTIONS;
+		attr.sample_period = 1;
+		attr.sample_type |= PERF_SAMPLE_ADDR;
+
+		err = perf_session__deliver_synth_attr_event(session, &attr, id);
+		if (err)
+			return err;
+		ptr->branches_sample_type = attr.sample_type;
+		ptr->branches_id = id;
+		id += 1;
+		attr.sample_type &= ~(u64)PERF_SAMPLE_ADDR;
+	}
+
+	return 0;
+}
+
+static int gtrace_process_queue(struct gtrace_queue *gtraceq)
+{
+	struct rvtrace_packet_buffer *packet_buffer = &gtraceq->decoder->packet_buffer;
+
+	for (int i = 0; i < packet_buffer->size; i++) {
+		struct rvtrace_packet packet = packet_buffer->packets[i];
+
+		if (packet.sample_type == RVTRACE_RANGE)
+			gtrace_synth_branch_sample(gtraceq, &packet);
+		else if (packet.sample_type == RVTRACE_LOSS)
+			fprintf(stdout, "RISC-V Trace: FIFO overrun, messages lost\n");
+		else if (packet.sample_type == RVTRACE_ERROR)
+			fprintf(stdout, "RISC-V Trace: Packet data parsing error\n");
+	}
+
+	return 0;
+}
+
+static int gtrace_get_trace(struct gtrace_queue *gtraceq)
+{
+	struct auxtrace_buffer *aux_buffer = gtraceq->buffer;
+	struct auxtrace_buffer *old_buffer = aux_buffer;
+	struct auxtrace_queue *queue;
+
+	queue = &gtraceq->ptr->queues.queue_array[gtraceq->queue_nr];
+
+	aux_buffer = auxtrace_buffer__next(queue, aux_buffer);
+
+	/* If no more data, drop the previous auxtrace_buffer and return */
+	if (!aux_buffer) {
+		if (old_buffer)
+			auxtrace_buffer__drop_data(old_buffer);
+		return 0;
+	}
+
+	gtraceq->buffer = aux_buffer;
+
+	/* If the aux_buffer doesn't have data associated, try to load it */
+	if (!aux_buffer->data) {
+		/* get the file desc associated with the perf data file */
+		int fd = perf_data__fd(gtraceq->ptr->session->data);
+
+		aux_buffer->data = auxtrace_buffer__get_data(aux_buffer, fd);
+		if (!aux_buffer->data)
+			return -ENOMEM;
+	}
+
+	/* If valid, drop the previous buffer */
+	if (old_buffer)
+		auxtrace_buffer__drop_data(old_buffer);
+
+	return aux_buffer->size;
+}
+
+/*
+ * gtrace_get_data_block: Fetch a block from the auxtrace_buffer queue
+ *                         if need be.
+ * Returns:     < 0     if error
+ *              = 0     if no more auxtrace_buffer to read
+ *              > 0     if the current buffer isn't empty yet
+ */
+static int gtrace_get_data_block(struct gtrace_queue *gtraceq)
+{
+	int ret;
+
+	ret = gtrace_get_trace(gtraceq);
+	if (ret <= 0)
+		return ret;
+
+	/*
+	 * We cannot assume consecutive blocks in the data file
+	 * are contiguous, reset the decoder to force re-sync.
+	 */
+	ret = rvtrace_decoder_reset(gtraceq->decoder);
+	if (ret)
+		return ret;
+
+	return gtraceq->buffer->size;
+}
+
+static int gtrace_run_timeless_decoder(struct gtrace_queue *gtraceq)
+{
+	struct rvtrace_decoder *decoder = gtraceq->decoder;
+	int ret;
+
+	while (1) {
+		ret = gtrace_get_data_block(gtraceq);
+		if (ret < 0)
+			return ret;
+
+		if (ret == 0)
+			break;
+
+		ret = decoder->decode(decoder, RVTRACE_DECODE_INSN,
+				      gtraceq->buffer->data,
+				      gtraceq->buffer->size);
+		if (ret)
+			return ret;
+
+		ret = gtrace_process_queue(gtraceq);
+		if (ret)
+			return ret;
+	}
+
+	return ret;
+}
+
+static int gtrace_process_timeless_queues(struct gtrace_decoder *ptr,
+					  pid_t tid)
+{
+	unsigned int i;
+	struct auxtrace_queues *queues = &ptr->queues;
+
+	for (i = 0; i < queues->nr_queues; i++) {
+		struct auxtrace_queue *queue = &ptr->queues.queue_array[i];
+		struct gtrace_queue *gtraceq = queue->priv;
+
+		if (gtraceq && ((tid == -1) || (queue->tid == tid))) {
+			gtrace_set_thread(gtraceq, queue->tid);
+			gtrace_run_timeless_decoder(gtraceq);
+		}
+	}
+
+	return 0;
+}
+
+static u64 gtrace_queue_get_timestamp(struct gtrace_queue *gtraceq)
+{
+	struct rvtrace_packet_buffer *packet_buffer = &gtraceq->decoder->packet_buffer;
+
+	for (int i = 0; i < packet_buffer->size; i++) {
+		struct rvtrace_packet packet = packet_buffer->packets[i];
+
+		if (packet.sample_type == RVTRACE_RANGE)
+			return packet.timestamp;
+	}
+
+	return 0;
+}
+
+static int gtrace_queue_first_timestamp(struct gtrace_decoder *ptr,
+					struct gtrace_queue *gtraceq,
+					unsigned int queue_nr)
+{
+	struct rvtrace_decoder *decoder = gtraceq->decoder;
+	u64 timestamp = 0;
+	int ret;
+
+	/* Decode the first segment of data until a timestamp is found,
+	 * then add it to the heap for sorting by time across multiple
+	 * queues.
+	 */
+	while (1) {
+		/*
+		 * Fetch an aux_buffer from this gtraceq. Bail if no more
+		 * blocks or an error has been encountered.
+		 */
+		ret = gtrace_get_data_block(gtraceq);
+		if (ret <= 0)
+			goto out;
+
+		ret = decoder->decode(decoder, RVTRACE_DECODE_INSN,
+				      gtraceq->buffer->data,
+				      gtraceq->buffer->size);
+		if (ret)
+			goto out;
+
+		timestamp = gtrace_queue_get_timestamp(gtraceq);
+
+		/* We found a timestamp, no need to continue. */
+		if (timestamp)
+			break;
+	}
+
+	/* We have a timestamp and add it to the min heap */
+	ret = auxtrace_heap__add(&ptr->heap, queue_nr, timestamp);
+out:
+	return ret;
+}
+
+static int gtrace_process_timestamped_queues(struct gtrace_decoder *ptr)
+{
+	int ret = 0;
+	unsigned int queue_nr, i;
+	struct auxtrace_queue *queue;
+	struct gtrace_queue *gtraceq;
+
+	/* First, find the first timestamp for each queue and add it to the heap. */
+	for (i = 0; i < ptr->queues.nr_queues; i++) {
+		queue = &ptr->queues.queue_array[i];
+		gtraceq = queue->priv;
+		if (!gtraceq)
+			continue;
+
+		gtrace_set_thread(gtraceq, queue->tid);
+
+		ret = gtrace_queue_first_timestamp(ptr, gtraceq, i);
+		if (ret)
+			return ret;
+	}
+
+	/* Process queues in the heap in timestamp order */
+	while (1) {
+		if (!ptr->heap.heap_cnt)
+			break;
+
+		/* Take the entry at the top of the min heap */
+		queue_nr = ptr->heap.heap_array[0].queue_nr;
+		queue = &ptr->queues.queue_array[queue_nr];
+		gtraceq = queue->priv;
+
+		/*
+		 * Remove the top entry from the heap since we are about
+		 * to process it.
+		 */
+		auxtrace_heap__pop(&ptr->heap);
+
+		/*
+		 * Packets associated with this timestamp are already in
+		 * the gtraceq->packet_buffer, so process them.
+		 */
+		ret = gtrace_process_queue(gtraceq);
+		if (ret)
+			return ret;
+
+		/*
+		 * Packets for this timestamp have been processed, time to
+		 * move on to the next timestamp, find the next timestamp
+		 * for this gtraceq.
+		 */
+		ret = gtrace_queue_first_timestamp(ptr, gtraceq, queue_nr);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static struct gtrace_queue *gtrace_alloc_queue(struct gtrace_decoder *ptr,
+					       unsigned int queue_nr)
+{
+	struct gtrace_queue *gtraceq;
+
+	gtraceq = zalloc(sizeof(*gtraceq));
+	if (!gtraceq)
+		return NULL;
+
+	gtraceq->event_buf = malloc(PERF_SAMPLE_MAX_SIZE);
+	if (!gtraceq->event_buf)
+		goto out_free;
+
+	gtraceq->ptr = ptr;
+	gtraceq->queue_nr = queue_nr;
+	gtraceq->offset = 0;
+
+	return gtraceq;
+
+out_free:
+	zfree(&gtraceq->event_buf);
+	free(gtraceq);
+
+	return NULL;
+
+}
+
+static int gtrace_setup_queue(struct gtrace_decoder *ptr,
+			      struct auxtrace_queue *queue,
+			      unsigned int queue_nr)
+{
+	struct gtrace_queue *gtraceq = queue->priv;
+
+	if (gtraceq)
+		return 0;
+
+	gtraceq = gtrace_alloc_queue(ptr, queue_nr);
+	if (!gtraceq)
+		return -ENOMEM;
+
+	queue->priv = gtraceq;
+	queue->cpu = queue_nr;
+
+	return 0;
+}
+
+static void gtrace_setup_timeless_decoding(struct gtrace_decoder *ptr)
+{
+	u64 *metadata = ptr->metadata[0];
+
+	/* Override timeless mode with user input from --itrace=Z */
+	if (ptr->synth_opts.timeless_decoding) {
+		ptr->timeless_decoding = true;
+		return;
+	}
+
+	ptr->timeless_decoding = metadata[RVTRACE_ENCODER_TIMESTAMP];
+}
+
 static const char * const gtrace_global_header_fmts[] = {
 	[GTRACE_PMU_TYPE_CPUS]		    = "	    PMU type/num cpus	    %llx\n",
 };
@@ -99,6 +966,251 @@ static void gtrace_print_auxtrace_info(u64 *val, int num_cpu)
 	}
 }
 
+static struct gtrace_queue *gtrace_get_queue(struct gtrace_decoder *ptr, int cpu)
+{
+	if (ptr->per_thread_decoding)
+		return ptr->queues.queue_array[0].priv;
+
+	if (cpu < 0 || cpu >= (int)ptr->queues.nr_queues)
+		return NULL;
+
+	return ptr->queues.queue_array[cpu].priv;
+}
+
+/**
+ * Puts a fragment of an auxtrace buffer into the auxtrace queues based
+ * on the bounds of aux_event, if it matches with the buffer that's at
+ * file_offset.
+ *
+ * Normally, whole auxtrace buffers would be added to the queue. But we
+ * want to reset the decoder for every PERF_RECORD_AUX event, and the decoder
+ * is reset across each buffer, so splitting the buffers up in advance has
+ * the same effect.
+ */
+static int gtrace_queue_aux_fragment(struct perf_session *session, off_t file_offset, size_t sz,
+				     struct perf_record_aux *aux_event, struct perf_sample *sample)
+{
+	int err;
+	char buf[PERF_SAMPLE_MAX_SIZE];
+	union perf_event *auxtrace_event_union;
+	struct perf_record_auxtrace *auxtrace_event;
+	union perf_event auxtrace_fragment;
+	__u64 aux_offset, aux_size;
+	enum coresight_format cs_format;
+
+	struct gtrace_decoder *ptr = container_of(session->auxtrace, struct gtrace_decoder,
+					    auxtrace);
+
+	/*
+	 * There should be a PERF_RECORD_AUXTRACE event at the file_offset that we got
+	 * from looping through the auxtrace index.
+	 */
+	err = perf_session__peek_event(session, file_offset, buf,
+				       PERF_SAMPLE_MAX_SIZE, &auxtrace_event_union, NULL);
+	if (err)
+		return err;
+	auxtrace_event = &auxtrace_event_union->auxtrace;
+	if (auxtrace_event->header.type != PERF_RECORD_AUXTRACE)
+		return -EINVAL;
+
+	if (auxtrace_event->header.size < sizeof(struct perf_record_auxtrace) ||
+		auxtrace_event->header.size != sz) {
+		return -EINVAL;
+	}
+
+	/*
+	 * Compare only TIDs if auxtrace CPU is -1, and CPUs if auxtrace CPU is not -1.
+	 * Return 'not found' if mismatch.
+	 */
+	if (auxtrace_event->cpu == (__u32) -1) {
+		ptr->per_thread_decoding = true;
+		if (auxtrace_event->tid != sample->tid)
+			return 1;
+	} else if (auxtrace_event->cpu != sample->cpu) {
+		if (ptr->per_thread_decoding) {
+			/*
+			 * Found a per-cpu buffer after a per-thread one was
+			 * already found
+			 */
+			pr_err("GTRACE: Inconsistent per-thread/per-cpu mode.\n");
+			return -EINVAL;
+		}
+		return 1;
+	}
+
+	if (aux_event->flags & PERF_AUX_FLAG_OVERWRITE) {
+		/*
+		 * Clamp size in snapshot mode. The buffer size is clamped in
+		 * __auxtrace_mmap__read() for snapshots, so the aux record size doesn't reflect
+		 * the buffer size.
+		 */
+		aux_size = min(aux_event->aux_size, auxtrace_event->size);
+
+		/*
+		 * In this mode, the head also points to the end of the buffer so aux_offset
+		 * needs to have the size subtracted so it points to the beginning as in normal mode
+		 */
+		aux_offset = aux_event->aux_offset - aux_size;
+	} else {
+		aux_size = aux_event->aux_size;
+		aux_offset = aux_event->aux_offset;
+	}
+
+	if (aux_offset >= auxtrace_event->offset &&
+	    aux_offset + aux_size <= auxtrace_event->offset + auxtrace_event->size) {
+		struct gtrace_queue *gtraceq = gtrace_get_queue(ptr, auxtrace_event->cpu);
+
+		if (!gtraceq)
+			return -EINVAL;
+
+		/*
+		 * If this AUX event was inside this buffer somewhere, create a new auxtrace event
+		 * based on the sizes of the aux event, and queue that fragment.
+		 */
+		auxtrace_fragment.auxtrace = *auxtrace_event;
+		auxtrace_fragment.auxtrace.size = aux_size;
+		auxtrace_fragment.auxtrace.offset = aux_offset;
+		auxtrace_fragment.auxtrace.idx = gtraceq->queue_nr;
+		file_offset += aux_offset - auxtrace_event->offset + auxtrace_event->header.size;
+
+		pr_debug3("GTRACE: Queue buffer size: %#"PRI_lx64" offset: %#"PRI_lx64
+			  " tid: %d cpu: %d\n", aux_size, aux_offset, sample->tid, sample->cpu);
+		err = auxtrace_queues__add_event(&ptr->queues, session, &auxtrace_fragment,
+						 file_offset, NULL);
+		if (err)
+			return err;
+
+		cs_format = (aux_event->flags & PERF_AUX_FLAG_GTRACE_FORMAT_CORESIGHT) ?
+					FORMATTED : UNFORMATTED;
+		if (gtraceq->cs_format != UNSET && cs_format != gtraceq->cs_format) {
+			pr_err("GTRACE: mixed coresight formatted and unformatted trace not supported\n");
+			return -EINVAL;
+		}
+		gtraceq->cs_format = cs_format;
+		return 0;
+	}
+
+	/* Wasn't inside this buffer, but there were no parse errors. 1 == 'not found' */
+	return 1;
+}
+
+static int gtrace_queue_aux_records_cb(struct perf_session *session, union perf_event *event,
+				       u64 offset __maybe_unused, void *data __maybe_unused)
+{
+	struct perf_sample sample;
+	int ret;
+	struct auxtrace_index_entry *ent;
+	struct auxtrace_index *auxtrace_index;
+	struct evsel *evsel;
+	size_t i;
+
+	/* Don't care about any other events, we're only queuing buffers for AUX events */
+	if (event->header.type != PERF_RECORD_AUX)
+		return 0;
+
+	if (event->header.size < sizeof(struct perf_record_aux))
+		return -EINVAL;
+
+	/* Truncated Aux records can have 0 size and shouldn't result in anything being queued. */
+	if (!event->aux.aux_size)
+		return 0;
+
+	/*
+	 * Parse the sample, we need the sample_id_all data that comes after the event so that the
+	 * CPU or PID can be matched to an AUXTRACE buffer's CPU or PID.
+	 */
+	evsel = evlist__event2evsel(session->evlist, event);
+	if (!evsel)
+		return -EINVAL;
+	perf_sample__init(&sample, /*all=*/false);
+	ret = evsel__parse_sample(evsel, event, &sample);
+	if (ret)
+		goto out;
+
+	/*
+	 * Loop through the auxtrace index to find the buffer that matches up with this aux event.
+	 */
+	list_for_each_entry(auxtrace_index, &session->auxtrace_index, list) {
+		for (i = 0; i < auxtrace_index->nr; i++) {
+			ent = &auxtrace_index->entries[i];
+			ret = gtrace_queue_aux_fragment(session, ent->file_offset,
+							ent->sz, &event->aux, &sample);
+			/*
+			 * Stop search on error or successful values. Continue search on
+			 * 1 ('not found')
+			 */
+			if (ret != 1)
+				goto out;
+		}
+	}
+
+	/*
+	 * Couldn't find the buffer corresponding to this aux record, something went wrong. Warn but
+	 * don't exit with an error because it will still be possible to decode other aux records.
+	 */
+	pr_err("CS ETM: Couldn't find auxtrace buffer for aux_offset: %#"PRI_lx64
+	       " tid: %d cpu: %d\n", event->aux.aux_offset, sample.tid, sample.cpu);
+	ret = 0;
+out:
+	perf_sample__exit(&sample);
+	return ret;
+
+}
+
+static int gtrace_queue_aux_records(struct perf_session *session)
+{
+	struct auxtrace_index *index = list_first_entry_or_null(&session->auxtrace_index,
+								struct auxtrace_index, list);
+
+	if (index && index->nr > 0)
+		return perf_session__peek_events(session, session->header.data_offset,
+						 session->header.data_size,
+						 gtrace_queue_aux_records_cb, NULL);
+
+	return 0;
+}
+
+static int gtrace_create_queue_decoder(struct gtrace_queue *gtraceq)
+{
+	struct gtrace_decoder *ptr = gtraceq->ptr;
+	struct rvtrace_decoder_params params;
+
+	params.data = gtraceq;
+	params.mem_access = gtrace_mem_access;
+	params.coresight_formatted = gtraceq->cs_format == FORMATTED;
+	params.format = ptr->metadata[gtraceq->queue_nr][RVTRACE_ENCODER_FORMAT];
+	params.srcbits = ptr->metadata[gtraceq->queue_nr][RVTRACE_ENCODER_SRCBITS];
+	gtraceq->decoder = rvtrace_decoder_new(&params);
+	if (!gtraceq->decoder)
+		return -EINVAL;
+
+	return 0;
+}
+
+static int gtrace_create_decoders(struct gtrace_decoder *ptr)
+{
+	struct auxtrace_queues *queues = &ptr->queues;
+
+	for (unsigned int i = 0; i < queues->nr_queues; i++) {
+		bool empty = list_empty(&queues->queue_array[i].head);
+		struct gtrace_queue *gtraceq = queues->queue_array[i].priv;
+		int ret;
+
+		/*
+		 * Don't create decoders for empty queues, mainly because
+		 * q->format is unknown for empty queues.
+		 */
+		assert(empty || gtraceq->cs_format != UNSET);
+		if (empty)
+			continue;
+
+		ret = gtrace_create_queue_decoder(gtraceq);
+		if (ret)
+			return ret;
+	}
+	return 0;
+}
+
 int gtrace__process_auxtrace_info(union perf_event *event,
 				   struct perf_session *session)
 {
@@ -106,7 +1218,7 @@ int gtrace__process_auxtrace_info(union perf_event *event,
 	struct gtrace_decoder *ptr;
 	u64 *priv;
 	u64 **metadata;
-	int num_cpu;
+	int num_cpu, max_cpu = 0;
 	int priv_size;
 	int err;
 	int i;
@@ -115,7 +1227,6 @@ int gtrace__process_auxtrace_info(union perf_event *event,
 	num_cpu = priv[GTRACE_PMU_TYPE_CPUS] & 0xffffffff;
 	if (dump_trace)
 		gtrace_print_auxtrace_info(priv, num_cpu);
-
 	priv_size = auxtrace_info->header.size - sizeof(struct perf_event_header) -
 		    INFO_HEADER_SIZE - GTRACE_HEADER_SIZE;
 	if (num_cpu <= 0 || priv_size <= 0 ||
@@ -126,7 +1237,7 @@ int gtrace__process_auxtrace_info(union perf_event *event,
 	if (!metadata)
 		return -ENOMEM;
 
-	i = GTRACE_HEADER_SIZE;
+	i = GTRACE_HEADER_MAX;
 
 	for (int j = 0; j < num_cpu; j++) {
 		metadata[j] = zalloc(sizeof(*metadata[j]) * RVTRACE_ENCODER_PRIV_MAX);
@@ -137,6 +1248,9 @@ int gtrace__process_auxtrace_info(union perf_event *event,
 
 		for (int k = 0; k < RVTRACE_ENCODER_PRIV_MAX; k++)
 			metadata[j][k] = priv[i + k];
+
+		if ((int)metadata[j][RVTRACE_ENCODER_CPU] > max_cpu)
+			max_cpu = metadata[j][RVTRACE_ENCODER_CPU];
 		i += RVTRACE_ENCODER_PRIV_MAX;
 	}
 
@@ -146,6 +1260,26 @@ int gtrace__process_auxtrace_info(union perf_event *event,
 		goto err_free_metadata;
 	}
 
+	err = auxtrace_queues__init_nr(&ptr->queues, max_cpu + 1);
+	if (err)
+		goto err_free_metadata;
+
+	for (unsigned int j = 0; j < ptr->queues.nr_queues; ++j) {
+		err = gtrace_setup_queue(ptr, &ptr->queues.queue_array[j], j);
+		if (err)
+			goto err_free_queues;
+	}
+
+	if (session->itrace_synth_opts->set) {
+		ptr->synth_opts = *session->itrace_synth_opts;
+	} else {
+		itrace_synth_opts__set_default(&ptr->synth_opts,
+				session->itrace_synth_opts->default_no_sample);
+		ptr->synth_opts.callchain = false;
+		ptr->synth_opts.thread_stack = session->itrace_synth_opts->thread_stack;
+	}
+
+
 	ptr->session = session;
 	ptr->machine = &session->machines.host;
 	ptr->auxtrace_type = auxtrace_info->type;
@@ -161,10 +1295,52 @@ int gtrace__process_auxtrace_info(union perf_event *event,
 	ptr->auxtrace.evsel_is_auxtrace = gtrace_evsel_is_auxtrace;
 	session->auxtrace = &ptr->auxtrace;
 
-	return 0;
+	gtrace_setup_timeless_decoding(ptr);
+
+	/* Load firmware binary if specified */
+	if (symbol_conf.firmware_name) {
+		u64 base_addr = 0;
+
+		if (symbol_conf.firmware_base)
+			base_addr = strtoull(symbol_conf.firmware_base, NULL, 0);
+
+		ptr->firmware_dso = dso__new(symbol_conf.firmware_name);
+		if (ptr->firmware_dso) {
+			ptr->firmware_maps = maps__new(ptr->machine);
+			if (!ptr->firmware_maps ||
+				gtrace_register_firmware_maps(ptr,
+							      ptr->firmware_dso,
+							      base_addr) < 0) {
+				pr_err("Failed to register machine code maps: %s\n",
+					symbol_conf.firmware_name);
+				dso__put(ptr->firmware_dso);
+				ptr->firmware_dso = NULL;
+				maps__put(ptr->firmware_maps);
+				ptr->firmware_maps = NULL;
+			}
+		}
+	}
 
+	err = gtrace_synth_events(ptr, session);
+	if (err)
+		goto err_free_queues;
+
+	err = gtrace_queue_aux_records(session);
+	if (err)
+		goto err_free_queues;
+
+	err = gtrace_create_decoders(ptr);
+	if (err)
+		goto err_free_queues;
+
+	ptr->data_queued = ptr->queues.populated;
+	return 0;
+err_free_queues:
+	auxtrace_queues__free(&ptr->queues);
+	session->auxtrace = NULL;
 err_free_metadata:
 	for (int j = 0; j < num_cpu; j++)
-		zfree(&metadata);
+		zfree(&metadata[j]);
+	zfree(&metadata);
 	return err;
 }
diff --git a/tools/perf/util/symbol_conf.h b/tools/perf/util/symbol_conf.h
index 0dee5aa6a534..83fc134ea8ac 100644
--- a/tools/perf/util/symbol_conf.h
+++ b/tools/perf/util/symbol_conf.h
@@ -72,6 +72,8 @@ struct symbol_conf {
 			prefer_latency;
 	const char	*vmlinux_name,
 			*kallsyms_name,
+			*firmware_name,
+			*firmware_base,
 			*source_prefix,
 			*field_sep,
 			*graph_function;
-- 
2.34.1


^ permalink raw reply	[flat|nested] 25+ messages in thread

end of thread, other threads:[~2026-10-23  3:18 UTC | newest]

Thread overview: 25+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-23  3:14 [RFC PATCH 00/24] Add Linux RISC-V trace support via the gtrace framework Zane Leung
2026-10-23  3:14 ` [PATCH 01/24] gtrace: Move connection release into component unregister Zane Leung
2026-10-23  3:14 ` [PATCH 02/24] gtrace: Initial implementation of gtrace sysfs framework Zane Leung
2026-10-23  3:14 ` [PATCH 03/24] gtrace: Drop trace format field and helper function Zane Leung
2026-10-23  3:14 ` [PATCH 04/24] gtrace: Add RISC-V trace encoder sysfs support Zane Leung
2026-10-23  3:14 ` [PATCH 05/24] gtrace: Introduce some PMU format attributes Zane Leung
2026-10-23  3:14 ` [PATCH 06/24] gtrace: Replace owner PID with perf_event pointer in gtrace_path Zane Leung
2026-10-23  3:14 ` [PATCH 07/24] gtrace: Parse RISC-V Trace encoder config from perf event Zane Leung
2026-10-23  3:14 ` [PATCH 08/24] gtrace: add perf symlink support for gtrace components Zane Leung
2026-10-23  3:14 ` [PATCH 09/24] gtrace: Add port-level start/stop driver callbacks Zane Leung
2026-10-23  3:14 ` [PATCH 10/24] gtrace: Add RISC-V trace funnel driver Zane Leung
2026-10-23  3:14 ` [PATCH 11/24] gtrace: Add RISC-V trace timestamp support Zane Leung
2026-10-23  3:14 ` [PATCH 12/24] gtrace: Add RISC-V trace ATB bridge driver Zane Leung
2026-10-23  3:14 ` [PATCH 13/24] gtrace: Add CoreSight platform core APIs Zane Leung
2026-10-23  3:14 ` [PATCH 14/24] gtrace: Add CoreSight funnel driver Zane Leung
2026-10-23  3:14 ` [PATCH 15/24] gtrace: Only copy AUX data from the path sink Zane Leung
2026-10-23  3:14 ` [PATCH 16/24] perf: Flag whether gtrace AUX data carries CoreSight framing Zane Leung
2026-10-23  3:14 ` [PATCH 17/24] gtrace: Unify default sink selection across CPUs in perf setup Zane Leung
2026-10-23  3:14 ` [PATCH 18/24] gtrace: Add sink-specific buffer alloc/free callbacks Zane Leung
2026-10-23  3:15 ` [PATCH 19/24] gtrace: Add setup_sysfs_buf callback to setup sysfs sink buffer Zane Leung
2026-10-23  3:15 ` [PATCH 20/24] gtrace: Add CoreSight TMC driver support Zane Leung
2026-10-23  3:15 ` [PATCH 21/24] perf tools: riscv: making gtrace PMU listable Zane Leung
2026-10-23  3:15 ` [PATCH 22/24] perf tools: Add Nexus RISC-V Trace decoder Zane Leung
2026-10-23  3:15 ` [PATCH 23/24] perf tools: gtrace: Record and parse trace encoder metadata Zane Leung
2026-10-23  3:15 ` [PATCH 24/24] perf tools: gtrace: Add AUX trace decoder and branch sample synthesis Zane Leung

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®