On kexec next kernel, walk the LINUX_EFI_POISONED_MEMORY bitmap during EFI init -- before memblock hands memory to the buddy allocator -- and memblock_reserve() every unit whose bit is set. So a frame poisoned under a previous kernel is never handed back out across a kexec. This runs from efi_config_parse_tables(), early enough to keep the frames out of memblock and the buddy allocator. Signed-off-by: Breno Leitao --- drivers/firmware/efi/efi.c | 2 ++ drivers/firmware/efi/poison.c | 71 +++++++++++++++++++++++++++++++++++++++++-- include/linux/efi.h | 2 ++ 3 files changed, 73 insertions(+), 2 deletions(-) diff --git a/drivers/firmware/efi/efi.c b/drivers/firmware/efi/efi.c index 111e60479211a..7474eeebb0add 100644 --- a/drivers/firmware/efi/efi.c +++ b/drivers/firmware/efi/efi.c @@ -812,6 +812,8 @@ int __init efi_config_parse_tables(const efi_config_table_t *config_tables, } } + efi_reserve_poisoned_memory(); + 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 2f47293a4e45f..e418c6b3aab81 100644 --- a/drivers/firmware/efi/poison.c +++ b/drivers/firmware/efi/poison.c @@ -3,8 +3,9 @@ * Runtime handling for the LINUX_EFI_POISONED_MEMORY configuration table: a * bitmap with one bit per EFI_POISON_UNIT_SIZE of physical memory that records * hardware-poisoned frames so the next kexec kernel can keep them out of its - * allocator. The stub installs the (empty) bitmap; this kernel sets bits at - * runtime. + * allocator. The stub allocates and installs the (empty) bitmap; this kernel + * sets bits at runtime; the next kernel reserves the set units before the + * allocator comes up. * * Copyright (c) 2026 Meta Platforms, Inc. and affiliates. * Copyright (c) 2026 Breno Leitao @@ -16,6 +17,8 @@ #include #include #include +#include +#include #include #include @@ -128,3 +131,67 @@ void efi_hwpoison_record_pfn(unsigned long pfn) set_bit(unit, efi_poison->bitmap); } + +void __init efi_reserve_poisoned_memory(void) +{ + u64 ppm = efi.poisoned_memory, phys_base, unit_size, bitmap_size, off; + struct linux_efi_poisoned_memory *pm; + unsigned int nr_units = 0; + + if (ppm == EFI_INVALID_TABLE_ADDR) + return; + + pm = early_memremap(ppm, sizeof(*pm)); + if (!pm) { + pr_warn("Could not map poisoned-memory table\n"); + return; + } + + if (!efi_poison_table_valid(pm)) { + /* Keep the runtime side off a table this pass rejected. */ + efi.poisoned_memory = EFI_INVALID_TABLE_ADDR; + early_memunmap(pm, sizeof(*pm)); + return; + } + + phys_base = pm->phys_base; + unit_size = pm->unit_size; + bitmap_size = pm->size; + + early_memunmap(pm, sizeof(*pm)); + + /* Reserve the table itself so it survives a further kexec. */ + memblock_reserve(PAGE_ALIGN_DOWN(ppm), + PAGE_ALIGN(ppm + sizeof(*pm) + bitmap_size) - + PAGE_ALIGN_DOWN(ppm)); + + /* + * Walk the bitmap a page at a time and reserve each poisoned unit. + * memblock.memory is not populated this early, so memblock_remove() + * and memblock_mark_nomap() would be no-ops; a reservation is what + * keeps the units away from the allocator. + */ + for (off = 0; off < bitmap_size; off += PAGE_SIZE) { + u64 chunk = min_t(u64, PAGE_SIZE, bitmap_size - off); + unsigned long bit, nbits = chunk * BITS_PER_BYTE; + unsigned long *map; + + map = early_memremap(ppm + offsetof(struct linux_efi_poisoned_memory, + bitmap) + off, chunk); + if (!map) { + pr_warn("Could not map poisoned-memory bitmap\n"); + return; + } + for_each_set_bit(bit, map, nbits) { + u64 unit = off * BITS_PER_BYTE + bit; + + memblock_reserve(phys_base + unit * unit_size, unit_size); + nr_units++; + } + early_memunmap(map, chunk); + } + + if (nr_units) + pr_info("reserved %u poisoned unit(s) (%lluK each) inherited across kexec\n", + nr_units, unit_size >> 10); +} diff --git a/include/linux/efi.h b/include/linux/efi.h index f03b1bb576133..350a3cb0babdd 100644 --- a/include/linux/efi.h +++ b/include/linux/efi.h @@ -1285,8 +1285,10 @@ struct linux_efi_poisoned_memory { #define EFI_POISON_UNIT_SIZE SZ_2M #ifdef CONFIG_EFI_POISONED_MEMORY +void efi_reserve_poisoned_memory(void); void efi_hwpoison_record_pfn(unsigned long pfn); #else +static inline void efi_reserve_poisoned_memory(void) { } static inline void efi_hwpoison_record_pfn(unsigned long pfn) { } #endif -- 2.53.0-Meta