acpi_memhotplug.c 14.5 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 33 34 35 36 37 38 39 40
 * 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>
#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

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

44 45
ACPI_MODULE_NAME("acpi_memhotplug");
MODULE_AUTHOR("Naveen B S <naveen.b.s@intel.com>");
46
MODULE_DESCRIPTION("Hotplug Mem Driver");
L
Linus Torvalds 已提交
47 48 49 50 51 52 53
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 已提交
54 55
static int acpi_memory_device_add(struct acpi_device *device);
static int acpi_memory_device_remove(struct acpi_device *device, int type);
56
static int acpi_memory_device_start(struct acpi_device *device);
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
	.ops = {
		.add = acpi_memory_device_add,
		.remove = acpi_memory_device_remove,
71
		.start = acpi_memory_device_start,
L
Len Brown 已提交
72
		},
L
Linus Torvalds 已提交
73 74
};

75 76 77 78 79 80 81 82 83
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 已提交
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;
}

L
Linus Torvalds 已提交
129 130 131 132
static int
acpi_memory_get_device_resources(struct acpi_memory_device *mem_device)
{
	acpi_status status;
133
	struct acpi_memory_info *info, *n;
L
Linus Torvalds 已提交
134 135


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

139
	status = acpi_walk_resources(mem_device->device->handle, METHOD_NAME__CRS,
140 141 142 143
				     acpi_memory_get_resource, mem_device);
	if (ACPI_FAILURE(status)) {
		list_for_each_entry_safe(info, n, &mem_device->res_list, list)
			kfree(info);
144
		INIT_LIST_HEAD(&mem_device->res_list);
145
		return -EINVAL;
L
Linus Torvalds 已提交
146 147
	}

148
	return 0;
L
Linus Torvalds 已提交
149 150 151 152
}

static int
acpi_memory_get_device(acpi_handle handle,
L
Len Brown 已提交
153
		       struct acpi_memory_device **mem_device)
L
Linus Torvalds 已提交
154 155 156 157 158
{
	acpi_status status;
	acpi_handle phandle;
	struct acpi_device *device = NULL;
	struct acpi_device *pdevice = NULL;
159
	int result;
L
Linus Torvalds 已提交
160 161 162 163 164 165 166


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

	status = acpi_get_parent(handle, &phandle);
	if (ACPI_FAILURE(status)) {
167
		ACPI_EXCEPTION((AE_INFO, status, "Cannot find acpi parent"));
168
		return -EINVAL;
L
Linus Torvalds 已提交
169 170 171
	}

	/* Get the parent device */
172 173 174
	result = acpi_bus_get_device(phandle, &pdevice);
	if (result) {
		printk(KERN_WARNING PREFIX "Cannot get acpi bus device");
175
		return -EINVAL;
L
Linus Torvalds 已提交
176 177 178 179 180 181
	}

	/*
	 * Now add the notified device.  This creates the acpi_device
	 * and invokes .add function
	 */
182 183 184
	result = acpi_bus_add(&device, pdevice, handle, ACPI_BUS_TYPE_DEVICE);
	if (result) {
		printk(KERN_WARNING PREFIX "Cannot add acpi bus");
185
		return -EINVAL;
L
Linus Torvalds 已提交
186 187
	}

L
Len Brown 已提交
188
      end:
L
Linus Torvalds 已提交
189 190
	*mem_device = acpi_driver_data(device);
	if (!(*mem_device)) {
L
Len Brown 已提交
191
		printk(KERN_ERR "\n driver data not found");
192
		return -ENODEV;
L
Linus Torvalds 已提交
193 194
	}

195
	return 0;
L
Linus Torvalds 已提交
196 197
}

L
Len Brown 已提交
198
static int acpi_memory_check_device(struct acpi_memory_device *mem_device)
L
Linus Torvalds 已提交
199
{
200
	unsigned long long current_status;
L
Linus Torvalds 已提交
201 202

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

215
	return 0;
L
Linus Torvalds 已提交
216 217
}

