linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v2 0/3] Add interconnect debugfs client
@ 2023-07-31 22:42 Mike Tipton
  2023-07-31 22:42 ` [PATCH v2 1/3] debugfs: Add write support to debugfs_create_str() Mike Tipton
                   ` (2 more replies)
  0 siblings, 3 replies; 5+ messages in thread
From: Mike Tipton @ 2023-07-31 22:42 UTC (permalink / raw)
  To: djakov, gregkh, rafael, corbet
  Cc: linux-pm, linux-doc, linux-kernel, quic_okukatla, quic_viveka,
	peterz, Mike Tipton

This series introduces interconnect debugfs files that support voting
for any path the framework supports.

We've historically relied on an out-of-tree module for this, which used
the old icc_get() that was recently removed in [0]. The old icc_get()
took integer endpoint IDs, which made identifying paths in our old
implementation non-intuitive. The logical node names typically don't
change much chip-to-chip, but the raw integer IDs do. Take this
opportunity to introduce an icc_get() that uses string names instead,
which allows for a more intuitive and generic debugfs interface.

We rely on this support for debug, test, and verification. Hopefully
it'll be useful for other vendors as well.

[0] commit 7dcdad6f32c9 ("interconnect: drop unused icc_get() interface")

Changes in v2:
- Make icc_get() an internal interface.
- RCU-protect src_node and dst_node.
- Replace PLATFORM_DEVID_AUTO with PLATFORM_DEVID_NONE.
- Remove unnecessary #include.
- Add debugfs client documentation.

Mike Tipton (3):
  debugfs: Add write support to debugfs_create_str()
  interconnect: Reintroduce icc_get()
  interconnect: Add debugfs test client

 Documentation/driver-api/interconnect.rst |  25 ++++
 drivers/interconnect/Makefile             |   2 +-
 drivers/interconnect/core.c               |  66 +++++++++
 drivers/interconnect/debugfs-client.c     | 168 ++++++++++++++++++++++
 drivers/interconnect/internal.h           |   3 +
 fs/debugfs/file.c                         |  48 ++++++-
 6 files changed, 309 insertions(+), 3 deletions(-)
 create mode 100644 drivers/interconnect/debugfs-client.c

-- 
2.17.1


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

* [PATCH v2 1/3] debugfs: Add write support to debugfs_create_str()
  2023-07-31 22:42 [PATCH v2 0/3] Add interconnect debugfs client Mike Tipton
@ 2023-07-31 22:42 ` Mike Tipton
  2023-07-31 22:42 ` [PATCH v2 2/3] interconnect: Reintroduce icc_get() Mike Tipton
  2023-07-31 22:42 ` [PATCH v2 3/3] interconnect: Add debugfs test client Mike Tipton
  2 siblings, 0 replies; 5+ messages in thread
From: Mike Tipton @ 2023-07-31 22:42 UTC (permalink / raw)
  To: djakov, gregkh, rafael, corbet
  Cc: linux-pm, linux-doc, linux-kernel, quic_okukatla, quic_viveka,
	peterz, Mike Tipton

Currently, debugfs_create_str() only supports reading strings from
debugfs. Add support for writing them as well.

Based on original implementation by Peter Zijlstra [0]. Write support
was present in the initial patch version, but dropped in v2 due to lack
of users. We have a user now, so reintroduce it.

[0] https://lore.kernel.org/all/YF3Hv5zXb%2F6lauzs@hirez.programming.kicks-ass.net/

Signed-off-by: Mike Tipton <quic_mdtipton@quicinc.com>
---
 fs/debugfs/file.c | 48 +++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 46 insertions(+), 2 deletions(-)

diff --git a/fs/debugfs/file.c b/fs/debugfs/file.c
index b7711888dd17..87b3753aa4b1 100644
--- a/fs/debugfs/file.c
+++ b/fs/debugfs/file.c
@@ -904,8 +904,52 @@ EXPORT_SYMBOL_GPL(debugfs_create_str);
 static ssize_t debugfs_write_file_str(struct file *file, const char __user *user_buf,
 				      size_t count, loff_t *ppos)
 {
-	/* This is really only for read-only strings */
-	return -EINVAL;
+	struct dentry *dentry = F_DENTRY(file);
+	char *old, *new = NULL;
+	int pos = *ppos;
+	int r;
+
+	r = debugfs_file_get(dentry);
+	if (unlikely(r))
+		return r;
+
+	old = *(char **)file->private_data;
+
+	/* only allow strict concatenation */
+	r = -EINVAL;
+	if (pos && pos != strlen(old))
+		goto error;
+
+	r = -E2BIG;
+	if (pos + count + 1 > PAGE_SIZE)
+		goto error;
+
+	r = -ENOMEM;
+	new = kmalloc(pos + count + 1, GFP_KERNEL);
+	if (!new)
+		goto error;
+
+	if (pos)
+		memcpy(new, old, pos);
+
+	r = -EFAULT;
+	if (copy_from_user(new + pos, user_buf, count))
+		goto error;
+
+	new[pos + count] = '\0';
+	strim(new);
+
+	rcu_assign_pointer(*(char **)file->private_data, new);
+	synchronize_rcu();
+	kfree(old);
+
+	debugfs_file_put(dentry);
+	return count;
+
+error:
+	kfree(new);
+	debugfs_file_put(dentry);
+	return r;
 }
 
 static const struct file_operations fops_str = {
-- 
2.17.1


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

* [PATCH v2 2/3] interconnect: Reintroduce icc_get()
  2023-07-31 22:42 [PATCH v2 0/3] Add interconnect debugfs client Mike Tipton
  2023-07-31 22:42 ` [PATCH v2 1/3] debugfs: Add write support to debugfs_create_str() Mike Tipton
