xref: /OK3568_Linux_fs/kernel/drivers/gpu/arm/bifrost/mali_kbase_debugfs_helper.c (revision 4882a59341e53eb6f0b4789bf948001014eff981)
1 // SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
2 /*
3  *
4  * (C) COPYRIGHT 2019-2023 ARM Limited. All rights reserved.
5  *
6  * This program is free software and is provided to you under the terms of the
7  * GNU General Public License version 2 as published by the Free Software
8  * Foundation, and any use by you of this program is subject to the terms
9  * of such GNU license.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, you can access it online at
18  * http://www.gnu.org/licenses/gpl-2.0.html.
19  *
20  */
21 
22 #include <linux/debugfs.h>
23 #include <linux/seq_file.h>
24 #include <linux/slab.h>
25 #include <linux/uaccess.h>
26 
27 #include "mali_kbase_debugfs_helper.h"
28 
29 /* Arbitrary maximum size to prevent user space allocating too much kernel
30  * memory
31  */
32 #define DEBUGFS_MEM_POOLS_MAX_WRITE_SIZE (256u)
33 
34 /**
35  * set_attr_from_string - Parse a string to set elements of an array
36  *
37  * @buf:         Input string to parse. Must be nul-terminated!
38  * @array:       Address of an object that can be accessed like an array.
39  * @nelems:      Number of elements in the array.
40  * @set_attr_fn: Function to be called back for each array element.
41  *
42  * This is the core of the implementation of
43  * kbase_debugfs_helper_set_attr_from_string. The only difference between the
44  * two functions is that this one requires the input string to be writable.
45  *
46  * Return: 0 if success, negative error code otherwise.
47  */
48 static int
set_attr_from_string(char * const buf,void * const array,size_t const nelems,kbase_debugfs_helper_set_attr_fn * const set_attr_fn)49 set_attr_from_string(char *const buf, void *const array, size_t const nelems,
50 		     kbase_debugfs_helper_set_attr_fn * const set_attr_fn)
51 {
52 	size_t index, err = 0;
53 	char *ptr = buf;
54 
55 	for (index = 0; index < nelems && *ptr; ++index) {
56 		unsigned long new_size;
57 		size_t len;
58 		char sep;
59 
60 		/* Drop leading spaces */
61 		while (*ptr == ' ')
62 			ptr++;
63 
64 		len = strcspn(ptr, "\n ");
65 		if (len == 0) {
66 			/* No more values (allow this) */
67 			break;
68 		}
69 
70 		/* Substitute a nul terminator for a space character
71 		 * to make the substring valid for kstrtoul.
72 		 */
73 		sep = ptr[len];
74 		if (sep == ' ')
75 			ptr[len++] = '\0';
76 
77 		err = kstrtoul(ptr, 0, &new_size);
78 		if (err)
79 			break;
80 
81 		/* Skip the substring (including any premature nul terminator)
82 		 */
83 		ptr += len;
84 
85 		set_attr_fn(array, index, new_size);
86 	}
87 
88 	return err;
89 }
90 
kbase_debugfs_string_validator(char * const buf)91 int kbase_debugfs_string_validator(char *const buf)
92 {
93 	int err = 0;
94 	char *ptr = buf;
95 
96 	while (*ptr) {
97 		unsigned long test_number;
98 		size_t len;
99 
100 		/* Drop leading spaces */
101 		while (*ptr == ' ')
102 			ptr++;
103 
104 		/* Strings passed into the validator will be NULL terminated
105 		 * by nature, so here strcspn only needs to delimit by
106 		 * newlines, spaces and NULL terminator (delimited natively).
107 		 */
108 		len = strcspn(ptr, "\n ");
109 		if (len == 0) {
110 			/* No more values (allow this) */
111 			break;
112 		}
113 
114 		/* Substitute a nul terminator for a space character to make
115 		 * the substring valid for kstrtoul, and then replace it back.
116 		 */
117 		if (ptr[len] == ' ') {
118 			ptr[len] = '\0';
119 			err = kstrtoul(ptr, 0, &test_number);
120 			ptr[len] = ' ';
121 
122 			/* len should only be incremented if there is a valid
123 			 * number to follow - otherwise this will skip over
124 			 * the NULL terminator in cases with no ending newline
125 			 */
126 			len++;
127 		} else {
128 			/* This would occur at the last element before a space
129 			 * or a NULL terminator.
130 			 */
131 			err = kstrtoul(ptr, 0, &test_number);
132 		}
133 
134 		if (err)
135 			break;
136 		/* Skip the substring (including any premature nul terminator)
137 		 */
138 		ptr += len;
139 	}
140 	return err;
141 }
142 
kbase_debugfs_helper_set_attr_from_string(const char * const buf,void * const array,size_t const nelems,kbase_debugfs_helper_set_attr_fn * const set_attr_fn)143 int kbase_debugfs_helper_set_attr_from_string(
144 	const char *const buf, void *const array, size_t const nelems,
145 	kbase_debugfs_helper_set_attr_fn * const set_attr_fn)
146 {
147 	char *const wbuf = kstrdup(buf, GFP_KERNEL);
148 	int err = 0;
149 
150 	if (!wbuf)
151 		return -ENOMEM;
152 
153 	/* validate string before actually writing values */
154 	err = kbase_debugfs_string_validator(wbuf);
155 	if (err) {
156 		kfree(wbuf);
157 		return err;
158 	}
159 
160 	err = set_attr_from_string(wbuf, array, nelems,
161 		set_attr_fn);
162 
163 	kfree(wbuf);
164 	return err;
165 }
166 
kbase_debugfs_helper_get_attr_to_string(char * const buf,size_t const size,void * const array,size_t const nelems,kbase_debugfs_helper_get_attr_fn * const get_attr_fn)167 ssize_t kbase_debugfs_helper_get_attr_to_string(
168 	char *const buf, size_t const size, void *const array,
169 	size_t const nelems,
170 	kbase_debugfs_helper_get_attr_fn * const get_attr_fn)
171 {
172 	ssize_t total = 0;
173 	size_t index;
174 
175 	for (index = 0; index < nelems; ++index) {
176 		const char *postfix = " ";
177 
178 		if (index == (nelems-1))
179 			postfix = "\n";
180 
181 		total += scnprintf(buf + total, size - total, "%zu%s",
182 				get_attr_fn(array, index), postfix);
183 	}
184 
185 	return total;
186 }
187 
kbase_debugfs_helper_seq_write(struct file * const file,const char __user * const ubuf,size_t const count,size_t const nelems,kbase_debugfs_helper_set_attr_fn * const set_attr_fn)188 int kbase_debugfs_helper_seq_write(
189 	struct file *const file, const char __user *const ubuf,
190 	size_t const count, size_t const nelems,
191 	kbase_debugfs_helper_set_attr_fn * const set_attr_fn)
192 {
193 	const struct seq_file *const sfile = file->private_data;
194 	void *const array = sfile->private;
195 	int err = 0;
196 	char *buf;
197 
198 	if (WARN_ON(!array))
199 		return -EINVAL;
200 
201 	if (WARN_ON(count > DEBUGFS_MEM_POOLS_MAX_WRITE_SIZE))
202 		return -EINVAL;
203 
204 	buf = kmalloc(count + 1, GFP_KERNEL);
205 	if (buf == NULL)
206 		return -ENOMEM;
207 
208 	if (copy_from_user(buf, ubuf, count)) {
209 		kfree(buf);
210 		return -EFAULT;
211 	}
212 
213 	buf[count] = '\0';
214 
215 	/* validate string before actually writing values */
216 	err = kbase_debugfs_string_validator(buf);
217 	if (err) {
218 		kfree(buf);
219 		return err;
220 	}
221 
222 	err = set_attr_from_string(buf,
223 		array, nelems, set_attr_fn);
224 	kfree(buf);
225 
226 	return err;
227 }
228 
kbase_debugfs_helper_seq_read(struct seq_file * const sfile,size_t const nelems,kbase_debugfs_helper_get_attr_fn * const get_attr_fn)229 int kbase_debugfs_helper_seq_read(
230 	struct seq_file * const sfile, size_t const nelems,
231 	kbase_debugfs_helper_get_attr_fn * const get_attr_fn)
232 {
233 	void *const array = sfile->private;
234 	size_t index;
235 
236 	if (WARN_ON(!array))
237 		return -EINVAL;
238 
239 	for (index = 0; index < nelems; ++index) {
240 		const char *postfix = " ";
241 
242 		if (index == (nelems-1))
243 			postfix = "\n";
244 
245 		seq_printf(sfile, "%zu%s", get_attr_fn(array, index), postfix);
246 	}
247 	return 0;
248 }
249