The bootctl tests are currently disabled due to some image incompatibilities: the multi UI uses one image and the simple UI uses a different one. Update the logic to switch between these logos when the layout changes. For now, use the U-Boot logo in both cases. Signed-off-by: Simon Glass <simon.glass@canonical.com>
491 lines
13 KiB
C
491 lines
13 KiB
C
// 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 <bootflow.h>
|
|
#include <bootmeth.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
|
|
* @last_bootdev: bootdev of the last bootflow added to the menu, NULL if none
|
|
*/
|
|
struct menu_priv {
|
|
int num_bootflows;
|
|
struct udevice *last_bootdev;
|
|
};
|
|
|
|
static int bootflow_menu_set_item_props(struct scene *scn,
|
|
int i, const struct bootflow *bflow)
|
|
{
|
|
struct expo *exp = scn->expo;
|
|
struct abuf *buf;
|
|
int ret;
|
|
|
|
scene_obj_set_hide(scn, ITEM_PREVIEW + i, true);
|
|
ret = scene_obj_set_hide(scn, ITEM_BOX + i, true);
|
|
ret |= scene_obj_set_hide(scn, ITEM_VERSION_NAME + i, true);
|
|
scene_obj_set_hide(scn, ITEM_VERIFIED + i, true);
|
|
ret |= scene_obj_set_hide(scn, ITEM_KEY + i, false);
|
|
scene_obj_set_hide(scn, ITEM_LOCKED + i, true);
|
|
scene_obj_set_hide(scn, ITEM_PASS + i, true);
|
|
scene_obj_set_hide(scn, ITEM_PASS_LABEL + i, true);
|
|
scene_obj_set_hide(scn, ITEM_PASS_EDIT + i, true);
|
|
if (ret)
|
|
return log_msg_ret("msp", ret);
|
|
|
|
ret = expo_edit_str(exp, STR_DESC + i, NULL, &buf);
|
|
if (ret)
|
|
return log_msg_ret("msr", ret);
|
|
abuf_printf(buf, "%s", bflow->os_name ? bflow->os_name : bflow->name);
|
|
|
|
ret = expo_edit_str(exp, STR_LABEL + i, NULL, &buf);
|
|
if (!ret)
|
|
abuf_printf(buf, "%s", bootflow_guess_label(bflow));
|
|
|
|
return 0;
|
|
}
|
|
|
|
int bootflow_menu_set_props(struct expo *exp, struct scene *scn, bool has_logo,
|
|
const char *title)
|
|
{
|
|
struct expo_theme *theme = &exp->theme;
|
|
struct menu_priv *priv = exp->priv;
|
|
struct bootstd_priv *std;
|
|
struct abuf *buf;
|
|
int i, ret = 0;
|
|
bool use_font;
|
|
|
|
ret |= scene_obj_set_bbox(scn, OBJ_BOX, 30, 90, 1366 - 30, 720);
|
|
scene_box_set_fill(scn, OBJ_BOX, false);
|
|
ret |= scene_obj_set_pos(scn, OBJ_MENU, MARGIN_LEFT, 100);
|
|
ret |= scene_obj_set_bbox(scn, OBJ_MENU_TITLE, 0, 32,
|
|
1366, 60);
|
|
ret |= scene_obj_set_halign(scn, OBJ_MENU_TITLE, SCENEOA_CENTRE);
|
|
|
|
if (has_logo) {
|
|
const void *logo;
|
|
int size;
|
|
|
|
ret |= scene_obj_set_pos(scn, OBJ_U_BOOT_LOGO, 1165, 100);
|
|
|
|
logo = video_get_u_boot_logo(&size);
|
|
ret |= scene_img_set_data(scn, OBJ_U_BOOT_LOGO, logo, size);
|
|
if (ret)
|
|
return log_msg_ret("log", ret);
|
|
}
|
|
|
|
ret |= scene_obj_set_bbox(scn, OBJ_PROMPT1A, 0, 590,
|
|
1366, 590 + 40);
|
|
ret |= scene_obj_set_bbox(scn, OBJ_PROMPT1B, 0, 620,
|
|
1366, 620 + 40);
|
|
ret |= scene_obj_set_bbox(scn, OBJ_PROMPT2, 100, 650,
|
|
1366 - 100, 700);
|
|
ret |= scene_obj_set_bbox(scn, OBJ_AUTOBOOT, 0, 720,
|
|
1366, 750);
|
|
ret |= scene_obj_set_halign(scn, OBJ_PROMPT1A, SCENEOA_CENTRE);
|
|
ret |= scene_obj_set_halign(scn, OBJ_PROMPT1B, SCENEOA_CENTRE);
|
|
ret |= scene_obj_set_halign(scn, OBJ_PROMPT2, SCENEOA_CENTRE);
|
|
ret |= scene_obj_set_valign(scn, OBJ_PROMPT2, SCENEOA_CENTRE);
|
|
ret |= scene_obj_set_halign(scn, OBJ_AUTOBOOT, SCENEOA_CENTRE);
|
|
ret |= scene_menu_set_pointer(scn, OBJ_MENU, OBJ_POINTER);
|
|
|
|
if (ret)
|
|
return log_msg_ret("msp", ret);
|
|
|
|
use_font = IS_ENABLED(CONFIG_CONSOLE_TRUETYPE);
|
|
scene_obj_set_hide(scn, OBJ_PROMPT1A, use_font);
|
|
scene_obj_set_hide(scn, OBJ_PROMPT1B, !use_font);
|
|
scene_obj_set_hide(scn, OBJ_AUTOBOOT, use_font);
|
|
|
|
/* Set the title and prompt texts */
|
|
ret = expo_edit_str(exp, STR_MENU_TITLE, NULL, &buf);
|
|
if (ret)
|
|
return log_msg_ret("mss", ret);
|
|
abuf_printf(buf, "%s", title);
|
|
|
|
ret = expo_edit_str(exp, STR_PROMPT1A, NULL, &buf);
|
|
if (!ret)
|
|
abuf_printf(buf, "Use the \x18 and \x19 keys to select which "
|
|
"entry is highlighted.");
|
|
|
|
ret = expo_edit_str(exp, STR_PROMPT1B, NULL, &buf);
|
|
if (!ret)
|
|
abuf_printf(buf, "Use the UP and DOWN keys to select which "
|
|
"entry is highlighted.");
|
|
|
|
ret = expo_edit_str(exp, STR_PROMPT2, NULL, &buf);
|
|
if (!ret)
|
|
abuf_printf(buf, "Press enter to boot the selected OS, 'e' to "
|
|
"edit the commands before booting or 'c' for a "
|
|
"command-line. ESC to return to previous menu");
|
|
|
|
/* hide a few things we don't use */
|
|
scene_obj_set_hide(scn, OBJ_OTHER_LOGO, true);
|
|
scene_obj_set_hide(scn, OBJ_SETTINGS, true);
|
|
scene_obj_set_hide(scn, OBJ_HELP, true);
|
|
|
|
/* select the menu and hide the pointer */
|
|
scene_set_highlight_id(scn, OBJ_MENU);
|
|
scene_obj_set_hide(scn, OBJ_POINTER, false);
|
|
|
|
/* tell the menu to lay out its objects */
|
|
scene_obj_set_manual(scn, OBJ_MENU, false);
|
|
|
|
scene_obj_set_hide(scn, OBJ_POINTER, false);
|
|
|
|
theme->white_on_black = true;
|
|
ret = expo_apply_theme(exp, true);
|
|
if (ret)
|
|
return log_msg_ret("mat", ret);
|
|
|
|
expo_set_mouse_enable(exp, false);
|
|
|
|
exp->show_highlight = true;
|
|
|
|
ret = bootstd_get_priv(&std);
|
|
if (ret)
|
|
return ret;
|
|
|
|
for (i = 0; i < priv->num_bootflows; i++) {
|
|
const struct bootflow *bflow;
|
|
|
|
bflow = alist_get(&std->bootflows, i, struct bootflow);
|
|
if (!bflow)
|
|
return log_msg_ret("bmb", -ENOENT);
|
|
ret = bootflow_menu_set_item_props(scn, i, bflow);
|
|
if (ret)
|
|
return log_msg_ret("mst", ret);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int bootflow_menu_new(struct expo **expp)
|
|
{
|
|
struct scene_obj_menu *menu;
|
|
struct menu_priv *priv;
|
|
struct scene *scn;
|
|
struct expo *exp;
|
|
void *logo;
|
|
int ret;
|
|
|
|
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);
|
|
expo_req_size(exp, 1366, 768);
|
|
|
|
expo_set_mouse_enable(exp, true);
|
|
|
|
ret = scene_new(exp, "main", MAIN, &scn);
|
|
if (ret < 0)
|
|
return log_msg_ret("scn", ret);
|
|
|
|
ret = scene_box(scn, "box", OBJ_BOX, 2, false, NULL);
|
|
if (ret < 0)
|
|
return log_msg_ret("bmb", ret);
|
|
|
|
ret = scene_menu(scn, "main", OBJ_MENU, &menu);
|
|
ret |= scene_txt_str(scn, "title", OBJ_MENU_TITLE, STR_MENU_TITLE,
|
|
"", NULL);
|
|
|
|
logo = video_get_u_boot_logo(NULL);
|
|
if (logo)
|
|
ret |= scene_img(scn, "ulogo", OBJ_U_BOOT_LOGO, logo, NULL);
|
|
|
|
ret |= scene_txt_str(scn, "prompt1a", OBJ_PROMPT1A, STR_PROMPT1A,
|
|
"", NULL);
|
|
ret |= scene_txt_str(scn, "prompt1b", OBJ_PROMPT1B, STR_PROMPT1B,
|
|
"", NULL);
|
|
ret |= scene_txt_str(scn, "prompt2", OBJ_PROMPT2, STR_PROMPT2,
|
|
"", NULL);
|
|
ret |= scene_txt_str(scn, "autoboot", OBJ_AUTOBOOT, STR_AUTOBOOT,
|
|
"The highlighted entry will be executed automatically in %ds.",
|
|
NULL);
|
|
|
|
ret |= scene_txt_str(scn, "cur_item", OBJ_POINTER, STR_POINTER, ">",
|
|
NULL);
|
|
if (ret < 0)
|
|
return log_msg_ret("new", -EINVAL);
|
|
|
|
ret = bootflow_menu_set_props(exp, scn, logo, "U-Boot - Boot Menu");
|
|
if (ret < 0)
|
|
return log_msg_ret("nep", -EINVAL);
|
|
|
|
*expp = exp;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int bootflow_menu_add(struct expo *exp, struct bootflow *bflow, int seq,
|
|
struct scene **scnp)
|
|
{
|
|
struct menu_priv *priv = exp->priv;
|
|
struct scene_obj_textline *tline;
|
|
struct scene_obj_txt *txt;
|
|
char str[2], *key;
|
|
struct scene *scn;
|
|
uint preview_id;
|
|
uint scene_id;
|
|
bool add_gap;
|
|
char name[40];
|
|
int ret;
|
|
|
|
ret = expo_first_scene_id(exp);
|
|
if (ret < 0)
|
|
return log_msg_ret("scn", ret);
|
|
scene_id = ret;
|
|
scn = expo_lookup_scene_id(exp, scene_id);
|
|
|
|
*str = seq < 10 ? '0' + seq : 'A' + seq - 10;
|
|
str[1] = '\0';
|
|
key = strdup(str);
|
|
if (!key)
|
|
return log_msg_ret("key", -ENOMEM);
|
|
|
|
add_gap = priv->last_bootdev != bflow->dev;
|
|
|
|
/* disable this gap for now, since it looks a little ugly */
|
|
add_gap = false;
|
|
priv->last_bootdev = bflow->dev;
|
|
|
|
ret = expo_str(exp, "prompt", STR_POINTER, ">");
|
|
snprintf(name, sizeof(name), "item%d.label", seq);
|
|
ret |= scene_txt_str(scn, name, ITEM_LABEL + seq,
|
|
STR_LABEL + seq, "", NULL);
|
|
snprintf(name, sizeof(name), "item%d.desc", seq);
|
|
ret |= scene_txt_str(scn, name, ITEM_DESC + seq, STR_DESC + seq,
|
|
"", NULL);
|
|
snprintf(name, sizeof(name), "item%d.key", seq);
|
|
ret |= scene_txt_str(scn, name, ITEM_KEY + seq, STR_KEY + seq, key,
|
|
NULL);
|
|
snprintf(name, sizeof(name), "item%d.box", seq);
|
|
ret |= scene_box(scn, name, ITEM_BOX + seq, 1, false, NULL);
|
|
snprintf(name, sizeof(name), "item%d.version", seq);
|
|
ret |= scene_txt_str(scn, name, ITEM_VERSION_NAME + seq,
|
|
STR_VERSION_NAME + seq, "", NULL);
|
|
|
|
preview_id = 0;
|
|
if (bflow->logo) {
|
|
preview_id = ITEM_PREVIEW + seq;
|
|
snprintf(name, sizeof(name), "item%d.preview", seq);
|
|
ret |= scene_img(scn, name, preview_id,
|
|
bflow->logo, NULL);
|
|
}
|
|
snprintf(name, sizeof(name), "item%d", seq);
|
|
ret |= scene_menuitem(scn, OBJ_MENU, name, ITEM + seq,
|
|
ITEM_KEY + seq, ITEM_LABEL + seq,
|
|
ITEM_DESC + seq, preview_id,
|
|
add_gap ? SCENEMIF_GAP_BEFORE : 0,
|
|
NULL);
|
|
if (ret < 0)
|
|
return log_msg_ret("itm", -EINVAL);
|
|
|
|
/*
|
|
* Create passphrase textline with label and edit field (12 chars). Show
|
|
* characters as asterisks
|
|
*/
|
|
snprintf(name, sizeof(name), "item%d.pass", seq);
|
|
ret = scene_textline(scn, name, ITEM_PASS + seq, 12, &tline);
|
|
if (ret < 0)
|
|
return log_msg_ret("itp", -EINVAL);
|
|
snprintf(name, sizeof(name), "item%d.pass.label", seq);
|
|
ret = scene_txt_str(scn, name, ITEM_PASS_LABEL + seq, 0,
|
|
"Passphrase:", NULL);
|
|
if (ret < 0)
|
|
return log_msg_ret("itl", -EINVAL);
|
|
tline->label_id = ret;
|
|
|
|
snprintf(name, sizeof(name), "item%d.pass.edit", seq);
|
|
ret = scene_txt_str(scn, name, ITEM_PASS_EDIT + seq, 0,
|
|
abuf_data(&tline->buf), &txt);
|
|
if (ret < 0)
|
|
return log_msg_ret("ite", -EINVAL);
|
|
tline->edit_id = ret;
|
|
txt->obj.flags |= SCENEOF_PASSWORD;
|
|
|
|
/* Create message text (hidden by default) for success/error feedback */
|
|
snprintf(name, sizeof(name), "item%d.pass_msg", seq);
|
|
ret = scene_txt_str(scn, name, ITEM_PASS_MSG + seq,
|
|
STR_PASS_MSG + seq, "", NULL);
|
|
if (ret < 0)
|
|
return log_msg_ret("ipm", -EINVAL);
|
|
scene_obj_set_hide(scn, ITEM_PASS_MSG + seq, true);
|
|
|
|
ret = bootflow_menu_set_item_props(scn, seq, bflow);
|
|
if (ret)
|
|
return log_msg_ret("itp", -EINVAL);
|
|
|
|
priv->num_bootflows++;
|
|
*scnp = scn;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int bootflow_menu_add_all(struct expo *exp)
|
|
{
|
|
struct bootflow *bflow;
|
|
struct scene *scn;
|
|
int ret, i;
|
|
|
|
for (ret = bootflow_first_glob(&bflow), i = 0; !ret && i < 36;
|
|
ret = bootflow_next_glob(&bflow), i++) {
|
|
struct bootmeth_uc_plat *ucp;
|
|
|
|
if (bflow->state != BOOTFLOWST_READY)
|
|
continue;
|
|
|
|
/* No media to show for BOOTMETHF_GLOBAL bootmeths */
|
|
ucp = dev_get_uclass_plat(bflow->method);
|
|
if (ucp->flags & BOOTMETHF_GLOBAL)
|
|
continue;
|
|
|
|
ret = bootflow_menu_add(exp, bflow, i, &scn);
|
|
if (ret)
|
|
return log_msg_ret("bao", ret);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int bootflow_menu_setup(struct bootstd_priv *std, bool text_mode,
|
|
struct expo **expp)
|
|
{
|
|
struct udevice *dev;
|
|
struct expo *exp;
|
|
int ret;
|
|
|
|
ret = bootflow_menu_new(&exp);
|
|
if (ret)
|
|
return log_msg_ret("bmn", 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)
|
|
expo_set_text_mode(exp, text_mode);
|
|
|
|
*expp = exp;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int bootflow_menu_start(struct bootstd_priv *std, bool text_mode,
|
|
struct expo **expp)
|
|
{
|
|
struct expo *exp;
|
|
int ret;
|
|
|
|
ret = bootflow_menu_setup(std, text_mode, &exp);
|
|
if (ret)
|
|
return log_msg_ret("bmd", ret);
|
|
|
|
ret = bootflow_menu_add_all(exp);
|
|
if (ret)
|
|
return log_msg_ret("bma", ret);
|
|
|
|
if (ofnode_valid(std->theme)) {
|
|
ret = expo_setup_theme(exp, std->theme);
|
|
if (ret)
|
|
return log_msg_ret("thm", ret);
|
|
}
|
|
|
|
ret = expo_calc_dims(exp);
|
|
if (ret)
|
|
return log_msg_ret("bmd", ret);
|
|
|
|
ret = expo_arrange(exp);
|
|
if (ret)
|
|
return log_msg_ret("arr", ret);
|
|
|
|
*expp = exp;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int bootflow_menu_poll(struct expo *exp, int *seqp)
|
|
{
|
|
struct bootflow *sel_bflow;
|
|
struct expo_action act;
|
|
struct scene *scn;
|
|
int item, ret;
|
|
|
|
sel_bflow = NULL;
|
|
|
|
scn = expo_lookup_scene_id(exp, exp->scene_id);
|
|
|
|
item = scene_menu_get_cur_item(scn, OBJ_MENU);
|
|
*seqp = item > 0 ? item - ITEM : -1;
|
|
|
|
ret = expo_poll(exp, &act);
|
|
if (ret)
|
|
return log_msg_ret("bmp", ret);
|
|
|
|
switch (act.type) {
|
|
case EXPOACT_SELECT:
|
|
*seqp = act.select.id - ITEM;
|
|
break;
|
|
case EXPOACT_POINT_ITEM: {
|
|
struct scene *scn = expo_lookup_scene_id(exp, MAIN);
|
|
|
|
if (!scn)
|
|
return log_msg_ret("bms", -ENOENT);
|
|
ret = scene_menu_select_item(scn, OBJ_MENU, act.select.id);
|
|
if (ret)
|
|
return log_msg_ret("bmp", ret);
|
|
if (act.select.changed)
|
|
return -EREMCHG;
|
|
return -ERESTART;
|
|
}
|
|
case EXPOACT_QUIT:
|
|
return -EPIPE;
|
|
case EXPOACT_CLOSE:
|
|
/*
|
|
* Password textline closed (Enter pressed) - treat as
|
|
* selection
|
|
*/
|
|
*seqp = act.select.id - ITEM_PASS;
|
|
break;
|
|
case EXPOACT_CLICK:
|
|
if (act.select.id == OBJ_SETTINGS)
|
|
return -ECOMM; /* layout change request */
|
|
return -EAGAIN;
|
|
case EXPOACT_SETTINGS:
|
|
return -ECOMM; /* layout change request */
|
|
default:
|
|
return -EAGAIN;
|
|
}
|
|
|
|
return 0;
|
|
}
|