bootstd: Add a bootflow command
Add a 'bootflow' command to handle listing and selection of bootflow. Signed-off-by: Simon Glass <sjg@chromium.org>
This commit is contained in:
		
							parent
							
								
									5d053cca38
								
							
						
					
					
						commit
						2d653f686b
					
				
							
								
								
									
										18
									
								
								cmd/Kconfig
								
								
								
								
							
							
						
						
									
										18
									
								
								cmd/Kconfig
								
								
								
								
							|  | @ -221,6 +221,24 @@ config CMD_BOOTDEV | ||||||
| 
 | 
 | ||||||
| 	  This command is not necessary for bootstd to work. | 	  This command is not necessary for bootstd to work. | ||||||
| 
 | 
 | ||||||
|  | config CMD_BOOTFLOW | ||||||
|  | 	bool "bootflow" | ||||||
|  | 	depends on BOOTSTD | ||||||
|  | 	default y | ||||||
|  | 	help | ||||||
|  | 	  Support scanning for bootflows available with the bootdevs. The | ||||||
|  | 	  bootflows can optionally be booted. | ||||||
|  | 
 | ||||||
|  | config CMD_BOOTFLOW_FULL | ||||||
|  | 	bool "bootflow - extract subcommands" | ||||||
|  | 	depends on BOOTSTD_FULL | ||||||
|  | 	default y if BOOTSTD_FULL | ||||||
|  | 	help | ||||||
|  | 	  Add the ability to list the available bootflows, select one and obtain | ||||||
|  | 	  information about it. | ||||||
|  | 
 | ||||||
|  | 	  This command is not necessary for bootstd to work. | ||||||
|  | 
 | ||||||
| config BOOTM_EFI | config BOOTM_EFI | ||||||
| 	bool "Support booting UEFI FIT images" | 	bool "Support booting UEFI FIT images" | ||||||
| 	depends on CMD_BOOTEFI && CMD_BOOTM && FIT | 	depends on CMD_BOOTEFI && CMD_BOOTM && FIT | ||||||
|  |  | ||||||
|  | @ -20,6 +20,7 @@ obj-$(CONFIG_CMD_ADC) += adc.o | ||||||
| obj-$(CONFIG_CMD_ARMFLASH) += armflash.o | obj-$(CONFIG_CMD_ARMFLASH) += armflash.o | ||||||
| obj-$(CONFIG_HAVE_BLOCK_DEVICE) += blk_common.o | obj-$(CONFIG_HAVE_BLOCK_DEVICE) += blk_common.o | ||||||
| obj-$(CONFIG_CMD_BOOTDEV) += bootdev.o | obj-$(CONFIG_CMD_BOOTDEV) += bootdev.o | ||||||
|  | obj-$(CONFIG_CMD_BOOTFLOW) += bootflow.o | ||||||
| obj-$(CONFIG_CMD_SOURCE) += source.o | obj-$(CONFIG_CMD_SOURCE) += source.o | ||||||
| obj-$(CONFIG_CMD_BCB) += bcb.o | obj-$(CONFIG_CMD_BCB) += bcb.o | ||||||
| obj-$(CONFIG_CMD_BDI) += bdinfo.o | obj-$(CONFIG_CMD_BDI) += bdinfo.o | ||||||
|  |  | ||||||
|  | @ -0,0 +1,404 @@ | ||||||
|  | // SPDX-License-Identifier: GPL-2.0+
 | ||||||
