Commit | Line | Data |
---|---|---|
1da177e4 LT |
1 | /* |
2 | * acpi_container.c - ACPI Generic Container Driver | |
3 | * ($Revision: ) | |
4 | * | |
5 | * Copyright (C) 2004 Anil S Keshavamurthy (anil.s.keshavamurthy@intel.com) | |
6 | * Copyright (C) 2004 Keiichiro Tokunaga (tokunaga.keiich@jp.fujitsu.com) | |
7 | * Copyright (C) 2004 Motoyuki Ito (motoyuki@soft.fujitsu.com) | |
8 | * Copyright (C) 2004 Intel Corp. | |
9 | * Copyright (C) 2004 FUJITSU LIMITED | |
10 | * | |
11 | * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
12 | * | |
13 | * This program is free software; you can redistribute it and/or modify | |
14 | * it under the terms of the GNU General Public License as published by | |
15 | * the Free Software Foundation; either version 2 of the License, or (at | |
16 | * your option) any later version. | |
17 | * | |
18 | * This program is distributed in the hope that it will be useful, but | |
19 | * WITHOUT ANY WARRANTY; without even the implied warranty of | |
20 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
21 | * General Public License for more details. | |
22 | * | |
23 | * You should have received a copy of the GNU General Public License along | |
24 | * with this program; if not, write to the Free Software Foundation, Inc., | |
25 | * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. | |
26 | * | |
27 | * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
28 | */ | |
29 | #include <linux/kernel.h> | |
30 | #include <linux/module.h> | |
31 | #include <linux/init.h> | |
32 | #include <linux/types.h> | |
33 | #include <linux/acpi.h> | |
34 | #include <acpi/acpi_bus.h> | |
35 | #include <acpi/acpi_drivers.h> | |
36 | #include <acpi/container.h> | |
37 | ||
38 | #define ACPI_CONTAINER_DRIVER_NAME "ACPI container driver" | |
39 | #define ACPI_CONTAINER_DEVICE_NAME "ACPI container device" | |
40 | #define ACPI_CONTAINER_CLASS "container" | |
41 | ||
42 | #define INSTALL_NOTIFY_HANDLER 1 | |
43 | #define UNINSTALL_NOTIFY_HANDLER 2 | |
44 | ||
45 | #define ACPI_CONTAINER_COMPONENT 0x01000000 | |
46 | #define _COMPONENT ACPI_CONTAINER_COMPONENT | |
47 | ACPI_MODULE_NAME ("acpi_container") | |
48 | ||
49 | MODULE_AUTHOR("Anil S Keshavamurthy"); | |
50 | MODULE_DESCRIPTION(ACPI_CONTAINER_DRIVER_NAME); | |
51 | MODULE_LICENSE("GPL"); | |
52 | ||
53 | #define ACPI_STA_PRESENT (0x00000001) | |
54 | ||
55 | static int acpi_container_add(struct acpi_device *device); | |
56 | static int acpi_container_remove(struct acpi_device *device, int type); | |
57 | ||
58 | static struct acpi_driver acpi_container_driver = { | |
59 | .name = ACPI_CONTAINER_DRIVER_NAME, | |
60 | .class = ACPI_CONTAINER_CLASS, | |
61 | .ids = "ACPI0004,PNP0A05,PNP0A06", | |
62 | .ops = { | |
63 | .add = acpi_container_add, | |
64 | .remove = acpi_container_remove, | |
65 | }, | |
66 | }; | |
67 | ||
68 | ||
69 | /*******************************************************************/ | |
70 | ||
71 | static int | |
72 | is_device_present(acpi_handle handle) | |
73 | { | |
74 | acpi_handle temp; | |
75 | acpi_status status; | |
76 | unsigned long sta; | |
77 | ||
78 | ACPI_FUNCTION_TRACE("is_device_present"); | |
79 | ||
80 | status = acpi_get_handle(handle, "_STA", &temp); | |
81 | if (ACPI_FAILURE(status)) | |
82 | return_VALUE(1); /* _STA not found, assmue device present */ | |
83 | ||
84 | status = acpi_evaluate_integer(handle, "_STA", NULL, &sta); | |
85 | if (ACPI_FAILURE(status)) | |
86 | return_VALUE(0); /* Firmware error */ | |
87 | ||
88 | return_VALUE((sta & ACPI_STA_PRESENT) == ACPI_STA_PRESENT); | |
89 | } | |
90 | ||
91 | /*******************************************************************/ | |
92 | static int | |
93 | acpi_container_add(struct acpi_device *device) | |
94 | { | |
95 | struct acpi_container *container; | |
96 | ||
97 | ACPI_FUNCTION_TRACE("acpi_container_add"); | |
98 | ||
99 | if (!device) { | |
100 | ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "device is NULL\n")); | |
101 | return_VALUE(-EINVAL); | |
102 | } | |
103 | ||
104 | container = kmalloc(sizeof(struct acpi_container), GFP_KERNEL); | |
105 | if(!container) | |
106 | return_VALUE(-ENOMEM); | |
107 | ||
108 | memset(container, 0, sizeof(struct acpi_container)); | |
109 | container->handle = device->handle; | |
110 | strcpy(acpi_device_name(device), ACPI_CONTAINER_DEVICE_NAME); | |
111 | strcpy(acpi_device_class(device), ACPI_CONTAINER_CLASS); | |
112 | acpi_driver_data(device) = container; | |
113 | ||
114 | ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Device <%s> bid <%s>\n", \ | |
115 | acpi_device_name(device), acpi_device_bid(device))); | |
116 | ||
117 | ||
118 | return_VALUE(0); | |
119 | } | |
120 | ||
121 | static int | |
122 | acpi_container_remove(struct acpi_device *device, int type) | |
123 | { | |
124 | acpi_status status = AE_OK; | |
125 | struct acpi_container *pc = NULL; | |
126 | pc = (struct acpi_container*) acpi_driver_data(device); | |
127 | ||
128 | if (pc) | |
129 | kfree(pc); | |
130 | ||
131 | return status; | |
132 | } | |
133 | ||
134 | ||
135 | static int | |
136 | container_device_add(struct acpi_device **device, acpi_handle handle) | |
137 | { | |
138 | acpi_handle phandle; | |
139 | struct acpi_device *pdev; | |
140 | int result; | |
141 | ||
142 | ACPI_FUNCTION_TRACE("container_device_add"); | |
143 | ||
144 | if (acpi_get_parent(handle, &phandle)) { | |
145 | return_VALUE(-ENODEV); | |
146 | } | |
147 | ||
148 | if (acpi_bus_get_device(phandle, &pdev)) { | |
149 | return_VALUE(-ENODEV); | |
150 | } | |
151 | ||
152 | if (acpi_bus_add(device, pdev, handle, ACPI_BUS_TYPE_DEVICE)) { | |
153 | return_VALUE(-ENODEV); | |
154 | } | |
155 | ||
156 | result = acpi_bus_scan(*device); | |
157 | ||
158 | return_VALUE(result); | |
159 | } | |
160 | ||
161 | static void | |
162 | container_notify_cb(acpi_handle handle, u32 type, void *context) | |
163 | { | |
164 | struct acpi_device *device = NULL; | |
165 | int result; | |
166 | int present; | |
167 | acpi_status status; | |
168 | ||
169 | ACPI_FUNCTION_TRACE("container_notify_cb"); | |
170 | ||
171 | present = is_device_present(handle); | |
172 | ||
173 | switch (type) { | |
174 | case ACPI_NOTIFY_BUS_CHECK: | |
175 | /* Fall through */ | |
176 | case ACPI_NOTIFY_DEVICE_CHECK: | |
177 | printk("Container driver received %s event\n", | |
178 | (type == ACPI_NOTIFY_BUS_CHECK)? | |
179 | "ACPI_NOTIFY_BUS_CHECK":"ACPI_NOTIFY_DEVICE_CHECK"); | |
180 | status = acpi_bus_get_device(handle, &device); | |
181 | if (present) { | |
182 | if (ACPI_FAILURE(status) || !device) { | |
183 | result = container_device_add(&device, handle); | |
184 | if (!result) | |
185 | kobject_hotplug(&device->kobj, | |
186 | KOBJ_ONLINE); | |
187 | else | |
188 | printk("Failed to add container\n"); | |
189 | } | |
190 | } else { | |
191 | if (ACPI_SUCCESS(status)) { | |
192 | /* device exist and this is a remove request */ | |
193 | kobject_hotplug(&device->kobj, KOBJ_OFFLINE); | |
194 | } | |
195 | } | |
196 | break; | |
197 | case ACPI_NOTIFY_EJECT_REQUEST: | |
198 | if (!acpi_bus_get_device(handle, &device) && device) { | |
199 | kobject_hotplug(&device->kobj, KOBJ_OFFLINE); | |
200 | } | |
201 | break; | |
202 | default: | |
203 | break; | |
204 | } | |
205 | return_VOID; | |
206 | } | |
207 | ||
208 | static acpi_status | |
209 | container_walk_namespace_cb(acpi_handle handle, | |
210 | u32 lvl, | |
211 | void *context, | |
212 | void **rv) | |
213 | { | |
214 | char *hid = NULL; | |
215 | struct acpi_buffer buffer = {ACPI_ALLOCATE_BUFFER, NULL}; | |
216 | struct acpi_device_info *info; | |
217 | acpi_status status; | |
218 | int *action = context; | |
219 | ||
220 | ACPI_FUNCTION_TRACE("container_walk_namespace_cb"); | |
221 | ||
222 | status = acpi_get_object_info(handle, &buffer); | |
223 | if (ACPI_FAILURE(status) || !buffer.pointer) { | |
224 | return_ACPI_STATUS(AE_OK); | |
225 | } | |
226 | ||
227 | info = buffer.pointer; | |
228 | if (info->valid & ACPI_VALID_HID) | |
229 | hid = info->hardware_id.value; | |
230 | ||
231 | if (hid == NULL) { | |
232 | goto end; | |
233 | } | |
234 | ||
235 | if (strcmp(hid, "ACPI0004") && strcmp(hid, "PNP0A05") && | |
236 | strcmp(hid, "PNP0A06")) { | |
237 | goto end; | |
238 | } | |
239 | ||
240 | switch(*action) { | |
241 | case INSTALL_NOTIFY_HANDLER: | |
242 | acpi_install_notify_handler(handle, | |
243 | ACPI_SYSTEM_NOTIFY, | |
244 | container_notify_cb, | |
245 | NULL); | |
246 | break; | |
247 | case UNINSTALL_NOTIFY_HANDLER: | |
248 | acpi_remove_notify_handler(handle, | |
249 | ACPI_SYSTEM_NOTIFY, | |
250 | container_notify_cb); | |
251 | break; | |
252 | default: | |
253 | break; | |
254 | } | |
255 | ||
256 | end: | |
257 | acpi_os_free(buffer.pointer); | |
258 | ||
259 | return_ACPI_STATUS(AE_OK); | |
260 | } | |
261 | ||
262 | ||
263 | static int __init | |
264 | acpi_container_init(void) | |
265 | { | |
266 | int result = 0; | |
267 | int action = INSTALL_NOTIFY_HANDLER; | |
268 | ||
269 | result = acpi_bus_register_driver(&acpi_container_driver); | |
270 | if (result < 0) { | |
271 | return(result); | |
272 | } | |
273 | ||
274 | /* register notify handler to every container device */ | |
275 | acpi_walk_namespace(ACPI_TYPE_DEVICE, | |
276 | ACPI_ROOT_OBJECT, | |
277 | ACPI_UINT32_MAX, | |
278 | container_walk_namespace_cb, | |
279 | &action, NULL); | |
280 | ||
281 | return(0); | |
282 | } | |
283 | ||
284 | static void __exit | |
285 | acpi_container_exit(void) | |
286 | { | |
287 | int action = UNINSTALL_NOTIFY_HANDLER; | |
288 | ||
289 | ACPI_FUNCTION_TRACE("acpi_container_exit"); | |
290 | ||
291 | acpi_walk_namespace(ACPI_TYPE_DEVICE, | |
292 | ACPI_ROOT_OBJECT, | |
293 | ACPI_UINT32_MAX, | |
294 | container_walk_namespace_cb, | |
295 | &action, NULL); | |
296 | ||
297 | acpi_bus_unregister_driver(&acpi_container_driver); | |
298 | ||
299 | return_VOID; | |
300 | } | |
301 | ||
302 | module_init(acpi_container_init); | |
303 | module_exit(acpi_container_exit); |