Skip to content

Commit 2c91f8f

Browse files
davidhildenbrandtorvalds
authored andcommitted
mm/memory_hotplug: fix try_offline_node()
try_offline_node() is pretty much broken right now: - The node span is updated when onlining memory, not when adding it. We ignore memory that was mever onlined. Bad. - We touch possible garbage memmaps. The pfn_to_nid(pfn) can easily trigger a kernel panic. Bad for memory that is offline but also bad for subsection hotadd with ZONE_DEVICE, whereby the memmap of the first PFN of a section might contain garbage. - Sections belonging to mixed nodes are not properly considered. As memory blocks might belong to multiple nodes, we would have to walk all pageblocks (or at least subsections) within present sections. However, we don't have a way to identify whether a memmap that is not online was initialized (relevant for ZONE_DEVICE). This makes things more complicated. Luckily, we can piggy pack on the node span and the nid stored in memory blocks. Currently, the node span is grown when calling move_pfn_range_to_zone() - e.g., when onlining memory, and shrunk when removing memory, before calling try_offline_node(). Sysfs links are created via link_mem_sections(), e.g., during boot or when adding memory. If the node still spans memory or if any memory block belongs to the nid, we don't set the node offline. As memory blocks that span multiple nodes cannot get offlined, the nid stored in memory blocks is reliable enough (for such online memory blocks, the node still spans the memory). Introduce for_each_memory_block() to efficiently walk all memory blocks. Note: We will soon stop shrinking the ZONE_DEVICE zone and the node span when removing ZONE_DEVICE memory to fix similar issues (access of garbage memmaps) - until we have a reliable way to identify whether these memmaps were properly initialized. This implies later, that once a node had ZONE_DEVICE memory, we won't be able to set a node offline - which should be acceptable. Since commit f1dd2cd ("mm, memory_hotplug: do not associate hotadded memory to zones until online") memory that is added is not assoziated with a zone/node (memmap not initialized). The introducing commit 60a5a19 ("memory-hotplug: remove sysfs file of node") already missed that we could have multiple nodes for a section and that the zone/node span is updated when onlining pages, not when adding them. I tested this by hotplugging two DIMMs to a memory-less and cpu-less NUMA node. The node is properly onlined when adding the DIMMs. When removing the DIMMs, the node is properly offlined. Masayoshi Mizuma reported: : Without this patch, memory hotplug fails as panic: : : BUG: kernel NULL pointer dereference, address: 0000000000000000 : ... : Call Trace: : remove_memory_block_devices+0x81/0xc0 : try_remove_memory+0xb4/0x130 : __remove_memory+0xa/0x20 : acpi_memory_device_remove+0x84/0x100 : acpi_bus_trim+0x57/0x90 : acpi_bus_trim+0x2e/0x90 : acpi_device_hotplug+0x2b2/0x4d0 : acpi_hotplug_work_fn+0x1a/0x30 : process_one_work+0x171/0x380 : worker_thread+0x49/0x3f0 : kthread+0xf8/0x130 : ret_from_fork+0x35/0x40 [[email protected]: v3] Link: http://lkml.kernel.org/r/[email protected] Link: http://lkml.kernel.org/r/[email protected] Fixes: 60a5a19 ("memory-hotplug: remove sysfs file of node") Fixes: f1dd2cd ("mm, memory_hotplug: do not associate hotadded memory to zones until online") # visiable after d0dc12e Signed-off-by: David Hildenbrand <[email protected]> Tested-by: Masayoshi Mizuma <[email protected]> Cc: Tang Chen <[email protected]> Cc: Greg Kroah-Hartman <[email protected]> Cc: "Rafael J. Wysocki" <[email protected]> Cc: Keith Busch <[email protected]> Cc: Jiri Olsa <[email protected]> Cc: "Peter Zijlstra (Intel)" <[email protected]> Cc: Jani Nikula <[email protected]> Cc: Nayna Jain <[email protected]> Cc: Michal Hocko <[email protected]> Cc: Oscar Salvador <[email protected]> Cc: Stephen Rothwell <[email protected]> Cc: Dan Williams <[email protected]> Cc: Pavel Tatashin <[email protected]> Cc: <[email protected]> Signed-off-by: Andrew Morton <[email protected]> Signed-off-by: Linus Torvalds <[email protected]>
1 parent 4655e5e commit 2c91f8f

File tree

3 files changed

+64
-16
lines changed

3 files changed

+64
-16
lines changed

