diff options
Diffstat (limited to 'boot')
-rw-r--r-- | boot/Kconfig | 12 | ||||
-rw-r--r-- | boot/Makefile | 3 | ||||
-rw-r--r-- | boot/bootflow.c | 1 | ||||
-rw-r--r-- | boot/bootflow_internal.h | 47 | ||||
-rw-r--r-- | boot/bootflow_menu.c | 284 | ||||
-rw-r--r-- | boot/bootmeth-uclass.c | 69 | ||||
-rw-r--r-- | boot/bootmeth_distro.c | 36 | ||||
-rw-r--r-- | boot/bootmeth_script.c | 40 | ||||
-rw-r--r-- | boot/bootstd-uclass.c | 2 | ||||
-rw-r--r-- | boot/expo.c | 170 | ||||
-rw-r--r-- | boot/image-board.c | 159 | ||||
-rw-r--r-- | boot/scene.c | 414 | ||||
-rw-r--r-- | boot/scene_internal.h | 123 | ||||
-rw-r--r-- | boot/scene_menu.c | 390 |
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; +} |