Commit | Line | Data |
---|---|---|
01b57e73 KCA |
1 | /* |
2 | * bay.c - ACPI removable drive bay driver | |
3 | * | |
4 | * Copyright (C) 2006 Kristen Carlson Accardi <kristen.c.accardi@intel.com> | |
5 | * | |
6 | * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
7 | * | |
8 | * This program is free software; you can redistribute it and/or modify | |
9 | * it under the terms of the GNU General Public License as published by | |
10 | * the Free Software Foundation; either version 2 of the License, or (at | |
11 | * your option) any later version. | |
12 | * | |
13 | * This program is distributed in the hope that it will be useful, but | |
14 | * WITHOUT ANY WARRANTY; without even the implied warranty of | |
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
16 | * General Public License for more details. | |
17 | * | |
18 | * You should have received a copy of the GNU General Public License along | |
19 | * with this program; if not, write to the Free Software Foundation, Inc., | |
20 | * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. | |
21 | * | |
22 | * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
23 | */ | |
24 | #include <linux/kernel.h> | |
25 | #include <linux/module.h> | |
26 | #include <linux/init.h> | |
27 | #include <linux/types.h> | |
28 | #include <linux/notifier.h> | |
29 | #include <acpi/acpi_bus.h> | |
30 | #include <acpi/acpi_drivers.h> | |
01b57e73 KCA |
31 | #include <linux/seq_file.h> |
32 | #include <asm/uaccess.h> | |
33 | ||
34 | #define ACPI_BAY_DRIVER_NAME "ACPI Removable Drive Bay Driver" | |
35 | ||
36 | ACPI_MODULE_NAME("bay") | |
37 | MODULE_AUTHOR("Kristen Carlson Accardi"); | |
38 | MODULE_DESCRIPTION(ACPI_BAY_DRIVER_NAME); | |
39 | MODULE_LICENSE("GPL"); | |
40 | #define ACPI_BAY_CLASS "bay" | |
41 | #define ACPI_BAY_COMPONENT 0x10000000 | |
42 | #define _COMPONENT ACPI_BAY_COMPONENT | |
43 | #define bay_dprintk(h,s) {\ | |
44 | char prefix[80] = {'\0'};\ | |
45 | struct acpi_buffer buffer = {sizeof(prefix), prefix};\ | |
46 | acpi_get_name(h, ACPI_FULL_PATHNAME, &buffer);\ | |
47 | printk(KERN_DEBUG PREFIX "%s: %s\n", prefix, s); } | |
48 | ||
49 | static void bay_notify(acpi_handle handle, u32 event, void *data); | |
50 | static int acpi_bay_add(struct acpi_device *device); | |
51 | static int acpi_bay_remove(struct acpi_device *device, int type); | |
52 | static int acpi_bay_match(struct acpi_device *device, | |
53 | struct acpi_driver *driver); | |
54 | ||
55 | static struct acpi_driver acpi_bay_driver = { | |
56 | .name = ACPI_BAY_DRIVER_NAME, | |
57 | .class = ACPI_BAY_CLASS, | |
58 | .ops = { | |
59 | .add = acpi_bay_add, | |
60 | .remove = acpi_bay_remove, | |
61 | .match = acpi_bay_match, | |
62 | }, | |
63 | }; | |
64 | ||
65 | struct bay { | |
66 | acpi_handle handle; | |
67 | char *name; | |
68 | struct list_head list; | |
01b57e73 KCA |
69 | }; |
70 | ||
71 | LIST_HEAD(drive_bays); | |
72 | ||
01b57e73 KCA |
73 | |
74 | /***************************************************************************** | |
75 | * Drive Bay functions * | |
76 | *****************************************************************************/ | |
77 | /** | |
78 | * is_ejectable - see if a device is ejectable | |
79 | * @handle: acpi handle of the device | |
80 | * | |
81 | * If an acpi object has a _EJ0 method, then it is ejectable | |
82 | */ | |
83 | static int is_ejectable(acpi_handle handle) | |
84 | { | |
85 | acpi_status status; | |
86 | acpi_handle tmp; | |
87 | ||
88 | status = acpi_get_handle(handle, "_EJ0", &tmp); | |
89 | if (ACPI_FAILURE(status)) | |
90 | return 0; | |
91 | return 1; | |
92 | } | |
93 | ||
94 | /** | |
95 | * bay_present - see if the bay device is present | |
96 | * @bay: the drive bay | |
97 | * | |
98 | * execute the _STA method. | |
99 | */ | |
100 | static int bay_present(struct bay *bay) | |
101 | { | |
102 | unsigned long sta; | |
103 | acpi_status status; | |
104 | ||
105 | if (bay) { | |
106 | status = acpi_evaluate_integer(bay->handle, "_STA", NULL, &sta); | |
107 | if (ACPI_SUCCESS(status) && sta) | |
108 | return 1; | |
109 | } | |
110 | return 0; | |
111 | } | |
112 | ||
113 | /** | |
114 | * eject_device - respond to an eject request | |
115 | * @handle - the device to eject | |
116 | * | |
117 | * Call this devices _EJ0 method. | |
118 | */ | |
119 | static void eject_device(acpi_handle handle) | |
120 | { | |
121 | struct acpi_object_list arg_list; | |
122 | union acpi_object arg; | |
123 | ||
124 | bay_dprintk(handle, "Ejecting device"); | |
125 | ||
126 | arg_list.count = 1; | |
127 | arg_list.pointer = &arg; | |
128 | arg.type = ACPI_TYPE_INTEGER; | |
129 | arg.integer.value = 1; | |
130 | ||
131 | if (ACPI_FAILURE(acpi_evaluate_object(handle, "_EJ0", | |
132 | &arg_list, NULL))) | |
133 | pr_debug("Failed to evaluate _EJ0!\n"); | |
134 | } | |
135 | ||
136 | ||
137 | /** | |
138 | * is_ata - see if a device is an ata device | |
139 | * @handle: acpi handle of the device | |
140 | * | |
141 | * If an acpi object has one of 4 ATA ACPI methods defined, | |
142 | * then it is an ATA device | |
143 | */ | |
144 | static int is_ata(acpi_handle handle) | |
145 | { | |
146 | acpi_handle tmp; | |
147 | ||
148 | if ((ACPI_SUCCESS(acpi_get_handle(handle, "_GTF", &tmp))) || | |
149 | (ACPI_SUCCESS(acpi_get_handle(handle, "_GTM", &tmp))) || | |
150 | (ACPI_SUCCESS(acpi_get_handle(handle, "_STM", &tmp))) || | |
151 | (ACPI_SUCCESS(acpi_get_handle(handle, "_SDD", &tmp)))) | |
152 | return 1; | |
153 | ||
154 | return 0; | |
155 | } | |
156 | ||
157 | /** | |
158 | * parent_is_ata(acpi_handle handle) | |
159 | * | |
160 | */ | |
161 | static int parent_is_ata(acpi_handle handle) | |
162 | { | |
163 | acpi_handle phandle; | |
164 | ||
165 | if (acpi_get_parent(handle, &phandle)) | |
166 | return 0; | |
167 | ||
168 | return is_ata(phandle); | |
169 | } | |
170 | ||
171 | /** | |
172 | * is_ejectable_bay - see if a device is an ejectable drive bay | |
173 | * @handle: acpi handle of the device | |
174 | * | |
175 | * If an acpi object is ejectable and has one of the ACPI ATA | |
176 | * methods defined, then we can safely call it an ejectable | |
177 | * drive bay | |
178 | */ | |
179 | static int is_ejectable_bay(acpi_handle handle) | |
180 | { | |
181 | if ((is_ata(handle) || parent_is_ata(handle)) && is_ejectable(handle)) | |
182 | return 1; | |
183 | return 0; | |
184 | } | |
185 | ||
186 | /** | |
187 | * eject_removable_drive - try to eject this drive | |
188 | * @dev : the device structure of the drive | |
189 | * | |
190 | * If a device is a removable drive that requires an _EJ0 method | |
191 | * to be executed in order to safely remove from the system, do | |
192 | * it. ATM - always returns success | |
193 | */ | |
194 | int eject_removable_drive(struct device *dev) | |
195 | { | |
196 | acpi_handle handle = DEVICE_ACPI_HANDLE(dev); | |
197 | ||
198 | if (handle) { | |
199 | bay_dprintk(handle, "Got device handle"); | |
200 | if (is_ejectable_bay(handle)) | |
201 | eject_device(handle); | |
202 | } else { | |
203 | printk("No acpi handle for device\n"); | |
204 | } | |
205 | ||
206 | /* should I return an error code? */ | |
207 | return 0; | |
208 | } | |
209 | EXPORT_SYMBOL_GPL(eject_removable_drive); | |
210 | ||
211 | static int acpi_bay_add(struct acpi_device *device) | |
212 | { | |
213 | bay_dprintk(device->handle, "adding bay device"); | |
214 | strcpy(acpi_device_name(device), "Dockable Bay"); | |
215 | strcpy(acpi_device_class(device), "bay"); | |
216 | return 0; | |
217 | } | |
218 | ||
01b57e73 KCA |
219 | static int acpi_bay_add_fs(struct bay *bay) |
220 | { | |
01b57e73 KCA |
221 | if (!bay) |
222 | return -EINVAL; | |
01b57e73 KCA |
223 | } |
224 | ||
225 | static void acpi_bay_remove_fs(struct bay *bay) | |
226 | { | |
227 | if (!bay) | |
228 | return; | |
01b57e73 KCA |
229 | } |
230 | ||
231 | static int bay_is_dock_device(acpi_handle handle) | |
232 | { | |
233 | acpi_handle parent; | |
234 | ||
235 | acpi_get_parent(handle, &parent); | |
236 | ||
237 | /* if the device or it's parent is dependent on the | |
238 | * dock, then we are a dock device | |
239 | */ | |
240 | return (is_dock_device(handle) || is_dock_device(parent)); | |
241 | } | |
242 | ||
243 | static int bay_add(acpi_handle handle) | |
244 | { | |
245 | acpi_status status; | |
246 | struct bay *new_bay; | |
247 | struct acpi_buffer nbuffer = {ACPI_ALLOCATE_BUFFER, NULL}; | |
248 | acpi_get_name(handle, ACPI_FULL_PATHNAME, &nbuffer); | |
249 | ||
250 | bay_dprintk(handle, "Adding notify handler"); | |
251 | ||
01b57e73 KCA |
252 | /* |
253 | * Initialize bay device structure | |
254 | */ | |
255 | new_bay = kmalloc(GFP_ATOMIC, sizeof(*new_bay)); | |
256 | INIT_LIST_HEAD(&new_bay->list); | |
257 | new_bay->handle = handle; | |
258 | new_bay->name = (char *)nbuffer.pointer; | |
259 | list_add(&new_bay->list, &drive_bays); | |
260 | acpi_bay_add_fs(new_bay); | |
261 | ||
262 | /* register for events on this device */ | |
263 | status = acpi_install_notify_handler(handle, ACPI_SYSTEM_NOTIFY, | |
264 | bay_notify, new_bay); | |
265 | if (ACPI_FAILURE(status)) { | |
266 | printk(KERN_ERR PREFIX "Error installing bay notify handler\n"); | |
267 | } | |
268 | ||
269 | /* if we are on a dock station, we should register for dock | |
270 | * notifications. | |
271 | */ | |
272 | if (bay_is_dock_device(handle)) { | |
273 | bay_dprintk(handle, "Is dependent on dock\n"); | |
274 | register_hotplug_dock_device(handle, bay_notify, new_bay); | |
275 | } | |
276 | printk(KERN_INFO PREFIX "Bay [%s] Added\n", new_bay->name); | |
277 | return 0; | |
278 | } | |
279 | ||
280 | static int acpi_bay_remove(struct acpi_device *device, int type) | |
281 | { | |
282 | /*** FIXME: do something here */ | |
283 | return 0; | |
284 | } | |
285 | ||
286 | static int acpi_bay_match(struct acpi_device *device, | |
287 | struct acpi_driver *driver) | |
288 | { | |
289 | if (!device || !driver) | |
290 | return -EINVAL; | |
291 | ||
292 | if (is_ejectable_bay(device->handle)) { | |
293 | bay_dprintk(device->handle, "matching bay device"); | |
294 | return 0; | |
295 | } | |
296 | ||
297 | return -ENODEV; | |
298 | } | |
299 | ||
300 | /** | |
301 | * bay_create_acpi_device - add new devices to acpi | |
302 | * @handle - handle of the device to add | |
303 | * | |
304 | * This function will create a new acpi_device for the given | |
305 | * handle if one does not exist already. This should cause | |
306 | * acpi to scan for drivers for the given devices, and call | |
307 | * matching driver's add routine. | |
308 | * | |
309 | * Returns a pointer to the acpi_device corresponding to the handle. | |
310 | */ | |
311 | static struct acpi_device * bay_create_acpi_device(acpi_handle handle) | |
312 | { | |
313 | struct acpi_device *device = NULL; | |
314 | struct acpi_device *parent_device; | |
315 | acpi_handle parent; | |
316 | int ret; | |
317 | ||
318 | bay_dprintk(handle, "Trying to get device"); | |
319 | if (acpi_bus_get_device(handle, &device)) { | |
320 | /* | |
321 | * no device created for this object, | |
322 | * so we should create one. | |
323 | */ | |
324 | bay_dprintk(handle, "No device for handle"); | |
325 | acpi_get_parent(handle, &parent); | |
326 | if (acpi_bus_get_device(parent, &parent_device)) | |
327 | parent_device = NULL; | |
328 | ||
329 | ret = acpi_bus_add(&device, parent_device, handle, | |
330 | ACPI_BUS_TYPE_DEVICE); | |
331 | if (ret) { | |
332 | pr_debug("error adding bus, %x\n", | |
333 | -ret); | |
334 | return NULL; | |
335 | } | |
336 | } | |
337 | return device; | |
338 | } | |
339 | ||
340 | /** | |
341 | * bay_notify - act upon an acpi bay notification | |
342 | * @handle: the bay handle | |
343 | * @event: the acpi event | |
344 | * @data: our driver data struct | |
345 | * | |
346 | */ | |
347 | static void bay_notify(acpi_handle handle, u32 event, void *data) | |
348 | { | |
349 | struct acpi_device *dev; | |
01b57e73 KCA |
350 | |
351 | bay_dprintk(handle, "Bay event"); | |
352 | ||
353 | switch(event) { | |
354 | case ACPI_NOTIFY_BUS_CHECK: | |
355 | printk("Bus Check\n"); | |
356 | case ACPI_NOTIFY_DEVICE_CHECK: | |
357 | printk("Device Check\n"); | |
358 | dev = bay_create_acpi_device(handle); | |
359 | if (dev) | |
360 | acpi_bus_generate_event(dev, event, 0); | |
361 | else | |
362 | printk("No device for generating event\n"); | |
363 | /* wouldn't it be a good idea to just rescan SATA | |
364 | * right here? | |
365 | */ | |
366 | break; | |
367 | case ACPI_NOTIFY_EJECT_REQUEST: | |
368 | printk("Eject request\n"); | |
369 | dev = bay_create_acpi_device(handle); | |
370 | if (dev) | |
371 | acpi_bus_generate_event(dev, event, 0); | |
372 | else | |
373 | printk("No device for generating eventn"); | |
374 | ||
375 | /* wouldn't it be a good idea to just call the | |
376 | * eject_device here if we were a SATA device? | |
377 | */ | |
378 | break; | |
379 | default: | |
380 | printk("unknown event %d\n", event); | |
381 | } | |
382 | } | |
383 | ||
384 | static acpi_status | |
385 | find_bay(acpi_handle handle, u32 lvl, void *context, void **rv) | |
386 | { | |
387 | int *count = (int *)context; | |
388 | ||
389 | /* | |
390 | * there could be more than one ejectable bay. | |
391 | * so, just return AE_OK always so that every object | |
392 | * will be checked. | |
393 | */ | |
394 | if (is_ejectable_bay(handle)) { | |
395 | bay_dprintk(handle, "found ejectable bay"); | |
396 | bay_add(handle); | |
397 | (*count)++; | |
398 | } | |
399 | return AE_OK; | |
400 | } | |
401 | ||
402 | static int __init bay_init(void) | |
403 | { | |
404 | int bays = 0; | |
405 | ||
01b57e73 KCA |
406 | INIT_LIST_HEAD(&drive_bays); |
407 | ||
408 | /* look for dockable drive bays */ | |
409 | acpi_walk_namespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT, | |
410 | ACPI_UINT32_MAX, find_bay, &bays, NULL); | |
411 | ||
e9dd85e5 KCA |
412 | if (bays) |
413 | if ((acpi_bus_register_driver(&acpi_bay_driver) < 0)) | |
01b57e73 | 414 | printk(KERN_ERR "Unable to register bay driver\n"); |
01b57e73 KCA |
415 | |
416 | if (!bays) | |
417 | return -ENODEV; | |
418 | ||
419 | return 0; | |
420 | } | |
421 | ||
422 | static void __exit bay_exit(void) | |
423 | { | |
424 | struct bay *bay, *tmp; | |
425 | ||
426 | list_for_each_entry_safe(bay, tmp, &drive_bays, list) { | |
427 | if (is_dock_device(bay->handle)) | |
428 | unregister_hotplug_dock_device(bay->handle); | |
429 | acpi_bay_remove_fs(bay); | |
430 | acpi_remove_notify_handler(bay->handle, ACPI_SYSTEM_NOTIFY, | |
431 | bay_notify); | |
432 | kfree(bay->name); | |
433 | kfree(bay); | |
434 | } | |
435 | ||
01b57e73 KCA |
436 | acpi_bus_unregister_driver(&acpi_bay_driver); |
437 | } | |
438 | ||
439 | postcore_initcall(bay_init); | |
440 | module_exit(bay_exit); | |
441 |