L
Len Brown 已提交
218
static int acpi_memory_enable_device(struct acpi_memory_device *mem_device)
L
Linus Torvalds 已提交
219
{
220 221
	int result, num_enabled = 0;
	struct acpi_memory_info *info;
222
	int node;
L
Linus Torvalds 已提交
223 224 225 226 227


	/* Get the range from the _CRS */
	result = acpi_memory_get_device_resources(mem_device);
	if (result) {
228
		printk(KERN_ERR PREFIX "get_device_resources failed\n");
L
Linus Torvalds 已提交
229 230 231 232
		mem_device->state = MEMORY_INVALID_STATE;
		return result;
	}

233
	node = acpi_get_node(mem_device->device->handle);
L
Linus Torvalds 已提交
234 235 236
	/*
	 * Tell the VM there is more memory here...
	 * Note: Assume that this function returns zero on success
237 238
	 * We don't have memory-hot-add rollback function,now.
	 * (i.e. memory-hot-remove function)
L
Linus Torvalds 已提交
239
	 */
240
	list_for_each_entry(info, &mem_device->res_list, list) {
241
		if (info->enabled) { /* just sanity check...*/
242 243 244
			num_enabled++;
			continue;
		}
245 246 247 248

		if (node < 0)
			node = memory_add_physaddr_to_nid(info->start_addr);

249
		result = add_memory(node, info->start_addr, info->length);
250 251 252 253 254 255
		if (result)
			continue;
		info->enabled = 1;
		num_enabled++;
	}
	if (!num_enabled) {
256
		printk(KERN_ERR PREFIX "add_memory failed\n");
L
Linus Torvalds 已提交
257
		mem_device->state = MEMORY_INVALID_STATE;
258
		return -EINVAL;
L
Linus Torvalds 已提交
259 260 261 262 263
	}

	return result;
}

L
Len Brown 已提交
264
static int acpi_memory_powerdown_device(struct acpi_memory_device *mem_device)
L
Linus Torvalds 已提交
265 266
{
	acpi_status status;
L
Len Brown 已提交
267
	struct acpi_object_list arg_list;
L
Linus Torvalds 已提交
268
	union acpi_object arg;
269
	unsigned long long current_status;
L
Linus Torvalds 已提交
270 271 272 273 274 275 276


	/* Issue the _EJ0 command */
	arg_list.count = 1;
	arg_list.pointer = &arg;
	arg.type = ACPI_TYPE_INTEGER;
	arg.integer.value = 1;
277
	status = acpi_evaluate_object(mem_device->device->handle,
L
Len Brown 已提交
278
				      "_EJ0", &arg_list, NULL);
L
Linus Torvalds 已提交
279 280
	/* Return on _EJ0 failure */
	if (ACPI_FAILURE(status)) {
281
		ACPI_EXCEPTION((AE_INFO, status, "_EJ0 failed"));
282
		return -ENODEV;
L
Linus Torvalds 已提交
283 284 285
	}

	/* Evalute _STA to check if the device is disabled */
286
	status = acpi_evaluate_integer(mem_device->device->handle, "_STA",
L
Len Brown 已提交
287
				       NULL, &current_status);
L
Linus Torvalds 已提交
288
	if (ACPI_FAILURE(status))
289
		return -ENODEV;
L
Linus Torvalds 已提交
290 291

	/* Check for device status.  Device should be disabled */
292
	if (current_status & ACPI_STA_DEVICE_ENABLED)
293
		return -EINVAL;
L
Linus Torvalds 已提交
294

295
	return 0;
L
Linus Torvalds 已提交
296 297
}

L
Len Brown 已提交
298
static int acpi_memory_disable_device(struct acpi_memory_device *mem_device)
L
Linus Torvalds 已提交
299 300
{
	int result;
301
	struct acpi_memory_info *info, *n;
L
Linus Torvalds 已提交
302 303 304 305 306 307


	/*
	 * Ask the VM to offline this memory range.
	 * Note: Assume that this function returns zero on success
	 */
308 309 310 311 312 313 314
	list_for_each_entry_safe(info, n, &mem_device->res_list, list) {
		if (info->enabled) {
			result = remove_memory(info->start_addr, info->length);
			if (result)
				return result;
		}
		kfree(info);
L
Linus Torvalds 已提交
315 316 317 318 319 320 321 322 323 324 325 326 327 328
	}

	/* Power-off and eject the device */
	result = acpi_memory_powerdown_device(mem_device);
	if (result) {
		/* Set the status of the device to invalid */
		mem_device->state = MEMORY_INVALID_STATE;
		return result;
	}

	mem_device->state = MEMORY_POWER_OFF_STATE;
	return result;
}

