diff options
Diffstat (limited to 'cmd/bootflow.c')
-rw-r--r-- | cmd/bootflow.c | 615 |
1 files changed, 615 insertions, 0 deletions
diff --git a/cmd/bootflow.c b/cmd/bootflow.c new file mode 100644 index 00000000000..1588f277a4a --- /dev/null +++ b/cmd/bootflow.c @@ -0,0 +1,615 @@ +// 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 <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 bootflow *bflow; + int ret; + + ret = bootflow_menu_run(std, text_mode, &bflow); + if (ret) { + if (ret == -EAGAIN) { + printf("Nothing chosen\n"); + std->cur_bootflow = NULL; + } else { + printf("Menu failed (err=%d)\n", ret); + } + + return ret; + } + + printf("Selected: %s\n", bflow->os_name ? bflow->os_name : bflow->name); + std->cur_bootflow = bflow; + *bflowp = bflow; + + 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 = 0; + 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) + bootdev_clear_bootflows(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 = bootdev_add_bootflow(&bflow); + if (ret) { + 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->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("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: %p\n", 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 +); |