u-boot/cmd/bootflow.c
Simon Glass a4bee0b455 bootstd: Add a menu option to bootflow scan
Allow showing a menu and automatically booting, with 'bootflow scan'.
This is more convenient than using a script.

Signed-off-by: Simon Glass <sjg@chromium.org>
2023-12-13 18:39:05 -05:00

618 lines
14 KiB
C

// 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 <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') {
if (argc < 4)
return CMD_RET_USAGE;
val = argv[3];
}
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
);