2451 lines · 68.1 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/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" enum {	MEMMAP_ON_MEMORY_DISABLE = 0,	MEMMAP_ON_MEMORY_ENABLE,	MEMMAP_ON_MEMORY_FORCE,}; static int memmap_mode __read_mostly = MEMMAP_ON_MEMORY_DISABLE; static inline unsigned long memory_block_memmap_size(void){	return PHYS_PFN(memory_block_size_bytes()) * sizeof(struct page);} static inline unsigned long memory_block_memmap_on_memory_pages(void){	unsigned long nr_pages = PFN_UP(memory_block_memmap_size()); 	/*	 * In "forced" memmap_on_memory mode, we add extra pages to align the	 * vmemmap size to cover full pageblocks. That way, we can add memory	 * even if the vmemmap size is not properly aligned, however, we might waste	 * memory.	 */	if (memmap_mode == MEMMAP_ON_MEMORY_FORCE)		return pageblock_align(nr_pages);	return nr_pages;} #ifdef CONFIG_MHP_MEMMAP_ON_MEMORY/* * memory_hotplug.memmap_on_memory parameter */static int set_memmap_mode(const char *val, const struct kernel_param *kp){	int ret, mode;	bool enabled; 	if (sysfs_streq(val, "force") ||  sysfs_streq(val, "FORCE")) {		mode = MEMMAP_ON_MEMORY_FORCE;	} else {		ret = kstrtobool(val, &enabled);		if (ret < 0)			return ret;		if (enabled)			mode = MEMMAP_ON_MEMORY_ENABLE;		else			mode = MEMMAP_ON_MEMORY_DISABLE;	}	*((int *)kp->arg) = mode;	if (mode == MEMMAP_ON_MEMORY_FORCE) {		unsigned long memmap_pages = memory_block_memmap_on_memory_pages(); 		pr_info_once("Memory hotplug will waste %ld pages in each memory block\n",			     memmap_pages - PFN_UP(memory_block_memmap_size()));	}	return 0;} static int get_memmap_mode(char *buffer, const struct kernel_param *kp){	int mode = *((int *)kp->arg); 	if (mode == MEMMAP_ON_MEMORY_FORCE)		return sprintf(buffer, "force\n");	return sprintf(buffer, "%c\n", mode ? 'Y' : 'N');} static const struct kernel_param_ops memmap_mode_ops = {	.set = set_memmap_mode,	.get = get_memmap_mode,};module_param_cb(memmap_on_memory, &memmap_mode_ops, &memmap_mode, 0444);MODULE_PARM_DESC(memmap_on_memory, "Enable memmap on memory for memory hotplug\n"		 "With value \"force\" it could result in memory wastage due "		 "to memmap size limitations (Y/N/force)"); static inline bool mhp_memmap_on_memory(void){	return memmap_mode != MEMMAP_ON_MEMORY_DISABLE;}#elsestatic inline bool mhp_memmap_on_memory(void){	return false;}#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; static int mhp_default_online_type = -1;int mhp_get_default_online_type(void){	if (mhp_default_online_type >= 0)		return mhp_default_online_type; 	if (IS_ENABLED(CONFIG_MHP_DEFAULT_ONLINE_TYPE_OFFLINE))		mhp_default_online_type = MMOP_OFFLINE;	else if (IS_ENABLED(CONFIG_MHP_DEFAULT_ONLINE_TYPE_ONLINE_AUTO))		mhp_default_online_type = MMOP_ONLINE;	else if (IS_ENABLED(CONFIG_MHP_DEFAULT_ONLINE_TYPE_ONLINE_KERNEL))		mhp_default_online_type = MMOP_ONLINE_KERNEL;	else if (IS_ENABLED(CONFIG_MHP_DEFAULT_ONLINE_TYPE_ONLINE_MOVABLE))		mhp_default_online_type = MMOP_ONLINE_MOVABLE;	else		mhp_default_online_type = MMOP_OFFLINE; 	return mhp_default_online_type;} void mhp_set_default_online_type(int online_type){	mhp_default_online_type = online_type;} 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){	/*	 * 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 | nr_pages, min_align))		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); int __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(!pgprot_val(params->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;	} 	if (check_pfn_span(pfn, nr_pages)) {		WARN(1, "Misaligned %s start: %#lx end: %#lx\n", __func__, pfn, pfn + nr_pages - 1);		return -EINVAL;	} 	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,					 params->pgmap);		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) {

Lines 1–500 of 2451.