linux-efi.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v3 0/9] efi/libstub: Avoid UTF-16 conversion busywork
@ 2026-09-16 14:46 Ard Biesheuvel
  2026-09-16 14:46 ` [PATCH v3 1/9] lib/ucs2_string: Drop arbitrary input size limit and associated WARN() Ard Biesheuvel
                   ` (8 more replies)
  0 siblings, 9 replies; 10+ messages in thread
From: Ard Biesheuvel @ 2026-09-16 14:46 UTC (permalink / raw)
  To: linux-efi; +Cc: linux-kernel, Ard Biesheuvel, Vincent Mailhol

From: Ard Biesheuvel <ardb@kernel.org>

The EFI libstub performs some redundant conversions between UTF-16 and
UTF-8 and back again, which includes dealing with surrogate pairs, which
UEFI implementations themselves simply ignore.

So drop all the homegrown code, and use the existing UCS-2 (== UTF-16
without surrogate pairs) library code where conversion to UTF-8 is
actually needed (the kernel command line).

The remaining handling involves the EFI console, which supports wide
characters natively, so just use those directly.

Changes since v2 [1]:
- drop x86 specific patch, it has been split out into a separate series
  [2]
- handle truncated output of ucs2_as_utf8() in the middle of a multibyte
  character correctly
- improve ucs2_as_utf8() documentation
- tweaks to the commit logs

Changes since v1 [0]:
- drop size limit from ucs2_strscpy() instead of just the WARN()
- suppress modinfo sections from ucs2_string when __DISABLE_EXPORTS is
  defined
- allow the input limit and max output size to be passed separately to
  ucs2_to_utf8()
- reimplement efi_convert_cmdline() to optimize the common case, and
  only process the input character by character if its size exceeds
  COMMAND_LINE_SIZE
- use memcpy() to avoid strscpy() semantics in handling of %ls
- incorporate Vincent's patch (which inspired this work) for
  completeness
	
Cc: Vincent Mailhol <mailhol@kernel.org>

[0] https://lore.kernel.org/all/20260906130817.1151961-9-ardb@kernel.org
[1] http://lore.kernel.org/r/20260909115530.1924665-12-ardb+git@google.com
[2] http://lore.kernel.org/r/20260914183745.37538-5-ardb@kernel.org

Ard Biesheuvel (8):
  lib/ucs2_string: Drop arbitrary input size limit and associated WARN()
  lib/ucs2_string: Suppress modinfo when __DISABLE_EXPORTS is set
  lib/ucs2_string: Split out ucs2_as_utf8_l() taking a separate limit
  efi/libstub: Use ucs2_string library for UTF-16 to UTF-8 conversion
  efi/libstub: Avoid efi_puts() for compile time constant strings
  efi/libstub: Output UTF-16 directly from vsnprintf()
  efi/libstub: Add support for printing human readable GUIDs
  efi/libstub: Add efi_snprintf() to construct wide strings

Vincent Mailhol (1):
  efi/libstub: add initial Boot Loader Interface support

 drivers/firmware/efi/libstub/Makefile          |   3 +-
 drivers/firmware/efi/libstub/bli.c             |  87 ++++++++++++
 drivers/firmware/efi/libstub/efi-stub-helper.c |  98 +++++--------
 drivers/firmware/efi/libstub/efi-stub.c        |   1 +
 drivers/firmware/efi/libstub/efistub.h         |   8 +-
 drivers/firmware/efi/libstub/gop.c             |   6 +-
 drivers/firmware/efi/libstub/printk.c          |  95 ++-----------
 drivers/firmware/efi/libstub/vsprintf.c        | 149 +++++++-------------
 drivers/firmware/efi/libstub/x86-stub.c        |   1 +
 include/linux/efi.h                            |  22 +++
 include/linux/ucs2_string.h                    |  11 +-
 lib/ucs2_string.c                              |  19 +--
 12 files changed, 242 insertions(+), 258 deletions(-)
 create mode 100644 drivers/firmware/efi/libstub/bli.c

-- 
2.55.0.1032.g73a4cd73de-goog


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

* [PATCH v3 1/9] lib/ucs2_string: Drop arbitrary input size limit and associated WARN()
  2026-09-16 14:46 [PATCH v3 0/9] efi/libstub: Avoid UTF-16 conversion busywork Ard Biesheuvel
@ 2026-09-16 14:46 ` Ard Biesheuvel
  2026-09-16 14:46 ` [PATCH v3 2/9] lib/ucs2_string: Suppress modinfo when __DISABLE_EXPORTS is set Ard Biesheuvel
                   ` (7 subsequent siblings)
  8 siblings, 0 replies; 10+ messages in thread
From: Ard Biesheuvel @ 2026-09-16 14:46 UTC (permalink / raw)
  To: linux-efi; +Cc: linux-kernel, Ard Biesheuvel, Vincent Mailhol

From: Ard Biesheuvel <ardb@kernel.org>

It's not really the job of library code to WARN and potentially bring
down the system (with panic_on_warn=1) on a condition that is fairly
arbitrary to begin with.

So drop the WARN_ON_ONCE() as well as the condition from ucs2_strscpy().

Signed-off-by: Ard Biesheuvel <ardb@kernel.org>
---
 lib/ucs2_string.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lib/ucs2_string.c b/lib/ucs2_string.c
index 1f7dd4eb640a..d66fef9c9b81 100644
--- a/lib/ucs2_string.c
+++ b/lib/ucs2_string.c
@@ -57,7 +57,7 @@ ssize_t ucs2_strscpy(ucs2_char_t *dst, const ucs2_char_t *src, size_t count)
 	 * Ensure that we have a valid amount of space. We need to store at
 	 * least one NUL-character.
 	 */
-	if (count == 0 || WARN_ON_ONCE(count > INT_MAX / sizeof(*dst)))
+	if (count == 0)
 		return -E2BIG;
 
 	/*
-- 
2.55.0.1032.g73a4cd73de-goog


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

* [PATCH v3 2/9] lib/ucs2_string: Suppress modinfo when __DISABLE_EXPORTS is set
  2026-09-16 14:46 [PATCH v3 0/9] efi/libstub: Avoid UTF-16 conversion busywork Ard Biesheuvel
  2026-09-16 14:46 ` [PATCH v3 1/9] lib/ucs2_string: Drop arbitrary input size limit and associated WARN() Ard Biesheuvel