L
Len Brown 已提交
329
static void acpi_memory_device_notify(acpi_handle handle, u32 event, void *data)
L
Linus Torvalds 已提交
330 331 332 333 334 335 336 337
{
	struct acpi_memory_device *mem_device;
	struct acpi_device *device;


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

		if (!acpi_memory_check_device(mem_device)) {
			if (acpi_memory_enable_device(mem_device))
351 352
				printk(KERN_ERR PREFIX
					    "Cannot enable memory device\n");
L
Linus Torvalds 已提交
353 354 355 356
		}
		break;
	case ACPI_NOTIFY_EJECT_REQUEST:
		ACPI_DEBUG_PRINT((ACPI_DB_INFO,
L
Len Brown 已提交
357
				  "\nReceived EJECT REQUEST notification for device\n"));
L
Linus Torvalds 已提交
358 359

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

		/*
		 * Currently disabling memory device from kernel mode
		 * TBD: Can also be disabled from user mode scripts
		 * TBD: Can also be disabled by Callback registration
L
Len Brown 已提交
373
		 *      with generic sysfs driver
L
Linus Torvalds 已提交
374 375
		 */
		if (acpi_memory_disable_device(mem_device))
376 377
			printk(KERN_ERR PREFIX
				    "Disable memory device\n");
L
Linus Torvalds 已提交
378 379 380 381 382 383
		/*
		 * TBD: Invoke acpi_bus_remove to cleanup data structures
		 */
		break;
	default:
		ACPI_DEBUG_PRINT((ACPI_DB_INFO,
L
Len Brown 已提交
384
				  "Unsupported event [0x%x]\n", event));
L
Linus Torvalds 已提交
385 386 387
		break;
	}

388
	return;
L
Linus Torvalds 已提交
389 390
}

L
Len Brown 已提交
391
static int acpi_memory_device_add(struct acpi_device *device)
L
Linus Torvalds 已提交
392 393 394 395 396 397
{
	int result;
	struct acpi_memory_device *mem_device = NULL;


	if (!device)
398
		return -EINVAL;
L
Linus Torvalds 已提交
399

400
	mem_device = kzalloc(sizeof(struct acpi_memory_device), GFP_KERNEL);
L
Linus Torvalds 已提交
401
	if (!mem_device)
402
		return -ENOMEM;
L
Linus Torvalds 已提交
403

404
	INIT_LIST_HEAD(&mem_device->res_list);
405
	mem_device->device = device;
L
Linus Torvalds 已提交
406 407
	sprintf(acpi_device_name(device), "%s", ACPI_MEMORY_DEVICE_NAME);
	sprintf(acpi_device_class(device), "%s", ACPI_MEMORY_DEVICE_CLASS);
408
	device->driver_data = mem_device;
L
Linus Torvalds 已提交
409 410 411 412 413

	/* Get the range from the _CRS */
	result = acpi_memory_get_device_resources(mem_device);
	if (result) {
		kfree(mem_device);
414
		return result;
L
Linus Torvalds 已提交
415 416 417 418 419
	}

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

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

422
	return result;
L
Linus Torvalds 已提交
423 424
}

L
Len Brown 已提交
425
static int acpi_memory_device_remove(struct acpi_device *device, int type)
L
Linus Torvalds 已提交
426 427 428 429 430
{
	struct acpi_memory_device *mem_device = NULL;


	if (!device || !acpi_driver_data(device))
431
		return -EINVAL;
L
Linus Torvalds 已提交
432

433
	mem_device = acpi_driver_data(device);
L
Linus Torvalds 已提交
434 435
	kfree(mem_device);

436
	return 0;
L
Linus Torvalds 已提交
437 438
}

439 440 441 442 443
static int acpi_memory_device_start (struct acpi_device *device)
{
	struct acpi_memory_device *mem_device;
	int result = 0;

444 445 446 447 448 449 450 451 452
	/*
	 * 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;

453 454 455 456 457 458
	mem_device = acpi_driver_data(device);

	if (!acpi_memory_check_device(mem_device)) {
		/* call add_memory func */
		result = acpi_memory_enable_device(mem_device);
		if (result)
459 460
			printk(KERN_ERR PREFIX
				"Error in acpi_memory_enable_device\n");
461
	}
L
Len Brown 已提交
462
	return result;
463 464
}

L
Linus Torvalds 已提交
465 466 467
/*
 * Helper function to check for memory device
 */
