xref: /linux/drivers/interconnect/debugfs-client.c (revision 573a6d4c7a21e34d7a2174b8d3c7ca67a5e850b6)
1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3  * Copyright (c) 2023, Qualcomm Innovation Center, Inc. All rights reserved.
4  */
5 #include <linux/debugfs.h>
6 #include <linux/interconnect.h>
7 #include <linux/platform_device.h>
8 #include <linux/slab.h>
9 
10 #include "internal.h"
11 
12 /*
13  * This can be dangerous, therefore don't provide any real compile time
14  * configuration option for this feature.
15  * People who want to use this will need to modify the source code directly.
16  */
17 #undef INTERCONNECT_ALLOW_WRITE_DEBUGFS
18 
19 #if defined(INTERCONNECT_ALLOW_WRITE_DEBUGFS) && defined(CONFIG_DEBUG_FS)
20 
21 static LIST_HEAD(debugfs_paths);
22 static DEFINE_MUTEX(debugfs_lock);
23 
24 static struct platform_device *pdev;
25 static struct icc_path *cur_path;
26 
27 static char *src_node;
28 static char *dst_node;
29 static u32 avg_bw;
30 static u32 peak_bw;
31 static u32 tag;
32 
33 struct debugfs_path {
34 	const char *src;
35 	const char *dst;
36 	struct icc_path *path;
37 	struct list_head list;
38 };
39 
40 static ssize_t icc_node_read(struct file *file, char __user *user_buf,
41 			     size_t count, loff_t *ppos)
42 {
43 	char **node = file->private_data;
44 	char *copy;
45 	size_t len;
46 	ssize_t ret;
47 
48 	scoped_guard(mutex, &debugfs_lock) {
49 		copy = kstrdup(*node ?: "", GFP_KERNEL);
50 	}
51 	if (!copy)
52 		return -ENOMEM;
53 
54 	len = strlen(copy);
55 	copy[len++] = '\n';
56 	ret = simple_read_from_buffer(user_buf, count, ppos, copy, len);
57 	kfree(copy);
58 	return ret;
59 }
60 
61 static ssize_t icc_node_write(struct file *file, const char __user *user_buf,
62 			      size_t count, loff_t *ppos)
63 {
64 	char **node = file->private_data;
65 	char *old, *new;
66 
67 	if (*ppos)
68 		return -EINVAL;
69 	if (count + 1 > PAGE_SIZE)
70 		return -E2BIG;
71 
72 	new = memdup_user_nul(user_buf, count);
73 	if (IS_ERR(new))
74 		return PTR_ERR(new);
75 	strim(new);
76 
77 	scoped_guard(mutex, &debugfs_lock) {
78 		old = *node;
79 		*node = new;
80 	}
81 
82 	kfree(old);
83 	return count;
84 }
85 
86 static const struct file_operations icc_node_fops = {
87 	.open = simple_open,
88 	.read = icc_node_read,
89 	.write = icc_node_write,
90 	.llseek = default_llseek,
91 };
92 
93 static struct icc_path *get_path(const char *src, const char *dst)
94 {
95 	struct debugfs_path *path;
96 
97 	list_for_each_entry(path, &debugfs_paths, list) {
98 		if (!strcmp(path->src, src) && !strcmp(path->dst, dst))
99 			return path->path;
100 	}
101 
102 	return NULL;
103 }
104 
105 static int icc_get_set(void *data, u64 val)
106 {
107 	struct debugfs_path *debugfs_path;
108 	char *src, *dst;
109 	int ret = 0;
110 
111 	guard(mutex)(&debugfs_lock);
112 
113 	/*
114 	 * If we've already looked up a path, then use the existing one instead
115 	 * of calling icc_get() again. This allows for updating previous BW
116 	 * votes when "get" is written to multiple times for multiple paths.
117 	 */
118 	cur_path = get_path(src_node, dst_node);
119 	if (cur_path)
120 		goto out;
121 
122 	src = kstrdup(src_node, GFP_KERNEL);
123 	dst = kstrdup(dst_node, GFP_KERNEL);
124 
125 	if (!src || !dst) {
126 		ret = -ENOMEM;
127 		goto err_free;
128 	}
129 
130 	cur_path = icc_get(&pdev->dev, src, dst);
131 	if (IS_ERR(cur_path)) {
132 		ret = PTR_ERR(cur_path);
133 		goto err_free;
134 	}
135 
136 	debugfs_path = kzalloc_obj(*debugfs_path);
137 	if (!debugfs_path) {
138 		ret = -ENOMEM;
139 		goto err_put;
140 	}
141 
142 	debugfs_path->path = cur_path;
143 	debugfs_path->src = src;
144 	debugfs_path->dst = dst;
145 	list_add_tail(&debugfs_path->list, &debugfs_paths);
146 
147 	goto out;
148 
149 err_put:
150 	icc_put(cur_path);
151 err_free:
152 	kfree(src);
153 	kfree(dst);
154 out:
155 	return ret;
156 }
157 
158 DEFINE_DEBUGFS_ATTRIBUTE(icc_get_fops, NULL, icc_get_set, "%llu\n");
159 
160 static int icc_commit_set(void *data, u64 val)
161 {
162 	int ret;
163 
164 	guard(mutex)(&debugfs_lock);
165 
166 	if (!cur_path) {
167 		ret = -EINVAL;
168 		goto out;
169 	}
170 
171 	if (IS_ERR(cur_path)) {
172 		ret = PTR_ERR(cur_path);
173 		goto out;
174 	}
175 
176 	icc_set_tag(cur_path, tag);
177 	ret = icc_set_bw(cur_path, avg_bw, peak_bw);
178 out:
179 	return ret;
180 }
181 
182 DEFINE_DEBUGFS_ATTRIBUTE(icc_commit_fops, NULL, icc_commit_set, "%llu\n");
183 
184 int icc_debugfs_client_init(struct dentry *icc_dir)
185 {
186 	struct dentry *client_dir;
187 	int ret;
188 
189 	pdev = platform_device_alloc("icc-debugfs-client", PLATFORM_DEVID_NONE);
190 	if (!pdev)
191 		return -ENOMEM;
192 
193 	ret = platform_device_add(pdev);
194 	if (ret) {
195 		pr_err("%s: failed to add platform device: %d\n", __func__, ret);
196 		platform_device_put(pdev);
197 		return ret;
198 	}
199 
200 	src_node = kstrdup("", GFP_KERNEL);
201 	dst_node = kstrdup("", GFP_KERNEL);
202 	if (!src_node || !dst_node) {
203 		kfree(dst_node);
204 		kfree(src_node);
205 		return -ENOMEM;
206 	}
207 
208 	client_dir = debugfs_create_dir("test_client", icc_dir);
209 
210 	debugfs_create_file("src_node", 0600, client_dir, &src_node,
211 			    &icc_node_fops);
212 	debugfs_create_file("dst_node", 0600, client_dir, &dst_node,
213 			    &icc_node_fops);
214 	debugfs_create_file("get", 0200, client_dir, NULL, &icc_get_fops);
215 	debugfs_create_u32("avg_bw", 0600, client_dir, &avg_bw);
216 	debugfs_create_u32("peak_bw", 0600, client_dir, &peak_bw);
217 	debugfs_create_u32("tag", 0600, client_dir, &tag);
218 	debugfs_create_file("commit", 0200, client_dir, NULL, &icc_commit_fops);
219 
220 	return 0;
221 }
222 
223 #else
224 
225 int icc_debugfs_client_init(struct dentry *icc_dir)
226 {
227 	return 0;
228 }
229 
230 #endif
231