/* * quickstart.c - ACPI Direct App Launch driver * * * Copyright (C) 2007-2010 Angelo Arrifano <miknix@gmail.com> * * Information gathered from disassebled dsdt and from here: * <http://www.microsoft.com/whdc/system/platform/firmware/DirAppLaunch.mspx> * * 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 * */ #define QUICKSTART_VERSION "1.03" #include <linux/kernel.h> #include <linux/module.h> #include <linux/init.h> #include <linux/types.h> #include <acpi/acpi_drivers.h> #include <linux/platform_device.h> #include <linux/input.h> MODULE_AUTHOR("Angelo Arrifano"); MODULE_DESCRIPTION("ACPI Direct App Launch driver"); MODULE_LICENSE("GPL"); #define QUICKSTART_ACPI_DEVICE_NAME "quickstart" #define QUICKSTART_ACPI_CLASS "quickstart" #define QUICKSTART_ACPI_HID "PNP0C32" #define QUICKSTART_PF_DRIVER_NAME "quickstart" #define QUICKSTART_PF_DEVICE_NAME "quickstart" #define QUICKSTART_PF_DEVATTR_NAME "pressed_button" #define QUICKSTART_MAX_BTN_NAME_LEN 16 /* There will be two events: * 0x02 - A hot button was pressed while device was off/sleeping. * 0x80 - A hot button was pressed while device was up. */ #define QUICKSTART_EVENT_WAKE 0x02 #define QUICKSTART_EVENT_RUNTIME 0x80 struct quickstart_btn { char *name; unsigned int id; struct quickstart_btn *next; }; static struct quickstart_driver_data { struct quickstart_btn *btn_lst; struct quickstart_btn *pressed; } quickstart_data; /* ACPI driver Structs */ struct quickstart_acpi { struct acpi_device *device; struct quickstart_btn *btn; }; static int quickstart_acpi_add(struct acpi_device *device); static int quickstart_acpi_remove(struct acpi_device *device, int type); static const struct acpi_device_id quickstart_device_ids[] = { {QUICKSTART_ACPI_HID, 0}, {"", 0}, }; static struct acpi_driver quickstart_acpi_driver = { .name = "quickstart", .class = QUICKSTART_ACPI_CLASS, .ids = quickstart_device_ids, .ops = { .add = quickstart_acpi_add, .remove = quickstart_acpi_remove, }, }; /* Input device structs */ struct input_dev *quickstart_input; /* Platform driver structs */ static ssize_t buttons_show(struct device *dev, struct device_attribute *attr, char *buf); static ssize_t pressed_button_show(struct device *dev, struct device_attribute *attr, char *buf); static ssize_t pressed_button_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count); static DEVICE_ATTR(pressed_button, 0666, pressed_button_show, pressed_button_store); static DEVICE_ATTR(buttons, 0444, buttons_show, NULL); static struct platform_device *pf_device; static struct platform_driver pf_driver = { .driver = { .name = QUICKSTART_PF_DRIVER_NAME, .owner = THIS_MODULE, } }; /* * Platform driver functions */ static ssize_t buttons_show(struct device *dev, struct device_attribute *attr, char *buf) { int count = 0; struct quickstart_btn *ptr = quickstart_data.btn_lst; if (!ptr) return snprintf(buf, PAGE_SIZE, "none"); while (ptr && (count < PAGE_SIZE)) { if (ptr->name) { count += snprintf(buf + count, PAGE_SIZE - count, "%d\t%s\n", ptr->id, ptr->name); } ptr = ptr->next; } return count; } static ssize_t pressed_button_show(struct device *dev, struct device_attribute *attr, char *buf) { return snprintf(buf, PAGE_SIZE, "%s\n", (quickstart_data.pressed ? quickstart_data.pressed->name : "none")); } static ssize_t pressed_button_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { if (count < 2) return -EINVAL; if (strncasecmp(buf, "none", 4) != 0) return -EINVAL; quickstart_data.pressed = NULL; return count; } /* Hotstart Helper functions */ static int quickstart_btnlst_add(struct quickstart_btn **data) { struct quickstart_btn **ptr = &quickstart_data.btn_lst; while (*ptr) ptr = &((*ptr)->next); *ptr = kzalloc(sizeof(struct quickstart_btn), GFP_KERNEL); if (!*ptr) { *data = NULL; return -ENOMEM; } *data = *ptr; return 0; } static void quickstart_btnlst_del(struct quickstart_btn *data) { struct quickstart_btn **ptr = &quickstart_data.btn_lst; if (!data) return; while (*ptr) { if (*ptr == data) { *ptr = (*ptr)->next; kfree(data); return; } ptr = &((*ptr)->next); } return; } static void quickstart_btnlst_free(void) { struct quickstart_btn *ptr = quickstart_data.btn_lst; struct quickstart_btn *lptr = NULL; while (ptr) { lptr = ptr; ptr = ptr->next; kfree(lptr->name); kfree(lptr); } return; } /* ACPI Driver functions */ static void quickstart_acpi_notify(acpi_handle handle, u32 event, void *data) { struct quickstart_acpi *quickstart = data; if (!quickstart) return; if (event == QUICKSTART_EVENT_WAKE) quickstart_data.pressed = quickstart->btn; else if (event == QUICKSTART_EVENT_RUNTIME) { input_report_key(quickstart_input, quickstart->btn->id, 1); input_sync(quickstart_input); input_report_key(quickstart_input, quickstart->btn->id, 0); input_sync(quickstart_input); } return; } static void quickstart_acpi_ghid(struct quickstart_acpi *quickstart) { acpi_status status; struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; uint32_t usageid = 0; if (!quickstart) return; /* This returns a buffer telling the button usage ID, * and triggers pending notify events (The ones before booting). */ status = acpi_evaluate_object(quickstart->device->handle, "GHID", NULL, &buffer); if (ACPI_FAILURE(status) || !buffer.pointer) { printk(KERN_ERR "quickstart: %s GHID method failed.\n", quickstart->btn->name); return; } if (buffer.length < 8) return; /* <<The GHID method can return a BYTE, WORD, or DWORD. * The value must be encoded in little-endian byte * order (least significant byte first).>> */ usageid = *((uint32_t *)(buffer.pointer + (buffer.length - 8))); quickstart->btn->id = usageid; kfree(buffer.pointer); } static int quickstart_acpi_config(struct quickstart_acpi *quickstart, char *bid) { int len = strlen(bid); int ret; /* Add button to list */ ret = quickstart_btnlst_add(&quickstart->btn); if (ret) return ret; quickstart->btn->name = kzalloc(len + 1, GFP_KERNEL); if (!quickstart->btn->name) { quickstart_btnlst_free(); return -ENOMEM; } strcpy(quickstart->btn->name, bid); return 0; } static int quickstart_acpi_add(struct acpi_device *device) { int ret = 0; acpi_status status = AE_OK; struct quickstart_acpi *quickstart = NULL; if (!device) return -EINVAL; quickstart = kzalloc(sizeof(struct quickstart_acpi), GFP_KERNEL); if (!quickstart) return -ENOMEM; quickstart->device = device; strcpy(acpi_device_name(device), QUICKSTART_ACPI_DEVICE_NAME); strcpy(acpi_device_class(device), QUICKSTART_ACPI_CLASS); device->driver_data = quickstart; /* Add button to list and initialize some stuff */ ret = quickstart_acpi_config(quickstart, acpi_device_bid(device)); if (ret) goto fail_config; status = acpi_install_notify_handler(device->handle, ACPI_ALL_NOTIFY, quickstart_acpi_notify, quickstart); if (ACPI_FAILURE(status)) { printk(KERN_ERR "quickstart: Notify handler install error\n"); ret = -ENODEV; goto fail_installnotify; } quickstart_acpi_ghid(quickstart); return 0; fail_installnotify: quickstart_btnlst_del(quickstart->btn); fail_config: kfree(quickstart); return ret; } static int quickstart_acpi_remove(struct acpi_device *device, int type) { acpi_status status = 0; struct quickstart_acpi *quickstart = NULL; if (!device || !acpi_driver_data(device)) return -EINVAL; quickstart = acpi_driver_data(device); status = acpi_remove_notify_handler(device->handle, ACPI_ALL_NOTIFY, quickstart_acpi_notify); if (ACPI_FAILURE(status)) printk(KERN_ERR "quickstart: Error removing notify handler\n"); kfree(quickstart); return 0; } /* Module functions */ static void quickstart_exit(void) { input_unregister_device(quickstart_input); device_remove_file(&pf_device->dev, &dev_attr_pressed_button); device_remove_file(&pf_device->dev, &dev_attr_buttons); platform_device_unregister(pf_device); platform_driver_unregister(&pf_driver); acpi_bus_unregister_driver(&quickstart_acpi_driver); quickstart_btnlst_free(); return; } static int __init quickstart_init_input(void) { struct quickstart_btn **ptr = &quickstart_data.btn_lst; int count; int ret; quickstart_input = input_allocate_device(); if (!quickstart_input) return -ENOMEM; quickstart_input->name = "Quickstart ACPI Buttons"; quickstart_input->id.bustype = BUS_HOST; while (*ptr) { count++; set_bit(EV_KEY, quickstart_input->evbit); set_bit((*ptr)->id, quickstart_input->keybit); ptr = &((*ptr)->next); } ret = input_register_device(quickstart_input); if (ret) { input_free_device(quickstart_input); return ret; } return 0; } static int __init quickstart_init(void) { int ret; /* ACPI Check */ if (acpi_disabled) return -ENODEV; /* ACPI driver register */ ret = acpi_bus_register_driver(&quickstart_acpi_driver); if (ret) return ret; /* If existing bus with no devices */ if (!quickstart_data.btn_lst) { ret = -ENODEV; goto fail_pfdrv_reg; } /* Platform driver register */ ret = platform_driver_register(&pf_driver); if (ret) goto fail_pfdrv_reg; /* Platform device register */ pf_device = platform_device_alloc(QUICKSTART_PF_DEVICE_NAME, -1); if (!pf_device) { ret = -ENOMEM; goto fail_pfdev_alloc; } ret = platform_device_add(pf_device); if (ret) goto fail_pfdev_add; /* Create device sysfs file */ ret = device_create_file(&pf_device->dev, &dev_attr_pressed_button); if (ret) goto fail_dev_file; ret = device_create_file(&pf_device->dev, &dev_attr_buttons); if (ret) goto fail_dev_file2; /* Input device */ ret = quickstart_init_input(); if (ret) goto fail_input; printk(KERN_INFO "quickstart: ACPI Direct App Launch ver %s\n", QUICKSTART_VERSION); return 0; fail_input: device_remove_file(&pf_device->dev, &dev_attr_buttons); fail_dev_file2: device_remove_file(&pf_device->dev, &dev_attr_pressed_button); fail_dev_file: platform_device_del(pf_device); fail_pfdev_add: platform_device_put(pf_device); fail_pfdev_alloc: platform_driver_unregister(&pf_driver); fail_pfdrv_reg: acpi_bus_unregister_driver(&quickstart_acpi_driver); return ret; } module_init(quickstart_init); module_exit(quickstart_exit);