// SPDX-License-Identifier: GPL-2.0+ /* * 'bootflow' command * * Copyright 2021 Google LLC * Written by Simon Glass */ #include #include #include #include #include #include #include #include /** * 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 [|] - 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] [] - 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 );