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/kernel.h> 37b4f9fe12SLen Brown #include <linux/init.h> 38b4f9fe12SLen Brown #include <linux/types.h> 391caab3c1SMatthew Garrett #include <linux/device.h> 40b4f9fe12SLen Brown #include <linux/list.h> 41b4f9fe12SLen Brown #include <linux/acpi.h> 425a0e3ad6STejun Heo #include <linux/slab.h> 437c52d551SPaul Gortmaker #include <linux/module.h> 449599ed91SAndy Lutomirski #include <linux/platform_device.h> 45844af950SAndy Lutomirski #include <linux/wmi.h> 46538d7eb8SAndy Shevchenko #include <linux/uuid.h> 47b4f9fe12SLen Brown 48b4f9fe12SLen Brown ACPI_MODULE_NAME("wmi"); 49b4f9fe12SLen Brown MODULE_AUTHOR("Carlos Corbacho"); 50b4f9fe12SLen Brown MODULE_DESCRIPTION("ACPI-WMI Mapping Driver"); 51b4f9fe12SLen Brown MODULE_LICENSE("GPL"); 52b4f9fe12SLen Brown 53762e1a2fSDmitry Torokhov static LIST_HEAD(wmi_block_list); 54b4f9fe12SLen Brown 55b4f9fe12SLen Brown struct guid_block { 56b4f9fe12SLen Brown char guid[16]; 57b4f9fe12SLen Brown union { 58b4f9fe12SLen Brown char object_id[2]; 59b4f9fe12SLen Brown struct { 60b4f9fe12SLen Brown unsigned char notify_id; 61b4f9fe12SLen Brown unsigned char reserved; 62b4f9fe12SLen Brown }; 63b4f9fe12SLen Brown }; 64b4f9fe12SLen Brown u8 instance_count; 65b4f9fe12SLen Brown u8 flags; 66b4f9fe12SLen Brown }; 67b4f9fe12SLen Brown 68b4f9fe12SLen Brown struct wmi_block { 69844af950SAndy Lutomirski struct wmi_device dev; 70b4f9fe12SLen Brown struct list_head list; 71b4f9fe12SLen Brown struct guid_block gblock; 72b0e86302SAndy Lutomirski struct acpi_device *acpi_device; 73b4f9fe12SLen Brown wmi_notify_handler handler; 74b4f9fe12SLen Brown void *handler_data; 75d4fc91adSAndy Lutomirski 76fd70da6aSDarren Hart (VMware) bool read_takes_no_args; 77b4f9fe12SLen Brown }; 78b4f9fe12SLen Brown 79b4f9fe12SLen Brown 80b4f9fe12SLen Brown /* 81b4f9fe12SLen Brown * If the GUID data block is marked as expensive, we must enable and 82b4f9fe12SLen Brown * explicitily disable data collection. 83b4f9fe12SLen Brown */ 84b4f9fe12SLen Brown #define ACPI_WMI_EXPENSIVE 0x1 85b4f9fe12SLen Brown #define ACPI_WMI_METHOD 0x2 /* GUID is a method */ 86b4f9fe12SLen Brown #define ACPI_WMI_STRING 0x4 /* GUID takes & returns a string */ 87b4f9fe12SLen Brown #define ACPI_WMI_EVENT 0x8 /* GUID is an event */ 88b4f9fe12SLen Brown 8990ab5ee9SRusty Russell static bool debug_event; 90fc3155b2SThomas Renninger module_param(debug_event, bool, 0444); 91fc3155b2SThomas Renninger MODULE_PARM_DESC(debug_event, 92fc3155b2SThomas Renninger "Log WMI Events [0/1]"); 93fc3155b2SThomas Renninger 9490ab5ee9SRusty Russell static bool debug_dump_wdg; 95a929aae0SThomas Renninger module_param(debug_dump_wdg, bool, 0444); 96a929aae0SThomas Renninger MODULE_PARM_DESC(debug_dump_wdg, 97a929aae0SThomas Renninger "Dump available WMI interfaces [0/1]"); 98a929aae0SThomas Renninger 999599ed91SAndy Lutomirski static int acpi_wmi_remove(struct platform_device *device); 1009599ed91SAndy Lutomirski static int acpi_wmi_probe(struct platform_device *device); 101b4f9fe12SLen Brown 102b4f9fe12SLen Brown static const struct acpi_device_id wmi_device_ids[] = { 103b4f9fe12SLen Brown {"PNP0C14", 0}, 104b4f9fe12SLen Brown {"pnp0c14", 0}, 105b4f9fe12SLen Brown {"", 0}, 106b4f9fe12SLen Brown }; 107b4f9fe12SLen Brown MODULE_DEVICE_TABLE(acpi, wmi_device_ids); 108b4f9fe12SLen Brown 1099599ed91SAndy Lutomirski static struct platform_driver acpi_wmi_driver = { 1109599ed91SAndy Lutomirski .driver = { 111844af950SAndy Lutomirski .name = "acpi-wmi", 1129599ed91SAndy Lutomirski .acpi_match_table = wmi_device_ids, 113b4f9fe12SLen Brown }, 1149599ed91SAndy Lutomirski .probe = acpi_wmi_probe, 1159599ed91SAndy Lutomirski .remove = acpi_wmi_remove, 116b4f9fe12SLen Brown }; 117b4f9fe12SLen Brown 118b4f9fe12SLen Brown /* 119b4f9fe12SLen Brown * GUID parsing functions 120b4f9fe12SLen Brown */ 121b4f9fe12SLen Brown 122b4f9fe12SLen Brown static bool find_guid(const char *guid_string, struct wmi_block **out) 123b4f9fe12SLen Brown { 124538d7eb8SAndy Shevchenko uuid_le guid_input; 125b4f9fe12SLen Brown struct wmi_block *wblock; 126b4f9fe12SLen Brown struct guid_block *block; 127b4f9fe12SLen Brown struct list_head *p; 128b4f9fe12SLen Brown 129538d7eb8SAndy Shevchenko if (uuid_le_to_bin(guid_string, &guid_input)) 130538d7eb8SAndy Shevchenko return false; 131b4f9fe12SLen Brown 132762e1a2fSDmitry Torokhov list_for_each(p, &wmi_block_list) { 133b4f9fe12SLen Brown wblock = list_entry(p, struct wmi_block, list); 134b4f9fe12SLen Brown block = &wblock->gblock; 135b4f9fe12SLen Brown 136538d7eb8SAndy Shevchenko if (memcmp(block->guid, &guid_input, 16) == 0) { 137b4f9fe12SLen Brown if (out) 138b4f9fe12SLen Brown *out = wblock; 139097c27fcSJoe Perches return true; 140b4f9fe12SLen Brown } 141b4f9fe12SLen Brown } 142097c27fcSJoe Perches return false; 143b4f9fe12SLen Brown } 144b4f9fe12SLen Brown 145d4fc91adSAndy Lutomirski static int get_subobj_info(acpi_handle handle, const char *pathname, 146d4fc91adSAndy Lutomirski struct acpi_device_info **info) 147d4fc91adSAndy Lutomirski { 148d4fc91adSAndy Lutomirski struct acpi_device_info *dummy_info, **info_ptr; 149d4fc91adSAndy Lutomirski acpi_handle subobj_handle; 150d4fc91adSAndy Lutomirski acpi_status status; 151d4fc91adSAndy Lutomirski 152d4fc91adSAndy Lutomirski status = acpi_get_handle(handle, (char *)pathname, &subobj_handle); 153d4fc91adSAndy Lutomirski if (status == AE_NOT_FOUND) 154d4fc91adSAndy Lutomirski return -ENOENT; 155d4fc91adSAndy Lutomirski else if (ACPI_FAILURE(status)) 156d4fc91adSAndy Lutomirski return -EIO; 157d4fc91adSAndy Lutomirski 158d4fc91adSAndy Lutomirski info_ptr = info ? info : &dummy_info; 159d4fc91adSAndy Lutomirski status = acpi_get_object_info(subobj_handle, info_ptr); 160d4fc91adSAndy Lutomirski if (ACPI_FAILURE(status)) 161d4fc91adSAndy Lutomirski return -EIO; 162d4fc91adSAndy Lutomirski 163d4fc91adSAndy Lutomirski if (!info) 164d4fc91adSAndy Lutomirski kfree(dummy_info); 165d4fc91adSAndy Lutomirski 166d4fc91adSAndy Lutomirski return 0; 167d4fc91adSAndy Lutomirski } 168d4fc91adSAndy Lutomirski 169b4f9fe12SLen Brown static acpi_status wmi_method_enable(struct wmi_block *wblock, int enable) 170b4f9fe12SLen Brown { 171b4f9fe12SLen Brown struct guid_block *block = NULL; 172b4f9fe12SLen Brown char method[5]; 173b4f9fe12SLen Brown acpi_status status; 174b4f9fe12SLen Brown acpi_handle handle; 175b4f9fe12SLen Brown 176b4f9fe12SLen Brown block = &wblock->gblock; 177b0e86302SAndy Lutomirski handle = wblock->acpi_device->handle; 178b4f9fe12SLen Brown 179b4f9fe12SLen Brown snprintf(method, 5, "WE%02X", block->notify_id); 1808122ab66SZhang Rui status = acpi_execute_simple_method(handle, method, enable); 181b4f9fe12SLen Brown 182b4f9fe12SLen Brown if (status != AE_OK && status != AE_NOT_FOUND) 183b4f9fe12SLen Brown return status; 184b4f9fe12SLen Brown else 185b4f9fe12SLen Brown return AE_OK; 186b4f9fe12SLen Brown } 187b4f9fe12SLen Brown 188b4f9fe12SLen Brown /* 189b4f9fe12SLen Brown * Exported WMI functions 190b4f9fe12SLen Brown */ 191b4f9fe12SLen Brown /** 192b4f9fe12SLen Brown * wmi_evaluate_method - Evaluate a WMI method 193b4f9fe12SLen Brown * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba 194b4f9fe12SLen Brown * @instance: Instance index 195b4f9fe12SLen Brown * @method_id: Method ID to call 196b4f9fe12SLen Brown * &in: Buffer containing input for the method call 197b4f9fe12SLen Brown * &out: Empty buffer to return the method results 198b4f9fe12SLen Brown * 199b4f9fe12SLen Brown * Call an ACPI-WMI method 200b4f9fe12SLen Brown */ 201b4f9fe12SLen Brown acpi_status wmi_evaluate_method(const char *guid_string, u8 instance, 202b4f9fe12SLen Brown u32 method_id, const struct acpi_buffer *in, struct acpi_buffer *out) 203b4f9fe12SLen Brown { 204b4f9fe12SLen Brown struct guid_block *block = NULL; 205b4f9fe12SLen Brown struct wmi_block *wblock = NULL; 206b4f9fe12SLen Brown acpi_handle handle; 207b4f9fe12SLen Brown acpi_status status; 208b4f9fe12SLen Brown struct acpi_object_list input; 209b4f9fe12SLen Brown union acpi_object params[3]; 210f3d83e24SCostantino Leandro char method[5] = "WM"; 211b4f9fe12SLen Brown 212b4f9fe12SLen Brown if (!find_guid(guid_string, &wblock)) 213b4f9fe12SLen Brown return AE_ERROR; 214b4f9fe12SLen Brown 215b4f9fe12SLen Brown block = &wblock->gblock; 216b0e86302SAndy Lutomirski handle = wblock->acpi_device->handle; 217b4f9fe12SLen Brown 218b4f9fe12SLen Brown if (!(block->flags & ACPI_WMI_METHOD)) 219b4f9fe12SLen Brown return AE_BAD_DATA; 220b4f9fe12SLen Brown 221b4f9fe12SLen Brown if (block->instance_count < instance) 222b4f9fe12SLen Brown return AE_BAD_PARAMETER; 223b4f9fe12SLen Brown 224b4f9fe12SLen Brown input.count = 2; 225b4f9fe12SLen Brown input.pointer = params; 226b4f9fe12SLen Brown params[0].type = ACPI_TYPE_INTEGER; 227b4f9fe12SLen Brown params[0].integer.value = instance; 228b4f9fe12SLen Brown params[1].type = ACPI_TYPE_INTEGER; 229b4f9fe12SLen Brown params[1].integer.value = method_id; 230b4f9fe12SLen Brown 231b4f9fe12SLen Brown if (in) { 232b4f9fe12SLen Brown input.count = 3; 233b4f9fe12SLen Brown 234b4f9fe12SLen Brown if (block->flags & ACPI_WMI_STRING) { 235b4f9fe12SLen Brown params[2].type = ACPI_TYPE_STRING; 236b4f9fe12SLen Brown } else { 237b4f9fe12SLen Brown params[2].type = ACPI_TYPE_BUFFER; 238b4f9fe12SLen Brown } 239b4f9fe12SLen Brown params[2].buffer.length = in->length; 240b4f9fe12SLen Brown params[2].buffer.pointer = in->pointer; 241b4f9fe12SLen Brown } 242b4f9fe12SLen Brown 243b4f9fe12SLen Brown strncat(method, block->object_id, 2); 244b4f9fe12SLen Brown 245b4f9fe12SLen Brown status = acpi_evaluate_object(handle, method, &input, out); 246b4f9fe12SLen Brown 247b4f9fe12SLen Brown return status; 248b4f9fe12SLen Brown } 249b4f9fe12SLen Brown EXPORT_SYMBOL_GPL(wmi_evaluate_method); 250b4f9fe12SLen Brown 25156a37025SAndy Lutomirski static acpi_status __query_block(struct wmi_block *wblock, u8 instance, 252b4f9fe12SLen Brown struct acpi_buffer *out) 253b4f9fe12SLen Brown { 254b4f9fe12SLen Brown struct guid_block *block = NULL; 25554f14c27SZhang Rui acpi_handle handle; 256b4f9fe12SLen Brown acpi_status status, wc_status = AE_ERROR; 2578122ab66SZhang Rui struct acpi_object_list input; 2588122ab66SZhang Rui union acpi_object wq_params[1]; 259f3d83e24SCostantino Leandro char method[5]; 260f3d83e24SCostantino Leandro char wc_method[5] = "WC"; 261b4f9fe12SLen Brown 26256a37025SAndy Lutomirski if (!out) 263b4f9fe12SLen Brown return AE_BAD_PARAMETER; 264b4f9fe12SLen Brown 265b4f9fe12SLen Brown block = &wblock->gblock; 266b0e86302SAndy Lutomirski handle = wblock->acpi_device->handle; 267b4f9fe12SLen Brown 268b4f9fe12SLen Brown if (block->instance_count < instance) 269b4f9fe12SLen Brown return AE_BAD_PARAMETER; 270b4f9fe12SLen Brown 271b4f9fe12SLen Brown /* Check GUID is a data block */ 272b4f9fe12SLen Brown if (block->flags & (ACPI_WMI_EVENT | ACPI_WMI_METHOD)) 273b4f9fe12SLen Brown return AE_ERROR; 274b4f9fe12SLen Brown 275b4f9fe12SLen Brown input.count = 1; 276b4f9fe12SLen Brown input.pointer = wq_params; 277b4f9fe12SLen Brown wq_params[0].type = ACPI_TYPE_INTEGER; 278b4f9fe12SLen Brown wq_params[0].integer.value = instance; 279b4f9fe12SLen Brown 280d4fc91adSAndy Lutomirski if (instance == 0 && wblock->read_takes_no_args) 281d4fc91adSAndy Lutomirski input.count = 0; 282d4fc91adSAndy Lutomirski 283b4f9fe12SLen Brown /* 284b4f9fe12SLen Brown * If ACPI_WMI_EXPENSIVE, call the relevant WCxx method first to 285b4f9fe12SLen Brown * enable collection. 286b4f9fe12SLen Brown */ 287b4f9fe12SLen Brown if (block->flags & ACPI_WMI_EXPENSIVE) { 288b4f9fe12SLen Brown strncat(wc_method, block->object_id, 2); 289b4f9fe12SLen Brown 290b4f9fe12SLen Brown /* 291b4f9fe12SLen Brown * Some GUIDs break the specification by declaring themselves 292b4f9fe12SLen Brown * expensive, but have no corresponding WCxx method. So we 293b4f9fe12SLen Brown * should not fail if this happens. 294b4f9fe12SLen Brown */ 29554f14c27SZhang Rui if (acpi_has_method(handle, wc_method)) 2968122ab66SZhang Rui wc_status = acpi_execute_simple_method(handle, 2978122ab66SZhang Rui wc_method, 1); 298b4f9fe12SLen Brown } 299b4f9fe12SLen Brown 300b4f9fe12SLen Brown strcpy(method, "WQ"); 301b4f9fe12SLen Brown strncat(method, block->object_id, 2); 302b4f9fe12SLen Brown 303b4f9fe12SLen Brown status = acpi_evaluate_object(handle, method, &input, out); 304b4f9fe12SLen Brown 305b4f9fe12SLen Brown /* 306b4f9fe12SLen Brown * If ACPI_WMI_EXPENSIVE, call the relevant WCxx method, even if 307b4f9fe12SLen Brown * the WQxx method failed - we should disable collection anyway. 308b4f9fe12SLen Brown */ 309b4f9fe12SLen Brown if ((block->flags & ACPI_WMI_EXPENSIVE) && ACPI_SUCCESS(wc_status)) { 3108122ab66SZhang Rui status = acpi_execute_simple_method(handle, wc_method, 0); 311b4f9fe12SLen Brown } 312b4f9fe12SLen Brown 313b4f9fe12SLen Brown return status; 314b4f9fe12SLen Brown } 31556a37025SAndy Lutomirski 31656a37025SAndy Lutomirski /** 31756a37025SAndy Lutomirski * wmi_query_block - Return contents of a WMI block (deprecated) 31856a37025SAndy Lutomirski * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba 31956a37025SAndy Lutomirski * @instance: Instance index 32056a37025SAndy Lutomirski * &out: Empty buffer to return the contents of the data block to 32156a37025SAndy Lutomirski * 32256a37025SAndy Lutomirski * Return the contents of an ACPI-WMI data block to a buffer 32356a37025SAndy Lutomirski */ 32456a37025SAndy Lutomirski acpi_status wmi_query_block(const char *guid_string, u8 instance, 32556a37025SAndy Lutomirski struct acpi_buffer *out) 32656a37025SAndy Lutomirski { 32756a37025SAndy Lutomirski struct wmi_block *wblock; 32856a37025SAndy Lutomirski 32956a37025SAndy Lutomirski if (!guid_string) 33056a37025SAndy Lutomirski return AE_BAD_PARAMETER; 33156a37025SAndy Lutomirski 33256a37025SAndy Lutomirski if (!find_guid(guid_string, &wblock)) 33356a37025SAndy Lutomirski return AE_ERROR; 33456a37025SAndy Lutomirski 33556a37025SAndy Lutomirski return __query_block(wblock, instance, out); 33656a37025SAndy Lutomirski } 337b4f9fe12SLen Brown EXPORT_SYMBOL_GPL(wmi_query_block); 338b4f9fe12SLen Brown 33956a37025SAndy Lutomirski union acpi_object *wmidev_block_query(struct wmi_device *wdev, u8 instance) 34056a37025SAndy Lutomirski { 34156a37025SAndy Lutomirski struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL }; 34256a37025SAndy Lutomirski struct wmi_block *wblock = container_of(wdev, struct wmi_block, dev); 34356a37025SAndy Lutomirski 34456a37025SAndy Lutomirski if (ACPI_FAILURE(__query_block(wblock, instance, &out))) 34556a37025SAndy Lutomirski return NULL; 34656a37025SAndy Lutomirski 34756a37025SAndy Lutomirski return (union acpi_object *)out.pointer; 34856a37025SAndy Lutomirski } 34956a37025SAndy Lutomirski EXPORT_SYMBOL_GPL(wmidev_block_query); 35056a37025SAndy Lutomirski 351f6301986SAndy Lutomirski struct wmi_device *wmidev_get_other_guid(struct wmi_device *wdev, 352f6301986SAndy Lutomirski const char *guid_string) 353f6301986SAndy Lutomirski { 354f6301986SAndy Lutomirski struct wmi_block *this_wb = container_of(wdev, struct wmi_block, dev); 355f6301986SAndy Lutomirski struct wmi_block *other_wb; 356f6301986SAndy Lutomirski 357f6301986SAndy Lutomirski if (!find_guid(guid_string, &other_wb)) 358f6301986SAndy Lutomirski return NULL; 359f6301986SAndy Lutomirski 360f6301986SAndy Lutomirski if (other_wb->acpi_device != this_wb->acpi_device) 361f6301986SAndy Lutomirski return NULL; 362f6301986SAndy Lutomirski 363f6301986SAndy Lutomirski get_device(&other_wb->dev.dev); 364f6301986SAndy Lutomirski return &other_wb->dev; 365f6301986SAndy Lutomirski } 366f6301986SAndy Lutomirski EXPORT_SYMBOL_GPL(wmidev_get_other_guid); 367f6301986SAndy Lutomirski 368b4f9fe12SLen Brown /** 369b4f9fe12SLen Brown * wmi_set_block - Write to a WMI block 370b4f9fe12SLen Brown * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba 371b4f9fe12SLen Brown * @instance: Instance index 372b4f9fe12SLen Brown * &in: Buffer containing new values for the data block 373b4f9fe12SLen Brown * 374b4f9fe12SLen Brown * Write the contents of the input buffer to an ACPI-WMI data block 375b4f9fe12SLen Brown */ 376b4f9fe12SLen Brown acpi_status wmi_set_block(const char *guid_string, u8 instance, 377b4f9fe12SLen Brown const struct acpi_buffer *in) 378b4f9fe12SLen Brown { 379b4f9fe12SLen Brown struct guid_block *block = NULL; 380b4f9fe12SLen Brown struct wmi_block *wblock = NULL; 381b4f9fe12SLen Brown acpi_handle handle; 382b4f9fe12SLen Brown struct acpi_object_list input; 383b4f9fe12SLen Brown union acpi_object params[2]; 384f3d83e24SCostantino Leandro char method[5] = "WS"; 385b4f9fe12SLen Brown 386b4f9fe12SLen Brown if (!guid_string || !in) 387b4f9fe12SLen Brown return AE_BAD_DATA; 388b4f9fe12SLen Brown 389b4f9fe12SLen Brown if (!find_guid(guid_string, &wblock)) 390b4f9fe12SLen Brown return AE_ERROR; 391b4f9fe12SLen Brown 392b4f9fe12SLen Brown block = &wblock->gblock; 393b0e86302SAndy Lutomirski handle = wblock->acpi_device->handle; 394b4f9fe12SLen Brown 395b4f9fe12SLen Brown if (block->instance_count < instance) 396b4f9fe12SLen Brown return AE_BAD_PARAMETER; 397b4f9fe12SLen Brown 398b4f9fe12SLen Brown /* Check GUID is a data block */ 399b4f9fe12SLen Brown if (block->flags & (ACPI_WMI_EVENT | ACPI_WMI_METHOD)) 400b4f9fe12SLen Brown return AE_ERROR; 401b4f9fe12SLen Brown 402b4f9fe12SLen Brown input.count = 2; 403b4f9fe12SLen Brown input.pointer = params; 404b4f9fe12SLen Brown params[0].type = ACPI_TYPE_INTEGER; 405b4f9fe12SLen Brown params[0].integer.value = instance; 406b4f9fe12SLen Brown 407b4f9fe12SLen Brown if (block->flags & ACPI_WMI_STRING) { 408b4f9fe12SLen Brown params[1].type = ACPI_TYPE_STRING; 409b4f9fe12SLen Brown } else { 410b4f9fe12SLen Brown params[1].type = ACPI_TYPE_BUFFER; 411b4f9fe12SLen Brown } 412b4f9fe12SLen Brown params[1].buffer.length = in->length; 413b4f9fe12SLen Brown params[1].buffer.pointer = in->pointer; 414b4f9fe12SLen Brown 415b4f9fe12SLen Brown strncat(method, block->object_id, 2); 416b4f9fe12SLen Brown 417b4f9fe12SLen Brown return acpi_evaluate_object(handle, method, &input, NULL); 418b4f9fe12SLen Brown } 419b4f9fe12SLen Brown EXPORT_SYMBOL_GPL(wmi_set_block); 420b4f9fe12SLen Brown 42137830662SDmitry Torokhov static void wmi_dump_wdg(const struct guid_block *g) 422a929aae0SThomas Renninger { 42385b4e4ebSRasmus Villemoes pr_info("%pUL:\n", g->guid); 424cd3921f8SPali Rohár if (g->flags & ACPI_WMI_EVENT) 425cd3921f8SPali Rohár pr_info("\tnotify_id: 0x%02X\n", g->notify_id); 426cd3921f8SPali Rohár else 427cd3921f8SPali Rohár pr_info("\tobject_id: %2pE\n", g->object_id); 4288e07514dSDmitry Torokhov pr_info("\tinstance_count: %d\n", g->instance_count); 4298e07514dSDmitry Torokhov pr_info("\tflags: %#x", g->flags); 430a929aae0SThomas Renninger if (g->flags) { 431a929aae0SThomas Renninger if (g->flags & ACPI_WMI_EXPENSIVE) 4328e07514dSDmitry Torokhov pr_cont(" ACPI_WMI_EXPENSIVE"); 433a929aae0SThomas Renninger if (g->flags & ACPI_WMI_METHOD) 4348e07514dSDmitry Torokhov pr_cont(" ACPI_WMI_METHOD"); 435a929aae0SThomas Renninger if (g->flags & ACPI_WMI_STRING) 4368e07514dSDmitry Torokhov pr_cont(" ACPI_WMI_STRING"); 437a929aae0SThomas Renninger if (g->flags & ACPI_WMI_EVENT) 4388e07514dSDmitry Torokhov pr_cont(" ACPI_WMI_EVENT"); 439a929aae0SThomas Renninger } 4408e07514dSDmitry Torokhov pr_cont("\n"); 441a929aae0SThomas Renninger 442a929aae0SThomas Renninger } 443a929aae0SThomas Renninger 444fc3155b2SThomas Renninger static void wmi_notify_debug(u32 value, void *context) 445fc3155b2SThomas Renninger { 446fc3155b2SThomas Renninger struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL }; 447fc3155b2SThomas Renninger union acpi_object *obj; 4481492616aSAxel Lin acpi_status status; 449fc3155b2SThomas Renninger 4501492616aSAxel Lin status = wmi_get_event_data(value, &response); 4511492616aSAxel Lin if (status != AE_OK) { 4528e07514dSDmitry Torokhov pr_info("bad event status 0x%x\n", status); 4531492616aSAxel Lin return; 4541492616aSAxel Lin } 455fc3155b2SThomas Renninger 456fc3155b2SThomas Renninger obj = (union acpi_object *)response.pointer; 457fc3155b2SThomas Renninger 458fc3155b2SThomas Renninger if (!obj) 459fc3155b2SThomas Renninger return; 460fc3155b2SThomas Renninger 4618e07514dSDmitry Torokhov pr_info("DEBUG Event "); 462fc3155b2SThomas Renninger switch(obj->type) { 463fc3155b2SThomas Renninger case ACPI_TYPE_BUFFER: 4648e07514dSDmitry Torokhov pr_cont("BUFFER_TYPE - length %d\n", obj->buffer.length); 465fc3155b2SThomas Renninger break; 466fc3155b2SThomas Renninger case ACPI_TYPE_STRING: 4678e07514dSDmitry Torokhov pr_cont("STRING_TYPE - %s\n", obj->string.pointer); 468fc3155b2SThomas Renninger break; 469fc3155b2SThomas Renninger case ACPI_TYPE_INTEGER: 4708e07514dSDmitry Torokhov pr_cont("INTEGER_TYPE - %llu\n", obj->integer.value); 471fc3155b2SThomas Renninger break; 472fc3155b2SThomas Renninger case ACPI_TYPE_PACKAGE: 4738e07514dSDmitry Torokhov pr_cont("PACKAGE_TYPE - %d elements\n", obj->package.count); 474fc3155b2SThomas Renninger break; 475fc3155b2SThomas Renninger default: 4768e07514dSDmitry Torokhov pr_cont("object type 0x%X\n", obj->type); 477fc3155b2SThomas Renninger } 4781492616aSAxel Lin kfree(obj); 479fc3155b2SThomas Renninger } 480fc3155b2SThomas Renninger 481b4f9fe12SLen Brown /** 482b4f9fe12SLen Brown * wmi_install_notify_handler - Register handler for WMI events 483b4f9fe12SLen Brown * @handler: Function to handle notifications 484b4f9fe12SLen Brown * @data: Data to be returned to handler when event is fired 485b4f9fe12SLen Brown * 486b4f9fe12SLen Brown * Register a handler for events sent to the ACPI-WMI mapper device. 487b4f9fe12SLen Brown */ 488b4f9fe12SLen Brown acpi_status wmi_install_notify_handler(const char *guid, 489b4f9fe12SLen Brown wmi_notify_handler handler, void *data) 490b4f9fe12SLen Brown { 491b4f9fe12SLen Brown struct wmi_block *block; 49258f6425eSColin King acpi_status status = AE_NOT_EXIST; 493538d7eb8SAndy Shevchenko uuid_le guid_input; 49458f6425eSColin King struct list_head *p; 495b4f9fe12SLen Brown 496b4f9fe12SLen Brown if (!guid || !handler) 497b4f9fe12SLen Brown return AE_BAD_PARAMETER; 498b4f9fe12SLen Brown 499538d7eb8SAndy Shevchenko if (uuid_le_to_bin(guid, &guid_input)) 500538d7eb8SAndy Shevchenko return AE_BAD_PARAMETER; 501b4f9fe12SLen Brown 50258f6425eSColin King list_for_each(p, &wmi_block_list) { 50358f6425eSColin King acpi_status wmi_status; 50458f6425eSColin King block = list_entry(p, struct wmi_block, list); 50558f6425eSColin King 506538d7eb8SAndy Shevchenko if (memcmp(block->gblock.guid, &guid_input, 16) == 0) { 50758f6425eSColin King if (block->handler && 50858f6425eSColin King block->handler != wmi_notify_debug) 509b4f9fe12SLen Brown return AE_ALREADY_ACQUIRED; 510b4f9fe12SLen Brown 511b4f9fe12SLen Brown block->handler = handler; 512b4f9fe12SLen Brown block->handler_data = data; 513b4f9fe12SLen Brown 51458f6425eSColin King wmi_status = wmi_method_enable(block, 1); 51558f6425eSColin King if ((wmi_status != AE_OK) || 51658f6425eSColin King ((wmi_status == AE_OK) && (status == AE_NOT_EXIST))) 51758f6425eSColin King status = wmi_status; 51858f6425eSColin King } 51958f6425eSColin King } 520b4f9fe12SLen Brown 521b4f9fe12SLen Brown return status; 522b4f9fe12SLen Brown } 523b4f9fe12SLen Brown EXPORT_SYMBOL_GPL(wmi_install_notify_handler); 524b4f9fe12SLen Brown 525b4f9fe12SLen Brown /** 526b4f9fe12SLen Brown * wmi_uninstall_notify_handler - Unregister handler for WMI events 527b4f9fe12SLen Brown * 528b4f9fe12SLen Brown * Unregister handler for events sent to the ACPI-WMI mapper device. 529b4f9fe12SLen Brown */ 530b4f9fe12SLen Brown acpi_status wmi_remove_notify_handler(const char *guid) 531b4f9fe12SLen Brown { 532b4f9fe12SLen Brown struct wmi_block *block; 53358f6425eSColin King acpi_status status = AE_NOT_EXIST; 534538d7eb8SAndy Shevchenko uuid_le guid_input; 53558f6425eSColin King struct list_head *p; 536b4f9fe12SLen Brown 537b4f9fe12SLen Brown if (!guid) 538b4f9fe12SLen Brown return AE_BAD_PARAMETER; 539b4f9fe12SLen Brown 540538d7eb8SAndy Shevchenko if (uuid_le_to_bin(guid, &guid_input)) 541538d7eb8SAndy Shevchenko return AE_BAD_PARAMETER; 542b4f9fe12SLen Brown 54358f6425eSColin King list_for_each(p, &wmi_block_list) { 54458f6425eSColin King acpi_status wmi_status; 54558f6425eSColin King block = list_entry(p, struct wmi_block, list); 54658f6425eSColin King 547538d7eb8SAndy Shevchenko if (memcmp(block->gblock.guid, &guid_input, 16) == 0) { 54858f6425eSColin King if (!block->handler || 54958f6425eSColin King block->handler == wmi_notify_debug) 550b4f9fe12SLen Brown return AE_NULL_ENTRY; 551b4f9fe12SLen Brown 552fc3155b2SThomas Renninger if (debug_event) { 553fc3155b2SThomas Renninger block->handler = wmi_notify_debug; 55458f6425eSColin King status = AE_OK; 555fc3155b2SThomas Renninger } else { 55658f6425eSColin King wmi_status = wmi_method_enable(block, 0); 557b4f9fe12SLen Brown block->handler = NULL; 558b4f9fe12SLen Brown block->handler_data = NULL; 55958f6425eSColin King if ((wmi_status != AE_OK) || 56058f6425eSColin King ((wmi_status == AE_OK) && 56158f6425eSColin King (status == AE_NOT_EXIST))) 56258f6425eSColin King status = wmi_status; 563fc3155b2SThomas Renninger } 56458f6425eSColin King } 56558f6425eSColin King } 56658f6425eSColin King 567b4f9fe12SLen Brown return status; 568b4f9fe12SLen Brown } 569b4f9fe12SLen Brown EXPORT_SYMBOL_GPL(wmi_remove_notify_handler); 570b4f9fe12SLen Brown 571b4f9fe12SLen Brown /** 572b4f9fe12SLen Brown * wmi_get_event_data - Get WMI data associated with an event 573b4f9fe12SLen Brown * 5743e9b988eSAnisse Astier * @event: Event to find 5753e9b988eSAnisse Astier * @out: Buffer to hold event data. out->pointer should be freed with kfree() 576b4f9fe12SLen Brown * 577b4f9fe12SLen Brown * Returns extra data associated with an event in WMI. 578b4f9fe12SLen Brown */ 579b4f9fe12SLen Brown acpi_status wmi_get_event_data(u32 event, struct acpi_buffer *out) 580b4f9fe12SLen Brown { 581b4f9fe12SLen Brown struct acpi_object_list input; 582b4f9fe12SLen Brown union acpi_object params[1]; 583b4f9fe12SLen Brown struct guid_block *gblock; 584b4f9fe12SLen Brown struct wmi_block *wblock; 585b4f9fe12SLen Brown struct list_head *p; 586b4f9fe12SLen Brown 587b4f9fe12SLen Brown input.count = 1; 588b4f9fe12SLen Brown input.pointer = params; 589b4f9fe12SLen Brown params[0].type = ACPI_TYPE_INTEGER; 590b4f9fe12SLen Brown params[0].integer.value = event; 591b4f9fe12SLen Brown 592762e1a2fSDmitry Torokhov list_for_each(p, &wmi_block_list) { 593b4f9fe12SLen Brown wblock = list_entry(p, struct wmi_block, list); 594b4f9fe12SLen Brown gblock = &wblock->gblock; 595b4f9fe12SLen Brown 596b4f9fe12SLen Brown if ((gblock->flags & ACPI_WMI_EVENT) && 597b4f9fe12SLen Brown (gblock->notify_id == event)) 598b0e86302SAndy Lutomirski return acpi_evaluate_object(wblock->acpi_device->handle, 599b0e86302SAndy Lutomirski "_WED", &input, out); 600b4f9fe12SLen Brown } 601b4f9fe12SLen Brown 602b4f9fe12SLen Brown return AE_NOT_FOUND; 603b4f9fe12SLen Brown } 604b4f9fe12SLen Brown EXPORT_SYMBOL_GPL(wmi_get_event_data); 605b4f9fe12SLen Brown 606b4f9fe12SLen Brown /** 607b4f9fe12SLen Brown * wmi_has_guid - Check if a GUID is available 608b4f9fe12SLen Brown * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba 609b4f9fe12SLen Brown * 610b4f9fe12SLen Brown * Check if a given GUID is defined by _WDG 611b4f9fe12SLen Brown */ 612b4f9fe12SLen Brown bool wmi_has_guid(const char *guid_string) 613b4f9fe12SLen Brown { 614b4f9fe12SLen Brown return find_guid(guid_string, NULL); 615b4f9fe12SLen Brown } 616b4f9fe12SLen Brown EXPORT_SYMBOL_GPL(wmi_has_guid); 617b4f9fe12SLen Brown 618844af950SAndy Lutomirski static struct wmi_block *dev_to_wblock(struct device *dev) 619844af950SAndy Lutomirski { 620844af950SAndy Lutomirski return container_of(dev, struct wmi_block, dev.dev); 621844af950SAndy Lutomirski } 622844af950SAndy Lutomirski 623844af950SAndy Lutomirski static struct wmi_device *dev_to_wdev(struct device *dev) 624844af950SAndy Lutomirski { 625844af950SAndy Lutomirski return container_of(dev, struct wmi_device, dev); 626844af950SAndy Lutomirski } 627844af950SAndy Lutomirski 628b4f9fe12SLen Brown /* 6291caab3c1SMatthew Garrett * sysfs interface 6301caab3c1SMatthew Garrett */ 631614ef432SDmitry Torokhov static ssize_t modalias_show(struct device *dev, struct device_attribute *attr, 6321caab3c1SMatthew Garrett char *buf) 6331caab3c1SMatthew Garrett { 634844af950SAndy Lutomirski struct wmi_block *wblock = dev_to_wblock(dev); 6351caab3c1SMatthew Garrett 63685b4e4ebSRasmus Villemoes return sprintf(buf, "wmi:%pUL\n", wblock->gblock.guid); 6371caab3c1SMatthew Garrett } 638e80b89a5SGreg Kroah-Hartman static DEVICE_ATTR_RO(modalias); 639614ef432SDmitry Torokhov 640844af950SAndy Lutomirski static ssize_t guid_show(struct device *dev, struct device_attribute *attr, 641844af950SAndy Lutomirski char *buf) 642844af950SAndy Lutomirski { 643844af950SAndy Lutomirski struct wmi_block *wblock = dev_to_wblock(dev); 644844af950SAndy Lutomirski 645844af950SAndy Lutomirski return sprintf(buf, "%pUL\n", wblock->gblock.guid); 646844af950SAndy Lutomirski } 647844af950SAndy Lutomirski static DEVICE_ATTR_RO(guid); 648844af950SAndy Lutomirski 649d79b1074SAndy Lutomirski static ssize_t instance_count_show(struct device *dev, 650d79b1074SAndy Lutomirski struct device_attribute *attr, char *buf) 651d79b1074SAndy Lutomirski { 652d79b1074SAndy Lutomirski struct wmi_block *wblock = dev_to_wblock(dev); 653d79b1074SAndy Lutomirski 654d79b1074SAndy Lutomirski return sprintf(buf, "%d\n", (int)wblock->gblock.instance_count); 655d79b1074SAndy Lutomirski } 656d79b1074SAndy Lutomirski static DEVICE_ATTR_RO(instance_count); 657d79b1074SAndy Lutomirski 658d79b1074SAndy Lutomirski static ssize_t expensive_show(struct device *dev, 659d79b1074SAndy Lutomirski struct device_attribute *attr, char *buf) 660d79b1074SAndy Lutomirski { 661d79b1074SAndy Lutomirski struct wmi_block *wblock = dev_to_wblock(dev); 662d79b1074SAndy Lutomirski 663d79b1074SAndy Lutomirski return sprintf(buf, "%d\n", 664d79b1074SAndy Lutomirski (wblock->gblock.flags & ACPI_WMI_EXPENSIVE) != 0); 665d79b1074SAndy Lutomirski } 666d79b1074SAndy Lutomirski static DEVICE_ATTR_RO(expensive); 667d79b1074SAndy Lutomirski 668e80b89a5SGreg Kroah-Hartman static struct attribute *wmi_attrs[] = { 669e80b89a5SGreg Kroah-Hartman &dev_attr_modalias.attr, 670844af950SAndy Lutomirski &dev_attr_guid.attr, 671d79b1074SAndy Lutomirski &dev_attr_instance_count.attr, 672d79b1074SAndy Lutomirski &dev_attr_expensive.attr, 673e80b89a5SGreg Kroah-Hartman NULL, 674614ef432SDmitry Torokhov }; 675e80b89a5SGreg Kroah-Hartman ATTRIBUTE_GROUPS(wmi); 6761caab3c1SMatthew Garrett 677d79b1074SAndy Lutomirski static ssize_t notify_id_show(struct device *dev, struct device_attribute *attr, 678d79b1074SAndy Lutomirski char *buf) 679d79b1074SAndy Lutomirski { 680d79b1074SAndy Lutomirski struct wmi_block *wblock = dev_to_wblock(dev); 681d79b1074SAndy Lutomirski 682d79b1074SAndy Lutomirski return sprintf(buf, "%02X\n", (unsigned int)wblock->gblock.notify_id); 683d79b1074SAndy Lutomirski } 684d79b1074SAndy Lutomirski static DEVICE_ATTR_RO(notify_id); 685d79b1074SAndy Lutomirski 686d79b1074SAndy Lutomirski static struct attribute *wmi_event_attrs[] = { 687d79b1074SAndy Lutomirski &dev_attr_notify_id.attr, 688d79b1074SAndy Lutomirski NULL, 689d79b1074SAndy Lutomirski }; 690d79b1074SAndy Lutomirski ATTRIBUTE_GROUPS(wmi_event); 691d79b1074SAndy Lutomirski 692d79b1074SAndy Lutomirski static ssize_t object_id_show(struct device *dev, struct device_attribute *attr, 693d79b1074SAndy Lutomirski char *buf) 694d79b1074SAndy Lutomirski { 695d79b1074SAndy Lutomirski struct wmi_block *wblock = dev_to_wblock(dev); 696d79b1074SAndy Lutomirski 697d79b1074SAndy Lutomirski return sprintf(buf, "%c%c\n", wblock->gblock.object_id[0], 698d79b1074SAndy Lutomirski wblock->gblock.object_id[1]); 699d79b1074SAndy Lutomirski } 700d79b1074SAndy Lutomirski static DEVICE_ATTR_RO(object_id); 701d79b1074SAndy Lutomirski 702fd70da6aSDarren Hart (VMware) static ssize_t setable_show(struct device *dev, struct device_attribute *attr, 703d4fc91adSAndy Lutomirski char *buf) 704d4fc91adSAndy Lutomirski { 705d4fc91adSAndy Lutomirski struct wmi_device *wdev = dev_to_wdev(dev); 706d4fc91adSAndy Lutomirski 707fd70da6aSDarren Hart (VMware) return sprintf(buf, "%d\n", (int)wdev->setable); 708d4fc91adSAndy Lutomirski } 709fd70da6aSDarren Hart (VMware) static DEVICE_ATTR_RO(setable); 710d4fc91adSAndy Lutomirski 711d4fc91adSAndy Lutomirski static struct attribute *wmi_data_attrs[] = { 712d4fc91adSAndy Lutomirski &dev_attr_object_id.attr, 713fd70da6aSDarren Hart (VMware) &dev_attr_setable.attr, 714d4fc91adSAndy Lutomirski NULL, 715d4fc91adSAndy Lutomirski }; 716d4fc91adSAndy Lutomirski ATTRIBUTE_GROUPS(wmi_data); 717d4fc91adSAndy Lutomirski 718d4fc91adSAndy Lutomirski static struct attribute *wmi_method_attrs[] = { 719d79b1074SAndy Lutomirski &dev_attr_object_id.attr, 720d79b1074SAndy Lutomirski NULL, 721d79b1074SAndy Lutomirski }; 722d4fc91adSAndy Lutomirski ATTRIBUTE_GROUPS(wmi_method); 723d79b1074SAndy Lutomirski 7241caab3c1SMatthew Garrett static int wmi_dev_uevent(struct device *dev, struct kobj_uevent_env *env) 7251caab3c1SMatthew Garrett { 726844af950SAndy Lutomirski struct wmi_block *wblock = dev_to_wblock(dev); 7271caab3c1SMatthew Garrett 728844af950SAndy Lutomirski if (add_uevent_var(env, "MODALIAS=wmi:%pUL", wblock->gblock.guid)) 7291caab3c1SMatthew Garrett return -ENOMEM; 7301caab3c1SMatthew Garrett 731844af950SAndy Lutomirski if (add_uevent_var(env, "WMI_GUID=%pUL", wblock->gblock.guid)) 7321caab3c1SMatthew Garrett return -ENOMEM; 7331caab3c1SMatthew Garrett 7341caab3c1SMatthew Garrett return 0; 7351caab3c1SMatthew Garrett } 7361caab3c1SMatthew Garrett 737844af950SAndy Lutomirski static void wmi_dev_release(struct device *dev) 7381caab3c1SMatthew Garrett { 739844af950SAndy Lutomirski struct wmi_block *wblock = dev_to_wblock(dev); 740c64eefd4SDmitry Torokhov 741844af950SAndy Lutomirski kfree(wblock); 7421caab3c1SMatthew Garrett } 7431caab3c1SMatthew Garrett 744844af950SAndy Lutomirski static int wmi_dev_match(struct device *dev, struct device_driver *driver) 745844af950SAndy Lutomirski { 746844af950SAndy Lutomirski struct wmi_driver *wmi_driver = 747844af950SAndy Lutomirski container_of(driver, struct wmi_driver, driver); 748844af950SAndy Lutomirski struct wmi_block *wblock = dev_to_wblock(dev); 749844af950SAndy Lutomirski const struct wmi_device_id *id = wmi_driver->id_table; 750844af950SAndy Lutomirski 751844af950SAndy Lutomirski while (id->guid_string) { 752844af950SAndy Lutomirski uuid_le driver_guid; 753844af950SAndy Lutomirski 754844af950SAndy Lutomirski if (WARN_ON(uuid_le_to_bin(id->guid_string, &driver_guid))) 755844af950SAndy Lutomirski continue; 756844af950SAndy Lutomirski if (!memcmp(&driver_guid, wblock->gblock.guid, 16)) 757844af950SAndy Lutomirski return 1; 758844af950SAndy Lutomirski 759844af950SAndy Lutomirski id++; 760844af950SAndy Lutomirski } 761844af950SAndy Lutomirski 762844af950SAndy Lutomirski return 0; 763844af950SAndy Lutomirski } 764844af950SAndy Lutomirski 765844af950SAndy Lutomirski static int wmi_dev_probe(struct device *dev) 766844af950SAndy Lutomirski { 767844af950SAndy Lutomirski struct wmi_block *wblock = dev_to_wblock(dev); 768844af950SAndy Lutomirski struct wmi_driver *wdriver = 769844af950SAndy Lutomirski container_of(dev->driver, struct wmi_driver, driver); 770844af950SAndy Lutomirski int ret = 0; 771844af950SAndy Lutomirski 772844af950SAndy Lutomirski if (ACPI_FAILURE(wmi_method_enable(wblock, 1))) 773844af950SAndy Lutomirski dev_warn(dev, "failed to enable device -- probing anyway\n"); 774844af950SAndy Lutomirski 775844af950SAndy Lutomirski if (wdriver->probe) { 776844af950SAndy Lutomirski ret = wdriver->probe(dev_to_wdev(dev)); 777844af950SAndy Lutomirski if (ret != 0 && ACPI_FAILURE(wmi_method_enable(wblock, 0))) 778844af950SAndy Lutomirski dev_warn(dev, "failed to disable device\n"); 779844af950SAndy Lutomirski } 780844af950SAndy Lutomirski 781844af950SAndy Lutomirski return ret; 782844af950SAndy Lutomirski } 783844af950SAndy Lutomirski 784844af950SAndy Lutomirski static int wmi_dev_remove(struct device *dev) 785844af950SAndy Lutomirski { 786844af950SAndy Lutomirski struct wmi_block *wblock = dev_to_wblock(dev); 787844af950SAndy Lutomirski struct wmi_driver *wdriver = 788844af950SAndy Lutomirski container_of(dev->driver, struct wmi_driver, driver); 789844af950SAndy Lutomirski int ret = 0; 790844af950SAndy Lutomirski 791844af950SAndy Lutomirski if (wdriver->remove) 792844af950SAndy Lutomirski ret = wdriver->remove(dev_to_wdev(dev)); 793844af950SAndy Lutomirski 794844af950SAndy Lutomirski if (ACPI_FAILURE(wmi_method_enable(wblock, 0))) 795844af950SAndy Lutomirski dev_warn(dev, "failed to disable device\n"); 796844af950SAndy Lutomirski 797844af950SAndy Lutomirski return ret; 798844af950SAndy Lutomirski } 799844af950SAndy Lutomirski 800844af950SAndy Lutomirski static struct class wmi_bus_class = { 801844af950SAndy Lutomirski .name = "wmi_bus", 8021caab3c1SMatthew Garrett }; 8031caab3c1SMatthew Garrett 804844af950SAndy Lutomirski static struct bus_type wmi_bus_type = { 805844af950SAndy Lutomirski .name = "wmi", 806844af950SAndy Lutomirski .dev_groups = wmi_groups, 807844af950SAndy Lutomirski .match = wmi_dev_match, 808844af950SAndy Lutomirski .uevent = wmi_dev_uevent, 809844af950SAndy Lutomirski .probe = wmi_dev_probe, 810844af950SAndy Lutomirski .remove = wmi_dev_remove, 811844af950SAndy Lutomirski }; 812844af950SAndy Lutomirski 813d79b1074SAndy Lutomirski static struct device_type wmi_type_event = { 814d79b1074SAndy Lutomirski .name = "event", 815d79b1074SAndy Lutomirski .groups = wmi_event_groups, 816d79b1074SAndy Lutomirski .release = wmi_dev_release, 817d79b1074SAndy Lutomirski }; 818d79b1074SAndy Lutomirski 819d79b1074SAndy Lutomirski static struct device_type wmi_type_method = { 820d79b1074SAndy Lutomirski .name = "method", 821d4fc91adSAndy Lutomirski .groups = wmi_method_groups, 822d79b1074SAndy Lutomirski .release = wmi_dev_release, 823d79b1074SAndy Lutomirski }; 824d79b1074SAndy Lutomirski 825d79b1074SAndy Lutomirski static struct device_type wmi_type_data = { 826d79b1074SAndy Lutomirski .name = "data", 827d4fc91adSAndy Lutomirski .groups = wmi_data_groups, 828d79b1074SAndy Lutomirski .release = wmi_dev_release, 829d79b1074SAndy Lutomirski }; 830d79b1074SAndy Lutomirski 831fd70da6aSDarren Hart (VMware) static int wmi_create_device(struct device *wmi_bus_dev, 832844af950SAndy Lutomirski const struct guid_block *gblock, 8337f5809bfSAndy Lutomirski struct wmi_block *wblock, 8347f5809bfSAndy Lutomirski struct acpi_device *device) 8351caab3c1SMatthew Garrett { 836d4fc91adSAndy Lutomirski struct acpi_device_info *info; 837d4fc91adSAndy Lutomirski char method[5]; 838d4fc91adSAndy Lutomirski int result; 839d4fc91adSAndy Lutomirski 840fd70da6aSDarren Hart (VMware) if (gblock->flags & ACPI_WMI_EVENT) { 841fd70da6aSDarren Hart (VMware) wblock->dev.dev.type = &wmi_type_event; 842fd70da6aSDarren Hart (VMware) goto out_init; 843fd70da6aSDarren Hart (VMware) } 844d4fc91adSAndy Lutomirski 845fd70da6aSDarren Hart (VMware) if (gblock->flags & ACPI_WMI_METHOD) { 846fd70da6aSDarren Hart (VMware) wblock->dev.dev.type = &wmi_type_method; 847fd70da6aSDarren Hart (VMware) goto out_init; 848fd70da6aSDarren Hart (VMware) } 849fd70da6aSDarren Hart (VMware) 850fd70da6aSDarren Hart (VMware) /* 851fd70da6aSDarren Hart (VMware) * Data Block Query Control Method (WQxx by convention) is 852fd70da6aSDarren Hart (VMware) * required per the WMI documentation. If it is not present, 853fd70da6aSDarren Hart (VMware) * we ignore this data block. 854fd70da6aSDarren Hart (VMware) */ 855d4fc91adSAndy Lutomirski strcpy(method, "WQ"); 856d4fc91adSAndy Lutomirski strncat(method, wblock->gblock.object_id, 2); 857d4fc91adSAndy Lutomirski result = get_subobj_info(device->handle, method, &info); 858d4fc91adSAndy Lutomirski 859fd70da6aSDarren Hart (VMware) if (result) { 860fd70da6aSDarren Hart (VMware) dev_warn(wmi_bus_dev, 861fd70da6aSDarren Hart (VMware) "%s data block query control method not found", 862fd70da6aSDarren Hart (VMware) method); 863fd70da6aSDarren Hart (VMware) return result; 864fd70da6aSDarren Hart (VMware) } 865fd70da6aSDarren Hart (VMware) 866fd70da6aSDarren Hart (VMware) wblock->dev.dev.type = &wmi_type_data; 867d4fc91adSAndy Lutomirski 868d4fc91adSAndy Lutomirski /* 869d4fc91adSAndy Lutomirski * The Microsoft documentation specifically states: 870d4fc91adSAndy Lutomirski * 871d4fc91adSAndy Lutomirski * Data blocks registered with only a single instance 872d4fc91adSAndy Lutomirski * can ignore the parameter. 873d4fc91adSAndy Lutomirski * 874fd70da6aSDarren Hart (VMware) * ACPICA will get mad at us if we call the method with the wrong number 875fd70da6aSDarren Hart (VMware) * of arguments, so check what our method expects. (On some Dell 876fd70da6aSDarren Hart (VMware) * laptops, WQxx may not be a method at all.) 877d4fc91adSAndy Lutomirski */ 878fd70da6aSDarren Hart (VMware) if (info->type != ACPI_TYPE_METHOD || info->param_count == 0) 879d4fc91adSAndy Lutomirski wblock->read_takes_no_args = true; 880d4fc91adSAndy Lutomirski 881d4fc91adSAndy Lutomirski kfree(info); 882d4fc91adSAndy Lutomirski 883d4fc91adSAndy Lutomirski strcpy(method, "WS"); 884d4fc91adSAndy Lutomirski strncat(method, wblock->gblock.object_id, 2); 885d4fc91adSAndy Lutomirski result = get_subobj_info(device->handle, method, NULL); 886d4fc91adSAndy Lutomirski 887fd70da6aSDarren Hart (VMware) if (result == 0) 888fd70da6aSDarren Hart (VMware) wblock->dev.setable = true; 889d4fc91adSAndy Lutomirski 890fd70da6aSDarren Hart (VMware) out_init: 891fd70da6aSDarren Hart (VMware) wblock->dev.dev.bus = &wmi_bus_type; 892fd70da6aSDarren Hart (VMware) wblock->dev.dev.parent = wmi_bus_dev; 893fd70da6aSDarren Hart (VMware) 894fd70da6aSDarren Hart (VMware) dev_set_name(&wblock->dev.dev, "%pUL", gblock->guid); 895c64eefd4SDmitry Torokhov 8966ee50aaaSDarren Hart (VMware) device_initialize(&wblock->dev.dev); 897fd70da6aSDarren Hart (VMware) 898fd70da6aSDarren Hart (VMware) return 0; 8991caab3c1SMatthew Garrett } 9001caab3c1SMatthew Garrett 901b0e86302SAndy Lutomirski static void wmi_free_devices(struct acpi_device *device) 9021caab3c1SMatthew Garrett { 903c64eefd4SDmitry Torokhov struct wmi_block *wblock, *next; 9041caab3c1SMatthew Garrett 9051caab3c1SMatthew Garrett /* Delete devices for all the GUIDs */ 906023b9565SDmitry Torokhov list_for_each_entry_safe(wblock, next, &wmi_block_list, list) { 907b0e86302SAndy Lutomirski if (wblock->acpi_device == device) { 908023b9565SDmitry Torokhov list_del(&wblock->list); 909844af950SAndy Lutomirski device_unregister(&wblock->dev.dev); 910023b9565SDmitry Torokhov } 9111caab3c1SMatthew Garrett } 912b0e86302SAndy Lutomirski } 9131caab3c1SMatthew Garrett 914b0e86302SAndy Lutomirski static bool guid_already_parsed(struct acpi_device *device, 915b0e86302SAndy Lutomirski const u8 *guid) 916d1f9e497SCarlos Corbacho { 917d1f9e497SCarlos Corbacho struct wmi_block *wblock; 918d1f9e497SCarlos Corbacho 919b0e86302SAndy Lutomirski list_for_each_entry(wblock, &wmi_block_list, list) { 920b0e86302SAndy Lutomirski if (memcmp(wblock->gblock.guid, guid, 16) == 0) { 921b0e86302SAndy Lutomirski /* 922b0e86302SAndy Lutomirski * Because we historically didn't track the relationship 923b0e86302SAndy Lutomirski * between GUIDs and ACPI nodes, we don't know whether 924b0e86302SAndy Lutomirski * we need to suppress GUIDs that are unique on a 925b0e86302SAndy Lutomirski * given node but duplicated across nodes. 926b0e86302SAndy Lutomirski */ 927b0e86302SAndy Lutomirski dev_warn(&device->dev, "duplicate WMI GUID %pUL (first instance was on %s)\n", 928b0e86302SAndy Lutomirski guid, dev_name(&wblock->acpi_device->dev)); 929d1f9e497SCarlos Corbacho return true; 930b0e86302SAndy Lutomirski } 931b0e86302SAndy Lutomirski } 932c64eefd4SDmitry Torokhov 933d1f9e497SCarlos Corbacho return false; 934d1f9e497SCarlos Corbacho } 935d1f9e497SCarlos Corbacho 9361caab3c1SMatthew Garrett /* 937b4f9fe12SLen Brown * Parse the _WDG method for the GUID data blocks 938b4f9fe12SLen Brown */ 939844af950SAndy Lutomirski static int parse_wdg(struct device *wmi_bus_dev, struct acpi_device *device) 940b4f9fe12SLen Brown { 941b4f9fe12SLen Brown struct acpi_buffer out = {ACPI_ALLOCATE_BUFFER, NULL}; 94237830662SDmitry Torokhov const struct guid_block *gblock; 9436ee50aaaSDarren Hart (VMware) struct wmi_block *wblock, *next; 9446ee50aaaSDarren Hart (VMware) union acpi_object *obj; 945b4f9fe12SLen Brown acpi_status status; 9466ee50aaaSDarren Hart (VMware) int retval = 0; 947b4f9fe12SLen Brown u32 i, total; 948b4f9fe12SLen Brown 9497f5809bfSAndy Lutomirski status = acpi_evaluate_object(device->handle, "_WDG", NULL, &out); 950b4f9fe12SLen Brown if (ACPI_FAILURE(status)) 951c64eefd4SDmitry Torokhov return -ENXIO; 952b4f9fe12SLen Brown 953b4f9fe12SLen Brown obj = (union acpi_object *) out.pointer; 9543d2c63ebSDmitry Torokhov if (!obj) 955c64eefd4SDmitry Torokhov return -ENXIO; 956b4f9fe12SLen Brown 95764ed0ab8SDmitry Torokhov if (obj->type != ACPI_TYPE_BUFFER) { 958c64eefd4SDmitry Torokhov retval = -ENXIO; 95964ed0ab8SDmitry Torokhov goto out_free_pointer; 96064ed0ab8SDmitry Torokhov } 961b4f9fe12SLen Brown 96237830662SDmitry Torokhov gblock = (const struct guid_block *)obj->buffer.pointer; 963b4f9fe12SLen Brown total = obj->buffer.length / sizeof(struct guid_block); 964b4f9fe12SLen Brown 965b4f9fe12SLen Brown for (i = 0; i < total; i++) { 966a929aae0SThomas Renninger if (debug_dump_wdg) 967a929aae0SThomas Renninger wmi_dump_wdg(&gblock[i]); 968a929aae0SThomas Renninger 969a1c31bcdSAndy Lutomirski /* 970a1c31bcdSAndy Lutomirski * Some WMI devices, like those for nVidia hooks, have a 971a1c31bcdSAndy Lutomirski * duplicate GUID. It's not clear what we should do in this 972a1c31bcdSAndy Lutomirski * case yet, so for now, we'll just ignore the duplicate 973a1c31bcdSAndy Lutomirski * for device creation. 974a1c31bcdSAndy Lutomirski */ 975a1c31bcdSAndy Lutomirski if (guid_already_parsed(device, gblock[i].guid)) 976a1c31bcdSAndy Lutomirski continue; 977a1c31bcdSAndy Lutomirski 97858f6425eSColin King wblock = kzalloc(sizeof(struct wmi_block), GFP_KERNEL); 9796ee50aaaSDarren Hart (VMware) if (!wblock) { 9806ee50aaaSDarren Hart (VMware) retval = -ENOMEM; 9816ee50aaaSDarren Hart (VMware) break; 9826ee50aaaSDarren Hart (VMware) } 98358f6425eSColin King 984b0e86302SAndy Lutomirski wblock->acpi_device = device; 98558f6425eSColin King wblock->gblock = gblock[i]; 98658f6425eSColin King 987fd70da6aSDarren Hart (VMware) retval = wmi_create_device(wmi_bus_dev, &gblock[i], wblock, device); 988fd70da6aSDarren Hart (VMware) if (retval) { 989fd70da6aSDarren Hart (VMware) kfree(wblock); 990fd70da6aSDarren Hart (VMware) continue; 991fd70da6aSDarren Hart (VMware) } 99258f6425eSColin King 99358f6425eSColin King list_add_tail(&wblock->list, &wmi_block_list); 994b4f9fe12SLen Brown 995fc3155b2SThomas Renninger if (debug_event) { 996fc3155b2SThomas Renninger wblock->handler = wmi_notify_debug; 9972d5ab555SDmitry Torokhov wmi_method_enable(wblock, 1); 998fc3155b2SThomas Renninger } 999b4f9fe12SLen Brown } 1000b4f9fe12SLen Brown 10016ee50aaaSDarren Hart (VMware) /* 10026ee50aaaSDarren Hart (VMware) * Now that all of the devices are created, add them to the 10036ee50aaaSDarren Hart (VMware) * device tree and probe subdrivers. 10046ee50aaaSDarren Hart (VMware) */ 10056ee50aaaSDarren Hart (VMware) list_for_each_entry_safe(wblock, next, &wmi_block_list, list) { 10066ee50aaaSDarren Hart (VMware) if (wblock->acpi_device != device) 10076ee50aaaSDarren Hart (VMware) continue; 10086ee50aaaSDarren Hart (VMware) 10096ee50aaaSDarren Hart (VMware) retval = device_add(&wblock->dev.dev); 10106ee50aaaSDarren Hart (VMware) if (retval) { 10116ee50aaaSDarren Hart (VMware) dev_err(wmi_bus_dev, "failed to register %pULL\n", 10126ee50aaaSDarren Hart (VMware) wblock->gblock.guid); 10136ee50aaaSDarren Hart (VMware) if (debug_event) 10146ee50aaaSDarren Hart (VMware) wmi_method_enable(wblock, 0); 10156ee50aaaSDarren Hart (VMware) list_del(&wblock->list); 10166ee50aaaSDarren Hart (VMware) put_device(&wblock->dev.dev); 10176ee50aaaSDarren Hart (VMware) } 10186ee50aaaSDarren Hart (VMware) } 1019c64eefd4SDmitry Torokhov 1020a5167c5bSAxel Lin out_free_pointer: 1021a5167c5bSAxel Lin kfree(out.pointer); 1022c64eefd4SDmitry Torokhov return retval; 1023b4f9fe12SLen Brown } 1024b4f9fe12SLen Brown 1025b4f9fe12SLen Brown /* 1026b4f9fe12SLen Brown * WMI can have EmbeddedControl access regions. In which case, we just want to 1027b4f9fe12SLen Brown * hand these off to the EC driver. 1028b4f9fe12SLen Brown */ 1029b4f9fe12SLen Brown static acpi_status 1030b4f9fe12SLen Brown acpi_wmi_ec_space_handler(u32 function, acpi_physical_address address, 1031439913ffSLin Ming u32 bits, u64 *value, 1032b4f9fe12SLen Brown void *handler_context, void *region_context) 1033b4f9fe12SLen Brown { 1034b4f9fe12SLen Brown int result = 0, i = 0; 1035b4f9fe12SLen Brown u8 temp = 0; 1036b4f9fe12SLen Brown 1037b4f9fe12SLen Brown if ((address > 0xFF) || !value) 1038b4f9fe12SLen Brown return AE_BAD_PARAMETER; 1039b4f9fe12SLen Brown 1040b4f9fe12SLen Brown if (function != ACPI_READ && function != ACPI_WRITE) 1041b4f9fe12SLen Brown return AE_BAD_PARAMETER; 1042b4f9fe12SLen Brown 1043b4f9fe12SLen Brown if (bits != 8) 1044b4f9fe12SLen Brown return AE_BAD_PARAMETER; 1045b4f9fe12SLen Brown 1046b4f9fe12SLen Brown if (function == ACPI_READ) { 1047b4f9fe12SLen Brown result = ec_read(address, &temp); 1048439913ffSLin Ming (*value) |= ((u64)temp) << i; 1049b4f9fe12SLen Brown } else { 1050b4f9fe12SLen Brown temp = 0xff & ((*value) >> i); 1051b4f9fe12SLen Brown result = ec_write(address, temp); 1052b4f9fe12SLen Brown } 1053b4f9fe12SLen Brown 1054b4f9fe12SLen Brown switch (result) { 1055b4f9fe12SLen Brown case -EINVAL: 1056b4f9fe12SLen Brown return AE_BAD_PARAMETER; 1057b4f9fe12SLen Brown break; 1058b4f9fe12SLen Brown case -ENODEV: 1059b4f9fe12SLen Brown return AE_NOT_FOUND; 1060b4f9fe12SLen Brown break; 1061b4f9fe12SLen Brown case -ETIME: 1062b4f9fe12SLen Brown return AE_TIME; 1063b4f9fe12SLen Brown break; 1064b4f9fe12SLen Brown default: 1065b4f9fe12SLen Brown return AE_OK; 1066b4f9fe12SLen Brown } 1067b4f9fe12SLen Brown } 1068b4f9fe12SLen Brown 10691686f544SAndy Lutomirski static void acpi_wmi_notify_handler(acpi_handle handle, u32 event, 10701686f544SAndy Lutomirski void *context) 1071b4f9fe12SLen Brown { 1072b4f9fe12SLen Brown struct guid_block *block; 1073b4f9fe12SLen Brown struct wmi_block *wblock; 1074b4f9fe12SLen Brown struct list_head *p; 10751686f544SAndy Lutomirski bool found_it = false; 1076b4f9fe12SLen Brown 1077762e1a2fSDmitry Torokhov list_for_each(p, &wmi_block_list) { 1078b4f9fe12SLen Brown wblock = list_entry(p, struct wmi_block, list); 1079b4f9fe12SLen Brown block = &wblock->gblock; 1080b4f9fe12SLen Brown 10811686f544SAndy Lutomirski if (wblock->acpi_device->handle == handle && 1082b0e86302SAndy Lutomirski (block->flags & ACPI_WMI_EVENT) && 10831686f544SAndy Lutomirski (block->notify_id == event)) 10841686f544SAndy Lutomirski { 10851686f544SAndy Lutomirski found_it = true; 10861686f544SAndy Lutomirski break; 10871686f544SAndy Lutomirski } 10881686f544SAndy Lutomirski } 10891686f544SAndy Lutomirski 10901686f544SAndy Lutomirski if (!found_it) 10911686f544SAndy Lutomirski return; 10921686f544SAndy Lutomirski 10931686f544SAndy Lutomirski /* If a driver is bound, then notify the driver. */ 10941686f544SAndy Lutomirski if (wblock->dev.dev.driver) { 10951686f544SAndy Lutomirski struct wmi_driver *driver; 10961686f544SAndy Lutomirski struct acpi_object_list input; 10971686f544SAndy Lutomirski union acpi_object params[1]; 10981686f544SAndy Lutomirski struct acpi_buffer evdata = { ACPI_ALLOCATE_BUFFER, NULL }; 10991686f544SAndy Lutomirski acpi_status status; 11001686f544SAndy Lutomirski 11011686f544SAndy Lutomirski driver = container_of(wblock->dev.dev.driver, 11021686f544SAndy Lutomirski struct wmi_driver, driver); 11031686f544SAndy Lutomirski 11041686f544SAndy Lutomirski input.count = 1; 11051686f544SAndy Lutomirski input.pointer = params; 11061686f544SAndy Lutomirski params[0].type = ACPI_TYPE_INTEGER; 11071686f544SAndy Lutomirski params[0].integer.value = event; 11081686f544SAndy Lutomirski 11091686f544SAndy Lutomirski status = acpi_evaluate_object(wblock->acpi_device->handle, 11101686f544SAndy Lutomirski "_WED", &input, &evdata); 11111686f544SAndy Lutomirski if (ACPI_FAILURE(status)) { 11121686f544SAndy Lutomirski dev_warn(&wblock->dev.dev, 11131686f544SAndy Lutomirski "failed to get event data\n"); 11141686f544SAndy Lutomirski return; 11151686f544SAndy Lutomirski } 11161686f544SAndy Lutomirski 11171686f544SAndy Lutomirski if (driver->notify) 11181686f544SAndy Lutomirski driver->notify(&wblock->dev, 11191686f544SAndy Lutomirski (union acpi_object *)evdata.pointer); 11201686f544SAndy Lutomirski 11211686f544SAndy Lutomirski kfree(evdata.pointer); 11221686f544SAndy Lutomirski } else if (wblock->handler) { 11231686f544SAndy Lutomirski /* Legacy handler */ 1124b4f9fe12SLen Brown wblock->handler(event, wblock->handler_data); 11251686f544SAndy Lutomirski } 11261686f544SAndy Lutomirski 11277715348cSThomas Renninger if (debug_event) { 112885b4e4ebSRasmus Villemoes pr_info("DEBUG Event GUID: %pUL\n", 112985b4e4ebSRasmus Villemoes wblock->gblock.guid); 11307715348cSThomas Renninger } 1131b4f9fe12SLen Brown 1132b4f9fe12SLen Brown acpi_bus_generate_netlink_event( 11331686f544SAndy Lutomirski wblock->acpi_device->pnp.device_class, 11341686f544SAndy Lutomirski dev_name(&wblock->dev.dev), 1135b4f9fe12SLen Brown event, 0); 11361686f544SAndy Lutomirski 1137b4f9fe12SLen Brown } 1138b4f9fe12SLen Brown 11399599ed91SAndy Lutomirski static int acpi_wmi_remove(struct platform_device *device) 1140b4f9fe12SLen Brown { 11419599ed91SAndy Lutomirski struct acpi_device *acpi_device = ACPI_COMPANION(&device->dev); 11429599ed91SAndy Lutomirski 11439599ed91SAndy Lutomirski acpi_remove_notify_handler(acpi_device->handle, ACPI_DEVICE_NOTIFY, 11441686f544SAndy Lutomirski acpi_wmi_notify_handler); 11459599ed91SAndy Lutomirski acpi_remove_address_space_handler(acpi_device->handle, 1146b4f9fe12SLen Brown ACPI_ADR_SPACE_EC, &acpi_wmi_ec_space_handler); 11479599ed91SAndy Lutomirski wmi_free_devices(acpi_device); 11489599ed91SAndy Lutomirski device_unregister((struct device *)dev_get_drvdata(&device->dev)); 1149b4f9fe12SLen Brown 1150b4f9fe12SLen Brown return 0; 1151b4f9fe12SLen Brown } 1152b4f9fe12SLen Brown 11539599ed91SAndy Lutomirski static int acpi_wmi_probe(struct platform_device *device) 1154b4f9fe12SLen Brown { 11559599ed91SAndy Lutomirski struct acpi_device *acpi_device; 1156844af950SAndy Lutomirski struct device *wmi_bus_dev; 1157b4f9fe12SLen Brown acpi_status status; 1158c64eefd4SDmitry Torokhov int error; 1159b4f9fe12SLen Brown 11609599ed91SAndy Lutomirski acpi_device = ACPI_COMPANION(&device->dev); 11619599ed91SAndy Lutomirski if (!acpi_device) { 11629599ed91SAndy Lutomirski dev_err(&device->dev, "ACPI companion is missing\n"); 11639599ed91SAndy Lutomirski return -ENODEV; 11649599ed91SAndy Lutomirski } 11659599ed91SAndy Lutomirski 11669599ed91SAndy Lutomirski status = acpi_install_address_space_handler(acpi_device->handle, 1167b4f9fe12SLen Brown ACPI_ADR_SPACE_EC, 1168b4f9fe12SLen Brown &acpi_wmi_ec_space_handler, 1169b4f9fe12SLen Brown NULL, NULL); 11705212cd67SDmitry Torokhov if (ACPI_FAILURE(status)) { 117146492ee4SAndy Lutomirski dev_err(&device->dev, "Error installing EC region handler\n"); 1172b4f9fe12SLen Brown return -ENODEV; 11735212cd67SDmitry Torokhov } 1174b4f9fe12SLen Brown 11759599ed91SAndy Lutomirski status = acpi_install_notify_handler(acpi_device->handle, 11769599ed91SAndy Lutomirski ACPI_DEVICE_NOTIFY, 11771686f544SAndy Lutomirski acpi_wmi_notify_handler, 11781686f544SAndy Lutomirski NULL); 11791686f544SAndy Lutomirski if (ACPI_FAILURE(status)) { 11801686f544SAndy Lutomirski dev_err(&device->dev, "Error installing notify handler\n"); 11811686f544SAndy Lutomirski error = -ENODEV; 11821686f544SAndy Lutomirski goto err_remove_ec_handler; 11831686f544SAndy Lutomirski } 11841686f544SAndy Lutomirski 1185844af950SAndy Lutomirski wmi_bus_dev = device_create(&wmi_bus_class, &device->dev, MKDEV(0, 0), 1186844af950SAndy Lutomirski NULL, "wmi_bus-%s", dev_name(&device->dev)); 1187844af950SAndy Lutomirski if (IS_ERR(wmi_bus_dev)) { 1188844af950SAndy Lutomirski error = PTR_ERR(wmi_bus_dev); 11891686f544SAndy Lutomirski goto err_remove_notify_handler; 1190844af950SAndy Lutomirski } 11919599ed91SAndy Lutomirski dev_set_drvdata(&device->dev, wmi_bus_dev); 1192844af950SAndy Lutomirski 11939599ed91SAndy Lutomirski error = parse_wdg(wmi_bus_dev, acpi_device); 1194c64eefd4SDmitry Torokhov if (error) { 11958e07514dSDmitry Torokhov pr_err("Failed to parse WDG method\n"); 1196844af950SAndy Lutomirski goto err_remove_busdev; 1197b4f9fe12SLen Brown } 1198b4f9fe12SLen Brown 1199c64eefd4SDmitry Torokhov return 0; 120046492ee4SAndy Lutomirski 1201844af950SAndy Lutomirski err_remove_busdev: 1202844af950SAndy Lutomirski device_unregister(wmi_bus_dev); 1203844af950SAndy Lutomirski 12041686f544SAndy Lutomirski err_remove_notify_handler: 12059599ed91SAndy Lutomirski acpi_remove_notify_handler(acpi_device->handle, ACPI_DEVICE_NOTIFY, 12061686f544SAndy Lutomirski acpi_wmi_notify_handler); 12071686f544SAndy Lutomirski 12081686f544SAndy Lutomirski err_remove_ec_handler: 12099599ed91SAndy Lutomirski acpi_remove_address_space_handler(acpi_device->handle, 121046492ee4SAndy Lutomirski ACPI_ADR_SPACE_EC, 121146492ee4SAndy Lutomirski &acpi_wmi_ec_space_handler); 121246492ee4SAndy Lutomirski 121346492ee4SAndy Lutomirski return error; 1214b4f9fe12SLen Brown } 1215b4f9fe12SLen Brown 1216844af950SAndy Lutomirski int __must_check __wmi_driver_register(struct wmi_driver *driver, 1217844af950SAndy Lutomirski struct module *owner) 1218844af950SAndy Lutomirski { 1219844af950SAndy Lutomirski driver->driver.owner = owner; 1220844af950SAndy Lutomirski driver->driver.bus = &wmi_bus_type; 1221844af950SAndy Lutomirski 1222844af950SAndy Lutomirski return driver_register(&driver->driver); 1223844af950SAndy Lutomirski } 1224844af950SAndy Lutomirski EXPORT_SYMBOL(__wmi_driver_register); 1225844af950SAndy Lutomirski 1226844af950SAndy Lutomirski void wmi_driver_unregister(struct wmi_driver *driver) 1227844af950SAndy Lutomirski { 1228844af950SAndy Lutomirski driver_unregister(&driver->driver); 1229844af950SAndy Lutomirski } 1230844af950SAndy Lutomirski EXPORT_SYMBOL(wmi_driver_unregister); 1231844af950SAndy Lutomirski 1232b4f9fe12SLen Brown static int __init acpi_wmi_init(void) 1233b4f9fe12SLen Brown { 1234c64eefd4SDmitry Torokhov int error; 1235b4f9fe12SLen Brown 1236b4f9fe12SLen Brown if (acpi_disabled) 1237b4f9fe12SLen Brown return -ENODEV; 1238b4f9fe12SLen Brown 1239844af950SAndy Lutomirski error = class_register(&wmi_bus_class); 1240c64eefd4SDmitry Torokhov if (error) 1241c64eefd4SDmitry Torokhov return error; 1242b4f9fe12SLen Brown 1243844af950SAndy Lutomirski error = bus_register(&wmi_bus_type); 1244844af950SAndy Lutomirski if (error) 1245844af950SAndy Lutomirski goto err_unreg_class; 1246844af950SAndy Lutomirski 12479599ed91SAndy Lutomirski error = platform_driver_register(&acpi_wmi_driver); 1248c64eefd4SDmitry Torokhov if (error) { 1249c64eefd4SDmitry Torokhov pr_err("Error loading mapper\n"); 1250844af950SAndy Lutomirski goto err_unreg_bus; 12511caab3c1SMatthew Garrett } 12521caab3c1SMatthew Garrett 12538e07514dSDmitry Torokhov return 0; 1254844af950SAndy Lutomirski 1255844af950SAndy Lutomirski err_unreg_class: 1256844af950SAndy Lutomirski class_unregister(&wmi_bus_class); 1257844af950SAndy Lutomirski 1258844af950SAndy Lutomirski err_unreg_bus: 1259844af950SAndy Lutomirski bus_unregister(&wmi_bus_type); 1260844af950SAndy Lutomirski 1261844af950SAndy Lutomirski return error; 1262b4f9fe12SLen Brown } 1263b4f9fe12SLen Brown 1264b4f9fe12SLen Brown static void __exit acpi_wmi_exit(void) 1265b4f9fe12SLen Brown { 12669599ed91SAndy Lutomirski platform_driver_unregister(&acpi_wmi_driver); 1267844af950SAndy Lutomirski class_unregister(&wmi_bus_class); 1268844af950SAndy Lutomirski bus_unregister(&wmi_bus_type); 1269b4f9fe12SLen Brown } 1270b4f9fe12SLen Brown 1271b4f9fe12SLen Brown subsys_initcall(acpi_wmi_init); 1272b4f9fe12SLen Brown module_exit(acpi_wmi_exit); 1273