We don't support storing the OS on an encrypted partition, but in some cases the root partition may be encrypted. Add an indication of this when listing the bootflows. Signed-off-by: Simon Glass <simon.glass@canonical.com>
639 lines
15 KiB
C
639 lines
15 KiB
C
// 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 <efi_device_path.h>
|
|
#include <expo.h>
|
|
#include <log.h>
|
|
#include <mapmem.h>
|
|
|
|
static void show_header(void)
|
|
{
|
|
printf("Seq Method State Uclass Part E 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);
|
|
|
|
expo_enter_mode(exp);
|
|
|
|
ret = -ERESTART;
|
|
do {
|
|
if (ret == -ERESTART) {
|
|
ret = expo_arrange(exp);
|
|
if (ret) {
|
|
expo_exit_mode(exp);
|
|
return log_msg_ret("bha", ret);
|
|
}
|
|
ret = expo_render(exp);
|
|
if (ret) {
|
|
expo_exit_mode(exp);
|
|
return log_msg_ret("bhr", ret);
|
|
}
|
|
}
|
|
ret = bootflow_menu_poll(exp, &seq);
|
|
} while (ret == -EAGAIN || ret == -ERESTART || ret == -EREMCHG);
|
|
|
|
expo_exit_mode(exp);
|
|
|
|
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;
|
|
bool any_part = 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');
|
|
any_part = strchr(argv[1], 'p');
|
|
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 (!any_part)
|
|
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)
|
|
bootflow_show(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;
|
|
bootflow_show(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;
|
|
bootflow_show(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) {
|
|
struct bootm_info bmi;
|
|
|
|
bootm_init(&bmi);
|
|
/* we don't know this at present */
|
|
bootm_x86_set(&bmi, bzimage_addr, 0);
|
|
bootm_x86_set(&bmi, base_ptr, bflow->x86_setup);
|
|
zimage_dump(&bmi, 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 ? bflow->method->name : "(none)");
|
|
printf("State: %s\n", bootflow_state_get_name(bflow->state));
|
|
printf("Partition: %d\n", bflow->part);
|
|
|
|
/* Show encryption status with LUKS version if applicable */
|
|
if (IS_ENABLED(CONFIG_BLK_LUKS)) {
|
|
if (bflow->flags & BOOTFLOWF_ENCRYPTED)
|
|
printf("Encrypted: LUKSv%d\n", bflow->luks_version);
|
|
else
|
|
printf("Encrypted: no\n");
|
|
}
|
|
|
|
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: %zx (%zd 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 (IS_ENABLED(CONFIG_BOOTMETH_EFI) &&
|
|
bflow->method->driver == DM_DRIVER_GET(bootmeth_4efi)) {
|
|
struct efi_device_path *dp;
|
|
bool alloced;
|
|
|
|
ret = efi_dp_from_bootflow(bflow, &dp, &alloced);
|
|
printf("EFI path ");
|
|
if (!ret) {
|
|
printf("%pD\n", dp);
|
|
if (alloced)
|
|
efi_free_pool(dp);
|
|
} else {
|
|
printf("(err %dE)\n", ret);
|
|
}
|
|
}
|
|
|
|
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;
|
|
bool fake = false;
|
|
int ret;
|
|
|
|
if (IS_ENABLED(CONFIG_BOOTM_FAKE_GO) && argc > 1 && *argv[1] == '-')
|
|
fake = strchr(argv[1], 'f');
|
|
|
|
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;
|
|
|
|
if (IS_ENABLED(CONFIG_BOOTM_FAKE_GO)) {
|
|
if (fake)
|
|
bflow->flags |= BOOTFLOWF_FAKE_GO;
|
|
else
|
|
bflow->flags &= ~BOOTFLOWF_FAKE_GO;
|
|
}
|
|
log_debug("cmd bflow flags %x\n", bflow->flags);
|
|
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 [-abeGHlmpt] [bdev] - scan for valid bootflows (-l list, -a all,\n"
|
|
" -e errors, -b boot, -G no global, -H no hunters\n"
|
|
" -m menu, -t text-only\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 [-f] - boot current bootflow (-f fake)\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, 2, 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
|
|
);
|