acpi_memhotplug.c 14.7 KB
Newer Older
L
Linus Torvalds 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
/*
 * Copyright (C) 2004 Intel Corporation <naveen.b.s@intel.com>
 *
 * 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
23
 * This driver fields notifications from firmware for memory add
L
Linus Torvalds 已提交
24 25 26 27 28 29 30 31 32
 * and remove operations and alerts the VM of the affected memory
 * ranges.
 */

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/memory_hotplug.h>
33
#include <linux/slab.h>
L
Linus Torvalds 已提交
34 35 36 37 38 39 40 41
#include <acpi/acpi_drivers.h>

#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 46
ACPI_MODULE_NAME("acpi_memhotplug");
MODULE_AUTHOR("Naveen B S <naveen.b.s@intel.com>");
47
MODULE_DESCRIPTION("Hotplug Mem Driver");
L
Linus Torvalds 已提交
48 49 50 51 52 53 54
MODULE_LICENSE("GPL");

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

L
Len Brown 已提交
55 56
static int acpi_memory_device_add(struct acpi_device *device);
static int acpi_memory_device_remove(struct acpi_device *device, int type);
L
Linus Torvalds 已提交
57

58 59 60 61 62 63
static const struct acpi_device_id memory_device_ids[] = {
	{ACPI_MEMORY_DEVICE_HID, 0},
	{"", 0},
};
MODULE_DEVICE_TABLE(acpi, memory_device_ids);

L
Linus Torvalds 已提交
64
static struct acpi_driver acpi_memory_device_driver = {
L
Len Brown 已提交
65
	.name = "acpi_memhotplug",
L
Len Brown 已提交
66
	.class = ACPI_MEMORY_DEVICE_CLASS,
67
	.ids = memory_device_ids,
L
Len Brown 已提交
68 69 70 71
	.ops = {
		.add = acpi_memory_device_add,
		.remove = acpi_memory_device_remove,
		},
L
Linus Torvalds 已提交
72 73
};

74 75 76 77 78 79 80
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;
81
	unsigned int failed:1;
82 83
};

L
Linus Torvalds 已提交
84
struct acpi_memory_device {
85
	struct acpi_device * device;
L
Len Brown 已提交
86
	unsigned int state;	/* State of the memory device */
87
	struct list_head res_list;
L
Linus Torvalds 已提交
88 89
};

90 91
static int acpi_hotmem_initialized;

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 122 123 124 125 126 127 128
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;
}

129 130 131 132 133 134 135 136 137 138
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 已提交
139 140 141 142 143
static int
acpi_memory_get_device_resources(struct acpi_memory_device *mem_device)
{
	acpi_status status;

144 145 146
	if (!list_empty(&mem_device->res_list))
		return 0;

147
	status = acpi_walk_resources(mem_device->device->handle, METHOD_NAME__CRS,
148 149
				     acpi_memory_get_resource, mem_device);
	if (ACPI_FAILURE(status)) {
150
		acpi_memory_free_device_resources(mem_device);
151
		return -EINVAL;
L
Linus Torvalds 已提交
152 153
	}

154
	return 0;
L
Linus Torvalds 已提交
155 156 157 158
}

static int
acpi_memory_get_device(acpi_handle handle,
L
Len Brown 已提交
159
		       struct acpi_memory_device **mem_device)
