All of lore.kernel.org
 help / color / mirror / Atom feed
From: Masahisa Kojima <masahisa.kojima@linaro.org>
To: u-boot@lists.denx.de
Cc: Heinrich Schuchardt <xypron.glpk@gmx.de>,
	Ilias Apalodimas <ilias.apalodimas@linaro.org>,
	Simon Glass <sjg@chromium.org>,
	Takahiro Akashi <takahiro.akashi@linaro.org>,
	Francois Ozog <francois.ozog@linaro.org>,
	Mark Kettenis <mark.kettenis@xs4all.nl>,
	Masahisa Kojima <masahisa.kojima@linaro.org>
Subject: [PATCH v4 09/11] efi_loader: add menu-driven UEFI Boot Variable maintenance
Date: Thu, 24 Mar 2022 22:54:41 +0900	[thread overview]
Message-ID: <20220324135443.1571-10-masahisa.kojima@linaro.org> (raw)
In-Reply-To: <20220324135443.1571-1-masahisa.kojima@linaro.org>

This commit adds the menu-driven UEFI Boot Variable maintenance.
User can add and delete the Boot#### variable, and update the
BootOrder variable through menu operation.

Signed-off-by: Masahisa Kojima <masahisa.kojima@linaro.org>
---
Chanes in v4:
- UEFI boot option maintenance menu is integrated into bootmenu
- display the simplified volume name(e.g. usb0:1, nvme1:2) for the
  volume selection
- instead of extending lib/efi_loader/efi_bootmgr.c, newly create
  lib/efi_loader/efi_bootmenu_maintenance.c and implement boot
  variable maintenance into it.

Changes in v3:
 not included in v3 series

Changes in v2:
- enable utf8 user input for boot option name
- create lib/efi_loader/efi_console.c::efi_console_get_u16_string() for
  utf8 user input handling
- use u16_strlcat instead of u16_strcat
- remove the EFI_CALLs, and newly create or expose the following
  xxx_int() functions.
    efi_locate_handle_buffer_int(), efi_open_volume_int(),
    efi_file_open_int(), efi_file_close_int(), efi_file_read_int() and
    efi_file_setpos_int().
  Note that EFI_CALLs still exist for EFI_DEVICE_PATH_TO_TEXT_PROTOCOL
  and EFI_SIMPLE_TEXT_INPUT/OUTPUT_PROTOCOL
- use efi_search_protocol() instead of calling locate_protocol() to get
  the device_path_to_text_protocol interface.
- remove unnecessary puts(ANSI_CLEAR_LINE), this patch is still depends on
  puts(ANSI_CLEAR_CONSOLE)
- skip SetVariable() if the bootorder is not changed
 cmd/bootmenu.c                            |   70 +-
 include/efi_loader.h                      |   37 +
 lib/efi_loader/Makefile                   |    1 +
 lib/efi_loader/efi_bootmenu_maintenance.c | 1116 +++++++++++++++++++++
 lib/efi_loader/efi_boottime.c             |   52 +-
 lib/efi_loader/efi_console.c              |   81 ++
 lib/efi_loader/efi_disk.c                 |   11 +
 lib/efi_loader/efi_file.c                 |   75 +-
 8 files changed, 1388 insertions(+), 55 deletions(-)
 create mode 100644 lib/efi_loader/efi_bootmenu_maintenance.c

diff --git a/cmd/bootmenu.c b/cmd/bootmenu.c
index d3591aa4a9..458eb90b63 100644
--- a/cmd/bootmenu.c
+++ b/cmd/bootmenu.c
@@ -19,6 +19,8 @@
 
 /* maximum bootmenu entries */
 #define MAX_COUNT	99
+#define STATIC_ENTRY 2
+#define MAX_DYNAMIC_ENTRY (MAX_COUNT - STATIC_ENTRY)
 
 /* maximal size of bootmenu env
  *  9 = strlen("bootmenu_")
@@ -39,10 +41,11 @@ enum boot_type {
 	BOOTMENU_TYPE_BOOTMENU,
 	BOOTMENU_TYPE_UEFI_BOOT_OPTION,
 	BOOTMENU_TYPE_DISTRO_BOOT,
+	BOOTMENU_TYPE_UEFI_MAINTENANCE,
 };
 
 struct bootmenu_entry {
-	unsigned short int num;		/* unique number 0 .. MAX_COUNT */
+	unsigned short int num;		/* unique number 0 .. MAX_DYNAMIC_ENTRY */
 	char key[3];			/* key identifier of number */
 	u16 *title;			/* title of entry */
 	char *command;			/* hush command of entry */
