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