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