diff options
Diffstat (limited to 'drivers/acpi/acpi_memhotplug.c')
-rw-r--r-- | drivers/acpi/acpi_memhotplug.c | 542 |
1 files changed, 542 insertions, 0 deletions
diff --git a/drivers/acpi/acpi_memhotplug.c b/drivers/acpi/acpi_memhotplug.c new file mode 100644 index 000000000000..77285ffe41c5 --- /dev/null +++ b/drivers/acpi/acpi_memhotplug.c | |||
@@ -0,0 +1,542 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2004 Intel Corporation <naveen.b.s@intel.com> | ||
3 | * | ||
4 | * All rights reserved. | ||
5 | * | ||
6 | * This program is free software; you can redistribute it and/or modify | ||
7 | * it under the terms of the GNU General Public License as published by | ||
8 | * the Free Software Foundation; either version 2 of the License, or (at | ||
9 | * your option) any later version. | ||
10 | * | ||
11 | * This program is distributed in the hope that it will be useful, but | ||
12 | * WITHOUT ANY WARRANTY; without even the implied warranty of | ||
13 | * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or | ||
14 | * NON INFRINGEMENT. See the GNU General Public License for more | ||
15 | * details. | ||
16 | * | ||
17 | * You should have received a copy of the GNU General Public License | ||
18 | * along with this program; if not, write to the Free Software | ||
19 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | ||
20 | * | ||
21 | * | ||
22 | * ACPI based HotPlug driver that supports Memory Hotplug | ||
23 | * This driver fields notifications from firmare for memory add | ||
24 | * and remove operations and alerts the VM of the affected memory | ||
25 | * ranges. | ||
26 | */ | ||
27 | |||
28 | #include <linux/kernel.h> | ||
29 | #include <linux/module.h> | ||
30 | #include <linux/init.h> | ||
31 | #include <linux/types.h> | ||
32 | #include <linux/memory_hotplug.h> | ||
33 | #include <acpi/acpi_drivers.h> | ||
34 | |||
35 | |||
36 | #define ACPI_MEMORY_DEVICE_COMPONENT 0x08000000UL | ||
37 | #define ACPI_MEMORY_DEVICE_CLASS "memory" | ||
38 | #define ACPI_MEMORY_DEVICE_HID "PNP0C80" | ||
39 | #define ACPI_MEMORY_DEVICE_DRIVER_NAME "Hotplug Mem Driver" | ||
40 | #define ACPI_MEMORY_DEVICE_NAME "Hotplug Mem Device" | ||
41 | |||
42 | #define _COMPONENT ACPI_MEMORY_DEVICE_COMPONENT | ||
43 | |||
44 | ACPI_MODULE_NAME ("acpi_memory") | ||
45 | MODULE_AUTHOR("Naveen B S <naveen.b.s@intel.com>"); | ||
46 | MODULE_DESCRIPTION(ACPI_MEMORY_DEVICE_DRIVER_NAME); | ||
47 | MODULE_LICENSE("GPL"); | ||
48 | |||
49 | /* ACPI _STA method values */ | ||
50 | #define ACPI_MEMORY_STA_PRESENT (0x00000001UL) | ||
51 | #define ACPI_MEMORY_STA_ENABLED (0x00000002UL) | ||
52 | #define ACPI_MEMORY_STA_FUNCTIONAL (0x00000008UL) | ||
53 | |||
54 | /* Memory Device States */ | ||
55 | #define MEMORY_INVALID_STATE 0 | ||
56 | #define MEMORY_POWER_ON_STATE 1 | ||
57 | #define MEMORY_POWER_OFF_STATE 2 | ||
58 | |||
59 | static int acpi_memory_device_add (struct acpi_device *device); | ||
60 | static int acpi_memory_device_remove (struct acpi_device *device, int type); | ||
61 | |||
62 | static struct acpi_driver acpi_memory_device_driver = { | ||
63 | .name = ACPI_MEMORY_DEVICE_DRIVER_NAME, | ||
64 | .class = ACPI_MEMORY_DEVICE_CLASS, | ||
65 | .ids = ACPI_MEMORY_DEVICE_HID, | ||
66 | .ops = { | ||
67 | .add = acpi_memory_device_add, | ||
68 | .remove = acpi_memory_device_remove, | ||
69 | }, | ||
70 | }; | ||
71 | |||
72 | struct acpi_memory_device { | ||
73 | acpi_handle handle; | ||
74 | unsigned int state; /* State of the memory device */ | ||
75 | unsigned short cache_attribute; /* memory cache attribute */ | ||
76 | unsigned short read_write_attribute;/* memory read/write attribute */ | ||
77 | u64 start_addr; /* Memory Range start physical addr */ | ||
78 | u64 end_addr; /* Memory Range end physical addr */ | ||
79 | }; | ||
80 | |||
81 | |||
82 | static int | ||
83 | acpi_memory_get_device_resources(struct acpi_memory_device *mem_device) | ||
84 | { | ||
85 | acpi_status status; | ||
86 | struct acpi_buffer buffer = {ACPI_ALLOCATE_BUFFER, NULL}; | ||
87 | struct acpi_resource *resource = NULL; | ||
88 | struct acpi_resource_address64 address64; | ||
89 | |||
90 | ACPI_FUNCTION_TRACE("acpi_memory_get_device_resources"); | ||
91 | |||
92 | /* Get the range from the _CRS */ | ||
93 | status = acpi_get_current_resources(mem_device->handle, &buffer); | ||
94 | if (ACPI_FAILURE(status)) | ||
95 | return_VALUE(-EINVAL); | ||
96 | |||
97 | resource = (struct acpi_resource *) buffer.pointer; | ||
98 | status = acpi_resource_to_address64(resource, &address64); | ||
99 | if (ACPI_SUCCESS(status)) { | ||
100 | if (address64.resource_type == ACPI_MEMORY_RANGE) { | ||
101 | /* Populate the structure */ | ||
102 | mem_device->cache_attribute = | ||
103 | address64.attribute.memory.cache_attribute; | ||
104 | mem_device->read_write_attribute = | ||
105 | address64.attribute.memory.read_write_attribute; | ||
106 | mem_device->start_addr = address64.min_address_range; | ||
107 | mem_device->end_addr = address64.max_address_range; | ||
108 | } | ||
109 | } | ||
110 | |||
111 | acpi_os_free(buffer.pointer); | ||
112 | return_VALUE(0); | ||
113 | } | ||
114 | |||
115 | static int | ||
116 | acpi_memory_get_device(acpi_handle handle, | ||
117 | struct acpi_memory_device **mem_device) | ||
118 | { | ||
119 | acpi_status status; | ||
120 | acpi_handle phandle; | ||
121 | struct acpi_device *device = NULL; | ||
122 | struct acpi_device *pdevice = NULL; | ||
123 | |||
124 | ACPI_FUNCTION_TRACE("acpi_memory_get_device"); | ||
125 | |||
126 | if (!acpi_bus_get_device(handle, &device) && device) | ||
127 | goto end; | ||
128 | |||
129 | status = acpi_get_parent(handle, &phandle); | ||
130 | if (ACPI_FAILURE(status)) { | ||
131 | ACPI_DEBUG_PRINT((ACPI_DB_ERROR, | ||
132 | "Error in acpi_get_parent\n")); | ||
133 | return_VALUE(-EINVAL); | ||
134 | } | ||
135 | |||
136 | /* Get the parent device */ | ||
137 | status = acpi_bus_get_device(phandle, &pdevice); | ||
138 | if (ACPI_FAILURE(status)) { | ||
139 | ACPI_DEBUG_PRINT((ACPI_DB_ERROR, | ||
140 | "Error in acpi_bus_get_device\n")); | ||
141 | return_VALUE(-EINVAL); | ||
142 | } | ||
143 | |||
144 | /* | ||
145 | * Now add the notified device. This creates the acpi_device | ||
146 | * and invokes .add function | ||
147 | */ | ||
148 | status = acpi_bus_add(&device, pdevice, handle, ACPI_BUS_TYPE_DEVICE); | ||
149 | if (ACPI_FAILURE(status)) { | ||
150 | ACPI_DEBUG_PRINT((ACPI_DB_ERROR, | ||
151 | "Error in acpi_bus_add\n")); | ||
152 | return_VALUE(-EINVAL); | ||
153 | } | ||
154 | |||
155 | end: | ||
156 | *mem_device = acpi_driver_data(device); | ||
157 | if (!(*mem_device)) { | ||
158 | printk(KERN_ERR "\n driver data not found" ); | ||
159 | return_VALUE(-ENODEV); | ||
160 | } | ||
161 | |||
162 | return_VALUE(0); | ||
163 | } | ||
164 | |||
165 | static int | ||
166 | acpi_memory_check_device(struct acpi_memory_device *mem_device) | ||
167 | { | ||
168 | unsigned long current_status; | ||
169 | |||
170 | ACPI_FUNCTION_TRACE("acpi_memory_check_device"); | ||
171 | |||
172 | /* Get device present/absent information from the _STA */ | ||
173 | if (ACPI_FAILURE(acpi_evaluate_integer(mem_device->handle, "_STA", | ||
174 | NULL, ¤t_status))) | ||
175 | return_VALUE(-ENODEV); | ||
176 | /* | ||
177 | * Check for device status. Device should be | ||
178 | * present/enabled/functioning. | ||
179 | */ | ||
180 | if (!((current_status & ACPI_MEMORY_STA_PRESENT) | ||
181 | && (current_status & ACPI_MEMORY_STA_ENABLED) | ||
182 | && (current_status & ACPI_MEMORY_STA_FUNCTIONAL))) | ||
183 | return_VALUE(-ENODEV); | ||
184 | |||
185 | return_VALUE(0); | ||
186 | } | ||
187 | |||
188 | static int | ||
189 | acpi_memory_enable_device(struct acpi_memory_device *mem_device) | ||
190 | { | ||
191 | int result; | ||
192 | |||
193 | ACPI_FUNCTION_TRACE("acpi_memory_enable_device"); | ||
194 | |||
195 | /* Get the range from the _CRS */ | ||
196 | result = acpi_memory_get_device_resources(mem_device); | ||
197 | if (result) { | ||
198 | ACPI_DEBUG_PRINT((ACPI_DB_ERROR, | ||
199 | "\nget_device_resources failed\n")); | ||
200 | mem_device->state = MEMORY_INVALID_STATE; | ||
201 | return result; | ||
202 | } | ||
203 | |||
204 | /* | ||
205 | * Tell the VM there is more memory here... | ||
206 | * Note: Assume that this function returns zero on success | ||
207 | */ | ||
208 | result = add_memory(mem_device->start_addr, | ||
209 | (mem_device->end_addr - mem_device->start_addr) + 1, | ||
210 | mem_device->read_write_attribute); | ||
211 | if (result) { | ||
212 | ACPI_DEBUG_PRINT((ACPI_DB_ERROR, | ||
213 | "\nadd_memory failed\n")); | ||
214 | mem_device->state = MEMORY_INVALID_STATE; | ||
215 | return result; | ||
216 | } | ||
217 | |||
218 | return result; | ||
219 | } | ||
220 | |||
221 | static int | ||
222 | acpi_memory_powerdown_device(struct acpi_memory_device *mem_device) | ||
223 | { | ||
224 | acpi_status status; | ||
225 | struct acpi_object_list arg_list; | ||
226 | union acpi_object arg; | ||
227 | unsigned long current_status; | ||
228 | |||
229 | ACPI_FUNCTION_TRACE("acpi_memory_powerdown_device"); | ||
230 | |||
231 | /* Issue the _EJ0 command */ | ||
232 | arg_list.count = 1; | ||
233 | arg_list.pointer = &arg; | ||
234 | arg.type = ACPI_TYPE_INTEGER; | ||
235 | arg.integer.value = 1; | ||
236 | status = acpi_evaluate_object(mem_device->handle, | ||
237 | "_EJ0", &arg_list, NULL); | ||
238 | /* Return on _EJ0 failure */ | ||
239 | if (ACPI_FAILURE(status)) { | ||
240 | ACPI_DEBUG_PRINT((ACPI_DB_ERROR,"_EJ0 failed.\n")); | ||
241 | return_VALUE(-ENODEV); | ||
242 | } | ||
243 | |||
244 | /* Evalute _STA to check if the device is disabled */ | ||
245 | status = acpi_evaluate_integer(mem_device->handle, "_STA", | ||
246 | NULL, ¤t_status); | ||
247 | if (ACPI_FAILURE(status)) | ||
248 | return_VALUE(-ENODEV); | ||
249 | |||
250 | /* Check for device status. Device should be disabled */ | ||
251 | if (current_status & ACPI_MEMORY_STA_ENABLED) | ||
252 | return_VALUE(-EINVAL); | ||
253 | |||
254 | return_VALUE(0); | ||
255 | } | ||
256 | |||
257 | static int | ||
258 | acpi_memory_disable_device(struct acpi_memory_device *mem_device) | ||
259 | { | ||
260 | int result; | ||
261 | u64 start = mem_device->start_addr; | ||
262 | u64 len = mem_device->end_addr - start + 1; | ||
263 | unsigned long attr = mem_device->read_write_attribute; | ||
264 | |||
265 | ACPI_FUNCTION_TRACE("acpi_memory_disable_device"); | ||
266 | |||
267 | /* | ||
268 | * Ask the VM to offline this memory range. | ||
269 | * Note: Assume that this function returns zero on success | ||
270 | */ | ||
271 | result = remove_memory(start, len, attr); | ||
272 | if (result) { | ||
273 | ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Hot-Remove failed.\n")); | ||
274 | return_VALUE(result); | ||
275 | } | ||
276 | |||
277 | /* Power-off and eject the device */ | ||
278 | result = acpi_memory_powerdown_device(mem_device); | ||
279 | if (result) { | ||
280 | ACPI_DEBUG_PRINT((ACPI_DB_ERROR, | ||
281 | "Device Power Down failed.\n")); | ||
282 | /* Set the status of the device to invalid */ | ||
283 | mem_device->state = MEMORY_INVALID_STATE; | ||
284 | return result; | ||
285 | } | ||
286 | |||
287 | mem_device->state = MEMORY_POWER_OFF_STATE; | ||
288 | return result; | ||
289 | } | ||
290 | |||
291 | static void | ||
292 | acpi_memory_device_notify(acpi_handle handle, u32 event, void *data) | ||
293 | { | ||
294 | struct acpi_memory_device *mem_device; | ||
295 | struct acpi_device *device; | ||
296 | |||
297 | ACPI_FUNCTION_TRACE("acpi_memory_device_notify"); | ||
298 | |||
299 | switch (event) { | ||
300 | case ACPI_NOTIFY_BUS_CHECK: | ||
301 | ACPI_DEBUG_PRINT((ACPI_DB_INFO, | ||
302 | "\nReceived BUS CHECK notification for device\n")); | ||
303 | /* Fall Through */ | ||
304 | case ACPI_NOTIFY_DEVICE_CHECK: | ||
305 | if (event == ACPI_NOTIFY_DEVICE_CHECK) | ||
306 | ACPI_DEBUG_PRINT((ACPI_DB_INFO, | ||
307 | "\nReceived DEVICE CHECK notification for device\n")); | ||
308 | if (acpi_memory_get_device(handle, &mem_device)) { | ||
309 | ACPI_DEBUG_PRINT((ACPI_DB_ERROR, | ||
310 | "Error in finding driver data\n")); | ||
311 | return_VOID; | ||
312 | } | ||
313 | |||
314 | if (!acpi_memory_check_device(mem_device)) { | ||
315 | if (acpi_memory_enable_device(mem_device)) | ||
316 | ACPI_DEBUG_PRINT((ACPI_DB_ERROR, | ||
317 | "Error in acpi_memory_enable_device\n")); | ||
318 | } | ||
319 | break; | ||
320 | case ACPI_NOTIFY_EJECT_REQUEST: | ||
321 | ACPI_DEBUG_PRINT((ACPI_DB_INFO, | ||
322 | "\nReceived EJECT REQUEST notification for device\n")); | ||
323 | |||
324 | if (acpi_bus_get_device(handle, &device)) { | ||
325 | ACPI_DEBUG_PRINT((ACPI_DB_ERROR, | ||
326 | "Device doesn't exist\n")); | ||
327 | break; | ||
328 | } | ||
329 | mem_device = acpi_driver_data(device); | ||
330 | if (!mem_device) { | ||
331 | ACPI_DEBUG_PRINT((ACPI_DB_ERROR, | ||
332 | "Driver Data is NULL\n")); | ||
333 | break; | ||
334 | } | ||
335 | |||
336 | /* | ||
337 | * Currently disabling memory device from kernel mode | ||
338 | * TBD: Can also be disabled from user mode scripts | ||
339 | * TBD: Can also be disabled by Callback registration | ||
340 | * with generic sysfs driver | ||
341 | */ | ||
342 | if (acpi_memory_disable_device(mem_device)) | ||
343 | ACPI_DEBUG_PRINT((ACPI_DB_ERROR, | ||
344 | "Error in acpi_memory_disable_device\n")); | ||
345 | /* | ||
346 | * TBD: Invoke acpi_bus_remove to cleanup data structures | ||
347 | */ | ||
348 | break; | ||
349 | default: | ||
350 | ACPI_DEBUG_PRINT((ACPI_DB_INFO, | ||
351 | "Unsupported event [0x%x]\n", event)); | ||
352 | break; | ||
353 | } | ||
354 | |||
355 | return_VOID; | ||
356 | } | ||
357 | |||
358 | static int | ||
359 | acpi_memory_device_add(struct acpi_device *device) | ||
360 | { | ||
361 | int result; | ||
362 | struct acpi_memory_device *mem_device = NULL; | ||
363 | |||
364 | ACPI_FUNCTION_TRACE("acpi_memory_device_add"); | ||
365 | |||
366 | if (!device) | ||
367 | return_VALUE(-EINVAL); | ||
368 | |||
369 | mem_device = kmalloc(sizeof(struct acpi_memory_device), GFP_KERNEL); | ||
370 | if (!mem_device) | ||
371 | return_VALUE(-ENOMEM); | ||
372 | memset(mem_device, 0, sizeof(struct acpi_memory_device)); | ||
373 | |||
374 | mem_device->handle = device->handle; | ||
375 | sprintf(acpi_device_name(device), "%s", ACPI_MEMORY_DEVICE_NAME); | ||
376 | sprintf(acpi_device_class(device), "%s", ACPI_MEMORY_DEVICE_CLASS); | ||
377 | acpi_driver_data(device) = mem_device; | ||
378 | |||
379 | /* Get the range from the _CRS */ | ||
380 | result = acpi_memory_get_device_resources(mem_device); | ||
381 | if (result) { | ||
382 | kfree(mem_device); | ||
383 | return_VALUE(result); | ||
384 | } | ||
385 | |||
386 | /* Set the device state */ | ||
387 | mem_device->state = MEMORY_POWER_ON_STATE; | ||
388 | |||
389 | printk(KERN_INFO "%s \n", acpi_device_name(device)); | ||
390 | |||
391 | return_VALUE(result); | ||
392 | } | ||
393 | |||
394 | static int | ||
395 | acpi_memory_device_remove (struct acpi_device *device, int type) | ||
396 | { | ||
397 | struct acpi_memory_device *mem_device = NULL; | ||
398 | |||
399 | ACPI_FUNCTION_TRACE("acpi_memory_device_remove"); | ||
400 | |||
401 | if (!device || !acpi_driver_data(device)) | ||
402 | return_VALUE(-EINVAL); | ||
403 | |||
404 | mem_device = (struct acpi_memory_device *) acpi_driver_data(device); | ||
405 | kfree(mem_device); | ||
406 | |||
407 | return_VALUE(0); | ||
408 | } | ||
409 | |||
410 | /* | ||
411 | * Helper function to check for memory device | ||
412 | */ | ||
413 | static acpi_status | ||
414 | is_memory_device(acpi_handle handle) | ||
415 | { | ||
416 | char *hardware_id; | ||
417 | acpi_status status; | ||
418 | struct acpi_buffer buffer = {ACPI_ALLOCATE_BUFFER, NULL}; | ||
419 | struct acpi_device_info *info; | ||
420 | |||
421 | ACPI_FUNCTION_TRACE("is_memory_device"); | ||
422 | |||
423 | status = acpi_get_object_info(handle, &buffer); | ||
424 | if (ACPI_FAILURE(status)) | ||
425 | return_ACPI_STATUS(AE_ERROR); | ||
426 | |||
427 | info = buffer.pointer; | ||
428 | if (!(info->valid & ACPI_VALID_HID)) { | ||
429 | acpi_os_free(buffer.pointer); | ||
430 | return_ACPI_STATUS(AE_ERROR); | ||
431 | } | ||
432 | |||
433 | hardware_id = info->hardware_id.value; | ||
434 | if ((hardware_id == NULL) || | ||
435 | (strcmp(hardware_id, ACPI_MEMORY_DEVICE_HID))) | ||
436 | status = AE_ERROR; | ||
437 | |||
438 | acpi_os_free(buffer.pointer); | ||
439 | return_ACPI_STATUS(status); | ||
440 | } | ||
441 | |||
442 | static acpi_status | ||
443 | acpi_memory_register_notify_handler (acpi_handle handle, | ||
444 | u32 level, void *ctxt, void **retv) | ||
445 | { | ||
446 | acpi_status status; | ||
447 | |||
448 | ACPI_FUNCTION_TRACE("acpi_memory_register_notify_handler"); | ||
449 | |||
450 | status = is_memory_device(handle); | ||
451 | if (ACPI_FAILURE(status)) | ||
452 | return_ACPI_STATUS(AE_OK); /* continue */ | ||
453 | |||
454 | status = acpi_install_notify_handler(handle, ACPI_SYSTEM_NOTIFY, | ||
455 | acpi_memory_device_notify, NULL); | ||
456 | if (ACPI_FAILURE(status)) { | ||
457 | ACPI_DEBUG_PRINT((ACPI_DB_ERROR, | ||
458 | "Error installing notify handler\n")); | ||
459 | return_ACPI_STATUS(AE_OK); /* continue */ | ||
460 | } | ||
461 | |||
462 | return_ACPI_STATUS(status); | ||
463 | } | ||
464 | |||
465 | static acpi_status | ||
466 | acpi_memory_deregister_notify_handler (acpi_handle handle, | ||
467 | u32 level, void *ctxt, void **retv) | ||
468 | { | ||
469 | acpi_status status; | ||
470 | |||
471 | ACPI_FUNCTION_TRACE("acpi_memory_deregister_notify_handler"); | ||
472 | |||
473 | status = is_memory_device(handle); | ||
474 | if (ACPI_FAILURE(status)) | ||
475 | return_ACPI_STATUS(AE_OK); /* continue */ | ||
476 | |||
477 | status = acpi_remove_notify_handler(handle, | ||
478 | ACPI_SYSTEM_NOTIFY, acpi_memory_device_notify); | ||
479 | if (ACPI_FAILURE(status)) { | ||
480 | ACPI_DEBUG_PRINT((ACPI_DB_ERROR, | ||
481 | "Error removing notify handler\n")); | ||
482 | return_ACPI_STATUS(AE_OK); /* continue */ | ||
483 | } | ||
484 | |||
485 | return_ACPI_STATUS(status); | ||
486 | } | ||
487 | |||
488 | static int __init | ||
489 | acpi_memory_device_init (void) | ||
490 | { | ||
491 | int result; | ||
492 | acpi_status status; | ||
493 | |||
494 | ACPI_FUNCTION_TRACE("acpi_memory_device_init"); | ||
495 | |||
496 | result = acpi_bus_register_driver(&acpi_memory_device_driver); | ||
497 | |||
498 | if (result < 0) | ||
499 | return_VALUE(-ENODEV); | ||
500 | |||
501 | status = acpi_walk_namespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT, | ||
502 | ACPI_UINT32_MAX, | ||
503 | acpi_memory_register_notify_handler, | ||
504 | NULL, NULL); | ||
505 | |||
506 | if (ACPI_FAILURE (status)) { | ||
507 | ACPI_DEBUG_PRINT ((ACPI_DB_ERROR, "walk_namespace failed\n")); | ||
508 | acpi_bus_unregister_driver(&acpi_memory_device_driver); | ||
509 | return_VALUE(-ENODEV); | ||
510 | } | ||
511 | |||
512 | return_VALUE(0); | ||
513 | } | ||
514 | |||
515 | static void __exit | ||
516 | acpi_memory_device_exit (void) | ||
517 | { | ||
518 | acpi_status status; | ||
519 | |||
520 | ACPI_FUNCTION_TRACE("acpi_memory_device_exit"); | ||
521 | |||
522 | /* | ||
523 | * Adding this to un-install notification handlers for all the device | ||
524 | * handles. | ||
525 | */ | ||
526 | status = acpi_walk_namespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT, | ||
527 | ACPI_UINT32_MAX, | ||
528 | acpi_memory_deregister_notify_handler, | ||
529 | NULL, NULL); | ||
530 | |||
531 | if (ACPI_FAILURE (status)) | ||
532 | ACPI_DEBUG_PRINT ((ACPI_DB_ERROR, "walk_namespace failed\n")); | ||
533 | |||
534 | acpi_bus_unregister_driver(&acpi_memory_device_driver); | ||
535 | |||
536 | return_VOID; | ||
537 | } | ||
538 | |||
539 | module_init(acpi_memory_device_init); | ||
540 | module_exit(acpi_memory_device_exit); | ||
541 | |||
542 | |||