L
Len Brown 已提交
468
static acpi_status is_memory_device(acpi_handle handle)
L
Linus Torvalds 已提交
469 470 471
{
	char *hardware_id;
	acpi_status status;
L
Len Brown 已提交
472
	struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
L
Linus Torvalds 已提交
473 474 475 476 477
	struct acpi_device_info *info;


	status = acpi_get_object_info(handle, &buffer);
	if (ACPI_FAILURE(status))
478
		return status;
L
Linus Torvalds 已提交
479 480 481

	info = buffer.pointer;
	if (!(info->valid & ACPI_VALID_HID)) {
482
		kfree(buffer.pointer);
483
		return AE_ERROR;
L
Linus Torvalds 已提交
484 485 486 487
	}

	hardware_id = info->hardware_id.value;
	if ((hardware_id == NULL) ||
L
Len Brown 已提交
488
	    (strcmp(hardware_id, ACPI_MEMORY_DEVICE_HID)))
L
Linus Torvalds 已提交
489 490
		status = AE_ERROR;

491
	kfree(buffer.pointer);
492
	return status;
L
Linus Torvalds 已提交
493 494 495
}

static acpi_status
L
Len Brown 已提交
496 497
acpi_memory_register_notify_handler(acpi_handle handle,
				    u32 level, void *ctxt, void **retv)
L
Linus Torvalds 已提交
498 499 500 501 502
{
	acpi_status status;


	status = is_memory_device(handle);
503
	if (ACPI_FAILURE(status))
504
		return AE_OK;	/* continue */
L
Linus Torvalds 已提交
505 506

	status = acpi_install_notify_handler(handle, ACPI_SYSTEM_NOTIFY,
L
Len Brown 已提交
507
					     acpi_memory_device_notify, NULL);
508
	/* continue */
509
	return AE_OK;
L
Linus Torvalds 已提交
510 511 512
}

static acpi_status
L
Len Brown 已提交
513 514
acpi_memory_deregister_notify_handler(acpi_handle handle,
				      u32 level, void *ctxt, void **retv)
L
Linus Torvalds 已提交
515 516 517 518 519
{
	acpi_status status;


	status = is_memory_device(handle);
520
	if (ACPI_FAILURE(status))
521
		return AE_OK;	/* continue */
L
Linus Torvalds 已提交
522 523

	status = acpi_remove_notify_handler(handle,
L
Len Brown 已提交
524 525
					    ACPI_SYSTEM_NOTIFY,
					    acpi_memory_device_notify);
L
Linus Torvalds 已提交
526

527
	return AE_OK;	/* continue */
L
Linus Torvalds 已提交
528 529
}

L
Len Brown 已提交
530
static int __init acpi_memory_device_init(void)
L
Linus Torvalds 已提交
531 532 533 534 535 536 537 538
{
	int result;
	acpi_status status;


	result = acpi_bus_register_driver(&acpi_memory_device_driver);

	if (result < 0)
539
		return -ENODEV;
L
Linus Torvalds 已提交
540 541

	status = acpi_walk_namespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT,
L
Len Brown 已提交
542 543 544
				     ACPI_UINT32_MAX,
				     acpi_memory_register_notify_handler,
				     NULL, NULL);
L
Linus Torvalds 已提交
545

L
Len Brown 已提交
546
	if (ACPI_FAILURE(status)) {
547
		ACPI_EXCEPTION((AE_INFO, status, "walk_namespace failed"));
L
Linus Torvalds 已提交
548
		acpi_bus_unregister_driver(&acpi_memory_device_driver);
549
		return -ENODEV;
L
Len Brown 已提交
550
	}
L
Linus Torvalds 已提交
551

552
	acpi_hotmem_initialized = 1;
553
	return 0;
L
Linus Torvalds 已提交
554 555
}

L
Len Brown 已提交
556
static void __exit acpi_memory_device_exit(void)
L
Linus Torvalds 已提交
557 558 559 560 561 562 563 564 565
{
	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 已提交
566 567 568
				     ACPI_UINT32_MAX,
				     acpi_memory_deregister_notify_handler,
				     NULL, NULL);
L
Linus Torvalds 已提交
569

L
Len Brown 已提交
570
	if (ACPI_FAILURE(status))
571
		ACPI_EXCEPTION((AE_INFO, status, "walk_namespace failed"));
L
Linus Torvalds 已提交
572 573 574

	acpi_bus_unregister_driver(&acpi_memory_device_driver);

575
	return;
L
Linus Torvalds 已提交
576 577 578 579
}

module_init(acpi_memory_device_init);
module_exit(acpi_memory_device_exit);