* [PATCH v6 00/14] Linux RISC-V trace framework and drivers
@ 2026-10-01 5:22 Mayuresh Chitale
2026-10-01 5:22 ` [PATCH v6 01/14] dt-bindings: Add RISC-V trace component bindings Mayuresh Chitale
` (13 more replies)
0 siblings, 14 replies; 25+ messages in thread
From: Mayuresh Chitale @ 2026-10-01 5:22 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Palmer Dabbelt,
Paul Walmsley, Greg KH, Alexander Shishkin, Ian Rogers
Cc: Mayuresh Chitale, Alexandre Ghiti, Peter Zijlstra, Ingo Molnar,
Namhyung Kim, Mark Rutland, Jiri Olsa, Adrian Hunter,
Mayuresh Chitale, Anup Patel, Atish Patra, Andrew Jones,
Sunil V L, linux-riscv, devicetree, linux-kernel, Zane Leung
This series adds 'gtrace' which is an architecture-neutral hardware trace
framework with RISC-V E-trace as its first backend. The 'gtrace' core framework
has the minimal shared plumbing (component registration, path create/destroy,
path start/stop, etc) and the RISC-V trace drivers are its first consumer.
It should be possible for the required ARM components to be adapted to the same
framework in a future.
This series also adds initial support for RISC-V trace compnent drivers.
The RISC-V trace v1.0 specification is already ratified and can be found at:
https://github.com/riscv-non-isa/e-trace-encap/releases/tag/v1.0.0-ratified
https://github.com/riscv-non-isa/tg-nexus-trace/releases/tag/1.0_Ratified
The RISC-V trace component drivers are designed to be agnostic to the
underlying trace protocol hence both RISC-V E-trace and RISC-V N-trace should
work fine. The discovery of trace protocl parameters are left to user-space
trace decoder.
In the future, there will be subsequent series adding:
1) Sysfs support
2) ACPI support
3) More trace drivers (such as funnel, ATB, etc)
4) Support for upcoming self-hosted trace specification
5) Arm component support under the gtrace framework
6) ... and more ...
These patches can also be found in the riscv_trace_support_v6 branch at:
https://github.com/mdchitale/linux.git
To test the patches, we need QEMU virt machine with RISC-V trace support
which can be found in rv-etrace branch at:
https://gitlab.com/danielhb/qemu.git
To capture gtrace data using perf on QEMU virt machine do the following:
1) Launch QEMU virt machine
$ qemu-system-riscv64 -nographic -M virt -smp 2 -bios fw_dynamic.bin \
-kernel Image -append "root=/dev/vda rw console=ttyS0 earlycon=sbi" \
-drive file=rootfs.img,id=disk1,if=none,format=raw \
-device virtio-blk-device,drive=disk1
2) Run perf record to capture gtrace data
$ perf record --all-cpus -e gtrace/event=0x1/ <command>
3) The step2 would create a perf.data file which has the gtrace data.
Now run perf report -D and look for PERF_RECORD_AUXTRACE event
section(s) which point(s) to the actual gtrace data offset.
Changes since v5:
- Moved the DT connection parsing into the gtrace core as gtrace-of.c.
- Added start_count and a lock to the core's private component data
to serialize start/stop/copyto_auxbuf operations. Dropped
perf_buf_lock.
- Added handling of partially enabled path by disabling all enabled
components
- Added cycle detection when walking the trace component graph
- Added trace source ID configuration in the encoder driver
- Added trace format (E-Trace/N-Trace) selection via the platform driver
compatible string. It is used by encoder and perf drivers to set it
into hardware and perf data header respectively
- Modified the ramsink buffer setup to read back the trRamStart and
trRamLimit registers, and program the sync frequency, memory format
and stop-on-wrap settings at start time
- Improved error handling and added path owner in the perf driver
- Fixed several issues reported by sashiko-bot but some of its findings
are deferred to a later version.
Changes since v4:
- Introduced a new architecture-neutral 'gtrace' (Generic Trace) framework
and moved the RISC-V trace drivers on top of it instead of a RISC-V-only
'rvtrace' core.
- Added new PATCH3 "gtrace: Add RISC-V platform driver for the gtrace
framework" to split out the RISC-V platform/DT code from the core
framework patch.
- Moved all rvtrace driver sources from drivers/hwtracing/rvtrace/ to
drivers/hwtracing/gtrace/, with single Kconfig, Makefile using
- Made the perf driver (previously rvtrace-perf.c) architecture-neutral
and moved it into the gtrace framework as gtrace-perf.c; PMU name
changed from "rvtrace" to "gtrace"
- Added new PATCH8 "perf: Add gtrace AUX buffer trace format type" to
define PERF_AUX_FLAG_GTRACE_FORMAT_ETRACE which is used in later patches.
- copyto_auxbuf() and gtrace_path_copyto_auxbuf() now take an output
parameter so the AUX data format can be reported by the sink
driver by setting PERF_AUX_FLAG_GTRACE_FORMAT_ETRACE.
- Renamed the generic side of the perf tools support from rvtrace* to
gtrace*. perf.data ABI is unchanged.
- Fixed gtrace ramsink start/stop to call gtrace_enable_component()/
gtrace_disable_component() to remove duplication with code in
gtrace-core.c
- Updated the MAINTAINERS patch to refer to gtrace framework instead of
rvtrace
Changes since v3:
- Rebased on Linux-7.1-rc1
- Use kzalloc_obj() in-place of kzalloc() in PATCH2, PATCH3 and PATCH9
- Improved PATCH7 to save the previous WP value
Changes since v2:
- Rebased on Linux-7.0-rc1
- Addressed Rob's comments on DT bindings in PATCH1
- Addressed ref-count related issues in rvtrace_of_parse_outconns()
of PATCH2
- Made RVtrace framework more generic by avoiding implicit access
to component registers in PATCH2
- More improvements in trRamStart/Limit/WP programming and other
improvments in PATCH7
- Removed RVTRACE_BUF_LEN from PATCH9
- Removed redundant page_size from PATCH10
- Renamed found_etm in PATCH10
- Removed rvtrace_recording_init() declaration from header in PATCH11
Changes since v1:
- Rebased on Linux-6.18-rc3
- Addressed Rob's comments in dt-bindings added by PATCH1
- Get reference of conn->dest_fwnode and add missing break in
rvtrace_of_parse_outconns() of rvtrace-platform drivers added
by PATCH2
- Added new inline function rvtrace_comp_is_empty() in PATCH2
and used it in rvtrace_encoder_stop() added by PATCH5
- Fixed trRamWPLow usage in PATCH7
- Determine RAM sink buffer size based on component implementation
ID and reduce default RAM sink buffer size to 1MB
- Add new PATCH8 to enable DMA_RESTRICTED_POOL in RISC-V defconfig
so that implementations with RAM sink address restrictions can
be handled.
Anup Patel (7):
dt-bindings: Add RISC-V trace component bindings
hwtracing: gtrace: Initial implementation of gtrace framework
gtrace: Add RISC-V platform driver for the gtrace framework
gtrace: Add functions to create/destroy a trace component path
gtrace: Add function to copy into perf AUX buffer
riscv: Enable DMA_RESTRICTED_POOL in defconfig
MAINTAINERS: Add entry for RISC-V trace framework
Mayuresh Chitale (7):
gtrace: Add functions to start/stop tracing on a component path
gtrace: Add RISC-V Trace encoder driver
perf: Add gtrace AUX buffer trace format type
gtrace: Add RISC-V Trace ramsink driver
gtrace: Add perf driver for tracing using perf tool
perf tools: Add RISC-V trace PMU record capabilities
perf tools: Initial support for gtrace decoder
.../bindings/riscv/riscv,trace-component.yaml | 120 +++
MAINTAINERS | 11 +
arch/riscv/configs/defconfig | 1 +
drivers/Makefile | 1 +
drivers/hwtracing/Kconfig | 2 +
drivers/hwtracing/gtrace/Kconfig | 59 ++
drivers/hwtracing/gtrace/Makefile | 9 +
drivers/hwtracing/gtrace/gtrace-core.c | 858 ++++++++++++++++++
drivers/hwtracing/gtrace/gtrace-of.c | 126 +++
drivers/hwtracing/gtrace/gtrace-perf.c | 390 ++++++++
drivers/hwtracing/gtrace/rvtrace-encoder.c | 236 +++++
drivers/hwtracing/gtrace/rvtrace-platform.c | 186 ++++
drivers/hwtracing/gtrace/rvtrace-ramsink.c | 357 ++++++++
drivers/hwtracing/gtrace/rvtrace.h | 121 +++
include/linux/gtrace.h | 332 +++++++
include/uapi/linux/perf_event.h | 5 +
tools/include/uapi/linux/perf_event.h | 5 +
tools/perf/arch/riscv/util/Build | 1 +
tools/perf/arch/riscv/util/auxtrace.c | 221 +++++
tools/perf/util/Build | 1 +
tools/perf/util/auxtrace.c | 4 +
tools/perf/util/auxtrace.h | 1 +
tools/perf/util/gtrace-decoder.c | 95 ++
tools/perf/util/gtrace.h | 18 +
24 files changed, 3160 insertions(+)
create mode 100644 Documentation/devicetree/bindings/riscv/riscv,trace-component.yaml
create mode 100644 drivers/hwtracing/gtrace/Kconfig
create mode 100644 drivers/hwtracing/gtrace/Makefile
create mode 100644 drivers/hwtracing/gtrace/gtrace-core.c
create mode 100644 drivers/hwtracing/gtrace/gtrace-of.c
create mode 100644 drivers/hwtracing/gtrace/gtrace-perf.c
create mode 100644 drivers/hwtracing/gtrace/rvtrace-encoder.c
create mode 100644 drivers/hwtracing/gtrace/rvtrace-platform.c
create mode 100644 drivers/hwtracing/gtrace/rvtrace-ramsink.c
create mode 100644 drivers/hwtracing/gtrace/rvtrace.h
create mode 100644 include/linux/gtrace.h
create mode 100644 tools/perf/arch/riscv/util/auxtrace.c
create mode 100644 tools/perf/util/gtrace-decoder.c
create mode 100644 tools/perf/util/gtrace.h
base-commit: 551c722f40809618230001baccf219193e22fc5a
--
2.43.0
^ permalink raw reply [flat|nested] 25+ messages in thread
* [PATCH v6 01/14] dt-bindings: Add RISC-V trace component bindings
2026-10-01 5:22 [PATCH v6 00/14] Linux RISC-V trace framework and drivers Mayuresh Chitale
@ 2026-10-01 5:22 ` Mayuresh Chitale
2026-10-01 5:33 ` sashiko-bot
2026-10-01 5:22 ` [PATCH v6 02/14] hwtracing: gtrace: Initial implementation of gtrace framework Mayuresh Chitale
` (12 subsequent siblings)
13 siblings, 1 reply; 25+ messages in thread
From: Mayuresh Chitale @ 2026-10-01 5:22 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Palmer Dabbelt,
Paul Walmsley, Greg KH, Alexander Shishkin, Ian Rogers
Cc: Anup Patel, Alexandre Ghiti, Peter Zijlstra, Ingo Molnar,
Namhyung Kim, Mark Rutland, Jiri Olsa, Adrian Hunter,
Mayuresh Chitale, Anup Patel, Atish Patra, Andrew Jones,
Sunil V L, linux-riscv, devicetree, linux-kernel,
Mayuresh Chitale, Zane Leung
From: Anup Patel <anup.patel@oss.qualcomm.com>
Add device tree bindings for the memory mapped RISC-V trace components
which support both the RISC-V efficient trace (E-trace) protocol and
the RISC-V Nexus-based trace (N-trace) protocol.
The RISC-V trace components are defined by the RISC-V trace control
interface specification.
Signed-off-by: Anup Patel <anup.patel@oss.qualcomm.com>
Reviewed-by: Rob Herring (Arm) <robh@kernel.org>
Signed-off-by: Mayuresh Chitale <mayuresh.chitale@oss.qualcomm.com>
---
.../bindings/riscv/riscv,trace-component.yaml | 120 ++++++++++++++++++
1 file changed, 120 insertions(+)
create mode 100644 Documentation/devicetree/bindings/riscv/riscv,trace-component.yaml
diff --git a/Documentation/devicetree/bindings/riscv/riscv,trace-component.yaml b/Documentation/devicetree/bindings/riscv/riscv,trace-component.yaml
new file mode 100644
index 000000000000..bb519bc4a163
--- /dev/null
+++ b/Documentation/devicetree/bindings/riscv/riscv,trace-component.yaml
@@ -0,0 +1,120 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/riscv/riscv,trace-component.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: RISC-V Trace Component
+
+maintainers:
+ - Anup Patel <anup@brainfault.org>
+
+description:
+ The RISC-V trace control interface specification standard memory mapped
+ components (or devices) which support both the RISC-V efficient trace
+ (E-trace) protocol and the RISC-V Nexus-based trace (N-trace) protocol.
+ The RISC-V trace components have implementation specific directed acyclic
+ graph style interdependency where output of one component serves as input
+ to another component and certain components (such as funnel) can take inputs
+ from multiple components. The type and version of a RISC-V trace component
+ can be discovered from it's IMPL memory mapped register hence component
+ specific compatible strings are not needed.
+
+properties:
+ compatible:
+ items:
+ - enum:
+ - qemu,trace-component
+ - const: riscv,trace-component
+
+ reg:
+ maxItems: 1
+
+ cpus:
+ maxItems: 1
+ description:
+ phandle to the cpu to which the RISC-V trace component is bound.
+
+ in-ports:
+ $ref: /schemas/graph.yaml#/properties/ports
+ patternProperties:
+ '^port(@[0-7])?$':
+ description: Input connections from RISC-V trace component
+ $ref: /schemas/graph.yaml#/properties/port
+
+ out-ports:
+ $ref: /schemas/graph.yaml#/properties/ports
+ patternProperties:
+ '^port(@[0-7])?$':
+ description: Output connections from RISC-V trace component
+ $ref: /schemas/graph.yaml#/properties/port
+
+required:
+ - compatible
+ - reg
+
+anyOf:
+ - required: [ in-ports ]
+ - required: [ out-ports ]
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ // Example 1 (Per-hart encoder and ramsink components):
+
+ trace@c000000 {
+ compatible = "qemu,trace-component", "riscv,trace-component";
+ reg = <0xc000000 0x1000>;
+ cpus = <&CPU0>;
+
+ out-ports {
+ port {
+ CPU0_ENCODER_OUTPUT: endpoint {
+ remote-endpoint = <&CPU0_RAMSINK_INPUT>;
+ };
+ };
+ };
+ };
+
+ trace@c001000 {
+ compatible = "qemu,trace-component", "riscv,trace-component";
+ reg = <0xc001000 0x1000>;
+ cpus = <&CPU0>;
+
+ in-ports {
+ port {
+ CPU0_RAMSINK_INPUT: endpoint {
+ };
+ };
+ };
+ };
+
+ trace@c002000 {
+ compatible = "qemu,trace-component", "riscv,trace-component";
+ reg = <0xc002000 0x1000>;
+ cpus = <&CPU1>;
+
+ out-ports {
+ port {
+ CPU1_ENCODER_OUTPUT: endpoint {
+ remote-endpoint = <&CPU1_RAMSINK_INPUT>;
+ };
+ };
+ };
+ };
+
+ trace@c003000 {
+ compatible = "qemu,trace-component", "riscv,trace-component";
+ reg = <0xc003000 0x1000>;
+ cpus = <&CPU1>;
+
+ in-ports {
+ port {
+ CPU1_RAMSINK_INPUT: endpoint {
+ };
+ };
+ };
+ };
+
+...
--
2.43.0
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [PATCH v6 02/14] hwtracing: gtrace: Initial implementation of gtrace framework
2026-10-01 5:22 [PATCH v6 00/14] Linux RISC-V trace framework and drivers Mayuresh Chitale
2026-10-01 5:22 ` [PATCH v6 01/14] dt-bindings: Add RISC-V trace component bindings Mayuresh Chitale
@ 2026-10-01 5:22 ` Mayuresh Chitale
2026-10-01 5:37 ` sashiko-bot
2026-10-01 5:22 ` [PATCH v6 03/14] gtrace: Add RISC-V platform driver for the " Mayuresh Chitale
` (11 subsequent siblings)
13 siblings, 1 reply; 25+ messages in thread
From: Mayuresh Chitale @ 2026-10-01 5:22 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Palmer Dabbelt,
Paul Walmsley, Greg KH, Alexander Shishkin, Ian Rogers
Cc: Anup Patel, Alexandre Ghiti, Peter Zijlstra, Ingo Molnar,
Namhyung Kim, Mark Rutland, Jiri Olsa, Adrian Hunter,
Mayuresh Chitale, Anup Patel, Atish Patra, Andrew Jones,
Sunil V L, linux-riscv, devicetree, linux-kernel,
Mayuresh Chitale, Zane Leung
From: Anup Patel <anup.patel@oss.qualcomm.com>
Implement a generic and architecture neutral trace framework (gtrace)
in which trace components are organized in a graph-like topology. The
framework provides a bus for the trace components along with helpers to
register components, build the topology and access the hardware registers.
Signed-off-by: Anup Patel <anup.patel@oss.qualcomm.com>
Co-developed-by: Mayuresh Chitale <mayuresh.chitale@oss.qualcomm.com>
Signed-off-by: Mayuresh Chitale <mayuresh.chitale@oss.qualcomm.com>
---
drivers/Makefile | 1 +
drivers/hwtracing/Kconfig | 2 +
drivers/hwtracing/gtrace/Kconfig | 15 +
drivers/hwtracing/gtrace/Makefile | 4 +
drivers/hwtracing/gtrace/gtrace-core.c | 459 +++++++++++++++++++++++++
include/linux/gtrace.h | 271 +++++++++++++++
6 files changed, 752 insertions(+)
create mode 100644 drivers/hwtracing/gtrace/Kconfig
create mode 100644 drivers/hwtracing/gtrace/Makefile
create mode 100644 drivers/hwtracing/gtrace/gtrace-core.c
create mode 100644 include/linux/gtrace.h
diff --git a/drivers/Makefile b/drivers/Makefile
index 0841ea851847..e018cc915f62 100644
--- a/drivers/Makefile
+++ b/drivers/Makefile
@@ -178,6 +178,7 @@ obj-$(CONFIG_CORESIGHT) += hwtracing/coresight/
obj-y += hwtracing/intel_th/
obj-$(CONFIG_STM) += hwtracing/stm/
obj-$(CONFIG_HISI_PTT) += hwtracing/ptt/
+obj-$(CONFIG_GTRACE) += hwtracing/gtrace/
obj-y += android/
obj-$(CONFIG_NVMEM) += nvmem/
obj-$(CONFIG_FPGA) += fpga/
diff --git a/drivers/hwtracing/Kconfig b/drivers/hwtracing/Kconfig
index 911ee977103c..ef53f5c8429f 100644
--- a/drivers/hwtracing/Kconfig
+++ b/drivers/hwtracing/Kconfig
@@ -7,4 +7,6 @@ source "drivers/hwtracing/intel_th/Kconfig"
source "drivers/hwtracing/ptt/Kconfig"
+source "drivers/hwtracing/gtrace/Kconfig"
+
endmenu
diff --git a/drivers/hwtracing/gtrace/Kconfig b/drivers/hwtracing/gtrace/Kconfig
new file mode 100644
index 000000000000..bcc3b4169a76
--- /dev/null
+++ b/drivers/hwtracing/gtrace/Kconfig
@@ -0,0 +1,15 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+menuconfig GTRACE
+ tristate "Generic Hardware Trace Support"
+ depends on OF
+ help
+ This framework provides an architecture-neutral kernel interface
+ for hardware trace drivers. It builds a topological view of the
+ trace components and configures the correct series of components
+ when trace is enabled. It is intended to be shared across RISC-V
+ and ARM trace components so that a single trace path may span
+ components of different architectures.
+
+ To compile this driver as a module, choose M here: the module
+ will be called gtrace.
diff --git a/drivers/hwtracing/gtrace/Makefile b/drivers/hwtracing/gtrace/Makefile
new file mode 100644
index 000000000000..3f90fb99c514
--- /dev/null
+++ b/drivers/hwtracing/gtrace/Makefile
@@ -0,0 +1,4 @@
+# SPDX-License-Identifier: GPL-2.0
+
+obj-$(CONFIG_GTRACE) += gtrace.o
+gtrace-y := gtrace-core.o
diff --git a/drivers/hwtracing/gtrace/gtrace-core.c b/drivers/hwtracing/gtrace/gtrace-core.c
new file mode 100644
index 000000000000..d374c679a649
--- /dev/null
+++ b/drivers/hwtracing/gtrace/gtrace-core.c
@@ -0,0 +1,459 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2026 Qualcomm Technologies, Inc.
+ *
+ */
+
+#include <linux/cpumask.h>
+#include <linux/export.h>
+#include <linux/iopoll.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/percpu.h>
+#include <linux/xarray.h>
+#include <linux/gtrace.h>
+
+#define GTRACE_POLL_TIMEOUT_US_DEFAULT 10
+
+/* Mutex to serialize component registration/unregistration */
+static DEFINE_MUTEX(gtrace_mutex);
+
+/* Per-CPU source instances */
+static DEFINE_PER_CPU(struct gtrace_component *, gtrace_cpu_source_comp);
+
+/* gtrace comp specific data, to be used by core functions only */
+struct gtrace_comp_priv {
+ struct gtrace_component comp;
+ u32 type_idx;
+ bool ready;
+};
+
+#define to_gtrace_comp_priv(__comp) \
+ container_of_const(__comp, struct gtrace_comp_priv, comp)
+
+/* Component type based id generator */
+struct gtrace_type_idx {
+ /* Lock to protect the type ID generator */
+ struct mutex lock;
+ struct xarray xa;
+};
+
+/* Array of component type based id generator */
+static struct gtrace_type_idx gtrace_type_idx_array[GTRACE_COMPONENT_TYPE_MAX];
+
+static int gtrace_alloc_type_idx(struct gtrace_component *comp)
+{
+ struct gtrace_comp_priv *cpriv = to_gtrace_comp_priv(comp);
+ struct gtrace_type_idx *gidx;
+ u32 idx;
+ int ret;
+
+ if (comp->id.type >= GTRACE_COMPONENT_TYPE_MAX)
+ return -EINVAL;
+
+ gidx = >race_type_idx_array[comp->id.type];
+ mutex_lock(&gidx->lock);
+ ret = xa_alloc(&gidx->xa, &idx, comp, xa_limit_32b, GFP_KERNEL);
+ mutex_unlock(&gidx->lock);
+ if (ret)
+ return ret;
+
+ cpriv->type_idx = idx;
+ return 0;
+}
+
+static void gtrace_free_type_idx(struct gtrace_component *comp)
+{
+ struct gtrace_comp_priv *cpriv = to_gtrace_comp_priv(comp);
+ struct gtrace_type_idx *gidx;
+
+ if (comp->id.type >= GTRACE_COMPONENT_TYPE_MAX)
+ return;
+
+ gidx = >race_type_idx_array[comp->id.type];
+ mutex_lock(&gidx->lock);
+ xa_erase(&gidx->xa, cpriv->type_idx);
+ mutex_unlock(&gidx->lock);
+}
+
+static void __init gtrace_init_type_idx(void)
+{
+ struct gtrace_type_idx *gidx;
+ int i;
+
+ for (i = 0; i < GTRACE_COMPONENT_TYPE_MAX; i++) {
+ gidx = >race_type_idx_array[i];
+ mutex_init(&gidx->lock);
+ xa_init_flags(&gidx->xa, XA_FLAGS_ALLOC);
+ }
+}
+
+const struct gtrace_component_id *gtrace_match_id(struct gtrace_component *comp,
+ const struct gtrace_component_id *ids)
+{
+ const struct gtrace_component_id *id;
+
+ for (id = ids; id->version; id++) {
+ if (comp->id.type != id->type)
+ continue;
+
+ return id;
+ }
+
+ return NULL;
+}
+EXPORT_SYMBOL_GPL(gtrace_match_id);
+
+static int gtrace_match_device(struct device *dev, const struct device_driver *drv)
+{
+ const struct gtrace_driver *gtdrv = to_gtrace_driver(drv);
+ struct gtrace_component *comp = to_gtrace_component(dev);
+
+ return gtrace_match_id(comp, gtdrv->id_table) ? 1 : 0;
+}
+
+static int gtrace_probe(struct device *dev)
+{
+ const struct gtrace_driver *gtdrv = to_gtrace_driver(dev->driver);
+ struct gtrace_component *comp = to_gtrace_component(dev);
+ struct gtrace_comp_priv *cpriv = to_gtrace_comp_priv(comp);
+ int ret = 0;
+
+ if (gtdrv->probe)
+ ret = gtdrv->probe(comp);
+
+ if (!ret)
+ cpriv->ready = true;
+
+ return ret;
+}
+
+static void gtrace_remove(struct device *dev)
+{
+ const struct gtrace_driver *gtdrv = to_gtrace_driver(dev->driver);
+ struct gtrace_component *comp = to_gtrace_component(dev);
+ struct gtrace_comp_priv *cpriv = to_gtrace_comp_priv(comp);
+
+ cpriv->ready = false;
+ if (gtdrv->remove)
+ gtdrv->remove(comp);
+}
+
+static const struct bus_type gtrace_bustype = {
+ .name = "gtrace",
+ .match = gtrace_match_device,
+ .probe = gtrace_probe,
+ .remove = gtrace_remove,
+};
+
+struct gtrace_fwnode_match_data {
+ struct fwnode_handle *fwnode;
+ struct gtrace_component *match;
+};
+
+static int gtrace_match_fwnode(struct device *dev, void *data)
+{
+ struct gtrace_component *comp = to_gtrace_component(dev);
+ struct gtrace_fwnode_match_data *d = data;
+
+ if (device_match_fwnode(&comp->dev, d->fwnode)) {
+ d->match = comp;
+ return 1;
+ }
+
+ return 0;
+}
+
+struct gtrace_component *gtrace_find_by_fwnode(struct fwnode_handle *fwnode)
+{
+ struct gtrace_fwnode_match_data d = { .fwnode = fwnode, .match = NULL };
+ int ret;
+
+ ret = bus_for_each_dev(>race_bustype, NULL, &d, gtrace_match_fwnode);
+ if (ret < 0)
+ return ERR_PTR(ret);
+
+ return d.match;
+}
+EXPORT_SYMBOL_GPL(gtrace_find_by_fwnode);
+
+int gtrace_poll_bit(struct gtrace_platform_data *pdata, int offset,
+ int bit, int bitval, int timeout)
+{
+ u32 val;
+
+ if (timeout <= 0)
+ timeout = GTRACE_POLL_TIMEOUT_US_DEFAULT;
+
+ return read_poll_timeout_atomic(gtrace_read32, val,
+ ((val >> bit) & 0x1) == bitval,
+ 1, timeout, false, pdata, offset);
+}
+EXPORT_SYMBOL_GPL(gtrace_poll_bit);
+
+int gtrace_enable_component(struct gtrace_component *comp)
+{
+ const struct gtrace_hw_ops *ops = comp->pdata->hw_ops;
+
+ if (!ops || !ops->enable)
+ return -EOPNOTSUPP;
+
+ return ops->enable(comp->pdata);
+}
+EXPORT_SYMBOL_GPL(gtrace_enable_component);
+
+int gtrace_disable_component(struct gtrace_component *comp)
+{
+ const struct gtrace_hw_ops *ops = comp->pdata->hw_ops;
+
+ if (!ops || !ops->disable)
+ return -EOPNOTSUPP;
+
+ return ops->disable(comp->pdata);
+}
+EXPORT_SYMBOL_GPL(gtrace_disable_component);
+
+int gtrace_reset_component(struct gtrace_component *comp)
+{
+ const struct gtrace_hw_ops *ops = comp->pdata->hw_ops;
+
+ if (!ops || !ops->reset)
+ return -EOPNOTSUPP;
+
+ return ops->reset(comp->pdata);
+}
+EXPORT_SYMBOL_GPL(gtrace_reset_component);
+
+struct gtrace_component *gtrace_cpu_source(unsigned int cpu)
+{
+ if (!cpu_present(cpu))
+ return NULL;
+
+ return per_cpu(gtrace_cpu_source_comp, cpu);
+}
+EXPORT_SYMBOL_GPL(gtrace_cpu_source);
+
+static int gtrace_cleanup_inconn(struct device *dev, void *data)
+{
+ struct gtrace_component *comp = to_gtrace_component(dev);
+ struct gtrace_platform_data *pdata = comp->pdata;
+ struct gtrace_connection *conn = data;
+ int i;
+
+ if (device_match_fwnode(&comp->dev, conn->dest_fwnode)) {
+ for (i = 0; i < pdata->nr_inconns; i++) {
+ if (pdata->inconns[i] != conn)
+ continue;
+ pdata->inconns[i] = NULL;
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+static void gtrace_cleanup_inconns_from_outconns(struct gtrace_component *comp)
+{
+ struct gtrace_platform_data *pdata = comp->pdata;
+ struct gtrace_connection *conn;
+ int i;
+
+ lockdep_assert_held(>race_mutex);
+
+ for (i = 0; i < pdata->nr_outconns; i++) {
+ conn = pdata->outconns[i];
+ bus_for_each_dev(>race_bustype, NULL, conn, gtrace_cleanup_inconn);
+ }
+}
+
+static int gtrace_setup_inconn(struct device *dev, void *data)
+{
+ struct gtrace_component *comp = to_gtrace_component(dev);
+ struct gtrace_platform_data *pdata = comp->pdata;
+ struct gtrace_connection *conn = data;
+ int i;
+
+ if (device_match_fwnode(&comp->dev, conn->dest_fwnode)) {
+ for (i = 0; i < pdata->nr_inconns; i++) {
+ if (pdata->inconns[i])
+ continue;
+ pdata->inconns[i] = conn;
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+static int gtrace_setup_inconns_from_outconns(struct gtrace_component *comp)
+{
+ struct gtrace_platform_data *pdata = comp->pdata;
+ struct gtrace_connection *conn;
+ int i, ret;
+
+ lockdep_assert_held(>race_mutex);
+
+ for (i = 0; i < pdata->nr_outconns; i++) {
+ conn = pdata->outconns[i];
+ ret = bus_for_each_dev(>race_bustype, NULL, conn, gtrace_setup_inconn);
+ if (ret < 0) {
+ gtrace_cleanup_inconns_from_outconns(comp);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static void gtrace_component_release(struct device *dev)
+{
+ struct gtrace_component *comp = to_gtrace_component(dev);
+ struct gtrace_comp_priv *cpriv = to_gtrace_comp_priv(comp);
+
+ fwnode_handle_put(comp->dev.fwnode);
+ gtrace_free_type_idx(comp);
+ kfree(cpriv);
+}
+
+struct gtrace_component *gtrace_register_component(struct gtrace_component_id *id,
+ const char *name,
+ struct gtrace_platform_data *pdata)
+{
+ struct gtrace_connection *conn;
+ struct gtrace_component *comp;
+ struct gtrace_comp_priv *cpriv;
+ int i, ret = 0;
+
+ if (!id || id->type >= GTRACE_COMPONENT_TYPE_MAX) {
+ ret = -EINVAL;
+ goto err_out;
+ }
+
+ if (!pdata || !pdata->dev) {
+ ret = -EINVAL;
+ goto err_out;
+ }
+
+ for (i = 0; i < pdata->nr_inconns; i++) {
+ if (pdata->inconns[i]) {
+ ret = -EINVAL;
+ goto err_out;
+ }
+ }
+
+ for (i = 0; i < pdata->nr_outconns; i++) {
+ conn = pdata->outconns[i];
+ if (!conn || conn->src_port < 0 || conn->src_comp ||
+ !device_match_fwnode(pdata->dev, conn->src_fwnode) ||
+ conn->dest_port < 0 || !conn->dest_fwnode || !conn->dest_comp) {
+ ret = -EINVAL;
+ goto err_out;
+ }
+ }
+
+ if (pdata->bound_cpu >= 0 && !cpu_present(pdata->bound_cpu)) {
+ ret = -EINVAL;
+ goto err_out;
+ }
+
+ cpriv = kzalloc(sizeof(*cpriv), GFP_KERNEL);
+ if (!cpriv) {
+ ret = -ENOMEM;
+ goto err_out;
+ }
+ comp = &cpriv->comp;
+ comp->pdata = pdata;
+ comp->id = *id;
+ ret = gtrace_alloc_type_idx(comp);
+ if (ret) {
+ kfree(cpriv);
+ goto err_out;
+ }
+
+ comp->dev.parent = pdata->dev;
+ comp->dev.coherent_dma_mask = pdata->dev->coherent_dma_mask;
+ comp->dev.release = gtrace_component_release;
+ comp->dev.bus = >race_bustype;
+ comp->dev.fwnode = fwnode_handle_get(dev_fwnode(pdata->dev));
+ dev_set_name(&comp->dev, "%s-%u", name ? name : "comp", cpriv->type_idx);
+
+ mutex_lock(>race_mutex);
+
+ ret = device_register(&comp->dev);
+ if (ret) {
+ put_device(&comp->dev);
+ goto err_out_unlock;
+ }
+
+ for (i = 0; i < pdata->nr_outconns; i++) {
+ conn = pdata->outconns[i];
+ conn->src_comp = comp;
+ }
+
+ ret = gtrace_setup_inconns_from_outconns(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;
+ }
+
+ mutex_unlock(>race_mutex);
+
+ return comp;
+
+err_out_unlock:
+ mutex_unlock(>race_mutex);
+err_out:
+ return ERR_PTR(ret);
+}
+EXPORT_SYMBOL_GPL(gtrace_register_component);
+
+void gtrace_unregister_component(struct gtrace_component *comp)
+{
+ struct gtrace_component *c;
+
+ mutex_lock(>race_mutex);
+
+ if (comp->pdata->bound_cpu >= 0) {
+ c = per_cpu(gtrace_cpu_source_comp, comp->pdata->bound_cpu);
+ per_cpu(gtrace_cpu_source_comp, comp->pdata->bound_cpu) = NULL;
+ gtrace_put_component(c);
+ }
+
+ gtrace_cleanup_inconns_from_outconns(comp);
+ device_unregister(&comp->dev);
+
+ mutex_unlock(>race_mutex);
+}
+EXPORT_SYMBOL_GPL(gtrace_unregister_component);
+
+int __gtrace_register_driver(struct module *owner, struct gtrace_driver *gtdrv)
+{
+ gtdrv->driver.owner = owner;
+ gtdrv->driver.bus = >race_bustype;
+
+ return driver_register(>drv->driver);
+}
+EXPORT_SYMBOL_GPL(__gtrace_register_driver);
+
+static int __init gtrace_init(void)
+{
+ gtrace_init_type_idx();
+
+ return bus_register(>race_bustype);
+}
+
+static void __exit gtrace_exit(void)
+{
+ bus_unregister(>race_bustype);
+}
+
+subsys_initcall(gtrace_init);
+module_exit(gtrace_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Generic hardware trace (gtrace) framework core");
diff --git a/include/linux/gtrace.h b/include/linux/gtrace.h
new file mode 100644
index 000000000000..3ae9fcf5bcfa
--- /dev/null
+++ b/include/linux/gtrace.h
@@ -0,0 +1,271 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2026 Qualcomm Technologies, Inc.
+ */
+
+#ifndef __LINUX_GTRACE_H__
+#define __LINUX_GTRACE_H__
+
+#include <linux/device.h>
+#include <linux/io.h>
+#include <linux/limits.h>
+#include <linux/list.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/types.h>
+
+/*
+ * Global list of trace component types across all architectures. Each backend
+ * uses the entries relevant to it.
+ */
+enum gtrace_component_type {
+ GTRACE_RVTRACE_ENCODER,
+ GTRACE_RVTRACE_FUNNEL,
+ GTRACE_RVTRACE_RAMSINK,
+ GTRACE_RVTRACE_PIBSINK,
+ GTRACE_RVTRACE_ATBBRIDGE,
+ GTRACE_COMPONENT_TYPE_MAX
+};
+
+/* Supported usage modes for trace components */
+enum gtrace_component_mode {
+ GTRACE_COMPONENT_MODE_PERF,
+ GTRACE_COMPONENT_MODE_MAX
+};
+
+/* Supported trace protocol formats */
+enum gtrace_format {
+ GTRACE_FORMAT_ETRACE,
+ GTRACE_FORMAT_NTRACE,
+};
+
+/**
+ * struct gtrace_connection - Physical connection between two trace components.
+ * @src_port: A connection's source port number.
+ * @src_fwnode: Source component's fwnode handle.
+ * @src_comp: Source component's pointer.
+ * @dest_port: A connection's destination port number.
+ * @dest_fwnode: Destination component's fwnode handle.
+ * @dest_comp: Destination component's pointer.
+ */
+struct gtrace_connection {
+ int src_port;
+ struct fwnode_handle *src_fwnode;
+ int dest_port;
+ struct fwnode_handle *dest_fwnode;
+ struct gtrace_component *src_comp;
+ struct gtrace_component *dest_comp;
+};
+
+struct gtrace_platform_data;
+
+/**
+ * struct gtrace_hw_ops - Architecture-specific low level control of a component.
+ * @enable: Enable the component's hardware block.
+ * @disable: Disable the component's hardware block.
+ * @reset: Reset the component's hardware block.
+ *
+ * These operate on the component's platform data rather than a full
+ * gtrace_component so that they can be invoked before the component is
+ * registered with the gtrace core (e.g. to reset the hardware during probe).
+ *
+ * These abstract the register-level programming that differs between
+ * architectures (RISC-V Trace Control registers vs ARM CoreSight, etc.). The
+ * core never touches component registers directly; it invokes these ops.
+ */
+struct gtrace_hw_ops {
+ int (*enable)(struct gtrace_platform_data *pdata);
+ int (*disable)(struct gtrace_platform_data *pdata);
+ int (*reset)(struct gtrace_platform_data *pdata);
+};
+
+/**
+ * struct gtrace_platform_data - Platform-level data for a trace component
+ * discovered from DT or ACPI.
+ * @dev: Parent device.
+ * @impid: Opaque, architecture-specific implementation ID.
+ * @hw_ops: Architecture-specific low level control ops.
+ * @io_mem: Flag showing whether component registers are memory mapped.
+ * @base: If io_mem == true then base address of the mapped registers.
+ * @read: If io_mem == false then read register from the given offset.
+ * @write: If io_mem == false then write register to the given offset.
+ * @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.
+ * @outconns: Array of pointers to output connections.
+ */
+struct gtrace_platform_data {
+ struct device *dev;
+
+ u32 impid;
+
+ const struct gtrace_hw_ops *hw_ops;
+
+ bool io_mem;
+ union {
+ void __iomem *base;
+ struct {
+ u32 (*read)(struct gtrace_platform_data *pdata,
+ u32 offset, bool relaxed);
+ void (*write)(struct gtrace_platform_data *pdata,
+ u32 val, u32 offset, bool relaxed);
+ };
+ };
+
+ int bound_cpu;
+
+ /* 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.
+ */
+ unsigned int nr_inconns;
+ struct gtrace_connection **inconns;
+
+ /*
+ * Platform driver must fully populate pointer array with individual
+ * array elements pointing to actual output connections. The src_comp
+ * of each output connection is automatically updated at the time of
+ * registering component.
+ */
+ unsigned int nr_outconns;
+ struct gtrace_connection **outconns;
+};
+
+static inline u32 gtrace_read32(struct gtrace_platform_data *pdata, u32 offset)
+{
+ if (likely(pdata->io_mem))
+ return readl(pdata->base + offset);
+
+ return pdata->read(pdata, offset, false);
+}
+
+static inline u32 gtrace_relaxed_read32(struct gtrace_platform_data *pdata, u32 offset)
+{
+ if (likely(pdata->io_mem))
+ return readl_relaxed(pdata->base + offset);
+
+ return pdata->read(pdata, offset, true);
+}
+
+static inline void gtrace_write32(struct gtrace_platform_data *pdata, u32 val, u32 offset)
+{
+ if (likely(pdata->io_mem))
+ writel(val, pdata->base + offset);
+ else
+ pdata->write(pdata, val, offset, false);
+}
+
+static inline void gtrace_relaxed_write32(struct gtrace_platform_data *pdata,
+ u32 val, u32 offset)
+{
+ if (likely(pdata->io_mem))
+ writel_relaxed(val, pdata->base + offset);
+ else
+ pdata->write(pdata, val, offset, true);
+}
+
+static inline bool gtrace_is_source(struct gtrace_platform_data *pdata)
+{
+ return !pdata->nr_inconns ? true : false;
+}
+
+static inline bool gtrace_is_sink(struct gtrace_platform_data *pdata)
+{
+ return !pdata->nr_outconns ? true : false;
+}
+
+/**
+ * struct gtrace_component_id - Details to identify or match a trace component.
+ * @type: Component type from the global gtrace_component_type list.
+ * @version: Architecture-specific version (opaque to the core).
+ * @data: Data pointer for driver use.
+ */
+struct gtrace_component_id {
+ enum gtrace_component_type type;
+ u32 version;
+ void *data;
+};
+
+/**
+ * struct gtrace_component - Representation of a trace component.
+ * @pdata: Pointer to underlying platform data.
+ * @id: Details to match the component.
+ * @dev: Device instance.
+ */
+struct gtrace_component {
+ struct gtrace_platform_data *pdata;
+ struct gtrace_component_id id;
+ struct device dev;
+};
+
+#define to_gtrace_component(__dev) container_of_const(__dev, struct gtrace_component, dev)
+
+static inline void gtrace_get_component(struct gtrace_component *comp)
+{
+ get_device(&comp->dev);
+}
+
+static inline void gtrace_put_component(struct gtrace_component *comp)
+{
+ put_device(&comp->dev);
+}
+
+const struct gtrace_component_id *gtrace_match_id(struct gtrace_component *comp,
+ const struct gtrace_component_id *ids);
+struct gtrace_component *gtrace_find_by_fwnode(struct fwnode_handle *fwnode);
+
+int gtrace_poll_bit(struct gtrace_platform_data *pdata, int offset,
+ int bit, int bitval, int timeout);
+int gtrace_enable_component(struct gtrace_component *comp);
+int gtrace_disable_component(struct gtrace_component *comp);
+int gtrace_reset_component(struct gtrace_component *comp);
+
+struct gtrace_component *gtrace_cpu_source(unsigned int cpu);
+
+struct gtrace_component *gtrace_register_component(struct gtrace_component_id *id,
+ const char *name,
+ struct gtrace_platform_data *pdata);
+void gtrace_unregister_component(struct gtrace_component *comp);
+
+/**
+ * struct gtrace_driver - Representation of a trace driver.
+ * @id_table: Table to match components handled by the driver.
+ * @probe: Driver probe() function.
+ * @remove: Driver remove() function.
+ * @get_trace_id: Get/allocate a trace ID.
+ * @put_trace_id: Put/free a trace ID.
+ * @driver: Device driver instance.
+ */
+struct gtrace_driver {
+ const struct gtrace_component_id *id_table;
+ int (*probe)(struct gtrace_component *comp);
+ void (*remove)(struct gtrace_component *comp);
+ int (*get_trace_id)(struct gtrace_component *comp,
+ enum gtrace_component_mode mode);
+ void (*put_trace_id)(struct gtrace_component *comp,
+ enum gtrace_component_mode mode,
+ u32 trace_id);
+ struct device_driver driver;
+};
+
+#define to_gtrace_driver(__drv) \
+ ((__drv) ? container_of_const((__drv), struct gtrace_driver, driver) : NULL)
+
+int __gtrace_register_driver(struct module *owner, struct gtrace_driver *gtdrv);
+#define gtrace_register_driver(driver) __gtrace_register_driver(THIS_MODULE, driver)
+static inline void gtrace_unregister_driver(struct gtrace_driver *gtdrv)
+{
+ if (gtdrv)
+ driver_unregister(>drv->driver);
+}
+
+#endif /* __LINUX_GTRACE_H__ */
--
2.43.0
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [PATCH v6 03/14] gtrace: Add RISC-V platform driver for the gtrace framework
2026-10-01 5:22 [PATCH v6 00/14] Linux RISC-V trace framework and drivers Mayuresh Chitale
2026-10-01 5:22 ` [PATCH v6 01/14] dt-bindings: Add RISC-V trace component bindings Mayuresh Chitale
2026-10-01 5:22 ` [PATCH v6 02/14] hwtracing: gtrace: Initial implementation of gtrace framework Mayuresh Chitale
@ 2026-10-01 5:22 ` Mayuresh Chitale
2026-10-01 5:36 ` sashiko-bot
2026-10-01 5:22 ` [PATCH v6 04/14] gtrace: Add functions to create/destroy a trace component path Mayuresh Chitale
` (10 subsequent siblings)
13 siblings, 1 reply; 25+ messages in thread
From: Mayuresh Chitale @ 2026-10-01 5:22 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Palmer Dabbelt,
Paul Walmsley, Greg KH, Alexander Shishkin, Ian Rogers
Cc: Anup Patel, Alexandre Ghiti, Peter Zijlstra, Ingo Molnar,
Namhyung Kim, Mark Rutland, Jiri Olsa, Adrian Hunter,
Mayuresh Chitale, Anup Patel, Atish Patra, Andrew Jones,
Sunil V L, linux-riscv, devicetree, linux-kernel,
Mayuresh Chitale, Zane Leung
From: Anup Patel <anup.patel@oss.qualcomm.com>
The RISC-V trace components defined by the RISC-V Trace Control Interface
Specification are memory mapped devices which are organized in a
graph-like topology and described in the device tree.
Add the RISC-V platform driver for the gtrace framework. The driver
parses the RISC-V component topology from the device tree, discovers the
type and version of each component from its IMPL register, provides the
RISC-V register level control ops and registers each component with the
gtrace core.
Signed-off-by: Anup Patel <anup.patel@oss.qualcomm.com>
Co-developed-by: Mayuresh Chitale <mayuresh.chitale@oss.qualcomm.com>
Signed-off-by: Mayuresh Chitale <mayuresh.chitale@oss.qualcomm.com>
---
drivers/hwtracing/gtrace/Kconfig | 15 ++
drivers/hwtracing/gtrace/Makefile | 3 +
drivers/hwtracing/gtrace/gtrace-of.c | 126 +++++++++++++
drivers/hwtracing/gtrace/rvtrace-platform.c | 186 ++++++++++++++++++++
drivers/hwtracing/gtrace/rvtrace.h | 121 +++++++++++++
include/linux/gtrace.h | 3 +
6 files changed, 454 insertions(+)
create mode 100644 drivers/hwtracing/gtrace/gtrace-of.c
create mode 100644 drivers/hwtracing/gtrace/rvtrace-platform.c
create mode 100644 drivers/hwtracing/gtrace/rvtrace.h
diff --git a/drivers/hwtracing/gtrace/Kconfig b/drivers/hwtracing/gtrace/Kconfig
index bcc3b4169a76..04986f216549 100644
--- a/drivers/hwtracing/gtrace/Kconfig
+++ b/drivers/hwtracing/gtrace/Kconfig
@@ -13,3 +13,18 @@ menuconfig GTRACE
To compile this driver as a module, choose M here: the module
will be called gtrace.
+
+config RVTRACE
+ tristate "RISC-V Trace Support"
+ depends on RISCV
+ depends on OF
+ depends on GTRACE
+ default RISCV
+ help
+ This provides the RISC-V backend for the generic hardware trace
+ (gtrace) framework. It parses the RISC-V trace component topology,
+ provides the RISC-V register-level control ops and registers each
+ component with the gtrace core.
+
+ To compile this driver as a module, choose M here: the module
+ will be called rvtrace.
diff --git a/drivers/hwtracing/gtrace/Makefile b/drivers/hwtracing/gtrace/Makefile
index 3f90fb99c514..20f67a78b8ab 100644
--- a/drivers/hwtracing/gtrace/Makefile
+++ b/drivers/hwtracing/gtrace/Makefile
@@ -2,3 +2,6 @@
obj-$(CONFIG_GTRACE) += gtrace.o
gtrace-y := gtrace-core.o
+gtrace-$(CONFIG_OF) += gtrace-of.o
+
+obj-$(CONFIG_RVTRACE) += rvtrace-platform.o
diff --git a/drivers/hwtracing/gtrace/gtrace-of.c b/drivers/hwtracing/gtrace/gtrace-of.c
new file mode 100644
index 000000000000..8f99cd35dcce
--- /dev/null
+++ b/drivers/hwtracing/gtrace/gtrace-of.c
@@ -0,0 +1,126 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2026 Qualcomm Technologies, Inc.
+ */
+
+#include <linux/device.h>
+#include <linux/export.h>
+#include <linux/gtrace.h>
+#include <linux/of.h>
+#include <linux/of_graph.h>
+#include <linux/property.h>
+
+/*
+ * Parse the "out-ports" graph of the component's device tree node and fill
+ * pdata->outconns.
+ * Return: 0 on success or when there are no output ports
+ * -EPROBE_DEFER if a destination is not registered yet
+ * negative error code otherwise.
+ */
+int gtrace_of_parse_outconns(struct gtrace_platform_data *pdata)
+{
+ struct device_node *parent, *ep_node, *rep_node, *rdev_node;
+ struct gtrace_connection *conn;
+ struct of_endpoint ep, rep;
+ int ret = 0, i = 0;
+
+ parent = of_get_child_by_name(dev_of_node(pdata->dev), "out-ports");
+ if (!parent)
+ return 0;
+
+ pdata->nr_outconns = of_graph_get_endpoint_count(parent);
+ pdata->outconns = devm_kcalloc(pdata->dev, pdata->nr_outconns,
+ sizeof(*pdata->outconns), GFP_KERNEL);
+ if (!pdata->outconns) {
+ ret = -ENOMEM;
+ goto done;
+ }
+
+ for_each_endpoint_of_node(parent, ep_node) {
+ conn = devm_kzalloc(pdata->dev, sizeof(*conn), GFP_KERNEL);
+ if (!conn) {
+ of_node_put(ep_node);
+ ret = -ENOMEM;
+ break;
+ }
+
+ ret = of_graph_parse_endpoint(ep_node, &ep);
+ if (ret) {
+ of_node_put(ep_node);
+ break;
+ }
+
+ rep_node = of_graph_get_remote_endpoint(ep_node);
+ if (!rep_node) {
+ ret = -ENODEV;
+ of_node_put(ep_node);
+ break;
+ }
+ rdev_node = of_graph_get_port_parent(rep_node);
+
+ ret = of_graph_parse_endpoint(rep_node, &rep);
+ if (ret) {
+ of_node_put(ep_node);
+ of_node_put(rep_node);
+ of_node_put(rdev_node);
+ break;
+ }
+
+ conn->src_port = ep.port;
+ conn->src_fwnode = dev_fwnode(pdata->dev);
+ /* The 'src_comp' is set by gtrace_register_component() */
+ conn->src_comp = NULL;
+ conn->dest_port = rep.port;
+ conn->dest_fwnode = of_fwnode_handle(rdev_node);
+ fwnode_handle_get(conn->dest_fwnode);
+ conn->dest_comp = gtrace_find_by_fwnode(conn->dest_fwnode);
+ if (!conn->dest_comp) {
+ ret = -EPROBE_DEFER;
+ of_node_put(ep_node);
+ of_node_put(rep_node);
+ of_node_put(rdev_node);
+ break;
+ }
+
+ pdata->outconns[i] = conn;
+ i++;
+ }
+
+done:
+ if (ret) {
+ for (i = 0; i < pdata->nr_outconns && pdata->outconns; i++) {
+ conn = pdata->outconns[i];
+ if (conn && conn->dest_fwnode)
+ fwnode_handle_put(conn->dest_fwnode);
+ }
+ }
+ of_node_put(parent);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(gtrace_of_parse_outconns);
+
+/*
+ * Parse the "out-ports" graph of the component's device tree node and allocate
+ * pdata->inconns.
+ *
+ * Return: 0 on success or when there are no input ports, -ENOMEM otherwise.
+ */
+int gtrace_of_parse_inconns(struct gtrace_platform_data *pdata)
+{
+ struct device_node *parent;
+ int ret = 0;
+
+ parent = of_get_child_by_name(dev_of_node(pdata->dev), "in-ports");
+ if (!parent)
+ return 0;
+
+ pdata->nr_inconns = of_graph_get_endpoint_count(parent);
+ pdata->inconns = devm_kcalloc(pdata->dev, pdata->nr_inconns,
+ sizeof(*pdata->inconns), GFP_KERNEL);
+ if (!pdata->inconns)
+ ret = -ENOMEM;
+
+ of_node_put(parent);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(gtrace_of_parse_inconns);
diff --git a/drivers/hwtracing/gtrace/rvtrace-platform.c b/drivers/hwtracing/gtrace/rvtrace-platform.c
new file mode 100644
index 000000000000..a5655e983e51
--- /dev/null
+++ b/drivers/hwtracing/gtrace/rvtrace-platform.c
@@ -0,0 +1,186 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2026 Qualcomm Technologies, Inc.
+ */
+
+#include <linux/device.h>
+#include <linux/gtrace.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/types.h>
+#include "rvtrace.h"
+
+static int rvtrace_hw_enable(struct gtrace_platform_data *pdata)
+{
+ u32 val;
+
+ val = gtrace_read32(pdata, RVTRACE_COMPONENT_CTRL_OFFSET);
+ val |= BIT(RVTRACE_COMPONENT_CTRL_ENABLE_SHIFT);
+ gtrace_write32(pdata, val, RVTRACE_COMPONENT_CTRL_OFFSET);
+ return gtrace_poll_bit(pdata, RVTRACE_COMPONENT_CTRL_OFFSET,
+ RVTRACE_COMPONENT_CTRL_ENABLE_SHIFT, 1,
+ pdata->control_poll_timeout_usecs);
+}
+
+static int rvtrace_hw_disable(struct gtrace_platform_data *pdata)
+{
+ u32 val;
+
+ val = gtrace_read32(pdata, RVTRACE_COMPONENT_CTRL_OFFSET);
+ val &= ~BIT(RVTRACE_COMPONENT_CTRL_ENABLE_SHIFT);
+ gtrace_write32(pdata, val, RVTRACE_COMPONENT_CTRL_OFFSET);
+ return gtrace_poll_bit(pdata, RVTRACE_COMPONENT_CTRL_OFFSET,
+ RVTRACE_COMPONENT_CTRL_ENABLE_SHIFT, 0,
+ pdata->control_poll_timeout_usecs);
+}
+
+static int rvtrace_hw_reset(struct gtrace_platform_data *pdata)
+{
+ int ret;
+
+ gtrace_write32(pdata, 0, RVTRACE_COMPONENT_CTRL_OFFSET);
+ ret = gtrace_poll_bit(pdata, RVTRACE_COMPONENT_CTRL_OFFSET,
+ RVTRACE_COMPONENT_CTRL_ACTIVE_SHIFT, 0,
+ pdata->control_poll_timeout_usecs);
+ if (ret)
+ return ret;
+
+ gtrace_write32(pdata, RVTRACE_COMPONENT_CTRL_ACTIVE_MASK,
+ RVTRACE_COMPONENT_CTRL_OFFSET);
+ return gtrace_poll_bit(pdata, RVTRACE_COMPONENT_CTRL_OFFSET,
+ RVTRACE_COMPONENT_CTRL_ACTIVE_SHIFT, 1,
+ pdata->control_poll_timeout_usecs);
+}
+
+const struct gtrace_hw_ops rvtrace_hw_ops = {
+ .enable = rvtrace_hw_enable,
+ .disable = rvtrace_hw_disable,
+ .reset = rvtrace_hw_reset,
+};
+
+static int rvtrace_platform_probe(struct platform_device *pdev)
+{
+ struct gtrace_platform_data *pdata;
+ struct device *dev = &pdev->dev;
+ struct gtrace_component_id id;
+ struct gtrace_component *comp;
+ u32 impl, type, major, minor;
+ struct device_node *node;
+ struct resource *res;
+ int gtype;
+ int ret;
+
+ pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
+ if (!pdata)
+ return -ENOMEM;
+ 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)
+ return -EINVAL;
+
+ pdata->io_mem = true;
+ pdata->base = devm_ioremap(&pdev->dev, res->start, resource_size(res));
+ if (!pdata->base)
+ return dev_err_probe(dev, -ENOMEM, "failed to ioremap %pR\n", res);
+
+ pdata->bound_cpu = -1;
+ node = of_parse_phandle(dev_of_node(dev), "cpus", 0);
+ if (node) {
+ ret = of_cpu_node_to_id(node);
+ of_node_put(node);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "failed to get CPU id for %pOF\n", node);
+ pdata->bound_cpu = ret;
+ }
+
+ /* Default control poll timeout */
+ pdata->control_poll_timeout_usecs = 10;
+
+ ret = gtrace_of_parse_outconns(pdata);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to parse output connections\n");
+
+ ret = gtrace_of_parse_inconns(pdata);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to parse input connections\n");
+
+ /* Reset the component before it is registered with the gtrace core. */
+ ret = rvtrace_hw_reset(pdata);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to reset component\n");
+
+ impl = gtrace_read32(pdata, RVTRACE_COMPONENT_IMPL_OFFSET);
+ type = (impl >> RVTRACE_COMPONENT_IMPL_TYPE_SHIFT) &
+ RVTRACE_COMPONENT_IMPL_TYPE_MASK;
+ major = (impl >> RVTRACE_COMPONENT_IMPL_VERMAJOR_SHIFT) &
+ RVTRACE_COMPONENT_IMPL_VERMAJOR_MASK;
+ minor = (impl >> RVTRACE_COMPONENT_IMPL_VERMINOR_SHIFT) &
+ RVTRACE_COMPONENT_IMPL_VERMINOR_MASK;
+
+ gtype = rvtrace_type_to_gtrace(type);
+ if (gtype < 0)
+ return dev_err_probe(dev, -ENODEV, "unsupported component type %u\n", type);
+
+ id.type = gtype;
+ id.version = rvtrace_component_mkversion(major, minor);
+
+ comp = gtrace_register_component(&id, rvtrace_type_name(gtype), pdata);
+ if (IS_ERR(comp))
+ return PTR_ERR(comp);
+
+ platform_set_drvdata(pdev, comp);
+ return 0;
+}
+
+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);
+}
+
+static const struct of_device_id rvtrace_platform_match[] = {
+ { .compatible = "riscv,trace-component", .data = (void *)GTRACE_FORMAT_ETRACE },
+ {}
+};
+
+static struct platform_driver rvtrace_platform_driver = {
+ .driver = {
+ .name = "rvtrace",
+ .of_match_table = rvtrace_platform_match,
+ },
+ .probe = rvtrace_platform_probe,
+ .remove = rvtrace_platform_remove,
+};
+
+static int __init rvtrace_platform_init(void)
+{
+ return platform_driver_register(&rvtrace_platform_driver);
+}
+
+static void __exit rvtrace_platform_exit(void)
+{
+ platform_driver_unregister(&rvtrace_platform_driver);
+}
+
+module_init(rvtrace_platform_init);
+module_exit(rvtrace_platform_exit);
+
+MODULE_AUTHOR("Anup Patel");
+MODULE_DESCRIPTION("RISC-V backend for the generic trace framework");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hwtracing/gtrace/rvtrace.h b/drivers/hwtracing/gtrace/rvtrace.h
new file mode 100644
index 000000000000..e6be1ce303cc
--- /dev/null
+++ b/drivers/hwtracing/gtrace/rvtrace.h
@@ -0,0 +1,121 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2026 Qualcomm Technologies, Inc.
+ *
+ * RISC-V trace component register layout and helpers for the RISC-V backend
+ * of the generic trace (gtrace) framework. These definitions are specific to
+ * the RISC-V Trace Control Interface and must not be used by the generic core.
+ */
+
+#ifndef __LINUX_RVTRACE_H__
+#define __LINUX_RVTRACE_H__
+
+#include <linux/gtrace.h>
+#include <linux/types.h>
+
+/* Control register common across all RISC-V trace components */
+#define RVTRACE_COMPONENT_CTRL_OFFSET 0x000
+#define RVTRACE_COMPONENT_CTRL_ACTIVE_MASK 0x1
+#define RVTRACE_COMPONENT_CTRL_ACTIVE_SHIFT 0
+#define RVTRACE_COMPONENT_CTRL_ENABLE_MASK 0x1
+#define RVTRACE_COMPONENT_CTRL_ENABLE_SHIFT 1
+#define RVTRACE_COMPONENT_CTRL_EMPTY_SHIFT 3
+
+/* Implementation register common across all RISC-V trace components */
+#define RVTRACE_COMPONENT_IMPL_OFFSET 0x004
+#define RVTRACE_COMPONENT_IMPL_VERMAJOR_MASK 0xf
+#define RVTRACE_COMPONENT_IMPL_VERMAJOR_SHIFT 0
+#define RVTRACE_COMPONENT_IMPL_VERMINOR_MASK 0xf
+#define RVTRACE_COMPONENT_IMPL_VERMINOR_SHIFT 4
+#define RVTRACE_COMPONENT_IMPL_TYPE_MASK 0xf
+#define RVTRACE_COMPONENT_IMPL_TYPE_SHIFT 8
+
+/* Component types defined by the RISC-V Trace Control Interface */
+enum rvtrace_component_type {
+ RVTRACE_COMPONENT_TYPE_RESV0,
+ RVTRACE_COMPONENT_TYPE_ENCODER, /* 0x1 */
+ RVTRACE_COMPONENT_TYPE_RESV2,
+ RVTRACE_COMPONENT_TYPE_RESV3,
+ RVTRACE_COMPONENT_TYPE_RESV4,
+ RVTRACE_COMPONENT_TYPE_RESV5,
+ RVTRACE_COMPONENT_TYPE_RESV6,
+ RVTRACE_COMPONENT_TYPE_RESV7,
+ RVTRACE_COMPONENT_TYPE_FUNNEL, /* 0x8 */
+ RVTRACE_COMPONENT_TYPE_RAMSINK, /* 0x9 */
+ RVTRACE_COMPONENT_TYPE_PIBSINK, /* 0xA */
+ RVTRACE_COMPONENT_TYPE_RESV11,
+ RVTRACE_COMPONENT_TYPE_RESV12,
+ RVTRACE_COMPONENT_TYPE_RESV13,
+ RVTRACE_COMPONENT_TYPE_ATBBRIDGE, /* 0xE */
+ RVTRACE_COMPONENT_TYPE_RESV15,
+ RVTRACE_COMPONENT_TYPE_MAX
+};
+
+/*
+ * Possible component implementation IDs discovered from DT or ACPI
+ * shared across the RISC-V trace drivers to infer trace parameters,
+ * quirks, and work-arounds. These component implementation IDs are
+ * internal to Linux and must not be exposed to user-space.
+ *
+ * The component implementation ID should be named as follows:
+ * RVTRACE_COMPONENT_IMPID_<vendor>_<part>
+ */
+enum rvtrace_component_impid {
+ RVTRACE_COMPONENT_IMPID_UNKNOWN,
+ RVTRACE_COMPONENT_IMPID_MAX
+};
+
+#define rvtrace_component_version_major(__version) \
+ (((__version) >> 16) & 0xffff)
+#define rvtrace_component_version_minor(__version) \
+ ((__version) & 0xffff)
+#define rvtrace_component_mkversion(__major, __minor) \
+ ((((__major) & 0xffff) << 16) | ((__minor) & 0xffff))
+
+/* Map a RISC-V hardware component type to the global gtrace type */
+static inline int rvtrace_type_to_gtrace(enum rvtrace_component_type type)
+{
+ switch (type) {
+ case RVTRACE_COMPONENT_TYPE_ENCODER:
+ return GTRACE_RVTRACE_ENCODER;
+ case RVTRACE_COMPONENT_TYPE_FUNNEL:
+ return GTRACE_RVTRACE_FUNNEL;
+ case RVTRACE_COMPONENT_TYPE_RAMSINK:
+ return GTRACE_RVTRACE_RAMSINK;
+ case RVTRACE_COMPONENT_TYPE_PIBSINK:
+ return GTRACE_RVTRACE_PIBSINK;
+ case RVTRACE_COMPONENT_TYPE_ATBBRIDGE:
+ return GTRACE_RVTRACE_ATBBRIDGE;
+ default:
+ return -EINVAL;
+ }
+}
+
+static inline const char *rvtrace_type_name(enum gtrace_component_type type)
+{
+ switch (type) {
+ case GTRACE_RVTRACE_ENCODER:
+ return "encoder";
+ case GTRACE_RVTRACE_FUNNEL:
+ return "funnel";
+ case GTRACE_RVTRACE_RAMSINK:
+ return "ramsink";
+ case GTRACE_RVTRACE_PIBSINK:
+ return "pibsink";
+ case GTRACE_RVTRACE_ATBBRIDGE:
+ return "atbbridge";
+ default:
+ return NULL;
+ }
+}
+
+static inline int rvtrace_comp_poll_empty(struct gtrace_component *comp)
+{
+ return gtrace_poll_bit(comp->pdata, RVTRACE_COMPONENT_CTRL_OFFSET,
+ RVTRACE_COMPONENT_CTRL_EMPTY_SHIFT, 1,
+ comp->pdata->control_poll_timeout_usecs);
+}
+
+extern const struct gtrace_hw_ops rvtrace_hw_ops;
+
+#endif /* __LINUX_RVTRACE_H__ */
diff --git a/include/linux/gtrace.h b/include/linux/gtrace.h
index 3ae9fcf5bcfa..5368dca0e936 100644
--- a/include/linux/gtrace.h
+++ b/include/linux/gtrace.h
@@ -236,6 +236,9 @@ struct gtrace_component *gtrace_register_component(struct gtrace_component_id *i
struct gtrace_platform_data *pdata);
void gtrace_unregister_component(struct gtrace_component *comp);
+int gtrace_of_parse_outconns(struct gtrace_platform_data *pdata);
+int gtrace_of_parse_inconns(struct gtrace_platform_data *pdata);
+
/**
* struct gtrace_driver - Representation of a trace driver.
* @id_table: Table to match components handled by the driver.
--
2.43.0
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [PATCH v6 04/14] gtrace: Add functions to create/destroy a trace component path
2026-10-01 5:22 [PATCH v6 00/14] Linux RISC-V trace framework and drivers Mayuresh Chitale
` (2 preceding siblings ...)
2026-10-01 5:22 ` [PATCH v6 03/14] gtrace: Add RISC-V platform driver for the " Mayuresh Chitale
@ 2026-10-01 5:22 ` Mayuresh Chitale
2026-10-01 5:36 ` sashiko-bot
2026-10-01 5:22 ` [PATCH v6 05/14] gtrace: Add functions to start/stop tracing on a " Mayuresh Chitale
` (9 subsequent siblings)
13 siblings, 1 reply; 25+ messages in thread
From: Mayuresh Chitale @ 2026-10-01 5:22 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Palmer Dabbelt,
Paul Walmsley, Greg KH, Alexander Shishkin, Ian Rogers
Cc: Anup Patel, Alexandre Ghiti, Peter Zijlstra, Ingo Molnar,
Namhyung Kim, Mark Rutland, Jiri Olsa, Adrian Hunter,
Mayuresh Chitale, Anup Patel, Atish Patra, Andrew Jones,
Sunil V L, linux-riscv, devicetree, linux-kernel,
Mayuresh Chitale, Zane Leung
From: Anup Patel <anup.patel@oss.qualcomm.com>
Trace needs to be configured on a chain of trace components which are
connected to each other. These chain of components is also referred
to as trace component path. Add functions to create/destroy a trace
component path which will be later used by gtrace perf support.
Signed-off-by: Anup Patel <anup.patel@oss.qualcomm.com>
Co-developed-by: Mayuresh Chitale <mayuresh.chitale@oss.qualcomm.com>
Signed-off-by: Mayuresh Chitale <mayuresh.chitale@oss.qualcomm.com>
---
drivers/hwtracing/gtrace/gtrace-core.c | 241 +++++++++++++++++++++++++
include/linux/gtrace.h | 24 +++
2 files changed, 265 insertions(+)
diff --git a/drivers/hwtracing/gtrace/gtrace-core.c b/drivers/hwtracing/gtrace/gtrace-core.c
index d374c679a649..f750025104ec 100644
--- a/drivers/hwtracing/gtrace/gtrace-core.c
+++ b/drivers/hwtracing/gtrace/gtrace-core.c
@@ -26,6 +26,7 @@ struct gtrace_comp_priv {
struct gtrace_component comp;
u32 type_idx;
bool ready;
+ bool visited;
};
#define to_gtrace_comp_priv(__comp) \
@@ -224,6 +225,62 @@ int gtrace_reset_component(struct gtrace_component *comp)
}
EXPORT_SYMBOL_GPL(gtrace_reset_component);
+static int __gtrace_walk_output_components(struct gtrace_component *comp,
+ bool *stop, void *priv,
+ int (*fn)(struct gtrace_component *comp, bool *stop,
+ struct gtrace_connection *stop_conn,
+ void *priv))
+{
+ struct gtrace_comp_priv *cpriv = to_gtrace_comp_priv(comp);
+ struct gtrace_connection *conn, *stop_conn = NULL;
+ struct gtrace_platform_data *pdata = comp->pdata;
+ int i, ret;
+
+ if (cpriv->visited) {
+ dev_err(&comp->dev, "loop detected, please fix the firmware\n");
+ return -ELOOP;
+ }
+ cpriv->visited = true;
+
+ for (i = 0; i < pdata->nr_outconns; i++) {
+ conn = pdata->outconns[i];
+ ret = __gtrace_walk_output_components(conn->dest_comp, stop, priv, fn);
+ if (ret) {
+ cpriv->visited = false;
+ return ret;
+ }
+
+ if (*stop) {
+ stop_conn = conn;
+ break;
+ }
+ }
+
+ ret = fn(comp, stop, stop_conn, priv);
+ cpriv->visited = false;
+
+ return ret;
+}
+
+int gtrace_walk_output_components(struct gtrace_component *comp, void *priv,
+ int (*fn)(struct gtrace_component *comp, bool *stop,
+ struct gtrace_connection *stop_conn,
+ void *priv))
+{
+ bool stop = false;
+ int ret;
+
+ if (!comp || !fn)
+ return -EINVAL;
+
+ mutex_lock(>race_mutex);
+ ret = __gtrace_walk_output_components(comp, &stop, priv, fn);
+ mutex_unlock(>race_mutex);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(gtrace_walk_output_components);
+
struct gtrace_component *gtrace_cpu_source(unsigned int cpu)
{
if (!cpu_present(cpu))
@@ -431,6 +488,190 @@ void gtrace_unregister_component(struct gtrace_component *comp)
}
EXPORT_SYMBOL_GPL(gtrace_unregister_component);
+struct gtrace_path_node {
+ struct list_head head;
+ struct gtrace_component *comp;
+ struct gtrace_connection *conn;
+};
+
+struct gtrace_component *gtrace_path_source(struct gtrace_path *path)
+{
+ struct gtrace_path_node *node;
+
+ node = list_first_entry(&path->comp_list, struct gtrace_path_node, head);
+ return node->comp;
+}
+EXPORT_SYMBOL_GPL(gtrace_path_source);
+
+struct gtrace_component *gtrace_path_sink(struct gtrace_path *path)
+{
+ struct gtrace_path_node *node;
+
+ node = list_last_entry(&path->comp_list, struct gtrace_path_node, head);
+ return node->comp;
+}
+EXPORT_SYMBOL_GPL(gtrace_path_sink);
+
+static int gtrace_assign_trace_id(struct gtrace_path *path)
+{
+ const struct gtrace_driver *gtdrv;
+ struct gtrace_component *comp;
+ struct gtrace_path_node *node;
+ int trace_id;
+
+ list_for_each_entry(node, &path->comp_list, head) {
+ comp = node->comp;
+ gtdrv = to_gtrace_driver(comp->dev.driver);
+
+ if (!gtdrv || !gtdrv->get_trace_id)
+ continue;
+
+ trace_id = gtdrv->get_trace_id(comp, path->mode);
+ if (trace_id > 0) {
+ path->trace_id = trace_id;
+ return 0;
+ } else if (trace_id < 0) {
+ return trace_id;
+ }
+ }
+
+ return 0;
+}
+
+static void gtrace_unassign_trace_id(struct gtrace_path *path)
+{
+ const struct gtrace_driver *gtdrv;
+ struct gtrace_component *comp;
+ struct gtrace_path_node *node;
+
+ list_for_each_entry(node, &path->comp_list, head) {
+ comp = node->comp;
+ gtdrv = to_gtrace_driver(comp->dev.driver);
+
+ if (!gtdrv || !gtdrv->put_trace_id)
+ continue;
+
+ gtdrv->put_trace_id(comp, path->mode, path->trace_id);
+ }
+}
+
+static bool gtrace_path_ready(struct gtrace_path *path)
+{
+ struct gtrace_comp_priv *cpriv;
+ struct gtrace_path_node *node;
+
+ list_for_each_entry(node, &path->comp_list, head) {
+ cpriv = to_gtrace_comp_priv(node->comp);
+ if (!cpriv->ready)
+ return false;
+ }
+
+ return true;
+}
+
+struct build_path_walk_priv {
+ struct gtrace_path *path;
+ struct gtrace_component *sink;
+};
+
+static int build_path_walk_fn(struct gtrace_component *comp, bool *stop,
+ struct gtrace_connection *stop_conn,
+ void *priv)
+{
+ struct build_path_walk_priv *ppriv = priv;
+ struct gtrace_path *path = ppriv->path;
+ struct gtrace_path_node *node;
+
+ if ((!ppriv->sink && gtrace_is_sink(comp->pdata)) ||
+ (ppriv->sink && ppriv->sink == comp))
+ *stop = true;
+
+ if (*stop) {
+ node = kzalloc_obj(*node);
+ if (!node)
+ return -ENOMEM;
+ INIT_LIST_HEAD(&node->head);
+ gtrace_get_component(comp);
+ node->comp = comp;
+ node->conn = stop_conn;
+ list_add(&node->head, &path->comp_list);
+ }
+
+ return 0;
+}
+
+static void gtrace_release_path_nodes(struct gtrace_path *path)
+{
+ struct gtrace_path_node *node, *node1;
+
+ list_for_each_entry_safe(node, node1, &path->comp_list, head) {
+ list_del(&node->head);
+ gtrace_put_component(node->comp);
+ kfree(node);
+ }
+}
+
+struct gtrace_path *gtrace_create_path(struct gtrace_component *source,
+ struct gtrace_component *sink,
+ enum gtrace_component_mode mode)
+{
+ struct build_path_walk_priv priv;
+ struct gtrace_path *path;
+ int ret = 0;
+
+ if (!source || mode >= GTRACE_COMPONENT_MODE_MAX) {
+ ret = -EINVAL;
+ goto err_out;
+ }
+
+ path = kzalloc(sizeof(*path), GFP_KERNEL);
+ if (!path) {
+ ret = -ENOMEM;
+ goto err_out;
+ }
+ INIT_LIST_HEAD(&path->comp_list);
+ path->mode = mode;
+ path->trace_id = GTRACE_INVALID_TRACE_ID;
+
+ priv.path = path;
+ priv.sink = sink;
+ ret = gtrace_walk_output_components(source, &priv, build_path_walk_fn);
+ if (ret < 0)
+ goto err_release_path_nodes;
+
+ /* Before proceeding, check that a valid path was built. */
+ if (list_empty(&path->comp_list)) {
+ ret = -ENODEV;
+ goto err_release_path_nodes;
+ }
+
+ if (!gtrace_path_ready(path)) {
+ ret = -EOPNOTSUPP;
+ goto err_release_path_nodes;
+ }
+
+ ret = gtrace_assign_trace_id(path);
+ if (ret < 0)
+ goto err_release_path_nodes;
+
+ return path;
+
+err_release_path_nodes:
+ gtrace_release_path_nodes(path);
+ kfree(path);
+err_out:
+ return ERR_PTR(ret);
+}
+EXPORT_SYMBOL_GPL(gtrace_create_path);
+
+void gtrace_destroy_path(struct gtrace_path *path)
+{
+ gtrace_unassign_trace_id(path);
+ gtrace_release_path_nodes(path);
+ kfree(path);
+}
+EXPORT_SYMBOL_GPL(gtrace_destroy_path);
+
int __gtrace_register_driver(struct module *owner, struct gtrace_driver *gtdrv)
{
gtdrv->driver.owner = owner;
diff --git a/include/linux/gtrace.h b/include/linux/gtrace.h
index 5368dca0e936..2c3be4d161c6 100644
--- a/include/linux/gtrace.h
+++ b/include/linux/gtrace.h
@@ -229,6 +229,10 @@ int gtrace_enable_component(struct gtrace_component *comp);
int gtrace_disable_component(struct gtrace_component *comp);
int gtrace_reset_component(struct gtrace_component *comp);
+int gtrace_walk_output_components(struct gtrace_component *comp, void *priv,
+ int (*fn)(struct gtrace_component *comp, bool *stop,
+ struct gtrace_connection *stop_conn,
+ void *priv));
struct gtrace_component *gtrace_cpu_source(unsigned int cpu);
struct gtrace_component *gtrace_register_component(struct gtrace_component_id *id,
@@ -239,6 +243,26 @@ void gtrace_unregister_component(struct gtrace_component *comp);
int gtrace_of_parse_outconns(struct gtrace_platform_data *pdata);
int gtrace_of_parse_inconns(struct gtrace_platform_data *pdata);
+/**
+ * struct gtrace_path - Representation of a trace path from source to sink.
+ * @comp_list: List of trace components in the path.
+ * @mode: Usage mode for trace components.
+ * @trace_id: ID of the trace source (typically hart/CPU id).
+ */
+struct gtrace_path {
+ struct list_head comp_list;
+ enum gtrace_component_mode mode;
+ u32 trace_id;
+#define GTRACE_INVALID_TRACE_ID 0
+};
+
+struct gtrace_component *gtrace_path_source(struct gtrace_path *path);
+struct gtrace_component *gtrace_path_sink(struct gtrace_path *path);
+struct gtrace_path *gtrace_create_path(struct gtrace_component *source,
+ struct gtrace_component *sink,
+ enum gtrace_component_mode mode);
+void gtrace_destroy_path(struct gtrace_path *path);
+
/**
* struct gtrace_driver - Representation of a trace driver.
* @id_table: Table to match components handled by the driver.
--
2.43.0
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [PATCH v6 05/14] gtrace: Add functions to start/stop tracing on a component path
2026-10-01 5:22 [PATCH v6 00/14] Linux RISC-V trace framework and drivers Mayuresh Chitale
` (3 preceding siblings ...)
2026-10-01 5:22 ` [PATCH v6 04/14] gtrace: Add functions to create/destroy a trace component path Mayuresh Chitale
@ 2026-10-01 5:22 ` Mayuresh Chitale
2026-10-01 5:36 ` sashiko-bot
2026-10-01 5:22 ` [PATCH v6 06/14] gtrace: Add RISC-V Trace encoder driver Mayuresh Chitale
` (8 subsequent siblings)
13 siblings, 1 reply; 25+ messages in thread
From: Mayuresh Chitale @ 2026-10-01 5:22 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Palmer Dabbelt,
Paul Walmsley, Greg KH, Alexander Shishkin, Ian Rogers
Cc: Mayuresh Chitale, Alexandre Ghiti, Peter Zijlstra, Ingo Molnar,
Namhyung Kim, Mark Rutland, Jiri Olsa, Adrian Hunter,
Mayuresh Chitale, Anup Patel, Atish Patra, Andrew Jones,
Sunil V L, linux-riscv, devicetree, linux-kernel, Anup Patel,
Zane Leung
The perf driver framework needs to be able to start / stop all components
in a trace component path during its operation. Add gtrace_path_start()
and gtrace_path_stop() functions for this purpose.
Co-developed-by: Anup Patel <anup.patel@oss.qualcomm.com>
Signed-off-by: Anup Patel <anup.patel@oss.qualcomm.com>
Signed-off-by: Mayuresh Chitale <mayuresh.chitale@oss.qualcomm.com>
---
drivers/hwtracing/gtrace/gtrace-core.c | 104 +++++++++++++++++++++++++
include/linux/gtrace.h | 7 ++
2 files changed, 111 insertions(+)
diff --git a/drivers/hwtracing/gtrace/gtrace-core.c b/drivers/hwtracing/gtrace/gtrace-core.c
index f750025104ec..d7f1df90aa31 100644
--- a/drivers/hwtracing/gtrace/gtrace-core.c
+++ b/drivers/hwtracing/gtrace/gtrace-core.c
@@ -10,6 +10,7 @@
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/percpu.h>
+#include <linux/spinlock.h>
#include <linux/xarray.h>
#include <linux/gtrace.h>
@@ -24,7 +25,15 @@ static DEFINE_PER_CPU(struct gtrace_component *, gtrace_cpu_source_comp);
/* gtrace comp specific data, to be used by core functions only */
struct gtrace_comp_priv {
struct gtrace_component comp;
+ /*
+ * Protects start_count and owner. Serializes start/stop against other paths
+ * sharing this component and, for a sink, against gtrace_path_copyto_auxbuf().
+ * Raw because the PMU start/stop callbacks are called with the rq_lock held.
+ */
+ raw_spinlock_t lock;
u32 type_idx;
+ u32 start_count;
+ pid_t owner;
bool ready;
bool visited;
};
@@ -418,6 +427,7 @@ struct gtrace_component *gtrace_register_component(struct gtrace_component_id *i
ret = -ENOMEM;
goto err_out;
}
+ raw_spin_lock_init(&cpriv->lock);
comp = &cpriv->comp;
comp->pdata = pdata;
comp->id = *id;
@@ -611,6 +621,100 @@ static void gtrace_release_path_nodes(struct gtrace_path *path)
}
}
+static int __gtrace_comp_start(struct gtrace_component *comp, pid_t owner)
+{
+ struct gtrace_comp_priv *cpriv = to_gtrace_comp_priv(comp);
+ const struct gtrace_driver *gtdrv = to_gtrace_driver(comp->dev.driver);
+ unsigned long flags;
+ int ret = 0;
+
+ if (!gtdrv)
+ return -ENODEV;
+
+ raw_spin_lock_irqsave(&cpriv->lock, flags);
+ if (cpriv->start_count) {
+ if (cpriv->owner != owner) {
+ ret = -EBUSY;
+ goto out;
+ }
+ } else {
+ if (gtdrv->start) {
+ ret = gtdrv->start(comp);
+ if (ret)
+ goto out;
+ }
+ cpriv->owner = owner;
+ }
+
+ cpriv->start_count++;
+out:
+ raw_spin_unlock_irqrestore(&cpriv->lock, flags);
+ return ret;
+}
+
+static int __gtrace_comp_stop(struct gtrace_component *comp)
+{
+ struct gtrace_comp_priv *cpriv = to_gtrace_comp_priv(comp);
+ const struct gtrace_driver *gtdrv = to_gtrace_driver(comp->dev.driver);
+ unsigned long flags;
+ int ret = 0;
+
+ raw_spin_lock_irqsave(&cpriv->lock, flags);
+ if (!cpriv->start_count) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ cpriv->start_count--;
+ if (!cpriv->start_count) {
+ if (!gtdrv) {
+ ret = -ENODEV;
+ goto out;
+ }
+
+ if (gtdrv->stop)
+ ret = gtdrv->stop(comp);
+ }
+out:
+ raw_spin_unlock_irqrestore(&cpriv->lock, flags);
+ return ret;
+}
+
+int gtrace_path_start(struct gtrace_path *path)
+{
+ struct gtrace_path_node *node;
+ int ret = 0;
+
+ list_for_each_entry_reverse(node, &path->comp_list, head) {
+ ret = __gtrace_comp_start(node->comp, path->owner);
+ 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);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(gtrace_path_start);
+
+int gtrace_path_stop(struct gtrace_path *path)
+{
+ struct gtrace_path_node *node;
+ int ret = 0, err;
+
+ list_for_each_entry(node, &path->comp_list, head) {
+ err = __gtrace_comp_stop(node->comp);
+ if (err && !ret)
+ ret = err;
+ }
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(gtrace_path_stop);
+
struct gtrace_path *gtrace_create_path(struct gtrace_component *source,
struct gtrace_component *sink,
enum gtrace_component_mode mode)
diff --git a/include/linux/gtrace.h b/include/linux/gtrace.h
index 2c3be4d161c6..a4b99c87d250 100644
--- a/include/linux/gtrace.h
+++ b/include/linux/gtrace.h
@@ -254,6 +254,7 @@ struct gtrace_path {
enum gtrace_component_mode mode;
u32 trace_id;
#define GTRACE_INVALID_TRACE_ID 0
+ pid_t owner;
};
struct gtrace_component *gtrace_path_source(struct gtrace_path *path);
@@ -262,10 +263,14 @@ struct gtrace_path *gtrace_create_path(struct gtrace_component *source,
struct gtrace_component *sink,
enum gtrace_component_mode mode);
void gtrace_destroy_path(struct gtrace_path *path);
+int gtrace_path_start(struct gtrace_path *path);
+int gtrace_path_stop(struct gtrace_path *path);
/**
* struct gtrace_driver - Representation of a trace driver.
* @id_table: Table to match components handled by the driver.
+ * @start: Callback to start tracing.
+ * @stop: Callback to stop tracing.
* @probe: Driver probe() function.
* @remove: Driver remove() function.
* @get_trace_id: Get/allocate a trace ID.
@@ -274,6 +279,8 @@ void gtrace_destroy_path(struct gtrace_path *path);
*/
struct gtrace_driver {
const struct gtrace_component_id *id_table;
+ int (*start)(struct gtrace_component *comp);
+ int (*stop)(struct gtrace_component *comp);
int (*probe)(struct gtrace_component *comp);
void (*remove)(struct gtrace_component *comp);
int (*get_trace_id)(struct gtrace_component *comp,
--
2.43.0
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [PATCH v6 06/14] gtrace: Add RISC-V Trace encoder driver
2026-10-01 5:22 [PATCH v6 00/14] Linux RISC-V trace framework and drivers Mayuresh Chitale
` (4 preceding siblings ...)
2026-10-01 5:22 ` [PATCH v6 05/14] gtrace: Add functions to start/stop tracing on a " Mayuresh Chitale
@ 2026-10-01 5:22 ` Mayuresh Chitale
2026-10-01 5:34 ` sashiko-bot
2026-10-01 5:22 ` [PATCH v6 07/14] gtrace: Add function to copy into perf AUX buffer Mayuresh Chitale
` (7 subsequent siblings)
13 siblings, 1 reply; 25+ messages in thread
From: Mayuresh Chitale @ 2026-10-01 5:22 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Palmer Dabbelt,
Paul Walmsley, Greg KH, Alexander Shishkin, Ian Rogers
Cc: Mayuresh Chitale, Alexandre Ghiti, Peter Zijlstra, Ingo Molnar,
Namhyung Kim, Mark Rutland, Jiri Olsa, Adrian Hunter,
Mayuresh Chitale, Anup Patel, Atish Patra, Andrew Jones,
Sunil V L, linux-riscv, devicetree, linux-kernel, Anup Patel,
Zane Leung
Add initial implementation of RISC-V E-Trace encoder driver. The encoder
is defined in the RISC-V Trace Control Interface specification.
Co-developed-by: Anup Patel <anup.patel@oss.qualcomm.com>
Signed-off-by: Anup Patel <anup.patel@oss.qualcomm.com>
Signed-off-by: Mayuresh Chitale <mayuresh.chitale@oss.qualcomm.com>
---
drivers/hwtracing/gtrace/Kconfig | 13 ++
drivers/hwtracing/gtrace/Makefile | 1 +
drivers/hwtracing/gtrace/rvtrace-encoder.c | 236 +++++++++++++++++++++
3 files changed, 250 insertions(+)
create mode 100644 drivers/hwtracing/gtrace/rvtrace-encoder.c
diff --git a/drivers/hwtracing/gtrace/Kconfig b/drivers/hwtracing/gtrace/Kconfig
index 04986f216549..e3a621b59913 100644
--- a/drivers/hwtracing/gtrace/Kconfig
+++ b/drivers/hwtracing/gtrace/Kconfig
@@ -28,3 +28,16 @@ config RVTRACE
To compile this driver as a module, choose M here: the module
will be called rvtrace.
+
+config RVTRACE_ENCODER
+ tristate "RISC-V Trace Encoder driver"
+ depends on RVTRACE
+ default y
+ help
+ This driver provides support for the RISC-V trace encoder component
+ which is defined by the RISC-V Trace Control Interface specification.
+ The encoder is bound to a CPU and generates the trace data for that
+ CPU.
+
+ To compile this driver as a module, choose M here: the module
+ will be called rvtrace-encoder.
diff --git a/drivers/hwtracing/gtrace/Makefile b/drivers/hwtracing/gtrace/Makefile
index 20f67a78b8ab..2660fcaa332b 100644
--- a/drivers/hwtracing/gtrace/Makefile
+++ b/drivers/hwtracing/gtrace/Makefile
@@ -5,3 +5,4 @@ gtrace-y := gtrace-core.o
gtrace-$(CONFIG_OF) += gtrace-of.o
obj-$(CONFIG_RVTRACE) += rvtrace-platform.o
+obj-$(CONFIG_RVTRACE_ENCODER) += rvtrace-encoder.o
diff --git a/drivers/hwtracing/gtrace/rvtrace-encoder.c b/drivers/hwtracing/gtrace/rvtrace-encoder.c
new file mode 100644
index 000000000000..1f06b1a2b412
--- /dev/null
+++ b/drivers/hwtracing/gtrace/rvtrace-encoder.c
@@ -0,0 +1,236 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2026 Qualcomm Technologies, Inc.
+ */
+
+#include <linux/cpumask.h>
+#include <linux/device.h>
+#include <linux/gtrace.h>
+#include <linux/log2.h>
+#include <linux/minmax.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+#include "rvtrace.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;
+};
+
+/*
+ * Set the srcid and clear trTeInhibitSrc bit so that the emitted trace messages carry
+ * the source id
+ */
+static void rvtrace_encoder_set_srcid(struct gtrace_platform_data *pdata, u32 srcbits)
+{
+ 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;
+ gtrace_write32(pdata, val, RVTRACE_ENCODER_INSTFEAT_OFFSET);
+
+ val = gtrace_read32(pdata, RVTRACE_COMPONENT_CTRL_OFFSET);
+ val &= ~BIT(RVTRACE_COMPONENT_CTRL_INHIBITSRC_SHIFT);
+ 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");
+ return -EINVAL;
+ }
+
+ priv = devm_kzalloc(&comp->dev, sizeof(*priv), GFP_KERNEL);
+ 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);
+ gtrace_write32(pdata, val, RVTRACE_COMPONENT_CTRL_OFFSET);
+ val = gtrace_read32(pdata, RVTRACE_COMPONENT_CTRL_OFFSET);
+ 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;
+ /* 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);
+ priv->srcbits = srcbits;
+ }
+ }
+ if (!priv->srcbits) {
+ dev_warn(&comp->dev, "cpu %d trace will not carry source id\n",
+ pdata->bound_cpu);
+ val = gtrace_read32(pdata, RVTRACE_COMPONENT_CTRL_OFFSET);
+ val |= BIT(RVTRACE_COMPONENT_CTRL_INHIBITSRC_SHIFT);
+ gtrace_write32(pdata, val, RVTRACE_COMPONENT_CTRL_OFFSET);
+ }
+ dev_set_drvdata(&comp->dev, priv);
+ return 0;
+}
+
+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;
+ int ret;
+ u32 val;
+
+ 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");
+ 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);
+ gtrace_write32(pdata, val, RVTRACE_COMPONENT_CTRL_OFFSET);
+
+ val = gtrace_read32(pdata, RVTRACE_COMPONENT_CTRL_OFFSET);
+ val |= BIT(RVTRACE_COMPONENT_CTRL_ITRACE_SHIFT);
+ gtrace_write32(pdata, val, RVTRACE_COMPONENT_CTRL_OFFSET);
+ ret = gtrace_poll_bit(pdata, RVTRACE_COMPONENT_CTRL_OFFSET,
+ RVTRACE_COMPONENT_CTRL_ITRACE_SHIFT, 1,
+ pdata->control_poll_timeout_usecs);
+ if (ret) {
+ dev_err(&comp->dev, "failed to enable tracing.\n");
+ val = gtrace_read32(pdata, RVTRACE_COMPONENT_CTRL_OFFSET);
+ val &= ~BIT(RVTRACE_COMPONENT_CTRL_ITRACE_SHIFT);
+ gtrace_write32(pdata, val, RVTRACE_COMPONENT_CTRL_OFFSET);
+ gtrace_disable_component(comp);
+ }
+
+ return ret;
+}
+
+static int rvtrace_encoder_stop(struct gtrace_component *comp)
+{
+ struct gtrace_platform_data *pdata = comp->pdata;
+ int ret, err;
+ u32 val;
+
+ val = gtrace_read32(pdata, RVTRACE_COMPONENT_CTRL_OFFSET);
+ val &= ~BIT(RVTRACE_COMPONENT_CTRL_ITRACE_SHIFT);
+ gtrace_write32(pdata, val, RVTRACE_COMPONENT_CTRL_OFFSET);
+ ret = gtrace_poll_bit(pdata, RVTRACE_COMPONENT_CTRL_OFFSET,
+ RVTRACE_COMPONENT_CTRL_ITRACE_SHIFT, 0,
+ pdata->control_poll_timeout_usecs);
+ if (ret)
+ dev_err(&comp->dev, "failed to stop tracing.\n");
+
+ err = gtrace_disable_component(comp);
+ if (err)
+ dev_err(&comp->dev, "failed to disable encoder.\n");
+
+ return ret ?: err;
+}
+
+static const struct gtrace_component_id rvtrace_encoder_ids[] = {
+ { .type = GTRACE_RVTRACE_ENCODER,
+ .version = rvtrace_component_mkversion(1, 0), },
+ {},
+};
+
+static struct gtrace_driver rvtrace_encoder_driver = {
+ .id_table = rvtrace_encoder_ids,
+ .probe = rvtrace_encoder_probe,
+ .start = rvtrace_encoder_start,
+ .stop = rvtrace_encoder_stop,
+ .driver = {
+ .name = "rvtrace-encoder",
+ },
+};
+
+static int __init rvtrace_encoder_init(void)
+{
+ return gtrace_register_driver(&rvtrace_encoder_driver);
+}
+
+static void __exit rvtrace_encoder_exit(void)
+{
+ gtrace_unregister_driver(&rvtrace_encoder_driver);
+}
+
+module_init(rvtrace_encoder_init);
+module_exit(rvtrace_encoder_exit);
+
+/* Module information */
+MODULE_AUTHOR("Mayuresh Chitale");
+MODULE_DESCRIPTION("RISC-V Trace Encoder Driver");
+MODULE_LICENSE("GPL");
--
2.43.0
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [PATCH v6 07/14] gtrace: Add function to copy into perf AUX buffer
2026-10-01 5:22 [PATCH v6 00/14] Linux RISC-V trace framework and drivers Mayuresh Chitale
` (5 preceding siblings ...)
2026-10-01 5:22 ` [PATCH v6 06/14] gtrace: Add RISC-V Trace encoder driver Mayuresh Chitale
@ 2026-10-01 5:22 ` Mayuresh Chitale
2026-10-01 5:40 ` sashiko-bot
2026-10-01 5:22 ` [PATCH v6 08/14] perf: Add gtrace AUX buffer trace format type Mayuresh Chitale
` (6 subsequent siblings)
13 siblings, 1 reply; 25+ messages in thread
From: Mayuresh Chitale @ 2026-10-01 5:22 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Palmer Dabbelt,
Paul Walmsley, Greg KH, Alexander Shishkin, Ian Rogers
Cc: Anup Patel, Alexandre Ghiti, Peter Zijlstra, Ingo Molnar,
Namhyung Kim, Mark Rutland, Jiri Olsa, Adrian Hunter,
Mayuresh Chitale, Anup Patel, Atish Patra, Andrew Jones,
Sunil V L, linux-riscv, devicetree, linux-kernel,
Mayuresh Chitale, Zane Leung
From: Anup Patel <anup.patel@oss.qualcomm.com>
The RISC-V trace ramsink will need a mechanism to copy trace data
into the perf AUX buffer. Add gtrace_path_copyto_auxbuf() function
and corresponding trace driver callback copyto_auxbuf() for this
purpose.
Signed-off-by: Anup Patel <anup.patel@oss.qualcomm.com>
Co-developed-by: Mayuresh Chitale <mayuresh.chitale@oss.qualcomm.com>
Signed-off-by: Mayuresh Chitale <mayuresh.chitale@oss.qualcomm.com>
---
drivers/hwtracing/gtrace/gtrace-core.c | 41 ++++++++++++++++++++++++++
include/linux/gtrace.h | 24 +++++++++++++++
2 files changed, 65 insertions(+)
diff --git a/drivers/hwtracing/gtrace/gtrace-core.c b/drivers/hwtracing/gtrace/gtrace-core.c
index d7f1df90aa31..acbeb3df2135 100644
--- a/drivers/hwtracing/gtrace/gtrace-core.c
+++ b/drivers/hwtracing/gtrace/gtrace-core.c
@@ -715,6 +715,47 @@ int gtrace_path_stop(struct gtrace_path *path)
}
EXPORT_SYMBOL_GPL(gtrace_path_stop);
+int gtrace_path_copyto_auxbuf(struct gtrace_path *path,
+ struct gtrace_perf_auxbuf *buf,
+ size_t *bytes_copied, u64 *format)
+{
+ 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;
+ unsigned long flags;
+
+ /*
+ * Copy only after every path using the sink has stopped; otherwise the
+ * hardware may overwrite data being copied, or the same data may be copied
+ * to aux buffer twice. The last path to stop does the copy. Holding the
+ * 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;
+
+ *bytes_copied = gtdrv->copyto_auxbuf(comp, buf, format);
+ ret = 0;
+ break;
+ }
+out:
+ raw_spin_unlock_irqrestore(&sink->lock, flags);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(gtrace_path_copyto_auxbuf);
+
struct gtrace_path *gtrace_create_path(struct gtrace_component *source,
struct gtrace_component *sink,
enum gtrace_component_mode mode)
diff --git a/include/linux/gtrace.h b/include/linux/gtrace.h
index a4b99c87d250..ed0e751faa91 100644
--- a/include/linux/gtrace.h
+++ b/include/linux/gtrace.h
@@ -266,9 +266,30 @@ void gtrace_destroy_path(struct gtrace_path *path);
int gtrace_path_start(struct gtrace_path *path);
int gtrace_path_stop(struct gtrace_path *path);
+/**
+ * struct gtrace_perf_auxbuf - Representation of the perf AUX buffer.
+ * @length: Size of the AUX buffer.
+ * @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.
+ */
+struct gtrace_perf_auxbuf {
+ size_t length;
+ int nr_pages;
+ void *base;
+ long pos;
+};
+
+int gtrace_path_copyto_auxbuf(struct gtrace_path *path,
+ struct gtrace_perf_auxbuf *buf,
+ size_t *bytes_copied, u64 *format);
+
/**
* 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).
* @start: Callback to start tracing.
* @stop: Callback to stop tracing.
* @probe: Driver probe() function.
@@ -279,6 +300,9 @@ int gtrace_path_stop(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);
int (*start)(struct gtrace_component *comp);
int (*stop)(struct gtrace_component *comp);
int (*probe)(struct gtrace_component *comp);
--
2.43.0
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [PATCH v6 08/14] perf: Add gtrace AUX buffer trace format type
2026-10-01 5:22 [PATCH v6 00/14] Linux RISC-V trace framework and drivers Mayuresh Chitale
` (6 preceding siblings ...)
2026-10-01 5:22 ` [PATCH v6 07/14] gtrace: Add function to copy into perf AUX buffer Mayuresh Chitale
@ 2026-10-01 5:22 ` Mayuresh Chitale
2026-10-01 5:22 ` [PATCH v6 09/14] gtrace: Add RISC-V Trace ramsink driver Mayuresh Chitale
` (5 subsequent siblings)
13 siblings, 0 replies; 25+ messages in thread
From: Mayuresh Chitale @ 2026-10-01 5:22 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Palmer Dabbelt,
Paul Walmsley, Greg KH, Alexander Shishkin, Ian Rogers
Cc: Mayuresh Chitale, Alexandre Ghiti, Peter Zijlstra, Ingo Molnar,
Namhyung Kim, Mark Rutland, Jiri Olsa, Adrian Hunter,
Mayuresh Chitale, Anup Patel, Atish Patra, Andrew Jones,
Sunil V L, linux-riscv, devicetree, linux-kernel, Zane Leung
The gtrace PMU can host trace components of different architectures and
trace protocols, so the recorded AUX data needs to carry its format as
well. Define PERF_AUX_FLAG_GTRACE_FORMAT_ETRACE for the RISC-V E-trace
format which the perf tool can use to decode the payload.
Signed-off-by: Mayuresh Chitale <mayuresh.chitale@oss.qualcomm.com>
---
include/uapi/linux/perf_event.h | 5 +++++
tools/include/uapi/linux/perf_event.h | 5 +++++
2 files changed, 10 insertions(+)
diff --git a/include/uapi/linux/perf_event.h b/include/uapi/linux/perf_event.h
index fd10aa8d697f..2ac00a409a04 100644
--- a/include/uapi/linux/perf_event.h
+++ b/include/uapi/linux/perf_event.h
@@ -1311,6 +1311,11 @@ enum perf_callchain_context {
#define PERF_AUX_FLAG_CORESIGHT_FORMAT_CORESIGHT 0x0000 /* Default for backward compatibility */
#define PERF_AUX_FLAG_CORESIGHT_FORMAT_RAW 0x0100 /* Raw format of the source */
+/* gtrace PMU AUX buffer formats */
+#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_FLAG_FD_NO_GROUP (1UL << 0)
#define PERF_FLAG_FD_OUTPUT (1UL << 1)
#define PERF_FLAG_PID_CGROUP (1UL << 2) /* pid=cgroup ID, per-CPU mode only */
diff --git a/tools/include/uapi/linux/perf_event.h b/tools/include/uapi/linux/perf_event.h
index fd10aa8d697f..2ac00a409a04 100644
--- a/tools/include/uapi/linux/perf_event.h
+++ b/tools/include/uapi/linux/perf_event.h
@@ -1311,6 +1311,11 @@ enum perf_callchain_context {
#define PERF_AUX_FLAG_CORESIGHT_FORMAT_CORESIGHT 0x0000 /* Default for backward compatibility */
#define PERF_AUX_FLAG_CORESIGHT_FORMAT_RAW 0x0100 /* Raw format of the source */
+/* gtrace PMU AUX buffer formats */
+#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_FLAG_FD_NO_GROUP (1UL << 0)
#define PERF_FLAG_FD_OUTPUT (1UL << 1)
#define PERF_FLAG_PID_CGROUP (1UL << 2) /* pid=cgroup ID, per-CPU mode only */
--
2.43.0
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [PATCH v6 09/14] gtrace: Add RISC-V Trace ramsink driver
2026-10-01 5:22 [PATCH v6 00/14] Linux RISC-V trace framework and drivers Mayuresh Chitale
` (7 preceding siblings ...)
2026-10-01 5:22 ` [PATCH v6 08/14] perf: Add gtrace AUX buffer trace format type Mayuresh Chitale
@ 2026-10-01 5:22 ` Mayuresh Chitale
2026-10-01 5:42 ` sashiko-bot
2026-10-01 5:22 ` [PATCH v6 10/14] riscv: Enable DMA_RESTRICTED_POOL in defconfig Mayuresh Chitale
` (4 subsequent siblings)
13 siblings, 1 reply; 25+ messages in thread
From: Mayuresh Chitale @ 2026-10-01 5:22 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Palmer Dabbelt,
Paul Walmsley, Greg KH, Alexander Shishkin, Ian Rogers
Cc: Mayuresh Chitale, Alexandre Ghiti, Peter Zijlstra, Ingo Molnar,
Namhyung Kim, Mark Rutland, Jiri Olsa, Adrian Hunter,
Mayuresh Chitale, Anup Patel, Atish Patra, Andrew Jones,
Sunil V L, linux-riscv, devicetree, linux-kernel, Anup Patel,
Zane Leung
Add initial implementation of RISC-V E-Trace ramsink driver. The ramsink
is defined in the RISC-V Trace Control Interface specification.
Co-developed-by: Anup Patel <anup.patel@oss.qualcomm.com>
Signed-off-by: Anup Patel <anup.patel@oss.qualcomm.com>
Signed-off-by: Mayuresh Chitale <mayuresh.chitale@oss.qualcomm.com>
---
drivers/hwtracing/gtrace/Kconfig | 15 +
drivers/hwtracing/gtrace/Makefile | 1 +
drivers/hwtracing/gtrace/rvtrace-ramsink.c | 357 +++++++++++++++++++++
3 files changed, 373 insertions(+)
create mode 100644 drivers/hwtracing/gtrace/rvtrace-ramsink.c
diff --git a/drivers/hwtracing/gtrace/Kconfig b/drivers/hwtracing/gtrace/Kconfig
index e3a621b59913..de4a3b533591 100644
--- a/drivers/hwtracing/gtrace/Kconfig
+++ b/drivers/hwtracing/gtrace/Kconfig
@@ -41,3 +41,18 @@ config RVTRACE_ENCODER
To compile this driver as a module, choose M here: the module
will be called rvtrace-encoder.
+
+config RVTRACE_RAMSINK
+ tristate "RISC-V Trace Ramsink driver"
+ depends on RVTRACE
+ select DMA_SHARED_BUFFER
+ default y
+ help
+ This driver provides support for the RISC-V trace ramsink component
+ which is defined by the RISC-V Trace Control Interface specification.
+ The ramsink is a trace sink which stores the trace data generated by
+ the upstream components into a system memory buffer. The buffer is
+ then copied to the perf AUX buffer when tracing using the perf tool.
+
+ To compile this driver as a module, choose M here: the module
+ will be called rvtrace-ramsink.
diff --git a/drivers/hwtracing/gtrace/Makefile b/drivers/hwtracing/gtrace/Makefile
index 2660fcaa332b..31262cf5993d 100644
--- a/drivers/hwtracing/gtrace/Makefile
+++ b/drivers/hwtracing/gtrace/Makefile
@@ -6,3 +6,4 @@ gtrace-$(CONFIG_OF) += gtrace-of.o
obj-$(CONFIG_RVTRACE) += rvtrace-platform.o
obj-$(CONFIG_RVTRACE_ENCODER) += rvtrace-encoder.o
+obj-$(CONFIG_RVTRACE_RAMSINK) += rvtrace-ramsink.o
diff --git a/drivers/hwtracing/gtrace/rvtrace-ramsink.c b/drivers/hwtracing/gtrace/rvtrace-ramsink.c
new file mode 100644
index 000000000000..67b6779aacee
--- /dev/null
+++ b/drivers/hwtracing/gtrace/rvtrace-ramsink.c
@@ -0,0 +1,357 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2026 Qualcomm Technologies, Inc.
+ */
+
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/dma-mapping.h>
+#include <linux/gtrace.h>
+#include <linux/minmax.h>
+#include <linux/types.h>
+#include <linux/sizes.h>
+#include <uapi/linux/perf_event.h>
+#include "rvtrace.h"
+
+#define RVTRACE_RAMSINK_STARTLOW_OFF 0x010
+#define RVTRACE_RAMSINK_LIMITLOW_OFF 0x018
+#define RVTRACE_RAMSINK_WPLOW_OFF 0x020
+#define RVTRACE_RAMSINK_WPLOW_WRAP 0x1
+#define RVTRACE_RAMSINK_CTRL_MODE_MASK 0x1
+#define RVTRACE_RAMSINK_CTRL_MODE_SHIFT 0x4
+#define RVTRACE_RAMSINK_CTRL_STP_WRAP_SHIFT 0x8
+#define RVTRACE_RAMSINK_CTRL_MEMFMT_MASK 0x3
+#define RVTRACE_RAMSINK_CTRL_MEMFMT_SHIFT 0x9
+#define RVTRACE_RAMSINK_CTRL_ASYNCFREQ_MASK 0x7
+#define RVTRACE_RAMSINK_CTRL_ASYNCFREQ_SHIFT 0xc
+#define RVTRACE_RAMSINK_MEM_ACC_WIDTH_MIN 4
+#define RVTRACE_RAMSINK_SIZE_MIN PAGE_SIZE
+
+enum rvtrace_ramsink_mode {
+ MODE_SRAM,
+ MODE_SMEM
+};
+
+struct rvtrace_ramsink_priv {
+ size_t size;
+ void *va;
+ dma_addr_t start;
+ dma_addr_t end;
+ /* alloc_* store the DMA allocation returned by the kernel and are used later for free */
+ size_t alloc_size;
+ void *alloc_va;
+ dma_addr_t alloc_start;
+ enum rvtrace_ramsink_mode mode;
+ bool stop_on_wrap;
+ u32 async_freq;
+ int mem_acc_width;
+ u64 prev_wp;
+};
+
+struct trace_buf {
+ void *base;
+ size_t cur;
+ size_t len;
+};
+
+/* Helper to access start, limit, wp pair registers */
+static u64 rvtrace_ramsink_read_pair(struct gtrace_platform_data *pdata, u32 low_off)
+{
+ u32 low, high;
+
+ low = gtrace_read32(pdata, low_off);
+ high = gtrace_read32(pdata, low_off + 4);
+
+ return (u64)high << 32 | low;
+}
+
+static void rvtrace_ramsink_write_pair(struct gtrace_platform_data *pdata, u64 val, u32 low_off)
+{
+ gtrace_write32(pdata, lower_32_bits(val), low_off);
+ gtrace_write32(pdata, upper_32_bits(val), low_off + 4);
+}
+
+static int rvtrace_ramsink_start(struct gtrace_component *comp)
+{
+ struct rvtrace_ramsink_priv *priv = dev_get_drvdata(&comp->dev);
+ u32 trram_ctrl;
+ int ret;
+
+ trram_ctrl = gtrace_read32(comp->pdata, RVTRACE_COMPONENT_CTRL_OFFSET);
+ trram_ctrl &= ~BIT(RVTRACE_RAMSINK_CTRL_STP_WRAP_SHIFT);
+ trram_ctrl |= priv->stop_on_wrap << RVTRACE_RAMSINK_CTRL_STP_WRAP_SHIFT;
+ trram_ctrl &= ~(RVTRACE_RAMSINK_CTRL_MEMFMT_MASK << RVTRACE_RAMSINK_CTRL_MEMFMT_SHIFT);
+ trram_ctrl &= ~(RVTRACE_RAMSINK_CTRL_ASYNCFREQ_MASK <<
+ RVTRACE_RAMSINK_CTRL_ASYNCFREQ_SHIFT);
+ trram_ctrl |= priv->async_freq << RVTRACE_RAMSINK_CTRL_ASYNCFREQ_SHIFT;
+ gtrace_write32(comp->pdata, trram_ctrl, RVTRACE_COMPONENT_CTRL_OFFSET);
+
+ trram_ctrl = gtrace_read32(comp->pdata, RVTRACE_COMPONENT_CTRL_OFFSET);
+ if ((trram_ctrl >> RVTRACE_RAMSINK_CTRL_MEMFMT_SHIFT) & RVTRACE_RAMSINK_CTRL_MEMFMT_MASK) {
+ dev_err(&comp->dev, "only plain memory format is supported\n");
+ return -EINVAL;
+ }
+
+ ret = gtrace_enable_component(comp);
+ if (ret)
+ dev_err(&comp->dev, "failed to start ramsink.\n");
+
+ return ret;
+}
+
+static int rvtrace_ramsink_stop(struct gtrace_component *comp)
+{
+ int ret;
+
+ ret = gtrace_disable_component(comp);
+ if (ret) {
+ dev_err(&comp->dev, "failed to stop ramsink.\n");
+ return ret;
+ }
+
+ return rvtrace_comp_poll_empty(comp);
+}
+
+static void tbuf_to_pbuf_copy(struct trace_buf *src, struct trace_buf *dst, size_t size)
+{
+ size_t bytes_dst, bytes_src, bytes;
+ void *dst_addr, *src_addr;
+
+ /* If destination cannot hold entire source buffer then write only the latest data. */
+ if (dst->len < size) {
+ src->cur = (src->cur + size - dst->len) % src->len;
+ size = dst->len;
+ }
+
+ while (size) {
+ src_addr = src->base + src->cur;
+ dst_addr = dst->base + dst->cur;
+
+ /* Ensure that there are no OOB memory accesses */
+ if (dst->len - dst->cur < size)
+ bytes_dst = dst->len - dst->cur;
+ else
+ bytes_dst = size;
+
+ if (src->len - src->cur < size)
+ bytes_src = src->len - src->cur;
+ else
+ bytes_src = size;
+ bytes = min(bytes_dst, bytes_src);
+ memcpy(dst_addr, src_addr, bytes);
+ dst->cur = (dst->cur + bytes) % dst->len;
+ src->cur = (src->cur + bytes) % src->len;
+ size -= bytes;
+ }
+}
+
+static size_t rvtrace_ramsink_copyto_auxbuf(struct gtrace_component *comp,
+ struct gtrace_perf_auxbuf *buf,
+ u64 *format)
+{
+ struct rvtrace_ramsink_priv *priv = dev_get_drvdata(&comp->dev);
+ struct trace_buf src, dst;
+ size_t bytes = 0;
+ bool wrap;
+ u64 wp;
+
+ dst.base = buf->base;
+ dst.len = buf->length;
+ 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;
+
+ if (wrap) {
+ rvtrace_ramsink_write_pair(comp->pdata, priv->start, RVTRACE_RAMSINK_WPLOW_OFF);
+ src.cur = wp - priv->start;
+ priv->prev_wp = priv->start;
+ /*
+ * There is no way to tell if trRamWp wrapped around more than once. As a
+ * result priv->prev_wp can't be used and the entire buffer must be copied
+ * even though some data might be duplicated.
+ */
+ bytes = priv->size;
+ } else {
+ src.cur = priv->prev_wp - priv->start;
+ bytes = wp - priv->prev_wp;
+ priv->prev_wp = wp;
+ }
+
+ tbuf_to_pbuf_copy(&src, &dst, bytes);
+ dev_dbg(&comp->dev, "Copied %zu bytes\n", bytes);
+ return bytes;
+}
+
+static int rvtrace_ramsink_setup_buf(struct gtrace_component *comp,
+ struct rvtrace_ramsink_priv *priv)
+{
+ struct device *pdev = comp->pdata->dev;
+ u64 limit_max, end;
+ int ret;
+
+ /* Probe max value for limit register */
+ rvtrace_ramsink_write_pair(comp->pdata, U64_MAX, RVTRACE_RAMSINK_LIMITLOW_OFF);
+ limit_max = rvtrace_ramsink_read_pair(comp->pdata, RVTRACE_RAMSINK_LIMITLOW_OFF);
+ if (!limit_max) {
+ dev_err(&comp->dev, "trRamLimit register not implemented\n");
+ return -EINVAL;
+ }
+
+ /* Set DMA mask based on the maximum allowed limit address */
+ ret = dma_set_mask_and_coherent(pdev, DMA_BIT_MASK(fls64(limit_max)));
+ if (ret)
+ return ret;
+
+ priv->alloc_size = priv->size;
+ priv->alloc_va = dma_alloc_coherent(pdev, priv->alloc_size, &priv->alloc_start,
+ GFP_KERNEL);
+ if (!priv->alloc_va)
+ return -ENOMEM;
+
+ /*
+ * Check if DMA start and end addresses allocated by the kernel can be set in the ramsink
+ * start, limit registers respectively.
+ */
+ rvtrace_ramsink_write_pair(comp->pdata, priv->alloc_start, RVTRACE_RAMSINK_STARTLOW_OFF);
+ /* Limit address needs to be set to end - mem_access_width to avoid overflow */
+ end = priv->alloc_start + priv->alloc_size - priv->mem_acc_width;
+ rvtrace_ramsink_write_pair(comp->pdata, end, RVTRACE_RAMSINK_LIMITLOW_OFF);
+
+ priv->start = rvtrace_ramsink_read_pair(comp->pdata, RVTRACE_RAMSINK_STARTLOW_OFF);
+ priv->end = rvtrace_ramsink_read_pair(comp->pdata, RVTRACE_RAMSINK_LIMITLOW_OFF) +
+ priv->mem_acc_width;
+
+ /* Sanity check start, end addresses */
+ if (priv->start < priv->alloc_start ||
+ priv->end > priv->alloc_start + priv->alloc_size ||
+ priv->end <= priv->start ||
+ priv->end - priv->start < RVTRACE_RAMSINK_SIZE_MIN) {
+ dev_err(&comp->dev, "invalid ramsink configuration detected.\n");
+ ret = -EINVAL;
+ goto err_free;
+ }
+
+ if (priv->start != priv->alloc_start ||
+ priv->end != priv->alloc_start + priv->alloc_size)
+ dev_warn(&comp->dev, "ramsink start, end updated to %pad and %pad\n",
+ &priv->start, &priv->end);
+
+ priv->va = priv->alloc_va + (priv->start - priv->alloc_start);
+ priv->size = priv->end - priv->start;
+ priv->prev_wp = priv->start;
+ rvtrace_ramsink_write_pair(comp->pdata, priv->start, RVTRACE_RAMSINK_WPLOW_OFF);
+
+ return 0;
+
+err_free:
+ dma_free_coherent(pdev, priv->alloc_size, priv->alloc_va, priv->alloc_start);
+ return ret;
+}
+
+static int rvtrace_ramsink_setup(struct gtrace_component *comp)
+{
+ struct rvtrace_ramsink_priv *priv;
+ u32 trram_ctrl;
+ int ret;
+
+ priv = devm_kzalloc(&comp->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ /* Derive RAM sink memory size based on component implementation ID */
+ switch (comp->pdata->impid) {
+ default:
+ priv->size = SZ_1M;
+ priv->mode = MODE_SMEM;
+ priv->stop_on_wrap = false;
+ priv->async_freq = 2;
+ priv->mem_acc_width = RVTRACE_RAMSINK_MEM_ACC_WIDTH_MIN;
+ break;
+ }
+
+ if (priv->mode != MODE_SMEM) {
+ dev_err(&comp->dev, "only smem mode is supported\n");
+ return -EOPNOTSUPP;
+ }
+
+ trram_ctrl = gtrace_read32(comp->pdata, RVTRACE_COMPONENT_CTRL_OFFSET);
+ trram_ctrl &= ~BIT(RVTRACE_RAMSINK_CTRL_MODE_SHIFT);
+ trram_ctrl |= priv->mode << RVTRACE_RAMSINK_CTRL_MODE_SHIFT;
+ gtrace_write32(comp->pdata, trram_ctrl, RVTRACE_COMPONENT_CTRL_OFFSET);
+ trram_ctrl = gtrace_read32(comp->pdata, RVTRACE_COMPONENT_CTRL_OFFSET);
+ if (((trram_ctrl >> RVTRACE_RAMSINK_CTRL_MODE_SHIFT) &
+ RVTRACE_RAMSINK_CTRL_MODE_MASK) != priv->mode) {
+ dev_err(&comp->dev, "smem mode is not supported\n");
+ return -EINVAL;
+ }
+
+ ret = rvtrace_ramsink_setup_buf(comp, priv);
+ if (!ret)
+ dev_set_drvdata(&comp->dev, priv);
+
+ return ret;
+}
+
+static void rvtrace_ramsink_cleanup(struct gtrace_component *comp)
+{
+ struct rvtrace_ramsink_priv *priv = dev_get_drvdata(&comp->dev);
+
+ dma_free_coherent(comp->pdata->dev, priv->alloc_size, priv->alloc_va, priv->alloc_start);
+}
+
+static int rvtrace_ramsink_probe(struct gtrace_component *comp)
+{
+ int ret;
+
+ ret = rvtrace_ramsink_setup(comp);
+ if (ret)
+ return dev_err_probe(&comp->dev, ret, "failed to setup ramsink.\n");
+
+ return ret;
+}
+
+static void rvtrace_ramsink_remove(struct gtrace_component *comp)
+{
+ rvtrace_ramsink_cleanup(comp);
+}
+
+static const struct gtrace_component_id rvtrace_ramsink_ids[] = {
+ { .type = GTRACE_RVTRACE_RAMSINK,
+ .version = rvtrace_component_mkversion(1, 0), },
+ {},
+};
+
+static struct gtrace_driver rvtrace_ramsink_driver = {
+ .id_table = rvtrace_ramsink_ids,
+ .copyto_auxbuf = rvtrace_ramsink_copyto_auxbuf,
+ .stop = rvtrace_ramsink_stop,
+ .start = rvtrace_ramsink_start,
+ .probe = rvtrace_ramsink_probe,
+ .remove = rvtrace_ramsink_remove,
+ .driver = {
+ .name = "rvtrace-ramsink",
+ },
+};
+
+static int __init rvtrace_ramsink_init(void)
+{
+ return gtrace_register_driver(&rvtrace_ramsink_driver);
+}
+
+static void __exit rvtrace_ramsink_exit(void)
+{
+ gtrace_unregister_driver(&rvtrace_ramsink_driver);
+}
+
+module_init(rvtrace_ramsink_init);
+module_exit(rvtrace_ramsink_exit);
+
+/* Module information */
+MODULE_AUTHOR("Mayuresh Chitale");
+MODULE_DESCRIPTION("RISC-V E-Trace Ramsink Driver");
+MODULE_LICENSE("GPL");
--
2.43.0
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [PATCH v6 10/14] riscv: Enable DMA_RESTRICTED_POOL in defconfig
2026-10-01 5:22 [PATCH v6 00/14] Linux RISC-V trace framework and drivers Mayuresh Chitale
` (8 preceding siblings ...)
2026-10-01 5:22 ` [PATCH v6 09/14] gtrace: Add RISC-V Trace ramsink driver Mayuresh Chitale
@ 2026-10-01 5:22 ` Mayuresh Chitale
2026-10-01 5:22 ` [PATCH v6 11/14] gtrace: Add perf driver for tracing using perf tool Mayuresh Chitale
` (3 subsequent siblings)
13 siblings, 0 replies; 25+ messages in thread
From: Mayuresh Chitale @ 2026-10-01 5:22 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Palmer Dabbelt,
Paul Walmsley, Greg KH, Alexander Shishkin, Ian Rogers
Cc: Anup Patel, Alexandre Ghiti, Peter Zijlstra, Ingo Molnar,
Namhyung Kim, Mark Rutland, Jiri Olsa, Adrian Hunter,
Mayuresh Chitale, Anup Patel, Atish Patra, Andrew Jones,
Sunil V L, linux-riscv, devicetree, linux-kernel, Zane Leung
From: Anup Patel <anup.patel@oss.qualcomm.com>
The RISC-V ramsink trace component may have implementation specific
restrictions such that the component can only write trace data in
particular parts of DRAM.
Enable DMA_RESTRICTED_POOL in the defconfig so that dma_alloc_*()
and dma_free_*() APIs work for devices with DMA address restrictions.
Signed-off-by: Anup Patel <anup.patel@oss.qualcomm.com>
---
arch/riscv/configs/defconfig | 1 +
1 file changed, 1 insertion(+)
diff --git a/arch/riscv/configs/defconfig b/arch/riscv/configs/defconfig
index 04ae305d5511..7319421dcf7c 100644
--- a/arch/riscv/configs/defconfig
+++ b/arch/riscv/configs/defconfig
@@ -306,6 +306,7 @@ CONFIG_SECURITY_APPARMOR=y
CONFIG_DEFAULT_SECURITY_DAC=y
CONFIG_CRYPTO_USER_API_HASH=y
CONFIG_CRYPTO_DEV_VIRTIO=y
+CONFIG_DMA_RESTRICTED_POOL=y
CONFIG_PRINTK_TIME=y
CONFIG_DEBUG_KERNEL=y
CONFIG_DEBUG_FS=y
--
2.43.0
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [PATCH v6 11/14] gtrace: Add perf driver for tracing using perf tool
2026-10-01 5:22 [PATCH v6 00/14] Linux RISC-V trace framework and drivers Mayuresh Chitale
` (9 preceding siblings ...)
2026-10-01 5:22 ` [PATCH v6 10/14] riscv: Enable DMA_RESTRICTED_POOL in defconfig Mayuresh Chitale
@ 2026-10-01 5:22 ` Mayuresh Chitale
2026-10-01 5:44 ` sashiko-bot
2026-10-01 5:22 ` [PATCH v6 12/14] perf tools: Add RISC-V trace PMU record capabilities Mayuresh Chitale
` (2 subsequent siblings)
13 siblings, 1 reply; 25+ messages in thread
From: Mayuresh Chitale @ 2026-10-01 5:22 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Palmer Dabbelt,
Paul Walmsley, Greg KH, Alexander Shishkin, Ian Rogers
Cc: Mayuresh Chitale, Alexandre Ghiti, Peter Zijlstra, Ingo Molnar,
Namhyung Kim, Mark Rutland, Jiri Olsa, Adrian Hunter,
Mayuresh Chitale, Anup Patel, Atish Patra, Andrew Jones,
Sunil V L, linux-riscv, devicetree, linux-kernel, Anup Patel,
Zane Leung
Add perf driver for the gtrace similar to ARM Coresight and Hisilicon
PTT drivers. The driver adds 'gtrace' event descriptor which can be used
by the perf tool to record the trace data. The actual data format depends
on type of encoder device and optionally the PMU specific trace format
type.
Co-developed-by: Anup Patel <anup.patel@oss.qualcomm.com>
Signed-off-by: Anup Patel <anup.patel@oss.qualcomm.com>
Signed-off-by: Mayuresh Chitale <mayuresh.chitale@oss.qualcomm.com>
---
drivers/hwtracing/gtrace/Kconfig | 1 +
drivers/hwtracing/gtrace/Makefile | 2 +-
drivers/hwtracing/gtrace/gtrace-core.c | 15 +-
drivers/hwtracing/gtrace/gtrace-perf.c | 390 +++++++++++++++++++++++++
include/linux/gtrace.h | 3 +
5 files changed, 409 insertions(+), 2 deletions(-)
create mode 100644 drivers/hwtracing/gtrace/gtrace-perf.c
diff --git a/drivers/hwtracing/gtrace/Kconfig b/drivers/hwtracing/gtrace/Kconfig
index de4a3b533591..e5b62d65e2fd 100644
--- a/drivers/hwtracing/gtrace/Kconfig
+++ b/drivers/hwtracing/gtrace/Kconfig
@@ -3,6 +3,7 @@
menuconfig GTRACE
tristate "Generic Hardware Trace Support"
depends on OF
+ select PERF_EVENTS
help
This framework provides an architecture-neutral kernel interface
for hardware trace drivers. It builds a topological view of the
diff --git a/drivers/hwtracing/gtrace/Makefile b/drivers/hwtracing/gtrace/Makefile
index 31262cf5993d..33274a1ed0b8 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-y := gtrace-core.o gtrace-perf.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 acbeb3df2135..034987237abc 100644
--- a/drivers/hwtracing/gtrace/gtrace-core.c
+++ b/drivers/hwtracing/gtrace/gtrace-core.c
@@ -828,13 +828,26 @@ EXPORT_SYMBOL_GPL(__gtrace_register_driver);
static int __init gtrace_init(void)
{
+ int ret;
+
gtrace_init_type_idx();
- return bus_register(>race_bustype);
+ ret = bus_register(>race_bustype);
+ if (ret)
+ return ret;
+
+ ret = gtrace_perf_init();
+ if (ret) {
+ bus_unregister(>race_bustype);
+ return ret;
+ }
+
+ return 0;
}
static void __exit gtrace_exit(void)
{
+ gtrace_perf_exit();
bus_unregister(>race_bustype);
}
diff --git a/drivers/hwtracing/gtrace/gtrace-perf.c b/drivers/hwtracing/gtrace/gtrace-perf.c
new file mode 100644
index 000000000000..928f0405c6c1
--- /dev/null
+++ b/drivers/hwtracing/gtrace/gtrace-perf.c
@@ -0,0 +1,390 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2026 Qualcomm Technologies, Inc.
+ */
+
+#include <linux/bitfield.h>
+#include <linux/cpumask.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/list.h>
+#include <linux/mm.h>
+#include <linux/init.h>
+#include <linux/perf_event.h>
+#include <linux/vmalloc.h>
+#include <linux/percpu-defs.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/stringhash.h>
+#include <linux/types.h>
+#include <linux/workqueue.h>
+#include <linux/gtrace.h>
+
+#define GTRACE_PMU_NAME "gtrace"
+static struct pmu gtrace_pmu;
+static struct workqueue_struct *gtrace_wq;
+
+/**
+ * struct gtrace_event_data - generic hardware trace perf event data
+ * @work: Handle to free allocated memory outside IRQ context.
+ * @mask: Hold the CPU(s) this event was set for.
+ * @aux_hwid_done: Whether a CPU has emitted the TraceID packet or not.
+ * @path: An array of path, each slot for one CPU.
+ * @buf: Aux buffer / pages allocated by perf framework.
+ */
+struct gtrace_event_data {
+ struct work_struct work;
+ cpumask_t mask;
+ cpumask_t aux_hwid_done;
+ struct gtrace_path * __percpu *path;
+ struct gtrace_perf_auxbuf buf;
+};
+
+struct gtrace_ctxt {
+ struct perf_output_handle handle;
+ struct gtrace_event_data *event_data;
+};
+
+static DEFINE_PER_CPU(struct gtrace_ctxt, gtrace_ctxt);
+
+static void *alloc_event_data(int cpu)
+{
+ struct gtrace_event_data *event_data;
+ cpumask_t *mask;
+
+ event_data = kzalloc_obj(*event_data);
+ if (!event_data)
+ return NULL;
+
+ /* Update mask as per selected CPUs */
+ mask = &event_data->mask;
+ if (cpu != -1)
+ cpumask_set_cpu(cpu, mask);
+ else
+ cpumask_copy(mask, cpu_present_mask);
+
+ event_data->path = alloc_percpu(struct gtrace_path *);
+ if (!event_data->path) {
+ kfree(event_data);
+ return NULL;
+ }
+
+ return event_data;
+}
+
+static void gtrace_free_aux(void *data)
+{
+ struct gtrace_event_data *event_data = data;
+
+ queue_work(gtrace_wq, &event_data->work);
+}
+
+static struct gtrace_path **gtrace_event_cpu_path_ptr(struct gtrace_event_data *data,
+ int cpu)
+{
+ return per_cpu_ptr(data->path, cpu);
+}
+
+static void free_event_data(struct work_struct *work)
+{
+ struct gtrace_event_data *event_data;
+ struct gtrace_path *path;
+ cpumask_t *mask;
+ int cpu;
+
+ event_data = container_of(work, struct gtrace_event_data, work);
+ mask = &event_data->mask;
+ for_each_cpu(cpu, mask) {
+ path = *gtrace_event_cpu_path_ptr(event_data, cpu);
+ gtrace_destroy_path(path);
+ }
+ if (event_data->buf.base)
+ vunmap(event_data->buf.base);
+ free_percpu(event_data->path);
+ kfree(event_data);
+}
+
+static void *gtrace_setup_aux(struct perf_event *event, void **pages,
+ int nr_pages, bool overwrite)
+{
+ struct gtrace_event_data *event_data = NULL;
+ struct page **pagelist;
+ int cpu = event->cpu, i;
+ cpumask_t *mask;
+
+ event_data = alloc_event_data(cpu);
+ if (!event_data)
+ return NULL;
+
+ 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
+ */
+ for_each_cpu(cpu, mask) {
+ struct gtrace_component *src;
+ struct gtrace_path *path;
+
+ src = gtrace_cpu_source(cpu);
+ if (!src) {
+ cpumask_clear_cpu(cpu, mask);
+ continue;
+ }
+
+ path = gtrace_create_path(src, NULL, GTRACE_COMPONENT_MODE_PERF);
+ if (IS_ERR(path)) {
+ cpumask_clear_cpu(cpu, mask);
+ continue;
+ }
+
+ path->owner = task_pid_nr(event->owner);
+ *gtrace_event_cpu_path_ptr(event_data, cpu) = path;
+ }
+
+ /* If we don't have any CPUs ready for tracing, abort */
+ cpu = cpumask_first(&event_data->mask);
+ if (cpu >= nr_cpu_ids)
+ goto err;
+
+ pagelist = kcalloc(nr_pages, sizeof(*pagelist), GFP_KERNEL);
+ if (!pagelist)
+ goto err;
+
+ for (i = 0; i < nr_pages; i++)
+ pagelist[i] = virt_to_page(pages[i]);
+
+ event_data->buf.base = vmap(pagelist, nr_pages, VM_MAP, PAGE_KERNEL);
+ kfree(pagelist);
+ if (!event_data->buf.base)
+ goto err;
+
+ event_data->buf.nr_pages = nr_pages;
+ event_data->buf.length = nr_pages * PAGE_SIZE;
+ event_data->buf.pos = 0;
+ return event_data;
+err:
+ gtrace_free_aux(event_data);
+ 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)
+{
+}
+
+static void gtrace_event_destroy(struct perf_event *event)
+{
+}
+
+static int gtrace_event_init(struct perf_event *event)
+{
+ if (event->attr.type != gtrace_pmu.type)
+ return -EINVAL;
+
+ event->destroy = gtrace_event_destroy;
+ return 0;
+}
+
+static void gtrace_event_start(struct perf_event *event, int flags)
+{
+ struct gtrace_ctxt *ctxt = this_cpu_ptr(>race_ctxt);
+ struct perf_output_handle *handle = &ctxt->handle;
+ struct gtrace_event_data *event_data;
+ int cpu = smp_processor_id();
+ struct gtrace_path *path;
+
+ if (WARN_ON(ctxt->event_data))
+ goto fail;
+
+ /*
+ * Deal with the ring buffer API and get a handle on the
+ * session's information.
+ */
+ event_data = perf_aux_output_begin(handle, event);
+ if (!event_data)
+ goto fail;
+
+ if (!cpumask_test_cpu(cpu, &event_data->mask))
+ goto out;
+
+ event_data->buf.pos = handle->head % event_data->buf.length;
+ path = *gtrace_event_cpu_path_ptr(event_data, cpu);
+ if (!path) {
+ dev_err_ratelimited(gtrace_pmu.dev, "Error. Path not found on cpu %d\n", cpu);
+ goto fail_end;
+ }
+
+ if (gtrace_path_start(path)) {
+ dev_err_ratelimited(gtrace_pmu.dev, "Error. Tracing not started on cpu %d\n", cpu);
+ goto fail_end;
+ }
+
+ /*
+ * output cpu / trace ID in perf record, once for the lifetime
+ * of the event.
+ */
+ if (!cpumask_test_cpu(cpu, &event_data->aux_hwid_done)) {
+ cpumask_set_cpu(cpu, &event_data->aux_hwid_done);
+ perf_report_aux_output_id(event, cpu);
+ }
+
+out:
+ /* Tell the perf core the event is alive */
+ event->hw.state = 0;
+ ctxt->event_data = event_data;
+ return;
+fail_end:
+ perf_aux_output_end(handle, 0);
+fail:
+ event->hw.state = PERF_HES_STOPPED;
+}
+
+static void gtrace_event_stop(struct perf_event *event, int mode)
+{
+ struct gtrace_ctxt *ctxt = this_cpu_ptr(>race_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;
+ size_t size = 0;
+
+ if (event->hw.state == PERF_HES_STOPPED)
+ return;
+
+ if (handle->event && WARN_ON(perf_get_aux(handle) != ctxt->event_data))
+ return;
+
+ event_data = ctxt->event_data;
+ ctxt->event_data = NULL;
+
+ if (WARN_ON(!event_data))
+ return;
+
+ if (!cpumask_test_cpu(cpu, &event_data->mask))
+ goto out_end;
+
+ /* stop tracing */
+ path = *gtrace_event_cpu_path_ptr(event_data, cpu);
+ if (!path) {
+ dev_err_ratelimited(gtrace_pmu.dev, "Error. Path not found on cpu %d\n", cpu);
+ goto out_end;
+ }
+
+ if (gtrace_path_stop(path)) {
+ dev_err_ratelimited(gtrace_pmu.dev, "Error. Tracing not stopped on cpu %d\n", cpu);
+ goto out_end;
+ }
+
+ event->hw.state = PERF_HES_STOPPED;
+ 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);
+ 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);
+ if (size > handle->size) {
+ size = handle->size;
+ perf_aux_output_flag(handle, PERF_AUX_FLAG_TRUNCATED);
+ }
+ perf_aux_output_end(handle, size);
+ } else
+ WARN_ON(size);
+ }
+
+ return;
+
+out_end:
+ event->hw.state = PERF_HES_STOPPED;
+ if (handle->event && (mode & PERF_EF_UPDATE))
+ perf_aux_output_end(handle, 0);
+}
+
+static int gtrace_event_add(struct perf_event *event, int mode)
+{
+ struct hw_perf_event *hwc = &event->hw;
+ int ret = 0;
+
+ if (mode & PERF_EF_START) {
+ gtrace_event_start(event, 0);
+ if (hwc->state & PERF_HES_STOPPED)
+ ret = -EINVAL;
+ } else {
+ hwc->state = PERF_HES_STOPPED;
+ }
+
+ return ret;
+}
+
+static void gtrace_event_del(struct perf_event *event, int mode)
+{
+ gtrace_event_stop(event, PERF_EF_UPDATE);
+}
+
+PMU_FORMAT_ATTR(event, "config:0-0");
+
+static struct attribute *gtrace_pmu_formats_attr[] = {
+ &format_attr_event.attr,
+ NULL,
+};
+
+static struct attribute_group gtrace_pmu_format_group = {
+ .name = "format",
+ .attrs = gtrace_pmu_formats_attr,
+};
+
+static const struct attribute_group *gtrace_pmu_attr_groups[] = {
+ >race_pmu_format_group,
+ NULL,
+};
+
+int __init gtrace_perf_init(void)
+{
+ int ret;
+
+ gtrace_pmu.capabilities = (PERF_PMU_CAP_EXCLUSIVE | PERF_PMU_CAP_ITRACE);
+ gtrace_pmu.attr_groups = gtrace_pmu_attr_groups;
+ gtrace_pmu.task_ctx_nr = perf_sw_context;
+ gtrace_pmu.read = gtrace_event_read;
+ gtrace_pmu.event_init = gtrace_event_init;
+ gtrace_pmu.setup_aux = gtrace_setup_aux;
+ gtrace_pmu.free_aux = gtrace_free_aux;
+ gtrace_pmu.start = gtrace_event_start;
+ gtrace_pmu.stop = gtrace_event_stop;
+ gtrace_pmu.add = gtrace_event_add;
+ gtrace_pmu.del = gtrace_event_del;
+ gtrace_pmu.module = THIS_MODULE;
+
+ gtrace_wq = alloc_workqueue(GTRACE_PMU_NAME, WQ_UNBOUND, 0);
+ if (!gtrace_wq)
+ return -ENOMEM;
+
+ ret = perf_pmu_register(>race_pmu, GTRACE_PMU_NAME, -1);
+ if (ret)
+ destroy_workqueue(gtrace_wq);
+
+ return ret;
+}
+
+void __exit gtrace_perf_exit(void)
+{
+ perf_pmu_unregister(>race_pmu);
+ /* Wait for any pending free_event_data() before the module exits. */
+ destroy_workqueue(gtrace_wq);
+}
diff --git a/include/linux/gtrace.h b/include/linux/gtrace.h
index ed0e751faa91..da8ba3779193 100644
--- a/include/linux/gtrace.h
+++ b/include/linux/gtrace.h
@@ -326,4 +326,7 @@ static inline void gtrace_unregister_driver(struct gtrace_driver *gtdrv)
driver_unregister(>drv->driver);
}
+int gtrace_perf_init(void);
+void gtrace_perf_exit(void);
+
#endif /* __LINUX_GTRACE_H__ */
--
2.43.0
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [PATCH v6 12/14] perf tools: Add RISC-V trace PMU record capabilities
2026-10-01 5:22 [PATCH v6 00/14] Linux RISC-V trace framework and drivers Mayuresh Chitale
` (10 preceding siblings ...)
2026-10-01 5:22 ` [PATCH v6 11/14] gtrace: Add perf driver for tracing using perf tool Mayuresh Chitale
@ 2026-10-01 5:22 ` Mayuresh Chitale
2026-10-01 5:22 ` [PATCH v6 13/14] perf tools: Initial support for gtrace decoder Mayuresh Chitale
2026-10-01 5:22 ` [PATCH v6 14/14] MAINTAINERS: Add entry for RISC-V trace framework Mayuresh Chitale
13 siblings, 0 replies; 25+ messages in thread
From: Mayuresh Chitale @ 2026-10-01 5:22 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Palmer Dabbelt,
Paul Walmsley, Greg KH, Alexander Shishkin, Ian Rogers
Cc: Mayuresh Chitale, Alexandre Ghiti, Peter Zijlstra, Ingo Molnar,
Namhyung Kim, Mark Rutland, Jiri Olsa, Adrian Hunter,
Mayuresh Chitale, Anup Patel, Atish Patra, Andrew Jones,
Sunil V L, linux-riscv, devicetree, linux-kernel, Anup Patel,
Nutty Liu, Zane Leung
Introduce the required auxiliary API functions allowing the perf core
to interact with gtrace perf driver. On RISC-V systems the gtrace
PMU uses the RISC-V E-Trace components and optionally an ATB Bridge
component for connecting to ARM-side sinks, if it is present on the SoC.
Co-developed-by: Anup Patel <anup.patel@oss.qualcomm.com>
Signed-off-by: Anup Patel <anup.patel@oss.qualcomm.com>
Reviewed-by: Nutty Liu <nutty.liu@hotmail.com>
Reviewed-by: Adrian Hunter <adrian.hunter@intel.com>
Signed-off-by: Mayuresh Chitale <mayuresh.chitale@oss.qualcomm.com>
---
tools/perf/arch/riscv/util/Build | 1 +
tools/perf/arch/riscv/util/auxtrace.c | 221 ++++++++++++++++++++++++++
tools/perf/util/auxtrace.c | 1 +
tools/perf/util/auxtrace.h | 1 +
tools/perf/util/gtrace.h | 17 ++
5 files changed, 241 insertions(+)
create mode 100644 tools/perf/arch/riscv/util/auxtrace.c
create mode 100644 tools/perf/util/gtrace.h
diff --git a/tools/perf/arch/riscv/util/Build b/tools/perf/arch/riscv/util/Build
index 2328fb9a30a3..e07d5525ece6 100644
--- a/tools/perf/arch/riscv/util/Build
+++ b/tools/perf/arch/riscv/util/Build
@@ -1 +1,2 @@
perf-util-y += header.o
+perf-util-y += auxtrace.o
diff --git a/tools/perf/arch/riscv/util/auxtrace.c b/tools/perf/arch/riscv/util/auxtrace.c
new file mode 100644
index 000000000000..882632511d09
--- /dev/null
+++ b/tools/perf/arch/riscv/util/auxtrace.c
@@ -0,0 +1,221 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Risc-V E-Trace support
+ * Copyright (c) 2026 Qualcomm Technologies, Inc.
+ */
+
+#include <linux/kernel.h>
+#include <linux/types.h>
+#include <linux/bitops.h>
+#include <linux/log2.h>
+#include <linux/zalloc.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <time.h>
+
+#include <internal/lib.h>
+#include "../../../util/auxtrace.h"
+#include "../../../util/cpumap.h"
+#include "../../../util/debug.h"
+#include "../../../util/event.h"
+#include "../../../util/evlist.h"
+#include "../../../util/evsel.h"
+#include "../../../util/gtrace.h"
+#include "../../../util/parse-events.h"
+#include "../../../util/pmu.h"
+#include "../../../util/pmus.h"
+#include "../../../util/record.h"
+#include "../../../util/session.h"
+
+
+#define GTRACE_PMU_NAME "gtrace"
+#define KiB(x) ((x) * 1024)
+#define MiB(x) ((x) * 1024 * 1024)
+
+struct rvtrace_recording {
+ struct auxtrace_record itr;
+ struct perf_pmu *rvtrace_pmu;
+ struct evlist *evlist;
+};
+
+static size_t rvtrace_info_priv_size(struct auxtrace_record *itr __maybe_unused,
+ struct evlist *evlist __maybe_unused)
+{
+ return GTRACE_AUXTRACE_PRIV_SIZE;
+}
+
+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)
+{
+ struct rvtrace_recording *ptr = container_of(itr, struct rvtrace_recording, itr);
+ struct perf_pmu *rvtrace_pmu = ptr->rvtrace_pmu;
+
+ if (priv_size != GTRACE_AUXTRACE_PRIV_SIZE)
+ return -EINVAL;
+
+ auxtrace_info->type = PERF_AUXTRACE_GTRACE;
+ auxtrace_info->priv[0] = rvtrace_pmu->type;
+
+ return 0;
+}
+
+static int rvtrace_set_auxtrace_mmap_page(struct record_opts *opts)
+{
+ bool privileged = perf_event_paranoid_check(-1);
+
+ if (!opts->full_auxtrace)
+ return 0;
+
+ if (opts->full_auxtrace && !opts->auxtrace_mmap_pages) {
+ if (privileged) {
+ opts->auxtrace_mmap_pages = MiB(16) / page_size;
+ } else {
+ opts->auxtrace_mmap_pages = KiB(128) / page_size;
+ if (opts->mmap_pages == UINT_MAX)
+ opts->mmap_pages = KiB(256) / page_size;
+ }
+ }
+
+ /* Validate auxtrace_mmap_pages */
+ if (opts->auxtrace_mmap_pages) {
+ size_t sz = opts->auxtrace_mmap_pages * (size_t)page_size;
+ size_t min_sz = KiB(8);
+
+ if (sz < min_sz || !is_power_of_2(sz)) {
+ pr_err("Invalid mmap size : must be at least %zuKiB and a power of 2\n",
+ min_sz / 1024);
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+static int rvtrace_recording_options(struct auxtrace_record *itr, struct evlist *evlist,
+ struct record_opts *opts)
+{
+ struct rvtrace_recording *ptr = container_of(itr, struct rvtrace_recording, itr);
+ struct perf_pmu *rvtrace_pmu = ptr->rvtrace_pmu;
+ struct evsel *evsel, *rvtrace_evsel = NULL;
+ struct evsel *tracking_evsel;
+ int err;
+
+ ptr->evlist = evlist;
+ evlist__for_each_entry(evlist, evsel) {
+ if (evsel->core.attr.type == rvtrace_pmu->type) {
+ if (rvtrace_evsel) {
+ pr_err("There may be only one " GTRACE_PMU_NAME " event\n");
+ return -EINVAL;
+ }
+ evsel->core.attr.freq = 0;
+ evsel->core.attr.sample_period = 1;
+ evsel->needs_auxtrace_mmap = true;
+ rvtrace_evsel = evsel;
+ opts->full_auxtrace = true;
+ }
+ }
+
+ err = rvtrace_set_auxtrace_mmap_page(opts);
+ if (err)
+ return err;
+ /*
+ * To obtain the auxtrace buffer file descriptor, the auxtrace event
+ * must come first.
+ */
+ evlist__to_front(evlist, rvtrace_evsel);
+ evsel__set_sample_bit(rvtrace_evsel, TIME);
+
+ /* Add dummy event to keep tracking */
+ err = parse_event(evlist, "dummy:u");
+ if (err)
+ return err;
+
+ tracking_evsel = evlist__last(evlist);
+ evlist__set_tracking_event(evlist, tracking_evsel);
+
+ tracking_evsel->core.attr.freq = 0;
+ tracking_evsel->core.attr.sample_period = 1;
+ evsel__set_sample_bit(tracking_evsel, TIME);
+
+ return 0;
+}
+
+static u64 rvtrace_reference(struct auxtrace_record *itr __maybe_unused)
+{
+ return 0;
+}
+
+static void rvtrace_recording_free(struct auxtrace_record *itr)
+{
+ struct rvtrace_recording *ptr = container_of(itr, struct rvtrace_recording, itr);
+
+ free(ptr);
+}
+
+static struct auxtrace_record *rvtrace_recording_init(int *err, struct perf_pmu *rvtrace_pmu)
+{
+ struct rvtrace_recording *ptr;
+
+ if (!rvtrace_pmu) {
+ *err = -ENODEV;
+ return NULL;
+ }
+
+ ptr = zalloc(sizeof(*ptr));
+ if (!ptr) {
+ *err = -ENOMEM;
+ return NULL;
+ }
+
+ ptr->rvtrace_pmu = rvtrace_pmu;
+ ptr->itr.recording_options = rvtrace_recording_options;
+ ptr->itr.info_priv_size = rvtrace_info_priv_size;
+ ptr->itr.info_fill = rvtrace_info_fill;
+ ptr->itr.free = rvtrace_recording_free;
+ ptr->itr.reference = rvtrace_reference;
+ ptr->itr.read_finish = auxtrace_record__read_finish;
+ ptr->itr.alignment = 0;
+
+ *err = 0;
+ return &ptr->itr;
+}
+
+static struct perf_pmu *find_pmu_for_event(struct perf_pmu **pmus,
+ int pmu_nr, struct evsel *evsel)
+{
+ int i;
+
+ if (!pmus)
+ return NULL;
+
+ for (i = 0; i < pmu_nr; i++) {
+ if (evsel->core.attr.type == pmus[i]->type)
+ return pmus[i];
+ }
+
+ return NULL;
+}
+
+struct auxtrace_record *auxtrace_record__init(struct evlist *evlist, int *err)
+{
+ struct perf_pmu *rvtrace_pmu = NULL;
+ struct perf_pmu *found_pmu = NULL;
+ struct evsel *evsel;
+
+ if (!evlist)
+ return NULL;
+
+ rvtrace_pmu = perf_pmus__find(GTRACE_PMU_NAME);
+ evlist__for_each_entry(evlist, evsel) {
+ if (rvtrace_pmu && !found_pmu)
+ found_pmu = find_pmu_for_event(&rvtrace_pmu, 1, evsel);
+ }
+
+ if (found_pmu)
+ return rvtrace_recording_init(err, rvtrace_pmu);
+
+ *err = 0;
+ return NULL;
+}
diff --git a/tools/perf/util/auxtrace.c b/tools/perf/util/auxtrace.c
index aa749e1c3036..1ecdde597153 100644
--- a/tools/perf/util/auxtrace.c
+++ b/tools/perf/util/auxtrace.c
@@ -1432,6 +1432,7 @@ int perf_event__process_auxtrace_info(const struct perf_tool *tool __maybe_unuse
case PERF_AUXTRACE_VPA_DTL:
err = powerpc_vpadtl_process_auxtrace_info(event, session);
break;
+ case PERF_AUXTRACE_GTRACE:
case PERF_AUXTRACE_UNKNOWN:
default:
return -EINVAL;
diff --git a/tools/perf/util/auxtrace.h b/tools/perf/util/auxtrace.h
index 6947f3f284c0..42b1481ec894 100644
--- a/tools/perf/util/auxtrace.h
+++ b/tools/perf/util/auxtrace.h
@@ -46,6 +46,7 @@ enum auxtrace_type {
PERF_AUXTRACE_S390_CPUMSF,
PERF_AUXTRACE_HISI_PTT,
PERF_AUXTRACE_VPA_DTL,
+ PERF_AUXTRACE_GTRACE,
};
enum itrace_period_type {
diff --git a/tools/perf/util/gtrace.h b/tools/perf/util/gtrace.h
new file mode 100644
index 000000000000..343d2e9d4fa1
--- /dev/null
+++ b/tools/perf/util/gtrace.h
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2026 Qualcomm Technologies, Inc.
+ */
+
+#ifndef INCLUDE__UTIL_PERF_GTRACE_H__
+#define INCLUDE__UTIL_PERF_GTRACE_H__
+
+#include <linux/types.h>
+#include "util/event.h"
+
+struct perf_session;
+
+#define GTRACE_AUXTRACE_PRIV_SIZE sizeof(u64)
+
+#endif
+
--
2.43.0
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [PATCH v6 13/14] perf tools: Initial support for gtrace decoder
2026-10-01 5:22 [PATCH v6 00/14] Linux RISC-V trace framework and drivers Mayuresh Chitale
` (11 preceding siblings ...)
2026-10-01 5:22 ` [PATCH v6 12/14] perf tools: Add RISC-V trace PMU record capabilities Mayuresh Chitale
@ 2026-10-01 5:22 ` Mayuresh Chitale
2026-10-01 5:35 ` sashiko-bot
2026-10-01 5:22 ` [PATCH v6 14/14] MAINTAINERS: Add entry for RISC-V trace framework Mayuresh Chitale
13 siblings, 1 reply; 25+ messages in thread
From: Mayuresh Chitale @ 2026-10-01 5:22 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Palmer Dabbelt,
Paul Walmsley, Greg KH, Alexander Shishkin, Ian Rogers
Cc: Mayuresh Chitale, Alexandre Ghiti, Peter Zijlstra, Ingo Molnar,
Namhyung Kim, Mark Rutland, Jiri Olsa, Adrian Hunter,
Mayuresh Chitale, Anup Patel, Atish Patra, Andrew Jones,
Sunil V L, linux-riscv, devicetree, linux-kernel, Anup Patel,
Zane Leung
Add bare bones support for gtrace decoder so that the data received
from the hardware by the gtrace perf driver can be written to the
perf record output file. Actual decoding of the trace payload is not yet
implemented.
Co-developed-by: Anup Patel <anup.patel@oss.qualcomm.com>
Signed-off-by: Anup Patel <anup.patel@oss.qualcomm.com>
Reviewed-by: Adrian Hunter <adrian.hunter@intel.com>
Signed-off-by: Mayuresh Chitale <mayuresh.chitale@oss.qualcomm.com>
---
tools/perf/util/Build | 1 +
tools/perf/util/auxtrace.c | 3 +
tools/perf/util/gtrace-decoder.c | 95 ++++++++++++++++++++++++++++++++
tools/perf/util/gtrace.h | 1 +
4 files changed, 100 insertions(+)
create mode 100644 tools/perf/util/gtrace-decoder.c
diff --git a/tools/perf/util/Build b/tools/perf/util/Build
index b26a0b1ddfa3..d25140a0d64f 100644
--- a/tools/perf/util/Build
+++ b/tools/perf/util/Build
@@ -149,6 +149,7 @@ perf-util-y += cs-etm.o
perf-util-y += cs-etm-decoder/
endif
perf-util-y += cs-etm-base.o
+perf-util-y += gtrace-decoder.o
perf-util-y += parse-branch-options.o
perf-util-y += parse-regs-options.o
diff --git a/tools/perf/util/auxtrace.c b/tools/perf/util/auxtrace.c
index 1ecdde597153..ba750d089a7a 100644
--- a/tools/perf/util/auxtrace.c
+++ b/tools/perf/util/auxtrace.c
@@ -54,6 +54,7 @@
#include "arm-spe.h"
#include "hisi-ptt.h"
#include "s390-cpumsf.h"
+#include "gtrace.h"
#include "util/mmap.h"
#include "powerpc-vpadtl.h"
@@ -1433,6 +1434,8 @@ int perf_event__process_auxtrace_info(const struct perf_tool *tool __maybe_unuse
err = powerpc_vpadtl_process_auxtrace_info(event, session);
break;
case PERF_AUXTRACE_GTRACE:
+ err = gtrace__process_auxtrace_info(event, session);
+ break;
case PERF_AUXTRACE_UNKNOWN:
default:
return -EINVAL;
diff --git a/tools/perf/util/gtrace-decoder.c b/tools/perf/util/gtrace-decoder.c
new file mode 100644
index 000000000000..c25fbfc61928
--- /dev/null
+++ b/tools/perf/util/gtrace-decoder.c
@@ -0,0 +1,95 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Generic hardware trace (gtrace) decoder
+ * Copyright (c) 2026 Qualcomm Technologies, Inc.
+ */
+
+#include <errno.h>
+#include <inttypes.h>
+#include <stdlib.h>
+#include <internal/lib.h>
+#include <linux/zalloc.h>
+#include "evlist.h"
+#include "session.h"
+#include "gtrace.h"
+
+struct gtrace_decoder {
+ struct auxtrace auxtrace;
+ u32 auxtrace_type;
+ struct perf_session *session;
+ struct machine *machine;
+ u32 pmu_type;
+};
+
+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)
+{
+ 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)
+{
+ return 0;
+}
+
+static int gtrace_flush(struct perf_session *session __maybe_unused,
+ const struct perf_tool *tool __maybe_unused)
+{
+ return 0;
+}
+
+static void gtrace_free_events(struct perf_session *session __maybe_unused)
+{
+}
+
+static void gtrace_free(struct perf_session *session)
+{
+ struct gtrace_decoder *ptr = container_of(session->auxtrace, struct gtrace_decoder,
+ auxtrace);
+
+ session->auxtrace = NULL;
+ free(ptr);
+}
+
+static bool gtrace_evsel_is_auxtrace(struct perf_session *session,
+ struct evsel *evsel)
+{
+ struct gtrace_decoder *ptr = container_of(session->auxtrace,
+ struct gtrace_decoder, auxtrace);
+
+ return evsel->core.attr.type == ptr->pmu_type;
+}
+
+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))
+ return -EINVAL;
+
+ ptr = zalloc(sizeof(*ptr));
+ if (!ptr)
+ return -ENOMEM;
+
+ ptr->session = session;
+ ptr->machine = &session->machines.host;
+ ptr->auxtrace_type = auxtrace_info->type;
+ ptr->pmu_type = auxtrace_info->priv[0];
+
+ ptr->auxtrace.process_event = gtrace_process_event;
+ ptr->auxtrace.process_auxtrace_event = gtrace_process_auxtrace_event;
+ ptr->auxtrace.flush_events = gtrace_flush;
+ ptr->auxtrace.free_events = gtrace_free_events;
+ ptr->auxtrace.free = gtrace_free;
+ ptr->auxtrace.evsel_is_auxtrace = gtrace_evsel_is_auxtrace;
+ session->auxtrace = &ptr->auxtrace;
+
+ return 0;
+}
diff --git a/tools/perf/util/gtrace.h b/tools/perf/util/gtrace.h
index 343d2e9d4fa1..02db4f2ff9f5 100644
--- a/tools/perf/util/gtrace.h
+++ b/tools/perf/util/gtrace.h
@@ -13,5 +13,6 @@ struct perf_session;
#define GTRACE_AUXTRACE_PRIV_SIZE sizeof(u64)
+int gtrace__process_auxtrace_info(union perf_event *event, struct perf_session *session);
#endif
--
2.43.0
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [PATCH v6 14/14] MAINTAINERS: Add entry for RISC-V trace framework
2026-10-01 5:22 [PATCH v6 00/14] Linux RISC-V trace framework and drivers Mayuresh Chitale
` (12 preceding siblings ...)
2026-10-01 5:22 ` [PATCH v6 13/14] perf tools: Initial support for gtrace decoder Mayuresh Chitale
@ 2026-10-01 5:22 ` Mayuresh Chitale
13 siblings, 0 replies; 25+ messages in thread
From: Mayuresh Chitale @ 2026-10-01 5:22 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Palmer Dabbelt,
Paul Walmsley, Greg KH, Alexander Shishkin, Ian Rogers
Cc: Anup Patel, Alexandre Ghiti, Peter Zijlstra, Ingo Molnar,
Namhyung Kim, Mark Rutland, Jiri Olsa, Adrian Hunter,
Mayuresh Chitale, Anup Patel, Atish Patra, Andrew Jones,
Sunil V L, linux-riscv, devicetree, linux-kernel, Zane Leung
From: Anup Patel <anup.patel@oss.qualcomm.com>
Add Mayuresh and myself as maintainers for the gtrace framework and
its RISC-V trace drivers.
Signed-off-by: Anup Patel <anup.patel@oss.qualcomm.com>
---
MAINTAINERS | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 360977678f70..8b5bd2815690 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -23656,6 +23656,17 @@ F: include/dt-bindings/power/thead,th1520-power.h
F: include/dt-bindings/reset/thead,th1520-reset.h
F: include/linux/firmware/thead/thead,th1520-aon.h
+GTRACE FRAMEWORK AND RISC-V TRACE DRIVERS
+M: Mayuresh Chitale <mchitale@gmail.com>
+M: Anup Patel <anup@brainfault.org>
+L: linux-riscv@lists.infradead.org
+S: Maintained
+F: Documentation/devicetree/bindings/riscv/riscv,trace-component.yaml
+F: drivers/hwtracing/gtrace/*
+F: include/linux/gtrace*
+F: tools/perf/arch/riscv/util/auxtrace.c
+F: tools/perf/util/gtrace*
+
RNBD BLOCK DRIVERS
M: Md. Haris Iqbal <haris.iqbal@ionos.com>
M: Jack Wang <jinpu.wang@ionos.com>
--
2.43.0
^ permalink raw reply related [flat|nested] 25+ messages in thread
* Re: [PATCH v6 01/14] dt-bindings: Add RISC-V trace component bindings
2026-10-01 5:22 ` [PATCH v6 01/14] dt-bindings: Add RISC-V trace component bindings Mayuresh Chitale
@ 2026-10-01 5:33 ` sashiko-bot
0 siblings, 0 replies; 25+ messages in thread
From: sashiko-bot @ 2026-10-01 5:33 UTC (permalink / raw)
To: Mayuresh Chitale; +Cc: conor+dt, devicetree, robh
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Low] dt-bindings: riscv: arbitrary 8-port limitation in trace component schema
- [Low] dt-bindings: riscv: missing reciprocal remote-endpoint in trace example
--
commit cd6c17f8ffc8a03ed1ca9c2518f088b9871f8ccc
Author: Anup Patel <anup.patel@oss.qualcomm.com>
dt-bindings: Add RISC-V trace component bindings
This adds device tree bindings for the memory mapped RISC-V trace components
which support both the RISC-V efficient trace (E-trace) and Nexus-based
trace (N-trace) protocols.
> diff --git a/Documentation/devicetree/bindings/riscv/riscv,trace-component.yaml b/Documentation/devicetree/bindings/riscv/riscv,trace-component.yaml
> new file mode 100644
> index 0000000000000..bb519bc4a163e
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/riscv/riscv,trace-component.yaml
[ ... ]
> + in-ports:
> + $ref: /schemas/graph.yaml#/properties/ports
> + patternProperties:
> + '^port(@[0-7])?$':
[Severity: Low]
Does this unintentionally restrict the trace component schema to a maximum of 8
input ports? The commit message mentions that RISC-V trace components have an
implementation-specific directed acyclic graph style interdependency. Since the
RISC-V specification does not enforce an 8-port limit, any future or existing
SoC implementation featuring a funnel with 9 or more ports (e.g., port@8 or
port@f) will artificially fail dt-schema validation.
Could the pattern be updated to use standard hexadecimal device tree unit
addresses, such as '^port(@[0-9a-f]+)?$'?
> + description: Input connections from RISC-V trace component
> + $ref: /schemas/graph.yaml#/properties/port
> +
> + out-ports:
> + $ref: /schemas/graph.yaml#/properties/ports
> + patternProperties:
> + '^port(@[0-7])?$':
[Severity: Low]
Similar to the in-ports property above, does this pattern artificially limit
the output connections to 8? Updating the regex to support standard hex unit
addresses would prevent validation failures for components with more than 8
output ports.
> + description: Output connections from RISC-V trace component
> + $ref: /schemas/graph.yaml#/properties/port
[ ... ]
> + trace@c001000 {
> + compatible = "qemu,trace-component", "riscv,trace-component";
> + reg = <0xc001000 0x1000>;
> + cpus = <&CPU0>;
> +
> + in-ports {
> + port {
> + CPU0_RAMSINK_INPUT: endpoint {
> + };
[Severity: Low]
Is there a missing reciprocal remote-endpoint property in this example node?
Standard device tree graph bindings specify that links between components must
be bidirectional when both nodes are present in the device tree.
In the example above, the encoder (trace@c000000) correctly points to the sink
by defining remote-endpoint = <&CPU0_RAMSINK_INPUT>;. However, the
corresponding ramsink endpoint (CPU0_RAMSINK_INPUT) is completely empty and
omits remote-endpoint = <&CPU0_ENCODER_OUTPUT>;.
The kernel's of_graph API and dtc parsing tools rely on bidirectional
references to traverse the trace topology. Omitting the back-reference breaks
backward traversal and causes dtc compiler warnings for missing bidirectional
links.
> + };
> + };
> + };
[ ... ]
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261001052243.3042627-1-mayuresh.chitale@oss.qualcomm.com?part=1
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH v6 06/14] gtrace: Add RISC-V Trace encoder driver
2026-10-01 5:22 ` [PATCH v6 06/14] gtrace: Add RISC-V Trace encoder driver Mayuresh Chitale
@ 2026-10-01 5:34 ` sashiko-bot
0 siblings, 0 replies; 25+ messages in thread
From: sashiko-bot @ 2026-10-01 5:34 UTC (permalink / raw)
To: Mayuresh Chitale; +Cc: robh, conor+dt, devicetree
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] gtrace: missing INHIBITSRC restore in rvtrace_encoder_start()
--
commit 89ac751d2a47352255588950ac5d611d8f1d501f
Author: Mayuresh Chitale <mayuresh.chitale@oss.qualcomm.com>
gtrace: Add RISC-V Trace encoder driver
This commit adds the initial implementation of the RISC-V E-Trace encoder
driver. The encoder generates trace data for its bound CPU as defined in the
RISC-V Trace Control Interface specification.
> diff --git a/drivers/hwtracing/gtrace/rvtrace-encoder.c b/drivers/hwtracing/gtrace/rvtrace-encoder.c
> new file mode 100644
> index 0000000000000..1f06b1a2b4128
> --- /dev/null
> +++ b/drivers/hwtracing/gtrace/rvtrace-encoder.c
[ ... ]
> +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;
> + int ret;
> + u32 val;
> +
> + if (priv->srcbits)
> + rvtrace_encoder_set_srcid(pdata, priv->srcbits);
> +
> + ret = rvtrace_encoder_set_format(pdata);
[Severity: Medium]
Does this skip restoring the INHIBITSRC bit when priv->srcbits is 0?
If the device loses context (for example, during power state changes when the
component is disabled and re-enabled) and tracing is started on a CPU where
source ID tracing is unsupported, could the hardware emit trace packets with
uninitialized or garbage source IDs?
During rvtrace_encoder_probe(), INHIBITSRC is explicitly set when
priv->srcbits is 0:
if (!priv->srcbits) {
...
val = gtrace_read32(pdata, RVTRACE_COMPONENT_CTRL_OFFSET);
val |= BIT(RVTRACE_COMPONENT_CTRL_INHIBITSRC_SHIFT);
gtrace_write32(pdata, val, RVTRACE_COMPONENT_CTRL_OFFSET);
}
Should rvtrace_encoder_start() also restore this state to ensure complete
hardware state initialization?
> + if (ret) {
> + dev_err(&comp->dev, "failed to set format %d\n", pdata->format);
> + return ret;
> + }
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261001052243.3042627-1-mayuresh.chitale@oss.qualcomm.com?part=6
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH v6 13/14] perf tools: Initial support for gtrace decoder
2026-10-01 5:22 ` [PATCH v6 13/14] perf tools: Initial support for gtrace decoder Mayuresh Chitale
@ 2026-10-01 5:35 ` sashiko-bot
0 siblings, 0 replies; 25+ messages in thread
From: sashiko-bot @ 2026-10-01 5:35 UTC (permalink / raw)
To: Mayuresh Chitale; +Cc: robh, conor+dt, devicetree
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Medium] perf: memory leak of gtrace_decoder on duplicate auxtrace info
- [Low] perf: missing explicit <stdbool.h> inclusion for musl compatibility
--
commit fdb0fe6a9d0684b5f29d2d2a91c37ec24db2cc06
Author: Mayuresh Chitale <mayuresh.chitale@oss.qualcomm.com>
perf tools: Initial support for gtrace decoder
Add bare bones support for gtrace decoder so that the data received
from the hardware by the gtrace perf driver can be written to the
perf record output file. Actual decoding of the trace payload is not yet
implemented.
> diff --git a/tools/perf/util/gtrace-decoder.c b/tools/perf/util/gtrace-decoder.c
> new file mode 100644
> index 0000000000000..c25fbfc61928d
> --- /dev/null
> +++ b/tools/perf/util/gtrace-decoder.c
> @@ -0,0 +1,95 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Generic hardware trace (gtrace) decoder
> + * Copyright (c) 2026 Qualcomm Technologies, Inc.
> + */
> +
> +#include <errno.h>
> +#include <inttypes.h>
> +#include <stdlib.h>
> +#include <internal/lib.h>
> +#include <linux/zalloc.h>
> +#include "evlist.h"
> +#include "session.h"
> +#include "gtrace.h"
[ ... ]
> +static bool gtrace_evsel_is_auxtrace(struct perf_session *session,
> + struct evsel *evsel)
> +{
[Severity: Low]
Does this file need an explicit inclusion of <stdbool.h>?
The gtrace_evsel_is_auxtrace() function signature requires the bool type,
but the file fails to explicitly include <stdbool.h>. This might lead to
build failures on musl libc systems where implicit inclusions differ from
glibc.
> + struct gtrace_decoder *ptr = container_of(session->auxtrace,
> + struct gtrace_decoder, auxtrace);
> +
> + return evsel->core.attr.type == ptr->pmu_type;
> +}
[ ... ]
> +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))
> + return -EINVAL;
> +
> + ptr = zalloc(sizeof(*ptr));
> + if (!ptr)
> + return -ENOMEM;
> +
> + ptr->session = session;
> + ptr->machine = &session->machines.host;
> + ptr->auxtrace_type = auxtrace_info->type;
> + ptr->pmu_type = auxtrace_info->priv[0];
> +
> + ptr->auxtrace.process_event = gtrace_process_event;
> + ptr->auxtrace.process_auxtrace_event = gtrace_process_auxtrace_event;
> + ptr->auxtrace.flush_events = gtrace_flush;
> + ptr->auxtrace.free_events = gtrace_free_events;
> + ptr->auxtrace.free = gtrace_free;
> + ptr->auxtrace.evsel_is_auxtrace = gtrace_evsel_is_auxtrace;
> + session->auxtrace = &ptr->auxtrace;
[Severity: Medium]
Can this leak the struct gtrace_decoder pointer?
If perf_event__process_auxtrace_info() processes multiple
PERF_RECORD_AUXTRACE_INFO events for GTRACE (e.g. in a malformed or
concatenated perf.data file), gtrace__process_auxtrace_info() will be
called repeatedly.
Each call allocates a new gtrace_decoder via zalloc() and unconditionally
overwrites session->auxtrace here, without verifying if one was already
present or freeing the previous pointer.
> +
> + return 0;
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261001052243.3042627-1-mayuresh.chitale@oss.qualcomm.com?part=13
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH v6 04/14] gtrace: Add functions to create/destroy a trace component path
2026-10-01 5:22 ` [PATCH v6 04/14] gtrace: Add functions to create/destroy a trace component path Mayuresh Chitale
@ 2026-10-01 5:36 ` sashiko-bot
0 siblings, 0 replies; 25+ messages in thread
From: sashiko-bot @ 2026-10-01 5:36 UTC (permalink / raw)
To: Mayuresh Chitale; +Cc: devicetree, robh, conor+dt
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] gtrace: lockless driver callbacks lead to unmapped memory execution
- [High] gtrace: gtrace_cpu_source returns component without holding reference
--
commit aae09d737c66acc0654dfd58c7118f63245ce4b1
Author: Anup Patel <anup.patel@oss.qualcomm.com>
gtrace: Add functions to create/destroy a trace component path
This patch adds functions to create and destroy a trace component path.
A trace component path represents a chain of trace components connected
to each other, which will be used by gtrace perf support.
> diff --git a/drivers/hwtracing/gtrace/gtrace-core.c b/drivers/hwtracing/gtrace/gtrace-core.c
> index d374c679a6498..f750025104ec9 100644
> --- a/drivers/hwtracing/gtrace/gtrace-core.c
> +++ b/drivers/hwtracing/gtrace/gtrace-core.c
[ ... ]
> struct gtrace_component *gtrace_cpu_source(unsigned int cpu)
> {
> if (!cpu_present(cpu))
> return NULL;
>
> return per_cpu(gtrace_cpu_source_comp, cpu);
> }
[Severity: High]
Does this return the component without holding a reference to it?
If a caller operates on this pointer without pinning it, and the component is
concurrently unregistered via gtrace_unregister_component(), could this lead
to a use-after-free? When the caller later passes this pointer to
gtrace_create_path(), it would be dereferenced during the walk of output
components.
Should this hold gtrace_mutex or take a reference via get_device() before
returning the component?
[ ... ]
> +static int gtrace_assign_trace_id(struct gtrace_path *path)
> +{
> + const struct gtrace_driver *gtdrv;
> + struct gtrace_component *comp;
> + struct gtrace_path_node *node;
> + int trace_id;
> +
> + list_for_each_entry(node, &path->comp_list, head) {
> + comp = node->comp;
> + gtdrv = to_gtrace_driver(comp->dev.driver);
> +
> + if (!gtdrv || !gtdrv->get_trace_id)
> + continue;
> +
> + trace_id = gtdrv->get_trace_id(comp, path->mode);
[Severity: High]
Can this locklessly access the driver and execute unmapped memory?
Taking a device reference during path creation prevents the device memory from
being freed, but does it prevent the module from unloading? If the component
module is unloaded concurrently, comp->dev.driver is cleared and the module
code is freed.
If a racing thread fetches the driver pointer just prior to unbind, could it
subsequently invoke gtdrv->get_trace_id() after the function has been unmapped,
resulting in a kernel panic?
> + if (trace_id > 0) {
> + path->trace_id = trace_id;
> + return 0;
> + } else if (trace_id < 0) {
[ ... ]
> +static bool gtrace_path_ready(struct gtrace_path *path)
> +{
> + struct gtrace_comp_priv *cpriv;
> + struct gtrace_path_node *node;
> +
> + list_for_each_entry(node, &path->comp_list, head) {
> + cpriv = to_gtrace_comp_priv(node->comp);
> + if (!cpriv->ready)
> + return false;
[Severity: High]
Is it safe to read cpriv->ready here without holding the device_lock?
If the driver is concurrently unbound, could this be reading a value that is
no longer protected against modification?
> + }
> +
> + return true;
> +}
[ ... ]
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261001052243.3042627-1-mayuresh.chitale@oss.qualcomm.com?part=4
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH v6 05/14] gtrace: Add functions to start/stop tracing on a component path
2026-10-01 5:22 ` [PATCH v6 05/14] gtrace: Add functions to start/stop tracing on a " Mayuresh Chitale
@ 2026-10-01 5:36 ` sashiko-bot
0 siblings, 0 replies; 25+ messages in thread
From: sashiko-bot @ 2026-10-01 5:36 UTC (permalink / raw)
To: Mayuresh Chitale; +Cc: devicetree, robh, conor+dt
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] gtrace: UAF in comp_start/stop due to missing module/device locks
--
commit ed6a9060977521b7cc68908d59a7104ec2d2492f
Author: Mayuresh Chitale <mayuresh.chitale@oss.qualcomm.com>
gtrace: Add functions to start/stop tracing on a component path
The perf driver framework needs to be able to start / stop all components
in a trace component path during its operation. Add gtrace_path_start()
and gtrace_path_stop() functions for this purpose.
> diff --git a/drivers/hwtracing/gtrace/gtrace-core.c b/drivers/hwtracing/gtrace/gtrace-core.c
> index f750025104ec9..d7f1df90aa31d 100644
> --- a/drivers/hwtracing/gtrace/gtrace-core.c
> +++ b/drivers/hwtracing/gtrace/gtrace-core.c
[ ... ]
> @@ -611,6 +621,100 @@ static void gtrace_release_path_nodes(struct gtrace_path *path)
> }
> }
>
> +static int __gtrace_comp_start(struct gtrace_component *comp, pid_t owner)
> +{
> + struct gtrace_comp_priv *cpriv = to_gtrace_comp_priv(comp);
> + const struct gtrace_driver *gtdrv = to_gtrace_driver(comp->dev.driver);
> + unsigned long flags;
> + int ret = 0;
> +
> + if (!gtdrv)
> + return -ENODEV;
> +
> + raw_spin_lock_irqsave(&cpriv->lock, flags);
[Severity: High]
Does this lockless driver check create a race with device unbind?
If a privileged user writes to a component's sysfs unbind file or unloads
its module while perf tracing is active, the driver core calls gtrace_remove()
and sets comp->dev.driver to NULL.
If __gtrace_comp_start() is concurrently called from perf_event_start() in
IRQ context, it could fetch the driver pointer here locklessly just before
it is cleared.
> + if (cpriv->start_count) {
> + if (cpriv->owner != owner) {
> + ret = -EBUSY;
> + goto out;
> + }
> + } else {
> + if (gtdrv->start) {
> + ret = gtdrv->start(comp);
[Severity: High]
Could this result in a use-after-free or a panic?
If the race described above occurs, the cached gtdrv pointer might now
point to a freed structure or unmapped driver text, causing a panic when
this callback is invoked inside the lock.
> + if (ret)
> + goto out;
> + }
> + cpriv->owner = owner;
> + }
> +
> + cpriv->start_count++;
> +out:
> + raw_spin_unlock_irqrestore(&cpriv->lock, flags);
> + return ret;
> +}
> +
> +static int __gtrace_comp_stop(struct gtrace_component *comp)
> +{
> + struct gtrace_comp_priv *cpriv = to_gtrace_comp_priv(comp);
> + const struct gtrace_driver *gtdrv = to_gtrace_driver(comp->dev.driver);
> + unsigned long flags;
> + int ret = 0;
> +
> + raw_spin_lock_irqsave(&cpriv->lock, flags);
[Severity: High]
Does this have the same race as __gtrace_comp_start()?
The driver pointer is cached locklessly here as well, exposing the stop
callback to a race with driver removal.
> + if (!cpriv->start_count) {
> + ret = -EINVAL;
> + goto out;
> + }
> +
> + cpriv->start_count--;
> + if (!cpriv->start_count) {
> + if (!gtdrv) {
> + ret = -ENODEV;
> + goto out;
> + }
> +
> + if (gtdrv->stop)
> + ret = gtdrv->stop(comp);
[Severity: High]
Could this trigger a use-after-free during the stop path?
If the driver has been removed and the structure freed, calling this
potentially freed stop callback could crash the kernel.
> + }
> +out:
> + raw_spin_unlock_irqrestore(&cpriv->lock, flags);
> + return ret;
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261001052243.3042627-1-mayuresh.chitale@oss.qualcomm.com?part=5
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH v6 03/14] gtrace: Add RISC-V platform driver for the gtrace framework
2026-10-01 5:22 ` [PATCH v6 03/14] gtrace: Add RISC-V platform driver for the " Mayuresh Chitale
@ 2026-10-01 5:36 ` sashiko-bot
0 siblings, 0 replies; 25+ messages in thread
From: sashiko-bot @ 2026-10-01 5:36 UTC (permalink / raw)
To: Mayuresh Chitale; +Cc: conor+dt, devicetree, robh
Thank you for your contribution! Sashiko AI review found 6 potential issue(s) to consider:
- [High] of: use-after-free of device node in rvtrace_platform_probe()
- [High] core: use-after-free hazard in rvtrace_platform_remove() during unregistration
- [Medium] of: memory leak of rep_node and rdev_node in gtrace_of_parse_outconns()
- [Medium] of: fwnode handle leak on -EPROBE_DEFER error path in gtrace_of_parse_outconns()
- [Medium] of: fwnode handle references leaked on rvtrace_platform_probe() error paths
- [Medium] module: missing MODULE_DEVICE_TABLE prevents automatic driver loading
--
commit 70aceab46d8019c787fc4aee6cb3f8a0b2772885
Author: Anup Patel <anup.patel@oss.qualcomm.com>
gtrace: Add RISC-V platform driver for the gtrace framework
The RISC-V trace components defined by the RISC-V Trace Control Interface
Specification are memory mapped devices which are organized in a
graph-like topology and described in the device tree.
Add the RISC-V platform driver for the gtrace framework. The driver
parses the RISC-V component topology from the device tree, discovers the
type and version of each component from its IMPL register, provides the
RISC-V register level control ops and registers each component with the
gtrace core.
> diff --git a/drivers/hwtracing/gtrace/gtrace-of.c b/drivers/hwtracing/gtrace/gtrace-of.c
> new file mode 100644
> --- /dev/null
> +++ b/drivers/hwtracing/gtrace/gtrace-of.c
[ ... ]
> + conn->dest_port = rep.port;
> + conn->dest_fwnode = of_fwnode_handle(rdev_node);
> + fwnode_handle_get(conn->dest_fwnode);
> + conn->dest_comp = gtrace_find_by_fwnode(conn->dest_fwnode);
> + if (!conn->dest_comp) {
> + ret = -EPROBE_DEFER;
> + of_node_put(ep_node);
> + of_node_put(rep_node);
> + of_node_put(rdev_node);
> + break;
[Severity: Medium]
Does this error path leak the conn->dest_fwnode reference?
The fwnode reference count was incremented by fwnode_handle_get(), but the
newly allocated conn has not yet been placed into the pdata->outconns array.
Consequently, the error cleanup loop at the done label skips this connection.
> + }
> +
> + pdata->outconns[i] = conn;
> + i++;
[Severity: Medium]
Does the success path of this endpoint parsing loop leak the device node
references?
The of_graph_get_remote_endpoint() and of_graph_get_port_parent() calls
acquire references to device nodes. On the success path, these references
are never released with of_node_put() before advancing to the next iteration.
> + }
> +
> +done:
> + if (ret) {
> + for (i = 0; i < pdata->nr_outconns && pdata->outconns; i++) {
> + conn = pdata->outconns[i];
> + if (conn && conn->dest_fwnode)
> + fwnode_handle_put(conn->dest_fwnode);
> + }
> + }
> + of_node_put(parent);
> + return ret;
> +}
> +EXPORT_SYMBOL_GPL(gtrace_of_parse_outconns);
[ ... ]
> diff --git a/drivers/hwtracing/gtrace/rvtrace-platform.c b/drivers/hwtracing/gtrace/rvtrace-platform.c
> new file mode 100644
> --- /dev/null
> +++ b/drivers/hwtracing/gtrace/rvtrace-platform.c
[ ... ]
> + pdata->bound_cpu = -1;
> + node = of_parse_phandle(dev_of_node(dev), "cpus", 0);
> + if (node) {
> + ret = of_cpu_node_to_id(node);
> + of_node_put(node);
> + if (ret < 0)
> + return dev_err_probe(dev, ret, "failed to get CPU id for %pOF\n", node);
[Severity: High]
Does this code trigger a use-after-free on the device node?
The node reference is explicitly released via of_node_put() before it is
passed to dev_err_probe(), where the %pOF specifier will dereference the
now-freed node pointer to extract its name.
> + pdata->bound_cpu = ret;
> + }
> +
> + /* Default control poll timeout */
> + pdata->control_poll_timeout_usecs = 10;
> +
> + ret = gtrace_of_parse_outconns(pdata);
> + if (ret)
> + return dev_err_probe(dev, ret, "failed to parse output connections\n");
> +
> + ret = gtrace_of_parse_inconns(pdata);
> + if (ret)
> + return dev_err_probe(dev, ret, "failed to parse input connections\n");
> +
> + /* Reset the component before it is registered with the gtrace core. */
> + ret = rvtrace_hw_reset(pdata);
> + if (ret)
> + return dev_err_probe(dev, ret, "failed to reset component\n");
[Severity: Medium]
Do these error paths leak the fwnode references stored in the outconns array?
The earlier call to gtrace_of_parse_outconns() allocates and acquires fwnode
references, storing them in pdata->outconns. If a subsequent call fails, the
probe function immediately returns an error. Because devm does not
automatically release fwnode references, and the custom teardown logic in
rvtrace_platform_remove() is bypassed on probe failure, these references
appear to be lost.
> +
> + impl = gtrace_read32(pdata, RVTRACE_COMPONENT_IMPL_OFFSET);
[ ... ]
> +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);
[Severity: High]
Does this sequence introduce a use-after-free hazard during unregistration?
The fwnode references are prematurely dropped before unregistering the
component. However, gtrace_unregister_component() relies on these pointers
internally during its teardown process, matching the fwnode device elements.
If the prior fwnode_handle_put() was the final reference, the memory is
freed, and the subsequent match access is a use-after-free.
> +}
> +
> +static const struct of_device_id rvtrace_platform_match[] = {
> + { .compatible = "riscv,trace-component", .data = (void *)GTRACE_FORMAT_ETRACE },
> + {}
> +};
[Severity: Medium]
Is MODULE_DEVICE_TABLE(of, rvtrace_platform_match) missing here?
Without it, the driver fails to export the necessary modaliases to userspace,
preventing the driver from loading automatically when its hardware matches
are present in modular builds.
> +
> +static struct platform_driver rvtrace_platform_driver = {
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261001052243.3042627-1-mayuresh.chitale@oss.qualcomm.com?part=3
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH v6 02/14] hwtracing: gtrace: Initial implementation of gtrace framework
2026-10-01 5:22 ` [PATCH v6 02/14] hwtracing: gtrace: Initial implementation of gtrace framework Mayuresh Chitale
@ 2026-10-01 5:37 ` sashiko-bot
0 siblings, 0 replies; 25+ messages in thread
From: sashiko-bot @ 2026-10-01 5:37 UTC (permalink / raw)
To: Mayuresh Chitale; +Cc: devicetree, robh, conor+dt
Thank you for your contribution! Sashiko AI review found 6 potential issue(s) to consider:
- [High] gtrace: Use-After-Free due to missing reference counting in lookup functions
- [High] gtrace: Topological Use-After-Free due to dangling dest_comp pointer
- [Medium] gtrace: Resource leak and UAF in per-CPU bound component handling
- [Medium] gtrace: gtrace_match_id() fails to match hardware components with version 0
- [Medium] gtrace: Null pointer dereference in gtrace_match_device()
- [Low] gtrace: Silent connection drop in gtrace_setup_inconn()
--
commit c892ee2c4bffce2e20b5a0429f11f8530ff96b44
Author: Anup Patel <anup.patel@oss.qualcomm.com>
Subject: hwtracing: gtrace: Initial implementation of gtrace framework
This commit implements a generic, architecture-neutral tracing framework
where trace components form a graph-like topology. It includes the core bus,
registration helpers, and a mechanism for polling component registers.
> diff --git a/drivers/hwtracing/gtrace/gtrace-core.c b/drivers/hwtracing/gtrace/gtrace-core.c
> new file mode 100644
> --- /dev/null
> +++ b/drivers/hwtracing/gtrace/gtrace-core.c
[ ... ]
> +const struct gtrace_component_id *gtrace_match_id(struct gtrace_component *comp,
> + const struct gtrace_component_id *ids)
> +{
> + const struct gtrace_component_id *id;
> +
> + for (id = ids; id->version; id++) {
[Severity: Medium]
Is it possible for a valid hardware component to have a version of 0? If an
architecture provides a hardware component with a 0 version identifier,
would this loop terminate prematurely and silently fail to probe the hardware?
Also, could the `ids` pointer be NULL here if a driver registers without
providing an `id_table`? Dereferencing it unconditionally might cause a
kernel panic if the framework does not strictly enforce `id_table` presence
during registration.
> + if (comp->id.type != id->type)
> + continue;
> +
> + return id;
> + }
> +
> + return NULL;
> +}
> +EXPORT_SYMBOL_GPL(gtrace_match_id);
> +
> +static int gtrace_match_device(struct device *dev, const struct device_driver *drv)
> +{
> + const struct gtrace_driver *gtdrv = to_gtrace_driver(drv);
> + struct gtrace_component *comp = to_gtrace_component(dev);
> +
> + return gtrace_match_id(comp, gtdrv->id_table) ? 1 : 0;
[Severity: Medium]
Is it guaranteed that `gtdrv->id_table` is non-NULL before passing it to
gtrace_match_id()? If a driver does not provide an `id_table`, does this
lead to a NULL pointer dereference inside gtrace_match_id()?
> +}
[ ... ]
> +static int gtrace_match_fwnode(struct device *dev, void *data)
> +{
> + struct gtrace_component *comp = to_gtrace_component(dev);
> + struct gtrace_fwnode_match_data *d = data;
> +
> + if (device_match_fwnode(&comp->dev, d->fwnode)) {
> + d->match = comp;
[Severity: High]
Does assigning the matched component here without calling get_device() leave
the object lifecycle unprotected? Could callers of gtrace_find_by_fwnode()
end up dereferencing a freed pointer if the target component is concurrently
unregistered and freed?
> + return 1;
> + }
> +
> + return 0;
> +}
[ ... ]
> +struct gtrace_component *gtrace_cpu_source(unsigned int cpu)
> +{
> + if (!cpu_present(cpu))
> + return NULL;
> +
> + return per_cpu(gtrace_cpu_source_comp, cpu);
[Severity: High]
Like the fwnode lookup above, does returning a raw pointer here without
reference counting or locking create a use-after-free window? If the
component is concurrently unregistered via gtrace_unregister_component()
while the caller is still using the pointer, could this result in memory
corruption?
> +}
> +EXPORT_SYMBOL_GPL(gtrace_cpu_source);
[ ... ]
> +static int gtrace_setup_inconn(struct device *dev, void *data)
> +{
> + struct gtrace_component *comp = to_gtrace_component(dev);
> + struct gtrace_platform_data *pdata = comp->pdata;
> + struct gtrace_connection *conn = data;
> + int i;
> +
> + if (device_match_fwnode(&comp->dev, conn->dest_fwnode)) {
> + for (i = 0; i < pdata->nr_inconns; i++) {
> + if (pdata->inconns[i])
> + continue;
> + pdata->inconns[i] = conn;
> + return 1;
> + }
> + }
> +
> + return 0;
[Severity: Low]
If the destination array is fully populated and we exhaust the loop, should
this return a negative error code instead of 0? Returning 0 appears to hide
the connection failure and silently drops the input connection when a
matching destination is found but saturated.
> +}
[ ... ]
> +struct gtrace_component *gtrace_register_component(struct gtrace_component_id *id,
> + const char *name,
> + struct gtrace_platform_data *pdata)
> +{
[ ... ]
> + if (comp->pdata->bound_cpu >= 0) {
> + gtrace_get_component(comp);
> + per_cpu(gtrace_cpu_source_comp, comp->pdata->bound_cpu) = comp;
[Severity: Medium]
What happens if multiple components are mistakenly mapped to the same CPU?
Does unconditionally overwriting the per-CPU variable here leak the reference
count of the previously stored component?
> + }
> +
> + mutex_unlock(>race_mutex);
> +
> + return comp;
> +
> +err_out_unlock:
> + mutex_unlock(>race_mutex);
> +err_out:
> + return ERR_PTR(ret);
> +}
> +EXPORT_SYMBOL_GPL(gtrace_register_component);
> +
> +void gtrace_unregister_component(struct gtrace_component *comp)
> +{
> + struct gtrace_component *c;
> +
> + mutex_lock(>race_mutex);
> +
> + if (comp->pdata->bound_cpu >= 0) {
> + c = per_cpu(gtrace_cpu_source_comp, comp->pdata->bound_cpu);
> + per_cpu(gtrace_cpu_source_comp, comp->pdata->bound_cpu) = NULL;
> + gtrace_put_component(c);
[Severity: Medium]
Does fetching and putting whatever is currently in the per-CPU variable
without verifying it matches `comp` risk dropping the reference on the wrong
component? If the per-CPU pointer was overwritten by a second component,
does this prematurely free the second one while the first one is being
unregistered?
> + }
> +
> + gtrace_cleanup_inconns_from_outconns(comp);
> + device_unregister(&comp->dev);
[Severity: High]
Does this sequence unregister the component without clearing the pointers
pointing to it from its source components' outconns arrays? When a trace
path is later constructed and walks the source's outconns, would it
dereference the freed dest_comp and cause a topological use-after-free?
> +
> + mutex_unlock(>race_mutex);
> +}
> +EXPORT_SYMBOL_GPL(gtrace_unregister_component);
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261001052243.3042627-1-mayuresh.chitale@oss.qualcomm.com?part=2
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH v6 07/14] gtrace: Add function to copy into perf AUX buffer
2026-10-01 5:22 ` [PATCH v6 07/14] gtrace: Add function to copy into perf AUX buffer Mayuresh Chitale
@ 2026-10-01 5:40 ` sashiko-bot
0 siblings, 0 replies; 25+ messages in thread
From: sashiko-bot @ 2026-10-01 5:40 UTC (permalink / raw)
To: Mayuresh Chitale; +Cc: devicetree, robh, conor+dt
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] gtrace: wild pointer and UAF during concurrent driver unbind
- [Medium] gtrace: lock mismatch by iterating over path in gtrace_path_copyto_auxbuf()
--
commit 727fcf662499fff522c570fa7a02e693a5593303
Author: Anup Patel <anup.patel@oss.qualcomm.com>
gtrace: Add function to copy into perf AUX buffer
The RISC-V trace ramsink will need a mechanism to copy trace data
into the perf AUX buffer. Add gtrace_path_copyto_auxbuf() function
and corresponding trace driver callback copyto_auxbuf() for this
purpose.
> diff --git a/drivers/hwtracing/gtrace/gtrace-core.c b/drivers/hwtracing/gtrace/gtrace-core.c
> index d7f1df90aa31d..acbeb3df21354 100644
> --- a/drivers/hwtracing/gtrace/gtrace-core.c
> +++ b/drivers/hwtracing/gtrace/gtrace-core.c
> @@ -715,6 +715,47 @@ int gtrace_path_stop(struct gtrace_path *path)
> }
> EXPORT_SYMBOL_GPL(gtrace_path_stop);
>
> +int gtrace_path_copyto_auxbuf(struct gtrace_path *path,
> + struct gtrace_perf_auxbuf *buf,
> + size_t *bytes_copied, u64 *format)
> +{
> + 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;
> + unsigned long flags;
> +
> + /*
> + * Copy only after every path using the sink has stopped; otherwise the
> + * hardware may overwrite data being copied, or the same data may be copied
> + * to aux buffer twice. The last path to stop does the copy. Holding the
> + * 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) {
[Severity: Medium]
Will this loop cause a lock mismatch if an intermediate component implements
copyto_auxbuf?
The function acquires sink->lock, but iterates over path->comp_list to find
and execute the callback. If a non-sink component is matched, its callback
will run under the sink's lock rather than its own, which may allow data races
with concurrent start or stop operations on that intermediate component.
> + comp = node->comp;
> + gtdrv = to_gtrace_driver(comp->dev.driver);
[Severity: High]
Does this code allow concurrent driver unbind to cause a use-after-free or
NULL pointer offset dereference?
The to_gtrace_driver() macro double-evaluates comp->dev.driver. If it is set
to NULL by a concurrent unbind between evaluations, the macro yields a negative
offset rather than NULL. This bypasses the !gtdrv check and dereferences an
invalid pointer.
Additionally, since no device lock or module reference is held here, can the
driver be unloaded while the callback is executing?
> + if (!gtdrv || !gtdrv->copyto_auxbuf)
> + continue;
> +
> + *bytes_copied = gtdrv->copyto_auxbuf(comp, buf, format);
> + ret = 0;
> + break;
> + }
> +out:
> + raw_spin_unlock_irqrestore(&sink->lock, flags);
> + return ret;
> +}
> +EXPORT_SYMBOL_GPL(gtrace_path_copyto_auxbuf);
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261001052243.3042627-1-mayuresh.chitale@oss.qualcomm.com?part=7
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH v6 09/14] gtrace: Add RISC-V Trace ramsink driver
2026-10-01 5:22 ` [PATCH v6 09/14] gtrace: Add RISC-V Trace ramsink driver Mayuresh Chitale
@ 2026-10-01 5:42 ` sashiko-bot
0 siblings, 0 replies; 25+ messages in thread
From: sashiko-bot @ 2026-10-01 5:42 UTC (permalink / raw)
To: Mayuresh Chitale; +Cc: robh, conor+dt, devicetree
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] gtrace: DMA use-after-free on driver unbind while tracing
- [Medium] gtrace: out-of-bounds read if hardware write pointer exceeds limit
--
commit 727d829afd136c7c3871f88297ecd560269b3dbd
Author: Mayuresh Chitale <mayuresh.chitale@oss.qualcomm.com>
gtrace: Add RISC-V Trace ramsink driver
This commit adds the initial implementation of the RISC-V E-Trace ramsink
driver. The ramsink stores trace data generated by upstream components into
a system memory buffer.
> diff --git a/drivers/hwtracing/gtrace/rvtrace-ramsink.c b/drivers/hwtracing/gtrace/rvtrace-ramsink.c
> new file mode 100644
> index 0000000000000..67b6779aaceec
> --- /dev/null
> +++ b/drivers/hwtracing/gtrace/rvtrace-ramsink.c
[ ... ]
> +static size_t rvtrace_ramsink_copyto_auxbuf(struct gtrace_component *comp,
> + struct gtrace_perf_auxbuf *buf,
> + u64 *format)
> +{
> + struct rvtrace_ramsink_priv *priv = dev_get_drvdata(&comp->dev);
> + struct trace_buf src, dst;
> + size_t bytes = 0;
> + bool wrap;
> + u64 wp;
> +
> + dst.base = buf->base;
> + dst.len = buf->length;
> + 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;
> +
> + if (wrap) {
> + rvtrace_ramsink_write_pair(comp->pdata, priv->start, RVTRACE_RAMSINK_WPLOW_OFF);
> + src.cur = wp - priv->start;
> + priv->prev_wp = priv->start;
> + /*
> + * There is no way to tell if trRamWp wrapped around more than once. As a
> + * result priv->prev_wp can't be used and the entire buffer must be copied
> + * even though some data might be duplicated.
> + */
> + bytes = priv->size;
> + } else {
> + src.cur = priv->prev_wp - priv->start;
> + bytes = wp - priv->prev_wp;
> + priv->prev_wp = wp;
> + }
[Severity: Medium]
Does this code safely handle anomalous hardware behavior? If the hardware
write pointer (wp) read directly from RVTRACE_RAMSINK_WPLOW_OFF exceeds the
configured priv->end limit, it seems src.cur could underflow or exceed the
buffer length (priv->size).
Without bounds checking on the offset returned by hardware, could this
cause the subsequent memcpy in tbuf_to_pbuf_copy to read out-of-bounds
kernel memory and leak it into the userspace perf ring buffer?
> +
> + tbuf_to_pbuf_copy(&src, &dst, bytes);
> + dev_dbg(&comp->dev, "Copied %zu bytes\n", bytes);
> + return bytes;
> +}
[ ... ]
> +static void rvtrace_ramsink_cleanup(struct gtrace_component *comp)
> +{
> + struct rvtrace_ramsink_priv *priv = dev_get_drvdata(&comp->dev);
> +
> + dma_free_coherent(comp->pdata->dev, priv->alloc_size, priv->alloc_va, priv->alloc_start);
> +}
[ ... ]
> +static void rvtrace_ramsink_remove(struct gtrace_component *comp)
> +{
> + rvtrace_ramsink_cleanup(comp);
> +}
[Severity: High]
Can unbinding this driver while tracing is active lead to a DMA
use-after-free?
rvtrace_ramsink_remove calls rvtrace_ramsink_cleanup which immediately
frees the DMA memory via dma_free_coherent. If the hardware isn't
explicitly halted via gtrace_disable_component first, the hardware might
continue writing trace data to the freed memory pages.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261001052243.3042627-1-mayuresh.chitale@oss.qualcomm.com?part=9
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH v6 11/14] gtrace: Add perf driver for tracing using perf tool
2026-10-01 5:22 ` [PATCH v6 11/14] gtrace: Add perf driver for tracing using perf tool Mayuresh Chitale
@ 2026-10-01 5:44 ` sashiko-bot
0 siblings, 0 replies; 25+ messages in thread
From: sashiko-bot @ 2026-10-01 5:44 UTC (permalink / raw)
To: Mayuresh Chitale; +Cc: devicetree, conor+dt, robh
Thank you for your contribution! Sashiko AI review found 6 potential issue(s) to consider:
- [Critical] gtrace: NULL pointer dereference of event->owner in gtrace_setup_aux
- [High] gtrace: deferred vunmap creates dangling page table entries mapping freed pages
- [High] gtrace: unbounded copyto_auxbuf overwrites unconsumed perf ring buffer data
- [Medium] gtrace: sink driver format flags overwritten in gtrace_event_stop
- [Medium] gtrace: perf_report_aux_output_id reports logical CPU instead of hardware trace ID
- [Medium] kconfig: GTRACE selects PERF_EVENTS without inheriting HAVE_PERF_EVENTS dependency
--
commit 94baa393278755b8b8c03ef3fec39f40b4499dbb
Author: Mayuresh Chitale <mayuresh.chitale@oss.qualcomm.com>
gtrace: Add perf driver for tracing using perf tool
Add perf driver for the gtrace similar to ARM Coresight and Hisilicon
PTT drivers. The driver adds 'gtrace' event descriptor which can be used
by the perf tool to record the trace data.
> diff --git a/drivers/hwtracing/gtrace/Kconfig b/drivers/hwtracing/gtrace/Kconfig
> index de4a3b5335911..e5b62d65e2fdb 100644
> --- a/drivers/hwtracing/gtrace/Kconfig
> +++ b/drivers/hwtracing/gtrace/Kconfig
> @@ -3,6 +3,7 @@
> menuconfig GTRACE
> tristate "Generic Hardware Trace Support"
> depends on OF
> + select PERF_EVENTS
[Severity: Medium]
Will this cause unmet direct dependency warnings or build failures?
The PERF_EVENTS config symbol depends on HAVE_PERF_EVENTS. By using
select PERF_EVENTS unconditionally here, it forces the configuration on
even on architectures that support OF but do not have HAVE_PERF_EVENTS.
> help
> This framework provides an architecture-neutral kernel interface
> for hardware trace drivers. It builds a topological view of the
[ ... ]
> diff --git a/drivers/hwtracing/gtrace/gtrace-perf.c b/drivers/hwtracing/gtrace/gtrace-perf.c
> new file mode 100644
> index 0000000000000..928f0405c6c17
> --- /dev/null
> +++ b/drivers/hwtracing/gtrace/gtrace-perf.c
[ ... ]
> +static void gtrace_free_aux(void *data)
> +{
> + struct gtrace_event_data *event_data = data;
> +
> + queue_work(gtrace_wq, &event_data->work);
> +}
[Severity: High]
Does this defer the vunmap() operation too late?
The perf core calls the pmu->free_aux callback synchronously, and immediately
after it returns, calls __free_page() on the underlying physical pages.
Because gtrace_free_aux() defers the cleanup to a workqueue, the physical
pages are freed while valid kernel vmap page table entries still point to
them.
This creates a window where memory corruption could occur if the driver or
hardware accesses the vmap address before the workqueue executes vunmap().
> +
> +static struct gtrace_path **gtrace_event_cpu_path_ptr(struct gtrace_event_data *data,
> + int cpu)
> +{
> + return per_cpu_ptr(data->path, cpu);
> +}
> +
> +static void free_event_data(struct work_struct *work)
> +{
> + struct gtrace_event_data *event_data;
> + struct gtrace_path *path;
> + cpumask_t *mask;
> + int cpu;
> +
> + event_data = container_of(work, struct gtrace_event_data, work);
> + mask = &event_data->mask;
> + for_each_cpu(cpu, mask) {
> + path = *gtrace_event_cpu_path_ptr(event_data, cpu);
> + gtrace_destroy_path(path);
> + }
> + if (event_data->buf.base)
> + vunmap(event_data->buf.base);
[Severity: High]
As mentioned above, executing vunmap() asynchronously here creates a race
where the underlying pages have already been returned to the page allocator.
> + free_percpu(event_data->path);
> + kfree(event_data);
> +}
> +
> +static void *gtrace_setup_aux(struct perf_event *event, void **pages,
> + int nr_pages, bool overwrite)
> +{
[ ... ]
> + /*
> + * Create the path for each CPU in the mask. In case of any failure skip the CPU
> + */
> + for_each_cpu(cpu, mask) {
> + struct gtrace_component *src;
> + struct gtrace_path *path;
> +
> + src = gtrace_cpu_source(cpu);
> + if (!src) {
> + cpumask_clear_cpu(cpu, mask);
> + continue;
> + }
> +
> + path = gtrace_create_path(src, NULL, GTRACE_COMPONENT_MODE_PERF);
> + if (IS_ERR(path)) {
> + cpumask_clear_cpu(cpu, mask);
> + continue;
> + }
> +
> + path->owner = task_pid_nr(event->owner);
[Severity: Critical]
Can event->owner be NULL or TASK_TOMBSTONE here?
If an unprivileged user creates a trace event, forks, and the parent exits,
event->owner is cleared to NULL via perf_event_exit_task(). If the child
then mmaps the inherited file descriptor, gtrace_setup_aux() unconditionally
dereferences event->owner via task_pid_nr().
Additionally, kernel counters set event->owner to TASK_TOMBSTONE
((void *)-1L), which would also cause a panic.
> + *gtrace_event_cpu_path_ptr(event_data, cpu) = path;
> + }
[ ... ]
> +static void gtrace_event_start(struct perf_event *event, int flags)
> +{
[ ... ]
> + /*
> + * output cpu / trace ID in perf record, once for the lifetime
> + * of the event.
> + */
> + if (!cpumask_test_cpu(cpu, &event_data->aux_hwid_done)) {
> + cpumask_set_cpu(cpu, &event_data->aux_hwid_done);
> + perf_report_aux_output_id(event, cpu);
[Severity: Medium]
Should this report the hardware trace ID instead of the logical CPU index?
If the hardware trace stream embeds the real hardware trace ID, passing the
logical CPU index here means the decoder in userspace won't be able to match
packets to CPUs, breaking trace decoding.
Could this pass path->trace_id instead of the local cpu variable?
> + }
> +
> +out:
> + /* Tell the perf core the event is alive */
> + event->hw.state = 0;
> + ctxt->event_data = event_data;
> + return;
> +fail_end:
> + perf_aux_output_end(handle, 0);
> +fail:
> + event->hw.state = PERF_HES_STOPPED;
> +}
> +
> +static void gtrace_event_stop(struct perf_event *event, int mode)
> +{
> + struct gtrace_ctxt *ctxt = this_cpu_ptr(>race_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;
> + size_t size = 0;
[ ... ]
> + event->hw.state = PERF_HES_STOPPED;
> + 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);
[Severity: High]
Does this allow an unbounded copy that can corrupt unread ring buffer data?
The gtrace_path_copyto_auxbuf() function is only passed event_data->buf,
which only knows the total buffer length, ignoring the handle->size limit
indicating how much contiguous space is safely writable.
> + WARN_ON_ONCE(ret);
> + format = gtrace_path_aux_format(path);
[Severity: Medium]
Are the format flags returned by the sink driver being discarded?
The gtrace_path_copyto_auxbuf() call allows the sink driver to set format
flags in the format parameter (like truncation or formatting details).
Immediately following this, the format variable is unconditionally
overwritten with gtrace_path_aux_format(), discarding the driver's flags
rather than OR'ing them.
> + 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);
> + }
[Severity: High]
The check for size > handle->size happens here, after the unbounded memory
copy has already been performed.
> + perf_aux_output_end(handle, size);
> + } else
> + WARN_ON(size);
> + }
> +
> + return;
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261001052243.3042627-1-mayuresh.chitale@oss.qualcomm.com?part=11
^ permalink raw reply [flat|nested] 25+ messages in thread
end of thread, other threads:[~2026-10-01 5:44 UTC | newest]
Thread overview: 25+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-01 5:22 [PATCH v6 00/14] Linux RISC-V trace framework and drivers Mayuresh Chitale
2026-10-01 5:22 ` [PATCH v6 01/14] dt-bindings: Add RISC-V trace component bindings Mayuresh Chitale
2026-10-01 5:33 ` sashiko-bot
2026-10-01 5:22 ` [PATCH v6 02/14] hwtracing: gtrace: Initial implementation of gtrace framework Mayuresh Chitale
2026-10-01 5:37 ` sashiko-bot
2026-10-01 5:22 ` [PATCH v6 03/14] gtrace: Add RISC-V platform driver for the " Mayuresh Chitale
2026-10-01 5:36 ` sashiko-bot
2026-10-01 5:22 ` [PATCH v6 04/14] gtrace: Add functions to create/destroy a trace component path Mayuresh Chitale
2026-10-01 5:36 ` sashiko-bot
2026-10-01 5:22 ` [PATCH v6 05/14] gtrace: Add functions to start/stop tracing on a " Mayuresh Chitale
2026-10-01 5:36 ` sashiko-bot
2026-10-01 5:22 ` [PATCH v6 06/14] gtrace: Add RISC-V Trace encoder driver Mayuresh Chitale
2026-10-01 5:34 ` sashiko-bot
2026-10-01 5:22 ` [PATCH v6 07/14] gtrace: Add function to copy into perf AUX buffer Mayuresh Chitale
2026-10-01 5:40 ` sashiko-bot
2026-10-01 5:22 ` [PATCH v6 08/14] perf: Add gtrace AUX buffer trace format type Mayuresh Chitale
2026-10-01 5:22 ` [PATCH v6 09/14] gtrace: Add RISC-V Trace ramsink driver Mayuresh Chitale
2026-10-01 5:42 ` sashiko-bot
2026-10-01 5:22 ` [PATCH v6 10/14] riscv: Enable DMA_RESTRICTED_POOL in defconfig Mayuresh Chitale
2026-10-01 5:22 ` [PATCH v6 11/14] gtrace: Add perf driver for tracing using perf tool Mayuresh Chitale
2026-10-01 5:44 ` sashiko-bot
2026-10-01 5:22 ` [PATCH v6 12/14] perf tools: Add RISC-V trace PMU record capabilities Mayuresh Chitale
2026-10-01 5:22 ` [PATCH v6 13/14] perf tools: Initial support for gtrace decoder Mayuresh Chitale
2026-10-01 5:35 ` sashiko-bot
2026-10-01 5:22 ` [PATCH v6 14/14] MAINTAINERS: Add entry for RISC-V trace framework Mayuresh Chitale
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox