Merge branch '2022-01-16-bootstd-updates'

To quote the author:
So far standard boot lacks a boot menu, although it is possible to create
a rudimentary one using the existing 'bootmenu' command.

Even then, this text-based menu offer only basic functionality and does
not take full advantage of the displays which are common on many devices.

This series provides a 'bootflow menu' command which allows the user to
select from the available bootflows. An attempt is made to show the name
of the available operating systems, by reading more information into the
bootflow. A logo can be read also, where supported, so that this can be
presented to the user when an option is highlighted.

Full use is made of TrueType fonts, if enabled. For cases where only a
serial console is available, it falls back to a simple text-based menu.

All of this is implementing using a new 'expo' construct, a collection of
scenes (like menu screens) which can be navigated by the user to view
information and select options. This is fairly general and should be able
to cope with a wider array of use cases, with less hacking of the menu
code, such as is currently needed for CMD_BOOTEFI_BOOTMGR.

Of course it would be possible to enhance the existing menu rather than
creating a new setup. Instead it seems better to make the existing menu
use expo, if code space permits. It avoids the event-loop problem and
should be more extensible, given its loosely coupled components and use of
IDs instead of pointers. Further motivation is provided in the
documentation.

For now the CLI keypress-decoding code is split out to be used by the new
menu. The key codes defined by menu.h are reused also.

This is of course just a starting point. Some ideas for future work are
included in the documentation.
This commit is contained in:
Tom Rini 2023-01-17 08:55:40 -05:00
commit 5b958dea5c
54 changed files with 4109 additions and 507 deletions

View file

@ -154,7 +154,7 @@ static int do_stm32prog(struct cmd_tbl *cmdtp, int flag, int argc,
do_bootz(cmdtp, 0, 4, bootm_argv);
}
if (data->script)
image_source_script(data->script, NULL, NULL);
cmd_source_script(data->script, NULL, NULL);
if (reset) {
puts("Reset...\n");

View file

@ -94,6 +94,10 @@
compatible = "u-boot,distro-efi";
};
theme {
font-size = <30>;
};
/*
* This is used for the VBE OS-request tests. A FAT filesystem
* created in a partition with the VBE information appearing
@ -1013,6 +1017,13 @@
non-removable;
};
/* This is used for bootstd bootmenu tests */
mmc4 {
status = "disabled";
compatible = "sandbox,mmc";
filename = "mmc4.img";
};
pch {
compatible = "sandbox,pch";
};

View file

@ -557,6 +557,18 @@ config VPL_BOOTMETH_VBE_SIMPLE_FW
endif # BOOTMETH_VBE
config EXPO
bool "Support for expos - groups of scenes displaying a UI"
default y if BOOTMETH_VBE
help
An expo is a way of presenting and collecting information from the
user. It consists of a collection of 'scenes' of which only one is
presented at a time. An expo is typically used to show a boot menu
and allow settings to be changed.
The expo can be presented in graphics form using a vidconsole, or in
text form on a serial console.
config BOOTMETH_SANDBOX
def_bool y
depends on SANDBOX

View file

@ -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,6 +48,8 @@ ifdef CONFIG_SPL_BUILD
obj-$(CONFIG_SPL_LOAD_FIT) += common_fit.o
endif
obj-$(CONFIG_$(SPL_TPL_)BOOTMETH_VBE) += expo.o scene.o scene_menu.o
obj-$(CONFIG_$(SPL_TPL_)BOOTMETH_VBE) += vbe.o vbe_request.o
obj-$(CONFIG_$(SPL_TPL_)BOOTMETH_VBE_SIMPLE) += vbe_simple.o
obj-$(CONFIG_$(SPL_TPL_)BOOTMETH_VBE_SIMPLE_FW) += vbe_simple_fw.o

View file

@ -354,6 +354,7 @@ void bootflow_free(struct bootflow *bflow)
free(bflow->subdir);
free(bflow->fname);
free(bflow->buf);
free(bflow->os_name);
}
void bootflow_remove(struct bootflow *bflow)

47
boot/bootflow_internal.h Normal file
View 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 */

284
boot/bootflow_menu.c Normal file
View file

@ -0,0 +1,284 @@
// 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_apply_theme(struct expo *exp, ofnode node)
{
struct menu_priv *priv = exp->priv;
struct scene *scn;
u32 font_size;
int ret;
log_debug("Applying theme %s\n", ofnode_get_name(node));
scn = expo_lookup_scene_id(exp, MAIN);
if (!scn)
return log_msg_ret("scn", -ENOENT);
/* Avoid error-checking optional items */
if (!ofnode_read_u32(node, "font-size", &font_size)) {
int i;
log_debug("font size %d\n", font_size);
scene_txt_set_font(scn, OBJ_PROMPT, NULL, font_size);
scene_txt_set_font(scn, OBJ_POINTER, NULL, font_size);
for (i = 0; i < priv->num_bootflows; i++) {
ret = scene_txt_set_font(scn, ITEM_DESC + i, NULL,
font_size);
if (ret)
return log_msg_ret("des", ret);
scene_txt_set_font(scn, ITEM_KEY + i, NULL, font_size);
scene_txt_set_font(scn, ITEM_LABEL + i, NULL,
font_size);
}
}
ret = scene_arrange(scn);
if (ret)
return log_msg_ret("arr", ret);
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);
if (ofnode_valid(std->theme)) {
ret = bootflow_menu_apply_theme(exp, std->theme);
if (ret)
return log_msg_ret("thm", 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;
}

View file

@ -290,11 +290,35 @@ int bootmeth_try_file(struct bootflow *bflow, struct blk_desc *desc,
return 0;
}
int bootmeth_alloc_file(struct bootflow *bflow, uint size_limit, uint align)
static int alloc_file(const char *fname, uint size, void **bufp)
{
loff_t bytes_read;
ulong addr;
char *buf;
int ret;
buf = malloc(size + 1);
if (!buf)
return log_msg_ret("buf", -ENOMEM);
addr = map_to_sysmem(buf);
ret = fs_read(fname, addr, 0, size, &bytes_read);
if (ret) {
free(buf);
return log_msg_ret("read", ret);
}
if (size != bytes_read)
return log_msg_ret("bread", -EINVAL);
buf[size] = '\0';
*bufp = buf;
return 0;
}
int bootmeth_alloc_file(struct bootflow *bflow, uint size_limit, uint align)
{
void *buf;
uint size;
int ret;
@ -303,25 +327,52 @@ int bootmeth_alloc_file(struct bootflow *bflow, uint size_limit, uint align)
if (size > size_limit)
return log_msg_ret("chk", -E2BIG);
buf = memalign(align, size + 1);
if (!buf)
return log_msg_ret("buf", -ENOMEM);
addr = map_to_sysmem(buf);
ret = alloc_file(bflow->fname, bflow->size, &buf);
if (ret)
return log_msg_ret("all", ret);
ret = fs_read(bflow->fname, addr, 0, 0, &bytes_read);
if (ret) {
free(buf);
return log_msg_ret("read", ret);
}
if (size != bytes_read)
return log_msg_ret("bread", -EINVAL);
buf[size] = '\0';
bflow->state = BOOTFLOWST_READY;
bflow->buf = buf;
return 0;
}
int bootmeth_alloc_other(struct bootflow *bflow, const char *fname,
void **bufp, uint *sizep)
{
struct blk_desc *desc = NULL;
char path[200];
loff_t size;
void *buf;
int ret;
snprintf(path, sizeof(path), "%s%s", bflow->subdir, fname);
log_debug("trying: %s\n", path);
if (bflow->blk)
desc = dev_get_uclass_plat(bflow->blk);
ret = setup_fs(bflow, desc);
if (ret)
return log_msg_ret("fs", ret);
ret = fs_size(path, &size);
log_debug(" %s - err=%d\n", path, ret);
ret = setup_fs(bflow, desc);
if (ret)
return log_msg_ret("fs", ret);
ret = alloc_file(path, size, &buf);
if (ret)
return log_msg_ret("all", ret);
*bufp = buf;
*sizep = size;
return 0;
}
int bootmeth_common_read_file(struct udevice *dev, struct bootflow *bflow,
const char *file_path, ulong addr, ulong *sizep)
{

View file

@ -66,6 +66,38 @@ static int distro_check(struct udevice *dev, struct bootflow_iter *iter)
return 0;
}
/**
* distro_fill_info() - Decode the extlinux file to find out distro info
*
* @bflow: Bootflow to process
* @return 0 if OK, -ve on error
*/
static int distro_fill_info(struct bootflow *bflow)
{
struct membuff mb;
char line[200];
char *data;
int len;
log_debug("parsing bflow file size %x\n", bflow->size);
membuff_init(&mb, bflow->buf, bflow->size);
membuff_putraw(&mb, bflow->size, true, &data);
while (len = membuff_readline(&mb, line, sizeof(line) - 1, ' '), len) {
char *tok, *p = line;
tok = strsep(&p, " ");
if (p) {
if (!strcmp("label", tok)) {
bflow->os_name = strdup(p);
if (!bflow->os_name)
return log_msg_ret("os", -ENOMEM);
}
}
}
return 0;
}
static int distro_read_bootflow(struct udevice *dev, struct bootflow *bflow)
{
struct blk_desc *desc;
@ -99,6 +131,10 @@ static int distro_read_bootflow(struct udevice *dev, struct bootflow *bflow)
if (ret)
return log_msg_ret("read", ret);
ret = distro_fill_info(bflow);
if (ret)
return log_msg_ret("inf", ret);
return 0;
}

View file

@ -35,6 +35,36 @@ static int script_check(struct udevice *dev, struct bootflow_iter *iter)
return 0;
}
/**
* script_fill_info() - Decode the U-Boot script to find out distro info
*
* @bflow: Bootflow to process
* @return 0 if OK, -ve on error
*/
static int script_fill_info(struct bootflow *bflow)
{
char *name = NULL;
char *data;
uint len;
int ret;
log_debug("parsing bflow file size %x\n", bflow->size);
ret = image_locate_script(bflow->buf, bflow->size, NULL, NULL, &data, &len);
if (!ret) {
if (strstr(data, "armbianEnv"))
name = "Armbian";
}
if (name) {
bflow->os_name = strdup(name);
if (!bflow->os_name)
return log_msg_ret("os", -ENOMEM);
}
return 0;
}
static int script_read_bootflow(struct udevice *dev, struct bootflow *bflow)
{
struct blk_desc *desc = NULL;
@ -75,6 +105,14 @@ static int script_read_bootflow(struct udevice *dev, struct bootflow *bflow)
if (ret)
return log_msg_ret("read", ret);
ret = script_fill_info(bflow);
if (ret)
return log_msg_ret("inf", ret);
ret = bootmeth_alloc_other(bflow, "boot.bmp", &bflow->logo,
&bflow->logo_size);
/* ignore error */
return 0;
}
@ -101,7 +139,7 @@ static int script_boot(struct udevice *dev, struct bootflow *bflow)
log_debug("mmc_bootdev: %s\n", env_get("mmc_bootdev"));
addr = map_to_sysmem(bflow->buf);
ret = image_source_script(addr, NULL, NULL);
ret = cmd_source_script(addr, NULL, NULL);
if (ret)
return log_msg_ret("boot", ret);

View file

@ -33,6 +33,8 @@ static int bootstd_of_to_plat(struct udevice *dev)
&priv->prefixes);
dev_read_string_list(dev, "bootdev-order",
&priv->bootdev_order);
priv->theme = ofnode_find_subnode(dev_ofnode(dev), "theme");
}
return 0;

170
boot/expo.c Normal file
View file

@ -0,0 +1,170 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Implementation of a expo, a collection of scenes providing menu options
*
* Copyright 2022 Google LLC
* Written by Simon Glass <sjg@chromium.org>
*/
#include <common.h>
#include <dm.h>
#include <expo.h>
#include <malloc.h>
#include <video.h>
#include "scene_internal.h"
int expo_new(const char *name, void *priv, struct expo **expp)
{
struct expo *exp;
exp = calloc(1, sizeof(struct expo));
if (!exp)
return log_msg_ret("expo", -ENOMEM);
exp->name = strdup(name);
if (!exp->name) {
free(exp);
return log_msg_ret("name", -ENOMEM);
}
exp->priv = priv;
INIT_LIST_HEAD(&exp->scene_head);
INIT_LIST_HEAD(&exp->str_head);
*expp = exp;
return 0;
}
static void estr_destroy(struct expo_string *estr)
{
free(estr);
}
void expo_destroy(struct expo *exp)
{
struct scene *scn, *next;
struct expo_string *estr, *enext;
list_for_each_entry_safe(scn, next, &exp->scene_head, sibling)
scene_destroy(scn);
list_for_each_entry_safe(estr, enext, &exp->str_head, sibling)
estr_destroy(estr);
free(exp->name);
free(exp);
}
int expo_str(struct expo *exp, const char *name, uint id, const char *str)
{
struct expo_string *estr;
estr = calloc(1, sizeof(struct expo_string));
if (!estr)
return log_msg_ret("obj", -ENOMEM);
estr->id = resolve_id(exp, id);
estr->str = str;
list_add_tail(&estr->sibling, &exp->str_head);
return estr->id;
}
const char *expo_get_str(struct expo *exp, uint id)
{
struct expo_string *estr;
list_for_each_entry(estr, &exp->str_head, sibling) {
if (estr->id == id)
return estr->str;
}
return NULL;
}
int expo_set_display(struct expo *exp, struct udevice *dev)
{
exp->display = dev;
return 0;
}
void exp_set_text_mode(struct expo *exp, bool text_mode)
{
exp->text_mode = text_mode;
}
struct scene *expo_lookup_scene_id(struct expo *exp, uint scene_id)
{
struct scene *scn;
list_for_each_entry(scn, &exp->scene_head, sibling) {
if (scn->id == scene_id)
return scn;
}
return NULL;
}
int expo_set_scene_id(struct expo *exp, uint scene_id)
{
if (!expo_lookup_scene_id(exp, scene_id))
return log_msg_ret("id", -ENOENT);
exp->scene_id = scene_id;
return 0;
}
int expo_render(struct expo *exp)
{
struct udevice *dev = exp->display;
struct video_priv *vid_priv = dev_get_uclass_priv(dev);
struct scene *scn = NULL;
u32 colour;
int ret;
colour = video_index_to_colour(vid_priv, VID_WHITE);
ret = video_fill(dev, colour);
if (ret)
return log_msg_ret("fill", ret);
if (exp->scene_id) {
scn = expo_lookup_scene_id(exp, exp->scene_id);
if (!scn)
return log_msg_ret("scn", -ENOENT);
ret = scene_render(scn);
if (ret)
return log_msg_ret("ren", ret);
}
video_sync(dev, true);
return scn ? 0 : -ECHILD;
}
int expo_send_key(struct expo *exp, int key)
{
struct scene *scn = NULL;
if (exp->scene_id) {
int ret;
scn = expo_lookup_scene_id(exp, exp->scene_id);
if (!scn)
return log_msg_ret("scn", -ENOENT);
ret = scene_send_key(scn, key, &exp->action);
if (ret)
return log_msg_ret("key", ret);
}
return scn ? 0 : -ECHILD;
}
int expo_action_get(struct expo *exp, struct expo_action *act)
{
*act = exp->action;
exp->action.type = EXPOACT_NONE;
return act->type == EXPOACT_NONE ? -EAGAIN : 0;
}

View file

@ -971,3 +971,162 @@ void genimg_print_time(time_t timestamp)
tm.tm_year, tm.tm_mon, tm.tm_mday,
tm.tm_hour, tm.tm_min, tm.tm_sec);
}
/**
* get_default_image() - Return default property from /images
*
* Return: Pointer to value of default property (or NULL)
*/
static const char *get_default_image(const void *fit)
{
int images_noffset;
images_noffset = fdt_path_offset(fit, FIT_IMAGES_PATH);
if (images_noffset < 0)
return NULL;
return fdt_getprop(fit, images_noffset, FIT_DEFAULT_PROP, NULL);
}
int image_locate_script(void *buf, int size, const char *fit_uname,
const char *confname, char **datap, uint *lenp)
{
const struct legacy_img_hdr *hdr;
const void *fit_data;
const void *fit_hdr;
size_t fit_len;
int noffset;
int verify;
ulong len;
u32 *data;
verify = env_get_yesno("verify");
switch (genimg_get_format(buf)) {
case IMAGE_FORMAT_LEGACY:
if (IS_ENABLED(CONFIG_LEGACY_IMAGE_FORMAT)) {
hdr = buf;
if (!image_check_magic(hdr)) {
puts("Bad magic number\n");
return 1;
}
if (!image_check_hcrc(hdr)) {
puts("Bad header crc\n");
return 1;
}
if (verify) {
if (!image_check_dcrc(hdr)) {
puts("Bad data crc\n");
return 1;
}
}
if (!image_check_type(hdr, IH_TYPE_SCRIPT)) {
puts("Bad image type\n");
return 1;
}
/* get length of script */
data = (u32 *)image_get_data(hdr);
len = uimage_to_cpu(*data);
if (!len) {
puts("Empty Script\n");
return 1;
}
/*
* scripts are just multi-image files with one
* component, so seek past the zero-terminated sequence
* of image lengths to get to the actual image data
*/
while (*data++);
}
break;
case IMAGE_FORMAT_FIT:
if (IS_ENABLED(CONFIG_FIT)) {
fit_hdr = buf;
if (fit_check_format(fit_hdr, IMAGE_SIZE_INVAL)) {
puts("Bad FIT image format\n");
return 1;
}
if (!fit_uname) {
/* If confname is empty, use the default */
if (confname && *confname)
noffset = fit_conf_get_node(fit_hdr, confname);
else
noffset = fit_conf_get_node(fit_hdr, NULL);
if (noffset < 0) {
if (!confname)
goto fallback;
printf("Could not find config %s\n", confname);
return 1;
}
if (verify && fit_config_verify(fit_hdr, noffset))
return 1;
noffset = fit_conf_get_prop_node(fit_hdr,
noffset,
FIT_SCRIPT_PROP,
IH_PHASE_NONE);
if (noffset < 0) {
if (!confname)
goto fallback;
printf("Could not find script in %s\n", confname);
return 1;
}
} else {
fallback:
if (!fit_uname || !*fit_uname)
fit_uname = get_default_image(fit_hdr);
if (!fit_uname) {
puts("No FIT subimage unit name\n");
return 1;
}
/* get script component image node offset */
noffset = fit_image_get_node(fit_hdr, fit_uname);
if (noffset < 0) {
printf("Can't find '%s' FIT subimage\n",
fit_uname);
return 1;
}
}
if (!fit_image_check_type(fit_hdr, noffset,
IH_TYPE_SCRIPT)) {
puts("Not a image image\n");
return 1;
}
/* verify integrity */
if (verify && !fit_image_verify(fit_hdr, noffset)) {
puts("Bad Data Hash\n");
return 1;
}
/* get script subimage data address and length */
if (fit_image_get_data(fit_hdr, noffset, &fit_data, &fit_len)) {
puts("Could not find script subimage data\n");
return 1;
}
data = (u32 *)fit_data;
len = (ulong)fit_len;
}
break;
default:
puts("Wrong image format for \"source\" command\n");
return -EPERM;
}
*datap = (char *)data;
*lenp = len;
return 0;
}

