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> 3244b6b766SMario Limonciello #include <uapi/linux/wmi.h> 33b4f9fe12SLen Brown 34b4f9fe12SLen Brown ACPI_MODULE_NAME("wmi"); 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 { 113538d7eb8SAndy Shevchenko uuid_le guid_input; 114b4f9fe12SLen Brown struct wmi_block *wblock; 115b4f9fe12SLen Brown struct guid_block *block; 116b4f9fe12SLen Brown 117538d7eb8SAndy Shevchenko if (uuid_le_to_bin(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; 136a48e2338SMattias Jacobsson uuid_le 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) { 145a48e2338SMattias Jacobsson if (uuid_le_to_bin(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 20444b6b766SMario Limonciello * @instance: Instance index 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 224b4f9fe12SLen Brown * &in: Buffer containing input for the method call 225b4f9fe12SLen Brown * &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 246722c856dSMario Limonciello * &in: Buffer containing input for the method call 247722c856dSMario Limonciello * &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 36656a37025SAndy Lutomirski * &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 401b4f9fe12SLen Brown * &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 512b4f9fe12SLen Brown * @handler: Function to handle notifications 513b4f9fe12SLen Brown * @data: Data to be returned to handler when event is fired 514b4f9fe12SLen Brown * 515b4f9fe12SLen Brown * Register a handler for events sent to the ACPI-WMI mapper device. 516b4f9fe12SLen Brown */ 517b4f9fe12SLen Brown acpi_status wmi_install_notify_handler(const char *guid, 518b4f9fe12SLen Brown wmi_notify_handler handler, void *data) 519b4f9fe12SLen Brown { 520b4f9fe12SLen Brown struct wmi_block *block; 52158f6425eSColin King acpi_status status = AE_NOT_EXIST; 522538d7eb8SAndy Shevchenko uuid_le guid_input; 523b4f9fe12SLen Brown 524b4f9fe12SLen Brown if (!guid || !handler) 525b4f9fe12SLen Brown return AE_BAD_PARAMETER; 526b4f9fe12SLen Brown 527538d7eb8SAndy Shevchenko if (uuid_le_to_bin(guid, &guid_input)) 528538d7eb8SAndy Shevchenko return AE_BAD_PARAMETER; 529b4f9fe12SLen Brown 530cedb3b2aSAndy Shevchenko list_for_each_entry(block, &wmi_block_list, list) { 53158f6425eSColin King acpi_status wmi_status; 53258f6425eSColin King 533538d7eb8SAndy Shevchenko if (memcmp(block->gblock.guid, &guid_input, 16) == 0) { 53458f6425eSColin King if (block->handler && 53558f6425eSColin King block->handler != wmi_notify_debug) 536b4f9fe12SLen Brown return AE_ALREADY_ACQUIRED; 537b4f9fe12SLen Brown 538b4f9fe12SLen Brown block->handler = handler; 539b4f9fe12SLen Brown block->handler_data = data; 540b4f9fe12SLen Brown 54158f6425eSColin King wmi_status = wmi_method_enable(block, 1); 54258f6425eSColin King if ((wmi_status != AE_OK) || 54358f6425eSColin King ((wmi_status == AE_OK) && (status == AE_NOT_EXIST))) 54458f6425eSColin King status = wmi_status; 54558f6425eSColin King } 54658f6425eSColin King } 547b4f9fe12SLen Brown 548b4f9fe12SLen Brown return status; 549b4f9fe12SLen Brown } 550b4f9fe12SLen Brown EXPORT_SYMBOL_GPL(wmi_install_notify_handler); 551b4f9fe12SLen Brown 552b4f9fe12SLen Brown /** 553b4f9fe12SLen Brown * wmi_uninstall_notify_handler - Unregister handler for WMI events 554b4f9fe12SLen Brown * 555b4f9fe12SLen Brown * Unregister handler for events sent to the ACPI-WMI mapper device. 556b4f9fe12SLen Brown */ 557b4f9fe12SLen Brown acpi_status wmi_remove_notify_handler(const char *guid) 558b4f9fe12SLen Brown { 559b4f9fe12SLen Brown struct wmi_block *block; 56058f6425eSColin King acpi_status status = AE_NOT_EXIST; 561538d7eb8SAndy Shevchenko uuid_le guid_input; 562b4f9fe12SLen Brown 563b4f9fe12SLen Brown if (!guid) 564b4f9fe12SLen Brown return AE_BAD_PARAMETER; 565b4f9fe12SLen Brown 566538d7eb8SAndy Shevchenko if (uuid_le_to_bin(guid, &guid_input)) 567538d7eb8SAndy Shevchenko return AE_BAD_PARAMETER; 568b4f9fe12SLen Brown 569cedb3b2aSAndy Shevchenko list_for_each_entry(block, &wmi_block_list, list) { 57058f6425eSColin King acpi_status wmi_status; 57158f6425eSColin King 572538d7eb8SAndy Shevchenko if (memcmp(block->gblock.guid, &guid_input, 16) == 0) { 57358f6425eSColin King if (!block->handler || 57458f6425eSColin King block->handler == wmi_notify_debug) 575b4f9fe12SLen Brown return AE_NULL_ENTRY; 576b4f9fe12SLen Brown 577fc3155b2SThomas Renninger if (debug_event) { 578fc3155b2SThomas Renninger block->handler = wmi_notify_debug; 57958f6425eSColin King status = AE_OK; 580fc3155b2SThomas Renninger } else { 58158f6425eSColin King wmi_status = wmi_method_enable(block, 0); 582b4f9fe12SLen Brown block->handler = NULL; 583b4f9fe12SLen Brown block->handler_data = NULL; 58458f6425eSColin King if ((wmi_status != AE_OK) || 58558f6425eSColin King ((wmi_status == AE_OK) && 58658f6425eSColin King (status == AE_NOT_EXIST))) 58758f6425eSColin King status = wmi_status; 588fc3155b2SThomas Renninger } 58958f6425eSColin King } 59058f6425eSColin King } 59158f6425eSColin King 592b4f9fe12SLen Brown return status; 593b4f9fe12SLen Brown } 594b4f9fe12SLen Brown EXPORT_SYMBOL_GPL(wmi_remove_notify_handler); 595b4f9fe12SLen Brown 596b4f9fe12SLen Brown /** 597b4f9fe12SLen Brown * wmi_get_event_data - Get WMI data associated with an event 598b4f9fe12SLen Brown * 5993e9b988eSAnisse Astier * @event: Event to find 6003e9b988eSAnisse Astier * @out: Buffer to hold event data. out->pointer should be freed with kfree() 601b4f9fe12SLen Brown * 602b4f9fe12SLen Brown * Returns extra data associated with an event in WMI. 603b4f9fe12SLen Brown */ 604b4f9fe12SLen Brown acpi_status wmi_get_event_data(u32 event, struct acpi_buffer *out) 605b4f9fe12SLen Brown { 606b4f9fe12SLen Brown struct acpi_object_list input; 607b4f9fe12SLen Brown union acpi_object params[1]; 608b4f9fe12SLen Brown struct guid_block *gblock; 609b4f9fe12SLen Brown struct wmi_block *wblock; 610b4f9fe12SLen Brown 611b4f9fe12SLen Brown input.count = 1; 612b4f9fe12SLen Brown input.pointer = params; 613b4f9fe12SLen Brown params[0].type = ACPI_TYPE_INTEGER; 614b4f9fe12SLen Brown params[0].integer.value = event; 615b4f9fe12SLen Brown 616cedb3b2aSAndy Shevchenko list_for_each_entry(wblock, &wmi_block_list, list) { 617b4f9fe12SLen Brown gblock = &wblock->gblock; 618b4f9fe12SLen Brown 619b4f9fe12SLen Brown if ((gblock->flags & ACPI_WMI_EVENT) && 620b4f9fe12SLen Brown (gblock->notify_id == event)) 621b0e86302SAndy Lutomirski return acpi_evaluate_object(wblock->acpi_device->handle, 622b0e86302SAndy Lutomirski "_WED", &input, out); 623b4f9fe12SLen Brown } 624b4f9fe12SLen Brown 625b4f9fe12SLen Brown return AE_NOT_FOUND; 626b4f9fe12SLen Brown } 627b4f9fe12SLen Brown EXPORT_SYMBOL_GPL(wmi_get_event_data); 628b4f9fe12SLen Brown 629b4f9fe12SLen Brown /** 630b4f9fe12SLen Brown * wmi_has_guid - Check if a GUID is available 631b4f9fe12SLen Brown * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba 632b4f9fe12SLen Brown * 633b4f9fe12SLen Brown * Check if a given GUID is defined by _WDG 634b4f9fe12SLen Brown */ 635b4f9fe12SLen Brown bool wmi_has_guid(const char *guid_string) 636b4f9fe12SLen Brown { 637b4f9fe12SLen Brown return find_guid(guid_string, NULL); 638b4f9fe12SLen Brown } 639b4f9fe12SLen Brown EXPORT_SYMBOL_GPL(wmi_has_guid); 640b4f9fe12SLen Brown 641e7488e58SYurii Pavlovskyi /** 642e7488e58SYurii Pavlovskyi * wmi_get_acpi_device_uid() - Get _UID name of ACPI device that defines GUID 643e7488e58SYurii Pavlovskyi * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba 644e7488e58SYurii Pavlovskyi * 645e7488e58SYurii Pavlovskyi * Find the _UID of ACPI device associated with this WMI GUID. 646e7488e58SYurii Pavlovskyi * 647e7488e58SYurii Pavlovskyi * Return: The ACPI _UID field value or NULL if the WMI GUID was not found 648e7488e58SYurii Pavlovskyi */ 649e7488e58SYurii Pavlovskyi char *wmi_get_acpi_device_uid(const char *guid_string) 650e7488e58SYurii Pavlovskyi { 651e7488e58SYurii Pavlovskyi struct wmi_block *wblock = NULL; 652e7488e58SYurii Pavlovskyi 653e7488e58SYurii Pavlovskyi if (!find_guid(guid_string, &wblock)) 654e7488e58SYurii Pavlovskyi return NULL; 655e7488e58SYurii Pavlovskyi 656e7488e58SYurii Pavlovskyi return acpi_device_uid(wblock->acpi_device); 657e7488e58SYurii Pavlovskyi } 658e7488e58SYurii Pavlovskyi EXPORT_SYMBOL_GPL(wmi_get_acpi_device_uid); 659e7488e58SYurii Pavlovskyi 660844af950SAndy Lutomirski static struct wmi_block *dev_to_wblock(struct device *dev) 661844af950SAndy Lutomirski { 662844af950SAndy Lutomirski return container_of(dev, struct wmi_block, dev.dev); 663844af950SAndy Lutomirski } 664844af950SAndy Lutomirski 665844af950SAndy Lutomirski static struct wmi_device *dev_to_wdev(struct device *dev) 666844af950SAndy Lutomirski { 667844af950SAndy Lutomirski return container_of(dev, struct wmi_device, dev); 668844af950SAndy Lutomirski } 669844af950SAndy Lutomirski 670b4f9fe12SLen Brown /* 6711caab3c1SMatthew Garrett * sysfs interface 6721caab3c1SMatthew Garrett */ 673614ef432SDmitry Torokhov static ssize_t modalias_show(struct device *dev, struct device_attribute *attr, 6741caab3c1SMatthew Garrett char *buf) 6751caab3c1SMatthew Garrett { 676844af950SAndy Lutomirski struct wmi_block *wblock = dev_to_wblock(dev); 6771caab3c1SMatthew Garrett 67885b4e4ebSRasmus Villemoes return sprintf(buf, "wmi:%pUL\n", wblock->gblock.guid); 6791caab3c1SMatthew Garrett } 680e80b89a5SGreg Kroah-Hartman static DEVICE_ATTR_RO(modalias); 681614ef432SDmitry Torokhov 682844af950SAndy Lutomirski static ssize_t guid_show(struct device *dev, struct device_attribute *attr, 683844af950SAndy Lutomirski char *buf) 684844af950SAndy Lutomirski { 685844af950SAndy Lutomirski struct wmi_block *wblock = dev_to_wblock(dev); 686844af950SAndy Lutomirski 687844af950SAndy Lutomirski return sprintf(buf, "%pUL\n", wblock->gblock.guid); 688844af950SAndy Lutomirski } 689844af950SAndy Lutomirski static DEVICE_ATTR_RO(guid); 690844af950SAndy Lutomirski 691d79b1074SAndy Lutomirski static ssize_t instance_count_show(struct device *dev, 692d79b1074SAndy Lutomirski struct device_attribute *attr, char *buf) 693d79b1074SAndy Lutomirski { 694d79b1074SAndy Lutomirski struct wmi_block *wblock = dev_to_wblock(dev); 695d79b1074SAndy Lutomirski 696d79b1074SAndy Lutomirski return sprintf(buf, "%d\n", (int)wblock->gblock.instance_count); 697d79b1074SAndy Lutomirski } 698d79b1074SAndy Lutomirski static DEVICE_ATTR_RO(instance_count); 699d79b1074SAndy Lutomirski 700d79b1074SAndy Lutomirski static ssize_t expensive_show(struct device *dev, 701d79b1074SAndy Lutomirski struct device_attribute *attr, char *buf) 702d79b1074SAndy Lutomirski { 703d79b1074SAndy Lutomirski struct wmi_block *wblock = dev_to_wblock(dev); 704d79b1074SAndy Lutomirski 705d79b1074SAndy Lutomirski return sprintf(buf, "%d\n", 706d79b1074SAndy Lutomirski (wblock->gblock.flags & ACPI_WMI_EXPENSIVE) != 0); 707d79b1074SAndy Lutomirski } 708d79b1074SAndy Lutomirski static DEVICE_ATTR_RO(expensive); 709d79b1074SAndy Lutomirski 710e80b89a5SGreg Kroah-Hartman static struct attribute *wmi_attrs[] = { 711e80b89a5SGreg Kroah-Hartman &dev_attr_modalias.attr, 712844af950SAndy Lutomirski &dev_attr_guid.attr, 713d79b1074SAndy Lutomirski &dev_attr_instance_count.attr, 714d79b1074SAndy Lutomirski &dev_attr_expensive.attr, 715e80b89a5SGreg Kroah-Hartman NULL, 716614ef432SDmitry Torokhov }; 717e80b89a5SGreg Kroah-Hartman ATTRIBUTE_GROUPS(wmi); 7181caab3c1SMatthew Garrett 719d79b1074SAndy Lutomirski static ssize_t notify_id_show(struct device *dev, struct device_attribute *attr, 720d79b1074SAndy Lutomirski char *buf) 721d79b1074SAndy Lutomirski { 722d79b1074SAndy Lutomirski struct wmi_block *wblock = dev_to_wblock(dev); 723d79b1074SAndy Lutomirski 724d79b1074SAndy Lutomirski return sprintf(buf, "%02X\n", (unsigned int)wblock->gblock.notify_id); 725d79b1074SAndy Lutomirski } 726d79b1074SAndy Lutomirski static DEVICE_ATTR_RO(notify_id); 727d79b1074SAndy Lutomirski 728d79b1074SAndy Lutomirski static struct attribute *wmi_event_attrs[] = { 729d79b1074SAndy Lutomirski &dev_attr_notify_id.attr, 730d79b1074SAndy Lutomirski NULL, 731d79b1074SAndy Lutomirski }; 732d79b1074SAndy Lutomirski ATTRIBUTE_GROUPS(wmi_event); 733d79b1074SAndy Lutomirski 734d79b1074SAndy Lutomirski static ssize_t object_id_show(struct device *dev, struct device_attribute *attr, 735d79b1074SAndy Lutomirski char *buf) 736d79b1074SAndy Lutomirski { 737d79b1074SAndy Lutomirski struct wmi_block *wblock = dev_to_wblock(dev); 738d79b1074SAndy Lutomirski 739d79b1074SAndy Lutomirski return sprintf(buf, "%c%c\n", wblock->gblock.object_id[0], 740d79b1074SAndy Lutomirski wblock->gblock.object_id[1]); 741d79b1074SAndy Lutomirski } 742d79b1074SAndy Lutomirski static DEVICE_ATTR_RO(object_id); 743d79b1074SAndy Lutomirski 744fd70da6aSDarren Hart (VMware) static ssize_t setable_show(struct device *dev, struct device_attribute *attr, 745d4fc91adSAndy Lutomirski char *buf) 746d4fc91adSAndy Lutomirski { 747d4fc91adSAndy Lutomirski struct wmi_device *wdev = dev_to_wdev(dev); 748d4fc91adSAndy Lutomirski 749fd70da6aSDarren Hart (VMware) return sprintf(buf, "%d\n", (int)wdev->setable); 750d4fc91adSAndy Lutomirski } 751fd70da6aSDarren Hart (VMware) static DEVICE_ATTR_RO(setable); 752d4fc91adSAndy Lutomirski 753d4fc91adSAndy Lutomirski static struct attribute *wmi_data_attrs[] = { 754d4fc91adSAndy Lutomirski &dev_attr_object_id.attr, 755fd70da6aSDarren Hart (VMware) &dev_attr_setable.attr, 756d4fc91adSAndy Lutomirski NULL, 757d4fc91adSAndy Lutomirski }; 758d4fc91adSAndy Lutomirski ATTRIBUTE_GROUPS(wmi_data); 759d4fc91adSAndy Lutomirski 760d4fc91adSAndy Lutomirski static struct attribute *wmi_method_attrs[] = { 761d79b1074SAndy Lutomirski &dev_attr_object_id.attr, 762d79b1074SAndy Lutomirski NULL, 763d79b1074SAndy Lutomirski }; 764d4fc91adSAndy Lutomirski ATTRIBUTE_GROUPS(wmi_method); 765d79b1074SAndy Lutomirski 7661caab3c1SMatthew Garrett static int wmi_dev_uevent(struct device *dev, struct kobj_uevent_env *env) 7671caab3c1SMatthew Garrett { 768844af950SAndy Lutomirski struct wmi_block *wblock = dev_to_wblock(dev); 7691caab3c1SMatthew Garrett 770844af950SAndy Lutomirski if (add_uevent_var(env, "MODALIAS=wmi:%pUL", wblock->gblock.guid)) 7711caab3c1SMatthew Garrett return -ENOMEM; 7721caab3c1SMatthew Garrett 773844af950SAndy Lutomirski if (add_uevent_var(env, "WMI_GUID=%pUL", wblock->gblock.guid)) 7741caab3c1SMatthew Garrett return -ENOMEM; 7751caab3c1SMatthew Garrett 7761caab3c1SMatthew Garrett return 0; 7771caab3c1SMatthew Garrett } 7781caab3c1SMatthew Garrett 779844af950SAndy Lutomirski static void wmi_dev_release(struct device *dev) 7801caab3c1SMatthew Garrett { 781844af950SAndy Lutomirski struct wmi_block *wblock = dev_to_wblock(dev); 782c64eefd4SDmitry Torokhov 783844af950SAndy Lutomirski kfree(wblock); 7841caab3c1SMatthew Garrett } 7851caab3c1SMatthew Garrett 786844af950SAndy Lutomirski static int wmi_dev_match(struct device *dev, struct device_driver *driver) 787844af950SAndy Lutomirski { 788844af950SAndy Lutomirski struct wmi_driver *wmi_driver = 789844af950SAndy Lutomirski container_of(driver, struct wmi_driver, driver); 790844af950SAndy Lutomirski struct wmi_block *wblock = dev_to_wblock(dev); 791844af950SAndy Lutomirski const struct wmi_device_id *id = wmi_driver->id_table; 792844af950SAndy Lutomirski 793c355ec65SMattias Jacobsson if (id == NULL) 794c355ec65SMattias Jacobsson return 0; 795c355ec65SMattias Jacobsson 796eacc95eaSMattias Jacobsson while (*id->guid_string) { 797844af950SAndy Lutomirski uuid_le driver_guid; 798844af950SAndy Lutomirski 799844af950SAndy Lutomirski if (WARN_ON(uuid_le_to_bin(id->guid_string, &driver_guid))) 800844af950SAndy Lutomirski continue; 801844af950SAndy Lutomirski if (!memcmp(&driver_guid, wblock->gblock.guid, 16)) 802844af950SAndy Lutomirski return 1; 803844af950SAndy Lutomirski 804844af950SAndy Lutomirski id++; 805844af950SAndy Lutomirski } 806844af950SAndy Lutomirski 807844af950SAndy Lutomirski return 0; 808844af950SAndy Lutomirski } 80944b6b766SMario Limonciello static int wmi_char_open(struct inode *inode, struct file *filp) 81044b6b766SMario Limonciello { 81144b6b766SMario Limonciello const char *driver_name = filp->f_path.dentry->d_iname; 81244b6b766SMario Limonciello struct wmi_block *wblock = NULL; 81344b6b766SMario Limonciello struct wmi_block *next = NULL; 81444b6b766SMario Limonciello 81544b6b766SMario Limonciello list_for_each_entry_safe(wblock, next, &wmi_block_list, list) { 81644b6b766SMario Limonciello if (!wblock->dev.dev.driver) 81744b6b766SMario Limonciello continue; 81844b6b766SMario Limonciello if (strcmp(driver_name, wblock->dev.dev.driver->name) == 0) { 81944b6b766SMario Limonciello filp->private_data = wblock; 82044b6b766SMario Limonciello break; 82144b6b766SMario Limonciello } 82244b6b766SMario Limonciello } 82344b6b766SMario Limonciello 82444b6b766SMario Limonciello if (!filp->private_data) 82544b6b766SMario Limonciello return -ENODEV; 82644b6b766SMario Limonciello 82744b6b766SMario Limonciello return nonseekable_open(inode, filp); 82844b6b766SMario Limonciello } 82944b6b766SMario Limonciello 83044b6b766SMario Limonciello static ssize_t wmi_char_read(struct file *filp, char __user *buffer, 83144b6b766SMario Limonciello size_t length, loff_t *offset) 83244b6b766SMario Limonciello { 83344b6b766SMario Limonciello struct wmi_block *wblock = filp->private_data; 83444b6b766SMario Limonciello 83544b6b766SMario Limonciello return simple_read_from_buffer(buffer, length, offset, 83644b6b766SMario Limonciello &wblock->req_buf_size, 83744b6b766SMario Limonciello sizeof(wblock->req_buf_size)); 83844b6b766SMario Limonciello } 83944b6b766SMario Limonciello 84044b6b766SMario Limonciello static long wmi_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) 84144b6b766SMario Limonciello { 84244b6b766SMario Limonciello struct wmi_ioctl_buffer __user *input = 84344b6b766SMario Limonciello (struct wmi_ioctl_buffer __user *) arg; 84444b6b766SMario Limonciello struct wmi_block *wblock = filp->private_data; 84544b6b766SMario Limonciello struct wmi_ioctl_buffer *buf = NULL; 84644b6b766SMario Limonciello struct wmi_driver *wdriver = NULL; 84744b6b766SMario Limonciello int ret; 84844b6b766SMario Limonciello 84944b6b766SMario Limonciello if (_IOC_TYPE(cmd) != WMI_IOC) 85044b6b766SMario Limonciello return -ENOTTY; 85144b6b766SMario Limonciello 85244b6b766SMario Limonciello /* make sure we're not calling a higher instance than exists*/ 85344b6b766SMario Limonciello if (_IOC_NR(cmd) >= wblock->gblock.instance_count) 85444b6b766SMario Limonciello return -EINVAL; 85544b6b766SMario Limonciello 85644b6b766SMario Limonciello mutex_lock(&wblock->char_mutex); 85744b6b766SMario Limonciello buf = wblock->handler_data; 85844b6b766SMario Limonciello if (get_user(buf->length, &input->length)) { 85944b6b766SMario Limonciello dev_dbg(&wblock->dev.dev, "Read length from user failed\n"); 86044b6b766SMario Limonciello ret = -EFAULT; 86144b6b766SMario Limonciello goto out_ioctl; 86244b6b766SMario Limonciello } 86344b6b766SMario Limonciello /* if it's too small, abort */ 86444b6b766SMario Limonciello if (buf->length < wblock->req_buf_size) { 86544b6b766SMario Limonciello dev_err(&wblock->dev.dev, 86644b6b766SMario Limonciello "Buffer %lld too small, need at least %lld\n", 86744b6b766SMario Limonciello buf->length, wblock->req_buf_size); 86844b6b766SMario Limonciello ret = -EINVAL; 86944b6b766SMario Limonciello goto out_ioctl; 87044b6b766SMario Limonciello } 87144b6b766SMario Limonciello /* if it's too big, warn, driver will only use what is needed */ 87244b6b766SMario Limonciello if (buf->length > wblock->req_buf_size) 87344b6b766SMario Limonciello dev_warn(&wblock->dev.dev, 87444b6b766SMario Limonciello "Buffer %lld is bigger than required %lld\n", 87544b6b766SMario Limonciello buf->length, wblock->req_buf_size); 87644b6b766SMario Limonciello 87744b6b766SMario Limonciello /* copy the structure from userspace */ 87844b6b766SMario Limonciello if (copy_from_user(buf, input, wblock->req_buf_size)) { 87944b6b766SMario Limonciello dev_dbg(&wblock->dev.dev, "Copy %llu from user failed\n", 88044b6b766SMario Limonciello wblock->req_buf_size); 88144b6b766SMario Limonciello ret = -EFAULT; 88244b6b766SMario Limonciello goto out_ioctl; 88344b6b766SMario Limonciello } 88444b6b766SMario Limonciello 88544b6b766SMario Limonciello /* let the driver do any filtering and do the call */ 88644b6b766SMario Limonciello wdriver = container_of(wblock->dev.dev.driver, 88744b6b766SMario Limonciello struct wmi_driver, driver); 8885e3e2297SMario Limonciello if (!try_module_get(wdriver->driver.owner)) { 8895e3e2297SMario Limonciello ret = -EBUSY; 8905e3e2297SMario Limonciello goto out_ioctl; 8915e3e2297SMario Limonciello } 89244b6b766SMario Limonciello ret = wdriver->filter_callback(&wblock->dev, cmd, buf); 89344b6b766SMario Limonciello module_put(wdriver->driver.owner); 89444b6b766SMario Limonciello if (ret) 89544b6b766SMario Limonciello goto out_ioctl; 89644b6b766SMario Limonciello 89744b6b766SMario Limonciello /* return the result (only up to our internal buffer size) */ 89844b6b766SMario Limonciello if (copy_to_user(input, buf, wblock->req_buf_size)) { 89944b6b766SMario Limonciello dev_dbg(&wblock->dev.dev, "Copy %llu to user failed\n", 90044b6b766SMario Limonciello wblock->req_buf_size); 90144b6b766SMario Limonciello ret = -EFAULT; 90244b6b766SMario Limonciello } 90344b6b766SMario Limonciello 90444b6b766SMario Limonciello out_ioctl: 90544b6b766SMario Limonciello mutex_unlock(&wblock->char_mutex); 90644b6b766SMario Limonciello return ret; 90744b6b766SMario Limonciello } 90844b6b766SMario Limonciello 90944b6b766SMario Limonciello static const struct file_operations wmi_fops = { 91044b6b766SMario Limonciello .owner = THIS_MODULE, 91144b6b766SMario Limonciello .read = wmi_char_read, 91244b6b766SMario Limonciello .open = wmi_char_open, 91344b6b766SMario Limonciello .unlocked_ioctl = wmi_ioctl, 9141832f2d8SArnd Bergmann .compat_ioctl = compat_ptr_ioctl, 91544b6b766SMario Limonciello }; 916844af950SAndy Lutomirski 917844af950SAndy Lutomirski static int wmi_dev_probe(struct device *dev) 918844af950SAndy Lutomirski { 919844af950SAndy Lutomirski struct wmi_block *wblock = dev_to_wblock(dev); 920844af950SAndy Lutomirski struct wmi_driver *wdriver = 921844af950SAndy Lutomirski container_of(dev->driver, struct wmi_driver, driver); 922844af950SAndy Lutomirski int ret = 0; 92344b6b766SMario Limonciello char *buf; 924844af950SAndy Lutomirski 925844af950SAndy Lutomirski if (ACPI_FAILURE(wmi_method_enable(wblock, 1))) 926844af950SAndy Lutomirski dev_warn(dev, "failed to enable device -- probing anyway\n"); 927844af950SAndy Lutomirski 928844af950SAndy Lutomirski if (wdriver->probe) { 929440c4983SMattias Jacobsson ret = wdriver->probe(dev_to_wdev(dev), 930440c4983SMattias Jacobsson find_guid_context(wblock, wdriver)); 93144b6b766SMario Limonciello if (ret != 0) 93244b6b766SMario Limonciello goto probe_failure; 933844af950SAndy Lutomirski } 934844af950SAndy Lutomirski 93544b6b766SMario Limonciello /* driver wants a character device made */ 93644b6b766SMario Limonciello if (wdriver->filter_callback) { 93744b6b766SMario Limonciello /* check that required buffer size declared by driver or MOF */ 93844b6b766SMario Limonciello if (!wblock->req_buf_size) { 93944b6b766SMario Limonciello dev_err(&wblock->dev.dev, 94044b6b766SMario Limonciello "Required buffer size not set\n"); 94144b6b766SMario Limonciello ret = -EINVAL; 94244b6b766SMario Limonciello goto probe_failure; 94344b6b766SMario Limonciello } 94444b6b766SMario Limonciello 9456fb74107SKees Cook wblock->handler_data = kmalloc(wblock->req_buf_size, 9466fb74107SKees Cook GFP_KERNEL); 94744b6b766SMario Limonciello if (!wblock->handler_data) { 94844b6b766SMario Limonciello ret = -ENOMEM; 94944b6b766SMario Limonciello goto probe_failure; 95044b6b766SMario Limonciello } 95144b6b766SMario Limonciello 9527f166addSAndy Shevchenko buf = kasprintf(GFP_KERNEL, "wmi/%s", wdriver->driver.name); 95344b6b766SMario Limonciello if (!buf) { 95444b6b766SMario Limonciello ret = -ENOMEM; 95544b6b766SMario Limonciello goto probe_string_failure; 95644b6b766SMario Limonciello } 95744b6b766SMario Limonciello wblock->char_dev.minor = MISC_DYNAMIC_MINOR; 95844b6b766SMario Limonciello wblock->char_dev.name = buf; 95944b6b766SMario Limonciello wblock->char_dev.fops = &wmi_fops; 96044b6b766SMario Limonciello wblock->char_dev.mode = 0444; 96144b6b766SMario Limonciello ret = misc_register(&wblock->char_dev); 96244b6b766SMario Limonciello if (ret) { 963501f7e52SJoe Perches dev_warn(dev, "failed to register char dev: %d\n", ret); 96444b6b766SMario Limonciello ret = -ENOMEM; 96544b6b766SMario Limonciello goto probe_misc_failure; 96644b6b766SMario Limonciello } 96744b6b766SMario Limonciello } 96844b6b766SMario Limonciello 96944b6b766SMario Limonciello return 0; 97044b6b766SMario Limonciello 97144b6b766SMario Limonciello probe_misc_failure: 97244b6b766SMario Limonciello kfree(buf); 97344b6b766SMario Limonciello probe_string_failure: 97444b6b766SMario Limonciello kfree(wblock->handler_data); 97544b6b766SMario Limonciello probe_failure: 97644b6b766SMario Limonciello if (ACPI_FAILURE(wmi_method_enable(wblock, 0))) 97744b6b766SMario Limonciello dev_warn(dev, "failed to disable device\n"); 978844af950SAndy Lutomirski return ret; 979844af950SAndy Lutomirski } 980844af950SAndy Lutomirski 981844af950SAndy Lutomirski static int wmi_dev_remove(struct device *dev) 982844af950SAndy Lutomirski { 983844af950SAndy Lutomirski struct wmi_block *wblock = dev_to_wblock(dev); 984844af950SAndy Lutomirski struct wmi_driver *wdriver = 985844af950SAndy Lutomirski container_of(dev->driver, struct wmi_driver, driver); 986844af950SAndy Lutomirski int ret = 0; 987844af950SAndy Lutomirski 98844b6b766SMario Limonciello if (wdriver->filter_callback) { 98944b6b766SMario Limonciello misc_deregister(&wblock->char_dev); 99044b6b766SMario Limonciello kfree(wblock->char_dev.name); 9916fb74107SKees Cook kfree(wblock->handler_data); 99244b6b766SMario Limonciello } 99344b6b766SMario Limonciello 994844af950SAndy Lutomirski if (wdriver->remove) 995844af950SAndy Lutomirski ret = wdriver->remove(dev_to_wdev(dev)); 996844af950SAndy Lutomirski 997844af950SAndy Lutomirski if (ACPI_FAILURE(wmi_method_enable(wblock, 0))) 998844af950SAndy Lutomirski dev_warn(dev, "failed to disable device\n"); 999844af950SAndy Lutomirski 1000844af950SAndy Lutomirski return ret; 1001844af950SAndy Lutomirski } 1002844af950SAndy Lutomirski 1003844af950SAndy Lutomirski static struct class wmi_bus_class = { 1004844af950SAndy Lutomirski .name = "wmi_bus", 10051caab3c1SMatthew Garrett }; 10061caab3c1SMatthew Garrett 1007844af950SAndy Lutomirski static struct bus_type wmi_bus_type = { 1008844af950SAndy Lutomirski .name = "wmi", 1009844af950SAndy Lutomirski .dev_groups = wmi_groups, 1010844af950SAndy Lutomirski .match = wmi_dev_match, 1011844af950SAndy Lutomirski .uevent = wmi_dev_uevent, 1012844af950SAndy Lutomirski .probe = wmi_dev_probe, 1013844af950SAndy Lutomirski .remove = wmi_dev_remove, 1014844af950SAndy Lutomirski }; 1015844af950SAndy Lutomirski 101669372c1dSBhumika Goyal static const struct device_type wmi_type_event = { 1017d79b1074SAndy Lutomirski .name = "event", 1018d79b1074SAndy Lutomirski .groups = wmi_event_groups, 1019d79b1074SAndy Lutomirski .release = wmi_dev_release, 1020d79b1074SAndy Lutomirski }; 1021d79b1074SAndy Lutomirski 102269372c1dSBhumika Goyal static const struct device_type wmi_type_method = { 1023d79b1074SAndy Lutomirski .name = "method", 1024d4fc91adSAndy Lutomirski .groups = wmi_method_groups, 1025d79b1074SAndy Lutomirski .release = wmi_dev_release, 1026d79b1074SAndy Lutomirski }; 1027d79b1074SAndy Lutomirski 102869372c1dSBhumika Goyal static const struct device_type wmi_type_data = { 1029d79b1074SAndy Lutomirski .name = "data", 1030d4fc91adSAndy Lutomirski .groups = wmi_data_groups, 1031d79b1074SAndy Lutomirski .release = wmi_dev_release, 1032d79b1074SAndy Lutomirski }; 1033d79b1074SAndy Lutomirski 1034fd70da6aSDarren Hart (VMware) static int wmi_create_device(struct device *wmi_bus_dev, 1035844af950SAndy Lutomirski const struct guid_block *gblock, 10367f5809bfSAndy Lutomirski struct wmi_block *wblock, 10377f5809bfSAndy Lutomirski struct acpi_device *device) 10381caab3c1SMatthew Garrett { 1039d4fc91adSAndy Lutomirski struct acpi_device_info *info; 1040d4fc91adSAndy Lutomirski char method[5]; 1041d4fc91adSAndy Lutomirski int result; 1042d4fc91adSAndy Lutomirski 1043fd70da6aSDarren Hart (VMware) if (gblock->flags & ACPI_WMI_EVENT) { 1044fd70da6aSDarren Hart (VMware) wblock->dev.dev.type = &wmi_type_event; 1045fd70da6aSDarren Hart (VMware) goto out_init; 1046fd70da6aSDarren Hart (VMware) } 1047d4fc91adSAndy Lutomirski 1048fd70da6aSDarren Hart (VMware) if (gblock->flags & ACPI_WMI_METHOD) { 1049fd70da6aSDarren Hart (VMware) wblock->dev.dev.type = &wmi_type_method; 105044b6b766SMario Limonciello mutex_init(&wblock->char_mutex); 1051fd70da6aSDarren Hart (VMware) goto out_init; 1052fd70da6aSDarren Hart (VMware) } 1053fd70da6aSDarren Hart (VMware) 1054fd70da6aSDarren Hart (VMware) /* 1055fd70da6aSDarren Hart (VMware) * Data Block Query Control Method (WQxx by convention) is 1056fd70da6aSDarren Hart (VMware) * required per the WMI documentation. If it is not present, 1057fd70da6aSDarren Hart (VMware) * we ignore this data block. 1058fd70da6aSDarren Hart (VMware) */ 1059d4fc91adSAndy Lutomirski strcpy(method, "WQ"); 1060d4fc91adSAndy Lutomirski strncat(method, wblock->gblock.object_id, 2); 1061d4fc91adSAndy Lutomirski result = get_subobj_info(device->handle, method, &info); 1062d4fc91adSAndy Lutomirski 1063fd70da6aSDarren Hart (VMware) if (result) { 1064fd70da6aSDarren Hart (VMware) dev_warn(wmi_bus_dev, 1065501f7e52SJoe Perches "%s data block query control method not found\n", 1066fd70da6aSDarren Hart (VMware) method); 1067fd70da6aSDarren Hart (VMware) return result; 1068fd70da6aSDarren Hart (VMware) } 1069fd70da6aSDarren Hart (VMware) 1070fd70da6aSDarren Hart (VMware) wblock->dev.dev.type = &wmi_type_data; 1071d4fc91adSAndy Lutomirski 1072d4fc91adSAndy Lutomirski /* 1073d4fc91adSAndy Lutomirski * The Microsoft documentation specifically states: 1074d4fc91adSAndy Lutomirski * 1075d4fc91adSAndy Lutomirski * Data blocks registered with only a single instance 1076d4fc91adSAndy Lutomirski * can ignore the parameter. 1077d4fc91adSAndy Lutomirski * 1078fd70da6aSDarren Hart (VMware) * ACPICA will get mad at us if we call the method with the wrong number 1079fd70da6aSDarren Hart (VMware) * of arguments, so check what our method expects. (On some Dell 1080fd70da6aSDarren Hart (VMware) * laptops, WQxx may not be a method at all.) 1081d4fc91adSAndy Lutomirski */ 1082fd70da6aSDarren Hart (VMware) if (info->type != ACPI_TYPE_METHOD || info->param_count == 0) 1083d4fc91adSAndy Lutomirski wblock->read_takes_no_args = true; 1084d4fc91adSAndy Lutomirski 1085d4fc91adSAndy Lutomirski kfree(info); 1086d4fc91adSAndy Lutomirski 1087d4fc91adSAndy Lutomirski strcpy(method, "WS"); 1088d4fc91adSAndy Lutomirski strncat(method, wblock->gblock.object_id, 2); 1089d4fc91adSAndy Lutomirski result = get_subobj_info(device->handle, method, NULL); 1090d4fc91adSAndy Lutomirski 1091fd70da6aSDarren Hart (VMware) if (result == 0) 1092fd70da6aSDarren Hart (VMware) wblock->dev.setable = true; 1093d4fc91adSAndy Lutomirski 1094fd70da6aSDarren Hart (VMware) out_init: 1095fd70da6aSDarren Hart (VMware) wblock->dev.dev.bus = &wmi_bus_type; 1096fd70da6aSDarren Hart (VMware) wblock->dev.dev.parent = wmi_bus_dev; 1097fd70da6aSDarren Hart (VMware) 1098fd70da6aSDarren Hart (VMware) dev_set_name(&wblock->dev.dev, "%pUL", gblock->guid); 1099c64eefd4SDmitry Torokhov 11006ee50aaaSDarren Hart (VMware) device_initialize(&wblock->dev.dev); 1101fd70da6aSDarren Hart (VMware) 1102fd70da6aSDarren Hart (VMware) return 0; 11031caab3c1SMatthew Garrett } 11041caab3c1SMatthew Garrett 1105b0e86302SAndy Lutomirski static void wmi_free_devices(struct acpi_device *device) 11061caab3c1SMatthew Garrett { 1107c64eefd4SDmitry Torokhov struct wmi_block *wblock, *next; 11081caab3c1SMatthew Garrett 11091caab3c1SMatthew Garrett /* Delete devices for all the GUIDs */ 1110023b9565SDmitry Torokhov list_for_each_entry_safe(wblock, next, &wmi_block_list, list) { 1111b0e86302SAndy Lutomirski if (wblock->acpi_device == device) { 1112023b9565SDmitry Torokhov list_del(&wblock->list); 1113844af950SAndy Lutomirski device_unregister(&wblock->dev.dev); 1114023b9565SDmitry Torokhov } 11151caab3c1SMatthew Garrett } 1116b0e86302SAndy Lutomirski } 11171caab3c1SMatthew Garrett 1118b0e86302SAndy Lutomirski static bool guid_already_parsed(struct acpi_device *device, 1119b0e86302SAndy Lutomirski const u8 *guid) 1120d1f9e497SCarlos Corbacho { 1121d1f9e497SCarlos Corbacho struct wmi_block *wblock; 1122d1f9e497SCarlos Corbacho 1123b0e86302SAndy Lutomirski list_for_each_entry(wblock, &wmi_block_list, list) { 1124b0e86302SAndy Lutomirski if (memcmp(wblock->gblock.guid, guid, 16) == 0) { 1125b0e86302SAndy Lutomirski /* 1126b0e86302SAndy Lutomirski * Because we historically didn't track the relationship 1127b0e86302SAndy Lutomirski * between GUIDs and ACPI nodes, we don't know whether 1128b0e86302SAndy Lutomirski * we need to suppress GUIDs that are unique on a 1129b0e86302SAndy Lutomirski * given node but duplicated across nodes. 1130b0e86302SAndy Lutomirski */ 1131b0e86302SAndy Lutomirski dev_warn(&device->dev, "duplicate WMI GUID %pUL (first instance was on %s)\n", 1132b0e86302SAndy Lutomirski guid, dev_name(&wblock->acpi_device->dev)); 1133d1f9e497SCarlos Corbacho return true; 1134b0e86302SAndy Lutomirski } 1135b0e86302SAndy Lutomirski } 1136c64eefd4SDmitry Torokhov 1137d1f9e497SCarlos Corbacho return false; 1138d1f9e497SCarlos Corbacho } 1139d1f9e497SCarlos Corbacho 11401caab3c1SMatthew Garrett /* 1141b4f9fe12SLen Brown * Parse the _WDG method for the GUID data blocks 1142b4f9fe12SLen Brown */ 1143844af950SAndy Lutomirski static int parse_wdg(struct device *wmi_bus_dev, struct acpi_device *device) 1144b4f9fe12SLen Brown { 1145b4f9fe12SLen Brown struct acpi_buffer out = {ACPI_ALLOCATE_BUFFER, NULL}; 114637830662SDmitry Torokhov const struct guid_block *gblock; 11476ee50aaaSDarren Hart (VMware) struct wmi_block *wblock, *next; 11486ee50aaaSDarren Hart (VMware) union acpi_object *obj; 1149b4f9fe12SLen Brown acpi_status status; 11506ee50aaaSDarren Hart (VMware) int retval = 0; 1151b4f9fe12SLen Brown u32 i, total; 1152b4f9fe12SLen Brown 11537f5809bfSAndy Lutomirski status = acpi_evaluate_object(device->handle, "_WDG", NULL, &out); 1154b4f9fe12SLen Brown if (ACPI_FAILURE(status)) 1155c64eefd4SDmitry Torokhov return -ENXIO; 1156b4f9fe12SLen Brown 1157b4f9fe12SLen Brown obj = (union acpi_object *) out.pointer; 11583d2c63ebSDmitry Torokhov if (!obj) 1159c64eefd4SDmitry Torokhov return -ENXIO; 1160b4f9fe12SLen Brown 116164ed0ab8SDmitry Torokhov if (obj->type != ACPI_TYPE_BUFFER) { 1162c64eefd4SDmitry Torokhov retval = -ENXIO; 116364ed0ab8SDmitry Torokhov goto out_free_pointer; 116464ed0ab8SDmitry Torokhov } 1165b4f9fe12SLen Brown 116637830662SDmitry Torokhov gblock = (const struct guid_block *)obj->buffer.pointer; 1167b4f9fe12SLen Brown total = obj->buffer.length / sizeof(struct guid_block); 1168b4f9fe12SLen Brown 1169b4f9fe12SLen Brown for (i = 0; i < total; i++) { 1170a929aae0SThomas Renninger if (debug_dump_wdg) 1171a929aae0SThomas Renninger wmi_dump_wdg(&gblock[i]); 1172a929aae0SThomas Renninger 1173a1c31bcdSAndy Lutomirski /* 1174a1c31bcdSAndy Lutomirski * Some WMI devices, like those for nVidia hooks, have a 1175a1c31bcdSAndy Lutomirski * duplicate GUID. It's not clear what we should do in this 1176a1c31bcdSAndy Lutomirski * case yet, so for now, we'll just ignore the duplicate 1177a1c31bcdSAndy Lutomirski * for device creation. 1178a1c31bcdSAndy Lutomirski */ 1179a1c31bcdSAndy Lutomirski if (guid_already_parsed(device, gblock[i].guid)) 1180a1c31bcdSAndy Lutomirski continue; 1181a1c31bcdSAndy Lutomirski 118258f6425eSColin King wblock = kzalloc(sizeof(struct wmi_block), GFP_KERNEL); 11836ee50aaaSDarren Hart (VMware) if (!wblock) { 11846ee50aaaSDarren Hart (VMware) retval = -ENOMEM; 11856ee50aaaSDarren Hart (VMware) break; 11866ee50aaaSDarren Hart (VMware) } 118758f6425eSColin King 1188b0e86302SAndy Lutomirski wblock->acpi_device = device; 118958f6425eSColin King wblock->gblock = gblock[i]; 119058f6425eSColin King 1191fd70da6aSDarren Hart (VMware) retval = wmi_create_device(wmi_bus_dev, &gblock[i], wblock, device); 1192fd70da6aSDarren Hart (VMware) if (retval) { 1193fd70da6aSDarren Hart (VMware) kfree(wblock); 1194fd70da6aSDarren Hart (VMware) continue; 1195fd70da6aSDarren Hart (VMware) } 119658f6425eSColin King 119758f6425eSColin King list_add_tail(&wblock->list, &wmi_block_list); 1198b4f9fe12SLen Brown 1199fc3155b2SThomas Renninger if (debug_event) { 1200fc3155b2SThomas Renninger wblock->handler = wmi_notify_debug; 12012d5ab555SDmitry Torokhov wmi_method_enable(wblock, 1); 1202fc3155b2SThomas Renninger } 1203b4f9fe12SLen Brown } 1204b4f9fe12SLen Brown 12056ee50aaaSDarren Hart (VMware) /* 12066ee50aaaSDarren Hart (VMware) * Now that all of the devices are created, add them to the 12076ee50aaaSDarren Hart (VMware) * device tree and probe subdrivers. 12086ee50aaaSDarren Hart (VMware) */ 12096ee50aaaSDarren Hart (VMware) list_for_each_entry_safe(wblock, next, &wmi_block_list, list) { 12106ee50aaaSDarren Hart (VMware) if (wblock->acpi_device != device) 12116ee50aaaSDarren Hart (VMware) continue; 12126ee50aaaSDarren Hart (VMware) 12136ee50aaaSDarren Hart (VMware) retval = device_add(&wblock->dev.dev); 12146ee50aaaSDarren Hart (VMware) if (retval) { 1215501f7e52SJoe Perches dev_err(wmi_bus_dev, "failed to register %pUL\n", 12166ee50aaaSDarren Hart (VMware) wblock->gblock.guid); 12176ee50aaaSDarren Hart (VMware) if (debug_event) 12186ee50aaaSDarren Hart (VMware) wmi_method_enable(wblock, 0); 12196ee50aaaSDarren Hart (VMware) list_del(&wblock->list); 12206ee50aaaSDarren Hart (VMware) put_device(&wblock->dev.dev); 12216ee50aaaSDarren Hart (VMware) } 12226ee50aaaSDarren Hart (VMware) } 1223c64eefd4SDmitry Torokhov 1224a5167c5bSAxel Lin out_free_pointer: 1225a5167c5bSAxel Lin kfree(out.pointer); 1226c64eefd4SDmitry Torokhov return retval; 1227b4f9fe12SLen Brown } 1228b4f9fe12SLen Brown 1229b4f9fe12SLen Brown /* 1230b4f9fe12SLen Brown * WMI can have EmbeddedControl access regions. In which case, we just want to 1231b4f9fe12SLen Brown * hand these off to the EC driver. 1232b4f9fe12SLen Brown */ 1233b4f9fe12SLen Brown static acpi_status 1234b4f9fe12SLen Brown acpi_wmi_ec_space_handler(u32 function, acpi_physical_address address, 1235439913ffSLin Ming u32 bits, u64 *value, 1236b4f9fe12SLen Brown void *handler_context, void *region_context) 1237b4f9fe12SLen Brown { 1238b4f9fe12SLen Brown int result = 0, i = 0; 1239b4f9fe12SLen Brown u8 temp = 0; 1240b4f9fe12SLen Brown 1241b4f9fe12SLen Brown if ((address > 0xFF) || !value) 1242b4f9fe12SLen Brown return AE_BAD_PARAMETER; 1243b4f9fe12SLen Brown 1244b4f9fe12SLen Brown if (function != ACPI_READ && function != ACPI_WRITE) 1245b4f9fe12SLen Brown return AE_BAD_PARAMETER; 1246b4f9fe12SLen Brown 1247b4f9fe12SLen Brown if (bits != 8) 1248b4f9fe12SLen Brown return AE_BAD_PARAMETER; 1249b4f9fe12SLen Brown 1250b4f9fe12SLen Brown if (function == ACPI_READ) { 1251b4f9fe12SLen Brown result = ec_read(address, &temp); 1252439913ffSLin Ming (*value) |= ((u64)temp) << i; 1253b4f9fe12SLen Brown } else { 1254b4f9fe12SLen Brown temp = 0xff & ((*value) >> i); 1255b4f9fe12SLen Brown result = ec_write(address, temp); 1256b4f9fe12SLen Brown } 1257b4f9fe12SLen Brown 1258b4f9fe12SLen Brown switch (result) { 1259b4f9fe12SLen Brown case -EINVAL: 1260b4f9fe12SLen Brown return AE_BAD_PARAMETER; 1261b4f9fe12SLen Brown break; 1262b4f9fe12SLen Brown case -ENODEV: 1263b4f9fe12SLen Brown return AE_NOT_FOUND; 1264b4f9fe12SLen Brown break; 1265b4f9fe12SLen Brown case -ETIME: 1266b4f9fe12SLen Brown return AE_TIME; 1267b4f9fe12SLen Brown break; 1268b4f9fe12SLen Brown default: 1269b4f9fe12SLen Brown return AE_OK; 1270b4f9fe12SLen Brown } 1271b4f9fe12SLen Brown } 1272b4f9fe12SLen Brown 12731686f544SAndy Lutomirski static void acpi_wmi_notify_handler(acpi_handle handle, u32 event, 12741686f544SAndy Lutomirski void *context) 1275b4f9fe12SLen Brown { 1276b4f9fe12SLen Brown struct guid_block *block; 1277b4f9fe12SLen Brown struct wmi_block *wblock; 12781686f544SAndy Lutomirski bool found_it = false; 1279b4f9fe12SLen Brown 1280cedb3b2aSAndy Shevchenko list_for_each_entry(wblock, &wmi_block_list, list) { 1281b4f9fe12SLen Brown block = &wblock->gblock; 1282b4f9fe12SLen Brown 12831686f544SAndy Lutomirski if (wblock->acpi_device->handle == handle && 1284b0e86302SAndy Lutomirski (block->flags & ACPI_WMI_EVENT) && 12851686f544SAndy Lutomirski (block->notify_id == event)) 12861686f544SAndy Lutomirski { 12871686f544SAndy Lutomirski found_it = true; 12881686f544SAndy Lutomirski break; 12891686f544SAndy Lutomirski } 12901686f544SAndy Lutomirski } 12911686f544SAndy Lutomirski 12921686f544SAndy Lutomirski if (!found_it) 12931686f544SAndy Lutomirski return; 12941686f544SAndy Lutomirski 12951686f544SAndy Lutomirski /* If a driver is bound, then notify the driver. */ 12961686f544SAndy Lutomirski if (wblock->dev.dev.driver) { 12971686f544SAndy Lutomirski struct wmi_driver *driver; 12981686f544SAndy Lutomirski struct acpi_object_list input; 12991686f544SAndy Lutomirski union acpi_object params[1]; 13001686f544SAndy Lutomirski struct acpi_buffer evdata = { ACPI_ALLOCATE_BUFFER, NULL }; 13011686f544SAndy Lutomirski acpi_status status; 13021686f544SAndy Lutomirski 13031686f544SAndy Lutomirski driver = container_of(wblock->dev.dev.driver, 13041686f544SAndy Lutomirski struct wmi_driver, driver); 13051686f544SAndy Lutomirski 13061686f544SAndy Lutomirski input.count = 1; 13071686f544SAndy Lutomirski input.pointer = params; 13081686f544SAndy Lutomirski params[0].type = ACPI_TYPE_INTEGER; 13091686f544SAndy Lutomirski params[0].integer.value = event; 13101686f544SAndy Lutomirski 13111686f544SAndy Lutomirski status = acpi_evaluate_object(wblock->acpi_device->handle, 13121686f544SAndy Lutomirski "_WED", &input, &evdata); 13131686f544SAndy Lutomirski if (ACPI_FAILURE(status)) { 13141686f544SAndy Lutomirski dev_warn(&wblock->dev.dev, 13151686f544SAndy Lutomirski "failed to get event data\n"); 13161686f544SAndy Lutomirski return; 13171686f544SAndy Lutomirski } 13181686f544SAndy Lutomirski 13191686f544SAndy Lutomirski if (driver->notify) 13201686f544SAndy Lutomirski driver->notify(&wblock->dev, 13211686f544SAndy Lutomirski (union acpi_object *)evdata.pointer); 13221686f544SAndy Lutomirski 13231686f544SAndy Lutomirski kfree(evdata.pointer); 13241686f544SAndy Lutomirski } else if (wblock->handler) { 13251686f544SAndy Lutomirski /* Legacy handler */ 1326b4f9fe12SLen Brown wblock->handler(event, wblock->handler_data); 13271686f544SAndy Lutomirski } 13281686f544SAndy Lutomirski 13297715348cSThomas Renninger if (debug_event) { 133085b4e4ebSRasmus Villemoes pr_info("DEBUG Event GUID: %pUL\n", 133185b4e4ebSRasmus Villemoes wblock->gblock.guid); 13327715348cSThomas Renninger } 1333b4f9fe12SLen Brown 1334b4f9fe12SLen Brown acpi_bus_generate_netlink_event( 13351686f544SAndy Lutomirski wblock->acpi_device->pnp.device_class, 13361686f544SAndy Lutomirski dev_name(&wblock->dev.dev), 1337b4f9fe12SLen Brown event, 0); 13381686f544SAndy Lutomirski 1339b4f9fe12SLen Brown } 1340b4f9fe12SLen Brown 13419599ed91SAndy Lutomirski static int acpi_wmi_remove(struct platform_device *device) 1342b4f9fe12SLen Brown { 13439599ed91SAndy Lutomirski struct acpi_device *acpi_device = ACPI_COMPANION(&device->dev); 13449599ed91SAndy Lutomirski 13459599ed91SAndy Lutomirski acpi_remove_notify_handler(acpi_device->handle, ACPI_DEVICE_NOTIFY, 13461686f544SAndy Lutomirski acpi_wmi_notify_handler); 13479599ed91SAndy Lutomirski acpi_remove_address_space_handler(acpi_device->handle, 1348b4f9fe12SLen Brown ACPI_ADR_SPACE_EC, &acpi_wmi_ec_space_handler); 13499599ed91SAndy Lutomirski wmi_free_devices(acpi_device); 13507b11e898SMario Limonciello device_destroy(&wmi_bus_class, MKDEV(0, 0)); 1351b4f9fe12SLen Brown 1352b4f9fe12SLen Brown return 0; 1353b4f9fe12SLen Brown } 1354b4f9fe12SLen Brown 13559599ed91SAndy Lutomirski static int acpi_wmi_probe(struct platform_device *device) 1356b4f9fe12SLen Brown { 13579599ed91SAndy Lutomirski struct acpi_device *acpi_device; 1358844af950SAndy Lutomirski struct device *wmi_bus_dev; 1359b4f9fe12SLen Brown acpi_status status; 1360c64eefd4SDmitry Torokhov int error; 1361b4f9fe12SLen Brown 13629599ed91SAndy Lutomirski acpi_device = ACPI_COMPANION(&device->dev); 13639599ed91SAndy Lutomirski if (!acpi_device) { 13649599ed91SAndy Lutomirski dev_err(&device->dev, "ACPI companion is missing\n"); 13659599ed91SAndy Lutomirski return -ENODEV; 13669599ed91SAndy Lutomirski } 13679599ed91SAndy Lutomirski 13689599ed91SAndy Lutomirski status = acpi_install_address_space_handler(acpi_device->handle, 1369b4f9fe12SLen Brown ACPI_ADR_SPACE_EC, 1370b4f9fe12SLen Brown &acpi_wmi_ec_space_handler, 1371b4f9fe12SLen Brown NULL, NULL); 13725212cd67SDmitry Torokhov if (ACPI_FAILURE(status)) { 137346492ee4SAndy Lutomirski dev_err(&device->dev, "Error installing EC region handler\n"); 1374b4f9fe12SLen Brown return -ENODEV; 13755212cd67SDmitry Torokhov } 1376b4f9fe12SLen Brown 13779599ed91SAndy Lutomirski status = acpi_install_notify_handler(acpi_device->handle, 13789599ed91SAndy Lutomirski ACPI_DEVICE_NOTIFY, 13791686f544SAndy Lutomirski acpi_wmi_notify_handler, 13801686f544SAndy Lutomirski NULL); 13811686f544SAndy Lutomirski if (ACPI_FAILURE(status)) { 13821686f544SAndy Lutomirski dev_err(&device->dev, "Error installing notify handler\n"); 13831686f544SAndy Lutomirski error = -ENODEV; 13841686f544SAndy Lutomirski goto err_remove_ec_handler; 13851686f544SAndy Lutomirski } 13861686f544SAndy Lutomirski 1387844af950SAndy Lutomirski wmi_bus_dev = device_create(&wmi_bus_class, &device->dev, MKDEV(0, 0), 1388844af950SAndy Lutomirski NULL, "wmi_bus-%s", dev_name(&device->dev)); 1389844af950SAndy Lutomirski if (IS_ERR(wmi_bus_dev)) { 1390844af950SAndy Lutomirski error = PTR_ERR(wmi_bus_dev); 13911686f544SAndy Lutomirski goto err_remove_notify_handler; 1392844af950SAndy Lutomirski } 13939599ed91SAndy Lutomirski dev_set_drvdata(&device->dev, wmi_bus_dev); 1394844af950SAndy Lutomirski 13959599ed91SAndy Lutomirski error = parse_wdg(wmi_bus_dev, acpi_device); 1396c64eefd4SDmitry Torokhov if (error) { 13978e07514dSDmitry Torokhov pr_err("Failed to parse WDG method\n"); 1398844af950SAndy Lutomirski goto err_remove_busdev; 1399b4f9fe12SLen Brown } 1400b4f9fe12SLen Brown 1401c64eefd4SDmitry Torokhov return 0; 140246492ee4SAndy Lutomirski 1403844af950SAndy Lutomirski err_remove_busdev: 14047b11e898SMario Limonciello device_destroy(&wmi_bus_class, MKDEV(0, 0)); 1405844af950SAndy Lutomirski 14061686f544SAndy Lutomirski err_remove_notify_handler: 14079599ed91SAndy Lutomirski acpi_remove_notify_handler(acpi_device->handle, ACPI_DEVICE_NOTIFY, 14081686f544SAndy Lutomirski acpi_wmi_notify_handler); 14091686f544SAndy Lutomirski 14101686f544SAndy Lutomirski err_remove_ec_handler: 14119599ed91SAndy Lutomirski acpi_remove_address_space_handler(acpi_device->handle, 141246492ee4SAndy Lutomirski ACPI_ADR_SPACE_EC, 141346492ee4SAndy Lutomirski &acpi_wmi_ec_space_handler); 141446492ee4SAndy Lutomirski 141546492ee4SAndy Lutomirski return error; 1416b4f9fe12SLen Brown } 1417b4f9fe12SLen Brown 1418844af950SAndy Lutomirski int __must_check __wmi_driver_register(struct wmi_driver *driver, 1419844af950SAndy Lutomirski struct module *owner) 1420844af950SAndy Lutomirski { 1421844af950SAndy Lutomirski driver->driver.owner = owner; 1422844af950SAndy Lutomirski driver->driver.bus = &wmi_bus_type; 1423844af950SAndy Lutomirski 1424844af950SAndy Lutomirski return driver_register(&driver->driver); 1425844af950SAndy Lutomirski } 1426844af950SAndy Lutomirski EXPORT_SYMBOL(__wmi_driver_register); 1427844af950SAndy Lutomirski 1428844af950SAndy Lutomirski void wmi_driver_unregister(struct wmi_driver *driver) 1429844af950SAndy Lutomirski { 1430844af950SAndy Lutomirski driver_unregister(&driver->driver); 1431844af950SAndy Lutomirski } 1432844af950SAndy Lutomirski EXPORT_SYMBOL(wmi_driver_unregister); 1433844af950SAndy Lutomirski 1434b4f9fe12SLen Brown static int __init acpi_wmi_init(void) 1435b4f9fe12SLen Brown { 1436c64eefd4SDmitry Torokhov int error; 1437b4f9fe12SLen Brown 1438b4f9fe12SLen Brown if (acpi_disabled) 1439b4f9fe12SLen Brown return -ENODEV; 1440b4f9fe12SLen Brown 1441844af950SAndy Lutomirski error = class_register(&wmi_bus_class); 1442c64eefd4SDmitry Torokhov if (error) 1443c64eefd4SDmitry Torokhov return error; 1444b4f9fe12SLen Brown 1445844af950SAndy Lutomirski error = bus_register(&wmi_bus_type); 1446844af950SAndy Lutomirski if (error) 1447844af950SAndy Lutomirski goto err_unreg_class; 1448844af950SAndy Lutomirski 14499599ed91SAndy Lutomirski error = platform_driver_register(&acpi_wmi_driver); 1450c64eefd4SDmitry Torokhov if (error) { 1451c64eefd4SDmitry Torokhov pr_err("Error loading mapper\n"); 1452844af950SAndy Lutomirski goto err_unreg_bus; 14531caab3c1SMatthew Garrett } 14541caab3c1SMatthew Garrett 14558e07514dSDmitry Torokhov return 0; 1456844af950SAndy Lutomirski 1457844af950SAndy Lutomirski err_unreg_bus: 1458844af950SAndy Lutomirski bus_unregister(&wmi_bus_type); 1459844af950SAndy Lutomirski 146097277717SAlexey Khoroshilov err_unreg_class: 146197277717SAlexey Khoroshilov class_unregister(&wmi_bus_class); 146297277717SAlexey Khoroshilov 1463844af950SAndy Lutomirski return error; 1464b4f9fe12SLen Brown } 1465b4f9fe12SLen Brown 1466b4f9fe12SLen Brown static void __exit acpi_wmi_exit(void) 1467b4f9fe12SLen Brown { 14689599ed91SAndy Lutomirski platform_driver_unregister(&acpi_wmi_driver); 1469844af950SAndy Lutomirski bus_unregister(&wmi_bus_type); 1470303d1fccSMario Limonciello class_unregister(&wmi_bus_class); 1471b4f9fe12SLen Brown } 1472b4f9fe12SLen Brown 147398b8e4e5SRafael J. Wysocki subsys_initcall_sync(acpi_wmi_init); 1474b4f9fe12SLen Brown module_exit(acpi_wmi_exit); 1475