|  | /*
 | ||||||
|  |  * 'bootflow' command | ||||||
|  |  * | ||||||
|  |  * Copyright 2021 Google LLC | ||||||
|  |  * Written by Simon Glass <sjg@chromium.org> | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | #include <common.h> | ||||||
|  | #include <bootdev.h> | ||||||
|  | #include <bootflow.h> | ||||||
|  | #include <bootstd.h> | ||||||
|  | #include <command.h> | ||||||
|  | #include <console.h> | ||||||
|  | #include <dm.h> | ||||||
|  | #include <mapmem.h> | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * report_bootflow_err() - Report where a bootflow failed | ||||||
|  |  * | ||||||
|  |  * When a bootflow does not make it to the 'loaded' state, something went wrong. | ||||||
|  |  * Print a helpful message if there is an error | ||||||
|  |  * | ||||||
|  |  * @bflow: Bootflow to process | ||||||
|  |  * @err: Error code (0 if none) | ||||||
|  |  */ | ||||||
|  | static void report_bootflow_err(struct bootflow *bflow, int err) | ||||||
|  | { | ||||||
|  | 	if (!err) | ||||||
|  | 		return; | ||||||
|  | 
 | ||||||
|  | 	/* Indent out to 'Method' */ | ||||||
|  | 	printf("     ** "); | ||||||
|  | 
 | ||||||
|  | 	switch (bflow->state) { | ||||||
|  | 	case BOOTFLOWST_BASE: | ||||||
|  | 		printf("No media/partition found"); | ||||||
|  | 		break; | ||||||
|  | 	case BOOTFLOWST_MEDIA: | ||||||
|  | 		printf("No partition found"); | ||||||
|  | 		break; | ||||||
|  | 	case BOOTFLOWST_PART: | ||||||
|  | 		printf("No filesystem found"); | ||||||
|  | 		break; | ||||||
|  | 	case BOOTFLOWST_FS: | ||||||
|  | 		printf("File not found"); | ||||||
|  | 		break; | ||||||
|  | 	case BOOTFLOWST_FILE: | ||||||
|  | 		printf("File cannot be loaded"); | ||||||
|  | 		break; | ||||||
|  | 	case BOOTFLOWST_READY: | ||||||
|  | 		printf("Ready"); | ||||||
|  | 		break; | ||||||
|  | 	case BOOTFLOWST_COUNT: | ||||||
|  | 		break; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	printf(", err=%d\n", err); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * show_bootflow() - Show the status of a bootflow | ||||||
|  |  * | ||||||
|  |  * @seq: Bootflow index | ||||||
|  |  * @bflow: Bootflow to show | ||||||
|  |  * @errors: True to show the error received, if any | ||||||
|  |  */ | ||||||
|  | static void show_bootflow(int index, struct bootflow *bflow, bool errors) | ||||||
|  | { | ||||||
|  | 	printf("%3x  %-11s  %-6s  %-9.9s %4x  %-25.25s %s\n", index, | ||||||
|  | 	       bflow->method->name, bootflow_state_get_name(bflow->state), | ||||||
|  | 	       dev_get_uclass_name(dev_get_parent(bflow->dev)), bflow->part, | ||||||
|  | 	       bflow->name, bflow->fname); | ||||||
|  | 	if (errors) | ||||||
|  | 		report_bootflow_err(bflow, bflow->err); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void show_header(void) | ||||||
|  | { | ||||||
|  | 	printf("Seq  Method       State   Uclass    Part  Name                      Filename\n"); | ||||||
|  | 	printf("---  -----------  ------  --------  ----  ------------------------  ----------------\n"); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void show_footer(int count, int num_valid) | ||||||
|  | { | ||||||
|  | 	printf("---  -----------  ------  --------  ----  ------------------------  ----------------\n"); | ||||||
|  | 	printf("(%d bootflow%s, %d valid)\n", count, count != 1 ? "s" : "", | ||||||
|  | 	       num_valid); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static int do_bootflow_scan(struct cmd_tbl *cmdtp, int flag, int argc, | ||||||
|  | 			    char *const argv[]) | ||||||
|  | { | ||||||
|  | 	struct bootstd_priv *std; | ||||||
|  | 	struct bootflow_iter iter; | ||||||
|  | 	struct udevice *dev; | ||||||
|  | 	struct bootflow bflow; | ||||||
|  | 	bool all = false, boot = false, errors = false, list = false; | ||||||
|  | 	int num_valid = 0; | ||||||
|  | 	bool has_args; | ||||||
|  | 	int ret, i; | ||||||
|  | 	int flags; | ||||||
|  | 
 | ||||||
|  | 	ret = bootstd_get_priv(&std); | ||||||
|  | 	if (ret) | ||||||
|  | 		return CMD_RET_FAILURE; | ||||||
|  | 	dev = std->cur_bootdev; | ||||||
|  | 
 | ||||||
|  | 	has_args = argc > 1 && *argv[1] == '-'; | ||||||
|  | 	if (IS_ENABLED(CONFIG_CMD_BOOTFLOW_FULL)) { | ||||||
|  | 		if (has_args) { | ||||||
|  | 			all = strchr(argv[1], 'a'); | ||||||
|  | 			boot = strchr(argv[1], 'b'); | ||||||
|  | 			errors = strchr(argv[1], 'e'); | ||||||
|  | 			list = strchr(argv[1], 'l'); | ||||||
|  | 			argc--; | ||||||
|  | 			argv++; | ||||||
|  | 		} | ||||||
|  | 		if (argc > 1) { | ||||||
|  | 			const char *label = argv[1]; | ||||||
|  | 
 | ||||||
|  | 			if (bootdev_find_by_any(label, &dev)) | ||||||
|  | 				return CMD_RET_FAILURE; | ||||||
|  | 		} | ||||||
|  | 	} else { | ||||||
|  | 		if (has_args) { | ||||||
|  | 			printf("Flags not supported: enable CONFIG_BOOTFLOW_FULL\n"); | ||||||
|  | 			return CMD_RET_USAGE; | ||||||
|  | 		} | ||||||
|  | 		boot = true; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	std->cur_bootflow = NULL; | ||||||
|  | 
 | ||||||
|  | 	flags = 0; | ||||||
|  | 	if (list) | ||||||
|  | 		flags |= BOOTFLOWF_SHOW; | ||||||
|  | 	if (all) | ||||||
|  | 		flags |= BOOTFLOWF_ALL; | ||||||
|  | 
 | ||||||
|  | 	/*
 | ||||||
|  | 	 * If we have a device, just scan for bootflows attached to that device | ||||||
|  | 	 */ | ||||||
|  | 	if (IS_ENABLED(CONFIG_CMD_BOOTFLOW_FULL) && dev) { | ||||||
|  | 		if (list) { | ||||||
|  | 			printf("Scanning for bootflows in bootdev '%s'\n", | ||||||
|  | 			       dev->name); | ||||||
|  | 			show_header(); | ||||||
|  | 		} | ||||||
|  | 		bootdev_clear_bootflows(dev); | ||||||
|  | 		for (i = 0, | ||||||
|  | 		     ret = bootflow_scan_bootdev(dev, &iter, flags, &bflow); | ||||||
|  | 		     i < 1000 && ret != -ENODEV; | ||||||
|  | 		     i++, ret = bootflow_scan_next(&iter, &bflow)) { | ||||||
|  | 			bflow.err = ret; | ||||||
|  | 			if (!ret) | ||||||
|  | 				num_valid++; | ||||||
|  | 			ret = bootdev_add_bootflow(&bflow); | ||||||
|  | 			if (ret) { | ||||||
|  | 				printf("Out of memory\n"); | ||||||
|  | 				return CMD_RET_FAILURE; | ||||||
|  | 			} | ||||||
|  | 			if (list) | ||||||
|  | 				show_bootflow(i, &bflow, errors); | ||||||
|  | 			if (boot && !bflow.err) | ||||||
|  | 				bootflow_run_boot(&iter, &bflow); | ||||||
|  | 		} | ||||||
|  | 	} else { | ||||||
|  | 		if (list) { | ||||||
|  | 			printf("Scanning for bootflows in all bootdevs\n"); | ||||||
|  | 			show_header(); | ||||||
|  | 		} | ||||||
|  | 		bootstd_clear_glob(); | ||||||
|  | 
 | ||||||
|  | 		for (i = 0, | ||||||
|  | 		     ret = bootflow_scan_first(&iter, flags, &bflow); | ||||||
|  | 		     i < 1000 && ret != -ENODEV; | ||||||
|  | 		     i++, ret = bootflow_scan_next(&iter, &bflow)) { | ||||||
|  | 			bflow.err = ret; | ||||||
|  | 			if (!ret) | ||||||
|  | 				num_valid++; | ||||||
|  | 			ret = bootdev_add_bootflow(&bflow); | ||||||
|  | 			if (ret) { | ||||||
|  | 				printf("Out of memory\n"); | ||||||
|  | 				return CMD_RET_FAILURE; | ||||||
|  | 			} | ||||||
|  | 			if (list) | ||||||
|  | 				show_bootflow(i, &bflow, errors); | ||||||
|  | 			if (boot && !bflow.err) | ||||||
|  | 				bootflow_run_boot(&iter, &bflow); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	bootflow_iter_uninit(&iter); | ||||||
|  | 	if (list) | ||||||
|  | 		show_footer(i, num_valid); | ||||||
|  | 
 | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #ifdef CONFIG_CMD_BOOTFLOW_FULL | ||||||
|  | static int do_bootflow_list(struct cmd_tbl *cmdtp, int flag, int argc, | ||||||
|  | 			    char *const argv[]) | ||||||
|  | { | ||||||
|  | 	struct bootstd_priv *std; | ||||||
|  | 	struct udevice *dev; | ||||||
|  | 	struct bootflow *bflow; | ||||||
|  | 	int num_valid = 0; | ||||||
|  | 	bool errors = false; | ||||||
|  | 	int ret, i; | ||||||
|  | 
 | ||||||
|  | 	if (argc > 1 && *argv[1] == '-') | ||||||
|  | 		errors = strchr(argv[1], 'e'); | ||||||
|  | 
 | ||||||
|  | 	ret = bootstd_get_priv(&std); | ||||||
|  | 	if (ret) | ||||||
|  | 		return CMD_RET_FAILURE; | ||||||
|  | 	dev = std->cur_bootdev; | ||||||
|  | 
 | ||||||
|  | 	/* If we have a device, just list bootflows attached to that device */ | ||||||
|  | 	if (dev) { | ||||||
|  | 		printf("Showing bootflows for bootdev '%s'\n", dev->name); | ||||||
|  | 		show_header(); | ||||||
|  | 		for (ret = bootdev_first_bootflow(dev, &bflow), i = 0; | ||||||
|  | 		     !ret; | ||||||
|  | 		     ret = bootdev_next_bootflow(&bflow), i++) { | ||||||
|  | 			num_valid += bflow->state == BOOTFLOWST_READY; | ||||||
|  | 			show_bootflow(i, bflow, errors); | ||||||
|  | 		} | ||||||
|  | 	} else { | ||||||
|  | 		printf("Showing all bootflows\n"); | ||||||
|  | 		show_header(); | ||||||
|  | 		for (ret = bootflow_first_glob(&bflow), i = 0; | ||||||
|  | 		     !ret; | ||||||
|  | 		     ret = bootflow_next_glob(&bflow), i++) { | ||||||
|  | 			num_valid += bflow->state == BOOTFLOWST_READY; | ||||||
|  | 			show_bootflow(i, bflow, errors); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	show_footer(i, num_valid); | ||||||
|  | 
 | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static int do_bootflow_select(struct cmd_tbl *cmdtp, int flag, int argc, | ||||||
|  | 			      char *const argv[]) | ||||||
|  | { | ||||||
|  | 	struct bootstd_priv *std; | ||||||
|  | 	struct bootflow *bflow, *found; | ||||||
|  | 	struct udevice *dev; | ||||||
|  | 	const char *name; | ||||||
|  | 	char *endp; | ||||||
|  | 	int seq, i; | ||||||
|  | 	int ret; | ||||||
|  | 
 | ||||||
|  | 	ret = bootstd_get_priv(&std); | ||||||
|  | 	if (ret) | ||||||
|  | 		return CMD_RET_FAILURE; | ||||||
|  | ; | ||||||
|  | 	if (argc < 2) { | ||||||
|  | 		std->cur_bootflow = NULL; | ||||||
|  | 		return 0; | ||||||
|  | 	} | ||||||
|  | 	dev = std->cur_bootdev; | ||||||
|  | 
 | ||||||
|  | 	name = argv[1]; | ||||||
|  | 	seq = simple_strtol(name, &endp, 16); | ||||||
|  | 	found = NULL; | ||||||
|  | 
 | ||||||
|  | 	/*
 | ||||||
|  | 	 * If we have a bootdev device, only allow selection of bootflows | ||||||
|  | 	 * attached to that device | ||||||
|  | 	 */ | ||||||
|  | 	if (dev) { | ||||||
|  | 		for (ret = bootdev_first_bootflow(dev, &bflow), i = 0; | ||||||
|  | 		     !ret; | ||||||
|  | 		     ret = bootdev_next_bootflow(&bflow), i++) { | ||||||
|  | 			if (*endp ? !strcmp(bflow->name, name) : i == seq) { | ||||||
|  | 				found = bflow; | ||||||
|  | 				break; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} else { | ||||||
|  | 		for (ret = bootflow_first_glob(&bflow), i = 0; | ||||||
|  | 		     !ret; | ||||||
|  | 		     ret = bootflow_next_glob(&bflow), i++) { | ||||||
|  | 			if (*endp ? !strcmp(bflow->name, name) : i == seq) { | ||||||
|  | 				found = bflow; | ||||||
|  | 				break; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if (!found) { | ||||||
|  | 		printf("Cannot find bootflow '%s' ", name); | ||||||
|  | 		if (dev) | ||||||
|  | 			printf("in bootdev '%s' ", dev->name); | ||||||
|  | 		printf("(err=%d)\n", ret); | ||||||
|  | 		return CMD_RET_FAILURE; | ||||||
|  | 	} | ||||||
|  | 	std->cur_bootflow = found; | ||||||
|  | 
 | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static int do_bootflow_info(struct cmd_tbl *cmdtp, int flag, int argc, | ||||||
|  | 			    char *const argv[]) | ||||||
|  | { | ||||||
|  | 	struct bootstd_priv *std; | ||||||
|  | 	struct bootflow *bflow; | ||||||
|  | 	bool dump = false; | ||||||
|  | 	int ret; | ||||||
|  | 
 | ||||||
|  | 	if (argc > 1 && *argv[1] == '-') | ||||||
|  | 		dump = strchr(argv[1], 'd'); | ||||||
|  | 
 | ||||||
|  | 	ret = bootstd_get_priv(&std); | ||||||
|  | 	if (ret) | ||||||
|  | 		return CMD_RET_FAILURE; | ||||||
|  | 
 | ||||||
|  | 	if (!std->cur_bootflow) { | ||||||
|  | 		printf("No bootflow selected\n"); | ||||||
|  | 		return CMD_RET_FAILURE; | ||||||
|  | 	} | ||||||
|  | 	bflow = std->cur_bootflow; | ||||||
|  | 
 | ||||||
|  | 	printf("Name:      %s\n", bflow->name); | ||||||
|  | 	printf("Device:    %s\n", bflow->dev->name); | ||||||
|  | 	printf("Block dev: %s\n", bflow->blk ? bflow->blk->name : "(none)"); | ||||||
|  | 	printf("Method:    %s\n", bflow->method->name); | ||||||
|  | 	printf("State:     %s\n", bootflow_state_get_name(bflow->state)); | ||||||
|  | 	printf("Partition: %d\n", bflow->part); | ||||||
|  | 	printf("Subdir:    %s\n", bflow->subdir ? bflow->subdir : "(none)"); | ||||||
|  | 	printf("Filename:  %s\n", bflow->fname); | ||||||
|  | 	printf("Buffer:    %lx\n", (ulong)map_to_sysmem(bflow->buf)); | ||||||
|  | 	printf("Size:      %x (%d bytes)\n", bflow->size, bflow->size); | ||||||
|  | 	printf("Error:     %d\n", bflow->err); | ||||||
|  | 	if (dump && bflow->buf) { | ||||||
|  | 		/* Set some sort of maximum on the size */ | ||||||
|  | 		int size = min(bflow->size, 10 << 10); | ||||||
|  | 		int i; | ||||||
|  | 
 | ||||||
|  | 		printf("Contents:\n\n"); | ||||||
|  | 		for (i = 0; i < size; i++) { | ||||||
|  | 			putc(bflow->buf[i]); | ||||||
|  | 			if (!(i % 128) && ctrlc()) { | ||||||
|  | 				printf("...interrupted\n"); | ||||||
|  | 				break; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static int do_bootflow_boot(struct cmd_tbl *cmdtp, int flag, int argc, | ||||||
|  | 			    char *const argv[]) | ||||||
|  | { | ||||||
|  | 	struct bootstd_priv *std; | ||||||
|  | 	struct bootflow *bflow; | ||||||
|  | 	int ret; | ||||||
|  | 
 | ||||||
|  | 	ret = bootstd_get_priv(&std); | ||||||
|  | 	if (ret) | ||||||
|  | 		return CMD_RET_FAILURE; | ||||||
|  | 
 | ||||||
|  | 	/*
 | ||||||
|  | 	 * Require a current bootflow. Users can use 'bootflow scan -b' to | ||||||
|  | 	 * automatically scan and boot, if needed. | ||||||
|  | 	 */ | ||||||
|  | 	if (!std->cur_bootflow) { | ||||||
|  | 		printf("No bootflow selected\n"); | ||||||
|  | 		return CMD_RET_FAILURE; | ||||||
|  | 	} | ||||||
|  | 	bflow = std->cur_bootflow; | ||||||
|  | 	ret = bootflow_run_boot(NULL, bflow); | ||||||
|  | 	if (ret) | ||||||
|  | 		return CMD_RET_FAILURE; | ||||||
|  | 
 | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  | #endif /* CONFIG_CMD_BOOTFLOW_FULL */ | ||||||
|  | 
 | ||||||
|  | #ifdef CONFIG_SYS_LONGHELP | ||||||
|  | static char bootflow_help_text[] = | ||||||
|  | #ifdef CONFIG_CMD_BOOTFLOW_FULL | ||||||
|  | 	"scan [-abel] [bdev]   - scan for valid bootflows (-l list, -a all, -e errors, -b boot)\n" | ||||||
|  | 	"bootflow list [-e]             - list scanned bootflows (-e errors)\n" | ||||||
|  | 	"bootflow select [<num>|<name>] - select a bootflow\n" | ||||||
|  | 	"bootflow info [-d]             - show info on current bootflow (-d dump bootflow)\n" | ||||||
|  | 	"bootflow boot                  - boot current bootflow (or first available if none selected)"; | ||||||
|  | #else | ||||||
|  | 	"scan - boot first available bootflow\n"; | ||||||
|  | #endif | ||||||
|  | #endif /* CONFIG_SYS_LONGHELP */ | ||||||
|  | 
 | ||||||
|  | U_BOOT_CMD_WITH_SUBCMDS(bootflow, "Boot flows", bootflow_help_text, | ||||||
|  | 	U_BOOT_SUBCMD_MKENT(scan, 3, 1, do_bootflow_scan), | ||||||
|  | #ifdef CONFIG_CMD_BOOTFLOW_FULL | ||||||
|  | 	U_BOOT_SUBCMD_MKENT(list, 2, 1, do_bootflow_list), | ||||||
|  | 	U_BOOT_SUBCMD_MKENT(select, 2, 1, do_bootflow_select), | ||||||
|  | 	U_BOOT_SUBCMD_MKENT(info, 2, 1, do_bootflow_info), | ||||||
|  | 	U_BOOT_SUBCMD_MKENT(boot, 1, 1, do_bootflow_boot) | ||||||
|  | #endif | ||||||
|  | ); | ||||||
		Loading…
	
		Reference in New Issue