/src/barebox/common/oftree.c
Line | Count | Source |
1 | | // SPDX-License-Identifier: GPL-2.0-only |
2 | | |
3 | | #include <common.h> |
4 | | #include <environment.h> |
5 | | #include <fdt.h> |
6 | | #include <of.h> |
7 | | #include <command.h> |
8 | | #include <fs.h> |
9 | | #include <malloc.h> |
10 | | #include <linux/ctype.h> |
11 | | #include <asm/byteorder.h> |
12 | | #include <errno.h> |
13 | | #include <getopt.h> |
14 | | #include <init.h> |
15 | | #include <boot.h> |
16 | | #include <bootsource.h> |
17 | | #include <i2c/i2c.h> |
18 | | #include <reset_source.h> |
19 | | #include <watchdog.h> |
20 | | #include <globalvar.h> |
21 | | #include <magicvar.h> |
22 | | |
23 | | #define MAX_LEVEL 32 /* how deeply nested we will go */ |
24 | | |
25 | | static int is_printable_string(const void *data, int len) |
26 | 0 | { |
27 | 0 | const char *s = data; |
28 | | |
29 | | /* zero length is not */ |
30 | 0 | if (len == 0) |
31 | 0 | return 0; |
32 | | |
33 | | /* must terminate with zero */ |
34 | 0 | if (s[len - 1] != '\0') |
35 | 0 | return 0; |
36 | | |
37 | | /* printable or a null byte (concatenated strings) */ |
38 | 0 | while (((*s == '\0') || isprint(*s)) && (len > 0)) { |
39 | | /* |
40 | | * If we see a null, there are three possibilities: |
41 | | * 1) If len == 1, it is the end of the string, printable |
42 | | * 2) Next character also a null, not printable. |
43 | | * 3) Next character not a null, continue to check. |
44 | | */ |
45 | 0 | if (s[0] == '\0') { |
46 | 0 | if (len == 1) |
47 | 0 | return 1; |
48 | 0 | if (s[1] == '\0') |
49 | 0 | return 0; |
50 | 0 | } |
51 | 0 | s++; |
52 | 0 | len--; |
53 | 0 | } |
54 | | |
55 | | /* Not the null termination, or not done yet: not printable */ |
56 | 0 | if (*s != '\0' || (len != 0)) |
57 | 0 | return 0; |
58 | | |
59 | 0 | return 1; |
60 | 0 | } |
61 | | |
62 | | /* |
63 | | * Print the property in the best format, a heuristic guess. Print as |
64 | | * a string, concatenated strings, a byte, word, double word, or (if all |
65 | | * else fails) it is printed as a stream of bytes. |
66 | | */ |
67 | | void of_print_property(const void *data, int len) |
68 | 0 | { |
69 | 0 | int j; |
70 | | |
71 | | /* no data, don't print */ |
72 | 0 | if (len == 0) |
73 | 0 | return; |
74 | | |
75 | | /* |
76 | | * It is a string, but it may have multiple strings (embedded '\0's). |
77 | | */ |
78 | 0 | if (is_printable_string(data, len)) { |
79 | 0 | puts("\""); |
80 | 0 | j = 0; |
81 | 0 | while (j < len) { |
82 | 0 | if (j > 0) |
83 | 0 | puts("\", \""); |
84 | 0 | puts(data); |
85 | 0 | j += strlen(data) + 1; |
86 | 0 | data += strlen(data) + 1; |
87 | 0 | } |
88 | 0 | puts("\""); |
89 | 0 | return; |
90 | 0 | } |
91 | | |
92 | 0 | if ((len % 4) == 0) { |
93 | 0 | const u32 *p; |
94 | |
|
95 | 0 | printf("<"); |
96 | 0 | for (j = 0, p = data; j < len/4; j ++) |
97 | 0 | printf("0x%x%s", fdt32_to_cpu(p[j]), j < (len/4 - 1) ? " " : ""); |
98 | 0 | printf(">"); |
99 | 0 | } else { /* anything else... hexdump */ |
100 | 0 | const u8 *s; |
101 | |
|
102 | 0 | printf("["); |
103 | 0 | for (j = 0, s = data; j < len; j++) |
104 | 0 | printf("%02x%s", s[j], j < len - 1 ? " " : ""); |
105 | 0 | printf("]"); |
106 | 0 | } |
107 | 0 | } |
108 | | |
109 | | void of_print_cmdline(struct device_node *root) |
110 | 0 | { |
111 | 0 | struct device_node *node = of_find_node_by_path_from(root, "/chosen"); |
112 | 0 | const char *cmdline; |
113 | |
|
114 | 0 | if (!node) { |
115 | 0 | printf("commandline: no /chosen node\n"); |
116 | 0 | return; |
117 | 0 | } |
118 | | |
119 | 0 | cmdline = of_get_property(node, "bootargs", NULL); |
120 | |
|
121 | 0 | pr_info("commandline: %s\n", cmdline); |
122 | 0 | } |
123 | | |
124 | | static int of_fixup_bootargs_bootsource(struct device_node *root, |
125 | | struct device_node *chosen) |
126 | 0 | { |
127 | 0 | struct device_node *bootsource; |
128 | 0 | int ret = 0; |
129 | |
|
130 | 0 | bootsource = bootsource_of_node_get(root); |
131 | | /* |
132 | | * If kernel DTB doesn't have the appropriate alias set up, |
133 | | * give up and exit early. No error is reported. |
134 | | */ |
135 | 0 | if (bootsource) |
136 | 0 | ret = of_set_property(chosen, "bootsource", bootsource->full_name, |
137 | 0 | strlen(bootsource->full_name) + 1, true); |
138 | |
|
139 | 0 | return ret; |
140 | 0 | } |
141 | | |
142 | | static int of_fixup_bootargs_reset_source(struct device_node *root, |
143 | | struct device_node *chosen) |
144 | 0 | { |
145 | 0 | struct device_node *np; |
146 | 0 | struct device *dev; |
147 | 0 | char *name; |
148 | |
|
149 | 0 | dev = reset_source_get_device(); |
150 | 0 | if (!dev || !dev->of_node) |
151 | 0 | return 0; |
152 | | |
153 | 0 | name = of_get_reproducible_name(dev->of_node); |
154 | 0 | np = of_find_node_by_reproducible_name(root, name); |
155 | 0 | free(name); |
156 | |
|
157 | 0 | if (!np) |
158 | 0 | return 0; |
159 | | |
160 | 0 | return of_property_write_string(chosen, "reset-source-device", np->full_name); |
161 | 0 | } |
162 | | |
163 | | static void watchdog_build_bootargs(struct watchdog *watchdog, struct device_node *root) |
164 | 0 | { |
165 | 0 | int alias_id; |
166 | 0 | char *buf; |
167 | 0 |
|
168 | 0 | if (!watchdog) |
169 | 0 | return; |
170 | 0 |
|
171 | 0 | alias_id = watchdog_get_alias_id_from(watchdog, root); |
172 | 0 | if (alias_id < 0) |
173 | 0 | return; |
174 | 0 |
|
175 | 0 | buf = xasprintf("systemd.watchdog_device=/dev/watchdog%d", alias_id); |
176 | 0 |
|
177 | 0 | globalvar_add_simple("linux.bootargs.dyn.watchdog", buf); |
178 | 0 | free(buf); |
179 | 0 | } |
180 | | |
181 | | static int bootargs_append = 0; |
182 | | BAREBOX_MAGICVAR(global.linux.bootargs_append, "append to original oftree bootargs"); |
183 | | |
184 | | static int of_write_bootargs(struct device_node *node) |
185 | 0 | { |
186 | 0 | const char *str; |
187 | 0 | char *buf = NULL; |
188 | 0 | int ret; |
189 | |
|
190 | 0 | if (IS_ENABLED(CONFIG_SYSTEMD_OF_WATCHDOG)) |
191 | 0 | watchdog_build_bootargs(boot_get_enabled_watchdog(), of_get_parent(node)); |
192 | |
|
193 | 0 | str = linux_bootargs_get(); |
194 | 0 | if (!str) |
195 | 0 | return 0; |
196 | | |
197 | 0 | str = skip_spaces(str); |
198 | 0 | if (strlen(str) == 0) |
199 | 0 | return 0; |
200 | | |
201 | 0 | if (bootargs_append) { |
202 | 0 | const char *oldstr; |
203 | |
|
204 | 0 | ret = of_property_read_string(node, "bootargs", &oldstr); |
205 | 0 | if (!ret) |
206 | 0 | str = buf = xasprintf("%s %s", oldstr, str); |
207 | 0 | } |
208 | |
|
209 | 0 | ret = of_property_write_string(node, "bootargs", str); |
210 | 0 | free(buf); |
211 | 0 | return ret; |
212 | 0 | } |
213 | | |
214 | | static int of_fixup_bootargs(struct device_node *root, void *unused) |
215 | 0 | { |
216 | 0 | struct device_node *node; |
217 | 0 | int err; |
218 | 0 | int instance = reset_source_get_instance(); |
219 | 0 | const char *serialno; |
220 | 0 | const char *compat; |
221 | |
|
222 | 0 | serialno = barebox_get_serial_number(); |
223 | 0 | if (serialno) |
224 | 0 | of_property_write_string(root, "serial-number", serialno); |
225 | |
|
226 | 0 | compat = barebox_get_of_machine_compatible(); |
227 | 0 | if (compat) |
228 | 0 | of_prepend_machine_compatible(root, compat); |
229 | |
|
230 | 0 | node = of_create_node(root, "/chosen"); |
231 | 0 | if (!node) |
232 | 0 | return -ENOMEM; |
233 | | |
234 | 0 | of_property_write_string(node, "barebox-version", release_string); |
235 | |
|
236 | 0 | err = of_write_bootargs(node); |
237 | 0 | if (err) |
238 | 0 | return err; |
239 | | |
240 | 0 | of_property_write_string(node, "reset-source", reset_source_name()); |
241 | 0 | if (instance >= 0) |
242 | 0 | of_property_write_u32(node, "reset-source-instance", instance); |
243 | | |
244 | |
|
245 | 0 | err = of_fixup_bootargs_reset_source(root, node); |
246 | 0 | if (err) |
247 | 0 | return err; |
248 | | |
249 | 0 | err = of_fixup_bootargs_bootsource(root, node); |
250 | 0 | if (err) |
251 | 0 | return err; |
252 | | |
253 | 0 | if (IS_ENABLED(CONFIG_RISCV)) { |
254 | 0 | const char *hartid; |
255 | |
|
256 | 0 | hartid = getenv("global.hartid"); |
257 | 0 | if (hartid) { |
258 | 0 | unsigned long id; |
259 | |
|
260 | 0 | err = kstrtoul(hartid, 10, &id); |
261 | 0 | if (!err) |
262 | 0 | err = of_property_write_u32(node, "boot-hartid", id); |
263 | 0 | } |
264 | 0 | } |
265 | |
|
266 | 0 | return err; |
267 | 0 | } |
268 | | |
269 | | static int of_register_bootargs_fixup(void) |
270 | 26 | { |
271 | 26 | globalvar_add_simple_bool("linux.bootargs_append", &bootargs_append); |
272 | 26 | return of_register_fixup(of_fixup_bootargs, NULL); |
273 | 26 | } |
274 | | late_initcall(of_register_bootargs_fixup); |
275 | | |
276 | | int of_fixup_reserved_memory(struct device_node *root, void *_res) |
277 | 0 | { |
278 | 0 | struct resource *res = _res; |
279 | 0 | struct device_node *node, *child; |
280 | 0 | struct property *pp; |
281 | 0 | unsigned addr_n_cells = sizeof(void *) / sizeof(__be32), |
282 | 0 | size_n_cells = sizeof(void *) / sizeof(__be32); |
283 | 0 | unsigned rangelen = 0; |
284 | 0 | __be32 reg[4]; |
285 | 0 | int ret; |
286 | |
|
287 | 0 | node = of_get_child_by_name(root, "reserved-memory") ?: of_new_node(root, "reserved-memory"); |
288 | |
|
289 | 0 | ret = of_property_read_u32(node, "#address-cells", &addr_n_cells); |
290 | 0 | if (ret) |
291 | 0 | of_property_write_u32(node, "#address-cells", addr_n_cells); |
292 | |
|
293 | 0 | ret = of_property_read_u32(node, "#size-cells", &size_n_cells); |
294 | 0 | if (ret) |
295 | 0 | of_property_write_u32(node, "#size-cells", size_n_cells); |
296 | |
|
297 | 0 | pp = of_find_property(node, "ranges", &rangelen); |
298 | 0 | if (!pp) { |
299 | 0 | of_new_property(node, "ranges", NULL, 0); |
300 | 0 | } else if (rangelen) { |
301 | 0 | pr_warn("reserved-memory ranges not 1:1 mapped. Aborting fixup\n"); |
302 | 0 | return -EINVAL; |
303 | 0 | } |
304 | | |
305 | 0 | child = of_get_child_by_name(node, res->name) ?: of_new_node(node, res->name); |
306 | |
|
307 | 0 | if (is_reserved_resource(res)) |
308 | 0 | of_property_write_bool(child, "no-map", true); |
309 | |
|
310 | 0 | of_write_number(reg, res->start, addr_n_cells); |
311 | 0 | of_write_number(reg + addr_n_cells, resource_size(res), size_n_cells); |
312 | |
|
313 | 0 | of_set_property(child, "reg", reg, |
314 | 0 | (addr_n_cells + size_n_cells) * sizeof(*reg), true); |
315 | |
|
316 | 0 | return 0; |
317 | 0 | } |
318 | | |
319 | | struct of_fixup_status_data { |
320 | | const char *path; |
321 | | bool status; |
322 | | }; |
323 | | |
324 | | static int of_fixup_status(struct device_node *root, void *context) |
325 | 0 | { |
326 | 0 | const struct of_fixup_status_data *data = context; |
327 | 0 | struct device_node *node; |
328 | |
|
329 | 0 | node = of_find_node_by_path_or_alias(root, data->path); |
330 | 0 | if (!node) |
331 | 0 | return -ENODEV; |
332 | | |
333 | 0 | if (data->status) |
334 | 0 | return of_device_enable(node); |
335 | 0 | else |
336 | 0 | return of_device_disable(node); |
337 | 0 | } |
338 | | |
339 | | /** |
340 | | * of_register_set_status_fixup - register fix up to set status of nodes |
341 | | * Register a fixup to enable or disable a node in the devicet tree by |
342 | | * passing the path or alias. |
343 | | */ |
344 | | int of_register_set_status_fixup(const char *path, bool status) |
345 | 0 | { |
346 | 0 | struct of_fixup_status_data *data; |
347 | |
|
348 | 0 | data = xzalloc(sizeof(*data)); |
349 | 0 | data->path = path; |
350 | 0 | data->status = status; |
351 | |
|
352 | 0 | return of_register_fixup(of_fixup_status, (void *)data); |
353 | 0 | } |
354 | | |
355 | | LIST_HEAD(of_fixup_list); |
356 | | |
357 | | static inline bool of_fixup_disabled(struct of_fixup *fixup) |
358 | 26 | { |
359 | 26 | return fixup->disabled; |
360 | 26 | } |
361 | | |
362 | | int of_register_fixup(int (*fixup)(struct device_node *, void *), void *context) |
363 | 559 | { |
364 | 559 | struct of_fixup *of_fixup = xzalloc(sizeof(*of_fixup)); |
365 | | |
366 | 559 | of_fixup->fixup = fixup; |
367 | 559 | of_fixup->context = context; |
368 | | |
369 | 559 | list_add_tail(&of_fixup->list, &of_fixup_list); |
370 | | |
371 | 559 | return 0; |
372 | 559 | } |
373 | | |
374 | | /* |
375 | | * Remove a previously registered fixup. Only the first (if any) is removed. |
376 | | * Returns 0 if a match was found (and removed), -ENOENT otherwise. |
377 | | */ |
378 | | int of_unregister_fixup(int (*fixup)(struct device_node *, void *), |
379 | | void *context) |
380 | 674 | { |
381 | 674 | struct of_fixup *of_fixup; |
382 | | |
383 | 5.09k | list_for_each_entry(of_fixup, &of_fixup_list, list) { |
384 | 5.09k | if (of_fixup->fixup == fixup && of_fixup->context == context) { |
385 | 377 | list_del(&of_fixup->list); |
386 | 377 | free(of_fixup); |
387 | 377 | return 0; |
388 | 377 | } |
389 | 5.09k | } |
390 | | |
391 | 297 | return -ENOENT; |
392 | 674 | } |
393 | | |
394 | | /* |
395 | | * Apply registered fixups for the given fdt. The fdt must have |
396 | | * enough free space to apply the fixups. |
397 | | */ |
398 | | void of_fix_tree(struct device_node *node) |
399 | 26 | { |
400 | 26 | struct of_fixup *of_fixup; |
401 | 26 | int ret; |
402 | | |
403 | 26 | of_overlay_load_firmware_clear(); |
404 | | |
405 | 26 | list_for_each_entry(of_fixup, &of_fixup_list, list) { |
406 | 26 | if (of_fixup_disabled(of_fixup)) |
407 | 0 | continue; |
408 | | |
409 | 26 | ret = of_fixup->fixup(node, of_fixup->context); |
410 | 26 | if (ret) |
411 | 26 | pr_warn("Failed to fixup node in %pS: %pe\n", |
412 | 26 | of_fixup->fixup, ERR_PTR(ret)); |
413 | 26 | } |
414 | 26 | } |
415 | | |
416 | | /* |
417 | | * Get the fdt. This function uses the fdt input pointer |
418 | | * if provided or the barebox internal devicetree if not. |
419 | | */ |
420 | | struct fdt_header *of_get_flattened_tree(const struct device_node *node, |
421 | | bool fixup) |
422 | 0 | { |
423 | 0 | struct fdt_header *fdt = NULL; |
424 | 0 | struct device_node *np = NULL; |
425 | |
|
426 | 0 | if (!node) { |
427 | 0 | node = of_get_root_node(); |
428 | 0 | if (!node) |
429 | 0 | return NULL; |
430 | 0 | } |
431 | | |
432 | 0 | if (fixup) |
433 | 0 | node = np = of_dup(node); |
434 | |
|
435 | 0 | if (!node) |
436 | 0 | return NULL; |
437 | | |
438 | 0 | if (fixup) |
439 | 0 | of_fix_tree(np); |
440 | |
|
441 | 0 | fdt = of_flatten_dtb((struct device_node *)node); |
442 | |
|
443 | 0 | if (fixup) |
444 | 0 | of_delete_node(np); |
445 | |
|
446 | 0 | return fdt; |
447 | 0 | } |
448 | | |
449 | | /** |
450 | | * of_autoenable_device_by_path() - Autoenable a device by a device tree path |
451 | | * @param path Device tree path up from the root to the device |
452 | | * @return 0 on success, -enodev on failure. If no device found in the device |
453 | | * tree. |
454 | | * |
455 | | * This function will search for a device and will enable it in the kernel |
456 | | * device tree, if it exists and is loaded. |
457 | | */ |
458 | | int of_autoenable_device_by_path(char *path) |
459 | 0 | { |
460 | 0 | struct device_node *node; |
461 | 0 | int ret; |
462 | |
|
463 | 0 | node = of_find_node_by_name_address(NULL, path); |
464 | 0 | if (!node) |
465 | 0 | node = of_find_node_by_path(path); |
466 | |
|
467 | 0 | if (!node) |
468 | 0 | return -ENODEV; |
469 | | |
470 | 0 | if (!of_device_is_available(node)) |
471 | 0 | return -ENODEV; |
472 | | |
473 | 0 | ret = of_register_set_status_fixup(path, 1); |
474 | 0 | if (!ret) |
475 | 0 | printf("autoenabled %s\n", node->name); |
476 | 0 | return ret; |
477 | 0 | } |
478 | | |
479 | | /** |
480 | | * of_autoenable_i2c_by_component - Autoenable a i2c client by a device tree path |
481 | | * @param path Device tree path up from the root to the i2c client |
482 | | * @return 0 on success, -enodev on failure. If no i2c client found in the i2c |
483 | | * device tree. |
484 | | * |
485 | | * This function will search for a i2c client, tries to write to the client and |
486 | | * will enable it in the kernel device tree, if it exists and is accessible. |
487 | | */ |
488 | | int of_autoenable_i2c_by_component(char *path) |
489 | 0 | { |
490 | 0 | struct device_node *node; |
491 | 0 | struct i2c_adapter *i2c_adapter; |
492 | 0 | struct i2c_msg msg; |
493 | 0 | char data[1] = {0x0}; |
494 | 0 | int ret; |
495 | 0 | uint32_t addr; |
496 | |
|
497 | 0 | if (!IS_ENABLED(CONFIG_I2C)) |
498 | 0 | return -ENODEV; |
499 | | |
500 | 0 | node = of_find_node_by_name_address(NULL, path); |
501 | 0 | if (!node) |
502 | 0 | node = of_find_node_by_path(path); |
503 | 0 | if (!node) |
504 | 0 | return -ENODEV; |
505 | 0 | if (!node->parent) |
506 | 0 | return -ENODEV; |
507 | | |
508 | 0 | ret = of_property_read_u32(node, "reg", &addr); |
509 | 0 | if (ret) |
510 | 0 | return -ENODEV; |
511 | | |
512 | 0 | i2c_adapter = of_find_i2c_adapter_by_node(node->parent); |
513 | 0 | if (!i2c_adapter) |
514 | 0 | return -ENODEV; |
515 | | |
516 | 0 | msg.buf = data; |
517 | 0 | msg.addr = addr; |
518 | 0 | msg.len = 1; |
519 | | |
520 | | /* Try to communicate with the i2c client */ |
521 | 0 | ret = i2c_transfer(i2c_adapter, &msg, 1); |
522 | 0 | if (ret == -EREMOTEIO) |
523 | 0 | return -ENODEV; |
524 | 0 | if (ret < 1) { |
525 | 0 | printf("failed to autoenable i2c device on address 0x%x with %i\n", |
526 | 0 | addr, ret); |
527 | 0 | return ret; |
528 | 0 | } |
529 | | |
530 | 0 | ret = of_register_set_status_fixup(path, 1); |
531 | 0 | if (!ret) |
532 | 0 | printf("autoenabled i2c device %s\n", node->name); |
533 | |
|
534 | 0 | return ret; |
535 | 0 | } |
536 | | |
537 | | int of_prepend_machine_compatible(struct device_node *root, const char *compat) |
538 | 0 | { |
539 | 0 | int cclen = 0, clen = strlen(compat) + 1; |
540 | 0 | const char *curcompat; |
541 | 0 | void *buf; |
542 | |
|
543 | 0 | if (!root) { |
544 | 0 | root = of_get_root_node(); |
545 | 0 | if (!root) |
546 | 0 | return -ENODEV; |
547 | 0 | } |
548 | | |
549 | 0 | if (of_device_is_compatible(root, compat)) |
550 | 0 | return 0; |
551 | | |
552 | 0 | curcompat = of_get_property(root, "compatible", &cclen); |
553 | 0 | if (!curcompat) |
554 | 0 | return -ENOENT; |
555 | | |
556 | 0 | buf = xzalloc(cclen + clen); |
557 | |
|
558 | 0 | memcpy(buf, compat, clen); |
559 | |
|
560 | 0 | if (curcompat) |
561 | 0 | memcpy(buf + clen, curcompat, cclen); |
562 | |
|
563 | 0 | of_set_property(root, "compatible", buf, cclen + clen, true); |
564 | |
|
565 | 0 | free(buf); |
566 | |
|
567 | 0 | return 0; |
568 | 0 | } |