aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--arch/arm/mach-stm32mp/cmd_stm32prog/cmd_stm32prog.c2
-rw-r--r--arch/sandbox/dts/test.dts11
-rw-r--r--boot/Kconfig12
-rw-r--r--boot/Makefile3
-rw-r--r--boot/bootflow.c1
-rw-r--r--boot/bootflow_internal.h47
-rw-r--r--boot/bootflow_menu.c284
-rw-r--r--boot/bootmeth-uclass.c69
-rw-r--r--boot/bootmeth_distro.c36
-rw-r--r--boot/bootmeth_script.c40
-rw-r--r--boot/bootstd-uclass.c2
-rw-r--r--boot/expo.c170
-rw-r--r--boot/image-board.c159
-rw-r--r--boot/scene.c414
-rw-r--r--boot/scene_internal.h123
-rw-r--r--boot/scene_menu.c390
-rw-r--r--cmd/bootflow.c44
-rw-r--r--cmd/bootmenu.c19
-rw-r--r--cmd/eficonfig.c38
-rw-r--r--cmd/font.c11
-rw-r--r--cmd/source.c166
-rw-r--r--common/Makefile6
-rw-r--r--common/cli_getch.c208
-rw-r--r--common/cli_readline.c150
-rw-r--r--common/command.c19
-rw-r--r--common/menu.c157
-rw-r--r--configs/sandbox_defconfig2
-rw-r--r--configs/sandbox_flattree_defconfig2
-rw-r--r--configs/snow_defconfig4
-rw-r--r--configs/tools-only_defconfig2
-rw-r--r--doc/develop/expo.rst188
-rw-r--r--doc/develop/index.rst1
-rw-r--r--drivers/usb/gadget/f_sdp.c2
-rw-r--r--drivers/video/Kconfig7
-rw-r--r--drivers/video/console_truetype.c37
-rw-r--r--drivers/video/vidconsole-uclass.c33
-rw-r--r--include/bootflow.h40
-rw-r--r--include/bootmeth.h16
-rw-r--r--include/bootstd.h4
-rw-r--r--include/cli.h74
-rw-r--r--include/command.h12
-rw-r--r--include/expo.h522
-rw-r--r--include/image.h30
-rw-r--r--include/menu.h77
-rw-r--r--include/video.h2
-rw-r--r--include/video_console.h75
-rw-r--r--test/boot/Makefile2
-rw-r--r--test/boot/bootflow.c130
-rw-r--r--test/boot/expo.c539
-rw-r--r--test/cmd/font.c6
-rw-r--r--test/py/tests/bootstd/armbian.bmp.xzbin0 -> 1384 bytes
-rw-r--r--test/py/tests/bootstd/mmc4.img.xzbin0 -> 7072 bytes
-rw-r--r--test/py/tests/test_android/test_avb.py2
-rw-r--r--test/py/tests/test_ut.py218
54 files changed, 4105 insertions, 503 deletions
diff --git a/arch/arm/mach-stm32mp/cmd_stm32prog/cmd_stm32prog.c b/arch/arm/mach-stm32mp/cmd_stm32prog/cmd_stm32prog.c
index cb13a14d8295..a8372356b0e8 100644
--- a/arch/arm/mach-stm32mp/cmd_stm32prog/cmd_stm32prog.c
+++ b/arch/arm/mach-stm32mp/cmd_stm32prog/cmd_stm32prog.c
@@ -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");
diff --git a/arch/sandbox/dts/test.dts b/arch/sandbox/dts/test.dts
index dffe10adbf47..2e580f980fc6 100644
--- a/arch/sandbox/dts/test.dts
+++ b/arch/sandbox/dts/test.dts
@@ -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";
};
diff --git a/boot/Kconfig b/boot/Kconfig
index 30bc182fcd5c..36ccbf6b5428 100644
--- a/boot/Kconfig
+++ b/boot/Kconfig
@@ -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
diff --git a/boot/Makefile b/boot/Makefile
index f0c315492132..f990e66f5220 100644
--- a/boot/Makefile
+++ b/boot/Makefile
@@ -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
diff --git a/boot/bootflow.c b/boot/bootflow.c
index f9ad40992442..163cd4953ddb 100644
--- a/boot/bootflow.c
+++ b/boot/bootflow.c
@@ -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)
diff --git a/boot/bootflow_internal.h b/boot/bootflow_internal.h
new file mode 100644
index 000000000000..38cf02a55b5c
--- /dev/null
+++ b/boot/bootflow_internal.h
@@ -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 */
diff --git a/boot/bootflow_menu.c b/boot/bootflow_menu.c
new file mode 100644
index 000000000000..7f06dac0af77
--- /dev/null
+++ b/boot/bootflow_menu.c
@@ -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;
+}
diff --git a/boot/bootmeth-uclass.c b/boot/bootmeth-uclass.c
index 25552dd96f67..4c3529d15552 100644
--- a/boot/bootmeth-uclass.c
+++ b/boot/bootmeth-uclass.c
@@ -290,25 +290,19 @@ 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;
- uint size;
int ret;
- size = bflow->size;
- log_debug(" - script file size %x\n", size);
- if (size > size_limit)
- return log_msg_ret("chk", -E2BIG);
-
- buf = memalign(align, size + 1);
+ buf = malloc(size + 1);
if (!buf)
return log_msg_ret("buf", -ENOMEM);
addr = map_to_sysmem(buf);
- ret = fs_read(bflow->fname, addr, 0, 0, &bytes_read);
+ ret = fs_read(fname, addr, 0, size, &bytes_read);
if (ret) {
free(buf);
return log_msg_ret("read", ret);
@@ -316,12 +310,69 @@ int bootmeth_alloc_file(struct bootflow *bflow, uint size_limit, uint align)
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;
+
+ size = bflow->size;
+ log_debug(" - script file size %x\n", size);
+ if (size > size_limit)
+ return log_msg_ret("chk", -E2BIG);
+
+ ret = alloc_file(bflow->fname, bflow->size, &buf);
+ if (ret)
+ return log_msg_ret("all", ret);
+
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)
{
diff --git a/boot/bootmeth_distro.c b/boot/bootmeth_distro.c
index 5c6c687f0a64..6ef0fa1f2c90 100644
--- a/boot/bootmeth_distro.c
+++ b/boot/bootmeth_distro.c
@@ -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;
}
diff --git a/boot/bootmeth_script.c b/boot/bootmeth_script.c
index 6c84721d1cd4..c7061eb998f4 100644
--- a/boot/bootmeth_script.c
+++ b/boot/bootmeth_script.c
@@ -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);
diff --git a/boot/bootstd-uclass.c b/boot/bootstd-uclass.c
index 565c22a36e78..7887acdc11b2 100644
--- a/boot/bootstd-uclass.c
+++ b/boot/bootstd-uclass.c
@@ -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;
diff --git a/boot/expo.c b/boot/expo.c
new file mode 100644
index 000000000000..05950a176038
--- /dev/null
+++ b/boot/expo.c
@@ -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;
+}
diff --git a/boot/image-board.c b/boot/image-board.c
index 0fd63291d3fc..e5d71a3d5419 100644
--- a/boot/image-board.c
+++ b/boot/image-board.c
@@ -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;
+}
diff --git a/boot/scene.c b/boot/scene.c
new file mode 100644
index 000000000000..030f6aa2a0a9
--- /dev/null
+++ b/boot/scene.c
@@ -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;
+}
diff --git a/boot/scene_internal.h b/boot/scene_internal.h
new file mode 100644
index 000000000000..e8fd765811e1
--- /dev/null
+++ b/boot/scene_internal.h
@@ -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 */
diff --git a/boot/scene_menu.c b/boot/scene_menu.c
new file mode 100644
index 000000000000..18998e862ab3
--- /dev/null
+++ b/boot/scene_menu.c
@@ -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;
+}
diff --git a/cmd/bootflow.c b/cmd/bootflow.c
index 313103d27754..2b6ed26fdcb6 100644
--- a/cmd/bootflow.c
+++ b/cmd/bootflow.c
@@ -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
);
diff --git a/cmd/bootmenu.c b/cmd/bootmenu.c
index e5a10f5d5c4d..3236ca5d7994 100644
--- a/cmd/bootmenu.c
+++ b/cmd/bootmenu.c
@@ -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)
diff --git a/cmd/eficonfig.c b/cmd/eficonfig.c
index ce7175a56666..d830e4af53b8 100644
--- a/cmd/eficonfig.c
+++ b/cmd/eficonfig.c
@@ -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 */
diff --git a/cmd/font.c b/cmd/font.c
index 3e522f3aaa17..7b4347f32b55 100644
--- a/cmd/font.c
+++ b/cmd/font.c
@@ -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;
diff --git a/cmd/source.c b/cmd/source.c
index 94da5d8d6ade..92c7835bf50e 100644
--- a/cmd/source.c
+++ b/cmd/source.c
@@ -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
diff --git a/common/Makefile b/common/Makefile
index 7789aab484fd..252e9656dfd8 100644
--- a/common/Makefile
+++ b/common/Makefile
@@ -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
diff --git a/common/cli_getch.c b/common/cli_getch.c
new file mode 100644
index 000000000000..87c23edcf4b4
--- /dev/null
+++ b/common/cli_getch.c
@@ -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;
+}
diff --git a/common/cli_readline.c b/common/cli_readline.c
index d6444f5fc1d3..709e9c3d38b1 100644
--- a/common/cli_readline.c
+++ b/common/cli_readline.c
@@ -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);
-
- while (!tstc()) { /* while no incoming data */
- if (get_ticks() >= etime)
- return -2; /* timed out */
- schedule();
+ /* Check for saved characters */
+ ichar = cli_ch_process(cch, 0);
+
+ 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
diff --git a/common/command.c b/common/command.c
index 41c91c6d8c87..7a86bd76a4c5 100644
--- a/common/command.c
+++ b/common/command.c
@@ -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);
+}
diff --git a/common/menu.c b/common/menu.c
index 8fe00965c0cd..cdcdbb2a185c 100644
--- a/common/menu.c
+++ b/common/menu.c
@@ -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)
{
- int c;
-
- if (*esc == 1) {
- if (tstc()) {
- c = getchar();
- } else {
- schedule();
- mdelay(10);
- if (tstc())
- c = getchar();
- else
- c = '\e';
- }
- } else {
- while (!tstc()) {
- schedule();
- mdelay(10);
- }
- c = getchar();
- }
+ enum bootmenu_key key;
- switch (*esc) {
- case 0:
- /* First char of ANSI escape sequence '\e' */
- if (c == '\e') {
- *esc = 1;
- *key = KEY_NONE;
- }
+ switch (ichar) {
+ case '\n':
+ /* enter key was pressed */
+ key = BKEY_SELECT;
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;
- }
+ case CTL_CH('c'):
+ case '\e':
+ /* ^C was pressed */
+ key = BKEY_QUIT;
break;
- case 2:
- case 3:
- /* Third char of ANSI (number '1') - optional */
- if (*esc == 2 && c == '1') {
- *esc = 3;
- *key = KEY_NONE;
- break;
- }
-
- *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;
-
+ 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;
}
- /* enter key was pressed */
- if (c == '\r')
- *key = KEY_SELECT;
+ return key;
+}
- /* ^C was pressed */
- if (c == 0x3)
- *key = KEY_QUIT;
+enum bootmenu_key bootmenu_loop(struct bootmenu_data *menu,
+ struct cli_ch_state *cch)
+{
+ enum bootmenu_key key;
+ int c;
- if (c == '+')
- *key = KEY_PLUS;
+ c = cli_ch_process(cch, 0);
+ if (!c) {
+ while (!c && !tstc()) {
+ schedule();
+ mdelay(10);
+ c = cli_ch_process(cch, -ETIMEDOUT);
+ }
+ if (!c) {
+ c = getchar();
+ c = cli_ch_process(cch, c);
+ }
+ }
- if (c == '-')
- *key = KEY_MINUS;
+ key = bootmenu_conv_key(c);
- if (c == ' ')
- *key = KEY_SPACE;
+ return key;
}
diff --git a/configs/sandbox_defconfig b/configs/sandbox_defconfig
index de799b5cea34..6c5bacea323c 100644
--- a/configs/sandbox_defconfig
+++ b/configs/sandbox_defconfig
@@ -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
diff --git a/configs/sandbox_flattree_defconfig b/configs/sandbox_flattree_defconfig
index 88aaddfa4ae2..41453e0e2fab 100644
--- a/configs/sandbox_flattree_defconfig
+++ b/configs/sandbox_flattree_defconfig
@@ -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
diff --git a/configs/snow_defconfig b/configs/snow_defconfig
index 6921c5667dac..faa3a944c027 100644
--- a/configs/snow_defconfig
+++ b/configs/snow_defconfig
@@ -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
diff --git a/configs/tools-only_defconfig b/configs/tools-only_defconfig
index de99f3857c3e..2197063112b0 100644
--- a/configs/tools-only_defconfig
+++ b/configs/tools-only_defconfig
@@ -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
diff --git a/doc/develop/expo.rst b/doc/develop/expo.rst
new file mode 100644
index 000000000000..32dd7f090307
--- /dev/null
+++ b/doc/develop/expo.rst
@@ -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
diff --git a/doc/develop/index.rst b/doc/develop/index.rst
index 97c526e997e1..79d7736b1343 100644
--- a/doc/develop/index.rst
+++ b/doc/develop/index.rst
@@ -34,6 +34,7 @@ Implementation
distro
driver-model/index
environment
+ expo
event
global_data
logging
diff --git a/drivers/usb/gadget/f_sdp.c b/drivers/usb/gadget/f_sdp.c
index 5ae5b62741a2..9ea43f29cfbc 100644
--- a/drivers/usb/gadget/f_sdp.c
+++ b/drivers/usb/gadget/f_sdp.c
@@ -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
}
diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig
index f539977d9b73..440b161b8423 100644
--- a/drivers/video/Kconfig
+++ b/drivers/video/Kconfig
@@ -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"
diff --git a/drivers/video/console_truetype.c b/drivers/video/console_truetype.c
index 6859c9fa1163..9cac9a6de4d1 100644
--- a/drivers/video/console_truetype.c
+++ b/drivers/video/console_truetype.c
@@ -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) = {
diff --git a/drivers/video/vidconsole-uclass.c b/drivers/video/vidconsole-uclass.c
index 6bdfb6e37dd5..aadd54bfd4ff 100644
--- a/drivers/video/vidconsole-uclass.c
+++ b/drivers/video/vidconsole-uclass.c
@@ -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)
{
diff --git a/include/bootflow.h b/include/bootflow.h
index 32dbbbbe2613..c201246c6ded 100644
--- a/include/bootflow.h
+++ b/include/bootflow.h
@@ -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
diff --git a/include/bootmeth.h b/include/bootmeth.h
index 50ded055f3fe..669b14ce81e6 100644
--- a/include/bootmeth.h
+++ b/include/bootmeth.h
@@ -266,6 +266,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
*
* Reads a named file from the same location as the bootflow file.
diff --git a/include/bootstd.h b/include/bootstd.h
index 01be249d16e9..4fa0d531001a 100644
--- a/include/bootstd.h
+++ b/include/bootstd.h
@@ -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;
};
/**
diff --git a/include/cli.h b/include/cli.h
index ba5b8ebd36eb..c777c90313f2 100644
--- a/include/cli.h
+++ b/include/cli.h
@@ -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
diff --git a/include/command.h b/include/command.h
index 966fd23c639f..3c6132e0c54e 100644
--- a/include/command.h
+++ b/include/command.h
@@ -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__ */
/*
diff --git a/include/expo.h b/include/expo.h
new file mode 100644
index 000000000000..d242f48e30c7
--- /dev/null
+++ b/include/expo.h
@@ -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 */
diff --git a/include/image.h b/include/image.h
index bed75ce1b384..7717a4c13d38 100644
--- a/include/image.h
+++ b/include/image.h
@@ -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.
- *
- * 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)
- */
-int image_source_script(ulong addr, const char *fit_uname,
- const char *confname);
+ * image_locate_script() - Locate the raw script in an image
+ *
+ * @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_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
diff --git a/include/menu.h b/include/menu.h
index 702aacb170c6..1e88141d6bf5 100644
--- a/include/menu.h
+++ b/include/menu.h
@@ -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__ */
diff --git a/include/video.h b/include/video.h
index 43f2e2c02f0f..3f67a93bc937 100644
--- a/include/video.h
+++ b/include/video.h
@@ -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,
diff --git a/include/video_console.h b/include/video_console.h
index d755eb73cf20..9d2c0f210e4b 100644
--- a/include/video_console.h
+++ b/include/video_console.h
@@ -63,6 +63,15 @@ struct vidconsole_priv {
};
/**
+ * 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
*
* These operations work on either an absolute console position (measured
@@ -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,12 +148,54 @@ 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
*
* @dev: Device to write to
@@ -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
/**
diff --git a/test/boot/Makefile b/test/boot/Makefile
index d724629d3b0f..22ed61c8fa02 100644
--- a/test/boot/Makefile
+++ b/test/boot/Makefile
@@ -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
diff --git a/test/boot/bootflow.c b/test/boot/bootflow.c
index e1e07082105f..5b76cd3ab14c 100644
--- a/test/boot/bootflow.c
+++ b/test/boot/bootflow.c
@@ -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);
diff --git a/test/boot/expo.c b/test/boot/expo.c
new file mode 100644
index 000000000000..7104dff05e8d
--- /dev/null
+++ b/test/boot/expo.c
@@ -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);
diff --git a/test/cmd/font.c b/test/cmd/font.c
index 7a4156ade62d..adb353965a7f 100644
--- a/test/cmd/font.c
+++ b/test/cmd/font.c
@@ -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;
diff --git a/test/py/tests/bootstd/armbian.bmp.xz b/test/py/tests/bootstd/armbian.bmp.xz
new file mode 100644
index 000000000000..ad137ea6e6df
--- /dev/null
+++ b/test/py/tests/bootstd/armbian.bmp.xz
Binary files differ
diff --git a/test/py/tests/bootstd/mmc4.img.xz b/test/py/tests/bootstd/mmc4.img.xz
new file mode 100644
index 000000000000..f4db011969fa
--- /dev/null
+++ b/test/py/tests/bootstd/mmc4.img.xz
Binary files differ
diff --git a/test/py/tests/test_android/test_avb.py b/test/py/tests/test_android/test_avb.py
index a3f883136b05..bc5c5b558215 100644
--- a/test/py/tests/test_android/test_avb.py
+++ b/test/py/tests/test_android/test_avb.py
@@ -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
"""
diff --git a/test/py/tests/test_ut.py b/test/py/tests/test_ut.py
index bab8b97672bb..6958fabfa34d 100644
--- a/test/py/tests/test_ut.py
+++ b/test/py/tests/test_ut.py
@@ -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,
+ ['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, 'sudo mount -o loop %s %s -o uid=%d,gid=%d' %
- (fatpart, mnt, os.getuid(), os.getgid()))
+ 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()