[PATCH v3 22/25] bootstd: Support creating a boot menu

Simon Glass sjg at chromium.org
Fri Jan 6 15:52:40 CET 2023


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 <sjg at chromium.org>
---

(no changes since v2)

Changes in v2:
- Rebase to master

 boot/Makefile            |   1 +
 boot/bootflow_internal.h |  47 ++++++++
 boot/bootflow_menu.c     | 241 +++++++++++++++++++++++++++++++++++++++
 cmd/bootflow.c           |  37 +++++-
 include/bootflow.h       |  23 ++++
 5 files changed, 347 insertions(+), 2 deletions(-)
 create mode 100644 boot/bootflow_internal.h
 create mode 100644 boot/bootflow_menu.c

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 <sjg at 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 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 <sjg at 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_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;
+}
diff --git a/cmd/bootflow.c b/cmd/bootflow.c
index 495ef85f25b..2b6ed26fdcb 100644
--- a/cmd/bootflow.c
+++ b/cmd/bootflow.c
@@ -389,6 +389,37 @@ static int do_bootflow_boot(struct cmd_tbl *cmdtp, int flag, int argc,
 
 	return 0;
 }
+
+static int do_bootflow_menu(struct cmd_tbl *cmdtp, int flag, int argc,
+			    char *const argv[])
+{
+	struct bootstd_priv *std;
+	struct bootflow *bflow;
+	bool text_mode = false;
+	int ret;
+
+	if (argc > 1 && *argv[1] == '-')
+		text_mode = strchr(argv[1], 't');
+
+	ret = bootstd_get_priv(&std);
+	if (ret)
+		return CMD_RET_FAILURE;
+
+	ret = bootflow_menu_run(std, text_mode, &bflow);
+	if (ret) {
+		if (ret == -EAGAIN)
+			printf("Nothing chosen\n");
+		else
+			printf("Menu failed (err=%d)\n", ret);
+
+		return CMD_RET_FAILURE;
+	}
+
+	printf("Selected: %s\n", bflow->os_name ? bflow->os_name : bflow->name);
+	std->cur_bootflow = bflow;
+
+	return 0;
+}
 #endif /* CONFIG_CMD_BOOTFLOW_FULL */
 
 #ifdef CONFIG_SYS_LONGHELP
@@ -398,7 +429,8 @@ static char bootflow_help_text[] =
 	"bootflow list [-e]             - list scanned bootflows (-e errors)\n"
 	"bootflow select [<num>|<name>] - select a bootflow\n"
 	"bootflow info [-d]             - show info on current bootflow (-d dump bootflow)\n"
-	"bootflow boot                  - boot current bootflow (or first available if none selected)";
+	"bootflow boot                  - boot current bootflow (or first available if none selected)\n"
+	"bootflow menu [-t]             - show a menu of available bootflows";
 #else
 	"scan - boot first available bootflow\n";
 #endif
@@ -410,6 +442,7 @@ U_BOOT_CMD_WITH_SUBCMDS(bootflow, "Boot flows", bootflow_help_text,
 	U_BOOT_SUBCMD_MKENT(list, 2, 1, do_bootflow_list),
 	U_BOOT_SUBCMD_MKENT(select, 2, 1, do_bootflow_select),
 	U_BOOT_SUBCMD_MKENT(info, 2, 1, do_bootflow_info),
-	U_BOOT_SUBCMD_MKENT(boot, 1, 1, do_bootflow_boot)
+	U_BOOT_SUBCMD_MKENT(boot, 1, 1, do_bootflow_boot),
+	U_BOOT_SUBCMD_MKENT(menu, 2, 1, do_bootflow_menu),
 #endif
 );
diff --git a/include/bootflow.h b/include/bootflow.h
index 8a07ab30191..e7a09568f1b 100644
--- a/include/bootflow.h
+++ b/include/bootflow.h
@@ -9,6 +9,9 @@
 
 #include <linux/list.h>
 
+struct bootstd_priv;
+struct expo;
+
 /**
  * enum bootflow_state_t - states that a particular bootflow can be in
  *
@@ -336,4 +339,24 @@ int bootflow_iter_uses_network(const struct bootflow_iter *iter);
  */
 int bootflow_iter_uses_system(const struct bootflow_iter *iter);
 
+/**
+ * bootflow_menu_new() - Create a new bootflow menu
+ *
+ * @expp: Returns the expo created
+ * Returns 0 on success, -ve on error
+ */
+int bootflow_menu_new(struct expo **expp);
+
+/**
+ * bootflow_menu_run() - Create and run a menu of available bootflows
+ *
+ * @std: Bootstd information
+ * @text_mode: Uses a text-based menu suitable for a serial port
+ * @bflowp: Returns chosen bootflow (set to NULL if nothing is chosen)
+ * @return 0 if an option was chosen, -EAGAIN if nothing was chosen, -ve on
+ * error
+ */
+int bootflow_menu_run(struct bootstd_priv *std, bool text_mode,
+		      struct bootflow **bflowp);
+
 #endif
-- 
2.39.0.314.g84b9a713c41-goog



More information about the U-Boot mailing list