L
Linus Torvalds 已提交
160 161 162 163 164
{
	acpi_status status;
	acpi_handle phandle;
	struct acpi_device *device = NULL;
	struct acpi_device *pdevice = NULL;
165
	int result;
L
Linus Torvalds 已提交
166 167 168 169 170 171 172


	if (!acpi_bus_get_device(handle, &device) && device)
		goto end;

	status = acpi_get_parent(handle, &phandle);
	if (ACPI_FAILURE(status)) {
173
		ACPI_EXCEPTION((AE_INFO, status, "Cannot find acpi parent"));
174
		return -EINVAL;
L
Linus Torvalds 已提交
175 176 177
	}

	/* Get the parent device */
178 179 180
	result = acpi_bus_get_device(phandle, &pdevice);
	if (result) {
		printk(KERN_WARNING PREFIX "Cannot get acpi bus device");
181
		return -EINVAL;
L
Linus Torvalds 已提交
182 183 184 185 186 187
	}

	/*
	 * Now add the notified device.  This creates the acpi_device
	 * and invokes .add function
	 */
188 189 190
	result = acpi_bus_add(&device, pdevice, handle, ACPI_BUS_TYPE_DEVICE);
	if (result) {
		printk(KERN_WARNING PREFIX "Cannot add acpi bus");
191
		return -EINVAL;
L
Linus Torvalds 已提交
192 193
	}

L
Len Brown 已提交
194
      end:
L
Linus Torvalds 已提交
195 196
	*mem_device = acpi_driver_data(device);
	if (!(*mem_device)) {
L
Len Brown 已提交
197
		printk(KERN_ERR "\n driver data not found");
198
		return -ENODEV;
L
Linus Torvalds 已提交
199 200
	}

201
	return 0;
L
Linus Torvalds 已提交
202 203
}

L
Len Brown 已提交
204
static int acpi_memory_check_device(struct acpi_memory_device *mem_device)
L
Linus Torvalds 已提交
205
{
206
	unsigned long long current_status;
L
Linus Torvalds 已提交
207 208

	/* Get device present/absent information from the _STA */
209
	if (ACPI_FAILURE(acpi_evaluate_integer(mem_device->device->handle, "_STA",
L
Len Brown 已提交
210
					       NULL, &current_status)))
211
		return -ENODEV;
L
Linus Torvalds 已提交
212 213 214 215
	/*
	 * Check for device status. Device should be
	 * present/enabled/functioning.
	 */
216 217 218
	if (!((current_status & ACPI_STA_DEVICE_PRESENT)
	      && (current_status & ACPI_STA_DEVICE_ENABLED)
	      && (current_status & ACPI_STA_DEVICE_FUNCTIONING)))
219
		return -ENODEV;
L
Linus Torvalds 已提交
220

221
	return 0;
L
Linus Torvalds 已提交
222 223
}

L
Len Brown 已提交
224
static int acpi_memory_enable_device(struct acpi_memory_device *mem_device)
L
Linus Torvalds 已提交
225
{
226 227
	int result, num_enabled = 0;
	struct acpi_memory_info *info;
228
	int node;
L
Linus Torvalds 已提交
229 230 231 232 233


	/* Get the range from the _CRS */
	result = acpi_memory_get_device_resources(mem_device);
	if (result) {
234
		printk(KERN_ERR PREFIX "get_device_resources failed\n");
L
Linus Torvalds 已提交
235 236 237 238
		mem_device->state = MEMORY_INVALID_STATE;
		return result;
	}

239
	node = acpi_get_node(mem_device->device->handle);
L
Linus Torvalds 已提交
240 241 242
	/*
	 * Tell the VM there is more memory here...
	 * Note: Assume that this function returns zero on success
243 244
	 * We don't have memory-hot-add rollback function,now.
	 * (i.e. memory-hot-remove function)
L
Linus Torvalds 已提交
245
	 */
246
	list_for_each_entry(info, &mem_device->res_list, list) {
247
		if (info->enabled) { /* just sanity check...*/
248 249 250
			num_enabled++;
			continue;
		}
251 252 253 254 255 256
		/*
		 * If the memory block size is zero, please ignore it.
		 * Don't try to do the following memory hotplug flowchart.
		 */
		if (!info->length)
			continue;
257 258 259
		if (node < 0)
			node = memory_add_physaddr_to_nid(info->start_addr);

260
		result = add_memory(node, info->start_addr, info->length);
261 262 263 264 265 266 267 268

		/*
		 * 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.
		 */
		if (result && result != -EEXIST) {
			info->failed = 1;
269
			continue;
270 271 272 273 274 275 276 277
		}

		if (!result)
			info->enabled = 1;
		/*
		 * Add num_enable even if add_memory() returns -EEXIST, so the
		 * device is bound to this driver.
		 */
278 279 280
		num_enabled++;
	}
	if (!num_enabled) {
281
		printk(KERN_ERR PREFIX "add_memory failed\n");
L
Linus Torvalds 已提交
282
		mem_device->state = MEMORY_INVALID_STATE;
283
		return -EINVAL;
L
Linus Torvalds 已提交
284
	}
285 286 287 288 289 290 291 292 293
	/*
	 * 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 已提交
294 295
}

296
static int acpi_memory_remove_memory(struct acpi_memory_device *mem_device)
L
Linus Torvalds 已提交
297
{
298
	int result = 0;
299
	struct acpi_memory_info *info, *n;
L
Linus Torvalds 已提交
300

301
	list_for_each_entry_safe(info, n, &mem_device->res_list, list) {
302 303 304 305 306 307 308 309 310 311 312 313 314 315
		if (info->failed)
			/* The kernel does not use this memory block */
			continue;

		if (!info->enabled)
			/*
			 * The kernel uses this memory block, but it may be not
			 * managed by us.
			 */
			return -EBUSY;

		result = remove_memory(info->start_addr, info->length);
		if (result)
			return result;
316 317

		list_del(&info->list);
318
		kfree(info);
L
Linus Torvalds 已提交
319 320
	}

