2339 lines · 65.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/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 <linux/module.h> #include <asm/tlbflush.h> #include "internal.h"#include "shuffle.h"  /* * memory_hotplug.memmap_on_memory parameter */static bool memmap_on_memory __ro_after_init;#ifdef CONFIG_MHP_MEMMAP_ON_MEMORYmodule_param(memmap_on_memory, bool, 0444);MODULE_PARM_DESC(memmap_on_memory, "Enable memmap on memory for memory hotplug");#endif enum {	ONLINE_POLICY_CONTIG_ZONES = 0,	ONLINE_POLICY_AUTO_MOVABLE,}; static const char * const online_policy_to_str[] = {	[ONLINE_POLICY_CONTIG_ZONES] = "contig-zones",	[ONLINE_POLICY_AUTO_MOVABLE] = "auto-movable",}; static int set_online_policy(const char *val, const struct kernel_param *kp){	int ret = sysfs_match_string(online_policy_to_str, val); 	if (ret < 0)		return ret;	*((int *)kp->arg) = ret;	return 0;} static int get_online_policy(char *buffer, const struct kernel_param *kp){	return sprintf(buffer, "%s\n", online_policy_to_str[*((int *)kp->arg)]);} /* * memory_hotplug.online_policy: configure online behavior when onlining without * specifying a zone (MMOP_ONLINE) * * "contig-zones": keep zone contiguous * "auto-movable": online memory to ZONE_MOVABLE if the configuration *                 (auto_movable_ratio, auto_movable_numa_aware) allows for it */static int online_policy __read_mostly = ONLINE_POLICY_CONTIG_ZONES;static const struct kernel_param_ops online_policy_ops = {	.set = set_online_policy,	.get = get_online_policy,};module_param_cb(online_policy, &online_policy_ops, &online_policy, 0644);MODULE_PARM_DESC(online_policy,		"Set the online policy (\"contig-zones\", \"auto-movable\") "		"Default: \"contig-zones\""); /* * memory_hotplug.auto_movable_ratio: specify maximum MOVABLE:KERNEL ratio * * The ratio represent an upper limit and the kernel might decide to not * online some memory to ZONE_MOVABLE -- e.g., because hotplugged KERNEL memory * doesn't allow for more MOVABLE memory. */static unsigned int auto_movable_ratio __read_mostly = 301;module_param(auto_movable_ratio, uint, 0644);MODULE_PARM_DESC(auto_movable_ratio,		"Set the maximum ratio of MOVABLE:KERNEL memory in the system "		"in percent for \"auto-movable\" online policy. Default: 301"); /* * memory_hotplug.auto_movable_numa_aware: consider numa node stats */#ifdef CONFIG_NUMAstatic bool auto_movable_numa_aware __read_mostly = true;module_param(auto_movable_numa_aware, bool, 0644);MODULE_PARM_DESC(auto_movable_numa_aware,		"Consider numa node stats in addition to global stats in "		"\"auto-movable\" online policy. Default: true");#endif /* CONFIG_NUMA */ /* * 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 mhp_default_online_type = MMOP_OFFLINE;#elseint mhp_default_online_type = MMOP_ONLINE;#endif static int __init setup_memhp_default_state(char *str){	const int online_type = mhp_online_type_from_str(str); 	if (online_type >= 0)		mhp_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_SYSRAM_DRIVER_MANAGED; 	if (!mhp_range_allowed(start, size, true))		return ERR_PTR(-E2BIG); 	/*	 * 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);} 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;} /* * Return page for the valid pfn only if the page is online. All pfn * walkers which rely on the fully initialized page->flags and others * should use this rather than pfn_valid && pfn_to_page */struct page *pfn_to_online_page(unsigned long pfn){	unsigned long nr = pfn_to_section_nr(pfn);	struct dev_pagemap *pgmap;	struct mem_section *ms; 	if (nr >= NR_MEM_SECTIONS)		return NULL; 	ms = __nr_to_section(nr);	if (!online_section(ms))		return NULL; 	/*	 * Save some code text when online_section() +	 * pfn_section_valid() are sufficient.	 */	if (IS_ENABLED(CONFIG_HAVE_ARCH_PFN_VALID) && !pfn_valid(pfn))		return NULL; 	if (!pfn_section_valid(ms, pfn))		return NULL; 	if (!online_device_section(ms))		return pfn_to_page(pfn); 	/*	 * Slowpath: when ZONE_DEVICE collides with	 * ZONE_{NORMAL,MOVABLE} within the same section some pfns in	 * the section may be 'offline' but 'valid'. Only	 * get_dev_pagemap() can determine sub-section online status.	 */	pgmap = get_dev_pagemap(pfn, NULL);	put_dev_pagemap(pgmap); 	/* The presence of a pgmap indicates ZONE_DEVICE offline pfn */	if (pgmap)		return NULL; 	return pfn_to_page(pfn);}EXPORT_SYMBOL_GPL(pfn_to_online_page); /* * 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; 	VM_BUG_ON(!mhp_range_allowed(PFN_PHYS(pfn), nr_pages * PAGE_SIZE, false)); 	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;} /* 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); 	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;		}	}} 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 end_pfn = zone_end_pfn(zone); 		/* 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 = end_pfn;			continue;		} 		if (end_pfn > node_end_pfn)			node_end_pfn = 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; 	/* 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);	} 	/*	 * Zone shrinking code cannot properly deal with ZONE_DEVICE. So	 * we will not try to shrink the zones - which is okay as	 * set_zone_contiguous() cannot deal with ZONE_DEVICE either way.	 */	if (zone_is_zone_device(zone))		return; 	clear_zone_contiguous(zone); 	shrink_zone_span(zone, start_pfn, start_pfn + nr_pages);	update_pgdat_span(pgdat); 	set_zone_contiguous(zone);} static void __remove_section(unsigned long pfn, unsigned long nr_pages,			     unsigned long map_offset,			     struct vmem_altmap *altmap){	struct mem_section *ms = __pfn_to_section(pfn); 

Lines 1–500 of 2339.