aboutsummaryrefslogtreecommitdiff
path: root/boot
diff options
context:
space:
mode:
Diffstat (limited to 'boot')
-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
14 files changed, 1740 insertions, 10 deletions
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;
+}