1b4f9fe12SLen Brown /* 2b4f9fe12SLen Brown * ACPI-WMI mapping driver 3b4f9fe12SLen Brown * 4b4f9fe12SLen Brown * Copyright (C) 2007-2008 Carlos Corbacho <carlos@strangeworlds.co.uk> 5b4f9fe12SLen Brown * 6b4f9fe12SLen Brown * GUID parsing code from ldm.c is: 7b4f9fe12SLen Brown * Copyright (C) 2001,2002 Richard Russon <ldm@flatcap.org> 8b4f9fe12SLen Brown * Copyright (c) 2001-2007 Anton Altaparmakov 9b4f9fe12SLen Brown * Copyright (C) 2001,2002 Jakob Kemi <jakob.kemi@telia.com> 10b4f9fe12SLen Brown * 112c9c5664SDarren Hart (VMware) * WMI bus infrastructure by Andrew Lutomirski and Darren Hart: 122c9c5664SDarren Hart (VMware) * Copyright (C) 2015 Andrew Lutomirski 132c9c5664SDarren Hart (VMware) * Copyright (C) 2017 VMware, Inc. All Rights Reserved. 142c9c5664SDarren Hart (VMware) * 15b4f9fe12SLen Brown * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 16b4f9fe12SLen Brown * 17b4f9fe12SLen Brown * This program is free software; you can redistribute it and/or modify 18b4f9fe12SLen Brown * it under the terms of the GNU General Public License as published by 19b4f9fe12SLen Brown * the Free Software Foundation; either version 2 of the License, or (at 20b4f9fe12SLen Brown * your option) any later version. 21b4f9fe12SLen Brown * 22b4f9fe12SLen Brown * This program is distributed in the hope that it will be useful, but 23b4f9fe12SLen Brown * WITHOUT ANY WARRANTY; without even the implied warranty of 24b4f9fe12SLen Brown * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 25b4f9fe12SLen Brown * General Public License for more details. 26b4f9fe12SLen Brown * 27b4f9fe12SLen Brown * You should have received a copy of the GNU General Public License along 28b4f9fe12SLen Brown * with this program; if not, write to the Free Software Foundation, Inc., 29b4f9fe12SLen Brown * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. 30b4f9fe12SLen Brown * 31b4f9fe12SLen Brown * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 32b4f9fe12SLen Brown */ 33b4f9fe12SLen Brown 348e07514dSDmitry Torokhov #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 358e07514dSDmitry Torokhov 36b4f9fe12SLen Brown #include <linux/acpi.h> 37b60ee4e0SMario Limonciello #include <linux/device.h> 38b60ee4e0SMario Limonciello #include <linux/init.h> 39b60ee4e0SMario Limonciello #include <linux/kernel.h> 40b60ee4e0SMario Limonciello #include <linux/list.h> 4144b6b766SMario Limonciello #include <linux/miscdevice.h> 427c52d551SPaul Gortmaker #include <linux/module.h> 439599ed91SAndy Lutomirski #include <linux/platform_device.h> 44b60ee4e0SMario Limonciello #include <linux/slab.h> 45b60ee4e0SMario Limonciello #include <linux/types.h> 4644b6b766SMario Limonciello #include <linux/uaccess.h> 47538d7eb8SAndy Shevchenko #include <linux/uuid.h> 48b60ee4e0SMario Limonciello #include <linux/wmi.h> 4944b6b766SMario Limonciello #include <uapi/linux/wmi.h> 50b4f9fe12SLen Brown 51b4f9fe12SLen Brown ACPI_MODULE_NAME("wmi"); 52b4f9fe12SLen Brown MODULE_AUTHOR("Carlos Corbacho"); 53b4f9fe12SLen Brown MODULE_DESCRIPTION("ACPI-WMI Mapping Driver"); 54b4f9fe12SLen Brown MODULE_LICENSE("GPL"); 55b4f9fe12SLen Brown 56762e1a2fSDmitry Torokhov static LIST_HEAD(wmi_block_list); 57b4f9fe12SLen Brown 58b4f9fe12SLen Brown struct guid_block { 59b4f9fe12SLen Brown char guid[16]; 60b4f9fe12SLen Brown union { 61b4f9fe12SLen Brown char object_id[2]; 62b4f9fe12SLen Brown struct { 63b4f9fe12SLen Brown unsigned char notify_id; 64b4f9fe12SLen Brown unsigned char reserved; 65b4f9fe12SLen Brown }; 66b4f9fe12SLen Brown }; 67b4f9fe12SLen Brown u8 instance_count; 68b4f9fe12SLen Brown u8 flags; 69b4f9fe12SLen Brown }; 70b4f9fe12SLen Brown 71b4f9fe12SLen Brown struct wmi_block { 72844af950SAndy Lutomirski struct wmi_device dev; 73b4f9fe12SLen Brown struct list_head list; 74b4f9fe12SLen Brown struct guid_block gblock; 7544b6b766SMario Limonciello struct miscdevice char_dev; 7644b6b766SMario Limonciello struct mutex char_mutex; 77b0e86302SAndy Lutomirski struct acpi_device *acpi_device; 78b4f9fe12SLen Brown wmi_notify_handler handler; 79b4f9fe12SLen Brown void *handler_data; 8044b6b766SMario Limonciello u64 req_buf_size; 81d4fc91adSAndy Lutomirski 82fd70da6aSDarren Hart (VMware) bool read_takes_no_args; 83b4f9fe12SLen Brown }; 84b4f9fe12SLen Brown 85b4f9fe12SLen Brown 86b4f9fe12SLen Brown /* 87b4f9fe12SLen Brown * If the GUID data block is marked as expensive, we must enable and 88b4f9fe12SLen Brown * explicitily disable data collection. 89b4f9fe12SLen Brown */ 90b4f9fe12SLen Brown #define ACPI_WMI_EXPENSIVE 0x1 91b4f9fe12SLen Brown #define ACPI_WMI_METHOD 0x2 /* GUID is a method */ 92b4f9fe12SLen Brown #define ACPI_WMI_STRING 0x4 /* GUID takes & returns a string */ 93b4f9fe12SLen Brown #define ACPI_WMI_EVENT 0x8 /* GUID is an event */ 94b4f9fe12SLen Brown 9590ab5ee9SRusty Russell static bool debug_event; 96fc3155b2SThomas Renninger module_param(debug_event, bool, 0444); 97fc3155b2SThomas Renninger MODULE_PARM_DESC(debug_event, 98fc3155b2SThomas Renninger "Log WMI Events [0/1]"); 99fc3155b2SThomas Renninger 10090ab5ee9SRusty Russell static bool debug_dump_wdg; 101a929aae0SThomas Renninger module_param(debug_dump_wdg, bool, 0444); 102a929aae0SThomas Renninger MODULE_PARM_DESC(debug_dump_wdg, 103a929aae0SThomas Renninger "Dump available WMI interfaces [0/1]"); 104a929aae0SThomas Renninger 1059599ed91SAndy Lutomirski static int acpi_wmi_remove(struct platform_device *device); 1069599ed91SAndy Lutomirski static int acpi_wmi_probe(struct platform_device *device); 107b4f9fe12SLen Brown 108b4f9fe12SLen Brown static const struct acpi_device_id wmi_device_ids[] = { 109b4f9fe12SLen Brown {"PNP0C14", 0}, 110b4f9fe12SLen Brown {"pnp0c14", 0}, 111b4f9fe12SLen Brown {"", 0}, 112b4f9fe12SLen Brown }; 113b4f9fe12SLen Brown MODULE_DEVICE_TABLE(acpi, wmi_device_ids); 114b4f9fe12SLen Brown 1159599ed91SAndy Lutomirski static struct platform_driver acpi_wmi_driver = { 1169599ed91SAndy Lutomirski .driver = { 117844af950SAndy Lutomirski .name = "acpi-wmi", 1189599ed91SAndy Lutomirski .acpi_match_table = wmi_device_ids, 119b4f9fe12SLen Brown }, 1209599ed91SAndy Lutomirski .probe = acpi_wmi_probe, 1219599ed91SAndy Lutomirski .remove = acpi_wmi_remove, 122b4f9fe12SLen Brown }; 123b4f9fe12SLen Brown 124b4f9fe12SLen Brown /* 125b4f9fe12SLen Brown * GUID parsing functions 126b4f9fe12SLen Brown */ 127b4f9fe12SLen Brown 128b4f9fe12SLen Brown static bool find_guid(const char *guid_string, struct wmi_block **out) 129b4f9fe12SLen Brown { 130538d7eb8SAndy Shevchenko uuid_le guid_input; 131b4f9fe12SLen Brown struct wmi_block *wblock; 132b4f9fe12SLen Brown struct guid_block *block; 133b4f9fe12SLen Brown 134538d7eb8SAndy Shevchenko if (uuid_le_to_bin(guid_string, &guid_input)) 135538d7eb8SAndy Shevchenko return false; 136b4f9fe12SLen Brown 137cedb3b2aSAndy Shevchenko list_for_each_entry(wblock, &wmi_block_list, list) { 138b4f9fe12SLen Brown block = &wblock->gblock; 139b4f9fe12SLen Brown 140538d7eb8SAndy Shevchenko if (memcmp(block->guid, &guid_input, 16) == 0) { 141b4f9fe12SLen Brown if (out) 142b4f9fe12SLen Brown *out = wblock; 143097c27fcSJoe Perches return true; 144b4f9fe12SLen Brown } 145b4f9fe12SLen Brown } 146097c27fcSJoe Perches return false; 147b4f9fe12SLen Brown } 148b4f9fe12SLen Brown 149d4fc91adSAndy Lutomirski static int get_subobj_info(acpi_handle handle, const char *pathname, 150d4fc91adSAndy Lutomirski struct acpi_device_info **info) 151d4fc91adSAndy Lutomirski { 152d4fc91adSAndy Lutomirski struct acpi_device_info *dummy_info, **info_ptr; 153d4fc91adSAndy Lutomirski acpi_handle subobj_handle; 154d4fc91adSAndy Lutomirski acpi_status status; 155d4fc91adSAndy Lutomirski 156d4fc91adSAndy Lutomirski status = acpi_get_handle(handle, (char *)pathname, &subobj_handle); 157d4fc91adSAndy Lutomirski if (status == AE_NOT_FOUND) 158d4fc91adSAndy Lutomirski return -ENOENT; 159d4fc91adSAndy Lutomirski else if (ACPI_FAILURE(status)) 160d4fc91adSAndy Lutomirski return -EIO; 161d4fc91adSAndy Lutomirski 162d4fc91adSAndy Lutomirski info_ptr = info ? info : &dummy_info; 163d4fc91adSAndy Lutomirski status = acpi_get_object_info(subobj_handle, info_ptr); 164d4fc91adSAndy Lutomirski if (ACPI_FAILURE(status)) 165d4fc91adSAndy Lutomirski return -EIO; 166d4fc91adSAndy Lutomirski 167d4fc91adSAndy Lutomirski if (!info) 168d4fc91adSAndy Lutomirski kfree(dummy_info); 169d4fc91adSAndy Lutomirski 170d4fc91adSAndy Lutomirski return 0; 171d4fc91adSAndy Lutomirski } 172d4fc91adSAndy Lutomirski 173b4f9fe12SLen Brown static acpi_status wmi_method_enable(struct wmi_block *wblock, int enable) 174b4f9fe12SLen Brown { 175b4f9fe12SLen Brown struct guid_block *block = NULL; 176b4f9fe12SLen Brown char method[5]; 177b4f9fe12SLen Brown acpi_status status; 178b4f9fe12SLen Brown acpi_handle handle; 179b4f9fe12SLen Brown 180b4f9fe12SLen Brown block = &wblock->gblock; 181b0e86302SAndy Lutomirski handle = wblock->acpi_device->handle; 182b4f9fe12SLen Brown 183b4f9fe12SLen Brown snprintf(method, 5, "WE%02X", block->notify_id); 1848122ab66SZhang Rui status = acpi_execute_simple_method(handle, method, enable); 185b4f9fe12SLen Brown 186b4f9fe12SLen Brown if (status != AE_OK && status != AE_NOT_FOUND) 187b4f9fe12SLen Brown return status; 188b4f9fe12SLen Brown else 189b4f9fe12SLen Brown return AE_OK; 190b4f9fe12SLen Brown } 191b4f9fe12SLen Brown 192b4f9fe12SLen Brown /* 193b4f9fe12SLen Brown * Exported WMI functions 194b4f9fe12SLen Brown */ 19544b6b766SMario Limonciello 19644b6b766SMario Limonciello /** 19744b6b766SMario Limonciello * set_required_buffer_size - Sets the buffer size needed for performing IOCTL 19844b6b766SMario Limonciello * @wdev: A wmi bus device from a driver 19944b6b766SMario Limonciello * @instance: Instance index 20044b6b766SMario Limonciello * 20144b6b766SMario Limonciello * Allocates memory needed for buffer, stores the buffer size in that memory 20244b6b766SMario Limonciello */ 20344b6b766SMario Limonciello int set_required_buffer_size(struct wmi_device *wdev, u64 length) 20444b6b766SMario Limonciello { 20544b6b766SMario Limonciello struct wmi_block *wblock; 20644b6b766SMario Limonciello 20744b6b766SMario Limonciello wblock = container_of(wdev, struct wmi_block, dev); 20844b6b766SMario Limonciello wblock->req_buf_size = length; 20944b6b766SMario Limonciello 21044b6b766SMario Limonciello return 0; 21144b6b766SMario Limonciello } 21244b6b766SMario Limonciello EXPORT_SYMBOL_GPL(set_required_buffer_size); 21344b6b766SMario Limonciello 214b4f9fe12SLen Brown /** 215b4f9fe12SLen Brown * wmi_evaluate_method - Evaluate a WMI method 216b4f9fe12SLen Brown * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba 217b4f9fe12SLen Brown * @instance: Instance index 218b4f9fe12SLen Brown * @method_id: Method ID to call 219b4f9fe12SLen Brown * &in: Buffer containing input for the method call 220b4f9fe12SLen Brown * &out: Empty buffer to return the method results 221b4f9fe12SLen Brown * 222b4f9fe12SLen Brown * Call an ACPI-WMI method 223b4f9fe12SLen Brown */ 224b4f9fe12SLen Brown acpi_status wmi_evaluate_method(const char *guid_string, u8 instance, 225b4f9fe12SLen Brown u32 method_id, const struct acpi_buffer *in, struct acpi_buffer *out) 226b4f9fe12SLen Brown { 227722c856dSMario Limonciello struct wmi_block *wblock = NULL; 228722c856dSMario Limonciello 229722c856dSMario Limonciello if (!find_guid(guid_string, &wblock)) 230722c856dSMario Limonciello return AE_ERROR; 231722c856dSMario Limonciello return wmidev_evaluate_method(&wblock->dev, instance, method_id, 232722c856dSMario Limonciello in, out); 233722c856dSMario Limonciello } 234722c856dSMario Limonciello EXPORT_SYMBOL_GPL(wmi_evaluate_method); 235722c856dSMario Limonciello 236722c856dSMario Limonciello /** 237722c856dSMario Limonciello * wmidev_evaluate_method - Evaluate a WMI method 238722c856dSMario Limonciello * @wdev: A wmi bus device from a driver 239722c856dSMario Limonciello * @instance: Instance index 240722c856dSMario Limonciello * @method_id: Method ID to call 241722c856dSMario Limonciello * &in: Buffer containing input for the method call 242722c856dSMario Limonciello * &out: Empty buffer to return the method results 243722c856dSMario Limonciello * 244722c856dSMario Limonciello * Call an ACPI-WMI method 245722c856dSMario Limonciello */ 246722c856dSMario Limonciello acpi_status wmidev_evaluate_method(struct wmi_device *wdev, u8 instance, 247722c856dSMario Limonciello u32 method_id, const struct acpi_buffer *in, struct acpi_buffer *out) 248722c856dSMario Limonciello { 249b4f9fe12SLen Brown struct guid_block *block = NULL; 250b4f9fe12SLen Brown struct wmi_block *wblock = NULL; 251b4f9fe12SLen Brown acpi_handle handle; 252b4f9fe12SLen Brown acpi_status status; 253b4f9fe12SLen Brown struct acpi_object_list input; 254b4f9fe12SLen Brown union acpi_object params[3]; 255f3d83e24SCostantino Leandro char method[5] = "WM"; 256b4f9fe12SLen Brown 257722c856dSMario Limonciello wblock = container_of(wdev, struct wmi_block, dev); 258b4f9fe12SLen Brown block = &wblock->gblock; 259b0e86302SAndy Lutomirski handle = wblock->acpi_device->handle; 260b4f9fe12SLen Brown 261b4f9fe12SLen Brown if (!(block->flags & ACPI_WMI_METHOD)) 262b4f9fe12SLen Brown return AE_BAD_DATA; 263b4f9fe12SLen Brown 2646afa1e2aSPali Rohár if (block->instance_count <= instance) 265b4f9fe12SLen Brown return AE_BAD_PARAMETER; 266b4f9fe12SLen Brown 267b4f9fe12SLen Brown input.count = 2; 268b4f9fe12SLen Brown input.pointer = params; 269b4f9fe12SLen Brown params[0].type = ACPI_TYPE_INTEGER; 270b4f9fe12SLen Brown params[0].integer.value = instance; 271b4f9fe12SLen Brown params[1].type = ACPI_TYPE_INTEGER; 272b4f9fe12SLen Brown params[1].integer.value = method_id; 273b4f9fe12SLen Brown 274b4f9fe12SLen Brown if (in) { 275b4f9fe12SLen Brown input.count = 3; 276b4f9fe12SLen Brown 277b4f9fe12SLen Brown if (block->flags & ACPI_WMI_STRING) { 278b4f9fe12SLen Brown params[2].type = ACPI_TYPE_STRING; 279b4f9fe12SLen Brown } else { 280b4f9fe12SLen Brown params[2].type = ACPI_TYPE_BUFFER; 281b4f9fe12SLen Brown } 282b4f9fe12SLen Brown params[2].buffer.length = in->length; 283b4f9fe12SLen Brown params[2].buffer.pointer = in->pointer; 284b4f9fe12SLen Brown } 285b4f9fe12SLen Brown 286b4f9fe12SLen Brown strncat(method, block->object_id, 2); 287b4f9fe12SLen Brown 288b4f9fe12SLen Brown status = acpi_evaluate_object(handle, method, &input, out); 289b4f9fe12SLen Brown 290b4f9fe12SLen Brown return status; 291b4f9fe12SLen Brown } 292722c856dSMario Limonciello EXPORT_SYMBOL_GPL(wmidev_evaluate_method); 293b4f9fe12SLen Brown 29456a37025SAndy Lutomirski static acpi_status __query_block(struct wmi_block *wblock, u8 instance, 295b4f9fe12SLen Brown struct acpi_buffer *out) 296b4f9fe12SLen Brown { 297b4f9fe12SLen Brown struct guid_block *block = NULL; 29854f14c27SZhang Rui acpi_handle handle; 299b4f9fe12SLen Brown acpi_status status, wc_status = AE_ERROR; 3008122ab66SZhang Rui struct acpi_object_list input; 3018122ab66SZhang Rui union acpi_object wq_params[1]; 302f3d83e24SCostantino Leandro char method[5]; 303f3d83e24SCostantino Leandro char wc_method[5] = "WC"; 304b4f9fe12SLen Brown 30556a37025SAndy Lutomirski if (!out) 306b4f9fe12SLen Brown return AE_BAD_PARAMETER; 307b4f9fe12SLen Brown 308b4f9fe12SLen Brown block = &wblock->gblock; 309b0e86302SAndy Lutomirski handle = wblock->acpi_device->handle; 310b4f9fe12SLen Brown 3116afa1e2aSPali Rohár if (block->instance_count <= instance) 312b4f9fe12SLen Brown return AE_BAD_PARAMETER; 313b4f9fe12SLen Brown 314b4f9fe12SLen Brown /* Check GUID is a data block */ 315b4f9fe12SLen Brown if (block->flags & (ACPI_WMI_EVENT | ACPI_WMI_METHOD)) 316b4f9fe12SLen Brown return AE_ERROR; 317b4f9fe12SLen Brown 318b4f9fe12SLen Brown input.count = 1; 319b4f9fe12SLen Brown input.pointer = wq_params; 320b4f9fe12SLen Brown wq_params[0].type = ACPI_TYPE_INTEGER; 321b4f9fe12SLen Brown wq_params[0].integer.value = instance; 322b4f9fe12SLen Brown 323d4fc91adSAndy Lutomirski if (instance == 0 && wblock->read_takes_no_args) 324d4fc91adSAndy Lutomirski input.count = 0; 325d4fc91adSAndy Lutomirski 326b4f9fe12SLen Brown /* 327b4f9fe12SLen Brown * If ACPI_WMI_EXPENSIVE, call the relevant WCxx method first to 328b4f9fe12SLen Brown * enable collection. 329b4f9fe12SLen Brown */ 330b4f9fe12SLen Brown if (block->flags & ACPI_WMI_EXPENSIVE) { 331b4f9fe12SLen Brown strncat(wc_method, block->object_id, 2); 332b4f9fe12SLen Brown 333b4f9fe12SLen Brown /* 334b4f9fe12SLen Brown * Some GUIDs break the specification by declaring themselves 335b4f9fe12SLen Brown * expensive, but have no corresponding WCxx method. So we 336b4f9fe12SLen Brown * should not fail if this happens. 337b4f9fe12SLen Brown */ 33854f14c27SZhang Rui if (acpi_has_method(handle, wc_method)) 3398122ab66SZhang Rui wc_status = acpi_execute_simple_method(handle, 3408122ab66SZhang Rui wc_method, 1); 341b4f9fe12SLen Brown } 342b4f9fe12SLen Brown 343b4f9fe12SLen Brown strcpy(method, "WQ"); 344b4f9fe12SLen Brown strncat(method, block->object_id, 2); 345b4f9fe12SLen Brown 346b4f9fe12SLen Brown status = acpi_evaluate_object(handle, method, &input, out); 347b4f9fe12SLen Brown 348b4f9fe12SLen Brown /* 349b4f9fe12SLen Brown * If ACPI_WMI_EXPENSIVE, call the relevant WCxx method, even if 350b4f9fe12SLen Brown * the WQxx method failed - we should disable collection anyway. 351b4f9fe12SLen Brown */ 352b4f9fe12SLen Brown if ((block->flags & ACPI_WMI_EXPENSIVE) && ACPI_SUCCESS(wc_status)) { 3538122ab66SZhang Rui status = acpi_execute_simple_method(handle, wc_method, 0); 354b4f9fe12SLen Brown } 355b4f9fe12SLen Brown 356b4f9fe12SLen Brown return status; 357b4f9fe12SLen Brown } 35856a37025SAndy Lutomirski 35956a37025SAndy Lutomirski /** 36056a37025SAndy Lutomirski * wmi_query_block - Return contents of a WMI block (deprecated) 36156a37025SAndy Lutomirski * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba 36256a37025SAndy Lutomirski * @instance: Instance index 36356a37025SAndy Lutomirski * &out: Empty buffer to return the contents of the data block to 36456a37025SAndy Lutomirski * 36556a37025SAndy Lutomirski * Return the contents of an ACPI-WMI data block to a buffer 36656a37025SAndy Lutomirski */ 36756a37025SAndy Lutomirski acpi_status wmi_query_block(const char *guid_string, u8 instance, 36856a37025SAndy Lutomirski struct acpi_buffer *out) 36956a37025SAndy Lutomirski { 37056a37025SAndy Lutomirski struct wmi_block *wblock; 37156a37025SAndy Lutomirski 37256a37025SAndy Lutomirski if (!guid_string) 37356a37025SAndy Lutomirski return AE_BAD_PARAMETER; 37456a37025SAndy Lutomirski 37556a37025SAndy Lutomirski if (!find_guid(guid_string, &wblock)) 37656a37025SAndy Lutomirski return AE_ERROR; 37756a37025SAndy Lutomirski 37856a37025SAndy Lutomirski return __query_block(wblock, instance, out); 37956a37025SAndy Lutomirski } 380b4f9fe12SLen Brown EXPORT_SYMBOL_GPL(wmi_query_block); 381b4f9fe12SLen Brown 38256a37025SAndy Lutomirski union acpi_object *wmidev_block_query(struct wmi_device *wdev, u8 instance) 38356a37025SAndy Lutomirski { 38456a37025SAndy Lutomirski struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL }; 38556a37025SAndy Lutomirski struct wmi_block *wblock = container_of(wdev, struct wmi_block, dev); 38656a37025SAndy Lutomirski 38756a37025SAndy Lutomirski if (ACPI_FAILURE(__query_block(wblock, instance, &out))) 38856a37025SAndy Lutomirski return NULL; 38956a37025SAndy Lutomirski 39056a37025SAndy Lutomirski return (union acpi_object *)out.pointer; 39156a37025SAndy Lutomirski } 39256a37025SAndy Lutomirski EXPORT_SYMBOL_GPL(wmidev_block_query); 39356a37025SAndy Lutomirski 394b4f9fe12SLen Brown /** 395b4f9fe12SLen Brown * wmi_set_block - Write to a WMI block 396b4f9fe12SLen Brown * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba 397b4f9fe12SLen Brown * @instance: Instance index 398b4f9fe12SLen Brown * &in: Buffer containing new values for the data block 399b4f9fe12SLen Brown * 400b4f9fe12SLen Brown * Write the contents of the input buffer to an ACPI-WMI data block 401b4f9fe12SLen Brown */ 402b4f9fe12SLen Brown acpi_status wmi_set_block(const char *guid_string, u8 instance, 403b4f9fe12SLen Brown const struct acpi_buffer *in) 404b4f9fe12SLen Brown { 405b4f9fe12SLen Brown struct guid_block *block = NULL; 406b4f9fe12SLen Brown struct wmi_block *wblock = NULL; 407b4f9fe12SLen Brown acpi_handle handle; 408b4f9fe12SLen Brown struct acpi_object_list input; 409b4f9fe12SLen Brown union acpi_object params[2]; 410f3d83e24SCostantino Leandro char method[5] = "WS"; 411b4f9fe12SLen Brown 412b4f9fe12SLen Brown if (!guid_string || !in) 413b4f9fe12SLen Brown return AE_BAD_DATA; 414b4f9fe12SLen Brown 415b4f9fe12SLen Brown if (!find_guid(guid_string, &wblock)) 416b4f9fe12SLen Brown return AE_ERROR; 417b4f9fe12SLen Brown 418b4f9fe12SLen Brown block = &wblock->gblock; 419b0e86302SAndy Lutomirski handle = wblock->acpi_device->handle; 420b4f9fe12SLen Brown 4216afa1e2aSPali Rohár if (block->instance_count <= instance) 422b4f9fe12SLen Brown return AE_BAD_PARAMETER; 423b4f9fe12SLen Brown 424b4f9fe12SLen Brown /* Check GUID is a data block */ 425b4f9fe12SLen Brown if (block->flags & (ACPI_WMI_EVENT | ACPI_WMI_METHOD)) 426b4f9fe12SLen Brown return AE_ERROR; 427b4f9fe12SLen Brown 428b4f9fe12SLen Brown input.count = 2; 429b4f9fe12SLen Brown input.pointer = params; 430b4f9fe12SLen Brown params[0].type = ACPI_TYPE_INTEGER; 431b4f9fe12SLen Brown params[0].integer.value = instance; 432b4f9fe12SLen Brown 433b4f9fe12SLen Brown if (block->flags & ACPI_WMI_STRING) { 434b4f9fe12SLen Brown params[1].type = ACPI_TYPE_STRING; 435b4f9fe12SLen Brown } else { 436b4f9fe12SLen Brown params[1].type = ACPI_TYPE_BUFFER; 437b4f9fe12SLen Brown } 438b4f9fe12SLen Brown params[1].buffer.length = in->length; 439b4f9fe12SLen Brown params[1].buffer.pointer = in->pointer; 440b4f9fe12SLen Brown 441b4f9fe12SLen Brown strncat(method, block->object_id, 2); 442b4f9fe12SLen Brown 443b4f9fe12SLen Brown return acpi_evaluate_object(handle, method, &input, NULL); 444b4f9fe12SLen Brown } 445b4f9fe12SLen Brown EXPORT_SYMBOL_GPL(wmi_set_block); 446b4f9fe12SLen Brown 44737830662SDmitry Torokhov static void wmi_dump_wdg(const struct guid_block *g) 448a929aae0SThomas Renninger { 44985b4e4ebSRasmus Villemoes pr_info("%pUL:\n", g->guid); 450cd3921f8SPali Rohár if (g->flags & ACPI_WMI_EVENT) 451cd3921f8SPali Rohár pr_info("\tnotify_id: 0x%02X\n", g->notify_id); 452cd3921f8SPali Rohár else 453cd3921f8SPali Rohár pr_info("\tobject_id: %2pE\n", g->object_id); 4548e07514dSDmitry Torokhov pr_info("\tinstance_count: %d\n", g->instance_count); 4558e07514dSDmitry Torokhov pr_info("\tflags: %#x", g->flags); 456a929aae0SThomas Renninger if (g->flags) { 457a929aae0SThomas Renninger if (g->flags & ACPI_WMI_EXPENSIVE) 4588e07514dSDmitry Torokhov pr_cont(" ACPI_WMI_EXPENSIVE"); 459a929aae0SThomas Renninger if (g->flags & ACPI_WMI_METHOD) 4608e07514dSDmitry Torokhov pr_cont(" ACPI_WMI_METHOD"); 461a929aae0SThomas Renninger if (g->flags & ACPI_WMI_STRING) 4628e07514dSDmitry Torokhov pr_cont(" ACPI_WMI_STRING"); 463a929aae0SThomas Renninger if (g->flags & ACPI_WMI_EVENT) 4648e07514dSDmitry Torokhov pr_cont(" ACPI_WMI_EVENT"); 465a929aae0SThomas Renninger } 4668e07514dSDmitry Torokhov pr_cont("\n"); 467a929aae0SThomas Renninger 468a929aae0SThomas Renninger } 469a929aae0SThomas Renninger 470fc3155b2SThomas Renninger static void wmi_notify_debug(u32 value, void *context) 471fc3155b2SThomas Renninger { 472fc3155b2SThomas Renninger struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL }; 473fc3155b2SThomas Renninger union acpi_object *obj; 4741492616aSAxel Lin acpi_status status; 475fc3155b2SThomas Renninger 4761492616aSAxel Lin status = wmi_get_event_data(value, &response); 4771492616aSAxel Lin if (status != AE_OK) { 4788e07514dSDmitry Torokhov pr_info("bad event status 0x%x\n", status); 4791492616aSAxel Lin return; 4801492616aSAxel Lin } 481fc3155b2SThomas Renninger 482fc3155b2SThomas Renninger obj = (union acpi_object *)response.pointer; 483fc3155b2SThomas Renninger 484fc3155b2SThomas Renninger if (!obj) 485fc3155b2SThomas Renninger return; 486fc3155b2SThomas Renninger 4878e07514dSDmitry Torokhov pr_info("DEBUG Event "); 488fc3155b2SThomas Renninger switch(obj->type) { 489fc3155b2SThomas Renninger case ACPI_TYPE_BUFFER: 4908e07514dSDmitry Torokhov pr_cont("BUFFER_TYPE - length %d\n", obj->buffer.length); 491fc3155b2SThomas Renninger break; 492fc3155b2SThomas Renninger case ACPI_TYPE_STRING: 4938e07514dSDmitry Torokhov pr_cont("STRING_TYPE - %s\n", obj->string.pointer); 494fc3155b2SThomas Renninger break; 495fc3155b2SThomas Renninger case ACPI_TYPE_INTEGER: 4968e07514dSDmitry Torokhov pr_cont("INTEGER_TYPE - %llu\n", obj->integer.value); 497fc3155b2SThomas Renninger break; 498fc3155b2SThomas Renninger case ACPI_TYPE_PACKAGE: 4998e07514dSDmitry Torokhov pr_cont("PACKAGE_TYPE - %d elements\n", obj->package.count); 500fc3155b2SThomas Renninger break; 501fc3155b2SThomas Renninger default: 5028e07514dSDmitry Torokhov pr_cont("object type 0x%X\n", obj->type); 503fc3155b2SThomas Renninger } 5041492616aSAxel Lin kfree(obj); 505fc3155b2SThomas Renninger } 506fc3155b2SThomas Renninger 507b4f9fe12SLen Brown /** 508b4f9fe12SLen Brown * wmi_install_notify_handler - Register handler for WMI events 509b4f9fe12SLen Brown * @handler: Function to handle notifications 510b4f9fe12SLen Brown * @data: Data to be returned to handler when event is fired 511b4f9fe12SLen Brown * 512b4f9fe12SLen Brown * Register a handler for events sent to the ACPI-WMI mapper device. 513b4f9fe12SLen Brown */ 514b4f9fe12SLen Brown acpi_status wmi_install_notify_handler(const char *guid, 515b4f9fe12SLen Brown wmi_notify_handler handler, void *data) 516b4f9fe12SLen Brown { 517b4f9fe12SLen Brown struct wmi_block *block; 51858f6425eSColin King acpi_status status = AE_NOT_EXIST; 519538d7eb8SAndy Shevchenko uuid_le guid_input; 520b4f9fe12SLen Brown 521b4f9fe12SLen Brown if (!guid || !handler) 522b4f9fe12SLen Brown return AE_BAD_PARAMETER; 523b4f9fe12SLen Brown 524538d7eb8SAndy Shevchenko if (uuid_le_to_bin(guid, &guid_input)) 525538d7eb8SAndy Shevchenko return AE_BAD_PARAMETER; 526b4f9fe12SLen Brown 527cedb3b2aSAndy Shevchenko list_for_each_entry(block, &wmi_block_list, list) { 52858f6425eSColin King acpi_status wmi_status; 52958f6425eSColin King 530538d7eb8SAndy Shevchenko if (memcmp(block->gblock.guid, &guid_input, 16) == 0) { 53158f6425eSColin King if (block->handler && 53258f6425eSColin King block->handler != wmi_notify_debug) 533b4f9fe12SLen Brown return AE_ALREADY_ACQUIRED; 534b4f9fe12SLen Brown 535b4f9fe12SLen Brown block->handler = handler; 536b4f9fe12SLen Brown block->handler_data = data; 537b4f9fe12SLen Brown 53858f6425eSColin King wmi_status = wmi_method_enable(block, 1); 53958f6425eSColin King if ((wmi_status != AE_OK) || 54058f6425eSColin King ((wmi_status == AE_OK) && (status == AE_NOT_EXIST))) 54158f6425eSColin King status = wmi_status; 54258f6425eSColin King } 54358f6425eSColin King } 544b4f9fe12SLen Brown 545b4f9fe12SLen Brown return status; 546b4f9fe12SLen Brown } 547b4f9fe12SLen Brown EXPORT_SYMBOL_GPL(wmi_install_notify_handler); 548b4f9fe12SLen Brown 549b4f9fe12SLen Brown /** 550b4f9fe12SLen Brown * wmi_uninstall_notify_handler - Unregister handler for WMI events 551b4f9fe12SLen Brown * 552b4f9fe12SLen Brown * Unregister handler for events sent to the ACPI-WMI mapper device. 553b4f9fe12SLen Brown */ 554b4f9fe12SLen Brown acpi_status wmi_remove_notify_handler(const char *guid) 555b4f9fe12SLen Brown { 556b4f9fe12SLen Brown struct wmi_block *block; 55758f6425eSColin King acpi_status status = AE_NOT_EXIST; 558538d7eb8SAndy Shevchenko uuid_le guid_input; 559b4f9fe12SLen Brown 560b4f9fe12SLen Brown if (!guid) 561b4f9fe12SLen Brown return AE_BAD_PARAMETER; 562b4f9fe12SLen Brown 563538d7eb8SAndy Shevchenko if (uuid_le_to_bin(guid, &guid_input)) 564538d7eb8SAndy Shevchenko return AE_BAD_PARAMETER; 565b4f9fe12SLen Brown 566cedb3b2aSAndy Shevchenko list_for_each_entry(block, &wmi_block_list, list) { 56758f6425eSColin King acpi_status wmi_status; 56858f6425eSColin King 569538d7eb8SAndy Shevchenko if (memcmp(block->gblock.guid, &guid_input, 16) == 0) { 57058f6425eSColin King if (!block->handler || 57158f6425eSColin King block->handler == wmi_notify_debug) 572b4f9fe12SLen Brown return AE_NULL_ENTRY; 573b4f9fe12SLen Brown 574fc3155b2SThomas Renninger if (debug_event) { 575fc3155b2SThomas Renninger block->handler = wmi_notify_debug; 57658f6425eSColin King status = AE_OK; 577fc3155b2SThomas Renninger } else { 57858f6425eSColin King wmi_status = wmi_method_enable(block, 0); 579b4f9fe12SLen Brown block->handler = NULL; 580b4f9fe12SLen Brown block->handler_data = NULL; 58158f6425eSColin King if ((wmi_status != AE_OK) || 58258f6425eSColin King ((wmi_status == AE_OK) && 58358f6425eSColin King (status == AE_NOT_EXIST))) 58458f6425eSColin King status = wmi_status; 585fc3155b2SThomas Renninger } 58658f6425eSColin King } 58758f6425eSColin King } 58858f6425eSColin King 589b4f9fe12SLen Brown return status; 590b4f9fe12SLen Brown } 591b4f9fe12SLen Brown EXPORT_SYMBOL_GPL(wmi_remove_notify_handler); 592b4f9fe12SLen Brown 593b4f9fe12SLen Brown /** 594b4f9fe12SLen Brown * wmi_get_event_data - Get WMI data associated with an event 595b4f9fe12SLen Brown * 5963e9b988eSAnisse Astier * @event: Event to find 5973e9b988eSAnisse Astier * @out: Buffer to hold event data. out->pointer should be freed with kfree() 598b4f9fe12SLen Brown * 599b4f9fe12SLen Brown * Returns extra data associated with an event in WMI. 600b4f9fe12SLen Brown */ 601b4f9fe12SLen Brown acpi_status wmi_get_event_data(u32 event, struct acpi_buffer *out) 602b4f9fe12SLen Brown { 603b4f9fe12SLen Brown struct acpi_object_list input; 604b4f9fe12SLen Brown union acpi_object params[1]; 605b4f9fe12SLen Brown struct guid_block *gblock; 606b4f9fe12SLen Brown struct wmi_block *wblock; 607b4f9fe12SLen Brown 608b4f9fe12SLen Brown input.count = 1; 609b4f9fe12SLen Brown input.pointer = params; 610b4f9fe12SLen Brown params[0].type = ACPI_TYPE_INTEGER; 611b4f9fe12SLen Brown params[0].integer.value = event; 612b4f9fe12SLen Brown 613cedb3b2aSAndy Shevchenko list_for_each_entry(wblock, &wmi_block_list, list) { 614b4f9fe12SLen Brown gblock = &wblock->gblock; 615b4f9fe12SLen Brown 616b4f9fe12SLen Brown if ((gblock->flags & ACPI_WMI_EVENT) && 617b4f9fe12SLen Brown (gblock->notify_id == event)) 618b0e86302SAndy Lutomirski return acpi_evaluate_object(wblock->acpi_device->handle, 619b0e86302SAndy Lutomirski "_WED", &input, out); 620b4f9fe12SLen Brown } 621b4f9fe12SLen Brown 622b4f9fe12SLen Brown return AE_NOT_FOUND; 623b4f9fe12SLen Brown } 624b4f9fe12SLen Brown EXPORT_SYMBOL_GPL(wmi_get_event_data); 625b4f9fe12SLen Brown 626b4f9fe12SLen Brown /** 627b4f9fe12SLen Brown * wmi_has_guid - Check if a GUID is available 628b4f9fe12SLen Brown * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba 629b4f9fe12SLen Brown * 630b4f9fe12SLen Brown * Check if a given GUID is defined by _WDG 631b4f9fe12SLen Brown */ 632b4f9fe12SLen Brown bool wmi_has_guid(const char *guid_string) 633b4f9fe12SLen Brown { 634b4f9fe12SLen Brown return find_guid(guid_string, NULL); 635b4f9fe12SLen Brown } 636b4f9fe12SLen Brown EXPORT_SYMBOL_GPL(wmi_has_guid); 637b4f9fe12SLen Brown 638e7488e58SYurii Pavlovskyi /** 639e7488e58SYurii Pavlovskyi * wmi_get_acpi_device_uid() - Get _UID name of ACPI device that defines GUID 640e7488e58SYurii Pavlovskyi * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba 641e7488e58SYurii Pavlovskyi * 642e7488e58SYurii Pavlovskyi * Find the _UID of ACPI device associated with this WMI GUID. 643e7488e58SYurii Pavlovskyi * 644e7488e58SYurii Pavlovskyi * Return: The ACPI _UID field value or NULL if the WMI GUID was not found 645e7488e58SYurii Pavlovskyi */ 646e7488e58SYurii Pavlovskyi char *wmi_get_acpi_device_uid(const char *guid_string) 647e7488e58SYurii Pavlovskyi { 648e7488e58SYurii Pavlovskyi struct wmi_block *wblock = NULL; 649e7488e58SYurii Pavlovskyi 650e7488e58SYurii Pavlovskyi if (!find_guid(guid_string, &wblock)) 651e7488e58SYurii Pavlovskyi return NULL; 652e7488e58SYurii Pavlovskyi 653e7488e58SYurii Pavlovskyi return acpi_device_uid(wblock->acpi_device); 654e7488e58SYurii Pavlovskyi } 655e7488e58SYurii Pavlovskyi EXPORT_SYMBOL_GPL(wmi_get_acpi_device_uid); 656e7488e58SYurii Pavlovskyi 657844af950SAndy Lutomirski static struct wmi_block *dev_to_wblock(struct device *dev) 658844af950SAndy Lutomirski { 659844af950SAndy Lutomirski return container_of(dev, struct wmi_block, dev.dev); 660844af950SAndy Lutomirski } 661844af950SAndy Lutomirski 662844af950SAndy Lutomirski static struct wmi_device *dev_to_wdev(struct device *dev) 663844af950SAndy Lutomirski { 664844af950SAndy Lutomirski return container_of(dev, struct wmi_device, dev); 665844af950SAndy Lutomirski } 666844af950SAndy Lutomirski 667b4f9fe12SLen Brown /* 6681caab3c1SMatthew Garrett * sysfs interface 6691caab3c1SMatthew Garrett */ 670614ef432SDmitry Torokhov static ssize_t modalias_show(struct device *dev, struct device_attribute *attr, 6711caab3c1SMatthew Garrett char *buf) 6721caab3c1SMatthew Garrett { 673844af950SAndy Lutomirski struct wmi_block *wblock = dev_to_wblock(dev); 6741caab3c1SMatthew Garrett 67585b4e4ebSRasmus Villemoes return sprintf(buf, "wmi:%pUL\n", wblock->gblock.guid); 6761caab3c1SMatthew Garrett } 677e80b89a5SGreg Kroah-Hartman static DEVICE_ATTR_RO(modalias); 678614ef432SDmitry Torokhov 679844af950SAndy Lutomirski static ssize_t guid_show(struct device *dev, struct device_attribute *attr, 680844af950SAndy Lutomirski char *buf) 681844af950SAndy Lutomirski { 682844af950SAndy Lutomirski struct wmi_block *wblock = dev_to_wblock(dev); 683844af950SAndy Lutomirski 684844af950SAndy Lutomirski return sprintf(buf, "%pUL\n", wblock->gblock.guid); 685844af950SAndy Lutomirski } 686844af950SAndy Lutomirski static DEVICE_ATTR_RO(guid); 687844af950SAndy Lutomirski 688d79b1074SAndy Lutomirski static ssize_t instance_count_show(struct device *dev, 689d79b1074SAndy Lutomirski struct device_attribute *attr, char *buf) 690d79b1074SAndy Lutomirski { 691d79b1074SAndy Lutomirski struct wmi_block *wblock = dev_to_wblock(dev); 692d79b1074SAndy Lutomirski 693d79b1074SAndy Lutomirski return sprintf(buf, "%d\n", (int)wblock->gblock.instance_count); 694d79b1074SAndy Lutomirski } 695d79b1074SAndy Lutomirski static DEVICE_ATTR_RO(instance_count); 696d79b1074SAndy Lutomirski 697d79b1074SAndy Lutomirski static ssize_t expensive_show(struct device *dev, 698d79b1074SAndy Lutomirski struct device_attribute *attr, char *buf) 699d79b1074SAndy Lutomirski { 700d79b1074SAndy Lutomirski struct wmi_block *wblock = dev_to_wblock(dev); 701d79b1074SAndy Lutomirski 702d79b1074SAndy Lutomirski return sprintf(buf, "%d\n", 703d79b1074SAndy Lutomirski (wblock->gblock.flags & ACPI_WMI_EXPENSIVE) != 0); 704d79b1074SAndy Lutomirski } 705d79b1074SAndy Lutomirski static DEVICE_ATTR_RO(expensive); 706d79b1074SAndy Lutomirski 707e80b89a5SGreg Kroah-Hartman static struct attribute *wmi_attrs[] = { 708e80b89a5SGreg Kroah-Hartman &dev_attr_modalias.attr, 709844af950SAndy Lutomirski &dev_attr_guid.attr, 710d79b1074SAndy Lutomirski &dev_attr_instance_count.attr, 711d79b1074SAndy Lutomirski &dev_attr_expensive.attr, 712e80b89a5SGreg Kroah-Hartman NULL, 713614ef432SDmitry Torokhov }; 714e80b89a5SGreg Kroah-Hartman ATTRIBUTE_GROUPS(wmi); 7151caab3c1SMatthew Garrett 716d79b1074SAndy Lutomirski static ssize_t notify_id_show(struct device *dev, struct device_attribute *attr, 717d79b1074SAndy Lutomirski char *buf) 718d79b1074SAndy Lutomirski { 719d79b1074SAndy Lutomirski struct wmi_block *wblock = dev_to_wblock(dev); 720d79b1074SAndy Lutomirski 721d79b1074SAndy Lutomirski return sprintf(buf, "%02X\n", (unsigned int)wblock->gblock.notify_id); 722d79b1074SAndy Lutomirski } 723d79b1074SAndy Lutomirski static DEVICE_ATTR_RO(notify_id); 724d79b1074SAndy Lutomirski 725d79b1074SAndy Lutomirski static struct attribute *wmi_event_attrs[] = { 726d79b1074SAndy Lutomirski &dev_attr_notify_id.attr, 727d79b1074SAndy Lutomirski NULL, 728d79b1074SAndy Lutomirski }; 729d79b1074SAndy Lutomirski ATTRIBUTE_GROUPS(wmi_event); 730d79b1074SAndy Lutomirski 731d79b1074SAndy Lutomirski static ssize_t object_id_show(struct device *dev, struct device_attribute *attr, 732d79b1074SAndy Lutomirski char *buf) 733d79b1074SAndy Lutomirski { 734d79b1074SAndy Lutomirski struct wmi_block *wblock = dev_to_wblock(dev); 735d79b1074SAndy Lutomirski 736d79b1074SAndy Lutomirski return sprintf(buf, "%c%c\n", wblock->gblock.object_id[0], 737d79b1074SAndy Lutomirski wblock->gblock.object_id[1]); 738d79b1074SAndy Lutomirski } 739d79b1074SAndy Lutomirski static DEVICE_ATTR_RO(object_id); 740d79b1074SAndy Lutomirski 741fd70da6aSDarren Hart (VMware) static ssize_t setable_show(struct device *dev, struct device_attribute *attr, 742d4fc91adSAndy Lutomirski char *buf) 743d4fc91adSAndy Lutomirski { 744d4fc91adSAndy Lutomirski struct wmi_device *wdev = dev_to_wdev(dev); 745d4fc91adSAndy Lutomirski 746fd70da6aSDarren Hart (VMware) return sprintf(buf, "%d\n", (int)wdev->setable); 747d4fc91adSAndy Lutomirski } 748fd70da6aSDarren Hart (VMware) static DEVICE_ATTR_RO(setable); 749d4fc91adSAndy Lutomirski 750d4fc91adSAndy Lutomirski static struct attribute *wmi_data_attrs[] = { 751d4fc91adSAndy Lutomirski &dev_attr_object_id.attr, 752fd70da6aSDarren Hart (VMware) &dev_attr_setable.attr, 753d4fc91adSAndy Lutomirski NULL, 754d4fc91adSAndy Lutomirski }; 755d4fc91adSAndy Lutomirski ATTRIBUTE_GROUPS(wmi_data); 756d4fc91adSAndy Lutomirski 757d4fc91adSAndy Lutomirski static struct attribute *wmi_method_attrs[] = { 758d79b1074SAndy Lutomirski &dev_attr_object_id.attr, 759d79b1074SAndy Lutomirski NULL, 760d79b1074SAndy Lutomirski }; 761d4fc91adSAndy Lutomirski ATTRIBUTE_GROUPS(wmi_method); 762d79b1074SAndy Lutomirski 7631caab3c1SMatthew Garrett static int wmi_dev_uevent(struct device *dev, struct kobj_uevent_env *env) 7641caab3c1SMatthew Garrett { 765844af950SAndy Lutomirski struct wmi_block *wblock = dev_to_wblock(dev); 7661caab3c1SMatthew Garrett 767844af950SAndy Lutomirski if (add_uevent_var(env, "MODALIAS=wmi:%pUL", wblock->gblock.guid)) 7681caab3c1SMatthew Garrett return -ENOMEM; 7691caab3c1SMatthew Garrett 770844af950SAndy Lutomirski if (add_uevent_var(env, "WMI_GUID=%pUL", wblock->gblock.guid)) 7711caab3c1SMatthew Garrett return -ENOMEM; 7721caab3c1SMatthew Garrett 7731caab3c1SMatthew Garrett return 0; 7741caab3c1SMatthew Garrett } 7751caab3c1SMatthew Garrett 776844af950SAndy Lutomirski static void wmi_dev_release(struct device *dev) 7771caab3c1SMatthew Garrett { 778844af950SAndy Lutomirski struct wmi_block *wblock = dev_to_wblock(dev); 779c64eefd4SDmitry Torokhov 780844af950SAndy Lutomirski kfree(wblock); 7811caab3c1SMatthew Garrett } 7821caab3c1SMatthew Garrett 783844af950SAndy Lutomirski static int wmi_dev_match(struct device *dev, struct device_driver *driver) 784844af950SAndy Lutomirski { 785844af950SAndy Lutomirski struct wmi_driver *wmi_driver = 786844af950SAndy Lutomirski container_of(driver, struct wmi_driver, driver); 787844af950SAndy Lutomirski struct wmi_block *wblock = dev_to_wblock(dev); 788844af950SAndy Lutomirski const struct wmi_device_id *id = wmi_driver->id_table; 789844af950SAndy Lutomirski 790c355ec65SMattias Jacobsson if (id == NULL) 791c355ec65SMattias Jacobsson return 0; 792c355ec65SMattias Jacobsson 793eacc95eaSMattias Jacobsson while (*id->guid_string) { 794844af950SAndy Lutomirski uuid_le driver_guid; 795844af950SAndy Lutomirski 796844af950SAndy Lutomirski if (WARN_ON(uuid_le_to_bin(id->guid_string, &driver_guid))) 797844af950SAndy Lutomirski continue; 798844af950SAndy Lutomirski if (!memcmp(&driver_guid, wblock->gblock.guid, 16)) 799844af950SAndy Lutomirski return 1; 800844af950SAndy Lutomirski 801844af950SAndy Lutomirski id++; 802844af950SAndy Lutomirski } 803844af950SAndy Lutomirski 804844af950SAndy Lutomirski return 0; 805844af950SAndy Lutomirski } 80644b6b766SMario Limonciello static int wmi_char_open(struct inode *inode, struct file *filp) 80744b6b766SMario Limonciello { 80844b6b766SMario Limonciello const char *driver_name = filp->f_path.dentry->d_iname; 80944b6b766SMario Limonciello struct wmi_block *wblock = NULL; 81044b6b766SMario Limonciello struct wmi_block *next = NULL; 81144b6b766SMario Limonciello 81244b6b766SMario Limonciello list_for_each_entry_safe(wblock, next, &wmi_block_list, list) { 81344b6b766SMario Limonciello if (!wblock->dev.dev.driver) 81444b6b766SMario Limonciello continue; 81544b6b766SMario Limonciello if (strcmp(driver_name, wblock->dev.dev.driver->name) == 0) { 81644b6b766SMario Limonciello filp->private_data = wblock; 81744b6b766SMario Limonciello break; 81844b6b766SMario Limonciello } 81944b6b766SMario Limonciello } 82044b6b766SMario Limonciello 82144b6b766SMario Limonciello if (!filp->private_data) 82244b6b766SMario Limonciello return -ENODEV; 82344b6b766SMario Limonciello 82444b6b766SMario Limonciello return nonseekable_open(inode, filp); 82544b6b766SMario Limonciello } 82644b6b766SMario Limonciello 82744b6b766SMario Limonciello static ssize_t wmi_char_read(struct file *filp, char __user *buffer, 82844b6b766SMario Limonciello size_t length, loff_t *offset) 82944b6b766SMario Limonciello { 83044b6b766SMario Limonciello struct wmi_block *wblock = filp->private_data; 83144b6b766SMario Limonciello 83244b6b766SMario Limonciello return simple_read_from_buffer(buffer, length, offset, 83344b6b766SMario Limonciello &wblock->req_buf_size, 83444b6b766SMario Limonciello sizeof(wblock->req_buf_size)); 83544b6b766SMario Limonciello } 83644b6b766SMario Limonciello 83744b6b766SMario Limonciello static long wmi_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) 83844b6b766SMario Limonciello { 83944b6b766SMario Limonciello struct wmi_ioctl_buffer __user *input = 84044b6b766SMario Limonciello (struct wmi_ioctl_buffer __user *) arg; 84144b6b766SMario Limonciello struct wmi_block *wblock = filp->private_data; 84244b6b766SMario Limonciello struct wmi_ioctl_buffer *buf = NULL; 84344b6b766SMario Limonciello struct wmi_driver *wdriver = NULL; 84444b6b766SMario Limonciello int ret; 84544b6b766SMario Limonciello 84644b6b766SMario Limonciello if (_IOC_TYPE(cmd) != WMI_IOC) 84744b6b766SMario Limonciello return -ENOTTY; 84844b6b766SMario Limonciello 84944b6b766SMario Limonciello /* make sure we're not calling a higher instance than exists*/ 85044b6b766SMario Limonciello if (_IOC_NR(cmd) >= wblock->gblock.instance_count) 85144b6b766SMario Limonciello return -EINVAL; 85244b6b766SMario Limonciello 85344b6b766SMario Limonciello mutex_lock(&wblock->char_mutex); 85444b6b766SMario Limonciello buf = wblock->handler_data; 85544b6b766SMario Limonciello if (get_user(buf->length, &input->length)) { 85644b6b766SMario Limonciello dev_dbg(&wblock->dev.dev, "Read length from user failed\n"); 85744b6b766SMario Limonciello ret = -EFAULT; 85844b6b766SMario Limonciello goto out_ioctl; 85944b6b766SMario Limonciello } 86044b6b766SMario Limonciello /* if it's too small, abort */ 86144b6b766SMario Limonciello if (buf->length < wblock->req_buf_size) { 86244b6b766SMario Limonciello dev_err(&wblock->dev.dev, 86344b6b766SMario Limonciello "Buffer %lld too small, need at least %lld\n", 86444b6b766SMario Limonciello buf->length, wblock->req_buf_size); 86544b6b766SMario Limonciello ret = -EINVAL; 86644b6b766SMario Limonciello goto out_ioctl; 86744b6b766SMario Limonciello } 86844b6b766SMario Limonciello /* if it's too big, warn, driver will only use what is needed */ 86944b6b766SMario Limonciello if (buf->length > wblock->req_buf_size) 87044b6b766SMario Limonciello dev_warn(&wblock->dev.dev, 87144b6b766SMario Limonciello "Buffer %lld is bigger than required %lld\n", 87244b6b766SMario Limonciello buf->length, wblock->req_buf_size); 87344b6b766SMario Limonciello 87444b6b766SMario Limonciello /* copy the structure from userspace */ 87544b6b766SMario Limonciello if (copy_from_user(buf, input, wblock->req_buf_size)) { 87644b6b766SMario Limonciello dev_dbg(&wblock->dev.dev, "Copy %llu from user failed\n", 87744b6b766SMario Limonciello wblock->req_buf_size); 87844b6b766SMario Limonciello ret = -EFAULT; 87944b6b766SMario Limonciello goto out_ioctl; 88044b6b766SMario Limonciello } 88144b6b766SMario Limonciello 88244b6b766SMario Limonciello /* let the driver do any filtering and do the call */ 88344b6b766SMario Limonciello wdriver = container_of(wblock->dev.dev.driver, 88444b6b766SMario Limonciello struct wmi_driver, driver); 8855e3e2297SMario Limonciello if (!try_module_get(wdriver->driver.owner)) { 8865e3e2297SMario Limonciello ret = -EBUSY; 8875e3e2297SMario Limonciello goto out_ioctl; 8885e3e2297SMario Limonciello } 88944b6b766SMario Limonciello ret = wdriver->filter_callback(&wblock->dev, cmd, buf); 89044b6b766SMario Limonciello module_put(wdriver->driver.owner); 89144b6b766SMario Limonciello if (ret) 89244b6b766SMario Limonciello goto out_ioctl; 89344b6b766SMario Limonciello 89444b6b766SMario Limonciello /* return the result (only up to our internal buffer size) */ 89544b6b766SMario Limonciello if (copy_to_user(input, buf, wblock->req_buf_size)) { 89644b6b766SMario Limonciello dev_dbg(&wblock->dev.dev, "Copy %llu to user failed\n", 89744b6b766SMario Limonciello wblock->req_buf_size); 89844b6b766SMario Limonciello ret = -EFAULT; 89944b6b766SMario Limonciello } 90044b6b766SMario Limonciello 90144b6b766SMario Limonciello out_ioctl: 90244b6b766SMario Limonciello mutex_unlock(&wblock->char_mutex); 90344b6b766SMario Limonciello return ret; 90444b6b766SMario Limonciello } 90544b6b766SMario Limonciello 90644b6b766SMario Limonciello static const struct file_operations wmi_fops = { 90744b6b766SMario Limonciello .owner = THIS_MODULE, 90844b6b766SMario Limonciello .read = wmi_char_read, 90944b6b766SMario Limonciello .open = wmi_char_open, 91044b6b766SMario Limonciello .unlocked_ioctl = wmi_ioctl, 91144b6b766SMario Limonciello .compat_ioctl = wmi_ioctl, 91244b6b766SMario Limonciello }; 913844af950SAndy Lutomirski 914844af950SAndy Lutomirski static int wmi_dev_probe(struct device *dev) 915844af950SAndy Lutomirski { 916844af950SAndy Lutomirski struct wmi_block *wblock = dev_to_wblock(dev); 917844af950SAndy Lutomirski struct wmi_driver *wdriver = 918844af950SAndy Lutomirski container_of(dev->driver, struct wmi_driver, driver); 919844af950SAndy Lutomirski int ret = 0; 92044b6b766SMario Limonciello char *buf; 921844af950SAndy Lutomirski 922844af950SAndy Lutomirski if (ACPI_FAILURE(wmi_method_enable(wblock, 1))) 923844af950SAndy Lutomirski dev_warn(dev, "failed to enable device -- probing anyway\n"); 924844af950SAndy Lutomirski 925844af950SAndy Lutomirski if (wdriver->probe) { 926844af950SAndy Lutomirski ret = wdriver->probe(dev_to_wdev(dev)); 92744b6b766SMario Limonciello if (ret != 0) 92844b6b766SMario Limonciello goto probe_failure; 929844af950SAndy Lutomirski } 930844af950SAndy Lutomirski 93144b6b766SMario Limonciello /* driver wants a character device made */ 93244b6b766SMario Limonciello if (wdriver->filter_callback) { 93344b6b766SMario Limonciello /* check that required buffer size declared by driver or MOF */ 93444b6b766SMario Limonciello if (!wblock->req_buf_size) { 93544b6b766SMario Limonciello dev_err(&wblock->dev.dev, 93644b6b766SMario Limonciello "Required buffer size not set\n"); 93744b6b766SMario Limonciello ret = -EINVAL; 93844b6b766SMario Limonciello goto probe_failure; 93944b6b766SMario Limonciello } 94044b6b766SMario Limonciello 9416fb74107SKees Cook wblock->handler_data = kmalloc(wblock->req_buf_size, 9426fb74107SKees Cook GFP_KERNEL); 94344b6b766SMario Limonciello if (!wblock->handler_data) { 94444b6b766SMario Limonciello ret = -ENOMEM; 94544b6b766SMario Limonciello goto probe_failure; 94644b6b766SMario Limonciello } 94744b6b766SMario Limonciello 9487f166addSAndy Shevchenko buf = kasprintf(GFP_KERNEL, "wmi/%s", wdriver->driver.name); 94944b6b766SMario Limonciello if (!buf) { 95044b6b766SMario Limonciello ret = -ENOMEM; 95144b6b766SMario Limonciello goto probe_string_failure; 95244b6b766SMario Limonciello } 95344b6b766SMario Limonciello wblock->char_dev.minor = MISC_DYNAMIC_MINOR; 95444b6b766SMario Limonciello wblock->char_dev.name = buf; 95544b6b766SMario Limonciello wblock->char_dev.fops = &wmi_fops; 95644b6b766SMario Limonciello wblock->char_dev.mode = 0444; 95744b6b766SMario Limonciello ret = misc_register(&wblock->char_dev); 95844b6b766SMario Limonciello if (ret) { 959501f7e52SJoe Perches dev_warn(dev, "failed to register char dev: %d\n", ret); 96044b6b766SMario Limonciello ret = -ENOMEM; 96144b6b766SMario Limonciello goto probe_misc_failure; 96244b6b766SMario Limonciello } 96344b6b766SMario Limonciello } 96444b6b766SMario Limonciello 96544b6b766SMario Limonciello return 0; 96644b6b766SMario Limonciello 96744b6b766SMario Limonciello probe_misc_failure: 96844b6b766SMario Limonciello kfree(buf); 96944b6b766SMario Limonciello probe_string_failure: 97044b6b766SMario Limonciello kfree(wblock->handler_data); 97144b6b766SMario Limonciello probe_failure: 97244b6b766SMario Limonciello if (ACPI_FAILURE(wmi_method_enable(wblock, 0))) 97344b6b766SMario Limonciello dev_warn(dev, "failed to disable device\n"); 974844af950SAndy Lutomirski return ret; 975844af950SAndy Lutomirski } 976844af950SAndy Lutomirski 977844af950SAndy Lutomirski static int wmi_dev_remove(struct device *dev) 978844af950SAndy Lutomirski { 979844af950SAndy Lutomirski struct wmi_block *wblock = dev_to_wblock(dev); 980844af950SAndy Lutomirski struct wmi_driver *wdriver = 981844af950SAndy Lutomirski container_of(dev->driver, struct wmi_driver, driver); 982844af950SAndy Lutomirski int ret = 0; 983844af950SAndy Lutomirski 98444b6b766SMario Limonciello if (wdriver->filter_callback) { 98544b6b766SMario Limonciello misc_deregister(&wblock->char_dev); 98644b6b766SMario Limonciello kfree(wblock->char_dev.name); 9876fb74107SKees Cook kfree(wblock->handler_data); 98844b6b766SMario Limonciello } 98944b6b766SMario Limonciello 990844af950SAndy Lutomirski if (wdriver->remove) 991844af950SAndy Lutomirski ret = wdriver->remove(dev_to_wdev(dev)); 992844af950SAndy Lutomirski 993844af950SAndy Lutomirski if (ACPI_FAILURE(wmi_method_enable(wblock, 0))) 994844af950SAndy Lutomirski dev_warn(dev, "failed to disable device\n"); 995844af950SAndy Lutomirski 996844af950SAndy Lutomirski return ret; 997844af950SAndy Lutomirski } 998844af950SAndy Lutomirski 999844af950SAndy Lutomirski static struct class wmi_bus_class = { 1000844af950SAndy Lutomirski .name = "wmi_bus", 10011caab3c1SMatthew Garrett }; 10021caab3c1SMatthew Garrett 1003844af950SAndy Lutomirski static struct bus_type wmi_bus_type = { 1004844af950SAndy Lutomirski .name = "wmi", 1005844af950SAndy Lutomirski .dev_groups = wmi_groups, 1006844af950SAndy Lutomirski .match = wmi_dev_match, 1007844af950SAndy Lutomirski .uevent = wmi_dev_uevent, 1008844af950SAndy Lutomirski .probe = wmi_dev_probe, 1009844af950SAndy Lutomirski .remove = wmi_dev_remove, 1010844af950SAndy Lutomirski }; 1011844af950SAndy Lutomirski 101269372c1dSBhumika Goyal static const struct device_type wmi_type_event = { 1013d79b1074SAndy Lutomirski .name = "event", 1014d79b1074SAndy Lutomirski .groups = wmi_event_groups, 1015d79b1074SAndy Lutomirski .release = wmi_dev_release, 1016d79b1074SAndy Lutomirski }; 1017d79b1074SAndy Lutomirski 101869372c1dSBhumika Goyal static const struct device_type wmi_type_method = { 1019d79b1074SAndy Lutomirski .name = "method", 1020d4fc91adSAndy Lutomirski .groups = wmi_method_groups, 1021d79b1074SAndy Lutomirski .release = wmi_dev_release, 1022d79b1074SAndy Lutomirski }; 1023d79b1074SAndy Lutomirski 102469372c1dSBhumika Goyal static const struct device_type wmi_type_data = { 1025d79b1074SAndy Lutomirski .name = "data", 1026d4fc91adSAndy Lutomirski .groups = wmi_data_groups, 1027d79b1074SAndy Lutomirski .release = wmi_dev_release, 1028d79b1074SAndy Lutomirski }; 1029d79b1074SAndy Lutomirski 1030fd70da6aSDarren Hart (VMware) static int wmi_create_device(struct device *wmi_bus_dev, 1031844af950SAndy Lutomirski const struct guid_block *gblock, 10327f5809bfSAndy Lutomirski struct wmi_block *wblock, 10337f5809bfSAndy Lutomirski struct acpi_device *device) 10341caab3c1SMatthew Garrett { 1035d4fc91adSAndy Lutomirski struct acpi_device_info *info; 1036d4fc91adSAndy Lutomirski char method[5]; 1037d4fc91adSAndy Lutomirski int result; 1038d4fc91adSAndy Lutomirski 1039fd70da6aSDarren Hart (VMware) if (gblock->flags & ACPI_WMI_EVENT) { 1040fd70da6aSDarren Hart (VMware) wblock->dev.dev.type = &wmi_type_event; 1041fd70da6aSDarren Hart (VMware) goto out_init; 1042fd70da6aSDarren Hart (VMware) } 1043d4fc91adSAndy Lutomirski 1044fd70da6aSDarren Hart (VMware) if (gblock->flags & ACPI_WMI_METHOD) { 1045fd70da6aSDarren Hart (VMware) wblock->dev.dev.type = &wmi_type_method; 104644b6b766SMario Limonciello mutex_init(&wblock->char_mutex); 1047fd70da6aSDarren Hart (VMware) goto out_init; 1048fd70da6aSDarren Hart (VMware) } 1049fd70da6aSDarren Hart (VMware) 1050fd70da6aSDarren Hart (VMware) /* 1051fd70da6aSDarren Hart (VMware) * Data Block Query Control Method (WQxx by convention) is 1052fd70da6aSDarren Hart (VMware) * required per the WMI documentation. If it is not present, 1053fd70da6aSDarren Hart (VMware) * we ignore this data block. 1054fd70da6aSDarren Hart (VMware) */ 1055d4fc91adSAndy Lutomirski strcpy(method, "WQ"); 1056d4fc91adSAndy Lutomirski strncat(method, wblock->gblock.object_id, 2); 1057d4fc91adSAndy Lutomirski result = get_subobj_info(device->handle, method, &info); 1058d4fc91adSAndy Lutomirski 1059fd70da6aSDarren Hart (VMware) if (result) { 1060fd70da6aSDarren Hart (VMware) dev_warn(wmi_bus_dev, 1061501f7e52SJoe Perches "%s data block query control method not found\n", 1062fd70da6aSDarren Hart (VMware) method); 1063fd70da6aSDarren Hart (VMware) return result; 1064fd70da6aSDarren Hart (VMware) } 1065fd70da6aSDarren Hart (VMware) 1066fd70da6aSDarren Hart (VMware) wblock->dev.dev.type = &wmi_type_data; 1067d4fc91adSAndy Lutomirski 1068d4fc91adSAndy Lutomirski /* 1069d4fc91adSAndy Lutomirski * The Microsoft documentation specifically states: 1070d4fc91adSAndy Lutomirski * 1071d4fc91adSAndy Lutomirski * Data blocks registered with only a single instance 1072d4fc91adSAndy Lutomirski * can ignore the parameter. 1073d4fc91adSAndy Lutomirski * 1074fd70da6aSDarren Hart (VMware) * ACPICA will get mad at us if we call the method with the wrong number 1075fd70da6aSDarren Hart (VMware) * of arguments, so check what our method expects. (On some Dell 1076fd70da6aSDarren Hart (VMware) * laptops, WQxx may not be a method at all.) 1077d4fc91adSAndy Lutomirski */ 1078fd70da6aSDarren Hart (VMware) if (info->type != ACPI_TYPE_METHOD || info->param_count == 0) 1079d4fc91adSAndy Lutomirski wblock->read_takes_no_args = true; 1080d4fc91adSAndy Lutomirski 1081d4fc91adSAndy Lutomirski kfree(info); 1082d4fc91adSAndy Lutomirski 1083d4fc91adSAndy Lutomirski strcpy(method, "WS"); 1084d4fc91adSAndy Lutomirski strncat(method, wblock->gblock.object_id, 2); 1085d4fc91adSAndy Lutomirski result = get_subobj_info(device->handle, method, NULL); 1086d4fc91adSAndy Lutomirski 1087fd70da6aSDarren Hart (VMware) if (result == 0) 1088fd70da6aSDarren Hart (VMware) wblock->dev.setable = true; 1089d4fc91adSAndy Lutomirski 1090fd70da6aSDarren Hart (VMware) out_init: 1091fd70da6aSDarren Hart (VMware) wblock->dev.dev.bus = &wmi_bus_type; 1092fd70da6aSDarren Hart (VMware) wblock->dev.dev.parent = wmi_bus_dev; 1093fd70da6aSDarren Hart (VMware) 1094fd70da6aSDarren Hart (VMware) dev_set_name(&wblock->dev.dev, "%pUL", gblock->guid); 1095c64eefd4SDmitry Torokhov 10966ee50aaaSDarren Hart (VMware) device_initialize(&wblock->dev.dev); 1097fd70da6aSDarren Hart (VMware) 1098fd70da6aSDarren Hart (VMware) return 0; 10991caab3c1SMatthew Garrett } 11001caab3c1SMatthew Garrett 1101b0e86302SAndy Lutomirski static void wmi_free_devices(struct acpi_device *device) 11021caab3c1SMatthew Garrett { 1103c64eefd4SDmitry Torokhov struct wmi_block *wblock, *next; 11041caab3c1SMatthew Garrett 11051caab3c1SMatthew Garrett /* Delete devices for all the GUIDs */ 1106023b9565SDmitry Torokhov list_for_each_entry_safe(wblock, next, &wmi_block_list, list) { 1107b0e86302SAndy Lutomirski if (wblock->acpi_device == device) { 1108023b9565SDmitry Torokhov list_del(&wblock->list); 1109844af950SAndy Lutomirski device_unregister(&wblock->dev.dev); 1110023b9565SDmitry Torokhov } 11111caab3c1SMatthew Garrett } 1112b0e86302SAndy Lutomirski } 11131caab3c1SMatthew Garrett 1114b0e86302SAndy Lutomirski static bool guid_already_parsed(struct acpi_device *device, 1115b0e86302SAndy Lutomirski const u8 *guid) 1116d1f9e497SCarlos Corbacho { 1117d1f9e497SCarlos Corbacho struct wmi_block *wblock; 1118d1f9e497SCarlos Corbacho 1119b0e86302SAndy Lutomirski list_for_each_entry(wblock, &wmi_block_list, list) { 1120b0e86302SAndy Lutomirski if (memcmp(wblock->gblock.guid, guid, 16) == 0) { 1121b0e86302SAndy Lutomirski /* 1122b0e86302SAndy Lutomirski * Because we historically didn't track the relationship 1123b0e86302SAndy Lutomirski * between GUIDs and ACPI nodes, we don't know whether 1124b0e86302SAndy Lutomirski * we need to suppress GUIDs that are unique on a 1125b0e86302SAndy Lutomirski * given node but duplicated across nodes. 1126b0e86302SAndy Lutomirski */ 1127b0e86302SAndy Lutomirski dev_warn(&device->dev, "duplicate WMI GUID %pUL (first instance was on %s)\n", 1128b0e86302SAndy Lutomirski guid, dev_name(&wblock->acpi_device->dev)); 1129d1f9e497SCarlos Corbacho return true; 1130b0e86302SAndy Lutomirski } 1131b0e86302SAndy Lutomirski } 1132c64eefd4SDmitry Torokhov 1133d1f9e497SCarlos Corbacho return false; 1134d1f9e497SCarlos Corbacho } 1135d1f9e497SCarlos Corbacho 11361caab3c1SMatthew Garrett /* 1137b4f9fe12SLen Brown * Parse the _WDG method for the GUID data blocks 1138b4f9fe12SLen Brown */ 1139844af950SAndy Lutomirski static int parse_wdg(struct device *wmi_bus_dev, struct acpi_device *device) 1140b4f9fe12SLen Brown { 1141b4f9fe12SLen Brown struct acpi_buffer out = {ACPI_ALLOCATE_BUFFER, NULL}; 114237830662SDmitry Torokhov const struct guid_block *gblock; 11436ee50aaaSDarren Hart (VMware) struct wmi_block *wblock, *next; 11446ee50aaaSDarren Hart (VMware) union acpi_object *obj; 1145b4f9fe12SLen Brown acpi_status status; 11466ee50aaaSDarren Hart (VMware) int retval = 0; 1147b4f9fe12SLen Brown u32 i, total; 1148b4f9fe12SLen Brown 11497f5809bfSAndy Lutomirski status = acpi_evaluate_object(device->handle, "_WDG", NULL, &out); 1150b4f9fe12SLen Brown if (ACPI_FAILURE(status)) 1151c64eefd4SDmitry Torokhov return -ENXIO; 1152b4f9fe12SLen Brown 1153b4f9fe12SLen Brown obj = (union acpi_object *) out.pointer; 11543d2c63ebSDmitry Torokhov if (!obj) 1155c64eefd4SDmitry Torokhov return -ENXIO; 1156b4f9fe12SLen Brown 115764ed0ab8SDmitry Torokhov if (obj->type != ACPI_TYPE_BUFFER) { 1158c64eefd4SDmitry Torokhov retval = -ENXIO; 115964ed0ab8SDmitry Torokhov goto out_free_pointer; 116064ed0ab8SDmitry Torokhov } 1161b4f9fe12SLen Brown 116237830662SDmitry Torokhov gblock = (const struct guid_block *)obj->buffer.pointer; 1163b4f9fe12SLen Brown total = obj->buffer.length / sizeof(struct guid_block); 1164b4f9fe12SLen Brown 1165b4f9fe12SLen Brown for (i = 0; i < total; i++) { 1166a929aae0SThomas Renninger if (debug_dump_wdg) 1167a929aae0SThomas Renninger wmi_dump_wdg(&gblock[i]); 1168a929aae0SThomas Renninger 1169a1c31bcdSAndy Lutomirski /* 1170a1c31bcdSAndy Lutomirski * Some WMI devices, like those for nVidia hooks, have a 1171a1c31bcdSAndy Lutomirski * duplicate GUID. It's not clear what we should do in this 1172a1c31bcdSAndy Lutomirski * case yet, so for now, we'll just ignore the duplicate 1173a1c31bcdSAndy Lutomirski * for device creation. 1174a1c31bcdSAndy Lutomirski */ 1175a1c31bcdSAndy Lutomirski if (guid_already_parsed(device, gblock[i].guid)) 1176a1c31bcdSAndy Lutomirski continue; 1177a1c31bcdSAndy Lutomirski 117858f6425eSColin King wblock = kzalloc(sizeof(struct wmi_block), GFP_KERNEL); 11796ee50aaaSDarren Hart (VMware) if (!wblock) { 11806ee50aaaSDarren Hart (VMware) retval = -ENOMEM; 11816ee50aaaSDarren Hart (VMware) break; 11826ee50aaaSDarren Hart (VMware) } 118358f6425eSColin King 1184b0e86302SAndy Lutomirski wblock->acpi_device = device; 118558f6425eSColin King wblock->gblock = gblock[i]; 118658f6425eSColin King 1187fd70da6aSDarren Hart (VMware) retval = wmi_create_device(wmi_bus_dev, &gblock[i], wblock, device); 1188fd70da6aSDarren Hart (VMware) if (retval) { 1189fd70da6aSDarren Hart (VMware) kfree(wblock); 1190fd70da6aSDarren Hart (VMware) continue; 1191fd70da6aSDarren Hart (VMware) } 119258f6425eSColin King 119358f6425eSColin King list_add_tail(&wblock->list, &wmi_block_list); 1194b4f9fe12SLen Brown 1195fc3155b2SThomas Renninger if (debug_event) { 1196fc3155b2SThomas Renninger wblock->handler = wmi_notify_debug; 11972d5ab555SDmitry Torokhov wmi_method_enable(wblock, 1); 1198fc3155b2SThomas Renninger } 1199b4f9fe12SLen Brown } 1200b4f9fe12SLen Brown 12016ee50aaaSDarren Hart (VMware) /* 12026ee50aaaSDarren Hart (VMware) * Now that all of the devices are created, add them to the 12036ee50aaaSDarren Hart (VMware) * device tree and probe subdrivers. 12046ee50aaaSDarren Hart (VMware) */ 12056ee50aaaSDarren Hart (VMware) list_for_each_entry_safe(wblock, next, &wmi_block_list, list) { 12066ee50aaaSDarren Hart (VMware) if (wblock->acpi_device != device) 12076ee50aaaSDarren Hart (VMware) continue; 12086ee50aaaSDarren Hart (VMware) 12096ee50aaaSDarren Hart (VMware) retval = device_add(&wblock->dev.dev); 12106ee50aaaSDarren Hart (VMware) if (retval) { 1211501f7e52SJoe Perches dev_err(wmi_bus_dev, "failed to register %pUL\n", 12126ee50aaaSDarren Hart (VMware) wblock->gblock.guid); 12136ee50aaaSDarren Hart (VMware) if (debug_event) 12146ee50aaaSDarren Hart (VMware) wmi_method_enable(wblock, 0); 12156ee50aaaSDarren Hart (VMware) list_del(&wblock->list); 12166ee50aaaSDarren Hart (VMware) put_device(&wblock->dev.dev); 12176ee50aaaSDarren Hart (VMware) } 12186ee50aaaSDarren Hart (VMware) } 1219c64eefd4SDmitry Torokhov 1220a5167c5bSAxel Lin out_free_pointer: 1221a5167c5bSAxel Lin kfree(out.pointer); 1222c64eefd4SDmitry Torokhov return retval; 1223b4f9fe12SLen Brown } 1224b4f9fe12SLen Brown 1225b4f9fe12SLen Brown /* 1226b4f9fe12SLen Brown * WMI can have EmbeddedControl access regions. In which case, we just want to 1227b4f9fe12SLen Brown * hand these off to the EC driver. 1228b4f9fe12SLen Brown */ 1229b4f9fe12SLen Brown static acpi_status 1230b4f9fe12SLen Brown acpi_wmi_ec_space_handler(u32 function, acpi_physical_address address, 1231439913ffSLin Ming u32 bits, u64 *value, 1232b4f9fe12SLen Brown void *handler_context, void *region_context) 1233b4f9fe12SLen Brown { 1234b4f9fe12SLen Brown int result = 0, i = 0; 1235b4f9fe12SLen Brown u8 temp = 0; 1236b4f9fe12SLen Brown 1237b4f9fe12SLen Brown if ((address > 0xFF) || !value) 1238b4f9fe12SLen Brown return AE_BAD_PARAMETER; 1239b4f9fe12SLen Brown 1240b4f9fe12SLen Brown if (function != ACPI_READ && function != ACPI_WRITE) 1241b4f9fe12SLen Brown return AE_BAD_PARAMETER; 1242b4f9fe12SLen Brown 1243b4f9fe12SLen Brown if (bits != 8) 1244b4f9fe12SLen Brown return AE_BAD_PARAMETER; 1245b4f9fe12SLen Brown 1246b4f9fe12SLen Brown if (function == ACPI_READ) { 1247b4f9fe12SLen Brown result = ec_read(address, &temp); 1248439913ffSLin Ming (*value) |= ((u64)temp) << i; 1249b4f9fe12SLen Brown } else { 1250b4f9fe12SLen Brown temp = 0xff & ((*value) >> i); 1251b4f9fe12SLen Brown result = ec_write(address, temp); 1252b4f9fe12SLen Brown } 1253b4f9fe12SLen Brown 1254b4f9fe12SLen Brown switch (result) { 1255b4f9fe12SLen Brown case -EINVAL: 1256b4f9fe12SLen Brown return AE_BAD_PARAMETER; 1257b4f9fe12SLen Brown break; 1258b4f9fe12SLen Brown case -ENODEV: 1259b4f9fe12SLen Brown return AE_NOT_FOUND; 1260b4f9fe12SLen Brown break; 1261b4f9fe12SLen Brown case -ETIME: 1262b4f9fe12SLen Brown return AE_TIME; 1263b4f9fe12SLen Brown break; 1264b4f9fe12SLen Brown default: 1265b4f9fe12SLen Brown return AE_OK; 1266b4f9fe12SLen Brown } 1267b4f9fe12SLen Brown } 1268b4f9fe12SLen Brown 12691686f544SAndy Lutomirski static void acpi_wmi_notify_handler(acpi_handle handle, u32 event, 12701686f544SAndy Lutomirski void *context) 1271b4f9fe12SLen Brown { 1272b4f9fe12SLen Brown struct guid_block *block; 1273b4f9fe12SLen Brown struct wmi_block *wblock; 12741686f544SAndy Lutomirski bool found_it = false; 1275b4f9fe12SLen Brown 1276cedb3b2aSAndy Shevchenko list_for_each_entry(wblock, &wmi_block_list, list) { 1277b4f9fe12SLen Brown block = &wblock->gblock; 1278b4f9fe12SLen Brown 12791686f544SAndy Lutomirski if (wblock->acpi_device->handle == handle && 1280b0e86302SAndy Lutomirski (block->flags & ACPI_WMI_EVENT) && 12811686f544SAndy Lutomirski (block->notify_id == event)) 12821686f544SAndy Lutomirski { 12831686f544SAndy Lutomirski found_it = true; 12841686f544SAndy Lutomirski break; 12851686f544SAndy Lutomirski } 12861686f544SAndy Lutomirski } 12871686f544SAndy Lutomirski 12881686f544SAndy Lutomirski if (!found_it) 12891686f544SAndy Lutomirski return; 12901686f544SAndy Lutomirski 12911686f544SAndy Lutomirski /* If a driver is bound, then notify the driver. */ 12921686f544SAndy Lutomirski if (wblock->dev.dev.driver) { 12931686f544SAndy Lutomirski struct wmi_driver *driver; 12941686f544SAndy Lutomirski struct acpi_object_list input; 12951686f544SAndy Lutomirski union acpi_object params[1]; 12961686f544SAndy Lutomirski struct acpi_buffer evdata = { ACPI_ALLOCATE_BUFFER, NULL }; 12971686f544SAndy Lutomirski acpi_status status; 12981686f544SAndy Lutomirski 12991686f544SAndy Lutomirski driver = container_of(wblock->dev.dev.driver, 13001686f544SAndy Lutomirski struct wmi_driver, driver); 13011686f544SAndy Lutomirski 13021686f544SAndy Lutomirski input.count = 1; 13031686f544SAndy Lutomirski input.pointer = params; 13041686f544SAndy Lutomirski params[0].type = ACPI_TYPE_INTEGER; 13051686f544SAndy Lutomirski params[0].integer.value = event; 13061686f544SAndy Lutomirski 13071686f544SAndy Lutomirski status = acpi_evaluate_object(wblock->acpi_device->handle, 13081686f544SAndy Lutomirski "_WED", &input, &evdata); 13091686f544SAndy Lutomirski if (ACPI_FAILURE(status)) { 13101686f544SAndy Lutomirski dev_warn(&wblock->dev.dev, 13111686f544SAndy Lutomirski "failed to get event data\n"); 13121686f544SAndy Lutomirski return; 13131686f544SAndy Lutomirski } 13141686f544SAndy Lutomirski 13151686f544SAndy Lutomirski if (driver->notify) 13161686f544SAndy Lutomirski driver->notify(&wblock->dev, 13171686f544SAndy Lutomirski (union acpi_object *)evdata.pointer); 13181686f544SAndy Lutomirski 13191686f544SAndy Lutomirski kfree(evdata.pointer); 13201686f544SAndy Lutomirski } else if (wblock->handler) { 13211686f544SAndy Lutomirski /* Legacy handler */ 1322b4f9fe12SLen Brown wblock->handler(event, wblock->handler_data); 13231686f544SAndy Lutomirski } 13241686f544SAndy Lutomirski 13257715348cSThomas Renninger if (debug_event) { 132685b4e4ebSRasmus Villemoes pr_info("DEBUG Event GUID: %pUL\n", 132785b4e4ebSRasmus Villemoes wblock->gblock.guid); 13287715348cSThomas Renninger } 1329b4f9fe12SLen Brown 1330b4f9fe12SLen Brown acpi_bus_generate_netlink_event( 13311686f544SAndy Lutomirski wblock->acpi_device->pnp.device_class, 13321686f544SAndy Lutomirski dev_name(&wblock->dev.dev), 1333b4f9fe12SLen Brown event, 0); 13341686f544SAndy Lutomirski 1335b4f9fe12SLen Brown } 1336b4f9fe12SLen Brown 13379599ed91SAndy Lutomirski static int acpi_wmi_remove(struct platform_device *device) 1338b4f9fe12SLen Brown { 13399599ed91SAndy Lutomirski struct acpi_device *acpi_device = ACPI_COMPANION(&device->dev); 13409599ed91SAndy Lutomirski 13419599ed91SAndy Lutomirski acpi_remove_notify_handler(acpi_device->handle, ACPI_DEVICE_NOTIFY, 13421686f544SAndy Lutomirski acpi_wmi_notify_handler); 13439599ed91SAndy Lutomirski acpi_remove_address_space_handler(acpi_device->handle, 1344b4f9fe12SLen Brown ACPI_ADR_SPACE_EC, &acpi_wmi_ec_space_handler); 13459599ed91SAndy Lutomirski wmi_free_devices(acpi_device); 13467b11e898SMario Limonciello device_destroy(&wmi_bus_class, MKDEV(0, 0)); 1347b4f9fe12SLen Brown 1348b4f9fe12SLen Brown return 0; 1349b4f9fe12SLen Brown } 1350b4f9fe12SLen Brown 13519599ed91SAndy Lutomirski static int acpi_wmi_probe(struct platform_device *device) 1352b4f9fe12SLen Brown { 13539599ed91SAndy Lutomirski struct acpi_device *acpi_device; 1354844af950SAndy Lutomirski struct device *wmi_bus_dev; 1355b4f9fe12SLen Brown acpi_status status; 1356c64eefd4SDmitry Torokhov int error; 1357b4f9fe12SLen Brown 13589599ed91SAndy Lutomirski acpi_device = ACPI_COMPANION(&device->dev); 13599599ed91SAndy Lutomirski if (!acpi_device) { 13609599ed91SAndy Lutomirski dev_err(&device->dev, "ACPI companion is missing\n"); 13619599ed91SAndy Lutomirski return -ENODEV; 13629599ed91SAndy Lutomirski } 13639599ed91SAndy Lutomirski 13649599ed91SAndy Lutomirski status = acpi_install_address_space_handler(acpi_device->handle, 1365b4f9fe12SLen Brown ACPI_ADR_SPACE_EC, 1366b4f9fe12SLen Brown &acpi_wmi_ec_space_handler, 1367b4f9fe12SLen Brown NULL, NULL); 13685212cd67SDmitry Torokhov if (ACPI_FAILURE(status)) { 136946492ee4SAndy Lutomirski dev_err(&device->dev, "Error installing EC region handler\n"); 1370b4f9fe12SLen Brown return -ENODEV; 13715212cd67SDmitry Torokhov } 1372b4f9fe12SLen Brown 13739599ed91SAndy Lutomirski status = acpi_install_notify_handler(acpi_device->handle, 13749599ed91SAndy Lutomirski ACPI_DEVICE_NOTIFY, 13751686f544SAndy Lutomirski acpi_wmi_notify_handler, 13761686f544SAndy Lutomirski NULL); 13771686f544SAndy Lutomirski if (ACPI_FAILURE(status)) { 13781686f544SAndy Lutomirski dev_err(&device->dev, "Error installing notify handler\n"); 13791686f544SAndy Lutomirski error = -ENODEV; 13801686f544SAndy Lutomirski goto err_remove_ec_handler; 13811686f544SAndy Lutomirski } 13821686f544SAndy Lutomirski 1383844af950SAndy Lutomirski wmi_bus_dev = device_create(&wmi_bus_class, &device->dev, MKDEV(0, 0), 1384844af950SAndy Lutomirski NULL, "wmi_bus-%s", dev_name(&device->dev)); 1385844af950SAndy Lutomirski if (IS_ERR(wmi_bus_dev)) { 1386844af950SAndy Lutomirski error = PTR_ERR(wmi_bus_dev); 13871686f544SAndy Lutomirski goto err_remove_notify_handler; 1388844af950SAndy Lutomirski } 13899599ed91SAndy Lutomirski dev_set_drvdata(&device->dev, wmi_bus_dev); 1390844af950SAndy Lutomirski 13919599ed91SAndy Lutomirski error = parse_wdg(wmi_bus_dev, acpi_device); 1392c64eefd4SDmitry Torokhov if (error) { 13938e07514dSDmitry Torokhov pr_err("Failed to parse WDG method\n"); 1394844af950SAndy Lutomirski goto err_remove_busdev; 1395b4f9fe12SLen Brown } 1396b4f9fe12SLen Brown 1397c64eefd4SDmitry Torokhov return 0; 139846492ee4SAndy Lutomirski 1399844af950SAndy Lutomirski err_remove_busdev: 14007b11e898SMario Limonciello device_destroy(&wmi_bus_class, MKDEV(0, 0)); 1401844af950SAndy Lutomirski 14021686f544SAndy Lutomirski err_remove_notify_handler: 14039599ed91SAndy Lutomirski acpi_remove_notify_handler(acpi_device->handle, ACPI_DEVICE_NOTIFY, 14041686f544SAndy Lutomirski acpi_wmi_notify_handler); 14051686f544SAndy Lutomirski 14061686f544SAndy Lutomirski err_remove_ec_handler: 14079599ed91SAndy Lutomirski acpi_remove_address_space_handler(acpi_device->handle, 140846492ee4SAndy Lutomirski ACPI_ADR_SPACE_EC, 140946492ee4SAndy Lutomirski &acpi_wmi_ec_space_handler); 141046492ee4SAndy Lutomirski 141146492ee4SAndy Lutomirski return error; 1412b4f9fe12SLen Brown } 1413b4f9fe12SLen Brown 1414844af950SAndy Lutomirski int __must_check __wmi_driver_register(struct wmi_driver *driver, 1415844af950SAndy Lutomirski struct module *owner) 1416844af950SAndy Lutomirski { 1417844af950SAndy Lutomirski driver->driver.owner = owner; 1418844af950SAndy Lutomirski driver->driver.bus = &wmi_bus_type; 1419844af950SAndy Lutomirski 1420844af950SAndy Lutomirski return driver_register(&driver->driver); 1421844af950SAndy Lutomirski } 1422844af950SAndy Lutomirski EXPORT_SYMBOL(__wmi_driver_register); 1423844af950SAndy Lutomirski 1424844af950SAndy Lutomirski void wmi_driver_unregister(struct wmi_driver *driver) 1425844af950SAndy Lutomirski { 1426844af950SAndy Lutomirski driver_unregister(&driver->driver); 1427844af950SAndy Lutomirski } 1428844af950SAndy Lutomirski EXPORT_SYMBOL(wmi_driver_unregister); 1429844af950SAndy Lutomirski 1430b4f9fe12SLen Brown static int __init acpi_wmi_init(void) 1431b4f9fe12SLen Brown { 1432c64eefd4SDmitry Torokhov int error; 1433b4f9fe12SLen Brown 1434b4f9fe12SLen Brown if (acpi_disabled) 1435b4f9fe12SLen Brown return -ENODEV; 1436b4f9fe12SLen Brown 1437844af950SAndy Lutomirski error = class_register(&wmi_bus_class); 1438c64eefd4SDmitry Torokhov if (error) 1439c64eefd4SDmitry Torokhov return error; 1440b4f9fe12SLen Brown 1441844af950SAndy Lutomirski error = bus_register(&wmi_bus_type); 1442844af950SAndy Lutomirski if (error) 1443844af950SAndy Lutomirski goto err_unreg_class; 1444844af950SAndy Lutomirski 14459599ed91SAndy Lutomirski error = platform_driver_register(&acpi_wmi_driver); 1446c64eefd4SDmitry Torokhov if (error) { 1447c64eefd4SDmitry Torokhov pr_err("Error loading mapper\n"); 1448844af950SAndy Lutomirski goto err_unreg_bus; 14491caab3c1SMatthew Garrett } 14501caab3c1SMatthew Garrett 14518e07514dSDmitry Torokhov return 0; 1452844af950SAndy Lutomirski 1453844af950SAndy Lutomirski err_unreg_bus: 1454844af950SAndy Lutomirski bus_unregister(&wmi_bus_type); 1455844af950SAndy Lutomirski 145697277717SAlexey Khoroshilov err_unreg_class: 145797277717SAlexey Khoroshilov class_unregister(&wmi_bus_class); 145897277717SAlexey Khoroshilov 1459844af950SAndy Lutomirski return error; 1460b4f9fe12SLen Brown } 1461b4f9fe12SLen Brown 1462b4f9fe12SLen Brown static void __exit acpi_wmi_exit(void) 1463b4f9fe12SLen Brown { 14649599ed91SAndy Lutomirski platform_driver_unregister(&acpi_wmi_driver); 1465844af950SAndy Lutomirski bus_unregister(&wmi_bus_type); 1466303d1fccSMario Limonciello class_unregister(&wmi_bus_class); 1467b4f9fe12SLen Brown } 1468b4f9fe12SLen Brown 146998b8e4e5SRafael J. Wysocki subsys_initcall_sync(acpi_wmi_init); 1470b4f9fe12SLen Brown module_exit(acpi_wmi_exit); 1471