[PATCH v2 6/6] efi: respect the poisoned pages coming from previous kernel

Breno Leitao leitao at debian.org
Fri Aug 21 03:06:06 PDT 2026


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 <leitao at debian.org>
---
 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 <leitao at debian.org>
@@ -16,6 +17,8 @@
 #include <linux/efi.h>
 #include <linux/io.h>
 #include <linux/log2.h>
+#include <linux/memblock.h>
+#include <linux/minmax.h>
 #include <linux/mm.h>
 #include <linux/overflow.h>
 
@@ -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




More information about the kexec mailing list