diff options
Diffstat (limited to 'drivers/platform/x86/wmi.c')
| -rw-r--r-- | drivers/platform/x86/wmi.c | 472 |
1 files changed, 255 insertions, 217 deletions
diff --git a/drivers/platform/x86/wmi.c b/drivers/platform/x86/wmi.c index b104302fea0..43d13295e63 100644 --- a/drivers/platform/x86/wmi.c +++ b/drivers/platform/x86/wmi.c @@ -27,14 +27,16 @@ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + #include <linux/kernel.h> #include <linux/init.h> #include <linux/types.h> #include <linux/device.h> #include <linux/list.h> #include <linux/acpi.h> -#include <acpi/acpi_bus.h> -#include <acpi/acpi_drivers.h> +#include <linux/slab.h> +#include <linux/module.h> ACPI_MODULE_NAME("wmi"); MODULE_AUTHOR("Carlos Corbacho"); @@ -43,9 +45,8 @@ MODULE_LICENSE("GPL"); #define ACPI_WMI_CLASS "wmi" -#define PREFIX "ACPI: WMI: " - static DEFINE_MUTEX(wmi_data_lock); +static LIST_HEAD(wmi_block_list); struct guid_block { char guid[16]; @@ -66,10 +67,9 @@ struct wmi_block { acpi_handle handle; wmi_notify_handler handler; void *handler_data; - struct device *dev; + struct device dev; }; -static struct wmi_block wmi_blocks; /* * If the GUID data block is marked as expensive, we must enable and @@ -80,7 +80,17 @@ static struct wmi_block wmi_blocks; #define ACPI_WMI_STRING 0x4 /* GUID takes & returns a string */ #define ACPI_WMI_EVENT 0x8 /* GUID is an event */ -static int acpi_wmi_remove(struct acpi_device *device, int type); +static bool debug_event; +module_param(debug_event, bool, 0444); +MODULE_PARM_DESC(debug_event, + "Log WMI Events [0/1]"); + +static bool debug_dump_wdg; +module_param(debug_dump_wdg, bool, 0444); +MODULE_PARM_DESC(debug_dump_wdg, + "Dump available WMI interfaces [0/1]"); + +static int acpi_wmi_remove(struct acpi_device *device); static int acpi_wmi_add(struct acpi_device *device); static void acpi_wmi_notify(struct acpi_device *device, u32 event); @@ -99,7 +109,7 @@ static struct acpi_driver acpi_wmi_driver = { .add = acpi_wmi_add, .remove = acpi_wmi_remove, .notify = acpi_wmi_notify, - }, + }, }; /* @@ -117,30 +127,18 @@ static struct acpi_driver acpi_wmi_driver = { */ static int wmi_parse_hexbyte(const u8 *src) { - unsigned int x; /* For correct wrapping */ int h; + int value; /* high part */ - x = src[0]; - if (x - '0' <= '9' - '0') { - h = x - '0'; - } else if (x - 'a' <= 'f' - 'a') { - h = x - 'a' + 10; - } else if (x - 'A' <= 'F' - 'A') { - h = x - 'A' + 10; - } else { + h = value = hex_to_bin(src[0]); + if (value < 0) return -1; - } - h <<= 4; /* low part */ - x = src[1]; - if (x - '0' <= '9' - '0') - return h | (x - '0'); - if (x - 'a' <= 'f' - 'a') - return h | (x - 'a' + 10); - if (x - 'A' <= 'F' - 'A') - return h | (x - 'A' + 10); + value = hex_to_bin(src[1]); + if (value >= 0) + return (h << 4) | value; return -1; } @@ -221,7 +219,7 @@ static int wmi_gtoa(const char *in, char *out) for (i = 10; i <= 15; i++) out += sprintf(out, "%02X", in[i] & 0xFF); - out = '\0'; + *out = '\0'; return 0; } @@ -235,7 +233,7 @@ static bool find_guid(const char *guid_string, struct wmi_block **out) wmi_parse_guid(guid_string, tmp); wmi_swap_bytes(tmp, guid_input); - list_for_each(p, &wmi_blocks.list) { + list_for_each(p, &wmi_block_list) { wblock = list_entry(p, struct wmi_block, list); block = &wblock->gblock; @@ -252,8 +250,6 @@ static acpi_status wmi_method_enable(struct wmi_block *wblock, int enable) { struct guid_block *block = NULL; char method[5]; - struct acpi_object_list input; - union acpi_object params[1]; acpi_status status; acpi_handle handle; @@ -263,13 +259,9 @@ static acpi_status wmi_method_enable(struct wmi_block *wblock, int enable) if (!block) return AE_NOT_EXIST; - input.count = 1; - input.pointer = params; - params[0].type = ACPI_TYPE_INTEGER; - params[0].integer.value = enable; snprintf(method, 5, "WE%02X", block->notify_id); - status = acpi_evaluate_object(handle, method, &input, NULL); + status = acpi_execute_simple_method(handle, method, enable); if (status != AE_OK && status != AE_NOT_FOUND) return status; @@ -353,10 +345,10 @@ struct acpi_buffer *out) { struct guid_block *block = NULL; struct wmi_block *wblock = NULL; - acpi_handle handle, wc_handle; + acpi_handle handle; acpi_status status, wc_status = AE_ERROR; - struct acpi_object_list input, wc_input; - union acpi_object wc_params[1], wq_params[1]; + struct acpi_object_list input; + union acpi_object wq_params[1]; char method[5]; char wc_method[5] = "WC"; @@ -386,11 +378,6 @@ struct acpi_buffer *out) * enable collection. */ if (block->flags & ACPI_WMI_EXPENSIVE) { - wc_input.count = 1; - wc_input.pointer = wc_params; - wc_params[0].type = ACPI_TYPE_INTEGER; - wc_params[0].integer.value = 1; - strncat(wc_method, block->object_id, 2); /* @@ -398,10 +385,9 @@ struct acpi_buffer *out) * expensive, but have no corresponding WCxx method. So we * should not fail if this happens. */ - wc_status = acpi_get_handle(handle, wc_method, &wc_handle); - if (ACPI_SUCCESS(wc_status)) - wc_status = acpi_evaluate_object(handle, wc_method, - &wc_input, NULL); + if (acpi_has_method(handle, wc_method)) + wc_status = acpi_execute_simple_method(handle, + wc_method, 1); } strcpy(method, "WQ"); @@ -414,9 +400,7 @@ struct acpi_buffer *out) * the WQxx method failed - we should disable collection anyway. */ if ((block->flags & ACPI_WMI_EXPENSIVE) && ACPI_SUCCESS(wc_status)) { - wc_params[0].integer.value = 0; - status = acpi_evaluate_object(handle, - wc_method, &wc_input, NULL); + status = acpi_execute_simple_method(handle, wc_method, 0); } return status; @@ -476,6 +460,69 @@ const struct acpi_buffer *in) } EXPORT_SYMBOL_GPL(wmi_set_block); +static void wmi_dump_wdg(const struct guid_block *g) +{ + char guid_string[37]; + + wmi_gtoa(g->guid, guid_string); + + pr_info("%s:\n", guid_string); + pr_info("\tobject_id: %c%c\n", g->object_id[0], g->object_id[1]); + pr_info("\tnotify_id: %02X\n", g->notify_id); + pr_info("\treserved: %02X\n", g->reserved); + pr_info("\tinstance_count: %d\n", g->instance_count); + pr_info("\tflags: %#x", g->flags); + if (g->flags) { + if (g->flags & ACPI_WMI_EXPENSIVE) + pr_cont(" ACPI_WMI_EXPENSIVE"); + if (g->flags & ACPI_WMI_METHOD) + pr_cont(" ACPI_WMI_METHOD"); + if (g->flags & ACPI_WMI_STRING) + pr_cont(" ACPI_WMI_STRING"); + if (g->flags & ACPI_WMI_EVENT) + pr_cont(" ACPI_WMI_EVENT"); + } + pr_cont("\n"); + +} + +static void wmi_notify_debug(u32 value, void *context) +{ + struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL }; + union acpi_object *obj; + acpi_status status; + + status = wmi_get_event_data(value, &response); + if (status != AE_OK) { + pr_info("bad event status 0x%x\n", status); + return; + } + + obj = (union acpi_object *)response.pointer; + + if (!obj) + return; + + pr_info("DEBUG Event "); + switch(obj->type) { + case ACPI_TYPE_BUFFER: + pr_cont("BUFFER_TYPE - length %d\n", obj->buffer.length); + break; + case ACPI_TYPE_STRING: + pr_cont("STRING_TYPE - %s\n", obj->string.pointer); + break; + case ACPI_TYPE_INTEGER: + pr_cont("INTEGER_TYPE - %llu\n", obj->integer.value); + break; + case ACPI_TYPE_PACKAGE: + pr_cont("PACKAGE_TYPE - %d elements\n", obj->package.count); + break; + default: + pr_cont("object type 0x%X\n", obj->type); + } + kfree(obj); +} + /** * wmi_install_notify_handler - Register handler for WMI events * @handler: Function to handle notifications @@ -487,21 +534,34 @@ acpi_status wmi_install_notify_handler(const char *guid, wmi_notify_handler handler, void *data) { struct wmi_block *block; - acpi_status status; + acpi_status status = AE_NOT_EXIST; + char tmp[16], guid_input[16]; + struct list_head *p; if (!guid || !handler) return AE_BAD_PARAMETER; - if (!find_guid(guid, &block)) - return AE_NOT_EXIST; + wmi_parse_guid(guid, tmp); + wmi_swap_bytes(tmp, guid_input); + + list_for_each(p, &wmi_block_list) { + acpi_status wmi_status; + block = list_entry(p, struct wmi_block, list); - if (block->handler) - return AE_ALREADY_ACQUIRED; + if (memcmp(block->gblock.guid, guid_input, 16) == 0) { + if (block->handler && + block->handler != wmi_notify_debug) + return AE_ALREADY_ACQUIRED; - block->handler = handler; - block->handler_data = data; + block->handler = handler; + block->handler_data = data; - status = wmi_method_enable(block, 1); + wmi_status = wmi_method_enable(block, 1); + if ((wmi_status != AE_OK) || + ((wmi_status == AE_OK) && (status == AE_NOT_EXIST))) + status = wmi_status; + } + } return status; } @@ -515,21 +575,39 @@ EXPORT_SYMBOL_GPL(wmi_install_notify_handler); acpi_status wmi_remove_notify_handler(const char *guid) { struct wmi_block *block; - acpi_status status; + acpi_status status = AE_NOT_EXIST; + char tmp[16], guid_input[16]; + struct list_head *p; if (!guid) return AE_BAD_PARAMETER; - if (!find_guid(guid, &block)) - return AE_NOT_EXIST; - - if (!block->handler) - return AE_NULL_ENTRY; - - status = wmi_method_enable(block, 0); + wmi_parse_guid(guid, tmp); + wmi_swap_bytes(tmp, guid_input); - block->handler = NULL; - block->handler_data = NULL; + list_for_each(p, &wmi_block_list) { + acpi_status wmi_status; + block = list_entry(p, struct wmi_block, list); + + if (memcmp(block->gblock.guid, guid_input, 16) == 0) { + if (!block->handler || + block->handler == wmi_notify_debug) + return AE_NULL_ENTRY; + + if (debug_event) { + block->handler = wmi_notify_debug; + status = AE_OK; + } else { + wmi_status = wmi_method_enable(block, 0); + block->handler = NULL; + block->handler_data = NULL; + if ((wmi_status != AE_OK) || + ((wmi_status == AE_OK) && + (status == AE_NOT_EXIST))) + status = wmi_status; + } + } + } return status; } @@ -556,7 +634,7 @@ acpi_status wmi_get_event_data(u32 event, struct acpi_buffer *out) params[0].type = ACPI_TYPE_INTEGER; params[0].integer.value = event; - list_for_each(p, &wmi_blocks.list) { + list_for_each(p, &wmi_block_list) { wblock = list_entry(p, struct wmi_block, list); gblock = &wblock->gblock; @@ -585,21 +663,29 @@ EXPORT_SYMBOL_GPL(wmi_has_guid); /* * sysfs interface */ -static ssize_t show_modalias(struct device *dev, struct device_attribute *attr, +static ssize_t modalias_show(struct device *dev, struct device_attribute *attr, char *buf) { char guid_string[37]; struct wmi_block *wblock; wblock = dev_get_drvdata(dev); - if (!wblock) - return -ENOMEM; + if (!wblock) { + strcat(buf, "\n"); + return strlen(buf); + } wmi_gtoa(wblock->gblock.guid, guid_string); return sprintf(buf, "wmi:%s\n", guid_string); } -static DEVICE_ATTR(modalias, S_IRUGO, show_modalias, NULL); +static DEVICE_ATTR_RO(modalias); + +static struct attribute *wmi_attrs[] = { + &dev_attr_modalias.attr, + NULL, +}; +ATTRIBUTE_GROUPS(wmi); static int wmi_dev_uevent(struct device *dev, struct kobj_uevent_env *env) { @@ -625,169 +711,126 @@ static int wmi_dev_uevent(struct device *dev, struct kobj_uevent_env *env) static void wmi_dev_free(struct device *dev) { - kfree(dev); + struct wmi_block *wmi_block = container_of(dev, struct wmi_block, dev); + + kfree(wmi_block); } static struct class wmi_class = { .name = "wmi", .dev_release = wmi_dev_free, .dev_uevent = wmi_dev_uevent, + .dev_groups = wmi_groups, }; -static int wmi_create_devs(void) +static int wmi_create_device(const struct guid_block *gblock, + struct wmi_block *wblock, acpi_handle handle) { - int result; char guid_string[37]; - struct guid_block *gblock; - struct wmi_block *wblock; - struct list_head *p; - struct device *guid_dev; - /* Create devices for all the GUIDs */ - list_for_each(p, &wmi_blocks.list) { - wblock = list_entry(p, struct wmi_block, list); - - guid_dev = kzalloc(sizeof(struct device), GFP_KERNEL); - if (!guid_dev) - return -ENOMEM; - - wblock->dev = guid_dev; + wblock->dev.class = &wmi_class; - guid_dev->class = &wmi_class; - dev_set_drvdata(guid_dev, wblock); + wmi_gtoa(gblock->guid, guid_string); + dev_set_name(&wblock->dev, "%s", guid_string); - gblock = &wblock->gblock; - - wmi_gtoa(gblock->guid, guid_string); - dev_set_name(guid_dev, guid_string); + dev_set_drvdata(&wblock->dev, wblock); - result = device_register(guid_dev); - if (result) - return result; - - result = device_create_file(guid_dev, &dev_attr_modalias); - if (result) - return result; - } - - return 0; + return device_register(&wblock->dev); } -static void wmi_remove_devs(void) +static void wmi_free_devices(void) { - struct guid_block *gblock; - struct wmi_block *wblock; - struct list_head *p; - struct device *guid_dev; + struct wmi_block *wblock, *next; /* Delete devices for all the GUIDs */ - list_for_each(p, &wmi_blocks.list) { - wblock = list_entry(p, struct wmi_block, list); - - guid_dev = wblock->dev; - gblock = &wblock->gblock; - - device_remove_file(guid_dev, &dev_attr_modalias); - - device_unregister(guid_dev); + list_for_each_entry_safe(wblock, next, &wmi_block_list, list) { + list_del(&wblock->list); + if (wblock->dev.class) + device_unregister(&wblock->dev); + else + kfree(wblock); } } -static void wmi_class_exit(void) -{ - wmi_remove_devs(); - class_unregister(&wmi_class); -} - -static int wmi_class_init(void) -{ - int ret; - - ret = class_register(&wmi_class); - if (ret) - return ret; - - ret = wmi_create_devs(); - if (ret) - wmi_class_exit(); - - return ret; -} - static bool guid_already_parsed(const char *guid_string) { - struct guid_block *gblock; struct wmi_block *wblock; - struct list_head *p; - list_for_each(p, &wmi_blocks.list) { - wblock = list_entry(p, struct wmi_block, list); - gblock = &wblock->gblock; - - if (strncmp(gblock->guid, guid_string, 16) == 0) + list_for_each_entry(wblock, &wmi_block_list, list) + if (memcmp(wblock->gblock.guid, guid_string, 16) == 0) return true; - } + return false; } /* * Parse the _WDG method for the GUID data blocks */ -static __init acpi_status parse_wdg(acpi_handle handle) +static int parse_wdg(acpi_handle handle) { struct acpi_buffer out = {ACPI_ALLOCATE_BUFFER, NULL}; union acpi_object *obj; - struct guid_block *gblock; + const struct guid_block *gblock; struct wmi_block *wblock; - char guid_string[37]; acpi_status status; + int retval; u32 i, total; status = acpi_evaluate_object(handle, "_WDG", NULL, &out); - if (ACPI_FAILURE(status)) - return status; + return -ENXIO; obj = (union acpi_object *) out.pointer; + if (!obj) + return -ENXIO; - if (obj->type != ACPI_TYPE_BUFFER) - return AE_ERROR; + if (obj->type != ACPI_TYPE_BUFFER) { + retval = -ENXIO; + goto out_free_pointer; + } + gblock = (const struct guid_block *)obj->buffer.pointer; total = obj->buffer.length / sizeof(struct guid_block); - gblock = kzalloc(obj->buffer.length, GFP_KERNEL); - if (!gblock) - return AE_NO_MEMORY; + for (i = 0; i < total; i++) { + if (debug_dump_wdg) + wmi_dump_wdg(&gblock[i]); + + wblock = kzalloc(sizeof(struct wmi_block), GFP_KERNEL); + if (!wblock) + return -ENOMEM; - memcpy(gblock, obj->buffer.pointer, obj->buffer.length); + wblock->handle = handle; + wblock->gblock = gblock[i]; - for (i = 0; i < total; i++) { /* Some WMI devices, like those for nVidia hooks, have a duplicate GUID. It's not clear what we should do in this - case yet, so for now, we'll just ignore the duplicate. - Anyone who wants to add support for that device can come - up with a better workaround for the mess then. + case yet, so for now, we'll just ignore the duplicate + for device creation. */ - if (guid_already_parsed(gblock[i].guid) == true) { - wmi_gtoa(gblock[i].guid, guid_string); - printk(KERN_INFO PREFIX "Skipping duplicate GUID %s\n", - guid_string); - continue; + if (!guid_already_parsed(gblock[i].guid)) { + retval = wmi_create_device(&gblock[i], wblock, handle); + if (retval) { + wmi_free_devices(); + goto out_free_pointer; + } } - wblock = kzalloc(sizeof(struct wmi_block), GFP_KERNEL); - if (!wblock) - return AE_NO_MEMORY; - wblock->gblock = gblock[i]; - wblock->handle = handle; - list_add_tail(&wblock->list, &wmi_blocks.list); + list_add_tail(&wblock->list, &wmi_block_list); + + if (debug_event) { + wblock->handler = wmi_notify_debug; + wmi_method_enable(wblock, 1); + } } + retval = 0; + +out_free_pointer: kfree(out.pointer); - kfree(gblock); - return status; + return retval; } /* @@ -796,7 +839,7 @@ static __init acpi_status parse_wdg(acpi_handle handle) */ static acpi_status acpi_wmi_ec_space_handler(u32 function, acpi_physical_address address, - u32 bits, acpi_integer * value, + u32 bits, u64 *value, void *handler_context, void *region_context) { int result = 0, i = 0; @@ -813,7 +856,7 @@ acpi_wmi_ec_space_handler(u32 function, acpi_physical_address address, if (function == ACPI_READ) { result = ec_read(address, &temp); - (*value) |= ((acpi_integer)temp) << i; + (*value) |= ((u64)temp) << i; } else { temp = 0xff & ((*value) >> i); result = ec_write(address, temp); @@ -839,8 +882,9 @@ static void acpi_wmi_notify(struct acpi_device *device, u32 event) struct guid_block *block; struct wmi_block *wblock; struct list_head *p; + char guid_string[37]; - list_for_each(p, &wmi_blocks.list) { + list_for_each(p, &wmi_block_list) { wblock = list_entry(p, struct wmi_block, list); block = &wblock->gblock; @@ -848,6 +892,10 @@ static void acpi_wmi_notify(struct acpi_device *device, u32 event) (block->notify_id == event)) { if (wblock->handler) wblock->handler(event, wblock->handler_data); + if (debug_event) { + wmi_gtoa(wblock->gblock.guid, guid_string); + pr_info("DEBUG Event GUID: %s\n", guid_string); + } acpi_bus_generate_netlink_event( device->pnp.device_class, dev_name(&device->dev), @@ -857,79 +905,69 @@ static void acpi_wmi_notify(struct acpi_device *device, u32 event) } } -static int acpi_wmi_remove(struct acpi_device *device, int type) +static int acpi_wmi_remove(struct acpi_device *device) { acpi_remove_address_space_handler(device->handle, ACPI_ADR_SPACE_EC, &acpi_wmi_ec_space_handler); + wmi_free_devices(); return 0; } -static int __init acpi_wmi_add(struct acpi_device *device) +static int acpi_wmi_add(struct acpi_device *device) { acpi_status status; - int result = 0; + int error; status = acpi_install_address_space_handler(device->handle, ACPI_ADR_SPACE_EC, &acpi_wmi_ec_space_handler, NULL, NULL); - if (ACPI_FAILURE(status)) - return -ENODEV; - - status = parse_wdg(device->handle); if (ACPI_FAILURE(status)) { - printk(KERN_ERR PREFIX "Error installing EC region handler\n"); + pr_err("Error installing EC region handler\n"); return -ENODEV; } - return result; + error = parse_wdg(device->handle); + if (error) { + acpi_remove_address_space_handler(device->handle, + ACPI_ADR_SPACE_EC, + &acpi_wmi_ec_space_handler); + pr_err("Failed to parse WDG method\n"); + return error; + } + + return 0; } static int __init acpi_wmi_init(void) { - int result; - - INIT_LIST_HEAD(&wmi_blocks.list); + int error; if (acpi_disabled) return -ENODEV; - result = acpi_bus_register_driver(&acpi_wmi_driver); + error = class_register(&wmi_class); + if (error) + return error; - if (result < 0) { - printk(KERN_INFO PREFIX "Error loading mapper\n"); - return -ENODEV; + error = acpi_bus_register_driver(&acpi_wmi_driver); + if (error) { + pr_err("Error loading mapper\n"); + class_unregister(&wmi_class); + return error; } - result = wmi_class_init(); - if (result) { - acpi_bus_unregister_driver(&acpi_wmi_driver); - return result; - } - - printk(KERN_INFO PREFIX "Mapper loaded\n"); - - return result; + pr_info("Mapper loaded\n"); + return 0; } static void __exit acpi_wmi_exit(void) { - struct list_head *p, *tmp; - struct wmi_block *wblock; - - wmi_class_exit(); - acpi_bus_unregister_driver(&acpi_wmi_driver); + class_unregister(&wmi_class); - list_for_each_safe(p, tmp, &wmi_blocks.list) { - wblock = list_entry(p, struct wmi_block, list); - - list_del(p); - kfree(wblock); - } - - printk(KERN_INFO PREFIX "Mapper unloaded\n"); + pr_info("Mapper unloaded\n"); } subsys_initcall(acpi_wmi_init); |
