On the next kernel, walk the LINUX_EFI_POISONED_MEMORY table during EFI init -- before memblock hands memory to the buddy allocator -- and memblock_reserve() each recorded frame, so a frame poisoned under a previous kernel is never handed back out across a kexec. Entries a later unpoison cleared are skipped. This mirrors the memreserve consume loop and runs from the same place, early enough to keep the frames out of memblock and the buddy allocator. Signed-off-by: Breno Leitao --- drivers/firmware/efi/efi.c | 3 +++ drivers/firmware/efi/poison.c | 53 +++++++++++++++++++++++++++++++++++++++++++ include/linux/efi.h | 2 ++ 3 files changed, 58 insertions(+) diff --git a/drivers/firmware/efi/efi.c b/drivers/firmware/efi/efi.c index f3b799930be44..1db041dce61ec 100644 --- a/drivers/firmware/efi/efi.c +++ b/drivers/firmware/efi/efi.c @@ -808,6 +808,9 @@ int __init efi_config_parse_tables(const efi_config_table_t *config_tables, } } + if (efi_reserve_poisoned_memory()) + return -ENOMEM; + if (rt_prop != EFI_INVALID_TABLE_ADDR) { efi_rt_properties_table_t *tbl; diff --git a/drivers/firmware/efi/poison.c b/drivers/firmware/efi/poison.c index ed3b8cee21ba6..971f131e6c556 100644 --- a/drivers/firmware/efi/poison.c +++ b/drivers/firmware/efi/poison.c @@ -4,6 +4,10 @@ * record and clear poisoned frames so the next kexec kernel can keep them out * of its allocator. * + * Handling for the LINUX_EFI_POISONED_MEMORY configuration table: record and + * clear poisoned frames at runtime, and reserve frames inherited across a kexec + * before the allocator comes up, so the next kernel keeps them out. + * * Copyright (c) 2026 Meta Platforms, Inc. and affiliates. * Copyright (c) 2026 Breno Leitao */ @@ -12,7 +16,9 @@ #include #include +#include #include +#include #include #include @@ -125,3 +131,50 @@ void efi_hwpoison_unrecord_pfn(unsigned long pfn) memunmap(pm); } } + +/* + * Reserve every frame recorded in the poisoned-memory table inherited across + * kexec, before the page allocator is up. Called from efi_config_parse_tables(). + */ +int __init efi_reserve_poisoned_memory(void) +{ + unsigned long ppm = efi.poisoned_memory; + unsigned int nr_poison = 0; + int i; + + if (ppm == EFI_INVALID_TABLE_ADDR) + return 0; + + while (ppm) { + struct linux_efi_poisoned_memory *pm; + u8 *p; + + p = early_memremap(ALIGN_DOWN(ppm, PAGE_SIZE), PAGE_SIZE); + if (!p) { + pr_err("Could not map poisoned-memory entry!\n"); + return -ENOMEM; + } + + pm = (void *)(p + ppm % PAGE_SIZE); + + /* reserve the list entry itself */ + memblock_reserve(ppm, struct_size(pm, entry, pm->size)); + + for (i = 0; i < atomic_read(&pm->count); i++) { + /* skip entries cleared by a later unpoison */ + if (pm->entry[i].size) { + memblock_reserve(pm->entry[i].base, + pm->entry[i].size); + nr_poison++; + } + } + + ppm = pm->next; + early_memunmap(p, PAGE_SIZE); + } + + if (nr_poison) + pr_info("reserved %u hardware-poisoned frame(s) inherited across kexec\n", + nr_poison); + return 0; +} diff --git a/include/linux/efi.h b/include/linux/efi.h index bd1b0934881fb..3dcfce7f593fb 100644 --- a/include/linux/efi.h +++ b/include/linux/efi.h @@ -1293,9 +1293,11 @@ struct linux_efi_poisoned_memory { / sizeof_field(struct linux_efi_poisoned_memory, entry[0])) #ifdef CONFIG_EFI_POISONED_MEMORY +int efi_reserve_poisoned_memory(void); void efi_hwpoison_record_pfn(unsigned long pfn); void efi_hwpoison_unrecord_pfn(unsigned long pfn); #else +static inline int efi_reserve_poisoned_memory(void) { return 0; } static inline void efi_hwpoison_record_pfn(unsigned long pfn) { } static inline void efi_hwpoison_unrecord_pfn(unsigned long pfn) { } #endif -- 2.53.0-Meta