All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v11 0/9] enable menu-driven UEFI variable maintenance
@ 2022-08-17  9:36 Masahisa Kojima
  2022-08-17  9:36 ` [PATCH v11 1/9] eficonfig: menu-driven addition of UEFI boot option Masahisa Kojima
                   ` (8 more replies)
  0 siblings, 9 replies; 25+ messages in thread
From: Masahisa Kojima @ 2022-08-17  9:36 UTC (permalink / raw)
  To: u-boot
  Cc: Heinrich Schuchardt, Ilias Apalodimas, Simon Glass,
	Takahiro Akashi, Mark Kettenis, Masahisa Kojima

This series adds the menu-driven UEFI boot variable maintenance
through the "eficonfig" new command.
This series also adds the removable media support in bootmenu.

Initrd file selection and python based unit test are added in v10.

Source code can be cloned with:
$ git clone https://git.linaro.org/people/masahisa.kojima/u-boot.git -b eficonfig_upstream_v11

[Major Changes]
- rebased to efi-2022-10-rc3 merge commit:20d4c6052fe5
- there is detailed changelog in each commit

Masahisa Kojima (9):
  eficonfig: menu-driven addition of UEFI boot option
  eficonfig: add "Edit Boot Option" menu entry
  menu: add KEY_PLUS, KEY_MINUS and KEY_SPACE handling
  eficonfig: add "Change Boot Order" menu entry
  eficonfig: add "Delete Boot Option" menu entry
  bootmenu: add removable media entries
  doc:bootmenu: add description for UEFI boot support
  doc:eficonfig: add documentation for eficonfig command
  test: unit test for eficonfig

 cmd/Kconfig                                   |    7 +
 cmd/Makefile                                  |    1 +
 cmd/bootmenu.c                                |  106 +-
 cmd/eficonfig.c                               | 2302 +++++++++++++++++
 common/menu.c                                 |    9 +
 configs/sandbox_defconfig                     |    1 +
 doc/usage/cmd/bootmenu.rst                    |   74 +
 doc/usage/cmd/eficonfig.rst                   |   63 +
 doc/usage/index.rst                           |    1 +
 include/efi_config.h                          |   91 +
 include/efi_loader.h                          |   63 +
 include/menu.h                                |    3 +
 lib/efi_loader/efi_bootmgr.c                  |    7 +
 lib/efi_loader/efi_boottime.c                 |   52 +-
 lib/efi_loader/efi_console.c                  |   70 +
 lib/efi_loader/efi_disk.c                     |   50 +
 lib/efi_loader/efi_file.c                     |   75 +-
 test/py/tests/test_eficonfig/conftest.py      |   40 +
 .../py/tests/test_eficonfig/test_eficonfig.py |  325 +++
 19 files changed, 3288 insertions(+), 52 deletions(-)
 create mode 100644 cmd/eficonfig.c
 create mode 100644 doc/usage/cmd/eficonfig.rst
 create mode 100644 include/efi_config.h
 create mode 100644 test/py/tests/test_eficonfig/conftest.py
 create mode 100644 test/py/tests/test_eficonfig/test_eficonfig.py

-- 
2.17.1


^ permalink raw reply	[flat|nested] 25+ messages in thread

* [PATCH v11 1/9] eficonfig: menu-driven addition of UEFI boot option
  2022-08-17  9:36 [PATCH v11 0/9] enable menu-driven UEFI variable maintenance Masahisa Kojima
@ 2022-08-17  9:36 ` Masahisa Kojima
  2022-08-18  6:32   ` Heinrich Schuchardt
  2022-08-18  6:43   ` Heinrich Schuchardt
  2022-08-17  9:36 ` [PATCH v11 2/9] eficonfig: add "Edit Boot Option" menu entry Masahisa Kojima
                   ` (7 subsequent siblings)
  8 siblings, 2 replies; 25+ messages in thread
From: Masahisa Kojima @ 2022-08-17  9:36 UTC (permalink / raw)
  To: u-boot
  Cc: Heinrich Schuchardt, Ilias Apalodimas, Simon Glass,
	Takahiro Akashi, Mark Kettenis, Masahisa Kojima, Michal Simek,
	Ovidiu Panait, Ashok Reddy Soma, John Keeping, Thomas Huth,
	Chris Morgan, Huang Jianan

This commit add the "eficonfig" command.
The "eficonfig" command implements the menu-driven UEFI boot option
maintenance feature. This commit implements the addition of
new boot option. User can select the block device volume having
efi_simple_file_system_protocol and select the file corresponding
to the Boot#### variable. User can also enter the description and
optional_data of the BOOT#### variable in utf8.

This commit adds "include/efi_config.h", it contains the common
definition to be used from other menus such as UEFI Secure Boot
key management.

Signed-off-by: Masahisa Kojima <masahisa.kojima@linaro.org>
---
Changes in v11:
- refactor menu entry construction, directly use eficonfig_entry structure
- remove reading directory info to calculate the number of entry
- fix invalid efi_free_pool() in ill_file_info()
- use ANSI_CURSOR_POSITION and ANSI_CLEAR_LINE instead of printf("\n")
  since current eficonfig implementation does not handle console size correctly.
  printf("\n") at the outside of console size breaks the console output.

Changes in v10:
- add initrd file selection
- do refactoring
- eficonfig_process_common() use list structure
- remove u'/' before copying file_path into current_path
- fix typos
- check snprintf error

Changes in v9:
- move "efi_guid_bootmenu_auto_generated definition" into efi_bootmgr.c
  to address build error when CMD_EFICONFIG is disabled
- fix typos and comment
- remove file system information from error message
- remove unreachable code in eficonfig_choice_entry()
- single printf() call as much as possible
- call only getchar() in  eficonfig_print_msg()
- filter out '.' entry from file selection
- update the efi_disk_get_device_name() implementation
- add function comment

Changes in v8:
- command name is change from "efimenu" to "eficonfig"
- function and struct prefixes is changed to "eficonfig"
- fix menu header string

Changes in v7:
- add "efimenu" command and uefi variable maintenance code
  moved into cmd/efimenu.c
- create include/efimenu.h to define the common definition for
  the other menu such as UEFI Secure Boot key management
- update boot option edit UI, user can select description, file,
  and optional_data to edit in the same menu like following.

  ** Edit Boot Option **

     Description: debian
     File: virtio 0:1/EFI\debian\grubaa64.efi
     Optional Data: test
     Save
     Quit

- remove exit parameter from efimenu_process_common()
- menu title type is changed from u16 to char
- efimenu_process_common() add menu title string
- reduce printf/puts function call for displaying the menu
- efi_console_get_u16_string() accept 0 length to allow
  optional_data is empty
- efi_console_get_u16_string() the "size" parameter name is changes to "count"
- efimenu is now designed to maintain the UEFI variables, remove autoboot related code
- remove one empty line before "Quit" entry
- efimenu_init() processes only the first time

Changes in v6:
- fix typos
- modify volume name to match U-Boot syntax
- compile in CONFIG_EFI_LOADER=n and CONFIG_CMD_BOOTEFI_BOOTMGR=n
- simplify u16_strncmp() usage
- support "a\b.efi" file path, use link list to handle filepath
- modify length check condition
- UEFI related menu items only appears with CONFIG_AUTOBOOT_MENU_SHOW=y

Changes in v5:
- remove forward declarations
- add const qualifier for menu items
- fix the possible unaligned access for directory info access
- split into three commit 1)add boot option 2) delete boot option 3)change boot order
  This commit is 1)add boot option.
- fix file name buffer allocation size, it should be EFI_BOOTMENU_FILE_PATH_MAX * sizeof(u16)
- fix wrong size checking for file selection

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 RFC v3:
 not included in v3 series

Changes in RFC 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/Kconfig                   |    7 +
 cmd/Makefile                  |    1 +
 cmd/eficonfig.c               | 1491 +++++++++++++++++++++++++++++++++
 include/efi_config.h          |   90 ++
 include/efi_loader.h          |   43 +
 lib/efi_loader/efi_bootmgr.c  |    3 +
 lib/efi_loader/efi_boottime.c |   52 +-
 lib/efi_loader/efi_console.c  |   70 ++
 lib/efi_loader/efi_disk.c     |   50 ++
 lib/efi_loader/efi_file.c     |   75 +-
 10 files changed, 1835 insertions(+), 47 deletions(-)
 create mode 100644 cmd/eficonfig.c
 create mode 100644 include/efi_config.h

diff --git a/cmd/Kconfig b/cmd/Kconfig
index 211ebe9c87..a1e8613c56 100644
--- a/cmd/Kconfig
+++ b/cmd/Kconfig
@@ -1928,6 +1928,13 @@ config CMD_EFIDEBUG
 	  particularly for managing boot parameters as  well as examining
 	  various EFI status for debugging.
 
+config CMD_EFICONFIG
+	bool "eficonfig - provide menu-driven uefi variables maintenance interface"
+	depends on CMD_BOOTEFI_BOOTMGR
+	help
+	  Enable the 'eficonfig' command which provides the menu-driven UEFI
+	  variable maintenance interface.
+
 config CMD_EXCEPTION
 	bool "exception - raise exception"
 	depends on ARM || RISCV || SANDBOX || X86
diff --git a/cmd/Makefile b/cmd/Makefile
index 6e87522b62..18f5cb890d 100644
--- a/cmd/Makefile
+++ b/cmd/Makefile
@@ -63,6 +63,7 @@ obj-$(CONFIG_ENV_IS_IN_EEPROM) += eeprom.o
 obj-$(CONFIG_CMD_EEPROM) += eeprom.o
 obj-$(CONFIG_EFI) += efi.o
 obj-$(CONFIG_CMD_EFIDEBUG) += efidebug.o
+obj-$(CONFIG_CMD_EFICONFIG) += eficonfig.o
 obj-$(CONFIG_CMD_ELF) += elf.o
 obj-$(CONFIG_CMD_EROFS) += erofs.o
 obj-$(CONFIG_HUSH_PARSER) += exit.o
diff --git a/cmd/eficonfig.c b/cmd/eficonfig.c
new file mode 100644
index 0000000000..39fbd3f0ad
--- /dev/null
+++ b/cmd/eficonfig.c
@@ -0,0 +1,1491 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ *  Menu-driven UEFI Variable maintenance
+ *
+ *  Copyright (c) 2022 Masahisa Kojima, Linaro Limited
+ */
+
+#include <ansi.h>
+#include <common.h>
+#include <charset.h>
+#include <efi_loader.h>
+#include <efi_load_initrd.h>
+#include <efi_config.h>
+#include <efi_variable.h>
+#include <log.h>
+#include <malloc.h>
+#include <menu.h>
+#include <watchdog.h>
+#include <asm/unaligned.h>
+#include <linux/delay.h>
+
+static struct efi_simple_text_input_protocol *cin;
+
+#define EFICONFIG_DESCRIPTION_MAX 32
+#define EFICONFIG_OPTIONAL_DATA_MAX 64
+
+/**
+ * struct eficonfig_filepath_info - structure to be used to store file path
+ *
+ * @name:	file or directory name
+ * @list:	list structure
+ */
+struct eficonfig_filepath_info {
+	u16 *name;
+	struct list_head list;
+};
+
+/**
+ * struct eficonfig_boot_option - structure to be used for updating UEFI boot option
+ *
+ * @file_info:		user selected file info
+ * @initrd_info:	user selected initrd file info
+ * @boot_index:		index of the UEFI BootOrder variable
+ * @description:	pointer to the description string
+ * @optional_data:	pointer to the optional_data
+ * @edit_completed:	flag indicates edit complete
+ */
+struct eficonfig_boot_option {
+	struct eficonfig_select_file_info file_info;
+	struct eficonfig_select_file_info initrd_info;
+	unsigned int boot_index;
+	u16 *description;
+	u16 *optional_data;
+	bool edit_completed;
+};
+
+/**
+ * struct eficonfig_volume_entry_data - structure to be used to store volume info
+ *
+ * @file_info:	pointer to file info structure
+ * @v:		pointer to the protocol interface
+ * @dp:		pointer to the device path
+ */
+struct eficonfig_volume_entry_data {
+	struct eficonfig_select_file_info *file_info;
+	struct efi_simple_file_system_protocol *v;
+	struct efi_device_path *dp;
+};
+
+/**
+ * struct eficonfig_file_entry_data - structure to be used to store file info
+ *
+ * @file_info:		pointer to file info structure
+ * @is_directory:	flag to indentify the directory or file
+ * @file_name:		name of directory or file
+ */
+struct eficonfig_file_entry_data {
+	struct eficonfig_select_file_info *file_info;
+	bool is_directory;
+	u16 *file_name;
+};
+
+/**
+ * eficonfig_print_msg() - print message
+ *
+ * display the message to the user, user proceeds the screen
+ * with any key press.
+ *
+ * @items:		pointer to the structure of each menu entry
+ * @count:		the number of menu entry
+ * @menu_header:	pointer to the menu header string
+ * Return:	status code
+ */
+void eficonfig_print_msg(char *msg)
+{
+	/* Flush input */
+	while (tstc())
+		getchar();
+
+	printf(ANSI_CURSOR_HIDE
+	       ANSI_CLEAR_CONSOLE
+	       ANSI_CURSOR_POSITION
+	       "%s\n\n  Press any key to continue", 3, 4, msg);
+
+	getchar();
+}
+
+/**
+ * eficonfig_print_entry() - print each menu entry
+ *
+ * @data:	pointer to the data associated with each menu entry
+ */
+static void eficonfig_print_entry(void *data)
+{
+	struct eficonfig_entry *entry = data;
+	int reverse = (entry->efi_menu->active == entry->num);
+
+	/* TODO: support scroll or page for many entries */
+
+	/*
+	 * Move cursor to line where the entry will be drawn (entry->num)
+	 * First 3 lines(menu header) + 1 empty line
+	 */
+	printf(ANSI_CURSOR_POSITION, entry->num + 4, 7);
+
+	if (reverse)
+		puts(ANSI_COLOR_REVERSE);
+
+	printf("%s", entry->title);
+
+	if (reverse)
+		puts(ANSI_COLOR_RESET);
+}
+
+/**
+ * eficonfig_display_statusline() - print status line
+ *
+ * @m:	pointer to the menu structure
+ */
+static void eficonfig_display_statusline(struct menu *m)
+{
+	struct eficonfig_entry *entry;
+
+	if (menu_default_choice(m, (void *)&entry) < 0)
+		return;
+
+	printf(ANSI_CURSOR_POSITION
+	      "\n%s\n"
+	       ANSI_CURSOR_POSITION ANSI_CLEAR_LINE ANSI_CURSOR_POSITION
+	       "  Press UP/DOWN to move, ENTER to select, ESC/CTRL+C to quit"
+	       ANSI_CLEAR_LINE_TO_END ANSI_CURSOR_POSITION ANSI_CLEAR_LINE,
+	       1, 1, entry->efi_menu->menu_header, entry->efi_menu->count + 5, 1,
+	       entry->efi_menu->count + 6, 1, entry->efi_menu->count + 7, 1);
+}
+
+/**
+ * eficonfig_choice_entry() - user key input handler
+ *
+ * @data:	pointer to the efimenu structure
+ * Return:	key string to identify the selected entry
+ */
+static char *eficonfig_choice_entry(void *data)
+{
+	int esc = 0;
+	struct list_head *pos, *n;
+	struct eficonfig_entry *entry;
+	enum bootmenu_key key = KEY_NONE;
+	struct efimenu *efi_menu = data;
+
+	while (1) {
+		bootmenu_loop((struct bootmenu_data *)efi_menu, &key, &esc);
+
+		switch (key) {
+		case KEY_UP:
+			if (efi_menu->active > 0)
+				--efi_menu->active;
+			/* no menu key selected, regenerate menu */
+			return NULL;
+		case KEY_DOWN:
+			if (efi_menu->active < efi_menu->count - 1)
+				++efi_menu->active;
+			/* no menu key selected, regenerate menu */
+			return NULL;
+		case KEY_SELECT:
+			list_for_each_safe(pos, n, &efi_menu->list) {
+				entry = list_entry(pos, struct eficonfig_entry, list);
+				if (entry->num == efi_menu->active)
+					return entry->key;
+			}
+		case KEY_QUIT:
+			/* Quit by choosing the last entry */
+			entry = list_last_entry(&efi_menu->list, struct eficonfig_entry, list);
+			return entry->key;
+		default:
+			break;
+		}
+	}
+}
+
+/**
+ * eficonfig_destroy() - destroy efimenu
+ *
+ * @efi_menu:	pointer to the efimenu structure
+ * @flag:	flag to free the allocated data
+ */
+static void eficonfig_destroy(struct efimenu *efi_menu, bool flag)
+{
+	struct list_head *pos, *n;
+	struct eficonfig_entry *entry;
+
+	list_for_each_safe(pos, n, &efi_menu->list) {
+		entry = list_entry(pos, struct eficonfig_entry, list);
+		free(entry->title);
+		if (flag)
+			free(entry->data);
+		list_del(&entry->list);
+		free(entry);
+	}
+	free(efi_menu->menu_header);
+	free(efi_menu);
+}
+
+/**
+ * eficonfig_process_quit() - callback function for "Quit" entry
+ *
+ * @data:	pointer to the data
+ * Return:	status code
+ */
+efi_status_t eficonfig_process_quit(void *data)
+{
+	return EFI_ABORTED;
+}
+
+/**
+ * append_entry() - append menu item
+ *
+ * @efi_menu:	pointer to the efimenu structure
+ * @title:	pointer to the entry title
+ * @func:	callback of each entry
+ * @data:	pointer to the data to be passed to each entry callback
+ * Return:	status code
+ */
+static efi_status_t append_entry(struct efimenu *efi_menu,
+				 char *title, eficonfig_entry_func func, void *data)
+{
+	struct eficonfig_entry *entry;
+
+	if (efi_menu->count >= EFICONFIG_ENTRY_NUM_MAX)
+		return EFI_OUT_OF_RESOURCES;
+
+	entry = calloc(1, sizeof(struct eficonfig_entry));
+	if (!entry)
+		return EFI_OUT_OF_RESOURCES;
+
+	entry->title = title;
+	sprintf(entry->key, "%d", efi_menu->count);
+	entry->efi_menu = efi_menu;
+	entry->func = func;
+	entry->data = data;
+	entry->num = efi_menu->count++;
+	list_add_tail(&entry->list, &efi_menu->list);
+
+	return EFI_SUCCESS;
+}
+
+/**
+ * append_quit_entry() - append quit entry
+ *
+ * @efi_menu:	pointer to the efimenu structure
+ * Return:	status code
+ */
+static efi_status_t append_quit_entry(struct efimenu *efi_menu)
+{
+	char *title;
+	efi_status_t ret;
+
+	title = strdup("Quit");
+	if (!title)
+		return EFI_OUT_OF_RESOURCES;
+
+	ret = append_entry(efi_menu, title, eficonfig_process_quit, NULL);
+	if (ret != EFI_SUCCESS)
+		free(title);
+
+	return ret;
+}
+
+/**
+ * eficonfig_create_fixed_menu() - create fixed entry menu structure
+ *
+ * @items:	pointer to the menu entry item
+ * @count:	the number of menu entry
+ * Return:	pointer to the efimenu structure
+ */
+void *eficonfig_create_fixed_menu(const struct eficonfig_item *items, int count)
+{
+	u32 i;
+	char *title;
+	efi_status_t ret;
+	struct efimenu *efi_menu;
+	const struct eficonfig_item *iter = items;
+
+	efi_menu = calloc(1, sizeof(struct efimenu));
+	if (!efi_menu)
+		return NULL;
+
+	INIT_LIST_HEAD(&efi_menu->list);
+	for (i = 0; i < count; i++, iter++) {
+		title = strdup(iter->title);
+		if (!title)
+			goto out;
+
+		ret = append_entry(efi_menu, title, iter->func, iter->data);
+		if (ret != EFI_SUCCESS) {
+			free(title);
+			goto out;
+		}
+	}
+
+	return efi_menu;
+out:
+	eficonfig_destroy(efi_menu, false);
+
+	return NULL;
+}
+
+/**
+ * eficonfig_process_common() - main handler for UEFI menu
+ *
+ * Construct the structures required to show the menu, then handle
+ * the user input interacting with u-boot menu functions.
+ *
+ * @efi_menu:		pointer to the efimenu structure
+ * @menu_header:	pointer to the menu header string
+ * Return:		status code
+ */
+efi_status_t eficonfig_process_common(struct efimenu *efi_menu, char *menu_header)
+{
+	efi_status_t ret;
+	struct menu *menu;
+	void *choice = NULL;
+	struct list_head *pos, *n;
+	struct eficonfig_entry *entry;
+
+	if (efi_menu->count > EFICONFIG_ENTRY_NUM_MAX)
+		return EFI_OUT_OF_RESOURCES;
+
+	efi_menu->delay = -1;
+	efi_menu->active = 0;
+
+	if (menu_header) {
+		efi_menu->menu_header = strdup(menu_header);
+		if (!efi_menu->menu_header) {
+			ret = EFI_OUT_OF_RESOURCES;
+			goto out;
+		}
+	}
+
+	menu = menu_create(NULL, 0, 1, eficonfig_display_statusline,
+			   eficonfig_print_entry, eficonfig_choice_entry,
+			   efi_menu);
+	if (!menu) {
+		ret = EFI_INVALID_PARAMETER;
+		goto out;
+	}
+
+	list_for_each_safe(pos, n, &efi_menu->list) {
+		entry = list_entry(pos, struct eficonfig_entry, list);
+		if (!menu_item_add(menu, entry->key, entry)) {
+			ret = EFI_INVALID_PARAMETER;
+			goto out;
+		}
+	}
+
+	entry = list_first_entry_or_null(&efi_menu->list, struct eficonfig_entry, list);
+	if (entry)
+		menu_default_set(menu, entry->key);
+
+	printf(ANSI_CURSOR_HIDE
+	       ANSI_CLEAR_CONSOLE
+	       ANSI_CURSOR_POSITION, 1, 1);
+
+	if (menu_get_choice(menu, &choice)) {
+		entry = choice;
+		if (entry->func)
+			ret = entry->func(entry->data);
+	}
+out:
+	menu_destroy(menu);
+
+	printf(ANSI_CLEAR_CONSOLE
+	       ANSI_CURSOR_POSITION
+	       ANSI_CURSOR_SHOW, 1, 1);
+
+	return ret;
+}
+
+/**
+ * eficonfig_volume_selected() - handler of volume selection
+ *
+ * @data:	pointer to the data of selected entry
+ * Return:	status code
+ */
+static efi_status_t eficonfig_volume_selected(void *data)
+{
+	struct eficonfig_volume_entry_data *info = data;
+
+	if (info) {
+		info->file_info->current_volume = info->v;
+		info->file_info->dp_volume = info->dp;
+	}
+
+	return EFI_SUCCESS;
+}
+
+/**
+ * create_selected_device_path() - create device path
+ *
+ * @file_info:	pointer to the selected file information
+ * Return:
+ * device path or NULL. Caller must free the returned value
+ */
+static
+struct efi_device_path *create_selected_device_path(struct eficonfig_select_file_info *file_info)
+{
+	char *p;
+	void *buf;
+	efi_uintn_t fp_size;
+	struct efi_device_path *dp;
+	struct efi_device_path_file_path *fp;
+
+	fp_size = sizeof(struct efi_device_path) +
+		  ((u16_strlen(file_info->current_path) + 1) * sizeof(u16));
+	buf = calloc(1, fp_size + sizeof(END));
+	if (!buf)
+		return NULL;
+
+	fp = buf;
+	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, file_info->current_path);
+
+	p = buf;
+	p += fp_size;
+	*((struct efi_device_path *)p) = END;
+
+	dp = efi_dp_append(file_info->dp_volume, (struct efi_device_path *)buf);
+	free(buf);
+
+	return dp;
+}
+
+/**
+ * eficonfig_file_selected() - handler of file selection
+ *
+ * @data:	pointer to the data of selected entry
+ * Return:	status code
+ */
+static efi_status_t eficonfig_file_selected(void *data)
+{
+	struct eficonfig_file_entry_data *info = data;
+
+	if (!info)
+		return EFI_INVALID_PARAMETER;
+
+	if (u16_strcmp(info->file_name, u".") == 0 &&
+	    u16_strlen(info->file_name) == 1) {
+		/* stay current path */
+	} else if (u16_strcmp(info->file_name, u"..") == 0 &&
+		   u16_strlen(info->file_name) == 2) {
+		struct eficonfig_filepath_info *iter;
+		struct list_head *pos, *n;
+		int is_last;
+
+		memset(info->file_info->current_path, 0, EFICONFIG_FILE_PATH_BUF_SIZE);
+		list_for_each_safe(pos, n, &info->file_info->filepath_list) {
+			iter = list_entry(pos, struct eficonfig_filepath_info, list);
+
+			is_last = list_is_last(&iter->list, &info->file_info->filepath_list);
+			if (is_last) {
+				list_del(&iter->list);
+				free(iter->name);
+				free(iter);
+				break;
+			}
+			u16_strlcat(info->file_info->current_path, iter->name,
+				    EFICONFIG_FILE_PATH_MAX);
+			u16_strlcat(info->file_info->current_path, u"\\",
+				    EFICONFIG_FILE_PATH_MAX);
+		}
+	} else {
+		size_t new_len;
+		struct eficonfig_filepath_info *filepath;
+
+		new_len = u16_strlen(info->file_info->current_path) +
+				     u16_strlen(info->file_name);
+		if (new_len >= EFICONFIG_FILE_PATH_MAX) {
+			eficonfig_print_msg("File path is too long!");
+			return EFI_INVALID_PARAMETER;
+		}
+		u16_strlcat(info->file_info->current_path, info->file_name,
+			    EFICONFIG_FILE_PATH_MAX);
+
+		filepath = calloc(1, sizeof(struct eficonfig_filepath_info));
+		if (!filepath)
+			return EFI_OUT_OF_RESOURCES;
+
+		filepath->name = u16_strdup(info->file_name);
+		if (!filepath->name) {
+			free(filepath);
+			return EFI_OUT_OF_RESOURCES;
+		}
+		list_add_tail(&filepath->list, &info->file_info->filepath_list);
+
+		if (info->is_directory) {
+			/*
+			 * Remainig buffer should have enough space to contain u"\\" and
+			 * at least one character for file name
+			 */
+			if (new_len + 2 >= EFICONFIG_FILE_PATH_MAX) {
+				eficonfig_print_msg("Directory path is too long!");
+				return EFI_INVALID_PARAMETER;
+			}
+			u16_strlcat(info->file_info->current_path, u"\\",
+				    EFICONFIG_FILE_PATH_MAX);
+		} else {
+			info->file_info->file_selected = true;
+		}
+	}
+	return EFI_SUCCESS;
+}
+
+/**
+ * eficonfig_select_volume() - construct the volume selection menu
+ *
+ * @file_info:	pointer to the file selection structure
+ * Return:	status code
+ */
+static efi_status_t eficonfig_select_volume(struct eficonfig_select_file_info *file_info)
+{
+	u32 i;
+	efi_status_t ret;
+	efi_uintn_t count;
+	struct efimenu *efi_menu;
+	struct efi_handler *handler;
+	struct efi_device_path *device_path;
+	efi_handle_t *volume_handles = NULL;
+	struct efi_simple_file_system_protocol *v;
+
+	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) {
+		eficonfig_print_msg("No block device found!");
+		return ret;
+	}
+
+	efi_menu = calloc(1, sizeof(struct efimenu));
+	if (!efi_menu)
+		return EFI_OUT_OF_RESOURCES;
+
+	INIT_LIST_HEAD(&efi_menu->list);
+	for (i = 0; i < count; i++) {
+		char *devname;
+		struct efi_block_io *block_io;
+		struct eficonfig_volume_entry_data *info;
+
+		if (efi_menu->count >= EFICONFIG_ENTRY_NUM_MAX - 1)
+			break;
+
+		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 eficonfig_volume_entry_data));
+		if (!info) {
+			ret = EFI_OUT_OF_RESOURCES;
+			goto out;
+		}
+
+		devname = calloc(1, BOOTMENU_DEVICE_NAME_MAX);
+		if (!devname) {
+			free(info);
+			ret = EFI_OUT_OF_RESOURCES;
+			goto out;
+		}
+		ret = efi_disk_get_device_name(volume_handles[i], devname,
+					       BOOTMENU_DEVICE_NAME_MAX);
+		if (ret != EFI_SUCCESS) {
+			free(info);
+			goto out;
+		}
+
+		info->v = v;
+		info->dp = device_path;
+		info->file_info = file_info;
+		ret = append_entry(efi_menu, devname, eficonfig_volume_selected, info);
+		if (ret != EFI_SUCCESS) {
+			free(info);
+			goto out;
+		}
+	}
+
+	ret = append_quit_entry(efi_menu);
+	if (ret != EFI_SUCCESS)
+		goto out;
+
+	ret = eficonfig_process_common(efi_menu, "  ** Select Volume **");
+out:
+	efi_free_pool(volume_handles);
+	eficonfig_destroy(efi_menu, true);
+
+	return ret;
+}
+
+/**
+ * eficonfig_select_file() - construct the file selection menu
+ *
+ * @file_info:	pointer to the file selection structure
+ * @root:	pointer to the file handle
+ * Return:	status code
+ */
+static efi_status_t eficonfig_select_file(struct eficonfig_select_file_info *file_info,
+					  struct efi_file_handle *root)
+{
+	efi_uintn_t len;
+	efi_status_t ret;
+	struct efimenu *efi_menu;
+	struct efi_file_handle *f;
+	struct efi_file_info *buf;
+	struct list_head *pos, *n;
+
+	buf = calloc(1, sizeof(struct efi_file_info) + EFICONFIG_FILE_PATH_BUF_SIZE);
+	if (!buf)
+		return EFI_OUT_OF_RESOURCES;
+
+	while (!file_info->file_selected) {
+		efi_menu = calloc(1, sizeof(struct efimenu));
+		if (!efi_menu) {
+			ret = EFI_OUT_OF_RESOURCES;
+			goto out;
+		}
+		INIT_LIST_HEAD(&efi_menu->list);
+
+		ret = efi_file_open_int(root, &f, file_info->current_path, EFI_FILE_MODE_READ, 0);
+		if (ret != EFI_SUCCESS) {
+			eficonfig_print_msg("Reading volume failed!");
+			ret = EFI_ABORTED;
+			goto out;
+		}
+
+		/* Read directory and construct menu structure */
+		for (;;) {
+			char *name, *p;
+			int name_len;
+			struct eficonfig_file_entry_data *info;
+
+			if (efi_menu->count >= EFICONFIG_ENTRY_NUM_MAX - 1)
+				break;
+
+			len = sizeof(struct efi_file_info) + EFICONFIG_FILE_PATH_BUF_SIZE;
+			ret = efi_file_read_int(f, &len, buf);
+			if (ret != EFI_SUCCESS || len == 0)
+				break;
+
+			info = calloc(1, sizeof(struct eficonfig_file_entry_data));
+			if (!info) {
+				ret = EFI_OUT_OF_RESOURCES;
+				goto err;
+			}
+
+			if (buf->attribute & EFI_FILE_DIRECTORY) {
+				/* append u'/' at the end of directory name */
+				name_len = utf16_utf8_strlen(buf->file_name) + 2;
+
+				/* filter out u'.' */
+				if (name_len == 3 && buf->file_name[0] == u'.') {
+					free(info);
+					continue;
+				}
+
+				name = calloc(1, name_len);
+				if (!name) {
+					free(info);
+					ret = EFI_OUT_OF_RESOURCES;
+					goto err;
+				}
+				p = name;
+				utf16_utf8_strcpy(&p, buf->file_name);
+				name[u16_strlen(buf->file_name)] = u'/';
+
+				info->is_directory = true;
+			} else {
+				name_len = utf16_utf8_strlen(buf->file_name) + 1;
+				name = calloc(1, name_len);
+				if (!name) {
+					free(info);
+					ret = EFI_OUT_OF_RESOURCES;
+					goto err;
+				}
+				p = name;
+				utf16_utf8_strcpy(&p, buf->file_name);
+			}
+
+			info->file_name = u16_strdup(buf->file_name);
+			if (!info->file_name) {
+				free(info);
+				free(name);
+				ret = EFI_OUT_OF_RESOURCES;
+				goto err;
+			}
+
+			info->file_info = file_info;
+			ret = append_entry(efi_menu, name, eficonfig_file_selected, info);
+			if (ret != EFI_SUCCESS) {
+				free(info);
+				free(name);
+				goto err;
+			}
+		}
+
+		ret = append_quit_entry(efi_menu);
+		if (ret != EFI_SUCCESS)
+			goto err;
+
+		ret = eficonfig_process_common(efi_menu, "  ** Select File **");
+err:
+		efi_file_close_int(f);
+		list_for_each_safe(pos, n, &efi_menu->list) {
+			struct eficonfig_entry *entry;
+
+			entry = list_entry(pos, struct eficonfig_entry, list);
+			/* skip "Quit" */
+			if (list_is_last(&entry->list, &efi_menu->list))
+				break;
+
+			free(((struct eficonfig_file_entry_data *)(entry->data))->file_name);
+		}
+		eficonfig_destroy(efi_menu, true);
+		if (ret != EFI_SUCCESS)
+			break;
+	}
+
+out:
+	free(buf);
+	return ret;
+}
+
+/**
+ * handle_user_input() - handle user input
+ *
+ * @buf:	pointer to the buffer
+ * @buf_size:	size of the buffer
+ * @cursol_col:	cursol column for user input
+ * @msg:	pointer to the string to display
+ * Return:	status code
+ */
+static efi_status_t handle_user_input(u16 *buf, int buf_size,
+				      int cursol_col, char *msg)
+{
+	u16 *tmp;
+	efi_status_t ret;
+
+	printf(ANSI_CLEAR_CONSOLE
+	       ANSI_CURSOR_POSITION
+	       "%s"
+	       ANSI_CURSOR_POSITION
+	       "  Press ENTER to complete, ESC/CTRL+C to quit",
+	       0, 1, msg, 8, 1);
+
+	/* tmp is used to accept user cancel */
+	tmp = calloc(1, buf_size * sizeof(u16));
+	if (!tmp)
+		return EFI_OUT_OF_RESOURCES;
+
+	ret = efi_console_get_u16_string(cin, tmp, buf_size, NULL, 4, cursol_col);
+	if (ret == EFI_SUCCESS)
+		u16_strcpy(buf, tmp);
+
+	free(tmp);
+
+	/* to stay the parent menu */
+	ret = (ret == EFI_ABORTED) ? EFI_NOT_READY : ret;
+
+	return ret;
+}
+
+/**
+ * eficonfig_boot_add_enter_description() - handle user input for description
+ *
+ * @data:	pointer to the internal boot option structure
+ * Return:	status code
+ */
+static efi_status_t eficonfig_boot_add_enter_description(void *data)
+{
+	struct eficonfig_boot_option *bo = data;
+
+	return handle_user_input(bo->description, EFICONFIG_DESCRIPTION_MAX, 22,
+				 "\n  ** Edit Description **\n"
+				 "\n"
+				 "  enter description: ");
+}
+
+/**
+ * eficonfig_boot_add_optional_data() - handle user input for optional data
+ *
+ * @data:	pointer to the internal boot option structure
+ * Return:	status code
+ */
+static efi_status_t eficonfig_boot_add_optional_data(void *data)
+{
+	struct eficonfig_boot_option *bo = data;
+
+	return handle_user_input(bo->optional_data, EFICONFIG_OPTIONAL_DATA_MAX, 24,
+				 "\n  ** Edit Optional Data **\n"
+				 "\n"
+				 "  enter optional data:");
+}
+
+/**
+ * eficonfig_boot_edit_save() - handler to save the boot option
+ *
+ * @data:	pointer to the internal boot option structure
+ * Return:	status code
+ */
+static efi_status_t eficonfig_boot_edit_save(void *data)
+{
+	struct eficonfig_boot_option *bo = data;
+
+	if (u16_strlen(bo->description) == 0) {
+		eficonfig_print_msg("Boot Description is empty!");
+		bo->edit_completed = false;
+		return EFI_NOT_READY;
+	}
+	if (u16_strlen(bo->file_info.current_path) == 0) {
+		eficonfig_print_msg("File is not selected!");
+		bo->edit_completed = false;
+		return EFI_NOT_READY;
+	}
+
+	bo->edit_completed = true;
+
+	return EFI_SUCCESS;
+}
+
+/**
+ * eficonfig_select_file_handler() - handle user file selection
+ *
+ * @data:	pointer to the data
+ * Return:	status code
+ */
+efi_status_t eficonfig_select_file_handler(void *data)
+{
+	size_t len;
+	efi_status_t ret;
+	struct list_head *pos, *n;
+	struct efi_file_handle *root;
+	struct eficonfig_filepath_info *item;
+	struct eficonfig_select_file_info *file_info = data;
+	struct eficonfig_select_file_info *tmp = NULL;
+
+	tmp = calloc(1, sizeof(struct eficonfig_select_file_info));
+	if (!tmp)
+		return EFI_OUT_OF_RESOURCES;
+
+	tmp->current_path = calloc(1, EFICONFIG_FILE_PATH_BUF_SIZE);
+	if (!tmp->current_path) {
+		free(tmp);
+		return EFI_OUT_OF_RESOURCES;
+	}
+	INIT_LIST_HEAD(&tmp->filepath_list);
+
+	while (!tmp->file_selected) {
+		tmp->current_volume = NULL;
+		memset(tmp->current_path, 0, EFICONFIG_FILE_PATH_BUF_SIZE);
+
+		ret = eficonfig_select_volume(tmp);
+		if (ret != EFI_SUCCESS)
+			goto out;
+
+		if (!tmp->current_volume)
+			return EFI_INVALID_PARAMETER;
+
+		ret = efi_open_volume_int(tmp->current_volume, &root);
+		if (ret != EFI_SUCCESS)
+			goto out;
+
+		ret = eficonfig_select_file(tmp, root);
+		if (ret == EFI_ABORTED)
+			continue;
+		if (ret != EFI_SUCCESS)
+			goto out;
+	}
+
+out:
+	if (ret == EFI_SUCCESS) {
+		len = u16_strlen(tmp->current_path);
+		len = (len >= EFICONFIG_FILE_PATH_MAX) ? (EFICONFIG_FILE_PATH_MAX - 1) : len;
+		memcpy(file_info->current_path, tmp->current_path, len * sizeof(u16));
+		file_info->current_path[len] = u'\0';
+		file_info->current_volume = tmp->current_volume;
+		file_info->dp_volume = tmp->dp_volume;
+	}
+
+	list_for_each_safe(pos, n, &tmp->filepath_list) {
+		item = list_entry(pos, struct eficonfig_filepath_info, list);
+		list_del(&item->list);
+		free(item->name);
+		free(item);
+	}
+	free(tmp->current_path);
+	free(tmp);
+
+	/* to stay the parent menu */
+	ret = (ret == EFI_ABORTED) ? EFI_NOT_READY : ret;
+
+	return ret;
+}
+
+/**
+ * eficonfig_get_unused_bootoption() - get unused "Boot####" index
+ *
+ * @buf:	pointer to the buffer to store boot option variable name
+ * @buf_size:	buffer size
+ * @index:	pointer to store the index in the BootOrder variable
+ * Return:	status code
+ */
+efi_status_t eficonfig_get_unused_bootoption(u16 *buf, efi_uintn_t buf_size,
+					     unsigned int *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;
+}
+
+/**
+ * eficonfig_set_boot_option() - set boot option
+ *
+ * @varname:		pointer to variable name
+ * @dp:			pointer to device path
+ * @label:		pointer to label string
+ * @optional_data:	pointer to optional data
+ * Return:		status code
+ */
+static efi_status_t eficonfig_set_boot_option(u16 *varname, struct efi_device_path *dp,
+					      efi_uintn_t dp_size, 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 = dp_size;
+	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(varname, &efi_global_variable_guid,
+				   EFI_VARIABLE_NON_VOLATILE |
+				   EFI_VARIABLE_BOOTSERVICE_ACCESS |
+				   EFI_VARIABLE_RUNTIME_ACCESS,
+				   size, p, false);
+	free(p);
+
+	return ret;
+}
+
+/**
+ * eficonfig_append_bootorder() - append new boot option in BootOrder variable
+ *
+ * @index:	"Boot####" index to append to BootOrder variable
+ * Return:	status code
+ */
+efi_status_t eficonfig_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;
+}
+
+/**
+ * create_boot_option_entry() - create boot option entry
+ *
+ * @efi_menu:	pointer to the efimenu structure
+ * @title:	pointer to the entry title
+ * @val:	pointer to boot option label
+ * @func:	callback of each entry
+ * @data:	pointer to the data to be passed to each entry callback
+ * Return:	status code
+ */
+static efi_status_t create_boot_option_entry(struct efimenu *efi_menu, char *title, u16 *val,
+					     eficonfig_entry_func func, void *data)
+{
+	u32 len;
+	char *p, *buf;
+
+	len = strlen(title) + 1;
+	if (val)
+		len += utf16_utf8_strlen(val);
+	buf = calloc(1, len);
+	if (!buf)
+		return EFI_OUT_OF_RESOURCES;
+
+	strcpy(buf, title);
+	if (val) {
+		p = buf + strlen(title);
+		utf16_utf8_strcpy(&p, val);
+	}
+
+	return append_entry(efi_menu, buf, func, data);
+}
+
+/**
+ * prepare_file_selection_entry() - prepare file selection entry
+ *
+ * @efi_menu:	pointer to the efimenu structure
+ * @title:	pointer to the title string
+ * @file_info:	pointer to the file info
+ * Return:	status code
+ */
+static efi_status_t prepare_file_selection_entry(struct efimenu *efi_menu, char *title,
+						 struct eficonfig_select_file_info *file_info)
+{
+	u32 len;
+	efi_status_t ret;
+	u16 *file_name, *p;
+	efi_handle_t handle;
+	char devname[BOOTMENU_DEVICE_NAME_MAX] = {0};
+
+	/* get the device name only when the user already selected the file path */
+	handle = efi_dp_find_obj(file_info->dp_volume, NULL, NULL);
+	if (handle) {
+		ret = efi_disk_get_device_name(handle, devname, BOOTMENU_DEVICE_NAME_MAX);
+		if (ret != EFI_SUCCESS)
+			return ret;
+	}
+
+	/* append u'/' to devname, it is just for display purpose. */
+	if (file_info->current_path[0] != u'\0' && file_info->current_path[0] != u'/')
+		strlcat(devname, "/", BOOTMENU_DEVICE_NAME_MAX);
+
+	len = strlen(devname);
+	len += utf16_utf8_strlen(file_info->current_path) + 1;
+	file_name = calloc(1, len * sizeof(u16));
+	if (!file_name)
+		return ret;
+
+	p = file_name;
+	utf8_utf16_strcpy(&p, devname);
+	u16_strlcat(file_name, file_info->current_path, len);
+	ret = create_boot_option_entry(efi_menu, title, file_name,
+				       eficonfig_select_file_handler, file_info);
+	free(file_name);
+	return ret;
+}
+
+/**
+ * eficonfig_show_boot_option() - prepare menu entry for editing boot option
+ *
+ * Construct the structures to create edit boot option menu
+ *
+ * @bo:		pointer to the boot option
+ * @header_str:	pointer to the header string
+ * Return:	status code
+ */
+static efi_status_t eficonfig_show_boot_option(struct eficonfig_boot_option *bo,
+					       char *header_str)
+{
+	struct efimenu *efi_menu;
+	efi_status_t ret;
+
+	efi_menu = calloc(1, sizeof(struct efimenu));
+	if (!efi_menu)
+		return EFI_OUT_OF_RESOURCES;
+
+	INIT_LIST_HEAD(&efi_menu->list);
+
+	ret = create_boot_option_entry(efi_menu, "Description: ", bo->description,
+				       eficonfig_boot_add_enter_description, bo);
+	if (ret != EFI_SUCCESS)
+		goto out;
+
+	ret = prepare_file_selection_entry(efi_menu, "File: ", &bo->file_info);
+	if (ret != EFI_SUCCESS)
+		goto out;
+
+	ret = prepare_file_selection_entry(efi_menu, "Initrd File: ", &bo->initrd_info);
+	if (ret != EFI_SUCCESS)
+		goto out;
+
+	ret = create_boot_option_entry(efi_menu, "Optional Data: ", bo->optional_data,
+				       eficonfig_boot_add_optional_data, bo);
+	if (ret != EFI_SUCCESS)
+		goto out;
+
+	ret = create_boot_option_entry(efi_menu, "Save", NULL,
+				       eficonfig_boot_edit_save, bo);
+	if (ret != EFI_SUCCESS)
+		goto out;
+
+	ret = create_boot_option_entry(efi_menu, "Quit", NULL,
+				       eficonfig_process_quit, bo);
+	if (ret != EFI_SUCCESS)
+		goto out;
+
+	ret = eficonfig_process_common(efi_menu, header_str);
+out:
+	eficonfig_destroy(efi_menu, false);
+
+	return ret;
+}
+
+/**
+ * fill_file_info() - fill the file info from efi_device_path structure
+ *
+ * @dp:		pointer to the device path
+ * @file_info:	pointer to the file info structure
+ * @device_dp:	pointer to the volume device path
+ */
+static void fill_file_info(struct efi_device_path *dp,
+			   struct eficonfig_select_file_info *file_info,
+			   struct efi_device_path *device_dp)
+{
+	u16 *file_str, *p;
+	struct efi_device_path *file_dp = NULL;
+
+	efi_dp_split_file_path(dp, &device_dp, &file_dp);
+	file_info->dp_volume = device_dp;
+	file_str = efi_dp_str(file_dp);
+	/*
+	 * efi_convert_device_path_to_text() automatically adds u'/' at the
+	 * beginning of file name, remove u'/' before copying to current_path
+	 */
+	p = file_str;
+	if (p[0] == u'/')
+		p++;
+
+	u16_strcpy(file_info->current_path, p);
+	efi_free_pool(file_dp);
+	efi_free_pool(file_str);
+}
+
+/**
+ * eficonfig_edit_boot_option() - prepare boot option structure for editing
+ *
+ * Construct the boot option structure and copy the existing value
+ *
+ * @varname:		pointer to the UEFI variable name
+ * @bo:			pointer to the boot option
+ * @load_option:	pointer to the load option
+ * @load_option_size:	size of the load option
+ * @header_str:		pointer to the header string
+ * Return	:	status code
+ */
+static efi_status_t eficonfig_edit_boot_option(u16 *varname, struct eficonfig_boot_option *bo,
+					       void *load_option, efi_uintn_t load_option_size,
+					       char *header_str)
+{
+	size_t len;
+	efi_status_t ret;
+	char *tmp = NULL, *p;
+	struct efi_load_option lo = {0};
+	efi_uintn_t final_dp_size;
+	struct efi_device_path *dp = NULL;
+	efi_uintn_t size = load_option_size;
+	struct efi_device_path *final_dp = NULL;
+	struct efi_device_path *device_dp = NULL;
+	struct efi_device_path *initrd_dp = NULL;
+	struct efi_device_path *initrd_device_dp = NULL;
+
+	const struct efi_initrd_dp id_dp = {
+		.vendor = {
+			{
+			DEVICE_PATH_TYPE_MEDIA_DEVICE,
+			DEVICE_PATH_SUB_TYPE_VENDOR_PATH,
+			sizeof(id_dp.vendor),
+			},
+			EFI_INITRD_MEDIA_GUID,
+		},
+		.end = {
+			DEVICE_PATH_TYPE_END,
+			DEVICE_PATH_SUB_TYPE_END,
+			sizeof(id_dp.end),
+		}
+	};
+
+	bo->file_info.current_path = calloc(1, EFICONFIG_FILE_PATH_BUF_SIZE);
+	if (!bo->file_info.current_path) {
+		ret =  EFI_OUT_OF_RESOURCES;
+		goto out;
+	}
+
+	bo->initrd_info.current_path = calloc(1, EFICONFIG_FILE_PATH_BUF_SIZE);
+	if (!bo->file_info.current_path) {
+		ret =  EFI_OUT_OF_RESOURCES;
+		goto out;
+	}
+
+	bo->description = calloc(1, EFICONFIG_DESCRIPTION_MAX * sizeof(u16));
+	if (!bo->description) {
+		ret =  EFI_OUT_OF_RESOURCES;
+		goto out;
+	}
+
+	bo->optional_data = calloc(1, EFICONFIG_OPTIONAL_DATA_MAX * sizeof(u16));
+	if (!bo->optional_data) {
+		ret =  EFI_OUT_OF_RESOURCES;
+		goto out;
+	}
+
+	/* copy the preset value */
+	if (load_option) {
+		ret = efi_deserialize_load_option(&lo, load_option, &size);
+		if (ret != EFI_SUCCESS)
+			goto out;
+
+		if (!lo.label || (lo.label && u16_strlen(lo.label) >= EFICONFIG_DESCRIPTION_MAX)) {
+			ret = EFI_INVALID_PARAMETER;
+			goto out;
+		}
+		u16_strcpy(bo->description, lo.label);
+
+		/* EFI image file path is a first instance */
+		if (lo.file_path)
+			fill_file_info(lo.file_path, &bo->file_info, device_dp);
+
+		/* Initrd file path(optional) is placed at second instance. */
+		initrd_dp = efi_dp_from_lo(&lo, &efi_lf2_initrd_guid);
+		if (initrd_dp) {
+			fill_file_info(initrd_dp, &bo->initrd_info, initrd_device_dp);
+			efi_free_pool(initrd_dp);
+		}
+
+		if (size > 0)
+			memcpy(bo->optional_data, lo.optional_data, size);
+	}
+
+	while (1) {
+		ret = eficonfig_show_boot_option(bo, header_str);
+		if (ret == EFI_SUCCESS && bo->edit_completed)
+			break;
+		if (ret == EFI_NOT_READY)
+			continue;
+		if (ret != EFI_SUCCESS)
+			goto out;
+	}
+
+	if (bo->initrd_info.dp_volume) {
+		dp = create_selected_device_path(&bo->initrd_info);
+		if (!dp) {
+			ret = EFI_OUT_OF_RESOURCES;
+			goto out;
+		}
+		initrd_dp = efi_dp_append((const struct efi_device_path *)&id_dp, dp);
+		efi_free_pool(dp);
+	}
+
+	dp = create_selected_device_path(&bo->file_info);
+	if (!dp) {
+		ret = EFI_OUT_OF_RESOURCES;
+		goto out;
+	}
+	final_dp_size = efi_dp_size(dp) + sizeof(END);
+	if (initrd_dp) {
+		final_dp = efi_dp_concat(dp, initrd_dp);
+		final_dp_size += efi_dp_size(initrd_dp) + sizeof(END);
+	} else {
+		final_dp = efi_dp_dup(dp);
+	}
+	efi_free_pool(dp);
+
+	if (!final_dp)
+		goto out;
+
+	len = utf16_utf8_strlen(bo->optional_data) + 1;
+	tmp = calloc(1, len);
+	if (!tmp)
+		goto out;
+	p = tmp;
+	utf16_utf8_strncpy(&p, bo->optional_data, u16_strlen(bo->optional_data));
+
+	ret = eficonfig_set_boot_option(varname, final_dp, final_dp_size, bo->description, tmp);
+	if (ret != EFI_SUCCESS)
+		goto out;
+out:
+	free(tmp);
+	free(bo->optional_data);
+	free(bo->description);
+	free(bo->file_info.current_path);
+	free(bo->initrd_info.current_path);
+	efi_free_pool(device_dp);
+	efi_free_pool(initrd_device_dp);
+	efi_free_pool(initrd_dp);
+	efi_free_pool(final_dp);
+
+	return ret;
+}
+
+/**
+ * eficonfig_process_add_boot_option() - handler to add boot option
+ *
+ * @data:	pointer to the data for each entry
+ * Return:	status code
+ */
+static efi_status_t eficonfig_process_add_boot_option(void *data)
+{
+	u16 varname[9];
+	efi_status_t ret;
+	struct eficonfig_boot_option *bo = NULL;
+
+	bo = calloc(1, sizeof(struct eficonfig_boot_option));
+	if (!bo)
+		return EFI_OUT_OF_RESOURCES;
+
+	ret = eficonfig_get_unused_bootoption(varname, sizeof(varname), &bo->boot_index);
+	if (ret != EFI_SUCCESS)
+		return ret;
+
+	ret = eficonfig_edit_boot_option(varname, bo, NULL, 0,  "  ** Add Boot Option ** ");
+	if (ret != EFI_SUCCESS)
+		goto out;
+
+	ret = eficonfig_append_bootorder((u16)bo->boot_index);
+	if (ret != EFI_SUCCESS)
+		goto out;
+
+out:
+	free(bo);
+
+	/* to stay the parent menu */
+	ret = (ret == EFI_ABORTED) ? EFI_SUCCESS : ret;
+
+	return ret;
+}
+
+/**
+ * eficonfig_init() - do required initialization for eficonfig command
+ *
+ * Return:	status code
+ */
+static efi_status_t eficonfig_init(void)
+{
+	efi_status_t ret;
+	static bool init;
+	struct efi_handler *handler;
+
+	if (!init) {
+		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;
+	}
+
+	init = true;
+
+	return ret;
+}
+
+static const struct eficonfig_item maintenance_menu_items[] = {
+	{"Add Boot Option", eficonfig_process_add_boot_option},
+	{"Quit", eficonfig_process_quit},
+};
+
+/**
+ * do_eficonfig() - execute `eficonfig` command
+ *
+ * @cmdtp:	table entry describing command
+ * @flag:	bitmap indicating how the command was invoked
+ * @argc:	number of arguments
+ * @argv:	command line arguments
+ * Return:	status code
+ */
+static int do_eficonfig(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[])
+{
+	efi_status_t ret;
+	struct efimenu *efi_menu;
+
+	if (argc > 1)
+		return CMD_RET_USAGE;
+
+	ret = efi_init_obj_list();
+	if (ret != EFI_SUCCESS) {
+		log_err("Error: Cannot initialize UEFI sub-system, r = %lu\n",
+			ret & ~EFI_ERROR_MASK);
+
+		return CMD_RET_FAILURE;
+	}
+
+	ret = eficonfig_init();
+	if (ret != EFI_SUCCESS)
+		return CMD_RET_FAILURE;
+
+	while (1) {
+		efi_menu = eficonfig_create_fixed_menu(maintenance_menu_items,
+						       ARRAY_SIZE(maintenance_menu_items));
+		if (!efi_menu)
+			return CMD_RET_FAILURE;
+
+		ret = eficonfig_process_common(efi_menu, "  ** UEFI Maintenance Menu **");
+		eficonfig_destroy(efi_menu, false);
+
+		if (ret == EFI_ABORTED)
+			break;
+	}
+
+	return CMD_RET_SUCCESS;
+}
+
+U_BOOT_CMD(
+	eficonfig, 1, 0, do_eficonfig,
+	"provide menu-driven UEFI variable maintenance interface",
+	""
+);
diff --git a/include/efi_config.h b/include/efi_config.h
new file mode 100644
index 0000000000..aaff5c7cc0
--- /dev/null
+++ b/include/efi_config.h
@@ -0,0 +1,90 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ *  Menu-driven UEFI Variable maintenance
+ *
+ *  Copyright (c) 2022 Masahisa Kojima, Linaro Limited
+ */
+
+#ifndef _EFI_CONFIG_H
+#define _EFI_CONFIG_H
+
+#define EFICONFIG_ENTRY_NUM_MAX 99
+#define EFICONFIG_FILE_PATH_MAX 512
+#define EFICONFIG_FILE_PATH_BUF_SIZE (EFICONFIG_FILE_PATH_MAX * sizeof(u16))
+
+typedef efi_status_t (*eficonfig_entry_func)(void *data);
+
+/**
+ * struct eficonfig_entry - menu entry structure
+ *
+ * @num:	menu entry index
+ * @title:	title of entry
+ * @key:	unique key
+ * @efi_menu:	pointer to the menu structure
+ * @func:	callback function to be called when this entry is selected
+ * @data:	data to be passed to the callback function
+ * @list:	list structure
+ */
+struct eficonfig_entry {
+	u32 num;
+	char *title;
+	char key[3];
+	struct efimenu *efi_menu;
+	eficonfig_entry_func func;
+	void *data;
+	struct list_head list;
+};
+
+/**
+ * struct efimenu - efi menu structure
+ *
+ * @delay:		delay for autoboot
+ * @active:		active menu entry index
+ * @count:		total count of menu entry
+ * @menu_header:	menu header string
+ * @list:		menu entry list structure
+ */
+struct efimenu {
+	int delay;
+	int active;
+	int count;
+	char *menu_header;
+	struct list_head list;
+};
+
+/**
+ * struct eficonfig_item - structure to construct eficonfig_entry
+ *
+ * @title:	title of entry
+ * @func:	callback function to be called when this entry is selected
+ * @data:	data to be passed to the callback function
+ */
+struct eficonfig_item {
+	char *title;
+	eficonfig_entry_func func;
+	void *data;
+};
+
+/**
+ * struct eficonfig_select_file_info - structure to be used for file selection
+ *
+ * @current_volume:	pointer to the efi_simple_file_system_protocol
+ * @dp_volume:		pointer to device path of the selected device
+ * @current_path:	pointer to the selected file path string
+ * @filepath_list:	list_head structure for file path list
+ * @file_selectred:	flag indicates file selecting status
+ */
+struct eficonfig_select_file_info {
+	struct efi_simple_file_system_protocol *current_volume;
+	struct efi_device_path *dp_volume;
+	u16 *current_path;
+	struct list_head filepath_list;
+	bool file_selected;
+};
+
+void eficonfig_print_msg(char *msg);
+efi_status_t eficonfig_process_quit(void *data);
+efi_status_t eficonfig_process_common(struct efimenu *efi_menu, char *menu_header);
+efi_status_t eficonfig_select_file_handler(void *data);
+
+#endif
diff --git a/include/efi_loader.h b/include/efi_loader.h
index b0d6fff67c..49e7d1e613 100644
--- a/include/efi_loader.h
+++ b/include/efi_loader.h
@@ -142,6 +142,11 @@ static inline efi_status_t efi_launch_capsules(void)
 	EFI_GUID(0x63293792, 0xadf5, 0x9325, \
 		 0xb9, 0x9f, 0x4e, 0x0e, 0x45, 0x5c, 0x1b, 0x1e)
 
+/* GUID for the auto generated boot menu entry */
+#define EFICONFIG_AUTO_GENERATED_ENTRY_GUID \
+	EFI_GUID(0x38c1acc1, 0x9fc0, 0x41f0, \
+		 0xb9, 0x01, 0xfa, 0x74, 0xd6, 0xd6, 0xe4, 0xde)
+
 /* Use internal device tree when starting UEFI application */
 #define EFI_FDT_USE_INTERNAL NULL
 
@@ -226,6 +231,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;
 
@@ -249,6 +257,9 @@ extern const struct efi_hii_string_protocol efi_hii_string;
 
 uint16_t *efi_dp_str(struct efi_device_path *dp);
 
+/* GUID for the auto generated boot menu entry */
+extern const efi_guid_t efi_guid_bootmenu_auto_generated;
+
 /* GUID of the U-Boot root node */
 extern const efi_guid_t efi_u_boot_guid;
 #ifdef CONFIG_SANDBOX
@@ -314,6 +325,8 @@ 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 char __efi_runtime_start[], __efi_runtime_stop[];
 extern char __efi_runtime_rel_start[], __efi_runtime_rel_stop[];
@@ -891,6 +904,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
  *
@@ -1064,4 +1079,32 @@ 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,
+		 u16 *buf, efi_uintn_t count, efi_console_filter_func filer_func,
+		 int row, int col);
+
+efi_status_t eficonfig_get_unused_bootoption(u16 *buf,
+					     efi_uintn_t buf_size, u32 *index);
+efi_status_t eficonfig_append_bootorder(u16 index);
+
+efi_status_t efi_disk_get_device_name(const efi_handle_t handle, char *buf, int size);
+
 #endif /* _EFI_LOADER_H */
diff --git a/lib/efi_loader/efi_bootmgr.c b/lib/efi_loader/efi_bootmgr.c
index 234073ecb7..ede9116b3c 100644
--- a/lib/efi_loader/efi_bootmgr.c
+++ b/lib/efi_loader/efi_bootmgr.c
@@ -19,6 +19,9 @@
 static const struct efi_boot_services *bs;
 static const struct efi_runtime_services *rs;
 
+const efi_guid_t efi_guid_bootmenu_auto_generated =
+		EFICONFIG_AUTO_GENERATED_ENTRY_GUID;
+
 /*
  * bootmgr implements the logic of trying to find a payload to boot
  * based on the BootOrder + BootXXXX variables, and then loading it.
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 3164fd484e..5be509f0d6 100644
--- a/lib/efi_loader/efi_console.c
+++ b/lib/efi_loader/efi_console.c
@@ -7,6 +7,7 @@
 
 #define LOG_CATEGORY LOGC_EFI
 
+#include <ansi.h>
 #include <common.h>
 #include <charset.h>
 #include <malloc.h>
@@ -1318,3 +1319,72 @@ 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
+ * @buf:		buffer to store user input string in UTF16
+ * @count:		number of u16 string including NULL terminator that buf has
+ * @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,
+					u16 *buf, efi_uintn_t count,
+					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
+	       ANSI_CLEAR_LINE_TO_END
+	       ANSI_CURSOR_SHOW, row, col);
+
+	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
+			       "%ls"
+			       ANSI_CLEAR_LINE_TO_END, row, col, buf);
+			continue;
+		} else if (key.unicode_char == u'\r') {
+			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 >= (count - 1))
+			continue;
+
+		buf[len] = key.unicode_char;
+		len++;
+		printf(ANSI_CURSOR_POSITION "%ls", row, col, buf);
+	}
+}
diff --git a/lib/efi_loader/efi_disk.c b/lib/efi_loader/efi_disk.c
index 16d14b0429..4c9c4cfec8 100644
--- a/lib/efi_loader/efi_disk.c
+++ b/lib/efi_loader/efi_disk.c
@@ -769,3 +769,53 @@ efi_status_t efi_disk_init(void)
 
 	return EFI_SUCCESS;
 }
+
+/**
+ * efi_disk_get_device_name() - get U-Boot device name associated with EFI handle
+ *
+ * @handle:	pointer to the EFI handle
+ * @buf:	pointer to the buffer to store the string
+ * @size:	size of buffer
+ * Return:	status code
+ */
+efi_status_t efi_disk_get_device_name(const efi_handle_t handle, char *buf, int size)
+{
+	int count;
+	int diskid;
+	enum uclass_id id;
+	unsigned int part;
+	struct udevice *dev;
+	struct blk_desc *desc;
+	const char *if_typename;
+	bool is_partition = false;
+	struct disk_part *part_data;
+
+	if (!handle || !buf || !size)
+		return EFI_INVALID_PARAMETER;
+
+	dev = handle->dev;
+	id = device_get_uclass_id(dev);
+	if (id == UCLASS_BLK) {
+		desc = dev_get_uclass_plat(dev);
+	} else if (id == UCLASS_PARTITION) {
+		desc = dev_get_uclass_plat(dev_get_parent(dev));
+		is_partition = true;
+	} else {
+		return EFI_INVALID_PARAMETER;
+	}
+	if_typename = blk_get_if_type_name(desc->if_type);
+	diskid = desc->devnum;
+
+	if (is_partition) {
+		part_data = dev_get_uclass_plat(dev);
+		part = part_data->partnum;
+		count = snprintf(buf, size, "%s %d:%d", if_typename, diskid, part);
+	} else {
+		count = snprintf(buf, size, "%s %d", if_typename, diskid);
+	}
+
+	if (count < 0 || (count + 1) > size)
+		return EFI_INVALID_PARAMETER;
+
+	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


^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [PATCH v11 2/9] eficonfig: add "Edit Boot Option" menu entry
  2022-08-17  9:36 [PATCH v11 0/9] enable menu-driven UEFI variable maintenance Masahisa Kojima
  2022-08-17  9:36 ` [PATCH v11 1/9] eficonfig: menu-driven addition of UEFI boot option Masahisa Kojima
@ 2022-08-17  9:36 ` Masahisa Kojima
  2022-08-17  9:36 ` [PATCH v11 3/9] menu: add KEY_PLUS, KEY_MINUS and KEY_SPACE handling Masahisa Kojima
                   ` (6 subsequent siblings)
  8 siblings, 0 replies; 25+ messages in thread
From: Masahisa Kojima @ 2022-08-17  9:36 UTC (permalink / raw)
  To: u-boot
  Cc: Heinrich Schuchardt, Ilias Apalodimas, Simon Glass,
	Takahiro Akashi, Mark Kettenis, Masahisa Kojima

This commit adds the menu entry to edit the existing
BOOT#### variable contents.
User selects the item from the boot option list, then
user can edit the description, file path and optional_data.

Note that automatically generated boot option entry by bootmenu
to support the removable media device is filtered out and user
can not edit the automatically generated entry.

Signed-off-by: Masahisa Kojima <masahisa.kojima@linaro.org>
---
Changes in v11:
- remove BootOrder variable dependency
- change the list load option order
   1) in the order of BootOrder
   2) remaing load option that is not included in the BootOrder
- add check for the number of menu entry exceeds max
- truncate the long load option label when user edits
- add EFICONFIG_VOLUME_PATH_MAX to display text converted volume
  device path in case the volume does not exist

Changes in v10:
- update eficonfig_edit_boot_option() argument

Changes in v9:
- add function comment

Changes in v8:
- fix menu header string
- fix function and structure prefix to "eficonfig"

Newly created in v7

 cmd/eficonfig.c      | 277 +++++++++++++++++++++++++++++++++++++++++--
 include/efi_config.h |   1 +
 2 files changed, 269 insertions(+), 9 deletions(-)

diff --git a/cmd/eficonfig.c b/cmd/eficonfig.c
index 39fbd3f0ad..938d46374e 100644
--- a/cmd/eficonfig.c
+++ b/cmd/eficonfig.c
@@ -40,7 +40,7 @@ struct eficonfig_filepath_info {
  *
  * @file_info:		user selected file info
  * @initrd_info:	user selected initrd file info
- * @boot_index:		index of the UEFI BootOrder variable
+ * @boot_index:		index of the boot option
  * @description:	pointer to the description string
  * @optional_data:	pointer to the optional_data
  * @edit_completed:	flag indicates edit complete
@@ -80,6 +80,17 @@ struct eficonfig_file_entry_data {
 	u16 *file_name;
 };
 
+/**
+ * struct eficonfig_boot_selection_data - structure to be used to select the boot option entry
+ *
+ * @boot_index:	index of the boot option
+ * @selected:		pointer to store the selected index in the BootOrder variable
+ */
+struct eficonfig_boot_selection_data {
+	u16 boot_index;
+	int *selected;
+};
+
 /**
  * eficonfig_print_msg() - print message
  *
@@ -1097,34 +1108,58 @@ static efi_status_t prepare_file_selection_entry(struct efimenu *efi_menu, char
 {
 	u32 len;
 	efi_status_t ret;
-	u16 *file_name, *p;
+	u16 *file_name = NULL, *p;
 	efi_handle_t handle;
-	char devname[BOOTMENU_DEVICE_NAME_MAX] = {0};
+	char *devname;
+
+	devname = calloc(1, EFICONFIG_VOLUME_PATH_MAX + 1);
+	if (!devname)
+		return EFI_OUT_OF_RESOURCES;
 
 	/* get the device name only when the user already selected the file path */
 	handle = efi_dp_find_obj(file_info->dp_volume, NULL, NULL);
 	if (handle) {
-		ret = efi_disk_get_device_name(handle, devname, BOOTMENU_DEVICE_NAME_MAX);
+		ret = efi_disk_get_device_name(handle, devname, EFICONFIG_VOLUME_PATH_MAX);
 		if (ret != EFI_SUCCESS)
-			return ret;
+			goto out;
+	}
+
+	/*
+	 * If the preconfigured volume does not exist in the system, display the text
+	 * converted volume device path instead of U-Boot friendly name(e.g. "usb 0:1").
+	 */
+	if (!handle && file_info->dp_volume) {
+		u16 *dp_str;
+		char *q = devname;
+
+		dp_str = efi_dp_str(file_info->dp_volume);
+		if (dp_str)
+			utf16_utf8_strncpy(&q, dp_str, EFICONFIG_VOLUME_PATH_MAX);
+
+		efi_free_pool(dp_str);
 	}
 
 	/* append u'/' to devname, it is just for display purpose. */
 	if (file_info->current_path[0] != u'\0' && file_info->current_path[0] != u'/')
-		strlcat(devname, "/", BOOTMENU_DEVICE_NAME_MAX);
+		strlcat(devname, "/", EFICONFIG_VOLUME_PATH_MAX + 1);
 
 	len = strlen(devname);
 	len += utf16_utf8_strlen(file_info->current_path) + 1;
 	file_name = calloc(1, len * sizeof(u16));
-	if (!file_name)
-		return ret;
+	if (!file_name) {
+		ret = EFI_OUT_OF_RESOURCES;
+		goto out;
+	}
 
 	p = file_name;
 	utf8_utf16_strcpy(&p, devname);
 	u16_strlcat(file_name, file_info->current_path, len);
 	ret = create_boot_option_entry(efi_menu, title, file_name,
 				       eficonfig_select_file_handler, file_info);
+out:
+	free(devname);
 	free(file_name);
+
 	return ret;
 }
 
@@ -1288,10 +1323,14 @@ static efi_status_t eficonfig_edit_boot_option(u16 *varname, struct eficonfig_bo
 		if (ret != EFI_SUCCESS)
 			goto out;
 
-		if (!lo.label || (lo.label && u16_strlen(lo.label) >= EFICONFIG_DESCRIPTION_MAX)) {
+		if (!lo.label) {
 			ret = EFI_INVALID_PARAMETER;
 			goto out;
 		}
+		/* truncate the long label string */
+		if (u16_strlen(lo.label) >= EFICONFIG_DESCRIPTION_MAX)
+			lo.label[EFICONFIG_DESCRIPTION_MAX - 1] = u'\0';
+
 		u16_strcpy(bo->description, lo.label);
 
 		/* EFI image file path is a first instance */
@@ -1407,6 +1446,225 @@ out:
 	return ret;
 }
 
+/**
+ * eficonfig_process_boot_selected() - handler to select boot option entry
+ *
+ * @data:	pointer to the data for each entry
+ * Return:	status code
+ */
+static efi_status_t eficonfig_process_boot_selected(void *data)
+{
+	struct eficonfig_boot_selection_data *info = data;
+
+	if (info)
+		*info->selected = info->boot_index;
+
+	return EFI_SUCCESS;
+}
+
+/**
+ * search_bootorder() - search the boot option index in BootOrder
+ *
+ * @bootorder:	pointer to the BootOrder variable
+ * @num:	number of BootOrder entry
+ * @target:	target boot option index to search
+ * @index:	pointer to store the index of BootOrder variable
+ * Return:	true if exists, false otherwise
+ */
+static bool search_bootorder(u16 *bootorder, efi_uintn_t num, u32 target, u32 *index)
+{
+	u32 i;
+
+	for (i = 0; i < num; i++) {
+		if (target == bootorder[i]) {
+			if (index)
+				*index = i;
+
+			return true;
+		}
+	}
+
+	return false;
+}
+
+/**
+ * eficonfig_add_boot_selection_entry() - add boot option menu entry
+ *
+ * @efi_menu:	pointer to store the efimenu structure
+ * @boot_index:	boot option index to be added
+ * @selected:	pointer to store the selected boot option index
+ * Return:	status code
+ */
+static efi_status_t eficonfig_add_boot_selection_entry(struct efimenu *efi_menu,
+						       unsigned int boot_index,
+						       unsigned int *selected)
+{
+	char *buf, *p;
+	efi_status_t ret;
+	efi_uintn_t size;
+	void *load_option;
+	struct efi_load_option lo;
+	u16 varname[] = u"Boot####";
+	struct eficonfig_boot_selection_data *info;
+
+	efi_create_indexed_name(varname, sizeof(varname), "Boot", boot_index);
+	load_option = efi_get_var(varname, &efi_global_variable_guid, &size);
+	if (!load_option)
+		return EFI_SUCCESS;
+
+	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);
+		return ret;
+	}
+
+	if (size >= sizeof(efi_guid_t) &&
+	    !guidcmp(lo.optional_data, &efi_guid_bootmenu_auto_generated)) {
+		/*
+		 * auto generated entry has GUID in optional_data,
+		 * skip auto generated entry because it will be generated
+		 * again even if it is edited or deleted.
+		 */
+		free(load_option);
+		return EFI_SUCCESS;
+	}
+
+	info = calloc(1, sizeof(struct eficonfig_boot_selection_data));
+	if (!info) {
+		free(load_option);
+		return EFI_OUT_OF_RESOURCES;
+	}
+
+	buf = calloc(1, utf16_utf8_strlen(lo.label) + 1);
+	if (!buf) {
+		free(load_option);
+		free(info);
+		return EFI_OUT_OF_RESOURCES;
+	}
+	p = buf;
+	utf16_utf8_strcpy(&p, lo.label);
+	info->boot_index = boot_index;
+	info->selected = selected;
+	ret = append_entry(efi_menu, buf, eficonfig_process_boot_selected, info);
+	if (ret != EFI_SUCCESS) {
+		free(load_option);
+		free(info);
+		return ret;
+	}
+	free(load_option);
+
+	return EFI_SUCCESS;
+}
+
+/**
+ * eficonfig_show_boot_selection() - construct boot option menu entry
+ *
+ * @selected:	pointer to store the selected boot option index
+ * Return:	status code
+ */
+static efi_status_t eficonfig_show_boot_selection(unsigned int *selected)
+{
+	u32 i;
+	u16 *bootorder;
+	efi_status_t ret;
+	efi_uintn_t num, size;
+	struct efimenu *efi_menu;
+
+	efi_menu = calloc(1, sizeof(struct efimenu));
+	if (!efi_menu)
+		return EFI_OUT_OF_RESOURCES;
+
+	bootorder = efi_get_var(u"BootOrder", &efi_global_variable_guid, &size);
+
+	INIT_LIST_HEAD(&efi_menu->list);
+	num = size / sizeof(u16);
+	/* list the load option in the order of BootOrder variable */
+	for (i = 0; i < num; i++) {
+		ret = eficonfig_add_boot_selection_entry(efi_menu, bootorder[i], selected);
+		if (ret != EFI_SUCCESS)
+			goto out;
+
+		if (efi_menu->count >= EFICONFIG_ENTRY_NUM_MAX - 1)
+			break;
+	}
+
+	/* list the remaining load option not included in the BootOrder */
+	for (i = 0; i <= 0xFFFF; i++) {
+		/* If the index is included in the BootOrder, skip it */
+		if (search_bootorder(bootorder, num, i, NULL))
+			continue;
+
+		ret = eficonfig_add_boot_selection_entry(efi_menu, i, selected);
+		if (ret != EFI_SUCCESS)
+			goto out;
+
+		if (efi_menu->count >= EFICONFIG_ENTRY_NUM_MAX - 1)
+			break;
+	}
+
+	ret = append_quit_entry(efi_menu);
+	if (ret != EFI_SUCCESS)
+		goto out;
+
+	ret = eficonfig_process_common(efi_menu, "  ** Select Boot Option **");
+out:
+	eficonfig_destroy(efi_menu, true);
+
+	return ret;
+}
+
+/**
+ * eficonfig_process_edit_boot_option() - handler to edit boot option
+ *
+ * @data:	pointer to the data for each entry
+ * Return:	status code
+ */
+static efi_status_t eficonfig_process_edit_boot_option(void *data)
+{
+	efi_status_t ret;
+	efi_uintn_t size;
+	struct eficonfig_boot_option *bo = NULL;
+
+	while (1) {
+		unsigned int selected;
+		void *load_option;
+		u16 varname[] = u"Boot####";
+
+		ret = eficonfig_show_boot_selection(&selected);
+		if (ret != EFI_SUCCESS)
+			break;
+
+		bo = calloc(1, sizeof(struct eficonfig_boot_option));
+		if (!bo) {
+			ret = EFI_OUT_OF_RESOURCES;
+			goto out;
+		}
+
+		bo->boot_index = selected;
+		efi_create_indexed_name(varname, sizeof(varname), "Boot", selected);
+		load_option = efi_get_var(varname, &efi_global_variable_guid, &size);
+		if (!load_option) {
+			free(bo);
+			ret = EFI_NOT_FOUND;
+			goto out;
+		}
+
+		ret = eficonfig_edit_boot_option(varname, bo, load_option, size,
+						 "  ** Edit Boot Option ** ");
+
+		free(load_option);
+		free(bo);
+		if (ret != EFI_SUCCESS && ret != EFI_ABORTED)
+			break;
+	}
+out:
+	/* to stay the parent menu */
+	ret = (ret == EFI_ABORTED) ? EFI_NOT_READY : ret;
+
+	return ret;
+}
+
 /**
  * eficonfig_init() - do required initialization for eficonfig command
  *
@@ -1436,6 +1694,7 @@ static efi_status_t eficonfig_init(void)
 
 static const struct eficonfig_item maintenance_menu_items[] = {
 	{"Add Boot Option", eficonfig_process_add_boot_option},
+	{"Edit Boot Option", eficonfig_process_edit_boot_option},
 	{"Quit", eficonfig_process_quit},
 };
 
diff --git a/include/efi_config.h b/include/efi_config.h
index aaff5c7cc0..25f5e92bac 100644
--- a/include/efi_config.h
+++ b/include/efi_config.h
@@ -9,6 +9,7 @@
 #define _EFI_CONFIG_H
 
 #define EFICONFIG_ENTRY_NUM_MAX 99
+#define EFICONFIG_VOLUME_PATH_MAX 512
 #define EFICONFIG_FILE_PATH_MAX 512
 #define EFICONFIG_FILE_PATH_BUF_SIZE (EFICONFIG_FILE_PATH_MAX * sizeof(u16))
 
-- 
2.17.1


^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [PATCH v11 3/9] menu: add KEY_PLUS, KEY_MINUS and KEY_SPACE handling
  2022-08-17  9:36 [PATCH v11 0/9] enable menu-driven UEFI variable maintenance Masahisa Kojima
  2022-08-17  9:36 ` [PATCH v11 1/9] eficonfig: menu-driven addition of UEFI boot option Masahisa Kojima
  2022-08-17  9:36 ` [PATCH v11 2/9] eficonfig: add "Edit Boot Option" menu entry Masahisa Kojima
@ 2022-08-17  9:36 ` Masahisa Kojima
  2022-08-17  9:36 ` [PATCH v11 4/9] eficonfig: add "Change Boot Order" menu entry Masahisa Kojima
                   ` (5 subsequent siblings)
  8 siblings, 0 replies; 25+ messages in thread
From: Masahisa Kojima @ 2022-08-17  9:36 UTC (permalink / raw)
  To: u-boot
  Cc: Heinrich Schuchardt, Ilias Apalodimas, Simon Glass,
	Takahiro Akashi, Mark Kettenis, Masahisa Kojima

This is preparation to support menu-driven UEFI BootOrder
variable updated by KEY_PLUS, KEY_MINUS and KEY_SPACE.

Signed-off-by: Masahisa Kojima <masahisa.kojima@linaro.org>
Reviewed-by: Heinrich Schuchardt <xypron.glpk@gmx.de>
Reviewed-by: Ilias Apalodimas <ilias.apalodimas@linaro.org>
---
Changes in v11:
- add SPACE key handling

Newly created in v7

 common/menu.c  | 9 +++++++++
 include/menu.h | 3 +++
 2 files changed, 12 insertions(+)

diff --git a/common/menu.c b/common/menu.c
index 3e876b55b3..0d19601cf5 100644
--- a/common/menu.c
+++ b/common/menu.c
@@ -548,4 +548,13 @@ void bootmenu_loop(struct bootmenu_data *menu,
 	/* ^C was pressed */
 	if (c == 0x3)
 		*key = KEY_QUIT;
+
+	if (c == '+')
+		*key = KEY_PLUS;
+
+	if (c == '-')
+		*key = KEY_MINUS;
+
+	if (c == ' ')
+		*key = KEY_SPACE;
 }
diff --git a/include/menu.h b/include/menu.h
index e74616cae8..702aacb170 100644
--- a/include/menu.h
+++ b/include/menu.h
@@ -48,6 +48,9 @@ enum bootmenu_key {
 	KEY_DOWN,
 	KEY_SELECT,
 	KEY_QUIT,
+	KEY_PLUS,
+	KEY_MINUS,
+	KEY_SPACE,
 };
 
 void bootmenu_autoboot_loop(struct bootmenu_data *menu,
-- 
2.17.1


^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [PATCH v11 4/9] eficonfig: add "Change Boot Order" menu entry
  2022-08-17  9:36 [PATCH v11 0/9] enable menu-driven UEFI variable maintenance Masahisa Kojima
                   ` (2 preceding siblings ...)
  2022-08-17  9:36 ` [PATCH v11 3/9] menu: add KEY_PLUS, KEY_MINUS and KEY_SPACE handling Masahisa Kojima
@ 2022-08-17  9:36 ` Masahisa Kojima
  2022-08-18  6:17   ` Heinrich Schuchardt
  2022-08-17  9:36 ` [PATCH v11 5/9] eficonfig: add "Delete Boot Option" " Masahisa Kojima
                   ` (4 subsequent siblings)
  8 siblings, 1 reply; 25+ messages in thread
From: Masahisa Kojima @ 2022-08-17  9:36 UTC (permalink / raw)
  To: u-boot
  Cc: Heinrich Schuchardt, Ilias Apalodimas, Simon Glass,
	Takahiro Akashi, Mark Kettenis, Masahisa Kojima

This commit adds the menu entry to update UEFI BootOrder variable.
User moves the entry with UP/DOWN key, changes the order
with PLUS/MINUS key, press SPACE to activate or deactivate
the entry, then finalizes the order by ENTER key.
If the entry is activated, the boot index is added into the
BootOrder variable in the order of the list.

The U-Boot menu framework is well designed for static menu,
this commit implements the own menu display and key handling
for dynamically change the order of menu entry.

Signed-off-by: Masahisa Kojima <masahisa.kojima@linaro.org>
---
Changes in v11:
- remove BootOrder variable dependency
- use ANSI_CURSOR_POSITION and ANSI_CLEAR_LINE instead of printf("\n")
  since current eficonfig implementation does not handle console size correctly.
  printf("\n") at the outside of console size breaks the console output.
- add KEY_SPACE to toggle the boot option active status

No update since v9

Changes in v9:
- add function comment

Changes in v8:
- add "Save" and "Quit" entries

Changes in v7:
- use UP/DOWN and PLUS/MINUS key to change to order

no update in v6:

 cmd/eficonfig.c | 346 ++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 346 insertions(+)

diff --git a/cmd/eficonfig.c b/cmd/eficonfig.c
index 938d46374e..f6152495c8 100644
--- a/cmd/eficonfig.c
+++ b/cmd/eficonfig.c
@@ -91,6 +91,23 @@ struct eficonfig_boot_selection_data {
 	int *selected;
 };
 
+/**
+ * struct eficonfig_boot_order - structure to be used to update BootOrder variable
+ *
+ * @num:		index in the menu entry
+ * @description:	pointer to the description string
+ * @boot_index:		boot option index
+ * @active:		flag to include the boot option into BootOrder variable
+ * @list:		list structure
+ */
+struct eficonfig_boot_order {
+	u32 num;
+	u16 *description;
+	u32 boot_index;
+	bool active;
+	struct list_head list;
+};
+
 /**
  * eficonfig_print_msg() - print message
  *
@@ -1665,6 +1682,334 @@ out:
 	return ret;
 }
 
+/**
+ * eficonfig_display_change_boot_order() - display the BootOrder list
+ *
+ * @efi_menu:	pointer to the efimenu structure
+ * Return:	status code
+ */
+static void eficonfig_display_change_boot_order(struct efimenu *efi_menu)
+{
+	bool reverse;
+	struct list_head *pos, *n;
+	struct eficonfig_boot_order *entry;
+
+	printf(ANSI_CLEAR_CONSOLE ANSI_CURSOR_POSITION
+	       "\n  ** Change Boot Order **\n"
+	       ANSI_CURSOR_POSITION
+	       "  Press UP/DOWN to move, +/- to change order"
+	       ANSI_CURSOR_POSITION
+	       "  Press SPACE to activate or deactivate the entry"
+	       ANSI_CURSOR_POSITION
+	       "  Select [Save] to complete, ESC/CTRL+C to quit"
+	       ANSI_CURSOR_POSITION ANSI_CLEAR_LINE,
+	       1, 1, efi_menu->count + 5, 1, efi_menu->count + 6, 1,
+	       efi_menu->count + 7, 1,  efi_menu->count + 8, 1);
+
+	/* draw boot option list */
+	list_for_each_safe(pos, n, &efi_menu->list) {
+		entry = list_entry(pos, struct eficonfig_boot_order, list);
+		reverse = (entry->num == efi_menu->active);
+
+		printf(ANSI_CURSOR_POSITION, entry->num + 4, 7);
+
+		if (reverse)
+			puts(ANSI_COLOR_REVERSE);
+
+		if (entry->num < efi_menu->count - 2) {
+			if (entry->active)
+				printf("[*]  ");
+			else
+				printf("[ ]  ");
+		}
+
+		printf("%ls", entry->description);
+
+		if (reverse)
+			puts(ANSI_COLOR_RESET);
+	}
+}
+
+/**
+ * eficonfig_choice_change_boot_order() - handle the BootOrder update
+ *
+ * @efi_menu:	pointer to the efimenu structure
+ * Return:	status code
+ */
+static efi_status_t eficonfig_choice_change_boot_order(struct efimenu *efi_menu)
+{
+	int esc = 0;
+	struct list_head *pos, *n;
+	struct eficonfig_boot_order *tmp;
+	enum bootmenu_key key = KEY_NONE;
+	struct eficonfig_boot_order *entry;
+
+	while (1) {
+		bootmenu_loop(NULL, &key, &esc);
+
+		switch (key) {
+		case KEY_PLUS:
+			if (efi_menu->active > 0) {
+				list_for_each_safe(pos, n, &efi_menu->list) {
+					entry = list_entry(pos, struct eficonfig_boot_order, list);
+					if (entry->num == efi_menu->active)
+						break;
+				}
+				tmp = list_entry(pos->prev, struct eficonfig_boot_order, list);
+				entry->num--;
+				tmp->num++;
+				list_del(&tmp->list);
+				list_add(&tmp->list, &entry->list);
+			}
+			fallthrough;
+		case KEY_UP:
+			if (efi_menu->active > 0)
+				--efi_menu->active;
+			return EFI_NOT_READY;
+		case KEY_MINUS:
+			if (efi_menu->active < efi_menu->count - 3) {
+				list_for_each_safe(pos, n, &efi_menu->list) {
+					entry = list_entry(pos, struct eficonfig_boot_order, list);
+					if (entry->num == efi_menu->active)
+						break;
+				}
+				tmp = list_entry(pos->next, struct eficonfig_boot_order, list);
+				entry->num++;
+				tmp->num--;
+				list_del(&entry->list);
+				list_add(&entry->list, &tmp->list);
+
+				++efi_menu->active;
+			}
+			return EFI_NOT_READY;
+		case KEY_DOWN:
+			if (efi_menu->active < efi_menu->count - 1)
+				++efi_menu->active;
+			return EFI_NOT_READY;
+		case KEY_SELECT:
+			/* "Save" */
+			if (efi_menu->active == efi_menu->count - 2)
+				return EFI_SUCCESS;
+
+			/* "Quit" */
+			if (efi_menu->active == efi_menu->count - 1)
+				return EFI_ABORTED;
+
+			break;
+		case KEY_SPACE:
+			if (efi_menu->active < efi_menu->count - 2) {
+				list_for_each_safe(pos, n, &efi_menu->list) {
+					entry = list_entry(pos, struct eficonfig_boot_order, list);
+					if (entry->num == efi_menu->active) {
+						entry->active = entry->active ? false : true;
+						return EFI_NOT_READY;
+					}
+				}
+			}
+			break;
+		case KEY_QUIT:
+			return EFI_ABORTED;
+		default:
+			break;
+		}
+	}
+}
+
+/**
+ * eficonfig_add_change_boot_order_entry() - add boot order entry
+ *
+ * @efi_menu:	pointer to the efimenu structure
+ * @boot_index:	boot option index to be added
+ * @active:	flag to include the boot option into BootOrder
+ * Return:	status code
+ */
+static efi_status_t eficonfig_add_change_boot_order_entry(struct efimenu *efi_menu,
+							  u32 boot_index, bool active)
+{
+	efi_status_t ret;
+	efi_uintn_t size;
+	void *load_option;
+	struct efi_load_option lo;
+	u16 varname[] = u"Boot####";
+	struct eficonfig_boot_order *entry;
+
+	efi_create_indexed_name(varname, sizeof(varname), "Boot", boot_index);
+	load_option = efi_get_var(varname, &efi_global_variable_guid, &size);
+	if (!load_option)
+		return EFI_SUCCESS;
+
+	ret = efi_deserialize_load_option(&lo, load_option, &size);
+	if (ret != EFI_SUCCESS) {
+		free(load_option);
+		return ret;
+	}
+
+	entry = calloc(1, sizeof(struct eficonfig_boot_order));
+	if (!entry) {
+		free(load_option);
+		return EFI_OUT_OF_RESOURCES;
+	}
+
+	entry->description = u16_strdup(lo.label);
+	if (!entry->description) {
+		free(load_option);
+		free(entry);
+		return EFI_OUT_OF_RESOURCES;
+	}
+	entry->num = efi_menu->count++;
+	entry->boot_index = boot_index;
+	entry->active = active;
+	list_add_tail(&entry->list, &efi_menu->list);
+
+	free(load_option);
+
+	return EFI_SUCCESS;
+}
+
+/**
+ * eficonfig_create_change_boot_order_entry() - create boot order entry
+ *
+ * @efi_menu:	pointer to the efimenu structure
+ * @bootorder:	pointer to the BootOrder variable
+ * @num:	number of BootOrder entry
+ * Return:	status code
+ */
+static efi_status_t eficonfig_create_change_boot_order_entry(struct efimenu *efi_menu,
+							     u16 *bootorder, efi_uintn_t num)
+{
+	u32 i;
+	efi_status_t ret;
+	struct eficonfig_boot_order *entry;
+
+	/* list the load option in the order of BootOrder variable */
+	for (i = 0; i < num; i++) {
+		if (efi_menu->count >= EFICONFIG_ENTRY_NUM_MAX - 2)
+			break;
+
+		ret = eficonfig_add_change_boot_order_entry(efi_menu, bootorder[i], true);
+		if (ret != EFI_SUCCESS)
+			goto out;
+	}
+
+	/* list the remaining load option not included in the BootOrder */
+	for (i = 0; i < 0xFFFF; i++) {
+		if (efi_menu->count >= EFICONFIG_ENTRY_NUM_MAX - 2)
+			break;
+
+		/* If the index is included in the BootOrder, skip it */
+		if (search_bootorder(bootorder, num, i, NULL))
+			continue;
+
+		ret = eficonfig_add_change_boot_order_entry(efi_menu, i, false);
+		if (ret != EFI_SUCCESS)
+			goto out;
+	}
+
+	/* add "Save" and "Quit" entries */
+	entry = calloc(1, sizeof(struct eficonfig_boot_order));
+	if (!entry)
+		goto out;
+
+	entry->num = efi_menu->count++;
+	entry->description = u16_strdup(u"Save");
+	list_add_tail(&entry->list, &efi_menu->list);
+
+	entry = calloc(1, sizeof(struct eficonfig_boot_order));
+	if (!entry)
+		goto out;
+
+	entry->num = efi_menu->count++;
+	entry->description = u16_strdup(u"Quit");
+	list_add_tail(&entry->list, &efi_menu->list);
+
+	efi_menu->active = 0;
+
+	return EFI_SUCCESS;
+out:
+	return EFI_OUT_OF_RESOURCES;
+}
+
+/**
+ * eficonfig_process_change_boot_order() - handler to change boot order
+ *
+ * @data:	pointer to the data for each entry
+ * Return:	status code
+ */
+static efi_status_t eficonfig_process_change_boot_order(void *data)
+{
+	u32 count;
+	u16 *bootorder;
+	efi_status_t ret;
+	efi_uintn_t num, size;
+	struct list_head *pos, *n;
+	struct eficonfig_boot_order *entry;
+	struct efimenu *efi_menu;
+
+	efi_menu = calloc(1, sizeof(struct efimenu));
+	if (!efi_menu)
+		return EFI_OUT_OF_RESOURCES;
+
+	bootorder = efi_get_var(u"BootOrder", &efi_global_variable_guid, &size);
+
+	INIT_LIST_HEAD(&efi_menu->list);
+	num = size / sizeof(u16);
+	ret = eficonfig_create_change_boot_order_entry(efi_menu, bootorder, num);
+	if (ret != EFI_SUCCESS)
+		goto out;
+
+	while (1) {
+		eficonfig_display_change_boot_order(efi_menu);
+
+		ret = eficonfig_choice_change_boot_order(efi_menu);
+		if (ret == EFI_SUCCESS) {
+			u16 *new_bootorder;
+
+			new_bootorder = calloc(1, (efi_menu->count - 2) * sizeof(u16));
+			if (!new_bootorder) {
+				ret = EFI_OUT_OF_RESOURCES;
+				goto out;
+			}
+
+			/* create new BootOrder  */
+			count = 0;
+			list_for_each_safe(pos, n, &efi_menu->list) {
+				entry = list_entry(pos, struct eficonfig_boot_order, list);
+				if (entry->active)
+					new_bootorder[count++] = entry->boot_index;
+			}
+
+			size = count * 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, new_bootorder, false);
+
+			free(new_bootorder);
+			goto out;
+		} else if (ret == EFI_NOT_READY) {
+			continue;
+		} else {
+			goto out;
+		}
+	}
+out:
+	list_for_each_safe(pos, n, &efi_menu->list) {
+		entry = list_entry(pos, struct eficonfig_boot_order, list);
+		list_del(&entry->list);
+		free(entry->description);
+		free(entry);
+	}
+
+	free(bootorder);
+	free(efi_menu);
+
+	/* to stay the parent menu */
+	ret = (ret == EFI_ABORTED) ? EFI_NOT_READY : ret;
+
+	return ret;
+}
+
 /**
  * eficonfig_init() - do required initialization for eficonfig command
  *
@@ -1695,6 +2040,7 @@ static efi_status_t eficonfig_init(void)
 static const struct eficonfig_item maintenance_menu_items[] = {
 	{"Add Boot Option", eficonfig_process_add_boot_option},
 	{"Edit Boot Option", eficonfig_process_edit_boot_option},
+	{"Change Boot Order", eficonfig_process_change_boot_order},
 	{"Quit", eficonfig_process_quit},
 };
 
-- 
2.17.1


^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [PATCH v11 5/9] eficonfig: add "Delete Boot Option" menu entry
  2022-08-17  9:36 [PATCH v11 0/9] enable menu-driven UEFI variable maintenance Masahisa Kojima
                   ` (3 preceding siblings ...)
  2022-08-17  9:36 ` [PATCH v11 4/9] eficonfig: add "Change Boot Order" menu entry Masahisa Kojima
@ 2022-08-17  9:36 ` Masahisa Kojima
  2022-08-17  9:36 ` [PATCH v11 6/9] bootmenu: add removable media entries Masahisa Kojima
                   ` (3 subsequent siblings)
  8 siblings, 0 replies; 25+ messages in thread
From: Masahisa Kojima @ 2022-08-17  9:36 UTC (permalink / raw)
  To: u-boot
  Cc: Heinrich Schuchardt, Ilias Apalodimas, Simon Glass,
	Takahiro Akashi, Mark Kettenis, Masahisa Kojima

This commit adds the menu entry to delete the UEFI boot option.
User moves the entry with UP/DOWN key, changes, then presses
ENTER key to delete the selected boot option.

Signed-off-by: Masahisa Kojima <masahisa.kojima@linaro.org>
---
Changes in v11:
- update function interface to show boot selection menu
- support to delete the load option is not included in BootOrder

No update since v9

Changes in v9:
- add function comment

Changes in v8:
- function and structure prefix is changed to "eficonfig"

Changes in v7:
- to stay the boot order list after user delete the entry

no update in v6:

changes in v5:

 cmd/eficonfig.c | 71 +++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 71 insertions(+)

diff --git a/cmd/eficonfig.c b/cmd/eficonfig.c
index f6152495c8..6e39c0cd4d 100644
--- a/cmd/eficonfig.c
+++ b/cmd/eficonfig.c
@@ -2010,6 +2010,76 @@ out:
 	return ret;
 }
 
+/**
+ * delete_boot_option() - delete selected boot option
+ *
+ * @boot_index:	boot option index to delete
+ * Return:	status code
+ */
+static efi_status_t delete_boot_option(u16 boot_index)
+{
+	u16 *bootorder;
+	u16 varname[9];
+	efi_status_t ret;
+	unsigned int index;
+	efi_uintn_t num, size;
+
+	efi_create_indexed_name(varname, sizeof(varname),
+				"Boot", boot_index);
+	ret = efi_set_variable_int(varname, &efi_global_variable_guid,
+				   0, 0, NULL, false);
+	if (ret != EFI_SUCCESS) {
+		log_err("delete boot option(%ls) failed\n", varname);
+		return ret;
+	}
+
+	/* update BootOrder if necessary */
+	bootorder = efi_get_var(u"BootOrder", &efi_global_variable_guid, &size);
+	if (!bootorder)
+		return EFI_SUCCESS;
+
+	num = size / sizeof(u16);
+	if (!search_bootorder(bootorder, num, boot_index, &index))
+		return EFI_SUCCESS;
+
+	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;
+}
+
+/**
+ * eficonfig_process_delete_boot_option() - handler to delete boot option
+ *
+ * @data:	pointer to the data for each entry
+ * Return:	status code
+ */
+static efi_status_t eficonfig_process_delete_boot_option(void *data)
+{
+	efi_status_t ret;
+	unsigned int selected;
+
+	while (1) {
+		ret = eficonfig_show_boot_selection(&selected);
+		if (ret == EFI_SUCCESS)
+			ret = delete_boot_option(selected);
+
+		if (ret != EFI_SUCCESS)
+			break;
+	}
+
+	/* to stay the parent menu */
+	ret = (ret == EFI_ABORTED) ? EFI_NOT_READY : ret;
+
+	return ret;
+}
+
 /**
  * eficonfig_init() - do required initialization for eficonfig command
  *
@@ -2041,6 +2111,7 @@ static const struct eficonfig_item maintenance_menu_items[] = {
 	{"Add Boot Option", eficonfig_process_add_boot_option},
 	{"Edit Boot Option", eficonfig_process_edit_boot_option},
 	{"Change Boot Order", eficonfig_process_change_boot_order},
+	{"Delete Boot Option", eficonfig_process_delete_boot_option},
 	{"Quit", eficonfig_process_quit},
 };
 
-- 
2.17.1


^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [PATCH v11 6/9] bootmenu: add removable media entries
  2022-08-17  9:36 [PATCH v11 0/9] enable menu-driven UEFI variable maintenance Masahisa Kojima
                   ` (4 preceding siblings ...)
  2022-08-17  9:36 ` [PATCH v11 5/9] eficonfig: add "Delete Boot Option" " Masahisa Kojima
@ 2022-08-17  9:36 ` Masahisa Kojima
  2022-08-19  1:31   ` Takahiro Akashi
  2022-08-17  9:36 ` [PATCH v11 7/9] doc:bootmenu: add description for UEFI boot support Masahisa Kojima
                   ` (2 subsequent siblings)
  8 siblings, 1 reply; 25+ messages in thread
From: Masahisa Kojima @ 2022-08-17  9:36 UTC (permalink / raw)
  To: u-boot
  Cc: Heinrich Schuchardt, Ilias Apalodimas, Simon Glass,
	Takahiro Akashi, Mark Kettenis, Masahisa Kojima

UEFI specification requires booting from removal media using
a architecture-specific default image name such as BOOTAA64.EFI.
This commit adds the removable media entries into bootmenu,
so that user can select the removable media and boot with
default image.

The bootmenu automatically enumerates the possible bootable
media devices supporting EFI_SIMPLE_FILE_SYSTEM_PROTOCOL,
add it as new UEFI boot option(BOOT####) and update BootOrder
variable. This automatically generated UEFI boot option
has the dedicated guid in the optional_data to distinguish it from
the UEFI boot option user adds manually. This optional_data is
removed when the efi bootmgr loads the selected UEFI boot option.

This commit also provides the BOOT#### variable maintenance feature.
Depending on the system hardware setup, some devices
may not exist at a later system boot, so bootmenu checks the
available device in each bootmenu invocation and automatically
removes the BOOT#### variable corrensponding to the non-existent
media device.

Signed-off-by: Masahisa Kojima <masahisa.kojima@linaro.org>
---
Changes in v11:
- update delete_boot_option() parameter

Changes in v10:
- add function comment
- devname dynamic allocation removes, allocate in stack
- delete BOOT#### when updating BootOrder fails

Changes in v9:
- update efi_disk_get_device_name() parameter to pass efi_handle_t
- add function comment

Changes in v8:
- function and structure prefix is changed to "eficonfig"

Changes in v7:
- rename prepare_media_device_entry() to generate_media_device_boot_option()

Changes in v6:
- optional_data size is changed to 16bytes
- check the load option size before comparison
- remove guid included in optional_data of auto generated
  entry when loading

Changes in v5:
- Return EFI_SUCCESS if there is no BootOrder defined
- correctly handle the case if no removable device found
- use guid to identify the automatically generated entry by bootmenu

 cmd/bootmenu.c               | 106 +++++++++++++++++++++++++--
 cmd/eficonfig.c              | 135 +++++++++++++++++++++++++++++++++++
 include/efi_loader.h         |  20 ++++++
 lib/efi_loader/efi_bootmgr.c |   4 ++
 4 files changed, 260 insertions(+), 5 deletions(-)

diff --git a/cmd/bootmenu.c b/cmd/bootmenu.c
index 704d36debe..04df41a0cb 100644
--- a/cmd/bootmenu.c
+++ b/cmd/bootmenu.c
@@ -220,7 +220,93 @@ static int prepare_bootmenu_entry(struct bootmenu_data *menu,
 	return 1;
 }
 
-#if (CONFIG_IS_ENABLED(CMD_BOOTEFI_BOOTMGR))
+#if (CONFIG_IS_ENABLED(CMD_BOOTEFI_BOOTMGR)) && (CONFIG_IS_ENABLED(CMD_EFICONFIG))
+/**
+ * generate_media_device_boot_option() - generate the media device boot option
+ *
+ * This function enumerates all devices supporting EFI_SIMPLE_FILE_SYSTEM_PROTOCOL
+ * and generate the bootmenu entries.
+ * This function also provide the BOOT#### variable maintenance for
+ * the media device entries.
+ *   - Automatically create the BOOT#### variable for the newly detected device,
+ *     this BOOT#### variable is distinguished by the special GUID
+ *     stored in the EFI_LOAD_OPTION.optional_data
+ *   - If the device is not attached to the system, the associated BOOT#### variable
+ *     is automatically deleted.
+ *
+ * Return:	status code
+ */
+static efi_status_t generate_media_device_boot_option(void)
+{
+	u32 i;
+	efi_status_t ret;
+	efi_uintn_t count;
+	efi_handle_t *volume_handles = NULL;
+	struct eficonfig_media_boot_option *opt = NULL;
+
+	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;
+
+	opt = calloc(count, sizeof(struct eficonfig_media_boot_option));
+	if (!opt)
+		goto out;
+
+	/* enumerate all devices supporting EFI_SIMPLE_FILE_SYSTEM_PROTOCOL */
+	ret = eficonfig_enumerate_boot_option(opt, volume_handles, count);
+	if (ret != EFI_SUCCESS)
+		goto out;
+
+	/*
+	 * System hardware configuration may vary depending on the user setup.
+	 * The boot option is automatically added by the bootmenu.
+	 * If the device is not attached to the system, the boot option needs
+	 * to be deleted.
+	 */
+	ret = eficonfig_delete_invalid_boot_option(opt, count);
+	if (ret != EFI_SUCCESS)
+		goto out;
+
+	/* add non-existent boot option */
+	for (i = 0; i < count; i++) {
+		u32 boot_index;
+		u16 var_name[9];
+
+		if (!opt[i].exist) {
+			ret = eficonfig_get_unused_bootoption(var_name, sizeof(var_name),
+							      &boot_index);
+			if (ret != EFI_SUCCESS)
+				goto out;
+
+			ret = efi_set_variable_int(var_name, &efi_global_variable_guid,
+						   EFI_VARIABLE_NON_VOLATILE |
+						   EFI_VARIABLE_BOOTSERVICE_ACCESS |
+						   EFI_VARIABLE_RUNTIME_ACCESS,
+						   opt[i].size, opt[i].lo, false);
+			if (ret != EFI_SUCCESS)
+				goto out;
+
+			ret = eficonfig_append_bootorder(boot_index);
+			if (ret != EFI_SUCCESS) {
+				efi_set_variable_int(var_name, &efi_global_variable_guid,
+						     0, 0, NULL, false);
+				goto out;
+			}
+		}
+	}
+
+out:
+	if (opt) {
+		for (i = 0; i < count; i++)
+			free(opt[i].lo);
+	}
+	free(opt);
+	efi_free_pool(volume_handles);
+
+	return ret;
+}
+
 /**
  * prepare_uefi_bootorder_entry() - generate the uefi bootmenu entries
  *
@@ -340,11 +426,21 @@ static struct bootmenu_data *bootmenu_create(int delay)
 	if (ret < 0)
 		goto cleanup;
 
-#if (CONFIG_IS_ENABLED(CMD_BOOTEFI_BOOTMGR))
+#if (CONFIG_IS_ENABLED(CMD_BOOTEFI_BOOTMGR)) && (CONFIG_IS_ENABLED(CMD_EFICONFIG))
 	if (i < MAX_COUNT - 1) {
-			ret = prepare_uefi_bootorder_entry(menu, &iter, &i);
-			if (ret < 0 && ret != -ENOENT)
-				goto cleanup;
+		efi_status_t efi_ret;
+
+		/*
+		 * UEFI specification requires booting from removal media using
+		 * a architecture-specific default image name such as BOOTAA64.EFI.
+		 */
+		efi_ret = generate_media_device_boot_option();
+		if (efi_ret != EFI_SUCCESS && efi_ret != EFI_NOT_FOUND)
+			goto cleanup;
+
+		ret = prepare_uefi_bootorder_entry(menu, &iter, &i);
+		if (ret < 0 && ret != -ENOENT)
+			goto cleanup;
 	}
 #endif
 
diff --git a/cmd/eficonfig.c b/cmd/eficonfig.c
index 6e39c0cd4d..c7f55c62fb 100644
--- a/cmd/eficonfig.c
+++ b/cmd/eficonfig.c
@@ -2080,6 +2080,141 @@ static efi_status_t eficonfig_process_delete_boot_option(void *data)
 	return ret;
 }
 
+/**
+ * eficonfig_enumerate_boot_option() - enumerate the possible bootable media
+ *
+ * @opt:		pointer to the media boot option structure
+ * @volume_handles:	pointer to the efi handles
+ * @count:		number of efi handle
+ * Return:		status code
+ */
+efi_status_t eficonfig_enumerate_boot_option(struct eficonfig_media_boot_option *opt,
+					     efi_handle_t *volume_handles, efi_status_t count)
+{
+	u32 i;
+	struct efi_handler *handler;
+	efi_status_t ret = EFI_SUCCESS;
+
+	for (i = 0; i < count; i++) {
+		u16 *p;
+		u16 dev_name[BOOTMENU_DEVICE_NAME_MAX];
+		char *optional_data;
+		struct efi_load_option lo;
+		char buf[BOOTMENU_DEVICE_NAME_MAX];
+		struct efi_device_path *device_path;
+
+		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_disk_get_device_name(volume_handles[i], buf, BOOTMENU_DEVICE_NAME_MAX);
+		if (ret != EFI_SUCCESS)
+			continue;
+
+		p = dev_name;
+		utf8_utf16_strncpy(&p, buf, strlen(buf));
+
+		lo.label = dev_name;
+		lo.attributes = LOAD_OPTION_ACTIVE;
+		lo.file_path = device_path;
+		lo.file_path_length = efi_dp_size(device_path) + sizeof(END);
+		/*
+		 * Set the dedicated guid to optional_data, it is used to identify
+		 * the boot option that automatically generated by the bootmenu.
+		 * efi_serialize_load_option() expects optional_data is null-terminated
+		 * utf8 string, so set the "1234567" string to allocate enough space
+		 * to store guid, instead of realloc the load_option.
+		 */
+		lo.optional_data = "1234567";
+		opt[i].size = efi_serialize_load_option(&lo, (u8 **)&opt[i].lo);
+		if (!opt[i].size) {
+			ret = EFI_OUT_OF_RESOURCES;
+			free(dev_name);
+			goto out;
+		}
+		/* set the guid */
+		optional_data = (char *)opt[i].lo + (opt[i].size - u16_strsize(u"1234567"));
+		memcpy(optional_data, &efi_guid_bootmenu_auto_generated, sizeof(efi_guid_t));
+	}
+
+out:
+	return ret;
+}
+
+/**
+ * eficonfig_delete_invalid_boot_option() - delete non-existing boot option
+ *
+ * @opt:		pointer to the media boot option structure
+ * @count:		number of media boot option structure
+ * Return:		status code
+ */
+efi_status_t eficonfig_delete_invalid_boot_option(struct eficonfig_media_boot_option *opt,
+						  efi_status_t count)
+{
+	u16 *bootorder;
+	u32 i, j;
+	efi_status_t ret;
+	efi_uintn_t num, size, bootorder_size;
+	void *load_option;
+	struct efi_load_option lo;
+	u16 varname[] = u"Boot####";
+
+	bootorder = efi_get_var(u"BootOrder", &efi_global_variable_guid, &bootorder_size);
+	if (!bootorder)
+		return EFI_SUCCESS; /* BootOrder is not defined, nothing to do */
+
+	num = bootorder_size / sizeof(u16);
+	for (i = 0; i < num;) {
+		efi_uintn_t tmp;
+
+		efi_create_indexed_name(varname, sizeof(varname),
+					"Boot", bootorder[i]);
+		load_option = efi_get_var(varname, &efi_global_variable_guid, &size);
+		if (!load_option)
+			goto next;
+
+		tmp = size;
+		ret = efi_deserialize_load_option(&lo, load_option, &size);
+		if (ret != EFI_SUCCESS)
+			goto next;
+
+		if (size >= sizeof(efi_guid_bootmenu_auto_generated)) {
+			if (guidcmp(lo.optional_data, &efi_guid_bootmenu_auto_generated) == 0) {
+				for (j = 0; j < count; j++) {
+					if (opt[j].size == tmp &&
+					    memcmp(opt[j].lo, load_option, tmp) == 0) {
+						opt[j].exist = true;
+						break;
+					}
+				}
+
+				if (j == count) {
+					ret = delete_boot_option(bootorder[i]);
+					if (ret != EFI_SUCCESS) {
+						free(load_option);
+						goto out;
+					}
+
+					num--;
+					bootorder_size -= sizeof(u16);
+					free(load_option);
+					continue;
+				}
+			}
+		}
+next:
+		free(load_option);
+		i++;
+	}
+
+out:
+	return ret;
+}
+
 /**
  * eficonfig_init() - do required initialization for eficonfig command
  *
diff --git a/include/efi_loader.h b/include/efi_loader.h
index 49e7d1e613..a5a0448fa0 100644
--- a/include/efi_loader.h
+++ b/include/efi_loader.h
@@ -955,6 +955,22 @@ struct efi_signature_store {
 struct x509_certificate;
 struct pkcs7_message;
 
+/**
+ * struct eficonfig_media_boot_option - boot option for (removable) media device
+ *
+ * This structure is used to enumerate possible boot option
+ *
+ * @lo:		Serialized load option data
+ * @size:	Size of serialized load option data
+ * @exist:	Flag to indicate the load option already exists
+ *		in Non-volatile load option
+ */
+struct eficonfig_media_boot_option {
+	void *lo;
+	efi_uintn_t size;
+	bool exist;
+};
+
 bool efi_hash_regions(struct image_region *regs, int count,
 		      void **hash, const char *hash_algo, int *len);
 bool efi_signature_lookup_digest(struct efi_image_regions *regs,
@@ -1104,6 +1120,10 @@ efi_status_t efi_console_get_u16_string
 efi_status_t eficonfig_get_unused_bootoption(u16 *buf,
 					     efi_uintn_t buf_size, u32 *index);
 efi_status_t eficonfig_append_bootorder(u16 index);
+efi_status_t eficonfig_enumerate_boot_option(struct eficonfig_media_boot_option *opt,
+					     efi_handle_t *volume_handles, efi_status_t count);
+efi_status_t eficonfig_delete_invalid_boot_option(struct eficonfig_media_boot_option *opt,
+						  efi_status_t count);
 
 efi_status_t efi_disk_get_device_name(const efi_handle_t handle, char *buf, int size);
 
diff --git a/lib/efi_loader/efi_bootmgr.c b/lib/efi_loader/efi_bootmgr.c
index ede9116b3c..4b24b41047 100644
--- a/lib/efi_loader/efi_bootmgr.c
+++ b/lib/efi_loader/efi_bootmgr.c
@@ -246,6 +246,10 @@ static efi_status_t try_load_entry(u16 n, efi_handle_t *handle,
 	}
 
 	/* Set load options */
+	if (size >= sizeof(efi_guid_t) &&
+	    !guidcmp(lo.optional_data, &efi_guid_bootmenu_auto_generated))
+		size = 0;
+
 	if (size) {
 		*load_options = malloc(size);
 		if (!*load_options) {
-- 
2.17.1


^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [PATCH v11 7/9] doc:bootmenu: add description for UEFI boot support
  2022-08-17  9:36 [PATCH v11 0/9] enable menu-driven UEFI variable maintenance Masahisa Kojima
                   ` (5 preceding siblings ...)
  2022-08-17  9:36 ` [PATCH v11 6/9] bootmenu: add removable media entries Masahisa Kojima
@ 2022-08-17  9:36 ` Masahisa Kojima
  2022-08-17  9:36 ` [PATCH v11 8/9] doc:eficonfig: add documentation for eficonfig command Masahisa Kojima
  2022-08-17  9:36 ` [PATCH v11 9/9] test: unit test for eficonfig Masahisa Kojima
  8 siblings, 0 replies; 25+ messages in thread
From: Masahisa Kojima @ 2022-08-17  9:36 UTC (permalink / raw)
  To: u-boot
  Cc: Heinrich Schuchardt, Ilias Apalodimas, Simon Glass,
	Takahiro Akashi, Mark Kettenis, Masahisa Kojima, Bin Meng

The bootmenu enumerates the UEFI boot options
for boot device selection.
This commit adds the description how the UEFI boot work
in bootmenu. This commit also adds "Synopsis", "Description"
and "Configuration" sections to follow the U-Boot command
documentation format.

Signed-off-by: Masahisa Kojima <masahisa.kojima@linaro.org>
Reviewed-by: Ilias Apalodimas <ilias.apalodimas@linaro.org>
---
No update since v10

Changes in v10:
- fix typos

Changes in v7:
- update the description what bootmenu do for uefi-related boot menu
- add default behavior when user exits from bootmenu

Changes in v6:
- remove distro boot related contents because the distro boot
support in bootmenu is dropped
- update uefi entry example
- add [delay] argument of bootmenu command
- add description to enable uefi boot entry

Changes in v5:
- follow the cmd documentation format same as other command, add "Synopsis",
  "Description" add "Configuration" sections

Newly created in v4

 doc/usage/cmd/bootmenu.rst | 74 ++++++++++++++++++++++++++++++++++++++
 1 file changed, 74 insertions(+)

diff --git a/doc/usage/cmd/bootmenu.rst b/doc/usage/cmd/bootmenu.rst
index 9430f8c9aa..cb3c8d2f93 100644
--- a/doc/usage/cmd/bootmenu.rst
+++ b/doc/usage/cmd/bootmenu.rst
@@ -4,6 +4,15 @@
 bootmenu command
 ================
 
+Synopsis
+--------
+::
+
+    bootmenu [delay]
+
+Description
+-----------
+
 The "bootmenu" command uses U-Boot menu interfaces and provides
 a simple mechanism for creating menus with different boot items.
 The cursor keys "Up" and "Down" are used for navigation through
@@ -79,6 +88,55 @@ The above example will be rendered as below::
 The selected menu entry will be highlighted - it will have inverted
 background and text colors.
 
+UEFI boot variable enumeration
+''''''''''''''''''''''''''''''
+If enabled, the bootmenu command will automatically generate and add
+UEFI-related boot menu entries for the following items.
+
+ * possible bootable media with default file names
+ * user-defined UEFI boot options
+
+The bootmenu automatically enumerates the possible bootable
+media devices supporting EFI_SIMPLE_FILE_SYSTEM_PROTOCOL.
+This auto generated entry is named as "<interface> <devnum>:<part>" format.
+(e.g. "usb 0:1")
+
+The bootmenu displays the UEFI-related menu entries in order of "BootOrder".
+When the user selects the UEFI boot menu entry, the bootmenu sets
+the selected boot variable index to "BootNext" without non-volatile attribute,
+then call the uefi boot manager with the command "bootefi bootmgr".
+
+Example bootmenu is as below::
+
+    *** U-Boot Boot Menu ***
+
+       mmc 0:1
+       mmc 0:2
+       debian
+       nvme 0:1
+       ubuntu
+       nvme 0:2
+       usb 0:2
+       U-Boot console
+
+Default behavior when user exits from the bootmenu
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+User can exit from bootmenu by selecting the last entry
+"U-Boot console"/"Quit" or ESC/CTRL+C key.
+
+When the CONFIG_BOOTMENU_DISABLE_UBOOT_CONSOLE is disabled,
+user exits from the bootmenu and returns to the U-Boot console.
+
+When the CONFIG_BOOTMENU_DISABLE_UBOOT_CONSOLE is enabled, user can not
+enter the U-Boot console. When the user exits from the bootmenu,
+the bootmenu invokes the following default behavior.
+
+ * if CONFIG_CMD_BOOTEFI_BOOTMGR is enabled, execute "bootefi bootmgr" command
+ * "bootefi bootmgr" fails or is not enabled, then execute "run bootcmd" command.
+
+Configuration
+-------------
+
 The "bootmenu" command is enabled by::
 
     CONFIG_CMD_BOOTMENU=y
@@ -88,3 +146,19 @@ To run the bootmenu at startup add these additional settings::
     CONFIG_AUTOBOOT_KEYED=y
     CONFIG_BOOTDELAY=30
     CONFIG_AUTOBOOT_MENU_SHOW=y
+
+UEFI boot variable enumeration is enabled by::
+
+    CONFIG_CMD_BOOTEFI_BOOTMGR=y
+
+To improve the product security, entering U-Boot console from bootmenu
+can be disabled by::
+
+    CONFIG_BOOTMENU_DISABLE_UBOOT_CONSOLE=y
+
+To scan the discoverable devices connected to the buses such as
+USB and PCIe prior to bootmenu showing up, CONFIG_PREBOOT can be
+used to run the command before showing the bootmenu, i.e.::
+
+    CONFIG_USE_PREBOOT=y
+    CONFIG_PREBOOT="pci enum; usb start; scsi scan; nvme scan; virtio scan"
-- 
2.17.1


^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [PATCH v11 8/9] doc:eficonfig: add documentation for eficonfig command
  2022-08-17  9:36 [PATCH v11 0/9] enable menu-driven UEFI variable maintenance Masahisa Kojima
                   ` (6 preceding siblings ...)
  2022-08-17  9:36 ` [PATCH v11 7/9] doc:bootmenu: add description for UEFI boot support Masahisa Kojima
@ 2022-08-17  9:36 ` Masahisa Kojima
  2022-08-19  5:57   ` Takahiro Akashi
  2022-08-17  9:36 ` [PATCH v11 9/9] test: unit test for eficonfig Masahisa Kojima
  8 siblings, 1 reply; 25+ messages in thread
From: Masahisa Kojima @ 2022-08-17  9:36 UTC (permalink / raw)
  To: u-boot
  Cc: Heinrich Schuchardt, Ilias Apalodimas, Simon Glass,
	Takahiro Akashi, Mark Kettenis, Masahisa Kojima, Bin Meng,
	Marek Behún

Add documentation for eficonfig command.

Signed-off-by: Masahisa Kojima <masahisa.kojima@linaro.org>
---
No update since v10

Changes in v10:
- describe how to boot system after editting by eficonfig

Changes in v8:
- command name is changed from "efimenu" to "eficonfig"

Newly created in v7

 doc/usage/cmd/eficonfig.rst | 63 +++++++++++++++++++++++++++++++++++++
 doc/usage/index.rst         |  1 +
 2 files changed, 64 insertions(+)
 create mode 100644 doc/usage/cmd/eficonfig.rst

diff --git a/doc/usage/cmd/eficonfig.rst b/doc/usage/cmd/eficonfig.rst
new file mode 100644
index 0000000000..958e96992c
--- /dev/null
+++ b/doc/usage/cmd/eficonfig.rst
@@ -0,0 +1,63 @@
+.. SPDX-License-Identifier: GPL-2.0+
+.. (C) Copyright 2022, Masahisa Kojima <masahisa.kojima@linaro.org>
+
+eficonfig command
+=================
+
+Synopsis
+--------
+::
+
+    eficonfig
+
+Description
+-----------
+
+The "eficonfig" command uses U-Boot menu interface and privides
+a menu-driven UEFI variable maintenance feature.
+The "eficonfig" has the following menu entries.
+
+Add Boot Option
+    Add new UEFI Boot Option.
+    User can edit description, file path, and optional_data.
+
+Edit Boot Option
+    Edit the existing UEFI Boot Option
+    User can edit description, file path, and optional_data.
+
+Change Boot Order
+    Change the order of UEFI BootOrder variable.
+
+Delete Boot Option
+    Delete the UEFI Boot Option
+
+Configuration
+-------------
+
+The "eficonfig" command is enabled by::
+
+    CONFIG_CMD_EFICONFIG=y
+
+If CONFIG_BOOTMENU_DISABLE_UBOOT_CONSOLE is enabled, user can not enter
+U-Boot console. In this case, bootmenu can be used to invoke "eficonfig"::
+
+    CONFIG_USE_PREBOOT=y
+    CONFIG_PREBOOT="setenv bootmenu_0 UEFI Maintenance Menu=eficonfig"
+
+How to boot the system with newly added UEFI Boot Option
+''''''''''''''''''''''''''''''''''''''''''''''''''''''''
+
+"eficonfig" command is responsible to configure the UEFI variables,
+not directly handle the system boot.
+The new Boot Option added by "eficonfig" is appended at the last entry
+of UEFI BootOrder variable, user may want to change the boot order
+through "Change Boot Order".
+If the bootmenu is enabled and "eficonfig" is configured as preboot command,
+the newly added Boot Options are enumerated in the bootmenu when user exits
+from the eficonfig menu.
+User may select the entry in the bootmenu to boot the system, or follow
+the U-Boot configuration the system already has.
+
+See also
+--------
+* :doc:`bootmenu<bootmenu>` provides a simple mechanism for creating menus with different boot items
diff --git a/doc/usage/index.rst b/doc/usage/index.rst
index 28f9683a3e..09f2928970 100644
--- a/doc/usage/index.rst
+++ b/doc/usage/index.rst
@@ -35,6 +35,7 @@ Shell commands
    cmd/conitrace
    cmd/dm
    cmd/echo
+   cmd/eficonfig
    cmd/env
    cmd/event
    cmd/exception
-- 
2.17.1


^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [PATCH v11 9/9] test: unit test for eficonfig
  2022-08-17  9:36 [PATCH v11 0/9] enable menu-driven UEFI variable maintenance Masahisa Kojima
                   ` (7 preceding siblings ...)
  2022-08-17  9:36 ` [PATCH v11 8/9] doc:eficonfig: add documentation for eficonfig command Masahisa Kojima
@ 2022-08-17  9:36 ` Masahisa Kojima
  8 siblings, 0 replies; 25+ messages in thread
From: Masahisa Kojima @ 2022-08-17  9:36 UTC (permalink / raw)
  To: u-boot
  Cc: Heinrich Schuchardt, Ilias Apalodimas, Simon Glass,
	Takahiro Akashi, Mark Kettenis, Masahisa Kojima

Provide a unit test for the eficonfig command.

Signed-off-by: Masahisa Kojima <masahisa.kojima@linaro.org>
---
Changes in v11:
- fix expected result when no BootOrder is defined

Newly added in v10

 configs/sandbox_defconfig                     |   1 +
 test/py/tests/test_eficonfig/conftest.py      |  40 +++
 .../py/tests/test_eficonfig/test_eficonfig.py | 325 ++++++++++++++++++
 3 files changed, 366 insertions(+)
 create mode 100644 test/py/tests/test_eficonfig/conftest.py
 create mode 100644 test/py/tests/test_eficonfig/test_eficonfig.py

diff --git a/configs/sandbox_defconfig b/configs/sandbox_defconfig
index eba7bcbb48..48c60c606d 100644
--- a/configs/sandbox_defconfig
+++ b/configs/sandbox_defconfig
@@ -93,6 +93,7 @@ CONFIG_CMD_LINK_LOCAL=y
 CONFIG_CMD_ETHSW=y
 CONFIG_CMD_BMP=y
 CONFIG_CMD_BOOTCOUNT=y
+CONFIG_CMD_EFICONFIG=y
 CONFIG_CMD_EFIDEBUG=y
 CONFIG_CMD_RTC=y
 CONFIG_CMD_TIME=y
diff --git a/test/py/tests/test_eficonfig/conftest.py b/test/py/tests/test_eficonfig/conftest.py
new file mode 100644
index 0000000000..f289df0362
--- /dev/null
+++ b/test/py/tests/test_eficonfig/conftest.py
@@ -0,0 +1,40 @@
+# SPDX-License-Identifier:      GPL-2.0+
+
+"""Fixture for UEFI eficonfig test
+"""
+
+import os
+import shutil
+from subprocess import check_call
+import pytest
+
+@pytest.fixture(scope='session')
+def efi_eficonfig_data(u_boot_config):
+    """Set up a file system to be used in UEFI "eficonfig" command
+       tests
+
+    Args:
+        u_boot_config -- U-boot configuration.
+
+    Return:
+        A path to disk image to be used for testing
+    """
+    mnt_point = u_boot_config.persistent_data_dir + '/test_efi_eficonfig'
+    image_path = u_boot_config.persistent_data_dir + '/efi_eficonfig.img'
+
+    shutil.rmtree(mnt_point, ignore_errors=True)
+    os.mkdir(mnt_point, mode = 0o755)
+
+    with open(mnt_point + '/initrd-1.img', 'w', encoding = 'ascii') as file:
+        file.write("initrd 1")
+
+    with open(mnt_point + '/initrd-2.img', 'w', encoding = 'ascii') as file:
+        file.write("initrd 2")
+
+    shutil.copyfile(u_boot_config.build_dir + '/lib/efi_loader/initrddump.efi',
+                    mnt_point + '/initrddump.efi')
+
+    check_call(f'virt-make-fs --partition=gpt --size=+1M --type=vfat {mnt_point} {image_path}',
+               shell=True)
+
+    return image_path
diff --git a/test/py/tests/test_eficonfig/test_eficonfig.py b/test/py/tests/test_eficonfig/test_eficonfig.py
new file mode 100644
index 0000000000..5cb0c480ad
--- /dev/null
+++ b/test/py/tests/test_eficonfig/test_eficonfig.py
@@ -0,0 +1,325 @@
+# SPDX-License-Identifier:      GPL-2.0+
+""" Unit test for UEFI menu-driven configuration
+"""
+
+import pytest
+import time
+
+@pytest.mark.boardspec('sandbox')
+@pytest.mark.buildconfigspec('cmd_eficonfig')
+@pytest.mark.buildconfigspec('cmd_bootefi_bootmgr')
+def test_efi_eficonfig(u_boot_console, efi_eficonfig_data):
+
+    def send_user_input_and_wait(user_str, expect_str):
+        time.sleep(0.1) # TODO: does not work correctly without sleep
+        u_boot_console.run_command(cmd=user_str, wait_for_prompt=False,
+                                   wait_for_echo=True, send_nl=False)
+        u_boot_console.run_command(cmd='\x0d', wait_for_prompt=False,
+                                   wait_for_echo=False, send_nl=False)
+        if expect_str is not None:
+            for i in expect_str:
+                u_boot_console.p.expect([i])
+
+    def press_up_down_enter_and_wait(up_count, down_count, enter, expect_str):
+        # press UP key
+        for i in range(up_count):
+            u_boot_console.run_command(cmd='\x1b\x5b\x41', wait_for_prompt=False,
+                                       wait_for_echo=False, send_nl=False)
+        # press DOWN key
+        for i in range(down_count):
+            u_boot_console.run_command(cmd='\x1b\x5b\x42', wait_for_prompt=False,
+                                       wait_for_echo=False, send_nl=False)
+        # press ENTER if requested
+        if enter:
+            u_boot_console.run_command(cmd='\x0d', wait_for_prompt=False,
+                                       wait_for_echo=False, send_nl=False)
+        # wait expected output
+        if expect_str is not None:
+            for i in expect_str:
+                u_boot_console.p.expect([i])
+
+    def press_escape_key(wait_prompt):
+        u_boot_console.run_command(cmd='\x1b', wait_for_prompt=wait_prompt, wait_for_echo=False, send_nl=False)
+
+    def press_enter_key(wait_prompt):
+        u_boot_console.run_command(cmd='\x0d', wait_for_prompt=wait_prompt,
+                                   wait_for_echo=False, send_nl=False)
+
+    def check_current_is_maintenance_menu():
+        for i in ('UEFI Maintenance Menu', 'Add Boot Option', 'Edit Boot Option',
+                  'Change Boot Order', 'Delete Boot Option', 'Quit'):
+            u_boot_console.p.expect([i])
+
+    """ Unit test for "eficonfig" command
+    The menu-driven interface is used to set up UEFI load options.
+    The bootefi bootmgr loads initrddump.efi as a payload.
+    The crc32 of the loaded initrd.img is checked
+
+    Args:
+        u_boot_console -- U-Boot console
+        efi__data -- Path to the disk image used for testing.
+                     Test disk image has following files.
+                         initrd-1.img
+                         initrddump.efi
+                         initrd-2.img
+
+    """
+    with u_boot_console.temporary_timeout(500):
+        #
+        # Test Case 1: Check the menu is displayed
+        #
+        u_boot_console.run_command('eficonfig', wait_for_prompt=False)
+        for i in ('UEFI Maintenance Menu', 'Add Boot Option', 'Edit Boot Option',
+                  'Change Boot Order', 'Delete Boot Option', 'Quit'):
+            u_boot_console.p.expect([i])
+        # Select "Add Boot Option"
+        press_enter_key(False)
+        for i in ('Add Boot Option', 'Description:', 'File', 'Initrd File', 'Optional Data',
+                  'Save', 'Quit'):
+            u_boot_console.p.expect([i])
+        press_escape_key(False)
+        check_current_is_maintenance_menu()
+        # return to U-Boot console
+        press_escape_key(True)
+
+        #
+        # Test Case 2: No block device found
+        #
+        u_boot_console.run_command('eficonfig', wait_for_prompt=False)
+
+        # Select 'Add Boot Option'
+        press_up_down_enter_and_wait(0, 0, True, 'Quit')
+
+        # Set EFI image
+        press_up_down_enter_and_wait(0, 1, True, 'No block device found!')
+        press_escape_key(False)
+        check_current_is_maintenance_menu()
+        # Return to U-Boot console
+        press_escape_key(True)
+
+        #
+        # Test Case 3: Add first Boot Option and load it
+        #
+
+        # bind the test disk image for succeeding tests
+        u_boot_console.run_command(cmd = f'host bind 0 {efi_eficonfig_data}')
+
+        u_boot_console.run_command('eficonfig', wait_for_prompt=False)
+
+        # Select 'Add Boot Option'
+        press_up_down_enter_and_wait(0, 0, True, 'Quit')
+
+        # Press the enter key to select 'Description:' entry, then enter Description
+        press_up_down_enter_and_wait(0, 0, True, 'enter description:')
+        # Send Description user input, press ENTER key to complete
+        send_user_input_and_wait('test 1', 'Quit')
+
+        # Set EFI image(initrddump.efi)
+        press_up_down_enter_and_wait(0, 1, True, 'host 0:1')
+        # Select 'host 0:1'
+        press_up_down_enter_and_wait(0, 0, True, 'Quit')
+        # Press down key to select "initrddump.efi" entry followed by the enter key
+        press_up_down_enter_and_wait(0, 1, True, 'Quit')
+
+        # Set Initrd file(initrd-1.img)
+        press_up_down_enter_and_wait(0, 2, True, 'host 0:1')
+        # Select 'host 0:1'
+        press_up_down_enter_and_wait(0, 0, True, 'Quit')
+        # Press down key to select "initrd-1.img" entry followed by the enter key
+        press_up_down_enter_and_wait(0, 0, True, 'Quit')
+
+        # Set optional_data
+        press_up_down_enter_and_wait(0, 3, True, 'Optional Data:')
+        # Send Description user input, press ENTER key to complete
+        send_user_input_and_wait('nocolor', None)
+        for i in ('Description: test 1', 'File: host 0:1/initrddump.efi',
+                  'Initrd File: host 0:1/initrd-1.img', 'Optional Data: nocolor', 'Save', 'Quit'):
+            u_boot_console.p.expect([i])
+
+        # Save the Boot Option
+        press_up_down_enter_and_wait(0, 4, True, None)
+        check_current_is_maintenance_menu()
+
+        # Check the newly added Boot Option is handled correctly
+        # Return to U-Boot console
+        press_escape_key(True)
+        u_boot_console.run_command(cmd = 'bootefi bootmgr')
+        response = u_boot_console.run_command(cmd = 'load', wait_for_echo=False)
+        assert 'crc32: 0x181464af' in response
+        u_boot_console.run_command(cmd = 'exit', wait_for_echo=False)
+
+        #
+        # Test Case 4: Add second Boot Option and load it
+        #
+        u_boot_console.run_command('eficonfig', wait_for_prompt=False)
+
+        # Select 'Add Boot Option'
+        press_up_down_enter_and_wait(0, 0, True, 'Quit')
+
+        # Press the enter key to select 'Description:' entry, then enter Description
+        press_up_down_enter_and_wait(0, 0, True, 'enter description:')
+        # Send Description user input, press ENTER key to complete
+        send_user_input_and_wait('test 2', 'Quit')
+
+        # Set EFI image(initrddump.efi)
+        press_up_down_enter_and_wait(0, 1, True, 'host 0:1')
+        # Select 'host 0:1'
+        press_up_down_enter_and_wait(0, 0, True, 'Quit')
+        # Press down key to select "initrddump.efi" entry followed by the enter key
+        press_up_down_enter_and_wait(0, 1, True, 'Quit')
+
+        # Set Initrd file(initrd-2.img)
+        press_up_down_enter_and_wait(0, 2, True, 'host 0:1')
+        # Select 'host 0:1'
+        press_up_down_enter_and_wait(0, 0, True, 'Quit')
+        # Press down key to select "initrd-2.img" entry followed by the enter key
+        press_up_down_enter_and_wait(0, 2, True, 'Quit')
+
+        # Set optional_data
+        press_up_down_enter_and_wait(0, 3, True, 'Optional Data:')
+        # Send Description user input, press ENTER key to complete
+        send_user_input_and_wait('nocolor', None)
+        for i in ('Description: test 2', 'File: host 0:1/initrddump.efi',
+                  'Initrd File: host 0:1/initrd-2.img', 'Optional Data: nocolor', 'Save', 'Quit'):
+            u_boot_console.p.expect([i])
+
+        # Save the Boot Option
+        press_up_down_enter_and_wait(0, 4, True, 'Quit')
+
+        # Change the Boot Order
+        press_up_down_enter_and_wait(0, 2, True, 'Quit')
+        press_up_down_enter_and_wait(0, 1, False, 'Quit')
+        # move 'test 1' to the second entry
+        u_boot_console.run_command(cmd='+', wait_for_prompt=False,
+                                       wait_for_echo=False, send_nl=False)
+        for i in ('test 2', 'test 1', 'Save', 'Quit'):
+            u_boot_console.p.expect([i])
+        # Save the BootOrder
+        press_up_down_enter_and_wait(0, 2, True, None)
+        check_current_is_maintenance_menu()
+
+        # Check the newly added Boot Option is handled correctly
+        # Return to U-Boot console
+        press_escape_key(True)
+        u_boot_console.run_command(cmd = 'bootefi bootmgr')
+        response = u_boot_console.run_command(cmd = 'load', wait_for_echo=False)
+        assert 'crc32: 0x811d3515' in response
+        u_boot_console.run_command(cmd = 'exit', wait_for_echo=False)
+
+        #
+        # Test Case 5: Change BootOrder and load it
+        #
+        u_boot_console.run_command('eficonfig', wait_for_prompt=False)
+
+        # Change the Boot Order
+        press_up_down_enter_and_wait(0, 2, True, None)
+        # Check the curren BootOrder
+        for i in ('test 2', 'test 1', 'Save', 'Quit'):
+            u_boot_console.p.expect([i])
+        # move 'test 2' to the second entry
+        u_boot_console.run_command(cmd='-', wait_for_prompt=False,
+                                       wait_for_echo=False, send_nl=False)
+        for i in ('test 1', 'test 2', 'Save', 'Quit'):
+            u_boot_console.p.expect([i])
+        # Save the BootOrder
+        press_up_down_enter_and_wait(0, 1, True, None)
+        check_current_is_maintenance_menu()
+
+        # Return to U-Boot console
+        press_escape_key(True)
+        u_boot_console.run_command(cmd = 'bootefi bootmgr')
+        response = u_boot_console.run_command(cmd = 'load', wait_for_echo=False)
+        assert 'crc32: 0x181464af' in response
+        u_boot_console.run_command(cmd = 'exit', wait_for_echo=False)
+
+        #
+        # Test Case 6: Delete Boot Option(label:test 2)
+        #
+        u_boot_console.run_command('eficonfig', wait_for_prompt=False)
+
+        # Select 'Delete Boot Option'
+        press_up_down_enter_and_wait(0, 3, True, None)
+        # Check the curren BootOrder
+        for i in ('test 1', 'test 2', 'Quit'):
+            u_boot_console.p.expect([i])
+
+        # Delete 'test 2'
+        press_up_down_enter_and_wait(0, 1, True, None)
+        for i in ('test 1', 'Quit'):
+            u_boot_console.p.expect([i])
+        press_escape_key(False)
+        check_current_is_maintenance_menu()
+        # Return to U-Boot console
+        press_escape_key(True)
+
+        #
+        # Test Case 7: Edit Boot Option
+        #
+        u_boot_console.run_command('eficonfig', wait_for_prompt=False)
+        # Select 'Edit Boot Option'
+        press_up_down_enter_and_wait(0, 1, True, None)
+        # Check the curren BootOrder
+        for i in ('test 1', 'Quit'):
+            u_boot_console.p.expect([i])
+        press_up_down_enter_and_wait(0, 0, True, None)
+        for i in ('Description: test 1', 'File: host 0:1/initrddump.efi',
+                  'Initrd File: host 0:1/initrd-1.img', 'Optional Data: nocolor', 'Save', 'Quit'):
+            u_boot_console.p.expect([i])
+
+        # Press the enter key to select 'Description:' entry, then enter Description
+        press_up_down_enter_and_wait(0, 0, True, 'enter description:')
+        # Send Description user input, press ENTER key to complete
+        send_user_input_and_wait('test 3', 'Quit')
+
+        # Set EFI image(initrddump.efi)
+        press_up_down_enter_and_wait(0, 1, True, 'host 0:1')
+        # Select 'host 0:1'
+        press_up_down_enter_and_wait(0, 0, True, 'Quit')
+        # Press down key to select "initrddump.efi" entry followed by the enter key
+        press_up_down_enter_and_wait(0, 1, True, 'Quit')
+
+        # Set Initrd file(initrd-2.img)
+        press_up_down_enter_and_wait(0, 2, True, 'host 0:1')
+        # Select 'host 0:1'
+        press_up_down_enter_and_wait(0, 0, True, 'Quit')
+        # Press down key to select "initrd-1.img" entry followed by the enter key
+        press_up_down_enter_and_wait(0, 2, True, 'Quit')
+
+        # Set optional_data
+        press_up_down_enter_and_wait(0, 3, True, 'Optional Data:')
+        # Send Description user input, press ENTER key to complete
+        send_user_input_and_wait('', None)
+        for i in ('Description: test 3', 'File: host 0:1/initrddump.efi',
+                  'Initrd File: host 0:1/initrd-2.img', 'Optional Data:', 'Save', 'Quit'):
+            u_boot_console.p.expect([i])
+
+        # Save the Boot Option
+        press_up_down_enter_and_wait(0, 4, True, 'Quit')
+        press_escape_key(False)
+        check_current_is_maintenance_menu()
+
+        # Check the updated Boot Option is handled correctly
+        # Return to U-Boot console
+        press_escape_key(True)
+        u_boot_console.run_command(cmd = 'bootefi bootmgr')
+        response = u_boot_console.run_command(cmd = 'load', wait_for_echo=False)
+        assert 'crc32: 0x811d3515' in response
+        u_boot_console.run_command(cmd = 'exit', wait_for_echo=False)
+
+        #
+        # Test Case 8: Delete Boot Option(label:test 3)
+        #
+        u_boot_console.run_command('eficonfig', wait_for_prompt=False)
+
+        # Select 'Delete Boot Option'
+        press_up_down_enter_and_wait(0, 3, True, None)
+        # Check the curren BootOrder
+        for i in ('test 3', 'Quit'):
+            u_boot_console.p.expect([i])
+
+        # Delete 'test 3'
+        press_up_down_enter_and_wait(0, 0, True, 'Quit')
+        press_escape_key(False)
+        check_current_is_maintenance_menu()
+        # Return to U-Boot console
+        press_escape_key(True)
-- 
2.17.1


^ permalink raw reply related	[flat|nested] 25+ messages in thread

* Re: [PATCH v11 4/9] eficonfig: add "Change Boot Order" menu entry
  2022-08-17  9:36 ` [PATCH v11 4/9] eficonfig: add "Change Boot Order" menu entry Masahisa Kojima
@ 2022-08-18  6:17   ` Heinrich Schuchardt
  2022-08-18  6:50     ` Heinrich Schuchardt
  0 siblings, 1 reply; 25+ messages in thread
From: Heinrich Schuchardt @ 2022-08-18  6:17 UTC (permalink / raw)
  To: Masahisa Kojima
  Cc: Ilias Apalodimas, Simon Glass, Takahiro Akashi, Mark Kettenis, u-boot

On 8/17/22 11:36, Masahisa Kojima wrote:
> This commit adds the menu entry to update UEFI BootOrder variable.
> User moves the entry with UP/DOWN key, changes the order
> with PLUS/MINUS key, press SPACE to activate or deactivate
> the entry, then finalizes the order by ENTER key.
> If the entry is activated, the boot index is added into the
> BootOrder variable in the order of the list.
>
> The U-Boot menu framework is well designed for static menu,
> this commit implements the own menu display and key handling
> for dynamically change the order of menu entry.
>
> Signed-off-by: Masahisa Kojima <masahisa.kojima@linaro.org>

Hello Masahisa,

not all boot option will necessarily be in the boot order.

It must be possible to add an existing boot option to the boot order.

It must be possible to delete a boot option from the boot order without
deleting the boot option.

I can't see how to do this inside the eficonfig command with the patch
series applied

Best regards

Heinrich

> ---
> Changes in v11:
> - remove BootOrder variable dependency
> - use ANSI_CURSOR_POSITION and ANSI_CLEAR_LINE instead of printf("\n")
>    since current eficonfig implementation does not handle console size correctly.
>    printf("\n") at the outside of console size breaks the console output.
> - add KEY_SPACE to toggle the boot option active status
>
> No update since v9
>
> Changes in v9:
> - add function comment
>
> Changes in v8:
> - add "Save" and "Quit" entries
>
> Changes in v7:
> - use UP/DOWN and PLUS/MINUS key to change to order
>
> no update in v6:
>
>   cmd/eficonfig.c | 346 ++++++++++++++++++++++++++++++++++++++++++++++++
>   1 file changed, 346 insertions(+)
>
> diff --git a/cmd/eficonfig.c b/cmd/eficonfig.c
> index 938d46374e..f6152495c8 100644
> --- a/cmd/eficonfig.c
> +++ b/cmd/eficonfig.c
> @@ -91,6 +91,23 @@ struct eficonfig_boot_selection_data {
>   	int *selected;
>   };
>
> +/**
> + * struct eficonfig_boot_order - structure to be used to update BootOrder variable
> + *
> + * @num:		index in the menu entry
> + * @description:	pointer to the description string
> + * @boot_index:		boot option index
> + * @active:		flag to include the boot option into BootOrder variable
> + * @list:		list structure
> + */
> +struct eficonfig_boot_order {
> +	u32 num;
> +	u16 *description;
> +	u32 boot_index;
> +	bool active;
> +	struct list_head list;
> +};
> +
>   /**
>    * eficonfig_print_msg() - print message
>    *
> @@ -1665,6 +1682,334 @@ out:
>   	return ret;
>   }
>
> +/**
> + * eficonfig_display_change_boot_order() - display the BootOrder list
> + *
> + * @efi_menu:	pointer to the efimenu structure
> + * Return:	status code
> + */
> +static void eficonfig_display_change_boot_order(struct efimenu *efi_menu)
> +{
> +	bool reverse;
> +	struct list_head *pos, *n;
> +	struct eficonfig_boot_order *entry;
> +
> +	printf(ANSI_CLEAR_CONSOLE ANSI_CURSOR_POSITION
> +	       "\n  ** Change Boot Order **\n"
> +	       ANSI_CURSOR_POSITION
> +	       "  Press UP/DOWN to move, +/- to change order"
> +	       ANSI_CURSOR_POSITION
> +	       "  Press SPACE to activate or deactivate the entry"
> +	       ANSI_CURSOR_POSITION
> +	       "  Select [Save] to complete, ESC/CTRL+C to quit"
> +	       ANSI_CURSOR_POSITION ANSI_CLEAR_LINE,
> +	       1, 1, efi_menu->count + 5, 1, efi_menu->count + 6, 1,
> +	       efi_menu->count + 7, 1,  efi_menu->count + 8, 1);
> +
> +	/* draw boot option list */
> +	list_for_each_safe(pos, n, &efi_menu->list) {
> +		entry = list_entry(pos, struct eficonfig_boot_order, list);
> +		reverse = (entry->num == efi_menu->active);
> +
> +		printf(ANSI_CURSOR_POSITION, entry->num + 4, 7);
> +
> +		if (reverse)
> +			puts(ANSI_COLOR_REVERSE);
> +
> +		if (entry->num < efi_menu->count - 2) {
> +			if (entry->active)
> +				printf("[*]  ");
> +			else
> +				printf("[ ]  ");
> +		}
> +
> +		printf("%ls", entry->description);
> +
> +		if (reverse)
> +			puts(ANSI_COLOR_RESET);
> +	}
> +}
> +
> +/**
> + * eficonfig_choice_change_boot_order() - handle the BootOrder update
> + *
> + * @efi_menu:	pointer to the efimenu structure
> + * Return:	status code
> + */
> +static efi_status_t eficonfig_choice_change_boot_order(struct efimenu *efi_menu)
> +{
> +	int esc = 0;
> +	struct list_head *pos, *n;
> +	struct eficonfig_boot_order *tmp;
> +	enum bootmenu_key key = KEY_NONE;
> +	struct eficonfig_boot_order *entry;
> +
> +	while (1) {
> +		bootmenu_loop(NULL, &key, &esc);
> +
> +		switch (key) {
> +		case KEY_PLUS:
> +			if (efi_menu->active > 0) {
> +				list_for_each_safe(pos, n, &efi_menu->list) {
> +					entry = list_entry(pos, struct eficonfig_boot_order, list);
> +					if (entry->num == efi_menu->active)
> +						break;
> +				}
> +				tmp = list_entry(pos->prev, struct eficonfig_boot_order, list);
> +				entry->num--;
> +				tmp->num++;
> +				list_del(&tmp->list);
> +				list_add(&tmp->list, &entry->list);
> +			}
> +			fallthrough;
> +		case KEY_UP:
> +			if (efi_menu->active > 0)
> +				--efi_menu->active;
> +			return EFI_NOT_READY;
> +		case KEY_MINUS:
> +			if (efi_menu->active < efi_menu->count - 3) {
> +				list_for_each_safe(pos, n, &efi_menu->list) {
> +					entry = list_entry(pos, struct eficonfig_boot_order, list);
> +					if (entry->num == efi_menu->active)
> +						break;
> +				}
> +				tmp = list_entry(pos->next, struct eficonfig_boot_order, list);
> +				entry->num++;
> +				tmp->num--;
> +				list_del(&entry->list);
> +				list_add(&entry->list, &tmp->list);
> +
> +				++efi_menu->active;
> +			}
> +			return EFI_NOT_READY;
> +		case KEY_DOWN:
> +			if (efi_menu->active < efi_menu->count - 1)
> +				++efi_menu->active;
> +			return EFI_NOT_READY;
> +		case KEY_SELECT:
> +			/* "Save" */
> +			if (efi_menu->active == efi_menu->count - 2)
> +				return EFI_SUCCESS;
> +
> +			/* "Quit" */
> +			if (efi_menu->active == efi_menu->count - 1)
> +				return EFI_ABORTED;
> +
> +			break;
> +		case KEY_SPACE:
> +			if (efi_menu->active < efi_menu->count - 2) {
> +				list_for_each_safe(pos, n, &efi_menu->list) {
> +					entry = list_entry(pos, struct eficonfig_boot_order, list);
> +					if (entry->num == efi_menu->active) {
> +						entry->active = entry->active ? false : true;
> +						return EFI_NOT_READY;
> +					}
> +				}
> +			}
> +			break;
> +		case KEY_QUIT:
> +			return EFI_ABORTED;
> +		default:
> +			break;
> +		}
> +	}
> +}
> +
> +/**
> + * eficonfig_add_change_boot_order_entry() - add boot order entry
> + *
> + * @efi_menu:	pointer to the efimenu structure
> + * @boot_index:	boot option index to be added
> + * @active:	flag to include the boot option into BootOrder
> + * Return:	status code
> + */
> +static efi_status_t eficonfig_add_change_boot_order_entry(struct efimenu *efi_menu,
> +							  u32 boot_index, bool active)
> +{
> +	efi_status_t ret;
> +	efi_uintn_t size;
> +	void *load_option;
> +	struct efi_load_option lo;
> +	u16 varname[] = u"Boot####";
> +	struct eficonfig_boot_order *entry;
> +
> +	efi_create_indexed_name(varname, sizeof(varname), "Boot", boot_index);
> +	load_option = efi_get_var(varname, &efi_global_variable_guid, &size);
> +	if (!load_option)
> +		return EFI_SUCCESS;
> +
> +	ret = efi_deserialize_load_option(&lo, load_option, &size);
> +	if (ret != EFI_SUCCESS) {
> +		free(load_option);
> +		return ret;
> +	}
> +
> +	entry = calloc(1, sizeof(struct eficonfig_boot_order));
> +	if (!entry) {
> +		free(load_option);
> +		return EFI_OUT_OF_RESOURCES;
> +	}
> +
> +	entry->description = u16_strdup(lo.label);
> +	if (!entry->description) {
> +		free(load_option);
> +		free(entry);
> +		return EFI_OUT_OF_RESOURCES;
> +	}
> +	entry->num = efi_menu->count++;
> +	entry->boot_index = boot_index;
> +	entry->active = active;
> +	list_add_tail(&entry->list, &efi_menu->list);
> +
> +	free(load_option);
> +
> +	return EFI_SUCCESS;
> +}
> +
> +/**
> + * eficonfig_create_change_boot_order_entry() - create boot order entry
> + *
> + * @efi_menu:	pointer to the efimenu structure
> + * @bootorder:	pointer to the BootOrder variable
> + * @num:	number of BootOrder entry
> + * Return:	status code
> + */
> +static efi_status_t eficonfig_create_change_boot_order_entry(struct efimenu *efi_menu,
> +							     u16 *bootorder, efi_uintn_t num)
> +{
> +	u32 i;
> +	efi_status_t ret;
> +	struct eficonfig_boot_order *entry;
> +
> +	/* list the load option in the order of BootOrder variable */
> +	for (i = 0; i < num; i++) {
> +		if (efi_menu->count >= EFICONFIG_ENTRY_NUM_MAX - 2)
> +			break;
> +
> +		ret = eficonfig_add_change_boot_order_entry(efi_menu, bootorder[i], true);
> +		if (ret != EFI_SUCCESS)
> +			goto out;
> +	}
> +
> +	/* list the remaining load option not included in the BootOrder */
> +	for (i = 0; i < 0xFFFF; i++) {
> +		if (efi_menu->count >= EFICONFIG_ENTRY_NUM_MAX - 2)
> +			break;
> +
> +		/* If the index is included in the BootOrder, skip it */
> +		if (search_bootorder(bootorder, num, i, NULL))
> +			continue;
> +
> +		ret = eficonfig_add_change_boot_order_entry(efi_menu, i, false);
> +		if (ret != EFI_SUCCESS)
> +			goto out;
> +	}
> +
> +	/* add "Save" and "Quit" entries */
> +	entry = calloc(1, sizeof(struct eficonfig_boot_order));
> +	if (!entry)
> +		goto out;
> +
> +	entry->num = efi_menu->count++;
> +	entry->description = u16_strdup(u"Save");
> +	list_add_tail(&entry->list, &efi_menu->list);
> +
> +	entry = calloc(1, sizeof(struct eficonfig_boot_order));
> +	if (!entry)
> +		goto out;
> +
> +	entry->num = efi_menu->count++;
> +	entry->description = u16_strdup(u"Quit");
> +	list_add_tail(&entry->list, &efi_menu->list);
> +
> +	efi_menu->active = 0;
> +
> +	return EFI_SUCCESS;
> +out:
> +	return EFI_OUT_OF_RESOURCES;
> +}
> +
> +/**
> + * eficonfig_process_change_boot_order() - handler to change boot order
> + *
> + * @data:	pointer to the data for each entry
> + * Return:	status code
> + */
> +static efi_status_t eficonfig_process_change_boot_order(void *data)
> +{
> +	u32 count;
> +	u16 *bootorder;
> +	efi_status_t ret;
> +	efi_uintn_t num, size;
> +	struct list_head *pos, *n;
> +	struct eficonfig_boot_order *entry;
> +	struct efimenu *efi_menu;
> +
> +	efi_menu = calloc(1, sizeof(struct efimenu));
> +	if (!efi_menu)
> +		return EFI_OUT_OF_RESOURCES;
> +
> +	bootorder = efi_get_var(u"BootOrder", &efi_global_variable_guid, &size);
> +
> +	INIT_LIST_HEAD(&efi_menu->list);
> +	num = size / sizeof(u16);
> +	ret = eficonfig_create_change_boot_order_entry(efi_menu, bootorder, num);
> +	if (ret != EFI_SUCCESS)
> +		goto out;
> +
> +	while (1) {
> +		eficonfig_display_change_boot_order(efi_menu);
> +
> +		ret = eficonfig_choice_change_boot_order(efi_menu);
> +		if (ret == EFI_SUCCESS) {
> +			u16 *new_bootorder;
> +
> +			new_bootorder = calloc(1, (efi_menu->count - 2) * sizeof(u16));
> +			if (!new_bootorder) {
> +				ret = EFI_OUT_OF_RESOURCES;
> +				goto out;
> +			}
> +
> +			/* create new BootOrder  */
> +			count = 0;
> +			list_for_each_safe(pos, n, &efi_menu->list) {
> +				entry = list_entry(pos, struct eficonfig_boot_order, list);
> +				if (entry->active)
> +					new_bootorder[count++] = entry->boot_index;
> +			}
> +
> +			size = count * 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, new_bootorder, false);
> +
> +			free(new_bootorder);
> +			goto out;
> +		} else if (ret == EFI_NOT_READY) {
> +			continue;
> +		} else {
> +			goto out;
> +		}
> +	}
> +out:
> +	list_for_each_safe(pos, n, &efi_menu->list) {
> +		entry = list_entry(pos, struct eficonfig_boot_order, list);
> +		list_del(&entry->list);
> +		free(entry->description);
> +		free(entry);
> +	}
> +
> +	free(bootorder);
> +	free(efi_menu);
> +
> +	/* to stay the parent menu */
> +	ret = (ret == EFI_ABORTED) ? EFI_NOT_READY : ret;
> +
> +	return ret;
> +}
> +
>   /**
>    * eficonfig_init() - do required initialization for eficonfig command
>    *
> @@ -1695,6 +2040,7 @@ static efi_status_t eficonfig_init(void)
>   static const struct eficonfig_item maintenance_menu_items[] = {
>   	{"Add Boot Option", eficonfig_process_add_boot_option},
>   	{"Edit Boot Option", eficonfig_process_edit_boot_option},
> +	{"Change Boot Order", eficonfig_process_change_boot_order},
>   	{"Quit", eficonfig_process_quit},
>   };
>


^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH v11 1/9] eficonfig: menu-driven addition of UEFI boot option
  2022-08-17  9:36 ` [PATCH v11 1/9] eficonfig: menu-driven addition of UEFI boot option Masahisa Kojima
@ 2022-08-18  6:32   ` Heinrich Schuchardt
  2022-08-18  6:43   ` Heinrich Schuchardt
  1 sibling, 0 replies; 25+ messages in thread
From: Heinrich Schuchardt @ 2022-08-18  6:32 UTC (permalink / raw)
  To: Masahisa Kojima
  Cc: Ilias Apalodimas, Simon Glass, Takahiro Akashi, Mark Kettenis,
	Michal Simek, Ovidiu Panait, Ashok Reddy Soma, John Keeping,
	Thomas Huth, Chris Morgan, Huang Jianan, u-boot

On 8/17/22 11:36, Masahisa Kojima wrote:
> This commit add the "eficonfig" command.
> The "eficonfig" command implements the menu-driven UEFI boot option
> maintenance feature. This commit implements the addition of
> new boot option. User can select the block device volume having
> efi_simple_file_system_protocol and select the file corresponding
> to the Boot#### variable. User can also enter the description and
> optional_data of the BOOT#### variable in utf8.
>
> This commit adds "include/efi_config.h", it contains the common
> definition to be used from other menus such as UEFI Secure Boot
> key management.
>
> Signed-off-by: Masahisa Kojima <masahisa.kojima@linaro.org>
> ---
> Changes in v11:
> - refactor menu entry construction, directly use eficonfig_entry structure
> - remove reading directory info to calculate the number of entry
> - fix invalid efi_free_pool() in ill_file_info()
> - use ANSI_CURSOR_POSITION and ANSI_CLEAR_LINE instead of printf("\n")
>    since current eficonfig implementation does not handle console size correctly.
>    printf("\n") at the outside of console size breaks the console output.
>
> Changes in v10:
> - add initrd file selection
> - do refactoring
> - eficonfig_process_common() use list structure
> - remove u'/' before copying file_path into current_path
> - fix typos
> - check snprintf error
>
> Changes in v9:
> - move "efi_guid_bootmenu_auto_generated definition" into efi_bootmgr.c
>    to address build error when CMD_EFICONFIG is disabled
> - fix typos and comment
> - remove file system information from error message
> - remove unreachable code in eficonfig_choice_entry()
> - single printf() call as much as possible
> - call only getchar() in  eficonfig_print_msg()
> - filter out '.' entry from file selection
> - update the efi_disk_get_device_name() implementation
> - add function comment
>
> Changes in v8:
> - command name is change from "efimenu" to "eficonfig"
> - function and struct prefixes is changed to "eficonfig"
> - fix menu header string
>
> Changes in v7:
> - add "efimenu" command and uefi variable maintenance code
>    moved into cmd/efimenu.c
> - create include/efimenu.h to define the common definition for
>    the other menu such as UEFI Secure Boot key management
> - update boot option edit UI, user can select description, file,
>    and optional_data to edit in the same menu like following.
>
>    ** Edit Boot Option **
>
>       Description: debian
>       File: virtio 0:1/EFI\debian\grubaa64.efi
>       Optional Data: test
>       Save
>       Quit
>
> - remove exit parameter from efimenu_process_common()
> - menu title type is changed from u16 to char
> - efimenu_process_common() add menu title string
> - reduce printf/puts function call for displaying the menu
> - efi_console_get_u16_string() accept 0 length to allow
>    optional_data is empty
> - efi_console_get_u16_string() the "size" parameter name is changes to "count"
> - efimenu is now designed to maintain the UEFI variables, remove autoboot related code
> - remove one empty line before "Quit" entry
> - efimenu_init() processes only the first time
>
> Changes in v6:
> - fix typos
> - modify volume name to match U-Boot syntax
> - compile in CONFIG_EFI_LOADER=n and CONFIG_CMD_BOOTEFI_BOOTMGR=n
> - simplify u16_strncmp() usage
> - support "a\b.efi" file path, use link list to handle filepath
> - modify length check condition
> - UEFI related menu items only appears with CONFIG_AUTOBOOT_MENU_SHOW=y
>
> Changes in v5:
> - remove forward declarations
> - add const qualifier for menu items
> - fix the possible unaligned access for directory info access
> - split into three commit 1)add boot option 2) delete boot option 3)change boot order
>    This commit is 1)add boot option.
> - fix file name buffer allocation size, it should be EFI_BOOTMENU_FILE_PATH_MAX * sizeof(u16)
> - fix wrong size checking for file selection
>
> 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 RFC v3:
>   not included in v3 series
>
> Changes in RFC 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/Kconfig                   |    7 +
>   cmd/Makefile                  |    1 +
>   cmd/eficonfig.c               | 1491 +++++++++++++++++++++++++++++++++
>   include/efi_config.h          |   90 ++
>   include/efi_loader.h          |   43 +
>   lib/efi_loader/efi_bootmgr.c  |    3 +
>   lib/efi_loader/efi_boottime.c |   52 +-
>   lib/efi_loader/efi_console.c  |   70 ++
>   lib/efi_loader/efi_disk.c     |   50 ++
>   lib/efi_loader/efi_file.c     |   75 +-
>   10 files changed, 1835 insertions(+), 47 deletions(-)
>   create mode 100644 cmd/eficonfig.c
>   create mode 100644 include/efi_config.h
>
> diff --git a/cmd/Kconfig b/cmd/Kconfig
> index 211ebe9c87..a1e8613c56 100644
> --- a/cmd/Kconfig
> +++ b/cmd/Kconfig
> @@ -1928,6 +1928,13 @@ config CMD_EFIDEBUG
>   	  particularly for managing boot parameters as  well as examining
>   	  various EFI status for debugging.
>
> +config CMD_EFICONFIG
> +	bool "eficonfig - provide menu-driven uefi variables maintenance interface"
> +	depends on CMD_BOOTEFI_BOOTMGR
> +	help
> +	  Enable the 'eficonfig' command which provides the menu-driven UEFI
> +	  variable maintenance interface.
> +
>   config CMD_EXCEPTION
>   	bool "exception - raise exception"
>   	depends on ARM || RISCV || SANDBOX || X86
> diff --git a/cmd/Makefile b/cmd/Makefile
> index 6e87522b62..18f5cb890d 100644
> --- a/cmd/Makefile
> +++ b/cmd/Makefile
> @@ -63,6 +63,7 @@ obj-$(CONFIG_ENV_IS_IN_EEPROM) += eeprom.o
>   obj-$(CONFIG_CMD_EEPROM) += eeprom.o
>   obj-$(CONFIG_EFI) += efi.o
>   obj-$(CONFIG_CMD_EFIDEBUG) += efidebug.o
> +obj-$(CONFIG_CMD_EFICONFIG) += eficonfig.o
>   obj-$(CONFIG_CMD_ELF) += elf.o
>   obj-$(CONFIG_CMD_EROFS) += erofs.o
>   obj-$(CONFIG_HUSH_PARSER) += exit.o
> diff --git a/cmd/eficonfig.c b/cmd/eficonfig.c
> new file mode 100644
> index 0000000000..39fbd3f0ad
> --- /dev/null
> +++ b/cmd/eficonfig.c
> @@ -0,0 +1,1491 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + *  Menu-driven UEFI Variable maintenance
> + *
> + *  Copyright (c) 2022 Masahisa Kojima, Linaro Limited
> + */
> +
> +#include <ansi.h>
> +#include <common.h>
> +#include <charset.h>
> +#include <efi_loader.h>
> +#include <efi_load_initrd.h>
> +#include <efi_config.h>
> +#include <efi_variable.h>
> +#include <log.h>
> +#include <malloc.h>
> +#include <menu.h>
> +#include <watchdog.h>
> +#include <asm/unaligned.h>
> +#include <linux/delay.h>
> +
> +static struct efi_simple_text_input_protocol *cin;
> +
> +#define EFICONFIG_DESCRIPTION_MAX 32
> +#define EFICONFIG_OPTIONAL_DATA_MAX 64
> +
> +/**
> + * struct eficonfig_filepath_info - structure to be used to store file path
> + *
> + * @name:	file or directory name
> + * @list:	list structure
> + */
> +struct eficonfig_filepath_info {
> +	u16 *name;
> +	struct list_head list;
> +};
> +
> +/**
> + * struct eficonfig_boot_option - structure to be used for updating UEFI boot option
> + *
> + * @file_info:		user selected file info
> + * @initrd_info:	user selected initrd file info
> + * @boot_index:		index of the UEFI BootOrder variable
> + * @description:	pointer to the description string
> + * @optional_data:	pointer to the optional_data
> + * @edit_completed:	flag indicates edit complete
> + */
> +struct eficonfig_boot_option {
> +	struct eficonfig_select_file_info file_info;
> +	struct eficonfig_select_file_info initrd_info;
> +	unsigned int boot_index;
> +	u16 *description;
> +	u16 *optional_data;
> +	bool edit_completed;
> +};
> +
> +/**
> + * struct eficonfig_volume_entry_data - structure to be used to store volume info
> + *
> + * @file_info:	pointer to file info structure
> + * @v:		pointer to the protocol interface
> + * @dp:		pointer to the device path
> + */
> +struct eficonfig_volume_entry_data {
> +	struct eficonfig_select_file_info *file_info;
> +	struct efi_simple_file_system_protocol *v;
> +	struct efi_device_path *dp;
> +};
> +
> +/**
> + * struct eficonfig_file_entry_data - structure to be used to store file info
> + *
> + * @file_info:		pointer to file info structure
> + * @is_directory:	flag to indentify the directory or file
> + * @file_name:		name of directory or file
> + */
> +struct eficonfig_file_entry_data {
> +	struct eficonfig_select_file_info *file_info;
> +	bool is_directory;
> +	u16 *file_name;
> +};
> +
> +/**
> + * eficonfig_print_msg() - print message
> + *
> + * display the message to the user, user proceeds the screen
> + * with any key press.
> + *
> + * @items:		pointer to the structure of each menu entry
> + * @count:		the number of menu entry
> + * @menu_header:	pointer to the menu header string
> + * Return:	status code
> + */
> +void eficonfig_print_msg(char *msg)
> +{
> +	/* Flush input */
> +	while (tstc())
> +		getchar();
> +
> +	printf(ANSI_CURSOR_HIDE
> +	       ANSI_CLEAR_CONSOLE
> +	       ANSI_CURSOR_POSITION
> +	       "%s\n\n  Press any key to continue", 3, 4, msg);
> +
> +	getchar();
> +}
> +
> +/**
> + * eficonfig_print_entry() - print each menu entry
> + *
> + * @data:	pointer to the data associated with each menu entry
> + */
> +static void eficonfig_print_entry(void *data)
> +{
> +	struct eficonfig_entry *entry = data;
> +	int reverse = (entry->efi_menu->active == entry->num);
> +
> +	/* TODO: support scroll or page for many entries */
> +
> +	/*
> +	 * Move cursor to line where the entry will be drawn (entry->num)
> +	 * First 3 lines(menu header) + 1 empty line
> +	 */
> +	printf(ANSI_CURSOR_POSITION, entry->num + 4, 7);
> +
> +	if (reverse)
> +		puts(ANSI_COLOR_REVERSE);
> +
> +	printf("%s", entry->title);
> +
> +	if (reverse)
> +		puts(ANSI_COLOR_RESET);
> +}
> +
> +/**
> + * eficonfig_display_statusline() - print status line
> + *
> + * @m:	pointer to the menu structure
> + */
> +static void eficonfig_display_statusline(struct menu *m)
> +{
> +	struct eficonfig_entry *entry;
> +
> +	if (menu_default_choice(m, (void *)&entry) < 0)
> +		return;
> +
> +	printf(ANSI_CURSOR_POSITION
> +	      "\n%s\n"
> +	       ANSI_CURSOR_POSITION ANSI_CLEAR_LINE ANSI_CURSOR_POSITION
> +	       "  Press UP/DOWN to move, ENTER to select, ESC/CTRL+C to quit"
> +	       ANSI_CLEAR_LINE_TO_END ANSI_CURSOR_POSITION ANSI_CLEAR_LINE,
> +	       1, 1, entry->efi_menu->menu_header, entry->efi_menu->count + 5, 1,
> +	       entry->efi_menu->count + 6, 1, entry->efi_menu->count + 7, 1);
> +}
> +
> +/**
> + * eficonfig_choice_entry() - user key input handler
> + *
> + * @data:	pointer to the efimenu structure
> + * Return:	key string to identify the selected entry
> + */
> +static char *eficonfig_choice_entry(void *data)
> +{
> +	int esc = 0;
> +	struct list_head *pos, *n;
> +	struct eficonfig_entry *entry;
> +	enum bootmenu_key key = KEY_NONE;
> +	struct efimenu *efi_menu = data;
> +
> +	while (1) {
> +		bootmenu_loop((struct bootmenu_data *)efi_menu, &key, &esc);
> +
> +		switch (key) {
> +		case KEY_UP:
> +			if (efi_menu->active > 0)
> +				--efi_menu->active;
> +			/* no menu key selected, regenerate menu */
> +			return NULL;
> +		case KEY_DOWN:
> +			if (efi_menu->active < efi_menu->count - 1)
> +				++efi_menu->active;
> +			/* no menu key selected, regenerate menu */
> +			return NULL;
> +		case KEY_SELECT:
> +			list_for_each_safe(pos, n, &efi_menu->list) {
> +				entry = list_entry(pos, struct eficonfig_entry, list);
> +				if (entry->num == efi_menu->active)
> +					return entry->key;
> +			}
> +		case KEY_QUIT:
> +			/* Quit by choosing the last entry */
> +			entry = list_last_entry(&efi_menu->list, struct eficonfig_entry, list);
> +			return entry->key;
> +		default:
> +			break;
> +		}
> +	}
> +}
> +
> +/**
> + * eficonfig_destroy() - destroy efimenu
> + *
> + * @efi_menu:	pointer to the efimenu structure
> + * @flag:	flag to free the allocated data
> + */
> +static void eficonfig_destroy(struct efimenu *efi_menu, bool flag)
> +{
> +	struct list_head *pos, *n;
> +	struct eficonfig_entry *entry;
> +
> +	list_for_each_safe(pos, n, &efi_menu->list) {
> +		entry = list_entry(pos, struct eficonfig_entry, list);
> +		free(entry->title);
> +		if (flag)
> +			free(entry->data);
> +		list_del(&entry->list);
> +		free(entry);
> +	}
> +	free(efi_menu->menu_header);
> +	free(efi_menu);
> +}
> +
> +/**
> + * eficonfig_process_quit() - callback function for "Quit" entry
> + *
> + * @data:	pointer to the data
> + * Return:	status code
> + */
> +efi_status_t eficonfig_process_quit(void *data)
> +{
> +	return EFI_ABORTED;
> +}
> +
> +/**
> + * append_entry() - append menu item
> + *
> + * @efi_menu:	pointer to the efimenu structure
> + * @title:	pointer to the entry title
> + * @func:	callback of each entry
> + * @data:	pointer to the data to be passed to each entry callback
> + * Return:	status code
> + */
> +static efi_status_t append_entry(struct efimenu *efi_menu,
> +				 char *title, eficonfig_entry_func func, void *data)
> +{
> +	struct eficonfig_entry *entry;
> +
> +	if (efi_menu->count >= EFICONFIG_ENTRY_NUM_MAX)
> +		return EFI_OUT_OF_RESOURCES;
> +
> +	entry = calloc(1, sizeof(struct eficonfig_entry));
> +	if (!entry)
> +		return EFI_OUT_OF_RESOURCES;
> +
> +	entry->title = title;
> +	sprintf(entry->key, "%d", efi_menu->count);
> +	entry->efi_menu = efi_menu;
> +	entry->func = func;
> +	entry->data = data;
> +	entry->num = efi_menu->count++;
> +	list_add_tail(&entry->list, &efi_menu->list);
> +
> +	return EFI_SUCCESS;
> +}
> +
> +/**
> + * append_quit_entry() - append quit entry
> + *
> + * @efi_menu:	pointer to the efimenu structure
> + * Return:	status code
> + */
> +static efi_status_t append_quit_entry(struct efimenu *efi_menu)
> +{
> +	char *title;
> +	efi_status_t ret;
> +
> +	title = strdup("Quit");
> +	if (!title)
> +		return EFI_OUT_OF_RESOURCES;
> +
> +	ret = append_entry(efi_menu, title, eficonfig_process_quit, NULL);
> +	if (ret != EFI_SUCCESS)
> +		free(title);
> +
> +	return ret;
> +}
> +
> +/**
> + * eficonfig_create_fixed_menu() - create fixed entry menu structure
> + *
> + * @items:	pointer to the menu entry item
> + * @count:	the number of menu entry
> + * Return:	pointer to the efimenu structure
> + */
> +void *eficonfig_create_fixed_menu(const struct eficonfig_item *items, int count)
> +{
> +	u32 i;
> +	char *title;
> +	efi_status_t ret;
> +	struct efimenu *efi_menu;
> +	const struct eficonfig_item *iter = items;
> +
> +	efi_menu = calloc(1, sizeof(struct efimenu));
> +	if (!efi_menu)
> +		return NULL;
> +
> +	INIT_LIST_HEAD(&efi_menu->list);
> +	for (i = 0; i < count; i++, iter++) {
> +		title = strdup(iter->title);
> +		if (!title)
> +			goto out;
> +
> +		ret = append_entry(efi_menu, title, iter->func, iter->data);
> +		if (ret != EFI_SUCCESS) {
> +			free(title);
> +			goto out;
> +		}
> +	}
> +
> +	return efi_menu;
> +out:
> +	eficonfig_destroy(efi_menu, false);
> +
> +	return NULL;
> +}
> +
> +/**
> + * eficonfig_process_common() - main handler for UEFI menu
> + *
> + * Construct the structures required to show the menu, then handle
> + * the user input interacting with u-boot menu functions.
> + *
> + * @efi_menu:		pointer to the efimenu structure
> + * @menu_header:	pointer to the menu header string
> + * Return:		status code
> + */
> +efi_status_t eficonfig_process_common(struct efimenu *efi_menu, char *menu_header)
> +{
> +	efi_status_t ret;
> +	struct menu *menu;
> +	void *choice = NULL;
> +	struct list_head *pos, *n;
> +	struct eficonfig_entry *entry;
> +
> +	if (efi_menu->count > EFICONFIG_ENTRY_NUM_MAX)
> +		return EFI_OUT_OF_RESOURCES;
> +
> +	efi_menu->delay = -1;
> +	efi_menu->active = 0;
> +
> +	if (menu_header) {
> +		efi_menu->menu_header = strdup(menu_header);
> +		if (!efi_menu->menu_header) {
> +			ret = EFI_OUT_OF_RESOURCES;
> +			goto out;
> +		}
> +	}
> +
> +	menu = menu_create(NULL, 0, 1, eficonfig_display_statusline,
> +			   eficonfig_print_entry, eficonfig_choice_entry,
> +			   efi_menu);
> +	if (!menu) {
> +		ret = EFI_INVALID_PARAMETER;
> +		goto out;
> +	}
> +
> +	list_for_each_safe(pos, n, &efi_menu->list) {
> +		entry = list_entry(pos, struct eficonfig_entry, list);
> +		if (!menu_item_add(menu, entry->key, entry)) {
> +			ret = EFI_INVALID_PARAMETER;
> +			goto out;
> +		}
> +	}
> +
> +	entry = list_first_entry_or_null(&efi_menu->list, struct eficonfig_entry, list);
> +	if (entry)
> +		menu_default_set(menu, entry->key);
> +
> +	printf(ANSI_CURSOR_HIDE
> +	       ANSI_CLEAR_CONSOLE
> +	       ANSI_CURSOR_POSITION, 1, 1);
> +
> +	if (menu_get_choice(menu, &choice)) {
> +		entry = choice;
> +		if (entry->func)
> +			ret = entry->func(entry->data);
> +	}
> +out:
> +	menu_destroy(menu);
> +
> +	printf(ANSI_CLEAR_CONSOLE
> +	       ANSI_CURSOR_POSITION
> +	       ANSI_CURSOR_SHOW, 1, 1);
> +
> +	return ret;
> +}
> +
> +/**
> + * eficonfig_volume_selected() - handler of volume selection
> + *
> + * @data:	pointer to the data of selected entry
> + * Return:	status code
> + */
> +static efi_status_t eficonfig_volume_selected(void *data)
> +{
> +	struct eficonfig_volume_entry_data *info = data;
> +
> +	if (info) {
> +		info->file_info->current_volume = info->v;
> +		info->file_info->dp_volume = info->dp;
> +	}
> +
> +	return EFI_SUCCESS;
> +}
> +
> +/**
> + * create_selected_device_path() - create device path
> + *
> + * @file_info:	pointer to the selected file information
> + * Return:
> + * device path or NULL. Caller must free the returned value
> + */
> +static
> +struct efi_device_path *create_selected_device_path(struct eficonfig_select_file_info *file_info)
> +{
> +	char *p;
> +	void *buf;
> +	efi_uintn_t fp_size;
> +	struct efi_device_path *dp;
> +	struct efi_device_path_file_path *fp;
> +
> +	fp_size = sizeof(struct efi_device_path) +
> +		  ((u16_strlen(file_info->current_path) + 1) * sizeof(u16));
> +	buf = calloc(1, fp_size + sizeof(END));
> +	if (!buf)
> +		return NULL;
> +
> +	fp = buf;
> +	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, file_info->current_path);
> +
> +	p = buf;
> +	p += fp_size;
> +	*((struct efi_device_path *)p) = END;
> +
> +	dp = efi_dp_append(file_info->dp_volume, (struct efi_device_path *)buf);
> +	free(buf);
> +
> +	return dp;
> +}
> +
> +/**
> + * eficonfig_file_selected() - handler of file selection
> + *
> + * @data:	pointer to the data of selected entry
> + * Return:	status code
> + */
> +static efi_status_t eficonfig_file_selected(void *data)
> +{
> +	struct eficonfig_file_entry_data *info = data;
> +
> +	if (!info)
> +		return EFI_INVALID_PARAMETER;
> +
> +	if (u16_strcmp(info->file_name, u".") == 0 &&
> +	    u16_strlen(info->file_name) == 1) {
> +		/* stay current path */
> +	} else if (u16_strcmp(info->file_name, u"..") == 0 &&
> +		   u16_strlen(info->file_name) == 2) {
> +		struct eficonfig_filepath_info *iter;
> +		struct list_head *pos, *n;
> +		int is_last;
> +
> +		memset(info->file_info->current_path, 0, EFICONFIG_FILE_PATH_BUF_SIZE);
> +		list_for_each_safe(pos, n, &info->file_info->filepath_list) {
> +			iter = list_entry(pos, struct eficonfig_filepath_info, list);
> +
> +			is_last = list_is_last(&iter->list, &info->file_info->filepath_list);
> +			if (is_last) {
> +				list_del(&iter->list);
> +				free(iter->name);
> +				free(iter);
> +				break;
> +			}
> +			u16_strlcat(info->file_info->current_path, iter->name,
> +				    EFICONFIG_FILE_PATH_MAX);
> +			u16_strlcat(info->file_info->current_path, u"\\",
> +				    EFICONFIG_FILE_PATH_MAX);
> +		}
> +	} else {
> +		size_t new_len;
> +		struct eficonfig_filepath_info *filepath;
> +
> +		new_len = u16_strlen(info->file_info->current_path) +
> +				     u16_strlen(info->file_name);
> +		if (new_len >= EFICONFIG_FILE_PATH_MAX) {
> +			eficonfig_print_msg("File path is too long!");
> +			return EFI_INVALID_PARAMETER;
> +		}
> +		u16_strlcat(info->file_info->current_path, info->file_name,
> +			    EFICONFIG_FILE_PATH_MAX);
> +
> +		filepath = calloc(1, sizeof(struct eficonfig_filepath_info));
> +		if (!filepath)
> +			return EFI_OUT_OF_RESOURCES;
> +
> +		filepath->name = u16_strdup(info->file_name);
> +		if (!filepath->name) {
> +			free(filepath);
> +			return EFI_OUT_OF_RESOURCES;
> +		}
> +		list_add_tail(&filepath->list, &info->file_info->filepath_list);
> +
> +		if (info->is_directory) {
> +			/*
> +			 * Remainig buffer should have enough space to contain u"\\" and
> +			 * at least one character for file name
> +			 */
> +			if (new_len + 2 >= EFICONFIG_FILE_PATH_MAX) {
> +				eficonfig_print_msg("Directory path is too long!");
> +				return EFI_INVALID_PARAMETER;
> +			}
> +			u16_strlcat(info->file_info->current_path, u"\\",
> +				    EFICONFIG_FILE_PATH_MAX);
> +		} else {
> +			info->file_info->file_selected = true;
> +		}
> +	}
> +	return EFI_SUCCESS;
> +}
> +
> +/**
> + * eficonfig_select_volume() - construct the volume selection menu
> + *
> + * @file_info:	pointer to the file selection structure
> + * Return:	status code
> + */
> +static efi_status_t eficonfig_select_volume(struct eficonfig_select_file_info *file_info)
> +{
> +	u32 i;
> +	efi_status_t ret;
> +	efi_uintn_t count;
> +	struct efimenu *efi_menu;
> +	struct efi_handler *handler;
> +	struct efi_device_path *device_path;
> +	efi_handle_t *volume_handles = NULL;
> +	struct efi_simple_file_system_protocol *v;
> +
> +	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) {
> +		eficonfig_print_msg("No block device found!");
> +		return ret;
> +	}
> +
> +	efi_menu = calloc(1, sizeof(struct efimenu));
> +	if (!efi_menu)
> +		return EFI_OUT_OF_RESOURCES;
> +
> +	INIT_LIST_HEAD(&efi_menu->list);
> +	for (i = 0; i < count; i++) {
> +		char *devname;
> +		struct efi_block_io *block_io;
> +		struct eficonfig_volume_entry_data *info;
> +
> +		if (efi_menu->count >= EFICONFIG_ENTRY_NUM_MAX - 1)
> +			break;
> +
> +		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 eficonfig_volume_entry_data));
> +		if (!info) {
> +			ret = EFI_OUT_OF_RESOURCES;
> +			goto out;
> +		}
> +
> +		devname = calloc(1, BOOTMENU_DEVICE_NAME_MAX);
> +		if (!devname) {
> +			free(info);
> +			ret = EFI_OUT_OF_RESOURCES;
> +			goto out;
> +		}
> +		ret = efi_disk_get_device_name(volume_handles[i], devname,
> +					       BOOTMENU_DEVICE_NAME_MAX);
> +		if (ret != EFI_SUCCESS) {
> +			free(info);
> +			goto out;
> +		}
> +
> +		info->v = v;
> +		info->dp = device_path;
> +		info->file_info = file_info;
> +		ret = append_entry(efi_menu, devname, eficonfig_volume_selected, info);
> +		if (ret != EFI_SUCCESS) {
> +			free(info);
> +			goto out;
> +		}
> +	}
> +
> +	ret = append_quit_entry(efi_menu);
> +	if (ret != EFI_SUCCESS)
> +		goto out;
> +
> +	ret = eficonfig_process_common(efi_menu, "  ** Select Volume **");
> +out:
> +	efi_free_pool(volume_handles);
> +	eficonfig_destroy(efi_menu, true);
> +
> +	return ret;
> +}
> +
> +/**
> + * eficonfig_select_file() - construct the file selection menu
> + *
> + * @file_info:	pointer to the file selection structure
> + * @root:	pointer to the file handle
> + * Return:	status code
> + */
> +static efi_status_t eficonfig_select_file(struct eficonfig_select_file_info *file_info,
> +					  struct efi_file_handle *root)
> +{
> +	efi_uintn_t len;
> +	efi_status_t ret;
> +	struct efimenu *efi_menu;
> +	struct efi_file_handle *f;
> +	struct efi_file_info *buf;
> +	struct list_head *pos, *n;
> +
> +	buf = calloc(1, sizeof(struct efi_file_info) + EFICONFIG_FILE_PATH_BUF_SIZE);
> +	if (!buf)
> +		return EFI_OUT_OF_RESOURCES;
> +
> +	while (!file_info->file_selected) {
> +		efi_menu = calloc(1, sizeof(struct efimenu));
> +		if (!efi_menu) {
> +			ret = EFI_OUT_OF_RESOURCES;
> +			goto out;
> +		}
> +		INIT_LIST_HEAD(&efi_menu->list);
> +
> +		ret = efi_file_open_int(root, &f, file_info->current_path, EFI_FILE_MODE_READ, 0);
> +		if (ret != EFI_SUCCESS) {
> +			eficonfig_print_msg("Reading volume failed!");
> +			ret = EFI_ABORTED;
> +			goto out;
> +		}
> +
> +		/* Read directory and construct menu structure */
> +		for (;;) {
> +			char *name, *p;
> +			int name_len;
> +			struct eficonfig_file_entry_data *info;
> +
> +			if (efi_menu->count >= EFICONFIG_ENTRY_NUM_MAX - 1)
> +				break;
> +
> +			len = sizeof(struct efi_file_info) + EFICONFIG_FILE_PATH_BUF_SIZE;
> +			ret = efi_file_read_int(f, &len, buf);
> +			if (ret != EFI_SUCCESS || len == 0)
> +				break;
> +
> +			info = calloc(1, sizeof(struct eficonfig_file_entry_data));
> +			if (!info) {
> +				ret = EFI_OUT_OF_RESOURCES;
> +				goto err;
> +			}
> +
> +			if (buf->attribute & EFI_FILE_DIRECTORY) {
> +				/* append u'/' at the end of directory name */
> +				name_len = utf16_utf8_strlen(buf->file_name) + 2;
> +
> +				/* filter out u'.' */
> +				if (name_len == 3 && buf->file_name[0] == u'.') {
> +					free(info);
> +					continue;
> +				}
> +
> +				name = calloc(1, name_len);
> +				if (!name) {
> +					free(info);
> +					ret = EFI_OUT_OF_RESOURCES;
> +					goto err;
> +				}
> +				p = name;
> +				utf16_utf8_strcpy(&p, buf->file_name);
> +				name[u16_strlen(buf->file_name)] = u'/';
> +
> +				info->is_directory = true;
> +			} else {
> +				name_len = utf16_utf8_strlen(buf->file_name) + 1;
> +				name = calloc(1, name_len);
> +				if (!name) {
> +					free(info);
> +					ret = EFI_OUT_OF_RESOURCES;
> +					goto err;
> +				}
> +				p = name;
> +				utf16_utf8_strcpy(&p, buf->file_name);
> +			}
> +
> +			info->file_name = u16_strdup(buf->file_name);
> +			if (!info->file_name) {
> +				free(info);
> +				free(name);
> +				ret = EFI_OUT_OF_RESOURCES;
> +				goto err;
> +			}
> +
> +			info->file_info = file_info;
> +			ret = append_entry(efi_menu, name, eficonfig_file_selected, info);
> +			if (ret != EFI_SUCCESS) {
> +				free(info);
> +				free(name);
> +				goto err;
> +			}
> +		}
> +
> +		ret = append_quit_entry(efi_menu);
> +		if (ret != EFI_SUCCESS)
> +			goto err;
> +
> +		ret = eficonfig_process_common(efi_menu, "  ** Select File **");
> +err:
> +		efi_file_close_int(f);
> +		list_for_each_safe(pos, n, &efi_menu->list) {
> +			struct eficonfig_entry *entry;
> +
> +			entry = list_entry(pos, struct eficonfig_entry, list);
> +			/* skip "Quit" */
> +			if (list_is_last(&entry->list, &efi_menu->list))
> +				break;
> +
> +			free(((struct eficonfig_file_entry_data *)(entry->data))->file_name);
> +		}
> +		eficonfig_destroy(efi_menu, true);
> +		if (ret != EFI_SUCCESS)
> +			break;
> +	}
> +
> +out:
> +	free(buf);
> +	return ret;
> +}
> +
> +/**
> + * handle_user_input() - handle user input
> + *
> + * @buf:	pointer to the buffer
> + * @buf_size:	size of the buffer
> + * @cursol_col:	cursol column for user input
> + * @msg:	pointer to the string to display
> + * Return:	status code
> + */
> +static efi_status_t handle_user_input(u16 *buf, int buf_size,
> +				      int cursol_col, char *msg)
> +{
> +	u16 *tmp;
> +	efi_status_t ret;
> +
> +	printf(ANSI_CLEAR_CONSOLE
> +	       ANSI_CURSOR_POSITION
> +	       "%s"
> +	       ANSI_CURSOR_POSITION
> +	       "  Press ENTER to complete, ESC/CTRL+C to quit",
> +	       0, 1, msg, 8, 1);
> +
> +	/* tmp is used to accept user cancel */
> +	tmp = calloc(1, buf_size * sizeof(u16));
> +	if (!tmp)
> +		return EFI_OUT_OF_RESOURCES;
> +
> +	ret = efi_console_get_u16_string(cin, tmp, buf_size, NULL, 4, cursol_col);
> +	if (ret == EFI_SUCCESS)
> +		u16_strcpy(buf, tmp);
> +
> +	free(tmp);
> +
> +	/* to stay the parent menu */
> +	ret = (ret == EFI_ABORTED) ? EFI_NOT_READY : ret;
> +
> +	return ret;
> +}
> +
> +/**
> + * eficonfig_boot_add_enter_description() - handle user input for description
> + *
> + * @data:	pointer to the internal boot option structure
> + * Return:	status code
> + */
> +static efi_status_t eficonfig_boot_add_enter_description(void *data)
> +{
> +	struct eficonfig_boot_option *bo = data;
> +
> +	return handle_user_input(bo->description, EFICONFIG_DESCRIPTION_MAX, 22,
> +				 "\n  ** Edit Description **\n"
> +				 "\n"
> +				 "  enter description: ");
> +}
> +
> +/**
> + * eficonfig_boot_add_optional_data() - handle user input for optional data
> + *
> + * @data:	pointer to the internal boot option structure
> + * Return:	status code
> + */
> +static efi_status_t eficonfig_boot_add_optional_data(void *data)
> +{
> +	struct eficonfig_boot_option *bo = data;
> +
> +	return handle_user_input(bo->optional_data, EFICONFIG_OPTIONAL_DATA_MAX, 24,
> +				 "\n  ** Edit Optional Data **\n"
> +				 "\n"
> +				 "  enter optional data:");
> +}
> +
> +/**
> + * eficonfig_boot_edit_save() - handler to save the boot option
> + *
> + * @data:	pointer to the internal boot option structure
> + * Return:	status code
> + */
> +static efi_status_t eficonfig_boot_edit_save(void *data)
> +{
> +	struct eficonfig_boot_option *bo = data;
> +
> +	if (u16_strlen(bo->description) == 0) {
> +		eficonfig_print_msg("Boot Description is empty!");
> +		bo->edit_completed = false;
> +		return EFI_NOT_READY;
> +	}
> +	if (u16_strlen(bo->file_info.current_path) == 0) {
> +		eficonfig_print_msg("File is not selected!");
> +		bo->edit_completed = false;
> +		return EFI_NOT_READY;
> +	}
> +
> +	bo->edit_completed = true;
> +
> +	return EFI_SUCCESS;
> +}
> +
> +/**
> + * eficonfig_select_file_handler() - handle user file selection
> + *
> + * @data:	pointer to the data
> + * Return:	status code
> + */
> +efi_status_t eficonfig_select_file_handler(void *data)
> +{
> +	size_t len;
> +	efi_status_t ret;
> +	struct list_head *pos, *n;
> +	struct efi_file_handle *root;
> +	struct eficonfig_filepath_info *item;
> +	struct eficonfig_select_file_info *file_info = data;
> +	struct eficonfig_select_file_info *tmp = NULL;
> +
> +	tmp = calloc(1, sizeof(struct eficonfig_select_file_info));
> +	if (!tmp)
> +		return EFI_OUT_OF_RESOURCES;
> +
> +	tmp->current_path = calloc(1, EFICONFIG_FILE_PATH_BUF_SIZE);
> +	if (!tmp->current_path) {
> +		free(tmp);
> +		return EFI_OUT_OF_RESOURCES;
> +	}
> +	INIT_LIST_HEAD(&tmp->filepath_list);
> +
> +	while (!tmp->file_selected) {
> +		tmp->current_volume = NULL;
> +		memset(tmp->current_path, 0, EFICONFIG_FILE_PATH_BUF_SIZE);
> +
> +		ret = eficonfig_select_volume(tmp);
> +		if (ret != EFI_SUCCESS)
> +			goto out;
> +
> +		if (!tmp->current_volume)
> +			return EFI_INVALID_PARAMETER;
> +
> +		ret = efi_open_volume_int(tmp->current_volume, &root);
> +		if (ret != EFI_SUCCESS)
> +			goto out;
> +
> +		ret = eficonfig_select_file(tmp, root);
> +		if (ret == EFI_ABORTED)
> +			continue;
> +		if (ret != EFI_SUCCESS)
> +			goto out;
> +	}
> +
> +out:
> +	if (ret == EFI_SUCCESS) {
> +		len = u16_strlen(tmp->current_path);
> +		len = (len >= EFICONFIG_FILE_PATH_MAX) ? (EFICONFIG_FILE_PATH_MAX - 1) : len;
> +		memcpy(file_info->current_path, tmp->current_path, len * sizeof(u16));
> +		file_info->current_path[len] = u'\0';
> +		file_info->current_volume = tmp->current_volume;
> +		file_info->dp_volume = tmp->dp_volume;
> +	}
> +
> +	list_for_each_safe(pos, n, &tmp->filepath_list) {
> +		item = list_entry(pos, struct eficonfig_filepath_info, list);
> +		list_del(&item->list);
> +		free(item->name);
> +		free(item);
> +	}
> +	free(tmp->current_path);
> +	free(tmp);
> +
> +	/* to stay the parent menu */
> +	ret = (ret == EFI_ABORTED) ? EFI_NOT_READY : ret;
> +
> +	return ret;
> +}
> +
> +/**
> + * eficonfig_get_unused_bootoption() - get unused "Boot####" index
> + *
> + * @buf:	pointer to the buffer to store boot option variable name
> + * @buf_size:	buffer size
> + * @index:	pointer to store the index in the BootOrder variable
> + * Return:	status code
> + */
> +efi_status_t eficonfig_get_unused_bootoption(u16 *buf, efi_uintn_t buf_size,
> +					     unsigned int *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;
> +}
> +
> +/**
> + * eficonfig_set_boot_option() - set boot option
> + *
> + * @varname:		pointer to variable name
> + * @dp:			pointer to device path
> + * @label:		pointer to label string
> + * @optional_data:	pointer to optional data
> + * Return:		status code
> + */
> +static efi_status_t eficonfig_set_boot_option(u16 *varname, struct efi_device_path *dp,
> +					      efi_uintn_t dp_size, 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 = dp_size;
> +	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(varname, &efi_global_variable_guid,
> +				   EFI_VARIABLE_NON_VOLATILE |
> +				   EFI_VARIABLE_BOOTSERVICE_ACCESS |
> +				   EFI_VARIABLE_RUNTIME_ACCESS,
> +				   size, p, false);
> +	free(p);
> +
> +	return ret;
> +}
> +
> +/**
> + * eficonfig_append_bootorder() - append new boot option in BootOrder variable
> + *
> + * @index:	"Boot####" index to append to BootOrder variable
> + * Return:	status code
> + */
> +efi_status_t eficonfig_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;
> +}
> +
> +/**
> + * create_boot_option_entry() - create boot option entry
> + *
> + * @efi_menu:	pointer to the efimenu structure
> + * @title:	pointer to the entry title
> + * @val:	pointer to boot option label
> + * @func:	callback of each entry
> + * @data:	pointer to the data to be passed to each entry callback
> + * Return:	status code
> + */
> +static efi_status_t create_boot_option_entry(struct efimenu *efi_menu, char *title, u16 *val,
> +					     eficonfig_entry_func func, void *data)
> +{
> +	u32 len;
> +	char *p, *buf;
> +
> +	len = strlen(title) + 1;
> +	if (val)
> +		len += utf16_utf8_strlen(val);
> +	buf = calloc(1, len);
> +	if (!buf)
> +		return EFI_OUT_OF_RESOURCES;
> +
> +	strcpy(buf, title);
> +	if (val) {
> +		p = buf + strlen(title);
> +		utf16_utf8_strcpy(&p, val);
> +	}
> +
> +	return append_entry(efi_menu, buf, func, data);
> +}
> +
> +/**
> + * prepare_file_selection_entry() - prepare file selection entry
> + *
> + * @efi_menu:	pointer to the efimenu structure
> + * @title:	pointer to the title string
> + * @file_info:	pointer to the file info
> + * Return:	status code
> + */
> +static efi_status_t prepare_file_selection_entry(struct efimenu *efi_menu, char *title,
> +						 struct eficonfig_select_file_info *file_info)
> +{
> +	u32 len;
> +	efi_status_t ret;
> +	u16 *file_name, *p;
> +	efi_handle_t handle;
> +	char devname[BOOTMENU_DEVICE_NAME_MAX] = {0};
> +
> +	/* get the device name only when the user already selected the file path */
> +	handle = efi_dp_find_obj(file_info->dp_volume, NULL, NULL);
> +	if (handle) {
> +		ret = efi_disk_get_device_name(handle, devname, BOOTMENU_DEVICE_NAME_MAX);
> +		if (ret != EFI_SUCCESS)
> +			return ret;
> +	}
> +
> +	/* append u'/' to devname, it is just for display purpose. */
> +	if (file_info->current_path[0] != u'\0' && file_info->current_path[0] != u'/')
> +		strlcat(devname, "/", BOOTMENU_DEVICE_NAME_MAX);
> +
> +	len = strlen(devname);
> +	len += utf16_utf8_strlen(file_info->current_path) + 1;
> +	file_name = calloc(1, len * sizeof(u16));
> +	if (!file_name)
> +		return ret;
> +
> +	p = file_name;
> +	utf8_utf16_strcpy(&p, devname);
> +	u16_strlcat(file_name, file_info->current_path, len);
> +	ret = create_boot_option_entry(efi_menu, title, file_name,
> +				       eficonfig_select_file_handler, file_info);
> +	free(file_name);
> +	return ret;
> +}
> +
> +/**
> + * eficonfig_show_boot_option() - prepare menu entry for editing boot option
> + *
> + * Construct the structures to create edit boot option menu
> + *
> + * @bo:		pointer to the boot option
> + * @header_str:	pointer to the header string
> + * Return:	status code
> + */
> +static efi_status_t eficonfig_show_boot_option(struct eficonfig_boot_option *bo,
> +					       char *header_str)
> +{
> +	struct efimenu *efi_menu;
> +	efi_status_t ret;
> +
> +	efi_menu = calloc(1, sizeof(struct efimenu));
> +	if (!efi_menu)
> +		return EFI_OUT_OF_RESOURCES;
> +
> +	INIT_LIST_HEAD(&efi_menu->list);
> +
> +	ret = create_boot_option_entry(efi_menu, "Description: ", bo->description,
> +				       eficonfig_boot_add_enter_description, bo);
> +	if (ret != EFI_SUCCESS)
> +		goto out;
> +
> +	ret = prepare_file_selection_entry(efi_menu, "File: ", &bo->file_info);
> +	if (ret != EFI_SUCCESS)
> +		goto out;
> +
> +	ret = prepare_file_selection_entry(efi_menu, "Initrd File: ", &bo->initrd_info);
> +	if (ret != EFI_SUCCESS)
> +		goto out;
> +
> +	ret = create_boot_option_entry(efi_menu, "Optional Data: ", bo->optional_data,
> +				       eficonfig_boot_add_optional_data, bo);
> +	if (ret != EFI_SUCCESS)
> +		goto out;
> +
> +	ret = create_boot_option_entry(efi_menu, "Save", NULL,
> +				       eficonfig_boot_edit_save, bo);
> +	if (ret != EFI_SUCCESS)
> +		goto out;
> +
> +	ret = create_boot_option_entry(efi_menu, "Quit", NULL,
> +				       eficonfig_process_quit, bo);
> +	if (ret != EFI_SUCCESS)
> +		goto out;
> +
> +	ret = eficonfig_process_common(efi_menu, header_str);
> +out:
> +	eficonfig_destroy(efi_menu, false);
> +
> +	return ret;
> +}
> +
> +/**
> + * fill_file_info() - fill the file info from efi_device_path structure
> + *
> + * @dp:		pointer to the device path
> + * @file_info:	pointer to the file info structure
> + * @device_dp:	pointer to the volume device path
> + */
> +static void fill_file_info(struct efi_device_path *dp,
> +			   struct eficonfig_select_file_info *file_info,
> +			   struct efi_device_path *device_dp)
> +{
> +	u16 *file_str, *p;
> +	struct efi_device_path *file_dp = NULL;
> +
> +	efi_dp_split_file_path(dp, &device_dp, &file_dp);
> +	file_info->dp_volume = device_dp;
> +	file_str = efi_dp_str(file_dp);
> +	/*
> +	 * efi_convert_device_path_to_text() automatically adds u'/' at the
> +	 * beginning of file name, remove u'/' before copying to current_path
> +	 */
> +	p = file_str;

On sandbox_defconfig one of my boot entries is:

Boot0000:
attributes: A-- (0x00000001)
   label: virtio0:1
   file_path:
/VenHw(e61d73b9-a384-4acc-aeab-82e828f3628b)/VenHw(63293792-adf5-9325-b99f-4e0e455c1b1e,00)/HD(1,GPT,c0107083-113b-4fd9-a09f-6260fc351a14,0x800,0xf7df)
   data:
     00000000: 62 00 6f 00 6f 00 74 00 6d 00 65 00 6e 00 75 00
b.o.o.t.m.e.n.u.
     00000010: 00 00

Inside the 'eficonfig' command I select 'edit' and get:

Segmentation violation
pc = 0x55ec2fca3e2d, pc_reloc = 0x57e2d

In my case p == NULL because there is no file name.

Best regards

Heinrich

> +	if (p[0] == u'/')
> +		p++;
> +
> +	u16_strcpy(file_info->current_path, p);
> +	efi_free_pool(file_dp);
> +	efi_free_pool(file_str);
> +}
> +
> +/**
> + * eficonfig_edit_boot_option() - prepare boot option structure for editing
> + *
> + * Construct the boot option structure and copy the existing value
> + *
> + * @varname:		pointer to the UEFI variable name
> + * @bo:			pointer to the boot option
> + * @load_option:	pointer to the load option
> + * @load_option_size:	size of the load option
> + * @header_str:		pointer to the header string
> + * Return	:	status code
> + */
> +static efi_status_t eficonfig_edit_boot_option(u16 *varname, struct eficonfig_boot_option *bo,
> +					       void *load_option, efi_uintn_t load_option_size,
> +					       char *header_str)
> +{
> +	size_t len;
> +	efi_status_t ret;
> +	char *tmp = NULL, *p;
> +	struct efi_load_option lo = {0};
> +	efi_uintn_t final_dp_size;
> +	struct efi_device_path *dp = NULL;
> +	efi_uintn_t size = load_option_size;
> +	struct efi_device_path *final_dp = NULL;
> +	struct efi_device_path *device_dp = NULL;
> +	struct efi_device_path *initrd_dp = NULL;
> +	struct efi_device_path *initrd_device_dp = NULL;
> +
> +	const struct efi_initrd_dp id_dp = {
> +		.vendor = {
> +			{
> +			DEVICE_PATH_TYPE_MEDIA_DEVICE,
> +			DEVICE_PATH_SUB_TYPE_VENDOR_PATH,
> +			sizeof(id_dp.vendor),
> +			},
> +			EFI_INITRD_MEDIA_GUID,
> +		},
> +		.end = {
> +			DEVICE_PATH_TYPE_END,
> +			DEVICE_PATH_SUB_TYPE_END,
> +			sizeof(id_dp.end),
> +		}
> +	};
> +
> +	bo->file_info.current_path = calloc(1, EFICONFIG_FILE_PATH_BUF_SIZE);
> +	if (!bo->file_info.current_path) {
> +		ret =  EFI_OUT_OF_RESOURCES;
> +		goto out;
> +	}
> +
> +	bo->initrd_info.current_path = calloc(1, EFICONFIG_FILE_PATH_BUF_SIZE);
> +	if (!bo->file_info.current_path) {
> +		ret =  EFI_OUT_OF_RESOURCES;
> +		goto out;
> +	}
> +
> +	bo->description = calloc(1, EFICONFIG_DESCRIPTION_MAX * sizeof(u16));
> +	if (!bo->description) {
> +		ret =  EFI_OUT_OF_RESOURCES;
> +		goto out;
> +	}
> +
> +	bo->optional_data = calloc(1, EFICONFIG_OPTIONAL_DATA_MAX * sizeof(u16));
> +	if (!bo->optional_data) {
> +		ret =  EFI_OUT_OF_RESOURCES;
> +		goto out;
> +	}
> +
> +	/* copy the preset value */
> +	if (load_option) {
> +		ret = efi_deserialize_load_option(&lo, load_option, &size);
> +		if (ret != EFI_SUCCESS)
> +			goto out;
> +
> +		if (!lo.label || (lo.label && u16_strlen(lo.label) >= EFICONFIG_DESCRIPTION_MAX)) {
> +			ret = EFI_INVALID_PARAMETER;
> +			goto out;
> +		}
> +		u16_strcpy(bo->description, lo.label);
> +
> +		/* EFI image file path is a first instance */
> +		if (lo.file_path)
> +			fill_file_info(lo.file_path, &bo->file_info, device_dp);
> +
> +		/* Initrd file path(optional) is placed at second instance. */
> +		initrd_dp = efi_dp_from_lo(&lo, &efi_lf2_initrd_guid);
> +		if (initrd_dp) {
> +			fill_file_info(initrd_dp, &bo->initrd_info, initrd_device_dp);
> +			efi_free_pool(initrd_dp);
> +		}
> +
> +		if (size > 0)
> +			memcpy(bo->optional_data, lo.optional_data, size);
> +	}
> +
> +	while (1) {
> +		ret = eficonfig_show_boot_option(bo, header_str);
> +		if (ret == EFI_SUCCESS && bo->edit_completed)
> +			break;
> +		if (ret == EFI_NOT_READY)
> +			continue;
> +		if (ret != EFI_SUCCESS)
> +			goto out;
> +	}
> +
> +	if (bo->initrd_info.dp_volume) {
> +		dp = create_selected_device_path(&bo->initrd_info);
> +		if (!dp) {
> +			ret = EFI_OUT_OF_RESOURCES;
> +			goto out;
> +		}
> +		initrd_dp = efi_dp_append((const struct efi_device_path *)&id_dp, dp);
> +		efi_free_pool(dp);
> +	}
> +
> +	dp = create_selected_device_path(&bo->file_info);
> +	if (!dp) {
> +		ret = EFI_OUT_OF_RESOURCES;
> +		goto out;
> +	}
> +	final_dp_size = efi_dp_size(dp) + sizeof(END);
> +	if (initrd_dp) {
> +		final_dp = efi_dp_concat(dp, initrd_dp);
> +		final_dp_size += efi_dp_size(initrd_dp) + sizeof(END);
> +	} else {
> +		final_dp = efi_dp_dup(dp);
> +	}
> +	efi_free_pool(dp);
> +
> +	if (!final_dp)
> +		goto out;
> +
> +	len = utf16_utf8_strlen(bo->optional_data) + 1;
> +	tmp = calloc(1, len);
> +	if (!tmp)
> +		goto out;
> +	p = tmp;
> +	utf16_utf8_strncpy(&p, bo->optional_data, u16_strlen(bo->optional_data));
> +
> +	ret = eficonfig_set_boot_option(varname, final_dp, final_dp_size, bo->description, tmp);
> +	if (ret != EFI_SUCCESS)
> +		goto out;
> +out:
> +	free(tmp);
> +	free(bo->optional_data);
> +	free(bo->description);
> +	free(bo->file_info.current_path);
> +	free(bo->initrd_info.current_path);
> +	efi_free_pool(device_dp);
> +	efi_free_pool(initrd_device_dp);
> +	efi_free_pool(initrd_dp);
> +	efi_free_pool(final_dp);
> +
> +	return ret;
> +}
> +
> +/**
> + * eficonfig_process_add_boot_option() - handler to add boot option
> + *
> + * @data:	pointer to the data for each entry
> + * Return:	status code
> + */
> +static efi_status_t eficonfig_process_add_boot_option(void *data)
> +{
> +	u16 varname[9];
> +	efi_status_t ret;
> +	struct eficonfig_boot_option *bo = NULL;
> +
> +	bo = calloc(1, sizeof(struct eficonfig_boot_option));
> +	if (!bo)
> +		return EFI_OUT_OF_RESOURCES;
> +
> +	ret = eficonfig_get_unused_bootoption(varname, sizeof(varname), &bo->boot_index);
> +	if (ret != EFI_SUCCESS)
> +		return ret;
> +
> +	ret = eficonfig_edit_boot_option(varname, bo, NULL, 0,  "  ** Add Boot Option ** ");
> +	if (ret != EFI_SUCCESS)
> +		goto out;
> +
> +	ret = eficonfig_append_bootorder((u16)bo->boot_index);
> +	if (ret != EFI_SUCCESS)
> +		goto out;
> +
> +out:
> +	free(bo);
> +
> +	/* to stay the parent menu */
> +	ret = (ret == EFI_ABORTED) ? EFI_SUCCESS : ret;
> +
> +	return ret;
> +}
> +
> +/**
> + * eficonfig_init() - do required initialization for eficonfig command
> + *
> + * Return:	status code
> + */
> +static efi_status_t eficonfig_init(void)
> +{
> +	efi_status_t ret;
> +	static bool init;
> +	struct efi_handler *handler;
> +
> +	if (!init) {
> +		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;
> +	}
> +
> +	init = true;
> +
> +	return ret;
> +}
> +
> +static const struct eficonfig_item maintenance_menu_items[] = {
> +	{"Add Boot Option", eficonfig_process_add_boot_option},
> +	{"Quit", eficonfig_process_quit},
> +};
> +
> +/**
> + * do_eficonfig() - execute `eficonfig` command
> + *
> + * @cmdtp:	table entry describing command
> + * @flag:	bitmap indicating how the command was invoked
> + * @argc:	number of arguments
> + * @argv:	command line arguments
> + * Return:	status code
> + */
> +static int do_eficonfig(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[])
> +{
> +	efi_status_t ret;
> +	struct efimenu *efi_menu;
> +
> +	if (argc > 1)
> +		return CMD_RET_USAGE;
> +
> +	ret = efi_init_obj_list();
> +	if (ret != EFI_SUCCESS) {
> +		log_err("Error: Cannot initialize UEFI sub-system, r = %lu\n",
> +			ret & ~EFI_ERROR_MASK);
> +
> +		return CMD_RET_FAILURE;
> +	}
> +
> +	ret = eficonfig_init();
> +	if (ret != EFI_SUCCESS)
> +		return CMD_RET_FAILURE;
> +
> +	while (1) {
> +		efi_menu = eficonfig_create_fixed_menu(maintenance_menu_items,
> +						       ARRAY_SIZE(maintenance_menu_items));
> +		if (!efi_menu)
> +			return CMD_RET_FAILURE;
> +
> +		ret = eficonfig_process_common(efi_menu, "  ** UEFI Maintenance Menu **");
> +		eficonfig_destroy(efi_menu, false);
> +
> +		if (ret == EFI_ABORTED)
> +			break;
> +	}
> +
> +	return CMD_RET_SUCCESS;
> +}
> +
> +U_BOOT_CMD(
> +	eficonfig, 1, 0, do_eficonfig,
> +	"provide menu-driven UEFI variable maintenance interface",
> +	""
> +);
> diff --git a/include/efi_config.h b/include/efi_config.h
> new file mode 100644
> index 0000000000..aaff5c7cc0
> --- /dev/null
> +++ b/include/efi_config.h
> @@ -0,0 +1,90 @@
> +/* SPDX-License-Identifier: GPL-2.0+ */
> +/*
> + *  Menu-driven UEFI Variable maintenance
> + *
> + *  Copyright (c) 2022 Masahisa Kojima, Linaro Limited
> + */
> +
> +#ifndef _EFI_CONFIG_H
> +#define _EFI_CONFIG_H
> +
> +#define EFICONFIG_ENTRY_NUM_MAX 99
> +#define EFICONFIG_FILE_PATH_MAX 512
> +#define EFICONFIG_FILE_PATH_BUF_SIZE (EFICONFIG_FILE_PATH_MAX * sizeof(u16))
> +
> +typedef efi_status_t (*eficonfig_entry_func)(void *data);
> +
> +/**
> + * struct eficonfig_entry - menu entry structure
> + *
> + * @num:	menu entry index
> + * @title:	title of entry
> + * @key:	unique key
> + * @efi_menu:	pointer to the menu structure
> + * @func:	callback function to be called when this entry is selected
> + * @data:	data to be passed to the callback function
> + * @list:	list structure
> + */
> +struct eficonfig_entry {
> +	u32 num;
> +	char *title;
> +	char key[3];
> +	struct efimenu *efi_menu;
> +	eficonfig_entry_func func;
> +	void *data;
> +	struct list_head list;
> +};
> +
> +/**
> + * struct efimenu - efi menu structure
> + *
> + * @delay:		delay for autoboot
> + * @active:		active menu entry index
> + * @count:		total count of menu entry
> + * @menu_header:	menu header string
> + * @list:		menu entry list structure
> + */
> +struct efimenu {
> +	int delay;
> +	int active;
> +	int count;
> +	char *menu_header;
> +	struct list_head list;
> +};
> +
> +/**
> + * struct eficonfig_item - structure to construct eficonfig_entry
> + *
> + * @title:	title of entry
> + * @func:	callback function to be called when this entry is selected
> + * @data:	data to be passed to the callback function
> + */
> +struct eficonfig_item {
> +	char *title;
> +	eficonfig_entry_func func;
> +	void *data;
> +};
> +
> +/**
> + * struct eficonfig_select_file_info - structure to be used for file selection
> + *
> + * @current_volume:	pointer to the efi_simple_file_system_protocol
> + * @dp_volume:		pointer to device path of the selected device
> + * @current_path:	pointer to the selected file path string
> + * @filepath_list:	list_head structure for file path list
> + * @file_selectred:	flag indicates file selecting status
> + */
> +struct eficonfig_select_file_info {
> +	struct efi_simple_file_system_protocol *current_volume;
> +	struct efi_device_path *dp_volume;
> +	u16 *current_path;
> +	struct list_head filepath_list;
> +	bool file_selected;
> +};
> +
> +void eficonfig_print_msg(char *msg);
> +efi_status_t eficonfig_process_quit(void *data);
> +efi_status_t eficonfig_process_common(struct efimenu *efi_menu, char *menu_header);
> +efi_status_t eficonfig_select_file_handler(void *data);
> +
> +#endif
> diff --git a/include/efi_loader.h b/include/efi_loader.h
> index b0d6fff67c..49e7d1e613 100644
> --- a/include/efi_loader.h
> +++ b/include/efi_loader.h
> @@ -142,6 +142,11 @@ static inline efi_status_t efi_launch_capsules(void)
>   	EFI_GUID(0x63293792, 0xadf5, 0x9325, \
>   		 0xb9, 0x9f, 0x4e, 0x0e, 0x45, 0x5c, 0x1b, 0x1e)
>
> +/* GUID for the auto generated boot menu entry */
> +#define EFICONFIG_AUTO_GENERATED_ENTRY_GUID \
> +	EFI_GUID(0x38c1acc1, 0x9fc0, 0x41f0, \
> +		 0xb9, 0x01, 0xfa, 0x74, 0xd6, 0xd6, 0xe4, 0xde)
> +
>   /* Use internal device tree when starting UEFI application */
>   #define EFI_FDT_USE_INTERNAL NULL
>
> @@ -226,6 +231,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;
>
> @@ -249,6 +257,9 @@ extern const struct efi_hii_string_protocol efi_hii_string;
>
>   uint16_t *efi_dp_str(struct efi_device_path *dp);
>
> +/* GUID for the auto generated boot menu entry */
> +extern const efi_guid_t efi_guid_bootmenu_auto_generated;
> +
>   /* GUID of the U-Boot root node */
>   extern const efi_guid_t efi_u_boot_guid;
>   #ifdef CONFIG_SANDBOX
> @@ -314,6 +325,8 @@ 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 char __efi_runtime_start[], __efi_runtime_stop[];
>   extern char __efi_runtime_rel_start[], __efi_runtime_rel_stop[];
> @@ -891,6 +904,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
>    *
> @@ -1064,4 +1079,32 @@ 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,
> +		 u16 *buf, efi_uintn_t count, efi_console_filter_func filer_func,
> +		 int row, int col);
> +
> +efi_status_t eficonfig_get_unused_bootoption(u16 *buf,
> +					     efi_uintn_t buf_size, u32 *index);
> +efi_status_t eficonfig_append_bootorder(u16 index);
> +
> +efi_status_t efi_disk_get_device_name(const efi_handle_t handle, char *buf, int size);
> +
>   #endif /* _EFI_LOADER_H */
> diff --git a/lib/efi_loader/efi_bootmgr.c b/lib/efi_loader/efi_bootmgr.c
> index 234073ecb7..ede9116b3c 100644
> --- a/lib/efi_loader/efi_bootmgr.c
> +++ b/lib/efi_loader/efi_bootmgr.c
> @@ -19,6 +19,9 @@
>   static const struct efi_boot_services *bs;
>   static const struct efi_runtime_services *rs;
>
> +const efi_guid_t efi_guid_bootmenu_auto_generated =
> +		EFICONFIG_AUTO_GENERATED_ENTRY_GUID;
> +
>   /*
>    * bootmgr implements the logic of trying to find a payload to boot
>    * based on the BootOrder + BootXXXX variables, and then loading it.
> 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 3164fd484e..5be509f0d6 100644
> --- a/lib/efi_loader/efi_console.c
> +++ b/lib/efi_loader/efi_console.c
> @@ -7,6 +7,7 @@
>
>   #define LOG_CATEGORY LOGC_EFI
>
> +#include <ansi.h>
>   #include <common.h>
>   #include <charset.h>
>   #include <malloc.h>
> @@ -1318,3 +1319,72 @@ 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
> + * @buf:		buffer to store user input string in UTF16
> + * @count:		number of u16 string including NULL terminator that buf has
> + * @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,
> +					u16 *buf, efi_uintn_t count,
> +					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
> +	       ANSI_CLEAR_LINE_TO_END
> +	       ANSI_CURSOR_SHOW, row, col);
> +
> +	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
> +			       "%ls"
> +			       ANSI_CLEAR_LINE_TO_END, row, col, buf);
> +			continue;
> +		} else if (key.unicode_char == u'\r') {
> +			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 >= (count - 1))
> +			continue;
> +
> +		buf[len] = key.unicode_char;
> +		len++;
> +		printf(ANSI_CURSOR_POSITION "%ls", row, col, buf);
> +	}
> +}
> diff --git a/lib/efi_loader/efi_disk.c b/lib/efi_loader/efi_disk.c
> index 16d14b0429..4c9c4cfec8 100644
> --- a/lib/efi_loader/efi_disk.c
> +++ b/lib/efi_loader/efi_disk.c
> @@ -769,3 +769,53 @@ efi_status_t efi_disk_init(void)
>
>   	return EFI_SUCCESS;
>   }
> +
> +/**
> + * efi_disk_get_device_name() - get U-Boot device name associated with EFI handle
> + *
> + * @handle:	pointer to the EFI handle
> + * @buf:	pointer to the buffer to store the string
> + * @size:	size of buffer
> + * Return:	status code
> + */
> +efi_status_t efi_disk_get_device_name(const efi_handle_t handle, char *buf, int size)
> +{
> +	int count;
> +	int diskid;
> +	enum uclass_id id;
> +	unsigned int part;
> +	struct udevice *dev;
> +	struct blk_desc *desc;
> +	const char *if_typename;
> +	bool is_partition = false;
> +	struct disk_part *part_data;
> +
> +	if (!handle || !buf || !size)
> +		return EFI_INVALID_PARAMETER;
> +
> +	dev = handle->dev;
> +	id = device_get_uclass_id(dev);
> +	if (id == UCLASS_BLK) {
> +		desc = dev_get_uclass_plat(dev);
> +	} else if (id == UCLASS_PARTITION) {
> +		desc = dev_get_uclass_plat(dev_get_parent(dev));
> +		is_partition = true;
> +	} else {
> +		return EFI_INVALID_PARAMETER;
> +	}
> +	if_typename = blk_get_if_type_name(desc->if_type);
> +	diskid = desc->devnum;
> +
> +	if (is_partition) {
> +		part_data = dev_get_uclass_plat(dev);
> +		part = part_data->partnum;
> +		count = snprintf(buf, size, "%s %d:%d", if_typename, diskid, part);
> +	} else {
> +		count = snprintf(buf, size, "%s %d", if_typename, diskid);
> +	}
> +
> +	if (count < 0 || (count + 1) > size)
> +		return EFI_INVALID_PARAMETER;
> +
> +	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 *


^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH v11 1/9] eficonfig: menu-driven addition of UEFI boot option
  2022-08-17  9:36 ` [PATCH v11 1/9] eficonfig: menu-driven addition of UEFI boot option Masahisa Kojima
  2022-08-18  6:32   ` Heinrich Schuchardt
@ 2022-08-18  6:43   ` Heinrich Schuchardt
  2022-08-18  8:59     ` Masahisa Kojima
  1 sibling, 1 reply; 25+ messages in thread
From: Heinrich Schuchardt @ 2022-08-18  6:43 UTC (permalink / raw)
  To: Masahisa Kojima
  Cc: Ilias Apalodimas, Simon Glass, Takahiro Akashi, Mark Kettenis,
	Michal Simek, Ovidiu Panait, Ashok Reddy Soma, John Keeping,
	Thomas Huth, Chris Morgan, Huang Jianan, u-boot

On 8/17/22 11:36, Masahisa Kojima wrote:
> This commit add the "eficonfig" command.
> The "eficonfig" command implements the menu-driven UEFI boot option
> maintenance feature. This commit implements the addition of
> new boot option. User can select the block device volume having
> efi_simple_file_system_protocol and select the file corresponding
> to the Boot#### variable. User can also enter the description and
> optional_data of the BOOT#### variable in utf8.
>
> This commit adds "include/efi_config.h", it contains the common
> definition to be used from other menus such as UEFI Secure Boot
> key management.
>
> Signed-off-by: Masahisa Kojima <masahisa.kojima@linaro.org>
> ---
> Changes in v11:
> - refactor menu entry construction, directly use eficonfig_entry structure
> - remove reading directory info to calculate the number of entry
> - fix invalid efi_free_pool() in ill_file_info()
> - use ANSI_CURSOR_POSITION and ANSI_CLEAR_LINE instead of printf("\n")
>    since current eficonfig implementation does not handle console size correctly.
>    printf("\n") at the outside of console size breaks the console output.
>
> Changes in v10:
> - add initrd file selection
> - do refactoring
> - eficonfig_process_common() use list structure
> - remove u'/' before copying file_path into current_path
> - fix typos
> - check snprintf error
>
> Changes in v9:
> - move "efi_guid_bootmenu_auto_generated definition" into efi_bootmgr.c
>    to address build error when CMD_EFICONFIG is disabled
> - fix typos and comment
> - remove file system information from error message
> - remove unreachable code in eficonfig_choice_entry()
> - single printf() call as much as possible
> - call only getchar() in  eficonfig_print_msg()
> - filter out '.' entry from file selection
> - update the efi_disk_get_device_name() implementation
> - add function comment
>
> Changes in v8:
> - command name is change from "efimenu" to "eficonfig"
> - function and struct prefixes is changed to "eficonfig"
> - fix menu header string
>
> Changes in v7:
> - add "efimenu" command and uefi variable maintenance code
>    moved into cmd/efimenu.c
> - create include/efimenu.h to define the common definition for
>    the other menu such as UEFI Secure Boot key management
> - update boot option edit UI, user can select description, file,
>    and optional_data to edit in the same menu like following.
>
>    ** Edit Boot Option **
>
>       Description: debian
>       File: virtio 0:1/EFI\debian\grubaa64.efi
>       Optional Data: test
>       Save
>       Quit
>
> - remove exit parameter from efimenu_process_common()
> - menu title type is changed from u16 to char
> - efimenu_process_common() add menu title string
> - reduce printf/puts function call for displaying the menu
> - efi_console_get_u16_string() accept 0 length to allow
>    optional_data is empty
> - efi_console_get_u16_string() the "size" parameter name is changes to "count"
> - efimenu is now designed to maintain the UEFI variables, remove autoboot related code
> - remove one empty line before "Quit" entry
> - efimenu_init() processes only the first time
>
> Changes in v6:
> - fix typos
> - modify volume name to match U-Boot syntax
> - compile in CONFIG_EFI_LOADER=n and CONFIG_CMD_BOOTEFI_BOOTMGR=n
> - simplify u16_strncmp() usage
> - support "a\b.efi" file path, use link list to handle filepath
> - modify length check condition
> - UEFI related menu items only appears with CONFIG_AUTOBOOT_MENU_SHOW=y
>
> Changes in v5:
> - remove forward declarations
> - add const qualifier for menu items
> - fix the possible unaligned access for directory info access
> - split into three commit 1)add boot option 2) delete boot option 3)change boot order
>    This commit is 1)add boot option.
> - fix file name buffer allocation size, it should be EFI_BOOTMENU_FILE_PATH_MAX * sizeof(u16)
> - fix wrong size checking for file selection
>
> 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 RFC v3:
>   not included in v3 series
>
> Changes in RFC 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/Kconfig                   |    7 +
>   cmd/Makefile                  |    1 +
>   cmd/eficonfig.c               | 1491 +++++++++++++++++++++++++++++++++
>   include/efi_config.h          |   90 ++
>   include/efi_loader.h          |   43 +
>   lib/efi_loader/efi_bootmgr.c  |    3 +
>   lib/efi_loader/efi_boottime.c |   52 +-
>   lib/efi_loader/efi_console.c  |   70 ++
>   lib/efi_loader/efi_disk.c     |   50 ++
>   lib/efi_loader/efi_file.c     |   75 +-
>   10 files changed, 1835 insertions(+), 47 deletions(-)
>   create mode 100644 cmd/eficonfig.c
>   create mode 100644 include/efi_config.h
>
> diff --git a/cmd/Kconfig b/cmd/Kconfig
> index 211ebe9c87..a1e8613c56 100644
> --- a/cmd/Kconfig
> +++ b/cmd/Kconfig
> @@ -1928,6 +1928,13 @@ config CMD_EFIDEBUG
>   	  particularly for managing boot parameters as  well as examining
>   	  various EFI status for debugging.
>
> +config CMD_EFICONFIG
> +	bool "eficonfig - provide menu-driven uefi variables maintenance interface"
> +	depends on CMD_BOOTEFI_BOOTMGR
> +	help
> +	  Enable the 'eficonfig' command which provides the menu-driven UEFI
> +	  variable maintenance interface.
> +
>   config CMD_EXCEPTION
>   	bool "exception - raise exception"
>   	depends on ARM || RISCV || SANDBOX || X86
> diff --git a/cmd/Makefile b/cmd/Makefile
> index 6e87522b62..18f5cb890d 100644
> --- a/cmd/Makefile
> +++ b/cmd/Makefile
> @@ -63,6 +63,7 @@ obj-$(CONFIG_ENV_IS_IN_EEPROM) += eeprom.o
>   obj-$(CONFIG_CMD_EEPROM) += eeprom.o
>   obj-$(CONFIG_EFI) += efi.o
>   obj-$(CONFIG_CMD_EFIDEBUG) += efidebug.o
> +obj-$(CONFIG_CMD_EFICONFIG) += eficonfig.o
>   obj-$(CONFIG_CMD_ELF) += elf.o
>   obj-$(CONFIG_CMD_EROFS) += erofs.o
>   obj-$(CONFIG_HUSH_PARSER) += exit.o
> diff --git a/cmd/eficonfig.c b/cmd/eficonfig.c
> new file mode 100644
> index 0000000000..39fbd3f0ad
> --- /dev/null
> +++ b/cmd/eficonfig.c
> @@ -0,0 +1,1491 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + *  Menu-driven UEFI Variable maintenance
> + *
> + *  Copyright (c) 2022 Masahisa Kojima, Linaro Limited
> + */
> +
> +#include <ansi.h>
> +#include <common.h>
> +#include <charset.h>
> +#include <efi_loader.h>
> +#include <efi_load_initrd.h>
> +#include <efi_config.h>
> +#include <efi_variable.h>
> +#include <log.h>
> +#include <malloc.h>
> +#include <menu.h>
> +#include <watchdog.h>
> +#include <asm/unaligned.h>
> +#include <linux/delay.h>
> +
> +static struct efi_simple_text_input_protocol *cin;
> +
> +#define EFICONFIG_DESCRIPTION_MAX 32
> +#define EFICONFIG_OPTIONAL_DATA_MAX 64
> +
> +/**
> + * struct eficonfig_filepath_info - structure to be used to store file path
> + *
> + * @name:	file or directory name
> + * @list:	list structure
> + */
> +struct eficonfig_filepath_info {
> +	u16 *name;
> +	struct list_head list;
> +};
> +
> +/**
> + * struct eficonfig_boot_option - structure to be used for updating UEFI boot option
> + *
> + * @file_info:		user selected file info
> + * @initrd_info:	user selected initrd file info
> + * @boot_index:		index of the UEFI BootOrder variable
> + * @description:	pointer to the description string
> + * @optional_data:	pointer to the optional_data
> + * @edit_completed:	flag indicates edit complete
> + */
> +struct eficonfig_boot_option {
> +	struct eficonfig_select_file_info file_info;
> +	struct eficonfig_select_file_info initrd_info;
> +	unsigned int boot_index;
> +	u16 *description;
> +	u16 *optional_data;
> +	bool edit_completed;
> +};
> +
> +/**
> + * struct eficonfig_volume_entry_data - structure to be used to store volume info
> + *
> + * @file_info:	pointer to file info structure
> + * @v:		pointer to the protocol interface
> + * @dp:		pointer to the device path
> + */
> +struct eficonfig_volume_entry_data {
> +	struct eficonfig_select_file_info *file_info;
> +	struct efi_simple_file_system_protocol *v;
> +	struct efi_device_path *dp;
> +};
> +
> +/**
> + * struct eficonfig_file_entry_data - structure to be used to store file info
> + *
> + * @file_info:		pointer to file info structure
> + * @is_directory:	flag to indentify the directory or file
> + * @file_name:		name of directory or file
> + */
> +struct eficonfig_file_entry_data {
> +	struct eficonfig_select_file_info *file_info;
> +	bool is_directory;
> +	u16 *file_name;
> +};
> +
> +/**
> + * eficonfig_print_msg() - print message
> + *
> + * display the message to the user, user proceeds the screen
> + * with any key press.
> + *
> + * @items:		pointer to the structure of each menu entry
> + * @count:		the number of menu entry
> + * @menu_header:	pointer to the menu header string
> + * Return:	status code
> + */
> +void eficonfig_print_msg(char *msg)
> +{
> +	/* Flush input */
> +	while (tstc())
> +		getchar();
> +
> +	printf(ANSI_CURSOR_HIDE
> +	       ANSI_CLEAR_CONSOLE
> +	       ANSI_CURSOR_POSITION
> +	       "%s\n\n  Press any key to continue", 3, 4, msg);
> +
> +	getchar();
> +}
> +
> +/**
> + * eficonfig_print_entry() - print each menu entry
> + *
> + * @data:	pointer to the data associated with each menu entry
> + */
> +static void eficonfig_print_entry(void *data)
> +{
> +	struct eficonfig_entry *entry = data;
> +	int reverse = (entry->efi_menu->active == entry->num);
> +
> +	/* TODO: support scroll or page for many entries */
> +
> +	/*
> +	 * Move cursor to line where the entry will be drawn (entry->num)
> +	 * First 3 lines(menu header) + 1 empty line
> +	 */
> +	printf(ANSI_CURSOR_POSITION, entry->num + 4, 7);
> +
> +	if (reverse)
> +		puts(ANSI_COLOR_REVERSE);
> +
> +	printf("%s", entry->title);
> +
> +	if (reverse)
> +		puts(ANSI_COLOR_RESET);
> +}
> +
> +/**
> + * eficonfig_display_statusline() - print status line
> + *
> + * @m:	pointer to the menu structure
> + */
> +static void eficonfig_display_statusline(struct menu *m)
> +{
> +	struct eficonfig_entry *entry;
> +
> +	if (menu_default_choice(m, (void *)&entry) < 0)
> +		return;
> +
> +	printf(ANSI_CURSOR_POSITION
> +	      "\n%s\n"
> +	       ANSI_CURSOR_POSITION ANSI_CLEAR_LINE ANSI_CURSOR_POSITION
> +	       "  Press UP/DOWN to move, ENTER to select, ESC/CTRL+C to quit"
> +	       ANSI_CLEAR_LINE_TO_END ANSI_CURSOR_POSITION ANSI_CLEAR_LINE,
> +	       1, 1, entry->efi_menu->menu_header, entry->efi_menu->count + 5, 1,
> +	       entry->efi_menu->count + 6, 1, entry->efi_menu->count + 7, 1);
> +}
> +
> +/**
> + * eficonfig_choice_entry() - user key input handler
> + *
> + * @data:	pointer to the efimenu structure
> + * Return:	key string to identify the selected entry
> + */
> +static char *eficonfig_choice_entry(void *data)
> +{
> +	int esc = 0;
> +	struct list_head *pos, *n;
> +	struct eficonfig_entry *entry;
> +	enum bootmenu_key key = KEY_NONE;
> +	struct efimenu *efi_menu = data;
> +
> +	while (1) {
> +		bootmenu_loop((struct bootmenu_data *)efi_menu, &key, &esc);
> +
> +		switch (key) {
> +		case KEY_UP:
> +			if (efi_menu->active > 0)
> +				--efi_menu->active;
> +			/* no menu key selected, regenerate menu */
> +			return NULL;
> +		case KEY_DOWN:
> +			if (efi_menu->active < efi_menu->count - 1)
> +				++efi_menu->active;
> +			/* no menu key selected, regenerate menu */
> +			return NULL;
> +		case KEY_SELECT:
> +			list_for_each_safe(pos, n, &efi_menu->list) {
> +				entry = list_entry(pos, struct eficonfig_entry, list);
> +				if (entry->num == efi_menu->active)
> +					return entry->key;
> +			}
> +		case KEY_QUIT:
> +			/* Quit by choosing the last entry */
> +			entry = list_last_entry(&efi_menu->list, struct eficonfig_entry, list);
> +			return entry->key;
> +		default:
> +			break;
> +		}
> +	}
> +}
> +
> +/**
> + * eficonfig_destroy() - destroy efimenu
> + *
> + * @efi_menu:	pointer to the efimenu structure
> + * @flag:	flag to free the allocated data
> + */
> +static void eficonfig_destroy(struct efimenu *efi_menu, bool flag)
> +{
> +	struct list_head *pos, *n;
> +	struct eficonfig_entry *entry;
> +
> +	list_for_each_safe(pos, n, &efi_menu->list) {
> +		entry = list_entry(pos, struct eficonfig_entry, list);
> +		free(entry->title);
> +		if (flag)
> +			free(entry->data);
> +		list_del(&entry->list);
> +		free(entry);
> +	}
> +	free(efi_menu->menu_header);
> +	free(efi_menu);
> +}
> +
> +/**
> + * eficonfig_process_quit() - callback function for "Quit" entry
> + *
> + * @data:	pointer to the data
> + * Return:	status code
> + */
> +efi_status_t eficonfig_process_quit(void *data)
> +{
> +	return EFI_ABORTED;
> +}
> +
> +/**
> + * append_entry() - append menu item
> + *
> + * @efi_menu:	pointer to the efimenu structure
> + * @title:	pointer to the entry title
> + * @func:	callback of each entry
> + * @data:	pointer to the data to be passed to each entry callback
> + * Return:	status code
> + */
> +static efi_status_t append_entry(struct efimenu *efi_menu,
> +				 char *title, eficonfig_entry_func func, void *data)
> +{
> +	struct eficonfig_entry *entry;
> +
> +	if (efi_menu->count >= EFICONFIG_ENTRY_NUM_MAX)
> +		return EFI_OUT_OF_RESOURCES;
> +
> +	entry = calloc(1, sizeof(struct eficonfig_entry));
> +	if (!entry)
> +		return EFI_OUT_OF_RESOURCES;
> +
> +	entry->title = title;
> +	sprintf(entry->key, "%d", efi_menu->count);
> +	entry->efi_menu = efi_menu;
> +	entry->func = func;
> +	entry->data = data;
> +	entry->num = efi_menu->count++;
> +	list_add_tail(&entry->list, &efi_menu->list);
> +
> +	return EFI_SUCCESS;
> +}
> +
> +/**
> + * append_quit_entry() - append quit entry
> + *
> + * @efi_menu:	pointer to the efimenu structure
> + * Return:	status code
> + */
> +static efi_status_t append_quit_entry(struct efimenu *efi_menu)
> +{
> +	char *title;
> +	efi_status_t ret;
> +
> +	title = strdup("Quit");
> +	if (!title)
> +		return EFI_OUT_OF_RESOURCES;
> +
> +	ret = append_entry(efi_menu, title, eficonfig_process_quit, NULL);
> +	if (ret != EFI_SUCCESS)
> +		free(title);
> +
> +	return ret;
> +}
> +
> +/**
> + * eficonfig_create_fixed_menu() - create fixed entry menu structure
> + *
> + * @items:	pointer to the menu entry item
> + * @count:	the number of menu entry
> + * Return:	pointer to the efimenu structure
> + */
> +void *eficonfig_create_fixed_menu(const struct eficonfig_item *items, int count)
> +{
> +	u32 i;
> +	char *title;
> +	efi_status_t ret;
> +	struct efimenu *efi_menu;
> +	const struct eficonfig_item *iter = items;
> +
> +	efi_menu = calloc(1, sizeof(struct efimenu));
> +	if (!efi_menu)
> +		return NULL;
> +
> +	INIT_LIST_HEAD(&efi_menu->list);
> +	for (i = 0; i < count; i++, iter++) {
> +		title = strdup(iter->title);
> +		if (!title)
> +			goto out;
> +
> +		ret = append_entry(efi_menu, title, iter->func, iter->data);
> +		if (ret != EFI_SUCCESS) {
> +			free(title);
> +			goto out;
> +		}
> +	}
> +
> +	return efi_menu;
> +out:
> +	eficonfig_destroy(efi_menu, false);
> +
> +	return NULL;
> +}
> +
> +/**
> + * eficonfig_process_common() - main handler for UEFI menu
> + *
> + * Construct the structures required to show the menu, then handle
> + * the user input interacting with u-boot menu functions.
> + *
> + * @efi_menu:		pointer to the efimenu structure
> + * @menu_header:	pointer to the menu header string
> + * Return:		status code
> + */
> +efi_status_t eficonfig_process_common(struct efimenu *efi_menu, char *menu_header)
> +{
> +	efi_status_t ret;
> +	struct menu *menu;
> +	void *choice = NULL;
> +	struct list_head *pos, *n;
> +	struct eficonfig_entry *entry;
> +
> +	if (efi_menu->count > EFICONFIG_ENTRY_NUM_MAX)
> +		return EFI_OUT_OF_RESOURCES;
> +
> +	efi_menu->delay = -1;
> +	efi_menu->active = 0;
> +
> +	if (menu_header) {
> +		efi_menu->menu_header = strdup(menu_header);
> +		if (!efi_menu->menu_header) {
> +			ret = EFI_OUT_OF_RESOURCES;
> +			goto out;
> +		}
> +	}
> +
> +	menu = menu_create(NULL, 0, 1, eficonfig_display_statusline,
> +			   eficonfig_print_entry, eficonfig_choice_entry,
> +			   efi_menu);
> +	if (!menu) {
> +		ret = EFI_INVALID_PARAMETER;
> +		goto out;
> +	}
> +
> +	list_for_each_safe(pos, n, &efi_menu->list) {
> +		entry = list_entry(pos, struct eficonfig_entry, list);
> +		if (!menu_item_add(menu, entry->key, entry)) {
> +			ret = EFI_INVALID_PARAMETER;
> +			goto out;
> +		}
> +	}
> +
> +	entry = list_first_entry_or_null(&efi_menu->list, struct eficonfig_entry, list);
> +	if (entry)
> +		menu_default_set(menu, entry->key);
> +
> +	printf(ANSI_CURSOR_HIDE
> +	       ANSI_CLEAR_CONSOLE
> +	       ANSI_CURSOR_POSITION, 1, 1);
> +
> +	if (menu_get_choice(menu, &choice)) {
> +		entry = choice;
> +		if (entry->func)
> +			ret = entry->func(entry->data);
> +	}
> +out:
> +	menu_destroy(menu);
> +
> +	printf(ANSI_CLEAR_CONSOLE
> +	       ANSI_CURSOR_POSITION
> +	       ANSI_CURSOR_SHOW, 1, 1);
> +
> +	return ret;
> +}
> +
> +/**
> + * eficonfig_volume_selected() - handler of volume selection
> + *
> + * @data:	pointer to the data of selected entry
> + * Return:	status code
> + */
> +static efi_status_t eficonfig_volume_selected(void *data)
> +{
> +	struct eficonfig_volume_entry_data *info = data;
> +
> +	if (info) {
> +		info->file_info->current_volume = info->v;
> +		info->file_info->dp_volume = info->dp;
> +	}
> +
> +	return EFI_SUCCESS;
> +}
> +
> +/**
> + * create_selected_device_path() - create device path
> + *
> + * @file_info:	pointer to the selected file information
> + * Return:
> + * device path or NULL. Caller must free the returned value
> + */
> +static
> +struct efi_device_path *create_selected_device_path(struct eficonfig_select_file_info *file_info)
> +{
> +	char *p;
> +	void *buf;
> +	efi_uintn_t fp_size;
> +	struct efi_device_path *dp;
> +	struct efi_device_path_file_path *fp;
> +
> +	fp_size = sizeof(struct efi_device_path) +
> +		  ((u16_strlen(file_info->current_path) + 1) * sizeof(u16));
> +	buf = calloc(1, fp_size + sizeof(END));
> +	if (!buf)
> +		return NULL;
> +
> +	fp = buf;
> +	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, file_info->current_path);
> +
> +	p = buf;
> +	p += fp_size;
> +	*((struct efi_device_path *)p) = END;
> +
> +	dp = efi_dp_append(file_info->dp_volume, (struct efi_device_path *)buf);
> +	free(buf);
> +
> +	return dp;
> +}
> +
> +/**
> + * eficonfig_file_selected() - handler of file selection
> + *
> + * @data:	pointer to the data of selected entry
> + * Return:	status code
> + */
> +static efi_status_t eficonfig_file_selected(void *data)
> +{
> +	struct eficonfig_file_entry_data *info = data;
> +
> +	if (!info)
> +		return EFI_INVALID_PARAMETER;
> +
> +	if (u16_strcmp(info->file_name, u".") == 0 &&
> +	    u16_strlen(info->file_name) == 1) {
> +		/* stay current path */
> +	} else if (u16_strcmp(info->file_name, u"..") == 0 &&
> +		   u16_strlen(info->file_name) == 2) {
> +		struct eficonfig_filepath_info *iter;
> +		struct list_head *pos, *n;
> +		int is_last;
> +
> +		memset(info->file_info->current_path, 0, EFICONFIG_FILE_PATH_BUF_SIZE);
> +		list_for_each_safe(pos, n, &info->file_info->filepath_list) {
> +			iter = list_entry(pos, struct eficonfig_filepath_info, list);
> +
> +			is_last = list_is_last(&iter->list, &info->file_info->filepath_list);
> +			if (is_last) {
> +				list_del(&iter->list);
> +				free(iter->name);
> +				free(iter);
> +				break;
> +			}
> +			u16_strlcat(info->file_info->current_path, iter->name,
> +				    EFICONFIG_FILE_PATH_MAX);
> +			u16_strlcat(info->file_info->current_path, u"\\",
> +				    EFICONFIG_FILE_PATH_MAX);
> +		}
> +	} else {
> +		size_t new_len;
> +		struct eficonfig_filepath_info *filepath;
> +
> +		new_len = u16_strlen(info->file_info->current_path) +
> +				     u16_strlen(info->file_name);
> +		if (new_len >= EFICONFIG_FILE_PATH_MAX) {
> +			eficonfig_print_msg("File path is too long!");
> +			return EFI_INVALID_PARAMETER;
> +		}
> +		u16_strlcat(info->file_info->current_path, info->file_name,
> +			    EFICONFIG_FILE_PATH_MAX);
> +
> +		filepath = calloc(1, sizeof(struct eficonfig_filepath_info));
> +		if (!filepath)
> +			return EFI_OUT_OF_RESOURCES;
> +
> +		filepath->name = u16_strdup(info->file_name);
> +		if (!filepath->name) {
> +			free(filepath);
> +			return EFI_OUT_OF_RESOURCES;
> +		}
> +		list_add_tail(&filepath->list, &info->file_info->filepath_list);
> +
> +		if (info->is_directory) {
> +			/*
> +			 * Remainig buffer should have enough space to contain u"\\" and
> +			 * at least one character for file name
> +			 */
> +			if (new_len + 2 >= EFICONFIG_FILE_PATH_MAX) {
> +				eficonfig_print_msg("Directory path is too long!");
> +				return EFI_INVALID_PARAMETER;
> +			}
> +			u16_strlcat(info->file_info->current_path, u"\\",
> +				    EFICONFIG_FILE_PATH_MAX);
> +		} else {
> +			info->file_info->file_selected = true;
> +		}
> +	}
> +	return EFI_SUCCESS;
> +}
> +
> +/**
> + * eficonfig_select_volume() - construct the volume selection menu
> + *
> + * @file_info:	pointer to the file selection structure
> + * Return:	status code
> + */
> +static efi_status_t eficonfig_select_volume(struct eficonfig_select_file_info *file_info)
> +{
> +	u32 i;
> +	efi_status_t ret;
> +	efi_uintn_t count;
> +	struct efimenu *efi_menu;
> +	struct efi_handler *handler;
> +	struct efi_device_path *device_path;
> +	efi_handle_t *volume_handles = NULL;
> +	struct efi_simple_file_system_protocol *v;
> +
> +	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) {
> +		eficonfig_print_msg("No block device found!");
> +		return ret;
> +	}
> +
> +	efi_menu = calloc(1, sizeof(struct efimenu));
> +	if (!efi_menu)
> +		return EFI_OUT_OF_RESOURCES;
> +
> +	INIT_LIST_HEAD(&efi_menu->list);
> +	for (i = 0; i < count; i++) {
> +		char *devname;
> +		struct efi_block_io *block_io;
> +		struct eficonfig_volume_entry_data *info;
> +
> +		if (efi_menu->count >= EFICONFIG_ENTRY_NUM_MAX - 1)
> +			break;
> +
> +		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 eficonfig_volume_entry_data));
> +		if (!info) {
> +			ret = EFI_OUT_OF_RESOURCES;
> +			goto out;
> +		}
> +
> +		devname = calloc(1, BOOTMENU_DEVICE_NAME_MAX);
> +		if (!devname) {
> +			free(info);
> +			ret = EFI_OUT_OF_RESOURCES;
> +			goto out;
> +		}
> +		ret = efi_disk_get_device_name(volume_handles[i], devname,
> +					       BOOTMENU_DEVICE_NAME_MAX);
> +		if (ret != EFI_SUCCESS) {
> +			free(info);
> +			goto out;
> +		}
> +
> +		info->v = v;
> +		info->dp = device_path;
> +		info->file_info = file_info;
> +		ret = append_entry(efi_menu, devname, eficonfig_volume_selected, info);
> +		if (ret != EFI_SUCCESS) {
> +			free(info);
> +			goto out;
> +		}
> +	}

Something is broken in the logic of the volume editor.

I cannot select 'Save' without choosing a file.

You should be able to add a boot option for:

- a block device
- a partition
- a file

> +
> +	ret = append_quit_entry(efi_menu);
> +	if (ret != EFI_SUCCESS)
> +		goto out;
> +
> +	ret = eficonfig_process_common(efi_menu, "  ** Select Volume **");
> +out:
> +	efi_free_pool(volume_handles);
> +	eficonfig_destroy(efi_menu, true);
> +
> +	return ret;
> +}
> +
> +/**
> + * eficonfig_select_file() - construct the file selection menu
> + *
> + * @file_info:	pointer to the file selection structure
> + * @root:	pointer to the file handle
> + * Return:	status code
> + */
> +static efi_status_t eficonfig_select_file(struct eficonfig_select_file_info *file_info,
> +					  struct efi_file_handle *root)
> +{
> +	efi_uintn_t len;
> +	efi_status_t ret;
> +	struct efimenu *efi_menu;
> +	struct efi_file_handle *f;
> +	struct efi_file_info *buf;
> +	struct list_head *pos, *n;
> +
> +	buf = calloc(1, sizeof(struct efi_file_info) + EFICONFIG_FILE_PATH_BUF_SIZE);
> +	if (!buf)
> +		return EFI_OUT_OF_RESOURCES;
> +
> +	while (!file_info->file_selected) {
> +		efi_menu = calloc(1, sizeof(struct efimenu));
> +		if (!efi_menu) {
> +			ret = EFI_OUT_OF_RESOURCES;
> +			goto out;
> +		}
> +		INIT_LIST_HEAD(&efi_menu->list);
> +
> +		ret = efi_file_open_int(root, &f, file_info->current_path, EFI_FILE_MODE_READ, 0);
> +		if (ret != EFI_SUCCESS) {
> +			eficonfig_print_msg("Reading volume failed!");
> +			ret = EFI_ABORTED;
> +			goto out;
> +		}
> +
> +		/* Read directory and construct menu structure */
> +		for (;;) {
> +			char *name, *p;
> +			int name_len;
> +			struct eficonfig_file_entry_data *info;
> +
> +			if (efi_menu->count >= EFICONFIG_ENTRY_NUM_MAX - 1)
> +				break;
> +
> +			len = sizeof(struct efi_file_info) + EFICONFIG_FILE_PATH_BUF_SIZE;
> +			ret = efi_file_read_int(f, &len, buf);
> +			if (ret != EFI_SUCCESS || len == 0)
> +				break;
> +
> +			info = calloc(1, sizeof(struct eficonfig_file_entry_data));
> +			if (!info) {
> +				ret = EFI_OUT_OF_RESOURCES;
> +				goto err;
> +			}
> +
> +			if (buf->attribute & EFI_FILE_DIRECTORY) {
> +				/* append u'/' at the end of directory name */
> +				name_len = utf16_utf8_strlen(buf->file_name) + 2;
> +
> +				/* filter out u'.' */
> +				if (name_len == 3 && buf->file_name[0] == u'.') {
> +					free(info);
> +					continue;
> +				}
> +
> +				name = calloc(1, name_len);
> +				if (!name) {
> +					free(info);
> +					ret = EFI_OUT_OF_RESOURCES;
> +					goto err;
> +				}
> +				p = name;
> +				utf16_utf8_strcpy(&p, buf->file_name);
> +				name[u16_strlen(buf->file_name)] = u'/';
> +
> +				info->is_directory = true;
> +			} else {
> +				name_len = utf16_utf8_strlen(buf->file_name) + 1;
> +				name = calloc(1, name_len);
> +				if (!name) {
> +					free(info);
> +					ret = EFI_OUT_OF_RESOURCES;
> +					goto err;
> +				}
> +				p = name;
> +				utf16_utf8_strcpy(&p, buf->file_name);
> +			}
> +
> +			info->file_name = u16_strdup(buf->file_name);
> +			if (!info->file_name) {
> +				free(info);
> +				free(name);
> +				ret = EFI_OUT_OF_RESOURCES;
> +				goto err;
> +			}
> +
> +			info->file_info = file_info;
> +			ret = append_entry(efi_menu, name, eficonfig_file_selected, info);
> +			if (ret != EFI_SUCCESS) {
> +				free(info);
> +				free(name);
> +				goto err;
> +			}
> +		}
> +
> +		ret = append_quit_entry(efi_menu);
> +		if (ret != EFI_SUCCESS)
> +			goto err;
> +
> +		ret = eficonfig_process_common(efi_menu, "  ** Select File **");
> +err:
> +		efi_file_close_int(f);
> +		list_for_each_safe(pos, n, &efi_menu->list) {
> +			struct eficonfig_entry *entry;
> +
> +			entry = list_entry(pos, struct eficonfig_entry, list);
> +			/* skip "Quit" */
> +			if (list_is_last(&entry->list, &efi_menu->list))
> +				break;
> +
> +			free(((struct eficonfig_file_entry_data *)(entry->data))->file_name);
> +		}
> +		eficonfig_destroy(efi_menu, true);
> +		if (ret != EFI_SUCCESS)
> +			break;
> +	}
> +
> +out:
> +	free(buf);
> +	return ret;
> +}
> +
> +/**
> + * handle_user_input() - handle user input
> + *
> + * @buf:	pointer to the buffer
> + * @buf_size:	size of the buffer
> + * @cursol_col:	cursol column for user input
> + * @msg:	pointer to the string to display
> + * Return:	status code
> + */
> +static efi_status_t handle_user_input(u16 *buf, int buf_size,
> +				      int cursol_col, char *msg)
> +{
> +	u16 *tmp;
> +	efi_status_t ret;
> +
> +	printf(ANSI_CLEAR_CONSOLE
> +	       ANSI_CURSOR_POSITION
> +	       "%s"
> +	       ANSI_CURSOR_POSITION
> +	       "  Press ENTER to complete, ESC/CTRL+C to quit",
> +	       0, 1, msg, 8, 1);
> +
> +	/* tmp is used to accept user cancel */
> +	tmp = calloc(1, buf_size * sizeof(u16));
> +	if (!tmp)
> +		return EFI_OUT_OF_RESOURCES;
> +
> +	ret = efi_console_get_u16_string(cin, tmp, buf_size, NULL, 4, cursol_col);
> +	if (ret == EFI_SUCCESS)
> +		u16_strcpy(buf, tmp);
> +
> +	free(tmp);
> +
> +	/* to stay the parent menu */
> +	ret = (ret == EFI_ABORTED) ? EFI_NOT_READY : ret;
> +
> +	return ret;
> +}
> +
> +/**
> + * eficonfig_boot_add_enter_description() - handle user input for description
> + *
> + * @data:	pointer to the internal boot option structure
> + * Return:	status code
> + */
> +static efi_status_t eficonfig_boot_add_enter_description(void *data)
> +{
> +	struct eficonfig_boot_option *bo = data;
> +
> +	return handle_user_input(bo->description, EFICONFIG_DESCRIPTION_MAX, 22,
> +				 "\n  ** Edit Description **\n"
> +				 "\n"
> +				 "  enter description: ");
> +}
> +
> +/**
> + * eficonfig_boot_add_optional_data() - handle user input for optional data
> + *
> + * @data:	pointer to the internal boot option structure
> + * Return:	status code
> + */
> +static efi_status_t eficonfig_boot_add_optional_data(void *data)
> +{
> +	struct eficonfig_boot_option *bo = data;
> +
> +	return handle_user_input(bo->optional_data, EFICONFIG_OPTIONAL_DATA_MAX, 24,
> +				 "\n  ** Edit Optional Data **\n"
> +				 "\n"
> +				 "  enter optional data:");
> +}
> +
> +/**
> + * eficonfig_boot_edit_save() - handler to save the boot option
> + *
> + * @data:	pointer to the internal boot option structure
> + * Return:	status code
> + */
> +static efi_status_t eficonfig_boot_edit_save(void *data)
> +{
> +	struct eficonfig_boot_option *bo = data;
> +
> +	if (u16_strlen(bo->description) == 0) {
> +		eficonfig_print_msg("Boot Description is empty!");
> +		bo->edit_completed = false;
> +		return EFI_NOT_READY;
> +	}
> +	if (u16_strlen(bo->file_info.current_path) == 0) {
> +		eficonfig_print_msg("File is not selected!");
> +		bo->edit_completed = false;
> +		return EFI_NOT_READY;

This string is wrong. There is no requirement for the device path to
contain a file in a boot option. So you should say 'No device path
selected'.

Best regards

Heinrich


> +	}
> +
> +	bo->edit_completed = true;
> +
> +	return EFI_SUCCESS;
> +}
> +
> +/**
> + * eficonfig_select_file_handler() - handle user file selection
> + *
> + * @data:	pointer to the data
> + * Return:	status code
> + */
> +efi_status_t eficonfig_select_file_handler(void *data)
> +{
> +	size_t len;
> +	efi_status_t ret;
> +	struct list_head *pos, *n;
> +	struct efi_file_handle *root;
> +	struct eficonfig_filepath_info *item;
> +	struct eficonfig_select_file_info *file_info = data;
> +	struct eficonfig_select_file_info *tmp = NULL;
> +
> +	tmp = calloc(1, sizeof(struct eficonfig_select_file_info));
> +	if (!tmp)
> +		return EFI_OUT_OF_RESOURCES;
> +
> +	tmp->current_path = calloc(1, EFICONFIG_FILE_PATH_BUF_SIZE);
> +	if (!tmp->current_path) {
> +		free(tmp);
> +		return EFI_OUT_OF_RESOURCES;
> +	}
> +	INIT_LIST_HEAD(&tmp->filepath_list);
> +
> +	while (!tmp->file_selected) {
> +		tmp->current_volume = NULL;
> +		memset(tmp->current_path, 0, EFICONFIG_FILE_PATH_BUF_SIZE);
> +
> +		ret = eficonfig_select_volume(tmp);
> +		if (ret != EFI_SUCCESS)
> +			goto out;
> +
> +		if (!tmp->current_volume)
> +			return EFI_INVALID_PARAMETER;
> +
> +		ret = efi_open_volume_int(tmp->current_volume, &root);
> +		if (ret != EFI_SUCCESS)
> +			goto out;
> +
> +		ret = eficonfig_select_file(tmp, root);
> +		if (ret == EFI_ABORTED)
> +			continue;
> +		if (ret != EFI_SUCCESS)
> +			goto out;
> +	}
> +
> +out:
> +	if (ret == EFI_SUCCESS) {
> +		len = u16_strlen(tmp->current_path);
> +		len = (len >= EFICONFIG_FILE_PATH_MAX) ? (EFICONFIG_FILE_PATH_MAX - 1) : len;
> +		memcpy(file_info->current_path, tmp->current_path, len * sizeof(u16));
> +		file_info->current_path[len] = u'\0';
> +		file_info->current_volume = tmp->current_volume;
> +		file_info->dp_volume = tmp->dp_volume;
> +	}
> +
> +	list_for_each_safe(pos, n, &tmp->filepath_list) {
> +		item = list_entry(pos, struct eficonfig_filepath_info, list);
> +		list_del(&item->list);
> +		free(item->name);
> +		free(item);
> +	}
> +	free(tmp->current_path);
> +	free(tmp);
> +
> +	/* to stay the parent menu */
> +	ret = (ret == EFI_ABORTED) ? EFI_NOT_READY : ret;
> +
> +	return ret;
> +}
> +
> +/**
> + * eficonfig_get_unused_bootoption() - get unused "Boot####" index
> + *
> + * @buf:	pointer to the buffer to store boot option variable name
> + * @buf_size:	buffer size
> + * @index:	pointer to store the index in the BootOrder variable
> + * Return:	status code
> + */
> +efi_status_t eficonfig_get_unused_bootoption(u16 *buf, efi_uintn_t buf_size,
> +					     unsigned int *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;
> +}
> +
> +/**
> + * eficonfig_set_boot_option() - set boot option
> + *
> + * @varname:		pointer to variable name
> + * @dp:			pointer to device path
> + * @label:		pointer to label string
> + * @optional_data:	pointer to optional data
> + * Return:		status code
> + */
> +static efi_status_t eficonfig_set_boot_option(u16 *varname, struct efi_device_path *dp,
> +					      efi_uintn_t dp_size, 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 = dp_size;
> +	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(varname, &efi_global_variable_guid,
> +				   EFI_VARIABLE_NON_VOLATILE |
> +				   EFI_VARIABLE_BOOTSERVICE_ACCESS |
> +				   EFI_VARIABLE_RUNTIME_ACCESS,
> +				   size, p, false);
> +	free(p);
> +
> +	return ret;
> +}
> +
> +/**
> + * eficonfig_append_bootorder() - append new boot option in BootOrder variable
> + *
> + * @index:	"Boot####" index to append to BootOrder variable
> + * Return:	status code
> + */
> +efi_status_t eficonfig_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;
> +}
> +
> +/**
> + * create_boot_option_entry() - create boot option entry
> + *
> + * @efi_menu:	pointer to the efimenu structure
> + * @title:	pointer to the entry title
> + * @val:	pointer to boot option label
> + * @func:	callback of each entry
> + * @data:	pointer to the data to be passed to each entry callback
> + * Return:	status code
> + */
> +static efi_status_t create_boot_option_entry(struct efimenu *efi_menu, char *title, u16 *val,
> +					     eficonfig_entry_func func, void *data)
> +{
> +	u32 len;
> +	char *p, *buf;
> +
> +	len = strlen(title) + 1;
> +	if (val)
> +		len += utf16_utf8_strlen(val);
> +	buf = calloc(1, len);
> +	if (!buf)
> +		return EFI_OUT_OF_RESOURCES;
> +
> +	strcpy(buf, title);
> +	if (val) {
> +		p = buf + strlen(title);
> +		utf16_utf8_strcpy(&p, val);
> +	}
> +
> +	return append_entry(efi_menu, buf, func, data);
> +}
> +
> +/**
> + * prepare_file_selection_entry() - prepare file selection entry
> + *
> + * @efi_menu:	pointer to the efimenu structure
> + * @title:	pointer to the title string
> + * @file_info:	pointer to the file info
> + * Return:	status code
> + */
> +static efi_status_t prepare_file_selection_entry(struct efimenu *efi_menu, char *title,
> +						 struct eficonfig_select_file_info *file_info)
> +{
> +	u32 len;
> +	efi_status_t ret;
> +	u16 *file_name, *p;
> +	efi_handle_t handle;
> +	char devname[BOOTMENU_DEVICE_NAME_MAX] = {0};
> +
> +	/* get the device name only when the user already selected the file path */
> +	handle = efi_dp_find_obj(file_info->dp_volume, NULL, NULL);
> +	if (handle) {
> +		ret = efi_disk_get_device_name(handle, devname, BOOTMENU_DEVICE_NAME_MAX);
> +		if (ret != EFI_SUCCESS)
> +			return ret;
> +	}
> +
> +	/* append u'/' to devname, it is just for display purpose. */
> +	if (file_info->current_path[0] != u'\0' && file_info->current_path[0] != u'/')
> +		strlcat(devname, "/", BOOTMENU_DEVICE_NAME_MAX);
> +
> +	len = strlen(devname);
> +	len += utf16_utf8_strlen(file_info->current_path) + 1;
> +	file_name = calloc(1, len * sizeof(u16));
> +	if (!file_name)
> +		return ret;
> +
> +	p = file_name;
> +	utf8_utf16_strcpy(&p, devname);
> +	u16_strlcat(file_name, file_info->current_path, len);
> +	ret = create_boot_option_entry(efi_menu, title, file_name,
> +				       eficonfig_select_file_handler, file_info);
> +	free(file_name);
> +	return ret;
> +}
> +
> +/**
> + * eficonfig_show_boot_option() - prepare menu entry for editing boot option
> + *
> + * Construct the structures to create edit boot option menu
> + *
> + * @bo:		pointer to the boot option
> + * @header_str:	pointer to the header string
> + * Return:	status code
> + */
> +static efi_status_t eficonfig_show_boot_option(struct eficonfig_boot_option *bo,
> +					       char *header_str)
> +{
> +	struct efimenu *efi_menu;
> +	efi_status_t ret;
> +
> +	efi_menu = calloc(1, sizeof(struct efimenu));
> +	if (!efi_menu)
> +		return EFI_OUT_OF_RESOURCES;
> +
> +	INIT_LIST_HEAD(&efi_menu->list);
> +
> +	ret = create_boot_option_entry(efi_menu, "Description: ", bo->description,
> +				       eficonfig_boot_add_enter_description, bo);
> +	if (ret != EFI_SUCCESS)
> +		goto out;
> +
> +	ret = prepare_file_selection_entry(efi_menu, "File: ", &bo->file_info);
> +	if (ret != EFI_SUCCESS)
> +		goto out;
> +
> +	ret = prepare_file_selection_entry(efi_menu, "Initrd File: ", &bo->initrd_info);
> +	if (ret != EFI_SUCCESS)
> +		goto out;
> +
> +	ret = create_boot_option_entry(efi_menu, "Optional Data: ", bo->optional_data,
> +				       eficonfig_boot_add_optional_data, bo);
> +	if (ret != EFI_SUCCESS)
> +		goto out;
> +
> +	ret = create_boot_option_entry(efi_menu, "Save", NULL,
> +				       eficonfig_boot_edit_save, bo);
> +	if (ret != EFI_SUCCESS)
> +		goto out;
> +
> +	ret = create_boot_option_entry(efi_menu, "Quit", NULL,
> +				       eficonfig_process_quit, bo);
> +	if (ret != EFI_SUCCESS)
> +		goto out;
> +
> +	ret = eficonfig_process_common(efi_menu, header_str);
> +out:
> +	eficonfig_destroy(efi_menu, false);
> +
> +	return ret;
> +}
> +
> +/**
> + * fill_file_info() - fill the file info from efi_device_path structure
> + *
> + * @dp:		pointer to the device path
> + * @file_info:	pointer to the file info structure
> + * @device_dp:	pointer to the volume device path
> + */
> +static void fill_file_info(struct efi_device_path *dp,
> +			   struct eficonfig_select_file_info *file_info,
> +			   struct efi_device_path *device_dp)
> +{
> +	u16 *file_str, *p;
> +	struct efi_device_path *file_dp = NULL;
> +
> +	efi_dp_split_file_path(dp, &device_dp, &file_dp);
> +	file_info->dp_volume = device_dp;
> +	file_str = efi_dp_str(file_dp);
> +	/*
> +	 * efi_convert_device_path_to_text() automatically adds u'/' at the
> +	 * beginning of file name, remove u'/' before copying to current_path
> +	 */
> +	p = file_str;
> +	if (p[0] == u'/')
> +		p++;
> +
> +	u16_strcpy(file_info->current_path, p);
> +	efi_free_pool(file_dp);
> +	efi_free_pool(file_str);
> +}
> +
> +/**
> + * eficonfig_edit_boot_option() - prepare boot option structure for editing
> + *
> + * Construct the boot option structure and copy the existing value
> + *
> + * @varname:		pointer to the UEFI variable name
> + * @bo:			pointer to the boot option
> + * @load_option:	pointer to the load option
> + * @load_option_size:	size of the load option
> + * @header_str:		pointer to the header string
> + * Return	:	status code
> + */
> +static efi_status_t eficonfig_edit_boot_option(u16 *varname, struct eficonfig_boot_option *bo,
> +					       void *load_option, efi_uintn_t load_option_size,
> +					       char *header_str)
> +{
> +	size_t len;
> +	efi_status_t ret;
> +	char *tmp = NULL, *p;
> +	struct efi_load_option lo = {0};
> +	efi_uintn_t final_dp_size;
> +	struct efi_device_path *dp = NULL;
> +	efi_uintn_t size = load_option_size;
> +	struct efi_device_path *final_dp = NULL;
> +	struct efi_device_path *device_dp = NULL;
> +	struct efi_device_path *initrd_dp = NULL;
> +	struct efi_device_path *initrd_device_dp = NULL;
> +
> +	const struct efi_initrd_dp id_dp = {
> +		.vendor = {
> +			{
> +			DEVICE_PATH_TYPE_MEDIA_DEVICE,
> +			DEVICE_PATH_SUB_TYPE_VENDOR_PATH,
> +			sizeof(id_dp.vendor),
> +			},
> +			EFI_INITRD_MEDIA_GUID,
> +		},
> +		.end = {
> +			DEVICE_PATH_TYPE_END,
> +			DEVICE_PATH_SUB_TYPE_END,
> +			sizeof(id_dp.end),
> +		}
> +	};
> +
> +	bo->file_info.current_path = calloc(1, EFICONFIG_FILE_PATH_BUF_SIZE);
> +	if (!bo->file_info.current_path) {
> +		ret =  EFI_OUT_OF_RESOURCES;
> +		goto out;
> +	}
> +
> +	bo->initrd_info.current_path = calloc(1, EFICONFIG_FILE_PATH_BUF_SIZE);
> +	if (!bo->file_info.current_path) {
> +		ret =  EFI_OUT_OF_RESOURCES;
> +		goto out;
> +	}
> +
> +	bo->description = calloc(1, EFICONFIG_DESCRIPTION_MAX * sizeof(u16));
> +	if (!bo->description) {
> +		ret =  EFI_OUT_OF_RESOURCES;
> +		goto out;
> +	}
> +
> +	bo->optional_data = calloc(1, EFICONFIG_OPTIONAL_DATA_MAX * sizeof(u16));
> +	if (!bo->optional_data) {
> +		ret =  EFI_OUT_OF_RESOURCES;
> +		goto out;
> +	}
> +
> +	/* copy the preset value */
> +	if (load_option) {
> +		ret = efi_deserialize_load_option(&lo, load_option, &size);
> +		if (ret != EFI_SUCCESS)
> +			goto out;
> +
> +		if (!lo.label || (lo.label && u16_strlen(lo.label) >= EFICONFIG_DESCRIPTION_MAX)) {
> +			ret = EFI_INVALID_PARAMETER;
> +			goto out;
> +		}
> +		u16_strcpy(bo->description, lo.label);
> +
> +		/* EFI image file path is a first instance */
> +		if (lo.file_path)
> +			fill_file_info(lo.file_path, &bo->file_info, device_dp);
> +
> +		/* Initrd file path(optional) is placed at second instance. */
> +		initrd_dp = efi_dp_from_lo(&lo, &efi_lf2_initrd_guid);
> +		if (initrd_dp) {
> +			fill_file_info(initrd_dp, &bo->initrd_info, initrd_device_dp);
> +			efi_free_pool(initrd_dp);
> +		}
> +
> +		if (size > 0)
> +			memcpy(bo->optional_data, lo.optional_data, size);
> +	}
> +
> +	while (1) {
> +		ret = eficonfig_show_boot_option(bo, header_str);
> +		if (ret == EFI_SUCCESS && bo->edit_completed)
> +			break;
> +		if (ret == EFI_NOT_READY)
> +			continue;
> +		if (ret != EFI_SUCCESS)
> +			goto out;
> +	}
> +
> +	if (bo->initrd_info.dp_volume) {
> +		dp = create_selected_device_path(&bo->initrd_info);
> +		if (!dp) {
> +			ret = EFI_OUT_OF_RESOURCES;
> +			goto out;
> +		}
> +		initrd_dp = efi_dp_append((const struct efi_device_path *)&id_dp, dp);
> +		efi_free_pool(dp);
> +	}
> +
> +	dp = create_selected_device_path(&bo->file_info);
> +	if (!dp) {
> +		ret = EFI_OUT_OF_RESOURCES;
> +		goto out;
> +	}
> +	final_dp_size = efi_dp_size(dp) + sizeof(END);
> +	if (initrd_dp) {
> +		final_dp = efi_dp_concat(dp, initrd_dp);
> +		final_dp_size += efi_dp_size(initrd_dp) + sizeof(END);
> +	} else {
> +		final_dp = efi_dp_dup(dp);
> +	}
> +	efi_free_pool(dp);
> +
> +	if (!final_dp)
> +		goto out;
> +
> +	len = utf16_utf8_strlen(bo->optional_data) + 1;
> +	tmp = calloc(1, len);
> +	if (!tmp)
> +		goto out;
> +	p = tmp;
> +	utf16_utf8_strncpy(&p, bo->optional_data, u16_strlen(bo->optional_data));
> +
> +	ret = eficonfig_set_boot_option(varname, final_dp, final_dp_size, bo->description, tmp);
> +	if (ret != EFI_SUCCESS)
> +		goto out;
> +out:
> +	free(tmp);
> +	free(bo->optional_data);
> +	free(bo->description);
> +	free(bo->file_info.current_path);
> +	free(bo->initrd_info.current_path);
> +	efi_free_pool(device_dp);
> +	efi_free_pool(initrd_device_dp);
> +	efi_free_pool(initrd_dp);
> +	efi_free_pool(final_dp);
> +
> +	return ret;
> +}
> +
> +/**
> + * eficonfig_process_add_boot_option() - handler to add boot option
> + *
> + * @data:	pointer to the data for each entry
> + * Return:	status code
> + */
> +static efi_status_t eficonfig_process_add_boot_option(void *data)
> +{
> +	u16 varname[9];
> +	efi_status_t ret;
> +	struct eficonfig_boot_option *bo = NULL;
> +
> +	bo = calloc(1, sizeof(struct eficonfig_boot_option));
> +	if (!bo)
> +		return EFI_OUT_OF_RESOURCES;
> +
> +	ret = eficonfig_get_unused_bootoption(varname, sizeof(varname), &bo->boot_index);
> +	if (ret != EFI_SUCCESS)
> +		return ret;
> +
> +	ret = eficonfig_edit_boot_option(varname, bo, NULL, 0,  "  ** Add Boot Option ** ");
> +	if (ret != EFI_SUCCESS)
> +		goto out;
> +
> +	ret = eficonfig_append_bootorder((u16)bo->boot_index);
> +	if (ret != EFI_SUCCESS)
> +		goto out;
> +
> +out:
> +	free(bo);
> +
> +	/* to stay the parent menu */
> +	ret = (ret == EFI_ABORTED) ? EFI_SUCCESS : ret;
> +
> +	return ret;
> +}
> +
> +/**
> + * eficonfig_init() - do required initialization for eficonfig command
> + *
> + * Return:	status code
> + */
> +static efi_status_t eficonfig_init(void)
> +{
> +	efi_status_t ret;
> +	static bool init;
> +	struct efi_handler *handler;
> +
> +	if (!init) {
> +		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;
> +	}
> +
> +	init = true;
> +
> +	return ret;
> +}
> +
> +static const struct eficonfig_item maintenance_menu_items[] = {
> +	{"Add Boot Option", eficonfig_process_add_boot_option},
> +	{"Quit", eficonfig_process_quit},
> +};
> +
> +/**
> + * do_eficonfig() - execute `eficonfig` command
> + *
> + * @cmdtp:	table entry describing command
> + * @flag:	bitmap indicating how the command was invoked
> + * @argc:	number of arguments
> + * @argv:	command line arguments
> + * Return:	status code
> + */
> +static int do_eficonfig(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[])
> +{
> +	efi_status_t ret;
> +	struct efimenu *efi_menu;
> +
> +	if (argc > 1)
> +		return CMD_RET_USAGE;
> +
> +	ret = efi_init_obj_list();
> +	if (ret != EFI_SUCCESS) {
> +		log_err("Error: Cannot initialize UEFI sub-system, r = %lu\n",
> +			ret & ~EFI_ERROR_MASK);
> +
> +		return CMD_RET_FAILURE;
> +	}
> +
> +	ret = eficonfig_init();
> +	if (ret != EFI_SUCCESS)
> +		return CMD_RET_FAILURE;
> +
> +	while (1) {
> +		efi_menu = eficonfig_create_fixed_menu(maintenance_menu_items,
> +						       ARRAY_SIZE(maintenance_menu_items));
> +		if (!efi_menu)
> +			return CMD_RET_FAILURE;
> +
> +		ret = eficonfig_process_common(efi_menu, "  ** UEFI Maintenance Menu **");
> +		eficonfig_destroy(efi_menu, false);
> +
> +		if (ret == EFI_ABORTED)
> +			break;
> +	}
> +
> +	return CMD_RET_SUCCESS;
> +}
> +
> +U_BOOT_CMD(
> +	eficonfig, 1, 0, do_eficonfig,
> +	"provide menu-driven UEFI variable maintenance interface",
> +	""
> +);
> diff --git a/include/efi_config.h b/include/efi_config.h
> new file mode 100644
> index 0000000000..aaff5c7cc0
> --- /dev/null
> +++ b/include/efi_config.h
> @@ -0,0 +1,90 @@
> +/* SPDX-License-Identifier: GPL-2.0+ */
> +/*
> + *  Menu-driven UEFI Variable maintenance
> + *
> + *  Copyright (c) 2022 Masahisa Kojima, Linaro Limited
> + */
> +
> +#ifndef _EFI_CONFIG_H
> +#define _EFI_CONFIG_H
> +
> +#define EFICONFIG_ENTRY_NUM_MAX 99
> +#define EFICONFIG_FILE_PATH_MAX 512
> +#define EFICONFIG_FILE_PATH_BUF_SIZE (EFICONFIG_FILE_PATH_MAX * sizeof(u16))
> +
> +typedef efi_status_t (*eficonfig_entry_func)(void *data);
> +
> +/**
> + * struct eficonfig_entry - menu entry structure
> + *
> + * @num:	menu entry index
> + * @title:	title of entry
> + * @key:	unique key
> + * @efi_menu:	pointer to the menu structure
> + * @func:	callback function to be called when this entry is selected
> + * @data:	data to be passed to the callback function
> + * @list:	list structure
> + */
> +struct eficonfig_entry {
> +	u32 num;
> +	char *title;
> +	char key[3];
> +	struct efimenu *efi_menu;
> +	eficonfig_entry_func func;
> +	void *data;
> +	struct list_head list;
> +};
> +
> +/**
> + * struct efimenu - efi menu structure
> + *
> + * @delay:		delay for autoboot
> + * @active:		active menu entry index
> + * @count:		total count of menu entry
> + * @menu_header:	menu header string
> + * @list:		menu entry list structure
> + */
> +struct efimenu {
> +	int delay;
> +	int active;
> +	int count;
> +	char *menu_header;
> +	struct list_head list;
> +};
> +
> +/**
> + * struct eficonfig_item - structure to construct eficonfig_entry
> + *
> + * @title:	title of entry
> + * @func:	callback function to be called when this entry is selected
> + * @data:	data to be passed to the callback function
> + */
> +struct eficonfig_item {
> +	char *title;
> +	eficonfig_entry_func func;
> +	void *data;
> +};
> +
> +/**
> + * struct eficonfig_select_file_info - structure to be used for file selection
> + *
> + * @current_volume:	pointer to the efi_simple_file_system_protocol
> + * @dp_volume:		pointer to device path of the selected device
> + * @current_path:	pointer to the selected file path string
> + * @filepath_list:	list_head structure for file path list
> + * @file_selectred:	flag indicates file selecting status
> + */
> +struct eficonfig_select_file_info {
> +	struct efi_simple_file_system_protocol *current_volume;
> +	struct efi_device_path *dp_volume;
> +	u16 *current_path;
> +	struct list_head filepath_list;
> +	bool file_selected;
> +};
> +
> +void eficonfig_print_msg(char *msg);
> +efi_status_t eficonfig_process_quit(void *data);
> +efi_status_t eficonfig_process_common(struct efimenu *efi_menu, char *menu_header);
> +efi_status_t eficonfig_select_file_handler(void *data);
> +
> +#endif
> diff --git a/include/efi_loader.h b/include/efi_loader.h
> index b0d6fff67c..49e7d1e613 100644
> --- a/include/efi_loader.h
> +++ b/include/efi_loader.h
> @@ -142,6 +142,11 @@ static inline efi_status_t efi_launch_capsules(void)
>   	EFI_GUID(0x63293792, 0xadf5, 0x9325, \
>   		 0xb9, 0x9f, 0x4e, 0x0e, 0x45, 0x5c, 0x1b, 0x1e)
>
> +/* GUID for the auto generated boot menu entry */
> +#define EFICONFIG_AUTO_GENERATED_ENTRY_GUID \
> +	EFI_GUID(0x38c1acc1, 0x9fc0, 0x41f0, \
> +		 0xb9, 0x01, 0xfa, 0x74, 0xd6, 0xd6, 0xe4, 0xde)
> +
>   /* Use internal device tree when starting UEFI application */
>   #define EFI_FDT_USE_INTERNAL NULL
>
> @@ -226,6 +231,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;
>
> @@ -249,6 +257,9 @@ extern const struct efi_hii_string_protocol efi_hii_string;
>
>   uint16_t *efi_dp_str(struct efi_device_path *dp);
>
> +/* GUID for the auto generated boot menu entry */
> +extern const efi_guid_t efi_guid_bootmenu_auto_generated;
> +
>   /* GUID of the U-Boot root node */
>   extern const efi_guid_t efi_u_boot_guid;
>   #ifdef CONFIG_SANDBOX
> @@ -314,6 +325,8 @@ 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 char __efi_runtime_start[], __efi_runtime_stop[];
>   extern char __efi_runtime_rel_start[], __efi_runtime_rel_stop[];
> @@ -891,6 +904,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
>    *
> @@ -1064,4 +1079,32 @@ 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,
> +		 u16 *buf, efi_uintn_t count, efi_console_filter_func filer_func,
> +		 int row, int col);
> +
> +efi_status_t eficonfig_get_unused_bootoption(u16 *buf,
> +					     efi_uintn_t buf_size, u32 *index);
> +efi_status_t eficonfig_append_bootorder(u16 index);
> +
> +efi_status_t efi_disk_get_device_name(const efi_handle_t handle, char *buf, int size);
> +
>   #endif /* _EFI_LOADER_H */
> diff --git a/lib/efi_loader/efi_bootmgr.c b/lib/efi_loader/efi_bootmgr.c
> index 234073ecb7..ede9116b3c 100644
> --- a/lib/efi_loader/efi_bootmgr.c
> +++ b/lib/efi_loader/efi_bootmgr.c
> @@ -19,6 +19,9 @@
>   static const struct efi_boot_services *bs;
>   static const struct efi_runtime_services *rs;
>
> +const efi_guid_t efi_guid_bootmenu_auto_generated =
> +		EFICONFIG_AUTO_GENERATED_ENTRY_GUID;
> +
>   /*
>    * bootmgr implements the logic of trying to find a payload to boot
>    * based on the BootOrder + BootXXXX variables, and then loading it.
> 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 3164fd484e..5be509f0d6 100644
> --- a/lib/efi_loader/efi_console.c
> +++ b/lib/efi_loader/efi_console.c
> @@ -7,6 +7,7 @@
>
>   #define LOG_CATEGORY LOGC_EFI
>
> +#include <ansi.h>
>   #include <common.h>
>   #include <charset.h>
>   #include <malloc.h>
> @@ -1318,3 +1319,72 @@ 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
> + * @buf:		buffer to store user input string in UTF16
> + * @count:		number of u16 string including NULL terminator that buf has
> + * @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,
> +					u16 *buf, efi_uintn_t count,
> +					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
> +	       ANSI_CLEAR_LINE_TO_END
> +	       ANSI_CURSOR_SHOW, row, col);
> +
> +	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
> +			       "%ls"
> +			       ANSI_CLEAR_LINE_TO_END, row, col, buf);
> +			continue;
> +		} else if (key.unicode_char == u'\r') {
> +			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 >= (count - 1))
> +			continue;
> +
> +		buf[len] = key.unicode_char;
> +		len++;
> +		printf(ANSI_CURSOR_POSITION "%ls", row, col, buf);
> +	}
> +}
> diff --git a/lib/efi_loader/efi_disk.c b/lib/efi_loader/efi_disk.c
> index 16d14b0429..4c9c4cfec8 100644
> --- a/lib/efi_loader/efi_disk.c
> +++ b/lib/efi_loader/efi_disk.c
> @@ -769,3 +769,53 @@ efi_status_t efi_disk_init(void)
>
>   	return EFI_SUCCESS;
>   }
> +
> +/**
> + * efi_disk_get_device_name() - get U-Boot device name associated with EFI handle
> + *
> + * @handle:	pointer to the EFI handle
> + * @buf:	pointer to the buffer to store the string
> + * @size:	size of buffer
> + * Return:	status code
> + */
> +efi_status_t efi_disk_get_device_name(const efi_handle_t handle, char *buf, int size)
> +{
> +	int count;
> +	int diskid;
> +	enum uclass_id id;
> +	unsigned int part;
> +	struct udevice *dev;
> +	struct blk_desc *desc;
> +	const char *if_typename;
> +	bool is_partition = false;
> +	struct disk_part *part_data;
> +
> +	if (!handle || !buf || !size)
> +		return EFI_INVALID_PARAMETER;
> +
> +	dev = handle->dev;
> +	id = device_get_uclass_id(dev);
> +	if (id == UCLASS_BLK) {
> +		desc = dev_get_uclass_plat(dev);
> +	} else if (id == UCLASS_PARTITION) {
> +		desc = dev_get_uclass_plat(dev_get_parent(dev));
> +		is_partition = true;
> +	} else {
> +		return EFI_INVALID_PARAMETER;
> +	}
> +	if_typename = blk_get_if_type_name(desc->if_type);
> +	diskid = desc->devnum;
> +
> +	if (is_partition) {
> +		part_data = dev_get_uclass_plat(dev);
> +		part = part_data->partnum;
> +		count = snprintf(buf, size, "%s %d:%d", if_typename, diskid, part);
> +	} else {
> +		count = snprintf(buf, size, "%s %d", if_typename, diskid);
> +	}
> +
> +	if (count < 0 || (count + 1) > size)
> +		return EFI_INVALID_PARAMETER;
> +
> +	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 *


^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH v11 4/9] eficonfig: add "Change Boot Order" menu entry
  2022-08-18  6:17   ` Heinrich Schuchardt
@ 2022-08-18  6:50     ` Heinrich Schuchardt
  2022-08-18  7:34       ` Masahisa Kojima
  0 siblings, 1 reply; 25+ messages in thread
From: Heinrich Schuchardt @ 2022-08-18  6:50 UTC (permalink / raw)
  To: Masahisa Kojima
  Cc: Ilias Apalodimas, Simon Glass, Takahiro Akashi, Mark Kettenis, u-boot

On 8/18/22 08:17, Heinrich Schuchardt wrote:
> On 8/17/22 11:36, Masahisa Kojima wrote:
>> This commit adds the menu entry to update UEFI BootOrder variable.
>> User moves the entry with UP/DOWN key, changes the order
>> with PLUS/MINUS key, press SPACE to activate or deactivate
>> the entry, then finalizes the order by ENTER key.
>> If the entry is activated, the boot index is added into the
>> BootOrder variable in the order of the list.
>>
>> The U-Boot menu framework is well designed for static menu,
>> this commit implements the own menu display and key handling
>> for dynamically change the order of menu entry.
>>
>> Signed-off-by: Masahisa Kojima <masahisa.kojima@linaro.org>
> 
> Hello Masahisa,
> 
> not all boot option will necessarily be in the boot order.
> 
> It must be possible to add an existing boot option to the boot order.
> 
> It must be possible to delete a boot option from the boot order without
> deleting the boot option.
> 
> I can't see how to do this inside the eficonfig command with the patch
> series applied

Sorry, I got this wrong. The inclusion/exclusion is done via the 
checkmark. This works fine.

Best regards

Heinrich

> 
>> ---
>> Changes in v11:
>> - remove BootOrder variable dependency
>> - use ANSI_CURSOR_POSITION and ANSI_CLEAR_LINE instead of printf("\n")
>>    since current eficonfig implementation does not handle console size 
>> correctly.
>>    printf("\n") at the outside of console size breaks the console output.
>> - add KEY_SPACE to toggle the boot option active status
>>
>> No update since v9
>>
>> Changes in v9:
>> - add function comment
>>
>> Changes in v8:
>> - add "Save" and "Quit" entries
>>
>> Changes in v7:
>> - use UP/DOWN and PLUS/MINUS key to change to order
>>
>> no update in v6:
>>
>>   cmd/eficonfig.c | 346 ++++++++++++++++++++++++++++++++++++++++++++++++
>>   1 file changed, 346 insertions(+)
>>
>> diff --git a/cmd/eficonfig.c b/cmd/eficonfig.c
>> index 938d46374e..f6152495c8 100644
>> --- a/cmd/eficonfig.c
>> +++ b/cmd/eficonfig.c
>> @@ -91,6 +91,23 @@ struct eficonfig_boot_selection_data {
>>       int *selected;
>>   };
>>
>> +/**
>> + * struct eficonfig_boot_order - structure to be used to update 
>> BootOrder variable
>> + *
>> + * @num:        index in the menu entry
>> + * @description:    pointer to the description string
>> + * @boot_index:        boot option index
>> + * @active:        flag to include the boot option into BootOrder 
>> variable
>> + * @list:        list structure
>> + */
>> +struct eficonfig_boot_order {
>> +    u32 num;
>> +    u16 *description;
>> +    u32 boot_index;
>> +    bool active;
>> +    struct list_head list;
>> +};
>> +
>>   /**
>>    * eficonfig_print_msg() - print message
>>    *
>> @@ -1665,6 +1682,334 @@ out:
>>       return ret;
>>   }
>>
>> +/**
>> + * eficonfig_display_change_boot_order() - display the BootOrder list
>> + *
>> + * @efi_menu:    pointer to the efimenu structure
>> + * Return:    status code
>> + */
>> +static void eficonfig_display_change_boot_order(struct efimenu 
>> *efi_menu)
>> +{
>> +    bool reverse;
>> +    struct list_head *pos, *n;
>> +    struct eficonfig_boot_order *entry;
>> +
>> +    printf(ANSI_CLEAR_CONSOLE ANSI_CURSOR_POSITION
>> +           "\n  ** Change Boot Order **\n"
>> +           ANSI_CURSOR_POSITION
>> +           "  Press UP/DOWN to move, +/- to change order"
>> +           ANSI_CURSOR_POSITION
>> +           "  Press SPACE to activate or deactivate the entry"
>> +           ANSI_CURSOR_POSITION
>> +           "  Select [Save] to complete, ESC/CTRL+C to quit"
>> +           ANSI_CURSOR_POSITION ANSI_CLEAR_LINE,
>> +           1, 1, efi_menu->count + 5, 1, efi_menu->count + 6, 1,
>> +           efi_menu->count + 7, 1,  efi_menu->count + 8, 1);
>> +
>> +    /* draw boot option list */
>> +    list_for_each_safe(pos, n, &efi_menu->list) {
>> +        entry = list_entry(pos, struct eficonfig_boot_order, list);
>> +        reverse = (entry->num == efi_menu->active);
>> +
>> +        printf(ANSI_CURSOR_POSITION, entry->num + 4, 7);
>> +
>> +        if (reverse)
>> +            puts(ANSI_COLOR_REVERSE);
>> +
>> +        if (entry->num < efi_menu->count - 2) {
>> +            if (entry->active)
>> +                printf("[*]  ");
>> +            else
>> +                printf("[ ]  ");
>> +        }
>> +
>> +        printf("%ls", entry->description);
>> +
>> +        if (reverse)
>> +            puts(ANSI_COLOR_RESET);
>> +    }
>> +}
>> +
>> +/**
>> + * eficonfig_choice_change_boot_order() - handle the BootOrder update
>> + *
>> + * @efi_menu:    pointer to the efimenu structure
>> + * Return:    status code
>> + */
>> +static efi_status_t eficonfig_choice_change_boot_order(struct efimenu 
>> *efi_menu)
>> +{
>> +    int esc = 0;
>> +    struct list_head *pos, *n;
>> +    struct eficonfig_boot_order *tmp;
>> +    enum bootmenu_key key = KEY_NONE;
>> +    struct eficonfig_boot_order *entry;
>> +
>> +    while (1) {
>> +        bootmenu_loop(NULL, &key, &esc);
>> +
>> +        switch (key) {
>> +        case KEY_PLUS:
>> +            if (efi_menu->active > 0) {
>> +                list_for_each_safe(pos, n, &efi_menu->list) {
>> +                    entry = list_entry(pos, struct 
>> eficonfig_boot_order, list);
>> +                    if (entry->num == efi_menu->active)
>> +                        break;
>> +                }
>> +                tmp = list_entry(pos->prev, struct 
>> eficonfig_boot_order, list);
>> +                entry->num--;
>> +                tmp->num++;
>> +                list_del(&tmp->list);
>> +                list_add(&tmp->list, &entry->list);
>> +            }
>> +            fallthrough;
>> +        case KEY_UP:
>> +            if (efi_menu->active > 0)
>> +                --efi_menu->active;
>> +            return EFI_NOT_READY;
>> +        case KEY_MINUS:
>> +            if (efi_menu->active < efi_menu->count - 3) {
>> +                list_for_each_safe(pos, n, &efi_menu->list) {
>> +                    entry = list_entry(pos, struct 
>> eficonfig_boot_order, list);
>> +                    if (entry->num == efi_menu->active)
>> +                        break;
>> +                }
>> +                tmp = list_entry(pos->next, struct 
>> eficonfig_boot_order, list);
>> +                entry->num++;
>> +                tmp->num--;
>> +                list_del(&entry->list);
>> +                list_add(&entry->list, &tmp->list);
>> +
>> +                ++efi_menu->active;
>> +            }
>> +            return EFI_NOT_READY;
>> +        case KEY_DOWN:
>> +            if (efi_menu->active < efi_menu->count - 1)
>> +                ++efi_menu->active;
>> +            return EFI_NOT_READY;
>> +        case KEY_SELECT:
>> +            /* "Save" */
>> +            if (efi_menu->active == efi_menu->count - 2)
>> +                return EFI_SUCCESS;
>> +
>> +            /* "Quit" */
>> +            if (efi_menu->active == efi_menu->count - 1)
>> +                return EFI_ABORTED;
>> +
>> +            break;
>> +        case KEY_SPACE:
>> +            if (efi_menu->active < efi_menu->count - 2) {
>> +                list_for_each_safe(pos, n, &efi_menu->list) {
>> +                    entry = list_entry(pos, struct 
>> eficonfig_boot_order, list);
>> +                    if (entry->num == efi_menu->active) {
>> +                        entry->active = entry->active ? false : true;
>> +                        return EFI_NOT_READY;
>> +                    }
>> +                }
>> +            }
>> +            break;
>> +        case KEY_QUIT:
>> +            return EFI_ABORTED;
>> +        default:
>> +            break;
>> +        }
>> +    }
>> +}
>> +
>> +/**
>> + * eficonfig_add_change_boot_order_entry() - add boot order entry
>> + *
>> + * @efi_menu:    pointer to the efimenu structure
>> + * @boot_index:    boot option index to be added
>> + * @active:    flag to include the boot option into BootOrder
>> + * Return:    status code
>> + */
>> +static efi_status_t eficonfig_add_change_boot_order_entry(struct 
>> efimenu *efi_menu,
>> +                              u32 boot_index, bool active)
>> +{
>> +    efi_status_t ret;
>> +    efi_uintn_t size;
>> +    void *load_option;
>> +    struct efi_load_option lo;
>> +    u16 varname[] = u"Boot####";
>> +    struct eficonfig_boot_order *entry;
>> +
>> +    efi_create_indexed_name(varname, sizeof(varname), "Boot", 
>> boot_index);
>> +    load_option = efi_get_var(varname, &efi_global_variable_guid, 
>> &size);
>> +    if (!load_option)
>> +        return EFI_SUCCESS;
>> +
>> +    ret = efi_deserialize_load_option(&lo, load_option, &size);
>> +    if (ret != EFI_SUCCESS) {
>> +        free(load_option);
>> +        return ret;
>> +    }
>> +
>> +    entry = calloc(1, sizeof(struct eficonfig_boot_order));
>> +    if (!entry) {
>> +        free(load_option);
>> +        return EFI_OUT_OF_RESOURCES;
>> +    }
>> +
>> +    entry->description = u16_strdup(lo.label);
>> +    if (!entry->description) {
>> +        free(load_option);
>> +        free(entry);
>> +        return EFI_OUT_OF_RESOURCES;
>> +    }
>> +    entry->num = efi_menu->count++;
>> +    entry->boot_index = boot_index;
>> +    entry->active = active;
>> +    list_add_tail(&entry->list, &efi_menu->list);
>> +
>> +    free(load_option);
>> +
>> +    return EFI_SUCCESS;
>> +}
>> +
>> +/**
>> + * eficonfig_create_change_boot_order_entry() - create boot order entry
>> + *
>> + * @efi_menu:    pointer to the efimenu structure
>> + * @bootorder:    pointer to the BootOrder variable
>> + * @num:    number of BootOrder entry
>> + * Return:    status code
>> + */
>> +static efi_status_t eficonfig_create_change_boot_order_entry(struct 
>> efimenu *efi_menu,
>> +                                 u16 *bootorder, efi_uintn_t num)
>> +{
>> +    u32 i;
>> +    efi_status_t ret;
>> +    struct eficonfig_boot_order *entry;
>> +
>> +    /* list the load option in the order of BootOrder variable */
>> +    for (i = 0; i < num; i++) {
>> +        if (efi_menu->count >= EFICONFIG_ENTRY_NUM_MAX - 2)
>> +            break;
>> +
>> +        ret = eficonfig_add_change_boot_order_entry(efi_menu, 
>> bootorder[i], true);
>> +        if (ret != EFI_SUCCESS)
>> +            goto out;
>> +    }
>> +
>> +    /* list the remaining load option not included in the BootOrder */
>> +    for (i = 0; i < 0xFFFF; i++) {
>> +        if (efi_menu->count >= EFICONFIG_ENTRY_NUM_MAX - 2)
>> +            break;
>> +
>> +        /* If the index is included in the BootOrder, skip it */
>> +        if (search_bootorder(bootorder, num, i, NULL))
>> +            continue;
>> +
>> +        ret = eficonfig_add_change_boot_order_entry(efi_menu, i, false);
>> +        if (ret != EFI_SUCCESS)
>> +            goto out;
>> +    }
>> +
>> +    /* add "Save" and "Quit" entries */
>> +    entry = calloc(1, sizeof(struct eficonfig_boot_order));
>> +    if (!entry)
>> +        goto out;
>> +
>> +    entry->num = efi_menu->count++;
>> +    entry->description = u16_strdup(u"Save");
>> +    list_add_tail(&entry->list, &efi_menu->list);
>> +
>> +    entry = calloc(1, sizeof(struct eficonfig_boot_order));
>> +    if (!entry)
>> +        goto out;
>> +
>> +    entry->num = efi_menu->count++;
>> +    entry->description = u16_strdup(u"Quit");
>> +    list_add_tail(&entry->list, &efi_menu->list);
>> +
>> +    efi_menu->active = 0;
>> +
>> +    return EFI_SUCCESS;
>> +out:
>> +    return EFI_OUT_OF_RESOURCES;
>> +}
>> +
>> +/**
>> + * eficonfig_process_change_boot_order() - handler to change boot order
>> + *
>> + * @data:    pointer to the data for each entry
>> + * Return:    status code
>> + */
>> +static efi_status_t eficonfig_process_change_boot_order(void *data)
>> +{
>> +    u32 count;
>> +    u16 *bootorder;
>> +    efi_status_t ret;
>> +    efi_uintn_t num, size;
>> +    struct list_head *pos, *n;
>> +    struct eficonfig_boot_order *entry;
>> +    struct efimenu *efi_menu;
>> +
>> +    efi_menu = calloc(1, sizeof(struct efimenu));
>> +    if (!efi_menu)
>> +        return EFI_OUT_OF_RESOURCES;
>> +
>> +    bootorder = efi_get_var(u"BootOrder", &efi_global_variable_guid, 
>> &size);
>> +
>> +    INIT_LIST_HEAD(&efi_menu->list);
>> +    num = size / sizeof(u16);
>> +    ret = eficonfig_create_change_boot_order_entry(efi_menu, 
>> bootorder, num);
>> +    if (ret != EFI_SUCCESS)
>> +        goto out;
>> +
>> +    while (1) {
>> +        eficonfig_display_change_boot_order(efi_menu);
>> +
>> +        ret = eficonfig_choice_change_boot_order(efi_menu);
>> +        if (ret == EFI_SUCCESS) {
>> +            u16 *new_bootorder;
>> +
>> +            new_bootorder = calloc(1, (efi_menu->count - 2) * 
>> sizeof(u16));
>> +            if (!new_bootorder) {
>> +                ret = EFI_OUT_OF_RESOURCES;
>> +                goto out;
>> +            }
>> +
>> +            /* create new BootOrder  */
>> +            count = 0;
>> +            list_for_each_safe(pos, n, &efi_menu->list) {
>> +                entry = list_entry(pos, struct eficonfig_boot_order, 
>> list);
>> +                if (entry->active)
>> +                    new_bootorder[count++] = entry->boot_index;
>> +            }
>> +
>> +            size = count * 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, new_bootorder, false);
>> +
>> +            free(new_bootorder);
>> +            goto out;
>> +        } else if (ret == EFI_NOT_READY) {
>> +            continue;
>> +        } else {
>> +            goto out;
>> +        }
>> +    }
>> +out:
>> +    list_for_each_safe(pos, n, &efi_menu->list) {
>> +        entry = list_entry(pos, struct eficonfig_boot_order, list);
>> +        list_del(&entry->list);
>> +        free(entry->description);
>> +        free(entry);
>> +    }
>> +
>> +    free(bootorder);
>> +    free(efi_menu);
>> +
>> +    /* to stay the parent menu */
>> +    ret = (ret == EFI_ABORTED) ? EFI_NOT_READY : ret;
>> +
>> +    return ret;
>> +}
>> +
>>   /**
>>    * eficonfig_init() - do required initialization for eficonfig command
>>    *
>> @@ -1695,6 +2040,7 @@ static efi_status_t eficonfig_init(void)
>>   static const struct eficonfig_item maintenance_menu_items[] = {
>>       {"Add Boot Option", eficonfig_process_add_boot_option},
>>       {"Edit Boot Option", eficonfig_process_edit_boot_option},
>> +    {"Change Boot Order", eficonfig_process_change_boot_order},
>>       {"Quit", eficonfig_process_quit},
>>   };
>>
> 


^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH v11 4/9] eficonfig: add "Change Boot Order" menu entry
  2022-08-18  6:50     ` Heinrich Schuchardt
@ 2022-08-18  7:34       ` Masahisa Kojima
  0 siblings, 0 replies; 25+ messages in thread
From: Masahisa Kojima @ 2022-08-18  7:34 UTC (permalink / raw)
  To: Heinrich Schuchardt
  Cc: Ilias Apalodimas, Simon Glass, Takahiro Akashi, Mark Kettenis, u-boot

Hi Heinrich,

On Thu, 18 Aug 2022 at 15:50, Heinrich Schuchardt <xypron.glpk@gmx.de> wrote:
>
> On 8/18/22 08:17, Heinrich Schuchardt wrote:
> > On 8/17/22 11:36, Masahisa Kojima wrote:
> >> This commit adds the menu entry to update UEFI BootOrder variable.
> >> User moves the entry with UP/DOWN key, changes the order
> >> with PLUS/MINUS key, press SPACE to activate or deactivate
> >> the entry, then finalizes the order by ENTER key.
> >> If the entry is activated, the boot index is added into the
> >> BootOrder variable in the order of the list.
> >>
> >> The U-Boot menu framework is well designed for static menu,
> >> this commit implements the own menu display and key handling
> >> for dynamically change the order of menu entry.
> >>
> >> Signed-off-by: Masahisa Kojima <masahisa.kojima@linaro.org>
> >
> > Hello Masahisa,
> >
> > not all boot option will necessarily be in the boot order.
> >
> > It must be possible to add an existing boot option to the boot order.
> >
> > It must be possible to delete a boot option from the boot order without
> > deleting the boot option.
> >
> > I can't see how to do this inside the eficonfig command with the patch
> > series applied
>
> Sorry, I got this wrong. The inclusion/exclusion is done via the
> checkmark. This works fine.

Thank you for your quick check!

Regrads,
Masahisa Kojima

>
> Best regards
>
> Heinrich
>
> >
> >> ---
> >> Changes in v11:
> >> - remove BootOrder variable dependency
> >> - use ANSI_CURSOR_POSITION and ANSI_CLEAR_LINE instead of printf("\n")
> >>    since current eficonfig implementation does not handle console size
> >> correctly.
> >>    printf("\n") at the outside of console size breaks the console output.
> >> - add KEY_SPACE to toggle the boot option active status
> >>
> >> No update since v9
> >>
> >> Changes in v9:
> >> - add function comment
> >>
> >> Changes in v8:
> >> - add "Save" and "Quit" entries
> >>
> >> Changes in v7:
> >> - use UP/DOWN and PLUS/MINUS key to change to order
> >>
> >> no update in v6:
> >>
> >>   cmd/eficonfig.c | 346 ++++++++++++++++++++++++++++++++++++++++++++++++
> >>   1 file changed, 346 insertions(+)
> >>
> >> diff --git a/cmd/eficonfig.c b/cmd/eficonfig.c
> >> index 938d46374e..f6152495c8 100644
> >> --- a/cmd/eficonfig.c
> >> +++ b/cmd/eficonfig.c
> >> @@ -91,6 +91,23 @@ struct eficonfig_boot_selection_data {
> >>       int *selected;
> >>   };
> >>
> >> +/**
> >> + * struct eficonfig_boot_order - structure to be used to update
> >> BootOrder variable
> >> + *
> >> + * @num:        index in the menu entry
> >> + * @description:    pointer to the description string
> >> + * @boot_index:        boot option index
> >> + * @active:        flag to include the boot option into BootOrder
> >> variable
> >> + * @list:        list structure
> >> + */
> >> +struct eficonfig_boot_order {
> >> +    u32 num;
> >> +    u16 *description;
> >> +    u32 boot_index;
> >> +    bool active;
> >> +    struct list_head list;
> >> +};
> >> +
> >>   /**
> >>    * eficonfig_print_msg() - print message
> >>    *
> >> @@ -1665,6 +1682,334 @@ out:
> >>       return ret;
> >>   }
> >>
> >> +/**
> >> + * eficonfig_display_change_boot_order() - display the BootOrder list
> >> + *
> >> + * @efi_menu:    pointer to the efimenu structure
> >> + * Return:    status code
> >> + */
> >> +static void eficonfig_display_change_boot_order(struct efimenu
> >> *efi_menu)
> >> +{
> >> +    bool reverse;
> >> +    struct list_head *pos, *n;
> >> +    struct eficonfig_boot_order *entry;
> >> +
> >> +    printf(ANSI_CLEAR_CONSOLE ANSI_CURSOR_POSITION
> >> +           "\n  ** Change Boot Order **\n"
> >> +           ANSI_CURSOR_POSITION
> >> +           "  Press UP/DOWN to move, +/- to change order"
> >> +           ANSI_CURSOR_POSITION
> >> +           "  Press SPACE to activate or deactivate the entry"
> >> +           ANSI_CURSOR_POSITION
> >> +           "  Select [Save] to complete, ESC/CTRL+C to quit"
> >> +           ANSI_CURSOR_POSITION ANSI_CLEAR_LINE,
> >> +           1, 1, efi_menu->count + 5, 1, efi_menu->count + 6, 1,
> >> +           efi_menu->count + 7, 1,  efi_menu->count + 8, 1);
> >> +
> >> +    /* draw boot option list */
> >> +    list_for_each_safe(pos, n, &efi_menu->list) {
> >> +        entry = list_entry(pos, struct eficonfig_boot_order, list);
> >> +        reverse = (entry->num == efi_menu->active);
> >> +
> >> +        printf(ANSI_CURSOR_POSITION, entry->num + 4, 7);
> >> +
> >> +        if (reverse)
> >> +            puts(ANSI_COLOR_REVERSE);
> >> +
> >> +        if (entry->num < efi_menu->count - 2) {
> >> +            if (entry->active)
> >> +                printf("[*]  ");
> >> +            else
> >> +                printf("[ ]  ");
> >> +        }
> >> +
> >> +        printf("%ls", entry->description);
> >> +
> >> +        if (reverse)
> >> +            puts(ANSI_COLOR_RESET);
> >> +    }
> >> +}
> >> +
> >> +/**
> >> + * eficonfig_choice_change_boot_order() - handle the BootOrder update
> >> + *
> >> + * @efi_menu:    pointer to the efimenu structure
> >> + * Return:    status code
> >> + */
> >> +static efi_status_t eficonfig_choice_change_boot_order(struct efimenu
> >> *efi_menu)
> >> +{
> >> +    int esc = 0;
> >> +    struct list_head *pos, *n;
> >> +    struct eficonfig_boot_order *tmp;
> >> +    enum bootmenu_key key = KEY_NONE;
> >> +    struct eficonfig_boot_order *entry;
> >> +
> >> +    while (1) {
> >> +        bootmenu_loop(NULL, &key, &esc);
> >> +
> >> +        switch (key) {
> >> +        case KEY_PLUS:
> >> +            if (efi_menu->active > 0) {
> >> +                list_for_each_safe(pos, n, &efi_menu->list) {
> >> +                    entry = list_entry(pos, struct
> >> eficonfig_boot_order, list);
> >> +                    if (entry->num == efi_menu->active)
> >> +                        break;
> >> +                }
> >> +                tmp = list_entry(pos->prev, struct
> >> eficonfig_boot_order, list);
> >> +                entry->num--;
> >> +                tmp->num++;
> >> +                list_del(&tmp->list);
> >> +                list_add(&tmp->list, &entry->list);
> >> +            }
> >> +            fallthrough;
> >> +        case KEY_UP:
> >> +            if (efi_menu->active > 0)
> >> +                --efi_menu->active;
> >> +            return EFI_NOT_READY;
> >> +        case KEY_MINUS:
> >> +            if (efi_menu->active < efi_menu->count - 3) {
> >> +                list_for_each_safe(pos, n, &efi_menu->list) {
> >> +                    entry = list_entry(pos, struct
> >> eficonfig_boot_order, list);
> >> +                    if (entry->num == efi_menu->active)
> >> +                        break;
> >> +                }
> >> +                tmp = list_entry(pos->next, struct
> >> eficonfig_boot_order, list);
> >> +                entry->num++;
> >> +                tmp->num--;
> >> +                list_del(&entry->list);
> >> +                list_add(&entry->list, &tmp->list);
> >> +
> >> +                ++efi_menu->active;
> >> +            }
> >> +            return EFI_NOT_READY;
> >> +        case KEY_DOWN:
> >> +            if (efi_menu->active < efi_menu->count - 1)
> >> +                ++efi_menu->active;
> >> +            return EFI_NOT_READY;
> >> +        case KEY_SELECT:
> >> +            /* "Save" */
> >> +            if (efi_menu->active == efi_menu->count - 2)
> >> +                return EFI_SUCCESS;
> >> +
> >> +            /* "Quit" */
> >> +            if (efi_menu->active == efi_menu->count - 1)
> >> +                return EFI_ABORTED;
> >> +
> >> +            break;
> >> +        case KEY_SPACE:
> >> +            if (efi_menu->active < efi_menu->count - 2) {
> >> +                list_for_each_safe(pos, n, &efi_menu->list) {
> >> +                    entry = list_entry(pos, struct
> >> eficonfig_boot_order, list);
> >> +                    if (entry->num == efi_menu->active) {
> >> +                        entry->active = entry->active ? false : true;
> >> +                        return EFI_NOT_READY;
> >> +                    }
> >> +                }
> >> +            }
> >> +            break;
> >> +        case KEY_QUIT:
> >> +            return EFI_ABORTED;
> >> +        default:
> >> +            break;
> >> +        }
> >> +    }
> >> +}
> >> +
> >> +/**
> >> + * eficonfig_add_change_boot_order_entry() - add boot order entry
> >> + *
> >> + * @efi_menu:    pointer to the efimenu structure
> >> + * @boot_index:    boot option index to be added
> >> + * @active:    flag to include the boot option into BootOrder
> >> + * Return:    status code
> >> + */
> >> +static efi_status_t eficonfig_add_change_boot_order_entry(struct
> >> efimenu *efi_menu,
> >> +                              u32 boot_index, bool active)
> >> +{
> >> +    efi_status_t ret;
> >> +    efi_uintn_t size;
> >> +    void *load_option;
> >> +    struct efi_load_option lo;
> >> +    u16 varname[] = u"Boot####";
> >> +    struct eficonfig_boot_order *entry;
> >> +
> >> +    efi_create_indexed_name(varname, sizeof(varname), "Boot",
> >> boot_index);
> >> +    load_option = efi_get_var(varname, &efi_global_variable_guid,
> >> &size);
> >> +    if (!load_option)
> >> +        return EFI_SUCCESS;
> >> +
> >> +    ret = efi_deserialize_load_option(&lo, load_option, &size);
> >> +    if (ret != EFI_SUCCESS) {
> >> +        free(load_option);
> >> +        return ret;
> >> +    }
> >> +
> >> +    entry = calloc(1, sizeof(struct eficonfig_boot_order));
> >> +    if (!entry) {
> >> +        free(load_option);
> >> +        return EFI_OUT_OF_RESOURCES;
> >> +    }
> >> +
> >> +    entry->description = u16_strdup(lo.label);
> >> +    if (!entry->description) {
> >> +        free(load_option);
> >> +        free(entry);
> >> +        return EFI_OUT_OF_RESOURCES;
> >> +    }
> >> +    entry->num = efi_menu->count++;
> >> +    entry->boot_index = boot_index;
> >> +    entry->active = active;
> >> +    list_add_tail(&entry->list, &efi_menu->list);
> >> +
> >> +    free(load_option);
> >> +
> >> +    return EFI_SUCCESS;
> >> +}
> >> +
> >> +/**
> >> + * eficonfig_create_change_boot_order_entry() - create boot order entry
> >> + *
> >> + * @efi_menu:    pointer to the efimenu structure
> >> + * @bootorder:    pointer to the BootOrder variable
> >> + * @num:    number of BootOrder entry
> >> + * Return:    status code
> >> + */
> >> +static efi_status_t eficonfig_create_change_boot_order_entry(struct
> >> efimenu *efi_menu,
> >> +                                 u16 *bootorder, efi_uintn_t num)
> >> +{
> >> +    u32 i;
> >> +    efi_status_t ret;
> >> +    struct eficonfig_boot_order *entry;
> >> +
> >> +    /* list the load option in the order of BootOrder variable */
> >> +    for (i = 0; i < num; i++) {
> >> +        if (efi_menu->count >= EFICONFIG_ENTRY_NUM_MAX - 2)
> >> +            break;
> >> +
> >> +        ret = eficonfig_add_change_boot_order_entry(efi_menu,
> >> bootorder[i], true);
> >> +        if (ret != EFI_SUCCESS)
> >> +            goto out;
> >> +    }
> >> +
> >> +    /* list the remaining load option not included in the BootOrder */
> >> +    for (i = 0; i < 0xFFFF; i++) {
> >> +        if (efi_menu->count >= EFICONFIG_ENTRY_NUM_MAX - 2)
> >> +            break;
> >> +
> >> +        /* If the index is included in the BootOrder, skip it */
> >> +        if (search_bootorder(bootorder, num, i, NULL))
> >> +            continue;
> >> +
> >> +        ret = eficonfig_add_change_boot_order_entry(efi_menu, i, false);
> >> +        if (ret != EFI_SUCCESS)
> >> +            goto out;
> >> +    }
> >> +
> >> +    /* add "Save" and "Quit" entries */
> >> +    entry = calloc(1, sizeof(struct eficonfig_boot_order));
> >> +    if (!entry)
> >> +        goto out;
> >> +
> >> +    entry->num = efi_menu->count++;
> >> +    entry->description = u16_strdup(u"Save");
> >> +    list_add_tail(&entry->list, &efi_menu->list);
> >> +
> >> +    entry = calloc(1, sizeof(struct eficonfig_boot_order));
> >> +    if (!entry)
> >> +        goto out;
> >> +
> >> +    entry->num = efi_menu->count++;
> >> +    entry->description = u16_strdup(u"Quit");
> >> +    list_add_tail(&entry->list, &efi_menu->list);
> >> +
> >> +    efi_menu->active = 0;
> >> +
> >> +    return EFI_SUCCESS;
> >> +out:
> >> +    return EFI_OUT_OF_RESOURCES;
> >> +}
> >> +
> >> +/**
> >> + * eficonfig_process_change_boot_order() - handler to change boot order
> >> + *
> >> + * @data:    pointer to the data for each entry
> >> + * Return:    status code
> >> + */
> >> +static efi_status_t eficonfig_process_change_boot_order(void *data)
> >> +{
> >> +    u32 count;
> >> +    u16 *bootorder;
> >> +    efi_status_t ret;
> >> +    efi_uintn_t num, size;
> >> +    struct list_head *pos, *n;
> >> +    struct eficonfig_boot_order *entry;
> >> +    struct efimenu *efi_menu;
> >> +
> >> +    efi_menu = calloc(1, sizeof(struct efimenu));
> >> +    if (!efi_menu)
> >> +        return EFI_OUT_OF_RESOURCES;
> >> +
> >> +    bootorder = efi_get_var(u"BootOrder", &efi_global_variable_guid,
> >> &size);
> >> +
> >> +    INIT_LIST_HEAD(&efi_menu->list);
> >> +    num = size / sizeof(u16);
> >> +    ret = eficonfig_create_change_boot_order_entry(efi_menu,
> >> bootorder, num);
> >> +    if (ret != EFI_SUCCESS)
> >> +        goto out;
> >> +
> >> +    while (1) {
> >> +        eficonfig_display_change_boot_order(efi_menu);
> >> +
> >> +        ret = eficonfig_choice_change_boot_order(efi_menu);
> >> +        if (ret == EFI_SUCCESS) {
> >> +            u16 *new_bootorder;
> >> +
> >> +            new_bootorder = calloc(1, (efi_menu->count - 2) *
> >> sizeof(u16));
> >> +            if (!new_bootorder) {
> >> +                ret = EFI_OUT_OF_RESOURCES;
> >> +                goto out;
> >> +            }
> >> +
> >> +            /* create new BootOrder  */
> >> +            count = 0;
> >> +            list_for_each_safe(pos, n, &efi_menu->list) {
> >> +                entry = list_entry(pos, struct eficonfig_boot_order,
> >> list);
> >> +                if (entry->active)
> >> +                    new_bootorder[count++] = entry->boot_index;
> >> +            }
> >> +
> >> +            size = count * 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, new_bootorder, false);
> >> +
> >> +            free(new_bootorder);
> >> +            goto out;
> >> +        } else if (ret == EFI_NOT_READY) {
> >> +            continue;
> >> +        } else {
> >> +            goto out;
> >> +        }
> >> +    }
> >> +out:
> >> +    list_for_each_safe(pos, n, &efi_menu->list) {
> >> +        entry = list_entry(pos, struct eficonfig_boot_order, list);
> >> +        list_del(&entry->list);
> >> +        free(entry->description);
> >> +        free(entry);
> >> +    }
> >> +
> >> +    free(bootorder);
> >> +    free(efi_menu);
> >> +
> >> +    /* to stay the parent menu */
> >> +    ret = (ret == EFI_ABORTED) ? EFI_NOT_READY : ret;
> >> +
> >> +    return ret;
> >> +}
> >> +
> >>   /**
> >>    * eficonfig_init() - do required initialization for eficonfig command
> >>    *
> >> @@ -1695,6 +2040,7 @@ static efi_status_t eficonfig_init(void)
> >>   static const struct eficonfig_item maintenance_menu_items[] = {
> >>       {"Add Boot Option", eficonfig_process_add_boot_option},
> >>       {"Edit Boot Option", eficonfig_process_edit_boot_option},
> >> +    {"Change Boot Order", eficonfig_process_change_boot_order},
> >>       {"Quit", eficonfig_process_quit},
> >>   };
> >>
> >
>

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH v11 1/9] eficonfig: menu-driven addition of UEFI boot option
  2022-08-18  6:43   ` Heinrich Schuchardt
@ 2022-08-18  8:59     ` Masahisa Kojima
  0 siblings, 0 replies; 25+ messages in thread
From: Masahisa Kojima @ 2022-08-18  8:59 UTC (permalink / raw)
  To: Heinrich Schuchardt
  Cc: Ilias Apalodimas, Simon Glass, Takahiro Akashi, Mark Kettenis,
	Michal Simek, Ovidiu Panait, Ashok Reddy Soma, John Keeping,
	Thomas Huth, Chris Morgan, Huang Jianan, u-boot

Hi Heinrich,

On Thu, 18 Aug 2022 at 15:49, Heinrich Schuchardt <xypron.glpk@gmx.de> wrote:
>
> On 8/17/22 11:36, Masahisa Kojima wrote:
> > This commit add the "eficonfig" command.
> > The "eficonfig" command implements the menu-driven UEFI boot option
> > maintenance feature. This commit implements the addition of
> > new boot option. User can select the block device volume having
> > efi_simple_file_system_protocol and select the file corresponding
> > to the Boot#### variable. User can also enter the description and
> > optional_data of the BOOT#### variable in utf8.
> >
> > This commit adds "include/efi_config.h", it contains the common
> > definition to be used from other menus such as UEFI Secure Boot
> > key management.
> >
> > Signed-off-by: Masahisa Kojima <masahisa.kojima@linaro.org>
> > ---
> > Changes in v11:
> > - refactor menu entry construction, directly use eficonfig_entry structure
> > - remove reading directory info to calculate the number of entry
> > - fix invalid efi_free_pool() in ill_file_info()
> > - use ANSI_CURSOR_POSITION and ANSI_CLEAR_LINE instead of printf("\n")
> >    since current eficonfig implementation does not handle console size correctly.
> >    printf("\n") at the outside of console size breaks the console output.
> >
> > Changes in v10:
> > - add initrd file selection
> > - do refactoring
> > - eficonfig_process_common() use list structure
> > - remove u'/' before copying file_path into current_path
> > - fix typos
> > - check snprintf error
> >
> > Changes in v9:
> > - move "efi_guid_bootmenu_auto_generated definition" into efi_bootmgr.c
> >    to address build error when CMD_EFICONFIG is disabled
> > - fix typos and comment
> > - remove file system information from error message
> > - remove unreachable code in eficonfig_choice_entry()
> > - single printf() call as much as possible
> > - call only getchar() in  eficonfig_print_msg()
> > - filter out '.' entry from file selection
> > - update the efi_disk_get_device_name() implementation
> > - add function comment
> >
> > Changes in v8:
> > - command name is change from "efimenu" to "eficonfig"
> > - function and struct prefixes is changed to "eficonfig"
> > - fix menu header string
> >
> > Changes in v7:
> > - add "efimenu" command and uefi variable maintenance code
> >    moved into cmd/efimenu.c
> > - create include/efimenu.h to define the common definition for
> >    the other menu such as UEFI Secure Boot key management
> > - update boot option edit UI, user can select description, file,
> >    and optional_data to edit in the same menu like following.
> >
> >    ** Edit Boot Option **
> >
> >       Description: debian
> >       File: virtio 0:1/EFI\debian\grubaa64.efi
> >       Optional Data: test
> >       Save
> >       Quit
> >
> > - remove exit parameter from efimenu_process_common()
> > - menu title type is changed from u16 to char
> > - efimenu_process_common() add menu title string
> > - reduce printf/puts function call for displaying the menu
> > - efi_console_get_u16_string() accept 0 length to allow
> >    optional_data is empty
> > - efi_console_get_u16_string() the "size" parameter name is changes to "count"
> > - efimenu is now designed to maintain the UEFI variables, remove autoboot related code
> > - remove one empty line before "Quit" entry
> > - efimenu_init() processes only the first time
> >
> > Changes in v6:
> > - fix typos
> > - modify volume name to match U-Boot syntax
> > - compile in CONFIG_EFI_LOADER=n and CONFIG_CMD_BOOTEFI_BOOTMGR=n
> > - simplify u16_strncmp() usage
> > - support "a\b.efi" file path, use link list to handle filepath
> > - modify length check condition
> > - UEFI related menu items only appears with CONFIG_AUTOBOOT_MENU_SHOW=y
> >
> > Changes in v5:
> > - remove forward declarations
> > - add const qualifier for menu items
> > - fix the possible unaligned access for directory info access
> > - split into three commit 1)add boot option 2) delete boot option 3)change boot order
> >    This commit is 1)add boot option.
> > - fix file name buffer allocation size, it should be EFI_BOOTMENU_FILE_PATH_MAX * sizeof(u16)
> > - fix wrong size checking for file selection
> >
> > 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 RFC v3:
> >   not included in v3 series
> >
> > Changes in RFC 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/Kconfig                   |    7 +
> >   cmd/Makefile                  |    1 +
> >   cmd/eficonfig.c               | 1491 +++++++++++++++++++++++++++++++++
> >   include/efi_config.h          |   90 ++
> >   include/efi_loader.h          |   43 +
> >   lib/efi_loader/efi_bootmgr.c  |    3 +
> >   lib/efi_loader/efi_boottime.c |   52 +-
> >   lib/efi_loader/efi_console.c  |   70 ++
> >   lib/efi_loader/efi_disk.c     |   50 ++
> >   lib/efi_loader/efi_file.c     |   75 +-
> >   10 files changed, 1835 insertions(+), 47 deletions(-)
> >   create mode 100644 cmd/eficonfig.c
> >   create mode 100644 include/efi_config.h
> >
> > diff --git a/cmd/Kconfig b/cmd/Kconfig
> > index 211ebe9c87..a1e8613c56 100644
> > --- a/cmd/Kconfig
> > +++ b/cmd/Kconfig
> > @@ -1928,6 +1928,13 @@ config CMD_EFIDEBUG
> >         particularly for managing boot parameters as  well as examining
> >         various EFI status for debugging.
> >
> > +config CMD_EFICONFIG
> > +     bool "eficonfig - provide menu-driven uefi variables maintenance interface"
> > +     depends on CMD_BOOTEFI_BOOTMGR
> > +     help
> > +       Enable the 'eficonfig' command which provides the menu-driven UEFI
> > +       variable maintenance interface.
> > +
> >   config CMD_EXCEPTION
> >       bool "exception - raise exception"
> >       depends on ARM || RISCV || SANDBOX || X86
> > diff --git a/cmd/Makefile b/cmd/Makefile
> > index 6e87522b62..18f5cb890d 100644
> > --- a/cmd/Makefile
> > +++ b/cmd/Makefile
> > @@ -63,6 +63,7 @@ obj-$(CONFIG_ENV_IS_IN_EEPROM) += eeprom.o
> >   obj-$(CONFIG_CMD_EEPROM) += eeprom.o
> >   obj-$(CONFIG_EFI) += efi.o
> >   obj-$(CONFIG_CMD_EFIDEBUG) += efidebug.o
> > +obj-$(CONFIG_CMD_EFICONFIG) += eficonfig.o
> >   obj-$(CONFIG_CMD_ELF) += elf.o
> >   obj-$(CONFIG_CMD_EROFS) += erofs.o
> >   obj-$(CONFIG_HUSH_PARSER) += exit.o
> > diff --git a/cmd/eficonfig.c b/cmd/eficonfig.c
> > new file mode 100644
> > index 0000000000..39fbd3f0ad
> > --- /dev/null
> > +++ b/cmd/eficonfig.c
> > @@ -0,0 +1,1491 @@
> > +// SPDX-License-Identifier: GPL-2.0+
> > +/*
> > + *  Menu-driven UEFI Variable maintenance
> > + *
> > + *  Copyright (c) 2022 Masahisa Kojima, Linaro Limited
> > + */
> > +
> > +#include <ansi.h>
> > +#include <common.h>
> > +#include <charset.h>
> > +#include <efi_loader.h>
> > +#include <efi_load_initrd.h>
> > +#include <efi_config.h>
> > +#include <efi_variable.h>
> > +#include <log.h>
> > +#include <malloc.h>
> > +#include <menu.h>
> > +#include <watchdog.h>
> > +#include <asm/unaligned.h>
> > +#include <linux/delay.h>
> > +
> > +static struct efi_simple_text_input_protocol *cin;
> > +
> > +#define EFICONFIG_DESCRIPTION_MAX 32
> > +#define EFICONFIG_OPTIONAL_DATA_MAX 64
> > +
> > +/**
> > + * struct eficonfig_filepath_info - structure to be used to store file path
> > + *
> > + * @name:    file or directory name
> > + * @list:    list structure
> > + */
> > +struct eficonfig_filepath_info {
> > +     u16 *name;
> > +     struct list_head list;
> > +};
> > +
> > +/**
> > + * struct eficonfig_boot_option - structure to be used for updating UEFI boot option
> > + *
> > + * @file_info:               user selected file info
> > + * @initrd_info:     user selected initrd file info
> > + * @boot_index:              index of the UEFI BootOrder variable
> > + * @description:     pointer to the description string
> > + * @optional_data:   pointer to the optional_data
> > + * @edit_completed:  flag indicates edit complete
> > + */
> > +struct eficonfig_boot_option {
> > +     struct eficonfig_select_file_info file_info;
> > +     struct eficonfig_select_file_info initrd_info;
> > +     unsigned int boot_index;
> > +     u16 *description;
> > +     u16 *optional_data;
> > +     bool edit_completed;
> > +};
> > +
> > +/**
> > + * struct eficonfig_volume_entry_data - structure to be used to store volume info
> > + *
> > + * @file_info:       pointer to file info structure
> > + * @v:               pointer to the protocol interface
> > + * @dp:              pointer to the device path
> > + */
> > +struct eficonfig_volume_entry_data {
> > +     struct eficonfig_select_file_info *file_info;
> > +     struct efi_simple_file_system_protocol *v;
> > +     struct efi_device_path *dp;
> > +};
> > +
> > +/**
> > + * struct eficonfig_file_entry_data - structure to be used to store file info
> > + *
> > + * @file_info:               pointer to file info structure
> > + * @is_directory:    flag to indentify the directory or file
> > + * @file_name:               name of directory or file
> > + */
> > +struct eficonfig_file_entry_data {
> > +     struct eficonfig_select_file_info *file_info;
> > +     bool is_directory;
> > +     u16 *file_name;
> > +};
> > +
> > +/**
> > + * eficonfig_print_msg() - print message
> > + *
> > + * display the message to the user, user proceeds the screen
> > + * with any key press.
> > + *
> > + * @items:           pointer to the structure of each menu entry
> > + * @count:           the number of menu entry
> > + * @menu_header:     pointer to the menu header string
> > + * Return:   status code
> > + */
> > +void eficonfig_print_msg(char *msg)
> > +{
> > +     /* Flush input */
> > +     while (tstc())
> > +             getchar();
> > +
> > +     printf(ANSI_CURSOR_HIDE
> > +            ANSI_CLEAR_CONSOLE
> > +            ANSI_CURSOR_POSITION
> > +            "%s\n\n  Press any key to continue", 3, 4, msg);
> > +
> > +     getchar();
> > +}
> > +
> > +/**
> > + * eficonfig_print_entry() - print each menu entry
> > + *
> > + * @data:    pointer to the data associated with each menu entry
> > + */
> > +static void eficonfig_print_entry(void *data)
> > +{
> > +     struct eficonfig_entry *entry = data;
> > +     int reverse = (entry->efi_menu->active == entry->num);
> > +
> > +     /* TODO: support scroll or page for many entries */
> > +
> > +     /*
> > +      * Move cursor to line where the entry will be drawn (entry->num)
> > +      * First 3 lines(menu header) + 1 empty line
> > +      */
> > +     printf(ANSI_CURSOR_POSITION, entry->num + 4, 7);
> > +
> > +     if (reverse)
> > +             puts(ANSI_COLOR_REVERSE);
> > +
> > +     printf("%s", entry->title);
> > +
> > +     if (reverse)
> > +             puts(ANSI_COLOR_RESET);
> > +}
> > +
> > +/**
> > + * eficonfig_display_statusline() - print status line
> > + *
> > + * @m:       pointer to the menu structure
> > + */
> > +static void eficonfig_display_statusline(struct menu *m)
> > +{
> > +     struct eficonfig_entry *entry;
> > +
> > +     if (menu_default_choice(m, (void *)&entry) < 0)
> > +             return;
> > +
> > +     printf(ANSI_CURSOR_POSITION
> > +           "\n%s\n"
> > +            ANSI_CURSOR_POSITION ANSI_CLEAR_LINE ANSI_CURSOR_POSITION
> > +            "  Press UP/DOWN to move, ENTER to select, ESC/CTRL+C to quit"
> > +            ANSI_CLEAR_LINE_TO_END ANSI_CURSOR_POSITION ANSI_CLEAR_LINE,
> > +            1, 1, entry->efi_menu->menu_header, entry->efi_menu->count + 5, 1,
> > +            entry->efi_menu->count + 6, 1, entry->efi_menu->count + 7, 1);
> > +}
> > +
> > +/**
> > + * eficonfig_choice_entry() - user key input handler
> > + *
> > + * @data:    pointer to the efimenu structure
> > + * Return:   key string to identify the selected entry
> > + */
> > +static char *eficonfig_choice_entry(void *data)
> > +{
> > +     int esc = 0;
> > +     struct list_head *pos, *n;
> > +     struct eficonfig_entry *entry;
> > +     enum bootmenu_key key = KEY_NONE;
> > +     struct efimenu *efi_menu = data;
> > +
> > +     while (1) {
> > +             bootmenu_loop((struct bootmenu_data *)efi_menu, &key, &esc);
> > +
> > +             switch (key) {
> > +             case KEY_UP:
> > +                     if (efi_menu->active > 0)
> > +                             --efi_menu->active;
> > +                     /* no menu key selected, regenerate menu */
> > +                     return NULL;
> > +             case KEY_DOWN:
> > +                     if (efi_menu->active < efi_menu->count - 1)
> > +                             ++efi_menu->active;
> > +                     /* no menu key selected, regenerate menu */
> > +                     return NULL;
> > +             case KEY_SELECT:
> > +                     list_for_each_safe(pos, n, &efi_menu->list) {
> > +                             entry = list_entry(pos, struct eficonfig_entry, list);
> > +                             if (entry->num == efi_menu->active)
> > +                                     return entry->key;
> > +                     }
> > +             case KEY_QUIT:
> > +                     /* Quit by choosing the last entry */
> > +                     entry = list_last_entry(&efi_menu->list, struct eficonfig_entry, list);
> > +                     return entry->key;
> > +             default:
> > +                     break;
> > +             }
> > +     }
> > +}
> > +
> > +/**
> > + * eficonfig_destroy() - destroy efimenu
> > + *
> > + * @efi_menu:        pointer to the efimenu structure
> > + * @flag:    flag to free the allocated data
> > + */
> > +static void eficonfig_destroy(struct efimenu *efi_menu, bool flag)
> > +{
> > +     struct list_head *pos, *n;
> > +     struct eficonfig_entry *entry;
> > +
> > +     list_for_each_safe(pos, n, &efi_menu->list) {
> > +             entry = list_entry(pos, struct eficonfig_entry, list);
> > +             free(entry->title);
> > +             if (flag)
> > +                     free(entry->data);
> > +             list_del(&entry->list);
> > +             free(entry);
> > +     }
> > +     free(efi_menu->menu_header);
> > +     free(efi_menu);
> > +}
> > +
> > +/**
> > + * eficonfig_process_quit() - callback function for "Quit" entry
> > + *
> > + * @data:    pointer to the data
> > + * Return:   status code
> > + */
> > +efi_status_t eficonfig_process_quit(void *data)
> > +{
> > +     return EFI_ABORTED;
> > +}
> > +
> > +/**
> > + * append_entry() - append menu item
> > + *
> > + * @efi_menu:        pointer to the efimenu structure
> > + * @title:   pointer to the entry title
> > + * @func:    callback of each entry
> > + * @data:    pointer to the data to be passed to each entry callback
> > + * Return:   status code
> > + */
> > +static efi_status_t append_entry(struct efimenu *efi_menu,
> > +                              char *title, eficonfig_entry_func func, void *data)
> > +{
> > +     struct eficonfig_entry *entry;
> > +
> > +     if (efi_menu->count >= EFICONFIG_ENTRY_NUM_MAX)
> > +             return EFI_OUT_OF_RESOURCES;
> > +
> > +     entry = calloc(1, sizeof(struct eficonfig_entry));
> > +     if (!entry)
> > +             return EFI_OUT_OF_RESOURCES;
> > +
> > +     entry->title = title;
> > +     sprintf(entry->key, "%d", efi_menu->count);
> > +     entry->efi_menu = efi_menu;
> > +     entry->func = func;
> > +     entry->data = data;
> > +     entry->num = efi_menu->count++;
> > +     list_add_tail(&entry->list, &efi_menu->list);
> > +
> > +     return EFI_SUCCESS;
> > +}
> > +
> > +/**
> > + * append_quit_entry() - append quit entry
> > + *
> > + * @efi_menu:        pointer to the efimenu structure
> > + * Return:   status code
> > + */
> > +static efi_status_t append_quit_entry(struct efimenu *efi_menu)
> > +{
> > +     char *title;
> > +     efi_status_t ret;
> > +
> > +     title = strdup("Quit");
> > +     if (!title)
> > +             return EFI_OUT_OF_RESOURCES;
> > +
> > +     ret = append_entry(efi_menu, title, eficonfig_process_quit, NULL);
> > +     if (ret != EFI_SUCCESS)
> > +             free(title);
> > +
> > +     return ret;
> > +}
> > +
> > +/**
> > + * eficonfig_create_fixed_menu() - create fixed entry menu structure
> > + *
> > + * @items:   pointer to the menu entry item
> > + * @count:   the number of menu entry
> > + * Return:   pointer to the efimenu structure
> > + */
> > +void *eficonfig_create_fixed_menu(const struct eficonfig_item *items, int count)
> > +{
> > +     u32 i;
> > +     char *title;
> > +     efi_status_t ret;
> > +     struct efimenu *efi_menu;
> > +     const struct eficonfig_item *iter = items;
> > +
> > +     efi_menu = calloc(1, sizeof(struct efimenu));
> > +     if (!efi_menu)
> > +             return NULL;
> > +
> > +     INIT_LIST_HEAD(&efi_menu->list);
> > +     for (i = 0; i < count; i++, iter++) {
> > +             title = strdup(iter->title);
> > +             if (!title)
> > +                     goto out;
> > +
> > +             ret = append_entry(efi_menu, title, iter->func, iter->data);
> > +             if (ret != EFI_SUCCESS) {
> > +                     free(title);
> > +                     goto out;
> > +             }
> > +     }
> > +
> > +     return efi_menu;
> > +out:
> > +     eficonfig_destroy(efi_menu, false);
> > +
> > +     return NULL;
> > +}
> > +
> > +/**
> > + * eficonfig_process_common() - main handler for UEFI menu
> > + *
> > + * Construct the structures required to show the menu, then handle
> > + * the user input interacting with u-boot menu functions.
> > + *
> > + * @efi_menu:                pointer to the efimenu structure
> > + * @menu_header:     pointer to the menu header string
> > + * Return:           status code
> > + */
> > +efi_status_t eficonfig_process_common(struct efimenu *efi_menu, char *menu_header)
> > +{
> > +     efi_status_t ret;
> > +     struct menu *menu;
> > +     void *choice = NULL;
> > +     struct list_head *pos, *n;
> > +     struct eficonfig_entry *entry;
> > +
> > +     if (efi_menu->count > EFICONFIG_ENTRY_NUM_MAX)
> > +             return EFI_OUT_OF_RESOURCES;
> > +
> > +     efi_menu->delay = -1;
> > +     efi_menu->active = 0;
> > +
> > +     if (menu_header) {
> > +             efi_menu->menu_header = strdup(menu_header);
> > +             if (!efi_menu->menu_header) {
> > +                     ret = EFI_OUT_OF_RESOURCES;
> > +                     goto out;
> > +             }
> > +     }
> > +
> > +     menu = menu_create(NULL, 0, 1, eficonfig_display_statusline,
> > +                        eficonfig_print_entry, eficonfig_choice_entry,
> > +                        efi_menu);
> > +     if (!menu) {
> > +             ret = EFI_INVALID_PARAMETER;
> > +             goto out;
> > +     }
> > +
> > +     list_for_each_safe(pos, n, &efi_menu->list) {
> > +             entry = list_entry(pos, struct eficonfig_entry, list);
> > +             if (!menu_item_add(menu, entry->key, entry)) {
> > +                     ret = EFI_INVALID_PARAMETER;
> > +                     goto out;
> > +             }
> > +     }
> > +
> > +     entry = list_first_entry_or_null(&efi_menu->list, struct eficonfig_entry, list);
> > +     if (entry)
> > +             menu_default_set(menu, entry->key);
> > +
> > +     printf(ANSI_CURSOR_HIDE
> > +            ANSI_CLEAR_CONSOLE
> > +            ANSI_CURSOR_POSITION, 1, 1);
> > +
> > +     if (menu_get_choice(menu, &choice)) {
> > +             entry = choice;
> > +             if (entry->func)
> > +                     ret = entry->func(entry->data);
> > +     }
> > +out:
> > +     menu_destroy(menu);
> > +
> > +     printf(ANSI_CLEAR_CONSOLE
> > +            ANSI_CURSOR_POSITION
> > +            ANSI_CURSOR_SHOW, 1, 1);
> > +
> > +     return ret;
> > +}
> > +
> > +/**
> > + * eficonfig_volume_selected() - handler of volume selection
> > + *
> > + * @data:    pointer to the data of selected entry
> > + * Return:   status code
> > + */
> > +static efi_status_t eficonfig_volume_selected(void *data)
> > +{
> > +     struct eficonfig_volume_entry_data *info = data;
> > +
> > +     if (info) {
> > +             info->file_info->current_volume = info->v;
> > +             info->file_info->dp_volume = info->dp;
> > +     }
> > +
> > +     return EFI_SUCCESS;
> > +}
> > +
> > +/**
> > + * create_selected_device_path() - create device path
> > + *
> > + * @file_info:       pointer to the selected file information
> > + * Return:
> > + * device path or NULL. Caller must free the returned value
> > + */
> > +static
> > +struct efi_device_path *create_selected_device_path(struct eficonfig_select_file_info *file_info)
> > +{
> > +     char *p;
> > +     void *buf;
> > +     efi_uintn_t fp_size;
> > +     struct efi_device_path *dp;
> > +     struct efi_device_path_file_path *fp;
> > +
> > +     fp_size = sizeof(struct efi_device_path) +
> > +               ((u16_strlen(file_info->current_path) + 1) * sizeof(u16));
> > +     buf = calloc(1, fp_size + sizeof(END));
> > +     if (!buf)
> > +             return NULL;
> > +
> > +     fp = buf;
> > +     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, file_info->current_path);
> > +
> > +     p = buf;
> > +     p += fp_size;
> > +     *((struct efi_device_path *)p) = END;
> > +
> > +     dp = efi_dp_append(file_info->dp_volume, (struct efi_device_path *)buf);
> > +     free(buf);
> > +
> > +     return dp;
> > +}
> > +
> > +/**
> > + * eficonfig_file_selected() - handler of file selection
> > + *
> > + * @data:    pointer to the data of selected entry
> > + * Return:   status code
> > + */
> > +static efi_status_t eficonfig_file_selected(void *data)
> > +{
> > +     struct eficonfig_file_entry_data *info = data;
> > +
> > +     if (!info)
> > +             return EFI_INVALID_PARAMETER;
> > +
> > +     if (u16_strcmp(info->file_name, u".") == 0 &&
> > +         u16_strlen(info->file_name) == 1) {
> > +             /* stay current path */
> > +     } else if (u16_strcmp(info->file_name, u"..") == 0 &&
> > +                u16_strlen(info->file_name) == 2) {
> > +             struct eficonfig_filepath_info *iter;
> > +             struct list_head *pos, *n;
> > +             int is_last;
> > +
> > +             memset(info->file_info->current_path, 0, EFICONFIG_FILE_PATH_BUF_SIZE);
> > +             list_for_each_safe(pos, n, &info->file_info->filepath_list) {
> > +                     iter = list_entry(pos, struct eficonfig_filepath_info, list);
> > +
> > +                     is_last = list_is_last(&iter->list, &info->file_info->filepath_list);
> > +                     if (is_last) {
> > +                             list_del(&iter->list);
> > +                             free(iter->name);
> > +                             free(iter);
> > +                             break;
> > +                     }
> > +                     u16_strlcat(info->file_info->current_path, iter->name,
> > +                                 EFICONFIG_FILE_PATH_MAX);
> > +                     u16_strlcat(info->file_info->current_path, u"\\",
> > +                                 EFICONFIG_FILE_PATH_MAX);
> > +             }
> > +     } else {
> > +             size_t new_len;
> > +             struct eficonfig_filepath_info *filepath;
> > +
> > +             new_len = u16_strlen(info->file_info->current_path) +
> > +                                  u16_strlen(info->file_name);
> > +             if (new_len >= EFICONFIG_FILE_PATH_MAX) {
> > +                     eficonfig_print_msg("File path is too long!");
> > +                     return EFI_INVALID_PARAMETER;
> > +             }
> > +             u16_strlcat(info->file_info->current_path, info->file_name,
> > +                         EFICONFIG_FILE_PATH_MAX);
> > +
> > +             filepath = calloc(1, sizeof(struct eficonfig_filepath_info));
> > +             if (!filepath)
> > +                     return EFI_OUT_OF_RESOURCES;
> > +
> > +             filepath->name = u16_strdup(info->file_name);
> > +             if (!filepath->name) {
> > +                     free(filepath);
> > +                     return EFI_OUT_OF_RESOURCES;
> > +             }
> > +             list_add_tail(&filepath->list, &info->file_info->filepath_list);
> > +
> > +             if (info->is_directory) {
> > +                     /*
> > +                      * Remainig buffer should have enough space to contain u"\\" and
> > +                      * at least one character for file name
> > +                      */
> > +                     if (new_len + 2 >= EFICONFIG_FILE_PATH_MAX) {
> > +                             eficonfig_print_msg("Directory path is too long!");
> > +                             return EFI_INVALID_PARAMETER;
> > +                     }
> > +                     u16_strlcat(info->file_info->current_path, u"\\",
> > +                                 EFICONFIG_FILE_PATH_MAX);
> > +             } else {
> > +                     info->file_info->file_selected = true;
> > +             }
> > +     }
> > +     return EFI_SUCCESS;
> > +}
> > +
> > +/**
> > + * eficonfig_select_volume() - construct the volume selection menu
> > + *
> > + * @file_info:       pointer to the file selection structure
> > + * Return:   status code
> > + */
> > +static efi_status_t eficonfig_select_volume(struct eficonfig_select_file_info *file_info)
> > +{
> > +     u32 i;
> > +     efi_status_t ret;
> > +     efi_uintn_t count;
> > +     struct efimenu *efi_menu;
> > +     struct efi_handler *handler;
> > +     struct efi_device_path *device_path;
> > +     efi_handle_t *volume_handles = NULL;
> > +     struct efi_simple_file_system_protocol *v;
> > +
> > +     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) {
> > +             eficonfig_print_msg("No block device found!");
> > +             return ret;
> > +     }
> > +
> > +     efi_menu = calloc(1, sizeof(struct efimenu));
> > +     if (!efi_menu)
> > +             return EFI_OUT_OF_RESOURCES;
> > +
> > +     INIT_LIST_HEAD(&efi_menu->list);
> > +     for (i = 0; i < count; i++) {
> > +             char *devname;
> > +             struct efi_block_io *block_io;
> > +             struct eficonfig_volume_entry_data *info;
> > +
> > +             if (efi_menu->count >= EFICONFIG_ENTRY_NUM_MAX - 1)
> > +                     break;
> > +
> > +             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 eficonfig_volume_entry_data));
> > +             if (!info) {
> > +                     ret = EFI_OUT_OF_RESOURCES;
> > +                     goto out;
> > +             }
> > +
> > +             devname = calloc(1, BOOTMENU_DEVICE_NAME_MAX);
> > +             if (!devname) {
> > +                     free(info);
> > +                     ret = EFI_OUT_OF_RESOURCES;
> > +                     goto out;
> > +             }
> > +             ret = efi_disk_get_device_name(volume_handles[i], devname,
> > +                                            BOOTMENU_DEVICE_NAME_MAX);
> > +             if (ret != EFI_SUCCESS) {
> > +                     free(info);
> > +                     goto out;
> > +             }
> > +
> > +             info->v = v;
> > +             info->dp = device_path;
> > +             info->file_info = file_info;
> > +             ret = append_entry(efi_menu, devname, eficonfig_volume_selected, info);
> > +             if (ret != EFI_SUCCESS) {
> > +                     free(info);
> > +                     goto out;
> > +             }
> > +     }
>
> Something is broken in the logic of the volume editor.
>
> I cannot select 'Save' without choosing a file.

It is intended.
I implement *file selection* menu.
 (Anyway, the segmentation fault reported in the previous email must be fixed.)

> You should be able to add a boot option for:
>
> - a partition

'a partition' boot option is automatically created with the patch
"[PATCH v11 6/9] bootmenu: add removable media entries"[1].
So it should be enough.

> - a block device

'a block device' boot option is not created in my patch series.
But the following two commands generate the same device path.

=> efidebug boot add -b 0005 test1 virtio 0 ''
=> efidebug boot add -b 0006 test2 virtio 0:1 ''

=> efidebug boot dump
Boot0005:
attributes: A-- (0x00000001)
  label: test1
  file_path: /HD(1,GPT,c2475a57-2735-4744-9f01-67fefa1d06ae,0x800,0x100000)
  data:
Boot0006:
attributes: A-- (0x00000001)
  label: test2
  file_path: /HD(1,GPT,c2475a57-2735-4744-9f01-67fefa1d06ae,0x800,0x100000)
  data:

So 'a block device' is also covered by the patch "[PATCH v11 6/9]
bootmenu: add removable media entries"[1].

> - a file

'a file' is implemented in this patch.

[1] https://lore.kernel.org/u-boot/20220817093614.32266-7-masahisa.kojima@linaro.org/T/#u

Thanks,
Masahisa Kojima

>
> > +
> > +     ret = append_quit_entry(efi_menu);
> > +     if (ret != EFI_SUCCESS)
> > +             goto out;
> > +
> > +     ret = eficonfig_process_common(efi_menu, "  ** Select Volume **");
> > +out:
> > +     efi_free_pool(volume_handles);
> > +     eficonfig_destroy(efi_menu, true);
> > +
> > +     return ret;
> > +}
> > +
> > +/**
> > + * eficonfig_select_file() - construct the file selection menu
> > + *
> > + * @file_info:       pointer to the file selection structure
> > + * @root:    pointer to the file handle
> > + * Return:   status code
> > + */
> > +static efi_status_t eficonfig_select_file(struct eficonfig_select_file_info *file_info,
> > +                                       struct efi_file_handle *root)
> > +{
> > +     efi_uintn_t len;
> > +     efi_status_t ret;
> > +     struct efimenu *efi_menu;
> > +     struct efi_file_handle *f;
> > +     struct efi_file_info *buf;
> > +     struct list_head *pos, *n;
> > +
> > +     buf = calloc(1, sizeof(struct efi_file_info) + EFICONFIG_FILE_PATH_BUF_SIZE);
> > +     if (!buf)
> > +             return EFI_OUT_OF_RESOURCES;
> > +
> > +     while (!file_info->file_selected) {
> > +             efi_menu = calloc(1, sizeof(struct efimenu));
> > +             if (!efi_menu) {
> > +                     ret = EFI_OUT_OF_RESOURCES;
> > +                     goto out;
> > +             }
> > +             INIT_LIST_HEAD(&efi_menu->list);
> > +
> > +             ret = efi_file_open_int(root, &f, file_info->current_path, EFI_FILE_MODE_READ, 0);
> > +             if (ret != EFI_SUCCESS) {
> > +                     eficonfig_print_msg("Reading volume failed!");
> > +                     ret = EFI_ABORTED;
> > +                     goto out;
> > +             }
> > +
> > +             /* Read directory and construct menu structure */
> > +             for (;;) {
> > +                     char *name, *p;
> > +                     int name_len;
> > +                     struct eficonfig_file_entry_data *info;
> > +
> > +                     if (efi_menu->count >= EFICONFIG_ENTRY_NUM_MAX - 1)
> > +                             break;
> > +
> > +                     len = sizeof(struct efi_file_info) + EFICONFIG_FILE_PATH_BUF_SIZE;
> > +                     ret = efi_file_read_int(f, &len, buf);
> > +                     if (ret != EFI_SUCCESS || len == 0)
> > +                             break;
> > +
> > +                     info = calloc(1, sizeof(struct eficonfig_file_entry_data));
> > +                     if (!info) {
> > +                             ret = EFI_OUT_OF_RESOURCES;
> > +                             goto err;
> > +                     }
> > +
> > +                     if (buf->attribute & EFI_FILE_DIRECTORY) {
> > +                             /* append u'/' at the end of directory name */
> > +                             name_len = utf16_utf8_strlen(buf->file_name) + 2;
> > +
> > +                             /* filter out u'.' */
> > +                             if (name_len == 3 && buf->file_name[0] == u'.') {
> > +                                     free(info);
> > +                                     continue;
> > +                             }
> > +
> > +                             name = calloc(1, name_len);
> > +                             if (!name) {
> > +                                     free(info);
> > +                                     ret = EFI_OUT_OF_RESOURCES;
> > +                                     goto err;
> > +                             }
> > +                             p = name;
> > +                             utf16_utf8_strcpy(&p, buf->file_name);
> > +                             name[u16_strlen(buf->file_name)] = u'/';
> > +
> > +                             info->is_directory = true;
> > +                     } else {
> > +                             name_len = utf16_utf8_strlen(buf->file_name) + 1;
> > +                             name = calloc(1, name_len);
> > +                             if (!name) {
> > +                                     free(info);
> > +                                     ret = EFI_OUT_OF_RESOURCES;
> > +                                     goto err;
> > +                             }
> > +                             p = name;
> > +                             utf16_utf8_strcpy(&p, buf->file_name);
> > +                     }
> > +
> > +                     info->file_name = u16_strdup(buf->file_name);
> > +                     if (!info->file_name) {
> > +                             free(info);
> > +                             free(name);
> > +                             ret = EFI_OUT_OF_RESOURCES;
> > +                             goto err;
> > +                     }
> > +
> > +                     info->file_info = file_info;
> > +                     ret = append_entry(efi_menu, name, eficonfig_file_selected, info);
> > +                     if (ret != EFI_SUCCESS) {
> > +                             free(info);
> > +                             free(name);
> > +                             goto err;
> > +                     }
> > +             }
> > +
> > +             ret = append_quit_entry(efi_menu);
> > +             if (ret != EFI_SUCCESS)
> > +                     goto err;
> > +
> > +             ret = eficonfig_process_common(efi_menu, "  ** Select File **");
> > +err:
> > +             efi_file_close_int(f);
> > +             list_for_each_safe(pos, n, &efi_menu->list) {
> > +                     struct eficonfig_entry *entry;
> > +
> > +                     entry = list_entry(pos, struct eficonfig_entry, list);
> > +                     /* skip "Quit" */
> > +                     if (list_is_last(&entry->list, &efi_menu->list))
> > +                             break;
> > +
> > +                     free(((struct eficonfig_file_entry_data *)(entry->data))->file_name);
> > +             }
> > +             eficonfig_destroy(efi_menu, true);
> > +             if (ret != EFI_SUCCESS)
> > +                     break;
> > +     }
> > +
> > +out:
> > +     free(buf);
> > +     return ret;
> > +}
> > +
> > +/**
> > + * handle_user_input() - handle user input
> > + *
> > + * @buf:     pointer to the buffer
> > + * @buf_size:        size of the buffer
> > + * @cursol_col:      cursol column for user input
> > + * @msg:     pointer to the string to display
> > + * Return:   status code
> > + */
> > +static efi_status_t handle_user_input(u16 *buf, int buf_size,
> > +                                   int cursol_col, char *msg)
> > +{
> > +     u16 *tmp;
> > +     efi_status_t ret;
> > +
> > +     printf(ANSI_CLEAR_CONSOLE
> > +            ANSI_CURSOR_POSITION
> > +            "%s"
> > +            ANSI_CURSOR_POSITION
> > +            "  Press ENTER to complete, ESC/CTRL+C to quit",
> > +            0, 1, msg, 8, 1);
> > +
> > +     /* tmp is used to accept user cancel */
> > +     tmp = calloc(1, buf_size * sizeof(u16));
> > +     if (!tmp)
> > +             return EFI_OUT_OF_RESOURCES;
> > +
> > +     ret = efi_console_get_u16_string(cin, tmp, buf_size, NULL, 4, cursol_col);
> > +     if (ret == EFI_SUCCESS)
> > +             u16_strcpy(buf, tmp);
> > +
> > +     free(tmp);
> > +
> > +     /* to stay the parent menu */
> > +     ret = (ret == EFI_ABORTED) ? EFI_NOT_READY : ret;
> > +
> > +     return ret;
> > +}
> > +
> > +/**
> > + * eficonfig_boot_add_enter_description() - handle user input for description
> > + *
> > + * @data:    pointer to the internal boot option structure
> > + * Return:   status code
> > + */
> > +static efi_status_t eficonfig_boot_add_enter_description(void *data)
> > +{
> > +     struct eficonfig_boot_option *bo = data;
> > +
> > +     return handle_user_input(bo->description, EFICONFIG_DESCRIPTION_MAX, 22,
> > +                              "\n  ** Edit Description **\n"
> > +                              "\n"
> > +                              "  enter description: ");
> > +}
> > +
> > +/**
> > + * eficonfig_boot_add_optional_data() - handle user input for optional data
> > + *
> > + * @data:    pointer to the internal boot option structure
> > + * Return:   status code
> > + */
> > +static efi_status_t eficonfig_boot_add_optional_data(void *data)
> > +{
> > +     struct eficonfig_boot_option *bo = data;
> > +
> > +     return handle_user_input(bo->optional_data, EFICONFIG_OPTIONAL_DATA_MAX, 24,
> > +                              "\n  ** Edit Optional Data **\n"
> > +                              "\n"
> > +                              "  enter optional data:");
> > +}
> > +
> > +/**
> > + * eficonfig_boot_edit_save() - handler to save the boot option
> > + *
> > + * @data:    pointer to the internal boot option structure
> > + * Return:   status code
> > + */
> > +static efi_status_t eficonfig_boot_edit_save(void *data)
> > +{
> > +     struct eficonfig_boot_option *bo = data;
> > +
> > +     if (u16_strlen(bo->description) == 0) {
> > +             eficonfig_print_msg("Boot Description is empty!");
> > +             bo->edit_completed = false;
> > +             return EFI_NOT_READY;
> > +     }
> > +     if (u16_strlen(bo->file_info.current_path) == 0) {
> > +             eficonfig_print_msg("File is not selected!");
> > +             bo->edit_completed = false;
> > +             return EFI_NOT_READY;
>
> This string is wrong. There is no requirement for the device path to
> contain a file in a boot option. So you should say 'No device path
> selected'.
>
> Best regards
>
> Heinrich
>
>
> > +     }
> > +
> > +     bo->edit_completed = true;
> > +
> > +     return EFI_SUCCESS;
> > +}
> > +
> > +/**
> > + * eficonfig_select_file_handler() - handle user file selection
> > + *
> > + * @data:    pointer to the data
> > + * Return:   status code
> > + */
> > +efi_status_t eficonfig_select_file_handler(void *data)
> > +{
> > +     size_t len;
> > +     efi_status_t ret;
> > +     struct list_head *pos, *n;
> > +     struct efi_file_handle *root;
> > +     struct eficonfig_filepath_info *item;
> > +     struct eficonfig_select_file_info *file_info = data;
> > +     struct eficonfig_select_file_info *tmp = NULL;
> > +
> > +     tmp = calloc(1, sizeof(struct eficonfig_select_file_info));
> > +     if (!tmp)
> > +             return EFI_OUT_OF_RESOURCES;
> > +
> > +     tmp->current_path = calloc(1, EFICONFIG_FILE_PATH_BUF_SIZE);
> > +     if (!tmp->current_path) {
> > +             free(tmp);
> > +             return EFI_OUT_OF_RESOURCES;
> > +     }
> > +     INIT_LIST_HEAD(&tmp->filepath_list);
> > +
> > +     while (!tmp->file_selected) {
> > +             tmp->current_volume = NULL;
> > +             memset(tmp->current_path, 0, EFICONFIG_FILE_PATH_BUF_SIZE);
> > +
> > +             ret = eficonfig_select_volume(tmp);
> > +             if (ret != EFI_SUCCESS)
> > +                     goto out;
> > +
> > +             if (!tmp->current_volume)
> > +                     return EFI_INVALID_PARAMETER;
> > +
> > +             ret = efi_open_volume_int(tmp->current_volume, &root);
> > +             if (ret != EFI_SUCCESS)
> > +                     goto out;
> > +
> > +             ret = eficonfig_select_file(tmp, root);
> > +             if (ret == EFI_ABORTED)
> > +                     continue;
> > +             if (ret != EFI_SUCCESS)
> > +                     goto out;
> > +     }
> > +
> > +out:
> > +     if (ret == EFI_SUCCESS) {
> > +             len = u16_strlen(tmp->current_path);
> > +             len = (len >= EFICONFIG_FILE_PATH_MAX) ? (EFICONFIG_FILE_PATH_MAX - 1) : len;
> > +             memcpy(file_info->current_path, tmp->current_path, len * sizeof(u16));
> > +             file_info->current_path[len] = u'\0';
> > +             file_info->current_volume = tmp->current_volume;
> > +             file_info->dp_volume = tmp->dp_volume;
> > +     }
> > +
> > +     list_for_each_safe(pos, n, &tmp->filepath_list) {
> > +             item = list_entry(pos, struct eficonfig_filepath_info, list);
> > +             list_del(&item->list);
> > +             free(item->name);
> > +             free(item);
> > +     }
> > +     free(tmp->current_path);
> > +     free(tmp);
> > +
> > +     /* to stay the parent menu */
> > +     ret = (ret == EFI_ABORTED) ? EFI_NOT_READY : ret;
> > +
> > +     return ret;
> > +}
> > +
> > +/**
> > + * eficonfig_get_unused_bootoption() - get unused "Boot####" index
> > + *
> > + * @buf:     pointer to the buffer to store boot option variable name
> > + * @buf_size:        buffer size
> > + * @index:   pointer to store the index in the BootOrder variable
> > + * Return:   status code
> > + */
> > +efi_status_t eficonfig_get_unused_bootoption(u16 *buf, efi_uintn_t buf_size,
> > +                                          unsigned int *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;
> > +}
> > +
> > +/**
> > + * eficonfig_set_boot_option() - set boot option
> > + *
> > + * @varname:         pointer to variable name
> > + * @dp:                      pointer to device path
> > + * @label:           pointer to label string
> > + * @optional_data:   pointer to optional data
> > + * Return:           status code
> > + */
> > +static efi_status_t eficonfig_set_boot_option(u16 *varname, struct efi_device_path *dp,
> > +                                           efi_uintn_t dp_size, 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 = dp_size;
> > +     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(varname, &efi_global_variable_guid,
> > +                                EFI_VARIABLE_NON_VOLATILE |
> > +                                EFI_VARIABLE_BOOTSERVICE_ACCESS |
> > +                                EFI_VARIABLE_RUNTIME_ACCESS,
> > +                                size, p, false);
> > +     free(p);
> > +
> > +     return ret;
> > +}
> > +
> > +/**
> > + * eficonfig_append_bootorder() - append new boot option in BootOrder variable
> > + *
> > + * @index:   "Boot####" index to append to BootOrder variable
> > + * Return:   status code
> > + */
> > +efi_status_t eficonfig_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;
> > +}
> > +
> > +/**
> > + * create_boot_option_entry() - create boot option entry
> > + *
> > + * @efi_menu:        pointer to the efimenu structure
> > + * @title:   pointer to the entry title
> > + * @val:     pointer to boot option label
> > + * @func:    callback of each entry
> > + * @data:    pointer to the data to be passed to each entry callback
> > + * Return:   status code
> > + */
> > +static efi_status_t create_boot_option_entry(struct efimenu *efi_menu, char *title, u16 *val,
> > +                                          eficonfig_entry_func func, void *data)
> > +{
> > +     u32 len;
> > +     char *p, *buf;
> > +
> > +     len = strlen(title) + 1;
> > +     if (val)
> > +             len += utf16_utf8_strlen(val);
> > +     buf = calloc(1, len);
> > +     if (!buf)
> > +             return EFI_OUT_OF_RESOURCES;
> > +
> > +     strcpy(buf, title);
> > +     if (val) {
> > +             p = buf + strlen(title);
> > +             utf16_utf8_strcpy(&p, val);
> > +     }
> > +
> > +     return append_entry(efi_menu, buf, func, data);
> > +}
> > +
> > +/**
> > + * prepare_file_selection_entry() - prepare file selection entry
> > + *
> > + * @efi_menu:        pointer to the efimenu structure
> > + * @title:   pointer to the title string
> > + * @file_info:       pointer to the file info
> > + * Return:   status code
> > + */
> > +static efi_status_t prepare_file_selection_entry(struct efimenu *efi_menu, char *title,
> > +                                              struct eficonfig_select_file_info *file_info)
> > +{
> > +     u32 len;
> > +     efi_status_t ret;
> > +     u16 *file_name, *p;
> > +     efi_handle_t handle;
> > +     char devname[BOOTMENU_DEVICE_NAME_MAX] = {0};
> > +
> > +     /* get the device name only when the user already selected the file path */
> > +     handle = efi_dp_find_obj(file_info->dp_volume, NULL, NULL);
> > +     if (handle) {
> > +             ret = efi_disk_get_device_name(handle, devname, BOOTMENU_DEVICE_NAME_MAX);
> > +             if (ret != EFI_SUCCESS)
> > +                     return ret;
> > +     }
> > +
> > +     /* append u'/' to devname, it is just for display purpose. */
> > +     if (file_info->current_path[0] != u'\0' && file_info->current_path[0] != u'/')
> > +             strlcat(devname, "/", BOOTMENU_DEVICE_NAME_MAX);
> > +
> > +     len = strlen(devname);
> > +     len += utf16_utf8_strlen(file_info->current_path) + 1;
> > +     file_name = calloc(1, len * sizeof(u16));
> > +     if (!file_name)
> > +             return ret;
> > +
> > +     p = file_name;
> > +     utf8_utf16_strcpy(&p, devname);
> > +     u16_strlcat(file_name, file_info->current_path, len);
> > +     ret = create_boot_option_entry(efi_menu, title, file_name,
> > +                                    eficonfig_select_file_handler, file_info);
> > +     free(file_name);
> > +     return ret;
> > +}
> > +
> > +/**
> > + * eficonfig_show_boot_option() - prepare menu entry for editing boot option
> > + *
> > + * Construct the structures to create edit boot option menu
> > + *
> > + * @bo:              pointer to the boot option
> > + * @header_str:      pointer to the header string
> > + * Return:   status code
> > + */
> > +static efi_status_t eficonfig_show_boot_option(struct eficonfig_boot_option *bo,
> > +                                            char *header_str)
> > +{
> > +     struct efimenu *efi_menu;
> > +     efi_status_t ret;
> > +
> > +     efi_menu = calloc(1, sizeof(struct efimenu));
> > +     if (!efi_menu)
> > +             return EFI_OUT_OF_RESOURCES;
> > +
> > +     INIT_LIST_HEAD(&efi_menu->list);
> > +
> > +     ret = create_boot_option_entry(efi_menu, "Description: ", bo->description,
> > +                                    eficonfig_boot_add_enter_description, bo);
> > +     if (ret != EFI_SUCCESS)
> > +             goto out;
> > +
> > +     ret = prepare_file_selection_entry(efi_menu, "File: ", &bo->file_info);
> > +     if (ret != EFI_SUCCESS)
> > +             goto out;
> > +
> > +     ret = prepare_file_selection_entry(efi_menu, "Initrd File: ", &bo->initrd_info);
> > +     if (ret != EFI_SUCCESS)
> > +             goto out;
> > +
> > +     ret = create_boot_option_entry(efi_menu, "Optional Data: ", bo->optional_data,
> > +                                    eficonfig_boot_add_optional_data, bo);
> > +     if (ret != EFI_SUCCESS)
> > +             goto out;
> > +
> > +     ret = create_boot_option_entry(efi_menu, "Save", NULL,
> > +                                    eficonfig_boot_edit_save, bo);
> > +     if (ret != EFI_SUCCESS)
> > +             goto out;
> > +
> > +     ret = create_boot_option_entry(efi_menu, "Quit", NULL,
> > +                                    eficonfig_process_quit, bo);
> > +     if (ret != EFI_SUCCESS)
> > +             goto out;
> > +
> > +     ret = eficonfig_process_common(efi_menu, header_str);
> > +out:
> > +     eficonfig_destroy(efi_menu, false);
> > +
> > +     return ret;
> > +}
> > +
> > +/**
> > + * fill_file_info() - fill the file info from efi_device_path structure
> > + *
> > + * @dp:              pointer to the device path
> > + * @file_info:       pointer to the file info structure
> > + * @device_dp:       pointer to the volume device path
> > + */
> > +static void fill_file_info(struct efi_device_path *dp,
> > +                        struct eficonfig_select_file_info *file_info,
> > +                        struct efi_device_path *device_dp)
> > +{
> > +     u16 *file_str, *p;
> > +     struct efi_device_path *file_dp = NULL;
> > +
> > +     efi_dp_split_file_path(dp, &device_dp, &file_dp);
> > +     file_info->dp_volume = device_dp;
> > +     file_str = efi_dp_str(file_dp);
> > +     /*
> > +      * efi_convert_device_path_to_text() automatically adds u'/' at the
> > +      * beginning of file name, remove u'/' before copying to current_path
> > +      */
> > +     p = file_str;
> > +     if (p[0] == u'/')
> > +             p++;
> > +
> > +     u16_strcpy(file_info->current_path, p);
> > +     efi_free_pool(file_dp);
> > +     efi_free_pool(file_str);
> > +}
> > +
> > +/**
> > + * eficonfig_edit_boot_option() - prepare boot option structure for editing
> > + *
> > + * Construct the boot option structure and copy the existing value
> > + *
> > + * @varname:         pointer to the UEFI variable name
> > + * @bo:                      pointer to the boot option
> > + * @load_option:     pointer to the load option
> > + * @load_option_size:        size of the load option
> > + * @header_str:              pointer to the header string
> > + * Return    :       status code
> > + */
> > +static efi_status_t eficonfig_edit_boot_option(u16 *varname, struct eficonfig_boot_option *bo,
> > +                                            void *load_option, efi_uintn_t load_option_size,
> > +                                            char *header_str)
> > +{
> > +     size_t len;
> > +     efi_status_t ret;
> > +     char *tmp = NULL, *p;
> > +     struct efi_load_option lo = {0};
> > +     efi_uintn_t final_dp_size;
> > +     struct efi_device_path *dp = NULL;
> > +     efi_uintn_t size = load_option_size;
> > +     struct efi_device_path *final_dp = NULL;
> > +     struct efi_device_path *device_dp = NULL;
> > +     struct efi_device_path *initrd_dp = NULL;
> > +     struct efi_device_path *initrd_device_dp = NULL;
> > +
> > +     const struct efi_initrd_dp id_dp = {
> > +             .vendor = {
> > +                     {
> > +                     DEVICE_PATH_TYPE_MEDIA_DEVICE,
> > +                     DEVICE_PATH_SUB_TYPE_VENDOR_PATH,
> > +                     sizeof(id_dp.vendor),
> > +                     },
> > +                     EFI_INITRD_MEDIA_GUID,
> > +             },
> > +             .end = {
> > +                     DEVICE_PATH_TYPE_END,
> > +                     DEVICE_PATH_SUB_TYPE_END,
> > +                     sizeof(id_dp.end),
> > +             }
> > +     };
> > +
> > +     bo->file_info.current_path = calloc(1, EFICONFIG_FILE_PATH_BUF_SIZE);
> > +     if (!bo->file_info.current_path) {
> > +             ret =  EFI_OUT_OF_RESOURCES;
> > +             goto out;
> > +     }
> > +
> > +     bo->initrd_info.current_path = calloc(1, EFICONFIG_FILE_PATH_BUF_SIZE);
> > +     if (!bo->file_info.current_path) {
> > +             ret =  EFI_OUT_OF_RESOURCES;
> > +             goto out;
> > +     }
> > +
> > +     bo->description = calloc(1, EFICONFIG_DESCRIPTION_MAX * sizeof(u16));
> > +     if (!bo->description) {
> > +             ret =  EFI_OUT_OF_RESOURCES;
> > +             goto out;
> > +     }
> > +
> > +     bo->optional_data = calloc(1, EFICONFIG_OPTIONAL_DATA_MAX * sizeof(u16));
> > +     if (!bo->optional_data) {
> > +             ret =  EFI_OUT_OF_RESOURCES;
> > +             goto out;
> > +     }
> > +
> > +     /* copy the preset value */
> > +     if (load_option) {
> > +             ret = efi_deserialize_load_option(&lo, load_option, &size);
> > +             if (ret != EFI_SUCCESS)
> > +                     goto out;
> > +
> > +             if (!lo.label || (lo.label && u16_strlen(lo.label) >= EFICONFIG_DESCRIPTION_MAX)) {
> > +                     ret = EFI_INVALID_PARAMETER;
> > +                     goto out;
> > +             }
> > +             u16_strcpy(bo->description, lo.label);
> > +
> > +             /* EFI image file path is a first instance */
> > +             if (lo.file_path)
> > +                     fill_file_info(lo.file_path, &bo->file_info, device_dp);
> > +
> > +             /* Initrd file path(optional) is placed at second instance. */
> > +             initrd_dp = efi_dp_from_lo(&lo, &efi_lf2_initrd_guid);
> > +             if (initrd_dp) {
> > +                     fill_file_info(initrd_dp, &bo->initrd_info, initrd_device_dp);
> > +                     efi_free_pool(initrd_dp);
> > +             }
> > +
> > +             if (size > 0)
> > +                     memcpy(bo->optional_data, lo.optional_data, size);
> > +     }
> > +
> > +     while (1) {
> > +             ret = eficonfig_show_boot_option(bo, header_str);
> > +             if (ret == EFI_SUCCESS && bo->edit_completed)
> > +                     break;
> > +             if (ret == EFI_NOT_READY)
> > +                     continue;
> > +             if (ret != EFI_SUCCESS)
> > +                     goto out;
> > +     }
> > +
> > +     if (bo->initrd_info.dp_volume) {
> > +             dp = create_selected_device_path(&bo->initrd_info);
> > +             if (!dp) {
> > +                     ret = EFI_OUT_OF_RESOURCES;
> > +                     goto out;
> > +             }
> > +             initrd_dp = efi_dp_append((const struct efi_device_path *)&id_dp, dp);
> > +             efi_free_pool(dp);
> > +     }
> > +
> > +     dp = create_selected_device_path(&bo->file_info);
> > +     if (!dp) {
> > +             ret = EFI_OUT_OF_RESOURCES;
> > +             goto out;
> > +     }
> > +     final_dp_size = efi_dp_size(dp) + sizeof(END);
> > +     if (initrd_dp) {
> > +             final_dp = efi_dp_concat(dp, initrd_dp);
> > +             final_dp_size += efi_dp_size(initrd_dp) + sizeof(END);
> > +     } else {
> > +             final_dp = efi_dp_dup(dp);
> > +     }
> > +     efi_free_pool(dp);
> > +
> > +     if (!final_dp)
> > +             goto out;
> > +
> > +     len = utf16_utf8_strlen(bo->optional_data) + 1;
> > +     tmp = calloc(1, len);
> > +     if (!tmp)
> > +             goto out;
> > +     p = tmp;
> > +     utf16_utf8_strncpy(&p, bo->optional_data, u16_strlen(bo->optional_data));
> > +
> > +     ret = eficonfig_set_boot_option(varname, final_dp, final_dp_size, bo->description, tmp);
> > +     if (ret != EFI_SUCCESS)
> > +             goto out;
> > +out:
> > +     free(tmp);
> > +     free(bo->optional_data);
> > +     free(bo->description);
> > +     free(bo->file_info.current_path);
> > +     free(bo->initrd_info.current_path);
> > +     efi_free_pool(device_dp);
> > +     efi_free_pool(initrd_device_dp);
> > +     efi_free_pool(initrd_dp);
> > +     efi_free_pool(final_dp);
> > +
> > +     return ret;
> > +}
> > +
> > +/**
> > + * eficonfig_process_add_boot_option() - handler to add boot option
> > + *
> > + * @data:    pointer to the data for each entry
> > + * Return:   status code
> > + */
> > +static efi_status_t eficonfig_process_add_boot_option(void *data)
> > +{
> > +     u16 varname[9];
> > +     efi_status_t ret;
> > +     struct eficonfig_boot_option *bo = NULL;
> > +
> > +     bo = calloc(1, sizeof(struct eficonfig_boot_option));
> > +     if (!bo)
> > +             return EFI_OUT_OF_RESOURCES;
> > +
> > +     ret = eficonfig_get_unused_bootoption(varname, sizeof(varname), &bo->boot_index);
> > +     if (ret != EFI_SUCCESS)
> > +             return ret;
> > +
> > +     ret = eficonfig_edit_boot_option(varname, bo, NULL, 0,  "  ** Add Boot Option ** ");
> > +     if (ret != EFI_SUCCESS)
> > +             goto out;
> > +
> > +     ret = eficonfig_append_bootorder((u16)bo->boot_index);
> > +     if (ret != EFI_SUCCESS)
> > +             goto out;
> > +
> > +out:
> > +     free(bo);
> > +
> > +     /* to stay the parent menu */
> > +     ret = (ret == EFI_ABORTED) ? EFI_SUCCESS : ret;
> > +
> > +     return ret;
> > +}
> > +
> > +/**
> > + * eficonfig_init() - do required initialization for eficonfig command
> > + *
> > + * Return:   status code
> > + */
> > +static efi_status_t eficonfig_init(void)
> > +{
> > +     efi_status_t ret;
> > +     static bool init;
> > +     struct efi_handler *handler;
> > +
> > +     if (!init) {
> > +             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;
> > +     }
> > +
> > +     init = true;
> > +
> > +     return ret;
> > +}
> > +
> > +static const struct eficonfig_item maintenance_menu_items[] = {
> > +     {"Add Boot Option", eficonfig_process_add_boot_option},
> > +     {"Quit", eficonfig_process_quit},
> > +};
> > +
> > +/**
> > + * do_eficonfig() - execute `eficonfig` command
> > + *
> > + * @cmdtp:   table entry describing command
> > + * @flag:    bitmap indicating how the command was invoked
> > + * @argc:    number of arguments
> > + * @argv:    command line arguments
> > + * Return:   status code
> > + */
> > +static int do_eficonfig(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[])
> > +{
> > +     efi_status_t ret;
> > +     struct efimenu *efi_menu;
> > +
> > +     if (argc > 1)
> > +             return CMD_RET_USAGE;
> > +
> > +     ret = efi_init_obj_list();
> > +     if (ret != EFI_SUCCESS) {
> > +             log_err("Error: Cannot initialize UEFI sub-system, r = %lu\n",
> > +                     ret & ~EFI_ERROR_MASK);
> > +
> > +             return CMD_RET_FAILURE;
> > +     }
> > +
> > +     ret = eficonfig_init();
> > +     if (ret != EFI_SUCCESS)
> > +             return CMD_RET_FAILURE;
> > +
> > +     while (1) {
> > +             efi_menu = eficonfig_create_fixed_menu(maintenance_menu_items,
> > +                                                    ARRAY_SIZE(maintenance_menu_items));
> > +             if (!efi_menu)
> > +                     return CMD_RET_FAILURE;
> > +
> > +             ret = eficonfig_process_common(efi_menu, "  ** UEFI Maintenance Menu **");
> > +             eficonfig_destroy(efi_menu, false);
> > +
> > +             if (ret == EFI_ABORTED)
> > +                     break;
> > +     }
> > +
> > +     return CMD_RET_SUCCESS;
> > +}
> > +
> > +U_BOOT_CMD(
> > +     eficonfig, 1, 0, do_eficonfig,
> > +     "provide menu-driven UEFI variable maintenance interface",
> > +     ""
> > +);
> > diff --git a/include/efi_config.h b/include/efi_config.h
> > new file mode 100644
> > index 0000000000..aaff5c7cc0
> > --- /dev/null
> > +++ b/include/efi_config.h
> > @@ -0,0 +1,90 @@
> > +/* SPDX-License-Identifier: GPL-2.0+ */
> > +/*
> > + *  Menu-driven UEFI Variable maintenance
> > + *
> > + *  Copyright (c) 2022 Masahisa Kojima, Linaro Limited
> > + */
> > +
> > +#ifndef _EFI_CONFIG_H
> > +#define _EFI_CONFIG_H
> > +
> > +#define EFICONFIG_ENTRY_NUM_MAX 99
> > +#define EFICONFIG_FILE_PATH_MAX 512
> > +#define EFICONFIG_FILE_PATH_BUF_SIZE (EFICONFIG_FILE_PATH_MAX * sizeof(u16))
> > +
> > +typedef efi_status_t (*eficonfig_entry_func)(void *data);
> > +
> > +/**
> > + * struct eficonfig_entry - menu entry structure
> > + *
> > + * @num:     menu entry index
> > + * @title:   title of entry
> > + * @key:     unique key
> > + * @efi_menu:        pointer to the menu structure
> > + * @func:    callback function to be called when this entry is selected
> > + * @data:    data to be passed to the callback function
> > + * @list:    list structure
> > + */
> > +struct eficonfig_entry {
> > +     u32 num;
> > +     char *title;
> > +     char key[3];
> > +     struct efimenu *efi_menu;
> > +     eficonfig_entry_func func;
> > +     void *data;
> > +     struct list_head list;
> > +};
> > +
> > +/**
> > + * struct efimenu - efi menu structure
> > + *
> > + * @delay:           delay for autoboot
> > + * @active:          active menu entry index
> > + * @count:           total count of menu entry
> > + * @menu_header:     menu header string
> > + * @list:            menu entry list structure
> > + */
> > +struct efimenu {
> > +     int delay;
> > +     int active;
> > +     int count;
> > +     char *menu_header;
> > +     struct list_head list;
> > +};
> > +
> > +/**
> > + * struct eficonfig_item - structure to construct eficonfig_entry
> > + *
> > + * @title:   title of entry
> > + * @func:    callback function to be called when this entry is selected
> > + * @data:    data to be passed to the callback function
> > + */
> > +struct eficonfig_item {
> > +     char *title;
> > +     eficonfig_entry_func func;
> > +     void *data;
> > +};
> > +
> > +/**
> > + * struct eficonfig_select_file_info - structure to be used for file selection
> > + *
> > + * @current_volume:  pointer to the efi_simple_file_system_protocol
> > + * @dp_volume:               pointer to device path of the selected device
> > + * @current_path:    pointer to the selected file path string
> > + * @filepath_list:   list_head structure for file path list
> > + * @file_selectred:  flag indicates file selecting status
> > + */
> > +struct eficonfig_select_file_info {
> > +     struct efi_simple_file_system_protocol *current_volume;
> > +     struct efi_device_path *dp_volume;
> > +     u16 *current_path;
> > +     struct list_head filepath_list;
> > +     bool file_selected;
> > +};
> > +
> > +void eficonfig_print_msg(char *msg);
> > +efi_status_t eficonfig_process_quit(void *data);
> > +efi_status_t eficonfig_process_common(struct efimenu *efi_menu, char *menu_header);
> > +efi_status_t eficonfig_select_file_handler(void *data);
> > +
> > +#endif
> > diff --git a/include/efi_loader.h b/include/efi_loader.h
> > index b0d6fff67c..49e7d1e613 100644
> > --- a/include/efi_loader.h
> > +++ b/include/efi_loader.h
> > @@ -142,6 +142,11 @@ static inline efi_status_t efi_launch_capsules(void)
> >       EFI_GUID(0x63293792, 0xadf5, 0x9325, \
> >                0xb9, 0x9f, 0x4e, 0x0e, 0x45, 0x5c, 0x1b, 0x1e)
> >
> > +/* GUID for the auto generated boot menu entry */
> > +#define EFICONFIG_AUTO_GENERATED_ENTRY_GUID \
> > +     EFI_GUID(0x38c1acc1, 0x9fc0, 0x41f0, \
> > +              0xb9, 0x01, 0xfa, 0x74, 0xd6, 0xd6, 0xe4, 0xde)
> > +
> >   /* Use internal device tree when starting UEFI application */
> >   #define EFI_FDT_USE_INTERNAL NULL
> >
> > @@ -226,6 +231,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;
> >
> > @@ -249,6 +257,9 @@ extern const struct efi_hii_string_protocol efi_hii_string;
> >
> >   uint16_t *efi_dp_str(struct efi_device_path *dp);
> >
> > +/* GUID for the auto generated boot menu entry */
> > +extern const efi_guid_t efi_guid_bootmenu_auto_generated;
> > +
> >   /* GUID of the U-Boot root node */
> >   extern const efi_guid_t efi_u_boot_guid;
> >   #ifdef CONFIG_SANDBOX
> > @@ -314,6 +325,8 @@ 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 char __efi_runtime_start[], __efi_runtime_stop[];
> >   extern char __efi_runtime_rel_start[], __efi_runtime_rel_stop[];
> > @@ -891,6 +904,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
> >    *
> > @@ -1064,4 +1079,32 @@ 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,
> > +              u16 *buf, efi_uintn_t count, efi_console_filter_func filer_func,
> > +              int row, int col);
> > +
> > +efi_status_t eficonfig_get_unused_bootoption(u16 *buf,
> > +                                          efi_uintn_t buf_size, u32 *index);
> > +efi_status_t eficonfig_append_bootorder(u16 index);
> > +
> > +efi_status_t efi_disk_get_device_name(const efi_handle_t handle, char *buf, int size);
> > +
> >   #endif /* _EFI_LOADER_H */
> > diff --git a/lib/efi_loader/efi_bootmgr.c b/lib/efi_loader/efi_bootmgr.c
> > index 234073ecb7..ede9116b3c 100644
> > --- a/lib/efi_loader/efi_bootmgr.c
> > +++ b/lib/efi_loader/efi_bootmgr.c
> > @@ -19,6 +19,9 @@
> >   static const struct efi_boot_services *bs;
> >   static const struct efi_runtime_services *rs;
> >
> > +const efi_guid_t efi_guid_bootmenu_auto_generated =
> > +             EFICONFIG_AUTO_GENERATED_ENTRY_GUID;
> > +
> >   /*
> >    * bootmgr implements the logic of trying to find a payload to boot
> >    * based on the BootOrder + BootXXXX variables, and then loading it.
> > 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 3164fd484e..5be509f0d6 100644
> > --- a/lib/efi_loader/efi_console.c
> > +++ b/lib/efi_loader/efi_console.c
> > @@ -7,6 +7,7 @@
> >
> >   #define LOG_CATEGORY LOGC_EFI
> >
> > +#include <ansi.h>
> >   #include <common.h>
> >   #include <charset.h>
> >   #include <malloc.h>
> > @@ -1318,3 +1319,72 @@ 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
> > + * @buf:             buffer to store user input string in UTF16
> > + * @count:           number of u16 string including NULL terminator that buf has
> > + * @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,
> > +                                     u16 *buf, efi_uintn_t count,
> > +                                     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
> > +            ANSI_CLEAR_LINE_TO_END
> > +            ANSI_CURSOR_SHOW, row, col);
> > +
> > +     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
> > +                            "%ls"
> > +                            ANSI_CLEAR_LINE_TO_END, row, col, buf);
> > +                     continue;
> > +             } else if (key.unicode_char == u'\r') {
> > +                     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 >= (count - 1))
> > +                     continue;
> > +
> > +             buf[len] = key.unicode_char;
> > +             len++;
> > +             printf(ANSI_CURSOR_POSITION "%ls", row, col, buf);
> > +     }
> > +}
> > diff --git a/lib/efi_loader/efi_disk.c b/lib/efi_loader/efi_disk.c
> > index 16d14b0429..4c9c4cfec8 100644
> > --- a/lib/efi_loader/efi_disk.c
> > +++ b/lib/efi_loader/efi_disk.c
> > @@ -769,3 +769,53 @@ efi_status_t efi_disk_init(void)
> >
> >       return EFI_SUCCESS;
> >   }
> > +
> > +/**
> > + * efi_disk_get_device_name() - get U-Boot device name associated with EFI handle
> > + *
> > + * @handle:  pointer to the EFI handle
> > + * @buf:     pointer to the buffer to store the string
> > + * @size:    size of buffer
> > + * Return:   status code
> > + */
> > +efi_status_t efi_disk_get_device_name(const efi_handle_t handle, char *buf, int size)
> > +{
> > +     int count;
> > +     int diskid;
> > +     enum uclass_id id;
> > +     unsigned int part;
> > +     struct udevice *dev;
> > +     struct blk_desc *desc;
> > +     const char *if_typename;
> > +     bool is_partition = false;
> > +     struct disk_part *part_data;
> > +
> > +     if (!handle || !buf || !size)
> > +             return EFI_INVALID_PARAMETER;
> > +
> > +     dev = handle->dev;
> > +     id = device_get_uclass_id(dev);
> > +     if (id == UCLASS_BLK) {
> > +             desc = dev_get_uclass_plat(dev);
> > +     } else if (id == UCLASS_PARTITION) {
> > +             desc = dev_get_uclass_plat(dev_get_parent(dev));
> > +             is_partition = true;
> > +     } else {
> > +             return EFI_INVALID_PARAMETER;
> > +     }
> > +     if_typename = blk_get_if_type_name(desc->if_type);
> > +     diskid = desc->devnum;
> > +
> > +     if (is_partition) {
> > +             part_data = dev_get_uclass_plat(dev);
> > +             part = part_data->partnum;
> > +             count = snprintf(buf, size, "%s %d:%d", if_typename, diskid, part);
> > +     } else {
> > +             count = snprintf(buf, size, "%s %d", if_typename, diskid);
> > +     }
> > +
> > +     if (count < 0 || (count + 1) > size)
> > +             return EFI_INVALID_PARAMETER;
> > +
> > +     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 *
>

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH v11 6/9] bootmenu: add removable media entries
  2022-08-17  9:36 ` [PATCH v11 6/9] bootmenu: add removable media entries Masahisa Kojima
@ 2022-08-19  1:31   ` Takahiro Akashi
  2022-08-19  3:05     ` Masahisa Kojima
  0 siblings, 1 reply; 25+ messages in thread
From: Takahiro Akashi @ 2022-08-19  1:31 UTC (permalink / raw)
  To: Masahisa Kojima
  Cc: u-boot, Heinrich Schuchardt, Ilias Apalodimas, Simon Glass,
	Mark Kettenis

On Wed, Aug 17, 2022 at 06:36:11PM +0900, Masahisa Kojima wrote:
> UEFI specification requires booting from removal media using
> a architecture-specific default image name such as BOOTAA64.EFI.
> This commit adds the removable media entries into bootmenu,
> so that user can select the removable media and boot with
> default image.
> 
> The bootmenu automatically enumerates the possible bootable
> media devices supporting EFI_SIMPLE_FILE_SYSTEM_PROTOCOL,
> add it as new UEFI boot option(BOOT####) and update BootOrder
> variable. This automatically generated UEFI boot option

Should this feature belong to bootmenu command?
Under the current implementation, those boot options are
generated only by bootmenu, and so if eficonfig is invoked
prior to bootmenu, we won't see them (under "Change Boot Order").

I expect that the functionality be also provided in eficonfig
(or even as part of system initialization?).

-Takahiro Akashi


> has the dedicated guid in the optional_data to distinguish it from
> the UEFI boot option user adds manually. This optional_data is
> removed when the efi bootmgr loads the selected UEFI boot option.
> 
> This commit also provides the BOOT#### variable maintenance feature.
> Depending on the system hardware setup, some devices
> may not exist at a later system boot, so bootmenu checks the
> available device in each bootmenu invocation and automatically
> removes the BOOT#### variable corrensponding to the non-existent
> media device.
> 
> Signed-off-by: Masahisa Kojima <masahisa.kojima@linaro.org>
> ---
> Changes in v11:
> - update delete_boot_option() parameter
> 
> Changes in v10:
> - add function comment
> - devname dynamic allocation removes, allocate in stack
> - delete BOOT#### when updating BootOrder fails
> 
> Changes in v9:
> - update efi_disk_get_device_name() parameter to pass efi_handle_t
> - add function comment
> 
> Changes in v8:
> - function and structure prefix is changed to "eficonfig"
> 
> Changes in v7:
> - rename prepare_media_device_entry() to generate_media_device_boot_option()
> 
> Changes in v6:
> - optional_data size is changed to 16bytes
> - check the load option size before comparison
> - remove guid included in optional_data of auto generated
>   entry when loading
> 
> Changes in v5:
> - Return EFI_SUCCESS if there is no BootOrder defined
> - correctly handle the case if no removable device found
> - use guid to identify the automatically generated entry by bootmenu
> 
>  cmd/bootmenu.c               | 106 +++++++++++++++++++++++++--
>  cmd/eficonfig.c              | 135 +++++++++++++++++++++++++++++++++++
>  include/efi_loader.h         |  20 ++++++
>  lib/efi_loader/efi_bootmgr.c |   4 ++
>  4 files changed, 260 insertions(+), 5 deletions(-)
> 
> diff --git a/cmd/bootmenu.c b/cmd/bootmenu.c
> index 704d36debe..04df41a0cb 100644
> --- a/cmd/bootmenu.c
> +++ b/cmd/bootmenu.c
> @@ -220,7 +220,93 @@ static int prepare_bootmenu_entry(struct bootmenu_data *menu,
>  	return 1;
>  }
>  
> -#if (CONFIG_IS_ENABLED(CMD_BOOTEFI_BOOTMGR))
> +#if (CONFIG_IS_ENABLED(CMD_BOOTEFI_BOOTMGR)) && (CONFIG_IS_ENABLED(CMD_EFICONFIG))
> +/**
> + * generate_media_device_boot_option() - generate the media device boot option
> + *
> + * This function enumerates all devices supporting EFI_SIMPLE_FILE_SYSTEM_PROTOCOL
> + * and generate the bootmenu entries.
> + * This function also provide the BOOT#### variable maintenance for
> + * the media device entries.
> + *   - Automatically create the BOOT#### variable for the newly detected device,
> + *     this BOOT#### variable is distinguished by the special GUID
> + *     stored in the EFI_LOAD_OPTION.optional_data
> + *   - If the device is not attached to the system, the associated BOOT#### variable
> + *     is automatically deleted.
> + *
> + * Return:	status code
> + */
> +static efi_status_t generate_media_device_boot_option(void)
> +{
> +	u32 i;
> +	efi_status_t ret;
> +	efi_uintn_t count;
> +	efi_handle_t *volume_handles = NULL;
> +	struct eficonfig_media_boot_option *opt = NULL;
> +
> +	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;
> +
> +	opt = calloc(count, sizeof(struct eficonfig_media_boot_option));
> +	if (!opt)
> +		goto out;
> +
> +	/* enumerate all devices supporting EFI_SIMPLE_FILE_SYSTEM_PROTOCOL */
> +	ret = eficonfig_enumerate_boot_option(opt, volume_handles, count);
> +	if (ret != EFI_SUCCESS)
> +		goto out;
> +
> +	/*
> +	 * System hardware configuration may vary depending on the user setup.
> +	 * The boot option is automatically added by the bootmenu.
> +	 * If the device is not attached to the system, the boot option needs
> +	 * to be deleted.
> +	 */
> +	ret = eficonfig_delete_invalid_boot_option(opt, count);
> +	if (ret != EFI_SUCCESS)
> +		goto out;
> +
> +	/* add non-existent boot option */
> +	for (i = 0; i < count; i++) {
> +		u32 boot_index;
> +		u16 var_name[9];
> +
> +		if (!opt[i].exist) {
> +			ret = eficonfig_get_unused_bootoption(var_name, sizeof(var_name),
> +							      &boot_index);
> +			if (ret != EFI_SUCCESS)
> +				goto out;
> +
> +			ret = efi_set_variable_int(var_name, &efi_global_variable_guid,
> +						   EFI_VARIABLE_NON_VOLATILE |
> +						   EFI_VARIABLE_BOOTSERVICE_ACCESS |
> +						   EFI_VARIABLE_RUNTIME_ACCESS,
> +						   opt[i].size, opt[i].lo, false);
> +			if (ret != EFI_SUCCESS)
> +				goto out;
> +
> +			ret = eficonfig_append_bootorder(boot_index);
> +			if (ret != EFI_SUCCESS) {
> +				efi_set_variable_int(var_name, &efi_global_variable_guid,
> +						     0, 0, NULL, false);
> +				goto out;
> +			}
> +		}
> +	}
> +
> +out:
> +	if (opt) {
> +		for (i = 0; i < count; i++)
> +			free(opt[i].lo);
> +	}
> +	free(opt);
> +	efi_free_pool(volume_handles);
> +
> +	return ret;
> +}
> +
>  /**
>   * prepare_uefi_bootorder_entry() - generate the uefi bootmenu entries
>   *
> @@ -340,11 +426,21 @@ static struct bootmenu_data *bootmenu_create(int delay)
>  	if (ret < 0)
>  		goto cleanup;
>  
> -#if (CONFIG_IS_ENABLED(CMD_BOOTEFI_BOOTMGR))
> +#if (CONFIG_IS_ENABLED(CMD_BOOTEFI_BOOTMGR)) && (CONFIG_IS_ENABLED(CMD_EFICONFIG))
>  	if (i < MAX_COUNT - 1) {
> -			ret = prepare_uefi_bootorder_entry(menu, &iter, &i);
> -			if (ret < 0 && ret != -ENOENT)
> -				goto cleanup;
> +		efi_status_t efi_ret;
> +
> +		/*
> +		 * UEFI specification requires booting from removal media using
> +		 * a architecture-specific default image name such as BOOTAA64.EFI.
> +		 */
> +		efi_ret = generate_media_device_boot_option();
> +		if (efi_ret != EFI_SUCCESS && efi_ret != EFI_NOT_FOUND)
> +			goto cleanup;
> +
> +		ret = prepare_uefi_bootorder_entry(menu, &iter, &i);
> +		if (ret < 0 && ret != -ENOENT)
> +			goto cleanup;
>  	}
>  #endif
>  
> diff --git a/cmd/eficonfig.c b/cmd/eficonfig.c
> index 6e39c0cd4d..c7f55c62fb 100644
> --- a/cmd/eficonfig.c
> +++ b/cmd/eficonfig.c
> @@ -2080,6 +2080,141 @@ static efi_status_t eficonfig_process_delete_boot_option(void *data)
>  	return ret;
>  }
>  
> +/**
> + * eficonfig_enumerate_boot_option() - enumerate the possible bootable media
> + *
> + * @opt:		pointer to the media boot option structure
> + * @volume_handles:	pointer to the efi handles
> + * @count:		number of efi handle
> + * Return:		status code
> + */
> +efi_status_t eficonfig_enumerate_boot_option(struct eficonfig_media_boot_option *opt,
> +					     efi_handle_t *volume_handles, efi_status_t count)
> +{
> +	u32 i;
> +	struct efi_handler *handler;
> +	efi_status_t ret = EFI_SUCCESS;
> +
> +	for (i = 0; i < count; i++) {
> +		u16 *p;
> +		u16 dev_name[BOOTMENU_DEVICE_NAME_MAX];
> +		char *optional_data;
> +		struct efi_load_option lo;
> +		char buf[BOOTMENU_DEVICE_NAME_MAX];
> +		struct efi_device_path *device_path;
> +
> +		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_disk_get_device_name(volume_handles[i], buf, BOOTMENU_DEVICE_NAME_MAX);
> +		if (ret != EFI_SUCCESS)
> +			continue;
> +
> +		p = dev_name;
> +		utf8_utf16_strncpy(&p, buf, strlen(buf));
> +
> +		lo.label = dev_name;
> +		lo.attributes = LOAD_OPTION_ACTIVE;
> +		lo.file_path = device_path;
> +		lo.file_path_length = efi_dp_size(device_path) + sizeof(END);
> +		/*
> +		 * Set the dedicated guid to optional_data, it is used to identify
> +		 * the boot option that automatically generated by the bootmenu.
> +		 * efi_serialize_load_option() expects optional_data is null-terminated
> +		 * utf8 string, so set the "1234567" string to allocate enough space
> +		 * to store guid, instead of realloc the load_option.
> +		 */
> +		lo.optional_data = "1234567";
> +		opt[i].size = efi_serialize_load_option(&lo, (u8 **)&opt[i].lo);
> +		if (!opt[i].size) {
> +			ret = EFI_OUT_OF_RESOURCES;
> +			free(dev_name);
> +			goto out;
> +		}
> +		/* set the guid */
> +		optional_data = (char *)opt[i].lo + (opt[i].size - u16_strsize(u"1234567"));
> +		memcpy(optional_data, &efi_guid_bootmenu_auto_generated, sizeof(efi_guid_t));
> +	}
> +
> +out:
> +	return ret;
> +}
> +
> +/**
> + * eficonfig_delete_invalid_boot_option() - delete non-existing boot option
> + *
> + * @opt:		pointer to the media boot option structure
> + * @count:		number of media boot option structure
> + * Return:		status code
> + */
> +efi_status_t eficonfig_delete_invalid_boot_option(struct eficonfig_media_boot_option *opt,
> +						  efi_status_t count)
> +{
> +	u16 *bootorder;
> +	u32 i, j;
> +	efi_status_t ret;
> +	efi_uintn_t num, size, bootorder_size;
> +	void *load_option;
> +	struct efi_load_option lo;
> +	u16 varname[] = u"Boot####";
> +
> +	bootorder = efi_get_var(u"BootOrder", &efi_global_variable_guid, &bootorder_size);
> +	if (!bootorder)
> +		return EFI_SUCCESS; /* BootOrder is not defined, nothing to do */
> +
> +	num = bootorder_size / sizeof(u16);
> +	for (i = 0; i < num;) {
> +		efi_uintn_t tmp;
> +
> +		efi_create_indexed_name(varname, sizeof(varname),
> +					"Boot", bootorder[i]);
> +		load_option = efi_get_var(varname, &efi_global_variable_guid, &size);
> +		if (!load_option)
> +			goto next;
> +
> +		tmp = size;
> +		ret = efi_deserialize_load_option(&lo, load_option, &size);
> +		if (ret != EFI_SUCCESS)
> +			goto next;
> +
> +		if (size >= sizeof(efi_guid_bootmenu_auto_generated)) {
> +			if (guidcmp(lo.optional_data, &efi_guid_bootmenu_auto_generated) == 0) {
> +				for (j = 0; j < count; j++) {
> +					if (opt[j].size == tmp &&
> +					    memcmp(opt[j].lo, load_option, tmp) == 0) {
> +						opt[j].exist = true;
> +						break;
> +					}
> +				}
> +
> +				if (j == count) {
> +					ret = delete_boot_option(bootorder[i]);
> +					if (ret != EFI_SUCCESS) {
> +						free(load_option);
> +						goto out;
> +					}
> +
> +					num--;
> +					bootorder_size -= sizeof(u16);
> +					free(load_option);
> +					continue;
> +				}
> +			}
> +		}
> +next:
> +		free(load_option);
> +		i++;
> +	}
> +
> +out:
> +	return ret;
> +}
> +
>  /**
>   * eficonfig_init() - do required initialization for eficonfig command
>   *
> diff --git a/include/efi_loader.h b/include/efi_loader.h
> index 49e7d1e613..a5a0448fa0 100644
> --- a/include/efi_loader.h
> +++ b/include/efi_loader.h
> @@ -955,6 +955,22 @@ struct efi_signature_store {
>  struct x509_certificate;
>  struct pkcs7_message;
>  
> +/**
> + * struct eficonfig_media_boot_option - boot option for (removable) media device
> + *
> + * This structure is used to enumerate possible boot option
> + *
> + * @lo:		Serialized load option data
> + * @size:	Size of serialized load option data
> + * @exist:	Flag to indicate the load option already exists
> + *		in Non-volatile load option
> + */
> +struct eficonfig_media_boot_option {
> +	void *lo;
> +	efi_uintn_t size;
> +	bool exist;
> +};
> +
>  bool efi_hash_regions(struct image_region *regs, int count,
>  		      void **hash, const char *hash_algo, int *len);
>  bool efi_signature_lookup_digest(struct efi_image_regions *regs,
> @@ -1104,6 +1120,10 @@ efi_status_t efi_console_get_u16_string
>  efi_status_t eficonfig_get_unused_bootoption(u16 *buf,
>  					     efi_uintn_t buf_size, u32 *index);
>  efi_status_t eficonfig_append_bootorder(u16 index);
> +efi_status_t eficonfig_enumerate_boot_option(struct eficonfig_media_boot_option *opt,
> +					     efi_handle_t *volume_handles, efi_status_t count);
> +efi_status_t eficonfig_delete_invalid_boot_option(struct eficonfig_media_boot_option *opt,
> +						  efi_status_t count);
>  
>  efi_status_t efi_disk_get_device_name(const efi_handle_t handle, char *buf, int size);
>  
> diff --git a/lib/efi_loader/efi_bootmgr.c b/lib/efi_loader/efi_bootmgr.c
> index ede9116b3c..4b24b41047 100644
> --- a/lib/efi_loader/efi_bootmgr.c
> +++ b/lib/efi_loader/efi_bootmgr.c
> @@ -246,6 +246,10 @@ static efi_status_t try_load_entry(u16 n, efi_handle_t *handle,
>  	}
>  
>  	/* Set load options */
> +	if (size >= sizeof(efi_guid_t) &&
> +	    !guidcmp(lo.optional_data, &efi_guid_bootmenu_auto_generated))
> +		size = 0;
> +
>  	if (size) {
>  		*load_options = malloc(size);
>  		if (!*load_options) {
> -- 
> 2.17.1
> 

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH v11 6/9] bootmenu: add removable media entries
  2022-08-19  1:31   ` Takahiro Akashi
@ 2022-08-19  3:05     ` Masahisa Kojima
  2022-08-24  1:57       ` Takahiro Akashi
  0 siblings, 1 reply; 25+ messages in thread
From: Masahisa Kojima @ 2022-08-19  3:05 UTC (permalink / raw)
  To: Takahiro Akashi, Masahisa Kojima, u-boot, Heinrich Schuchardt,
	Ilias Apalodimas, Simon Glass, Mark Kettenis

Hi Akashi-san,

On Fri, 19 Aug 2022 at 10:31, Takahiro Akashi
<takahiro.akashi@linaro.org> wrote:
>
> On Wed, Aug 17, 2022 at 06:36:11PM +0900, Masahisa Kojima wrote:
> > UEFI specification requires booting from removal media using
> > a architecture-specific default image name such as BOOTAA64.EFI.
> > This commit adds the removable media entries into bootmenu,
> > so that user can select the removable media and boot with
> > default image.
> >
> > The bootmenu automatically enumerates the possible bootable
> > media devices supporting EFI_SIMPLE_FILE_SYSTEM_PROTOCOL,
> > add it as new UEFI boot option(BOOT####) and update BootOrder
> > variable. This automatically generated UEFI boot option
>
> Should this feature belong to bootmenu command?
> Under the current implementation, those boot options are
> generated only by bootmenu, and so if eficonfig is invoked
> prior to bootmenu, we won't see them (under "Change Boot Order").
>
> I expect that the functionality be also provided in eficonfig
> (or even as part of system initialization?).

OK, generating the (removable) media boot options will be added
in "Change Boot Order".

Thanks,
Masahisa Kojima

>
> -Takahiro Akashi
>
>
> > has the dedicated guid in the optional_data to distinguish it from
> > the UEFI boot option user adds manually. This optional_data is
> > removed when the efi bootmgr loads the selected UEFI boot option.
> >
> > This commit also provides the BOOT#### variable maintenance feature.
> > Depending on the system hardware setup, some devices
> > may not exist at a later system boot, so bootmenu checks the
> > available device in each bootmenu invocation and automatically
> > removes the BOOT#### variable corrensponding to the non-existent
> > media device.
> >
> > Signed-off-by: Masahisa Kojima <masahisa.kojima@linaro.org>
> > ---
> > Changes in v11:
> > - update delete_boot_option() parameter
> >
> > Changes in v10:
> > - add function comment
> > - devname dynamic allocation removes, allocate in stack
> > - delete BOOT#### when updating BootOrder fails
> >
> > Changes in v9:
> > - update efi_disk_get_device_name() parameter to pass efi_handle_t
> > - add function comment
> >
> > Changes in v8:
> > - function and structure prefix is changed to "eficonfig"
> >
> > Changes in v7:
> > - rename prepare_media_device_entry() to generate_media_device_boot_option()
> >
> > Changes in v6:
> > - optional_data size is changed to 16bytes
> > - check the load option size before comparison
> > - remove guid included in optional_data of auto generated
> >   entry when loading
> >
> > Changes in v5:
> > - Return EFI_SUCCESS if there is no BootOrder defined
> > - correctly handle the case if no removable device found
> > - use guid to identify the automatically generated entry by bootmenu
> >
> >  cmd/bootmenu.c               | 106 +++++++++++++++++++++++++--
> >  cmd/eficonfig.c              | 135 +++++++++++++++++++++++++++++++++++
> >  include/efi_loader.h         |  20 ++++++
> >  lib/efi_loader/efi_bootmgr.c |   4 ++
> >  4 files changed, 260 insertions(+), 5 deletions(-)
> >
> > diff --git a/cmd/bootmenu.c b/cmd/bootmenu.c
> > index 704d36debe..04df41a0cb 100644
> > --- a/cmd/bootmenu.c
> > +++ b/cmd/bootmenu.c
> > @@ -220,7 +220,93 @@ static int prepare_bootmenu_entry(struct bootmenu_data *menu,
> >       return 1;
> >  }
> >
> > -#if (CONFIG_IS_ENABLED(CMD_BOOTEFI_BOOTMGR))
> > +#if (CONFIG_IS_ENABLED(CMD_BOOTEFI_BOOTMGR)) && (CONFIG_IS_ENABLED(CMD_EFICONFIG))
> > +/**
> > + * generate_media_device_boot_option() - generate the media device boot option
> > + *
> > + * This function enumerates all devices supporting EFI_SIMPLE_FILE_SYSTEM_PROTOCOL
> > + * and generate the bootmenu entries.
> > + * This function also provide the BOOT#### variable maintenance for
> > + * the media device entries.
> > + *   - Automatically create the BOOT#### variable for the newly detected device,
> > + *     this BOOT#### variable is distinguished by the special GUID
> > + *     stored in the EFI_LOAD_OPTION.optional_data
> > + *   - If the device is not attached to the system, the associated BOOT#### variable
> > + *     is automatically deleted.
> > + *
> > + * Return:   status code
> > + */
> > +static efi_status_t generate_media_device_boot_option(void)
> > +{
> > +     u32 i;
> > +     efi_status_t ret;
> > +     efi_uintn_t count;
> > +     efi_handle_t *volume_handles = NULL;
> > +     struct eficonfig_media_boot_option *opt = NULL;
> > +
> > +     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;
> > +
> > +     opt = calloc(count, sizeof(struct eficonfig_media_boot_option));
> > +     if (!opt)
> > +             goto out;
> > +
> > +     /* enumerate all devices supporting EFI_SIMPLE_FILE_SYSTEM_PROTOCOL */
> > +     ret = eficonfig_enumerate_boot_option(opt, volume_handles, count);
> > +     if (ret != EFI_SUCCESS)
> > +             goto out;
> > +
> > +     /*
> > +      * System hardware configuration may vary depending on the user setup.
> > +      * The boot option is automatically added by the bootmenu.
> > +      * If the device is not attached to the system, the boot option needs
> > +      * to be deleted.
> > +      */
> > +     ret = eficonfig_delete_invalid_boot_option(opt, count);
> > +     if (ret != EFI_SUCCESS)
> > +             goto out;
> > +
> > +     /* add non-existent boot option */
> > +     for (i = 0; i < count; i++) {
> > +             u32 boot_index;
> > +             u16 var_name[9];
> > +
> > +             if (!opt[i].exist) {
> > +                     ret = eficonfig_get_unused_bootoption(var_name, sizeof(var_name),
> > +                                                           &boot_index);
> > +                     if (ret != EFI_SUCCESS)
> > +                             goto out;
> > +
> > +                     ret = efi_set_variable_int(var_name, &efi_global_variable_guid,
> > +                                                EFI_VARIABLE_NON_VOLATILE |
> > +                                                EFI_VARIABLE_BOOTSERVICE_ACCESS |
> > +                                                EFI_VARIABLE_RUNTIME_ACCESS,
> > +                                                opt[i].size, opt[i].lo, false);
> > +                     if (ret != EFI_SUCCESS)
> > +                             goto out;
> > +
> > +                     ret = eficonfig_append_bootorder(boot_index);
> > +                     if (ret != EFI_SUCCESS) {
> > +                             efi_set_variable_int(var_name, &efi_global_variable_guid,
> > +                                                  0, 0, NULL, false);
> > +                             goto out;
> > +                     }
> > +             }
> > +     }
> > +
> > +out:
> > +     if (opt) {
> > +             for (i = 0; i < count; i++)
> > +                     free(opt[i].lo);
> > +     }
> > +     free(opt);
> > +     efi_free_pool(volume_handles);
> > +
> > +     return ret;
> > +}
> > +
> >  /**
> >   * prepare_uefi_bootorder_entry() - generate the uefi bootmenu entries
> >   *
> > @@ -340,11 +426,21 @@ static struct bootmenu_data *bootmenu_create(int delay)
> >       if (ret < 0)
> >               goto cleanup;
> >
> > -#if (CONFIG_IS_ENABLED(CMD_BOOTEFI_BOOTMGR))
> > +#if (CONFIG_IS_ENABLED(CMD_BOOTEFI_BOOTMGR)) && (CONFIG_IS_ENABLED(CMD_EFICONFIG))
> >       if (i < MAX_COUNT - 1) {
> > -                     ret = prepare_uefi_bootorder_entry(menu, &iter, &i);
> > -                     if (ret < 0 && ret != -ENOENT)
> > -                             goto cleanup;
> > +             efi_status_t efi_ret;
> > +
> > +             /*
> > +              * UEFI specification requires booting from removal media using
> > +              * a architecture-specific default image name such as BOOTAA64.EFI.
> > +              */
> > +             efi_ret = generate_media_device_boot_option();
> > +             if (efi_ret != EFI_SUCCESS && efi_ret != EFI_NOT_FOUND)
> > +                     goto cleanup;
> > +
> > +             ret = prepare_uefi_bootorder_entry(menu, &iter, &i);
> > +             if (ret < 0 && ret != -ENOENT)
> > +                     goto cleanup;
> >       }
> >  #endif
> >
> > diff --git a/cmd/eficonfig.c b/cmd/eficonfig.c
> > index 6e39c0cd4d..c7f55c62fb 100644
> > --- a/cmd/eficonfig.c
> > +++ b/cmd/eficonfig.c
> > @@ -2080,6 +2080,141 @@ static efi_status_t eficonfig_process_delete_boot_option(void *data)
> >       return ret;
> >  }
> >
> > +/**
> > + * eficonfig_enumerate_boot_option() - enumerate the possible bootable media
> > + *
> > + * @opt:             pointer to the media boot option structure
> > + * @volume_handles:  pointer to the efi handles
> > + * @count:           number of efi handle
> > + * Return:           status code
> > + */
> > +efi_status_t eficonfig_enumerate_boot_option(struct eficonfig_media_boot_option *opt,
> > +                                          efi_handle_t *volume_handles, efi_status_t count)
> > +{
> > +     u32 i;
> > +     struct efi_handler *handler;
> > +     efi_status_t ret = EFI_SUCCESS;
> > +
> > +     for (i = 0; i < count; i++) {
> > +             u16 *p;
> > +             u16 dev_name[BOOTMENU_DEVICE_NAME_MAX];
> > +             char *optional_data;
> > +             struct efi_load_option lo;
> > +             char buf[BOOTMENU_DEVICE_NAME_MAX];
> > +             struct efi_device_path *device_path;
> > +
> > +             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_disk_get_device_name(volume_handles[i], buf, BOOTMENU_DEVICE_NAME_MAX);
> > +             if (ret != EFI_SUCCESS)
> > +                     continue;
> > +
> > +             p = dev_name;
> > +             utf8_utf16_strncpy(&p, buf, strlen(buf));
> > +
> > +             lo.label = dev_name;
> > +             lo.attributes = LOAD_OPTION_ACTIVE;
> > +             lo.file_path = device_path;
> > +             lo.file_path_length = efi_dp_size(device_path) + sizeof(END);
> > +             /*
> > +              * Set the dedicated guid to optional_data, it is used to identify
> > +              * the boot option that automatically generated by the bootmenu.
> > +              * efi_serialize_load_option() expects optional_data is null-terminated
> > +              * utf8 string, so set the "1234567" string to allocate enough space
> > +              * to store guid, instead of realloc the load_option.
> > +              */
> > +             lo.optional_data = "1234567";
> > +             opt[i].size = efi_serialize_load_option(&lo, (u8 **)&opt[i].lo);
> > +             if (!opt[i].size) {
> > +                     ret = EFI_OUT_OF_RESOURCES;
> > +                     free(dev_name);
> > +                     goto out;
> > +             }
> > +             /* set the guid */
> > +             optional_data = (char *)opt[i].lo + (opt[i].size - u16_strsize(u"1234567"));
> > +             memcpy(optional_data, &efi_guid_bootmenu_auto_generated, sizeof(efi_guid_t));
> > +     }
> > +
> > +out:
> > +     return ret;
> > +}
> > +
> > +/**
> > + * eficonfig_delete_invalid_boot_option() - delete non-existing boot option
> > + *
> > + * @opt:             pointer to the media boot option structure
> > + * @count:           number of media boot option structure
> > + * Return:           status code
> > + */
> > +efi_status_t eficonfig_delete_invalid_boot_option(struct eficonfig_media_boot_option *opt,
> > +                                               efi_status_t count)
> > +{
> > +     u16 *bootorder;
> > +     u32 i, j;
> > +     efi_status_t ret;
> > +     efi_uintn_t num, size, bootorder_size;
> > +     void *load_option;
> > +     struct efi_load_option lo;
> > +     u16 varname[] = u"Boot####";
> > +
> > +     bootorder = efi_get_var(u"BootOrder", &efi_global_variable_guid, &bootorder_size);
> > +     if (!bootorder)
> > +             return EFI_SUCCESS; /* BootOrder is not defined, nothing to do */
> > +
> > +     num = bootorder_size / sizeof(u16);
> > +     for (i = 0; i < num;) {
> > +             efi_uintn_t tmp;
> > +
> > +             efi_create_indexed_name(varname, sizeof(varname),
> > +                                     "Boot", bootorder[i]);
> > +             load_option = efi_get_var(varname, &efi_global_variable_guid, &size);
> > +             if (!load_option)
> > +                     goto next;
> > +
> > +             tmp = size;
> > +             ret = efi_deserialize_load_option(&lo, load_option, &size);
> > +             if (ret != EFI_SUCCESS)
> > +                     goto next;
> > +
> > +             if (size >= sizeof(efi_guid_bootmenu_auto_generated)) {
> > +                     if (guidcmp(lo.optional_data, &efi_guid_bootmenu_auto_generated) == 0) {
> > +                             for (j = 0; j < count; j++) {
> > +                                     if (opt[j].size == tmp &&
> > +                                         memcmp(opt[j].lo, load_option, tmp) == 0) {
> > +                                             opt[j].exist = true;
> > +                                             break;
> > +                                     }
> > +                             }
> > +
> > +                             if (j == count) {
> > +                                     ret = delete_boot_option(bootorder[i]);
> > +                                     if (ret != EFI_SUCCESS) {
> > +                                             free(load_option);
> > +                                             goto out;
> > +                                     }
> > +
> > +                                     num--;
> > +                                     bootorder_size -= sizeof(u16);
> > +                                     free(load_option);
> > +                                     continue;
> > +                             }
> > +                     }
> > +             }
> > +next:
> > +             free(load_option);
> > +             i++;
> > +     }
> > +
> > +out:
> > +     return ret;
> > +}
> > +
> >  /**
> >   * eficonfig_init() - do required initialization for eficonfig command
> >   *
> > diff --git a/include/efi_loader.h b/include/efi_loader.h
> > index 49e7d1e613..a5a0448fa0 100644
> > --- a/include/efi_loader.h
> > +++ b/include/efi_loader.h
> > @@ -955,6 +955,22 @@ struct efi_signature_store {
> >  struct x509_certificate;
> >  struct pkcs7_message;
> >
> > +/**
> > + * struct eficonfig_media_boot_option - boot option for (removable) media device
> > + *
> > + * This structure is used to enumerate possible boot option
> > + *
> > + * @lo:              Serialized load option data
> > + * @size:    Size of serialized load option data
> > + * @exist:   Flag to indicate the load option already exists
> > + *           in Non-volatile load option
> > + */
> > +struct eficonfig_media_boot_option {
> > +     void *lo;
> > +     efi_uintn_t size;
> > +     bool exist;
> > +};
> > +
> >  bool efi_hash_regions(struct image_region *regs, int count,
> >                     void **hash, const char *hash_algo, int *len);
> >  bool efi_signature_lookup_digest(struct efi_image_regions *regs,
> > @@ -1104,6 +1120,10 @@ efi_status_t efi_console_get_u16_string
> >  efi_status_t eficonfig_get_unused_bootoption(u16 *buf,
> >                                            efi_uintn_t buf_size, u32 *index);
> >  efi_status_t eficonfig_append_bootorder(u16 index);
> > +efi_status_t eficonfig_enumerate_boot_option(struct eficonfig_media_boot_option *opt,
> > +                                          efi_handle_t *volume_handles, efi_status_t count);
> > +efi_status_t eficonfig_delete_invalid_boot_option(struct eficonfig_media_boot_option *opt,
> > +                                               efi_status_t count);
> >
> >  efi_status_t efi_disk_get_device_name(const efi_handle_t handle, char *buf, int size);
> >
> > diff --git a/lib/efi_loader/efi_bootmgr.c b/lib/efi_loader/efi_bootmgr.c
> > index ede9116b3c..4b24b41047 100644
> > --- a/lib/efi_loader/efi_bootmgr.c
> > +++ b/lib/efi_loader/efi_bootmgr.c
> > @@ -246,6 +246,10 @@ static efi_status_t try_load_entry(u16 n, efi_handle_t *handle,
> >       }
> >
> >       /* Set load options */
> > +     if (size >= sizeof(efi_guid_t) &&
> > +         !guidcmp(lo.optional_data, &efi_guid_bootmenu_auto_generated))
> > +             size = 0;
> > +
> >       if (size) {
> >               *load_options = malloc(size);
> >               if (!*load_options) {
> > --
> > 2.17.1
> >

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH v11 8/9] doc:eficonfig: add documentation for eficonfig command
  2022-08-17  9:36 ` [PATCH v11 8/9] doc:eficonfig: add documentation for eficonfig command Masahisa Kojima
@ 2022-08-19  5:57   ` Takahiro Akashi
  2022-08-19  6:15     ` Masahisa Kojima
  0 siblings, 1 reply; 25+ messages in thread
From: Takahiro Akashi @ 2022-08-19  5:57 UTC (permalink / raw)
  To: Masahisa Kojima
  Cc: u-boot, Heinrich Schuchardt, Ilias Apalodimas, Simon Glass,
	Mark Kettenis, Bin Meng, Marek Beh??n

On Wed, Aug 17, 2022 at 06:36:13PM +0900, Masahisa Kojima wrote:
> Add documentation for eficonfig command.
> 
> Signed-off-by: Masahisa Kojima <masahisa.kojima@linaro.org>
> ---
> No update since v10
> 
> Changes in v10:
> - describe how to boot system after editting by eficonfig
> 
> Changes in v8:
> - command name is changed from "efimenu" to "eficonfig"
> 
> Newly created in v7
> 
>  doc/usage/cmd/eficonfig.rst | 63 +++++++++++++++++++++++++++++++++++++
>  doc/usage/index.rst         |  1 +
>  2 files changed, 64 insertions(+)
>  create mode 100644 doc/usage/cmd/eficonfig.rst
> 
> diff --git a/doc/usage/cmd/eficonfig.rst b/doc/usage/cmd/eficonfig.rst
> new file mode 100644
> index 0000000000..958e96992c
> --- /dev/null
> +++ b/doc/usage/cmd/eficonfig.rst
> @@ -0,0 +1,63 @@
> +.. SPDX-License-Identifier: GPL-2.0+
> +.. (C) Copyright 2022, Masahisa Kojima <masahisa.kojima@linaro.org>
> +
> +eficonfig command
> +=================
> +
> +Synopsis
> +--------
> +::
> +
> +    eficonfig
> +
> +Description
> +-----------
> +
> +The "eficonfig" command uses U-Boot menu interface and privides
> +a menu-driven UEFI variable maintenance feature.
> +The "eficonfig" has the following menu entries.
> +
> +Add Boot Option
> +    Add new UEFI Boot Option.
> +    User can edit description, file path, and optional_data.
> +
> +Edit Boot Option
> +    Edit the existing UEFI Boot Option
> +    User can edit description, file path, and optional_data.
> +
> +Change Boot Order
> +    Change the order of UEFI BootOrder variable.
> +
> +Delete Boot Option
> +    Delete the UEFI Boot Option
> +
> +Configuration
> +-------------
> +
> +The "eficonfig" command is enabled by::
> +
> +    CONFIG_CMD_EFICONFIG=y
> +
> +If CONFIG_BOOTMENU_DISABLE_UBOOT_CONSOLE is enabled, user can not enter
> +U-Boot console. In this case, bootmenu can be used to invoke "eficonfig"::
> +
> +    CONFIG_USE_PREBOOT=y
> +    CONFIG_PREBOOT="setenv bootmenu_0 UEFI Maintenance Menu=eficonfig"
> +
> +How to boot the system with newly added UEFI Boot Option
> +''''''''''''''''''''''''''''''''''''''''''''''''''''''''
> +
> +"eficonfig" command is responsible to configure the UEFI variables,
> +not directly handle the system boot.
> +The new Boot Option added by "eficonfig" is appended at the last entry
> +of UEFI BootOrder variable, user may want to change the boot order
> +through "Change Boot Order".
> +If the bootmenu is enabled and "eficonfig" is configured as preboot command,
> +the newly added Boot Options are enumerated in the bootmenu when user exits
> +from the eficonfig menu.

Right, and when user quits from eficonfig, bootmenu also quits.
*If possible*, I expect that we return to bootmenu's screen
directly for better user-experience :)

Otherwise, we have to enable U-Boot console to re-invoke
bootmenu. (we can instead reboot U-Boot, though.)

-Takahiro Akashi

> +User may select the entry in the bootmenu to boot the system, or follow
> +the U-Boot configuration the system already has.
> +
> +See also
> +--------
> +* :doc:`bootmenu<bootmenu>` provides a simple mechanism for creating menus with different boot items
> diff --git a/doc/usage/index.rst b/doc/usage/index.rst
> index 28f9683a3e..09f2928970 100644
> --- a/doc/usage/index.rst
> +++ b/doc/usage/index.rst
> @@ -35,6 +35,7 @@ Shell commands
>     cmd/conitrace
>     cmd/dm
>     cmd/echo
> +   cmd/eficonfig
>     cmd/env
>     cmd/event
>     cmd/exception
> -- 
> 2.17.1
> 

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH v11 8/9] doc:eficonfig: add documentation for eficonfig command
  2022-08-19  5:57   ` Takahiro Akashi
@ 2022-08-19  6:15     ` Masahisa Kojima
  2022-08-19  6:34       ` Takahiro Akashi
  0 siblings, 1 reply; 25+ messages in thread
From: Masahisa Kojima @ 2022-08-19  6:15 UTC (permalink / raw)
  To: Takahiro Akashi, Masahisa Kojima, u-boot, Heinrich Schuchardt,
	Ilias Apalodimas, Simon Glass, Mark Kettenis, Bin Meng,
	Marek Beh??n

Hi Akashi-san,

On Fri, 19 Aug 2022 at 14:57, Takahiro Akashi
<takahiro.akashi@linaro.org> wrote:
>
> On Wed, Aug 17, 2022 at 06:36:13PM +0900, Masahisa Kojima wrote:
> > Add documentation for eficonfig command.
> >
> > Signed-off-by: Masahisa Kojima <masahisa.kojima@linaro.org>
> > ---
> > No update since v10
> >
> > Changes in v10:
> > - describe how to boot system after editting by eficonfig
> >
> > Changes in v8:
> > - command name is changed from "efimenu" to "eficonfig"
> >
> > Newly created in v7
> >
> >  doc/usage/cmd/eficonfig.rst | 63 +++++++++++++++++++++++++++++++++++++
> >  doc/usage/index.rst         |  1 +
> >  2 files changed, 64 insertions(+)
> >  create mode 100644 doc/usage/cmd/eficonfig.rst
> >
> > diff --git a/doc/usage/cmd/eficonfig.rst b/doc/usage/cmd/eficonfig.rst
> > new file mode 100644
> > index 0000000000..958e96992c
> > --- /dev/null
> > +++ b/doc/usage/cmd/eficonfig.rst
> > @@ -0,0 +1,63 @@
> > +.. SPDX-License-Identifier: GPL-2.0+
> > +.. (C) Copyright 2022, Masahisa Kojima <masahisa.kojima@linaro.org>
> > +
> > +eficonfig command
> > +=================
> > +
> > +Synopsis
> > +--------
> > +::
> > +
> > +    eficonfig
> > +
> > +Description
> > +-----------
> > +
> > +The "eficonfig" command uses U-Boot menu interface and privides
> > +a menu-driven UEFI variable maintenance feature.
> > +The "eficonfig" has the following menu entries.
> > +
> > +Add Boot Option
> > +    Add new UEFI Boot Option.
> > +    User can edit description, file path, and optional_data.
> > +
> > +Edit Boot Option
> > +    Edit the existing UEFI Boot Option
> > +    User can edit description, file path, and optional_data.
> > +
> > +Change Boot Order
> > +    Change the order of UEFI BootOrder variable.
> > +
> > +Delete Boot Option
> > +    Delete the UEFI Boot Option
> > +
> > +Configuration
> > +-------------
> > +
> > +The "eficonfig" command is enabled by::
> > +
> > +    CONFIG_CMD_EFICONFIG=y
> > +
> > +If CONFIG_BOOTMENU_DISABLE_UBOOT_CONSOLE is enabled, user can not enter
> > +U-Boot console. In this case, bootmenu can be used to invoke "eficonfig"::
> > +
> > +    CONFIG_USE_PREBOOT=y
> > +    CONFIG_PREBOOT="setenv bootmenu_0 UEFI Maintenance Menu=eficonfig"
> > +
> > +How to boot the system with newly added UEFI Boot Option
> > +''''''''''''''''''''''''''''''''''''''''''''''''''''''''
> > +
> > +"eficonfig" command is responsible to configure the UEFI variables,
> > +not directly handle the system boot.
> > +The new Boot Option added by "eficonfig" is appended at the last entry
> > +of UEFI BootOrder variable, user may want to change the boot order
> > +through "Change Boot Order".
> > +If the bootmenu is enabled and "eficonfig" is configured as preboot command,
> > +the newly added Boot Options are enumerated in the bootmenu when user exits
> > +from the eficonfig menu.
>
> Right, and when user quits from eficonfig, bootmenu also quits.
> *If possible*, I expect that we return to bootmenu's screen
> directly for better user-experience :)
>
> Otherwise, we have to enable U-Boot console to re-invoke
> bootmenu. (we can instead reboot U-Boot, though.)

If CONFIG_BOOTMENU_DISABLE_UBOOT_CONSOLE is enabled, bootmenu appears
again when user quits from eficonfig.
I need to describe this condition in the document.

Thanks,
Masahisa Kojima

>
> -Takahiro Akashi
>
> > +User may select the entry in the bootmenu to boot the system, or follow
> > +the U-Boot configuration the system already has.
> > +
> > +See also
> > +--------
> > +* :doc:`bootmenu<bootmenu>` provides a simple mechanism for creating menus with different boot items
> > diff --git a/doc/usage/index.rst b/doc/usage/index.rst
> > index 28f9683a3e..09f2928970 100644
> > --- a/doc/usage/index.rst
> > +++ b/doc/usage/index.rst
> > @@ -35,6 +35,7 @@ Shell commands
> >     cmd/conitrace
> >     cmd/dm
> >     cmd/echo
> > +   cmd/eficonfig
> >     cmd/env
> >     cmd/event
> >     cmd/exception
> > --
> > 2.17.1
> >

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH v11 8/9] doc:eficonfig: add documentation for eficonfig command
  2022-08-19  6:15     ` Masahisa Kojima
@ 2022-08-19  6:34       ` Takahiro Akashi
  2022-08-19  7:32         ` Masahisa Kojima
  0 siblings, 1 reply; 25+ messages in thread
From: Takahiro Akashi @ 2022-08-19  6:34 UTC (permalink / raw)
  To: Masahisa Kojima
  Cc: u-boot, Heinrich Schuchardt, Ilias Apalodimas, Simon Glass,
	Mark Kettenis, Bin Meng, Marek Beh??n

On Fri, Aug 19, 2022 at 03:15:09PM +0900, Masahisa Kojima wrote:
> Hi Akashi-san,
> 
> On Fri, 19 Aug 2022 at 14:57, Takahiro Akashi
> <takahiro.akashi@linaro.org> wrote:
> >
> > On Wed, Aug 17, 2022 at 06:36:13PM +0900, Masahisa Kojima wrote:
> > > Add documentation for eficonfig command.
> > >
> > > Signed-off-by: Masahisa Kojima <masahisa.kojima@linaro.org>
> > > ---
> > > No update since v10
> > >
> > > Changes in v10:
> > > - describe how to boot system after editting by eficonfig
> > >
> > > Changes in v8:
> > > - command name is changed from "efimenu" to "eficonfig"
> > >
> > > Newly created in v7
> > >
> > >  doc/usage/cmd/eficonfig.rst | 63 +++++++++++++++++++++++++++++++++++++
> > >  doc/usage/index.rst         |  1 +
> > >  2 files changed, 64 insertions(+)
> > >  create mode 100644 doc/usage/cmd/eficonfig.rst
> > >
> > > diff --git a/doc/usage/cmd/eficonfig.rst b/doc/usage/cmd/eficonfig.rst
> > > new file mode 100644
> > > index 0000000000..958e96992c
> > > --- /dev/null
> > > +++ b/doc/usage/cmd/eficonfig.rst
> > > @@ -0,0 +1,63 @@
> > > +.. SPDX-License-Identifier: GPL-2.0+
> > > +.. (C) Copyright 2022, Masahisa Kojima <masahisa.kojima@linaro.org>
> > > +
> > > +eficonfig command
> > > +=================
> > > +
> > > +Synopsis
> > > +--------
> > > +::
> > > +
> > > +    eficonfig
> > > +
> > > +Description
> > > +-----------
> > > +
> > > +The "eficonfig" command uses U-Boot menu interface and privides
> > > +a menu-driven UEFI variable maintenance feature.
> > > +The "eficonfig" has the following menu entries.
> > > +
> > > +Add Boot Option
> > > +    Add new UEFI Boot Option.
> > > +    User can edit description, file path, and optional_data.
> > > +
> > > +Edit Boot Option
> > > +    Edit the existing UEFI Boot Option
> > > +    User can edit description, file path, and optional_data.
> > > +
> > > +Change Boot Order
> > > +    Change the order of UEFI BootOrder variable.
> > > +
> > > +Delete Boot Option
> > > +    Delete the UEFI Boot Option
> > > +
> > > +Configuration
> > > +-------------
> > > +
> > > +The "eficonfig" command is enabled by::
> > > +
> > > +    CONFIG_CMD_EFICONFIG=y
> > > +
> > > +If CONFIG_BOOTMENU_DISABLE_UBOOT_CONSOLE is enabled, user can not enter
> > > +U-Boot console. In this case, bootmenu can be used to invoke "eficonfig"::
> > > +
> > > +    CONFIG_USE_PREBOOT=y
> > > +    CONFIG_PREBOOT="setenv bootmenu_0 UEFI Maintenance Menu=eficonfig"
> > > +
> > > +How to boot the system with newly added UEFI Boot Option
> > > +''''''''''''''''''''''''''''''''''''''''''''''''''''''''
> > > +
> > > +"eficonfig" command is responsible to configure the UEFI variables,
> > > +not directly handle the system boot.
> > > +The new Boot Option added by "eficonfig" is appended at the last entry
> > > +of UEFI BootOrder variable, user may want to change the boot order
> > > +through "Change Boot Order".
> > > +If the bootmenu is enabled and "eficonfig" is configured as preboot command,
> > > +the newly added Boot Options are enumerated in the bootmenu when user exits
> > > +from the eficonfig menu.
> >
> > Right, and when user quits from eficonfig, bootmenu also quits.
> > *If possible*, I expect that we return to bootmenu's screen
> > directly for better user-experience :)
> >
> > Otherwise, we have to enable U-Boot console to re-invoke
> > bootmenu. (we can instead reboot U-Boot, though.)
> 
> If CONFIG_BOOTMENU_DISABLE_UBOOT_CONSOLE is enabled, bootmenu appears
> again when user quits from eficonfig.

Ah, okay but why not always enable this behavior on?

-Takahiro Akashi

> I need to describe this condition in the document.
> 
> Thanks,
> Masahisa Kojima
> 
> >
> > -Takahiro Akashi
> >
> > > +User may select the entry in the bootmenu to boot the system, or follow
> > > +the U-Boot configuration the system already has.
> > > +
> > > +See also
> > > +--------
> > > +* :doc:`bootmenu<bootmenu>` provides a simple mechanism for creating menus with different boot items
> > > diff --git a/doc/usage/index.rst b/doc/usage/index.rst
> > > index 28f9683a3e..09f2928970 100644
> > > --- a/doc/usage/index.rst
> > > +++ b/doc/usage/index.rst
> > > @@ -35,6 +35,7 @@ Shell commands
> > >     cmd/conitrace
> > >     cmd/dm
> > >     cmd/echo
> > > +   cmd/eficonfig
> > >     cmd/env
> > >     cmd/event
> > >     cmd/exception
> > > --
> > > 2.17.1
> > >

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH v11 8/9] doc:eficonfig: add documentation for eficonfig command
  2022-08-19  6:34       ` Takahiro Akashi
@ 2022-08-19  7:32         ` Masahisa Kojima
  0 siblings, 0 replies; 25+ messages in thread
From: Masahisa Kojima @ 2022-08-19  7:32 UTC (permalink / raw)
  To: Takahiro Akashi, Masahisa Kojima, u-boot, Heinrich Schuchardt,
	Ilias Apalodimas, Simon Glass, Mark Kettenis, Bin Meng,
	Marek Beh??n

On Fri, 19 Aug 2022 at 15:34, Takahiro Akashi
<takahiro.akashi@linaro.org> wrote:
>
> On Fri, Aug 19, 2022 at 03:15:09PM +0900, Masahisa Kojima wrote:
> > Hi Akashi-san,
> >
> > On Fri, 19 Aug 2022 at 14:57, Takahiro Akashi
> > <takahiro.akashi@linaro.org> wrote:
> > >
> > > On Wed, Aug 17, 2022 at 06:36:13PM +0900, Masahisa Kojima wrote:
> > > > Add documentation for eficonfig command.
> > > >
> > > > Signed-off-by: Masahisa Kojima <masahisa.kojima@linaro.org>
> > > > ---
> > > > No update since v10
> > > >
> > > > Changes in v10:
> > > > - describe how to boot system after editting by eficonfig
> > > >
> > > > Changes in v8:
> > > > - command name is changed from "efimenu" to "eficonfig"
> > > >
> > > > Newly created in v7
> > > >
> > > >  doc/usage/cmd/eficonfig.rst | 63 +++++++++++++++++++++++++++++++++++++
> > > >  doc/usage/index.rst         |  1 +
> > > >  2 files changed, 64 insertions(+)
> > > >  create mode 100644 doc/usage/cmd/eficonfig.rst
> > > >
> > > > diff --git a/doc/usage/cmd/eficonfig.rst b/doc/usage/cmd/eficonfig.rst
> > > > new file mode 100644
> > > > index 0000000000..958e96992c
> > > > --- /dev/null
> > > > +++ b/doc/usage/cmd/eficonfig.rst
> > > > @@ -0,0 +1,63 @@
> > > > +.. SPDX-License-Identifier: GPL-2.0+
> > > > +.. (C) Copyright 2022, Masahisa Kojima <masahisa.kojima@linaro.org>
> > > > +
> > > > +eficonfig command
> > > > +=================
> > > > +
> > > > +Synopsis
> > > > +--------
> > > > +::
> > > > +
> > > > +    eficonfig
> > > > +
> > > > +Description
> > > > +-----------
> > > > +
> > > > +The "eficonfig" command uses U-Boot menu interface and privides
> > > > +a menu-driven UEFI variable maintenance feature.
> > > > +The "eficonfig" has the following menu entries.
> > > > +
> > > > +Add Boot Option
> > > > +    Add new UEFI Boot Option.
> > > > +    User can edit description, file path, and optional_data.
> > > > +
> > > > +Edit Boot Option
> > > > +    Edit the existing UEFI Boot Option
> > > > +    User can edit description, file path, and optional_data.
> > > > +
> > > > +Change Boot Order
> > > > +    Change the order of UEFI BootOrder variable.
> > > > +
> > > > +Delete Boot Option
> > > > +    Delete the UEFI Boot Option
> > > > +
> > > > +Configuration
> > > > +-------------
> > > > +
> > > > +The "eficonfig" command is enabled by::
> > > > +
> > > > +    CONFIG_CMD_EFICONFIG=y
> > > > +
> > > > +If CONFIG_BOOTMENU_DISABLE_UBOOT_CONSOLE is enabled, user can not enter
> > > > +U-Boot console. In this case, bootmenu can be used to invoke "eficonfig"::
> > > > +
> > > > +    CONFIG_USE_PREBOOT=y
> > > > +    CONFIG_PREBOOT="setenv bootmenu_0 UEFI Maintenance Menu=eficonfig"
> > > > +
> > > > +How to boot the system with newly added UEFI Boot Option
> > > > +''''''''''''''''''''''''''''''''''''''''''''''''''''''''
> > > > +
> > > > +"eficonfig" command is responsible to configure the UEFI variables,
> > > > +not directly handle the system boot.
> > > > +The new Boot Option added by "eficonfig" is appended at the last entry
> > > > +of UEFI BootOrder variable, user may want to change the boot order
> > > > +through "Change Boot Order".
> > > > +If the bootmenu is enabled and "eficonfig" is configured as preboot command,
> > > > +the newly added Boot Options are enumerated in the bootmenu when user exits
> > > > +from the eficonfig menu.
> > >
> > > Right, and when user quits from eficonfig, bootmenu also quits.
> > > *If possible*, I expect that we return to bootmenu's screen
> > > directly for better user-experience :)
> > >
> > > Otherwise, we have to enable U-Boot console to re-invoke
> > > bootmenu. (we can instead reboot U-Boot, though.)
> >
> > If CONFIG_BOOTMENU_DISABLE_UBOOT_CONSOLE is enabled, bootmenu appears
> > again when user quits from eficonfig.
>
> Ah, okay but why not always enable this behavior on?

Current behavior is bootmenu default behavior and I don't want
to change the existing bootmenu user experience.

Thanks,
Masahisa Kojima

>
> -Takahiro Akashi
>
> > I need to describe this condition in the document.
> >
> > Thanks,
> > Masahisa Kojima
> >
> > >
> > > -Takahiro Akashi
> > >
> > > > +User may select the entry in the bootmenu to boot the system, or follow
> > > > +the U-Boot configuration the system already has.
> > > > +
> > > > +See also
> > > > +--------
> > > > +* :doc:`bootmenu<bootmenu>` provides a simple mechanism for creating menus with different boot items
> > > > diff --git a/doc/usage/index.rst b/doc/usage/index.rst
> > > > index 28f9683a3e..09f2928970 100644
> > > > --- a/doc/usage/index.rst
> > > > +++ b/doc/usage/index.rst
> > > > @@ -35,6 +35,7 @@ Shell commands
> > > >     cmd/conitrace
> > > >     cmd/dm
> > > >     cmd/echo
> > > > +   cmd/eficonfig
> > > >     cmd/env
> > > >     cmd/event
> > > >     cmd/exception
> > > > --
> > > > 2.17.1
> > > >

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH v11 6/9] bootmenu: add removable media entries
  2022-08-19  3:05     ` Masahisa Kojima
@ 2022-08-24  1:57       ` Takahiro Akashi
  2022-08-24  4:29         ` Masahisa Kojima
  0 siblings, 1 reply; 25+ messages in thread
From: Takahiro Akashi @ 2022-08-24  1:57 UTC (permalink / raw)
  To: Masahisa Kojima
  Cc: u-boot, Heinrich Schuchardt, Ilias Apalodimas, Simon Glass,
	Mark Kettenis

On Fri, Aug 19, 2022 at 12:05:50PM +0900, Masahisa Kojima wrote:
> Hi Akashi-san,
> 
> On Fri, 19 Aug 2022 at 10:31, Takahiro Akashi
> <takahiro.akashi@linaro.org> wrote:
> >
> > On Wed, Aug 17, 2022 at 06:36:11PM +0900, Masahisa Kojima wrote:
> > > UEFI specification requires booting from removal media using
> > > a architecture-specific default image name such as BOOTAA64.EFI.
> > > This commit adds the removable media entries into bootmenu,
> > > so that user can select the removable media and boot with
> > > default image.
> > >
> > > The bootmenu automatically enumerates the possible bootable
> > > media devices supporting EFI_SIMPLE_FILE_SYSTEM_PROTOCOL,
> > > add it as new UEFI boot option(BOOT####) and update BootOrder
> > > variable. This automatically generated UEFI boot option
> >
> > Should this feature belong to bootmenu command?
> > Under the current implementation, those boot options are
> > generated only by bootmenu, and so if eficonfig is invoked
> > prior to bootmenu, we won't see them (under "Change Boot Order").
> >
> > I expect that the functionality be also provided in eficonfig
> > (or even as part of system initialization?).
> 
> OK, generating the (removable) media boot options will be added
> in "Change Boot Order".

I found another wrong behavior. What I did was
- eficonfig
  it shows no boot options.
- scsi rescan
  One disk with two partitions was detected.
- eficonfig
  Now it shows two options for removal media.
  I disabled one of two partitions from BootOrder.
- bootmenu
  It still shows both boot options. -> Probably okay?
- eficonfig
  Then a duplicated option comes up:
  ** Change Boot Order **

        [*]  scsi 0:1
        [*]  scsi 0:2
        [ ]  scsi 0:2
        Save
        Quit

Internally there exist three boot options now.

-Takahiro Akashi

> Thanks,
> Masahisa Kojima
> 
> >
> > -Takahiro Akashi
> >
> >
> > > has the dedicated guid in the optional_data to distinguish it from
> > > the UEFI boot option user adds manually. This optional_data is
> > > removed when the efi bootmgr loads the selected UEFI boot option.
> > >
> > > This commit also provides the BOOT#### variable maintenance feature.
> > > Depending on the system hardware setup, some devices
> > > may not exist at a later system boot, so bootmenu checks the
> > > available device in each bootmenu invocation and automatically
> > > removes the BOOT#### variable corrensponding to the non-existent
> > > media device.
> > >
> > > Signed-off-by: Masahisa Kojima <masahisa.kojima@linaro.org>
> > > ---
> > > Changes in v11:
> > > - update delete_boot_option() parameter
> > >
> > > Changes in v10:
> > > - add function comment
> > > - devname dynamic allocation removes, allocate in stack
> > > - delete BOOT#### when updating BootOrder fails
> > >
> > > Changes in v9:
> > > - update efi_disk_get_device_name() parameter to pass efi_handle_t
> > > - add function comment
> > >
> > > Changes in v8:
> > > - function and structure prefix is changed to "eficonfig"
> > >
> > > Changes in v7:
> > > - rename prepare_media_device_entry() to generate_media_device_boot_option()
> > >
> > > Changes in v6:
> > > - optional_data size is changed to 16bytes
> > > - check the load option size before comparison
> > > - remove guid included in optional_data of auto generated
> > >   entry when loading
> > >
> > > Changes in v5:
> > > - Return EFI_SUCCESS if there is no BootOrder defined
> > > - correctly handle the case if no removable device found
> > > - use guid to identify the automatically generated entry by bootmenu
> > >
> > >  cmd/bootmenu.c               | 106 +++++++++++++++++++++++++--
> > >  cmd/eficonfig.c              | 135 +++++++++++++++++++++++++++++++++++
> > >  include/efi_loader.h         |  20 ++++++
> > >  lib/efi_loader/efi_bootmgr.c |   4 ++
> > >  4 files changed, 260 insertions(+), 5 deletions(-)
> > >
> > > diff --git a/cmd/bootmenu.c b/cmd/bootmenu.c
> > > index 704d36debe..04df41a0cb 100644
> > > --- a/cmd/bootmenu.c
> > > +++ b/cmd/bootmenu.c
> > > @@ -220,7 +220,93 @@ static int prepare_bootmenu_entry(struct bootmenu_data *menu,
> > >       return 1;
> > >  }
> > >
> > > -#if (CONFIG_IS_ENABLED(CMD_BOOTEFI_BOOTMGR))
> > > +#if (CONFIG_IS_ENABLED(CMD_BOOTEFI_BOOTMGR)) && (CONFIG_IS_ENABLED(CMD_EFICONFIG))
> > > +/**
> > > + * generate_media_device_boot_option() - generate the media device boot option
> > > + *
> > > + * This function enumerates all devices supporting EFI_SIMPLE_FILE_SYSTEM_PROTOCOL
> > > + * and generate the bootmenu entries.
> > > + * This function also provide the BOOT#### variable maintenance for
> > > + * the media device entries.
> > > + *   - Automatically create the BOOT#### variable for the newly detected device,
> > > + *     this BOOT#### variable is distinguished by the special GUID
> > > + *     stored in the EFI_LOAD_OPTION.optional_data
> > > + *   - If the device is not attached to the system, the associated BOOT#### variable
> > > + *     is automatically deleted.
> > > + *
> > > + * Return:   status code
> > > + */
> > > +static efi_status_t generate_media_device_boot_option(void)
> > > +{
> > > +     u32 i;
> > > +     efi_status_t ret;
> > > +     efi_uintn_t count;
> > > +     efi_handle_t *volume_handles = NULL;
> > > +     struct eficonfig_media_boot_option *opt = NULL;
> > > +
> > > +     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;
> > > +
> > > +     opt = calloc(count, sizeof(struct eficonfig_media_boot_option));
> > > +     if (!opt)
> > > +             goto out;
> > > +
> > > +     /* enumerate all devices supporting EFI_SIMPLE_FILE_SYSTEM_PROTOCOL */
> > > +     ret = eficonfig_enumerate_boot_option(opt, volume_handles, count);
> > > +     if (ret != EFI_SUCCESS)
> > > +             goto out;
> > > +
> > > +     /*
> > > +      * System hardware configuration may vary depending on the user setup.
> > > +      * The boot option is automatically added by the bootmenu.
> > > +      * If the device is not attached to the system, the boot option needs
> > > +      * to be deleted.
> > > +      */
> > > +     ret = eficonfig_delete_invalid_boot_option(opt, count);
> > > +     if (ret != EFI_SUCCESS)
> > > +             goto out;
> > > +
> > > +     /* add non-existent boot option */
> > > +     for (i = 0; i < count; i++) {
> > > +             u32 boot_index;
> > > +             u16 var_name[9];
> > > +
> > > +             if (!opt[i].exist) {
> > > +                     ret = eficonfig_get_unused_bootoption(var_name, sizeof(var_name),
> > > +                                                           &boot_index);
> > > +                     if (ret != EFI_SUCCESS)
> > > +                             goto out;
> > > +
> > > +                     ret = efi_set_variable_int(var_name, &efi_global_variable_guid,
> > > +                                                EFI_VARIABLE_NON_VOLATILE |
> > > +                                                EFI_VARIABLE_BOOTSERVICE_ACCESS |
> > > +                                                EFI_VARIABLE_RUNTIME_ACCESS,
> > > +                                                opt[i].size, opt[i].lo, false);
> > > +                     if (ret != EFI_SUCCESS)
> > > +                             goto out;
> > > +
> > > +                     ret = eficonfig_append_bootorder(boot_index);
> > > +                     if (ret != EFI_SUCCESS) {
> > > +                             efi_set_variable_int(var_name, &efi_global_variable_guid,
> > > +                                                  0, 0, NULL, false);
> > > +                             goto out;
> > > +                     }
> > > +             }
> > > +     }
> > > +
> > > +out:
> > > +     if (opt) {
> > > +             for (i = 0; i < count; i++)
> > > +                     free(opt[i].lo);
> > > +     }
> > > +     free(opt);
> > > +     efi_free_pool(volume_handles);
> > > +
> > > +     return ret;
> > > +}
> > > +
> > >  /**
> > >   * prepare_uefi_bootorder_entry() - generate the uefi bootmenu entries
> > >   *
> > > @@ -340,11 +426,21 @@ static struct bootmenu_data *bootmenu_create(int delay)
> > >       if (ret < 0)
> > >               goto cleanup;
> > >
> > > -#if (CONFIG_IS_ENABLED(CMD_BOOTEFI_BOOTMGR))
> > > +#if (CONFIG_IS_ENABLED(CMD_BOOTEFI_BOOTMGR)) && (CONFIG_IS_ENABLED(CMD_EFICONFIG))
> > >       if (i < MAX_COUNT - 1) {
> > > -                     ret = prepare_uefi_bootorder_entry(menu, &iter, &i);
> > > -                     if (ret < 0 && ret != -ENOENT)
> > > -                             goto cleanup;
> > > +             efi_status_t efi_ret;
> > > +
> > > +             /*
> > > +              * UEFI specification requires booting from removal media using
> > > +              * a architecture-specific default image name such as BOOTAA64.EFI.
> > > +              */
> > > +             efi_ret = generate_media_device_boot_option();
> > > +             if (efi_ret != EFI_SUCCESS && efi_ret != EFI_NOT_FOUND)
> > > +                     goto cleanup;
> > > +
> > > +             ret = prepare_uefi_bootorder_entry(menu, &iter, &i);
> > > +             if (ret < 0 && ret != -ENOENT)
> > > +                     goto cleanup;
> > >       }
> > >  #endif
> > >
> > > diff --git a/cmd/eficonfig.c b/cmd/eficonfig.c
> > > index 6e39c0cd4d..c7f55c62fb 100644
> > > --- a/cmd/eficonfig.c
> > > +++ b/cmd/eficonfig.c
> > > @@ -2080,6 +2080,141 @@ static efi_status_t eficonfig_process_delete_boot_option(void *data)
> > >       return ret;
> > >  }
> > >
> > > +/**
> > > + * eficonfig_enumerate_boot_option() - enumerate the possible bootable media
> > > + *
> > > + * @opt:             pointer to the media boot option structure
> > > + * @volume_handles:  pointer to the efi handles
> > > + * @count:           number of efi handle
> > > + * Return:           status code
> > > + */
> > > +efi_status_t eficonfig_enumerate_boot_option(struct eficonfig_media_boot_option *opt,
> > > +                                          efi_handle_t *volume_handles, efi_status_t count)
> > > +{
> > > +     u32 i;
> > > +     struct efi_handler *handler;
> > > +     efi_status_t ret = EFI_SUCCESS;
> > > +
> > > +     for (i = 0; i < count; i++) {
> > > +             u16 *p;
> > > +             u16 dev_name[BOOTMENU_DEVICE_NAME_MAX];
> > > +             char *optional_data;
> > > +             struct efi_load_option lo;
> > > +             char buf[BOOTMENU_DEVICE_NAME_MAX];
> > > +             struct efi_device_path *device_path;
> > > +
> > > +             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_disk_get_device_name(volume_handles[i], buf, BOOTMENU_DEVICE_NAME_MAX);
> > > +             if (ret != EFI_SUCCESS)
> > > +                     continue;
> > > +
> > > +             p = dev_name;
> > > +             utf8_utf16_strncpy(&p, buf, strlen(buf));
> > > +
> > > +             lo.label = dev_name;
> > > +             lo.attributes = LOAD_OPTION_ACTIVE;
> > > +             lo.file_path = device_path;
> > > +             lo.file_path_length = efi_dp_size(device_path) + sizeof(END);
> > > +             /*
> > > +              * Set the dedicated guid to optional_data, it is used to identify
> > > +              * the boot option that automatically generated by the bootmenu.
> > > +              * efi_serialize_load_option() expects optional_data is null-terminated
> > > +              * utf8 string, so set the "1234567" string to allocate enough space
> > > +              * to store guid, instead of realloc the load_option.
> > > +              */
> > > +             lo.optional_data = "1234567";
> > > +             opt[i].size = efi_serialize_load_option(&lo, (u8 **)&opt[i].lo);
> > > +             if (!opt[i].size) {
> > > +                     ret = EFI_OUT_OF_RESOURCES;
> > > +                     free(dev_name);
> > > +                     goto out;
> > > +             }
> > > +             /* set the guid */
> > > +             optional_data = (char *)opt[i].lo + (opt[i].size - u16_strsize(u"1234567"));
> > > +             memcpy(optional_data, &efi_guid_bootmenu_auto_generated, sizeof(efi_guid_t));
> > > +     }
> > > +
> > > +out:
> > > +     return ret;
> > > +}
> > > +
> > > +/**
> > > + * eficonfig_delete_invalid_boot_option() - delete non-existing boot option
> > > + *
> > > + * @opt:             pointer to the media boot option structure
> > > + * @count:           number of media boot option structure
> > > + * Return:           status code
> > > + */
> > > +efi_status_t eficonfig_delete_invalid_boot_option(struct eficonfig_media_boot_option *opt,
> > > +                                               efi_status_t count)
> > > +{
> > > +     u16 *bootorder;
> > > +     u32 i, j;
> > > +     efi_status_t ret;
> > > +     efi_uintn_t num, size, bootorder_size;
> > > +     void *load_option;
> > > +     struct efi_load_option lo;
> > > +     u16 varname[] = u"Boot####";
> > > +
> > > +     bootorder = efi_get_var(u"BootOrder", &efi_global_variable_guid, &bootorder_size);
> > > +     if (!bootorder)
> > > +             return EFI_SUCCESS; /* BootOrder is not defined, nothing to do */
> > > +
> > > +     num = bootorder_size / sizeof(u16);
> > > +     for (i = 0; i < num;) {
> > > +             efi_uintn_t tmp;
> > > +
> > > +             efi_create_indexed_name(varname, sizeof(varname),
> > > +                                     "Boot", bootorder[i]);
> > > +             load_option = efi_get_var(varname, &efi_global_variable_guid, &size);
> > > +             if (!load_option)
> > > +                     goto next;
> > > +
> > > +             tmp = size;
> > > +             ret = efi_deserialize_load_option(&lo, load_option, &size);
> > > +             if (ret != EFI_SUCCESS)
> > > +                     goto next;
> > > +
> > > +             if (size >= sizeof(efi_guid_bootmenu_auto_generated)) {
> > > +                     if (guidcmp(lo.optional_data, &efi_guid_bootmenu_auto_generated) == 0) {
> > > +                             for (j = 0; j < count; j++) {
> > > +                                     if (opt[j].size == tmp &&
> > > +                                         memcmp(opt[j].lo, load_option, tmp) == 0) {
> > > +                                             opt[j].exist = true;
> > > +                                             break;
> > > +                                     }
> > > +                             }
> > > +
> > > +                             if (j == count) {
> > > +                                     ret = delete_boot_option(bootorder[i]);
> > > +                                     if (ret != EFI_SUCCESS) {
> > > +                                             free(load_option);
> > > +                                             goto out;
> > > +                                     }
> > > +
> > > +                                     num--;
> > > +                                     bootorder_size -= sizeof(u16);
> > > +                                     free(load_option);
> > > +                                     continue;
> > > +                             }
> > > +                     }
> > > +             }
> > > +next:
> > > +             free(load_option);
> > > +             i++;
> > > +     }
> > > +
> > > +out:
> > > +     return ret;
> > > +}
> > > +
> > >  /**
> > >   * eficonfig_init() - do required initialization for eficonfig command
> > >   *
> > > diff --git a/include/efi_loader.h b/include/efi_loader.h
> > > index 49e7d1e613..a5a0448fa0 100644
> > > --- a/include/efi_loader.h
> > > +++ b/include/efi_loader.h
> > > @@ -955,6 +955,22 @@ struct efi_signature_store {
> > >  struct x509_certificate;
> > >  struct pkcs7_message;
> > >
> > > +/**
> > > + * struct eficonfig_media_boot_option - boot option for (removable) media device
> > > + *
> > > + * This structure is used to enumerate possible boot option
> > > + *
> > > + * @lo:              Serialized load option data
> > > + * @size:    Size of serialized load option data
> > > + * @exist:   Flag to indicate the load option already exists
> > > + *           in Non-volatile load option
> > > + */
> > > +struct eficonfig_media_boot_option {
> > > +     void *lo;
> > > +     efi_uintn_t size;
> > > +     bool exist;
> > > +};
> > > +
> > >  bool efi_hash_regions(struct image_region *regs, int count,
> > >                     void **hash, const char *hash_algo, int *len);
> > >  bool efi_signature_lookup_digest(struct efi_image_regions *regs,
> > > @@ -1104,6 +1120,10 @@ efi_status_t efi_console_get_u16_string
> > >  efi_status_t eficonfig_get_unused_bootoption(u16 *buf,
> > >                                            efi_uintn_t buf_size, u32 *index);
> > >  efi_status_t eficonfig_append_bootorder(u16 index);
> > > +efi_status_t eficonfig_enumerate_boot_option(struct eficonfig_media_boot_option *opt,
> > > +                                          efi_handle_t *volume_handles, efi_status_t count);
> > > +efi_status_t eficonfig_delete_invalid_boot_option(struct eficonfig_media_boot_option *opt,
> > > +                                               efi_status_t count);
> > >
> > >  efi_status_t efi_disk_get_device_name(const efi_handle_t handle, char *buf, int size);
> > >
> > > diff --git a/lib/efi_loader/efi_bootmgr.c b/lib/efi_loader/efi_bootmgr.c
> > > index ede9116b3c..4b24b41047 100644
> > > --- a/lib/efi_loader/efi_bootmgr.c
> > > +++ b/lib/efi_loader/efi_bootmgr.c
> > > @@ -246,6 +246,10 @@ static efi_status_t try_load_entry(u16 n, efi_handle_t *handle,
> > >       }
> > >
> > >       /* Set load options */
> > > +     if (size >= sizeof(efi_guid_t) &&
> > > +         !guidcmp(lo.optional_data, &efi_guid_bootmenu_auto_generated))
> > > +             size = 0;
> > > +
> > >       if (size) {
> > >               *load_options = malloc(size);
> > >               if (!*load_options) {
> > > --
> > > 2.17.1
> > >

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH v11 6/9] bootmenu: add removable media entries
  2022-08-24  1:57       ` Takahiro Akashi
@ 2022-08-24  4:29         ` Masahisa Kojima
  2022-08-24  4:48           ` Masahisa Kojima
  0 siblings, 1 reply; 25+ messages in thread
From: Masahisa Kojima @ 2022-08-24  4:29 UTC (permalink / raw)
  To: Takahiro Akashi, Masahisa Kojima, u-boot, Heinrich Schuchardt,
	Ilias Apalodimas, Simon Glass, Mark Kettenis

Hi Akashi-san,

On Wed, 24 Aug 2022 at 10:57, Takahiro Akashi
<takahiro.akashi@linaro.org> wrote:
>
> On Fri, Aug 19, 2022 at 12:05:50PM +0900, Masahisa Kojima wrote:
> > Hi Akashi-san,
> >
> > On Fri, 19 Aug 2022 at 10:31, Takahiro Akashi
> > <takahiro.akashi@linaro.org> wrote:
> > >
> > > On Wed, Aug 17, 2022 at 06:36:11PM +0900, Masahisa Kojima wrote:
> > > > UEFI specification requires booting from removal media using
> > > > a architecture-specific default image name such as BOOTAA64.EFI.
> > > > This commit adds the removable media entries into bootmenu,
> > > > so that user can select the removable media and boot with
> > > > default image.
> > > >
> > > > The bootmenu automatically enumerates the possible bootable
> > > > media devices supporting EFI_SIMPLE_FILE_SYSTEM_PROTOCOL,
> > > > add it as new UEFI boot option(BOOT####) and update BootOrder
> > > > variable. This automatically generated UEFI boot option
> > >
> > > Should this feature belong to bootmenu command?
> > > Under the current implementation, those boot options are
> > > generated only by bootmenu, and so if eficonfig is invoked
> > > prior to bootmenu, we won't see them (under "Change Boot Order").
> > >
> > > I expect that the functionality be also provided in eficonfig
> > > (or even as part of system initialization?).
> >
> > OK, generating the (removable) media boot options will be added
> > in "Change Boot Order".
>
> I found another wrong behavior. What I did was
> - eficonfig
>   it shows no boot options.
> - scsi rescan
>   One disk with two partitions was detected.
> - eficonfig
>   Now it shows two options for removal media.
>   I disabled one of two partitions from BootOrder.
> - bootmenu
>   It still shows both boot options. -> Probably okay?
> - eficonfig
>   Then a duplicated option comes up:
>   ** Change Boot Order **
>
>         [*]  scsi 0:1
>         [*]  scsi 0:2
>         [ ]  scsi 0:2
>         Save
>         Quit
>
> Internally there exist three boot options now.

I have not checked this email before sending the v12 series.
I will confirm behavior.

Thanks,
Masahisa Kojima

>
> -Takahiro Akashi
>
> > Thanks,
> > Masahisa Kojima
> >
> > >
> > > -Takahiro Akashi
> > >
> > >
> > > > has the dedicated guid in the optional_data to distinguish it from
> > > > the UEFI boot option user adds manually. This optional_data is
> > > > removed when the efi bootmgr loads the selected UEFI boot option.
> > > >
> > > > This commit also provides the BOOT#### variable maintenance feature.
> > > > Depending on the system hardware setup, some devices
> > > > may not exist at a later system boot, so bootmenu checks the
> > > > available device in each bootmenu invocation and automatically
> > > > removes the BOOT#### variable corrensponding to the non-existent
> > > > media device.
> > > >
> > > > Signed-off-by: Masahisa Kojima <masahisa.kojima@linaro.org>
> > > > ---
> > > > Changes in v11:
> > > > - update delete_boot_option() parameter
> > > >
> > > > Changes in v10:
> > > > - add function comment
> > > > - devname dynamic allocation removes, allocate in stack
> > > > - delete BOOT#### when updating BootOrder fails
> > > >
> > > > Changes in v9:
> > > > - update efi_disk_get_device_name() parameter to pass efi_handle_t
> > > > - add function comment
> > > >
> > > > Changes in v8:
> > > > - function and structure prefix is changed to "eficonfig"
> > > >
> > > > Changes in v7:
> > > > - rename prepare_media_device_entry() to generate_media_device_boot_option()
> > > >
> > > > Changes in v6:
> > > > - optional_data size is changed to 16bytes
> > > > - check the load option size before comparison
> > > > - remove guid included in optional_data of auto generated
> > > >   entry when loading
> > > >
> > > > Changes in v5:
> > > > - Return EFI_SUCCESS if there is no BootOrder defined
> > > > - correctly handle the case if no removable device found
> > > > - use guid to identify the automatically generated entry by bootmenu
> > > >
> > > >  cmd/bootmenu.c               | 106 +++++++++++++++++++++++++--
> > > >  cmd/eficonfig.c              | 135 +++++++++++++++++++++++++++++++++++
> > > >  include/efi_loader.h         |  20 ++++++
> > > >  lib/efi_loader/efi_bootmgr.c |   4 ++
> > > >  4 files changed, 260 insertions(+), 5 deletions(-)
> > > >
> > > > diff --git a/cmd/bootmenu.c b/cmd/bootmenu.c
> > > > index 704d36debe..04df41a0cb 100644
> > > > --- a/cmd/bootmenu.c
> > > > +++ b/cmd/bootmenu.c
> > > > @@ -220,7 +220,93 @@ static int prepare_bootmenu_entry(struct bootmenu_data *menu,
> > > >       return 1;
> > > >  }
> > > >
> > > > -#if (CONFIG_IS_ENABLED(CMD_BOOTEFI_BOOTMGR))
> > > > +#if (CONFIG_IS_ENABLED(CMD_BOOTEFI_BOOTMGR)) && (CONFIG_IS_ENABLED(CMD_EFICONFIG))
> > > > +/**
> > > > + * generate_media_device_boot_option() - generate the media device boot option
> > > > + *
> > > > + * This function enumerates all devices supporting EFI_SIMPLE_FILE_SYSTEM_PROTOCOL
> > > > + * and generate the bootmenu entries.
> > > > + * This function also provide the BOOT#### variable maintenance for
> > > > + * the media device entries.
> > > > + *   - Automatically create the BOOT#### variable for the newly detected device,
> > > > + *     this BOOT#### variable is distinguished by the special GUID
> > > > + *     stored in the EFI_LOAD_OPTION.optional_data
> > > > + *   - If the device is not attached to the system, the associated BOOT#### variable
> > > > + *     is automatically deleted.
> > > > + *
> > > > + * Return:   status code
> > > > + */
> > > > +static efi_status_t generate_media_device_boot_option(void)
> > > > +{
> > > > +     u32 i;
> > > > +     efi_status_t ret;
> > > > +     efi_uintn_t count;
> > > > +     efi_handle_t *volume_handles = NULL;
> > > > +     struct eficonfig_media_boot_option *opt = NULL;
> > > > +
> > > > +     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;
> > > > +
> > > > +     opt = calloc(count, sizeof(struct eficonfig_media_boot_option));
> > > > +     if (!opt)
> > > > +             goto out;
> > > > +
> > > > +     /* enumerate all devices supporting EFI_SIMPLE_FILE_SYSTEM_PROTOCOL */
> > > > +     ret = eficonfig_enumerate_boot_option(opt, volume_handles, count);
> > > > +     if (ret != EFI_SUCCESS)
> > > > +             goto out;
> > > > +
> > > > +     /*
> > > > +      * System hardware configuration may vary depending on the user setup.
> > > > +      * The boot option is automatically added by the bootmenu.
> > > > +      * If the device is not attached to the system, the boot option needs
> > > > +      * to be deleted.
> > > > +      */
> > > > +     ret = eficonfig_delete_invalid_boot_option(opt, count);
> > > > +     if (ret != EFI_SUCCESS)
> > > > +             goto out;
> > > > +
> > > > +     /* add non-existent boot option */
> > > > +     for (i = 0; i < count; i++) {
> > > > +             u32 boot_index;
> > > > +             u16 var_name[9];
> > > > +
> > > > +             if (!opt[i].exist) {
> > > > +                     ret = eficonfig_get_unused_bootoption(var_name, sizeof(var_name),
> > > > +                                                           &boot_index);
> > > > +                     if (ret != EFI_SUCCESS)
> > > > +                             goto out;
> > > > +
> > > > +                     ret = efi_set_variable_int(var_name, &efi_global_variable_guid,
> > > > +                                                EFI_VARIABLE_NON_VOLATILE |
> > > > +                                                EFI_VARIABLE_BOOTSERVICE_ACCESS |
> > > > +                                                EFI_VARIABLE_RUNTIME_ACCESS,
> > > > +                                                opt[i].size, opt[i].lo, false);
> > > > +                     if (ret != EFI_SUCCESS)
> > > > +                             goto out;
> > > > +
> > > > +                     ret = eficonfig_append_bootorder(boot_index);
> > > > +                     if (ret != EFI_SUCCESS) {
> > > > +                             efi_set_variable_int(var_name, &efi_global_variable_guid,
> > > > +                                                  0, 0, NULL, false);
> > > > +                             goto out;
> > > > +                     }
> > > > +             }
> > > > +     }
> > > > +
> > > > +out:
> > > > +     if (opt) {
> > > > +             for (i = 0; i < count; i++)
> > > > +                     free(opt[i].lo);
> > > > +     }
> > > > +     free(opt);
> > > > +     efi_free_pool(volume_handles);
> > > > +
> > > > +     return ret;
> > > > +}
> > > > +
> > > >  /**
> > > >   * prepare_uefi_bootorder_entry() - generate the uefi bootmenu entries
> > > >   *
> > > > @@ -340,11 +426,21 @@ static struct bootmenu_data *bootmenu_create(int delay)
> > > >       if (ret < 0)
> > > >               goto cleanup;
> > > >
> > > > -#if (CONFIG_IS_ENABLED(CMD_BOOTEFI_BOOTMGR))
> > > > +#if (CONFIG_IS_ENABLED(CMD_BOOTEFI_BOOTMGR)) && (CONFIG_IS_ENABLED(CMD_EFICONFIG))
> > > >       if (i < MAX_COUNT - 1) {
> > > > -                     ret = prepare_uefi_bootorder_entry(menu, &iter, &i);
> > > > -                     if (ret < 0 && ret != -ENOENT)
> > > > -                             goto cleanup;
> > > > +             efi_status_t efi_ret;
> > > > +
> > > > +             /*
> > > > +              * UEFI specification requires booting from removal media using
> > > > +              * a architecture-specific default image name such as BOOTAA64.EFI.
> > > > +              */
> > > > +             efi_ret = generate_media_device_boot_option();
> > > > +             if (efi_ret != EFI_SUCCESS && efi_ret != EFI_NOT_FOUND)
> > > > +                     goto cleanup;
> > > > +
> > > > +             ret = prepare_uefi_bootorder_entry(menu, &iter, &i);
> > > > +             if (ret < 0 && ret != -ENOENT)
> > > > +                     goto cleanup;
> > > >       }
> > > >  #endif
> > > >
> > > > diff --git a/cmd/eficonfig.c b/cmd/eficonfig.c
> > > > index 6e39c0cd4d..c7f55c62fb 100644
> > > > --- a/cmd/eficonfig.c
> > > > +++ b/cmd/eficonfig.c
> > > > @@ -2080,6 +2080,141 @@ static efi_status_t eficonfig_process_delete_boot_option(void *data)
> > > >       return ret;
> > > >  }
> > > >
> > > > +/**
> > > > + * eficonfig_enumerate_boot_option() - enumerate the possible bootable media
> > > > + *
> > > > + * @opt:             pointer to the media boot option structure
> > > > + * @volume_handles:  pointer to the efi handles
> > > > + * @count:           number of efi handle
> > > > + * Return:           status code
> > > > + */
> > > > +efi_status_t eficonfig_enumerate_boot_option(struct eficonfig_media_boot_option *opt,
> > > > +                                          efi_handle_t *volume_handles, efi_status_t count)
> > > > +{
> > > > +     u32 i;
> > > > +     struct efi_handler *handler;
> > > > +     efi_status_t ret = EFI_SUCCESS;
> > > > +
> > > > +     for (i = 0; i < count; i++) {
> > > > +             u16 *p;
> > > > +             u16 dev_name[BOOTMENU_DEVICE_NAME_MAX];
> > > > +             char *optional_data;
> > > > +             struct efi_load_option lo;
> > > > +             char buf[BOOTMENU_DEVICE_NAME_MAX];
> > > > +             struct efi_device_path *device_path;
> > > > +
> > > > +             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_disk_get_device_name(volume_handles[i], buf, BOOTMENU_DEVICE_NAME_MAX);
> > > > +             if (ret != EFI_SUCCESS)
> > > > +                     continue;
> > > > +
> > > > +             p = dev_name;
> > > > +             utf8_utf16_strncpy(&p, buf, strlen(buf));
> > > > +
> > > > +             lo.label = dev_name;
> > > > +             lo.attributes = LOAD_OPTION_ACTIVE;
> > > > +             lo.file_path = device_path;
> > > > +             lo.file_path_length = efi_dp_size(device_path) + sizeof(END);
> > > > +             /*
> > > > +              * Set the dedicated guid to optional_data, it is used to identify
> > > > +              * the boot option that automatically generated by the bootmenu.
> > > > +              * efi_serialize_load_option() expects optional_data is null-terminated
> > > > +              * utf8 string, so set the "1234567" string to allocate enough space
> > > > +              * to store guid, instead of realloc the load_option.
> > > > +              */
> > > > +             lo.optional_data = "1234567";
> > > > +             opt[i].size = efi_serialize_load_option(&lo, (u8 **)&opt[i].lo);
> > > > +             if (!opt[i].size) {
> > > > +                     ret = EFI_OUT_OF_RESOURCES;
> > > > +                     free(dev_name);
> > > > +                     goto out;
> > > > +             }
> > > > +             /* set the guid */
> > > > +             optional_data = (char *)opt[i].lo + (opt[i].size - u16_strsize(u"1234567"));
> > > > +             memcpy(optional_data, &efi_guid_bootmenu_auto_generated, sizeof(efi_guid_t));
> > > > +     }
> > > > +
> > > > +out:
> > > > +     return ret;
> > > > +}
> > > > +
> > > > +/**
> > > > + * eficonfig_delete_invalid_boot_option() - delete non-existing boot option
> > > > + *
> > > > + * @opt:             pointer to the media boot option structure
> > > > + * @count:           number of media boot option structure
> > > > + * Return:           status code
> > > > + */
> > > > +efi_status_t eficonfig_delete_invalid_boot_option(struct eficonfig_media_boot_option *opt,
> > > > +                                               efi_status_t count)
> > > > +{
> > > > +     u16 *bootorder;
> > > > +     u32 i, j;
> > > > +     efi_status_t ret;
> > > > +     efi_uintn_t num, size, bootorder_size;
> > > > +     void *load_option;
> > > > +     struct efi_load_option lo;
> > > > +     u16 varname[] = u"Boot####";
> > > > +
> > > > +     bootorder = efi_get_var(u"BootOrder", &efi_global_variable_guid, &bootorder_size);
> > > > +     if (!bootorder)
> > > > +             return EFI_SUCCESS; /* BootOrder is not defined, nothing to do */
> > > > +
> > > > +     num = bootorder_size / sizeof(u16);
> > > > +     for (i = 0; i < num;) {
> > > > +             efi_uintn_t tmp;
> > > > +
> > > > +             efi_create_indexed_name(varname, sizeof(varname),
> > > > +                                     "Boot", bootorder[i]);
> > > > +             load_option = efi_get_var(varname, &efi_global_variable_guid, &size);
> > > > +             if (!load_option)
> > > > +                     goto next;
> > > > +
> > > > +             tmp = size;
> > > > +             ret = efi_deserialize_load_option(&lo, load_option, &size);
> > > > +             if (ret != EFI_SUCCESS)
> > > > +                     goto next;
> > > > +
> > > > +             if (size >= sizeof(efi_guid_bootmenu_auto_generated)) {
> > > > +                     if (guidcmp(lo.optional_data, &efi_guid_bootmenu_auto_generated) == 0) {
> > > > +                             for (j = 0; j < count; j++) {
> > > > +                                     if (opt[j].size == tmp &&
> > > > +                                         memcmp(opt[j].lo, load_option, tmp) == 0) {
> > > > +                                             opt[j].exist = true;
> > > > +                                             break;
> > > > +                                     }
> > > > +                             }
> > > > +
> > > > +                             if (j == count) {
> > > > +                                     ret = delete_boot_option(bootorder[i]);
> > > > +                                     if (ret != EFI_SUCCESS) {
> > > > +                                             free(load_option);
> > > > +                                             goto out;
> > > > +                                     }
> > > > +
> > > > +                                     num--;
> > > > +                                     bootorder_size -= sizeof(u16);
> > > > +                                     free(load_option);
> > > > +                                     continue;
> > > > +                             }
> > > > +                     }
> > > > +             }
> > > > +next:
> > > > +             free(load_option);
> > > > +             i++;
> > > > +     }
> > > > +
> > > > +out:
> > > > +     return ret;
> > > > +}
> > > > +
> > > >  /**
> > > >   * eficonfig_init() - do required initialization for eficonfig command
> > > >   *
> > > > diff --git a/include/efi_loader.h b/include/efi_loader.h
> > > > index 49e7d1e613..a5a0448fa0 100644
> > > > --- a/include/efi_loader.h
> > > > +++ b/include/efi_loader.h
> > > > @@ -955,6 +955,22 @@ struct efi_signature_store {
> > > >  struct x509_certificate;
> > > >  struct pkcs7_message;
> > > >
> > > > +/**
> > > > + * struct eficonfig_media_boot_option - boot option for (removable) media device
> > > > + *
> > > > + * This structure is used to enumerate possible boot option
> > > > + *
> > > > + * @lo:              Serialized load option data
> > > > + * @size:    Size of serialized load option data
> > > > + * @exist:   Flag to indicate the load option already exists
> > > > + *           in Non-volatile load option
> > > > + */
> > > > +struct eficonfig_media_boot_option {
> > > > +     void *lo;
> > > > +     efi_uintn_t size;
> > > > +     bool exist;
> > > > +};
> > > > +
> > > >  bool efi_hash_regions(struct image_region *regs, int count,
> > > >                     void **hash, const char *hash_algo, int *len);
> > > >  bool efi_signature_lookup_digest(struct efi_image_regions *regs,
> > > > @@ -1104,6 +1120,10 @@ efi_status_t efi_console_get_u16_string
> > > >  efi_status_t eficonfig_get_unused_bootoption(u16 *buf,
> > > >                                            efi_uintn_t buf_size, u32 *index);
> > > >  efi_status_t eficonfig_append_bootorder(u16 index);
> > > > +efi_status_t eficonfig_enumerate_boot_option(struct eficonfig_media_boot_option *opt,
> > > > +                                          efi_handle_t *volume_handles, efi_status_t count);
> > > > +efi_status_t eficonfig_delete_invalid_boot_option(struct eficonfig_media_boot_option *opt,
> > > > +                                               efi_status_t count);
> > > >
> > > >  efi_status_t efi_disk_get_device_name(const efi_handle_t handle, char *buf, int size);
> > > >
> > > > diff --git a/lib/efi_loader/efi_bootmgr.c b/lib/efi_loader/efi_bootmgr.c
> > > > index ede9116b3c..4b24b41047 100644
> > > > --- a/lib/efi_loader/efi_bootmgr.c
> > > > +++ b/lib/efi_loader/efi_bootmgr.c
> > > > @@ -246,6 +246,10 @@ static efi_status_t try_load_entry(u16 n, efi_handle_t *handle,
> > > >       }
> > > >
> > > >       /* Set load options */
> > > > +     if (size >= sizeof(efi_guid_t) &&
> > > > +         !guidcmp(lo.optional_data, &efi_guid_bootmenu_auto_generated))
> > > > +             size = 0;
> > > > +
> > > >       if (size) {
> > > >               *load_options = malloc(size);
> > > >               if (!*load_options) {
> > > > --
> > > > 2.17.1
> > > >

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH v11 6/9] bootmenu: add removable media entries
  2022-08-24  4:29         ` Masahisa Kojima
@ 2022-08-24  4:48           ` Masahisa Kojima
  0 siblings, 0 replies; 25+ messages in thread
From: Masahisa Kojima @ 2022-08-24  4:48 UTC (permalink / raw)
  To: Takahiro Akashi, Masahisa Kojima, u-boot, Heinrich Schuchardt,
	Ilias Apalodimas, Simon Glass, Mark Kettenis

On Wed, 24 Aug 2022 at 13:29, Masahisa Kojima
<masahisa.kojima@linaro.org> wrote:
>
> Hi Akashi-san,
>
> On Wed, 24 Aug 2022 at 10:57, Takahiro Akashi
> <takahiro.akashi@linaro.org> wrote:
> >
> > On Fri, Aug 19, 2022 at 12:05:50PM +0900, Masahisa Kojima wrote:
> > > Hi Akashi-san,
> > >
> > > On Fri, 19 Aug 2022 at 10:31, Takahiro Akashi
> > > <takahiro.akashi@linaro.org> wrote:
> > > >
> > > > On Wed, Aug 17, 2022 at 06:36:11PM +0900, Masahisa Kojima wrote:
> > > > > UEFI specification requires booting from removal media using
> > > > > a architecture-specific default image name such as BOOTAA64.EFI.
> > > > > This commit adds the removable media entries into bootmenu,
> > > > > so that user can select the removable media and boot with
> > > > > default image.
> > > > >
> > > > > The bootmenu automatically enumerates the possible bootable
> > > > > media devices supporting EFI_SIMPLE_FILE_SYSTEM_PROTOCOL,
> > > > > add it as new UEFI boot option(BOOT####) and update BootOrder
> > > > > variable. This automatically generated UEFI boot option
> > > >
> > > > Should this feature belong to bootmenu command?
> > > > Under the current implementation, those boot options are
> > > > generated only by bootmenu, and so if eficonfig is invoked
> > > > prior to bootmenu, we won't see them (under "Change Boot Order").
> > > >
> > > > I expect that the functionality be also provided in eficonfig
> > > > (or even as part of system initialization?).
> > >
> > > OK, generating the (removable) media boot options will be added
> > > in "Change Boot Order".
> >
> > I found another wrong behavior. What I did was
> > - eficonfig
> >   it shows no boot options.
> > - scsi rescan
> >   One disk with two partitions was detected.
> > - eficonfig
> >   Now it shows two options for removal media.
> >   I disabled one of two partitions from BootOrder.
> > - bootmenu
> >   It still shows both boot options. -> Probably okay?

No, disabled option must not be displayed.

> > - eficonfig
> >   Then a duplicated option comes up:
> >   ** Change Boot Order **
> >
> >         [*]  scsi 0:1
> >         [*]  scsi 0:2
> >         [ ]  scsi 0:2
> >         Save
> >         Quit
> >
> > Internally there exist three boot options now.
>
> I have not checked this email before sending the v12 series.
> I will confirm behavior.

I could reproduce the problem and fix it in the next version.

Thanks,
Masahisa Kojima

>
> Thanks,
> Masahisa Kojima
>
> >
> > -Takahiro Akashi
> >
> > > Thanks,
> > > Masahisa Kojima
> > >
> > > >
> > > > -Takahiro Akashi
> > > >
> > > >
> > > > > has the dedicated guid in the optional_data to distinguish it from
> > > > > the UEFI boot option user adds manually. This optional_data is
> > > > > removed when the efi bootmgr loads the selected UEFI boot option.
> > > > >
> > > > > This commit also provides the BOOT#### variable maintenance feature.
> > > > > Depending on the system hardware setup, some devices
> > > > > may not exist at a later system boot, so bootmenu checks the
> > > > > available device in each bootmenu invocation and automatically
> > > > > removes the BOOT#### variable corrensponding to the non-existent
> > > > > media device.
> > > > >
> > > > > Signed-off-by: Masahisa Kojima <masahisa.kojima@linaro.org>
> > > > > ---
> > > > > Changes in v11:
> > > > > - update delete_boot_option() parameter
> > > > >
> > > > > Changes in v10:
> > > > > - add function comment
> > > > > - devname dynamic allocation removes, allocate in stack
> > > > > - delete BOOT#### when updating BootOrder fails
> > > > >
> > > > > Changes in v9:
> > > > > - update efi_disk_get_device_name() parameter to pass efi_handle_t
> > > > > - add function comment
> > > > >
> > > > > Changes in v8:
> > > > > - function and structure prefix is changed to "eficonfig"
> > > > >
> > > > > Changes in v7:
> > > > > - rename prepare_media_device_entry() to generate_media_device_boot_option()
> > > > >
> > > > > Changes in v6:
> > > > > - optional_data size is changed to 16bytes
> > > > > - check the load option size before comparison
> > > > > - remove guid included in optional_data of auto generated
> > > > >   entry when loading
> > > > >
> > > > > Changes in v5:
> > > > > - Return EFI_SUCCESS if there is no BootOrder defined
> > > > > - correctly handle the case if no removable device found
> > > > > - use guid to identify the automatically generated entry by bootmenu
> > > > >
> > > > >  cmd/bootmenu.c               | 106 +++++++++++++++++++++++++--
> > > > >  cmd/eficonfig.c              | 135 +++++++++++++++++++++++++++++++++++
> > > > >  include/efi_loader.h         |  20 ++++++
> > > > >  lib/efi_loader/efi_bootmgr.c |   4 ++
> > > > >  4 files changed, 260 insertions(+), 5 deletions(-)
> > > > >
> > > > > diff --git a/cmd/bootmenu.c b/cmd/bootmenu.c
> > > > > index 704d36debe..04df41a0cb 100644
> > > > > --- a/cmd/bootmenu.c
> > > > > +++ b/cmd/bootmenu.c
> > > > > @@ -220,7 +220,93 @@ static int prepare_bootmenu_entry(struct bootmenu_data *menu,
> > > > >       return 1;
> > > > >  }
> > > > >
> > > > > -#if (CONFIG_IS_ENABLED(CMD_BOOTEFI_BOOTMGR))
> > > > > +#if (CONFIG_IS_ENABLED(CMD_BOOTEFI_BOOTMGR)) && (CONFIG_IS_ENABLED(CMD_EFICONFIG))
> > > > > +/**
> > > > > + * generate_media_device_boot_option() - generate the media device boot option
> > > > > + *
> > > > > + * This function enumerates all devices supporting EFI_SIMPLE_FILE_SYSTEM_PROTOCOL
> > > > > + * and generate the bootmenu entries.
> > > > > + * This function also provide the BOOT#### variable maintenance for
> > > > > + * the media device entries.
> > > > > + *   - Automatically create the BOOT#### variable for the newly detected device,
> > > > > + *     this BOOT#### variable is distinguished by the special GUID
> > > > > + *     stored in the EFI_LOAD_OPTION.optional_data
> > > > > + *   - If the device is not attached to the system, the associated BOOT#### variable
> > > > > + *     is automatically deleted.
> > > > > + *
> > > > > + * Return:   status code
> > > > > + */
> > > > > +static efi_status_t generate_media_device_boot_option(void)
> > > > > +{
> > > > > +     u32 i;
> > > > > +     efi_status_t ret;
> > > > > +     efi_uintn_t count;
> > > > > +     efi_handle_t *volume_handles = NULL;
> > > > > +     struct eficonfig_media_boot_option *opt = NULL;
> > > > > +
> > > > > +     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;
> > > > > +
> > > > > +     opt = calloc(count, sizeof(struct eficonfig_media_boot_option));
> > > > > +     if (!opt)
> > > > > +             goto out;
> > > > > +
> > > > > +     /* enumerate all devices supporting EFI_SIMPLE_FILE_SYSTEM_PROTOCOL */
> > > > > +     ret = eficonfig_enumerate_boot_option(opt, volume_handles, count);
> > > > > +     if (ret != EFI_SUCCESS)
> > > > > +             goto out;
> > > > > +
> > > > > +     /*
> > > > > +      * System hardware configuration may vary depending on the user setup.
> > > > > +      * The boot option is automatically added by the bootmenu.
> > > > > +      * If the device is not attached to the system, the boot option needs
> > > > > +      * to be deleted.
> > > > > +      */
> > > > > +     ret = eficonfig_delete_invalid_boot_option(opt, count);
> > > > > +     if (ret != EFI_SUCCESS)
> > > > > +             goto out;
> > > > > +
> > > > > +     /* add non-existent boot option */
> > > > > +     for (i = 0; i < count; i++) {
> > > > > +             u32 boot_index;
> > > > > +             u16 var_name[9];
> > > > > +
> > > > > +             if (!opt[i].exist) {
> > > > > +                     ret = eficonfig_get_unused_bootoption(var_name, sizeof(var_name),
> > > > > +                                                           &boot_index);
> > > > > +                     if (ret != EFI_SUCCESS)
> > > > > +                             goto out;
> > > > > +
> > > > > +                     ret = efi_set_variable_int(var_name, &efi_global_variable_guid,
> > > > > +                                                EFI_VARIABLE_NON_VOLATILE |
> > > > > +                                                EFI_VARIABLE_BOOTSERVICE_ACCESS |
> > > > > +                                                EFI_VARIABLE_RUNTIME_ACCESS,
> > > > > +                                                opt[i].size, opt[i].lo, false);
> > > > > +                     if (ret != EFI_SUCCESS)
> > > > > +                             goto out;
> > > > > +
> > > > > +                     ret = eficonfig_append_bootorder(boot_index);
> > > > > +                     if (ret != EFI_SUCCESS) {
> > > > > +                             efi_set_variable_int(var_name, &efi_global_variable_guid,
> > > > > +                                                  0, 0, NULL, false);
> > > > > +                             goto out;
> > > > > +                     }
> > > > > +             }
> > > > > +     }
> > > > > +
> > > > > +out:
> > > > > +     if (opt) {
> > > > > +             for (i = 0; i < count; i++)
> > > > > +                     free(opt[i].lo);
> > > > > +     }
> > > > > +     free(opt);
> > > > > +     efi_free_pool(volume_handles);
> > > > > +
> > > > > +     return ret;
> > > > > +}
> > > > > +
> > > > >  /**
> > > > >   * prepare_uefi_bootorder_entry() - generate the uefi bootmenu entries
> > > > >   *
> > > > > @@ -340,11 +426,21 @@ static struct bootmenu_data *bootmenu_create(int delay)
> > > > >       if (ret < 0)
> > > > >               goto cleanup;
> > > > >
> > > > > -#if (CONFIG_IS_ENABLED(CMD_BOOTEFI_BOOTMGR))
> > > > > +#if (CONFIG_IS_ENABLED(CMD_BOOTEFI_BOOTMGR)) && (CONFIG_IS_ENABLED(CMD_EFICONFIG))
> > > > >       if (i < MAX_COUNT - 1) {
> > > > > -                     ret = prepare_uefi_bootorder_entry(menu, &iter, &i);
> > > > > -                     if (ret < 0 && ret != -ENOENT)
> > > > > -                             goto cleanup;
> > > > > +             efi_status_t efi_ret;
> > > > > +
> > > > > +             /*
> > > > > +              * UEFI specification requires booting from removal media using
> > > > > +              * a architecture-specific default image name such as BOOTAA64.EFI.
> > > > > +              */
> > > > > +             efi_ret = generate_media_device_boot_option();
> > > > > +             if (efi_ret != EFI_SUCCESS && efi_ret != EFI_NOT_FOUND)
> > > > > +                     goto cleanup;
> > > > > +
> > > > > +             ret = prepare_uefi_bootorder_entry(menu, &iter, &i);
> > > > > +             if (ret < 0 && ret != -ENOENT)
> > > > > +                     goto cleanup;
> > > > >       }
> > > > >  #endif
> > > > >
> > > > > diff --git a/cmd/eficonfig.c b/cmd/eficonfig.c
> > > > > index 6e39c0cd4d..c7f55c62fb 100644
> > > > > --- a/cmd/eficonfig.c
> > > > > +++ b/cmd/eficonfig.c
> > > > > @@ -2080,6 +2080,141 @@ static efi_status_t eficonfig_process_delete_boot_option(void *data)
> > > > >       return ret;
> > > > >  }
> > > > >
> > > > > +/**
> > > > > + * eficonfig_enumerate_boot_option() - enumerate the possible bootable media
> > > > > + *
> > > > > + * @opt:             pointer to the media boot option structure
> > > > > + * @volume_handles:  pointer to the efi handles
> > > > > + * @count:           number of efi handle
> > > > > + * Return:           status code
> > > > > + */
> > > > > +efi_status_t eficonfig_enumerate_boot_option(struct eficonfig_media_boot_option *opt,
> > > > > +                                          efi_handle_t *volume_handles, efi_status_t count)
> > > > > +{
> > > > > +     u32 i;
> > > > > +     struct efi_handler *handler;
> > > > > +     efi_status_t ret = EFI_SUCCESS;
> > > > > +
> > > > > +     for (i = 0; i < count; i++) {
> > > > > +             u16 *p;
> > > > > +             u16 dev_name[BOOTMENU_DEVICE_NAME_MAX];
> > > > > +             char *optional_data;
> > > > > +             struct efi_load_option lo;
> > > > > +             char buf[BOOTMENU_DEVICE_NAME_MAX];
> > > > > +             struct efi_device_path *device_path;
> > > > > +
> > > > > +             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_disk_get_device_name(volume_handles[i], buf, BOOTMENU_DEVICE_NAME_MAX);
> > > > > +             if (ret != EFI_SUCCESS)
> > > > > +                     continue;
> > > > > +
> > > > > +             p = dev_name;
> > > > > +             utf8_utf16_strncpy(&p, buf, strlen(buf));
> > > > > +
> > > > > +             lo.label = dev_name;
> > > > > +             lo.attributes = LOAD_OPTION_ACTIVE;
> > > > > +             lo.file_path = device_path;
> > > > > +             lo.file_path_length = efi_dp_size(device_path) + sizeof(END);
> > > > > +             /*
> > > > > +              * Set the dedicated guid to optional_data, it is used to identify
> > > > > +              * the boot option that automatically generated by the bootmenu.
> > > > > +              * efi_serialize_load_option() expects optional_data is null-terminated
> > > > > +              * utf8 string, so set the "1234567" string to allocate enough space
> > > > > +              * to store guid, instead of realloc the load_option.
> > > > > +              */
> > > > > +             lo.optional_data = "1234567";
> > > > > +             opt[i].size = efi_serialize_load_option(&lo, (u8 **)&opt[i].lo);
> > > > > +             if (!opt[i].size) {
> > > > > +                     ret = EFI_OUT_OF_RESOURCES;
> > > > > +                     free(dev_name);
> > > > > +                     goto out;
> > > > > +             }
> > > > > +             /* set the guid */
> > > > > +             optional_data = (char *)opt[i].lo + (opt[i].size - u16_strsize(u"1234567"));
> > > > > +             memcpy(optional_data, &efi_guid_bootmenu_auto_generated, sizeof(efi_guid_t));
> > > > > +     }
> > > > > +
> > > > > +out:
> > > > > +     return ret;
> > > > > +}
> > > > > +
> > > > > +/**
> > > > > + * eficonfig_delete_invalid_boot_option() - delete non-existing boot option
> > > > > + *
> > > > > + * @opt:             pointer to the media boot option structure
> > > > > + * @count:           number of media boot option structure
> > > > > + * Return:           status code
> > > > > + */
> > > > > +efi_status_t eficonfig_delete_invalid_boot_option(struct eficonfig_media_boot_option *opt,
> > > > > +                                               efi_status_t count)
> > > > > +{
> > > > > +     u16 *bootorder;
> > > > > +     u32 i, j;
> > > > > +     efi_status_t ret;
> > > > > +     efi_uintn_t num, size, bootorder_size;
> > > > > +     void *load_option;
> > > > > +     struct efi_load_option lo;
> > > > > +     u16 varname[] = u"Boot####";
> > > > > +
> > > > > +     bootorder = efi_get_var(u"BootOrder", &efi_global_variable_guid, &bootorder_size);
> > > > > +     if (!bootorder)
> > > > > +             return EFI_SUCCESS; /* BootOrder is not defined, nothing to do */
> > > > > +
> > > > > +     num = bootorder_size / sizeof(u16);
> > > > > +     for (i = 0; i < num;) {
> > > > > +             efi_uintn_t tmp;
> > > > > +
> > > > > +             efi_create_indexed_name(varname, sizeof(varname),
> > > > > +                                     "Boot", bootorder[i]);
> > > > > +             load_option = efi_get_var(varname, &efi_global_variable_guid, &size);
> > > > > +             if (!load_option)
> > > > > +                     goto next;
> > > > > +
> > > > > +             tmp = size;
> > > > > +             ret = efi_deserialize_load_option(&lo, load_option, &size);
> > > > > +             if (ret != EFI_SUCCESS)
> > > > > +                     goto next;
> > > > > +
> > > > > +             if (size >= sizeof(efi_guid_bootmenu_auto_generated)) {
> > > > > +                     if (guidcmp(lo.optional_data, &efi_guid_bootmenu_auto_generated) == 0) {
> > > > > +                             for (j = 0; j < count; j++) {
> > > > > +                                     if (opt[j].size == tmp &&
> > > > > +                                         memcmp(opt[j].lo, load_option, tmp) == 0) {
> > > > > +                                             opt[j].exist = true;
> > > > > +                                             break;
> > > > > +                                     }
> > > > > +                             }
> > > > > +
> > > > > +                             if (j == count) {
> > > > > +                                     ret = delete_boot_option(bootorder[i]);
> > > > > +                                     if (ret != EFI_SUCCESS) {
> > > > > +                                             free(load_option);
> > > > > +                                             goto out;
> > > > > +                                     }
> > > > > +
> > > > > +                                     num--;
> > > > > +                                     bootorder_size -= sizeof(u16);
> > > > > +                                     free(load_option);
> > > > > +                                     continue;
> > > > > +                             }
> > > > > +                     }
> > > > > +             }
> > > > > +next:
> > > > > +             free(load_option);
> > > > > +             i++;
> > > > > +     }
> > > > > +
> > > > > +out:
> > > > > +     return ret;
> > > > > +}
> > > > > +
> > > > >  /**
> > > > >   * eficonfig_init() - do required initialization for eficonfig command
> > > > >   *
> > > > > diff --git a/include/efi_loader.h b/include/efi_loader.h
> > > > > index 49e7d1e613..a5a0448fa0 100644
> > > > > --- a/include/efi_loader.h
> > > > > +++ b/include/efi_loader.h
> > > > > @@ -955,6 +955,22 @@ struct efi_signature_store {
> > > > >  struct x509_certificate;
> > > > >  struct pkcs7_message;
> > > > >
> > > > > +/**
> > > > > + * struct eficonfig_media_boot_option - boot option for (removable) media device
> > > > > + *
> > > > > + * This structure is used to enumerate possible boot option
> > > > > + *
> > > > > + * @lo:              Serialized load option data
> > > > > + * @size:    Size of serialized load option data
> > > > > + * @exist:   Flag to indicate the load option already exists
> > > > > + *           in Non-volatile load option
> > > > > + */
> > > > > +struct eficonfig_media_boot_option {
> > > > > +     void *lo;
> > > > > +     efi_uintn_t size;
> > > > > +     bool exist;
> > > > > +};
> > > > > +
> > > > >  bool efi_hash_regions(struct image_region *regs, int count,
> > > > >                     void **hash, const char *hash_algo, int *len);
> > > > >  bool efi_signature_lookup_digest(struct efi_image_regions *regs,
> > > > > @@ -1104,6 +1120,10 @@ efi_status_t efi_console_get_u16_string
> > > > >  efi_status_t eficonfig_get_unused_bootoption(u16 *buf,
> > > > >                                            efi_uintn_t buf_size, u32 *index);
> > > > >  efi_status_t eficonfig_append_bootorder(u16 index);
> > > > > +efi_status_t eficonfig_enumerate_boot_option(struct eficonfig_media_boot_option *opt,
> > > > > +                                          efi_handle_t *volume_handles, efi_status_t count);
> > > > > +efi_status_t eficonfig_delete_invalid_boot_option(struct eficonfig_media_boot_option *opt,
> > > > > +                                               efi_status_t count);
> > > > >
> > > > >  efi_status_t efi_disk_get_device_name(const efi_handle_t handle, char *buf, int size);
> > > > >
> > > > > diff --git a/lib/efi_loader/efi_bootmgr.c b/lib/efi_loader/efi_bootmgr.c
> > > > > index ede9116b3c..4b24b41047 100644
> > > > > --- a/lib/efi_loader/efi_bootmgr.c
> > > > > +++ b/lib/efi_loader/efi_bootmgr.c
> > > > > @@ -246,6 +246,10 @@ static efi_status_t try_load_entry(u16 n, efi_handle_t *handle,
> > > > >       }
> > > > >
> > > > >       /* Set load options */
> > > > > +     if (size >= sizeof(efi_guid_t) &&
> > > > > +         !guidcmp(lo.optional_data, &efi_guid_bootmenu_auto_generated))
> > > > > +             size = 0;
> > > > > +
> > > > >       if (size) {
> > > > >               *load_options = malloc(size);
> > > > >               if (!*load_options) {
> > > > > --
> > > > > 2.17.1
> > > > >

^ permalink raw reply	[flat|nested] 25+ messages in thread

end of thread, other threads:[~2022-08-24  4:49 UTC | newest]

Thread overview: 25+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-08-17  9:36 [PATCH v11 0/9] enable menu-driven UEFI variable maintenance Masahisa Kojima
2022-08-17  9:36 ` [PATCH v11 1/9] eficonfig: menu-driven addition of UEFI boot option Masahisa Kojima
2022-08-18  6:32   ` Heinrich Schuchardt
2022-08-18  6:43   ` Heinrich Schuchardt
2022-08-18  8:59     ` Masahisa Kojima
2022-08-17  9:36 ` [PATCH v11 2/9] eficonfig: add "Edit Boot Option" menu entry Masahisa Kojima
2022-08-17  9:36 ` [PATCH v11 3/9] menu: add KEY_PLUS, KEY_MINUS and KEY_SPACE handling Masahisa Kojima
2022-08-17  9:36 ` [PATCH v11 4/9] eficonfig: add "Change Boot Order" menu entry Masahisa Kojima
2022-08-18  6:17   ` Heinrich Schuchardt
2022-08-18  6:50     ` Heinrich Schuchardt
2022-08-18  7:34       ` Masahisa Kojima
2022-08-17  9:36 ` [PATCH v11 5/9] eficonfig: add "Delete Boot Option" " Masahisa Kojima
2022-08-17  9:36 ` [PATCH v11 6/9] bootmenu: add removable media entries Masahisa Kojima
2022-08-19  1:31   ` Takahiro Akashi
2022-08-19  3:05     ` Masahisa Kojima
2022-08-24  1:57       ` Takahiro Akashi
2022-08-24  4:29         ` Masahisa Kojima
2022-08-24  4:48           ` Masahisa Kojima
2022-08-17  9:36 ` [PATCH v11 7/9] doc:bootmenu: add description for UEFI boot support Masahisa Kojima
2022-08-17  9:36 ` [PATCH v11 8/9] doc:eficonfig: add documentation for eficonfig command Masahisa Kojima
2022-08-19  5:57   ` Takahiro Akashi
2022-08-19  6:15     ` Masahisa Kojima
2022-08-19  6:34       ` Takahiro Akashi
2022-08-19  7:32         ` Masahisa Kojima
2022-08-17  9:36 ` [PATCH v11 9/9] test: unit test for eficonfig Masahisa Kojima

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.