321
	return result;
322 323
}

L
Len Brown 已提交
324
static void acpi_memory_device_notify(acpi_handle handle, u32 event, void *data)
L
Linus Torvalds 已提交
325 326 327
{
	struct acpi_memory_device *mem_device;
	struct acpi_device *device;
328
	struct acpi_eject_event *ej_event = NULL;
329
	u32 ost_code = ACPI_OST_SC_NON_SPECIFIC_FAILURE; /* default */
L
Linus Torvalds 已提交
330 331 332 333

	switch (event) {
	case ACPI_NOTIFY_BUS_CHECK:
		ACPI_DEBUG_PRINT((ACPI_DB_INFO,
L
Len Brown 已提交
334
				  "\nReceived BUS CHECK notification for device\n"));
L
Linus Torvalds 已提交
335 336 337 338
		/* Fall Through */
	case ACPI_NOTIFY_DEVICE_CHECK:
		if (event == ACPI_NOTIFY_DEVICE_CHECK)
			ACPI_DEBUG_PRINT((ACPI_DB_INFO,
L
Len Brown 已提交
339
					  "\nReceived DEVICE CHECK notification for device\n"));
L
Linus Torvalds 已提交
340
		if (acpi_memory_get_device(handle, &mem_device)) {
341
			printk(KERN_ERR PREFIX "Cannot find driver data\n");
342
			break;
L
Linus Torvalds 已提交
343 344
		}

345 346 347 348 349 350
		if (acpi_memory_check_device(mem_device))
			break;

		if (acpi_memory_enable_device(mem_device)) {
			printk(KERN_ERR PREFIX "Cannot enable memory device\n");
			break;
L
Linus Torvalds 已提交
351
		}
352 353

		ost_code = ACPI_OST_SC_SUCCESS;
L
Linus Torvalds 已提交
354
		break;
355

L
Linus Torvalds 已提交
356 357
	case ACPI_NOTIFY_EJECT_REQUEST:
		ACPI_DEBUG_PRINT((ACPI_DB_INFO,
L
Len Brown 已提交
358
				  "\nReceived EJECT REQUEST notification for device\n"));
L
Linus Torvalds 已提交
359 360

		if (acpi_bus_get_device(handle, &device)) {
361
			printk(KERN_ERR PREFIX "Device doesn't exist\n");
L
Linus Torvalds 已提交
362 363 364 365
			break;
		}
		mem_device = acpi_driver_data(device);
		if (!mem_device) {
366
			printk(KERN_ERR PREFIX "Driver Data is NULL\n");
L
Linus Torvalds 已提交
367 368 369
			break;
		}

370 371 372
		ej_event = kmalloc(sizeof(*ej_event), GFP_KERNEL);
		if (!ej_event) {
			pr_err(PREFIX "No memory, dropping EJECT\n");
373 374 375
			break;
		}

376 377 378 379
		ej_event->handle = handle;
		ej_event->event = ACPI_NOTIFY_EJECT_REQUEST;
		acpi_os_hotplug_execute(acpi_bus_hot_remove_device,
					(void *)ej_event);
380

381
		/* eject is performed asynchronously */
382
		return;
L
Linus Torvalds 已提交
383 384
	default:
		ACPI_DEBUG_PRINT((ACPI_DB_INFO,
L
Len Brown 已提交
385
				  "Unsupported event [0x%x]\n", event));
386 387 388

		/* non-hotplug event; possibly handled by other handler */
		return;
L
Linus Torvalds 已提交
389 390
	}

391 392
	/* Inform firmware that the hotplug operation has completed */
	(void) acpi_evaluate_hotplug_ost(handle, event, ost_code, NULL);
393
	return;
L
Linus Torvalds 已提交
394 395
}

