acpi_memhotplug.c 9.8 KB
Newer Older
L
Linus Torvalds 已提交
1
/*
2 3 4
 * Copyright (C) 2004, 2013 Intel Corporation
 * Author: Naveen B S <naveen.b.s@intel.com>
 * Author: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
L
Linus Torvalds 已提交
5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
 *
 * All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or (at
 * your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or
 * NON INFRINGEMENT.  See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *
 * ACPI based HotPlug driver that supports Memory Hotplug
25
 * This driver fields notifications from firmware for memory add
L
Linus Torvalds 已提交
26 27 28 29
 * and remove operations and alerts the VM of the affected memory
 * ranges.
 */

30
#include <linux/acpi.h>
31
#include <linux/memory.h>
32 33 34
#include <linux/memory_hotplug.h>

#include "internal.h"
L
Linus Torvalds 已提交
35 36 37 38 39 40 41

#define ACPI_MEMORY_DEVICE_CLASS		"memory"
#define ACPI_MEMORY_DEVICE_HID			"PNP0C80"
#define ACPI_MEMORY_DEVICE_NAME			"Hotplug Mem Device"

#define _COMPONENT		ACPI_MEMORY_DEVICE_COMPONENT

42 43 44
#undef PREFIX
#define 	PREFIX		"ACPI:memory_hp:"

45
ACPI_MODULE_NAME("acpi_memhotplug");
L
Linus Torvalds 已提交
46 47 48 49 50 51

/* Memory Device States */
#define MEMORY_INVALID_STATE	0
#define MEMORY_POWER_ON_STATE	1
#define MEMORY_POWER_OFF_STATE	2

52 53 54
static int acpi_memory_device_add(struct acpi_device *device,
				  const struct acpi_device_id *not_used);
static void acpi_memory_device_remove(struct acpi_device *device);
L
Linus Torvalds 已提交
55

56 57 58 59 60
static const struct acpi_device_id memory_device_ids[] = {
	{ACPI_MEMORY_DEVICE_HID, 0},
	{"", 0},
};

61
static struct acpi_scan_handler memory_device_handler = {
62
	.ids = memory_device_ids,
63 64 65 66 67
	.attach = acpi_memory_device_add,
	.detach = acpi_memory_device_remove,
	.hotplug = {
		.enabled = true,
	},
L
Linus Torvalds 已提交
68 69
};

70 71 72 73 74 75 76 77 78
struct acpi_memory_info {
	struct list_head list;
	u64 start_addr;		/* Memory Range start physical addr */
	u64 length;		/* Memory Range length */
	unsigned short caching;	/* memory cache attribute */
	unsigned short write_protect;	/* memory read/write attribute */
	unsigned int enabled:1;
};

L
Linus Torvalds 已提交
79
struct acpi_memory_device {
80
	struct acpi_device * device;
L
Len Brown 已提交
81
	unsigned int state;	/* State of the memory device */
82
	struct list_head res_list;
L
Linus Torvalds 已提交
83 84
};

85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121
static acpi_status
acpi_memory_get_resource(struct acpi_resource *resource, void *context)
{
	struct acpi_memory_device *mem_device = context;
	struct acpi_resource_address64 address64;
	struct acpi_memory_info *info, *new;
	acpi_status status;

	status = acpi_resource_to_address64(resource, &address64);
	if (ACPI_FAILURE(status) ||
	    (address64.resource_type != ACPI_MEMORY_RANGE))
		return AE_OK;

	list_for_each_entry(info, &mem_device->res_list, list) {
		/* Can we combine the resource range information? */
		if ((info->caching == address64.info.mem.caching) &&
		    (info->write_protect == address64.info.mem.write_protect) &&
		    (info->start_addr + info->length == address64.minimum)) {
			info->length += address64.address_length;
			return AE_OK;
		}
	}

	new = kzalloc(sizeof(struct acpi_memory_info), GFP_KERNEL);
	if (!new)
		return AE_ERROR;

	INIT_LIST_HEAD(&new->list);
	new->caching = address64.info.mem.caching;
	new->write_protect = address64.info.mem.write_protect;
	new->start_addr = address64.minimum;
	new->length = address64.address_length;
	list_add_tail(&new->list, &mem_device->res_list);

	return AE_OK;
}

122 123 124 125 126 127 128 129 130 131
static void
acpi_memory_free_device_resources(struct acpi_memory_device *mem_device)
{
	struct acpi_memory_info *info, *n;

	list_for_each_entry_safe(info, n, &mem_device->res_list, list)
		kfree(info);
	INIT_LIST_HEAD(&mem_device->res_list);
}

