All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v3 00/25] bootstd: Add a boot menu
@ 2023-01-06 14:52 Simon Glass
  2023-01-06 14:52 ` [PATCH v3 01/25] sandbox: Enable mmc command and legacy images Simon Glass
                   ` (25 more replies)
  0 siblings, 26 replies; 40+ messages in thread
From: Simon Glass @ 2023-01-06 14:52 UTC (permalink / raw)
  To: U-Boot Mailing List
  Cc: Anatolij Gustschin, Tom Rini, Heinrich Schuchardt, Simon Glass,
	Joe Hershberger, Marek Vasut

So far standard boot lacks a boot menu, although it is possible to create
a rudimentary one using the existing 'bootmenu' command.

Even then, this text-based menu offer only basic functionality and does
not take full advantage of the displays which are common on many devices.

This series provides a 'bootflow menu' command which allows the user to
select from the available bootflows. An attempt is made to show the name
of the available operating systems, by reading more information into the
bootflow. A logo can be read also, where supported, so that this can be
presented to the user when an option is highlighted.

Full use is made of TrueType fonts, if enabled. For cases where only a
serial console is available, it falls back to a simple text-based menu.

All of this is implementing using a new 'expo' construct, a collection of
scenes (like menu screens) which can be navigated by the user to view
information and select options. This is fairly general and should be able
to cope with a wider array of use cases, with less hacking of the menu
code, such as is currently needed for CMD_BOOTEFI_BOOTMGR.

Of course it would be possible to enhance the existing menu rather than
creating a new setup. Instead it seems better to make the existing menu
use expo, if code space permits. It avoids the event-loop problem and
should be more extensible, given its loosely coupled components and use of
IDs instead of pointers. Further motivation is provided in the
documentation.

For now the CLI keypress-decoding code is split out to be used by the new
menu. The key codes defined by menu.h are reused also.

This is of course just a starting point. Some ideas for future work are
included in the documentation.

Changes in v3:
- Rebase to master

Changes in v2:
- Drop the _add suffix on expo creation function
- Fix 'touse' typo
- Fix pylint warning in mkdir_cond()
- Put strings in a separate structure referenced by ID
- Rebase to master
- Rename vidconsole_get_font() to vidconsole_get_font_size()
- Update for new API

Simon Glass (25):
  sandbox: Enable mmc command and legacy images
  cli: Move readline character-processing to a state machine
  bootmenu: Add a few comments
  menu: Rename KEY_... to BKEY_...
  menu: Update bootmenu_autoboot_loop() to return the code
  menu: Update bootmenu_loop() to return the code
  menu: Use a switch statement
  menu: Make use of CLI character processing
  image: Add a function to find a script in an image
  image: Move common image code to image_board and command
  video: Enable VIDEO_ANSI by default only with EFI
  video: truetype: Rename the metrics function
  video: Fix unchnaged typo
  video: Add font functions to the vidconsole API
  bootstd: Read the Operating System name for distro/scripts
  bootstd: Allow reading a logo for the OS
  menu: Factor out menu-keypress decoding
  expo: Add basic implementation
  expo: Add support for scenes
  expo: Add support for scene menus
  expo: Add basic tests
  bootstd: Support creating a boot menu
  bootstd: Add a test for the bootstd menu
  bootstd: Support setting a theme for the menu
  expo: Add documentation

 .../cmd_stm32prog/cmd_stm32prog.c             |   2 +-
 arch/sandbox/dts/test.dts                     |  11 +
 boot/Kconfig                                  |  12 +
 boot/Makefile                                 |   3 +
 boot/bootflow.c                               |   1 +
 boot/bootflow_internal.h                      |  47 ++
 boot/bootflow_menu.c                          | 284 +++++++++
 boot/bootmeth-uclass.c                        |  69 ++-
 boot/bootmeth_distro.c                        |  36 ++
 boot/bootmeth_script.c                        |  40 +-
 boot/bootstd-uclass.c                         |   2 +
 boot/expo.c                                   | 170 ++++++
 boot/image-board.c                            | 133 +++++
 boot/scene.c                                  | 414 ++++++++++++++
 boot/scene_internal.h                         | 123 ++++
 boot/scene_menu.c                             | 390 +++++++++++++
 cmd/bootflow.c                                |  44 +-
 cmd/bootmenu.c                                |  19 +-
 cmd/eficonfig.c                               |  38 +-
 cmd/font.c                                    |  11 +-
 cmd/source.c                                  | 140 +----
 common/Makefile                               |   6 +-
 common/cli_getch.c                            | 208 +++++++
 common/cli_readline.c                         | 150 +----
 common/command.c                              |  19 +
 common/menu.c                                 | 157 +++--
 configs/sandbox_defconfig                     |   2 +
 configs/sandbox_flattree_defconfig            |   2 +
 configs/snow_defconfig                        |   4 +
 configs/tools-only_defconfig                  |   2 +
 doc/develop/expo.rst                          | 188 ++++++
 doc/develop/index.rst                         |   1 +
 drivers/usb/gadget/f_sdp.c                    |   2 +-
 drivers/video/Kconfig                         |   7 +-
 drivers/video/console_truetype.c              |  37 +-
 drivers/video/vidconsole-uclass.c             |  33 ++
 include/bootflow.h                            |  40 ++
 include/bootmeth.h                            |  16 +
 include/bootstd.h                             |   4 +
 include/cli.h                                 |  74 +++
 include/command.h                             |  12 +
 include/expo.h                                | 521 +++++++++++++++++
 include/image.h                               |  17 +-
 include/menu.h                                |  77 ++-
 include/video.h                               |   2 +-
 include/video_console.h                       |  75 ++-
 test/boot/Makefile                            |   2 +
 test/boot/bootflow.c                          | 130 +++++
 test/boot/expo.c                              | 539 ++++++++++++++++++
 test/cmd/font.c                               |   6 +-
 test/py/tests/bootstd/armbian.bmp.xz          | Bin 0 -> 1384 bytes
 test/py/tests/bootstd/mmc4.img.xz             | Bin 0 -> 7072 bytes
 test/py/tests/test_android/test_avb.py        |   2 +
 test/py/tests/test_ut.py                      | 218 ++++++-
 54 files changed, 4075 insertions(+), 467 deletions(-)
 create mode 100644 boot/bootflow_internal.h
 create mode 100644 boot/bootflow_menu.c
 create mode 100644 boot/expo.c
 create mode 100644 boot/scene.c
 create mode 100644 boot/scene_internal.h
 create mode 100644 boot/scene_menu.c
 create mode 100644 common/cli_getch.c
 create mode 100644 doc/develop/expo.rst
 create mode 100644 include/expo.h
 create mode 100644 test/boot/expo.c
 create mode 100644 test/py/tests/bootstd/armbian.bmp.xz
 create mode 100644 test/py/tests/bootstd/mmc4.img.xz

-- 
2.39.0.314.g84b9a713c41-goog


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

* [PATCH v3 01/25] sandbox: Enable mmc command and legacy images
  2023-01-06 14:52 [PATCH v3 00/25] bootstd: Add a boot menu Simon Glass
@ 2023-01-06 14:52 ` Simon Glass
  2023-01-06 14:52 ` [PATCH v3 02/25] cli: Move readline character-processing to a state machine Simon Glass
                   ` (24 subsequent siblings)
  25 siblings, 0 replies; 40+ messages in thread
From: Simon Glass @ 2023-01-06 14:52 UTC (permalink / raw)
  To: U-Boot Mailing List
  Cc: Anatolij Gustschin, Tom Rini, Heinrich Schuchardt, Simon Glass

The mmc command is useful for testing mmc disk images in sandbox, so
enable it. We also need to enable legacy images so that we can run tests
which use them.

Disable it for a few avb tests since MMC is not implemented there yet.

Signed-off-by: Simon Glass <sjg@chromium.org>
---

(no changes since v1)

 configs/sandbox_defconfig              | 2 ++
 configs/sandbox_flattree_defconfig     | 2 ++
 test/py/tests/test_android/test_avb.py | 2 ++
 3 files changed, 6 insertions(+)

diff --git a/configs/sandbox_defconfig b/configs/sandbox_defconfig
index be46cae7aad..0b26673bf4f 100644
--- a/configs/sandbox_defconfig
+++ b/configs/sandbox_defconfig
@@ -13,6 +13,7 @@ CONFIG_FIT=y
 CONFIG_FIT_RSASSA_PSS=y
 CONFIG_FIT_CIPHER=y
 CONFIG_FIT_VERBOSE=y
+CONFIG_LEGACY_IMAGE_FORMAT=y
 CONFIG_BOOTSTAGE=y
 CONFIG_BOOTSTAGE_REPORT=y
 CONFIG_BOOTSTAGE_FDT=y
@@ -72,6 +73,7 @@ CONFIG_CMD_IDE=y
 CONFIG_CMD_I2C=y
 CONFIG_CMD_LOADM=y
 CONFIG_CMD_LSBLK=y
+CONFIG_CMD_MMC=y
 CONFIG_CMD_MUX=y
 CONFIG_CMD_OSD=y
 CONFIG_CMD_PCI=y
diff --git a/configs/sandbox_flattree_defconfig b/configs/sandbox_flattree_defconfig
index 88aaddfa4ae..41453e0e2fa 100644
--- a/configs/sandbox_flattree_defconfig
+++ b/configs/sandbox_flattree_defconfig
@@ -11,6 +11,7 @@ CONFIG_DISTRO_DEFAULTS=y
 CONFIG_FIT=y
 CONFIG_FIT_SIGNATURE=y
 CONFIG_FIT_VERBOSE=y
+CONFIG_LEGACY_IMAGE_FORMAT=y
 CONFIG_BOOTSTAGE=y
 CONFIG_BOOTSTAGE_REPORT=y
 CONFIG_BOOTSTAGE_FDT=y
@@ -40,6 +41,7 @@ CONFIG_CMD_DEMO=y
 CONFIG_CMD_GPIO=y
 CONFIG_CMD_GPT=y
 CONFIG_CMD_I2C=y
+CONFIG_CMD_MMC=y
 CONFIG_CMD_OSD=y
 CONFIG_CMD_PCI=y
 CONFIG_CMD_REMOTEPROC=y
diff --git a/test/py/tests/test_android/test_avb.py b/test/py/tests/test_android/test_avb.py
index a3f883136b0..bc5c5b55821 100644
--- a/test/py/tests/test_android/test_avb.py
+++ b/test/py/tests/test_android/test_avb.py
@@ -39,6 +39,7 @@ def test_avb_verify(u_boot_console):
 
 @pytest.mark.buildconfigspec('cmd_avb')
 @pytest.mark.buildconfigspec('cmd_mmc')
+@pytest.mark.notbuildconfigspec('sandbox')
 def test_avb_mmc_uuid(u_boot_console):
     """Check if 'avb get_uuid' works, compare results with
     'part list mmc 1' output
@@ -97,6 +98,7 @@ def test_avb_is_unlocked(u_boot_console):
 
 @pytest.mark.buildconfigspec('cmd_avb')
 @pytest.mark.buildconfigspec('cmd_mmc')
+@pytest.mark.notbuildconfigspec('sandbox')
 def test_avb_mmc_read(u_boot_console):
     """Test mmc read operation
     """
-- 
2.39.0.314.g84b9a713c41-goog


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

* [PATCH v3 02/25] cli: Move readline character-processing to a state machine
  2023-01-06 14:52 [PATCH v3 00/25] bootstd: Add a boot menu Simon Glass
  2023-01-06 14:52 ` [PATCH v3 01/25] sandbox: Enable mmc command and legacy images Simon Glass
@ 2023-01-06 14:52 ` Simon Glass
  2023-01-06 15:50   ` Heinrich Schuchardt
  2023-01-24 15:19   ` [BUG] " Heinrich Schuchardt
  2023-01-06 14:52 ` [PATCH v3 03/25] bootmenu: Add a few comments Simon Glass
                   ` (23 subsequent siblings)
  25 siblings, 2 replies; 40+ messages in thread
From: Simon Glass @ 2023-01-06 14:52 UTC (permalink / raw)
  To: U-Boot Mailing List
  Cc: Anatolij Gustschin, Tom Rini, Heinrich Schuchardt, Simon Glass

The current cread_line() function is very long. It handles the escape
processing inline. The menu command does similar processing but at the
character level, so there is some duplication.

Split the character processing into a new function cli_ch_process() which
processes individual characters and returns the resulting input character,
taking account of escape sequences. It requires the caller to set up and
maintain its state.

Update cread_line() to use this new function.

The only intended functional change is that an invalid escape sequence
does not add invalid/control characters into the input buffer, but instead
discards these.

Signed-off-by: Simon Glass <sjg@chromium.org>
---

(no changes since v1)

 common/Makefile       |   6 +-
 common/cli_getch.c    | 204 ++++++++++++++++++++++++++++++++++++++++++
 common/cli_readline.c | 150 +++++--------------------------
 include/cli.h         |  72 +++++++++++++++
 4 files changed, 301 insertions(+), 131 deletions(-)
 create mode 100644 common/cli_getch.c

diff --git a/common/Makefile b/common/Makefile
index 20addfb244c..67485e77a04 100644
--- a/common/Makefile
+++ b/common/Makefile
@@ -38,7 +38,7 @@ obj-$(CONFIG_SPLASH_SOURCE) += splash_source.o
 obj-$(CONFIG_MENU) += menu.o
 obj-$(CONFIG_UPDATE_COMMON) += update.o
 obj-$(CONFIG_USB_KEYBOARD) += usb_kbd.o
-obj-$(CONFIG_CMDLINE) += cli_readline.o cli_simple.o
+obj-$(CONFIG_CMDLINE) += cli_getch.o cli_readline.o cli_simple.o
 
 endif # !CONFIG_SPL_BUILD
 
@@ -93,8 +93,8 @@ obj-y += eeprom/eeprom_field.o eeprom/eeprom_layout.o
 endif
 
 obj-y += cli.o
-obj-$(CONFIG_FSL_DDR_INTERACTIVE) += cli_simple.o cli_readline.o
-obj-$(CONFIG_STM32MP1_DDR_INTERACTIVE) += cli_simple.o cli_readline.o
+obj-$(CONFIG_FSL_DDR_INTERACTIVE) += cli_getch.o cli_simple.o cli_readline.o
+obj-$(CONFIG_STM32MP1_DDR_INTERACTIVE) += cli_getch.o cli_simple.o cli_readline.o
 obj-$(CONFIG_DFU_OVER_USB) += dfu.o
 obj-y += command.o
 obj-$(CONFIG_$(SPL_TPL_)LOG) += log.o
diff --git a/common/cli_getch.c b/common/cli_getch.c
new file mode 100644
index 00000000000..9eeea7fef29
--- /dev/null
+++ b/common/cli_getch.c
@@ -0,0 +1,204 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * (C) Copyright 2000
+ * Wolfgang Denk, DENX Software Engineering, wd@denx.de.
+ *
+ * Copyright 2022 Google LLC
+ */
+
+#include <common.h>
+#include <cli.h>
+
+/**
+ * enum cli_esc_state_t - indicates what to do with an escape character
+ *
+ * @ESC_REJECT: Invalid escape sequence, so the esc_save[] characters are
+ *	returned from each subsequent call to cli_ch_esc()
+ * @ESC_SAVE: Character should be saved in esc_save until we have another one
+ * @ESC_CONVERTED: Escape sequence has been completed and the resulting
+ *	character is available
+ */
+enum cli_esc_state_t {
+	ESC_REJECT,
+	ESC_SAVE,
+	ESC_CONVERTED
+};
+
+void cli_ch_init(struct cli_ch_state *cch)
+{
+	memset(cch, '\0', sizeof(*cch));
+}
+
+/**
+ * cli_ch_esc() - Process a character in an ongoing escape sequence
+ *
+ * @cch: State information
+ * @ichar: Character to process
+ * @actp: Returns the action to take
+ * Returns: Output character if *actp is ESC_CONVERTED, else 0
+ */
+static int cli_ch_esc(struct cli_ch_state *cch, int ichar,
+		      enum cli_esc_state_t *actp)
+{
+	enum cli_esc_state_t act = ESC_REJECT;
+
+	switch (cch->esc_len) {
+	case 1:
+		if (ichar == '[' || ichar == 'O')
+			act = ESC_SAVE;
+		break;
+	case 2:
+		switch (ichar) {
+		case 'D':	/* <- key */
+			ichar = CTL_CH('b');
+			act = ESC_CONVERTED;
+			break;	/* pass off to ^B handler */
+		case 'C':	/* -> key */
+			ichar = CTL_CH('f');
+			act = ESC_CONVERTED;
+			break;	/* pass off to ^F handler */
+		case 'H':	/* Home key */
+			ichar = CTL_CH('a');
+			act = ESC_CONVERTED;
+			break;	/* pass off to ^A handler */
+		case 'F':	/* End key */
+			ichar = CTL_CH('e');
+			act = ESC_CONVERTED;
+			break;	/* pass off to ^E handler */
+		case 'A':	/* up arrow */
+			ichar = CTL_CH('p');
+			act = ESC_CONVERTED;
+			break;	/* pass off to ^P handler */
+		case 'B':	/* down arrow */
+			ichar = CTL_CH('n');
+			act = ESC_CONVERTED;
+			break;	/* pass off to ^N handler */
+		case '1':
+		case '2':
+		case '3':
+		case '4':
+		case '7':
+		case '8':
+			if (cch->esc_save[1] == '[') {
+				/* see if next character is ~ */
+				act = ESC_SAVE;
+			}
+			break;
+		}
+		break;
+	case 3:
+		switch (ichar) {
+		case '~':
+			switch (cch->esc_save[2]) {
+			case '3':	/* Delete key */
+				ichar = CTL_CH('d');
+				act = ESC_CONVERTED;
+				break;	/* pass to ^D handler */
+			case '1':	/* Home key */
+			case '7':
+				ichar = CTL_CH('a');
+				act = ESC_CONVERTED;
+				break;	/* pass to ^A handler */
+			case '4':	/* End key */
+			case '8':
+				ichar = CTL_CH('e');
+				act = ESC_CONVERTED;
+				break;	/* pass to ^E handler */
+			}
+			break;
+		case '0':
+			if (cch->esc_save[2] == '2')
+				act = ESC_SAVE;
+			break;
+		}
+		break;
+	case 4:
+		switch (ichar) {
+		case '0':
+		case '1':
+			act = ESC_SAVE;
+			break;		/* bracketed paste */
+		}
+		break;
+	case 5:
+		if (ichar == '~') {	/* bracketed paste */
+			ichar = 0;
+			act = ESC_CONVERTED;
+		}
+	}
+
+	*actp = act;
+
+	return act == ESC_CONVERTED ? ichar : 0;
+}
+
+int cli_ch_process(struct cli_ch_state *cch, int ichar)
+{
+	/*
+	 * ichar=0x0 when error occurs in U-Boot getchar() or when the caller
+	 * wants to check if there are more characters saved in the escape
+	 * sequence
+	 */
+	if (!ichar) {
+		if (cch->emit_upto) {
+			if (cch->emit_upto < cch->esc_len)
+				return cch->esc_save[cch->emit_upto++];
+			cch->emit_upto = 0;
+		}
+		return 0;
+	} else if (ichar == -ETIMEDOUT) {
+		/*
+		 * If we are in an escape sequence but nothing has followed the
+		 * Escape character, then the user probably just pressed the
+		 * Escape key. Return it and clear the sequence.
+		 */
+		if (cch->esc_len) {
+			cch->esc_len = 0;
+			return '\e';
+		}
+
+		/* Otherwise there is nothing to return */
+		return 0;
+	}
+
+	if (ichar == '\n' || ichar == '\r')
+		return '\n';
+
+	/* handle standard linux xterm esc sequences for arrow key, etc. */
+	if (cch->esc_len != 0) {
+		enum cli_esc_state_t act;
+
+		ichar = cli_ch_esc(cch, ichar, &act);
+
+		switch (act) {
+		case ESC_SAVE:
+			/* save this character and return nothing */
+			cch->esc_save[cch->esc_len++] = ichar;
+			return 0;
+		case ESC_REJECT:
+			/*
+			 * invalid escape sequence, start returning the
+			 * characters in it
+			 */
+			cch->esc_save[cch->esc_len++] = ichar;
+			return cch->esc_save[cch->emit_upto++];
+		case ESC_CONVERTED:
+			/* valid escape sequence, return the resulting char */
+			cch->esc_len = 0;
+			return ichar;
+		}
+	}
+
+	if (ichar == '\e') {
+		if (!cch->esc_len) {
+			cch->esc_save[cch->esc_len] = ichar;
+			cch->esc_len = 1;
+		} else {
+			puts("impossible condition #876\n");
+			cch->esc_len = 0;
+		}
+		return 0;
+	}
+
+	return ichar;
+}
diff --git a/common/cli_readline.c b/common/cli_readline.c
index d6444f5fc1d..709e9c3d38b 100644
--- a/common/cli_readline.c
+++ b/common/cli_readline.c
@@ -62,7 +62,6 @@ static char *delete_char (char *buffer, char *p, int *colp, int *np, int plen)
 
 #define putnstr(str, n)	printf("%.*s", (int)n, str)
 
-#define CTL_CH(c)		((c) - 'a' + 1)
 #define CTL_BACKSPACE		('\b')
 #define DEL			((char)255)
 #define DEL7			((char)127)
@@ -252,156 +251,53 @@ static void cread_add_str(char *str, int strsize, int insert,
 static int cread_line(const char *const prompt, char *buf, unsigned int *len,
 		int timeout)
 {
+	struct cli_ch_state s_cch, *cch = &s_cch;
 	unsigned long num = 0;
 	unsigned long eol_num = 0;
 	unsigned long wlen;
 	char ichar;
 	int insert = 1;
-	int esc_len = 0;
-	char esc_save[8];
 	int init_len = strlen(buf);
 	int first = 1;
 
+	cli_ch_init(cch);
+
 	if (init_len)
 		cread_add_str(buf, init_len, 1, &num, &eol_num, buf, *len);
 
 	while (1) {
-		if (bootretry_tstc_timeout())
-			return -2;	/* timed out */
-		if (first && timeout) {
-			uint64_t etime = endtick(timeout);
-
-			while (!tstc()) {	/* while no incoming data */
-				if (get_ticks() >= etime)
-					return -2;	/* timed out */
-				schedule();
+		/* Check for saved characters */
+		ichar = cli_ch_process(cch, 0);
+
+		if (!ichar) {
+			if (bootretry_tstc_timeout())
+				return -2;	/* timed out */
+			if (first && timeout) {
+				u64 etime = endtick(timeout);
+
+				while (!tstc()) {	/* while no incoming data */
+					if (get_ticks() >= etime)
+						return -2;	/* timed out */
+					schedule();
+				}
+				first = 0;
 			}
-			first = 0;
+
+			ichar = getcmd_getch();
 		}
 
-		ichar = getcmd_getch();
+		ichar = cli_ch_process(cch, ichar);
 
 		/* ichar=0x0 when error occurs in U-Boot getc */
 		if (!ichar)
 			continue;
 
-		if ((ichar == '\n') || (ichar == '\r')) {
+		if (ichar == '\n') {
 			putc('\n');
 			break;
 		}
 
-		/*
-		 * handle standard linux xterm esc sequences for arrow key, etc.
-		 */
-		if (esc_len != 0) {
-			enum { ESC_REJECT, ESC_SAVE, ESC_CONVERTED } act = ESC_REJECT;
-
-			if (esc_len == 1) {
-				if (ichar == '[' || ichar == 'O')
-					act = ESC_SAVE;
-			} else if (esc_len == 2) {
-				switch (ichar) {
-				case 'D':	/* <- key */
-					ichar = CTL_CH('b');
-					act = ESC_CONVERTED;
-					break;	/* pass off to ^B handler */
-				case 'C':	/* -> key */
-					ichar = CTL_CH('f');
-					act = ESC_CONVERTED;
-					break;	/* pass off to ^F handler */
-				case 'H':	/* Home key */
-					ichar = CTL_CH('a');
-					act = ESC_CONVERTED;
-					break;	/* pass off to ^A handler */
-				case 'F':	/* End key */
-					ichar = CTL_CH('e');
-					act = ESC_CONVERTED;
-					break;	/* pass off to ^E handler */
-				case 'A':	/* up arrow */
-					ichar = CTL_CH('p');
-					act = ESC_CONVERTED;
-					break;	/* pass off to ^P handler */
-				case 'B':	/* down arrow */
-					ichar = CTL_CH('n');
-					act = ESC_CONVERTED;
-					break;	/* pass off to ^N handler */
-				case '1':
-				case '2':
-				case '3':
-				case '4':
-				case '7':
-				case '8':
-					if (esc_save[1] == '[') {
-						/* see if next character is ~ */
-						act = ESC_SAVE;
-					}
-					break;
-				}
-			} else if (esc_len == 3) {
-				switch (ichar) {
-				case '~':
-					switch (esc_save[2]) {
-					case '3':	/* Delete key */
-						ichar = CTL_CH('d');
-						act = ESC_CONVERTED;
-						break;	/* pass to ^D handler */
-					case '1':	/* Home key */
-					case '7':
-						ichar = CTL_CH('a');
-						act = ESC_CONVERTED;
-						break;	/* pass to ^A handler */
-					case '4':	/* End key */
-					case '8':
-						ichar = CTL_CH('e');
-						act = ESC_CONVERTED;
-						break;	/* pass to ^E handler */
-					}
-					break;
-				case '0':
-					if (esc_save[2] == '2')
-						act = ESC_SAVE;
-					break;
-				}
-			} else if (esc_len == 4) {
-				switch (ichar) {
-				case '0':
-				case '1':
-					act = ESC_SAVE;
-					break;		/* bracketed paste */
-				}
-			} else if (esc_len == 5) {
-				if (ichar == '~') {	/* bracketed paste */
-					ichar = 0;
-					act = ESC_CONVERTED;
-				}
-			}
-			switch (act) {
-			case ESC_SAVE:
-				esc_save[esc_len++] = ichar;
-				continue;
-			case ESC_REJECT:
-				esc_save[esc_len++] = ichar;
-				cread_add_str(esc_save, esc_len, insert,
-					      &num, &eol_num, buf, *len);
-				esc_len = 0;
-				continue;
-			case ESC_CONVERTED:
-				esc_len = 0;
-				break;
-			}
-		}
-
 		switch (ichar) {
-		case 0x1b:
-			if (esc_len == 0) {
-				esc_save[esc_len] = ichar;
-				esc_len = 1;
-			} else {
-				puts("impossible condition #876\n");
-				esc_len = 0;
-			}
-			break;
-
 		case CTL_CH('a'):
 			BEGINNING_OF_LINE();
 			break;
@@ -470,8 +366,6 @@ static int cread_line(const char *const prompt, char *buf, unsigned int *len,
 		{
 			char *hline;
 
-			esc_len = 0;
-
 			if (ichar == CTL_CH('p'))
 				hline = hist_prev();
 			else
diff --git a/include/cli.h b/include/cli.h
index ba5b8ebd36e..863519e4b13 100644
--- a/include/cli.h
+++ b/include/cli.h
@@ -7,6 +7,21 @@
 #ifndef __CLI_H
 #define __CLI_H
 
+#include <stdbool.h>
+
+/**
+ * struct cli_ch_state - state information for reading cmdline characters
+ *
+ * @esc_len: Number of escape characters read so far
+ * @esc_save: Escape characters collected so far
+ * @emit_upto: Next character to emit from esc_save (0 if not emitting)
+ */
+struct cli_ch_state {
+	int esc_len;
+	char esc_save[8];
+	int emit_upto;
+};
+
 /**
  * Go into the command loop
  *
@@ -154,5 +169,62 @@ void cli_loop(void);
 void cli_init(void);
 
 #define endtick(seconds) (get_ticks() + (uint64_t)(seconds) * get_tbclk())
+#define CTL_CH(c)		((c) - 'a' + 1)
+
+/**
+ * cli_ch_init() - Set up the initial state to process input characters
+ *
+ * @cch: State to set up
+ */
+void cli_ch_init(struct cli_ch_state *cch);
+
+/**
+ * cli_ch_process() - Process an input character
+ *
+ * When @ichar is 0, this function returns any characters from an invalid escape
+ * sequence which are still pending in the buffer
+ *
+ * Otherwise it processes the input character. If it is an escape character,
+ * then an escape sequence is started and the function returns 0. If we are in
+ * the middle of an escape sequence, the character is processed and may result
+ * in returning 0 (if more characters are needed) or a valid character (if
+ * @ichar finishes the sequence).
+ *
+ * If @ichar is a valid character and there is no escape sequence in progress,
+ * then it is returned as is.
+ *
+ * If the Enter key is pressed, '\n' is returned.
+ *
+ * Usage should be like this::
+ *
+ *    struct cli_ch_state cch;
+ *
+ *    cli_ch_init(cch);
+ *    do
+ *       {
+ *       int ichar, ch;
+ *
+ *       ichar = cli_ch_process(cch, 0);
+ *       if (!ichar) {
+ *          ch = getchar();
+ *          ichar = cli_ch_process(cch, ch);
+ *       }
+ *       (handle the ichar character)
+ *    } while (!done)
+ *
+ * If tstc() is used to look for keypresses, this function can be called with
+ * @ichar set to -ETIMEDOUT if there is no character after 5-10ms. This allows
+ * the ambgiuity between the Escape key and the arrow keys (which generate an
+ * escape character followed by other characters) to be resolved.
+ *
+ * @cch: Current state
+ * @ichar: Input character to process, or 0 if none, or -ETIMEDOUT if no
+ * character has been received within a small number of milliseconds (this
+ * cancels any existing escape sequence and allows pressing the Escape key to
+ * work)
+ * Returns: Resulting input character after processing, 0 if none, '\e' if
+ * an existing escape sequence was cancelled
+ */
+int cli_ch_process(struct cli_ch_state *cch, int ichar);
 
 #endif
-- 
2.39.0.314.g84b9a713c41-goog


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

* [PATCH v3 03/25] bootmenu: Add a few comments
  2023-01-06 14:52 [PATCH v3 00/25] bootstd: Add a boot menu Simon Glass
  2023-01-06 14:52 ` [PATCH v3 01/25] sandbox: Enable mmc command and legacy images Simon Glass
  2023-01-06 14:52 ` [PATCH v3 02/25] cli: Move readline character-processing to a state machine Simon Glass
@ 2023-01-06 14:52 ` Simon Glass
  2023-01-06 15:53   ` Heinrich Schuchardt
  2023-01-06 14:52 ` [PATCH v3 04/25] menu: Rename KEY_... to BKEY_ Simon Glass
                   ` (22 subsequent siblings)
  25 siblings, 1 reply; 40+ messages in thread
From: Simon Glass @ 2023-01-06 14:52 UTC (permalink / raw)
  To: U-Boot Mailing List
  Cc: Anatolij Gustschin, Tom Rini, Heinrich Schuchardt, Simon Glass

The behaviour of these two functions is completely undocumented. Add some
notes so the poor, suffering dev can figure out what is going on.

Signed-off-by: Simon Glass <sjg@chromium.org>
---

(no changes since v1)

 include/menu.h | 42 ++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 42 insertions(+)

diff --git a/include/menu.h b/include/menu.h
index 702aacb170c..0b4d9734149 100644
--- a/include/menu.h
+++ b/include/menu.h
@@ -42,6 +42,7 @@ struct bootmenu_data {
 	struct bootmenu_entry *first;	/* first menu entry */
 };
 
+/** enum bootmenu_key - keys that can be returned by the bootmenu */
 enum bootmenu_key {
 	KEY_NONE = 0,
 	KEY_UP,
@@ -53,8 +54,49 @@ enum bootmenu_key {
 	KEY_SPACE,
 };
 
+/**
+ * bootmenu_autoboot_loop() - handle autobooting if no key is pressed
+ *
+ * This shows a prompt to allow the user to press a key to interrupt auto boot
+ * of the first menu option.
+ *
+ * It then waits for the required time (menu->delay in seconds) for a key to be
+ * pressed. If nothing is pressed in that time, @key returns KEY_SELECT
+ * indicating that the current option should be chosen.
+ *
+ * @menu: Menu being processed
+ * @key: Returns the code for the key the user pressed:
+ *	enter: KEY_SELECT
+ *	Ctrl-C: KEY_QUIT
+ *	anything else: KEY_NONE
+ * @esc: Set to 1 if the escape key is pressed, otherwise not updated
+ */
 void bootmenu_autoboot_loop(struct bootmenu_data *menu,
 			    enum bootmenu_key *key, int *esc);
+
+/**
+ * bootmenu_loop() - handle waiting for a keypress when autoboot is disabled
+ *
+ * This is used when the menu delay is negative, indicating that the delay has
+ * elapsed, or there was no delay to begin with.
+ *
+ * It reads a character and processes it, returning a menu-key code if a
+ * character is recognised
+ *
+ * @menu: Menu being processed
+ * @key: Returns the code for the key the user pressed:
+ *	enter: KEY_SELECT
+ *	Ctrl-C: KEY_QUIT
+ *	Up arrow: KEY_UP
+ *	Down arrow: KEY_DOWN
+ *	Escape (by itself): KEY_QUIT
+ *	Plus: KEY_PLUS
+ *	Minus: KEY_MINUS
+ *	Space: KEY_SPACE
+ * @esc: On input, a non-zero value indicates that an escape sequence has
+ *	resulted in that many characters so far. On exit this is updated to the
+ *	new number of characters
+ */
 void bootmenu_loop(struct bootmenu_data *menu,
 		   enum bootmenu_key *key, int *esc);
 
-- 
2.39.0.314.g84b9a713c41-goog


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

* [PATCH v3 04/25] menu: Rename KEY_... to BKEY_...
  2023-01-06 14:52 [PATCH v3 00/25] bootstd: Add a boot menu Simon Glass
                   ` (2 preceding siblings ...)
  2023-01-06 14:52 ` [PATCH v3 03/25] bootmenu: Add a few comments Simon Glass
@ 2023-01-06 14:52 ` Simon Glass
  2023-01-06 14:52 ` [PATCH v3 05/25] menu: Update bootmenu_autoboot_loop() to return the code Simon Glass
                   ` (21 subsequent siblings)
  25 siblings, 0 replies; 40+ messages in thread
From: Simon Glass @ 2023-01-06 14:52 UTC (permalink / raw)
  To: U-Boot Mailing List
  Cc: Anatolij Gustschin, Tom Rini, Heinrich Schuchardt, Simon Glass

This enum values conflict with linux/input.h so rename them.

Signed-off-by: Simon Glass <sjg@chromium.org>
---

(no changes since v1)

 cmd/bootmenu.c  | 10 +++++-----
 cmd/eficonfig.c | 26 +++++++++++++-------------
 common/menu.c   | 34 +++++++++++++++++-----------------
 include/menu.h  | 32 ++++++++++++++++----------------
 4 files changed, 51 insertions(+), 51 deletions(-)

diff --git a/cmd/bootmenu.c b/cmd/bootmenu.c
index 3340be16325..c80004c54dc 100644
--- a/cmd/bootmenu.c
+++ b/cmd/bootmenu.c
@@ -86,7 +86,7 @@ static char *bootmenu_choice_entry(void *data)
 {
 	struct bootmenu_data *menu = data;
 	struct bootmenu_entry *iter;
-	enum bootmenu_key key = KEY_NONE;
+	enum bootmenu_key key = BKEY_NONE;
 	int esc = 0;
 	int i;
 
@@ -100,22 +100,22 @@ static char *bootmenu_choice_entry(void *data)
 		}
 
 		switch (key) {
-		case KEY_UP:
+		case BKEY_UP:
 			if (menu->active > 0)
 				--menu->active;
 			/* no menu key selected, regenerate menu */
 			return NULL;
-		case KEY_DOWN:
+		case BKEY_DOWN:
 			if (menu->active < menu->count - 1)
 				++menu->active;
 			/* no menu key selected, regenerate menu */
 			return NULL;
-		case KEY_SELECT:
+		case BKEY_SELECT:
 			iter = menu->first;
 			for (i = 0; i < menu->active; ++i)
 				iter = iter->next;
 			return iter->key;
-		case KEY_QUIT:
+		case BKEY_QUIT:
 			/* Quit by choosing the last entry - U-Boot console */
 			iter = menu->first;
 			while (iter->next)
diff --git a/cmd/eficonfig.c b/cmd/eficonfig.c
index ce7175a5666..8f246bc271f 100644
--- a/cmd/eficonfig.c
+++ b/cmd/eficonfig.c
@@ -187,31 +187,31 @@ 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;
+	enum bootmenu_key key = BKEY_NONE;
 	struct efimenu *efi_menu = data;
 
 	while (1) {
 		bootmenu_loop((struct bootmenu_data *)efi_menu, &key, &esc);
 
 		switch (key) {
-		case KEY_UP:
+		case BKEY_UP:
 			if (efi_menu->active > 0)
 				--efi_menu->active;
 			/* no menu key selected, regenerate menu */
 			return NULL;
-		case KEY_DOWN:
+		case BKEY_DOWN:
 			if (efi_menu->active < efi_menu->count - 1)
 				++efi_menu->active;
 			/* no menu key selected, regenerate menu */
 			return NULL;
-		case KEY_SELECT:
+		case BKEY_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;
 			}
 			break;
-		case KEY_QUIT:
+		case BKEY_QUIT:
 			/* Quit by choosing the last entry */
 			entry = list_last_entry(&efi_menu->list, struct eficonfig_entry, list);
 			return entry->key;
@@ -1864,14 +1864,14 @@ static efi_status_t eficonfig_choice_change_boot_order(struct efimenu *efi_menu)
 {
 	int esc = 0;
 	struct list_head *pos, *n;
-	enum bootmenu_key key = KEY_NONE;
+	enum bootmenu_key key = BKEY_NONE;
 	struct eficonfig_entry *entry, *tmp;
 
 	while (1) {
 		bootmenu_loop(NULL, &key, &esc);
 
 		switch (key) {
-		case KEY_PLUS:
+		case BKEY_PLUS:
 			if (efi_menu->active > 0) {
 				list_for_each_safe(pos, n, &efi_menu->list) {
 					entry = list_entry(pos, struct eficonfig_entry, list);
@@ -1885,11 +1885,11 @@ static efi_status_t eficonfig_choice_change_boot_order(struct efimenu *efi_menu)
 				list_add(&tmp->list, &entry->list);
 			}
 			fallthrough;
-		case KEY_UP:
+		case BKEY_UP:
 			if (efi_menu->active > 0)
 				--efi_menu->active;
 			return EFI_NOT_READY;
-		case KEY_MINUS:
+		case BKEY_MINUS:
 			if (efi_menu->active < efi_menu->count - 3) {
 				list_for_each_safe(pos, n, &efi_menu->list) {
 					entry = list_entry(pos, struct eficonfig_entry, list);
@@ -1905,11 +1905,11 @@ static efi_status_t eficonfig_choice_change_boot_order(struct efimenu *efi_menu)
 				++efi_menu->active;
 			}
 			return EFI_NOT_READY;
-		case KEY_DOWN:
+		case BKEY_DOWN:
 			if (efi_menu->active < efi_menu->count - 1)
 				++efi_menu->active;
 			return EFI_NOT_READY;
-		case KEY_SELECT:
+		case BKEY_SELECT:
 			/* "Save" */
 			if (efi_menu->active == efi_menu->count - 2)
 				return EFI_SUCCESS;
@@ -1919,7 +1919,7 @@ static efi_status_t eficonfig_choice_change_boot_order(struct efimenu *efi_menu)
 				return EFI_ABORTED;
 
 			break;
-		case KEY_SPACE:
+		case BKEY_SPACE:
 			if (efi_menu->active < efi_menu->count - 2) {
 				list_for_each_safe(pos, n, &efi_menu->list) {
 					entry = list_entry(pos, struct eficonfig_entry, list);
@@ -1932,7 +1932,7 @@ static efi_status_t eficonfig_choice_change_boot_order(struct efimenu *efi_menu)
 				}
 			}
 			break;
-		case KEY_QUIT:
+		case BKEY_QUIT:
 			return EFI_ABORTED;
 		default:
 			/* Pressed key is not valid, no need to regenerate the menu */
diff --git a/common/menu.c b/common/menu.c
index 8fe00965c0c..a245c5a9c6c 100644
--- a/common/menu.c
+++ b/common/menu.c
@@ -446,16 +446,16 @@ void bootmenu_autoboot_loop(struct bootmenu_data *menu,
 			switch (c) {
 			case '\e':
 				*esc = 1;
-				*key = KEY_NONE;
+				*key = BKEY_NONE;
 				break;
 			case '\r':
-				*key = KEY_SELECT;
+				*key = BKEY_SELECT;
 				break;
 			case 0x3: /* ^C */
-				*key = KEY_QUIT;
+				*key = BKEY_QUIT;
 				break;
 			default:
-				*key = KEY_NONE;
+				*key = BKEY_NONE;
 				break;
 			}
 
@@ -471,7 +471,7 @@ void bootmenu_autoboot_loop(struct bootmenu_data *menu,
 	printf(ANSI_CURSOR_POSITION ANSI_CLEAR_LINE, menu->count + 5, 1);
 
 	if (menu->delay == 0)
-		*key = KEY_SELECT;
+		*key = BKEY_SELECT;
 }
 
 void bootmenu_loop(struct bootmenu_data *menu,
@@ -503,17 +503,17 @@ void bootmenu_loop(struct bootmenu_data *menu,
 		/* First char of ANSI escape sequence '\e' */
 		if (c == '\e') {
 			*esc = 1;
-			*key = KEY_NONE;
+			*key = BKEY_NONE;
 		}
 		break;
 	case 1:
 		/* Second char of ANSI '[' */
 		if (c == '[') {
 			*esc = 2;
-			*key = KEY_NONE;
+			*key = BKEY_NONE;
 		} else {
 		/* Alone ESC key was pressed */
-			*key = KEY_QUIT;
+			*key = BKEY_QUIT;
 			*esc = (c == '\e') ? 1 : 0;
 		}
 		break;
@@ -522,7 +522,7 @@ void bootmenu_loop(struct bootmenu_data *menu,
 		/* Third char of ANSI (number '1') - optional */
 		if (*esc == 2 && c == '1') {
 			*esc = 3;
-			*key = KEY_NONE;
+			*key = BKEY_NONE;
 			break;
 		}
 
@@ -530,31 +530,31 @@ void bootmenu_loop(struct bootmenu_data *menu,
 
 		/* ANSI 'A' - key up was pressed */
 		if (c == 'A')
-			*key = KEY_UP;
+			*key = BKEY_UP;
 		/* ANSI 'B' - key down was pressed */
 		else if (c == 'B')
-			*key = KEY_DOWN;
+			*key = BKEY_DOWN;
 		/* other key was pressed */
 		else
-			*key = KEY_NONE;
+			*key = BKEY_NONE;
 
 		break;
 	}
 
 	/* enter key was pressed */
 	if (c == '\r')
-		*key = KEY_SELECT;
+		*key = BKEY_SELECT;
 
 	/* ^C was pressed */
 	if (c == 0x3)
-		*key = KEY_QUIT;
+		*key = BKEY_QUIT;
 
 	if (c == '+')
-		*key = KEY_PLUS;
+		*key = BKEY_PLUS;
 
 	if (c == '-')
-		*key = KEY_MINUS;
+		*key = BKEY_MINUS;
 
 	if (c == ' ')
-		*key = KEY_SPACE;
+		*key = BKEY_SPACE;
 }
diff --git a/include/menu.h b/include/menu.h
index 0b4d9734149..29b457921e9 100644
--- a/include/menu.h
+++ b/include/menu.h
@@ -44,14 +44,14 @@ struct bootmenu_data {
 
 /** enum bootmenu_key - keys that can be returned by the bootmenu */
 enum bootmenu_key {
-	KEY_NONE = 0,
-	KEY_UP,
-	KEY_DOWN,
-	KEY_SELECT,
-	KEY_QUIT,
-	KEY_PLUS,
-	KEY_MINUS,
-	KEY_SPACE,
+	BKEY_NONE = 0,
+	BKEY_UP,
+	BKEY_DOWN,
+	BKEY_SELECT,
+	BKEY_QUIT,
+	BKEY_PLUS,
+	BKEY_MINUS,
+	BKEY_SPACE,
 };
 
 /**
@@ -85,14 +85,14 @@ void bootmenu_autoboot_loop(struct bootmenu_data *menu,
  *
  * @menu: Menu being processed
  * @key: Returns the code for the key the user pressed:
- *	enter: KEY_SELECT
- *	Ctrl-C: KEY_QUIT
- *	Up arrow: KEY_UP
- *	Down arrow: KEY_DOWN
- *	Escape (by itself): KEY_QUIT
- *	Plus: KEY_PLUS
- *	Minus: KEY_MINUS
- *	Space: KEY_SPACE
+ *	enter: BKEY_SELECT
+ *	Ctrl-C: BKEY_QUIT
+ *	Up arrow: BKEY_UP
+ *	Down arrow: BKEY_DOWN
+ *	Escape (by itself): BKEY_QUIT
+ *	Plus: BKEY_PLUS
+ *	Minus: BKEY_MINUS
+ *	Space: BKEY_SPACE
  * @esc: On input, a non-zero value indicates that an escape sequence has
  *	resulted in that many characters so far. On exit this is updated to the
  *	new number of characters
-- 
2.39.0.314.g84b9a713c41-goog


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

* [PATCH v3 05/25] menu: Update bootmenu_autoboot_loop() to return the code
  2023-01-06 14:52 [PATCH v3 00/25] bootstd: Add a boot menu Simon Glass
                   ` (3 preceding siblings ...)
  2023-01-06 14:52 ` [PATCH v3 04/25] menu: Rename KEY_... to BKEY_ Simon Glass
@ 2023-01-06 14:52 ` Simon Glass
  2023-01-06 14:52 ` [PATCH v3 06/25] menu: Update bootmenu_loop() " Simon Glass
                   ` (20 subsequent siblings)
  25 siblings, 0 replies; 40+ messages in thread
From: Simon Glass @ 2023-01-06 14:52 UTC (permalink / raw)
  To: U-Boot Mailing List
  Cc: Anatolij Gustschin, Tom Rini, Heinrich Schuchardt, Simon Glass

Use the return value to save having to pass around a pointer. This also
resolves any ambiguity about what *key contains when the function is
called.

Signed-off-by: Simon Glass <sjg@chromium.org>
---

(no changes since v1)

 cmd/bootmenu.c |  2 +-
 common/menu.c  | 16 +++++++++-------
 include/menu.h |  7 +++----
 3 files changed, 13 insertions(+), 12 deletions(-)

diff --git a/cmd/bootmenu.c b/cmd/bootmenu.c
index c80004c54dc..0e22f504fe4 100644
--- a/cmd/bootmenu.c
+++ b/cmd/bootmenu.c
@@ -93,7 +93,7 @@ static char *bootmenu_choice_entry(void *data)
 	while (1) {
 		if (menu->delay >= 0) {
 			/* Autoboot was not stopped */
-			bootmenu_autoboot_loop(menu, &key, &esc);
+			key = bootmenu_autoboot_loop(menu, &esc);
 		} else {
 			/* Some key was pressed, so autoboot was stopped */
 			bootmenu_loop(menu, &key, &esc);
diff --git a/common/menu.c b/common/menu.c
index a245c5a9c6c..bafc8470d7d 100644
--- a/common/menu.c
+++ b/common/menu.c
@@ -425,9 +425,9 @@ int menu_destroy(struct menu *m)
 	return 1;
 }
 
-void bootmenu_autoboot_loop(struct bootmenu_data *menu,
-			    enum bootmenu_key *key, int *esc)
+enum bootmenu_key bootmenu_autoboot_loop(struct bootmenu_data *menu, int *esc)
 {
+	enum bootmenu_key key = BKEY_NONE;
 	int i, c;
 
 	while (menu->delay > 0) {
@@ -446,16 +446,16 @@ void bootmenu_autoboot_loop(struct bootmenu_data *menu,
 			switch (c) {
 			case '\e':
 				*esc = 1;
-				*key = BKEY_NONE;
+				key = BKEY_NONE;
 				break;
 			case '\r':
-				*key = BKEY_SELECT;
+				key = BKEY_SELECT;
 				break;
 			case 0x3: /* ^C */
-				*key = BKEY_QUIT;
+				key = BKEY_QUIT;
 				break;
 			default:
-				*key = BKEY_NONE;
+				key = BKEY_NONE;
 				break;
 			}
 
@@ -471,7 +471,9 @@ void bootmenu_autoboot_loop(struct bootmenu_data *menu,
 	printf(ANSI_CURSOR_POSITION ANSI_CLEAR_LINE, menu->count + 5, 1);
 
 	if (menu->delay == 0)
-		*key = BKEY_SELECT;
+		key = BKEY_SELECT;
+
+	return key;
 }
 
 void bootmenu_loop(struct bootmenu_data *menu,
diff --git a/include/menu.h b/include/menu.h
index 29b457921e9..9f30a3c1acd 100644
--- a/include/menu.h
+++ b/include/menu.h
@@ -65,14 +65,13 @@ enum bootmenu_key {
  * indicating that the current option should be chosen.
  *
  * @menu: Menu being processed
- * @key: Returns the code for the key the user pressed:
+ * @esc: Set to 1 if the escape key is pressed, otherwise not updated
+ * Returns: code for the key the user pressed:
  *	enter: KEY_SELECT
  *	Ctrl-C: KEY_QUIT
  *	anything else: KEY_NONE
- * @esc: Set to 1 if the escape key is pressed, otherwise not updated
  */
-void bootmenu_autoboot_loop(struct bootmenu_data *menu,
-			    enum bootmenu_key *key, int *esc);
+enum bootmenu_key bootmenu_autoboot_loop(struct bootmenu_data *menu, int *esc);
 
 /**
  * bootmenu_loop() - handle waiting for a keypress when autoboot is disabled
-- 
2.39.0.314.g84b9a713c41-goog


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

* [PATCH v3 06/25] menu: Update bootmenu_loop() to return the code
  2023-01-06 14:52 [PATCH v3 00/25] bootstd: Add a boot menu Simon Glass
                   ` (4 preceding siblings ...)
  2023-01-06 14:52 ` [PATCH v3 05/25] menu: Update bootmenu_autoboot_loop() to return the code Simon Glass
@ 2023-01-06 14:52 ` Simon Glass
  2023-01-06 14:52 ` [PATCH v3 07/25] menu: Use a switch statement Simon Glass
                   ` (19 subsequent siblings)
  25 siblings, 0 replies; 40+ messages in thread
From: Simon Glass @ 2023-01-06 14:52 UTC (permalink / raw)
  To: U-Boot Mailing List
  Cc: Anatolij Gustschin, Tom Rini, Heinrich Schuchardt, Simon Glass

Use the return value to save having to pass around a pointer. This also
resolves any ambiguity about what *key contains when the function is
called.

Signed-off-by: Simon Glass <sjg@chromium.org>
---

(no changes since v1)

 cmd/bootmenu.c  |  2 +-
 cmd/eficonfig.c |  4 ++--
 common/menu.c   | 30 ++++++++++++++++--------------
 include/menu.h  | 11 +++++------
 4 files changed, 24 insertions(+), 23 deletions(-)

diff --git a/cmd/bootmenu.c b/cmd/bootmenu.c
index 0e22f504fe4..573afe16609 100644
--- a/cmd/bootmenu.c
+++ b/cmd/bootmenu.c
@@ -96,7 +96,7 @@ static char *bootmenu_choice_entry(void *data)
 			key = bootmenu_autoboot_loop(menu, &esc);
 		} else {
 			/* Some key was pressed, so autoboot was stopped */
-			bootmenu_loop(menu, &key, &esc);
+			key = bootmenu_loop(menu, &esc);
 		}
 
 		switch (key) {
diff --git a/cmd/eficonfig.c b/cmd/eficonfig.c
index 8f246bc271f..96cb1a367f3 100644
--- a/cmd/eficonfig.c
+++ b/cmd/eficonfig.c
@@ -191,7 +191,7 @@ static char *eficonfig_choice_entry(void *data)
 	struct efimenu *efi_menu = data;
 
 	while (1) {
-		bootmenu_loop((struct bootmenu_data *)efi_menu, &key, &esc);
+		key = bootmenu_loop((struct bootmenu_data *)efi_menu, &esc);
 
 		switch (key) {
 		case BKEY_UP:
@@ -1868,7 +1868,7 @@ static efi_status_t eficonfig_choice_change_boot_order(struct efimenu *efi_menu)
 	struct eficonfig_entry *entry, *tmp;
 
 	while (1) {
-		bootmenu_loop(NULL, &key, &esc);
+		key = bootmenu_loop(NULL, &esc);
 
 		switch (key) {
 		case BKEY_PLUS:
diff --git a/common/menu.c b/common/menu.c
index bafc8470d7d..6842f5409d6 100644
--- a/common/menu.c
+++ b/common/menu.c
@@ -476,9 +476,9 @@ enum bootmenu_key bootmenu_autoboot_loop(struct bootmenu_data *menu, int *esc)
 	return key;
 }
 
-void bootmenu_loop(struct bootmenu_data *menu,
-		   enum bootmenu_key *key, int *esc)
+enum bootmenu_key bootmenu_loop(struct bootmenu_data *menu, int *esc)
 {
+	enum bootmenu_key key = BKEY_NONE;
 	int c;
 
 	if (*esc == 1) {
@@ -505,17 +505,17 @@ void bootmenu_loop(struct bootmenu_data *menu,
 		/* First char of ANSI escape sequence '\e' */
 		if (c == '\e') {
 			*esc = 1;
-			*key = BKEY_NONE;
+			key = BKEY_NONE;
 		}
 		break;
 	case 1:
 		/* Second char of ANSI '[' */
 		if (c == '[') {
 			*esc = 2;
-			*key = BKEY_NONE;
+			key = BKEY_NONE;
 		} else {
 		/* Alone ESC key was pressed */
-			*key = BKEY_QUIT;
+			key = BKEY_QUIT;
 			*esc = (c == '\e') ? 1 : 0;
 		}
 		break;
@@ -524,7 +524,7 @@ void bootmenu_loop(struct bootmenu_data *menu,
 		/* Third char of ANSI (number '1') - optional */
 		if (*esc == 2 && c == '1') {
 			*esc = 3;
-			*key = BKEY_NONE;
+			key = BKEY_NONE;
 			break;
 		}
 
@@ -532,31 +532,33 @@ void bootmenu_loop(struct bootmenu_data *menu,
 
 		/* ANSI 'A' - key up was pressed */
 		if (c == 'A')
-			*key = BKEY_UP;
+			key = BKEY_UP;
 		/* ANSI 'B' - key down was pressed */
 		else if (c == 'B')
-			*key = BKEY_DOWN;
+			key = BKEY_DOWN;
 		/* other key was pressed */
 		else
-			*key = BKEY_NONE;
+			key = BKEY_NONE;
 
 		break;
 	}
 
 	/* enter key was pressed */
 	if (c == '\r')
-		*key = BKEY_SELECT;
+		key = BKEY_SELECT;
 
 	/* ^C was pressed */
 	if (c == 0x3)
-		*key = BKEY_QUIT;
+		key = BKEY_QUIT;
 
 	if (c == '+')
-		*key = BKEY_PLUS;
+		key = BKEY_PLUS;
 
 	if (c == '-')
-		*key = BKEY_MINUS;
+		key = BKEY_MINUS;
 
 	if (c == ' ')
-		*key = BKEY_SPACE;
+		key = BKEY_SPACE;
+
+	return key;
 }
diff --git a/include/menu.h b/include/menu.h
index 9f30a3c1acd..8b9b36214f7 100644
--- a/include/menu.h
+++ b/include/menu.h
@@ -83,7 +83,10 @@ enum bootmenu_key bootmenu_autoboot_loop(struct bootmenu_data *menu, int *esc);
  * character is recognised
  *
  * @menu: Menu being processed
- * @key: Returns the code for the key the user pressed:
+ * @esc: On input, a non-zero value indicates that an escape sequence has
+ *	resulted in that many characters so far. On exit this is updated to the
+ *	new number of characters
+ * Returns: code for the key the user pressed:
  *	enter: BKEY_SELECT
  *	Ctrl-C: BKEY_QUIT
  *	Up arrow: BKEY_UP
@@ -92,11 +95,7 @@ enum bootmenu_key bootmenu_autoboot_loop(struct bootmenu_data *menu, int *esc);
  *	Plus: BKEY_PLUS
  *	Minus: BKEY_MINUS
  *	Space: BKEY_SPACE
- * @esc: On input, a non-zero value indicates that an escape sequence has
- *	resulted in that many characters so far. On exit this is updated to the
- *	new number of characters
  */
-void bootmenu_loop(struct bootmenu_data *menu,
-		   enum bootmenu_key *key, int *esc);
+enum bootmenu_key bootmenu_loop(struct bootmenu_data *menu, int *esc);
 
 #endif /* __MENU_H__ */
-- 
2.39.0.314.g84b9a713c41-goog


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

* [PATCH v3 07/25] menu: Use a switch statement
  2023-01-06 14:52 [PATCH v3 00/25] bootstd: Add a boot menu Simon Glass
                   ` (5 preceding siblings ...)
  2023-01-06 14:52 ` [PATCH v3 06/25] menu: Update bootmenu_loop() " Simon Glass
@ 2023-01-06 14:52 ` Simon Glass
  2023-01-06 14:52 ` [PATCH v3 08/25] menu: Make use of CLI character processing Simon Glass
                   ` (18 subsequent siblings)
  25 siblings, 0 replies; 40+ messages in thread
From: Simon Glass @ 2023-01-06 14:52 UTC (permalink / raw)
  To: U-Boot Mailing List
  Cc: Anatolij Gustschin, Tom Rini, Heinrich Schuchardt, Simon Glass

Convert the long line of if() statements to a switch() since this makes
better use of the C language.

Signed-off-by: Simon Glass <sjg@chromium.org>
---

(no changes since v1)

 common/menu.c | 31 ++++++++++++++++++++-----------
 1 file changed, 20 insertions(+), 11 deletions(-)

diff --git a/common/menu.c b/common/menu.c
index 6842f5409d6..7db98942a61 100644
--- a/common/menu.c
+++ b/common/menu.c
@@ -543,22 +543,31 @@ enum bootmenu_key bootmenu_loop(struct bootmenu_data *menu, int *esc)
 		break;
 	}
 
-	/* enter key was pressed */
-	if (c == '\r')
+	switch (c) {
+	case '\r':
+		/* enter key was pressed */
 		key = BKEY_SELECT;
-
-	/* ^C was pressed */
-	if (c == 0x3)
+		break;
+	case CTL_CH('c'):
+		/* ^C was pressed */
 		key = BKEY_QUIT;
-
-	if (c == '+')
+		break;
+	case CTL_CH('p'):
+		key = BKEY_UP;
+		break;
+	case CTL_CH('n'):
+		key = BKEY_DOWN;
+		break;
+	case '+':
 		key = BKEY_PLUS;
-
-	if (c == '-')
+		break;
+	case '-':
 		key = BKEY_MINUS;
-
-	if (c == ' ')
+		break;
+	case ' ':
 		key = BKEY_SPACE;
+		break;
+	}
 
 	return key;
 }
-- 
2.39.0.314.g84b9a713c41-goog


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

* [PATCH v3 08/25] menu: Make use of CLI character processing
  2023-01-06 14:52 [PATCH v3 00/25] bootstd: Add a boot menu Simon Glass
                   ` (6 preceding siblings ...)
  2023-01-06 14:52 ` [PATCH v3 07/25] menu: Use a switch statement Simon Glass
@ 2023-01-06 14:52 ` Simon Glass
  2023-04-11 20:19   ` Daniel Golle
  2023-01-06 14:52 ` [PATCH v3 09/25] image: Add a function to find a script in an image Simon Glass
                   ` (17 subsequent siblings)
  25 siblings, 1 reply; 40+ messages in thread
From: Simon Glass @ 2023-01-06 14:52 UTC (permalink / raw)
  To: U-Boot Mailing List
  Cc: Anatolij Gustschin, Tom Rini, Heinrich Schuchardt, Simon Glass

Avoid duplicating some of the escape-sequence processing here and use the
CLI function instead.

Signed-off-by: Simon Glass <sjg@chromium.org>
---

(no changes since v2)

Changes in v2:
- Rebase to master

 cmd/bootmenu.c     |  9 +++--
 cmd/eficonfig.c    | 12 ++++--
 common/cli_getch.c | 12 ++++--
 common/menu.c      | 92 +++++++++++++---------------------------------
 include/cli.h      |  4 +-
 include/menu.h     |  7 +++-
 6 files changed, 56 insertions(+), 80 deletions(-)

diff --git a/cmd/bootmenu.c b/cmd/bootmenu.c
index 573afe16609..52d8c2f266d 100644
--- a/cmd/bootmenu.c
+++ b/cmd/bootmenu.c
@@ -4,6 +4,7 @@
  */
 
 #include <charset.h>
+#include <cli.h>
 #include <common.h>
 #include <command.h>
 #include <ansi.h>
@@ -84,19 +85,21 @@ static void bootmenu_print_entry(void *data)
 
 static char *bootmenu_choice_entry(void *data)
 {
+	struct cli_ch_state s_cch, *cch = &s_cch;
 	struct bootmenu_data *menu = data;
 	struct bootmenu_entry *iter;
 	enum bootmenu_key key = BKEY_NONE;
-	int esc = 0;
 	int i;
 
+	cli_ch_init(cch);
+
 	while (1) {
 		if (menu->delay >= 0) {
 			/* Autoboot was not stopped */
-			key = bootmenu_autoboot_loop(menu, &esc);
+			key = bootmenu_autoboot_loop(menu, cch);
 		} else {
 			/* Some key was pressed, so autoboot was stopped */
-			key = bootmenu_loop(menu, &esc);
+			key = bootmenu_loop(menu, cch);
 		}
 
 		switch (key) {
diff --git a/cmd/eficonfig.c b/cmd/eficonfig.c
index 96cb1a367f3..d830e4af53b 100644
--- a/cmd/eficonfig.c
+++ b/cmd/eficonfig.c
@@ -6,6 +6,7 @@
  */
 
 #include <ansi.h>
+#include <cli.h>
 #include <common.h>
 #include <charset.h>
 #include <efi_loader.h>
@@ -184,14 +185,16 @@ static void eficonfig_display_statusline(struct menu *m)
  */
 static char *eficonfig_choice_entry(void *data)
 {
-	int esc = 0;
+	struct cli_ch_state s_cch, *cch = &s_cch;
 	struct list_head *pos, *n;
 	struct eficonfig_entry *entry;
 	enum bootmenu_key key = BKEY_NONE;
 	struct efimenu *efi_menu = data;
 
+	cli_ch_init(cch);
+
 	while (1) {
-		key = bootmenu_loop((struct bootmenu_data *)efi_menu, &esc);
+		key = bootmenu_loop((struct bootmenu_data *)efi_menu, cch);
 
 		switch (key) {
 		case BKEY_UP:
@@ -1862,13 +1865,14 @@ static void eficonfig_display_change_boot_order(struct efimenu *efi_menu)
  */
 static efi_status_t eficonfig_choice_change_boot_order(struct efimenu *efi_menu)
 {
-	int esc = 0;
+	struct cli_ch_state s_cch, *cch = &s_cch;
 	struct list_head *pos, *n;
 	enum bootmenu_key key = BKEY_NONE;
 	struct eficonfig_entry *entry, *tmp;
 
+	cli_ch_init(cch);
 	while (1) {
-		key = bootmenu_loop(NULL, &esc);
+		key = bootmenu_loop(NULL, cch);
 
 		switch (key) {
 		case BKEY_PLUS:
diff --git a/common/cli_getch.c b/common/cli_getch.c
index 9eeea7fef29..87c23edcf4b 100644
--- a/common/cli_getch.c
+++ b/common/cli_getch.c
@@ -140,10 +140,11 @@ int cli_ch_process(struct cli_ch_state *cch, int ichar)
 	 * sequence
 	 */
 	if (!ichar) {
-		if (cch->emit_upto) {
+		if (cch->emitting) {
 			if (cch->emit_upto < cch->esc_len)
 				return cch->esc_save[cch->emit_upto++];
 			cch->emit_upto = 0;
+			cch->emitting = false;
 		}
 		return 0;
 	} else if (ichar == -ETIMEDOUT) {
@@ -174,18 +175,21 @@ int cli_ch_process(struct cli_ch_state *cch, int ichar)
 		case ESC_SAVE:
 			/* save this character and return nothing */
 			cch->esc_save[cch->esc_len++] = ichar;
-			return 0;
+			ichar = 0;
+			break;
 		case ESC_REJECT:
 			/*
 			 * invalid escape sequence, start returning the
 			 * characters in it
 			 */
 			cch->esc_save[cch->esc_len++] = ichar;
-			return cch->esc_save[cch->emit_upto++];
+			ichar = cch->esc_save[cch->emit_upto++];
+			cch->emitting = true;
+			break;
 		case ESC_CONVERTED:
 			/* valid escape sequence, return the resulting char */
 			cch->esc_len = 0;
-			return ichar;
+			break;
 		}
 	}
 
diff --git a/common/menu.c b/common/menu.c
index 7db98942a61..45f36ae3ede 100644
--- a/common/menu.c
+++ b/common/menu.c
@@ -15,6 +15,8 @@
 
 #include "menu.h"
 
+#define ansi 0
+
 /*
  * Internally, each item in a menu is represented by a struct menu_item.
  *
@@ -425,15 +427,19 @@ int menu_destroy(struct menu *m)
 	return 1;
 }
 
-enum bootmenu_key bootmenu_autoboot_loop(struct bootmenu_data *menu, int *esc)
+enum bootmenu_key bootmenu_autoboot_loop(struct bootmenu_data *menu,
+					 struct cli_ch_state *cch)
 {
 	enum bootmenu_key key = BKEY_NONE;
 	int i, c;
 
 	while (menu->delay > 0) {
-		printf(ANSI_CURSOR_POSITION, menu->count + 5, 3);
+		if (ansi)
+			printf(ANSI_CURSOR_POSITION, menu->count + 5, 3);
 		printf("Hit any key to stop autoboot: %d ", menu->delay);
 		for (i = 0; i < 100; ++i) {
+			int ichar;
+
 			if (!tstc()) {
 				schedule();
 				mdelay(10);
@@ -443,12 +449,13 @@ enum bootmenu_key bootmenu_autoboot_loop(struct bootmenu_data *menu, int *esc)
 			menu->delay = -1;
 			c = getchar();
 
-			switch (c) {
-			case '\e':
-				*esc = 1;
+			ichar = cli_ch_process(cch, c);
+
+			switch (ichar) {
+			case '\0':
 				key = BKEY_NONE;
 				break;
-			case '\r':
+			case '\n':
 				key = BKEY_SELECT;
 				break;
 			case 0x3: /* ^C */
@@ -458,7 +465,6 @@ enum bootmenu_key bootmenu_autoboot_loop(struct bootmenu_data *menu, int *esc)
 				key = BKEY_NONE;
 				break;
 			}
-
 			break;
 		}
 
@@ -468,7 +474,8 @@ enum bootmenu_key bootmenu_autoboot_loop(struct bootmenu_data *menu, int *esc)
 		--menu->delay;
 	}
 
-	printf(ANSI_CURSOR_POSITION ANSI_CLEAR_LINE, menu->count + 5, 1);
+	if (ansi)
+		printf(ANSI_CURSOR_POSITION ANSI_CLEAR_LINE, menu->count + 5, 1);
 
 	if (menu->delay == 0)
 		key = BKEY_SELECT;
@@ -476,79 +483,32 @@ enum bootmenu_key bootmenu_autoboot_loop(struct bootmenu_data *menu, int *esc)
 	return key;
 }
 
-enum bootmenu_key bootmenu_loop(struct bootmenu_data *menu, int *esc)
+enum bootmenu_key bootmenu_loop(struct bootmenu_data *menu,
+				struct cli_ch_state *cch)
 {
 	enum bootmenu_key key = BKEY_NONE;
 	int c;
 
-	if (*esc == 1) {
-		if (tstc()) {
-			c = getchar();
-		} else {
+	c = cli_ch_process(cch, 0);
+	if (!c) {
+		while (!c && !tstc()) {
 			schedule();
 			mdelay(10);
-			if (tstc())
-				c = getchar();
-			else
-				c = '\e';
+			c = cli_ch_process(cch, -ETIMEDOUT);
 		}
-	} else {
-		while (!tstc()) {
-			schedule();
-			mdelay(10);
-		}
-		c = getchar();
-	}
-
-	switch (*esc) {
-	case 0:
-		/* First char of ANSI escape sequence '\e' */
-		if (c == '\e') {
-			*esc = 1;
-			key = BKEY_NONE;
-		}
-		break;
-	case 1:
-		/* Second char of ANSI '[' */
-		if (c == '[') {
-			*esc = 2;
-			key = BKEY_NONE;
-		} else {
-		/* Alone ESC key was pressed */
-			key = BKEY_QUIT;
-			*esc = (c == '\e') ? 1 : 0;
-		}
-		break;
-	case 2:
-	case 3:
-		/* Third char of ANSI (number '1') - optional */
-		if (*esc == 2 && c == '1') {
-			*esc = 3;
-			key = BKEY_NONE;
-			break;
+		if (!c) {
+			c = getchar();
+			c = cli_ch_process(cch, c);
 		}
-
-		*esc = 0;
-
-		/* ANSI 'A' - key up was pressed */
-		if (c == 'A')
-			key = BKEY_UP;
-		/* ANSI 'B' - key down was pressed */
-		else if (c == 'B')
-			key = BKEY_DOWN;
-		/* other key was pressed */
-		else
-			key = BKEY_NONE;
-
-		break;
 	}
 
 	switch (c) {
-	case '\r':
+	case '\n':
 		/* enter key was pressed */
 		key = BKEY_SELECT;
 		break;
 	case CTL_CH('c'):
+	case '\e':
 		/* ^C was pressed */
 		key = BKEY_QUIT;
 		break;
diff --git a/include/cli.h b/include/cli.h
index 863519e4b13..c777c90313f 100644
--- a/include/cli.h
+++ b/include/cli.h
@@ -14,12 +14,14 @@
  *
  * @esc_len: Number of escape characters read so far
  * @esc_save: Escape characters collected so far
- * @emit_upto: Next character to emit from esc_save (0 if not emitting)
+ * @emit_upto: Next index to emit from esc_save
+ * @emitting: true if emitting from esc_save
  */
 struct cli_ch_state {
 	int esc_len;
 	char esc_save[8];
 	int emit_upto;
+	bool emitting;
 };
 
 /**
diff --git a/include/menu.h b/include/menu.h
index 8b9b36214f7..3996075a337 100644
--- a/include/menu.h
+++ b/include/menu.h
@@ -6,6 +6,7 @@
 #ifndef __MENU_H__
 #define __MENU_H__
 
+struct cli_ch_state;
 struct menu;
 
 struct menu *menu_create(char *title, int timeout, int prompt,
@@ -71,7 +72,8 @@ enum bootmenu_key {
  *	Ctrl-C: KEY_QUIT
  *	anything else: KEY_NONE
  */
-enum bootmenu_key bootmenu_autoboot_loop(struct bootmenu_data *menu, int *esc);
+enum bootmenu_key bootmenu_autoboot_loop(struct bootmenu_data *menu,
+					 struct cli_ch_state *cch);
 
 /**
  * bootmenu_loop() - handle waiting for a keypress when autoboot is disabled
@@ -96,6 +98,7 @@ enum bootmenu_key bootmenu_autoboot_loop(struct bootmenu_data *menu, int *esc);
  *	Minus: BKEY_MINUS
  *	Space: BKEY_SPACE
  */
-enum bootmenu_key bootmenu_loop(struct bootmenu_data *menu, int *esc);
+enum bootmenu_key bootmenu_loop(struct bootmenu_data *menu,
+				struct cli_ch_state *cch);
 
 #endif /* __MENU_H__ */
-- 
2.39.0.314.g84b9a713c41-goog


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

* [PATCH v3 09/25] image: Add a function to find a script in an image
  2023-01-06 14:52 [PATCH v3 00/25] bootstd: Add a boot menu Simon Glass
                   ` (7 preceding siblings ...)
  2023-01-06 14:52 ` [PATCH v3 08/25] menu: Make use of CLI character processing Simon Glass
@ 2023-01-06 14:52 ` Simon Glass
  2023-01-06 14:52 ` [PATCH v3 10/25] image: Move common image code to image_board and command Simon Glass
                   ` (16 subsequent siblings)
  25 siblings, 0 replies; 40+ messages in thread
From: Simon Glass @ 2023-01-06 14:52 UTC (permalink / raw)
  To: U-Boot Mailing List
  Cc: Anatolij Gustschin, Tom Rini, Heinrich Schuchardt, Simon Glass,
	Joe Hershberger, Marek Vasut

Split this functionality out of the 'source' command so it can be used
from another place. For now leave it where it is, but a future patch will
move it out of cmd/

Signed-off-by: Simon Glass <sjg@chromium.org>
---

(no changes since v1)

 cmd/source.c    | 173 ++++++++++++++++++++++++++----------------------
 include/image.h |  13 ++++
 2 files changed, 107 insertions(+), 79 deletions(-)

diff --git a/cmd/source.c b/cmd/source.c
index 698d9f86d96..dfa995f1df6 100644
--- a/cmd/source.c
+++ b/cmd/source.c
@@ -24,7 +24,6 @@
 #include <asm/byteorder.h>
 #include <asm/io.h>
 
-#if defined(CONFIG_FIT)
 /**
  * get_default_image() - Return default property from /images
  *
@@ -40,123 +39,139 @@ static const char *get_default_image(const void *fit)
 
 	return fdt_getprop(fit, images_noffset, FIT_DEFAULT_PROP, NULL);
 }
-#endif
 
-int image_source_script(ulong addr, const char *fit_uname)
+int image_locate_script(void *buf, int size, const char *fit_uname,
+			char **datap, uint *lenp)
 {
 	ulong		len;
-#if defined(CONFIG_LEGACY_IMAGE_FORMAT)
 	const struct legacy_img_hdr *hdr;
-#endif
 	u32		*data;
 	int		verify;
-	void *buf;
-#if defined(CONFIG_FIT)
 	const void*	fit_hdr;
 	int		noffset;
 	const void	*fit_data;
 	size_t		fit_len;
-#endif
 
 	verify = env_get_yesno("verify");
 
-	buf = map_sysmem(addr, 0);
 	switch (genimg_get_format(buf)) {
-#if defined(CONFIG_LEGACY_IMAGE_FORMAT)
 	case IMAGE_FORMAT_LEGACY:
-		hdr = buf;
+		if (IS_ENABLED(CONFIG_LEGACY_IMAGE_FORMAT)) {
+			hdr = buf;
 
-		if (!image_check_magic (hdr)) {
-			puts ("Bad magic number\n");
-			return 1;
-		}
+			if (!image_check_magic(hdr)) {
+				puts("Bad magic number\n");
+				return 1;
+			}
 
-		if (!image_check_hcrc (hdr)) {
-			puts ("Bad header crc\n");
-			return 1;
-		}
+			if (!image_check_hcrc(hdr)) {
+				puts("Bad header crc\n");
+				return 1;
+			}
+
+			if (verify) {
+				if (!image_check_dcrc(hdr)) {
+					puts("Bad data crc\n");
+					return 1;
+				}
+			}
 
-		if (verify) {
-			if (!image_check_dcrc (hdr)) {
-				puts ("Bad data crc\n");
+			if (!image_check_type(hdr, IH_TYPE_SCRIPT)) {
+				puts("Bad image type\n");
 				return 1;
 			}
-		}
 
-		if (!image_check_type (hdr, IH_TYPE_SCRIPT)) {
-			puts ("Bad image type\n");
-			return 1;
-		}
+			/* get length of script */
+			data = (u32 *)image_get_data(hdr);
 
-		/* get length of script */
-		data = (u32 *)image_get_data (hdr);
+			len = uimage_to_cpu(*data);
+			if (!len) {
+				puts("Empty Script\n");
+				return 1;
+			}
 
-		if ((len = uimage_to_cpu (*data)) == 0) {
-			puts ("Empty Script\n");
-			return 1;
+			/*
+			 * scripts are just multi-image files with one
+			 * component, so seek past the zero-terminated sequence
+			 * of image lengths to get to the actual image data
+			 */
+			while (*data++);
 		}
-
-		/*
-		 * scripts are just multi-image files with one component, seek
-		 * past the zero-terminated sequence of image lengths to get
-		 * to the actual image data
-		 */
-		while (*data++);
 		break;
-#endif
-#if defined(CONFIG_FIT)
 	case IMAGE_FORMAT_FIT:
-		fit_hdr = buf;
-		if (fit_check_format(fit_hdr, IMAGE_SIZE_INVAL)) {
-			puts ("Bad FIT image format\n");
-			return 1;
-		}
+		if (IS_ENABLED(CONFIG_FIT)) {
+			fit_hdr = buf;
+			if (fit_check_format(fit_hdr, IMAGE_SIZE_INVAL)) {
+				puts("Bad FIT image format\n");
+				return 1;
+			}
 
-		if (!fit_uname)
-			fit_uname = get_default_image(fit_hdr);
+			if (!fit_uname)
+				fit_uname = get_default_image(fit_hdr);
 
-		if (!fit_uname) {
-			puts("No FIT subimage unit name\n");
-			return 1;
-		}
+			if (!fit_uname) {
+				puts("No FIT subimage unit name\n");
+				return 1;
+			}
 
-		/* get script component image node offset */
-		noffset = fit_image_get_node (fit_hdr, fit_uname);
-		if (noffset < 0) {
-			printf ("Can't find '%s' FIT subimage\n", fit_uname);
-			return 1;
-		}
+			/* get script component image node offset */
+			noffset = fit_image_get_node(fit_hdr, fit_uname);
+			if (noffset < 0) {
+				printf("Can't find '%s' FIT subimage\n",
+				       fit_uname);
+				return 1;
+			}
 
-		if (!fit_image_check_type (fit_hdr, noffset, IH_TYPE_SCRIPT)) {
-			puts ("Not a image image\n");
-			return 1;
-		}
+			if (!fit_image_check_type(fit_hdr, noffset,
+						  IH_TYPE_SCRIPT)) {
+				puts("Not a image image\n");
+				return 1;
+			}
+
+			/* verify integrity */
+			if (verify) {
+				if (!fit_image_verify(fit_hdr, noffset)) {
+					puts("Bad Data Hash\n");
+					return 1;
+				}
+			}
 
-		/* verify integrity */
-		if (verify) {
-			if (!fit_image_verify(fit_hdr, noffset)) {
-				puts ("Bad Data Hash\n");
+			/* get script subimage data address and length */
+			if (fit_image_get_data(fit_hdr, noffset, &fit_data, &fit_len)) {
+				puts("Could not find script subimage data\n");
 				return 1;
 			}
-		}
 
-		/* get script subimage data address and length */
-		if (fit_image_get_data (fit_hdr, noffset, &fit_data, &fit_len)) {
-			puts ("Could not find script subimage data\n");
-			return 1;
+			data = (u32 *)fit_data;
+			len = (ulong)fit_len;
 		}
-
-		data = (u32 *)fit_data;
-		len = (ulong)fit_len;
 		break;
-#endif
 	default:
-		puts ("Wrong image format for \"source\" command\n");
-		return 1;
+		puts("Wrong image format for \"source\" command\n");
+		return -EPERM;
 	}
 
-	debug("** Script length: %ld\n", len);
-	return run_command_list((char *)data, len, 0);
+	*datap = (char *)data;
+	*lenp = len;
+
+	return 0;
+}
+
+int image_source_script(ulong addr, const char *fit_uname)
+{
+	char *data;
+	void *buf;
+	uint len;
+	int ret;
+
+	buf = map_sysmem(addr, 0);
+	ret = image_locate_script(buf, 0, fit_uname, &data, &len);
+	unmap_sysmem(buf);
+	if (ret)
+		return CMD_RET_FAILURE;
+
+	debug("** Script length: %d\n", len);
+	return run_command_list(data, len, 0);
 }
 
 /**************************************************/
diff --git a/include/image.h b/include/image.h
index 6f21dafba8c..1e67335eedc 100644
--- a/include/image.h
+++ b/include/image.h
@@ -720,6 +720,19 @@ int fit_image_load(struct bootm_headers *images, ulong addr,
  */
 int image_source_script(ulong addr, const char *fit_uname);
 
+/**
+ * image_locate_script() - Locate the raw script in an image
+ *
+ * @buf: Address of image
+ * @size: Size of image in bytes
+ * @fit_uname: Node name of FIT image to read
+ * @datap: Returns pointer to raw script on success
+ * @lenp: Returns size of raw script on success
+ * @return 0 if OK, non-zero on error
+ */
+int image_locate_script(void *buf, int size, const char *fit_uname,
+			char **datap, uint *lenp);
+
 /**
  * fit_get_node_from_config() - Look up an image a FIT by type
  *
-- 
2.39.0.314.g84b9a713c41-goog


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

* [PATCH v3 10/25] image: Move common image code to image_board and command
  2023-01-06 14:52 [PATCH v3 00/25] bootstd: Add a boot menu Simon Glass
                   ` (8 preceding siblings ...)
  2023-01-06 14:52 ` [PATCH v3 09/25] image: Add a function to find a script in an image Simon Glass
@ 2023-01-06 14:52 ` Simon Glass
  2023-01-06 14:52 ` [PATCH v3 11/25] video: Enable VIDEO_ANSI by default only with EFI Simon Glass
                   ` (15 subsequent siblings)
  25 siblings, 0 replies; 40+ messages in thread
From: Simon Glass @ 2023-01-06 14:52 UTC (permalink / raw)
  To: U-Boot Mailing List
  Cc: Anatolij Gustschin, Tom Rini, Heinrich Schuchardt, Simon Glass,
	Joe Hershberger, Marek Vasut

We should use the cmd/ directory for commands rather than for common code
used elsewhere in U-Boot. Move the common 'source' code into
image-board.c to achieve this.

The image_source_script() function needs to call run_command_list()  so
seems to belong better in the command library. Move and rename it.

Signed-off-by: Simon Glass <sjg@chromium.org>
---

(no changes since v1)

 .../cmd_stm32prog/cmd_stm32prog.c             |   2 +-
 boot/bootmeth_script.c                        |   2 +-
 boot/image-board.c                            | 133 +++++++++++++++
 cmd/source.c                                  | 155 +-----------------
 common/command.c                              |  19 +++
 drivers/usb/gadget/f_sdp.c                    |   2 +-
 include/command.h                             |  12 ++
 include/image.h                               |  12 --
 8 files changed, 168 insertions(+), 169 deletions(-)

diff --git a/arch/arm/mach-stm32mp/cmd_stm32prog/cmd_stm32prog.c b/arch/arm/mach-stm32mp/cmd_stm32prog/cmd_stm32prog.c
index 007f7131306..6fb3d868d77 100644
--- a/arch/arm/mach-stm32mp/cmd_stm32prog/cmd_stm32prog.c
+++ b/arch/arm/mach-stm32mp/cmd_stm32prog/cmd_stm32prog.c
@@ -154,7 +154,7 @@ static int do_stm32prog(struct cmd_tbl *cmdtp, int flag, int argc,
 			do_bootz(cmdtp, 0, 4, bootm_argv);
 	}
 	if (data->script)
-		image_source_script(data->script, "script@stm32prog");
+		cmd_source_script(data->script, "script@stm32prog");
 
 	if (reset) {
 		puts("Reset...\n");
diff --git a/boot/bootmeth_script.c b/boot/bootmeth_script.c
index d1c3f940037..16c5dadcca1 100644
--- a/boot/bootmeth_script.c
+++ b/boot/bootmeth_script.c
@@ -101,7 +101,7 @@ static int script_boot(struct udevice *dev, struct bootflow *bflow)
 	log_debug("mmc_bootdev: %s\n", env_get("mmc_bootdev"));
 
 	addr = map_to_sysmem(bflow->buf);
-	ret = image_source_script(addr, NULL);
+	ret = cmd_source_script(addr, NULL);
 	if (ret)
 		return log_msg_ret("boot", ret);
 
diff --git a/boot/image-board.c b/boot/image-board.c
index 34d1e5f18be..7f96a80c85b 100644
--- a/boot/image-board.c
+++ b/boot/image-board.c
@@ -971,3 +971,136 @@ void genimg_print_time(time_t timestamp)
 	       tm.tm_year, tm.tm_mon, tm.tm_mday,
 	       tm.tm_hour, tm.tm_min, tm.tm_sec);
 }
+
+/**
+ * get_default_image() - Return default property from /images
+ *
+ * Return: Pointer to value of default property (or NULL)
+ */
+static const char *get_default_image(const void *fit)
+{
+	int images_noffset;
+
+	images_noffset = fdt_path_offset(fit, FIT_IMAGES_PATH);
+	if (images_noffset < 0)
+		return NULL;
+
+	return fdt_getprop(fit, images_noffset, FIT_DEFAULT_PROP, NULL);
+}
+
+int image_locate_script(void *buf, int size, const char *fit_uname,
+			char **datap, uint *lenp)
+{
+	const struct legacy_img_hdr *hdr;
+	const void *fit_data;
+	const void *fit_hdr;
+	size_t fit_len;
+	int noffset;
+	int verify;
+	ulong len;
+	u32 *data;
+
+	verify = env_get_yesno("verify");
+
+	switch (genimg_get_format(buf)) {
+	case IMAGE_FORMAT_LEGACY:
+		if (IS_ENABLED(CONFIG_LEGACY_IMAGE_FORMAT)) {
+			hdr = buf;
+
+			if (!image_check_magic(hdr)) {
+				puts("Bad magic number\n");
+				return 1;
+			}
+
+			if (!image_check_hcrc(hdr)) {
+				puts("Bad header crc\n");
+				return 1;
+			}
+
+			if (verify) {
+				if (!image_check_dcrc(hdr)) {
+					puts("Bad data crc\n");
+					return 1;
+				}
+			}
+
+			if (!image_check_type(hdr, IH_TYPE_SCRIPT)) {
+				puts("Bad image type\n");
+				return 1;
+			}
+
+			/* get length of script */
+			data = (u32 *)image_get_data(hdr);
+
+			len = uimage_to_cpu(*data);
+			if (!len) {
+				puts("Empty Script\n");
+				return 1;
+			}
+
+			/*
+			 * scripts are just multi-image files with one
+			 * component, so seek past the zero-terminated sequence
+			 * of image lengths to get to the actual image data
+			 */
+			while (*data++);
+		}
+		break;
+	case IMAGE_FORMAT_FIT:
+		if (IS_ENABLED(CONFIG_FIT)) {
+			fit_hdr = buf;
+			if (fit_check_format(fit_hdr, IMAGE_SIZE_INVAL)) {
+				puts("Bad FIT image format\n");
+				return 1;
+			}
+
+			if (!fit_uname)
+				fit_uname = get_default_image(fit_hdr);
+
+			if (!fit_uname) {
+				puts("No FIT subimage unit name\n");
+				return 1;
+			}
+
+			/* get script component image node offset */
+			noffset = fit_image_get_node(fit_hdr, fit_uname);
+			if (noffset < 0) {
+				printf("Can't find '%s' FIT subimage\n",
+				       fit_uname);
+				return 1;
+			}
+
+			if (!fit_image_check_type(fit_hdr, noffset,
+						  IH_TYPE_SCRIPT)) {
+				puts("Not a image image\n");
+				return 1;
+			}
+
+			/* verify integrity */
+			if (verify) {
+				if (!fit_image_verify(fit_hdr, noffset)) {
+					puts("Bad Data Hash\n");
+					return 1;
+				}
+			}
+
+			/* get script subimage data address and length */
+			if (fit_image_get_data(fit_hdr, noffset, &fit_data, &fit_len)) {
+				puts("Could not find script subimage data\n");
+				return 1;
+			}
+
+			data = (u32 *)fit_data;
+			len = (ulong)fit_len;
+		}
+		break;
+	default:
+		puts("Wrong image format for \"source\" command\n");
+		return -EPERM;
+	}
+
+	*datap = (char *)data;
+	*lenp = len;
+
+	return 0;
+}
diff --git a/cmd/source.c b/cmd/source.c
index dfa995f1df6..04a086b3582 100644
--- a/cmd/source.c
+++ b/cmd/source.c
@@ -24,158 +24,6 @@
 #include <asm/byteorder.h>
 #include <asm/io.h>
 
-/**
- * get_default_image() - Return default property from /images
- *
- * Return: Pointer to value of default property (or NULL)
- */
-static const char *get_default_image(const void *fit)
-{
-	int images_noffset;
-
-	images_noffset = fdt_path_offset(fit, FIT_IMAGES_PATH);
-	if (images_noffset < 0)
-		return NULL;
-
-	return fdt_getprop(fit, images_noffset, FIT_DEFAULT_PROP, NULL);
-}
-
-int image_locate_script(void *buf, int size, const char *fit_uname,
-			char **datap, uint *lenp)
-{
-	ulong		len;
-	const struct legacy_img_hdr *hdr;
-	u32		*data;
-	int		verify;
-	const void*	fit_hdr;
-	int		noffset;
-	const void	*fit_data;
-	size_t		fit_len;
-
-	verify = env_get_yesno("verify");
-
-	switch (genimg_get_format(buf)) {
-	case IMAGE_FORMAT_LEGACY:
-		if (IS_ENABLED(CONFIG_LEGACY_IMAGE_FORMAT)) {
-			hdr = buf;
-
-			if (!image_check_magic(hdr)) {
-				puts("Bad magic number\n");
-				return 1;
-			}
-
-			if (!image_check_hcrc(hdr)) {
-				puts("Bad header crc\n");
-				return 1;
-			}
-
-			if (verify) {
-				if (!image_check_dcrc(hdr)) {
-					puts("Bad data crc\n");
-					return 1;
-				}
-			}
-
-			if (!image_check_type(hdr, IH_TYPE_SCRIPT)) {
-				puts("Bad image type\n");
-				return 1;
-			}
-
-			/* get length of script */
-			data = (u32 *)image_get_data(hdr);
-
-			len = uimage_to_cpu(*data);
-			if (!len) {
-				puts("Empty Script\n");
-				return 1;
-			}
-
-			/*
-			 * scripts are just multi-image files with one
-			 * component, so seek past the zero-terminated sequence
-			 * of image lengths to get to the actual image data
-			 */
-			while (*data++);
-		}
-		break;
-	case IMAGE_FORMAT_FIT:
-		if (IS_ENABLED(CONFIG_FIT)) {
-			fit_hdr = buf;
-			if (fit_check_format(fit_hdr, IMAGE_SIZE_INVAL)) {
-				puts("Bad FIT image format\n");
-				return 1;
-			}
-
-			if (!fit_uname)
-				fit_uname = get_default_image(fit_hdr);
-
-			if (!fit_uname) {
-				puts("No FIT subimage unit name\n");
-				return 1;
-			}
-
-			/* get script component image node offset */
-			noffset = fit_image_get_node(fit_hdr, fit_uname);
-			if (noffset < 0) {
-				printf("Can't find '%s' FIT subimage\n",
-				       fit_uname);
-				return 1;
-			}
-
-			if (!fit_image_check_type(fit_hdr, noffset,
-						  IH_TYPE_SCRIPT)) {
-				puts("Not a image image\n");
-				return 1;
-			}
-
-			/* verify integrity */
-			if (verify) {
-				if (!fit_image_verify(fit_hdr, noffset)) {
-					puts("Bad Data Hash\n");
-					return 1;
-				}
-			}
-
-			/* get script subimage data address and length */
-			if (fit_image_get_data(fit_hdr, noffset, &fit_data, &fit_len)) {
-				puts("Could not find script subimage data\n");
-				return 1;
-			}
-
-			data = (u32 *)fit_data;
-			len = (ulong)fit_len;
-		}
-		break;
-	default:
-		puts("Wrong image format for \"source\" command\n");
-		return -EPERM;
-	}
-
-	*datap = (char *)data;
-	*lenp = len;
-
-	return 0;
-}
-
-int image_source_script(ulong addr, const char *fit_uname)
-{
-	char *data;
-	void *buf;
-	uint len;
-	int ret;
-
-	buf = map_sysmem(addr, 0);
-	ret = image_locate_script(buf, 0, fit_uname, &data, &len);
-	unmap_sysmem(buf);
-	if (ret)
-		return CMD_RET_FAILURE;
-
-	debug("** Script length: %d\n", len);
-	return run_command_list(data, len, 0);
-}
-
-/**************************************************/
-#if defined(CONFIG_CMD_SOURCE)
 static int do_source(struct cmd_tbl *cmdtp, int flag, int argc,
 		     char *const argv[])
 {
@@ -199,7 +47,7 @@ static int do_source(struct cmd_tbl *cmdtp, int flag, int argc,
 	}
 
 	printf ("## Executing script at %08lx\n", addr);
-	rcode = image_source_script(addr, fit_uname);
+	rcode = cmd_source_script(addr, fit_uname);
 	return rcode;
 }
 
@@ -220,4 +68,3 @@ U_BOOT_CMD(
 	source, 2, 0,	do_source,
 	"run script from memory", source_help_text
 );
-#endif
diff --git a/common/command.c b/common/command.c
index 41c91c6d8c8..1f52a15bc12 100644
--- a/common/command.c
+++ b/common/command.c
@@ -13,7 +13,9 @@
 #include <command.h>
 #include <console.h>
 #include <env.h>
+#include <image.h>
 #include <log.h>
+#include <mapmem.h>
 #include <asm/global_data.h>
 #include <linux/ctype.h>
 
@@ -654,3 +656,20 @@ int cmd_process_error(struct cmd_tbl *cmdtp, int err)
 
 	return CMD_RET_SUCCESS;
 }
+
+int cmd_source_script(ulong addr, const char *fit_uname)
+{
+	char *data;
+	void *buf;
+	uint len;
+	int ret;
+
+	buf = map_sysmem(addr, 0);
+	ret = image_locate_script(buf, 0, fit_uname, &data, &len);
+	unmap_sysmem(buf);
+	if (ret)
+		return CMD_RET_FAILURE;
+
+	debug("** Script length: %d\n", len);
+	return run_command_list(data, len, 0);
+}
diff --git a/drivers/usb/gadget/f_sdp.c b/drivers/usb/gadget/f_sdp.c
index af4b167e17a..282c15ce21f 100644
--- a/drivers/usb/gadget/f_sdp.c
+++ b/drivers/usb/gadget/f_sdp.c
@@ -868,7 +868,7 @@ static int sdp_handle_in_ep(struct spl_image_info *spl_image,
 			jump_to_image_no_args(&spl_image);
 #else
 			/* In U-Boot, allow jumps to scripts */
-			image_source_script(sdp_func->jmp_address, "script@1");
+			cmd_source_script(sdp_func->jmp_address, "script@1");
 #endif
 		}
 
diff --git a/include/command.h b/include/command.h
index 966fd23c639..f32d41ccc34 100644
--- a/include/command.h
+++ b/include/command.h
@@ -279,6 +279,18 @@ int run_commandf(const char *fmt, ...);
  * Return: 0 on success, or != 0 on error.
  */
 int run_command_list(const char *cmd, int len, int flag);
+
+/**
+ * cmd_source_script() - Execute a script
+ *
+ * Executes a U-Boot script at a particular address in memory. The script should
+ * have a header (FIT or legacy) with the script type (IH_TYPE_SCRIPT).
+ *
+ * @addr: Address of script
+ * @fit_uname: FIT subimage name
+ * Return: result code (enum command_ret_t)
+ */
+int cmd_source_script(ulong addr, const char *fit_uname);
 #endif	/* __ASSEMBLY__ */
 
 /*
diff --git a/include/image.h b/include/image.h
index 1e67335eedc..7b794f2ce49 100644
--- a/include/image.h
+++ b/include/image.h
@@ -708,18 +708,6 @@ int fit_image_load(struct bootm_headers *images, ulong addr,
 		   int arch, int image_ph_type, int bootstage_id,
 		   enum fit_load_op load_op, ulong *datap, ulong *lenp);
 
-/**
- * image_source_script() - Execute a script
- *
- * Executes a U-Boot script at a particular address in memory. The script should
- * have a header (FIT or legacy) with the script type (IH_TYPE_SCRIPT).
- *
- * @addr: Address of script
- * @fit_uname: FIT subimage name
- * Return: result code (enum command_ret_t)
- */
-int image_source_script(ulong addr, const char *fit_uname);
-
 /**
  * image_locate_script() - Locate the raw script in an image
  *
-- 
2.39.0.314.g84b9a713c41-goog


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

* [PATCH v3 11/25] video: Enable VIDEO_ANSI by default only with EFI
  2023-01-06 14:52 [PATCH v3 00/25] bootstd: Add a boot menu Simon Glass
                   ` (9 preceding siblings ...)
  2023-01-06 14:52 ` [PATCH v3 10/25] image: Move common image code to image_board and command Simon Glass
@ 2023-01-06 14:52 ` Simon Glass
  2023-01-06 14:52 ` [PATCH v3 12/25] video: truetype: Rename the metrics function Simon Glass
                   ` (14 subsequent siblings)
  25 siblings, 0 replies; 40+ messages in thread
From: Simon Glass @ 2023-01-06 14:52 UTC (permalink / raw)
  To: U-Boot Mailing List
  Cc: Anatolij Gustschin, Tom Rini, Heinrich Schuchardt, Simon Glass

This is not generally needed unless EFI_LOADER is used. Adjust the default
setting to reduce the size of the U-Boot build.

Signed-off-by: Simon Glass <sjg@chromium.org>
---

(no changes since v1)

 drivers/video/Kconfig | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig
index c841b99bb30..0c914dc1165 100644
--- a/drivers/video/Kconfig
+++ b/drivers/video/Kconfig
@@ -112,10 +112,13 @@ config VIDEO_BPP32
 
 config VIDEO_ANSI
 	bool "Support ANSI escape sequences in video console"
-	default y
+	default y if EFI_LOADER
 	help
 	  Enable ANSI escape sequence decoding for a more fully functional
-	  console.
+	  console. Functionality includes changing the text colour and moving
+	  the cursor. These date from the 1970s and are still widely used today
+	  to control a text terminal. U-Boot implements these by decoding the
+	  sequences and performing the appropriate operation.
 
 config VIDEO_MIPI_DSI
 	bool "Support MIPI DSI interface"
-- 
2.39.0.314.g84b9a713c41-goog


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

* [PATCH v3 12/25] video: truetype: Rename the metrics function
  2023-01-06 14:52 [PATCH v3 00/25] bootstd: Add a boot menu Simon Glass
                   ` (10 preceding siblings ...)
  2023-01-06 14:52 ` [PATCH v3 11/25] video: Enable VIDEO_ANSI by default only with EFI Simon Glass
@ 2023-01-06 14:52 ` Simon Glass
  2023-01-06 14:52 ` [PATCH v3 13/25] video: Fix unchnaged typo Simon Glass
                   ` (13 subsequent siblings)
  25 siblings, 0 replies; 40+ messages in thread
From: Simon Glass @ 2023-01-06 14:52 UTC (permalink / raw)
  To: U-Boot Mailing List
  Cc: Anatolij Gustschin, Tom Rini, Heinrich Schuchardt, Simon Glass

This should really have a 'truetype' prefix. Fix it.

Signed-off-by: Simon Glass <sjg@chromium.org>
---

(no changes since v1)

 drivers/video/console_truetype.c | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/drivers/video/console_truetype.c b/drivers/video/console_truetype.c
index 6859c9fa116..4abc0bc2ff1 100644
--- a/drivers/video/console_truetype.c
+++ b/drivers/video/console_truetype.c
@@ -595,7 +595,7 @@ void vidconsole_list_fonts(void)
 }
 
 /**
- * vidconsole_add_metrics() - Add a new font/size combination
+ * truetype_add_metrics() - Add a new font/size combination
  *
  * @dev:	Video console device to update
  * @font_name:	Name of font
@@ -604,8 +604,8 @@ void vidconsole_list_fonts(void)
  * @return 0 if OK, -EPERM if stbtt failed, -E2BIG if the the metrics table is
  *	full
  */
-static int vidconsole_add_metrics(struct udevice *dev, const char *font_name,
-				  uint font_size, const void *font_data)
+static int truetype_add_metrics(struct udevice *dev, const char *font_name,
+				uint font_size, const void *font_data)
 {
 	struct console_tt_priv *priv = dev_get_priv(dev);
 	struct console_tt_metrics *met;
@@ -693,7 +693,7 @@ int vidconsole_select_font(struct udevice *dev, const char *name, uint size)
 				    !strcmp(name, tab->name)) {
 					int ret;
 
-					ret = vidconsole_add_metrics(dev,
+					ret = truetype_add_metrics(dev,
 						tab->name, size, tab->begin);
 					if (ret < 0)
 						return log_msg_ret("add", ret);
@@ -745,7 +745,7 @@ static int console_truetype_probe(struct udevice *dev)
 		return -EBFONT;
 	}
 
-	ret = vidconsole_add_metrics(dev, tab->name, font_size, tab->begin);
+	ret = truetype_add_metrics(dev, tab->name, font_size, tab->begin);
 	if (ret < 0)
 		return log_msg_ret("add", ret);
 	priv->cur_met = &priv->metrics[ret];
-- 
2.39.0.314.g84b9a713c41-goog


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

* [PATCH v3 13/25] video: Fix unchnaged typo
  2023-01-06 14:52 [PATCH v3 00/25] bootstd: Add a boot menu Simon Glass
                   ` (11 preceding siblings ...)
  2023-01-06 14:52 ` [PATCH v3 12/25] video: truetype: Rename the metrics function Simon Glass
@ 2023-01-06 14:52 ` Simon Glass
  2023-01-06 14:52 ` [PATCH v3 14/25] video: Add font functions to the vidconsole API Simon Glass
                   ` (12 subsequent siblings)
  25 siblings, 0 replies; 40+ messages in thread
From: Simon Glass @ 2023-01-06 14:52 UTC (permalink / raw)
  To: U-Boot Mailing List
  Cc: Anatolij Gustschin, Tom Rini, Heinrich Schuchardt, Simon Glass

Fix this typo in the header file.

Signed-off-by: Simon Glass <sjg@chromium.org>
Reviewed-by: Heinrich Schuchardt <xypron.glpk@gmx.de>
---

(no changes since v1)

 include/video.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/include/video.h b/include/video.h
index 43f2e2c02f0..3f67a93bc93 100644
--- a/include/video.h
+++ b/include/video.h
@@ -248,7 +248,7 @@ void video_bmp_get_info(void *bmp_image, ulong *widthp, ulong *heightp,
  *		  that direction
  *		- if a coordinate is -ve then it will be offset to the
  *		  left/top of the centre by that many pixels
- *		- if a coordinate is positive it will be used unchnaged.
+ *		- if a coordinate is positive it will be used unchanged.
  * Return: 0 if OK, -ve on error
  */
 int video_bmp_display(struct udevice *dev, ulong bmp_image, int x, int y,
-- 
2.39.0.314.g84b9a713c41-goog


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

* [PATCH v3 14/25] video: Add font functions to the vidconsole API
  2023-01-06 14:52 [PATCH v3 00/25] bootstd: Add a boot menu Simon Glass
                   ` (12 preceding siblings ...)
  2023-01-06 14:52 ` [PATCH v3 13/25] video: Fix unchnaged typo Simon Glass
@ 2023-01-06 14:52 ` Simon Glass
  2023-01-06 14:52 ` [PATCH v3 15/25] bootstd: Read the Operating System name for distro/scripts Simon Glass
                   ` (11 subsequent siblings)
  25 siblings, 0 replies; 40+ messages in thread
From: Simon Glass @ 2023-01-06 14:52 UTC (permalink / raw)
  To: U-Boot Mailing List
  Cc: Anatolij Gustschin, Tom Rini, Heinrich Schuchardt, Simon Glass

Support for fonts currently depends on the type of vidconsole in use. Add
two new methods to enumerate fonts and to set the font.

Fix a few other method comments while we are here.

Signed-off-by: Simon Glass <sjg@chromium.org>
---

(no changes since v2)

Changes in v2:
- Rename vidconsole_get_font() to vidconsole_get_font_size()

 cmd/font.c                        | 11 ++++-
 drivers/video/console_truetype.c  | 27 +++++++----
 drivers/video/vidconsole-uclass.c | 33 ++++++++++++++
 include/video_console.h           | 75 ++++++++++++++++++++++++++-----
 test/cmd/font.c                   |  6 +--
 5 files changed, 127 insertions(+), 25 deletions(-)

diff --git a/cmd/font.c b/cmd/font.c
index 3e522f3aaa1..7b4347f32b5 100644
--- a/cmd/font.c
+++ b/cmd/font.c
@@ -15,7 +15,11 @@
 static int do_font_list(struct cmd_tbl *cmdtp, int flag, int argc,
 			char *const argv[])
 {
-	vidconsole_list_fonts();
+	struct udevice *dev;
+
+	if (uclass_first_device_err(UCLASS_VIDEO_CONSOLE, &dev))
+		return CMD_RET_FAILURE;
+	vidconsole_list_fonts(dev);
 
 	return 0;
 }
@@ -47,6 +51,7 @@ static int do_font_select(struct cmd_tbl *cmdtp, int flag, int argc,
 static int do_font_size(struct cmd_tbl *cmdtp, int flag, int argc,
 			char *const argv[])
 {
+	const char *font_name;
 	struct udevice *dev;
 	uint size;
 	int ret;
@@ -56,9 +61,11 @@ static int do_font_size(struct cmd_tbl *cmdtp, int flag, int argc,
 
 	if (uclass_first_device_err(UCLASS_VIDEO_CONSOLE, &dev))
 		return CMD_RET_FAILURE;
+	font_name = vidconsole_get_font_size(dev, &size);
 
 	size = dectoul(argv[1], NULL);
-	ret = vidconsole_select_font(dev, NULL, size);
+
+	ret = vidconsole_select_font(dev, font_name, size);
 	if (ret) {
 		printf("Failed (error %d)\n", ret);
 		return CMD_RET_FAILURE;
diff --git a/drivers/video/console_truetype.c b/drivers/video/console_truetype.c
index 4abc0bc2ff1..9cac9a6de4d 100644
--- a/drivers/video/console_truetype.c
+++ b/drivers/video/console_truetype.c
@@ -584,14 +584,20 @@ static struct font_info *console_truetype_find_font(void)
 	return NULL;
 }
 
-void vidconsole_list_fonts(void)
+int console_truetype_get_font(struct udevice *dev, int seq,
+			      struct vidfont_info *info)
 {
 	struct font_info *tab;
+	int i;
 
-	for (tab = font_table; tab->begin; tab++) {
-		if (abs(tab->begin - tab->end) > 4)
-			printf("%s\n", tab->name);
+	for (i = 0, tab = font_table; tab->begin; tab++, i++) {
+		if (i == seq && font_valid(tab)) {
+			info->name = tab->name;
+			return 0;
+		}
 	}
+
+	return -ENOENT;
 }
 
 /**
@@ -674,7 +680,8 @@ static void select_metrics(struct udevice *dev, struct console_tt_metrics *met)
 	vc_priv->tab_width_frac = VID_TO_POS(met->font_size) * 8 / 2;
 }
 
-int vidconsole_select_font(struct udevice *dev, const char *name, uint size)
+static int truetype_select_font(struct udevice *dev, const char *name,
+				uint size)
 {
 	struct console_tt_priv *priv = dev_get_priv(dev);
 	struct console_tt_metrics *met;
@@ -684,7 +691,7 @@ int vidconsole_select_font(struct udevice *dev, const char *name, uint size)
 		if (!size)
 			size = CONFIG_CONSOLE_TRUETYPE_SIZE;
 		if (!name)
-			name = priv->cur_met->font_name;
+			name = font_table->name;
 
 		met = find_metrics(dev, name, size);
 		if (!met) {
@@ -694,7 +701,9 @@ int vidconsole_select_font(struct udevice *dev, const char *name, uint size)
 					int ret;
 
 					ret = truetype_add_metrics(dev,
-						tab->name, size, tab->begin);
+								   tab->name,
+								   size,
+								   tab->begin);
 					if (ret < 0)
 						return log_msg_ret("add", ret);
 
@@ -715,7 +724,7 @@ int vidconsole_select_font(struct udevice *dev, const char *name, uint size)
 	return 0;
 }
 
-const char *vidconsole_get_font(struct udevice *dev, uint *sizep)
+const char *vidconsole_get_font_size(struct udevice *dev, uint *sizep)
 {
 	struct console_tt_priv *priv = dev_get_priv(dev);
 	struct console_tt_metrics *met = priv->cur_met;
@@ -763,6 +772,8 @@ struct vidconsole_ops console_truetype_ops = {
 	.set_row	= console_truetype_set_row,
 	.backspace	= console_truetype_backspace,
 	.entry_start	= console_truetype_entry_start,
+	.get_font	= console_truetype_get_font,
+	.select_font	= truetype_select_font,
 };
 
 U_BOOT_DRIVER(vidconsole_truetype) = {
diff --git a/drivers/video/vidconsole-uclass.c b/drivers/video/vidconsole-uclass.c
index 6bdfb6e37dd..aadd54bfd4f 100644
--- a/drivers/video/vidconsole-uclass.c
+++ b/drivers/video/vidconsole-uclass.c
@@ -557,6 +557,39 @@ static void vidconsole_puts(struct stdio_dev *sdev, const char *s)
 	}
 }
 
+void vidconsole_list_fonts(struct udevice *dev)
+{
+	struct vidfont_info info;
+	int ret, i;
+
+	for (i = 0, ret = 0; !ret; i++) {
+		ret = vidconsole_get_font(dev, i, &info);
+		if (!ret)
+			printf("%s\n", info.name);
+	}
+}
+
+int vidconsole_get_font(struct udevice *dev, int seq,
+			struct vidfont_info *info)
+{
+	struct vidconsole_ops *ops = vidconsole_get_ops(dev);
+
+	if (!ops->get_font)
+		return -ENOSYS;
+
+	return ops->get_font(dev, seq, info);
+}
+
+int vidconsole_select_font(struct udevice *dev, const char *name, uint size)
+{
+	struct vidconsole_ops *ops = vidconsole_get_ops(dev);
+
+	if (!ops->select_font)
+		return -ENOSYS;
+
+	return ops->select_font(dev, name, size);
+}
+
 /* Set up the number of rows and colours (rotated drivers override this) */
 static int vidconsole_pre_probe(struct udevice *dev)
 {
diff --git a/include/video_console.h b/include/video_console.h
index d755eb73cf2..9d2c0f210e4 100644
--- a/include/video_console.h
+++ b/include/video_console.h
@@ -62,6 +62,15 @@ struct vidconsole_priv {
 	char escape_buf[32];
 };
 
+/**
+ * struct vidfont_info - information about a font
+ *
+ * @name: Font name, e.g. nimbus_sans_l_regular
+ */
+struct vidfont_info {
+	const char *name;
+};
+
 /**
  * struct vidconsole_ops - Video console operations
  *
@@ -111,6 +120,9 @@ struct vidconsole_ops {
 	/**
 	 * entry_start() - Indicate that text entry is starting afresh
 	 *
+	 * @dev:	Device to adjust
+	 * Returns: 0 on success, -ve on error
+	 *
 	 * Consoles which use proportional fonts need to track the position of
 	 * each character output so that backspace will return to the correct
 	 * place. This method signals to the console driver that a new entry
@@ -123,6 +135,9 @@ struct vidconsole_ops {
 	/**
 	 * backspace() - Handle erasing the last character
 	 *
+	 * @dev:	Device to adjust
+	 * Returns: 0 on success, -ve on error
+	 *
 	 * With proportional fonts the vidconsole uclass cannot itself erase
 	 * the previous character. This optional method will be called when
 	 * a backspace is needed. The driver should erase the previous
@@ -133,11 +148,53 @@ struct vidconsole_ops {
 	 * characters.
 	 */
 	int (*backspace)(struct udevice *dev);
+
+	/**
+	 * get_font() - Obtain information about a font (optional)
+	 *
+	 * @dev:	Device to check
+	 * @seq:	Font number to query (0=first, 1=second, etc.)
+	 * @info:	Returns font information on success
+	 * Returns: 0 on success, -ENOENT if no such font
+	 */
+	int (*get_font)(struct udevice *dev, int seq,
+			struct vidfont_info *info);
+
+	/**
+	 * select_font() - Select a particular font by name / size
+	 *
+	 * @dev:	Device to adjust
+	 * @name:	Font name to use (NULL to use default)
+	 * @size:	Font size to use (0 to use default)
+	 * Returns: 0 on success, -ENOENT if no such font
+	 */
+	int (*select_font)(struct udevice *dev, const char *name, uint size);
 };
 
 /* Get a pointer to the driver operations for a video console device */
 #define vidconsole_get_ops(dev)  ((struct vidconsole_ops *)(dev)->driver->ops)
 
+/**
+ * vidconsole_get_font() - Obtain information about a font
+ *
+ * @dev:	Device to check
+ * @seq:	Font number to query (0=first, 1=second, etc.)
+ * @info:	Returns font information on success
+ * Returns: 0 on success, -ENOENT if no such font, -ENOSYS if there is no such
+ * method
+ */
+int vidconsole_get_font(struct udevice *dev, int seq,
+			struct vidfont_info *info);
+
+/**
+ * vidconsole_select_font() - Select a particular font by name / size
+ *
+ * @dev:	Device to adjust
+ * @name:	Font name to use (NULL to use default)
+ * @size:	Font size to use (0 to use default)
+ */
+int vidconsole_select_font(struct udevice *dev, const char *name, uint size);
+
 /**
  * vidconsole_putc_xy() - write a single character to a position
  *
@@ -234,27 +291,21 @@ void vidconsole_set_cursor_pos(struct udevice *dev, int x, int y);
 /**
  * vidconsole_list_fonts() - List the available fonts
  *
- * This shows a list on the console
- */
-void vidconsole_list_fonts(void);
-
-/**
- * vidconsole_select_font() - Select a font to use
+ * @dev: vidconsole device to check
  *
- * @dev: vidconsole device
- * @name: Font name
- * @size: Size of the font (norminal pixel height) or 0 for default
+ * This shows a list of fonts known by this vidconsole. The list is displayed on
+ * the console (not necessarily @dev but probably)
  */
-int vidconsole_select_font(struct udevice *dev, const char *name, uint size);
+void vidconsole_list_fonts(struct udevice *dev);
 
 /**
- * vidconsole_get_font() - get the current font name and size
+ * vidconsole_get_font_size() - get the current font name and size
  *
  * @dev: vidconsole device
  * @sizep: Place to put the font size (nominal height in pixels)
  * Returns: Current font name
  */
-const char *vidconsole_get_font(struct udevice *dev, uint *sizep);
+const char *vidconsole_get_font_size(struct udevice *dev, uint *sizep);
 
 #ifdef CONFIG_VIDEO_COPY
 /**
diff --git a/test/cmd/font.c b/test/cmd/font.c
index 7a4156ade62..adb353965a7 100644
--- a/test/cmd/font.c
+++ b/test/cmd/font.c
@@ -33,7 +33,7 @@ static int font_test_base(struct unit_test_state *uts)
 	ut_assertok(ut_check_console_end(uts));
 
 	ut_asserteq_str("nimbus_sans_l_regular",
-			vidconsole_get_font(dev, &size));
+			vidconsole_get_font_size(dev, &size));
 	ut_asserteq(18, size);
 
 	max_metrics = 1;
@@ -53,14 +53,14 @@ static int font_test_base(struct unit_test_state *uts)
 	ut_assertok(ut_check_console_end(uts));
 
 	ut_asserteq_str("cantoraone_regular",
-			vidconsole_get_font(dev, &size));
+			vidconsole_get_font_size(dev, &size));
 	ut_asserteq(40, size);
 
 	ut_assertok(run_command("font size 30", 0));
 	ut_assertok(ut_check_console_end(uts));
 
 	ut_asserteq_str("cantoraone_regular",
-			vidconsole_get_font(dev, &size));
+			vidconsole_get_font_size(dev, &size));
 	ut_asserteq(30, size);
 
 	return 0;
-- 
2.39.0.314.g84b9a713c41-goog


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

* [PATCH v3 15/25] bootstd: Read the Operating System name for distro/scripts
  2023-01-06 14:52 [PATCH v3 00/25] bootstd: Add a boot menu Simon Glass
                   ` (13 preceding siblings ...)
  2023-01-06 14:52 ` [PATCH v3 14/25] video: Add font functions to the vidconsole API Simon Glass
@ 2023-01-06 14:52 ` Simon Glass
  2023-01-06 14:52 ` [PATCH v3 16/25] bootstd: Allow reading a logo for the OS Simon Glass
                   ` (10 subsequent siblings)
  25 siblings, 0 replies; 40+ messages in thread
From: Simon Glass @ 2023-01-06 14:52 UTC (permalink / raw)
  To: U-Boot Mailing List
  Cc: Anatolij Gustschin, Tom Rini, Heinrich Schuchardt, Simon Glass

Add the concept of an OS name to the bootflow. This typically includes the
OS name, version and kernel version.

Implement this for the distro and script bootmeths so that it works with
Armbian and older version of Fedora.

Signed-off-by: Simon Glass <sjg@chromium.org>
---

(no changes since v1)

 boot/bootflow.c        |  1 +
 boot/bootmeth_distro.c | 36 ++++++++++++++++++++++++++++++++++++
 boot/bootmeth_script.c | 34 ++++++++++++++++++++++++++++++++++
 cmd/bootflow.c         |  1 +
 include/bootflow.h     |  3 +++
 test/boot/bootflow.c   |  1 +
 6 files changed, 76 insertions(+)

diff --git a/boot/bootflow.c b/boot/bootflow.c
index f9ad4099244..163cd4953dd 100644
--- a/boot/bootflow.c
+++ b/boot/bootflow.c
@@ -354,6 +354,7 @@ void bootflow_free(struct bootflow *bflow)
 	free(bflow->subdir);
 	free(bflow->fname);
 	free(bflow->buf);
+	free(bflow->os_name);
 }
 
 void bootflow_remove(struct bootflow *bflow)
diff --git a/boot/bootmeth_distro.c b/boot/bootmeth_distro.c
index 5c6c687f0a6..6ef0fa1f2c9 100644
--- a/boot/bootmeth_distro.c
+++ b/boot/bootmeth_distro.c
@@ -66,6 +66,38 @@ static int distro_check(struct udevice *dev, struct bootflow_iter *iter)
 	return 0;
 }
 
+/**
+ * distro_fill_info() - Decode the extlinux file to find out distro info
+ *
+ * @bflow: Bootflow to process
+ * @return 0 if OK, -ve on error
+ */
+static int distro_fill_info(struct bootflow *bflow)
+{
+	struct membuff mb;
+	char line[200];
+	char *data;
+	int len;
+
+	log_debug("parsing bflow file size %x\n", bflow->size);
+	membuff_init(&mb, bflow->buf, bflow->size);
+	membuff_putraw(&mb, bflow->size, true, &data);
+	while (len = membuff_readline(&mb, line, sizeof(line) - 1, ' '), len) {
+		char *tok, *p = line;
+
+		tok = strsep(&p, " ");
+		if (p) {
+			if (!strcmp("label", tok)) {
+				bflow->os_name = strdup(p);
+				if (!bflow->os_name)
+					return log_msg_ret("os", -ENOMEM);
+			}
+		}
+	}
+
+	return 0;
+}
+
 static int distro_read_bootflow(struct udevice *dev, struct bootflow *bflow)
 {
 	struct blk_desc *desc;
@@ -99,6 +131,10 @@ static int distro_read_bootflow(struct udevice *dev, struct bootflow *bflow)
 	if (ret)
 		return log_msg_ret("read", ret);
 
+	ret = distro_fill_info(bflow);
+	if (ret)
+		return log_msg_ret("inf", ret);
+
 	return 0;
 }
 
diff --git a/boot/bootmeth_script.c b/boot/bootmeth_script.c
index 16c5dadcca1..6f2a09846db 100644
--- a/boot/bootmeth_script.c
+++ b/boot/bootmeth_script.c
@@ -35,6 +35,36 @@ static int script_check(struct udevice *dev, struct bootflow_iter *iter)
 	return 0;
 }
 
+/**
+ * script_fill_info() - Decode the U-Boot script to find out distro info
+ *
+ * @bflow: Bootflow to process
+ * @return 0 if OK, -ve on error
+ */
+static int script_fill_info(struct bootflow *bflow)
+{
+	char *name = NULL;
+	char *data;
+	uint len;
+	int ret;
+
+	log_debug("parsing bflow file size %x\n", bflow->size);
+
+	ret = image_locate_script(bflow->buf, bflow->size, NULL, &data, &len);
+	if (!ret) {
+		if (strstr(data, "armbianEnv"))
+			name = "Armbian";
+	}
+
+	if (name) {
+		bflow->os_name = strdup(name);
+		if (!bflow->os_name)
+			return log_msg_ret("os", -ENOMEM);
+	}
+
+	return 0;
+}
+
 static int script_read_bootflow(struct udevice *dev, struct bootflow *bflow)
 {
 	struct blk_desc *desc = NULL;
@@ -75,6 +105,10 @@ static int script_read_bootflow(struct udevice *dev, struct bootflow *bflow)
 	if (ret)
 		return log_msg_ret("read", ret);
 
+	ret = script_fill_info(bflow);
+	if (ret)
+		return log_msg_ret("inf", ret);
+
 	return 0;
 }
 
diff --git a/cmd/bootflow.c b/cmd/bootflow.c
index 313103d2775..6b8ac8c8504 100644
--- a/cmd/bootflow.c
+++ b/cmd/bootflow.c
@@ -337,6 +337,7 @@ static int do_bootflow_info(struct cmd_tbl *cmdtp, int flag, int argc,
 	printf("Filename:  %s\n", bflow->fname);
 	printf("Buffer:    %lx\n", (ulong)map_to_sysmem(bflow->buf));
 	printf("Size:      %x (%d bytes)\n", bflow->size, bflow->size);
+	printf("OS:        %s\n", bflow->os_name ? bflow->os_name : "(none)");
 	printf("Error:     %d\n", bflow->err);
 	if (dump && bflow->buf) {
 		/* Set some sort of maximum on the size */
diff --git a/include/bootflow.h b/include/bootflow.h
index 32dbbbbe261..776158c65df 100644
--- a/include/bootflow.h
+++ b/include/bootflow.h
@@ -52,6 +52,8 @@ enum bootflow_state_t {
  * @buf: Bootflow file contents (allocated)
  * @size: Size of bootflow file in bytes
  * @err: Error number received (0 if OK)
+ * @os_name: Name of the OS / distro being booted, or NULL if not known
+ *	(allocated)
  */
 struct bootflow {
 	struct list_head bm_node;
@@ -68,6 +70,7 @@ struct bootflow {
 	char *buf;
 	int size;
 	int err;
+	char *os_name;
 };
 
 /**
diff --git a/test/boot/bootflow.c b/test/boot/bootflow.c
index e1e07082105..3296316cf0d 100644
--- a/test/boot/bootflow.c
+++ b/test/boot/bootflow.c
@@ -188,6 +188,7 @@ static int bootflow_cmd_info(struct unit_test_state *uts)
 	ut_assert_nextline("Filename:  /extlinux/extlinux.conf");
 	ut_assert_nextlinen("Buffer:    ");
 	ut_assert_nextline("Size:      253 (595 bytes)");
+	ut_assert_nextline("OS:        Fedora-Workstation-armhfp-31-1.9 (5.3.7-301.fc31.armv7hl)");
 	ut_assert_nextline("Error:     0");
 	ut_assert_console_end();
 
-- 
2.39.0.314.g84b9a713c41-goog


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

* [PATCH v3 16/25] bootstd: Allow reading a logo for the OS
  2023-01-06 14:52 [PATCH v3 00/25] bootstd: Add a boot menu Simon Glass
                   ` (14 preceding siblings ...)
  2023-01-06 14:52 ` [PATCH v3 15/25] bootstd: Read the Operating System name for distro/scripts Simon Glass
@ 2023-01-06 14:52 ` Simon Glass
  2023-01-06 14:52 ` [PATCH v3 17/25] menu: Factor out menu-keypress decoding Simon Glass
                   ` (9 subsequent siblings)
  25 siblings, 0 replies; 40+ messages in thread
From: Simon Glass @ 2023-01-06 14:52 UTC (permalink / raw)
  To: U-Boot Mailing List
  Cc: Anatolij Gustschin, Tom Rini, Heinrich Schuchardt, Simon Glass

Some operating systems provide a logo in bmp format. Read this in if
present so it can be displayed in the menu.

Signed-off-by: Simon Glass <sjg@chromium.org>
---

(no changes since v1)

 boot/bootmeth-uclass.c | 69 ++++++++++++++++++++++++++++++++++++------
 boot/bootmeth_script.c |  4 +++
 cmd/bootflow.c         |  6 ++++
 include/bootflow.h     |  4 +++
 include/bootmeth.h     | 16 ++++++++++
 test/boot/bootflow.c   |  1 +
 6 files changed, 91 insertions(+), 9 deletions(-)

diff --git a/boot/bootmeth-uclass.c b/boot/bootmeth-uclass.c
index 25552dd96f6..4c3529d1555 100644
--- a/boot/bootmeth-uclass.c
+++ b/boot/bootmeth-uclass.c
@@ -290,25 +290,19 @@ int bootmeth_try_file(struct bootflow *bflow, struct blk_desc *desc,
 	return 0;
 }
 
-int bootmeth_alloc_file(struct bootflow *bflow, uint size_limit, uint align)
+static int alloc_file(const char *fname, uint size, void **bufp)
 {
 	loff_t bytes_read;
 	ulong addr;
 	char *buf;
-	uint size;
 	int ret;
 
-	size = bflow->size;
-	log_debug("   - script file size %x\n", size);
-	if (size > size_limit)
-		return log_msg_ret("chk", -E2BIG);
-
-	buf = memalign(align, size + 1);
+	buf = malloc(size + 1);
 	if (!buf)
 		return log_msg_ret("buf", -ENOMEM);
 	addr = map_to_sysmem(buf);
 
-	ret = fs_read(bflow->fname, addr, 0, 0, &bytes_read);
+	ret = fs_read(fname, addr, 0, size, &bytes_read);
 	if (ret) {
 		free(buf);
 		return log_msg_ret("read", ret);
@@ -316,12 +310,69 @@ int bootmeth_alloc_file(struct bootflow *bflow, uint size_limit, uint align)
 	if (size != bytes_read)
 		return log_msg_ret("bread", -EINVAL);
 	buf[size] = '\0';
+
+	*bufp = buf;
+
+	return 0;
+}
+
+int bootmeth_alloc_file(struct bootflow *bflow, uint size_limit, uint align)
+{
+	void *buf;
+	uint size;
+	int ret;
+
+	size = bflow->size;
+	log_debug("   - script file size %x\n", size);
+	if (size > size_limit)
+		return log_msg_ret("chk", -E2BIG);
+
+	ret = alloc_file(bflow->fname, bflow->size, &buf);
+	if (ret)
+		return log_msg_ret("all", ret);
+
 	bflow->state = BOOTFLOWST_READY;
 	bflow->buf = buf;
 
 	return 0;
 }
 
+int bootmeth_alloc_other(struct bootflow *bflow, const char *fname,
+			 void **bufp, uint *sizep)
+{
+	struct blk_desc *desc = NULL;
+	char path[200];
+	loff_t size;
+	void *buf;
+	int ret;
+
+	snprintf(path, sizeof(path), "%s%s", bflow->subdir, fname);
+	log_debug("trying: %s\n", path);
+
+	if (bflow->blk)
+		desc = dev_get_uclass_plat(bflow->blk);
+
+	ret = setup_fs(bflow, desc);
+	if (ret)
+		return log_msg_ret("fs", ret);
+
+	ret = fs_size(path, &size);
+	log_debug("   %s - err=%d\n", path, ret);
+
+	ret = setup_fs(bflow, desc);
+	if (ret)
+		return log_msg_ret("fs", ret);
+
+	ret = alloc_file(path, size, &buf);
+	if (ret)
+		return log_msg_ret("all", ret);
+
+	*bufp = buf;
+	*sizep = size;
+
+	return 0;
+}
+
 int bootmeth_common_read_file(struct udevice *dev, struct bootflow *bflow,
 			      const char *file_path, ulong addr, ulong *sizep)
 {
diff --git a/boot/bootmeth_script.c b/boot/bootmeth_script.c
index 6f2a09846db..a8f77e29f99 100644
--- a/boot/bootmeth_script.c
+++ b/boot/bootmeth_script.c
@@ -109,6 +109,10 @@ static int script_read_bootflow(struct udevice *dev, struct bootflow *bflow)
 	if (ret)
 		return log_msg_ret("inf", ret);
 
+	ret = bootmeth_alloc_other(bflow, "boot.bmp", &bflow->logo,
+				   &bflow->logo_size);
+	/* ignore error */
+
 	return 0;
 }
 
diff --git a/cmd/bootflow.c b/cmd/bootflow.c
index 6b8ac8c8504..495ef85f25b 100644
--- a/cmd/bootflow.c
+++ b/cmd/bootflow.c
@@ -338,6 +338,12 @@ static int do_bootflow_info(struct cmd_tbl *cmdtp, int flag, int argc,
 	printf("Buffer:    %lx\n", (ulong)map_to_sysmem(bflow->buf));
 	printf("Size:      %x (%d bytes)\n", bflow->size, bflow->size);
 	printf("OS:        %s\n", bflow->os_name ? bflow->os_name : "(none)");
+	printf("Logo:      %s\n", bflow->logo ?
+	       simple_xtoa((ulong)map_to_sysmem(bflow->logo)) : "(none)");
+	if (bflow->logo) {
+		printf("Logo size: %x (%d bytes)\n", bflow->logo_size,
+		       bflow->logo_size);
+	}
 	printf("Error:     %d\n", bflow->err);
 	if (dump && bflow->buf) {
 		/* Set some sort of maximum on the size */
diff --git a/include/bootflow.h b/include/bootflow.h
index 776158c65df..8a07ab30191 100644
--- a/include/bootflow.h
+++ b/include/bootflow.h
@@ -49,6 +49,8 @@ enum bootflow_state_t {
  * @state: Current state (enum bootflow_state_t)
  * @subdir: Subdirectory to fetch files from (with trailing /), or NULL if none
  * @fname: Filename of bootflow file (allocated)
+ * @logo: Logo to display for this bootflow (BMP format)
+ * @logo_size: Size of the logo in bytes
  * @buf: Bootflow file contents (allocated)
  * @size: Size of bootflow file in bytes
  * @err: Error number received (0 if OK)
@@ -67,6 +69,8 @@ struct bootflow {
 	enum bootflow_state_t state;
 	char *subdir;
 	char *fname;
+	void *logo;
+	uint logo_size;
 	char *buf;
 	int size;
 	int err;
diff --git a/include/bootmeth.h b/include/bootmeth.h
index 50ded055f3f..669b14ce81e 100644
--- a/include/bootmeth.h
+++ b/include/bootmeth.h
@@ -265,6 +265,22 @@ int bootmeth_try_file(struct bootflow *bflow, struct blk_desc *desc,
  */
 int bootmeth_alloc_file(struct bootflow *bflow, uint size_limit, uint align);
 
+/**
+ * bootmeth_alloc_other() - Allocate and read a file for a bootflow
+ *
+ * This reads an arbitrary file in the same directory as the bootflow,
+ * allocating memory for it. The buffer is one byte larger than the file length,
+ * so that it can be nul-terminated.
+ *
+ * @bflow: Information about file to read
+ * @fname: Filename to read from (within bootflow->subdir)
+ * @bufp: Returns a pointer to the allocated buffer
+ * @sizep: Returns the size of the buffer
+ * Return: 0 if OK,  -ENOMEM if out of memory, other -ve on other error
+ */
+int bootmeth_alloc_other(struct bootflow *bflow, const char *fname,
+			 void **bufp, uint *sizep);
+
 /**
  * bootmeth_common_read_file() - Common handler for reading a file
  *
diff --git a/test/boot/bootflow.c b/test/boot/bootflow.c
index 3296316cf0d..00dfd990687 100644
--- a/test/boot/bootflow.c
+++ b/test/boot/bootflow.c
@@ -189,6 +189,7 @@ static int bootflow_cmd_info(struct unit_test_state *uts)
 	ut_assert_nextlinen("Buffer:    ");
 	ut_assert_nextline("Size:      253 (595 bytes)");
 	ut_assert_nextline("OS:        Fedora-Workstation-armhfp-31-1.9 (5.3.7-301.fc31.armv7hl)");
+	ut_assert_nextline("Logo:      (none)");
 	ut_assert_nextline("Error:     0");
 	ut_assert_console_end();
 
-- 
2.39.0.314.g84b9a713c41-goog


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

* [PATCH v3 17/25] menu: Factor out menu-keypress decoding
  2023-01-06 14:52 [PATCH v3 00/25] bootstd: Add a boot menu Simon Glass
                   ` (15 preceding siblings ...)
  2023-01-06 14:52 ` [PATCH v3 16/25] bootstd: Allow reading a logo for the OS Simon Glass
@ 2023-01-06 14:52 ` Simon Glass
  2023-01-06 14:52 ` [PATCH v3 18/25] expo: Add basic implementation Simon Glass
                   ` (8 subsequent siblings)
  25 siblings, 0 replies; 40+ messages in thread
From: Simon Glass @ 2023-01-06 14:52 UTC (permalink / raw)
  To: U-Boot Mailing List
  Cc: Anatolij Gustschin, Tom Rini, Heinrich Schuchardt, Simon Glass

Move this code into a separate function so that it can be used in the new
VBE menu.

Signed-off-by: Simon Glass <sjg@chromium.org>
---

(no changes since v1)

 common/menu.c  | 48 ++++++++++++++++++++++++++++++------------------
 include/menu.h | 10 ++++++++++
 2 files changed, 40 insertions(+), 18 deletions(-)

diff --git a/common/menu.c b/common/menu.c
index 45f36ae3ede..cdcdbb2a185 100644
--- a/common/menu.c
+++ b/common/menu.c
@@ -483,26 +483,11 @@ enum bootmenu_key bootmenu_autoboot_loop(struct bootmenu_data *menu,
 	return key;
 }
 
-enum bootmenu_key bootmenu_loop(struct bootmenu_data *menu,
-				struct cli_ch_state *cch)
+enum bootmenu_key bootmenu_conv_key(int ichar)
 {
-	enum bootmenu_key key = BKEY_NONE;
-	int c;
-
-	c = cli_ch_process(cch, 0);
-	if (!c) {
-		while (!c && !tstc()) {
-			schedule();
-			mdelay(10);
-			c = cli_ch_process(cch, -ETIMEDOUT);
-		}
-		if (!c) {
-			c = getchar();
-			c = cli_ch_process(cch, c);
-		}
-	}
+	enum bootmenu_key key;
 
-	switch (c) {
+	switch (ichar) {
 	case '\n':
 		/* enter key was pressed */
 		key = BKEY_SELECT;
@@ -527,7 +512,34 @@ enum bootmenu_key bootmenu_loop(struct bootmenu_data *menu,
 	case ' ':
 		key = BKEY_SPACE;
 		break;
+	default:
+		key = BKEY_NONE;
+		break;
+	}
+
+	return key;
+}
+
+enum bootmenu_key bootmenu_loop(struct bootmenu_data *menu,
+				struct cli_ch_state *cch)
+{
+	enum bootmenu_key key;
+	int c;
+
+	c = cli_ch_process(cch, 0);
+	if (!c) {
+		while (!c && !tstc()) {
+			schedule();
+			mdelay(10);
+			c = cli_ch_process(cch, -ETIMEDOUT);
+		}
+		if (!c) {
+			c = getchar();
+			c = cli_ch_process(cch, c);
+		}
 	}
 
+	key = bootmenu_conv_key(c);
+
 	return key;
 }
diff --git a/include/menu.h b/include/menu.h
index 3996075a337..1e88141d6bf 100644
--- a/include/menu.h
+++ b/include/menu.h
@@ -53,6 +53,8 @@ enum bootmenu_key {
 	BKEY_PLUS,
 	BKEY_MINUS,
 	BKEY_SPACE,
+
+	BKEY_COUNT,
 };
 
 /**
@@ -101,4 +103,12 @@ enum bootmenu_key bootmenu_autoboot_loop(struct bootmenu_data *menu,
 enum bootmenu_key bootmenu_loop(struct bootmenu_data *menu,
 				struct cli_ch_state *cch);
 
+/**
+ * bootmenu_conv_key() - Convert a U-Boot keypress into a menu key
+ *
+ * @ichar: Keypress to convert (ASCII, including control characters)
+ * Returns: Menu key that corresponds to @ichar, or BKEY_NONE if none
+ */
+enum bootmenu_key bootmenu_conv_key(int ichar);
+
 #endif /* __MENU_H__ */
-- 
2.39.0.314.g84b9a713c41-goog


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

* [PATCH v3 18/25] expo: Add basic implementation
  2023-01-06 14:52 [PATCH v3 00/25] bootstd: Add a boot menu Simon Glass
                   ` (16 preceding siblings ...)
  2023-01-06 14:52 ` [PATCH v3 17/25] menu: Factor out menu-keypress decoding Simon Glass
@ 2023-01-06 14:52 ` Simon Glass
  2023-01-06 14:52 ` [PATCH v3 19/25] expo: Add support for scenes Simon Glass
                   ` (7 subsequent siblings)
  25 siblings, 0 replies; 40+ messages in thread
From: Simon Glass @ 2023-01-06 14:52 UTC (permalink / raw)
  To: U-Boot Mailing List
  Cc: Anatolij Gustschin, Tom Rini, Heinrich Schuchardt, Simon Glass

An expo is a way of presenting and collecting information from the
user. It consists of a collection of 'scenes' of which only one is
presented at a time. An expo is typically used to show a boot menu
and allow settings to be changed.

One created, the same expo can be automatically presented in graphical
form using a vidconsole, or in text form on a serial console.

Add an initial implementation of the expo itself. Supports for scenes
and objects is provided later.

Signed-off-by: Simon Glass <sjg@chromium.org>
---

(no changes since v2)

Changes in v2:
- Drop the _add suffix on expo creation function
- Put strings in a separate structure referenced by ID

 boot/Kconfig                 |  12 +
 boot/expo.c                  | 170 ++++++++++++
 configs/tools-only_defconfig |   2 +
 include/expo.h               | 521 +++++++++++++++++++++++++++++++++++
 4 files changed, 705 insertions(+)
 create mode 100644 boot/expo.c
 create mode 100644 include/expo.h

diff --git a/boot/Kconfig b/boot/Kconfig
index 424ad0e466d..f672a325776 100644
--- a/boot/Kconfig
+++ b/boot/Kconfig
@@ -556,6 +556,18 @@ config VPL_BOOTMETH_VBE_SIMPLE_FW
 
 endif # BOOTMETH_VBE
 
+config EXPO
+	bool "Support for expos - groups of scenes displaying a UI"
+	default y if BOOTMETH_VBE
+	help
+	  An expo is a way of presenting and collecting information from the
+	  user. It consists of a collection of 'scenes' of which only one is
+	  presented at a time. An expo is typically used to show a boot menu
+	  and allow settings to be changed.
+
+	  The expo can be presented in graphics form using a vidconsole, or in
+	  text form on a serial console.
+
 config BOOTMETH_SANDBOX
 	def_bool y
 	depends on SANDBOX
diff --git a/boot/expo.c b/boot/expo.c
new file mode 100644
index 00000000000..05950a17603
--- /dev/null
+++ b/boot/expo.c
@@ -0,0 +1,170 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Implementation of a expo, a collection of scenes providing menu options
+ *
+ * Copyright 2022 Google LLC
+ * Written by Simon Glass <sjg@chromium.org>
+ */
+
+#include <common.h>
+#include <dm.h>
+#include <expo.h>
+#include <malloc.h>
+#include <video.h>
+#include "scene_internal.h"
+
+int expo_new(const char *name, void *priv, struct expo **expp)
+{
+	struct expo *exp;
+
+	exp = calloc(1, sizeof(struct expo));
+	if (!exp)
+		return log_msg_ret("expo", -ENOMEM);
+	exp->name = strdup(name);
+	if (!exp->name) {
+		free(exp);
+		return log_msg_ret("name", -ENOMEM);
+	}
+	exp->priv = priv;
+	INIT_LIST_HEAD(&exp->scene_head);
+	INIT_LIST_HEAD(&exp->str_head);
+
+	*expp = exp;
+
+	return 0;
+}
+
+static void estr_destroy(struct expo_string *estr)
+{
+	free(estr);
+}
+
+void expo_destroy(struct expo *exp)
+{
+	struct scene *scn, *next;
+	struct expo_string *estr, *enext;
+
+	list_for_each_entry_safe(scn, next, &exp->scene_head, sibling)
+		scene_destroy(scn);
+
+	list_for_each_entry_safe(estr, enext, &exp->str_head, sibling)
+		estr_destroy(estr);
+
+	free(exp->name);
+	free(exp);
+}
+
+int expo_str(struct expo *exp, const char *name, uint id, const char *str)
+{
+	struct expo_string *estr;
+
+	estr = calloc(1, sizeof(struct expo_string));
+	if (!estr)
+		return log_msg_ret("obj", -ENOMEM);
+
+	estr->id = resolve_id(exp, id);
+	estr->str = str;
+	list_add_tail(&estr->sibling, &exp->str_head);
+
+	return estr->id;
+}
+
+const char *expo_get_str(struct expo *exp, uint id)
+{
+	struct expo_string *estr;
+
+	list_for_each_entry(estr, &exp->str_head, sibling) {
+		if (estr->id == id)
+			return estr->str;
+	}
+
+	return NULL;
+}
+
+int expo_set_display(struct expo *exp, struct udevice *dev)
+{
+	exp->display = dev;
+
+	return 0;
+}
+
+void exp_set_text_mode(struct expo *exp, bool text_mode)
+{
+	exp->text_mode = text_mode;
+}
+
+struct scene *expo_lookup_scene_id(struct expo *exp, uint scene_id)
+{
+	struct scene *scn;
+
+	list_for_each_entry(scn, &exp->scene_head, sibling) {
+		if (scn->id == scene_id)
+			return scn;
+	}
+
+	return NULL;
+}
+
+int expo_set_scene_id(struct expo *exp, uint scene_id)
+{
+	if (!expo_lookup_scene_id(exp, scene_id))
+		return log_msg_ret("id", -ENOENT);
+	exp->scene_id = scene_id;
+
+	return 0;
+}
+
+int expo_render(struct expo *exp)
+{
+	struct udevice *dev = exp->display;
+	struct video_priv *vid_priv = dev_get_uclass_priv(dev);
+	struct scene *scn = NULL;
+	u32 colour;
+	int ret;
+
+	colour = video_index_to_colour(vid_priv, VID_WHITE);
+	ret = video_fill(dev, colour);
+	if (ret)
+		return log_msg_ret("fill", ret);
+
+	if (exp->scene_id) {
+		scn = expo_lookup_scene_id(exp, exp->scene_id);
+		if (!scn)
+			return log_msg_ret("scn", -ENOENT);
+
+		ret = scene_render(scn);
+		if (ret)
+			return log_msg_ret("ren", ret);
+	}
+
+	video_sync(dev, true);
+
+	return scn ? 0 : -ECHILD;
+}
+
+int expo_send_key(struct expo *exp, int key)
+{
+	struct scene *scn = NULL;
+
+	if (exp->scene_id) {
+		int ret;
+
+		scn = expo_lookup_scene_id(exp, exp->scene_id);
+		if (!scn)
+			return log_msg_ret("scn", -ENOENT);
+
+		ret = scene_send_key(scn, key, &exp->action);
+		if (ret)
+			return log_msg_ret("key", ret);
+	}
+
+	return scn ? 0 : -ECHILD;
+}
+
+int expo_action_get(struct expo *exp, struct expo_action *act)
+{
+	*act = exp->action;
+	exp->action.type = EXPOACT_NONE;
+
+	return act->type == EXPOACT_NONE ? -EAGAIN : 0;
+}
diff --git a/configs/tools-only_defconfig b/configs/tools-only_defconfig
index de99f3857c3..2197063112b 100644
--- a/configs/tools-only_defconfig
+++ b/configs/tools-only_defconfig
@@ -7,6 +7,8 @@ CONFIG_ANDROID_BOOT_IMAGE=y
 CONFIG_FIT=y
 CONFIG_TIMESTAMP=y
 CONFIG_FIT_SIGNATURE=y
+# CONFIG_BOOTSTD_FULL is not set
+# CONFIG_BOOTMETH_VBE is not set
 CONFIG_USE_BOOTCOMMAND=y
 CONFIG_BOOTCOMMAND="run distro_bootcmd"
 # CONFIG_CMD_BOOTD is not set
diff --git a/include/expo.h b/include/expo.h
new file mode 100644
index 00000000000..ce348a7d6f8
--- /dev/null
+++ b/include/expo.h
@@ -0,0 +1,521 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Copyright 2022 Google LLC
+ * Written by Simon Glass <sjg@chromium.org>
+ */
+
+#ifndef __SCENE_H
+#define __SCENE_H
+
+#include <linux/list.h>
+
+struct udevice;
+
+/**
+ * enum expoact_type - types of actions reported by the expo
+ *
+ * @EXPOACT_NONE: no action
+ * @EXPOACT_POINT: menu item was highlighted (@id indicates which)
+ * @EXPOACT_SELECT: menu item was selected (@id indicates which)
+ * @EXPOACT_QUIT: request to exit the menu
+ */
+enum expoact_type {
+	EXPOACT_NONE,
+	EXPOACT_POINT,
+	EXPOACT_SELECT,
+	EXPOACT_QUIT,
+};
+
+/**
+ * struct expo_action - an action report by the expo
+ *
+ * @type: Action type (EXPOACT_NONE if there is no action)
+ * @select: Used for EXPOACT_POINT and EXPOACT_SELECT
+ * @id: ID number of the object affected.
+ */
+struct expo_action {
+	enum expoact_type type;
+	union {
+		struct {
+			int id;
+		} select;
+	};
+};
+
+/**
+ * struct expo - information about an expo
+ *
+ * A group of scenes which can be presented to the user, typically to obtain
+ * input or to make a selection.
+ *
+ * @name: Name of the expo (allocated)
+ * @display: Display to use (`UCLASS_VIDEO`), or NULL to use text mode
+ * @scene_id: Current scene ID (0 if none)
+ * @next_id: Next ID number to use, for automatic allocation
+ * @action: Action selected by user. At present only one is supported, with the
+ * type set to EXPOACT_NONE if there is no action
+ * @text_mode: true to use text mode for the menu (no vidconsole)
+ * @priv: Private data for the controller
+ * @scene_head: List of scenes
+ * @str_head: list of strings
+ */
+struct expo {
+	char *name;
+	struct udevice *display;
+	uint scene_id;
+	uint next_id;
+	struct expo_action action;
+	bool text_mode;
+	void *priv;
+	struct list_head scene_head;
+	struct list_head str_head;
+};
+
+/**
+ * struct expo_string - a string that can be used in an expo
+ *
+ * @id: ID number of the string
+ * @str: String
+ */
+struct expo_string {
+	uint id;
+	const char *str;
+	struct list_head sibling;
+};
+
+/**
+ * struct scene - information about a scene in an expo
+ *
+ * A collection of text/image/menu items in an expo
+ *
+ * @expo: Expo this scene is part of
+ * @name: Name of the scene (allocated)
+ * @id: ID number of the scene
+ * @title: Title of the scene (allocated)
+ * @sibling: Node to link this scene to its siblings
+ * @obj_head: List of objects in the scene
+ */
+struct scene {
+	struct expo *expo;
+	char *name;
+	uint id;
+	char *title;
+	struct list_head sibling;
+	struct list_head obj_head;
+};
+
+/**
+ * enum scene_obj_t - type of a scene object
+ *
+ * @SCENEOBJT_NONE: Used to indicate that the type does not matter
+ * @SCENEOBJT_IMAGE: Image data to render
+ * @SCENEOBJT_TEXT: Text line to render
+ * @SCENEOBJT_MENU: Menu containing items the user can select
+ */
+enum scene_obj_t {
+	SCENEOBJT_NONE		= 0,
+	SCENEOBJT_IMAGE,
+	SCENEOBJT_TEXT,
+	SCENEOBJT_MENU,
+};
+
+/**
+ * struct scene_obj - information about an object in a scene
+ *
+ * @scene: Scene that this object relates to
+ * @name: Name of the object (allocated)
+ * @id: ID number of the object
+ * @type: Type of this object
+ * @x: x position, in pixels from left side
+ * @y: y position, in pixels from top
+ * @hide: true if the object should be hidden
+ * @sibling: Node to link this object to its siblings
+ */
+struct scene_obj {
+	struct scene *scene;
+	char *name;
+	uint id;
+	enum scene_obj_t type;
+	int x;
+	int y;
+	bool hide;
+	struct list_head sibling;
+};
+
+/**
+ * struct scene_obj_img - information about an image object in a scene
+ *
+ * This is a rectangular image which is blitted onto the display
+ *
+ * @obj: Basic object information
+ * @data: Image data in BMP format
+ */
+struct scene_obj_img {
+	struct scene_obj obj;
+	char *data;
+};
+
+/**
+ * struct scene_obj_txt - information about a text object in a scene
+ *
+ * This is a single-line text object
+ *
+ * @obj: Basic object information
+ * @str_id: ID of the text string to display
+ * @font_name: Name of font (allocated by caller)
+ * @font_size: Nominal size of font in pixels
+ */
+struct scene_obj_txt {
+	struct scene_obj obj;
+	uint str_id;
+	const char *font_name;
+	uint font_size;
+};
+
+/**
+ * struct scene_obj_menu - information about a menu object in a scene
+ *
+ * A menu has a number of items which can be selected by the user
+ *
+ * It also has:
+ *
+ * - a text/image object (@pointer_id) which points to the current item
+ *   (@cur_item_id)
+ *
+ * - a preview object which shows an image related to the current item
+ *
+ * @obj: Basic object information
+ * @title_id: ID of the title text, or 0 if none
+ * @cur_item_id: ID of the current menu item, or 0 if none
+ * @pointer_id: ID of the object pointing to the current selection
+ * @item_head: List of items in the menu
+ */
+struct scene_obj_menu {
+	struct scene_obj obj;
+	uint title_id;
+	uint cur_item_id;
+	uint pointer_id;
+	struct list_head item_head;
+};
+
+/**
+ * enum scene_menuitem_flags_t - flags for menu items
+ *
+ * @SCENEMIF_GAP_BEFORE: Add a gap before this item
+ */
+enum scene_menuitem_flags_t {
+	SCENEMIF_GAP_BEFORE	= 1 << 0,
+};
+
+/**
+ * struct scene_menitem - a menu item in a menu
+ *
+ * A menu item has:
+ *
+ * - text object holding the name (short) and description (can be longer)
+ * - a text object holding the keypress
+ *
+ * @name: Name of the item (this is allocated by this call)
+ * @id: ID number of the object
+ * @key_id: ID of text object to use as the keypress to show
+ * @label_id: ID of text object to use as the label text
+ * @desc_id: ID of text object to use as the description text
+ * @preview_id: ID of the preview object, or 0 if none
+ * @flags: Flags for this item
+ * @sibling: Node to link this item to its siblings
+ */
+struct scene_menitem {
+	char *name;
+	uint id;
+	uint key_id;
+	uint label_id;
+	uint desc_id;
+	uint preview_id;
+	uint flags;
+	struct list_head sibling;
+};
+
+/**
+ * expo_new() - create a new expo
+ *
+ * Allocates a new expo
+ *
+ * @name: Name of expo (this is allocated by this call)
+ * @priv: Private data for the controller
+ * @expp: Returns a pointer to the new expo on success
+ * Returns: 0 if OK, -ENOMEM if out of memory
+ */
+int expo_new(const char *name, void *priv, struct expo **expp);
+
+/**
+ * expo_destroy() - Destroy an expo and free all its memory
+ *
+ * @exp: Expo to destroy
+ */
+void expo_destroy(struct expo *exp);
+
+/**
+ * expo_str() - add a new string to an expo
+ *
+ * @exp: Expo to update
+ * @name: Name to use (this is allocated by this call)
+ * @id: ID to use for the new object (0 to allocate one)
+ * @str: Pointer to text to display (allocated by caller)
+ * Returns: ID number for the object (typically @id), or -ve on error
+ */
+int expo_str(struct expo *exp, const char *name, uint id, const char *str);
+
+/**
+ * expo_get_str() - Get a string by ID
+ *
+ * @exp: Expo to use
+ * @id: String ID to look up
+ * @returns string, or NULL if not found
+ */
+const char *expo_get_str(struct expo *exp, uint id);
+
+/**
+ * expo_set_display() - set the display to use for a expo
+ *
+ * @exp: Expo to update
+ * @dev: Display to use (`UCLASS_VIDEO`), NULL to use text mode
+ * Returns: 0 (always)
+ */
+int expo_set_display(struct expo *exp, struct udevice *dev);
+
+/**
+ * expo_set_scene_id() - Set the current scene ID
+ *
+ * @exp: Expo to update
+ * @scene_id: New scene ID to use (0 to select no scene)
+ * Returns: 0 if OK, -ENOENT if there is no scene with that ID
+ */
+int expo_set_scene_id(struct expo *exp, uint scene_id);
+
+/**
+ * expo_render() - render the expo on the display / console
+ *
+ * @exp: Expo to render
+ *
+ * Returns: 0 if OK, -ECHILD if there is no current scene, -ENOENT if the
+ * current scene is not found, other error if something else goes wrong
+ */
+int expo_render(struct expo *exp);
+
+/**
+ * exp_set_text_mode() - Controls whether the expo renders in text mode
+ *
+ * @exp: Expo to update
+ * @text_mode: true to use text mode, false to use the console
+ */
+void exp_set_text_mode(struct expo *exp, bool text_mode);
+
+/**
+ * scene_new() - create a new scene in a expo
+ *
+ * The scene is given the ID @id which must be unique across all scenes, objects
+ * and items. The expo's @next_id is updated to at least @id + 1
+ *
+ * @exp: Expo to update
+ * @name: Name to use (this is allocated by this call)
+ * @id: ID to use for the new scene (0 to allocate one)
+ * @scnp: Returns a pointer to the new scene on success
+ * Returns: ID number for the scene (typically @id), or -ve on error
+ */
+int scene_new(struct expo *exp, const char *name, uint id, struct scene **scnp);
+
+/**
+ * expo_lookup_scene_id() - Look up a scene by ID
+ *
+ * @exp: Expo to check
+ * @scene_id: Scene ID to look up
+ * @returns pointer to scene if found, else NULL
+ */
+struct scene *expo_lookup_scene_id(struct expo *exp, uint scene_id);
+
+/**
+ * scene_title_set() - set the scene title
+ *
+ * @scn: Scene to update
+ * @title: Title to set, NULL if none (this is allocated by this call)
+ * Returns: 0 if OK, -ENOMEM if out of memory
+ */
+int scene_title_set(struct scene *scn, const char *title);
+
+/**
+ * scene_obj_count() - Count the number of objects in a scene
+ *
+ * @scn: Scene to check
+ * Returns: number of objects in the scene, 0 if none
+ */
+int scene_obj_count(struct scene *scn);
+
+/**
+ * scene_img() - add a new image to a scene
+ *
+ * @scn: Scene to update
+ * @name: Name to use (this is allocated by this call)
+ * @id: ID to use for the new object (0 to allocate one)
+ * @data: Pointer to image data
+ * @imgp: If non-NULL, returns the new object
+ * Returns: ID number for the object (typically @id), or -ve on error
+ */
+int scene_img(struct scene *scn, const char *name, uint id, char *data,
+	      struct scene_obj_img **imgp);
+
+/**
+ * scene_txt() - add a new text object to a scene
+ *
+ * @scn: Scene to update
+ * @name: Name to use (this is allocated by this call)
+ * @id: ID to use for the new object (0 to allocate one)
+ * @str_id: ID of the string to use
+ * @txtp: If non-NULL, returns the new object
+ * Returns: ID number for the object (typically @id), or -ve on error
+ */
+int scene_txt(struct scene *scn, const char *name, uint id, uint str_id,
+	      struct scene_obj_txt **txtp);
+
+/**
+ * scene_txt_str() - add a new string to expr and text object to a scene
+ *
+ * @scn: Scene to update
+ * @name: Name to use (this is allocated by this call)
+ * @id: ID to use for the new object (0 to allocate one)
+ * @str_id: ID of the string to use
+ * @str: Pointer to text to display (allocated by caller)
+ * @txtp: If non-NULL, returns the new object
+ * Returns: ID number for the object (typically @id), or -ve on error
+ */
+int scene_txt_str(struct scene *scn, const char *name, uint id, uint str_id,
+		  const char *str, struct scene_obj_txt **txtp);
+
+/**
+ *  scene_menu() - create a menu
+ *
+ * @scn: Scene to update
+ * @name: Name to use (this is allocated by this call)
+ * @id: ID to use for the new object (0 to allocate one)
+ * @menup: If non-NULL, returns the new object
+ * Returns: ID number for the object (typically @id), or -ve on error
+ */
+int scene_menu(struct scene *scn, const char *name, uint id,
+	       struct scene_obj_menu **menup);
+
+/**
+ * scene_txt_set_font() - Set the font for an object
+ *
+ * @scn: Scene to update
+ * @id: ID of object to update
+ * @font_name: Font name to use (allocated by caller)
+ * @font_size: Font size to use (nominal height in pixels)
+ */
+int scene_txt_set_font(struct scene *scn, uint id, const char *font_name,
+		       uint font_size);
+
+/**
+ * scene_obj_set_pos() - Set the postion of an object
+ *
+ * @scn: Scene to update
+ * @id: ID of object to update
+ * @x: x position, in pixels from left side
+ * @y: y position, in pixels from top
+ * Returns: 0 if OK, -ENOENT if @id is invalid
+ */
+int scene_obj_set_pos(struct scene *scn, uint id, int x, int y);
+
+/**
+ * scene_obj_set_hide() - Set whether an object is hidden
+ *
+ * The update happens when the expo is next rendered.
+ *
+ * @scn: Scene to update
+ * @id: ID of object to update
+ * @hide: true to hide the object, false to show it
+ * Returns: 0 if OK, -ENOENT if @id is invalid
+ */
+int scene_obj_set_hide(struct scene *scn, uint id, bool hide);
+
+/**
+ * scene_menu_set_title() - Set the title of a menu
+ *
+ * @scn: Scene to update
+ * @id: ID of menu object to update
+ * @title_id: ID of text object to use as the title
+ * Returns: 0 if OK, -ENOENT if @id is invalid, -EINVAL if @title_id is invalid
+ */
+int scene_menu_set_title(struct scene *scn, uint id, uint title_id);
+
+/**
+ * scene_menu_set_pointer() - Set the item pointer for a menu
+ *
+ * This is a visual indicator of the current item, typically a ">" character
+ * which sits next to the current item and moves when the user presses the
+ * up/down arrow keys
+ *
+ * @scn: Scene to update
+ * @id: ID of menu object to update
+ * @cur_item_id: ID of text or image object to use as a pointer to the current
+ * item
+ * Returns: 0 if OK, -ENOENT if @id is invalid, -EINVAL if @cur_item_id is invalid
+ */
+int scene_menu_set_pointer(struct scene *scn, uint id, uint cur_item_id);
+
+/**
+ * scene_obj_get_hw() - Get width and height of an object in a scene
+ *
+ * @scn: Scene to check
+ * @id: ID of menu object to check
+ * @widthp: If non-NULL, returns width of object in pixels
+ * Returns: Height of object in pixels
+ */
+int scene_obj_get_hw(struct scene *scn, uint id, int *widthp);
+
+/**
+ * scene_menuitem() - Add an item to a menu
+ *
+ * @scn: Scene to update
+ * @menu_id: ID of menu object to update
+ * @name: Name to use (this is allocated by this call)
+ * @id: ID to use for the new object (0 to allocate one)
+ * @key_id: ID of text object to use as the keypress to show
+ * @label_id: ID of text object to use as the label text
+ * @desc_id: ID of text object to use as the description text
+ * @preview_id: ID of object to use as the preview (text or image)
+ * @flags: Flags for this item (enum scene_menuitem_flags_t)
+ * @itemp: If non-NULL, returns the new object
+ * Returns: ID number for the item (typically @id), or -ve on error
+ */
+int scene_menuitem(struct scene *scn, uint menu_id, const char *name, uint id,
+		   uint key_id, uint label_id, uint desc_id, uint preview_id,
+		   uint flags, struct scene_menitem **itemp);
+
+/**
+ * scene_arrange() - Arrange the scene to deal with object sizes
+ *
+ * Updates any menus in the scene so that their objects are in the right place.
+ *
+ * @scn: Scene to arrange
+ * Returns: 0 if OK, -ve on error
+ */
+int scene_arrange(struct scene *scn);
+
+/**
+ * expo_send_key() - set a keypress to the expo
+ *
+ * @exp: Expo to receive the key
+ * @key: Key to send (ASCII or enum bootmenu_key)
+ * Returns: 0 if OK, -ECHILD if there is no current scene
+ */
+int expo_send_key(struct expo *exp, int key);
+
+/**
+ * expo_action_get() - read user input from the expo
+ *
+ * @exp: Expo to check
+ * @act: Returns action
+ * Returns: 0 if OK, -EAGAIN if there was no action to return
+ */
+int expo_action_get(struct expo *exp, struct expo_action *act);
+
+#endif /*__SCENE_H */
-- 
2.39.0.314.g84b9a713c41-goog


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

* [PATCH v3 19/25] expo: Add support for scenes
  2023-01-06 14:52 [PATCH v3 00/25] bootstd: Add a boot menu Simon Glass
                   ` (17 preceding siblings ...)
  2023-01-06 14:52 ` [PATCH v3 18/25] expo: Add basic implementation Simon Glass
@ 2023-01-06 14:52 ` Simon Glass
  2023-01-06 14:52 ` [PATCH v3 20/25] expo: Add support for scene menus Simon Glass
                   ` (6 subsequent siblings)
  25 siblings, 0 replies; 40+ messages in thread
From: Simon Glass @ 2023-01-06 14:52 UTC (permalink / raw)
  To: U-Boot Mailing List
  Cc: Anatolij Gustschin, Tom Rini, Heinrich Schuchardt, Simon Glass

A scene is a single screen within an expo. It is possible to move between
scenes but only one can be displayed at once.

Add a basic implementation.

Signed-off-by: Simon Glass <sjg@chromium.org>
---

(no changes since v2)

Changes in v2:
- Drop the _add suffix on expo creation function
- Put strings in a separate structure referenced by ID

 boot/scene.c          | 414 ++++++++++++++++++++++++++++++++++++++++++
 boot/scene_internal.h | 123 +++++++++++++
 2 files changed, 537 insertions(+)
 create mode 100644 boot/scene.c
 create mode 100644 boot/scene_internal.h

diff --git a/boot/scene.c b/boot/scene.c
new file mode 100644
index 00000000000..030f6aa2a0a
--- /dev/null
+++ b/boot/scene.c
@@ -0,0 +1,414 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Implementation of a scene, a collection of text/image/menu items in an expo
+ *
+ * Copyright 2022 Google LLC
+ * Written by Simon Glass <sjg@chromium.org>
+ */
+
+#include <common.h>
+#include <dm.h>
+#include <expo.h>
+#include <malloc.h>
+#include <mapmem.h>
+#include <video.h>
+#include <video_console.h>
+#include <linux/input.h>
+#include "scene_internal.h"
+
+uint resolve_id(struct expo *exp, uint id)
+{
+	if (!id)
+		id = exp->next_id++;
+	else if (id >= exp->next_id)
+		exp->next_id = id + 1;
+
+	return id;
+}
+
+int scene_new(struct expo *exp, const char *name, uint id, struct scene **scnp)
+{
+	struct scene *scn;
+
+	scn = calloc(1, sizeof(struct scene));
+	if (!scn)
+		return log_msg_ret("expo", -ENOMEM);
+	scn->name = strdup(name);
+	if (!scn->name) {
+		free(scn);
+		return log_msg_ret("name", -ENOMEM);
+	}
+
+	INIT_LIST_HEAD(&scn->obj_head);
+	scn->id = resolve_id(exp, id);
+	scn->expo = exp;
+	list_add_tail(&scn->sibling, &exp->scene_head);
+
+	*scnp = scn;
+
+	return scn->id;
+}
+
+void scene_obj_destroy(struct scene_obj *obj)
+{
+	if (obj->type == SCENEOBJT_MENU)
+		scene_menu_destroy((struct scene_obj_menu *)obj);
+	free(obj->name);
+	free(obj);
+}
+
+void scene_destroy(struct scene *scn)
+{
+	struct scene_obj *obj, *next;
+
+	list_for_each_entry_safe(obj, next, &scn->obj_head, sibling)
+		scene_obj_destroy(obj);
+
+	free(scn->name);
+	free(scn->title);
+	free(scn);
+}
+
+int scene_title_set(struct scene *scn, const char *title)
+{
+	free(scn->title);
+	scn->title = strdup(title);
+	if (!scn->title)
+		return log_msg_ret("tit", -ENOMEM);
+
+	return 0;
+}
+
+int scene_obj_count(struct scene *scn)
+{
+	struct scene_obj *obj;
+	int count = 0;
+
+	list_for_each_entry(obj, &scn->obj_head, sibling)
+		count++;
+
+	return count;
+}
+
+void *scene_obj_find(struct scene *scn, uint id, enum scene_obj_t type)
+{
+	struct scene_obj *obj;
+
+	list_for_each_entry(obj, &scn->obj_head, sibling) {
+		if (obj->id == id &&
+		    (type == SCENEOBJT_NONE || obj->type == type))
+			return obj;
+	}
+
+	return NULL;
+}
+
+int scene_obj_add(struct scene *scn, const char *name, uint id,
+		  enum scene_obj_t type, uint size, struct scene_obj **objp)
+{
+	struct scene_obj *obj;
+
+	obj = calloc(1, size);
+	if (!obj)
+		return log_msg_ret("obj", -ENOMEM);
+	obj->name = strdup(name);
+	if (!obj->name) {
+		free(obj);
+		return log_msg_ret("name", -ENOMEM);
+	}
+
+	obj->id = resolve_id(scn->expo, id);
+	obj->scene = scn;
+	obj->type = type;
+	list_add_tail(&obj->sibling, &scn->obj_head);
+	*objp = obj;
+
+	return obj->id;
+}
+
+int scene_img(struct scene *scn, const char *name, uint id, char *data,
+	      struct scene_obj_img **imgp)
+{
+	struct scene_obj_img *img;
+	int ret;
+
+	ret = scene_obj_add(scn, name, id, SCENEOBJT_IMAGE,
+			    sizeof(struct scene_obj_img),
+			    (struct scene_obj **)&img);
+	if (ret < 0)
+		return log_msg_ret("obj", -ENOMEM);
+
+	img->data = data;
+
+	if (imgp)
+		*imgp = img;
+
+	return img->obj.id;
+}
+
+int scene_txt(struct scene *scn, const char *name, uint id, uint str_id,
+	      struct scene_obj_txt **txtp)
+{
+	struct scene_obj_txt *txt;
+	int ret;
+
+	ret = scene_obj_add(scn, name, id, SCENEOBJT_TEXT,
+			    sizeof(struct scene_obj_txt),
+			    (struct scene_obj **)&txt);
+	if (ret < 0)
+		return log_msg_ret("obj", -ENOMEM);
+
+	txt->str_id = str_id;
+
+	if (txtp)
+		*txtp = txt;
+
+	return txt->obj.id;
+}
+
+int scene_txt_str(struct scene *scn, const char *name, uint id, uint str_id,
+		  const char *str, struct scene_obj_txt **txtp)
+{
+	struct scene_obj_txt *txt;
+	int ret;
+
+	ret = expo_str(scn->expo, name, str_id, str);
+	if (ret < 0)
+		return log_msg_ret("str", ret);
+	else if (ret != str_id)
+		return log_msg_ret("id", -EEXIST);
+
+	ret = scene_obj_add(scn, name, id, SCENEOBJT_TEXT,
+			    sizeof(struct scene_obj_txt),
+			    (struct scene_obj **)&txt);
+	if (ret < 0)
+		return log_msg_ret("obj", -ENOMEM);
+
+	txt->str_id = str_id;
+
+	if (txtp)
+		*txtp = txt;
+
+	return txt->obj.id;
+}
+
+int scene_txt_set_font(struct scene *scn, uint id, const char *font_name,
+		       uint font_size)
+{
+	struct scene_obj_txt *txt;
+
+	txt = scene_obj_find(scn, id, SCENEOBJT_TEXT);
+	if (!txt)
+		return log_msg_ret("find", -ENOENT);
+	txt->font_name = font_name;
+	txt->font_size = font_size;
+
+	return 0;
+}
+
+int scene_obj_set_pos(struct scene *scn, uint id, int x, int y)
+{
+	struct scene_obj *obj;
+
+	obj = scene_obj_find(scn, id, SCENEOBJT_NONE);
+	if (!obj)
+		return log_msg_ret("find", -ENOENT);
+	obj->x = x;
+	obj->y = y;
+	if (obj->type == SCENEOBJT_MENU)
+		scene_menu_arrange(scn, (struct scene_obj_menu *)obj);
+
+	return 0;
+}
+
+int scene_obj_set_hide(struct scene *scn, uint id, bool hide)
+{
+	struct scene_obj *obj;
+
+	obj = scene_obj_find(scn, id, SCENEOBJT_NONE);
+	if (!obj)
+		return log_msg_ret("find", -ENOENT);
+	obj->hide = hide;
+
+	return 0;
+}
+
+int scene_obj_get_hw(struct scene *scn, uint id, int *widthp)
+{
+	struct scene_obj *obj;
+
+	obj = scene_obj_find(scn, id, SCENEOBJT_NONE);
+	if (!obj)
+		return log_msg_ret("find", -ENOENT);
+
+	switch (obj->type) {
+	case SCENEOBJT_NONE:
+	case SCENEOBJT_MENU:
+		break;
+	case SCENEOBJT_IMAGE: {
+		struct scene_obj_img *img = (struct scene_obj_img *)obj;
+		ulong width, height;
+		uint bpix;
+
+		video_bmp_get_info(img->data, &width, &height, &bpix);
+		if (widthp)
+			*widthp = width;
+		return height;
+	}
+	case SCENEOBJT_TEXT: {
+		struct scene_obj_txt *txt = (struct scene_obj_txt *)obj;
+		struct expo *exp = scn->expo;
+
+		if (widthp)
+			*widthp = 16; /* fake value for now */
+		if (txt->font_size)
+			return txt->font_size;
+		if (exp->display)
+			return video_default_font_height(exp->display);
+
+		/* use a sensible default */
+		return 16;
+	}
+	}
+
+	return 0;
+}
+
+/**
+ * scene_obj_render() - Render an object
+ *
+ */
+static int scene_obj_render(struct scene_obj *obj, bool text_mode)
+{
+	struct scene *scn = obj->scene;
+	struct expo *exp = scn->expo;
+	struct udevice *cons, *dev = exp->display;
+	int x, y, ret;
+
+	cons = NULL;
+	if (!text_mode) {
+		ret = device_find_first_child_by_uclass(dev,
+							UCLASS_VIDEO_CONSOLE,
+							&cons);
+	}
+
+	x = obj->x;
+	y = obj->y;
+
+	switch (obj->type) {
+	case SCENEOBJT_NONE:
+		break;
+	case SCENEOBJT_IMAGE: {
+		struct scene_obj_img *img = (struct scene_obj_img *)obj;
+
+		if (!cons)
+			return -ENOTSUPP;
+		ret = video_bmp_display(dev, map_to_sysmem(img->data), x, y,
+					true);
+		if (ret < 0)
+			return log_msg_ret("img", ret);
+		break;
+	}
+	case SCENEOBJT_TEXT: {
+		struct scene_obj_txt *txt = (struct scene_obj_txt *)obj;
+		const char *str;
+
+		if (!cons)
+			return -ENOTSUPP;
+
+		if (txt->font_name || txt->font_size) {
+			ret = vidconsole_select_font(cons,
+						     txt->font_name,
+						     txt->font_size);
+		} else {
+			ret = vidconsole_select_font(cons, NULL, 0);
+		}
+		if (ret && ret != -ENOSYS)
+			return log_msg_ret("font", ret);
+		vidconsole_set_cursor_pos(cons, x, y);
+		str = expo_get_str(exp, txt->str_id);
+		if (str)
+			vidconsole_put_string(cons, str);
+		break;
+	}
+	case SCENEOBJT_MENU: {
+		struct scene_obj_menu *menu = (struct scene_obj_menu *)obj;
+		/*
+		 * With a vidconsole, the text and item pointer are rendered as
+		 * normal objects so we don't need to do anything here. The menu
+		 * simply controls where they are positioned.
+		 */
+		if (cons)
+			return -ENOTSUPP;
+
+		ret = scene_menu_display(menu);
+		if (ret < 0)
+			return log_msg_ret("img", ret);
+
+		break;
+	}
+	}
+
+	return 0;
+}
+
+int scene_arrange(struct scene *scn)
+{
+	struct scene_obj *obj;
+	int ret;
+
+	list_for_each_entry(obj, &scn->obj_head, sibling) {
+		if (obj->type == SCENEOBJT_MENU) {
+			struct scene_obj_menu *menu;
+
+			menu = (struct scene_obj_menu *)obj,
+			ret = scene_menu_arrange(scn, menu);
+			if (ret)
+				return log_msg_ret("arr", ret);
+		}
+	}
+
+	return 0;
+}
+
+int scene_render(struct scene *scn)
+{
+	struct expo *exp = scn->expo;
+	struct scene_obj *obj;
+	int ret;
+
+	list_for_each_entry(obj, &scn->obj_head, sibling) {
+		if (!obj->hide) {
+			ret = scene_obj_render(obj, exp->text_mode);
+			if (ret && ret != -ENOTSUPP)
+				return log_msg_ret("ren", ret);
+		}
+	}
+
+	return 0;
+}
+
+int scene_send_key(struct scene *scn, int key, struct expo_action *event)
+{
+	struct scene_obj *obj;
+	int ret;
+
+	list_for_each_entry(obj, &scn->obj_head, sibling) {
+		if (obj->type == SCENEOBJT_MENU) {
+			struct scene_obj_menu *menu;
+
+			menu = (struct scene_obj_menu *)obj,
+			ret = scene_menu_send_key(scn, menu, key, event);
+			if (ret)
+				return log_msg_ret("key", ret);
+
+			/* only allow one menu */
+			ret = scene_menu_arrange(scn, menu);
+			if (ret)
+				return log_msg_ret("arr", ret);
+			break;
+		}
+	}
+
+	return 0;
+}
diff --git a/boot/scene_internal.h b/boot/scene_internal.h
new file mode 100644
index 00000000000..e8fd765811e
--- /dev/null
+++ b/boot/scene_internal.h
@@ -0,0 +1,123 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Internal header file for scenes
+ *
+ * Copyright 2022 Google LLC
+ * Written by Simon Glass <sjg@chromium.org>
+ */
+
+#ifndef __SCENE_INTERNAL_H
+#define __SCENE_INTERNAL_H
+
+/**
+ * expo_lookup_scene_id() - Look up a scene ID
+ *
+ * @exp: Expo to use
+ * @id: scene ID to look up
+ * Returns: Scene for that ID, or NULL if none
+ */
+struct scene *expo_lookup_scene_id(struct expo *exp, uint scene_id);
+
+/**
+ * resolve_id() - Automatically allocate an ID if needed
+ *
+ * @exp: Expo to use
+ * @id: ID to use, or 0 to auto-allocate one
+ * @return: Either @id, or the auto-allocated ID
+ */
+uint resolve_id(struct expo *exp, uint id);
+
+/**
+ * scene_obj_find() - Find an object in a scene
+ *
+ * Note that @type is used to restrict the search when the object type is known.
+ * If any type is acceptable, set @type to SCENEOBJT_NONE
+ *
+ * @scn: Scene to search
+ * @id: ID of object to find
+ * @type: Type of the object, or SCENEOBJT_NONE to match any type
+ */
+void *scene_obj_find(struct scene *scn, uint id, enum scene_obj_t type);
+
+/**
+ * scene_obj_add() - Add a new object to a scene
+ *
+ * @scn: Scene to update
+ * @name: Name to use (this is allocated by this call)
+ * @id: ID to use for the new object (0 to allocate one)
+ * @type: Type of object to add
+ * @size: Size to allocate for the object, in bytes
+ * @objp: Returns a pointer to the new object (must not be NULL)
+ * Returns: ID number for the object (generally @id), or -ve on error
+ */
+int scene_obj_add(struct scene *scn, const char *name, uint id,
+		  enum scene_obj_t type, uint size, struct scene_obj **objp);
+
+/**
+ * scene_menu_arrange() - Set the position of things in the menu
+ *
+ * This updates any items associated with a menu to make sure they are
+ * positioned correctly relative to the menu. It also selects the first item
+ * if not already done
+ *
+ * @scn: Scene to update
+ * @menu: Menu to process
+ */
+int scene_menu_arrange(struct scene *scn, struct scene_obj_menu *menu);
+
+/**
+ * scene_menu_send_key() - Send a key to a menu for processing
+ *
+ * @scn: Scene to use
+ * @menu: Menu to use
+ * @key: Key code to send (KEY_...)
+ * @event: Place to put any event which is generated by the key
+ * @return 0 if OK, -ENOTTY if there is no current menu item, other -ve on other
+ *	error
+ */
+int scene_menu_send_key(struct scene *scn, struct scene_obj_menu *menu, int key,
+			struct expo_action *event);
+
+/**
+ * scene_menu_destroy() - Destroy a menu in a scene
+ *
+ * @scn: Scene to destroy
+ */
+void scene_menu_destroy(struct scene_obj_menu *menu);
+
+/**
+ * scene_menu_display() - Display a menu as text
+ *
+ * @menu: Menu to display
+ * @return 0 if OK, -ENOENT if @id is invalid
+ */
+int scene_menu_display(struct scene_obj_menu *menu);
+
+/**
+ * scene_destroy() - Destroy a scene and all its memory
+ *
+ * @scn: Scene to destroy
+ */
+void scene_destroy(struct scene *scn);
+
+/**
+ * scene_render() - Render a scene
+ *
+ * This is called from expo_render()
+ *
+ * @scn: Scene to render
+ * Returns: 0 if OK, -ve on error
+ */
+int scene_render(struct scene *scn);
+
+/**
+ * scene_send_key() - set a keypress to a scene
+ *
+ * @scn: Scene to receive the key
+ * @key: Key to send (KEYCODE_UP)
+ * @event: Returns resulting event from this keypress
+ * Returns: 0 if OK, -ve on error
+ */
+int scene_send_key(struct scene *scn, int key, struct expo_action *event);
+
+#endif /* __SCENE_INTERNAL_H */
-- 
2.39.0.314.g84b9a713c41-goog


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

* [PATCH v3 20/25] expo: Add support for scene menus
  2023-01-06 14:52 [PATCH v3 00/25] bootstd: Add a boot menu Simon Glass
                   ` (18 preceding siblings ...)
  2023-01-06 14:52 ` [PATCH v3 19/25] expo: Add support for scenes Simon Glass
@ 2023-01-06 14:52 ` Simon Glass
  2023-01-06 14:52 ` [PATCH v3 21/25] expo: Add basic tests Simon Glass
                   ` (5 subsequent siblings)
  25 siblings, 0 replies; 40+ messages in thread
From: Simon Glass @ 2023-01-06 14:52 UTC (permalink / raw)
  To: U-Boot Mailing List
  Cc: Anatolij Gustschin, Tom Rini, Heinrich Schuchardt, Simon Glass

A menu is a key part of the expo design. It consists of a number of items
which the user can select from.

Add the initial implementation of this.

Signed-off-by: Simon Glass <sjg@chromium.org>
---

(no changes since v2)

Changes in v2:
- Drop the _add suffix on expo creation function
- Put strings in a separate structure referenced by ID

 boot/Makefile     |   2 +
 boot/scene_menu.c | 390 ++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 392 insertions(+)
 create mode 100644 boot/scene_menu.c

diff --git a/boot/Makefile b/boot/Makefile
index f0c31549213..0b30fcd64a9 100644
--- a/boot/Makefile
+++ b/boot/Makefile
@@ -47,6 +47,8 @@ ifdef CONFIG_SPL_BUILD
 obj-$(CONFIG_SPL_LOAD_FIT) += common_fit.o
 endif
 
+obj-$(CONFIG_$(SPL_TPL_)BOOTMETH_VBE) += expo.o scene.o scene_menu.o
+
 obj-$(CONFIG_$(SPL_TPL_)BOOTMETH_VBE) += vbe.o vbe_request.o
 obj-$(CONFIG_$(SPL_TPL_)BOOTMETH_VBE_SIMPLE) += vbe_simple.o
 obj-$(CONFIG_$(SPL_TPL_)BOOTMETH_VBE_SIMPLE_FW) += vbe_simple_fw.o
diff --git a/boot/scene_menu.c b/boot/scene_menu.c
new file mode 100644
index 00000000000..18998e862ab
--- /dev/null
+++ b/boot/scene_menu.c
@@ -0,0 +1,390 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Implementation of a menu in a scene
+ *
+ * Copyright 2022 Google LLC
+ * Written by Simon Glass <sjg@chromium.org>
+ */
+
+#define LOG_CATEGORY	LOGC_BOOT
+
+#include <common.h>
+#include <dm.h>
+#include <expo.h>
+#include <malloc.h>
+#include <mapmem.h>
+#include <menu.h>
+#include <video.h>
+#include <video_console.h>
+#include <linux/input.h>
+#include "scene_internal.h"
+
+static void scene_menuitem_destroy(struct scene_menitem *item)
+{
+	free(item->name);
+	free(item);
+}
+
+void scene_menu_destroy(struct scene_obj_menu *menu)
+{
+	struct scene_menitem *item, *next;
+
+	list_for_each_entry_safe(item, next, &menu->item_head, sibling)
+		scene_menuitem_destroy(item);
+}
+
+/**
+ * menu_point_to_item() - Point to a particular menu item
+ *
+ * Sets the currently pointed-to / highlighted menu item
+ */
+static void menu_point_to_item(struct scene_obj_menu *menu, uint item_id)
+{
+	menu->cur_item_id = item_id;
+}
+
+int scene_menu_arrange(struct scene *scn, struct scene_obj_menu *menu)
+{
+	struct scene_menitem *item;
+	int y, cur_y;
+	int ret;
+
+	y = menu->obj.y;
+	if (menu->title_id) {
+		ret = scene_obj_set_pos(scn, menu->title_id, menu->obj.x, y);
+		if (ret < 0)
+			return log_msg_ret("tit", ret);
+
+		ret = scene_obj_get_hw(scn, menu->title_id, NULL);
+		if (ret < 0)
+			return log_msg_ret("hei", ret);
+
+		y += ret * 2;
+	}
+
+	/*
+	 * Currently everything is hard-coded to particular columns so this
+	 * won't work on small displays and looks strange if the font size is
+	 * small. This can be updated once text measuring is supported in
+	 * vidconsole
+	 */
+	cur_y = -1;
+	list_for_each_entry(item, &menu->item_head, sibling) {
+		int height;
+
+		ret = scene_obj_get_hw(scn, item->desc_id, NULL);
+		if (ret < 0)
+			return log_msg_ret("get", ret);
+		height = ret;
+
+		if (item->flags & SCENEMIF_GAP_BEFORE)
+			y += height;
+
+		/* select an item if not done already */
+		if (!menu->cur_item_id)
+			menu_point_to_item(menu, item->id);
+
+		/*
+		 * Put the label on the left, then leave a space for the
+		 * pointer, then the key and the description
+		 */
+		if (item->label_id) {
+			ret = scene_obj_set_pos(scn, item->label_id, menu->obj.x,
+						y);
+			if (ret < 0)
+				return log_msg_ret("nam", ret);
+		}
+
+		ret = scene_obj_set_pos(scn, item->key_id, menu->obj.x + 230,
+					y);
+		if (ret < 0)
+			return log_msg_ret("key", ret);
+
+		ret = scene_obj_set_pos(scn, item->desc_id, menu->obj.x + 280,
+					y);
+		if (ret < 0)
+			return log_msg_ret("des", ret);
+
+		if (menu->cur_item_id == item->id)
+			cur_y = y;
+
+		if (item->preview_id) {
+			bool hide;
+
+			/*
+			 * put all previews on top of each other, on the right
+			 * size of the display
+			 */
+			ret = scene_obj_set_pos(scn, item->preview_id, -4, y);
+			if (ret < 0)
+				return log_msg_ret("prev", ret);
+
+			hide = menu->cur_item_id != item->id;
+			ret = scene_obj_set_hide(scn, item->preview_id, hide);
+			if (ret < 0)
+				return log_msg_ret("hid", ret);
+		}
+
+		y += height;
+	}
+
+	if (menu->pointer_id && cur_y != -1) {
+		/*
+		 * put the pointer to the right of and level with the item it
+		 * points to
+		 */
+		ret = scene_obj_set_pos(scn, menu->pointer_id,
+					menu->obj.x + 200, cur_y);
+		if (ret < 0)
+			return log_msg_ret("ptr", ret);
+	}
+
+	return 0;
+}
+
+int scene_menu(struct scene *scn, const char *name, uint id,
+	       struct scene_obj_menu **menup)
+{
+	struct scene_obj_menu *menu;
+	int ret;
+
+	ret = scene_obj_add(scn, name, id, SCENEOBJT_MENU,
+			    sizeof(struct scene_obj_menu),
+			    (struct scene_obj **)&menu);
+	if (ret < 0)
+		return log_msg_ret("obj", -ENOMEM);
+
+	if (menup)
+		*menup = menu;
+	INIT_LIST_HEAD(&menu->item_head);
+
+	ret = scene_menu_arrange(scn, menu);
+	if (ret)
+		return log_msg_ret("pos", ret);
+
+	return menu->obj.id;
+}
+
+static struct scene_menitem *scene_menu_find_key(struct scene *scn,
+						  struct scene_obj_menu *menu,
+						  int key)
+{
+	struct scene_menitem *item;
+
+	list_for_each_entry(item, &menu->item_head, sibling) {
+		if (item->key_id) {
+			struct scene_obj_txt *txt;
+			const char *str;
+
+			txt = scene_obj_find(scn, item->key_id, SCENEOBJT_TEXT);
+			if (txt) {
+				str = expo_get_str(scn->expo, txt->str_id);
+				if (str && *str == key)
+					return item;
+			}
+		}
+	}
+
+	return NULL;
+}
+
+int scene_menu_send_key(struct scene *scn, struct scene_obj_menu *menu, int key,
+			struct expo_action *event)
+{
+	struct scene_menitem *item, *cur, *key_item;
+
+	cur = NULL;
+	key_item = NULL;
+
+	if (!list_empty(&menu->item_head)) {
+		list_for_each_entry(item, &menu->item_head, sibling) {
+			/* select an item if not done already */
+			if (menu->cur_item_id == item->id) {
+				cur = item;
+				break;
+			}
+		}
+	}
+
+	if (!cur)
+		return -ENOTTY;
+
+	switch (key) {
+	case BKEY_UP:
+		if (item != list_first_entry(&menu->item_head,
+					     struct scene_menitem, sibling)) {
+			item = list_entry(item->sibling.prev,
+					  struct scene_menitem, sibling);
+			event->type = EXPOACT_POINT;
+			event->select.id = item->id;
+			log_debug("up to item %d\n", event->select.id);
+		}
+		break;
+	case BKEY_DOWN:
+		if (!list_is_last(&item->sibling, &menu->item_head)) {
+			item = list_entry(item->sibling.next,
+					  struct scene_menitem, sibling);
+			event->type = EXPOACT_POINT;
+			event->select.id = item->id;
+			log_debug("down to item %d\n", event->select.id);
+		}
+		break;
+	case BKEY_SELECT:
+		event->type = EXPOACT_SELECT;
+		event->select.id = item->id;
+		log_debug("select item %d\n", event->select.id);
+		break;
+	case BKEY_QUIT:
+		event->type = EXPOACT_QUIT;
+		log_debug("quit\n");
+		break;
+	case '0'...'9':
+		key_item = scene_menu_find_key(scn, menu, key);
+		if (key_item) {
+			event->type = EXPOACT_SELECT;
+			event->select.id = key_item->id;
+		}
+		break;
+	}
+
+	menu_point_to_item(menu, item->id);
+
+	return 0;
+}
+
+int scene_menuitem(struct scene *scn, uint menu_id, const char *name, uint id,
+		   uint key_id, uint label_id, uint desc_id, uint preview_id,
+		   uint flags, struct scene_menitem **itemp)
+{
+	struct scene_obj_menu *menu;
+	struct scene_menitem *item;
+	int ret;
+
+	menu = scene_obj_find(scn, menu_id, SCENEOBJT_MENU);
+	if (!menu)
+		return log_msg_ret("find", -ENOENT);
+
+	/* Check that the text ID is valid */
+	if (!scene_obj_find(scn, desc_id, SCENEOBJT_TEXT))
+		return log_msg_ret("txt", -EINVAL);
+
+	item = calloc(1, sizeof(struct scene_obj_menu));
+	if (!item)
+		return log_msg_ret("item", -ENOMEM);
+	item->name = strdup(name);
+	if (!item->name) {
+		free(item);
+		return log_msg_ret("name", -ENOMEM);
+	}
+
+	item->id = resolve_id(scn->expo, id);
+	item->key_id = key_id;
+	item->label_id = label_id;
+	item->desc_id = desc_id;
+	item->preview_id = preview_id;
+	item->flags = flags;
+	list_add_tail(&item->sibling, &menu->item_head);
+
+	ret = scene_menu_arrange(scn, menu);
+	if (ret)
+		return log_msg_ret("pos", ret);
+
+	if (itemp)
+		*itemp = item;
+
+	return item->id;
+}
+
+int scene_menu_set_title(struct scene *scn, uint id, uint title_id)
+{
+	struct scene_obj_menu *menu;
+	struct scene_obj_txt *txt;
+
+	menu = scene_obj_find(scn, id, SCENEOBJT_MENU);
+	if (!menu)
+		return log_msg_ret("menu", -ENOENT);
+
+	/* Check that the ID is valid */
+	if (title_id) {
+		txt = scene_obj_find(scn, title_id, SCENEOBJT_TEXT);
+		if (!txt)
+			return log_msg_ret("txt", -EINVAL);
+	}
+
+	menu->title_id = title_id;
+
+	return 0;
+}
+
+int scene_menu_set_pointer(struct scene *scn, uint id, uint pointer_id)
+{
+	struct scene_obj_menu *menu;
+	struct scene_obj *obj;
+
+	menu = scene_obj_find(scn, id, SCENEOBJT_MENU);
+	if (!menu)
+		return log_msg_ret("menu", -ENOENT);
+
+	/* Check that the ID is valid */
+	if (pointer_id) {
+		obj = scene_obj_find(scn, pointer_id, SCENEOBJT_NONE);
+		if (!obj)
+			return log_msg_ret("obj", -EINVAL);
+	}
+
+	menu->pointer_id = pointer_id;
+
+	return 0;
+}
+
+int scene_menu_display(struct scene_obj_menu *menu)
+{
+	struct scene *scn = menu->obj.scene;
+	struct scene_obj_txt *pointer;
+	struct expo *exp = scn->expo;
+	struct scene_menitem *item;
+	const char *pstr;
+
+	printf("U-Boot    :    Boot Menu\n\n");
+	if (menu->title_id) {
+		struct scene_obj_txt *txt;
+		const char *str;
+
+		txt = scene_obj_find(scn, menu->title_id, SCENEOBJT_TEXT);
+		if (!txt)
+			return log_msg_ret("txt", -EINVAL);
+
+		str = expo_get_str(exp, txt->str_id);
+		printf("%s\n\n", str);
+	}
+
+	if (list_empty(&menu->item_head))
+		return 0;
+
+	pointer = scene_obj_find(scn, menu->pointer_id, SCENEOBJT_TEXT);
+	pstr = expo_get_str(scn->expo, pointer->str_id);
+
+	list_for_each_entry(item, &menu->item_head, sibling) {
+		struct scene_obj_txt *key = NULL, *label = NULL;
+		struct scene_obj_txt *desc = NULL;
+		const char *kstr = NULL, *lstr = NULL, *dstr = NULL;
+
+		key = scene_obj_find(scn, item->key_id, SCENEOBJT_TEXT);
+		if (key)
+			kstr = expo_get_str(exp, key->str_id);
+
+		label = scene_obj_find(scn, item->label_id, SCENEOBJT_TEXT);
+		if (label)
+			lstr = expo_get_str(exp, label->str_id);
+
+		desc = scene_obj_find(scn, item->desc_id, SCENEOBJT_TEXT);
+		if (desc)
+			dstr = expo_get_str(exp, desc->str_id);
+
+		printf("%3s  %3s  %-10s  %s\n",
+		       pointer && menu->cur_item_id == item->id ? pstr : "",
+		       kstr, lstr, dstr);
+	}
+
+	return -ENOTSUPP;
+}
-- 
2.39.0.314.g84b9a713c41-goog


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

* [PATCH v3 21/25] expo: Add basic tests
  2023-01-06 14:52 [PATCH v3 00/25] bootstd: Add a boot menu Simon Glass
                   ` (19 preceding siblings ...)
  2023-01-06 14:52 ` [PATCH v3 20/25] expo: Add support for scene menus Simon Glass
@ 2023-01-06 14:52 ` Simon Glass
  2023-01-06 14:52 ` [PATCH v3 22/25] bootstd: Support creating a boot menu Simon Glass
                   ` (4 subsequent siblings)
  25 siblings, 0 replies; 40+ messages in thread
From: Simon Glass @ 2023-01-06 14:52 UTC (permalink / raw)
  To: U-Boot Mailing List
  Cc: Anatolij Gustschin, Tom Rini, Heinrich Schuchardt, Simon Glass

Add some tests for the expo, including setting up and rendering an expo.

Signed-off-by: Simon Glass <sjg@chromium.org>
---

(no changes since v2)

Changes in v2:
- Drop the _add suffix on expo creation function
- Put strings in a separate structure referenced by ID

 test/boot/Makefile |   2 +
 test/boot/expo.c   | 539 +++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 541 insertions(+)
 create mode 100644 test/boot/expo.c

diff --git a/test/boot/Makefile b/test/boot/Makefile
index d724629d3b0..22ed61c8fa0 100644
--- a/test/boot/Makefile
+++ b/test/boot/Makefile
@@ -5,6 +5,8 @@
 obj-$(CONFIG_BOOTSTD) += bootdev.o bootstd_common.o bootflow.o bootmeth.o
 obj-$(CONFIG_FIT) += image.o
 
+obj-$(CONFIG_EXPO) += expo.o
+
 ifdef CONFIG_OF_LIVE
 obj-$(CONFIG_BOOTMETH_VBE_SIMPLE) += vbe_simple.o
 endif
diff --git a/test/boot/expo.c b/test/boot/expo.c
new file mode 100644
index 00000000000..7104dff05e8
--- /dev/null
+++ b/test/boot/expo.c
@@ -0,0 +1,539 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2022 Google LLC
+ * Written by Simon Glass <sjg@chromium.org>
+ */
+
+#include <common.h>
+#include <dm.h>
+#include <expo.h>
+#include <menu.h>
+#include <video.h>
+#include <linux/input.h>
+#include <test/suites.h>
+#include <test/ut.h>
+#include "bootstd_common.h"
+#include "../../boot/scene_internal.h"
+
+enum {
+	/* scenes */
+	SCENE1		= 7,
+	SCENE2,
+
+	/* objects */
+	OBJ_LOGO,
+	OBJ_TEXT,
+	OBJ_TEXT2,
+	OBJ_MENU,
+	OBJ_MENU_TITLE,
+
+	/* strings */
+	STR_TEXT,
+	STR_TEXT2,
+	STR_MENU_TITLE,
+	STR_POINTER_TEXT,
+
+	STR_ITEM1_LABEL,
+	STR_ITEM1_DESC,
+	STR_ITEM1_KEY,
+	STR_ITEM1_PREVIEW,
+
+	STR_ITEM2_LABEL,
+	STR_ITEM2_DESC,
+	STR_ITEM2_KEY,
+	STR_ITEM2_PREVIEW,
+
+	/* menu items */
+	ITEM1,
+	ITEM1_LABEL,
+	ITEM1_DESC,
+	ITEM1_KEY,
+	ITEM1_PREVIEW,
+
+	ITEM2,
+	ITEM2_LABEL,
+	ITEM2_DESC,
+	ITEM2_KEY,
+	ITEM2_PREVIEW,
+
+	/* pointer to current item */
+	POINTER_TEXT,
+};
+
+#define BAD_POINTER	((void *)1)
+
+/* names for various things */
+#define EXPO_NAME	"my menus"
+#define SCENE_NAME1	"main"
+#define SCENE_NAME2	"second"
+#define SCENE_TITLE	"Main Menu"
+#define LOGO_NAME	"logo"
+
+/* Check base expo support */
+static int expo_base(struct unit_test_state *uts)
+{
+	struct udevice *dev;
+	struct expo *exp;
+	ulong start_mem;
+	char name[100];
+	int i;
+
+	ut_assertok(uclass_first_device_err(UCLASS_VIDEO, &dev));
+
+	start_mem = ut_check_free();
+
+	exp = NULL;
+	strcpy(name, EXPO_NAME);
+	ut_assertok(expo_new(name, NULL, &exp));
+	*name = '\0';
+	ut_assertnonnull(exp);
+	ut_asserteq(0, exp->scene_id);
+	ut_asserteq(0, exp->next_id);
+
+	/* Make sure the name was allocated */
+	ut_assertnonnull(exp->name);
+	ut_asserteq_str(EXPO_NAME, exp->name);
+
+	ut_assertok(expo_set_display(exp, dev));
+	expo_destroy(exp);
+	ut_assertok(ut_check_delta(start_mem));
+
+	/* test handling out-of-memory conditions */
+	for (i = 0; i < 2; i++) {
+		struct expo *exp2;
+
+		malloc_enable_testing(i);
+		exp2 = BAD_POINTER;
+		ut_asserteq(-ENOMEM, expo_new(EXPO_NAME, NULL, &exp2));
+		ut_asserteq_ptr(BAD_POINTER, exp2);
+		malloc_disable_testing();
+	}
+
+	return 0;
+}
+BOOTSTD_TEST(expo_base, UT_TESTF_DM | UT_TESTF_SCAN_FDT);
+
+/* Check creating a scene */
+static int expo_scene(struct unit_test_state *uts)
+{
+	struct scene *scn;
+	struct expo *exp;
+	ulong start_mem;
+	char name[100];
+	int id;
+
+	start_mem = ut_check_free();
+
+	ut_assertok(expo_new(EXPO_NAME, NULL, &exp));
+
+	scn = NULL;
+	ut_asserteq(0, exp->next_id);
+	strcpy(name, SCENE_NAME1);
+	id = scene_new(exp, name, SCENE1, &scn);
+	*name = '\0';
+	ut_assertnonnull(scn);
+	ut_asserteq(SCENE1, id);
+	ut_asserteq(SCENE1 + 1, exp->next_id);
+	ut_asserteq_ptr(exp, scn->expo);
+
+	/* Make sure the name was allocated */
+	ut_assertnonnull(scn->name);
+	ut_asserteq_str(SCENE_NAME1, scn->name);
+
+	/* Set the title */
+	strcpy(name, SCENE_TITLE);
+	ut_assertok(scene_title_set(scn, name));
+	*name = '\0';
+	ut_assertnonnull(scn->title);
+	ut_asserteq_str(SCENE_TITLE, scn->title);
+
+	/* Use an allocated ID */
+	scn = NULL;
+	id = scene_new(exp, SCENE_NAME2, 0, &scn);
+	ut_assertnonnull(scn);
+	ut_asserteq(SCENE2, id);
+	ut_asserteq(SCENE2 + 1, exp->next_id);
+	ut_asserteq_ptr(exp, scn->expo);
+
+	ut_asserteq_str(SCENE_NAME2, scn->name);
+
+	expo_destroy(exp);
+
+	ut_assertok(ut_check_delta(start_mem));
+
+	return 0;
+}
+BOOTSTD_TEST(expo_scene, UT_TESTF_DM | UT_TESTF_SCAN_FDT);
+
+/* Check creating a scene with objects */
+static int expo_object(struct unit_test_state *uts)
+{
+	struct scene_obj_img *img;
+	struct scene_obj_txt *txt;
+	struct scene *scn;
+	struct expo *exp;
+	ulong start_mem;
+	char name[100];
+	char *data;
+	int id;
+
+	start_mem = ut_check_free();
+
+	ut_assertok(expo_new(EXPO_NAME, NULL, &exp));
+	id = scene_new(exp, SCENE_NAME1, SCENE1, &scn);
+	ut_assert(id > 0);
+
+	ut_asserteq(0, scene_obj_count(scn));
+
+	data = NULL;
+	strcpy(name, LOGO_NAME);
+	id = scene_img(scn, name, OBJ_LOGO, data, &img);
+	ut_assert(id > 0);
+	*name = '\0';
+	ut_assertnonnull(img);
+	ut_asserteq(OBJ_LOGO, id);
+	ut_asserteq(OBJ_LOGO + 1, exp->next_id);
+	ut_asserteq_ptr(scn, img->obj.scene);
+	ut_asserteq(SCENEOBJT_IMAGE, img->obj.type);
+
+	ut_asserteq_ptr(data, img->data);
+
+	/* Make sure the name was allocated */
+	ut_assertnonnull(scn->name);
+	ut_asserteq_str(SCENE_NAME1, scn->name);
+
+	ut_asserteq(1, scene_obj_count(scn));
+
+	id = scene_txt_str(scn, "text", OBJ_TEXT, STR_TEXT, "my string", &txt);
+	ut_assert(id > 0);
+	ut_assertnonnull(txt);
+	ut_asserteq(OBJ_TEXT, id);
+	ut_asserteq(SCENEOBJT_TEXT, txt->obj.type);
+	ut_asserteq(2, scene_obj_count(scn));
+
+	/* Check passing NULL as the final parameter */
+	id = scene_txt_str(scn, "text2", OBJ_TEXT2, STR_TEXT2, "another string",
+			   NULL);
+	ut_assert(id > 0);
+	ut_asserteq(3, scene_obj_count(scn));
+
+	expo_destroy(exp);
+
+	ut_assertok(ut_check_delta(start_mem));
+
+	return 0;
+}
+BOOTSTD_TEST(expo_object, UT_TESTF_DM | UT_TESTF_SCAN_FDT);
+
+/* Check setting object attributes */
+static int expo_object_attr(struct unit_test_state *uts)
+{
+	struct scene_obj_menu *menu;
+	struct scene_obj_img *img;
+	struct scene_obj_txt *txt;
+	struct scene *scn;
+	struct expo *exp;
+	ulong start_mem;
+	char name[100];
+	char *data;
+	int id;
+
+	start_mem = ut_check_free();
+
+	ut_assertok(expo_new(EXPO_NAME, NULL, &exp));
+	id = scene_new(exp, SCENE_NAME1, SCENE1, &scn);
+	ut_assert(id > 0);
+
+	data = NULL;
+	id = scene_img(scn, LOGO_NAME, OBJ_LOGO, data, &img);
+	ut_assert(id > 0);
+
+	ut_assertok(scene_obj_set_pos(scn, OBJ_LOGO, 123, 456));
+	ut_asserteq(123, img->obj.x);
+	ut_asserteq(456, img->obj.y);
+
+	ut_asserteq(-ENOENT, scene_obj_set_pos(scn, OBJ_TEXT2, 0, 0));
+
+	id = scene_txt_str(scn, "text", OBJ_TEXT, STR_TEXT, "my string", &txt);
+	ut_assert(id > 0);
+
+	strcpy(name, "font2");
+	ut_assertok(scene_txt_set_font(scn, OBJ_TEXT, name, 42));
+	ut_asserteq_ptr(name, txt->font_name);
+	ut_asserteq(42, txt->font_size);
+
+	ut_asserteq(-ENOENT, scene_txt_set_font(scn, OBJ_TEXT2, name, 42));
+
+	id = scene_menu(scn, "main", OBJ_MENU, &menu);
+	ut_assert(id > 0);
+
+	ut_assertok(scene_menu_set_title(scn, OBJ_MENU, OBJ_TEXT));
+
+	ut_asserteq(-ENOENT, scene_menu_set_title(scn, OBJ_TEXT2, OBJ_TEXT));
+	ut_asserteq(-EINVAL, scene_menu_set_title(scn, OBJ_MENU, OBJ_TEXT2));
+
+	expo_destroy(exp);
+
+	ut_assertok(ut_check_delta(start_mem));
+
+	return 0;
+}
+BOOTSTD_TEST(expo_object_attr, UT_TESTF_DM | UT_TESTF_SCAN_FDT);
+
+/* Check creating a scene with a menu */
+static int expo_object_menu(struct unit_test_state *uts)
+{
+	struct scene_obj_menu *menu;
+	struct scene_menitem *item;
+	int id, label_id, desc_id, key_id, pointer_id, preview_id;
+	struct scene_obj_txt *ptr, *name1, *desc1, *key1, *tit, *prev1;
+	struct scene *scn;
+	struct expo *exp;
+	ulong start_mem;
+
+	start_mem = ut_check_free();
+
+	ut_assertok(expo_new(EXPO_NAME, NULL, &exp));
+	id = scene_new(exp, SCENE_NAME1, SCENE1, &scn);
+	ut_assert(id > 0);
+
+	id = scene_menu(scn, "main", OBJ_MENU, &menu);
+	ut_assert(id > 0);
+	ut_assertnonnull(menu);
+	ut_asserteq(OBJ_MENU, id);
+	ut_asserteq(SCENEOBJT_MENU, menu->obj.type);
+	ut_asserteq(0, menu->title_id);
+	ut_asserteq(0, menu->pointer_id);
+
+	ut_assertok(scene_obj_set_pos(scn, OBJ_MENU, 50, 400));
+	ut_asserteq(50, menu->obj.x);
+	ut_asserteq(400, menu->obj.y);
+
+	id = scene_txt_str(scn, "title", OBJ_MENU_TITLE, STR_MENU_TITLE,
+			   "Main Menu", &tit);
+	ut_assert(id > 0);
+	ut_assertok(scene_menu_set_title(scn, OBJ_MENU, OBJ_MENU_TITLE));
+	ut_asserteq(OBJ_MENU_TITLE, menu->title_id);
+
+	pointer_id = scene_txt_str(scn, "cur_item", POINTER_TEXT,
+				   STR_POINTER_TEXT, ">", &ptr);
+	ut_assert(pointer_id > 0);
+
+	ut_assertok(scene_menu_set_pointer(scn, OBJ_MENU, POINTER_TEXT));
+	ut_asserteq(POINTER_TEXT, menu->pointer_id);
+
+	label_id = scene_txt_str(scn, "label1", ITEM1_LABEL, STR_ITEM1_LABEL,
+				 "Play", &name1);
+	ut_assert(label_id > 0);
+
+	desc_id = scene_txt_str(scn, "desc1", ITEM1_DESC, STR_ITEM1_DESC,
+				"Lord Melchett", &desc1);
+	ut_assert(desc_id > 0);
+
+	key_id = scene_txt_str(scn, "item1-key", ITEM1_KEY, STR_ITEM1_KEY, "1",
+			       &key1);
+	ut_assert(key_id > 0);
+
+	preview_id = scene_txt_str(scn, "item1-preview", ITEM1_PREVIEW,
+				   STR_ITEM1_PREVIEW, "(preview1)", &prev1);
+	ut_assert(preview_id > 0);
+
+	id = scene_menuitem(scn, OBJ_MENU, "linux", ITEM1, ITEM1_KEY,
+			    ITEM1_LABEL, ITEM1_DESC, ITEM1_PREVIEW, 0, &item);
+	ut_asserteq(ITEM1, id);
+	ut_asserteq(id, item->id);
+	ut_asserteq(key_id, item->key_id);
+	ut_asserteq(label_id, item->label_id);
+	ut_asserteq(desc_id, item->desc_id);
+	ut_asserteq(preview_id, item->preview_id);
+
+	/* adding an item should cause the first item to become current */
+	ut_asserteq(id, menu->cur_item_id);
+
+	/* the title should be at the top */
+	ut_asserteq(menu->obj.x, tit->obj.x);
+	ut_asserteq(menu->obj.y, tit->obj.y);
+
+	/* the first item should be next */
+	ut_asserteq(menu->obj.x, name1->obj.x);
+	ut_asserteq(menu->obj.y + 32, name1->obj.y);
+
+	ut_asserteq(menu->obj.x + 230, key1->obj.x);
+	ut_asserteq(menu->obj.y + 32, key1->obj.y);
+
+	ut_asserteq(menu->obj.x + 200, ptr->obj.x);
+	ut_asserteq(menu->obj.y + 32, ptr->obj.y);
+
+	ut_asserteq(menu->obj.x + 280, desc1->obj.x);
+	ut_asserteq(menu->obj.y + 32, desc1->obj.y);
+
+	ut_asserteq(-4, prev1->obj.x);
+	ut_asserteq(menu->obj.y + 32, prev1->obj.y);
+	ut_asserteq(false, prev1->obj.hide);
+
+	expo_destroy(exp);
+
+	ut_assertok(ut_check_delta(start_mem));
+
+	return 0;
+}
+BOOTSTD_TEST(expo_object_menu, UT_TESTF_DM | UT_TESTF_SCAN_FDT);
+
+/* Check rendering a scene */
+static int expo_render_image(struct unit_test_state *uts)
+{
+	struct scene_obj_menu *menu;
+	struct scene *scn, *scn2;
+	struct expo_action act;
+	struct scene_obj *obj;
+	struct udevice *dev;
+	struct expo *exp;
+	int id;
+
+	console_record_reset_enable();
+	ut_assertok(uclass_first_device_err(UCLASS_VIDEO, &dev));
+
+	ut_assertok(expo_new(EXPO_NAME, NULL, &exp));
+	id = scene_new(exp, SCENE_NAME1, SCENE1, &scn);
+	ut_assert(id > 0);
+	ut_assertok(expo_set_display(exp, dev));
+
+	id = scene_img(scn, "logo", OBJ_LOGO, video_get_u_boot_logo(), NULL);
+	ut_assert(id > 0);
+	ut_assertok(scene_obj_set_pos(scn, OBJ_LOGO, 50, 20));
+
+	id = scene_txt_str(scn, "text", OBJ_TEXT, STR_TEXT, "my string", NULL);
+	ut_assert(id > 0);
+	ut_assertok(scene_txt_set_font(scn, OBJ_TEXT, "cantoraone_regular",
+				       40));
+	ut_assertok(scene_obj_set_pos(scn, OBJ_TEXT, 400, 100));
+
+	id = scene_txt_str(scn, "text", OBJ_TEXT2, STR_TEXT2, "another string",
+			   NULL);
+	ut_assert(id > 0);
+	ut_assertok(scene_txt_set_font(scn, OBJ_TEXT2, "nimbus_sans_l_regular",
+				       60));
+	ut_assertok(scene_obj_set_pos(scn, OBJ_TEXT2, 200, 600));
+
+	id = scene_menu(scn, "main", OBJ_MENU, &menu);
+	ut_assert(id > 0);
+
+	id = scene_txt_str(scn, "title", OBJ_MENU_TITLE, STR_MENU_TITLE,
+			   "Main Menu", NULL);
+	ut_assert(id > 0);
+	ut_assertok(scene_menu_set_title(scn, OBJ_MENU, OBJ_MENU_TITLE));
+
+	id = scene_txt_str(scn, "cur_item", POINTER_TEXT, STR_POINTER_TEXT, ">",
+			   NULL);
+	ut_assert(id > 0);
+	ut_assertok(scene_menu_set_pointer(scn, OBJ_MENU, POINTER_TEXT));
+
+	id = scene_txt_str(scn, "label1", ITEM1_LABEL, STR_ITEM1_LABEL, "Play",
+			   NULL);
+	ut_assert(id > 0);
+	id = scene_txt_str(scn, "item1 txt", ITEM1_DESC, STR_ITEM1_DESC,
+			   "Lord Melchett", NULL);
+	ut_assert(id > 0);
+	id = scene_txt_str(scn, "item1-key", ITEM1_KEY, STR_ITEM1_KEY, "1",
+			   NULL);
+	ut_assert(id > 0);
+	id = scene_img(scn, "item1-preview", ITEM1_PREVIEW,
+		       video_get_u_boot_logo(), NULL);
+	id = scene_menuitem(scn, OBJ_MENU, "item1", ITEM1, ITEM1_KEY,
+			    ITEM1_LABEL, ITEM1_DESC, ITEM1_PREVIEW, 0, NULL);
+	ut_assert(id > 0);
+
+	id = scene_txt_str(scn, "label2", ITEM2_LABEL, STR_ITEM2_LABEL, "Now",
+			   NULL);
+	ut_assert(id > 0);
+	id = scene_txt_str(scn, "item2 txt", ITEM2_DESC, STR_ITEM2_DESC,
+			   "Lord Percy", NULL);
+	ut_assert(id > 0);
+	id = scene_txt_str(scn, "item2-key", ITEM2_KEY, STR_ITEM2_KEY, "2",
+			   NULL);
+	ut_assert(id > 0);
+	id = scene_img(scn, "item2-preview", ITEM2_PREVIEW,
+		       video_get_u_boot_logo(), NULL);
+	ut_assert(id > 0);
+
+	id = scene_menuitem(scn, OBJ_MENU, "item2", ITEM2, ITEM2_KEY,
+			    ITEM2_LABEL, ITEM2_DESC, ITEM2_PREVIEW, 0, NULL);
+	ut_assert(id > 0);
+
+	ut_assertok(scene_obj_set_pos(scn, OBJ_MENU, 50, 400));
+
+	scn2 = expo_lookup_scene_id(exp, SCENE1);
+	ut_asserteq_ptr(scn, scn2);
+	scn2 = expo_lookup_scene_id(exp, SCENE2);
+	ut_assertnull(scn2);
+
+	/* render without a scene */
+	ut_asserteq(-ECHILD, expo_render(exp));
+
+	/* render it */
+	expo_set_scene_id(exp, SCENE1);
+	ut_assertok(expo_render(exp));
+
+	/* move down */
+	ut_assertok(expo_send_key(exp, BKEY_DOWN));
+
+	ut_assertok(expo_action_get(exp, &act));
+
+	ut_asserteq(EXPOACT_POINT, act.type);
+	ut_asserteq(ITEM2, act.select.id);
+	ut_assertok(expo_render(exp));
+
+	/* make sure only the preview for the second item is shown */
+	obj = scene_obj_find(scn, ITEM1_PREVIEW, SCENEOBJT_NONE);
+	ut_asserteq(true, obj->hide);
+
+	obj = scene_obj_find(scn, ITEM2_PREVIEW, SCENEOBJT_NONE);
+	ut_asserteq(false, obj->hide);
+
+	/* select it */
+	ut_assertok(expo_send_key(exp, BKEY_SELECT));
+
+	ut_assertok(expo_action_get(exp, &act));
+	ut_asserteq(EXPOACT_SELECT, act.type);
+	ut_asserteq(ITEM2, act.select.id);
+
+	/* make sure the action doesn't come again */
+	ut_asserteq(-EAGAIN, expo_action_get(exp, &act));
+
+	/* make sure there was no console output */
+	ut_assert_console_end();
+
+	/* now try in text mode */
+	exp_set_text_mode(exp, true);
+	ut_assertok(expo_render(exp));
+
+	ut_assert_nextline("U-Boot    :    Boot Menu");
+	ut_assert_nextline("%s", "");
+	ut_assert_nextline("Main Menu");
+	ut_assert_nextline("%s", "");
+	ut_assert_nextline("       1  Play        Lord Melchett");
+	ut_assert_nextline("  >    2  Now         Lord Percy");
+
+	/* Move back up to the first item */
+	ut_assertok(expo_send_key(exp, BKEY_UP));
+
+	ut_assertok(expo_action_get(exp, &act));
+
+	ut_asserteq(EXPOACT_POINT, act.type);
+	ut_asserteq(ITEM1, act.select.id);
+
+	ut_assertok(expo_render(exp));
+	ut_assert_nextline("U-Boot    :    Boot Menu");
+	ut_assert_nextline("%s", "");
+	ut_assert_nextline("Main Menu");
+	ut_assert_nextline("%s", "");
+	ut_assert_nextline("  >    1  Play        Lord Melchett");
+	ut_assert_nextline("       2  Now         Lord Percy");
+
+	ut_assert_console_end();
+
+	expo_destroy(exp);
+
+	return 0;
+}
+BOOTSTD_TEST(expo_render_image, UT_TESTF_DM | UT_TESTF_SCAN_FDT);
-- 
2.39.0.314.g84b9a713c41-goog


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

* [PATCH v3 22/25] bootstd: Support creating a boot menu
  2023-01-06 14:52 [PATCH v3 00/25] bootstd: Add a boot menu Simon Glass
                   ` (20 preceding siblings ...)
  2023-01-06 14:52 ` [PATCH v3 21/25] expo: Add basic tests Simon Glass
@ 2023-01-06 14:52 ` Simon Glass
  2023-01-06 14:52 ` [PATCH v3 23/25] bootstd: Add a test for the bootstd menu Simon Glass
                   ` (3 subsequent siblings)
  25 siblings, 0 replies; 40+ messages in thread
From: Simon Glass @ 2023-01-06 14:52 UTC (permalink / raw)
  To: U-Boot Mailing List
  Cc: Anatolij Gustschin, Tom Rini, Heinrich Schuchardt, Simon Glass

Create an expo to handle the boot menu. For now this is quite simple, with
just a header, some menu items and a pointer to show the current one.

Signed-off-by: Simon Glass <sjg@chromium.org>
---

(no changes since v2)

Changes in v2:
- Rebase to master

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

diff --git a/boot/Makefile b/boot/Makefile
index 0b30fcd64a9..f990e66f522 100644
--- a/boot/Makefile
+++ b/boot/Makefile
@@ -30,6 +30,7 @@ obj-$(CONFIG_$(SPL_TPL_)BOOTMETH_SANDBOX) += bootmeth_sandbox.o
 obj-$(CONFIG_$(SPL_TPL_)BOOTMETH_SCRIPT) += bootmeth_script.o
 ifdef CONFIG_$(SPL_TPL_)BOOTSTD_FULL
 obj-$(CONFIG_$(SPL_TPL_)CMD_BOOTEFI_BOOTMGR) += bootmeth_efi_mgr.o
+obj-$(CONFIG_$(SPL_TPL_)BOOTSTD) += bootflow_menu.o
 endif
 
 obj-$(CONFIG_$(SPL_TPL_)OF_LIBFDT) += image-fdt.o
diff --git a/boot/bootflow_internal.h b/boot/bootflow_internal.h
new file mode 100644
index 00000000000..38cf02a55b5
--- /dev/null
+++ b/boot/bootflow_internal.h
@@ -0,0 +1,47 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Internal header file for bootflow
+ *
+ * Copyright 2022 Google LLC
+ * Written by Simon Glass <sjg@chromium.org>
+ */
+
+#ifndef __BOOTFLOW_INTERNAL_H
+#define __BOOTFLOW_INTERNAL_H
+
+/* expo IDs for elements of the bootflow menu */
+enum {
+	START,
+
+	/* strings */
+	STR_PROMPT,
+	STR_MENU_TITLE,
+	STR_POINTER,
+
+	/* scene */
+	MAIN,
+
+	/* objects */
+	OBJ_U_BOOT_LOGO,
+	OBJ_MENU,
+	OBJ_PROMPT,
+	OBJ_MENU_TITLE,
+	OBJ_POINTER,
+
+	/* strings for menu items */
+	STR_LABEL = 100,
+	STR_DESC = 200,
+	STR_KEY = 300,
+
+	/* menu items / components (bootflow number is added to these) */
+	ITEM = 400,
+	ITEM_LABEL = 500,
+	ITEM_DESC = 600,
+	ITEM_KEY = 700,
+	ITEM_PREVIEW = 800,
+
+	/* left margin for the main menu */
+	MARGIN_LEFT	 = 100,
+};
+
+#endif /* __BOOTFLOW_INTERNAL_H */
diff --git a/boot/bootflow_menu.c b/boot/bootflow_menu.c
new file mode 100644
index 00000000000..1105783afeb
--- /dev/null
+++ b/boot/bootflow_menu.c
@@ -0,0 +1,241 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Provide a menu of available bootflows and related options
+ *
+ * Copyright 2022 Google LLC
+ * Written by Simon Glass <sjg@chromium.org>
+ */
+
+#define LOG_CATEGORY UCLASS_BOOTSTD
+
+#include <common.h>
+#include <bootflow.h>
+#include <bootstd.h>
+#include <cli.h>
+#include <dm.h>
+#include <expo.h>
+#include <malloc.h>
+#include <menu.h>
+#include <video_console.h>
+#include <watchdog.h>
+#include <linux/delay.h>
+#include "bootflow_internal.h"
+
+/**
+ * struct menu_priv - information about the menu
+ *
+ * @num_bootflows: Number of bootflows in the menu
+ */
+struct menu_priv {
+	int num_bootflows;
+};
+
+int bootflow_menu_new(struct expo **expp)
+{
+	struct udevice *last_bootdev;
+	struct scene_obj_menu *menu;
+	struct menu_priv *priv;
+	struct bootflow *bflow;
+	struct scene *scn;
+	struct expo *exp;
+	void *logo;
+	int ret, i;
+
+	priv = calloc(1, sizeof(*priv));
+	if (!priv)
+		return log_msg_ret("prv", -ENOMEM);
+
+	ret = expo_new("bootflows", priv, &exp);
+	if (ret)
+		return log_msg_ret("exp", ret);
+
+	ret = scene_new(exp, "main", MAIN, &scn);
+	if (ret < 0)
+		return log_msg_ret("scn", ret);
+
+	ret |= scene_txt_str(scn, "prompt", OBJ_PROMPT, STR_PROMPT,
+			     "UP and DOWN to choose, ENTER to select", NULL);
+
+	ret = scene_menu(scn, "main", OBJ_MENU, &menu);
+	ret |= scene_obj_set_pos(scn, OBJ_MENU, MARGIN_LEFT, 100);
+	ret |= scene_txt_str(scn, "title", OBJ_MENU_TITLE, STR_MENU_TITLE,
+			     "U-Boot - Boot Menu", NULL);
+	ret |= scene_menu_set_title(scn, OBJ_MENU, OBJ_PROMPT);
+
+	logo = video_get_u_boot_logo();
+	if (logo) {
+		ret |= scene_img(scn, "ulogo", OBJ_U_BOOT_LOGO, logo, NULL);
+		ret |= scene_obj_set_pos(scn, OBJ_U_BOOT_LOGO, -4, 4);
+	}
+
+	ret |= scene_txt_str(scn, "cur_item", OBJ_POINTER, STR_POINTER, ">",
+			     NULL);
+	ret |= scene_menu_set_pointer(scn, OBJ_MENU, OBJ_POINTER);
+	if (ret < 0)
+		return log_msg_ret("new", -EINVAL);
+
+	last_bootdev = NULL;
+	for (ret = bootflow_first_glob(&bflow), i = 0; !ret && i < 36;
+	     ret = bootflow_next_glob(&bflow), i++) {
+		char str[2], *label, *key;
+		uint preview_id;
+		bool add_gap;
+
+		if (bflow->state != BOOTFLOWST_READY)
+			continue;
+
+		*str = i < 10 ? '0' + i : 'A' + i - 10;
+		str[1] = '\0';
+		key = strdup(str);
+		if (!key)
+			return log_msg_ret("key", -ENOMEM);
+		label = strdup(dev_get_parent(bflow->dev)->name);
+		if (!label) {
+			free(key);
+			return log_msg_ret("nam", -ENOMEM);
+		}
+
+		add_gap = last_bootdev != bflow->dev;
+		last_bootdev = bflow->dev;
+
+		ret = expo_str(exp, "prompt", STR_POINTER, ">");
+		ret |= scene_txt_str(scn, "label", ITEM_LABEL + i,
+				      STR_LABEL + i, label, NULL);
+		ret |= scene_txt_str(scn, "desc", ITEM_DESC + i, STR_DESC + i,
+				    bflow->os_name ? bflow->os_name :
+				    bflow->name, NULL);
+		ret |= scene_txt_str(scn, "key", ITEM_KEY + i, STR_KEY + i, key,
+				      NULL);
+		preview_id = 0;
+		if (bflow->logo) {
+			preview_id = ITEM_PREVIEW + i;
+			ret |= scene_img(scn, "preview", preview_id,
+					     bflow->logo, NULL);
+		}
+		ret |= scene_menuitem(scn, OBJ_MENU, "item", ITEM + i,
+					  ITEM_KEY + i, ITEM_LABEL + i,
+					  ITEM_DESC + i, preview_id,
+					  add_gap ? SCENEMIF_GAP_BEFORE : 0,
+					  NULL);
+
+		if (ret < 0)
+			return log_msg_ret("itm", -EINVAL);
+		ret = 0;
+		priv->num_bootflows++;
+	}
+
+	*expp = exp;
+
+	return 0;
+}
+
+int bootflow_menu_run(struct bootstd_priv *std, bool text_mode,
+		      struct bootflow **bflowp)
+{
+	struct cli_ch_state s_cch, *cch = &s_cch;
+	struct bootflow *sel_bflow;
+	struct udevice *dev;
+	struct expo *exp;
+	uint sel_id;
+	bool done;
+	int ret;
+
+	cli_ch_init(cch);
+
+	sel_bflow = NULL;
+	*bflowp = NULL;
+
+	ret = bootflow_menu_new(&exp);
+	if (ret)
+		return log_msg_ret("exp", ret);
+
+	/* For now we only support a video console */
+	ret = uclass_first_device_err(UCLASS_VIDEO, &dev);
+	if (ret)
+		return log_msg_ret("vid", ret);
+	ret = expo_set_display(exp, dev);
+	if (ret)
+		return log_msg_ret("dis", ret);
+
+	ret = expo_set_scene_id(exp, MAIN);
+	if (ret)
+		return log_msg_ret("scn", ret);
+
+	if (text_mode)
+		exp_set_text_mode(exp, text_mode);
+
+	done = false;
+	do {
+		struct expo_action act;
+		int ichar, key;
+
+		ret = expo_render(exp);
+		if (ret)
+			break;
+
+		ichar = cli_ch_process(cch, 0);
+		if (!ichar) {
+			while (!ichar && !tstc()) {
+				schedule();
+				mdelay(2);
+				ichar = cli_ch_process(cch, -ETIMEDOUT);
+			}
+			if (!ichar) {
+				ichar = getchar();
+				ichar = cli_ch_process(cch, ichar);
+			}
+		}
+
+		key = 0;
+		if (ichar) {
+			key = bootmenu_conv_key(ichar);
+			if (key == BKEY_NONE)
+				key = ichar;
+		}
+		if (!key)
+			continue;
+
+		ret = expo_send_key(exp, key);
+		if (ret)
+			break;
+
+		ret = expo_action_get(exp, &act);
+		if (!ret) {
+			switch (act.type) {
+			case EXPOACT_SELECT:
+				sel_id = act.select.id;
+				done = true;
+				break;
+			case EXPOACT_QUIT:
+				done = true;
+				break;
+			default:
+				break;
+			}
+		}
+	} while (!done);
+
+	if (ret)
+		return log_msg_ret("end", ret);
+
+	if (sel_id) {
+		struct bootflow *bflow;
+		int i;
+
+		for (ret = bootflow_first_glob(&bflow), i = 0; !ret && i < 36;
+		     ret = bootflow_next_glob(&bflow), i++) {
+			if (i == sel_id - ITEM) {
+				sel_bflow = bflow;
+				break;
+			}
+		}
+	}
+
+	expo_destroy(exp);
+
+	if (!sel_bflow)
+		return -EAGAIN;
+	*bflowp = sel_bflow;
+
+	return 0;
+}
diff --git a/cmd/bootflow.c b/cmd/bootflow.c
index 495ef85f25b..2b6ed26fdcb 100644
--- a/cmd/bootflow.c
+++ b/cmd/bootflow.c
@@ -389,6 +389,37 @@ static int do_bootflow_boot(struct cmd_tbl *cmdtp, int flag, int argc,
 
 	return 0;
 }
+
+static int do_bootflow_menu(struct cmd_tbl *cmdtp, int flag, int argc,
+			    char *const argv[])
+{
+	struct bootstd_priv *std;
+	struct bootflow *bflow;
+	bool text_mode = false;
+	int ret;
+
+	if (argc > 1 && *argv[1] == '-')
+		text_mode = strchr(argv[1], 't');
+
+	ret = bootstd_get_priv(&std);
+	if (ret)
+		return CMD_RET_FAILURE;
+
+	ret = bootflow_menu_run(std, text_mode, &bflow);
+	if (ret) {
+		if (ret == -EAGAIN)
+			printf("Nothing chosen\n");
+		else
+			printf("Menu failed (err=%d)\n", ret);
+
+		return CMD_RET_FAILURE;
+	}
+
+	printf("Selected: %s\n", bflow->os_name ? bflow->os_name : bflow->name);
+	std->cur_bootflow = bflow;
+
+	return 0;
+}
 #endif /* CONFIG_CMD_BOOTFLOW_FULL */
 
 #ifdef CONFIG_SYS_LONGHELP
@@ -398,7 +429,8 @@ static char bootflow_help_text[] =
 	"bootflow list [-e]             - list scanned bootflows (-e errors)\n"
 	"bootflow select [<num>|<name>] - select a bootflow\n"
 	"bootflow info [-d]             - show info on current bootflow (-d dump bootflow)\n"
-	"bootflow boot                  - boot current bootflow (or first available if none selected)";
+	"bootflow boot                  - boot current bootflow (or first available if none selected)\n"
+	"bootflow menu [-t]             - show a menu of available bootflows";
 #else
 	"scan - boot first available bootflow\n";
 #endif
@@ -410,6 +442,7 @@ U_BOOT_CMD_WITH_SUBCMDS(bootflow, "Boot flows", bootflow_help_text,
 	U_BOOT_SUBCMD_MKENT(list, 2, 1, do_bootflow_list),
 	U_BOOT_SUBCMD_MKENT(select, 2, 1, do_bootflow_select),
 	U_BOOT_SUBCMD_MKENT(info, 2, 1, do_bootflow_info),
-	U_BOOT_SUBCMD_MKENT(boot, 1, 1, do_bootflow_boot)
+	U_BOOT_SUBCMD_MKENT(boot, 1, 1, do_bootflow_boot),
+	U_BOOT_SUBCMD_MKENT(menu, 2, 1, do_bootflow_menu),
 #endif
 );
diff --git a/include/bootflow.h b/include/bootflow.h
index 8a07ab30191..e7a09568f1b 100644
--- a/include/bootflow.h
+++ b/include/bootflow.h
@@ -9,6 +9,9 @@
 
 #include <linux/list.h>
 
+struct bootstd_priv;
+struct expo;
+
 /**
  * enum bootflow_state_t - states that a particular bootflow can be in
  *
@@ -336,4 +339,24 @@ int bootflow_iter_uses_network(const struct bootflow_iter *iter);
  */
 int bootflow_iter_uses_system(const struct bootflow_iter *iter);
 
+/**
+ * bootflow_menu_new() - Create a new bootflow menu
+ *
+ * @expp: Returns the expo created
+ * Returns 0 on success, -ve on error
+ */
+int bootflow_menu_new(struct expo **expp);
+
+/**
+ * bootflow_menu_run() - Create and run a menu of available bootflows
+ *
+ * @std: Bootstd information
+ * @text_mode: Uses a text-based menu suitable for a serial port
+ * @bflowp: Returns chosen bootflow (set to NULL if nothing is chosen)
+ * @return 0 if an option was chosen, -EAGAIN if nothing was chosen, -ve on
+ * error
+ */
+int bootflow_menu_run(struct bootstd_priv *std, bool text_mode,
+		      struct bootflow **bflowp);
+
 #endif
-- 
2.39.0.314.g84b9a713c41-goog


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

* [PATCH v3 23/25] bootstd: Add a test for the bootstd menu
  2023-01-06 14:52 [PATCH v3 00/25] bootstd: Add a boot menu Simon Glass
                   ` (21 preceding siblings ...)
  2023-01-06 14:52 ` [PATCH v3 22/25] bootstd: Support creating a boot menu Simon Glass
@ 2023-01-06 14:52 ` Simon Glass
  2023-01-06 14:52 ` [PATCH v3 24/25] bootstd: Support setting a theme for the menu Simon Glass
                   ` (2 subsequent siblings)
  25 siblings, 0 replies; 40+ messages in thread
From: Simon Glass @ 2023-01-06 14:52 UTC (permalink / raw)
  To: U-Boot Mailing List
  Cc: Anatolij Gustschin, Tom Rini, Heinrich Schuchardt, Simon Glass

Add a test which checks that two operating systems can be displayed in a
menu, allowing one to be selected.

Enable a few things on snow so that the unit tests build.

Signed-off-by: Simon Glass <sjg@chromium.org>
---

(no changes since v2)

Changes in v2:
- Fix 'touse' typo
- Fix pylint warning in mkdir_cond()

 arch/sandbox/dts/test.dts            |  11 ++
 configs/snow_defconfig               |   4 +
 test/boot/bootflow.c                 |  51 +++++++
 test/py/tests/bootstd/armbian.bmp.xz | Bin 0 -> 1384 bytes
 test/py/tests/bootstd/mmc4.img.xz    | Bin 0 -> 7072 bytes
 test/py/tests/test_ut.py             | 218 ++++++++++++++++++++++++---
 6 files changed, 265 insertions(+), 19 deletions(-)
 create mode 100644 test/py/tests/bootstd/armbian.bmp.xz
 create mode 100644 test/py/tests/bootstd/mmc4.img.xz

diff --git a/arch/sandbox/dts/test.dts b/arch/sandbox/dts/test.dts
index dffe10adbf4..2e580f980fc 100644
--- a/arch/sandbox/dts/test.dts
+++ b/arch/sandbox/dts/test.dts
@@ -94,6 +94,10 @@
 			compatible = "u-boot,distro-efi";
 		};
 
+		theme {
+			font-size = <30>;
+		};
+
 		/*
 		 * This is used for the VBE OS-request tests. A FAT filesystem
 		 * created in a partition with the VBE information appearing
@@ -1013,6 +1017,13 @@
 		non-removable;
 	};
 
+	/* This is used for bootstd bootmenu tests */
+	mmc4 {
+		status = "disabled";
+		compatible = "sandbox,mmc";
+		filename = "mmc4.img";
+	};
+
 	pch {
 		compatible = "sandbox,pch";
 	};
diff --git a/configs/snow_defconfig b/configs/snow_defconfig
index 6921c5667da..faa3a944c02 100644
--- a/configs/snow_defconfig
+++ b/configs/snow_defconfig
@@ -28,7 +28,11 @@ CONFIG_HAS_CUSTOM_SYS_INIT_SP_ADDR=y
 CONFIG_CUSTOM_SYS_INIT_SP_ADDR=0x2050000
 CONFIG_FIT=y
 CONFIG_FIT_BEST_MATCH=y
+CONFIG_BOOTSTD_FULL=y
 CONFIG_SILENT_CONSOLE=y
+CONFIG_BLOBLIST=y
+# CONFIG_SPL_BLOBLIST is not set
+CONFIG_BLOBLIST_ADDR=0x43d00000
 # CONFIG_SPL_FRAMEWORK is not set
 CONFIG_SPL_FOOTPRINT_LIMIT=y
 CONFIG_SPL_MAX_FOOTPRINT=0x3800
diff --git a/test/boot/bootflow.c b/test/boot/bootflow.c
index 00dfd990687..abafa44b2ed 100644
--- a/test/boot/bootflow.c
+++ b/test/boot/bootflow.c
@@ -11,15 +11,21 @@
 #include <bootflow.h>
 #include <bootmeth.h>
 #include <bootstd.h>
+#include <cli.h>
 #include <dm.h>
 #ifdef CONFIG_SANDBOX
 #include <asm/test.h>
 #endif
+#include <dm/device-internal.h>
 #include <dm/lists.h>
 #include <test/suites.h>
 #include <test/ut.h>
 #include "bootstd_common.h"
 
+DECLARE_GLOBAL_DATA_PTR;
+
+extern U_BOOT_DRIVER(bootmeth_script);
+
 static int inject_response(struct unit_test_state *uts)
 {
 	/*
@@ -462,3 +468,48 @@ static int bootflow_cmd_boot(struct unit_test_state *uts)
 	return 0;
 }
 BOOTSTD_TEST(bootflow_cmd_boot, UT_TESTF_DM | UT_TESTF_SCAN_FDT);
+
+/* Check 'bootflow menu' to select a bootflow */
+static int bootflow_cmd_menu(struct unit_test_state *uts)
+{
+	static const char *order[] = {"mmc2", "mmc1", "mmc4", NULL};
+	struct udevice *dev, *bootstd;
+	struct bootstd_priv *std;
+	const char **old_order;
+	char prev[3];
+	ofnode node;
+
+	/* Enable the mmc4 node since we need a second bootflow */
+	node = ofnode_path("/mmc4");
+	ut_assertok(lists_bind_fdt(gd->dm_root, node, &dev, NULL, false));
+
+	/* Enable the script bootmeth too */
+	ut_assertok(uclass_first_device_err(UCLASS_BOOTSTD, &bootstd));
+	ut_assertok(device_bind(bootstd, DM_DRIVER_REF(bootmeth_script),
+				"bootmeth_script", 0, ofnode_null(), &dev));
+
+	/* Change the order to include mmc4 */
+	std = dev_get_priv(bootstd);
+	old_order = std->bootdev_order;
+	std->bootdev_order = order;
+
+	console_record_reset_enable();
+	ut_assertok(run_command("bootflow scan", 0));
+	ut_assert_console_end();
+
+	/* Restore the order used by the device tree */
+	std->bootdev_order = old_order;
+
+	/* Add keypresses to move to and select the second one in the list */
+	prev[0] = CTL_CH('n');
+	prev[1] = '\r';
+	prev[2] = '\0';
+	ut_asserteq(2, console_in_puts(prev));
+
+	ut_assertok(run_command("bootflow menu", 0));
+	ut_assert_nextline("Selected: Armbian");
+	ut_assert_console_end();
+
+	return 0;
+}
+BOOTSTD_TEST(bootflow_cmd_menu, UT_TESTF_DM | UT_TESTF_SCAN_FDT);
diff --git a/test/py/tests/bootstd/armbian.bmp.xz b/test/py/tests/bootstd/armbian.bmp.xz
new file mode 100644
index 0000000000000000000000000000000000000000..ad137ea6e6df5c51d0a98a4be190c4cb4c48fc76
GIT binary patch
literal 1384
zcmV-u1(*8$H+ooF000E$*0e?f03iV!0000G&sfahT|osUT>v2yL`vV4GWns~_Xm1L
zxE>Ubk@J6u_+aW`8~-8B2<e|Au4!68N=d*)`gT$sdsQ6~IO}ImLOcgScjC#+Pwg7i
z%N{PxBxSeP-e!(>G301rYP}LjG-Uj!5v5M@cMEp)w7Jy+>!JE+yYjbr*Cj;GV^2Hw
z@W7BuO=bDneVey~z?{IPxUWVi=>AB+fXkNrMXePTCtS4$20)TF&Ej-}em=4#*uIHD
z1l@)}!B8#S*Y2}K?!cbS0x0Q6+%GVl9r84w9-r^ww#S)^#5L%?tS_gQGFqN4x0;M*
z069Xd{Tg$MBJXPB3ZuxY@@+^>NZzLHs%(t8LBUGbn8_tB+-cv$3n422Xoz1d8hrHM
z(HW_#2QB54kA#LC*EWh*<^|;Iq{oeo2`LwHJ&s-Um&v;`i<Hh_{uQ<aDpBW%)4f3j
z%htcy{*A=zeeIUWny<X_ityCve&sL(46xV?51|w+KZF*%Bj?&`#$%GmZ)*@=?t?-!
zaURO*dr)zuJYIVQADTWr5l1b}?i2otPY;ASIu%ZQYrcbsCH%S6W%<9ak5(_(a)gj)
zGAT^3y*cYq`8#=(_a50+F6668Cs*<5_OqF9DD8<%OzUmJ-ZN(p#q}ZMIh1I;*Tb{{
z47ZuQ;sIfdkNL{ba>lv|L2QbW*>IAi?Ac$V1;eoHwRcj6<3Yj~Om#>)(xD*U9^*iF
z(_&Bn;ATGLy(x8wegW)L3qq6kQv=_<ZC`EUJM;PXU}^ZJHP%v#gkbAUx&fvHtH>3Z
zx*?Kp$;0{#iDe<z!u*+<(t;1|g6X;C2Sovpdcao)09^lsGTfCfirjb_!>|0~1!@^T
z)sbCy$vmaHvbwE}%8<l;aDj#1o{xBJ%mDXP5KTGzl2ZYzUGShKYy76tHEBf(KK$iS
z5L&cFsoG%pCEr7*AzehFd+qNf|Ch(&0|Pm7tXaRb><EIiC6c%TQPzH2^;r6?oQrk_
zVLI8GCW88UnCOeO1NGpLO%L#XOvdtRV`nczGDXrab<~YeRQi~0If#H9%BAZ!g#bYV
znD;X}b-7fS?$Ml_;CI3mWm4uU_xv5pR}NQsYRxo<%AXP$%4N$dI|1t6{yv3k_;6TI
zh_mFpJ?Z0M8yd=wU5gUQOEc|7QkjFELHn(kyXPW?0<7m?T$3(J+EINmZgnI<NKa$r
z(9(+{4`Z3Ji=T0ZEKLGu_kxz#l(<5&+ZA(g{QK;T8K0hR3%c7Vxuu3WQggES!W_37
z3m8|BG<mx`zT9fzs$|2>WHO)D1ZmGr2!sS+OWbZF=;oH_jv+}huqHA-6Xk^mvDd5M
z(n<NtWpM%9SiUE&k8UD|qWNcT9v0`$AEkUuS{6LkVwA^oS9ev(*{1v$WkOWIrl@lC
z^KwdBFO$7Rn<_&S!IR;dY-3GOc0C#d0gb2GhTKb;r`YWEx|cU7MelgjEYTPr4}cMs
z9#15ELINv@YeO)Up{)+)qPgl0C`|92yvN?fG?V3I?UYgndEIk!FduFJYuDwsX=jin
z1-5O%AD7g66AlQAMtA;HB17+a7&VX34owZ=2o-^vZfRP-Zzkj^0ko|ce>V_bZUl6s
z0M21Xax4^PqqUQB_#oh0zKUmjc`-*=$Yu=#ARfS%t8K^XZk83$pxE{bst#FcVg*`V
z50vnrqts+Y9yvx&WN-3m0S*!nB~!7YuX!%)BKGIjHybq_@N8&zRFA>YNjS{Vc4FH4
qta<tX0000nxT(en3ebcA0l^Bwx&Z)ZZO2}*#Ao{g000001X)^sXQz_@

literal 0
HcmV?d00001

diff --git a/test/py/tests/bootstd/mmc4.img.xz b/test/py/tests/bootstd/mmc4.img.xz
new file mode 100644
index 0000000000000000000000000000000000000000..f4db011969fa8bea352ee2b8318dfdd72ccf72d8
GIT binary patch
literal 7072
zcmeHMXHXPemhDEeB*{$<O3qP00SQfRGB!aPP%@H(AV?6T$yrcB10Ol3mYf?TXAlq(
z5SpApY@lIgYWK%h?T>YK=i8dCo!_rs)w`$8ednHYvC;uhGXMY=J=Xxn0k8v^0002R
zICSLp_5uFxGXTI1d#!1VGNTr`mE;p*%0&LKnku_|*GGQJP@UYW>mQ)!S^JbvQYkY(
z46W26RjbZTot!9{G(@Wx*b0~aDkttKa_KAZeVurk05wnjsJ_IcR{H{__QF(ucVN)d
z-lkL|MungQ%rzK*PZ?3resA^mSYFFcDUpiomLlbs$1BrccSmp2c);YP*MZAdA4P;r
zx)LHz--dSCq8=FjI!}@k#Vf2iaO-y0Q6rg5x>r_d#oRJh%S~@#`fl6K0NSzbTVcAs
zVU9<L`Mg+pePzJ!J<4LvriA%s`1lv^3nICYVLq4dNVy|ayAZ65TweLbm%fY>|8&%T
zehHBty>Ke+c_zbISABGfV`}TDd7fk`WnO^o*aJOcQsyS>S(b6*O&6LGScxq-nbF&l
z*qMRP8MmRcj(#7Av-nQP<^Upc`t<1=?K~Rt2J!%<2ZDf`%-k@-T&=+>XQBar-B(1t
zIWn5YMc0ek@OGsbLxJFV9r0XZj~xB>g{ATEQ_?k_0UO039vtE!mUDLlehppJo8dgN
z_#G_I+Il8h88;u0@txv$alb}7m5QSvYx_x@TO6VHAEUJN{PX7=TC9YhuOzoIKNvq*
z(=3aAaItuM_%+49PLt%wKG{E%^SD=CZRrWN>fHFNs57I?s72}X2*y>K&x&IUGm4|e
zOy*1KwUVDx6VJ`VZB6)j?7{0&n1MYu@P1k)Sb&WChpcfXJj9eR2ReE+Irq~w_3j$b
zl11cmE}08${Wex|jtL-PP{I{Nwye8FaMmkX9lxr0%8?%}5o~jUo;cZ3W#&62hWv2C
zq6bxHm3rO0GA}2!5GgY+TCzoXl6i<HuPwhEZD*y57kg^b5SG7MraJp1?ikPZNrIcL
zWm0~yA+Om3WK1ful9W8`>-7zkC)_Ufwe?e04UaqZC+nq>&zo>;{2hB8+4c>IFBu=|
z9F?u{9~Co9i)(W<K{f(#J?GgE9J6;AIE8eXWq)nEX&3XeYa_gs$y6!^Sg9BtSVnd}
z4Dk0&oblw#pU8OhbvHHQ_<+>E)`0>k;nxwH%!CXBw$?Gx)U+_uVIfdkQIq}M(;=yJ
z-ba1<51*$@-(mh)=>A<06cw4}+7?*rw+t@|Y{W}_TFrVOT0;%x++b-W-ApK%oQk`V
zm)XekQd)nd%lpW6DvvK<N$#W3QDme$c(#sd+qU?wSycn0k)zQmNT-vi_aq?5k^8V`
zN>GJ={HlXtkISHGSkWn70bl`l4BhopV0ur(D1>@MPSyN5c{zG=3<m<24Fe~Mca(Pm
z@0ssaB3lI~K3m<cIvn95)1MKRJECbdg*SW1Q7<1_;&|;0J~5+qjq;KT(v)>+QZ~Vd
zOFv*Wfw?*#Fp#K;Az5Ue&L=$@8(O@co4%_{I1A$T^=P!tN0q%>NI2kjzJwI6;+-9X
znN3>wHy$Ib#}h_KH&eH%Ag9)<Je!^cH@ok*hsh#Hc&D*_dkCz3#O7tv$|rBbj*?IQ
zq&%eB?~Ozibto2kcbVvDPWVD!VQagZiCMeCoRMT=Qg$0&v58COO9M>4A_uOcB8Ic<
znHrI<IN6a6n6$KKV)Y0Wm7Zho9zOmk()XXlMqcXW89s07s>OUGXE3=QRL?n6%I?7d
zVo6}4rC?ybi&@xmt==9{L{z_?xwST&`JU2qoTv%4WQjLx&6hQ}fr+`kDMIj66pztv
zPi3Y7T`K;z*bxtm52Bx2Np|eeIl83UjT9{XHNHGF5y<dlH`KZW<ivcwHOBvgdSmBF
z7GmwGL!{c41<ZuXwUBEhxkTsoh0#vujspM+?$^B_xAqSb3v0hcyqi9A1j&qoncQ3E
zoScYx$e?6JeuY*>(zMoSMH$14?dZ4S!J0!+=)yENaE`praq+X~_#1M}6QO2xcioGQ
zi@Jo9Z)gsfL*C}+9^|VaPeQ7!+q%6dkAX5#)|IIhK~>{+K5q64Jvwx7Uk9QlOo%=A
zQm`?@3o1~tYlPP>N6=0%PVgD(%az3&BCz?F(XI=v*9kW}{cqbYe>5FHf8s#Oh1LwV
zs3sZJ1oSC-QoI!>RQTCyfHCoj+(WMmmd2@iGWcg3_l;9OwkD%wB4>=ZzC~)I1VfOL
zH0d9;KaJCp{h`(Ua^l^5M)Z({nrJh0{KZ2O{_r;`g-4kH`Vv=hLYu(nlhM5)ZbR$|
zOWa9HY%4$Z(lBb5kWGWNWPB4)j!Mqj)H?X5(GlE7K;&zWo>3A<I{qElpt~G(mzZ=$
zAq}u_>klHny|P;~t_n{Q+T`L6nS}eR?m5MrusxpPh{?2!=r?>xC)_?4pf3Cjoeb=J
z^=u;fzpID<aubHQeF=02?0Fd$V>_w>MHvu{aiXsO<$cC{Paq-Q$<m8P8n2ho)UYe4
zX1fDi!cWSI=YEjadv_YQDWpp3nNvwv+)9W3_|@S~W0r+$z28^r2o`}DR)b|OZOxOk
zoQMHCl`{K62yXpzh<-+0C|jV^VoLhY8JgjqVTo2JvD!BtU=Ko0;7h8f61psizvF>_
zhD`p06nf?8iVJZ|0p$O?QU50{>c2B~{4SpQ<O+3&TS|P}-vI2?7cK?H!wilvLv~p3
zO+?UJDk3wXe~ceH%_K>NhUFRN=DxPINO4o{wIR5rlg)QAqLVMtY~};qXYG~o-7lzq
zr^#Z|Af39Og=6}WGuvG5U@_=rzY^cEVUqs-f!46Il;&3ftN7UM(Zk#F_Y=(DoSB!f
z@Y((nK+FO|dBVA7eJ{l6(TzI(D)Oz=h?w8|?>IE3WommRaXrD+59F{W6!c;RYz-{O
z81@tW!QRy&&*X*J_5D7<XFuoLGlO*;Sat3>HgnS$ZFQDBZg2)F6H8obxdS@cS1ydm
zfZ_3`miv=L)6)mUGsp>F<jH)cZ+ftuy==8!Za&sfA(CMM7oL2>WL(o|;?k!VxP2NW
zcsz46t~T$<1j$J~&<0Z6PgwnBAlsLt{MH!M*Wr2m1(TcKU+a}N4yTB7w5)bs-dWOg
zZr^(qeh*zlFr%Jgf0yixY|F3i&TH@9x<W3AZM?-B&FIlt&epmHl5}iWmxU3L&d24c
zpjLZ)ksbFd#n8%M!O#!WjncUJA}XvIr-Jbeyy;C$;Mi|-pD2@b?sc$zgsu(o^4X2i
zvdyPx*}aGXY@&wb&>n`1KAd?z>^H{fa`k7-)OB<|-$ue&p*KX-*QF1$6NyiqVqY<Z
zea1@mMdat=39B|)eJB)8#jSc63sJok&TV-tS}D7k*hz&x5Y})8&*z%f5Yr|EoC<ZM
z9Co~C1LB0t(Ar_%o|Hku<@mUeV$3FjkMr=GCZ%7DFx;@J51&Qh$_Z47po2q3-SmWD
zOg;_s$XB`T-WuUCk&X^$%Y|%t991!7VGp|o!p-|R!)rC|Evdoe@+=QWFRRYWY(2_G
z0~n!EK%ycXJ}L7C_n7C^v7-m4t#OvPcZn-bUTd}6F8aWgAI0roVXB)!mE^JqgLJB^
z>yLVg4i^xzNqOD3jQ(NmPesZH0cT8HpwQxqgS0maYiC|<Bih=>6&3ewX6xnLT{0Y;
zbs-`Ltj!&d<D>Z=TgK%0{=!ssRIEDWP-#6`x`s{235*_&ot;{`eb*-B!x_^UAFT^2
zJr7bD$u%w_NP!R()+!wIP!F`6!5oYeURk;buv`7q>}#=6b--)lW_A}xe-xRtRvg}h
z>?-jSU^574^ka+BO`Q$5c-V5o4@XWhq;qthe0fpIzO4RjyJ|n`a7DZp6()&-1eM<O
z70Du^jPvx8UGF4nGa1@?zGJL-y}|u5B4QDgEv7`r8kXFq8cWJCsZGX>k-56xqUsH*
zYDynV=W$Ie-eM<=ov)z6eWa(Boe0$mUU@Z73E_w)$y0W0%VFjCE-km%Dre!}3KE$<
zMt^Av33Qsh7$7`}$nojO)Lk|73otQfFd_5pD6y(?ut&wZ)Rfe>@=~o>XBTi4E2zqb
zlZBkVk3@I6Dsptbg*5Pv)~Aj=stGM~yOg41_vX75uHt03as8eLR0}x_kiH=9jwpM8
z@ffZ_Q%cjiYD6`zVbYLZ%$!SV6jXRNh5HtR$hLR04X#CrA+EjYt!uB<zv+0b&Z9Ff
z_Qt}h(9n5+W6yw(9pdV+z+3f8+eU5+-9<VKHrydDOfcnmr-n0Y=0j><Lt)g>3C-L-
zX<^HBjgby6dOxj!VJz5gz0hF%BonvtoUYw8jhdtZ5bxS=ndC%7H>euQsTRKLlwB&4
zblF2t#mbF$^bvydX#_50NZII&v*S<)^Jw{?Pb)a2t!4DXiMgDXMHBj2;|uPoS(Dun
zDp!n+Yhd2-wmBvuA*Sb7Xy!L$Q{ADCBtDUAt;%H`3$2bqtqDVb@^1GEnDhUG9{&gF
zGQ%qNArZI4c<R3)H5c?DA(uafo||oyuVipgsv6DOPPZizH9zEuiKcYvGB1WRGmw1b
zXd=*ULGe{}%xZ)bA(nQgw$)k{`)S47X1=Hzlfrul#|?BBy@oo-kAP%k7_BD}0yW7}
zHg4a<Uq==fH%Nf&yFLs^V88nhnl0weF_{GFVi%u%lk|i3xQ86Zb=0tC>B%dUoTy#e
zZ)+$$AXL3VhCOBEp_}7g-i1xysGU-plDn;pCDJi9-M|RRVay*))XdTywC-C$D?)!F
zgwn8mocFU!qRMaLncreDZK0>^YPTd}S|Ee2AlqyN_&gLzVU)jJA{kbWytrP@@^3Uf
ztbiW792Pvz-${w&L|yOpE09P>2H@GLiBNWRDe{)F{-msXR9YG92($m17dD>FLQhvC
z+zXGJFB+Tjy{g>u=euIynw~9Gm*-taa2A9WBv|q3q*(CIH0JVizMP3}TOD+AX_<A_
zO3^KhC*YZQ(F=8PLsXob+6XY=mF#@c1KbHEEPwaV5W+8<YFx44)*Dt|F+=vpoN>1~
zLWxcjHFI;Q?GiZ*dayo#?vocSN}AE3Ji*ayu6NCGG<9#P%9ktL<42F8BujipOgl<D
zo*R2%j7~BNwAzi}L+3{c%S%g|^bxZyRm=f?oVzQ$ooD3jfxFs|dIw;z3EJfL)$vzE
z@4bx59f%cP6CW#IQxtUF`_OfVwX%@zZ8XM2*9e7G7Tm8w+2S#iafq+4tT3kg^tjJd
z)u3l<q?1Xco6WYZSrbmo{dUe9`N0<wIzQqHo;c1tr+Bw5tz+dNf)9{#hsNLk$lso&
zVFN*erMDO+l86O@Mj~`|!}N#T1FP=uf2R`;3J}p=PP(8hs)xbEZ7BS5avmV%ukL5D
zJT*3@pVJV$T&W3;%6iEXrAi`qSfSB8jp02aV!r4aSz)B+x>^2MF>fns-SkpMtol{6
z@7zK09fX|L;Y+bRwWIFoql2<8au^S$_pF-yq+_o_m9xJ*44f~E;AnOG1xl=5lBVrq
zPu2P3wUiufo*2FsacSRDUHunn_xM<rQ^kXaQ(H<C2K}{}ML^_AQikLoXo@=-17g*U
z+uge5_xXW$A5K+_P<IFh5kB8lBih?;A{}fl!o3%}A37$uI3{CjwrN^^7}uGnpKTeJ
z&dy7ZGU>mUb3A;Dq~CSw)HHACW-VA+S5~?7iXdME(dwl2)}jfsS!i-<lx=3hH@oMo
z$Vc-8O%+&dD(K~1aWFB`ks$x+k)3%B%hSv}VM6WxA<bajw-O##XHyY1Rv8~cVTI==
zN5UR5n+jvApA|zL(<AN>d*UT^eRkO-rt~;3VJr77NYNm`2^L3bhb-_&PVHH%wx(|R
zO{|7#bK=8S_xf4WHF>*tf+N7Z><8wyh~8AGF3*F@H2^aaFJuiMPm5SCR2qgWSO5|x
zAk|zvsUED4f8VG5W7EFh9lQVMK_d{iUI1}R3l#sY-}IAZi{LUe!hV)c)?p8Pt@md*
z-v1*=Vn#Q{|7H;deg`<vBtSW+4!U3#`M;4F{F5RG8J-#Vm#O}1mjAom{col^8z}Kx
z6#LKF(VxqM+@Np;I%tQ0{c>=rb(`3Fy276vh5ddx3j@Crga1XfzmsYqghl{7Kzq!n
pcF<o}Z2;3}KI-mavEBe6Mk*vEgcwDdLRgEkzry+3A+D*}UjQf@%fJ8t

literal 0
HcmV?d00001

diff --git a/test/py/tests/test_ut.py b/test/py/tests/test_ut.py
index bab8b97672b..6958fabfa34 100644
--- a/test/py/tests/test_ut.py
+++ b/test/py/tests/test_ut.py
@@ -1,6 +1,7 @@
 # SPDX-License-Identifier: GPL-2.0
 # Copyright (c) 2016, NVIDIA CORPORATION. All rights reserved.
 
+import getpass
 import gzip
 import os
 import os.path
@@ -13,34 +14,217 @@ def mkdir_cond(dirname):
     """Create a directory if it doesn't already exist
 
     Args:
-        dirname: Name of directory to create
+        dirname (str): Name of directory to create
     """
     if not os.path.exists(dirname):
         os.mkdir(dirname)
 
-def setup_bootflow_image(u_boot_console):
-    """Create a 20MB disk image with a single FAT partition"""
-    cons = u_boot_console
-    fname = os.path.join(cons.config.source_dir, 'mmc1.img')
+def setup_image(cons, mmc_dev, part_type):
+    """Create a 20MB disk image with a single partition
+
+    Args:
+        cons (ConsoleBase): Console to use
+        mmc_dev (int): MMC device number to use, e.g. 1
+        part_type (int): Partition type, e.g. 0xc for FAT32
+
+    Returns:
+        tuple:
+            str: Filename of MMC image
+            str: Directory name of 'mnt' directory
+    """
+    fname = os.path.join(cons.config.source_dir, f'mmc{mmc_dev}.img')
     mnt = os.path.join(cons.config.persistent_data_dir, 'mnt')
     mkdir_cond(mnt)
 
     u_boot_utils.run_and_log(cons, 'qemu-img create %s 20M' % fname)
     u_boot_utils.run_and_log(cons, 'sudo sfdisk %s' % fname,
-                             stdin=b'type=c')
+                             stdin=f'type={part_type:x}'.encode('utf-8'))
+    return fname, mnt
+
+def mount_image(cons, fname, mnt, fstype):
+    """Create a filesystem and mount it on partition 1
+
+    Args:
+        cons (ConsoleBase): Console to use
+        fname (str): Filename of MMC image
+        mnt (str): Directory name of 'mnt' directory
+        fstype (str): Filesystem type ('vfat' or 'ext4')
+
+    Returns:
+        str: Name of loop device used
+    """
+    out = u_boot_utils.run_and_log(cons, 'sudo losetup --show -f -P %s' % fname)
+    loop = out.strip()
+    part = f'{loop}p1'
+    u_boot_utils.run_and_log(cons, f'sudo mkfs.{fstype} {part}')
+    opts = ''
+    if fstype == 'vfat':
+         opts += ' -o uid={os.getuid()},gid={os.getgid()}'
+    u_boot_utils.run_and_log(cons, f'sudo mount -o loop {part} {mnt}{opts}')
+    u_boot_utils.run_and_log(cons, f'sudo chown {getpass.getuser()} {mnt}')
+    return loop
+
+def copy_prepared_image(cons, mmc_dev, fname):
+    """Use a prepared image since we cannot create one
+
+    Args:
+        cons (ConsoleBase): Console touse
+        mmc_dev (int): MMC device number
+        fname (str): Filename of MMC image
+    """
+    infname = os.path.join(cons.config.source_dir,
+                           f'test/py/tests/bootstd/mmc{mmc_dev}.img.xz')
+    u_boot_utils.run_and_log(
+        cons,
+        ['sh', '-c', 'xz -dc %s >%s' % (infname, fname)])
+
+def setup_bootmenu_image(cons):
+    """Create a 20MB disk image with a single ext4 partition
+
+    This is modelled on Armbian 22.08 Jammy
+    """
+    mmc_dev = 4
+    fname, mnt = setup_image(cons, mmc_dev, 0x83)
 
     loop = None
     mounted = False
     complete = False
     try:
-        out = u_boot_utils.run_and_log(cons,
-                                       'sudo losetup --show -f -P %s' % fname)
-        loop = out.strip()
-        fatpart = '%sp1' % loop
-        u_boot_utils.run_and_log(cons, 'sudo mkfs.vfat %s' % fatpart)
+        loop = mount_image(cons, fname, mnt, 'ext4')
+        mounted = True
+
+        vmlinux = 'Image'
+        initrd = 'uInitrd'
+        dtbdir = 'dtb'
+        script = '''# DO NOT EDIT THIS FILE
+#
+# Please edit /boot/armbianEnv.txt to set supported parameters
+#
+
+setenv load_addr "0x9000000"
+setenv overlay_error "false"
+# default values
+setenv rootdev "/dev/mmcblk%dp1"
+setenv verbosity "1"
+setenv console "both"
+setenv bootlogo "false"
+setenv rootfstype "ext4"
+setenv docker_optimizations "on"
+setenv earlycon "off"
+
+echo "Boot script loaded from ${devtype} ${devnum}"
+
+if test -e ${devtype} ${devnum} ${prefix}armbianEnv.txt; then
+	load ${devtype} ${devnum} ${load_addr} ${prefix}armbianEnv.txt
+	env import -t ${load_addr} ${filesize}
+fi
+
+if test "${logo}" = "disabled"; then setenv logo "logo.nologo"; fi
+
+if test "${console}" = "display" || test "${console}" = "both"; then setenv consoleargs "console=tty1"; fi
+if test "${console}" = "serial" || test "${console}" = "both"; then setenv consoleargs "console=ttyS2,1500000 ${consoleargs}"; fi
+if test "${earlycon}" = "on"; then setenv consoleargs "earlycon ${consoleargs}"; fi
+if test "${bootlogo}" = "true"; then setenv consoleargs "bootsplash.bootfile=bootsplash.armbian ${consoleargs}"; fi
+
+# get PARTUUID of first partition on SD/eMMC the boot script was loaded from
+if test "${devtype}" = "mmc"; then part uuid mmc ${devnum}:1 partuuid; fi
+
+setenv bootargs "root=${rootdev} rootwait rootfstype=${rootfstype} ${consoleargs} consoleblank=0 loglevel=${verbosity} ubootpart=${partuuid} usb-storage.quirks=${usbstoragequirks} ${extraargs} ${extraboardargs}"
+
+if test "${docker_optimizations}" = "on"; then setenv bootargs "${bootargs} cgroup_enable=cpuset cgroup_memory=1 cgroup_enable=memory swapaccount=1"; fi
+
+load ${devtype} ${devnum} ${ramdisk_addr_r} ${prefix}uInitrd
+load ${devtype} ${devnum} ${kernel_addr_r} ${prefix}Image
+
+load ${devtype} ${devnum} ${fdt_addr_r} ${prefix}dtb/${fdtfile}
+fdt addr ${fdt_addr_r}
+fdt resize 65536
+for overlay_file in ${overlays}; do
+	if load ${devtype} ${devnum} ${load_addr} ${prefix}dtb/rockchip/overlay/${overlay_prefix}-${overlay_file}.dtbo; then
+		echo "Applying kernel provided DT overlay ${overlay_prefix}-${overlay_file}.dtbo"
+		fdt apply ${load_addr} || setenv overlay_error "true"
+	fi
+done
+for overlay_file in ${user_overlays}; do
+	if load ${devtype} ${devnum} ${load_addr} ${prefix}overlay-user/${overlay_file}.dtbo; then
+		echo "Applying user provided DT overlay ${overlay_file}.dtbo"
+		fdt apply ${load_addr} || setenv overlay_error "true"
+	fi
+done
+if test "${overlay_error}" = "true"; then
+	echo "Error applying DT overlays, restoring original DT"
+	load ${devtype} ${devnum} ${fdt_addr_r} ${prefix}dtb/${fdtfile}
+else
+	if load ${devtype} ${devnum} ${load_addr} ${prefix}dtb/rockchip/overlay/${overlay_prefix}-fixup.scr; then
+		echo "Applying kernel provided DT fixup script (${overlay_prefix}-fixup.scr)"
+		source ${load_addr}
+	fi
+	if test -e ${devtype} ${devnum} ${prefix}fixup.scr; then
+		load ${devtype} ${devnum} ${load_addr} ${prefix}fixup.scr
+		echo "Applying user provided fixup script (fixup.scr)"
+		source ${load_addr}
+	fi
+fi
+booti ${kernel_addr_r} ${ramdisk_addr_r} ${fdt_addr_r}
+
+# Recompile with:
+# mkimage -C none -A arm -T script -d /boot/boot.cmd /boot/boot.scr
+''' % (mmc_dev)
+        bootdir = os.path.join(mnt, 'boot')
+        mkdir_cond(bootdir)
+        cmd_fname = os.path.join(bootdir, 'boot.cmd')
+        scr_fname = os.path.join(bootdir, 'boot.scr')
+        with open(cmd_fname, 'w') as outf:
+            print(script, file=outf)
+
+        infname = os.path.join(cons.config.source_dir,
+                               'test/py/tests/bootstd/armbian.bmp.xz')
+        bmp_file = os.path.join(bootdir, 'boot.bmp')
+        u_boot_utils.run_and_log(
+            cons,
+            ['sh', '-c', f'xz -dc {infname} >{bmp_file}'])
+
+        u_boot_utils.run_and_log(
+            cons, f'mkimage -C none -A arm -T script -d {cmd_fname} {scr_fname}')
+
+        kernel = 'vmlinuz-5.15.63-rockchip64'
+        target = os.path.join(bootdir, kernel)
+        with open(target, 'wb') as outf:
+            print('kernel', outf)
+
+        symlink = os.path.join(bootdir, 'Image')
+        if os.path.exists(symlink):
+            os.remove(symlink)
+        u_boot_utils.run_and_log(
+            cons, f'echo here {kernel} {symlink}')
+        os.symlink(kernel, symlink)
+
         u_boot_utils.run_and_log(
-            cons, 'sudo mount -o loop %s %s -o uid=%d,gid=%d' %
-            (fatpart, mnt, os.getuid(), os.getgid()))
+            cons, f'mkimage -C none -A arm -T script -d {cmd_fname} {scr_fname}')
+        complete = True
+
+    except ValueError as exc:
+        print('Falled to create image, failing back to prepared copy: %s',
+              str(exc))
+    finally:
+        if mounted:
+            u_boot_utils.run_and_log(cons, 'sudo umount %s' % mnt)
+        if loop:
+            u_boot_utils.run_and_log(cons, 'sudo losetup -d %s' % loop)
+
+    if not complete:
+        copy_prepared_image(cons, mmc_dev, fname)
+
+def setup_bootflow_image(cons):
+    """Create a 20MB disk image with a single FAT partition"""
+    mmc_dev = 1
+    fname, mnt = setup_image(cons, mmc_dev, 0xc)
+
+    loop = None
+    mounted = False
+    complete = False
+    try:
+        loop = mount_image(cons, fname, mnt, 'vfat')
         mounted = True
 
         vmlinux = 'vmlinuz-5.3.7-301.fc31.armv7hl'
@@ -90,12 +274,7 @@ label Fedora-Workstation-armhfp-31-1.9 (5.3.7-301.fc31.armv7hl)
             u_boot_utils.run_and_log(cons, 'sudo losetup -d %s' % loop)
 
     if not complete:
-        # Use a prepared image since we cannot create one
-        infname = os.path.join(cons.config.source_dir,
-                               'test/py/tests/bootstd/mmc1.img.xz')
-        u_boot_utils.run_and_log(
-            cons,
-            ['sh', '-c', 'xz -dc %s >%s' % (infname, fname)])
+        copy_prepared_image(cons, mmc_dev, fname)
 
 
 @pytest.mark.buildconfigspec('ut_dm')
@@ -134,6 +313,7 @@ def test_ut_dm_init_bootstd(u_boot_console):
     """Initialise data for bootflow tests"""
 
     setup_bootflow_image(u_boot_console)
+    setup_bootmenu_image(u_boot_console)
 
     # Restart so that the new mmc1.img is picked up
     u_boot_console.restart_uboot()
-- 
2.39.0.314.g84b9a713c41-goog


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

* [PATCH v3 24/25] bootstd: Support setting a theme for the menu
  2023-01-06 14:52 [PATCH v3 00/25] bootstd: Add a boot menu Simon Glass
                   ` (22 preceding siblings ...)
  2023-01-06 14:52 ` [PATCH v3 23/25] bootstd: Add a test for the bootstd menu Simon Glass
@ 2023-01-06 14:52 ` Simon Glass
  2023-01-06 14:52 ` [PATCH v3 25/25] expo: Add documentation Simon Glass
  2023-01-17 13:57 ` [PATCH v3 00/25] bootstd: Add a boot menu Tom Rini
  25 siblings, 0 replies; 40+ messages in thread
From: Simon Glass @ 2023-01-06 14:52 UTC (permalink / raw)
  To: U-Boot Mailing List
  Cc: Anatolij Gustschin, Tom Rini, Heinrich Schuchardt, Simon Glass

Allow a theme to be set. For now this is very simple, just a default font
size to use for all elements.

Signed-off-by: Simon Glass <sjg@chromium.org>
---

(no changes since v1)

 boot/bootflow_menu.c  | 43 ++++++++++++++++++++++
 boot/bootstd-uclass.c |  2 ++
 include/bootflow.h    | 10 ++++++
 include/bootstd.h     |  4 +++
 test/boot/bootflow.c  | 83 +++++++++++++++++++++++++++++++++++++++++--
 5 files changed, 139 insertions(+), 3 deletions(-)

diff --git a/boot/bootflow_menu.c b/boot/bootflow_menu.c
index 1105783afeb..7f06dac0af7 100644
--- a/boot/bootflow_menu.c
+++ b/boot/bootflow_menu.c
@@ -129,6 +129,43 @@ int bootflow_menu_new(struct expo **expp)
 	return 0;
 }
 
+int bootflow_menu_apply_theme(struct expo *exp, ofnode node)
+{
+	struct menu_priv *priv = exp->priv;
+	struct scene *scn;
+	u32 font_size;
+	int ret;
+
+	log_debug("Applying theme %s\n", ofnode_get_name(node));
+	scn = expo_lookup_scene_id(exp, MAIN);
+	if (!scn)
+		return log_msg_ret("scn", -ENOENT);
+
+	/* Avoid error-checking optional items */
+	if (!ofnode_read_u32(node, "font-size", &font_size)) {
+		int i;
+
+		log_debug("font size %d\n", font_size);
+		scene_txt_set_font(scn, OBJ_PROMPT, NULL, font_size);
+		scene_txt_set_font(scn, OBJ_POINTER, NULL, font_size);
+		for (i = 0; i < priv->num_bootflows; i++) {
+			ret = scene_txt_set_font(scn, ITEM_DESC + i, NULL,
+						 font_size);
+			if (ret)
+				return log_msg_ret("des", ret);
+			scene_txt_set_font(scn, ITEM_KEY + i, NULL, font_size);
+			scene_txt_set_font(scn, ITEM_LABEL + i, NULL,
+					   font_size);
+		}
+	}
+
+	ret = scene_arrange(scn);
+	if (ret)
+		return log_msg_ret("arr", ret);
+
+	return 0;
+}
+
 int bootflow_menu_run(struct bootstd_priv *std, bool text_mode,
 		      struct bootflow **bflowp)
 {
@@ -149,6 +186,12 @@ int bootflow_menu_run(struct bootstd_priv *std, bool text_mode,
 	if (ret)
 		return log_msg_ret("exp", ret);
 
+	if (ofnode_valid(std->theme)) {
+		ret = bootflow_menu_apply_theme(exp, std->theme);
+		if (ret)
+			return log_msg_ret("thm", ret);
+	}
+
 	/* For now we only support a video console */
 	ret = uclass_first_device_err(UCLASS_VIDEO, &dev);
 	if (ret)
diff --git a/boot/bootstd-uclass.c b/boot/bootstd-uclass.c
index 565c22a36e7..7887acdc11b 100644
--- a/boot/bootstd-uclass.c
+++ b/boot/bootstd-uclass.c
@@ -33,6 +33,8 @@ static int bootstd_of_to_plat(struct udevice *dev)
 					   &priv->prefixes);
 		dev_read_string_list(dev, "bootdev-order",
 				     &priv->bootdev_order);
+
+		priv->theme = ofnode_find_subnode(dev_ofnode(dev), "theme");
 	}
 
 	return 0;
diff --git a/include/bootflow.h b/include/bootflow.h
index e7a09568f1b..c201246c6de 100644
--- a/include/bootflow.h
+++ b/include/bootflow.h
@@ -7,6 +7,7 @@
 #ifndef __bootflow_h
 #define __bootflow_h
 
+#include <dm/ofnode_decl.h>
 #include <linux/list.h>
 
 struct bootstd_priv;
@@ -347,6 +348,15 @@ int bootflow_iter_uses_system(const struct bootflow_iter *iter);
  */
 int bootflow_menu_new(struct expo **expp);
 
+/**
+ * bootflow_menu_apply_theme() - Apply a theme to a bootmenu
+ *
+ * @exp: Expo to update
+ * @node: Node containing the theme information
+ * Returns 0 on success, -ve on error
+ */
+int bootflow_menu_apply_theme(struct expo *exp, ofnode node);
+
 /**
  * bootflow_menu_run() - Create and run a menu of available bootflows
  *
diff --git a/include/bootstd.h b/include/bootstd.h
index 01be249d16e..4fa0d531001 100644
--- a/include/bootstd.h
+++ b/include/bootstd.h
@@ -9,6 +9,8 @@
 #ifndef __bootstd_h
 #define __bootstd_h
 
+#include <dm/ofnode_decl.h>
+
 struct udevice;
 
 /**
@@ -27,6 +29,7 @@ struct udevice;
  * @bootmeth_count: Number of bootmeth devices in @bootmeth_order
  * @bootmeth_order: List of bootmeth devices to use, in order, NULL-terminated
  * @vbe_bootmeth: Currently selected VBE bootmeth, NULL if none
+ * @theme: Node containing the theme information
  */
 struct bootstd_priv {
 	const char **prefixes;
@@ -37,6 +40,7 @@ struct bootstd_priv {
 	int bootmeth_count;
 	struct udevice **bootmeth_order;
 	struct udevice *vbe_bootmeth;
+	ofnode theme;
 };
 
 /**
diff --git a/test/boot/bootflow.c b/test/boot/bootflow.c
index abafa44b2ed..5b76cd3ab14 100644
--- a/test/boot/bootflow.c
+++ b/test/boot/bootflow.c
@@ -13,6 +13,7 @@
 #include <bootstd.h>
 #include <cli.h>
 #include <dm.h>
+#include <expo.h>
 #ifdef CONFIG_SANDBOX
 #include <asm/test.h>
 #endif
@@ -21,6 +22,8 @@
 #include <test/suites.h>
 #include <test/ut.h>
 #include "bootstd_common.h"
+#include "../../boot/bootflow_internal.h"
+#include "../../boot/scene_internal.h"
 
 DECLARE_GLOBAL_DATA_PTR;
 
@@ -469,14 +472,18 @@ static int bootflow_cmd_boot(struct unit_test_state *uts)
 }
 BOOTSTD_TEST(bootflow_cmd_boot, UT_TESTF_DM | UT_TESTF_SCAN_FDT);
 
-/* Check 'bootflow menu' to select a bootflow */
-static int bootflow_cmd_menu(struct unit_test_state *uts)
+/**
+ * prep_mmc4_bootdev() - Set up the mmc4 bootdev so we can access a fake Armbian
+ *
+ * @uts: Unit test state
+ * Returns 0 on success, -ve on failure
+ */
+static int prep_mmc4_bootdev(struct unit_test_state *uts)
 {
 	static const char *order[] = {"mmc2", "mmc1", "mmc4", NULL};
 	struct udevice *dev, *bootstd;
 	struct bootstd_priv *std;
 	const char **old_order;
-	char prev[3];
 	ofnode node;
 
 	/* Enable the mmc4 node since we need a second bootflow */
@@ -500,6 +507,16 @@ static int bootflow_cmd_menu(struct unit_test_state *uts)
 	/* Restore the order used by the device tree */
 	std->bootdev_order = old_order;
 
+	return 0;
+}
+
+/* Check 'bootflow menu' to select a bootflow */
+static int bootflow_cmd_menu(struct unit_test_state *uts)
+{
+	char prev[3];
+
+	ut_assertok(prep_mmc4_bootdev(uts));
+
 	/* Add keypresses to move to and select the second one in the list */
 	prev[0] = CTL_CH('n');
 	prev[1] = '\r';
@@ -513,3 +530,63 @@ static int bootflow_cmd_menu(struct unit_test_state *uts)
 	return 0;
 }
 BOOTSTD_TEST(bootflow_cmd_menu, UT_TESTF_DM | UT_TESTF_SCAN_FDT);
+
+/**
+ * check_font() - Check that the font size for an item matches expectations
+ *
+ * @uts: Unit test state
+ * @scn: Scene containing the text object
+ * @id: ID of the text object
+ * Returns 0 on success, -ve on failure
+ */
+static int check_font(struct unit_test_state *uts, struct scene *scn, uint id,
+		      int font_size)
+{
+	struct scene_obj_txt *txt;
+
+	txt = scene_obj_find(scn, id, SCENEOBJT_TEXT);
+	ut_assertnonnull(txt);
+
+	ut_asserteq(font_size, txt->font_size);
+
+	return 0;
+}
+
+/* Check themes work with a bootflow menu */
+static int bootflow_menu_theme(struct unit_test_state *uts)
+{
+	const int font_size = 30;
+	struct scene *scn;
+	struct expo *exp;
+	ofnode node;
+	int i;
+
+	ut_assertok(prep_mmc4_bootdev(uts));
+
+	ut_assertok(bootflow_menu_new(&exp));
+	node = ofnode_path("/bootstd/theme");
+	ut_assert(ofnode_valid(node));
+	ut_assertok(bootflow_menu_apply_theme(exp, node));
+
+	scn = expo_lookup_scene_id(exp, MAIN);
+	ut_assertnonnull(scn);
+
+	/*
+	 * Check that the txt objects have the correct font size from the
+	 * device tree node: bootstd/theme
+	 *
+	 * Check both menu items, since there are two bootflows
+	 */
+	ut_assertok(check_font(uts, scn, OBJ_PROMPT, font_size));
+	ut_assertok(check_font(uts, scn, OBJ_POINTER, font_size));
+	for (i = 0; i < 2; i++) {
+		ut_assertok(check_font(uts, scn, ITEM_DESC + i, font_size));
+		ut_assertok(check_font(uts, scn, ITEM_KEY + i, font_size));
+		ut_assertok(check_font(uts, scn, ITEM_LABEL + i, font_size));
+	}
+
+	expo_destroy(exp);
+
+	return 0;
+}
+BOOTSTD_TEST(bootflow_menu_theme, UT_TESTF_DM | UT_TESTF_SCAN_FDT);
-- 
2.39.0.314.g84b9a713c41-goog


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

* [PATCH v3 25/25] expo: Add documentation
  2023-01-06 14:52 [PATCH v3 00/25] bootstd: Add a boot menu Simon Glass
                   ` (23 preceding siblings ...)
  2023-01-06 14:52 ` [PATCH v3 24/25] bootstd: Support setting a theme for the menu Simon Glass
@ 2023-01-06 14:52 ` Simon Glass
  2023-01-17 13:57 ` [PATCH v3 00/25] bootstd: Add a boot menu Tom Rini
  25 siblings, 0 replies; 40+ messages in thread
From: Simon Glass @ 2023-01-06 14:52 UTC (permalink / raw)
  To: U-Boot Mailing List
  Cc: Anatolij Gustschin, Tom Rini, Heinrich Schuchardt, Simon Glass

Add some documentation for the expo feature.

Signed-off-by: Simon Glass <sjg@chromium.org>
---

Changes in v3:
- Rebase to master

Changes in v2:
- Update for new API

 doc/develop/expo.rst  | 188 ++++++++++++++++++++++++++++++++++++++++++
 doc/develop/index.rst |   1 +
 2 files changed, 189 insertions(+)
 create mode 100644 doc/develop/expo.rst

diff --git a/doc/develop/expo.rst b/doc/develop/expo.rst
new file mode 100644
index 00000000000..32dd7f09030
--- /dev/null
+++ b/doc/develop/expo.rst
@@ -0,0 +1,188 @@
+.. SPDX-License-Identifier: GPL-2.0+
+
+Expo menu
+=========
+
+U-Boot provides a menu implementation for use with selecting bootflows and
+changing U-Boot settings. This is in early stages of development.
+
+Motivation
+----------
+
+U-Boot already has a text-based menu system accessed via the
+:doc:`../usage/cmd/bootmenu`. This works using environment variables, or via
+some EFI-specific hacks.
+
+The command makes use of a lower-level `menu` implementation, which is quite
+flexible and can be used to make menu hierarchies.
+
+However this system is not flexible enough for use with standard boot. It does
+not support a graphical user interface and cannot currently support anything
+more than a very simple list of items. While it does support multiple menus in
+hierarchies, these are implemented by the caller. See for example `eficonfig.c`.
+
+Another challenge with the current menu implementation is that it controls
+the event loop, such that bootmenu_loop() does not return until a key is
+pressed. This makes it difficult to implement dynamic displays or to do other
+things while the menu is running, such as searching for more bootflows.
+
+For these reasons an attempt has been made to develop a more flexible system
+which can handle menus as well as other elements. This is called 'expo', short
+for exposition, in an attempt to avoid common words like display, screen, menu
+and the like. The primary goal is to support Verified Boot for Embedded (VBE),
+although it is available to any boot method, using the 'bootflow menu' command.
+
+Efforts have been made to use common code with the existing menu, including
+key processing in particular.
+
+Previous work looked at integrating Nuklear into U-Boot. This works fine and
+could provide a way to provide a more flexible UI, perhaps with expo dealing
+with the interface to Nuklear. But this is quite a big step and it may be years
+before this becomes desirable, if at all. For now, U-Boot only needs a fairly
+simple set of menus and options, so rendering them directly is fairly
+straightforward.
+
+Concepts
+--------
+
+The creator of the expo is here called a `controller` and it controls most
+aspects of the expo. This is the code that you must write to use expo.
+
+An `expo` is a set of scenes which can be presented to the user one at a time,
+to show information and obtain input from the user.
+
+A `scene` is a collection of objects which are displayed together on the screen.
+Only one scene is visible at a time and scenes do not share objects.
+
+A `scene object` is something that appears in the scene, such as some text, an
+image or a menu. Objects can be positioned and hidden.
+
+A `menu object` contains a title, a set of `menu items` and a pointer to the
+current item. Menu items consist of a keypress (indicating what to press to
+select the item), label and description. All three are shown in a single line
+within the menu. Items can also have a preview image, which is shown when the
+item is highlighted.
+
+All components have a name. This is purely for debugging, so it is easy to see
+what object is referred to. Of course the ID numbers can help as well, but they
+are less easy to distinguish.
+
+While the expo implementation provides support for handling keypresses and
+rendering on the display or serial port, it does not actually deal with reading
+input from the user, nor what should be done when a particular menu item is
+selected. This is deliberate since having the event loop outside the expo is
+more flexible, particularly in a single-threaded environment like U-Boot.
+
+Everything within an expo has a unique ID number. This is done so that it is
+easy to refer to things after the expo has been created. The expectation is that
+the controller declares an enum containing all of the elements in the expo,
+passing the ID of each object as it is created. When a menu item is selected,
+its ID is returned. When a object's font or position needs to change, the ID is
+passed to expo functions to indicate which object it is. It is possible for expo
+to auto-allocate IDs, but this is not recommended. The use of IDs is a
+convenience, removing the need for the controller to store pointers to objects,
+or even the IDs of objects. Programmatic creation of many items in a loop can be
+handled by allocating space in the enum for a maximum number of items, then
+adding the loop count to the enum values to obtain unique IDs.
+
+All text strings are stored in a structure attached to the expo, referenced by
+a text ID. This makes it easier at some point to implement multiple languages or
+to support Unicode strings.
+
+Menu objects do not have their own text and image objects. Instead they simply
+refer to objects which have been created. So a menu item is just a collection
+of IDs of text and image objects. When adding a menu item you must create these
+objects first, then create the menu item, passing in the relevant IDs.
+
+Creating an expo
+----------------
+
+To create an expo, use `expo_new()` followed by `scene_new()` to create a scene.
+Then add objects to the scene, using functions like `scene_txt_str()` and
+`scene_menu()`. For every menu item, add text and image objects, then create
+the menu item with `scene_menuitem()`, referring to those objects.
+
+Layout
+------
+
+Individual objects can be positioned using `scene_obj_set_pos()`. Menu items
+cannot be positioned manually: this is done by `scene_arrange()` which is called
+automatically when something changes. The menu itself determines the position of
+its items.
+
+Rendering
+---------
+
+Rendering is performed by calling `expo_render()`. This uses either the
+vidconsole, if present, or the serial console in `text mode`. Expo handles
+presentation automatically in either case, without any change in how the expo is
+created.
+
+For the vidconsole, Truetype fonts can be used if enabled, to enhance the
+quality of the display. For text mode, each menu item is shown in a single line,
+allowing easy selection using arrow keys.
+
+Input
+-----
+
+The controller is responsible for collecting keyboard input. A good way to do
+this is to use `cli_ch_process()`, since it handles conversion of escape
+sequences into keys. However, expo has some special menu-key codes for
+navigating the interface. These are defined in `enum bootmenu_key` and include
+`BKEY_UP` for moving up and `BKEY_SELECT` for selecting an item. You can use
+`bootmenu_conv_key()` to convert an ASCII key into one of these.
+
+Once a keypress is decoded, call `expo_send_key()` to send it to the expo. This
+may cause an update to the expo state and may produce an action.
+
+Actions
+-------
+
+Call `expo_action_get()` in the event loop to check for any actions that the
+expo wants to report. These can include selecting a particular menu item, or
+quitting the menu. Processing of these is the responsibility of your controller.
+
+Event loop
+----------
+
+Expo is intended to be used in an event loop. For an example loop, see
+`bootflow_menu_run()`. It is possible to perform other work in your event loop,
+such as scanning devices for more bootflows.
+
+Themes
+------
+
+Expo does not itself support themes. The bootflow_menu implement supposed a
+basic theme, applying font sizes to the various text objects in the expo.
+
+API documentation
+-----------------
+
+.. kernel-doc:: include/expo.h
+
+Future ideas
+------------
+
+Some ideas for future work:
+
+- Default menu item and a timeout
+- Higher-level / automatic / more flexible layout of objects
+- Image formats other than BMP
+- Use of ANSI sequences to control a serial terminal
+- Colour selection
+- Better support for handling lots of settings, e.g. with multiple menus and
+  radio/option widgets
+- Mouse support
+- Integrate Nuklear, NxWidgets or some other library for a richer UI
+- Optimise rendering by only updating the display with changes since last render
+- Use expo to replace the existing menu implementation
+- Add a Kconfig option to drop the names to save code / data space
+- Add a Kconfig option to disable vidconsole support to save code / data space
+- Support both graphical and text menus at the same time on different devices
+- Implement proper measurement of object bounding boxes, to permit more exact
+  layout. This would tidy up the layout when Truetype is not used
+- Support unicode
+- Support curses for proper serial-terminal menus
+
+.. Simon Glass <sjg@chromium.org>
+.. 7-Oct-22
diff --git a/doc/develop/index.rst b/doc/develop/index.rst
index 97c526e997e..79d7736b134 100644
--- a/doc/develop/index.rst
+++ b/doc/develop/index.rst
@@ -34,6 +34,7 @@ Implementation
    distro
    driver-model/index
    environment
+   expo
    event
    global_data
    logging
-- 
2.39.0.314.g84b9a713c41-goog


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

* Re: [PATCH v3 02/25] cli: Move readline character-processing to a state machine
  2023-01-06 14:52 ` [PATCH v3 02/25] cli: Move readline character-processing to a state machine Simon Glass
@ 2023-01-06 15:50   ` Heinrich Schuchardt
  2023-01-07  0:13     ` Simon Glass
  2023-01-24 15:19   ` [BUG] " Heinrich Schuchardt
  1 sibling, 1 reply; 40+ messages in thread
From: Heinrich Schuchardt @ 2023-01-06 15:50 UTC (permalink / raw)
  To: Simon Glass; +Cc: Anatolij Gustschin, Tom Rini, U-Boot Mailing List

On 1/6/23 15:52, Simon Glass wrote:
> The current cread_line() function is very long. It handles the escape
> processing inline. The menu command does similar processing but at the
> character level, so there is some duplication.
>
> Split the character processing into a new function cli_ch_process() which
> processes individual characters and returns the resulting input character,
> taking account of escape sequences. It requires the caller to set up and
> maintain its state.
>
> Update cread_line() to use this new function.
>
> The only intended functional change is that an invalid escape sequence
> does not add invalid/control characters into the input buffer, but instead
> discards these.
>
> Signed-off-by: Simon Glass <sjg@chromium.org>
> ---
>
> (no changes since v1)
>
>   common/Makefile       |   6 +-
>   common/cli_getch.c    | 204 ++++++++++++++++++++++++++++++++++++++++++
>   common/cli_readline.c | 150 +++++--------------------------
>   include/cli.h         |  72 +++++++++++++++
>   4 files changed, 301 insertions(+), 131 deletions(-)
>   create mode 100644 common/cli_getch.c
>
> diff --git a/common/Makefile b/common/Makefile
> index 20addfb244c..67485e77a04 100644
> --- a/common/Makefile
> +++ b/common/Makefile
> @@ -38,7 +38,7 @@ obj-$(CONFIG_SPLASH_SOURCE) += splash_source.o
>   obj-$(CONFIG_MENU) += menu.o
>   obj-$(CONFIG_UPDATE_COMMON) += update.o
>   obj-$(CONFIG_USB_KEYBOARD) += usb_kbd.o
> -obj-$(CONFIG_CMDLINE) += cli_readline.o cli_simple.o
> +obj-$(CONFIG_CMDLINE) += cli_getch.o cli_readline.o cli_simple.o
>
>   endif # !CONFIG_SPL_BUILD
>
> @@ -93,8 +93,8 @@ obj-y += eeprom/eeprom_field.o eeprom/eeprom_layout.o
>   endif
>
>   obj-y += cli.o
> -obj-$(CONFIG_FSL_DDR_INTERACTIVE) += cli_simple.o cli_readline.o
> -obj-$(CONFIG_STM32MP1_DDR_INTERACTIVE) += cli_simple.o cli_readline.o
> +obj-$(CONFIG_FSL_DDR_INTERACTIVE) += cli_getch.o cli_simple.o cli_readline.o
> +obj-$(CONFIG_STM32MP1_DDR_INTERACTIVE) += cli_getch.o cli_simple.o cli_readline.o
>   obj-$(CONFIG_DFU_OVER_USB) += dfu.o
>   obj-y += command.o
>   obj-$(CONFIG_$(SPL_TPL_)LOG) += log.o
> diff --git a/common/cli_getch.c b/common/cli_getch.c
> new file mode 100644
> index 00000000000..9eeea7fef29
> --- /dev/null
> +++ b/common/cli_getch.c
> @@ -0,0 +1,204 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * (C) Copyright 2000
> + * Wolfgang Denk, DENX Software Engineering, wd@denx.de.
> + *
> + * Copyright 2022 Google LLC
> + */
> +
> +#include <common.h>
> +#include <cli.h>
> +
> +/**
> + * enum cli_esc_state_t - indicates what to do with an escape character
> + *
> + * @ESC_REJECT: Invalid escape sequence, so the esc_save[] characters are
> + *	returned from each subsequent call to cli_ch_esc()
> + * @ESC_SAVE: Character should be saved in esc_save until we have another one
> + * @ESC_CONVERTED: Escape sequence has been completed and the resulting
> + *	character is available
> + */
> +enum cli_esc_state_t {
> +	ESC_REJECT,
> +	ESC_SAVE,
> +	ESC_CONVERTED
> +};
> +
> +void cli_ch_init(struct cli_ch_state *cch)
> +{
> +	memset(cch, '\0', sizeof(*cch));
> +}
> +
> +/**
> + * cli_ch_esc() - Process a character in an ongoing escape sequence
> + *
> + * @cch: State information
> + * @ichar: Character to process
> + * @actp: Returns the action to take
> + * Returns: Output character if *actp is ESC_CONVERTED, else 0
> + */
> +static int cli_ch_esc(struct cli_ch_state *cch, int ichar,
> +		      enum cli_esc_state_t *actp)
> +{
> +	enum cli_esc_state_t act = ESC_REJECT;
> +
> +	switch (cch->esc_len) {
> +	case 1:
> +		if (ichar == '[' || ichar == 'O')
> +			act = ESC_SAVE;
> +		break;
> +	case 2:
> +		switch (ichar) {
> +		case 'D':	/* <- key */
> +			ichar = CTL_CH('b');
> +			act = ESC_CONVERTED;
> +			break;	/* pass off to ^B handler */
> +		case 'C':	/* -> key */
> +			ichar = CTL_CH('f');
> +			act = ESC_CONVERTED;
> +			break;	/* pass off to ^F handler */
> +		case 'H':	/* Home key */
> +			ichar = CTL_CH('a');
> +			act = ESC_CONVERTED;
> +			break;	/* pass off to ^A handler */
> +		case 'F':	/* End key */
> +			ichar = CTL_CH('e');
> +			act = ESC_CONVERTED;
> +			break;	/* pass off to ^E handler */
> +		case 'A':	/* up arrow */
> +			ichar = CTL_CH('p');
> +			act = ESC_CONVERTED;
> +			break;	/* pass off to ^P handler */
> +		case 'B':	/* down arrow */
> +			ichar = CTL_CH('n');
> +			act = ESC_CONVERTED;
> +			break;	/* pass off to ^N handler */
> +		case '1':
> +		case '2':
> +		case '3':
> +		case '4':
> +		case '7':
> +		case '8':
> +			if (cch->esc_save[1] == '[') {
> +				/* see if next character is ~ */
> +				act = ESC_SAVE;
> +			}
> +			break;
> +		}
> +		break;
> +	case 3:
> +		switch (ichar) {
> +		case '~':
> +			switch (cch->esc_save[2]) {
> +			case '3':	/* Delete key */
> +				ichar = CTL_CH('d');
> +				act = ESC_CONVERTED;
> +				break;	/* pass to ^D handler */
> +			case '1':	/* Home key */
> +			case '7':
> +				ichar = CTL_CH('a');
> +				act = ESC_CONVERTED;
> +				break;	/* pass to ^A handler */
> +			case '4':	/* End key */
> +			case '8':
> +				ichar = CTL_CH('e');
> +				act = ESC_CONVERTED;
> +				break;	/* pass to ^E handler */
> +			}
> +			break;
> +		case '0':
> +			if (cch->esc_save[2] == '2')
> +				act = ESC_SAVE;
> +			break;
> +		}
> +		break;
> +	case 4:
> +		switch (ichar) {
> +		case '0':
> +		case '1':
> +			act = ESC_SAVE;
> +			break;		/* bracketed paste */
> +		}
> +		break;
> +	case 5:
> +		if (ichar == '~') {	/* bracketed paste */
> +			ichar = 0;
> +			act = ESC_CONVERTED;
> +		}
> +	}
> +
> +	*actp = act;
> +
> +	return act == ESC_CONVERTED ? ichar : 0;
> +}
> +
> +int cli_ch_process(struct cli_ch_state *cch, int ichar)
> +{
> +	/*
> +	 * ichar=0x0 when error occurs in U-Boot getchar() or when the caller
> +	 * wants to check if there are more characters saved in the escape
> +	 * sequence
> +	 */
> +	if (!ichar) {
> +		if (cch->emit_upto) {
> +			if (cch->emit_upto < cch->esc_len)
> +				return cch->esc_save[cch->emit_upto++];
> +			cch->emit_upto = 0;
> +		}
> +		return 0;
> +	} else if (ichar == -ETIMEDOUT) {
> +		/*
> +		 * If we are in an escape sequence but nothing has followed the
> +		 * Escape character, then the user probably just pressed the
> +		 * Escape key. Return it and clear the sequence.
> +		 */
> +		if (cch->esc_len) {
> +			cch->esc_len = 0;
> +			return '\e';
> +		}
> +
> +		/* Otherwise there is nothing to return */
> +		return 0;
> +	}
> +
> +	if (ichar == '\n' || ichar == '\r')
> +		return '\n';
> +
> +	/* handle standard linux xterm esc sequences for arrow key, etc. */
> +	if (cch->esc_len != 0) {
> +		enum cli_esc_state_t act;
> +
> +		ichar = cli_ch_esc(cch, ichar, &act);
> +
> +		switch (act) {
> +		case ESC_SAVE:
> +			/* save this character and return nothing */
> +			cch->esc_save[cch->esc_len++] = ichar;
> +			return 0;
> +		case ESC_REJECT:
> +			/*
> +			 * invalid escape sequence, start returning the
> +			 * characters in it
> +			 */
> +			cch->esc_save[cch->esc_len++] = ichar;
> +			return cch->esc_save[cch->emit_upto++];
> +		case ESC_CONVERTED:
> +			/* valid escape sequence, return the resulting char */
> +			cch->esc_len = 0;
> +			return ichar;
> +		}
> +	}
> +
> +	if (ichar == '\e') {
> +		if (!cch->esc_len) {
> +			cch->esc_save[cch->esc_len] = ichar;
> +			cch->esc_len = 1;
> +		} else {
> +			puts("impossible condition #876\n");
> +			cch->esc_len = 0;
> +		}
> +		return 0;
> +	}
> +
> +	return ichar;
> +}
> diff --git a/common/cli_readline.c b/common/cli_readline.c
> index d6444f5fc1d..709e9c3d38b 100644
> --- a/common/cli_readline.c
> +++ b/common/cli_readline.c
> @@ -62,7 +62,6 @@ static char *delete_char (char *buffer, char *p, int *colp, int *np, int plen)
>
>   #define putnstr(str, n)	printf("%.*s", (int)n, str)
>
> -#define CTL_CH(c)		((c) - 'a' + 1)
>   #define CTL_BACKSPACE		('\b')
>   #define DEL			((char)255)
>   #define DEL7			((char)127)
> @@ -252,156 +251,53 @@ static void cread_add_str(char *str, int strsize, int insert,
>   static int cread_line(const char *const prompt, char *buf, unsigned int *len,
>   		int timeout)
>   {
> +	struct cli_ch_state s_cch, *cch = &s_cch;
>   	unsigned long num = 0;
>   	unsigned long eol_num = 0;
>   	unsigned long wlen;
>   	char ichar;
>   	int insert = 1;
> -	int esc_len = 0;
> -	char esc_save[8];
>   	int init_len = strlen(buf);
>   	int first = 1;
>
> +	cli_ch_init(cch);
> +
>   	if (init_len)
>   		cread_add_str(buf, init_len, 1, &num, &eol_num, buf, *len);
>
>   	while (1) {
> -		if (bootretry_tstc_timeout())
> -			return -2;	/* timed out */
> -		if (first && timeout) {
> -			uint64_t etime = endtick(timeout);
> -
> -			while (!tstc()) {	/* while no incoming data */
> -				if (get_ticks() >= etime)
> -					return -2;	/* timed out */
> -				schedule();
> +		/* Check for saved characters */
> +		ichar = cli_ch_process(cch, 0);
> +
> +		if (!ichar) {
> +			if (bootretry_tstc_timeout())
> +				return -2;	/* timed out */
> +			if (first && timeout) {
> +				u64 etime = endtick(timeout);
> +
> +				while (!tstc()) {	/* while no incoming data */
> +					if (get_ticks() >= etime)
> +						return -2;	/* timed out */
> +					schedule();
> +				}
> +				first = 0;
>   			}
> -			first = 0;
> +
> +			ichar = getcmd_getch();
>   		}
>
> -		ichar = getcmd_getch();
> +		ichar = cli_ch_process(cch, ichar);
>
>   		/* ichar=0x0 when error occurs in U-Boot getc */
>   		if (!ichar)
>   			continue;
>
> -		if ((ichar == '\n') || (ichar == '\r')) {
> +		if (ichar == '\n') {
>   			putc('\n');
>   			break;
>   		}
>
> -		/*
> -		 * handle standard linux xterm esc sequences for arrow key, etc.
> -		 */
> -		if (esc_len != 0) {
> -			enum { ESC_REJECT, ESC_SAVE, ESC_CONVERTED } act = ESC_REJECT;
> -
> -			if (esc_len == 1) {
> -				if (ichar == '[' || ichar == 'O')
> -					act = ESC_SAVE;
> -			} else if (esc_len == 2) {
> -				switch (ichar) {
> -				case 'D':	/* <- key */
> -					ichar = CTL_CH('b');
> -					act = ESC_CONVERTED;
> -					break;	/* pass off to ^B handler */

We have similar code to decode escape sequences and Unicode letters in
efi_cin_read_key_stroke_ex(). The big difference in the EFI code is that
it does not use dummy control characters but uses a structure:

struct efi_key_data {
         struct efi_input_key key;
         struct efi_key_state key_state;
};

with

struct efi_input_key {
         u16 scan_code;
         s16 unicode_char;
};

The special keys are represented as numbers in scan_code while
characters are represented in unicode_char.

Separating special keys from characters is much cleaner than creating
dummy control characters.

Anyway you would be able to first create the structure above and then in
a separate step to convert to control characters.

The code below misses to interprete some special keys like FN1 - FN12
which could be quite useful for navigating a UI.

Best regards

Heinrich

> -				case 'C':	/* -> key */
> -					ichar = CTL_CH('f');
> -					act = ESC_CONVERTED;
> -					break;	/* pass off to ^F handler */
> -				case 'H':	/* Home key */
> -					ichar = CTL_CH('a');
> -					act = ESC_CONVERTED;
> -					break;	/* pass off to ^A handler */
> -				case 'F':	/* End key */
> -					ichar = CTL_CH('e');
> -					act = ESC_CONVERTED;
> -					break;	/* pass off to ^E handler */
> -				case 'A':	/* up arrow */
> -					ichar = CTL_CH('p');
> -					act = ESC_CONVERTED;
> -					break;	/* pass off to ^P handler */
> -				case 'B':	/* down arrow */
> -					ichar = CTL_CH('n');
> -					act = ESC_CONVERTED;
> -					break;	/* pass off to ^N handler */
> -				case '1':
> -				case '2':
> -				case '3':
> -				case '4':
> -				case '7':
> -				case '8':
> -					if (esc_save[1] == '[') {
> -						/* see if next character is ~ */
> -						act = ESC_SAVE;
> -					}
> -					break;
> -				}
> -			} else if (esc_len == 3) {
> -				switch (ichar) {
> -				case '~':
> -					switch (esc_save[2]) {
> -					case '3':	/* Delete key */
> -						ichar = CTL_CH('d');
> -						act = ESC_CONVERTED;
> -						break;	/* pass to ^D handler */
> -					case '1':	/* Home key */
> -					case '7':
> -						ichar = CTL_CH('a');
> -						act = ESC_CONVERTED;
> -						break;	/* pass to ^A handler */
> -					case '4':	/* End key */
> -					case '8':
> -						ichar = CTL_CH('e');
> -						act = ESC_CONVERTED;
> -						break;	/* pass to ^E handler */
> -					}
> -					break;
> -				case '0':
> -					if (esc_save[2] == '2')
> -						act = ESC_SAVE;
> -					break;
> -				}
> -			} else if (esc_len == 4) {
> -				switch (ichar) {
> -				case '0':
> -				case '1':
> -					act = ESC_SAVE;
> -					break;		/* bracketed paste */
> -				}
> -			} else if (esc_len == 5) {
> -				if (ichar == '~') {	/* bracketed paste */
> -					ichar = 0;
> -					act = ESC_CONVERTED;
> -				}
> -			}
> -			switch (act) {
> -			case ESC_SAVE:
> -				esc_save[esc_len++] = ichar;
> -				continue;
> -			case ESC_REJECT:
> -				esc_save[esc_len++] = ichar;
> -				cread_add_str(esc_save, esc_len, insert,
> -					      &num, &eol_num, buf, *len);
> -				esc_len = 0;
> -				continue;
> -			case ESC_CONVERTED:
> -				esc_len = 0;
> -				break;
> -			}
> -		}
> -
>   		switch (ichar) {
> -		case 0x1b:
> -			if (esc_len == 0) {
> -				esc_save[esc_len] = ichar;
> -				esc_len = 1;
> -			} else {
> -				puts("impossible condition #876\n");
> -				esc_len = 0;
> -			}
> -			break;
> -
>   		case CTL_CH('a'):
>   			BEGINNING_OF_LINE();
>   			break;
> @@ -470,8 +366,6 @@ static int cread_line(const char *const prompt, char *buf, unsigned int *len,
>   		{
>   			char *hline;
>
> -			esc_len = 0;
> -
>   			if (ichar == CTL_CH('p'))
>   				hline = hist_prev();
>   			else
> diff --git a/include/cli.h b/include/cli.h
> index ba5b8ebd36e..863519e4b13 100644
> --- a/include/cli.h
> +++ b/include/cli.h
> @@ -7,6 +7,21 @@
>   #ifndef __CLI_H
>   #define __CLI_H
>
> +#include <stdbool.h>
> +
> +/**
> + * struct cli_ch_state - state information for reading cmdline characters
> + *
> + * @esc_len: Number of escape characters read so far
> + * @esc_save: Escape characters collected so far
> + * @emit_upto: Next character to emit from esc_save (0 if not emitting)
> + */
> +struct cli_ch_state {
> +	int esc_len;
> +	char esc_save[8];
> +	int emit_upto;
> +};
> +
>   /**
>    * Go into the command loop
>    *
> @@ -154,5 +169,62 @@ void cli_loop(void);
>   void cli_init(void);
>
>   #define endtick(seconds) (get_ticks() + (uint64_t)(seconds) * get_tbclk())
> +#define CTL_CH(c)		((c) - 'a' + 1)
> +
> +/**
> + * cli_ch_init() - Set up the initial state to process input characters
> + *
> + * @cch: State to set up
> + */
> +void cli_ch_init(struct cli_ch_state *cch);
> +
> +/**
> + * cli_ch_process() - Process an input character
> + *
> + * When @ichar is 0, this function returns any characters from an invalid escape
> + * sequence which are still pending in the buffer
> + *
> + * Otherwise it processes the input character. If it is an escape character,
> + * then an escape sequence is started and the function returns 0. If we are in
> + * the middle of an escape sequence, the character is processed and may result
> + * in returning 0 (if more characters are needed) or a valid character (if
> + * @ichar finishes the sequence).
> + *
> + * If @ichar is a valid character and there is no escape sequence in progress,
> + * then it is returned as is.
> + *
> + * If the Enter key is pressed, '\n' is returned.
> + *
> + * Usage should be like this::
> + *
> + *    struct cli_ch_state cch;
> + *
> + *    cli_ch_init(cch);
> + *    do
> + *       {
> + *       int ichar, ch;
> + *
> + *       ichar = cli_ch_process(cch, 0);
> + *       if (!ichar) {
> + *          ch = getchar();
> + *          ichar = cli_ch_process(cch, ch);
> + *       }
> + *       (handle the ichar character)
> + *    } while (!done)
> + *
> + * If tstc() is used to look for keypresses, this function can be called with
> + * @ichar set to -ETIMEDOUT if there is no character after 5-10ms. This allows
> + * the ambgiuity between the Escape key and the arrow keys (which generate an
> + * escape character followed by other characters) to be resolved.
> + *
> + * @cch: Current state
> + * @ichar: Input character to process, or 0 if none, or -ETIMEDOUT if no
> + * character has been received within a small number of milliseconds (this
> + * cancels any existing escape sequence and allows pressing the Escape key to
> + * work)
> + * Returns: Resulting input character after processing, 0 if none, '\e' if
> + * an existing escape sequence was cancelled
> + */
> +int cli_ch_process(struct cli_ch_state *cch, int ichar);
>
>   #endif


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

* Re: [PATCH v3 03/25] bootmenu: Add a few comments
  2023-01-06 14:52 ` [PATCH v3 03/25] bootmenu: Add a few comments Simon Glass
@ 2023-01-06 15:53   ` Heinrich Schuchardt
  2023-01-07  0:13     ` Simon Glass
  0 siblings, 1 reply; 40+ messages in thread
From: Heinrich Schuchardt @ 2023-01-06 15:53 UTC (permalink / raw)
  To: Simon Glass; +Cc: Anatolij Gustschin, Tom Rini, U-Boot Mailing List

On 1/6/23 15:52, Simon Glass wrote:
> The behaviour of these two functions is completely undocumented. Add some
> notes so the poor, suffering dev can figure out what is going on.
>
> Signed-off-by: Simon Glass <sjg@chromium.org>
> ---
>
> (no changes since v1)
>
>   include/menu.h | 42 ++++++++++++++++++++++++++++++++++++++++++
>   1 file changed, 42 insertions(+)
>
> diff --git a/include/menu.h b/include/menu.h
> index 702aacb170c..0b4d9734149 100644
> --- a/include/menu.h
> +++ b/include/menu.h
> @@ -42,6 +42,7 @@ struct bootmenu_data {
>   	struct bootmenu_entry *first;	/* first menu entry */
>   };
>
> +/** enum bootmenu_key - keys that can be returned by the bootmenu */
>   enum bootmenu_key {
>   	KEY_NONE = 0,
>   	KEY_UP,
> @@ -53,8 +54,49 @@ enum bootmenu_key {
>   	KEY_SPACE,
>   };
>
> +/**
> + * bootmenu_autoboot_loop() - handle autobooting if no key is pressed
> + *
> + * This shows a prompt to allow the user to press a key to interrupt auto boot
> + * of the first menu option.
> + *
> + * It then waits for the required time (menu->delay in seconds) for a key to be
> + * pressed. If nothing is pressed in that time, @key returns KEY_SELECT
> + * indicating that the current option should be chosen.
> + *
> + * @menu: Menu being processed
> + * @key: Returns the code for the key the user pressed:
> + *	enter: KEY_SELECT
> + *	Ctrl-C: KEY_QUIT
> + *	anything else: KEY_NONE
> + * @esc: Set to 1 if the escape key is pressed, otherwise not updated
> + */
>   void bootmenu_autoboot_loop(struct bootmenu_data *menu,
>   			    enum bootmenu_key *key, int *esc);
> +
> +/**
> + * bootmenu_loop() - handle waiting for a keypress when autoboot is disabled
> + *
> + * This is used when the menu delay is negative, indicating that the delay has
> + * elapsed, or there was no delay to begin with.
> + *
> + * It reads a character and processes it, returning a menu-key code if a
> + * character is recognised
> + *
> + * @menu: Menu being processed
> + * @key: Returns the code for the key the user pressed:
> + *	enter: KEY_SELECT
> + *	Ctrl-C: KEY_QUIT
> + *	Up arrow: KEY_UP
> + *	Down arrow: KEY_DOWN
> + *	Escape (by itself): KEY_QUIT
> + *	Plus: KEY_PLUS
> + *	Minus: KEY_MINUS
> + *	Space: KEY_SPACE
> + * @esc: On input, a non-zero value indicates that an escape sequence has
> + *	resulted in that many characters so far. On exit this is updated to the
> + *	new number of characters
> + */
>   void bootmenu_loop(struct bootmenu_data *menu,
>   		   enum bootmenu_key *key, int *esc);

This should not be an exported function. The program part that wants to
display a menu should not have to deal with navigation keys.

Best regards

Heinrich



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

* Re: [PATCH v3 03/25] bootmenu: Add a few comments
  2023-01-06 15:53   ` Heinrich Schuchardt
@ 2023-01-07  0:13     ` Simon Glass
  2023-01-07 22:34       ` Heinrich Schuchardt
  0 siblings, 1 reply; 40+ messages in thread
From: Simon Glass @ 2023-01-07  0:13 UTC (permalink / raw)
  To: Heinrich Schuchardt; +Cc: Anatolij Gustschin, Tom Rini, U-Boot Mailing List

Hi Heinrich,

On Fri, 6 Jan 2023 at 09:53, Heinrich Schuchardt <xypron.glpk@gmx.de> wrote:
>
> On 1/6/23 15:52, Simon Glass wrote:
> > The behaviour of these two functions is completely undocumented. Add some
> > notes so the poor, suffering dev can figure out what is going on.
> >
> > Signed-off-by: Simon Glass <sjg@chromium.org>
> > ---
> >
> > (no changes since v1)
> >
> >   include/menu.h | 42 ++++++++++++++++++++++++++++++++++++++++++
> >   1 file changed, 42 insertions(+)
> >
> > diff --git a/include/menu.h b/include/menu.h
> > index 702aacb170c..0b4d9734149 100644
> > --- a/include/menu.h
> > +++ b/include/menu.h
> > @@ -42,6 +42,7 @@ struct bootmenu_data {
> >       struct bootmenu_entry *first;   /* first menu entry */
> >   };
> >
> > +/** enum bootmenu_key - keys that can be returned by the bootmenu */
> >   enum bootmenu_key {
> >       KEY_NONE = 0,
> >       KEY_UP,
> > @@ -53,8 +54,49 @@ enum bootmenu_key {
> >       KEY_SPACE,
> >   };
> >
> > +/**
> > + * bootmenu_autoboot_loop() - handle autobooting if no key is pressed
> > + *
> > + * This shows a prompt to allow the user to press a key to interrupt auto boot
> > + * of the first menu option.
> > + *
> > + * It then waits for the required time (menu->delay in seconds) for a key to be
> > + * pressed. If nothing is pressed in that time, @key returns KEY_SELECT
> > + * indicating that the current option should be chosen.
> > + *
> > + * @menu: Menu being processed
> > + * @key: Returns the code for the key the user pressed:
> > + *   enter: KEY_SELECT
> > + *   Ctrl-C: KEY_QUIT
> > + *   anything else: KEY_NONE
> > + * @esc: Set to 1 if the escape key is pressed, otherwise not updated
> > + */
> >   void bootmenu_autoboot_loop(struct bootmenu_data *menu,
> >                           enum bootmenu_key *key, int *esc);
> > +
> > +/**
> > + * bootmenu_loop() - handle waiting for a keypress when autoboot is disabled
> > + *
> > + * This is used when the menu delay is negative, indicating that the delay has
> > + * elapsed, or there was no delay to begin with.
> > + *
> > + * It reads a character and processes it, returning a menu-key code if a
> > + * character is recognised
> > + *
> > + * @menu: Menu being processed
> > + * @key: Returns the code for the key the user pressed:
> > + *   enter: KEY_SELECT
> > + *   Ctrl-C: KEY_QUIT
> > + *   Up arrow: KEY_UP
> > + *   Down arrow: KEY_DOWN
> > + *   Escape (by itself): KEY_QUIT
> > + *   Plus: KEY_PLUS
> > + *   Minus: KEY_MINUS
> > + *   Space: KEY_SPACE
> > + * @esc: On input, a non-zero value indicates that an escape sequence has
> > + *   resulted in that many characters so far. On exit this is updated to the
> > + *   new number of characters
> > + */
> >   void bootmenu_loop(struct bootmenu_data *menu,
> >                  enum bootmenu_key *key, int *esc);
>
> This should not be an exported function. The program part that wants to
> display a menu should not have to deal with navigation keys.

This is a patch to add comments to code which is not otherwise
addressed in this series. We need comments so that we know what things
do. We can always adjust the logic later.

My intent with this series is to add a new menu which is more flexible.

Regards,
Simon

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

* Re: [PATCH v3 02/25] cli: Move readline character-processing to a state machine
  2023-01-06 15:50   ` Heinrich Schuchardt
@ 2023-01-07  0:13     ` Simon Glass
  2023-01-07 22:31       ` Heinrich Schuchardt
  0 siblings, 1 reply; 40+ messages in thread
From: Simon Glass @ 2023-01-07  0:13 UTC (permalink / raw)
  To: Heinrich Schuchardt; +Cc: Anatolij Gustschin, Tom Rini, U-Boot Mailing List

Hi Heinrich,

On Fri, 6 Jan 2023 at 08:50, Heinrich Schuchardt <xypron.glpk@gmx.de> wrote:
>
> On 1/6/23 15:52, Simon Glass wrote:
> > The current cread_line() function is very long. It handles the escape
> > processing inline. The menu command does similar processing but at the
> > character level, so there is some duplication.
> >
> > Split the character processing into a new function cli_ch_process() which
> > processes individual characters and returns the resulting input character,
> > taking account of escape sequences. It requires the caller to set up and
> > maintain its state.
> >
> > Update cread_line() to use this new function.
> >
> > The only intended functional change is that an invalid escape sequence
> > does not add invalid/control characters into the input buffer, but instead
> > discards these.
> >
> > Signed-off-by: Simon Glass <sjg@chromium.org>
> > ---
> >
> > (no changes since v1)
> >
> >   common/Makefile       |   6 +-
> >   common/cli_getch.c    | 204 ++++++++++++++++++++++++++++++++++++++++++
> >   common/cli_readline.c | 150 +++++--------------------------
> >   include/cli.h         |  72 +++++++++++++++
> >   4 files changed, 301 insertions(+), 131 deletions(-)
> >   create mode 100644 common/cli_getch.c
> >
> > diff --git a/common/Makefile b/common/Makefile
> > index 20addfb244c..67485e77a04 100644
> > --- a/common/Makefile
> > +++ b/common/Makefile
> > @@ -38,7 +38,7 @@ obj-$(CONFIG_SPLASH_SOURCE) += splash_source.o
> >   obj-$(CONFIG_MENU) += menu.o
> >   obj-$(CONFIG_UPDATE_COMMON) += update.o
> >   obj-$(CONFIG_USB_KEYBOARD) += usb_kbd.o
> > -obj-$(CONFIG_CMDLINE) += cli_readline.o cli_simple.o
> > +obj-$(CONFIG_CMDLINE) += cli_getch.o cli_readline.o cli_simple.o
> >
> >   endif # !CONFIG_SPL_BUILD
> >
> > @@ -93,8 +93,8 @@ obj-y += eeprom/eeprom_field.o eeprom/eeprom_layout.o
> >   endif
> >
> >   obj-y += cli.o
> > -obj-$(CONFIG_FSL_DDR_INTERACTIVE) += cli_simple.o cli_readline.o
> > -obj-$(CONFIG_STM32MP1_DDR_INTERACTIVE) += cli_simple.o cli_readline.o
> > +obj-$(CONFIG_FSL_DDR_INTERACTIVE) += cli_getch.o cli_simple.o cli_readline.o
> > +obj-$(CONFIG_STM32MP1_DDR_INTERACTIVE) += cli_getch.o cli_simple.o cli_readline.o
> >   obj-$(CONFIG_DFU_OVER_USB) += dfu.o
> >   obj-y += command.o
> >   obj-$(CONFIG_$(SPL_TPL_)LOG) += log.o
> > diff --git a/common/cli_getch.c b/common/cli_getch.c
> > new file mode 100644
> > index 00000000000..9eeea7fef29
> > --- /dev/null
> > +++ b/common/cli_getch.c
> > @@ -0,0 +1,204 @@
> > +// SPDX-License-Identifier: GPL-2.0+
> > +/*
> > + * (C) Copyright 2000
> > + * Wolfgang Denk, DENX Software Engineering, wd@denx.de.
> > + *
> > + * Copyright 2022 Google LLC
> > + */
> > +
> > +#include <common.h>
> > +#include <cli.h>
> > +
> > +/**
> > + * enum cli_esc_state_t - indicates what to do with an escape character
> > + *
> > + * @ESC_REJECT: Invalid escape sequence, so the esc_save[] characters are
> > + *   returned from each subsequent call to cli_ch_esc()
> > + * @ESC_SAVE: Character should be saved in esc_save until we have another one
> > + * @ESC_CONVERTED: Escape sequence has been completed and the resulting
> > + *   character is available
> > + */
> > +enum cli_esc_state_t {
> > +     ESC_REJECT,
> > +     ESC_SAVE,
> > +     ESC_CONVERTED
> > +};
> > +
> > +void cli_ch_init(struct cli_ch_state *cch)
> > +{
> > +     memset(cch, '\0', sizeof(*cch));
> > +}
> > +
> > +/**
> > + * cli_ch_esc() - Process a character in an ongoing escape sequence
> > + *
> > + * @cch: State information
> > + * @ichar: Character to process
> > + * @actp: Returns the action to take
> > + * Returns: Output character if *actp is ESC_CONVERTED, else 0
> > + */
> > +static int cli_ch_esc(struct cli_ch_state *cch, int ichar,
> > +                   enum cli_esc_state_t *actp)
> > +{
> > +     enum cli_esc_state_t act = ESC_REJECT;
> > +
> > +     switch (cch->esc_len) {
> > +     case 1:
> > +             if (ichar == '[' || ichar == 'O')
> > +                     act = ESC_SAVE;
> > +             break;
> > +     case 2:
> > +             switch (ichar) {
> > +             case 'D':       /* <- key */
> > +                     ichar = CTL_CH('b');
> > +                     act = ESC_CONVERTED;
> > +                     break;  /* pass off to ^B handler */
> > +             case 'C':       /* -> key */
> > +                     ichar = CTL_CH('f');
> > +                     act = ESC_CONVERTED;
> > +                     break;  /* pass off to ^F handler */
> > +             case 'H':       /* Home key */
> > +                     ichar = CTL_CH('a');
> > +                     act = ESC_CONVERTED;
> > +                     break;  /* pass off to ^A handler */
> > +             case 'F':       /* End key */
> > +                     ichar = CTL_CH('e');
> > +                     act = ESC_CONVERTED;
> > +                     break;  /* pass off to ^E handler */
> > +             case 'A':       /* up arrow */
> > +                     ichar = CTL_CH('p');
> > +                     act = ESC_CONVERTED;
> > +                     break;  /* pass off to ^P handler */
> > +             case 'B':       /* down arrow */
> > +                     ichar = CTL_CH('n');
> > +                     act = ESC_CONVERTED;
> > +                     break;  /* pass off to ^N handler */
> > +             case '1':
> > +             case '2':
> > +             case '3':
> > +             case '4':
> > +             case '7':
> > +             case '8':
> > +                     if (cch->esc_save[1] == '[') {
> > +                             /* see if next character is ~ */
> > +                             act = ESC_SAVE;
> > +                     }
> > +                     break;
> > +             }
> > +             break;
> > +     case 3:
> > +             switch (ichar) {
> > +             case '~':
> > +                     switch (cch->esc_save[2]) {
> > +                     case '3':       /* Delete key */
> > +                             ichar = CTL_CH('d');
> > +                             act = ESC_CONVERTED;
> > +                             break;  /* pass to ^D handler */
> > +                     case '1':       /* Home key */
> > +                     case '7':
> > +                             ichar = CTL_CH('a');
> > +                             act = ESC_CONVERTED;
> > +                             break;  /* pass to ^A handler */
> > +                     case '4':       /* End key */
> > +                     case '8':
> > +                             ichar = CTL_CH('e');
> > +                             act = ESC_CONVERTED;
> > +                             break;  /* pass to ^E handler */
> > +                     }
> > +                     break;
> > +             case '0':
> > +                     if (cch->esc_save[2] == '2')
> > +                             act = ESC_SAVE;
> > +                     break;
> > +             }
> > +             break;
> > +     case 4:
> > +             switch (ichar) {
> > +             case '0':
> > +             case '1':
> > +                     act = ESC_SAVE;
> > +                     break;          /* bracketed paste */
> > +             }
> > +             break;
> > +     case 5:
> > +             if (ichar == '~') {     /* bracketed paste */
> > +                     ichar = 0;
> > +                     act = ESC_CONVERTED;
> > +             }
> > +     }
> > +
> > +     *actp = act;
> > +
> > +     return act == ESC_CONVERTED ? ichar : 0;
> > +}
> > +
> > +int cli_ch_process(struct cli_ch_state *cch, int ichar)
> > +{
> > +     /*
> > +      * ichar=0x0 when error occurs in U-Boot getchar() or when the caller
> > +      * wants to check if there are more characters saved in the escape
> > +      * sequence
> > +      */
> > +     if (!ichar) {
> > +             if (cch->emit_upto) {
> > +                     if (cch->emit_upto < cch->esc_len)
> > +                             return cch->esc_save[cch->emit_upto++];
> > +                     cch->emit_upto = 0;
> > +             }
> > +             return 0;
> > +     } else if (ichar == -ETIMEDOUT) {
> > +             /*
> > +              * If we are in an escape sequence but nothing has followed the
> > +              * Escape character, then the user probably just pressed the
> > +              * Escape key. Return it and clear the sequence.
> > +              */
> > +             if (cch->esc_len) {
> > +                     cch->esc_len = 0;
> > +                     return '\e';
> > +             }
> > +
> > +             /* Otherwise there is nothing to return */
> > +             return 0;
> > +     }
> > +
> > +     if (ichar == '\n' || ichar == '\r')
> > +             return '\n';
> > +
> > +     /* handle standard linux xterm esc sequences for arrow key, etc. */
> > +     if (cch->esc_len != 0) {
> > +             enum cli_esc_state_t act;
> > +
> > +             ichar = cli_ch_esc(cch, ichar, &act);
> > +
> > +             switch (act) {
> > +             case ESC_SAVE:
> > +                     /* save this character and return nothing */
> > +                     cch->esc_save[cch->esc_len++] = ichar;
> > +                     return 0;
> > +             case ESC_REJECT:
> > +                     /*
> > +                      * invalid escape sequence, start returning the
> > +                      * characters in it
> > +                      */
> > +                     cch->esc_save[cch->esc_len++] = ichar;
> > +                     return cch->esc_save[cch->emit_upto++];
> > +             case ESC_CONVERTED:
> > +                     /* valid escape sequence, return the resulting char */
> > +                     cch->esc_len = 0;
> > +                     return ichar;
> > +             }
> > +     }
> > +
> > +     if (ichar == '\e') {
> > +             if (!cch->esc_len) {
> > +                     cch->esc_save[cch->esc_len] = ichar;
> > +                     cch->esc_len = 1;
> > +             } else {
> > +                     puts("impossible condition #876\n");
> > +                     cch->esc_len = 0;
> > +             }
> > +             return 0;
> > +     }
> > +
> > +     return ichar;
> > +}
> > diff --git a/common/cli_readline.c b/common/cli_readline.c
> > index d6444f5fc1d..709e9c3d38b 100644
> > --- a/common/cli_readline.c
> > +++ b/common/cli_readline.c
> > @@ -62,7 +62,6 @@ static char *delete_char (char *buffer, char *p, int *colp, int *np, int plen)
> >
> >   #define putnstr(str, n)     printf("%.*s", (int)n, str)
> >
> > -#define CTL_CH(c)            ((c) - 'a' + 1)
> >   #define CTL_BACKSPACE               ('\b')
> >   #define DEL                 ((char)255)
> >   #define DEL7                        ((char)127)
> > @@ -252,156 +251,53 @@ static void cread_add_str(char *str, int strsize, int insert,
> >   static int cread_line(const char *const prompt, char *buf, unsigned int *len,
> >               int timeout)
> >   {
> > +     struct cli_ch_state s_cch, *cch = &s_cch;
> >       unsigned long num = 0;
> >       unsigned long eol_num = 0;
> >       unsigned long wlen;
> >       char ichar;
> >       int insert = 1;
> > -     int esc_len = 0;
> > -     char esc_save[8];
> >       int init_len = strlen(buf);
> >       int first = 1;
> >
> > +     cli_ch_init(cch);
> > +
> >       if (init_len)
> >               cread_add_str(buf, init_len, 1, &num, &eol_num, buf, *len);
> >
> >       while (1) {
> > -             if (bootretry_tstc_timeout())
> > -                     return -2;      /* timed out */
> > -             if (first && timeout) {
> > -                     uint64_t etime = endtick(timeout);
> > -
> > -                     while (!tstc()) {       /* while no incoming data */
> > -                             if (get_ticks() >= etime)
> > -                                     return -2;      /* timed out */
> > -                             schedule();
> > +             /* Check for saved characters */
> > +             ichar = cli_ch_process(cch, 0);
> > +
> > +             if (!ichar) {
> > +                     if (bootretry_tstc_timeout())
> > +                             return -2;      /* timed out */
> > +                     if (first && timeout) {
> > +                             u64 etime = endtick(timeout);
> > +
> > +                             while (!tstc()) {       /* while no incoming data */
> > +                                     if (get_ticks() >= etime)
> > +                                             return -2;      /* timed out */
> > +                                     schedule();
> > +                             }
> > +                             first = 0;
> >                       }
> > -                     first = 0;
> > +
> > +                     ichar = getcmd_getch();
> >               }
> >
> > -             ichar = getcmd_getch();
> > +             ichar = cli_ch_process(cch, ichar);
> >
> >               /* ichar=0x0 when error occurs in U-Boot getc */
> >               if (!ichar)
> >                       continue;
> >
> > -             if ((ichar == '\n') || (ichar == '\r')) {
> > +             if (ichar == '\n') {
> >                       putc('\n');
> >                       break;
> >               }
> >
> > -             /*
> > -              * handle standard linux xterm esc sequences for arrow key, etc.
> > -              */
> > -             if (esc_len != 0) {
> > -                     enum { ESC_REJECT, ESC_SAVE, ESC_CONVERTED } act = ESC_REJECT;
> > -
> > -                     if (esc_len == 1) {
> > -                             if (ichar == '[' || ichar == 'O')
> > -                                     act = ESC_SAVE;
> > -                     } else if (esc_len == 2) {
> > -                             switch (ichar) {
> > -                             case 'D':       /* <- key */
> > -                                     ichar = CTL_CH('b');
> > -                                     act = ESC_CONVERTED;
> > -                                     break;  /* pass off to ^B handler */
>
> We have similar code to decode escape sequences and Unicode letters in
> efi_cin_read_key_stroke_ex(). The big difference in the EFI code is that
> it does not use dummy control characters but uses a structure:
>
> struct efi_key_data {
>          struct efi_input_key key;
>          struct efi_key_state key_state;
> };
>
> with
>
> struct efi_input_key {
>          u16 scan_code;
>          s16 unicode_char;
> };
>
> The special keys are represented as numbers in scan_code while
> characters are represented in unicode_char.
>
> Separating special keys from characters is much cleaner than creating
> dummy control characters.
>
> Anyway you would be able to first create the structure above and then in
> a separate step to convert to control characters.

I thought this discussion started in the last version of the series.
Do we need to support unicode input? How would that work in U-Boot,
since it seems to only be in the EFI layer at present?

>
> The code below misses to interprete some special keys like FN1 - FN12
> which could be quite useful for navigating a UI.

Can we add them later? I don't have any use for them at present.

Regards,
Simon

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

* Re: [PATCH v3 02/25] cli: Move readline character-processing to a state machine
  2023-01-07  0:13     ` Simon Glass
@ 2023-01-07 22:31       ` Heinrich Schuchardt
  0 siblings, 0 replies; 40+ messages in thread
From: Heinrich Schuchardt @ 2023-01-07 22:31 UTC (permalink / raw)
  To: Simon Glass; +Cc: Anatolij Gustschin, Tom Rini, U-Boot Mailing List

On 1/7/23 01:13, Simon Glass wrote:
> Hi Heinrich,
>
> On Fri, 6 Jan 2023 at 08:50, Heinrich Schuchardt <xypron.glpk@gmx.de> wrote:
>>
>> On 1/6/23 15:52, Simon Glass wrote:
>>> The current cread_line() function is very long. It handles the escape
>>> processing inline. The menu command does similar processing but at the
>>> character level, so there is some duplication.
>>>
>>> Split the character processing into a new function cli_ch_process() which
>>> processes individual characters and returns the resulting input character,
>>> taking account of escape sequences. It requires the caller to set up and
>>> maintain its state.
>>>
>>> Update cread_line() to use this new function.
>>>
>>> The only intended functional change is that an invalid escape sequence
>>> does not add invalid/control characters into the input buffer, but instead
>>> discards these.
>>>
>>> Signed-off-by: Simon Glass <sjg@chromium.org>
>>> ---
>>>
>>> (no changes since v1)
>>>
>>>    common/Makefile       |   6 +-
>>>    common/cli_getch.c    | 204 ++++++++++++++++++++++++++++++++++++++++++
>>>    common/cli_readline.c | 150 +++++--------------------------
>>>    include/cli.h         |  72 +++++++++++++++
>>>    4 files changed, 301 insertions(+), 131 deletions(-)
>>>    create mode 100644 common/cli_getch.c
>>>
>>> diff --git a/common/Makefile b/common/Makefile
>>> index 20addfb244c..67485e77a04 100644
>>> --- a/common/Makefile
>>> +++ b/common/Makefile
>>> @@ -38,7 +38,7 @@ obj-$(CONFIG_SPLASH_SOURCE) += splash_source.o
>>>    obj-$(CONFIG_MENU) += menu.o
>>>    obj-$(CONFIG_UPDATE_COMMON) += update.o
>>>    obj-$(CONFIG_USB_KEYBOARD) += usb_kbd.o
>>> -obj-$(CONFIG_CMDLINE) += cli_readline.o cli_simple.o
>>> +obj-$(CONFIG_CMDLINE) += cli_getch.o cli_readline.o cli_simple.o
>>>
>>>    endif # !CONFIG_SPL_BUILD
>>>
>>> @@ -93,8 +93,8 @@ obj-y += eeprom/eeprom_field.o eeprom/eeprom_layout.o
>>>    endif
>>>
>>>    obj-y += cli.o
>>> -obj-$(CONFIG_FSL_DDR_INTERACTIVE) += cli_simple.o cli_readline.o
>>> -obj-$(CONFIG_STM32MP1_DDR_INTERACTIVE) += cli_simple.o cli_readline.o
>>> +obj-$(CONFIG_FSL_DDR_INTERACTIVE) += cli_getch.o cli_simple.o cli_readline.o
>>> +obj-$(CONFIG_STM32MP1_DDR_INTERACTIVE) += cli_getch.o cli_simple.o cli_readline.o
>>>    obj-$(CONFIG_DFU_OVER_USB) += dfu.o
>>>    obj-y += command.o
>>>    obj-$(CONFIG_$(SPL_TPL_)LOG) += log.o
>>> diff --git a/common/cli_getch.c b/common/cli_getch.c
>>> new file mode 100644
>>> index 00000000000..9eeea7fef29
>>> --- /dev/null
>>> +++ b/common/cli_getch.c
>>> @@ -0,0 +1,204 @@
>>> +// SPDX-License-Identifier: GPL-2.0+
>>> +/*
>>> + * (C) Copyright 2000
>>> + * Wolfgang Denk, DENX Software Engineering, wd@denx.de.
>>> + *
>>> + * Copyright 2022 Google LLC
>>> + */
>>> +
>>> +#include <common.h>
>>> +#include <cli.h>
>>> +
>>> +/**
>>> + * enum cli_esc_state_t - indicates what to do with an escape character
>>> + *
>>> + * @ESC_REJECT: Invalid escape sequence, so the esc_save[] characters are
>>> + *   returned from each subsequent call to cli_ch_esc()
>>> + * @ESC_SAVE: Character should be saved in esc_save until we have another one
>>> + * @ESC_CONVERTED: Escape sequence has been completed and the resulting
>>> + *   character is available
>>> + */
>>> +enum cli_esc_state_t {
>>> +     ESC_REJECT,
>>> +     ESC_SAVE,
>>> +     ESC_CONVERTED
>>> +};
>>> +
>>> +void cli_ch_init(struct cli_ch_state *cch)
>>> +{
>>> +     memset(cch, '\0', sizeof(*cch));
>>> +}
>>> +
>>> +/**
>>> + * cli_ch_esc() - Process a character in an ongoing escape sequence
>>> + *
>>> + * @cch: State information
>>> + * @ichar: Character to process
>>> + * @actp: Returns the action to take
>>> + * Returns: Output character if *actp is ESC_CONVERTED, else 0
>>> + */
>>> +static int cli_ch_esc(struct cli_ch_state *cch, int ichar,
>>> +                   enum cli_esc_state_t *actp)
>>> +{
>>> +     enum cli_esc_state_t act = ESC_REJECT;
>>> +
>>> +     switch (cch->esc_len) {
>>> +     case 1:
>>> +             if (ichar == '[' || ichar == 'O')
>>> +                     act = ESC_SAVE;
>>> +             break;
>>> +     case 2:
>>> +             switch (ichar) {
>>> +             case 'D':       /* <- key */
>>> +                     ichar = CTL_CH('b');
>>> +                     act = ESC_CONVERTED;
>>> +                     break;  /* pass off to ^B handler */
>>> +             case 'C':       /* -> key */
>>> +                     ichar = CTL_CH('f');
>>> +                     act = ESC_CONVERTED;
>>> +                     break;  /* pass off to ^F handler */
>>> +             case 'H':       /* Home key */
>>> +                     ichar = CTL_CH('a');
>>> +                     act = ESC_CONVERTED;
>>> +                     break;  /* pass off to ^A handler */
>>> +             case 'F':       /* End key */
>>> +                     ichar = CTL_CH('e');
>>> +                     act = ESC_CONVERTED;
>>> +                     break;  /* pass off to ^E handler */
>>> +             case 'A':       /* up arrow */
>>> +                     ichar = CTL_CH('p');
>>> +                     act = ESC_CONVERTED;
>>> +                     break;  /* pass off to ^P handler */
>>> +             case 'B':       /* down arrow */
>>> +                     ichar = CTL_CH('n');
>>> +                     act = ESC_CONVERTED;
>>> +                     break;  /* pass off to ^N handler */
>>> +             case '1':
>>> +             case '2':
>>> +             case '3':
>>> +             case '4':
>>> +             case '7':
>>> +             case '8':
>>> +                     if (cch->esc_save[1] == '[') {
>>> +                             /* see if next character is ~ */
>>> +                             act = ESC_SAVE;
>>> +                     }
>>> +                     break;
>>> +             }
>>> +             break;
>>> +     case 3:
>>> +             switch (ichar) {
>>> +             case '~':
>>> +                     switch (cch->esc_save[2]) {
>>> +                     case '3':       /* Delete key */
>>> +                             ichar = CTL_CH('d');
>>> +                             act = ESC_CONVERTED;
>>> +                             break;  /* pass to ^D handler */
>>> +                     case '1':       /* Home key */
>>> +                     case '7':
>>> +                             ichar = CTL_CH('a');
>>> +                             act = ESC_CONVERTED;
>>> +                             break;  /* pass to ^A handler */
>>> +                     case '4':       /* End key */
>>> +                     case '8':
>>> +                             ichar = CTL_CH('e');
>>> +                             act = ESC_CONVERTED;
>>> +                             break;  /* pass to ^E handler */
>>> +                     }
>>> +                     break;
>>> +             case '0':
>>> +                     if (cch->esc_save[2] == '2')
>>> +                             act = ESC_SAVE;
>>> +                     break;
>>> +             }
>>> +             break;
>>> +     case 4:
>>> +             switch (ichar) {
>>> +             case '0':
>>> +             case '1':
>>> +                     act = ESC_SAVE;
>>> +                     break;          /* bracketed paste */
>>> +             }
>>> +             break;
>>> +     case 5:
>>> +             if (ichar == '~') {     /* bracketed paste */
>>> +                     ichar = 0;
>>> +                     act = ESC_CONVERTED;
>>> +             }
>>> +     }
>>> +
>>> +     *actp = act;
>>> +
>>> +     return act == ESC_CONVERTED ? ichar : 0;
>>> +}
>>> +
>>> +int cli_ch_process(struct cli_ch_state *cch, int ichar)
>>> +{
>>> +     /*
>>> +      * ichar=0x0 when error occurs in U-Boot getchar() or when the caller
>>> +      * wants to check if there are more characters saved in the escape
>>> +      * sequence
>>> +      */
>>> +     if (!ichar) {
>>> +             if (cch->emit_upto) {
>>> +                     if (cch->emit_upto < cch->esc_len)
>>> +                             return cch->esc_save[cch->emit_upto++];
>>> +                     cch->emit_upto = 0;
>>> +             }
>>> +             return 0;
>>> +     } else if (ichar == -ETIMEDOUT) {
>>> +             /*
>>> +              * If we are in an escape sequence but nothing has followed the
>>> +              * Escape character, then the user probably just pressed the
>>> +              * Escape key. Return it and clear the sequence.
>>> +              */
>>> +             if (cch->esc_len) {
>>> +                     cch->esc_len = 0;
>>> +                     return '\e';
>>> +             }
>>> +
>>> +             /* Otherwise there is nothing to return */
>>> +             return 0;
>>> +     }
>>> +
>>> +     if (ichar == '\n' || ichar == '\r')
>>> +             return '\n';
>>> +
>>> +     /* handle standard linux xterm esc sequences for arrow key, etc. */
>>> +     if (cch->esc_len != 0) {
>>> +             enum cli_esc_state_t act;
>>> +
>>> +             ichar = cli_ch_esc(cch, ichar, &act);
>>> +
>>> +             switch (act) {
>>> +             case ESC_SAVE:
>>> +                     /* save this character and return nothing */
>>> +                     cch->esc_save[cch->esc_len++] = ichar;
>>> +                     return 0;
>>> +             case ESC_REJECT:
>>> +                     /*
>>> +                      * invalid escape sequence, start returning the
>>> +                      * characters in it
>>> +                      */
>>> +                     cch->esc_save[cch->esc_len++] = ichar;
>>> +                     return cch->esc_save[cch->emit_upto++];
>>> +             case ESC_CONVERTED:
>>> +                     /* valid escape sequence, return the resulting char */
>>> +                     cch->esc_len = 0;
>>> +                     return ichar;
>>> +             }
>>> +     }
>>> +
>>> +     if (ichar == '\e') {
>>> +             if (!cch->esc_len) {
>>> +                     cch->esc_save[cch->esc_len] = ichar;
>>> +                     cch->esc_len = 1;
>>> +             } else {
>>> +                     puts("impossible condition #876\n");
>>> +                     cch->esc_len = 0;
>>> +             }
>>> +             return 0;
>>> +     }
>>> +
>>> +     return ichar;
>>> +}
>>> diff --git a/common/cli_readline.c b/common/cli_readline.c
>>> index d6444f5fc1d..709e9c3d38b 100644
>>> --- a/common/cli_readline.c
>>> +++ b/common/cli_readline.c
>>> @@ -62,7 +62,6 @@ static char *delete_char (char *buffer, char *p, int *colp, int *np, int plen)
>>>
>>>    #define putnstr(str, n)     printf("%.*s", (int)n, str)
>>>
>>> -#define CTL_CH(c)            ((c) - 'a' + 1)
>>>    #define CTL_BACKSPACE               ('\b')
>>>    #define DEL                 ((char)255)
>>>    #define DEL7                        ((char)127)
>>> @@ -252,156 +251,53 @@ static void cread_add_str(char *str, int strsize, int insert,
>>>    static int cread_line(const char *const prompt, char *buf, unsigned int *len,
>>>                int timeout)
>>>    {
>>> +     struct cli_ch_state s_cch, *cch = &s_cch;
>>>        unsigned long num = 0;
>>>        unsigned long eol_num = 0;
>>>        unsigned long wlen;
>>>        char ichar;
>>>        int insert = 1;
>>> -     int esc_len = 0;
>>> -     char esc_save[8];
>>>        int init_len = strlen(buf);
>>>        int first = 1;
>>>
>>> +     cli_ch_init(cch);
>>> +
>>>        if (init_len)
>>>                cread_add_str(buf, init_len, 1, &num, &eol_num, buf, *len);
>>>
>>>        while (1) {
>>> -             if (bootretry_tstc_timeout())
>>> -                     return -2;      /* timed out */
>>> -             if (first && timeout) {
>>> -                     uint64_t etime = endtick(timeout);
>>> -
>>> -                     while (!tstc()) {       /* while no incoming data */
>>> -                             if (get_ticks() >= etime)
>>> -                                     return -2;      /* timed out */
>>> -                             schedule();
>>> +             /* Check for saved characters */
>>> +             ichar = cli_ch_process(cch, 0);
>>> +
>>> +             if (!ichar) {
>>> +                     if (bootretry_tstc_timeout())
>>> +                             return -2;      /* timed out */
>>> +                     if (first && timeout) {
>>> +                             u64 etime = endtick(timeout);
>>> +
>>> +                             while (!tstc()) {       /* while no incoming data */
>>> +                                     if (get_ticks() >= etime)
>>> +                                             return -2;      /* timed out */
>>> +                                     schedule();
>>> +                             }
>>> +                             first = 0;
>>>                        }
>>> -                     first = 0;
>>> +
>>> +                     ichar = getcmd_getch();
>>>                }
>>>
>>> -             ichar = getcmd_getch();
>>> +             ichar = cli_ch_process(cch, ichar);
>>>
>>>                /* ichar=0x0 when error occurs in U-Boot getc */
>>>                if (!ichar)
>>>                        continue;
>>>
>>> -             if ((ichar == '\n') || (ichar == '\r')) {
>>> +             if (ichar == '\n') {
>>>                        putc('\n');
>>>                        break;
>>>                }
>>>
>>> -             /*
>>> -              * handle standard linux xterm esc sequences for arrow key, etc.
>>> -              */
>>> -             if (esc_len != 0) {
>>> -                     enum { ESC_REJECT, ESC_SAVE, ESC_CONVERTED } act = ESC_REJECT;
>>> -
>>> -                     if (esc_len == 1) {
>>> -                             if (ichar == '[' || ichar == 'O')
>>> -                                     act = ESC_SAVE;
>>> -                     } else if (esc_len == 2) {
>>> -                             switch (ichar) {
>>> -                             case 'D':       /* <- key */
>>> -                                     ichar = CTL_CH('b');
>>> -                                     act = ESC_CONVERTED;
>>> -                                     break;  /* pass off to ^B handler */
>>
>> We have similar code to decode escape sequences and Unicode letters in
>> efi_cin_read_key_stroke_ex(). The big difference in the EFI code is that
>> it does not use dummy control characters but uses a structure:
>>
>> struct efi_key_data {
>>           struct efi_input_key key;
>>           struct efi_key_state key_state;
>> };
>>
>> with
>>
>> struct efi_input_key {
>>           u16 scan_code;
>>           s16 unicode_char;
>> };
>>
>> The special keys are represented as numbers in scan_code while
>> characters are represented in unicode_char.
>>
>> Separating special keys from characters is much cleaner than creating
>> dummy control characters.
>>
>> Anyway you would be able to first create the structure above and then in
>> a separate step to convert to control characters.
>
> I thought this discussion started in the last version of the series.
> Do we need to support unicode input? How would that work in U-Boot,
> since it seems to only be in the EFI layer at present?

File systems like FAT and EXT4 use Unicode.

>
>>
>> The code below misses to interprete some special keys like FN1 - FN12
>> which could be quite useful for navigating a UI.
>
> Can we add them later? I don't have any use for them at present.

We can work on the unification of those codes later.

Best regards

Heinrich

>
> Regards,
> Simon


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

* Re: [PATCH v3 03/25] bootmenu: Add a few comments
  2023-01-07  0:13     ` Simon Glass
@ 2023-01-07 22:34       ` Heinrich Schuchardt
  2023-01-07 22:54         ` Simon Glass
  0 siblings, 1 reply; 40+ messages in thread
From: Heinrich Schuchardt @ 2023-01-07 22:34 UTC (permalink / raw)
  To: Simon Glass; +Cc: Anatolij Gustschin, Tom Rini, U-Boot Mailing List

On 1/7/23 01:13, Simon Glass wrote:
> Hi Heinrich,
>
> On Fri, 6 Jan 2023 at 09:53, Heinrich Schuchardt <xypron.glpk@gmx.de> wrote:
>>
>> On 1/6/23 15:52, Simon Glass wrote:
>>> The behaviour of these two functions is completely undocumented. Add some
>>> notes so the poor, suffering dev can figure out what is going on.
>>>
>>> Signed-off-by: Simon Glass <sjg@chromium.org>
>>> ---
>>>
>>> (no changes since v1)
>>>
>>>    include/menu.h | 42 ++++++++++++++++++++++++++++++++++++++++++
>>>    1 file changed, 42 insertions(+)
>>>
>>> diff --git a/include/menu.h b/include/menu.h
>>> index 702aacb170c..0b4d9734149 100644
>>> --- a/include/menu.h
>>> +++ b/include/menu.h
>>> @@ -42,6 +42,7 @@ struct bootmenu_data {
>>>        struct bootmenu_entry *first;   /* first menu entry */
>>>    };
>>>
>>> +/** enum bootmenu_key - keys that can be returned by the bootmenu */
>>>    enum bootmenu_key {
>>>        KEY_NONE = 0,
>>>        KEY_UP,
>>> @@ -53,8 +54,49 @@ enum bootmenu_key {
>>>        KEY_SPACE,
>>>    };
>>>
>>> +/**
>>> + * bootmenu_autoboot_loop() - handle autobooting if no key is pressed
>>> + *
>>> + * This shows a prompt to allow the user to press a key to interrupt auto boot
>>> + * of the first menu option.
>>> + *
>>> + * It then waits for the required time (menu->delay in seconds) for a key to be
>>> + * pressed. If nothing is pressed in that time, @key returns KEY_SELECT
>>> + * indicating that the current option should be chosen.
>>> + *
>>> + * @menu: Menu being processed
>>> + * @key: Returns the code for the key the user pressed:
>>> + *   enter: KEY_SELECT
>>> + *   Ctrl-C: KEY_QUIT
>>> + *   anything else: KEY_NONE
>>> + * @esc: Set to 1 if the escape key is pressed, otherwise not updated
>>> + */
>>>    void bootmenu_autoboot_loop(struct bootmenu_data *menu,
>>>                            enum bootmenu_key *key, int *esc);
>>> +
>>> +/**
>>> + * bootmenu_loop() - handle waiting for a keypress when autoboot is disabled
>>> + *
>>> + * This is used when the menu delay is negative, indicating that the delay has
>>> + * elapsed, or there was no delay to begin with.
>>> + *
>>> + * It reads a character and processes it, returning a menu-key code if a
>>> + * character is recognised
>>> + *
>>> + * @menu: Menu being processed
>>> + * @key: Returns the code for the key the user pressed:
>>> + *   enter: KEY_SELECT
>>> + *   Ctrl-C: KEY_QUIT
>>> + *   Up arrow: KEY_UP
>>> + *   Down arrow: KEY_DOWN
>>> + *   Escape (by itself): KEY_QUIT
>>> + *   Plus: KEY_PLUS
>>> + *   Minus: KEY_MINUS
>>> + *   Space: KEY_SPACE
>>> + * @esc: On input, a non-zero value indicates that an escape sequence has
>>> + *   resulted in that many characters so far. On exit this is updated to the
>>> + *   new number of characters
>>> + */
>>>    void bootmenu_loop(struct bootmenu_data *menu,
>>>                   enum bootmenu_key *key, int *esc);
>>
>> This should not be an exported function. The program part that wants to
>> display a menu should not have to deal with navigation keys.
>
> This is a patch to add comments to code which is not otherwise
> addressed in this series. We need comments so that we know what things
> do. We can always adjust the logic later.
>
> My intent with this series is to add a new menu which is more flexible.

We should take care that the new menu is not using this design.

>
> Regards,
> Simon

This series should be of interest for you too:

https://patchwork.ozlabs.org/project/uboot/list/?series=335054
eficonfig: add vertical scroll support and refactoring

Best regards

Heinrich


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

* Re: [PATCH v3 03/25] bootmenu: Add a few comments
  2023-01-07 22:34       ` Heinrich Schuchardt
@ 2023-01-07 22:54         ` Simon Glass
  0 siblings, 0 replies; 40+ messages in thread
From: Simon Glass @ 2023-01-07 22:54 UTC (permalink / raw)
  To: Heinrich Schuchardt; +Cc: Anatolij Gustschin, Tom Rini, U-Boot Mailing List

Hi Heinrich,

On Sat, 7 Jan 2023 at 15:35, Heinrich Schuchardt <xypron.glpk@gmx.de> wrote:
>
> On 1/7/23 01:13, Simon Glass wrote:
> > Hi Heinrich,
> >
> > On Fri, 6 Jan 2023 at 09:53, Heinrich Schuchardt <xypron.glpk@gmx.de> wrote:
> >>
> >> On 1/6/23 15:52, Simon Glass wrote:
> >>> The behaviour of these two functions is completely undocumented. Add some
> >>> notes so the poor, suffering dev can figure out what is going on.
> >>>
> >>> Signed-off-by: Simon Glass <sjg@chromium.org>
> >>> ---
> >>>
> >>> (no changes since v1)
> >>>
> >>>    include/menu.h | 42 ++++++++++++++++++++++++++++++++++++++++++
> >>>    1 file changed, 42 insertions(+)
> >>>
> >>> diff --git a/include/menu.h b/include/menu.h
> >>> index 702aacb170c..0b4d9734149 100644
> >>> --- a/include/menu.h
> >>> +++ b/include/menu.h
> >>> @@ -42,6 +42,7 @@ struct bootmenu_data {
> >>>        struct bootmenu_entry *first;   /* first menu entry */
> >>>    };
> >>>
> >>> +/** enum bootmenu_key - keys that can be returned by the bootmenu */
> >>>    enum bootmenu_key {
> >>>        KEY_NONE = 0,
> >>>        KEY_UP,
> >>> @@ -53,8 +54,49 @@ enum bootmenu_key {
> >>>        KEY_SPACE,
> >>>    };
> >>>
> >>> +/**
> >>> + * bootmenu_autoboot_loop() - handle autobooting if no key is pressed
> >>> + *
> >>> + * This shows a prompt to allow the user to press a key to interrupt auto boot
> >>> + * of the first menu option.
> >>> + *
> >>> + * It then waits for the required time (menu->delay in seconds) for a key to be
> >>> + * pressed. If nothing is pressed in that time, @key returns KEY_SELECT
> >>> + * indicating that the current option should be chosen.
> >>> + *
> >>> + * @menu: Menu being processed
> >>> + * @key: Returns the code for the key the user pressed:
> >>> + *   enter: KEY_SELECT
> >>> + *   Ctrl-C: KEY_QUIT
> >>> + *   anything else: KEY_NONE
> >>> + * @esc: Set to 1 if the escape key is pressed, otherwise not updated
> >>> + */
> >>>    void bootmenu_autoboot_loop(struct bootmenu_data *menu,
> >>>                            enum bootmenu_key *key, int *esc);
> >>> +
> >>> +/**
> >>> + * bootmenu_loop() - handle waiting for a keypress when autoboot is disabled
> >>> + *
> >>> + * This is used when the menu delay is negative, indicating that the delay has
> >>> + * elapsed, or there was no delay to begin with.
> >>> + *
> >>> + * It reads a character and processes it, returning a menu-key code if a
> >>> + * character is recognised
> >>> + *
> >>> + * @menu: Menu being processed
> >>> + * @key: Returns the code for the key the user pressed:
> >>> + *   enter: KEY_SELECT
> >>> + *   Ctrl-C: KEY_QUIT
> >>> + *   Up arrow: KEY_UP
> >>> + *   Down arrow: KEY_DOWN
> >>> + *   Escape (by itself): KEY_QUIT
> >>> + *   Plus: KEY_PLUS
> >>> + *   Minus: KEY_MINUS
> >>> + *   Space: KEY_SPACE
> >>> + * @esc: On input, a non-zero value indicates that an escape sequence has
> >>> + *   resulted in that many characters so far. On exit this is updated to the
> >>> + *   new number of characters
> >>> + */
> >>>    void bootmenu_loop(struct bootmenu_data *menu,
> >>>                   enum bootmenu_key *key, int *esc);
> >>
> >> This should not be an exported function. The program part that wants to
> >> display a menu should not have to deal with navigation keys.
> >
> > This is a patch to add comments to code which is not otherwise
> > addressed in this series. We need comments so that we know what things
> > do. We can always adjust the logic later.
> >
> > My intent with this series is to add a new menu which is more flexible.
>
> We should take care that the new menu is not using this design.
>

Yes, the new menu does not use any of the code here.

This patch is purely to add comments.

I wonder whether the expo menu might eventually replace the current
menu, since the expo one does support text mode too.

>
> This series should be of interest for you too:
>
> https://patchwork.ozlabs.org/project/uboot/list/?series=335054
> eficonfig: add vertical scroll support and refactoring

OK ta. Scrolling is another thing not supported by expo.

Regards,
Simon

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

* Re: [PATCH v3 00/25] bootstd: Add a boot menu
  2023-01-06 14:52 [PATCH v3 00/25] bootstd: Add a boot menu Simon Glass
                   ` (24 preceding siblings ...)
  2023-01-06 14:52 ` [PATCH v3 25/25] expo: Add documentation Simon Glass
@ 2023-01-17 13:57 ` Tom Rini
  25 siblings, 0 replies; 40+ messages in thread
From: Tom Rini @ 2023-01-17 13:57 UTC (permalink / raw)
  To: Simon Glass
  Cc: U-Boot Mailing List, Anatolij Gustschin, Heinrich Schuchardt,
	Joe Hershberger, Marek Vasut

[-- Attachment #1: Type: text/plain, Size: 2048 bytes --]

On Fri, Jan 06, 2023 at 08:52:18AM -0600, Simon Glass wrote:

> So far standard boot lacks a boot menu, although it is possible to create
> a rudimentary one using the existing 'bootmenu' command.
> 
> Even then, this text-based menu offer only basic functionality and does
> not take full advantage of the displays which are common on many devices.
> 
> This series provides a 'bootflow menu' command which allows the user to
> select from the available bootflows. An attempt is made to show the name
> of the available operating systems, by reading more information into the
> bootflow. A logo can be read also, where supported, so that this can be
> presented to the user when an option is highlighted.
> 
> Full use is made of TrueType fonts, if enabled. For cases where only a
> serial console is available, it falls back to a simple text-based menu.
> 
> All of this is implementing using a new 'expo' construct, a collection of
> scenes (like menu screens) which can be navigated by the user to view
> information and select options. This is fairly general and should be able
> to cope with a wider array of use cases, with less hacking of the menu
> code, such as is currently needed for CMD_BOOTEFI_BOOTMGR.
> 
> Of course it would be possible to enhance the existing menu rather than
> creating a new setup. Instead it seems better to make the existing menu
> use expo, if code space permits. It avoids the event-loop problem and
> should be more extensible, given its loosely coupled components and use of
> IDs instead of pointers. Further motivation is provided in the
> documentation.
> 
> For now the CLI keypress-decoding code is split out to be used by the new
> menu. The key codes defined by menu.h are reused also.
> 
> This is of course just a starting point. Some ideas for future work are
> included in the documentation.
> 
> Changes in v3:
> - Rebase to master

With a minor change to add a doc comment to include/expo.h, this has
been applied to u-boot/master, thanks!

-- 
Tom

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 659 bytes --]

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

* [BUG] Re: [PATCH v3 02/25] cli: Move readline character-processing to a state machine
  2023-01-06 14:52 ` [PATCH v3 02/25] cli: Move readline character-processing to a state machine Simon Glass
  2023-01-06 15:50   ` Heinrich Schuchardt
@ 2023-01-24 15:19   ` Heinrich Schuchardt
  2023-01-28 22:01     ` Simon Glass
  1 sibling, 1 reply; 40+ messages in thread
From: Heinrich Schuchardt @ 2023-01-24 15:19 UTC (permalink / raw)
  To: Simon Glass
  Cc: Anatolij Gustschin, Tom Rini, U-Boot Mailing List,
	Masahisa Kojima, Ilias Apalodimas

On 1/6/23 15:52, Simon Glass wrote:
> The current cread_line() function is very long. It handles the escape
> processing inline. The menu command does similar processing but at the
> character level, so there is some duplication.
> 
> Split the character processing into a new function cli_ch_process() which
> processes individual characters and returns the resulting input character,
> taking account of escape sequences. It requires the caller to set up and
> maintain its state.
> 
> Update cread_line() to use this new function.
> 
> The only intended functional change is that an invalid escape sequence
> does not add invalid/control characters into the input buffer, but instead
> discards these.
> 
> Signed-off-by: Simon Glass <sjg@chromium.org>
> ---
> 

Hello Simon

in the Change Boot Order menu of the eficonfig command I hit the 
PAGE-UP or PAGE-DOWN button and get this output:

       [ ]  label0038
       [ ]  label0039impossible condition #876
impossible condition #876
   Press UP/DOWN to move, +/- to change orde
   Press SPACE to activate or deactivate the entry
   Select [Save] to complete, ESC/CTRL+C to quit

Hitting an unsupported key should not result in output.

This is the line providing the output.

common/cli_getch.c:201:
puts("impossible condition #876\n");

How line be reached by an "impossible condition". That text does not 
make any sense.

This is the patch to blame

b08e9d4b6632 ("cli: Move readline character-processing to a state machine")

Please, remove that debug message.

The conitrace command shows these escape sequences:

PAGE-DOWN 1b 5b 36 7e
PAGE-UP 1b 5b 35 7e

Best regards

Heinrich


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

* Re: [BUG] Re: [PATCH v3 02/25] cli: Move readline character-processing to a state machine
  2023-01-24 15:19   ` [BUG] " Heinrich Schuchardt
@ 2023-01-28 22:01     ` Simon Glass
  0 siblings, 0 replies; 40+ messages in thread
From: Simon Glass @ 2023-01-28 22:01 UTC (permalink / raw)
  To: Heinrich Schuchardt
  Cc: Anatolij Gustschin, Tom Rini, U-Boot Mailing List,
	Masahisa Kojima, Ilias Apalodimas

Hi Heinrich,

On Tue, 24 Jan 2023 at 08:19, Heinrich Schuchardt
<heinrich.schuchardt@canonical.com> wrote:
>
> On 1/6/23 15:52, Simon Glass wrote:
> > The current cread_line() function is very long. It handles the escape
> > processing inline. The menu command does similar processing but at the
> > character level, so there is some duplication.
> >
> > Split the character processing into a new function cli_ch_process() which
> > processes individual characters and returns the resulting input character,
> > taking account of escape sequences. It requires the caller to set up and
> > maintain its state.
> >
> > Update cread_line() to use this new function.
> >
> > The only intended functional change is that an invalid escape sequence
> > does not add invalid/control characters into the input buffer, but instead
> > discards these.
> >
> > Signed-off-by: Simon Glass <sjg@chromium.org>
> > ---
> >
>
> Hello Simon
>
> in the Change Boot Order menu of the eficonfig command I hit the
> PAGE-UP or PAGE-DOWN button and get this output:
>
>        [ ]  label0038
>        [ ]  label0039impossible condition #876
> impossible condition #876
>    Press UP/DOWN to move, +/- to change orde
>    Press SPACE to activate or deactivate the entry
>    Select [Save] to complete, ESC/CTRL+C to quit
>
> Hitting an unsupported key should not result in output.
>
> This is the line providing the output.
>
> common/cli_getch.c:201:
> puts("impossible condition #876\n");
>
> How line be reached by an "impossible condition". That text does not
> make any sense.
>
> This is the patch to blame
>
> b08e9d4b6632 ("cli: Move readline character-processing to a state machine")
>
> Please, remove that debug message.

This was already in the code. I just split it out into a separate file.

>
> The conitrace command shows these escape sequences:
>
> PAGE-DOWN 1b 5b 36 7e
> PAGE-UP 1b 5b 35 7e

I believe it indicates that the sequence is not understood. It is
therefore rejected and sent out as an escape sequence to the terminal.

This works by calling cli_ch_process() with a character of 0, to find
out if there is a rejected sequence to be emitted. Only once it
returns 0 should getchar() be called. See the detailed function
comments for how it works.

From what I can see, bootmenu_autoboot_loop() is violating that.
Probably that is what is going on. The message is correct in that the
condition cannot happen if the caller is behaving correctly.

In any case it seems that these keys are not supported, so in addition
to fixing the bug you found, we need to add support for them to
cli_ch_esc().


>
> Best regards
>
> Heinrich
>

Regards,
Simon

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

* Re: [PATCH v3 08/25] menu: Make use of CLI character processing
  2023-01-06 14:52 ` [PATCH v3 08/25] menu: Make use of CLI character processing Simon Glass
@ 2023-04-11 20:19   ` Daniel Golle
  2023-04-19  1:49     ` Simon Glass
  0 siblings, 1 reply; 40+ messages in thread
From: Daniel Golle @ 2023-04-11 20:19 UTC (permalink / raw)
  To: Simon Glass
  Cc: U-Boot Mailing List, Anatolij Gustschin, Tom Rini, Heinrich Schuchardt

On Fri, Jan 06, 2023 at 08:52:26AM -0600, Simon Glass wrote:
> Avoid duplicating some of the escape-sequence processing here and use the
> CLI function instead.
> 
> Signed-off-by: Simon Glass <sjg@chromium.org>
> ---
> 
> [...]
> diff --git a/common/menu.c b/common/menu.c
> index 7db98942a61..45f36ae3ede 100644
> --- a/common/menu.c
> +++ b/common/menu.c
> @@ -15,6 +15,8 @@
>  
>  #include "menu.h"
>  
> +#define ansi 0

Now that I'm using U-Boot 2023.04 I noticed that this introduces a (cosmetic)
regression: The autoboot countdown in bootmenu is now longer re-written, but
instead the last menu item line is continously appended like:

U-Boot consoleHit any key to stop autoboot: 3 Hit any key to stop autoboot: 2

To regain the previous behavior one should change the line to
#define ansi 1

To me this looks a bit unfinished, as if there was some sort of
probing of the terminal capabilities supposed to happen but then
this has never been implemeneted...

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

* Re: [PATCH v3 08/25] menu: Make use of CLI character processing
  2023-04-11 20:19   ` Daniel Golle
@ 2023-04-19  1:49     ` Simon Glass
  2023-04-24  1:49       ` Tom Rini
  0 siblings, 1 reply; 40+ messages in thread
From: Simon Glass @ 2023-04-19  1:49 UTC (permalink / raw)
  To: Daniel Golle
  Cc: U-Boot Mailing List, Anatolij Gustschin, Tom Rini, Heinrich Schuchardt

Hi Daniel,

On Tue, 11 Apr 2023 at 14:19, Daniel Golle <daniel@makrotopia.org> wrote:
>
> On Fri, Jan 06, 2023 at 08:52:26AM -0600, Simon Glass wrote:
> > Avoid duplicating some of the escape-sequence processing here and use the
> > CLI function instead.
> >
> > Signed-off-by: Simon Glass <sjg@chromium.org>
> > ---
> >
> > [...]
> > diff --git a/common/menu.c b/common/menu.c
> > index 7db98942a61..45f36ae3ede 100644
> > --- a/common/menu.c
> > +++ b/common/menu.c
> > @@ -15,6 +15,8 @@
> >
> >  #include "menu.h"
> >
> > +#define ansi 0
>
> Now that I'm using U-Boot 2023.04 I noticed that this introduces a (cosmetic)
> regression: The autoboot countdown in bootmenu is now longer re-written, but
> instead the last menu item line is continously appended like:
>
> U-Boot consoleHit any key to stop autoboot: 3 Hit any key to stop autoboot: 2
>
> To regain the previous behavior one should change the line to
> #define ansi 1

Yes, perhaps you could send a patch?

>
> To me this looks a bit unfinished, as if there was some sort of
> probing of the terminal capabilities supposed to happen but then
> this has never been implemeneted...

I have expressed the review that ANSI should be something we can
enable or disable, rather than always having it enabled. For tests in
particular, ANSI is a pain since it messes up the logs with escape
sequences and it is hard to see what is happening.

IMO we should have an ANSI library which you call to emit sequences.
Or perhaps we could try to be clever and build them into printf() as
special strings, so long as we can avoid compiler warnings.

Regards,
SImon

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

* Re: [PATCH v3 08/25] menu: Make use of CLI character processing
  2023-04-19  1:49     ` Simon Glass
@ 2023-04-24  1:49       ` Tom Rini
  2023-04-24 19:42         ` Simon Glass
  0 siblings, 1 reply; 40+ messages in thread
From: Tom Rini @ 2023-04-24  1:49 UTC (permalink / raw)
  To: Simon Glass
  Cc: Daniel Golle, U-Boot Mailing List, Anatolij Gustschin,
	Heinrich Schuchardt

[-- Attachment #1: Type: text/plain, Size: 2150 bytes --]

On Tue, Apr 18, 2023 at 07:49:34PM -0600, Simon Glass wrote:
> Hi Daniel,
> 
> On Tue, 11 Apr 2023 at 14:19, Daniel Golle <daniel@makrotopia.org> wrote:
> >
> > On Fri, Jan 06, 2023 at 08:52:26AM -0600, Simon Glass wrote:
> > > Avoid duplicating some of the escape-sequence processing here and use the
> > > CLI function instead.
> > >
> > > Signed-off-by: Simon Glass <sjg@chromium.org>
> > > ---
> > >
> > > [...]
> > > diff --git a/common/menu.c b/common/menu.c
> > > index 7db98942a61..45f36ae3ede 100644
> > > --- a/common/menu.c
> > > +++ b/common/menu.c
> > > @@ -15,6 +15,8 @@
> > >
> > >  #include "menu.h"
> > >
> > > +#define ansi 0
> >
> > Now that I'm using U-Boot 2023.04 I noticed that this introduces a (cosmetic)
> > regression: The autoboot countdown in bootmenu is now longer re-written, but
> > instead the last menu item line is continously appended like:
> >
> > U-Boot consoleHit any key to stop autoboot: 3 Hit any key to stop autoboot: 2
> >
> > To regain the previous behavior one should change the line to
> > #define ansi 1
> 
> Yes, perhaps you could send a patch?

As a new CONFIG symbol, default y for the old behavior please.

> > To me this looks a bit unfinished, as if there was some sort of
> > probing of the terminal capabilities supposed to happen but then
> > this has never been implemeneted...
> 
> I have expressed the review that ANSI should be something we can
> enable or disable, rather than always having it enabled. For tests in
> particular, ANSI is a pain since it messes up the logs with escape
> sequences and it is hard to see what is happening.
> 
> IMO we should have an ANSI library which you call to emit sequences.
> Or perhaps we could try to be clever and build them into printf() as
> special strings, so long as we can avoid compiler warnings.

Well, I think for starting we can just make the behavior configurable at
build time. I'm not sure we need to do run-time (not clear if you're
suggesting that or not), and I don't know if code changes to deal with
ANSI-or-not would result in some unreasonable code growth here.

-- 
Tom

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 659 bytes --]

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

* Re: [PATCH v3 08/25] menu: Make use of CLI character processing
  2023-04-24  1:49       ` Tom Rini
@ 2023-04-24 19:42         ` Simon Glass
  0 siblings, 0 replies; 40+ messages in thread
From: Simon Glass @ 2023-04-24 19:42 UTC (permalink / raw)
  To: Tom Rini
  Cc: Daniel Golle, U-Boot Mailing List, Anatolij Gustschin,
	Heinrich Schuchardt

Hi,

On Sun, 23 Apr 2023 at 19:49, Tom Rini <trini@konsulko.com> wrote:
>
> On Tue, Apr 18, 2023 at 07:49:34PM -0600, Simon Glass wrote:
> > Hi Daniel,
> >
> > On Tue, 11 Apr 2023 at 14:19, Daniel Golle <daniel@makrotopia.org> wrote:
> > >
> > > On Fri, Jan 06, 2023 at 08:52:26AM -0600, Simon Glass wrote:
> > > > Avoid duplicating some of the escape-sequence processing here and use the
> > > > CLI function instead.
> > > >
> > > > Signed-off-by: Simon Glass <sjg@chromium.org>
> > > > ---
> > > >
> > > > [...]
> > > > diff --git a/common/menu.c b/common/menu.c
> > > > index 7db98942a61..45f36ae3ede 100644
> > > > --- a/common/menu.c
> > > > +++ b/common/menu.c
> > > > @@ -15,6 +15,8 @@
> > > >
> > > >  #include "menu.h"
> > > >
> > > > +#define ansi 0
> > >
> > > Now that I'm using U-Boot 2023.04 I noticed that this introduces a (cosmetic)
> > > regression: The autoboot countdown in bootmenu is now longer re-written, but
> > > instead the last menu item line is continously appended like:
> > >
> > > U-Boot consoleHit any key to stop autoboot: 3 Hit any key to stop autoboot: 2
> > >
> > > To regain the previous behavior one should change the line to
> > > #define ansi 1
> >
> > Yes, perhaps you could send a patch?
>
> As a new CONFIG symbol, default y for the old behavior please.
>
> > > To me this looks a bit unfinished, as if there was some sort of
> > > probing of the terminal capabilities supposed to happen but then
> > > this has never been implemeneted...
> >
> > I have expressed the review that ANSI should be something we can
> > enable or disable, rather than always having it enabled. For tests in
> > particular, ANSI is a pain since it messes up the logs with escape
> > sequences and it is hard to see what is happening.
> >
> > IMO we should have an ANSI library which you call to emit sequences.
> > Or perhaps we could try to be clever and build them into printf() as
> > special strings, so long as we can avoid compiler warnings.
>
> Well, I think for starting we can just make the behavior configurable at
> build time. I'm not sure we need to do run-time (not clear if you're
> suggesting that or not), and I don't know if code changes to deal with
> ANSI-or-not would result in some unreasonable code growth here.

My main goal is to avoid this stuff in test logs, so mostly sandbox,
and having it configurable at runtime (for sandbox) would be ideal.

Making it build-time configuration is fine as a starting point.

Regards,
Simon

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

end of thread, other threads:[~2023-04-24 19:44 UTC | newest]

Thread overview: 40+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2023-01-06 14:52 [PATCH v3 00/25] bootstd: Add a boot menu Simon Glass
2023-01-06 14:52 ` [PATCH v3 01/25] sandbox: Enable mmc command and legacy images Simon Glass
2023-01-06 14:52 ` [PATCH v3 02/25] cli: Move readline character-processing to a state machine Simon Glass
2023-01-06 15:50   ` Heinrich Schuchardt
2023-01-07  0:13     ` Simon Glass
2023-01-07 22:31       ` Heinrich Schuchardt
2023-01-24 15:19   ` [BUG] " Heinrich Schuchardt
2023-01-28 22:01     ` Simon Glass
2023-01-06 14:52 ` [PATCH v3 03/25] bootmenu: Add a few comments Simon Glass
2023-01-06 15:53   ` Heinrich Schuchardt
2023-01-07  0:13     ` Simon Glass
2023-01-07 22:34       ` Heinrich Schuchardt
2023-01-07 22:54         ` Simon Glass
2023-01-06 14:52 ` [PATCH v3 04/25] menu: Rename KEY_... to BKEY_ Simon Glass
2023-01-06 14:52 ` [PATCH v3 05/25] menu: Update bootmenu_autoboot_loop() to return the code Simon Glass
2023-01-06 14:52 ` [PATCH v3 06/25] menu: Update bootmenu_loop() " Simon Glass
2023-01-06 14:52 ` [PATCH v3 07/25] menu: Use a switch statement Simon Glass
2023-01-06 14:52 ` [PATCH v3 08/25] menu: Make use of CLI character processing Simon Glass
2023-04-11 20:19   ` Daniel Golle
2023-04-19  1:49     ` Simon Glass
2023-04-24  1:49       ` Tom Rini
2023-04-24 19:42         ` Simon Glass
2023-01-06 14:52 ` [PATCH v3 09/25] image: Add a function to find a script in an image Simon Glass
2023-01-06 14:52 ` [PATCH v3 10/25] image: Move common image code to image_board and command Simon Glass
2023-01-06 14:52 ` [PATCH v3 11/25] video: Enable VIDEO_ANSI by default only with EFI Simon Glass
2023-01-06 14:52 ` [PATCH v3 12/25] video: truetype: Rename the metrics function Simon Glass
2023-01-06 14:52 ` [PATCH v3 13/25] video: Fix unchnaged typo Simon Glass
2023-01-06 14:52 ` [PATCH v3 14/25] video: Add font functions to the vidconsole API Simon Glass
2023-01-06 14:52 ` [PATCH v3 15/25] bootstd: Read the Operating System name for distro/scripts Simon Glass
2023-01-06 14:52 ` [PATCH v3 16/25] bootstd: Allow reading a logo for the OS Simon Glass
2023-01-06 14:52 ` [PATCH v3 17/25] menu: Factor out menu-keypress decoding Simon Glass
2023-01-06 14:52 ` [PATCH v3 18/25] expo: Add basic implementation Simon Glass
2023-01-06 14:52 ` [PATCH v3 19/25] expo: Add support for scenes Simon Glass
2023-01-06 14:52 ` [PATCH v3 20/25] expo: Add support for scene menus Simon Glass
2023-01-06 14:52 ` [PATCH v3 21/25] expo: Add basic tests Simon Glass
2023-01-06 14:52 ` [PATCH v3 22/25] bootstd: Support creating a boot menu Simon Glass
2023-01-06 14:52 ` [PATCH v3 23/25] bootstd: Add a test for the bootstd menu Simon Glass
2023-01-06 14:52 ` [PATCH v3 24/25] bootstd: Support setting a theme for the menu Simon Glass
2023-01-06 14:52 ` [PATCH v3 25/25] expo: Add documentation Simon Glass
2023-01-17 13:57 ` [PATCH v3 00/25] bootstd: Add a boot menu Tom Rini

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.