396 397 398 399 400 401 402 403 404
static void acpi_memory_device_free(struct acpi_memory_device *mem_device)
{
	if (!mem_device)
		return;

	acpi_memory_free_device_resources(mem_device);
	kfree(mem_device);
}

L
Len Brown 已提交
405
static int acpi_memory_device_add(struct acpi_device *device)
L
Linus Torvalds 已提交
406 407 408 409 410 411
{
	int result;
	struct acpi_memory_device *mem_device = NULL;


	if (!device)
412
		return -EINVAL;
L
Linus Torvalds 已提交
413

414
	mem_device = kzalloc(sizeof(struct acpi_memory_device), GFP_KERNEL);
L
Linus Torvalds 已提交
415
	if (!mem_device)
416
		return -ENOMEM;
L
Linus Torvalds 已提交
417

418
	INIT_LIST_HEAD(&mem_device->res_list);
419
	mem_device->device = device;
L
Linus Torvalds 已提交
420 421
	sprintf(acpi_device_name(device), "%s", ACPI_MEMORY_DEVICE_NAME);
	sprintf(acpi_device_class(device), "%s", ACPI_MEMORY_DEVICE_CLASS);
422
	device->driver_data = mem_device;
L
Linus Torvalds 已提交
423 424 425 426 427

	/* Get the range from the _CRS */
	result = acpi_memory_get_device_resources(mem_device);
	if (result) {
		kfree(mem_device);
428
		return result;
L
Linus Torvalds 已提交
429 430 431 432 433
	}

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

434
	printk(KERN_DEBUG "%s \n", acpi_device_name(device));
L
Linus Torvalds 已提交
435

436 437 438 439 440 441 442 443 444
	/*
	 * Early boot code has recognized memory area by EFI/E820.
	 * If DSDT shows these memory devices on boot, hotplug is not necessary
	 * for them. So, it just returns until completion of this driver's
	 * start up.
	 */
	if (!acpi_hotmem_initialized)
		return 0;

445 446 447
	if (!acpi_memory_check_device(mem_device)) {
		/* call add_memory func */
		result = acpi_memory_enable_device(mem_device);
448
		if (result) {
449 450
			printk(KERN_ERR PREFIX
				"Error in acpi_memory_enable_device\n");
451 452
			acpi_memory_device_free(mem_device);
		}
453
	}
L
Len Brown 已提交
454
	return result;
455 456
}

457 458 459
static int acpi_memory_device_remove(struct acpi_device *device, int type)
{
	struct acpi_memory_device *mem_device = NULL;
460
	int result;
461 462 463 464 465

	if (!device || !acpi_driver_data(device))
		return -EINVAL;

	mem_device = acpi_driver_data(device);
466 467 468 469 470

	result = acpi_memory_remove_memory(mem_device);
	if (result)
		return result;

471
	acpi_memory_device_free(mem_device);
472 473 474 475

	return 0;
}

L
Linus Torvalds 已提交
476 477 478
/*
 * Helper function to check for memory device
 */
L
Len Brown 已提交
479
static acpi_status is_memory_device(acpi_handle handle)
L
Linus Torvalds 已提交
480 481 482 483 484
{
	char *hardware_id;
	acpi_status status;
	struct acpi_device_info *info;

485
	status = acpi_get_object_info(handle, &info);
L
Linus Torvalds 已提交
486
	if (ACPI_FAILURE(status))
487
		return status;
L
Linus Torvalds 已提交
488 489

	if (!(info->valid & ACPI_VALID_HID)) {
490
		kfree(info);
491
		return AE_ERROR;
L
Linus Torvalds 已提交
492 493
	}

494
	hardware_id = info->hardware_id.string;
L
Linus Torvalds 已提交
495
	if ((hardware_id == NULL) ||
L
Len Brown 已提交
496
	    (strcmp(hardware_id, ACPI_MEMORY_DEVICE_HID)))
L
Linus Torvalds 已提交
497 498
		status = AE_ERROR;

499
	kfree(info);
500
	return status;
L
Linus Torvalds 已提交
501 502 503
}

