1b4f9fe12SLen Brown /* 2b4f9fe12SLen Brown * ACPI-WMI mapping driver 3b4f9fe12SLen Brown * 4b4f9fe12SLen Brown * Copyright (C) 2007-2008 Carlos Corbacho <carlos@strangeworlds.co.uk> 5b4f9fe12SLen Brown * 6b4f9fe12SLen Brown * GUID parsing code from ldm.c is: 7b4f9fe12SLen Brown * Copyright (C) 2001,2002 Richard Russon <ldm@flatcap.org> 8b4f9fe12SLen Brown * Copyright (c) 2001-2007 Anton Altaparmakov 9b4f9fe12SLen Brown * Copyright (C) 2001,2002 Jakob Kemi <jakob.kemi@telia.com> 10b4f9fe12SLen Brown * 11b4f9fe12SLen Brown * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 12b4f9fe12SLen Brown * 13b4f9fe12SLen Brown * This program is free software; you can redistribute it and/or modify 14b4f9fe12SLen Brown * it under the terms of the GNU General Public License as published by 15b4f9fe12SLen Brown * the Free Software Foundation; either version 2 of the License, or (at 16b4f9fe12SLen Brown * your option) any later version. 17b4f9fe12SLen Brown * 18b4f9fe12SLen Brown * This program is distributed in the hope that it will be useful, but 19b4f9fe12SLen Brown * WITHOUT ANY WARRANTY; without even the implied warranty of 20b4f9fe12SLen Brown * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 21b4f9fe12SLen Brown * General Public License for more details. 22b4f9fe12SLen Brown * 23b4f9fe12SLen Brown * You should have received a copy of the GNU General Public License along 24b4f9fe12SLen Brown * with this program; if not, write to the Free Software Foundation, Inc., 25b4f9fe12SLen Brown * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. 26b4f9fe12SLen Brown * 27b4f9fe12SLen Brown * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 28b4f9fe12SLen Brown */ 29b4f9fe12SLen Brown 308e07514dSDmitry Torokhov #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 318e07514dSDmitry Torokhov 32b4f9fe12SLen Brown #include <linux/kernel.h> 33b4f9fe12SLen Brown #include <linux/init.h> 34b4f9fe12SLen Brown #include <linux/types.h> 351caab3c1SMatthew Garrett #include <linux/device.h> 36b4f9fe12SLen Brown #include <linux/list.h> 37b4f9fe12SLen Brown #include <linux/acpi.h> 385a0e3ad6STejun Heo #include <linux/slab.h> 397c52d551SPaul Gortmaker #include <linux/module.h> 40b4f9fe12SLen Brown #include <acpi/acpi_bus.h> 41b4f9fe12SLen Brown #include <acpi/acpi_drivers.h> 42b4f9fe12SLen Brown 43b4f9fe12SLen Brown ACPI_MODULE_NAME("wmi"); 44b4f9fe12SLen Brown MODULE_AUTHOR("Carlos Corbacho"); 45b4f9fe12SLen Brown MODULE_DESCRIPTION("ACPI-WMI Mapping Driver"); 46b4f9fe12SLen Brown MODULE_LICENSE("GPL"); 47b4f9fe12SLen Brown 48b4f9fe12SLen Brown #define ACPI_WMI_CLASS "wmi" 49b4f9fe12SLen Brown 50b4f9fe12SLen Brown static DEFINE_MUTEX(wmi_data_lock); 51762e1a2fSDmitry Torokhov static LIST_HEAD(wmi_block_list); 52b4f9fe12SLen Brown 53b4f9fe12SLen Brown struct guid_block { 54b4f9fe12SLen Brown char guid[16]; 55b4f9fe12SLen Brown union { 56b4f9fe12SLen Brown char object_id[2]; 57b4f9fe12SLen Brown struct { 58b4f9fe12SLen Brown unsigned char notify_id; 59b4f9fe12SLen Brown unsigned char reserved; 60b4f9fe12SLen Brown }; 61b4f9fe12SLen Brown }; 62b4f9fe12SLen Brown u8 instance_count; 63b4f9fe12SLen Brown u8 flags; 64b4f9fe12SLen Brown }; 65b4f9fe12SLen Brown 66b4f9fe12SLen Brown struct wmi_block { 67b4f9fe12SLen Brown struct list_head list; 68b4f9fe12SLen Brown struct guid_block gblock; 69b4f9fe12SLen Brown acpi_handle handle; 70b4f9fe12SLen Brown wmi_notify_handler handler; 71b4f9fe12SLen Brown void *handler_data; 72c64eefd4SDmitry Torokhov struct device dev; 73b4f9fe12SLen Brown }; 74b4f9fe12SLen Brown 75b4f9fe12SLen Brown 76b4f9fe12SLen Brown /* 77b4f9fe12SLen Brown * If the GUID data block is marked as expensive, we must enable and 78b4f9fe12SLen Brown * explicitily disable data collection. 79b4f9fe12SLen Brown */ 80b4f9fe12SLen Brown #define ACPI_WMI_EXPENSIVE 0x1 81b4f9fe12SLen Brown #define ACPI_WMI_METHOD 0x2 /* GUID is a method */ 82b4f9fe12SLen Brown #define ACPI_WMI_STRING 0x4 /* GUID takes & returns a string */ 83b4f9fe12SLen Brown #define ACPI_WMI_EVENT 0x8 /* GUID is an event */ 84b4f9fe12SLen Brown 8590ab5ee9SRusty Russell static bool debug_event; 86fc3155b2SThomas Renninger module_param(debug_event, bool, 0444); 87fc3155b2SThomas Renninger MODULE_PARM_DESC(debug_event, 88fc3155b2SThomas Renninger "Log WMI Events [0/1]"); 89fc3155b2SThomas Renninger 9090ab5ee9SRusty Russell static bool debug_dump_wdg; 91a929aae0SThomas Renninger module_param(debug_dump_wdg, bool, 0444); 92a929aae0SThomas Renninger MODULE_PARM_DESC(debug_dump_wdg, 93a929aae0SThomas Renninger "Dump available WMI interfaces [0/1]"); 94a929aae0SThomas Renninger 9551fac838SRafael J. Wysocki static int acpi_wmi_remove(struct acpi_device *device); 96b4f9fe12SLen Brown static int acpi_wmi_add(struct acpi_device *device); 97f61bb939SBjorn Helgaas static void acpi_wmi_notify(struct acpi_device *device, u32 event); 98b4f9fe12SLen Brown 99b4f9fe12SLen Brown static const struct acpi_device_id wmi_device_ids[] = { 100b4f9fe12SLen Brown {"PNP0C14", 0}, 101b4f9fe12SLen Brown {"pnp0c14", 0}, 102b4f9fe12SLen Brown {"", 0}, 103b4f9fe12SLen Brown }; 104b4f9fe12SLen Brown MODULE_DEVICE_TABLE(acpi, wmi_device_ids); 105b4f9fe12SLen Brown 106b4f9fe12SLen Brown static struct acpi_driver acpi_wmi_driver = { 107b4f9fe12SLen Brown .name = "wmi", 108b4f9fe12SLen Brown .class = ACPI_WMI_CLASS, 109b4f9fe12SLen Brown .ids = wmi_device_ids, 110b4f9fe12SLen Brown .ops = { 111b4f9fe12SLen Brown .add = acpi_wmi_add, 112b4f9fe12SLen Brown .remove = acpi_wmi_remove, 113f61bb939SBjorn Helgaas .notify = acpi_wmi_notify, 114b4f9fe12SLen Brown }, 115b4f9fe12SLen Brown }; 116b4f9fe12SLen Brown 117b4f9fe12SLen Brown /* 118b4f9fe12SLen Brown * GUID parsing functions 119b4f9fe12SLen Brown */ 120b4f9fe12SLen Brown 121b4f9fe12SLen Brown /** 122b4f9fe12SLen Brown * wmi_parse_hexbyte - Convert a ASCII hex number to a byte 123b4f9fe12SLen Brown * @src: Pointer to at least 2 characters to convert. 124b4f9fe12SLen Brown * 125b4f9fe12SLen Brown * Convert a two character ASCII hex string to a number. 126b4f9fe12SLen Brown * 127b4f9fe12SLen Brown * Return: 0-255 Success, the byte was parsed correctly 128b4f9fe12SLen Brown * -1 Error, an invalid character was supplied 129b4f9fe12SLen Brown */ 130b4f9fe12SLen Brown static int wmi_parse_hexbyte(const u8 *src) 131b4f9fe12SLen Brown { 132b4f9fe12SLen Brown int h; 133392bd8b5SAndy Shevchenko int value; 134b4f9fe12SLen Brown 135b4f9fe12SLen Brown /* high part */ 136392bd8b5SAndy Shevchenko h = value = hex_to_bin(src[0]); 137392bd8b5SAndy Shevchenko if (value < 0) 138b4f9fe12SLen Brown return -1; 139b4f9fe12SLen Brown 140b4f9fe12SLen Brown /* low part */ 141392bd8b5SAndy Shevchenko value = hex_to_bin(src[1]); 142392bd8b5SAndy Shevchenko if (value >= 0) 143392bd8b5SAndy Shevchenko return (h << 4) | value; 144b4f9fe12SLen Brown return -1; 145b4f9fe12SLen Brown } 146b4f9fe12SLen Brown 147b4f9fe12SLen Brown /** 148b4f9fe12SLen Brown * wmi_swap_bytes - Rearrange GUID bytes to match GUID binary 149b4f9fe12SLen Brown * @src: Memory block holding binary GUID (16 bytes) 150b4f9fe12SLen Brown * @dest: Memory block to hold byte swapped binary GUID (16 bytes) 151b4f9fe12SLen Brown * 152b4f9fe12SLen Brown * Byte swap a binary GUID to match it's real GUID value 153b4f9fe12SLen Brown */ 154b4f9fe12SLen Brown static void wmi_swap_bytes(u8 *src, u8 *dest) 155b4f9fe12SLen Brown { 156b4f9fe12SLen Brown int i; 157b4f9fe12SLen Brown 158b4f9fe12SLen Brown for (i = 0; i <= 3; i++) 159b4f9fe12SLen Brown memcpy(dest + i, src + (3 - i), 1); 160b4f9fe12SLen Brown 161b4f9fe12SLen Brown for (i = 0; i <= 1; i++) 162b4f9fe12SLen Brown memcpy(dest + 4 + i, src + (5 - i), 1); 163b4f9fe12SLen Brown 164b4f9fe12SLen Brown for (i = 0; i <= 1; i++) 165b4f9fe12SLen Brown memcpy(dest + 6 + i, src + (7 - i), 1); 166b4f9fe12SLen Brown 167b4f9fe12SLen Brown memcpy(dest + 8, src + 8, 8); 168b4f9fe12SLen Brown } 169b4f9fe12SLen Brown 170b4f9fe12SLen Brown /** 171b4f9fe12SLen Brown * wmi_parse_guid - Convert GUID from ASCII to binary 172b4f9fe12SLen Brown * @src: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba 173b4f9fe12SLen Brown * @dest: Memory block to hold binary GUID (16 bytes) 174b4f9fe12SLen Brown * 175b4f9fe12SLen Brown * N.B. The GUID need not be NULL terminated. 176b4f9fe12SLen Brown * 177b4f9fe12SLen Brown * Return: 'true' @dest contains binary GUID 178b4f9fe12SLen Brown * 'false' @dest contents are undefined 179b4f9fe12SLen Brown */ 180b4f9fe12SLen Brown static bool wmi_parse_guid(const u8 *src, u8 *dest) 181b4f9fe12SLen Brown { 182b4f9fe12SLen Brown static const int size[] = { 4, 2, 2, 2, 6 }; 183b4f9fe12SLen Brown int i, j, v; 184b4f9fe12SLen Brown 185b4f9fe12SLen Brown if (src[8] != '-' || src[13] != '-' || 186b4f9fe12SLen Brown src[18] != '-' || src[23] != '-') 187b4f9fe12SLen Brown return false; 188b4f9fe12SLen Brown 189b4f9fe12SLen Brown for (j = 0; j < 5; j++, src++) { 190b4f9fe12SLen Brown for (i = 0; i < size[j]; i++, src += 2, *dest++ = v) { 191b4f9fe12SLen Brown v = wmi_parse_hexbyte(src); 192b4f9fe12SLen Brown if (v < 0) 193b4f9fe12SLen Brown return false; 194b4f9fe12SLen Brown } 195b4f9fe12SLen Brown } 196b4f9fe12SLen Brown 197b4f9fe12SLen Brown return true; 198b4f9fe12SLen Brown } 199b4f9fe12SLen Brown 2001caab3c1SMatthew Garrett /* 2011caab3c1SMatthew Garrett * Convert a raw GUID to the ACII string representation 2021caab3c1SMatthew Garrett */ 2031caab3c1SMatthew Garrett static int wmi_gtoa(const char *in, char *out) 2041caab3c1SMatthew Garrett { 2051caab3c1SMatthew Garrett int i; 2061caab3c1SMatthew Garrett 2071caab3c1SMatthew Garrett for (i = 3; i >= 0; i--) 2081caab3c1SMatthew Garrett out += sprintf(out, "%02X", in[i] & 0xFF); 2091caab3c1SMatthew Garrett 2101caab3c1SMatthew Garrett out += sprintf(out, "-"); 2111caab3c1SMatthew Garrett out += sprintf(out, "%02X", in[5] & 0xFF); 2121caab3c1SMatthew Garrett out += sprintf(out, "%02X", in[4] & 0xFF); 2131caab3c1SMatthew Garrett out += sprintf(out, "-"); 2141caab3c1SMatthew Garrett out += sprintf(out, "%02X", in[7] & 0xFF); 2151caab3c1SMatthew Garrett out += sprintf(out, "%02X", in[6] & 0xFF); 2161caab3c1SMatthew Garrett out += sprintf(out, "-"); 2171caab3c1SMatthew Garrett out += sprintf(out, "%02X", in[8] & 0xFF); 2181caab3c1SMatthew Garrett out += sprintf(out, "%02X", in[9] & 0xFF); 2191caab3c1SMatthew Garrett out += sprintf(out, "-"); 2201caab3c1SMatthew Garrett 2211caab3c1SMatthew Garrett for (i = 10; i <= 15; i++) 2221caab3c1SMatthew Garrett out += sprintf(out, "%02X", in[i] & 0xFF); 2231caab3c1SMatthew Garrett 2244e4304d7SDmitry Torokhov *out = '\0'; 2251caab3c1SMatthew Garrett return 0; 2261caab3c1SMatthew Garrett } 2271caab3c1SMatthew Garrett 228b4f9fe12SLen Brown static bool find_guid(const char *guid_string, struct wmi_block **out) 229b4f9fe12SLen Brown { 230b4f9fe12SLen Brown char tmp[16], guid_input[16]; 231b4f9fe12SLen Brown struct wmi_block *wblock; 232b4f9fe12SLen Brown struct guid_block *block; 233b4f9fe12SLen Brown struct list_head *p; 234b4f9fe12SLen Brown 235b4f9fe12SLen Brown wmi_parse_guid(guid_string, tmp); 236b4f9fe12SLen Brown wmi_swap_bytes(tmp, guid_input); 237b4f9fe12SLen Brown 238762e1a2fSDmitry Torokhov list_for_each(p, &wmi_block_list) { 239b4f9fe12SLen Brown wblock = list_entry(p, struct wmi_block, list); 240b4f9fe12SLen Brown block = &wblock->gblock; 241b4f9fe12SLen Brown 242b4f9fe12SLen Brown if (memcmp(block->guid, guid_input, 16) == 0) { 243b4f9fe12SLen Brown if (out) 244b4f9fe12SLen Brown *out = wblock; 245b4f9fe12SLen Brown return 1; 246b4f9fe12SLen Brown } 247b4f9fe12SLen Brown } 248b4f9fe12SLen Brown return 0; 249b4f9fe12SLen Brown } 250b4f9fe12SLen Brown 251b4f9fe12SLen Brown static acpi_status wmi_method_enable(struct wmi_block *wblock, int enable) 252b4f9fe12SLen Brown { 253b4f9fe12SLen Brown struct guid_block *block = NULL; 254b4f9fe12SLen Brown char method[5]; 255b4f9fe12SLen Brown struct acpi_object_list input; 256b4f9fe12SLen Brown union acpi_object params[1]; 257b4f9fe12SLen Brown acpi_status status; 258b4f9fe12SLen Brown acpi_handle handle; 259b4f9fe12SLen Brown 260b4f9fe12SLen Brown block = &wblock->gblock; 261b4f9fe12SLen Brown handle = wblock->handle; 262b4f9fe12SLen Brown 263b4f9fe12SLen Brown if (!block) 264b4f9fe12SLen Brown return AE_NOT_EXIST; 265b4f9fe12SLen Brown 266b4f9fe12SLen Brown input.count = 1; 267b4f9fe12SLen Brown input.pointer = params; 268b4f9fe12SLen Brown params[0].type = ACPI_TYPE_INTEGER; 269b4f9fe12SLen Brown params[0].integer.value = enable; 270b4f9fe12SLen Brown 271b4f9fe12SLen Brown snprintf(method, 5, "WE%02X", block->notify_id); 272b4f9fe12SLen Brown status = acpi_evaluate_object(handle, method, &input, NULL); 273b4f9fe12SLen Brown 274b4f9fe12SLen Brown if (status != AE_OK && status != AE_NOT_FOUND) 275b4f9fe12SLen Brown return status; 276b4f9fe12SLen Brown else 277b4f9fe12SLen Brown return AE_OK; 278b4f9fe12SLen Brown } 279b4f9fe12SLen Brown 280b4f9fe12SLen Brown /* 281b4f9fe12SLen Brown * Exported WMI functions 282b4f9fe12SLen Brown */ 283b4f9fe12SLen Brown /** 284b4f9fe12SLen Brown * wmi_evaluate_method - Evaluate a WMI method 285b4f9fe12SLen Brown * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba 286b4f9fe12SLen Brown * @instance: Instance index 287b4f9fe12SLen Brown * @method_id: Method ID to call 288b4f9fe12SLen Brown * &in: Buffer containing input for the method call 289b4f9fe12SLen Brown * &out: Empty buffer to return the method results 290b4f9fe12SLen Brown * 291b4f9fe12SLen Brown * Call an ACPI-WMI method 292b4f9fe12SLen Brown */ 293b4f9fe12SLen Brown acpi_status wmi_evaluate_method(const char *guid_string, u8 instance, 294b4f9fe12SLen Brown u32 method_id, const struct acpi_buffer *in, struct acpi_buffer *out) 295b4f9fe12SLen Brown { 296b4f9fe12SLen Brown struct guid_block *block = NULL; 297b4f9fe12SLen Brown struct wmi_block *wblock = NULL; 298b4f9fe12SLen Brown acpi_handle handle; 299b4f9fe12SLen Brown acpi_status status; 300b4f9fe12SLen Brown struct acpi_object_list input; 301b4f9fe12SLen Brown union acpi_object params[3]; 302f3d83e24SCostantino Leandro char method[5] = "WM"; 303b4f9fe12SLen Brown 304b4f9fe12SLen Brown if (!find_guid(guid_string, &wblock)) 305b4f9fe12SLen Brown return AE_ERROR; 306b4f9fe12SLen Brown 307b4f9fe12SLen Brown block = &wblock->gblock; 308b4f9fe12SLen Brown handle = wblock->handle; 309b4f9fe12SLen Brown 310b4f9fe12SLen Brown if (!(block->flags & ACPI_WMI_METHOD)) 311b4f9fe12SLen Brown return AE_BAD_DATA; 312b4f9fe12SLen Brown 313b4f9fe12SLen Brown if (block->instance_count < instance) 314b4f9fe12SLen Brown return AE_BAD_PARAMETER; 315b4f9fe12SLen Brown 316b4f9fe12SLen Brown input.count = 2; 317b4f9fe12SLen Brown input.pointer = params; 318b4f9fe12SLen Brown params[0].type = ACPI_TYPE_INTEGER; 319b4f9fe12SLen Brown params[0].integer.value = instance; 320b4f9fe12SLen Brown params[1].type = ACPI_TYPE_INTEGER; 321b4f9fe12SLen Brown params[1].integer.value = method_id; 322b4f9fe12SLen Brown 323b4f9fe12SLen Brown if (in) { 324b4f9fe12SLen Brown input.count = 3; 325b4f9fe12SLen Brown 326b4f9fe12SLen Brown if (block->flags & ACPI_WMI_STRING) { 327b4f9fe12SLen Brown params[2].type = ACPI_TYPE_STRING; 328b4f9fe12SLen Brown } else { 329b4f9fe12SLen Brown params[2].type = ACPI_TYPE_BUFFER; 330b4f9fe12SLen Brown } 331b4f9fe12SLen Brown params[2].buffer.length = in->length; 332b4f9fe12SLen Brown params[2].buffer.pointer = in->pointer; 333b4f9fe12SLen Brown } 334b4f9fe12SLen Brown 335b4f9fe12SLen Brown strncat(method, block->object_id, 2); 336b4f9fe12SLen Brown 337b4f9fe12SLen Brown status = acpi_evaluate_object(handle, method, &input, out); 338b4f9fe12SLen Brown 339b4f9fe12SLen Brown return status; 340b4f9fe12SLen Brown } 341b4f9fe12SLen Brown EXPORT_SYMBOL_GPL(wmi_evaluate_method); 342b4f9fe12SLen Brown 343b4f9fe12SLen Brown /** 344b4f9fe12SLen Brown * wmi_query_block - Return contents of a WMI block 345b4f9fe12SLen Brown * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba 346b4f9fe12SLen Brown * @instance: Instance index 347b4f9fe12SLen Brown * &out: Empty buffer to return the contents of the data block to 348b4f9fe12SLen Brown * 349b4f9fe12SLen Brown * Return the contents of an ACPI-WMI data block to a buffer 350b4f9fe12SLen Brown */ 351b4f9fe12SLen Brown acpi_status wmi_query_block(const char *guid_string, u8 instance, 352b4f9fe12SLen Brown struct acpi_buffer *out) 353b4f9fe12SLen Brown { 354b4f9fe12SLen Brown struct guid_block *block = NULL; 355b4f9fe12SLen Brown struct wmi_block *wblock = NULL; 356b4f9fe12SLen Brown acpi_handle handle, wc_handle; 357b4f9fe12SLen Brown acpi_status status, wc_status = AE_ERROR; 358b4f9fe12SLen Brown struct acpi_object_list input, wc_input; 359b4f9fe12SLen Brown union acpi_object wc_params[1], wq_params[1]; 360f3d83e24SCostantino Leandro char method[5]; 361f3d83e24SCostantino Leandro char wc_method[5] = "WC"; 362b4f9fe12SLen Brown 363b4f9fe12SLen Brown if (!guid_string || !out) 364b4f9fe12SLen Brown return AE_BAD_PARAMETER; 365b4f9fe12SLen Brown 366b4f9fe12SLen Brown if (!find_guid(guid_string, &wblock)) 367b4f9fe12SLen Brown return AE_ERROR; 368b4f9fe12SLen Brown 369b4f9fe12SLen Brown block = &wblock->gblock; 370b4f9fe12SLen Brown handle = wblock->handle; 371b4f9fe12SLen Brown 372b4f9fe12SLen Brown if (block->instance_count < instance) 373b4f9fe12SLen Brown return AE_BAD_PARAMETER; 374b4f9fe12SLen Brown 375b4f9fe12SLen Brown /* Check GUID is a data block */ 376b4f9fe12SLen Brown if (block->flags & (ACPI_WMI_EVENT | ACPI_WMI_METHOD)) 377b4f9fe12SLen Brown return AE_ERROR; 378b4f9fe12SLen Brown 379b4f9fe12SLen Brown input.count = 1; 380b4f9fe12SLen Brown input.pointer = wq_params; 381b4f9fe12SLen Brown wq_params[0].type = ACPI_TYPE_INTEGER; 382b4f9fe12SLen Brown wq_params[0].integer.value = instance; 383b4f9fe12SLen Brown 384b4f9fe12SLen Brown /* 385b4f9fe12SLen Brown * If ACPI_WMI_EXPENSIVE, call the relevant WCxx method first to 386b4f9fe12SLen Brown * enable collection. 387b4f9fe12SLen Brown */ 388b4f9fe12SLen Brown if (block->flags & ACPI_WMI_EXPENSIVE) { 389b4f9fe12SLen Brown wc_input.count = 1; 390b4f9fe12SLen Brown wc_input.pointer = wc_params; 391b4f9fe12SLen Brown wc_params[0].type = ACPI_TYPE_INTEGER; 392b4f9fe12SLen Brown wc_params[0].integer.value = 1; 393b4f9fe12SLen Brown 394b4f9fe12SLen Brown strncat(wc_method, block->object_id, 2); 395b4f9fe12SLen Brown 396b4f9fe12SLen Brown /* 397b4f9fe12SLen Brown * Some GUIDs break the specification by declaring themselves 398b4f9fe12SLen Brown * expensive, but have no corresponding WCxx method. So we 399b4f9fe12SLen Brown * should not fail if this happens. 400b4f9fe12SLen Brown */ 401b4f9fe12SLen Brown wc_status = acpi_get_handle(handle, wc_method, &wc_handle); 402b4f9fe12SLen Brown if (ACPI_SUCCESS(wc_status)) 403b4f9fe12SLen Brown wc_status = acpi_evaluate_object(handle, wc_method, 404b4f9fe12SLen Brown &wc_input, NULL); 405b4f9fe12SLen Brown } 406b4f9fe12SLen Brown 407b4f9fe12SLen Brown strcpy(method, "WQ"); 408b4f9fe12SLen Brown strncat(method, block->object_id, 2); 409b4f9fe12SLen Brown 410b4f9fe12SLen Brown status = acpi_evaluate_object(handle, method, &input, out); 411b4f9fe12SLen Brown 412b4f9fe12SLen Brown /* 413b4f9fe12SLen Brown * If ACPI_WMI_EXPENSIVE, call the relevant WCxx method, even if 414b4f9fe12SLen Brown * the WQxx method failed - we should disable collection anyway. 415b4f9fe12SLen Brown */ 416b4f9fe12SLen Brown if ((block->flags & ACPI_WMI_EXPENSIVE) && ACPI_SUCCESS(wc_status)) { 417b4f9fe12SLen Brown wc_params[0].integer.value = 0; 418b4f9fe12SLen Brown status = acpi_evaluate_object(handle, 419b4f9fe12SLen Brown wc_method, &wc_input, NULL); 420b4f9fe12SLen Brown } 421b4f9fe12SLen Brown 422b4f9fe12SLen Brown return status; 423b4f9fe12SLen Brown } 424b4f9fe12SLen Brown EXPORT_SYMBOL_GPL(wmi_query_block); 425b4f9fe12SLen Brown 426b4f9fe12SLen Brown /** 427b4f9fe12SLen Brown * wmi_set_block - Write to a WMI block 428b4f9fe12SLen Brown * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba 429b4f9fe12SLen Brown * @instance: Instance index 430b4f9fe12SLen Brown * &in: Buffer containing new values for the data block 431b4f9fe12SLen Brown * 432b4f9fe12SLen Brown * Write the contents of the input buffer to an ACPI-WMI data block 433b4f9fe12SLen Brown */ 434b4f9fe12SLen Brown acpi_status wmi_set_block(const char *guid_string, u8 instance, 435b4f9fe12SLen Brown const struct acpi_buffer *in) 436b4f9fe12SLen Brown { 437b4f9fe12SLen Brown struct guid_block *block = NULL; 438b4f9fe12SLen Brown struct wmi_block *wblock = NULL; 439b4f9fe12SLen Brown acpi_handle handle; 440b4f9fe12SLen Brown struct acpi_object_list input; 441b4f9fe12SLen Brown union acpi_object params[2]; 442f3d83e24SCostantino Leandro char method[5] = "WS"; 443b4f9fe12SLen Brown 444b4f9fe12SLen Brown if (!guid_string || !in) 445b4f9fe12SLen Brown return AE_BAD_DATA; 446b4f9fe12SLen Brown 447b4f9fe12SLen Brown if (!find_guid(guid_string, &wblock)) 448b4f9fe12SLen Brown return AE_ERROR; 449b4f9fe12SLen Brown 450b4f9fe12SLen Brown block = &wblock->gblock; 451b4f9fe12SLen Brown handle = wblock->handle; 452b4f9fe12SLen Brown 453b4f9fe12SLen Brown if (block->instance_count < instance) 454b4f9fe12SLen Brown return AE_BAD_PARAMETER; 455b4f9fe12SLen Brown 456b4f9fe12SLen Brown /* Check GUID is a data block */ 457b4f9fe12SLen Brown if (block->flags & (ACPI_WMI_EVENT | ACPI_WMI_METHOD)) 458b4f9fe12SLen Brown return AE_ERROR; 459b4f9fe12SLen Brown 460b4f9fe12SLen Brown input.count = 2; 461b4f9fe12SLen Brown input.pointer = params; 462b4f9fe12SLen Brown params[0].type = ACPI_TYPE_INTEGER; 463b4f9fe12SLen Brown params[0].integer.value = instance; 464b4f9fe12SLen Brown 465b4f9fe12SLen Brown if (block->flags & ACPI_WMI_STRING) { 466b4f9fe12SLen Brown params[1].type = ACPI_TYPE_STRING; 467b4f9fe12SLen Brown } else { 468b4f9fe12SLen Brown params[1].type = ACPI_TYPE_BUFFER; 469b4f9fe12SLen Brown } 470b4f9fe12SLen Brown params[1].buffer.length = in->length; 471b4f9fe12SLen Brown params[1].buffer.pointer = in->pointer; 472b4f9fe12SLen Brown 473b4f9fe12SLen Brown strncat(method, block->object_id, 2); 474b4f9fe12SLen Brown 475b4f9fe12SLen Brown return acpi_evaluate_object(handle, method, &input, NULL); 476b4f9fe12SLen Brown } 477b4f9fe12SLen Brown EXPORT_SYMBOL_GPL(wmi_set_block); 478b4f9fe12SLen Brown 47937830662SDmitry Torokhov static void wmi_dump_wdg(const struct guid_block *g) 480a929aae0SThomas Renninger { 481a929aae0SThomas Renninger char guid_string[37]; 482a929aae0SThomas Renninger 483a929aae0SThomas Renninger wmi_gtoa(g->guid, guid_string); 4848e07514dSDmitry Torokhov 4858e07514dSDmitry Torokhov pr_info("%s:\n", guid_string); 4868e07514dSDmitry Torokhov pr_info("\tobject_id: %c%c\n", g->object_id[0], g->object_id[1]); 4878e07514dSDmitry Torokhov pr_info("\tnotify_id: %02X\n", g->notify_id); 4888e07514dSDmitry Torokhov pr_info("\treserved: %02X\n", g->reserved); 4898e07514dSDmitry Torokhov pr_info("\tinstance_count: %d\n", g->instance_count); 4908e07514dSDmitry Torokhov pr_info("\tflags: %#x", g->flags); 491a929aae0SThomas Renninger if (g->flags) { 492a929aae0SThomas Renninger if (g->flags & ACPI_WMI_EXPENSIVE) 4938e07514dSDmitry Torokhov pr_cont(" ACPI_WMI_EXPENSIVE"); 494a929aae0SThomas Renninger if (g->flags & ACPI_WMI_METHOD) 4958e07514dSDmitry Torokhov pr_cont(" ACPI_WMI_METHOD"); 496a929aae0SThomas Renninger if (g->flags & ACPI_WMI_STRING) 4978e07514dSDmitry Torokhov pr_cont(" ACPI_WMI_STRING"); 498a929aae0SThomas Renninger if (g->flags & ACPI_WMI_EVENT) 4998e07514dSDmitry Torokhov pr_cont(" ACPI_WMI_EVENT"); 500a929aae0SThomas Renninger } 5018e07514dSDmitry Torokhov pr_cont("\n"); 502a929aae0SThomas Renninger 503a929aae0SThomas Renninger } 504a929aae0SThomas Renninger 505fc3155b2SThomas Renninger static void wmi_notify_debug(u32 value, void *context) 506fc3155b2SThomas Renninger { 507fc3155b2SThomas Renninger struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL }; 508fc3155b2SThomas Renninger union acpi_object *obj; 5091492616aSAxel Lin acpi_status status; 510fc3155b2SThomas Renninger 5111492616aSAxel Lin status = wmi_get_event_data(value, &response); 5121492616aSAxel Lin if (status != AE_OK) { 5138e07514dSDmitry Torokhov pr_info("bad event status 0x%x\n", status); 5141492616aSAxel Lin return; 5151492616aSAxel Lin } 516fc3155b2SThomas Renninger 517fc3155b2SThomas Renninger obj = (union acpi_object *)response.pointer; 518fc3155b2SThomas Renninger 519fc3155b2SThomas Renninger if (!obj) 520fc3155b2SThomas Renninger return; 521fc3155b2SThomas Renninger 5228e07514dSDmitry Torokhov pr_info("DEBUG Event "); 523fc3155b2SThomas Renninger switch(obj->type) { 524fc3155b2SThomas Renninger case ACPI_TYPE_BUFFER: 5258e07514dSDmitry Torokhov pr_cont("BUFFER_TYPE - length %d\n", obj->buffer.length); 526fc3155b2SThomas Renninger break; 527fc3155b2SThomas Renninger case ACPI_TYPE_STRING: 5288e07514dSDmitry Torokhov pr_cont("STRING_TYPE - %s\n", obj->string.pointer); 529fc3155b2SThomas Renninger break; 530fc3155b2SThomas Renninger case ACPI_TYPE_INTEGER: 5318e07514dSDmitry Torokhov pr_cont("INTEGER_TYPE - %llu\n", obj->integer.value); 532fc3155b2SThomas Renninger break; 533fc3155b2SThomas Renninger case ACPI_TYPE_PACKAGE: 5348e07514dSDmitry Torokhov pr_cont("PACKAGE_TYPE - %d elements\n", obj->package.count); 535fc3155b2SThomas Renninger break; 536fc3155b2SThomas Renninger default: 5378e07514dSDmitry Torokhov pr_cont("object type 0x%X\n", obj->type); 538fc3155b2SThomas Renninger } 5391492616aSAxel Lin kfree(obj); 540fc3155b2SThomas Renninger } 541fc3155b2SThomas Renninger 542b4f9fe12SLen Brown /** 543b4f9fe12SLen Brown * wmi_install_notify_handler - Register handler for WMI events 544b4f9fe12SLen Brown * @handler: Function to handle notifications 545b4f9fe12SLen Brown * @data: Data to be returned to handler when event is fired 546b4f9fe12SLen Brown * 547b4f9fe12SLen Brown * Register a handler for events sent to the ACPI-WMI mapper device. 548b4f9fe12SLen Brown */ 549b4f9fe12SLen Brown acpi_status wmi_install_notify_handler(const char *guid, 550b4f9fe12SLen Brown wmi_notify_handler handler, void *data) 551b4f9fe12SLen Brown { 552b4f9fe12SLen Brown struct wmi_block *block; 55358f6425eSColin King acpi_status status = AE_NOT_EXIST; 55458f6425eSColin King char tmp[16], guid_input[16]; 55558f6425eSColin King struct list_head *p; 556b4f9fe12SLen Brown 557b4f9fe12SLen Brown if (!guid || !handler) 558b4f9fe12SLen Brown return AE_BAD_PARAMETER; 559b4f9fe12SLen Brown 56058f6425eSColin King wmi_parse_guid(guid, tmp); 56158f6425eSColin King wmi_swap_bytes(tmp, guid_input); 562b4f9fe12SLen Brown 56358f6425eSColin King list_for_each(p, &wmi_block_list) { 56458f6425eSColin King acpi_status wmi_status; 56558f6425eSColin King block = list_entry(p, struct wmi_block, list); 56658f6425eSColin King 56758f6425eSColin King if (memcmp(block->gblock.guid, guid_input, 16) == 0) { 56858f6425eSColin King if (block->handler && 56958f6425eSColin King block->handler != wmi_notify_debug) 570b4f9fe12SLen Brown return AE_ALREADY_ACQUIRED; 571b4f9fe12SLen Brown 572b4f9fe12SLen Brown block->handler = handler; 573b4f9fe12SLen Brown block->handler_data = data; 574b4f9fe12SLen Brown 57558f6425eSColin King wmi_status = wmi_method_enable(block, 1); 57658f6425eSColin King if ((wmi_status != AE_OK) || 57758f6425eSColin King ((wmi_status == AE_OK) && (status == AE_NOT_EXIST))) 57858f6425eSColin King status = wmi_status; 57958f6425eSColin King } 58058f6425eSColin King } 581b4f9fe12SLen Brown 582b4f9fe12SLen Brown return status; 583b4f9fe12SLen Brown } 584b4f9fe12SLen Brown EXPORT_SYMBOL_GPL(wmi_install_notify_handler); 585b4f9fe12SLen Brown 586b4f9fe12SLen Brown /** 587b4f9fe12SLen Brown * wmi_uninstall_notify_handler - Unregister handler for WMI events 588b4f9fe12SLen Brown * 589b4f9fe12SLen Brown * Unregister handler for events sent to the ACPI-WMI mapper device. 590b4f9fe12SLen Brown */ 591b4f9fe12SLen Brown acpi_status wmi_remove_notify_handler(const char *guid) 592b4f9fe12SLen Brown { 593b4f9fe12SLen Brown struct wmi_block *block; 59458f6425eSColin King acpi_status status = AE_NOT_EXIST; 59558f6425eSColin King char tmp[16], guid_input[16]; 59658f6425eSColin King struct list_head *p; 597b4f9fe12SLen Brown 598b4f9fe12SLen Brown if (!guid) 599b4f9fe12SLen Brown return AE_BAD_PARAMETER; 600b4f9fe12SLen Brown 60158f6425eSColin King wmi_parse_guid(guid, tmp); 60258f6425eSColin King wmi_swap_bytes(tmp, guid_input); 603b4f9fe12SLen Brown 60458f6425eSColin King list_for_each(p, &wmi_block_list) { 60558f6425eSColin King acpi_status wmi_status; 60658f6425eSColin King block = list_entry(p, struct wmi_block, list); 60758f6425eSColin King 60858f6425eSColin King if (memcmp(block->gblock.guid, guid_input, 16) == 0) { 60958f6425eSColin King if (!block->handler || 61058f6425eSColin King block->handler == wmi_notify_debug) 611b4f9fe12SLen Brown return AE_NULL_ENTRY; 612b4f9fe12SLen Brown 613fc3155b2SThomas Renninger if (debug_event) { 614fc3155b2SThomas Renninger block->handler = wmi_notify_debug; 61558f6425eSColin King status = AE_OK; 616fc3155b2SThomas Renninger } else { 61758f6425eSColin King wmi_status = wmi_method_enable(block, 0); 618b4f9fe12SLen Brown block->handler = NULL; 619b4f9fe12SLen Brown block->handler_data = NULL; 62058f6425eSColin King if ((wmi_status != AE_OK) || 62158f6425eSColin King ((wmi_status == AE_OK) && 62258f6425eSColin King (status == AE_NOT_EXIST))) 62358f6425eSColin King status = wmi_status; 624fc3155b2SThomas Renninger } 62558f6425eSColin King } 62658f6425eSColin King } 62758f6425eSColin King 628b4f9fe12SLen Brown return status; 629b4f9fe12SLen Brown } 630b4f9fe12SLen Brown EXPORT_SYMBOL_GPL(wmi_remove_notify_handler); 631b4f9fe12SLen Brown 632b4f9fe12SLen Brown /** 633b4f9fe12SLen Brown * wmi_get_event_data - Get WMI data associated with an event 634b4f9fe12SLen Brown * 6353e9b988eSAnisse Astier * @event: Event to find 6363e9b988eSAnisse Astier * @out: Buffer to hold event data. out->pointer should be freed with kfree() 637b4f9fe12SLen Brown * 638b4f9fe12SLen Brown * Returns extra data associated with an event in WMI. 639b4f9fe12SLen Brown */ 640b4f9fe12SLen Brown acpi_status wmi_get_event_data(u32 event, struct acpi_buffer *out) 641b4f9fe12SLen Brown { 642b4f9fe12SLen Brown struct acpi_object_list input; 643b4f9fe12SLen Brown union acpi_object params[1]; 644b4f9fe12SLen Brown struct guid_block *gblock; 645b4f9fe12SLen Brown struct wmi_block *wblock; 646b4f9fe12SLen Brown struct list_head *p; 647b4f9fe12SLen Brown 648b4f9fe12SLen Brown input.count = 1; 649b4f9fe12SLen Brown input.pointer = params; 650b4f9fe12SLen Brown params[0].type = ACPI_TYPE_INTEGER; 651b4f9fe12SLen Brown params[0].integer.value = event; 652b4f9fe12SLen Brown 653762e1a2fSDmitry Torokhov list_for_each(p, &wmi_block_list) { 654b4f9fe12SLen Brown wblock = list_entry(p, struct wmi_block, list); 655b4f9fe12SLen Brown gblock = &wblock->gblock; 656b4f9fe12SLen Brown 657b4f9fe12SLen Brown if ((gblock->flags & ACPI_WMI_EVENT) && 658b4f9fe12SLen Brown (gblock->notify_id == event)) 659b4f9fe12SLen Brown return acpi_evaluate_object(wblock->handle, "_WED", 660b4f9fe12SLen Brown &input, out); 661b4f9fe12SLen Brown } 662b4f9fe12SLen Brown 663b4f9fe12SLen Brown return AE_NOT_FOUND; 664b4f9fe12SLen Brown } 665b4f9fe12SLen Brown EXPORT_SYMBOL_GPL(wmi_get_event_data); 666b4f9fe12SLen Brown 667b4f9fe12SLen Brown /** 668b4f9fe12SLen Brown * wmi_has_guid - Check if a GUID is available 669b4f9fe12SLen Brown * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba 670b4f9fe12SLen Brown * 671b4f9fe12SLen Brown * Check if a given GUID is defined by _WDG 672b4f9fe12SLen Brown */ 673b4f9fe12SLen Brown bool wmi_has_guid(const char *guid_string) 674b4f9fe12SLen Brown { 675b4f9fe12SLen Brown return find_guid(guid_string, NULL); 676b4f9fe12SLen Brown } 677b4f9fe12SLen Brown EXPORT_SYMBOL_GPL(wmi_has_guid); 678b4f9fe12SLen Brown 679b4f9fe12SLen Brown /* 6801caab3c1SMatthew Garrett * sysfs interface 6811caab3c1SMatthew Garrett */ 682614ef432SDmitry Torokhov static ssize_t modalias_show(struct device *dev, struct device_attribute *attr, 6831caab3c1SMatthew Garrett char *buf) 6841caab3c1SMatthew Garrett { 6851caab3c1SMatthew Garrett char guid_string[37]; 6861caab3c1SMatthew Garrett struct wmi_block *wblock; 6871caab3c1SMatthew Garrett 6881caab3c1SMatthew Garrett wblock = dev_get_drvdata(dev); 6891caab3c1SMatthew Garrett if (!wblock) 6901caab3c1SMatthew Garrett return -ENOMEM; 6911caab3c1SMatthew Garrett 6921caab3c1SMatthew Garrett wmi_gtoa(wblock->gblock.guid, guid_string); 6931caab3c1SMatthew Garrett 6941caab3c1SMatthew Garrett return sprintf(buf, "wmi:%s\n", guid_string); 6951caab3c1SMatthew Garrett } 696614ef432SDmitry Torokhov 697614ef432SDmitry Torokhov static struct device_attribute wmi_dev_attrs[] = { 698614ef432SDmitry Torokhov __ATTR_RO(modalias), 699614ef432SDmitry Torokhov __ATTR_NULL 700614ef432SDmitry Torokhov }; 7011caab3c1SMatthew Garrett 7021caab3c1SMatthew Garrett static int wmi_dev_uevent(struct device *dev, struct kobj_uevent_env *env) 7031caab3c1SMatthew Garrett { 7041caab3c1SMatthew Garrett char guid_string[37]; 7051caab3c1SMatthew Garrett 7061caab3c1SMatthew Garrett struct wmi_block *wblock; 7071caab3c1SMatthew Garrett 7081caab3c1SMatthew Garrett if (add_uevent_var(env, "MODALIAS=")) 7091caab3c1SMatthew Garrett return -ENOMEM; 7101caab3c1SMatthew Garrett 7111caab3c1SMatthew Garrett wblock = dev_get_drvdata(dev); 7121caab3c1SMatthew Garrett if (!wblock) 7131caab3c1SMatthew Garrett return -ENOMEM; 7141caab3c1SMatthew Garrett 7151caab3c1SMatthew Garrett wmi_gtoa(wblock->gblock.guid, guid_string); 7161caab3c1SMatthew Garrett 7171caab3c1SMatthew Garrett strcpy(&env->buf[env->buflen - 1], "wmi:"); 7181caab3c1SMatthew Garrett memcpy(&env->buf[env->buflen - 1 + 4], guid_string, 36); 7191caab3c1SMatthew Garrett env->buflen += 40; 7201caab3c1SMatthew Garrett 7211caab3c1SMatthew Garrett return 0; 7221caab3c1SMatthew Garrett } 7231caab3c1SMatthew Garrett 7241caab3c1SMatthew Garrett static void wmi_dev_free(struct device *dev) 7251caab3c1SMatthew Garrett { 726c64eefd4SDmitry Torokhov struct wmi_block *wmi_block = container_of(dev, struct wmi_block, dev); 727c64eefd4SDmitry Torokhov 728c64eefd4SDmitry Torokhov kfree(wmi_block); 7291caab3c1SMatthew Garrett } 7301caab3c1SMatthew Garrett 7311caab3c1SMatthew Garrett static struct class wmi_class = { 7321caab3c1SMatthew Garrett .name = "wmi", 7331caab3c1SMatthew Garrett .dev_release = wmi_dev_free, 7341caab3c1SMatthew Garrett .dev_uevent = wmi_dev_uevent, 735614ef432SDmitry Torokhov .dev_attrs = wmi_dev_attrs, 7361caab3c1SMatthew Garrett }; 7371caab3c1SMatthew Garrett 73858f6425eSColin King static int wmi_create_device(const struct guid_block *gblock, 73958f6425eSColin King struct wmi_block *wblock, acpi_handle handle) 7401caab3c1SMatthew Garrett { 741c64eefd4SDmitry Torokhov char guid_string[37]; 7421caab3c1SMatthew Garrett 743c64eefd4SDmitry Torokhov wblock->dev.class = &wmi_class; 7441caab3c1SMatthew Garrett 7451caab3c1SMatthew Garrett wmi_gtoa(gblock->guid, guid_string); 74602aa2a37SKees Cook dev_set_name(&wblock->dev, "%s", guid_string); 7471caab3c1SMatthew Garrett 748c64eefd4SDmitry Torokhov dev_set_drvdata(&wblock->dev, wblock); 749c64eefd4SDmitry Torokhov 75058f6425eSColin King return device_register(&wblock->dev); 7511caab3c1SMatthew Garrett } 7521caab3c1SMatthew Garrett 753c64eefd4SDmitry Torokhov static void wmi_free_devices(void) 7541caab3c1SMatthew Garrett { 755c64eefd4SDmitry Torokhov struct wmi_block *wblock, *next; 7561caab3c1SMatthew Garrett 7571caab3c1SMatthew Garrett /* Delete devices for all the GUIDs */ 758023b9565SDmitry Torokhov list_for_each_entry_safe(wblock, next, &wmi_block_list, list) { 759023b9565SDmitry Torokhov list_del(&wblock->list); 76058f6425eSColin King if (wblock->dev.class) 761c64eefd4SDmitry Torokhov device_unregister(&wblock->dev); 762023b9565SDmitry Torokhov else 763023b9565SDmitry Torokhov kfree(wblock); 764023b9565SDmitry Torokhov } 7651caab3c1SMatthew Garrett } 7661caab3c1SMatthew Garrett 767d1f9e497SCarlos Corbacho static bool guid_already_parsed(const char *guid_string) 768d1f9e497SCarlos Corbacho { 769d1f9e497SCarlos Corbacho struct wmi_block *wblock; 770d1f9e497SCarlos Corbacho 771c64eefd4SDmitry Torokhov list_for_each_entry(wblock, &wmi_block_list, list) 7728b14d7b2SThadeu Lima de Souza Cascardo if (memcmp(wblock->gblock.guid, guid_string, 16) == 0) 773d1f9e497SCarlos Corbacho return true; 774c64eefd4SDmitry Torokhov 775d1f9e497SCarlos Corbacho return false; 776d1f9e497SCarlos Corbacho } 777d1f9e497SCarlos Corbacho 7781caab3c1SMatthew Garrett /* 779b4f9fe12SLen Brown * Parse the _WDG method for the GUID data blocks 780b4f9fe12SLen Brown */ 781925b1089SThomas Renninger static acpi_status parse_wdg(acpi_handle handle) 782b4f9fe12SLen Brown { 783b4f9fe12SLen Brown struct acpi_buffer out = {ACPI_ALLOCATE_BUFFER, NULL}; 784b4f9fe12SLen Brown union acpi_object *obj; 78537830662SDmitry Torokhov const struct guid_block *gblock; 786b4f9fe12SLen Brown struct wmi_block *wblock; 787b4f9fe12SLen Brown acpi_status status; 788c64eefd4SDmitry Torokhov int retval; 789b4f9fe12SLen Brown u32 i, total; 790b4f9fe12SLen Brown 791b4f9fe12SLen Brown status = acpi_evaluate_object(handle, "_WDG", NULL, &out); 792b4f9fe12SLen Brown if (ACPI_FAILURE(status)) 793c64eefd4SDmitry Torokhov return -ENXIO; 794b4f9fe12SLen Brown 795b4f9fe12SLen Brown obj = (union acpi_object *) out.pointer; 7963d2c63ebSDmitry Torokhov if (!obj) 797c64eefd4SDmitry Torokhov return -ENXIO; 798b4f9fe12SLen Brown 79964ed0ab8SDmitry Torokhov if (obj->type != ACPI_TYPE_BUFFER) { 800c64eefd4SDmitry Torokhov retval = -ENXIO; 80164ed0ab8SDmitry Torokhov goto out_free_pointer; 80264ed0ab8SDmitry Torokhov } 803b4f9fe12SLen Brown 80437830662SDmitry Torokhov gblock = (const struct guid_block *)obj->buffer.pointer; 805b4f9fe12SLen Brown total = obj->buffer.length / sizeof(struct guid_block); 806b4f9fe12SLen Brown 807b4f9fe12SLen Brown for (i = 0; i < total; i++) { 808a929aae0SThomas Renninger if (debug_dump_wdg) 809a929aae0SThomas Renninger wmi_dump_wdg(&gblock[i]); 810a929aae0SThomas Renninger 81158f6425eSColin King wblock = kzalloc(sizeof(struct wmi_block), GFP_KERNEL); 81258f6425eSColin King if (!wblock) 81358f6425eSColin King return AE_NO_MEMORY; 81458f6425eSColin King 81558f6425eSColin King wblock->handle = handle; 81658f6425eSColin King wblock->gblock = gblock[i]; 81758f6425eSColin King 81858f6425eSColin King /* 81958f6425eSColin King Some WMI devices, like those for nVidia hooks, have a 82058f6425eSColin King duplicate GUID. It's not clear what we should do in this 82158f6425eSColin King case yet, so for now, we'll just ignore the duplicate 82258f6425eSColin King for device creation. 82358f6425eSColin King */ 82458f6425eSColin King if (!guid_already_parsed(gblock[i].guid)) { 82558f6425eSColin King retval = wmi_create_device(&gblock[i], wblock, handle); 82658f6425eSColin King if (retval) { 827c64eefd4SDmitry Torokhov wmi_free_devices(); 828e1e0dacbSDan Carpenter goto out_free_pointer; 829a5167c5bSAxel Lin } 83058f6425eSColin King } 83158f6425eSColin King 83258f6425eSColin King list_add_tail(&wblock->list, &wmi_block_list); 833b4f9fe12SLen Brown 834fc3155b2SThomas Renninger if (debug_event) { 835fc3155b2SThomas Renninger wblock->handler = wmi_notify_debug; 8362d5ab555SDmitry Torokhov wmi_method_enable(wblock, 1); 837fc3155b2SThomas Renninger } 838b4f9fe12SLen Brown } 839b4f9fe12SLen Brown 840c64eefd4SDmitry Torokhov retval = 0; 841c64eefd4SDmitry Torokhov 842a5167c5bSAxel Lin out_free_pointer: 843a5167c5bSAxel Lin kfree(out.pointer); 844b4f9fe12SLen Brown 845c64eefd4SDmitry Torokhov return retval; 846b4f9fe12SLen Brown } 847b4f9fe12SLen Brown 848b4f9fe12SLen Brown /* 849b4f9fe12SLen Brown * WMI can have EmbeddedControl access regions. In which case, we just want to 850b4f9fe12SLen Brown * hand these off to the EC driver. 851b4f9fe12SLen Brown */ 852b4f9fe12SLen Brown static acpi_status 853b4f9fe12SLen Brown acpi_wmi_ec_space_handler(u32 function, acpi_physical_address address, 854439913ffSLin Ming u32 bits, u64 *value, 855b4f9fe12SLen Brown void *handler_context, void *region_context) 856b4f9fe12SLen Brown { 857b4f9fe12SLen Brown int result = 0, i = 0; 858b4f9fe12SLen Brown u8 temp = 0; 859b4f9fe12SLen Brown 860b4f9fe12SLen Brown if ((address > 0xFF) || !value) 861b4f9fe12SLen Brown return AE_BAD_PARAMETER; 862b4f9fe12SLen Brown 863b4f9fe12SLen Brown if (function != ACPI_READ && function != ACPI_WRITE) 864b4f9fe12SLen Brown return AE_BAD_PARAMETER; 865b4f9fe12SLen Brown 866b4f9fe12SLen Brown if (bits != 8) 867b4f9fe12SLen Brown return AE_BAD_PARAMETER; 868b4f9fe12SLen Brown 869b4f9fe12SLen Brown if (function == ACPI_READ) { 870b4f9fe12SLen Brown result = ec_read(address, &temp); 871439913ffSLin Ming (*value) |= ((u64)temp) << i; 872b4f9fe12SLen Brown } else { 873b4f9fe12SLen Brown temp = 0xff & ((*value) >> i); 874b4f9fe12SLen Brown result = ec_write(address, temp); 875b4f9fe12SLen Brown } 876b4f9fe12SLen Brown 877b4f9fe12SLen Brown switch (result) { 878b4f9fe12SLen Brown case -EINVAL: 879b4f9fe12SLen Brown return AE_BAD_PARAMETER; 880b4f9fe12SLen Brown break; 881b4f9fe12SLen Brown case -ENODEV: 882b4f9fe12SLen Brown return AE_NOT_FOUND; 883b4f9fe12SLen Brown break; 884b4f9fe12SLen Brown case -ETIME: 885b4f9fe12SLen Brown return AE_TIME; 886b4f9fe12SLen Brown break; 887b4f9fe12SLen Brown default: 888b4f9fe12SLen Brown return AE_OK; 889b4f9fe12SLen Brown } 890b4f9fe12SLen Brown } 891b4f9fe12SLen Brown 892f61bb939SBjorn Helgaas static void acpi_wmi_notify(struct acpi_device *device, u32 event) 893b4f9fe12SLen Brown { 894b4f9fe12SLen Brown struct guid_block *block; 895b4f9fe12SLen Brown struct wmi_block *wblock; 896b4f9fe12SLen Brown struct list_head *p; 8977715348cSThomas Renninger char guid_string[37]; 898b4f9fe12SLen Brown 899762e1a2fSDmitry Torokhov list_for_each(p, &wmi_block_list) { 900b4f9fe12SLen Brown wblock = list_entry(p, struct wmi_block, list); 901b4f9fe12SLen Brown block = &wblock->gblock; 902b4f9fe12SLen Brown 903b4f9fe12SLen Brown if ((block->flags & ACPI_WMI_EVENT) && 904b4f9fe12SLen Brown (block->notify_id == event)) { 905b4f9fe12SLen Brown if (wblock->handler) 906b4f9fe12SLen Brown wblock->handler(event, wblock->handler_data); 9077715348cSThomas Renninger if (debug_event) { 9087715348cSThomas Renninger wmi_gtoa(wblock->gblock.guid, guid_string); 9098e07514dSDmitry Torokhov pr_info("DEBUG Event GUID: %s\n", guid_string); 9107715348cSThomas Renninger } 911b4f9fe12SLen Brown 912b4f9fe12SLen Brown acpi_bus_generate_netlink_event( 913b4f9fe12SLen Brown device->pnp.device_class, dev_name(&device->dev), 914b4f9fe12SLen Brown event, 0); 915b4f9fe12SLen Brown break; 916b4f9fe12SLen Brown } 917b4f9fe12SLen Brown } 918b4f9fe12SLen Brown } 919b4f9fe12SLen Brown 92051fac838SRafael J. Wysocki static int acpi_wmi_remove(struct acpi_device *device) 921b4f9fe12SLen Brown { 922b4f9fe12SLen Brown acpi_remove_address_space_handler(device->handle, 923b4f9fe12SLen Brown ACPI_ADR_SPACE_EC, &acpi_wmi_ec_space_handler); 924c64eefd4SDmitry Torokhov wmi_free_devices(); 925b4f9fe12SLen Brown 926b4f9fe12SLen Brown return 0; 927b4f9fe12SLen Brown } 928b4f9fe12SLen Brown 929925b1089SThomas Renninger static int acpi_wmi_add(struct acpi_device *device) 930b4f9fe12SLen Brown { 931b4f9fe12SLen Brown acpi_status status; 932c64eefd4SDmitry Torokhov int error; 933b4f9fe12SLen Brown 934b4f9fe12SLen Brown status = acpi_install_address_space_handler(device->handle, 935b4f9fe12SLen Brown ACPI_ADR_SPACE_EC, 936b4f9fe12SLen Brown &acpi_wmi_ec_space_handler, 937b4f9fe12SLen Brown NULL, NULL); 9385212cd67SDmitry Torokhov if (ACPI_FAILURE(status)) { 9398e07514dSDmitry Torokhov pr_err("Error installing EC region handler\n"); 940b4f9fe12SLen Brown return -ENODEV; 9415212cd67SDmitry Torokhov } 942b4f9fe12SLen Brown 943c64eefd4SDmitry Torokhov error = parse_wdg(device->handle); 944c64eefd4SDmitry Torokhov if (error) { 9455212cd67SDmitry Torokhov acpi_remove_address_space_handler(device->handle, 9465212cd67SDmitry Torokhov ACPI_ADR_SPACE_EC, 9475212cd67SDmitry Torokhov &acpi_wmi_ec_space_handler); 9488e07514dSDmitry Torokhov pr_err("Failed to parse WDG method\n"); 949c64eefd4SDmitry Torokhov return error; 950b4f9fe12SLen Brown } 951b4f9fe12SLen Brown 952c64eefd4SDmitry Torokhov return 0; 953b4f9fe12SLen Brown } 954b4f9fe12SLen Brown 955b4f9fe12SLen Brown static int __init acpi_wmi_init(void) 956b4f9fe12SLen Brown { 957c64eefd4SDmitry Torokhov int error; 958b4f9fe12SLen Brown 959b4f9fe12SLen Brown if (acpi_disabled) 960b4f9fe12SLen Brown return -ENODEV; 961b4f9fe12SLen Brown 962c64eefd4SDmitry Torokhov error = class_register(&wmi_class); 963c64eefd4SDmitry Torokhov if (error) 964c64eefd4SDmitry Torokhov return error; 965b4f9fe12SLen Brown 966c64eefd4SDmitry Torokhov error = acpi_bus_register_driver(&acpi_wmi_driver); 967c64eefd4SDmitry Torokhov if (error) { 968c64eefd4SDmitry Torokhov pr_err("Error loading mapper\n"); 969c64eefd4SDmitry Torokhov class_unregister(&wmi_class); 970c64eefd4SDmitry Torokhov return error; 9711caab3c1SMatthew Garrett } 9721caab3c1SMatthew Garrett 9738e07514dSDmitry Torokhov pr_info("Mapper loaded\n"); 9748e07514dSDmitry Torokhov return 0; 975b4f9fe12SLen Brown } 976b4f9fe12SLen Brown 977b4f9fe12SLen Brown static void __exit acpi_wmi_exit(void) 978b4f9fe12SLen Brown { 979b4f9fe12SLen Brown acpi_bus_unregister_driver(&acpi_wmi_driver); 980c64eefd4SDmitry Torokhov class_unregister(&wmi_class); 981b4f9fe12SLen Brown 9828e07514dSDmitry Torokhov pr_info("Mapper unloaded\n"); 983b4f9fe12SLen Brown } 984b4f9fe12SLen Brown 985b4f9fe12SLen Brown subsys_initcall(acpi_wmi_init); 986b4f9fe12SLen Brown module_exit(acpi_wmi_exit); 987