When the pages are being given to the allocator, check if they are poisoned, and mark them as such.
Similar to unaccepted memory, hook it in __free_pages_core(), and thus the frames never enter the allocator, rather than being taken back out of it. hwpoison_boot_page() leaves a frame 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. Suggested-by: Kiryl Shutsemau <[email protected]> Signed-off-by: Breno Leitao <[email protected]> --- include/linux/mm.h | 5 +++++ mm/memory-failure.c | 15 +++++++++++++++ mm/page_alloc.c | 25 +++++++++++++++++++++++++ 3 files changed, 45 insertions(+) diff --git a/include/linux/mm.h b/include/linux/mm.h index b68824fcfbef1..9d9f2e8fdc136 100644 --- a/include/linux/mm.h +++ b/include/linux/mm.h @@ -5225,6 +5225,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); +void __meminit hwpoison_boot_page(struct page *page); 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 +5233,10 @@ static inline void memory_failure_queue(unsigned long pfn, int flags) { } +static inline void hwpoison_boot_page(struct page *page) +{ +} + static inline void num_poisoned_pages_inc(unsigned long pfn) { } diff --git a/mm/memory-failure.c b/mm/memory-failure.c index d9b8be696aac3..f6afdb2a89a94 100644 --- a/mm/memory-failure.c +++ b/mm/memory-failure.c @@ -137,6 +137,21 @@ 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); + +/* Not num_poisoned_pages_inc(): its per block half divides by zero this early. */ +void __meminit hwpoison_boot_page(struct page *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(page_to_pfn(page), MF_RECOVERED); + 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. diff --git a/mm/page_alloc.c b/mm/page_alloc.c index 404896b53003e..9e2ce833fd409 100644 --- a/mm/page_alloc.c +++ b/mm/page_alloc.c @@ -1579,6 +1579,19 @@ static void __free_pages_ok(struct page *page, unsigned int order, free_one_page(zone, page, pfn, order, fpi_flags); } +/* Flag the frames an earlier kernel recorded as bad. */ +static void __meminit poison_block(struct page *page, unsigned int order) +{ + unsigned long i, nr_pages = 1UL << order; + + for (i = 0; i < nr_pages; i++) { + struct page *p = page + i; + + if (range_contains_poisoned_memory(page_to_phys(p), PAGE_SIZE)) + hwpoison_boot_page(p); + } +} + void __meminit __free_pages_core(struct page *page, unsigned int order, enum meminit_context context) { @@ -1613,6 +1626,18 @@ void __meminit __free_pages_core(struct page *page, unsigned int order, atomic_long_add(nr_pages, &page_zone(page)->managed_pages); } + /* First: a block parked by __free_unaccepted() never returns here. */ + if (range_contains_poisoned_memory(page_to_phys(page), + PAGE_SIZE << order)) { + poison_block(page, order); + /* + * TODO: free the frames in the block that are not poisoned. + * They stay out of the allocator and still count in + * managed_pages, so a unit costs up to a block. + */ + return; + } + if (page_contains_unaccepted(page, order)) { if (order == MAX_PAGE_ORDER && __free_unaccepted(page)) return; -- 2.53.0-Meta
