* [PATCH RFC 0/3] efi: mm/memory-failure: keep hardware-poisoned pages out of the next kexec
@ 2026-07-17 14:03 Breno Leitao
2026-07-17 14:03 ` [PATCH RFC 1/3] efi: add the LINUX_EFI_POISONED_MEMORY configuration table Breno Leitao
` (2 more replies)
0 siblings, 3 replies; 4+ messages in thread
From: Breno Leitao @ 2026-07-17 14:03 UTC (permalink / raw)
To: kas, Ard Biesheuvel, Ilias Apalodimas, Miaohe Lin,
Naoya Horiguchi, Andrew Morton
Cc: linux-efi, linux-kernel, kexec, rneu, riel, caggio, anilagrawal,
rmikey, linux-mm, Breno Leitao, kernel-team
Problem:
========
When a page is hard-offlined due to an uncorrectable memory error (multi
bit ECC), memory_failure() sets PG_hwpoison, unmaps it, removes it from
the buddy allocator. This information is not carried to the next kernel
that is kexeced. The new kernel kexecs and trip over that bad memory
bank _again_.
Why now:
========
Several industry trends make this increasingly important:
1) DRAM is getting more expensive
2) soldered / on-package memory (LPDDR, HBM) is becoming more common, so a
failing part can no longer simply be swapped;
3) memory is kept in service far longer (at Meta, DRAM lifetime is being
drastically extended).
4) It is more and more common to kexec instead of full reboot
5) Increase of memory per system with CXL
Proposed Solution:
==================
Carry the poisoned frames to the next kernel in a new EFI configuration
table, LINUX_EFI_POISONED_MEMORY, modeled on the existing
LINUX_EFI_MEMRESERVE table.
EFI configuration tables already survive kexec: firmware hands the EFI
system table to every kernel in the chain, so a table installed once is
seen by all successors without a new handover channel.
The mechanism is architecture independent, so x86 and arm64 use the same
code.
The EFI stub installs an empty table while EFI boot service is up. A
configuration table can only be created there; the running kernel can only
append to it.
Each hard-offlined frame is appended; an unpoison "removes" its entry
so a frame that is good again is not carried forward.
The next kernel walks the inherited table early in
efi_config_parse_tables(), before memblock and the buddy allocator are
up, and memblock_reserve()s every recorded frame. The bad RAM is never
handed out.
The series is three patches:
1) add the LINUX_EFI_POISONED_MEMORY table and install it from the stub
2) record poisoned frames into the table from the memory_failure() path
3) reserve the inherited frames before the allocator comes up
This was initially discussed at
https://lore.kernel.org/all/ajut_LDQGYCShApx@gmail.com/
A special thanks to Kiryl Shutsemau, for feedbacks and suggestions.
Signed-off-by: Breno Leitao <leitao@debian.org>
---
Breno Leitao (3):
efi: add the LINUX_EFI_POISONED_MEMORY configuration table
efi: record hardware-poisoned frames into the poisoned-memory table
efi: reserve inherited poisoned frames before the allocator comes up
drivers/firmware/efi/Kconfig | 10 ++
drivers/firmware/efi/Makefile | 1 +
drivers/firmware/efi/efi.c | 5 +
drivers/firmware/efi/libstub/efi-stub-helper.c | 30 +++++
drivers/firmware/efi/libstub/efi-stub.c | 1 +
drivers/firmware/efi/libstub/efistub.h | 6 +
drivers/firmware/efi/libstub/x86-stub.c | 2 +
drivers/firmware/efi/poison.c | 180 +++++++++++++++++++++++++
include/linux/efi.h | 31 +++++
mm/memory-failure.c | 6 +-
10 files changed, 271 insertions(+), 1 deletion(-)
---
base-commit: b8809969e1d7a591e0f49dd464a5d04b3cf02ab1
change-id: 20260622-hwpoison-kho-fc9db2ada8ba
Best regards,
--
Breno Leitao <leitao@debian.org>
^ permalink raw reply [flat|nested] 4+ messages in thread
* [PATCH RFC 1/3] efi: add the LINUX_EFI_POISONED_MEMORY configuration table
2026-07-17 14:03 [PATCH RFC 0/3] efi: mm/memory-failure: keep hardware-poisoned pages out of the next kexec Breno Leitao
@ 2026-07-17 14:03 ` Breno Leitao
2026-07-17 14:03 ` [PATCH RFC 2/3] efi: record hardware-poisoned frames into the poisoned-memory table Breno Leitao
2026-07-17 14:03 ` [PATCH RFC 3/3] efi: reserve inherited poisoned frames before the allocator comes up Breno Leitao
2 siblings, 0 replies; 4+ messages in thread
From: Breno Leitao @ 2026-07-17 14:03 UTC (permalink / raw)
To: kas, Ard Biesheuvel, Ilias Apalodimas, Miaohe Lin,
Naoya Horiguchi, Andrew Morton
Cc: linux-efi, linux-kernel, kexec, rneu, riel, caggio, anilagrawal,
rmikey, linux-mm, Breno Leitao, kernel-team
Hardware-poisoned page frames are tracked only in the running kernel's data
structures, so a kexec loses them and the next kernel hands the known-bad RAM
back out.
Add an EFI configuration table to carry that list to the next kernel. It uses
the same growable linked-list layout as LINUX_EFI_MEMRESERVE and, like it, is
installed empty by the stub while boot services are still available -- a
running kernel can only append to the list, not install a new config table --
and rides the EFI system table across kexec.
Define the table and install the empty root here; the record and reserve
paths follow.
Signed-off-by: Breno Leitao <leitao@debian.org>
---
drivers/firmware/efi/Kconfig | 10 +++++++++
drivers/firmware/efi/efi.c | 2 ++
drivers/firmware/efi/libstub/efi-stub-helper.c | 30 ++++++++++++++++++++++++++
drivers/firmware/efi/libstub/efi-stub.c | 1 +
drivers/firmware/efi/libstub/efistub.h | 6 ++++++
drivers/firmware/efi/libstub/x86-stub.c | 2 ++
include/linux/efi.h | 21 ++++++++++++++++++
7 files changed, 72 insertions(+)
diff --git a/drivers/firmware/efi/Kconfig b/drivers/firmware/efi/Kconfig
index 29e0729299f5b..0e4ccfd968b87 100644
--- a/drivers/firmware/efi/Kconfig
+++ b/drivers/firmware/efi/Kconfig
@@ -263,6 +263,16 @@ config EFI_COCO_SECRET
virt/coco/efi_secret module to access the secrets, which in turn
allows userspace programs to access the injected secrets.
+config EFI_POISONED_MEMORY
+ bool "Carry hardware-poisoned pages across kexec"
+ depends on EFI_STUB && MEMORY_FAILURE
+ help
+ Record page frames that are hardware-poisoned while this kernel runs
+ into an EFI configuration table, and honor that table early on the
+ next kernel so a kexec does not hand known-bad RAM back out.
+
+ If unsure, say N.
+
config OVMF_DEBUG_LOG
bool "Expose OVMF firmware debug log via sysfs"
depends on EFI
diff --git a/drivers/firmware/efi/efi.c b/drivers/firmware/efi/efi.c
index 0327a39d31fa5..f3b799930be44 100644
--- a/drivers/firmware/efi/efi.c
+++ b/drivers/firmware/efi/efi.c
@@ -55,6 +55,7 @@ struct efi __read_mostly efi = {
#ifdef CONFIG_UNACCEPTED_MEMORY
.unaccepted = EFI_INVALID_TABLE_ADDR,
#endif
+ .poisoned_memory = EFI_INVALID_TABLE_ADDR,
};
EXPORT_SYMBOL(efi);
@@ -629,6 +630,7 @@ static const efi_config_table_type_t common_tables[] __initconst = {
{EFI_TCG2_FINAL_EVENTS_TABLE_GUID, &efi.tpm_final_log, "TPMFinalLog" },
{EFI_CC_FINAL_EVENTS_TABLE_GUID, &efi.tpm_final_log, "CCFinalLog" },
{LINUX_EFI_MEMRESERVE_TABLE_GUID, &mem_reserve, "MEMRESERVE" },
+ {LINUX_EFI_POISONED_MEMORY_TABLE_GUID, &efi.poisoned_memory, "POISON" },
{LINUX_EFI_INITRD_MEDIA_GUID, &initrd, "INITRD" },
{EFI_RT_PROPERTIES_TABLE_GUID, &rt_prop, "RTPROP" },
#ifdef CONFIG_OVMF_DEBUG_LOG
diff --git a/drivers/firmware/efi/libstub/efi-stub-helper.c b/drivers/firmware/efi/libstub/efi-stub-helper.c
index f27f2e1f00199..2d873bccac481 100644
--- a/drivers/firmware/efi/libstub/efi-stub-helper.c
+++ b/drivers/firmware/efi/libstub/efi-stub-helper.c
@@ -774,3 +774,33 @@ void efi_remap_image(unsigned long image_base, unsigned alloc_size,
efi_warn("Failed to remap data region non-executable\n");
}
}
+
+/*
+ * Install an empty root for the poisoned-memory table now, while boot services
+ * are available; the running kernel can only append to it later, not install a
+ * new config table. Shared by all stubs (x86 and the generic stub).
+ */
+#ifdef CONFIG_EFI_POISONED_MEMORY
+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;
+ efi_status_t status;
+
+ status = efi_bs_call(allocate_pool, EFI_LOADER_DATA, sizeof(*pm),
+ (void **)&pm);
+ if (status != EFI_SUCCESS) {
+ efi_err("Failed to allocate poisoned-memory entry!\n");
+ return;
+ }
+
+ pm->next = 0;
+ pm->size = 0;
+ atomic_set(&pm->count, 0);
+
+ status = efi_bs_call(install_configuration_table,
+ &poisoned_memory_table_guid, pm);
+ if (status != EFI_SUCCESS)
+ 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 42d6073bcd062..008635eb5027a 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 cef32e2c82d8f..f90de11bc8855 100644
--- a/drivers/firmware/efi/libstub/x86-stub.c
+++ b/drivers/firmware/efi/libstub/x86-stub.c
@@ -1023,6 +1023,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");
diff --git a/include/linux/efi.h b/include/linux/efi.h
index b3c83516593d1..7787eb8d4e4c1 100644
--- a/include/linux/efi.h
+++ b/include/linux/efi.h
@@ -422,6 +422,7 @@ void efi_native_runtime_setup(void);
#define LINUX_EFI_COCO_SECRET_AREA_GUID EFI_GUID(0xadf956ad, 0xe98c, 0x484c, 0xae, 0x11, 0xb5, 0x1c, 0x7d, 0x33, 0x64, 0x47)
#define LINUX_EFI_BOOT_MEMMAP_GUID EFI_GUID(0x800f683f, 0xd08b, 0x423a, 0xa2, 0x93, 0x96, 0x5c, 0x3c, 0x6f, 0xe2, 0xb4)
#define LINUX_EFI_UNACCEPTED_MEM_TABLE_GUID EFI_GUID(0xd5d1de3c, 0x105c, 0x44f9, 0x9e, 0xa9, 0xbc, 0xef, 0x98, 0x12, 0x00, 0x31)
+#define LINUX_EFI_POISONED_MEMORY_TABLE_GUID EFI_GUID(0x78a5bf07, 0x7d2a, 0x2889, 0x16, 0x31, 0x63, 0xd4, 0x56, 0xf2, 0x83, 0x50)
#define RISCV_EFI_BOOT_PROTOCOL_GUID EFI_GUID(0xccd15fec, 0x6f73, 0x4eec, 0x83, 0x95, 0x3e, 0x69, 0xe4, 0xb9, 0x40, 0xbf)
@@ -650,6 +651,7 @@ extern struct efi {
unsigned long mokvar_table; /* MOK variable config table */
unsigned long coco_secret; /* Confidential computing secret table */
unsigned long unaccepted; /* Unaccepted memory table */
+ unsigned long poisoned_memory; /* Hardware-poisoned memory table */
efi_get_time_t *get_time;
efi_set_time_t *set_time;
@@ -1271,6 +1273,25 @@ struct linux_efi_memreserve {
#define EFI_MEMRESERVE_COUNT(size) (((size) - sizeof(struct linux_efi_memreserve)) \
/ sizeof_field(struct linux_efi_memreserve, entry[0]))
+/*
+ * Frames hardware-poisoned while a kernel runs are recorded here so a kexec'd
+ * kernel can keep them out of its allocator. Same growable linked-list shape
+ * as linux_efi_memreserve; carried across kexec via the EFI system table.
+ */
+struct linux_efi_poisoned_memory {
+ int size; // allocated size of the array
+ atomic_t count; // number of entries used
+ phys_addr_t next; // pa of next struct instance
+ struct {
+ phys_addr_t base;
+ phys_addr_t size;
+ } entry[];
+};
+
+#define EFI_POISONED_MEMORY_COUNT(size) \
+ (((size) - sizeof(struct linux_efi_poisoned_memory)) \
+ / sizeof_field(struct linux_efi_poisoned_memory, entry[0]))
+
void __init efi_arch_mem_reserve(phys_addr_t addr, u64 size);
/*
--
2.53.0-Meta
^ permalink raw reply [flat|nested] 4+ messages in thread
* [PATCH RFC 2/3] efi: record hardware-poisoned frames into the poisoned-memory table
2026-07-17 14:03 [PATCH RFC 0/3] efi: mm/memory-failure: keep hardware-poisoned pages out of the next kexec Breno Leitao
2026-07-17 14:03 ` [PATCH RFC 1/3] efi: add the LINUX_EFI_POISONED_MEMORY configuration table Breno Leitao
@ 2026-07-17 14:03 ` Breno Leitao
2026-07-17 14:03 ` [PATCH RFC 3/3] efi: reserve inherited poisoned frames before the allocator comes up Breno Leitao
2 siblings, 0 replies; 4+ messages in thread
From: Breno Leitao @ 2026-07-17 14:03 UTC (permalink / raw)
To: kas, Ard Biesheuvel, Ilias Apalodimas, Miaohe Lin,
Naoya Horiguchi, Andrew Morton
Cc: linux-efi, linux-kernel, kexec, rneu, riel, caggio, anilagrawal,
rmikey, linux-mm, Breno Leitao, kernel-team
num_poisoned_pages_inc() and num_poisoned_pages_sub() are the single choke
point for every poison and unpoison event. Hook them so each hardware-poisoned
frame is appended to the LINUX_EFI_POISONED_MEMORY table, and each unpoison
tombstones its entry so a frame that is good again is not carried forward.
The append mirrors efi_mem_reserve_persistent(): claim a slot in an existing
list entry, or allocate and link a new list page -- itself reserved via
memreserve so it survives to the next kernel. memory_failure() has already
taken the frame out of this kernel's allocator, so only the cross-kexec record
happens here.
Signed-off-by: Breno Leitao <leitao@debian.org>
---
drivers/firmware/efi/Makefile | 1 +
drivers/firmware/efi/poison.c | 127 ++++++++++++++++++++++++++++++++++++++++++
include/linux/efi.h | 8 +++
mm/memory-failure.c | 6 +-
4 files changed, 141 insertions(+), 1 deletion(-)
diff --git a/drivers/firmware/efi/Makefile b/drivers/firmware/efi/Makefile
index 8efbcf699e4ff..05d0a490923e5 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/poison.c b/drivers/firmware/efi/poison.c
new file mode 100644
index 0000000000000..ed3b8cee21ba6
--- /dev/null
+++ b/drivers/firmware/efi/poison.c
@@ -0,0 +1,127 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Runtime handling for the LINUX_EFI_POISONED_MEMORY configuration table:
+ * record and clear poisoned frames so the next kexec kernel can keep them out
+ * of its allocator.
+ *
+ * Copyright (c) 2026 Meta Platforms, Inc. and affiliates.
+ * Copyright (c) 2026 Breno Leitao <leitao@debian.org>
+ */
+
+#define pr_fmt(fmt) "efi: " fmt
+
+#include <linux/efi.h>
+#include <linux/io.h>
+#include <linux/mm.h>
+#include <linux/sizes.h>
+#include <linux/spinlock.h>
+
+static struct linux_efi_poisoned_memory *efi_poisoned_memory_root __ro_after_init;
+static DEFINE_SPINLOCK(efi_poisoned_memory_lock);
+
+static int __init efi_poisoned_memory_map_root(void)
+{
+ if (efi.poisoned_memory == EFI_INVALID_TABLE_ADDR)
+ return -ENODEV;
+
+ efi_poisoned_memory_root = memremap(efi.poisoned_memory,
+ sizeof(*efi_poisoned_memory_root),
+ MEMREMAP_WB);
+ if (WARN_ON_ONCE(!efi_poisoned_memory_root))
+ return -ENOMEM;
+ return 0;
+}
+
+static int __init efi_poisoned_memory_root_init(void)
+{
+ if (efi_poisoned_memory_root)
+ return 0;
+ if (efi_poisoned_memory_map_root())
+ efi_poisoned_memory_root = (void *)ULONG_MAX;
+ return 0;
+}
+early_initcall(efi_poisoned_memory_root_init);
+
+/*
+ * Record a hardware-poisoned frame so the next kernel can keep it out of its
+ * allocator. memory_failure() has already removed it from this kernel, so only
+ * the cross-kexec record is needed here.
+ */
+void efi_hwpoison_record_pfn(unsigned long pfn)
+{
+ phys_addr_t addr = PFN_PHYS(pfn);
+ struct linux_efi_poisoned_memory *pm;
+ unsigned long ppm;
+ int index;
+
+ if (!efi_poisoned_memory_root ||
+ efi_poisoned_memory_root == (void *)ULONG_MAX)
+ return;
+
+ /* Try to claim a slot in an existing list entry. */
+ for (ppm = efi_poisoned_memory_root->next; ppm; ) {
+ pm = memremap(ppm, sizeof(*pm), MEMREMAP_WB);
+ if (!pm)
+ return;
+ index = atomic_fetch_add_unless(&pm->count, 1, pm->size);
+ if (index < pm->size) {
+ pm->entry[index].base = addr;
+ pm->entry[index].size = PAGE_SIZE;
+ memunmap(pm);
+ return;
+ }
+ ppm = pm->next;
+ memunmap(pm);
+ }
+
+ /*
+ * No slot free - allocate a new list entry and link it in. The page
+ * stays allocated for the rest of this boot, and the next kernel
+ * reserves it while parsing the EFI configuration tables, so no
+ * separate cross-kexec reservation is needed here.
+ */
+ pm = (void *)__get_free_page(GFP_ATOMIC);
+ if (!pm)
+ return;
+
+ pm->size = EFI_POISONED_MEMORY_COUNT(SZ_4K);
+ atomic_set(&pm->count, 1);
+ pm->entry[0].base = addr;
+ pm->entry[0].size = PAGE_SIZE;
+
+ spin_lock(&efi_poisoned_memory_lock);
+ pm->next = efi_poisoned_memory_root->next;
+ efi_poisoned_memory_root->next = __pa(pm);
+ spin_unlock(&efi_poisoned_memory_lock);
+}
+
+/*
+ * A frame was unpoisoned (typically the hwpoison injector under test).
+ * Tombstone its entry so the next kernel does not reserve a now-good frame.
+ */
+void efi_hwpoison_unrecord_pfn(unsigned long pfn)
+{
+ phys_addr_t addr = PFN_PHYS(pfn);
+ struct linux_efi_poisoned_memory *pm;
+ unsigned long ppm;
+ int i;
+
+ if (!efi_poisoned_memory_root ||
+ efi_poisoned_memory_root == (void *)ULONG_MAX)
+ return;
+
+ for (ppm = efi_poisoned_memory_root->next; ppm; ) {
+ pm = memremap(ppm, sizeof(*pm), MEMREMAP_WB);
+ if (!pm)
+ return;
+ for (i = 0; i < atomic_read(&pm->count); i++) {
+ if (pm->entry[i].base == addr) {
+ pm->entry[i].size = 0;
+ memunmap(pm);
+ return;
+ }
+ }
+ ppm = pm->next;
+ memunmap(pm);
+ }
+}
diff --git a/include/linux/efi.h b/include/linux/efi.h
index 7787eb8d4e4c1..bd1b0934881fb 100644
--- a/include/linux/efi.h
+++ b/include/linux/efi.h
@@ -1292,6 +1292,14 @@ struct linux_efi_poisoned_memory {
(((size) - sizeof(struct linux_efi_poisoned_memory)) \
/ sizeof_field(struct linux_efi_poisoned_memory, entry[0]))
+#ifdef CONFIG_EFI_POISONED_MEMORY
+void efi_hwpoison_record_pfn(unsigned long pfn);
+void efi_hwpoison_unrecord_pfn(unsigned long pfn);
+#else
+static inline void efi_hwpoison_record_pfn(unsigned long pfn) { }
+static inline void efi_hwpoison_unrecord_pfn(unsigned long pfn) { }
+#endif
+
void __init efi_arch_mem_reserve(phys_addr_t addr, u64 size);
/*
diff --git a/mm/memory-failure.c b/mm/memory-failure.c
index aaf14608b30e2..03fa921ef1b7a 100644
--- a/mm/memory-failure.c
+++ b/mm/memory-failure.c
@@ -43,6 +43,7 @@
#include <linux/sched/signal.h>
#include <linux/sched/task.h>
#include <linux/dax.h>
+#include <linux/efi.h>
#include <linux/ksm.h>
#include <linux/rmap.h>
#include <linux/export.h>
@@ -87,13 +88,16 @@ void num_poisoned_pages_inc(unsigned long pfn)
{
atomic_long_inc(&num_poisoned_pages);
memblk_nr_poison_inc(pfn);
+ efi_hwpoison_record_pfn(pfn);
}
void num_poisoned_pages_sub(unsigned long pfn, long i)
{
atomic_long_sub(i, &num_poisoned_pages);
- if (pfn != -1UL)
+ if (pfn != -1UL) {
memblk_nr_poison_sub(pfn, i);
+ efi_hwpoison_unrecord_pfn(pfn);
+ }
}
/**
--
2.53.0-Meta
^ permalink raw reply [flat|nested] 4+ messages in thread
* [PATCH RFC 3/3] efi: reserve inherited poisoned frames before the allocator comes up
2026-07-17 14:03 [PATCH RFC 0/3] efi: mm/memory-failure: keep hardware-poisoned pages out of the next kexec Breno Leitao
2026-07-17 14:03 ` [PATCH RFC 1/3] efi: add the LINUX_EFI_POISONED_MEMORY configuration table Breno Leitao
2026-07-17 14:03 ` [PATCH RFC 2/3] efi: record hardware-poisoned frames into the poisoned-memory table Breno Leitao
@ 2026-07-17 14:03 ` Breno Leitao
2 siblings, 0 replies; 4+ messages in thread
From: Breno Leitao @ 2026-07-17 14:03 UTC (permalink / raw)
To: kas, Ard Biesheuvel, Ilias Apalodimas, Miaohe Lin,
Naoya Horiguchi, Andrew Morton
Cc: linux-efi, linux-kernel, kexec, rneu, riel, caggio, anilagrawal,
rmikey, linux-mm, Breno Leitao, kernel-team
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 <leitao@debian.org>
---
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 <leitao@debian.org>
*/
@@ -12,7 +16,9 @@
#include <linux/efi.h>
#include <linux/io.h>
+#include <linux/memblock.h>
#include <linux/mm.h>
+#include <linux/overflow.h>
#include <linux/sizes.h>
#include <linux/spinlock.h>
@@ -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
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-07-17 14:03 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-07-17 14:03 [PATCH RFC 0/3] efi: mm/memory-failure: keep hardware-poisoned pages out of the next kexec Breno Leitao
2026-07-17 14:03 ` [PATCH RFC 1/3] efi: add the LINUX_EFI_POISONED_MEMORY configuration table Breno Leitao
2026-07-17 14:03 ` [PATCH RFC 2/3] efi: record hardware-poisoned frames into the poisoned-memory table Breno Leitao
2026-07-17 14:03 ` [PATCH RFC 3/3] efi: reserve inherited poisoned frames before the allocator comes up Breno Leitao
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox