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
