1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * pSeries_reconfig.c - support for dynamic reconfiguration (including PCI |
4 | * Hotplug and Dynamic Logical Partitioning on RPA platforms). |
5 | * |
6 | * Copyright (C) 2005 Nathan Lynch |
7 | * Copyright (C) 2005 IBM Corporation |
8 | */ |
9 | |
10 | #include <linux/kernel.h> |
11 | #include <linux/notifier.h> |
12 | #include <linux/proc_fs.h> |
13 | #include <linux/security.h> |
14 | #include <linux/slab.h> |
15 | #include <linux/of.h> |
16 | |
17 | #include <asm/machdep.h> |
18 | #include <linux/uaccess.h> |
19 | #include <asm/mmu.h> |
20 | |
21 | #include "of_helpers.h" |
22 | |
23 | static int pSeries_reconfig_add_node(const char *path, struct property *proplist) |
24 | { |
25 | struct device_node *np; |
26 | int err = -ENOMEM; |
27 | |
28 | np = kzalloc(size: sizeof(*np), GFP_KERNEL); |
29 | if (!np) |
30 | goto out_err; |
31 | |
32 | np->full_name = kstrdup(s: kbasename(path), GFP_KERNEL); |
33 | if (!np->full_name) |
34 | goto out_err; |
35 | |
36 | np->properties = proplist; |
37 | of_node_set_flag(n: np, OF_DYNAMIC); |
38 | of_node_init(node: np); |
39 | |
40 | np->parent = pseries_of_derive_parent(path); |
41 | if (IS_ERR(ptr: np->parent)) { |
42 | err = PTR_ERR(ptr: np->parent); |
43 | goto out_err; |
44 | } |
45 | |
46 | err = of_attach_node(np); |
47 | if (err) { |
48 | printk(KERN_ERR "Failed to add device node %s\n" , path); |
49 | goto out_err; |
50 | } |
51 | |
52 | of_node_put(node: np->parent); |
53 | |
54 | return 0; |
55 | |
56 | out_err: |
57 | if (np) { |
58 | of_node_put(node: np->parent); |
59 | kfree(objp: np->full_name); |
60 | kfree(objp: np); |
61 | } |
62 | return err; |
63 | } |
64 | |
65 | static int pSeries_reconfig_remove_node(struct device_node *np) |
66 | { |
67 | struct device_node *parent, *child; |
68 | |
69 | parent = of_get_parent(node: np); |
70 | if (!parent) |
71 | return -EINVAL; |
72 | |
73 | if ((child = of_get_next_child(node: np, NULL))) { |
74 | of_node_put(node: child); |
75 | of_node_put(node: parent); |
76 | return -EBUSY; |
77 | } |
78 | |
79 | of_detach_node(np); |
80 | of_node_put(node: parent); |
81 | return 0; |
82 | } |
83 | |
84 | /* |
85 | * /proc/powerpc/ofdt - yucky binary interface for adding and removing |
86 | * OF device nodes. Should be deprecated as soon as we get an |
87 | * in-kernel wrapper for the RTAS ibm,configure-connector call. |
88 | */ |
89 | |
90 | static void release_prop_list(const struct property *prop) |
91 | { |
92 | struct property *next; |
93 | for (; prop; prop = next) { |
94 | next = prop->next; |
95 | kfree(objp: prop->name); |
96 | kfree(objp: prop->value); |
97 | kfree(objp: prop); |
98 | } |
99 | |
100 | } |
101 | |
102 | /** |
103 | * parse_next_property - process the next property from raw input buffer |
104 | * @buf: input buffer, must be nul-terminated |
105 | * @end: end of the input buffer + 1, for validation |
106 | * @name: return value; set to property name in buf |
107 | * @length: return value; set to length of value |
108 | * @value: return value; set to the property value in buf |
109 | * |
110 | * Note that the caller must make copies of the name and value returned, |
111 | * this function does no allocation or copying of the data. Return value |
112 | * is set to the next name in buf, or NULL on error. |
113 | */ |
114 | static char * parse_next_property(char *buf, char *end, char **name, int *length, |
115 | unsigned char **value) |
116 | { |
117 | char *tmp; |
118 | |
119 | *name = buf; |
120 | |
121 | tmp = strchr(buf, ' '); |
122 | if (!tmp) { |
123 | printk(KERN_ERR "property parse failed in %s at line %d\n" , |
124 | __func__, __LINE__); |
125 | return NULL; |
126 | } |
127 | *tmp = '\0'; |
128 | |
129 | if (++tmp >= end) { |
130 | printk(KERN_ERR "property parse failed in %s at line %d\n" , |
131 | __func__, __LINE__); |
132 | return NULL; |
133 | } |
134 | |
135 | /* now we're on the length */ |
136 | *length = -1; |
137 | *length = simple_strtoul(tmp, &tmp, 10); |
138 | if (*length == -1) { |
139 | printk(KERN_ERR "property parse failed in %s at line %d\n" , |
140 | __func__, __LINE__); |
141 | return NULL; |
142 | } |
143 | if (*tmp != ' ' || ++tmp >= end) { |
144 | printk(KERN_ERR "property parse failed in %s at line %d\n" , |
145 | __func__, __LINE__); |
146 | return NULL; |
147 | } |
148 | |
149 | /* now we're on the value */ |
150 | *value = tmp; |
151 | tmp += *length; |
152 | if (tmp > end) { |
153 | printk(KERN_ERR "property parse failed in %s at line %d\n" , |
154 | __func__, __LINE__); |
155 | return NULL; |
156 | } |
157 | else if (tmp < end && *tmp != ' ' && *tmp != '\0') { |
158 | printk(KERN_ERR "property parse failed in %s at line %d\n" , |
159 | __func__, __LINE__); |
160 | return NULL; |
161 | } |
162 | tmp++; |
163 | |
164 | /* and now we should be on the next name, or the end */ |
165 | return tmp; |
166 | } |
167 | |
168 | static struct property *new_property(const char *name, const int length, |
169 | const unsigned char *value, struct property *last) |
170 | { |
171 | struct property *new = kzalloc(size: sizeof(*new), GFP_KERNEL); |
172 | |
173 | if (!new) |
174 | return NULL; |
175 | |
176 | if (!(new->name = kstrdup(s: name, GFP_KERNEL))) |
177 | goto cleanup; |
178 | if (!(new->value = kmalloc(size: length + 1, GFP_KERNEL))) |
179 | goto cleanup; |
180 | |
181 | memcpy(new->value, value, length); |
182 | *(((char *)new->value) + length) = 0; |
183 | new->length = length; |
184 | new->next = last; |
185 | return new; |
186 | |
187 | cleanup: |
188 | kfree(objp: new->name); |
189 | kfree(objp: new->value); |
190 | kfree(objp: new); |
191 | return NULL; |
192 | } |
193 | |
194 | static int do_add_node(char *buf, size_t bufsize) |
195 | { |
196 | char *path, *end, *name; |
197 | struct device_node *np; |
198 | struct property *prop = NULL; |
199 | unsigned char* value; |
200 | int length, rv = 0; |
201 | |
202 | end = buf + bufsize; |
203 | path = buf; |
204 | buf = strchr(buf, ' '); |
205 | if (!buf) |
206 | return -EINVAL; |
207 | *buf = '\0'; |
208 | buf++; |
209 | |
210 | if ((np = of_find_node_by_path(path))) { |
211 | of_node_put(node: np); |
212 | return -EINVAL; |
213 | } |
214 | |
215 | /* rv = build_prop_list(tmp, bufsize - (tmp - buf), &proplist); */ |
216 | while (buf < end && |
217 | (buf = parse_next_property(buf, end, name: &name, length: &length, value: &value))) { |
218 | struct property *last = prop; |
219 | |
220 | prop = new_property(name, length, value, last); |
221 | if (!prop) { |
222 | rv = -ENOMEM; |
223 | prop = last; |
224 | goto out; |
225 | } |
226 | } |
227 | if (!buf) { |
228 | rv = -EINVAL; |
229 | goto out; |
230 | } |
231 | |
232 | rv = pSeries_reconfig_add_node(path, proplist: prop); |
233 | |
234 | out: |
235 | if (rv) |
236 | release_prop_list(prop); |
237 | return rv; |
238 | } |
239 | |
240 | static int do_remove_node(char *buf) |
241 | { |
242 | struct device_node *node; |
243 | int rv = -ENODEV; |
244 | |
245 | if ((node = of_find_node_by_path(path: buf))) |
246 | rv = pSeries_reconfig_remove_node(np: node); |
247 | |
248 | of_node_put(node); |
249 | return rv; |
250 | } |
251 | |
252 | static char *parse_node(char *buf, size_t bufsize, struct device_node **npp) |
253 | { |
254 | char *handle_str; |
255 | phandle handle; |
256 | *npp = NULL; |
257 | |
258 | handle_str = buf; |
259 | |
260 | buf = strchr(buf, ' '); |
261 | if (!buf) |
262 | return NULL; |
263 | *buf = '\0'; |
264 | buf++; |
265 | |
266 | handle = simple_strtoul(handle_str, NULL, 0); |
267 | |
268 | *npp = of_find_node_by_phandle(handle); |
269 | return buf; |
270 | } |
271 | |
272 | static int do_add_property(char *buf, size_t bufsize) |
273 | { |
274 | struct property *prop = NULL; |
275 | struct device_node *np; |
276 | unsigned char *value; |
277 | char *name, *end; |
278 | int length; |
279 | end = buf + bufsize; |
280 | buf = parse_node(buf, bufsize, npp: &np); |
281 | |
282 | if (!np) |
283 | return -ENODEV; |
284 | |
285 | if (parse_next_property(buf, end, name: &name, length: &length, value: &value) == NULL) |
286 | return -EINVAL; |
287 | |
288 | prop = new_property(name, length, value, NULL); |
289 | if (!prop) |
290 | return -ENOMEM; |
291 | |
292 | of_add_property(np, prop); |
293 | |
294 | return 0; |
295 | } |
296 | |
297 | static int do_remove_property(char *buf, size_t bufsize) |
298 | { |
299 | struct device_node *np; |
300 | char *tmp; |
301 | buf = parse_node(buf, bufsize, npp: &np); |
302 | |
303 | if (!np) |
304 | return -ENODEV; |
305 | |
306 | tmp = strchr(buf,' '); |
307 | if (tmp) |
308 | *tmp = '\0'; |
309 | |
310 | if (strlen(buf) == 0) |
311 | return -EINVAL; |
312 | |
313 | return of_remove_property(np, prop: of_find_property(np, name: buf, NULL)); |
314 | } |
315 | |
316 | static int do_update_property(char *buf, size_t bufsize) |
317 | { |
318 | struct device_node *np; |
319 | unsigned char *value; |
320 | char *name, *end, *next_prop; |
321 | int length; |
322 | struct property *newprop; |
323 | buf = parse_node(buf, bufsize, npp: &np); |
324 | end = buf + bufsize; |
325 | |
326 | if (!np) |
327 | return -ENODEV; |
328 | |
329 | next_prop = parse_next_property(buf, end, name: &name, length: &length, value: &value); |
330 | if (!next_prop) |
331 | return -EINVAL; |
332 | |
333 | if (!strlen(name)) |
334 | return -ENODEV; |
335 | |
336 | newprop = new_property(name, length, value, NULL); |
337 | if (!newprop) |
338 | return -ENOMEM; |
339 | |
340 | if (!strcmp(name, "slb-size" ) || !strcmp(name, "ibm,slb-size" )) |
341 | slb_set_size(*(int *)value); |
342 | |
343 | return of_update_property(np, newprop); |
344 | } |
345 | |
346 | /** |
347 | * ofdt_write - perform operations on the Open Firmware device tree |
348 | * |
349 | * @file: not used |
350 | * @buf: command and arguments |
351 | * @count: size of the command buffer |
352 | * @off: not used |
353 | * |
354 | * Operations supported at this time are addition and removal of |
355 | * whole nodes along with their properties. Operations on individual |
356 | * properties are not implemented (yet). |
357 | */ |
358 | static ssize_t ofdt_write(struct file *file, const char __user *buf, size_t count, |
359 | loff_t *off) |
360 | { |
361 | int rv; |
362 | char *kbuf; |
363 | char *tmp; |
364 | |
365 | rv = security_locked_down(what: LOCKDOWN_DEVICE_TREE); |
366 | if (rv) |
367 | return rv; |
368 | |
369 | kbuf = memdup_user_nul(buf, count); |
370 | if (IS_ERR(ptr: kbuf)) |
371 | return PTR_ERR(ptr: kbuf); |
372 | |
373 | tmp = strchr(kbuf, ' '); |
374 | if (!tmp) { |
375 | rv = -EINVAL; |
376 | goto out; |
377 | } |
378 | *tmp = '\0'; |
379 | tmp++; |
380 | |
381 | if (!strcmp(kbuf, "add_node" )) |
382 | rv = do_add_node(buf: tmp, bufsize: count - (tmp - kbuf)); |
383 | else if (!strcmp(kbuf, "remove_node" )) |
384 | rv = do_remove_node(buf: tmp); |
385 | else if (!strcmp(kbuf, "add_property" )) |
386 | rv = do_add_property(buf: tmp, bufsize: count - (tmp - kbuf)); |
387 | else if (!strcmp(kbuf, "remove_property" )) |
388 | rv = do_remove_property(buf: tmp, bufsize: count - (tmp - kbuf)); |
389 | else if (!strcmp(kbuf, "update_property" )) |
390 | rv = do_update_property(buf: tmp, bufsize: count - (tmp - kbuf)); |
391 | else |
392 | rv = -EINVAL; |
393 | out: |
394 | kfree(objp: kbuf); |
395 | return rv ? rv : count; |
396 | } |
397 | |
398 | static const struct proc_ops ofdt_proc_ops = { |
399 | .proc_write = ofdt_write, |
400 | .proc_lseek = noop_llseek, |
401 | }; |
402 | |
403 | /* create /proc/powerpc/ofdt write-only by root */ |
404 | static int proc_ppc64_create_ofdt(void) |
405 | { |
406 | struct proc_dir_entry *ent; |
407 | |
408 | ent = proc_create(name: "powerpc/ofdt" , mode: 0200, NULL, proc_ops: &ofdt_proc_ops); |
409 | if (ent) |
410 | proc_set_size(ent, 0); |
411 | |
412 | return 0; |
413 | } |
414 | machine_device_initcall(pseries, proc_ppc64_create_ofdt); |
415 | |