L
Linus Torvalds 已提交
132 133 134 135 136
static int
acpi_memory_get_device_resources(struct acpi_memory_device *mem_device)
{
	acpi_status status;

137 138 139
	if (!list_empty(&mem_device->res_list))
		return 0;

140
	status = acpi_walk_resources(mem_device->device->handle, METHOD_NAME__CRS,
141 142
				     acpi_memory_get_resource, mem_device);
	if (ACPI_FAILURE(status)) {
143
		acpi_memory_free_device_resources(mem_device);
144
		return -EINVAL;
L
Linus Torvalds 已提交
145 146
	}

147
	return 0;
L
Linus Torvalds 已提交
148 149
}

L
Len Brown 已提交
150
static int acpi_memory_check_device(struct acpi_memory_device *mem_device)
L
Linus Torvalds 已提交
151
{
152
	unsigned long long current_status;
L
Linus Torvalds 已提交
153 154

	/* Get device present/absent information from the _STA */
155
	if (ACPI_FAILURE(acpi_evaluate_integer(mem_device->device->handle, "_STA",
L
Len Brown 已提交
156
					       NULL, &current_status)))
157
		return -ENODEV;
L
Linus Torvalds 已提交
158 159 160 161
	/*
	 * Check for device status. Device should be
	 * present/enabled/functioning.
	 */
162 163 164
	if (!((current_status & ACPI_STA_DEVICE_PRESENT)
	      && (current_status & ACPI_STA_DEVICE_ENABLED)
	      && (current_status & ACPI_STA_DEVICE_FUNCTIONING)))
165
		return -ENODEV;
L
Linus Torvalds 已提交
166

167
	return 0;
L
Linus Torvalds 已提交
168 169
}

170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205
static unsigned long acpi_meminfo_start_pfn(struct acpi_memory_info *info)
{
	return PFN_DOWN(info->start_addr);
}

static unsigned long acpi_meminfo_end_pfn(struct acpi_memory_info *info)
{
	return PFN_UP(info->start_addr + info->length-1);
}

static int acpi_bind_memblk(struct memory_block *mem, void *arg)
{
	return acpi_bind_one(&mem->dev, (acpi_handle)arg);
}

static int acpi_bind_memory_blocks(struct acpi_memory_info *info,
				   acpi_handle handle)
{
	return walk_memory_range(acpi_meminfo_start_pfn(info),
				 acpi_meminfo_end_pfn(info), (void *)handle,
				 acpi_bind_memblk);
}

static int acpi_unbind_memblk(struct memory_block *mem, void *arg)
{
	acpi_unbind_one(&mem->dev);
	return 0;
}

static void acpi_unbind_memory_blocks(struct acpi_memory_info *info,
				      acpi_handle handle)
{
	walk_memory_range(acpi_meminfo_start_pfn(info),
			  acpi_meminfo_end_pfn(info), NULL, acpi_unbind_memblk);
}

L
Len Brown 已提交
206
static int acpi_memory_enable_device(struct acpi_memory_device *mem_device)
L
Linus Torvalds 已提交
207
{
208
	acpi_handle handle = mem_device->device->handle;
209 210
	int result, num_enabled = 0;
	struct acpi_memory_info *info;
211
	int node;
L
Linus Torvalds 已提交
212

213
	node = acpi_get_node(handle);
L
Linus Torvalds 已提交
214 215 216
	/*
	 * Tell the VM there is more memory here...
	 * Note: Assume that this function returns zero on success
217 218
	 * We don't have memory-hot-add rollback function,now.
	 * (i.e. memory-hot-remove function)
L
Linus Torvalds 已提交
219
	 */
220
	list_for_each_entry(info, &mem_device->res_list, list) {
221
		if (info->enabled) { /* just sanity check...*/
222 223 224
			num_enabled++;
			continue;
		}
225 226 227 228 229 230
		/*
		 * If the memory block size is zero, please ignore it.
		 * Don't try to do the following memory hotplug flowchart.
		 */
		if (!info->length)
			continue;
231 232 233
		if (node < 0)
			node = memory_add_physaddr_to_nid(info->start_addr);

234
		result = add_memory(node, info->start_addr, info->length);
235 236 237 238 239 240

		/*
		 * If the memory block has been used by the kernel, add_memory()
		 * returns -EEXIST. If add_memory() returns the other error, it
		 * means that this memory block is not used by the kernel.
		 */
241
		if (result && result != -EEXIST)
242
			continue;
243

244 245 246 247 248 249
		result = acpi_bind_memory_blocks(info, handle);
		if (result) {
			acpi_unbind_memory_blocks(info, handle);
			return -ENODEV;
		}

250 251
		info->enabled = 1;

252 253 254 255
		/*
		 * Add num_enable even if add_memory() returns -EEXIST, so the
		 * device is bound to this driver.
		 */
256 257 258
		num_enabled++;
	}
	if (!num_enabled) {
259
		dev_err(&mem_device->device->dev, "add_memory failed\n");
L
Linus Torvalds 已提交
260
		mem_device->state = MEMORY_INVALID_STATE;
261
		return -EINVAL;
L
Linus Torvalds 已提交
262
	}
263 264 265 266 267 268 269 270 271
	/*
	 * Sometimes the memory device will contain several memory blocks.
	 * When one memory block is hot-added to the system memory, it will
	 * be regarded as a success.
	 * Otherwise if the last memory block can't be hot-added to the system
	 * memory, it will be failure and the memory device can't be bound with
	 * driver.
	 */
	return 0;
L
Linus Torvalds 已提交
272 273
}

