From 844af950da946cfab227a04b950614da04cb6275 Mon Sep 17 00:00:00 2001 From: Andy Lutomirski Date: Tue, 24 Nov 2015 19:49:23 -0800 Subject: platform/x86: wmi: Turn WMI into a bus driver MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit WMI is logically a bus: the WMI driver binds to an ACPI node (or more than one), and each instance of the WMI driver enumerates its children and hopes that drivers will attach to the children that are useful. This patch gives WMI a driver model bus type and the ability to match to drivers. The bus itself is a device in the new "wmi_bus" class, and all of the individual WMI devices are slotted into the device hierarchy correctly. Signed-off-by: Andy Lutomirski Cc: Andy Lutomirski Cc: Mario Limonciello Cc: Pali Rohár Cc: linux-kernel@vger.kernel.org Cc: platform-driver-x86@vger.kernel.org Cc: linux-acpi@vger.kernel.org Acked-by: Rafael J. Wysocki Signed-off-by: Darren Hart (VMware) --- include/linux/wmi.h | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 include/linux/wmi.h (limited to 'include') diff --git a/include/linux/wmi.h b/include/linux/wmi.h new file mode 100644 index 000000000000..29ed34b4dae1 --- /dev/null +++ b/include/linux/wmi.h @@ -0,0 +1,47 @@ +/* + * wmi.h - ACPI WMI interface + * + * Copyright (c) 2015 Andrew Lutomirski + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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. + */ + +#ifndef _LINUX_WMI_H +#define _LINUX_WMI_H + +#include +#include + +struct wmi_device { + struct device dev; +}; + +struct wmi_device_id { + const char *guid_string; +}; + +struct wmi_driver { + struct device_driver driver; + const struct wmi_device_id *id_table; + + int (*probe)(struct wmi_device *wdev); + int (*remove)(struct wmi_device *wdev); +}; + +extern int __must_check __wmi_driver_register(struct wmi_driver *driver, + struct module *owner); +extern void wmi_driver_unregister(struct wmi_driver *driver); +#define wmi_driver_register(driver) __wmi_driver_register((driver), THIS_MODULE) + +#define module_wmi_driver(__wmi_driver) \ + module_driver(__wmi_driver, wmi_driver_register, \ + wmi_driver_unregister) + +#endif -- cgit v1.2.3 From d4fc91adfde11c41295d1cf001bdbec5d6879016 Mon Sep 17 00:00:00 2001 From: Andy Lutomirski Date: Wed, 25 Nov 2015 14:03:43 -0800 Subject: platform/x86: wmi: Probe data objects for read and write capabilities MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Dell XPS 13 9350 has one RW data object, one RO data object, and one totally inaccessible data object. Check for the existence of the accessor methods and report in sysfs. The docs also permit WQxx getters for single-instance objects to take no parameters. Probe for that as well to avoid ACPICA warnings about mismatched signatures. Signed-off-by: Andy Lutomirski Cc: Andy Lutomirski Cc: Mario Limonciello Cc: Pali Rohár Cc: linux-kernel@vger.kernel.org Cc: platform-driver-x86@vger.kernel.org Cc: linux-acpi@vger.kernel.org Acked-by: Rafael J. Wysocki Signed-off-by: Darren Hart (VMware) --- drivers/platform/x86/wmi.c | 101 +++++++++++++++++++++++++++++++++++++++++++-- include/linux/wmi.h | 6 +++ 2 files changed, 103 insertions(+), 4 deletions(-) (limited to 'include') diff --git a/drivers/platform/x86/wmi.c b/drivers/platform/x86/wmi.c index 33a3609d54db..651693a5e0ea 100644 --- a/drivers/platform/x86/wmi.c +++ b/drivers/platform/x86/wmi.c @@ -67,6 +67,8 @@ struct wmi_block { struct acpi_device *acpi_device; wmi_notify_handler handler; void *handler_data; + + bool read_takes_no_args; /* only defined if readable */ }; @@ -138,6 +140,30 @@ static bool find_guid(const char *guid_string, struct wmi_block **out) return false; } +static int get_subobj_info(acpi_handle handle, const char *pathname, + struct acpi_device_info **info) +{ + struct acpi_device_info *dummy_info, **info_ptr; + acpi_handle subobj_handle; + acpi_status status; + + status = acpi_get_handle(handle, (char *)pathname, &subobj_handle); + if (status == AE_NOT_FOUND) + return -ENOENT; + else if (ACPI_FAILURE(status)) + return -EIO; + + info_ptr = info ? info : &dummy_info; + status = acpi_get_object_info(subobj_handle, info_ptr); + if (ACPI_FAILURE(status)) + return -EIO; + + if (!info) + kfree(dummy_info); + + return 0; +} + static acpi_status wmi_method_enable(struct wmi_block *wblock, int enable) { struct guid_block *block = NULL; @@ -261,6 +287,9 @@ struct acpi_buffer *out) wq_params[0].type = ACPI_TYPE_INTEGER; wq_params[0].integer.value = instance; + if (instance == 0 && wblock->read_takes_no_args) + input.count = 0; + /* * If ACPI_WMI_EXPENSIVE, call the relevant WCxx method first to * enable collection. @@ -628,11 +657,37 @@ static ssize_t object_id_show(struct device *dev, struct device_attribute *attr, } static DEVICE_ATTR_RO(object_id); -static struct attribute *wmi_data_or_method_attrs[] = { +static ssize_t readable_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct wmi_device *wdev = dev_to_wdev(dev); + + return sprintf(buf, "%d\n", (int)wdev->readable); +} +static DEVICE_ATTR_RO(readable); + +static ssize_t writeable_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct wmi_device *wdev = dev_to_wdev(dev); + + return sprintf(buf, "%d\n", (int)wdev->writeable); +} +static DEVICE_ATTR_RO(writeable); + +static struct attribute *wmi_data_attrs[] = { + &dev_attr_object_id.attr, + &dev_attr_readable.attr, + &dev_attr_writeable.attr, + NULL, +}; +ATTRIBUTE_GROUPS(wmi_data); + +static struct attribute *wmi_method_attrs[] = { &dev_attr_object_id.attr, NULL, }; -ATTRIBUTE_GROUPS(wmi_data_or_method); +ATTRIBUTE_GROUPS(wmi_method); static int wmi_dev_uevent(struct device *dev, struct kobj_uevent_env *env) { @@ -731,13 +786,13 @@ static struct device_type wmi_type_event = { static struct device_type wmi_type_method = { .name = "method", - .groups = wmi_data_or_method_groups, + .groups = wmi_method_groups, .release = wmi_dev_release, }; static struct device_type wmi_type_data = { .name = "data", - .groups = wmi_data_or_method_groups, + .groups = wmi_data_groups, .release = wmi_dev_release, }; @@ -756,7 +811,45 @@ static int wmi_create_device(struct device *wmi_bus_dev, } else if (gblock->flags & ACPI_WMI_METHOD) { wblock->dev.dev.type = &wmi_type_method; } else { + struct acpi_device_info *info; + char method[5]; + int result; + wblock->dev.dev.type = &wmi_type_data; + + strcpy(method, "WQ"); + strncat(method, wblock->gblock.object_id, 2); + result = get_subobj_info(device->handle, method, &info); + + if (result == 0) { + wblock->dev.readable = true; + + /* + * The Microsoft documentation specifically states: + * + * Data blocks registered with only a single instance + * can ignore the parameter. + * + * ACPICA will get mad at us if we call the method + * with the wrong number of arguments, so check what + * our method expects. (On some Dell laptops, WQxx + * may not be a method at all.) + */ + if (info->type != ACPI_TYPE_METHOD || + info->param_count == 0) + wblock->read_takes_no_args = true; + + kfree(info); + } + + strcpy(method, "WS"); + strncat(method, wblock->gblock.object_id, 2); + result = get_subobj_info(device->handle, method, NULL); + + if (result == 0) { + wblock->dev.writeable = true; + } + } return device_register(&wblock->dev.dev); diff --git a/include/linux/wmi.h b/include/linux/wmi.h index 29ed34b4dae1..53095006821e 100644 --- a/include/linux/wmi.h +++ b/include/linux/wmi.h @@ -21,6 +21,12 @@ struct wmi_device { struct device dev; + + /* + * These are true for data objects that support reads and writes, + * respectively. + */ + bool readable, writeable; }; struct wmi_device_id { -- cgit v1.2.3 From 1686f5444546c3b53547aa8736afcf05833ed31a Mon Sep 17 00:00:00 2001 From: Andy Lutomirski Date: Wed, 25 Nov 2015 17:33:25 -0800 Subject: platform/x86: wmi: Incorporate acpi_install_notify_handler MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit As a platform driver, acpi_driver.notify will not be available, so use acpi_install_notify_handler as we will be converting to a platform driver. This gives event drivers a simple way to handle events. It also seems closer to what the Windows docs suggest that Windows does: it sounds like, in Windows, the mapper is responsible for called _WED before dispatching to the subdriver. Signed-off-by: Andy Lutomirski [dvhart: merge two development commits and update commit message] Cc: Andy Lutomirski Cc: Mario Limonciello Cc: Pali Rohár Cc: linux-kernel@vger.kernel.org Cc: platform-driver-x86@vger.kernel.org Cc: linux-acpi@vger.kernel.org Acked-by: Rafael J. Wysocki Signed-off-by: Darren Hart (VMware) --- drivers/platform/x86/wmi.c | 89 +++++++++++++++++++++++++++++++++++++--------- include/linux/wmi.h | 1 + 2 files changed, 73 insertions(+), 17 deletions(-) (limited to 'include') diff --git a/drivers/platform/x86/wmi.c b/drivers/platform/x86/wmi.c index fbce8765e222..4395d83ba9cc 100644 --- a/drivers/platform/x86/wmi.c +++ b/drivers/platform/x86/wmi.c @@ -93,7 +93,6 @@ MODULE_PARM_DESC(debug_dump_wdg, 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); static const struct acpi_device_id wmi_device_ids[] = { {"PNP0C14", 0}, @@ -109,7 +108,6 @@ static struct acpi_driver acpi_wmi_driver = { .ops = { .add = acpi_wmi_add, .remove = acpi_wmi_remove, - .notify = acpi_wmi_notify, }, }; @@ -1019,36 +1017,80 @@ acpi_wmi_ec_space_handler(u32 function, acpi_physical_address address, } } -static void acpi_wmi_notify(struct acpi_device *device, u32 event) +static void acpi_wmi_notify_handler(acpi_handle handle, u32 event, + void *context) { struct guid_block *block; struct wmi_block *wblock; struct list_head *p; + bool found_it = false; list_for_each(p, &wmi_block_list) { wblock = list_entry(p, struct wmi_block, list); block = &wblock->gblock; - if (wblock->acpi_device == device && + if (wblock->acpi_device->handle == handle && (block->flags & ACPI_WMI_EVENT) && - (block->notify_id == event)) { - if (wblock->handler) - wblock->handler(event, wblock->handler_data); - if (debug_event) { - pr_info("DEBUG Event GUID: %pUL\n", - wblock->gblock.guid); - } - - acpi_bus_generate_netlink_event( - device->pnp.device_class, dev_name(&device->dev), - event, 0); + (block->notify_id == event)) + { + found_it = true; break; } } + + if (!found_it) + return; + + /* If a driver is bound, then notify the driver. */ + if (wblock->dev.dev.driver) { + struct wmi_driver *driver; + struct acpi_object_list input; + union acpi_object params[1]; + struct acpi_buffer evdata = { ACPI_ALLOCATE_BUFFER, NULL }; + acpi_status status; + + driver = container_of(wblock->dev.dev.driver, + struct wmi_driver, driver); + + input.count = 1; + input.pointer = params; + params[0].type = ACPI_TYPE_INTEGER; + params[0].integer.value = event; + + status = acpi_evaluate_object(wblock->acpi_device->handle, + "_WED", &input, &evdata); + if (ACPI_FAILURE(status)) { + dev_warn(&wblock->dev.dev, + "failed to get event data\n"); + return; + } + + if (driver->notify) + driver->notify(&wblock->dev, + (union acpi_object *)evdata.pointer); + + kfree(evdata.pointer); + } else if (wblock->handler) { + /* Legacy handler */ + wblock->handler(event, wblock->handler_data); + } + + if (debug_event) { + pr_info("DEBUG Event GUID: %pUL\n", + wblock->gblock.guid); + } + + acpi_bus_generate_netlink_event( + wblock->acpi_device->pnp.device_class, + dev_name(&wblock->dev.dev), + event, 0); + } static int acpi_wmi_remove(struct acpi_device *device) { + acpi_remove_notify_handler(device->handle, ACPI_DEVICE_NOTIFY, + acpi_wmi_notify_handler); acpi_remove_address_space_handler(device->handle, ACPI_ADR_SPACE_EC, &acpi_wmi_ec_space_handler); wmi_free_devices(device); @@ -1073,11 +1115,20 @@ static int acpi_wmi_add(struct acpi_device *device) return -ENODEV; } + status = acpi_install_notify_handler(device->handle, ACPI_DEVICE_NOTIFY, + acpi_wmi_notify_handler, + NULL); + if (ACPI_FAILURE(status)) { + dev_err(&device->dev, "Error installing notify handler\n"); + error = -ENODEV; + goto err_remove_ec_handler; + } + wmi_bus_dev = device_create(&wmi_bus_class, &device->dev, MKDEV(0, 0), NULL, "wmi_bus-%s", dev_name(&device->dev)); if (IS_ERR(wmi_bus_dev)) { error = PTR_ERR(wmi_bus_dev); - goto err_remove_handler; + goto err_remove_notify_handler; } device->driver_data = wmi_bus_dev; @@ -1092,7 +1143,11 @@ static int acpi_wmi_add(struct acpi_device *device) err_remove_busdev: device_unregister(wmi_bus_dev); -err_remove_handler: +err_remove_notify_handler: + acpi_remove_notify_handler(device->handle, ACPI_DEVICE_NOTIFY, + acpi_wmi_notify_handler); + +err_remove_ec_handler: acpi_remove_address_space_handler(device->handle, ACPI_ADR_SPACE_EC, &acpi_wmi_ec_space_handler); diff --git a/include/linux/wmi.h b/include/linux/wmi.h index 53095006821e..c6eedfd94e7d 100644 --- a/include/linux/wmi.h +++ b/include/linux/wmi.h @@ -39,6 +39,7 @@ struct wmi_driver { int (*probe)(struct wmi_device *wdev); int (*remove)(struct wmi_device *wdev); + void (*notify)(struct wmi_device *device, union acpi_object *data); }; extern int __must_check __wmi_driver_register(struct wmi_driver *driver, -- cgit v1.2.3 From 56a370259db4f6204d3514431d1629e0a7135b53 Mon Sep 17 00:00:00 2001 From: Andy Lutomirski Date: Wed, 25 Nov 2015 18:19:26 -0800 Subject: platform/x86: wmi: Add a new interface to read block data MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit wmi_query_block is unnecessarily indirect. Add a straightforward method for wmi bus drivers to use to read block data. Signed-off-by: Andy Lutomirski Cc: Andy Lutomirski Cc: Mario Limonciello Cc: Pali Rohár Cc: linux-kernel@vger.kernel.org Cc: platform-driver-x86@vger.kernel.org Cc: linux-acpi@vger.kernel.org Acked-by: Rafael J. Wysocki Signed-off-by: Darren Hart (VMware) --- drivers/platform/x86/wmi.c | 54 ++++++++++++++++++++++++++++++++-------------- include/linux/wmi.h | 4 ++++ 2 files changed, 42 insertions(+), 16 deletions(-) (limited to 'include') diff --git a/drivers/platform/x86/wmi.c b/drivers/platform/x86/wmi.c index 4395d83ba9cc..f8fdd48b78c4 100644 --- a/drivers/platform/x86/wmi.c +++ b/drivers/platform/x86/wmi.c @@ -244,19 +244,10 @@ u32 method_id, const struct acpi_buffer *in, struct acpi_buffer *out) } EXPORT_SYMBOL_GPL(wmi_evaluate_method); -/** - * wmi_query_block - Return contents of a WMI block - * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba - * @instance: Instance index - * &out: Empty buffer to return the contents of the data block to - * - * Return the contents of an ACPI-WMI data block to a buffer - */ -acpi_status wmi_query_block(const char *guid_string, u8 instance, -struct acpi_buffer *out) +static acpi_status __query_block(struct wmi_block *wblock, u8 instance, + struct acpi_buffer *out) { struct guid_block *block = NULL; - struct wmi_block *wblock = NULL; acpi_handle handle; acpi_status status, wc_status = AE_ERROR; struct acpi_object_list input; @@ -264,12 +255,9 @@ struct acpi_buffer *out) char method[5]; char wc_method[5] = "WC"; - if (!guid_string || !out) + if (!out) return AE_BAD_PARAMETER; - if (!find_guid(guid_string, &wblock)) - return AE_ERROR; - block = &wblock->gblock; handle = wblock->acpi_device->handle; @@ -320,8 +308,42 @@ struct acpi_buffer *out) return status; } + +/** + * wmi_query_block - Return contents of a WMI block (deprecated) + * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba + * @instance: Instance index + * &out: Empty buffer to return the contents of the data block to + * + * Return the contents of an ACPI-WMI data block to a buffer + */ +acpi_status wmi_query_block(const char *guid_string, u8 instance, + struct acpi_buffer *out) +{ + struct wmi_block *wblock; + + if (!guid_string) + return AE_BAD_PARAMETER; + + if (!find_guid(guid_string, &wblock)) + return AE_ERROR; + + return __query_block(wblock, instance, out); +} EXPORT_SYMBOL_GPL(wmi_query_block); +union acpi_object *wmidev_block_query(struct wmi_device *wdev, u8 instance) +{ + struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL }; + struct wmi_block *wblock = container_of(wdev, struct wmi_block, dev); + + if (ACPI_FAILURE(__query_block(wblock, instance, &out))) + return NULL; + + return (union acpi_object *)out.pointer; +} +EXPORT_SYMBOL_GPL(wmidev_block_query); + /** * wmi_set_block - Write to a WMI block * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba @@ -331,7 +353,7 @@ EXPORT_SYMBOL_GPL(wmi_query_block); * Write the contents of the input buffer to an ACPI-WMI data block */ acpi_status wmi_set_block(const char *guid_string, u8 instance, -const struct acpi_buffer *in) + const struct acpi_buffer *in) { struct guid_block *block = NULL; struct wmi_block *wblock = NULL; diff --git a/include/linux/wmi.h b/include/linux/wmi.h index c6eedfd94e7d..0ab254019488 100644 --- a/include/linux/wmi.h +++ b/include/linux/wmi.h @@ -29,6 +29,10 @@ struct wmi_device { bool readable, writeable; }; +/* Caller must kfree the result. */ +extern union acpi_object *wmidev_block_query(struct wmi_device *wdev, + u8 instance); + struct wmi_device_id { const char *guid_string; }; -- cgit v1.2.3 From f63019861cd1192e546397b13f926876a93450fd Mon Sep 17 00:00:00 2001 From: Andy Lutomirski Date: Tue, 29 Dec 2015 22:53:51 -0800 Subject: platform/x86: wmi: Add an interface for subdrivers to access sibling devices MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Some subdrivers need to access sibling devices. This gives them a clean way to do so. Signed-off-by: Andy Lutomirski Cc: Andy Lutomirski Cc: Mario Limonciello Cc: Pali Rohár Cc: linux-kernel@vger.kernel.org Cc: platform-driver-x86@vger.kernel.org Cc: linux-acpi@vger.kernel.org Acked-by: Rafael J. Wysocki Signed-off-by: Darren Hart (VMware) --- drivers/platform/x86/wmi.c | 17 +++++++++++++++++ include/linux/wmi.h | 4 ++++ 2 files changed, 21 insertions(+) (limited to 'include') diff --git a/drivers/platform/x86/wmi.c b/drivers/platform/x86/wmi.c index 52452ec5099b..250d7b2398b4 100644 --- a/drivers/platform/x86/wmi.c +++ b/drivers/platform/x86/wmi.c @@ -344,6 +344,23 @@ union acpi_object *wmidev_block_query(struct wmi_device *wdev, u8 instance) } EXPORT_SYMBOL_GPL(wmidev_block_query); +struct wmi_device *wmidev_get_other_guid(struct wmi_device *wdev, + const char *guid_string) +{ + struct wmi_block *this_wb = container_of(wdev, struct wmi_block, dev); + struct wmi_block *other_wb; + + if (!find_guid(guid_string, &other_wb)) + return NULL; + + if (other_wb->acpi_device != this_wb->acpi_device) + return NULL; + + get_device(&other_wb->dev.dev); + return &other_wb->dev; +} +EXPORT_SYMBOL_GPL(wmidev_get_other_guid); + /** * wmi_set_block - Write to a WMI block * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba diff --git a/include/linux/wmi.h b/include/linux/wmi.h index 0ab254019488..a283768afb7e 100644 --- a/include/linux/wmi.h +++ b/include/linux/wmi.h @@ -33,6 +33,10 @@ struct wmi_device { extern union acpi_object *wmidev_block_query(struct wmi_device *wdev, u8 instance); +/* Gets another device on the same bus. Caller must put_device the result. */ +extern struct wmi_device *wmidev_get_other_guid(struct wmi_device *wdev, + const char *guid_string); + struct wmi_device_id { const char *guid_string; }; -- cgit v1.2.3 From fd70da6a6267c91fbdda9c560f098cfd52fba00f Mon Sep 17 00:00:00 2001 From: "Darren Hart (VMware)" Date: Fri, 19 May 2017 19:28:36 -0700 Subject: platform/x86: wmi: Require query for data blocks, rename writable to setable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Microsoft WMI documentation requires all data blocks to implement the Query Control Method (WQxx). If we encounter a data block not implementing this control method, issue a warning, and ignore the data block. Remove the "readable" attribute as all data blocks must be readable (query-able). Be consistent with the language in the documentation, replace the "writable" attribute with "setable". Simplify (flatten) the control flow of wmi_create_device a bit while we are updating it for the above changes. Signed-off-by: Darren Hart (VMware) Cc: Andy Lutomirski Cc: Mario Limonciello Cc: Pali Rohár Cc: linux-kernel@vger.kernel.org Cc: platform-driver-x86@vger.kernel.org Cc: linux-acpi@vger.kernel.org Acked-by: Rafael J. Wysocki --- drivers/platform/x86/wmi.c | 117 +++++++++++++++++++++++---------------------- include/linux/wmi.h | 7 +-- 2 files changed, 63 insertions(+), 61 deletions(-) (limited to 'include') diff --git a/drivers/platform/x86/wmi.c b/drivers/platform/x86/wmi.c index 250d7b2398b4..37f6651d1bf7 100644 --- a/drivers/platform/x86/wmi.c +++ b/drivers/platform/x86/wmi.c @@ -69,7 +69,7 @@ struct wmi_block { wmi_notify_handler handler; void *handler_data; - bool read_takes_no_args; /* only defined if readable */ + bool read_takes_no_args; }; @@ -694,28 +694,18 @@ static ssize_t object_id_show(struct device *dev, struct device_attribute *attr, } static DEVICE_ATTR_RO(object_id); -static ssize_t readable_show(struct device *dev, struct device_attribute *attr, - char *buf) +static ssize_t setable_show(struct device *dev, struct device_attribute *attr, + char *buf) { struct wmi_device *wdev = dev_to_wdev(dev); - return sprintf(buf, "%d\n", (int)wdev->readable); + return sprintf(buf, "%d\n", (int)wdev->setable); } -static DEVICE_ATTR_RO(readable); - -static ssize_t writeable_show(struct device *dev, struct device_attribute *attr, - char *buf) -{ - struct wmi_device *wdev = dev_to_wdev(dev); - - return sprintf(buf, "%d\n", (int)wdev->writeable); -} -static DEVICE_ATTR_RO(writeable); +static DEVICE_ATTR_RO(setable); static struct attribute *wmi_data_attrs[] = { &dev_attr_object_id.attr, - &dev_attr_readable.attr, - &dev_attr_writeable.attr, + &dev_attr_setable.attr, NULL, }; ATTRIBUTE_GROUPS(wmi_data); @@ -833,63 +823,74 @@ static struct device_type wmi_type_data = { .release = wmi_dev_release, }; -static void wmi_create_device(struct device *wmi_bus_dev, +static int wmi_create_device(struct device *wmi_bus_dev, const struct guid_block *gblock, struct wmi_block *wblock, struct acpi_device *device) { - wblock->dev.dev.bus = &wmi_bus_type; - wblock->dev.dev.parent = wmi_bus_dev; - - dev_set_name(&wblock->dev.dev, "%pUL", gblock->guid); + struct acpi_device_info *info; + char method[5]; + int result; if (gblock->flags & ACPI_WMI_EVENT) { wblock->dev.dev.type = &wmi_type_event; - } else if (gblock->flags & ACPI_WMI_METHOD) { + goto out_init; + } + + if (gblock->flags & ACPI_WMI_METHOD) { wblock->dev.dev.type = &wmi_type_method; - } else { - struct acpi_device_info *info; - char method[5]; - int result; + goto out_init; + } - wblock->dev.dev.type = &wmi_type_data; + /* + * Data Block Query Control Method (WQxx by convention) is + * required per the WMI documentation. If it is not present, + * we ignore this data block. + */ + strcpy(method, "WQ"); + strncat(method, wblock->gblock.object_id, 2); + result = get_subobj_info(device->handle, method, &info); + + if (result) { + dev_warn(wmi_bus_dev, + "%s data block query control method not found", + method); + return result; + } - strcpy(method, "WQ"); - strncat(method, wblock->gblock.object_id, 2); - result = get_subobj_info(device->handle, method, &info); + wblock->dev.dev.type = &wmi_type_data; - if (result == 0) { - wblock->dev.readable = true; + /* + * The Microsoft documentation specifically states: + * + * Data blocks registered with only a single instance + * can ignore the parameter. + * + * ACPICA will get mad at us if we call the method with the wrong number + * of arguments, so check what our method expects. (On some Dell + * laptops, WQxx may not be a method at all.) + */ + if (info->type != ACPI_TYPE_METHOD || info->param_count == 0) + wblock->read_takes_no_args = true; - /* - * The Microsoft documentation specifically states: - * - * Data blocks registered with only a single instance - * can ignore the parameter. - * - * ACPICA will get mad at us if we call the method - * with the wrong number of arguments, so check what - * our method expects. (On some Dell laptops, WQxx - * may not be a method at all.) - */ - if (info->type != ACPI_TYPE_METHOD || - info->param_count == 0) - wblock->read_takes_no_args = true; + kfree(info); - kfree(info); - } + strcpy(method, "WS"); + strncat(method, wblock->gblock.object_id, 2); + result = get_subobj_info(device->handle, method, NULL); - strcpy(method, "WS"); - strncat(method, wblock->gblock.object_id, 2); - result = get_subobj_info(device->handle, method, NULL); + if (result == 0) + wblock->dev.setable = true; - if (result == 0) { - wblock->dev.writeable = true; - } + out_init: + wblock->dev.dev.bus = &wmi_bus_type; + wblock->dev.dev.parent = wmi_bus_dev; - } + dev_set_name(&wblock->dev.dev, "%pUL", gblock->guid); device_initialize(&wblock->dev.dev); + + return 0; } static void wmi_free_devices(struct acpi_device *device) @@ -978,7 +979,11 @@ static int parse_wdg(struct device *wmi_bus_dev, struct acpi_device *device) wblock->acpi_device = device; wblock->gblock = gblock[i]; - wmi_create_device(wmi_bus_dev, &gblock[i], wblock, device); + retval = wmi_create_device(wmi_bus_dev, &gblock[i], wblock, device); + if (retval) { + kfree(wblock); + continue; + } list_add_tail(&wblock->list, &wmi_block_list); diff --git a/include/linux/wmi.h b/include/linux/wmi.h index a283768afb7e..cd0d7734dc49 100644 --- a/include/linux/wmi.h +++ b/include/linux/wmi.h @@ -22,11 +22,8 @@ struct wmi_device { struct device dev; - /* - * These are true for data objects that support reads and writes, - * respectively. - */ - bool readable, writeable; + /* True for data blocks implementing the Set Control Method */ + bool setable; }; /* Caller must kfree the result. */ -- cgit v1.2.3