@ 2026-09-16 14:46 ` Ard Biesheuvel
  2026-09-16 14:46 ` [PATCH v3 3/9] lib/ucs2_string: Split out ucs2_as_utf8_l() taking a separate limit Ard Biesheuvel
                   ` (6 subsequent siblings)
  8 siblings, 0 replies; 10+ messages in thread
From: Ard Biesheuvel @ 2026-09-16 14:46 UTC (permalink / raw)
  To: linux-efi; +Cc: linux-kernel, Ard Biesheuvel, Vincent Mailhol

From: Ard Biesheuvel <ardb@kernel.org>

Allow the UCS-2 string library to be reused in the EFI stub, by
suppressing the modinfo data that is usually emitted so that the library
can be built as a module.

Signed-off-by: Ard Biesheuvel <ardb@kernel.org>
---
 lib/ucs2_string.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/lib/ucs2_string.c b/lib/ucs2_string.c
index d66fef9c9b81..f75fb4f7961a 100644
--- a/lib/ucs2_string.c
+++ b/lib/ucs2_string.c
@@ -165,5 +165,7 @@ ucs2_as_utf8(u8 *dest, const ucs2_char_t *src, unsigned long maxlength)
 }
 EXPORT_SYMBOL(ucs2_as_utf8);
 
+#ifndef __DISABLE_EXPORTS
 MODULE_DESCRIPTION("UCS2 string handling");
 MODULE_LICENSE("GPL v2");
+#endif
-- 
2.55.0.1032.g73a4cd73de-goog


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

* [PATCH v3 3/9] lib/ucs2_string: Split out ucs2_as_utf8_l() taking a separate limit
  2026-09-16 14:46 [PATCH v3 0/9] efi/libstub: Avoid UTF-16 conversion busywork Ard Biesheuvel
  2026-09-16 14:46 ` [PATCH v3 1/9] lib/ucs2_string: Drop arbitrary input size limit and associated WARN() Ard Biesheuvel
  2026-09-16 14:46 ` [PATCH v3 2/9] lib/ucs2_string: Suppress modinfo when __DISABLE_EXPORTS is set Ard Biesheuvel
@ 2026-09-16 14:46 ` Ard Biesheuvel
  2026-09-16 14:46 ` [PATCH v3 4/9] efi/libstub: Use ucs2_string library for UTF-16 to UTF-8 conversion Ard Biesheuvel
                   ` (5 subsequent siblings)
  8 siblings, 0 replies; 10+ messages in thread
From: Ard Biesheuvel @ 2026-09-16 14:46 UTC (permalink / raw)
  To: linux-efi; +Cc: linux-kernel, Ard Biesheuvel, Vincent Mailhol

From: Ard Biesheuvel <ardb@kernel.org>

ucs2_as_utf8() takes a maxlength argument, which specifies how many
bytes the function is permitted to store into the destination buffer.

The same value is used as an upper bound for the ucs2_strnlen()
invocation, which is reasonable in the general case, as each UCS-2
character produces at least one byte of UTF-8 output, and so there is
never a need to process more than 'maxlength' UCS-2 characters.

However, if the UCS-2 string is not NUL terminated, ucs2_strnlen() may
read past the end of the buffer if 'maxlength' is set to a high value.

Current callers pass UCS-2 strings that are expected to be NUL
terminated, but for processing the load options in the EFI stub, a
version is needed that takes a separate limit argument. So split that
off from the current implementation.

Signed-off-by: Ard Biesheuvel <ardb@kernel.org>
---
 include/linux/ucs2_string.h | 11 ++++++++++-
 lib/ucs2_string.c           | 15 ++++++++-------
 2 files changed, 18 insertions(+), 8 deletions(-)

diff --git a/include/linux/ucs2_string.h b/include/linux/ucs2_string.h
index c499ae809c7d..74f23ca5a967 100644
--- a/include/linux/ucs2_string.h
+++ b/include/linux/ucs2_string.h
@@ -14,7 +14,16 @@ ssize_t ucs2_strscpy(ucs2_char_t *dst, const ucs2_char_t *src, size_t count);
 int ucs2_strncmp(const ucs2_char_t *a, const ucs2_char_t *b, size_t len);
 
 unsigned long ucs2_utf8size(const ucs2_char_t *src);
+unsigned long
+ucs2_as_utf8_l(u8 *dest, const ucs2_char_t *src, unsigned long limit,
+	       unsigned long maxlength);
+
+static inline
 unsigned long ucs2_as_utf8(u8 *dest, const ucs2_char_t *src,
-			   unsigned long maxlength);
+			   unsigned long maxlength)
+{
+	return ucs2_as_utf8_l(dest, src, ucs2_strnlen(src, maxlength),
+			      maxlength);
+}
 
 #endif /* _LINUX_UCS2_STRING_H_ */
diff --git a/lib/ucs2_string.c b/lib/ucs2_string.c
index f75fb4f7961a..6c067b4280b9 100644
--- a/lib/ucs2_string.c
+++ b/lib/ucs2_string.c
@@ -125,18 +125,19 @@ ucs2_utf8size(const ucs2_char_t *src)
 EXPORT_SYMBOL(ucs2_utf8size);
 
 /*
- * copy at most maxlength bytes of whole utf8 characters to dest from the
- * ucs2 string src.
+ * Copy at most @limit whole utf8 characters to @dest from the ucs2 string
+ * @src, using no more than @maxlength bytes of buffer space.
  *
- * The return value is the number of characters copied, not including the
- * final NUL character.
+ * The return value is the number of bytes copied, not including the final NUL
+ * character. No NUL character will be appended if the output length equals
+ * @maxlength.
  */
 unsigned long
