2b80bc1eee
Add support for handling this concept in bootflows. Update the 'bootflow' command to allow only the normal bootmeths to be used. This alllows skipping EFI bootmgr and VBE, for example. Signed-off-by: Simon Glass <sjg@chromium.org>
409 lines
9.8 KiB
C
409 lines
9.8 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 <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),
|
|
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);
|
|
}
|
|
|
|
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, no_global = false;
|
|
bool 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');
|
|
no_global = strchr(argv[1], 'G');
|
|
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 (no_global)
|
|
flags |= BOOTFLOWF_SKIP_GLOBAL;
|
|
|
|
/*
|
|
* 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 [-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 [-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
|
|
);
|