@ 2023-07-31 22:42 ` Mike Tipton
  2023-07-31 22:42 ` [PATCH v2 3/3] interconnect: Add debugfs test client Mike Tipton
  2 siblings, 0 replies; 5+ messages in thread
From: Mike Tipton @ 2023-07-31 22:42 UTC (permalink / raw)
  To: djakov, gregkh, rafael, corbet
  Cc: linux-pm, linux-doc, linux-kernel, quic_okukatla, quic_viveka,
	peterz, Mike Tipton

The original icc_get() that took integer node IDs was removed due to
lack of users. Reintroduce a new version that takes string node names,
which is needed for the debugfs client.

Signed-off-by: Mike Tipton <quic_mdtipton@quicinc.com>
---
 drivers/interconnect/core.c     | 63 +++++++++++++++++++++++++++++++++
 drivers/interconnect/internal.h |  2 ++
 2 files changed, 65 insertions(+)

diff --git a/drivers/interconnect/core.c b/drivers/interconnect/core.c
index 5fac448c28fd..fc1461dfc61b 100644
--- a/drivers/interconnect/core.c
+++ b/drivers/interconnect/core.c
@@ -147,6 +147,21 @@ static struct icc_node *node_find(const int id)
 	return idr_find(&icc_idr, id);
 }
 
+static struct icc_node *node_find_by_name(const char *name)
+{
+	struct icc_provider *provider;
+	struct icc_node *n;
+
+	list_for_each_entry(provider, &icc_providers, provider_list) {
+		list_for_each_entry(n, &provider->nodes, node_list) {
+			if (!strcmp(n->name, name))
+				return n;
+		}
+	}
+
+	return NULL;
+}
+
 static struct icc_path *path_init(struct device *dev, struct icc_node *dst,
 				  ssize_t num_nodes)
 {
@@ -561,6 +576,54 @@ struct icc_path *of_icc_get(struct device *dev, const char *name)
 }
 EXPORT_SYMBOL_GPL(of_icc_get);
 
