[PATCH v2 04/15] efi/libstub: Add a general way to get symbols from the vmlinux into zboot

Jason Gunthorpe jgg at nvidia.com
Fri Oct 2 16:31:41 PDT 2026


Currently the zboot stub has no access to symbols in the vmlinux. In
contrast x86 has a voffsets.h header that allows the stub to know whatever
symbols it wants for the decompressed image. ARM DRTM needs another 4
symbol locations to work, and it also wants to compile the DRTM libstub
code once and have it work in both zboot and embedded.

The current solution in arm is "code_size" from commit 45dd403da851
("efi/zboot: arm64: Inject kernel code size symbol into the zboot
payload"). This is neat, but it makes the zboot and embedded cases
work differently.

The x86 voffsets.h approach is also nice, but it can only work in zboot
since it relies on 'nm vmlinux' to extract the locations. That's a
circular dependency for the embedded stub.

One idea that meets the requirements is to embed a struct of offsets
inside the vmlinux and then have both stubs know its offset from the start
of the image.

First, each arch defines what information it wants the stub to have in a
struct:

       struct efi_image_info {
	       __le64 code_size;
       };

Second, it populates the struct with the linker:

       EFI_IMAGE_INFO(
	       /* code_size */
	       EFI_IMAGE_INFO_OFFSET(__inittext_end)
       )

This approach with the linker is guaranteed to emit a value and not a
relocation. I looked at writing the struct initializer in C, but that can
result in relocations inside the struct. That seemed sketchy so I stuck
with a linker script approach.

Finally, any stub code, either in zboot or embedded can access the struct
through:

  const struct efi_image_info *efi_get_image_info(unsigned long image_base);

The makefiles arrange that both links have a "u64 __efi_image_info_offset"
symbol that contains the value. For vmlinux this links to a symbol created
by the EFI_IMAGE_INFO() macro, for zboot it uses the same technique as
code_size through the zboot.lds.

Since this needs arch changes, protect it under
CONFIG_EFI_STUB_IMAGE_INFO. The arch should define the struct and use the
linker macro before enabling that config.

Since everything is linked together, and the actual location of the struct
is not fixed, this is all private within the kernel build and can't become
ABI to any bootloader.

As some follow-up work, it seems interesting to standardize on this
mechanism and remove the PROVIDE() and voffset.h alternatives. This would
also avoid needing to pass sysfb_primary_display through EFI from zboot,
for example.

Signed-off-by: Jason Gunthorpe <jgg at nvidia.com>
---
 drivers/firmware/efi/Kconfig                |  3 ++
 drivers/firmware/efi/libstub/Makefile.zboot |  9 +++++
 drivers/firmware/efi/libstub/efistub.h      | 29 ++++++++++++++++
 drivers/firmware/efi/libstub/zboot.lds      |  4 +++
 include/asm-generic/vmlinux.lds.h           | 38 +++++++++++++++++++++
 5 files changed, 83 insertions(+)

diff --git a/drivers/firmware/efi/Kconfig b/drivers/firmware/efi/Kconfig
index 29e0729299f5bd..3d6fb3ca2806ca 100644
--- a/drivers/firmware/efi/Kconfig
+++ b/drivers/firmware/efi/Kconfig
@@ -72,6 +72,9 @@ config EFI_RUNTIME_WRAPPERS
 config EFI_GENERIC_STUB
 	bool
 
+config EFI_STUB_IMAGE_INFO
+	bool
+
 config EFI_ZBOOT
 	bool "Enable the generic EFI decompressor"
 	depends on EFI_GENERIC_STUB && !ARM
diff --git a/drivers/firmware/efi/libstub/Makefile.zboot b/drivers/firmware/efi/libstub/Makefile.zboot
index 832deee36e48e9..42408132249ae7 100644
--- a/drivers/firmware/efi/libstub/Makefile.zboot
+++ b/drivers/firmware/efi/libstub/Makefile.zboot
@@ -26,8 +26,17 @@ zboot-size-len-$(CONFIG_KERNEL_ZSTD)	:= 4
 $(obj)/vmlinuz: $(obj)/vmlinux.bin FORCE
 	$(call if_changed,$(zboot-method-y))
 
+# Architectures may expose a private image information structure to the EFI
+# stub. Inject its offset into the zboot executable so it remains available
+# after the payload has been compressed.
+efi-zboot-objcopy-flags-$(CONFIG_EFI_STUB_IMAGE_INFO) = \
+	--add-symbol efi_zboot_image_info_offset=0x$$( \
+		$(NM) vmlinux | \
+		awk '$$3 == "_efi_image_info_offset_value" { print $$1 }')
+
 # avoid eager evaluation to prevent references to non-existent build artifacts
 OBJCOPYFLAGS_vmlinuz.o = -I binary -O $(EFI_ZBOOT_BFD_TARGET) $(EFI_ZBOOT_OBJCOPY_FLAGS) \
+			  $(efi-zboot-objcopy-flags-y) \
 			  --rename-section .data=.gzdata,load,alloc,readonly,contents
 $(obj)/vmlinuz.o: $(obj)/vmlinuz FORCE
 	$(call if_changed,objcopy)
diff --git a/drivers/firmware/efi/libstub/efistub.h b/drivers/firmware/efi/libstub/efistub.h
index fd91fc15ec810b..d15cca8d6ff8be 100644
--- a/drivers/firmware/efi/libstub/efistub.h
+++ b/drivers/firmware/efi/libstub/efistub.h
@@ -1176,6 +1176,35 @@ void free_primary_display(struct sysfb_display_info *dpy);
 void efi_cache_sync_image(unsigned long image_base,
 			  unsigned long alloc_size);
 
+#ifdef CONFIG_EFI_STUB_IMAGE_INFO
+struct efi_image_info;
+
+static inline const struct efi_image_info *
+efi_get_image_info(unsigned long image_base)
+{
+	/*
+	 * The offset of the struct efi_image_info from the start of the kernel
+	 * image. The linker script of whatever is embedding the stub emits this
+	 * word, see EFI_IMAGE_INFO() for the vmlinux case and zboot.lds for the
+	 * zboot case.
+	 */
+	extern const u64 efi_image_info_offset;
+
+	return (const struct efi_image_info *)(image_base +
+					      efi_image_info_offset);
+}
+
+static inline void *__efi_get_image_symbol(unsigned long image_base,
+					   const u64 *symbol)
+{
+	return (void *)(image_base + *symbol);
+}
+
+#define efi_get_image_symbol(image_base, symbol) \
+	__efi_get_image_symbol(image_base,       \
+			       &efi_get_image_info(image_base)->symbol)
+#endif
+
 struct efi_smbios_record {
 	u8	type;
 	u8	length;
diff --git a/drivers/firmware/efi/libstub/zboot.lds b/drivers/firmware/efi/libstub/zboot.lds
index 367907eb7d8698..f389aaadbe44bd 100644
--- a/drivers/firmware/efi/libstub/zboot.lds
+++ b/drivers/firmware/efi/libstub/zboot.lds
@@ -3,6 +3,7 @@
 ENTRY(__efistub_efi_zboot_header);
 
 PROVIDE(zboot_code_size = ABSOLUTE(0));
+PROVIDE(efi_zboot_image_info_offset = ABSOLUTE(0));
 
 SECTIONS
 {
@@ -24,6 +25,9 @@ SECTIONS
 		. = ALIGN(4);
 		__efistub_code_size = .;
 		LONG(zboot_code_size);
+		. = ALIGN(8);
+		__efistub_efi_image_info_offset = .;
+		QUAD(efi_zboot_image_info_offset);
 
 		_etext = ALIGN(4096);
 		. = _etext;
diff --git a/include/asm-generic/vmlinux.lds.h b/include/asm-generic/vmlinux.lds.h
index b2988aa12f6645..4dd52ddb148401 100644
--- a/include/asm-generic/vmlinux.lds.h
+++ b/include/asm-generic/vmlinux.lds.h
@@ -83,6 +83,44 @@
 #define RO_EXCEPTION_TABLE
 #endif
 
+/*
+ * Architecture specific information made available to the EFI stub, see
+ * CONFIG_EFI_STUB_IMAGE_INFO. The architecture describes the data with a
+ * struct efi_image_info in its asm/image.h and emits the matching bytes here
+ * through contents, using linker expressions the compiler cannot compute.
+ *
+ * Place in the same section as INIT_DATA.
+ *
+ * The architecture must also define EFI_IMAGE_INFO_SIZE to sizeof(struct
+ * efi_image_info), and that definition has to be visible to the linker script
+ * before this macro is used. Keeping the two in sync is checked by a
+ * static_assert() next to the struct and by the ASSERT() below.
+ *
+ * The struct's offset from _text is emitted as a u64 at
+ * __efi_image_info_offset. The architecture aliases this location to
+ * __efistub_efi_image_info_offset for the embedded stub. The absolute
+ * _efi_image_info_offset_value contains the offset itself; Makefile.zboot
+ * extracts it with nm and zboot.lds emits it for the compressed image.
+ */
+#ifdef CONFIG_EFI_STUB_IMAGE_INFO
+#define EFI_IMAGE_INFO_ENTRY(value) QUAD(value)
+#define EFI_IMAGE_INFO_OFFSET(symbol) EFI_IMAGE_INFO_ENTRY(symbol - _text)
+
+#define EFI_IMAGE_INFO(contents)                                               \
+	. = ALIGN(8);                                                          \
+	__efi_image_info = .;                                                  \
+	contents;                                                              \
+	__efi_image_info_end = .;                                              \
+	ASSERT(__efi_image_info_end - __efi_image_info == EFI_IMAGE_INFO_SIZE, \
+	       "invalid EFI image-info size");                                 \
+	_efi_image_info_offset_value = ABSOLUTE(__efi_image_info - _text);     \
+	. = ALIGN(8);                                                          \
+	__efi_image_info_offset = .;                                           \
+	QUAD(_efi_image_info_offset_value);
+#else
+#define EFI_IMAGE_INFO(contents)
+#endif
+
 /* Align . function alignment. */
 #define ALIGN_FUNCTION()  . = ALIGN(CONFIG_FUNCTION_ALIGNMENT)
 
-- 
2.43.0




More information about the linux-riscv mailing list