1950 lines · 50.1 KB · C
RawDownload
/* *  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/bootmem.h>#include <linux/compaction.h> #include <asm/tlbflush.h> #include "internal.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 void generic_online_page(struct page *page); 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_ONLINEbool memhp_auto_online;#elsebool memhp_auto_online = true;#endifEXPORT_SYMBOL_GPL(memhp_auto_online); static int __init setup_memhp_default_state(char *str){	if (!strcmp(str, "online"))		memhp_auto_online = true;	else if (!strcmp(str, "offline"))		memhp_auto_online = false; 	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();} /* add this memory to iomem resource */static struct resource *register_memory_resource(u64 start, u64 size){	struct resource *res;	res = kzalloc(sizeof(struct resource), GFP_KERNEL);	if (!res)		return ERR_PTR(-ENOMEM); 	res->name = "System RAM";	res->start = start;	res->end = start + size - 1;	res->flags = IORESOURCE_SYSTEM_RAM | IORESOURCE_BUSY;	if (request_resource(&iomem_resource, res) < 0) {		pr_debug("System RAM resource %pR cannot be added\n", res);		kfree(res);		return ERR_PTR(-EEXIST);	}	return res;} static void release_memory_resource(struct resource *res){	if (!res)		return;	release_resource(res);	kfree(res);	return;} #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 *usemap, mapsize, section_nr, i;	struct mem_section *ms;	struct page *page, *memmap; 	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); 	usemap = __nr_to_section(section_nr)->pageblock_flags;	page = virt_to_page(usemap); 	mapsize = PAGE_ALIGN(usemap_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 *usemap, mapsize, section_nr, i;	struct mem_section *ms;	struct page *page, *memmap; 	if (!pfn_valid(start_pfn))		return; 	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); 	usemap = __nr_to_section(section_nr)->pageblock_flags;	page = virt_to_page(usemap); 	mapsize = PAGE_ALIGN(usemap_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 __meminit __add_section(int nid, unsigned long phys_start_pfn,		bool want_memblock){	int ret;	int i; 	if (pfn_valid(phys_start_pfn))		return -EEXIST; 	ret = sparse_add_one_section(NODE_DATA(nid), phys_start_pfn);	if (ret < 0)		return ret; 	/*	 * Make all the pages reserved so that nobody will stumble over half	 * initialized state.	 * FIXME: We also have to associate it with a node because pfn_to_node	 * relies on having page with the proper node.	 */	for (i = 0; i < PAGES_PER_SECTION; i++) {		unsigned long pfn = phys_start_pfn + i;		struct page *page;		if (!pfn_valid(pfn))			continue; 		page = pfn_to_page(pfn);		set_page_node(page, nid);		SetPageReserved(page);	} 	if (!want_memblock)		return 0; 	return register_new_memory(nid, __pfn_to_section(phys_start_pfn));} /* * 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 phys_start_pfn,			unsigned long nr_pages, bool want_memblock){	unsigned long i;	int err = 0;	int start_sec, end_sec;	struct vmem_altmap *altmap; 	/* during initialize mem_map, align hot-added range to section */	start_sec = pfn_to_section_nr(phys_start_pfn);	end_sec = pfn_to_section_nr(phys_start_pfn + nr_pages - 1); 	altmap = to_vmem_altmap((unsigned long) pfn_to_page(phys_start_pfn));	if (altmap) {		/*		 * Validate altmap is within bounds of the total request		 */		if (altmap->base_pfn != phys_start_pfn				|| vmem_altmap_offset(altmap) > nr_pages) {			pr_warn_once("memory add fail, invalid altmap\n");			err = -EINVAL;			goto out;		}		altmap->alloc = 0;	} 	for (i = start_sec; i <= end_sec; i++) {		err = __add_section(nid, section_nr_to_pfn(i), want_memblock); 		/*		 * EEXIST is finally dealt with by ioresource collision		 * check. see add_memory() => register_memory_resource()		 * Warning will be printed if there is collision.		 */		if (err && (err != -EEXIST))			break;		err = 0;	}	vmemmap_populate_print_last();out:	return err;}EXPORT_SYMBOL_GPL(__add_pages); #ifdef CONFIG_MEMORY_HOTREMOVE/* find the smallest valid pfn in the range [start_pfn, end_pfn) */static int find_smallest_section_pfn(int nid, struct zone *zone,				     unsigned long start_pfn,				     unsigned long end_pfn){	struct mem_section *ms; 	for (; start_pfn < end_pfn; start_pfn += PAGES_PER_SECTION) {		ms = __pfn_to_section(start_pfn); 		if (unlikely(!valid_section(ms)))			continue; 		if (unlikely(pfn_to_nid(start_pfn) != nid))			continue; 		if (zone && 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 int find_biggest_section_pfn(int nid, struct zone *zone,				    unsigned long start_pfn,				    unsigned long end_pfn){	struct mem_section *ms;	unsigned long pfn; 	/* pfn is the end pfn of a memory section. */	pfn = end_pfn - 1;	for (; pfn >= start_pfn; pfn -= PAGES_PER_SECTION) {		ms = __pfn_to_section(pfn); 		if (unlikely(!valid_section(ms)))			continue; 		if (unlikely(pfn_to_nid(pfn) != nid))			continue; 		if (zone && 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 zone_start_pfn = zone->zone_start_pfn;	unsigned long z = zone_end_pfn(zone); /* zone_end_pfn namespace clash */	unsigned long zone_end_pfn = z;	unsigned long pfn;	struct mem_section *ms;	int nid = zone_to_nid(zone); 	zone_span_writelock(zone);	if (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);		if (pfn) {			zone->zone_start_pfn = pfn;			zone->spanned_pages = zone_end_pfn - pfn;		}	} else if (zone_end_pfn == 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_start_pfn,					       start_pfn);		if (pfn)			zone->spanned_pages = pfn - zone_start_pfn + 1;	} 	/*	 * The section is not biggest or smallest mem_section in the zone, it	 * only creates a hole in the zone. So in this case, we need not	 * change the zone. But perhaps, the zone has only hole data. Thus	 * it check the zone has only hole or not.	 */	pfn = zone_start_pfn;	for (; pfn < zone_end_pfn; pfn += PAGES_PER_SECTION) {		ms = __pfn_to_section(pfn); 		if (unlikely(!valid_section(ms)))			continue; 		if (page_zone(pfn_to_page(pfn)) != zone)			continue; 		 /* If the section is current section, it continues the loop */		if (start_pfn == pfn)			continue; 		/* If we find valid section, we have nothing to do */		zone_span_writeunlock(zone);		return;	} 	/* The zone has no valid section */	zone->zone_start_pfn = 0;	zone->spanned_pages = 0;	zone_span_writeunlock(zone);} static void shrink_pgdat_span(struct pglist_data *pgdat,			      unsigned long start_pfn, unsigned long end_pfn){	unsigned long pgdat_start_pfn = pgdat->node_start_pfn;	unsigned long p = pgdat_end_pfn(pgdat); /* pgdat_end_pfn namespace clash */	unsigned long pgdat_end_pfn = p;	unsigned long pfn;	struct mem_section *ms;	int nid = pgdat->node_id; 	if (pgdat_start_pfn == start_pfn) {		/*		 * If the section is smallest section in the pgdat, it need		 * shrink pgdat->node_start_pfn and pgdat->node_spanned_pages.		 * In this case, we find second smallest valid mem_section		 * for shrinking zone.		 */		pfn = find_smallest_section_pfn(nid, NULL, end_pfn,						pgdat_end_pfn);		if (pfn) {			pgdat->node_start_pfn = pfn;			pgdat->node_spanned_pages = pgdat_end_pfn - pfn;		}	} else if (pgdat_end_pfn == end_pfn) {		/*		 * If the section is biggest section in the pgdat, it need		 * shrink pgdat->node_spanned_pages.		 * In this case, we find second biggest valid mem_section for		 * shrinking zone.		 */		pfn = find_biggest_section_pfn(nid, NULL, pgdat_start_pfn,					       start_pfn);		if (pfn)			pgdat->node_spanned_pages = pfn - pgdat_start_pfn + 1;	} 	/*	 * If the section is not biggest or smallest mem_section in the pgdat,	 * it only creates a hole in the pgdat. So in this case, we need not	 * change the pgdat.	 * But perhaps, the pgdat has only hole data. Thus it check the pgdat	 * has only hole or not.	 */	pfn = pgdat_start_pfn;	for (; pfn < pgdat_end_pfn; pfn += PAGES_PER_SECTION) {		ms = __pfn_to_section(pfn); 

Lines 1–500 of 1950.