[PATCH v6 09/14] gtrace: Add RISC-V Trace ramsink driver
From: Mayuresh Chitale
Date: Thu Oct 01 2026 - 01:26:43 EST
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@xxxxxxxxxxxxxxxx>
Signed-off-by: Anup Patel <anup.patel@xxxxxxxxxxxxxxxx>
Signed-off-by: Mayuresh Chitale <mayuresh.chitale@xxxxxxxxxxxxxxxx>
---
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