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 "u32 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.
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 <[email protected]>
---
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 | 3 ++
include/asm-generic/vmlinux.lds.h | 42 +++++++++++++++++++++
5 files changed, 86 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..6eebac66b73b18 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" { 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..da01d6005a62af 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 u32 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 __le64 *symbol)
+{
+ return (void *)(image_base + (unsigned long)le64_to_cpup(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..cc55aa8fb0630f 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,8 @@ SECTIONS
. = ALIGN(4);
__efistub_code_size = .;
LONG(zboot_code_size);
+ __efistub_efi_image_info_offset = .;
+ LONG(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 1786a8e4323b9f..196c0f27efcd83 100644
--- a/include/asm-generic/vmlinux.lds.h
+++ b/include/asm-generic/vmlinux.lds.h
@@ -98,6 +98,48 @@
#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 u32 at
+ * __efi_image_info_offset, which is what the stub's efi_get_image_info()
+ * reads. The arch has to alias it into the stub's symbol namespace. The
+ * absolute _efi_image_info_offset is the same value, it is extracted from
+ * vmlinux with nm and injected into the zboot stub, see Makefile.zboot.
+ */
+#ifdef CONFIG_EFI_STUB_IMAGE_INFO
+#define EFI_IMAGE_INFO_ENTRY(value) \
+ LONG(DATA_LE32((value) & 0xffffffff)); \
+ LONG(DATA_LE32((value) >> 32))
+#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 = ABSOLUTE(__efi_image_info - _text); \
+ ASSERT(_efi_image_info_offset <= 0xffffffff, \
+ "EFI image-info offset does not fit in u32"); \
+ .= ALIGN(4); \
+ __efi_image_info_offset =.; \
+ LONG(_efi_image_info_offset);
+#else
+#define EFI_IMAGE_INFO(contents)
+#endif
+
/* Align . function alignment. */
#define ALIGN_FUNCTION() . = ALIGN(CONFIG_FUNCTION_ALIGNMENT)
--
2.43.0