mirror of
https://github.com/u-boot/u-boot.git
synced 2024-11-23 12:14:32 +08:00
bootstd: Support creating a boot menu
Create an expo to handle the boot menu. For now this is quite simple, with just a header, some menu items and a pointer to show the current one. Signed-off-by: Simon Glass <sjg@chromium.org>
This commit is contained in:
parent
fe93c14b4c
commit
02d929bfb2
@ -30,6 +30,7 @@ obj-$(CONFIG_$(SPL_TPL_)BOOTMETH_SANDBOX) += bootmeth_sandbox.o
|
||||
obj-$(CONFIG_$(SPL_TPL_)BOOTMETH_SCRIPT) += bootmeth_script.o
|
||||
ifdef CONFIG_$(SPL_TPL_)BOOTSTD_FULL
|
||||
obj-$(CONFIG_$(SPL_TPL_)CMD_BOOTEFI_BOOTMGR) += bootmeth_efi_mgr.o
|
||||
obj-$(CONFIG_$(SPL_TPL_)BOOTSTD) += bootflow_menu.o
|
||||
endif
|
||||
|
||||
obj-$(CONFIG_$(SPL_TPL_)OF_LIBFDT) += image-fdt.o
|
||||
|
47
boot/bootflow_internal.h
Normal file
47
boot/bootflow_internal.h
Normal file
@ -0,0 +1,47 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0+ */
|
||||
/*
|
||||
* Internal header file for bootflow
|
||||
*
|
||||
* Copyright 2022 Google LLC
|
||||
* Written by Simon Glass <sjg@chromium.org>
|
||||
*/
|
||||
|
||||
#ifndef __BOOTFLOW_INTERNAL_H
|
||||
#define __BOOTFLOW_INTERNAL_H
|
||||
|
||||
/* expo IDs for elements of the bootflow menu */
|
||||
enum {
|
||||
START,
|
||||
|
||||
/* strings */
|
||||
STR_PROMPT,
|
||||
STR_MENU_TITLE,
|
||||
STR_POINTER,
|
||||
|
||||
/* scene */
|
||||
MAIN,
|
||||
|
||||
/* objects */
|
||||
OBJ_U_BOOT_LOGO,
|
||||
OBJ_MENU,
|
||||
OBJ_PROMPT,
|
||||
OBJ_MENU_TITLE,
|
||||
OBJ_POINTER,
|
||||
|
||||
/* strings for menu items */
|
||||
STR_LABEL = 100,
|
||||
STR_DESC = 200,
|
||||
STR_KEY = 300,
|
||||
|
||||
/* menu items / components (bootflow number is added to these) */
|
||||
ITEM = 400,
|
||||
ITEM_LABEL = 500,
|
||||
ITEM_DESC = 600,
|
||||
ITEM_KEY = 700,
|
||||
ITEM_PREVIEW = 800,
|
||||
|
||||
/* left margin for the main menu */
|
||||
MARGIN_LEFT = 100,
|
||||
};
|
||||
|
||||
#endif /* __BOOTFLOW_INTERNAL_H */
|
241
boot/bootflow_menu.c
Normal file
241
boot/bootflow_menu.c
Normal file
@ -0,0 +1,241 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* Provide a menu of available bootflows and related options
|
||||
*
|
||||
* Copyright 2022 Google LLC
|
||||
* Written by Simon Glass <sjg@chromium.org>
|
||||
*/
|
||||
|
||||
#define LOG_CATEGORY UCLASS_BOOTSTD
|
||||
|
||||
#include <common.h>
|
||||
#include <bootflow.h>
|
||||
#include <bootstd.h>
|
||||
#include <cli.h>
|
||||
#include <dm.h>
|
||||
#include <expo.h>
|
||||
#include <malloc.h>
|
||||
#include <menu.h>
|
||||
#include <video_console.h>
|
||||
#include <watchdog.h>
|
||||
#include <linux/delay.h>
|
||||
#include "bootflow_internal.h"
|
||||
|
||||
/**
|
||||
* struct menu_priv - information about the menu
|
||||
*
|
||||
* @num_bootflows: Number of bootflows in the menu
|
||||
*/
|
||||
struct menu_priv {
|
||||
int num_bootflows;
|
||||
};
|
||||
|
||||
int bootflow_menu_new(struct expo **expp)
|
||||
{
|
||||
struct udevice *last_bootdev;
|
||||
struct scene_obj_menu *menu;
|
||||
struct menu_priv *priv;
|
||||
struct bootflow *bflow;
|
||||
struct scene *scn;
|
||||
struct expo *exp;
|
||||
void *logo;
|
||||
int ret, i;
|
||||
|
||||
priv = calloc(1, sizeof(*priv));
|
||||
if (!priv)
|
||||
return log_msg_ret("prv", -ENOMEM);
|
||||
|
||||
ret = expo_new("bootflows", priv, &exp);
|
||||
if (ret)
|
||||
return log_msg_ret("exp", ret);
|
||||
|
||||
ret = scene_new(exp, "main", MAIN, &scn);
|
||||
if (ret < 0)
|
||||
return log_msg_ret("scn", ret);
|
||||
|
||||
ret |= scene_txt_str(scn, "prompt", OBJ_PROMPT, STR_PROMPT,
|
||||
"UP and DOWN to choose, ENTER to select", NULL);
|
||||
|
||||
ret = scene_menu(scn, "main", OBJ_MENU, &menu);
|
||||
ret |= scene_obj_set_pos(scn, OBJ_MENU, MARGIN_LEFT, 100);
|
||||
ret |= scene_txt_str(scn, "title", OBJ_MENU_TITLE, STR_MENU_TITLE,
|
||||
"U-Boot - Boot Menu", NULL);
|
||||
ret |= scene_menu_set_title(scn, OBJ_MENU, OBJ_PROMPT);
|
||||
|
||||
logo = video_get_u_boot_logo();
|
||||
if (logo) {
|
||||
ret |= scene_img(scn, "ulogo", OBJ_U_BOOT_LOGO, logo, NULL);
|
||||
ret |= scene_obj_set_pos(scn, OBJ_U_BOOT_LOGO, -4, 4);
|
||||
}
|
||||
|
||||
ret |= scene_txt_str(scn, "cur_item", OBJ_POINTER, STR_POINTER, ">",
|
||||
NULL);
|
||||
ret |= scene_menu_set_pointer(scn, OBJ_MENU, OBJ_POINTER);
|
||||
if (ret < 0)
|
||||
return log_msg_ret("new", -EINVAL);
|
||||
|
||||
last_bootdev = NULL;
|
||||
for (ret = bootflow_first_glob(&bflow), i = 0; !ret && i < 36;
|
||||
ret = bootflow_next_glob(&bflow), i++) {
|
||||
char str[2], *label, *key;
|
||||
uint preview_id;
|
||||
bool add_gap;
|
||||
|
||||
if (bflow->state != BOOTFLOWST_READY)
|
||||
continue;
|
||||
|
||||
*str = i < 10 ? '0' + i : 'A' + i - 10;
|
||||
str[1] = '\0';
|
||||
key = strdup(str);
|
||||
if (!key)
|
||||
return log_msg_ret("key", -ENOMEM);
|
||||
label = strdup(dev_get_parent(bflow->dev)->name);
|
||||
if (!label) {
|
||||
free(key);
|
||||
return log_msg_ret("nam", -ENOMEM);
|
||||
}
|
||||
|
||||
add_gap = last_bootdev != bflow->dev;
|
||||
last_bootdev = bflow->dev;
|
||||
|
||||
ret = expo_str(exp, "prompt", STR_POINTER, ">");
|
||||
ret |= scene_txt_str(scn, "label", ITEM_LABEL + i,
|
||||
STR_LABEL + i, label, NULL);
|
||||
ret |= scene_txt_str(scn, "desc", ITEM_DESC + i, STR_DESC + i,
|
||||
bflow->os_name ? bflow->os_name :
|
||||
bflow->name, NULL);
|
||||
ret |= scene_txt_str(scn, "key", ITEM_KEY + i, STR_KEY + i, key,
|
||||
NULL);
|
||||
preview_id = 0;
|
||||
if (bflow->logo) {
|
||||
preview_id = ITEM_PREVIEW + i;
|
||||
ret |= scene_img(scn, "preview", preview_id,
|
||||
bflow->logo, NULL);
|
||||
}
|
||||
ret |= scene_menuitem(scn, OBJ_MENU, "item", ITEM + i,
|
||||
ITEM_KEY + i, ITEM_LABEL + i,
|
||||
ITEM_DESC + i, preview_id,
|
||||
add_gap ? SCENEMIF_GAP_BEFORE : 0,
|
||||
NULL);
|
||||
|
||||
if (ret < 0)
|
||||
return log_msg_ret("itm", -EINVAL);
|
||||
ret = 0;
|
||||
priv->num_bootflows++;
|
||||
}
|
||||
|
||||
*expp = exp;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int bootflow_menu_run(struct bootstd_priv *std, bool text_mode,
|
||||
struct bootflow **bflowp)
|
||||
{
|
||||
struct cli_ch_state s_cch, *cch = &s_cch;
|
||||
struct bootflow *sel_bflow;
|
||||
struct udevice *dev;
|
||||
struct expo *exp;
|
||||
uint sel_id;
|
||||
bool done;
|
||||
int ret;
|
||||
|
||||
cli_ch_init(cch);
|
||||
|
||||
sel_bflow = NULL;
|
||||
*bflowp = NULL;
|
||||
|
||||
ret = bootflow_menu_new(&exp);
|
||||
if (ret)
|
||||
return log_msg_ret("exp", ret);
|
||||
|
||||
/* For now we only support a video console */
|
||||
ret = uclass_first_device_err(UCLASS_VIDEO, &dev);
|
||||
if (ret)
|
||||
return log_msg_ret("vid", ret);
|
||||
ret = expo_set_display(exp, dev);
|
||||
if (ret)
|
||||
return log_msg_ret("dis", ret);
|
||||
|
||||
ret = expo_set_scene_id(exp, MAIN);
|
||||
if (ret)
|
||||
return log_msg_ret("scn", ret);
|
||||
|
||||
if (text_mode)
|
||||
exp_set_text_mode(exp, text_mode);
|
||||
|
||||
done = false;
|
||||
do {
|
||||
struct expo_action act;
|
||||
int ichar, key;
|
||||
|
||||
ret = expo_render(exp);
|
||||
if (ret)
|
||||
break;
|
||||
|
||||
ichar = cli_ch_process(cch, 0);
|
||||
if (!ichar) {
|
||||
while (!ichar && !tstc()) {
|
||||
schedule();
|
||||
mdelay(2);
|
||||
ichar = cli_ch_process(cch, -ETIMEDOUT);
|
||||
}
|
||||
if (!ichar) {
|
||||
ichar = getchar();
|
||||
ichar = cli_ch_process(cch, ichar);
|
||||
}
|
||||
}
|
||||
|
||||
key = 0;
|
||||
if (ichar) {
|
||||
key = bootmenu_conv_key(ichar);
|
||||
if (key == BKEY_NONE)
|
||||
key = ichar;
|
||||
}
|
||||
if (!key)
|
||||
continue;
|
||||
|
||||
ret = expo_send_key(exp, key);
|
||||
if (ret)
|
||||
break;
|
||||
|
||||
ret = expo_action_get(exp, &act);
|
||||
if (!ret) {
|
||||
switch (act.type) {
|
||||
case EXPOACT_SELECT:
|
||||
sel_id = act.select.id;
|
||||
done = true;
|
||||
break;
|
||||
case EXPOACT_QUIT:
|
||||
done = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while (!done);
|
||||
|
||||
if (ret)
|
||||
return log_msg_ret("end", ret);
|
||||
|
||||
if (sel_id) {
|
||||
struct bootflow *bflow;
|
||||
int i;
|
||||
|
||||
for (ret = bootflow_first_glob(&bflow), i = 0; !ret && i < 36;
|
||||
ret = bootflow_next_glob(&bflow), i++) {
|
||||
if (i == sel_id - ITEM) {
|
||||
sel_bflow = bflow;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
expo_destroy(exp);
|
||||
|
||||
if (!sel_bflow)
|
||||
return -EAGAIN;
|
||||
*bflowp = sel_bflow;
|
||||
|
||||
return 0;
|
||||
}
|
@ -389,6 +389,37 @@ static int do_bootflow_boot(struct cmd_tbl *cmdtp, int flag, int argc,
|
||||
|
||||
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 (argc > 1 && *argv[1] == '-')
|
||||
text_mode = strchr(argv[1], 't');
|
||||
|
||||
ret = bootstd_get_priv(&std);
|
||||
if (ret)
|
||||
return CMD_RET_FAILURE;
|
||||
|
||||
ret = bootflow_menu_run(std, text_mode, &bflow);
|
||||
if (ret) {
|
||||
if (ret == -EAGAIN)
|
||||
printf("Nothing chosen\n");
|
||||
else
|
||||
printf("Menu failed (err=%d)\n", ret);
|
||||
|
||||
return CMD_RET_FAILURE;
|
||||
}
|
||||
|
||||
printf("Selected: %s\n", bflow->os_name ? bflow->os_name : bflow->name);
|
||||
std->cur_bootflow = bflow;
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif /* CONFIG_CMD_BOOTFLOW_FULL */
|
||||
|
||||
#ifdef CONFIG_SYS_LONGHELP
|
||||
@ -398,7 +429,8 @@ static char bootflow_help_text[] =
|
||||
"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)";
|
||||
"bootflow boot - boot current bootflow (or first available if none selected)\n"
|
||||
"bootflow menu [-t] - show a menu of available bootflows";
|
||||
#else
|
||||
"scan - boot first available bootflow\n";
|
||||
#endif
|
||||
@ -410,6 +442,7 @@ U_BOOT_CMD_WITH_SUBCMDS(bootflow, "Boot flows", bootflow_help_text,
|
||||
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)
|
||||
U_BOOT_SUBCMD_MKENT(boot, 1, 1, do_bootflow_boot),
|
||||
U_BOOT_SUBCMD_MKENT(menu, 2, 1, do_bootflow_menu),
|
||||
#endif
|
||||
);
|
||||
|
@ -9,6 +9,9 @@
|
||||
|
||||
#include <linux/list.h>
|
||||
|
||||
struct bootstd_priv;
|
||||
struct expo;
|
||||
|
||||
/**
|
||||
* enum bootflow_state_t - states that a particular bootflow can be in
|
||||
*
|
||||
@ -336,4 +339,24 @@ int bootflow_iter_uses_network(const struct bootflow_iter *iter);
|
||||
*/
|
||||
int bootflow_iter_uses_system(const struct bootflow_iter *iter);
|
||||
|
||||
/**
|
||||
* bootflow_menu_new() - Create a new bootflow menu
|
||||
*
|
||||
* @expp: Returns the expo created
|
||||
* Returns 0 on success, -ve on error
|
||||
*/
|
||||
int bootflow_menu_new(struct expo **expp);
|
||||
|
||||
/**
|
||||
* bootflow_menu_run() - Create and run a menu of available bootflows
|
||||
*
|
||||
* @std: Bootstd information
|
||||
* @text_mode: Uses a text-based menu suitable for a serial port
|
||||
* @bflowp: Returns chosen bootflow (set to NULL if nothing is chosen)
|
||||
* @return 0 if an option was chosen, -EAGAIN if nothing was chosen, -ve on
|
||||
* error
|
||||
*/
|
||||
int bootflow_menu_run(struct bootstd_priv *std, bool text_mode,
|
||||
struct bootflow **bflowp);
|
||||
|
||||
#endif
|
||||
|
Loading…
Reference in New Issue
Block a user