[PATCH v5 4/9] mm/memory-failure: efi: adopt the inherited poisoned-memory table

From: Breno Leitao

Date: Tue Sep 15 2026 - 09:26:25 EST


A table installed by an earlier boot rides the EFI system table into this
kernel, but nothing looks at it yet.

Take it into use from efi_config_parse_tables(). The table can come from
any kernel further up the kexec chain, so vet the header first: the
version, whole-word bitmap size, a bit count and a footprint that can be
taken without wrapping, and a power-of-two unit the base is aligned to.
A table that fails any of those is dropped rather than trusted.

The table is EFI ACPI reclaim memory, which x86 turns into E820_TYPE_ACPI
and leaves out of memblock, so it never reaches the direct map and
touching it later faults. Hand its pages to memblock the way commit
8dbe33956d96 ("efi/unaccepted: Make sure unaccepted table is mapped")
does for the unaccepted memory table, so everything afterwards can reach
it with phys_to_virt().

Signed-off-by: Breno Leitao <leitao@xxxxxxxxxx>
---
drivers/firmware/efi/Makefile | 1 +
drivers/firmware/efi/efi.c | 2 +
drivers/firmware/efi/poison.c | 93 +++++++++++++++++++++++++++++++++++++++++++
include/linux/efi.h | 6 +++
4 files changed, 102 insertions(+)

diff --git a/drivers/firmware/efi/Makefile b/drivers/firmware/efi/Makefile
index 8efbcf699e4ff9..05d0a490923e56 100644
--- a/drivers/firmware/efi/Makefile
+++ b/drivers/firmware/efi/Makefile
@@ -43,4 +43,5 @@ obj-$(CONFIG_EFI_EARLYCON) += earlycon.o
obj-$(CONFIG_UEFI_CPER_ARM) += cper-arm.o
obj-$(CONFIG_UEFI_CPER_X86) += cper-x86.o
obj-$(CONFIG_UNACCEPTED_MEMORY) += unaccepted_memory.o
+obj-$(CONFIG_EFI_POISONED_MEMORY) += poison.o
obj-$(CONFIG_TEE_STMM_EFI) += stmm/tee_stmm_efi.o
diff --git a/drivers/firmware/efi/efi.c b/drivers/firmware/efi/efi.c
index af1fa443839c4d..55b2ee53fc2688 100644
--- a/drivers/firmware/efi/efi.c
+++ b/drivers/firmware/efi/efi.c
@@ -883,6 +883,8 @@ int __init efi_config_parse_tables(const efi_config_table_t *config_tables,
}
}

+ efi_poisoned_memory_reserve();
+
return 0;
}

diff --git a/drivers/firmware/efi/poison.c b/drivers/firmware/efi/poison.c
new file mode 100644
index 00000000000000..3f12db3dc9b844
--- /dev/null
+++ b/drivers/firmware/efi/poison.c
@@ -0,0 +1,93 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Runtime side of the LINUX_EFI_POISONED_MEMORY table: one bit per
+ * EFI_POISON_UNIT_SIZE, set here as frames go bad, honored by the next kernel.
+ *
+ * Copyright (c) 2026 Meta Platforms, Inc. and affiliates.
+ * Copyright (c) 2026 Breno Leitao <leitao@xxxxxxxxxx>
+ */
+
+#define pr_fmt(fmt) "efi: " fmt
+
+#include <linux/bitmap.h>
+#include <linux/efi.h>
+#include <linux/io.h>
+#include <linux/log2.h>
+#include <linux/memblock.h>
+#include <linux/mm.h>
+#include <linux/overflow.h>
+
+static bool __init
+efi_poison_geometry_valid(const struct linux_efi_poisoned_memory *pm)
+{
+ u64 nbits, end;
+
+ /* Whole words, and a bit count that can be taken without wrapping. */
+ if (!pm->size || !IS_ALIGNED(pm->size, sizeof(unsigned long)) ||
+ check_mul_overflow(pm->size, (u64)BITS_PER_BYTE, &nbits))
+ return false;
+
+ /* And a footprint that can be page aligned without wrapping either. */
+ if (check_add_overflow(efi.poisoned_memory, sizeof(*pm) + pm->size,
+ &end) || end > PHYS_ADDR_MAX - PAGE_SIZE)
+ return false;
+
+ if (pm->unit_size < PAGE_SIZE || !is_power_of_2(pm->unit_size))
+ return false;
+
+ return IS_ALIGNED(pm->phys_base, pm->unit_size);
+}
+
+/* The table may come from an earlier kernel, so vet it before using it. */
+static bool __init
+efi_poison_table_valid(const struct linux_efi_poisoned_memory *pm)
+{
+ if (pm->version != 1) {
+ pr_warn("Ignoring poisoned-memory table with version %u\n",
+ pm->version);
+ return false;
+ }
+
+ if (!efi_poison_geometry_valid(pm)) {
+ pr_warn("Ignoring malformed poisoned-memory table\n");
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ * Vet the inherited table and hand its pages to memblock, the way the
+ * unaccepted memory table is handled. It is EFI ACPI reclaim memory, which
+ * becomes E820_TYPE_ACPI and would otherwise stay out of the direct map, and
+ * touching it then faults. Called from efi_config_parse_tables(), so
+ * everything later can reach it with efi_poisoned_memory().
+ */
+void __init efi_poisoned_memory_reserve(void)
+{
+ struct linux_efi_poisoned_memory *pm;
+ phys_addr_t start, end;
+
+ if (efi.poisoned_memory == EFI_INVALID_TABLE_ADDR)
+ return;
+
+ pm = early_memremap(efi.poisoned_memory, sizeof(*pm));
+ if (!pm) {
+ pr_warn("Could not map poisoned-memory table\n");
+ efi.poisoned_memory = EFI_INVALID_TABLE_ADDR;
+ return;
+ }
+
+ if (!efi_poison_table_valid(pm)) {
+ efi.poisoned_memory = EFI_INVALID_TABLE_ADDR;
+ early_memunmap(pm, sizeof(*pm));
+ return;
+ }
+
+ start = PAGE_ALIGN_DOWN(efi.poisoned_memory);
+ end = PAGE_ALIGN(efi.poisoned_memory + sizeof(*pm) + pm->size);
+ early_memunmap(pm, sizeof(*pm));
+
+ memblock_add(start, end - start);
+ memblock_reserve(start, end - start);
+}
diff --git a/include/linux/efi.h b/include/linux/efi.h
index efaf63f9a54edd..dd3263456dd4a3 100644
--- a/include/linux/efi.h
+++ b/include/linux/efi.h
@@ -1286,6 +1286,12 @@ struct linux_efi_poisoned_memory {

#define EFI_POISON_UNIT_SIZE SZ_2M

+#ifdef CONFIG_EFI_POISONED_MEMORY
+void __init efi_poisoned_memory_reserve(void);
+#else
+static inline void efi_poisoned_memory_reserve(void) { }
+#endif
+
void __init efi_arch_mem_reserve(phys_addr_t addr, u64 size);

/*

--
2.53.0-Meta