diff options
Diffstat (limited to 'drivers/base/memory.c')
| -rw-r--r-- | drivers/base/memory.c | 101 | 
1 files changed, 85 insertions, 16 deletions
| diff --git a/drivers/base/memory.c b/drivers/base/memory.c index f35298425575..b31b3af5c490 100644 --- a/drivers/base/memory.c +++ b/drivers/base/memory.c @@ -169,30 +169,98 @@ int memory_notify(unsigned long val, void *v)  	return blocking_notifier_call_chain(&memory_chain, val, v);  } +static int memory_block_online(struct memory_block *mem) +{ +	unsigned long start_pfn = section_nr_to_pfn(mem->start_section_nr); +	unsigned long nr_pages = PAGES_PER_SECTION * sections_per_block; +	unsigned long nr_vmemmap_pages = mem->nr_vmemmap_pages; +	struct zone *zone; +	int ret; + +	zone = zone_for_pfn_range(mem->online_type, mem->nid, start_pfn, nr_pages); + +	/* +	 * Although vmemmap pages have a different lifecycle than the pages +	 * they describe (they remain until the memory is unplugged), doing +	 * their initialization and accounting at memory onlining/offlining +	 * stage helps to keep accounting easier to follow - e.g vmemmaps +	 * belong to the same zone as the memory they backed. +	 */ +	if (nr_vmemmap_pages) { +		ret = mhp_init_memmap_on_memory(start_pfn, nr_vmemmap_pages, zone); +		if (ret) +			return ret; +	} + +	ret = online_pages(start_pfn + nr_vmemmap_pages, +			   nr_pages - nr_vmemmap_pages, zone); +	if (ret) { +		if (nr_vmemmap_pages) +			mhp_deinit_memmap_on_memory(start_pfn, nr_vmemmap_pages); +		return ret; +	} + +	/* +	 * Account once onlining succeeded. If the zone was unpopulated, it is +	 * now already properly populated. +	 */ +	if (nr_vmemmap_pages) +		adjust_present_page_count(zone, nr_vmemmap_pages); + +	return ret; +} + +static int memory_block_offline(struct memory_block *mem) +{ +	unsigned long start_pfn = section_nr_to_pfn(mem->start_section_nr); +	unsigned long nr_pages = PAGES_PER_SECTION * sections_per_block; +	unsigned long nr_vmemmap_pages = mem->nr_vmemmap_pages; +	struct zone *zone; +	int ret; + +	zone = page_zone(pfn_to_page(start_pfn)); + +	/* +	 * Unaccount before offlining, such that unpopulated zone and kthreads +	 * can properly be torn down in offline_pages(). +	 */ +	if (nr_vmemmap_pages) +		adjust_present_page_count(zone, -nr_vmemmap_pages); + +	ret = offline_pages(start_pfn + nr_vmemmap_pages, +			    nr_pages - nr_vmemmap_pages); +	if (ret) { +		/* offline_pages() failed. Account back. */ +		if (nr_vmemmap_pages) +			adjust_present_page_count(zone, nr_vmemmap_pages); +		return ret; +	} + +	if (nr_vmemmap_pages) +		mhp_deinit_memmap_on_memory(start_pfn, nr_vmemmap_pages); + +	return ret; +} +  /*   * MEMORY_HOTPLUG depends on SPARSEMEM in mm/Kconfig, so it is   * OK to have direct references to sparsemem variables in here.   */  static int -memory_block_action(unsigned long start_section_nr, unsigned long action, -		    int online_type, int nid) +memory_block_action(struct memory_block *mem, unsigned long action)  { -	unsigned long start_pfn; -	unsigned long nr_pages = PAGES_PER_SECTION * sections_per_block;  	int ret; -	start_pfn = section_nr_to_pfn(start_section_nr); -  	switch (action) {  	case MEM_ONLINE: -		ret = online_pages(start_pfn, nr_pages, online_type, nid); +		ret = memory_block_online(mem);  		break;  	case MEM_OFFLINE: -		ret = offline_pages(start_pfn, nr_pages); +		ret = memory_block_offline(mem);  		break;  	default:  		WARN(1, KERN_WARNING "%s(%ld, %ld) unknown action: " -		     "%ld\n", __func__, start_section_nr, action, action); +		     "%ld\n", __func__, mem->start_section_nr, action, action);  		ret = -EINVAL;  	} @@ -210,9 +278,7 @@ static int memory_block_change_state(struct memory_block *mem,  	if (to_state == MEM_OFFLINE)  		mem->state = MEM_GOING_OFFLINE; -	ret = memory_block_action(mem->start_section_nr, to_state, -				  mem->online_type, mem->nid); - +	ret = memory_block_action(mem, to_state);  	mem->state = ret ? from_state_req : to_state;  	return ret; @@ -567,7 +633,8 @@ int register_memory(struct memory_block *memory)  	return ret;  } -static int init_memory_block(unsigned long block_id, unsigned long state) +static int init_memory_block(unsigned long block_id, unsigned long state, +			     unsigned long nr_vmemmap_pages)  {  	struct memory_block *mem;  	int ret = 0; @@ -584,6 +651,7 @@ static int init_memory_block(unsigned long block_id, unsigned long state)  	mem->start_section_nr = block_id * sections_per_block;  	mem->state = state;  	mem->nid = NUMA_NO_NODE; +	mem->nr_vmemmap_pages = nr_vmemmap_pages;  	ret = register_memory(mem); @@ -603,7 +671,7 @@ static int add_memory_block(unsigned long base_section_nr)  	if (section_count == 0)  		return 0;  	return init_memory_block(memory_block_id(base_section_nr), -				 MEM_ONLINE); +				 MEM_ONLINE, 0);  }  static void unregister_memory(struct memory_block *memory) @@ -625,7 +693,8 @@ static void unregister_memory(struct memory_block *memory)   *   * Called under device_hotplug_lock.   */ -int create_memory_block_devices(unsigned long start, unsigned long size) +int create_memory_block_devices(unsigned long start, unsigned long size, +				unsigned long vmemmap_pages)  {  	const unsigned long start_block_id = pfn_to_block_id(PFN_DOWN(start));  	unsigned long end_block_id = pfn_to_block_id(PFN_DOWN(start + size)); @@ -638,7 +707,7 @@ int create_memory_block_devices(unsigned long start, unsigned long size)  		return -EINVAL;  	for (block_id = start_block_id; block_id != end_block_id; block_id++) { -		ret = init_memory_block(block_id, MEM_OFFLINE); +		ret = init_memory_block(block_id, MEM_OFFLINE, vmemmap_pages);  		if (ret)  			break;  	} | 
