1863 lines · 49.3 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0-only/* *  linux/mm/memory_hotplug.c * *  Copyright (C) */ #include <linux/stddef.h>#include <linux/mm.h>#include <linux/sched/signal.h>#include <linux/swap.h>#include <linux/interrupt.h>#include <linux/pagemap.h>#include <linux/compiler.h>#include <linux/export.h>#include <linux/pagevec.h>#include <linux/writeback.h>#include <linux/slab.h>#include <linux/sysctl.h>#include <linux/cpu.h>#include <linux/memory.h>#include <linux/memremap.h>#include <linux/memory_hotplug.h>#include <linux/highmem.h>#include <linux/vmalloc.h>#include <linux/ioport.h>#include <linux/delay.h>#include <linux/migrate.h>#include <linux/page-isolation.h>#include <linux/pfn.h>#include <linux/suspend.h>#include <linux/mm_inline.h>#include <linux/firmware-map.h>#include <linux/stop_machine.h>#include <linux/hugetlb.h>#include <linux/memblock.h>#include <linux/compaction.h>#include <linux/rmap.h> #include <asm/tlbflush.h> #include "internal.h"#include "shuffle.h" /* * online_page_callback contains pointer to current page onlining function. * Initially it is generic_online_page(). If it is required it could be * changed by calling set_online_page_callback() for callback registration * and restore_online_page_callback() for generic callback restore. */ static online_page_callback_t online_page_callback = generic_online_page;static DEFINE_MUTEX(online_page_callback_lock); DEFINE_STATIC_PERCPU_RWSEM(mem_hotplug_lock); void get_online_mems(void){	percpu_down_read(&mem_hotplug_lock);} void put_online_mems(void){	percpu_up_read(&mem_hotplug_lock);} bool movable_node_enabled = false; #ifndef CONFIG_MEMORY_HOTPLUG_DEFAULT_ONLINEint memhp_default_online_type = MMOP_OFFLINE;#elseint memhp_default_online_type = MMOP_ONLINE;#endif static int __init setup_memhp_default_state(char *str){	const int online_type = memhp_online_type_from_str(str); 	if (online_type >= 0)		memhp_default_online_type = online_type; 	return 1;}__setup("memhp_default_state=", setup_memhp_default_state); void mem_hotplug_begin(void){	cpus_read_lock();	percpu_down_write(&mem_hotplug_lock);} void mem_hotplug_done(void){	percpu_up_write(&mem_hotplug_lock);	cpus_read_unlock();} u64 max_mem_size = U64_MAX; /* add this memory to iomem resource */static struct resource *register_memory_resource(u64 start, u64 size,						 const char *resource_name){	struct resource *res;	unsigned long flags =  IORESOURCE_SYSTEM_RAM | IORESOURCE_BUSY; 	if (strcmp(resource_name, "System RAM"))		flags |= IORESOURCE_MEM_DRIVER_MANAGED; 	/*	 * Make sure value parsed from 'mem=' only restricts memory adding	 * while booting, so that memory hotplug won't be impacted. Please	 * refer to document of 'mem=' in kernel-parameters.txt for more	 * details.	 */	if (start + size > max_mem_size && system_state < SYSTEM_RUNNING)		return ERR_PTR(-E2BIG); 	/*	 * Request ownership of the new memory range.  This might be	 * a child of an existing resource that was present but	 * not marked as busy.	 */	res = __request_region(&iomem_resource, start, size,			       resource_name, flags); 	if (!res) {		pr_debug("Unable to reserve System RAM region: %016llx->%016llx\n",				start, start + size);		return ERR_PTR(-EEXIST);	}	return res;} static void release_memory_resource(struct resource *res){	if (!res)		return;	release_resource(res);	kfree(res);} #ifdef CONFIG_MEMORY_HOTPLUG_SPARSEvoid get_page_bootmem(unsigned long info,  struct page *page,		      unsigned long type){	page->freelist = (void *)type;	SetPagePrivate(page);	set_page_private(page, info);	page_ref_inc(page);} void put_page_bootmem(struct page *page){	unsigned long type; 	type = (unsigned long) page->freelist;	BUG_ON(type < MEMORY_HOTPLUG_MIN_BOOTMEM_TYPE ||	       type > MEMORY_HOTPLUG_MAX_BOOTMEM_TYPE); 	if (page_ref_dec_return(page) == 1) {		page->freelist = NULL;		ClearPagePrivate(page);		set_page_private(page, 0);		INIT_LIST_HEAD(&page->lru);		free_reserved_page(page);	}} #ifdef CONFIG_HAVE_BOOTMEM_INFO_NODE#ifndef CONFIG_SPARSEMEM_VMEMMAPstatic void register_page_bootmem_info_section(unsigned long start_pfn){	unsigned long mapsize, section_nr, i;	struct mem_section *ms;	struct page *page, *memmap;	struct mem_section_usage *usage; 	section_nr = pfn_to_section_nr(start_pfn);	ms = __nr_to_section(section_nr); 	/* Get section's memmap address */	memmap = sparse_decode_mem_map(ms->section_mem_map, section_nr); 	/*	 * Get page for the memmap's phys address	 * XXX: need more consideration for sparse_vmemmap...	 */	page = virt_to_page(memmap);	mapsize = sizeof(struct page) * PAGES_PER_SECTION;	mapsize = PAGE_ALIGN(mapsize) >> PAGE_SHIFT; 	/* remember memmap's page */	for (i = 0; i < mapsize; i++, page++)		get_page_bootmem(section_nr, page, SECTION_INFO); 	usage = ms->usage;	page = virt_to_page(usage); 	mapsize = PAGE_ALIGN(mem_section_usage_size()) >> PAGE_SHIFT; 	for (i = 0; i < mapsize; i++, page++)		get_page_bootmem(section_nr, page, MIX_SECTION_INFO); }#else /* CONFIG_SPARSEMEM_VMEMMAP */static void register_page_bootmem_info_section(unsigned long start_pfn){	unsigned long mapsize, section_nr, i;	struct mem_section *ms;	struct page *page, *memmap;	struct mem_section_usage *usage; 	section_nr = pfn_to_section_nr(start_pfn);	ms = __nr_to_section(section_nr); 	memmap = sparse_decode_mem_map(ms->section_mem_map, section_nr); 	register_page_bootmem_memmap(section_nr, memmap, PAGES_PER_SECTION); 	usage = ms->usage;	page = virt_to_page(usage); 	mapsize = PAGE_ALIGN(mem_section_usage_size()) >> PAGE_SHIFT; 	for (i = 0; i < mapsize; i++, page++)		get_page_bootmem(section_nr, page, MIX_SECTION_INFO);}#endif /* !CONFIG_SPARSEMEM_VMEMMAP */ void __init register_page_bootmem_info_node(struct pglist_data *pgdat){	unsigned long i, pfn, end_pfn, nr_pages;	int node = pgdat->node_id;	struct page *page; 	nr_pages = PAGE_ALIGN(sizeof(struct pglist_data)) >> PAGE_SHIFT;	page = virt_to_page(pgdat); 	for (i = 0; i < nr_pages; i++, page++)		get_page_bootmem(node, page, NODE_INFO); 	pfn = pgdat->node_start_pfn;	end_pfn = pgdat_end_pfn(pgdat); 	/* register section info */	for (; pfn < end_pfn; pfn += PAGES_PER_SECTION) {		/*		 * Some platforms can assign the same pfn to multiple nodes - on		 * node0 as well as nodeN.  To avoid registering a pfn against		 * multiple nodes we check that this pfn does not already		 * reside in some other nodes.		 */		if (pfn_valid(pfn) && (early_pfn_to_nid(pfn) == node))			register_page_bootmem_info_section(pfn);	}}#endif /* CONFIG_HAVE_BOOTMEM_INFO_NODE */ static int check_pfn_span(unsigned long pfn, unsigned long nr_pages,		const char *reason){	/*	 * Disallow all operations smaller than a sub-section and only	 * allow operations smaller than a section for	 * SPARSEMEM_VMEMMAP. Note that check_hotplug_memory_range()	 * enforces a larger memory_block_size_bytes() granularity for	 * memory that will be marked online, so this check should only	 * fire for direct arch_{add,remove}_memory() users outside of	 * add_memory_resource().	 */	unsigned long min_align; 	if (IS_ENABLED(CONFIG_SPARSEMEM_VMEMMAP))		min_align = PAGES_PER_SUBSECTION;	else		min_align = PAGES_PER_SECTION;	if (!IS_ALIGNED(pfn, min_align)			|| !IS_ALIGNED(nr_pages, min_align)) {		WARN(1, "Misaligned __%s_pages start: %#lx end: #%lx\n",				reason, pfn, pfn + nr_pages - 1);		return -EINVAL;	}	return 0;} static int check_hotplug_memory_addressable(unsigned long pfn,					    unsigned long nr_pages){	const u64 max_addr = PFN_PHYS(pfn + nr_pages) - 1; 	if (max_addr >> MAX_PHYSMEM_BITS) {		const u64 max_allowed = (1ull << (MAX_PHYSMEM_BITS + 1)) - 1;		WARN(1,		     "Hotplugged memory exceeds maximum addressable address, range=%#llx-%#llx, maximum=%#llx\n",		     (u64)PFN_PHYS(pfn), max_addr, max_allowed);		return -E2BIG;	} 	return 0;} /* * Reasonably generic function for adding memory.  It is * expected that archs that support memory hotplug will * call this function after deciding the zone to which to * add the new pages. */int __ref __add_pages(int nid, unsigned long pfn, unsigned long nr_pages,		struct mhp_params *params){	const unsigned long end_pfn = pfn + nr_pages;	unsigned long cur_nr_pages;	int err;	struct vmem_altmap *altmap = params->altmap; 	if (WARN_ON_ONCE(!params->pgprot.pgprot))		return -EINVAL; 	err = check_hotplug_memory_addressable(pfn, nr_pages);	if (err)		return err; 	if (altmap) {		/*		 * Validate altmap is within bounds of the total request		 */		if (altmap->base_pfn != pfn				|| vmem_altmap_offset(altmap) > nr_pages) {			pr_warn_once("memory add fail, invalid altmap\n");			return -EINVAL;		}		altmap->alloc = 0;	} 	err = check_pfn_span(pfn, nr_pages, "add");	if (err)		return err; 	for (; pfn < end_pfn; pfn += cur_nr_pages) {		/* Select all remaining pages up to the next section boundary */		cur_nr_pages = min(end_pfn - pfn,				   SECTION_ALIGN_UP(pfn + 1) - pfn);		err = sparse_add_section(nid, pfn, cur_nr_pages, altmap);		if (err)			break;		cond_resched();	}	vmemmap_populate_print_last();	return err;} #ifdef CONFIG_NUMAint __weak memory_add_physaddr_to_nid(u64 start){	pr_info_once("Unknown target node for memory at 0x%llx, assuming node 0\n",			start);	return 0;}EXPORT_SYMBOL_GPL(memory_add_physaddr_to_nid);#endif /* find the smallest valid pfn in the range [start_pfn, end_pfn) */static unsigned long find_smallest_section_pfn(int nid, struct zone *zone,				     unsigned long start_pfn,				     unsigned long end_pfn){	for (; start_pfn < end_pfn; start_pfn += PAGES_PER_SUBSECTION) {		if (unlikely(!pfn_to_online_page(start_pfn)))			continue; 		if (unlikely(pfn_to_nid(start_pfn) != nid))			continue; 		if (zone != page_zone(pfn_to_page(start_pfn)))			continue; 		return start_pfn;	} 	return 0;} /* find the biggest valid pfn in the range [start_pfn, end_pfn). */static unsigned long find_biggest_section_pfn(int nid, struct zone *zone,				    unsigned long start_pfn,				    unsigned long end_pfn){	unsigned long pfn; 	/* pfn is the end pfn of a memory section. */	pfn = end_pfn - 1;	for (; pfn >= start_pfn; pfn -= PAGES_PER_SUBSECTION) {		if (unlikely(!pfn_to_online_page(pfn)))			continue; 		if (unlikely(pfn_to_nid(pfn) != nid))			continue; 		if (zone != page_zone(pfn_to_page(pfn)))			continue; 		return pfn;	} 	return 0;} static void shrink_zone_span(struct zone *zone, unsigned long start_pfn,			     unsigned long end_pfn){	unsigned long pfn;	int nid = zone_to_nid(zone); 	zone_span_writelock(zone);	if (zone->zone_start_pfn == start_pfn) {		/*		 * If the section is smallest section in the zone, it need		 * shrink zone->zone_start_pfn and zone->zone_spanned_pages.		 * In this case, we find second smallest valid mem_section		 * for shrinking zone.		 */		pfn = find_smallest_section_pfn(nid, zone, end_pfn,						zone_end_pfn(zone));		if (pfn) {			zone->spanned_pages = zone_end_pfn(zone) - pfn;			zone->zone_start_pfn = pfn;		} else {			zone->zone_start_pfn = 0;			zone->spanned_pages = 0;		}	} else if (zone_end_pfn(zone) == end_pfn) {		/*		 * If the section is biggest section in the zone, it need		 * shrink zone->spanned_pages.		 * In this case, we find second biggest valid mem_section for		 * shrinking zone.		 */		pfn = find_biggest_section_pfn(nid, zone, zone->zone_start_pfn,					       start_pfn);		if (pfn)			zone->spanned_pages = pfn - zone->zone_start_pfn + 1;		else {			zone->zone_start_pfn = 0;			zone->spanned_pages = 0;		}	}	zone_span_writeunlock(zone);} static void update_pgdat_span(struct pglist_data *pgdat){	unsigned long node_start_pfn = 0, node_end_pfn = 0;	struct zone *zone; 	for (zone = pgdat->node_zones;	     zone < pgdat->node_zones + MAX_NR_ZONES; zone++) {		unsigned long zone_end_pfn = zone->zone_start_pfn +					     zone->spanned_pages; 		/* No need to lock the zones, they can't change. */		if (!zone->spanned_pages)			continue;		if (!node_end_pfn) {			node_start_pfn = zone->zone_start_pfn;			node_end_pfn = zone_end_pfn;			continue;		} 		if (zone_end_pfn > node_end_pfn)			node_end_pfn = zone_end_pfn;		if (zone->zone_start_pfn < node_start_pfn)			node_start_pfn = zone->zone_start_pfn;	} 	pgdat->node_start_pfn = node_start_pfn;	pgdat->node_spanned_pages = node_end_pfn - node_start_pfn;} void __ref remove_pfn_range_from_zone(struct zone *zone,				      unsigned long start_pfn,				      unsigned long nr_pages){	const unsigned long end_pfn = start_pfn + nr_pages;	struct pglist_data *pgdat = zone->zone_pgdat;	unsigned long pfn, cur_nr_pages, flags; 	/* Poison struct pages because they are now uninitialized again. */	for (pfn = start_pfn; pfn < end_pfn; pfn += cur_nr_pages) {		cond_resched(); 		/* Select all remaining pages up to the next section boundary */		cur_nr_pages =			min(end_pfn - pfn, SECTION_ALIGN_UP(pfn + 1) - pfn);		page_init_poison(pfn_to_page(pfn),				 sizeof(struct page) * cur_nr_pages);	} #ifdef CONFIG_ZONE_DEVICE	/*

Lines 1–500 of 1863.