@@ -56,7 +59,7 @@ static char *bootmenu_getoption(unsigned short int n)
 {
 	char name[MAX_ENV_SIZE];
 
-	if (n > MAX_COUNT)
+	if (n > MAX_DYNAMIC_ENTRY)
 		return NULL;
 
 	sprintf(name, "bootmenu_%d", n);
@@ -215,7 +218,7 @@ static int prepare_bootmenu_entry(struct bootmenu_data *menu,
 		iter = entry;
 		i++;
 
-		if (i == MAX_COUNT - 1)
+		if (i == MAX_DYNAMIC_ENTRY)
 			break;
 	}
 
@@ -290,7 +293,7 @@ static int prepare_uefi_bootorder_entry(struct bootmenu_data *menu,
 
 		free(load_option);
 
-		if (i == MAX_COUNT - 1)
+		if (i == MAX_DYNAMIC_ENTRY)
 			break;
 	}
 
@@ -365,7 +368,7 @@ static int prepare_distro_boot_entry(struct bootmenu_data *menu,
 		iter = entry;
 		i++;
 
-		if (i == MAX_COUNT - 1)
+		if (i == MAX_DYNAMIC_ENTRY)
 			break;
 
 		token = strtok(NULL, " ");
@@ -405,19 +408,56 @@ static struct bootmenu_data *bootmenu_create(int delay)
 		goto cleanup;
 
 	if (IS_ENABLED(CONFIG_CMD_BOOTEFI_BOOTMGR)) {
-		if (i < MAX_COUNT - 1) {
+		if (i < MAX_DYNAMIC_ENTRY) {
 			ret = prepare_uefi_bootorder_entry(menu, &iter, &i);
 			if (ret < 0 && ret != -ENOENT)
 				goto cleanup;
 		}
 	}
 
-	if (i < MAX_COUNT - 1) {
+	if (i < MAX_DYNAMIC_ENTRY) {
 		ret = prepare_distro_boot_entry(menu, &iter, &i);
 		if (ret < 0 && ret != -ENOENT)
 			goto cleanup;
 	}
 
+	if (IS_ENABLED(CONFIG_CMD_BOOTEFI_BOOTMGR)) {
+		/* Add UEFI Boot Manager Maintenance entry */
+		if (i <= MAX_DYNAMIC_ENTRY) {
+			entry = malloc(sizeof(struct bootmenu_entry));
+			if (!entry)
+				goto cleanup;
+
+			entry->title = u16_strdup(u"UEFI Boot Manager Maintenance");
+			if (!entry->title) {
+				free(entry);
+				goto cleanup;
+			}
+
+			entry->command = strdup("");
+			if (!entry->command) {
+				free(entry->title);
+				free(entry);
+				goto cleanup;
+			}
+
+			sprintf(entry->key, "%d", i);
+
+			entry->num = i;
+			entry->menu = menu;
+			entry->type = BOOTMENU_TYPE_UEFI_MAINTENANCE;
+			entry->next = NULL;
+
+			if (!iter)
+				menu->first = entry;
+			else
+				iter->next = entry;
+
+			iter = entry;
+			i++;
+		}
+	}
+
 	/* Add U-Boot console entry at the end */
 	if (i <= MAX_COUNT - 1) {
 		entry = malloc(sizeof(struct bootmenu_entry));
@@ -585,6 +625,13 @@ static enum bootmenu_ret bootmenu_show(int delay)
 
 	if (menu_get_choice(menu, &choice) == 1) {
 		iter = choice;
+		if (iter->type == BOOTMENU_TYPE_UEFI_MAINTENANCE) {
+			efi_bootmenu_show_maintenance_menu();
+			menu_destroy(menu);
+			bootmenu_destroy(bootmenu);
+			return BOOTMENU_RET_UPDATED;
+		}
+
 		/* last entry is U-Boot console or Quit */
 		if (iter->num == iter->menu->count - 1) {
 			menu_destroy(menu);
@@ -674,6 +721,7 @@ int do_bootmenu(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[])
 {
 	char *delay_str = NULL;
 	int delay = 10;
+	int ret;
 
 #if defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >= 0)
 	delay = CONFIG_BOOTDELAY;
@@ -688,7 +736,13 @@ int do_bootmenu(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[])
 	if (delay_str)
 		delay = (int)simple_strtol(delay_str, NULL, 10);
 
-	bootmenu_show(delay);
+	while (1) {
+		ret =  bootmenu_show(delay);
+		delay = -1;
+		if (ret != BOOTMENU_RET_UPDATED)
+			break;
+	}
+
 	return 0;
 }
 
diff --git a/include/efi_loader.h b/include/efi_loader.h
index ce22660d5b..7b100ca030 100644
--- a/include/efi_loader.h
+++ b/include/efi_loader.h
@@ -226,6 +226,9 @@ const char *__efi_nesting_dec(void);
 #define EFI_CACHELINE_SIZE 128
 #endif
 
+/* max bootmenu title size for volume selection */
+#define BOOTMENU_DEVICE_NAME_MAX 16
+
 /* Key identifying current memory map */
 extern efi_uintn_t efi_memory_map_key;
 
@@ -312,6 +315,9 @@ extern const efi_guid_t efi_guid_firmware_management_protocol;
 extern const efi_guid_t efi_esrt_guid;
 /* GUID of the SMBIOS table */
 extern const efi_guid_t smbios_guid;
+/*GUID of console */
+extern const efi_guid_t efi_guid_text_input_protocol;
+extern const efi_guid_t efi_guid_text_output_protocol;
 
 extern char __efi_runtime_start[], __efi_runtime_stop[];
 extern char __efi_runtime_rel_start[], __efi_runtime_rel_stop[];
@@ -869,6 +875,8 @@ efi_status_t efi_set_load_options(efi_handle_t handle,
 				  void *load_options);
 efi_status_t efi_bootmgr_load(efi_handle_t *handle, void **load_options);
 
+efi_status_t efi_bootmenu_show_maintenance_menu(void);
+
 /**
  * struct efi_image_regions - A list of memory regions
  *
@@ -1004,4 +1012,33 @@ efi_status_t efi_esrt_populate(void);
 efi_status_t efi_load_capsule_drivers(void);
 
 efi_status_t platform_get_eventlog(struct udevice *dev, u64 *addr, u32 *sz);
+
+efi_status_t efi_locate_handle_buffer_int(enum efi_locate_search_type search_type,
+					  const efi_guid_t *protocol, void *search_key,
+					  efi_uintn_t *no_handles, efi_handle_t **buffer);
+
+efi_status_t efi_open_volume_int(struct efi_simple_file_system_protocol *this,
+				 struct efi_file_handle **root);
+efi_status_t efi_file_open_int(struct efi_file_handle *this,
+			       struct efi_file_handle **new_handle,
+			       u16 *file_name, u64 open_mode,
+			       u64 attributes);
+efi_status_t efi_file_close_int(struct efi_file_handle *file);
+efi_status_t efi_file_read_int(struct efi_file_handle *this,
+			       efi_uintn_t *buffer_size, void *buffer);
+efi_status_t efi_file_setpos_int(struct efi_file_handle *file, u64 pos);
+
+typedef efi_status_t (*efi_console_filter_func)(struct efi_input_key *key);
+efi_status_t efi_console_get_u16_string
+		(struct efi_simple_text_input_protocol *cin,
+		 struct efi_simple_text_output_protocol *cout,
+		 u16 *buf, efi_uintn_t count, efi_console_filter_func filer_func,
+		 int row, int col);
+
+efi_status_t efi_bootmenu_get_unused_bootoption(u16 *buf,
+						efi_uintn_t buf_size, u32 *index);
+efi_status_t efi_bootmenu_append_bootorder(u16 index);
+
+efi_status_t efi_disk_get_device_name(struct efi_block_io *this, char *buf, int size);
+
 #endif /* _EFI_LOADER_H */
diff --git a/lib/efi_loader/Makefile b/lib/efi_loader/Makefile
index befed7144e..fd900a23c7 100644
--- a/lib/efi_loader/Makefile
+++ b/lib/efi_loader/Makefile
@@ -77,6 +77,7 @@ obj-$(CONFIG_EFI_TCG2_PROTOCOL) += efi_tcg2.o
 obj-$(CONFIG_EFI_RISCV_BOOT_PROTOCOL) += efi_riscv.o
 obj-$(CONFIG_EFI_LOAD_FILE2_INITRD) += efi_load_initrd.o
 obj-$(CONFIG_EFI_SIGNATURE_SUPPORT) += efi_signature.o
+obj-$(CONFIG_CMD_BOOTMENU) += efi_bootmenu_maintenance.o
 
 EFI_VAR_SEED_FILE := $(subst $\",,$(CONFIG_EFI_VAR_SEED_FILE))
 $(obj)/efi_var_seed.o: $(srctree)/$(EFI_VAR_SEED_FILE)
diff --git a/lib/efi_loader/efi_bootmenu_maintenance.c b/lib/efi_loader/efi_bootmenu_maintenance.c
new file mode 100644
index 0000000000..2ce5a47b10
--- /dev/null
+++ b/lib/efi_loader/efi_bootmenu_maintenance.c
@@ -0,0 +1,1116 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ *  Menu-driven UEFI Boot Variable maintenance
+ *
+ *  Copyright (c) 2022 Masahisa Kojima, Linaro Limited
+ */
+
+#define LOG_CATEGORY LOGC_EFI
+
+#include <ansi.h>
+#include <common.h>
+#include <charset.h>
+#include <log.h>
+#include <malloc.h>
+#include <menu.h>
+#include <efi_loader.h>
+#include <efi_variable.h>
+#include <asm/unaligned.h>
+
+static struct efi_simple_text_input_protocol *cin;
+static struct efi_simple_text_output_protocol *cout;
+
+#define EFI_BOOTMENU_ENTRY_NUM_MAX 99
+#define EFI_BOOTMENU_FILE_PATH_MAX 512
+#define EFI_BOOTMENU_BOOT_NAME_MAX 32
+#define EFI_BOOT_ORDER_MAX_SIZE_IN_DECIMAL 6
+
+typedef efi_status_t (*efi_bootmenu_entry_func)(void *data, bool *exit);
+
+/**
+ * struct efi_bootmenu_entry - menu entry structure
+ *
+ * @num:		menu entry index
+ * @title:		title of entry
+ * @key:		unique key
+ * @bootmgr_menu:	pointer to the menu structure
+ * @next:		pointer to the next entry
+ * @func:		callback function to be called when this entry is selected
+ * @data:		data to be passed to the callback function
+ */
+struct efi_bootmenu_entry {
+	u32 num;
+	u16 *title;
+	char key[6];
+	struct efi_bootmenu *bootmgr_menu;
+	struct efi_bootmenu_entry *next;
+	efi_bootmenu_entry_func func;
+	void *data;
+};
+
+/**
+ * struct efi_bootmenu - bootmgr menu structure
+ *
+ * @delay:	delay for autoboot
+ * @active:	active menu entry index
+ * @count:	total count of menu entry
+ * @first:	pointer to the first menu entry
+ */
+struct efi_bootmenu {
+	int delay;
+	int active;
+	int count;
+	struct efi_bootmenu_entry *first;
+};
+
+struct efi_bootmenu_item {
+	u16 *title;
+	efi_bootmenu_entry_func func;
+	void *data;
+};
+
+struct efi_bootmenu_boot_selection_data {
+	u16 bootorder_index;
+	void *load_option;
+	int *selected;
+};
+
+struct efi_bootmenu_boot_option {
+	struct efi_simple_file_system_protocol *current_volume;
+	struct efi_device_path *dp_volume;
+	u16 *current_path;
+	u16 *boot_name;
+	bool file_selected;
+};
+
+static const struct efi_device_path END = {
+	.type     = DEVICE_PATH_TYPE_END,
+	.sub_type = DEVICE_PATH_SUB_TYPE_END,
+	.length   = sizeof(END),
+};
+
+struct efi_bootmenu_volume_entry_data {
+	struct efi_bootmenu_boot_option *bo;
+	struct efi_simple_file_system_protocol *v;
+	struct efi_device_path *dp;
+};
+
+struct efi_bootmenu_file_entry_data {
+	struct efi_bootmenu_boot_option *bo;
+	struct efi_file_info *f;
+};
+
+static efi_status_t efi_bootmenu_process_boot_selected(void *data, bool *exit);
+static efi_status_t efi_bootmenu_process_add_boot_option(void *data, bool *exit);
+static efi_status_t efi_bootmenu_process_delete_boot_option(void *data, bool *exit);
+static efi_status_t efi_bootmenu_process_change_boot_order(void *data, bool *exit);
+
+static struct efi_bootmenu_item maintenance_menu_items[] = {
+	{u"Add Boot Option", efi_bootmenu_process_add_boot_option},
+	{u"Delete Boot Option", efi_bootmenu_process_delete_boot_option},
+	{u"Change Boot Order", efi_bootmenu_process_change_boot_order},
+	{u"Quit", NULL},
+};
+
+static void efi_bootmenu_print_entry(void *data)
+{
+	struct efi_bootmenu_entry *entry = data;
+	int reverse = (entry->bootmgr_menu->active == entry->num);
+
+	/* TODO: support scroll or page for many entries */
+
+	/*
+	 * Move cursor to line where the entry will be drown (entry->count)
+	 * First 3 lines contain bootmgr menu header + one empty line
+	 * For the last "Quit" entry, add one empty line
+	 */
+	if (entry->num == (entry->bootmgr_menu->count - 1))
+		printf(ANSI_CURSOR_POSITION, entry->num + 5, 1);
+	else
+		printf(ANSI_CURSOR_POSITION, entry->num + 4, 1);
+
+	puts("     ");
+
+	if (reverse)
+		puts(ANSI_COLOR_REVERSE);
+
+	printf("%ls", entry->title);
+
+	if (reverse)
+		puts(ANSI_COLOR_RESET);
+}
+
+static void efi_bootmenu_display_statusline(struct menu *m)
+{
+	struct efi_bootmenu_entry *entry;
+	struct efi_bootmenu *bootmgr_menu;
+
+	if (menu_default_choice(m, (void *)&entry) < 0)
+		return;
+
+	bootmgr_menu = entry->bootmgr_menu;
+
+	printf(ANSI_CURSOR_POSITION, 1, 1);
+	puts(ANSI_CLEAR_LINE);
+	printf(ANSI_CURSOR_POSITION, 2, 1);
+	puts("  *** U-Boot EFI Boot Manager ***");
+	puts(ANSI_CLEAR_LINE_TO_END);
+	printf(ANSI_CURSOR_POSITION, 3, 1);
+	puts(ANSI_CLEAR_LINE);
+
+	/* First 3 lines are bootmgr_menu header + 2 empty lines between entries */
+	printf(ANSI_CURSOR_POSITION, bootmgr_menu->count + 5, 1);
+	puts(ANSI_CLEAR_LINE);
+	printf(ANSI_CURSOR_POSITION, bootmgr_menu->count + 6, 1);
+	puts("  Press UP/DOWN to move, ENTER to select, ESC/CTRL+C to quit");
+	puts(ANSI_CLEAR_LINE_TO_END);
+	printf(ANSI_CURSOR_POSITION, bootmgr_menu->count + 7, 1);
+	puts(ANSI_CLEAR_LINE);
+}
+
+static char *efi_bootmenu_choice_entry(void *data)
+{
+	int i;
+	int esc = 0;
+	struct efi_bootmenu_entry *iter;
+	enum bootmenu_key key = KEY_NONE;
+	struct efi_bootmenu *bootmgr_menu = data;
+
+	while (1) {
+		if (bootmgr_menu->delay >= 0) {
+			/* Autoboot was not stopped */
+			bootmenu_autoboot_loop((struct bootmenu_data *)bootmgr_menu, &key, &esc);
+		} else {
+			/* Some key was pressed, so autoboot was stopped */
+			bootmenu_loop((struct bootmenu_data *)bootmgr_menu, &key, &esc);
+		}
+
+		if (bootmgr_menu->delay == 0)
+			key = KEY_QUIT;
+
+		switch (key) {
+		case KEY_UP:
+			if (bootmgr_menu->active > 0)
+				--bootmgr_menu->active;
+			/* no menu key selected, regenerate menu */
+			return NULL;
+		case KEY_DOWN:
+			if (bootmgr_menu->active < bootmgr_menu->count - 1)
+				++bootmgr_menu->active;
+			/* no menu key selected, regenerate menu */
+			return NULL;
+		case KEY_SELECT:
+			iter = bootmgr_menu->first;
+			for (i = 0; i < bootmgr_menu->active; ++i)
+				iter = iter->next;
+			return iter->key;
+		case KEY_QUIT:
+			/* Quit by choosing the last entry */
+			iter = bootmgr_menu->first;
+			while (iter->next)
+				iter = iter->next;
+			return iter->key;
+		default:
+			break;
+		}
+	}
+
+	/* never happens */
+	debug("bootmgr menu: this should not happen");
+	return NULL;
+}
+
+static void efi_bootmenu_destroy(struct efi_bootmenu *bootmgr_menu)
+{
+	struct efi_bootmenu_entry *next;
+	struct efi_bootmenu_entry *iter = bootmgr_menu->first;
+
+	while (iter) {
+		next = iter->next;
+		free(iter);
+		iter = next;
+	}
+	free(bootmgr_menu);
+}
+
+/**
+ * efi_bootmenu_process_common() - main handler for uefi bootmgr menu
+ *
+ * Construct the structures required to show the menu, then handle
+ * the user input intracting with u-boot menu functions.
+ *
+ * @items:	pointer to the structure of each menu entry
+ * @count:	the number of menu entry
+ * @delay:	delay for autoboot/autoselect
+ * Return:	status code
+ */
+static efi_status_t efi_bootmenu_process_common(struct efi_bootmenu_item *items,
+						int count, int delay)
+{
+	u32 i;
+	bool exit = false;
+	efi_status_t ret;
+	struct menu *menu;
+	void *choice = NULL;
+	struct efi_bootmenu_entry *entry;
+	struct efi_bootmenu *bootmgr_menu;
+	struct efi_bootmenu_entry *iter = NULL;
+
+	if (count > EFI_BOOTMENU_ENTRY_NUM_MAX)
+		return EFI_OUT_OF_RESOURCES;
+
+	bootmgr_menu = calloc(1, sizeof(struct efi_bootmenu));
+	if (!bootmgr_menu)
+		return EFI_OUT_OF_RESOURCES;
+
+	bootmgr_menu->delay = delay;
+	bootmgr_menu->active = 0;
+	bootmgr_menu->first = NULL;
+
+	for (i = 0; i < count; i++) {
+		entry = calloc(1, sizeof(struct efi_bootmenu_entry));
+		if (!entry) {
+			ret = EFI_LOAD_ERROR;
+			goto out;
+		}
+
+		entry->num = i;
+		entry->title = items->title;
+		snprintf(entry->key, sizeof(entry->key), "%04X", i);
+		entry->bootmgr_menu = bootmgr_menu;
+		entry->func = items->func;
+		entry->data = items->data;
+		entry->next = NULL;
+
+		if (!iter)
+			bootmgr_menu->first = entry;
+		else
+			iter->next = entry;
+
+		iter = entry;
+		items++;
+	}
+	bootmgr_menu->count = count;
+
+	menu = menu_create(NULL, 0, 1, efi_bootmenu_display_statusline,
+			   efi_bootmenu_print_entry, efi_bootmenu_choice_entry,
+			   bootmgr_menu);
+	if (!menu) {
+		ret = EFI_INVALID_PARAMETER;
+		goto out;
+	}
+
+	for (entry = bootmgr_menu->first; entry; entry = entry->next) {
+		if (!menu_item_add(menu, entry->key, entry)) {
+			ret = EFI_INVALID_PARAMETER;
+			goto out;
+		}
+	}
+
+	menu_default_set(menu, bootmgr_menu->first->key);
+
+	while (!exit) {
+		puts(ANSI_CURSOR_HIDE);
+		puts(ANSI_CLEAR_CONSOLE);
+		printf(ANSI_CURSOR_POSITION, 1, 1);
+
+		if (menu_get_choice(menu, &choice)) {
+			entry = choice;
+			if (entry->func)
+				ret = entry->func(entry->data, &exit);
+
+			/* last entry "Quit" is selected, exit this menu */
+			if (entry->num == (entry->bootmgr_menu->count - 1)) {
+				ret = EFI_ABORTED;
+				break;
+			}
+		}
+	}
+
+out:
+	menu_destroy(menu);
+	efi_bootmenu_destroy(bootmgr_menu);
+
+	puts(ANSI_CLEAR_CONSOLE);
+	printf(ANSI_CURSOR_POSITION, 1, 1);
+	puts(ANSI_CURSOR_SHOW);
+
+	return ret;
+}
+
+static efi_status_t efi_bootmenu_show_boot_selection(u16 *bootorder, efi_uintn_t count,
+						     int *selected)
+{
+	u32 i;
+	efi_status_t ret;
+	efi_uintn_t size;
+	void *load_option;
+	struct efi_load_option lo;
+	u16 varname[] = u"Boot####";
+	struct efi_bootmenu_item *menu_item, *iter;
+
+	menu_item = calloc(count + 1, sizeof(struct efi_bootmenu_item));
+	if (!menu_item) {
+		ret = EFI_OUT_OF_RESOURCES;
+		goto out;
+	}
+
+	iter = menu_item;
+	for (i = 0; i < count; i++) {
+		efi_create_indexed_name(varname, sizeof(varname),
+					"Boot", bootorder[i]);
+		load_option = efi_get_var(varname, &efi_global_variable_guid, &size);
+		if (!load_option)
+			continue;
+
+		ret = efi_deserialize_load_option(&lo, load_option, &size);
+		if (ret != EFI_SUCCESS) {
+			log_warning("Invalid load option for %ls\n", varname);
+			free(load_option);
+			continue;
+		}
+
+		if (lo.attributes & LOAD_OPTION_ACTIVE) {
+			struct efi_bootmenu_boot_selection_data *info;
+
+			info = calloc(1, sizeof(struct efi_bootmenu_boot_selection_data));
+			if (!info) {
+				ret = EFI_OUT_OF_RESOURCES;
+				goto out;
+			}
+
+			info->bootorder_index = i;
+			info->load_option = load_option;
+			info->selected = selected;
+			iter->title = lo.label;
+			iter->func = efi_bootmenu_process_boot_selected;
+			iter->data = info;
+			iter++;
+		}
+	}
+
+	/* add "Quit" entry */
+	iter->title = u"Quit";
+	iter->func = NULL;
+	iter->data = NULL;
+	count += 1;
+
+	ret = efi_bootmenu_process_common(menu_item, count, -1);
+
+out:
+	iter = menu_item;
+	for (i = 0; i < count - 1; i++, iter++) {
+		free(((struct efi_bootmenu_boot_selection_data *)iter->data)->load_option);
+		free(iter->data);
+	}
+
+	free(menu_item);
+
+	return ret;
+}
+
+static efi_status_t efi_bootmenu_process_boot_selected(void *data, bool *exit)
+{
+	struct efi_bootmenu_boot_selection_data *info = data;
+
+	*exit = true;
+
+	if (info)
+		*info->selected = info->bootorder_index;
+
+	return EFI_SUCCESS;
+}
+
+static efi_status_t efi_bootmenu_volume_selected(void *data, bool *exit)
+{
+	struct efi_bootmenu_volume_entry_data *info = data;
+
+	*exit = true;
+
+	if (info) {
+		info->bo->current_volume = info->v;
+		info->bo->dp_volume = info->dp;
+	}
+
+	return EFI_SUCCESS;
+}
+
+static efi_status_t efi_bootmenu_file_selected(void *data, bool *exit)
+{
+	struct efi_bootmenu_file_entry_data *info = data;
+
+	*exit = true;
+
+	if (!info)
+		return EFI_INVALID_PARAMETER;
+
+	if (u16_strncmp(info->f->file_name, u".", 1) == 0 &&
+	    u16_strlen(info->f->file_name) == 1) {
+		/* stay current path */
+	} else if (u16_strncmp(info->f->file_name, u"..", 2) == 0 &&
+		   u16_strlen(info->f->file_name) == 2) {
+		u32 i;
+		int len = u16_strlen(info->bo->current_path);
+
+		for (i = len - 2; i > 0; i--) {
+			if (info->bo->current_path[i] == u'\\')
+				break;
+		}
+
+		if (i == 0)
+			info->bo->current_path[0] = u'\0';
+		else
+			info->bo->current_path[i + 1] = u'\0';
+	} else {
+		size_t new_len;
+
+		new_len = u16_strlen(info->bo->current_path) +
+				     u16_strlen(info->f->file_name) + 1;
+		if (new_len >= EFI_BOOTMENU_FILE_PATH_MAX) {
+			/* TODO: show error notification to user */
+			log_err("file path is too long\n");
+			return EFI_INVALID_PARAMETER;
+		}
+		u16_strlcat(info->bo->current_path, info->f->file_name, EFI_BOOTMENU_FILE_PATH_MAX);
+		if (info->f->attribute & EFI_FILE_DIRECTORY) {
+			if (new_len + 1 >= EFI_BOOTMENU_FILE_PATH_MAX) {
+				log_err("file path is too long\n");
+				return EFI_INVALID_PARAMETER;
+			}
+			u16_strlcat(info->bo->current_path, u"\\", EFI_BOOTMENU_FILE_PATH_MAX);
+		} else {
+			info->bo->file_selected = true;
+		}
+	}
+	return EFI_SUCCESS;
+}
+
+static efi_status_t efi_bootmenu_select_volume(struct efi_bootmenu_boot_option *bo)
+{
+	u32 i;
+	efi_status_t ret;
+	efi_uintn_t count;
+	struct efi_handler *handler;
+	struct efi_device_path *device_path;
+	efi_handle_t *volume_handles = NULL;
+	struct efi_simple_file_system_protocol *v;
+	struct efi_bootmenu_item *menu_item, *iter;
+
+	ret = efi_locate_handle_buffer_int(BY_PROTOCOL, &efi_simple_file_system_protocol_guid,
+					   NULL, &count, (efi_handle_t **)&volume_handles);
+	if (ret != EFI_SUCCESS)
+		return ret;
+
+	menu_item = calloc(count + 1, sizeof(struct efi_bootmenu_item));
+	if (!menu_item) {
+		ret = EFI_OUT_OF_RESOURCES;
+		goto out1;
+	}
+
+	iter = menu_item;
+	for (i = 0; i < count; i++) {
+		u16 *dev_name, *p;
+		struct efi_block_io *block_io;
+		char buf[BOOTMENU_DEVICE_NAME_MAX];
+		struct efi_bootmenu_volume_entry_data *info;
+
+		ret = efi_search_protocol(volume_handles[i],
+					  &efi_simple_file_system_protocol_guid, &handler);
+		if (ret != EFI_SUCCESS)
+			continue;
+		ret = efi_protocol_open(handler, (void **)&v, efi_root, NULL,
+					EFI_OPEN_PROTOCOL_GET_PROTOCOL);
+		if (ret != EFI_SUCCESS)
+			continue;
+
+		ret = efi_search_protocol(volume_handles[i], &efi_guid_device_path, &handler);
+		if (ret != EFI_SUCCESS)
+			continue;
+		ret = efi_protocol_open(handler, (void **)&device_path,
+					efi_root, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL);
+		if (ret != EFI_SUCCESS)
+			continue;
+
+		ret = efi_search_protocol(volume_handles[i], &efi_block_io_guid, &handler);
+		if (ret != EFI_SUCCESS)
+			continue;
+		ret = efi_protocol_open(handler, (void **)&block_io,
+					efi_root, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL);
+		if (ret != EFI_SUCCESS)
+			continue;
+
+		info = calloc(1, sizeof(struct efi_bootmenu_volume_entry_data));
+		if (!info) {
+			ret = EFI_OUT_OF_RESOURCES;
+			goto out2;
+		}
+
+		efi_disk_get_device_name(block_io, buf, BOOTMENU_DEVICE_NAME_MAX);
+		dev_name = calloc(1, (strlen(buf) + 1) * sizeof(u16));
+		if (!dev_name) {
+			free(info);
+			ret = EFI_OUT_OF_RESOURCES;
+			goto out2;
+		}
+		p = dev_name;
+		utf8_utf16_strncpy(&p, buf, strlen(buf));
+
+		info->v = v;
+		info->dp = device_path;
+		info->bo = bo;
+		iter->title = dev_name;
+		iter->func = efi_bootmenu_volume_selected;
+		iter->data = info;
+		iter++;
+	}
+
+	iter->title = u16_strdup(u"Quit");
+	iter->func = NULL;
+	iter->data = NULL;
+	count += 1;
+
+	ret = efi_bootmenu_process_common(menu_item, count, -1);
+
+out2:
+	iter = menu_item;
+	for (i = 0; i < count; i++) {
+		struct efi_bootmenu_volume_entry_data *p;
+
+		p = (struct efi_bootmenu_volume_entry_data *)(iter->data);
+		free(iter->title);
+		free(p);
+		iter++;
+	}
+
+	free(menu_item);
+
+out1:
+	efi_free_pool(volume_handles);
+
+	return ret;
+}
+
+static efi_status_t efi_bootmenu_select_file(struct efi_bootmenu_boot_option *bo,
+					     struct efi_file_handle *root)
+{
+	char *buf;
+	u32 i;
+	char *dir_buf;
+	efi_uintn_t len;
+	efi_status_t ret;
+	efi_uintn_t size;
+	u32 count = 0;
+	struct efi_file_handle *f;
+	struct efi_file_info *ptr;
+	struct efi_bootmenu_item *menu_item, *iter;
+
+	buf = calloc(1, EFI_BOOTMENU_FILE_PATH_MAX);
+	if (!buf)
+		return EFI_OUT_OF_RESOURCES;
+
+	while (!bo->file_selected) {
+		size = 0;
+		count = 0;
+
+		ret = efi_file_open_int(root, &f, bo->current_path, EFI_FILE_MODE_READ, 0);
+		if (ret != EFI_SUCCESS)
+			return ret;
+
+		/* calculate directory information total size */
+		for (;;) {
+			len = EFI_BOOTMENU_FILE_PATH_MAX;
+			ret = efi_file_read_int(f, &len, buf);
+			if (ret != EFI_SUCCESS || len == 0)
+				break;
+
+			size += len;
+			count++;
+		}
+
+		dir_buf = calloc(1, size);
+		if (!dir_buf) {
+			efi_file_close_int(f);
+			ret = EFI_OUT_OF_RESOURCES;
+			goto out;
+		}
+		menu_item = calloc(count + 1, sizeof(struct efi_bootmenu_item));
+		if (!menu_item) {
+			efi_file_close_int(f);
+			free(dir_buf);
+			ret = EFI_OUT_OF_RESOURCES;
+			goto out;
+		}
+
+		/* read directory and construct menu structure */
+		efi_file_setpos_int(f, 0);
+		iter = menu_item;
+		ptr = (struct efi_file_info *)dir_buf;
+		for (i = 0; i < count; i++) {
+			int name_len;
+			u16 *name;
+			struct efi_bootmenu_file_entry_data *info;
+
+			len = size;
+			ret = efi_file_read_int(f, &len, ptr);
+			if (ret != EFI_SUCCESS || len == 0)
+				goto err;
+
+			if (ptr->attribute & EFI_FILE_DIRECTORY) {
+				/* append u'/' at the end of directory name */
+				name_len = u16_strsize(ptr->file_name) + sizeof(u16);
+				name = calloc(1, name_len);
+				if (!name) {
+					ret = EFI_OUT_OF_RESOURCES;
+					goto err;
+				}
+				u16_strcpy(name, ptr->file_name);
+				name[u16_strlen(ptr->file_name)] = u'/';
+			} else {
+				name_len = u16_strsize(ptr->file_name);
+				name = calloc(1, name_len);
+				if (!name) {
+					ret = EFI_OUT_OF_RESOURCES;
+					goto err;
+				}
+				u16_strcpy(name, ptr->file_name);
+			}
+
+			info = calloc(1, sizeof(struct efi_bootmenu_file_entry_data));
+			if (!info) {
+				ret = EFI_OUT_OF_RESOURCES;
+				goto err;
+			}
+			info->f = ptr;
+			info->bo = bo;
+			iter->title = name;
+			iter->func = efi_bootmenu_file_selected;
+			iter->data = info;
+			iter++;
+
+			size -= len;
+			ptr = (struct efi_file_info *)((char *)ptr + len);
+		}
+
+		/* add "Quit" entry */
+		iter->title = u"Quit";
+		iter->func = NULL;
+		iter->data = NULL;
+		count += 1;
+
+		ret = efi_bootmenu_process_common(menu_item, count, -1);
+err:
+		efi_file_close_int(f);
+		iter = menu_item;
+		for (i = 0; i < count - 1; i++, iter++) {
+			free(iter->title);
+			free(iter->data);
+		}
+
+		free(dir_buf);
+		free(menu_item);
+
+		if (ret != EFI_SUCCESS)
+			break;
+	}
+
+out:
+	free(buf);
+	return ret;
+}
+
+static efi_status_t efi_bootmenu_boot_add_enter_name(struct efi_bootmenu_boot_option *bo)
+{
+	efi_status_t ret;
+
+	printf(ANSI_CURSOR_POSITION, 2, 1);
+	puts("  *** U-Boot EFI Boot Manager Menu ***");
+	printf(ANSI_CURSOR_POSITION, 4, 1);
+	puts("  enter name:");
+
+	printf(ANSI_CURSOR_POSITION, 8, 1);
+	puts("  ENTER to complete, ESC/CTRL+C to quit");
+
+	ret = efi_console_get_u16_string(cin, cout, bo->boot_name,
+					 EFI_BOOTMENU_BOOT_NAME_MAX, NULL, 4, 15);
+
+	return ret;
+}
+
+static efi_status_t allow_decimal(struct efi_input_key *key)
+{
+	if (u'0' <= key->unicode_char && key->unicode_char <= u'9')
+		return EFI_SUCCESS;
+
+	return EFI_INVALID_PARAMETER;
+}
+
+static efi_status_t efi_bootmenu_change_boot_order(int selected, int max, int *new)
+{
+	efi_status_t ret;
+	u16 new_order[EFI_BOOT_ORDER_MAX_SIZE_IN_DECIMAL] = {0};
+
+	printf(ANSI_CURSOR_POSITION, 2, 1);
+	puts("  *** U-Boot EFI Boot Manager Menu ***");
+	printf(ANSI_CURSOR_POSITION, 4, 1);
+	printf("  current boot order      : %d", selected);
+
+	printf(ANSI_CURSOR_POSITION, 6, 1);
+	printf("  new boot order(0 - %4d): ", max);
+
+	printf(ANSI_CURSOR_POSITION, 8, 1);
+	puts("  ENTER to complete, ESC/CTRL+C to quit");
+
+	printf(ANSI_CURSOR_POSITION, 6, 29);
+	puts(ANSI_CURSOR_SHOW);
+
+	for (;;) {
+		memset(new_order, 0, sizeof(new_order));
+		ret = efi_console_get_u16_string(cin, cout, new_order, 6, allow_decimal, 6, 29);
+		if (ret == EFI_SUCCESS) {
+			int i;
+			int val = 0;
+
+			for (i = 0;
+			     i < u16_strnlen(new_order, EFI_BOOT_ORDER_MAX_SIZE_IN_DECIMAL - 1);
+			     i++)
+				val = (val * 10) + (new_order[i] - u'0');
+
+			if (val > max)
+				continue;
+
+			*new = val;
+			return EFI_SUCCESS;
+		} else {
+			return ret;
+		}
+	}
+}
+
+static efi_status_t efi_bootmenu_select_file_handler(struct efi_bootmenu_boot_option *bo)
+{
+	efi_status_t ret;
+	struct efi_file_handle *root;
+
+	bo->file_selected = false;
+
+	while (!bo->file_selected) {
+		bo->current_volume = NULL;
+		memset(bo->current_path, 0, sizeof(bo->current_path));
+
+		ret = efi_bootmenu_select_volume(bo);
+		if (ret != EFI_SUCCESS)
+			return ret;
+
+		if (!bo->current_volume)
+			return EFI_INVALID_PARAMETER;
+
+		ret = efi_open_volume_int(bo->current_volume, &root);
+		if (ret != EFI_SUCCESS)
+			return ret;
+
+		ret = efi_bootmenu_select_file(bo, root);
+		if (ret != EFI_SUCCESS)
+			return ret;
+	}
+
+	ret = efi_bootmenu_boot_add_enter_name(bo);
+
+	return ret;
+}
+
+efi_status_t efi_bootmenu_get_unused_bootoption(u16 *buf,
+						efi_uintn_t buf_size, u32 *index)
+{
+	u32 i;
+	efi_status_t ret;
+	efi_uintn_t size;
+
+	if (buf_size < u16_strsize(u"Boot####"))
+		return EFI_BUFFER_TOO_SMALL;
+
+	for (i = 0; i <= 0xFFFF; i++) {
+		size = 0;
+		efi_create_indexed_name(buf, buf_size, "Boot", i);
+		ret = efi_get_variable_int(buf, &efi_global_variable_guid,
+					   NULL, &size, NULL, NULL);
+		if (ret == EFI_BUFFER_TOO_SMALL)
+			continue;
+		else
+			break;
+	}
+
+	if (i > 0xFFFF)
+		return EFI_OUT_OF_RESOURCES;
+
+	*index = i;
+
+	return EFI_SUCCESS;
+}
+
+static efi_status_t efi_bootmenu_set_boot_option(u16 *var_name, struct efi_device_path *dp,
+						 u16 *label, char *optional_data)
+{
+	void *p = NULL;
+	efi_status_t ret;
+	efi_uintn_t size;
+	struct efi_load_option lo;
+
+	lo.file_path = dp;
+	lo.file_path_length = efi_dp_size(dp) + sizeof(END);
+	lo.attributes = LOAD_OPTION_ACTIVE;
+	lo.optional_data = optional_data;
+	lo.label = label;
+
+	size = efi_serialize_load_option(&lo, (u8 **)&p);
+	if (!size)
+		return EFI_INVALID_PARAMETER;
+
+	ret = efi_set_variable_int(var_name, &efi_global_variable_guid,
+				   EFI_VARIABLE_NON_VOLATILE |
+				   EFI_VARIABLE_BOOTSERVICE_ACCESS |
+				   EFI_VARIABLE_RUNTIME_ACCESS,
+				   size, p, false);
+	free(p);
+
+	return ret;
+}
+
+efi_status_t efi_bootmenu_append_bootorder(u16 index)
+{
+	u16 *bootorder;
+	efi_status_t ret;
+	u16 *new_bootorder = NULL;
+	efi_uintn_t last, size, new_size;
+
+	/* append new boot option */
+	bootorder = efi_get_var(u"BootOrder", &efi_global_variable_guid, &size);
+	last = size / sizeof(u16);
+	new_size = size + sizeof(u16);
+	new_bootorder = calloc(1, new_size);
+	if (!new_bootorder) {
+		ret = EFI_OUT_OF_RESOURCES;
+		goto out;
+	}
+	memcpy(new_bootorder, bootorder, size);
+	new_bootorder[last] = index;
+
+	ret = efi_set_variable_int(u"BootOrder", &efi_global_variable_guid,
+				   EFI_VARIABLE_NON_VOLATILE |
+				   EFI_VARIABLE_BOOTSERVICE_ACCESS |
+				   EFI_VARIABLE_RUNTIME_ACCESS,
+				   new_size, new_bootorder, false);
+	if (ret != EFI_SUCCESS)
+		goto out;
+
+out:
+	free(bootorder);
+	free(new_bootorder);
+
+	return ret;
+}
+
+static efi_status_t efi_bootmenu_process_add_boot_option(void *data, bool *exit)
+{
+	u32 index;
+	u16 var_name[9];
+	char *buf = NULL;
+	efi_status_t ret;
+	char *iter = NULL;
+	efi_uintn_t dp_size, fp_size;
+	struct efi_bootmenu_boot_option bo;
+	struct efi_device_path_file_path *fp;
+
+	ret = efi_bootmenu_get_unused_bootoption(var_name, sizeof(var_name),
+						 &index);
+	if (ret != EFI_SUCCESS)
+		return ret;
+
+	bo.current_path = calloc(1, EFI_BOOTMENU_FILE_PATH_MAX);
+	if (!bo.current_path)
+		goto out;
+
+	bo.boot_name = calloc(1, EFI_BOOTMENU_BOOT_NAME_MAX * sizeof(u16));
+	if (!bo.boot_name)
+		goto out;
+
+	ret = efi_bootmenu_select_file_handler(&bo);
+	if (ret != EFI_SUCCESS)
+		goto out;
+
+	dp_size = efi_dp_size(bo.dp_volume);
+	fp_size = sizeof(struct efi_device_path) +
+		  ((u16_strlen(bo.current_path) + 1) * sizeof(u16));
+	buf = calloc(1, dp_size + fp_size + sizeof(END));
+	if (!buf)
+		goto out;
+
+	iter = buf;
+	memcpy(iter, bo.dp_volume, dp_size);
+	iter += dp_size;
+
+	fp = (struct efi_device_path_file_path *)iter;
+	fp->dp.type = DEVICE_PATH_TYPE_MEDIA_DEVICE;
+	fp->dp.sub_type = DEVICE_PATH_SUB_TYPE_FILE_PATH;
+	fp->dp.length = (u16)fp_size;
+	u16_strcpy(fp->str, bo.current_path);
+	iter += fp_size;
+	*((struct efi_device_path *)iter) = END;
+
+	ret = efi_bootmenu_set_boot_option(var_name, (struct efi_device_path *)buf,
+					   bo.boot_name, NULL);
+	if (ret != EFI_SUCCESS)
+		goto out;
+
+	efi_bootmenu_append_bootorder((u16)index);
+	if (ret != EFI_SUCCESS)
+		goto out;
+
+out:
+	free(buf);
+	free(bo.boot_name);
+	free(bo.current_path);
+
+	return ret;
+}
+
+static efi_status_t delete_boot_option(u16 *bootorder, u16 index, efi_uintn_t size)
+{
+	u16 var_name[9];
+	efi_status_t ret;
+	efi_uintn_t num;
+
+	efi_create_indexed_name(var_name, sizeof(var_name),
+				"Boot", bootorder[index]);
+	ret = efi_set_variable_int(var_name, &efi_global_variable_guid,
+				   0, 0, NULL, false);
+	if (ret != EFI_SUCCESS) {
+		log_err("delete boot option(%ls) failed\n", var_name);
+		return ret;
+	}
+
+	/* update BootOrder */
+	num = size / sizeof(u16);
+	memmove(&bootorder[index], &bootorder[index + 1],
+		(num - index - 1) * sizeof(u16));
+	size -= sizeof(u16);
+	ret = efi_set_variable_int(u"BootOrder", &efi_global_variable_guid,
+				   EFI_VARIABLE_NON_VOLATILE |
+				   EFI_VARIABLE_BOOTSERVICE_ACCESS |
+				   EFI_VARIABLE_RUNTIME_ACCESS,
+				   size, bootorder, false);
+
+	return ret;
+}
+
+static efi_status_t efi_bootmenu_process_delete_boot_option(void *data, bool *exit)
+{
+	int selected;
+	u16 *bootorder;
+	efi_status_t ret;
+	efi_uintn_t num, size;
+
+	bootorder = efi_get_var(u"BootOrder", &efi_global_variable_guid, &size);
+	if (!bootorder) {
+		ret = EFI_NOT_FOUND;
+		return ret;
+	}
+
+	num = size / sizeof(u16);
+	ret = efi_bootmenu_show_boot_selection(bootorder, num, &selected);
+	if (ret == EFI_SUCCESS)
+		ret = delete_boot_option(bootorder, selected, size);
+
+	free(bootorder);
+
+	return ret;
+}
+
+static efi_status_t efi_bootmenu_process_change_boot_order(void *data, bool *exit)
+{
+	int selected;
+	int new_order;
+	efi_status_t ret;
+	efi_uintn_t num, size;
+	u16 *bootorder = NULL;
+	u16 *new_bootorder = NULL;
+
+	bootorder = efi_get_var(u"BootOrder", &efi_global_variable_guid, &size);
+	if (!bootorder)
+		return EFI_NOT_FOUND;
+
+	num = size / sizeof(u16);
+	ret = efi_bootmenu_show_boot_selection(bootorder, num, &selected);
+	if (ret != EFI_SUCCESS)
+		goto out;
+
+	ret = efi_bootmenu_change_boot_order(selected, num - 1, &new_order);
+	if (ret != EFI_SUCCESS)
+		goto out;
+
+	new_bootorder = calloc(1, size);
+	if (!new_bootorder)
+		goto out;
+
+	memcpy(new_bootorder, bootorder, size);
+	if (selected > new_order) {
+		new_bootorder[new_order] = bootorder[selected];
+		memcpy(&new_bootorder[new_order + 1], &bootorder[new_order],
+		       (selected - new_order) * sizeof(u16));
+	} else if (selected < new_order) {
+		new_bootorder[new_order] = bootorder[selected];
+		memcpy(&new_bootorder[selected], &bootorder[selected + 1],
+		       (new_order - selected) * sizeof(u16));
+	} else {
+		/* nothing to change */
+		goto out;
+	}
+	ret = efi_set_variable_int(u"BootOrder", &efi_global_variable_guid,
+				   EFI_VARIABLE_NON_VOLATILE |
+				   EFI_VARIABLE_BOOTSERVICE_ACCESS |
+				   EFI_VARIABLE_RUNTIME_ACCESS,
+				   size, new_bootorder, false);
+out:
+	free(new_bootorder);
+	free(bootorder);
+
+	return ret;
+}
+
+static efi_status_t efi_bootmenu_init(void)
+{
+	efi_status_t ret;
+	struct efi_handler *handler;
+
+	ret = efi_search_protocol(efi_root, &efi_guid_text_input_protocol, &handler);
+	if (ret != EFI_SUCCESS)
+		return ret;
+
+	ret = efi_protocol_open(handler, (void **)&cin, efi_root, NULL,
+				EFI_OPEN_PROTOCOL_GET_PROTOCOL);
+	if (ret != EFI_SUCCESS)
+		return ret;
+
+	ret = efi_search_protocol(efi_root, &efi_guid_text_output_protocol, &handler);
+	if (ret != EFI_SUCCESS)
+		return ret;
+
+	ret = efi_protocol_open(handler, (void **)&cout, efi_root, NULL,
+				EFI_OPEN_PROTOCOL_GET_PROTOCOL);
+	if (ret != EFI_SUCCESS)
+		return ret;
+
+	return ret;
+}
+
+efi_status_t efi_bootmenu_show_maintenance_menu(void)
+{
+	efi_status_t ret;
+
+	ret = efi_bootmenu_init();
+	if (ret != EFI_SUCCESS)
+		return ret;
+
+	return efi_bootmenu_process_common(maintenance_menu_items,
+					  ARRAY_SIZE(maintenance_menu_items),
+					  -1);
+}
diff --git a/lib/efi_loader/efi_boottime.c b/lib/efi_loader/efi_boottime.c
index 4da64b5d29..1233418e77 100644
--- a/lib/efi_loader/efi_boottime.c
+++ b/lib/efi_loader/efi_boottime.c
@@ -2453,6 +2453,35 @@ static efi_status_t EFIAPI efi_protocols_per_handle(
 	return EFI_EXIT(EFI_SUCCESS);
 }
 
+efi_status_t efi_locate_handle_buffer_int(enum efi_locate_search_type search_type,
+					  const efi_guid_t *protocol, void *search_key,
+					  efi_uintn_t *no_handles, efi_handle_t **buffer)
+{
+	efi_status_t r;
+	efi_uintn_t buffer_size = 0;
+
+	if (!no_handles || !buffer) {
+		r = EFI_INVALID_PARAMETER;
+		goto out;
+	}
+	*no_handles = 0;
+	*buffer = NULL;
+	r = efi_locate_handle(search_type, protocol, search_key, &buffer_size,
+			      *buffer);
+	if (r != EFI_BUFFER_TOO_SMALL)
+		goto out;
+	r = efi_allocate_pool(EFI_BOOT_SERVICES_DATA, buffer_size,
+			      (void **)buffer);
+	if (r != EFI_SUCCESS)
+		goto out;
+	r = efi_locate_handle(search_type, protocol, search_key, &buffer_size,
+			      *buffer);
+	if (r == EFI_SUCCESS)
+		*no_handles = buffer_size / sizeof(efi_handle_t);
+out:
+	return r;
+}
+
 /**
  * efi_locate_handle_buffer() - locate handles implementing a protocol
  * @search_type: selection criterion
@@ -2474,30 +2503,13 @@ efi_status_t EFIAPI efi_locate_handle_buffer(
 			efi_uintn_t *no_handles, efi_handle_t **buffer)
 {
 	efi_status_t r;
-	efi_uintn_t buffer_size = 0;
 
 	EFI_ENTRY("%d, %pUs, %p, %p, %p", search_type, protocol, search_key,
 		  no_handles, buffer);
 
-	if (!no_handles || !buffer) {
-		r = EFI_INVALID_PARAMETER;
-		goto out;
-	}
-	*no_handles = 0;
-	*buffer = NULL;
-	r = efi_locate_handle(search_type, protocol, search_key, &buffer_size,
-			      *buffer);
-	if (r != EFI_BUFFER_TOO_SMALL)
-		goto out;
-	r = efi_allocate_pool(EFI_BOOT_SERVICES_DATA, buffer_size,
-			      (void **)buffer);
-	if (r != EFI_SUCCESS)
-		goto out;
-	r = efi_locate_handle(search_type, protocol, search_key, &buffer_size,
-			      *buffer);
-	if (r == EFI_SUCCESS)
-		*no_handles = buffer_size / sizeof(efi_handle_t);
-out:
+	r = efi_locate_handle_buffer_int(search_type, protocol, search_key,
+					 no_handles, buffer);
+
 	return EFI_EXIT(r);
 }
 
diff --git a/lib/efi_loader/efi_console.c b/lib/efi_loader/efi_console.c
index ba68a15017..f5002e1c99 100644
--- a/lib/efi_loader/efi_console.c
+++ b/lib/efi_loader/efi_console.c
@@ -5,6 +5,7 @@
  *  Copyright (c) 2016 Alexander Graf
  */
 
+#include <ansi.h>
 #include <common.h>
 #include <charset.h>
 #include <malloc.h>
@@ -1312,3 +1313,83 @@ out_of_memory:
 	printf("ERROR: Out of memory\n");
 	return r;
 }
+
+/**
+ * efi_console_get_u16_string() - get user input string
+ *
+ * @cin:		protocol interface to EFI_SIMPLE_TEXT_INPUT_PROTOCOL
+ * @cout:		protocol interface to EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL
+ * @buf:		buffer to store user input string in UTF16
+ * @size:		buffer size including NULL terminator
+ * @filter_func:	callback to filter user input
+ * @row:		row number to locate user input form
+ * @col:		column number to locate user input form
+ * Return:		status code
+ */
+efi_status_t efi_console_get_u16_string(struct efi_simple_text_input_protocol *cin,
+					struct efi_simple_text_output_protocol *cout,
+					u16 *buf, efi_uintn_t size,
+					efi_console_filter_func filter_func,
+					int row, int col)
+{
+	efi_status_t ret;
+	efi_uintn_t len = 0;
+	struct efi_input_key key;
+
+	printf(ANSI_CURSOR_POSITION, row, col);
+	puts(ANSI_CLEAR_LINE_TO_END);
+	puts(ANSI_CURSOR_SHOW);
+
+	ret = EFI_CALL(cin->reset(cin, false));
+	if (ret != EFI_SUCCESS)
+		return ret;
+
+	for (;;) {
+		do {
+			ret = EFI_CALL(cin->read_key_stroke(cin, &key));
+			mdelay(10);
+		} while (ret == EFI_NOT_READY);
+
+		if (key.unicode_char == u'\b') {
+			if (len > 0)
+				buf[--len] = u'\0';
+
+			printf(ANSI_CURSOR_POSITION, row, col);
+			ret = EFI_CALL(cout->output_string(cout, buf));
+			if (ret != EFI_SUCCESS)
+				return ret;
+
+			puts(ANSI_CLEAR_LINE_TO_END);
+			continue;
+		} else if (key.unicode_char == u'\r') {
+			if (len == 0) /* no user input */
+				continue;
+
+			buf[len] = u'\0';
+			return EFI_SUCCESS;
+		} else if (key.unicode_char == 0x3 || key.scan_code == 23) {
+			return EFI_ABORTED;
+		} else if (key.unicode_char < 0x20) {
+			/* ignore control codes other than Ctrl+C, '\r' and '\b' */
+			continue;
+		} else if (key.scan_code != 0) {
+			/* only accept single ESC press for cancel */
+			continue;
+		}
+
+		if (filter_func) {
+			if (filter_func(&key) != EFI_SUCCESS)
+				continue;
+		}
+
+		if (len >= (size - 1))
+			continue;
+
+		buf[len] = key.unicode_char;
+		len++;
+		printf(ANSI_CURSOR_POSITION, row, col);
+		ret = EFI_CALL(cout->output_string(cout, buf));
+		if (ret != EFI_SUCCESS)
+			return ret;
+	}
+}
diff --git a/lib/efi_loader/efi_disk.c b/lib/efi_loader/efi_disk.c
index c905c12abc..4963fefd86 100644
--- a/lib/efi_loader/efi_disk.c
+++ b/lib/efi_loader/efi_disk.c
@@ -587,3 +587,14 @@ efi_status_t efi_disk_register(void)
 
 	return EFI_SUCCESS;
 }
+
+efi_status_t efi_disk_get_device_name(struct efi_block_io *this, char *buf, int size)
+{
+	struct efi_disk_obj *diskobj;
+
+	diskobj = container_of(this, struct efi_disk_obj, ops);
+
+	snprintf(buf, size, "%s%d:%d", diskobj->ifname, diskobj->dev_index, diskobj->part);
+
+	return EFI_SUCCESS;
+}
diff --git a/lib/efi_loader/efi_file.c b/lib/efi_loader/efi_file.c
index 7a7077e6d0..c96a7f7ca3 100644
--- a/lib/efi_loader/efi_file.c
+++ b/lib/efi_loader/efi_file.c
@@ -246,10 +246,10 @@ error:
 	return NULL;
 }
 
-static efi_status_t efi_file_open_int(struct efi_file_handle *this,
-				      struct efi_file_handle **new_handle,
-				      u16 *file_name, u64 open_mode,
-				      u64 attributes)
+efi_status_t efi_file_open_int(struct efi_file_handle *this,
+			       struct efi_file_handle **new_handle,
+			       u16 *file_name, u64 open_mode,
+			       u64 attributes)
 {
 	struct file_handle *fh = to_fh(this);
 	efi_status_t ret;
@@ -369,11 +369,17 @@ static efi_status_t file_close(struct file_handle *fh)
 	return EFI_SUCCESS;
 }
 
-static efi_status_t EFIAPI efi_file_close(struct efi_file_handle *file)
+efi_status_t efi_file_close_int(struct efi_file_handle *file)
 {
 	struct file_handle *fh = to_fh(file);
+
+	return file_close(fh);
+}
+
+static efi_status_t EFIAPI efi_file_close(struct efi_file_handle *file)
+{
 	EFI_ENTRY("%p", file);
-	return EFI_EXIT(file_close(fh));
+	return EFI_EXIT(efi_file_close_int(file));
 }
 
 static efi_status_t EFIAPI efi_file_delete(struct efi_file_handle *file)
@@ -562,8 +568,8 @@ static efi_status_t dir_read(struct file_handle *fh, u64 *buffer_size,
 	return EFI_SUCCESS;
 }
 
-static efi_status_t efi_file_read_int(struct efi_file_handle *this,
-				      efi_uintn_t *buffer_size, void *buffer)
+efi_status_t efi_file_read_int(struct efi_file_handle *this,
+			       efi_uintn_t *buffer_size, void *buffer)
 {
 	struct file_handle *fh = to_fh(this);
 	efi_status_t ret = EFI_SUCCESS;
@@ -773,24 +779,11 @@ out:
 	return EFI_EXIT(ret);
 }
 
-/**
- * efi_file_setpos() - set current position in file
- *
- * This function implements the SetPosition service of the EFI file protocol.
- * See the UEFI spec for details.
- *
- * @file:	file handle
- * @pos:	new file position
- * Return:	status code
- */
-static efi_status_t EFIAPI efi_file_setpos(struct efi_file_handle *file,
-					   u64 pos)
+efi_status_t efi_file_setpos_int(struct efi_file_handle *file, u64 pos)
 {
 	struct file_handle *fh = to_fh(file);
 	efi_status_t ret = EFI_SUCCESS;
 
-	EFI_ENTRY("%p, %llu", file, pos);
-
 	if (fh->isdir) {
 		if (pos != 0) {
 			ret = EFI_UNSUPPORTED;
@@ -812,6 +805,28 @@ static efi_status_t EFIAPI efi_file_setpos(struct efi_file_handle *file,
 	fh->offset = pos;
 
 error:
+	return ret;
+}
+
+/**
+ * efi_file_setpos() - set current position in file
+ *
+ * This function implements the SetPosition service of the EFI file protocol.
+ * See the UEFI spec for details.
+ *
+ * @file:	file handle
+ * @pos:	new file position
+ * Return:	status code
+ */
+static efi_status_t EFIAPI efi_file_setpos(struct efi_file_handle *file,
+					   u64 pos)
+{
+	efi_status_t ret = EFI_SUCCESS;
+
+	EFI_ENTRY("%p, %llu", file, pos);
+
+	ret = efi_file_setpos_int(file, pos);
+
 	return EFI_EXIT(ret);
 }
 
@@ -1138,17 +1153,23 @@ struct efi_file_handle *efi_file_from_path(struct efi_device_path *fp)
 	return f;
 }
 
+efi_status_t efi_open_volume_int(struct efi_simple_file_system_protocol *this,
+				 struct efi_file_handle **root)
+{
+	struct file_system *fs = to_fs(this);
+
+	*root = file_open(fs, NULL, NULL, 0, 0);
+
+	return EFI_SUCCESS;
+}
+
 static efi_status_t EFIAPI
 efi_open_volume(struct efi_simple_file_system_protocol *this,
 		struct efi_file_handle **root)
 {
-	struct file_system *fs = to_fs(this);
-
 	EFI_ENTRY("%p, %p", this, root);
 
-	*root = file_open(fs, NULL, NULL, 0, 0);
-
-	return EFI_EXIT(EFI_SUCCESS);
+	return EFI_EXIT(efi_open_volume_int(this, root));
 }
 
 struct efi_simple_file_system_protocol *
-- 
2.17.1


  parent reply	other threads:[~2022-03-24 13:57 UTC|newest]

Thread overview: 43+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2022-03-24 13:54 [PATCH v4 00/11] enable menu-driven boot device selection Masahisa Kojima
2022-03-24 13:54 ` [PATCH v4 01/11] bootmenu: fix menu API error handling Masahisa Kojima
2022-03-30  8:55   ` Ilias Apalodimas
2022-03-24 13:54 ` [PATCH v4 02/11] lib/charset: add u16_strlcat() function Masahisa Kojima
2022-04-02  7:14   ` Heinrich Schuchardt
2022-04-04 14:50     ` Masahisa Kojima
2022-04-16  7:32   ` Heinrich Schuchardt
2022-04-18  7:47     ` Masahisa Kojima
2022-04-28  7:45       ` Masahisa Kojima
2022-03-24 13:54 ` [PATCH v4 03/11] test: unit test for u16_strlcat() Masahisa Kojima
2022-04-02  7:47   ` Heinrich Schuchardt
2022-04-04 14:54     ` Masahisa Kojima
2022-03-24 13:54 ` [PATCH v4 04/11] menu: always show the menu regardless of the number of entry Masahisa Kojima
2022-04-02  7:56   ` Heinrich Schuchardt
2022-03-24 13:54 ` [PATCH v4 05/11] efi_loader: export efi_locate_device_handle() Masahisa Kojima
2022-04-01  5:43   ` Ilias Apalodimas
2022-03-24 13:54 ` [PATCH v4 06/11] efi_loader: bootmgr: add booting from removable media Masahisa Kojima
2022-03-30 19:13   ` Ilias Apalodimas
2022-03-31  0:51     ` Masahisa Kojima
2022-03-31  6:25       ` Ilias Apalodimas
2022-04-02  6:12   ` Heinrich Schuchardt
2022-04-04  6:48     ` Masahisa Kojima
2022-04-04 21:54       ` Heinrich Schuchardt
2022-03-24 13:54 ` [PATCH v4 07/11] bootmenu: add UEFI and disto_boot entries Masahisa Kojima
2022-04-01  6:08   ` Ilias Apalodimas
2022-04-02  6:33   ` Heinrich Schuchardt
2022-04-04  8:10     ` Masahisa Kojima
2022-03-24 13:54 ` [PATCH v4 08/11] bootmenu: factor out the user input handling Masahisa Kojima
2022-03-24 13:54 ` Masahisa Kojima [this message]
2022-03-31  8:31   ` [PATCH v4 09/11] efi_loader: add menu-driven UEFI Boot Variable maintenance Ilias Apalodimas
2022-04-14  9:25     ` Masahisa Kojima
2022-03-24 13:54 ` [PATCH v4 10/11] bootmenu: add removable media entries Masahisa Kojima
2022-03-31  8:48   ` Ilias Apalodimas
2022-03-31 10:18     ` Masahisa Kojima
2022-04-04 22:00     ` Heinrich Schuchardt
2022-03-24 13:54 ` [PATCH v4 11/11] doc:bootmenu: add UEFI boot variable and distro boot support Masahisa Kojima
2022-04-02  5:51   ` Heinrich Schuchardt
2022-03-25  1:20 ` [PATCH v4 00/11] enable menu-driven boot device selection Takahiro Akashi
2022-03-25  6:57   ` Masahisa Kojima
2022-04-02  5:48 ` Heinrich Schuchardt
2022-04-04  6:10   ` Masahisa Kojima
2022-04-16  6:46 ` Heinrich Schuchardt
2022-04-28  7:35   ` Masahisa Kojima

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20220324135443.1571-10-masahisa.kojima@linaro.org \
    --to=masahisa.kojima@linaro.org \
    --cc=francois.ozog@linaro.org \
    --cc=ilias.apalodimas@linaro.org \
    --cc=mark.kettenis@xs4all.nl \
    --cc=sjg@chromium.org \
    --cc=takahiro.akashi@linaro.org \
    --cc=u-boot@lists.denx.de \
    --cc=xypron.glpk@gmx.de \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.