diff options
Diffstat (limited to 'drivers/acpi/hotkey.c')
-rw-r--r-- | drivers/acpi/hotkey.c | 1019 |
1 files changed, 1019 insertions, 0 deletions
diff --git a/drivers/acpi/hotkey.c b/drivers/acpi/hotkey.c new file mode 100644 index 000000000000..babdf762eadb --- /dev/null +++ b/drivers/acpi/hotkey.c | |||
@@ -0,0 +1,1019 @@ | |||
1 | /* | ||
2 | * hotkey.c - ACPI Hotkey Driver ($Revision:$) | ||
3 | * | ||
4 | * Copyright (C) 2004 Luming Yu <luming.yu@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/proc_fs.h> | ||
29 | #include <linux/sched.h> | ||
30 | #include <linux/kmod.h> | ||
31 | #include <linux/seq_file.h> | ||
32 | #include <acpi/acpi_drivers.h> | ||
33 | #include <acpi/acpi_bus.h> | ||
34 | #include <asm/uaccess.h> | ||
35 | |||
36 | #define HOTKEY_ACPI_VERSION "0.1" | ||
37 | |||
38 | #define HOTKEY_PROC "hotkey" | ||
39 | #define HOTKEY_EV_CONFIG "event_config" | ||
40 | #define HOTKEY_PL_CONFIG "poll_config" | ||
41 | #define HOTKEY_ACTION "action" | ||
42 | #define HOTKEY_INFO "info" | ||
43 | |||
44 | #define ACPI_HOTK_NAME "Generic Hotkey Driver" | ||
45 | #define ACPI_HOTK_CLASS "Hotkey" | ||
46 | #define ACPI_HOTK_DEVICE_NAME "Hotkey" | ||
47 | #define ACPI_HOTK_HID "Unknown?" | ||
48 | #define ACPI_HOTKEY_COMPONENT 0x20000000 | ||
49 | |||
50 | #define ACPI_HOTKEY_EVENT 0x1 | ||
51 | #define ACPI_HOTKEY_POLLING 0x2 | ||
52 | #define ACPI_UNDEFINED_EVENT 0xf | ||
53 | |||
54 | #define MAX_CONFIG_RECORD_LEN 80 | ||
55 | #define MAX_NAME_PATH_LEN 80 | ||
56 | #define MAX_CALL_PARM 80 | ||
57 | |||
58 | #define IS_EVENT(e) 0xff /* ((e) & 0x40000000) */ | ||
59 | #define IS_POLL(e) 0xff /* (~((e) & 0x40000000)) */ | ||
60 | |||
61 | #define _COMPONENT ACPI_HOTKEY_COMPONENT | ||
62 | ACPI_MODULE_NAME("acpi_hotkey") | ||
63 | |||
64 | MODULE_AUTHOR("luming.yu@intel.com"); | ||
65 | MODULE_DESCRIPTION(ACPI_HOTK_NAME); | ||
66 | MODULE_LICENSE("GPL"); | ||
67 | |||
68 | /* standardized internal hotkey number/event */ | ||
69 | enum { | ||
70 | /* Video Extension event */ | ||
71 | HK_EVENT_CYCLE_OUTPUT_DEVICE = 0x80, | ||
72 | HK_EVENT_OUTPUT_DEVICE_STATUS_CHANGE, | ||
73 | HK_EVENT_CYCLE_DISPLAY_OUTPUT, | ||
74 | HK_EVENT_NEXT_DISPLAY_OUTPUT, | ||
75 | HK_EVENT_PREVIOUS_DISPLAY_OUTPUT, | ||
76 | HK_EVENT_CYCLE_BRIGHTNESS, | ||
77 | HK_EVENT_INCREASE_BRIGHTNESS, | ||
78 | HK_EVENT_DECREASE_BRIGHTNESS, | ||
79 | HK_EVENT_ZERO_BRIGHTNESS, | ||
80 | HK_EVENT_DISPLAY_DEVICE_OFF, | ||
81 | |||
82 | /* Snd Card event */ | ||
83 | HK_EVENT_VOLUME_MUTE, | ||
84 | HK_EVENT_VOLUME_INCLREASE, | ||
85 | HK_EVENT_VOLUME_DECREASE, | ||
86 | |||
87 | /* running state control */ | ||
88 | HK_EVENT_ENTERRING_S3, | ||
89 | HK_EVENT_ENTERRING_S4, | ||
90 | HK_EVENT_ENTERRING_S5, | ||
91 | }; | ||
92 | |||
93 | /* procdir we use */ | ||
94 | static struct proc_dir_entry *hotkey_proc_dir; | ||
95 | static struct proc_dir_entry *hotkey_config; | ||
96 | static struct proc_dir_entry *hotkey_poll_config; | ||
97 | static struct proc_dir_entry *hotkey_action; | ||
98 | static struct proc_dir_entry *hotkey_info; | ||
99 | |||
100 | /* linkage for all type of hotkey */ | ||
101 | struct acpi_hotkey_link { | ||
102 | struct list_head entries; | ||
103 | int hotkey_type; /* event or polling based hotkey */ | ||
104 | int hotkey_standard_num; /* standardized hotkey(event) number */ | ||
105 | }; | ||
106 | |||
107 | /* event based hotkey */ | ||
108 | struct acpi_event_hotkey { | ||
109 | struct acpi_hotkey_link hotkey_link; | ||
110 | int flag; | ||
111 | acpi_handle bus_handle; /* bus to install notify handler */ | ||
112 | int external_hotkey_num; /* external hotkey/event number */ | ||
113 | acpi_handle action_handle; /* acpi handle attached aml action method */ | ||
114 | char *action_method; /* action method */ | ||
115 | }; | ||
116 | |||
117 | /* | ||
118 | * There are two ways to poll status | ||
119 | * 1. directy call read_xxx method, without any arguments passed in | ||
120 | * 2. call write_xxx method, with arguments passed in, you need | ||
121 | * the result is saved in acpi_polling_hotkey.poll_result. | ||
122 | * anthoer read command through polling interface. | ||
123 | * | ||
124 | */ | ||
125 | |||
126 | /* polling based hotkey */ | ||
127 | struct acpi_polling_hotkey { | ||
128 | struct acpi_hotkey_link hotkey_link; | ||
129 | int flag; | ||
130 | acpi_handle poll_handle; /* acpi handle attached polling method */ | ||
131 | char *poll_method; /* poll method */ | ||
132 | acpi_handle action_handle; /* acpi handle attached action method */ | ||
133 | char *action_method; /* action method */ | ||
134 | void *poll_result; /* polling_result */ | ||
135 | struct proc_dir_entry *proc; | ||
136 | }; | ||
137 | |||
138 | /* hotkey object union */ | ||
139 | union acpi_hotkey { | ||
140 | struct list_head entries; | ||
141 | struct acpi_hotkey_link link; | ||
142 | struct acpi_event_hotkey event_hotkey; | ||
143 | struct acpi_polling_hotkey poll_hotkey; | ||
144 | }; | ||
145 | |||
146 | /* hotkey object list */ | ||
147 | struct acpi_hotkey_list { | ||
148 | struct list_head *entries; | ||
149 | int count; | ||
150 | }; | ||
151 | |||
152 | static int auto_hotkey_add(struct acpi_device *device); | ||
153 | static int auto_hotkey_remove(struct acpi_device *device, int type); | ||
154 | |||
155 | static struct acpi_driver hotkey_driver = { | ||
156 | .name = ACPI_HOTK_NAME, | ||
157 | .class = ACPI_HOTK_CLASS, | ||
158 | .ids = ACPI_HOTK_HID, | ||
159 | .ops = { | ||
160 | .add = auto_hotkey_add, | ||
161 | .remove = auto_hotkey_remove, | ||
162 | }, | ||
163 | }; | ||
164 | |||
165 | static int hotkey_open_config(struct inode *inode, struct file *file); | ||
166 | static ssize_t hotkey_write_config(struct file *file, | ||
167 | const char __user * buffer, | ||
168 | size_t count, loff_t * data); | ||
169 | static ssize_t hotkey_write_poll_config(struct file *file, | ||
170 | const char __user * buffer, | ||
171 | size_t count, loff_t * data); | ||
172 | static int hotkey_info_open_fs(struct inode *inode, struct file *file); | ||
173 | static int hotkey_action_open_fs(struct inode *inode, struct file *file); | ||
174 | static ssize_t hotkey_execute_aml_method(struct file *file, | ||
175 | const char __user * buffer, | ||
176 | size_t count, loff_t * data); | ||
177 | static int hotkey_config_seq_show(struct seq_file *seq, void *offset); | ||
178 | static int hotkey_polling_open_fs(struct inode *inode, struct file *file); | ||
179 | |||
180 | /* event based config */ | ||
181 | static struct file_operations hotkey_config_fops = { | ||
182 | .open = hotkey_open_config, | ||
183 | .read = seq_read, | ||
184 | .write = hotkey_write_config, | ||
185 | .llseek = seq_lseek, | ||
186 | .release = single_release, | ||
187 | }; | ||
188 | |||
189 | /* polling based config */ | ||
190 | static struct file_operations hotkey_poll_config_fops = { | ||
191 | .open = hotkey_open_config, | ||
192 | .read = seq_read, | ||
193 | .write = hotkey_write_poll_config, | ||
194 | .llseek = seq_lseek, | ||
195 | .release = single_release, | ||
196 | }; | ||
197 | |||
198 | /* hotkey driver info */ | ||
199 | static struct file_operations hotkey_info_fops = { | ||
200 | .open = hotkey_info_open_fs, | ||
201 | .read = seq_read, | ||
202 | .llseek = seq_lseek, | ||
203 | .release = single_release, | ||
204 | }; | ||
205 | |||
206 | /* action */ | ||
207 | static struct file_operations hotkey_action_fops = { | ||
208 | .open = hotkey_action_open_fs, | ||
209 | .read = seq_read, | ||
210 | .write = hotkey_execute_aml_method, | ||
211 | .llseek = seq_lseek, | ||
212 | .release = single_release, | ||
213 | }; | ||
214 | |||
215 | /* polling results */ | ||
216 | static struct file_operations hotkey_polling_fops = { | ||
217 | .open = hotkey_polling_open_fs, | ||
218 | .read = seq_read, | ||
219 | .llseek = seq_lseek, | ||
220 | .release = single_release, | ||
221 | }; | ||
222 | |||
223 | struct acpi_hotkey_list global_hotkey_list; /* link all ev or pl hotkey */ | ||
224 | struct list_head hotkey_entries; /* head of the list of hotkey_list */ | ||
225 | |||
226 | static int hotkey_info_seq_show(struct seq_file *seq, void *offset) | ||
227 | { | ||
228 | ACPI_FUNCTION_TRACE("hotkey_info_seq_show"); | ||
229 | |||
230 | seq_printf(seq, "Hotkey generic driver ver: %s", HOTKEY_ACPI_VERSION); | ||
231 | |||
232 | return_VALUE(0); | ||
233 | } | ||
234 | |||
235 | static int hotkey_info_open_fs(struct inode *inode, struct file *file) | ||
236 | { | ||
237 | return single_open(file, hotkey_info_seq_show, PDE(inode)->data); | ||
238 | } | ||
239 | |||
240 | static char *format_result(union acpi_object *object) | ||
241 | { | ||
242 | char *buf = (char *)kmalloc(sizeof(union acpi_object), GFP_KERNEL); | ||
243 | |||
244 | memset(buf, 0, sizeof(union acpi_object)); | ||
245 | |||
246 | /* Now, just support integer type */ | ||
247 | if (object->type == ACPI_TYPE_INTEGER) | ||
248 | sprintf(buf, "%d", (u32) object->integer.value); | ||
249 | |||
250 | return buf; | ||
251 | } | ||
252 | |||
253 | static int hotkey_polling_seq_show(struct seq_file *seq, void *offset) | ||
254 | { | ||
255 | struct acpi_polling_hotkey *poll_hotkey = | ||
256 | (struct acpi_polling_hotkey *)seq->private; | ||
257 | |||
258 | ACPI_FUNCTION_TRACE("hotkey_polling_seq_show"); | ||
259 | |||
260 | if (poll_hotkey->poll_result) | ||
261 | seq_printf(seq, "%s", format_result(poll_hotkey->poll_result)); | ||
262 | |||
263 | return_VALUE(0); | ||
264 | } | ||
265 | |||
266 | static int hotkey_polling_open_fs(struct inode *inode, struct file *file) | ||
267 | { | ||
268 | return single_open(file, hotkey_polling_seq_show, PDE(inode)->data); | ||
269 | } | ||
270 | |||
271 | static int hotkey_action_open_fs(struct inode *inode, struct file *file) | ||
272 | { | ||
273 | return single_open(file, hotkey_info_seq_show, PDE(inode)->data); | ||
274 | } | ||
275 | |||
276 | /* Mapping external hotkey number to standardized hotkey event num */ | ||
277 | static int hotkey_get_internal_event(int event, struct acpi_hotkey_list *list) | ||
278 | { | ||
279 | struct list_head *entries, *next; | ||
280 | int val = 0; | ||
281 | |||
282 | ACPI_FUNCTION_TRACE("hotkey_get_internal_event"); | ||
283 | |||
284 | list_for_each_safe(entries, next, list->entries) { | ||
285 | union acpi_hotkey *key = | ||
286 | container_of(entries, union acpi_hotkey, entries); | ||
287 | if (key->link.hotkey_type == ACPI_HOTKEY_EVENT | ||
288 | && key->event_hotkey.external_hotkey_num == event) | ||
289 | val = key->link.hotkey_standard_num; | ||
290 | else | ||
291 | val = -1; | ||
292 | } | ||
293 | |||
294 | return_VALUE(val); | ||
295 | } | ||
296 | |||
297 | static void | ||
298 | acpi_hotkey_notify_handler(acpi_handle handle, u32 event, void *data) | ||
299 | { | ||
300 | struct acpi_device *device = NULL; | ||
301 | u32 internal_event; | ||
302 | |||
303 | ACPI_FUNCTION_TRACE("acpi_hotkey_notify_handler"); | ||
304 | |||
305 | if (acpi_bus_get_device(handle, &device)) | ||
306 | return_VOID; | ||
307 | |||
308 | internal_event = hotkey_get_internal_event(event, &global_hotkey_list); | ||
309 | acpi_bus_generate_event(device, event, 0); | ||
310 | |||
311 | return_VOID; | ||
312 | } | ||
313 | |||
314 | /* Need to invent automatically hotkey add method */ | ||
315 | static int auto_hotkey_add(struct acpi_device *device) | ||
316 | { | ||
317 | /* Implement me */ | ||
318 | return 0; | ||
319 | } | ||
320 | |||
321 | /* Need to invent automatically hotkey remove method */ | ||
322 | static int auto_hotkey_remove(struct acpi_device *device, int type) | ||
323 | { | ||
324 | /* Implement me */ | ||
325 | return 0; | ||
326 | } | ||
327 | |||
328 | /* Create a proc file for each polling method */ | ||
329 | static int create_polling_proc(union acpi_hotkey *device) | ||
330 | { | ||
331 | struct proc_dir_entry *proc; | ||
332 | mode_t mode; | ||
333 | |||
334 | ACPI_FUNCTION_TRACE("create_polling_proc"); | ||
335 | mode = S_IFREG | S_IRUGO | S_IWUGO; | ||
336 | |||
337 | proc = create_proc_entry(device->poll_hotkey.action_method, | ||
338 | mode, hotkey_proc_dir); | ||
339 | |||
340 | if (!proc) { | ||
341 | ACPI_DEBUG_PRINT((ACPI_DB_ERROR, | ||
342 | "Hotkey: Unable to create %s entry\n", | ||
343 | device->poll_hotkey.poll_method)); | ||
344 | return_VALUE(-ENODEV); | ||
345 | } else { | ||
346 | proc->proc_fops = &hotkey_polling_fops; | ||
347 | proc->owner = THIS_MODULE; | ||
348 | proc->data = device; | ||
349 | proc->uid = 0; | ||
350 | proc->gid = 0; | ||
351 | device->poll_hotkey.proc = proc; | ||
352 | } | ||
353 | return_VALUE(0); | ||
354 | } | ||
355 | |||
356 | static int is_valid_acpi_path(const char *pathname) | ||
357 | { | ||
358 | acpi_handle handle; | ||
359 | acpi_status status; | ||
360 | ACPI_FUNCTION_TRACE("is_valid_acpi_path"); | ||
361 | |||
362 | status = acpi_get_handle(NULL, (char *)pathname, &handle); | ||
363 | return_VALUE(!ACPI_FAILURE(status)); | ||
364 | } | ||
365 | |||
366 | static int is_valid_hotkey(union acpi_hotkey *device) | ||
367 | { | ||
368 | ACPI_FUNCTION_TRACE("is_valid_hotkey"); | ||
369 | /* Implement valid check */ | ||
370 | return_VALUE(1); | ||
371 | } | ||
372 | |||
373 | static int hotkey_add(union acpi_hotkey *device) | ||
374 | { | ||
375 | int status = 0; | ||
376 | struct acpi_device *dev = NULL; | ||
377 | |||
378 | ACPI_FUNCTION_TRACE("hotkey_add"); | ||
379 | |||
380 | if (device->link.hotkey_type == ACPI_HOTKEY_EVENT) { | ||
381 | status = | ||
382 | acpi_bus_get_device(device->event_hotkey.bus_handle, &dev); | ||
383 | if (status) | ||
384 | return_VALUE(status); | ||
385 | |||
386 | status = acpi_install_notify_handler(dev->handle, | ||
387 | ACPI_SYSTEM_NOTIFY, | ||
388 | acpi_hotkey_notify_handler, | ||
389 | device); | ||
390 | } else /* Add polling hotkey */ | ||
391 | create_polling_proc(device); | ||
392 | |||
393 | global_hotkey_list.count++; | ||
394 | |||
395 | list_add_tail(&device->link.entries, global_hotkey_list.entries); | ||
396 | |||
397 | return_VALUE(status); | ||
398 | } | ||
399 | |||
400 | static int hotkey_remove(union acpi_hotkey *device) | ||
401 | { | ||
402 | struct list_head *entries, *next; | ||
403 | |||
404 | ACPI_FUNCTION_TRACE("hotkey_remove"); | ||
405 | |||
406 | list_for_each_safe(entries, next, global_hotkey_list.entries) { | ||
407 | union acpi_hotkey *key = | ||
408 | container_of(entries, union acpi_hotkey, entries); | ||
409 | if (key->link.hotkey_standard_num == | ||
410 | device->link.hotkey_standard_num) { | ||
411 | list_del(&key->link.entries); | ||
412 | remove_proc_entry(key->poll_hotkey.action_method, | ||
413 | hotkey_proc_dir); | ||
414 | global_hotkey_list.count--; | ||
415 | break; | ||
416 | } | ||
417 | } | ||
418 | return_VALUE(0); | ||
419 | } | ||
420 | |||
421 | static void hotkey_update(union acpi_hotkey *key) | ||
422 | { | ||
423 | struct list_head *entries, *next; | ||
424 | |||
425 | ACPI_FUNCTION_TRACE("hotkey_update"); | ||
426 | |||
427 | list_for_each_safe(entries, next, global_hotkey_list.entries) { | ||
428 | union acpi_hotkey *key = | ||
429 | container_of(entries, union acpi_hotkey, entries); | ||
430 | if (key->link.hotkey_standard_num == | ||
431 | key->link.hotkey_standard_num) { | ||
432 | key->event_hotkey.bus_handle = | ||
433 | key->event_hotkey.bus_handle; | ||
434 | key->event_hotkey.external_hotkey_num = | ||
435 | key->event_hotkey.external_hotkey_num; | ||
436 | key->event_hotkey.action_handle = | ||
437 | key->event_hotkey.action_handle; | ||
438 | key->event_hotkey.action_method = | ||
439 | key->event_hotkey.action_method; | ||
440 | break; | ||
441 | } | ||
442 | } | ||
443 | |||
444 | return_VOID; | ||
445 | } | ||
446 | |||
447 | static void free_hotkey_device(union acpi_hotkey *key) | ||
448 | { | ||
449 | struct acpi_device *dev; | ||
450 | int status; | ||
451 | |||
452 | ACPI_FUNCTION_TRACE("free_hotkey_device"); | ||
453 | |||
454 | if (key->link.hotkey_type == ACPI_HOTKEY_EVENT) { | ||
455 | status = | ||
456 | acpi_bus_get_device(key->event_hotkey.bus_handle, &dev); | ||
457 | if (dev->handle) | ||
458 | acpi_remove_notify_handler(dev->handle, | ||
459 | ACPI_SYSTEM_NOTIFY, | ||
460 | acpi_hotkey_notify_handler); | ||
461 | } else | ||
462 | remove_proc_entry(key->poll_hotkey.action_method, | ||
463 | hotkey_proc_dir); | ||
464 | kfree(key); | ||
465 | return_VOID; | ||
466 | } | ||
467 | |||
468 | static int | ||
469 | init_hotkey_device(union acpi_hotkey *key, char *bus_str, char *action_str, | ||
470 | char *method, int std_num, int external_num) | ||
471 | { | ||
472 | ACPI_FUNCTION_TRACE("init_hotkey_device"); | ||
473 | |||
474 | key->link.hotkey_type = ACPI_HOTKEY_EVENT; | ||
475 | key->link.hotkey_standard_num = std_num; | ||
476 | key->event_hotkey.flag = 0; | ||
477 | if (is_valid_acpi_path(bus_str)) | ||
478 | acpi_get_handle((acpi_handle) 0, | ||
479 | bus_str, &(key->event_hotkey.bus_handle)); | ||
480 | else | ||
481 | return_VALUE(-ENODEV); | ||
482 | key->event_hotkey.external_hotkey_num = external_num; | ||
483 | if (is_valid_acpi_path(action_str)) | ||
484 | acpi_get_handle((acpi_handle) 0, | ||
485 | action_str, &(key->event_hotkey.action_handle)); | ||
486 | key->event_hotkey.action_method = kmalloc(sizeof(method), GFP_KERNEL); | ||
487 | strcpy(key->event_hotkey.action_method, method); | ||
488 | |||
489 | return_VALUE(!is_valid_hotkey(key)); | ||
490 | } | ||
491 | |||
492 | static int | ||
493 | init_poll_hotkey_device(union acpi_hotkey *key, | ||
494 | char *poll_str, | ||
495 | char *poll_method, | ||
496 | char *action_str, char *action_method, int std_num) | ||
497 | { | ||
498 | ACPI_FUNCTION_TRACE("init_poll_hotkey_device"); | ||
499 | |||
500 | key->link.hotkey_type = ACPI_HOTKEY_POLLING; | ||
501 | key->link.hotkey_standard_num = std_num; | ||
502 | key->poll_hotkey.flag = 0; | ||
503 | if (is_valid_acpi_path(poll_str)) | ||
504 | acpi_get_handle((acpi_handle) 0, | ||
505 | poll_str, &(key->poll_hotkey.poll_handle)); | ||
506 | else | ||
507 | return_VALUE(-ENODEV); | ||
508 | key->poll_hotkey.poll_method = poll_method; | ||
509 | if (is_valid_acpi_path(action_str)) | ||
510 | acpi_get_handle((acpi_handle) 0, | ||
511 | action_str, &(key->poll_hotkey.action_handle)); | ||
512 | key->poll_hotkey.action_method = | ||
513 | kmalloc(sizeof(action_method), GFP_KERNEL); | ||
514 | strcpy(key->poll_hotkey.action_method, action_method); | ||
515 | key->poll_hotkey.poll_result = | ||
516 | (union acpi_object *)kmalloc(sizeof(union acpi_object), GFP_KERNEL); | ||
517 | return_VALUE(is_valid_hotkey(key)); | ||
518 | } | ||
519 | |||
520 | static int check_hotkey_valid(union acpi_hotkey *key, | ||
521 | struct acpi_hotkey_list *list) | ||
522 | { | ||
523 | ACPI_FUNCTION_TRACE("check_hotkey_valid"); | ||
524 | return_VALUE(0); | ||
525 | } | ||
526 | |||
527 | static int hotkey_open_config(struct inode *inode, struct file *file) | ||
528 | { | ||
529 | ACPI_FUNCTION_TRACE("hotkey_open_config"); | ||
530 | return_VALUE(single_open | ||
531 | (file, hotkey_config_seq_show, PDE(inode)->data)); | ||
532 | } | ||
533 | |||
534 | static int hotkey_config_seq_show(struct seq_file *seq, void *offset) | ||
535 | { | ||
536 | struct acpi_hotkey_list *hotkey_list = &global_hotkey_list; | ||
537 | struct list_head *entries, *next; | ||
538 | char bus_name[ACPI_PATHNAME_MAX] = { 0 }; | ||
539 | char action_name[ACPI_PATHNAME_MAX] = { 0 }; | ||
540 | struct acpi_buffer bus = { ACPI_PATHNAME_MAX, bus_name }; | ||
541 | struct acpi_buffer act = { ACPI_PATHNAME_MAX, action_name }; | ||
542 | |||
543 | ACPI_FUNCTION_TRACE(("hotkey_config_seq_show")); | ||
544 | |||
545 | if (!hotkey_list) | ||
546 | goto end; | ||
547 | |||
548 | list_for_each_safe(entries, next, hotkey_list->entries) { | ||
549 | union acpi_hotkey *key = | ||
550 | container_of(entries, union acpi_hotkey, entries); | ||
551 | if (key->link.hotkey_type == ACPI_HOTKEY_EVENT) { | ||
552 | acpi_get_name(key->event_hotkey.bus_handle, | ||
553 | ACPI_NAME_TYPE_MAX, &bus); | ||
554 | acpi_get_name(key->event_hotkey.action_handle, | ||
555 | ACPI_NAME_TYPE_MAX, &act); | ||
556 | seq_printf(seq, "%s:%s:%s:%d:%d", bus_name, | ||
557 | action_name, | ||
558 | key->event_hotkey.action_method, | ||
559 | key->link.hotkey_standard_num, | ||
560 | key->event_hotkey.external_hotkey_num); | ||
561 | } /* ACPI_HOTKEY_POLLING */ | ||
562 | else { | ||
563 | acpi_get_name(key->poll_hotkey.poll_handle, | ||
564 | ACPI_NAME_TYPE_MAX, &bus); | ||
565 | acpi_get_name(key->poll_hotkey.action_handle, | ||
566 | ACPI_NAME_TYPE_MAX, &act); | ||
567 | seq_printf(seq, "%s:%s:%s:%s:%d", bus_name, | ||
568 | key->poll_hotkey.poll_method, | ||
569 | action_name, | ||
570 | key->poll_hotkey.action_method, | ||
571 | key->link.hotkey_standard_num); | ||
572 | } | ||
573 | } | ||
574 | seq_puts(seq, "\n"); | ||
575 | end: | ||
576 | return_VALUE(0); | ||
577 | } | ||
578 | |||
579 | static int | ||
580 | get_parms(char *config_record, | ||
581 | int *cmd, | ||
582 | char *bus_handle, | ||
583 | char *bus_method, | ||
584 | char *action_handle, | ||
585 | char *method, int *internal_event_num, int *external_event_num) | ||
586 | { | ||
587 | char *tmp, *tmp1; | ||
588 | ACPI_FUNCTION_TRACE(("get_parms")); | ||
589 | |||
590 | sscanf(config_record, "%d", cmd); | ||
591 | |||
592 | tmp = strchr(config_record, ':'); | ||
593 | tmp++; | ||
594 | tmp1 = strchr(tmp, ':'); | ||
595 | strncpy(bus_handle, tmp, tmp1 - tmp); | ||
596 | bus_handle[tmp1 - tmp] = 0; | ||
597 | |||
598 | tmp = tmp1; | ||
599 | tmp++; | ||
600 | tmp1 = strchr(tmp, ':'); | ||
601 | strncpy(bus_method, tmp, tmp1 - tmp); | ||
602 | bus_method[tmp1 - tmp] = 0; | ||
603 | |||
604 | tmp = tmp1; | ||
605 | tmp++; | ||
606 | tmp1 = strchr(tmp, ':'); | ||
607 | strncpy(action_handle, tmp, tmp1 - tmp); | ||
608 | action_handle[tmp1 - tmp] = 0; | ||
609 | |||
610 | tmp = tmp1; | ||
611 | tmp++; | ||
612 | tmp1 = strchr(tmp, ':'); | ||
613 | strncpy(method, tmp, tmp1 - tmp); | ||
614 | method[tmp1 - tmp] = 0; | ||
615 | |||
616 | sscanf(tmp1 + 1, "%d:%d", internal_event_num, external_event_num); | ||
617 | return_VALUE(6); | ||
618 | } | ||
619 | |||
620 | /* count is length for one input record */ | ||
621 | static ssize_t hotkey_write_config(struct file *file, | ||
622 | const char __user * buffer, | ||
623 | size_t count, loff_t * data) | ||
624 | { | ||
625 | struct acpi_hotkey_list *hotkey_list = &global_hotkey_list; | ||
626 | char config_record[MAX_CONFIG_RECORD_LEN]; | ||
627 | char bus_handle[MAX_NAME_PATH_LEN]; | ||
628 | char bus_method[MAX_NAME_PATH_LEN]; | ||
629 | char action_handle[MAX_NAME_PATH_LEN]; | ||
630 | char method[20]; | ||
631 | int cmd, internal_event_num, external_event_num; | ||
632 | int ret = 0; | ||
633 | union acpi_hotkey *key = NULL; | ||
634 | |||
635 | ACPI_FUNCTION_TRACE(("hotkey_write_config")); | ||
636 | |||
637 | if (!hotkey_list || count > MAX_CONFIG_RECORD_LEN) { | ||
638 | ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Invalid arguments\n")); | ||
639 | return_VALUE(-EINVAL); | ||
640 | } | ||
641 | |||
642 | if (copy_from_user(config_record, buffer, count)) { | ||
643 | ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Invalid data \n")); | ||
644 | return_VALUE(-EINVAL); | ||
645 | } | ||
646 | config_record[count] = '\0'; | ||
647 | |||
648 | ret = get_parms(config_record, | ||
649 | &cmd, | ||
650 | bus_handle, | ||
651 | bus_method, | ||
652 | action_handle, | ||
653 | method, &internal_event_num, &external_event_num); | ||
654 | if (ret != 6) { | ||
655 | ACPI_DEBUG_PRINT((ACPI_DB_ERROR, | ||
656 | "Invalid data format ret=%d\n", ret)); | ||
657 | return_VALUE(-EINVAL); | ||
658 | } | ||
659 | |||
660 | key = kmalloc(sizeof(union acpi_hotkey), GFP_KERNEL); | ||
661 | ret = init_hotkey_device(key, bus_handle, action_handle, method, | ||
662 | internal_event_num, external_event_num); | ||
663 | |||
664 | if (ret || check_hotkey_valid(key, hotkey_list)) { | ||
665 | kfree(key); | ||
666 | ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Invalid hotkey \n")); | ||
667 | return_VALUE(-EINVAL); | ||
668 | } | ||
669 | switch (cmd) { | ||
670 | case 0: | ||
671 | hotkey_add(key); | ||
672 | break; | ||
673 | case 1: | ||
674 | hotkey_remove(key); | ||
675 | free_hotkey_device(key); | ||
676 | break; | ||
677 | case 2: | ||
678 | hotkey_update(key); | ||
679 | break; | ||
680 | default: | ||
681 | break; | ||
682 | } | ||
683 | return_VALUE(count); | ||
684 | } | ||
685 | |||
686 | /* count is length for one input record */ | ||
687 | static ssize_t hotkey_write_poll_config(struct file *file, | ||
688 | const char __user * buffer, | ||
689 | size_t count, loff_t * data) | ||
690 | { | ||
691 | struct seq_file *m = (struct seq_file *)file->private_data; | ||
692 | struct acpi_hotkey_list *hotkey_list = | ||
693 | (struct acpi_hotkey_list *)m->private; | ||
694 | |||
695 | char config_record[MAX_CONFIG_RECORD_LEN]; | ||
696 | char polling_handle[MAX_NAME_PATH_LEN]; | ||
697 | char action_handle[MAX_NAME_PATH_LEN]; | ||
698 | char poll_method[20], action_method[20]; | ||
699 | int ret, internal_event_num, cmd, external_event_num; | ||
700 | union acpi_hotkey *key = NULL; | ||
701 | |||
702 | ACPI_FUNCTION_TRACE("hotkey_write_poll_config"); | ||
703 | |||
704 | if (!hotkey_list || count > MAX_CONFIG_RECORD_LEN) { | ||
705 | ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Invalid arguments\n")); | ||
706 | return_VALUE(-EINVAL); | ||
707 | } | ||
708 | |||
709 | if (copy_from_user(config_record, buffer, count)) { | ||
710 | ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Invalid data \n")); | ||
711 | return_VALUE(-EINVAL); | ||
712 | } | ||
713 | config_record[count] = '\0'; | ||
714 | |||
715 | ret = get_parms(config_record, | ||
716 | &cmd, | ||
717 | polling_handle, | ||
718 | poll_method, | ||
719 | action_handle, | ||
720 | action_method, | ||
721 | &internal_event_num, &external_event_num); | ||
722 | |||
723 | if (ret != 6) { | ||
724 | ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Invalid data format\n")); | ||
725 | return_VALUE(-EINVAL); | ||
726 | } | ||
727 | |||
728 | key = kmalloc(sizeof(union acpi_hotkey), GFP_KERNEL); | ||
729 | ret = init_poll_hotkey_device(key, polling_handle, poll_method, | ||
730 | action_handle, action_method, | ||
731 | internal_event_num); | ||
732 | if (ret || check_hotkey_valid(key, hotkey_list)) { | ||
733 | kfree(key); | ||
734 | ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Invalid hotkey \n")); | ||
735 | return_VALUE(-EINVAL); | ||
736 | } | ||
737 | switch (cmd) { | ||
738 | case 0: | ||
739 | hotkey_add(key); | ||
740 | break; | ||
741 | case 1: | ||
742 | hotkey_remove(key); | ||
743 | break; | ||
744 | case 2: | ||
745 | hotkey_update(key); | ||
746 | break; | ||
747 | default: | ||
748 | break; | ||
749 | } | ||
750 | return_VALUE(count); | ||
751 | } | ||
752 | |||
753 | /* | ||
754 | * This function evaluates an ACPI method, given an int as parameter, the | ||
755 | * method is searched within the scope of the handle, can be NULL. The output | ||
756 | * of the method is written is output, which can also be NULL | ||
757 | * | ||
758 | * returns 1 if write is successful, 0 else. | ||
759 | */ | ||
760 | static int write_acpi_int(acpi_handle handle, const char *method, int val, | ||
761 | struct acpi_buffer *output) | ||
762 | { | ||
763 | struct acpi_object_list params; /* list of input parameters (an int here) */ | ||
764 | union acpi_object in_obj; /* the only param we use */ | ||
765 | acpi_status status; | ||
766 | |||
767 | ACPI_FUNCTION_TRACE("write_acpi_int"); | ||
768 | params.count = 1; | ||
769 | params.pointer = &in_obj; | ||
770 | in_obj.type = ACPI_TYPE_INTEGER; | ||
771 | in_obj.integer.value = val; | ||
772 | |||
773 | status = acpi_evaluate_object(handle, (char *)method, ¶ms, output); | ||
774 | |||
775 | return_VALUE(status == AE_OK); | ||
776 | } | ||
777 | |||
778 | static int read_acpi_int(acpi_handle handle, const char *method, int *val) | ||
779 | { | ||
780 | struct acpi_buffer output; | ||
781 | union acpi_object out_obj; | ||
782 | acpi_status status; | ||
783 | |||
784 | ACPI_FUNCTION_TRACE("read_acpi_int"); | ||
785 | output.length = sizeof(out_obj); | ||
786 | output.pointer = &out_obj; | ||
787 | |||
788 | status = acpi_evaluate_object(handle, (char *)method, NULL, &output); | ||
789 | *val = out_obj.integer.value; | ||
790 | return_VALUE((status == AE_OK) | ||
791 | && (out_obj.type == ACPI_TYPE_INTEGER)); | ||
792 | } | ||
793 | |||
794 | static acpi_handle | ||
795 | get_handle_from_hotkeylist(struct acpi_hotkey_list *hotkey_list, int event_num) | ||
796 | { | ||
797 | struct list_head *entries, *next; | ||
798 | |||
799 | list_for_each_safe(entries, next, hotkey_list->entries) { | ||
800 | union acpi_hotkey *key = | ||
801 | container_of(entries, union acpi_hotkey, entries); | ||
802 | if (key->link.hotkey_type == ACPI_HOTKEY_EVENT | ||
803 | && key->link.hotkey_standard_num == event_num) { | ||
804 | return (key->event_hotkey.action_handle); | ||
805 | } | ||
806 | } | ||
807 | return (NULL); | ||
808 | } | ||
809 | |||
810 | static | ||
811 | char *get_method_from_hotkeylist(struct acpi_hotkey_list *hotkey_list, | ||
812 | int event_num) | ||
813 | { | ||
814 | struct list_head *entries, *next; | ||
815 | |||
816 | list_for_each_safe(entries, next, hotkey_list->entries) { | ||
817 | union acpi_hotkey *key = | ||
818 | container_of(entries, union acpi_hotkey, entries); | ||
819 | |||
820 | if (key->link.hotkey_type == ACPI_HOTKEY_EVENT && | ||
821 | key->link.hotkey_standard_num == event_num) | ||
822 | return (key->event_hotkey.action_method); | ||
823 | } | ||
824 | return (NULL); | ||
825 | } | ||
826 | |||
827 | static struct acpi_polling_hotkey *get_hotkey_by_event(struct | ||
828 | acpi_hotkey_list | ||
829 | *hotkey_list, int event) | ||
830 | { | ||
831 | struct list_head *entries, *next; | ||
832 | |||
833 | list_for_each_safe(entries, next, hotkey_list->entries) { | ||
834 | union acpi_hotkey *key = | ||
835 | container_of(entries, union acpi_hotkey, entries); | ||
836 | if (key->link.hotkey_type == ACPI_HOTKEY_POLLING | ||
837 | && key->link.hotkey_standard_num == event) { | ||
838 | return (&key->poll_hotkey); | ||
839 | } | ||
840 | } | ||
841 | return (NULL); | ||
842 | } | ||
843 | |||
844 | /* | ||
845 | * user call AML method interface: | ||
846 | * Call convention: | ||
847 | * echo "event_num: arg type : value" | ||
848 | * example: echo "1:1:30" > /proc/acpi/action | ||
849 | * Just support 1 integer arg passing to AML method | ||
850 | */ | ||
851 | |||
852 | static ssize_t hotkey_execute_aml_method(struct file *file, | ||
853 | const char __user * buffer, | ||
854 | size_t count, loff_t * data) | ||
855 | { | ||
856 | struct acpi_hotkey_list *hotkey_list = &global_hotkey_list; | ||
857 | char arg[MAX_CALL_PARM]; | ||
858 | int event, type, value; | ||
859 | |||
860 | char *method; | ||
861 | acpi_handle handle; | ||
862 | |||
863 | ACPI_FUNCTION_TRACE("hotkey_execte_aml_method"); | ||
864 | |||
865 | if (!hotkey_list || count > MAX_CALL_PARM) { | ||
866 | ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Invalid argument 1")); | ||
867 | return_VALUE(-EINVAL); | ||
868 | } | ||
869 | |||
870 | if (copy_from_user(arg, buffer, count)) { | ||
871 | ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Invalid argument 2")); | ||
872 | return_VALUE(-EINVAL); | ||
873 | } | ||
874 | |||
875 | arg[count] = '\0'; | ||
876 | |||
877 | if (sscanf(arg, "%d:%d:%d", &event, &type, &value) != 3) { | ||
878 | ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Invalid argument 3")); | ||
879 | return_VALUE(-EINVAL); | ||
880 | } | ||
881 | |||
882 | if (type == ACPI_TYPE_INTEGER) { | ||
883 | handle = get_handle_from_hotkeylist(hotkey_list, event); | ||
884 | method = (char *)get_method_from_hotkeylist(hotkey_list, event); | ||
885 | if (IS_EVENT(event)) | ||
886 | write_acpi_int(handle, method, value, NULL); | ||
887 | else if (IS_POLL(event)) { | ||
888 | struct acpi_polling_hotkey *key; | ||
889 | key = (struct acpi_polling_hotkey *) | ||
890 | get_hotkey_by_event(hotkey_list, event); | ||
891 | read_acpi_int(handle, method, key->poll_result); | ||
892 | } | ||
893 | } else { | ||
894 | ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Not supported")); | ||
895 | return_VALUE(-EINVAL); | ||
896 | } | ||
897 | |||
898 | return_VALUE(count); | ||
899 | } | ||
900 | |||
901 | static int __init hotkey_init(void) | ||
902 | { | ||
903 | int result; | ||
904 | mode_t mode = S_IFREG | S_IRUGO | S_IWUGO; | ||
905 | |||
906 | ACPI_FUNCTION_TRACE("hotkey_init"); | ||
907 | |||
908 | if (acpi_disabled) | ||
909 | return -ENODEV; | ||
910 | |||
911 | if (acpi_specific_hotkey_enabled) { | ||
912 | printk("Using specific hotkey driver\n"); | ||
913 | return -ENODEV; | ||
914 | } | ||
915 | |||
916 | hotkey_proc_dir = proc_mkdir(HOTKEY_PROC, acpi_root_dir); | ||
917 | if (!hotkey_proc_dir) { | ||
918 | ACPI_DEBUG_PRINT((ACPI_DB_ERROR, | ||
919 | "Hotkey: Unable to create %s entry\n", | ||
920 | HOTKEY_PROC)); | ||
921 | return (-ENODEV); | ||
922 | } | ||
923 | hotkey_proc_dir->owner = THIS_MODULE; | ||
924 | |||
925 | hotkey_config = | ||
926 | create_proc_entry(HOTKEY_EV_CONFIG, mode, hotkey_proc_dir); | ||
927 | if (!hotkey_config) { | ||
928 | ACPI_DEBUG_PRINT((ACPI_DB_ERROR, | ||
929 | "Hotkey: Unable to create %s entry\n", | ||
930 | HOTKEY_EV_CONFIG)); | ||
931 | return (-ENODEV); | ||
932 | } else { | ||
933 | hotkey_config->proc_fops = &hotkey_config_fops; | ||
934 | hotkey_config->data = &global_hotkey_list; | ||
935 | hotkey_config->owner = THIS_MODULE; | ||
936 | hotkey_config->uid = 0; | ||
937 | hotkey_config->gid = 0; | ||
938 | } | ||
939 | |||
940 | hotkey_poll_config = | ||
941 | create_proc_entry(HOTKEY_PL_CONFIG, mode, hotkey_proc_dir); | ||
942 | if (!hotkey_poll_config) { | ||
943 | ACPI_DEBUG_PRINT((ACPI_DB_ERROR, | ||
944 | "Hotkey: Unable to create %s entry\n", | ||
945 | HOTKEY_EV_CONFIG)); | ||
946 | return (-ENODEV); | ||
947 | } else { | ||
948 | hotkey_poll_config->proc_fops = &hotkey_poll_config_fops; | ||
949 | hotkey_poll_config->data = &global_hotkey_list; | ||
950 | hotkey_poll_config->owner = THIS_MODULE; | ||
951 | hotkey_poll_config->uid = 0; | ||
952 | hotkey_poll_config->gid = 0; | ||
953 | } | ||
954 | |||
955 | hotkey_action = create_proc_entry(HOTKEY_ACTION, mode, hotkey_proc_dir); | ||
956 | if (!hotkey_action) { | ||
957 | ACPI_DEBUG_PRINT((ACPI_DB_ERROR, | ||
958 | "Hotkey: Unable to create %s entry\n", | ||
959 | HOTKEY_ACTION)); | ||
960 | return (-ENODEV); | ||
961 | } else { | ||
962 | hotkey_action->proc_fops = &hotkey_action_fops; | ||
963 | hotkey_action->owner = THIS_MODULE; | ||
964 | hotkey_action->uid = 0; | ||
965 | hotkey_action->gid = 0; | ||
966 | } | ||
967 | |||
968 | hotkey_info = create_proc_entry(HOTKEY_INFO, mode, hotkey_proc_dir); | ||
969 | if (!hotkey_info) { | ||
970 | ACPI_DEBUG_PRINT((ACPI_DB_ERROR, | ||
971 | "Hotkey: Unable to create %s entry\n", | ||
972 | HOTKEY_INFO)); | ||
973 | return (-ENODEV); | ||
974 | } else { | ||
975 | hotkey_info->proc_fops = &hotkey_info_fops; | ||
976 | hotkey_info->owner = THIS_MODULE; | ||
977 | hotkey_info->uid = 0; | ||
978 | hotkey_info->gid = 0; | ||
979 | } | ||
980 | |||
981 | result = acpi_bus_register_driver(&hotkey_driver); | ||
982 | if (result < 0) { | ||
983 | remove_proc_entry(HOTKEY_PROC, acpi_root_dir); | ||
984 | return (-ENODEV); | ||
985 | } | ||
986 | global_hotkey_list.count = 0; | ||
987 | global_hotkey_list.entries = &hotkey_entries; | ||
988 | |||
989 | INIT_LIST_HEAD(&hotkey_entries); | ||
990 | |||
991 | return (0); | ||
992 | } | ||
993 | |||
994 | static void __exit hotkey_exit(void) | ||
995 | { | ||
996 | struct list_head *entries, *next; | ||
997 | |||
998 | ACPI_FUNCTION_TRACE("hotkey_remove"); | ||
999 | |||
1000 | list_for_each_safe(entries, next, global_hotkey_list.entries) { | ||
1001 | union acpi_hotkey *key = | ||
1002 | container_of(entries, union acpi_hotkey, entries); | ||
1003 | |||
1004 | acpi_os_wait_events_complete(NULL); | ||
1005 | list_del(&key->link.entries); | ||
1006 | global_hotkey_list.count--; | ||
1007 | free_hotkey_device(key); | ||
1008 | } | ||
1009 | acpi_bus_unregister_driver(&hotkey_driver); | ||
1010 | remove_proc_entry(HOTKEY_EV_CONFIG, hotkey_proc_dir); | ||
1011 | remove_proc_entry(HOTKEY_PL_CONFIG, hotkey_proc_dir); | ||
1012 | remove_proc_entry(HOTKEY_ACTION, hotkey_proc_dir); | ||
1013 | remove_proc_entry(HOTKEY_INFO, hotkey_proc_dir); | ||
1014 | remove_proc_entry(HOTKEY_PROC, acpi_root_dir); | ||
1015 | return; | ||
1016 | } | ||
1017 | |||
1018 | module_init(hotkey_init); | ||
1019 | module_exit(hotkey_exit); | ||