* [PATCH 01/24] gtrace: Move connection release into component unregister
2026-10-23 3:14 [RFC PATCH 00/24] Add Linux RISC-V trace support via the gtrace framework Zane Leung
@ 2026-10-23 3:14 ` Zane Leung
2026-10-23 3:14 ` [PATCH 02/24] gtrace: Initial implementation of gtrace sysfs framework Zane Leung
` (22 subsequent siblings)
23 siblings, 0 replies; 25+ messages in thread
From: Zane Leung @ 2026-10-23 3:14 UTC (permalink / raw)
To: gregkh, palmer, pjw, alexander.shishkin, suzuki.poulose,
mchitale, anup, irogers
Cc: mike.leach, james.clark, leo.yan, peterz, mingo, namhyung,
mark.rutland, jolsa, adrian.hunter, alex, atish.patra,
andrew.jones, sunilvl, liangzhen, zhuangqiubin, coresight,
linux-riscv, linux-kernel
Add gtrace_release_outconns() and call it from
gtrace_unregister_component() so the output connection fwnode
references are released in the core instead of being duplicated
in every platform driver's remove callback.
Signed-off-by: Zane Leung <liangzhen@linux.spacemit.com>
---
drivers/hwtracing/gtrace/gtrace-core.c | 15 +++++++++++++++
drivers/hwtracing/gtrace/rvtrace-platform.c | 9 ---------
2 files changed, 15 insertions(+), 9 deletions(-)
diff --git a/drivers/hwtracing/gtrace/gtrace-core.c b/drivers/hwtracing/gtrace/gtrace-core.c
index 034987237abc..5704e426abe8 100644
--- a/drivers/hwtracing/gtrace/gtrace-core.c
+++ b/drivers/hwtracing/gtrace/gtrace-core.c
@@ -479,10 +479,25 @@ struct gtrace_component *gtrace_register_component(struct gtrace_component_id *i
}
EXPORT_SYMBOL_GPL(gtrace_register_component);
+static void gtrace_release_outconns(struct gtrace_component *comp)
+{
+ struct gtrace_platform_data *pdata = comp->pdata;
+ struct gtrace_connection *conn;
+ int i;
+
+ for (i = 0; i < pdata->nr_outconns; i++) {
+ conn = pdata->outconns[i];
+ if (conn && conn->dest_fwnode)
+ fwnode_handle_put(conn->dest_fwnode);
+ }
+}
+
void gtrace_unregister_component(struct gtrace_component *comp)
{
struct gtrace_component *c;
+ gtrace_release_outconns(comp);
+
mutex_lock(>race_mutex);
if (comp->pdata->bound_cpu >= 0) {
diff --git a/drivers/hwtracing/gtrace/rvtrace-platform.c b/drivers/hwtracing/gtrace/rvtrace-platform.c
index a5655e983e51..189ff0b8bc18 100644
--- a/drivers/hwtracing/gtrace/rvtrace-platform.c
+++ b/drivers/hwtracing/gtrace/rvtrace-platform.c
@@ -141,15 +141,6 @@ static int rvtrace_platform_probe(struct platform_device *pdev)
static void rvtrace_platform_remove(struct platform_device *pdev)
{
struct gtrace_component *comp = platform_get_drvdata(pdev);
- struct gtrace_platform_data *pdata = comp->pdata;
- struct gtrace_connection *conn;
- int i;
-
- for (i = 0; i < pdata->nr_outconns; i++) {
- conn = pdata->outconns[i];
- if (conn && conn->dest_fwnode)
- fwnode_handle_put(conn->dest_fwnode);
- }
gtrace_unregister_component(comp);
}
--
2.34.1
^ permalink raw reply [flat|nested] 25+ messages in thread* [PATCH 02/24] gtrace: Initial implementation of gtrace sysfs framework
2026-10-23 3:14 [RFC PATCH 00/24] Add Linux RISC-V trace support via the gtrace framework Zane Leung
2026-10-23 3:14 ` [PATCH 01/24] gtrace: Move connection release into component unregister Zane Leung
@ 2026-10-23 3:14 ` Zane Leung
2026-10-23 3:14 ` [PATCH 03/24] gtrace: Drop trace format field and helper function Zane Leung
` (21 subsequent siblings)
23 siblings, 0 replies; 25+ messages in thread
From: Zane Leung @ 2026-10-23 3:14 UTC (permalink / raw)
To: gregkh, palmer, pjw, alexander.shishkin, suzuki.poulose,
mchitale, anup, irogers
Cc: mike.leach, james.clark, leo.yan, peterz, mingo, namhyung,
mark.rutland, jolsa, adrian.hunter, alex, atish.patra,
andrew.jones, sunilvl, liangzhen, zhuangqiubin, coresight,
linux-riscv, linux-kernel
Add per-component sysfs attribute groups (enable_sink, enable_source,
label) and a connections/ group with nr_links and bidirectional out:X /
in:Y symlinks. Drivers can attach extra groups via pdata->groups; they
are registered and cleaned up inside gtrace_sysfs_add/remove.
Signed-off-by: Zane Leung <liangzhen@linux.spacemit.com>
---
drivers/hwtracing/gtrace/Makefile | 2 +-
drivers/hwtracing/gtrace/gtrace-core.c | 47 ++-
drivers/hwtracing/gtrace/gtrace-sysfs.c | 440 ++++++++++++++++++++++++
include/linux/gtrace.h | 104 ++++++
4 files changed, 583 insertions(+), 10 deletions(-)
create mode 100644 drivers/hwtracing/gtrace/gtrace-sysfs.c
diff --git a/drivers/hwtracing/gtrace/Makefile b/drivers/hwtracing/gtrace/Makefile
index 33274a1ed0b8..ff79a49bdb5e 100644
--- a/drivers/hwtracing/gtrace/Makefile
+++ b/drivers/hwtracing/gtrace/Makefile
@@ -1,7 +1,7 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_GTRACE) += gtrace.o
-gtrace-y := gtrace-core.o gtrace-perf.o
+gtrace-y := gtrace-core.o gtrace-perf.o gtrace-sysfs.o
gtrace-$(CONFIG_OF) += gtrace-of.o
obj-$(CONFIG_RVTRACE) += rvtrace-platform.o
diff --git a/drivers/hwtracing/gtrace/gtrace-core.c b/drivers/hwtracing/gtrace/gtrace-core.c
index 5704e426abe8..cae0a7fa820f 100644
--- a/drivers/hwtracing/gtrace/gtrace-core.c
+++ b/drivers/hwtracing/gtrace/gtrace-core.c
@@ -17,7 +17,7 @@
#define GTRACE_POLL_TIMEOUT_US_DEFAULT 10
/* Mutex to serialize component registration/unregistration */
-static DEFINE_MUTEX(gtrace_mutex);
+DEFINE_MUTEX(gtrace_mutex);
/* Per-CPU source instances */
static DEFINE_PER_CPU(struct gtrace_component *, gtrace_cpu_source_comp);
@@ -36,11 +36,20 @@ struct gtrace_comp_priv {
pid_t owner;
bool ready;
bool visited;
+ enum gtrace_component_mode mode;
};
#define to_gtrace_comp_priv(__comp) \
container_of_const(__comp, struct gtrace_comp_priv, comp)
+enum gtrace_component_mode gtrace_get_mode(struct gtrace_component *comp)
+{
+ struct gtrace_comp_priv *cpriv = to_gtrace_comp_priv(comp);
+
+ return cpriv->mode;
+}
+EXPORT_SYMBOL_GPL(gtrace_get_mode);
+
/* Component type based id generator */
struct gtrace_type_idx {
/* Lock to protect the type ID generator */
@@ -463,6 +472,12 @@ struct gtrace_component *gtrace_register_component(struct gtrace_component_id *i
goto err_out_unlock;
}
+ ret = gtrace_sysfs_add(comp);
+ if (ret < 0) {
+ device_unregister(&comp->dev);
+ goto err_out_unlock;
+ }
+
if (comp->pdata->bound_cpu >= 0) {
gtrace_get_component(comp);
per_cpu(gtrace_cpu_source_comp, comp->pdata->bound_cpu) = comp;
@@ -507,6 +522,7 @@ void gtrace_unregister_component(struct gtrace_component *comp)
}
gtrace_cleanup_inconns_from_outconns(comp);
+ gtrace_sysfs_remove(comp);
device_unregister(&comp->dev);
mutex_unlock(>race_mutex);
@@ -636,7 +652,8 @@ static void gtrace_release_path_nodes(struct gtrace_path *path)
}
}
-static int __gtrace_comp_start(struct gtrace_component *comp, pid_t owner)
+static int __gtrace_comp_start(struct gtrace_component *comp,
+ struct gtrace_path *path)
{
struct gtrace_comp_priv *cpriv = to_gtrace_comp_priv(comp);
const struct gtrace_driver *gtdrv = to_gtrace_driver(comp->dev.driver);
@@ -648,17 +665,21 @@ static int __gtrace_comp_start(struct gtrace_component *comp, pid_t owner)
raw_spin_lock_irqsave(&cpriv->lock, flags);
if (cpriv->start_count) {
- if (cpriv->owner != owner) {
+ if (cpriv->owner != path->owner || cpriv->mode != path->mode) {
ret = -EBUSY;
goto out;
}
} else {
if (gtdrv->start) {
+ comp->path = path;
ret = gtdrv->start(comp);
- if (ret)
+ if (ret) {
+ comp->path = NULL;
goto out;
+ }
}
- cpriv->owner = owner;
+ cpriv->owner = path->owner;
+ cpriv->mode = path->mode;
}
cpriv->start_count++;
@@ -667,7 +688,8 @@ static int __gtrace_comp_start(struct gtrace_component *comp, pid_t owner)
return ret;
}
-static int __gtrace_comp_stop(struct gtrace_component *comp)
+static int __gtrace_comp_stop(struct gtrace_component *comp,
+ struct gtrace_path *path)
{
struct gtrace_comp_priv *cpriv = to_gtrace_comp_priv(comp);
const struct gtrace_driver *gtdrv = to_gtrace_driver(comp->dev.driver);
@@ -680,6 +702,11 @@ static int __gtrace_comp_stop(struct gtrace_component *comp)
goto out;
}
+ if (cpriv->mode != path->mode) {
+ ret = -EBUSY;
+ goto out;
+ }
+
cpriv->start_count--;
if (!cpriv->start_count) {
if (!gtdrv) {
@@ -689,6 +716,8 @@ static int __gtrace_comp_stop(struct gtrace_component *comp)
if (gtdrv->stop)
ret = gtdrv->stop(comp);
+ comp->path = NULL;
+ cpriv->mode = GTRACE_COMPONENT_MODE_DISABLED;
}
out:
raw_spin_unlock_irqrestore(&cpriv->lock, flags);
@@ -701,7 +730,7 @@ int gtrace_path_start(struct gtrace_path *path)
int ret = 0;
list_for_each_entry_reverse(node, &path->comp_list, head) {
- ret = __gtrace_comp_start(node->comp, path->owner);
+ ret = __gtrace_comp_start(node->comp, path);
if (ret)
break;
}
@@ -709,7 +738,7 @@ int gtrace_path_start(struct gtrace_path *path)
/* If a component failed to start, stop all the components that were started before it */
if (ret)
list_for_each_entry_continue(node, &path->comp_list, head)
- __gtrace_comp_stop(node->comp);
+ __gtrace_comp_stop(node->comp, path);
return ret;
}
@@ -721,7 +750,7 @@ int gtrace_path_stop(struct gtrace_path *path)
int ret = 0, err;
list_for_each_entry(node, &path->comp_list, head) {
- err = __gtrace_comp_stop(node->comp);
+ err = __gtrace_comp_stop(node->comp, path);
if (err && !ret)
ret = err;
}
diff --git a/drivers/hwtracing/gtrace/gtrace-sysfs.c b/drivers/hwtracing/gtrace/gtrace-sysfs.c
new file mode 100644
index 000000000000..5f4f3fcb56a4
--- /dev/null
+++ b/drivers/hwtracing/gtrace/gtrace-sysfs.c
@@ -0,0 +1,440 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2026 Spacemit Technologies, Inc.
+ *
+ * Based on drivers/hwtracing/coresight/coresight-sysfs.c
+ */
+
+#include <linux/device.h>
+#include <linux/gtrace.h>
+#include <linux/property.h>
+
+ssize_t gtrace_simple_show_pair(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct gtrace_component *comp = to_gtrace_component(dev);
+ struct gtrace_pair_attribute *gtrace_attr =
+ container_of(attr, struct gtrace_pair_attribute, attr);
+ u64 val;
+
+ val = gtrace_relaxed_read_pair(comp->pdata, gtrace_attr->lo_off, gtrace_attr->hi_off);
+ return sysfs_emit(buf, "0x%llx\n", val);
+}
+EXPORT_SYMBOL_GPL(gtrace_simple_show_pair);
+
+ssize_t gtrace_simple_show32(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct gtrace_component *comp = to_gtrace_component(dev);
+ struct gtrace_off_attribute *gtrace_attr =
+ container_of(attr, struct gtrace_off_attribute, attr);
+ u32 val;
+
+ val = gtrace_relaxed_read32(comp->pdata, gtrace_attr->off);
+ return sysfs_emit(buf, "0x%x\n", val);
+}
+EXPORT_SYMBOL_GPL(gtrace_simple_show32);
+
+/*
+ * gtrace_find_activated_sysfs_sink - find the first sink activated via
+ * walks outconns forward from @comp.
+ */
+static struct gtrace_component *
+gtrace_find_activated_sysfs_sink(struct gtrace_component *comp)
+{
+ int i;
+ struct gtrace_component *sink = NULL;
+ struct gtrace_platform_data *pdata = comp->pdata;
+
+ if (comp->sysfs_sink_activated)
+ return comp;
+
+ for (i = 0; i < pdata->nr_outconns; i++) {
+ struct gtrace_component *dest = pdata->outconns[i]->dest_comp;
+
+ if (dest)
+ sink = gtrace_find_activated_sysfs_sink(dest);
+ if (sink)
+ return sink;
+ }
+
+ return NULL;
+}
+
+/** gtrace_validate_source_sysfs - make sure a source has the right credentials to
+ * be used via sysfs.
+ * @comp: the component structure for a source.
+ * @function: the function this was called from.
+ *
+ * Assumes the gtrace_mutex is held.
+ */
+static int gtrace_validate_source_sysfs(struct gtrace_component *comp,
+ const char *function)
+{
+ if (!gtrace_is_source(comp->pdata)) {
+ dev_err(&comp->dev, "component is not a trace source in %s\n", function);
+ return -EINVAL;
+ }
+
+ if (!cpu_online(comp->pdata->bound_cpu))
+ return -ENODEV;
+
+ return 0;
+}
+
+int gtrace_enable_sysfs(struct gtrace_component *comp)
+{
+ struct gtrace_component *sink;
+ struct gtrace_path *path;
+ int ret;
+
+ mutex_lock(>race_mutex);
+ ret = gtrace_validate_source_sysfs(comp, __func__);
+ if (ret)
+ goto out;
+
+ /* mode == SYSFS implies that it's already enabled. */
+ if (gtrace_get_mode(comp) == GTRACE_COMPONENT_MODE_SYSFS)
+ goto out;
+
+ sink = gtrace_find_activated_sysfs_sink(comp);
+
+ /*
+ * gtrace_create_path() acquires gtrace_mutex internally via
+ * gtrace_walk_output_components(). Release it here first to
+ * avoid a deadlock.
+ */
+ mutex_unlock(>race_mutex);
+
+ path = gtrace_create_path(comp, sink, GTRACE_COMPONENT_MODE_SYSFS);
+ if (IS_ERR(path)) {
+ pr_err("building path(s) failed\n");
+ return PTR_ERR(path);
+ }
+
+ mutex_lock(>race_mutex);
+
+ ret = gtrace_path_start(path);
+ if (ret)
+ gtrace_destroy_path(path);
+
+out:
+ mutex_unlock(>race_mutex);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(gtrace_enable_sysfs);
+
+int gtrace_disable_sysfs(struct gtrace_component *comp)
+{
+ struct gtrace_path *path;
+ int ret;
+
+ mutex_lock(>race_mutex);
+
+ ret = gtrace_validate_source_sysfs(comp, __func__);
+ if (ret)
+ goto out;
+
+ path = comp->path;
+
+ if (gtrace_get_mode(comp) != GTRACE_COMPONENT_MODE_SYSFS)
+ goto out;
+
+ ret = gtrace_path_stop(path);
+ if (ret)
+ pr_err("Error. Tracing not stopped\n");
+
+ gtrace_destroy_path(path);
+out:
+ mutex_unlock(>race_mutex);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(gtrace_disable_sysfs);
+
+static ssize_t enable_sink_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct gtrace_component *comp = to_gtrace_component(dev);
+
+ guard(mutex)(>race_mutex);
+
+ return sysfs_emit(buf, "%u\n", comp->sysfs_sink_activated);
+}
+
+static ssize_t enable_sink_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ int ret;
+ unsigned long val;
+ struct gtrace_component *comp = to_gtrace_component(dev);
+
+ ret = kstrtoul(buf, 10, &val);
+ if (ret)
+ return ret;
+
+ comp->sysfs_sink_activated = !!val;
+
+ return size;
+}
+static DEVICE_ATTR_RW(enable_sink);
+
+static ssize_t enable_source_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct gtrace_component *comp = to_gtrace_component(dev);
+
+ guard(mutex)(>race_mutex);
+ return sysfs_emit(buf, "%u\n",
+ gtrace_get_mode(comp) == GTRACE_COMPONENT_MODE_SYSFS);
+}
+
+static ssize_t enable_source_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ int ret = 0;
+ unsigned long val;
+ struct gtrace_component *comp = to_gtrace_component(dev);
+
+ ret = kstrtoul(buf, 10, &val);
+ if (ret)
+ return ret;
+
+
+ /*
+ * gtrace hotplug callbacks in core layer control a activated path
+ * from its source to sink. Taking hotplug lock here protects a race
+ * condition with hotplug callbacks.
+ */
+ guard(cpus_read_lock)();
+
+ if (val) {
+ ret = gtrace_enable_sysfs(comp);
+ if (ret)
+ return ret;
+ } else {
+ ret = gtrace_disable_sysfs(comp);
+ if (ret)
+ return ret;
+ }
+
+ return size;
+}
+static DEVICE_ATTR_RW(enable_source);
+
+static ssize_t label_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ const char *str;
+ int ret;
+
+ ret = fwnode_property_read_string(dev_fwnode(dev), "label", &str);
+ if (ret)
+ return ret;
+
+ return sysfs_emit(buf, "%s\n", str);
+}
+static DEVICE_ATTR_RO(label);
+
+static umode_t label_is_visible(struct kobject *kobj,
+ struct attribute *attr, int n)
+{
+ struct device *dev = kobj_to_dev(kobj);
+
+ if (attr == &dev_attr_label.attr) {
+ if (fwnode_property_present(dev_fwnode(dev), "label"))
+ return attr->mode;
+ else
+ return 0;
+ }
+
+ return attr->mode;
+}
+
+static struct attribute *gtrace_sink_attrs[] = {
+ &dev_attr_enable_sink.attr,
+ &dev_attr_label.attr,
+ NULL,
+};
+
+static const struct attribute_group gtrace_sink_group = {
+ .attrs = gtrace_sink_attrs,
+ .is_visible = label_is_visible,
+};
+
+
+static struct attribute *gtrace_source_attrs[] = {
+ &dev_attr_enable_source.attr,
+ &dev_attr_label.attr,
+ NULL,
+};
+
+static const struct attribute_group gtrace_source_group = {
+ .attrs = gtrace_source_attrs,
+ .is_visible = label_is_visible,
+};
+
+static struct attribute *gtrace_link_attrs[] = {
+ &dev_attr_label.attr,
+ NULL,
+};
+
+static const struct attribute_group gtrace_link_group = {
+ .attrs = gtrace_link_attrs,
+ .is_visible = label_is_visible,
+};
+
+static ssize_t nr_links_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct gtrace_component *comp = to_gtrace_component(dev);
+
+ return sysfs_emit(buf, "%u\n", comp->pdata->nr_inconns + comp->pdata->nr_outconns);
+}
+static DEVICE_ATTR_RO(nr_links);
+
+static struct attribute *gtrace_conns_attrs[] = {
+ &dev_attr_nr_links.attr,
+ NULL,
+};
+
+static struct attribute_group gtrace_conns_group = {
+ .attrs = gtrace_conns_attrs,
+ .name = "connections",
+};
+
+static int gtrace_make_links(struct gtrace_component *comp)
+{
+ struct gtrace_platform_data *pdata = comp->pdata;
+ struct gtrace_connection *conn;
+ char *outs, *ins;
+ int i, ret;
+
+ for (i = 0; i < pdata->nr_outconns; i++) {
+ conn = pdata->outconns[i];
+ if (!conn->dest_comp)
+ continue;
+
+ outs = devm_kasprintf(pdata->dev, GFP_KERNEL,
+ "out:%d", conn->src_port);
+ ins = devm_kasprintf(&conn->dest_comp->dev, GFP_KERNEL,
+ "in:%d", conn->dest_port);
+ if (!outs || !ins)
+ return -ENOMEM;
+
+ ret = sysfs_add_link_to_group(&comp->dev.kobj,
+ "connections",
+ &conn->dest_comp->dev.kobj, outs);
+ if (ret)
+ return ret;
+
+ ret = sysfs_add_link_to_group(&conn->dest_comp->dev.kobj,
+ "connections",
+ &comp->dev.kobj, ins);
+ if (ret) {
+ sysfs_remove_link_from_group(&comp->dev.kobj,
+ "connections", outs);
+ return ret;
+ }
+ }
+ return 0;
+}
+
+static void gtrace_remove_links(struct gtrace_component *comp)
+{
+ struct gtrace_platform_data *pdata = comp->pdata;
+ struct gtrace_connection *conn;
+ char buf[32];
+ int i;
+
+ for (i = 0; i < pdata->nr_outconns; i++) {
+ conn = pdata->outconns[i];
+ if (!conn->dest_comp)
+ continue;
+
+ snprintf(buf, sizeof(buf), "out:%d", conn->src_port);
+ sysfs_remove_link_from_group(&comp->dev.kobj,
+ "connections", buf);
+
+ snprintf(buf, sizeof(buf), "in:%d", conn->dest_port);
+ sysfs_remove_link_from_group(&conn->dest_comp->dev.kobj,
+ "connections", buf);
+ }
+}
+
+int gtrace_sysfs_add(struct gtrace_component *comp)
+{
+ const struct attribute_group *grp;
+ int ret;
+
+ if (gtrace_is_sink(comp->pdata) || comp->pdata->is_linksink)
+ grp = >race_sink_group;
+ else if (gtrace_is_source(comp->pdata))
+ grp = >race_source_group;
+ else
+ grp = >race_link_group;
+
+ ret = sysfs_create_group(&comp->dev.kobj, grp);
+ if (ret) {
+ dev_err(&comp->dev, "failed to create sysfs group: %d\n", ret);
+ goto out;
+ }
+
+ ret = sysfs_create_group(&comp->dev.kobj, >race_conns_group);
+ if (ret) {
+ dev_err(&comp->dev, "failed to create connections sysfs group: %d\n", ret);
+ goto out_remove_group;
+ }
+
+ ret = gtrace_make_links(comp);
+ if (ret) {
+ dev_err(&comp->dev, "failed to make sysfs links: %d\n", ret);
+ goto out_remove_conns;
+ }
+
+ if (comp->pdata->groups) {
+ ret = device_add_groups(&comp->dev, comp->pdata->groups);
+ if (ret) {
+ dev_err(&comp->dev, "failed to create sysfs component groups: %d\n", ret);
+ goto out_remove_links;
+ }
+ }
+
+ return ret;
+
+out_remove_links:
+ gtrace_remove_links(comp);
+out_remove_conns:
+ sysfs_remove_group(&comp->dev.kobj, >race_conns_group);
+out_remove_group:
+ sysfs_remove_group(&comp->dev.kobj, grp);
+out:
+ return ret;
+}
+EXPORT_SYMBOL_GPL(gtrace_sysfs_add);
+
+void gtrace_sysfs_remove(struct gtrace_component *comp)
+{
+ mutex_lock(>race_mutex);
+ if (comp->path) {
+ gtrace_path_stop(comp->path);
+ gtrace_destroy_path(comp->path);
+ comp->path = NULL;
+ }
+ mutex_unlock(>race_mutex);
+
+ if (comp->pdata->groups)
+ device_remove_groups(&comp->dev, comp->pdata->groups);
+ gtrace_remove_links(comp);
+ sysfs_remove_group(&comp->dev.kobj, >race_conns_group);
+
+ if (gtrace_is_sink(comp->pdata) || comp->pdata->is_linksink)
+ sysfs_remove_group(&comp->dev.kobj, >race_sink_group);
+ else if (gtrace_is_source(comp->pdata))
+ sysfs_remove_group(&comp->dev.kobj, >race_source_group);
+ else
+ sysfs_remove_group(&comp->dev.kobj, >race_link_group);
+}
+EXPORT_SYMBOL_GPL(gtrace_sysfs_remove);
diff --git a/include/linux/gtrace.h b/include/linux/gtrace.h
index da8ba3779193..9945303d2713 100644
--- a/include/linux/gtrace.h
+++ b/include/linux/gtrace.h
@@ -14,6 +14,8 @@
#include <linux/property.h>
#include <linux/types.h>
+extern struct mutex gtrace_mutex;
+
/*
* Global list of trace component types across all architectures. Each backend
* uses the entries relevant to it.
@@ -29,6 +31,8 @@ enum gtrace_component_type {
/* Supported usage modes for trace components */
enum gtrace_component_mode {
+ GTRACE_COMPONENT_MODE_DISABLED,
+ GTRACE_COMPONENT_MODE_SYSFS,
GTRACE_COMPONENT_MODE_PERF,
GTRACE_COMPONENT_MODE_MAX
};
@@ -97,6 +101,9 @@ struct gtrace_hw_ops {
* @inconns: Array of pointers to input connections.
* @nr_outconns: Number of output connections.
* @outconns: Array of pointers to output connections.
+ * @is_linksink: A component that can serve as both a link and a sink.
+ * @groups: operations specific to this component. These will end up in
+ * the component's sysfs sub-directory.
*/
struct gtrace_platform_data {
struct device *dev;
@@ -138,6 +145,13 @@ struct gtrace_platform_data {
*/
unsigned int nr_outconns;
struct gtrace_connection **outconns;
+
+ /*
+ * True for components like ETF that can operate as both a link and
+ * a sink.
+ */
+ bool is_linksink;
+ const struct attribute_group **groups;
};
static inline u32 gtrace_read32(struct gtrace_platform_data *pdata, u32 offset)
@@ -156,6 +170,28 @@ static inline u32 gtrace_relaxed_read32(struct gtrace_platform_data *pdata, u32
return pdata->read(pdata, offset, true);
}
+static inline u64 gtrace_read_pair(struct gtrace_platform_data *pdata,
+ u32 lo_offset, u32 hi_offset)
+{
+ if (likely(pdata->io_mem))
+ return readl(pdata->base + lo_offset) |
+ ((u64)readl(pdata->base + hi_offset) << 32);
+
+ return pdata->read(pdata, lo_offset, false) |
+ ((u64)pdata->read(pdata, hi_offset, false) << 32);
+}
+
+static inline u64 gtrace_relaxed_read_pair(struct gtrace_platform_data *pdata,
+ u32 lo_offset, u32 hi_offset)
+{
+ if (likely(pdata->io_mem))
+ return readl_relaxed(pdata->base + lo_offset) |
+ ((u64)readl_relaxed(pdata->base + hi_offset) << 32);
+
+ return pdata->read(pdata, lo_offset, true) |
+ ((u64)pdata->read(pdata, hi_offset, true) << 32);
+}
+
static inline void gtrace_write32(struct gtrace_platform_data *pdata, u32 val, u32 offset)
{
if (likely(pdata->io_mem))
@@ -173,6 +209,30 @@ static inline void gtrace_relaxed_write32(struct gtrace_platform_data *pdata,
pdata->write(pdata, val, offset, true);
}
+static inline void gtrace_relaxed_write_pair(struct gtrace_platform_data *pdata,
+ u64 val, u32 lo_offset, u32 hi_offset)
+{
+ if (likely(pdata->io_mem)) {
+ writel_relaxed((u32)val, pdata->base + lo_offset);
+ writel_relaxed((u32)(val >> 32), pdata->base + hi_offset);
+ } else {
+ pdata->write(pdata, (u32)val, lo_offset, true);
+ pdata->write(pdata, (u32)(val >> 32), hi_offset, true);
+ }
+}
+
+static inline void gtrace_write_pair(struct gtrace_platform_data *pdata,
+ u64 val, u32 lo_offset, u32 hi_offset)
+{
+ if (likely(pdata->io_mem)) {
+ writel((u32)val, pdata->base + lo_offset);
+ writel((u32)(val >> 32), pdata->base + hi_offset);
+ } else {
+ pdata->write(pdata, (u32)val, lo_offset, false);
+ pdata->write(pdata, (u32)(val >> 32), hi_offset, false);
+ }
+}
+
static inline bool gtrace_is_source(struct gtrace_platform_data *pdata)
{
return !pdata->nr_inconns ? true : false;
@@ -200,11 +260,18 @@ struct gtrace_component_id {
* @pdata: Pointer to underlying platform data.
* @id: Details to match the component.
* @dev: Device instance.
+ * @path: Used for sysfs sources to get path, and for ETF link/sink detection.
+ * @sysfs_sink_activated: When set, this sink is preferred over other
+ * reachable sinks when a source walks forward
+ * looking for a sysfs trace target (e.g. stop
+ * at ETF instead of ETR).
*/
struct gtrace_component {
struct gtrace_platform_data *pdata;
struct gtrace_component_id id;
struct device dev;
+ struct gtrace_path *path;
+ bool sysfs_sink_activated;
};
#define to_gtrace_component(__dev) container_of_const(__dev, struct gtrace_component, dev)
@@ -329,4 +396,41 @@ static inline void gtrace_unregister_driver(struct gtrace_driver *gtdrv)
int gtrace_perf_init(void);
void gtrace_perf_exit(void);
+enum gtrace_component_mode gtrace_get_mode(struct gtrace_component *comp);
+
+struct gtrace_pair_attribute {
+ struct device_attribute attr;
+ u32 lo_off;
+ u32 hi_off;
+};
+
+struct gtrace_off_attribute {
+ struct device_attribute attr;
+ u32 off;
+};
+
+ssize_t gtrace_simple_show32(struct device *dev, struct device_attribute *attr, char *buf);
+ssize_t gtrace_simple_show_pair(struct device *dev, struct device_attribute *attr, char *buf);
+
+#define gtrace_simple_reg32(name, offset) \
+ (&((struct gtrace_off_attribute[]) { \
+ { \
+ __ATTR(name, 0444, gtrace_simple_show32, NULL), \
+ offset \
+ } \
+ })[0].attr.attr)
+
+#define gtrace_simple_reg64(name, lo_off, hi_off) \
+ (&((struct gtrace_pair_attribute[]) { \
+ { \
+ __ATTR(name, 0444, gtrace_simple_show_pair, NULL), \
+ lo_off, hi_off \
+ } \
+ })[0].attr.attr)
+
+int gtrace_enable_sysfs(struct gtrace_component *comp);
+int gtrace_disable_sysfs(struct gtrace_component *comp);
+int gtrace_sysfs_add(struct gtrace_component *comp);
+void gtrace_sysfs_remove(struct gtrace_component *comp);
+
#endif /* __LINUX_GTRACE_H__ */
--
2.34.1
^ permalink raw reply [flat|nested] 25+ messages in thread* [PATCH 03/24] gtrace: Drop trace format field and helper function
2026-10-23 3:14 [RFC PATCH 00/24] Add Linux RISC-V trace support via the gtrace framework Zane Leung
2026-10-23 3:14 ` [PATCH 01/24] gtrace: Move connection release into component unregister Zane Leung
2026-10-23 3:14 ` [PATCH 02/24] gtrace: Initial implementation of gtrace sysfs framework Zane Leung
@ 2026-10-23 3:14 ` Zane Leung
2026-10-23 3:14 ` [PATCH 04/24] gtrace: Add RISC-V trace encoder sysfs support Zane Leung
` (20 subsequent siblings)
23 siblings, 0 replies; 25+ messages in thread
From: Zane Leung @ 2026-10-23 3:14 UTC (permalink / raw)
To: gregkh, palmer, pjw, alexander.shishkin, suzuki.poulose,
mchitale, anup, irogers
Cc: mike.leach, james.clark, leo.yan, peterz, mingo, namhyung,
mark.rutland, jolsa, adrian.hunter, alex, atish.patra,
andrew.jones, sunilvl, liangzhen, zhuangqiubin, coresight,
linux-riscv, linux-kernel
Remove the format field from struct gtrace_platform_data and drop
the associated gtrace_path_aux_format() helper function.
Signed-off-by: Zane Leung <liangzhen@linux.spacemit.com>
---
drivers/hwtracing/gtrace/gtrace-perf.c | 15 --------
drivers/hwtracing/gtrace/rvtrace-encoder.c | 39 ---------------------
drivers/hwtracing/gtrace/rvtrace-platform.c | 3 +-
drivers/hwtracing/gtrace/rvtrace-ramsink.c | 1 -
include/linux/gtrace.h | 3 --
5 files changed, 1 insertion(+), 60 deletions(-)
diff --git a/drivers/hwtracing/gtrace/gtrace-perf.c b/drivers/hwtracing/gtrace/gtrace-perf.c
index 928f0405c6c1..56713c8ee850 100644
--- a/drivers/hwtracing/gtrace/gtrace-perf.c
+++ b/drivers/hwtracing/gtrace/gtrace-perf.c
@@ -167,20 +167,6 @@ static void *gtrace_setup_aux(struct perf_event *event, void **pages,
return NULL;
}
-static u64 gtrace_path_aux_format(struct gtrace_path *path)
-{
- struct gtrace_component *src = gtrace_path_source(path);
-
- switch (src->pdata->format) {
- case GTRACE_FORMAT_ETRACE:
- return PERF_AUX_FLAG_GTRACE_FORMAT_ETRACE;
- case GTRACE_FORMAT_NTRACE:
- return PERF_AUX_FLAG_GTRACE_FORMAT_NTRACE;
- default:
- return PERF_AUX_FLAG_GTRACE_FORMAT_UNKNOWN;
- }
-}
-
static void gtrace_event_read(struct perf_event *event)
{
}
@@ -295,7 +281,6 @@ static void gtrace_event_stop(struct perf_event *event, int mode)
return;
ret = gtrace_path_copyto_auxbuf(path, &event_data->buf, &size, &format);
WARN_ON_ONCE(ret);
- format = gtrace_path_aux_format(path);
if (READ_ONCE(handle->event)) {
/* Tag the AUX data with the source's trace format. */
perf_aux_output_flag(handle, format);
diff --git a/drivers/hwtracing/gtrace/rvtrace-encoder.c b/drivers/hwtracing/gtrace/rvtrace-encoder.c
index 1f06b1a2b412..f24780b1ca1e 100644
--- a/drivers/hwtracing/gtrace/rvtrace-encoder.c
+++ b/drivers/hwtracing/gtrace/rvtrace-encoder.c
@@ -53,38 +53,11 @@ static void rvtrace_encoder_set_srcid(struct gtrace_platform_data *pdata, u32 sr
gtrace_write32(pdata, val, RVTRACE_COMPONENT_CTRL_OFFSET);
}
-/* Set requested trace format and return 0 if hardware supports that format */
-static int rvtrace_encoder_set_format(struct gtrace_platform_data *pdata)
-{
- u32 val, fmt;
-
- switch (pdata->format) {
- case GTRACE_FORMAT_ETRACE:
- fmt = RVTRACE_COMPONENT_CTRL_FORMAT_ETRACE;
- break;
- case GTRACE_FORMAT_NTRACE:
- fmt = RVTRACE_COMPONENT_CTRL_FORMAT_NTRACE;
- break;
- default:
- return -EINVAL;
- }
-
- val = gtrace_read32(pdata, RVTRACE_COMPONENT_CTRL_OFFSET);
- val &= ~(RVTRACE_COMPONENT_CTRL_FORMAT_MASK << RVTRACE_COMPONENT_CTRL_FORMAT_SHIFT);
- val |= fmt << RVTRACE_COMPONENT_CTRL_FORMAT_SHIFT;
- gtrace_write32(pdata, val, RVTRACE_COMPONENT_CTRL_OFFSET);
-
- val = gtrace_read32(pdata, RVTRACE_COMPONENT_CTRL_OFFSET);
- val = (val >> RVTRACE_COMPONENT_CTRL_FORMAT_SHIFT) & RVTRACE_COMPONENT_CTRL_FORMAT_MASK;
- return val == fmt ? 0 : -EOPNOTSUPP;
-}
-
static int rvtrace_encoder_probe(struct gtrace_component *comp)
{
struct gtrace_platform_data *pdata = comp->pdata;
struct rvtrace_encoder_priv *priv;
u32 val, hw_srcbits, srcbits;
- int ret;
if (pdata->bound_cpu < 0) {
dev_err(&comp->dev, "cpu property missing. Please fix firmware.\n");
@@ -95,12 +68,6 @@ static int rvtrace_encoder_probe(struct gtrace_component *comp)
if (!priv)
return -ENOMEM;
- ret = rvtrace_encoder_set_format(pdata);
- if (ret) {
- dev_err(&comp->dev, "failed to set format %d\n", pdata->format);
- return ret;
- }
-
/* Check if trTeInhibitSrc is hardwired to 1 */
val = gtrace_read32(pdata, RVTRACE_COMPONENT_CTRL_OFFSET);
val &= ~BIT(RVTRACE_COMPONENT_CTRL_INHIBITSRC_SHIFT);
@@ -144,12 +111,6 @@ static int rvtrace_encoder_start(struct gtrace_component *comp)
if (priv->srcbits)
rvtrace_encoder_set_srcid(pdata, priv->srcbits);
- ret = rvtrace_encoder_set_format(pdata);
- if (ret) {
- dev_err(&comp->dev, "failed to set format %d\n", pdata->format);
- return ret;
- }
-
ret = gtrace_enable_component(comp);
if (ret) {
dev_err(&comp->dev, "failed to enable encoder.\n");
diff --git a/drivers/hwtracing/gtrace/rvtrace-platform.c b/drivers/hwtracing/gtrace/rvtrace-platform.c
index 189ff0b8bc18..e5c1f47388a2 100644
--- a/drivers/hwtracing/gtrace/rvtrace-platform.c
+++ b/drivers/hwtracing/gtrace/rvtrace-platform.c
@@ -78,7 +78,6 @@ static int rvtrace_platform_probe(struct platform_device *pdev)
pdata->dev = dev;
pdata->impid = RVTRACE_COMPONENT_IMPID_UNKNOWN;
pdata->hw_ops = &rvtrace_hw_ops;
- pdata->format = (uintptr_t)device_get_match_data(dev);
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res)
@@ -146,7 +145,7 @@ static void rvtrace_platform_remove(struct platform_device *pdev)
}
static const struct of_device_id rvtrace_platform_match[] = {
- { .compatible = "riscv,trace-component", .data = (void *)GTRACE_FORMAT_ETRACE },
+ { .compatible = "riscv,trace-component" },
{}
};
diff --git a/drivers/hwtracing/gtrace/rvtrace-ramsink.c b/drivers/hwtracing/gtrace/rvtrace-ramsink.c
index 67b6779aacee..9588c32c5b13 100644
--- a/drivers/hwtracing/gtrace/rvtrace-ramsink.c
+++ b/drivers/hwtracing/gtrace/rvtrace-ramsink.c
@@ -161,7 +161,6 @@ static size_t rvtrace_ramsink_copyto_auxbuf(struct gtrace_component *comp,
dst.cur = buf->pos;
src.base = priv->va;
src.len = priv->size;
- *format = PERF_AUX_FLAG_GTRACE_FORMAT_ETRACE;
wp = rvtrace_ramsink_read_pair(comp->pdata, RVTRACE_RAMSINK_WPLOW_OFF);
wrap = wp & RVTRACE_RAMSINK_WPLOW_WRAP;
wp &= ~RVTRACE_RAMSINK_WPLOW_WRAP;
diff --git a/include/linux/gtrace.h b/include/linux/gtrace.h
index 9945303d2713..0a017f1f888d 100644
--- a/include/linux/gtrace.h
+++ b/include/linux/gtrace.h
@@ -96,7 +96,6 @@ struct gtrace_hw_ops {
* @bound_cpu: CPU to which the component is bound, or -1 if unbound. For a
* source component bound to a CPU this must not be -1.
* @control_poll_timeout_usecs: Delay in usecs when polling control bits.
- * @format: Trace format selected by the platform driver.
* @nr_inconns: Number of input connections.
* @inconns: Array of pointers to input connections.
* @nr_outconns: Number of output connections.
@@ -128,8 +127,6 @@ struct gtrace_platform_data {
/* Delay in microseconds when polling control register bits */
int control_poll_timeout_usecs;
- enum gtrace_format format;
-
/*
* Platform driver must only populate empty pointer array without
* any actual input connections.
--
2.34.1
^ permalink raw reply [flat|nested] 25+ messages in thread* [PATCH 04/24] gtrace: Add RISC-V trace encoder sysfs support
2026-10-23 3:14 [RFC PATCH 00/24] Add Linux RISC-V trace support via the gtrace framework Zane Leung
` (2 preceding siblings ...)
2026-10-23 3:14 ` [PATCH 03/24] gtrace: Drop trace format field and helper function Zane Leung
@ 2026-10-23 3:14 ` Zane Leung
2026-10-23 3:14 ` [PATCH 05/24] gtrace: Introduce some PMU format attributes Zane Leung
` (19 subsequent siblings)
23 siblings, 0 replies; 25+ messages in thread
From: Zane Leung @ 2026-10-23 3:14 UTC (permalink / raw)
To: gregkh, palmer, pjw, alexander.shishkin, suzuki.poulose,
mchitale, anup, irogers
Cc: mike.leach, james.clark, leo.yan, peterz, mingo, namhyung,
mark.rutland, jolsa, adrian.hunter, alex, atish.patra,
andrew.jones, sunilvl, liangzhen, zhuangqiubin, coresight,
linux-riscv, linux-kernel
Add sysfs attribute support for RISC-V trace encoder components in
the platform driver. The encoder attribute groups are now assigned to
pdata->groups based on component type during registration, enabling
encoder configuration through sysfs interfaces.
Signed-off-by: Zane Leung <liangzhen@linux.spacemit.com>
---
drivers/hwtracing/gtrace/Makefile | 1 +
...trace-encoder.c => rvtrace-encoder-core.c} | 117 +++++--
.../hwtracing/gtrace/rvtrace-encoder-sysfs.c | 290 ++++++++++++++++++
drivers/hwtracing/gtrace/rvtrace-encoder.h | 53 ++++
drivers/hwtracing/gtrace/rvtrace-platform.c | 8 +
drivers/hwtracing/gtrace/rvtrace.h | 3 +
6 files changed, 438 insertions(+), 34 deletions(-)
rename drivers/hwtracing/gtrace/{rvtrace-encoder.c => rvtrace-encoder-core.c} (60%)
create mode 100644 drivers/hwtracing/gtrace/rvtrace-encoder-sysfs.c
create mode 100644 drivers/hwtracing/gtrace/rvtrace-encoder.h
diff --git a/drivers/hwtracing/gtrace/Makefile b/drivers/hwtracing/gtrace/Makefile
index ff79a49bdb5e..87cb5add78d4 100644
--- a/drivers/hwtracing/gtrace/Makefile
+++ b/drivers/hwtracing/gtrace/Makefile
@@ -6,4 +6,5 @@ gtrace-$(CONFIG_OF) += gtrace-of.o
obj-$(CONFIG_RVTRACE) += rvtrace-platform.o
obj-$(CONFIG_RVTRACE_ENCODER) += rvtrace-encoder.o
+rvtrace-encoder-y := rvtrace-encoder-core.o rvtrace-encoder-sysfs.o
obj-$(CONFIG_RVTRACE_RAMSINK) += rvtrace-ramsink.o
diff --git a/drivers/hwtracing/gtrace/rvtrace-encoder.c b/drivers/hwtracing/gtrace/rvtrace-encoder-core.c
similarity index 60%
rename from drivers/hwtracing/gtrace/rvtrace-encoder.c
rename to drivers/hwtracing/gtrace/rvtrace-encoder-core.c
index f24780b1ca1e..01925ebdfbde 100644
--- a/drivers/hwtracing/gtrace/rvtrace-encoder.c
+++ b/drivers/hwtracing/gtrace/rvtrace-encoder-core.c
@@ -10,28 +10,13 @@
#include <linux/minmax.h>
#include <linux/slab.h>
#include <linux/types.h>
+#include <linux/bitfield.h>
#include "rvtrace.h"
+#include "rvtrace-encoder.h"
-#define RVTRACE_COMPONENT_CTRL_ITRACE_SHIFT 2
-#define RVTRACE_COMPONENT_CTRL_INSTMODE_MASK 0x7
-#define RVTRACE_COMPONENT_CTRL_INSTMODE_SHIFT 4
-#define RVTRACE_COMPONENT_CTRL_INSTMODE_OPIT 0x6
-#define RVTRACE_COMPONENT_CTRL_INHIBITSRC_SHIFT 15
-#define RVTRACE_COMPONENT_CTRL_FORMAT_MASK 0x7
-#define RVTRACE_COMPONENT_CTRL_FORMAT_SHIFT 24
-#define RVTRACE_COMPONENT_CTRL_FORMAT_ETRACE 0x0
-#define RVTRACE_COMPONENT_CTRL_FORMAT_NTRACE 0x1
-
-#define RVTRACE_ENCODER_INSTFEAT_OFFSET 0x8
-#define RVTRACE_ENCODER_INSTFEAT_SRCID_MASK 0xfff
-#define RVTRACE_ENCODER_INSTFEAT_SRCID_SHIFT 16
-#define RVTRACE_ENCODER_INSTFEAT_SRCBITS_MASK 0xf
-#define RVTRACE_ENCODER_INSTFEAT_SRCBITS_SHIFT 28
-#define RVTRACE_ENCODER_INSTFEAT_SRCBITS_MAX 12
-
-struct rvtrace_encoder_priv {
- u32 srcbits;
-};
+static int boot_enable;
+module_param(boot_enable, int, 0444);
+MODULE_PARM_DESC(boot_enable, "Enable tracing on boot");
/*
* Set the srcid and clear trTeInhibitSrc bit so that the emitted trace messages carry
@@ -42,10 +27,10 @@ static void rvtrace_encoder_set_srcid(struct gtrace_platform_data *pdata, u32 sr
u32 val;
val = gtrace_read32(pdata, RVTRACE_ENCODER_INSTFEAT_OFFSET);
- val &= ~(RVTRACE_ENCODER_INSTFEAT_SRCID_MASK << RVTRACE_ENCODER_INSTFEAT_SRCID_SHIFT);
- val &= ~(RVTRACE_ENCODER_INSTFEAT_SRCBITS_MASK << RVTRACE_ENCODER_INSTFEAT_SRCBITS_SHIFT);
- val |= pdata->bound_cpu << RVTRACE_ENCODER_INSTFEAT_SRCID_SHIFT;
- val |= srcbits << RVTRACE_ENCODER_INSTFEAT_SRCBITS_SHIFT;
+ val &= ~RVTRACE_ENCODER_INSTFEAT_SRCID_MASK;
+ val &= ~RVTRACE_ENCODER_INSTFEAT_SRCBITS_MASK;
+ val |= FIELD_PREP(RVTRACE_ENCODER_INSTFEAT_SRCBITS_MASK, srcbits);
+ val |= FIELD_PREP(RVTRACE_ENCODER_INSTFEAT_SRCID_MASK, pdata->bound_cpu);
gtrace_write32(pdata, val, RVTRACE_ENCODER_INSTFEAT_OFFSET);
val = gtrace_read32(pdata, RVTRACE_COMPONENT_CTRL_OFFSET);
@@ -53,11 +38,32 @@ static void rvtrace_encoder_set_srcid(struct gtrace_platform_data *pdata, u32 sr
gtrace_write32(pdata, val, RVTRACE_COMPONENT_CTRL_OFFSET);
}
+static void rvtrace_encoder_get_syncmode_hw(struct gtrace_platform_data *pdata,
+ bool *syncmode_hw)
+{
+ u32 ctrl, val;
+ enum rvtrace_encoder_syncmode mode;
+
+ ctrl = gtrace_read32(pdata, RVTRACE_COMPONENT_CTRL_OFFSET);
+
+ for (mode = RVTRACE_ENCODER_SYNCMODE_OFF; mode < RVTRACE_ENCODER_SYNCMODE_MAX; mode++) {
+ val = ctrl & ~RVTRACE_COMPONENT_CTRL_SYNCMODE_MASK;
+ val |= FIELD_PREP(RVTRACE_COMPONENT_CTRL_SYNCMODE_MASK, mode);
+ gtrace_write32(pdata, val, RVTRACE_COMPONENT_CTRL_OFFSET);
+ val = gtrace_read32(pdata, RVTRACE_COMPONENT_CTRL_OFFSET);
+ val = FIELD_GET(RVTRACE_COMPONENT_CTRL_SYNCMODE_MASK, val);
+ syncmode_hw[mode] = (mode == val);
+ }
+
+ gtrace_write32(pdata, ctrl, RVTRACE_COMPONENT_CTRL_OFFSET);
+}
+
static int rvtrace_encoder_probe(struct gtrace_component *comp)
{
struct gtrace_platform_data *pdata = comp->pdata;
struct rvtrace_encoder_priv *priv;
u32 val, hw_srcbits, srcbits;
+ int ret;
if (pdata->bound_cpu < 0) {
dev_err(&comp->dev, "cpu property missing. Please fix firmware.\n");
@@ -68,6 +74,28 @@ static int rvtrace_encoder_probe(struct gtrace_component *comp)
if (!priv)
return -ENOMEM;
+ /* Get trTeFormat */
+ val = gtrace_read32(pdata, RVTRACE_COMPONENT_CTRL_OFFSET);
+ priv->format = FIELD_GET(RVTRACE_COMPONENT_CTRL_FORMAT_MASK, val);
+ if (priv->format > GTRACE_FORMAT_NTRACE) {
+ dev_err(&comp->dev, "unsupported trace format %d\n", priv->format);
+ return -EINVAL;
+ }
+
+ /* Get trTeInstSyncMode */
+ priv->syncmode = FIELD_GET(RVTRACE_COMPONENT_CTRL_SYNCMODE_MASK, val);
+ rvtrace_encoder_get_syncmode_hw(pdata, priv->syncmode_hw);
+
+ /* Check if trTeContext is hardwired to 0 */
+ val |= BIT(RVTRACE_COMPONENT_CTRL_CONTEXT_SHIFT);
+ gtrace_write32(pdata, val, RVTRACE_COMPONENT_CTRL_OFFSET);
+ val = gtrace_read32(pdata, RVTRACE_COMPONENT_CTRL_OFFSET);
+ if (val & BIT(RVTRACE_COMPONENT_CTRL_CONTEXT_SHIFT)) {
+ priv->has_context = true;
+ val &= ~BIT(RVTRACE_COMPONENT_CTRL_CONTEXT_SHIFT);
+ gtrace_write32(pdata, val, RVTRACE_COMPONENT_CTRL_OFFSET);
+ }
+
/* Check if trTeInhibitSrc is hardwired to 1 */
val = gtrace_read32(pdata, RVTRACE_COMPONENT_CTRL_OFFSET);
val &= ~BIT(RVTRACE_COMPONENT_CTRL_INHIBITSRC_SHIFT);
@@ -76,14 +104,16 @@ static int rvtrace_encoder_probe(struct gtrace_component *comp)
if (!(val & BIT(RVTRACE_COMPONENT_CTRL_INHIBITSRC_SHIFT))) {
/* Probe how many bits can trTeSrcID span in the emitted Trace messages */
val = gtrace_read32(pdata, RVTRACE_ENCODER_INSTFEAT_OFFSET);
- val |= (RVTRACE_ENCODER_INSTFEAT_SRCBITS_MASK <<
- RVTRACE_ENCODER_INSTFEAT_SRCBITS_SHIFT);
- gtrace_write32(pdata, val, RVTRACE_ENCODER_INSTFEAT_OFFSET);
- val = gtrace_read32(pdata, RVTRACE_ENCODER_INSTFEAT_OFFSET);
- hw_srcbits = (val >> RVTRACE_ENCODER_INSTFEAT_SRCBITS_SHIFT) &
- RVTRACE_ENCODER_INSTFEAT_SRCBITS_MASK;
+ hw_srcbits = RVTRACE_ENCODER_INSTFEAT_SRCBITS_MAX;
+ do {
+ val &= ~RVTRACE_ENCODER_INSTFEAT_SRCBITS_MASK;
+ val |= FIELD_PREP(RVTRACE_ENCODER_INSTFEAT_SRCBITS_MASK, hw_srcbits);
+ gtrace_write32(pdata, val, RVTRACE_ENCODER_INSTFEAT_OFFSET);
+ val = gtrace_read32(pdata, RVTRACE_ENCODER_INSTFEAT_OFFSET);
+ if (FIELD_GET(RVTRACE_ENCODER_INSTFEAT_SRCBITS_MASK, val) == hw_srcbits)
+ break;
+ } while (--hw_srcbits);
/* Determine if there are sufficient bits to hold max cpus */
- hw_srcbits = min_t(u32, hw_srcbits, RVTRACE_ENCODER_INSTFEAT_SRCBITS_MAX);
srcbits = min_t(u32, hw_srcbits, order_base_2(nr_cpu_ids));
if (srcbits && pdata->bound_cpu < BIT(srcbits)) {
rvtrace_encoder_set_srcid(pdata, srcbits);
@@ -98,6 +128,15 @@ static int rvtrace_encoder_probe(struct gtrace_component *comp)
gtrace_write32(pdata, val, RVTRACE_COMPONENT_CTRL_OFFSET);
}
dev_set_drvdata(&comp->dev, priv);
+
+ if (boot_enable) {
+ ret = gtrace_enable_sysfs(comp);
+ if (ret) {
+ dev_err(&comp->dev, "failed to enable tracing on boot.\n");
+ return -EINVAL;
+ }
+ priv->boot_enable = true;
+ }
return 0;
}
@@ -117,10 +156,20 @@ static int rvtrace_encoder_start(struct gtrace_component *comp)
return ret;
}
- /* set mode */
val = gtrace_read32(pdata, RVTRACE_COMPONENT_CTRL_OFFSET);
- val &= ~(RVTRACE_COMPONENT_CTRL_INSTMODE_MASK << RVTRACE_COMPONENT_CTRL_INSTMODE_SHIFT);
- val |= (RVTRACE_COMPONENT_CTRL_INSTMODE_OPIT << RVTRACE_COMPONENT_CTRL_INSTMODE_SHIFT);
+ /* set mode */
+ val &= ~RVTRACE_COMPONENT_CTRL_INSTMODE_MASK;
+ val |= FIELD_PREP(RVTRACE_COMPONENT_CTRL_INSTMODE_MASK,
+ RVTRACE_COMPONENT_CTRL_INSTMODE_OPIT);
+ /* set context */
+ val &= ~BIT(RVTRACE_COMPONENT_CTRL_CONTEXT_SHIFT);
+ val |= FIELD_PREP(BIT(RVTRACE_COMPONENT_CTRL_CONTEXT_SHIFT), !!priv->context);
+ /* set syncmode */
+ val &= ~RVTRACE_COMPONENT_CTRL_SYNCMODE_MASK;
+ val |= FIELD_PREP(RVTRACE_COMPONENT_CTRL_SYNCMODE_MASK, priv->syncmode);
+ /* set syncmax */
+ val &= ~RVTRACE_COMPONENT_CTRL_SYNCMAX_MASK;
+ val |= FIELD_PREP(RVTRACE_COMPONENT_CTRL_SYNCMAX_MASK, priv->syncmax);
gtrace_write32(pdata, val, RVTRACE_COMPONENT_CTRL_OFFSET);
val = gtrace_read32(pdata, RVTRACE_COMPONENT_CTRL_OFFSET);
diff --git a/drivers/hwtracing/gtrace/rvtrace-encoder-sysfs.c b/drivers/hwtracing/gtrace/rvtrace-encoder-sysfs.c
new file mode 100644
index 000000000000..e973d0e43036
--- /dev/null
+++ b/drivers/hwtracing/gtrace/rvtrace-encoder-sysfs.c
@@ -0,0 +1,290 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2026 Qualcomm Technologies, Inc.
+ */
+
+#include <linux/device.h>
+#include <linux/gtrace.h>
+#include <linux/types.h>
+#include "rvtrace.h"
+#include "rvtrace-encoder.h"
+
+struct rvtrace_component_reg {
+ struct gtrace_component *comp;
+ u32 offset;
+ u32 data;
+};
+
+static void do_smp_cross_read(void *data)
+{
+ struct rvtrace_component_reg *reg = data;
+
+ reg->data = gtrace_relaxed_read32(reg->comp->pdata, reg->offset);
+}
+
+static u32 rvtrace_cross_read(struct gtrace_component *comp, u32 offset)
+{
+ struct gtrace_platform_data *pdata = comp->pdata;
+ struct rvtrace_component_reg reg;
+
+ reg.comp = comp;
+ reg.offset = offset;
+ /*
+ * smp cross call ensures the CPU will be powered up before
+ * accessing the RISC-V trace core registers
+ */
+ smp_call_function_single(pdata->bound_cpu, do_smp_cross_read, ®, 1);
+ return reg.data;
+}
+
+static u32 rvtrace_attr_to_offset(struct device_attribute *attr)
+{
+ struct dev_ext_attribute *eattr;
+
+ eattr = container_of(attr, struct dev_ext_attribute, attr);
+ return (u32)(unsigned long)eattr->var;
+}
+
+static ssize_t rvtrace_reg_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ u32 val, offset;
+ struct gtrace_component *comp = to_gtrace_component(dev);
+
+ offset = rvtrace_attr_to_offset(attr);
+
+ val = rvtrace_cross_read(comp, offset);
+
+ return scnprintf(buf, PAGE_SIZE, "0x%x\n", val);
+}
+
+/*
+ * Macro to set an RO ext attribute with offset and show function.
+ */
+#define rvtrace_reg_showfn(name, offset, showfn) ( \
+ &((struct dev_ext_attribute[]) { \
+ { \
+ __ATTR(name, 0444, showfn, NULL), \
+ (void *)(unsigned long)offset \
+ } \
+ })[0].attr.attr \
+ )
+
+/* macro using the default rvtrace_reg_show function */
+#define rvtrace_reg(name, offset) \
+ rvtrace_reg_showfn(name, offset, rvtrace_reg_show)
+
+static ssize_t cpu_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct gtrace_component *comp = to_gtrace_component(dev);
+
+ return sysfs_emit(buf, "%d\n", comp->pdata->bound_cpu);
+}
+static DEVICE_ATTR_RO(cpu);
+
+static struct attribute *rvtrace_encoder_attrs[] = {
+ &dev_attr_cpu.attr,
+ NULL,
+};
+
+static struct attribute *rvtrace_encoder_mgmt_attrs[] = {
+ rvtrace_reg(control, RVTRACE_COMPONENT_CTRL_OFFSET),
+ rvtrace_reg(features, RVTRACE_ENCODER_INSTFEAT_OFFSET),
+ NULL,
+};
+
+static ssize_t format_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct gtrace_component *comp = to_gtrace_component(dev);
+ struct rvtrace_encoder_priv *priv = dev_get_drvdata(&comp->dev);
+
+ return sysfs_emit(buf, "%u\n", priv->format);
+}
+static DEVICE_ATTR_RO(format);
+
+static ssize_t has_context_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct gtrace_component *comp = to_gtrace_component(dev);
+ struct rvtrace_encoder_priv *priv = dev_get_drvdata(&comp->dev);
+
+ return sysfs_emit(buf, "%u\n", priv->has_context);
+}
+static DEVICE_ATTR_RO(has_context);
+
+static ssize_t context_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct gtrace_component *comp = to_gtrace_component(dev);
+ struct rvtrace_encoder_priv *priv = dev_get_drvdata(&comp->dev);
+
+ return sysfs_emit(buf, "%u\n", priv->context);
+}
+
+static ssize_t context_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ int ret;
+ unsigned long val;
+ struct gtrace_component *comp = to_gtrace_component(dev);
+ struct rvtrace_encoder_priv *priv = dev_get_drvdata(&comp->dev);
+
+ if (!priv->has_context)
+ return -EINVAL;
+
+ ret = kstrtoul(buf, 10, &val);
+ if (ret)
+ return ret;
+
+ priv->context = !!val;
+
+ return size;
+}
+static DEVICE_ATTR_RW(context);
+
+static const char *const syncmodes_str[] = {
+ [RVTRACE_ENCODER_SYNCMODE_OFF] = "off",
+ [RVTRACE_ENCODER_SYNCMODE_MESSAGES] = "messages",
+ [RVTRACE_ENCODER_SYNCMODE_CLOCK] = "clock",
+ [RVTRACE_ENCODER_SYNCMODE_INSTRUCTION] = "instruction",
+};
+
+static ssize_t syncmodes_available_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct gtrace_component *comp = to_gtrace_component(dev);
+ struct rvtrace_encoder_priv *priv = dev_get_drvdata(&comp->dev);
+ ssize_t size = 0;
+ enum rvtrace_encoder_syncmode mode;
+
+ for (mode = RVTRACE_ENCODER_SYNCMODE_OFF; mode < RVTRACE_ENCODER_SYNCMODE_MAX; mode++) {
+ if (priv->syncmode_hw[mode])
+ size += sysfs_emit_at(buf, size, "%s ",
+ syncmodes_str[mode]);
+ }
+
+ size += sysfs_emit_at(buf, size, "\n");
+ return size;
+}
+static DEVICE_ATTR_RO(syncmodes_available);
+
+static ssize_t syncmode_preferred_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct gtrace_component *comp = to_gtrace_component(dev);
+ struct rvtrace_encoder_priv *priv = dev_get_drvdata(&comp->dev);
+
+ return sysfs_emit(buf, "%s\n", syncmodes_str[priv->syncmode]);
+}
+
+static ssize_t syncmode_preferred_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct gtrace_component *comp = to_gtrace_component(dev);
+ struct rvtrace_encoder_priv *priv = dev_get_drvdata(&comp->dev);
+
+ if (sysfs_streq(buf, syncmodes_str[RVTRACE_ENCODER_SYNCMODE_OFF]) &&
+ priv->syncmode_hw[RVTRACE_ENCODER_SYNCMODE_OFF])
+ priv->syncmode = RVTRACE_ENCODER_SYNCMODE_OFF;
+ else if (sysfs_streq(buf, syncmodes_str[RVTRACE_ENCODER_SYNCMODE_MESSAGES]) &&
+ priv->syncmode_hw[RVTRACE_ENCODER_SYNCMODE_MESSAGES])
+ priv->syncmode = RVTRACE_ENCODER_SYNCMODE_MESSAGES;
+ else if (sysfs_streq(buf, syncmodes_str[RVTRACE_ENCODER_SYNCMODE_CLOCK]) &&
+ priv->syncmode_hw[RVTRACE_ENCODER_SYNCMODE_CLOCK])
+ priv->syncmode = RVTRACE_ENCODER_SYNCMODE_CLOCK;
+ else if (sysfs_streq(buf, syncmodes_str[RVTRACE_ENCODER_SYNCMODE_INSTRUCTION]) &&
+ priv->syncmode_hw[RVTRACE_ENCODER_SYNCMODE_INSTRUCTION])
+ priv->syncmode = RVTRACE_ENCODER_SYNCMODE_INSTRUCTION;
+ else
+ return -EINVAL;
+ return size;
+}
+static DEVICE_ATTR_RW(syncmode_preferred);
+
+static ssize_t syncmax_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct gtrace_component *comp = to_gtrace_component(dev);
+ struct rvtrace_encoder_priv *priv = dev_get_drvdata(&comp->dev);
+
+ return sysfs_emit(buf, "%u\n", priv->syncmax);
+}
+
+static ssize_t syncmax_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct gtrace_component *comp = to_gtrace_component(dev);
+ struct rvtrace_encoder_priv *priv = dev_get_drvdata(&comp->dev);
+ unsigned long val;
+ int ret;
+
+ ret = kstrtoul(buf, 0, &val);
+ if (ret)
+ return ret;
+
+ if (val > RVTRACE_COMPONENT_CTRL_SYNCMAX_MAX)
+ return -EINVAL;
+
+ priv->syncmax = val;
+ return size;
+}
+static DEVICE_ATTR_RW(syncmax);
+
+static struct attribute *rvtrace_encoder_control_attrs[] = {
+ &dev_attr_format.attr,
+ &dev_attr_has_context.attr,
+ &dev_attr_context.attr,
+ &dev_attr_syncmodes_available.attr,
+ &dev_attr_syncmode_preferred.attr,
+ &dev_attr_syncmax.attr,
+ NULL,
+};
+
+static ssize_t srcbits_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct gtrace_component *comp = to_gtrace_component(dev);
+ struct rvtrace_encoder_priv *priv = dev_get_drvdata(&comp->dev);
+
+ return sysfs_emit(buf, "%u\n", priv->srcbits);
+}
+static DEVICE_ATTR_RO(srcbits);
+
+static struct attribute *rvtrace_encoder_features_attrs[] = {
+ &dev_attr_srcbits.attr,
+ NULL,
+};
+
+static const struct attribute_group rvtrace_encoder_group = {
+ .attrs = rvtrace_encoder_attrs,
+};
+
+static const struct attribute_group rvtrace_encoder_mgmt_group = {
+ .attrs = rvtrace_encoder_mgmt_attrs,
+ .name = "mgmt",
+};
+
+static const struct attribute_group rvtrace_encoder_control_group = {
+ .attrs = rvtrace_encoder_control_attrs,
+ .name = "control",
+};
+
+static const struct attribute_group rvtrace_encoder_features_group = {
+ .attrs = rvtrace_encoder_features_attrs,
+ .name = "features",
+};
+
+const struct attribute_group *rvtrace_encoder_groups[] = {
+ &rvtrace_encoder_group,
+ &rvtrace_encoder_mgmt_group,
+ &rvtrace_encoder_control_group,
+ &rvtrace_encoder_features_group,
+ NULL,
+};
+EXPORT_SYMBOL_GPL(rvtrace_encoder_groups);
diff --git a/drivers/hwtracing/gtrace/rvtrace-encoder.h b/drivers/hwtracing/gtrace/rvtrace-encoder.h
new file mode 100644
index 000000000000..733d44c0e641
--- /dev/null
+++ b/drivers/hwtracing/gtrace/rvtrace-encoder.h
@@ -0,0 +1,53 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#ifndef __RVTRACE_ENCODER_H__
+#define __RVTRACE_ENCODER_H__
+
+#define RVTRACE_COMPONENT_CTRL_ITRACE_SHIFT 2
+#define RVTRACE_COMPONENT_CTRL_INSTMODE_MASK GENMASK(6, 4)
+#define RVTRACE_COMPONENT_CTRL_INSTMODE_OPIT 0x6
+#define RVTRACE_COMPONENT_CTRL_CONTEXT_SHIFT 9
+#define RVTRACE_COMPONENT_CTRL_INHIBITSRC_SHIFT 15
+#define RVTRACE_COMPONENT_CTRL_SYNCMODE_MASK GENMASK(17, 16)
+#define RVTRACE_COMPONENT_CTRL_SYNCMAX_MASK GENMASK(23, 20)
+#define RVTRACE_COMPONENT_CTRL_SYNCMAX_MAX 15
+#define RVTRACE_COMPONENT_CTRL_FORMAT_MASK GENMASK(26, 24)
+#define RVTRACE_COMPONENT_CTRL_FORMAT_ETRACE 0x0
+#define RVTRACE_COMPONENT_CTRL_FORMAT_NTRACE 0x1
+
+#define RVTRACE_ENCODER_INSTFEAT_OFFSET 0x8
+#define RVTRACE_ENCODER_INSTFEAT_SRCID_MASK GENMASK(27, 16)
+#define RVTRACE_ENCODER_INSTFEAT_SRCBITS_MASK GENMASK(31, 28)
+#define RVTRACE_ENCODER_INSTFEAT_SRCBITS_MAX 12
+
+enum rvtrace_encoder_syncmode {
+ RVTRACE_ENCODER_SYNCMODE_OFF,
+ RVTRACE_ENCODER_SYNCMODE_MESSAGES,
+ RVTRACE_ENCODER_SYNCMODE_CLOCK,
+ RVTRACE_ENCODER_SYNCMODE_INSTRUCTION,
+ RVTRACE_ENCODER_SYNCMODE_MAX,
+};
+
+/**
+ * struct rvtrace_encoder_priv - Private driver data for the rvtrace encoder.
+ * @syncmode_hw: Hardware capability for synchronization modes.
+ * @syncmode: Current operational synchronization mode configured in hardware.
+ * @syncmax: Maximum interval value allowed for synchronization.
+ * @srcbits: Width of source bits allocated for trace generation.
+ * @format: Trace format type used by this encoder compinent.
+ * @has_context: Hardware flag for scontext/mcontext and privilege tracking.
+ * @context: Current configuration for scontext/mcontext and privilege tracking.
+ * @boot_enable: Flag to automatically enable tracing on boot.
+ */
+struct rvtrace_encoder_priv {
+ bool syncmode_hw[RVTRACE_ENCODER_SYNCMODE_MAX];
+ u32 syncmode;
+ u32 syncmax;
+ u32 srcbits;
+ enum gtrace_format format;
+ bool has_context;
+ bool context;
+ bool boot_enable;
+};
+
+#endif/* __RVTRACE_ENCODER_H__ */
diff --git a/drivers/hwtracing/gtrace/rvtrace-platform.c b/drivers/hwtracing/gtrace/rvtrace-platform.c
index e5c1f47388a2..64496a638359 100644
--- a/drivers/hwtracing/gtrace/rvtrace-platform.c
+++ b/drivers/hwtracing/gtrace/rvtrace-platform.c
@@ -129,6 +129,14 @@ static int rvtrace_platform_probe(struct platform_device *pdev)
id.type = gtype;
id.version = rvtrace_component_mkversion(major, minor);
+ switch (gtype) {
+ case GTRACE_RVTRACE_ENCODER:
+ pdata->groups = rvtrace_encoder_groups;
+ break;
+ default:
+ pdata->groups = NULL;
+ }
+
comp = gtrace_register_component(&id, rvtrace_type_name(gtype), pdata);
if (IS_ERR(comp))
return PTR_ERR(comp);
diff --git a/drivers/hwtracing/gtrace/rvtrace.h b/drivers/hwtracing/gtrace/rvtrace.h
index e6be1ce303cc..fee8e2e4f42a 100644
--- a/drivers/hwtracing/gtrace/rvtrace.h
+++ b/drivers/hwtracing/gtrace/rvtrace.h
@@ -118,4 +118,7 @@ static inline int rvtrace_comp_poll_empty(struct gtrace_component *comp)
extern const struct gtrace_hw_ops rvtrace_hw_ops;
+/* Sysfs attribute groups for RISC-V trace components */
+extern const struct attribute_group *rvtrace_encoder_groups[];
+
#endif /* __LINUX_RVTRACE_H__ */
--
2.34.1
^ permalink raw reply [flat|nested] 25+ messages in thread* [PATCH 05/24] gtrace: Introduce some PMU format attributes
2026-10-23 3:14 [RFC PATCH 00/24] Add Linux RISC-V trace support via the gtrace framework Zane Leung
` (3 preceding siblings ...)
2026-10-23 3:14 ` [PATCH 04/24] gtrace: Add RISC-V trace encoder sysfs support Zane Leung
@ 2026-10-23 3:14 ` Zane Leung
2026-10-23 3:14 ` [PATCH 06/24] gtrace: Replace owner PID with perf_event pointer in gtrace_path Zane Leung
` (18 subsequent siblings)
23 siblings, 0 replies; 25+ messages in thread
From: Zane Leung @ 2026-10-23 3:14 UTC (permalink / raw)
To: gregkh, palmer, pjw, alexander.shishkin, suzuki.poulose,
mchitale, anup, irogers
Cc: mike.leach, james.clark, leo.yan, peterz, mingo, namhyung,
mark.rutland, jolsa, adrian.hunter, alex, atish.patra,
andrew.jones, sunilvl, liangzhen, zhuangqiubin, coresight,
linux-riscv, linux-kernel
Add new PMU format attributes to support timestamp, context, and
synchronization controls. This allows userspace tools like perf to
pass detailed hardware configurations down to the gtrace framework.
Signed-off-by: Zane Leung <liangzhen@linux.spacemit.com>
---
drivers/hwtracing/gtrace/gtrace-perf.c | 12 ++++++++++++
include/linux/gtrace.h | 7 +++++++
2 files changed, 19 insertions(+)
diff --git a/drivers/hwtracing/gtrace/gtrace-perf.c b/drivers/hwtracing/gtrace/gtrace-perf.c
index 56713c8ee850..703c12b95665 100644
--- a/drivers/hwtracing/gtrace/gtrace-perf.c
+++ b/drivers/hwtracing/gtrace/gtrace-perf.c
@@ -323,9 +323,21 @@ static void gtrace_event_del(struct perf_event *event, int mode)
}
PMU_FORMAT_ATTR(event, "config:0-0");
+PMU_FORMAT_ATTR(context, "config:1-1");
+PMU_FORMAT_ATTR(syncmode, "config:2-3");
+PMU_FORMAT_ATTR(syncmax, "config:4-7");
+PMU_FORMAT_ATTR(timestamp, "config:8-8");
+PMU_FORMAT_ATTR(tsmode, "config:9-11");
+PMU_FORMAT_ATTR(tsprescale, "config:12-13");
static struct attribute *gtrace_pmu_formats_attr[] = {
&format_attr_event.attr,
+ &format_attr_context.attr,
+ &format_attr_syncmode.attr,
+ &format_attr_syncmax.attr,
+ &format_attr_timestamp.attr,
+ &format_attr_tsmode.attr,
+ &format_attr_tsprescale.attr,
NULL,
};
diff --git a/include/linux/gtrace.h b/include/linux/gtrace.h
index 0a017f1f888d..76b1aa9cfe79 100644
--- a/include/linux/gtrace.h
+++ b/include/linux/gtrace.h
@@ -14,6 +14,13 @@
#include <linux/property.h>
#include <linux/types.h>
+#define GTRACE_PMU_ATTR_CFG_CONTEXT_SHIFT 1
+#define GTRACE_PMU_ATTR_CFG_SYNCMODE_MASK GENMASK(3, 2)
+#define GTRACE_PMU_ATTR_CFG_SYNCMAX_MASK GENMASK(7, 4)
+#define GTRACE_PMU_ATTR_CFG_TIMESTAMP_SHIFT 8
+#define GTRACE_PMU_ATTR_CFG_TSMODE_MASK GENMASK(11, 9)
+#define GTRACE_PMU_ATTR_CFG_TSPRESCALE_MASK GENMASK(13, 12)
+
extern struct mutex gtrace_mutex;
/*
--
2.34.1
^ permalink raw reply [flat|nested] 25+ messages in thread* [PATCH 06/24] gtrace: Replace owner PID with perf_event pointer in gtrace_path
2026-10-23 3:14 [RFC PATCH 00/24] Add Linux RISC-V trace support via the gtrace framework Zane Leung
` (4 preceding siblings ...)
2026-10-23 3:14 ` [PATCH 05/24] gtrace: Introduce some PMU format attributes Zane Leung
@ 2026-10-23 3:14 ` Zane Leung
2026-10-23 3:14 ` [PATCH 07/24] gtrace: Parse RISC-V Trace encoder config from perf event Zane Leung
` (17 subsequent siblings)
23 siblings, 0 replies; 25+ messages in thread
From: Zane Leung @ 2026-10-23 3:14 UTC (permalink / raw)
To: gregkh, palmer, pjw, alexander.shishkin, suzuki.poulose,
mchitale, anup, irogers
Cc: mike.leach, james.clark, leo.yan, peterz, mingo, namhyung,
mark.rutland, jolsa, adrian.hunter, alex, atish.patra,
andrew.jones, sunilvl, liangzhen, zhuangqiubin, coresight,
linux-riscv, linux-kernel
Replace 'pid_t owner' with 'struct perf_event *event' in struct gtrace_path.
This allows component drivers to directly access the perf_event for PMU
configuration.
Signed-off-by: Zane Leung <liangzhen@linux.spacemit.com>
---
drivers/hwtracing/gtrace/gtrace-core.c | 12 ++++++++++--
drivers/hwtracing/gtrace/gtrace-perf.c | 2 +-
include/linux/gtrace.h | 2 +-
3 files changed, 12 insertions(+), 4 deletions(-)
diff --git a/drivers/hwtracing/gtrace/gtrace-core.c b/drivers/hwtracing/gtrace/gtrace-core.c
index cae0a7fa820f..3f55d8b7302d 100644
--- a/drivers/hwtracing/gtrace/gtrace-core.c
+++ b/drivers/hwtracing/gtrace/gtrace-core.c
@@ -13,6 +13,7 @@
#include <linux/spinlock.h>
#include <linux/xarray.h>
#include <linux/gtrace.h>
+#include <linux/perf_event.h>
#define GTRACE_POLL_TIMEOUT_US_DEFAULT 10
@@ -665,10 +666,16 @@ static int __gtrace_comp_start(struct gtrace_component *comp,
raw_spin_lock_irqsave(&cpriv->lock, flags);
if (cpriv->start_count) {
- if (cpriv->owner != path->owner || cpriv->mode != path->mode) {
+ if (cpriv->mode != path->mode) {
ret = -EBUSY;
goto out;
}
+ if (cpriv->mode == GTRACE_COMPONENT_MODE_PERF) {
+ if (cpriv->owner != task_pid_nr(path->event->owner)) {
+ ret = -EBUSY;
+ goto out;
+ }
+ }
} else {
if (gtdrv->start) {
comp->path = path;
@@ -678,8 +685,9 @@ static int __gtrace_comp_start(struct gtrace_component *comp,
goto out;
}
}
- cpriv->owner = path->owner;
cpriv->mode = path->mode;
+ if (cpriv->mode == GTRACE_COMPONENT_MODE_PERF)
+ cpriv->owner = task_pid_nr(path->event->owner);
}
cpriv->start_count++;
diff --git a/drivers/hwtracing/gtrace/gtrace-perf.c b/drivers/hwtracing/gtrace/gtrace-perf.c
index 703c12b95665..2fcfbae9acf6 100644
--- a/drivers/hwtracing/gtrace/gtrace-perf.c
+++ b/drivers/hwtracing/gtrace/gtrace-perf.c
@@ -137,7 +137,7 @@ static void *gtrace_setup_aux(struct perf_event *event, void **pages,
continue;
}
- path->owner = task_pid_nr(event->owner);
+ path->event = event;
*gtrace_event_cpu_path_ptr(event_data, cpu) = path;
}
diff --git a/include/linux/gtrace.h b/include/linux/gtrace.h
index 76b1aa9cfe79..3d769b405030 100644
--- a/include/linux/gtrace.h
+++ b/include/linux/gtrace.h
@@ -325,7 +325,7 @@ struct gtrace_path {
enum gtrace_component_mode mode;
u32 trace_id;
#define GTRACE_INVALID_TRACE_ID 0
- pid_t owner;
+ struct perf_event *event;
};
struct gtrace_component *gtrace_path_source(struct gtrace_path *path);
--
2.34.1
^ permalink raw reply [flat|nested] 25+ messages in thread* [PATCH 07/24] gtrace: Parse RISC-V Trace encoder config from perf event
2026-10-23 3:14 [RFC PATCH 00/24] Add Linux RISC-V trace support via the gtrace framework Zane Leung
` (5 preceding siblings ...)
2026-10-23 3:14 ` [PATCH 06/24] gtrace: Replace owner PID with perf_event pointer in gtrace_path Zane Leung
@ 2026-10-23 3:14 ` Zane Leung
2026-10-23 3:14 ` [PATCH 08/24] gtrace: add perf symlink support for gtrace components Zane Leung
` (16 subsequent siblings)
23 siblings, 0 replies; 25+ messages in thread
From: Zane Leung @ 2026-10-23 3:14 UTC (permalink / raw)
To: gregkh, palmer, pjw, alexander.shishkin, suzuki.poulose,
mchitale, anup, irogers
Cc: mike.leach, james.clark, leo.yan, peterz, mingo, namhyung,
mark.rutland, jolsa, adrian.hunter, alex, atish.patra,
andrew.jones, sunilvl, liangzhen, zhuangqiubin, coresight,
linux-riscv, linux-kernel
Parse the hardware trace encoder configurations (timestamp, context,
syncmode, and syncmax) from perf event attributes when operating
in GTRACE_COMPONENT_MODE_PERF.
Signed-off-by: Zane Leung <liangzhen@linux.spacemit.com>
---
.../hwtracing/gtrace/rvtrace-encoder-core.c | 20 +++++++++++++++++++
1 file changed, 20 insertions(+)
diff --git a/drivers/hwtracing/gtrace/rvtrace-encoder-core.c b/drivers/hwtracing/gtrace/rvtrace-encoder-core.c
index 01925ebdfbde..817535ff9558 100644
--- a/drivers/hwtracing/gtrace/rvtrace-encoder-core.c
+++ b/drivers/hwtracing/gtrace/rvtrace-encoder-core.c
@@ -140,13 +140,33 @@ static int rvtrace_encoder_probe(struct gtrace_component *comp)
return 0;
}
+static void rvtrace_encoder_parse_event_config(struct rvtrace_encoder_priv *priv,
+ struct perf_event *event)
+{
+ u64 config = event->attr.config;
+ enum rvtrace_encoder_syncmode syncmode;
+
+ if (priv->has_context)
+ priv->context = config & BIT(GTRACE_PMU_ATTR_CFG_CONTEXT_SHIFT);
+
+ syncmode = FIELD_GET(GTRACE_PMU_ATTR_CFG_SYNCMODE_MASK, config);
+ if (priv->syncmode_hw[syncmode]) {
+ priv->syncmode = syncmode;
+ priv->syncmax = FIELD_GET(GTRACE_PMU_ATTR_CFG_SYNCMAX_MASK, config);
+ }
+}
+
static int rvtrace_encoder_start(struct gtrace_component *comp)
{
struct rvtrace_encoder_priv *priv = dev_get_drvdata(&comp->dev);
struct gtrace_platform_data *pdata = comp->pdata;
+ struct gtrace_path *path = comp->path;
int ret;
u32 val;
+ if (path->mode == GTRACE_COMPONENT_MODE_PERF)
+ rvtrace_encoder_parse_event_config(priv, path->event);
+
if (priv->srcbits)
rvtrace_encoder_set_srcid(pdata, priv->srcbits);
--
2.34.1
^ permalink raw reply [flat|nested] 25+ messages in thread* [PATCH 08/24] gtrace: add perf symlink support for gtrace components
2026-10-23 3:14 [RFC PATCH 00/24] Add Linux RISC-V trace support via the gtrace framework Zane Leung
` (6 preceding siblings ...)
2026-10-23 3:14 ` [PATCH 07/24] gtrace: Parse RISC-V Trace encoder config from perf event Zane Leung
@ 2026-10-23 3:14 ` Zane Leung
2026-10-23 3:14 ` [PATCH 09/24] gtrace: Add port-level start/stop driver callbacks Zane Leung
` (15 subsequent siblings)
23 siblings, 0 replies; 25+ messages in thread
From: Zane Leung @ 2026-10-23 3:14 UTC (permalink / raw)
To: gregkh, palmer, pjw, alexander.shishkin, suzuki.poulose,
mchitale, anup, irogers
Cc: mike.leach, james.clark, leo.yan, peterz, mingo, namhyung,
mark.rutland, jolsa, adrian.hunter, alex, atish.patra,
andrew.jones, sunilvl, liangzhen, zhuangqiubin, coresight,
linux-riscv, linux-kernel
Implement gtrace_perf_symlink() to let RISC-V Trace encoder
link themselves to the gtrace PMU directory as "cpuX" based
on their bound CPU.
---
drivers/hwtracing/gtrace/gtrace-perf.c | 29 +++++++++++++++++--
.../hwtracing/gtrace/rvtrace-encoder-core.c | 10 +++++++
include/linux/gtrace.h | 2 ++
3 files changed, 39 insertions(+), 2 deletions(-)
diff --git a/drivers/hwtracing/gtrace/gtrace-perf.c b/drivers/hwtracing/gtrace/gtrace-perf.c
index 2fcfbae9acf6..604b673a29d5 100644
--- a/drivers/hwtracing/gtrace/gtrace-perf.c
+++ b/drivers/hwtracing/gtrace/gtrace-perf.c
@@ -23,6 +23,7 @@
#define GTRACE_PMU_NAME "gtrace"
static struct pmu gtrace_pmu;
static struct workqueue_struct *gtrace_wq;
+static bool gtrace_perf_up;
/**
* struct gtrace_event_data - generic hardware trace perf event data
@@ -351,6 +352,27 @@ static const struct attribute_group *gtrace_pmu_attr_groups[] = {
NULL,
};
+int gtrace_perf_symlink(struct gtrace_component *comp, bool link)
+{
+ char entry[sizeof("cpu9999999")];
+ int ret = 0, cpu = comp->pdata->bound_cpu;
+ struct device *pmu_dev = gtrace_pmu.dev;
+ struct device *comp_dev = &comp->dev;
+
+ sprintf(entry, "cpu%d", cpu);
+
+ if (!gtrace_perf_up)
+ return -EPROBE_DEFER;
+
+ if (link)
+ ret = sysfs_create_link(&pmu_dev->kobj, &comp_dev->kobj, entry);
+ else
+ sysfs_remove_link(&pmu_dev->kobj, entry);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(gtrace_perf_symlink);
+
int __init gtrace_perf_init(void)
{
int ret;
@@ -373,10 +395,13 @@ int __init gtrace_perf_init(void)
return -ENOMEM;
ret = perf_pmu_register(>race_pmu, GTRACE_PMU_NAME, -1);
- if (ret)
+ if (ret) {
destroy_workqueue(gtrace_wq);
+ return ret;
+ }
- return ret;
+ gtrace_perf_up = true;
+ return 0;
}
void __exit gtrace_perf_exit(void)
diff --git a/drivers/hwtracing/gtrace/rvtrace-encoder-core.c b/drivers/hwtracing/gtrace/rvtrace-encoder-core.c
index 817535ff9558..17a3dde1314c 100644
--- a/drivers/hwtracing/gtrace/rvtrace-encoder-core.c
+++ b/drivers/hwtracing/gtrace/rvtrace-encoder-core.c
@@ -129,6 +129,10 @@ static int rvtrace_encoder_probe(struct gtrace_component *comp)
}
dev_set_drvdata(&comp->dev, priv);
+ ret = gtrace_perf_symlink(comp, true);
+ if (ret)
+ return ret;
+
if (boot_enable) {
ret = gtrace_enable_sysfs(comp);
if (ret) {
@@ -140,6 +144,11 @@ static int rvtrace_encoder_probe(struct gtrace_component *comp)
return 0;
}
+static void rvtrace_encoder_remove(struct gtrace_component *comp)
+{
+ gtrace_perf_symlink(comp, false);
+}
+
static void rvtrace_encoder_parse_event_config(struct rvtrace_encoder_priv *priv,
struct perf_event *event)
{
@@ -240,6 +249,7 @@ static const struct gtrace_component_id rvtrace_encoder_ids[] = {
static struct gtrace_driver rvtrace_encoder_driver = {
.id_table = rvtrace_encoder_ids,
.probe = rvtrace_encoder_probe,
+ .remove = rvtrace_encoder_remove,
.start = rvtrace_encoder_start,
.stop = rvtrace_encoder_stop,
.driver = {
diff --git a/include/linux/gtrace.h b/include/linux/gtrace.h
index 3d769b405030..4bbe5ac835ec 100644
--- a/include/linux/gtrace.h
+++ b/include/linux/gtrace.h
@@ -437,4 +437,6 @@ int gtrace_disable_sysfs(struct gtrace_component *comp);
int gtrace_sysfs_add(struct gtrace_component *comp);
void gtrace_sysfs_remove(struct gtrace_component *comp);
+int gtrace_perf_symlink(struct gtrace_component *comp, bool link);
+
#endif /* __LINUX_GTRACE_H__ */
--
2.34.1
^ permalink raw reply [flat|nested] 25+ messages in thread* [PATCH 09/24] gtrace: Add port-level start/stop driver callbacks
2026-10-23 3:14 [RFC PATCH 00/24] Add Linux RISC-V trace support via the gtrace framework Zane Leung
` (7 preceding siblings ...)
2026-10-23 3:14 ` [PATCH 08/24] gtrace: add perf symlink support for gtrace components Zane Leung
@ 2026-10-23 3:14 ` Zane Leung
2026-10-23 3:14 ` [PATCH 10/24] gtrace: Add RISC-V trace funnel driver Zane Leung
` (14 subsequent siblings)
23 siblings, 0 replies; 25+ messages in thread
From: Zane Leung @ 2026-10-23 3:14 UTC (permalink / raw)
To: gregkh, palmer, pjw, alexander.shishkin, suzuki.poulose,
mchitale, anup, irogers
Cc: mike.leach, james.clark, leo.yan, peterz, mingo, namhyung,
mark.rutland, jolsa, adrian.hunter, alex, atish.patra,
andrew.jones, sunilvl, liangzhen, zhuangqiubin, coresight,
linux-riscv, linux-kernel
Add start_port() and stop_port() callbacks to gtrace_driver so
multi-port link components like funnel and replicator can enable a
specific input/output port the first time a path uses it, tracked by
the per-connection src_refcnt/dest_refcnt fields.
Signed-off-by: Zane Leung <liangzhen@linux.spacemit.com>
---
drivers/hwtracing/gtrace/gtrace-core.c | 66 ++++++++++++++++++++------
include/linux/gtrace.h | 12 +++++
2 files changed, 64 insertions(+), 14 deletions(-)
diff --git a/drivers/hwtracing/gtrace/gtrace-core.c b/drivers/hwtracing/gtrace/gtrace-core.c
index 3f55d8b7302d..231d06218d8e 100644
--- a/drivers/hwtracing/gtrace/gtrace-core.c
+++ b/drivers/hwtracing/gtrace/gtrace-core.c
@@ -654,7 +654,9 @@ static void gtrace_release_path_nodes(struct gtrace_path *path)
}
static int __gtrace_comp_start(struct gtrace_component *comp,
- struct gtrace_path *path)
+ struct gtrace_path *path,
+ struct gtrace_connection *inconn,
+ struct gtrace_connection *outconn)
{
struct gtrace_comp_priv *cpriv = to_gtrace_comp_priv(comp);
const struct gtrace_driver *gtdrv = to_gtrace_driver(comp->dev.driver);
@@ -690,6 +692,12 @@ static int __gtrace_comp_start(struct gtrace_component *comp,
cpriv->owner = task_pid_nr(path->event->owner);
}
+ if (gtdrv->start_port) {
+ ret = gtdrv->start_port(comp, inconn, outconn);
+ if (ret)
+ goto out;
+ }
+
cpriv->start_count++;
out:
raw_spin_unlock_irqrestore(&cpriv->lock, flags);
@@ -697,7 +705,9 @@ static int __gtrace_comp_start(struct gtrace_component *comp,
}
static int __gtrace_comp_stop(struct gtrace_component *comp,
- struct gtrace_path *path)
+ struct gtrace_path *path,
+ struct gtrace_connection *inconn,
+ struct gtrace_connection *outconn)
{
struct gtrace_comp_priv *cpriv = to_gtrace_comp_priv(comp);
const struct gtrace_driver *gtdrv = to_gtrace_driver(comp->dev.driver);
@@ -715,13 +725,16 @@ static int __gtrace_comp_stop(struct gtrace_component *comp,
goto out;
}
+ if (!gtdrv) {
+ ret = -ENODEV;
+ goto out;
+ }
+
+ if (gtdrv->stop_port)
+ gtdrv->stop_port(comp, inconn, outconn);
+
cpriv->start_count--;
if (!cpriv->start_count) {
- if (!gtdrv) {
- ret = -ENODEV;
- goto out;
- }
-
if (gtdrv->stop)
ret = gtdrv->stop(comp);
comp->path = NULL;
@@ -734,19 +747,36 @@ static int __gtrace_comp_stop(struct gtrace_component *comp,
int gtrace_path_start(struct gtrace_path *path)
{
- struct gtrace_path_node *node;
+ struct gtrace_path_node *node, *prev;
+ struct gtrace_connection *inconn;
int ret = 0;
list_for_each_entry_reverse(node, &path->comp_list, head) {
- ret = __gtrace_comp_start(node->comp, path);
+ if (node->comp == gtrace_path_source(path)) {
+ inconn = NULL;
+ } else {
+ prev = list_prev_entry(node, head);
+ inconn = prev->conn;
+ }
+
+ ret = __gtrace_comp_start(node->comp, path, inconn, node->conn);
if (ret)
break;
}
/* If a component failed to start, stop all the components that were started before it */
- if (ret)
- list_for_each_entry_continue(node, &path->comp_list, head)
- __gtrace_comp_stop(node->comp, path);
+ if (ret) {
+ list_for_each_entry_continue(node, &path->comp_list, head) {
+ if (node->comp == gtrace_path_source(path)) {
+ inconn = NULL;
+ } else {
+ prev = list_prev_entry(node, head);
+ inconn = prev->conn;
+ }
+
+ __gtrace_comp_stop(node->comp, path, inconn, node->conn);
+ }
+ }
return ret;
}
@@ -754,11 +784,19 @@ EXPORT_SYMBOL_GPL(gtrace_path_start);
int gtrace_path_stop(struct gtrace_path *path)
{
- struct gtrace_path_node *node;
+ struct gtrace_path_node *node, *prev;
+ struct gtrace_connection *inconn;
int ret = 0, err;
list_for_each_entry(node, &path->comp_list, head) {
- err = __gtrace_comp_stop(node->comp, path);
+ if (node->comp == gtrace_path_source(path)) {
+ inconn = NULL;
+ } else {
+ prev = list_prev_entry(node, head);
+ inconn = prev->conn;
+ }
+
+ err = __gtrace_comp_stop(node->comp, path, inconn, node->conn);
if (err && !ret)
ret = err;
}
diff --git a/include/linux/gtrace.h b/include/linux/gtrace.h
index 4bbe5ac835ec..ee60585ef65c 100644
--- a/include/linux/gtrace.h
+++ b/include/linux/gtrace.h
@@ -55,9 +55,11 @@ enum gtrace_format {
* @src_port: A connection's source port number.
* @src_fwnode: Source component's fwnode handle.
* @src_comp: Source component's pointer.
+ * @src_refcnt: Number of paths currently using this connection's source port.
* @dest_port: A connection's destination port number.
* @dest_fwnode: Destination component's fwnode handle.
* @dest_comp: Destination component's pointer.
+ * @dest_refcnt: Number of paths currently routing data into the destination port.
*/
struct gtrace_connection {
int src_port;
@@ -66,6 +68,8 @@ struct gtrace_connection {
struct fwnode_handle *dest_fwnode;
struct gtrace_component *src_comp;
struct gtrace_component *dest_comp;
+ u32 src_refcnt;
+ u32 dest_refcnt;
};
struct gtrace_platform_data;
@@ -363,6 +367,8 @@ int gtrace_path_copyto_auxbuf(struct gtrace_path *path,
* via @format (see PERF_AUX_FLAG_PMU_FORMAT_TYPE_MASK).
* @start: Callback to start tracing.
* @stop: Callback to stop tracing.
+ * @start_port: Callback to start a specific component port.
+ * @stop_port: Callback to stop a specific component port.
* @probe: Driver probe() function.
* @remove: Driver remove() function.
* @get_trace_id: Get/allocate a trace ID.
@@ -376,6 +382,12 @@ struct gtrace_driver {
u64 *format);
int (*start)(struct gtrace_component *comp);
int (*stop)(struct gtrace_component *comp);
+ int (*start_port)(struct gtrace_component *comp,
+ struct gtrace_connection *inconn,
+ struct gtrace_connection *outconn);
+ void (*stop_port)(struct gtrace_component *comp,
+ struct gtrace_connection *inconn,
+ struct gtrace_connection *outconn);
int (*probe)(struct gtrace_component *comp);
void (*remove)(struct gtrace_component *comp);
int (*get_trace_id)(struct gtrace_component *comp,
--
2.34.1
^ permalink raw reply [flat|nested] 25+ messages in thread* [PATCH 10/24] gtrace: Add RISC-V trace funnel driver
2026-10-23 3:14 [RFC PATCH 00/24] Add Linux RISC-V trace support via the gtrace framework Zane Leung
` (8 preceding siblings ...)
2026-10-23 3:14 ` [PATCH 09/24] gtrace: Add port-level start/stop driver callbacks Zane Leung
@ 2026-10-23 3:14 ` Zane Leung
2026-10-23 3:14 ` [PATCH 11/24] gtrace: Add RISC-V trace timestamp support Zane Leung
` (13 subsequent siblings)
23 siblings, 0 replies; 25+ messages in thread
From: Zane Leung @ 2026-10-23 3:14 UTC (permalink / raw)
To: gregkh, palmer, pjw, alexander.shishkin, suzuki.poulose,
mchitale, anup, irogers
Cc: mike.leach, james.clark, leo.yan, peterz, mingo, namhyung,
mark.rutland, jolsa, adrian.hunter, alex, atish.patra,
andrew.jones, sunilvl, liangzhen, zhuangqiubin, coresight,
linux-riscv, linux-kernel
Add initial implementation of RISC-V Trace funnel driver. The funnel
is defined in the RISC-V Trace Control Interface specification.
Signed-off-by: Zane Leung <liangzhen@linux.spacemit.com>
---
drivers/hwtracing/gtrace/Kconfig | 14 ++
drivers/hwtracing/gtrace/Makefile | 1 +
drivers/hwtracing/gtrace/rvtrace-funnel.c | 165 ++++++++++++++++++++
drivers/hwtracing/gtrace/rvtrace-funnel.h | 21 +++
drivers/hwtracing/gtrace/rvtrace-platform.c | 3 +
drivers/hwtracing/gtrace/rvtrace.h | 1 +
6 files changed, 205 insertions(+)
create mode 100644 drivers/hwtracing/gtrace/rvtrace-funnel.c
create mode 100644 drivers/hwtracing/gtrace/rvtrace-funnel.h
diff --git a/drivers/hwtracing/gtrace/Kconfig b/drivers/hwtracing/gtrace/Kconfig
index e5b62d65e2fd..f6026965c54d 100644
--- a/drivers/hwtracing/gtrace/Kconfig
+++ b/drivers/hwtracing/gtrace/Kconfig
@@ -57,3 +57,17 @@ config RVTRACE_RAMSINK
To compile this driver as a module, choose M here: the module
will be called rvtrace-ramsink.
+
+config RVTRACE_FUNNEL
+ tristate "RISC-V Trace Funnel driver"
+ depends on RVTRACE
+ default y
+ help
+ This driver provides support for the RISC-V trace funnel component
+ which is defined by the RISC-V Trace Control Interface specification.
+ The funnel merges trace data from multiple input ports into a single
+ output stream. It is a link component in the trace path between
+ sources and sinks.
+
+ To compile this driver as a module, choose M here: the module
+ will be called rvtrace-funnel.
diff --git a/drivers/hwtracing/gtrace/Makefile b/drivers/hwtracing/gtrace/Makefile
index 87cb5add78d4..9c99d917b9f4 100644
--- a/drivers/hwtracing/gtrace/Makefile
+++ b/drivers/hwtracing/gtrace/Makefile
@@ -8,3 +8,4 @@ obj-$(CONFIG_RVTRACE) += rvtrace-platform.o
obj-$(CONFIG_RVTRACE_ENCODER) += rvtrace-encoder.o
rvtrace-encoder-y := rvtrace-encoder-core.o rvtrace-encoder-sysfs.o
obj-$(CONFIG_RVTRACE_RAMSINK) += rvtrace-ramsink.o
+obj-$(CONFIG_RVTRACE_FUNNEL) += rvtrace-funnel.o
diff --git a/drivers/hwtracing/gtrace/rvtrace-funnel.c b/drivers/hwtracing/gtrace/rvtrace-funnel.c
new file mode 100644
index 000000000000..941cd3360395
--- /dev/null
+++ b/drivers/hwtracing/gtrace/rvtrace-funnel.c
@@ -0,0 +1,165 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2026 Spacemit Technologies, Inc.
+ */
+
+#include <linux/device.h>
+#include <linux/gtrace.h>
+#include <linux/types.h>
+#include "rvtrace.h"
+#include "rvtrace-funnel.h"
+
+static int rvtrace_funnel_start(struct gtrace_component *comp)
+{
+ int ret;
+
+ ret = gtrace_enable_component(comp);
+ if (ret)
+ dev_err(&comp->dev, "failed to start rvtrace funnel.\n");
+
+ return ret;
+}
+
+static int rvtrace_funnel_stop(struct gtrace_component *comp)
+{
+ struct rvtrace_funnel_priv *priv = dev_get_drvdata(&comp->dev);
+ int ret;
+
+ ret = gtrace_disable_component(comp);
+ if (ret) {
+ dev_err(&comp->dev, "failed to stop rvtrace funnel.\n");
+ return ret;
+ }
+
+ if (priv->disinput_mask)
+ gtrace_write32(comp->pdata, RVTRACE_FUNNEL_DISINPUT_MASK,
+ RVTRACE_FUNNEL_DISINPUT_OFFSET);
+
+ return 0;
+}
+
+static int _rvtrace_funnel_start_port(struct gtrace_component *comp, int port)
+{
+ struct gtrace_platform_data *pdata = comp->pdata;
+ struct rvtrace_funnel_priv *priv = dev_get_drvdata(&comp->dev);
+ u32 disinput;
+ int ret;
+
+ if (!(priv->disinput_mask & BIT(port)))
+ return 0;
+
+ disinput = gtrace_read32(pdata, RVTRACE_FUNNEL_DISINPUT_OFFSET);
+ disinput &= ~BIT(port);
+ gtrace_write32(pdata, disinput, RVTRACE_FUNNEL_DISINPUT_OFFSET);
+ ret = gtrace_poll_bit(pdata, RVTRACE_FUNNEL_DISINPUT_OFFSET,
+ port, 0, comp->pdata->control_poll_timeout_usecs);
+ if (ret)
+ dev_err(&comp->dev, "failed to enable rvtrace funnel inport %d.\n", port);
+
+ return ret;
+}
+
+static int rvtrace_funnel_start_port(struct gtrace_component *comp,
+ struct gtrace_connection *in,
+ struct gtrace_connection *out)
+{
+ int ret = 0;
+
+ if (in->dest_refcnt == 0)
+ ret = _rvtrace_funnel_start_port(comp, in->dest_port);
+ if (!ret)
+ in->dest_refcnt++;
+
+ return ret;
+}
+
+static void rvtrace_funnel_stop_port(struct gtrace_component *comp,
+ struct gtrace_connection *in,
+ struct gtrace_connection *out)
+{
+ /*
+ * Setting trFunnelEnable to 0 flushes any queued trace data to output.
+ * Due to this flush behavior of the RISC-V trace funnel during component
+ * disablement, we do not disable individual ports here. Instead, we
+ * defer it and disable all ports at once when the entire component is
+ * stopped. For now, we only need to decrement the reference count.
+ */
+ --in->dest_refcnt;
+}
+
+static ssize_t disinput_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ u32 val;
+ struct gtrace_component *comp = to_gtrace_component(dev);
+
+ val = gtrace_read32(comp->pdata, RVTRACE_FUNNEL_DISINPUT_OFFSET);
+
+ return scnprintf(buf, PAGE_SIZE, "0x%x\n", val);
+}
+static DEVICE_ATTR_RO(disinput);
+
+static struct attribute *rvtrace_funnel_attrs[] = {
+ &dev_attr_disinput.attr,
+ NULL,
+};
+
+static struct attribute_group rvtrace_funnel_group = {
+ .attrs = rvtrace_funnel_attrs,
+};
+
+const struct attribute_group *rvtrace_funnel_groups[] = {
+ &rvtrace_funnel_group,
+ NULL,
+};
+EXPORT_SYMBOL_GPL(rvtrace_funnel_groups);
+
+static int rvtrace_funnel_probe(struct gtrace_component *comp)
+{
+ struct gtrace_platform_data *pdata = comp->pdata;
+ struct rvtrace_funnel_priv *priv;
+
+ priv = devm_kzalloc(&comp->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ gtrace_write32(pdata, RVTRACE_FUNNEL_DISINPUT_MASK, RVTRACE_FUNNEL_DISINPUT_OFFSET);
+ priv->disinput_mask = gtrace_read32(pdata, RVTRACE_FUNNEL_DISINPUT_OFFSET);
+ dev_set_drvdata(&comp->dev, priv);
+ return 0;
+}
+
+static const struct gtrace_component_id rvtrace_funnel_ids[] = {
+ { .type = GTRACE_RVTRACE_FUNNEL,
+ .version = rvtrace_component_mkversion(1, 0), },
+ {},
+};
+
+static struct gtrace_driver rvtrace_funnel_driver = {
+ .id_table = rvtrace_funnel_ids,
+ .start = rvtrace_funnel_start,
+ .stop = rvtrace_funnel_stop,
+ .start_port = rvtrace_funnel_start_port,
+ .stop_port = rvtrace_funnel_stop_port,
+ .probe = rvtrace_funnel_probe,
+ .driver = {
+ .name = "rvtrace-funnel",
+ },
+};
+
+static int __init rvtrace_funnel_init(void)
+{
+ return gtrace_register_driver(&rvtrace_funnel_driver);
+}
+
+static void __exit rvtrace_funnel_exit(void)
+{
+ gtrace_unregister_driver(&rvtrace_funnel_driver);
+}
+
+module_init(rvtrace_funnel_init);
+module_exit(rvtrace_funnel_exit);
+
+MODULE_AUTHOR("Zane Leung");
+MODULE_DESCRIPTION("RISC-V Trace Funnel Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hwtracing/gtrace/rvtrace-funnel.h b/drivers/hwtracing/gtrace/rvtrace-funnel.h
new file mode 100644
index 000000000000..41547c8971ba
--- /dev/null
+++ b/drivers/hwtracing/gtrace/rvtrace-funnel.h
@@ -0,0 +1,21 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2026 Spacemit Technologies, Inc.
+ */
+
+#ifndef __RVTRACE_HENNEL_H__
+#define __RVTRACE_HENNEL_H__
+
+#define RVTRACE_FUNNEL_DISINPUT_OFFSET 0x008
+#define RVTRACE_FUNNEL_DISINPUT_MASK 0xffff
+
+/**
+ * struct rvtrace_encoder_priv - Private driver data for the rvtrace funnel
+ * @disinput_mask: Bitmask of inputs supporting disable.
+ */
+
+struct rvtrace_funnel_priv {
+ u32 disinput_mask;
+};
+
+#endif /* __RVTRACE_ENCODER_H__ */
diff --git a/drivers/hwtracing/gtrace/rvtrace-platform.c b/drivers/hwtracing/gtrace/rvtrace-platform.c
index 64496a638359..ad8daa06e13e 100644
--- a/drivers/hwtracing/gtrace/rvtrace-platform.c
+++ b/drivers/hwtracing/gtrace/rvtrace-platform.c
@@ -133,6 +133,9 @@ static int rvtrace_platform_probe(struct platform_device *pdev)
case GTRACE_RVTRACE_ENCODER:
pdata->groups = rvtrace_encoder_groups;
break;
+ case GTRACE_RVTRACE_FUNNEL:
+ pdata->groups = rvtrace_funnel_groups;
+ break;
default:
pdata->groups = NULL;
}
diff --git a/drivers/hwtracing/gtrace/rvtrace.h b/drivers/hwtracing/gtrace/rvtrace.h
index fee8e2e4f42a..a345ba110267 100644
--- a/drivers/hwtracing/gtrace/rvtrace.h
+++ b/drivers/hwtracing/gtrace/rvtrace.h
@@ -120,5 +120,6 @@ extern const struct gtrace_hw_ops rvtrace_hw_ops;
/* Sysfs attribute groups for RISC-V trace components */
extern const struct attribute_group *rvtrace_encoder_groups[];
+extern const struct attribute_group *rvtrace_funnel_groups[];
#endif /* __LINUX_RVTRACE_H__ */
--
2.34.1
^ permalink raw reply [flat|nested] 25+ messages in thread* [PATCH 11/24] gtrace: Add RISC-V trace timestamp support
2026-10-23 3:14 [RFC PATCH 00/24] Add Linux RISC-V trace support via the gtrace framework Zane Leung
` (9 preceding siblings ...)
2026-10-23 3:14 ` [PATCH 10/24] gtrace: Add RISC-V trace funnel driver Zane Leung
@ 2026-10-23 3:14 ` Zane Leung
2026-10-23 3:14 ` [PATCH 12/24] gtrace: Add RISC-V trace ATB bridge driver Zane Leung
` (12 subsequent siblings)
23 siblings, 0 replies; 25+ messages in thread
From: Zane Leung @ 2026-10-23 3:14 UTC (permalink / raw)
To: gregkh, palmer, pjw, alexander.shishkin, suzuki.poulose,
mchitale, anup, irogers
Cc: mike.leach, james.clark, leo.yan, peterz, mingo, namhyung,
mark.rutland, jolsa, adrian.hunter, alex, atish.patra,
andrew.jones, sunilvl, liangzhen, zhuangqiubin, coresight,
linux-riscv, linux-kernel
Add a shared timestamp component for the RISC-V trace encoder and
funnel.
Signed-off-by: Zane Leung <liangzhen@linux.spacemit.com>
---
drivers/hwtracing/gtrace/Makefile | 3 +-
.../hwtracing/gtrace/rvtrace-encoder-core.c | 16 +-
.../hwtracing/gtrace/rvtrace-encoder-sysfs.c | 33 ++
drivers/hwtracing/gtrace/rvtrace-encoder.h | 8 +
drivers/hwtracing/gtrace/rvtrace-funnel.c | 55 +++
drivers/hwtracing/gtrace/rvtrace-funnel.h | 8 +
drivers/hwtracing/gtrace/rvtrace-timestamp.c | 336 ++++++++++++++++++
drivers/hwtracing/gtrace/rvtrace-timestamp.h | 44 +++
8 files changed, 500 insertions(+), 3 deletions(-)
create mode 100644 drivers/hwtracing/gtrace/rvtrace-timestamp.c
create mode 100644 drivers/hwtracing/gtrace/rvtrace-timestamp.h
diff --git a/drivers/hwtracing/gtrace/Makefile b/drivers/hwtracing/gtrace/Makefile
index 9c99d917b9f4..b60d3cfe5248 100644
--- a/drivers/hwtracing/gtrace/Makefile
+++ b/drivers/hwtracing/gtrace/Makefile
@@ -4,7 +4,8 @@ obj-$(CONFIG_GTRACE) += gtrace.o
gtrace-y := gtrace-core.o gtrace-perf.o gtrace-sysfs.o
gtrace-$(CONFIG_OF) += gtrace-of.o
-obj-$(CONFIG_RVTRACE) += rvtrace-platform.o
+obj-$(CONFIG_RVTRACE) += rvtrace.o
+rvtrace-y := rvtrace-platform.o rvtrace-timestamp.o
obj-$(CONFIG_RVTRACE_ENCODER) += rvtrace-encoder.o
rvtrace-encoder-y := rvtrace-encoder-core.o rvtrace-encoder-sysfs.o
obj-$(CONFIG_RVTRACE_RAMSINK) += rvtrace-ramsink.o
diff --git a/drivers/hwtracing/gtrace/rvtrace-encoder-core.c b/drivers/hwtracing/gtrace/rvtrace-encoder-core.c
index 17a3dde1314c..c0541fee6b93 100644
--- a/drivers/hwtracing/gtrace/rvtrace-encoder-core.c
+++ b/drivers/hwtracing/gtrace/rvtrace-encoder-core.c
@@ -127,6 +127,10 @@ static int rvtrace_encoder_probe(struct gtrace_component *comp)
val |= BIT(RVTRACE_COMPONENT_CTRL_INHIBITSRC_SHIFT);
gtrace_write32(pdata, val, RVTRACE_COMPONENT_CTRL_OFFSET);
}
+
+ priv->has_timestamp = rvtrace_timestamp_reset(pdata) == 0;
+ if (priv->has_timestamp)
+ rvtrace_timestamp_init(pdata, &priv->ts_priv);
dev_set_drvdata(&comp->dev, priv);
ret = gtrace_perf_symlink(comp, true);
@@ -155,6 +159,8 @@ static void rvtrace_encoder_parse_event_config(struct rvtrace_encoder_priv *priv
u64 config = event->attr.config;
enum rvtrace_encoder_syncmode syncmode;
+ if (priv->has_timestamp)
+ priv->ts_ctrl = config & BIT(GTRACE_PMU_ATTR_CFG_TIMESTAMP_SHIFT);
if (priv->has_context)
priv->context = config & BIT(GTRACE_PMU_ATTR_CFG_CONTEXT_SHIFT);
@@ -170,11 +176,17 @@ static int rvtrace_encoder_start(struct gtrace_component *comp)
struct rvtrace_encoder_priv *priv = dev_get_drvdata(&comp->dev);
struct gtrace_platform_data *pdata = comp->pdata;
struct gtrace_path *path = comp->path;
+ struct perf_event *event = NULL;
int ret;
u32 val;
- if (path->mode == GTRACE_COMPONENT_MODE_PERF)
- rvtrace_encoder_parse_event_config(priv, path->event);
+ if (path->mode == GTRACE_COMPONENT_MODE_PERF) {
+ event = path->event;
+ rvtrace_encoder_parse_event_config(priv, event);
+ }
+
+ if (priv->has_timestamp)
+ rvtrace_timestamp_setup(pdata, &priv->ts_priv, event, priv->ts_ctrl);
if (priv->srcbits)
rvtrace_encoder_set_srcid(pdata, priv->srcbits);
diff --git a/drivers/hwtracing/gtrace/rvtrace-encoder-sysfs.c b/drivers/hwtracing/gtrace/rvtrace-encoder-sysfs.c
index e973d0e43036..638759be2987 100644
--- a/drivers/hwtracing/gtrace/rvtrace-encoder-sysfs.c
+++ b/drivers/hwtracing/gtrace/rvtrace-encoder-sysfs.c
@@ -84,8 +84,40 @@ static ssize_t cpu_show(struct device *dev,
}
static DEVICE_ATTR_RO(cpu);
+static ssize_t ts_ctrl_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct gtrace_component *comp = to_gtrace_component(dev);
+ struct rvtrace_encoder_priv *priv = dev_get_drvdata(&comp->dev);
+
+ return sysfs_emit(buf, "%u\n", priv->ts_ctrl);
+}
+
+static ssize_t ts_ctrl_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ int ret;
+ unsigned long val;
+ struct gtrace_component *comp = to_gtrace_component(dev);
+ struct rvtrace_encoder_priv *priv = dev_get_drvdata(&comp->dev);
+
+ if (!priv->has_timestamp)
+ return -EINVAL;
+
+ ret = kstrtoul(buf, 10, &val);
+ if (ret)
+ return ret;
+
+ priv->ts_ctrl = !!val;
+
+ return size;
+}
+static DEVICE_ATTR_RW(ts_ctrl);
+
static struct attribute *rvtrace_encoder_attrs[] = {
&dev_attr_cpu.attr,
+ &dev_attr_ts_ctrl.attr,
NULL,
};
@@ -285,6 +317,7 @@ const struct attribute_group *rvtrace_encoder_groups[] = {
&rvtrace_encoder_mgmt_group,
&rvtrace_encoder_control_group,
&rvtrace_encoder_features_group,
+ &rvtrace_timestamp_group,
NULL,
};
EXPORT_SYMBOL_GPL(rvtrace_encoder_groups);
diff --git a/drivers/hwtracing/gtrace/rvtrace-encoder.h b/drivers/hwtracing/gtrace/rvtrace-encoder.h
index 733d44c0e641..579567dabb3a 100644
--- a/drivers/hwtracing/gtrace/rvtrace-encoder.h
+++ b/drivers/hwtracing/gtrace/rvtrace-encoder.h
@@ -3,6 +3,8 @@
#ifndef __RVTRACE_ENCODER_H__
#define __RVTRACE_ENCODER_H__
+#include "rvtrace-timestamp.h"
+
#define RVTRACE_COMPONENT_CTRL_ITRACE_SHIFT 2
#define RVTRACE_COMPONENT_CTRL_INSTMODE_MASK GENMASK(6, 4)
#define RVTRACE_COMPONENT_CTRL_INSTMODE_OPIT 0x6
@@ -38,6 +40,9 @@ enum rvtrace_encoder_syncmode {
* @has_context: Hardware flag for scontext/mcontext and privilege tracking.
* @context: Current configuration for scontext/mcontext and privilege tracking.
* @boot_enable: Flag to automatically enable tracing on boot.
+ * @has_timestamp: Hardware flag indicating if timestamp generation is supported.
+ * @ts_ctrl: Current configuration state of timestamp control.
+ * @ts_priv: Private configuration data specific to the timestamp unit.
*/
struct rvtrace_encoder_priv {
bool syncmode_hw[RVTRACE_ENCODER_SYNCMODE_MAX];
@@ -48,6 +53,9 @@ struct rvtrace_encoder_priv {
bool has_context;
bool context;
bool boot_enable;
+ bool has_timestamp;
+ bool ts_ctrl;
+ struct rvtrace_timestamp_priv ts_priv;
};
#endif/* __RVTRACE_ENCODER_H__ */
diff --git a/drivers/hwtracing/gtrace/rvtrace-funnel.c b/drivers/hwtracing/gtrace/rvtrace-funnel.c
index 941cd3360395..c4b3396568f7 100644
--- a/drivers/hwtracing/gtrace/rvtrace-funnel.c
+++ b/drivers/hwtracing/gtrace/rvtrace-funnel.c
@@ -9,10 +9,28 @@
#include "rvtrace.h"
#include "rvtrace-funnel.h"
+static void rvtrace_funnel_parse_event_config(struct rvtrace_funnel_priv *priv,
+ struct perf_event *event)
+{
+ u64 config = event->attr.config;
+
+ priv->ts_ctrl = config & BIT(GTRACE_PMU_ATTR_CFG_TIMESTAMP_SHIFT);
+}
+
static int rvtrace_funnel_start(struct gtrace_component *comp)
{
+ struct gtrace_path *path = comp->path;
+ struct rvtrace_funnel_priv *priv = dev_get_drvdata(&comp->dev);
+ struct perf_event *event = NULL;
int ret;
+ if (path->mode == GTRACE_COMPONENT_MODE_PERF) {
+ event = path->event;
+ rvtrace_funnel_parse_event_config(priv, event);
+ }
+ if (priv->has_timestamp)
+ rvtrace_timestamp_setup(comp->pdata, &priv->ts_priv, event, priv->ts_ctrl);
+
ret = gtrace_enable_component(comp);
if (ret)
dev_err(&comp->dev, "failed to start rvtrace funnel.\n");
@@ -99,8 +117,40 @@ static ssize_t disinput_show(struct device *dev,
}
static DEVICE_ATTR_RO(disinput);
+static ssize_t ts_ctrl_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct gtrace_component *comp = to_gtrace_component(dev);
+ struct rvtrace_funnel_priv *priv = dev_get_drvdata(&comp->dev);
+
+ return sysfs_emit(buf, "%u\n", priv->ts_ctrl);
+}
+
+static ssize_t ts_ctrl_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ int ret;
+ unsigned long val;
+ struct gtrace_component *comp = to_gtrace_component(dev);
+ struct rvtrace_funnel_priv *priv = dev_get_drvdata(&comp->dev);
+
+ if (!priv->has_timestamp)
+ return -EINVAL;
+
+ ret = kstrtoul(buf, 10, &val);
+ if (ret)
+ return ret;
+
+ priv->ts_ctrl = !!val;
+
+ return size;
+}
+static DEVICE_ATTR_RW(ts_ctrl);
+
static struct attribute *rvtrace_funnel_attrs[] = {
&dev_attr_disinput.attr,
+ &dev_attr_ts_ctrl.attr,
NULL,
};
@@ -110,6 +160,7 @@ static struct attribute_group rvtrace_funnel_group = {
const struct attribute_group *rvtrace_funnel_groups[] = {
&rvtrace_funnel_group,
+ &rvtrace_timestamp_group,
NULL,
};
EXPORT_SYMBOL_GPL(rvtrace_funnel_groups);
@@ -123,6 +174,10 @@ static int rvtrace_funnel_probe(struct gtrace_component *comp)
if (!priv)
return -ENOMEM;
+ priv->has_timestamp = rvtrace_timestamp_reset(pdata) == 0;
+ if (priv->has_timestamp)
+ rvtrace_timestamp_init(pdata, &priv->ts_priv);
+
gtrace_write32(pdata, RVTRACE_FUNNEL_DISINPUT_MASK, RVTRACE_FUNNEL_DISINPUT_OFFSET);
priv->disinput_mask = gtrace_read32(pdata, RVTRACE_FUNNEL_DISINPUT_OFFSET);
dev_set_drvdata(&comp->dev, priv);
diff --git a/drivers/hwtracing/gtrace/rvtrace-funnel.h b/drivers/hwtracing/gtrace/rvtrace-funnel.h
index 41547c8971ba..dddab5db8bef 100644
--- a/drivers/hwtracing/gtrace/rvtrace-funnel.h
+++ b/drivers/hwtracing/gtrace/rvtrace-funnel.h
@@ -6,16 +6,24 @@
#ifndef __RVTRACE_HENNEL_H__
#define __RVTRACE_HENNEL_H__
+#include "rvtrace-timestamp.h"
+
#define RVTRACE_FUNNEL_DISINPUT_OFFSET 0x008
#define RVTRACE_FUNNEL_DISINPUT_MASK 0xffff
/**
* struct rvtrace_encoder_priv - Private driver data for the rvtrace funnel
* @disinput_mask: Bitmask of inputs supporting disable.
+ * @has_timestamp: Hardware flag indicating if timestamp generation is supported.
+ * @ts_ctrl: Current configuration state of timestamp control.
+ * @ts_priv: Private configuration data specific to the timestamp unit.
*/
struct rvtrace_funnel_priv {
u32 disinput_mask;
+ bool has_timestamp;
+ bool ts_ctrl;
+ struct rvtrace_timestamp_priv ts_priv;
};
#endif /* __RVTRACE_ENCODER_H__ */
diff --git a/drivers/hwtracing/gtrace/rvtrace-timestamp.c b/drivers/hwtracing/gtrace/rvtrace-timestamp.c
new file mode 100644
index 000000000000..0ec7436add16
--- /dev/null
+++ b/drivers/hwtracing/gtrace/rvtrace-timestamp.c
@@ -0,0 +1,336 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright(C) 2026 Spacemit Technologies, Inc.
+ */
+
+#include <linux/device.h>
+#include <linux/gtrace.h>
+#include <linux/bitfield.h>
+#include "rvtrace.h"
+#include "rvtrace-timestamp.h"
+#include "rvtrace-encoder.h"
+#include "rvtrace-funnel.h"
+
+int rvtrace_timestamp_reset(struct gtrace_platform_data *pdata)
+{
+ gtrace_write32(pdata, 0, RVTRACE_TIMESTAMP_CTRL_OFFSET);
+ gtrace_write32(pdata, RVTRACE_COMPONENT_CTRL_ACTIVE_MASK, RVTRACE_TIMESTAMP_CTRL_OFFSET);
+ return gtrace_poll_bit(pdata, RVTRACE_TIMESTAMP_CTRL_OFFSET,
+ RVTRACE_COMPONENT_CTRL_ACTIVE_SHIFT, 1,
+ pdata->control_poll_timeout_usecs);
+}
+EXPORT_SYMBOL_GPL(rvtrace_timestamp_reset);
+
+void rvtrace_timestamp_init(struct gtrace_platform_data *pdata,
+ struct rvtrace_timestamp_priv *priv)
+{
+ u32 ctrl, val;
+ enum rvtrace_timestamp_mode mode;
+
+ ctrl = gtrace_read32(pdata, RVTRACE_TIMESTAMP_CTRL_OFFSET);
+ priv->mode = FIELD_GET(RVTRACE_TIMESTAMP_MODE_MASK, ctrl);
+
+ /* get timestamp mode hw */
+ for (mode = RVTRACE_TIMESTAMP_MODE_NONE; mode < RVTRACE_TIMESTAMP_MODE_MAX; mode++) {
+ val = ctrl & ~RVTRACE_TIMESTAMP_MODE_MASK;
+ val |= FIELD_PREP(RVTRACE_TIMESTAMP_MODE_MASK, mode);
+ gtrace_write32(pdata, val, RVTRACE_TIMESTAMP_CTRL_OFFSET);
+ val = gtrace_read32(pdata, RVTRACE_TIMESTAMP_CTRL_OFFSET);
+ val = FIELD_GET(RVTRACE_TIMESTAMP_MODE_MASK, val);
+ priv->mode_hw[mode] = (mode == val);
+ }
+
+ gtrace_write32(pdata, ctrl, RVTRACE_TIMESTAMP_CTRL_OFFSET);
+}
+EXPORT_SYMBOL_GPL(rvtrace_timestamp_init);
+
+static bool rvtrace_timestamp_is_internal(struct rvtrace_timestamp_priv *priv)
+{
+ return (priv->mode == RVTRACE_TIMESTAMP_MODE_INTERNAL_SYSTEM ||
+ priv->mode == RVTRACE_TIMESTAMP_MODE_INTERNAL_CORE);
+}
+
+static void rvtrace_timestamp_parse_event_config(struct rvtrace_timestamp_priv *priv,
+ struct perf_event *event)
+{
+ u64 config = event->attr.config;
+
+ if (priv->mode_hw[FIELD_GET(GTRACE_PMU_ATTR_CFG_TSMODE_MASK, config)])
+ priv->mode = FIELD_GET(GTRACE_PMU_ATTR_CFG_TSMODE_MASK, config);
+ if (rvtrace_timestamp_is_internal(priv))
+ priv->prescale = FIELD_GET(GTRACE_PMU_ATTR_CFG_TSPRESCALE_MASK, config);
+}
+
+void rvtrace_timestamp_setup(struct gtrace_platform_data *pdata,
+ struct rvtrace_timestamp_priv *priv,
+ struct perf_event *event,
+ bool enable)
+{
+ u32 val;
+
+ if (event)
+ rvtrace_timestamp_parse_event_config(priv, event);
+
+ val = gtrace_read32(pdata, RVTRACE_TIMESTAMP_CTRL_OFFSET);
+ val &= ~RVTRACE_TIMESTAMP_MODE_MASK;
+ val &= ~RVTRACE_TIMESTAMP_PRESCALE_MASK;
+ val |= FIELD_PREP(RVTRACE_TIMESTAMP_MODE_MASK, priv->mode);
+ val |= FIELD_PREP(RVTRACE_TIMESTAMP_PRESCALE_MASK, priv->prescale);
+
+ val &= ~BIT(RVTRACE_TIMESTAMP_COUNT_SHIFT);
+ val &= ~BIT(RVTRACE_TIMESTAMP_ENABLE_SHIFT);
+ if (enable) {
+ val |= BIT(RVTRACE_TIMESTAMP_COUNT_SHIFT);
+ val |= BIT(RVTRACE_TIMESTAMP_ENABLE_SHIFT);
+ }
+
+ gtrace_write32(pdata, val, RVTRACE_TIMESTAMP_CTRL_OFFSET);
+}
+EXPORT_SYMBOL_GPL(rvtrace_timestamp_setup);
+
+static struct rvtrace_timestamp_priv *get_timestamp_priv(struct gtrace_component *comp)
+{
+ enum gtrace_component_type gtype = comp->id.type;
+
+ if (gtype == GTRACE_RVTRACE_ENCODER) {
+ struct rvtrace_encoder_priv *priv = dev_get_drvdata(&comp->dev);
+ return &priv->ts_priv;
+ } else if (gtype == GTRACE_RVTRACE_FUNNEL) {
+ struct rvtrace_funnel_priv *priv = dev_get_drvdata(&comp->dev);
+ return &priv->ts_priv;
+ }
+
+ return NULL;
+}
+
+static struct gtrace_component *rvtrace_find_funnel(struct gtrace_component *comp)
+{
+ struct gtrace_platform_data *pdata = comp->pdata;
+ struct gtrace_connection *conn = NULL;
+ int i;
+
+ if (comp->id.type == GTRACE_RVTRACE_FUNNEL)
+ return comp;
+
+ if (!pdata->nr_outconns)
+ return NULL;
+
+ for (i = 0; i < pdata->nr_outconns; i++) {
+ conn = pdata->outconns[i];
+ if (conn && conn->dest_comp)
+ break;
+ }
+
+ if (!conn)
+ return NULL;
+
+ return rvtrace_find_funnel(conn->dest_comp);
+}
+
+static struct gtrace_component *rvtrace_timestamp_get_source(struct gtrace_component *comp)
+{
+ struct gtrace_component *dest = comp;
+
+ if (dest->id.type == GTRACE_RVTRACE_ENCODER) {
+ struct rvtrace_encoder_priv *priv = dev_get_drvdata(&dest->dev);
+
+ if (priv && priv->has_timestamp)
+ return dest;
+
+ dest = rvtrace_find_funnel(dest);
+ if (!dest)
+ return NULL;
+ }
+
+ if (dest->id.type == GTRACE_RVTRACE_FUNNEL) {
+ struct rvtrace_funnel_priv *priv = dev_get_drvdata(&dest->dev);
+
+ if (priv && priv->has_timestamp)
+ return dest;
+ }
+
+ return NULL;
+}
+
+static ssize_t source_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct gtrace_component *comp = to_gtrace_component(dev);
+
+ comp = rvtrace_timestamp_get_source(comp);
+ if (!comp)
+ return -EINVAL;
+
+ return sysfs_emit(buf, "%s\n", dev_name(&comp->dev));
+}
+static DEVICE_ATTR_RO(source);
+
+static const char * const modes_str[] = {
+ [RVTRACE_TIMESTAMP_MODE_NONE] = "none",
+ [RVTRACE_TIMESTAMP_MODE_EXTERNAL] = "external",
+ [RVTRACE_TIMESTAMP_MODE_INTERNAL_SYSTEM] = "internal_system",
+ [RVTRACE_TIMESTAMP_MODE_INTERNAL_CORE] = "internal_core",
+ [RVTRACE_TIMESTAMP_MODE_SHARED] = "shared",
+};
+
+static ssize_t modes_available_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct gtrace_component *comp = to_gtrace_component(dev);
+ struct rvtrace_timestamp_priv *priv;
+ enum rvtrace_timestamp_mode mode;
+ ssize_t size = 0;
+
+ comp = rvtrace_timestamp_get_source(comp);
+ if (!comp)
+ return -EINVAL;
+
+ priv = get_timestamp_priv(comp);
+
+ for (mode = RVTRACE_TIMESTAMP_MODE_NONE; mode < RVTRACE_TIMESTAMP_MODE_MAX; mode++) {
+ if (priv->mode_hw[mode])
+ size += sysfs_emit_at(buf, size, "%s ", modes_str[mode]);
+ }
+
+ size += sysfs_emit_at(buf, size, "\n");
+ return size;
+}
+static DEVICE_ATTR_RO(modes_available);
+
+static ssize_t mode_preferred_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct gtrace_component *comp = to_gtrace_component(dev);
+ struct rvtrace_timestamp_priv *priv;
+
+ comp = rvtrace_timestamp_get_source(comp);
+ if (!comp)
+ return -EINVAL;
+
+ priv = get_timestamp_priv(comp);
+
+ return sysfs_emit(buf, "%s\n", modes_str[priv->mode]);
+}
+
+static ssize_t mode_preferred_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct gtrace_component *comp = to_gtrace_component(dev);
+ struct rvtrace_timestamp_priv *priv;
+
+ comp = rvtrace_timestamp_get_source(comp);
+ if (!comp)
+ return -EINVAL;
+
+ priv = get_timestamp_priv(comp);
+
+ if (sysfs_streq(buf, modes_str[RVTRACE_TIMESTAMP_MODE_NONE]) &&
+ priv->mode_hw[RVTRACE_TIMESTAMP_MODE_NONE])
+ priv->mode = RVTRACE_TIMESTAMP_MODE_NONE;
+ else if (sysfs_streq(buf, modes_str[RVTRACE_TIMESTAMP_MODE_EXTERNAL]) &&
+ priv->mode_hw[RVTRACE_TIMESTAMP_MODE_EXTERNAL])
+ priv->mode = RVTRACE_TIMESTAMP_MODE_EXTERNAL;
+ else if (sysfs_streq(buf, modes_str[RVTRACE_TIMESTAMP_MODE_INTERNAL_SYSTEM]) &&
+ priv->mode_hw[RVTRACE_TIMESTAMP_MODE_INTERNAL_SYSTEM])
+ priv->mode = RVTRACE_TIMESTAMP_MODE_INTERNAL_SYSTEM;
+ else if (sysfs_streq(buf, modes_str[RVTRACE_TIMESTAMP_MODE_INTERNAL_CORE]) &&
+ priv->mode_hw[RVTRACE_TIMESTAMP_MODE_INTERNAL_CORE])
+ priv->mode = RVTRACE_TIMESTAMP_MODE_INTERNAL_CORE;
+ else if (sysfs_streq(buf, modes_str[RVTRACE_TIMESTAMP_MODE_SHARED]) &&
+ priv->mode_hw[RVTRACE_TIMESTAMP_MODE_SHARED])
+ priv->mode = RVTRACE_TIMESTAMP_MODE_SHARED;
+ else
+ return -EINVAL;
+ return size;
+}
+static DEVICE_ATTR_RW(mode_preferred);
+
+static ssize_t prescale_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct gtrace_component *comp = to_gtrace_component(dev);
+ struct rvtrace_timestamp_priv *priv;
+
+ comp = rvtrace_timestamp_get_source(comp);
+ if (!comp)
+ return -EINVAL;
+
+ priv = get_timestamp_priv(comp);
+
+ return sysfs_emit(buf, "%u\n", priv->prescale);
+}
+
+static ssize_t prescale_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct gtrace_component *comp = to_gtrace_component(dev);
+ struct rvtrace_timestamp_priv *priv;
+ unsigned long val;
+ int ret;
+
+ comp = rvtrace_timestamp_get_source(comp);
+ if (!comp)
+ return -EINVAL;
+
+ ret = kstrtoul(buf, 0, &val);
+ if (ret)
+ return ret;
+
+ if (val > RVTRACE_TIMESTAMP_PRESCALE_MAX)
+ return -EINVAL;
+
+ priv = get_timestamp_priv(comp);
+ if (!rvtrace_timestamp_is_internal(priv))
+ return -EINVAL;
+
+ priv->prescale = val;
+
+ return size;
+}
+static DEVICE_ATTR_RW(prescale);
+
+static ssize_t control_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ u32 val;
+ struct gtrace_component *comp = to_gtrace_component(dev);
+
+ comp = rvtrace_timestamp_get_source(comp);
+ if (!comp)
+ return -EINVAL;
+
+ val = gtrace_read32(comp->pdata, RVTRACE_TIMESTAMP_CTRL_OFFSET);
+
+ return sysfs_emit(buf, "0x%x\n", val);
+}
+static DEVICE_ATTR_RO(control);
+
+static struct attribute *rvtrace_timestamp_attrs[] = {
+ &dev_attr_source.attr,
+ &dev_attr_modes_available.attr,
+ &dev_attr_mode_preferred.attr,
+ &dev_attr_prescale.attr,
+ &dev_attr_control.attr,
+ NULL,
+};
+
+static umode_t timestamp_attr_is_visible(struct kobject *kobj,
+ struct attribute *attr, int idx)
+{
+ struct device *dev = kobj_to_dev(kobj);
+ struct gtrace_component *comp = to_gtrace_component(dev);
+
+ if (rvtrace_timestamp_get_source(comp))
+ return attr->mode;
+
+ return 0;
+}
+
+struct attribute_group rvtrace_timestamp_group = {
+ .attrs = rvtrace_timestamp_attrs,
+ .name = "timestamp",
+ .is_visible = timestamp_attr_is_visible,
+};
+EXPORT_SYMBOL_GPL(rvtrace_timestamp_group);
diff --git a/drivers/hwtracing/gtrace/rvtrace-timestamp.h b/drivers/hwtracing/gtrace/rvtrace-timestamp.h
new file mode 100644
index 000000000000..75924d397996
--- /dev/null
+++ b/drivers/hwtracing/gtrace/rvtrace-timestamp.h
@@ -0,0 +1,44 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright(C) 2026 Spacemit Technologies, Inc.
+ */
+
+#ifndef __RVTRACE_TIMESTAMP_H__
+#define __RVTRACE_TIMESTAMP_H__
+
+#include <linux/types.h>
+
+#define RVTRACE_TIMESTAMP_CTRL_OFFSET 0x040
+#define RVTRACE_TIMESTAMP_COUNT_SHIFT 1
+#define RVTRACE_TIMESTAMP_MODE_MASK GENMASK(6, 4)
+#define RVTRACE_TIMESTAMP_PRESCALE_MASK GENMASK(9, 8)
+#define RVTRACE_TIMESTAMP_PRESCALE_MAX 3
+#define RVTRACE_TIMESTAMP_ENABLE_SHIFT 15
+#define RVTRACE_TIMESTAMP_WIDTH_MASK GENMASK(29, 24)
+
+enum rvtrace_timestamp_mode {
+ RVTRACE_TIMESTAMP_MODE_NONE,
+ RVTRACE_TIMESTAMP_MODE_EXTERNAL,
+ RVTRACE_TIMESTAMP_MODE_INTERNAL_SYSTEM,
+ RVTRACE_TIMESTAMP_MODE_INTERNAL_CORE,
+ RVTRACE_TIMESTAMP_MODE_SHARED,
+ RVTRACE_TIMESTAMP_MODE_MAX
+};
+
+struct rvtrace_timestamp_priv {
+ bool mode_hw[RVTRACE_TIMESTAMP_MODE_MAX];
+ enum rvtrace_timestamp_mode mode;
+ u32 prescale;
+};
+
+int rvtrace_timestamp_reset(struct gtrace_platform_data *pdata);
+void rvtrace_timestamp_init(struct gtrace_platform_data *pdata,
+ struct rvtrace_timestamp_priv *priv);
+void rvtrace_timestamp_setup(struct gtrace_platform_data *pdata,
+ struct rvtrace_timestamp_priv *priv,
+ struct perf_event *event,
+ bool enable);
+
+extern struct attribute_group rvtrace_timestamp_group;
+
+#endif/* __RVTRACE_TIMESTAMP_H__ */
--
2.34.1
^ permalink raw reply [flat|nested] 25+ messages in thread* [PATCH 12/24] gtrace: Add RISC-V trace ATB bridge driver
2026-10-23 3:14 [RFC PATCH 00/24] Add Linux RISC-V trace support via the gtrace framework Zane Leung
` (10 preceding siblings ...)
2026-10-23 3:14 ` [PATCH 11/24] gtrace: Add RISC-V trace timestamp support Zane Leung
@ 2026-10-23 3:14 ` Zane Leung
2026-10-23 3:14 ` [PATCH 13/24] gtrace: Add CoreSight platform core APIs Zane Leung
` (11 subsequent siblings)
23 siblings, 0 replies; 25+ messages in thread
From: Zane Leung @ 2026-10-23 3:14 UTC (permalink / raw)
To: gregkh, palmer, pjw, alexander.shishkin, suzuki.poulose,
mchitale, anup, irogers
Cc: mike.leach, james.clark, leo.yan, peterz, mingo, namhyung,
mark.rutland, jolsa, adrian.hunter, alex, atish.patra,
andrew.jones, sunilvl, liangzhen, zhuangqiubin, coresight,
linux-riscv, linux-kernel
Add initial implementation of RISC-V Trace atbbridge driver. The
atbbridge is defined in the RISC-V Trace Control Interface specification.
Signed-off-by: Zane Leung <liangzhen@linux.spacemit.com>
---
drivers/hwtracing/gtrace/Kconfig | 15 ++
drivers/hwtracing/gtrace/Makefile | 1 +
drivers/hwtracing/gtrace/rvtrace-atbbridge.c | 153 +++++++++++++++++++
drivers/hwtracing/gtrace/rvtrace-platform.c | 3 +
drivers/hwtracing/gtrace/rvtrace.h | 1 +
5 files changed, 173 insertions(+)
create mode 100644 drivers/hwtracing/gtrace/rvtrace-atbbridge.c
diff --git a/drivers/hwtracing/gtrace/Kconfig b/drivers/hwtracing/gtrace/Kconfig
index f6026965c54d..3af47d7c3ce3 100644
--- a/drivers/hwtracing/gtrace/Kconfig
+++ b/drivers/hwtracing/gtrace/Kconfig
@@ -71,3 +71,18 @@ config RVTRACE_FUNNEL
To compile this driver as a module, choose M here: the module
will be called rvtrace-funnel.
+
+config RVTRACE_ATBBRIDGE
+ tristate "RISC-V Trace ATB Bridge driver"
+ depends on RVTRACE
+ default y
+ help
+ This driver provides support for the RISC-V trace ATB bridge
+ component which is defined by the RISC-V Trace Control Interface
+ specification. The ATB bridge converts the RISC-V trace stream
+ to the ARM Advanced Trace Bus (ATB) protocol, allowing RISC-V
+ trace sources to feed into ARM CoreSight trace components. It is
+ a link component in the trace path.
+
+ To compile this driver as a module, choose M here: the module
+ will be called rvtrace-atbbridge.
diff --git a/drivers/hwtracing/gtrace/Makefile b/drivers/hwtracing/gtrace/Makefile
index b60d3cfe5248..3f1dcb5664a2 100644
--- a/drivers/hwtracing/gtrace/Makefile
+++ b/drivers/hwtracing/gtrace/Makefile
@@ -10,3 +10,4 @@ obj-$(CONFIG_RVTRACE_ENCODER) += rvtrace-encoder.o
rvtrace-encoder-y := rvtrace-encoder-core.o rvtrace-encoder-sysfs.o
obj-$(CONFIG_RVTRACE_RAMSINK) += rvtrace-ramsink.o
obj-$(CONFIG_RVTRACE_FUNNEL) += rvtrace-funnel.o
+obj-$(CONFIG_RVTRACE_ATBBRIDGE) += rvtrace-atbbridge.o
diff --git a/drivers/hwtracing/gtrace/rvtrace-atbbridge.c b/drivers/hwtracing/gtrace/rvtrace-atbbridge.c
new file mode 100644
index 000000000000..92a59ed80a11
--- /dev/null
+++ b/drivers/hwtracing/gtrace/rvtrace-atbbridge.c
@@ -0,0 +1,153 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2026 Spacemit Technologies, Inc.
+ */
+
+#include <linux/device.h>
+#include <linux/gtrace.h>
+#include <linux/idr.h>
+#include <linux/types.h>
+#include <linux/bitfield.h>
+#include "rvtrace.h"
+
+#define RVTRACE_ATBBRIDGE_CTRL_ATID_MASK GENMASK(14, 8)
+#define RVTRACE_ATBBRIDGE_ATID_MIN 0x1
+#define RVTRACE_ATBBRIDGE_ATID_MAX 0x6f
+
+static DEFINE_IDA(rvtrace_atbbridge_atid_ida);
+
+struct rvtrace_atbbridge_priv {
+ int atid;
+};
+
+static int rvtrace_atbbridge_start(struct gtrace_component *comp)
+{
+ int ret;
+
+ ret = gtrace_enable_component(comp);
+ if (ret)
+ dev_err(&comp->dev, "failed to start atbbridge.\n");
+
+ return ret;
+}
+
+static int rvtrace_atbbridge_stop(struct gtrace_component *comp)
+{
+ int ret;
+
+ ret = gtrace_disable_component(comp);
+ if (ret)
+ dev_err(&comp->dev, "failed to stop atbbridge.\n");
+
+ return ret;
+}
+
+static ssize_t cpu_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct gtrace_component *comp = to_gtrace_component(dev);
+
+ return sysfs_emit(buf, "%d\n", comp->pdata->bound_cpu);
+}
+static DEVICE_ATTR_RO(cpu);
+
+static ssize_t atid_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct gtrace_component *comp = to_gtrace_component(dev);
+ struct rvtrace_atbbridge_priv *priv = dev_get_drvdata(&comp->dev);
+
+ return sysfs_emit(buf, "%u\n", priv->atid);
+}
+static DEVICE_ATTR_RO(atid);
+
+static struct attribute *rvtrace_atbbridge_attrs[] = {
+ &dev_attr_cpu.attr,
+ &dev_attr_atid.attr,
+ NULL,
+};
+
+static const struct attribute_group rvtrace_atbbridge_group = {
+ .attrs = rvtrace_atbbridge_attrs,
+};
+
+const struct attribute_group *rvtrace_atbbridge_groups[] = {
+ &rvtrace_atbbridge_group,
+ NULL,
+};
+EXPORT_SYMBOL_GPL(rvtrace_atbbridge_groups);
+
+static void rvtrace_atbbridge_cleanup(struct gtrace_component *comp)
+{
+ struct rvtrace_atbbridge_priv *priv = dev_get_drvdata(&comp->dev);
+
+ ida_free(&rvtrace_atbbridge_atid_ida, priv->atid);
+}
+
+static int rvtrace_atbbridge_probe(struct gtrace_component *comp)
+{
+ struct rvtrace_atbbridge_priv *priv;
+ u32 val;
+
+ priv = devm_kzalloc(&comp->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->atid = ida_alloc_range(&rvtrace_atbbridge_atid_ida,
+ RVTRACE_ATBBRIDGE_ATID_MIN,
+ RVTRACE_ATBBRIDGE_ATID_MAX,
+ GFP_KERNEL);
+ if (priv->atid < 0) {
+ dev_err(&comp->dev, "failed to allocate ATID.\n");
+ return priv->atid;
+ }
+
+ val = gtrace_read32(comp->pdata, RVTRACE_COMPONENT_CTRL_OFFSET);
+ val &= ~RVTRACE_ATBBRIDGE_CTRL_ATID_MASK;
+ val |= FIELD_PREP(RVTRACE_ATBBRIDGE_CTRL_ATID_MASK, priv->atid);
+ gtrace_write32(comp->pdata, val, RVTRACE_COMPONENT_CTRL_OFFSET);
+
+ dev_set_drvdata(&comp->dev, priv);
+
+ return 0;
+}
+
+static void rvtrace_atbbridge_remove(struct gtrace_component *comp)
+{
+ rvtrace_atbbridge_cleanup(comp);
+}
+
+static const struct gtrace_component_id rvtrace_atbbridge_ids[] = {
+ { .type = GTRACE_RVTRACE_ATBBRIDGE,
+ .version = rvtrace_component_mkversion(1, 0), },
+ {},
+};
+
+static struct gtrace_driver rvtrace_atbbridge_driver = {
+ .id_table = rvtrace_atbbridge_ids,
+ .probe = rvtrace_atbbridge_probe,
+ .remove = rvtrace_atbbridge_remove,
+ .start = rvtrace_atbbridge_start,
+ .stop = rvtrace_atbbridge_stop,
+ .driver = {
+ .name = "rvtrace-atbbridge",
+ },
+};
+
+static int __init rvtrace_atbbridge_init(void)
+{
+ return gtrace_register_driver(&rvtrace_atbbridge_driver);
+}
+
+static void __exit rvtrace_atbbridge_exit(void)
+{
+ gtrace_unregister_driver(&rvtrace_atbbridge_driver);
+ ida_destroy(&rvtrace_atbbridge_atid_ida);
+}
+
+module_init(rvtrace_atbbridge_init);
+module_exit(rvtrace_atbbridge_exit);
+
+MODULE_AUTHOR("Zane Leung");
+MODULE_DESCRIPTION("RISC-V Trace ATB Bridge Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hwtracing/gtrace/rvtrace-platform.c b/drivers/hwtracing/gtrace/rvtrace-platform.c
index ad8daa06e13e..9bd315867082 100644
--- a/drivers/hwtracing/gtrace/rvtrace-platform.c
+++ b/drivers/hwtracing/gtrace/rvtrace-platform.c
@@ -136,6 +136,9 @@ static int rvtrace_platform_probe(struct platform_device *pdev)
case GTRACE_RVTRACE_FUNNEL:
pdata->groups = rvtrace_funnel_groups;
break;
+ case GTRACE_RVTRACE_ATBBRIDGE:
+ pdata->groups = rvtrace_atbbridge_groups;
+ break;
default:
pdata->groups = NULL;
}
diff --git a/drivers/hwtracing/gtrace/rvtrace.h b/drivers/hwtracing/gtrace/rvtrace.h
index a345ba110267..5cbe3405e274 100644
--- a/drivers/hwtracing/gtrace/rvtrace.h
+++ b/drivers/hwtracing/gtrace/rvtrace.h
@@ -121,5 +121,6 @@ extern const struct gtrace_hw_ops rvtrace_hw_ops;
/* Sysfs attribute groups for RISC-V trace components */
extern const struct attribute_group *rvtrace_encoder_groups[];
extern const struct attribute_group *rvtrace_funnel_groups[];
+extern const struct attribute_group *rvtrace_atbbridge_groups[];
#endif /* __LINUX_RVTRACE_H__ */
--
2.34.1
^ permalink raw reply [flat|nested] 25+ messages in thread* [PATCH 13/24] gtrace: Add CoreSight platform core APIs
2026-10-23 3:14 [RFC PATCH 00/24] Add Linux RISC-V trace support via the gtrace framework Zane Leung
` (11 preceding siblings ...)
2026-10-23 3:14 ` [PATCH 12/24] gtrace: Add RISC-V trace ATB bridge driver Zane Leung
@ 2026-10-23 3:14 ` Zane Leung
2026-10-23 3:14 ` [PATCH 14/24] gtrace: Add CoreSight funnel driver Zane Leung
` (10 subsequent siblings)
23 siblings, 0 replies; 25+ messages in thread
From: Zane Leung @ 2026-10-23 3:14 UTC (permalink / raw)
To: gregkh, palmer, pjw, alexander.shishkin, suzuki.poulose,
mchitale, anup, irogers
Cc: mike.leach, james.clark, leo.yan, peterz, mingo, namhyung,
mark.rutland, jolsa, adrian.hunter, alex, atish.patra,
andrew.jones, sunilvl, liangzhen, zhuangqiubin, coresight,
linux-riscv, linux-kernel
Add CoreSight infrastructure for the gtrace framework, providing driver
registration helper, clock management, and other core integration APIs.
Signed-off-by: Zane Leung <liangzhen@linux.spacemit.com>
---
drivers/hwtracing/gtrace/Kconfig | 15 +
drivers/hwtracing/gtrace/Makefile | 1 +
drivers/hwtracing/gtrace/coresight-platform.c | 279 ++++++++++++++++++
drivers/hwtracing/gtrace/coresight.h | 212 +++++++++++++
include/linux/gtrace.h | 7 +
5 files changed, 514 insertions(+)
create mode 100644 drivers/hwtracing/gtrace/coresight-platform.c
create mode 100644 drivers/hwtracing/gtrace/coresight.h
diff --git a/drivers/hwtracing/gtrace/Kconfig b/drivers/hwtracing/gtrace/Kconfig
index 3af47d7c3ce3..5dce4474db7c 100644
--- a/drivers/hwtracing/gtrace/Kconfig
+++ b/drivers/hwtracing/gtrace/Kconfig
@@ -86,3 +86,18 @@ config RVTRACE_ATBBRIDGE
To compile this driver as a module, choose M here: the module
will be called rvtrace-atbbridge.
+
+config GTRACE_CORESIGHT
+ tristate "CoreSight Trace Support"
+ depends on RISCV
+ depends on GTRACE
+ select ARM_AMBA
+ help
+ This provides the CoreSight backend for the generic hardware trace
+ (gtrace) framework. It provides the CoreSight claim protocol,
+ component and driver registration helpers, and clock management
+ used by the individual CoreSight component drivers (TMC, funnel,
+ etc.) to register CoreSight hardware with the gtrace core.
+
+ To compile this driver as a module, choose M here: the module
+ will be called coresight.
diff --git a/drivers/hwtracing/gtrace/Makefile b/drivers/hwtracing/gtrace/Makefile
index 3f1dcb5664a2..0833a40c397c 100644
--- a/drivers/hwtracing/gtrace/Makefile
+++ b/drivers/hwtracing/gtrace/Makefile
@@ -11,3 +11,4 @@ rvtrace-encoder-y := rvtrace-encoder-core.o rvtrace-encoder-sysfs.o
obj-$(CONFIG_RVTRACE_RAMSINK) += rvtrace-ramsink.o
obj-$(CONFIG_RVTRACE_FUNNEL) += rvtrace-funnel.o
obj-$(CONFIG_RVTRACE_ATBBRIDGE) += rvtrace-atbbridge.o
+obj-$(CONFIG_GTRACE_CORESIGHT) += coresight-platform.o
diff --git a/drivers/hwtracing/gtrace/coresight-platform.c b/drivers/hwtracing/gtrace/coresight-platform.c
new file mode 100644
index 000000000000..f5a472e86a32
--- /dev/null
+++ b/drivers/hwtracing/gtrace/coresight-platform.c
@@ -0,0 +1,279 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * CoreSight platform backend shared functions for the gtrace framework.
+ * Derived from drivers/hwtracing/coresight/coresight-core.c.
+ *
+ * Copyright (c) 2012, The Linux Foundation. All rights reserved.
+ */
+
+#include <linux/acpi.h>
+#include <linux/bitfield.h>
+#include <linux/build_bug.h>
+#include <linux/cpu_pm.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/device.h>
+#include <linux/io.h>
+#include <linux/err.h>
+#include <linux/export.h>
+#include <linux/slab.h>
+#include <linux/stringhash.h>
+#include <linux/clk.h>
+#include <linux/gtrace.h>
+#include <linux/property.h>
+#include <linux/delay.h>
+#include <linux/pm_runtime.h>
+#include <linux/panic_notifier.h>
+
+#include "coresight.h"
+
+/*
+ * When losing synchronisation a new barrier packet needs to be inserted at the
+ * beginning of the data collected in a buffer. That way the decoder knows that
+ * it needs to look for another sync sequence.
+ */
+const u32 coresight_barrier_pkt[4] = {0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff};
+EXPORT_SYMBOL_GPL(coresight_barrier_pkt);
+
+static u32 coresight_read_claim_tags_unlocked(struct gtrace_component *comp)
+{
+ return FIELD_GET(CORESIGHT_CLAIM_MASK,
+ gtrace_relaxed_read32(comp->pdata, CORESIGHT_CLAIMCLR));
+}
+
+static void coresight_set_self_claim_tag_unlocked(struct gtrace_component *comp)
+{
+ gtrace_relaxed_write32(comp->pdata, CORESIGHT_CLAIM_SELF_HOSTED,
+ CORESIGHT_CLAIMSET);
+ /* Make sure everyone has seen this */
+ mb();
+}
+
+void coresight_clear_self_claim_tag(struct gtrace_platform_data *pdata)
+{
+ if (pdata->io_mem)
+ CS_UNLOCK(pdata->base);
+ coresight_clear_self_claim_tag_unlocked(pdata);
+ if (pdata->io_mem)
+ CS_LOCK(pdata->base);
+}
+EXPORT_SYMBOL_GPL(coresight_clear_self_claim_tag);
+
+void coresight_clear_self_claim_tag_unlocked(struct gtrace_platform_data *pdata)
+{
+ gtrace_relaxed_write32(pdata, CORESIGHT_CLAIM_SELF_HOSTED,
+ CORESIGHT_CLAIMCLR);
+ /* Make sure everyone has seen this */
+ mb();
+}
+EXPORT_SYMBOL_GPL(coresight_clear_self_claim_tag_unlocked);
+
+/*
+ * coresight_claim_device_unlocked : Claim the device for self-hosted usage
+ * to prevent an external tool from touching this device. As per PSCI
+ * standards, section "Preserving the execution context" => "Debug and Trace
+ * save and Restore", DBGCLAIM[1] is reserved for Self-hosted debug/trace and
+ * DBGCLAIM[0] is reserved for external tools.
+ *
+ * Called with CS_UNLOCKed for the component.
+ * Returns : 0 on success
+ */
+int coresight_claim_device_unlocked(struct gtrace_component *comp)
+{
+ u32 tag;
+ struct gtrace_platform_data *pdata;
+
+ if (WARN_ON(!comp))
+ return -EINVAL;
+
+ pdata = comp->pdata;
+ tag = coresight_read_claim_tags_unlocked(comp);
+
+ switch (tag) {
+ case CORESIGHT_CLAIM_FREE:
+ coresight_set_self_claim_tag_unlocked(comp);
+ if (coresight_read_claim_tags_unlocked(comp) == CORESIGHT_CLAIM_SELF_HOSTED)
+ return 0;
+
+ /* There was a race setting the tag, clean up and fail */
+ coresight_clear_self_claim_tag_unlocked(pdata);
+ dev_dbg(&comp->dev, "Busy: Couldn't set self claim tag");
+ return -EBUSY;
+
+ case CORESIGHT_CLAIM_EXTERNAL:
+ /* External debug is an expected state, so log and report BUSY */
+ dev_dbg(&comp->dev, "Busy: Claimed by external debugger");
+ return -EBUSY;
+
+ default:
+ case CORESIGHT_CLAIM_SELF_HOSTED:
+ case CORESIGHT_CLAIM_INVALID:
+ dev_err_once(&comp->dev, "Invalid claim tag state: %x", tag);
+ return -EBUSY;
+ }
+}
+EXPORT_SYMBOL_GPL(coresight_claim_device_unlocked);
+
+int coresight_claim_device(struct gtrace_component *comp)
+{
+ int rc;
+
+ if (WARN_ON(!comp))
+ return -EINVAL;
+
+ CS_UNLOCK(comp->pdata->base);
+ rc = coresight_claim_device_unlocked(comp);
+ CS_LOCK(comp->pdata->base);
+
+ return rc;
+}
+EXPORT_SYMBOL_GPL(coresight_claim_device);
+
+/*
+ * coresight_disclaim_device_unlocked : Clear the claim tag for the device.
+ * Called with CS_UNLOCKed for the component.
+ */
+void coresight_disclaim_device_unlocked(struct gtrace_component *comp)
+{
+ if (WARN_ON(!comp))
+ return;
+
+ if (coresight_read_claim_tags_unlocked(comp) == CORESIGHT_CLAIM_SELF_HOSTED)
+ coresight_clear_self_claim_tag_unlocked(comp->pdata);
+ else
+ /*
+ * The external agent may have not honoured our claim
+ * and has manipulated it. Or something else has seriously
+ * gone wrong in our driver.
+ */
+ dev_WARN_ONCE(&comp->dev, 1, "External agent took claim tag");
+}
+EXPORT_SYMBOL_GPL(coresight_disclaim_device_unlocked);
+
+void coresight_disclaim_device(struct gtrace_component *comp)
+{
+ if (WARN_ON(!comp))
+ return;
+
+ CS_UNLOCK(comp->pdata->base);
+ coresight_disclaim_device_unlocked(comp);
+ CS_LOCK(comp->pdata->base);
+}
+EXPORT_SYMBOL_GPL(coresight_disclaim_device);
+
+/*
+ * Attempt to find and enable programming clock (pclk) and trace clock (atclk)
+ * for the given device.
+ *
+ * For ACPI devices, clocks are controlled by firmware, so bail out early in
+ * this case. Also, skip enabling pclk if the clock is managed by the AMBA
+ * bus driver instead.
+ *
+ * atclk is an optional clock, it will be only enabled when it is existed.
+ * Otherwise, a NULL pointer will be returned to caller.
+ *
+ * Returns: '0' on Success; Error code otherwise.
+ */
+int coresight_get_enable_clocks(struct device *dev, struct clk **pclk,
+ struct clk **atclk)
+{
+ WARN_ON(!pclk);
+
+ if (has_acpi_companion(dev))
+ return 0;
+
+ if (!dev_is_amba(dev)) {
+ /*
+ * "apb_pclk" is the default clock name for an Arm Primecell
+ * peripheral, while "apb" is used only by the CTCU driver.
+ *
+ * For easier maintenance, CoreSight drivers should use
+ * "apb_pclk" as the programming clock name.
+ */
+ *pclk = devm_clk_get_optional_enabled(dev, "apb_pclk");
+ if (!*pclk)
+ *pclk = devm_clk_get_optional_enabled(dev, "apb");
+ if (IS_ERR(*pclk))
+ return PTR_ERR(*pclk);
+ }
+
+ /* Initialization of atclk is skipped if it is a NULL pointer. */
+ if (atclk) {
+ *atclk = devm_clk_get_optional_enabled(dev, "atclk");
+ if (IS_ERR(*atclk))
+ return PTR_ERR(*atclk);
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(coresight_get_enable_clocks);
+
+struct gtrace_platform_data *
+coresight_get_platform_data(struct device *dev)
+{
+ int ret;
+ struct gtrace_platform_data *pdata = NULL;
+
+ pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
+ if (!pdata) {
+ ret = -ENOMEM;
+ goto err_out;
+ }
+
+ pdata->dev = dev;
+ pdata->control_poll_timeout_usecs = 100;
+
+ ret = gtrace_of_parse_outconns(pdata);
+ if (ret) {
+ dev_err_probe(pdata->dev, ret, "failed to parse output connections\n");
+ goto err_out;
+ }
+
+ ret = gtrace_of_parse_inconns(pdata);
+ if (ret) {
+ dev_err_probe(pdata->dev, ret, "failed to parse input connections\n");
+ goto err_out;
+ }
+
+ return pdata;
+err_out:
+ return ERR_PTR(ret);
+}
+EXPORT_SYMBOL_GPL(coresight_get_platform_data);
+
+int coresight_init_driver_with_owner(const char *drv, struct amba_driver *amba_drv,
+ struct platform_driver *pdev_drv, struct module *owner,
+ const char *mod_name)
+{
+ int ret;
+
+ ret = __amba_driver_register(amba_drv, owner);
+ if (ret) {
+ pr_err("%s: error registering AMBA driver\n", drv);
+ return ret;
+ }
+
+ ret = __platform_driver_register(pdev_drv, owner, mod_name);
+ if (!ret)
+ return 0;
+
+ pr_err("%s: error registering platform driver\n", drv);
+ amba_driver_unregister(amba_drv);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(coresight_init_driver_with_owner);
+
+void coresight_remove_driver(struct amba_driver *amba_drv,
+ struct platform_driver *pdev_drv)
+{
+ platform_driver_unregister(pdev_drv);
+ amba_driver_unregister(amba_drv);
+}
+EXPORT_SYMBOL_GPL(coresight_remove_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Pratik Patel <pratikp@codeaurora.org>");
+MODULE_AUTHOR("Mathieu Poirier <mathieu.poirier@linaro.org>");
+MODULE_AUTHOR("Zane Leung <liangzhen@linux.spacemit.com>");
+MODULE_DESCRIPTION("CoreSight backend for the generic trace framework");
diff --git a/drivers/hwtracing/gtrace/coresight.h b/drivers/hwtracing/gtrace/coresight.h
new file mode 100644
index 000000000000..f369911daace
--- /dev/null
+++ b/drivers/hwtracing/gtrace/coresight.h
@@ -0,0 +1,212 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * CoreSight component register layout and helpers for the gtrace framework.
+ * Derived from drivers/hwtracing/coresight/coresight-priv.h and
+ * include/linux/coresight.h.
+ *
+ * Copyright (c) 2012, The Linux Foundation. All rights reserved.
+ *
+ * CoreSight component register layout and helpers for the CoreSight backend
+ * of the generic trace (gtrace) framework. These definitions are specific to
+ * the CoreSight architecture and must not be used by the generic core.
+ */
+
+#ifndef __LINUX_CORESIGHT_H__
+#define __LINUX_CORESIGHT_H__
+
+#include <linux/gtrace.h>
+#include <linux/types.h>
+#include <linux/amba/bus.h>
+
+/*
+ * Coresight management registers (0xf00-0xfcc)
+ * 0xfa0 - 0xfa4: Management registers in PFTv1.0
+ * Trace registers in PFTv1.1
+ */
+#define CORESIGHT_ITCTRL 0xf00
+#define CORESIGHT_CLAIMSET 0xfa0
+#define CORESIGHT_CLAIMCLR 0xfa4
+#define CORESIGHT_LAR 0xfb0
+#define CORESIGHT_LSR 0xfb4
+#define CORESIGHT_DEVARCH 0xfbc
+#define CORESIGHT_AUTHSTATUS 0xfb8
+#define CORESIGHT_DEVID 0xfc8
+#define CORESIGHT_DEVTYPE 0xfcc
+
+/*
+ * Coresight device CLAIM protocol.
+ * See PSCI - ARM DEN 0022D, Section: 6.8.1 Debug and Trace save and restore.
+ */
+#define CORESIGHT_CLAIM_MASK GENMASK(1, 0)
+#define CORESIGHT_CLAIM_FREE 0
+#define CORESIGHT_CLAIM_EXTERNAL 1
+#define CORESIGHT_CLAIM_SELF_HOSTED 2
+#define CORESIGHT_CLAIM_INVALID 3
+
+/* Peripheral id registers (0xFD0-0xFEC) */
+#define CORESIGHT_PERIPHIDR4 0xfd0
+#define CORESIGHT_PERIPHIDR5 0xfd4
+#define CORESIGHT_PERIPHIDR6 0xfd8
+#define CORESIGHT_PERIPHIDR7 0xfdC
+#define CORESIGHT_PERIPHIDR0 0xfe0
+#define CORESIGHT_PERIPHIDR1 0xfe4
+#define CORESIGHT_PERIPHIDR2 0xfe8
+#define CORESIGHT_PERIPHIDR3 0xfeC
+/* Component id registers (0xFF0-0xFFC) */
+#define CORESIGHT_COMPIDR0 0xff0
+#define CORESIGHT_COMPIDR1 0xff4
+#define CORESIGHT_COMPIDR2 0xff8
+#define CORESIGHT_COMPIDR3 0xffC
+
+#define CORESIGHT_UNLOCK 0xc5acce55
+
+#define BMVAL(val, lsb, msb) ((val & GENMASK(msb, lsb)) >> lsb)
+
+extern const u32 coresight_barrier_pkt[4];
+#define CORESIGHT_BARRIER_PKT_SIZE (sizeof(coresight_barrier_pkt))
+
+#define CORESIGHT_COMPONENT_ID_VERSION 1
+
+static inline void coresight_insert_barrier_packet(void *buf)
+{
+ if (buf)
+ memcpy(buf, coresight_barrier_pkt, CORESIGHT_BARRIER_PKT_SIZE);
+}
+
+static inline void CS_LOCK(void __iomem *addr)
+{
+ do {
+ /* Wait for things to settle */
+ mb();
+ writel_relaxed(0x0, addr + CORESIGHT_LAR);
+ } while (0);
+}
+
+static inline void CS_UNLOCK(void __iomem *addr)
+{
+ do {
+ writel_relaxed(CORESIGHT_UNLOCK, addr + CORESIGHT_LAR);
+ /* Make sure everyone has seen this */
+ mb();
+ } while (0);
+}
+
+#define CORESIGHT_CIDRn(i) (0xFF0 + ((i) * 4))
+
+static inline u32 coresight_get_cid(void __iomem *base)
+{
+ u32 i, cid = 0;
+
+ for (i = 0; i < 4; i++)
+ cid |= readl(base + CORESIGHT_CIDRn(i)) << (i * 8);
+
+ return cid;
+}
+
+static inline bool is_coresight_device(void __iomem *base)
+{
+ u32 cid = coresight_get_cid(base);
+
+ return cid == CORESIGHT_CID;
+}
+
+#define CORESIGHT_PIDRn(i) (0xFE0 + ((i) * 4))
+
+static inline u32 coresight_get_pid(struct gtrace_platform_data *pdata)
+{
+ u32 i, pid = 0;
+
+ for (i = 0; i < 4; i++)
+ pid |= gtrace_relaxed_read32(pdata, CORESIGHT_PIDRn(i)) << (i * 8);
+
+ return pid;
+}
+
+/*
+ * Macros and inline functions to handle CoreSight UCI data and driver
+ * private data in AMBA ID table entries, and extract data values.
+ */
+
+/* coresight AMBA ID, no UCI, no driver data: id table entry */
+#define CS_AMBA_ID(pid) \
+ { \
+ .id = pid, \
+ .mask = 0x000fffff, \
+ }
+
+/* coresight AMBA ID, UCI with driver data only: id table entry. */
+#define CS_AMBA_ID_DATA(pid, dval) \
+ { \
+ .id = pid, \
+ .mask = 0x000fffff, \
+ .data = (void *)&(struct amba_cs_uci_id) \
+ { \
+ .data = (void *)dval, \
+ } \
+ }
+
+/* coresight AMBA ID, full UCI structure: id table entry. */
+#define __CS_AMBA_UCI_ID(pid, m, uci_ptr) \
+ { \
+ .id = pid, \
+ .mask = m, \
+ .data = (void *)uci_ptr \
+ }
+#define CS_AMBA_UCI_ID(pid, uci) __CS_AMBA_UCI_ID(pid, 0x000fffff, uci)
+/*
+ * PIDR2[JEDEC], BIT(3) must be 1 (Read As One) to indicate that rest of the
+ * PIDR1, PIDR2 DES_* fields follow JEDEC encoding for the designer. Use that
+ * as a match value for blanket matching all devices in the given CoreSight
+ * device type and architecture.
+ */
+#define PIDR2_JEDEC BIT(3)
+#define PID_PIDR2_JEDEC (PIDR2_JEDEC << 16)
+/*
+ * Match all PIDs in a given CoreSight device type and architecture, defined
+ * by the uci.
+ */
+#define CS_AMBA_MATCH_ALL_UCI(uci) \
+ __CS_AMBA_UCI_ID(PID_PIDR2_JEDEC, PID_PIDR2_JEDEC, uci)
+
+/* extract the data value from a UCI structure given amba_id pointer. */
+static inline void *coresight_get_uci_data(const struct amba_id *id)
+{
+ struct amba_cs_uci_id *uci_id = id->data;
+
+ if (!uci_id)
+ return NULL;
+
+ return uci_id->data;
+}
+
+static inline void *coresight_get_uci_data_from_amba(const struct amba_id *table, u32 pid)
+{
+ while (table->mask) {
+ if ((pid & table->mask) == table->id)
+ return coresight_get_uci_data(table);
+ table++;
+ };
+ return NULL;
+}
+
+int coresight_claim_device(struct gtrace_component *comp);
+int coresight_claim_device_unlocked(struct gtrace_component *comp);
+void coresight_clear_self_claim_tag(struct gtrace_platform_data *pdata);
+void coresight_clear_self_claim_tag_unlocked(struct gtrace_platform_data *pdata);
+void coresight_disclaim_device(struct gtrace_component *comp);
+void coresight_disclaim_device_unlocked(struct gtrace_component *comp);
+
+struct gtrace_platform_data *coresight_get_platform_data(struct device *dev);
+int coresight_get_enable_clocks(struct device *dev, struct clk **pclk,
+ struct clk **atclk);
+
+int coresight_init_driver_with_owner(const char *drv, struct amba_driver *amba_drv,
+ struct platform_driver *pdev_drv, struct module *owner,
+ const char *mod_name);
+#define coresight_init_driver(drv, amba_drv, pdev_drv) \
+ coresight_init_driver_with_owner(drv, amba_drv, pdev_drv, THIS_MODULE, KBUILD_MODNAME)
+
+void coresight_remove_driver(struct amba_driver *amba_drv,
+ struct platform_driver *pdev_drv);
+
+#endif /* __LINUX_CORESIGHT_H__ */
diff --git a/include/linux/gtrace.h b/include/linux/gtrace.h
index ee60585ef65c..e9210b0a73b5 100644
--- a/include/linux/gtrace.h
+++ b/include/linux/gtrace.h
@@ -33,6 +33,13 @@ enum gtrace_component_type {
GTRACE_RVTRACE_RAMSINK,
GTRACE_RVTRACE_PIBSINK,
GTRACE_RVTRACE_ATBBRIDGE,
+ GTRACE_CORESIGHT_FUNNEL,
+ GTRACE_CORESIGHT_REPLICATOR,
+ GTRACE_CORESIGHT_TMC_ETB,
+ GTRACE_CORESIGHT_TMC_ETR,
+ GTRACE_CORESIGHT_TMC_ETF,
+ GTRACE_CORESIGHT_ETB,
+ GTRACE_CORESIGHT_TPIU,
GTRACE_COMPONENT_TYPE_MAX
};
--
2.34.1
^ permalink raw reply [flat|nested] 25+ messages in thread* [PATCH 14/24] gtrace: Add CoreSight funnel driver
2026-10-23 3:14 [RFC PATCH 00/24] Add Linux RISC-V trace support via the gtrace framework Zane Leung
` (12 preceding siblings ...)
2026-10-23 3:14 ` [PATCH 13/24] gtrace: Add CoreSight platform core APIs Zane Leung
@ 2026-10-23 3:14 ` Zane Leung
2026-10-23 3:14 ` [PATCH 15/24] gtrace: Only copy AUX data from the path sink Zane Leung
` (9 subsequent siblings)
23 siblings, 0 replies; 25+ messages in thread
From: Zane Leung @ 2026-10-23 3:14 UTC (permalink / raw)
To: gregkh, palmer, pjw, alexander.shishkin, suzuki.poulose,
mchitale, anup, irogers
Cc: mike.leach, james.clark, leo.yan, peterz, mingo, namhyung,
mark.rutland, jolsa, adrian.hunter, alex, atish.patra,
andrew.jones, sunilvl, liangzhen, zhuangqiubin, coresight,
linux-riscv, linux-kernel
Add a CoreSight funnel driver for the gtrace framework, derived from
drivers/hwtracing/coresight/coresight-funnel.c.
Signed-off-by: Zane Leung <liangzhen@linux.spacemit.com>
---
drivers/hwtracing/gtrace/Kconfig | 13 +
drivers/hwtracing/gtrace/Makefile | 1 +
drivers/hwtracing/gtrace/coresight-funnel.c | 430 ++++++++++++++++++++
3 files changed, 444 insertions(+)
create mode 100644 drivers/hwtracing/gtrace/coresight-funnel.c
diff --git a/drivers/hwtracing/gtrace/Kconfig b/drivers/hwtracing/gtrace/Kconfig
index 5dce4474db7c..081af9c0c670 100644
--- a/drivers/hwtracing/gtrace/Kconfig
+++ b/drivers/hwtracing/gtrace/Kconfig
@@ -101,3 +101,16 @@ config GTRACE_CORESIGHT
To compile this driver as a module, choose M here: the module
will be called coresight.
+
+config CORESIGHT_FUNNEL
+ tristate "CoreSight Trace Funnel driver"
+ depends on GTRACE_CORESIGHT
+ default y
+ help
+ This driver provides support for the CoreSight funnel component.
+ The funnel merges trace data from multiple input ports into a single
+ output stream. It supports both static funnels (platform bus,
+ arm,coresight-static-funnel) and dynamic funnels (AMBA bus).
+
+ To compile this driver as a module, choose M here: the module
+ will be called coresight-funnel.
diff --git a/drivers/hwtracing/gtrace/Makefile b/drivers/hwtracing/gtrace/Makefile
index 0833a40c397c..d2745c961a75 100644
--- a/drivers/hwtracing/gtrace/Makefile
+++ b/drivers/hwtracing/gtrace/Makefile
@@ -12,3 +12,4 @@ obj-$(CONFIG_RVTRACE_RAMSINK) += rvtrace-ramsink.o
obj-$(CONFIG_RVTRACE_FUNNEL) += rvtrace-funnel.o
obj-$(CONFIG_RVTRACE_ATBBRIDGE) += rvtrace-atbbridge.o
obj-$(CONFIG_GTRACE_CORESIGHT) += coresight-platform.o
+obj-$(CONFIG_CORESIGHT_FUNNEL) += coresight-funnel.o
diff --git a/drivers/hwtracing/gtrace/coresight-funnel.c b/drivers/hwtracing/gtrace/coresight-funnel.c
new file mode 100644
index 000000000000..e7a3f185da56
--- /dev/null
+++ b/drivers/hwtracing/gtrace/coresight-funnel.c
@@ -0,0 +1,430 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2011-2012, The Linux Foundation. All rights reserved.
+ *
+ * Description: CoreSight Funnel driver for the gtrace framework.
+ * Derived from drivers/hwtracing/coresight/coresight-funnel.c.
+ */
+
+#include <linux/acpi.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/fs.h>
+#include <linux/slab.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/amba/bus.h>
+#include <linux/clk.h>
+
+#include "coresight.h"
+
+#define FUNNEL_FUNCTL 0x000
+#define FUNNEL_PRICTL 0x004
+
+#define FUNNEL_HOLDTIME_MASK 0xf00
+#define FUNNEL_HOLDTIME_SHFT 0x8
+#define FUNNEL_HOLDTIME (0x7 << FUNNEL_HOLDTIME_SHFT)
+#define FUNNEL_ENSx_MASK 0xff
+
+/**
+ * struct funnel_drvdata - specifics associated to a funnel component
+ * @base: memory mapped base address for this component.
+ * @atclk: optional clock for the core parts of the funnel.
+ * @pclk: APB clock if present, otherwise NULL
+ * @comp: component vitals needed by the framework.
+ * @priority: port selection order.
+ */
+struct funnel_drvdata {
+ void __iomem *base;
+ struct clk *atclk;
+ struct clk *pclk;
+ struct gtrace_component *comp;
+ unsigned long priority;
+};
+
+static int dynamic_funnel_enable_hw(struct funnel_drvdata *drvdata, int port)
+{
+ u32 functl;
+ int rc = 0;
+ struct gtrace_component *comp = drvdata->comp;
+
+ CS_UNLOCK(drvdata->base);
+
+ functl = readl_relaxed(drvdata->base + FUNNEL_FUNCTL);
+ /* Claim the device only when we enable the first slave */
+ if (!(functl & FUNNEL_ENSx_MASK)) {
+ rc = coresight_claim_device_unlocked(comp);
+ if (rc)
+ goto done;
+ }
+
+ functl &= ~FUNNEL_HOLDTIME_MASK;
+ functl |= FUNNEL_HOLDTIME;
+ functl |= (1 << port);
+ writel_relaxed(functl, drvdata->base + FUNNEL_FUNCTL);
+ writel_relaxed(drvdata->priority, drvdata->base + FUNNEL_PRICTL);
+done:
+ CS_LOCK(drvdata->base);
+ return rc;
+}
+
+static int funnel_enable(struct gtrace_component *comp,
+ struct gtrace_connection *in,
+ struct gtrace_connection *out)
+{
+ int rc = 0;
+ struct funnel_drvdata *drvdata = dev_get_drvdata(comp->dev.parent);
+ bool first_enable = false;
+
+ if (in->dest_refcnt == 0) {
+ if (drvdata->base)
+ rc = dynamic_funnel_enable_hw(drvdata, in->dest_port);
+ if (!rc)
+ first_enable = true;
+ }
+ if (!rc)
+ in->dest_refcnt++;
+
+ if (first_enable)
+ dev_dbg(&comp->dev, "FUNNEL inport %d enabled\n",
+ in->dest_port);
+ return rc;
+}
+
+static void dynamic_funnel_disable_hw(struct funnel_drvdata *drvdata,
+ int inport)
+{
+ u32 functl;
+ struct gtrace_component *comp = drvdata->comp;
+
+ CS_UNLOCK(drvdata->base);
+
+ functl = readl_relaxed(drvdata->base + FUNNEL_FUNCTL);
+ functl &= ~(1 << inport);
+ writel_relaxed(functl, drvdata->base + FUNNEL_FUNCTL);
+
+ /* Disclaim the device if none of the slaves are now active */
+ if (!(functl & FUNNEL_ENSx_MASK))
+ coresight_disclaim_device_unlocked(comp);
+
+ CS_LOCK(drvdata->base);
+}
+
+static void funnel_disable(struct gtrace_component *comp,
+ struct gtrace_connection *in,
+ struct gtrace_connection *out)
+{
+ struct funnel_drvdata *drvdata = dev_get_drvdata(comp->dev.parent);
+ bool last_disable = false;
+
+ if (--in->dest_refcnt == 0) {
+ if (drvdata->base)
+ dynamic_funnel_disable_hw(drvdata, in->dest_port);
+ last_disable = true;
+ }
+
+ if (last_disable)
+ dev_dbg(&comp->dev, "FUNNEL inport %d disabled\n",
+ in->dest_port);
+}
+
+static ssize_t priority_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct funnel_drvdata *drvdata = dev_get_drvdata(dev->parent);
+ unsigned long val = drvdata->priority;
+
+ return sprintf(buf, "%#lx\n", val);
+}
+
+static ssize_t priority_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ int ret;
+ unsigned long val;
+ struct funnel_drvdata *drvdata = dev_get_drvdata(dev->parent);
+
+ ret = kstrtoul(buf, 16, &val);
+ if (ret)
+ return ret;
+
+ drvdata->priority = val;
+ return size;
+}
+static DEVICE_ATTR_RW(priority);
+
+static u32 get_funnel_ctrl_hw(struct funnel_drvdata *drvdata)
+{
+ u32 functl;
+
+ CS_UNLOCK(drvdata->base);
+ functl = readl_relaxed(drvdata->base + FUNNEL_FUNCTL);
+ CS_LOCK(drvdata->base);
+
+ return functl;
+}
+
+static ssize_t funnel_ctrl_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ u32 val;
+ struct funnel_drvdata *drvdata = dev_get_drvdata(dev->parent);
+
+ pm_runtime_get_sync(dev->parent);
+
+ val = get_funnel_ctrl_hw(drvdata);
+
+ pm_runtime_put(dev->parent);
+
+ return sprintf(buf, "%#x\n", val);
+}
+static DEVICE_ATTR_RO(funnel_ctrl);
+
+static struct attribute *coresight_funnel_attrs[] = {
+ &dev_attr_funnel_ctrl.attr,
+ &dev_attr_priority.attr,
+ NULL,
+};
+ATTRIBUTE_GROUPS(coresight_funnel);
+
+static int funnel_probe(struct device *dev, struct resource *res)
+{
+ void __iomem *base;
+ struct gtrace_platform_data *pdata = NULL;
+ struct funnel_drvdata *drvdata;
+ struct gtrace_component_id id = {
+ .type = GTRACE_CORESIGHT_FUNNEL,
+ .version = CORESIGHT_COMPONENT_ID_VERSION,
+ };
+ int ret;
+
+ if (is_of_node(dev_fwnode(dev)) &&
+ of_device_is_compatible(dev->of_node, "arm,coresight-funnel"))
+ dev_warn_once(dev, "Uses OBSOLETE CoreSight funnel binding\n");
+
+ drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL);
+ if (!drvdata)
+ return -ENOMEM;
+
+ ret = coresight_get_enable_clocks(dev, &drvdata->pclk, &drvdata->atclk);
+ if (ret)
+ return ret;
+
+ pdata = coresight_get_platform_data(dev);
+ if (IS_ERR(pdata))
+ return PTR_ERR(pdata);
+
+ /*
+ * Map the device base for dynamic-funnel, which has been
+ * validated by AMBA core.
+ */
+ if (res) {
+ base = devm_ioremap_resource(dev, res);
+ if (IS_ERR(base))
+ return PTR_ERR(base);
+ drvdata->base = base;
+ pdata->groups = coresight_funnel_groups;
+ pdata->io_mem = true;
+ pdata->base = base;
+ coresight_clear_self_claim_tag(pdata);
+ }
+
+ dev_set_drvdata(dev, drvdata);
+
+ drvdata->comp = gtrace_register_component(&id, "cs_funnel", pdata);
+ if (IS_ERR(drvdata->comp))
+ return PTR_ERR(drvdata->comp);
+
+ return 0;
+}
+
+static void funnel_remove(struct device *dev)
+{
+ struct funnel_drvdata *drvdata = dev_get_drvdata(dev);
+
+ gtrace_unregister_component(drvdata->comp);
+}
+
+#ifdef CONFIG_PM
+static int funnel_runtime_suspend(struct device *dev)
+{
+ struct funnel_drvdata *drvdata = dev_get_drvdata(dev);
+
+ clk_disable_unprepare(drvdata->atclk);
+ clk_disable_unprepare(drvdata->pclk);
+
+ return 0;
+}
+
+static int funnel_runtime_resume(struct device *dev)
+{
+ struct funnel_drvdata *drvdata = dev_get_drvdata(dev);
+ int ret;
+
+ ret = clk_prepare_enable(drvdata->pclk);
+ if (ret)
+ return ret;
+
+ ret = clk_prepare_enable(drvdata->atclk);
+ if (ret)
+ clk_disable_unprepare(drvdata->pclk);
+
+ return ret;
+}
+#endif
+
+static const struct dev_pm_ops funnel_dev_pm_ops = {
+ SET_RUNTIME_PM_OPS(funnel_runtime_suspend, funnel_runtime_resume, NULL)
+};
+
+static int funnel_platform_probe(struct platform_device *pdev)
+{
+ struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ int ret;
+
+ pm_runtime_get_noresume(&pdev->dev);
+ pm_runtime_set_active(&pdev->dev);
+ pm_runtime_enable(&pdev->dev);
+
+ ret = funnel_probe(&pdev->dev, res);
+ pm_runtime_put(&pdev->dev);
+ if (ret)
+ pm_runtime_disable(&pdev->dev);
+
+ return ret;
+}
+
+static void funnel_platform_remove(struct platform_device *pdev)
+{
+ struct funnel_drvdata *drvdata = dev_get_drvdata(&pdev->dev);
+
+ if (WARN_ON(!drvdata))
+ return;
+
+ funnel_remove(&pdev->dev);
+ pm_runtime_disable(&pdev->dev);
+}
+
+static const struct of_device_id funnel_match[] = {
+ {.compatible = "arm,coresight-static-funnel"},
+ {}
+};
+
+MODULE_DEVICE_TABLE(of, funnel_match);
+
+#ifdef CONFIG_ACPI
+static const struct acpi_device_id funnel_acpi_ids[] = {
+ {"ARMHC9FE", 0, 0, 0}, /* ARM Coresight Static Funnel */
+ {"ARMHC9FF", 0, 0, 0}, /* ARM CoreSight Dynamic Funnel */
+ {},
+};
+MODULE_DEVICE_TABLE(acpi, funnel_acpi_ids);
+#endif
+
+static struct platform_driver funnel_driver = {
+ .probe = funnel_platform_probe,
+ .remove = funnel_platform_remove,
+ .driver = {
+ .name = "coresight-funnel",
+ /* THIS_MODULE is taken care of by platform_driver_register() */
+ .of_match_table = funnel_match,
+ .acpi_match_table = ACPI_PTR(funnel_acpi_ids),
+ .pm = &funnel_dev_pm_ops,
+ .suppress_bind_attrs = true,
+ },
+};
+
+static int dynamic_funnel_probe(struct amba_device *adev,
+ const struct amba_id *id)
+{
+ int ret;
+
+ ret = funnel_probe(&adev->dev, &adev->res);
+ if (!ret)
+ pm_runtime_put(&adev->dev);
+
+ return ret;
+}
+
+static void dynamic_funnel_remove(struct amba_device *adev)
+{
+ funnel_remove(&adev->dev);
+}
+
+static const struct amba_id dynamic_funnel_ids[] = {
+ {
+ .id = 0x000bb908,
+ .mask = 0x000fffff,
+ },
+ {
+ /* Coresight SoC-600 */
+ .id = 0x000bb9eb,
+ .mask = 0x000fffff,
+ },
+ { 0, 0, NULL },
+};
+
+MODULE_DEVICE_TABLE(amba, dynamic_funnel_ids);
+
+static struct amba_driver dynamic_funnel_driver = {
+ .drv = {
+ .name = "coresight-dynamic-funnel",
+ .pm = &funnel_dev_pm_ops,
+ .suppress_bind_attrs = true,
+ },
+ .probe = dynamic_funnel_probe,
+ .remove = dynamic_funnel_remove,
+ .id_table = dynamic_funnel_ids,
+};
+
+static const struct gtrace_component_id coresight_funnel_ids[] = {
+ { .type = GTRACE_CORESIGHT_FUNNEL,
+ .version = CORESIGHT_COMPONENT_ID_VERSION, },
+ {},
+};
+
+static struct gtrace_driver coresight_funnel_driver = {
+ .id_table = coresight_funnel_ids,
+ .start_port = funnel_enable,
+ .stop_port = funnel_disable,
+ .driver = {
+ .name = "coresight-funnel",
+ },
+};
+
+static int __init funnel_init(void)
+{
+ int ret;
+
+ ret = gtrace_register_driver(&coresight_funnel_driver);
+ if (ret)
+ return ret;
+
+ ret = coresight_init_driver("funnel", &dynamic_funnel_driver, &funnel_driver);
+ if (ret) {
+ gtrace_unregister_driver(&coresight_funnel_driver);
+ return ret;
+ }
+
+ return 0;
+}
+
+static void __exit funnel_exit(void)
+{
+ coresight_remove_driver(&dynamic_funnel_driver, &funnel_driver);
+ gtrace_unregister_driver(&coresight_funnel_driver);
+}
+
+module_init(funnel_init);
+module_exit(funnel_exit);
+
+MODULE_AUTHOR("Pratik Patel <pratikp@codeaurora.org>");
+MODULE_AUTHOR("Mathieu Poirier <mathieu.poirier@linaro.org>");
+MODULE_AUTHOR("Zane Leung <liangzhen@linux.spacemit.com>");
+MODULE_DESCRIPTION("Arm CoreSight Funnel Driver");
+MODULE_LICENSE("GPL v2");
--
2.34.1
^ permalink raw reply [flat|nested] 25+ messages in thread* [PATCH 15/24] gtrace: Only copy AUX data from the path sink
2026-10-23 3:14 [RFC PATCH 00/24] Add Linux RISC-V trace support via the gtrace framework Zane Leung
` (13 preceding siblings ...)
2026-10-23 3:14 ` [PATCH 14/24] gtrace: Add CoreSight funnel driver Zane Leung
@ 2026-10-23 3:14 ` Zane Leung
2026-10-23 3:14 ` [PATCH 16/24] perf: Flag whether gtrace AUX data carries CoreSight framing Zane Leung
` (8 subsequent siblings)
23 siblings, 0 replies; 25+ messages in thread
From: Zane Leung @ 2026-10-23 3:14 UTC (permalink / raw)
To: gregkh, palmer, pjw, alexander.shishkin, suzuki.poulose,
mchitale, anup, irogers
Cc: mike.leach, james.clark, leo.yan, peterz, mingo, namhyung,
mark.rutland, jolsa, adrian.hunter, alex, atish.patra,
andrew.jones, sunilvl, liangzhen, zhuangqiubin, coresight,
linux-riscv, linux-kernel
gtrace_path_copyto_auxbuf() walked the path and copied from the first
component implementing copyto_auxbuf(). For a linksink like ETF used as
a link, this picked the wrong component. Call the sink's callback
directly via gtrace_path_sink() and pass the perf handle so the sink
can set AUX flags itself.
Signed-off-by: Zane Leung <liangzhen@linux.spacemit.com>
---
drivers/hwtracing/gtrace/gtrace-core.c | 39 ++++++++++------------
drivers/hwtracing/gtrace/gtrace-perf.c | 13 ++------
drivers/hwtracing/gtrace/rvtrace-ramsink.c | 10 ++++--
include/linux/gtrace.h | 10 +++---
4 files changed, 34 insertions(+), 38 deletions(-)
diff --git a/drivers/hwtracing/gtrace/gtrace-core.c b/drivers/hwtracing/gtrace/gtrace-core.c
index 231d06218d8e..a42b1844da29 100644
--- a/drivers/hwtracing/gtrace/gtrace-core.c
+++ b/drivers/hwtracing/gtrace/gtrace-core.c
@@ -806,14 +806,14 @@ int gtrace_path_stop(struct gtrace_path *path)
EXPORT_SYMBOL_GPL(gtrace_path_stop);
int gtrace_path_copyto_auxbuf(struct gtrace_path *path,
+ struct perf_output_handle *handle,
struct gtrace_perf_auxbuf *buf,
- size_t *bytes_copied, u64 *format)
+ size_t *bytes_copied)
{
- struct gtrace_comp_priv *sink = to_gtrace_comp_priv(gtrace_path_sink(path));
- const struct gtrace_driver *gtdrv;
- struct gtrace_component *comp;
- struct gtrace_path_node *node;
- int ret = -EOPNOTSUPP;
+ struct gtrace_component *sink = gtrace_path_sink(path);
+ struct gtrace_comp_priv *spriv = to_gtrace_comp_priv(sink);
+ const struct gtrace_driver *sinkdrv = to_gtrace_driver(sink->dev.driver);
+ int ret = 0;
unsigned long flags;
/*
@@ -823,25 +823,20 @@ int gtrace_path_copyto_auxbuf(struct gtrace_path *path,
* sink's lock also stops another path from starting the sink before copy
* completes.
*/
- raw_spin_lock_irqsave(&sink->lock, flags);
- if (sink->start_count) {
- *bytes_copied = 0;
- ret = 0;
- goto out;
- }
-
- list_for_each_entry(node, &path->comp_list, head) {
- comp = node->comp;
- gtdrv = to_gtrace_driver(comp->dev.driver);
- if (!gtdrv || !gtdrv->copyto_auxbuf)
- continue;
+ raw_spin_lock_irqsave(&spriv->lock, flags);
+ if (!spriv->start_count) {
+ if (!sinkdrv) {
+ ret = -ENODEV;
+ goto out;
+ }
- *bytes_copied = gtdrv->copyto_auxbuf(comp, buf, format);
- ret = 0;
- break;
+ if (sinkdrv->copyto_auxbuf) {
+ *bytes_copied = sinkdrv->copyto_auxbuf(sink, handle, buf);
+ ret = 0;
+ }
}
out:
- raw_spin_unlock_irqrestore(&sink->lock, flags);
+ raw_spin_unlock_irqrestore(&spriv->lock, flags);
return ret;
}
EXPORT_SYMBOL_GPL(gtrace_path_copyto_auxbuf);
diff --git a/drivers/hwtracing/gtrace/gtrace-perf.c b/drivers/hwtracing/gtrace/gtrace-perf.c
index 604b673a29d5..3c8b620df5a8 100644
--- a/drivers/hwtracing/gtrace/gtrace-perf.c
+++ b/drivers/hwtracing/gtrace/gtrace-perf.c
@@ -243,7 +243,6 @@ static void gtrace_event_stop(struct perf_event *event, int mode)
{
struct gtrace_ctxt *ctxt = this_cpu_ptr(>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;
@@ -280,17 +279,11 @@ static void gtrace_event_stop(struct perf_event *event, int mode)
if (handle->event && (mode & PERF_EF_UPDATE)) {
if (WARN_ON_ONCE(handle->event != event))
return;
- ret = gtrace_path_copyto_auxbuf(path, &event_data->buf, &size, &format);
+ ret = gtrace_path_copyto_auxbuf(path, handle, &event_data->buf, &size);
WARN_ON_ONCE(ret);
- if (READ_ONCE(handle->event)) {
- /* Tag the AUX data with the source's trace format. */
- perf_aux_output_flag(handle, format);
- if (size > handle->size) {
- size = handle->size;
- perf_aux_output_flag(handle, PERF_AUX_FLAG_TRUNCATED);
- }
+ if (READ_ONCE(handle->event))
perf_aux_output_end(handle, size);
- } else
+ else
WARN_ON(size);
}
diff --git a/drivers/hwtracing/gtrace/rvtrace-ramsink.c b/drivers/hwtracing/gtrace/rvtrace-ramsink.c
index 9588c32c5b13..e0826d3f0d60 100644
--- a/drivers/hwtracing/gtrace/rvtrace-ramsink.c
+++ b/drivers/hwtracing/gtrace/rvtrace-ramsink.c
@@ -11,6 +11,7 @@
#include <linux/minmax.h>
#include <linux/types.h>
#include <linux/sizes.h>
+#include <linux/perf_event.h>
#include <uapi/linux/perf_event.h>
#include "rvtrace.h"
@@ -147,8 +148,8 @@ static void tbuf_to_pbuf_copy(struct trace_buf *src, struct trace_buf *dst, size
}
static size_t rvtrace_ramsink_copyto_auxbuf(struct gtrace_component *comp,
- struct gtrace_perf_auxbuf *buf,
- u64 *format)
+ struct perf_output_handle *handle,
+ struct gtrace_perf_auxbuf *buf)
{
struct rvtrace_ramsink_priv *priv = dev_get_drvdata(&comp->dev);
struct trace_buf src, dst;
@@ -181,6 +182,11 @@ static size_t rvtrace_ramsink_copyto_auxbuf(struct gtrace_component *comp,
priv->prev_wp = wp;
}
+ if (bytes > handle->size) {
+ bytes = handle->size;
+ perf_aux_output_flag(handle, PERF_AUX_FLAG_TRUNCATED);
+ }
+
tbuf_to_pbuf_copy(&src, &dst, bytes);
dev_dbg(&comp->dev, "Copied %zu bytes\n", bytes);
return bytes;
diff --git a/include/linux/gtrace.h b/include/linux/gtrace.h
index e9210b0a73b5..39ae13701615 100644
--- a/include/linux/gtrace.h
+++ b/include/linux/gtrace.h
@@ -13,6 +13,7 @@
#include <linux/platform_device.h>
#include <linux/property.h>
#include <linux/types.h>
+#include <linux/perf_event.h>
#define GTRACE_PMU_ATTR_CFG_CONTEXT_SHIFT 1
#define GTRACE_PMU_ATTR_CFG_SYNCMODE_MASK GENMASK(3, 2)
@@ -363,15 +364,16 @@ struct gtrace_perf_auxbuf {
};
int gtrace_path_copyto_auxbuf(struct gtrace_path *path,
+ struct perf_output_handle *handle,
struct gtrace_perf_auxbuf *buf,
- size_t *bytes_copied, u64 *format);
+ size_t *bytes_copied);
/**
* struct gtrace_driver - Representation of a trace driver.
* @id_table: Table to match components handled by the driver.
* @copyto_auxbuf: Callback to copy data into perf AUX buffer. The driver
* reports the PMU specific trace format of the copied data
- * via @format (see PERF_AUX_FLAG_PMU_FORMAT_TYPE_MASK).
+ * via @handle (see PERF_AUX_FLAG_PMU_FORMAT_TYPE_MASK).
* @start: Callback to start tracing.
* @stop: Callback to stop tracing.
* @start_port: Callback to start a specific component port.
@@ -385,8 +387,8 @@ int gtrace_path_copyto_auxbuf(struct gtrace_path *path,
struct gtrace_driver {
const struct gtrace_component_id *id_table;
size_t (*copyto_auxbuf)(struct gtrace_component *comp,
- struct gtrace_perf_auxbuf *buf,
- u64 *format);
+ struct perf_output_handle *handle,
+ struct gtrace_perf_auxbuf *buf);
int (*start)(struct gtrace_component *comp);
int (*stop)(struct gtrace_component *comp);
int (*start_port)(struct gtrace_component *comp,
--
2.34.1
^ permalink raw reply [flat|nested] 25+ messages in thread* [PATCH 16/24] perf: Flag whether gtrace AUX data carries CoreSight framing
2026-10-23 3:14 [RFC PATCH 00/24] Add Linux RISC-V trace support via the gtrace framework Zane Leung
` (14 preceding siblings ...)
2026-10-23 3:14 ` [PATCH 15/24] gtrace: Only copy AUX data from the path sink Zane Leung
@ 2026-10-23 3:14 ` Zane Leung
2026-10-23 3:14 ` [PATCH 17/24] gtrace: Unify default sink selection across CPUs in perf setup Zane Leung
` (7 subsequent siblings)
23 siblings, 0 replies; 25+ messages in thread
From: Zane Leung @ 2026-10-23 3:14 UTC (permalink / raw)
To: gregkh, palmer, pjw, alexander.shishkin, suzuki.poulose,
mchitale, anup, irogers
Cc: mike.leach, james.clark, leo.yan, peterz, mingo, namhyung,
mark.rutland, jolsa, adrian.hunter, alex, atish.patra,
andrew.jones, sunilvl, liangzhen, zhuangqiubin, coresight,
linux-riscv, linux-kernel
Define PERF_AUX_FLAG_GTRACE_FORMAT_CORESIGHT for the gtrace format
which reports whether the data has been wrapped in CoreSight frame
formatting by a CoreSight sink.
Signed-off-by: Zane Leung <liangzhen@linux.spacemit.com>
---
include/uapi/linux/perf_event.h | 1 +
tools/include/uapi/linux/perf_event.h | 1 +
2 files changed, 2 insertions(+)
diff --git a/include/uapi/linux/perf_event.h b/include/uapi/linux/perf_event.h
index 2ac00a409a04..9d9ed2adc1d4 100644
--- a/include/uapi/linux/perf_event.h
+++ b/include/uapi/linux/perf_event.h
@@ -1315,6 +1315,7 @@ enum perf_callchain_context {
#define PERF_AUX_FLAG_GTRACE_FORMAT_UNKNOWN 0x0000
#define PERF_AUX_FLAG_GTRACE_FORMAT_ETRACE 0x0100 /* RISC-V E-trace format */
#define PERF_AUX_FLAG_GTRACE_FORMAT_NTRACE 0x0200 /* RISC-V N-Trace format */
+#define PERF_AUX_FLAG_GTRACE_FORMAT_CORESIGHT 0x0300 /* Trace with CoreSight formatted frames */
#define PERF_FLAG_FD_NO_GROUP (1UL << 0)
#define PERF_FLAG_FD_OUTPUT (1UL << 1)
diff --git a/tools/include/uapi/linux/perf_event.h b/tools/include/uapi/linux/perf_event.h
index 2ac00a409a04..9d9ed2adc1d4 100644
--- a/tools/include/uapi/linux/perf_event.h
+++ b/tools/include/uapi/linux/perf_event.h
@@ -1315,6 +1315,7 @@ enum perf_callchain_context {
#define PERF_AUX_FLAG_GTRACE_FORMAT_UNKNOWN 0x0000
#define PERF_AUX_FLAG_GTRACE_FORMAT_ETRACE 0x0100 /* RISC-V E-trace format */
#define PERF_AUX_FLAG_GTRACE_FORMAT_NTRACE 0x0200 /* RISC-V N-Trace format */
+#define PERF_AUX_FLAG_GTRACE_FORMAT_CORESIGHT 0x0300 /* Trace with CoreSight formatted frames */
#define PERF_FLAG_FD_NO_GROUP (1UL << 0)
#define PERF_FLAG_FD_OUTPUT (1UL << 1)
--
2.34.1
^ permalink raw reply [flat|nested] 25+ messages in thread* [PATCH 17/24] gtrace: Unify default sink selection across CPUs in perf setup
2026-10-23 3:14 [RFC PATCH 00/24] Add Linux RISC-V trace support via the gtrace framework Zane Leung
` (15 preceding siblings ...)
2026-10-23 3:14 ` [PATCH 16/24] perf: Flag whether gtrace AUX data carries CoreSight framing Zane Leung
@ 2026-10-23 3:14 ` Zane Leung
2026-10-23 3:14 ` [PATCH 18/24] gtrace: Add sink-specific buffer alloc/free callbacks Zane Leung
` (6 subsequent siblings)
23 siblings, 0 replies; 25+ messages in thread
From: Zane Leung @ 2026-10-23 3:14 UTC (permalink / raw)
To: gregkh, palmer, pjw, alexander.shishkin, suzuki.poulose,
mchitale, anup, irogers
Cc: mike.leach, james.clark, leo.yan, peterz, mingo, namhyung,
mark.rutland, jolsa, adrian.hunter, alex, atish.patra,
andrew.jones, sunilvl, liangzhen, zhuangqiubin, coresight,
linux-riscv, linux-kernel
In gtrace_setup_aux(), remember the sink found for the first CPU and
pass it to gtrace_create_path() for the remaining CPUs so they share a
compatible default sink. Clear a CPU from the mask when no path can be
built for it and abort when no sink is reachable at all.
---
drivers/hwtracing/gtrace/gtrace-perf.c | 17 +++++++++++++++--
1 file changed, 15 insertions(+), 2 deletions(-)
diff --git a/drivers/hwtracing/gtrace/gtrace-perf.c b/drivers/hwtracing/gtrace/gtrace-perf.c
index 3c8b620df5a8..9b874daa807f 100644
--- a/drivers/hwtracing/gtrace/gtrace-perf.c
+++ b/drivers/hwtracing/gtrace/gtrace-perf.c
@@ -109,6 +109,7 @@ static void *gtrace_setup_aux(struct perf_event *event, void **pages,
int nr_pages, bool overwrite)
{
struct gtrace_event_data *event_data = NULL;
+ struct gtrace_component *sink = NULL;
struct page **pagelist;
int cpu = event->cpu, i;
cpumask_t *mask;
@@ -120,7 +121,7 @@ static void *gtrace_setup_aux(struct perf_event *event, void **pages,
INIT_WORK(&event_data->work, free_event_data);
mask = &event_data->mask;
/*
- * Create the path for each CPU in the mask. In case of any failure skip the CPU
+ * Create the path for each CPU in the mask. In case of any failure clear the CPU
*/
for_each_cpu(cpu, mask) {
struct gtrace_component *src;
@@ -132,16 +133,28 @@ static void *gtrace_setup_aux(struct perf_event *event, void **pages,
continue;
}
- path = gtrace_create_path(src, NULL, GTRACE_COMPONENT_MODE_PERF);
+ path = gtrace_create_path(src, sink, GTRACE_COMPONENT_MODE_PERF);
if (IS_ERR(path)) {
cpumask_clear_cpu(cpu, mask);
continue;
}
+ /*
+ * The first found sink is saved here and passed to
+ * gtrace_create_path() to check whether the remaining sources
+ * have a compatible default sink.
+ */
+ if (!sink)
+ sink = gtrace_path_sink(path);
+
path->event = event;
*gtrace_event_cpu_path_ptr(event_data, cpu) = path;
}
+ /* no sink found for any CPU - cannot trace */
+ if (!sink)
+ goto err;
+
/* If we don't have any CPUs ready for tracing, abort */
cpu = cpumask_first(&event_data->mask);
if (cpu >= nr_cpu_ids)
--
2.34.1
^ permalink raw reply [flat|nested] 25+ messages in thread* [PATCH 18/24] gtrace: Add sink-specific buffer alloc/free callbacks
2026-10-23 3:14 [RFC PATCH 00/24] Add Linux RISC-V trace support via the gtrace framework Zane Leung
` (16 preceding siblings ...)
2026-10-23 3:14 ` [PATCH 17/24] gtrace: Unify default sink selection across CPUs in perf setup Zane Leung
@ 2026-10-23 3:14 ` Zane Leung
2026-10-23 3:15 ` [PATCH 19/24] gtrace: Add setup_sysfs_buf callback to setup sysfs sink buffer Zane Leung
` (5 subsequent siblings)
23 siblings, 0 replies; 25+ messages in thread
From: Zane Leung @ 2026-10-23 3:14 UTC (permalink / raw)
To: gregkh, palmer, pjw, alexander.shishkin, suzuki.poulose,
mchitale, anup, irogers
Cc: mike.leach, james.clark, leo.yan, peterz, mingo, namhyung,
mark.rutland, jolsa, adrian.hunter, alex, atish.patra,
andrew.jones, sunilvl, liangzhen, zhuangqiubin, coresight,
linux-riscv, linux-kernel
Extend gtrace_driver with alloc_buf() and free_buf() so a sink can
allocate its own buffer (e.g. an ETR SG table) during perf setup and
release it when the event data is freed. Store the resulting buffer in
gtrace_perf_auxbuf along with the event pid and snapshot mode.
Signed-off-by: Zane Leung <liangzhen@linux.spacemit.com>
---
drivers/hwtracing/gtrace/gtrace-perf.c | 34 ++++++++++++++++++++++++++
include/linux/gtrace.h | 12 +++++++++
2 files changed, 46 insertions(+)
diff --git a/drivers/hwtracing/gtrace/gtrace-perf.c b/drivers/hwtracing/gtrace/gtrace-perf.c
index 9b874daa807f..e05a833aa6a4 100644
--- a/drivers/hwtracing/gtrace/gtrace-perf.c
+++ b/drivers/hwtracing/gtrace/gtrace-perf.c
@@ -86,6 +86,27 @@ static struct gtrace_path **gtrace_event_cpu_path_ptr(struct gtrace_event_data *
return per_cpu_ptr(data->path, cpu);
}
+static void free_sink_buffer(struct gtrace_event_data *event_data)
+{
+ int cpu;
+ cpumask_t *mask = &event_data->mask;
+ struct gtrace_path *path;
+ struct gtrace_component *sink;
+ struct gtrace_driver *sinkdrv;
+
+ if (!event_data->buf.buf)
+ return;
+
+ if (WARN_ON(cpumask_empty(mask)))
+ return;
+
+ cpu = cpumask_first(mask);
+ path = *gtrace_event_cpu_path_ptr(event_data, cpu);
+ sink = gtrace_path_sink(path);
+ sinkdrv = to_gtrace_driver(sink->dev.driver);
+ sinkdrv->free_buf(event_data->buf.buf);
+}
+
static void free_event_data(struct work_struct *work)
{
struct gtrace_event_data *event_data;
@@ -95,6 +116,7 @@ static void free_event_data(struct work_struct *work)
event_data = container_of(work, struct gtrace_event_data, work);
mask = &event_data->mask;
+ free_sink_buffer(event_data);
for_each_cpu(cpu, mask) {
path = *gtrace_event_cpu_path_ptr(event_data, cpu);
gtrace_destroy_path(path);
@@ -110,6 +132,7 @@ static void *gtrace_setup_aux(struct perf_event *event, void **pages,
{
struct gtrace_event_data *event_data = NULL;
struct gtrace_component *sink = NULL;
+ struct gtrace_driver *sinkdrv = NULL;
struct page **pagelist;
int cpu = event->cpu, i;
cpumask_t *mask;
@@ -175,6 +198,17 @@ static void *gtrace_setup_aux(struct perf_event *event, void **pages,
event_data->buf.nr_pages = nr_pages;
event_data->buf.length = nr_pages * PAGE_SIZE;
event_data->buf.pos = 0;
+ event_data->buf.snapshot = overwrite;
+ event_data->buf.pages = pages;
+
+ sinkdrv = to_gtrace_driver(sink->dev.driver);
+ if (sinkdrv && sinkdrv->alloc_buf) {
+ event_data->buf.buf = sinkdrv->alloc_buf(sink, event, pages,
+ nr_pages, overwrite);
+ if (!event_data->buf.buf)
+ goto err;
+ }
+
return event_data;
err:
gtrace_free_aux(event_data);
diff --git a/include/linux/gtrace.h b/include/linux/gtrace.h
index 39ae13701615..a3d125882ec9 100644
--- a/include/linux/gtrace.h
+++ b/include/linux/gtrace.h
@@ -355,12 +355,18 @@ int gtrace_path_stop(struct gtrace_path *path);
* @nr_pages: Number of pages of the AUX buffer.
* @base: Start address of AUX buffer.
* @pos: Position in the AUX buffer to commit traced data.
+ * @snapshot: Is this run in snapshot mode.
+ * @pages: a handle to the ring buffer.
+ * @buf: Optional sink-specific buffer.
*/
struct gtrace_perf_auxbuf {
size_t length;
int nr_pages;
void *base;
long pos;
+ bool snapshot;
+ void **pages;
+ void *buf;
};
int gtrace_path_copyto_auxbuf(struct gtrace_path *path,
@@ -371,6 +377,8 @@ int gtrace_path_copyto_auxbuf(struct gtrace_path *path,
/**
* struct gtrace_driver - Representation of a trace driver.
* @id_table: Table to match components handled by the driver.
+ * @alloc_buf: Callback to alloc sink-specific buffer.
+ * @free_buf: Callback to free sink-specific buffer.
* @copyto_auxbuf: Callback to copy data into perf AUX buffer. The driver
* reports the PMU specific trace format of the copied data
* via @handle (see PERF_AUX_FLAG_PMU_FORMAT_TYPE_MASK).
@@ -386,6 +394,10 @@ int gtrace_path_copyto_auxbuf(struct gtrace_path *path,
*/
struct gtrace_driver {
const struct gtrace_component_id *id_table;
+ void *(*alloc_buf)(struct gtrace_component *comp,
+ struct perf_event *event, void **pages,
+ int nr_pages, bool overwrite);
+ void (*free_buf)(void *buf);
size_t (*copyto_auxbuf)(struct gtrace_component *comp,
struct perf_output_handle *handle,
struct gtrace_perf_auxbuf *buf);
--
2.34.1
^ permalink raw reply [flat|nested] 25+ messages in thread* [PATCH 19/24] gtrace: Add setup_sysfs_buf callback to setup sysfs sink buffer
2026-10-23 3:14 [RFC PATCH 00/24] Add Linux RISC-V trace support via the gtrace framework Zane Leung
` (17 preceding siblings ...)
2026-10-23 3:14 ` [PATCH 18/24] gtrace: Add sink-specific buffer alloc/free callbacks Zane Leung
@ 2026-10-23 3:15 ` Zane Leung
2026-10-23 3:15 ` [PATCH 20/24] gtrace: Add CoreSight TMC driver support Zane Leung
` (4 subsequent siblings)
23 siblings, 0 replies; 25+ messages in thread
From: Zane Leung @ 2026-10-23 3:15 UTC (permalink / raw)
To: gregkh, palmer, pjw, alexander.shishkin, suzuki.poulose,
mchitale, anup, irogers
Cc: mike.leach, james.clark, leo.yan, peterz, mingo, namhyung,
mark.rutland, jolsa, adrian.hunter, alex, atish.patra,
andrew.jones, sunilvl, liangzhen, zhuangqiubin, coresight,
linux-riscv, linux-kernel
Add a setup_sysfs_buf() callback to gtrace_driver and invoke it from
gtrace_enable_sysfs() before the path is started, so the sink can
allocate its sysfs trace buffer outside the spinlock held by the core
start path.
Signed-off-by: Zane Leung <liangzhen@linux.spacemit.com>
---
drivers/hwtracing/gtrace/gtrace-sysfs.c | 12 ++++++++++++
include/linux/gtrace.h | 2 ++
2 files changed, 14 insertions(+)
diff --git a/drivers/hwtracing/gtrace/gtrace-sysfs.c b/drivers/hwtracing/gtrace/gtrace-sysfs.c
index 5f4f3fcb56a4..23b0a21ef9c0 100644
--- a/drivers/hwtracing/gtrace/gtrace-sysfs.c
+++ b/drivers/hwtracing/gtrace/gtrace-sysfs.c
@@ -85,6 +85,7 @@ static int gtrace_validate_source_sysfs(struct gtrace_component *comp,
int gtrace_enable_sysfs(struct gtrace_component *comp)
{
struct gtrace_component *sink;
+ struct gtrace_driver *sinkdrv;
struct gtrace_path *path;
int ret;
@@ -114,6 +115,17 @@ int gtrace_enable_sysfs(struct gtrace_component *comp)
mutex_lock(>race_mutex);
+ if (!sink)
+ sink = gtrace_path_sink(path);
+ sinkdrv = to_gtrace_driver(sink->dev.driver);
+ if (sinkdrv && sinkdrv->setup_sysfs_buf) {
+ ret = sinkdrv->setup_sysfs_buf(sink);
+ if (ret) {
+ gtrace_destroy_path(path);
+ goto out;
+ }
+ }
+
ret = gtrace_path_start(path);
if (ret)
gtrace_destroy_path(path);
diff --git a/include/linux/gtrace.h b/include/linux/gtrace.h
index a3d125882ec9..0957d0490c57 100644
--- a/include/linux/gtrace.h
+++ b/include/linux/gtrace.h
@@ -377,6 +377,7 @@ int gtrace_path_copyto_auxbuf(struct gtrace_path *path,
/**
* struct gtrace_driver - Representation of a trace driver.
* @id_table: Table to match components handled by the driver.
+ * @setup_sysfs_buf: Callback to setup sink-specific sysfs buffer.
* @alloc_buf: Callback to alloc sink-specific buffer.
* @free_buf: Callback to free sink-specific buffer.
* @copyto_auxbuf: Callback to copy data into perf AUX buffer. The driver
@@ -394,6 +395,7 @@ int gtrace_path_copyto_auxbuf(struct gtrace_path *path,
*/
struct gtrace_driver {
const struct gtrace_component_id *id_table;
+ int (*setup_sysfs_buf)(struct gtrace_component *comp);
void *(*alloc_buf)(struct gtrace_component *comp,
struct perf_event *event, void **pages,
int nr_pages, bool overwrite);
--
2.34.1
^ permalink raw reply [flat|nested] 25+ messages in thread* [PATCH 20/24] gtrace: Add CoreSight TMC driver support
2026-10-23 3:14 [RFC PATCH 00/24] Add Linux RISC-V trace support via the gtrace framework Zane Leung
` (18 preceding siblings ...)
2026-10-23 3:15 ` [PATCH 19/24] gtrace: Add setup_sysfs_buf callback to setup sysfs sink buffer Zane Leung
@ 2026-10-23 3:15 ` Zane Leung
2026-10-23 3:15 ` [PATCH 21/24] perf tools: riscv: making gtrace PMU listable Zane Leung
` (3 subsequent siblings)
23 siblings, 0 replies; 25+ messages in thread
From: Zane Leung @ 2026-10-23 3:15 UTC (permalink / raw)
To: gregkh, palmer, pjw, alexander.shishkin, suzuki.poulose,
mchitale, anup, irogers
Cc: mike.leach, james.clark, leo.yan, peterz, mingo, namhyung,
mark.rutland, jolsa, adrian.hunter, alex, atish.patra,
andrew.jones, sunilvl, liangzhen, zhuangqiubin, coresight,
linux-riscv, linux-kernel
Add CoreSight Trace Memory Controller (TMC) driver as part of the
gtrace framework. ETB and ETF share one driver (ETF additionally
works as a link), while ETR uses system memory and provides perf
buffer alloc/free callbacks.
Signed-off-by: Zane Leung <liangzhen@linux.spacemit.com>
---
.../hwtracing/coresight/coresight-tmc-etf.c | 1 -
drivers/hwtracing/gtrace/Kconfig | 13 +
drivers/hwtracing/gtrace/Makefile | 2 +
drivers/hwtracing/gtrace/coresight-tmc-core.c | 1111 ++++++++++
drivers/hwtracing/gtrace/coresight-tmc-etf.c | 573 +++++
drivers/hwtracing/gtrace/coresight-tmc-etr.c | 1888 +++++++++++++++++
drivers/hwtracing/gtrace/coresight-tmc.h | 469 ++++
7 files changed, 4056 insertions(+), 1 deletion(-)
create mode 100644 drivers/hwtracing/gtrace/coresight-tmc-core.c
create mode 100644 drivers/hwtracing/gtrace/coresight-tmc-etf.c
create mode 100644 drivers/hwtracing/gtrace/coresight-tmc-etr.c
create mode 100644 drivers/hwtracing/gtrace/coresight-tmc.h
diff --git a/drivers/hwtracing/coresight/coresight-tmc-etf.c b/drivers/hwtracing/coresight/coresight-tmc-etf.c
index 8882b1c4cdc0..11082fd249eb 100644
--- a/drivers/hwtracing/coresight/coresight-tmc-etf.c
+++ b/drivers/hwtracing/coresight/coresight-tmc-etf.c
@@ -80,7 +80,6 @@ static void tmc_etb_dump_hw(struct tmc_drvdata *drvdata)
if (lost)
coresight_insert_barrier_packet(drvdata->buf);
- return;
}
static void __tmc_etb_disable_hw(struct tmc_drvdata *drvdata)
diff --git a/drivers/hwtracing/gtrace/Kconfig b/drivers/hwtracing/gtrace/Kconfig
index 081af9c0c670..83ee442ad860 100644
--- a/drivers/hwtracing/gtrace/Kconfig
+++ b/drivers/hwtracing/gtrace/Kconfig
@@ -114,3 +114,16 @@ config CORESIGHT_FUNNEL
To compile this driver as a module, choose M here: the module
will be called coresight-funnel.
+
+config CORESIGHT_TMC
+ tristate "CoreSight generic TMC driver"
+ depends on GTRACE_CORESIGHT
+ default y
+ help
+ This enables support for the CoreSight Trace Memory Controller
+ (TMC) driver. Depending on its configuration the device can act
+ as an embedded trace buffer (ETB), an embedded trace FIFO (ETF)
+ or a trace router (ETR) that stores trace data in system memory.
+
+ To compile this driver as a module, choose M here: the module
+ will be called coresight-tmc.
diff --git a/drivers/hwtracing/gtrace/Makefile b/drivers/hwtracing/gtrace/Makefile
index d2745c961a75..e995ba8e7fc1 100644
--- a/drivers/hwtracing/gtrace/Makefile
+++ b/drivers/hwtracing/gtrace/Makefile
@@ -13,3 +13,5 @@ obj-$(CONFIG_RVTRACE_FUNNEL) += rvtrace-funnel.o
obj-$(CONFIG_RVTRACE_ATBBRIDGE) += rvtrace-atbbridge.o
obj-$(CONFIG_GTRACE_CORESIGHT) += coresight-platform.o
obj-$(CONFIG_CORESIGHT_FUNNEL) += coresight-funnel.o
+obj-$(CONFIG_CORESIGHT_TMC) += coresight-tmc.o
+coresight-tmc-y := coresight-tmc-core.o coresight-tmc-etf.o coresight-tmc-etr.o
diff --git a/drivers/hwtracing/gtrace/coresight-tmc-core.c b/drivers/hwtracing/gtrace/coresight-tmc-core.c
new file mode 100644
index 000000000000..26572f1d3b6f
--- /dev/null
+++ b/drivers/hwtracing/gtrace/coresight-tmc-core.c
@@ -0,0 +1,1111 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2012, The Linux Foundation. All rights reserved.
+ *
+ * Description: CoreSight Trace Memory Controller driver
+ * Derived from drivers/hwtracing/coresight/coresight-tmc-core.c
+ */
+
+#include <linux/acpi.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/device.h>
+#include <linux/idr.h>
+#include <linux/io.h>
+#include <linux/iommu.h>
+#include <linux/err.h>
+#include <linux/fs.h>
+#include <linux/miscdevice.h>
+#include <linux/mutex.h>
+#include <linux/property.h>
+#include <linux/uaccess.h>
+#include <linux/slab.h>
+#include <linux/dma-mapping.h>
+#include <linux/spinlock.h>
+#include <linux/pm_runtime.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_reserved_mem.h>
+#include <linux/gtrace.h>
+#include <linux/amba/bus.h>
+#include <linux/platform_device.h>
+
+#include "coresight.h"
+#include "coresight-tmc.h"
+
+int tmc_wait_for_tmcready(struct tmc_drvdata *drvdata)
+{
+ struct gtrace_component *comp = drvdata->comp;
+ struct gtrace_platform_data *pdata = comp->pdata;
+
+ /* Ensure formatter, unformatter and hardware fifo are empty */
+ if (gtrace_poll_bit(pdata, TMC_STS, TMC_STS_TMCREADY_BIT, 1,
+ pdata->control_poll_timeout_usecs)) {
+ dev_err(&comp->dev,
+ "timeout while waiting for TMC to be Ready\n");
+ return -EBUSY;
+ }
+ return 0;
+}
+
+void tmc_flush_and_stop(struct tmc_drvdata *drvdata)
+{
+ struct gtrace_component *comp = drvdata->comp;
+ struct gtrace_platform_data *pdata = comp->pdata;
+ u32 ffcr;
+
+ ffcr = readl_relaxed(drvdata->base + TMC_FFCR);
+ ffcr |= TMC_FFCR_STOP_ON_FLUSH;
+ writel_relaxed(ffcr, drvdata->base + TMC_FFCR);
+ ffcr |= BIT(TMC_FFCR_FLUSHMAN_BIT);
+ writel_relaxed(ffcr, drvdata->base + TMC_FFCR);
+ /* Ensure flush completes */
+ if (gtrace_poll_bit(pdata, TMC_FFCR, TMC_FFCR_FLUSHMAN_BIT, 0,
+ pdata->control_poll_timeout_usecs)) {
+ dev_err(&comp->dev,
+ "timeout while waiting for completion of Manual Flush\n");
+ }
+
+ tmc_wait_for_tmcready(drvdata);
+}
+
+void tmc_enable_hw(struct tmc_drvdata *drvdata)
+{
+ writel_relaxed(TMC_CTL_CAPT_EN, drvdata->base + TMC_CTL);
+}
+
+void tmc_disable_hw(struct tmc_drvdata *drvdata)
+{
+ writel_relaxed(0x0, drvdata->base + TMC_CTL);
+}
+
+u32 tmc_get_memwidth_mask(struct tmc_drvdata *drvdata)
+{
+ u32 mask = 0;
+
+ /*
+ * When moving RRP or an offset address forward, the new values must
+ * be byte-address aligned to the width of the trace memory databus
+ * _and_ to a frame boundary (16 byte), whichever is the biggest. For
+ * example, for 32-bit, 64-bit and 128-bit wide trace memory, the four
+ * LSBs must be 0s. For 256-bit wide trace memory, the five LSBs must
+ * be 0s.
+ */
+ switch (drvdata->memwidth) {
+ case TMC_MEM_INTF_WIDTH_32BITS:
+ case TMC_MEM_INTF_WIDTH_64BITS:
+ case TMC_MEM_INTF_WIDTH_128BITS:
+ mask = GENMASK(31, 4);
+ break;
+ case TMC_MEM_INTF_WIDTH_256BITS:
+ mask = GENMASK(31, 5);
+ break;
+ }
+
+ return mask;
+}
+
+static bool is_tmc_crashdata_valid(struct tmc_drvdata *drvdata)
+{
+ struct tmc_crash_metadata *mdata;
+
+ if (!tmc_has_reserved_buffer(drvdata) ||
+ !tmc_has_crash_mdata_buffer(drvdata))
+ return false;
+
+ mdata = drvdata->crash_mdata.vaddr;
+
+ /* Check version match */
+ if (mdata->version != CS_CRASHDATA_VERSION)
+ return false;
+
+ /* Check for valid metadata */
+ if (!mdata->valid) {
+ dev_dbg(&drvdata->comp->dev,
+ "Data invalid in tmc crash metadata\n");
+ return false;
+ }
+
+ /*
+ * Buffer address given by metadata for retrieval of trace data
+ * from previous boot is expected to be same as the reserved
+ * trace buffer memory region provided through DTS
+ */
+ if (drvdata->resrv_buf.paddr != mdata->trace_paddr) {
+ dev_dbg(&drvdata->comp->dev,
+ "Trace buffer address of previous boot invalid\n");
+ return false;
+ }
+
+ /* Check data integrity of metadata */
+ if (mdata->crc32_mdata != find_crash_metadata_crc(mdata)) {
+ dev_err(&drvdata->comp->dev,
+ "CRC mismatch in tmc crash metadata\n");
+ return false;
+ }
+ /* Check data integrity of tracedata */
+ if (mdata->crc32_tdata != find_crash_tracedata_crc(drvdata, mdata)) {
+ dev_err(&drvdata->comp->dev,
+ "CRC mismatch in tmc crash tracedata\n");
+ return false;
+ }
+
+ return true;
+}
+
+static inline ssize_t tmc_get_resvbuf_trace(struct tmc_drvdata *drvdata,
+ loff_t pos, size_t len, char **bufpp)
+{
+ s64 offset;
+ ssize_t actual = len;
+ struct tmc_resrv_buf *rbuf = &drvdata->resrv_buf;
+
+ if (pos + actual > rbuf->len)
+ actual = rbuf->len - pos;
+ if (actual <= 0)
+ return 0;
+
+ /* Compute the offset from which we read the data */
+ offset = rbuf->offset + pos;
+ if (offset >= rbuf->size)
+ offset -= rbuf->size;
+
+ /* Adjust the length to limit this transaction to end of buffer */
+ actual = (actual < (rbuf->size - offset)) ?
+ actual : rbuf->size - offset;
+
+ *bufpp = (char *)rbuf->vaddr + offset;
+
+ return actual;
+}
+
+static int tmc_prepare_crashdata(struct tmc_drvdata *drvdata)
+{
+ char *bufp;
+ ssize_t len;
+ u32 status, size;
+ u64 rrp, rwp, dba;
+ struct tmc_resrv_buf *rbuf;
+ struct tmc_crash_metadata *mdata;
+
+ mdata = drvdata->crash_mdata.vaddr;
+ rbuf = &drvdata->resrv_buf;
+
+ rrp = mdata->tmc_rrp;
+ rwp = mdata->tmc_rwp;
+ dba = mdata->tmc_dba;
+ status = mdata->tmc_sts;
+ size = mdata->tmc_ram_size << 2;
+
+ /* Sync the buffer pointers */
+ rbuf->offset = rrp - dba;
+ if (status & TMC_STS_FULL)
+ rbuf->len = size;
+ else
+ rbuf->len = rwp - rrp;
+
+ /* Additional sanity checks for validating metadata */
+ if ((rbuf->offset > size) ||
+ (rbuf->len > size)) {
+ dev_dbg(&drvdata->comp->dev,
+ "Offset and length invalid in tmc crash metadata\n");
+ return -EINVAL;
+ }
+
+ if (status & TMC_STS_FULL) {
+ len = tmc_get_resvbuf_trace(drvdata, 0x0,
+ CORESIGHT_BARRIER_PKT_SIZE, &bufp);
+ if (len >= CORESIGHT_BARRIER_PKT_SIZE) {
+ coresight_insert_barrier_packet(bufp);
+ /* Recalculate crc */
+ mdata->crc32_tdata = find_crash_tracedata_crc(drvdata,
+ mdata);
+ mdata->crc32_mdata = find_crash_metadata_crc(mdata);
+ }
+ }
+
+ return 0;
+}
+
+static int tmc_read_prepare(struct tmc_drvdata *drvdata)
+{
+ int ret = 0;
+
+ switch (drvdata->config_type) {
+ case TMC_CONFIG_TYPE_ETB:
+ case TMC_CONFIG_TYPE_ETF:
+ ret = tmc_read_prepare_etb(drvdata);
+ break;
+ case TMC_CONFIG_TYPE_ETR:
+ ret = tmc_read_prepare_etr(drvdata);
+ break;
+ default:
+ ret = -EINVAL;
+ }
+
+ if (!ret)
+ dev_dbg(&drvdata->comp->dev, "TMC read start\n");
+
+ return ret;
+}
+
+static int tmc_read_unprepare(struct tmc_drvdata *drvdata)
+{
+ int ret = 0;
+
+ switch (drvdata->config_type) {
+ case TMC_CONFIG_TYPE_ETB:
+ case TMC_CONFIG_TYPE_ETF:
+ ret = tmc_read_unprepare_etb(drvdata);
+ break;
+ case TMC_CONFIG_TYPE_ETR:
+ ret = tmc_read_unprepare_etr(drvdata);
+ break;
+ default:
+ ret = -EINVAL;
+ }
+
+ if (!ret)
+ dev_dbg(&drvdata->comp->dev, "TMC read end\n");
+
+ return ret;
+}
+
+static int tmc_open(struct inode *inode, struct file *file)
+{
+ int ret;
+ struct tmc_drvdata *drvdata = container_of(file->private_data,
+ struct tmc_drvdata, miscdev);
+
+ ret = tmc_read_prepare(drvdata);
+ if (ret)
+ return ret;
+
+ nonseekable_open(inode, file);
+
+ dev_dbg(&drvdata->comp->dev, "%s: successfully opened\n", __func__);
+ return 0;
+}
+
+static ssize_t tmc_get_sysfs_trace(struct tmc_drvdata *drvdata, loff_t pos, size_t len,
+ char **bufpp)
+{
+ switch (drvdata->config_type) {
+ case TMC_CONFIG_TYPE_ETB:
+ case TMC_CONFIG_TYPE_ETF:
+ return tmc_etb_get_sysfs_trace(drvdata, pos, len, bufpp);
+ case TMC_CONFIG_TYPE_ETR:
+ return tmc_etr_get_sysfs_trace(drvdata, pos, len, bufpp);
+ }
+
+ return -EINVAL;
+}
+
+static ssize_t tmc_read(struct file *file, char __user *data, size_t len,
+ loff_t *ppos)
+{
+ char *bufp;
+ ssize_t actual;
+ struct tmc_drvdata *drvdata = container_of(file->private_data,
+ struct tmc_drvdata, miscdev);
+ actual = tmc_get_sysfs_trace(drvdata, *ppos, len, &bufp);
+ if (actual <= 0)
+ return 0;
+
+ if (copy_to_user(data, bufp, actual)) {
+ dev_dbg(&drvdata->comp->dev,
+ "%s: copy_to_user failed\n", __func__);
+ return -EFAULT;
+ }
+
+ *ppos += actual;
+ dev_dbg(&drvdata->comp->dev, "%zu bytes copied\n", actual);
+
+ return actual;
+}
+
+static int tmc_release(struct inode *inode, struct file *file)
+{
+ int ret;
+ struct tmc_drvdata *drvdata = container_of(file->private_data,
+ struct tmc_drvdata, miscdev);
+
+ ret = tmc_read_unprepare(drvdata);
+ if (ret)
+ return ret;
+
+ dev_dbg(&drvdata->comp->dev, "%s: released\n", __func__);
+ return 0;
+}
+
+static const struct file_operations tmc_fops = {
+ .owner = THIS_MODULE,
+ .open = tmc_open,
+ .read = tmc_read,
+ .release = tmc_release,
+};
+
+static int tmc_crashdata_open(struct inode *inode, struct file *file)
+{
+ int err = 0;
+ unsigned long flags;
+ struct tmc_resrv_buf *rbuf;
+ struct tmc_crash_metadata *mdata;
+ struct tmc_drvdata *drvdata = container_of(file->private_data,
+ struct tmc_drvdata,
+ crashdev);
+
+ mdata = drvdata->crash_mdata.vaddr;
+ rbuf = &drvdata->resrv_buf;
+
+ raw_spin_lock_irqsave(&drvdata->spinlock, flags);
+ if (mdata->valid)
+ rbuf->reading = true;
+ else
+ err = -ENOENT;
+ raw_spin_unlock_irqrestore(&drvdata->spinlock, flags);
+ if (err)
+ goto exit;
+
+ nonseekable_open(inode, file);
+ dev_dbg(&drvdata->comp->dev, "%s: successfully opened\n", __func__);
+exit:
+ return err;
+}
+
+static ssize_t tmc_crashdata_read(struct file *file, char __user *data,
+ size_t len, loff_t *ppos)
+{
+ char *bufp;
+ ssize_t actual;
+ struct tmc_drvdata *drvdata = container_of(file->private_data,
+ struct tmc_drvdata,
+ crashdev);
+
+ actual = tmc_get_resvbuf_trace(drvdata, *ppos, len, &bufp);
+ if (actual <= 0)
+ return 0;
+
+ if (copy_to_user(data, bufp, actual)) {
+ dev_dbg(&drvdata->comp->dev,
+ "%s: copy_to_user failed\n", __func__);
+ return -EFAULT;
+ }
+
+ *ppos += actual;
+ dev_dbg(&drvdata->comp->dev, "%zu bytes copied\n", actual);
+
+ return actual;
+}
+
+static int tmc_crashdata_release(struct inode *inode, struct file *file)
+{
+ unsigned long flags;
+ struct tmc_resrv_buf *rbuf;
+ struct tmc_drvdata *drvdata = container_of(file->private_data,
+ struct tmc_drvdata,
+ crashdev);
+
+ rbuf = &drvdata->resrv_buf;
+ raw_spin_lock_irqsave(&drvdata->spinlock, flags);
+ rbuf->reading = false;
+ raw_spin_unlock_irqrestore(&drvdata->spinlock, flags);
+
+ dev_dbg(&drvdata->comp->dev, "%s: released\n", __func__);
+ return 0;
+}
+
+static const struct file_operations tmc_crashdata_fops = {
+ .owner = THIS_MODULE,
+ .open = tmc_crashdata_open,
+ .read = tmc_crashdata_read,
+ .release = tmc_crashdata_release,
+};
+
+static enum tmc_mem_intf_width tmc_get_memwidth(u32 devid)
+{
+ enum tmc_mem_intf_width memwidth;
+
+ /*
+ * Excerpt from the TRM:
+ *
+ * DEVID::MEMWIDTH[10:8]
+ * 0x2 Memory interface databus is 32 bits wide.
+ * 0x3 Memory interface databus is 64 bits wide.
+ * 0x4 Memory interface databus is 128 bits wide.
+ * 0x5 Memory interface databus is 256 bits wide.
+ */
+ switch (BMVAL(devid, 8, 10)) {
+ case 0x2:
+ memwidth = TMC_MEM_INTF_WIDTH_32BITS;
+ break;
+ case 0x3:
+ memwidth = TMC_MEM_INTF_WIDTH_64BITS;
+ break;
+ case 0x4:
+ memwidth = TMC_MEM_INTF_WIDTH_128BITS;
+ break;
+ case 0x5:
+ memwidth = TMC_MEM_INTF_WIDTH_256BITS;
+ break;
+ default:
+ memwidth = 0;
+ }
+
+ return memwidth;
+}
+
+static struct attribute *coresight_tmc_mgmt_attrs[] = {
+ gtrace_simple_reg32(rsz, TMC_RSZ),
+ gtrace_simple_reg32(sts, TMC_STS),
+ gtrace_simple_reg64(rrp, TMC_RRP, TMC_RRPHI),
+ gtrace_simple_reg64(rwp, TMC_RWP, TMC_RWPHI),
+ gtrace_simple_reg32(trg, TMC_TRG),
+ gtrace_simple_reg32(ctl, TMC_CTL),
+ gtrace_simple_reg32(ffsr, TMC_FFSR),
+ gtrace_simple_reg32(ffcr, TMC_FFCR),
+ gtrace_simple_reg32(mode, TMC_MODE),
+ gtrace_simple_reg32(pscr, TMC_PSCR),
+ gtrace_simple_reg32(devid, CORESIGHT_DEVID),
+ gtrace_simple_reg64(dba, TMC_DBALO, TMC_DBAHI),
+ gtrace_simple_reg32(axictl, TMC_AXICTL),
+ gtrace_simple_reg32(authstatus, TMC_AUTHSTATUS),
+ NULL,
+};
+
+static ssize_t trigger_cntr_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct tmc_drvdata *drvdata = dev_get_drvdata(dev->parent);
+ unsigned long val = drvdata->trigger_cntr;
+
+ return sprintf(buf, "%#lx\n", val);
+}
+
+static ssize_t trigger_cntr_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ int ret;
+ unsigned long val;
+ struct tmc_drvdata *drvdata = dev_get_drvdata(dev->parent);
+
+ ret = kstrtoul(buf, 16, &val);
+ if (ret)
+ return ret;
+
+ drvdata->trigger_cntr = val;
+ return size;
+}
+static DEVICE_ATTR_RW(trigger_cntr);
+
+static ssize_t buffer_size_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct tmc_drvdata *drvdata = dev_get_drvdata(dev->parent);
+
+ return sprintf(buf, "%#x\n", drvdata->size);
+}
+
+static ssize_t buffer_size_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ int ret;
+ unsigned long val;
+ struct tmc_drvdata *drvdata = dev_get_drvdata(dev->parent);
+
+ /* Only permitted for TMC-ETRs */
+ if (drvdata->config_type != TMC_CONFIG_TYPE_ETR)
+ return -EPERM;
+
+ ret = kstrtoul(buf, 0, &val);
+ if (ret)
+ return ret;
+ /* The buffer size should be page aligned */
+ if (val & (PAGE_SIZE - 1))
+ return -EINVAL;
+ drvdata->size = val;
+ return size;
+}
+
+static DEVICE_ATTR_RW(buffer_size);
+
+static ssize_t stop_on_flush_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct tmc_drvdata *drvdata = dev_get_drvdata(dev->parent);
+
+ return sprintf(buf, "%#x\n", drvdata->stop_on_flush);
+}
+
+static ssize_t stop_on_flush_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ int ret;
+ u8 val;
+ struct tmc_drvdata *drvdata = dev_get_drvdata(dev->parent);
+
+ ret = kstrtou8(buf, 0, &val);
+ if (ret)
+ return ret;
+ if (val)
+ drvdata->stop_on_flush = true;
+ else
+ drvdata->stop_on_flush = false;
+
+ return size;
+}
+
+static DEVICE_ATTR_RW(stop_on_flush);
+
+
+static struct attribute *coresight_tmc_attrs[] = {
+ &dev_attr_trigger_cntr.attr,
+ &dev_attr_buffer_size.attr,
+ &dev_attr_stop_on_flush.attr,
+ NULL,
+};
+
+static const struct attribute_group coresight_tmc_group = {
+ .attrs = coresight_tmc_attrs,
+};
+
+static const struct attribute_group coresight_tmc_mgmt_group = {
+ .attrs = coresight_tmc_mgmt_attrs,
+ .name = "mgmt",
+};
+
+static const struct attribute_group *coresight_etf_groups[] = {
+ &coresight_tmc_group,
+ &coresight_tmc_mgmt_group,
+ NULL,
+};
+
+static const struct attribute_group *coresight_etr_groups[] = {
+ &coresight_etr_group,
+ &coresight_tmc_group,
+ &coresight_tmc_mgmt_group,
+ NULL,
+};
+
+static bool tmc_etr_can_use_sg(struct device *dev)
+{
+ int ret;
+ u8 val_u8;
+
+ /*
+ * Presence of the property 'arm,scatter-gather' is checked
+ * on the platform for the feature support, rather than its
+ * value.
+ */
+ if (is_of_node(dev->fwnode)) {
+ return fwnode_property_present(dev->fwnode, "arm,scatter-gather");
+ } else if (is_acpi_device_node(dev->fwnode)) {
+ /*
+ * TMC_DEVID_NOSCAT test in tmc_etr_setup_caps(), has already ensured
+ * this property is only checked for Coresight SoC 400 TMC configured
+ * as ETR.
+ */
+ ret = fwnode_property_read_u8(dev->fwnode, "arm-armhc97c-sg-enable", &val_u8);
+ if (!ret)
+ return !!val_u8;
+
+ if (fwnode_property_present(dev->fwnode, "arm,scatter-gather")) {
+ pr_warn_once("Deprecated ACPI property - arm,scatter-gather\n");
+ return true;
+ }
+ }
+ return false;
+}
+
+static bool tmc_etr_has_non_secure_access(struct tmc_drvdata *drvdata)
+{
+ u32 auth = readl_relaxed(drvdata->base + TMC_AUTHSTATUS);
+
+ return (auth & TMC_AUTH_NSID_MASK) == 0x3;
+}
+
+static const struct amba_id tmc_ids[];
+
+static int of_tmc_get_reserved_resource_by_name(struct device *dev,
+ const char *name,
+ struct resource *res)
+{
+ int rc = -ENODEV;
+
+ rc = of_reserved_mem_region_to_resource_byname(dev->of_node, name, res);
+ if (rc < 0)
+ return rc;
+
+ if (res->start == 0 || resource_size(res) == 0)
+ rc = -ENODEV;
+
+ return rc;
+}
+
+static void tmc_get_reserved_region(struct device *parent)
+{
+ struct tmc_drvdata *drvdata = dev_get_drvdata(parent);
+ struct resource res;
+
+ if (of_tmc_get_reserved_resource_by_name(parent, "tracedata", &res))
+ return;
+
+ drvdata->resrv_buf.vaddr = memremap(res.start,
+ resource_size(&res),
+ MEMREMAP_WC);
+ if (IS_ERR_OR_NULL(drvdata->resrv_buf.vaddr)) {
+ dev_err(parent, "Reserved trace buffer mapping failed\n");
+ return;
+ }
+
+ drvdata->resrv_buf.paddr = res.start;
+ drvdata->resrv_buf.size = resource_size(&res);
+
+ if (of_tmc_get_reserved_resource_by_name(parent, "metadata", &res))
+ return;
+
+ drvdata->crash_mdata.vaddr = memremap(res.start,
+ resource_size(&res),
+ MEMREMAP_WC);
+ if (IS_ERR_OR_NULL(drvdata->crash_mdata.vaddr)) {
+ dev_err(parent, "Metadata memory mapping failed\n");
+ return;
+ }
+
+ drvdata->crash_mdata.paddr = res.start;
+ drvdata->crash_mdata.size = resource_size(&res);
+}
+
+/* Detect and initialise the capabilities of a TMC ETR */
+static int tmc_etr_setup_caps(struct device *parent, u32 devid,
+ struct gtrace_platform_data *pdata)
+{
+ int rc;
+ u32 tmc_pid, dma_mask = 0;
+ struct tmc_drvdata *drvdata = dev_get_drvdata(parent);
+ void *dev_caps;
+
+ if (!tmc_etr_has_non_secure_access(drvdata))
+ return -EACCES;
+
+ tmc_pid = coresight_get_pid(pdata);
+ dev_caps = coresight_get_uci_data_from_amba(tmc_ids, tmc_pid);
+
+ /* Set the unadvertised capabilities */
+ tmc_etr_init_caps(drvdata, (u32)(unsigned long)dev_caps);
+
+ if (!(devid & TMC_DEVID_NOSCAT) && tmc_etr_can_use_sg(parent))
+ tmc_etr_set_cap(drvdata, TMC_ETR_SG);
+
+ /* Check if the AXI address width is available */
+ if (devid & TMC_DEVID_AXIAW_VALID)
+ dma_mask = ((devid >> TMC_DEVID_AXIAW_SHIFT) &
+ TMC_DEVID_AXIAW_MASK);
+
+ /*
+ * Unless specified in the device configuration, ETR uses a 40-bit
+ * AXI master in place of the embedded SRAM of ETB/ETF.
+ */
+ switch (dma_mask) {
+ case 32:
+ case 40:
+ case 44:
+ case 48:
+ case 52:
+ dev_info(parent, "Detected dma mask %dbits\n", dma_mask);
+ break;
+ default:
+ dma_mask = 40;
+ }
+
+ rc = dma_set_mask_and_coherent(parent, DMA_BIT_MASK(dma_mask));
+ if (rc)
+ dev_err(parent, "Failed to setup DMA mask: %d\n", rc);
+ return rc;
+}
+
+static u32 tmc_etr_get_default_buffer_size(struct device *dev)
+{
+ u32 size;
+
+ if (fwnode_property_read_u32(dev->fwnode, "arm,buffer-size", &size))
+ size = SZ_1M;
+ return size;
+}
+
+static u32 tmc_etr_get_max_burst_size(struct device *dev)
+{
+ u32 burst_size;
+
+ if (fwnode_property_read_u32(dev->fwnode, "arm,max-burst-size",
+ &burst_size))
+ return TMC_AXICTL_WR_BURST_16;
+
+ /* Only permissible values are 0 to 15 */
+ if (burst_size > 0xF)
+ burst_size = TMC_AXICTL_WR_BURST_16;
+
+ return burst_size;
+}
+
+static void register_crash_dev_interface(struct tmc_drvdata *drvdata,
+ const char *name)
+{
+ drvdata->crashdev.name =
+ devm_kasprintf(&drvdata->comp->dev, GFP_KERNEL, "%s_%s", "crash", name);
+ drvdata->crashdev.minor = MISC_DYNAMIC_MINOR;
+ drvdata->crashdev.fops = &tmc_crashdata_fops;
+ if (misc_register(&drvdata->crashdev)) {
+ dev_dbg(&drvdata->comp->dev,
+ "Failed to setup user interface for crashdata\n");
+ drvdata->crashdev.fops = NULL;
+ } else
+ dev_info(&drvdata->comp->dev,
+ "Valid crash tracedata found\n");
+}
+
+static int __tmc_probe(struct device *dev, struct resource *res)
+{
+ int ret = 0;
+ u32 devid;
+ void __iomem *base;
+ struct gtrace_platform_data *pdata = NULL;
+ struct tmc_drvdata *drvdata;
+ struct gtrace_component_id id = {
+ .version = CORESIGHT_COMPONENT_ID_VERSION,
+ };
+ const char *comp_name = NULL;
+
+ drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL);
+ if (!drvdata)
+ return -ENOMEM;
+
+ dev_set_drvdata(dev, drvdata);
+
+ ret = coresight_get_enable_clocks(dev, &drvdata->pclk, &drvdata->atclk);
+ if (ret)
+ return ret;
+
+ ret = -ENOMEM;
+
+ /* Validity for the resource is already checked by the AMBA core */
+ base = devm_ioremap_resource(dev, res);
+ if (IS_ERR(base)) {
+ ret = PTR_ERR(base);
+ goto out;
+ }
+
+ drvdata->base = base;
+
+ raw_spin_lock_init(&drvdata->spinlock);
+
+ devid = readl_relaxed(drvdata->base + CORESIGHT_DEVID);
+ drvdata->config_type = BMVAL(devid, 6, 7);
+ drvdata->memwidth = tmc_get_memwidth(devid);
+ drvdata->etr_mode = ETR_MODE_AUTO;
+
+ if (drvdata->config_type == TMC_CONFIG_TYPE_ETR) {
+ drvdata->size = tmc_etr_get_default_buffer_size(dev);
+ drvdata->max_burst_size = tmc_etr_get_max_burst_size(dev);
+ } else {
+ drvdata->size = readl_relaxed(drvdata->base + TMC_RSZ) * 4;
+ }
+
+ tmc_get_reserved_region(dev);
+
+ pdata = coresight_get_platform_data(dev);
+ if (IS_ERR(pdata)) {
+ ret = PTR_ERR(pdata);
+ goto out;
+ }
+ pdata->io_mem = true;
+ pdata->base = base;
+
+ switch (drvdata->config_type) {
+ case TMC_CONFIG_TYPE_ETB:
+ pdata->groups = coresight_etf_groups;
+ id.type = GTRACE_CORESIGHT_TMC_ETB;
+ comp_name = "tmc_etb";
+ break;
+ case TMC_CONFIG_TYPE_ETR:
+ pdata->groups = coresight_etr_groups;
+ id.type = GTRACE_CORESIGHT_TMC_ETR;
+ ret = tmc_etr_setup_caps(dev, devid, pdata);
+ if (ret)
+ goto out;
+ idr_init(&drvdata->idr);
+ mutex_init(&drvdata->idr_mutex);
+ comp_name = "tmc_etr";
+ break;
+ case TMC_CONFIG_TYPE_ETF:
+ pdata->is_linksink = true;
+ pdata->groups = coresight_etf_groups;
+ id.type = GTRACE_CORESIGHT_TMC_ETF;
+ comp_name = "tmc_etf";
+ break;
+ default:
+ pr_err("%u: Unsupported TMC config\n", drvdata->config_type);
+ ret = -EINVAL;
+ goto out;
+ }
+
+ coresight_clear_self_claim_tag(pdata);
+
+ drvdata->comp = gtrace_register_component(&id, comp_name, pdata);
+ if (IS_ERR(drvdata->comp)) {
+ ret = PTR_ERR(drvdata->comp);
+ goto out;
+ }
+
+ drvdata->miscdev.name = dev_name(&drvdata->comp->dev);
+ drvdata->miscdev.minor = MISC_DYNAMIC_MINOR;
+ drvdata->miscdev.fops = &tmc_fops;
+ ret = misc_register(&drvdata->miscdev);
+ if (ret) {
+ gtrace_unregister_component(drvdata->comp);
+ goto out;
+ }
+
+out:
+ if (is_tmc_crashdata_valid(drvdata) &&
+ !tmc_prepare_crashdata(drvdata))
+ register_crash_dev_interface(drvdata, dev_name(&drvdata->comp->dev));
+ return ret;
+}
+
+static int tmc_probe(struct amba_device *adev, const struct amba_id *id)
+{
+ int ret;
+
+ ret = __tmc_probe(&adev->dev, &adev->res);
+ if (!ret)
+ pm_runtime_put(&adev->dev);
+
+ return ret;
+}
+
+static void tmc_shutdown(struct amba_device *adev)
+{
+ unsigned long flags;
+ struct tmc_drvdata *drvdata = amba_get_drvdata(adev);
+
+ raw_spin_lock_irqsave(&drvdata->spinlock, flags);
+
+ if (gtrace_get_mode(drvdata->comp) == GTRACE_COMPONENT_MODE_DISABLED)
+ goto out;
+
+ if (drvdata->config_type == TMC_CONFIG_TYPE_ETR)
+ tmc_etr_disable_hw(drvdata);
+
+ /*
+ * We do not care about coresight unregister here unlike remove
+ * callback which is required for making coresight modular since
+ * the system is going down after this.
+ */
+out:
+ raw_spin_unlock_irqrestore(&drvdata->spinlock, flags);
+}
+
+static void __tmc_remove(struct device *dev)
+{
+ struct tmc_drvdata *drvdata = dev_get_drvdata(dev);
+
+ /*
+ * Since misc_open() holds a refcount on the f_ops, which is
+ * etb fops in this case, device is there until last file
+ * handler to this device is closed.
+ */
+ misc_deregister(&drvdata->miscdev);
+ if (drvdata->crashdev.fops)
+ misc_deregister(&drvdata->crashdev);
+ gtrace_unregister_component(drvdata->comp);
+}
+
+static void tmc_remove(struct amba_device *adev)
+{
+ __tmc_remove(&adev->dev);
+}
+
+static const struct amba_id tmc_ids[] = {
+ CS_AMBA_ID(0x000bb961),
+ /* Coresight SoC 600 TMC-ETR/ETS */
+ CS_AMBA_ID_DATA(0x000bb9e8, (unsigned long)CORESIGHT_SOC_600_ETR_CAPS),
+ /* Coresight SoC 600 TMC-ETB */
+ CS_AMBA_ID(0x000bb9e9),
+ /* Coresight SoC 600 TMC-ETF */
+ CS_AMBA_ID(0x000bb9ea),
+ { 0, 0, NULL },
+};
+
+MODULE_DEVICE_TABLE(amba, tmc_ids);
+
+static struct amba_driver tmc_driver = {
+ .drv = {
+ .name = "coresight-tmc",
+ .suppress_bind_attrs = true,
+ },
+ .probe = tmc_probe,
+ .shutdown = tmc_shutdown,
+ .remove = tmc_remove,
+ .id_table = tmc_ids,
+};
+
+static int tmc_platform_probe(struct platform_device *pdev)
+{
+ struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ int ret = 0;
+
+ pm_runtime_get_noresume(&pdev->dev);
+ pm_runtime_set_active(&pdev->dev);
+ pm_runtime_enable(&pdev->dev);
+
+ ret = __tmc_probe(&pdev->dev, res);
+ pm_runtime_put(&pdev->dev);
+ if (ret)
+ pm_runtime_disable(&pdev->dev);
+
+ return ret;
+}
+
+static void tmc_platform_remove(struct platform_device *pdev)
+{
+ struct tmc_drvdata *drvdata = dev_get_drvdata(&pdev->dev);
+
+ if (WARN_ON(!drvdata))
+ return;
+
+ __tmc_remove(&pdev->dev);
+ pm_runtime_disable(&pdev->dev);
+}
+
+#ifdef CONFIG_PM
+static int tmc_runtime_suspend(struct device *dev)
+{
+ struct tmc_drvdata *drvdata = dev_get_drvdata(dev);
+
+ clk_disable_unprepare(drvdata->atclk);
+ clk_disable_unprepare(drvdata->pclk);
+
+ return 0;
+}
+
+static int tmc_runtime_resume(struct device *dev)
+{
+ struct tmc_drvdata *drvdata = dev_get_drvdata(dev);
+ int ret;
+
+ ret = clk_prepare_enable(drvdata->pclk);
+ if (ret)
+ return ret;
+
+ ret = clk_prepare_enable(drvdata->atclk);
+ if (ret)
+ clk_disable_unprepare(drvdata->pclk);
+
+ return ret;
+}
+#endif
+
+static const struct dev_pm_ops tmc_dev_pm_ops = {
+ SET_RUNTIME_PM_OPS(tmc_runtime_suspend, tmc_runtime_resume, NULL)
+};
+
+#ifdef CONFIG_ACPI
+static const struct acpi_device_id tmc_acpi_ids[] = {
+ {"ARMHC501", 0, 0, 0}, /* ARM CoreSight ETR */
+ {"ARMHC97C", 0, 0, 0}, /* ARM CoreSight SoC-400 TMC, SoC-600 ETF/ETB */
+ {},
+};
+MODULE_DEVICE_TABLE(acpi, tmc_acpi_ids);
+#endif
+
+static struct platform_driver tmc_platform_driver = {
+ .probe = tmc_platform_probe,
+ .remove = tmc_platform_remove,
+ .driver = {
+ .name = "coresight-tmc-platform",
+ .acpi_match_table = ACPI_PTR(tmc_acpi_ids),
+ .suppress_bind_attrs = true,
+ .pm = &tmc_dev_pm_ops,
+ },
+};
+
+static const struct gtrace_component_id tmc_etr_ids[] = {
+ { .type = GTRACE_CORESIGHT_TMC_ETR,
+ .version = CORESIGHT_COMPONENT_ID_VERSION, },
+ {},
+};
+
+static struct gtrace_driver tmc_etr_driver = {
+ .id_table = tmc_etr_ids,
+ .setup_sysfs_buf = tmc_etr_setup_sysfs_buf,
+ .alloc_buf = tmc_alloc_etr_buffer,
+ .free_buf = tmc_free_etr_buffer,
+ .copyto_auxbuf = tmc_update_etr_buffer,
+ .start = tmc_enable_etr_sink,
+ .stop = tmc_disable_etr_sink,
+ .driver = {
+ .name = "coresight-tmc-etr"
+ },
+};
+
+static const struct gtrace_component_id tmc_etf_ids[] = {
+ { .type = GTRACE_CORESIGHT_TMC_ETB,
+ .version = CORESIGHT_COMPONENT_ID_VERSION, },
+ { .type = GTRACE_CORESIGHT_TMC_ETF,
+ .version = CORESIGHT_COMPONENT_ID_VERSION, },
+ {},
+};
+
+static struct gtrace_driver tmc_etf_driver = {
+ .id_table = tmc_etf_ids,
+ .setup_sysfs_buf = tmc_etf_setup_sysfs_buf,
+ .copyto_auxbuf = tmc_update_etf_buffer,
+ .start = tmc_enable_etf,
+ .stop = tmc_disable_etf,
+ .driver = {
+ .name = "coresight-tmc-etf"
+ },
+};
+
+static int __init tmc_init(void)
+{
+ int ret;
+
+ ret = gtrace_register_driver(&tmc_etr_driver);
+ if (ret)
+ return ret;
+
+ ret = gtrace_register_driver(&tmc_etf_driver);
+ if (ret)
+ goto err_etr;
+
+ ret = coresight_init_driver("tmc", &tmc_driver, &tmc_platform_driver);
+ if (ret)
+ goto err_etf;
+
+ return 0;
+
+err_etf:
+ gtrace_unregister_driver(&tmc_etf_driver);
+err_etr:
+ gtrace_unregister_driver(&tmc_etr_driver);
+ return ret;
+}
+
+static void __exit tmc_exit(void)
+{
+ coresight_remove_driver(&tmc_driver, &tmc_platform_driver);
+ gtrace_unregister_driver(&tmc_etf_driver);
+ gtrace_unregister_driver(&tmc_etr_driver);
+}
+module_init(tmc_init);
+module_exit(tmc_exit);
+
+MODULE_AUTHOR("Pratik Patel <pratikp@codeaurora.org>");
+MODULE_AUTHOR("Zane Leung <liangzhen@linux.spacemit.com>");
+MODULE_DESCRIPTION("Arm CoreSight Trace Memory Controller driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/hwtracing/gtrace/coresight-tmc-etf.c b/drivers/hwtracing/gtrace/coresight-tmc-etf.c
new file mode 100644
index 000000000000..cb35a398bf28
--- /dev/null
+++ b/drivers/hwtracing/gtrace/coresight-tmc-etf.c
@@ -0,0 +1,573 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright(C) 2016 Linaro Limited. All rights reserved.
+ * Author: Mathieu Poirier <mathieu.poirier@linaro.org>
+ * Derived from drivers/hwtracing/coresight/coresight-tmc-etf.c
+ */
+
+#include <linux/atomic.h>
+#include <linux/circ_buf.h>
+#include <linux/gtrace.h>
+#include <linux/perf_event.h>
+#include <linux/slab.h>
+#include "coresight.h"
+#include "coresight-tmc.h"
+
+static int __tmc_etb_enable_hw(struct tmc_drvdata *drvdata)
+{
+ int rc = 0;
+ u32 ffcr;
+
+ CS_UNLOCK(drvdata->base);
+
+ /* Wait for TMCSReady bit to be set */
+ rc = tmc_wait_for_tmcready(drvdata);
+ if (rc) {
+ dev_err(&drvdata->comp->dev,
+ "Failed to enable: TMC not ready\n");
+ CS_LOCK(drvdata->base);
+ return rc;
+ }
+
+ writel_relaxed(TMC_MODE_CIRCULAR_BUFFER, drvdata->base + TMC_MODE);
+
+ ffcr = TMC_FFCR_EN_FMT | TMC_FFCR_EN_TI | TMC_FFCR_FON_FLIN |
+ TMC_FFCR_FON_TRIG_EVT | TMC_FFCR_TRIGON_TRIGIN;
+ if (drvdata->stop_on_flush)
+ ffcr |= TMC_FFCR_STOP_ON_FLUSH;
+ writel_relaxed(ffcr, drvdata->base + TMC_FFCR);
+
+ writel_relaxed(drvdata->trigger_cntr, drvdata->base + TMC_TRG);
+ tmc_enable_hw(drvdata);
+
+ CS_LOCK(drvdata->base);
+ return rc;
+}
+
+static int tmc_etb_enable_hw(struct tmc_drvdata *drvdata)
+{
+ int rc = coresight_claim_device(drvdata->comp);
+
+ if (rc)
+ return rc;
+
+ rc = __tmc_etb_enable_hw(drvdata);
+ if (rc)
+ coresight_disclaim_device(drvdata->comp);
+ return rc;
+}
+
+static void tmc_etb_dump_hw(struct tmc_drvdata *drvdata)
+{
+ char *bufp;
+ u32 read_data, lost;
+
+ /* Check if the buffer wrapped around. */
+ lost = readl_relaxed(drvdata->base + TMC_STS) & TMC_STS_FULL;
+ bufp = drvdata->buf;
+ drvdata->len = 0;
+ while (1) {
+ read_data = readl_relaxed(drvdata->base + TMC_RRD);
+ if (read_data == 0xFFFFFFFF)
+ break;
+ memcpy(bufp, &read_data, 4);
+ bufp += 4;
+ drvdata->len += 4;
+ }
+
+ if (lost)
+ coresight_insert_barrier_packet(drvdata->buf);
+ return;
+}
+
+static void __tmc_etb_disable_hw(struct tmc_drvdata *drvdata)
+{
+ CS_UNLOCK(drvdata->base);
+
+ tmc_flush_and_stop(drvdata);
+ /*
+ * When operating in sysFS mode the content of the buffer needs to be
+ * read before the TMC is disabled.
+ */
+ if (gtrace_get_mode(drvdata->comp) == GTRACE_COMPONENT_MODE_SYSFS)
+ tmc_etb_dump_hw(drvdata);
+ tmc_disable_hw(drvdata);
+
+ CS_LOCK(drvdata->base);
+}
+
+static void tmc_etb_disable_hw(struct tmc_drvdata *drvdata)
+{
+ __tmc_etb_disable_hw(drvdata);
+ coresight_disclaim_device(drvdata->comp);
+}
+
+static int __tmc_etf_enable_hw(struct tmc_drvdata *drvdata)
+{
+ int rc = 0;
+
+ CS_UNLOCK(drvdata->base);
+
+ /* Wait for TMCSReady bit to be set */
+ rc = tmc_wait_for_tmcready(drvdata);
+ if (rc) {
+ dev_err(&drvdata->comp->dev,
+ "Failed to enable : TMC is not ready\n");
+ CS_LOCK(drvdata->base);
+ return rc;
+ }
+
+ writel_relaxed(TMC_MODE_HARDWARE_FIFO, drvdata->base + TMC_MODE);
+ writel_relaxed(TMC_FFCR_EN_FMT | TMC_FFCR_EN_TI,
+ drvdata->base + TMC_FFCR);
+ writel_relaxed(0x0, drvdata->base + TMC_BUFWM);
+ tmc_enable_hw(drvdata);
+
+ CS_LOCK(drvdata->base);
+ return rc;
+}
+
+static int tmc_etf_enable_hw(struct tmc_drvdata *drvdata)
+{
+ int rc = coresight_claim_device(drvdata->comp);
+
+ if (rc)
+ return rc;
+
+ rc = __tmc_etf_enable_hw(drvdata);
+ if (rc)
+ coresight_disclaim_device(drvdata->comp);
+ return rc;
+}
+
+static void tmc_etf_disable_hw(struct tmc_drvdata *drvdata)
+{
+ struct gtrace_component *comp = drvdata->comp;
+
+ CS_UNLOCK(drvdata->base);
+
+ tmc_flush_and_stop(drvdata);
+ tmc_disable_hw(drvdata);
+ coresight_disclaim_device_unlocked(comp);
+ CS_LOCK(drvdata->base);
+}
+
+/*
+ * Return the available trace data in the buffer from @pos, with
+ * a maximum limit of @len, updating the @bufpp on where to
+ * find it.
+ */
+ssize_t tmc_etb_get_sysfs_trace(struct tmc_drvdata *drvdata,
+ loff_t pos, size_t len, char **bufpp)
+{
+ ssize_t actual = len;
+
+ /* Adjust the len to available size @pos */
+ if (pos + actual > drvdata->len)
+ actual = drvdata->len - pos;
+ if (actual > 0)
+ *bufpp = drvdata->buf + pos;
+ return actual;
+}
+
+int tmc_etf_setup_sysfs_buf(struct gtrace_component *comp)
+{
+ int ret = 0;
+ bool used = false;
+ char *buf = NULL;
+ unsigned long flags;
+ struct tmc_drvdata *drvdata = dev_get_drvdata(comp->dev.parent);
+
+ /*
+ * If we don't have a buffer release the lock and allocate memory.
+ * Otherwise keep the lock and move along.
+ */
+ raw_spin_lock_irqsave(&drvdata->spinlock, flags);
+ if (!drvdata->buf) {
+ raw_spin_unlock_irqrestore(&drvdata->spinlock, flags);
+
+ /* Allocating the memory here while outside of the spinlock */
+ buf = kzalloc(drvdata->size, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ /* Let's try again */
+ raw_spin_lock_irqsave(&drvdata->spinlock, flags);
+ }
+
+ if (drvdata->reading) {
+ ret = -EBUSY;
+ goto out;
+ }
+
+ /*
+ * If drvdata::buf isn't NULL, memory was allocated for a previous
+ * trace run but wasn't read. If so simply zero-out the memory.
+ * Otherwise use the memory allocated above.
+ *
+ * The memory is freed when users read the buffer using the
+ * /dev/xyz.{etf|etb} interface. See tmc_read_unprepare_etf() for
+ * details.
+ */
+ if (drvdata->buf) {
+ memset(drvdata->buf, 0, drvdata->size);
+ } else {
+ used = true;
+ drvdata->buf = buf;
+ }
+out:
+ raw_spin_unlock_irqrestore(&drvdata->spinlock, flags);
+
+ /* Free memory outside the spinlock if need be */
+ if (!used)
+ kfree(buf);
+
+ return ret;
+}
+
+int tmc_enable_etf(struct gtrace_component *comp)
+{
+ int ret = 0;
+ struct gtrace_path *path = comp->path;
+ struct tmc_drvdata *drvdata = dev_get_drvdata(comp->dev.parent);
+
+ if (WARN_ON(!path))
+ return -EINVAL;
+
+ if (drvdata->reading)
+ return -EBUSY;
+
+ drvdata->is_sink = gtrace_path_sink(path);
+ if (drvdata->is_sink)
+ ret = tmc_etb_enable_hw(drvdata);
+ else
+ ret = tmc_etf_enable_hw(drvdata);
+
+ if (!ret)
+ dev_dbg(&comp->dev, "TMC-ETB/ETF enabled\n");
+
+ return ret;
+}
+
+int tmc_disable_etf(struct gtrace_component *comp)
+{
+ struct gtrace_path *path = comp->path;
+ struct tmc_drvdata *drvdata = dev_get_drvdata(comp->dev.parent);
+
+ if (WARN_ON(!path))
+ return -EINVAL;
+
+ if (drvdata->reading)
+ return -EBUSY;
+
+ if (drvdata->is_sink)
+ tmc_etb_disable_hw(drvdata);
+ else
+ tmc_etf_disable_hw(drvdata);
+
+ dev_dbg(&comp->dev, "TMC-ETB/ETF disabled\n");
+ return 0;
+}
+
+unsigned long tmc_update_etf_buffer(struct gtrace_component *comp,
+ struct perf_output_handle *handle,
+ struct gtrace_perf_auxbuf *buf)
+{
+ bool lost = false;
+ int i, cur;
+ const u32 *barrier;
+ u32 *buf_ptr;
+ u64 read_ptr, write_ptr;
+ u32 status;
+ unsigned long offset, to_read = 0, flags;
+ struct tmc_drvdata *drvdata = dev_get_drvdata(comp->dev.parent);
+ struct perf_event *event = handle->event;
+
+ /* This shouldn't happen */
+ if (WARN_ON_ONCE(gtrace_get_mode(comp) != GTRACE_COMPONENT_MODE_PERF))
+ return 0;
+
+ raw_spin_lock_irqsave(&drvdata->spinlock, flags);
+
+ CS_UNLOCK(drvdata->base);
+
+ tmc_flush_and_stop(drvdata);
+
+ read_ptr = tmc_read_rrp(drvdata);
+ write_ptr = tmc_read_rwp(drvdata);
+
+ /*
+ * Get a hold of the status register and see if a wrap around
+ * has occurred. If so adjust things accordingly.
+ */
+ status = readl_relaxed(drvdata->base + TMC_STS);
+ if (status & TMC_STS_FULL) {
+ lost = true;
+ to_read = drvdata->size;
+ } else {
+ to_read = CIRC_CNT(write_ptr, read_ptr, drvdata->size);
+ }
+
+ /*
+ * The TMC RAM buffer may be bigger than the space available in the
+ * perf ring buffer (handle->size). If so advance the RRP so that we
+ * get the latest trace data. In snapshot mode none of that matters
+ * since we are expected to clobber stale data in favour of the latest
+ * traces.
+ */
+ if (!buf->snapshot && to_read > handle->size) {
+ u32 mask = tmc_get_memwidth_mask(drvdata);
+
+ /*
+ * Make sure the new size is aligned in accordance with the
+ * requirement explained in function tmc_get_memwidth_mask().
+ */
+ to_read = handle->size & mask;
+ /* Move the RAM read pointer up */
+ read_ptr = (write_ptr + drvdata->size) - to_read;
+ /* Make sure we are still within our limits */
+ if (read_ptr > (drvdata->size - 1))
+ read_ptr -= drvdata->size;
+ /* Tell the HW */
+ tmc_write_rrp(drvdata, read_ptr);
+ lost = true;
+ }
+
+ /*
+ * Don't set the TRUNCATED flag in snapshot mode because 1) the
+ * captured buffer is expected to be truncated and 2) a full buffer
+ * prevents the event from being re-enabled by the perf core,
+ * resulting in stale data being send to user space.
+ */
+ if (!buf->snapshot && lost)
+ perf_aux_output_flag(handle, PERF_AUX_FLAG_TRUNCATED);
+
+ cur = buf->pos / PAGE_SIZE;
+ offset = buf->pos % PAGE_SIZE;
+ barrier = coresight_barrier_pkt;
+
+ /* for every byte to read */
+ for (i = 0; i < to_read; i += 4) {
+ buf_ptr = buf->pages[cur] + offset;
+ *buf_ptr = readl_relaxed(drvdata->base + TMC_RRD);
+
+ if (lost && i < CORESIGHT_BARRIER_PKT_SIZE) {
+ *buf_ptr = *barrier;
+ barrier++;
+ }
+
+ offset += 4;
+ if (offset >= PAGE_SIZE) {
+ offset = 0;
+ cur++;
+ /* wrap around at the end of the buffer */
+ cur &= buf->nr_pages - 1;
+ }
+ }
+
+ /*
+ * In snapshot mode we simply increment the head by the number of byte
+ * that were written. User space will figure out how many bytes to get
+ * from the AUX buffer based on the position of the head.
+ */
+ if (buf->snapshot)
+ handle->head += to_read;
+
+ /*
+ * CS_LOCK() contains mb() so it can ensure visibility of the AUX trace
+ * data before the aux_head is updated via perf_aux_output_end(), which
+ * is expected by the perf ring buffer.
+ */
+ CS_LOCK(drvdata->base);
+
+ /*
+ * If the event is active, it is triggered during an AUX pause.
+ * Re-enable the sink so that it is ready when AUX resume is invoked.
+ */
+ if (!event->hw.state)
+ __tmc_etb_enable_hw(drvdata);
+
+ /* set the GTRACE_FORMAT_CORESIGHT flag */
+ perf_aux_output_flag(handle, PERF_AUX_FLAG_GTRACE_FORMAT_CORESIGHT);
+
+ return to_read;
+}
+
+int tmc_panic_sync_etf(struct gtrace_component *comp)
+{
+ u32 val;
+ struct tmc_crash_metadata *mdata;
+ struct tmc_drvdata *drvdata = dev_get_drvdata(comp->dev.parent);
+
+ mdata = (struct tmc_crash_metadata *)drvdata->crash_mdata.vaddr;
+
+ /* Make sure we have valid reserved memory */
+ if (!tmc_has_reserved_buffer(drvdata) ||
+ !tmc_has_crash_mdata_buffer(drvdata))
+ return 0;
+
+ tmc_crashdata_set_invalid(drvdata);
+
+ CS_UNLOCK(drvdata->base);
+
+ /* Proceed only if ETF is enabled or configured as sink */
+ val = readl(drvdata->base + TMC_CTL);
+ if (!(val & TMC_CTL_CAPT_EN))
+ goto out;
+ val = readl(drvdata->base + TMC_MODE);
+ if (val != TMC_MODE_CIRCULAR_BUFFER)
+ goto out;
+
+ val = readl(drvdata->base + TMC_FFSR);
+ /* Do manual flush and stop only if its not auto-stopped */
+ if (!(val & TMC_FFSR_FT_STOPPED)) {
+ dev_dbg(&comp->dev,
+ "%s: Triggering manual flush\n", __func__);
+ tmc_flush_and_stop(drvdata);
+ } else
+ tmc_wait_for_tmcready(drvdata);
+
+ /* Sync registers from hardware to metadata region */
+ mdata->tmc_sts = readl(drvdata->base + TMC_STS);
+ mdata->tmc_mode = readl(drvdata->base + TMC_MODE);
+ mdata->tmc_ffcr = readl(drvdata->base + TMC_FFCR);
+ mdata->tmc_ffsr = readl(drvdata->base + TMC_FFSR);
+
+ /* Sync Internal SRAM to reserved trace buffer region */
+ drvdata->buf = drvdata->resrv_buf.vaddr;
+ tmc_etb_dump_hw(drvdata);
+ /* Store as per RSZ register convention */
+ mdata->tmc_ram_size = drvdata->len >> 2;
+
+ /* Other fields for processing trace buffer reads */
+ mdata->tmc_rrp = 0;
+ mdata->tmc_dba = 0;
+ mdata->tmc_rwp = drvdata->len;
+ mdata->trace_paddr = drvdata->resrv_buf.paddr;
+
+ mdata->version = CS_CRASHDATA_VERSION;
+
+ /*
+ * Make sure all previous writes are ordered,
+ * before we mark valid
+ */
+ smp_wmb();
+ mdata->valid = true;
+ /*
+ * Below order need to maintained, since crc of metadata
+ * is dependent on first
+ */
+ mdata->crc32_tdata = find_crash_tracedata_crc(drvdata, mdata);
+ mdata->crc32_mdata = find_crash_metadata_crc(mdata);
+
+ tmc_disable_hw(drvdata);
+
+ dev_dbg(&comp->dev, "%s: success\n", __func__);
+out:
+ CS_UNLOCK(drvdata->base);
+ return 0;
+}
+
+int tmc_read_prepare_etb(struct tmc_drvdata *drvdata)
+{
+ enum tmc_mode mode;
+ int ret = 0;
+ unsigned long flags;
+
+ /* config types are set a boot time and never change */
+ if (WARN_ON_ONCE(drvdata->config_type != TMC_CONFIG_TYPE_ETB &&
+ drvdata->config_type != TMC_CONFIG_TYPE_ETF))
+ return -EINVAL;
+
+ raw_spin_lock_irqsave(&drvdata->spinlock, flags);
+
+ if (drvdata->reading) {
+ ret = -EBUSY;
+ goto out;
+ }
+
+ /* Don't interfere if operated from Perf */
+ if (gtrace_get_mode(drvdata->comp) == GTRACE_COMPONENT_MODE_PERF) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ /* If drvdata::buf is NULL the trace data has been read already */
+ if (drvdata->buf == NULL) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ /* Disable the TMC if need be */
+ if (gtrace_get_mode(drvdata->comp) == GTRACE_COMPONENT_MODE_SYSFS) {
+ /* There is no point in reading a TMC in HW FIFO mode */
+ mode = readl_relaxed(drvdata->base + TMC_MODE);
+ if (mode != TMC_MODE_CIRCULAR_BUFFER) {
+ ret = -EINVAL;
+ goto out;
+ }
+ __tmc_etb_disable_hw(drvdata);
+ }
+
+ drvdata->reading = true;
+out:
+ raw_spin_unlock_irqrestore(&drvdata->spinlock, flags);
+
+ return ret;
+}
+
+int tmc_read_unprepare_etb(struct tmc_drvdata *drvdata)
+{
+ char *buf = NULL;
+ enum tmc_mode mode;
+ unsigned long flags;
+
+ /* config types are set a boot time and never change */
+ if (WARN_ON_ONCE(drvdata->config_type != TMC_CONFIG_TYPE_ETB &&
+ drvdata->config_type != TMC_CONFIG_TYPE_ETF))
+ return -EINVAL;
+
+ raw_spin_lock_irqsave(&drvdata->spinlock, flags);
+
+ /* Re-enable the TMC if need be */
+ if (gtrace_get_mode(drvdata->comp) == GTRACE_COMPONENT_MODE_SYSFS) {
+ /* There is no point in reading a TMC in HW FIFO mode */
+ mode = readl_relaxed(drvdata->base + TMC_MODE);
+ if (mode != TMC_MODE_CIRCULAR_BUFFER) {
+ raw_spin_unlock_irqrestore(&drvdata->spinlock, flags);
+ return -EINVAL;
+ }
+ /*
+ * The trace run will continue with the same allocated trace
+ * buffer. As such zero-out the buffer so that we don't end
+ * up with stale data.
+ *
+ * Since the tracer is still enabled drvdata::buf
+ * can't be NULL.
+ */
+ memset(drvdata->buf, 0, drvdata->size);
+ /*
+ * Ignore failures to enable the TMC to make sure, we don't
+ * leave the TMC in a "reading" state.
+ */
+ __tmc_etb_enable_hw(drvdata);
+ } else {
+ /*
+ * The ETB/ETF is not tracing and the buffer was just read.
+ * As such prepare to free the trace buffer.
+ */
+ buf = drvdata->buf;
+ drvdata->buf = NULL;
+ }
+
+ drvdata->reading = false;
+ raw_spin_unlock_irqrestore(&drvdata->spinlock, flags);
+
+ /*
+ * Free allocated memory outside of the spinlock. There is no need
+ * to assert the validity of 'buf' since calling kfree(NULL) is safe.
+ */
+ kfree(buf);
+
+ return 0;
+}
diff --git a/drivers/hwtracing/gtrace/coresight-tmc-etr.c b/drivers/hwtracing/gtrace/coresight-tmc-etr.c
new file mode 100644
index 000000000000..11b7df8ae1b2
--- /dev/null
+++ b/drivers/hwtracing/gtrace/coresight-tmc-etr.c
@@ -0,0 +1,1888 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright(C) 2016 Linaro Limited. All rights reserved.
+ * Author: Mathieu Poirier <mathieu.poirier@linaro.org>
+ * Derived from drivers/hwtracing/coresight/coresight-tmc-etr.c
+ */
+
+#include <linux/atomic.h>
+#include <linux/gtrace.h>
+#include <linux/dma-mapping.h>
+#include <linux/iommu.h>
+#include <linux/idr.h>
+#include <linux/mutex.h>
+#include <linux/refcount.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+#include <linux/vmalloc.h>
+#include "coresight.h"
+#include "coresight-tmc.h"
+
+struct etr_flat_buf {
+ struct device *dev;
+ dma_addr_t daddr;
+ void *vaddr;
+ size_t size;
+};
+
+struct etr_buf_hw {
+ bool has_iommu;
+ bool has_etr_sg;
+ bool has_catu;
+ bool has_resrv;
+};
+
+/* Convert the perf index to an offset within the ETR buffer */
+#define PERF_IDX2OFF(idx, buf) \
+ ((idx) % ((unsigned long)(buf)->nr_pages << PAGE_SHIFT))
+
+/* Lower limit for ETR hardware buffer */
+#define TMC_ETR_PERF_MIN_BUF_SIZE SZ_1M
+
+/*
+ * The TMC ETR SG has a page size of 4K. The SG table contains pointers
+ * to 4KB buffers. However, the OS may use a PAGE_SIZE different from
+ * 4K (i.e, 16KB or 64KB). This implies that a single OS page could
+ * contain more than one SG buffer and tables.
+ *
+ * A table entry has the following format:
+ *
+ * ---Bit31------------Bit4-------Bit1-----Bit0--
+ * | Address[39:12] | SBZ | Entry Type |
+ * ----------------------------------------------
+ *
+ * Address: Bits [39:12] of a physical page address. Bits [11:0] are
+ * always zero.
+ *
+ * Entry type:
+ * b00 - Reserved.
+ * b01 - Last entry in the tables, points to 4K page buffer.
+ * b10 - Normal entry, points to 4K page buffer.
+ * b11 - Link. The address points to the base of next table.
+ */
+
+typedef u32 sgte_t;
+
+#define ETR_SG_PAGE_SHIFT 12
+#define ETR_SG_PAGE_SIZE (1UL << ETR_SG_PAGE_SHIFT)
+#define ETR_SG_PAGES_PER_SYSPAGE (PAGE_SIZE / ETR_SG_PAGE_SIZE)
+#define ETR_SG_PTRS_PER_PAGE (ETR_SG_PAGE_SIZE / sizeof(sgte_t))
+#define ETR_SG_PTRS_PER_SYSPAGE (PAGE_SIZE / sizeof(sgte_t))
+
+#define ETR_SG_ET_MASK 0x3
+#define ETR_SG_ET_LAST 0x1
+#define ETR_SG_ET_NORMAL 0x2
+#define ETR_SG_ET_LINK 0x3
+
+#define ETR_SG_ADDR_SHIFT 4
+
+#define ETR_SG_ENTRY(addr, type) \
+ ((sgte_t)((((addr) >> ETR_SG_PAGE_SHIFT) << ETR_SG_ADDR_SHIFT) | \
+ ((type) & ETR_SG_ET_MASK)))
+
+#define ETR_SG_ADDR(entry) \
+ (((dma_addr_t)(entry) >> ETR_SG_ADDR_SHIFT) << ETR_SG_PAGE_SHIFT)
+#define ETR_SG_ET(entry) ((entry) & ETR_SG_ET_MASK)
+
+/*
+ * struct etr_sg_table : ETR SG Table
+ * @sg_table: Generic SG Table holding the data/table pages.
+ * @hwaddr: hwaddress used by the TMC, which is the base
+ * address of the table.
+ */
+struct etr_sg_table {
+ struct tmc_sg_table *sg_table;
+ dma_addr_t hwaddr;
+};
+
+/*
+ * tmc_etr_sg_table_entries: Total number of table entries required to map
+ * @nr_pages system pages.
+ *
+ * We need to map @nr_pages * ETR_SG_PAGES_PER_SYSPAGE data pages.
+ * Each TMC page can map (ETR_SG_PTRS_PER_PAGE - 1) buffer pointers,
+ * with the last entry pointing to another page of table entries.
+ * If we spill over to a new page for mapping 1 entry, we could as
+ * well replace the link entry of the previous page with the last entry.
+ */
+static unsigned long __attribute_const__
+tmc_etr_sg_table_entries(int nr_pages)
+{
+ unsigned long nr_sgpages = nr_pages * ETR_SG_PAGES_PER_SYSPAGE;
+ unsigned long nr_sglinks = nr_sgpages / (ETR_SG_PTRS_PER_PAGE - 1);
+ /*
+ * If we spill over to a new page for 1 entry, we could as well
+ * make it the LAST entry in the previous page, skipping the Link
+ * address.
+ */
+ if (nr_sglinks && (nr_sgpages % (ETR_SG_PTRS_PER_PAGE - 1) < 2))
+ nr_sglinks--;
+ return nr_sgpages + nr_sglinks;
+}
+
+/*
+ * tmc_pages_get_offset: Go through all the pages in the tmc_pages
+ * and map the device address @addr to an offset within the virtual
+ * contiguous buffer.
+ */
+static long
+tmc_pages_get_offset(struct tmc_pages *tmc_pages, dma_addr_t addr)
+{
+ int i;
+ dma_addr_t page_start;
+
+ for (i = 0; i < tmc_pages->nr_pages; i++) {
+ page_start = tmc_pages->daddrs[i];
+ if (addr >= page_start && addr < (page_start + PAGE_SIZE))
+ return (long)i * PAGE_SIZE + (addr - page_start);
+ }
+
+ return -EINVAL;
+}
+
+/*
+ * tmc_pages_free : Unmap and free the pages used by tmc_pages.
+ * If the pages were not allocated in tmc_pages_alloc(), we would
+ * simply drop the refcount.
+ */
+static void tmc_pages_free(struct tmc_pages *tmc_pages,
+ struct device *dev, enum dma_data_direction dir)
+{
+ int i;
+ struct device *real_dev = dev->parent;
+
+ for (i = 0; i < tmc_pages->nr_pages; i++) {
+ if (tmc_pages->daddrs && tmc_pages->daddrs[i])
+ dma_unmap_page(real_dev, tmc_pages->daddrs[i],
+ PAGE_SIZE, dir);
+ if (tmc_pages->pages && tmc_pages->pages[i])
+ __free_page(tmc_pages->pages[i]);
+ }
+
+ kfree(tmc_pages->pages);
+ kfree(tmc_pages->daddrs);
+ tmc_pages->pages = NULL;
+ tmc_pages->daddrs = NULL;
+ tmc_pages->nr_pages = 0;
+}
+
+/*
+ * tmc_pages_alloc : Allocate and map pages for a given @tmc_pages.
+ * If @pages is not NULL, the list of page virtual addresses are
+ * used as the data pages. The pages are then dma_map'ed for @dev
+ * with dma_direction @dir.
+ *
+ * Returns 0 upon success, else the error number.
+ */
+static int tmc_pages_alloc(struct tmc_pages *tmc_pages,
+ struct device *dev, int node,
+ enum dma_data_direction dir, void **pages)
+{
+ int i, nr_pages;
+ dma_addr_t paddr;
+ struct page *page;
+ struct device *real_dev = dev->parent;
+
+ nr_pages = tmc_pages->nr_pages;
+ tmc_pages->daddrs = kzalloc_objs(*tmc_pages->daddrs, nr_pages);
+ if (!tmc_pages->daddrs)
+ return -ENOMEM;
+ tmc_pages->pages = kzalloc_objs(*tmc_pages->pages, nr_pages);
+ if (!tmc_pages->pages) {
+ kfree(tmc_pages->daddrs);
+ tmc_pages->daddrs = NULL;
+ return -ENOMEM;
+ }
+
+ for (i = 0; i < nr_pages; i++) {
+ if (pages && pages[i]) {
+ page = virt_to_page(pages[i]);
+ /* Hold a refcount on the page */
+ get_page(page);
+ } else {
+ page = alloc_pages_node(node,
+ GFP_KERNEL | __GFP_ZERO, 0);
+ if (!page)
+ goto err;
+ }
+ paddr = dma_map_page(real_dev, page, 0, PAGE_SIZE, dir);
+ if (dma_mapping_error(real_dev, paddr))
+ goto err;
+ tmc_pages->daddrs[i] = paddr;
+ tmc_pages->pages[i] = page;
+ }
+ return 0;
+err:
+ tmc_pages_free(tmc_pages, dev, dir);
+ return -ENOMEM;
+}
+
+static long
+tmc_sg_get_data_page_offset(struct tmc_sg_table *sg_table, dma_addr_t addr)
+{
+ return tmc_pages_get_offset(&sg_table->data_pages, addr);
+}
+
+static void tmc_free_table_pages(struct tmc_sg_table *sg_table)
+{
+ if (sg_table->table_vaddr)
+ vunmap(sg_table->table_vaddr);
+ tmc_pages_free(&sg_table->table_pages, sg_table->dev, DMA_TO_DEVICE);
+}
+
+static void tmc_free_data_pages(struct tmc_sg_table *sg_table)
+{
+ if (sg_table->data_vaddr)
+ vunmap(sg_table->data_vaddr);
+ tmc_pages_free(&sg_table->data_pages, sg_table->dev, DMA_FROM_DEVICE);
+}
+
+void tmc_free_sg_table(struct tmc_sg_table *sg_table)
+{
+ tmc_free_table_pages(sg_table);
+ tmc_free_data_pages(sg_table);
+ kfree(sg_table);
+}
+EXPORT_SYMBOL_GPL(tmc_free_sg_table);
+
+/*
+ * Alloc pages for the table. Since this will be used by the device,
+ * allocate the pages closer to the device (i.e, dev_to_node(dev)
+ * rather than the CPU node).
+ */
+static int tmc_alloc_table_pages(struct tmc_sg_table *sg_table)
+{
+ int rc;
+ struct tmc_pages *table_pages = &sg_table->table_pages;
+
+ rc = tmc_pages_alloc(table_pages, sg_table->dev,
+ dev_to_node(sg_table->dev),
+ DMA_TO_DEVICE, NULL);
+ if (rc)
+ return rc;
+ sg_table->table_vaddr = vmap(table_pages->pages,
+ table_pages->nr_pages,
+ VM_MAP,
+ PAGE_KERNEL);
+ if (!sg_table->table_vaddr)
+ rc = -ENOMEM;
+ else
+ sg_table->table_daddr = table_pages->daddrs[0];
+ return rc;
+}
+
+static int tmc_alloc_data_pages(struct tmc_sg_table *sg_table, void **pages)
+{
+ int rc;
+
+ /* Allocate data pages on the node requested by the caller */
+ rc = tmc_pages_alloc(&sg_table->data_pages,
+ sg_table->dev, sg_table->node,
+ DMA_FROM_DEVICE, pages);
+ if (!rc) {
+ sg_table->data_vaddr = vmap(sg_table->data_pages.pages,
+ sg_table->data_pages.nr_pages,
+ VM_MAP,
+ PAGE_KERNEL);
+ if (!sg_table->data_vaddr)
+ rc = -ENOMEM;
+ }
+ return rc;
+}
+
+/*
+ * tmc_alloc_sg_table: Allocate and setup dma pages for the TMC SG table
+ * and data buffers. TMC writes to the data buffers and reads from the SG
+ * Table pages.
+ *
+ * @dev - Coresight device to which page should be DMA mapped.
+ * @node - Numa node for mem allocations
+ * @nr_tpages - Number of pages for the table entries.
+ * @nr_dpages - Number of pages for Data buffer.
+ * @pages - Optional list of virtual address of pages.
+ */
+struct tmc_sg_table *tmc_alloc_sg_table(struct device *dev,
+ int node,
+ int nr_tpages,
+ int nr_dpages,
+ void **pages)
+{
+ long rc;
+ struct tmc_sg_table *sg_table;
+
+ sg_table = kzalloc_obj(*sg_table);
+ if (!sg_table)
+ return ERR_PTR(-ENOMEM);
+ sg_table->data_pages.nr_pages = nr_dpages;
+ sg_table->table_pages.nr_pages = nr_tpages;
+ sg_table->node = node;
+ sg_table->dev = dev;
+
+ rc = tmc_alloc_data_pages(sg_table, pages);
+ if (!rc)
+ rc = tmc_alloc_table_pages(sg_table);
+ if (rc) {
+ tmc_free_sg_table(sg_table);
+ return ERR_PTR(rc);
+ }
+
+ return sg_table;
+}
+EXPORT_SYMBOL_GPL(tmc_alloc_sg_table);
+
+/*
+ * tmc_sg_table_sync_data_range: Sync the data buffer written
+ * by the device from @offset up to a @size bytes.
+ */
+void tmc_sg_table_sync_data_range(struct tmc_sg_table *table,
+ u64 offset, u64 size)
+{
+ int i, index, start;
+ int npages = DIV_ROUND_UP(size, PAGE_SIZE);
+ struct device *real_dev = table->dev->parent;
+ struct tmc_pages *data = &table->data_pages;
+
+ start = offset >> PAGE_SHIFT;
+ for (i = start; i < (start + npages); i++) {
+ index = i % data->nr_pages;
+ dma_sync_single_for_cpu(real_dev, data->daddrs[index],
+ PAGE_SIZE, DMA_FROM_DEVICE);
+ }
+}
+EXPORT_SYMBOL_GPL(tmc_sg_table_sync_data_range);
+
+/* tmc_sg_sync_table: Sync the page table */
+void tmc_sg_table_sync_table(struct tmc_sg_table *sg_table)
+{
+ int i;
+ struct device *real_dev = sg_table->dev->parent;
+ struct tmc_pages *table_pages = &sg_table->table_pages;
+
+ for (i = 0; i < table_pages->nr_pages; i++)
+ dma_sync_single_for_device(real_dev, table_pages->daddrs[i],
+ PAGE_SIZE, DMA_TO_DEVICE);
+}
+EXPORT_SYMBOL_GPL(tmc_sg_table_sync_table);
+
+/*
+ * tmc_sg_table_get_data: Get the buffer pointer for data @offset
+ * in the SG buffer. The @bufpp is updated to point to the buffer.
+ * Returns :
+ * the length of linear data available at @offset.
+ * or
+ * <= 0 if no data is available.
+ */
+ssize_t tmc_sg_table_get_data(struct tmc_sg_table *sg_table,
+ u64 offset, size_t len, char **bufpp)
+{
+ size_t size;
+ int pg_idx = offset >> PAGE_SHIFT;
+ int pg_offset = offset & (PAGE_SIZE - 1);
+ struct tmc_pages *data_pages = &sg_table->data_pages;
+
+ size = tmc_sg_table_buf_size(sg_table);
+ if (offset >= size)
+ return -EINVAL;
+
+ /* Make sure we don't go beyond the end */
+ len = (len < (size - offset)) ? len : size - offset;
+ /* Respect the page boundaries */
+ len = (len < (PAGE_SIZE - pg_offset)) ? len : (PAGE_SIZE - pg_offset);
+ if (len > 0)
+ *bufpp = page_address(data_pages->pages[pg_idx]) + pg_offset;
+ return len;
+}
+EXPORT_SYMBOL_GPL(tmc_sg_table_get_data);
+
+#ifdef ETR_SG_DEBUG
+/* Map a dma address to virtual address */
+static unsigned long
+tmc_sg_daddr_to_vaddr(struct tmc_sg_table *sg_table,
+ dma_addr_t addr, bool table)
+{
+ long offset;
+ unsigned long base;
+ struct tmc_pages *tmc_pages;
+
+ if (table) {
+ tmc_pages = &sg_table->table_pages;
+ base = (unsigned long)sg_table->table_vaddr;
+ } else {
+ tmc_pages = &sg_table->data_pages;
+ base = (unsigned long)sg_table->data_vaddr;
+ }
+
+ offset = tmc_pages_get_offset(tmc_pages, addr);
+ if (offset < 0)
+ return 0;
+ return base + offset;
+}
+
+/* Dump the given sg_table */
+static void tmc_etr_sg_table_dump(struct etr_sg_table *etr_table)
+{
+ sgte_t *ptr;
+ int i = 0;
+ dma_addr_t addr;
+ struct tmc_sg_table *sg_table = etr_table->sg_table;
+
+ ptr = (sgte_t *)tmc_sg_daddr_to_vaddr(sg_table,
+ etr_table->hwaddr, true);
+ while (ptr) {
+ addr = ETR_SG_ADDR(*ptr);
+ switch (ETR_SG_ET(*ptr)) {
+ case ETR_SG_ET_NORMAL:
+ dev_dbg(sg_table->dev,
+ "%05d: %p\t:[N] 0x%llx\n", i, ptr, addr);
+ ptr++;
+ break;
+ case ETR_SG_ET_LINK:
+ dev_dbg(sg_table->dev,
+ "%05d: *** %p\t:{L} 0x%llx ***\n",
+ i, ptr, addr);
+ ptr = (sgte_t *)tmc_sg_daddr_to_vaddr(sg_table,
+ addr, true);
+ break;
+ case ETR_SG_ET_LAST:
+ dev_dbg(sg_table->dev,
+ "%05d: ### %p\t:[L] 0x%llx ###\n",
+ i, ptr, addr);
+ return;
+ default:
+ dev_dbg(sg_table->dev,
+ "%05d: xxx %p\t:[INVALID] 0x%llx xxx\n",
+ i, ptr, addr);
+ return;
+ }
+ i++;
+ }
+ dev_dbg(sg_table->dev, "******* End of Table *****\n");
+}
+#else
+static void tmc_etr_sg_table_dump(struct etr_sg_table *etr_table) {}
+#endif
+
+/*
+ * Populate the SG Table page table entries from table/data
+ * pages allocated. Each Data page has ETR_SG_PAGES_PER_SYSPAGE SG pages.
+ * So does a Table page. So we keep track of indices of the tables
+ * in each system page and move the pointers accordingly.
+ */
+#define INC_IDX_ROUND(idx, size) ((idx) = ((idx) + 1) % (size))
+static void tmc_etr_sg_table_populate(struct etr_sg_table *etr_table)
+{
+ dma_addr_t paddr;
+ int i, type, nr_entries;
+ int tpidx = 0; /* index to the current system table_page */
+ int sgtidx = 0; /* index to the sg_table within the current syspage */
+ int sgtentry = 0; /* the entry within the sg_table */
+ int dpidx = 0; /* index to the current system data_page */
+ int spidx = 0; /* index to the SG page within the current data page */
+ sgte_t *ptr; /* pointer to the table entry to fill */
+ struct tmc_sg_table *sg_table = etr_table->sg_table;
+ dma_addr_t *table_daddrs = sg_table->table_pages.daddrs;
+ dma_addr_t *data_daddrs = sg_table->data_pages.daddrs;
+
+ nr_entries = tmc_etr_sg_table_entries(sg_table->data_pages.nr_pages);
+ /*
+ * Use the contiguous virtual address of the table to update entries.
+ */
+ ptr = sg_table->table_vaddr;
+ /*
+ * Fill all the entries, except the last entry to avoid special
+ * checks within the loop.
+ */
+ for (i = 0; i < nr_entries - 1; i++) {
+ if (sgtentry == ETR_SG_PTRS_PER_PAGE - 1) {
+ /*
+ * Last entry in a sg_table page is a link address to
+ * the next table page. If this sg_table is the last
+ * one in the system page, it links to the first
+ * sg_table in the next system page. Otherwise, it
+ * links to the next sg_table page within the system
+ * page.
+ */
+ if (sgtidx == ETR_SG_PAGES_PER_SYSPAGE - 1) {
+ paddr = table_daddrs[tpidx + 1];
+ } else {
+ paddr = table_daddrs[tpidx] +
+ (ETR_SG_PAGE_SIZE * (sgtidx + 1));
+ }
+ type = ETR_SG_ET_LINK;
+ } else {
+ /*
+ * Update the indices to the data_pages to point to the
+ * next sg_page in the data buffer.
+ */
+ type = ETR_SG_ET_NORMAL;
+ paddr = data_daddrs[dpidx] + spidx * ETR_SG_PAGE_SIZE;
+ if (!INC_IDX_ROUND(spidx, ETR_SG_PAGES_PER_SYSPAGE))
+ dpidx++;
+ }
+ *ptr++ = ETR_SG_ENTRY(paddr, type);
+ /*
+ * Move to the next table pointer, moving the table page index
+ * if necessary
+ */
+ if (!INC_IDX_ROUND(sgtentry, ETR_SG_PTRS_PER_PAGE)) {
+ if (!INC_IDX_ROUND(sgtidx, ETR_SG_PAGES_PER_SYSPAGE))
+ tpidx++;
+ }
+ }
+
+ /* Set up the last entry, which is always a data pointer */
+ paddr = data_daddrs[dpidx] + spidx * ETR_SG_PAGE_SIZE;
+ *ptr++ = ETR_SG_ENTRY(paddr, ETR_SG_ET_LAST);
+}
+
+/*
+ * tmc_init_etr_sg_table: Allocate a TMC ETR SG table, data buffer of @size and
+ * populate the table.
+ *
+ * @dev - Device pointer for the TMC
+ * @node - NUMA node where the memory should be allocated
+ * @size - Total size of the data buffer
+ * @pages - Optional list of page virtual address
+ */
+static struct etr_sg_table *
+tmc_init_etr_sg_table(struct device *dev, int node,
+ unsigned long size, void **pages)
+{
+ int nr_entries, nr_tpages;
+ int nr_dpages = size >> PAGE_SHIFT;
+ struct tmc_sg_table *sg_table;
+ struct etr_sg_table *etr_table;
+
+ etr_table = kzalloc_obj(*etr_table);
+ if (!etr_table)
+ return ERR_PTR(-ENOMEM);
+ nr_entries = tmc_etr_sg_table_entries(nr_dpages);
+ nr_tpages = DIV_ROUND_UP(nr_entries, ETR_SG_PTRS_PER_SYSPAGE);
+
+ sg_table = tmc_alloc_sg_table(dev, node, nr_tpages, nr_dpages, pages);
+ if (IS_ERR(sg_table)) {
+ kfree(etr_table);
+ return ERR_CAST(sg_table);
+ }
+
+ etr_table->sg_table = sg_table;
+ /* TMC should use table base address for DBA */
+ etr_table->hwaddr = sg_table->table_daddr;
+ tmc_etr_sg_table_populate(etr_table);
+ /* Sync the table pages for the HW */
+ tmc_sg_table_sync_table(sg_table);
+ tmc_etr_sg_table_dump(etr_table);
+
+ return etr_table;
+}
+
+/*
+ * tmc_etr_alloc_flat_buf: Allocate a contiguous DMA buffer.
+ */
+static int tmc_etr_alloc_flat_buf(struct tmc_drvdata *drvdata,
+ struct etr_buf *etr_buf, int node,
+ void **pages)
+{
+ struct etr_flat_buf *flat_buf;
+ struct device *real_dev = drvdata->comp->dev.parent;
+
+ /* We cannot reuse existing pages for flat buf */
+ if (pages)
+ return -EINVAL;
+
+ flat_buf = kzalloc_obj(*flat_buf);
+ if (!flat_buf)
+ return -ENOMEM;
+
+ flat_buf->vaddr = dma_alloc_noncoherent(real_dev, etr_buf->size,
+ &flat_buf->daddr,
+ DMA_FROM_DEVICE,
+ GFP_KERNEL | __GFP_NOWARN);
+ if (!flat_buf->vaddr) {
+ kfree(flat_buf);
+ return -ENOMEM;
+ }
+
+ flat_buf->size = etr_buf->size;
+ flat_buf->dev = &drvdata->comp->dev;
+ etr_buf->hwaddr = flat_buf->daddr;
+ etr_buf->mode = ETR_MODE_FLAT;
+ etr_buf->private = flat_buf;
+ return 0;
+}
+
+static void tmc_etr_free_flat_buf(struct etr_buf *etr_buf)
+{
+ struct etr_flat_buf *flat_buf = etr_buf->private;
+
+ if (flat_buf && flat_buf->daddr) {
+ struct device *real_dev = flat_buf->dev->parent;
+
+ dma_free_noncoherent(real_dev, etr_buf->size,
+ flat_buf->vaddr, flat_buf->daddr,
+ DMA_FROM_DEVICE);
+ }
+ kfree(flat_buf);
+}
+
+static void tmc_etr_sync_flat_buf(struct etr_buf *etr_buf, u64 rrp, u64 rwp)
+{
+ struct etr_flat_buf *flat_buf = etr_buf->private;
+ struct device *real_dev = flat_buf->dev->parent;
+
+ /*
+ * Adjust the buffer to point to the beginning of the trace data
+ * and update the available trace data.
+ */
+ etr_buf->offset = rrp - etr_buf->hwaddr;
+ if (etr_buf->full)
+ etr_buf->len = etr_buf->size;
+ else
+ etr_buf->len = rwp - rrp;
+
+ /*
+ * The driver always starts tracing at the beginning of the buffer,
+ * the only reason why we would get a wrap around is when the buffer
+ * is full. Sync the entire buffer in one go for this case.
+ */
+ if (etr_buf->offset + etr_buf->len > etr_buf->size)
+ dma_sync_single_for_cpu(real_dev, flat_buf->daddr,
+ etr_buf->size, DMA_FROM_DEVICE);
+ else
+ dma_sync_single_for_cpu(real_dev,
+ flat_buf->daddr + etr_buf->offset,
+ etr_buf->len, DMA_FROM_DEVICE);
+}
+
+static ssize_t tmc_etr_get_data_flat_buf(struct etr_buf *etr_buf,
+ u64 offset, size_t len, char **bufpp)
+{
+ struct etr_flat_buf *flat_buf = etr_buf->private;
+
+ *bufpp = (char *)flat_buf->vaddr + offset;
+ /*
+ * tmc_etr_buf_get_data already adjusts the length to handle
+ * buffer wrapping around.
+ */
+ return len;
+}
+
+static const struct etr_buf_operations etr_flat_buf_ops = {
+ .alloc = tmc_etr_alloc_flat_buf,
+ .free = tmc_etr_free_flat_buf,
+ .sync = tmc_etr_sync_flat_buf,
+ .get_data = tmc_etr_get_data_flat_buf,
+};
+
+/*
+ * tmc_etr_alloc_resrv_buf: Allocate a contiguous DMA buffer from reserved region.
+ */
+static int tmc_etr_alloc_resrv_buf(struct tmc_drvdata *drvdata,
+ struct etr_buf *etr_buf, int node,
+ void **pages)
+{
+ struct etr_flat_buf *resrv_buf;
+ struct device *real_dev = drvdata->comp->dev.parent;
+
+ /* We cannot reuse existing pages for resrv buf */
+ if (pages)
+ return -EINVAL;
+
+ resrv_buf = kzalloc_obj(*resrv_buf);
+ if (!resrv_buf)
+ return -ENOMEM;
+
+ resrv_buf->daddr = dma_map_resource(real_dev, drvdata->resrv_buf.paddr,
+ drvdata->resrv_buf.size,
+ DMA_FROM_DEVICE, 0);
+ if (dma_mapping_error(real_dev, resrv_buf->daddr)) {
+ dev_err(real_dev, "failed to map source buffer address\n");
+ kfree(resrv_buf);
+ return -ENOMEM;
+ }
+
+ resrv_buf->vaddr = drvdata->resrv_buf.vaddr;
+ resrv_buf->size = etr_buf->size = drvdata->resrv_buf.size;
+ resrv_buf->dev = &drvdata->comp->dev;
+ etr_buf->hwaddr = resrv_buf->daddr;
+ etr_buf->mode = ETR_MODE_RESRV;
+ etr_buf->private = resrv_buf;
+ return 0;
+}
+
+static void tmc_etr_free_resrv_buf(struct etr_buf *etr_buf)
+{
+ struct etr_flat_buf *resrv_buf = etr_buf->private;
+
+ if (resrv_buf && resrv_buf->daddr) {
+ struct device *real_dev = resrv_buf->dev->parent;
+
+ dma_unmap_resource(real_dev, resrv_buf->daddr,
+ resrv_buf->size, DMA_FROM_DEVICE, 0);
+ }
+ kfree(resrv_buf);
+}
+
+static void tmc_etr_sync_resrv_buf(struct etr_buf *etr_buf, u64 rrp, u64 rwp)
+{
+ /*
+ * Adjust the buffer to point to the beginning of the trace data
+ * and update the available trace data.
+ */
+ etr_buf->offset = rrp - etr_buf->hwaddr;
+ if (etr_buf->full)
+ etr_buf->len = etr_buf->size;
+ else
+ etr_buf->len = rwp - rrp;
+}
+
+static const struct etr_buf_operations etr_resrv_buf_ops = {
+ .alloc = tmc_etr_alloc_resrv_buf,
+ .free = tmc_etr_free_resrv_buf,
+ .sync = tmc_etr_sync_resrv_buf,
+ .get_data = tmc_etr_get_data_flat_buf,
+};
+
+/*
+ * tmc_etr_alloc_sg_buf: Allocate an SG buf @etr_buf. Setup the parameters
+ * appropriately.
+ */
+static int tmc_etr_alloc_sg_buf(struct tmc_drvdata *drvdata,
+ struct etr_buf *etr_buf, int node,
+ void **pages)
+{
+ struct etr_sg_table *etr_table;
+ struct device *dev = &drvdata->comp->dev;
+
+ etr_table = tmc_init_etr_sg_table(dev, node,
+ etr_buf->size, pages);
+ if (IS_ERR(etr_table))
+ return -ENOMEM;
+ etr_buf->hwaddr = etr_table->hwaddr;
+ etr_buf->mode = ETR_MODE_ETR_SG;
+ etr_buf->private = etr_table;
+ return 0;
+}
+
+static void tmc_etr_free_sg_buf(struct etr_buf *etr_buf)
+{
+ struct etr_sg_table *etr_table = etr_buf->private;
+
+ if (etr_table) {
+ tmc_free_sg_table(etr_table->sg_table);
+ kfree(etr_table);
+ }
+}
+
+static ssize_t tmc_etr_get_data_sg_buf(struct etr_buf *etr_buf, u64 offset,
+ size_t len, char **bufpp)
+{
+ struct etr_sg_table *etr_table = etr_buf->private;
+
+ return tmc_sg_table_get_data(etr_table->sg_table, offset, len, bufpp);
+}
+
+static void tmc_etr_sync_sg_buf(struct etr_buf *etr_buf, u64 rrp, u64 rwp)
+{
+ long r_offset, w_offset;
+ struct etr_sg_table *etr_table = etr_buf->private;
+ struct tmc_sg_table *table = etr_table->sg_table;
+
+ /* Convert hw address to offset in the buffer */
+ r_offset = tmc_sg_get_data_page_offset(table, rrp);
+ if (r_offset < 0) {
+ dev_warn(table->dev,
+ "Unable to map RRP %llx to offset\n", rrp);
+ etr_buf->len = 0;
+ return;
+ }
+
+ w_offset = tmc_sg_get_data_page_offset(table, rwp);
+ if (w_offset < 0) {
+ dev_warn(table->dev,
+ "Unable to map RWP %llx to offset\n", rwp);
+ etr_buf->len = 0;
+ return;
+ }
+
+ etr_buf->offset = r_offset;
+ if (etr_buf->full)
+ etr_buf->len = etr_buf->size;
+ else
+ etr_buf->len = ((w_offset < r_offset) ? etr_buf->size : 0) +
+ w_offset - r_offset;
+ tmc_sg_table_sync_data_range(table, r_offset, etr_buf->len);
+}
+
+static const struct etr_buf_operations etr_sg_buf_ops = {
+ .alloc = tmc_etr_alloc_sg_buf,
+ .free = tmc_etr_free_sg_buf,
+ .sync = tmc_etr_sync_sg_buf,
+ .get_data = tmc_etr_get_data_sg_buf,
+};
+
+/*
+ * TMC ETR could be connected to a CATU device, which can provide address
+ * translation service. This is represented by the Output port of the TMC
+ * (ETR) connected to the input port of the CATU.
+ *
+ * Returns : gtrace_component ptr for the CATU device if a CATU is found.
+ * : NULL otherwise.
+ */
+struct gtrace_component *
+tmc_etr_get_catu_device(struct tmc_drvdata *drvdata)
+{
+ return NULL;
+}
+EXPORT_SYMBOL_GPL(tmc_etr_get_catu_device);
+
+static const struct etr_buf_operations *etr_buf_ops[] = {
+ [ETR_MODE_FLAT] = &etr_flat_buf_ops,
+ [ETR_MODE_ETR_SG] = &etr_sg_buf_ops,
+ [ETR_MODE_CATU] = NULL,
+ [ETR_MODE_RESRV] = &etr_resrv_buf_ops
+};
+
+void tmc_etr_set_catu_ops(const struct etr_buf_operations *catu)
+{
+ etr_buf_ops[ETR_MODE_CATU] = catu;
+}
+EXPORT_SYMBOL_GPL(tmc_etr_set_catu_ops);
+
+void tmc_etr_remove_catu_ops(void)
+{
+ etr_buf_ops[ETR_MODE_CATU] = NULL;
+}
+EXPORT_SYMBOL_GPL(tmc_etr_remove_catu_ops);
+
+static int tmc_etr_mode_alloc_buf(int mode, struct tmc_drvdata *drvdata, struct etr_buf *etr_buf,
+ int node, void **pages)
+{
+ int rc = -EINVAL;
+
+ switch (mode) {
+ case ETR_MODE_FLAT:
+ case ETR_MODE_ETR_SG:
+ case ETR_MODE_CATU:
+ case ETR_MODE_RESRV:
+ if (etr_buf_ops[mode] && etr_buf_ops[mode]->alloc)
+ rc = etr_buf_ops[mode]->alloc(drvdata, etr_buf,
+ node, pages);
+ if (!rc)
+ etr_buf->ops = etr_buf_ops[mode];
+ return rc;
+ default:
+ return -EINVAL;
+ }
+}
+
+static void get_etr_buf_hw(struct device *dev, struct etr_buf_hw *buf_hw)
+{
+ struct tmc_drvdata *drvdata = dev_get_drvdata(dev->parent);
+
+ buf_hw->has_iommu = iommu_get_domain_for_dev(dev->parent);
+ buf_hw->has_etr_sg = tmc_etr_has_cap(drvdata, TMC_ETR_SG);
+ buf_hw->has_catu = !!tmc_etr_get_catu_device(drvdata);
+ buf_hw->has_resrv = tmc_has_reserved_buffer(drvdata);
+}
+
+static bool etr_can_use_flat_mode(struct etr_buf_hw *buf_hw, ssize_t etr_buf_size)
+{
+ bool has_sg = buf_hw->has_catu || buf_hw->has_etr_sg;
+
+ return !has_sg || buf_hw->has_iommu || etr_buf_size < SZ_1M;
+}
+
+/*
+ * tmc_alloc_etr_buf: Allocate a buffer use by ETR.
+ * @drvdata : ETR device details.
+ * @size : size of the requested buffer.
+ * @flags : Required properties for the buffer.
+ * @node : Node for memory allocations.
+ * @pages : An optional list of pages.
+ */
+static struct etr_buf *tmc_alloc_etr_buf(struct tmc_drvdata *drvdata,
+ ssize_t size, int flags,
+ int node, void **pages)
+{
+ int rc = -ENOMEM;
+ struct etr_buf *etr_buf;
+ struct etr_buf_hw buf_hw;
+ struct device *dev = &drvdata->comp->dev;
+
+ get_etr_buf_hw(dev, &buf_hw);
+ etr_buf = kzalloc_obj(*etr_buf);
+ if (!etr_buf)
+ return ERR_PTR(-ENOMEM);
+
+ etr_buf->size = size;
+
+ /* If there is user directive for buffer mode, try that first */
+ if (drvdata->etr_mode != ETR_MODE_AUTO)
+ rc = tmc_etr_mode_alloc_buf(drvdata->etr_mode, drvdata,
+ etr_buf, node, pages);
+
+ /*
+ * If we have to use an existing list of pages, we cannot reliably
+ * use a contiguous DMA memory (even if we have an IOMMU). Otherwise,
+ * we use the contiguous DMA memory if at least one of the following
+ * conditions is true:
+ * a) The ETR cannot use Scatter-Gather.
+ * b) we have a backing IOMMU
+ * c) The requested memory size is smaller (< 1M).
+ *
+ * Fallback to available mechanisms.
+ *
+ */
+ if (rc && !pages && etr_can_use_flat_mode(&buf_hw, size))
+ rc = tmc_etr_mode_alloc_buf(ETR_MODE_FLAT, drvdata,
+ etr_buf, node, pages);
+ if (rc && buf_hw.has_etr_sg)
+ rc = tmc_etr_mode_alloc_buf(ETR_MODE_ETR_SG, drvdata,
+ etr_buf, node, pages);
+ if (rc && buf_hw.has_catu)
+ rc = tmc_etr_mode_alloc_buf(ETR_MODE_CATU, drvdata,
+ etr_buf, node, pages);
+ if (rc) {
+ kfree(etr_buf);
+ return ERR_PTR(rc);
+ }
+
+ /*
+ * Keep a reference to the ETR this buffer has been allocated for
+ * in order to have access to the IDR in tmc_free_etr_buffer().
+ */
+ etr_buf->drvdata = drvdata;
+ /* per-thread buffers are not shared and never enter the idr */
+ etr_buf->pid = -1;
+
+ refcount_set(&etr_buf->refcount, 1);
+ dev_dbg(dev, "allocated buffer of size %ldKB in mode %d\n",
+ (unsigned long)size >> 10, etr_buf->mode);
+ return etr_buf;
+}
+
+static void tmc_free_etr_buf(struct etr_buf *etr_buf)
+{
+ WARN_ON(!etr_buf->ops || !etr_buf->ops->free);
+ etr_buf->ops->free(etr_buf);
+ kfree(etr_buf);
+}
+
+/*
+ * tmc_etr_buf_get_data: Get the pointer the trace data at @offset
+ * with a maximum of @len bytes.
+ * Returns: The size of the linear data available @pos, with *bufpp
+ * updated to point to the buffer.
+ */
+static ssize_t tmc_etr_buf_get_data(struct etr_buf *etr_buf,
+ u64 offset, size_t len, char **bufpp)
+{
+ /* Adjust the length to limit this transaction to end of buffer */
+ len = (len < (etr_buf->size - offset)) ? len : etr_buf->size - offset;
+
+ return etr_buf->ops->get_data(etr_buf, (u64)offset, len, bufpp);
+}
+
+static s64
+tmc_etr_buf_insert_barrier_packet(struct etr_buf *etr_buf, u64 offset)
+{
+ ssize_t len;
+ char *bufp;
+
+ len = tmc_etr_buf_get_data(etr_buf, offset,
+ CORESIGHT_BARRIER_PKT_SIZE, &bufp);
+ if (WARN_ON(len < 0 || len < CORESIGHT_BARRIER_PKT_SIZE))
+ return -EINVAL;
+ coresight_insert_barrier_packet(bufp);
+ return offset + CORESIGHT_BARRIER_PKT_SIZE;
+}
+
+/*
+ * tmc_sync_etr_buf: Sync the trace buffer availability with drvdata.
+ * Makes sure the trace data is synced to the memory for consumption.
+ * @etr_buf->offset will hold the offset to the beginning of the trace data
+ * within the buffer, with @etr_buf->len bytes to consume.
+ */
+static void tmc_sync_etr_buf(struct tmc_drvdata *drvdata)
+{
+ struct etr_buf *etr_buf = drvdata->etr_buf;
+ u64 rrp, rwp;
+ u32 status;
+
+ rrp = tmc_read_rrp(drvdata);
+ rwp = tmc_read_rwp(drvdata);
+ status = readl_relaxed(drvdata->base + TMC_STS);
+
+ /*
+ * If there were memory errors in the session, truncate the
+ * buffer.
+ */
+ if (WARN_ON_ONCE(status & TMC_STS_MEMERR)) {
+ dev_dbg(&drvdata->comp->dev,
+ "tmc memory error detected, truncating buffer\n");
+ etr_buf->len = 0;
+ etr_buf->full = false;
+ return;
+ }
+
+ etr_buf->full = !!(status & TMC_STS_FULL);
+
+ WARN_ON(!etr_buf->ops || !etr_buf->ops->sync);
+
+ etr_buf->ops->sync(etr_buf, rrp, rwp);
+}
+
+static int __tmc_etr_enable_hw(struct tmc_drvdata *drvdata)
+{
+ u32 axictl, sts, ffcr;
+ struct etr_buf *etr_buf = drvdata->etr_buf;
+ int rc = 0;
+
+ CS_UNLOCK(drvdata->base);
+
+ /* Wait for TMCSReady bit to be set */
+ rc = tmc_wait_for_tmcready(drvdata);
+ if (rc) {
+ dev_err(&drvdata->comp->dev,
+ "Failed to enable : TMC not ready\n");
+ CS_LOCK(drvdata->base);
+ return rc;
+ }
+
+ writel_relaxed(etr_buf->size / 4, drvdata->base + TMC_RSZ);
+ writel_relaxed(TMC_MODE_CIRCULAR_BUFFER, drvdata->base + TMC_MODE);
+
+ axictl = readl_relaxed(drvdata->base + TMC_AXICTL);
+ axictl &= ~TMC_AXICTL_CLEAR_MASK;
+ axictl |= TMC_AXICTL_PROT_CTL_B1;
+ axictl |= TMC_AXICTL_WR_BURST(drvdata->max_burst_size);
+ axictl |= TMC_AXICTL_AXCACHE_OS;
+
+ if (tmc_etr_has_cap(drvdata, TMC_ETR_AXI_ARCACHE)) {
+ axictl &= ~TMC_AXICTL_ARCACHE_MASK;
+ axictl |= TMC_AXICTL_ARCACHE_OS;
+ }
+
+ if (etr_buf->mode == ETR_MODE_ETR_SG)
+ axictl |= TMC_AXICTL_SCT_GAT_MODE;
+
+ writel_relaxed(axictl, drvdata->base + TMC_AXICTL);
+ tmc_write_dba(drvdata, etr_buf->hwaddr);
+ /*
+ * If the TMC pointers must be programmed before the session,
+ * we have to set it properly (i.e, RRP/RWP to base address and
+ * STS to "not full").
+ */
+ if (tmc_etr_has_cap(drvdata, TMC_ETR_SAVE_RESTORE)) {
+ tmc_write_rrp(drvdata, etr_buf->hwaddr);
+ tmc_write_rwp(drvdata, etr_buf->hwaddr);
+ sts = readl_relaxed(drvdata->base + TMC_STS) & ~TMC_STS_FULL;
+ writel_relaxed(sts, drvdata->base + TMC_STS);
+ }
+
+ ffcr = TMC_FFCR_EN_FMT | TMC_FFCR_EN_TI | TMC_FFCR_FON_FLIN |
+ TMC_FFCR_FON_TRIG_EVT | TMC_FFCR_TRIGON_TRIGIN;
+ if (drvdata->stop_on_flush)
+ ffcr |= TMC_FFCR_STOP_ON_FLUSH;
+ writel_relaxed(ffcr, drvdata->base + TMC_FFCR);
+
+ writel_relaxed(drvdata->trigger_cntr, drvdata->base + TMC_TRG);
+ tmc_enable_hw(drvdata);
+
+ CS_LOCK(drvdata->base);
+ return rc;
+}
+
+static int tmc_etr_enable_hw(struct tmc_drvdata *drvdata,
+ struct etr_buf *etr_buf)
+{
+ int rc;
+
+ /* Callers should provide an appropriate buffer for use */
+ if (WARN_ON(!etr_buf))
+ return -EINVAL;
+
+ if ((etr_buf->mode == ETR_MODE_ETR_SG) &&
+ WARN_ON(!tmc_etr_has_cap(drvdata, TMC_ETR_SG)))
+ return -EINVAL;
+
+ if (WARN_ON(drvdata->etr_buf))
+ return -EBUSY;
+
+ rc = coresight_claim_device(drvdata->comp);
+ if (!rc) {
+ drvdata->etr_buf = etr_buf;
+ rc = __tmc_etr_enable_hw(drvdata);
+ if (rc) {
+ drvdata->etr_buf = NULL;
+ coresight_disclaim_device(drvdata->comp);
+ }
+ }
+
+ return rc;
+}
+
+/*
+ * Return the available trace data in the buffer (starts at etr_buf->offset,
+ * limited by etr_buf->len) from @pos, with a maximum limit of @len,
+ * also updating the @bufpp on where to find it. Since the trace data
+ * starts at anywhere in the buffer, depending on the RRP, we adjust the
+ * @len returned to handle buffer wrapping around.
+ *
+ * We are protected here by drvdata->reading != 0, which ensures the
+ * sysfs_buf stays alive.
+ */
+ssize_t tmc_etr_get_sysfs_trace(struct tmc_drvdata *drvdata,
+ loff_t pos, size_t len, char **bufpp)
+{
+ s64 offset;
+ ssize_t actual = len;
+ struct etr_buf *etr_buf = drvdata->sysfs_buf;
+
+ if (pos + actual > etr_buf->len)
+ actual = etr_buf->len - pos;
+ if (actual <= 0)
+ return actual;
+
+ /* Compute the offset from which we read the data */
+ offset = etr_buf->offset + pos;
+ if (offset >= etr_buf->size)
+ offset -= etr_buf->size;
+ return tmc_etr_buf_get_data(etr_buf, offset, actual, bufpp);
+}
+
+static struct etr_buf *
+tmc_etr_alloc_sysfs_buf(struct tmc_drvdata *drvdata)
+{
+ return tmc_alloc_etr_buf(drvdata, drvdata->size,
+ 0, cpu_to_node(0), NULL);
+}
+
+static void
+tmc_etr_free_sysfs_buf(struct etr_buf *buf)
+{
+ if (buf)
+ tmc_free_etr_buf(buf);
+}
+
+static void tmc_etr_sync_sysfs_buf(struct tmc_drvdata *drvdata)
+{
+ struct etr_buf *etr_buf = drvdata->etr_buf;
+
+ if (WARN_ON(drvdata->sysfs_buf != etr_buf)) {
+ tmc_etr_free_sysfs_buf(drvdata->sysfs_buf);
+ drvdata->sysfs_buf = NULL;
+ } else {
+ tmc_sync_etr_buf(drvdata);
+ /*
+ * Insert barrier packets at the beginning, if there was
+ * an overflow.
+ */
+ if (etr_buf->full)
+ tmc_etr_buf_insert_barrier_packet(etr_buf,
+ etr_buf->offset);
+ }
+ /* Reset the ETR buf used by hardware */
+ drvdata->etr_buf = NULL;
+}
+
+static void __tmc_etr_disable_hw(struct tmc_drvdata *drvdata)
+{
+ CS_UNLOCK(drvdata->base);
+
+ tmc_flush_and_stop(drvdata);
+ /*
+ * When operating in sysFS mode the content of the buffer needs to be
+ * read before the TMC is disabled.
+ */
+ if (gtrace_get_mode(drvdata->comp) == GTRACE_COMPONENT_MODE_SYSFS)
+ tmc_etr_sync_sysfs_buf(drvdata);
+
+ tmc_disable_hw(drvdata);
+
+ CS_LOCK(drvdata->base);
+
+}
+
+void tmc_etr_disable_hw(struct tmc_drvdata *drvdata)
+{
+ __tmc_etr_disable_hw(drvdata);
+ coresight_disclaim_device(drvdata->comp);
+}
+
+int tmc_etr_setup_sysfs_buf(struct gtrace_component *comp)
+{
+ int ret = 0;
+ unsigned long flags;
+ struct tmc_drvdata *drvdata = dev_get_drvdata(comp->dev.parent);
+ struct etr_buf *sysfs_buf = NULL, *new_buf = NULL, *free_buf = NULL;
+
+ /*
+ * If we are enabling the ETR from disabled state, we need to make
+ * sure we have a buffer with the right size. The etr_buf is not reset
+ * immediately after we stop the tracing in SYSFS mode as we wait for
+ * the user to collect the data. We may be able to reuse the existing
+ * buffer, provided the size matches. Any allocation has to be done
+ * with the lock released.
+ */
+ raw_spin_lock_irqsave(&drvdata->spinlock, flags);
+
+ sysfs_buf = READ_ONCE(drvdata->sysfs_buf);
+ if (!sysfs_buf || (sysfs_buf->size != drvdata->size)) {
+ raw_spin_unlock_irqrestore(&drvdata->spinlock, flags);
+
+ /* Allocate memory with the locks released */
+ free_buf = new_buf = tmc_etr_alloc_sysfs_buf(drvdata);
+ if (IS_ERR(new_buf))
+ return PTR_ERR(new_buf);
+
+ /* Let's try again */
+ raw_spin_lock_irqsave(&drvdata->spinlock, flags);
+ }
+
+ if (drvdata->reading) {
+ ret = -EBUSY;
+ goto out;
+ }
+
+ /*
+ * If we don't have a buffer or it doesn't match the requested size,
+ * use the buffer allocated above. Otherwise reuse the existing buffer.
+ */
+ sysfs_buf = READ_ONCE(drvdata->sysfs_buf);
+ if (!sysfs_buf || (new_buf && sysfs_buf->size != new_buf->size)) {
+ free_buf = sysfs_buf;
+ drvdata->sysfs_buf = new_buf;
+ }
+
+out:
+ raw_spin_unlock_irqrestore(&drvdata->spinlock, flags);
+
+ /* Free memory outside the spinlock if need be */
+ if (free_buf)
+ tmc_etr_free_sysfs_buf(free_buf);
+ return ret;
+}
+
+struct etr_buf *tmc_etr_get_buffer(struct gtrace_component *comp)
+{
+ struct gtrace_path *path = comp->path;
+ struct tmc_drvdata *drvdata = dev_get_drvdata(comp->dev.parent);
+ struct etr_buf *etr_buf;
+
+ switch (path->mode) {
+ case GTRACE_COMPONENT_MODE_SYSFS:
+ etr_buf = drvdata->sysfs_buf;
+ break;
+ case GTRACE_COMPONENT_MODE_PERF:
+ etr_buf = drvdata->perf_buf;
+ break;
+ default:
+ return ERR_PTR(-EINVAL);
+ }
+
+ WARN_ON(!etr_buf);
+
+ return etr_buf;
+}
+EXPORT_SYMBOL_GPL(tmc_etr_get_buffer);
+
+/*
+ * alloc_etr_buf: Allocate ETR buffer for use by perf.
+ * Allocate the largest possible size, scaling down the size by half until it
+ * reaches a minimum limit (1M), beyond which we give up.
+ */
+static struct etr_buf *
+alloc_etr_buf(struct tmc_drvdata *drvdata, struct perf_event *event,
+ int nr_pages, void **pages, bool snapshot)
+{
+ int node;
+ struct etr_buf *etr_buf;
+ ssize_t size;
+
+ node = (event->cpu == -1) ? NUMA_NO_NODE : cpu_to_node(event->cpu);
+
+ /* Use the minimum limit if the required size is smaller */
+ size = (ssize_t)nr_pages << PAGE_SHIFT;
+ size = max_t(ssize_t, size, TMC_ETR_PERF_MIN_BUF_SIZE);
+
+ /*
+ * Try to allocate the required size for this ETR, if failed scale
+ * down until we hit the minimum limit.
+ */
+ do {
+ etr_buf = tmc_alloc_etr_buf(drvdata, size, 0, node, NULL);
+ if (!IS_ERR(etr_buf))
+ return etr_buf;
+ size /= 2;
+ } while (size >= TMC_ETR_PERF_MIN_BUF_SIZE);
+
+ return ERR_PTR(-ENOMEM);
+}
+
+static struct etr_buf *
+get_perf_etr_buf_cpu_wide(struct tmc_drvdata *drvdata,
+ struct perf_event *event, int nr_pages,
+ void **pages, bool snapshot)
+{
+ int ret;
+ pid_t pid = task_pid_nr(event->owner);
+ struct etr_buf *etr_buf;
+
+retry:
+ /*
+ * An gtrace_perf_auxbuf is associated with an event and holds a reference
+ * to the AUX ring buffer that was created for that event. In CPU-wide
+ * N:1 mode multiple events (one per CPU), each with its own AUX ring
+ * buffer, share a sink. As such an gtrace_perf_auxbuf is created for each
+ * event but a single etr_buf associated with the ETR is shared between
+ * them. The last event in a trace session will copy the content of the
+ * etr_buf to its AUX ring buffer. Ring buffer associated to other
+ * events are simply not used an freed as events are destoyed. We still
+ * need to allocate a ring buffer for each event since we don't know
+ * which event will be last.
+ */
+
+ /*
+ * The first thing to do here is check if an etr_buf has already been
+ * allocated for this session. If so it is shared with this event,
+ * otherwise it is created.
+ */
+ mutex_lock(&drvdata->idr_mutex);
+ etr_buf = idr_find(&drvdata->idr, pid);
+ if (etr_buf) {
+ refcount_inc(&etr_buf->refcount);
+ mutex_unlock(&drvdata->idr_mutex);
+ return etr_buf;
+ }
+
+ /* If we made it here no buffer has been allocated, do so now. */
+ mutex_unlock(&drvdata->idr_mutex);
+
+ etr_buf = alloc_etr_buf(drvdata, event, nr_pages, pages, snapshot);
+ if (IS_ERR(etr_buf))
+ return etr_buf;
+
+ /* Now that we have a buffer, add it to the IDR. */
+ mutex_lock(&drvdata->idr_mutex);
+ ret = idr_alloc(&drvdata->idr, etr_buf, pid, pid + 1, GFP_KERNEL);
+ mutex_unlock(&drvdata->idr_mutex);
+
+ /* Another event with this session ID has allocated this buffer. */
+ if (ret == -ENOSPC) {
+ tmc_free_etr_buf(etr_buf);
+ goto retry;
+ }
+
+ /* The IDR can't allocate room for a new session, abandon ship. */
+ if (ret == -ENOMEM) {
+ tmc_free_etr_buf(etr_buf);
+ return ERR_PTR(ret);
+ }
+
+ /*
+ * Keep a reference to the ETR this buffer has been allocated for
+ * in order to have access to the IDR in tmc_free_etr_buffer().
+ */
+ etr_buf->drvdata = drvdata;
+ /* cpu-wide buffers are shared and tracked in the idr by session pid */
+ etr_buf->pid = pid;
+
+ return etr_buf;
+}
+
+static struct etr_buf *
+get_perf_etr_buf_per_thread(struct tmc_drvdata *drvdata,
+ struct perf_event *event, int nr_pages,
+ void **pages, bool snapshot)
+{
+ /*
+ * In per-thread mode the etr_buf isn't shared, so just go ahead
+ * with memory allocation.
+ */
+ return alloc_etr_buf(drvdata, event, nr_pages, pages, snapshot);
+}
+
+static struct etr_buf *
+get_perf_etr_buf(struct tmc_drvdata *drvdata, struct perf_event *event,
+ int nr_pages, void **pages, bool snapshot)
+{
+ if (event->cpu == -1)
+ return get_perf_etr_buf_per_thread(drvdata, event, nr_pages,
+ pages, snapshot);
+
+ return get_perf_etr_buf_cpu_wide(drvdata, event, nr_pages,
+ pages, snapshot);
+}
+
+void *tmc_alloc_etr_buffer(struct gtrace_component *comp,
+ struct perf_event *event, void **pages,
+ int nr_pages, bool snapshot)
+{
+ int node;
+ struct etr_buf *etr_buf;
+ struct tmc_drvdata *drvdata = dev_get_drvdata(comp->dev.parent);
+
+ node = (event->cpu == -1) ? NUMA_NO_NODE : cpu_to_node(event->cpu);
+
+ etr_buf = get_perf_etr_buf(drvdata, event, nr_pages, pages, snapshot);
+ if (IS_ERR(etr_buf))
+ return NULL;
+
+ drvdata->perf_buf = etr_buf;
+
+ return etr_buf;
+}
+
+void tmc_free_etr_buffer(void *config)
+{
+ struct etr_buf *buf = NULL, *etr_buf = config;
+ struct tmc_drvdata *drvdata;
+
+ drvdata = etr_buf->drvdata;
+
+ mutex_lock(&drvdata->idr_mutex);
+ /* If we are not the last one to use the buffer, don't touch it. */
+ if (!refcount_dec_and_test(&etr_buf->refcount)) {
+ mutex_unlock(&drvdata->idr_mutex);
+ return;
+ }
+
+ /* only shared buffers are present in the idr */
+ if (etr_buf->pid != -1) {
+ /* We are the last one, remove from the IDR and free the buffer. */
+ buf = idr_remove(&drvdata->idr, etr_buf->pid);
+
+ }
+ mutex_unlock(&drvdata->idr_mutex);
+
+ /*
+ * Something went very wrong if the buffer associated with this ID
+ * is not the same in the IDR. Leak to avoid use after free.
+ */
+ if (buf && WARN_ON(buf != etr_buf))
+ return;
+
+ tmc_free_etr_buf(etr_buf);
+ /* Reset perf specific data */
+ drvdata->perf_buf = NULL;
+}
+
+/*
+ * tmc_etr_sync_perf_buffer: Copy the actual trace data from the hardware
+ * buffer to the perf ring buffer.
+ */
+static void tmc_etr_sync_perf_buffer(struct gtrace_perf_auxbuf *etr_perf,
+ unsigned long head,
+ unsigned long src_offset,
+ unsigned long to_copy)
+{
+ long bytes;
+ long pg_idx, pg_offset;
+ char **dst_pages, *src_buf;
+ struct etr_buf *etr_buf = etr_perf->buf;
+
+ head = PERF_IDX2OFF(head, etr_perf);
+ pg_idx = head >> PAGE_SHIFT;
+ pg_offset = head & (PAGE_SIZE - 1);
+ dst_pages = (char **)etr_perf->pages;
+
+ while (to_copy > 0) {
+ /*
+ * In one iteration, we can copy minimum of :
+ * 1) what is available in the source buffer,
+ * 2) what is available in the source buffer, before it
+ * wraps around.
+ * 3) what is available in the destination page.
+ * in one iteration.
+ */
+ if (src_offset >= etr_buf->size)
+ src_offset -= etr_buf->size;
+ bytes = tmc_etr_buf_get_data(etr_buf, src_offset, to_copy,
+ &src_buf);
+ if (WARN_ON_ONCE(bytes <= 0))
+ break;
+ bytes = min(bytes, (long)(PAGE_SIZE - pg_offset));
+
+ memcpy(dst_pages[pg_idx] + pg_offset, src_buf, bytes);
+
+ to_copy -= bytes;
+
+ /* Move destination pointers */
+ pg_offset += bytes;
+ if (pg_offset == PAGE_SIZE) {
+ pg_offset = 0;
+ if (++pg_idx == etr_perf->nr_pages)
+ pg_idx = 0;
+ }
+
+ /* Move source pointers */
+ src_offset += bytes;
+ }
+}
+
+/*
+ * tmc_update_etr_buffer : Update the perf ring buffer with the
+ * available trace data. We use software double buffering at the moment.
+ *
+ * TODO: Add support for reusing the perf ring buffer.
+ */
+unsigned long
+tmc_update_etr_buffer(struct gtrace_component *comp,
+ struct perf_output_handle *handle,
+ struct gtrace_perf_auxbuf *etr_perf)
+{
+ bool lost = false;
+ unsigned long offset, size = 0;
+ struct tmc_drvdata *drvdata = dev_get_drvdata(comp->dev.parent);
+ struct etr_buf *etr_buf = etr_perf->buf;
+ struct perf_event *event = handle->event;
+
+ if (WARN_ON(drvdata->perf_buf != etr_buf)) {
+ lost = true;
+ goto out;
+ }
+
+ CS_UNLOCK(drvdata->base);
+
+ tmc_flush_and_stop(drvdata);
+ tmc_sync_etr_buf(drvdata);
+
+ CS_LOCK(drvdata->base);
+
+ lost = etr_buf->full;
+ offset = etr_buf->offset;
+ size = etr_buf->len;
+
+ /*
+ * The ETR buffer may be bigger than the space available in the
+ * perf ring buffer (handle->size). If so advance the offset so that we
+ * get the latest trace data. In snapshot mode none of that matters
+ * since we are expected to clobber stale data in favour of the latest
+ * traces.
+ */
+ if (!etr_perf->snapshot && size > handle->size) {
+ u32 mask = tmc_get_memwidth_mask(drvdata);
+
+ /*
+ * Make sure the new size is aligned in accordance with the
+ * requirement explained in function tmc_get_memwidth_mask().
+ */
+ size = handle->size & mask;
+ offset = etr_buf->offset + etr_buf->len - size;
+
+ if (offset >= etr_buf->size)
+ offset -= etr_buf->size;
+ lost = true;
+ }
+
+ /* Insert barrier packets at the beginning, if there was an overflow */
+ if (lost)
+ tmc_etr_buf_insert_barrier_packet(etr_buf, offset);
+ tmc_etr_sync_perf_buffer(etr_perf, handle->head, offset, size);
+
+ /*
+ * In snapshot mode we simply increment the head by the number of byte
+ * that were written. User space will figure out how many bytes to get
+ * from the AUX buffer based on the position of the head.
+ */
+ if (etr_perf->snapshot)
+ handle->head += size;
+
+ /*
+ * Ensure that the AUX trace data is visible before the aux_head
+ * is updated via perf_aux_output_end(), as expected by the
+ * perf ring buffer.
+ */
+ smp_wmb();
+
+ /*
+ * If the event is active, it is triggered during an AUX pause.
+ * Re-enable the sink so that it is ready when AUX resume is invoked.
+ */
+ if (!event->hw.state)
+ __tmc_etr_enable_hw(drvdata);
+
+ /* Reset the ETR buf used by hardware */
+ if (event->hw.state == PERF_HES_STOPPED)
+ drvdata->etr_buf = NULL;
+
+ /* set the GTRACE_FORMAT_CORESIGHT flag */
+ perf_aux_output_flag(handle, PERF_AUX_FLAG_GTRACE_FORMAT_CORESIGHT);
+out:
+ /*
+ * Don't set the TRUNCATED flag in snapshot mode because 1) the
+ * captured buffer is expected to be truncated and 2) a full buffer
+ * prevents the event from being re-enabled by the perf core,
+ * resulting in stale data being send to user space.
+ */
+ if (!etr_perf->snapshot && lost)
+ perf_aux_output_flag(handle, PERF_AUX_FLAG_TRUNCATED);
+ return size;
+}
+
+int tmc_enable_etr_sink(struct gtrace_component *comp)
+{
+ struct tmc_drvdata *drvdata = dev_get_drvdata(comp->dev.parent);
+ struct etr_buf *etr_buf;
+ int ret;
+
+ etr_buf = tmc_etr_get_buffer(comp);
+ if (IS_ERR(etr_buf))
+ return PTR_ERR(etr_buf);
+
+ ret = tmc_etr_enable_hw(drvdata, etr_buf);
+ if (!ret)
+ dev_dbg(&comp->dev, "TMC-ETR enabled\n");
+
+ return ret;
+}
+
+int tmc_disable_etr_sink(struct gtrace_component *comp)
+{
+ struct tmc_drvdata *drvdata = dev_get_drvdata(comp->dev.parent);
+
+ if (drvdata->reading)
+ return -EBUSY;
+
+ tmc_etr_disable_hw(drvdata);
+
+ dev_dbg(&comp->dev, "TMC-ETR disabled\n");
+ return 0;
+}
+
+int tmc_panic_sync_etr(struct gtrace_component *comp)
+{
+ u32 val;
+ struct tmc_crash_metadata *mdata;
+ struct tmc_drvdata *drvdata = dev_get_drvdata(comp->dev.parent);
+
+ mdata = (struct tmc_crash_metadata *)drvdata->crash_mdata.vaddr;
+
+ if (!drvdata->etr_buf)
+ return 0;
+
+ /* Being in RESRV mode implies valid reserved memory as well */
+ if (drvdata->etr_buf->mode != ETR_MODE_RESRV)
+ return 0;
+
+ if (!tmc_has_crash_mdata_buffer(drvdata))
+ return 0;
+
+ CS_UNLOCK(drvdata->base);
+
+ /* Proceed only if ETR is enabled */
+ val = readl(drvdata->base + TMC_CTL);
+ if (!(val & TMC_CTL_CAPT_EN))
+ goto out;
+
+ val = readl(drvdata->base + TMC_FFSR);
+ /* Do manual flush and stop only if its not auto-stopped */
+ if (!(val & TMC_FFSR_FT_STOPPED)) {
+ dev_dbg(&comp->dev,
+ "%s: Triggering manual flush\n", __func__);
+ tmc_flush_and_stop(drvdata);
+ } else
+ tmc_wait_for_tmcready(drvdata);
+
+ /* Sync registers from hardware to metadata region */
+ mdata->tmc_ram_size = readl(drvdata->base + TMC_RSZ);
+ mdata->tmc_sts = readl(drvdata->base + TMC_STS);
+ mdata->tmc_mode = readl(drvdata->base + TMC_MODE);
+ mdata->tmc_ffcr = readl(drvdata->base + TMC_FFCR);
+ mdata->tmc_ffsr = readl(drvdata->base + TMC_FFSR);
+ mdata->tmc_rrp = tmc_read_rrp(drvdata);
+ mdata->tmc_rwp = tmc_read_rwp(drvdata);
+ mdata->tmc_dba = tmc_read_dba(drvdata);
+ mdata->trace_paddr = drvdata->resrv_buf.paddr;
+ mdata->version = CS_CRASHDATA_VERSION;
+
+ /*
+ * Make sure all previous writes are ordered,
+ * before we mark valid
+ */
+ smp_wmb();
+ mdata->valid = true;
+ /*
+ * Below order need to maintained, since crc of metadata
+ * is dependent on first
+ */
+ mdata->crc32_tdata = find_crash_tracedata_crc(drvdata, mdata);
+ mdata->crc32_mdata = find_crash_metadata_crc(mdata);
+
+ tmc_disable_hw(drvdata);
+
+ dev_dbg(&comp->dev, "%s: success\n", __func__);
+out:
+ CS_UNLOCK(drvdata->base);
+
+ return 0;
+}
+
+int tmc_read_prepare_etr(struct tmc_drvdata *drvdata)
+{
+ int ret = 0;
+ unsigned long flags;
+
+ /* config types are set a boot time and never change */
+ if (WARN_ON_ONCE(drvdata->config_type != TMC_CONFIG_TYPE_ETR))
+ return -EINVAL;
+
+ raw_spin_lock_irqsave(&drvdata->spinlock, flags);
+ if (drvdata->reading) {
+ ret = -EBUSY;
+ goto out;
+ }
+
+ /*
+ * We can safely allow reads even if the ETR is operating in PERF mode,
+ * since the sysfs session is captured in mode specific data.
+ * If drvdata::sysfs_data is NULL the trace data has been read already.
+ */
+ if (!drvdata->sysfs_buf) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ /* Disable the TMC if we are trying to read from a running session. */
+ if (gtrace_get_mode(drvdata->comp) == GTRACE_COMPONENT_MODE_SYSFS)
+ __tmc_etr_disable_hw(drvdata);
+
+ drvdata->reading = true;
+out:
+ raw_spin_unlock_irqrestore(&drvdata->spinlock, flags);
+
+ return ret;
+}
+
+int tmc_read_unprepare_etr(struct tmc_drvdata *drvdata)
+{
+ unsigned long flags;
+ struct etr_buf *sysfs_buf = NULL;
+
+ /* config types are set a boot time and never change */
+ if (WARN_ON_ONCE(drvdata->config_type != TMC_CONFIG_TYPE_ETR))
+ return -EINVAL;
+
+ raw_spin_lock_irqsave(&drvdata->spinlock, flags);
+
+ /* RE-enable the TMC if need be */
+ if (gtrace_get_mode(drvdata->comp) == GTRACE_COMPONENT_MODE_SYSFS) {
+ /*
+ * The trace run will continue with the same allocated trace
+ * buffer. Since the tracer is still enabled drvdata::buf can't
+ * be NULL.
+ */
+ __tmc_etr_enable_hw(drvdata);
+ } else {
+ /*
+ * The ETR is not tracing and the buffer was just read.
+ * As such prepare to free the trace buffer.
+ */
+ sysfs_buf = drvdata->sysfs_buf;
+ drvdata->sysfs_buf = NULL;
+ }
+
+ drvdata->reading = false;
+ raw_spin_unlock_irqrestore(&drvdata->spinlock, flags);
+
+ /* Free allocated memory out side of the spinlock */
+ if (sysfs_buf)
+ tmc_etr_free_sysfs_buf(sysfs_buf);
+
+ return 0;
+}
+
+static const char *const buf_modes_str[] = {
+ [ETR_MODE_FLAT] = "flat",
+ [ETR_MODE_ETR_SG] = "tmc-sg",
+ [ETR_MODE_CATU] = "catu",
+ [ETR_MODE_RESRV] = "resrv",
+ [ETR_MODE_AUTO] = "auto",
+};
+
+static ssize_t buf_modes_available_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct etr_buf_hw buf_hw;
+ ssize_t size = 0;
+
+ get_etr_buf_hw(dev, &buf_hw);
+ size += sysfs_emit(buf, "%s ", buf_modes_str[ETR_MODE_AUTO]);
+ size += sysfs_emit_at(buf, size, "%s ", buf_modes_str[ETR_MODE_FLAT]);
+ if (buf_hw.has_etr_sg)
+ size += sysfs_emit_at(buf, size, "%s ", buf_modes_str[ETR_MODE_ETR_SG]);
+
+ if (buf_hw.has_catu)
+ size += sysfs_emit_at(buf, size, "%s ", buf_modes_str[ETR_MODE_CATU]);
+
+ if (buf_hw.has_resrv)
+ size += sysfs_emit_at(buf, size, "%s ", buf_modes_str[ETR_MODE_RESRV]);
+
+ size += sysfs_emit_at(buf, size, "\n");
+ return size;
+}
+static DEVICE_ATTR_RO(buf_modes_available);
+
+static ssize_t buf_mode_preferred_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct tmc_drvdata *drvdata = dev_get_drvdata(dev->parent);
+
+ return sysfs_emit(buf, "%s\n", buf_modes_str[drvdata->etr_mode]);
+}
+
+static int buf_mode_set_resrv(struct tmc_drvdata *drvdata)
+{
+ int err = -EBUSY;
+ unsigned long flags;
+ struct tmc_resrv_buf *rbuf;
+
+ rbuf = &drvdata->resrv_buf;
+
+ /* Ensure there are no active crashdata read sessions */
+ raw_spin_lock_irqsave(&drvdata->spinlock, flags);
+ if (!rbuf->reading) {
+ tmc_crashdata_set_invalid(drvdata);
+ rbuf->len = 0;
+ drvdata->etr_mode = ETR_MODE_RESRV;
+ err = 0;
+ }
+ raw_spin_unlock_irqrestore(&drvdata->spinlock, flags);
+ return err;
+}
+
+static ssize_t buf_mode_preferred_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct tmc_drvdata *drvdata = dev_get_drvdata(dev->parent);
+ struct etr_buf_hw buf_hw;
+
+ get_etr_buf_hw(dev, &buf_hw);
+ if (sysfs_streq(buf, buf_modes_str[ETR_MODE_FLAT]))
+ drvdata->etr_mode = ETR_MODE_FLAT;
+ else if (sysfs_streq(buf, buf_modes_str[ETR_MODE_ETR_SG]) && buf_hw.has_etr_sg)
+ drvdata->etr_mode = ETR_MODE_ETR_SG;
+ else if (sysfs_streq(buf, buf_modes_str[ETR_MODE_CATU]) && buf_hw.has_catu)
+ drvdata->etr_mode = ETR_MODE_CATU;
+ else if (sysfs_streq(buf, buf_modes_str[ETR_MODE_RESRV]) && buf_hw.has_resrv)
+ return buf_mode_set_resrv(drvdata) ? : size;
+ else if (sysfs_streq(buf, buf_modes_str[ETR_MODE_AUTO]))
+ drvdata->etr_mode = ETR_MODE_AUTO;
+ else
+ return -EINVAL;
+ return size;
+}
+static DEVICE_ATTR_RW(buf_mode_preferred);
+
+static struct attribute *coresight_etr_attrs[] = {
+ &dev_attr_buf_modes_available.attr,
+ &dev_attr_buf_mode_preferred.attr,
+ NULL,
+};
+
+const struct attribute_group coresight_etr_group = {
+ .attrs = coresight_etr_attrs,
+};
diff --git a/drivers/hwtracing/gtrace/coresight-tmc.h b/drivers/hwtracing/gtrace/coresight-tmc.h
new file mode 100644
index 000000000000..78281e04f243
--- /dev/null
+++ b/drivers/hwtracing/gtrace/coresight-tmc.h
@@ -0,0 +1,469 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright(C) 2015 Linaro Limited. All rights reserved.
+ * Author: Mathieu Poirier <mathieu.poirier@linaro.org>
+ * Derived from drivers/hwtracing/coresight/coresight-tmc.h
+ */
+
+#ifndef _CORESIGHT_TMC_H
+#define _CORESIGHT_TMC_H
+
+#include <linux/dma-mapping.h>
+#include <linux/idr.h>
+#include <linux/miscdevice.h>
+#include <linux/mutex.h>
+#include <linux/refcount.h>
+#include <linux/crc32.h>
+
+#define TMC_RSZ 0x004
+#define TMC_STS 0x00c
+#define TMC_RRD 0x010
+#define TMC_RRP 0x014
+#define TMC_RWP 0x018
+#define TMC_TRG 0x01c
+#define TMC_CTL 0x020
+#define TMC_RWD 0x024
+#define TMC_MODE 0x028
+#define TMC_LBUFLEVEL 0x02c
+#define TMC_CBUFLEVEL 0x030
+#define TMC_BUFWM 0x034
+#define TMC_RRPHI 0x038
+#define TMC_RWPHI 0x03c
+#define TMC_AXICTL 0x110
+#define TMC_DBALO 0x118
+#define TMC_DBAHI 0x11c
+#define TMC_FFSR 0x300
+#define TMC_FFCR 0x304
+#define TMC_PSCR 0x308
+#define TMC_ITMISCOP0 0xee0
+#define TMC_ITTRFLIN 0xee8
+#define TMC_ITATBDATA0 0xeec
+#define TMC_ITATBCTR2 0xef0
+#define TMC_ITATBCTR1 0xef4
+#define TMC_ITATBCTR0 0xef8
+#define TMC_AUTHSTATUS 0xfb8
+
+/* register description */
+/* TMC_CTL - 0x020 */
+#define TMC_CTL_CAPT_EN BIT(0)
+/* TMC_STS - 0x00C */
+#define TMC_STS_TMCREADY_BIT 2
+#define TMC_STS_FULL BIT(0)
+#define TMC_STS_TRIGGERED BIT(1)
+#define TMC_STS_MEMERR BIT(5)
+/*
+ * TMC_AXICTL - 0x110
+ *
+ * TMC AXICTL format for SoC-400
+ * Bits [0-1] : ProtCtrlBit0-1
+ * Bits [2-5] : CacheCtrlBits 0-3 (AXCACHE)
+ * Bit 6 : Reserved
+ * Bit 7 : ScatterGatherMode
+ * Bits [8-11] : WrBurstLen
+ * Bits [12-31] : Reserved.
+ * TMC AXICTL format for SoC-600, as above except:
+ * Bits [2-5] : AXI WCACHE
+ * Bits [16-19] : AXI RCACHE
+ * Bits [20-31] : Reserved
+ */
+#define TMC_AXICTL_CLEAR_MASK 0xfbf
+#define TMC_AXICTL_ARCACHE_MASK (0xf << 16)
+
+#define TMC_AXICTL_PROT_CTL_B0 BIT(0)
+#define TMC_AXICTL_PROT_CTL_B1 BIT(1)
+#define TMC_AXICTL_SCT_GAT_MODE BIT(7)
+#define TMC_AXICTL_WR_BURST(v) (((v) & 0xf) << 8)
+#define TMC_AXICTL_WR_BURST_16 0xf
+/* Write-back Read and Write-allocate */
+#define TMC_AXICTL_AXCACHE_OS (0xf << 2)
+#define TMC_AXICTL_ARCACHE_OS (0xf << 16)
+
+/* TMC_FFSR - 0x300 */
+#define TMC_FFSR_FT_STOPPED BIT(1)
+
+/* TMC_FFCR - 0x304 */
+#define TMC_FFCR_FLUSHMAN_BIT 6
+#define TMC_FFCR_EN_FMT BIT(0)
+#define TMC_FFCR_EN_TI BIT(1)
+#define TMC_FFCR_FON_FLIN BIT(4)
+#define TMC_FFCR_FON_TRIG_EVT BIT(5)
+#define TMC_FFCR_TRIGON_TRIGIN BIT(8)
+#define TMC_FFCR_STOP_ON_FLUSH BIT(12)
+
+
+#define TMC_DEVID_NOSCAT BIT(24)
+
+#define TMC_DEVID_AXIAW_VALID BIT(16)
+#define TMC_DEVID_AXIAW_SHIFT 17
+#define TMC_DEVID_AXIAW_MASK 0x7f
+
+#define TMC_AUTH_NSID_MASK GENMASK(1, 0)
+
+/* Major version 1 Minor version 0 */
+#define CS_CRASHDATA_VERSION (1 << 16)
+
+enum tmc_config_type {
+ TMC_CONFIG_TYPE_ETB,
+ TMC_CONFIG_TYPE_ETR,
+ TMC_CONFIG_TYPE_ETF,
+};
+
+enum tmc_mode {
+ TMC_MODE_CIRCULAR_BUFFER,
+ TMC_MODE_SOFTWARE_FIFO,
+ TMC_MODE_HARDWARE_FIFO,
+};
+
+enum tmc_mem_intf_width {
+ TMC_MEM_INTF_WIDTH_32BITS = 1,
+ TMC_MEM_INTF_WIDTH_64BITS = 2,
+ TMC_MEM_INTF_WIDTH_128BITS = 4,
+ TMC_MEM_INTF_WIDTH_256BITS = 8,
+};
+
+/* TMC ETR Capability bit definitions */
+#define TMC_ETR_SG (0x1U << 0)
+/* ETR has separate read/write cache encodings */
+#define TMC_ETR_AXI_ARCACHE (0x1U << 1)
+/*
+ * TMC_ETR_SAVE_RESTORE - Values of RRP/RWP/STS.Full are
+ * retained when TMC leaves Disabled state, allowing us to continue
+ * the tracing from a point where we stopped. This also implies that
+ * the RRP/RWP/STS.Full should always be programmed to the correct
+ * value. Unfortunately this is not advertised by the hardware,
+ * so we have to rely on PID of the IP to detect the functionality.
+ */
+#define TMC_ETR_SAVE_RESTORE (0x1U << 2)
+
+/* Coresight SoC-600 TMC-ETR unadvertised capabilities */
+#define CORESIGHT_SOC_600_ETR_CAPS \
+ (TMC_ETR_SAVE_RESTORE | TMC_ETR_AXI_ARCACHE)
+
+/* TMC metadata region for ETR and ETF configurations */
+struct tmc_crash_metadata {
+ uint32_t crc32_mdata; /* crc of metadata */
+ uint32_t crc32_tdata; /* crc of tracedata */
+ uint32_t version; /* 31:16 Major version, 15:0 Minor version */
+ uint32_t valid; /* Indicate if this ETF/ETR was enabled */
+ uint32_t tmc_ram_size; /* Ram Size register */
+ uint32_t tmc_sts; /* Status register */
+ uint32_t tmc_mode; /* Mode register */
+ uint32_t tmc_ffcr; /* Formatter and flush control register */
+ uint32_t tmc_ffsr; /* Formatter and flush status register */
+ uint32_t reserved32;
+ uint64_t tmc_rrp; /* Ram Read pointer register */
+ uint64_t tmc_rwp; /* Ram Write pointer register */
+ uint64_t tmc_dba; /* Data buffer address register */
+ uint64_t trace_paddr; /* Phys address of trace buffer */
+ uint64_t reserved64[3];
+};
+
+enum etr_mode {
+ ETR_MODE_FLAT, /* Uses contiguous flat buffer */
+ ETR_MODE_ETR_SG, /* Uses in-built TMC ETR SG mechanism */
+ ETR_MODE_CATU, /* Use SG mechanism in CATU */
+ ETR_MODE_RESRV, /* Use reserved region contiguous buffer */
+ ETR_MODE_AUTO, /* Use the default mechanism */
+};
+
+struct tmc_drvdata;
+struct etr_buf_operations;
+
+/**
+ * struct etr_buf - Details of the buffer used by ETR
+ * drvdata ; The ETR drvdaga this buffer has been allocated for.
+ * @pid ; The PID of the session owner that etr_buf belongs to.
+ * refcount ; Number of sources currently using this etr_buf.
+ * @mode : Mode of the ETR buffer, contiguous, Scatter Gather etc.
+ * @full : Trace data overflow
+ * @size : Size of the buffer.
+ * @hwaddr : Address to be programmed in the TMC:DBA{LO,HI}
+ * @offset : Offset of the trace data in the buffer for consumption.
+ * @len : Available trace data @buf (may round up to the beginning).
+ * @ops : ETR buffer operations for the mode.
+ * @private : Backend specific information for the buf
+ */
+struct etr_buf {
+ struct tmc_drvdata *drvdata;
+ pid_t pid;
+ refcount_t refcount;
+ enum etr_mode mode;
+ bool full;
+ ssize_t size;
+ dma_addr_t hwaddr;
+ unsigned long offset;
+ s64 len;
+ const struct etr_buf_operations *ops;
+ void *private;
+};
+
+/**
+ * @paddr : Start address of reserved memory region.
+ * @vaddr : Corresponding CPU virtual address.
+ * @size : Size of reserved memory region.
+ * @offset : Offset of the trace data in the buffer for consumption.
+ * @reading : Flag to indicate if reading is active
+ * @len : Available trace data @buf (may round up to the beginning).
+ */
+struct tmc_resrv_buf {
+ phys_addr_t paddr;
+ void *vaddr;
+ size_t size;
+ unsigned long offset;
+ bool reading;
+ s64 len;
+};
+
+/**
+ * struct tmc_drvdata - specifics associated to an TMC component
+ * @atclk: optional clock for the core parts of the TMC.
+ * @pclk: APB clock if present, otherwise NULL
+ * @base: memory mapped base address for this component.
+ * @comp: component vitals needed by the framework.
+ * @miscdev: specifics to handle "/dev/xyz.tmc" entry.
+ * @crashdev: specifics to handle "/dev/crash_tmc_xyz" entry for reading
+ * crash tracedata.
+ * @spinlock: only one at a time pls.
+ * @reading: buffer's in the reading through "/dev/xyz.tmc" entry
+ * @stop_on_flush: Stop on flush trigger user configuration.
+ * @is_sink: True if the ETF is configured as a sink.
+ * @buf: Snapshot of the trace data for ETF/ETB.
+ * @etr_buf: details of buffer used in TMC-ETR
+ * @len: size of the available trace for ETF/ETB.
+ * @size: trace buffer size for this TMC (common for all modes).
+ * @max_burst_size: The maximum burst size that can be initiated by
+ * TMC-ETR on AXI bus.
+ * @config_type: TMC variant, must be of type @tmc_config_type.
+ * @memwidth: width of the memory interface databus, in bytes.
+ * @trigger_cntr: amount of words to store after a trigger.
+ * @etr_caps: Bitmask of capabilities of the TMC ETR, inferred from the
+ * device configuration register (DEVID)
+ * @etr_mode: User preferred mode of the ETR device, default auto mode.
+ * @idr: Holds etr_bufs allocated for this ETR.
+ * @idr_mutex: Access serialisation for idr.
+ * @sysfs_buf: SYSFS buffer for ETR.
+ * @perf_buf: PERF buffer for ETR.
+ * @resrv_buf: Used by ETR as hardware trace buffer and for trace data
+ * retention (after crash) only when ETR_MODE_RESRV buffer
+ * mode is enabled. Used by ETF for trace data retention
+ * (after crash) by default.
+ * @crash_mdata: Reserved memory for storing tmc crash metadata.
+ * Used by ETR/ETF.
+ */
+struct tmc_drvdata {
+ struct clk *atclk;
+ struct clk *pclk;
+ void __iomem *base;
+ struct gtrace_component *comp;
+ struct miscdevice miscdev;
+ struct miscdevice crashdev;
+ raw_spinlock_t spinlock;
+ bool reading;
+ bool stop_on_flush;
+ bool is_sink;
+ union {
+ char *buf; /* TMC ETB */
+ struct etr_buf *etr_buf; /* TMC ETR */
+ };
+ u32 len;
+ u32 size;
+ u32 max_burst_size;
+ enum tmc_config_type config_type;
+ enum tmc_mem_intf_width memwidth;
+ u32 trigger_cntr;
+ u32 etr_caps;
+ enum etr_mode etr_mode;
+ struct idr idr;
+ struct mutex idr_mutex;
+ struct etr_buf *sysfs_buf;
+ struct etr_buf *perf_buf;
+ struct tmc_resrv_buf resrv_buf;
+ struct tmc_resrv_buf crash_mdata;
+};
+
+struct etr_buf_operations {
+ int (*alloc)(struct tmc_drvdata *drvdata, struct etr_buf *etr_buf,
+ int node, void **pages);
+ void (*sync)(struct etr_buf *etr_buf, u64 rrp, u64 rwp);
+ ssize_t (*get_data)(struct etr_buf *etr_buf, u64 offset, size_t len,
+ char **bufpp);
+ void (*free)(struct etr_buf *etr_buf);
+};
+
+/**
+ * struct tmc_pages - Collection of pages used for SG.
+ * @nr_pages: Number of pages in the list.
+ * @daddrs: Array of DMA'able page address.
+ * @pages: Array pages for the buffer.
+ */
+struct tmc_pages {
+ int nr_pages;
+ dma_addr_t *daddrs;
+ struct page **pages;
+};
+
+/*
+ * struct tmc_sg_table - Generic SG table for TMC
+ * @dev: Device for DMA allocations
+ * @table_vaddr: Contiguous Virtual address for PageTable
+ * @data_vaddr: Contiguous Virtual address for Data Buffer
+ * @table_daddr: DMA address of the PageTable base
+ * @node: Node for Page allocations
+ * @table_pages: List of pages & dma address for Table
+ * @data_pages: List of pages & dma address for Data
+ */
+struct tmc_sg_table {
+ struct device *dev;
+ void *table_vaddr;
+ void *data_vaddr;
+ dma_addr_t table_daddr;
+ int node;
+ struct tmc_pages table_pages;
+ struct tmc_pages data_pages;
+};
+
+/* Generic functions */
+int tmc_wait_for_tmcready(struct tmc_drvdata *drvdata);
+void tmc_flush_and_stop(struct tmc_drvdata *drvdata);
+void tmc_enable_hw(struct tmc_drvdata *drvdata);
+void tmc_disable_hw(struct tmc_drvdata *drvdata);
+u32 tmc_get_memwidth_mask(struct tmc_drvdata *drvdata);
+int tmc_read_prepare_crashdata(struct tmc_drvdata *drvdata);
+
+/* ETB/ETF functions */
+int tmc_read_prepare_etb(struct tmc_drvdata *drvdata);
+int tmc_read_unprepare_etb(struct tmc_drvdata *drvdata);
+int tmc_etf_setup_sysfs_buf(struct gtrace_component *comp);
+unsigned long tmc_update_etf_buffer(struct gtrace_component *comp,
+ struct perf_output_handle *handle,
+ struct gtrace_perf_auxbuf *buf);
+int tmc_enable_etf(struct gtrace_component *comp);
+int tmc_disable_etf(struct gtrace_component *comp);
+int tmc_panic_sync_etf(struct gtrace_component *comp);
+
+ssize_t tmc_etb_get_sysfs_trace(struct tmc_drvdata *drvdata,
+ loff_t pos, size_t len, char **bufpp);
+/* ETR functions */
+int tmc_read_prepare_etr(struct tmc_drvdata *drvdata);
+int tmc_read_unprepare_etr(struct tmc_drvdata *drvdata);
+void tmc_etr_disable_hw(struct tmc_drvdata *drvdata);
+int tmc_etr_setup_sysfs_buf(struct gtrace_component *comp);
+void *tmc_alloc_etr_buffer(struct gtrace_component *comp,
+ struct perf_event *event, void **pages,
+ int nr_pages, bool snapshot);
+void tmc_free_etr_buffer(void *config);
+unsigned long
+tmc_update_etr_buffer(struct gtrace_component *comp,
+ struct perf_output_handle *handle,
+ struct gtrace_perf_auxbuf *buf);
+int tmc_enable_etr_sink(struct gtrace_component *comp);
+int tmc_disable_etr_sink(struct gtrace_component *comp);
+int tmc_panic_sync_etr(struct gtrace_component *comp);
+ssize_t tmc_etr_get_sysfs_trace(struct tmc_drvdata *drvdata,
+ loff_t pos, size_t len, char **bufpp);
+
+
+#define TMC_REG_PAIR(name, lo_off, hi_off) \
+static inline u64 \
+tmc_read_##name(struct tmc_drvdata *drvdata) \
+{ \
+ return gtrace_relaxed_read_pair(drvdata->comp->pdata, lo_off, hi_off); \
+} \
+static inline void \
+tmc_write_##name(struct tmc_drvdata *drvdata, u64 val) \
+{ \
+ gtrace_relaxed_write_pair(drvdata->comp->pdata, val, lo_off, hi_off); \
+}
+
+TMC_REG_PAIR(rrp, TMC_RRP, TMC_RRPHI)
+TMC_REG_PAIR(rwp, TMC_RWP, TMC_RWPHI)
+TMC_REG_PAIR(dba, TMC_DBALO, TMC_DBAHI)
+
+/* Initialise the caps from unadvertised static capabilities of the device */
+static inline void tmc_etr_init_caps(struct tmc_drvdata *drvdata, u32 dev_caps)
+{
+ WARN_ON(drvdata->etr_caps);
+ drvdata->etr_caps = dev_caps;
+}
+
+static inline void tmc_etr_set_cap(struct tmc_drvdata *drvdata, u32 cap)
+{
+ drvdata->etr_caps |= cap;
+}
+
+static inline bool tmc_etr_has_cap(struct tmc_drvdata *drvdata, u32 cap)
+{
+ return !!(drvdata->etr_caps & cap);
+}
+
+struct tmc_sg_table *tmc_alloc_sg_table(struct device *dev,
+ int node,
+ int nr_tpages,
+ int nr_dpages,
+ void **pages);
+void tmc_free_sg_table(struct tmc_sg_table *sg_table);
+void tmc_sg_table_sync_table(struct tmc_sg_table *sg_table);
+void tmc_sg_table_sync_data_range(struct tmc_sg_table *table,
+ u64 offset, u64 size);
+ssize_t tmc_sg_table_get_data(struct tmc_sg_table *sg_table,
+ u64 offset, size_t len, char **bufpp);
+
+static inline unsigned long
+tmc_sg_table_buf_size(struct tmc_sg_table *sg_table)
+{
+ return (unsigned long)sg_table->data_pages.nr_pages << PAGE_SHIFT;
+}
+
+static inline bool tmc_has_reserved_buffer(struct tmc_drvdata *drvdata)
+{
+ if (drvdata->resrv_buf.vaddr &&
+ drvdata->resrv_buf.size)
+ return true;
+ return false;
+}
+
+static inline bool tmc_has_crash_mdata_buffer(struct tmc_drvdata *drvdata)
+{
+ if (drvdata->crash_mdata.vaddr &&
+ drvdata->crash_mdata.size)
+ return true;
+ return false;
+}
+
+static inline void tmc_crashdata_set_invalid(struct tmc_drvdata *drvdata)
+{
+ struct tmc_crash_metadata *mdata;
+
+ mdata = (struct tmc_crash_metadata *)drvdata->crash_mdata.vaddr;
+
+ if (tmc_has_crash_mdata_buffer(drvdata))
+ mdata->valid = false;
+}
+
+static inline uint32_t find_crash_metadata_crc(struct tmc_crash_metadata *md)
+{
+ unsigned long crc_size;
+
+ crc_size = sizeof(struct tmc_crash_metadata) -
+ offsetof(struct tmc_crash_metadata, crc32_tdata);
+ return crc32_le(0, (void *)&md->crc32_tdata, crc_size);
+}
+
+static inline uint32_t find_crash_tracedata_crc(struct tmc_drvdata *drvdata,
+ struct tmc_crash_metadata *md)
+{
+ unsigned long crc_size;
+
+ /* Take CRC of configured buffer size to keep it simple */
+ crc_size = md->tmc_ram_size << 2;
+ return crc32_le(0, (void *)drvdata->resrv_buf.vaddr, crc_size);
+}
+
+struct gtrace_component *tmc_etr_get_catu_device(struct tmc_drvdata *drvdata);
+
+void tmc_etr_set_catu_ops(const struct etr_buf_operations *catu);
+void tmc_etr_remove_catu_ops(void);
+struct etr_buf *tmc_etr_get_buffer(struct gtrace_component *comp);
+extern const struct attribute_group coresight_etr_group;
+
+#endif
--
2.34.1
^ permalink raw reply [flat|nested] 25+ messages in thread* [PATCH 21/24] perf tools: riscv: making gtrace PMU listable
2026-10-23 3:14 [RFC PATCH 00/24] Add Linux RISC-V trace support via the gtrace framework Zane Leung
` (19 preceding siblings ...)
2026-10-23 3:15 ` [PATCH 20/24] gtrace: Add CoreSight TMC driver support Zane Leung
@ 2026-10-23 3:15 ` Zane Leung
2026-10-23 3:15 ` [PATCH 22/24] perf tools: Add Nexus RISC-V Trace decoder Zane Leung
` (2 subsequent siblings)
23 siblings, 0 replies; 25+ messages in thread
From: Zane Leung @ 2026-10-23 3:15 UTC (permalink / raw)
To: gregkh, palmer, pjw, alexander.shishkin, suzuki.poulose,
mchitale, anup, irogers
Cc: mike.leach, james.clark, leo.yan, peterz, mingo, namhyung,
mark.rutland, jolsa, adrian.hunter, alex, atish.patra,
andrew.jones, sunilvl, liangzhen, zhuangqiubin, coresight,
linux-riscv, linux-kernel
This commit enhances the RISC-V performance tools by allowing the
gtrace PMU to be listed and selected.
Signed-off-by: Zane Leung <liangzhen@linux.spacemit.com>
---
tools/perf/arch/riscv/util/Build | 1 +
tools/perf/arch/riscv/util/pmu.c | 19 +++++++++++++++++++
2 files changed, 20 insertions(+)
create mode 100644 tools/perf/arch/riscv/util/pmu.c
diff --git a/tools/perf/arch/riscv/util/Build b/tools/perf/arch/riscv/util/Build
index e07d5525ece6..57991c43e8f0 100644
--- a/tools/perf/arch/riscv/util/Build
+++ b/tools/perf/arch/riscv/util/Build
@@ -1,2 +1,3 @@
perf-util-y += header.o
perf-util-y += auxtrace.o
+perf-util-y += pmu.o
diff --git a/tools/perf/arch/riscv/util/pmu.c b/tools/perf/arch/riscv/util/pmu.c
new file mode 100644
index 000000000000..8cb8c82182ac
--- /dev/null
+++ b/tools/perf/arch/riscv/util/pmu.c
@@ -0,0 +1,19 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2026 Spacemit Technologies, Inc.
+ */
+
+#include <string.h>
+#include <linux/perf_event.h>
+
+#include "../../../util/pmu.h"
+
+#define GTRACE_PMU_NAME "gtrace"
+
+void perf_pmu__arch_init(struct perf_pmu *pmu)
+{
+ if (!strcmp(pmu->name, GTRACE_PMU_NAME)) {
+ pmu->auxtrace = true;
+ pmu->selectable = true;
+ }
+}
--
2.34.1
^ permalink raw reply [flat|nested] 25+ messages in thread* [PATCH 22/24] perf tools: Add Nexus RISC-V Trace decoder
2026-10-23 3:14 [RFC PATCH 00/24] Add Linux RISC-V trace support via the gtrace framework Zane Leung
` (20 preceding siblings ...)
2026-10-23 3:15 ` [PATCH 21/24] perf tools: riscv: making gtrace PMU listable Zane Leung
@ 2026-10-23 3:15 ` Zane Leung
2026-10-23 3:15 ` [PATCH 23/24] perf tools: gtrace: Record and parse trace encoder metadata Zane Leung
2026-10-23 3:15 ` [PATCH 24/24] perf tools: gtrace: Add AUX trace decoder and branch sample synthesis Zane Leung
23 siblings, 0 replies; 25+ messages in thread
From: Zane Leung @ 2026-10-23 3:15 UTC (permalink / raw)
To: gregkh, palmer, pjw, alexander.shishkin, suzuki.poulose,
mchitale, anup, irogers
Cc: mike.leach, james.clark, leo.yan, peterz, mingo, namhyung,
mark.rutland, jolsa, adrian.hunter, alex, atish.patra,
andrew.jones, sunilvl, liangzhen, zhuangqiubin, coresight,
linux-riscv, linux-kernel
Add support for RISC-V Nexus Trace decoder based on the reference
implementation from the RISC-V Nexus Trace specification. This includes
a CoreSight frame deformatter to remove the trace formatter overhead.
Signed-off-by: Zane Leung <liangzhen@linux.spacemit.com>
---
tools/arch/riscv/include/asm/insn.h | 645 +++++++++++++
tools/perf/util/Build | 1 +
tools/perf/util/rvtrace-decoder/Build | 2 +
.../util/rvtrace-decoder/rvtrace-decoder.c | 423 ++++++++
.../util/rvtrace-decoder/rvtrace-decoder.h | 122 +++
.../rvtrace-decoder/rvtrace-ntrace-decoder.c | 910 ++++++++++++++++++
.../rvtrace-decoder/rvtrace-ntrace-decoder.h | 124 +++
7 files changed, 2227 insertions(+)
create mode 100644 tools/arch/riscv/include/asm/insn.h
create mode 100644 tools/perf/util/rvtrace-decoder/Build
create mode 100644 tools/perf/util/rvtrace-decoder/rvtrace-decoder.c
create mode 100644 tools/perf/util/rvtrace-decoder/rvtrace-decoder.h
create mode 100644 tools/perf/util/rvtrace-decoder/rvtrace-ntrace-decoder.c
create mode 100644 tools/perf/util/rvtrace-decoder/rvtrace-ntrace-decoder.h
diff --git a/tools/arch/riscv/include/asm/insn.h b/tools/arch/riscv/include/asm/insn.h
new file mode 100644
index 000000000000..6c020afe926b
--- /dev/null
+++ b/tools/arch/riscv/include/asm/insn.h
@@ -0,0 +1,645 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2020 SiFive
+ */
+
+#ifndef _ASM_RISCV_INSN_H
+#define _ASM_RISCV_INSN_H
+
+#include <linux/bits.h>
+
+#define RV_INSN_FUNCT3_MASK GENMASK(14, 12)
+#define RV_INSN_FUNCT3_OPOFF 12
+#define RV_INSN_OPCODE_MASK GENMASK(6, 0)
+#define RV_INSN_OPCODE_OPOFF 0
+#define RV_INSN_FUNCT12_OPOFF 20
+
+#define RV_ENCODE_FUNCT3(f_) (RVG_FUNCT3_##f_ << RV_INSN_FUNCT3_OPOFF)
+#define RV_ENCODE_FUNCT12(f_) (RVG_FUNCT12_##f_ << RV_INSN_FUNCT12_OPOFF)
+
+/* The bit field of immediate value in I-type instruction */
+#define RV_I_IMM_SIGN_OPOFF 31
+#define RV_I_IMM_11_0_OPOFF 20
+#define RV_I_IMM_SIGN_OFF 12
+#define RV_I_IMM_11_0_OFF 0
+#define RV_I_IMM_11_0_MASK GENMASK(11, 0)
+
+/* The bit field of immediate value in J-type instruction */
+#define RV_J_IMM_SIGN_OPOFF 31
+#define RV_J_IMM_10_1_OPOFF 21
+#define RV_J_IMM_11_OPOFF 20
+#define RV_J_IMM_19_12_OPOFF 12
+#define RV_J_IMM_SIGN_OFF 20
+#define RV_J_IMM_10_1_OFF 1
+#define RV_J_IMM_11_OFF 11
+#define RV_J_IMM_19_12_OFF 12
+#define RV_J_IMM_10_1_MASK GENMASK(9, 0)
+#define RV_J_IMM_11_MASK GENMASK(0, 0)
+#define RV_J_IMM_19_12_MASK GENMASK(7, 0)
+
+/*
+ * U-type IMMs contain the upper 20bits [31:20] of an immediate with
+ * the rest filled in by zeros, so no shifting required. Similarly,
+ * bit31 contains the signed state, so no sign extension necessary.
+ */
+#define RV_U_IMM_SIGN_OPOFF 31
+#define RV_U_IMM_31_12_OPOFF 0
+#define RV_U_IMM_31_12_MASK GENMASK(31, 12)
+
+/* The bit field of immediate value in B-type instruction */
+#define RV_B_IMM_SIGN_OPOFF 31
+#define RV_B_IMM_10_5_OPOFF 25
+#define RV_B_IMM_4_1_OPOFF 8
+#define RV_B_IMM_11_OPOFF 7
+#define RV_B_IMM_SIGN_OFF 12
+#define RV_B_IMM_10_5_OFF 5
+#define RV_B_IMM_4_1_OFF 1
+#define RV_B_IMM_11_OFF 11
+#define RV_B_IMM_10_5_MASK GENMASK(5, 0)
+#define RV_B_IMM_4_1_MASK GENMASK(3, 0)
+#define RV_B_IMM_11_MASK GENMASK(0, 0)
+
+/* The register offset in RVG instruction */
+#define RVG_RS1_OPOFF 15
+#define RVG_RS2_OPOFF 20
+#define RVG_RD_OPOFF 7
+#define RVG_RS1_MASK GENMASK(4, 0)
+#define RVG_RS2_MASK GENMASK(4, 0)
+#define RVG_RD_MASK GENMASK(4, 0)
+
+/* The bit field of immediate value in RVC J instruction */
+#define RVC_J_IMM_SIGN_OPOFF 12
+#define RVC_J_IMM_4_OPOFF 11
+#define RVC_J_IMM_9_8_OPOFF 9
+#define RVC_J_IMM_10_OPOFF 8
+#define RVC_J_IMM_6_OPOFF 7
+#define RVC_J_IMM_7_OPOFF 6
+#define RVC_J_IMM_3_1_OPOFF 3
+#define RVC_J_IMM_5_OPOFF 2
+#define RVC_J_IMM_SIGN_OFF 11
+#define RVC_J_IMM_4_OFF 4
+#define RVC_J_IMM_9_8_OFF 8
+#define RVC_J_IMM_10_OFF 10
+#define RVC_J_IMM_6_OFF 6
+#define RVC_J_IMM_7_OFF 7
+#define RVC_J_IMM_3_1_OFF 1
+#define RVC_J_IMM_5_OFF 5
+#define RVC_J_IMM_4_MASK GENMASK(0, 0)
+#define RVC_J_IMM_9_8_MASK GENMASK(1, 0)
+#define RVC_J_IMM_10_MASK GENMASK(0, 0)
+#define RVC_J_IMM_6_MASK GENMASK(0, 0)
+#define RVC_J_IMM_7_MASK GENMASK(0, 0)
+#define RVC_J_IMM_3_1_MASK GENMASK(2, 0)
+#define RVC_J_IMM_5_MASK GENMASK(0, 0)
+
+/* The bit field of immediate value in RVC B instruction */
+#define RVC_B_IMM_SIGN_OPOFF 12
+#define RVC_B_IMM_4_3_OPOFF 10
+#define RVC_B_IMM_7_6_OPOFF 5
+#define RVC_B_IMM_2_1_OPOFF 3
+#define RVC_B_IMM_5_OPOFF 2
+#define RVC_B_IMM_SIGN_OFF 8
+#define RVC_B_IMM_4_3_OFF 3
+#define RVC_B_IMM_7_6_OFF 6
+#define RVC_B_IMM_2_1_OFF 1
+#define RVC_B_IMM_5_OFF 5
+#define RVC_B_IMM_4_3_MASK GENMASK(1, 0)
+#define RVC_B_IMM_7_6_MASK GENMASK(1, 0)
+#define RVC_B_IMM_2_1_MASK GENMASK(1, 0)
+#define RVC_B_IMM_5_MASK GENMASK(0, 0)
+
+#define RVC_INSN_FUNCT4_MASK GENMASK(15, 12)
+#define RVC_INSN_FUNCT4_OPOFF 12
+#define RVC_INSN_FUNCT3_MASK GENMASK(15, 13)
+#define RVC_INSN_FUNCT3_OPOFF 13
+#define RVC_INSN_J_RS1_MASK GENMASK(11, 7)
+#define RVC_INSN_J_RS2_MASK GENMASK(6, 2)
+#define RVC_INSN_OPCODE_MASK GENMASK(1, 0)
+#define RVC_ENCODE_FUNCT3(f_) (RVC_FUNCT3_##f_ << RVC_INSN_FUNCT3_OPOFF)
+#define RVC_ENCODE_FUNCT4(f_) (RVC_FUNCT4_##f_ << RVC_INSN_FUNCT4_OPOFF)
+
+/* The register offset in RVC op=C0 instruction */
+#define RVC_C0_RS1_OPOFF 7
+#define RVC_C0_RS2_OPOFF 2
+#define RVC_C0_RD_OPOFF 2
+
+/* The register offset in RVC op=C1 instruction */
+#define RVC_C1_RS1_OPOFF 7
+#define RVC_C1_RS2_OPOFF 2
+#define RVC_C1_RD_OPOFF 7
+
+/* The register offset in RVC op=C2 instruction */
+#define RVC_C2_RS1_OPOFF 7
+#define RVC_C2_RS2_OPOFF 2
+#define RVC_C2_RD_OPOFF 7
+#define RVC_C2_RS1_MASK GENMASK(4, 0)
+
+/* parts of opcode for RVG*/
+#define RVG_OPCODE_FENCE 0x0f
+#define RVG_OPCODE_AUIPC 0x17
+#define RVG_OPCODE_BRANCH 0x63
+#define RVG_OPCODE_JALR 0x67
+#define RVG_OPCODE_JAL 0x6f
+#define RVG_OPCODE_SYSTEM 0x73
+#define RVG_SYSTEM_CSR_OFF 20
+#define RVG_SYSTEM_CSR_MASK GENMASK(12, 0)
+
+/* parts of opcode for RVF, RVD and RVQ */
+#define RVFDQ_FL_FS_WIDTH_OFF 12
+#define RVFDQ_FL_FS_WIDTH_MASK GENMASK(2, 0)
+#define RVFDQ_FL_FS_WIDTH_W 2
+#define RVFDQ_FL_FS_WIDTH_D 3
+#define RVFDQ_LS_FS_WIDTH_Q 4
+#define RVFDQ_OPCODE_FL 0x07
+#define RVFDQ_OPCODE_FS 0x27
+
+/* parts of opcode for RVV */
+#define RVV_OPCODE_VECTOR 0x57
+#define RVV_VL_VS_WIDTH_8 0
+#define RVV_VL_VS_WIDTH_16 5
+#define RVV_VL_VS_WIDTH_32 6
+#define RVV_VL_VS_WIDTH_64 7
+#define RVV_OPCODE_VL RVFDQ_OPCODE_FL
+#define RVV_OPCODE_VS RVFDQ_OPCODE_FS
+
+/* parts of opcode for RVC*/
+#define RVC_OPCODE_C0 0x0
+#define RVC_OPCODE_C1 0x1
+#define RVC_OPCODE_C2 0x2
+
+/* parts of funct3 code for I, M, A extension*/
+#define RVG_FUNCT3_JALR 0x0
+#define RVG_FUNCT3_BEQ 0x0
+#define RVG_FUNCT3_BNE 0x1
+#define RVG_FUNCT3_BLT 0x4
+#define RVG_FUNCT3_BGE 0x5
+#define RVG_FUNCT3_BLTU 0x6
+#define RVG_FUNCT3_BGEU 0x7
+
+/* parts of funct3 code for C extension*/
+#define RVC_FUNCT3_C_BEQZ 0x6
+#define RVC_FUNCT3_C_BNEZ 0x7
+#define RVC_FUNCT3_C_J 0x5
+#define RVC_FUNCT3_C_JAL 0x1
+#define RVC_FUNCT4_C_JR 0x8
+#define RVC_FUNCT4_C_JALR 0x9
+#define RVC_FUNCT4_C_EBREAK 0x9
+
+#define RVG_FUNCT12_ECALL 0x0
+#define RVG_FUNCT12_EBREAK 0x1
+#define RVG_FUNCT12_SRET 0x102
+#define RVG_FUNCT12_MRET 0x303
+
+#define RVG_MATCH_AUIPC (RVG_OPCODE_AUIPC)
+#define RVG_MATCH_JALR (RV_ENCODE_FUNCT3(JALR) | RVG_OPCODE_JALR)
+#define RVG_MATCH_JAL (RVG_OPCODE_JAL)
+#define RVG_MATCH_FENCE (RVG_OPCODE_FENCE)
+#define RVG_MATCH_BEQ (RV_ENCODE_FUNCT3(BEQ) | RVG_OPCODE_BRANCH)
+#define RVG_MATCH_BNE (RV_ENCODE_FUNCT3(BNE) | RVG_OPCODE_BRANCH)
+#define RVG_MATCH_BLT (RV_ENCODE_FUNCT3(BLT) | RVG_OPCODE_BRANCH)
+#define RVG_MATCH_BGE (RV_ENCODE_FUNCT3(BGE) | RVG_OPCODE_BRANCH)
+#define RVG_MATCH_BLTU (RV_ENCODE_FUNCT3(BLTU) | RVG_OPCODE_BRANCH)
+#define RVG_MATCH_BGEU (RV_ENCODE_FUNCT3(BGEU) | RVG_OPCODE_BRANCH)
+#define RVG_MATCH_ECALL (RV_ENCODE_FUNCT12(EBREAK) | RVG_OPCODE_SYSTEM)
+#define RVG_MATCH_EBREAK (RV_ENCODE_FUNCT12(EBREAK) | RVG_OPCODE_SYSTEM)
+#define RVG_MATCH_SRET (RV_ENCODE_FUNCT12(SRET) | RVG_OPCODE_SYSTEM)
+#define RVG_MATCH_MRET (RV_ENCODE_FUNCT12(MRET) | RVG_OPCODE_SYSTEM)
+#define RVC_MATCH_C_BEQZ (RVC_ENCODE_FUNCT3(C_BEQZ) | RVC_OPCODE_C1)
+#define RVC_MATCH_C_BNEZ (RVC_ENCODE_FUNCT3(C_BNEZ) | RVC_OPCODE_C1)
+#define RVC_MATCH_C_J (RVC_ENCODE_FUNCT3(C_J) | RVC_OPCODE_C1)
+#define RVC_MATCH_C_JAL (RVC_ENCODE_FUNCT3(C_JAL) | RVC_OPCODE_C1)
+#define RVC_MATCH_C_JR (RVC_ENCODE_FUNCT4(C_JR) | RVC_OPCODE_C2)
+#define RVC_MATCH_C_JALR (RVC_ENCODE_FUNCT4(C_JALR) | RVC_OPCODE_C2)
+#define RVC_MATCH_C_EBREAK (RVC_ENCODE_FUNCT4(C_EBREAK) | RVC_OPCODE_C2)
+
+#define RVG_MASK_AUIPC (RV_INSN_OPCODE_MASK)
+#define RVG_MASK_JALR (RV_INSN_FUNCT3_MASK | RV_INSN_OPCODE_MASK)
+#define RVG_MASK_JAL (RV_INSN_OPCODE_MASK)
+#define RVG_MASK_FENCE (RV_INSN_OPCODE_MASK)
+#define RVC_MASK_C_JALR (RVC_INSN_FUNCT4_MASK | RVC_INSN_J_RS2_MASK | RVC_INSN_OPCODE_MASK)
+#define RVC_MASK_C_JR (RVC_INSN_FUNCT4_MASK | RVC_INSN_J_RS2_MASK | RVC_INSN_OPCODE_MASK)
+#define RVC_MASK_C_JAL (RVC_INSN_FUNCT3_MASK | RVC_INSN_OPCODE_MASK)
+#define RVC_MASK_C_J (RVC_INSN_FUNCT3_MASK | RVC_INSN_OPCODE_MASK)
+#define RVG_MASK_BEQ (RV_INSN_FUNCT3_MASK | RV_INSN_OPCODE_MASK)
+#define RVG_MASK_BNE (RV_INSN_FUNCT3_MASK | RV_INSN_OPCODE_MASK)
+#define RVG_MASK_BLT (RV_INSN_FUNCT3_MASK | RV_INSN_OPCODE_MASK)
+#define RVG_MASK_BGE (RV_INSN_FUNCT3_MASK | RV_INSN_OPCODE_MASK)
+#define RVG_MASK_BLTU (RV_INSN_FUNCT3_MASK | RV_INSN_OPCODE_MASK)
+#define RVG_MASK_BGEU (RV_INSN_FUNCT3_MASK | RV_INSN_OPCODE_MASK)
+#define RVC_MASK_C_BEQZ (RVC_INSN_FUNCT3_MASK | RVC_INSN_OPCODE_MASK)
+#define RVC_MASK_C_BNEZ (RVC_INSN_FUNCT3_MASK | RVC_INSN_OPCODE_MASK)
+#define RVC_MASK_C_EBREAK 0xffff
+#define RVG_MASK_EBREAK 0xffffffff
+#define RVG_MASK_ECALL 0xffffffff
+#define RVG_MASK_SRET 0xffffffff
+#define RVG_MASK_MRET 0xffffffff
+
+#define __INSN_LENGTH_MASK _UL(0x3)
+#define __INSN_LENGTH_GE_32 _UL(0x3)
+#define __INSN_OPCODE_MASK _UL(0x7F)
+#define __INSN_BRANCH_OPCODE _UL(RVG_OPCODE_BRANCH)
+
+#define __RISCV_INSN_FUNCS(name, mask, val) \
+static __always_inline bool riscv_insn_is_##name(u32 code) \
+{ \
+ BUILD_BUG_ON(~(mask) & (val)); \
+ return (code & (mask)) == (val); \
+} \
+
+#if __riscv_xlen == 32
+/* C.JAL is an RV32C-only instruction */
+__RISCV_INSN_FUNCS(c_jal, RVC_MASK_C_JAL, RVC_MATCH_C_JAL)
+#else
+#define riscv_insn_is_c_jal(opcode) 0
+#endif
+__RISCV_INSN_FUNCS(auipc, RVG_MASK_AUIPC, RVG_MATCH_AUIPC)
+__RISCV_INSN_FUNCS(jalr, RVG_MASK_JALR, RVG_MATCH_JALR)
+__RISCV_INSN_FUNCS(jal, RVG_MASK_JAL, RVG_MATCH_JAL)
+__RISCV_INSN_FUNCS(c_j, RVC_MASK_C_J, RVC_MATCH_C_J)
+__RISCV_INSN_FUNCS(beq, RVG_MASK_BEQ, RVG_MATCH_BEQ)
+__RISCV_INSN_FUNCS(bne, RVG_MASK_BNE, RVG_MATCH_BNE)
+__RISCV_INSN_FUNCS(blt, RVG_MASK_BLT, RVG_MATCH_BLT)
+__RISCV_INSN_FUNCS(bge, RVG_MASK_BGE, RVG_MATCH_BGE)
+__RISCV_INSN_FUNCS(bltu, RVG_MASK_BLTU, RVG_MATCH_BLTU)
+__RISCV_INSN_FUNCS(bgeu, RVG_MASK_BGEU, RVG_MATCH_BGEU)
+__RISCV_INSN_FUNCS(c_beqz, RVC_MASK_C_BEQZ, RVC_MATCH_C_BEQZ)
+__RISCV_INSN_FUNCS(c_bnez, RVC_MASK_C_BNEZ, RVC_MATCH_C_BNEZ)
+__RISCV_INSN_FUNCS(c_ebreak, RVC_MASK_C_EBREAK, RVC_MATCH_C_EBREAK)
+__RISCV_INSN_FUNCS(ebreak, RVG_MASK_EBREAK, RVG_MATCH_EBREAK)
+__RISCV_INSN_FUNCS(ecall, RVG_MASK_EBREAK, RVG_MATCH_EBREAK)
+__RISCV_INSN_FUNCS(sret, RVG_MASK_SRET, RVG_MATCH_SRET)
+__RISCV_INSN_FUNCS(mret, RVG_MASK_MRET, RVG_MATCH_MRET)
+__RISCV_INSN_FUNCS(fence, RVG_MASK_FENCE, RVG_MATCH_FENCE);
+
+/* special case to catch _any_ system instruction */
+static __always_inline bool riscv_insn_is_system(u32 code)
+{
+ return (code & RV_INSN_OPCODE_MASK) == RVG_OPCODE_SYSTEM;
+}
+
+/* special case to catch _any_ branch instruction */
+static __always_inline bool riscv_insn_is_branch(u32 code)
+{
+ return (code & RV_INSN_OPCODE_MASK) == RVG_OPCODE_BRANCH;
+}
+
+static __always_inline bool riscv_insn_is_c_jr(u32 code)
+{
+ return (code & RVC_MASK_C_JR) == RVC_MATCH_C_JR &&
+ (code & RVC_INSN_J_RS1_MASK) != 0;
+}
+
+static __always_inline bool riscv_insn_is_c_jalr(u32 code)
+{
+ return (code & RVC_MASK_C_JALR) == RVC_MATCH_C_JALR &&
+ (code & RVC_INSN_J_RS1_MASK) != 0;
+}
+
+#define INSN_MATCH_LB 0x3
+#define INSN_MASK_LB 0x707f
+#define INSN_MATCH_LH 0x1003
+#define INSN_MASK_LH 0x707f
+#define INSN_MATCH_LW 0x2003
+#define INSN_MASK_LW 0x707f
+#define INSN_MATCH_LD 0x3003
+#define INSN_MASK_LD 0x707f
+#define INSN_MATCH_LBU 0x4003
+#define INSN_MASK_LBU 0x707f
+#define INSN_MATCH_LHU 0x5003
+#define INSN_MASK_LHU 0x707f
+#define INSN_MATCH_LWU 0x6003
+#define INSN_MASK_LWU 0x707f
+#define INSN_MATCH_SB 0x23
+#define INSN_MASK_SB 0x707f
+#define INSN_MATCH_SH 0x1023
+#define INSN_MASK_SH 0x707f
+#define INSN_MATCH_SW 0x2023
+#define INSN_MASK_SW 0x707f
+#define INSN_MATCH_SD 0x3023
+#define INSN_MASK_SD 0x707f
+
+#define INSN_MATCH_C_LD 0x6000
+#define INSN_MASK_C_LD 0xe003
+#define INSN_MATCH_C_SD 0xe000
+#define INSN_MASK_C_SD 0xe003
+#define INSN_MATCH_C_LW 0x4000
+#define INSN_MASK_C_LW 0xe003
+#define INSN_MATCH_C_SW 0xc000
+#define INSN_MASK_C_SW 0xe003
+#define INSN_MATCH_C_LDSP 0x6002
+#define INSN_MASK_C_LDSP 0xe003
+#define INSN_MATCH_C_SDSP 0xe002
+#define INSN_MASK_C_SDSP 0xe003
+#define INSN_MATCH_C_LWSP 0x4002
+#define INSN_MASK_C_LWSP 0xe003
+#define INSN_MATCH_C_SWSP 0xc002
+#define INSN_MASK_C_SWSP 0xe003
+
+#define INSN_OPCODE_MASK 0x007c
+#define INSN_OPCODE_SHIFT 2
+#define INSN_OPCODE_SYSTEM 28
+
+#define INSN_MASK_WFI 0xffffffff
+#define INSN_MATCH_WFI 0x10500073
+
+#define INSN_MASK_WRS 0xffffffff
+#define INSN_MATCH_WRS 0x00d00073
+
+#define INSN_MATCH_CSRRW 0x1073
+#define INSN_MASK_CSRRW 0x707f
+#define INSN_MATCH_CSRRS 0x2073
+#define INSN_MASK_CSRRS 0x707f
+#define INSN_MATCH_CSRRC 0x3073
+#define INSN_MASK_CSRRC 0x707f
+#define INSN_MATCH_CSRRWI 0x5073
+#define INSN_MASK_CSRRWI 0x707f
+#define INSN_MATCH_CSRRSI 0x6073
+#define INSN_MASK_CSRRSI 0x707f
+#define INSN_MATCH_CSRRCI 0x7073
+#define INSN_MASK_CSRRCI 0x707f
+
+#define INSN_MATCH_FLW 0x2007
+#define INSN_MASK_FLW 0x707f
+#define INSN_MATCH_FLD 0x3007
+#define INSN_MASK_FLD 0x707f
+#define INSN_MATCH_FLQ 0x4007
+#define INSN_MASK_FLQ 0x707f
+#define INSN_MATCH_FSW 0x2027
+#define INSN_MASK_FSW 0x707f
+#define INSN_MATCH_FSD 0x3027
+#define INSN_MASK_FSD 0x707f
+#define INSN_MATCH_FSQ 0x4027
+#define INSN_MASK_FSQ 0x707f
+
+#define INSN_MATCH_C_FLD 0x2000
+#define INSN_MASK_C_FLD 0xe003
+#define INSN_MATCH_C_FLW 0x6000
+#define INSN_MASK_C_FLW 0xe003
+#define INSN_MATCH_C_FSD 0xa000
+#define INSN_MASK_C_FSD 0xe003
+#define INSN_MATCH_C_FSW 0xe000
+#define INSN_MASK_C_FSW 0xe003
+#define INSN_MATCH_C_FLDSP 0x2002
+#define INSN_MASK_C_FLDSP 0xe003
+#define INSN_MATCH_C_FSDSP 0xa002
+#define INSN_MASK_C_FSDSP 0xe003
+#define INSN_MATCH_C_FLWSP 0x6002
+#define INSN_MASK_C_FLWSP 0xe003
+#define INSN_MATCH_C_FSWSP 0xe002
+#define INSN_MASK_C_FSWSP 0xe003
+
+#define INSN_MATCH_C_LHU 0x8400
+#define INSN_MASK_C_LHU 0xfc43
+#define INSN_MATCH_C_LH 0x8440
+#define INSN_MASK_C_LH 0xfc43
+#define INSN_MATCH_C_SH 0x8c00
+#define INSN_MASK_C_SH 0xfc43
+
+#define INSN_16BIT_MASK 0x3
+#define INSN_IS_16BIT(insn) (((insn) & INSN_16BIT_MASK) != INSN_16BIT_MASK)
+#define INSN_LEN(insn) (INSN_IS_16BIT(insn) ? 2 : 4)
+
+#define SHIFT_RIGHT(x, y) \
+ ((y) < 0 ? ((x) << -(y)) : ((x) >> (y)))
+
+#define REG_MASK \
+ ((1 << (5 + LOG_REGBYTES)) - (1 << LOG_REGBYTES))
+
+#define REG_OFFSET(insn, pos) \
+ (SHIFT_RIGHT((insn), (pos) - LOG_REGBYTES) & REG_MASK)
+
+#define REG_PTR(insn, pos, regs) \
+ ((ulong *)((ulong)(regs) + REG_OFFSET(insn, pos)))
+
+#define GET_RS1(insn, regs) (*REG_PTR(insn, SH_RS1, regs))
+#define GET_RS2(insn, regs) (*REG_PTR(insn, SH_RS2, regs))
+#define GET_RS1S(insn, regs) (*REG_PTR(RVC_RS1S(insn), 0, regs))
+#define GET_RS2S(insn, regs) (*REG_PTR(RVC_RS2S(insn), 0, regs))
+#define GET_RS2C(insn, regs) (*REG_PTR(insn, SH_RS2C, regs))
+#define GET_SP(regs) (*REG_PTR(2, 0, regs))
+#define SET_RD(insn, regs, val) (*REG_PTR(insn, SH_RD, regs) = (val))
+#define IMM_I(insn) ((s32)(insn) >> 20)
+#define IMM_S(insn) (((s32)(insn) >> 25 << 5) | \
+ (s32)(((insn) >> 7) & 0x1f))
+
+#define SH_RD 7
+#define SH_RS1 15
+#define SH_RS2 20
+#define SH_RS2C 2
+#define MASK_RX 0x1f
+
+#if defined(CONFIG_64BIT)
+#define LOG_REGBYTES 3
+#else
+#define LOG_REGBYTES 2
+#endif
+
+#define MASK_FUNCT3 0x7000
+
+#define GET_FUNCT3(insn) (((insn) >> 12) & 7)
+
+#define RV_IMM_SIGN(x) (-(((x) >> 31) & 1))
+#define RVC_IMM_SIGN(x) (-(((x) >> 12) & 1))
+#define RV_X_MASK(X, s, mask) (((X) >> (s)) & (mask))
+#define RV_X(X, s, n) RV_X_MASK(X, s, ((1 << (n)) - 1))
+#define RVC_LW_IMM(x) ((RV_X(x, 6, 1) << 2) | \
+ (RV_X(x, 10, 3) << 3) | \
+ (RV_X(x, 5, 1) << 6))
+#define RVC_LD_IMM(x) ((RV_X(x, 10, 3) << 3) | \
+ (RV_X(x, 5, 2) << 6))
+#define RVC_LWSP_IMM(x) ((RV_X(x, 4, 3) << 2) | \
+ (RV_X(x, 12, 1) << 5) | \
+ (RV_X(x, 2, 2) << 6))
+#define RVC_LDSP_IMM(x) ((RV_X(x, 5, 2) << 3) | \
+ (RV_X(x, 12, 1) << 5) | \
+ (RV_X(x, 2, 3) << 6))
+#define RVC_SWSP_IMM(x) ((RV_X(x, 9, 4) << 2) | \
+ (RV_X(x, 7, 2) << 6))
+#define RVC_SDSP_IMM(x) ((RV_X(x, 10, 3) << 3) | \
+ (RV_X(x, 7, 3) << 6))
+#define RVC_RS1S(insn) (8 + RV_X(insn, SH_RD, 3))
+#define RVC_RS2S(insn) (8 + RV_X(insn, SH_RS2C, 3))
+#define RVC_RS2(insn) RV_X(insn, SH_RS2C, 5)
+#define RVC_X(X, s, mask) RV_X_MASK(X, s, mask)
+
+#define RV_EXTRACT_FUNCT3(x) \
+ ({typeof(x) x_ = (x); \
+ (RV_X_MASK(x_, RV_INSN_FUNCT3_OPOFF, \
+ RV_INSN_FUNCT3_MASK >> RV_INSN_FUNCT3_OPOFF)); })
+
+#define RV_EXTRACT_RS1_REG(x) \
+ ({typeof(x) x_ = (x); \
+ (RV_X_MASK(x_, RVG_RS1_OPOFF, RVG_RS1_MASK)); })
+
+#define RV_EXTRACT_RS2_REG(x) \
+ ({typeof(x) x_ = (x); \
+ (RV_X_MASK(x_, RVG_RS2_OPOFF, RVG_RS2_MASK)); })
+
+#define RV_EXTRACT_RD_REG(x) \
+ ({typeof(x) x_ = (x); \
+ (RV_X_MASK(x_, RVG_RD_OPOFF, RVG_RD_MASK)); })
+
+#define RV_EXTRACT_UTYPE_IMM(x) \
+ ({typeof(x) x_ = (x); \
+ (RV_X_MASK(x_, RV_U_IMM_31_12_OPOFF, RV_U_IMM_31_12_MASK)); })
+
+#define RV_EXTRACT_JTYPE_IMM(x) \
+ ({typeof(x) x_ = (x); \
+ (RV_X_MASK(x_, RV_J_IMM_10_1_OPOFF, RV_J_IMM_10_1_MASK) << RV_J_IMM_10_1_OFF) | \
+ (RV_X_MASK(x_, RV_J_IMM_11_OPOFF, RV_J_IMM_11_MASK) << RV_J_IMM_11_OFF) | \
+ (RV_X_MASK(x_, RV_J_IMM_19_12_OPOFF, RV_J_IMM_19_12_MASK) << RV_J_IMM_19_12_OFF) | \
+ (RV_IMM_SIGN(x_) << RV_J_IMM_SIGN_OFF); })
+
+#define RV_EXTRACT_ITYPE_IMM(x) \
+ ({typeof(x) x_ = (x); \
+ (RV_X_MASK(x_, RV_I_IMM_11_0_OPOFF, RV_I_IMM_11_0_MASK)) | \
+ (RV_IMM_SIGN(x_) << RV_I_IMM_SIGN_OFF); })
+
+#define RV_EXTRACT_BTYPE_IMM(x) \
+ ({typeof(x) x_ = (x); \
+ (RV_X_MASK(x_, RV_B_IMM_4_1_OPOFF, RV_B_IMM_4_1_MASK) << RV_B_IMM_4_1_OFF) | \
+ (RV_X_MASK(x_, RV_B_IMM_10_5_OPOFF, RV_B_IMM_10_5_MASK) << RV_B_IMM_10_5_OFF) | \
+ (RV_X_MASK(x_, RV_B_IMM_11_OPOFF, RV_B_IMM_11_MASK) << RV_B_IMM_11_OFF) | \
+ (RV_IMM_SIGN(x_) << RV_B_IMM_SIGN_OFF); })
+
+#define RVC_EXTRACT_C2_RS1_REG(x) \
+ ({typeof(x) x_ = (x); \
+ (RV_X_MASK(x_, RVC_C2_RS1_OPOFF, RVC_C2_RS1_MASK)); })
+
+#define RVC_EXTRACT_JTYPE_IMM(x) \
+ ({typeof(x) x_ = (x); \
+ (RVC_X(x_, RVC_J_IMM_3_1_OPOFF, RVC_J_IMM_3_1_MASK) << RVC_J_IMM_3_1_OFF) | \
+ (RVC_X(x_, RVC_J_IMM_4_OPOFF, RVC_J_IMM_4_MASK) << RVC_J_IMM_4_OFF) | \
+ (RVC_X(x_, RVC_J_IMM_5_OPOFF, RVC_J_IMM_5_MASK) << RVC_J_IMM_5_OFF) | \
+ (RVC_X(x_, RVC_J_IMM_6_OPOFF, RVC_J_IMM_6_MASK) << RVC_J_IMM_6_OFF) | \
+ (RVC_X(x_, RVC_J_IMM_7_OPOFF, RVC_J_IMM_7_MASK) << RVC_J_IMM_7_OFF) | \
+ (RVC_X(x_, RVC_J_IMM_9_8_OPOFF, RVC_J_IMM_9_8_MASK) << RVC_J_IMM_9_8_OFF) | \
+ (RVC_X(x_, RVC_J_IMM_10_OPOFF, RVC_J_IMM_10_MASK) << RVC_J_IMM_10_OFF) | \
+ (RVC_IMM_SIGN(x_) << RVC_J_IMM_SIGN_OFF); })
+
+#define RVC_EXTRACT_BTYPE_IMM(x) \
+ ({typeof(x) x_ = (x); \
+ (RVC_X(x_, RVC_B_IMM_2_1_OPOFF, RVC_B_IMM_2_1_MASK) << RVC_B_IMM_2_1_OFF) | \
+ (RVC_X(x_, RVC_B_IMM_4_3_OPOFF, RVC_B_IMM_4_3_MASK) << RVC_B_IMM_4_3_OFF) | \
+ (RVC_X(x_, RVC_B_IMM_5_OPOFF, RVC_B_IMM_5_MASK) << RVC_B_IMM_5_OFF) | \
+ (RVC_X(x_, RVC_B_IMM_7_6_OPOFF, RVC_B_IMM_7_6_MASK) << RVC_B_IMM_7_6_OFF) | \
+ (RVC_IMM_SIGN(x_) << RVC_B_IMM_SIGN_OFF); })
+
+#define RVG_EXTRACT_SYSTEM_CSR(x) \
+ ({typeof(x) x_ = (x); RV_X_MASK(x_, RVG_SYSTEM_CSR_OFF, RVG_SYSTEM_CSR_MASK); })
+
+#define RVFDQ_EXTRACT_FL_FS_WIDTH(x) \
+ ({typeof(x) x_ = (x); RV_X_MASK(x_, RVFDQ_FL_FS_WIDTH_OFF, \
+ RVFDQ_FL_FS_WIDTH_MASK); })
+
+#define RVV_EXTRACT_VL_VS_WIDTH(x) RVFDQ_EXTRACT_FL_FS_WIDTH(x)
+
+/*
+ * Get the immediate from a J-type instruction.
+ *
+ * @insn: instruction to process
+ * Return: immediate
+ */
+static inline s32 riscv_insn_extract_jtype_imm(u32 insn)
+{
+ return RV_EXTRACT_JTYPE_IMM(insn);
+}
+
+/*
+ * Update a J-type instruction with an immediate value.
+ *
+ * @insn: pointer to the jtype instruction
+ * @imm: the immediate to insert into the instruction
+ */
+static inline void riscv_insn_insert_jtype_imm(u32 *insn, s32 imm)
+{
+ /* drop the old IMMs, all jal IMM bits sit at 31:12 */
+ *insn &= ~GENMASK(31, 12);
+ *insn |= (RV_X_MASK(imm, RV_J_IMM_10_1_OFF, RV_J_IMM_10_1_MASK) << RV_J_IMM_10_1_OPOFF) |
+ (RV_X_MASK(imm, RV_J_IMM_11_OFF, RV_J_IMM_11_MASK) << RV_J_IMM_11_OPOFF) |
+ (RV_X_MASK(imm, RV_J_IMM_19_12_OFF, RV_J_IMM_19_12_MASK) << RV_J_IMM_19_12_OPOFF) |
+ (RV_X_MASK(imm, RV_J_IMM_SIGN_OFF, 1) << RV_J_IMM_SIGN_OPOFF);
+}
+
+/*
+ * Put together one immediate from a U-type and I-type instruction pair.
+ *
+ * The U-type contains an upper immediate, meaning bits[31:12] with [11:0]
+ * being zero, while the I-type contains a 12bit immediate.
+ * Combined these can encode larger 32bit values and are used for example
+ * in auipc + jalr pairs to allow larger jumps.
+ *
+ * @utype_insn: instruction containing the upper immediate
+ * @itype_insn: instruction
+ * Return: combined immediate
+ */
+static inline s32 riscv_insn_extract_utype_itype_imm(u32 utype_insn, u32 itype_insn)
+{
+ s32 imm;
+
+ imm = RV_EXTRACT_UTYPE_IMM(utype_insn);
+ imm += RV_EXTRACT_ITYPE_IMM(itype_insn);
+
+ return imm;
+}
+
+/*
+ * Update a set of two instructions (U-type + I-type) with an immediate value.
+ *
+ * Used for example in auipc+jalrs pairs the U-type instructions contains
+ * a 20bit upper immediate representing bits[31:12], while the I-type
+ * instruction contains a 12bit immediate representing bits[11:0].
+ *
+ * This also takes into account that both separate immediates are
+ * considered as signed values, so if the I-type immediate becomes
+ * negative (BIT(11) set) the U-type part gets adjusted.
+ *
+ * @utype_insn: pointer to the utype instruction of the pair
+ * @itype_insn: pointer to the itype instruction of the pair
+ * @imm: the immediate to insert into the two instructions
+ */
+static inline void riscv_insn_insert_utype_itype_imm(u32 *utype_insn, u32 *itype_insn, s32 imm)
+{
+ /* drop possible old IMM values */
+ *utype_insn &= ~(RV_U_IMM_31_12_MASK);
+ *itype_insn &= ~(RV_I_IMM_11_0_MASK << RV_I_IMM_11_0_OPOFF);
+
+ /* add the adapted IMMs */
+ *utype_insn |= (imm & RV_U_IMM_31_12_MASK) + ((imm & BIT(11)) << 1);
+ *itype_insn |= ((imm & RV_I_IMM_11_0_MASK) << RV_I_IMM_11_0_OPOFF);
+}
+
+/*
+ * Get the immediate from a B-type instruction.
+ *
+ * @insn: instruction to process
+ * Return: immediate
+ */
+static inline s32 riscv_insn_extract_btype_imm(u32 insn)
+{
+ return RV_EXTRACT_BTYPE_IMM(insn);
+}
+
+/*
+ * Get the immediate from a RVC B-type instruction.
+ *
+ * @insn: instruction to process
+ * Return: immediate
+ */
+static inline s32 riscv_insn_extract_cbtype_imm(u32 insn)
+{
+ return RVC_EXTRACT_BTYPE_IMM(insn);
+}
+
+/*
+ * Get the immediate from a RVC J-type instruction.
+ *
+ * @insn: instruction to process
+ * Return: immediate
+ */
+static inline s32 riscv_insn_extract_cjtype_imm(u16 insn)
+{
+ return RVC_EXTRACT_JTYPE_IMM(insn);
+}
+
+#endif /* _ASM_RISCV_INSN_H */
diff --git a/tools/perf/util/Build b/tools/perf/util/Build
index d25140a0d64f..342b4f75457b 100644
--- a/tools/perf/util/Build
+++ b/tools/perf/util/Build
@@ -150,6 +150,7 @@ perf-util-y += cs-etm-decoder/
endif
perf-util-y += cs-etm-base.o
perf-util-y += gtrace-decoder.o
+perf-util-y += rvtrace-decoder/
perf-util-y += parse-branch-options.o
perf-util-y += parse-regs-options.o
diff --git a/tools/perf/util/rvtrace-decoder/Build b/tools/perf/util/rvtrace-decoder/Build
new file mode 100644
index 000000000000..dc447eec372a
--- /dev/null
+++ b/tools/perf/util/rvtrace-decoder/Build
@@ -0,0 +1,2 @@
+perf-util-y += rvtrace-decoder.o
+perf-util-y += rvtrace-ntrace-decoder.o
diff --git a/tools/perf/util/rvtrace-decoder/rvtrace-decoder.c b/tools/perf/util/rvtrace-decoder/rvtrace-decoder.c
new file mode 100644
index 000000000000..e4b54fe8f51a
--- /dev/null
+++ b/tools/perf/util/rvtrace-decoder/rvtrace-decoder.c
@@ -0,0 +1,423 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2020 IAR Systems AB.
+ * Copyright (c) 2026 Spacemit Technologies, Inc.
+ */
+
+#include <linux/err.h>
+#include <linux/zalloc.h>
+#include <linux/types.h>
+#include <errno.h>
+#include <api/fs/fs.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "../debug.h"
+#include "../intlist.h"
+#include "rvtrace-decoder.h"
+
+#define BUFFER_SIZE 1024
+
+/*
+ * Shared by the per-source staging buffer used during CoreSight frame
+ * de-formatting and by the contiguous de-formatted output.
+ */
+static int rvtrace_buf_append(struct rvtrace_defmt_buf *b, const void *src, size_t len)
+{
+ if (b->size + len > b->capacity) {
+ size_t new_cap = b->capacity ? b->capacity : BUFFER_SIZE;
+ unsigned char *p;
+
+ while (new_cap < b->size + len)
+ new_cap *= 2;
+
+ p = realloc(b->buf, new_cap);
+ if (!p)
+ return -ENOMEM;
+ b->buf = p;
+ b->capacity = new_cap;
+ }
+ memcpy(b->buf + b->size, src, len);
+ b->size += len;
+ return 0;
+}
+
+static struct rvtrace_defmt_buf *rvtrace_get_buf(struct rvtrace_defmt_buf defmt_bufs[],
+ unsigned char id)
+{
+ if (defmt_bufs[id].buf != NULL)
+ return &defmt_bufs[id];
+
+ defmt_bufs[id].buf = (unsigned char *)malloc(BUFFER_SIZE);
+ if (!defmt_bufs[id].buf)
+ return NULL;
+
+ defmt_bufs[id].size = 0;
+ defmt_bufs[id].capacity = BUFFER_SIZE;
+
+ return &defmt_bufs[id];
+}
+
+static int rvtrace_defmt_buf_append(struct rvtrace_defmt_buf *defmt_buf,
+ struct rvtrace_defmt_buf *out, unsigned char data)
+{
+ int err;
+
+ err = rvtrace_buf_append(defmt_buf, &data, 1);
+ if (err)
+ return err;
+
+ /* A Nexus message ends when the MSEO bits of the last byte are '11'. */
+ if ((data & 0x3) == 0x3) {
+ err = rvtrace_buf_append(out, defmt_buf->buf, defmt_buf->size);
+ if (err) {
+ pr_err("Encoder: failed to write nexus data\n");
+ return err;
+ }
+ defmt_buf->size = 0;
+ }
+
+ return 0;
+}
+
+static size_t skip_frame_syncs(const unsigned char *buf, size_t len, size_t *fsync_count)
+{
+ const uint32_t FSYNC_PATTERN = 0x7FFFFFFF; /* LE host pattern for Frame SYNC */
+ size_t skipped_bytes = 0;
+
+ while (skipped_bytes + 4 <= len) {
+ if (*((uint32_t *)(buf + skipped_bytes)) == FSYNC_PATTERN) {
+ skipped_bytes += 4;
+ (*fsync_count)++;
+ } else {
+ break;
+ }
+ }
+
+ return skipped_bytes;
+}
+
+/* remove coresight formatter frame */
+static int rvtrace_data_defmt(struct rvtrace_defmt_buf defmt_bufs[],
+ struct rvtrace_defmt_buf *out,
+ const unsigned char *buf, size_t len)
+{
+ int err;
+ uint8_t flag_bit;
+ unsigned char data = 0;
+ int cur_id = -1, old_id = -1, new_id = -1;
+
+ /* 16 bytes is output by coresight trace formatter */
+ while (len >= 16) {
+ /* some linux drivers (e.g. for perf) will insert FSYNCS to pad or differentiate
+ * between blocks of aligned data, always in frame aligned complete 16 byte frames.
+ * we need to skip past these frames, resetting as we go.
+ */
+ size_t fsync_count = 0;
+ size_t fsync_bytes = skip_frame_syncs(buf, len, &fsync_count);
+
+ if (fsync_bytes > 0) {
+ if (fsync_count % 4 == 0) {
+ cur_id = -1;
+ old_id = -1;
+ new_id = -1;
+ } else {
+ pr_err("Incorrect FSYNC reset pattern\n");
+ return -EINVAL;
+ }
+ buf += fsync_bytes;
+ len -= fsync_bytes;
+ continue;
+ }
+
+ flag_bit = 0x1;
+ for (int i = 0; i < 15; i += 2) {
+ if (buf[i] & 0x1) {
+ /* it's id */
+ old_id = new_id;
+ new_id = buf[i] >> 1; /* get new_id */
+ cur_id = (flag_bit & buf[15]) ? old_id : new_id;
+ } else {
+ /* it's data */
+ data = buf[i] | ((flag_bit & buf[15]) ? 0x1 : 0x0);
+ }
+
+ if (IS_VALID_ID(cur_id)) {
+ struct rvtrace_defmt_buf *defmt_buf =
+ rvtrace_get_buf(defmt_bufs, cur_id);
+
+ if (!defmt_buf)
+ return -ENOMEM;
+
+ /* it's data */
+ if ((buf[i] & 0x1) == 0) {
+ err = rvtrace_defmt_buf_append(defmt_buf, out, data);
+ if (err)
+ return err;
+ }
+
+ /* buf[i+1] is alway data when i != 14 */
+ if (i != 14) {
+ err = rvtrace_defmt_buf_append(defmt_buf, out,
+ buf[i + 1]);
+ if (err)
+ return err;
+ }
+ }
+
+ if (cur_id != new_id)
+ cur_id = new_id;
+
+ flag_bit <<= 1;
+ }
+ buf += 16;
+ len -= 16;
+ }
+ return 0;
+}
+
+/*
+ * Common processing for both dump and decode: de-format the raw AUX buffer
+ * into a contiguous Nexus byte stream (in memory), then dispatch to the
+ * format specific parser based on 'type'.
+ */
+static int rvtrace_decode(struct rvtrace_decoder *decoder,
+ enum rvtrace_decode_type type,
+ const unsigned char *buf, size_t len)
+{
+ int err;
+ const unsigned char *data = buf;
+ size_t size = len;
+ struct rvtrace_defmt_buf out = { 0 };
+
+ if (decoder->coresight_formatted) {
+ err = rvtrace_data_defmt(decoder->defmt_bufs, &out, buf, len);
+ if (err) {
+ pr_err("Encoder: failed to remove coresight formatter frame\n");
+ free(out.buf);
+ return err;
+ }
+ data = out.buf;
+ size = out.size;
+ }
+
+ switch (decoder->format) {
+ case GTRACE_FORMAT_NTRACE:
+ err = rvtrace_ntrace_decode(decoder, type, data, size);
+ break;
+ case GTRACE_FORMAT_ETRACE:
+ default:
+ err = -EINVAL;
+ pr_err("unsupported trace format %d\n", decoder->format);
+ break;
+ }
+
+ free(out.buf);
+ return err;
+}
+
+static int rvtrace_init_packet_buffer(struct rvtrace_packet_buffer *packet_buffer)
+{
+ if (!packet_buffer->packets) {
+ packet_buffer->packets = calloc(BUFFER_SIZE, sizeof(struct rvtrace_packet));
+ if (!packet_buffer->packets)
+ return -ENOMEM;
+ }
+
+ packet_buffer->size = 0;
+ packet_buffer->capacity = BUFFER_SIZE;
+
+ return 0;
+}
+
+static void rvtrace_free_packet_buffer(struct rvtrace_packet_buffer *packet_buffer)
+{
+ free(packet_buffer->packets);
+}
+
+int rvtrace_packet_buffer_append(struct rvtrace_packet_buffer *packet_buffer,
+ struct rvtrace_packet packet)
+{
+ if (packet_buffer->size == packet_buffer->capacity) {
+ packet_buffer->capacity *= 2;
+ packet_buffer->packets = realloc(packet_buffer->packets,
+ sizeof(struct rvtrace_packet) * packet_buffer->capacity);
+ if (!packet_buffer->packets)
+ return -ENOMEM;
+ }
+ packet_buffer->packets[packet_buffer->size++] = packet;
+
+ return 0;
+}
+
+int rvtrace_init_stack(struct rvtrace_stack *stack)
+{
+ if (!stack->data) {
+ stack->data = (u64 *)malloc(sizeof(u64) * BUFFER_SIZE);
+ if (!stack->data)
+ return -ENOMEM;
+ }
+
+ stack->top = -1;
+ stack->capacity = BUFFER_SIZE;
+
+ return 0;
+}
+
+int rvtrace_stack_push(struct rvtrace_stack *stack, u64 value)
+{
+ if (stack->top == stack->capacity - 1) {
+ stack->capacity *= 2;
+ stack->data = (u64 *)realloc(stack->data, sizeof(u64) * stack->capacity);
+ if (!stack->data)
+ return -ENOMEM;
+ }
+ stack->data[++stack->top] = value;
+
+ return 0;
+}
+
+int rvtrace_stack_pop(struct rvtrace_stack *stack)
+{
+ u64 value;
+
+ if (stack->top == -1)
+ return 1; /* Empty */
+
+ value = stack->data[stack->top--];
+ return value;
+}
+
+static void rvtrace_free_stack(struct rvtrace_stack *stack)
+{
+ free(stack->data);
+}
+
+struct rvtrace_src_context *rvtrace_get_src_context(struct rvtrace_decoder *decoder, int srcid)
+{
+ struct int_node *node;
+ struct rvtrace_src_context *ctx;
+
+ node = intlist__find(decoder->src_contexts, srcid);
+ if (node)
+ return (struct rvtrace_src_context *)node->priv;
+
+ ctx = zalloc(sizeof(struct rvtrace_src_context));
+ if (!ctx)
+ return NULL;
+
+ ctx->srcid = srcid;
+ ctx->nexdeco_pc = 1;
+ ctx->nexdeco_lastaddr = 1;
+ ctx->prv = 0;
+ ctx->v = 0;
+ ctx->timestamp = 0;
+ ctx->context = -1;
+ ctx->resourcefull_icnt = 0;
+
+ if (rvtrace_init_stack(&ctx->stack)) {
+ free(ctx);
+ return NULL;
+ }
+
+ /* Add to RB Tree */
+ node = intlist__findnew(decoder->src_contexts, srcid);
+ if (!node) {
+ rvtrace_free_stack(&ctx->stack);
+ free(ctx);
+ return NULL;
+ }
+
+ node->priv = ctx;
+
+ return ctx;
+}
+
+static void rvtrace_free_src_contexts(struct intlist *src_contexts)
+{
+ struct int_node *node;
+
+ intlist__for_each_entry(node, src_contexts) {
+ struct rvtrace_src_context *ctx = (struct rvtrace_src_context *)node->priv;
+
+ rvtrace_free_stack(&ctx->stack);
+ free(ctx);
+ }
+ intlist__delete(src_contexts);
+}
+
+struct rvtrace_decoder *rvtrace_decoder_new(struct rvtrace_decoder_params *params)
+{
+ int err;
+ struct rvtrace_decoder *decoder;
+
+ if (!params)
+ return NULL;
+
+ decoder = zalloc(sizeof(struct rvtrace_decoder));
+ if (!decoder)
+ return NULL;
+
+ decoder->data = params->data;
+ decoder->coresight_formatted = params->coresight_formatted;
+ decoder->format = params->format;
+ decoder->srcbits = params->srcbits;
+ decoder->mem_access = params->mem_access;
+ decoder->decode = rvtrace_decode;
+ decoder->current_src = -1;
+ decoder->src_contexts = intlist__new(NULL);
+ if (!decoder->src_contexts)
+ goto err_out_free_decoder;
+
+ err = rvtrace_init_packet_buffer(&decoder->packet_buffer);
+ if (err)
+ goto err_out_free_context;
+
+ return decoder;
+
+err_out_free_context:
+ rvtrace_free_src_contexts(decoder->src_contexts);
+err_out_free_decoder:
+ zfree(&decoder);
+ return NULL;
+}
+
+void rvtrace_decoder_free(struct rvtrace_decoder *decoder)
+{
+ for (int i = 0; i < MAX_ID; ++i)
+ free(decoder->defmt_bufs[i].buf);
+
+ rvtrace_free_packet_buffer(&decoder->packet_buffer);
+ rvtrace_free_src_contexts(decoder->src_contexts);
+ zfree(&decoder);
+}
+
+int rvtrace_decoder_reset(struct rvtrace_decoder *decoder)
+{
+ int err;
+ struct int_node *node;
+ struct rvtrace_src_context *ctx;
+
+ if (decoder->src_contexts) {
+ intlist__for_each_entry(node, decoder->src_contexts) {
+ ctx = (struct rvtrace_src_context *)node->priv;
+ ctx->nexdeco_pc = 1;
+ ctx->nexdeco_lastaddr = 1;
+ ctx->prv = 0;
+ ctx->v = 0;
+ ctx->timestamp = 0;
+ ctx->context = -1;
+ ctx->resourcefull_icnt = 0;
+ rvtrace_init_stack(&ctx->stack);
+ }
+ }
+ decoder->current_src_ctx = NULL;
+ decoder->current_src = -1;
+
+ err = rvtrace_init_packet_buffer(&decoder->packet_buffer);
+ if (err)
+ return err;
+
+ return 0;
+}
diff --git a/tools/perf/util/rvtrace-decoder/rvtrace-decoder.h b/tools/perf/util/rvtrace-decoder/rvtrace-decoder.h
new file mode 100644
index 000000000000..27c27d5fc3b8
--- /dev/null
+++ b/tools/perf/util/rvtrace-decoder/rvtrace-decoder.h
@@ -0,0 +1,122 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2026 Spacemit Technologies, Inc.
+ */
+
+#ifndef INCLUDE__RVTRACE_DECODER_H__
+#define INCLUDE__RVTRACE_DECODER_H__
+
+#include "../gtrace.h"
+#include "rvtrace-ntrace-decoder.h"
+
+#define MAX_ID 112
+
+/* check an ID is in the valid range */
+#define IS_VALID_ID(id) \
+ ((id > 0) && (id < MAX_ID))
+
+struct rvtrace_defmt_buf {
+ unsigned char *buf;
+ size_t size;
+ size_t capacity;
+};
+
+struct rvtrace_stack {
+ u64 *data;
+ int top;
+ int capacity;
+};
+
+enum rvtrace_sample_type {
+ RVTRACE_EMPTY,
+ RVTRACE_RANGE,
+ RVTRACE_LOSS,
+ RVTRACE_ERROR,
+};
+
+enum riscv_privilege_mode {
+ RISCV_PRIV_USER_MODE,
+ RISCV_PRIV_SUPERVISOR_MODE,
+ RISCV_PRIV_MACHINE_MODE = 3,
+};
+
+typedef u32 (*rvtrace_mem_access_type)(void *, u64, enum riscv_privilege_mode,
+ int, size_t, u8 *);
+
+struct rvtrace_packet {
+ enum rvtrace_sample_type sample_type;
+ u64 start_addr;
+ u64 end_addr;
+ u32 insn_cnt;
+ int cpu;
+ enum riscv_privilege_mode prv;
+ bool v;
+ int context;
+ u64 timestamp;
+};
+
+struct rvtrace_packet_buffer {
+ struct rvtrace_packet *packets;
+ int size;
+ int capacity;
+};
+
+struct rvtrace_src_context {
+ int srcid;
+ u64 nexdeco_pc;
+ u64 nexdeco_lastaddr;
+ u64 current_pc;
+ u64 timestamp;
+ enum riscv_privilege_mode prv;
+ bool v;
+ int context;
+ int resourcefull_icnt;
+ struct rvtrace_stack stack;
+};
+
+struct rvtrace_decoder_params {
+ void *data;
+ bool coresight_formatted;
+ enum gtrace_format format;
+ u32 srcbits;
+ rvtrace_mem_access_type mem_access;
+};
+
+/* What the decoder should do with the de-formatted trace stream. */
+enum rvtrace_decode_type {
+ RVTRACE_DECODE_MSG, /* print the Nexus messages */
+ RVTRACE_DECODE_INSN, /* reconstruct instruction trace packets */
+};
+
+struct rvtrace_decoder {
+ void *data;
+ bool coresight_formatted;
+ enum gtrace_format format;
+ u32 srcbits;
+ rvtrace_mem_access_type mem_access;
+ int (*decode)(struct rvtrace_decoder *decoder, enum rvtrace_decode_type type,
+ const unsigned char *buf, size_t size);
+ struct rvtrace_defmt_buf defmt_bufs[MAX_ID];
+ int current_src;
+ struct intlist *src_contexts;
+ struct rvtrace_src_context *current_src_ctx;
+ struct rvtrace_packet_buffer packet_buffer;
+ struct rvtrace_ntrace_msg msg; /* most recently parsed message */
+ struct rvtrace_ntrace_msg saved_msg; /* previous message, for RepeatBranch replay */
+};
+
+int rvtrace_packet_buffer_append(struct rvtrace_packet_buffer *packet_buffer,
+ struct rvtrace_packet packet);
+int rvtrace_init_stack(struct rvtrace_stack *stack);
+int rvtrace_stack_push(struct rvtrace_stack *stack, u64 value);
+int rvtrace_stack_pop(struct rvtrace_stack *stack);
+struct rvtrace_src_context *
+rvtrace_get_src_context(struct rvtrace_decoder *decoder, int srcid);
+struct rvtrace_decoder *rvtrace_decoder_new(struct rvtrace_decoder_params *params);
+void rvtrace_decoder_free(struct rvtrace_decoder *decoder);
+int rvtrace_decoder_reset(struct rvtrace_decoder *decoder);
+
+int rvtrace_ntrace_decode(struct rvtrace_decoder *decoder, enum rvtrace_decode_type type,
+ const unsigned char *buf, size_t size);
+
+#endif
diff --git a/tools/perf/util/rvtrace-decoder/rvtrace-ntrace-decoder.c b/tools/perf/util/rvtrace-decoder/rvtrace-ntrace-decoder.c
new file mode 100644
index 000000000000..f72e5baa323e
--- /dev/null
+++ b/tools/perf/util/rvtrace-decoder/rvtrace-ntrace-decoder.c
@@ -0,0 +1,910 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2020 IAR Systems AB.
+ * Copyright (c) 2026 Spacemit Technologies, Inc.
+ */
+
+#include <linux/err.h>
+#include <linux/zalloc.h>
+#include <linux/types.h>
+#include <errno.h>
+#include <api/fs/fs.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "../debug.h"
+#include "../color.h"
+#include "../intlist.h"
+#include "rvtrace-decoder.h"
+#include "rvtrace-ntrace-decoder.h"
+#include "../../../arch/riscv/include/asm/insn.h"
+
+#define RV_REGNO_ZERO 0
+#define RV_REGNO_RA 1
+
+/* Definition of Nexus Messages (subset applicable to RISC-V PC trace). */
+const struct rvtrace_ntrace_msg_field_def rvtrace_ntrace_msg_defs[] = {
+ NEXM_BEG(Ownership),
+ NEXM_FLD_PAR(SRC),
+ NEXM_FLD(FORMAT),
+ NEXM_FLD(PRV),
+ NEXM_FLD(V),
+ NEXM_VAR(CONTEXT),
+ NEXM_VAR(TSTAMP),
+ NEXM_END(),
+
+ NEXM_BEG(DirectBranch),
+ NEXM_FLD_PAR(SRC),
+ NEXM_VAR(ICNT),
+ NEXM_VAR(TSTAMP),
+ NEXM_END(),
+
+ NEXM_BEG(IndirectBranch),
+ NEXM_FLD_PAR(SRC),
+ NEXM_FLD(BTYPE),
+ NEXM_VAR(ICNT),
+ NEXM_ADR(UADDR),
+ NEXM_VAR(TSTAMP),
+ NEXM_END(),
+
+ NEXM_BEG(Error),
+ NEXM_FLD_PAR(SRC),
+ NEXM_FLD(ETYPE),
+ NEXM_VAR(PAD),
+ NEXM_VAR(TSTAMP),
+ NEXM_END(),
+
+ NEXM_BEG(ProgTraceSync),
+ NEXM_FLD_PAR(SRC),
+ NEXM_FLD(SYNC),
+ NEXM_VAR(ICNT),
+ NEXM_ADR(FADDR),
+ NEXM_VAR(TSTAMP),
+ NEXM_END(),
+
+ NEXM_BEG(DirectBranchSync),
+ NEXM_FLD_PAR(SRC),
+ NEXM_FLD(SYNC),
+ NEXM_VAR(ICNT),
+ NEXM_ADR(FADDR),
+ NEXM_VAR(TSTAMP),
+ NEXM_END(),
+
+ NEXM_BEG(IndirectBranchSync),
+ NEXM_FLD_PAR(SRC),
+ NEXM_FLD(SYNC),
+ NEXM_FLD(BTYPE),
+ NEXM_VAR(ICNT),
+ NEXM_ADR(FADDR),
+ NEXM_VAR(TSTAMP),
+ NEXM_END(),
+
+ NEXM_BEG(ResourceFull),
+ NEXM_FLD_PAR(SRC),
+ NEXM_FLD(RCODE),
+ NEXM_VAR(RDATA),
+ NEXM_VAR(HREPEAT),
+ NEXM_VAR(TSTAMP),
+ NEXM_END(),
+
+ NEXM_BEG(IndirectBranchHist),
+ NEXM_FLD_PAR(SRC),
+ NEXM_FLD(BTYPE),
+ NEXM_VAR(ICNT),
+ NEXM_ADR(UADDR),
+ NEXM_VAR(HIST),
+ NEXM_VAR(TSTAMP),
+ NEXM_END(),
+
+ NEXM_BEG(IndirectBranchHistSync),
+ NEXM_FLD_PAR(SRC),
+ NEXM_FLD(SYNC),
+ NEXM_FLD(BTYPE),
+ NEXM_VAR(ICNT),
+ NEXM_ADR(FADDR),
+ NEXM_VAR(HIST),
+ NEXM_VAR(TSTAMP),
+ NEXM_END(),
+
+ NEXM_BEG(RepeatBranch),
+ NEXM_FLD_PAR(SRC),
+ NEXM_VAR(BCNT),
+ NEXM_VAR(TSTAMP),
+ NEXM_END(),
+
+ NEXM_BEG(ProgTraceCorrelation),
+ NEXM_FLD_PAR(SRC),
+ NEXM_FLD(EVCODE),
+ NEXM_FLD(CDF),
+ NEXM_VAR(ICNT),
+ NEXM_VAR(HIST),
+ NEXM_VAR(TSTAMP),
+ NEXM_END(),
+
+ { NULL, 0 } /* end marker */
+};
+
+/*
+ * Look up the rvtrace_ntrace_msg_defs entry index of a message header whose low 8 bits
+ * match 'tcode'. Returns the index, or -1 if the TCODE is not defined.
+ */
+static int rvtrace_ntrace_find_tcode(unsigned int tcode)
+{
+ for (int d = 0; rvtrace_ntrace_msg_defs[d].def != 0; d++) {
+ if ((rvtrace_ntrace_msg_defs[d].def & NEXM_DEF_MSG) == 0)
+ continue;
+ if ((rvtrace_ntrace_msg_defs[d].def & 0xff) == tcode)
+ return d;
+ }
+ return -1;
+}
+
+/*
+ * Return the bit width of the fixed-size field at rvtrace_ntrace_msg_defs[def]. Fields
+ * marked NEXM_DEF_PARAM take their width from the decoder's srcbits.
+ */
+static int rvtrace_ntrace_field_size(struct rvtrace_decoder *decoder, int def)
+{
+ int size = rvtrace_ntrace_msg_defs[def].def & 0xff;
+
+ if (rvtrace_ntrace_msg_defs[def].def & NEXM_DEF_PARAM)
+ size = decoder->srcbits;
+ return size;
+}
+
+/*
+ * Feed the byte stream one byte at a time. The parser accumulates
+ * MDO/MSEO bits into 'msg' and returns:
+ *
+ * 1 a complete message has been parsed into *msg
+ * 0 more bytes are needed (or an idle/partial byte was consumed)
+ */
+static int rvtrace_ntrace_msg_parse(struct rvtrace_decoder *decoder,
+ struct rvtrace_ntrace_msg_parser *p,
+ struct rvtrace_ntrace_msg *msg,
+ unsigned char byte, unsigned char prev_byte)
+{
+ unsigned int mdo = byte >> 2;
+ unsigned int mseo = byte & 0x3;
+
+ pr_debug3("0x%02x ", byte);
+ for (int b = 0x80; b != 0; b >>= 1) {
+ if (b == 0x2)
+ pr_debug3("_");
+ pr_debug3("%d", !!(byte & b));
+ }
+ pr_debug3("\n");
+
+ if (p->resync) {
+ if ((prev_byte & 0x3) == 0x3) { /* MSEO==3: message end */
+ p->resync = false;
+ p->field_def = -1;
+ p->field_bits = 0;
+ p->field_val = 0;
+ }
+ return 0;
+ }
+
+ /* Skip long runs of idle bytes (0xFF). */
+ if (byte == 0xFF && prev_byte == 0xFF)
+ return 0;
+
+ if (mseo == 0x2) {
+ pr_err("ERROR: MSEO='10' is not allowed\n");
+ p->resync = true;
+ return 0;
+ }
+
+ /* Message header: the first byte's MDO is the TCODE. */
+ if (p->field_def < 0) {
+ if (mseo == 0x3)
+ return 0; /* skip idle */
+
+ if (mseo != 0x0) {
+ pr_err("ERROR: Message must start from MSEO='00'\n");
+ p->resync = true;
+ return 0;
+ }
+
+ p->field_def = rvtrace_ntrace_find_tcode(mdo);
+ if (p->field_def < 0) {
+ pr_err("ERROR: Message with TCODE=%d is not defined for RISC-V\n", mdo);
+ p->resync = true;
+ return 0;
+ }
+
+ msg->field_pos = p->field_def;
+ msg->tcode = mdo;
+ msg->nfields = 0;
+ msg->fields[msg->nfields++] = mdo;
+
+ p->field_def++;
+ p->field_bits = 0;
+ p->field_val = 0;
+ return 0;
+ }
+
+ /* Accumulate this byte's MDO into the current field value. */
+ p->field_val |= (u64)mdo << p->field_bits;
+ p->field_bits += 6;
+
+ /* Emit as many complete fixed-size fields as we now have bits for. */
+ while ((rvtrace_ntrace_msg_defs[p->field_def].def & NEXM_DEF_FLD) &&
+ p->field_bits >= rvtrace_ntrace_field_size(decoder, p->field_def)) {
+ int width = rvtrace_ntrace_field_size(decoder, p->field_def);
+
+ msg->fields[msg->nfields++] = p->field_val & ((1ULL << width) - 1);
+ p->field_def++;
+ p->field_val >>= width;
+ p->field_bits -= width;
+ }
+
+ if (mseo == 0x0)
+ return 0;
+
+ if (rvtrace_ntrace_msg_defs[p->field_def].def & NEXM_DEF_VAR) {
+ /* Variable-size field: the whole remaining value. */
+ msg->fields[msg->nfields++] = p->field_val;
+
+ if (rvtrace_ntrace_msg_defs[p->field_def].def & NEXM_DEF_ADR)
+ msg->addr_bits = p->field_bits;
+
+ if (mseo == 3) {
+ p->field_def = -1;
+ p->field_bits = 0;
+ p->field_val = 0;
+ return 1; /* complete message */
+ }
+ p->field_def++;
+ p->field_bits = 0;
+ p->field_val = 0;
+ return 0;
+ }
+
+ if (p->field_bits > 0) {
+ pr_err("Decode: Not enough bits for non-variable field\n");
+ p->resync = true;
+ }
+ return 0;
+}
+
+static int rvtrace_ntrace_msg_sprint(char *buf, size_t size,
+ struct rvtrace_ntrace_msg *msg, int msgs, int bytes)
+{
+ int len;
+
+ len = snprintf(buf, size, "MSG #%d +%d - %s TCODE[6]=%d",
+ msgs, bytes,
+ rvtrace_ntrace_msg_defs[msg->field_pos].name, msg->tcode);
+
+ for (int i = 1; i < (int)msg->nfields && len < (int)size; i++)
+ len += snprintf(buf + len, size - len, " %s=0x%llx",
+ rvtrace_ntrace_msg_defs[msg->field_pos + i].name,
+ (unsigned long long)msg->fields[i]);
+
+ return len;
+}
+
+static int handle_error_msg(struct rvtrace_src_context *ctx, const char *err)
+{
+ pr_err("\nERROR: %s\n", err);
+ ctx->nexdeco_pc = 1; /* 1 means, that last address is unknown */
+ rvtrace_init_stack(&ctx->stack);
+ return -EINVAL;
+}
+
+static int rvtrace_ntrace_insn_info_get(struct rvtrace_decoder *decoder, u8 *info, u64 *dest)
+{
+ u32 insn;
+ struct rvtrace_src_context *ctx = decoder->current_src_ctx;
+ u64 addr = ctx->nexdeco_pc;
+
+ if (!decoder->mem_access(decoder->data, addr, ctx->prv, ctx->context,
+ sizeof(insn), (u8 *)&insn))
+ return -EINVAL;
+
+ *info = INFO_LINEAR;
+ if ((insn & (__INSN_LENGTH_MASK)) != (__INSN_LENGTH_GE_32)) {
+ if (riscv_insn_is_c_beqz(insn) || riscv_insn_is_c_bnez(insn)) {
+ *info = INFO_BRANCH;
+ *dest = addr + riscv_insn_extract_cbtype_imm(insn);
+ } else if (riscv_insn_is_c_j(insn)) {
+ /* c.j offset */
+ *info = INFO_JUMP;
+ *dest = addr + riscv_insn_extract_cjtype_imm(insn);
+ } else if (riscv_insn_is_c_jr(insn)) {
+ *info = INFO_JUMP | INFO_INDIRECT;
+ /* ret => c.jr x1 */
+ if (RVC_EXTRACT_C2_RS1_REG(insn) == RV_REGNO_RA)
+ *info |= INFO_RET;
+ } else if (riscv_insn_is_c_jalr(insn)) {
+ *info = INFO_JUMP | INFO_CALL | INFO_INDIRECT;
+ } else {
+ *info = INFO_LINEAR;
+ }
+ } else {
+ if (riscv_insn_is_branch(insn)) {
+ *info = INFO_BRANCH;
+ *dest = addr + riscv_insn_extract_btype_imm(insn);
+ } else if (riscv_insn_is_jalr(insn)) {
+ *info = INFO_JUMP | INFO_INDIRECT;
+ if (RV_EXTRACT_RD_REG(insn) == RV_REGNO_ZERO
+ && riscv_insn_extract_jtype_imm(insn) == 0) {
+ /* ret => jalr x0,x1,0 */
+ if (RV_EXTRACT_RS1_REG(insn) == RV_REGNO_RA)
+ *info |= INFO_RET;
+ else
+ /* jr rs1 => jalr x0,rs1,0 */
+ *info |= INFO_CALL;
+ }
+ } else if (riscv_insn_is_jal(insn)) {
+ *info = INFO_JUMP;
+ /* j offset => jal x0,offset */
+ if (RV_EXTRACT_RD_REG(insn) != RV_REGNO_ZERO)
+ *info |= INFO_CALL;
+ *dest = addr + riscv_insn_extract_jtype_imm(insn);
+ } else if (riscv_insn_is_sret(insn) || riscv_insn_is_mret(insn)) {
+ *info = INFO_JUMP | INFO_INDIRECT | INFO_RET;
+ } else if (riscv_insn_is_ecall(insn)) {
+ *info = INFO_JUMP | INFO_INDIRECT | INFO_CALL;
+ } else {
+ *info = INFO_LINEAR;
+ }
+ *info |= INFO_4;
+ }
+
+ pr_debug2(".nexdeco_pc=0x%lx .insn=0x%0*x\n", addr,
+ (*info & INFO_4) ? 8 : 4,
+ (*info & INFO_4) ? insn : (u16)insn);
+ return 0;
+}
+
+static int rvtrace_ntrace_emit_icnt(struct rvtrace_decoder *decoder, int n, u32 hist)
+{
+ u64 a = 0;
+ u32 hist_mask;
+ u8 info;
+ int done_icnt = 0; /* Number of done instruction count */
+ struct rvtrace_src_context *ctx = decoder->current_src_ctx;
+
+ if (ctx->nexdeco_pc & 1)
+ return 0; /* Not synchronized ... */
+
+ pr_debug2(".PC=0x%lX, EmitICNT(n=%d,hist=0x%x)\n", ctx->nexdeco_pc, n, hist);
+
+ /*
+ * Adjust ICNT by what was handled by ResourceFull message[s]
+ * before this message (message with normal ICNT field).
+ * NOTE: ctx->resourcefull_icnt maybe positive or negative!
+ */
+ if (n >= 0 && ctx->resourcefull_icnt != 0) {
+ n += ctx->resourcefull_icnt;
+ if (n < 0)
+ return handle_error_msg(ctx, "ICNT adjustment ERROR");
+
+ ctx->resourcefull_icnt = 0; /* Make adjustment 'consumed' */
+ }
+
+ hist_mask = 0; /* MSB is first in history, so we need sliding mask */
+ if (hist != 0) {
+ if (hist & (1 << (sizeof(u32) * 8 - 1))) {
+ hist_mask = (1 << (sizeof(u32) * 8 - 2));
+ } else {
+ hist_mask = 0x1;
+ while (hist_mask <= hist)
+ hist_mask <<= 1;
+ hist_mask >>= 2;
+ }
+ }
+
+ while (n != 0) {
+ ctx->current_pc = ctx->nexdeco_pc;
+ if (rvtrace_ntrace_insn_info_get(decoder, &info, &a))
+ return handle_error_msg(ctx, "failed to get insn info");
+
+ if (n > 0) {
+ n -= (info & INFO_4) ? 2 : 1;
+ if (n < 0)
+ return handle_error_msg(ctx, "ICNT too small");
+ done_icnt += 1;
+ }
+
+ if (info & INFO_CALL) {
+ u64 ret = ctx->nexdeco_pc + ((info & INFO_4) ? 4 : 2);
+
+ if (rvtrace_stack_push(&ctx->stack, ret))
+ return handle_error_msg(ctx, "failed to push value to stack");
+ }
+
+ if (info & INFO_INDIRECT) { /* Cannot continue over indirect... */
+ if (info & INFO_RET) {
+ u64 ret = rvtrace_stack_pop(&ctx->stack);
+
+ ctx->nexdeco_pc = ret;
+ if (n != 0) {
+ if (ret == 1)
+ return handle_error_msg(ctx,
+ "Not enough entries on callstack");
+ continue;
+ }
+ }
+
+ if (n > 0)
+ return handle_error_msg(ctx,
+ "indirect address encountered in ICNT");
+
+ break;
+ }
+
+ if (info & INFO_BRANCH) {
+ if (hist == 0) {
+ /* This is calling as DirectBranch */
+ if (n == 0)
+ info |= INFO_JUMP; /* Force PC change below */
+ } else {
+ if (hist_mask & hist)
+ info |= INFO_JUMP; /* Force PC change below */
+ hist_mask >>= 1;
+ if (hist_mask == 0 && n < 0)
+ n = 0;
+ }
+ }
+
+ if (info & INFO_JUMP)
+ ctx->nexdeco_pc = a; /* Direct jump/call/branch */
+ else if (info & INFO_4)
+ ctx->nexdeco_pc += 4;
+ else
+ ctx->nexdeco_pc += 2;
+
+ }
+
+ return done_icnt;
+}
+
+static u64 rvtrace_ntrace_field_get(struct rvtrace_decoder *decoder, const char *name)
+{
+ struct rvtrace_ntrace_msg *msg = &decoder->msg;
+
+ for (int d = msg->field_pos; rvtrace_ntrace_msg_defs[d].def != 1; d++) {
+ if (strcmp(rvtrace_ntrace_msg_defs[d].name, name) == 0) {
+ int fi = d - msg->field_pos;
+
+ if (fi < (int)msg->nfields)
+ return msg->fields[fi];
+ return 0;
+ }
+ }
+ return 0;
+}
+
+#define NEX_FLDGET(n) rvtrace_ntrace_field_get(decoder, #n)
+
+/*
+ * Switch the active source context to 'new_src', allocating it on first use.
+ * Returns 0 on success, negative on error.
+ */
+static int rvtrace_ntrace_switch_src(struct rvtrace_decoder *decoder, int new_src)
+{
+ if (new_src == decoder->current_src)
+ return 0;
+
+ decoder->current_src_ctx = rvtrace_get_src_context(decoder, new_src);
+ if (!decoder->current_src_ctx)
+ return -ENOMEM;
+
+ decoder->current_src = new_src;
+ return 0;
+}
+
+/*
+ * Fill 'packet' from the active source context and append it to the decoder's
+ * packet buffer. 'ret' is the instruction count produced by emit_icnt.
+ */
+static int rvtrace_ntrace_emit_packet(struct rvtrace_decoder *decoder, int tcode,
+ u64 start_addr, int ret, int src)
+{
+ struct rvtrace_packet packet;
+
+ if (ret >= 0) {
+ packet.sample_type = (tcode == NEXUS_TCODE_Error) ?
+ RVTRACE_LOSS :
+ (ret ? RVTRACE_RANGE : RVTRACE_EMPTY);
+ packet.start_addr = start_addr;
+ packet.end_addr = decoder->current_src_ctx->current_pc;
+ packet.insn_cnt = ret;
+ packet.cpu = src;
+ packet.prv = decoder->current_src_ctx->prv;
+ packet.v = decoder->current_src_ctx->v;
+ packet.context = decoder->current_src_ctx->context;
+ packet.timestamp = decoder->current_src_ctx->timestamp;
+ } else {
+ packet.sample_type = RVTRACE_ERROR;
+ }
+
+ return rvtrace_packet_buffer_append(&decoder->packet_buffer, packet);
+}
+
+/* Reconstruct an address from the transmitted F-ADDR/U-ADDR field */
+static u64 rvtrace_ntrace_calculate_addr(struct rvtrace_decoder *decoder,
+ u64 fu_addr, int full, u64 prev_addr)
+{
+ u32 bits = decoder->msg.addr_bits;
+
+ fu_addr <<= 1; /* LSB bit is never sent */
+
+ /*
+ * After shifting the LSB back in, the most significant transmitted bit
+ * sits at position 'bits'. Extend it (0 or 1) all the way to bit 63.
+ * This is sign-extension in effect, but the spec notes it is NOT a sign
+ * bit -- it is simply the value of the skipped identical bits.
+ */
+ if (bits && bits < 64 && (fu_addr & (1ULL << bits)))
+ fu_addr |= ~0ULL << bits;
+
+ /* Update (NEW or XOR) */
+ if (!full) {
+ if (prev_addr & 1) /* Not Sync */
+ fu_addr = prev_addr;
+ else
+ fu_addr ^= prev_addr;
+ }
+ return fu_addr;
+}
+
+/*
+ * Per-message handler result. Each handler fills these and the dispatcher
+ * emits a single packet from them.
+ */
+struct rvtrace_ntrace_result {
+ int icnt; /* instruction count from emit_icnt() */
+ u64 start_addr;
+ int src;
+};
+
+/* Common prologue: switch to the message's source context. */
+static int rvtrace_ntrace_handler_prologue(struct rvtrace_decoder *decoder,
+ struct rvtrace_ntrace_result *res)
+{
+ res->src = NEX_FLDGET(SRC);
+ return rvtrace_ntrace_switch_src(decoder, res->src);
+}
+
+/*
+ * Common F-ADDR/U-ADDR handling: reconstruct the address and make it the
+ * current PC. 'full' selects full (sync) vs XOR (compressed) update.
+ */
+static void rvtrace_ntrace_update_addr(struct rvtrace_decoder *decoder, u64 addr, int full)
+{
+ struct rvtrace_src_context *ctx = decoder->current_src_ctx;
+
+ ctx->nexdeco_lastaddr = rvtrace_ntrace_calculate_addr(decoder, addr, full,
+ ctx->nexdeco_lastaddr);
+ pr_debug2(".nexdeco_lastaddr=0x%lx\n", ctx->nexdeco_lastaddr);
+ ctx->nexdeco_pc = ctx->nexdeco_lastaddr;
+}
+
+static int rvtrace_ntrace_handle_ownership(struct rvtrace_decoder *decoder,
+ struct rvtrace_ntrace_result *res)
+{
+ int ret = rvtrace_ntrace_handler_prologue(decoder, res);
+
+ if (ret)
+ return ret;
+
+ decoder->current_src_ctx->prv = NEX_FLDGET(PRV);
+ decoder->current_src_ctx->v = NEX_FLDGET(V);
+ if (NEX_FLDGET(FORMAT) && NEX_FLDGET(CONTEXT))
+ decoder->current_src_ctx->context = NEX_FLDGET(CONTEXT);
+ decoder->current_src_ctx->timestamp += NEX_FLDGET(TSTAMP);
+ return 0;
+}
+
+static int rvtrace_ntrace_handle_direct_branch(struct rvtrace_decoder *decoder,
+ struct rvtrace_ntrace_result *res)
+{
+ int ret = rvtrace_ntrace_handler_prologue(decoder, res);
+
+ if (ret)
+ return ret;
+
+ res->start_addr = decoder->current_src_ctx->nexdeco_pc;
+ res->icnt = rvtrace_ntrace_emit_icnt(decoder, NEX_FLDGET(ICNT), 0x0);
+ decoder->current_src_ctx->timestamp += NEX_FLDGET(TSTAMP);
+ return 0;
+}
+
+static int rvtrace_ntrace_handle_indirect_branch(struct rvtrace_decoder *decoder,
+ struct rvtrace_ntrace_result *res)
+{
+ int ret = rvtrace_ntrace_handler_prologue(decoder, res);
+
+ if (ret)
+ return ret;
+
+ res->start_addr = decoder->current_src_ctx->nexdeco_pc;
+ res->icnt = rvtrace_ntrace_emit_icnt(decoder, NEX_FLDGET(ICNT), 0x0);
+
+ rvtrace_ntrace_update_addr(decoder, NEX_FLDGET(UADDR), 0);
+ decoder->current_src_ctx->timestamp += NEX_FLDGET(TSTAMP);
+ return 0;
+}
+
+static int rvtrace_ntrace_handle_prog_trace_sync(struct rvtrace_decoder *decoder,
+ struct rvtrace_ntrace_result *res)
+{
+ int ret = rvtrace_ntrace_handler_prologue(decoder, res);
+
+ if (ret)
+ return ret;
+
+ res->start_addr = decoder->current_src_ctx->nexdeco_pc;
+ res->icnt = rvtrace_ntrace_emit_icnt(decoder, NEX_FLDGET(ICNT), 0x0);
+
+ rvtrace_ntrace_update_addr(decoder, NEX_FLDGET(FADDR), 1);
+ decoder->current_src_ctx->timestamp = NEX_FLDGET(TSTAMP);
+ return 0;
+}
+
+static int rvtrace_ntrace_handle_direct_branch_sync(struct rvtrace_decoder *decoder,
+ struct rvtrace_ntrace_result *res)
+{
+ int ret = rvtrace_ntrace_handler_prologue(decoder, res);
+
+ if (ret)
+ return ret;
+
+ res->start_addr = decoder->current_src_ctx->nexdeco_pc;
+ res->icnt = rvtrace_ntrace_emit_icnt(decoder, NEX_FLDGET(ICNT), 0x0);
+
+ rvtrace_ntrace_update_addr(decoder, NEX_FLDGET(FADDR), 1);
+ decoder->current_src_ctx->timestamp = NEX_FLDGET(TSTAMP);
+ return 0;
+}
+
+static int rvtrace_ntrace_handle_indirect_branch_sync(struct rvtrace_decoder *decoder,
+ struct rvtrace_ntrace_result *res)
+{
+ int ret = rvtrace_ntrace_handler_prologue(decoder, res);
+
+ if (ret)
+ return ret;
+
+ res->start_addr = decoder->current_src_ctx->nexdeco_pc;
+ res->icnt = rvtrace_ntrace_emit_icnt(decoder, NEX_FLDGET(ICNT), 0x0);
+
+ rvtrace_ntrace_update_addr(decoder, NEX_FLDGET(FADDR), 1);
+ decoder->current_src_ctx->timestamp = NEX_FLDGET(TSTAMP);
+ return 0;
+}
+
+static int rvtrace_ntrace_handle_indirect_branch_hist(struct rvtrace_decoder *decoder,
+ struct rvtrace_ntrace_result *res)
+{
+ int ret = rvtrace_ntrace_handler_prologue(decoder, res);
+
+ if (ret)
+ return ret;
+
+ res->start_addr = decoder->current_src_ctx->nexdeco_pc;
+ res->icnt = rvtrace_ntrace_emit_icnt(decoder, NEX_FLDGET(ICNT), NEX_FLDGET(HIST));
+
+ rvtrace_ntrace_update_addr(decoder, NEX_FLDGET(UADDR), 0);
+ decoder->current_src_ctx->timestamp += NEX_FLDGET(TSTAMP);
+ return 0;
+}
+
+static int rvtrace_ntrace_handle_indirect_branch_hist_sync(struct rvtrace_decoder *decoder,
+ struct rvtrace_ntrace_result *res)
+{
+ int ret = rvtrace_ntrace_handler_prologue(decoder, res);
+
+ if (ret)
+ return ret;
+
+ res->start_addr = decoder->current_src_ctx->nexdeco_pc;
+ res->icnt = rvtrace_ntrace_emit_icnt(decoder, NEX_FLDGET(ICNT), NEX_FLDGET(HIST));
+
+ rvtrace_ntrace_update_addr(decoder, NEX_FLDGET(FADDR), 1);
+ decoder->current_src_ctx->timestamp = NEX_FLDGET(TSTAMP);
+ return 0;
+}
+
+static int rvtrace_ntrace_handle_resource_full(struct rvtrace_decoder *decoder,
+ struct rvtrace_ntrace_result *res)
+{
+ int ret = rvtrace_ntrace_handler_prologue(decoder, res);
+ int rcode = NEX_FLDGET(RCODE);
+ int hrepeat = 0;
+
+ if (ret)
+ return ret;
+
+ if (rcode == 2)
+ hrepeat = NEX_FLDGET(HREPEAT);
+
+ res->start_addr = decoder->current_src_ctx->nexdeco_pc;
+ decoder->current_src_ctx->timestamp += NEX_FLDGET(TSTAMP);
+ if (rcode == 1 || rcode == 2) {
+ int rdata = NEX_FLDGET(RDATA);
+
+ if (rdata > 1) {
+ do {
+ /* ICNT is unknown (-1), what will process only HIST bits */
+ res->icnt = rvtrace_ntrace_emit_icnt(decoder, -1, rdata);
+ if (res->icnt < 0)
+ break;
+
+ /* Consume, so next time ICNT will be adjusted */
+ decoder->current_src_ctx->resourcefull_icnt -= res->icnt;
+ hrepeat--;
+ } while (hrepeat > 0);
+ }
+ } else if (rcode == 0) {
+ decoder->current_src_ctx->resourcefull_icnt += NEX_FLDGET(RDATA);
+ }
+ return 0;
+}
+
+static int rvtrace_ntrace_handle_prog_trace_correlation(struct rvtrace_decoder *decoder,
+ struct rvtrace_ntrace_result *res)
+{
+ int ret = rvtrace_ntrace_handler_prologue(decoder, res);
+ int hist = 0;
+
+ if (ret)
+ return ret;
+
+ if (NEX_FLDGET(CDF) == 1)
+ hist = NEX_FLDGET(HIST);
+
+ res->start_addr = decoder->current_src_ctx->nexdeco_pc;
+ res->icnt = rvtrace_ntrace_emit_icnt(decoder, NEX_FLDGET(ICNT), hist);
+ decoder->current_src_ctx->timestamp += NEX_FLDGET(TSTAMP);
+ return 0;
+}
+
+static int rvtrace_ntrace_handle_error(struct rvtrace_decoder *decoder,
+ struct rvtrace_ntrace_result *res)
+{
+ int ret = rvtrace_ntrace_handler_prologue(decoder, res);
+
+ if (ret)
+ return ret;
+
+ decoder->current_src_ctx->timestamp += NEX_FLDGET(TSTAMP);
+ return 0;
+}
+
+static int rvtrace_ntrace_msg_handle(struct rvtrace_decoder *decoder)
+{
+ struct rvtrace_ntrace_result res = { 0 };
+ int tcode = decoder->msg.tcode;
+ int err;
+
+ switch (tcode) {
+ case NEXUS_TCODE_Ownership:
+ err = rvtrace_ntrace_handle_ownership(decoder, &res);
+ break;
+ case NEXUS_TCODE_DirectBranch:
+ err = rvtrace_ntrace_handle_direct_branch(decoder, &res);
+ break;
+ case NEXUS_TCODE_IndirectBranch:
+ err = rvtrace_ntrace_handle_indirect_branch(decoder, &res);
+ break;
+ case NEXUS_TCODE_ProgTraceSync:
+ err = rvtrace_ntrace_handle_prog_trace_sync(decoder, &res);
+ break;
+ case NEXUS_TCODE_DirectBranchSync:
+ err = rvtrace_ntrace_handle_direct_branch_sync(decoder, &res);
+ break;
+ case NEXUS_TCODE_IndirectBranchSync:
+ err = rvtrace_ntrace_handle_indirect_branch_sync(decoder, &res);
+ break;
+ case NEXUS_TCODE_IndirectBranchHist:
+ err = rvtrace_ntrace_handle_indirect_branch_hist(decoder, &res);
+ break;
+ case NEXUS_TCODE_IndirectBranchHistSync:
+ err = rvtrace_ntrace_handle_indirect_branch_hist_sync(decoder, &res);
+ break;
+ case NEXUS_TCODE_ResourceFull:
+ err = rvtrace_ntrace_handle_resource_full(decoder, &res);
+ break;
+ case NEXUS_TCODE_ProgTraceCorrelation:
+ err = rvtrace_ntrace_handle_prog_trace_correlation(decoder, &res);
+ break;
+ case NEXUS_TCODE_Error:
+ err = rvtrace_ntrace_handle_error(decoder, &res);
+ break;
+ case NEXUS_TCODE_RepeatBranch: /* Handled differently! */
+ default:
+ return -EINVAL;
+ }
+
+ if (err)
+ return err;
+
+ return rvtrace_ntrace_emit_packet(decoder, tcode, res.start_addr, res.icnt, res.src);
+}
+
+/*
+ * Decode the N-Trace byte stream.
+ *
+ * RVTRACE_DECODE_MSG - print the messages
+ * RVTRACE_DECODE_INSN - reconstruct instruction flow
+ */
+int rvtrace_ntrace_decode(struct rvtrace_decoder *decoder, enum rvtrace_decode_type type,
+ const unsigned char *buf, size_t size)
+{
+ const char *color = PERF_COLOR_BLUE;
+ struct rvtrace_ntrace_msg_parser p = { .field_def = -1 };
+ struct rvtrace_ntrace_msg *msg = &decoder->msg;
+ struct {
+ int msgs;
+ int bytes;
+ int idles;
+ int errors;
+ int invalid;
+ } stat = { 0 };
+ unsigned char prev_byte = 0;
+ bool was_resync = false;
+ size_t pos;
+ int err;
+
+ msg->nfields = 0;
+
+ for (pos = 0; pos < size; pos++) {
+ unsigned char byte = buf[pos];
+ char line[256];
+ int done = rvtrace_ntrace_msg_parse(decoder, &p, msg, byte, prev_byte);
+ int cnt = 1;
+
+ prev_byte = byte;
+ stat.bytes++;
+
+ if (p.resync && !was_resync)
+ stat.invalid++;
+ was_resync = p.resync;
+
+ if (done <= 0)
+ continue;
+
+ stat.msgs++;
+ if (msg->tcode == NEXUS_TCODE_Error)
+ stat.errors++;
+
+ rvtrace_ntrace_msg_sprint(line, sizeof(line), msg, stat.msgs, stat.bytes);
+ if (type == RVTRACE_DECODE_MSG) {
+ color_fprintf(stdout, color, "%s\n", line);
+ continue;
+ }
+ pr_debug2("%s\n", line);
+
+ if (msg->tcode == NEXUS_TCODE_RepeatBranch) {
+ cnt = msg->fields[1];
+ *msg = decoder->saved_msg;
+ } else {
+ /* Save previous message fields */
+ decoder->saved_msg = *msg;
+ }
+
+ while (cnt-- > 0) {
+ err = rvtrace_ntrace_msg_handle(decoder);
+ if (err)
+ return err;
+ }
+ }
+
+ if (type == RVTRACE_DECODE_MSG) {
+ color_fprintf(stdout, color,
+ "\nN-Trace: %d bytes, %d messages, %d error messages, %d invalid messages",
+ stat.bytes, stat.msgs, stat.errors, stat.invalid);
+ } else {
+ pr_debug2("\nN-Trace: %d bytes, %d messages, %d errors, %d invalid\n",
+ stat.bytes, stat.msgs, stat.errors, stat.invalid);
+ }
+
+ return 0;
+}
+
diff --git a/tools/perf/util/rvtrace-decoder/rvtrace-ntrace-decoder.h b/tools/perf/util/rvtrace-decoder/rvtrace-ntrace-decoder.h
new file mode 100644
index 000000000000..4575e631ba5e
--- /dev/null
+++ b/tools/perf/util/rvtrace-decoder/rvtrace-ntrace-decoder.h
@@ -0,0 +1,124 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2020 IAR Systems AB.
+ * Copyright (c) 2026 Spacemit Technologies, Inc.
+ */
+
+#ifndef INCLUDE__RVTRACE_NTRACE_DECODER_H__
+#define INCLUDE__RVTRACE_NTRACE_DECODER_H__
+
+#include <stdint.h> // For uint32_t and uint64_t
+#include <stdbool.h>
+#include <linux/types.h>
+
+/****************************************************************************/
+/* Instruction information */
+#define INFO_LINEAR 0x1 // Linear (plain instruction or not taken BRANCH)
+#define INFO_4 0x2 // If not 4, it must be 2 on RISC-V
+// 0x4 // Reserved (for exception or so ...)
+#define INFO_INDIRECT 0x8 // Possible for most types above
+#define INFO_BRANCH 0x10 // Always direct on RISC-V (may have LINEAR too)
+#define INFO_JUMP 0x20 // Direct or indirect
+#define INFO_CALL 0x40 // Direct or indirect (and always a jump)
+#define INFO_RET 0x80 // Return (always indirect and always a jump)
+
+/****************************************************************************/
+
+/****************************************************************************/
+/* Nexus specific values (based on Nexus Standard PDF) */
+
+#define NEXUS_FLDSIZE_TCODE 6 // This is standard
+
+/* Nexus TCODE values applicable to RISC-V */
+#define NEXUS_TCODE_Ownership 2
+#define NEXUS_TCODE_DirectBranch 3
+#define NEXUS_TCODE_IndirectBranch 4
+#define NEXUS_TCODE_Error 8
+#define NEXUS_TCODE_ProgTraceSync 9
+#define NEXUS_TCODE_DirectBranchSync 11
+#define NEXUS_TCODE_IndirectBranchSync 12
+#define NEXUS_TCODE_ResourceFull 27
+#define NEXUS_TCODE_IndirectBranchHist 28
+#define NEXUS_TCODE_IndirectBranchHistSync 29
+#define NEXUS_TCODE_RepeatBranch 30
+#define NEXUS_TCODE_ProgTraceCorrelation 33
+
+/* End of standard values */
+/****************************************************************************/
+
+/****************************************************************************/
+/* RISC-V Nexus Trace related values (most 'recommended' by Nexus) */
+
+// Sizes of fields:
+#define NEXUS_FLDSIZE_BTYPE 2 // Branch type
+#define NEXUS_FLDSIZE_SYNC 4 // Synchronization code
+#define NEXUS_FLDSIZE_ETYPE 4 // Error type
+#define NEXUS_FLDSIZE_EVCODE 4 // Event code (correlation)
+#define NEXUS_FLDSIZE_CDF 2
+#define NEXUS_FLDSIZE_RCODE 4 // Resource full code size
+// from PROCESS fields:
+#define NEXUS_FLDSIZE_FORMAT 2
+#define NEXUS_FLDSIZE_PRV 2
+#define NEXUS_FLDSIZE_V 1
+
+#define NEXUS_PAR_SIZE_SRC 0 // SRC field size is defined by parameter #0
+
+#define NEXUS_HIST_BITS 31 // Number of valid HIST bits
+
+// Address skipping (on RISC-V LSB of PC is always 0, so it is not encoded)
+#define NEXUS_PARAM_AddrSkip 1
+#define NEXUS_PARAM_AddrUnit 1
+
+// End of RISC-V related values
+/****************************************************************************/
+
+/*
+ * Field definition flags packed into rvtrace_ntrace_msg_field_def.def. The
+ * low 8 bits of a fixed field carry its bit width; bit 7 marks a
+ * runtime-sized (parameter) field such as SRC.
+ */
+#define NEXM_DEF_MSG 0x100 /* message header: low bits are the TCODE */
+#define NEXM_DEF_FLD 0x200 /* fixed-size field */
+#define NEXM_DEF_VAR 0x400 /* variable-size field */
+#define NEXM_DEF_ADR 0x800 /* address field (variable + addr semantics) */
+#define NEXM_DEF_PARAM 0x80 /* field size is a runtime parameter */
+
+// Macros to define Nexus Messages (NEXM_...)
+// NOTE: These macros refer to 'NEXUS_TCODE_...' and 'NEXUS_FLDSIZE_...'
+// It is also possible to NOT do it, but it provides less flexibility.
+//
+// name def (marker | value)
+#define NEXM_BEG(n) {#n, NEXM_DEF_MSG | (NEXUS_TCODE_##n) }
+#define NEXM_FLD(n) {#n, NEXM_DEF_FLD | (NEXUS_FLDSIZE_##n) }
+#define NEXM_FLD_PAR(n) {#n, NEXM_DEF_FLD | NEXM_DEF_PARAM }
+#define NEXM_VAR(n) {#n, NEXM_DEF_VAR }
+#define NEXM_ADR(n) {#n, NEXM_DEF_VAR | NEXM_DEF_ADR }
+#define NEXM_END() {NULL, 1 }
+
+#define MSGFIELDS_MAX 10
+
+struct rvtrace_ntrace_msg_field_def {
+ const char *name; /* Name of message/field */
+ int def; /* Definition of field (see NEXM_... above) */
+};
+
+extern const struct rvtrace_ntrace_msg_field_def rvtrace_ntrace_msg_defs[];
+
+/* A single decoded N-Trace message */
+struct rvtrace_ntrace_msg {
+ u32 tcode;
+ u32 field_pos;
+ u32 nfields;
+ u32 addr_bits; /* transmitted bit width of the last F-ADDR/U-ADDR field */
+ u64 fields[MSGFIELDS_MAX];
+};
+
+/* Bit-level N-Trace message parser state */
+struct rvtrace_ntrace_msg_parser {
+ int field_def; /* index into rvtrace_ntrace_msg_defs of the current field */
+ int field_bits; /* bits accumulated for the current field */
+ u64 field_val; /* accumulated value of the current field */
+ bool resync; /* skip bytes until the next MSEO==3 terminator */
+};
+
+#endif
--
2.34.1
^ permalink raw reply [flat|nested] 25+ messages in thread* [PATCH 23/24] perf tools: gtrace: Record and parse trace encoder metadata
2026-10-23 3:14 [RFC PATCH 00/24] Add Linux RISC-V trace support via the gtrace framework Zane Leung
` (21 preceding siblings ...)
2026-10-23 3:15 ` [PATCH 22/24] perf tools: Add Nexus RISC-V Trace decoder Zane Leung
@ 2026-10-23 3:15 ` Zane Leung
2026-10-23 3:15 ` [PATCH 24/24] perf tools: gtrace: Add AUX trace decoder and branch sample synthesis Zane Leung
23 siblings, 0 replies; 25+ messages in thread
From: Zane Leung @ 2026-10-23 3:15 UTC (permalink / raw)
To: gregkh, palmer, pjw, alexander.shishkin, suzuki.poulose,
mchitale, anup, irogers
Cc: mike.leach, james.clark, leo.yan, peterz, mingo, namhyung,
mark.rutland, jolsa, adrian.hunter, alex, atish.patra,
andrew.jones, sunilvl, liangzhen, zhuangqiubin, coresight,
linux-riscv, linux-kernel
Collect the read-only trace encoder control, features and timestamp
registers per CPU into the AUXTRACE_INFO metadata and validate the
requested context, syncmode, syncmax and timestamp options against the
hardware before recording. Parse the metadata back in the decoder.
Signed-off-by: Zane Leung <liangzhen@linux.spacemit.com>
---
tools/perf/arch/riscv/util/auxtrace.c | 203 +++++++++++++++++++++++++-
tools/perf/util/gtrace-decoder.c | 89 ++++++++++-
tools/perf/util/gtrace.h | 30 +++-
3 files changed, 311 insertions(+), 11 deletions(-)
diff --git a/tools/perf/arch/riscv/util/auxtrace.c b/tools/perf/arch/riscv/util/auxtrace.c
index 882632511d09..c11c5c34cbc8 100644
--- a/tools/perf/arch/riscv/util/auxtrace.c
+++ b/tools/perf/arch/riscv/util/auxtrace.c
@@ -9,6 +9,7 @@
#include <linux/bitops.h>
#include <linux/log2.h>
#include <linux/zalloc.h>
+#include <linux/bitfield.h>
#include <errno.h>
#include <limits.h>
#include <stdlib.h>
@@ -39,28 +40,217 @@ struct rvtrace_recording {
struct evlist *evlist;
};
+static const char * const metadata_encoder_ro[] = {
+ [RVTRACE_ENCODER_FORMAT] = "control/format",
+ [RVTRACE_ENCODER_CONTEXT] = "control/has_context",
+ [RVTRACE_ENCODER_SRCBITS] = "features/srcbits",
+ [RVTRACE_ENCODER_TIMESTAMP] = "timestamp/source",
+};
+
static size_t rvtrace_info_priv_size(struct auxtrace_record *itr __maybe_unused,
struct evlist *evlist __maybe_unused)
{
- return GTRACE_AUXTRACE_PRIV_SIZE;
+ int encoder;
+ struct perf_cpu_map *event_cpus = evlist->core.user_requested_cpus;
+ struct perf_cpu_map *intersect_cpus;
+
+ if (!perf_cpu_map__has_any_cpu(event_cpus)) {
+ /* cpu map is not "any" CPU , we have specific CPUs to work with */
+ struct perf_cpu_map *online_cpus = perf_cpu_map__new_online_cpus();
+
+ intersect_cpus = perf_cpu_map__intersect(event_cpus, online_cpus);
+ perf_cpu_map__put(online_cpus);
+ } else {
+ /* Event can be "any" CPU so count all online CPUs. */
+ intersect_cpus = perf_cpu_map__new_online_cpus();
+ }
+
+ encoder = perf_cpu_map__nr(intersect_cpus);
+ perf_cpu_map__put(intersect_cpus);
+
+ return (GTRACE_HEADER_SIZE + encoder * RVTRACE_ENCODER_PRIV_SIZE);
+
+}
+
+static bool gtrace_pmu_path_exists(struct perf_pmu *pmu, struct perf_cpu cpu, const char *path)
+{
+ char pmu_path[PATH_MAX];
+
+ /* Get RO metadata from sysfs */
+ snprintf(pmu_path, PATH_MAX, "cpu%d/%s", cpu.cpu, path);
+
+ return perf_pmu__file_exists(pmu, pmu_path);
+}
+
+static int rvtrace_get_ro(struct perf_pmu *pmu, struct perf_cpu cpu, const char *path, __u64 *val)
+{
+ char pmu_path[PATH_MAX];
+ int scan;
+
+ /* Get RO metadata from sysfs */
+ snprintf(pmu_path, PATH_MAX, "cpu%d/%s", cpu.cpu, path);
+
+ scan = perf_pmu__scan_file(pmu, pmu_path, "%llx", val);
+ if (scan != 1) {
+ pr_err("%s: error reading: %s\n", __func__, pmu_path);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static void rvtrace_get_metadata(struct perf_cpu cpu, u32 *offset,
+ struct auxtrace_record *itr,
+ struct perf_record_auxtrace_info *info)
+{
+ struct rvtrace_recording *ptr = container_of(itr, struct rvtrace_recording, itr);
+ struct perf_pmu *rvtrace_pmu = ptr->rvtrace_pmu;
+ bool timestamp_exists;
+
+ info->priv[*offset + RVTRACE_ENCODER_CPU] = cpu.cpu;
+ info->priv[*offset + RVTRACE_ENCODER_NR_TRC_PARAMS] = RVTRACE_ENCODER_NR_TRC_PARAMS_LENGTH;
+
+ /* Get read-only information from sysFS */
+ rvtrace_get_ro(rvtrace_pmu, cpu, metadata_encoder_ro[RVTRACE_ENCODER_FORMAT],
+ &info->priv[*offset + RVTRACE_ENCODER_FORMAT]);
+ rvtrace_get_ro(rvtrace_pmu, cpu, metadata_encoder_ro[RVTRACE_ENCODER_CONTEXT],
+ &info->priv[*offset + RVTRACE_ENCODER_CONTEXT]);
+ rvtrace_get_ro(rvtrace_pmu, cpu, metadata_encoder_ro[RVTRACE_ENCODER_SRCBITS],
+ &info->priv[*offset + RVTRACE_ENCODER_SRCBITS]);
+
+ timestamp_exists = gtrace_pmu_path_exists(rvtrace_pmu, cpu,
+ metadata_encoder_ro[RVTRACE_ENCODER_TIMESTAMP]);
+ info->priv[*offset + RVTRACE_ENCODER_TIMESTAMP] = timestamp_exists ? 1 : 0;
+
+ /* Where the next CPU entry should start from */
+ *offset += RVTRACE_ENCODER_PRIV_MAX;
}
static int rvtrace_info_fill(struct auxtrace_record *itr,
struct perf_session *session __maybe_unused,
struct perf_record_auxtrace_info *auxtrace_info, size_t priv_size)
{
+ unsigned int i;
+ u32 offset;
+ u64 nr_cpu, type;
+ struct perf_cpu_map *cpu_map;
+ struct perf_cpu_map *event_cpus = evlist__core(session->evlist)->user_requested_cpus;
+ struct perf_cpu_map *online_cpus = perf_cpu_map__new_online_cpus();
struct rvtrace_recording *ptr = container_of(itr, struct rvtrace_recording, itr);
struct perf_pmu *rvtrace_pmu = ptr->rvtrace_pmu;
+ struct perf_cpu cpu;
+
+ if (priv_size != rvtrace_info_priv_size(itr, session->evlist))
+ return -EINVAL;
- if (priv_size != GTRACE_AUXTRACE_PRIV_SIZE)
+ if (!evlist__core(session->evlist)->nr_mmaps)
return -EINVAL;
+ if (perf_cpu_map__has_any_cpu(event_cpus)) {
+ cpu_map = online_cpus;
+ } else {
+ perf_cpu_map__for_each_cpu(cpu, i, event_cpus) {
+ if (!perf_cpu_map__has(online_cpus, cpu))
+ return -EINVAL;
+ }
+
+ cpu_map = event_cpus;
+ }
+
+ nr_cpu = perf_cpu_map__nr(cpu_map);
+ type = rvtrace_pmu->type;
+
auxtrace_info->type = PERF_AUXTRACE_GTRACE;
- auxtrace_info->priv[0] = rvtrace_pmu->type;
+ auxtrace_info->priv[GTRACE_PMU_TYPE_CPUS] = type << 32;
+ auxtrace_info->priv[GTRACE_PMU_TYPE_CPUS] |= nr_cpu;
+
+ offset = GTRACE_HEADER_MAX;
+
+ perf_cpu_map__for_each_cpu(cpu, i, cpu_map) {
+ assert(offset < priv_size);
+ rvtrace_get_metadata(cpu, &offset, itr, auxtrace_info);
+ }
+
+ perf_cpu_map__put(online_cpus);
+
+ return 0;
+}
+
+static int rvtrace_encoder_validate_context(struct perf_pmu *rvtrace_pmu, struct evsel *evsel,
+ struct perf_cpu cpu)
+{
+ int err;
+ u64 ctxt;
+ __u64 context;
+
+ evsel__get_config_val(evsel, "context", &ctxt);
+
+ if (!ctxt)
+ return 0;
+
+ err = rvtrace_get_ro(rvtrace_pmu, cpu,
+ metadata_encoder_ro[RVTRACE_ENCODER_CONTEXT], &context);
+ if (err)
+ return err;
+
+ if (!context) {
+ pr_err("context not supported");
+ return -EINVAL;
+ }
return 0;
}
+static int rvtrace_encoder_validate_timestamp(struct perf_pmu *rvtrace_pmu, struct evsel *evsel,
+ struct perf_cpu cpu)
+{
+ u64 ts;
+
+ evsel__get_config_val(evsel, "timestamp", &ts);
+
+ if (!ts)
+ return 0;
+
+ if (!gtrace_pmu_path_exists(rvtrace_pmu, cpu,
+ metadata_encoder_ro[RVTRACE_ENCODER_TIMESTAMP])) {
+ pr_err("timestamp not supported");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int rvtrace_validate_config(struct perf_pmu *rvtrace_pmu, struct evsel *evsel)
+{
+ unsigned int idx;
+ int err = 0;
+ struct perf_cpu_map *event_cpus = evlist__core(evsel->evlist)->user_requested_cpus;
+ struct perf_cpu_map *intersect_cpus;
+ struct perf_cpu cpu;
+
+ if (!perf_cpu_map__has_any_cpu(event_cpus)) {
+ struct perf_cpu_map *online_cpus = perf_cpu_map__new_online_cpus();
+
+ intersect_cpus = perf_cpu_map__intersect(event_cpus, online_cpus);
+ perf_cpu_map__put(online_cpus);
+ } else {
+ intersect_cpus = perf_cpu_map__new_online_cpus();
+ }
+
+ perf_cpu_map__for_each_cpu_skip_any(cpu, idx, intersect_cpus) {
+ err = rvtrace_encoder_validate_context(rvtrace_pmu, evsel, cpu);
+ if (err)
+ break;
+
+ err = rvtrace_encoder_validate_timestamp(rvtrace_pmu, evsel, cpu);
+ if (err)
+ break;
+ }
+
+ perf_cpu_map__put(intersect_cpus);
+ return err;
+}
+
static int rvtrace_set_auxtrace_mmap_page(struct record_opts *opts)
{
bool privileged = perf_event_paranoid_check(-1);
@@ -127,6 +317,9 @@ static int rvtrace_recording_options(struct auxtrace_record *itr, struct evlist
evlist__to_front(evlist, rvtrace_evsel);
evsel__set_sample_bit(rvtrace_evsel, TIME);
+ if (opts->sample_time_set)
+ evsel__set_config_if_unset(rvtrace_evsel, "timestamp", 1);
+
/* Add dummy event to keep tracking */
err = parse_event(evlist, "dummy:u");
if (err)
@@ -139,6 +332,10 @@ static int rvtrace_recording_options(struct auxtrace_record *itr, struct evlist
tracking_evsel->core.attr.sample_period = 1;
evsel__set_sample_bit(tracking_evsel, TIME);
+ err = rvtrace_validate_config(rvtrace_pmu, rvtrace_evsel);
+ if (err)
+ return err;
+
return 0;
}
diff --git a/tools/perf/util/gtrace-decoder.c b/tools/perf/util/gtrace-decoder.c
index c25fbfc61928..d70cb7cd399f 100644
--- a/tools/perf/util/gtrace-decoder.c
+++ b/tools/perf/util/gtrace-decoder.c
@@ -12,6 +12,7 @@
#include "evlist.h"
#include "session.h"
#include "gtrace.h"
+#include "debug.h"
struct gtrace_decoder {
struct auxtrace auxtrace;
@@ -19,6 +20,8 @@ struct gtrace_decoder {
struct perf_session *session;
struct machine *machine;
u32 pmu_type;
+ int num_cpu;
+ u64 **metadata;
};
static int gtrace_process_event(struct perf_session *session __maybe_unused,
@@ -52,7 +55,10 @@ static void gtrace_free(struct perf_session *session)
auxtrace);
session->auxtrace = NULL;
- free(ptr);
+ for (int i = 0; i < ptr->num_cpu; i++)
+ zfree(&ptr->metadata[i]);
+ zfree(&ptr->metadata);
+ zfree(&ptr);
}
static bool gtrace_evsel_is_auxtrace(struct perf_session *session,
@@ -64,24 +70,88 @@ static bool gtrace_evsel_is_auxtrace(struct perf_session *session,
return evsel->core.attr.type == ptr->pmu_type;
}
+static const char * const gtrace_global_header_fmts[] = {
+ [GTRACE_PMU_TYPE_CPUS] = " PMU type/num cpus %llx\n",
+};
+
+static const char * const rvtrace_encoder_priv_fmts[] = {
+ [RVTRACE_ENCODER_CPU] = " CPU %lld\n",
+ [RVTRACE_ENCODER_NR_TRC_PARAMS] = " NR_TRC_PARAMS %lld\n",
+ [RVTRACE_ENCODER_FORMAT] = " FORMAT %lld\n",
+ [RVTRACE_ENCODER_CONTEXT] = " CONTEXT %lld\n",
+ [RVTRACE_ENCODER_SRCBITS] = " SRCBITS %lld\n",
+ [RVTRACE_ENCODER_TIMESTAMP] = " TIMESTAMP %lld\n",
+};
+
+static void gtrace_print_auxtrace_info(u64 *val, int num_cpu)
+{
+ int i, j, cpu = 0, nr_params = 0, fmt_offset = 0;
+
+ for (i = 0; i < GTRACE_HEADER_MAX; i++)
+ fprintf(stdout, gtrace_global_header_fmts[i], val[i]);
+
+ for (i = GTRACE_HEADER_MAX; cpu < num_cpu; cpu++) {
+ fprintf(stdout, rvtrace_encoder_priv_fmts[RVTRACE_ENCODER_CPU], val[i++]);
+ nr_params = val[i++];
+ fmt_offset = RVTRACE_ENCODER_FORMAT;
+ for (j = fmt_offset; j < nr_params + fmt_offset; j++, i++)
+ fprintf(stdout, rvtrace_encoder_priv_fmts[j], val[i]);
+ }
+}
+
int gtrace__process_auxtrace_info(union perf_event *event,
struct perf_session *session)
{
struct perf_record_auxtrace_info *auxtrace_info = &event->auxtrace_info;
struct gtrace_decoder *ptr;
-
- if (auxtrace_info->header.size < GTRACE_AUXTRACE_PRIV_SIZE +
- sizeof(struct perf_record_auxtrace_info))
+ u64 *priv;
+ u64 **metadata;
+ int num_cpu;
+ int priv_size;
+ int err;
+ int i;
+
+ priv = (u64 *) auxtrace_info->priv;
+ num_cpu = priv[GTRACE_PMU_TYPE_CPUS] & 0xffffffff;
+ if (dump_trace)
+ gtrace_print_auxtrace_info(priv, num_cpu);
+
+ priv_size = auxtrace_info->header.size - sizeof(struct perf_event_header) -
+ INFO_HEADER_SIZE - GTRACE_HEADER_SIZE;
+ if (num_cpu <= 0 || priv_size <= 0 ||
+ num_cpu > priv_size / (int)sizeof(u64))
return -EINVAL;
- ptr = zalloc(sizeof(*ptr));
- if (!ptr)
+ metadata = zalloc(sizeof(*metadata) * num_cpu);
+ if (!metadata)
return -ENOMEM;
+ i = GTRACE_HEADER_SIZE;
+
+ for (int j = 0; j < num_cpu; j++) {
+ metadata[j] = zalloc(sizeof(*metadata[j]) * RVTRACE_ENCODER_PRIV_MAX);
+ if (!metadata[j]) {
+ err = -ENOMEM;
+ goto err_free_metadata;
+ }
+
+ for (int k = 0; k < RVTRACE_ENCODER_PRIV_MAX; k++)
+ metadata[j][k] = priv[i + k];
+ i += RVTRACE_ENCODER_PRIV_MAX;
+ }
+
+ ptr = zalloc(sizeof(*ptr));
+ if (!ptr) {
+ err = -ENOMEM;
+ goto err_free_metadata;
+ }
+
ptr->session = session;
ptr->machine = &session->machines.host;
ptr->auxtrace_type = auxtrace_info->type;
- ptr->pmu_type = auxtrace_info->priv[0];
+ ptr->pmu_type = (unsigned int) ((priv[GTRACE_PMU_TYPE_CPUS] >> 32) & 0xffffffff);
+ ptr->num_cpu = num_cpu;
+ ptr->metadata = metadata;
ptr->auxtrace.process_event = gtrace_process_event;
ptr->auxtrace.process_auxtrace_event = gtrace_process_auxtrace_event;
@@ -92,4 +162,9 @@ int gtrace__process_auxtrace_info(union perf_event *event,
session->auxtrace = &ptr->auxtrace;
return 0;
+
+err_free_metadata:
+ for (int j = 0; j < num_cpu; j++)
+ zfree(&metadata);
+ return err;
}
diff --git a/tools/perf/util/gtrace.h b/tools/perf/util/gtrace.h
index 02db4f2ff9f5..76006e30e7c3 100644
--- a/tools/perf/util/gtrace.h
+++ b/tools/perf/util/gtrace.h
@@ -11,7 +11,35 @@
struct perf_session;
-#define GTRACE_AUXTRACE_PRIV_SIZE sizeof(u64)
+/* Supported trace protocol formats */
+enum gtrace_format {
+ GTRACE_FORMAT_ETRACE,
+ GTRACE_FORMAT_NTRACE,
+};
+
+enum {
+ GTRACE_PMU_TYPE_CPUS,
+ GTRACE_HEADER_MAX,
+};
+
+/* RISC-V Trace Encoder metadata */
+enum {
+ RVTRACE_ENCODER_CPU,
+ RVTRACE_ENCODER_NR_TRC_PARAMS,
+ RVTRACE_ENCODER_FORMAT,
+ RVTRACE_ENCODER_CONTEXT,
+ RVTRACE_ENCODER_SRCBITS,
+ RVTRACE_ENCODER_TIMESTAMP,
+ RVTRACE_ENCODER_PRIV_MAX,
+};
+
+#define RVTRACE_ENCODER_NR_TRC_PARAMS_LENGTH (RVTRACE_ENCODER_PRIV_MAX - RVTRACE_ENCODER_FORMAT)
+
+#define GTRACE_HEADER_SIZE (GTRACE_HEADER_MAX * sizeof(u64))
+#define RVTRACE_ENCODER_PRIV_SIZE (RVTRACE_ENCODER_PRIV_MAX * sizeof(u64))
+
+#define INFO_HEADER_SIZE (sizeof(((struct perf_record_auxtrace_info *)0)->type) + \
+ sizeof(((struct perf_record_auxtrace_info *)0)->reserved__))
int gtrace__process_auxtrace_info(union perf_event *event, struct perf_session *session);
#endif
--
2.34.1
^ permalink raw reply [flat|nested] 25+ messages in thread* [PATCH 24/24] perf tools: gtrace: Add AUX trace decoder and branch sample synthesis
2026-10-23 3:14 [RFC PATCH 00/24] Add Linux RISC-V trace support via the gtrace framework Zane Leung
` (22 preceding siblings ...)
2026-10-23 3:15 ` [PATCH 23/24] perf tools: gtrace: Record and parse trace encoder metadata Zane Leung
@ 2026-10-23 3:15 ` Zane Leung
23 siblings, 0 replies; 25+ messages in thread
From: Zane Leung @ 2026-10-23 3:15 UTC (permalink / raw)
To: gregkh, palmer, pjw, alexander.shishkin, suzuki.poulose,
mchitale, anup, irogers
Cc: mike.leach, james.clark, leo.yan, peterz, mingo, namhyung,
mark.rutland, jolsa, adrian.hunter, alex, atish.patra,
andrew.jones, sunilvl, liangzhen, zhuangqiubin, coresight,
linux-riscv, linux-kernel
Implement the gtrace auxtrace decoder: queue AUX buffers, run timeless or
timestamped decoding, and synthesize branch samples from decoded trace
packets. Support machine-mode instruction access via new '-m/--firmware'
and '--firmware-base' options.
---
tools/perf/builtin-script.c | 4 +
tools/perf/util/gtrace-decoder.c | 1206 +++++++++++++++++++++++++++++-
tools/perf/util/symbol_conf.h | 2 +
3 files changed, 1197 insertions(+), 15 deletions(-)
diff --git a/tools/perf/builtin-script.c b/tools/perf/builtin-script.c
index ad8ca08ceb5f..d1173c80b6fa 100644
--- a/tools/perf/builtin-script.c
+++ b/tools/perf/builtin-script.c
@@ -4088,6 +4088,10 @@ int cmd_script(int argc, const char **argv)
"file", "vmlinux pathname"),
OPT_STRING(0, "kallsyms", &symbol_conf.kallsyms_name,
"file", "kallsyms pathname"),
+ OPT_STRING('m', "firmware", &symbol_conf.firmware_name,
+ "file", "machine mode firmware binary pathname"),
+ OPT_STRING(0, "firmware-base", &symbol_conf.firmware_base,
+ "address", "base address for machine mode firmware (e.g., 0x100000000)"),
OPT_BOOLEAN('G', "hide-call-graph", &no_callchain,
"When printing symbols do not display call chain"),
OPT_CALLBACK(0, "symfs", NULL, "directory[,layout]", SYMFS_HELP,
diff --git a/tools/perf/util/gtrace-decoder.c b/tools/perf/util/gtrace-decoder.c
index d70cb7cd399f..b2973f7ad3fe 100644
--- a/tools/perf/util/gtrace-decoder.c
+++ b/tools/perf/util/gtrace-decoder.c
@@ -13,6 +13,24 @@
#include "session.h"
#include "gtrace.h"
#include "debug.h"
+#include "color.h"
+#include <linux/bitfield.h>
+#include "intlist.h"
+#include "machine.h"
+#include "map.h"
+#include "thread.h"
+#include "thread_map.h"
+#include "thread-stack.h"
+#include "util.h"
+#include "dso.h"
+#include "maps.h"
+#include "symbol.h"
+#include "addr_location.h"
+#include "util/synthetic-events.h"
+#include <fcntl.h>
+#include <unistd.h>
+#include "rvtrace-decoder/rvtrace-decoder.h"
+#include "../../arch/riscv/include/asm/insn.h"
struct gtrace_decoder {
struct auxtrace auxtrace;
@@ -22,31 +40,242 @@ struct gtrace_decoder {
u32 pmu_type;
int num_cpu;
u64 **metadata;
+ struct auxtrace_queues queues;
+ struct auxtrace_heap heap;
+ struct itrace_synth_opts synth_opts;
+ struct dso *firmware_dso;
+ struct maps *firmware_maps;
+ bool timeless_decoding;
+ bool per_thread_decoding;
+ bool data_queued;
+ u64 latest_kernel_timestamp;
+ u64 branches_sample_type;
+ u64 branches_id;
};
-static int gtrace_process_event(struct perf_session *session __maybe_unused,
- union perf_event *event __maybe_unused,
- struct perf_sample *sample __maybe_unused,
- const struct perf_tool *tool __maybe_unused)
+enum coresight_format {
+ UNSET,
+ FORMATTED,
+ UNFORMATTED
+};
+
+struct gtrace_queue {
+ struct gtrace_decoder *ptr;
+ struct thread *thread;
+ struct rvtrace_decoder *decoder;
+ struct auxtrace_buffer *buffer;
+ union perf_event *event_buf;
+ unsigned int queue_nr;
+ u64 offset;
+ enum coresight_format cs_format;
+};
+
+static int gtrace_process_timeless_queues(struct gtrace_decoder *ptr,
+ pid_t tid);
+static int gtrace_process_timestamped_queues(struct gtrace_decoder *ptr);
+
+static int gtrace_process_itrace_start(struct gtrace_decoder *ptr,
+ union perf_event *event)
{
+ struct thread *th;
+
+ if (ptr->timeless_decoding)
+ return 0;
+
+ /*
+ * Add the tid/pid to the log so that we can get a match when
+ * we get a contextID from the decoder.
+ */
+ th = machine__findnew_thread(ptr->machine,
+ event->itrace_start.pid,
+ event->itrace_start.tid);
+ if (!th)
+ return -ENOMEM;
+
+ thread__put(th);
+
return 0;
}
-static int gtrace_process_auxtrace_event(struct perf_session *session __maybe_unused,
- union perf_event *event __maybe_unused,
- const struct perf_tool *tool __maybe_unused)
+static int gtrace_process_switch_cpu_wide(struct gtrace_decoder *ptr,
+ union perf_event *event)
{
+ struct thread *th;
+ bool out = event->header.misc & PERF_RECORD_MISC_SWITCH_OUT;
+
+ if (ptr->timeless_decoding)
+ return 0;
+
+ if (!out)
+ return 0;
+
+ th = machine__findnew_thread(&ptr->session->machines.host,
+ event->context_switch.next_prev_pid,
+ event->context_switch.next_prev_tid);
+
+ if (!th)
+ return -ENOMEM;
+
+ thread__put(th);
+
return 0;
}
-static int gtrace_flush(struct perf_session *session __maybe_unused,
- const struct perf_tool *tool __maybe_unused)
+static int gtrace_process_event(struct perf_session *session,
+ union perf_event *event,
+ struct perf_sample *sample,
+ const struct perf_tool *tool)
{
+ struct gtrace_decoder *ptr = container_of(session->auxtrace, struct gtrace_decoder,
+ auxtrace);
+ if (dump_trace)
+ return 0;
+
+ if (!tool->ordered_events) {
+ pr_err("GTRACE requires ordered events\n");
+ return -EINVAL;
+ }
+
+ switch (event->header.type) {
+ case PERF_RECORD_EXIT:
+ if (ptr->per_thread_decoding && ptr->timeless_decoding)
+ return gtrace_process_timeless_queues(ptr, event->fork.tid);
+ break;
+
+ case PERF_RECORD_ITRACE_START:
+ return gtrace_process_itrace_start(ptr, event);
+
+ case PERF_RECORD_SWITCH_CPU_WIDE:
+ return gtrace_process_switch_cpu_wide(ptr, event);
+
+ case PERF_RECORD_AUX:
+ if (sample->time && (sample->time != (u64)-1))
+ ptr->latest_kernel_timestamp = sample->time;
+ break;
+
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static void gtrace_dump_event(struct gtrace_queue *gtraceq,
+ struct auxtrace_buffer *buffer)
+{
+ int ret;
+ const char *color = PERF_COLOR_BLUE;
+ struct rvtrace_decoder *decoder = gtraceq->decoder;
+
+ color_fprintf(stdout, color,
+ ". ... Trace Encoder Trace data: size %#zx bytes\n",
+ buffer->size);
+
+ ret = decoder->decode(gtraceq->decoder, RVTRACE_DECODE_MSG,
+ buffer->data, buffer->size);
+ if (ret)
+ color_fprintf(stdout, color, " Bad packet!\n");
+}
+
+static void dump_queued_data(struct gtrace_decoder *ptr,
+ struct perf_record_auxtrace *event)
+{
+ struct auxtrace_buffer *buf;
+ unsigned int i;
+ /*
+ * Find all buffers with same reference in the queues and dump them.
+ * This is because the queues can contain multiple entries of the same
+ * buffer that were split on aux records.
+ */
+ for (i = 0; i < ptr->queues.nr_queues; ++i)
+ list_for_each_entry(buf, &ptr->queues.queue_array[i].head, list)
+ if (buf->reference == event->reference)
+ gtrace_dump_event(ptr->queues.queue_array[i].priv, buf);
+}
+
+static int gtrace_process_auxtrace_event(struct perf_session *session,
+ union perf_event *event,
+ const struct perf_tool *tool __maybe_unused)
+{
+ struct gtrace_decoder *ptr = container_of(session->auxtrace, struct gtrace_decoder,
+ auxtrace);
+
+ if (!ptr->data_queued) {
+ struct auxtrace_buffer *buffer;
+ off_t data_offset;
+ int fd = perf_data__fd(session->data);
+ bool is_pipe = perf_data__is_pipe(session->data);
+ int err;
+ int idx = event->auxtrace.idx;
+
+ if (is_pipe)
+ data_offset = 0;
+ else {
+ data_offset = lseek(fd, 0, SEEK_CUR);
+ if (data_offset == -1)
+ return -errno;
+ }
+
+ err = auxtrace_queues__add_event(&ptr->queues, session,
+ event, data_offset, &buffer);
+ if (err)
+ return err;
+
+ if (dump_trace)
+ if (auxtrace_buffer__get_data(buffer, fd)) {
+ gtrace_dump_event(ptr->queues.queue_array[idx].priv, buffer);
+ auxtrace_buffer__put_data(buffer);
+ }
+ } else if (dump_trace)
+ dump_queued_data(ptr, &event->auxtrace);
+
return 0;
}
-static void gtrace_free_events(struct perf_session *session __maybe_unused)
+static int gtrace_flush(struct perf_session *session,
+ const struct perf_tool *tool)
{
+ struct gtrace_decoder *ptr = container_of(session->auxtrace, struct gtrace_decoder,
+ auxtrace);
+ if (dump_trace)
+ return 0;
+
+ if (!tool->ordered_events)
+ return -EINVAL;
+
+ if (ptr->timeless_decoding)
+ return gtrace_process_timeless_queues(ptr, -1);
+
+ return gtrace_process_timestamped_queues(ptr);
+}
+
+static void gtrace_free_queue(void *priv)
+{
+ struct gtrace_queue *gtraceq = priv;
+
+ if (!gtraceq)
+ return;
+
+ if (gtraceq->decoder)
+ rvtrace_decoder_free(gtraceq->decoder);
+ thread__zput(gtraceq->thread);
+ zfree(>raceq->event_buf);
+ free(gtraceq);
+}
+
+static void gtrace_free_events(struct perf_session *session)
+{
+ unsigned int i;
+ struct gtrace_decoder *ptr = container_of(session->auxtrace, struct gtrace_decoder,
+ auxtrace);
+ struct auxtrace_queues *queues = &ptr->queues;
+
+ for (i = 0; i < queues->nr_queues; i++) {
+ gtrace_free_queue(queues->queue_array[i].priv);
+ queues->queue_array[i].priv = NULL;
+ }
+
+ auxtrace_queues__free(queues);
}
static void gtrace_free(struct perf_session *session)
@@ -54,6 +283,7 @@ static void gtrace_free(struct perf_session *session)
struct gtrace_decoder *ptr = container_of(session->auxtrace, struct gtrace_decoder,
auxtrace);
+ gtrace_free_events(session);
session->auxtrace = NULL;
for (int i = 0; i < ptr->num_cpu; i++)
zfree(&ptr->metadata[i]);
@@ -70,6 +300,643 @@ static bool gtrace_evsel_is_auxtrace(struct perf_session *session,
return evsel->core.attr.type == ptr->pmu_type;
}
+static void gtrace_set_thread(struct gtrace_queue *gtraceq,
+ pid_t tid)
+{
+ struct gtrace_decoder *ptr = gtraceq->ptr;
+
+ if (tid != -1) {
+ thread__zput(gtraceq->thread);
+ gtraceq->thread = machine__find_thread(ptr->machine, -1, tid);
+ }
+
+ /* Couldn't find a known thread */
+ if (!gtraceq->thread)
+ gtraceq->thread = machine__idle_thread(ptr->machine);
+}
+
+/*
+ * Register firmware ELF PT_LOAD segments as maps in the machine's kernel maps,
+ * so that perf script can resolve symbols for M-mode (machine mode) samples.
+ *
+ * base is the absolute physical load address of the firmware (from --firmware-base).
+ * p_paddr in the ELF is the relative address within the firmware image.
+ *
+ * map->start = base + p_paddr
+ * map->end = base + p_paddr + p_memsz
+ * map->pgoff = p_offset
+ *
+ * map__map_ip(paddr) = paddr - start + pgoff = p_offset + (paddr - base - p_paddr)
+ */
+static int gtrace_register_firmware_maps(struct gtrace_decoder *ptr,
+ struct dso *dso, u64 base)
+{
+ Elf64_Ehdr ehdr;
+ Elf64_Phdr phdr;
+ int fd, i, ret = 0;
+ const char *filename = dso__long_name(dso);
+
+ fd = open(filename, O_RDONLY);
+ if (fd < 0)
+ return -1;
+
+ if (pread(fd, &ehdr, sizeof(ehdr), 0) != sizeof(ehdr)) {
+ ret = -1;
+ goto out;
+ }
+
+ for (i = 0; i < ehdr.e_phnum; i++) {
+ struct map *map;
+
+ if (pread(fd, &phdr, sizeof(phdr),
+ ehdr.e_phoff + (u64)i * ehdr.e_phentsize) != sizeof(phdr))
+ continue;
+
+ if (phdr.p_type != PT_LOAD || phdr.p_memsz == 0)
+ continue;
+
+ map = map__new2(base + phdr.p_paddr, dso);
+ if (!map) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ map__set_end(map, base + phdr.p_paddr + phdr.p_memsz);
+ map__set_pgoff(map, phdr.p_offset);
+
+ pr_debug("gtrace: machine code map [%#"PRIx64"-%#"PRIx64"] pgoff=%#"PRIx64" %s\n",
+ base + phdr.p_paddr, base + phdr.p_paddr + phdr.p_memsz,
+ (u64)phdr.p_offset, dso__long_name(dso));
+
+ ret = maps__insert(ptr->firmware_maps, map);
+ map__put(map);
+ if (ret)
+ goto out;
+ }
+out:
+ close(fd);
+ return ret;
+}
+
+static int gtrace_insert_map_cb(struct map *map, void *data)
+{
+ struct maps *kmaps = data;
+
+ maps__insert(kmaps, map);
+ return 0;
+}
+
+static u32 gtrace_machine_mode_access(struct gtrace_queue *gtraceq, u64 address,
+ size_t size, u8 *buffer)
+{
+ struct machine *machine = gtraceq->ptr->machine;
+ struct map *map;
+ struct dso *dso;
+ u64 offset;
+ int len;
+
+ if (!gtraceq->ptr->firmware_dso) {
+ ui__warning_once("RISC-V Trace: Machine mode code binary not provided.\n"
+ " Failed to read address 0x%lx.\n"
+ " Use option '-m /path/to/firmware --firmware-base 0xBASE'\n"
+ " to specify the binary and its base address.\n",
+ address);
+ return 0;
+ }
+
+ map = maps__find(gtraceq->ptr->firmware_maps, address);
+ if (!map)
+ return 0;
+
+ /*
+ * Insert firmware maps into kernel maps for symbol resolution.
+ * Re-insert if kcore loading has removed them (kcore calls
+ * maps__remove_maps which clears all non-kernel maps).
+ */
+ if (!maps__find(machine__kernel_maps(machine), address)) {
+ maps__for_each_map(gtraceq->ptr->firmware_maps,
+ gtrace_insert_map_cb,
+ machine__kernel_maps(machine));
+ map__load(map);
+ }
+
+ dso = map__dso(map);
+ offset = map__map_ip(map, address);
+ len = dso__data_read_offset(dso, machine, offset, buffer, size);
+ map__put(map);
+ return len > 0 ? len : 0;
+}
+
+static u32 gtrace_mem_access(void *data, u64 address, enum riscv_privilege_mode prv,
+ int context, size_t size, u8 *buffer)
+{
+ u8 cpumode;
+ u64 offset;
+ int len;
+ struct machine *machine;
+ struct addr_location al;
+ struct dso *dso;
+ int ret = 0;
+ struct gtrace_queue *gtraceq = data;
+
+ if (!gtraceq)
+ goto out;
+
+ addr_location__init(&al);
+
+ /* If the riscv_privilege_mode is machine mode, access physical address by /dev/mem */
+ if (prv == RISCV_PRIV_MACHINE_MODE)
+ return gtrace_machine_mode_access(gtraceq, address, size, buffer);
+
+ machine = gtraceq->ptr->machine;
+ if (address >= machine__kernel_start(machine))
+ cpumode = PERF_RECORD_MISC_KERNEL;
+ else
+ cpumode = PERF_RECORD_MISC_USER;
+
+ if (thread__tid(gtraceq->thread) != context)
+ gtrace_set_thread(gtraceq, context);
+
+ if (!thread__find_map(gtraceq->thread, cpumode, address, &al))
+ goto out;
+
+ dso = map__dso(al.map);
+ if (!dso)
+ goto out;
+
+ if (dso__data(dso)->status == DSO_DATA_STATUS_ERROR &&
+ dso__data_status_seen(dso, DSO_DATA_STATUS_SEEN_ITRACE))
+ goto out;
+
+ offset = map__map_ip(al.map, address);
+
+ map__load(al.map);
+
+ len = dso__data_read_offset(dso, machine, offset, buffer, size);
+ if (len <= 0) {
+ ui__warning_once("GTRACE: Missing DSO. Use 'perf archive' or debuginfod to export data from the traced system.\n"
+ " Enable CONFIG_PROC_KCORE or use option '-k /path/to/vmlinux' for kernel symbols.\n");
+ if (!dso__auxtrace_warned(dso)) {
+ pr_err("RISC-V Trace: Debug data not found for address %#"PRIx64" in %s\n",
+ address,
+ dso__long_name(dso) ? dso__long_name(dso) : "Unknown");
+ dso__set_auxtrace_warned(dso);
+ }
+ goto out;
+ }
+ ret = len;
+out:
+ addr_location__exit(&al);
+ return ret;
+}
+
+static u8 gtrace_cpu_mode(enum riscv_privilege_mode prv)
+{
+ u8 cpumode;
+
+ switch (prv) {
+ case RISCV_PRIV_USER_MODE:
+ cpumode = PERF_RECORD_MISC_USER;
+ break;
+ case RISCV_PRIV_SUPERVISOR_MODE:
+ case RISCV_PRIV_MACHINE_MODE:
+ default:
+ cpumode = PERF_RECORD_MISC_KERNEL;
+ break;
+ }
+ return cpumode;
+}
+
+static void gtrace_synth_copy_insn(struct gtrace_queue *gtraceq,
+ struct rvtrace_packet *packet,
+ struct perf_sample *sample)
+{
+ int ret;
+ u32 insn;
+
+ ret = gtrace_mem_access(gtraceq, sample->ip, packet->prv, packet->context,
+ sizeof(insn), (u8 *)&insn);
+ if (!ret)
+ return;
+
+ sample->insn_len = ((insn & __INSN_LENGTH_MASK) == __INSN_LENGTH_GE_32) ? 4 : 2;
+ memcpy(sample->insn, &insn, sample->insn_len);
+}
+
+static inline u64 gtrace_resolve_sample_time(struct gtrace_queue *gtraceq,
+ struct rvtrace_packet *packet)
+{
+ struct gtrace_decoder *ptr = gtraceq->ptr;
+
+ if (!ptr->timeless_decoding)
+ return packet->timestamp;
+ else
+ return ptr->latest_kernel_timestamp;
+}
+
+static int gtrace_synth_branch_sample(struct gtrace_queue *gtraceq,
+ struct rvtrace_packet *packet)
+{
+ int ret = 0;
+ struct gtrace_decoder *ptr = gtraceq->ptr;
+ struct perf_sample sample;
+ union perf_event *event = gtraceq->event_buf;
+
+ perf_sample__init(&sample, /*all=*/true);
+ event->sample.header.type = PERF_RECORD_SAMPLE;
+ event->sample.header.misc = gtrace_cpu_mode(packet->prv);
+ event->sample.header.size = sizeof(struct perf_event_header);
+
+ if (thread__tid(gtraceq->thread) != packet->context)
+ gtrace_set_thread(gtraceq, packet->context);
+
+ /* Set time field based on rvtrace auxtrace config. */
+ sample.time = gtrace_resolve_sample_time(gtraceq, packet);
+
+ sample.ip = packet->start_addr;
+ sample.pid = thread__pid(gtraceq->thread);
+ sample.tid = thread__tid(gtraceq->thread);
+ sample.addr = packet->end_addr;
+ sample.insn_cnt = packet->insn_cnt;
+ sample.id = gtraceq->ptr->branches_id;
+ sample.stream_id = gtraceq->ptr->branches_id;
+ sample.period = 1;
+ sample.cpu = packet->cpu;
+ sample.flags = 0;
+ sample.cpumode = event->sample.header.misc;
+
+ gtrace_synth_copy_insn(gtraceq, packet, &sample);
+
+ ret = perf_session__deliver_synth_event(ptr->session, event, &sample);
+ if (ret)
+ pr_err(
+ "RISC-V Trace: failed to deliver instruction event, error %d\n",
+ ret);
+ perf_sample__exit(&sample);
+
+ return ret;
+}
+
+static int gtrace_synth_events(struct gtrace_decoder *ptr,
+ struct perf_session *session)
+{
+ struct evlist *evlist = session->evlist;
+ struct evsel *evsel;
+ struct perf_event_attr attr;
+ bool found = false;
+ u64 id;
+ int err;
+
+ evlist__for_each_entry(evlist, evsel) {
+ if (evsel->core.attr.type == ptr->pmu_type) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ pr_debug("No selected events with RISC-V Trace data\n");
+ return 0;
+ }
+
+ memset(&attr, 0, sizeof(struct perf_event_attr));
+ attr.size = sizeof(struct perf_event_attr);
+ attr.type = PERF_TYPE_HARDWARE;
+ attr.sample_type = evsel->core.attr.sample_type & PERF_SAMPLE_MASK;
+ attr.sample_type |= PERF_SAMPLE_IP | PERF_SAMPLE_TID |
+ PERF_SAMPLE_PERIOD;
+ if (ptr->timeless_decoding)
+ attr.sample_type &= ~(u64)PERF_SAMPLE_TIME;
+ else
+ attr.sample_type |= PERF_SAMPLE_TIME;
+
+ attr.exclude_user = evsel->core.attr.exclude_user;
+ attr.exclude_kernel = evsel->core.attr.exclude_kernel;
+ attr.exclude_hv = evsel->core.attr.exclude_hv;
+ attr.exclude_host = evsel->core.attr.exclude_host;
+ attr.exclude_guest = evsel->core.attr.exclude_guest;
+ attr.sample_id_all = evsel->core.attr.sample_id_all;
+ attr.read_format = evsel->core.attr.read_format;
+
+ /* create new id val to be a fixed offset from evsel id */
+ id = evsel->core.id[0] + 1000000000;
+ if (!id)
+ id = 1;
+
+ if (ptr->synth_opts.branches) {
+ attr.config = PERF_COUNT_HW_BRANCH_INSTRUCTIONS;
+ attr.sample_period = 1;
+ attr.sample_type |= PERF_SAMPLE_ADDR;
+
+ err = perf_session__deliver_synth_attr_event(session, &attr, id);
+ if (err)
+ return err;
+ ptr->branches_sample_type = attr.sample_type;
+ ptr->branches_id = id;
+ id += 1;
+ attr.sample_type &= ~(u64)PERF_SAMPLE_ADDR;
+ }
+
+ return 0;
+}
+
+static int gtrace_process_queue(struct gtrace_queue *gtraceq)
+{
+ struct rvtrace_packet_buffer *packet_buffer = >raceq->decoder->packet_buffer;
+
+ for (int i = 0; i < packet_buffer->size; i++) {
+ struct rvtrace_packet packet = packet_buffer->packets[i];
+
+ if (packet.sample_type == RVTRACE_RANGE)
+ gtrace_synth_branch_sample(gtraceq, &packet);
+ else if (packet.sample_type == RVTRACE_LOSS)
+ fprintf(stdout, "RISC-V Trace: FIFO overrun, messages lost\n");
+ else if (packet.sample_type == RVTRACE_ERROR)
+ fprintf(stdout, "RISC-V Trace: Packet data parsing error\n");
+ }
+
+ return 0;
+}
+
+static int gtrace_get_trace(struct gtrace_queue *gtraceq)
+{
+ struct auxtrace_buffer *aux_buffer = gtraceq->buffer;
+ struct auxtrace_buffer *old_buffer = aux_buffer;
+ struct auxtrace_queue *queue;
+
+ queue = >raceq->ptr->queues.queue_array[gtraceq->queue_nr];
+
+ aux_buffer = auxtrace_buffer__next(queue, aux_buffer);
+
+ /* If no more data, drop the previous auxtrace_buffer and return */
+ if (!aux_buffer) {
+ if (old_buffer)
+ auxtrace_buffer__drop_data(old_buffer);
+ return 0;
+ }
+
+ gtraceq->buffer = aux_buffer;
+
+ /* If the aux_buffer doesn't have data associated, try to load it */
+ if (!aux_buffer->data) {
+ /* get the file desc associated with the perf data file */
+ int fd = perf_data__fd(gtraceq->ptr->session->data);
+
+ aux_buffer->data = auxtrace_buffer__get_data(aux_buffer, fd);
+ if (!aux_buffer->data)
+ return -ENOMEM;
+ }
+
+ /* If valid, drop the previous buffer */
+ if (old_buffer)
+ auxtrace_buffer__drop_data(old_buffer);
+
+ return aux_buffer->size;
+}
+
+/*
+ * gtrace_get_data_block: Fetch a block from the auxtrace_buffer queue
+ * if need be.
+ * Returns: < 0 if error
+ * = 0 if no more auxtrace_buffer to read
+ * > 0 if the current buffer isn't empty yet
+ */
+static int gtrace_get_data_block(struct gtrace_queue *gtraceq)
+{
+ int ret;
+
+ ret = gtrace_get_trace(gtraceq);
+ if (ret <= 0)
+ return ret;
+
+ /*
+ * We cannot assume consecutive blocks in the data file
+ * are contiguous, reset the decoder to force re-sync.
+ */
+ ret = rvtrace_decoder_reset(gtraceq->decoder);
+ if (ret)
+ return ret;
+
+ return gtraceq->buffer->size;
+}
+
+static int gtrace_run_timeless_decoder(struct gtrace_queue *gtraceq)
+{
+ struct rvtrace_decoder *decoder = gtraceq->decoder;
+ int ret;
+
+ while (1) {
+ ret = gtrace_get_data_block(gtraceq);
+ if (ret < 0)
+ return ret;
+
+ if (ret == 0)
+ break;
+
+ ret = decoder->decode(decoder, RVTRACE_DECODE_INSN,
+ gtraceq->buffer->data,
+ gtraceq->buffer->size);
+ if (ret)
+ return ret;
+
+ ret = gtrace_process_queue(gtraceq);
+ if (ret)
+ return ret;
+ }
+
+ return ret;
+}
+
+static int gtrace_process_timeless_queues(struct gtrace_decoder *ptr,
+ pid_t tid)
+{
+ unsigned int i;
+ struct auxtrace_queues *queues = &ptr->queues;
+
+ for (i = 0; i < queues->nr_queues; i++) {
+ struct auxtrace_queue *queue = &ptr->queues.queue_array[i];
+ struct gtrace_queue *gtraceq = queue->priv;
+
+ if (gtraceq && ((tid == -1) || (queue->tid == tid))) {
+ gtrace_set_thread(gtraceq, queue->tid);
+ gtrace_run_timeless_decoder(gtraceq);
+ }
+ }
+
+ return 0;
+}
+
+static u64 gtrace_queue_get_timestamp(struct gtrace_queue *gtraceq)
+{
+ struct rvtrace_packet_buffer *packet_buffer = >raceq->decoder->packet_buffer;
+
+ for (int i = 0; i < packet_buffer->size; i++) {
+ struct rvtrace_packet packet = packet_buffer->packets[i];
+
+ if (packet.sample_type == RVTRACE_RANGE)
+ return packet.timestamp;
+ }
+
+ return 0;
+}
+
+static int gtrace_queue_first_timestamp(struct gtrace_decoder *ptr,
+ struct gtrace_queue *gtraceq,
+ unsigned int queue_nr)
+{
+ struct rvtrace_decoder *decoder = gtraceq->decoder;
+ u64 timestamp = 0;
+ int ret;
+
+ /* Decode the first segment of data until a timestamp is found,
+ * then add it to the heap for sorting by time across multiple
+ * queues.
+ */
+ while (1) {
+ /*
+ * Fetch an aux_buffer from this gtraceq. Bail if no more
+ * blocks or an error has been encountered.
+ */
+ ret = gtrace_get_data_block(gtraceq);
+ if (ret <= 0)
+ goto out;
+
+ ret = decoder->decode(decoder, RVTRACE_DECODE_INSN,
+ gtraceq->buffer->data,
+ gtraceq->buffer->size);
+ if (ret)
+ goto out;
+
+ timestamp = gtrace_queue_get_timestamp(gtraceq);
+
+ /* We found a timestamp, no need to continue. */
+ if (timestamp)
+ break;
+ }
+
+ /* We have a timestamp and add it to the min heap */
+ ret = auxtrace_heap__add(&ptr->heap, queue_nr, timestamp);
+out:
+ return ret;
+}
+
+static int gtrace_process_timestamped_queues(struct gtrace_decoder *ptr)
+{
+ int ret = 0;
+ unsigned int queue_nr, i;
+ struct auxtrace_queue *queue;
+ struct gtrace_queue *gtraceq;
+
+ /* First, find the first timestamp for each queue and add it to the heap. */
+ for (i = 0; i < ptr->queues.nr_queues; i++) {
+ queue = &ptr->queues.queue_array[i];
+ gtraceq = queue->priv;
+ if (!gtraceq)
+ continue;
+
+ gtrace_set_thread(gtraceq, queue->tid);
+
+ ret = gtrace_queue_first_timestamp(ptr, gtraceq, i);
+ if (ret)
+ return ret;
+ }
+
+ /* Process queues in the heap in timestamp order */
+ while (1) {
+ if (!ptr->heap.heap_cnt)
+ break;
+
+ /* Take the entry at the top of the min heap */
+ queue_nr = ptr->heap.heap_array[0].queue_nr;
+ queue = &ptr->queues.queue_array[queue_nr];
+ gtraceq = queue->priv;
+
+ /*
+ * Remove the top entry from the heap since we are about
+ * to process it.
+ */
+ auxtrace_heap__pop(&ptr->heap);
+
+ /*
+ * Packets associated with this timestamp are already in
+ * the gtraceq->packet_buffer, so process them.
+ */
+ ret = gtrace_process_queue(gtraceq);
+ if (ret)
+ return ret;
+
+ /*
+ * Packets for this timestamp have been processed, time to
+ * move on to the next timestamp, find the next timestamp
+ * for this gtraceq.
+ */
+ ret = gtrace_queue_first_timestamp(ptr, gtraceq, queue_nr);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static struct gtrace_queue *gtrace_alloc_queue(struct gtrace_decoder *ptr,
+ unsigned int queue_nr)
+{
+ struct gtrace_queue *gtraceq;
+
+ gtraceq = zalloc(sizeof(*gtraceq));
+ if (!gtraceq)
+ return NULL;
+
+ gtraceq->event_buf = malloc(PERF_SAMPLE_MAX_SIZE);
+ if (!gtraceq->event_buf)
+ goto out_free;
+
+ gtraceq->ptr = ptr;
+ gtraceq->queue_nr = queue_nr;
+ gtraceq->offset = 0;
+
+ return gtraceq;
+
+out_free:
+ zfree(>raceq->event_buf);
+ free(gtraceq);
+
+ return NULL;
+
+}
+
+static int gtrace_setup_queue(struct gtrace_decoder *ptr,
+ struct auxtrace_queue *queue,
+ unsigned int queue_nr)
+{
+ struct gtrace_queue *gtraceq = queue->priv;
+
+ if (gtraceq)
+ return 0;
+
+ gtraceq = gtrace_alloc_queue(ptr, queue_nr);
+ if (!gtraceq)
+ return -ENOMEM;
+
+ queue->priv = gtraceq;
+ queue->cpu = queue_nr;
+
+ return 0;
+}
+
+static void gtrace_setup_timeless_decoding(struct gtrace_decoder *ptr)
+{
+ u64 *metadata = ptr->metadata[0];
+
+ /* Override timeless mode with user input from --itrace=Z */
+ if (ptr->synth_opts.timeless_decoding) {
+ ptr->timeless_decoding = true;
+ return;
+ }
+
+ ptr->timeless_decoding = metadata[RVTRACE_ENCODER_TIMESTAMP];
+}
+
static const char * const gtrace_global_header_fmts[] = {
[GTRACE_PMU_TYPE_CPUS] = " PMU type/num cpus %llx\n",
};
@@ -99,6 +966,251 @@ static void gtrace_print_auxtrace_info(u64 *val, int num_cpu)
}
}
+static struct gtrace_queue *gtrace_get_queue(struct gtrace_decoder *ptr, int cpu)
+{
+ if (ptr->per_thread_decoding)
+ return ptr->queues.queue_array[0].priv;
+
+ if (cpu < 0 || cpu >= (int)ptr->queues.nr_queues)
+ return NULL;
+
+ return ptr->queues.queue_array[cpu].priv;
+}
+
+/**
+ * Puts a fragment of an auxtrace buffer into the auxtrace queues based
+ * on the bounds of aux_event, if it matches with the buffer that's at
+ * file_offset.
+ *
+ * Normally, whole auxtrace buffers would be added to the queue. But we
+ * want to reset the decoder for every PERF_RECORD_AUX event, and the decoder
+ * is reset across each buffer, so splitting the buffers up in advance has
+ * the same effect.
+ */
+static int gtrace_queue_aux_fragment(struct perf_session *session, off_t file_offset, size_t sz,
+ struct perf_record_aux *aux_event, struct perf_sample *sample)
+{
+ int err;
+ char buf[PERF_SAMPLE_MAX_SIZE];
+ union perf_event *auxtrace_event_union;
+ struct perf_record_auxtrace *auxtrace_event;
+ union perf_event auxtrace_fragment;
+ __u64 aux_offset, aux_size;
+ enum coresight_format cs_format;
+
+ struct gtrace_decoder *ptr = container_of(session->auxtrace, struct gtrace_decoder,
+ auxtrace);
+
+ /*
+ * There should be a PERF_RECORD_AUXTRACE event at the file_offset that we got
+ * from looping through the auxtrace index.
+ */
+ err = perf_session__peek_event(session, file_offset, buf,
+ PERF_SAMPLE_MAX_SIZE, &auxtrace_event_union, NULL);
+ if (err)
+ return err;
+ auxtrace_event = &auxtrace_event_union->auxtrace;
+ if (auxtrace_event->header.type != PERF_RECORD_AUXTRACE)
+ return -EINVAL;
+
+ if (auxtrace_event->header.size < sizeof(struct perf_record_auxtrace) ||
+ auxtrace_event->header.size != sz) {
+ return -EINVAL;
+ }
+
+ /*
+ * Compare only TIDs if auxtrace CPU is -1, and CPUs if auxtrace CPU is not -1.
+ * Return 'not found' if mismatch.
+ */
+ if (auxtrace_event->cpu == (__u32) -1) {
+ ptr->per_thread_decoding = true;
+ if (auxtrace_event->tid != sample->tid)
+ return 1;
+ } else if (auxtrace_event->cpu != sample->cpu) {
+ if (ptr->per_thread_decoding) {
+ /*
+ * Found a per-cpu buffer after a per-thread one was
+ * already found
+ */
+ pr_err("GTRACE: Inconsistent per-thread/per-cpu mode.\n");
+ return -EINVAL;
+ }
+ return 1;
+ }
+
+ if (aux_event->flags & PERF_AUX_FLAG_OVERWRITE) {
+ /*
+ * Clamp size in snapshot mode. The buffer size is clamped in
+ * __auxtrace_mmap__read() for snapshots, so the aux record size doesn't reflect
+ * the buffer size.
+ */
+ aux_size = min(aux_event->aux_size, auxtrace_event->size);
+
+ /*
+ * In this mode, the head also points to the end of the buffer so aux_offset
+ * needs to have the size subtracted so it points to the beginning as in normal mode
+ */
+ aux_offset = aux_event->aux_offset - aux_size;
+ } else {
+ aux_size = aux_event->aux_size;
+ aux_offset = aux_event->aux_offset;
+ }
+
+ if (aux_offset >= auxtrace_event->offset &&
+ aux_offset + aux_size <= auxtrace_event->offset + auxtrace_event->size) {
+ struct gtrace_queue *gtraceq = gtrace_get_queue(ptr, auxtrace_event->cpu);
+
+ if (!gtraceq)
+ return -EINVAL;
+
+ /*
+ * If this AUX event was inside this buffer somewhere, create a new auxtrace event
+ * based on the sizes of the aux event, and queue that fragment.
+ */
+ auxtrace_fragment.auxtrace = *auxtrace_event;
+ auxtrace_fragment.auxtrace.size = aux_size;
+ auxtrace_fragment.auxtrace.offset = aux_offset;
+ auxtrace_fragment.auxtrace.idx = gtraceq->queue_nr;
+ file_offset += aux_offset - auxtrace_event->offset + auxtrace_event->header.size;
+
+ pr_debug3("GTRACE: Queue buffer size: %#"PRI_lx64" offset: %#"PRI_lx64
+ " tid: %d cpu: %d\n", aux_size, aux_offset, sample->tid, sample->cpu);
+ err = auxtrace_queues__add_event(&ptr->queues, session, &auxtrace_fragment,
+ file_offset, NULL);
+ if (err)
+ return err;
+
+ cs_format = (aux_event->flags & PERF_AUX_FLAG_GTRACE_FORMAT_CORESIGHT) ?
+ FORMATTED : UNFORMATTED;
+ if (gtraceq->cs_format != UNSET && cs_format != gtraceq->cs_format) {
+ pr_err("GTRACE: mixed coresight formatted and unformatted trace not supported\n");
+ return -EINVAL;
+ }
+ gtraceq->cs_format = cs_format;
+ return 0;
+ }
+
+ /* Wasn't inside this buffer, but there were no parse errors. 1 == 'not found' */
+ return 1;
+}
+
+static int gtrace_queue_aux_records_cb(struct perf_session *session, union perf_event *event,
+ u64 offset __maybe_unused, void *data __maybe_unused)
+{
+ struct perf_sample sample;
+ int ret;
+ struct auxtrace_index_entry *ent;
+ struct auxtrace_index *auxtrace_index;
+ struct evsel *evsel;
+ size_t i;
+
+ /* Don't care about any other events, we're only queuing buffers for AUX events */
+ if (event->header.type != PERF_RECORD_AUX)
+ return 0;
+
+ if (event->header.size < sizeof(struct perf_record_aux))
+ return -EINVAL;
+
+ /* Truncated Aux records can have 0 size and shouldn't result in anything being queued. */
+ if (!event->aux.aux_size)
+ return 0;
+
+ /*
+ * Parse the sample, we need the sample_id_all data that comes after the event so that the
+ * CPU or PID can be matched to an AUXTRACE buffer's CPU or PID.
+ */
+ evsel = evlist__event2evsel(session->evlist, event);
+ if (!evsel)
+ return -EINVAL;
+ perf_sample__init(&sample, /*all=*/false);
+ ret = evsel__parse_sample(evsel, event, &sample);
+ if (ret)
+ goto out;
+
+ /*
+ * Loop through the auxtrace index to find the buffer that matches up with this aux event.
+ */
+ list_for_each_entry(auxtrace_index, &session->auxtrace_index, list) {
+ for (i = 0; i < auxtrace_index->nr; i++) {
+ ent = &auxtrace_index->entries[i];
+ ret = gtrace_queue_aux_fragment(session, ent->file_offset,
+ ent->sz, &event->aux, &sample);
+ /*
+ * Stop search on error or successful values. Continue search on
+ * 1 ('not found')
+ */
+ if (ret != 1)
+ goto out;
+ }
+ }
+
+ /*
+ * Couldn't find the buffer corresponding to this aux record, something went wrong. Warn but
+ * don't exit with an error because it will still be possible to decode other aux records.
+ */
+ pr_err("CS ETM: Couldn't find auxtrace buffer for aux_offset: %#"PRI_lx64
+ " tid: %d cpu: %d\n", event->aux.aux_offset, sample.tid, sample.cpu);
+ ret = 0;
+out:
+ perf_sample__exit(&sample);
+ return ret;
+
+}
+
+static int gtrace_queue_aux_records(struct perf_session *session)
+{
+ struct auxtrace_index *index = list_first_entry_or_null(&session->auxtrace_index,
+ struct auxtrace_index, list);
+
+ if (index && index->nr > 0)
+ return perf_session__peek_events(session, session->header.data_offset,
+ session->header.data_size,
+ gtrace_queue_aux_records_cb, NULL);
+
+ return 0;
+}
+
+static int gtrace_create_queue_decoder(struct gtrace_queue *gtraceq)
+{
+ struct gtrace_decoder *ptr = gtraceq->ptr;
+ struct rvtrace_decoder_params params;
+
+ params.data = gtraceq;
+ params.mem_access = gtrace_mem_access;
+ params.coresight_formatted = gtraceq->cs_format == FORMATTED;
+ params.format = ptr->metadata[gtraceq->queue_nr][RVTRACE_ENCODER_FORMAT];
+ params.srcbits = ptr->metadata[gtraceq->queue_nr][RVTRACE_ENCODER_SRCBITS];
+ gtraceq->decoder = rvtrace_decoder_new(¶ms);
+ if (!gtraceq->decoder)
+ return -EINVAL;
+
+ return 0;
+}
+
+static int gtrace_create_decoders(struct gtrace_decoder *ptr)
+{
+ struct auxtrace_queues *queues = &ptr->queues;
+
+ for (unsigned int i = 0; i < queues->nr_queues; i++) {
+ bool empty = list_empty(&queues->queue_array[i].head);
+ struct gtrace_queue *gtraceq = queues->queue_array[i].priv;
+ int ret;
+
+ /*
+ * Don't create decoders for empty queues, mainly because
+ * q->format is unknown for empty queues.
+ */
+ assert(empty || gtraceq->cs_format != UNSET);
+ if (empty)
+ continue;
+
+ ret = gtrace_create_queue_decoder(gtraceq);
+ if (ret)
+ return ret;
+ }
+ return 0;
+}
+
int gtrace__process_auxtrace_info(union perf_event *event,
struct perf_session *session)
{
@@ -106,7 +1218,7 @@ int gtrace__process_auxtrace_info(union perf_event *event,
struct gtrace_decoder *ptr;
u64 *priv;
u64 **metadata;
- int num_cpu;
+ int num_cpu, max_cpu = 0;
int priv_size;
int err;
int i;
@@ -115,7 +1227,6 @@ int gtrace__process_auxtrace_info(union perf_event *event,
num_cpu = priv[GTRACE_PMU_TYPE_CPUS] & 0xffffffff;
if (dump_trace)
gtrace_print_auxtrace_info(priv, num_cpu);
-
priv_size = auxtrace_info->header.size - sizeof(struct perf_event_header) -
INFO_HEADER_SIZE - GTRACE_HEADER_SIZE;
if (num_cpu <= 0 || priv_size <= 0 ||
@@ -126,7 +1237,7 @@ int gtrace__process_auxtrace_info(union perf_event *event,
if (!metadata)
return -ENOMEM;
- i = GTRACE_HEADER_SIZE;
+ i = GTRACE_HEADER_MAX;
for (int j = 0; j < num_cpu; j++) {
metadata[j] = zalloc(sizeof(*metadata[j]) * RVTRACE_ENCODER_PRIV_MAX);
@@ -137,6 +1248,9 @@ int gtrace__process_auxtrace_info(union perf_event *event,
for (int k = 0; k < RVTRACE_ENCODER_PRIV_MAX; k++)
metadata[j][k] = priv[i + k];
+
+ if ((int)metadata[j][RVTRACE_ENCODER_CPU] > max_cpu)
+ max_cpu = metadata[j][RVTRACE_ENCODER_CPU];
i += RVTRACE_ENCODER_PRIV_MAX;
}
@@ -146,6 +1260,26 @@ int gtrace__process_auxtrace_info(union perf_event *event,
goto err_free_metadata;
}
+ err = auxtrace_queues__init_nr(&ptr->queues, max_cpu + 1);
+ if (err)
+ goto err_free_metadata;
+
+ for (unsigned int j = 0; j < ptr->queues.nr_queues; ++j) {
+ err = gtrace_setup_queue(ptr, &ptr->queues.queue_array[j], j);
+ if (err)
+ goto err_free_queues;
+ }
+
+ if (session->itrace_synth_opts->set) {
+ ptr->synth_opts = *session->itrace_synth_opts;
+ } else {
+ itrace_synth_opts__set_default(&ptr->synth_opts,
+ session->itrace_synth_opts->default_no_sample);
+ ptr->synth_opts.callchain = false;
+ ptr->synth_opts.thread_stack = session->itrace_synth_opts->thread_stack;
+ }
+
+
ptr->session = session;
ptr->machine = &session->machines.host;
ptr->auxtrace_type = auxtrace_info->type;
@@ -161,10 +1295,52 @@ int gtrace__process_auxtrace_info(union perf_event *event,
ptr->auxtrace.evsel_is_auxtrace = gtrace_evsel_is_auxtrace;
session->auxtrace = &ptr->auxtrace;
- return 0;
+ gtrace_setup_timeless_decoding(ptr);
+
+ /* Load firmware binary if specified */
+ if (symbol_conf.firmware_name) {
+ u64 base_addr = 0;
+
+ if (symbol_conf.firmware_base)
+ base_addr = strtoull(symbol_conf.firmware_base, NULL, 0);
+
+ ptr->firmware_dso = dso__new(symbol_conf.firmware_name);
+ if (ptr->firmware_dso) {
+ ptr->firmware_maps = maps__new(ptr->machine);
+ if (!ptr->firmware_maps ||
+ gtrace_register_firmware_maps(ptr,
+ ptr->firmware_dso,
+ base_addr) < 0) {
+ pr_err("Failed to register machine code maps: %s\n",
+ symbol_conf.firmware_name);
+ dso__put(ptr->firmware_dso);
+ ptr->firmware_dso = NULL;
+ maps__put(ptr->firmware_maps);
+ ptr->firmware_maps = NULL;
+ }
+ }
+ }
+ err = gtrace_synth_events(ptr, session);
+ if (err)
+ goto err_free_queues;
+
+ err = gtrace_queue_aux_records(session);
+ if (err)
+ goto err_free_queues;
+
+ err = gtrace_create_decoders(ptr);
+ if (err)
+ goto err_free_queues;
+
+ ptr->data_queued = ptr->queues.populated;
+ return 0;
+err_free_queues:
+ auxtrace_queues__free(&ptr->queues);
+ session->auxtrace = NULL;
err_free_metadata:
for (int j = 0; j < num_cpu; j++)
- zfree(&metadata);
+ zfree(&metadata[j]);
+ zfree(&metadata);
return err;
}
diff --git a/tools/perf/util/symbol_conf.h b/tools/perf/util/symbol_conf.h
index 0dee5aa6a534..83fc134ea8ac 100644
--- a/tools/perf/util/symbol_conf.h
+++ b/tools/perf/util/symbol_conf.h
@@ -72,6 +72,8 @@ struct symbol_conf {
prefer_latency;
const char *vmlinux_name,
*kallsyms_name,
+ *firmware_name,
+ *firmware_base,
*source_prefix,
*field_sep,
*graph_function;
--
2.34.1
^ permalink raw reply [flat|nested] 25+ messages in thread