action_result() is where memory_failure() reports the outcome of a hard
offline, so hook it to set the frame's bit in the
LINUX_EFI_POISONED_MEMORY bitmap.

Soft-offlined pages reach num_poisoned_pages_inc() through
page_handle_poison() and are deliberately left out: they are still
functional and were offlined predictively, so recording them would turn
a prediction into a permanent loss for every kernel further down the
kexec chain.

A bit is only ever set, never cleared, given that multiple pages can set
the same bit, and it is not trivial to decide if the bit should be unset
when a page is unrecorded.

Unpoisoning a frame therefore does not hand its unit back to the next
kernel. That is a known limitation.

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 <[email protected]>
---
 drivers/firmware/efi/poison.c | 27 +++++++++++++++++++++++++++
 include/linux/efi.h           |  2 ++
 mm/memory-failure.c           |  3 +++
 3 files changed, 32 insertions(+)

diff --git a/drivers/firmware/efi/poison.c b/drivers/firmware/efi/poison.c
index 3f12db3dc9b844..847592862f01d3 100644
--- a/drivers/firmware/efi/poison.c
+++ b/drivers/firmware/efi/poison.c
@@ -91,3 +91,30 @@ void __init efi_poisoned_memory_reserve(void)
        memblock_add(start, end - start);
        memblock_reserve(start, end - start);
 }
+
+/* The table, vetted at parse time, or NULL if this boot has none. */
+static struct linux_efi_poisoned_memory *efi_poisoned_memory(void)
+{
+       if (efi.poisoned_memory == EFI_INVALID_TABLE_ADDR)
+               return NULL;
+
+       return phys_to_virt(efi.poisoned_memory);
+}
+
+/*
+ * A bit is never cleared: it stands for a whole EFI_POISON_UNIT_SIZE, so an
+ * unpoison cannot tell whether the unit as a whole is good again.
+ */
+void efi_hwpoison_record_pfn(unsigned long pfn)
+{
+       struct linux_efi_poisoned_memory *pm = efi_poisoned_memory();
+       phys_addr_t addr = PFN_PHYS(pfn);
+       u64 unit;
+
+       if (!pm || addr < pm->phys_base)
+               return;
+
+       unit = (addr - pm->phys_base) / pm->unit_size;
+       if (unit < pm->size * BITS_PER_BYTE)
+               set_bit(unit, pm->bitmap);
+}
diff --git a/include/linux/efi.h b/include/linux/efi.h
index dd3263456dd4a3..56402fdccd1149 100644
--- a/include/linux/efi.h
+++ b/include/linux/efi.h
@@ -1288,8 +1288,10 @@ struct linux_efi_poisoned_memory {
 
 #ifdef CONFIG_EFI_POISONED_MEMORY
 void __init efi_poisoned_memory_reserve(void);
+void efi_hwpoison_record_pfn(unsigned long pfn);
 #else
 static inline void efi_poisoned_memory_reserve(void) { }
+static inline void efi_hwpoison_record_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 a2ca8df501caee..d9b8be696aac38 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>
@@ -1326,6 +1327,8 @@ static int action_result(unsigned long pfn, enum 
mf_action_page_type type,
        if (type != MF_MSG_ALREADY_POISONED && type != MF_MSG_PFN_MAP) {
                num_poisoned_pages_inc(pfn);
                update_per_node_mf_stats(pfn, result);
+               /* Only hard offlines are carried over to the next kernel. */
+               efi_hwpoison_record_pfn(pfn);
        }
 
        pr_err("%#lx: recovery action for %s: %s\n",

-- 
2.53.0-Meta


Reply via email to