+/**
+ * icc_get() - get a path handle between two endpoints
+ * @dev: device pointer for the consumer device
+ * @src: source node name
+ * @dst: destination node name
+ *
+ * This function will search for a path between two endpoints and return an
+ * icc_path handle on success. Use icc_put() to release constraints when they
+ * are not needed anymore.
+ *
+ * Return: icc_path pointer on success or ERR_PTR() on error. NULL is returned
+ * when the API is disabled.
+ */
+struct icc_path *icc_get(struct device *dev, const char *src, const char *dst)
+{
+	struct icc_node *src_node, *dst_node;
+	struct icc_path *path = ERR_PTR(-EPROBE_DEFER);
+
+	mutex_lock(&icc_lock);
+
+	src_node = node_find_by_name(src);
+	if (!src_node) {
+		dev_err(dev, "%s: invalid src=%s\n", __func__, src);
+		goto out;
+	}
+
+	dst_node = node_find_by_name(dst);
+	if (!dst_node) {
+		dev_err(dev, "%s: invalid dst=%s\n", __func__, dst);
+		goto out;
+	}
+
+	path = path_find(dev, src_node, dst_node);
+	if (IS_ERR(path)) {
+		dev_err(dev, "%s: invalid path=%ld\n", __func__, PTR_ERR(path));
+		goto out;
+	}
+
+	path->name = kasprintf(GFP_KERNEL, "%s-%s", src_node->name, dst_node->name);
+	if (!path->name) {
+		kfree(path);
+		path = ERR_PTR(-ENOMEM);
+	}
+out:
+	mutex_unlock(&icc_lock);
+	return path;
+}
+
 /**
  * icc_set_tag() - set an optional tag on a path
  * @path: the path we want to tag
diff --git a/drivers/interconnect/internal.h b/drivers/interconnect/internal.h
index f5f82a5c939e..95d6ba27bf78 100644
--- a/drivers/interconnect/internal.h
+++ b/drivers/interconnect/internal.h
@@ -41,4 +41,6 @@ struct icc_path {
 	struct icc_req reqs[];
 };
 
+struct icc_path *icc_get(struct device *dev, const char *src, const char *dst);
+
 #endif
-- 
2.17.1


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

* [PATCH v2 3/3] interconnect: Add debugfs test client
  2023-07-31 22:42 [PATCH v2 0/3] Add interconnect debugfs client Mike Tipton
  2023-07-31 22:42 ` [PATCH v2 1/3] debugfs: Add write support to debugfs_create_str() Mike Tipton
  2023-07-31 22:42 ` [PATCH v2 2/3] interconnect: Reintroduce icc_get() Mike Tipton
@ 2023-07-31 22:42 ` Mike Tipton
  2023-08-01  4:29   ` Pavan Kondeti
  2 siblings, 1 reply; 5+ messages in thread
From: Mike Tipton @ 2023-07-31 22:42 UTC (permalink / raw)
  To: djakov, gregkh, rafael, corbet
  Cc: linux-pm, linux-doc, linux-kernel, quic_okukatla, quic_viveka,
	peterz, Mike Tipton

It's often useful during test, debug, and development to issue path
votes from shell. Add a debugfs client for this purpose.

Example usage:
	cd /sys/kernel/debug/interconnect/test-client/

	# Configure node endpoints for the path from CPU to DDR on
	# qcom/sm8550.
	echo chm_apps > src_node
	echo ebi > dst_node

	# Get path between src_node and dst_node. This is only
	# necessary after updating the node endpoints.
	echo 1 > get

	# Set desired BW to 1GBps avg and 2GBps peak.
	echo 1000000 > avg_bw
	echo 2000000 > peak_bw

	# Vote for avg_bw and peak_bw on the latest path from "get".
	# Voting for multiple paths is possible by repeating this
	# process for different nodes endpoints.
	echo 1 > commit

Allowing userspace to directly enable and set bus rates can be dangerous
So, following in the footsteps of the regmap [0] and clk [1] frameworks,
keep these userspace controls compile-time disabled without Kconfig
options to enable them. Enabling this will require code changes to
define INTERCONNECT_ALLOW_WRITE_DEBUGFS.

[0] commit 09c6ecd39410 ("regmap: Add support for writing to regmap registers via debugfs")
[1] commit 37215da5553e ("clk: Add support for setting clk_rate via debugfs")

Signed-off-by: Mike Tipton <quic_mdtipton@quicinc.com>
---
 Documentation/driver-api/interconnect.rst |  25 ++++
 drivers/interconnect/Makefile             |   2 +-
 drivers/interconnect/core.c               |   3 +
 drivers/interconnect/debugfs-client.c     | 168 ++++++++++++++++++++++
 drivers/interconnect/internal.h           |   1 +
 5 files changed, 198 insertions(+), 1 deletion(-)
 create mode 100644 drivers/interconnect/debugfs-client.c

diff --git a/Documentation/driver-api/interconnect.rst b/Documentation/driver-api/interconnect.rst
index 5ed4f57a6bac..a92d0f277a1f 100644
--- a/Documentation/driver-api/interconnect.rst
+++ b/Documentation/driver-api/interconnect.rst
@@ -113,3 +113,28 @@ through dot to generate diagrams in many graphical formats::
 
         $ cat /sys/kernel/debug/interconnect/interconnect_graph | \
                 dot -Tsvg > interconnect_graph.svg
+
+The ``test-client`` directory provides interfaces for issuing BW requests to
+any arbitrary path. Note that for safety reasons, this feature is disabled by
+default without a Kconfig to enable it. Enabling it requires code changes to
+``#define INTERCONNECT_ALLOW_WRITE_DEBUGFS``. Example usage::
+
+        cd /sys/kernel/debug/interconnect/test-client/
+
+        # Configure node endpoints for the path from CPU to DDR on
+        # qcom/sm8550.
+        echo chm_apps > src_node
+        echo ebi > dst_node
+
+        # Get path between src_node and dst_node. This is only
+        # necessary after updating the node endpoints.
+        echo 1 > get
+
+        # Set desired BW to 1GBps avg and 2GBps peak.
+        echo 1000000 > avg_bw
+        echo 2000000 > peak_bw
+
+        # Vote for avg_bw and peak_bw on the latest path from "get".
+        # Voting for multiple paths is possible by repeating this
+        # process for different nodes endpoints.
+        echo 1 > commit
diff --git a/drivers/interconnect/Makefile b/drivers/interconnect/Makefile
index 5604ce351a9f..d0888babb9a1 100644
--- a/drivers/interconnect/Makefile
+++ b/drivers/interconnect/Makefile
@@ -1,7 +1,7 @@
 # SPDX-License-Identifier: GPL-2.0
 
 CFLAGS_core.o				:= -I$(src)
-icc-core-objs				:= core.o bulk.o
+icc-core-objs				:= core.o bulk.o debugfs-client.o
 
 obj-$(CONFIG_INTERCONNECT)		+= icc-core.o
 obj-$(CONFIG_INTERCONNECT_IMX)		+= imx/
diff --git a/drivers/interconnect/core.c b/drivers/interconnect/core.c
index fc1461dfc61b..9e12bb5e523d 100644
--- a/drivers/interconnect/core.c
+++ b/drivers/interconnect/core.c
@@ -1116,6 +1116,9 @@ static int __init icc_init(void)
 			    icc_debugfs_dir, NULL, &icc_summary_fops);
 	debugfs_create_file("interconnect_graph", 0444,
 			    icc_debugfs_dir, NULL, &icc_graph_fops);
+
+	icc_debugfs_client_init(icc_debugfs_dir);
+
 	return 0;
 }
 
diff --git a/drivers/interconnect/debugfs-client.c b/drivers/interconnect/debugfs-client.c
new file mode 100644
index 000000000000..c41255cc44ca
--- /dev/null
+++ b/drivers/interconnect/debugfs-client.c
@@ -0,0 +1,168 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2023, Qualcomm Innovation Center, Inc. All rights reserved.
+ */
+#include <linux/debugfs.h>
+#include <linux/interconnect.h>
+#include <linux/platform_device.h>
+
+#include "internal.h"
+
+/*
+ * This can be dangerous, therefore don't provide any real compile time
+ * configuration option for this feature.
+ * People who want to use this will need to modify the source code directly.
+ */
+#undef INTERCONNECT_ALLOW_WRITE_DEBUGFS
+
+#if defined(INTERCONNECT_ALLOW_WRITE_DEBUGFS) && defined(CONFIG_DEBUG_FS)
+
+static LIST_HEAD(debugfs_paths);
+static DEFINE_MUTEX(debugfs_lock);
+
+static struct platform_device *pdev;
+static struct icc_path *cur_path;
+
+static char *src_node;
+static char *dst_node;
+static u32 avg_bw;
+static u32 peak_bw;
+static u32 tag;
+
+struct debugfs_path {
+	const char *src;
+	const char *dst;
+	struct icc_path *path;
+	struct list_head list;
+};
+
+static struct icc_path *get_path(const char *src, const char *dst)
+{
+	struct debugfs_path *path;
+
+	list_for_each_entry(path, &debugfs_paths, list) {
+		if (!strcmp(path->src, src) && !strcmp(path->dst, dst))
+			return path->path;
+	}
+
+	return NULL;
+}
+
+static int icc_get_set(void *data, u64 val)
+{
+	struct debugfs_path *debugfs_path;
+	char *src, *dst;
+	int ret = 0;
+
+	mutex_lock(&debugfs_lock);
+
+	rcu_read_lock();
+	src = rcu_dereference(src_node);
+	dst = rcu_dereference(dst_node);
+
+	/*
+	 * If we've already looked up a path, then use the existing one instead
+	 * of calling icc_get() again. This allows for updating previous BW
+	 * votes when "get" is written to multiple times for multiple paths.
+	 */
+	cur_path = get_path(src, dst);
+	if (cur_path) {
+		rcu_read_unlock();
+		goto out;
+	}
+
+	src = kstrdup(src, GFP_KERNEL);
+	dst = kstrdup(dst, GFP_KERNEL);
+	rcu_read_unlock();
+
+	if (!src || !dst) {
+		ret = -ENOMEM;
+		goto err_free;
+	}
+
+	cur_path = icc_get(&pdev->dev, src, dst);
+	if (IS_ERR(cur_path)) {
+		ret = PTR_ERR(cur_path);
+		goto err_free;
+	}
+
+	debugfs_path = kzalloc(sizeof(*debugfs_path), GFP_KERNEL);
+	if (!debugfs_path) {
+		ret = -ENOMEM;
+		goto err_put;
+	}
+
+	debugfs_path->path = cur_path;
+	debugfs_path->src = src;
+	debugfs_path->dst = dst;
+	list_add_tail(&debugfs_path->list, &debugfs_paths);
+
+	goto out;
+
+err_put:
+	icc_put(cur_path);
+err_free:
+	kfree(src);
+	kfree(dst);
+out:
+	mutex_unlock(&debugfs_lock);
+	return ret;
+}
+
+DEFINE_DEBUGFS_ATTRIBUTE(icc_get_fops, NULL, icc_get_set, "%llu\n");
+
+static int icc_commit_set(void *data, u64 val)
+{
+	int ret;
+
+	mutex_lock(&debugfs_lock);
+
+	if (IS_ERR_OR_NULL(cur_path)) {
+		ret = PTR_ERR(cur_path);
+		goto out;
+	}
+
+	icc_set_tag(cur_path, tag);
+	ret = icc_set_bw(cur_path, avg_bw, peak_bw);
+out:
+	mutex_unlock(&debugfs_lock);
+	return ret;
+}
+
+DEFINE_DEBUGFS_ATTRIBUTE(icc_commit_fops, NULL, icc_commit_set, "%llu\n");
+
+int icc_debugfs_client_init(struct dentry *icc_dir)
+{
+	struct dentry *client_dir;
+	int ret;
+
+	pdev = platform_device_alloc("icc-debugfs-client", PLATFORM_DEVID_NONE);
+
+	ret = platform_device_add(pdev);
+	if (ret) {
+		pr_err("%s: failed to add platform device: %d\n", __func__, ret);
+		platform_device_put(pdev);
+		return ret;
+	}
+
+	client_dir = debugfs_create_dir("test_client", icc_dir);
+
+	debugfs_create_str("src_node", 0600, client_dir, &src_node);
+	debugfs_create_str("dst_node", 0600, client_dir, &dst_node);
+	debugfs_create_file("get", 0200, client_dir, NULL, &icc_get_fops);
+	debugfs_create_u32("avg_bw", 0600, client_dir, &avg_bw);
+	debugfs_create_u32("peak_bw", 0600, client_dir, &peak_bw);
+	debugfs_create_u32("tag", 0600, client_dir, &tag);
+	debugfs_create_file("commit", 0200, client_dir, NULL, &icc_commit_fops);
+
+	return 0;
+}
+
+#else
+
+int icc_debugfs_client_init(struct dentry *icc_dir)
+{
+	return 0;
+}
+
+#endif
diff --git a/drivers/interconnect/internal.h b/drivers/interconnect/internal.h
index 95d6ba27bf78..3b2cfd32e449 100644
--- a/drivers/interconnect/internal.h
+++ b/drivers/interconnect/internal.h
@@ -42,5 +42,6 @@ struct icc_path {
 };
 
 struct icc_path *icc_get(struct device *dev, const char *src, const char *dst);
+int icc_debugfs_client_init(struct dentry *icc_dir);
 
 #endif
-- 
2.17.1


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

* Re: [PATCH v2 3/3] interconnect: Add debugfs test client
  2023-07-31 22:42 ` [PATCH v2 3/3] interconnect: Add debugfs test client Mike Tipton
@ 2023-08-01  4:29   ` Pavan Kondeti
  0 siblings, 0 replies; 5+ messages in thread
From: Pavan Kondeti @ 2023-08-01  4:29 UTC (permalink / raw)
  To: Mike Tipton
  Cc: djakov, gregkh, rafael, corbet, linux-pm, linux-doc,
	linux-kernel, quic_okukatla, quic_viveka, peterz

On Mon, Jul 31, 2023 at 03:42:47PM -0700, Mike Tipton wrote:
> +static int icc_get_set(void *data, u64 val)
> +{
> +	struct debugfs_path *debugfs_path;
> +	char *src, *dst;
> +	int ret = 0;
> +
> +	mutex_lock(&debugfs_lock);
> +
> +	rcu_read_lock();
> +	src = rcu_dereference(src_node);
> +	dst = rcu_dereference(dst_node);
> +
> +	/*
> +	 * If we've already looked up a path, then use the existing one instead
> +	 * of calling icc_get() again. This allows for updating previous BW
> +	 * votes when "get" is written to multiple times for multiple paths.
> +	 */
> +	cur_path = get_path(src, dst);
> +	if (cur_path) {
> +		rcu_read_unlock();
> +		goto out;
> +	}
> +
> +	src = kstrdup(src, GFP_KERNEL);
> +	dst = kstrdup(dst, GFP_KERNEL);
> +	rcu_read_unlock();
> +

Blocking calls are not allowed from RCU critical section. So we would
need to replace GFP_KERNEL with GFP_ATOMIC.

> +	if (!src || !dst) {
> +		ret = -ENOMEM;
> +		goto err_free;
> +	}
> +
> +	cur_path = icc_get(&pdev->dev, src, dst);
> +	if (IS_ERR(cur_path)) {
> +		ret = PTR_ERR(cur_path);
> +		goto err_free;
> +	}
> +
> +	debugfs_path = kzalloc(sizeof(*debugfs_path), GFP_KERNEL);
> +	if (!debugfs_path) {
> +		ret = -ENOMEM;
> +		goto err_put;
> +	}
> +
> +	debugfs_path->path = cur_path;
> +	debugfs_path->src = src;
> +	debugfs_path->dst = dst;
> +	list_add_tail(&debugfs_path->list, &debugfs_paths);
> +
> +	goto out;
> +
> +err_put:
> +	icc_put(cur_path);
> +err_free:
> +	kfree(src);
> +	kfree(dst);
> +out:
> +	mutex_unlock(&debugfs_lock);
> +	return ret;
> +}

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

end of thread, other threads:[~2023-08-01  4:30 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2023-07-31 22:42 [PATCH v2 0/3] Add interconnect debugfs client Mike Tipton
2023-07-31 22:42 ` [PATCH v2 1/3] debugfs: Add write support to debugfs_create_str() Mike Tipton
2023-07-31 22:42 ` [PATCH v2 2/3] interconnect: Reintroduce icc_get() Mike Tipton
2023-07-31 22:42 ` [PATCH v2 3/3] interconnect: Add debugfs test client Mike Tipton
2023-08-01  4:29   ` Pavan Kondeti

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).