274
static void acpi_memory_remove_memory(struct acpi_memory_device *mem_device)
L
Linus Torvalds 已提交
275
{
276
	acpi_handle handle = mem_device->device->handle;
277
	struct acpi_memory_info *info, *n;
278
	int nid = acpi_get_node(handle);
279

280
	list_for_each_entry_safe(info, n, &mem_device->res_list, list) {
281
		if (!info->enabled)
282
			continue;
283

284 285
		if (nid < 0)
			nid = memory_add_physaddr_to_nid(info->start_addr);
286 287

		acpi_unbind_memory_blocks(info, handle);
288
		remove_memory(nid, info->start_addr, info->length);
289
		list_del(&info->list);
290
		kfree(info);
L
Linus Torvalds 已提交
291
	}
292 293
}

294 295 296 297 298 299
static void acpi_memory_device_free(struct acpi_memory_device *mem_device)
{
	if (!mem_device)
		return;

	acpi_memory_free_device_resources(mem_device);
300
	mem_device->device->driver_data = NULL;
301 302 303
	kfree(mem_device);
}

304 305
static int acpi_memory_device_add(struct acpi_device *device,
				  const struct acpi_device_id *not_used)
L
Linus Torvalds 已提交
306
{
307
	struct acpi_memory_device *mem_device;
L
Linus Torvalds 已提交
308 309 310
	int result;

	if (!device)
311
		return -EINVAL;
L
Linus Torvalds 已提交
312

313
	mem_device = kzalloc(sizeof(struct acpi_memory_device), GFP_KERNEL);
L
Linus Torvalds 已提交
314
	if (!mem_device)
315
		return -ENOMEM;
L
Linus Torvalds 已提交
316

317
	INIT_LIST_HEAD(&mem_device->res_list);
318
	mem_device->device = device;
L
Linus Torvalds 已提交
319 320
	sprintf(acpi_device_name(device), "%s", ACPI_MEMORY_DEVICE_NAME);
	sprintf(acpi_device_class(device), "%s", ACPI_MEMORY_DEVICE_CLASS);
321
	device->driver_data = mem_device;
L
Linus Torvalds 已提交
322 323 324 325

	/* Get the range from the _CRS */
	result = acpi_memory_get_device_resources(mem_device);
	if (result) {
326
		device->driver_data = NULL;
L
Linus Torvalds 已提交
327
		kfree(mem_device);
328
		return result;
L
Linus Torvalds 已提交
329 330 331 332 333
	}

	/* Set the device state */
	mem_device->state = MEMORY_POWER_ON_STATE;

334 335 336 337 338
	result = acpi_memory_check_device(mem_device);
	if (result) {
		acpi_memory_device_free(mem_device);
		return 0;
	}
L
Linus Torvalds 已提交
339

340 341 342 343
	result = acpi_memory_enable_device(mem_device);
	if (result) {
		dev_err(&device->dev, "acpi_memory_enable_device() error\n");
		acpi_memory_device_free(mem_device);
344
		return result;
345
	}
346 347 348

	dev_dbg(&device->dev, "Memory device configured by ACPI\n");
	return 1;
349 350
}

351
static void acpi_memory_device_remove(struct acpi_device *device)
352
{
353
	struct acpi_memory_device *mem_device;
354 355

	if (!device || !acpi_driver_data(device))
356
		return;
357 358

	mem_device = acpi_driver_data(device);
359
	acpi_memory_remove_memory(mem_device);
360
	acpi_memory_device_free(mem_device);
L
Linus Torvalds 已提交
361 362
}

363
void __init acpi_memory_hotplug_init(void)
L
Linus Torvalds 已提交
364
{
365
	acpi_scan_add_handler_with_hotplug(&memory_device_handler, "memory");
L
Linus Torvalds 已提交
366
}