A kernel that inherits a record of hardware-poisoned frames from an earlier kernel needs a way to apply it. memory_failure() does not fit: it takes mf_mutex, prints a line per frame, and a 2M unit is 512 of them. Add hwpoison_boot_pfn(). A frame that is free takes the same route memory_failure() takes for a free page: take_page_off_buddy(), the flag, the refcount and the counter. A frame the kernel is already sitting on is only flagged, since it cannot be taken away from whoever reserved it. free_pages_prepare() drops such a frame if it is ever handed back, so it does not reach the allocator either way. The accounting cannot go through num_poisoned_pages_inc(). Its per memory block half divides by sections_per_block, which memory_dev_init() only sets up from driver_init(), so where this runs it is still zero and the boot dies on a divide by zero. Bump the global counter directly; the block counters stay short by these frames. Signed-off-by: Breno Leitao --- include/linux/mm.h | 1 + mm/memory-failure.c | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/include/linux/mm.h b/include/linux/mm.h index 32bb723ffbb92..52a00a0e090c7 100644 --- a/include/linux/mm.h +++ b/include/linux/mm.h @@ -5104,6 +5104,7 @@ extern const struct attribute_group memory_failure_attr_group; extern void memory_failure_queue(unsigned long pfn, int flags); void num_poisoned_pages_inc(unsigned long pfn); void num_poisoned_pages_sub(unsigned long pfn, long i); +bool __init hwpoison_boot_pfn(unsigned long pfn); #else static inline void memory_failure_queue(unsigned long pfn, int flags) { diff --git a/mm/memory-failure.c b/mm/memory-failure.c index 357a72ffda625..713fb2f8e0332 100644 --- a/mm/memory-failure.c +++ b/mm/memory-failure.c @@ -97,6 +97,30 @@ void num_poisoned_pages_sub(unsigned long pfn, long i) memblk_nr_poison_sub(pfn, i); } +static void update_per_node_mf_stats(unsigned long pfn, enum mf_result result); + +bool __init hwpoison_boot_pfn(unsigned long pfn) +{ + struct page *page = pfn_to_online_page(pfn); + + if (!page || PageHWPoison(page)) + return false; + + if (is_free_buddy_page(page)) { + if (!take_page_off_buddy(page)) + return false; + page_ref_inc(page); + } else if (!PageReserved(page)) { + return false; + } + + SetPageHWPoison(page); + update_per_node_mf_stats(pfn, MF_RECOVERED); + atomic_long_inc(&num_poisoned_pages); + + return true; +} + /** * MF_ATTR_RO - Create sysfs entry for each memory failure statistics. * @_name: name of the file in the per NUMA sysfs directory. -- 2.53.0-Meta