From 30f3333d8860fd97e131e24ad33a80f4d46e98b1 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Fri, 6 Jan 2023 08:52:28 -0600 Subject: image: Move common image code to image_board and command We should use the cmd/ directory for commands rather than for common code used elsewhere in U-Boot. Move the common 'source' code into image-board.c to achieve this. The image_source_script() function needs to call run_command_list() so seems to belong better in the command library. Move and rename it. Signed-off-by: Simon Glass --- boot/bootmeth_script.c | 2 +- boot/image-board.c | 159 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 160 insertions(+), 1 deletion(-) (limited to 'boot') diff --git a/boot/bootmeth_script.c b/boot/bootmeth_script.c index 6c84721d1cd..5799c89a466 100644 --- a/boot/bootmeth_script.c +++ b/boot/bootmeth_script.c @@ -101,7 +101,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/image-board.c b/boot/image-board.c index 0fd63291d3f..e5d71a3d541 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; +} -- cgit v1.2.3 From 2175e76a51e53798ee4e19903b368a7e6c98356a Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Fri, 6 Jan 2023 08:52:33 -0600 Subject: bootstd: Read the Operating System name for distro/scripts Add the concept of an OS name to the bootflow. This typically includes the OS name, version and kernel version. Implement this for the distro and script bootmeths so that it works with Armbian and older version of Fedora. Signed-off-by: Simon Glass --- boot/bootflow.c | 1 + boot/bootmeth_distro.c | 36 ++++++++++++++++++++++++++++++++++++ boot/bootmeth_script.c | 34 ++++++++++++++++++++++++++++++++++ 3 files changed, 71 insertions(+) (limited to 'boot') diff --git a/boot/bootflow.c b/boot/bootflow.c index f9ad4099244..163cd4953dd 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/bootmeth_distro.c b/boot/bootmeth_distro.c index 5c6c687f0a6..6ef0fa1f2c9 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 5799c89a466..ba8e5d0438a 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,10 @@ 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); + return 0; } -- cgit v1.2.3 From 24d8e1b37b90760a6c9867f37210aa4b1f2e8f63 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Fri, 6 Jan 2023 08:52:34 -0600 Subject: bootstd: Allow reading a logo for the OS Some operating systems provide a logo in bmp format. Read this in if present so it can be displayed in the menu. Signed-off-by: Simon Glass --- boot/bootmeth-uclass.c | 69 +++++++++++++++++++++++++++++++++++++++++++------- boot/bootmeth_script.c | 4 +++ 2 files changed, 64 insertions(+), 9 deletions(-) (limited to 'boot') diff --git a/boot/bootmeth-uclass.c b/boot/bootmeth-uclass.c index 25552dd96f6..4c3529d1555 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_script.c b/boot/bootmeth_script.c index ba8e5d0438a..c7061eb998f 100644 --- a/boot/bootmeth_script.c +++ b/boot/bootmeth_script.c @@ -109,6 +109,10 @@ static int script_read_bootflow(struct udevice *dev, struct bootflow *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; } -- cgit v1.2.3 From 87c6f8a4880d849fc4b782d0c43880ef7eb1fe8e Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Fri, 6 Jan 2023 08:52:36 -0600 Subject: expo: Add basic implementation 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. One created, the same expo can be automatically presented in graphical form using a vidconsole, or in text form on a serial console. Add an initial implementation of the expo itself. Supports for scenes and objects is provided later. Signed-off-by: Simon Glass --- boot/Kconfig | 12 +++++ boot/expo.c | 170 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 182 insertions(+) create mode 100644 boot/expo.c (limited to 'boot') diff --git a/boot/Kconfig b/boot/Kconfig index 30bc182fcd5..36ccbf6b542 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/expo.c b/boot/expo.c new file mode 100644 index 00000000000..05950a17603 --- /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 + */ + +#include +#include +#include +#include +#include +#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; +} -- cgit v1.2.3 From 5e2607aedb6ad55268956eb7d0bce70520d57d11 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Fri, 6 Jan 2023 08:52:37 -0600 Subject: expo: Add support for scenes A scene is a single screen within an expo. It is possible to move between scenes but only one can be displayed at once. Add a basic implementation. Signed-off-by: Simon Glass --- boot/scene.c | 414 ++++++++++++++++++++++++++++++++++++++++++++++++++ boot/scene_internal.h | 123 +++++++++++++++ 2 files changed, 537 insertions(+) create mode 100644 boot/scene.c create mode 100644 boot/scene_internal.h (limited to 'boot') diff --git a/boot/scene.c b/boot/scene.c new file mode 100644 index 00000000000..030f6aa2a0a --- /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 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#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 00000000000..e8fd765811e --- /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 + */ + +#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 */ -- cgit v1.2.3 From 226777f170f1533eacc097e73e59a7a3ab1c62a0 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Fri, 6 Jan 2023 08:52:38 -0600 Subject: expo: Add support for scene menus A menu is a key part of the expo design. It consists of a number of items which the user can select from. Add the initial implementation of this. Signed-off-by: Simon Glass --- boot/Makefile | 2 + boot/scene_menu.c | 390 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 392 insertions(+) create mode 100644 boot/scene_menu.c (limited to 'boot') diff --git a/boot/Makefile b/boot/Makefile index f0c31549213..0b30fcd64a9 100644 --- a/boot/Makefile +++ b/boot/Makefile @@ -47,6 +47,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/scene_menu.c b/boot/scene_menu.c new file mode 100644 index 00000000000..18998e862ab --- /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 + */ + +#define LOG_CATEGORY LOGC_BOOT + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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; +} -- cgit v1.2.3 From 02d929bfb25af22171dbd100f38ffd8fa6bf6238 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Fri, 6 Jan 2023 08:52:40 -0600 Subject: bootstd: Support creating a boot menu Create an expo to handle the boot menu. For now this is quite simple, with just a header, some menu items and a pointer to show the current one. Signed-off-by: Simon Glass --- boot/Makefile | 1 + boot/bootflow_internal.h | 47 +++++++++ boot/bootflow_menu.c | 241 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 289 insertions(+) create mode 100644 boot/bootflow_internal.h create mode 100644 boot/bootflow_menu.c (limited to 'boot') diff --git a/boot/Makefile b/boot/Makefile index 0b30fcd64a9..f990e66f522 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 diff --git a/boot/bootflow_internal.h b/boot/bootflow_internal.h new file mode 100644 index 00000000000..38cf02a55b5 --- /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 + */ + +#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 00000000000..1105783afeb --- /dev/null +++ b/boot/bootflow_menu.c @@ -0,0 +1,241 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Provide a menu of available bootflows and related options + * + * Copyright 2022 Google LLC + * Written by Simon Glass + */ + +#define LOG_CATEGORY UCLASS_BOOTSTD + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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_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); + + /* 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; +} -- cgit v1.2.3 From e64c29521c0e6111b446fd67e85c6dd3d491103c Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Fri, 6 Jan 2023 08:52:42 -0600 Subject: bootstd: Support setting a theme for the menu Allow a theme to be set. For now this is very simple, just a default font size to use for all elements. Signed-off-by: Simon Glass --- boot/bootflow_menu.c | 43 +++++++++++++++++++++++++++++++++++++++++++ boot/bootstd-uclass.c | 2 ++ 2 files changed, 45 insertions(+) (limited to 'boot') diff --git a/boot/bootflow_menu.c b/boot/bootflow_menu.c index 1105783afeb..7f06dac0af7 100644 --- a/boot/bootflow_menu.c +++ b/boot/bootflow_menu.c @@ -129,6 +129,43 @@ int bootflow_menu_new(struct expo **expp) 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) { @@ -149,6 +186,12 @@ int bootflow_menu_run(struct bootstd_priv *std, bool text_mode, 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) diff --git a/boot/bootstd-uclass.c b/boot/bootstd-uclass.c index 565c22a36e7..7887acdc11b 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; -- cgit v1.2.3