| // SPDX-License-Identifier: GPL-2.0+ | |
| /* | |
| * 'bootflow' command | |
| * | |
| * Copyright 2021 Google LLC | |
| * Written by Simon Glass <sjg@chromium.org> | |
| */ | |
| #include <bootdev.h> | |
| #include <bootflow.h> | |
| #include <bootm.h> | |
| #include <bootstd.h> | |
| #include <command.h> | |
| #include <console.h> | |
| #include <dm.h> | |
| #include <env.h> | |
| #include <expo.h> | |
| #include <log.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=%dE\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), | |
| bflow->dev ? dev_get_uclass_name(dev_get_parent(bflow->dev)) : | |
| "(none)", 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); | |
| } | |
| /** | |
| * bootflow_handle_menu() - Handle running the menu and updating cur bootflow | |
| * | |
| * This shows the menu, allows the user to select something and then prints | |
| * what happened | |
| * | |
| * @std: bootstd information | |
| * @text_mode: true to run the menu in text mode | |
| * @bflowp: Returns selected bootflow, on success | |
| * Return: 0 on success (a bootflow was selected), -EAGAIN if nothing was | |
| * chosen, other -ve value on other error | |
| */ | |
| __maybe_unused static int bootflow_handle_menu(struct bootstd_priv *std, | |
| bool text_mode, | |
| struct bootflow **bflowp) | |
| { | |
| struct expo *exp; | |
| struct bootflow *bflow; | |
| int ret, seq; | |
| ret = bootflow_menu_start(std, text_mode, &exp); | |
| if (ret) | |
| return log_msg_ret("bhs", ret); | |
| ret = -ERESTART; | |
| do { | |
| if (ret == -ERESTART) { | |
| ret = expo_render(exp); | |
| if (ret) | |
| return log_msg_ret("bhr", ret); | |
| } | |
| ret = bootflow_menu_poll(exp, &seq); | |
| } while (ret == -EAGAIN || ret == -ERESTART); | |
| if (ret == -EPIPE) { | |
| printf("Nothing chosen\n"); | |
| std->cur_bootflow = NULL; | |
| } else if (ret) { | |
| printf("Menu failed (err=%d)\n", ret); | |
| } else { | |
| bflow = alist_getw(&std->bootflows, seq, struct bootflow); | |
| printf("Selected: %s\n", bflow->os_name ? bflow->os_name : | |
| bflow->name); | |
| std->cur_bootflow = bflow; | |
| *bflowp = bflow; | |
| } | |
| expo_destroy(exp); | |
| if (ret) | |
| return ret; | |
| return 0; | |
| } | |
| 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 = NULL; | |
| struct bootflow bflow; | |
| bool all = false, boot = false, errors = false, no_global = false; | |
| bool list = false, no_hunter = false, menu = false, text_mode = false; | |
| int num_valid = 0; | |
| const char *label = NULL; | |
| bool has_args; | |
| int ret, i; | |
| int flags; | |
| ret = bootstd_get_priv(&std); | |
| if (ret) | |
| return CMD_RET_FAILURE; | |
| 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'); | |
| no_global = strchr(argv[1], 'G'); | |
| list = strchr(argv[1], 'l'); | |
| no_hunter = strchr(argv[1], 'H'); | |
| menu = strchr(argv[1], 'm'); | |
| text_mode = strchr(argv[1], 't'); | |
| argc--; | |
| argv++; | |
| } | |
| if (argc > 1) | |
| label = argv[1]; | |
| if (!label) | |
| dev = std->cur_bootdev; | |
| } else { | |
| if (has_args) { | |
| printf("Flags not supported: enable CONFIG_BOOTSTD_FULL\n"); | |
| return CMD_RET_USAGE; | |
| } | |
| boot = true; | |
| } | |
| std->cur_bootflow = NULL; | |
| flags = BOOTFLOWIF_ONLY_BOOTABLE; | |
| if (list) | |
| flags |= BOOTFLOWIF_SHOW; | |
| if (all) | |
| flags |= BOOTFLOWIF_ALL; | |
| if (no_global) | |
| flags |= BOOTFLOWIF_SKIP_GLOBAL; | |
| if (!no_hunter) | |
| flags |= BOOTFLOWIF_HUNT; | |
| /* | |
| * If we have a device, just scan for bootflows attached to that device | |
| */ | |
| if (list) { | |
| printf("Scanning for bootflows "); | |
| if (dev) | |
| printf("in bootdev '%s'\n", dev->name); | |
| else if (label) | |
| printf("with label '%s'\n", label); | |
| else | |
| printf("in all bootdevs\n"); | |
| show_header(); | |
| } | |
| if (dev) | |
| bootstd_clear_bootflows_for_bootdev(dev); | |
| else | |
| bootstd_clear_glob(); | |
| for (i = 0, | |
| ret = bootflow_scan_first(dev, label, &iter, flags, &bflow); | |
| i < 1000 && ret != -ENODEV; | |
| i++, ret = bootflow_scan_next(&iter, &bflow)) { | |
| bflow.err = ret; | |
| if (!ret) | |
| num_valid++; | |
| ret = bootstd_add_bootflow(&bflow); | |
| if (ret < 0) { | |
| printf("Out of memory\n"); | |
| return CMD_RET_FAILURE; | |
| } | |
| if (list) | |
| show_bootflow(i, &bflow, errors); | |
| if (!menu && boot && !bflow.err) | |
| bootflow_run_boot(&iter, &bflow); | |
| } | |
| bootflow_iter_uninit(&iter); | |
| if (list) | |
| show_footer(i, num_valid); | |
| if (IS_ENABLED(CONFIG_CMD_BOOTFLOW_FULL) && IS_ENABLED(CONFIG_EXPO)) { | |
| if (!num_valid && !list) { | |
| printf("No bootflows found; try again with -l\n"); | |
| } else if (menu) { | |
| struct bootflow *sel_bflow; | |
| ret = bootflow_handle_menu(std, text_mode, &sel_bflow); | |
| if (!ret && boot) { | |
| ret = console_clear(); | |
| if (ret) { | |
| log_err("Failed to clear console: %dE\n", | |
| ret); | |
| return ret; | |
| } | |
| bootflow_run_boot(NULL, sel_bflow); | |
| } | |
| } | |
| } | |
| 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; | |
| if (IS_ENABLED(CONFIG_BOOTSTD_FULL)) { | |
| if (env_set("bootargs", found->cmdline)) { | |
| printf("Cannot set bootargs\n"); | |
| return CMD_RET_FAILURE; | |
| } | |
| } | |
| 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 x86_setup = false; | |
| bool dump = false; | |
| int ret; | |
| if (argc > 1 && *argv[1] == '-') { | |
| dump = strchr(argv[1], 'd'); | |
| x86_setup = strchr(argv[1], 's'); | |
| } | |
| 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; | |
| if (IS_ENABLED(CONFIG_X86) && x86_setup) { | |
| zimage_dump(bflow->x86_setup, false); | |
| return 0; | |
| } | |
| printf("Name: %s\n", bflow->name); | |
| printf("Device: %s\n", bflow->dev ? bflow->dev->name : "(none)"); | |
| 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: "); | |
| if (bflow->buf) | |
| printf("%lx\n", (ulong)map_to_sysmem(bflow->buf)); | |
| else | |
| printf("(not loaded)\n"); | |
| printf("Size: %x (%d bytes)\n", bflow->size, bflow->size); | |
| printf("OS: %s\n", bflow->os_name ? bflow->os_name : "(none)"); | |
| printf("Cmdline: "); | |
| if (bflow->cmdline) | |
| puts(bflow->cmdline); | |
| else | |
| puts("(none)"); | |
| putc('\n'); | |
| if (bflow->x86_setup) | |
| printf("X86 setup: %lx\n", | |
| (ulong)map_to_sysmem(bflow->x86_setup)); | |
| printf("Logo: %s\n", bflow->logo ? | |
| simple_xtoa((ulong)map_to_sysmem(bflow->logo)) : "(none)"); | |
| if (bflow->logo) { | |
| printf("Logo size: %x (%d bytes)\n", bflow->logo_size, | |
| bflow->logo_size); | |
| } | |
| printf("FDT: %s\n", bflow->fdt_fname); | |
| if (bflow->fdt_fname) { | |
| printf("FDT size: %x (%d bytes)\n", bflow->fdt_size, | |
| bflow->fdt_size); | |
| printf("FDT addr: %lx\n", bflow->fdt_addr); | |
| } | |
| 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_read(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_read_all(bflow); | |
| if (ret) { | |
| printf("Failed: err=%dE\n", ret); | |
| return CMD_RET_FAILURE; | |
| } | |
| 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; | |
| } | |
| static int do_bootflow_menu(struct cmd_tbl *cmdtp, int flag, int argc, | |
| char *const argv[]) | |
| { | |
| struct bootstd_priv *std; | |
| struct bootflow *bflow; | |
| bool text_mode = false; | |
| int ret; | |
| if (!IS_ENABLED(CONFIG_EXPO)) { | |
| printf("Menu not supported\n"); | |
| return CMD_RET_FAILURE; | |
| } | |
| if (argc > 1 && *argv[1] == '-') | |
| text_mode = strchr(argv[1], 't'); | |
| ret = bootstd_get_priv(&std); | |
| if (ret) | |
| return CMD_RET_FAILURE; | |
| ret = bootflow_handle_menu(std, text_mode, &bflow); | |
| if (ret) | |
| return CMD_RET_FAILURE; | |
| return 0; | |
| } | |
| static int do_bootflow_cmdline(struct cmd_tbl *cmdtp, int flag, int argc, | |
| char *const argv[]) | |
| { | |
| struct bootstd_priv *std; | |
| struct bootflow *bflow; | |
| const char *op, *arg, *val = NULL; | |
| int ret; | |
| if (argc < 3) | |
| return CMD_RET_USAGE; | |
| ret = bootstd_get_priv(&std); | |
| if (ret) | |
| return CMD_RET_FAILURE; | |
| bflow = std->cur_bootflow; | |
| if (!bflow) { | |
| printf("No bootflow selected\n"); | |
| return CMD_RET_FAILURE; | |
| } | |
| op = argv[1]; | |
| arg = argv[2]; | |
| if (*op == 's') { | |
| val = argv[3] ?: (const char *)BOOTFLOWCL_EMPTY; | |
| } | |
| switch (*op) { | |
| case 'c': /* clear */ | |
| val = ""; | |
| fallthrough; | |
| case 's': /* set */ | |
| case 'd': /* delete */ | |
| ret = bootflow_cmdline_set_arg(bflow, arg, val, true); | |
| break; | |
| case 'g': /* get */ | |
| ret = bootflow_cmdline_get_arg(bflow, arg, &val); | |
| if (ret >= 0) | |
| printf("%.*s\n", ret, val); | |
| break; | |
| case 'a': /* auto */ | |
| ret = bootflow_cmdline_auto(bflow, arg); | |
| break; | |
| } | |
| switch (ret) { | |
| case -E2BIG: | |
| printf("Argument too long\n"); | |
| break; | |
| case -ENOENT: | |
| printf("Argument not found\n"); | |
| break; | |
| case -EINVAL: | |
| printf("Mismatched quotes\n"); | |
| break; | |
| case -EBADF: | |
| printf("Value must be quoted\n"); | |
| break; | |
| default: | |
| if (ret < 0) | |
| printf("Unknown error: %dE\n", ret); | |
| } | |
| if (ret < 0) | |
| return CMD_RET_FAILURE; | |
| return 0; | |
| } | |
| #endif /* CONFIG_CMD_BOOTFLOW_FULL */ | |
| U_BOOT_LONGHELP(bootflow, | |
| #ifdef CONFIG_CMD_BOOTFLOW_FULL | |
| "scan [-abeGl] [bdev] - scan for valid bootflows (-l list, -a all, -e errors, -b boot, -G no global)\n" | |
| "bootflow list [-e] - list scanned bootflows (-e errors)\n" | |
| "bootflow select [<num>|<name>] - select a bootflow\n" | |
| "bootflow info [-ds] - show info on current bootflow (-d dump bootflow)\n" | |
| "bootflow read - read all current-bootflow files\n" | |
| "bootflow boot - boot current bootflow\n" | |
| "bootflow menu [-t] - show a menu of available bootflows\n" | |
| "bootflow cmdline [set|get|clear|delete|auto] <param> [<value>] - update cmdline" | |
| #else | |
| "scan - boot first available bootflow\n" | |
| #endif | |
| ); | |
| 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(read, 1, 1, do_bootflow_read), | |
| U_BOOT_SUBCMD_MKENT(boot, 1, 1, do_bootflow_boot), | |
| U_BOOT_SUBCMD_MKENT(menu, 2, 1, do_bootflow_menu), | |
| U_BOOT_SUBCMD_MKENT(cmdline, 4, 1, do_bootflow_cmdline), | |
| #endif | |
| ); |