diff options
author | Luming Yu <luming.yu@intel.com> | 2005-03-18 18:03:45 -0500 |
---|---|---|
committer | Len Brown <len.brown@intel.com> | 2005-07-11 23:27:04 -0400 |
commit | fb9802fa59b196d7f90bb3c2e33c555c6bdc4c54 (patch) | |
tree | aca6fd906629c6da124a73b43e6f6c50f81b90b4 /drivers/acpi/hotkey.c | |
parent | d58da590451cf6ae75379a2ebf96d3afb8d810d8 (diff) |
[ACPI] generic Hot Key support
See Documentation/acpi-hotkey.txt
Use cmdline "acpi_specific_hotkey" to enable
legacy platform specific drivers.
http://bugzilla.kernel.org/show_bug.cgi?id=3887
Signed-off-by: Luming Yu <luming.yu@intel.com>
Signed-off-by: Len Brown <len.brown@intel.com>
Diffstat (limited to 'drivers/acpi/hotkey.c')
-rw-r--r-- | drivers/acpi/hotkey.c | 1018 |
1 files changed, 1018 insertions, 0 deletions
diff --git a/drivers/acpi/hotkey.c b/drivers/acpi/hotkey.c new file mode 100644 index 000000000000..0aef9fc449c5 --- /dev/null +++ b/drivers/acpi/hotkey.c | |||
@@ -0,0 +1,1018 @@ | |||
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 | |||
333 | ACPI_FUNCTION_TRACE("create_polling_proc"); | ||
334 | mode_t mode = S_IFREG | S_IRUGO | S_IWUGO; | ||
335 | |||
336 | proc = create_proc_entry(device->poll_hotkey.action_method, | ||
337 | mode, hotkey_proc_dir); | ||
338 | |||
339 | if (!proc) { | ||
340 | ACPI_DEBUG_PRINT((ACPI_DB_ERROR, | ||
341 | "Hotkey: Unable to create %s entry\n", | ||
342 | device->poll_hotkey.poll_method)); | ||
343 | return_VALUE(-ENODEV); | ||
344 | } else { | ||
345 | proc->proc_fops = &hotkey_polling_fops; | ||
346 | proc->owner = THIS_MODULE; | ||
347 | proc->data = device; | ||
348 | proc->uid = 0; | ||
349 | proc->gid = 0; | ||
350 | device->poll_hotkey.proc = proc; | ||
351 | } | ||
352 | return_VALUE(0); | ||
353 | } | ||
354 | |||
355 | static int is_valid_acpi_path(const char *pathname) | ||
356 | { | ||
357 | acpi_handle handle; | ||
358 | acpi_status status; | ||
359 | ACPI_FUNCTION_TRACE("is_valid_acpi_path"); | ||
360 | |||
361 | status = acpi_get_handle(NULL, (char *)pathname, &handle); | ||
362 | return_VALUE(!ACPI_FAILURE(status)); | ||
363 | } | ||
364 | |||
365 | static int is_valid_hotkey(union acpi_hotkey *device) | ||
366 | { | ||
367 | ACPI_FUNCTION_TRACE("is_valid_hotkey"); | ||
368 | /* Implement valid check */ | ||
369 | return_VALUE(1); | ||
370 | } | ||
371 | |||
372 | static int hotkey_add(union acpi_hotkey *device) | ||
373 | { | ||
374 | int status = 0; | ||
375 | struct acpi_device *dev = NULL; | ||
376 | |||
377 | ACPI_FUNCTION_TRACE("hotkey_add"); | ||
378 | |||
379 | if (device->link.hotkey_type == ACPI_HOTKEY_EVENT) { | ||
380 | status = | ||
381 | acpi_bus_get_device(device->event_hotkey.bus_handle, &dev); | ||
382 | if (status) | ||
383 | return_VALUE(status); | ||
384 | |||
385 | status = acpi_install_notify_handler(dev->handle, | ||
386 | ACPI_SYSTEM_NOTIFY, | ||
387 | acpi_hotkey_notify_handler, | ||
388 | device); | ||
389 | } else /* Add polling hotkey */ | ||
390 | create_polling_proc(device); | ||
391 | |||
392 | global_hotkey_list.count++; | ||
393 | |||
394 | list_add_tail(&device->link.entries, global_hotkey_list.entries); | ||
395 | |||
396 | return_VALUE(status); | ||
397 | } | ||
398 | |||
399 | static int hotkey_remove(union acpi_hotkey *device) | ||
400 | { | ||
401 | struct list_head *entries, *next; | ||
402 | |||
403 | ACPI_FUNCTION_TRACE("hotkey_remove"); | ||
404 | |||
405 | list_for_each_safe(entries, next, global_hotkey_list.entries) { | ||
406 | union acpi_hotkey *key = | ||
407 | container_of(entries, union acpi_hotkey, entries); | ||
408 | if (key->link.hotkey_standard_num == | ||
409 | device->link.hotkey_standard_num) { | ||
410 | list_del(&key->link.entries); | ||
411 | remove_proc_entry(key->poll_hotkey.action_method, | ||
412 | hotkey_proc_dir); | ||
413 | global_hotkey_list.count--; | ||
414 | break; | ||
415 | } | ||
416 | } | ||
417 | return_VALUE(0); | ||
418 | } | ||
419 | |||
420 | static void hotkey_update(union acpi_hotkey *key) | ||
421 | { | ||
422 | struct list_head *entries, *next; | ||
423 | |||
424 | ACPI_FUNCTION_TRACE("hotkey_update"); | ||
425 | |||
426 | list_for_each_safe(entries, next, global_hotkey_list.entries) { | ||
427 | union acpi_hotkey *key = | ||
428 | container_of(entries, union acpi_hotkey, entries); | ||
429 | if (key->link.hotkey_standard_num == | ||
430 | key->link.hotkey_standard_num) { | ||
431 | key->event_hotkey.bus_handle = | ||
432 | key->event_hotkey.bus_handle; | ||
433 | key->event_hotkey.external_hotkey_num = | ||
434 | key->event_hotkey.external_hotkey_num; | ||
435 | key->event_hotkey.action_handle = | ||
436 | key->event_hotkey.action_handle; | ||
437 | key->event_hotkey.action_method = | ||
438 | key->event_hotkey.action_method; | ||
439 | break; | ||
440 | } | ||
441 | } | ||
442 | |||
443 | return_VOID; | ||
444 | } | ||
445 | |||
446 | static void free_hotkey_device(union acpi_hotkey *key) | ||
447 | { | ||
448 | struct acpi_device *dev; | ||
449 | int status; | ||
450 | |||
451 | ACPI_FUNCTION_TRACE("free_hotkey_device"); | ||
452 | |||
453 | if (key->link.hotkey_type == ACPI_HOTKEY_EVENT) { | ||
454 | status = | ||
455 | acpi_bus_get_device(key->event_hotkey.bus_handle, &dev); | ||
456 | if (dev->handle) | ||
457 | acpi_remove_notify_handler(dev->handle, | ||
458 | ACPI_SYSTEM_NOTIFY, | ||
459 | acpi_hotkey_notify_handler); | ||
460 | } else | ||
461 | remove_proc_entry(key->poll_hotkey.action_method, | ||
462 | hotkey_proc_dir); | ||
463 | kfree(key); | ||
464 | return_VOID; | ||
465 | } | ||
466 | |||
467 | static int | ||
468 | init_hotkey_device(union acpi_hotkey *key, char *bus_str, char *action_str, | ||
469 | char *method, int std_num, int external_num) | ||
470 | { | ||
471 | ACPI_FUNCTION_TRACE("init_hotkey_device"); | ||
472 | |||
473 | key->link.hotkey_type = ACPI_HOTKEY_EVENT; | ||
474 | key->link.hotkey_standard_num = std_num; | ||
475 | key->event_hotkey.flag = 0; | ||
476 | if (is_valid_acpi_path(bus_str)) | ||
477 | acpi_get_handle((acpi_handle) 0, | ||
478 | bus_str, &(key->event_hotkey.bus_handle)); | ||
479 | else | ||
480 | return_VALUE(-ENODEV); | ||
481 | key->event_hotkey.external_hotkey_num = external_num; | ||
482 | if (is_valid_acpi_path(action_str)) | ||
483 | acpi_get_handle((acpi_handle) 0, | ||
484 | action_str, &(key->event_hotkey.action_handle)); | ||
485 | key->event_hotkey.action_method = kmalloc(sizeof(method), GFP_KERNEL); | ||
486 | strcpy(key->event_hotkey.action_method, method); | ||
487 | |||
488 | return_VALUE(!is_valid_hotkey(key)); | ||
489 | } | ||
490 | |||
491 | static int | ||
492 | init_poll_hotkey_device(union acpi_hotkey *key, | ||
493 | char *poll_str, | ||
494 | char *poll_method, | ||
495 | char *action_str, char *action_method, int std_num) | ||
496 | { | ||
497 | ACPI_FUNCTION_TRACE("init_poll_hotkey_device"); | ||
498 | |||
499 | key->link.hotkey_type = ACPI_HOTKEY_POLLING; | ||
500 | key->link.hotkey_standard_num = std_num; | ||
501 | key->poll_hotkey.flag = 0; | ||
502 | if (is_valid_acpi_path(poll_str)) | ||
503 | acpi_get_handle((acpi_handle) 0, | ||
504 | poll_str, &(key->poll_hotkey.poll_handle)); | ||
505 | else | ||
506 | return_VALUE(-ENODEV); | ||
507 | key->poll_hotkey.poll_method = poll_method; | ||
508 | if (is_valid_acpi_path(action_str)) | ||
509 | acpi_get_handle((acpi_handle) 0, | ||
510 | action_str, &(key->poll_hotkey.action_handle)); | ||
511 | key->poll_hotkey.action_method = | ||
512 | kmalloc(sizeof(action_method), GFP_KERNEL); | ||
513 | strcpy(key->poll_hotkey.action_method, action_method); | ||
514 | key->poll_hotkey.poll_result = | ||
515 | (union acpi_object *)kmalloc(sizeof(union acpi_object), GFP_KERNEL); | ||
516 | return_VALUE(is_valid_hotkey(key)); | ||
517 | } | ||
518 | |||
519 | static int check_hotkey_valid(union acpi_hotkey *key, | ||
520 | struct acpi_hotkey_list *list) | ||
521 | { | ||
522 | ACPI_FUNCTION_TRACE("check_hotkey_valid"); | ||
523 | return_VALUE(0); | ||
524 | } | ||
525 | |||
526 | static int hotkey_open_config(struct inode *inode, struct file *file) | ||
527 | { | ||
528 | ACPI_FUNCTION_TRACE("hotkey_open_config"); | ||
529 | return_VALUE(single_open | ||
530 | (file, hotkey_config_seq_show, PDE(inode)->data)); | ||
531 | } | ||
532 | |||
533 | static int hotkey_config_seq_show(struct seq_file *seq, void *offset) | ||
534 | { | ||
535 | struct acpi_hotkey_list *hotkey_list = &global_hotkey_list; | ||
536 | struct list_head *entries, *next; | ||
537 | char bus_name[ACPI_PATHNAME_MAX] = { 0 }; | ||
538 | char action_name[ACPI_PATHNAME_MAX] = { 0 }; | ||
539 | struct acpi_buffer bus = { ACPI_PATHNAME_MAX, bus_name }; | ||
540 | struct acpi_buffer act = { ACPI_PATHNAME_MAX, action_name }; | ||
541 | |||
542 | ACPI_FUNCTION_TRACE(("hotkey_config_seq_show")); | ||
543 | |||
544 | if (!hotkey_list) | ||
545 | goto end; | ||
546 | |||
547 | list_for_each_safe(entries, next, hotkey_list->entries) { | ||
548 | union acpi_hotkey *key = | ||
549 | container_of(entries, union acpi_hotkey, entries); | ||
550 | if (key->link.hotkey_type == ACPI_HOTKEY_EVENT) { | ||
551 | acpi_get_name(key->event_hotkey.bus_handle, | ||
552 | ACPI_NAME_TYPE_MAX, &bus); | ||
553 | acpi_get_name(key->event_hotkey.action_handle, | ||
554 | ACPI_NAME_TYPE_MAX, &act); | ||
555 | seq_printf(seq, "%s:%s:%s:%d:%d", bus_name, | ||
556 | action_name, | ||
557 | key->event_hotkey.action_method, | ||
558 | key->link.hotkey_standard_num, | ||
559 | key->event_hotkey.external_hotkey_num); | ||
560 | } /* ACPI_HOTKEY_POLLING */ | ||
561 | else { | ||
562 | acpi_get_name(key->poll_hotkey.poll_handle, | ||
563 | ACPI_NAME_TYPE_MAX, &bus); | ||
564 | acpi_get_name(key->poll_hotkey.action_handle, | ||
565 | ACPI_NAME_TYPE_MAX, &act); | ||
566 | seq_printf(seq, "%s:%s:%s:%s:%d", bus_name, | ||
567 | key->poll_hotkey.poll_method, | ||
568 | action_name, | ||
569 | key->poll_hotkey.action_method, | ||
570 | key->link.hotkey_standard_num); | ||
571 | } | ||
572 | } | ||
573 | seq_puts(seq, "\n"); | ||
574 | end: | ||
575 | return_VALUE(0); | ||
576 | } | ||
577 | |||
578 | static int | ||
579 | get_parms(char *config_record, | ||
580 | int *cmd, | ||
581 | char *bus_handle, | ||
582 | char *bus_method, | ||
583 | char *action_handle, | ||
584 | char *method, int *internal_event_num, int *external_event_num) | ||
585 | { | ||
586 | char *tmp, *tmp1; | ||
587 | ACPI_FUNCTION_TRACE(("get_parms")); | ||
588 | |||
589 | sscanf(config_record, "%d", cmd); | ||
590 | |||
591 | tmp = strchr(config_record, ':'); | ||
592 | tmp++; | ||
593 | tmp1 = strchr(tmp, ':'); | ||
594 | strncpy(bus_handle, tmp, tmp1 - tmp); | ||
595 | bus_handle[tmp1 - tmp] = 0; | ||
596 | |||
597 | tmp = tmp1; | ||
598 | tmp++; | ||
599 | tmp1 = strchr(tmp, ':'); | ||
600 | strncpy(bus_method, tmp, tmp1 - tmp); | ||
601 | bus_method[tmp1 - tmp] = 0; | ||
602 | |||
603 | tmp = tmp1; | ||
604 | tmp++; | ||
605 | tmp1 = strchr(tmp, ':'); | ||
606 | strncpy(action_handle, tmp, tmp1 - tmp); | ||
607 | action_handle[tmp1 - tmp] = 0; | ||
608 | |||
609 | tmp = tmp1; | ||
610 | tmp++; | ||
611 | tmp1 = strchr(tmp, ':'); | ||
612 | strncpy(method, tmp, tmp1 - tmp); | ||
613 | method[tmp1 - tmp] = 0; | ||
614 | |||
615 | sscanf(tmp1 + 1, "%d:%d", internal_event_num, external_event_num); | ||
616 | return_VALUE(6); | ||
617 | } | ||
618 | |||
619 | /* count is length for one input record */ | ||
620 | static ssize_t hotkey_write_config(struct file *file, | ||
621 | const char __user * buffer, | ||
622 | size_t count, loff_t * data) | ||
623 | { | ||
624 | struct acpi_hotkey_list *hotkey_list = &global_hotkey_list; | ||
625 | char config_record[MAX_CONFIG_RECORD_LEN]; | ||
626 | char bus_handle[MAX_NAME_PATH_LEN]; | ||
627 | char bus_method[MAX_NAME_PATH_LEN]; | ||
628 | char action_handle[MAX_NAME_PATH_LEN]; | ||
629 | char method[20]; | ||
630 | int cmd, internal_event_num, external_event_num; | ||
631 | int ret = 0; | ||
632 | union acpi_hotkey *key = NULL; | ||
633 | |||
634 | ACPI_FUNCTION_TRACE(("hotkey_write_config")); | ||
635 | |||
636 | if (!hotkey_list || count > MAX_CONFIG_RECORD_LEN) { | ||
637 | ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Invalid arguments\n")); | ||
638 | return_VALUE(-EINVAL); | ||
639 | } | ||
640 | |||
641 | if (copy_from_user(config_record, buffer, count)) { | ||
642 | ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Invalid data \n")); | ||
643 | return_VALUE(-EINVAL); | ||
644 | } | ||
645 | config_record[count] = '\0'; | ||
646 | |||
647 | ret = get_parms(config_record, | ||
648 | &cmd, | ||
649 | bus_handle, | ||
650 | bus_method, | ||
651 | action_handle, | ||
652 | method, &internal_event_num, &external_event_num); | ||
653 | if (ret != 6) { | ||
654 | ACPI_DEBUG_PRINT((ACPI_DB_ERROR, | ||
655 | "Invalid data format ret=%d\n", ret)); | ||
656 | return_VALUE(-EINVAL); | ||
657 | } | ||
658 | |||
659 | key = kmalloc(sizeof(union acpi_hotkey), GFP_KERNEL); | ||
660 | ret = init_hotkey_device(key, bus_handle, action_handle, method, | ||
661 | internal_event_num, external_event_num); | ||
662 | |||
663 | if (ret || check_hotkey_valid(key, hotkey_list)) { | ||
664 | kfree(key); | ||
665 | ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Invalid hotkey \n")); | ||
666 | return_VALUE(-EINVAL); | ||
667 | } | ||
668 | switch (cmd) { | ||
669 | case 0: | ||
670 | hotkey_add(key); | ||
671 | break; | ||
672 | case 1: | ||
673 | hotkey_remove(key); | ||
674 | free_hotkey_device(key); | ||
675 | break; | ||
676 | case 2: | ||
677 | hotkey_update(key); | ||
678 | break; | ||
679 | default: | ||
680 | break; | ||
681 | } | ||
682 | return_VALUE(count); | ||
683 | } | ||
684 | |||
685 | /* count is length for one input record */ | ||
686 | static ssize_t hotkey_write_poll_config(struct file *file, | ||
687 | const char __user * buffer, | ||
688 | size_t count, loff_t * data) | ||
689 | { | ||
690 | struct seq_file *m = (struct seq_file *)file->private_data; | ||
691 | struct acpi_hotkey_list *hotkey_list = | ||
692 | (struct acpi_hotkey_list *)m->private; | ||
693 | |||
694 | char config_record[MAX_CONFIG_RECORD_LEN]; | ||
695 | char polling_handle[MAX_NAME_PATH_LEN]; | ||
696 | char action_handle[MAX_NAME_PATH_LEN]; | ||
697 | char poll_method[20], action_method[20]; | ||
698 | int ret, internal_event_num, cmd, external_event_num; | ||
699 | union acpi_hotkey *key = NULL; | ||
700 | |||
701 | ACPI_FUNCTION_TRACE("hotkey_write_poll_config"); | ||
702 | |||
703 | if (!hotkey_list || count > MAX_CONFIG_RECORD_LEN) { | ||
704 | ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Invalid arguments\n")); | ||
705 | return_VALUE(-EINVAL); | ||
706 | } | ||
707 | |||
708 | if (copy_from_user(config_record, buffer, count)) { | ||
709 | ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Invalid data \n")); | ||
710 | return_VALUE(-EINVAL); | ||
711 | } | ||
712 | config_record[count] = '\0'; | ||
713 | |||
714 | ret = get_parms(config_record, | ||
715 | &cmd, | ||
716 | polling_handle, | ||
717 | poll_method, | ||
718 | action_handle, | ||
719 | action_method, | ||
720 | &internal_event_num, &external_event_num); | ||
721 | |||
722 | if (ret != 6) { | ||
723 | ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Invalid data format\n")); | ||
724 | return_VALUE(-EINVAL); | ||
725 | } | ||
726 | |||
727 | key = kmalloc(sizeof(union acpi_hotkey), GFP_KERNEL); | ||
728 | ret = init_poll_hotkey_device(key, polling_handle, poll_method, | ||
729 | action_handle, action_method, | ||
730 | internal_event_num); | ||
731 | if (ret || check_hotkey_valid(key, hotkey_list)) { | ||
732 | kfree(key); | ||
733 | ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Invalid hotkey \n")); | ||
734 | return_VALUE(-EINVAL); | ||
735 | } | ||
736 | switch (cmd) { | ||
737 | case 0: | ||
738 | hotkey_add(key); | ||
739 | break; | ||
740 | case 1: | ||
741 | hotkey_remove(key); | ||
742 | break; | ||
743 | case 2: | ||
744 | hotkey_update(key); | ||
745 | break; | ||
746 | default: | ||
747 | break; | ||
748 | } | ||
749 | return_VALUE(count); | ||
750 | } | ||
751 | |||
752 | /* | ||
753 | * This function evaluates an ACPI method, given an int as parameter, the | ||
754 | * method is searched within the scope of the handle, can be NULL. The output | ||
755 | * of the method is written is output, which can also be NULL | ||
756 | * | ||
757 | * returns 1 if write is successful, 0 else. | ||
758 | */ | ||
759 | static int write_acpi_int(acpi_handle handle, const char *method, int val, | ||
760 | struct acpi_buffer *output) | ||
761 | { | ||
762 | struct acpi_object_list params; /* list of input parameters (an int here) */ | ||
763 | union acpi_object in_obj; /* the only param we use */ | ||
764 | acpi_status status; | ||
765 | |||
766 | ACPI_FUNCTION_TRACE("write_acpi_int"); | ||
767 | params.count = 1; | ||
768 | params.pointer = &in_obj; | ||
769 | in_obj.type = ACPI_TYPE_INTEGER; | ||
770 | in_obj.integer.value = val; | ||
771 | |||
772 | status = acpi_evaluate_object(handle, (char *)method, ¶ms, output); | ||
773 | |||
774 | return_VALUE(status == AE_OK); | ||
775 | } | ||
776 | |||
777 | static int read_acpi_int(acpi_handle handle, const char *method, int *val) | ||
778 | { | ||
779 | struct acpi_buffer output; | ||
780 | union acpi_object out_obj; | ||
781 | acpi_status status; | ||
782 | |||
783 | ACPI_FUNCTION_TRACE("read_acpi_int"); | ||
784 | output.length = sizeof(out_obj); | ||
785 | output.pointer = &out_obj; | ||
786 | |||
787 | status = acpi_evaluate_object(handle, (char *)method, NULL, &output); | ||
788 | *val = out_obj.integer.value; | ||
789 | return_VALUE((status == AE_OK) | ||
790 | && (out_obj.type == ACPI_TYPE_INTEGER)); | ||
791 | } | ||
792 | |||
793 | static acpi_handle | ||
794 | get_handle_from_hotkeylist(struct acpi_hotkey_list *hotkey_list, int event_num) | ||
795 | { | ||
796 | struct list_head *entries, *next; | ||
797 | |||
798 | list_for_each_safe(entries, next, hotkey_list->entries) { | ||
799 | union acpi_hotkey *key = | ||
800 | container_of(entries, union acpi_hotkey, entries); | ||
801 | if (key->link.hotkey_type == ACPI_HOTKEY_EVENT | ||
802 | && key->link.hotkey_standard_num == event_num) { | ||
803 | return (key->event_hotkey.action_handle); | ||
804 | } | ||
805 | } | ||
806 | return (NULL); | ||
807 | } | ||
808 | |||
809 | static | ||
810 | char *get_method_from_hotkeylist(struct acpi_hotkey_list *hotkey_list, | ||
811 | int event_num) | ||
812 | { | ||
813 | struct list_head *entries, *next; | ||
814 | |||
815 | list_for_each_safe(entries, next, hotkey_list->entries) { | ||
816 | union acpi_hotkey *key = | ||
817 | container_of(entries, union acpi_hotkey, entries); | ||
818 | |||
819 | if (key->link.hotkey_type == ACPI_HOTKEY_EVENT && | ||
820 | key->link.hotkey_standard_num == event_num) | ||
821 | return (key->event_hotkey.action_method); | ||
822 | } | ||
823 | return (NULL); | ||
824 | } | ||
825 | |||
826 | static struct acpi_polling_hotkey *get_hotkey_by_event(struct | ||
827 | acpi_hotkey_list | ||
828 | *hotkey_list, int event) | ||
829 | { | ||
830 | struct list_head *entries, *next; | ||
831 | |||
832 | list_for_each_safe(entries, next, hotkey_list->entries) { | ||
833 | union acpi_hotkey *key = | ||
834 | container_of(entries, union acpi_hotkey, entries); | ||
835 | if (key->link.hotkey_type == ACPI_HOTKEY_POLLING | ||
836 | && key->link.hotkey_standard_num == event) { | ||
837 | return (&key->poll_hotkey); | ||
838 | } | ||
839 | } | ||
840 | return (NULL); | ||
841 | } | ||
842 | |||
843 | /* | ||
844 | * user call AML method interface: | ||
845 | * Call convention: | ||
846 | * echo "event_num: arg type : value" | ||
847 | * example: echo "1:1:30" > /proc/acpi/action | ||
848 | * Just support 1 integer arg passing to AML method | ||
849 | */ | ||
850 | |||
851 | static ssize_t hotkey_execute_aml_method(struct file *file, | ||
852 | const char __user * buffer, | ||
853 | size_t count, loff_t * data) | ||
854 | { | ||
855 | struct acpi_hotkey_list *hotkey_list = &global_hotkey_list; | ||
856 | char arg[MAX_CALL_PARM]; | ||
857 | int event, type, value; | ||
858 | |||
859 | char *method; | ||
860 | acpi_handle handle; | ||
861 | |||
862 | ACPI_FUNCTION_TRACE("hotkey_execte_aml_method"); | ||
863 | |||
864 | if (!hotkey_list || count > MAX_CALL_PARM) { | ||
865 | ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Invalid argument 1")); | ||
866 | return_VALUE(-EINVAL); | ||
867 | } | ||
868 | |||
869 | if (copy_from_user(arg, buffer, count)) { | ||
870 | ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Invalid argument 2")); | ||
871 | return_VALUE(-EINVAL); | ||
872 | } | ||
873 | |||
874 | arg[count] = '\0'; | ||
875 | |||
876 | if (sscanf(arg, "%d:%d:%d", &event, &type, &value) != 3) { | ||
877 | ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Invalid argument 3")); | ||
878 | return_VALUE(-EINVAL); | ||
879 | } | ||
880 | |||
881 | if (type == ACPI_TYPE_INTEGER) { | ||
882 | handle = get_handle_from_hotkeylist(hotkey_list, event); | ||
883 | method = (char *)get_method_from_hotkeylist(hotkey_list, event); | ||
884 | if (IS_EVENT(event)) | ||
885 | write_acpi_int(handle, method, value, NULL); | ||
886 | else if (IS_POLL(event)) { | ||
887 | struct acpi_polling_hotkey *key; | ||
888 | key = (struct acpi_polling_hotkey *) | ||
889 | get_hotkey_by_event(hotkey_list, event); | ||
890 | read_acpi_int(handle, method, key->poll_result); | ||
891 | } | ||
892 | } else { | ||
893 | ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Not supported")); | ||
894 | return_VALUE(-EINVAL); | ||
895 | } | ||
896 | |||
897 | return_VALUE(count); | ||
898 | } | ||
899 | |||
900 | static int __init hotkey_init(void) | ||
901 | { | ||
902 | int result; | ||
903 | mode_t mode = S_IFREG | S_IRUGO | S_IWUGO; | ||
904 | |||
905 | ACPI_FUNCTION_TRACE("hotkey_init"); | ||
906 | |||
907 | if (acpi_disabled) | ||
908 | return -ENODEV; | ||
909 | |||
910 | if (acpi_specific_hotkey_enabled) { | ||
911 | printk("Using specific hotkey driver\n"); | ||
912 | return -ENODEV; | ||
913 | } | ||
914 | |||
915 | hotkey_proc_dir = proc_mkdir(HOTKEY_PROC, acpi_root_dir); | ||
916 | if (!hotkey_proc_dir) { | ||
917 | ACPI_DEBUG_PRINT((ACPI_DB_ERROR, | ||
918 | "Hotkey: Unable to create %s entry\n", | ||
919 | HOTKEY_PROC)); | ||
920 | return (-ENODEV); | ||
921 | } | ||
922 | hotkey_proc_dir->owner = THIS_MODULE; | ||
923 | |||
924 | hotkey_config = | ||
925 | create_proc_entry(HOTKEY_EV_CONFIG, mode, hotkey_proc_dir); | ||
926 | if (!hotkey_config) { | ||
927 | ACPI_DEBUG_PRINT((ACPI_DB_ERROR, | ||
928 | "Hotkey: Unable to create %s entry\n", | ||
929 | HOTKEY_EV_CONFIG)); | ||
930 | return (-ENODEV); | ||
931 | } else { | ||
932 | hotkey_config->proc_fops = &hotkey_config_fops; | ||
933 | hotkey_config->data = &global_hotkey_list; | ||
934 | hotkey_config->owner = THIS_MODULE; | ||
935 | hotkey_config->uid = 0; | ||
936 | hotkey_config->gid = 0; | ||
937 | } | ||
938 | |||
939 | hotkey_poll_config = | ||
940 | create_proc_entry(HOTKEY_PL_CONFIG, mode, hotkey_proc_dir); | ||
941 | if (!hotkey_poll_config) { | ||
942 | ACPI_DEBUG_PRINT((ACPI_DB_ERROR, | ||
943 | "Hotkey: Unable to create %s entry\n", | ||
944 | HOTKEY_EV_CONFIG)); | ||
945 | return (-ENODEV); | ||
946 | } else { | ||
947 | hotkey_poll_config->proc_fops = &hotkey_poll_config_fops; | ||
948 | hotkey_poll_config->data = &global_hotkey_list; | ||
949 | hotkey_poll_config->owner = THIS_MODULE; | ||
950 | hotkey_poll_config->uid = 0; | ||
951 | hotkey_poll_config->gid = 0; | ||
952 | } | ||
953 | |||
954 | hotkey_action = create_proc_entry(HOTKEY_ACTION, mode, hotkey_proc_dir); | ||
955 | if (!hotkey_action) { | ||
956 | ACPI_DEBUG_PRINT((ACPI_DB_ERROR, | ||
957 | "Hotkey: Unable to create %s entry\n", | ||
958 | HOTKEY_ACTION)); | ||
959 | return (-ENODEV); | ||
960 | } else { | ||
961 | hotkey_action->proc_fops = &hotkey_action_fops; | ||
962 | hotkey_action->owner = THIS_MODULE; | ||
963 | hotkey_action->uid = 0; | ||
964 | hotkey_action->gid = 0; | ||
965 | } | ||
966 | |||
967 | hotkey_info = create_proc_entry(HOTKEY_INFO, mode, hotkey_proc_dir); | ||
968 | if (!hotkey_info) { | ||
969 | ACPI_DEBUG_PRINT((ACPI_DB_ERROR, | ||
970 | "Hotkey: Unable to create %s entry\n", | ||
971 | HOTKEY_INFO)); | ||
972 | return (-ENODEV); | ||
973 | } else { | ||
974 | hotkey_info->proc_fops = &hotkey_info_fops; | ||
975 | hotkey_info->owner = THIS_MODULE; | ||
976 | hotkey_info->uid = 0; | ||
977 | hotkey_info->gid = 0; | ||
978 | } | ||
979 | |||
980 | result = acpi_bus_register_driver(&hotkey_driver); | ||
981 | if (result < 0) { | ||
982 | remove_proc_entry(HOTKEY_PROC, acpi_root_dir); | ||
983 | return (-ENODEV); | ||
984 | } | ||
985 | global_hotkey_list.count = 0; | ||
986 | global_hotkey_list.entries = &hotkey_entries; | ||
987 | |||
988 | INIT_LIST_HEAD(&hotkey_entries); | ||
989 | |||
990 | return (0); | ||
991 | } | ||
992 | |||
993 | static void __exit hotkey_exit(void) | ||
994 | { | ||
995 | struct list_head *entries, *next; | ||
996 | |||
997 | ACPI_FUNCTION_TRACE("hotkey_remove"); | ||
998 | |||
999 | list_for_each_safe(entries, next, global_hotkey_list.entries) { | ||
1000 | union acpi_hotkey *key = | ||
1001 | container_of(entries, union acpi_hotkey, entries); | ||
1002 | |||
1003 | acpi_os_wait_events_complete(NULL); | ||
1004 | list_del(&key->link.entries); | ||
1005 | global_hotkey_list.count--; | ||
1006 | free_hotkey_device(key); | ||
1007 | } | ||
1008 | acpi_bus_unregister_driver(&hotkey_driver); | ||
1009 | remove_proc_entry(HOTKEY_EV_CONFIG, hotkey_proc_dir); | ||
1010 | remove_proc_entry(HOTKEY_PL_CONFIG, hotkey_proc_dir); | ||
1011 | remove_proc_entry(HOTKEY_ACTION, hotkey_proc_dir); | ||
1012 | remove_proc_entry(HOTKEY_INFO, hotkey_proc_dir); | ||
1013 | remove_proc_entry(HOTKEY_PROC, acpi_root_dir); | ||
1014 | return; | ||
1015 | } | ||
1016 | |||
1017 | module_init(hotkey_init); | ||
1018 | module_exit(hotkey_exit); | ||