static acpi_status
L
Len Brown 已提交
504 505
acpi_memory_register_notify_handler(acpi_handle handle,
				    u32 level, void *ctxt, void **retv)
L
Linus Torvalds 已提交
506 507 508 509 510
{
	acpi_status status;


	status = is_memory_device(handle);
511
	if (ACPI_FAILURE(status))
512
		return AE_OK;	/* continue */
L
Linus Torvalds 已提交
513 514

	status = acpi_install_notify_handler(handle, ACPI_SYSTEM_NOTIFY,
L
Len Brown 已提交
515
					     acpi_memory_device_notify, NULL);
516
	/* continue */
517
	return AE_OK;
L
Linus Torvalds 已提交
518 519 520
}

static acpi_status
L
Len Brown 已提交
521 522
acpi_memory_deregister_notify_handler(acpi_handle handle,
				      u32 level, void *ctxt, void **retv)
L
Linus Torvalds 已提交
523 524 525 526 527
{
	acpi_status status;


	status = is_memory_device(handle);
528
	if (ACPI_FAILURE(status))
529
		return AE_OK;	/* continue */
L
Linus Torvalds 已提交
530 531

	status = acpi_remove_notify_handler(handle,
L
Len Brown 已提交
532 533
					    ACPI_SYSTEM_NOTIFY,
					    acpi_memory_device_notify);
L
Linus Torvalds 已提交
534

535
	return AE_OK;	/* continue */
L
Linus Torvalds 已提交
536 537
}

L
Len Brown 已提交
538
static int __init acpi_memory_device_init(void)
L
Linus Torvalds 已提交
539 540 541 542 543 544 545 546
{
	int result;
	acpi_status status;


	result = acpi_bus_register_driver(&acpi_memory_device_driver);

	if (result < 0)
547
		return -ENODEV;
L
Linus Torvalds 已提交
548 549

	status = acpi_walk_namespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT,
L
Len Brown 已提交
550
				     ACPI_UINT32_MAX,
551
				     acpi_memory_register_notify_handler, NULL,
L
Len Brown 已提交
552
				     NULL, NULL);
L
Linus Torvalds 已提交
553

L
Len Brown 已提交
554
	if (ACPI_FAILURE(status)) {
555
		ACPI_EXCEPTION((AE_INFO, status, "walk_namespace failed"));
L
Linus Torvalds 已提交
556
		acpi_bus_unregister_driver(&acpi_memory_device_driver);
557
		return -ENODEV;
L
Len Brown 已提交
558
	}
L
Linus Torvalds 已提交
559

560
	acpi_hotmem_initialized = 1;
561
	return 0;
L
Linus Torvalds 已提交
562 563
}

L
Len Brown 已提交
564
static void __exit acpi_memory_device_exit(void)
L
Linus Torvalds 已提交
565 566 567 568 569 570 571 572 573
{
	acpi_status status;


	/*
	 * Adding this to un-install notification handlers for all the device
	 * handles.
	 */
	status = acpi_walk_namespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT,
L
Len Brown 已提交
574
				     ACPI_UINT32_MAX,
575
				     acpi_memory_deregister_notify_handler, NULL,
L
Len Brown 已提交
576
				     NULL, NULL);
L
Linus Torvalds 已提交
577

L
Len Brown 已提交
578
	if (ACPI_FAILURE(status))
579
		ACPI_EXCEPTION((AE_INFO, status, "walk_namespace failed"));
L
Linus Torvalds 已提交
580 581 582

	acpi_bus_unregister_driver(&acpi_memory_device_driver);

583
	return;
L
Linus Torvalds 已提交
584 585 586 587
}

module_init(acpi_memory_device_init);
module_exit(acpi_memory_device_exit);