Re: [PATCH v4 2/5] mm/memory-failure: libstub: install the poisoned-memory EFI table
From: Ard Biesheuvel
Date: Wed Sep 09 2026 - 13:36:07 EST
Hello Breno,
Apologies for chiming in late.
On Wed, 9 Sep 2026, at 15:05, Breno Leitao wrote:
> A EFI config table can only be installed while boot services are still
> up, so the stub has to create it; the running kernel can only flip bits
> in a table that already exists.
>
This is true, but that also means a config table could have a 'next' field
pointing to an allocation that was added later.
There is a EFI memreserve table based on this principle: this is a hack
that we added for the arm64 GICv3 LPI table handling, which is a braindead
piece of kit that must use the same physical allocation as the previous
kernel. It is not currently enabled on x86.
Please consider whether or not that is more suitable, and can be repurposed
or shared. (Feel free to make changes to the current format if needed).
I don't have a strong preference either way, but I feel the 2M granularity
may be a bit wasteful, no?
> Size the bitmap from the span the UEFI memory map describes, which
> efi_get_ram_range() walks since the stub has no max_pfn. Bit 0 covers
> the bottom of that span, recorded in phys_base, so a machine whose RAM
> starts high does not pay for the hole below it. Memory the firmware
> hot-adds later sits outside the span and is not carried across a kexec.
>
> One table has to serve every architecture, and they do not agree on what
> becomes RAM: x86 decides by descriptor type, arm64 by attribute. So
> efi_get_ram_range() does not filter at all and spans every descriptor in
> the map. Sizing wide only costs bitmap bytes; sizing narrow silently
> drops the records for every frame outside the span.
>
> At one bit per 2M that is 64K per TiB, and 256M at the 4PB x86
> architectural maximum. The 2M granule is called "unit" here, and the
> table carries it so the granule can change later without breaking the
> kernels already reading it.
>
> Allocate it as EFI_ACPI_RECLAIM_MEMORY so the next kernel does not take
> it for free RAM, and install it empty.
>
> A table installed by an earlier boot rides the system table across kexec
> and is reused as-is.
>
> x86 does not go through efi_stub_common(), so the generic stub and the
> x86 stub each need the call; on x86 it has to come before exit_boot(),
> which is the last point a configuration table can be installed.
>
> Signed-off-by: Breno Leitao <leitao@xxxxxxxxxx>
> ---
> drivers/firmware/efi/libstub/efi-stub-helper.c | 100 +++++++++++++++++++++++++
> drivers/firmware/efi/libstub/efi-stub.c | 1 +
> drivers/firmware/efi/libstub/efistub.h | 6 ++
> drivers/firmware/efi/libstub/x86-stub.c | 2 +
> 4 files changed, 109 insertions(+)
>
> diff --git a/drivers/firmware/efi/libstub/efi-stub-helper.c
> b/drivers/firmware/efi/libstub/efi-stub-helper.c
> index 48f93f7758e9e..5cbe675491333 100644
> --- a/drivers/firmware/efi/libstub/efi-stub-helper.c
> +++ b/drivers/firmware/efi/libstub/efi-stub-helper.c
> @@ -774,3 +774,103 @@ void efi_remap_image(unsigned long image_base,
> unsigned alloc_size,
> efi_warn("Failed to remap data region non-executable\n");
> }
> }
> +
> +#ifdef CONFIG_EFI_POISONED_MEMORY
> +/*
> + * Find the base and top of the memory, so, we can create the bitmap
> for
> + * the full range.
> + */
> +static efi_status_t efi_get_ram_range(u64 *base, u64 *top)
> +{
> + struct efi_boot_memmap *map __free(efi_pool) = NULL;
> + u64 ram_base = ULLONG_MAX, ram_top = 0;
> + efi_status_t status;
> + int i, nr_desc;
> +
> + status = efi_get_memory_map(&map, false);
> + if (status != EFI_SUCCESS)
> + return status;
> +
> + nr_desc = map->map_size / map->desc_size;
> + for (i = 0; i < nr_desc; i++) {
> + efi_memory_desc_t *d;
> +
> + d = efi_memdesc_ptr((unsigned long)map->map, map->desc_size, i);
> + ram_base = min(ram_base, d->phys_addr);
> + ram_top = max(ram_top,
> + d->phys_addr + d->num_pages * EFI_PAGE_SIZE);
> + }
> + if (!ram_top || ram_base == ULLONG_MAX)
> + return EFI_NOT_FOUND;
> +
> + *base = round_down(ram_base, EFI_POISON_UNIT_SIZE);
> + *top = round_up(ram_top, EFI_POISON_UNIT_SIZE);
> +
> + return EFI_SUCCESS;
> +}
> +
> +/* The size of the bitmap */
> +static u64 efi_poison_bitmap_size(u64 span)
> +{
> + u64 bytes = DIV_ROUND_UP(DIV_ROUND_UP(span, EFI_POISON_UNIT_SIZE),
> + BITS_PER_BYTE);
> +
> + return round_up(bytes, sizeof(unsigned long));
> +}
> +
> +static struct linux_efi_poisoned_memory *efi_poison_alloc(u64
> phys_base,
> + u64 bitmap_size)
> +{
> + struct linux_efi_poisoned_memory *pm;
> + efi_status_t status;
> +
> + status = efi_bs_call(allocate_pool, EFI_ACPI_RECLAIM_MEMORY,
> + sizeof(*pm) + bitmap_size, (void **)&pm);
> + if (status != EFI_SUCCESS)
> + return NULL;
> +
> + pm->version = 1;
> + pm->unit_size = EFI_POISON_UNIT_SIZE;
> + pm->phys_base = phys_base;
> + pm->size = bitmap_size;
> + memset(pm->bitmap, 0, bitmap_size);
> +
> + return pm;
> +}
> +
> +/* This needs to be done while boot service is still active */
> +void install_poisoned_memory_table(void)
> +{
> + efi_guid_t poisoned_memory_table_guid =
> LINUX_EFI_POISONED_MEMORY_TABLE_GUID;
> + struct linux_efi_poisoned_memory *pm;
> + u64 ram_base, ram_top, bitmap_size;
> + efi_status_t status;
> +
> + /* A table installed by an earlier boot rides the system table across
> kexec. */
> + pm = get_efi_config_table(poisoned_memory_table_guid);
> + if (pm) {
> + if (pm->version != 1)
> + efi_err("Unknown version of poisoned-memory table\n");
> + return;
> + }
> +
> + if (efi_get_ram_range(&ram_base, &ram_top) != EFI_SUCCESS) {
> + efi_err("Failed to size the poisoned-memory table!\n");
> + return;
> + }
> +
> + bitmap_size = efi_poison_bitmap_size(ram_top - ram_base);
> + pm = efi_poison_alloc(ram_base, bitmap_size);
> + if (!pm) {
> + efi_err("Failed to allocate poisoned-memory table!\n");
> + return;
> + }
> +
> + status = efi_bs_call(install_configuration_table,
> + &poisoned_memory_table_guid, pm);
> + if (status != EFI_SUCCESS) {
> + efi_bs_call(free_pool, pm);
> + efi_err("Failed to install poisoned-memory config table!\n");
> + }
> +}
> +#endif
> diff --git a/drivers/firmware/efi/libstub/efi-stub.c
> b/drivers/firmware/efi/libstub/efi-stub.c
> index 235c9738da2d6..22a315e2814a1 100644
> --- a/drivers/firmware/efi/libstub/efi-stub.c
> +++ b/drivers/firmware/efi/libstub/efi-stub.c
> @@ -179,6 +179,7 @@ efi_status_t efi_stub_common(efi_handle_t handle,
> EFI_RT_SUPPORTED_SET_VIRTUAL_ADDRESS_MAP);
>
> install_memreserve_table();
> + install_poisoned_memory_table();
>
> status = efi_boot_kernel(handle, image, image_addr, cmdline_ptr);
>
> diff --git a/drivers/firmware/efi/libstub/efistub.h
> b/drivers/firmware/efi/libstub/efistub.h
> index fd91fc15ec810..44436869c4efe 100644
> --- a/drivers/firmware/efi/libstub/efistub.h
> +++ b/drivers/firmware/efi/libstub/efistub.h
> @@ -1169,6 +1169,12 @@ efi_enable_reset_attack_mitigation(void) { }
>
> void efi_retrieve_eventlog(void);
>
> +#ifdef CONFIG_EFI_POISONED_MEMORY
> +void install_poisoned_memory_table(void);
> +#else
> +static inline void install_poisoned_memory_table(void) { }
> +#endif
> +
> struct sysfb_display_info *alloc_primary_display(void);
> struct sysfb_display_info *__alloc_primary_display(void);
> void free_primary_display(struct sysfb_display_info *dpy);
> diff --git a/drivers/firmware/efi/libstub/x86-stub.c
> b/drivers/firmware/efi/libstub/x86-stub.c
> index 0bae0f06b6763..3136132b9628a 100644
> --- a/drivers/firmware/efi/libstub/x86-stub.c
> +++ b/drivers/firmware/efi/libstub/x86-stub.c
> @@ -1024,6 +1024,8 @@ void __noreturn efi_stub_entry(efi_handle_t
> handle,
>
> setup_unaccepted_memory();
>
> + install_poisoned_memory_table();
> +
> status = exit_boot(boot_params, handle);
> if (status != EFI_SUCCESS) {
> efi_err("exit_boot() failed!\n");
>
> --
> 2.53.0-Meta