414
boot/scene.c Normal file
View file

@ -0,0 +1,414 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Implementation of a scene, a collection of text/image/menu items in an expo
*
* Copyright 2022 Google LLC
* Written by Simon Glass <sjg@chromium.org>
*/
#include <common.h>
#include <dm.h>
#include <expo.h>
#include <malloc.h>
#include <mapmem.h>
#include <video.h>
#include <video_console.h>
#include <linux/input.h>
#include "scene_internal.h"
uint resolve_id(struct expo *exp, uint id)
{
if (!id)
id = exp->next_id++;
else if (id >= exp->next_id)
exp->next_id = id + 1;
return id;
}
int scene_new(struct expo *exp, const char *name, uint id, struct scene **scnp)
{
struct scene *scn;
scn = calloc(1, sizeof(struct scene));
if (!scn)
return log_msg_ret("expo", -ENOMEM);
scn->name = strdup(name);
if (!scn->name) {
free(scn);
return log_msg_ret("name", -ENOMEM);
}
INIT_LIST_HEAD(&scn->obj_head);
scn->id = resolve_id(exp, id);
scn->expo = exp;
list_add_tail(&scn->sibling, &exp->scene_head);
*scnp = scn;
return scn->id;
}
void scene_obj_destroy(struct scene_obj *obj)
{
if (obj->type == SCENEOBJT_MENU)
scene_menu_destroy((struct scene_obj_menu *)obj);
free(obj->name);
free(obj);
}
void scene_destroy(struct scene *scn)
{
struct scene_obj *obj, *next;
list_for_each_entry_safe(obj, next, &scn->obj_head, sibling)
scene_obj_destroy(obj);
free(scn->name);
free(scn->title);
free(scn);
}
int scene_title_set(struct scene *scn, const char *title)
{
free(scn->title);
scn->title = strdup(title);
if (!scn->title)
return log_msg_ret("tit", -ENOMEM);
return 0;
}
int scene_obj_count(struct scene *scn)
{
struct scene_obj *obj;
int count = 0;
list_for_each_entry(obj, &scn->obj_head, sibling)
count++;
return count;
}
void *scene_obj_find(struct scene *scn, uint id, enum scene_obj_t type)
{
struct scene_obj *obj;
list_for_each_entry(obj, &scn->obj_head, sibling) {
if (obj->id == id &&
(type == SCENEOBJT_NONE || obj->type == type))
return obj;
}
return NULL;
}
int scene_obj_add(struct scene *scn, const char *name, uint id,
enum scene_obj_t type, uint size, struct scene_obj **objp)
{
struct scene_obj *obj;
obj = calloc(1, size);
if (!obj)
return log_msg_ret("obj", -ENOMEM);
obj->name = strdup(name);
if (!obj->name) {
free(obj);
return log_msg_ret("name", -ENOMEM);
}
obj->id = resolve_id(scn->expo, id);
obj->scene = scn;
obj->type = type;
list_add_tail(&obj->sibling, &scn->obj_head);
*objp = obj;
return obj->id;
}
int scene_img(struct scene *scn, const char *name, uint id, char *data,
struct scene_obj_img **imgp)
{
struct scene_obj_img *img;
int ret;
ret = scene_obj_add(scn, name, id, SCENEOBJT_IMAGE,
sizeof(struct scene_obj_img),
(struct scene_obj **)&img);
if (ret < 0)
return log_msg_ret("obj", -ENOMEM);
img->data = data;
if (imgp)
*imgp = img;
return img->obj.id;
}
int scene_txt(struct scene *scn, const char *name, uint id, uint str_id,
struct scene_obj_txt **txtp)
{
struct scene_obj_txt *txt;
int ret;
ret = scene_obj_add(scn, name, id, SCENEOBJT_TEXT,
sizeof(struct scene_obj_txt),
(struct scene_obj **)&txt);
if (ret < 0)
return log_msg_ret("obj", -ENOMEM);
txt->str_id = str_id;
if (txtp)
*txtp = txt;
return txt->obj.id;
}
int scene_txt_str(struct scene *scn, const char *name, uint id, uint str_id,
const char *str, struct scene_obj_txt **txtp)
{
struct scene_obj_txt *txt;
int ret;
ret = expo_str(scn->expo, name, str_id, str);
if (ret < 0)
return log_msg_ret("str", ret);
else if (ret != str_id)
return log_msg_ret("id", -EEXIST);
ret = scene_obj_add(scn, name, id, SCENEOBJT_TEXT,
sizeof(struct scene_obj_txt),
(struct scene_obj **)&txt);
if (ret < 0)
return log_msg_ret("obj", -ENOMEM);
txt->str_id = str_id;
if (txtp)
*txtp = txt;
return txt->obj.id;
}
int scene_txt_set_font(struct scene *scn, uint id, const char *font_name,
uint font_size)
{
struct scene_obj_txt *txt;
txt = scene_obj_find(scn, id, SCENEOBJT_TEXT);
if (!txt)
return log_msg_ret("find", -ENOENT);
txt->font_name = font_name;
txt->font_size = font_size;
return 0;
}
int scene_obj_set_pos(struct scene *scn, uint id, int x, int y)
{
struct scene_obj *obj;
obj = scene_obj_find(scn, id, SCENEOBJT_NONE);
if (!obj)
return log_msg_ret("find", -ENOENT);
obj->x = x;
obj->y = y;
if (obj->type == SCENEOBJT_MENU)
scene_menu_arrange(scn, (struct scene_obj_menu *)obj);
return 0;
}
int scene_obj_set_hide(struct scene *scn, uint id, bool hide)
{
struct scene_obj *obj;
obj = scene_obj_find(scn, id, SCENEOBJT_NONE);
if (!obj)
return log_msg_ret("find", -ENOENT);
obj->hide = hide;
return 0;
}
int scene_obj_get_hw(struct scene *scn, uint id, int *widthp)
{
struct scene_obj *obj;
obj = scene_obj_find(scn, id, SCENEOBJT_NONE);
if (!obj)
return log_msg_ret("find", -ENOENT);
switch (obj->type) {
case SCENEOBJT_NONE:
case SCENEOBJT_MENU:
break;
case SCENEOBJT_IMAGE: {
struct scene_obj_img *img = (struct scene_obj_img *)obj;
ulong width, height;
uint bpix;
video_bmp_get_info(img->data, &width, &height, &bpix);
if (widthp)
*widthp = width;
return height;
}
case SCENEOBJT_TEXT: {
struct scene_obj_txt *txt = (struct scene_obj_txt *)obj;
struct expo *exp = scn->expo;
if (widthp)
*widthp = 16; /* fake value for now */
if (txt->font_size)
return txt->font_size;
if (exp->display)
return video_default_font_height(exp->display);
/* use a sensible default */
return 16;
}
}
return 0;
}
/**
* scene_obj_render() - Render an object
*
*/
static int scene_obj_render(struct scene_obj *obj, bool text_mode)
{
struct scene *scn = obj->scene;
struct expo *exp = scn->expo;
struct udevice *cons, *dev = exp->display;
int x, y, ret;
cons = NULL;
if (!text_mode) {
ret = device_find_first_child_by_uclass(dev,
UCLASS_VIDEO_CONSOLE,
&cons);
}
x = obj->x;
y = obj->y;
switch (obj->type) {
case SCENEOBJT_NONE:
break;
case SCENEOBJT_IMAGE: {
struct scene_obj_img *img = (struct scene_obj_img *)obj;
if (!cons)
return -ENOTSUPP;
ret = video_bmp_display(dev, map_to_sysmem(img->data), x, y,
true);
if (ret < 0)
return log_msg_ret("img", ret);
break;
}
case SCENEOBJT_TEXT: {
struct scene_obj_txt *txt = (struct scene_obj_txt *)obj;
const char *str;
if (!cons)
return -ENOTSUPP;
if (txt->font_name || txt->font_size) {
ret = vidconsole_select_font(cons,
txt->font_name,
txt->font_size);
} else {
ret = vidconsole_select_font(cons, NULL, 0);
}
if (ret && ret != -ENOSYS)
return log_msg_ret("font", ret);
vidconsole_set_cursor_pos(cons, x, y);
str = expo_get_str(exp, txt->str_id);
if (str)
vidconsole_put_string(cons, str);
break;
}
case SCENEOBJT_MENU: {
struct scene_obj_menu *menu = (struct scene_obj_menu *)obj;
/*
* With a vidconsole, the text and item pointer are rendered as
* normal objects so we don't need to do anything here. The menu
* simply controls where they are positioned.
*/
if (cons)
return -ENOTSUPP;
ret = scene_menu_display(menu);
if (ret < 0)
return log_msg_ret("img", ret);
break;
}
}
return 0;
}
int scene_arrange(struct scene *scn)
{
struct scene_obj *obj;
int ret;
list_for_each_entry(obj, &scn->obj_head, sibling) {
if (obj->type == SCENEOBJT_MENU) {
struct scene_obj_menu *menu;
menu = (struct scene_obj_menu *)obj,
ret = scene_menu_arrange(scn, menu);
if (ret)
return log_msg_ret("arr", ret);
}
}
return 0;
}
int scene_render(struct scene *scn)
{
struct expo *exp = scn->expo;
struct scene_obj *obj;
int ret;
list_for_each_entry(obj, &scn->obj_head, sibling) {
if (!obj->hide) {
ret = scene_obj_render(obj, exp->text_mode);
if (ret && ret != -ENOTSUPP)
return log_msg_ret("ren", ret);
}
}
return 0;
}
int scene_send_key(struct scene *scn, int key, struct expo_action *event)
{
struct scene_obj *obj;
int ret;
list_for_each_entry(obj, &scn->obj_head, sibling) {
if (obj->type == SCENEOBJT_MENU) {
struct scene_obj_menu *menu;
menu = (struct scene_obj_menu *)obj,
ret = scene_menu_send_key(scn, menu, key, event);
if (ret)
return log_msg_ret("key", ret);
/* only allow one menu */
ret = scene_menu_arrange(scn, menu);
if (ret)
return log_msg_ret("arr", ret);
break;
}
}
return 0;
}

123
boot/scene_internal.h Normal file
View file

@ -0,0 +1,123 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* Internal header file for scenes
*
* Copyright 2022 Google LLC
* Written by Simon Glass <sjg@chromium.org>
*/
#ifndef __SCENE_INTERNAL_H
#define __SCENE_INTERNAL_H
/**
* expo_lookup_scene_id() - Look up a scene ID
*
* @exp: Expo to use
* @id: scene ID to look up
* Returns: Scene for that ID, or NULL if none
*/
struct scene *expo_lookup_scene_id(struct expo *exp, uint scene_id);
/**
* resolve_id() - Automatically allocate an ID if needed
*
* @exp: Expo to use
* @id: ID to use, or 0 to auto-allocate one
* @return: Either @id, or the auto-allocated ID
*/
uint resolve_id(struct expo *exp, uint id);
/**
* scene_obj_find() - Find an object in a scene
*
* Note that @type is used to restrict the search when the object type is known.
* If any type is acceptable, set @type to SCENEOBJT_NONE
*
* @scn: Scene to search
* @id: ID of object to find
* @type: Type of the object, or SCENEOBJT_NONE to match any type
*/
void *scene_obj_find(struct scene *scn, uint id, enum scene_obj_t type);
/**
* scene_obj_add() - Add a new object to a scene
*
* @scn: Scene to update
* @name: Name to use (this is allocated by this call)
* @id: ID to use for the new object (0 to allocate one)
* @type: Type of object to add
* @size: Size to allocate for the object, in bytes
* @objp: Returns a pointer to the new object (must not be NULL)
* Returns: ID number for the object (generally @id), or -ve on error
*/
int scene_obj_add(struct scene *scn, const char *name, uint id,
enum scene_obj_t type, uint size, struct scene_obj **objp);
/**
* scene_menu_arrange() - Set the position of things in the menu
*
* This updates any items associated with a menu to make sure they are
* positioned correctly relative to the menu. It also selects the first item
* if not already done
*
* @scn: Scene to update
* @menu: Menu to process
*/
int scene_menu_arrange(struct scene *scn, struct scene_obj_menu *menu);
/**
* scene_menu_send_key() - Send a key to a menu for processing
*
* @scn: Scene to use
* @menu: Menu to use
* @key: Key code to send (KEY_...)
* @event: Place to put any event which is generated by the key
* @return 0 if OK, -ENOTTY if there is no current menu item, other -ve on other
* error
*/
int scene_menu_send_key(struct scene *scn, struct scene_obj_menu *menu, int key,
struct expo_action *event);
/**
* scene_menu_destroy() - Destroy a menu in a scene
*
* @scn: Scene to destroy
*/
void scene_menu_destroy(struct scene_obj_menu *menu);
/**
* scene_menu_display() - Display a menu as text
*
* @menu: Menu to display
* @return 0 if OK, -ENOENT if @id is invalid
*/
int scene_menu_display(struct scene_obj_menu *menu);
/**
* scene_destroy() - Destroy a scene and all its memory
*
* @scn: Scene to destroy
*/
void scene_destroy(struct scene *scn);
/**
* scene_render() - Render a scene
*
* This is called from expo_render()
*
* @scn: Scene to render
* Returns: 0 if OK, -ve on error
*/
int scene_render(struct scene *scn);
/**
* scene_send_key() - set a keypress to a scene
*
* @scn: Scene to receive the key
* @key: Key to send (KEYCODE_UP)
* @event: Returns resulting event from this keypress
* Returns: 0 if OK, -ve on error
*/
int scene_send_key(struct scene *scn, int key, struct expo_action *event);
#endif /* __SCENE_INTERNAL_H */

390
boot/scene_menu.c Normal file
View file

@ -0,0 +1,390 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Implementation of a menu in a scene
*
* Copyright 2022 Google LLC
* Written by Simon Glass <sjg@chromium.org>
*/
#define LOG_CATEGORY LOGC_BOOT
#include <common.h>
#include <dm.h>
#include <expo.h>
#include <malloc.h>
#include <mapmem.h>
#include <menu.h>
#include <video.h>
#include <video_console.h>
#include <linux/input.h>
#include "scene_internal.h"
static void scene_menuitem_destroy(struct scene_menitem *item)
{
free(item->name);
free(item);
}
void scene_menu_destroy(struct scene_obj_menu *menu)
{
struct scene_menitem *item, *next;
list_for_each_entry_safe(item, next, &menu->item_head, sibling)
scene_menuitem_destroy(item);
}
/**
* menu_point_to_item() - Point to a particular menu item
*
* Sets the currently pointed-to / highlighted menu item
*/
static void menu_point_to_item(struct scene_obj_menu *menu, uint item_id)
{
menu->cur_item_id = item_id;
}
int scene_menu_arrange(struct scene *scn, struct scene_obj_menu *menu)
{
struct scene_menitem *item;
int y, cur_y;
int ret;
y = menu->obj.y;
if (menu->title_id) {
ret = scene_obj_set_pos(scn, menu->title_id, menu->obj.x, y);
if (ret < 0)
return log_msg_ret("tit", ret);
ret = scene_obj_get_hw(scn, menu->title_id, NULL);
if (ret < 0)
return log_msg_ret("hei", ret);
y += ret * 2;
}
/*
* Currently everything is hard-coded to particular columns so this
* won't work on small displays and looks strange if the font size is
* small. This can be updated once text measuring is supported in
* vidconsole
*/
cur_y = -1;
list_for_each_entry(item, &menu->item_head, sibling) {
int height;
ret = scene_obj_get_hw(scn, item->desc_id, NULL);
if (ret < 0)
return log_msg_ret("get", ret);
height = ret;
if (item->flags & SCENEMIF_GAP_BEFORE)
y += height;
/* select an item if not done already */
if (!menu->cur_item_id)
menu_point_to_item(menu, item->id);
/*
* Put the label on the left, then leave a space for the
* pointer, then the key and the description
*/
if (item->label_id) {
ret = scene_obj_set_pos(scn, item->label_id, menu->obj.x,
y);
if (ret < 0)
return log_msg_ret("nam", ret);
}
ret = scene_obj_set_pos(scn, item->key_id, menu->obj.x + 230,
y);
if (ret < 0)
return log_msg_ret("key", ret);
ret = scene_obj_set_pos(scn, item->desc_id, menu->obj.x + 280,
y);
if (ret < 0)
return log_msg_ret("des", ret);
if (menu->cur_item_id == item->id)
cur_y = y;
if (item->preview_id) {
bool hide;
/*
* put all previews on top of each other, on the right
* size of the display
*/
ret = scene_obj_set_pos(scn, item->preview_id, -4, y);
if (ret < 0)
return log_msg_ret("prev", ret);
hide = menu->cur_item_id != item->id;
ret = scene_obj_set_hide(scn, item->preview_id, hide);
if (ret < 0)
return log_msg_ret("hid", ret);
}
y += height;
}
if (menu->pointer_id && cur_y != -1) {
/*
* put the pointer to the right of and level with the item it
* points to
*/
ret = scene_obj_set_pos(scn, menu->pointer_id,
menu->obj.x + 200, cur_y);
if (ret < 0)
return log_msg_ret("ptr", ret);
}
return 0;
}
int scene_menu(struct scene *scn, const char *name, uint id,
struct scene_obj_menu **menup)
{
struct scene_obj_menu *menu;
int ret;
ret = scene_obj_add(scn, name, id, SCENEOBJT_MENU,
sizeof(struct scene_obj_menu),
(struct scene_obj **)&menu);
if (ret < 0)
return log_msg_ret("obj", -ENOMEM);
if (menup)
*menup = menu;
INIT_LIST_HEAD(&menu->item_head);
ret = scene_menu_arrange(scn, menu);
if (ret)
return log_msg_ret("pos", ret);
return menu->obj.id;
}
static struct scene_menitem *scene_menu_find_key(struct scene *scn,
struct scene_obj_menu *menu,
int key)
{
struct scene_menitem *item;
list_for_each_entry(item, &menu->item_head, sibling) {
if (item->key_id) {
struct scene_obj_txt *txt;
const char *str;
txt = scene_obj_find(scn, item->key_id, SCENEOBJT_TEXT);
if (txt) {
str = expo_get_str(scn->expo, txt->str_id);
if (str && *str == key)
return item;
}
}
}
return NULL;
}
int scene_menu_send_key(struct scene *scn, struct scene_obj_menu *menu, int key,
struct expo_action *event)
{
struct scene_menitem *item, *cur, *key_item;
cur = NULL;
key_item = NULL;
if (!list_empty(&menu->item_head)) {
list_for_each_entry(item, &menu->item_head, sibling) {
/* select an item if not done already */
if (menu->cur_item_id == item->id) {
cur = item;
break;
}
}
}
if (!cur)
return -ENOTTY;
switch (key) {
case BKEY_UP:
if (item != list_first_entry(&menu->item_head,
struct scene_menitem, sibling)) {
item = list_entry(item->sibling.prev,
struct scene_menitem, sibling);
event->type = EXPOACT_POINT;
event->select.id = item->id;
log_debug("up to item %d\n", event->select.id);
}
break;
case BKEY_DOWN:
if (!list_is_last(&item->sibling, &menu->item_head)) {
item = list_entry(item->sibling.next,
struct scene_menitem, sibling);
event->type = EXPOACT_POINT;
event->select.id = item->id;
log_debug("down to item %d\n", event->select.id);
}
break;
case BKEY_SELECT:
event->type = EXPOACT_SELECT;
event->select.id = item->id;
log_debug("select item %d\n", event->select.id);
break;
case BKEY_QUIT:
event->type = EXPOACT_QUIT;
log_debug("quit\n");
break;
case '0'...'9':
key_item = scene_menu_find_key(scn, menu, key);
if (key_item) {
event->type = EXPOACT_SELECT;
event->select.id = key_item->id;
}
break;
}
menu_point_to_item(menu, item->id);
return 0;
}
int scene_menuitem(struct scene *scn, uint menu_id, const char *name, uint id,
uint key_id, uint label_id, uint desc_id, uint preview_id,
uint flags, struct scene_menitem **itemp)
{
struct scene_obj_menu *menu;
struct scene_menitem *item;
int ret;
menu = scene_obj_find(scn, menu_id, SCENEOBJT_MENU);
if (!menu)
return log_msg_ret("find", -ENOENT);
/* Check that the text ID is valid */
if (!scene_obj_find(scn, desc_id, SCENEOBJT_TEXT))
return log_msg_ret("txt", -EINVAL);
item = calloc(1, sizeof(struct scene_obj_menu));
if (!item)
return log_msg_ret("item", -ENOMEM);
item->name = strdup(name);
if (!item->name) {
free(item);
return log_msg_ret("name", -ENOMEM);
}
item->id = resolve_id(scn->expo, id);
item->key_id = key_id;
item->label_id = label_id;
item->desc_id = desc_id;
item->preview_id = preview_id;
item->flags = flags;
list_add_tail(&item->sibling, &menu->item_head);
ret = scene_menu_arrange(scn, menu);
if (ret)
return log_msg_ret("pos", ret);
if (itemp)
*itemp = item;
return item->id;
}
int scene_menu_set_title(struct scene *scn, uint id, uint title_id)
{
struct scene_obj_menu *menu;
struct scene_obj_txt *txt;
menu = scene_obj_find(scn, id, SCENEOBJT_MENU);
if (!menu)
return log_msg_ret("menu", -ENOENT);
/* Check that the ID is valid */
if (title_id) {
txt = scene_obj_find(scn, title_id, SCENEOBJT_TEXT);
if (!txt)
return log_msg_ret("txt", -EINVAL);
}
menu->title_id = title_id;
return 0;
}
int scene_menu_set_pointer(struct scene *scn, uint id, uint pointer_id)
{
struct scene_obj_menu *menu;
struct scene_obj *obj;
menu = scene_obj_find(scn, id, SCENEOBJT_MENU);
if (!menu)
return log_msg_ret("menu", -ENOENT);
/* Check that the ID is valid */
if (pointer_id) {
obj = scene_obj_find(scn, pointer_id, SCENEOBJT_NONE);
if (!obj)
return log_msg_ret("obj", -EINVAL);
}
menu->pointer_id = pointer_id;
return 0;
}
int scene_menu_display(struct scene_obj_menu *menu)
{
struct scene *scn = menu->obj.scene;
struct scene_obj_txt *pointer;
struct expo *exp = scn->expo;
struct scene_menitem *item;
const char *pstr;
printf("U-Boot : Boot Menu\n\n");
if (menu->title_id) {
struct scene_obj_txt *txt;
const char *str;
txt = scene_obj_find(scn, menu->title_id, SCENEOBJT_TEXT);
if (!txt)
return log_msg_ret("txt", -EINVAL);
str = expo_get_str(exp, txt->str_id);
printf("%s\n\n", str);
}
if (list_empty(&menu->item_head))
return 0;
pointer = scene_obj_find(scn, menu->pointer_id, SCENEOBJT_TEXT);
pstr = expo_get_str(scn->expo, pointer->str_id);
list_for_each_entry(item, &menu->item_head, sibling) {
struct scene_obj_txt *key = NULL, *label = NULL;
struct scene_obj_txt *desc = NULL;
const char *kstr = NULL, *lstr = NULL, *dstr = NULL;
key = scene_obj_find(scn, item->key_id, SCENEOBJT_TEXT);
if (key)
kstr = expo_get_str(exp, key->str_id);
label = scene_obj_find(scn, item->label_id, SCENEOBJT_TEXT);
if (label)
lstr = expo_get_str(exp, label->str_id);
desc = scene_obj_find(scn, item->desc_id, SCENEOBJT_TEXT);
if (desc)
dstr = expo_get_str(exp, desc->str_id);
printf("%3s %3s %-10s %s\n",
pointer && menu->cur_item_id == item->id ? pstr : "",
kstr, lstr, dstr);
}
return -ENOTSUPP;
}