-ucs2_as_utf8(u8 *dest, const ucs2_char_t *src, unsigned long maxlength)
+ucs2_as_utf8_l(u8 *dest, const ucs2_char_t *src, unsigned long limit,
+	       unsigned long maxlength)
 {
 	unsigned int i;
 	unsigned long j = 0;
-	unsigned long limit = ucs2_strnlen(src, maxlength);
 
 	for (i = 0; maxlength && i < limit; i++) {
 		u16 c = src[i];
@@ -163,7 +164,7 @@ ucs2_as_utf8(u8 *dest, const ucs2_char_t *src, unsigned long maxlength)
 		dest[j] = '\0';
 	return j;
 }
-EXPORT_SYMBOL(ucs2_as_utf8);
+EXPORT_SYMBOL(ucs2_as_utf8_l);
 
 #ifndef __DISABLE_EXPORTS
 MODULE_DESCRIPTION("UCS2 string handling");
-- 
2.55.0.1032.g73a4cd73de-goog


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

* [PATCH v3 4/9] efi/libstub: Use ucs2_string library for UTF-16 to UTF-8 conversion
  2026-09-16 14:46 [PATCH v3 0/9] efi/libstub: Avoid UTF-16 conversion busywork Ard Biesheuvel
                   ` (2 preceding siblings ...)
  2026-09-16 14:46 ` [PATCH v3 3/9] lib/ucs2_string: Split out ucs2_as_utf8_l() taking a separate limit Ard Biesheuvel
@ 2026-09-16 14:46 ` Ard Biesheuvel
  2026-09-16 14:46 ` [PATCH v3 5/9] efi/libstub: Avoid efi_puts() for compile time constant strings Ard Biesheuvel
                   ` (4 subsequent siblings)
  8 siblings, 0 replies; 10+ messages in thread
From: Ard Biesheuvel @ 2026-09-16 14:46 UTC (permalink / raw)
  To: linux-efi; +Cc: linux-kernel, Ard Biesheuvel, Vincent Mailhol

From: Ard Biesheuvel <ardb@kernel.org>

Don't rely on sprintf() with a wide string conversion modifier to
convert the command line from UTF-16 to UTF-8. Instead, use the
existing ucs2 string library routine that does the same. Note that while
UEFI claims support for UTF-16, in practice it ignores surrogate pairs
entirely, and so the simplified UCS-2 character set (where each
character takes up exactly 2 bytes) is sufficient here.

This removes the only user of sprintf() in the EFI stub, so drop that
function as well.

Since boot memory is plentiful on UEFI systems, just establish a worst
case upper bound for the size of the buffer (which can never exceed
COMMAND_LINE_SIZE), and allocate that first. Then, perform the
conversion, and only fall back to processing the command line character
by character if that resulted in truncation. This makes the common
execution path much simpler.

Note that this no longer truncates the command line at the first
newline, but there is no evidence that this has ever been needed.

Signed-off-by: Ard Biesheuvel <ardb@kernel.org>
---
 drivers/firmware/efi/libstub/Makefile          |  3 +-
 drivers/firmware/efi/libstub/efi-stub-helper.c | 98 ++++++++------------
 drivers/firmware/efi/libstub/vsprintf.c        | 11 ---
 3 files changed, 39 insertions(+), 73 deletions(-)

diff --git a/drivers/firmware/efi/libstub/Makefile b/drivers/firmware/efi/libstub/Makefile
index 77a2b2d74f3f..12c0c7deb5cb 100644
--- a/drivers/firmware/efi/libstub/Makefile
+++ b/drivers/firmware/efi/libstub/Makefile
@@ -66,7 +66,8 @@ KBUILD_AFLAGS			:= $(KBUILD_CFLAGS) -D__ASSEMBLY__
 lib-y				:= efi-stub-helper.o gop.o secureboot.o tpm.o \
 				   file.o mem.o random.o randomalloc.o pci.o \
 				   skip_spaces.o lib-cmdline.o lib-ctype.o \
-				   alignedmem.o printk.o vsprintf.o
+				   alignedmem.o printk.o vsprintf.o \
+				   lib-ucs2_string.o
 
 # include the stub's libfdt dependencies from lib/ when needed
 libfdt-deps			:= fdt_rw.c fdt_ro.c fdt_wip.c fdt.c \
diff --git a/drivers/firmware/efi/libstub/efi-stub-helper.c b/drivers/firmware/efi/libstub/efi-stub-helper.c
index f27f2e1f0019..3dc365492301 100644
--- a/drivers/firmware/efi/libstub/efi-stub-helper.c
+++ b/drivers/firmware/efi/libstub/efi-stub-helper.c
@@ -12,6 +12,7 @@
 #include <linux/efi.h>
 #include <linux/kernel.h>
 #include <linux/overflow.h>
+#include <linux/ucs2_string.h>
 #include <asm/efi.h>
 #include <asm/setup.h>
 
@@ -334,81 +335,56 @@ char *efi_convert_cmdline(efi_loaded_image_t *image)
 {
 	const efi_char16_t *options = efi_table_attr(image, load_options);
 	u32 options_size = efi_table_attr(image, load_options_size);
-	int options_bytes = 0, safe_options_bytes = 0;  /* UTF-8 bytes */
-	unsigned long cmdline_addr = 0;
-	const efi_char16_t *s2;
-	bool in_quote = false;
+	unsigned long options_chars = 0;
+	unsigned long cmdline_bytes;
 	efi_status_t status;
-	u32 options_chars;
+	char *cmdline_addr;
 
 	if (options_size > 0)
 		efi_measure_tagged_event((unsigned long)options, options_size,
 					 EFISTUB_EVT_LOAD_OPTIONS);
 
 	efi_apply_loadoptions_quirk((const void **)&options, &options_size);
-	options_chars = options_size / sizeof(efi_char16_t);
-
-	if (options) {
-		s2 = options;
-		while (options_bytes < COMMAND_LINE_SIZE && options_chars--) {
-			efi_char16_t c = *s2++;
-
-			if (c < 0x80) {
-				if (c == L'\0' || c == L'\n')
-					break;
-				if (c == L'"')
-					in_quote = !in_quote;
-				else if (!in_quote && isspace((char)c))
-					safe_options_bytes = options_bytes;
-
-				options_bytes++;
-				continue;
-			}
-
-			/*
-			 * Get the number of UTF-8 bytes corresponding to a
-			 * UTF-16 character.
-			 * The first part handles everything in the BMP.
-			 */
-			options_bytes += 2 + (c >= 0x800);
-			/*
-			 * Add one more byte for valid surrogate pairs. Invalid
-			 * surrogates will be replaced with 0xfffd and take up
-			 * only 3 bytes.
-			 */
-			if ((c & 0xfc00) == 0xd800) {
-				/*
-				 * If the very last word is a high surrogate,
-				 * we must ignore it since we can't access the
-				 * low surrogate.
-				 */
-				if (!options_chars) {
-					options_bytes -= 3;
-				} else if ((*s2 & 0xfc00) == 0xdc00) {
-					options_bytes++;
-					options_chars--;
-					s2++;
-				}
-			}
-		}
-		if (options_bytes >= COMMAND_LINE_SIZE) {
-			options_bytes = safe_options_bytes;
-			efi_err("Command line is too long: truncated to %d bytes\n",
-				options_bytes);
-		}
-	}
+	if (options)
+		options_chars = ucs2_strnlen(options,
+					     options_size / sizeof(efi_char16_t));
 
-	options_bytes++;	/* NUL termination */
+	/* Each UCS-2 char takes up at most 3 UTF-8 bytes */
+	cmdline_bytes = min(3 * options_chars, COMMAND_LINE_SIZE - 1) + 3;
 
-	status = efi_bs_call(allocate_pool, EFI_LOADER_DATA, options_bytes,
+	status = efi_bs_call(allocate_pool, EFI_LOADER_DATA, cmdline_bytes,
 			     (void **)&cmdline_addr);
 	if (status != EFI_SUCCESS)
 		return NULL;
 
-	snprintf((char *)cmdline_addr, options_bytes, "%.*ls",
-		 options_bytes - 1, options);
+	if (ucs2_as_utf8_l(cmdline_addr, options, options_chars,
+			   cmdline_bytes) >= COMMAND_LINE_SIZE) {
+		/*
+		 * The output fills up the entire buffer, and may have been
+		 * truncated. Work backwards through the buffer to find a safe
+		 * truncation point (i.e., a blank character not inside a
+		 * quoted string).
+		 */
+		int safe_pos[2] = {};
+		int in_quote = 0;
+
+		for (int i = COMMAND_LINE_SIZE - 1; i >= 0; i--) {
+			char c = cmdline_addr[i];
+
+			if (!c)
+				return cmdline_addr;
+			else if (c == '"')
+				in_quote ^= 1;
+			else if (!safe_pos[in_quote] && isspace(c))
+				safe_pos[in_quote] = i;
+		}
+
+		efi_err("Command line is too long: truncated to %d bytes\n",
+			safe_pos[in_quote]);
+		cmdline_addr[safe_pos[in_quote]] = '\0';
+	}
 
-	return (char *)cmdline_addr;
+	return cmdline_addr;
 }
 
 /**
diff --git a/drivers/firmware/efi/libstub/vsprintf.c b/drivers/firmware/efi/libstub/vsprintf.c
index 71c71c222346..dba136679172 100644
--- a/drivers/firmware/efi/libstub/vsprintf.c
+++ b/drivers/firmware/efi/libstub/vsprintf.c
@@ -551,14 +551,3 @@ int vsnprintf(char *buf, size_t size, const char *fmt, va_list ap)
 
 	return pos;
 }
-
-int snprintf(char *buf, size_t size, const char *fmt, ...)
-{
-	va_list args;
-	int i;
-
-	va_start(args, fmt);
-	i = vsnprintf(buf, size, fmt, args);
-	va_end(args);
-	return i;
-}
-- 
2.55.0.1032.g73a4cd73de-goog


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

* [PATCH v3 5/9] efi/libstub: Avoid efi_puts() for compile time constant strings
  2026-09-16 14:46 [PATCH v3 0/9] efi/libstub: Avoid UTF-16 conversion busywork Ard Biesheuvel
                   ` (3 preceding siblings ...)
  2026-09-16 14:46 ` [PATCH v3 4/9] efi/libstub: Use ucs2_string library for UTF-16 to UTF-8 conversion Ard Biesheuvel
@ 2026-09-16 14:46 ` Ard Biesheuvel
  2026-09-16 14:46 ` [PATCH v3 6/9] efi/libstub: Output UTF-16 directly from vsnprintf() Ard Biesheuvel
                   ` (3 subsequent siblings)
  8 siblings, 0 replies; 10+ messages in thread
From: Ard Biesheuvel @ 2026-09-16 14:46 UTC (permalink / raw)
  To: linux-efi; +Cc: linux-kernel, Ard Biesheuvel, Vincent Mailhol

From: Ard Biesheuvel <ardb@kernel.org>

efi_puts() performs a UTF-8 to UTF-16 conversion on its input, as the
EFI console's native character set is UTF-16.

This is pointless for compile time constant strings, since we can
simply define those as UTF-16 to begin with. This takes slightly more
space, but removes any runtime handling of those strings, simplifying
the code.

Note that efi_puts() also performs LF to CR-LF conversion, so this needs
to be taken into account as well.

Signed-off-by: Ard Biesheuvel <ardb@kernel.org>
---
 drivers/firmware/efi/libstub/gop.c    | 6 +++---
 drivers/firmware/efi/libstub/printk.c | 4 ++--
 2 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/drivers/firmware/efi/libstub/gop.c b/drivers/firmware/efi/libstub/gop.c
index 80dc8cfeb33e..6919e28ba92b 100644
--- a/drivers/firmware/efi/libstub/gop.c
+++ b/drivers/firmware/efi/libstub/gop.c
@@ -309,12 +309,12 @@ static u32 choose_mode_list(efi_graphics_output_protocol_t *gop)
 	efi_status_t status;
 
 	efi_printk("Available graphics modes are 0-%u\n", max_mode-1);
-	efi_puts("  * = current mode\n"
-		 "  - = unusable mode\n");
+	efi_char16_puts(L"  * = current mode\r\n"
+			 "  - = unusable mode\r\n");
 
 	choose_mode(gop, match_list, (void *)cur_mode);
 
-	efi_puts("\nPress any key to continue (or wait 10 seconds)\n");
+	efi_char16_puts(L"\r\nPress any key to continue (or wait 10 seconds)\r\n");
 	status = efi_wait_for_key(10 * EFI_USEC_PER_SEC, &key);
 	if (status != EFI_SUCCESS && status != EFI_TIMEOUT) {
 		efi_err("Unable to read key, continuing in 10 seconds\n");
diff --git a/drivers/firmware/efi/libstub/printk.c b/drivers/firmware/efi/libstub/printk.c
index bc599212c05d..f36639886d00 100644
--- a/drivers/firmware/efi/libstub/printk.c
+++ b/drivers/firmware/efi/libstub/printk.c
@@ -136,7 +136,7 @@ int efi_printk(const char *fmt, ...)
 		return 0;
 
 	if (loglevel >= 0)
-		efi_puts("EFI stub: ");
+		efi_char16_puts(L"EFI stub: ");
 
 	fmt = printk_skip_level(fmt);
 
@@ -146,7 +146,7 @@ int efi_printk(const char *fmt, ...)
 
 	efi_puts(printf_buf);
 	if (printed >= sizeof(printf_buf)) {
-		efi_puts("[Message truncated]\n");
+		efi_char16_puts(L"[Message truncated]\r\n");
 		return -1;
 	}
 
-- 
2.55.0.1032.g73a4cd73de-goog


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

* [PATCH v3 6/9] efi/libstub: Output UTF-16 directly from vsnprintf()
  2026-09-16 14:46 [PATCH v3 0/9] efi/libstub: Avoid UTF-16 conversion busywork Ard Biesheuvel
                   ` (4 preceding siblings ...)
  2026-09-16 14:46 ` [PATCH v3 5/9] efi/libstub: Avoid efi_puts() for compile time constant strings Ard Biesheuvel
@ 2026-09-16 14:46 ` Ard Biesheuvel
  2026-09-16 14:46 ` [PATCH v3 7/9] efi/libstub: Add support for printing human readable GUIDs Ard Biesheuvel
                   ` (2 subsequent siblings)
  8 siblings, 0 replies; 10+ messages in thread
From: Ard Biesheuvel @ 2026-09-16 14:46 UTC (permalink / raw)
  To: linux-efi; +Cc: linux-kernel, Ard Biesheuvel, Vincent Mailhol

From: Ard Biesheuvel <ardb@kernel.org>

The only remaining users of vsnprintf() in the EFI stub are the
diagnostic printk()'s, which are emitted to the console and not recorded
for posterity.

The EFI console uses UTF-16 (or actually, UCS-2) natively, and so all
non-UTF16 strings that are emitted need to be converted. Given the
stub's vsnprintf() support for wide strings (using the %ls conversion
modifier), which uses UTF-16 to UTF-8 conversion internally, the final
conversion to UTF-16 needs to support not just plain ASCII but UTF-8 as
well.

This is all pointless, of course, and it makes more sense to use UTF-16
internally. This removes the need for UTF-16 to UTF-8 conversion in
vsnprintf(), and given that all non-wide string inputs to vsnprintf()
that exist in the stub today are compile time constant ASCII strings,
the need to convert UTF-8 to UTF-16 disappears as well.

So implement efi_vsnprintf() taking a const char *fmt as before, but
outputting a efi_char16_t[] that can be passed to the EFI console
directly, rather than via efi_puts(), leaving the latter unused and
therefore removed.

Note that efi_puts() performs LF to CR-LF conversion internally, so add
this capability to efi_vsnprintf() as well.

Signed-off-by: Ard Biesheuvel <ardb@kernel.org>
---
 drivers/firmware/efi/libstub/efistub.h  |  5 +-
 drivers/firmware/efi/libstub/printk.c   | 91 ++-----------------
 drivers/firmware/efi/libstub/vsprintf.c | 96 +++-----------------
 3 files changed, 22 insertions(+), 170 deletions(-)

diff --git a/drivers/firmware/efi/libstub/efistub.h b/drivers/firmware/efi/libstub/efistub.h
index fd91fc15ec81..36056c624782 100644
--- a/drivers/firmware/efi/libstub/efistub.h
+++ b/drivers/firmware/efi/libstub/efistub.h
@@ -1078,9 +1078,10 @@ efi_status_t check_platform_features(void);
 
 void *get_efi_config_table(efi_guid_t guid);
 
-/* NOTE: These functions do not print a trailing newline after the string */
 void efi_char16_puts(efi_char16_t *);
-void efi_puts(const char *str);
+
+int efi_vsnprintf(efi_char16_t *buf, size_t size, const char *fmt, va_list ap,
+		  bool crlf);
 
 __printf(1, 2) int efi_printk(char const *fmt, ...);
 
diff --git a/drivers/firmware/efi/libstub/printk.c b/drivers/firmware/efi/libstub/printk.c
index f36639886d00..0a18cfe32528 100644
--- a/drivers/firmware/efi/libstub/printk.c
+++ b/drivers/firmware/efi/libstub/printk.c
@@ -23,98 +23,20 @@ void efi_char16_puts(efi_char16_t *str)
 		       output_string, str);
 }
 
-static
-u32 utf8_to_utf32(const u8 **s8)
-{
-	u32 c32;
-	u8 c0, cx;
-	size_t clen, i;
-
-	c0 = cx = *(*s8)++;
-	/*
-	 * The position of the most-significant 0 bit gives us the length of
-	 * a multi-octet encoding.
-	 */
-	for (clen = 0; cx & 0x80; ++clen)
-		cx <<= 1;
-	/*
-	 * If the 0 bit is in position 8, this is a valid single-octet
-	 * encoding. If the 0 bit is in position 7 or positions 1-3, the
-	 * encoding is invalid.
-	 * In either case, we just return the first octet.
-	 */
-	if (clen < 2 || clen > 4)
-		return c0;
-	/* Get the bits from the first octet. */
-	c32 = cx >> clen--;
-	for (i = 0; i < clen; ++i) {
-		/* Trailing octets must have 10 in most significant bits. */
-		cx = (*s8)[i] ^ 0x80;
-		if (cx & 0xc0)
-			return c0;
-		c32 = (c32 << 6) | cx;
-	}
-	/*
-	 * Check for validity:
-	 * - The character must be in the Unicode range.
-	 * - It must not be a surrogate.
-	 * - It must be encoded using the correct number of octets.
-	 */
-	if (c32 > 0x10ffff ||
-	    (c32 & 0xf800) == 0xd800 ||
-	    clen != (c32 >= 0x80) + (c32 >= 0x800) + (c32 >= 0x10000))
-		return c0;
-	*s8 += clen;
-	return c32;
-}
-
-/**
- * efi_puts() - Write a UTF-8 encoded string to the console
- * @str:	UTF-8 encoded string
- */
-void efi_puts(const char *str)
-{
-	efi_char16_t buf[128];
-	size_t pos = 0, lim = ARRAY_SIZE(buf);
-	const u8 *s8 = (const u8 *)str;
-	u32 c32;
-
-	while (*s8) {
-		if (*s8 == '\n')
-			buf[pos++] = L'\r';
-		c32 = utf8_to_utf32(&s8);
-		if (c32 < 0x10000) {
-			/* Characters in plane 0 use a single word. */
-			buf[pos++] = c32;
-		} else {
-			/*
-			 * Characters in other planes encode into a surrogate
-			 * pair.
-			 */
-			buf[pos++] = (0xd800 - (0x10000 >> 10)) + (c32 >> 10);
-			buf[pos++] = 0xdc00 + (c32 & 0x3ff);
-		}
-		if (*s8 == '\0' || pos >= lim - 2) {
-			buf[pos] = L'\0';
-			efi_char16_puts(buf);
-			pos = 0;
-		}
-	}
-}
-
 /**
  * efi_printk() - Print a kernel message
  * @fmt:	format string
  *
  * The first letter of the format string is used to determine the logging level
  * of the message. If the level is less then the current EFI logging level, the
- * message is suppressed. The message will be truncated to 255 bytes.
+ * message is suppressed. The message will be truncated to 255 characters
+ * (ignoring surrogates).
  *
  * Return:	number of printed characters
  */
 int efi_printk(const char *fmt, ...)
 {
-	char printf_buf[256];
+	efi_char16_t printf_buf[256];
 	va_list args;
 	int printed;
 	int loglevel = printk_get_level(fmt);
@@ -141,11 +63,12 @@ int efi_printk(const char *fmt, ...)
 	fmt = printk_skip_level(fmt);
 
 	va_start(args, fmt);
-	printed = vsnprintf(printf_buf, sizeof(printf_buf), fmt, args);
+	printed = efi_vsnprintf(printf_buf, ARRAY_SIZE(printf_buf), fmt, args,
+				true);
 	va_end(args);
 
-	efi_puts(printf_buf);
-	if (printed >= sizeof(printf_buf)) {
+	efi_char16_puts(printf_buf);
+	if (printed >= ARRAY_SIZE(printf_buf)) {
 		efi_char16_puts(L"[Message truncated]\r\n");
 		return -1;
 	}
diff --git a/drivers/firmware/efi/libstub/vsprintf.c b/drivers/firmware/efi/libstub/vsprintf.c
index dba136679172..bd32af6b4f4d 100644
--- a/drivers/firmware/efi/libstub/vsprintf.c
+++ b/drivers/firmware/efi/libstub/vsprintf.c
@@ -14,10 +14,14 @@
 
 #include <linux/compiler.h>
 #include <linux/ctype.h>
+#include <linux/efi.h>
 #include <linux/kernel.h>
 #include <linux/limits.h>
 #include <linux/string.h>
 #include <linux/types.h>
+#include <linux/ucs2_string.h>
+
+#include "efistub.h"
 
 static
 int skip_atoi(const char **s)
@@ -239,58 +243,6 @@ char get_sign(long long *num, int flags)
 	return 0;
 }
 
-static
-size_t utf16s_utf8nlen(const u16 *s16, size_t maxlen)
-{
-	size_t len, clen;
-
-	for (len = 0; len < maxlen && *s16; len += clen) {
-		u16 c0 = *s16++;
-
-		/* First, get the length for a BMP character */
-		clen = 1 + (c0 >= 0x80) + (c0 >= 0x800);
-		if (len + clen > maxlen)
-			break;
-		/*
-		 * If this is a high surrogate, and we're already at maxlen, we
-		 * can't include the character if it's a valid surrogate pair.
-		 * Avoid accessing one extra word just to check if it's valid
-		 * or not.
-		 */
-		if ((c0 & 0xfc00) == 0xd800) {
-			if (len + clen == maxlen)
-				break;
-			if ((*s16 & 0xfc00) == 0xdc00) {
-				++s16;
-				++clen;
-			}
-		}
-	}
-
-	return len;
-}
-
-static
-u32 utf16_to_utf32(const u16 **s16)
-{
-	u16 c0, c1;
-
-	c0 = *(*s16)++;
-	/* not a surrogate */
-	if ((c0 & 0xf800) != 0xd800)
-		return c0;
-	/* invalid: low surrogate instead of high */
-	if (c0 & 0x0400)
-		return 0xfffd;
-	c1 = **s16;
-	/* invalid: missing low surrogate */
-	if ((c1 & 0xfc00) != 0xdc00)
-		return 0xfffd;
-	/* valid surrogate pair */
-	++(*s16);
-	return (0x10000 - (0xd800 << 10) - 0xdc00) + (c0 << 10) + c1;
-}
-
 #define PUTC(c) \
 do {				\
 	if (pos < size)		\
@@ -298,7 +250,8 @@ do {				\
 	++pos;			\
 } while (0);
 
-int vsnprintf(char *buf, size_t size, const char *fmt, va_list ap)
+int efi_vsnprintf(efi_char16_t *buf, size_t size, const char *fmt, va_list ap,
+		  bool crlf)
 {
 	/* The maximum space required is to print a 64-bit number in octal */
 	char tmp[(sizeof(unsigned long long) * 8 + 2) / 3];
@@ -336,6 +289,8 @@ int vsnprintf(char *buf, size_t size, const char *fmt, va_list ap)
 
 	for (pos = 0; *fmt; ++fmt) {
 		if (*fmt != '%' || *++fmt == '%') {
+			if (crlf && *fmt == '\n')
+				PUTC('\r');
 			PUTC(*fmt);
 			continue;
 		}
@@ -400,7 +355,7 @@ int vsnprintf(char *buf, size_t size, const char *fmt, va_list ap)
 			else if (qualifier == 'l') {
 		wstring:
 				flags |= WIDE;
-				precision = len = utf16s_utf8nlen((const u16 *)s, precision);
+				precision = len = ucs2_strnlen((const u16 *)s, precision);
 				goto output;
 			}
 			precision = len = strnlen(s, precision);
@@ -505,36 +460,9 @@ int vsnprintf(char *buf, size_t size, const char *fmt, va_list ap)
 		if (flags & WIDE) {
 			const u16 *ws = (const u16 *)s;
 
-			while (len-- > 0) {
-				u32 c32 = utf16_to_utf32(&ws);
-				u8 *s8;
-				size_t clen;
-
-				if (c32 < 0x80) {
-					PUTC(c32);
-					continue;
-				}
-
-				/* Number of trailing octets */
-				clen = 1 + (c32 >= 0x800) + (c32 >= 0x10000);
-
-				len -= clen;
-				s8 = (u8 *)&buf[pos];
-
-				/* Avoid writing partial character */
-				PUTC('\0');
-				pos += clen;
-				if (pos >= size)
-					continue;
-
-				/* Set high bits of leading octet */
-				*s8 = (0xf00 >> 1) >> clen;
-				/* Write trailing octets in reverse order */
-				for (s8 += clen; clen; --clen, c32 >>= 6)
-					*s8-- = 0x80 | (c32 & 0x3f);
-				/* Set low bits of leading octet */
-				*s8 |= c32;
-			}
+			if (pos < size)
+				memcpy(&buf[pos], ws, min(len, size - pos) * sizeof(*ws));
+			pos += len;
 		} else {
 			while (len-- > 0)
 				PUTC(*s++);
-- 
2.55.0.1032.g73a4cd73de-goog


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

* [PATCH v3 7/9] efi/libstub: Add support for printing human readable GUIDs
  2026-09-16 14:46 [PATCH v3 0/9] efi/libstub: Avoid UTF-16 conversion busywork Ard Biesheuvel
                   ` (5 preceding siblings ...)
  2026-09-16 14:46 ` [PATCH v3 6/9] efi/libstub: Output UTF-16 directly from vsnprintf() Ard Biesheuvel
@ 2026-09-16 14:46 ` Ard Biesheuvel
  2026-09-16 14:46 ` [PATCH v3 8/9] efi/libstub: Add efi_snprintf() to construct wide strings Ard Biesheuvel
  2026-09-16 14:46 ` [PATCH v3 9/9] efi/libstub: add initial Boot Loader Interface support Ard Biesheuvel
  8 siblings, 0 replies; 10+ messages in thread
From: Ard Biesheuvel @ 2026-09-16 14:46 UTC (permalink / raw)
  To: linux-efi; +Cc: linux-kernel, Ard Biesheuvel, Vincent Mailhol

From: Ard Biesheuvel <ardb@kernel.org>

Add support for the %pUl printk conversion specifier, which takes a
pointer to a GUID and prints it in the usual format:

  aaaaaaaa-bbbb-cccc-dddd-dddddddddddd

Co-developed-by: Vincent Mailhol <mailhol@kernel.org>
Signed-off-by: Vincent Mailhol <mailhol@kernel.org>
Signed-off-by: Ard Biesheuvel <ardb@kernel.org>
---
 drivers/firmware/efi/libstub/vsprintf.c | 39 +++++++++++++++++---
 1 file changed, 34 insertions(+), 5 deletions(-)

diff --git a/drivers/firmware/efi/libstub/vsprintf.c b/drivers/firmware/efi/libstub/vsprintf.c
index bd32af6b4f4d..54b82ba798a2 100644
--- a/drivers/firmware/efi/libstub/vsprintf.c
+++ b/drivers/firmware/efi/libstub/vsprintf.c
@@ -113,6 +113,8 @@ char *put_dec(char *end, unsigned long long n)
 	return p;
 }
 
+static const char digits[16] = "0123456789ABCDEF";
+
 static
 char *number(char *end, unsigned long long num, int base, char locase)
 {
@@ -121,9 +123,6 @@ char *number(char *end, unsigned long long num, int base, char locase)
 	 * produces same digits or (maybe lowercased) letters
 	 */
 
-	/* we are called with base 8, 10 or 16, only, thus don't need "G..."  */
-	static const char digits[16] = "0123456789ABCDEF"; /* "GHIJKLMNOPQRSTUVWXYZ"; */
-
 	switch (base) {
 	case 10:
 		if (num != 0)
@@ -144,6 +143,29 @@ char *number(char *end, unsigned long long num, int base, char locase)
 	return end;
 }
 
+static char *guid_to_str(const efi_guid_t *guid, char *out, char locase)
+{
+	static const u8 guid_index[UUID_SIZE] = {
+		3, 2, 1, 0, 5, 4, 7, 6, 8, 9, 10, 11, 12, 13, 14, 15,
+	};
+
+	for (int i = 0, p = 0; i < ARRAY_SIZE(guid_index); i++) {
+		u8 byte = guid->b[guid_index[i]];
+
+		out[p++] = locase | digits[byte >> 4];
+		out[p++] = locase | digits[byte & 0xf];
+
+		switch (i) {
+		case 3:
+		case 5:
+		case 7:
+		case 9:
+			out[p++] = '-';
+		}
+	}
+	return out;
+}
+
 #define ZEROPAD	1		/* pad with zero */
 #define SIGN	2		/* unsigned/signed long */
 #define PLUS	4		/* show plus */
@@ -253,8 +275,7 @@ do {				\
 int efi_vsnprintf(efi_char16_t *buf, size_t size, const char *fmt, va_list ap,
 		  bool crlf)
 {
-	/* The maximum space required is to print a 64-bit number in octal */
-	char tmp[(sizeof(unsigned long long) * 8 + 2) / 3];
+	char tmp[UUID_STRING_LEN];
 	char *tmp_end = &tmp[ARRAY_SIZE(tmp)];
 	long long num;
 	int base;
@@ -367,6 +388,14 @@ int efi_vsnprintf(efi_char16_t *buf, size_t size, const char *fmt, va_list ap,
 			break;
 
 		case 'p':
+			if (fmt[1] == 'U' && (fmt[2] | 0x20) == 'l') {
+				flags &= LEFT;
+				s = guid_to_str(va_arg(args, efi_guid_t *), tmp, fmt[2] & 0x20);
+				precision = len = UUID_STRING_LEN;
+				fmt += 2;
+				goto output;
+			}
+
 			if (precision < 0)
 				precision = 2 * sizeof(void *);
 			fallthrough;
-- 
2.55.0.1032.g73a4cd73de-goog


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

* [PATCH v3 8/9] efi/libstub: Add efi_snprintf() to construct wide strings
  2026-09-16 14:46 [PATCH v3 0/9] efi/libstub: Avoid UTF-16 conversion busywork Ard Biesheuvel
                   ` (6 preceding siblings ...)
  2026-09-16 14:46 ` [PATCH v3 7/9] efi/libstub: Add support for printing human readable GUIDs Ard Biesheuvel
@ 2026-09-16 14:46 ` Ard Biesheuvel
  2026-09-16 14:46 ` [PATCH v3 9/9] efi/libstub: add initial Boot Loader Interface support Ard Biesheuvel
  8 siblings, 0 replies; 10+ messages in thread
From: Ard Biesheuvel @ 2026-09-16 14:46 UTC (permalink / raw)
  To: linux-efi; +Cc: linux-kernel, Ard Biesheuvel, Vincent Mailhol

From: Ard Biesheuvel <ardb@kernel.org>

The native EFI character set is UTF-16 (or in practice, UCS-2).
Implement efi_snprintf() to construct UTF-16 strings using printf style
templates. This will be used in a subsequent patch to set the
LoaderDevicePartUUID EFI variable.

Link: https://lore.kernel.org/all/20260903-efi_stub_bli-v2-1-dbf7ba915117@kernel.org/
Signed-off-by: Ard Biesheuvel <ardb@kernel.org>
---
 drivers/firmware/efi/libstub/efistub.h  |  1 +
 drivers/firmware/efi/libstub/vsprintf.c | 11 +++++++++++
 2 files changed, 12 insertions(+)

diff --git a/drivers/firmware/efi/libstub/efistub.h b/drivers/firmware/efi/libstub/efistub.h
index 36056c624782..880c1d0c464b 100644
--- a/drivers/firmware/efi/libstub/efistub.h
+++ b/drivers/firmware/efi/libstub/efistub.h
@@ -1084,6 +1084,7 @@ int efi_vsnprintf(efi_char16_t *buf, size_t size, const char *fmt, va_list ap,
 		  bool crlf);
 
 __printf(1, 2) int efi_printk(char const *fmt, ...);
+__printf(3, 4) int efi_snprintf(efi_char16_t *buf, size_t size, const char *fmt, ...);
 
 void efi_free(unsigned long size, unsigned long addr);
 DEFINE_FREE(efi_pool, void *, if (_T) efi_bs_call(free_pool, _T));
diff --git a/drivers/firmware/efi/libstub/vsprintf.c b/drivers/firmware/efi/libstub/vsprintf.c
index 54b82ba798a2..071c8b245b68 100644
--- a/drivers/firmware/efi/libstub/vsprintf.c
+++ b/drivers/firmware/efi/libstub/vsprintf.c
@@ -508,3 +508,14 @@ int efi_vsnprintf(efi_char16_t *buf, size_t size, const char *fmt, va_list ap,
 
 	return pos;
 }
+
+int efi_snprintf(efi_char16_t *buf, size_t size, const char *fmt, ...)
+{
+       va_list args;
+       int i;
+
+       va_start(args, fmt);
+       i = efi_vsnprintf(buf, size, fmt, args, false);
+       va_end(args);
+       return i;
+}
-- 
2.55.0.1032.g73a4cd73de-goog


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

* [PATCH v3 9/9] efi/libstub: add initial Boot Loader Interface support
  2026-09-16 14:46 [PATCH v3 0/9] efi/libstub: Avoid UTF-16 conversion busywork Ard Biesheuvel
                   ` (7 preceding siblings ...)
  2026-09-16 14:46 ` [PATCH v3 8/9] efi/libstub: Add efi_snprintf() to construct wide strings Ard Biesheuvel
@ 2026-09-16 14:46 ` Ard Biesheuvel
  8 siblings, 0 replies; 10+ messages in thread
From: Ard Biesheuvel @ 2026-09-16 14:46 UTC (permalink / raw)
  To: linux-efi; +Cc: linux-kernel, Ard Biesheuvel, Vincent Mailhol

From: Vincent Mailhol <mailhol@kernel.org>

The Boot Loader Interface (BLI) [1] defines EFI variables that expose
boot loader state to the running OS. LoaderInfo identifies the boot
loader, while LoaderDevicePartUUID records the GPT partition UUID of
the partition containing it.

LoaderDevicePartUUID is used, for example, by systemd-gpt-auto-generator
[2] to identify the disk the boot loader was launched from and
automatically detect and mount partitions on it.

GRUB [3] and systemd-boot [4] populate these variables, but when the
kernel is started directly by EFI firmware, there is no conventional
external boot loader to provide them. In that case, because the EFI stub
performs the boot loader role, it should provide the variables itself.

Use LoaderInfo as a sentinel: if it is already set by an earlier boot
stage or cannot be set, bail out. Otherwise, populate the other BLI
variables.

Parse the loaded image device path, extract the GUID signature from its
GPT HD() node and publish it under the Linux loader entry vendor GUID as
the volatile LoaderDevicePartUUID EFI variable.

Install the efi_bli_set_variables() hook in both the generic efi-stub.c
path and the x86-specific x86-stub.c path.

[1] The Boot Loader Interface
Link: https://systemd.io/BOOT_LOADER_INTERFACE/

[2] systemd-gpt-auto-generator
Link: https://www.freedesktop.org/software/systemd/man/latest/systemd-gpt-auto-generator.html

[3] GRUB -- §16.2 bli
Link: https://www.gnu.org/software/grub/manual/grub/html_node/bli_005fmodule.html

[4] systemd -- systemd-boot UEFI Boot Manager
Link: https://github.com/systemd/systemd/blob/main/docs/BOOT.md?plain=1#L102

Signed-off-by: Vincent Mailhol <mailhol@kernel.org>
[ardb: - constify 'image' pointer parameter
       - pass efi_guid_t* to efi_snprintf()]
Signed-off-by: Ard Biesheuvel <ardb@kernel.org>
---
 drivers/firmware/efi/libstub/Makefile   |  2 +-
 drivers/firmware/efi/libstub/bli.c      | 87 ++++++++++++++++++++
 drivers/firmware/efi/libstub/efi-stub.c |  1 +
 drivers/firmware/efi/libstub/efistub.h  |  2 +
 drivers/firmware/efi/libstub/x86-stub.c |  1 +
 include/linux/efi.h                     | 22 +++++
 6 files changed, 114 insertions(+), 1 deletion(-)

diff --git a/drivers/firmware/efi/libstub/Makefile b/drivers/firmware/efi/libstub/Makefile
index 12c0c7deb5cb..564773c89d14 100644
--- a/drivers/firmware/efi/libstub/Makefile
+++ b/drivers/firmware/efi/libstub/Makefile
@@ -66,7 +66,7 @@ KBUILD_AFLAGS			:= $(KBUILD_CFLAGS) -D__ASSEMBLY__
 lib-y				:= efi-stub-helper.o gop.o secureboot.o tpm.o \
 				   file.o mem.o random.o randomalloc.o pci.o \
 				   skip_spaces.o lib-cmdline.o lib-ctype.o \
-				   alignedmem.o printk.o vsprintf.o \
+				   alignedmem.o printk.o vsprintf.o bli.o \
 				   lib-ucs2_string.o
 
 # include the stub's libfdt dependencies from lib/ when needed
diff --git a/drivers/firmware/efi/libstub/bli.c b/drivers/firmware/efi/libstub/bli.c
new file mode 100644
index 000000000000..b2407f63b743
--- /dev/null
+++ b/drivers/firmware/efi/libstub/bli.c
@@ -0,0 +1,87 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <generated/utsrelease.h>
+
+#include <linux/efi.h>
+#include <linux/errno.h>
+#include <linux/unaligned.h>
+
+#include "efistub.h"
+
+static efi_guid_t loader_entry_guid = LINUX_EFI_LOADER_ENTRY_GUID;
+
+static const struct efi_hd_dev_path *
+efi_bli_find_hd_node(const struct efi_dev_path *path)
+{
+	const struct efi_dev_path *node;
+	u16 node_len;
+
+	for (node = path;
+	     node->header.type != EFI_DEV_END_PATH &&
+	     node->header.type != EFI_DEV_END_PATH2;
+	     node = (const void *)node + node_len) {
+		node_len = get_unaligned_le16(&node->header.length);
+
+		if (node_len < sizeof(node->header))
+			return NULL;
+
+		if (node->header.type != EFI_DEV_MEDIA ||
+		    node->header.sub_type != EFI_DEV_MEDIA_HARD_DRIVE)
+			continue;
+
+		if (node_len < sizeof(node->hd))
+			return NULL;
+
+		if (node->hd.partition_format != EFI_HD_PARTITION_FORMAT_GPT ||
+		    node->hd.signature_type != EFI_HD_SIGNATURE_TYPE_GUID)
+			continue;
+
+		return &node->hd;
+	}
+
+	return NULL;
+}
+
+static void efi_bli_populate_loader_part_uuid(const efi_loaded_image_t *image)
+{
+	static efi_guid_t device_path_guid = EFI_DEVICE_PATH_PROTOCOL_GUID;
+	efi_char16_t partuuid[UUID_STRING_LEN + 1];
+	const struct efi_hd_dev_path *hd_node;
+	const struct efi_dev_path *path;
+
+	if (efi_bs_call(handle_protocol, efi_table_attr(image, device_handle),
+			&device_path_guid, (void **)&path) != EFI_SUCCESS)
+		return;
+
+	hd_node = efi_bli_find_hd_node(path);
+	if (!hd_node)
+		return;
+
+	if (efi_snprintf(partuuid, ARRAY_SIZE(partuuid), "%pUl",
+			 &hd_node->signature) != UUID_STRING_LEN)
+		return;
+
+	set_efi_var(L"LoaderDevicePartUUID", &loader_entry_guid,
+		    EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS,
+		    sizeof(partuuid), partuuid);
+}
+
+void efi_bli_set_variables(const efi_loaded_image_t *image)
+{
+	static efi_char16_t loader_info[] = L"Linux EFI stub " UTS_RELEASE;
+	unsigned long size = 0;
+
+	if (!image)
+		return;
+
+	if (get_efi_var(L"LoaderInfo", &loader_entry_guid,
+			NULL, &size, NULL) != EFI_NOT_FOUND)
+		return;
+
+	if (set_efi_var(L"LoaderInfo", &loader_entry_guid,
+			EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS,
+			sizeof(loader_info), loader_info) != EFI_SUCCESS)
+		return;
+
+	efi_bli_populate_loader_part_uuid(image);
+}
diff --git a/drivers/firmware/efi/libstub/efi-stub.c b/drivers/firmware/efi/libstub/efi-stub.c
index 42d6073bcd06..2a95f4ea104a 100644
--- a/drivers/firmware/efi/libstub/efi-stub.c
+++ b/drivers/firmware/efi/libstub/efi-stub.c
@@ -165,6 +165,7 @@ efi_status_t efi_stub_common(efi_handle_t handle,
 	dpy = setup_primary_display();
 
 	efi_retrieve_eventlog();
+	efi_bli_set_variables(image);
 
 	/* Ask the firmware to clear memory on unclean shutdown */
 	efi_enable_reset_attack_mitigation();
diff --git a/drivers/firmware/efi/libstub/efistub.h b/drivers/firmware/efi/libstub/efistub.h
index 880c1d0c464b..4f9e7ae28b6c 100644
--- a/drivers/firmware/efi/libstub/efistub.h
+++ b/drivers/firmware/efi/libstub/efistub.h
@@ -1072,6 +1072,8 @@ efi_status_t efi_random_alloc(unsigned long size, unsigned long align,
 			      int memory_type, unsigned long alloc_min,
 			      unsigned long alloc_max);
 
+void efi_bli_set_variables(const efi_loaded_image_t *image);
+
 efi_status_t efi_random_get_seed(void);
 
 efi_status_t check_platform_features(void);
diff --git a/drivers/firmware/efi/libstub/x86-stub.c b/drivers/firmware/efi/libstub/x86-stub.c
index cef32e2c82d8..b762f7f37f28 100644
--- a/drivers/firmware/efi/libstub/x86-stub.c
+++ b/drivers/firmware/efi/libstub/x86-stub.c
@@ -1014,6 +1014,7 @@ void __noreturn efi_stub_entry(efi_handle_t handle,
 	efi_random_get_seed();
 
 	efi_retrieve_eventlog();
+	efi_bli_set_variables(image);
 
 	setup_graphics(boot_params);
 
diff --git a/include/linux/efi.h b/include/linux/efi.h
index c35446a0b66f..ecb34be37a87 100644
--- a/include/linux/efi.h
+++ b/include/linux/efi.h
@@ -957,6 +957,17 @@ extern int efi_status_to_err(efi_status_t status);
 #define   EFI_DEV_END_INSTANCE			0x01
 #define   EFI_DEV_END_ENTIRE			0xFF
 
+enum efi_hd_partition_format {
+	EFI_HD_PARTITION_FORMAT_MBR = 1,
+	EFI_HD_PARTITION_FORMAT_GPT,
+};
+
+enum efi_hd_signature_type {
+	EFI_HD_SIGNATURE_TYPE_NONE,
+	EFI_HD_SIGNATURE_TYPE_MBR,
+	EFI_HD_SIGNATURE_TYPE_GUID,
+};
+
 struct efi_generic_dev_path {
 	u8				type;
 	u8				sub_type;
@@ -988,6 +999,16 @@ struct efi_rel_offset_dev_path {
 	u64				ending_offset;
 } __packed;
 
+struct efi_hd_dev_path {
+	struct efi_generic_dev_path	header;
+	u32				partition_number;
+	u64				partition_start;
+	u64				partition_size;
+	efi_guid_t			signature;
+	u8				partition_format;
+	u8				signature_type;
+} __packed;
+
 struct efi_mem_mapped_dev_path {
 	struct efi_generic_dev_path	header;
 	u32				memory_type;
@@ -1007,6 +1028,7 @@ struct efi_dev_path {
 		struct efi_pci_dev_path		pci;
 		struct efi_vendor_dev_path	vendor;
 		struct efi_rel_offset_dev_path	rel_offset;
+		struct efi_hd_dev_path		hd;
 	};
 } __packed;
 
-- 
2.55.0.1032.g73a4cd73de-goog


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

end of thread, other threads:[~2026-09-16 14:47 UTC | newest]

Thread overview: 10+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-16 14:46 [PATCH v3 0/9] efi/libstub: Avoid UTF-16 conversion busywork Ard Biesheuvel
2026-09-16 14:46 ` [PATCH v3 1/9] lib/ucs2_string: Drop arbitrary input size limit and associated WARN() Ard Biesheuvel
2026-09-16 14:46 ` [PATCH v3 2/9] lib/ucs2_string: Suppress modinfo when __DISABLE_EXPORTS is set Ard Biesheuvel
2026-09-16 14:46 ` [PATCH v3 3/9] lib/ucs2_string: Split out ucs2_as_utf8_l() taking a separate limit Ard Biesheuvel
2026-09-16 14:46 ` [PATCH v3 4/9] efi/libstub: Use ucs2_string library for UTF-16 to UTF-8 conversion Ard Biesheuvel
2026-09-16 14:46 ` [PATCH v3 5/9] efi/libstub: Avoid efi_puts() for compile time constant strings Ard Biesheuvel
2026-09-16 14:46 ` [PATCH v3 6/9] efi/libstub: Output UTF-16 directly from vsnprintf() Ard Biesheuvel
2026-09-16 14:46 ` [PATCH v3 7/9] efi/libstub: Add support for printing human readable GUIDs Ard Biesheuvel
2026-09-16 14:46 ` [PATCH v3 8/9] efi/libstub: Add efi_snprintf() to construct wide strings Ard Biesheuvel
2026-09-16 14:46 ` [PATCH v3 9/9] efi/libstub: add initial Boot Loader Interface support Ard Biesheuvel

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).