1240 lines · 32.2 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0/* * Memory subsystem support * * Written by Matt Tolentino <matthew.e.tolentino@intel.com> *            Dave Hansen <haveblue@us.ibm.com> * * This file provides the necessary infrastructure to represent * a SPARSEMEM-memory-model system's physical memory in /sysfs. * All arch-independent code that assumes MEMORY_HOTPLUG requires * SPARSEMEM should be contained here, or in mm/memory_hotplug.c. */ #include <linux/module.h>#include <linux/init.h>#include <linux/topology.h>#include <linux/capability.h>#include <linux/device.h>#include <linux/memory.h>#include <linux/memory_hotplug.h>#include <linux/mm.h>#include <linux/stat.h>#include <linux/slab.h>#include <linux/xarray.h> #include <linux/atomic.h>#include <linux/uaccess.h> #define MEMORY_CLASS_NAME	"memory" static const char *const online_type_to_str[] = {	[MMOP_OFFLINE] = "offline",	[MMOP_ONLINE] = "online",	[MMOP_ONLINE_KERNEL] = "online_kernel",	[MMOP_ONLINE_MOVABLE] = "online_movable",}; int mhp_online_type_from_str(const char *str){	int i; 	for (i = 0; i < ARRAY_SIZE(online_type_to_str); i++) {		if (sysfs_streq(str, online_type_to_str[i]))			return i;	}	return -EINVAL;} #define to_memory_block(dev) container_of(dev, struct memory_block, dev) static int sections_per_block; static inline unsigned long memory_block_id(unsigned long section_nr){	return section_nr / sections_per_block;} static inline unsigned long pfn_to_block_id(unsigned long pfn){	return memory_block_id(pfn_to_section_nr(pfn));} static inline unsigned long phys_to_block_id(unsigned long phys){	return pfn_to_block_id(PFN_DOWN(phys));} static int memory_subsys_online(struct device *dev);static int memory_subsys_offline(struct device *dev); static const struct bus_type memory_subsys = {	.name = MEMORY_CLASS_NAME,	.dev_name = MEMORY_CLASS_NAME,	.online = memory_subsys_online,	.offline = memory_subsys_offline,}; /* * Memory blocks are cached in a local radix tree to avoid * a costly linear search for the corresponding device on * the subsystem bus. */static DEFINE_XARRAY(memory_blocks); /* * Memory groups, indexed by memory group id (mgid). */static DEFINE_XARRAY_FLAGS(memory_groups, XA_FLAGS_ALLOC);#define MEMORY_GROUP_MARK_DYNAMIC	XA_MARK_1 static BLOCKING_NOTIFIER_HEAD(memory_chain); int register_memory_notifier(struct notifier_block *nb){	return blocking_notifier_chain_register(&memory_chain, nb);}EXPORT_SYMBOL(register_memory_notifier); void unregister_memory_notifier(struct notifier_block *nb){	blocking_notifier_chain_unregister(&memory_chain, nb);}EXPORT_SYMBOL(unregister_memory_notifier); static void memory_block_release(struct device *dev){	struct memory_block *mem = to_memory_block(dev);	/* Verify that the altmap is freed */	WARN_ON(mem->altmap);	kfree(mem);} unsigned long __weak memory_block_size_bytes(void){	return MIN_MEMORY_BLOCK_SIZE;}EXPORT_SYMBOL_GPL(memory_block_size_bytes); /* Show the memory block ID, relative to the memory block size */static ssize_t phys_index_show(struct device *dev,			       struct device_attribute *attr, char *buf){	struct memory_block *mem = to_memory_block(dev); 	return sysfs_emit(buf, "%08lx\n", memory_block_id(mem->start_section_nr));} /* * Legacy interface that we cannot remove. Always indicate "removable" * with CONFIG_MEMORY_HOTREMOVE - bad heuristic. */static ssize_t removable_show(struct device *dev, struct device_attribute *attr,			      char *buf){	return sysfs_emit(buf, "%d\n", (int)IS_ENABLED(CONFIG_MEMORY_HOTREMOVE));} /* * online, offline, going offline, etc. */static ssize_t state_show(struct device *dev, struct device_attribute *attr,			  char *buf){	struct memory_block *mem = to_memory_block(dev);	const char *output; 	/*	 * We can probably put these states in a nice little array	 * so that they're not open-coded	 */	switch (mem->state) {	case MEM_ONLINE:		output = "online";		break;	case MEM_OFFLINE:		output = "offline";		break;	case MEM_GOING_OFFLINE:		output = "going-offline";		break;	default:		WARN_ON(1);		return sysfs_emit(buf, "ERROR-UNKNOWN-%ld\n", mem->state);	} 	return sysfs_emit(buf, "%s\n", output);} int memory_notify(unsigned long val, void *v){	return blocking_notifier_call_chain(&memory_chain, val, v);} #if defined(CONFIG_MEMORY_FAILURE) && defined(CONFIG_MEMORY_HOTPLUG)static unsigned long memblk_nr_poison(struct memory_block *mem);#elsestatic inline unsigned long memblk_nr_poison(struct memory_block *mem){	return 0;}#endif /* * Must acquire mem_hotplug_lock in write mode. */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 = 0;	struct memory_notify arg;	struct zone *zone;	int ret; 	if (memblk_nr_poison(mem))		return -EHWPOISON; 	zone = zone_for_pfn_range(mem->online_type, mem->nid, mem->group,				  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 (mem->altmap)		nr_vmemmap_pages = mem->altmap->free; 	arg.altmap_start_pfn = start_pfn;	arg.altmap_nr_pages = nr_vmemmap_pages;	arg.start_pfn = start_pfn + nr_vmemmap_pages;	arg.nr_pages = nr_pages - nr_vmemmap_pages;	mem_hotplug_begin();	ret = memory_notify(MEM_PREPARE_ONLINE, &arg);	ret = notifier_to_errno(ret);	if (ret)		goto out_notifier; 	if (nr_vmemmap_pages) {		ret = mhp_init_memmap_on_memory(start_pfn, nr_vmemmap_pages,						zone, mem->altmap->inaccessible);		if (ret)			goto out;	} 	ret = online_pages(start_pfn + nr_vmemmap_pages,			   nr_pages - nr_vmemmap_pages, zone, mem->group);	if (ret) {		if (nr_vmemmap_pages)			mhp_deinit_memmap_on_memory(start_pfn, nr_vmemmap_pages);		goto out;	} 	/*	 * Account once onlining succeeded. If the zone was unpopulated, it is	 * now already properly populated.	 */	if (nr_vmemmap_pages)		adjust_present_page_count(pfn_to_page(start_pfn), mem->group,					  nr_vmemmap_pages); 	mem->zone = zone;	mem_hotplug_done();	return ret;out:	memory_notify(MEM_FINISH_OFFLINE, &arg);out_notifier:	mem_hotplug_done();	return ret;} /* * Must acquire mem_hotplug_lock in write mode. */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 = 0;	struct memory_notify arg;	int ret; 	if (!mem->zone)		return -EINVAL; 	/*	 * Unaccount before offlining, such that unpopulated zone and kthreads	 * can properly be torn down in offline_pages().	 */	if (mem->altmap)		nr_vmemmap_pages = mem->altmap->free; 	mem_hotplug_begin();	if (nr_vmemmap_pages)		adjust_present_page_count(pfn_to_page(start_pfn), mem->group,					  -nr_vmemmap_pages); 	ret = offline_pages(start_pfn + nr_vmemmap_pages,			    nr_pages - nr_vmemmap_pages, mem->zone, mem->group);	if (ret) {		/* offline_pages() failed. Account back. */		if (nr_vmemmap_pages)			adjust_present_page_count(pfn_to_page(start_pfn),						  mem->group, nr_vmemmap_pages);		goto out;	} 	if (nr_vmemmap_pages)		mhp_deinit_memmap_on_memory(start_pfn, nr_vmemmap_pages); 	mem->zone = NULL;	arg.altmap_start_pfn = start_pfn;	arg.altmap_nr_pages = nr_vmemmap_pages;	arg.start_pfn = start_pfn + nr_vmemmap_pages;	arg.nr_pages = nr_pages - nr_vmemmap_pages;	memory_notify(MEM_FINISH_OFFLINE, &arg);out:	mem_hotplug_done();	return ret;} /* * MEMORY_HOTPLUG depends on SPARSEMEM in mm/Kconfig, so it is * OK to have direct references to sparsemem variables in here. */static intmemory_block_action(struct memory_block *mem, unsigned long action){	int ret; 	switch (action) {	case MEM_ONLINE:		ret = memory_block_online(mem);		break;	case MEM_OFFLINE:		ret = memory_block_offline(mem);		break;	default:		WARN(1, KERN_WARNING "%s(%ld, %ld) unknown action: "		     "%ld\n", __func__, mem->start_section_nr, action, action);		ret = -EINVAL;	} 	return ret;} static int memory_block_change_state(struct memory_block *mem,		unsigned long to_state, unsigned long from_state_req){	int ret = 0; 	if (mem->state != from_state_req)		return -EINVAL; 	if (to_state == MEM_OFFLINE)		mem->state = MEM_GOING_OFFLINE; 	ret = memory_block_action(mem, to_state);	mem->state = ret ? from_state_req : to_state; 	return ret;} /* The device lock serializes operations on memory_subsys_[online|offline] */static int memory_subsys_online(struct device *dev){	struct memory_block *mem = to_memory_block(dev);	int ret; 	if (mem->state == MEM_ONLINE)		return 0; 	/*	 * When called via device_online() without configuring the online_type,	 * we want to default to MMOP_ONLINE.	 */	if (mem->online_type == MMOP_OFFLINE)		mem->online_type = MMOP_ONLINE; 	ret = memory_block_change_state(mem, MEM_ONLINE, MEM_OFFLINE);	mem->online_type = MMOP_OFFLINE; 	return ret;} static int memory_subsys_offline(struct device *dev){	struct memory_block *mem = to_memory_block(dev); 	if (mem->state == MEM_OFFLINE)		return 0; 	return memory_block_change_state(mem, MEM_OFFLINE, MEM_ONLINE);} static ssize_t state_store(struct device *dev, struct device_attribute *attr,			   const char *buf, size_t count){	const int online_type = mhp_online_type_from_str(buf);	struct memory_block *mem = to_memory_block(dev);	int ret; 	if (online_type < 0)		return -EINVAL; 	ret = lock_device_hotplug_sysfs();	if (ret)		return ret; 	switch (online_type) {	case MMOP_ONLINE_KERNEL:	case MMOP_ONLINE_MOVABLE:	case MMOP_ONLINE:		/* mem->online_type is protected by device_hotplug_lock */		mem->online_type = online_type;		ret = device_online(&mem->dev);		break;	case MMOP_OFFLINE:		ret = device_offline(&mem->dev);		break;	default:		ret = -EINVAL; /* should never happen */	} 	unlock_device_hotplug(); 	if (ret < 0)		return ret;	if (ret)		return -EINVAL; 	return count;} /* * Legacy interface that we cannot remove: s390x exposes the storage increment * covered by a memory block, allowing for identifying which memory blocks * comprise a storage increment. Since a memory block spans complete * storage increments nowadays, this interface is basically unused. Other * archs never exposed != 0. */static ssize_t phys_device_show(struct device *dev,				struct device_attribute *attr, char *buf){	struct memory_block *mem = to_memory_block(dev);	unsigned long start_pfn = section_nr_to_pfn(mem->start_section_nr); 	return sysfs_emit(buf, "%d\n",			  arch_get_memory_phys_device(start_pfn));} #ifdef CONFIG_MEMORY_HOTREMOVEstatic int print_allowed_zone(char *buf, int len, int nid,			      struct memory_group *group,			      unsigned long start_pfn, unsigned long nr_pages,			      int online_type, struct zone *default_zone){	struct zone *zone; 	zone = zone_for_pfn_range(online_type, nid, group, start_pfn, nr_pages);	if (zone == default_zone)		return 0; 	return sysfs_emit_at(buf, len, " %s", zone->name);} static ssize_t valid_zones_show(struct device *dev,				struct device_attribute *attr, char *buf){	struct memory_block *mem = to_memory_block(dev);	unsigned long start_pfn = section_nr_to_pfn(mem->start_section_nr);	unsigned long nr_pages = PAGES_PER_SECTION * sections_per_block;	struct memory_group *group = mem->group;	struct zone *default_zone;	int nid = mem->nid;	int len; 	/*	 * Check the existing zone. Make sure that we do that only on the	 * online nodes otherwise the page_zone is not reliable	 */	if (mem->state == MEM_ONLINE) {		/*		 * If !mem->zone, the memory block spans multiple zones and		 * cannot get offlined.		 */		return sysfs_emit(buf, "%s\n",				  mem->zone ? mem->zone->name : "none");	} 	default_zone = zone_for_pfn_range(MMOP_ONLINE, nid, group,					  start_pfn, nr_pages); 	len = sysfs_emit(buf, "%s", default_zone->name);	len += print_allowed_zone(buf, len, nid, group, start_pfn, nr_pages,				  MMOP_ONLINE_KERNEL, default_zone);	len += print_allowed_zone(buf, len, nid, group, start_pfn, nr_pages,				  MMOP_ONLINE_MOVABLE, default_zone);	len += sysfs_emit_at(buf, len, "\n");	return len;}static DEVICE_ATTR_RO(valid_zones);#endif static DEVICE_ATTR_RO(phys_index);static DEVICE_ATTR_RW(state);static DEVICE_ATTR_RO(phys_device);static DEVICE_ATTR_RO(removable); /* * Show the memory block size (shared by all memory blocks). */static ssize_t block_size_bytes_show(struct device *dev,				     struct device_attribute *attr, char *buf){	return sysfs_emit(buf, "%lx\n", memory_block_size_bytes());} 

Lines 1–500 of 1240.