/src/barebox/common/startup.c
Line | Count | Source |
1 | | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | | /* |
3 | | * (C) Copyright 2002-2006 |
4 | | * Wolfgang Denk, DENX Software Engineering, wd@denx.de. |
5 | | * |
6 | | * (C) Copyright 2002 |
7 | | * Sysgo Real-Time Solutions, GmbH <www.elinos.com> |
8 | | * Marius Groeger <mgroeger@sysgo.de> |
9 | | */ |
10 | | |
11 | | #ifdef CONFIG_DEBUG_INITCALLS |
12 | | #define DEBUG |
13 | | #endif |
14 | | |
15 | | /** |
16 | | * @file |
17 | | * @brief Main entry into the C part of barebox |
18 | | */ |
19 | | #include <common.h> |
20 | | #include <shell.h> |
21 | | #include <init.h> |
22 | | #include <command.h> |
23 | | #include <poweroff.h> |
24 | | #include <malloc.h> |
25 | | #include <debug_ll.h> |
26 | | #include <fs.h> |
27 | | #include <errno.h> |
28 | | #include <slice.h> |
29 | | #include <linux/stat.h> |
30 | | #include <envfs.h> |
31 | | #include <magicvar.h> |
32 | | #include <readkey.h> |
33 | | #include <linux/reboot-mode.h> |
34 | | #include <asm/sections.h> |
35 | | #include <libfile.h> |
36 | | #include <uncompress.h> |
37 | | #include <globalvar.h> |
38 | | #include <console_countdown.h> |
39 | | #include <environment.h> |
40 | | #include <linux/ctype.h> |
41 | | #include <watchdog.h> |
42 | | #include <glob.h> |
43 | | #include <net.h> |
44 | | #include <efi/mode.h> |
45 | | #include <bselftest.h> |
46 | | #include <pbl/handoff-data.h> |
47 | | #include <libfile.h> |
48 | | #include <fuzz.h> |
49 | | #include <of.h> |
50 | | |
51 | | extern initcall_t __barebox_initcalls_start[], __barebox_early_initcalls_end[], |
52 | | __barebox_initcalls_end[]; |
53 | | |
54 | | extern exitcall_t __barebox_exitcalls_start[], __barebox_exitcalls_end[]; |
55 | | |
56 | | enum system_states barebox_system_state; |
57 | | |
58 | | #if defined CONFIG_FS_RAMFS && defined CONFIG_FS_DEVFS |
59 | | static int mount_root(void) |
60 | 26 | { |
61 | 26 | mount("none", "ramfs", "/", NULL); |
62 | 26 | mkdir("/dev", 0); |
63 | 26 | mkdir("/tmp", 0); |
64 | 26 | mkdir("/mnt", 0); |
65 | 26 | devfs_init(); |
66 | | |
67 | 26 | if (IS_ENABLED(CONFIG_FS_EFIVARFS)) { |
68 | 0 | mkdir("/efivars", 0); |
69 | 0 | automount_add("/efivars", "mount -t efivarfs none /efivars"); |
70 | 0 | } |
71 | | |
72 | 26 | if (IS_ENABLED(CONFIG_FS_PSTORE)) { |
73 | 26 | mkdir("/pstore", 0); |
74 | 26 | mount("none", "pstore", "/pstore", NULL); |
75 | 26 | } |
76 | | |
77 | 26 | if (IS_ENABLED(CONFIG_9P_FS)) |
78 | 0 | mkdir("/mnt/9p", 0); |
79 | | |
80 | 26 | if (IS_ENABLED(CONFIG_FS_SMHFS)) { |
81 | 0 | mkdir("/mnt/smhfs", 0); |
82 | 0 | automount_add("/mnt/smhfs", "mount -t smhfs /dev/null /mnt/smhfs"); |
83 | 0 | } |
84 | | |
85 | 26 | return 0; |
86 | 26 | } |
87 | | fs_initcall(mount_root); |
88 | | #endif |
89 | | |
90 | | static bool may_autoload_external_env = IS_ENABLED(CONFIG_ENV_HANDLING); |
91 | | |
92 | | #ifdef CONFIG_ENV_HANDLING |
93 | | void autoload_external_env(bool endis) |
94 | 0 | { |
95 | 0 | may_autoload_external_env = endis; |
96 | 0 | } |
97 | | #endif |
98 | | |
99 | | static void env_merge_of_compat_overlays(const char *dir) |
100 | 26 | { |
101 | 26 | struct device_node *root; |
102 | 26 | const char *compat; |
103 | 26 | struct stat s; |
104 | 26 | char *ofdir, *src; |
105 | 26 | int count; |
106 | | |
107 | 26 | root = of_get_root_node(); |
108 | 26 | if (!root) |
109 | 0 | return; |
110 | | |
111 | 26 | ofdir = xasprintf("%s/match.of_compatible", dir); |
112 | | |
113 | 26 | if (stat(ofdir, &s) || !S_ISDIR(s.st_mode)) |
114 | 0 | goto out; |
115 | | |
116 | 26 | count = of_property_count_strings(root, "compatible"); |
117 | | |
118 | | /* |
119 | | * Apply overlays from most generic to most specific, so that |
120 | | * more specific entries take precedence. |
121 | | */ |
122 | 52 | for (int i = count - 1; i >= 0; i--) { |
123 | 26 | if (of_property_read_string_index(root, "compatible", |
124 | 26 | i, &compat)) |
125 | 0 | continue; |
126 | | |
127 | 26 | src = xasprintf("%s/%s", ofdir, compat); |
128 | | |
129 | 26 | if (stat(src, &s) == 0 && S_ISDIR(s.st_mode)) { |
130 | 26 | pr_debug("Applying '%s'-compatible environment overlay\n", |
131 | 26 | compat); |
132 | 26 | copy_recursive(src, dir, 0); |
133 | 26 | } |
134 | | |
135 | 26 | free(src); |
136 | 26 | } |
137 | | |
138 | 26 | unlink_recursive(ofdir, NULL); |
139 | 26 | out: |
140 | 26 | free(ofdir); |
141 | 26 | } |
142 | | |
143 | | static int load_environment(void) |
144 | 26 | { |
145 | 26 | const char *default_environment_path; |
146 | 26 | int __maybe_unused ret = 0; |
147 | | |
148 | 26 | default_environment_path = default_environment_path_get(); |
149 | | |
150 | 26 | if (IS_ENABLED(CONFIG_DEFAULT_ENVIRONMENT)) { |
151 | 26 | ret = defaultenv_load("/env", 0); |
152 | 26 | if (ret) |
153 | 26 | pr_warn("Failed loading (some) defaultenv overlays: %pe\n", |
154 | 26 | ERR_PTR(ret)); |
155 | 26 | } |
156 | | |
157 | 26 | if (may_autoload_external_env) |
158 | 26 | envfs_load(default_environment_path, "/env", 0); |
159 | 0 | else if (IS_ENABLED(CONFIG_DEFAULT_ENVIRONMENT)) |
160 | 0 | pr_info("external environment support %s. Using default environment\n", |
161 | 26 | IS_ENABLED(CONFIG_ENV_HANDLING) ? "disallowed" : "disabled"); |
162 | | |
163 | 26 | env_merge_of_compat_overlays("/env"); |
164 | | |
165 | 26 | nvvar_load(); |
166 | | |
167 | 26 | return 0; |
168 | 26 | } |
169 | | environment_initcall(load_environment); |
170 | | |
171 | | static char *global_autoboot_abort_key; |
172 | | static int global_autoboot_timeout = 3; |
173 | | |
174 | | static const char * const global_autoboot_states[] = { |
175 | | [AUTOBOOT_COUNTDOWN] = "countdown", |
176 | | [AUTOBOOT_ABORT] = "abort", |
177 | | [AUTOBOOT_HALT] = "halt", |
178 | | [AUTOBOOT_MENU] = "menu", |
179 | | [AUTOBOOT_BOOT] = "boot", |
180 | | }; |
181 | | static int global_autoboot_state = AUTOBOOT_COUNTDOWN; |
182 | | |
183 | | static bool test_abort(void) |
184 | 0 | { |
185 | 0 | bool do_abort = false; |
186 | 0 | int c, ret; |
187 | 0 | char key; |
188 | |
|
189 | 0 | while (tstc()) { |
190 | 0 | c = getchar(); |
191 | 0 | if (tolower(c) == 'q' || c == 3) |
192 | 0 | do_abort = true; |
193 | 0 | } |
194 | |
|
195 | 0 | if (!do_abort) |
196 | 0 | return false; |
197 | | |
198 | 0 | printf("Abort init sequence? (y/n)\n" |
199 | 0 | "Will continue with init sequence in:"); |
200 | |
|
201 | 0 | ret = console_countdown(5, CONSOLE_COUNTDOWN_EXTERN, "yYnN", &key); |
202 | 0 | if (!ret) |
203 | 0 | return false; |
204 | | |
205 | 0 | if (tolower(key) == 'y') |
206 | 0 | return true; |
207 | | |
208 | 0 | return false; |
209 | 0 | } |
210 | | |
211 | 0 | #define INITFILE "/env/bin/init" |
212 | 0 | #define MENUFILE "/env/menu/mainmenu" |
213 | | |
214 | | /** |
215 | | * set_autoboot_state - set the autoboot state |
216 | | * @autoboot: the state to set |
217 | | * |
218 | | * This functions sets the autoboot state to the given state. This can be called |
219 | | * by boards which have its own idea when and how the autoboot shall be aborted. |
220 | | */ |
221 | | void set_autoboot_state(enum autoboot_state autoboot) |
222 | 0 | { |
223 | 0 | global_autoboot_state = autoboot; |
224 | 0 | } |
225 | | |
226 | | /** |
227 | | * do_autoboot_countdown - print autoboot countdown to console |
228 | | * |
229 | | * This prints the autoboot countdown to the console and waits for input. This |
230 | | * evaluates the global.autoboot_abort_key to determine which keys are allowed |
231 | | * to interrupt booting and also global.autoboot_timeout to determine the timeout |
232 | | * for the counter. This function can be called multiple times, it is executed |
233 | | * only the first time. |
234 | | * |
235 | | * Returns an enum autoboot_state value containing the user input. |
236 | | */ |
237 | | enum autoboot_state do_autoboot_countdown(void) |
238 | 0 | { |
239 | 0 | static enum autoboot_state autoboot_state = AUTOBOOT_UNKNOWN; |
240 | 0 | unsigned flags = CONSOLE_COUNTDOWN_EXTERN; |
241 | 0 | int ret; |
242 | 0 | struct stat s; |
243 | 0 | bool menu_exists; |
244 | 0 | char abortkeys[3] = {}; |
245 | 0 | unsigned char outkey; |
246 | |
|
247 | 0 | if (autoboot_state != AUTOBOOT_UNKNOWN) |
248 | 0 | return autoboot_state; |
249 | | |
250 | 0 | if (!console_get_first_interactive() && |
251 | 0 | global_autoboot_state != AUTOBOOT_ABORT && |
252 | 0 | global_autoboot_state != AUTOBOOT_HALT) { |
253 | 0 | printf("\nNon-interactive console, booting system\n"); |
254 | 0 | return autoboot_state = AUTOBOOT_BOOT; |
255 | 0 | } |
256 | | |
257 | 0 | if (global_autoboot_state != AUTOBOOT_COUNTDOWN) |
258 | 0 | return global_autoboot_state; |
259 | | |
260 | 0 | menu_exists = stat(MENUFILE, &s) == 0; |
261 | |
|
262 | 0 | if (menu_exists) |
263 | 0 | strcat(abortkeys, "m"); |
264 | |
|
265 | 0 | if (!global_autoboot_abort_key || |
266 | 0 | !strcmp(global_autoboot_abort_key, "any")) |
267 | 0 | flags |= CONSOLE_COUNTDOWN_ANYKEY; |
268 | 0 | else if (!strcmp(global_autoboot_abort_key, "ctrl-c")) |
269 | 0 | flags |= CONSOLE_COUNTDOWN_CTRLC; |
270 | 0 | else |
271 | 0 | strcat(abortkeys, global_autoboot_abort_key); |
272 | |
|
273 | 0 | printf("\nHit %s%s to stop autoboot: ", |
274 | 0 | menu_exists ? "m for menu or " : "", |
275 | 0 | global_autoboot_abort_key ? global_autoboot_abort_key : "any"); |
276 | |
|
277 | 0 | command_slice_release(); |
278 | 0 | ret = console_countdown(global_autoboot_timeout, flags, abortkeys, |
279 | 0 | &outkey); |
280 | 0 | command_slice_acquire(); |
281 | |
|
282 | 0 | if (ret == 0) |
283 | 0 | autoboot_state = AUTOBOOT_BOOT; |
284 | 0 | else if (menu_exists && outkey == 'm') |
285 | 0 | autoboot_state = AUTOBOOT_MENU; |
286 | 0 | else if (outkey == CTL_CH('d')) |
287 | 0 | autoboot_state = AUTOBOOT_HALT; |
288 | 0 | else |
289 | 0 | autoboot_state = AUTOBOOT_ABORT; |
290 | |
|
291 | 0 | return autoboot_state; |
292 | 0 | } |
293 | | |
294 | | static int autoboot_abort_key_set(struct param_d *p, void *priv) |
295 | 0 | { |
296 | 0 | if (!strcmp(global_autoboot_abort_key, "any")) |
297 | 0 | return 0; |
298 | 0 | if (!strcmp(global_autoboot_abort_key, "ctrl-c")) |
299 | 0 | return 0; |
300 | | |
301 | 0 | if (strlen(global_autoboot_abort_key) != 1) |
302 | 0 | return -EINVAL; |
303 | | |
304 | 0 | return 0; |
305 | 0 | } |
306 | | |
307 | | static int register_autoboot_vars(void) |
308 | 26 | { |
309 | 26 | globalvar_add_param_string("autoboot_abort_key", |
310 | 26 | autoboot_abort_key_set, |
311 | 26 | NULL, |
312 | 26 | &global_autoboot_abort_key, |
313 | 26 | NULL); |
314 | 26 | globalvar_add_simple_int("autoboot_timeout", |
315 | 26 | &global_autoboot_timeout, "%u"); |
316 | 26 | globalvar_add_simple_enum("autoboot", |
317 | 26 | &global_autoboot_state, |
318 | 26 | global_autoboot_states, |
319 | 26 | ARRAY_SIZE(global_autoboot_states)); |
320 | | |
321 | 26 | return 0; |
322 | 26 | } |
323 | | postcore_initcall(register_autoboot_vars); |
324 | | |
325 | | static enum autoboot_state current_autoboot = AUTOBOOT_UNKNOWN; |
326 | | |
327 | | /** |
328 | | * get_autoboot_state - get the autoboot state |
329 | | * |
330 | | * This functions returns the autoboot state last used. |
331 | | */ |
332 | | enum autoboot_state get_autoboot_state(void) |
333 | 0 | { |
334 | 0 | return current_autoboot; |
335 | 0 | } |
336 | | |
337 | | static int run_init(void) |
338 | 0 | { |
339 | 0 | const char *bmode, *cmdline; |
340 | 0 | bool env_bin_init_exists; |
341 | 0 | struct stat s; |
342 | 0 | glob_t g; |
343 | 0 | int i, ret; |
344 | 0 | size_t size; |
345 | 0 | void *ext_dtb; |
346 | |
|
347 | 0 | setenv("PATH", "/env/bin"); |
348 | 0 | export("PATH"); |
349 | | |
350 | | /* Run legacy /env/bin/init if it exists */ |
351 | 0 | env_bin_init_exists = stat(INITFILE, &s) == 0; |
352 | 0 | if (env_bin_init_exists) { |
353 | 0 | pr_info("running legacy %s...\n", INITFILE); |
354 | 0 | run_command("source " INITFILE); |
355 | 0 | return 0; |
356 | 0 | } |
357 | | |
358 | | /* Unblank console cursor */ |
359 | 0 | printf("\e[?25h"); |
360 | |
|
361 | 0 | if (test_abort()) { |
362 | 0 | pr_info("Init sequence aborted\n"); |
363 | 0 | return -EINTR; |
364 | 0 | } |
365 | | |
366 | | /* Run scripts in /env/init/ */ |
367 | 0 | ret = glob("/env/init/*", 0, NULL, &g); |
368 | 0 | if (!ret) { |
369 | 0 | for (i = 0; i < g.gl_pathc; i++) { |
370 | 0 | const char *path = g.gl_pathv[i]; |
371 | |
|
372 | 0 | ret = stat(path, &s); |
373 | 0 | if (ret) |
374 | 0 | continue; |
375 | | |
376 | 0 | if (!S_ISREG(s.st_mode)) |
377 | 0 | continue; |
378 | | |
379 | 0 | pr_debug("Executing '%s'...\n", path); |
380 | 0 | run_command("source %s", path); |
381 | 0 | } |
382 | |
|
383 | 0 | globfree(&g); |
384 | 0 | } |
385 | |
|
386 | 0 | cmdline = barebox_cmdline_get(); |
387 | 0 | if (cmdline) { |
388 | 0 | ret = write_file("/cmdline", cmdline, strlen(cmdline)); |
389 | 0 | if (ret) |
390 | 0 | return ret; |
391 | | |
392 | 0 | console_ctrlc_allow(); |
393 | 0 | run_command("source /cmdline"); |
394 | 0 | } |
395 | | |
396 | 0 | ext_dtb = handoff_data_get_entry(HANDOFF_DATA_EXTERNAL_DT, &size); |
397 | 0 | if (ext_dtb) |
398 | 0 | write_file("/external-devicetree", ext_dtb, size); |
399 | | |
400 | | /* source matching script in /env/bmode/ */ |
401 | 0 | bmode = reboot_mode_get(); |
402 | 0 | if (bmode) { |
403 | 0 | char *scr, *path; |
404 | |
|
405 | 0 | scr = xasprintf("source /env/bmode/%s", bmode); |
406 | 0 | path = &scr[strlen("source ")]; |
407 | 0 | if (stat(path, &s) == 0) { |
408 | 0 | pr_info("Invoking '%s'...\n", path); |
409 | 0 | run_command("%s", scr); |
410 | 0 | } |
411 | 0 | free(scr); |
412 | 0 | } |
413 | |
|
414 | 0 | current_autoboot = do_autoboot_countdown(); |
415 | |
|
416 | 0 | console_ctrlc_allow(); |
417 | |
|
418 | 0 | if (current_autoboot == AUTOBOOT_BOOT) |
419 | 0 | run_command("boot"); |
420 | |
|
421 | 0 | if (IS_ENABLED(CONFIG_NET) && !IS_ENABLED(CONFIG_CONSOLE_DISABLE_INPUT) && |
422 | 0 | current_autoboot != AUTOBOOT_HALT) |
423 | 0 | eth_open_all(); |
424 | |
|
425 | 0 | if (current_autoboot != AUTOBOOT_MENU) { |
426 | 0 | if (current_autoboot == AUTOBOOT_ABORT && |
427 | 0 | current_autoboot == global_autoboot_state) |
428 | 0 | watchdog_inhibit_all(); |
429 | |
|
430 | 0 | run_shell(); |
431 | 0 | } |
432 | |
|
433 | 0 | do { |
434 | | /* |
435 | | * Let's run the command once at least, so an error |
436 | | * message is printed if the file doesn't exist |
437 | | */ |
438 | 0 | run_command(MENUFILE); |
439 | 0 | } while (stat(MENUFILE, &s) == 0); |
440 | |
|
441 | 0 | hang(); |
442 | 0 | } |
443 | | |
444 | | int (*barebox_main)(void) |
445 | | = !IS_ENABLED(CONFIG_SHELL_NONE) && |
446 | | IS_ENABLED(CONFIG_COMMAND_SUPPORT) ? run_init : NULL; |
447 | | |
448 | | void __noreturn start_barebox(void) |
449 | 26 | { |
450 | 26 | initcall_t *initcall; |
451 | 26 | int result; |
452 | | |
453 | 26 | for (initcall = __barebox_initcalls_start; |
454 | 3.58k | initcall < __barebox_initcalls_end; initcall++) { |
455 | 3.56k | pr_debug("initcall-> %pS\n", *initcall); |
456 | 3.56k | result = (*initcall)(); |
457 | 3.56k | if (result) |
458 | 3.56k | pr_err("initcall %pS failed: %pe\n", *initcall, |
459 | 3.56k | ERR_PTR(result)); |
460 | 3.56k | } |
461 | | |
462 | 26 | barebox_system_state = BAREBOX_RUNNING; |
463 | 26 | pr_debug("initcalls done\n"); |
464 | | |
465 | | |
466 | 26 | if (IS_ENABLED(CONFIG_SELFTEST_AUTORUN)) |
467 | 0 | selftests_run(); |
468 | | |
469 | 26 | if (barebox_main) |
470 | 26 | barebox_main(); |
471 | | |
472 | 26 | if (IS_ENABLED(CONFIG_SHELL_NONE)) { |
473 | 0 | pr_crit("Nothing left to do\n"); |
474 | 0 | poweroff_machine(0); |
475 | 26 | } else { |
476 | 26 | while (1) |
477 | 0 | run_shell(); |
478 | 26 | } |
479 | 26 | } |
480 | | |
481 | | void __noreturn hang (void) |
482 | 0 | { |
483 | 0 | puts ("### ERROR ### Please RESET the board ###\n"); |
484 | 0 | for (;;); |
485 | 0 | } |
486 | | |
487 | | /* Everything needed to cleanly shutdown barebox. |
488 | | * Should be called before starting an OS to get |
489 | | * the devices into a clean state |
490 | | */ |
491 | | void shutdown_barebox(void) |
492 | 0 | { |
493 | 0 | exitcall_t *exitcall; |
494 | |
|
495 | 0 | barebox_system_state = BAREBOX_EXITING; |
496 | |
|
497 | 0 | for (exitcall = __barebox_exitcalls_start; |
498 | 0 | exitcall < __barebox_exitcalls_end; exitcall++) { |
499 | 0 | pr_debug("exitcall-> %pS\n", *exitcall); |
500 | 0 | (*exitcall)(); |
501 | 0 | } |
502 | |
|
503 | 0 | console_flush(); |
504 | 0 | } |
505 | | |
506 | | BAREBOX_MAGICVAR(global.autoboot, |
507 | | "Autoboot state. Possible values: countdown (default), abort, halt, menu, boot"); |
508 | | BAREBOX_MAGICVAR(global.autoboot_abort_key, |
509 | | "Which key allows to interrupt autoboot. Possible values: 'any', 'ctrl-c' or any other single ascii character."); |
510 | | BAREBOX_MAGICVAR(global.autoboot_timeout, |
511 | | "Timeout before autoboot starts in seconds"); |