A frame the previous kernel recorded as poisoned has to be flagged before it reaches the allocator, which is long before memory_failure() can run. Add a helper that leaves it in the state a frame poisoned by this kernel would be in, so everything that already understands PG_hwpoison covers it, the kexec segment placement check included.
num_poisoned_pages_inc() does not work at boot: its per memory block half looks the block up by pfn, and memory_dev_init() has not run, so it divides by zero. Take only the global counter there. A hotplugged block is already there, so that path takes both counters as usual. The caller comes later in this series. Signed-off-by: Breno Leitao <[email protected]> --- include/linux/mm.h | 7 +++++++ mm/memory-failure.c | 26 ++++++++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/include/linux/mm.h b/include/linux/mm.h index b68824fcfbef19..8039830998dd4b 100644 --- a/include/linux/mm.h +++ b/include/linux/mm.h @@ -5225,6 +5225,8 @@ 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); +void __meminit hwpoison_boot_page(struct page *page, + enum meminit_context context); phys_addr_t range_first_hwpoison(phys_addr_t start, unsigned long size); phys_addr_t range_last_hwpoison(phys_addr_t start, unsigned long size); #else @@ -5232,6 +5234,11 @@ static inline void memory_failure_queue(unsigned long pfn, int flags) { } +static inline void hwpoison_boot_page(struct page *page, + enum meminit_context context) +{ +} + static inline void num_poisoned_pages_inc(unsigned long pfn) { } diff --git a/mm/memory-failure.c b/mm/memory-failure.c index d9b8be696aac38..60e9682434700b 100644 --- a/mm/memory-failure.c +++ b/mm/memory-failure.c @@ -137,6 +137,32 @@ phys_addr_t range_last_hwpoison(phys_addr_t start, unsigned long size) return range_hwpoison(start, size, false); } +static void update_per_node_mf_stats(unsigned long pfn, enum mf_result result); + +void __meminit hwpoison_boot_page(struct page *page, + enum meminit_context context) +{ + unsigned long pfn = page_to_pfn(page); + + if (PageHWPoison(page)) + return; + + SetPageHWPoison(page); + set_page_count(page, 1); + /* The page has been completely isolated == MF_RECOVERED */ + update_per_node_mf_stats(pfn, MF_RECOVERED); + + /* + * The per memory block half of num_poisoned_pages_inc() has no block to + * find at boot, and divides by zero looking for one. A hotplugged block + * is already there. + */ + if (context == MEMINIT_HOTPLUG) + num_poisoned_pages_inc(pfn); + else + atomic_long_inc(&num_poisoned_pages); +} + /** * 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
