11a59d1b8SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later 2b4f9fe12SLen Brown /* 3b4f9fe12SLen Brown * ACPI-WMI mapping driver 4b4f9fe12SLen Brown * 5b4f9fe12SLen Brown * Copyright (C) 2007-2008 Carlos Corbacho <carlos@strangeworlds.co.uk> 6b4f9fe12SLen Brown * 7b4f9fe12SLen Brown * GUID parsing code from ldm.c is: 8b4f9fe12SLen Brown * Copyright (C) 2001,2002 Richard Russon <ldm@flatcap.org> 9b4f9fe12SLen Brown * Copyright (c) 2001-2007 Anton Altaparmakov 10b4f9fe12SLen Brown * Copyright (C) 2001,2002 Jakob Kemi <jakob.kemi@telia.com> 11b4f9fe12SLen Brown * 122c9c5664SDarren Hart (VMware) * WMI bus infrastructure by Andrew Lutomirski and Darren Hart: 132c9c5664SDarren Hart (VMware) * Copyright (C) 2015 Andrew Lutomirski 142c9c5664SDarren Hart (VMware) * Copyright (C) 2017 VMware, Inc. All Rights Reserved. 15b4f9fe12SLen Brown */ 16b4f9fe12SLen Brown 178e07514dSDmitry Torokhov #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 188e07514dSDmitry Torokhov 19b4f9fe12SLen Brown #include <linux/acpi.h> 20b60ee4e0SMario Limonciello #include <linux/device.h> 21b60ee4e0SMario Limonciello #include <linux/init.h> 22b60ee4e0SMario Limonciello #include <linux/kernel.h> 23b60ee4e0SMario Limonciello #include <linux/list.h> 2444b6b766SMario Limonciello #include <linux/miscdevice.h> 257c52d551SPaul Gortmaker #include <linux/module.h> 269599ed91SAndy Lutomirski #include <linux/platform_device.h> 27b60ee4e0SMario Limonciello #include <linux/slab.h> 28b60ee4e0SMario Limonciello #include <linux/types.h> 2944b6b766SMario Limonciello #include <linux/uaccess.h> 30538d7eb8SAndy Shevchenko #include <linux/uuid.h> 31b60ee4e0SMario Limonciello #include <linux/wmi.h> 32df23e2beSPeter Zijlstra #include <linux/fs.h> 3344b6b766SMario Limonciello #include <uapi/linux/wmi.h> 34b4f9fe12SLen Brown 35b4f9fe12SLen Brown MODULE_AUTHOR("Carlos Corbacho"); 36b4f9fe12SLen Brown MODULE_DESCRIPTION("ACPI-WMI Mapping Driver"); 37b4f9fe12SLen Brown MODULE_LICENSE("GPL"); 38b4f9fe12SLen Brown 39762e1a2fSDmitry Torokhov static LIST_HEAD(wmi_block_list); 40b4f9fe12SLen Brown 41b4f9fe12SLen Brown struct guid_block { 42b4f9fe12SLen Brown char guid[16]; 43b4f9fe12SLen Brown union { 44b4f9fe12SLen Brown char object_id[2]; 45b4f9fe12SLen Brown struct { 46b4f9fe12SLen Brown unsigned char notify_id; 47b4f9fe12SLen Brown unsigned char reserved; 48b4f9fe12SLen Brown }; 49b4f9fe12SLen Brown }; 50b4f9fe12SLen Brown u8 instance_count; 51b4f9fe12SLen Brown u8 flags; 52b4f9fe12SLen Brown }; 53b4f9fe12SLen Brown 54b4f9fe12SLen Brown struct wmi_block { 55844af950SAndy Lutomirski struct wmi_device dev; 56b4f9fe12SLen Brown struct list_head list; 57b4f9fe12SLen Brown struct guid_block gblock; 5844b6b766SMario Limonciello struct miscdevice char_dev; 5944b6b766SMario Limonciello struct mutex char_mutex; 60b0e86302SAndy Lutomirski struct acpi_device *acpi_device; 61b4f9fe12SLen Brown wmi_notify_handler handler; 62b4f9fe12SLen Brown void *handler_data; 6344b6b766SMario Limonciello u64 req_buf_size; 64d4fc91adSAndy Lutomirski 65fd70da6aSDarren Hart (VMware) bool read_takes_no_args; 66b4f9fe12SLen Brown }; 67b4f9fe12SLen Brown 68b4f9fe12SLen Brown 69b4f9fe12SLen Brown /* 70b4f9fe12SLen Brown * If the GUID data block is marked as expensive, we must enable and 71b4f9fe12SLen Brown * explicitily disable data collection. 72b4f9fe12SLen Brown */ 73b4f9fe12SLen Brown #define ACPI_WMI_EXPENSIVE 0x1 74b4f9fe12SLen Brown #define ACPI_WMI_METHOD 0x2 /* GUID is a method */ 75b4f9fe12SLen Brown #define ACPI_WMI_STRING 0x4 /* GUID takes & returns a string */ 76b4f9fe12SLen Brown #define ACPI_WMI_EVENT 0x8 /* GUID is an event */ 77b4f9fe12SLen Brown 7890ab5ee9SRusty Russell static bool debug_event; 79fc3155b2SThomas Renninger module_param(debug_event, bool, 0444); 80fc3155b2SThomas Renninger MODULE_PARM_DESC(debug_event, 81fc3155b2SThomas Renninger "Log WMI Events [0/1]"); 82fc3155b2SThomas Renninger 8390ab5ee9SRusty Russell static bool debug_dump_wdg; 84a929aae0SThomas Renninger module_param(debug_dump_wdg, bool, 0444); 85a929aae0SThomas Renninger MODULE_PARM_DESC(debug_dump_wdg, 86a929aae0SThomas Renninger "Dump available WMI interfaces [0/1]"); 87a929aae0SThomas Renninger 889599ed91SAndy Lutomirski static int acpi_wmi_remove(struct platform_device *device); 899599ed91SAndy Lutomirski static int acpi_wmi_probe(struct platform_device *device); 90b4f9fe12SLen Brown 91b4f9fe12SLen Brown static const struct acpi_device_id wmi_device_ids[] = { 92b4f9fe12SLen Brown {"PNP0C14", 0}, 93b4f9fe12SLen Brown {"pnp0c14", 0}, 94b4f9fe12SLen Brown {"", 0}, 95b4f9fe12SLen Brown }; 96b4f9fe12SLen Brown MODULE_DEVICE_TABLE(acpi, wmi_device_ids); 97b4f9fe12SLen Brown 989599ed91SAndy Lutomirski static struct platform_driver acpi_wmi_driver = { 999599ed91SAndy Lutomirski .driver = { 100844af950SAndy Lutomirski .name = "acpi-wmi", 1019599ed91SAndy Lutomirski .acpi_match_table = wmi_device_ids, 102b4f9fe12SLen Brown }, 1039599ed91SAndy Lutomirski .probe = acpi_wmi_probe, 1049599ed91SAndy Lutomirski .remove = acpi_wmi_remove, 105b4f9fe12SLen Brown }; 106b4f9fe12SLen Brown 107b4f9fe12SLen Brown /* 108b4f9fe12SLen Brown * GUID parsing functions 109b4f9fe12SLen Brown */ 110b4f9fe12SLen Brown 111b4f9fe12SLen Brown static bool find_guid(const char *guid_string, struct wmi_block **out) 112b4f9fe12SLen Brown { 113f9dffc14SAndy Shevchenko guid_t guid_input; 114b4f9fe12SLen Brown struct wmi_block *wblock; 115b4f9fe12SLen Brown struct guid_block *block; 116b4f9fe12SLen Brown 117f9dffc14SAndy Shevchenko if (guid_parse(guid_string, &guid_input)) 118538d7eb8SAndy Shevchenko return false; 119b4f9fe12SLen Brown 120cedb3b2aSAndy Shevchenko list_for_each_entry(wblock, &wmi_block_list, list) { 121b4f9fe12SLen Brown block = &wblock->gblock; 122b4f9fe12SLen Brown 123538d7eb8SAndy Shevchenko if (memcmp(block->guid, &guid_input, 16) == 0) { 124b4f9fe12SLen Brown if (out) 125b4f9fe12SLen Brown *out = wblock; 126097c27fcSJoe Perches return true; 127b4f9fe12SLen Brown } 128b4f9fe12SLen Brown } 129097c27fcSJoe Perches return false; 130b4f9fe12SLen Brown } 131b4f9fe12SLen Brown 132a48e2338SMattias Jacobsson static const void *find_guid_context(struct wmi_block *wblock, 133a48e2338SMattias Jacobsson struct wmi_driver *wdriver) 134a48e2338SMattias Jacobsson { 135a48e2338SMattias Jacobsson const struct wmi_device_id *id; 136f9dffc14SAndy Shevchenko guid_t guid_input; 137a48e2338SMattias Jacobsson 138a48e2338SMattias Jacobsson if (wblock == NULL || wdriver == NULL) 139a48e2338SMattias Jacobsson return NULL; 140a48e2338SMattias Jacobsson if (wdriver->id_table == NULL) 141a48e2338SMattias Jacobsson return NULL; 142a48e2338SMattias Jacobsson 143a48e2338SMattias Jacobsson id = wdriver->id_table; 144a48e2338SMattias Jacobsson while (*id->guid_string) { 145f9dffc14SAndy Shevchenko if (guid_parse(id->guid_string, &guid_input)) 146a48e2338SMattias Jacobsson continue; 147a48e2338SMattias Jacobsson if (!memcmp(wblock->gblock.guid, &guid_input, 16)) 148a48e2338SMattias Jacobsson return id->context; 149a48e2338SMattias Jacobsson id++; 150a48e2338SMattias Jacobsson } 151a48e2338SMattias Jacobsson return NULL; 152a48e2338SMattias Jacobsson } 153a48e2338SMattias Jacobsson 154d4fc91adSAndy Lutomirski static int get_subobj_info(acpi_handle handle, const char *pathname, 155d4fc91adSAndy Lutomirski struct acpi_device_info **info) 156d4fc91adSAndy Lutomirski { 157d4fc91adSAndy Lutomirski struct acpi_device_info *dummy_info, **info_ptr; 158d4fc91adSAndy Lutomirski acpi_handle subobj_handle; 159d4fc91adSAndy Lutomirski acpi_status status; 160d4fc91adSAndy Lutomirski 161d4fc91adSAndy Lutomirski status = acpi_get_handle(handle, (char *)pathname, &subobj_handle); 162d4fc91adSAndy Lutomirski if (status == AE_NOT_FOUND) 163d4fc91adSAndy Lutomirski return -ENOENT; 164d4fc91adSAndy Lutomirski else if (ACPI_FAILURE(status)) 165d4fc91adSAndy Lutomirski return -EIO; 166d4fc91adSAndy Lutomirski 167d4fc91adSAndy Lutomirski info_ptr = info ? info : &dummy_info; 168d4fc91adSAndy Lutomirski status = acpi_get_object_info(subobj_handle, info_ptr); 169d4fc91adSAndy Lutomirski if (ACPI_FAILURE(status)) 170d4fc91adSAndy Lutomirski return -EIO; 171d4fc91adSAndy Lutomirski 172d4fc91adSAndy Lutomirski if (!info) 173d4fc91adSAndy Lutomirski kfree(dummy_info); 174d4fc91adSAndy Lutomirski 175d4fc91adSAndy Lutomirski return 0; 176d4fc91adSAndy Lutomirski } 177d4fc91adSAndy Lutomirski 178b4f9fe12SLen Brown static acpi_status wmi_method_enable(struct wmi_block *wblock, int enable) 179b4f9fe12SLen Brown { 180b4f9fe12SLen Brown struct guid_block *block = NULL; 181b4f9fe12SLen Brown char method[5]; 182b4f9fe12SLen Brown acpi_status status; 183b4f9fe12SLen Brown acpi_handle handle; 184b4f9fe12SLen Brown 185b4f9fe12SLen Brown block = &wblock->gblock; 186b0e86302SAndy Lutomirski handle = wblock->acpi_device->handle; 187b4f9fe12SLen Brown 188b4f9fe12SLen Brown snprintf(method, 5, "WE%02X", block->notify_id); 1898122ab66SZhang Rui status = acpi_execute_simple_method(handle, method, enable); 190b4f9fe12SLen Brown 191b4f9fe12SLen Brown if (status != AE_OK && status != AE_NOT_FOUND) 192b4f9fe12SLen Brown return status; 193b4f9fe12SLen Brown else 194b4f9fe12SLen Brown return AE_OK; 195b4f9fe12SLen Brown } 196b4f9fe12SLen Brown 197b4f9fe12SLen Brown /* 198b4f9fe12SLen Brown * Exported WMI functions 199b4f9fe12SLen Brown */ 20044b6b766SMario Limonciello 20144b6b766SMario Limonciello /** 20244b6b766SMario Limonciello * set_required_buffer_size - Sets the buffer size needed for performing IOCTL 20344b6b766SMario Limonciello * @wdev: A wmi bus device from a driver 2045a707af1SAndy Shevchenko * @length: Required buffer size 20544b6b766SMario Limonciello * 20644b6b766SMario Limonciello * Allocates memory needed for buffer, stores the buffer size in that memory 20744b6b766SMario Limonciello */ 20844b6b766SMario Limonciello int set_required_buffer_size(struct wmi_device *wdev, u64 length) 20944b6b766SMario Limonciello { 21044b6b766SMario Limonciello struct wmi_block *wblock; 21144b6b766SMario Limonciello 21244b6b766SMario Limonciello wblock = container_of(wdev, struct wmi_block, dev); 21344b6b766SMario Limonciello wblock->req_buf_size = length; 21444b6b766SMario Limonciello 21544b6b766SMario Limonciello return 0; 21644b6b766SMario Limonciello } 21744b6b766SMario Limonciello EXPORT_SYMBOL_GPL(set_required_buffer_size); 21844b6b766SMario Limonciello 219b4f9fe12SLen Brown /** 220b4f9fe12SLen Brown * wmi_evaluate_method - Evaluate a WMI method 221b4f9fe12SLen Brown * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba 222b4f9fe12SLen Brown * @instance: Instance index 223b4f9fe12SLen Brown * @method_id: Method ID to call 2245a707af1SAndy Shevchenko * @in: Buffer containing input for the method call 2255a707af1SAndy Shevchenko * @out: Empty buffer to return the method results 226b4f9fe12SLen Brown * 227b4f9fe12SLen Brown * Call an ACPI-WMI method 228b4f9fe12SLen Brown */ 229b4f9fe12SLen Brown acpi_status wmi_evaluate_method(const char *guid_string, u8 instance, 230b4f9fe12SLen Brown u32 method_id, const struct acpi_buffer *in, struct acpi_buffer *out) 231b4f9fe12SLen Brown { 232722c856dSMario Limonciello struct wmi_block *wblock = NULL; 233722c856dSMario Limonciello 234722c856dSMario Limonciello if (!find_guid(guid_string, &wblock)) 235722c856dSMario Limonciello return AE_ERROR; 236722c856dSMario Limonciello return wmidev_evaluate_method(&wblock->dev, instance, method_id, 237722c856dSMario Limonciello in, out); 238722c856dSMario Limonciello } 239722c856dSMario Limonciello EXPORT_SYMBOL_GPL(wmi_evaluate_method); 240722c856dSMario Limonciello 241722c856dSMario Limonciello /** 242722c856dSMario Limonciello * wmidev_evaluate_method - Evaluate a WMI method 243722c856dSMario Limonciello * @wdev: A wmi bus device from a driver 244722c856dSMario Limonciello * @instance: Instance index 245722c856dSMario Limonciello * @method_id: Method ID to call 2465a707af1SAndy Shevchenko * @in: Buffer containing input for the method call 2475a707af1SAndy Shevchenko * @out: Empty buffer to return the method results 248722c856dSMario Limonciello * 249722c856dSMario Limonciello * Call an ACPI-WMI method 250722c856dSMario Limonciello */ 251722c856dSMario Limonciello acpi_status wmidev_evaluate_method(struct wmi_device *wdev, u8 instance, 252722c856dSMario Limonciello u32 method_id, const struct acpi_buffer *in, struct acpi_buffer *out) 253722c856dSMario Limonciello { 254b4f9fe12SLen Brown struct guid_block *block = NULL; 255b4f9fe12SLen Brown struct wmi_block *wblock = NULL; 256b4f9fe12SLen Brown acpi_handle handle; 257b4f9fe12SLen Brown acpi_status status; 258b4f9fe12SLen Brown struct acpi_object_list input; 259b4f9fe12SLen Brown union acpi_object params[3]; 260f3d83e24SCostantino Leandro char method[5] = "WM"; 261b4f9fe12SLen Brown 262722c856dSMario Limonciello wblock = container_of(wdev, struct wmi_block, dev); 263b4f9fe12SLen Brown block = &wblock->gblock; 264b0e86302SAndy Lutomirski handle = wblock->acpi_device->handle; 265b4f9fe12SLen Brown 266b4f9fe12SLen Brown if (!(block->flags & ACPI_WMI_METHOD)) 267b4f9fe12SLen Brown return AE_BAD_DATA; 268b4f9fe12SLen Brown 2696afa1e2aSPali Rohár if (block->instance_count <= instance) 270b4f9fe12SLen Brown return AE_BAD_PARAMETER; 271b4f9fe12SLen Brown 272b4f9fe12SLen Brown input.count = 2; 273b4f9fe12SLen Brown input.pointer = params; 274b4f9fe12SLen Brown params[0].type = ACPI_TYPE_INTEGER; 275b4f9fe12SLen Brown params[0].integer.value = instance; 276b4f9fe12SLen Brown params[1].type = ACPI_TYPE_INTEGER; 277b4f9fe12SLen Brown params[1].integer.value = method_id; 278b4f9fe12SLen Brown 279b4f9fe12SLen Brown if (in) { 280b4f9fe12SLen Brown input.count = 3; 281b4f9fe12SLen Brown 282b4f9fe12SLen Brown if (block->flags & ACPI_WMI_STRING) { 283b4f9fe12SLen Brown params[2].type = ACPI_TYPE_STRING; 284b4f9fe12SLen Brown } else { 285b4f9fe12SLen Brown params[2].type = ACPI_TYPE_BUFFER; 286b4f9fe12SLen Brown } 287b4f9fe12SLen Brown params[2].buffer.length = in->length; 288b4f9fe12SLen Brown params[2].buffer.pointer = in->pointer; 289b4f9fe12SLen Brown } 290b4f9fe12SLen Brown 291b4f9fe12SLen Brown strncat(method, block->object_id, 2); 292b4f9fe12SLen Brown 293b4f9fe12SLen Brown status = acpi_evaluate_object(handle, method, &input, out); 294b4f9fe12SLen Brown 295b4f9fe12SLen Brown return status; 296b4f9fe12SLen Brown } 297722c856dSMario Limonciello EXPORT_SYMBOL_GPL(wmidev_evaluate_method); 298b4f9fe12SLen Brown 29956a37025SAndy Lutomirski static acpi_status __query_block(struct wmi_block *wblock, u8 instance, 300b4f9fe12SLen Brown struct acpi_buffer *out) 301b4f9fe12SLen Brown { 302b4f9fe12SLen Brown struct guid_block *block = NULL; 30354f14c27SZhang Rui acpi_handle handle; 304b4f9fe12SLen Brown acpi_status status, wc_status = AE_ERROR; 3058122ab66SZhang Rui struct acpi_object_list input; 3068122ab66SZhang Rui union acpi_object wq_params[1]; 307f3d83e24SCostantino Leandro char method[5]; 308f3d83e24SCostantino Leandro char wc_method[5] = "WC"; 309b4f9fe12SLen Brown 31056a37025SAndy Lutomirski if (!out) 311b4f9fe12SLen Brown return AE_BAD_PARAMETER; 312b4f9fe12SLen Brown 313b4f9fe12SLen Brown block = &wblock->gblock; 314b0e86302SAndy Lutomirski handle = wblock->acpi_device->handle; 315b4f9fe12SLen Brown 3166afa1e2aSPali Rohár if (block->instance_count <= instance) 317b4f9fe12SLen Brown return AE_BAD_PARAMETER; 318b4f9fe12SLen Brown 319b4f9fe12SLen Brown /* Check GUID is a data block */ 320b4f9fe12SLen Brown if (block->flags & (ACPI_WMI_EVENT | ACPI_WMI_METHOD)) 321b4f9fe12SLen Brown return AE_ERROR; 322b4f9fe12SLen Brown 323b4f9fe12SLen Brown input.count = 1; 324b4f9fe12SLen Brown input.pointer = wq_params; 325b4f9fe12SLen Brown wq_params[0].type = ACPI_TYPE_INTEGER; 326b4f9fe12SLen Brown wq_params[0].integer.value = instance; 327b4f9fe12SLen Brown 328d4fc91adSAndy Lutomirski if (instance == 0 && wblock->read_takes_no_args) 329d4fc91adSAndy Lutomirski input.count = 0; 330d4fc91adSAndy Lutomirski 331b4f9fe12SLen Brown /* 332b4f9fe12SLen Brown * If ACPI_WMI_EXPENSIVE, call the relevant WCxx method first to 333b4f9fe12SLen Brown * enable collection. 334b4f9fe12SLen Brown */ 335b4f9fe12SLen Brown if (block->flags & ACPI_WMI_EXPENSIVE) { 336b4f9fe12SLen Brown strncat(wc_method, block->object_id, 2); 337b4f9fe12SLen Brown 338b4f9fe12SLen Brown /* 339b4f9fe12SLen Brown * Some GUIDs break the specification by declaring themselves 340b4f9fe12SLen Brown * expensive, but have no corresponding WCxx method. So we 341b4f9fe12SLen Brown * should not fail if this happens. 342b4f9fe12SLen Brown */ 343bad9da86SKelsey Skunberg wc_status = acpi_execute_simple_method(handle, wc_method, 1); 344b4f9fe12SLen Brown } 345b4f9fe12SLen Brown 346b4f9fe12SLen Brown strcpy(method, "WQ"); 347b4f9fe12SLen Brown strncat(method, block->object_id, 2); 348b4f9fe12SLen Brown 349b4f9fe12SLen Brown status = acpi_evaluate_object(handle, method, &input, out); 350b4f9fe12SLen Brown 351b4f9fe12SLen Brown /* 352b4f9fe12SLen Brown * If ACPI_WMI_EXPENSIVE, call the relevant WCxx method, even if 353b4f9fe12SLen Brown * the WQxx method failed - we should disable collection anyway. 354b4f9fe12SLen Brown */ 355b4f9fe12SLen Brown if ((block->flags & ACPI_WMI_EXPENSIVE) && ACPI_SUCCESS(wc_status)) { 3568122ab66SZhang Rui status = acpi_execute_simple_method(handle, wc_method, 0); 357b4f9fe12SLen Brown } 358b4f9fe12SLen Brown 359b4f9fe12SLen Brown return status; 360b4f9fe12SLen Brown } 36156a37025SAndy Lutomirski 36256a37025SAndy Lutomirski /** 36356a37025SAndy Lutomirski * wmi_query_block - Return contents of a WMI block (deprecated) 36456a37025SAndy Lutomirski * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba 36556a37025SAndy Lutomirski * @instance: Instance index 3665a707af1SAndy Shevchenko * @out: Empty buffer to return the contents of the data block to 36756a37025SAndy Lutomirski * 36856a37025SAndy Lutomirski * Return the contents of an ACPI-WMI data block to a buffer 36956a37025SAndy Lutomirski */ 37056a37025SAndy Lutomirski acpi_status wmi_query_block(const char *guid_string, u8 instance, 37156a37025SAndy Lutomirski struct acpi_buffer *out) 37256a37025SAndy Lutomirski { 37356a37025SAndy Lutomirski struct wmi_block *wblock; 37456a37025SAndy Lutomirski 37556a37025SAndy Lutomirski if (!guid_string) 37656a37025SAndy Lutomirski return AE_BAD_PARAMETER; 37756a37025SAndy Lutomirski 37856a37025SAndy Lutomirski if (!find_guid(guid_string, &wblock)) 37956a37025SAndy Lutomirski return AE_ERROR; 38056a37025SAndy Lutomirski 38156a37025SAndy Lutomirski return __query_block(wblock, instance, out); 38256a37025SAndy Lutomirski } 383b4f9fe12SLen Brown EXPORT_SYMBOL_GPL(wmi_query_block); 384b4f9fe12SLen Brown 38556a37025SAndy Lutomirski union acpi_object *wmidev_block_query(struct wmi_device *wdev, u8 instance) 38656a37025SAndy Lutomirski { 38756a37025SAndy Lutomirski struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL }; 38856a37025SAndy Lutomirski struct wmi_block *wblock = container_of(wdev, struct wmi_block, dev); 38956a37025SAndy Lutomirski 39056a37025SAndy Lutomirski if (ACPI_FAILURE(__query_block(wblock, instance, &out))) 39156a37025SAndy Lutomirski return NULL; 39256a37025SAndy Lutomirski 39356a37025SAndy Lutomirski return (union acpi_object *)out.pointer; 39456a37025SAndy Lutomirski } 39556a37025SAndy Lutomirski EXPORT_SYMBOL_GPL(wmidev_block_query); 39656a37025SAndy Lutomirski 397b4f9fe12SLen Brown /** 398b4f9fe12SLen Brown * wmi_set_block - Write to a WMI block 399b4f9fe12SLen Brown * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba 400b4f9fe12SLen Brown * @instance: Instance index 4015a707af1SAndy Shevchenko * @in: Buffer containing new values for the data block 402b4f9fe12SLen Brown * 403b4f9fe12SLen Brown * Write the contents of the input buffer to an ACPI-WMI data block 404b4f9fe12SLen Brown */ 405b4f9fe12SLen Brown acpi_status wmi_set_block(const char *guid_string, u8 instance, 406b4f9fe12SLen Brown const struct acpi_buffer *in) 407b4f9fe12SLen Brown { 408b4f9fe12SLen Brown struct guid_block *block = NULL; 409b4f9fe12SLen Brown struct wmi_block *wblock = NULL; 410b4f9fe12SLen Brown acpi_handle handle; 411b4f9fe12SLen Brown struct acpi_object_list input; 412b4f9fe12SLen Brown union acpi_object params[2]; 413f3d83e24SCostantino Leandro char method[5] = "WS"; 414b4f9fe12SLen Brown 415b4f9fe12SLen Brown if (!guid_string || !in) 416b4f9fe12SLen Brown return AE_BAD_DATA; 417b4f9fe12SLen Brown 418b4f9fe12SLen Brown if (!find_guid(guid_string, &wblock)) 419b4f9fe12SLen Brown return AE_ERROR; 420b4f9fe12SLen Brown 421b4f9fe12SLen Brown block = &wblock->gblock; 422b0e86302SAndy Lutomirski handle = wblock->acpi_device->handle; 423b4f9fe12SLen Brown 4246afa1e2aSPali Rohár if (block->instance_count <= instance) 425b4f9fe12SLen Brown return AE_BAD_PARAMETER; 426b4f9fe12SLen Brown 427b4f9fe12SLen Brown /* Check GUID is a data block */ 428b4f9fe12SLen Brown if (block->flags & (ACPI_WMI_EVENT | ACPI_WMI_METHOD)) 429b4f9fe12SLen Brown return AE_ERROR; 430b4f9fe12SLen Brown 431b4f9fe12SLen Brown input.count = 2; 432b4f9fe12SLen Brown input.pointer = params; 433b4f9fe12SLen Brown params[0].type = ACPI_TYPE_INTEGER; 434b4f9fe12SLen Brown params[0].integer.value = instance; 435b4f9fe12SLen Brown 436b4f9fe12SLen Brown if (block->flags & ACPI_WMI_STRING) { 437b4f9fe12SLen Brown params[1].type = ACPI_TYPE_STRING; 438b4f9fe12SLen Brown } else { 439b4f9fe12SLen Brown params[1].type = ACPI_TYPE_BUFFER; 440b4f9fe12SLen Brown } 441b4f9fe12SLen Brown params[1].buffer.length = in->length; 442b4f9fe12SLen Brown params[1].buffer.pointer = in->pointer; 443b4f9fe12SLen Brown 444b4f9fe12SLen Brown strncat(method, block->object_id, 2); 445b4f9fe12SLen Brown 446b4f9fe12SLen Brown return acpi_evaluate_object(handle, method, &input, NULL); 447b4f9fe12SLen Brown } 448b4f9fe12SLen Brown EXPORT_SYMBOL_GPL(wmi_set_block); 449b4f9fe12SLen Brown 45037830662SDmitry Torokhov static void wmi_dump_wdg(const struct guid_block *g) 451a929aae0SThomas Renninger { 45285b4e4ebSRasmus Villemoes pr_info("%pUL:\n", g->guid); 453cd3921f8SPali Rohár if (g->flags & ACPI_WMI_EVENT) 454cd3921f8SPali Rohár pr_info("\tnotify_id: 0x%02X\n", g->notify_id); 455cd3921f8SPali Rohár else 456cd3921f8SPali Rohár pr_info("\tobject_id: %2pE\n", g->object_id); 4578e07514dSDmitry Torokhov pr_info("\tinstance_count: %d\n", g->instance_count); 4588e07514dSDmitry Torokhov pr_info("\tflags: %#x", g->flags); 459a929aae0SThomas Renninger if (g->flags) { 460a929aae0SThomas Renninger if (g->flags & ACPI_WMI_EXPENSIVE) 4618e07514dSDmitry Torokhov pr_cont(" ACPI_WMI_EXPENSIVE"); 462a929aae0SThomas Renninger if (g->flags & ACPI_WMI_METHOD) 4638e07514dSDmitry Torokhov pr_cont(" ACPI_WMI_METHOD"); 464a929aae0SThomas Renninger if (g->flags & ACPI_WMI_STRING) 4658e07514dSDmitry Torokhov pr_cont(" ACPI_WMI_STRING"); 466a929aae0SThomas Renninger if (g->flags & ACPI_WMI_EVENT) 4678e07514dSDmitry Torokhov pr_cont(" ACPI_WMI_EVENT"); 468a929aae0SThomas Renninger } 4698e07514dSDmitry Torokhov pr_cont("\n"); 470a929aae0SThomas Renninger 471a929aae0SThomas Renninger } 472a929aae0SThomas Renninger 473fc3155b2SThomas Renninger static void wmi_notify_debug(u32 value, void *context) 474fc3155b2SThomas Renninger { 475fc3155b2SThomas Renninger struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL }; 476fc3155b2SThomas Renninger union acpi_object *obj; 4771492616aSAxel Lin acpi_status status; 478fc3155b2SThomas Renninger 4791492616aSAxel Lin status = wmi_get_event_data(value, &response); 4801492616aSAxel Lin if (status != AE_OK) { 4818e07514dSDmitry Torokhov pr_info("bad event status 0x%x\n", status); 4821492616aSAxel Lin return; 4831492616aSAxel Lin } 484fc3155b2SThomas Renninger 485fc3155b2SThomas Renninger obj = (union acpi_object *)response.pointer; 486fc3155b2SThomas Renninger 487fc3155b2SThomas Renninger if (!obj) 488fc3155b2SThomas Renninger return; 489fc3155b2SThomas Renninger 4908e07514dSDmitry Torokhov pr_info("DEBUG Event "); 491fc3155b2SThomas Renninger switch(obj->type) { 492fc3155b2SThomas Renninger case ACPI_TYPE_BUFFER: 4938e07514dSDmitry Torokhov pr_cont("BUFFER_TYPE - length %d\n", obj->buffer.length); 494fc3155b2SThomas Renninger break; 495fc3155b2SThomas Renninger case ACPI_TYPE_STRING: 4968e07514dSDmitry Torokhov pr_cont("STRING_TYPE - %s\n", obj->string.pointer); 497fc3155b2SThomas Renninger break; 498fc3155b2SThomas Renninger case ACPI_TYPE_INTEGER: 4998e07514dSDmitry Torokhov pr_cont("INTEGER_TYPE - %llu\n", obj->integer.value); 500fc3155b2SThomas Renninger break; 501fc3155b2SThomas Renninger case ACPI_TYPE_PACKAGE: 5028e07514dSDmitry Torokhov pr_cont("PACKAGE_TYPE - %d elements\n", obj->package.count); 503fc3155b2SThomas Renninger break; 504fc3155b2SThomas Renninger default: 5058e07514dSDmitry Torokhov pr_cont("object type 0x%X\n", obj->type); 506fc3155b2SThomas Renninger } 5071492616aSAxel Lin kfree(obj); 508fc3155b2SThomas Renninger } 509fc3155b2SThomas Renninger 510b4f9fe12SLen Brown /** 511b4f9fe12SLen Brown * wmi_install_notify_handler - Register handler for WMI events 5125a707af1SAndy Shevchenko * @guid: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba 513b4f9fe12SLen Brown * @handler: Function to handle notifications 514b4f9fe12SLen Brown * @data: Data to be returned to handler when event is fired 515b4f9fe12SLen Brown * 516b4f9fe12SLen Brown * Register a handler for events sent to the ACPI-WMI mapper device. 517b4f9fe12SLen Brown */ 518b4f9fe12SLen Brown acpi_status wmi_install_notify_handler(const char *guid, 519b4f9fe12SLen Brown wmi_notify_handler handler, void *data) 520b4f9fe12SLen Brown { 521b4f9fe12SLen Brown struct wmi_block *block; 52258f6425eSColin King acpi_status status = AE_NOT_EXIST; 523f9dffc14SAndy Shevchenko guid_t guid_input; 524b4f9fe12SLen Brown 525b4f9fe12SLen Brown if (!guid || !handler) 526b4f9fe12SLen Brown return AE_BAD_PARAMETER; 527b4f9fe12SLen Brown 528f9dffc14SAndy Shevchenko if (guid_parse(guid, &guid_input)) 529538d7eb8SAndy Shevchenko return AE_BAD_PARAMETER; 530b4f9fe12SLen Brown 531cedb3b2aSAndy Shevchenko list_for_each_entry(block, &wmi_block_list, list) { 53258f6425eSColin King acpi_status wmi_status; 53358f6425eSColin King 534538d7eb8SAndy Shevchenko if (memcmp(block->gblock.guid, &guid_input, 16) == 0) { 53558f6425eSColin King if (block->handler && 53658f6425eSColin King block->handler != wmi_notify_debug) 537b4f9fe12SLen Brown return AE_ALREADY_ACQUIRED; 538b4f9fe12SLen Brown 539b4f9fe12SLen Brown block->handler = handler; 540b4f9fe12SLen Brown block->handler_data = data; 541b4f9fe12SLen Brown 54258f6425eSColin King wmi_status = wmi_method_enable(block, 1); 54358f6425eSColin King if ((wmi_status != AE_OK) || 54458f6425eSColin King ((wmi_status == AE_OK) && (status == AE_NOT_EXIST))) 54558f6425eSColin King status = wmi_status; 54658f6425eSColin King } 54758f6425eSColin King } 548b4f9fe12SLen Brown 549b4f9fe12SLen Brown return status; 550b4f9fe12SLen Brown } 551b4f9fe12SLen Brown EXPORT_SYMBOL_GPL(wmi_install_notify_handler); 552b4f9fe12SLen Brown 553b4f9fe12SLen Brown /** 554b4f9fe12SLen Brown * wmi_uninstall_notify_handler - Unregister handler for WMI events 5555a707af1SAndy Shevchenko * @guid: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba 556b4f9fe12SLen Brown * 557b4f9fe12SLen Brown * Unregister handler for events sent to the ACPI-WMI mapper device. 558b4f9fe12SLen Brown */ 559b4f9fe12SLen Brown acpi_status wmi_remove_notify_handler(const char *guid) 560b4f9fe12SLen Brown { 561b4f9fe12SLen Brown struct wmi_block *block; 56258f6425eSColin King acpi_status status = AE_NOT_EXIST; 563f9dffc14SAndy Shevchenko guid_t guid_input; 564b4f9fe12SLen Brown 565b4f9fe12SLen Brown if (!guid) 566b4f9fe12SLen Brown return AE_BAD_PARAMETER; 567b4f9fe12SLen Brown 568f9dffc14SAndy Shevchenko if (guid_parse(guid, &guid_input)) 569538d7eb8SAndy Shevchenko return AE_BAD_PARAMETER; 570b4f9fe12SLen Brown 571cedb3b2aSAndy Shevchenko list_for_each_entry(block, &wmi_block_list, list) { 57258f6425eSColin King acpi_status wmi_status; 57358f6425eSColin King 574538d7eb8SAndy Shevchenko if (memcmp(block->gblock.guid, &guid_input, 16) == 0) { 57558f6425eSColin King if (!block->handler || 57658f6425eSColin King block->handler == wmi_notify_debug) 577b4f9fe12SLen Brown return AE_NULL_ENTRY; 578b4f9fe12SLen Brown 579fc3155b2SThomas Renninger if (debug_event) { 580fc3155b2SThomas Renninger block->handler = wmi_notify_debug; 58158f6425eSColin King status = AE_OK; 582fc3155b2SThomas Renninger } else { 58358f6425eSColin King wmi_status = wmi_method_enable(block, 0); 584b4f9fe12SLen Brown block->handler = NULL; 585b4f9fe12SLen Brown block->handler_data = NULL; 58658f6425eSColin King if ((wmi_status != AE_OK) || 58758f6425eSColin King ((wmi_status == AE_OK) && 58858f6425eSColin King (status == AE_NOT_EXIST))) 58958f6425eSColin King status = wmi_status; 590fc3155b2SThomas Renninger } 59158f6425eSColin King } 59258f6425eSColin King } 59358f6425eSColin King 594b4f9fe12SLen Brown return status; 595b4f9fe12SLen Brown } 596b4f9fe12SLen Brown EXPORT_SYMBOL_GPL(wmi_remove_notify_handler); 597b4f9fe12SLen Brown 598b4f9fe12SLen Brown /** 599b4f9fe12SLen Brown * wmi_get_event_data - Get WMI data associated with an event 600b4f9fe12SLen Brown * 6013e9b988eSAnisse Astier * @event: Event to find 6023e9b988eSAnisse Astier * @out: Buffer to hold event data. out->pointer should be freed with kfree() 603b4f9fe12SLen Brown * 604b4f9fe12SLen Brown * Returns extra data associated with an event in WMI. 605b4f9fe12SLen Brown */ 606b4f9fe12SLen Brown acpi_status wmi_get_event_data(u32 event, struct acpi_buffer *out) 607b4f9fe12SLen Brown { 608b4f9fe12SLen Brown struct acpi_object_list input; 609b4f9fe12SLen Brown union acpi_object params[1]; 610b4f9fe12SLen Brown struct guid_block *gblock; 611b4f9fe12SLen Brown struct wmi_block *wblock; 612b4f9fe12SLen Brown 613b4f9fe12SLen Brown input.count = 1; 614b4f9fe12SLen Brown input.pointer = params; 615b4f9fe12SLen Brown params[0].type = ACPI_TYPE_INTEGER; 616b4f9fe12SLen Brown params[0].integer.value = event; 617b4f9fe12SLen Brown 618cedb3b2aSAndy Shevchenko list_for_each_entry(wblock, &wmi_block_list, list) { 619b4f9fe12SLen Brown gblock = &wblock->gblock; 620b4f9fe12SLen Brown 621b4f9fe12SLen Brown if ((gblock->flags & ACPI_WMI_EVENT) && 622b4f9fe12SLen Brown (gblock->notify_id == event)) 623b0e86302SAndy Lutomirski return acpi_evaluate_object(wblock->acpi_device->handle, 624b0e86302SAndy Lutomirski "_WED", &input, out); 625b4f9fe12SLen Brown } 626b4f9fe12SLen Brown 627b4f9fe12SLen Brown return AE_NOT_FOUND; 628b4f9fe12SLen Brown } 629b4f9fe12SLen Brown EXPORT_SYMBOL_GPL(wmi_get_event_data); 630b4f9fe12SLen Brown 631b4f9fe12SLen Brown /** 632b4f9fe12SLen Brown * wmi_has_guid - Check if a GUID is available 633b4f9fe12SLen Brown * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba 634b4f9fe12SLen Brown * 635b4f9fe12SLen Brown * Check if a given GUID is defined by _WDG 636b4f9fe12SLen Brown */ 637b4f9fe12SLen Brown bool wmi_has_guid(const char *guid_string) 638b4f9fe12SLen Brown { 639b4f9fe12SLen Brown return find_guid(guid_string, NULL); 640b4f9fe12SLen Brown } 641b4f9fe12SLen Brown EXPORT_SYMBOL_GPL(wmi_has_guid); 642b4f9fe12SLen Brown 643e7488e58SYurii Pavlovskyi /** 644e7488e58SYurii Pavlovskyi * wmi_get_acpi_device_uid() - Get _UID name of ACPI device that defines GUID 645e7488e58SYurii Pavlovskyi * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba 646e7488e58SYurii Pavlovskyi * 647e7488e58SYurii Pavlovskyi * Find the _UID of ACPI device associated with this WMI GUID. 648e7488e58SYurii Pavlovskyi * 649e7488e58SYurii Pavlovskyi * Return: The ACPI _UID field value or NULL if the WMI GUID was not found 650e7488e58SYurii Pavlovskyi */ 651e7488e58SYurii Pavlovskyi char *wmi_get_acpi_device_uid(const char *guid_string) 652e7488e58SYurii Pavlovskyi { 653e7488e58SYurii Pavlovskyi struct wmi_block *wblock = NULL; 654e7488e58SYurii Pavlovskyi 655e7488e58SYurii Pavlovskyi if (!find_guid(guid_string, &wblock)) 656e7488e58SYurii Pavlovskyi return NULL; 657e7488e58SYurii Pavlovskyi 658e7488e58SYurii Pavlovskyi return acpi_device_uid(wblock->acpi_device); 659e7488e58SYurii Pavlovskyi } 660e7488e58SYurii Pavlovskyi EXPORT_SYMBOL_GPL(wmi_get_acpi_device_uid); 661e7488e58SYurii Pavlovskyi 662844af950SAndy Lutomirski static struct wmi_block *dev_to_wblock(struct device *dev) 663844af950SAndy Lutomirski { 664844af950SAndy Lutomirski return container_of(dev, struct wmi_block, dev.dev); 665844af950SAndy Lutomirski } 666844af950SAndy Lutomirski 667844af950SAndy Lutomirski static struct wmi_device *dev_to_wdev(struct device *dev) 668844af950SAndy Lutomirski { 669844af950SAndy Lutomirski return container_of(dev, struct wmi_device, dev); 670844af950SAndy Lutomirski } 671844af950SAndy Lutomirski 672b4f9fe12SLen Brown /* 6731caab3c1SMatthew Garrett * sysfs interface 6741caab3c1SMatthew Garrett */ 675614ef432SDmitry Torokhov static ssize_t modalias_show(struct device *dev, struct device_attribute *attr, 6761caab3c1SMatthew Garrett char *buf) 6771caab3c1SMatthew Garrett { 678844af950SAndy Lutomirski struct wmi_block *wblock = dev_to_wblock(dev); 6791caab3c1SMatthew Garrett 68085b4e4ebSRasmus Villemoes return sprintf(buf, "wmi:%pUL\n", wblock->gblock.guid); 6811caab3c1SMatthew Garrett } 682e80b89a5SGreg Kroah-Hartman static DEVICE_ATTR_RO(modalias); 683614ef432SDmitry Torokhov 684844af950SAndy Lutomirski static ssize_t guid_show(struct device *dev, struct device_attribute *attr, 685844af950SAndy Lutomirski char *buf) 686844af950SAndy Lutomirski { 687844af950SAndy Lutomirski struct wmi_block *wblock = dev_to_wblock(dev); 688844af950SAndy Lutomirski 689844af950SAndy Lutomirski return sprintf(buf, "%pUL\n", wblock->gblock.guid); 690844af950SAndy Lutomirski } 691844af950SAndy Lutomirski static DEVICE_ATTR_RO(guid); 692844af950SAndy Lutomirski 693d79b1074SAndy Lutomirski static ssize_t instance_count_show(struct device *dev, 694d79b1074SAndy Lutomirski struct device_attribute *attr, char *buf) 695d79b1074SAndy Lutomirski { 696d79b1074SAndy Lutomirski struct wmi_block *wblock = dev_to_wblock(dev); 697d79b1074SAndy Lutomirski 698d79b1074SAndy Lutomirski return sprintf(buf, "%d\n", (int)wblock->gblock.instance_count); 699d79b1074SAndy Lutomirski } 700d79b1074SAndy Lutomirski static DEVICE_ATTR_RO(instance_count); 701d79b1074SAndy Lutomirski 702d79b1074SAndy Lutomirski static ssize_t expensive_show(struct device *dev, 703d79b1074SAndy Lutomirski struct device_attribute *attr, char *buf) 704d79b1074SAndy Lutomirski { 705d79b1074SAndy Lutomirski struct wmi_block *wblock = dev_to_wblock(dev); 706d79b1074SAndy Lutomirski 707d79b1074SAndy Lutomirski return sprintf(buf, "%d\n", 708d79b1074SAndy Lutomirski (wblock->gblock.flags & ACPI_WMI_EXPENSIVE) != 0); 709d79b1074SAndy Lutomirski } 710d79b1074SAndy Lutomirski static DEVICE_ATTR_RO(expensive); 711d79b1074SAndy Lutomirski 712e80b89a5SGreg Kroah-Hartman static struct attribute *wmi_attrs[] = { 713e80b89a5SGreg Kroah-Hartman &dev_attr_modalias.attr, 714844af950SAndy Lutomirski &dev_attr_guid.attr, 715d79b1074SAndy Lutomirski &dev_attr_instance_count.attr, 716d79b1074SAndy Lutomirski &dev_attr_expensive.attr, 717e80b89a5SGreg Kroah-Hartman NULL, 718614ef432SDmitry Torokhov }; 719e80b89a5SGreg Kroah-Hartman ATTRIBUTE_GROUPS(wmi); 7201caab3c1SMatthew Garrett 721d79b1074SAndy Lutomirski static ssize_t notify_id_show(struct device *dev, struct device_attribute *attr, 722d79b1074SAndy Lutomirski char *buf) 723d79b1074SAndy Lutomirski { 724d79b1074SAndy Lutomirski struct wmi_block *wblock = dev_to_wblock(dev); 725d79b1074SAndy Lutomirski 726d79b1074SAndy Lutomirski return sprintf(buf, "%02X\n", (unsigned int)wblock->gblock.notify_id); 727d79b1074SAndy Lutomirski } 728d79b1074SAndy Lutomirski static DEVICE_ATTR_RO(notify_id); 729d79b1074SAndy Lutomirski 730d79b1074SAndy Lutomirski static struct attribute *wmi_event_attrs[] = { 731d79b1074SAndy Lutomirski &dev_attr_notify_id.attr, 732d79b1074SAndy Lutomirski NULL, 733d79b1074SAndy Lutomirski }; 734d79b1074SAndy Lutomirski ATTRIBUTE_GROUPS(wmi_event); 735d79b1074SAndy Lutomirski 736d79b1074SAndy Lutomirski static ssize_t object_id_show(struct device *dev, struct device_attribute *attr, 737d79b1074SAndy Lutomirski char *buf) 738d79b1074SAndy Lutomirski { 739d79b1074SAndy Lutomirski struct wmi_block *wblock = dev_to_wblock(dev); 740d79b1074SAndy Lutomirski 741d79b1074SAndy Lutomirski return sprintf(buf, "%c%c\n", wblock->gblock.object_id[0], 742d79b1074SAndy Lutomirski wblock->gblock.object_id[1]); 743d79b1074SAndy Lutomirski } 744d79b1074SAndy Lutomirski static DEVICE_ATTR_RO(object_id); 745d79b1074SAndy Lutomirski 746fd70da6aSDarren Hart (VMware) static ssize_t setable_show(struct device *dev, struct device_attribute *attr, 747d4fc91adSAndy Lutomirski char *buf) 748d4fc91adSAndy Lutomirski { 749d4fc91adSAndy Lutomirski struct wmi_device *wdev = dev_to_wdev(dev); 750d4fc91adSAndy Lutomirski 751fd70da6aSDarren Hart (VMware) return sprintf(buf, "%d\n", (int)wdev->setable); 752d4fc91adSAndy Lutomirski } 753fd70da6aSDarren Hart (VMware) static DEVICE_ATTR_RO(setable); 754d4fc91adSAndy Lutomirski 755d4fc91adSAndy Lutomirski static struct attribute *wmi_data_attrs[] = { 756d4fc91adSAndy Lutomirski &dev_attr_object_id.attr, 757fd70da6aSDarren Hart (VMware) &dev_attr_setable.attr, 758d4fc91adSAndy Lutomirski NULL, 759d4fc91adSAndy Lutomirski }; 760d4fc91adSAndy Lutomirski ATTRIBUTE_GROUPS(wmi_data); 761d4fc91adSAndy Lutomirski 762d4fc91adSAndy Lutomirski static struct attribute *wmi_method_attrs[] = { 763d79b1074SAndy Lutomirski &dev_attr_object_id.attr, 764d79b1074SAndy Lutomirski NULL, 765d79b1074SAndy Lutomirski }; 766d4fc91adSAndy Lutomirski ATTRIBUTE_GROUPS(wmi_method); 767d79b1074SAndy Lutomirski 7681caab3c1SMatthew Garrett static int wmi_dev_uevent(struct device *dev, struct kobj_uevent_env *env) 7691caab3c1SMatthew Garrett { 770844af950SAndy Lutomirski struct wmi_block *wblock = dev_to_wblock(dev); 7711caab3c1SMatthew Garrett 772844af950SAndy Lutomirski if (add_uevent_var(env, "MODALIAS=wmi:%pUL", wblock->gblock.guid)) 7731caab3c1SMatthew Garrett return -ENOMEM; 7741caab3c1SMatthew Garrett 775844af950SAndy Lutomirski if (add_uevent_var(env, "WMI_GUID=%pUL", wblock->gblock.guid)) 7761caab3c1SMatthew Garrett return -ENOMEM; 7771caab3c1SMatthew Garrett 7781caab3c1SMatthew Garrett return 0; 7791caab3c1SMatthew Garrett } 7801caab3c1SMatthew Garrett 781844af950SAndy Lutomirski static void wmi_dev_release(struct device *dev) 7821caab3c1SMatthew Garrett { 783844af950SAndy Lutomirski struct wmi_block *wblock = dev_to_wblock(dev); 784c64eefd4SDmitry Torokhov 785844af950SAndy Lutomirski kfree(wblock); 7861caab3c1SMatthew Garrett } 7871caab3c1SMatthew Garrett 788844af950SAndy Lutomirski static int wmi_dev_match(struct device *dev, struct device_driver *driver) 789844af950SAndy Lutomirski { 790844af950SAndy Lutomirski struct wmi_driver *wmi_driver = 791844af950SAndy Lutomirski container_of(driver, struct wmi_driver, driver); 792844af950SAndy Lutomirski struct wmi_block *wblock = dev_to_wblock(dev); 793844af950SAndy Lutomirski const struct wmi_device_id *id = wmi_driver->id_table; 794844af950SAndy Lutomirski 795c355ec65SMattias Jacobsson if (id == NULL) 796c355ec65SMattias Jacobsson return 0; 797c355ec65SMattias Jacobsson 798eacc95eaSMattias Jacobsson while (*id->guid_string) { 799f9dffc14SAndy Shevchenko guid_t driver_guid; 800844af950SAndy Lutomirski 801f9dffc14SAndy Shevchenko if (WARN_ON(guid_parse(id->guid_string, &driver_guid))) 802844af950SAndy Lutomirski continue; 803844af950SAndy Lutomirski if (!memcmp(&driver_guid, wblock->gblock.guid, 16)) 804844af950SAndy Lutomirski return 1; 805844af950SAndy Lutomirski 806844af950SAndy Lutomirski id++; 807844af950SAndy Lutomirski } 808844af950SAndy Lutomirski 809844af950SAndy Lutomirski return 0; 810844af950SAndy Lutomirski } 81144b6b766SMario Limonciello static int wmi_char_open(struct inode *inode, struct file *filp) 81244b6b766SMario Limonciello { 81344b6b766SMario Limonciello const char *driver_name = filp->f_path.dentry->d_iname; 81444b6b766SMario Limonciello struct wmi_block *wblock = NULL; 81544b6b766SMario Limonciello struct wmi_block *next = NULL; 81644b6b766SMario Limonciello 81744b6b766SMario Limonciello list_for_each_entry_safe(wblock, next, &wmi_block_list, list) { 81844b6b766SMario Limonciello if (!wblock->dev.dev.driver) 81944b6b766SMario Limonciello continue; 82044b6b766SMario Limonciello if (strcmp(driver_name, wblock->dev.dev.driver->name) == 0) { 82144b6b766SMario Limonciello filp->private_data = wblock; 82244b6b766SMario Limonciello break; 82344b6b766SMario Limonciello } 82444b6b766SMario Limonciello } 82544b6b766SMario Limonciello 82644b6b766SMario Limonciello if (!filp->private_data) 82744b6b766SMario Limonciello return -ENODEV; 82844b6b766SMario Limonciello 82944b6b766SMario Limonciello return nonseekable_open(inode, filp); 83044b6b766SMario Limonciello } 83144b6b766SMario Limonciello 83244b6b766SMario Limonciello static ssize_t wmi_char_read(struct file *filp, char __user *buffer, 83344b6b766SMario Limonciello size_t length, loff_t *offset) 83444b6b766SMario Limonciello { 83544b6b766SMario Limonciello struct wmi_block *wblock = filp->private_data; 83644b6b766SMario Limonciello 83744b6b766SMario Limonciello return simple_read_from_buffer(buffer, length, offset, 83844b6b766SMario Limonciello &wblock->req_buf_size, 83944b6b766SMario Limonciello sizeof(wblock->req_buf_size)); 84044b6b766SMario Limonciello } 84144b6b766SMario Limonciello 84244b6b766SMario Limonciello static long wmi_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) 84344b6b766SMario Limonciello { 84444b6b766SMario Limonciello struct wmi_ioctl_buffer __user *input = 84544b6b766SMario Limonciello (struct wmi_ioctl_buffer __user *) arg; 84644b6b766SMario Limonciello struct wmi_block *wblock = filp->private_data; 84744b6b766SMario Limonciello struct wmi_ioctl_buffer *buf = NULL; 84844b6b766SMario Limonciello struct wmi_driver *wdriver = NULL; 84944b6b766SMario Limonciello int ret; 85044b6b766SMario Limonciello 85144b6b766SMario Limonciello if (_IOC_TYPE(cmd) != WMI_IOC) 85244b6b766SMario Limonciello return -ENOTTY; 85344b6b766SMario Limonciello 85444b6b766SMario Limonciello /* make sure we're not calling a higher instance than exists*/ 85544b6b766SMario Limonciello if (_IOC_NR(cmd) >= wblock->gblock.instance_count) 85644b6b766SMario Limonciello return -EINVAL; 85744b6b766SMario Limonciello 85844b6b766SMario Limonciello mutex_lock(&wblock->char_mutex); 85944b6b766SMario Limonciello buf = wblock->handler_data; 86044b6b766SMario Limonciello if (get_user(buf->length, &input->length)) { 86144b6b766SMario Limonciello dev_dbg(&wblock->dev.dev, "Read length from user failed\n"); 86244b6b766SMario Limonciello ret = -EFAULT; 86344b6b766SMario Limonciello goto out_ioctl; 86444b6b766SMario Limonciello } 86544b6b766SMario Limonciello /* if it's too small, abort */ 86644b6b766SMario Limonciello if (buf->length < wblock->req_buf_size) { 86744b6b766SMario Limonciello dev_err(&wblock->dev.dev, 86844b6b766SMario Limonciello "Buffer %lld too small, need at least %lld\n", 86944b6b766SMario Limonciello buf->length, wblock->req_buf_size); 87044b6b766SMario Limonciello ret = -EINVAL; 87144b6b766SMario Limonciello goto out_ioctl; 87244b6b766SMario Limonciello } 87344b6b766SMario Limonciello /* if it's too big, warn, driver will only use what is needed */ 87444b6b766SMario Limonciello if (buf->length > wblock->req_buf_size) 87544b6b766SMario Limonciello dev_warn(&wblock->dev.dev, 87644b6b766SMario Limonciello "Buffer %lld is bigger than required %lld\n", 87744b6b766SMario Limonciello buf->length, wblock->req_buf_size); 87844b6b766SMario Limonciello 87944b6b766SMario Limonciello /* copy the structure from userspace */ 88044b6b766SMario Limonciello if (copy_from_user(buf, input, wblock->req_buf_size)) { 88144b6b766SMario Limonciello dev_dbg(&wblock->dev.dev, "Copy %llu from user failed\n", 88244b6b766SMario Limonciello wblock->req_buf_size); 88344b6b766SMario Limonciello ret = -EFAULT; 88444b6b766SMario Limonciello goto out_ioctl; 88544b6b766SMario Limonciello } 88644b6b766SMario Limonciello 88744b6b766SMario Limonciello /* let the driver do any filtering and do the call */ 88844b6b766SMario Limonciello wdriver = container_of(wblock->dev.dev.driver, 88944b6b766SMario Limonciello struct wmi_driver, driver); 8905e3e2297SMario Limonciello if (!try_module_get(wdriver->driver.owner)) { 8915e3e2297SMario Limonciello ret = -EBUSY; 8925e3e2297SMario Limonciello goto out_ioctl; 8935e3e2297SMario Limonciello } 89444b6b766SMario Limonciello ret = wdriver->filter_callback(&wblock->dev, cmd, buf); 89544b6b766SMario Limonciello module_put(wdriver->driver.owner); 89644b6b766SMario Limonciello if (ret) 89744b6b766SMario Limonciello goto out_ioctl; 89844b6b766SMario Limonciello 89944b6b766SMario Limonciello /* return the result (only up to our internal buffer size) */ 90044b6b766SMario Limonciello if (copy_to_user(input, buf, wblock->req_buf_size)) { 90144b6b766SMario Limonciello dev_dbg(&wblock->dev.dev, "Copy %llu to user failed\n", 90244b6b766SMario Limonciello wblock->req_buf_size); 90344b6b766SMario Limonciello ret = -EFAULT; 90444b6b766SMario Limonciello } 90544b6b766SMario Limonciello 90644b6b766SMario Limonciello out_ioctl: 90744b6b766SMario Limonciello mutex_unlock(&wblock->char_mutex); 90844b6b766SMario Limonciello return ret; 90944b6b766SMario Limonciello } 91044b6b766SMario Limonciello 91144b6b766SMario Limonciello static const struct file_operations wmi_fops = { 91244b6b766SMario Limonciello .owner = THIS_MODULE, 91344b6b766SMario Limonciello .read = wmi_char_read, 91444b6b766SMario Limonciello .open = wmi_char_open, 91544b6b766SMario Limonciello .unlocked_ioctl = wmi_ioctl, 9161832f2d8SArnd Bergmann .compat_ioctl = compat_ptr_ioctl, 91744b6b766SMario Limonciello }; 918844af950SAndy Lutomirski 919844af950SAndy Lutomirski static int wmi_dev_probe(struct device *dev) 920844af950SAndy Lutomirski { 921844af950SAndy Lutomirski struct wmi_block *wblock = dev_to_wblock(dev); 922844af950SAndy Lutomirski struct wmi_driver *wdriver = 923844af950SAndy Lutomirski container_of(dev->driver, struct wmi_driver, driver); 924844af950SAndy Lutomirski int ret = 0; 92544b6b766SMario Limonciello char *buf; 926844af950SAndy Lutomirski 927844af950SAndy Lutomirski if (ACPI_FAILURE(wmi_method_enable(wblock, 1))) 928844af950SAndy Lutomirski dev_warn(dev, "failed to enable device -- probing anyway\n"); 929844af950SAndy Lutomirski 930844af950SAndy Lutomirski if (wdriver->probe) { 931440c4983SMattias Jacobsson ret = wdriver->probe(dev_to_wdev(dev), 932440c4983SMattias Jacobsson find_guid_context(wblock, wdriver)); 93344b6b766SMario Limonciello if (ret != 0) 93444b6b766SMario Limonciello goto probe_failure; 935844af950SAndy Lutomirski } 936844af950SAndy Lutomirski 93744b6b766SMario Limonciello /* driver wants a character device made */ 93844b6b766SMario Limonciello if (wdriver->filter_callback) { 93944b6b766SMario Limonciello /* check that required buffer size declared by driver or MOF */ 94044b6b766SMario Limonciello if (!wblock->req_buf_size) { 94144b6b766SMario Limonciello dev_err(&wblock->dev.dev, 94244b6b766SMario Limonciello "Required buffer size not set\n"); 94344b6b766SMario Limonciello ret = -EINVAL; 94444b6b766SMario Limonciello goto probe_failure; 94544b6b766SMario Limonciello } 94644b6b766SMario Limonciello 9476fb74107SKees Cook wblock->handler_data = kmalloc(wblock->req_buf_size, 9486fb74107SKees Cook GFP_KERNEL); 94944b6b766SMario Limonciello if (!wblock->handler_data) { 95044b6b766SMario Limonciello ret = -ENOMEM; 95144b6b766SMario Limonciello goto probe_failure; 95244b6b766SMario Limonciello } 95344b6b766SMario Limonciello 9547f166addSAndy Shevchenko buf = kasprintf(GFP_KERNEL, "wmi/%s", wdriver->driver.name); 95544b6b766SMario Limonciello if (!buf) { 95644b6b766SMario Limonciello ret = -ENOMEM; 95744b6b766SMario Limonciello goto probe_string_failure; 95844b6b766SMario Limonciello } 95944b6b766SMario Limonciello wblock->char_dev.minor = MISC_DYNAMIC_MINOR; 96044b6b766SMario Limonciello wblock->char_dev.name = buf; 96144b6b766SMario Limonciello wblock->char_dev.fops = &wmi_fops; 96244b6b766SMario Limonciello wblock->char_dev.mode = 0444; 96344b6b766SMario Limonciello ret = misc_register(&wblock->char_dev); 96444b6b766SMario Limonciello if (ret) { 965501f7e52SJoe Perches dev_warn(dev, "failed to register char dev: %d\n", ret); 96644b6b766SMario Limonciello ret = -ENOMEM; 96744b6b766SMario Limonciello goto probe_misc_failure; 96844b6b766SMario Limonciello } 96944b6b766SMario Limonciello } 97044b6b766SMario Limonciello 97144b6b766SMario Limonciello return 0; 97244b6b766SMario Limonciello 97344b6b766SMario Limonciello probe_misc_failure: 97444b6b766SMario Limonciello kfree(buf); 97544b6b766SMario Limonciello probe_string_failure: 97644b6b766SMario Limonciello kfree(wblock->handler_data); 97744b6b766SMario Limonciello probe_failure: 97844b6b766SMario Limonciello if (ACPI_FAILURE(wmi_method_enable(wblock, 0))) 97944b6b766SMario Limonciello dev_warn(dev, "failed to disable device\n"); 980844af950SAndy Lutomirski return ret; 981844af950SAndy Lutomirski } 982844af950SAndy Lutomirski 983*fc7a6209SUwe Kleine-König static void wmi_dev_remove(struct device *dev) 984844af950SAndy Lutomirski { 985844af950SAndy Lutomirski struct wmi_block *wblock = dev_to_wblock(dev); 986844af950SAndy Lutomirski struct wmi_driver *wdriver = 987844af950SAndy Lutomirski container_of(dev->driver, struct wmi_driver, driver); 988844af950SAndy Lutomirski 98944b6b766SMario Limonciello if (wdriver->filter_callback) { 99044b6b766SMario Limonciello misc_deregister(&wblock->char_dev); 99144b6b766SMario Limonciello kfree(wblock->char_dev.name); 9926fb74107SKees Cook kfree(wblock->handler_data); 99344b6b766SMario Limonciello } 99444b6b766SMario Limonciello 995844af950SAndy Lutomirski if (wdriver->remove) 9962b329f56SUwe Kleine-König wdriver->remove(dev_to_wdev(dev)); 997844af950SAndy Lutomirski 998844af950SAndy Lutomirski if (ACPI_FAILURE(wmi_method_enable(wblock, 0))) 999844af950SAndy Lutomirski dev_warn(dev, "failed to disable device\n"); 1000844af950SAndy Lutomirski } 1001844af950SAndy Lutomirski 1002844af950SAndy Lutomirski static struct class wmi_bus_class = { 1003844af950SAndy Lutomirski .name = "wmi_bus", 10041caab3c1SMatthew Garrett }; 10051caab3c1SMatthew Garrett 1006844af950SAndy Lutomirski static struct bus_type wmi_bus_type = { 1007844af950SAndy Lutomirski .name = "wmi", 1008844af950SAndy Lutomirski .dev_groups = wmi_groups, 1009844af950SAndy Lutomirski .match = wmi_dev_match, 1010844af950SAndy Lutomirski .uevent = wmi_dev_uevent, 1011844af950SAndy Lutomirski .probe = wmi_dev_probe, 1012844af950SAndy Lutomirski .remove = wmi_dev_remove, 1013844af950SAndy Lutomirski }; 1014844af950SAndy Lutomirski 101569372c1dSBhumika Goyal static const struct device_type wmi_type_event = { 1016d79b1074SAndy Lutomirski .name = "event", 1017d79b1074SAndy Lutomirski .groups = wmi_event_groups, 1018d79b1074SAndy Lutomirski .release = wmi_dev_release, 1019d79b1074SAndy Lutomirski }; 1020d79b1074SAndy Lutomirski 102169372c1dSBhumika Goyal static const struct device_type wmi_type_method = { 1022d79b1074SAndy Lutomirski .name = "method", 1023d4fc91adSAndy Lutomirski .groups = wmi_method_groups, 1024d79b1074SAndy Lutomirski .release = wmi_dev_release, 1025d79b1074SAndy Lutomirski }; 1026d79b1074SAndy Lutomirski 102769372c1dSBhumika Goyal static const struct device_type wmi_type_data = { 1028d79b1074SAndy Lutomirski .name = "data", 1029d4fc91adSAndy Lutomirski .groups = wmi_data_groups, 1030d79b1074SAndy Lutomirski .release = wmi_dev_release, 1031d79b1074SAndy Lutomirski }; 1032d79b1074SAndy Lutomirski 1033fd70da6aSDarren Hart (VMware) static int wmi_create_device(struct device *wmi_bus_dev, 1034844af950SAndy Lutomirski const struct guid_block *gblock, 10357f5809bfSAndy Lutomirski struct wmi_block *wblock, 10367f5809bfSAndy Lutomirski struct acpi_device *device) 10371caab3c1SMatthew Garrett { 1038d4fc91adSAndy Lutomirski struct acpi_device_info *info; 1039d4fc91adSAndy Lutomirski char method[5]; 1040d4fc91adSAndy Lutomirski int result; 1041d4fc91adSAndy Lutomirski 1042fd70da6aSDarren Hart (VMware) if (gblock->flags & ACPI_WMI_EVENT) { 1043fd70da6aSDarren Hart (VMware) wblock->dev.dev.type = &wmi_type_event; 1044fd70da6aSDarren Hart (VMware) goto out_init; 1045fd70da6aSDarren Hart (VMware) } 1046d4fc91adSAndy Lutomirski 1047fd70da6aSDarren Hart (VMware) if (gblock->flags & ACPI_WMI_METHOD) { 1048fd70da6aSDarren Hart (VMware) wblock->dev.dev.type = &wmi_type_method; 104944b6b766SMario Limonciello mutex_init(&wblock->char_mutex); 1050fd70da6aSDarren Hart (VMware) goto out_init; 1051fd70da6aSDarren Hart (VMware) } 1052fd70da6aSDarren Hart (VMware) 1053fd70da6aSDarren Hart (VMware) /* 1054fd70da6aSDarren Hart (VMware) * Data Block Query Control Method (WQxx by convention) is 1055fd70da6aSDarren Hart (VMware) * required per the WMI documentation. If it is not present, 1056fd70da6aSDarren Hart (VMware) * we ignore this data block. 1057fd70da6aSDarren Hart (VMware) */ 1058d4fc91adSAndy Lutomirski strcpy(method, "WQ"); 1059d4fc91adSAndy Lutomirski strncat(method, wblock->gblock.object_id, 2); 1060d4fc91adSAndy Lutomirski result = get_subobj_info(device->handle, method, &info); 1061d4fc91adSAndy Lutomirski 1062fd70da6aSDarren Hart (VMware) if (result) { 1063fd70da6aSDarren Hart (VMware) dev_warn(wmi_bus_dev, 1064501f7e52SJoe Perches "%s data block query control method not found\n", 1065fd70da6aSDarren Hart (VMware) method); 1066fd70da6aSDarren Hart (VMware) return result; 1067fd70da6aSDarren Hart (VMware) } 1068fd70da6aSDarren Hart (VMware) 1069fd70da6aSDarren Hart (VMware) wblock->dev.dev.type = &wmi_type_data; 1070d4fc91adSAndy Lutomirski 1071d4fc91adSAndy Lutomirski /* 1072d4fc91adSAndy Lutomirski * The Microsoft documentation specifically states: 1073d4fc91adSAndy Lutomirski * 1074d4fc91adSAndy Lutomirski * Data blocks registered with only a single instance 1075d4fc91adSAndy Lutomirski * can ignore the parameter. 1076d4fc91adSAndy Lutomirski * 1077fd70da6aSDarren Hart (VMware) * ACPICA will get mad at us if we call the method with the wrong number 1078fd70da6aSDarren Hart (VMware) * of arguments, so check what our method expects. (On some Dell 1079fd70da6aSDarren Hart (VMware) * laptops, WQxx may not be a method at all.) 1080d4fc91adSAndy Lutomirski */ 1081fd70da6aSDarren Hart (VMware) if (info->type != ACPI_TYPE_METHOD || info->param_count == 0) 1082d4fc91adSAndy Lutomirski wblock->read_takes_no_args = true; 1083d4fc91adSAndy Lutomirski 1084d4fc91adSAndy Lutomirski kfree(info); 1085d4fc91adSAndy Lutomirski 1086d4fc91adSAndy Lutomirski strcpy(method, "WS"); 1087d4fc91adSAndy Lutomirski strncat(method, wblock->gblock.object_id, 2); 1088d4fc91adSAndy Lutomirski result = get_subobj_info(device->handle, method, NULL); 1089d4fc91adSAndy Lutomirski 1090fd70da6aSDarren Hart (VMware) if (result == 0) 1091fd70da6aSDarren Hart (VMware) wblock->dev.setable = true; 1092d4fc91adSAndy Lutomirski 1093fd70da6aSDarren Hart (VMware) out_init: 1094fd70da6aSDarren Hart (VMware) wblock->dev.dev.bus = &wmi_bus_type; 1095fd70da6aSDarren Hart (VMware) wblock->dev.dev.parent = wmi_bus_dev; 1096fd70da6aSDarren Hart (VMware) 1097fd70da6aSDarren Hart (VMware) dev_set_name(&wblock->dev.dev, "%pUL", gblock->guid); 1098c64eefd4SDmitry Torokhov 10996ee50aaaSDarren Hart (VMware) device_initialize(&wblock->dev.dev); 1100fd70da6aSDarren Hart (VMware) 1101fd70da6aSDarren Hart (VMware) return 0; 11021caab3c1SMatthew Garrett } 11031caab3c1SMatthew Garrett 1104b0e86302SAndy Lutomirski static void wmi_free_devices(struct acpi_device *device) 11051caab3c1SMatthew Garrett { 1106c64eefd4SDmitry Torokhov struct wmi_block *wblock, *next; 11071caab3c1SMatthew Garrett 11081caab3c1SMatthew Garrett /* Delete devices for all the GUIDs */ 1109023b9565SDmitry Torokhov list_for_each_entry_safe(wblock, next, &wmi_block_list, list) { 1110b0e86302SAndy Lutomirski if (wblock->acpi_device == device) { 1111023b9565SDmitry Torokhov list_del(&wblock->list); 1112844af950SAndy Lutomirski device_unregister(&wblock->dev.dev); 1113023b9565SDmitry Torokhov } 11141caab3c1SMatthew Garrett } 1115b0e86302SAndy Lutomirski } 11161caab3c1SMatthew Garrett 11176701cc8fSAndy Shevchenko static bool guid_already_parsed(struct acpi_device *device, const u8 *guid) 1118d1f9e497SCarlos Corbacho { 1119d1f9e497SCarlos Corbacho struct wmi_block *wblock; 1120d1f9e497SCarlos Corbacho 1121b0e86302SAndy Lutomirski list_for_each_entry(wblock, &wmi_block_list, list) { 1122b0e86302SAndy Lutomirski if (memcmp(wblock->gblock.guid, guid, 16) == 0) { 1123b0e86302SAndy Lutomirski /* 1124b0e86302SAndy Lutomirski * Because we historically didn't track the relationship 1125b0e86302SAndy Lutomirski * between GUIDs and ACPI nodes, we don't know whether 1126b0e86302SAndy Lutomirski * we need to suppress GUIDs that are unique on a 1127b0e86302SAndy Lutomirski * given node but duplicated across nodes. 1128b0e86302SAndy Lutomirski */ 1129b0e86302SAndy Lutomirski dev_warn(&device->dev, "duplicate WMI GUID %pUL (first instance was on %s)\n", 1130b0e86302SAndy Lutomirski guid, dev_name(&wblock->acpi_device->dev)); 1131d1f9e497SCarlos Corbacho return true; 1132b0e86302SAndy Lutomirski } 1133b0e86302SAndy Lutomirski } 1134c64eefd4SDmitry Torokhov 1135d1f9e497SCarlos Corbacho return false; 1136d1f9e497SCarlos Corbacho } 1137d1f9e497SCarlos Corbacho 11381caab3c1SMatthew Garrett /* 1139b4f9fe12SLen Brown * Parse the _WDG method for the GUID data blocks 1140b4f9fe12SLen Brown */ 1141844af950SAndy Lutomirski static int parse_wdg(struct device *wmi_bus_dev, struct acpi_device *device) 1142b4f9fe12SLen Brown { 1143b4f9fe12SLen Brown struct acpi_buffer out = {ACPI_ALLOCATE_BUFFER, NULL}; 114437830662SDmitry Torokhov const struct guid_block *gblock; 11456ee50aaaSDarren Hart (VMware) struct wmi_block *wblock, *next; 11466ee50aaaSDarren Hart (VMware) union acpi_object *obj; 1147b4f9fe12SLen Brown acpi_status status; 11486ee50aaaSDarren Hart (VMware) int retval = 0; 1149b4f9fe12SLen Brown u32 i, total; 1150b4f9fe12SLen Brown 11517f5809bfSAndy Lutomirski status = acpi_evaluate_object(device->handle, "_WDG", NULL, &out); 1152b4f9fe12SLen Brown if (ACPI_FAILURE(status)) 1153c64eefd4SDmitry Torokhov return -ENXIO; 1154b4f9fe12SLen Brown 1155b4f9fe12SLen Brown obj = (union acpi_object *) out.pointer; 11563d2c63ebSDmitry Torokhov if (!obj) 1157c64eefd4SDmitry Torokhov return -ENXIO; 1158b4f9fe12SLen Brown 115964ed0ab8SDmitry Torokhov if (obj->type != ACPI_TYPE_BUFFER) { 1160c64eefd4SDmitry Torokhov retval = -ENXIO; 116164ed0ab8SDmitry Torokhov goto out_free_pointer; 116264ed0ab8SDmitry Torokhov } 1163b4f9fe12SLen Brown 116437830662SDmitry Torokhov gblock = (const struct guid_block *)obj->buffer.pointer; 1165b4f9fe12SLen Brown total = obj->buffer.length / sizeof(struct guid_block); 1166b4f9fe12SLen Brown 1167b4f9fe12SLen Brown for (i = 0; i < total; i++) { 1168a929aae0SThomas Renninger if (debug_dump_wdg) 1169a929aae0SThomas Renninger wmi_dump_wdg(&gblock[i]); 1170a929aae0SThomas Renninger 1171a1c31bcdSAndy Lutomirski /* 1172a1c31bcdSAndy Lutomirski * Some WMI devices, like those for nVidia hooks, have a 1173a1c31bcdSAndy Lutomirski * duplicate GUID. It's not clear what we should do in this 1174a1c31bcdSAndy Lutomirski * case yet, so for now, we'll just ignore the duplicate 1175a1c31bcdSAndy Lutomirski * for device creation. 1176a1c31bcdSAndy Lutomirski */ 1177a1c31bcdSAndy Lutomirski if (guid_already_parsed(device, gblock[i].guid)) 1178a1c31bcdSAndy Lutomirski continue; 1179a1c31bcdSAndy Lutomirski 118058f6425eSColin King wblock = kzalloc(sizeof(struct wmi_block), GFP_KERNEL); 11816ee50aaaSDarren Hart (VMware) if (!wblock) { 11826ee50aaaSDarren Hart (VMware) retval = -ENOMEM; 11836ee50aaaSDarren Hart (VMware) break; 11846ee50aaaSDarren Hart (VMware) } 118558f6425eSColin King 1186b0e86302SAndy Lutomirski wblock->acpi_device = device; 118758f6425eSColin King wblock->gblock = gblock[i]; 118858f6425eSColin King 1189fd70da6aSDarren Hart (VMware) retval = wmi_create_device(wmi_bus_dev, &gblock[i], wblock, device); 1190fd70da6aSDarren Hart (VMware) if (retval) { 1191fd70da6aSDarren Hart (VMware) kfree(wblock); 1192fd70da6aSDarren Hart (VMware) continue; 1193fd70da6aSDarren Hart (VMware) } 119458f6425eSColin King 119558f6425eSColin King list_add_tail(&wblock->list, &wmi_block_list); 1196b4f9fe12SLen Brown 1197fc3155b2SThomas Renninger if (debug_event) { 1198fc3155b2SThomas Renninger wblock->handler = wmi_notify_debug; 11992d5ab555SDmitry Torokhov wmi_method_enable(wblock, 1); 1200fc3155b2SThomas Renninger } 1201b4f9fe12SLen Brown } 1202b4f9fe12SLen Brown 12036ee50aaaSDarren Hart (VMware) /* 12046ee50aaaSDarren Hart (VMware) * Now that all of the devices are created, add them to the 12056ee50aaaSDarren Hart (VMware) * device tree and probe subdrivers. 12066ee50aaaSDarren Hart (VMware) */ 12076ee50aaaSDarren Hart (VMware) list_for_each_entry_safe(wblock, next, &wmi_block_list, list) { 12086ee50aaaSDarren Hart (VMware) if (wblock->acpi_device != device) 12096ee50aaaSDarren Hart (VMware) continue; 12106ee50aaaSDarren Hart (VMware) 12116ee50aaaSDarren Hart (VMware) retval = device_add(&wblock->dev.dev); 12126ee50aaaSDarren Hart (VMware) if (retval) { 1213501f7e52SJoe Perches dev_err(wmi_bus_dev, "failed to register %pUL\n", 12146ee50aaaSDarren Hart (VMware) wblock->gblock.guid); 12156ee50aaaSDarren Hart (VMware) if (debug_event) 12166ee50aaaSDarren Hart (VMware) wmi_method_enable(wblock, 0); 12176ee50aaaSDarren Hart (VMware) list_del(&wblock->list); 12186ee50aaaSDarren Hart (VMware) put_device(&wblock->dev.dev); 12196ee50aaaSDarren Hart (VMware) } 12206ee50aaaSDarren Hart (VMware) } 1221c64eefd4SDmitry Torokhov 1222a5167c5bSAxel Lin out_free_pointer: 1223a5167c5bSAxel Lin kfree(out.pointer); 1224c64eefd4SDmitry Torokhov return retval; 1225b4f9fe12SLen Brown } 1226b4f9fe12SLen Brown 1227b4f9fe12SLen Brown /* 1228b4f9fe12SLen Brown * WMI can have EmbeddedControl access regions. In which case, we just want to 1229b4f9fe12SLen Brown * hand these off to the EC driver. 1230b4f9fe12SLen Brown */ 1231b4f9fe12SLen Brown static acpi_status 1232b4f9fe12SLen Brown acpi_wmi_ec_space_handler(u32 function, acpi_physical_address address, 1233439913ffSLin Ming u32 bits, u64 *value, 1234b4f9fe12SLen Brown void *handler_context, void *region_context) 1235b4f9fe12SLen Brown { 1236b4f9fe12SLen Brown int result = 0, i = 0; 1237b4f9fe12SLen Brown u8 temp = 0; 1238b4f9fe12SLen Brown 1239b4f9fe12SLen Brown if ((address > 0xFF) || !value) 1240b4f9fe12SLen Brown return AE_BAD_PARAMETER; 1241b4f9fe12SLen Brown 1242b4f9fe12SLen Brown if (function != ACPI_READ && function != ACPI_WRITE) 1243b4f9fe12SLen Brown return AE_BAD_PARAMETER; 1244b4f9fe12SLen Brown 1245b4f9fe12SLen Brown if (bits != 8) 1246b4f9fe12SLen Brown return AE_BAD_PARAMETER; 1247b4f9fe12SLen Brown 1248b4f9fe12SLen Brown if (function == ACPI_READ) { 1249b4f9fe12SLen Brown result = ec_read(address, &temp); 1250439913ffSLin Ming (*value) |= ((u64)temp) << i; 1251b4f9fe12SLen Brown } else { 1252b4f9fe12SLen Brown temp = 0xff & ((*value) >> i); 1253b4f9fe12SLen Brown result = ec_write(address, temp); 1254b4f9fe12SLen Brown } 1255b4f9fe12SLen Brown 1256b4f9fe12SLen Brown switch (result) { 1257b4f9fe12SLen Brown case -EINVAL: 1258b4f9fe12SLen Brown return AE_BAD_PARAMETER; 1259b4f9fe12SLen Brown case -ENODEV: 1260b4f9fe12SLen Brown return AE_NOT_FOUND; 1261b4f9fe12SLen Brown case -ETIME: 1262b4f9fe12SLen Brown return AE_TIME; 1263b4f9fe12SLen Brown default: 1264b4f9fe12SLen Brown return AE_OK; 1265b4f9fe12SLen Brown } 1266b4f9fe12SLen Brown } 1267b4f9fe12SLen Brown 12681686f544SAndy Lutomirski static void acpi_wmi_notify_handler(acpi_handle handle, u32 event, 12691686f544SAndy Lutomirski void *context) 1270b4f9fe12SLen Brown { 1271b4f9fe12SLen Brown struct guid_block *block; 1272b4f9fe12SLen Brown struct wmi_block *wblock; 12731686f544SAndy Lutomirski bool found_it = false; 1274b4f9fe12SLen Brown 1275cedb3b2aSAndy Shevchenko list_for_each_entry(wblock, &wmi_block_list, list) { 1276b4f9fe12SLen Brown block = &wblock->gblock; 1277b4f9fe12SLen Brown 12781686f544SAndy Lutomirski if (wblock->acpi_device->handle == handle && 1279b0e86302SAndy Lutomirski (block->flags & ACPI_WMI_EVENT) && 12801686f544SAndy Lutomirski (block->notify_id == event)) 12811686f544SAndy Lutomirski { 12821686f544SAndy Lutomirski found_it = true; 12831686f544SAndy Lutomirski break; 12841686f544SAndy Lutomirski } 12851686f544SAndy Lutomirski } 12861686f544SAndy Lutomirski 12871686f544SAndy Lutomirski if (!found_it) 12881686f544SAndy Lutomirski return; 12891686f544SAndy Lutomirski 12901686f544SAndy Lutomirski /* If a driver is bound, then notify the driver. */ 12911686f544SAndy Lutomirski if (wblock->dev.dev.driver) { 12921686f544SAndy Lutomirski struct wmi_driver *driver; 12931686f544SAndy Lutomirski struct acpi_object_list input; 12941686f544SAndy Lutomirski union acpi_object params[1]; 12951686f544SAndy Lutomirski struct acpi_buffer evdata = { ACPI_ALLOCATE_BUFFER, NULL }; 12961686f544SAndy Lutomirski acpi_status status; 12971686f544SAndy Lutomirski 12981686f544SAndy Lutomirski driver = container_of(wblock->dev.dev.driver, 12991686f544SAndy Lutomirski struct wmi_driver, driver); 13001686f544SAndy Lutomirski 13011686f544SAndy Lutomirski input.count = 1; 13021686f544SAndy Lutomirski input.pointer = params; 13031686f544SAndy Lutomirski params[0].type = ACPI_TYPE_INTEGER; 13041686f544SAndy Lutomirski params[0].integer.value = event; 13051686f544SAndy Lutomirski 13061686f544SAndy Lutomirski status = acpi_evaluate_object(wblock->acpi_device->handle, 13071686f544SAndy Lutomirski "_WED", &input, &evdata); 13081686f544SAndy Lutomirski if (ACPI_FAILURE(status)) { 13091686f544SAndy Lutomirski dev_warn(&wblock->dev.dev, 13101686f544SAndy Lutomirski "failed to get event data\n"); 13111686f544SAndy Lutomirski return; 13121686f544SAndy Lutomirski } 13131686f544SAndy Lutomirski 13141686f544SAndy Lutomirski if (driver->notify) 13151686f544SAndy Lutomirski driver->notify(&wblock->dev, 13161686f544SAndy Lutomirski (union acpi_object *)evdata.pointer); 13171686f544SAndy Lutomirski 13181686f544SAndy Lutomirski kfree(evdata.pointer); 13191686f544SAndy Lutomirski } else if (wblock->handler) { 13201686f544SAndy Lutomirski /* Legacy handler */ 1321b4f9fe12SLen Brown wblock->handler(event, wblock->handler_data); 13221686f544SAndy Lutomirski } 13231686f544SAndy Lutomirski 13246701cc8fSAndy Shevchenko if (debug_event) 13256701cc8fSAndy Shevchenko pr_info("DEBUG Event GUID: %pUL\n", wblock->gblock.guid); 1326b4f9fe12SLen Brown 1327b4f9fe12SLen Brown acpi_bus_generate_netlink_event( 13281686f544SAndy Lutomirski wblock->acpi_device->pnp.device_class, 13291686f544SAndy Lutomirski dev_name(&wblock->dev.dev), 1330b4f9fe12SLen Brown event, 0); 13311686f544SAndy Lutomirski 1332b4f9fe12SLen Brown } 1333b4f9fe12SLen Brown 13349599ed91SAndy Lutomirski static int acpi_wmi_remove(struct platform_device *device) 1335b4f9fe12SLen Brown { 13369599ed91SAndy Lutomirski struct acpi_device *acpi_device = ACPI_COMPANION(&device->dev); 13379599ed91SAndy Lutomirski 13389599ed91SAndy Lutomirski acpi_remove_notify_handler(acpi_device->handle, ACPI_DEVICE_NOTIFY, 13391686f544SAndy Lutomirski acpi_wmi_notify_handler); 13409599ed91SAndy Lutomirski acpi_remove_address_space_handler(acpi_device->handle, 1341b4f9fe12SLen Brown ACPI_ADR_SPACE_EC, &acpi_wmi_ec_space_handler); 13429599ed91SAndy Lutomirski wmi_free_devices(acpi_device); 134356afb8d4SYongxin Liu device_unregister((struct device *)dev_get_drvdata(&device->dev)); 1344b4f9fe12SLen Brown 1345b4f9fe12SLen Brown return 0; 1346b4f9fe12SLen Brown } 1347b4f9fe12SLen Brown 13489599ed91SAndy Lutomirski static int acpi_wmi_probe(struct platform_device *device) 1349b4f9fe12SLen Brown { 13509599ed91SAndy Lutomirski struct acpi_device *acpi_device; 1351844af950SAndy Lutomirski struct device *wmi_bus_dev; 1352b4f9fe12SLen Brown acpi_status status; 1353c64eefd4SDmitry Torokhov int error; 1354b4f9fe12SLen Brown 13559599ed91SAndy Lutomirski acpi_device = ACPI_COMPANION(&device->dev); 13569599ed91SAndy Lutomirski if (!acpi_device) { 13579599ed91SAndy Lutomirski dev_err(&device->dev, "ACPI companion is missing\n"); 13589599ed91SAndy Lutomirski return -ENODEV; 13599599ed91SAndy Lutomirski } 13609599ed91SAndy Lutomirski 13619599ed91SAndy Lutomirski status = acpi_install_address_space_handler(acpi_device->handle, 1362b4f9fe12SLen Brown ACPI_ADR_SPACE_EC, 1363b4f9fe12SLen Brown &acpi_wmi_ec_space_handler, 1364b4f9fe12SLen Brown NULL, NULL); 13655212cd67SDmitry Torokhov if (ACPI_FAILURE(status)) { 136646492ee4SAndy Lutomirski dev_err(&device->dev, "Error installing EC region handler\n"); 1367b4f9fe12SLen Brown return -ENODEV; 13685212cd67SDmitry Torokhov } 1369b4f9fe12SLen Brown 13709599ed91SAndy Lutomirski status = acpi_install_notify_handler(acpi_device->handle, 13719599ed91SAndy Lutomirski ACPI_DEVICE_NOTIFY, 13721686f544SAndy Lutomirski acpi_wmi_notify_handler, 13731686f544SAndy Lutomirski NULL); 13741686f544SAndy Lutomirski if (ACPI_FAILURE(status)) { 13751686f544SAndy Lutomirski dev_err(&device->dev, "Error installing notify handler\n"); 13761686f544SAndy Lutomirski error = -ENODEV; 13771686f544SAndy Lutomirski goto err_remove_ec_handler; 13781686f544SAndy Lutomirski } 13791686f544SAndy Lutomirski 1380844af950SAndy Lutomirski wmi_bus_dev = device_create(&wmi_bus_class, &device->dev, MKDEV(0, 0), 1381844af950SAndy Lutomirski NULL, "wmi_bus-%s", dev_name(&device->dev)); 1382844af950SAndy Lutomirski if (IS_ERR(wmi_bus_dev)) { 1383844af950SAndy Lutomirski error = PTR_ERR(wmi_bus_dev); 13841686f544SAndy Lutomirski goto err_remove_notify_handler; 1385844af950SAndy Lutomirski } 13869599ed91SAndy Lutomirski dev_set_drvdata(&device->dev, wmi_bus_dev); 1387844af950SAndy Lutomirski 13889599ed91SAndy Lutomirski error = parse_wdg(wmi_bus_dev, acpi_device); 1389c64eefd4SDmitry Torokhov if (error) { 13908e07514dSDmitry Torokhov pr_err("Failed to parse WDG method\n"); 1391844af950SAndy Lutomirski goto err_remove_busdev; 1392b4f9fe12SLen Brown } 1393b4f9fe12SLen Brown 1394c64eefd4SDmitry Torokhov return 0; 139546492ee4SAndy Lutomirski 1396844af950SAndy Lutomirski err_remove_busdev: 139756afb8d4SYongxin Liu device_unregister(wmi_bus_dev); 1398844af950SAndy Lutomirski 13991686f544SAndy Lutomirski err_remove_notify_handler: 14009599ed91SAndy Lutomirski acpi_remove_notify_handler(acpi_device->handle, ACPI_DEVICE_NOTIFY, 14011686f544SAndy Lutomirski acpi_wmi_notify_handler); 14021686f544SAndy Lutomirski 14031686f544SAndy Lutomirski err_remove_ec_handler: 14049599ed91SAndy Lutomirski acpi_remove_address_space_handler(acpi_device->handle, 140546492ee4SAndy Lutomirski ACPI_ADR_SPACE_EC, 140646492ee4SAndy Lutomirski &acpi_wmi_ec_space_handler); 140746492ee4SAndy Lutomirski 140846492ee4SAndy Lutomirski return error; 1409b4f9fe12SLen Brown } 1410b4f9fe12SLen Brown 1411844af950SAndy Lutomirski int __must_check __wmi_driver_register(struct wmi_driver *driver, 1412844af950SAndy Lutomirski struct module *owner) 1413844af950SAndy Lutomirski { 1414844af950SAndy Lutomirski driver->driver.owner = owner; 1415844af950SAndy Lutomirski driver->driver.bus = &wmi_bus_type; 1416844af950SAndy Lutomirski 1417844af950SAndy Lutomirski return driver_register(&driver->driver); 1418844af950SAndy Lutomirski } 1419844af950SAndy Lutomirski EXPORT_SYMBOL(__wmi_driver_register); 1420844af950SAndy Lutomirski 1421844af950SAndy Lutomirski void wmi_driver_unregister(struct wmi_driver *driver) 1422844af950SAndy Lutomirski { 1423844af950SAndy Lutomirski driver_unregister(&driver->driver); 1424844af950SAndy Lutomirski } 1425844af950SAndy Lutomirski EXPORT_SYMBOL(wmi_driver_unregister); 1426844af950SAndy Lutomirski 1427b4f9fe12SLen Brown static int __init acpi_wmi_init(void) 1428b4f9fe12SLen Brown { 1429c64eefd4SDmitry Torokhov int error; 1430b4f9fe12SLen Brown 1431b4f9fe12SLen Brown if (acpi_disabled) 1432b4f9fe12SLen Brown return -ENODEV; 1433b4f9fe12SLen Brown 1434844af950SAndy Lutomirski error = class_register(&wmi_bus_class); 1435c64eefd4SDmitry Torokhov if (error) 1436c64eefd4SDmitry Torokhov return error; 1437b4f9fe12SLen Brown 1438844af950SAndy Lutomirski error = bus_register(&wmi_bus_type); 1439844af950SAndy Lutomirski if (error) 1440844af950SAndy Lutomirski goto err_unreg_class; 1441844af950SAndy Lutomirski 14429599ed91SAndy Lutomirski error = platform_driver_register(&acpi_wmi_driver); 1443c64eefd4SDmitry Torokhov if (error) { 1444c64eefd4SDmitry Torokhov pr_err("Error loading mapper\n"); 1445844af950SAndy Lutomirski goto err_unreg_bus; 14461caab3c1SMatthew Garrett } 14471caab3c1SMatthew Garrett 14488e07514dSDmitry Torokhov return 0; 1449844af950SAndy Lutomirski 1450844af950SAndy Lutomirski err_unreg_bus: 1451844af950SAndy Lutomirski bus_unregister(&wmi_bus_type); 1452844af950SAndy Lutomirski 145397277717SAlexey Khoroshilov err_unreg_class: 145497277717SAlexey Khoroshilov class_unregister(&wmi_bus_class); 145597277717SAlexey Khoroshilov 1456844af950SAndy Lutomirski return error; 1457b4f9fe12SLen Brown } 1458b4f9fe12SLen Brown 1459b4f9fe12SLen Brown static void __exit acpi_wmi_exit(void) 1460b4f9fe12SLen Brown { 14619599ed91SAndy Lutomirski platform_driver_unregister(&acpi_wmi_driver); 1462844af950SAndy Lutomirski bus_unregister(&wmi_bus_type); 1463303d1fccSMario Limonciello class_unregister(&wmi_bus_class); 1464b4f9fe12SLen Brown } 1465b4f9fe12SLen Brown 146698b8e4e5SRafael J. Wysocki subsys_initcall_sync(acpi_wmi_init); 1467b4f9fe12SLen Brown module_exit(acpi_wmi_exit); 1468