brintos

brintos / linux-shallow public Read only

0
0
Text · 9.5 KiB · d0c1a71 Raw
372 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 * Copyright (C) 2004, 2013 Intel Corporation4 * Author: Naveen B S <naveen.b.s@intel.com>5 * Author: Rafael J. Wysocki <rafael.j.wysocki@intel.com>6 *7 * All rights reserved.8 *9 * ACPI based HotPlug driver that supports Memory Hotplug10 * This driver fields notifications from firmware for memory add11 * and remove operations and alerts the VM of the affected memory12 * ranges.13 */14 15#include <linux/acpi.h>16#include <linux/memory.h>17#include <linux/memory_hotplug.h>18 19#include "internal.h"20 21#define ACPI_MEMORY_DEVICE_CLASS		"memory"22#define ACPI_MEMORY_DEVICE_HID			"PNP0C80"23#define ACPI_MEMORY_DEVICE_NAME			"Hotplug Mem Device"24 25static const struct acpi_device_id memory_device_ids[] = {26	{ACPI_MEMORY_DEVICE_HID, 0},27	{"", 0},28};29 30#ifdef CONFIG_ACPI_HOTPLUG_MEMORY31 32static int acpi_memory_device_add(struct acpi_device *device,33				  const struct acpi_device_id *not_used);34static void acpi_memory_device_remove(struct acpi_device *device);35 36static struct acpi_scan_handler memory_device_handler = {37	.ids = memory_device_ids,38	.attach = acpi_memory_device_add,39	.detach = acpi_memory_device_remove,40	.hotplug = {41		.enabled = true,42	},43};44 45struct acpi_memory_info {46	struct list_head list;47	u64 start_addr;		/* Memory Range start physical addr */48	u64 length;		/* Memory Range length */49	unsigned short caching;	/* memory cache attribute */50	unsigned short write_protect;	/* memory read/write attribute */51	unsigned int enabled:1;52};53 54struct acpi_memory_device {55	struct acpi_device *device;56	struct list_head res_list;57	int mgid;58};59 60static acpi_status61acpi_memory_get_resource(struct acpi_resource *resource, void *context)62{63	struct acpi_memory_device *mem_device = context;64	struct acpi_resource_address64 address64;65	struct acpi_memory_info *info, *new;66	acpi_status status;67 68	status = acpi_resource_to_address64(resource, &address64);69	if (ACPI_FAILURE(status) ||70	    (address64.resource_type != ACPI_MEMORY_RANGE))71		return AE_OK;72 73	list_for_each_entry(info, &mem_device->res_list, list) {74		/* Can we combine the resource range information? */75		if ((info->caching == address64.info.mem.caching) &&76		    (info->write_protect == address64.info.mem.write_protect) &&77		    (info->start_addr + info->length == address64.address.minimum)) {78			info->length += address64.address.address_length;79			return AE_OK;80		}81	}82 83	new = kzalloc(sizeof(struct acpi_memory_info), GFP_KERNEL);84	if (!new)85		return AE_ERROR;86 87	INIT_LIST_HEAD(&new->list);88	new->caching = address64.info.mem.caching;89	new->write_protect = address64.info.mem.write_protect;90	new->start_addr = address64.address.minimum;91	new->length = address64.address.address_length;92	list_add_tail(&new->list, &mem_device->res_list);93 94	return AE_OK;95}96 97static void98acpi_memory_free_device_resources(struct acpi_memory_device *mem_device)99{100	struct acpi_memory_info *info, *n;101 102	list_for_each_entry_safe(info, n, &mem_device->res_list, list)103		kfree(info);104	INIT_LIST_HEAD(&mem_device->res_list);105}106 107static int108acpi_memory_get_device_resources(struct acpi_memory_device *mem_device)109{110	acpi_status status;111 112	if (!list_empty(&mem_device->res_list))113		return 0;114 115	status = acpi_walk_resources(mem_device->device->handle, METHOD_NAME__CRS,116				     acpi_memory_get_resource, mem_device);117	if (ACPI_FAILURE(status)) {118		acpi_memory_free_device_resources(mem_device);119		return -EINVAL;120	}121 122	return 0;123}124 125static int acpi_memory_check_device(struct acpi_memory_device *mem_device)126{127	unsigned long long current_status;128 129	/* Get device present/absent information from the _STA */130	if (ACPI_FAILURE(acpi_evaluate_integer(mem_device->device->handle,131					       METHOD_NAME__STA, NULL,132					       &current_status)))133		return -ENODEV;134	/*135	 * Check for device status. Device should be136	 * present/enabled/functioning.137	 */138	if (!((current_status & ACPI_STA_DEVICE_PRESENT)139	      && (current_status & ACPI_STA_DEVICE_ENABLED)140	      && (current_status & ACPI_STA_DEVICE_FUNCTIONING)))141		return -ENODEV;142 143	return 0;144}145 146static int acpi_bind_memblk(struct memory_block *mem, void *arg)147{148	return acpi_bind_one(&mem->dev, arg);149}150 151static int acpi_bind_memory_blocks(struct acpi_memory_info *info,152				   struct acpi_device *adev)153{154	return walk_memory_blocks(info->start_addr, info->length, adev,155				  acpi_bind_memblk);156}157 158static int acpi_unbind_memblk(struct memory_block *mem, void *arg)159{160	acpi_unbind_one(&mem->dev);161	return 0;162}163 164static void acpi_unbind_memory_blocks(struct acpi_memory_info *info)165{166	walk_memory_blocks(info->start_addr, info->length, NULL,167			   acpi_unbind_memblk);168}169 170static int acpi_memory_enable_device(struct acpi_memory_device *mem_device)171{172	acpi_handle handle = mem_device->device->handle;173	mhp_t mhp_flags = MHP_NID_IS_MGID;174	int result, num_enabled = 0;175	struct acpi_memory_info *info;176	u64 total_length = 0;177	int node, mgid;178 179	node = acpi_get_node(handle);180 181	list_for_each_entry(info, &mem_device->res_list, list) {182		if (!info->length)183			continue;184		/* We want a single node for the whole memory group */185		if (node < 0)186			node = memory_add_physaddr_to_nid(info->start_addr);187		total_length += info->length;188	}189 190	if (!total_length) {191		dev_err(&mem_device->device->dev, "device is empty\n");192		return -EINVAL;193	}194 195	mgid = memory_group_register_static(node, PFN_UP(total_length));196	if (mgid < 0)197		return mgid;198	mem_device->mgid = mgid;199 200	/*201	 * Tell the VM there is more memory here...202	 * Note: Assume that this function returns zero on success203	 * We don't have memory-hot-add rollback function,now.204	 * (i.e. memory-hot-remove function)205	 */206	list_for_each_entry(info, &mem_device->res_list, list) {207		/*208		 * If the memory block size is zero, please ignore it.209		 * Don't try to do the following memory hotplug flowchart.210		 */211		if (!info->length)212			continue;213 214		mhp_flags |= MHP_MEMMAP_ON_MEMORY;215		result = __add_memory(mgid, info->start_addr, info->length,216				      mhp_flags);217 218		/*219		 * If the memory block has been used by the kernel, add_memory()220		 * returns -EEXIST. If add_memory() returns the other error, it221		 * means that this memory block is not used by the kernel.222		 */223		if (result && result != -EEXIST)224			continue;225 226		result = acpi_bind_memory_blocks(info, mem_device->device);227		if (result) {228			acpi_unbind_memory_blocks(info);229			return -ENODEV;230		}231 232		info->enabled = 1;233 234		/*235		 * Add num_enable even if add_memory() returns -EEXIST, so the236		 * device is bound to this driver.237		 */238		num_enabled++;239	}240	if (!num_enabled) {241		dev_err(&mem_device->device->dev, "add_memory failed\n");242		return -EINVAL;243	}244	/*245	 * Sometimes the memory device will contain several memory blocks.246	 * When one memory block is hot-added to the system memory, it will247	 * be regarded as a success.248	 * Otherwise if the last memory block can't be hot-added to the system249	 * memory, it will be failure and the memory device can't be bound with250	 * driver.251	 */252	return 0;253}254 255static void acpi_memory_remove_memory(struct acpi_memory_device *mem_device)256{257	struct acpi_memory_info *info, *n;258 259	list_for_each_entry_safe(info, n, &mem_device->res_list, list) {260		if (!info->enabled)261			continue;262 263		acpi_unbind_memory_blocks(info);264		__remove_memory(info->start_addr, info->length);265		list_del(&info->list);266		kfree(info);267	}268}269 270static void acpi_memory_device_free(struct acpi_memory_device *mem_device)271{272	if (!mem_device)273		return;274 275	/* In case we succeeded adding *some* memory, unregistering fails. */276	if (mem_device->mgid >= 0)277		memory_group_unregister(mem_device->mgid);278 279	acpi_memory_free_device_resources(mem_device);280	mem_device->device->driver_data = NULL;281	kfree(mem_device);282}283 284static int acpi_memory_device_add(struct acpi_device *device,285				  const struct acpi_device_id *not_used)286{287	struct acpi_memory_device *mem_device;288	int result;289 290	if (!device)291		return -EINVAL;292 293	mem_device = kzalloc(sizeof(struct acpi_memory_device), GFP_KERNEL);294	if (!mem_device)295		return -ENOMEM;296 297	INIT_LIST_HEAD(&mem_device->res_list);298	mem_device->device = device;299	mem_device->mgid = -1;300	sprintf(acpi_device_name(device), "%s", ACPI_MEMORY_DEVICE_NAME);301	sprintf(acpi_device_class(device), "%s", ACPI_MEMORY_DEVICE_CLASS);302	device->driver_data = mem_device;303 304	/* Get the range from the _CRS */305	result = acpi_memory_get_device_resources(mem_device);306	if (result) {307		device->driver_data = NULL;308		kfree(mem_device);309		return result;310	}311 312	result = acpi_memory_check_device(mem_device);313	if (result) {314		acpi_memory_device_free(mem_device);315		return 0;316	}317 318	result = acpi_memory_enable_device(mem_device);319	if (result) {320		dev_err(&device->dev, "acpi_memory_enable_device() error\n");321		acpi_memory_device_free(mem_device);322		return result;323	}324 325	dev_dbg(&device->dev, "Memory device configured by ACPI\n");326	return 1;327}328 329static void acpi_memory_device_remove(struct acpi_device *device)330{331	struct acpi_memory_device *mem_device;332 333	if (!device || !acpi_driver_data(device))334		return;335 336	mem_device = acpi_driver_data(device);337	acpi_memory_remove_memory(mem_device);338	acpi_memory_device_free(mem_device);339}340 341static bool __initdata acpi_no_memhotplug;342 343void __init acpi_memory_hotplug_init(void)344{345	if (acpi_no_memhotplug) {346		memory_device_handler.attach = NULL;347		acpi_scan_add_handler(&memory_device_handler);348		return;349	}350	acpi_scan_add_handler_with_hotplug(&memory_device_handler, "memory");351}352 353static int __init disable_acpi_memory_hotplug(char *str)354{355	acpi_no_memhotplug = true;356	return 1;357}358__setup("acpi_no_memhotplug", disable_acpi_memory_hotplug);359 360#else361 362static struct acpi_scan_handler memory_device_handler = {363	.ids = memory_device_ids,364};365 366void __init acpi_memory_hotplug_init(void)367{368	acpi_scan_add_handler(&memory_device_handler);369}370 371#endif /* CONFIG_ACPI_HOTPLUG_MEMORY */372