1 // SPDX-License-Identifier: GPL-2.0+
2 /*
3  * 'bootflow' command
4  *
5  * Copyright 2021 Google LLC
6  * Written by Simon Glass <sjg@chromium.org>
7  */
8 
9 #include <bootdev.h>
10 #include <bootflow.h>
11 #include <bootm.h>
12 #include <bootstd.h>
13 #include <command.h>
14 #include <console.h>
15 #include <dm.h>
16 #include <env.h>
17 #include <expo.h>
18 #include <log.h>
19 #include <mapmem.h>
20 
21 /**
22  * report_bootflow_err() - Report where a bootflow failed
23  *
24  * When a bootflow does not make it to the 'loaded' state, something went wrong.
25  * Print a helpful message if there is an error
26  *
27  * @bflow: Bootflow to process
28  * @err: Error code (0 if none)
29  */
report_bootflow_err(struct bootflow * bflow,int err)30 static void report_bootflow_err(struct bootflow *bflow, int err)
31 {
32 	if (!err)
33 		return;
34 
35 	/* Indent out to 'Method' */
36 	printf("     ** ");
37 
38 	switch (bflow->state) {
39 	case BOOTFLOWST_BASE:
40 		printf("No media/partition found");
41 		break;
42 	case BOOTFLOWST_MEDIA:
43 		printf("No partition found");
44 		break;
45 	case BOOTFLOWST_PART:
46 		printf("No filesystem found");
47 		break;
48 	case BOOTFLOWST_FS:
49 		printf("File not found");
50 		break;
51 	case BOOTFLOWST_FILE:
52 		printf("File cannot be loaded");
53 		break;
54 	case BOOTFLOWST_READY:
55 		printf("Ready");
56 		break;
57 	case BOOTFLOWST_COUNT:
58 		break;
59 	}
60 
61 	printf(", err=%dE\n", err);
62 }
63 
64 /**
65  * show_bootflow() - Show the status of a bootflow
66  *
67  * @seq: Bootflow index
68  * @bflow: Bootflow to show
69  * @errors: True to show the error received, if any
70  */
show_bootflow(int index,struct bootflow * bflow,bool errors)71 static void show_bootflow(int index, struct bootflow *bflow, bool errors)
72 {
73 	printf("%3x  %-11s  %-6s  %-9.9s %4x  %-25.25s %s\n", index,
74 	       bflow->method->name, bootflow_state_get_name(bflow->state),
75 	       bflow->dev ? dev_get_uclass_name(dev_get_parent(bflow->dev)) :
76 	       "(none)", bflow->part, bflow->name, bflow->fname ?: "");
77 	if (errors)
78 		report_bootflow_err(bflow, bflow->err);
79 }
80 
show_header(void)81 static void show_header(void)
82 {
83 	printf("Seq  Method       State   Uclass    Part  Name                      Filename\n");
84 	printf("---  -----------  ------  --------  ----  ------------------------  ----------------\n");
85 }
86 
show_footer(int count,int num_valid)87 static void show_footer(int count, int num_valid)
88 {
89 	printf("---  -----------  ------  --------  ----  ------------------------  ----------------\n");
90 	printf("(%d bootflow%s, %d valid)\n", count, count != 1 ? "s" : "",
91 	       num_valid);
92 }
93 
94 /**
95  * bootflow_handle_menu() - Handle running the menu and updating cur bootflow
96  *
97  * This shows the menu, allows the user to select something and then prints
98  * what happened
99  *
100  * @std: bootstd information
101  * @text_mode: true to run the menu in text mode
102  * @bflowp: Returns selected bootflow, on success
103  * Return: 0 on success (a bootflow was selected), -EAGAIN if nothing was
104  *	chosen, other -ve value on other error
105  */
bootflow_handle_menu(struct bootstd_priv * std,bool text_mode,struct bootflow ** bflowp)106 __maybe_unused static int bootflow_handle_menu(struct bootstd_priv *std,
107 					       bool text_mode,
108 					       struct bootflow **bflowp)
109 {
110 	struct expo *exp;
111 	struct bootflow *bflow;
112 	int ret, seq;
113 
114 	ret = bootflow_menu_start(std, text_mode, &exp);
115 	if (ret)
116 		return log_msg_ret("bhs", ret);
117 
118 	ret = -ERESTART;
119 	do {
120 		if (ret == -ERESTART) {
121 			ret = expo_render(exp);
122 			if (ret)
123 				return log_msg_ret("bhr", ret);
124 		}
125 		ret = bootflow_menu_poll(exp, &seq);
126 	} while (ret == -EAGAIN || ret == -ERESTART);
127 
128 	if (ret == -EPIPE) {
129 		printf("Nothing chosen\n");
130 		std->cur_bootflow = NULL;
131 	} else if (ret) {
132 		printf("Menu failed (err=%d)\n", ret);
133 	} else {
134 		bflow = alist_getw(&std->bootflows, seq, struct bootflow);
135 		printf("Selected: %s\n", bflow->os_name ? bflow->os_name :
136 		       bflow->name);
137 		std->cur_bootflow = bflow;
138 		*bflowp = bflow;
139 	}
140 	expo_destroy(exp);
141 	if (ret)
142 		return ret;
143 
144 	return 0;
145 }
146 
do_bootflow_scan(struct cmd_tbl * cmdtp,int flag,int argc,char * const argv[])147 static int do_bootflow_scan(struct cmd_tbl *cmdtp, int flag, int argc,
148 			    char *const argv[])
149 {
150 	struct bootstd_priv *std;
151 	struct bootflow_iter iter;
152 	struct udevice *dev = NULL;
153 	struct bootflow bflow;
154 	bool all = false, boot = false, errors = false, no_global = false;
155 	bool list = false, no_hunter = false, menu = false, text_mode = false;
156 	int num_valid = 0;
157 	const char *label = NULL;
158 	bool has_args;
159 	int ret, i;
160 	int flags;
161 
162 	ret = bootstd_get_priv(&std);
163 	if (ret)
164 		return CMD_RET_FAILURE;
165 
166 	has_args = argc > 1 && *argv[1] == '-';
167 	if (IS_ENABLED(CONFIG_CMD_BOOTFLOW_FULL)) {
168 		if (has_args) {
169 			all = strchr(argv[1], 'a');
170 			boot = strchr(argv[1], 'b');
171 			errors = strchr(argv[1], 'e');
172 			no_global = strchr(argv[1], 'G');
173 			list = strchr(argv[1], 'l');
174 			no_hunter = strchr(argv[1], 'H');
175 			menu = strchr(argv[1], 'm');
176 			text_mode = strchr(argv[1], 't');
177 			argc--;
178 			argv++;
179 		}
180 		if (argc > 1)
181 			label = argv[1];
182 		if (!label)
183 			dev = std->cur_bootdev;
184 	} else {
185 		if (has_args) {
186 			printf("Flags not supported: enable CONFIG_BOOTSTD_FULL\n");
187 			return CMD_RET_USAGE;
188 		}
189 		boot = true;
190 	}
191 
192 	std->cur_bootflow = NULL;
193 
194 	flags = BOOTFLOWIF_ONLY_BOOTABLE;
195 	if (list)
196 		flags |= BOOTFLOWIF_SHOW;
197 	if (all)
198 		flags |= BOOTFLOWIF_ALL;
199 	if (no_global)
200 		flags |= BOOTFLOWIF_SKIP_GLOBAL;
201 	if (!no_hunter)
202 		flags |= BOOTFLOWIF_HUNT;
203 
204 	/*
205 	 * If we have a device, just scan for bootflows attached to that device
206 	 */
207 	if (list) {
208 		printf("Scanning for bootflows ");
209 		if (dev)
210 			printf("in bootdev '%s'\n", dev->name);
211 		else if (label)
212 			printf("with label '%s'\n", label);
213 		else
214 			printf("in all bootdevs\n");
215 		show_header();
216 	}
217 	if (dev)
218 		bootstd_clear_bootflows_for_bootdev(dev);
219 	else
220 		bootstd_clear_glob();
221 	for (i = 0,
222 	     ret = bootflow_scan_first(dev, label, &iter, flags, &bflow);
223 	     i < 1000 && ret != -ENODEV;
224 	     i++, ret = bootflow_scan_next(&iter, &bflow)) {
225 		bflow.err = ret;
226 		if (!ret)
227 			num_valid++;
228 		ret = bootstd_add_bootflow(&bflow);
229 		if (ret < 0) {
230 			printf("Out of memory\n");
231 			return CMD_RET_FAILURE;
232 		}
233 		if (list)
234 			show_bootflow(i, &bflow, errors);
235 		if (!menu && boot && !bflow.err)
236 			bootflow_run_boot(&iter, &bflow);
237 	}
238 	bootflow_iter_uninit(&iter);
239 	if (list)
240 		show_footer(i, num_valid);
241 
242 	if (IS_ENABLED(CONFIG_CMD_BOOTFLOW_FULL) && IS_ENABLED(CONFIG_EXPO)) {
243 		if (!num_valid && !list) {
244 			printf("No bootflows found; try again with -l\n");
245 		} else if (menu) {
246 			struct bootflow *sel_bflow;
247 
248 			ret = bootflow_handle_menu(std, text_mode, &sel_bflow);
249 			if (!ret && boot) {
250 				ret = console_clear();
251 				if (ret) {
252 					log_err("Failed to clear console: %dE\n",
253 						ret);
254 					return ret;
255 				}
256 
257 				bootflow_run_boot(NULL, sel_bflow);
258 			}
259 		}
260 	}
261 
262 	return 0;
263 }
264 
265 #ifdef CONFIG_CMD_BOOTFLOW_FULL
do_bootflow_list(struct cmd_tbl * cmdtp,int flag,int argc,char * const argv[])266 static int do_bootflow_list(struct cmd_tbl *cmdtp, int flag, int argc,
267 			    char *const argv[])
268 {
269 	struct bootstd_priv *std;
270 	struct udevice *dev;
271 	struct bootflow *bflow;
272 	int num_valid = 0;
273 	bool errors = false;
274 	int ret, i;
275 
276 	if (argc > 1 && *argv[1] == '-')
277 		errors = strchr(argv[1], 'e');
278 
279 	ret = bootstd_get_priv(&std);
280 	if (ret)
281 		return CMD_RET_FAILURE;
282 	dev = std->cur_bootdev;
283 
284 	/* If we have a device, just list bootflows attached to that device */
285 	if (dev) {
286 		printf("Showing bootflows for bootdev '%s'\n", dev->name);
287 		show_header();
288 		for (ret = bootdev_first_bootflow(dev, &bflow), i = 0;
289 		     !ret;
290 		     ret = bootdev_next_bootflow(&bflow), i++) {
291 			num_valid += bflow->state == BOOTFLOWST_READY;
292 			show_bootflow(i, bflow, errors);
293 		}
294 	} else {
295 		printf("Showing all bootflows\n");
296 		show_header();
297 		for (ret = bootflow_first_glob(&bflow), i = 0;
298 		     !ret;
299 		     ret = bootflow_next_glob(&bflow), i++) {
300 			num_valid += bflow->state == BOOTFLOWST_READY;
301 			show_bootflow(i, bflow, errors);
302 		}
303 	}
304 	show_footer(i, num_valid);
305 
306 	return 0;
307 }
308 
do_bootflow_select(struct cmd_tbl * cmdtp,int flag,int argc,char * const argv[])309 static int do_bootflow_select(struct cmd_tbl *cmdtp, int flag, int argc,
310 			      char *const argv[])
311 {
312 	struct bootstd_priv *std;
313 	struct bootflow *bflow, *found;
314 	struct udevice *dev;
315 	const char *name;
316 	char *endp;
317 	int seq, i;
318 	int ret;
319 
320 	ret = bootstd_get_priv(&std);
321 	if (ret)
322 		return CMD_RET_FAILURE;
323 ;
324 	if (argc < 2) {
325 		std->cur_bootflow = NULL;
326 		return 0;
327 	}
328 	dev = std->cur_bootdev;
329 
330 	name = argv[1];
331 	seq = simple_strtol(name, &endp, 16);
332 	found = NULL;
333 
334 	/*
335 	 * If we have a bootdev device, only allow selection of bootflows
336 	 * attached to that device
337 	 */
338 	if (dev) {
339 		for (ret = bootdev_first_bootflow(dev, &bflow), i = 0;
340 		     !ret;
341 		     ret = bootdev_next_bootflow(&bflow), i++) {
342 			if (*endp ? !strcmp(bflow->name, name) : i == seq) {
343 				found = bflow;
344 				break;
345 			}
346 		}
347 	} else {
348 		for (ret = bootflow_first_glob(&bflow), i = 0;
349 		     !ret;
350 		     ret = bootflow_next_glob(&bflow), i++) {
351 			if (*endp ? !strcmp(bflow->name, name) : i == seq) {
352 				found = bflow;
353 				break;
354 			}
355 		}
356 	}
357 
358 	if (!found) {
359 		printf("Cannot find bootflow '%s' ", name);
360 		if (dev)
361 			printf("in bootdev '%s' ", dev->name);
362 		printf("(err=%d)\n", ret);
363 		return CMD_RET_FAILURE;
364 	}
365 	std->cur_bootflow = found;
366 	if (IS_ENABLED(CONFIG_BOOTSTD_FULL)) {
367 		if (env_set("bootargs", found->cmdline)) {
368 			printf("Cannot set bootargs\n");
369 			return CMD_RET_FAILURE;
370 		}
371 	}
372 
373 	return 0;
374 }
375 
do_bootflow_info(struct cmd_tbl * cmdtp,int flag,int argc,char * const argv[])376 static int do_bootflow_info(struct cmd_tbl *cmdtp, int flag, int argc,
377 			    char *const argv[])
378 {
379 	struct bootstd_priv *std;
380 	struct bootflow *bflow;
381 	bool x86_setup = false;
382 	bool dump = false;
383 	int ret;
384 
385 	if (argc > 1 && *argv[1] == '-') {
386 		dump = strchr(argv[1], 'd');
387 		x86_setup = strchr(argv[1], 's');
388 	}
389 
390 	ret = bootstd_get_priv(&std);
391 	if (ret)
392 		return CMD_RET_FAILURE;
393 
394 	if (!std->cur_bootflow) {
395 		printf("No bootflow selected\n");
396 		return CMD_RET_FAILURE;
397 	}
398 	bflow = std->cur_bootflow;
399 
400 	if (IS_ENABLED(CONFIG_X86) && x86_setup) {
401 		zimage_dump(bflow->x86_setup, false);
402 
403 		return 0;
404 	}
405 
406 	printf("Name:      %s\n", bflow->name);
407 	printf("Device:    %s\n", bflow->dev->name);
408 	printf("Block dev: %s\n", bflow->blk ? bflow->blk->name : "(none)");
409 	printf("Method:    %s\n", bflow->method->name);
410 	printf("State:     %s\n", bootflow_state_get_name(bflow->state));
411 	printf("Partition: %d\n", bflow->part);
412 	printf("Subdir:    %s\n", bflow->subdir ? bflow->subdir : "(none)");
413 	printf("Filename:  %s\n", bflow->fname);
414 	printf("Buffer:    ");
415 	if (bflow->buf)
416 		printf("%lx\n", (ulong)map_to_sysmem(bflow->buf));
417 	else
418 		printf("(not loaded)\n");
419 	printf("Size:      %x (%d bytes)\n", bflow->size, bflow->size);
420 	printf("OS:        %s\n", bflow->os_name ? bflow->os_name : "(none)");
421 	printf("Cmdline:   ");
422 	if (bflow->cmdline)
423 		puts(bflow->cmdline);
424 	else
425 		puts("(none)");
426 	putc('\n');
427 	if (bflow->x86_setup)
428 		printf("X86 setup: %lx\n",
429 		       (ulong)map_to_sysmem(bflow->x86_setup));
430 	printf("Logo:      %s\n", bflow->logo ?
431 	       simple_xtoa((ulong)map_to_sysmem(bflow->logo)) : "(none)");
432 	if (bflow->logo) {
433 		printf("Logo size: %x (%d bytes)\n", bflow->logo_size,
434 		       bflow->logo_size);
435 	}
436 	printf("FDT:       %s\n", bflow->fdt_fname);
437 	if (bflow->fdt_fname) {
438 		printf("FDT size:  %x (%d bytes)\n", bflow->fdt_size,
439 		       bflow->fdt_size);
440 		printf("FDT addr:  %lx\n", bflow->fdt_addr);
441 	}
442 	printf("Error:     %d\n", bflow->err);
443 	if (dump && bflow->buf) {
444 		/* Set some sort of maximum on the size */
445 		int size = min(bflow->size, 10 << 10);
446 		int i;
447 
448 		printf("Contents:\n\n");
449 		for (i = 0; i < size; i++) {
450 			putc(bflow->buf[i]);
451 			if (!(i % 128) && ctrlc()) {
452 				printf("...interrupted\n");
453 				break;
454 			}
455 		}
456 	}
457 
458 	return 0;
459 }
460 
do_bootflow_read(struct cmd_tbl * cmdtp,int flag,int argc,char * const argv[])461 static int do_bootflow_read(struct cmd_tbl *cmdtp, int flag, int argc,
462 			    char *const argv[])
463 {
464 	struct bootstd_priv *std;
465 	struct bootflow *bflow;
466 	int ret;
467 
468 	ret = bootstd_get_priv(&std);
469 	if (ret)
470 		return CMD_RET_FAILURE;
471 
472 	/*
473 	 * Require a current bootflow. Users can use 'bootflow scan -b' to
474 	 * automatically scan and boot, if needed.
475 	 */
476 	if (!std->cur_bootflow) {
477 		printf("No bootflow selected\n");
478 		return CMD_RET_FAILURE;
479 	}
480 	bflow = std->cur_bootflow;
481 	ret = bootflow_read_all(bflow);
482 	if (ret) {
483 		printf("Failed: err=%dE\n", ret);
484 		return CMD_RET_FAILURE;
485 	}
486 
487 	return 0;
488 }
489 
do_bootflow_boot(struct cmd_tbl * cmdtp,int flag,int argc,char * const argv[])490 static int do_bootflow_boot(struct cmd_tbl *cmdtp, int flag, int argc,
491 			    char *const argv[])
492 {
493 	struct bootstd_priv *std;
494 	struct bootflow *bflow;
495 	int ret;
496 
497 	ret = bootstd_get_priv(&std);
498 	if (ret)
499 		return CMD_RET_FAILURE;
500 
501 	/*
502 	 * Require a current bootflow. Users can use 'bootflow scan -b' to
503 	 * automatically scan and boot, if needed.
504 	 */
505 	if (!std->cur_bootflow) {
506 		printf("No bootflow selected\n");
507 		return CMD_RET_FAILURE;
508 	}
509 	bflow = std->cur_bootflow;
510 	ret = bootflow_run_boot(NULL, bflow);
511 	if (ret)
512 		return CMD_RET_FAILURE;
513 
514 	return 0;
515 }
516 
do_bootflow_menu(struct cmd_tbl * cmdtp,int flag,int argc,char * const argv[])517 static int do_bootflow_menu(struct cmd_tbl *cmdtp, int flag, int argc,
518 			    char *const argv[])
519 {
520 	struct bootstd_priv *std;
521 	struct bootflow *bflow;
522 	bool text_mode = false;
523 	int ret;
524 
525 	if (!IS_ENABLED(CONFIG_EXPO)) {
526 		printf("Menu not supported\n");
527 		return CMD_RET_FAILURE;
528 	}
529 
530 	if (argc > 1 && *argv[1] == '-')
531 		text_mode = strchr(argv[1], 't');
532 
533 	ret = bootstd_get_priv(&std);
534 	if (ret)
535 		return CMD_RET_FAILURE;
536 
537 	ret = bootflow_handle_menu(std, text_mode, &bflow);
538 	if (ret)
539 		return CMD_RET_FAILURE;
540 
541 	return 0;
542 }
543 
do_bootflow_cmdline(struct cmd_tbl * cmdtp,int flag,int argc,char * const argv[])544 static int do_bootflow_cmdline(struct cmd_tbl *cmdtp, int flag, int argc,
545 			       char *const argv[])
546 {
547 	struct bootstd_priv *std;
548 	struct bootflow *bflow;
549 	const char *op, *arg, *val = NULL;
550 	int ret;
551 
552 	if (argc < 3)
553 		return CMD_RET_USAGE;
554 
555 	ret = bootstd_get_priv(&std);
556 	if (ret)
557 		return CMD_RET_FAILURE;
558 
559 	bflow = std->cur_bootflow;
560 	if (!bflow) {
561 		printf("No bootflow selected\n");
562 		return CMD_RET_FAILURE;
563 	}
564 
565 	op = argv[1];
566 	arg = argv[2];
567 	if (*op == 's') {
568 		val = argv[3] ?: (const char *)BOOTFLOWCL_EMPTY;
569 	}
570 
571 	switch (*op) {
572 	case 'c':	/* clear */
573 		val = "";
574 		fallthrough;
575 	case 's':	/* set */
576 	case 'd':	/* delete */
577 		ret = bootflow_cmdline_set_arg(bflow, arg, val, true);
578 		break;
579 	case 'g':	/* get */
580 		ret = bootflow_cmdline_get_arg(bflow, arg, &val);
581 		if (ret >= 0)
582 			printf("%.*s\n", ret, val);
583 		break;
584 	case 'a':	/* auto */
585 		ret = bootflow_cmdline_auto(bflow, arg);
586 		break;
587 	}
588 	switch (ret) {
589 	case -E2BIG:
590 		printf("Argument too long\n");
591 		break;
592 	case -ENOENT:
593 		printf("Argument not found\n");
594 		break;
595 	case -EINVAL:
596 		printf("Mismatched quotes\n");
597 		break;
598 	case -EBADF:
599 		printf("Value must be quoted\n");
600 		break;
601 	default:
602 		if (ret < 0)
603 			printf("Unknown error: %dE\n", ret);
604 	}
605 	if (ret < 0)
606 		return CMD_RET_FAILURE;
607 
608 	return 0;
609 }
610 #endif /* CONFIG_CMD_BOOTFLOW_FULL */
611 
612 U_BOOT_LONGHELP(bootflow,
613 #ifdef CONFIG_CMD_BOOTFLOW_FULL
614 	"scan [-abeGl] [bdev]  - scan for valid bootflows (-l list, -a all, -e errors, -b boot, -G no global)\n"
615 	"bootflow list [-e]             - list scanned bootflows (-e errors)\n"
616 	"bootflow select [<num>|<name>] - select a bootflow\n"
617 	"bootflow info [-ds]            - show info on current bootflow (-d dump bootflow)\n"
618 	"bootflow read                  - read all current-bootflow files\n"
619 	"bootflow boot                  - boot current bootflow\n"
620 	"bootflow menu [-t]             - show a menu of available bootflows\n"
621 	"bootflow cmdline [set|get|clear|delete|auto] <param> [<value>] - update cmdline"
622 #else
623 	"scan - boot first available bootflow\n"
624 #endif
625 	);
626 
627 U_BOOT_CMD_WITH_SUBCMDS(bootflow, "Boot flows", bootflow_help_text,
628 	U_BOOT_SUBCMD_MKENT(scan, 3, 1, do_bootflow_scan),
629 #ifdef CONFIG_CMD_BOOTFLOW_FULL
630 	U_BOOT_SUBCMD_MKENT(list, 2, 1, do_bootflow_list),
631 	U_BOOT_SUBCMD_MKENT(select, 2, 1, do_bootflow_select),
632 	U_BOOT_SUBCMD_MKENT(info, 2, 1, do_bootflow_info),
633 	U_BOOT_SUBCMD_MKENT(read, 1, 1, do_bootflow_read),
634 	U_BOOT_SUBCMD_MKENT(boot, 1, 1, do_bootflow_boot),
635 	U_BOOT_SUBCMD_MKENT(menu, 2, 1, do_bootflow_menu),
636 	U_BOOT_SUBCMD_MKENT(cmdline, 4, 1, do_bootflow_cmdline),
637 #endif
638 );
639