View file

@ -337,6 +337,13 @@ static int do_bootflow_info(struct cmd_tbl *cmdtp, int flag, int argc,
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("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("Error: %d\n", bflow->err);
if (dump && bflow->buf) {
/* Set some sort of maximum on the size */
@ -382,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
@ -391,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
@ -403,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
);

View file

@ -4,6 +4,7 @@
*/
#include <charset.h>
#include <cli.h>
#include <common.h>
#include <command.h>
#include <ansi.h>
@ -84,38 +85,40 @@ static void bootmenu_print_entry(void *data)
static char *bootmenu_choice_entry(void *data)
{
struct cli_ch_state s_cch, *cch = &s_cch;
struct bootmenu_data *menu = data;
struct bootmenu_entry *iter;
enum bootmenu_key key = KEY_NONE;
int esc = 0;
enum bootmenu_key key = BKEY_NONE;
int i;
cli_ch_init(cch);
while (1) {
if (menu->delay >= 0) {
/* Autoboot was not stopped */
bootmenu_autoboot_loop(menu, &key, &esc);
key = bootmenu_autoboot_loop(menu, cch);
} else {
/* Some key was pressed, so autoboot was stopped */
bootmenu_loop(menu, &key, &esc);
key = bootmenu_loop(menu, cch);
}
switch (key) {
case KEY_UP:
case BKEY_UP:
if (menu->active > 0)
--menu->active;
/* no menu key selected, regenerate menu */
return NULL;
case KEY_DOWN:
case BKEY_DOWN:
if (menu->active < menu->count - 1)
++menu->active;
/* no menu key selected, regenerate menu */
return NULL;
case KEY_SELECT:
case BKEY_SELECT:
iter = menu->first;
for (i = 0; i < menu->active; ++i)
iter = iter->next;
return iter->key;
case KEY_QUIT:
case BKEY_QUIT:
/* Quit by choosing the last entry - U-Boot console */
iter = menu->first;
while (iter->next)

View file

@ -6,6 +6,7 @@
*/
#include <ansi.h>
#include <cli.h>
#include <common.h>
#include <charset.h>
#include <efi_loader.h>
@ -184,34 +185,36 @@ static void eficonfig_display_statusline(struct menu *m)
*/
static char *eficonfig_choice_entry(void *data)
{
int esc = 0;
struct cli_ch_state s_cch, *cch = &s_cch;
struct list_head *pos, *n;
struct eficonfig_entry *entry;
enum bootmenu_key key = KEY_NONE;
enum bootmenu_key key = BKEY_NONE;
struct efimenu *efi_menu = data;
cli_ch_init(cch);
while (1) {
bootmenu_loop((struct bootmenu_data *)efi_menu, &key, &esc);
key = bootmenu_loop((struct bootmenu_data *)efi_menu, cch);
switch (key) {
case KEY_UP:
case BKEY_UP:
if (efi_menu->active > 0)
--efi_menu->active;
/* no menu key selected, regenerate menu */
return NULL;
case KEY_DOWN:
case BKEY_DOWN:
if (efi_menu->active < efi_menu->count - 1)
++efi_menu->active;
/* no menu key selected, regenerate menu */
return NULL;
case KEY_SELECT:
case BKEY_SELECT:
list_for_each_safe(pos, n, &efi_menu->list) {
entry = list_entry(pos, struct eficonfig_entry, list);
if (entry->num == efi_menu->active)
return entry->key;
}
break;
case KEY_QUIT:
case BKEY_QUIT:
/* Quit by choosing the last entry */
entry = list_last_entry(&efi_menu->list, struct eficonfig_entry, list);
return entry->key;
@ -1862,16 +1865,17 @@ static void eficonfig_display_change_boot_order(struct efimenu *efi_menu)
*/
static efi_status_t eficonfig_choice_change_boot_order(struct efimenu *efi_menu)
{
int esc = 0;
struct cli_ch_state s_cch, *cch = &s_cch;
struct list_head *pos, *n;
enum bootmenu_key key = KEY_NONE;
enum bootmenu_key key = BKEY_NONE;
struct eficonfig_entry *entry, *tmp;
cli_ch_init(cch);
while (1) {
bootmenu_loop(NULL, &key, &esc);
key = bootmenu_loop(NULL, cch);
switch (key) {
case KEY_PLUS:
case BKEY_PLUS:
if (efi_menu->active > 0) {
list_for_each_safe(pos, n, &efi_menu->list) {
entry = list_entry(pos, struct eficonfig_entry, list);
@ -1885,11 +1889,11 @@ static efi_status_t eficonfig_choice_change_boot_order(struct efimenu *efi_menu)
list_add(&tmp->list, &entry->list);
}
fallthrough;
case KEY_UP:
case BKEY_UP:
if (efi_menu->active > 0)
--efi_menu->active;
return EFI_NOT_READY;
case KEY_MINUS:
case BKEY_MINUS:
if (efi_menu->active < efi_menu->count - 3) {
list_for_each_safe(pos, n, &efi_menu->list) {
entry = list_entry(pos, struct eficonfig_entry, list);
@ -1905,11 +1909,11 @@ static efi_status_t eficonfig_choice_change_boot_order(struct efimenu *efi_menu)
++efi_menu->active;
}
return EFI_NOT_READY;
case KEY_DOWN:
case BKEY_DOWN:
if (efi_menu->active < efi_menu->count - 1)
++efi_menu->active;
return EFI_NOT_READY;
case KEY_SELECT:
case BKEY_SELECT:
/* "Save" */
if (efi_menu->active == efi_menu->count - 2)
return EFI_SUCCESS;
@ -1919,7 +1923,7 @@ static efi_status_t eficonfig_choice_change_boot_order(struct efimenu *efi_menu)
return EFI_ABORTED;
break;
case KEY_SPACE:
case BKEY_SPACE:
if (efi_menu->active < efi_menu->count - 2) {
list_for_each_safe(pos, n, &efi_menu->list) {
entry = list_entry(pos, struct eficonfig_entry, list);
@ -1932,7 +1936,7 @@ static efi_status_t eficonfig_choice_change_boot_order(struct efimenu *efi_menu)
}
}
break;
case KEY_QUIT:
case BKEY_QUIT:
return EFI_ABORTED;
default:
/* Pressed key is not valid, no need to regenerate the menu */

View file

@ -15,7 +15,11 @@
static int do_font_list(struct cmd_tbl *cmdtp, int flag, int argc,
char *const argv[])
{
vidconsole_list_fonts();
struct udevice *dev;
if (uclass_first_device_err(UCLASS_VIDEO_CONSOLE, &dev))
return CMD_RET_FAILURE;
vidconsole_list_fonts(dev);
return 0;
}
@ -47,6 +51,7 @@ static int do_font_select(struct cmd_tbl *cmdtp, int flag, int argc,
static int do_font_size(struct cmd_tbl *cmdtp, int flag, int argc,
char *const argv[])
{
const char *font_name;
struct udevice *dev;
uint size;
int ret;
@ -56,9 +61,11 @@ static int do_font_size(struct cmd_tbl *cmdtp, int flag, int argc,
if (uclass_first_device_err(UCLASS_VIDEO_CONSOLE, &dev))
return CMD_RET_FAILURE;
font_name = vidconsole_get_font_size(dev, &size);
size = dectoul(argv[1], NULL);
ret = vidconsole_select_font(dev, NULL, size);
ret = vidconsole_select_font(dev, font_name, size);
if (ret) {
printf("Failed (error %d)\n", ret);
return CMD_RET_FAILURE;

View file

@ -24,169 +24,6 @@
#include <asm/byteorder.h>
#include <asm/io.h>
#if defined(CONFIG_FIT)
/**
* get_default_image() - Return default property from /images
*
* Return: Pointer to value of default property (or NULL)
*/
static const char *get_default_image(const void *fit)
{
int images_noffset;
images_noffset = fdt_path_offset(fit, FIT_IMAGES_PATH);
if (images_noffset < 0)
return NULL;
return fdt_getprop(fit, images_noffset, FIT_DEFAULT_PROP, NULL);
}
#endif
int image_source_script(ulong addr, const char *fit_uname, const char *confname)
{
ulong len;
#if defined(CONFIG_LEGACY_IMAGE_FORMAT)
const struct legacy_img_hdr *hdr;
#endif
u32 *data;
int verify;
void *buf;
#if defined(CONFIG_FIT)
const void* fit_hdr;
int noffset;
const void *fit_data;
size_t fit_len;
#endif
verify = env_get_yesno("verify");
buf = map_sysmem(addr, 0);
switch (genimg_get_format(buf)) {
#if defined(CONFIG_LEGACY_IMAGE_FORMAT)
case IMAGE_FORMAT_LEGACY:
hdr = buf;
if (!image_check_magic (hdr)) {
puts ("Bad magic number\n");
return 1;
}
if (!image_check_hcrc (hdr)) {
puts ("Bad header crc\n");
return 1;
}
if (verify) {
if (!image_check_dcrc (hdr)) {
puts ("Bad data crc\n");
return 1;
}
}
if (!image_check_type (hdr, IH_TYPE_SCRIPT)) {
puts ("Bad image type\n");
return 1;
}
/* get length of script */
data = (u32 *)image_get_data (hdr);
if ((len = uimage_to_cpu (*data)) == 0) {
puts ("Empty Script\n");
return 1;
}
/*
* scripts are just multi-image files with one component, seek
* past the zero-terminated sequence of image lengths to get
* to the actual image data
*/
while (*data++);
break;
#endif
#if defined(CONFIG_FIT)
case IMAGE_FORMAT_FIT:
fit_hdr = buf;
if (fit_check_format(fit_hdr, IMAGE_SIZE_INVAL)) {
puts ("Bad FIT image format\n");
return 1;
}
if (!fit_uname) {
/* If confname is empty, use the default */
if (confname && *confname)
noffset = fit_conf_get_node(fit_hdr, confname);
else
noffset = fit_conf_get_node(fit_hdr, NULL);
if (noffset < 0) {
if (!confname)
goto fallback;
printf("Could not find config %s\n", confname);
return 1;
}
if (verify && fit_config_verify(fit_hdr, noffset))
return 1;
noffset = fit_conf_get_prop_node(fit_hdr, noffset,
FIT_SCRIPT_PROP,
IH_PHASE_NONE);
if (noffset < 0) {
if (!confname)
goto fallback;
printf("Could not find script in %s\n", confname);
return 1;
}
} else {
fallback:
if (!fit_uname || !*fit_uname)
fit_uname = get_default_image(fit_hdr);
if (!fit_uname) {
puts("No FIT subimage unit name\n");
return 1;
}
/* get script component image node offset */
noffset = fit_image_get_node(fit_hdr, fit_uname);
if (noffset < 0) {
printf("Can't find '%s' FIT subimage\n",
fit_uname);
return 1;
}
}
if (!fit_image_check_type (fit_hdr, noffset, IH_TYPE_SCRIPT)) {
puts("Not a script image\n");
return 1;
}
/* verify integrity */
if (verify && !fit_image_verify(fit_hdr, noffset)) {
puts("Bad Data Hash\n");
return 1;
}
/* get script subimage data address and length */
if (fit_image_get_data (fit_hdr, noffset, &fit_data, &fit_len)) {
puts ("Could not find script subimage data\n");
return 1;
}
data = (u32 *)fit_data;
len = (ulong)fit_len;
break;
#endif
default:
puts ("Wrong image format for \"source\" command\n");
return 1;
}
debug("** Script length: %ld\n", len);
return run_command_list((char *)data, len, 0);
}
/**************************************************/
#if defined(CONFIG_CMD_SOURCE)
static int do_source(struct cmd_tbl *cmdtp, int flag, int argc,
char *const argv[])
{
@ -213,7 +50,7 @@ static int do_source(struct cmd_tbl *cmdtp, int flag, int argc,
}
printf ("## Executing script at %08lx\n", addr);
rcode = image_source_script(addr, fit_uname, confname);
rcode = cmd_source_script(addr, fit_uname, confname);
return rcode;
}
@ -235,4 +72,3 @@ U_BOOT_CMD(
source, 2, 0, do_source,
"run script from memory", source_help_text
);
#endif

View file

@ -39,7 +39,7 @@ obj-$(CONFIG_SPLASH_SOURCE) += splash_source.o
obj-$(CONFIG_MENU) += menu.o
obj-$(CONFIG_UPDATE_COMMON) += update.o
obj-$(CONFIG_USB_KEYBOARD) += usb_kbd.o
obj-$(CONFIG_CMDLINE) += cli_readline.o cli_simple.o
obj-$(CONFIG_CMDLINE) += cli_getch.o cli_readline.o cli_simple.o
endif # !CONFIG_SPL_BUILD
@ -94,8 +94,8 @@ obj-y += eeprom/eeprom_field.o eeprom/eeprom_layout.o
endif
obj-y += cli.o
obj-$(CONFIG_FSL_DDR_INTERACTIVE) += cli_simple.o cli_readline.o
obj-$(CONFIG_STM32MP1_DDR_INTERACTIVE) += cli_simple.o cli_readline.o
obj-$(CONFIG_FSL_DDR_INTERACTIVE) += cli_getch.o cli_simple.o cli_readline.o
obj-$(CONFIG_STM32MP1_DDR_INTERACTIVE) += cli_getch.o cli_simple.o cli_readline.o
obj-$(CONFIG_DFU_OVER_USB) += dfu.o
obj-y += command.o
obj-$(CONFIG_$(SPL_TPL_)LOG) += log.o

208
common/cli_getch.c Normal file
View file

@ -0,0 +1,208 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* (C) Copyright 2000
* Wolfgang Denk, DENX Software Engineering, wd@denx.de.
*
* Copyright 2022 Google LLC
*/
#include <common.h>
#include <cli.h>
/**
* enum cli_esc_state_t - indicates what to do with an escape character
*
* @ESC_REJECT: Invalid escape sequence, so the esc_save[] characters are
* returned from each subsequent call to cli_ch_esc()
* @ESC_SAVE: Character should be saved in esc_save until we have another one
* @ESC_CONVERTED: Escape sequence has been completed and the resulting
* character is available
*/
enum cli_esc_state_t {
ESC_REJECT,
ESC_SAVE,
ESC_CONVERTED
};
void cli_ch_init(struct cli_ch_state *cch)
{
memset(cch, '\0', sizeof(*cch));
}
/**
* cli_ch_esc() - Process a character in an ongoing escape sequence
*
* @cch: State information
* @ichar: Character to process
* @actp: Returns the action to take
* Returns: Output character if *actp is ESC_CONVERTED, else 0
*/
static int cli_ch_esc(struct cli_ch_state *cch, int ichar,
enum cli_esc_state_t *actp)
{
enum cli_esc_state_t act = ESC_REJECT;
switch (cch->esc_len) {
case 1:
if (ichar == '[' || ichar == 'O')
act = ESC_SAVE;
break;
case 2:
switch (ichar) {
case 'D': /* <- key */
ichar = CTL_CH('b');
act = ESC_CONVERTED;
break; /* pass off to ^B handler */
case 'C': /* -> key */
ichar = CTL_CH('f');
act = ESC_CONVERTED;
break; /* pass off to ^F handler */
case 'H': /* Home key */
ichar = CTL_CH('a');
act = ESC_CONVERTED;
break; /* pass off to ^A handler */
case 'F': /* End key */
ichar = CTL_CH('e');
act = ESC_CONVERTED;
break; /* pass off to ^E handler */
case 'A': /* up arrow */
ichar = CTL_CH('p');
act = ESC_CONVERTED;
break; /* pass off to ^P handler */
case 'B': /* down arrow */
ichar = CTL_CH('n');
act = ESC_CONVERTED;
break; /* pass off to ^N handler */
case '1':
case '2':
case '3':
case '4':
case '7':
case '8':
if (cch->esc_save[1] == '[') {
/* see if next character is ~ */
act = ESC_SAVE;
}
break;
}
break;
case 3:
switch (ichar) {
case '~':
switch (cch->esc_save[2]) {
case '3': /* Delete key */
ichar = CTL_CH('d');
act = ESC_CONVERTED;
break; /* pass to ^D handler */
case '1': /* Home key */
case '7':
ichar = CTL_CH('a');
act = ESC_CONVERTED;
break; /* pass to ^A handler */
case '4': /* End key */
case '8':
ichar = CTL_CH('e');
act = ESC_CONVERTED;
break; /* pass to ^E handler */
}
break;
case '0':
if (cch->esc_save[2] == '2')
act = ESC_SAVE;
break;
}
break;
case 4:
switch (ichar) {
case '0':
case '1':
act = ESC_SAVE;
break; /* bracketed paste */
}
break;
case 5:
if (ichar == '~') { /* bracketed paste */
ichar = 0;
act = ESC_CONVERTED;
}
}
*actp = act;
return act == ESC_CONVERTED ? ichar : 0;
}
int cli_ch_process(struct cli_ch_state *cch, int ichar)
{
/*
* ichar=0x0 when error occurs in U-Boot getchar() or when the caller
* wants to check if there are more characters saved in the escape
* sequence
*/
if (!ichar) {
if (cch->emitting) {
if (cch->emit_upto < cch->esc_len)
return cch->esc_save[cch->emit_upto++];
cch->emit_upto = 0;
cch->emitting = false;
}
return 0;
} else if (ichar == -ETIMEDOUT) {
/*
* If we are in an escape sequence but nothing has followed the
* Escape character, then the user probably just pressed the
* Escape key. Return it and clear the sequence.
*/
if (cch->esc_len) {
cch->esc_len = 0;
return '\e';
}
/* Otherwise there is nothing to return */
return 0;
}
if (ichar == '\n' || ichar == '\r')
return '\n';
/* handle standard linux xterm esc sequences for arrow key, etc. */
if (cch->esc_len != 0) {
enum cli_esc_state_t act;
ichar = cli_ch_esc(cch, ichar, &act);
switch (act) {
case ESC_SAVE:
/* save this character and return nothing */
cch->esc_save[cch->esc_len++] = ichar;
ichar = 0;
break;
case ESC_REJECT:
/*
* invalid escape sequence, start returning the
* characters in it
*/
cch->esc_save[cch->esc_len++] = ichar;
ichar = cch->esc_save[cch->emit_upto++];
cch->emitting = true;
break;
case ESC_CONVERTED:
/* valid escape sequence, return the resulting char */
cch->esc_len = 0;
break;
}
}
if (ichar == '\e') {
if (!cch->esc_len) {
cch->esc_save[cch->esc_len] = ichar;
cch->esc_len = 1;
} else {
puts("impossible condition #876\n");
cch->esc_len = 0;
}
return 0;
}
return ichar;
}

View file

@ -62,7 +62,6 @@ static char *delete_char (char *buffer, char *p, int *colp, int *np, int plen)
#define putnstr(str, n) printf("%.*s", (int)n, str)
#define CTL_CH(c) ((c) - 'a' + 1)
#define CTL_BACKSPACE ('\b')
#define DEL ((char)255)
#define DEL7 ((char)127)
@ -252,156 +251,53 @@ static void cread_add_str(char *str, int strsize, int insert,
static int cread_line(const char *const prompt, char *buf, unsigned int *len,
int timeout)
{
struct cli_ch_state s_cch, *cch = &s_cch;
unsigned long num = 0;
unsigned long eol_num = 0;
unsigned long wlen;
char ichar;
int insert = 1;
int esc_len = 0;
char esc_save[8];
int init_len = strlen(buf);
int first = 1;
cli_ch_init(cch);
if (init_len)
cread_add_str(buf, init_len, 1, &num, &eol_num, buf, *len);
while (1) {
if (bootretry_tstc_timeout())
return -2; /* timed out */
if (first && timeout) {
uint64_t etime = endtick(timeout);
/* Check for saved characters */
ichar = cli_ch_process(cch, 0);
while (!tstc()) { /* while no incoming data */
if (get_ticks() >= etime)
return -2; /* timed out */
schedule();
if (!ichar) {
if (bootretry_tstc_timeout())
return -2; /* timed out */
if (first && timeout) {
u64 etime = endtick(timeout);
while (!tstc()) { /* while no incoming data */
if (get_ticks() >= etime)
return -2; /* timed out */
schedule();
}
first = 0;
}
first = 0;
ichar = getcmd_getch();
}
ichar = getcmd_getch();
ichar = cli_ch_process(cch, ichar);
/* ichar=0x0 when error occurs in U-Boot getc */
if (!ichar)
continue;
if ((ichar == '\n') || (ichar == '\r')) {
if (ichar == '\n') {
putc('\n');
break;
}
/*
* handle standard linux xterm esc sequences for arrow key, etc.
*/
if (esc_len != 0) {
enum { ESC_REJECT, ESC_SAVE, ESC_CONVERTED } act = ESC_REJECT;
if (esc_len == 1) {
if (ichar == '[' || ichar == 'O')
act = ESC_SAVE;
} else if (esc_len == 2) {
switch (ichar) {
case 'D': /* <- key */
ichar = CTL_CH('b');
act = ESC_CONVERTED;
break; /* pass off to ^B handler */
case 'C': /* -> key */
ichar = CTL_CH('f');
act = ESC_CONVERTED;
break; /* pass off to ^F handler */
case 'H': /* Home key */
ichar = CTL_CH('a');
act = ESC_CONVERTED;
break; /* pass off to ^A handler */
case 'F': /* End key */
ichar = CTL_CH('e');
act = ESC_CONVERTED;
break; /* pass off to ^E handler */
case 'A': /* up arrow */
ichar = CTL_CH('p');
act = ESC_CONVERTED;
break; /* pass off to ^P handler */
case 'B': /* down arrow */
ichar = CTL_CH('n');
act = ESC_CONVERTED;
break; /* pass off to ^N handler */
case '1':
case '2':
case '3':
case '4':
case '7':
case '8':
if (esc_save[1] == '[') {
/* see if next character is ~ */
act = ESC_SAVE;
}
break;
}
} else if (esc_len == 3) {
switch (ichar) {
case '~':
switch (esc_save[2]) {
case '3': /* Delete key */
ichar = CTL_CH('d');
act = ESC_CONVERTED;
break; /* pass to ^D handler */
case '1': /* Home key */
case '7':
ichar = CTL_CH('a');
act = ESC_CONVERTED;
break; /* pass to ^A handler */
case '4': /* End key */
case '8':
ichar = CTL_CH('e');
act = ESC_CONVERTED;
break; /* pass to ^E handler */
}
break;
case '0':
if (esc_save[2] == '2')
act = ESC_SAVE;
break;
}
} else if (esc_len == 4) {
switch (ichar) {
case '0':
case '1':
act = ESC_SAVE;
break; /* bracketed paste */
}
} else if (esc_len == 5) {
if (ichar == '~') { /* bracketed paste */
ichar = 0;
act = ESC_CONVERTED;
}
}
switch (act) {
case ESC_SAVE:
esc_save[esc_len++] = ichar;
continue;
case ESC_REJECT:
esc_save[esc_len++] = ichar;
cread_add_str(esc_save, esc_len, insert,
&num, &eol_num, buf, *len);
esc_len = 0;
continue;
case ESC_CONVERTED:
esc_len = 0;
break;
}
}
switch (ichar) {
case 0x1b:
if (esc_len == 0) {
esc_save[esc_len] = ichar;
esc_len = 1;
} else {
puts("impossible condition #876\n");
esc_len = 0;
}
break;
case CTL_CH('a'):
BEGINNING_OF_LINE();
break;
@ -470,8 +366,6 @@ static int cread_line(const char *const prompt, char *buf, unsigned int *len,
{
char *hline;
esc_len = 0;
if (ichar == CTL_CH('p'))
hline = hist_prev();
else

View file

@ -13,7 +13,9 @@
#include <command.h>
#include <console.h>
#include <env.h>
#include <image.h>
#include <log.h>
#include <mapmem.h>
#include <asm/global_data.h>
#include <linux/ctype.h>
@ -654,3 +656,20 @@ int cmd_process_error(struct cmd_tbl *cmdtp, int err)
return CMD_RET_SUCCESS;
}
int cmd_source_script(ulong addr, const char *fit_uname, const char *confname)
{
char *data;
void *buf;
uint len;
int ret;
buf = map_sysmem(addr, 0);
ret = image_locate_script(buf, 0, fit_uname, confname, &data, &len);
unmap_sysmem(buf);
if (ret)
return CMD_RET_FAILURE;
debug("** Script length: %d\n", len);
return run_command_list(data, len, 0);
}

View file

@ -15,6 +15,8 @@
#include "menu.h"
#define ansi 0
/*
* Internally, each item in a menu is represented by a struct menu_item.
*
@ -425,15 +427,19 @@ int menu_destroy(struct menu *m)
return 1;
}
void bootmenu_autoboot_loop(struct bootmenu_data *menu,
enum bootmenu_key *key, int *esc)
enum bootmenu_key bootmenu_autoboot_loop(struct bootmenu_data *menu,
struct cli_ch_state *cch)
{
enum bootmenu_key key = BKEY_NONE;
int i, c;
while (menu->delay > 0) {
printf(ANSI_CURSOR_POSITION, menu->count + 5, 3);
if (ansi)
printf(ANSI_CURSOR_POSITION, menu->count + 5, 3);
printf("Hit any key to stop autoboot: %d ", menu->delay);
for (i = 0; i < 100; ++i) {
int ichar;
if (!tstc()) {
schedule();
mdelay(10);
@ -443,22 +449,22 @@ void bootmenu_autoboot_loop(struct bootmenu_data *menu,
menu->delay = -1;
c = getchar();
switch (c) {
case '\e':
*esc = 1;
*key = KEY_NONE;
ichar = cli_ch_process(cch, c);
switch (ichar) {
case '\0':
key = BKEY_NONE;
break;
case '\r':
*key = KEY_SELECT;
case '\n':
key = BKEY_SELECT;
break;
case 0x3: /* ^C */
*key = KEY_QUIT;
key = BKEY_QUIT;
break;
default:
*key = KEY_NONE;
key = BKEY_NONE;
break;
}
break;
}
@ -468,93 +474,72 @@ void bootmenu_autoboot_loop(struct bootmenu_data *menu,
--menu->delay;
}
printf(ANSI_CURSOR_POSITION ANSI_CLEAR_LINE, menu->count + 5, 1);
if (ansi)
printf(ANSI_CURSOR_POSITION ANSI_CLEAR_LINE, menu->count + 5, 1);
if (menu->delay == 0)
*key = KEY_SELECT;
key = BKEY_SELECT;
return key;
}
void bootmenu_loop(struct bootmenu_data *menu,
enum bootmenu_key *key, int *esc)
enum bootmenu_key bootmenu_conv_key(int ichar)
{
enum bootmenu_key key;
switch (ichar) {
case '\n':
/* enter key was pressed */
key = BKEY_SELECT;
break;
case CTL_CH('c'):
case '\e':
/* ^C was pressed */
key = BKEY_QUIT;
break;
case CTL_CH('p'):
key = BKEY_UP;
break;
case CTL_CH('n'):
key = BKEY_DOWN;
break;
case '+':
key = BKEY_PLUS;
break;
case '-':
key = BKEY_MINUS;
break;
case ' ':
key = BKEY_SPACE;
break;
default:
key = BKEY_NONE;
break;
}
return key;
}
enum bootmenu_key bootmenu_loop(struct bootmenu_data *menu,
struct cli_ch_state *cch)
{
enum bootmenu_key key;
int c;
if (*esc == 1) {
if (tstc()) {
c = cli_ch_process(cch, 0);
if (!c) {
while (!c && !tstc()) {
schedule();
mdelay(10);
c = cli_ch_process(cch, -ETIMEDOUT);
}
if (!c) {
c = getchar();
} else {
schedule();
mdelay(10);
if (tstc())
c = getchar();
else
c = '\e';
c = cli_ch_process(cch, c);
}
} else {
while (!tstc()) {
schedule();
mdelay(10);
}
c = getchar();
}
switch (*esc) {
case 0:
/* First char of ANSI escape sequence '\e' */
if (c == '\e') {
*esc = 1;
*key = KEY_NONE;
}
break;
case 1:
/* Second char of ANSI '[' */
if (c == '[') {
*esc = 2;
*key = KEY_NONE;
} else {
/* Alone ESC key was pressed */
*key = KEY_QUIT;
*esc = (c == '\e') ? 1 : 0;
}
break;
case 2:
case 3:
/* Third char of ANSI (number '1') - optional */
if (*esc == 2 && c == '1') {
*esc = 3;
*key = KEY_NONE;
break;
}
key = bootmenu_conv_key(c);
*esc = 0;
/* ANSI 'A' - key up was pressed */
if (c == 'A')
*key = KEY_UP;
/* ANSI 'B' - key down was pressed */
else if (c == 'B')
*key = KEY_DOWN;
/* other key was pressed */
else
*key = KEY_NONE;
break;
}
/* enter key was pressed */
if (c == '\r')
*key = KEY_SELECT;
/* ^C was pressed */
if (c == 0x3)
*key = KEY_QUIT;
if (c == '+')
*key = KEY_PLUS;
if (c == '-')
*key = KEY_MINUS;
if (c == ' ')
*key = KEY_SPACE;
return key;
}

View file

@ -13,6 +13,7 @@ CONFIG_FIT=y
CONFIG_FIT_RSASSA_PSS=y
CONFIG_FIT_CIPHER=y
CONFIG_FIT_VERBOSE=y
CONFIG_LEGACY_IMAGE_FORMAT=y
CONFIG_BOOTSTAGE=y
CONFIG_BOOTSTAGE_REPORT=y
CONFIG_BOOTSTAGE_FDT=y
@ -72,6 +73,7 @@ CONFIG_CMD_IDE=y
CONFIG_CMD_I2C=y
CONFIG_CMD_LOADM=y
CONFIG_CMD_LSBLK=y
CONFIG_CMD_MMC=y
CONFIG_CMD_MUX=y
CONFIG_CMD_OSD=y
CONFIG_CMD_PCI=y

View file

@ -11,6 +11,7 @@ CONFIG_DISTRO_DEFAULTS=y
CONFIG_FIT=y
CONFIG_FIT_SIGNATURE=y
CONFIG_FIT_VERBOSE=y
CONFIG_LEGACY_IMAGE_FORMAT=y
CONFIG_BOOTSTAGE=y
CONFIG_BOOTSTAGE_REPORT=y
CONFIG_BOOTSTAGE_FDT=y
@ -40,6 +41,7 @@ CONFIG_CMD_DEMO=y
CONFIG_CMD_GPIO=y
CONFIG_CMD_GPT=y
CONFIG_CMD_I2C=y
CONFIG_CMD_MMC=y
CONFIG_CMD_OSD=y
CONFIG_CMD_PCI=y
CONFIG_CMD_REMOTEPROC=y

View file

@ -28,7 +28,11 @@ CONFIG_HAS_CUSTOM_SYS_INIT_SP_ADDR=y
CONFIG_CUSTOM_SYS_INIT_SP_ADDR=0x2050000
CONFIG_FIT=y
CONFIG_FIT_BEST_MATCH=y
CONFIG_BOOTSTD_FULL=y
CONFIG_SILENT_CONSOLE=y
CONFIG_BLOBLIST=y
# CONFIG_SPL_BLOBLIST is not set
CONFIG_BLOBLIST_ADDR=0x43d00000
# CONFIG_SPL_FRAMEWORK is not set
CONFIG_SPL_FOOTPRINT_LIMIT=y
CONFIG_SPL_MAX_FOOTPRINT=0x3800

View file

@ -7,6 +7,8 @@ CONFIG_ANDROID_BOOT_IMAGE=y
CONFIG_FIT=y
CONFIG_TIMESTAMP=y
CONFIG_FIT_SIGNATURE=y
# CONFIG_BOOTSTD_FULL is not set
# CONFIG_BOOTMETH_VBE is not set
CONFIG_USE_BOOTCOMMAND=y
CONFIG_BOOTCOMMAND="run distro_bootcmd"
# CONFIG_CMD_BOOTD is not set

188
doc/develop/expo.rst Normal file
View file

@ -0,0 +1,188 @@
.. SPDX-License-Identifier: GPL-2.0+
Expo menu
=========
U-Boot provides a menu implementation for use with selecting bootflows and
changing U-Boot settings. This is in early stages of development.
Motivation
----------
U-Boot already has a text-based menu system accessed via the
:doc:`../usage/cmd/bootmenu`. This works using environment variables, or via
some EFI-specific hacks.
The command makes use of a lower-level `menu` implementation, which is quite
flexible and can be used to make menu hierarchies.
However this system is not flexible enough for use with standard boot. It does
not support a graphical user interface and cannot currently support anything
more than a very simple list of items. While it does support multiple menus in
hierarchies, these are implemented by the caller. See for example `eficonfig.c`.
Another challenge with the current menu implementation is that it controls
the event loop, such that bootmenu_loop() does not return until a key is
pressed. This makes it difficult to implement dynamic displays or to do other
things while the menu is running, such as searching for more bootflows.
For these reasons an attempt has been made to develop a more flexible system
which can handle menus as well as other elements. This is called 'expo', short
for exposition, in an attempt to avoid common words like display, screen, menu
and the like. The primary goal is to support Verified Boot for Embedded (VBE),
although it is available to any boot method, using the 'bootflow menu' command.
Efforts have been made to use common code with the existing menu, including
key processing in particular.
Previous work looked at integrating Nuklear into U-Boot. This works fine and
could provide a way to provide a more flexible UI, perhaps with expo dealing
with the interface to Nuklear. But this is quite a big step and it may be years
before this becomes desirable, if at all. For now, U-Boot only needs a fairly
simple set of menus and options, so rendering them directly is fairly
straightforward.
Concepts
--------
The creator of the expo is here called a `controller` and it controls most
aspects of the expo. This is the code that you must write to use expo.
An `expo` is a set of scenes which can be presented to the user one at a time,
to show information and obtain input from the user.
A `scene` is a collection of objects which are displayed together on the screen.
Only one scene is visible at a time and scenes do not share objects.
A `scene object` is something that appears in the scene, such as some text, an
image or a menu. Objects can be positioned and hidden.
A `menu object` contains a title, a set of `menu items` and a pointer to the
current item. Menu items consist of a keypress (indicating what to press to
select the item), label and description. All three are shown in a single line
within the menu. Items can also have a preview image, which is shown when the
item is highlighted.
All components have a name. This is purely for debugging, so it is easy to see
what object is referred to. Of course the ID numbers can help as well, but they
are less easy to distinguish.
While the expo implementation provides support for handling keypresses and
rendering on the display or serial port, it does not actually deal with reading
input from the user, nor what should be done when a particular menu item is
selected. This is deliberate since having the event loop outside the expo is
more flexible, particularly in a single-threaded environment like U-Boot.
Everything within an expo has a unique ID number. This is done so that it is
easy to refer to things after the expo has been created. The expectation is that
the controller declares an enum containing all of the elements in the expo,
passing the ID of each object as it is created. When a menu item is selected,
its ID is returned. When a object's font or position needs to change, the ID is
passed to expo functions to indicate which object it is. It is possible for expo
to auto-allocate IDs, but this is not recommended. The use of IDs is a
convenience, removing the need for the controller to store pointers to objects,
or even the IDs of objects. Programmatic creation of many items in a loop can be
handled by allocating space in the enum for a maximum number of items, then
adding the loop count to the enum values to obtain unique IDs.
All text strings are stored in a structure attached to the expo, referenced by
a text ID. This makes it easier at some point to implement multiple languages or
to support Unicode strings.
Menu objects do not have their own text and image objects. Instead they simply
refer to objects which have been created. So a menu item is just a collection
of IDs of text and image objects. When adding a menu item you must create these
objects first, then create the menu item, passing in the relevant IDs.
Creating an expo
----------------
To create an expo, use `expo_new()` followed by `scene_new()` to create a scene.
Then add objects to the scene, using functions like `scene_txt_str()` and
`scene_menu()`. For every menu item, add text and image objects, then create
the menu item with `scene_menuitem()`, referring to those objects.
Layout
------
Individual objects can be positioned using `scene_obj_set_pos()`. Menu items
cannot be positioned manually: this is done by `scene_arrange()` which is called
automatically when something changes. The menu itself determines the position of
its items.
Rendering
---------
Rendering is performed by calling `expo_render()`. This uses either the
vidconsole, if present, or the serial console in `text mode`. Expo handles
presentation automatically in either case, without any change in how the expo is
created.
For the vidconsole, Truetype fonts can be used if enabled, to enhance the
quality of the display. For text mode, each menu item is shown in a single line,
allowing easy selection using arrow keys.
Input
-----
The controller is responsible for collecting keyboard input. A good way to do
this is to use `cli_ch_process()`, since it handles conversion of escape
sequences into keys. However, expo has some special menu-key codes for
navigating the interface. These are defined in `enum bootmenu_key` and include
`BKEY_UP` for moving up and `BKEY_SELECT` for selecting an item. You can use
`bootmenu_conv_key()` to convert an ASCII key into one of these.
Once a keypress is decoded, call `expo_send_key()` to send it to the expo. This
may cause an update to the expo state and may produce an action.
Actions
-------
Call `expo_action_get()` in the event loop to check for any actions that the
expo wants to report. These can include selecting a particular menu item, or
quitting the menu. Processing of these is the responsibility of your controller.
Event loop
----------
Expo is intended to be used in an event loop. For an example loop, see
`bootflow_menu_run()`. It is possible to perform other work in your event loop,
such as scanning devices for more bootflows.
Themes
------
Expo does not itself support themes. The bootflow_menu implement supposed a
basic theme, applying font sizes to the various text objects in the expo.
API documentation
-----------------
.. kernel-doc:: include/expo.h
Future ideas
------------
Some ideas for future work:
- Default menu item and a timeout
- Higher-level / automatic / more flexible layout of objects
- Image formats other than BMP
- Use of ANSI sequences to control a serial terminal
- Colour selection
- Better support for handling lots of settings, e.g. with multiple menus and
radio/option widgets
- Mouse support
- Integrate Nuklear, NxWidgets or some other library for a richer UI
- Optimise rendering by only updating the display with changes since last render
- Use expo to replace the existing menu implementation
- Add a Kconfig option to drop the names to save code / data space
- Add a Kconfig option to disable vidconsole support to save code / data space
- Support both graphical and text menus at the same time on different devices
- Implement proper measurement of object bounding boxes, to permit more exact
layout. This would tidy up the layout when Truetype is not used
- Support unicode
- Support curses for proper serial-terminal menus
.. Simon Glass <sjg@chromium.org>
.. 7-Oct-22

View file

@ -34,6 +34,7 @@ Implementation
distro
driver-model/index
environment
expo
event
global_data
logging

View file

@ -868,7 +868,7 @@ static int sdp_handle_in_ep(struct spl_image_info *spl_image,
jump_to_image_no_args(&spl_image);
#else
/* In U-Boot, allow jumps to scripts */
image_source_script(sdp_func->jmp_address, NULL, NULL);
cmd_source_script(sdp_func->jmp_address, NULL, NULL);
#endif
}

View file

@ -112,10 +112,13 @@ config VIDEO_BPP32
config VIDEO_ANSI
bool "Support ANSI escape sequences in video console"
default y
default y if EFI_LOADER
help
Enable ANSI escape sequence decoding for a more fully functional
console.
console. Functionality includes changing the text colour and moving
the cursor. These date from the 1970s and are still widely used today
to control a text terminal. U-Boot implements these by decoding the
sequences and performing the appropriate operation.
config VIDEO_MIPI_DSI
bool "Support MIPI DSI interface"

View file

@ -584,18 +584,24 @@ static struct font_info *console_truetype_find_font(void)
return NULL;
}
void vidconsole_list_fonts(void)
int console_truetype_get_font(struct udevice *dev, int seq,
struct vidfont_info *info)
{
struct font_info *tab;
int i;
for (tab = font_table; tab->begin; tab++) {
if (abs(tab->begin - tab->end) > 4)
printf("%s\n", tab->name);
for (i = 0, tab = font_table; tab->begin; tab++, i++) {
if (i == seq && font_valid(tab)) {
info->name = tab->name;
return 0;
}
}
return -ENOENT;
}
/**
* vidconsole_add_metrics() - Add a new font/size combination
* truetype_add_metrics() - Add a new font/size combination
*
* @dev: Video console device to update
* @font_name: Name of font
@ -604,8 +610,8 @@ void vidconsole_list_fonts(void)
* @return 0 if OK, -EPERM if stbtt failed, -E2BIG if the the metrics table is
* full
*/
static int vidconsole_add_metrics(struct udevice *dev, const char *font_name,
uint font_size, const void *font_data)
static int truetype_add_metrics(struct udevice *dev, const char *font_name,
uint font_size, const void *font_data)
{
struct console_tt_priv *priv = dev_get_priv(dev);
struct console_tt_metrics *met;
@ -674,7 +680,8 @@ static void select_metrics(struct udevice *dev, struct console_tt_metrics *met)
vc_priv->tab_width_frac = VID_TO_POS(met->font_size) * 8 / 2;
}
int vidconsole_select_font(struct udevice *dev, const char *name, uint size)
static int truetype_select_font(struct udevice *dev, const char *name,
uint size)
{
struct console_tt_priv *priv = dev_get_priv(dev);
struct console_tt_metrics *met;
@ -684,7 +691,7 @@ int vidconsole_select_font(struct udevice *dev, const char *name, uint size)
if (!size)
size = CONFIG_CONSOLE_TRUETYPE_SIZE;
if (!name)
name = priv->cur_met->font_name;
name = font_table->name;
met = find_metrics(dev, name, size);
if (!met) {
@ -693,8 +700,10 @@ int vidconsole_select_font(struct udevice *dev, const char *name, uint size)
!strcmp(name, tab->name)) {
int ret;
ret = vidconsole_add_metrics(dev,
tab->name, size, tab->begin);
ret = truetype_add_metrics(dev,
tab->name,
size,
tab->begin);
if (ret < 0)
return log_msg_ret("add", ret);
@ -715,7 +724,7 @@ int vidconsole_select_font(struct udevice *dev, const char *name, uint size)
return 0;
}
const char *vidconsole_get_font(struct udevice *dev, uint *sizep)
const char *vidconsole_get_font_size(struct udevice *dev, uint *sizep)
{
struct console_tt_priv *priv = dev_get_priv(dev);
struct console_tt_metrics *met = priv->cur_met;
@ -745,7 +754,7 @@ static int console_truetype_probe(struct udevice *dev)
return -EBFONT;
}
ret = vidconsole_add_metrics(dev, tab->name, font_size, tab->begin);
ret = truetype_add_metrics(dev, tab->name, font_size, tab->begin);
if (ret < 0)
return log_msg_ret("add", ret);
priv->cur_met = &priv->metrics[ret];
@ -763,6 +772,8 @@ struct vidconsole_ops console_truetype_ops = {
.set_row = console_truetype_set_row,
.backspace = console_truetype_backspace,
.entry_start = console_truetype_entry_start,
.get_font = console_truetype_get_font,
.select_font = truetype_select_font,
};
U_BOOT_DRIVER(vidconsole_truetype) = {

View file

@ -557,6 +557,39 @@ static void vidconsole_puts(struct stdio_dev *sdev, const char *s)
}
}
void vidconsole_list_fonts(struct udevice *dev)
{
struct vidfont_info info;
int ret, i;
for (i = 0, ret = 0; !ret; i++) {
ret = vidconsole_get_font(dev, i, &info);
if (!ret)
printf("%s\n", info.name);
}
}
int vidconsole_get_font(struct udevice *dev, int seq,
struct vidfont_info *info)
{
struct vidconsole_ops *ops = vidconsole_get_ops(dev);
if (!ops->get_font)
return -ENOSYS;
return ops->get_font(dev, seq, info);
}
int vidconsole_select_font(struct udevice *dev, const char *name, uint size)
{
struct vidconsole_ops *ops = vidconsole_get_ops(dev);
if (!ops->select_font)
return -ENOSYS;
return ops->select_font(dev, name, size);
}
/* Set up the number of rows and colours (rotated drivers override this) */
static int vidconsole_pre_probe(struct udevice *dev)
{

View file

@ -7,8 +7,12 @@
#ifndef __bootflow_h
#define __bootflow_h
#include <dm/ofnode_decl.h>
#include <linux/list.h>
struct bootstd_priv;
struct expo;
/**
* enum bootflow_state_t - states that a particular bootflow can be in
*
@ -49,9 +53,13 @@ enum bootflow_state_t {
* @state: Current state (enum bootflow_state_t)
* @subdir: Subdirectory to fetch files from (with trailing /), or NULL if none
* @fname: Filename of bootflow file (allocated)
* @logo: Logo to display for this bootflow (BMP format)
* @logo_size: Size of the logo in bytes
* @buf: Bootflow file contents (allocated)
* @size: Size of bootflow file in bytes
* @err: Error number received (0 if OK)
* @os_name: Name of the OS / distro being booted, or NULL if not known
* (allocated)
*/
struct bootflow {
struct list_head bm_node;
@ -65,9 +73,12 @@ struct bootflow {
enum bootflow_state_t state;
char *subdir;
char *fname;
void *logo;
uint logo_size;
char *buf;
int size;
int err;
char *os_name;
};
/**
@ -329,4 +340,33 @@ 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_apply_theme() - Apply a theme to a bootmenu
*
* @exp: Expo to update
* @node: Node containing the theme information
* Returns 0 on success, -ve on error
*/
int bootflow_menu_apply_theme(struct expo *exp, ofnode node);
/**
* 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

View file

@ -265,6 +265,22 @@ int bootmeth_try_file(struct bootflow *bflow, struct blk_desc *desc,
*/
int bootmeth_alloc_file(struct bootflow *bflow, uint size_limit, uint align);
/**
* bootmeth_alloc_other() - Allocate and read a file for a bootflow
*
* This reads an arbitrary file in the same directory as the bootflow,
* allocating memory for it. The buffer is one byte larger than the file length,
* so that it can be nul-terminated.
*
* @bflow: Information about file to read
* @fname: Filename to read from (within bootflow->subdir)
* @bufp: Returns a pointer to the allocated buffer
* @sizep: Returns the size of the buffer
* Return: 0 if OK, -ENOMEM if out of memory, other -ve on other error
*/
int bootmeth_alloc_other(struct bootflow *bflow, const char *fname,
void **bufp, uint *sizep);
/**
* bootmeth_common_read_file() - Common handler for reading a file
*

View file

@ -9,6 +9,8 @@
#ifndef __bootstd_h
#define __bootstd_h
#include <dm/ofnode_decl.h>
struct udevice;
/**
@ -27,6 +29,7 @@ struct udevice;
* @bootmeth_count: Number of bootmeth devices in @bootmeth_order
* @bootmeth_order: List of bootmeth devices to use, in order, NULL-terminated
* @vbe_bootmeth: Currently selected VBE bootmeth, NULL if none
* @theme: Node containing the theme information
*/
struct bootstd_priv {
const char **prefixes;
@ -37,6 +40,7 @@ struct bootstd_priv {
int bootmeth_count;
struct udevice **bootmeth_order;
struct udevice *vbe_bootmeth;
ofnode theme;
};
/**

View file

@ -7,6 +7,23 @@
#ifndef __CLI_H
#define __CLI_H
#include <stdbool.h>
/**
* struct cli_ch_state - state information for reading cmdline characters
*
* @esc_len: Number of escape characters read so far
* @esc_save: Escape characters collected so far
* @emit_upto: Next index to emit from esc_save
* @emitting: true if emitting from esc_save
*/
struct cli_ch_state {
int esc_len;
char esc_save[8];
int emit_upto;
bool emitting;
};
/**
* Go into the command loop
*
@ -154,5 +171,62 @@ void cli_loop(void);
void cli_init(void);
#define endtick(seconds) (get_ticks() + (uint64_t)(seconds) * get_tbclk())
#define CTL_CH(c) ((c) - 'a' + 1)
/**
* cli_ch_init() - Set up the initial state to process input characters
*
* @cch: State to set up
*/
void cli_ch_init(struct cli_ch_state *cch);
/**
* cli_ch_process() - Process an input character
*
* When @ichar is 0, this function returns any characters from an invalid escape
* sequence which are still pending in the buffer
*
* Otherwise it processes the input character. If it is an escape character,
* then an escape sequence is started and the function returns 0. If we are in
* the middle of an escape sequence, the character is processed and may result
* in returning 0 (if more characters are needed) or a valid character (if
* @ichar finishes the sequence).
*
* If @ichar is a valid character and there is no escape sequence in progress,
* then it is returned as is.
*
* If the Enter key is pressed, '\n' is returned.
*
* Usage should be like this::
*
* struct cli_ch_state cch;
*
* cli_ch_init(cch);
* do
* {
* int ichar, ch;
*
* ichar = cli_ch_process(cch, 0);
* if (!ichar) {
* ch = getchar();
* ichar = cli_ch_process(cch, ch);
* }
* (handle the ichar character)
* } while (!done)
*
* If tstc() is used to look for keypresses, this function can be called with
* @ichar set to -ETIMEDOUT if there is no character after 5-10ms. This allows
* the ambgiuity between the Escape key and the arrow keys (which generate an
* escape character followed by other characters) to be resolved.
*
* @cch: Current state
* @ichar: Input character to process, or 0 if none, or -ETIMEDOUT if no
* character has been received within a small number of milliseconds (this
* cancels any existing escape sequence and allows pressing the Escape key to
* work)
* Returns: Resulting input character after processing, 0 if none, '\e' if
* an existing escape sequence was cancelled
*/
int cli_ch_process(struct cli_ch_state *cch, int ichar);
#endif

View file

@ -279,6 +279,18 @@ int run_commandf(const char *fmt, ...);
* Return: 0 on success, or != 0 on error.
*/
int run_command_list(const char *cmd, int len, int flag);
/**
* cmd_source_script() - Execute a script
*
* Executes a U-Boot script at a particular address in memory. The script should
* have a header (FIT or legacy) with the script type (IH_TYPE_SCRIPT).
*
* @addr: Address of script
* @fit_uname: FIT subimage name
* Return: result code (enum command_ret_t)
*/
int cmd_source_script(ulong addr, const char *fit_uname, const char *confname);
#endif /* __ASSEMBLY__ */
/*

522
include/expo.h Normal file
View file

@ -0,0 +1,522 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* Copyright 2022 Google LLC
* Written by Simon Glass <sjg@chromium.org>
*/
#ifndef __SCENE_H
#define __SCENE_H
#include <linux/list.h>
struct udevice;
/**
* enum expoact_type - types of actions reported by the expo
*
* @EXPOACT_NONE: no action
* @EXPOACT_POINT: menu item was highlighted (@id indicates which)
* @EXPOACT_SELECT: menu item was selected (@id indicates which)
* @EXPOACT_QUIT: request to exit the menu
*/
enum expoact_type {
EXPOACT_NONE,
EXPOACT_POINT,
EXPOACT_SELECT,
EXPOACT_QUIT,
};
/**
* struct expo_action - an action report by the expo
*
* @type: Action type (EXPOACT_NONE if there is no action)
* @select: Used for EXPOACT_POINT and EXPOACT_SELECT
* @id: ID number of the object affected.
*/
struct expo_action {
enum expoact_type type;
union {
struct {
int id;
} select;
};
};
/**
* struct expo - information about an expo
*
* A group of scenes which can be presented to the user, typically to obtain
* input or to make a selection.
*
* @name: Name of the expo (allocated)
* @display: Display to use (`UCLASS_VIDEO`), or NULL to use text mode
* @scene_id: Current scene ID (0 if none)
* @next_id: Next ID number to use, for automatic allocation
* @action: Action selected by user. At present only one is supported, with the
* type set to EXPOACT_NONE if there is no action
* @text_mode: true to use text mode for the menu (no vidconsole)
* @priv: Private data for the controller
* @scene_head: List of scenes
* @str_head: list of strings
*/
struct expo {
char *name;
struct udevice *display;
uint scene_id;
uint next_id;
struct expo_action action;
bool text_mode;
void *priv;
struct list_head scene_head;
struct list_head str_head;
};
/**
* struct expo_string - a string that can be used in an expo
*
* @id: ID number of the string
* @str: String
* @sibling: Node to link this object to its siblings
*/
struct expo_string {
uint id;
const char *str;
struct list_head sibling;
};
/**
* struct scene - information about a scene in an expo
*
* A collection of text/image/menu items in an expo
*
* @expo: Expo this scene is part of
* @name: Name of the scene (allocated)
* @id: ID number of the scene
* @title: Title of the scene (allocated)
* @sibling: Node to link this scene to its siblings
* @obj_head: List of objects in the scene
*/
struct scene {
struct expo *expo;
char *name;
uint id;
char *title;
struct list_head sibling;
struct list_head obj_head;
};
/**
* enum scene_obj_t - type of a scene object
*
* @SCENEOBJT_NONE: Used to indicate that the type does not matter
* @SCENEOBJT_IMAGE: Image data to render
* @SCENEOBJT_TEXT: Text line to render
* @SCENEOBJT_MENU: Menu containing items the user can select
*/
enum scene_obj_t {
SCENEOBJT_NONE = 0,
SCENEOBJT_IMAGE,
SCENEOBJT_TEXT,
SCENEOBJT_MENU,
};
/**
* struct scene_obj - information about an object in a scene
*
* @scene: Scene that this object relates to
* @name: Name of the object (allocated)
* @id: ID number of the object
* @type: Type of this object
* @x: x position, in pixels from left side
* @y: y position, in pixels from top
* @hide: true if the object should be hidden
* @sibling: Node to link this object to its siblings
*/
struct scene_obj {
struct scene *scene;
char *name;
uint id;
enum scene_obj_t type;
int x;
int y;
bool hide;
struct list_head sibling;
};
/**
* struct scene_obj_img - information about an image object in a scene
*
* This is a rectangular image which is blitted onto the display
*
* @obj: Basic object information
* @data: Image data in BMP format
*/
struct scene_obj_img {
struct scene_obj obj;
char *data;
};
/**
* struct scene_obj_txt - information about a text object in a scene
*
* This is a single-line text object
*
* @obj: Basic object information
* @str_id: ID of the text string to display
* @font_name: Name of font (allocated by caller)
* @font_size: Nominal size of font in pixels
*/
struct scene_obj_txt {
struct scene_obj obj;
uint str_id;
const char *font_name;
uint font_size;
};
/**
* struct scene_obj_menu - information about a menu object in a scene
*
* A menu has a number of items which can be selected by the user
*
* It also has:
*
* - a text/image object (@pointer_id) which points to the current item
* (@cur_item_id)
*
* - a preview object which shows an image related to the current item
*
* @obj: Basic object information
* @title_id: ID of the title text, or 0 if none
* @cur_item_id: ID of the current menu item, or 0 if none
* @pointer_id: ID of the object pointing to the current selection
* @item_head: List of items in the menu
*/
struct scene_obj_menu {
struct scene_obj obj;
uint title_id;
uint cur_item_id;
uint pointer_id;
struct list_head item_head;
};
/**
* enum scene_menuitem_flags_t - flags for menu items
*
* @SCENEMIF_GAP_BEFORE: Add a gap before this item
*/
enum scene_menuitem_flags_t {
SCENEMIF_GAP_BEFORE = 1 << 0,
};
/**
* struct scene_menitem - a menu item in a menu
*
* A menu item has:
*
* - text object holding the name (short) and description (can be longer)
* - a text object holding the keypress
*
* @name: Name of the item (this is allocated by this call)
* @id: ID number of the object
* @key_id: ID of text object to use as the keypress to show
* @label_id: ID of text object to use as the label text
* @desc_id: ID of text object to use as the description text
* @preview_id: ID of the preview object, or 0 if none
* @flags: Flags for this item
* @sibling: Node to link this item to its siblings
*/
struct scene_menitem {
char *name;
uint id;
uint key_id;
uint label_id;
uint desc_id;
uint preview_id;
uint flags;
struct list_head sibling;
};
/**
* expo_new() - create a new expo
*
* Allocates a new expo
*
* @name: Name of expo (this is allocated by this call)
* @priv: Private data for the controller
* @expp: Returns a pointer to the new expo on success
* Returns: 0 if OK, -ENOMEM if out of memory
*/
int expo_new(const char *name, void *priv, struct expo **expp);
/**
* expo_destroy() - Destroy an expo and free all its memory
*
* @exp: Expo to destroy
*/
void expo_destroy(struct expo *exp);
/**
* expo_str() - add a new string to an expo
*
* @exp: Expo to update
* @name: Name to use (this is allocated by this call)
* @id: ID to use for the new object (0 to allocate one)
* @str: Pointer to text to display (allocated by caller)
* Returns: ID number for the object (typically @id), or -ve on error
*/
int expo_str(struct expo *exp, const char *name, uint id, const char *str);
/**
* expo_get_str() - Get a string by ID
*
* @exp: Expo to use
* @id: String ID to look up
* @returns string, or NULL if not found
*/
const char *expo_get_str(struct expo *exp, uint id);
/**
* expo_set_display() - set the display to use for a expo
*
* @exp: Expo to update
* @dev: Display to use (`UCLASS_VIDEO`), NULL to use text mode
* Returns: 0 (always)
*/
int expo_set_display(struct expo *exp, struct udevice *dev);
/**
* expo_set_scene_id() - Set the current scene ID
*
* @exp: Expo to update
* @scene_id: New scene ID to use (0 to select no scene)
* Returns: 0 if OK, -ENOENT if there is no scene with that ID
*/
int expo_set_scene_id(struct expo *exp, uint scene_id);
/**
* expo_render() - render the expo on the display / console
*
* @exp: Expo to render
*
* Returns: 0 if OK, -ECHILD if there is no current scene, -ENOENT if the
* current scene is not found, other error if something else goes wrong
*/
int expo_render(struct expo *exp);
/**
* exp_set_text_mode() - Controls whether the expo renders in text mode
*
* @exp: Expo to update
* @text_mode: true to use text mode, false to use the console
*/
void exp_set_text_mode(struct expo *exp, bool text_mode);
/**
* scene_new() - create a new scene in a expo
*
* The scene is given the ID @id which must be unique across all scenes, objects
* and items. The expo's @next_id is updated to at least @id + 1
*
* @exp: Expo to update
* @name: Name to use (this is allocated by this call)
* @id: ID to use for the new scene (0 to allocate one)
* @scnp: Returns a pointer to the new scene on success
* Returns: ID number for the scene (typically @id), or -ve on error
*/
int scene_new(struct expo *exp, const char *name, uint id, struct scene **scnp);
/**
* expo_lookup_scene_id() - Look up a scene by ID
*
* @exp: Expo to check
* @scene_id: Scene ID to look up
* @returns pointer to scene if found, else NULL
*/
struct scene *expo_lookup_scene_id(struct expo *exp, uint scene_id);
/**
* scene_title_set() - set the scene title
*
* @scn: Scene to update
* @title: Title to set, NULL if none (this is allocated by this call)
* Returns: 0 if OK, -ENOMEM if out of memory
*/
int scene_title_set(struct scene *scn, const char *title);
/**
* scene_obj_count() - Count the number of objects in a scene
*
* @scn: Scene to check
* Returns: number of objects in the scene, 0 if none
*/
int scene_obj_count(struct scene *scn);
/**
* scene_img() - add a new image to a scene
*
* @scn: Scene to update
* @name: Name to use (this is allocated by this call)
* @id: ID to use for the new object (0 to allocate one)
* @data: Pointer to image data
* @imgp: If non-NULL, returns the new object
* Returns: ID number for the object (typically @id), or -ve on error
*/
int scene_img(struct scene *scn, const char *name, uint id, char *data,
struct scene_obj_img **imgp);
/**
* scene_txt() - add a new text object to a scene
*
* @scn: Scene to update
* @name: Name to use (this is allocated by this call)
* @id: ID to use for the new object (0 to allocate one)
* @str_id: ID of the string to use
* @txtp: If non-NULL, returns the new object
* Returns: ID number for the object (typically @id), or -ve on error
*/
int scene_txt(struct scene *scn, const char *name, uint id, uint str_id,
struct scene_obj_txt **txtp);
/**
* scene_txt_str() - add a new string to expr and text object to a scene
*
* @scn: Scene to update
* @name: Name to use (this is allocated by this call)
* @id: ID to use for the new object (0 to allocate one)
* @str_id: ID of the string to use
* @str: Pointer to text to display (allocated by caller)
* @txtp: If non-NULL, returns the new object
* Returns: ID number for the object (typically @id), or -ve on error
*/
int scene_txt_str(struct scene *scn, const char *name, uint id, uint str_id,
const char *str, struct scene_obj_txt **txtp);
/**
* scene_menu() - create a menu
*
* @scn: Scene to update
* @name: Name to use (this is allocated by this call)
* @id: ID to use for the new object (0 to allocate one)
* @menup: If non-NULL, returns the new object
* Returns: ID number for the object (typically @id), or -ve on error
*/
int scene_menu(struct scene *scn, const char *name, uint id,
struct scene_obj_menu **menup);
/**
* scene_txt_set_font() - Set the font for an object
*
* @scn: Scene to update
* @id: ID of object to update
* @font_name: Font name to use (allocated by caller)
* @font_size: Font size to use (nominal height in pixels)
*/
int scene_txt_set_font(struct scene *scn, uint id, const char *font_name,
uint font_size);
/**
* scene_obj_set_pos() - Set the postion of an object
*
* @scn: Scene to update
* @id: ID of object to update
* @x: x position, in pixels from left side
* @y: y position, in pixels from top
* Returns: 0 if OK, -ENOENT if @id is invalid
*/
int scene_obj_set_pos(struct scene *scn, uint id, int x, int y);
/**
* scene_obj_set_hide() - Set whether an object is hidden
*
* The update happens when the expo is next rendered.
*
* @scn: Scene to update
* @id: ID of object to update
* @hide: true to hide the object, false to show it
* Returns: 0 if OK, -ENOENT if @id is invalid
*/
int scene_obj_set_hide(struct scene *scn, uint id, bool hide);
/**
* scene_menu_set_title() - Set the title of a menu
*
* @scn: Scene to update
* @id: ID of menu object to update
* @title_id: ID of text object to use as the title
* Returns: 0 if OK, -ENOENT if @id is invalid, -EINVAL if @title_id is invalid
*/
int scene_menu_set_title(struct scene *scn, uint id, uint title_id);
/**
* scene_menu_set_pointer() - Set the item pointer for a menu
*
* This is a visual indicator of the current item, typically a ">" character
* which sits next to the current item and moves when the user presses the
* up/down arrow keys
*
* @scn: Scene to update
* @id: ID of menu object to update
* @cur_item_id: ID of text or image object to use as a pointer to the current
* item
* Returns: 0 if OK, -ENOENT if @id is invalid, -EINVAL if @cur_item_id is invalid
*/
int scene_menu_set_pointer(struct scene *scn, uint id, uint cur_item_id);
/**
* scene_obj_get_hw() - Get width and height of an object in a scene
*
* @scn: Scene to check
* @id: ID of menu object to check
* @widthp: If non-NULL, returns width of object in pixels
* Returns: Height of object in pixels
*/
int scene_obj_get_hw(struct scene *scn, uint id, int *widthp);
/**
* scene_menuitem() - Add an item to a menu
*
* @scn: Scene to update
* @menu_id: ID of menu object to update
* @name: Name to use (this is allocated by this call)
* @id: ID to use for the new object (0 to allocate one)
* @key_id: ID of text object to use as the keypress to show
* @label_id: ID of text object to use as the label text
* @desc_id: ID of text object to use as the description text
* @preview_id: ID of object to use as the preview (text or image)
* @flags: Flags for this item (enum scene_menuitem_flags_t)
* @itemp: If non-NULL, returns the new object
* Returns: ID number for the item (typically @id), or -ve on error
*/
int scene_menuitem(struct scene *scn, uint menu_id, const char *name, uint id,
uint key_id, uint label_id, uint desc_id, uint preview_id,
uint flags, struct scene_menitem **itemp);
/**
* scene_arrange() - Arrange the scene to deal with object sizes
*
* Updates any menus in the scene so that their objects are in the right place.
*
* @scn: Scene to arrange
* Returns: 0 if OK, -ve on error
*/
int scene_arrange(struct scene *scn);
/**
* expo_send_key() - set a keypress to the expo
*
* @exp: Expo to receive the key
* @key: Key to send (ASCII or enum bootmenu_key)
* Returns: 0 if OK, -ECHILD if there is no current scene
*/
int expo_send_key(struct expo *exp, int key);
/**
* expo_action_get() - read user input from the expo
*
* @exp: Expo to check
* @act: Returns action
* Returns: 0 if OK, -EAGAIN if there was no action to return
*/
int expo_action_get(struct expo *exp, struct expo_action *act);
#endif /*__SCENE_H */

View file

@ -710,24 +710,18 @@ int fit_image_load(struct bootm_headers *images, ulong addr,
enum fit_load_op load_op, ulong *datap, ulong *lenp);
/**
* image_source_script() - Execute a script
* @addr: Address of script
* @fit_uname: FIT subimage name
* @confname: FIT config name. The subimage is chosen based on FIT_SCRIPT_PROP.
* image_locate_script() - Locate the raw script in an image
*
* Executes a U-Boot script at a particular address in memory. The script should
* have a header (FIT or legacy) with the script type (IH_TYPE_SCRIPT).
*
* If @fit_uname is the empty string, then the default image is used. If
* @confname is the empty string, the default config is used. If @confname and
* @fit_uname are both non-%NULL, then @confname is ignored. If @confname and
* @fit_uname are both %NULL, then first the default config is tried, and then
* the default image.
*
* Return: result code (enum command_ret_t)
* @buf: Address of image
* @size: Size of image in bytes
* @fit_uname: Node name of FIT image to read
* @confname: Node name of FIT config to read
* @datap: Returns pointer to raw script on success
* @lenp: Returns size of raw script on success
* @return 0 if OK, non-zero on error
*/
int image_source_script(ulong addr, const char *fit_uname,
const char *confname);
int image_locate_script(void *buf, int size, const char *fit_uname,
const char *confname, char **datap, uint *lenp);
/**
* fit_get_node_from_config() - Look up an image a FIT by type

View file

@ -6,6 +6,7 @@
#ifndef __MENU_H__
#define __MENU_H__
struct cli_ch_state;
struct menu;
struct menu *menu_create(char *title, int timeout, int prompt,
@ -42,20 +43,72 @@ struct bootmenu_data {
struct bootmenu_entry *first; /* first menu entry */
};
/** enum bootmenu_key - keys that can be returned by the bootmenu */
enum bootmenu_key {
KEY_NONE = 0,
KEY_UP,
KEY_DOWN,
KEY_SELECT,
KEY_QUIT,
KEY_PLUS,
KEY_MINUS,
KEY_SPACE,
BKEY_NONE = 0,
BKEY_UP,
BKEY_DOWN,
BKEY_SELECT,
BKEY_QUIT,
BKEY_PLUS,
BKEY_MINUS,
BKEY_SPACE,
BKEY_COUNT,
};
void bootmenu_autoboot_loop(struct bootmenu_data *menu,
enum bootmenu_key *key, int *esc);
void bootmenu_loop(struct bootmenu_data *menu,
enum bootmenu_key *key, int *esc);
/**
* bootmenu_autoboot_loop() - handle autobooting if no key is pressed
*
* This shows a prompt to allow the user to press a key to interrupt auto boot
* of the first menu option.
*
* It then waits for the required time (menu->delay in seconds) for a key to be
* pressed. If nothing is pressed in that time, @key returns KEY_SELECT
* indicating that the current option should be chosen.
*
* @menu: Menu being processed
* @esc: Set to 1 if the escape key is pressed, otherwise not updated
* Returns: code for the key the user pressed:
* enter: KEY_SELECT
* Ctrl-C: KEY_QUIT
* anything else: KEY_NONE
*/
enum bootmenu_key bootmenu_autoboot_loop(struct bootmenu_data *menu,
struct cli_ch_state *cch);
/**
* bootmenu_loop() - handle waiting for a keypress when autoboot is disabled
*
* This is used when the menu delay is negative, indicating that the delay has
* elapsed, or there was no delay to begin with.
*
* It reads a character and processes it, returning a menu-key code if a
* character is recognised
*
* @menu: Menu being processed
* @esc: On input, a non-zero value indicates that an escape sequence has
* resulted in that many characters so far. On exit this is updated to the
* new number of characters
* Returns: code for the key the user pressed:
* enter: BKEY_SELECT
* Ctrl-C: BKEY_QUIT
* Up arrow: BKEY_UP
* Down arrow: BKEY_DOWN
* Escape (by itself): BKEY_QUIT
* Plus: BKEY_PLUS
* Minus: BKEY_MINUS
* Space: BKEY_SPACE
*/
enum bootmenu_key bootmenu_loop(struct bootmenu_data *menu,
struct cli_ch_state *cch);
/**
* bootmenu_conv_key() - Convert a U-Boot keypress into a menu key
*
* @ichar: Keypress to convert (ASCII, including control characters)
* Returns: Menu key that corresponds to @ichar, or BKEY_NONE if none
*/
enum bootmenu_key bootmenu_conv_key(int ichar);
#endif /* __MENU_H__ */

View file

@ -248,7 +248,7 @@ void video_bmp_get_info(void *bmp_image, ulong *widthp, ulong *heightp,
* that direction
* - if a coordinate is -ve then it will be offset to the
* left/top of the centre by that many pixels
* - if a coordinate is positive it will be used unchnaged.
* - if a coordinate is positive it will be used unchanged.
* Return: 0 if OK, -ve on error
*/
int video_bmp_display(struct udevice *dev, ulong bmp_image, int x, int y,

View file

@ -62,6 +62,15 @@ struct vidconsole_priv {
char escape_buf[32];
};
/**
* struct vidfont_info - information about a font
*
* @name: Font name, e.g. nimbus_sans_l_regular
*/
struct vidfont_info {
const char *name;
};
/**
* struct vidconsole_ops - Video console operations
*
@ -111,6 +120,9 @@ struct vidconsole_ops {
/**
* entry_start() - Indicate that text entry is starting afresh
*
* @dev: Device to adjust
* Returns: 0 on success, -ve on error
*
* Consoles which use proportional fonts need to track the position of
* each character output so that backspace will return to the correct
* place. This method signals to the console driver that a new entry
@ -123,6 +135,9 @@ struct vidconsole_ops {
/**
* backspace() - Handle erasing the last character
*
* @dev: Device to adjust
* Returns: 0 on success, -ve on error
*
* With proportional fonts the vidconsole uclass cannot itself erase
* the previous character. This optional method will be called when
* a backspace is needed. The driver should erase the previous
@ -133,11 +148,53 @@ struct vidconsole_ops {
* characters.
*/
int (*backspace)(struct udevice *dev);
/**
* get_font() - Obtain information about a font (optional)
*
* @dev: Device to check
* @seq: Font number to query (0=first, 1=second, etc.)
* @info: Returns font information on success
* Returns: 0 on success, -ENOENT if no such font
*/
int (*get_font)(struct udevice *dev, int seq,
struct vidfont_info *info);
/**
* select_font() - Select a particular font by name / size
*
* @dev: Device to adjust
* @name: Font name to use (NULL to use default)
* @size: Font size to use (0 to use default)
* Returns: 0 on success, -ENOENT if no such font
*/
int (*select_font)(struct udevice *dev, const char *name, uint size);
};
/* Get a pointer to the driver operations for a video console device */
#define vidconsole_get_ops(dev) ((struct vidconsole_ops *)(dev)->driver->ops)
/**
* vidconsole_get_font() - Obtain information about a font
*
* @dev: Device to check
* @seq: Font number to query (0=first, 1=second, etc.)
* @info: Returns font information on success
* Returns: 0 on success, -ENOENT if no such font, -ENOSYS if there is no such
* method
*/
int vidconsole_get_font(struct udevice *dev, int seq,
struct vidfont_info *info);
/**
* vidconsole_select_font() - Select a particular font by name / size
*
* @dev: Device to adjust
* @name: Font name to use (NULL to use default)
* @size: Font size to use (0 to use default)
*/
int vidconsole_select_font(struct udevice *dev, const char *name, uint size);
/**
* vidconsole_putc_xy() - write a single character to a position
*
@ -234,27 +291,21 @@ void vidconsole_set_cursor_pos(struct udevice *dev, int x, int y);
/**
* vidconsole_list_fonts() - List the available fonts
*
* This shows a list on the console
*/
void vidconsole_list_fonts(void);
/**
* vidconsole_select_font() - Select a font to use
* @dev: vidconsole device to check
*
* @dev: vidconsole device
* @name: Font name
* @size: Size of the font (norminal pixel height) or 0 for default
* This shows a list of fonts known by this vidconsole. The list is displayed on
* the console (not necessarily @dev but probably)
*/
int vidconsole_select_font(struct udevice *dev, const char *name, uint size);
void vidconsole_list_fonts(struct udevice *dev);
/**
* vidconsole_get_font() - get the current font name and size
* vidconsole_get_font_size() - get the current font name and size
*
* @dev: vidconsole device
* @sizep: Place to put the font size (nominal height in pixels)
* Returns: Current font name
*/
const char *vidconsole_get_font(struct udevice *dev, uint *sizep);
const char *vidconsole_get_font_size(struct udevice *dev, uint *sizep);
#ifdef CONFIG_VIDEO_COPY
/**

View file

@ -5,6 +5,8 @@
obj-$(CONFIG_BOOTSTD) += bootdev.o bootstd_common.o bootflow.o bootmeth.o
obj-$(CONFIG_FIT) += image.o
obj-$(CONFIG_EXPO) += expo.o
ifdef CONFIG_OF_LIVE
obj-$(CONFIG_BOOTMETH_VBE_SIMPLE) += vbe_simple.o
endif

View file

@ -11,14 +11,23 @@
#include <bootflow.h>
#include <bootmeth.h>
#include <bootstd.h>
#include <cli.h>
#include <dm.h>
#include <expo.h>
#ifdef CONFIG_SANDBOX
#include <asm/test.h>
#endif
#include <dm/device-internal.h>
#include <dm/lists.h>
#include <test/suites.h>
#include <test/ut.h>
#include "bootstd_common.h"
#include "../../boot/bootflow_internal.h"
#include "../../boot/scene_internal.h"
DECLARE_GLOBAL_DATA_PTR;
extern U_BOOT_DRIVER(bootmeth_script);
static int inject_response(struct unit_test_state *uts)
{
@ -188,6 +197,8 @@ static int bootflow_cmd_info(struct unit_test_state *uts)
ut_assert_nextline("Filename: /extlinux/extlinux.conf");
ut_assert_nextlinen("Buffer: ");
ut_assert_nextline("Size: 253 (595 bytes)");
ut_assert_nextline("OS: Fedora-Workstation-armhfp-31-1.9 (5.3.7-301.fc31.armv7hl)");
ut_assert_nextline("Logo: (none)");
ut_assert_nextline("Error: 0");
ut_assert_console_end();
@ -460,3 +471,122 @@ static int bootflow_cmd_boot(struct unit_test_state *uts)
return 0;
}
BOOTSTD_TEST(bootflow_cmd_boot, UT_TESTF_DM | UT_TESTF_SCAN_FDT);
/**
* prep_mmc4_bootdev() - Set up the mmc4 bootdev so we can access a fake Armbian
*
* @uts: Unit test state
* Returns 0 on success, -ve on failure
*/
static int prep_mmc4_bootdev(struct unit_test_state *uts)
{
static const char *order[] = {"mmc2", "mmc1", "mmc4", NULL};
struct udevice *dev, *bootstd;
struct bootstd_priv *std;
const char **old_order;
ofnode node;
/* Enable the mmc4 node since we need a second bootflow */
node = ofnode_path("/mmc4");
ut_assertok(lists_bind_fdt(gd->dm_root, node, &dev, NULL, false));
/* Enable the script bootmeth too */
ut_assertok(uclass_first_device_err(UCLASS_BOOTSTD, &bootstd));
ut_assertok(device_bind(bootstd, DM_DRIVER_REF(bootmeth_script),
"bootmeth_script", 0, ofnode_null(), &dev));
/* Change the order to include mmc4 */
std = dev_get_priv(bootstd);
old_order = std->bootdev_order;
std->bootdev_order = order;
console_record_reset_enable();
ut_assertok(run_command("bootflow scan", 0));
ut_assert_console_end();
/* Restore the order used by the device tree */
std->bootdev_order = old_order;
return 0;
}
/* Check 'bootflow menu' to select a bootflow */
static int bootflow_cmd_menu(struct unit_test_state *uts)
{
char prev[3];
ut_assertok(prep_mmc4_bootdev(uts));
/* Add keypresses to move to and select the second one in the list */
prev[0] = CTL_CH('n');
prev[1] = '\r';
prev[2] = '\0';
ut_asserteq(2, console_in_puts(prev));
ut_assertok(run_command("bootflow menu", 0));
ut_assert_nextline("Selected: Armbian");
ut_assert_console_end();
return 0;
}
BOOTSTD_TEST(bootflow_cmd_menu, UT_TESTF_DM | UT_TESTF_SCAN_FDT);
/**
* check_font() - Check that the font size for an item matches expectations
*
* @uts: Unit test state
* @scn: Scene containing the text object
* @id: ID of the text object
* Returns 0 on success, -ve on failure
*/
static int check_font(struct unit_test_state *uts, struct scene *scn, uint id,
int font_size)
{
struct scene_obj_txt *txt;
txt = scene_obj_find(scn, id, SCENEOBJT_TEXT);
ut_assertnonnull(txt);
ut_asserteq(font_size, txt->font_size);
return 0;
}
/* Check themes work with a bootflow menu */
static int bootflow_menu_theme(struct unit_test_state *uts)
{
const int font_size = 30;
struct scene *scn;
struct expo *exp;
ofnode node;
int i;
ut_assertok(prep_mmc4_bootdev(uts));
ut_assertok(bootflow_menu_new(&exp));
node = ofnode_path("/bootstd/theme");
ut_assert(ofnode_valid(node));
ut_assertok(bootflow_menu_apply_theme(exp, node));
scn = expo_lookup_scene_id(exp, MAIN);
ut_assertnonnull(scn);
/*
* Check that the txt objects have the correct font size from the
* device tree node: bootstd/theme
*
* Check both menu items, since there are two bootflows
*/
ut_assertok(check_font(uts, scn, OBJ_PROMPT, font_size));
ut_assertok(check_font(uts, scn, OBJ_POINTER, font_size));
for (i = 0; i < 2; i++) {
ut_assertok(check_font(uts, scn, ITEM_DESC + i, font_size));
ut_assertok(check_font(uts, scn, ITEM_KEY + i, font_size));
ut_assertok(check_font(uts, scn, ITEM_LABEL + i, font_size));
}
expo_destroy(exp);
return 0;
}
BOOTSTD_TEST(bootflow_menu_theme, UT_TESTF_DM | UT_TESTF_SCAN_FDT);

539
test/boot/expo.c Normal file
View file

@ -0,0 +1,539 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright 2022 Google LLC
* Written by Simon Glass <sjg@chromium.org>
*/
#include <common.h>
#include <dm.h>
#include <expo.h>
#include <menu.h>
#include <video.h>
#include <linux/input.h>
#include <test/suites.h>
#include <test/ut.h>
#include "bootstd_common.h"
#include "../../boot/scene_internal.h"
enum {
/* scenes */
SCENE1 = 7,
SCENE2,
/* objects */
OBJ_LOGO,
OBJ_TEXT,
OBJ_TEXT2,
OBJ_MENU,
OBJ_MENU_TITLE,
/* strings */
STR_TEXT,
STR_TEXT2,
STR_MENU_TITLE,
STR_POINTER_TEXT,
STR_ITEM1_LABEL,
STR_ITEM1_DESC,
STR_ITEM1_KEY,
STR_ITEM1_PREVIEW,
STR_ITEM2_LABEL,
STR_ITEM2_DESC,
STR_ITEM2_KEY,
STR_ITEM2_PREVIEW,
/* menu items */
ITEM1,
ITEM1_LABEL,
ITEM1_DESC,
ITEM1_KEY,
ITEM1_PREVIEW,
ITEM2,
ITEM2_LABEL,
ITEM2_DESC,
ITEM2_KEY,
ITEM2_PREVIEW,
/* pointer to current item */
POINTER_TEXT,
};
#define BAD_POINTER ((void *)1)
/* names for various things */
#define EXPO_NAME "my menus"
#define SCENE_NAME1 "main"
#define SCENE_NAME2 "second"
#define SCENE_TITLE "Main Menu"
#define LOGO_NAME "logo"
/* Check base expo support */
static int expo_base(struct unit_test_state *uts)
{
struct udevice *dev;
struct expo *exp;
ulong start_mem;
char name[100];
int i;
ut_assertok(uclass_first_device_err(UCLASS_VIDEO, &dev));
start_mem = ut_check_free();
exp = NULL;
strcpy(name, EXPO_NAME);
ut_assertok(expo_new(name, NULL, &exp));
*name = '\0';
ut_assertnonnull(exp);
ut_asserteq(0, exp->scene_id);
ut_asserteq(0, exp->next_id);
/* Make sure the name was allocated */
ut_assertnonnull(exp->name);
ut_asserteq_str(EXPO_NAME, exp->name);
ut_assertok(expo_set_display(exp, dev));
expo_destroy(exp);
ut_assertok(ut_check_delta(start_mem));
/* test handling out-of-memory conditions */
for (i = 0; i < 2; i++) {
struct expo *exp2;
malloc_enable_testing(i);
exp2 = BAD_POINTER;
ut_asserteq(-ENOMEM, expo_new(EXPO_NAME, NULL, &exp2));
ut_asserteq_ptr(BAD_POINTER, exp2);
malloc_disable_testing();
}
return 0;
}
BOOTSTD_TEST(expo_base, UT_TESTF_DM | UT_TESTF_SCAN_FDT);
/* Check creating a scene */
static int expo_scene(struct unit_test_state *uts)
{
struct scene *scn;
struct expo *exp;
ulong start_mem;
char name[100];
int id;
start_mem = ut_check_free();
ut_assertok(expo_new(EXPO_NAME, NULL, &exp));
scn = NULL;
ut_asserteq(0, exp->next_id);
strcpy(name, SCENE_NAME1);
id = scene_new(exp, name, SCENE1, &scn);
*name = '\0';
ut_assertnonnull(scn);
ut_asserteq(SCENE1, id);
ut_asserteq(SCENE1 + 1, exp->next_id);
ut_asserteq_ptr(exp, scn->expo);
/* Make sure the name was allocated */
ut_assertnonnull(scn->name);
ut_asserteq_str(SCENE_NAME1, scn->name);
/* Set the title */
strcpy(name, SCENE_TITLE);
ut_assertok(scene_title_set(scn, name));
*name = '\0';
ut_assertnonnull(scn->title);
ut_asserteq_str(SCENE_TITLE, scn->title);
/* Use an allocated ID */
scn = NULL;
id = scene_new(exp, SCENE_NAME2, 0, &scn);
ut_assertnonnull(scn);
ut_asserteq(SCENE2, id);
ut_asserteq(SCENE2 + 1, exp->next_id);
ut_asserteq_ptr(exp, scn->expo);
ut_asserteq_str(SCENE_NAME2, scn->name);
expo_destroy(exp);
ut_assertok(ut_check_delta(start_mem));
return 0;
}
BOOTSTD_TEST(expo_scene, UT_TESTF_DM | UT_TESTF_SCAN_FDT);
/* Check creating a scene with objects */
static int expo_object(struct unit_test_state *uts)
{
struct scene_obj_img *img;
struct scene_obj_txt *txt;
struct scene *scn;
struct expo *exp;
ulong start_mem;
char name[100];
char *data;
int id;
start_mem = ut_check_free();
ut_assertok(expo_new(EXPO_NAME, NULL, &exp));
id = scene_new(exp, SCENE_NAME1, SCENE1, &scn);
ut_assert(id > 0);
ut_asserteq(0, scene_obj_count(scn));
data = NULL;
strcpy(name, LOGO_NAME);
id = scene_img(scn, name, OBJ_LOGO, data, &img);
ut_assert(id > 0);
*name = '\0';
ut_assertnonnull(img);
ut_asserteq(OBJ_LOGO, id);
ut_asserteq(OBJ_LOGO + 1, exp->next_id);
ut_asserteq_ptr(scn, img->obj.scene);
ut_asserteq(SCENEOBJT_IMAGE, img->obj.type);
ut_asserteq_ptr(data, img->data);
/* Make sure the name was allocated */
ut_assertnonnull(scn->name);
ut_asserteq_str(SCENE_NAME1, scn->name);
ut_asserteq(1, scene_obj_count(scn));
id = scene_txt_str(scn, "text", OBJ_TEXT, STR_TEXT, "my string", &txt);
ut_assert(id > 0);
ut_assertnonnull(txt);
ut_asserteq(OBJ_TEXT, id);
ut_asserteq(SCENEOBJT_TEXT, txt->obj.type);
ut_asserteq(2, scene_obj_count(scn));
/* Check passing NULL as the final parameter */
id = scene_txt_str(scn, "text2", OBJ_TEXT2, STR_TEXT2, "another string",
NULL);
ut_assert(id > 0);
ut_asserteq(3, scene_obj_count(scn));
expo_destroy(exp);
ut_assertok(ut_check_delta(start_mem));
return 0;
}
BOOTSTD_TEST(expo_object, UT_TESTF_DM | UT_TESTF_SCAN_FDT);
/* Check setting object attributes */
static int expo_object_attr(struct unit_test_state *uts)
{
struct scene_obj_menu *menu;
struct scene_obj_img *img;
struct scene_obj_txt *txt;
struct scene *scn;
struct expo *exp;
ulong start_mem;
char name[100];
char *data;
int id;
start_mem = ut_check_free();
ut_assertok(expo_new(EXPO_NAME, NULL, &exp));
id = scene_new(exp, SCENE_NAME1, SCENE1, &scn);
ut_assert(id > 0);
data = NULL;
id = scene_img(scn, LOGO_NAME, OBJ_LOGO, data, &img);
ut_assert(id > 0);
ut_assertok(scene_obj_set_pos(scn, OBJ_LOGO, 123, 456));
ut_asserteq(123, img->obj.x);
ut_asserteq(456, img->obj.y);
ut_asserteq(-ENOENT, scene_obj_set_pos(scn, OBJ_TEXT2, 0, 0));
id = scene_txt_str(scn, "text", OBJ_TEXT, STR_TEXT, "my string", &txt);
ut_assert(id > 0);
strcpy(name, "font2");
ut_assertok(scene_txt_set_font(scn, OBJ_TEXT, name, 42));
ut_asserteq_ptr(name, txt->font_name);
ut_asserteq(42, txt->font_size);
ut_asserteq(-ENOENT, scene_txt_set_font(scn, OBJ_TEXT2, name, 42));
id = scene_menu(scn, "main", OBJ_MENU, &menu);
ut_assert(id > 0);
ut_assertok(scene_menu_set_title(scn, OBJ_MENU, OBJ_TEXT));
ut_asserteq(-ENOENT, scene_menu_set_title(scn, OBJ_TEXT2, OBJ_TEXT));
ut_asserteq(-EINVAL, scene_menu_set_title(scn, OBJ_MENU, OBJ_TEXT2));
expo_destroy(exp);
ut_assertok(ut_check_delta(start_mem));
return 0;
}
BOOTSTD_TEST(expo_object_attr, UT_TESTF_DM | UT_TESTF_SCAN_FDT);
/* Check creating a scene with a menu */
static int expo_object_menu(struct unit_test_state *uts)
{
struct scene_obj_menu *menu;
struct scene_menitem *item;
int id, label_id, desc_id, key_id, pointer_id, preview_id;
struct scene_obj_txt *ptr, *name1, *desc1, *key1, *tit, *prev1;
struct scene *scn;
struct expo *exp;
ulong start_mem;
start_mem = ut_check_free();
ut_assertok(expo_new(EXPO_NAME, NULL, &exp));
id = scene_new(exp, SCENE_NAME1, SCENE1, &scn);
ut_assert(id > 0);
id = scene_menu(scn, "main", OBJ_MENU, &menu);
ut_assert(id > 0);
ut_assertnonnull(menu);
ut_asserteq(OBJ_MENU, id);
ut_asserteq(SCENEOBJT_MENU, menu->obj.type);
ut_asserteq(0, menu->title_id);
ut_asserteq(0, menu->pointer_id);
ut_assertok(scene_obj_set_pos(scn, OBJ_MENU, 50, 400));
ut_asserteq(50, menu->obj.x);
ut_asserteq(400, menu->obj.y);
id = scene_txt_str(scn, "title", OBJ_MENU_TITLE, STR_MENU_TITLE,
"Main Menu", &tit);
ut_assert(id > 0);
ut_assertok(scene_menu_set_title(scn, OBJ_MENU, OBJ_MENU_TITLE));
ut_asserteq(OBJ_MENU_TITLE, menu->title_id);
pointer_id = scene_txt_str(scn, "cur_item", POINTER_TEXT,
STR_POINTER_TEXT, ">", &ptr);
ut_assert(pointer_id > 0);
ut_assertok(scene_menu_set_pointer(scn, OBJ_MENU, POINTER_TEXT));
ut_asserteq(POINTER_TEXT, menu->pointer_id);
label_id = scene_txt_str(scn, "label1", ITEM1_LABEL, STR_ITEM1_LABEL,
"Play", &name1);
ut_assert(label_id > 0);
desc_id = scene_txt_str(scn, "desc1", ITEM1_DESC, STR_ITEM1_DESC,
"Lord Melchett", &desc1);
ut_assert(desc_id > 0);
key_id = scene_txt_str(scn, "item1-key", ITEM1_KEY, STR_ITEM1_KEY, "1",
&key1);
ut_assert(key_id > 0);
preview_id = scene_txt_str(scn, "item1-preview", ITEM1_PREVIEW,
STR_ITEM1_PREVIEW, "(preview1)", &prev1);
ut_assert(preview_id > 0);
id = scene_menuitem(scn, OBJ_MENU, "linux", ITEM1, ITEM1_KEY,
ITEM1_LABEL, ITEM1_DESC, ITEM1_PREVIEW, 0, &item);
ut_asserteq(ITEM1, id);
ut_asserteq(id, item->id);
ut_asserteq(key_id, item->key_id);
ut_asserteq(label_id, item->label_id);
ut_asserteq(desc_id, item->desc_id);
ut_asserteq(preview_id, item->preview_id);
/* adding an item should cause the first item to become current */
ut_asserteq(id, menu->cur_item_id);
/* the title should be at the top */
ut_asserteq(menu->obj.x, tit->obj.x);
ut_asserteq(menu->obj.y, tit->obj.y);
/* the first item should be next */
ut_asserteq(menu->obj.x, name1->obj.x);
ut_asserteq(menu->obj.y + 32, name1->obj.y);
ut_asserteq(menu->obj.x + 230, key1->obj.x);
ut_asserteq(menu->obj.y + 32, key1->obj.y);
ut_asserteq(menu->obj.x + 200, ptr->obj.x);
ut_asserteq(menu->obj.y + 32, ptr->obj.y);
ut_asserteq(menu->obj.x + 280, desc1->obj.x);
ut_asserteq(menu->obj.y + 32, desc1->obj.y);
ut_asserteq(-4, prev1->obj.x);
ut_asserteq(menu->obj.y + 32, prev1->obj.y);
ut_asserteq(false, prev1->obj.hide);
expo_destroy(exp);
ut_assertok(ut_check_delta(start_mem));
return 0;
}
BOOTSTD_TEST(expo_object_menu, UT_TESTF_DM | UT_TESTF_SCAN_FDT);
/* Check rendering a scene */
static int expo_render_image(struct unit_test_state *uts)
{
struct scene_obj_menu *menu;
struct scene *scn, *scn2;
struct expo_action act;
struct scene_obj *obj;
struct udevice *dev;
struct expo *exp;
int id;
console_record_reset_enable();
ut_assertok(uclass_first_device_err(UCLASS_VIDEO, &dev));
ut_assertok(expo_new(EXPO_NAME, NULL, &exp));
id = scene_new(exp, SCENE_NAME1, SCENE1, &scn);
ut_assert(id > 0);
ut_assertok(expo_set_display(exp, dev));
id = scene_img(scn, "logo", OBJ_LOGO, video_get_u_boot_logo(), NULL);
ut_assert(id > 0);
ut_assertok(scene_obj_set_pos(scn, OBJ_LOGO, 50, 20));
id = scene_txt_str(scn, "text", OBJ_TEXT, STR_TEXT, "my string", NULL);
ut_assert(id > 0);
ut_assertok(scene_txt_set_font(scn, OBJ_TEXT, "cantoraone_regular",
40));
ut_assertok(scene_obj_set_pos(scn, OBJ_TEXT, 400, 100));
id = scene_txt_str(scn, "text", OBJ_TEXT2, STR_TEXT2, "another string",
NULL);
ut_assert(id > 0);
ut_assertok(scene_txt_set_font(scn, OBJ_TEXT2, "nimbus_sans_l_regular",
60));
ut_assertok(scene_obj_set_pos(scn, OBJ_TEXT2, 200, 600));
id = scene_menu(scn, "main", OBJ_MENU, &menu);
ut_assert(id > 0);
id = scene_txt_str(scn, "title", OBJ_MENU_TITLE, STR_MENU_TITLE,
"Main Menu", NULL);
ut_assert(id > 0);
ut_assertok(scene_menu_set_title(scn, OBJ_MENU, OBJ_MENU_TITLE));
id = scene_txt_str(scn, "cur_item", POINTER_TEXT, STR_POINTER_TEXT, ">",
NULL);
ut_assert(id > 0);
ut_assertok(scene_menu_set_pointer(scn, OBJ_MENU, POINTER_TEXT));
id = scene_txt_str(scn, "label1", ITEM1_LABEL, STR_ITEM1_LABEL, "Play",
NULL);
ut_assert(id > 0);
id = scene_txt_str(scn, "item1 txt", ITEM1_DESC, STR_ITEM1_DESC,
"Lord Melchett", NULL);
ut_assert(id > 0);
id = scene_txt_str(scn, "item1-key", ITEM1_KEY, STR_ITEM1_KEY, "1",
NULL);
ut_assert(id > 0);
id = scene_img(scn, "item1-preview", ITEM1_PREVIEW,
video_get_u_boot_logo(), NULL);
id = scene_menuitem(scn, OBJ_MENU, "item1", ITEM1, ITEM1_KEY,
ITEM1_LABEL, ITEM1_DESC, ITEM1_PREVIEW, 0, NULL);
ut_assert(id > 0);
id = scene_txt_str(scn, "label2", ITEM2_LABEL, STR_ITEM2_LABEL, "Now",
NULL);
ut_assert(id > 0);
id = scene_txt_str(scn, "item2 txt", ITEM2_DESC, STR_ITEM2_DESC,
"Lord Percy", NULL);
ut_assert(id > 0);
id = scene_txt_str(scn, "item2-key", ITEM2_KEY, STR_ITEM2_KEY, "2",
NULL);
ut_assert(id > 0);
id = scene_img(scn, "item2-preview", ITEM2_PREVIEW,
video_get_u_boot_logo(), NULL);
ut_assert(id > 0);
id = scene_menuitem(scn, OBJ_MENU, "item2", ITEM2, ITEM2_KEY,
ITEM2_LABEL, ITEM2_DESC, ITEM2_PREVIEW, 0, NULL);
ut_assert(id > 0);
ut_assertok(scene_obj_set_pos(scn, OBJ_MENU, 50, 400));
scn2 = expo_lookup_scene_id(exp, SCENE1);
ut_asserteq_ptr(scn, scn2);
scn2 = expo_lookup_scene_id(exp, SCENE2);
ut_assertnull(scn2);
/* render without a scene */
ut_asserteq(-ECHILD, expo_render(exp));
/* render it */
expo_set_scene_id(exp, SCENE1);
ut_assertok(expo_render(exp));
/* move down */
ut_assertok(expo_send_key(exp, BKEY_DOWN));
ut_assertok(expo_action_get(exp, &act));
ut_asserteq(EXPOACT_POINT, act.type);
ut_asserteq(ITEM2, act.select.id);
ut_assertok(expo_render(exp));
/* make sure only the preview for the second item is shown */
obj = scene_obj_find(scn, ITEM1_PREVIEW, SCENEOBJT_NONE);
ut_asserteq(true, obj->hide);
obj = scene_obj_find(scn, ITEM2_PREVIEW, SCENEOBJT_NONE);
ut_asserteq(false, obj->hide);
/* select it */
ut_assertok(expo_send_key(exp, BKEY_SELECT));
ut_assertok(expo_action_get(exp, &act));
ut_asserteq(EXPOACT_SELECT, act.type);
ut_asserteq(ITEM2, act.select.id);
/* make sure the action doesn't come again */
ut_asserteq(-EAGAIN, expo_action_get(exp, &act));
/* make sure there was no console output */
ut_assert_console_end();
/* now try in text mode */
exp_set_text_mode(exp, true);
ut_assertok(expo_render(exp));
ut_assert_nextline("U-Boot : Boot Menu");
ut_assert_nextline("%s", "");
ut_assert_nextline("Main Menu");
ut_assert_nextline("%s", "");
ut_assert_nextline(" 1 Play Lord Melchett");
ut_assert_nextline(" > 2 Now Lord Percy");
/* Move back up to the first item */
ut_assertok(expo_send_key(exp, BKEY_UP));
ut_assertok(expo_action_get(exp, &act));
ut_asserteq(EXPOACT_POINT, act.type);
ut_asserteq(ITEM1, act.select.id);
ut_assertok(expo_render(exp));
ut_assert_nextline("U-Boot : Boot Menu");
ut_assert_nextline("%s", "");
ut_assert_nextline("Main Menu");
ut_assert_nextline("%s", "");
ut_assert_nextline(" > 1 Play Lord Melchett");
ut_assert_nextline(" 2 Now Lord Percy");
ut_assert_console_end();
expo_destroy(exp);
return 0;
}
BOOTSTD_TEST(expo_render_image, UT_TESTF_DM | UT_TESTF_SCAN_FDT);

View file

@ -33,7 +33,7 @@ static int font_test_base(struct unit_test_state *uts)
ut_assertok(ut_check_console_end(uts));
ut_asserteq_str("nimbus_sans_l_regular",
vidconsole_get_font(dev, &size));
vidconsole_get_font_size(dev, &size));
ut_asserteq(18, size);
max_metrics = 1;
@ -53,14 +53,14 @@ static int font_test_base(struct unit_test_state *uts)
ut_assertok(ut_check_console_end(uts));
ut_asserteq_str("cantoraone_regular",
vidconsole_get_font(dev, &size));
vidconsole_get_font_size(dev, &size));
ut_asserteq(40, size);
ut_assertok(run_command("font size 30", 0));
ut_assertok(ut_check_console_end(uts));
ut_asserteq_str("cantoraone_regular",
vidconsole_get_font(dev, &size));
vidconsole_get_font_size(dev, &size));
ut_asserteq(30, size);
return 0;

Binary file not shown.

Binary file not shown.

View file

@ -39,6 +39,7 @@ def test_avb_verify(u_boot_console):
@pytest.mark.buildconfigspec('cmd_avb')
@pytest.mark.buildconfigspec('cmd_mmc')
@pytest.mark.notbuildconfigspec('sandbox')
def test_avb_mmc_uuid(u_boot_console):
"""Check if 'avb get_uuid' works, compare results with
'part list mmc 1' output
@ -97,6 +98,7 @@ def test_avb_is_unlocked(u_boot_console):
@pytest.mark.buildconfigspec('cmd_avb')
@pytest.mark.buildconfigspec('cmd_mmc')
@pytest.mark.notbuildconfigspec('sandbox')
def test_avb_mmc_read(u_boot_console):
"""Test mmc read operation
"""

View file

@ -1,6 +1,7 @@
# SPDX-License-Identifier: GPL-2.0
# Copyright (c) 2016, NVIDIA CORPORATION. All rights reserved.
import getpass
import gzip
import os
import os.path
@ -13,34 +14,217 @@ def mkdir_cond(dirname):
"""Create a directory if it doesn't already exist
Args:
dirname: Name of directory to create
dirname (str): Name of directory to create
"""
if not os.path.exists(dirname):
os.mkdir(dirname)
def setup_bootflow_image(u_boot_console):
"""Create a 20MB disk image with a single FAT partition"""
cons = u_boot_console
fname = os.path.join(cons.config.source_dir, 'mmc1.img')
def setup_image(cons, mmc_dev, part_type):
"""Create a 20MB disk image with a single partition
Args:
cons (ConsoleBase): Console to use
mmc_dev (int): MMC device number to use, e.g. 1
part_type (int): Partition type, e.g. 0xc for FAT32
Returns:
tuple:
str: Filename of MMC image
str: Directory name of 'mnt' directory
"""
fname = os.path.join(cons.config.source_dir, f'mmc{mmc_dev}.img')
mnt = os.path.join(cons.config.persistent_data_dir, 'mnt')
mkdir_cond(mnt)
u_boot_utils.run_and_log(cons, 'qemu-img create %s 20M' % fname)
u_boot_utils.run_and_log(cons, 'sudo sfdisk %s' % fname,
stdin=b'type=c')
stdin=f'type={part_type:x}'.encode('utf-8'))
return fname, mnt
def mount_image(cons, fname, mnt, fstype):
"""Create a filesystem and mount it on partition 1
Args:
cons (ConsoleBase): Console to use
fname (str): Filename of MMC image
mnt (str): Directory name of 'mnt' directory
fstype (str): Filesystem type ('vfat' or 'ext4')
Returns:
str: Name of loop device used
"""
out = u_boot_utils.run_and_log(cons, 'sudo losetup --show -f -P %s' % fname)
loop = out.strip()
part = f'{loop}p1'
u_boot_utils.run_and_log(cons, f'sudo mkfs.{fstype} {part}')
opts = ''
if fstype == 'vfat':
opts += ' -o uid={os.getuid()},gid={os.getgid()}'
u_boot_utils.run_and_log(cons, f'sudo mount -o loop {part} {mnt}{opts}')
u_boot_utils.run_and_log(cons, f'sudo chown {getpass.getuser()} {mnt}')
return loop
def copy_prepared_image(cons, mmc_dev, fname):
"""Use a prepared image since we cannot create one
Args:
cons (ConsoleBase): Console touse
mmc_dev (int): MMC device number
fname (str): Filename of MMC image
"""
infname = os.path.join(cons.config.source_dir,
f'test/py/tests/bootstd/mmc{mmc_dev}.img.xz')
u_boot_utils.run_and_log(
cons,
['sh', '-c', 'xz -dc %s >%s' % (infname, fname)])
def setup_bootmenu_image(cons):
"""Create a 20MB disk image with a single ext4 partition
This is modelled on Armbian 22.08 Jammy
"""
mmc_dev = 4
fname, mnt = setup_image(cons, mmc_dev, 0x83)
loop = None
mounted = False
complete = False
try:
out = u_boot_utils.run_and_log(cons,
'sudo losetup --show -f -P %s' % fname)
loop = out.strip()
fatpart = '%sp1' % loop
u_boot_utils.run_and_log(cons, 'sudo mkfs.vfat %s' % fatpart)
loop = mount_image(cons, fname, mnt, 'ext4')
mounted = True
vmlinux = 'Image'
initrd = 'uInitrd'
dtbdir = 'dtb'
script = '''# DO NOT EDIT THIS FILE
#
# Please edit /boot/armbianEnv.txt to set supported parameters
#
setenv load_addr "0x9000000"
setenv overlay_error "false"
# default values
setenv rootdev "/dev/mmcblk%dp1"
setenv verbosity "1"
setenv console "both"
setenv bootlogo "false"
setenv rootfstype "ext4"
setenv docker_optimizations "on"
setenv earlycon "off"
echo "Boot script loaded from ${devtype} ${devnum}"
if test -e ${devtype} ${devnum} ${prefix}armbianEnv.txt; then
load ${devtype} ${devnum} ${load_addr} ${prefix}armbianEnv.txt
env import -t ${load_addr} ${filesize}
fi
if test "${logo}" = "disabled"; then setenv logo "logo.nologo"; fi
if test "${console}" = "display" || test "${console}" = "both"; then setenv consoleargs "console=tty1"; fi
if test "${console}" = "serial" || test "${console}" = "both"; then setenv consoleargs "console=ttyS2,1500000 ${consoleargs}"; fi
if test "${earlycon}" = "on"; then setenv consoleargs "earlycon ${consoleargs}"; fi
if test "${bootlogo}" = "true"; then setenv consoleargs "bootsplash.bootfile=bootsplash.armbian ${consoleargs}"; fi
# get PARTUUID of first partition on SD/eMMC the boot script was loaded from
if test "${devtype}" = "mmc"; then part uuid mmc ${devnum}:1 partuuid; fi
setenv bootargs "root=${rootdev} rootwait rootfstype=${rootfstype} ${consoleargs} consoleblank=0 loglevel=${verbosity} ubootpart=${partuuid} usb-storage.quirks=${usbstoragequirks} ${extraargs} ${extraboardargs}"
if test "${docker_optimizations}" = "on"; then setenv bootargs "${bootargs} cgroup_enable=cpuset cgroup_memory=1 cgroup_enable=memory swapaccount=1"; fi
load ${devtype} ${devnum} ${ramdisk_addr_r} ${prefix}uInitrd
load ${devtype} ${devnum} ${kernel_addr_r} ${prefix}Image
load ${devtype} ${devnum} ${fdt_addr_r} ${prefix}dtb/${fdtfile}
fdt addr ${fdt_addr_r}
fdt resize 65536
for overlay_file in ${overlays}; do
if load ${devtype} ${devnum} ${load_addr} ${prefix}dtb/rockchip/overlay/${overlay_prefix}-${overlay_file}.dtbo; then
echo "Applying kernel provided DT overlay ${overlay_prefix}-${overlay_file}.dtbo"
fdt apply ${load_addr} || setenv overlay_error "true"
fi
done
for overlay_file in ${user_overlays}; do
if load ${devtype} ${devnum} ${load_addr} ${prefix}overlay-user/${overlay_file}.dtbo; then
echo "Applying user provided DT overlay ${overlay_file}.dtbo"
fdt apply ${load_addr} || setenv overlay_error "true"
fi
done
if test "${overlay_error}" = "true"; then
echo "Error applying DT overlays, restoring original DT"
load ${devtype} ${devnum} ${fdt_addr_r} ${prefix}dtb/${fdtfile}
else
if load ${devtype} ${devnum} ${load_addr} ${prefix}dtb/rockchip/overlay/${overlay_prefix}-fixup.scr; then
echo "Applying kernel provided DT fixup script (${overlay_prefix}-fixup.scr)"
source ${load_addr}
fi
if test -e ${devtype} ${devnum} ${prefix}fixup.scr; then
load ${devtype} ${devnum} ${load_addr} ${prefix}fixup.scr
echo "Applying user provided fixup script (fixup.scr)"
source ${load_addr}
fi
fi
booti ${kernel_addr_r} ${ramdisk_addr_r} ${fdt_addr_r}
# Recompile with:
# mkimage -C none -A arm -T script -d /boot/boot.cmd /boot/boot.scr
''' % (mmc_dev)
bootdir = os.path.join(mnt, 'boot')
mkdir_cond(bootdir)
cmd_fname = os.path.join(bootdir, 'boot.cmd')
scr_fname = os.path.join(bootdir, 'boot.scr')
with open(cmd_fname, 'w') as outf:
print(script, file=outf)
infname = os.path.join(cons.config.source_dir,
'test/py/tests/bootstd/armbian.bmp.xz')
bmp_file = os.path.join(bootdir, 'boot.bmp')
u_boot_utils.run_and_log(
cons, 'sudo mount -o loop %s %s -o uid=%d,gid=%d' %
(fatpart, mnt, os.getuid(), os.getgid()))
cons,
['sh', '-c', f'xz -dc {infname} >{bmp_file}'])
u_boot_utils.run_and_log(
cons, f'mkimage -C none -A arm -T script -d {cmd_fname} {scr_fname}')
kernel = 'vmlinuz-5.15.63-rockchip64'
target = os.path.join(bootdir, kernel)
with open(target, 'wb') as outf:
print('kernel', outf)
symlink = os.path.join(bootdir, 'Image')
if os.path.exists(symlink):
os.remove(symlink)
u_boot_utils.run_and_log(
cons, f'echo here {kernel} {symlink}')
os.symlink(kernel, symlink)
u_boot_utils.run_and_log(
cons, f'mkimage -C none -A arm -T script -d {cmd_fname} {scr_fname}')
complete = True
except ValueError as exc:
print('Falled to create image, failing back to prepared copy: %s',
str(exc))
finally:
if mounted:
u_boot_utils.run_and_log(cons, 'sudo umount %s' % mnt)
if loop:
u_boot_utils.run_and_log(cons, 'sudo losetup -d %s' % loop)
if not complete:
copy_prepared_image(cons, mmc_dev, fname)
def setup_bootflow_image(cons):
"""Create a 20MB disk image with a single FAT partition"""
mmc_dev = 1
fname, mnt = setup_image(cons, mmc_dev, 0xc)
loop = None
mounted = False
complete = False
try:
loop = mount_image(cons, fname, mnt, 'vfat')
mounted = True
vmlinux = 'vmlinuz-5.3.7-301.fc31.armv7hl'
@ -90,12 +274,7 @@ label Fedora-Workstation-armhfp-31-1.9 (5.3.7-301.fc31.armv7hl)
u_boot_utils.run_and_log(cons, 'sudo losetup -d %s' % loop)
if not complete:
# Use a prepared image since we cannot create one
infname = os.path.join(cons.config.source_dir,
'test/py/tests/bootstd/mmc1.img.xz')
u_boot_utils.run_and_log(
cons,
['sh', '-c', 'xz -dc %s >%s' % (infname, fname)])
copy_prepared_image(cons, mmc_dev, fname)
@pytest.mark.buildconfigspec('ut_dm')
@ -134,6 +313,7 @@ def test_ut_dm_init_bootstd(u_boot_console):
"""Initialise data for bootflow tests"""
setup_bootflow_image(u_boot_console)
setup_bootmenu_image(u_boot_console)
# Restart so that the new mmc1.img is picked up
u_boot_console.restart_uboot()