A frame a kexec handed over is flagged as it reaches the allocator, long before its memory block exists, so memblk_nr_poison_inc() had nowhere to count it. Walk the block once when it is created and take the count from the page flag instead.
Without it an unpoison later subtracts from a counter that was never incremented and wraps it, which then refuses memory_block_online() for good. Only a block created online needs the walk. A hotplugged one is created before its pages are, so there is nothing to find, and its frames are counted by num_poisoned_pages_inc() as they are flagged. Signed-off-by: Breno Leitao <[email protected]> --- drivers/base/memory.c | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/drivers/base/memory.c b/drivers/base/memory.c index 5eead3346f1e32..49a33ddbb2e717 100644 --- a/drivers/base/memory.c +++ b/drivers/base/memory.c @@ -220,11 +220,16 @@ int memory_notify(enum memory_block_state state, void *v) #if defined(CONFIG_MEMORY_FAILURE) && defined(CONFIG_MEMORY_HOTPLUG) static unsigned long memblk_nr_poison(struct memory_block *mem); +static void memblk_nr_poison_init(struct memory_block *mem); #else static inline unsigned long memblk_nr_poison(struct memory_block *mem) { return 0; } + +static inline void memblk_nr_poison_init(struct memory_block *mem) +{ +} #endif /* @@ -807,6 +812,7 @@ static int add_memory_block(unsigned long block_id, int nid, unsigned long state mem->state = state; mem->nid = nid; INIT_LIST_HEAD(&mem->group_next); + memblk_nr_poison_init(mem); #ifndef CONFIG_NUMA if (state == MEM_ONLINE) @@ -1251,4 +1257,33 @@ static unsigned long memblk_nr_poison(struct memory_block *mem) { return atomic_long_read(&mem->nr_hwpoison); } + +/* + * Frames a kexec handed over are flagged as they reach the allocator, long + * before this block exists, so memblk_nr_poison_inc() had nowhere to count + * them. Take them from the page flag instead. + */ +static void memblk_nr_poison_init(struct memory_block *mem) +{ + unsigned long pfn = section_nr_to_pfn(mem->start_section_nr); + unsigned long nr_pages = PAGES_PER_SECTION * sections_per_block; + unsigned long i, nr_poison = 0; + + /* A hotplugged block is created before its pages are online. */ + if (mem->state != MEM_ONLINE) + return; + + if (!range_contains_poisoned_memory(PFN_PHYS(pfn), + nr_pages << PAGE_SHIFT)) + return; + + for (i = 0; i < nr_pages; i++) { + struct page *page = pfn_to_online_page(pfn + i); + + if (page && PageHWPoison(page)) + nr_poison++; + } + + atomic_long_set(&mem->nr_hwpoison, nr_poison); +} #endif -- 2.53.0-Meta
