[PATCH 02/24] gtrace: Initial implementation of gtrace sysfs framework

Zane Leung liangzhen at linux.spacemit.com
Thu Oct 22 20:14:43 PDT 2026


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 at linux.spacemit.com>
---
 drivers/hwtracing/gtrace/Makefile       |   2 +-
 drivers/hwtracing/gtrace/gtrace-core.c  |  47 ++-
 drivers/hwtracing/gtrace/gtrace-sysfs.c | 440 ++++++++++++++++++++++++
 include/linux/gtrace.h                  | 104 ++++++
 4 files changed, 583 insertions(+), 10 deletions(-)
 create mode 100644 drivers/hwtracing/gtrace/gtrace-sysfs.c

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




More information about the linux-riscv mailing list