drivers/base/memory.c

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -872,3 +872,39 @@ int walk_memory_blocks(unsigned long start, unsigned long size,
872872
}
873873
return ret;
874874
}
875+
876+
struct for_each_memory_block_cb_data {
877+
walk_memory_blocks_func_t func;
878+
void *arg;
879+
};
880+
881+
static int for_each_memory_block_cb(struct device *dev, void *data)
882+
{
883+
struct memory_block *mem = to_memory_block(dev);
884+
struct for_each_memory_block_cb_data *cb_data = data;
885+
886+
return cb_data->func(mem, cb_data->arg);
887+
}
888+
889+
/**
890+
* for_each_memory_block - walk through all present memory blocks
891+
*
892+
* @arg: argument passed to func
893+
* @func: callback for each memory block walked
894+
*
895+
* This function walks through all present memory blocks, calling func on
896+
* each memory block.
897+
*
898+
* In case func() returns an error, walking is aborted and the error is
899+
* returned.
900+
*/
901+
int for_each_memory_block(void *arg, walk_memory_blocks_func_t func)
902+
{
903+
struct for_each_memory_block_cb_data cb_data = {
904+
.func = func,
905+
.arg = arg,
906+
};
907+
908+
return bus_for_each_dev(&memory_subsys, NULL, &cb_data,
909+
for_each_memory_block_cb);
910+
}

include/linux/memory.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ extern struct memory_block *find_memory_block(struct mem_section *);
119119
typedef int (*walk_memory_blocks_func_t)(struct memory_block *, void *);
120120
extern int walk_memory_blocks(unsigned long start, unsigned long size,
121121
void *arg, walk_memory_blocks_func_t func);
122+
extern int for_each_memory_block(void *arg, walk_memory_blocks_func_t func);
122123
#define CONFIG_MEM_BLOCK_SIZE (PAGES_PER_SECTION<<PAGE_SHIFT)
123124
#endif /* CONFIG_MEMORY_HOTPLUG_SPARSE */
124125

mm/memory_hotplug.c

Lines changed: 27 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1646,6 +1646,18 @@ static int check_cpu_on_node(pg_data_t *pgdat)
16461646
return 0;
16471647
}
16481648

1649+
static int check_no_memblock_for_node_cb(struct memory_block *mem, void *arg)
1650+
{
1651+
int nid = *(int *)arg;
1652+
1653+
/*
1654+
* If a memory block belongs to multiple nodes, the stored nid is not
1655+
* reliable. However, such blocks are always online (e.g., cannot get
1656+
* offlined) and, therefore, are still spanned by the node.
1657+
*/
1658+
return mem->nid == nid ? -EEXIST : 0;
1659+
}
1660+
16491661
/**
16501662
* try_offline_node
16511663
* @nid: the node ID
@@ -1658,25 +1670,24 @@ static int check_cpu_on_node(pg_data_t *pgdat)
16581670
void try_offline_node(int nid)
16591671
{
16601672
pg_data_t *pgdat = NODE_DATA(nid);
1661-
unsigned long start_pfn = pgdat->node_start_pfn;
1662-
unsigned long end_pfn = start_pfn + pgdat->node_spanned_pages;
1663-
unsigned long pfn;
1664-
1665-
for (pfn = start_pfn; pfn < end_pfn; pfn += PAGES_PER_SECTION) {
1666-
unsigned long section_nr = pfn_to_section_nr(pfn);
1667-
1668-
if (!present_section_nr(section_nr))
1669-
continue;
1673+
int rc;
16701674

1671-
if (pfn_to_nid(pfn) != nid)
1672-
continue;
1675+
/*
1676+
* If the node still spans pages (especially ZONE_DEVICE), don't
1677+
* offline it. A node spans memory after move_pfn_range_to_zone(),
1678+
* e.g., after the memory block was onlined.
1679+
*/
1680+
if (pgdat->node_spanned_pages)
1681+
return;
16731682

1674-
/*
1675-
* some memory sections of this node are not removed, and we
1676-
* can't offline node now.
1677-
*/
1683+
/*
1684+
* Especially offline memory blocks might not be spanned by the
1685+
* node. They will get spanned by the node once they get onlined.
1686+
* However, they link to the node in sysfs and can get onlined later.
1687+
*/
1688+
rc = for_each_memory_block(&nid, check_no_memblock_for_node_cb);
1689+
if (rc)
16781690
return;
1679-
}
16801691

16811692
if (check_cpu_on_node(pgdat))
16821693
return;

0 commit comments

Comments
 (0)