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> 20*1c95ace7SBarnabás Pőcze #include <linux/bits.h> 21b60ee4e0SMario Limonciello #include <linux/device.h> 22b60ee4e0SMario Limonciello #include <linux/init.h> 23b60ee4e0SMario Limonciello #include <linux/kernel.h> 24b60ee4e0SMario Limonciello #include <linux/list.h> 2544b6b766SMario Limonciello #include <linux/miscdevice.h> 267c52d551SPaul Gortmaker #include <linux/module.h> 279599ed91SAndy Lutomirski #include <linux/platform_device.h> 28b60ee4e0SMario Limonciello #include <linux/slab.h> 29b60ee4e0SMario Limonciello #include <linux/types.h> 3044b6b766SMario Limonciello #include <linux/uaccess.h> 31538d7eb8SAndy Shevchenko #include <linux/uuid.h> 32b60ee4e0SMario Limonciello #include <linux/wmi.h> 33df23e2beSPeter Zijlstra #include <linux/fs.h> 3444b6b766SMario Limonciello #include <uapi/linux/wmi.h> 35b4f9fe12SLen Brown 36b4f9fe12SLen Brown MODULE_AUTHOR("Carlos Corbacho"); 37b4f9fe12SLen Brown MODULE_DESCRIPTION("ACPI-WMI Mapping Driver"); 38b4f9fe12SLen Brown MODULE_LICENSE("GPL"); 39b4f9fe12SLen Brown 40762e1a2fSDmitry Torokhov static LIST_HEAD(wmi_block_list); 41b4f9fe12SLen Brown 42b4f9fe12SLen Brown struct guid_block { 43b4f9fe12SLen Brown char guid[16]; 44b4f9fe12SLen Brown union { 45b4f9fe12SLen Brown char object_id[2]; 46b4f9fe12SLen Brown struct { 47b4f9fe12SLen Brown unsigned char notify_id; 48b4f9fe12SLen Brown unsigned char reserved; 49b4f9fe12SLen Brown }; 50b4f9fe12SLen Brown }; 51b4f9fe12SLen Brown u8 instance_count; 52b4f9fe12SLen Brown u8 flags; 53b4f9fe12SLen Brown }; 54b4f9fe12SLen Brown 55b4f9fe12SLen Brown struct wmi_block { 56844af950SAndy Lutomirski struct wmi_device dev; 57b4f9fe12SLen Brown struct list_head list; 58b4f9fe12SLen Brown struct guid_block gblock; 5944b6b766SMario Limonciello struct miscdevice char_dev; 6044b6b766SMario Limonciello struct mutex char_mutex; 61b0e86302SAndy Lutomirski struct acpi_device *acpi_device; 62b4f9fe12SLen Brown wmi_notify_handler handler; 63b4f9fe12SLen Brown void *handler_data; 6444b6b766SMario Limonciello u64 req_buf_size; 65d4fc91adSAndy Lutomirski 66fd70da6aSDarren Hart (VMware) bool read_takes_no_args; 67b4f9fe12SLen Brown }; 68b4f9fe12SLen Brown 69b4f9fe12SLen Brown 70b4f9fe12SLen Brown /* 71b4f9fe12SLen Brown * If the GUID data block is marked as expensive, we must enable and 72b4f9fe12SLen Brown * explicitily disable data collection. 73b4f9fe12SLen Brown */ 74*1c95ace7SBarnabás Pőcze #define ACPI_WMI_EXPENSIVE BIT(0) 75*1c95ace7SBarnabás Pőcze #define ACPI_WMI_METHOD BIT(1) /* GUID is a method */ 76*1c95ace7SBarnabás Pőcze #define ACPI_WMI_STRING BIT(2) /* GUID takes & returns a string */ 77*1c95ace7SBarnabás Pőcze #define ACPI_WMI_EVENT BIT(3) /* GUID is an event */ 78b4f9fe12SLen Brown 7990ab5ee9SRusty Russell static bool debug_event; 80fc3155b2SThomas Renninger module_param(debug_event, bool, 0444); 81fc3155b2SThomas Renninger MODULE_PARM_DESC(debug_event, 82fc3155b2SThomas Renninger "Log WMI Events [0/1]"); 83fc3155b2SThomas Renninger 8490ab5ee9SRusty Russell static bool debug_dump_wdg; 85a929aae0SThomas Renninger module_param(debug_dump_wdg, bool, 0444); 86a929aae0SThomas Renninger MODULE_PARM_DESC(debug_dump_wdg, 87a929aae0SThomas Renninger "Dump available WMI interfaces [0/1]"); 88a929aae0SThomas Renninger 899599ed91SAndy Lutomirski static int acpi_wmi_remove(struct platform_device *device); 909599ed91SAndy Lutomirski static int acpi_wmi_probe(struct platform_device *device); 91b4f9fe12SLen Brown 92b4f9fe12SLen Brown static const struct acpi_device_id wmi_device_ids[] = { 93b4f9fe12SLen Brown {"PNP0C14", 0}, 94b4f9fe12SLen Brown {"pnp0c14", 0}, 959bf9ca95SBarnabás Pőcze { } 96b4f9fe12SLen Brown }; 97b4f9fe12SLen Brown MODULE_DEVICE_TABLE(acpi, wmi_device_ids); 98b4f9fe12SLen Brown 999599ed91SAndy Lutomirski static struct platform_driver acpi_wmi_driver = { 1009599ed91SAndy Lutomirski .driver = { 101844af950SAndy Lutomirski .name = "acpi-wmi", 1029599ed91SAndy Lutomirski .acpi_match_table = wmi_device_ids, 103b4f9fe12SLen Brown }, 1049599ed91SAndy Lutomirski .probe = acpi_wmi_probe, 1059599ed91SAndy Lutomirski .remove = acpi_wmi_remove, 106b4f9fe12SLen Brown }; 107b4f9fe12SLen Brown 108b4f9fe12SLen Brown /* 109b4f9fe12SLen Brown * GUID parsing functions 110b4f9fe12SLen Brown */ 111b4f9fe12SLen Brown 112b4f9fe12SLen Brown static bool find_guid(const char *guid_string, struct wmi_block **out) 113b4f9fe12SLen Brown { 114f9dffc14SAndy Shevchenko guid_t guid_input; 115b4f9fe12SLen Brown struct wmi_block *wblock; 116b4f9fe12SLen Brown struct guid_block *block; 117b4f9fe12SLen Brown 118f9dffc14SAndy Shevchenko if (guid_parse(guid_string, &guid_input)) 119538d7eb8SAndy Shevchenko return false; 120b4f9fe12SLen Brown 121cedb3b2aSAndy Shevchenko list_for_each_entry(wblock, &wmi_block_list, list) { 122b4f9fe12SLen Brown block = &wblock->gblock; 123b4f9fe12SLen Brown 124538d7eb8SAndy Shevchenko if (memcmp(block->guid, &guid_input, 16) == 0) { 125b4f9fe12SLen Brown if (out) 126b4f9fe12SLen Brown *out = wblock; 127097c27fcSJoe Perches return true; 128b4f9fe12SLen Brown } 129b4f9fe12SLen Brown } 130097c27fcSJoe Perches return false; 131b4f9fe12SLen Brown } 132b4f9fe12SLen Brown 133a48e2338SMattias Jacobsson static const void *find_guid_context(struct wmi_block *wblock, 134a48e2338SMattias Jacobsson struct wmi_driver *wdriver) 135a48e2338SMattias Jacobsson { 136a48e2338SMattias Jacobsson const struct wmi_device_id *id; 137f9dffc14SAndy Shevchenko guid_t guid_input; 138a48e2338SMattias Jacobsson 139a48e2338SMattias Jacobsson if (wdriver->id_table == NULL) 140a48e2338SMattias Jacobsson return NULL; 141a48e2338SMattias Jacobsson 142a48e2338SMattias Jacobsson id = wdriver->id_table; 143a48e2338SMattias Jacobsson while (*id->guid_string) { 144f9dffc14SAndy Shevchenko if (guid_parse(id->guid_string, &guid_input)) 145a48e2338SMattias Jacobsson continue; 146a48e2338SMattias Jacobsson if (!memcmp(wblock->gblock.guid, &guid_input, 16)) 147a48e2338SMattias Jacobsson return id->context; 148a48e2338SMattias Jacobsson id++; 149a48e2338SMattias Jacobsson } 150a48e2338SMattias Jacobsson return NULL; 151a48e2338SMattias Jacobsson } 152a48e2338SMattias Jacobsson 153d4fc91adSAndy Lutomirski static int get_subobj_info(acpi_handle handle, const char *pathname, 154d4fc91adSAndy Lutomirski struct acpi_device_info **info) 155d4fc91adSAndy Lutomirski { 156d4fc91adSAndy Lutomirski struct acpi_device_info *dummy_info, **info_ptr; 157d4fc91adSAndy Lutomirski acpi_handle subobj_handle; 158d4fc91adSAndy Lutomirski acpi_status status; 159d4fc91adSAndy Lutomirski 160d4fc91adSAndy Lutomirski status = acpi_get_handle(handle, (char *)pathname, &subobj_handle); 161d4fc91adSAndy Lutomirski if (status == AE_NOT_FOUND) 162d4fc91adSAndy Lutomirski return -ENOENT; 163d4fc91adSAndy Lutomirski else if (ACPI_FAILURE(status)) 164d4fc91adSAndy Lutomirski return -EIO; 165d4fc91adSAndy Lutomirski 166d4fc91adSAndy Lutomirski info_ptr = info ? info : &dummy_info; 167d4fc91adSAndy Lutomirski status = acpi_get_object_info(subobj_handle, info_ptr); 168d4fc91adSAndy Lutomirski if (ACPI_FAILURE(status)) 169d4fc91adSAndy Lutomirski return -EIO; 170d4fc91adSAndy Lutomirski 171d4fc91adSAndy Lutomirski if (!info) 172d4fc91adSAndy Lutomirski kfree(dummy_info); 173d4fc91adSAndy Lutomirski 174d4fc91adSAndy Lutomirski return 0; 175d4fc91adSAndy Lutomirski } 176d4fc91adSAndy Lutomirski 177b4f9fe12SLen Brown static acpi_status wmi_method_enable(struct wmi_block *wblock, int enable) 178b4f9fe12SLen Brown { 17943aacf83SBarnabás Pőcze struct guid_block *block; 180b4f9fe12SLen Brown char method[5]; 181b4f9fe12SLen Brown acpi_status status; 182b4f9fe12SLen Brown acpi_handle handle; 183b4f9fe12SLen Brown 184b4f9fe12SLen Brown block = &wblock->gblock; 185b0e86302SAndy Lutomirski handle = wblock->acpi_device->handle; 186b4f9fe12SLen Brown 187b4f9fe12SLen Brown snprintf(method, 5, "WE%02X", block->notify_id); 1888122ab66SZhang Rui status = acpi_execute_simple_method(handle, method, enable); 189b4f9fe12SLen Brown 190b4f9fe12SLen Brown if (status != AE_OK && status != AE_NOT_FOUND) 191b4f9fe12SLen Brown return status; 192b4f9fe12SLen Brown else 193b4f9fe12SLen Brown return AE_OK; 194b4f9fe12SLen Brown } 195b4f9fe12SLen Brown 196b4f9fe12SLen Brown /* 197b4f9fe12SLen Brown * Exported WMI functions 198b4f9fe12SLen Brown */ 19944b6b766SMario Limonciello 20044b6b766SMario Limonciello /** 20144b6b766SMario Limonciello * set_required_buffer_size - Sets the buffer size needed for performing IOCTL 20244b6b766SMario Limonciello * @wdev: A wmi bus device from a driver 2035a707af1SAndy Shevchenko * @length: Required buffer size 20444b6b766SMario Limonciello * 20544b6b766SMario Limonciello * Allocates memory needed for buffer, stores the buffer size in that memory 20644b6b766SMario Limonciello */ 20744b6b766SMario Limonciello int set_required_buffer_size(struct wmi_device *wdev, u64 length) 20844b6b766SMario Limonciello { 20944b6b766SMario Limonciello struct wmi_block *wblock; 21044b6b766SMario Limonciello 21144b6b766SMario Limonciello wblock = container_of(wdev, struct wmi_block, dev); 21244b6b766SMario Limonciello wblock->req_buf_size = length; 21344b6b766SMario Limonciello 21444b6b766SMario Limonciello return 0; 21544b6b766SMario Limonciello } 21644b6b766SMario Limonciello EXPORT_SYMBOL_GPL(set_required_buffer_size); 21744b6b766SMario Limonciello 218b4f9fe12SLen Brown /** 219b4f9fe12SLen Brown * wmi_evaluate_method - Evaluate a WMI method 220b4f9fe12SLen Brown * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba 221b4f9fe12SLen Brown * @instance: Instance index 222b4f9fe12SLen Brown * @method_id: Method ID to call 2235a707af1SAndy Shevchenko * @in: Buffer containing input for the method call 2245a707af1SAndy Shevchenko * @out: Empty buffer to return the method results 225b4f9fe12SLen Brown * 226b4f9fe12SLen Brown * Call an ACPI-WMI method 227b4f9fe12SLen Brown */ 228b4f9fe12SLen Brown acpi_status wmi_evaluate_method(const char *guid_string, u8 instance, 229b4f9fe12SLen Brown u32 method_id, const struct acpi_buffer *in, struct acpi_buffer *out) 230b4f9fe12SLen Brown { 231722c856dSMario Limonciello struct wmi_block *wblock = NULL; 232722c856dSMario Limonciello 233722c856dSMario Limonciello if (!find_guid(guid_string, &wblock)) 234722c856dSMario Limonciello return AE_ERROR; 235722c856dSMario Limonciello return wmidev_evaluate_method(&wblock->dev, instance, method_id, 236722c856dSMario Limonciello in, out); 237722c856dSMario Limonciello } 238722c856dSMario Limonciello EXPORT_SYMBOL_GPL(wmi_evaluate_method); 239722c856dSMario Limonciello 240722c856dSMario Limonciello /** 241722c856dSMario Limonciello * wmidev_evaluate_method - Evaluate a WMI method 242722c856dSMario Limonciello * @wdev: A wmi bus device from a driver 243722c856dSMario Limonciello * @instance: Instance index 244722c856dSMario Limonciello * @method_id: Method ID to call 2455a707af1SAndy Shevchenko * @in: Buffer containing input for the method call 2465a707af1SAndy Shevchenko * @out: Empty buffer to return the method results 247722c856dSMario Limonciello * 248722c856dSMario Limonciello * Call an ACPI-WMI method 249722c856dSMario Limonciello */ 250722c856dSMario Limonciello acpi_status wmidev_evaluate_method(struct wmi_device *wdev, u8 instance, 251722c856dSMario Limonciello u32 method_id, const struct acpi_buffer *in, struct acpi_buffer *out) 252722c856dSMario Limonciello { 25343aacf83SBarnabás Pőcze struct guid_block *block; 25443aacf83SBarnabás Pőcze struct wmi_block *wblock; 255b4f9fe12SLen Brown acpi_handle handle; 256b4f9fe12SLen Brown struct acpi_object_list input; 257b4f9fe12SLen Brown union acpi_object params[3]; 258f3d83e24SCostantino Leandro char method[5] = "WM"; 259b4f9fe12SLen Brown 260722c856dSMario Limonciello wblock = container_of(wdev, struct wmi_block, dev); 261b4f9fe12SLen Brown block = &wblock->gblock; 262b0e86302SAndy Lutomirski handle = wblock->acpi_device->handle; 263b4f9fe12SLen Brown 264b4f9fe12SLen Brown if (!(block->flags & ACPI_WMI_METHOD)) 265b4f9fe12SLen Brown return AE_BAD_DATA; 266b4f9fe12SLen Brown 2676afa1e2aSPali Rohár if (block->instance_count <= instance) 268b4f9fe12SLen Brown return AE_BAD_PARAMETER; 269b4f9fe12SLen Brown 270b4f9fe12SLen Brown input.count = 2; 271b4f9fe12SLen Brown input.pointer = params; 272b4f9fe12SLen Brown params[0].type = ACPI_TYPE_INTEGER; 273b4f9fe12SLen Brown params[0].integer.value = instance; 274b4f9fe12SLen Brown params[1].type = ACPI_TYPE_INTEGER; 275b4f9fe12SLen Brown params[1].integer.value = method_id; 276b4f9fe12SLen Brown 277b4f9fe12SLen Brown if (in) { 278b4f9fe12SLen Brown input.count = 3; 279b4f9fe12SLen Brown 280b4f9fe12SLen Brown if (block->flags & ACPI_WMI_STRING) { 281b4f9fe12SLen Brown params[2].type = ACPI_TYPE_STRING; 282b4f9fe12SLen Brown } else { 283b4f9fe12SLen Brown params[2].type = ACPI_TYPE_BUFFER; 284b4f9fe12SLen Brown } 285b4f9fe12SLen Brown params[2].buffer.length = in->length; 286b4f9fe12SLen Brown params[2].buffer.pointer = in->pointer; 287b4f9fe12SLen Brown } 288b4f9fe12SLen Brown 289b4f9fe12SLen Brown strncat(method, block->object_id, 2); 290b4f9fe12SLen Brown 29121397cacSBarnabás Pőcze return acpi_evaluate_object(handle, method, &input, out); 292b4f9fe12SLen Brown } 293722c856dSMario Limonciello EXPORT_SYMBOL_GPL(wmidev_evaluate_method); 294b4f9fe12SLen Brown 29556a37025SAndy Lutomirski static acpi_status __query_block(struct wmi_block *wblock, u8 instance, 296b4f9fe12SLen Brown struct acpi_buffer *out) 297b4f9fe12SLen Brown { 29843aacf83SBarnabás Pőcze struct guid_block *block; 29954f14c27SZhang Rui acpi_handle handle; 300b4f9fe12SLen Brown acpi_status status, wc_status = AE_ERROR; 3018122ab66SZhang Rui struct acpi_object_list input; 3028122ab66SZhang Rui union acpi_object wq_params[1]; 303f3d83e24SCostantino Leandro char method[5]; 304f3d83e24SCostantino Leandro char wc_method[5] = "WC"; 305b4f9fe12SLen Brown 30656a37025SAndy Lutomirski if (!out) 307b4f9fe12SLen Brown return AE_BAD_PARAMETER; 308b4f9fe12SLen Brown 309b4f9fe12SLen Brown block = &wblock->gblock; 310b0e86302SAndy Lutomirski handle = wblock->acpi_device->handle; 311b4f9fe12SLen Brown 3126afa1e2aSPali Rohár if (block->instance_count <= instance) 313b4f9fe12SLen Brown return AE_BAD_PARAMETER; 314b4f9fe12SLen Brown 315b4f9fe12SLen Brown /* Check GUID is a data block */ 316b4f9fe12SLen Brown if (block->flags & (ACPI_WMI_EVENT | ACPI_WMI_METHOD)) 317b4f9fe12SLen Brown return AE_ERROR; 318b4f9fe12SLen Brown 319b4f9fe12SLen Brown input.count = 1; 320b4f9fe12SLen Brown input.pointer = wq_params; 321b4f9fe12SLen Brown wq_params[0].type = ACPI_TYPE_INTEGER; 322b4f9fe12SLen Brown wq_params[0].integer.value = instance; 323b4f9fe12SLen Brown 324d4fc91adSAndy Lutomirski if (instance == 0 && wblock->read_takes_no_args) 325d4fc91adSAndy Lutomirski input.count = 0; 326d4fc91adSAndy Lutomirski 327b4f9fe12SLen Brown /* 328b4f9fe12SLen Brown * If ACPI_WMI_EXPENSIVE, call the relevant WCxx method first to 329b4f9fe12SLen Brown * enable collection. 330b4f9fe12SLen Brown */ 331b4f9fe12SLen Brown if (block->flags & ACPI_WMI_EXPENSIVE) { 332b4f9fe12SLen Brown strncat(wc_method, block->object_id, 2); 333b4f9fe12SLen Brown 334b4f9fe12SLen Brown /* 335b4f9fe12SLen Brown * Some GUIDs break the specification by declaring themselves 336b4f9fe12SLen Brown * expensive, but have no corresponding WCxx method. So we 337b4f9fe12SLen Brown * should not fail if this happens. 338b4f9fe12SLen Brown */ 339bad9da86SKelsey Skunberg wc_status = acpi_execute_simple_method(handle, wc_method, 1); 340b4f9fe12SLen Brown } 341b4f9fe12SLen Brown 342b4f9fe12SLen Brown strcpy(method, "WQ"); 343b4f9fe12SLen Brown strncat(method, block->object_id, 2); 344b4f9fe12SLen Brown 345b4f9fe12SLen Brown status = acpi_evaluate_object(handle, method, &input, out); 346b4f9fe12SLen Brown 347b4f9fe12SLen Brown /* 348b4f9fe12SLen Brown * If ACPI_WMI_EXPENSIVE, call the relevant WCxx method, even if 349b4f9fe12SLen Brown * the WQxx method failed - we should disable collection anyway. 350b4f9fe12SLen Brown */ 351b4f9fe12SLen Brown if ((block->flags & ACPI_WMI_EXPENSIVE) && ACPI_SUCCESS(wc_status)) { 3528122ab66SZhang Rui status = acpi_execute_simple_method(handle, wc_method, 0); 353b4f9fe12SLen Brown } 354b4f9fe12SLen Brown 355b4f9fe12SLen Brown return status; 356b4f9fe12SLen Brown } 35756a37025SAndy Lutomirski 35856a37025SAndy Lutomirski /** 35956a37025SAndy Lutomirski * wmi_query_block - Return contents of a WMI block (deprecated) 36056a37025SAndy Lutomirski * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba 36156a37025SAndy Lutomirski * @instance: Instance index 3625a707af1SAndy Shevchenko * @out: Empty buffer to return the contents of the data block to 36356a37025SAndy Lutomirski * 36456a37025SAndy Lutomirski * Return the contents of an ACPI-WMI data block to a buffer 36556a37025SAndy Lutomirski */ 36656a37025SAndy Lutomirski acpi_status wmi_query_block(const char *guid_string, u8 instance, 36756a37025SAndy Lutomirski struct acpi_buffer *out) 36856a37025SAndy Lutomirski { 36956a37025SAndy Lutomirski struct wmi_block *wblock; 37056a37025SAndy Lutomirski 37156a37025SAndy Lutomirski if (!guid_string) 37256a37025SAndy Lutomirski return AE_BAD_PARAMETER; 37356a37025SAndy Lutomirski 37456a37025SAndy Lutomirski if (!find_guid(guid_string, &wblock)) 37556a37025SAndy Lutomirski return AE_ERROR; 37656a37025SAndy Lutomirski 37756a37025SAndy Lutomirski return __query_block(wblock, instance, out); 37856a37025SAndy Lutomirski } 379b4f9fe12SLen Brown EXPORT_SYMBOL_GPL(wmi_query_block); 380b4f9fe12SLen Brown 38156a37025SAndy Lutomirski union acpi_object *wmidev_block_query(struct wmi_device *wdev, u8 instance) 38256a37025SAndy Lutomirski { 38356a37025SAndy Lutomirski struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL }; 38456a37025SAndy Lutomirski struct wmi_block *wblock = container_of(wdev, struct wmi_block, dev); 38556a37025SAndy Lutomirski 38656a37025SAndy Lutomirski if (ACPI_FAILURE(__query_block(wblock, instance, &out))) 38756a37025SAndy Lutomirski return NULL; 38856a37025SAndy Lutomirski 389c06a2fdeSBarnabás Pőcze return out.pointer; 39056a37025SAndy Lutomirski } 39156a37025SAndy Lutomirski EXPORT_SYMBOL_GPL(wmidev_block_query); 39256a37025SAndy Lutomirski 393b4f9fe12SLen Brown /** 394b4f9fe12SLen Brown * wmi_set_block - Write to a WMI block 395b4f9fe12SLen Brown * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba 396b4f9fe12SLen Brown * @instance: Instance index 3975a707af1SAndy Shevchenko * @in: Buffer containing new values for the data block 398b4f9fe12SLen Brown * 399b4f9fe12SLen Brown * Write the contents of the input buffer to an ACPI-WMI data block 400b4f9fe12SLen Brown */ 401b4f9fe12SLen Brown acpi_status wmi_set_block(const char *guid_string, u8 instance, 402b4f9fe12SLen Brown const struct acpi_buffer *in) 403b4f9fe12SLen Brown { 404b4f9fe12SLen Brown struct wmi_block *wblock = NULL; 40543aacf83SBarnabás Pőcze struct guid_block *block; 406b4f9fe12SLen Brown acpi_handle handle; 407b4f9fe12SLen Brown struct acpi_object_list input; 408b4f9fe12SLen Brown union acpi_object params[2]; 409f3d83e24SCostantino Leandro char method[5] = "WS"; 410b4f9fe12SLen Brown 411b4f9fe12SLen Brown if (!guid_string || !in) 412b4f9fe12SLen Brown return AE_BAD_DATA; 413b4f9fe12SLen Brown 414b4f9fe12SLen Brown if (!find_guid(guid_string, &wblock)) 415b4f9fe12SLen Brown return AE_ERROR; 416b4f9fe12SLen Brown 417b4f9fe12SLen Brown block = &wblock->gblock; 418b0e86302SAndy Lutomirski handle = wblock->acpi_device->handle; 419b4f9fe12SLen Brown 4206afa1e2aSPali Rohár if (block->instance_count <= instance) 421b4f9fe12SLen Brown return AE_BAD_PARAMETER; 422b4f9fe12SLen Brown 423b4f9fe12SLen Brown /* Check GUID is a data block */ 424b4f9fe12SLen Brown if (block->flags & (ACPI_WMI_EVENT | ACPI_WMI_METHOD)) 425b4f9fe12SLen Brown return AE_ERROR; 426b4f9fe12SLen Brown 427b4f9fe12SLen Brown input.count = 2; 428b4f9fe12SLen Brown input.pointer = params; 429b4f9fe12SLen Brown params[0].type = ACPI_TYPE_INTEGER; 430b4f9fe12SLen Brown params[0].integer.value = instance; 431b4f9fe12SLen Brown 432b4f9fe12SLen Brown if (block->flags & ACPI_WMI_STRING) { 433b4f9fe12SLen Brown params[1].type = ACPI_TYPE_STRING; 434b4f9fe12SLen Brown } else { 435b4f9fe12SLen Brown params[1].type = ACPI_TYPE_BUFFER; 436b4f9fe12SLen Brown } 437b4f9fe12SLen Brown params[1].buffer.length = in->length; 438b4f9fe12SLen Brown params[1].buffer.pointer = in->pointer; 439b4f9fe12SLen Brown 440b4f9fe12SLen Brown strncat(method, block->object_id, 2); 441b4f9fe12SLen Brown 442b4f9fe12SLen Brown return acpi_evaluate_object(handle, method, &input, NULL); 443b4f9fe12SLen Brown } 444b4f9fe12SLen Brown EXPORT_SYMBOL_GPL(wmi_set_block); 445b4f9fe12SLen Brown 44637830662SDmitry Torokhov static void wmi_dump_wdg(const struct guid_block *g) 447a929aae0SThomas Renninger { 44885b4e4ebSRasmus Villemoes pr_info("%pUL:\n", g->guid); 449cd3921f8SPali Rohár if (g->flags & ACPI_WMI_EVENT) 450cd3921f8SPali Rohár pr_info("\tnotify_id: 0x%02X\n", g->notify_id); 451cd3921f8SPali Rohár else 452cd3921f8SPali Rohár pr_info("\tobject_id: %2pE\n", g->object_id); 4538e07514dSDmitry Torokhov pr_info("\tinstance_count: %d\n", g->instance_count); 4548e07514dSDmitry Torokhov pr_info("\tflags: %#x", g->flags); 455a929aae0SThomas Renninger if (g->flags) { 456a929aae0SThomas Renninger if (g->flags & ACPI_WMI_EXPENSIVE) 4578e07514dSDmitry Torokhov pr_cont(" ACPI_WMI_EXPENSIVE"); 458a929aae0SThomas Renninger if (g->flags & ACPI_WMI_METHOD) 4598e07514dSDmitry Torokhov pr_cont(" ACPI_WMI_METHOD"); 460a929aae0SThomas Renninger if (g->flags & ACPI_WMI_STRING) 4618e07514dSDmitry Torokhov pr_cont(" ACPI_WMI_STRING"); 462a929aae0SThomas Renninger if (g->flags & ACPI_WMI_EVENT) 4638e07514dSDmitry Torokhov pr_cont(" ACPI_WMI_EVENT"); 464a929aae0SThomas Renninger } 4658e07514dSDmitry Torokhov pr_cont("\n"); 466a929aae0SThomas Renninger 467a929aae0SThomas Renninger } 468a929aae0SThomas Renninger 469fc3155b2SThomas Renninger static void wmi_notify_debug(u32 value, void *context) 470fc3155b2SThomas Renninger { 471fc3155b2SThomas Renninger struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL }; 472fc3155b2SThomas Renninger union acpi_object *obj; 4731492616aSAxel Lin acpi_status status; 474fc3155b2SThomas Renninger 4751492616aSAxel Lin status = wmi_get_event_data(value, &response); 4761492616aSAxel Lin if (status != AE_OK) { 4778e07514dSDmitry Torokhov pr_info("bad event status 0x%x\n", status); 4781492616aSAxel Lin return; 4791492616aSAxel Lin } 480fc3155b2SThomas Renninger 481c06a2fdeSBarnabás Pőcze obj = response.pointer; 482fc3155b2SThomas Renninger if (!obj) 483fc3155b2SThomas Renninger return; 484fc3155b2SThomas Renninger 4858e07514dSDmitry Torokhov pr_info("DEBUG Event "); 486fc3155b2SThomas Renninger switch (obj->type) { 487fc3155b2SThomas Renninger case ACPI_TYPE_BUFFER: 4888e07514dSDmitry Torokhov pr_cont("BUFFER_TYPE - length %d\n", obj->buffer.length); 489fc3155b2SThomas Renninger break; 490fc3155b2SThomas Renninger case ACPI_TYPE_STRING: 4918e07514dSDmitry Torokhov pr_cont("STRING_TYPE - %s\n", obj->string.pointer); 492fc3155b2SThomas Renninger break; 493fc3155b2SThomas Renninger case ACPI_TYPE_INTEGER: 4948e07514dSDmitry Torokhov pr_cont("INTEGER_TYPE - %llu\n", obj->integer.value); 495fc3155b2SThomas Renninger break; 496fc3155b2SThomas Renninger case ACPI_TYPE_PACKAGE: 4978e07514dSDmitry Torokhov pr_cont("PACKAGE_TYPE - %d elements\n", obj->package.count); 498fc3155b2SThomas Renninger break; 499fc3155b2SThomas Renninger default: 5008e07514dSDmitry Torokhov pr_cont("object type 0x%X\n", obj->type); 501fc3155b2SThomas Renninger } 5021492616aSAxel Lin kfree(obj); 503fc3155b2SThomas Renninger } 504fc3155b2SThomas Renninger 505b4f9fe12SLen Brown /** 506b4f9fe12SLen Brown * wmi_install_notify_handler - Register handler for WMI events 5075a707af1SAndy Shevchenko * @guid: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba 508b4f9fe12SLen Brown * @handler: Function to handle notifications 509b4f9fe12SLen Brown * @data: Data to be returned to handler when event is fired 510b4f9fe12SLen Brown * 511b4f9fe12SLen Brown * Register a handler for events sent to the ACPI-WMI mapper device. 512b4f9fe12SLen Brown */ 513b4f9fe12SLen Brown acpi_status wmi_install_notify_handler(const char *guid, 514b4f9fe12SLen Brown wmi_notify_handler handler, void *data) 515b4f9fe12SLen Brown { 516b4f9fe12SLen Brown struct wmi_block *block; 51758f6425eSColin King acpi_status status = AE_NOT_EXIST; 518f9dffc14SAndy Shevchenko guid_t guid_input; 519b4f9fe12SLen Brown 520b4f9fe12SLen Brown if (!guid || !handler) 521b4f9fe12SLen Brown return AE_BAD_PARAMETER; 522b4f9fe12SLen Brown 523f9dffc14SAndy Shevchenko if (guid_parse(guid, &guid_input)) 524538d7eb8SAndy Shevchenko return AE_BAD_PARAMETER; 525b4f9fe12SLen Brown 526cedb3b2aSAndy Shevchenko list_for_each_entry(block, &wmi_block_list, list) { 52758f6425eSColin King acpi_status wmi_status; 52858f6425eSColin King 529538d7eb8SAndy Shevchenko if (memcmp(block->gblock.guid, &guid_input, 16) == 0) { 53058f6425eSColin King if (block->handler && 53158f6425eSColin King block->handler != wmi_notify_debug) 532b4f9fe12SLen Brown return AE_ALREADY_ACQUIRED; 533b4f9fe12SLen Brown 534b4f9fe12SLen Brown block->handler = handler; 535b4f9fe12SLen Brown block->handler_data = data; 536b4f9fe12SLen Brown 53758f6425eSColin King wmi_status = wmi_method_enable(block, 1); 53858f6425eSColin King if ((wmi_status != AE_OK) || 53958f6425eSColin King ((wmi_status == AE_OK) && (status == AE_NOT_EXIST))) 54058f6425eSColin King status = wmi_status; 54158f6425eSColin King } 54258f6425eSColin King } 543b4f9fe12SLen Brown 544b4f9fe12SLen Brown return status; 545b4f9fe12SLen Brown } 546b4f9fe12SLen Brown EXPORT_SYMBOL_GPL(wmi_install_notify_handler); 547b4f9fe12SLen Brown 548b4f9fe12SLen Brown /** 54907ce4cfdSBarnabás Pőcze * wmi_remove_notify_handler - Unregister handler for WMI events 5505a707af1SAndy Shevchenko * @guid: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba 551b4f9fe12SLen Brown * 552b4f9fe12SLen Brown * Unregister handler for events sent to the ACPI-WMI mapper device. 553b4f9fe12SLen Brown */ 554b4f9fe12SLen Brown acpi_status wmi_remove_notify_handler(const char *guid) 555b4f9fe12SLen Brown { 556b4f9fe12SLen Brown struct wmi_block *block; 55758f6425eSColin King acpi_status status = AE_NOT_EXIST; 558f9dffc14SAndy Shevchenko guid_t guid_input; 559b4f9fe12SLen Brown 560b4f9fe12SLen Brown if (!guid) 561b4f9fe12SLen Brown return AE_BAD_PARAMETER; 562b4f9fe12SLen Brown 563f9dffc14SAndy Shevchenko if (guid_parse(guid, &guid_input)) 564538d7eb8SAndy Shevchenko return AE_BAD_PARAMETER; 565b4f9fe12SLen Brown 566cedb3b2aSAndy Shevchenko list_for_each_entry(block, &wmi_block_list, list) { 56758f6425eSColin King acpi_status wmi_status; 56858f6425eSColin King 569538d7eb8SAndy Shevchenko if (memcmp(block->gblock.guid, &guid_input, 16) == 0) { 57058f6425eSColin King if (!block->handler || 57158f6425eSColin King block->handler == wmi_notify_debug) 572b4f9fe12SLen Brown return AE_NULL_ENTRY; 573b4f9fe12SLen Brown 574fc3155b2SThomas Renninger if (debug_event) { 575fc3155b2SThomas Renninger block->handler = wmi_notify_debug; 57658f6425eSColin King status = AE_OK; 577fc3155b2SThomas Renninger } else { 57858f6425eSColin King wmi_status = wmi_method_enable(block, 0); 579b4f9fe12SLen Brown block->handler = NULL; 580b4f9fe12SLen Brown block->handler_data = NULL; 58158f6425eSColin King if ((wmi_status != AE_OK) || 58258f6425eSColin King ((wmi_status == AE_OK) && 58358f6425eSColin King (status == AE_NOT_EXIST))) 58458f6425eSColin King status = wmi_status; 585fc3155b2SThomas Renninger } 58658f6425eSColin King } 58758f6425eSColin King } 58858f6425eSColin King 589b4f9fe12SLen Brown return status; 590b4f9fe12SLen Brown } 591b4f9fe12SLen Brown EXPORT_SYMBOL_GPL(wmi_remove_notify_handler); 592b4f9fe12SLen Brown 593b4f9fe12SLen Brown /** 594b4f9fe12SLen Brown * wmi_get_event_data - Get WMI data associated with an event 595b4f9fe12SLen Brown * 5963e9b988eSAnisse Astier * @event: Event to find 5973e9b988eSAnisse Astier * @out: Buffer to hold event data. out->pointer should be freed with kfree() 598b4f9fe12SLen Brown * 599b4f9fe12SLen Brown * Returns extra data associated with an event in WMI. 600b4f9fe12SLen Brown */ 601b4f9fe12SLen Brown acpi_status wmi_get_event_data(u32 event, struct acpi_buffer *out) 602b4f9fe12SLen Brown { 603b4f9fe12SLen Brown struct acpi_object_list input; 604b4f9fe12SLen Brown union acpi_object params[1]; 605b4f9fe12SLen Brown struct guid_block *gblock; 606b4f9fe12SLen Brown struct wmi_block *wblock; 607b4f9fe12SLen Brown 608b4f9fe12SLen Brown input.count = 1; 609b4f9fe12SLen Brown input.pointer = params; 610b4f9fe12SLen Brown params[0].type = ACPI_TYPE_INTEGER; 611b4f9fe12SLen Brown params[0].integer.value = event; 612b4f9fe12SLen Brown 613cedb3b2aSAndy Shevchenko list_for_each_entry(wblock, &wmi_block_list, list) { 614b4f9fe12SLen Brown gblock = &wblock->gblock; 615b4f9fe12SLen Brown 616b4f9fe12SLen Brown if ((gblock->flags & ACPI_WMI_EVENT) && 617b4f9fe12SLen Brown (gblock->notify_id == event)) 618b0e86302SAndy Lutomirski return acpi_evaluate_object(wblock->acpi_device->handle, 619b0e86302SAndy Lutomirski "_WED", &input, out); 620b4f9fe12SLen Brown } 621b4f9fe12SLen Brown 622b4f9fe12SLen Brown return AE_NOT_FOUND; 623b4f9fe12SLen Brown } 624b4f9fe12SLen Brown EXPORT_SYMBOL_GPL(wmi_get_event_data); 625b4f9fe12SLen Brown 626b4f9fe12SLen Brown /** 627b4f9fe12SLen Brown * wmi_has_guid - Check if a GUID is available 628b4f9fe12SLen Brown * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba 629b4f9fe12SLen Brown * 630b4f9fe12SLen Brown * Check if a given GUID is defined by _WDG 631b4f9fe12SLen Brown */ 632b4f9fe12SLen Brown bool wmi_has_guid(const char *guid_string) 633b4f9fe12SLen Brown { 634b4f9fe12SLen Brown return find_guid(guid_string, NULL); 635b4f9fe12SLen Brown } 636b4f9fe12SLen Brown EXPORT_SYMBOL_GPL(wmi_has_guid); 637b4f9fe12SLen Brown 638e7488e58SYurii Pavlovskyi /** 639e7488e58SYurii Pavlovskyi * wmi_get_acpi_device_uid() - Get _UID name of ACPI device that defines GUID 640e7488e58SYurii Pavlovskyi * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba 641e7488e58SYurii Pavlovskyi * 642e7488e58SYurii Pavlovskyi * Find the _UID of ACPI device associated with this WMI GUID. 643e7488e58SYurii Pavlovskyi * 644e7488e58SYurii Pavlovskyi * Return: The ACPI _UID field value or NULL if the WMI GUID was not found 645e7488e58SYurii Pavlovskyi */ 646e7488e58SYurii Pavlovskyi char *wmi_get_acpi_device_uid(const char *guid_string) 647e7488e58SYurii Pavlovskyi { 648e7488e58SYurii Pavlovskyi struct wmi_block *wblock = NULL; 649e7488e58SYurii Pavlovskyi 650e7488e58SYurii Pavlovskyi if (!find_guid(guid_string, &wblock)) 651e7488e58SYurii Pavlovskyi return NULL; 652e7488e58SYurii Pavlovskyi 653e7488e58SYurii Pavlovskyi return acpi_device_uid(wblock->acpi_device); 654e7488e58SYurii Pavlovskyi } 655e7488e58SYurii Pavlovskyi EXPORT_SYMBOL_GPL(wmi_get_acpi_device_uid); 656e7488e58SYurii Pavlovskyi 657844af950SAndy Lutomirski static struct wmi_block *dev_to_wblock(struct device *dev) 658844af950SAndy Lutomirski { 659844af950SAndy Lutomirski return container_of(dev, struct wmi_block, dev.dev); 660844af950SAndy Lutomirski } 661844af950SAndy Lutomirski 662844af950SAndy Lutomirski static struct wmi_device *dev_to_wdev(struct device *dev) 663844af950SAndy Lutomirski { 664844af950SAndy Lutomirski return container_of(dev, struct wmi_device, dev); 665844af950SAndy Lutomirski } 666844af950SAndy Lutomirski 667b4f9fe12SLen Brown /* 6681caab3c1SMatthew Garrett * sysfs interface 6691caab3c1SMatthew Garrett */ 670614ef432SDmitry Torokhov static ssize_t modalias_show(struct device *dev, struct device_attribute *attr, 6711caab3c1SMatthew Garrett char *buf) 6721caab3c1SMatthew Garrett { 673844af950SAndy Lutomirski struct wmi_block *wblock = dev_to_wblock(dev); 6741caab3c1SMatthew Garrett 67585b4e4ebSRasmus Villemoes return sprintf(buf, "wmi:%pUL\n", wblock->gblock.guid); 6761caab3c1SMatthew Garrett } 677e80b89a5SGreg Kroah-Hartman static DEVICE_ATTR_RO(modalias); 678614ef432SDmitry Torokhov 679844af950SAndy Lutomirski static ssize_t guid_show(struct device *dev, struct device_attribute *attr, 680844af950SAndy Lutomirski char *buf) 681844af950SAndy Lutomirski { 682844af950SAndy Lutomirski struct wmi_block *wblock = dev_to_wblock(dev); 683844af950SAndy Lutomirski 684844af950SAndy Lutomirski return sprintf(buf, "%pUL\n", wblock->gblock.guid); 685844af950SAndy Lutomirski } 686844af950SAndy Lutomirski static DEVICE_ATTR_RO(guid); 687844af950SAndy Lutomirski 688d79b1074SAndy Lutomirski static ssize_t instance_count_show(struct device *dev, 689d79b1074SAndy Lutomirski struct device_attribute *attr, char *buf) 690d79b1074SAndy Lutomirski { 691d79b1074SAndy Lutomirski struct wmi_block *wblock = dev_to_wblock(dev); 692d79b1074SAndy Lutomirski 693d79b1074SAndy Lutomirski return sprintf(buf, "%d\n", (int)wblock->gblock.instance_count); 694d79b1074SAndy Lutomirski } 695d79b1074SAndy Lutomirski static DEVICE_ATTR_RO(instance_count); 696d79b1074SAndy Lutomirski 697d79b1074SAndy Lutomirski static ssize_t expensive_show(struct device *dev, 698d79b1074SAndy Lutomirski struct device_attribute *attr, char *buf) 699d79b1074SAndy Lutomirski { 700d79b1074SAndy Lutomirski struct wmi_block *wblock = dev_to_wblock(dev); 701d79b1074SAndy Lutomirski 702d79b1074SAndy Lutomirski return sprintf(buf, "%d\n", 703d79b1074SAndy Lutomirski (wblock->gblock.flags & ACPI_WMI_EXPENSIVE) != 0); 704d79b1074SAndy Lutomirski } 705d79b1074SAndy Lutomirski static DEVICE_ATTR_RO(expensive); 706d79b1074SAndy Lutomirski 707e80b89a5SGreg Kroah-Hartman static struct attribute *wmi_attrs[] = { 708e80b89a5SGreg Kroah-Hartman &dev_attr_modalias.attr, 709844af950SAndy Lutomirski &dev_attr_guid.attr, 710d79b1074SAndy Lutomirski &dev_attr_instance_count.attr, 711d79b1074SAndy Lutomirski &dev_attr_expensive.attr, 712cd3e3d29SBarnabás Pőcze NULL 713614ef432SDmitry Torokhov }; 714e80b89a5SGreg Kroah-Hartman ATTRIBUTE_GROUPS(wmi); 7151caab3c1SMatthew Garrett 716d79b1074SAndy Lutomirski static ssize_t notify_id_show(struct device *dev, struct device_attribute *attr, 717d79b1074SAndy Lutomirski char *buf) 718d79b1074SAndy Lutomirski { 719d79b1074SAndy Lutomirski struct wmi_block *wblock = dev_to_wblock(dev); 720d79b1074SAndy Lutomirski 721d79b1074SAndy Lutomirski return sprintf(buf, "%02X\n", (unsigned int)wblock->gblock.notify_id); 722d79b1074SAndy Lutomirski } 723d79b1074SAndy Lutomirski static DEVICE_ATTR_RO(notify_id); 724d79b1074SAndy Lutomirski 725d79b1074SAndy Lutomirski static struct attribute *wmi_event_attrs[] = { 726d79b1074SAndy Lutomirski &dev_attr_notify_id.attr, 727cd3e3d29SBarnabás Pőcze NULL 728d79b1074SAndy Lutomirski }; 729d79b1074SAndy Lutomirski ATTRIBUTE_GROUPS(wmi_event); 730d79b1074SAndy Lutomirski 731d79b1074SAndy Lutomirski static ssize_t object_id_show(struct device *dev, struct device_attribute *attr, 732d79b1074SAndy Lutomirski char *buf) 733d79b1074SAndy Lutomirski { 734d79b1074SAndy Lutomirski struct wmi_block *wblock = dev_to_wblock(dev); 735d79b1074SAndy Lutomirski 736d79b1074SAndy Lutomirski return sprintf(buf, "%c%c\n", wblock->gblock.object_id[0], 737d79b1074SAndy Lutomirski wblock->gblock.object_id[1]); 738d79b1074SAndy Lutomirski } 739d79b1074SAndy Lutomirski static DEVICE_ATTR_RO(object_id); 740d79b1074SAndy Lutomirski 741fd70da6aSDarren Hart (VMware) static ssize_t setable_show(struct device *dev, struct device_attribute *attr, 742d4fc91adSAndy Lutomirski char *buf) 743d4fc91adSAndy Lutomirski { 744d4fc91adSAndy Lutomirski struct wmi_device *wdev = dev_to_wdev(dev); 745d4fc91adSAndy Lutomirski 746fd70da6aSDarren Hart (VMware) return sprintf(buf, "%d\n", (int)wdev->setable); 747d4fc91adSAndy Lutomirski } 748fd70da6aSDarren Hart (VMware) static DEVICE_ATTR_RO(setable); 749d4fc91adSAndy Lutomirski 750d4fc91adSAndy Lutomirski static struct attribute *wmi_data_attrs[] = { 751d4fc91adSAndy Lutomirski &dev_attr_object_id.attr, 752fd70da6aSDarren Hart (VMware) &dev_attr_setable.attr, 753cd3e3d29SBarnabás Pőcze NULL 754d4fc91adSAndy Lutomirski }; 755d4fc91adSAndy Lutomirski ATTRIBUTE_GROUPS(wmi_data); 756d4fc91adSAndy Lutomirski 757d4fc91adSAndy Lutomirski static struct attribute *wmi_method_attrs[] = { 758d79b1074SAndy Lutomirski &dev_attr_object_id.attr, 759cd3e3d29SBarnabás Pőcze NULL 760d79b1074SAndy Lutomirski }; 761d4fc91adSAndy Lutomirski ATTRIBUTE_GROUPS(wmi_method); 762d79b1074SAndy Lutomirski 7631caab3c1SMatthew Garrett static int wmi_dev_uevent(struct device *dev, struct kobj_uevent_env *env) 7641caab3c1SMatthew Garrett { 765844af950SAndy Lutomirski struct wmi_block *wblock = dev_to_wblock(dev); 7661caab3c1SMatthew Garrett 767844af950SAndy Lutomirski if (add_uevent_var(env, "MODALIAS=wmi:%pUL", wblock->gblock.guid)) 7681caab3c1SMatthew Garrett return -ENOMEM; 7691caab3c1SMatthew Garrett 770844af950SAndy Lutomirski if (add_uevent_var(env, "WMI_GUID=%pUL", wblock->gblock.guid)) 7711caab3c1SMatthew Garrett return -ENOMEM; 7721caab3c1SMatthew Garrett 7731caab3c1SMatthew Garrett return 0; 7741caab3c1SMatthew Garrett } 7751caab3c1SMatthew Garrett 776844af950SAndy Lutomirski static void wmi_dev_release(struct device *dev) 7771caab3c1SMatthew Garrett { 778844af950SAndy Lutomirski struct wmi_block *wblock = dev_to_wblock(dev); 779c64eefd4SDmitry Torokhov 780844af950SAndy Lutomirski kfree(wblock); 7811caab3c1SMatthew Garrett } 7821caab3c1SMatthew Garrett 783844af950SAndy Lutomirski static int wmi_dev_match(struct device *dev, struct device_driver *driver) 784844af950SAndy Lutomirski { 785844af950SAndy Lutomirski struct wmi_driver *wmi_driver = 786844af950SAndy Lutomirski container_of(driver, struct wmi_driver, driver); 787844af950SAndy Lutomirski struct wmi_block *wblock = dev_to_wblock(dev); 788844af950SAndy Lutomirski const struct wmi_device_id *id = wmi_driver->id_table; 789844af950SAndy Lutomirski 790c355ec65SMattias Jacobsson if (id == NULL) 791c355ec65SMattias Jacobsson return 0; 792c355ec65SMattias Jacobsson 793eacc95eaSMattias Jacobsson while (*id->guid_string) { 794f9dffc14SAndy Shevchenko guid_t driver_guid; 795844af950SAndy Lutomirski 796f9dffc14SAndy Shevchenko if (WARN_ON(guid_parse(id->guid_string, &driver_guid))) 797844af950SAndy Lutomirski continue; 798844af950SAndy Lutomirski if (!memcmp(&driver_guid, wblock->gblock.guid, 16)) 799844af950SAndy Lutomirski return 1; 800844af950SAndy Lutomirski 801844af950SAndy Lutomirski id++; 802844af950SAndy Lutomirski } 803844af950SAndy Lutomirski 804844af950SAndy Lutomirski return 0; 805844af950SAndy Lutomirski } 80644b6b766SMario Limonciello static int wmi_char_open(struct inode *inode, struct file *filp) 80744b6b766SMario Limonciello { 80844b6b766SMario Limonciello const char *driver_name = filp->f_path.dentry->d_iname; 80943aacf83SBarnabás Pőcze struct wmi_block *wblock; 81043aacf83SBarnabás Pőcze struct wmi_block *next; 81144b6b766SMario Limonciello 81244b6b766SMario Limonciello list_for_each_entry_safe(wblock, next, &wmi_block_list, list) { 81344b6b766SMario Limonciello if (!wblock->dev.dev.driver) 81444b6b766SMario Limonciello continue; 81544b6b766SMario Limonciello if (strcmp(driver_name, wblock->dev.dev.driver->name) == 0) { 81644b6b766SMario Limonciello filp->private_data = wblock; 81744b6b766SMario Limonciello break; 81844b6b766SMario Limonciello } 81944b6b766SMario Limonciello } 82044b6b766SMario Limonciello 82144b6b766SMario Limonciello if (!filp->private_data) 82244b6b766SMario Limonciello return -ENODEV; 82344b6b766SMario Limonciello 82444b6b766SMario Limonciello return nonseekable_open(inode, filp); 82544b6b766SMario Limonciello } 82644b6b766SMario Limonciello 82744b6b766SMario Limonciello static ssize_t wmi_char_read(struct file *filp, char __user *buffer, 82844b6b766SMario Limonciello size_t length, loff_t *offset) 82944b6b766SMario Limonciello { 83044b6b766SMario Limonciello struct wmi_block *wblock = filp->private_data; 83144b6b766SMario Limonciello 83244b6b766SMario Limonciello return simple_read_from_buffer(buffer, length, offset, 83344b6b766SMario Limonciello &wblock->req_buf_size, 83444b6b766SMario Limonciello sizeof(wblock->req_buf_size)); 83544b6b766SMario Limonciello } 83644b6b766SMario Limonciello 83744b6b766SMario Limonciello static long wmi_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) 83844b6b766SMario Limonciello { 83944b6b766SMario Limonciello struct wmi_ioctl_buffer __user *input = 84044b6b766SMario Limonciello (struct wmi_ioctl_buffer __user *) arg; 84144b6b766SMario Limonciello struct wmi_block *wblock = filp->private_data; 84243aacf83SBarnabás Pőcze struct wmi_ioctl_buffer *buf; 84343aacf83SBarnabás Pőcze struct wmi_driver *wdriver; 84444b6b766SMario Limonciello int ret; 84544b6b766SMario Limonciello 84644b6b766SMario Limonciello if (_IOC_TYPE(cmd) != WMI_IOC) 84744b6b766SMario Limonciello return -ENOTTY; 84844b6b766SMario Limonciello 84944b6b766SMario Limonciello /* make sure we're not calling a higher instance than exists*/ 85044b6b766SMario Limonciello if (_IOC_NR(cmd) >= wblock->gblock.instance_count) 85144b6b766SMario Limonciello return -EINVAL; 85244b6b766SMario Limonciello 85344b6b766SMario Limonciello mutex_lock(&wblock->char_mutex); 85444b6b766SMario Limonciello buf = wblock->handler_data; 85544b6b766SMario Limonciello if (get_user(buf->length, &input->length)) { 85644b6b766SMario Limonciello dev_dbg(&wblock->dev.dev, "Read length from user failed\n"); 85744b6b766SMario Limonciello ret = -EFAULT; 85844b6b766SMario Limonciello goto out_ioctl; 85944b6b766SMario Limonciello } 86044b6b766SMario Limonciello /* if it's too small, abort */ 86144b6b766SMario Limonciello if (buf->length < wblock->req_buf_size) { 86244b6b766SMario Limonciello dev_err(&wblock->dev.dev, 86344b6b766SMario Limonciello "Buffer %lld too small, need at least %lld\n", 86444b6b766SMario Limonciello buf->length, wblock->req_buf_size); 86544b6b766SMario Limonciello ret = -EINVAL; 86644b6b766SMario Limonciello goto out_ioctl; 86744b6b766SMario Limonciello } 86844b6b766SMario Limonciello /* if it's too big, warn, driver will only use what is needed */ 86944b6b766SMario Limonciello if (buf->length > wblock->req_buf_size) 87044b6b766SMario Limonciello dev_warn(&wblock->dev.dev, 87144b6b766SMario Limonciello "Buffer %lld is bigger than required %lld\n", 87244b6b766SMario Limonciello buf->length, wblock->req_buf_size); 87344b6b766SMario Limonciello 87444b6b766SMario Limonciello /* copy the structure from userspace */ 87544b6b766SMario Limonciello if (copy_from_user(buf, input, wblock->req_buf_size)) { 87644b6b766SMario Limonciello dev_dbg(&wblock->dev.dev, "Copy %llu from user failed\n", 87744b6b766SMario Limonciello wblock->req_buf_size); 87844b6b766SMario Limonciello ret = -EFAULT; 87944b6b766SMario Limonciello goto out_ioctl; 88044b6b766SMario Limonciello } 88144b6b766SMario Limonciello 88244b6b766SMario Limonciello /* let the driver do any filtering and do the call */ 88344b6b766SMario Limonciello wdriver = container_of(wblock->dev.dev.driver, 88444b6b766SMario Limonciello struct wmi_driver, driver); 8855e3e2297SMario Limonciello if (!try_module_get(wdriver->driver.owner)) { 8865e3e2297SMario Limonciello ret = -EBUSY; 8875e3e2297SMario Limonciello goto out_ioctl; 8885e3e2297SMario Limonciello } 88944b6b766SMario Limonciello ret = wdriver->filter_callback(&wblock->dev, cmd, buf); 89044b6b766SMario Limonciello module_put(wdriver->driver.owner); 89144b6b766SMario Limonciello if (ret) 89244b6b766SMario Limonciello goto out_ioctl; 89344b6b766SMario Limonciello 89444b6b766SMario Limonciello /* return the result (only up to our internal buffer size) */ 89544b6b766SMario Limonciello if (copy_to_user(input, buf, wblock->req_buf_size)) { 89644b6b766SMario Limonciello dev_dbg(&wblock->dev.dev, "Copy %llu to user failed\n", 89744b6b766SMario Limonciello wblock->req_buf_size); 89844b6b766SMario Limonciello ret = -EFAULT; 89944b6b766SMario Limonciello } 90044b6b766SMario Limonciello 90144b6b766SMario Limonciello out_ioctl: 90244b6b766SMario Limonciello mutex_unlock(&wblock->char_mutex); 90344b6b766SMario Limonciello return ret; 90444b6b766SMario Limonciello } 90544b6b766SMario Limonciello 90644b6b766SMario Limonciello static const struct file_operations wmi_fops = { 90744b6b766SMario Limonciello .owner = THIS_MODULE, 90844b6b766SMario Limonciello .read = wmi_char_read, 90944b6b766SMario Limonciello .open = wmi_char_open, 91044b6b766SMario Limonciello .unlocked_ioctl = wmi_ioctl, 9111832f2d8SArnd Bergmann .compat_ioctl = compat_ptr_ioctl, 91244b6b766SMario Limonciello }; 913844af950SAndy Lutomirski 914844af950SAndy Lutomirski static int wmi_dev_probe(struct device *dev) 915844af950SAndy Lutomirski { 916844af950SAndy Lutomirski struct wmi_block *wblock = dev_to_wblock(dev); 917844af950SAndy Lutomirski struct wmi_driver *wdriver = 918844af950SAndy Lutomirski container_of(dev->driver, struct wmi_driver, driver); 919844af950SAndy Lutomirski int ret = 0; 92044b6b766SMario Limonciello char *buf; 921844af950SAndy Lutomirski 922844af950SAndy Lutomirski if (ACPI_FAILURE(wmi_method_enable(wblock, 1))) 923844af950SAndy Lutomirski dev_warn(dev, "failed to enable device -- probing anyway\n"); 924844af950SAndy Lutomirski 925844af950SAndy Lutomirski if (wdriver->probe) { 926440c4983SMattias Jacobsson ret = wdriver->probe(dev_to_wdev(dev), 927440c4983SMattias Jacobsson find_guid_context(wblock, wdriver)); 92844b6b766SMario Limonciello if (ret != 0) 92944b6b766SMario Limonciello goto probe_failure; 930844af950SAndy Lutomirski } 931844af950SAndy Lutomirski 93244b6b766SMario Limonciello /* driver wants a character device made */ 93344b6b766SMario Limonciello if (wdriver->filter_callback) { 93444b6b766SMario Limonciello /* check that required buffer size declared by driver or MOF */ 93544b6b766SMario Limonciello if (!wblock->req_buf_size) { 93644b6b766SMario Limonciello dev_err(&wblock->dev.dev, 93744b6b766SMario Limonciello "Required buffer size not set\n"); 93844b6b766SMario Limonciello ret = -EINVAL; 93944b6b766SMario Limonciello goto probe_failure; 94044b6b766SMario Limonciello } 94144b6b766SMario Limonciello 9426fb74107SKees Cook wblock->handler_data = kmalloc(wblock->req_buf_size, 9436fb74107SKees Cook GFP_KERNEL); 94444b6b766SMario Limonciello if (!wblock->handler_data) { 94544b6b766SMario Limonciello ret = -ENOMEM; 94644b6b766SMario Limonciello goto probe_failure; 94744b6b766SMario Limonciello } 94844b6b766SMario Limonciello 9497f166addSAndy Shevchenko buf = kasprintf(GFP_KERNEL, "wmi/%s", wdriver->driver.name); 95044b6b766SMario Limonciello if (!buf) { 95144b6b766SMario Limonciello ret = -ENOMEM; 95244b6b766SMario Limonciello goto probe_string_failure; 95344b6b766SMario Limonciello } 95444b6b766SMario Limonciello wblock->char_dev.minor = MISC_DYNAMIC_MINOR; 95544b6b766SMario Limonciello wblock->char_dev.name = buf; 95644b6b766SMario Limonciello wblock->char_dev.fops = &wmi_fops; 95744b6b766SMario Limonciello wblock->char_dev.mode = 0444; 95844b6b766SMario Limonciello ret = misc_register(&wblock->char_dev); 95944b6b766SMario Limonciello if (ret) { 960501f7e52SJoe Perches dev_warn(dev, "failed to register char dev: %d\n", ret); 96144b6b766SMario Limonciello ret = -ENOMEM; 96244b6b766SMario Limonciello goto probe_misc_failure; 96344b6b766SMario Limonciello } 96444b6b766SMario Limonciello } 96544b6b766SMario Limonciello 96644b6b766SMario Limonciello return 0; 96744b6b766SMario Limonciello 96844b6b766SMario Limonciello probe_misc_failure: 96944b6b766SMario Limonciello kfree(buf); 97044b6b766SMario Limonciello probe_string_failure: 97144b6b766SMario Limonciello kfree(wblock->handler_data); 97244b6b766SMario Limonciello probe_failure: 97344b6b766SMario Limonciello if (ACPI_FAILURE(wmi_method_enable(wblock, 0))) 97444b6b766SMario Limonciello dev_warn(dev, "failed to disable device\n"); 975844af950SAndy Lutomirski return ret; 976844af950SAndy Lutomirski } 977844af950SAndy Lutomirski 978fc7a6209SUwe Kleine-König static void wmi_dev_remove(struct device *dev) 979844af950SAndy Lutomirski { 980844af950SAndy Lutomirski struct wmi_block *wblock = dev_to_wblock(dev); 981844af950SAndy Lutomirski struct wmi_driver *wdriver = 982844af950SAndy Lutomirski container_of(dev->driver, struct wmi_driver, driver); 983844af950SAndy Lutomirski 98444b6b766SMario Limonciello if (wdriver->filter_callback) { 98544b6b766SMario Limonciello misc_deregister(&wblock->char_dev); 98644b6b766SMario Limonciello kfree(wblock->char_dev.name); 9876fb74107SKees Cook kfree(wblock->handler_data); 98844b6b766SMario Limonciello } 98944b6b766SMario Limonciello 990844af950SAndy Lutomirski if (wdriver->remove) 9912b329f56SUwe Kleine-König wdriver->remove(dev_to_wdev(dev)); 992844af950SAndy Lutomirski 993844af950SAndy Lutomirski if (ACPI_FAILURE(wmi_method_enable(wblock, 0))) 994844af950SAndy Lutomirski dev_warn(dev, "failed to disable device\n"); 995844af950SAndy Lutomirski } 996844af950SAndy Lutomirski 997844af950SAndy Lutomirski static struct class wmi_bus_class = { 998844af950SAndy Lutomirski .name = "wmi_bus", 9991caab3c1SMatthew Garrett }; 10001caab3c1SMatthew Garrett 1001844af950SAndy Lutomirski static struct bus_type wmi_bus_type = { 1002844af950SAndy Lutomirski .name = "wmi", 1003844af950SAndy Lutomirski .dev_groups = wmi_groups, 1004844af950SAndy Lutomirski .match = wmi_dev_match, 1005844af950SAndy Lutomirski .uevent = wmi_dev_uevent, 1006844af950SAndy Lutomirski .probe = wmi_dev_probe, 1007844af950SAndy Lutomirski .remove = wmi_dev_remove, 1008844af950SAndy Lutomirski }; 1009844af950SAndy Lutomirski 101069372c1dSBhumika Goyal static const struct device_type wmi_type_event = { 1011d79b1074SAndy Lutomirski .name = "event", 1012d79b1074SAndy Lutomirski .groups = wmi_event_groups, 1013d79b1074SAndy Lutomirski .release = wmi_dev_release, 1014d79b1074SAndy Lutomirski }; 1015d79b1074SAndy Lutomirski 101669372c1dSBhumika Goyal static const struct device_type wmi_type_method = { 1017d79b1074SAndy Lutomirski .name = "method", 1018d4fc91adSAndy Lutomirski .groups = wmi_method_groups, 1019d79b1074SAndy Lutomirski .release = wmi_dev_release, 1020d79b1074SAndy Lutomirski }; 1021d79b1074SAndy Lutomirski 102269372c1dSBhumika Goyal static const struct device_type wmi_type_data = { 1023d79b1074SAndy Lutomirski .name = "data", 1024d4fc91adSAndy Lutomirski .groups = wmi_data_groups, 1025d79b1074SAndy Lutomirski .release = wmi_dev_release, 1026d79b1074SAndy Lutomirski }; 1027d79b1074SAndy Lutomirski 1028fd70da6aSDarren Hart (VMware) static int wmi_create_device(struct device *wmi_bus_dev, 10297f5809bfSAndy Lutomirski struct wmi_block *wblock, 10307f5809bfSAndy Lutomirski struct acpi_device *device) 10311caab3c1SMatthew Garrett { 1032d4fc91adSAndy Lutomirski struct acpi_device_info *info; 1033d4fc91adSAndy Lutomirski char method[5]; 1034d4fc91adSAndy Lutomirski int result; 1035d4fc91adSAndy Lutomirski 103684eacf7eSBarnabás Pőcze if (wblock->gblock.flags & ACPI_WMI_EVENT) { 1037fd70da6aSDarren Hart (VMware) wblock->dev.dev.type = &wmi_type_event; 1038fd70da6aSDarren Hart (VMware) goto out_init; 1039fd70da6aSDarren Hart (VMware) } 1040d4fc91adSAndy Lutomirski 104184eacf7eSBarnabás Pőcze if (wblock->gblock.flags & ACPI_WMI_METHOD) { 1042fd70da6aSDarren Hart (VMware) wblock->dev.dev.type = &wmi_type_method; 104344b6b766SMario Limonciello mutex_init(&wblock->char_mutex); 1044fd70da6aSDarren Hart (VMware) goto out_init; 1045fd70da6aSDarren Hart (VMware) } 1046fd70da6aSDarren Hart (VMware) 1047fd70da6aSDarren Hart (VMware) /* 1048fd70da6aSDarren Hart (VMware) * Data Block Query Control Method (WQxx by convention) is 1049fd70da6aSDarren Hart (VMware) * required per the WMI documentation. If it is not present, 1050fd70da6aSDarren Hart (VMware) * we ignore this data block. 1051fd70da6aSDarren Hart (VMware) */ 1052d4fc91adSAndy Lutomirski strcpy(method, "WQ"); 1053d4fc91adSAndy Lutomirski strncat(method, wblock->gblock.object_id, 2); 1054d4fc91adSAndy Lutomirski result = get_subobj_info(device->handle, method, &info); 1055d4fc91adSAndy Lutomirski 1056fd70da6aSDarren Hart (VMware) if (result) { 1057fd70da6aSDarren Hart (VMware) dev_warn(wmi_bus_dev, 1058501f7e52SJoe Perches "%s data block query control method not found\n", 1059fd70da6aSDarren Hart (VMware) method); 1060fd70da6aSDarren Hart (VMware) return result; 1061fd70da6aSDarren Hart (VMware) } 1062fd70da6aSDarren Hart (VMware) 1063fd70da6aSDarren Hart (VMware) wblock->dev.dev.type = &wmi_type_data; 1064d4fc91adSAndy Lutomirski 1065d4fc91adSAndy Lutomirski /* 1066d4fc91adSAndy Lutomirski * The Microsoft documentation specifically states: 1067d4fc91adSAndy Lutomirski * 1068d4fc91adSAndy Lutomirski * Data blocks registered with only a single instance 1069d4fc91adSAndy Lutomirski * can ignore the parameter. 1070d4fc91adSAndy Lutomirski * 1071fd70da6aSDarren Hart (VMware) * ACPICA will get mad at us if we call the method with the wrong number 1072fd70da6aSDarren Hart (VMware) * of arguments, so check what our method expects. (On some Dell 1073fd70da6aSDarren Hart (VMware) * laptops, WQxx may not be a method at all.) 1074d4fc91adSAndy Lutomirski */ 1075fd70da6aSDarren Hart (VMware) if (info->type != ACPI_TYPE_METHOD || info->param_count == 0) 1076d4fc91adSAndy Lutomirski wblock->read_takes_no_args = true; 1077d4fc91adSAndy Lutomirski 1078d4fc91adSAndy Lutomirski kfree(info); 1079d4fc91adSAndy Lutomirski 1080d4fc91adSAndy Lutomirski strcpy(method, "WS"); 1081d4fc91adSAndy Lutomirski strncat(method, wblock->gblock.object_id, 2); 1082d4fc91adSAndy Lutomirski result = get_subobj_info(device->handle, method, NULL); 1083d4fc91adSAndy Lutomirski 1084fd70da6aSDarren Hart (VMware) if (result == 0) 1085fd70da6aSDarren Hart (VMware) wblock->dev.setable = true; 1086d4fc91adSAndy Lutomirski 1087fd70da6aSDarren Hart (VMware) out_init: 1088fd70da6aSDarren Hart (VMware) wblock->dev.dev.bus = &wmi_bus_type; 1089fd70da6aSDarren Hart (VMware) wblock->dev.dev.parent = wmi_bus_dev; 1090fd70da6aSDarren Hart (VMware) 109184eacf7eSBarnabás Pőcze dev_set_name(&wblock->dev.dev, "%pUL", wblock->gblock.guid); 1092c64eefd4SDmitry Torokhov 10936ee50aaaSDarren Hart (VMware) device_initialize(&wblock->dev.dev); 1094fd70da6aSDarren Hart (VMware) 1095fd70da6aSDarren Hart (VMware) return 0; 10961caab3c1SMatthew Garrett } 10971caab3c1SMatthew Garrett 1098b0e86302SAndy Lutomirski static void wmi_free_devices(struct acpi_device *device) 10991caab3c1SMatthew Garrett { 1100c64eefd4SDmitry Torokhov struct wmi_block *wblock, *next; 11011caab3c1SMatthew Garrett 11021caab3c1SMatthew Garrett /* Delete devices for all the GUIDs */ 1103023b9565SDmitry Torokhov list_for_each_entry_safe(wblock, next, &wmi_block_list, list) { 1104b0e86302SAndy Lutomirski if (wblock->acpi_device == device) { 1105023b9565SDmitry Torokhov list_del(&wblock->list); 1106844af950SAndy Lutomirski device_unregister(&wblock->dev.dev); 1107023b9565SDmitry Torokhov } 11081caab3c1SMatthew Garrett } 1109b0e86302SAndy Lutomirski } 11101caab3c1SMatthew Garrett 11116701cc8fSAndy Shevchenko static bool guid_already_parsed(struct acpi_device *device, const u8 *guid) 1112d1f9e497SCarlos Corbacho { 1113d1f9e497SCarlos Corbacho struct wmi_block *wblock; 1114d1f9e497SCarlos Corbacho 1115b0e86302SAndy Lutomirski list_for_each_entry(wblock, &wmi_block_list, list) { 1116b0e86302SAndy Lutomirski if (memcmp(wblock->gblock.guid, guid, 16) == 0) { 1117b0e86302SAndy Lutomirski /* 1118b0e86302SAndy Lutomirski * Because we historically didn't track the relationship 1119b0e86302SAndy Lutomirski * between GUIDs and ACPI nodes, we don't know whether 1120b0e86302SAndy Lutomirski * we need to suppress GUIDs that are unique on a 1121b0e86302SAndy Lutomirski * given node but duplicated across nodes. 1122b0e86302SAndy Lutomirski */ 1123b0e86302SAndy Lutomirski dev_warn(&device->dev, "duplicate WMI GUID %pUL (first instance was on %s)\n", 1124b0e86302SAndy Lutomirski guid, dev_name(&wblock->acpi_device->dev)); 1125d1f9e497SCarlos Corbacho return true; 1126b0e86302SAndy Lutomirski } 1127b0e86302SAndy Lutomirski } 1128c64eefd4SDmitry Torokhov 1129d1f9e497SCarlos Corbacho return false; 1130d1f9e497SCarlos Corbacho } 1131d1f9e497SCarlos Corbacho 11321caab3c1SMatthew Garrett /* 1133b4f9fe12SLen Brown * Parse the _WDG method for the GUID data blocks 1134b4f9fe12SLen Brown */ 1135844af950SAndy Lutomirski static int parse_wdg(struct device *wmi_bus_dev, struct acpi_device *device) 1136b4f9fe12SLen Brown { 1137b4f9fe12SLen Brown struct acpi_buffer out = {ACPI_ALLOCATE_BUFFER, NULL}; 113837830662SDmitry Torokhov const struct guid_block *gblock; 11396ee50aaaSDarren Hart (VMware) struct wmi_block *wblock, *next; 11406ee50aaaSDarren Hart (VMware) union acpi_object *obj; 1141b4f9fe12SLen Brown acpi_status status; 11426ee50aaaSDarren Hart (VMware) int retval = 0; 1143b4f9fe12SLen Brown u32 i, total; 1144b4f9fe12SLen Brown 11457f5809bfSAndy Lutomirski status = acpi_evaluate_object(device->handle, "_WDG", NULL, &out); 1146b4f9fe12SLen Brown if (ACPI_FAILURE(status)) 1147c64eefd4SDmitry Torokhov return -ENXIO; 1148b4f9fe12SLen Brown 1149c06a2fdeSBarnabás Pőcze obj = out.pointer; 11503d2c63ebSDmitry Torokhov if (!obj) 1151c64eefd4SDmitry Torokhov return -ENXIO; 1152b4f9fe12SLen Brown 115364ed0ab8SDmitry Torokhov if (obj->type != ACPI_TYPE_BUFFER) { 1154c64eefd4SDmitry Torokhov retval = -ENXIO; 115564ed0ab8SDmitry Torokhov goto out_free_pointer; 115664ed0ab8SDmitry Torokhov } 1157b4f9fe12SLen Brown 115837830662SDmitry Torokhov gblock = (const struct guid_block *)obj->buffer.pointer; 1159b4f9fe12SLen Brown total = obj->buffer.length / sizeof(struct guid_block); 1160b4f9fe12SLen Brown 1161b4f9fe12SLen Brown for (i = 0; i < total; i++) { 1162a929aae0SThomas Renninger if (debug_dump_wdg) 1163a929aae0SThomas Renninger wmi_dump_wdg(&gblock[i]); 1164a929aae0SThomas Renninger 1165a1c31bcdSAndy Lutomirski /* 1166a1c31bcdSAndy Lutomirski * Some WMI devices, like those for nVidia hooks, have a 1167a1c31bcdSAndy Lutomirski * duplicate GUID. It's not clear what we should do in this 1168a1c31bcdSAndy Lutomirski * case yet, so for now, we'll just ignore the duplicate 1169a1c31bcdSAndy Lutomirski * for device creation. 1170a1c31bcdSAndy Lutomirski */ 1171a1c31bcdSAndy Lutomirski if (guid_already_parsed(device, gblock[i].guid)) 1172a1c31bcdSAndy Lutomirski continue; 1173a1c31bcdSAndy Lutomirski 117458f6425eSColin King wblock = kzalloc(sizeof(struct wmi_block), GFP_KERNEL); 11756ee50aaaSDarren Hart (VMware) if (!wblock) { 11766ee50aaaSDarren Hart (VMware) retval = -ENOMEM; 11776ee50aaaSDarren Hart (VMware) break; 11786ee50aaaSDarren Hart (VMware) } 117958f6425eSColin King 1180b0e86302SAndy Lutomirski wblock->acpi_device = device; 118158f6425eSColin King wblock->gblock = gblock[i]; 118258f6425eSColin King 118384eacf7eSBarnabás Pőcze retval = wmi_create_device(wmi_bus_dev, wblock, device); 1184fd70da6aSDarren Hart (VMware) if (retval) { 1185fd70da6aSDarren Hart (VMware) kfree(wblock); 1186fd70da6aSDarren Hart (VMware) continue; 1187fd70da6aSDarren Hart (VMware) } 118858f6425eSColin King 118958f6425eSColin King list_add_tail(&wblock->list, &wmi_block_list); 1190b4f9fe12SLen Brown 1191fc3155b2SThomas Renninger if (debug_event) { 1192fc3155b2SThomas Renninger wblock->handler = wmi_notify_debug; 11932d5ab555SDmitry Torokhov wmi_method_enable(wblock, 1); 1194fc3155b2SThomas Renninger } 1195b4f9fe12SLen Brown } 1196b4f9fe12SLen Brown 11976ee50aaaSDarren Hart (VMware) /* 11986ee50aaaSDarren Hart (VMware) * Now that all of the devices are created, add them to the 11996ee50aaaSDarren Hart (VMware) * device tree and probe subdrivers. 12006ee50aaaSDarren Hart (VMware) */ 12016ee50aaaSDarren Hart (VMware) list_for_each_entry_safe(wblock, next, &wmi_block_list, list) { 12026ee50aaaSDarren Hart (VMware) if (wblock->acpi_device != device) 12036ee50aaaSDarren Hart (VMware) continue; 12046ee50aaaSDarren Hart (VMware) 12056ee50aaaSDarren Hart (VMware) retval = device_add(&wblock->dev.dev); 12066ee50aaaSDarren Hart (VMware) if (retval) { 1207501f7e52SJoe Perches dev_err(wmi_bus_dev, "failed to register %pUL\n", 12086ee50aaaSDarren Hart (VMware) wblock->gblock.guid); 12096ee50aaaSDarren Hart (VMware) if (debug_event) 12106ee50aaaSDarren Hart (VMware) wmi_method_enable(wblock, 0); 12116ee50aaaSDarren Hart (VMware) list_del(&wblock->list); 12126ee50aaaSDarren Hart (VMware) put_device(&wblock->dev.dev); 12136ee50aaaSDarren Hart (VMware) } 12146ee50aaaSDarren Hart (VMware) } 1215c64eefd4SDmitry Torokhov 1216a5167c5bSAxel Lin out_free_pointer: 1217a5167c5bSAxel Lin kfree(out.pointer); 1218c64eefd4SDmitry Torokhov return retval; 1219b4f9fe12SLen Brown } 1220b4f9fe12SLen Brown 1221b4f9fe12SLen Brown /* 1222b4f9fe12SLen Brown * WMI can have EmbeddedControl access regions. In which case, we just want to 1223b4f9fe12SLen Brown * hand these off to the EC driver. 1224b4f9fe12SLen Brown */ 1225b4f9fe12SLen Brown static acpi_status 1226b4f9fe12SLen Brown acpi_wmi_ec_space_handler(u32 function, acpi_physical_address address, 1227439913ffSLin Ming u32 bits, u64 *value, 1228b4f9fe12SLen Brown void *handler_context, void *region_context) 1229b4f9fe12SLen Brown { 1230b4f9fe12SLen Brown int result = 0, i = 0; 1231b4f9fe12SLen Brown u8 temp = 0; 1232b4f9fe12SLen Brown 1233b4f9fe12SLen Brown if ((address > 0xFF) || !value) 1234b4f9fe12SLen Brown return AE_BAD_PARAMETER; 1235b4f9fe12SLen Brown 1236b4f9fe12SLen Brown if (function != ACPI_READ && function != ACPI_WRITE) 1237b4f9fe12SLen Brown return AE_BAD_PARAMETER; 1238b4f9fe12SLen Brown 1239b4f9fe12SLen Brown if (bits != 8) 1240b4f9fe12SLen Brown return AE_BAD_PARAMETER; 1241b4f9fe12SLen Brown 1242b4f9fe12SLen Brown if (function == ACPI_READ) { 1243b4f9fe12SLen Brown result = ec_read(address, &temp); 1244439913ffSLin Ming (*value) |= ((u64)temp) << i; 1245b4f9fe12SLen Brown } else { 1246b4f9fe12SLen Brown temp = 0xff & ((*value) >> i); 1247b4f9fe12SLen Brown result = ec_write(address, temp); 1248b4f9fe12SLen Brown } 1249b4f9fe12SLen Brown 1250b4f9fe12SLen Brown switch (result) { 1251b4f9fe12SLen Brown case -EINVAL: 1252b4f9fe12SLen Brown return AE_BAD_PARAMETER; 1253b4f9fe12SLen Brown case -ENODEV: 1254b4f9fe12SLen Brown return AE_NOT_FOUND; 1255b4f9fe12SLen Brown case -ETIME: 1256b4f9fe12SLen Brown return AE_TIME; 1257b4f9fe12SLen Brown default: 1258b4f9fe12SLen Brown return AE_OK; 1259b4f9fe12SLen Brown } 1260b4f9fe12SLen Brown } 1261b4f9fe12SLen Brown 12621686f544SAndy Lutomirski static void acpi_wmi_notify_handler(acpi_handle handle, u32 event, 12631686f544SAndy Lutomirski void *context) 1264b4f9fe12SLen Brown { 1265b4f9fe12SLen Brown struct guid_block *block; 1266b4f9fe12SLen Brown struct wmi_block *wblock; 12671686f544SAndy Lutomirski bool found_it = false; 1268b4f9fe12SLen Brown 1269cedb3b2aSAndy Shevchenko list_for_each_entry(wblock, &wmi_block_list, list) { 1270b4f9fe12SLen Brown block = &wblock->gblock; 1271b4f9fe12SLen Brown 12721686f544SAndy Lutomirski if (wblock->acpi_device->handle == handle && 1273b0e86302SAndy Lutomirski (block->flags & ACPI_WMI_EVENT) && 12743ecace31SBarnabás Pőcze (block->notify_id == event)) { 12751686f544SAndy Lutomirski found_it = true; 12761686f544SAndy Lutomirski break; 12771686f544SAndy Lutomirski } 12781686f544SAndy Lutomirski } 12791686f544SAndy Lutomirski 12801686f544SAndy Lutomirski if (!found_it) 12811686f544SAndy Lutomirski return; 12821686f544SAndy Lutomirski 12831686f544SAndy Lutomirski /* If a driver is bound, then notify the driver. */ 12841686f544SAndy Lutomirski if (wblock->dev.dev.driver) { 12851686f544SAndy Lutomirski struct wmi_driver *driver; 12861686f544SAndy Lutomirski struct acpi_object_list input; 12871686f544SAndy Lutomirski union acpi_object params[1]; 12881686f544SAndy Lutomirski struct acpi_buffer evdata = { ACPI_ALLOCATE_BUFFER, NULL }; 12891686f544SAndy Lutomirski acpi_status status; 12901686f544SAndy Lutomirski 12911686f544SAndy Lutomirski driver = container_of(wblock->dev.dev.driver, 12921686f544SAndy Lutomirski struct wmi_driver, driver); 12931686f544SAndy Lutomirski 12941686f544SAndy Lutomirski input.count = 1; 12951686f544SAndy Lutomirski input.pointer = params; 12961686f544SAndy Lutomirski params[0].type = ACPI_TYPE_INTEGER; 12971686f544SAndy Lutomirski params[0].integer.value = event; 12981686f544SAndy Lutomirski 12991686f544SAndy Lutomirski status = acpi_evaluate_object(wblock->acpi_device->handle, 13001686f544SAndy Lutomirski "_WED", &input, &evdata); 13011686f544SAndy Lutomirski if (ACPI_FAILURE(status)) { 13021686f544SAndy Lutomirski dev_warn(&wblock->dev.dev, 13031686f544SAndy Lutomirski "failed to get event data\n"); 13041686f544SAndy Lutomirski return; 13051686f544SAndy Lutomirski } 13061686f544SAndy Lutomirski 13071686f544SAndy Lutomirski if (driver->notify) 1308c06a2fdeSBarnabás Pőcze driver->notify(&wblock->dev, evdata.pointer); 13091686f544SAndy Lutomirski 13101686f544SAndy Lutomirski kfree(evdata.pointer); 13111686f544SAndy Lutomirski } else if (wblock->handler) { 13121686f544SAndy Lutomirski /* Legacy handler */ 1313b4f9fe12SLen Brown wblock->handler(event, wblock->handler_data); 13141686f544SAndy Lutomirski } 13151686f544SAndy Lutomirski 13166701cc8fSAndy Shevchenko if (debug_event) 13176701cc8fSAndy Shevchenko pr_info("DEBUG Event GUID: %pUL\n", wblock->gblock.guid); 1318b4f9fe12SLen Brown 1319b4f9fe12SLen Brown acpi_bus_generate_netlink_event( 13201686f544SAndy Lutomirski wblock->acpi_device->pnp.device_class, 13211686f544SAndy Lutomirski dev_name(&wblock->dev.dev), 1322b4f9fe12SLen Brown event, 0); 1323b4f9fe12SLen Brown } 1324b4f9fe12SLen Brown 13259599ed91SAndy Lutomirski static int acpi_wmi_remove(struct platform_device *device) 1326b4f9fe12SLen Brown { 13279599ed91SAndy Lutomirski struct acpi_device *acpi_device = ACPI_COMPANION(&device->dev); 13289599ed91SAndy Lutomirski 13299599ed91SAndy Lutomirski acpi_remove_notify_handler(acpi_device->handle, ACPI_DEVICE_NOTIFY, 13301686f544SAndy Lutomirski acpi_wmi_notify_handler); 13319599ed91SAndy Lutomirski acpi_remove_address_space_handler(acpi_device->handle, 1332b4f9fe12SLen Brown ACPI_ADR_SPACE_EC, &acpi_wmi_ec_space_handler); 13339599ed91SAndy Lutomirski wmi_free_devices(acpi_device); 1334c06a2fdeSBarnabás Pőcze device_unregister(dev_get_drvdata(&device->dev)); 1335b4f9fe12SLen Brown 1336b4f9fe12SLen Brown return 0; 1337b4f9fe12SLen Brown } 1338b4f9fe12SLen Brown 13399599ed91SAndy Lutomirski static int acpi_wmi_probe(struct platform_device *device) 1340b4f9fe12SLen Brown { 13419599ed91SAndy Lutomirski struct acpi_device *acpi_device; 1342844af950SAndy Lutomirski struct device *wmi_bus_dev; 1343b4f9fe12SLen Brown acpi_status status; 1344c64eefd4SDmitry Torokhov int error; 1345b4f9fe12SLen Brown 13469599ed91SAndy Lutomirski acpi_device = ACPI_COMPANION(&device->dev); 13479599ed91SAndy Lutomirski if (!acpi_device) { 13489599ed91SAndy Lutomirski dev_err(&device->dev, "ACPI companion is missing\n"); 13499599ed91SAndy Lutomirski return -ENODEV; 13509599ed91SAndy Lutomirski } 13519599ed91SAndy Lutomirski 13529599ed91SAndy Lutomirski status = acpi_install_address_space_handler(acpi_device->handle, 1353b4f9fe12SLen Brown ACPI_ADR_SPACE_EC, 1354b4f9fe12SLen Brown &acpi_wmi_ec_space_handler, 1355b4f9fe12SLen Brown NULL, NULL); 13565212cd67SDmitry Torokhov if (ACPI_FAILURE(status)) { 135746492ee4SAndy Lutomirski dev_err(&device->dev, "Error installing EC region handler\n"); 1358b4f9fe12SLen Brown return -ENODEV; 13595212cd67SDmitry Torokhov } 1360b4f9fe12SLen Brown 13619599ed91SAndy Lutomirski status = acpi_install_notify_handler(acpi_device->handle, 13629599ed91SAndy Lutomirski ACPI_DEVICE_NOTIFY, 13631686f544SAndy Lutomirski acpi_wmi_notify_handler, 13641686f544SAndy Lutomirski NULL); 13651686f544SAndy Lutomirski if (ACPI_FAILURE(status)) { 13661686f544SAndy Lutomirski dev_err(&device->dev, "Error installing notify handler\n"); 13671686f544SAndy Lutomirski error = -ENODEV; 13681686f544SAndy Lutomirski goto err_remove_ec_handler; 13691686f544SAndy Lutomirski } 13701686f544SAndy Lutomirski 1371844af950SAndy Lutomirski wmi_bus_dev = device_create(&wmi_bus_class, &device->dev, MKDEV(0, 0), 1372844af950SAndy Lutomirski NULL, "wmi_bus-%s", dev_name(&device->dev)); 1373844af950SAndy Lutomirski if (IS_ERR(wmi_bus_dev)) { 1374844af950SAndy Lutomirski error = PTR_ERR(wmi_bus_dev); 13751686f544SAndy Lutomirski goto err_remove_notify_handler; 1376844af950SAndy Lutomirski } 13779599ed91SAndy Lutomirski dev_set_drvdata(&device->dev, wmi_bus_dev); 1378844af950SAndy Lutomirski 13799599ed91SAndy Lutomirski error = parse_wdg(wmi_bus_dev, acpi_device); 1380c64eefd4SDmitry Torokhov if (error) { 13818e07514dSDmitry Torokhov pr_err("Failed to parse WDG method\n"); 1382844af950SAndy Lutomirski goto err_remove_busdev; 1383b4f9fe12SLen Brown } 1384b4f9fe12SLen Brown 1385c64eefd4SDmitry Torokhov return 0; 138646492ee4SAndy Lutomirski 1387844af950SAndy Lutomirski err_remove_busdev: 138856afb8d4SYongxin Liu device_unregister(wmi_bus_dev); 1389844af950SAndy Lutomirski 13901686f544SAndy Lutomirski err_remove_notify_handler: 13919599ed91SAndy Lutomirski acpi_remove_notify_handler(acpi_device->handle, ACPI_DEVICE_NOTIFY, 13921686f544SAndy Lutomirski acpi_wmi_notify_handler); 13931686f544SAndy Lutomirski 13941686f544SAndy Lutomirski err_remove_ec_handler: 13959599ed91SAndy Lutomirski acpi_remove_address_space_handler(acpi_device->handle, 139646492ee4SAndy Lutomirski ACPI_ADR_SPACE_EC, 139746492ee4SAndy Lutomirski &acpi_wmi_ec_space_handler); 139846492ee4SAndy Lutomirski 139946492ee4SAndy Lutomirski return error; 1400b4f9fe12SLen Brown } 1401b4f9fe12SLen Brown 1402844af950SAndy Lutomirski int __must_check __wmi_driver_register(struct wmi_driver *driver, 1403844af950SAndy Lutomirski struct module *owner) 1404844af950SAndy Lutomirski { 1405844af950SAndy Lutomirski driver->driver.owner = owner; 1406844af950SAndy Lutomirski driver->driver.bus = &wmi_bus_type; 1407844af950SAndy Lutomirski 1408844af950SAndy Lutomirski return driver_register(&driver->driver); 1409844af950SAndy Lutomirski } 1410844af950SAndy Lutomirski EXPORT_SYMBOL(__wmi_driver_register); 1411844af950SAndy Lutomirski 1412844af950SAndy Lutomirski void wmi_driver_unregister(struct wmi_driver *driver) 1413844af950SAndy Lutomirski { 1414844af950SAndy Lutomirski driver_unregister(&driver->driver); 1415844af950SAndy Lutomirski } 1416844af950SAndy Lutomirski EXPORT_SYMBOL(wmi_driver_unregister); 1417844af950SAndy Lutomirski 1418b4f9fe12SLen Brown static int __init acpi_wmi_init(void) 1419b4f9fe12SLen Brown { 1420c64eefd4SDmitry Torokhov int error; 1421b4f9fe12SLen Brown 1422b4f9fe12SLen Brown if (acpi_disabled) 1423b4f9fe12SLen Brown return -ENODEV; 1424b4f9fe12SLen Brown 1425844af950SAndy Lutomirski error = class_register(&wmi_bus_class); 1426c64eefd4SDmitry Torokhov if (error) 1427c64eefd4SDmitry Torokhov return error; 1428b4f9fe12SLen Brown 1429844af950SAndy Lutomirski error = bus_register(&wmi_bus_type); 1430844af950SAndy Lutomirski if (error) 1431844af950SAndy Lutomirski goto err_unreg_class; 1432844af950SAndy Lutomirski 14339599ed91SAndy Lutomirski error = platform_driver_register(&acpi_wmi_driver); 1434c64eefd4SDmitry Torokhov if (error) { 1435c64eefd4SDmitry Torokhov pr_err("Error loading mapper\n"); 1436844af950SAndy Lutomirski goto err_unreg_bus; 14371caab3c1SMatthew Garrett } 14381caab3c1SMatthew Garrett 14398e07514dSDmitry Torokhov return 0; 1440844af950SAndy Lutomirski 1441844af950SAndy Lutomirski err_unreg_bus: 1442844af950SAndy Lutomirski bus_unregister(&wmi_bus_type); 1443844af950SAndy Lutomirski 144497277717SAlexey Khoroshilov err_unreg_class: 144597277717SAlexey Khoroshilov class_unregister(&wmi_bus_class); 144697277717SAlexey Khoroshilov 1447844af950SAndy Lutomirski return error; 1448b4f9fe12SLen Brown } 1449b4f9fe12SLen Brown 1450b4f9fe12SLen Brown static void __exit acpi_wmi_exit(void) 1451b4f9fe12SLen Brown { 14529599ed91SAndy Lutomirski platform_driver_unregister(&acpi_wmi_driver); 1453844af950SAndy Lutomirski bus_unregister(&wmi_bus_type); 1454303d1fccSMario Limonciello class_unregister(&wmi_bus_class); 1455b4f9fe12SLen Brown } 1456b4f9fe12SLen Brown 145798b8e4e5SRafael J. Wysocki subsys_initcall_sync(acpi_wmi_init); 1458b4f9fe12SLen Brown module_exit(acpi_wmi_exit); 1459