aboutsummaryrefslogtreecommitdiff
path: root/boot/bootflow_menu.c
diff options
context:
space:
mode:
Diffstat (limited to 'boot/bootflow_menu.c')
-rw-r--r--boot/bootflow_menu.c284
1 files changed, 284 insertions, 0 deletions
diff --git a/boot/bootflow_menu.c b/boot/bootflow_menu.c
new file mode 100644
index 0000000000..7f06dac0af
--- /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;
+}