/* * hotkey.c - ACPI Hotkey Driver ($Revision: 0.2 $) * * Copyright (C) 2004 Luming Yu <luming.yu@intel.com> * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or (at * your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ #include <linux/kernel.h> #include <linux/module.h> #include <linux/init.h> #include <linux/types.h> #include <linux/proc_fs.h> #include <linux/sched.h> #include <linux/kmod.h> #include <linux/seq_file.h> #include <acpi/acpi_drivers.h> #include <acpi/acpi_bus.h> #include <asm/uaccess.h> #define HOTKEY_ACPI_VERSION "0.1" #define HOTKEY_PROC "hotkey" #define HOTKEY_EV_CONFIG "event_config" #define HOTKEY_PL_CONFIG "poll_config" #define HOTKEY_ACTION "action" #define HOTKEY_INFO "info" #define ACPI_HOTK_NAME "Generic Hotkey Driver" #define ACPI_HOTK_CLASS "Hotkey" #define ACPI_HOTK_DEVICE_NAME "Hotkey" #define ACPI_HOTK_HID "Unknown?" #define ACPI_HOTKEY_COMPONENT 0x20000000 #define ACPI_HOTKEY_EVENT 0x1 #define ACPI_HOTKEY_POLLING 0x2 #define ACPI_UNDEFINED_EVENT 0xf #define RESULT_STR_LEN 80 #define ACTION_METHOD 0 #define POLL_METHOD 1 #define IS_EVENT(e) ((e) <= 10000 && (e) >0) #define IS_POLL(e) ((e) > 10000) #define IS_OTHERS(e) ((e)<=0 || (e)>=20000) #define _COMPONENT ACPI_HOTKEY_COMPONENT ACPI_MODULE_NAME("acpi_hotkey") MODULE_AUTHOR("luming.yu@intel.com"); MODULE_DESCRIPTION(ACPI_HOTK_NAME); MODULE_LICENSE("GPL"); /* standardized internal hotkey number/event */ enum { /* Video Extension event */ HK_EVENT_CYCLE_OUTPUT_DEVICE = 0x80, HK_EVENT_OUTPUT_DEVICE_STATUS_CHANGE, HK_EVENT_CYCLE_DISPLAY_OUTPUT, HK_EVENT_NEXT_DISPLAY_OUTPUT, HK_EVENT_PREVIOUS_DISPLAY_OUTPUT, HK_EVENT_CYCLE_BRIGHTNESS, HK_EVENT_INCREASE_BRIGHTNESS, HK_EVENT_DECREASE_BRIGHTNESS, HK_EVENT_ZERO_BRIGHTNESS, HK_EVENT_DISPLAY_DEVICE_OFF, /* Snd Card event */ HK_EVENT_VOLUME_MUTE, HK_EVENT_VOLUME_INCLREASE, HK_EVENT_VOLUME_DECREASE, /* running state control */ HK_EVENT_ENTERRING_S3, HK_EVENT_ENTERRING_S4, HK_EVENT_ENTERRING_S5, }; /* procdir we use */ static struct proc_dir_entry *hotkey_proc_dir; static struct proc_dir_entry *hotkey_config; static struct proc_dir_entry *hotkey_poll_config; static struct proc_dir_entry *hotkey_action; static struct proc_dir_entry *hotkey_info; /* linkage for all type of hotkey */ struct acpi_hotkey_link { struct list_head entries; int hotkey_type; /* event or polling based hotkey */ int hotkey_standard_num; /* standardized hotkey(event) number */ }; /* event based hotkey */ struct acpi_event_hotkey { struct acpi_hotkey_link hotkey_link; int flag; acpi_handle bus_handle; /* bus to install notify handler */ int external_hotkey_num; /* external hotkey/event number */ acpi_handle action_handle; /* acpi handle attached aml action method */ char *action_method; /* action method */ }; /* * There are two ways to poll status * 1. directy call read_xxx method, without any arguments passed in * 2. call write_xxx method, with arguments passed in, you need * the result is saved in acpi_polling_hotkey.poll_result. * anthoer read command through polling interface. * */ /* polling based hotkey */ struct acpi_polling_hotkey { struct acpi_hotkey_link hotkey_link; int flag; acpi_handle poll_handle; /* acpi handle attached polling method */ char *poll_method; /* poll method */ acpi_handle action_handle; /* acpi handle attached action method */ char *action_method; /* action method */ union acpi_object *poll_result; /* polling_result */ struct proc_dir_entry *proc; }; /* hotkey object union */ union acpi_hotkey { struct list_head entries; struct acpi_hotkey_link link; struct acpi_event_hotkey event_hotkey; struct acpi_polling_hotkey poll_hotkey; }; /* hotkey object list */ struct acpi_hotkey_list { struct list_head *entries; int count; }; static int auto_hotkey_add(struct acpi_device *device); static int auto_hotkey_remove(struct acpi_device *device, int type); static struct acpi_driver hotkey_driver = { .name = ACPI_HOTK_NAME, .class = ACPI_HOTK_CLASS, .ids = ACPI_HOTK_HID, .ops = { .add = auto_hotkey_add, .remove = auto_hotkey_remove, }, }; static void free_hotkey_device(union acpi_hotkey *key); static void free_hotkey_buffer(union acpi_hotkey *key); static void free_poll_hotkey_buffer(union acpi_hotkey *key); static int hotkey_open_config(struct inode *inode, struct file *file); static int hotkey_poll_open_config(struct inode *inode, struct file *file); static ssize_t hotkey_write_config(struct file *file, const char __user * buffer, size_t count, loff_t * data); static int hotkey_info_open_fs(struct inode *inode, struct file *file); static int hotkey_action_open_fs(struct inode *inode, struct file *file); static ssize_t hotkey_execute_aml_method(struct file *file, const char __user * buffer, size_t count, loff_t * data); static int hotkey_config_seq_show(struct seq_file *seq, void *offset); static int hotkey_poll_config_seq_show(struct seq_file *seq, void *offset); static int hotkey_polling_open_fs(struct inode *inode, struct file *file); static union acpi_hotkey *get_hotkey_by_event(struct acpi_hotkey_list *hotkey_list, int event); /* event based config */ static struct file_operations hotkey_config_fops = { .open = hotkey_open_config, .read = seq_read, .write = hotkey_write_config, .llseek = seq_lseek, .release = single_release, }; /* polling based config */ static struct file_operations hotkey_poll_config_fops = { .open = hotkey_poll_open_config, .read = seq_read, .write = hotkey_write_config, .llseek = seq_lseek, .release = single_release, }; /* hotkey driver info */ static struct file_operations hotkey_info_fops = { .open = hotkey_info_open_fs, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; /* action */ static struct file_operations hotkey_action_fops = { .open = hotkey_action_open_fs, .read = seq_read, .write = hotkey_execute_aml_method, .llseek = seq_lseek, .release = single_release, }; /* polling results */ static struct file_operations hotkey_polling_fops = { .open = hotkey_polling_open_fs, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; struct acpi_hotkey_list global_hotkey_list; /* link all ev or pl hotkey */ struct list_head hotkey_entries; /* head of the list of hotkey_list */ static int hotkey_info_seq_show(struct seq_file *seq, void *offset) { ACPI_FUNCTION_TRACE("hotkey_info_seq_show"); seq_printf(seq, "Hotkey generic driver ver: %s\n", HOTKEY_ACPI_VERSION); return_VALUE(0); } static int hotkey_info_open_fs(struct inode *inode, struct file *file) { return single_open(file, hotkey_info_seq_show, PDE(inode)->data); } static char *format_result(union acpi_object *object) { char *buf = NULL; buf = (char *)kmalloc(RESULT_STR_LEN, GFP_KERNEL); if (buf) memset(buf, 0, RESULT_STR_LEN); else goto do_fail; /* Now, just support integer type */ if (object->type == ACPI_TYPE_INTEGER) sprintf(buf, "%d\n", (u32) object->integer.value); do_fail: return (buf); } static int hotkey_polling_seq_show(struct seq_file *seq, void *offset) { struct acpi_polling_hotkey *poll_hotkey = (struct acpi_polling_hotkey *)seq->private; char *buf; ACPI_FUNCTION_TRACE("hotkey_polling_seq_show"); if (poll_hotkey->poll_result) { buf = format_result(poll_hotkey->poll_result); if (buf) seq_printf(seq, "%s", buf); kfree(buf); } return_VALUE(0); } static int hotkey_polling_open_fs(struct inode *inode, struct file *file) { return single_open(file, hotkey_polling_seq_show, PDE(inode)->data); } static int hotkey_action_open_fs(struct inode *inode, struct file *file) { return single_open(file, hotkey_info_seq_show, PDE(inode)->data); } /* Mapping external hotkey number to standardized hotkey event num */ static int hotkey_get_internal_event(int event, struct acpi_hotkey_list *list) { struct list_head *entries; int val = -1; ACPI_FUNCTION_TRACE("hotkey_get_internal_event"); list_for_each(entries, list->entries) { union acpi_hotkey *key = container_of(entries, union acpi_hotkey, entries); if (key->link.hotkey_type == ACPI_HOTKEY_EVENT && key->event_hotkey.external_hotkey_num == event) { val = key->link.hotkey_standard_num; break; } } return_VALUE(val); } static void acpi_hotkey_notify_handler(acpi_handle handle, u32 event, void *data) { struct acpi_device *device = NULL; u32 internal_event; ACPI_FUNCTION_TRACE("acpi_hotkey_notify_handler"); if (acpi_bus_get_device(handle, &device)) return_VOID; internal_event = hotkey_get_internal_event(event, &global_hotkey_list); acpi_bus_generate_event(device, internal_event, 0); return_VOID; } /* Need to invent automatically hotkey add method */ static int auto_hotkey_add(struct acpi_device *device) { /* Implement me */ return 0; } /* Need to invent automatically hotkey remove method */ static int auto_hotkey_remove(struct acpi_device *device, int type) { /* Implement me */ return 0; } /* Create a proc file for each polling method */ static int create_polling_proc(union acpi_hotkey *device) { struct proc_dir_entry *proc; char proc_name[80]; mode_t mode; ACPI_FUNCTION_TRACE("create_polling_proc"); mode = S_IFREG | S_IRUGO | S_IWUGO; sprintf(proc_name, "%d", device->link.hotkey_standard_num); /* strcat(proc_name, device->poll_hotkey.poll_method); */ proc = create_proc_entry(proc_name, mode, hotkey_proc_dir); if (!proc) { ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Hotkey: Unable to create %s entry\n", device->poll_hotkey.poll_method)); return_VALUE(-ENODEV); } else { proc->proc_fops = &hotkey_polling_fops; proc->owner = THIS_MODULE; proc->data = device; proc->uid = 0; proc->gid = 0; device->poll_hotkey.proc = proc; } return_VALUE(0); } static int hotkey_add(union acpi_hotkey *device) { int status = 0; struct acpi_device *dev = NULL; ACPI_FUNCTION_TRACE("hotkey_add"); if (device->link.hotkey_type == ACPI_HOTKEY_EVENT) { acpi_bus_get_device(device->event_hotkey.bus_handle, &dev); status = acpi_install_notify_handler(dev->handle, ACPI_DEVICE_NOTIFY, acpi_hotkey_notify_handler, dev); } else /* Add polling hotkey */ create_polling_proc(device); global_hotkey_list.count++; list_add_tail(&device->link.entries, global_hotkey_list.entries); return_VALUE(status); } static int hotkey_remove(union acpi_hotkey *device) { struct list_head *entries, *next; ACPI_FUNCTION_TRACE("hotkey_remove"); list_for_each_safe(entries, next, global_hotkey_list.entries) { union acpi_hotkey *key = container_of(entries, union acpi_hotkey, entries); if (key->link.hotkey_standard_num == device->link.hotkey_standard_num) { list_del(&key->link.entries); free_hotkey_device(key); global_hotkey_list.count--; break; } } kfree(device); return_VALUE(0); } static int hotkey_update(union acpi_hotkey *key) { struct list_head *entries; ACPI_FUNCTION_TRACE("hotkey_update"); list_for_each(entries, global_hotkey_list.entries) { union acpi_hotkey *tmp = container_of(entries, union acpi_hotkey, entries); if (tmp->link.hotkey_standard_num == key->link.hotkey_standard_num) { if (key->link.hotkey_type == ACPI_HOTKEY_EVENT) { free_hotkey_buffer(tmp); tmp->event_hotkey.bus_handle = key->event_hotkey.bus_handle; tmp->event_hotkey.external_hotkey_num = key->event_hotkey.external_hotkey_num; tmp->event_hotkey.action_handle = key->event_hotkey.action_handle; tmp->event_hotkey.action_method = key->event_hotkey.action_method; kfree(key); } else { /* char proc_name[80]; sprintf(proc_name, "%d", tmp->link.hotkey_standard_num); strcat(proc_name, tmp->poll_hotkey.poll_method); remove_proc_entry(proc_name,hotkey_proc_dir); */ free_poll_hotkey_buffer(tmp); tmp->poll_hotkey.poll_handle = key->poll_hotkey.poll_handle; tmp->poll_hotkey.poll_method = key->poll_hotkey.poll_method; tmp->poll_hotkey.action_handle = key->poll_hotkey.action_handle; tmp->poll_hotkey.action_method = key->poll_hotkey.action_method; tmp->poll_hotkey.poll_result = key->poll_hotkey.poll_result; /* create_polling_proc(tmp); */ kfree(key); } return_VALUE(0); break; } } return_VALUE(-ENODEV); } static void free_hotkey_device(union acpi_hotkey *key) { struct acpi_device *dev; ACPI_FUNCTION_TRACE("free_hotkey_device"); if (key->link.hotkey_type == ACPI_HOTKEY_EVENT) { acpi_bus_get_device(key->event_hotkey.bus_handle, &dev); if (dev->handle) acpi_remove_notify_handler(dev->handle, ACPI_DEVICE_NOTIFY, acpi_hotkey_notify_handler); free_hotkey_buffer(key); } else { char proc_name[80]; sprintf(proc_name, "%d", key->link.hotkey_standard_num); /* strcat(proc_name, key->poll_hotkey.poll_method); */ remove_proc_entry(proc_name, hotkey_proc_dir); free_poll_hotkey_buffer(key); } kfree(key); return_VOID; } static void free_hotkey_buffer(union acpi_hotkey *key) { kfree(key->event_hotkey.action_method); } static void free_poll_hotkey_buffer(union acpi_hotkey *key) { kfree(key->poll_hotkey.action_method); kfree(key->poll_hotkey.poll_method); kfree(key->poll_hotkey.poll_result); } static int init_hotkey_device(union acpi_hotkey *key, char *bus_str, char *action_str, char *method, int std_num, int external_num) { acpi_handle tmp_handle; acpi_status status = AE_OK; ACPI_FUNCTION_TRACE("init_hotkey_device"); if (std_num < 0 || IS_POLL(std_num) || !key) goto do_fail; if (!bus_str || !action_str || !method) goto do_fail; key->link.hotkey_type = ACPI_HOTKEY_EVENT; key->link.hotkey_standard_num = std_num; key->event_hotkey.flag = 0; key->event_hotkey.action_method = method; status = acpi_get_handle(NULL, bus_str, &(key->event_hotkey.bus_handle)); if (ACPI_FAILURE(status)) goto do_fail; key->event_hotkey.external_hotkey_num = external_num; status = acpi_get_handle(NULL, action_str, &(key->event_hotkey.action_handle)); if (ACPI_FAILURE(status)) goto do_fail; status = acpi_get_handle(key->event_hotkey.action_handle, method, &tmp_handle); if (ACPI_FAILURE(status)) goto do_fail; return_VALUE(AE_OK); do_fail: return_VALUE(-ENODEV); } static int init_poll_hotkey_device(union acpi_hotkey *key, char *poll_str, char *poll_method, char *action_str, char *action_method, int std_num) { acpi_status status = AE_OK; acpi_handle tmp_handle; ACPI_FUNCTION_TRACE("init_poll_hotkey_device"); if (std_num < 0 || IS_EVENT(std_num) || !key) goto do_fail; if (!poll_str || !poll_method || !action_str || !action_method) goto do_fail; key->link.hotkey_type = ACPI_HOTKEY_POLLING; key->link.hotkey_standard_num = std_num; key->poll_hotkey.flag = 0; key->poll_hotkey.poll_method = poll_method; key->poll_hotkey.action_method = action_method; status = acpi_get_handle(NULL, poll_str, &(key->poll_hotkey.poll_handle)); if (ACPI_FAILURE(status)) goto do_fail; status = acpi_get_handle(key->poll_hotkey.poll_handle, poll_method, &tmp_handle); if (ACPI_FAILURE(status)) goto do_fail; status = acpi_get_handle(NULL, action_str, &(key->poll_hotkey.action_handle)); if (ACPI_FAILURE(status)) goto do_fail; status = acpi_get_handle(key->poll_hotkey.action_handle, action_method, &tmp_handle); if (ACPI_FAILURE(status)) goto do_fail; key->poll_hotkey.poll_result = (union acpi_object *)kmalloc(sizeof(union acpi_object), GFP_KERNEL); if (!key->poll_hotkey.poll_result) goto do_fail; return_VALUE(AE_OK); do_fail: return_VALUE(-ENODEV); } static int hotkey_open_config(struct inode *inode, struct file *file) { ACPI_FUNCTION_TRACE("hotkey_open_config"); return_VALUE(single_open (file, hotkey_config_seq_show, PDE(inode)->data)); } static int hotkey_poll_open_config(struct inode *inode, struct file *file) { ACPI_FUNCTION_TRACE("hotkey_poll_open_config"); return_VALUE(single_open (file, hotkey_poll_config_seq_show, PDE(inode)->data)); } static int hotkey_config_seq_show(struct seq_file *seq, void *offset) { struct acpi_hotkey_list *hotkey_list = &global_hotkey_list; struct list_head *entries; char bus_name[ACPI_PATHNAME_MAX] = { 0 }; char action_name[ACPI_PATHNAME_MAX] = { 0 }; struct acpi_buffer bus = { ACPI_PATHNAME_MAX, bus_name }; struct acpi_buffer act = { ACPI_PATHNAME_MAX, action_name }; ACPI_FUNCTION_TRACE(("hotkey_config_seq_show")); list_for_each(entries, hotkey_list->entries) { union acpi_hotkey *key = container_of(entries, union acpi_hotkey, entries); if (key->link.hotkey_type == ACPI_HOTKEY_EVENT) { acpi_get_name(key->event_hotkey.bus_handle, ACPI_NAME_TYPE_MAX, &bus); acpi_get_name(key->event_hotkey.action_handle, ACPI_NAME_TYPE_MAX, &act); seq_printf(seq, "%s:%s:%s:%d:%d\n", bus_name, action_name, key->event_hotkey.action_method, key->link.hotkey_standard_num, key->event_hotkey.external_hotkey_num); } } seq_puts(seq, "\n"); return_VALUE(0); } static int hotkey_poll_config_seq_show(struct seq_file *seq, void *offset) { struct acpi_hotkey_list *hotkey_list = &global_hotkey_list; struct list_head *entries; char bus_name[ACPI_PATHNAME_MAX] = { 0 }; char action_name[ACPI_PATHNAME_MAX] = { 0 }; struct acpi_buffer bus = { ACPI_PATHNAME_MAX, bus_name }; struct acpi_buffer act = { ACPI_PATHNAME_MAX, action_name }; ACPI_FUNCTION_TRACE(("hotkey_config_seq_show")); list_for_each(entries, hotkey_list->entries) { union acpi_hotkey *key = container_of(entries, union acpi_hotkey, entries); if (key->link.hotkey_type == ACPI_HOTKEY_POLLING) { acpi_get_name(key->poll_hotkey.poll_handle, ACPI_NAME_TYPE_MAX, &bus); acpi_get_name(key->poll_hotkey.action_handle, ACPI_NAME_TYPE_MAX, &act); seq_printf(seq, "%s:%s:%s:%s:%d\n", bus_name, key->poll_hotkey.poll_method, action_name, key->poll_hotkey.action_method, key->link.hotkey_standard_num); } } seq_puts(seq, "\n"); return_VALUE(0); } static int get_parms(char *config_record, int *cmd, char **bus_handle, char **bus_method, char **action_handle, char **method, int *internal_event_num, int *external_event_num) { char *tmp, *tmp1, count; ACPI_FUNCTION_TRACE(("get_parms")); sscanf(config_record, "%d", cmd); if (*cmd == 1) { if (sscanf(config_record, "%d:%d", cmd, internal_event_num) != 2) goto do_fail; else return (6); } tmp = strchr(config_record, ':'); if (!tmp) goto do_fail; tmp++; tmp1 = strchr(tmp, ':'); if (!tmp1) goto do_fail; count = tmp1 - tmp; *bus_handle = (char *)kmalloc(count + 1, GFP_KERNEL); if (!*bus_handle) goto do_fail; strncpy(*bus_handle, tmp, count); *(*bus_handle + count) = 0; tmp = tmp1; tmp++; tmp1 = strchr(tmp, ':'); if (!tmp1) goto do_fail; count = tmp1 - tmp; *bus_method = (char *)kmalloc(count + 1, GFP_KERNEL); if (!*bus_method) goto do_fail; strncpy(*bus_method, tmp, count); *(*bus_method + count) = 0; tmp = tmp1; tmp++; tmp1 = strchr(tmp, ':'); if (!tmp1) goto do_fail; count = tmp1 - tmp; *action_handle = (char *)kmalloc(count + 1, GFP_KERNEL); strncpy(*action_handle, tmp, count); *(*action_handle + count) = 0; tmp = tmp1; tmp++; tmp1 = strchr(tmp, ':'); if (!tmp1) goto do_fail; count = tmp1 - tmp; *method = (char *)kmalloc(count + 1, GFP_KERNEL); if (!*method) goto do_fail; strncpy(*method, tmp, count); *(*method + count) = 0; if (sscanf(tmp1 + 1, "%d:%d", internal_event_num, external_event_num) <= 0) goto do_fail; return_VALUE(6); do_fail: return_VALUE(-1); } /* count is length for one input record */ static ssize_t hotkey_write_config(struct file *file, const char __user * buffer, size_t count, loff_t * data) { char *config_record = NULL; char *bus_handle = NULL; char *bus_method = NULL; char *action_handle = NULL; char *method = NULL; int cmd, internal_event_num, external_event_num; int ret = 0; union acpi_hotkey *key = NULL; ACPI_FUNCTION_TRACE(("hotkey_write_config")); config_record = (char *)kmalloc(count + 1, GFP_KERNEL); if (!config_record) return_VALUE(-ENOMEM); if (copy_from_user(config_record, buffer, count)) { kfree(config_record); ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Invalid data \n")); return_VALUE(-EINVAL); } config_record[count] = 0; ret = get_parms(config_record, &cmd, &bus_handle, &bus_method, &action_handle, &method, &internal_event_num, &external_event_num); kfree(config_record); if (IS_OTHERS(internal_event_num)) goto do_fail; if (ret != 6) { do_fail: kfree(bus_handle); kfree(bus_method); kfree(action_handle); kfree(method); ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Invalid data format ret=%d\n", ret)); return_VALUE(-EINVAL); } key = kmalloc(sizeof(union acpi_hotkey), GFP_KERNEL); if (!key) goto do_fail; memset(key, 0, sizeof(union acpi_hotkey)); if (cmd == 1) { union acpi_hotkey *tmp = NULL; tmp = get_hotkey_by_event(&global_hotkey_list, internal_event_num); if (!tmp) ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Invalid key")); else memcpy(key, tmp, sizeof(union acpi_hotkey)); goto cont_cmd; } if (IS_EVENT(internal_event_num)) { kfree(bus_method); ret = init_hotkey_device(key, bus_handle, action_handle, method, internal_event_num, external_event_num); } else ret = init_poll_hotkey_device(key, bus_handle, bus_method, action_handle, method, internal_event_num); if (ret) { kfree(bus_handle); kfree(action_handle); if (IS_EVENT(internal_event_num)) free_hotkey_buffer(key); else free_poll_hotkey_buffer(key); kfree(key); ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Invalid hotkey \n")); return_VALUE(-EINVAL); } cont_cmd: kfree(bus_handle); kfree(action_handle); switch (cmd) { case 0: if (get_hotkey_by_event (&global_hotkey_list, key->link.hotkey_standard_num)) goto fail_out; else hotkey_add(key); break; case 1: hotkey_remove(key); break; case 2: if (hotkey_update(key)) goto fail_out; break; default: goto fail_out; break; } return_VALUE(count); fail_out: if (IS_EVENT(internal_event_num)) free_hotkey_buffer(key); else free_poll_hotkey_buffer(key); kfree(key); ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "invalid key\n")); return_VALUE(-EINVAL); } /* * This function evaluates an ACPI method, given an int as parameter, the * method is searched within the scope of the handle, can be NULL. The output * of the method is written is output, which can also be NULL * * returns 1 if write is successful, 0 else. */ static int write_acpi_int(acpi_handle handle, const char *method, int val, struct acpi_buffer *output) { struct acpi_object_list params; /* list of input parameters (an int here) */ union acpi_object in_obj; /* the only param we use */ acpi_status status; ACPI_FUNCTION_TRACE("write_acpi_int"); params.count = 1; params.pointer = &in_obj; in_obj.type = ACPI_TYPE_INTEGER; in_obj.integer.value = val; status = acpi_evaluate_object(handle, (char *)method, ¶ms, output); return_VALUE(status == AE_OK); } static int read_acpi_int(acpi_handle handle, const char *method, union acpi_object *val) { struct acpi_buffer output; union acpi_object out_obj; acpi_status status; ACPI_FUNCTION_TRACE("read_acpi_int"); output.length = sizeof(out_obj); output.pointer = &out_obj; status = acpi_evaluate_object(handle, (char *)method, NULL, &output); if (val) { val->integer.value = out_obj.integer.value; val->type = out_obj.type; } else ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "null val pointer")); return_VALUE((status == AE_OK) && (out_obj.type == ACPI_TYPE_INTEGER)); } static union acpi_hotkey *get_hotkey_by_event(struct acpi_hotkey_list *hotkey_list, int event) { struct list_head *entries; list_for_each(entries, hotkey_list->entries) { union acpi_hotkey *key = container_of(entries, union acpi_hotkey, entries); if (key->link.hotkey_standard_num == event) { return (key); } } return (NULL); } /* * user call AML method interface: * Call convention: * echo "event_num: arg type : value" * example: echo "1:1:30" > /proc/acpi/action * Just support 1 integer arg passing to AML method */ static ssize_t hotkey_execute_aml_method(struct file *file, const char __user * buffer, size_t count, loff_t * data) { struct acpi_hotkey_list *hotkey_list = &global_hotkey_list; char *arg; int event, method_type, type, value; union acpi_hotkey *key; ACPI_FUNCTION_TRACE("hotkey_execte_aml_method"); arg = (char *)kmalloc(count + 1, GFP_KERNEL); if (!arg) return_VALUE(-ENOMEM); arg[count] = 0; if (copy_from_user(arg, buffer, count)) { kfree(arg); ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Invalid argument 2")); return_VALUE(-EINVAL); } if (sscanf(arg, "%d:%d:%d:%d", &event, &method_type, &type, &value) != 4) { kfree(arg); ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Invalid argument 3")); return_VALUE(-EINVAL); } kfree(arg); if (type == ACPI_TYPE_INTEGER) { key = get_hotkey_by_event(hotkey_list, event); if (!key) goto do_fail; if (IS_EVENT(event)) write_acpi_int(key->event_hotkey.action_handle, key->event_hotkey.action_method, value, NULL); else if (IS_POLL(event)) { if (method_type == POLL_METHOD) read_acpi_int(key->poll_hotkey.poll_handle, key->poll_hotkey.poll_method, key->poll_hotkey.poll_result); else if (method_type == ACTION_METHOD) write_acpi_int(key->poll_hotkey.action_handle, key->poll_hotkey.action_method, value, NULL); else goto do_fail; } } else { ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Not supported")); return_VALUE(-EINVAL); } return_VALUE(count); do_fail: return_VALUE(-EINVAL); } static int __init hotkey_init(void) { int result; mode_t mode = S_IFREG | S_IRUGO | S_IWUGO; ACPI_FUNCTION_TRACE("hotkey_init"); if (acpi_disabled) return -ENODEV; if (acpi_specific_hotkey_enabled) { printk("Using specific hotkey driver\n"); return -ENODEV; } hotkey_proc_dir = proc_mkdir(HOTKEY_PROC, acpi_root_dir); if (!hotkey_proc_dir) { ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Hotkey: Unable to create %s entry\n", HOTKEY_PROC)); return (-ENODEV); } hotkey_proc_dir->owner = THIS_MODULE; hotkey_config = create_proc_entry(HOTKEY_EV_CONFIG, mode, hotkey_proc_dir); if (!hotkey_config) { ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Hotkey: Unable to create %s entry\n", HOTKEY_EV_CONFIG)); goto do_fail1; } else { hotkey_config->proc_fops = &hotkey_config_fops; hotkey_config->data = &global_hotkey_list; hotkey_config->owner = THIS_MODULE; hotkey_config->uid = 0; hotkey_config->gid = 0; } hotkey_poll_config = create_proc_entry(HOTKEY_PL_CONFIG, mode, hotkey_proc_dir); if (!hotkey_poll_config) { ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Hotkey: Unable to create %s entry\n", HOTKEY_EV_CONFIG)); goto do_fail2; } else { hotkey_poll_config->proc_fops = &hotkey_poll_config_fops; hotkey_poll_config->data = &global_hotkey_list; hotkey_poll_config->owner = THIS_MODULE; hotkey_poll_config->uid = 0; hotkey_poll_config->gid = 0; } hotkey_action = create_proc_entry(HOTKEY_ACTION, mode, hotkey_proc_dir); if (!hotkey_action) { ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Hotkey: Unable to create %s entry\n", HOTKEY_ACTION)); goto do_fail3; } else { hotkey_action->proc_fops = &hotkey_action_fops; hotkey_action->owner = THIS_MODULE; hotkey_action->uid = 0; hotkey_action->gid = 0; } hotkey_info = create_proc_entry(HOTKEY_INFO, mode, hotkey_proc_dir); if (!hotkey_info) { ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Hotkey: Unable to create %s entry\n", HOTKEY_INFO)); goto do_fail4; } else { hotkey_info->proc_fops = &hotkey_info_fops; hotkey_info->owner = THIS_MODULE; hotkey_info->uid = 0; hotkey_info->gid = 0; } result = acpi_bus_register_driver(&hotkey_driver); if (result < 0) goto do_fail5; global_hotkey_list.count = 0; global_hotkey_list.entries = &hotkey_entries; INIT_LIST_HEAD(&hotkey_entries); return (0); do_fail5: remove_proc_entry(HOTKEY_INFO, hotkey_proc_dir); do_fail4: remove_proc_entry(HOTKEY_ACTION, hotkey_proc_dir); do_fail3: remove_proc_entry(HOTKEY_PL_CONFIG, hotkey_proc_dir); do_fail2: remove_proc_entry(HOTKEY_EV_CONFIG, hotkey_proc_dir); do_fail1: remove_proc_entry(HOTKEY_PROC, acpi_root_dir); return (-ENODEV); } static void __exit hotkey_exit(void) { struct list_head *entries, *next; ACPI_FUNCTION_TRACE("hotkey_exit"); list_for_each_safe(entries, next, global_hotkey_list.entries) { union acpi_hotkey *key = container_of(entries, union acpi_hotkey, entries); acpi_os_wait_events_complete(NULL); list_del(&key->link.entries); global_hotkey_list.count--; free_hotkey_device(key); } acpi_bus_unregister_driver(&hotkey_driver); remove_proc_entry(HOTKEY_EV_CONFIG, hotkey_proc_dir); remove_proc_entry(HOTKEY_PL_CONFIG, hotkey_proc_dir); remove_proc_entry(HOTKEY_ACTION, hotkey_proc_dir); remove_proc_entry(HOTKEY_INFO, hotkey_proc_dir); remove_proc_entry(HOTKEY_PROC, acpi_root_dir); return; } module_init(hotkey_init); module_exit(hotkey_exit);