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> 39b4f9fe12SLen Brown #include <acpi/acpi_bus.h> 40b4f9fe12SLen Brown #include <acpi/acpi_drivers.h> 41b4f9fe12SLen Brown 42b4f9fe12SLen Brown ACPI_MODULE_NAME("wmi"); 43b4f9fe12SLen Brown MODULE_AUTHOR("Carlos Corbacho"); 44b4f9fe12SLen Brown MODULE_DESCRIPTION("ACPI-WMI Mapping Driver"); 45b4f9fe12SLen Brown MODULE_LICENSE("GPL"); 46b4f9fe12SLen Brown 47b4f9fe12SLen Brown #define ACPI_WMI_CLASS "wmi" 48b4f9fe12SLen Brown 49b4f9fe12SLen Brown static DEFINE_MUTEX(wmi_data_lock); 50762e1a2fSDmitry Torokhov static LIST_HEAD(wmi_block_list); 51b4f9fe12SLen Brown 52b4f9fe12SLen Brown struct guid_block { 53b4f9fe12SLen Brown char guid[16]; 54b4f9fe12SLen Brown union { 55b4f9fe12SLen Brown char object_id[2]; 56b4f9fe12SLen Brown struct { 57b4f9fe12SLen Brown unsigned char notify_id; 58b4f9fe12SLen Brown unsigned char reserved; 59b4f9fe12SLen Brown }; 60b4f9fe12SLen Brown }; 61b4f9fe12SLen Brown u8 instance_count; 62b4f9fe12SLen Brown u8 flags; 63b4f9fe12SLen Brown }; 64b4f9fe12SLen Brown 65b4f9fe12SLen Brown struct wmi_block { 66b4f9fe12SLen Brown struct list_head list; 67b4f9fe12SLen Brown struct guid_block gblock; 68b4f9fe12SLen Brown acpi_handle handle; 69b4f9fe12SLen Brown wmi_notify_handler handler; 70b4f9fe12SLen Brown void *handler_data; 711caab3c1SMatthew Garrett struct device *dev; 72b4f9fe12SLen Brown }; 73b4f9fe12SLen Brown 74b4f9fe12SLen Brown 75b4f9fe12SLen Brown /* 76b4f9fe12SLen Brown * If the GUID data block is marked as expensive, we must enable and 77b4f9fe12SLen Brown * explicitily disable data collection. 78b4f9fe12SLen Brown */ 79b4f9fe12SLen Brown #define ACPI_WMI_EXPENSIVE 0x1 80b4f9fe12SLen Brown #define ACPI_WMI_METHOD 0x2 /* GUID is a method */ 81b4f9fe12SLen Brown #define ACPI_WMI_STRING 0x4 /* GUID takes & returns a string */ 82b4f9fe12SLen Brown #define ACPI_WMI_EVENT 0x8 /* GUID is an event */ 83b4f9fe12SLen Brown 84fc3155b2SThomas Renninger static int debug_event; 85fc3155b2SThomas Renninger module_param(debug_event, bool, 0444); 86fc3155b2SThomas Renninger MODULE_PARM_DESC(debug_event, 87fc3155b2SThomas Renninger "Log WMI Events [0/1]"); 88fc3155b2SThomas Renninger 89a929aae0SThomas Renninger static int debug_dump_wdg; 90a929aae0SThomas Renninger module_param(debug_dump_wdg, bool, 0444); 91a929aae0SThomas Renninger MODULE_PARM_DESC(debug_dump_wdg, 92a929aae0SThomas Renninger "Dump available WMI interfaces [0/1]"); 93a929aae0SThomas Renninger 94b4f9fe12SLen Brown static int acpi_wmi_remove(struct acpi_device *device, int type); 95b4f9fe12SLen Brown static int acpi_wmi_add(struct acpi_device *device); 96f61bb939SBjorn Helgaas static void acpi_wmi_notify(struct acpi_device *device, u32 event); 97b4f9fe12SLen Brown 98b4f9fe12SLen Brown static const struct acpi_device_id wmi_device_ids[] = { 99b4f9fe12SLen Brown {"PNP0C14", 0}, 100b4f9fe12SLen Brown {"pnp0c14", 0}, 101b4f9fe12SLen Brown {"", 0}, 102b4f9fe12SLen Brown }; 103b4f9fe12SLen Brown MODULE_DEVICE_TABLE(acpi, wmi_device_ids); 104b4f9fe12SLen Brown 105b4f9fe12SLen Brown static struct acpi_driver acpi_wmi_driver = { 106b4f9fe12SLen Brown .name = "wmi", 107b4f9fe12SLen Brown .class = ACPI_WMI_CLASS, 108b4f9fe12SLen Brown .ids = wmi_device_ids, 109b4f9fe12SLen Brown .ops = { 110b4f9fe12SLen Brown .add = acpi_wmi_add, 111b4f9fe12SLen Brown .remove = acpi_wmi_remove, 112f61bb939SBjorn Helgaas .notify = acpi_wmi_notify, 113b4f9fe12SLen Brown }, 114b4f9fe12SLen Brown }; 115b4f9fe12SLen Brown 116b4f9fe12SLen Brown /* 117b4f9fe12SLen Brown * GUID parsing functions 118b4f9fe12SLen Brown */ 119b4f9fe12SLen Brown 120b4f9fe12SLen Brown /** 121b4f9fe12SLen Brown * wmi_parse_hexbyte - Convert a ASCII hex number to a byte 122b4f9fe12SLen Brown * @src: Pointer to at least 2 characters to convert. 123b4f9fe12SLen Brown * 124b4f9fe12SLen Brown * Convert a two character ASCII hex string to a number. 125b4f9fe12SLen Brown * 126b4f9fe12SLen Brown * Return: 0-255 Success, the byte was parsed correctly 127b4f9fe12SLen Brown * -1 Error, an invalid character was supplied 128b4f9fe12SLen Brown */ 129b4f9fe12SLen Brown static int wmi_parse_hexbyte(const u8 *src) 130b4f9fe12SLen Brown { 131b4f9fe12SLen Brown int h; 132392bd8b5SAndy Shevchenko int value; 133b4f9fe12SLen Brown 134b4f9fe12SLen Brown /* high part */ 135392bd8b5SAndy Shevchenko h = value = hex_to_bin(src[0]); 136392bd8b5SAndy Shevchenko if (value < 0) 137b4f9fe12SLen Brown return -1; 138b4f9fe12SLen Brown 139b4f9fe12SLen Brown /* low part */ 140392bd8b5SAndy Shevchenko value = hex_to_bin(src[1]); 141392bd8b5SAndy Shevchenko if (value >= 0) 142392bd8b5SAndy Shevchenko return (h << 4) | value; 143b4f9fe12SLen Brown return -1; 144b4f9fe12SLen Brown } 145b4f9fe12SLen Brown 146b4f9fe12SLen Brown /** 147b4f9fe12SLen Brown * wmi_swap_bytes - Rearrange GUID bytes to match GUID binary 148b4f9fe12SLen Brown * @src: Memory block holding binary GUID (16 bytes) 149b4f9fe12SLen Brown * @dest: Memory block to hold byte swapped binary GUID (16 bytes) 150b4f9fe12SLen Brown * 151b4f9fe12SLen Brown * Byte swap a binary GUID to match it's real GUID value 152b4f9fe12SLen Brown */ 153b4f9fe12SLen Brown static void wmi_swap_bytes(u8 *src, u8 *dest) 154b4f9fe12SLen Brown { 155b4f9fe12SLen Brown int i; 156b4f9fe12SLen Brown 157b4f9fe12SLen Brown for (i = 0; i <= 3; i++) 158b4f9fe12SLen Brown memcpy(dest + i, src + (3 - i), 1); 159b4f9fe12SLen Brown 160b4f9fe12SLen Brown for (i = 0; i <= 1; i++) 161b4f9fe12SLen Brown memcpy(dest + 4 + i, src + (5 - i), 1); 162b4f9fe12SLen Brown 163b4f9fe12SLen Brown for (i = 0; i <= 1; i++) 164b4f9fe12SLen Brown memcpy(dest + 6 + i, src + (7 - i), 1); 165b4f9fe12SLen Brown 166b4f9fe12SLen Brown memcpy(dest + 8, src + 8, 8); 167b4f9fe12SLen Brown } 168b4f9fe12SLen Brown 169b4f9fe12SLen Brown /** 170b4f9fe12SLen Brown * wmi_parse_guid - Convert GUID from ASCII to binary 171b4f9fe12SLen Brown * @src: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba 172b4f9fe12SLen Brown * @dest: Memory block to hold binary GUID (16 bytes) 173b4f9fe12SLen Brown * 174b4f9fe12SLen Brown * N.B. The GUID need not be NULL terminated. 175b4f9fe12SLen Brown * 176b4f9fe12SLen Brown * Return: 'true' @dest contains binary GUID 177b4f9fe12SLen Brown * 'false' @dest contents are undefined 178b4f9fe12SLen Brown */ 179b4f9fe12SLen Brown static bool wmi_parse_guid(const u8 *src, u8 *dest) 180b4f9fe12SLen Brown { 181b4f9fe12SLen Brown static const int size[] = { 4, 2, 2, 2, 6 }; 182b4f9fe12SLen Brown int i, j, v; 183b4f9fe12SLen Brown 184b4f9fe12SLen Brown if (src[8] != '-' || src[13] != '-' || 185b4f9fe12SLen Brown src[18] != '-' || src[23] != '-') 186b4f9fe12SLen Brown return false; 187b4f9fe12SLen Brown 188b4f9fe12SLen Brown for (j = 0; j < 5; j++, src++) { 189b4f9fe12SLen Brown for (i = 0; i < size[j]; i++, src += 2, *dest++ = v) { 190b4f9fe12SLen Brown v = wmi_parse_hexbyte(src); 191b4f9fe12SLen Brown if (v < 0) 192b4f9fe12SLen Brown return false; 193b4f9fe12SLen Brown } 194b4f9fe12SLen Brown } 195b4f9fe12SLen Brown 196b4f9fe12SLen Brown return true; 197b4f9fe12SLen Brown } 198b4f9fe12SLen Brown 1991caab3c1SMatthew Garrett /* 2001caab3c1SMatthew Garrett * Convert a raw GUID to the ACII string representation 2011caab3c1SMatthew Garrett */ 2021caab3c1SMatthew Garrett static int wmi_gtoa(const char *in, char *out) 2031caab3c1SMatthew Garrett { 2041caab3c1SMatthew Garrett int i; 2051caab3c1SMatthew Garrett 2061caab3c1SMatthew Garrett for (i = 3; i >= 0; i--) 2071caab3c1SMatthew Garrett out += sprintf(out, "%02X", in[i] & 0xFF); 2081caab3c1SMatthew Garrett 2091caab3c1SMatthew Garrett out += sprintf(out, "-"); 2101caab3c1SMatthew Garrett out += sprintf(out, "%02X", in[5] & 0xFF); 2111caab3c1SMatthew Garrett out += sprintf(out, "%02X", in[4] & 0xFF); 2121caab3c1SMatthew Garrett out += sprintf(out, "-"); 2131caab3c1SMatthew Garrett out += sprintf(out, "%02X", in[7] & 0xFF); 2141caab3c1SMatthew Garrett out += sprintf(out, "%02X", in[6] & 0xFF); 2151caab3c1SMatthew Garrett out += sprintf(out, "-"); 2161caab3c1SMatthew Garrett out += sprintf(out, "%02X", in[8] & 0xFF); 2171caab3c1SMatthew Garrett out += sprintf(out, "%02X", in[9] & 0xFF); 2181caab3c1SMatthew Garrett out += sprintf(out, "-"); 2191caab3c1SMatthew Garrett 2201caab3c1SMatthew Garrett for (i = 10; i <= 15; i++) 2211caab3c1SMatthew Garrett out += sprintf(out, "%02X", in[i] & 0xFF); 2221caab3c1SMatthew Garrett 2234e4304d7SDmitry Torokhov *out = '\0'; 2241caab3c1SMatthew Garrett return 0; 2251caab3c1SMatthew Garrett } 2261caab3c1SMatthew Garrett 227b4f9fe12SLen Brown static bool find_guid(const char *guid_string, struct wmi_block **out) 228b4f9fe12SLen Brown { 229b4f9fe12SLen Brown char tmp[16], guid_input[16]; 230b4f9fe12SLen Brown struct wmi_block *wblock; 231b4f9fe12SLen Brown struct guid_block *block; 232b4f9fe12SLen Brown struct list_head *p; 233b4f9fe12SLen Brown 234b4f9fe12SLen Brown wmi_parse_guid(guid_string, tmp); 235b4f9fe12SLen Brown wmi_swap_bytes(tmp, guid_input); 236b4f9fe12SLen Brown 237762e1a2fSDmitry Torokhov list_for_each(p, &wmi_block_list) { 238b4f9fe12SLen Brown wblock = list_entry(p, struct wmi_block, list); 239b4f9fe12SLen Brown block = &wblock->gblock; 240b4f9fe12SLen Brown 241b4f9fe12SLen Brown if (memcmp(block->guid, guid_input, 16) == 0) { 242b4f9fe12SLen Brown if (out) 243b4f9fe12SLen Brown *out = wblock; 244b4f9fe12SLen Brown return 1; 245b4f9fe12SLen Brown } 246b4f9fe12SLen Brown } 247b4f9fe12SLen Brown return 0; 248b4f9fe12SLen Brown } 249b4f9fe12SLen Brown 250b4f9fe12SLen Brown static acpi_status wmi_method_enable(struct wmi_block *wblock, int enable) 251b4f9fe12SLen Brown { 252b4f9fe12SLen Brown struct guid_block *block = NULL; 253b4f9fe12SLen Brown char method[5]; 254b4f9fe12SLen Brown struct acpi_object_list input; 255b4f9fe12SLen Brown union acpi_object params[1]; 256b4f9fe12SLen Brown acpi_status status; 257b4f9fe12SLen Brown acpi_handle handle; 258b4f9fe12SLen Brown 259b4f9fe12SLen Brown block = &wblock->gblock; 260b4f9fe12SLen Brown handle = wblock->handle; 261b4f9fe12SLen Brown 262b4f9fe12SLen Brown if (!block) 263b4f9fe12SLen Brown return AE_NOT_EXIST; 264b4f9fe12SLen Brown 265b4f9fe12SLen Brown input.count = 1; 266b4f9fe12SLen Brown input.pointer = params; 267b4f9fe12SLen Brown params[0].type = ACPI_TYPE_INTEGER; 268b4f9fe12SLen Brown params[0].integer.value = enable; 269b4f9fe12SLen Brown 270b4f9fe12SLen Brown snprintf(method, 5, "WE%02X", block->notify_id); 271b4f9fe12SLen Brown status = acpi_evaluate_object(handle, method, &input, NULL); 272b4f9fe12SLen Brown 273b4f9fe12SLen Brown if (status != AE_OK && status != AE_NOT_FOUND) 274b4f9fe12SLen Brown return status; 275b4f9fe12SLen Brown else 276b4f9fe12SLen Brown return AE_OK; 277b4f9fe12SLen Brown } 278b4f9fe12SLen Brown 279b4f9fe12SLen Brown /* 280b4f9fe12SLen Brown * Exported WMI functions 281b4f9fe12SLen Brown */ 282b4f9fe12SLen Brown /** 283b4f9fe12SLen Brown * wmi_evaluate_method - Evaluate a WMI method 284b4f9fe12SLen Brown * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba 285b4f9fe12SLen Brown * @instance: Instance index 286b4f9fe12SLen Brown * @method_id: Method ID to call 287b4f9fe12SLen Brown * &in: Buffer containing input for the method call 288b4f9fe12SLen Brown * &out: Empty buffer to return the method results 289b4f9fe12SLen Brown * 290b4f9fe12SLen Brown * Call an ACPI-WMI method 291b4f9fe12SLen Brown */ 292b4f9fe12SLen Brown acpi_status wmi_evaluate_method(const char *guid_string, u8 instance, 293b4f9fe12SLen Brown u32 method_id, const struct acpi_buffer *in, struct acpi_buffer *out) 294b4f9fe12SLen Brown { 295b4f9fe12SLen Brown struct guid_block *block = NULL; 296b4f9fe12SLen Brown struct wmi_block *wblock = NULL; 297b4f9fe12SLen Brown acpi_handle handle; 298b4f9fe12SLen Brown acpi_status status; 299b4f9fe12SLen Brown struct acpi_object_list input; 300b4f9fe12SLen Brown union acpi_object params[3]; 301f3d83e24SCostantino Leandro char method[5] = "WM"; 302b4f9fe12SLen Brown 303b4f9fe12SLen Brown if (!find_guid(guid_string, &wblock)) 304b4f9fe12SLen Brown return AE_ERROR; 305b4f9fe12SLen Brown 306b4f9fe12SLen Brown block = &wblock->gblock; 307b4f9fe12SLen Brown handle = wblock->handle; 308b4f9fe12SLen Brown 309b4f9fe12SLen Brown if (!(block->flags & ACPI_WMI_METHOD)) 310b4f9fe12SLen Brown return AE_BAD_DATA; 311b4f9fe12SLen Brown 312b4f9fe12SLen Brown if (block->instance_count < instance) 313b4f9fe12SLen Brown return AE_BAD_PARAMETER; 314b4f9fe12SLen Brown 315b4f9fe12SLen Brown input.count = 2; 316b4f9fe12SLen Brown input.pointer = params; 317b4f9fe12SLen Brown params[0].type = ACPI_TYPE_INTEGER; 318b4f9fe12SLen Brown params[0].integer.value = instance; 319b4f9fe12SLen Brown params[1].type = ACPI_TYPE_INTEGER; 320b4f9fe12SLen Brown params[1].integer.value = method_id; 321b4f9fe12SLen Brown 322b4f9fe12SLen Brown if (in) { 323b4f9fe12SLen Brown input.count = 3; 324b4f9fe12SLen Brown 325b4f9fe12SLen Brown if (block->flags & ACPI_WMI_STRING) { 326b4f9fe12SLen Brown params[2].type = ACPI_TYPE_STRING; 327b4f9fe12SLen Brown } else { 328b4f9fe12SLen Brown params[2].type = ACPI_TYPE_BUFFER; 329b4f9fe12SLen Brown } 330b4f9fe12SLen Brown params[2].buffer.length = in->length; 331b4f9fe12SLen Brown params[2].buffer.pointer = in->pointer; 332b4f9fe12SLen Brown } 333b4f9fe12SLen Brown 334b4f9fe12SLen Brown strncat(method, block->object_id, 2); 335b4f9fe12SLen Brown 336b4f9fe12SLen Brown status = acpi_evaluate_object(handle, method, &input, out); 337b4f9fe12SLen Brown 338b4f9fe12SLen Brown return status; 339b4f9fe12SLen Brown } 340b4f9fe12SLen Brown EXPORT_SYMBOL_GPL(wmi_evaluate_method); 341b4f9fe12SLen Brown 342b4f9fe12SLen Brown /** 343b4f9fe12SLen Brown * wmi_query_block - Return contents of a WMI block 344b4f9fe12SLen Brown * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba 345b4f9fe12SLen Brown * @instance: Instance index 346b4f9fe12SLen Brown * &out: Empty buffer to return the contents of the data block to 347b4f9fe12SLen Brown * 348b4f9fe12SLen Brown * Return the contents of an ACPI-WMI data block to a buffer 349b4f9fe12SLen Brown */ 350b4f9fe12SLen Brown acpi_status wmi_query_block(const char *guid_string, u8 instance, 351b4f9fe12SLen Brown struct acpi_buffer *out) 352b4f9fe12SLen Brown { 353b4f9fe12SLen Brown struct guid_block *block = NULL; 354b4f9fe12SLen Brown struct wmi_block *wblock = NULL; 355b4f9fe12SLen Brown acpi_handle handle, wc_handle; 356b4f9fe12SLen Brown acpi_status status, wc_status = AE_ERROR; 357b4f9fe12SLen Brown struct acpi_object_list input, wc_input; 358b4f9fe12SLen Brown union acpi_object wc_params[1], wq_params[1]; 359f3d83e24SCostantino Leandro char method[5]; 360f3d83e24SCostantino Leandro char wc_method[5] = "WC"; 361b4f9fe12SLen Brown 362b4f9fe12SLen Brown if (!guid_string || !out) 363b4f9fe12SLen Brown return AE_BAD_PARAMETER; 364b4f9fe12SLen Brown 365b4f9fe12SLen Brown if (!find_guid(guid_string, &wblock)) 366b4f9fe12SLen Brown return AE_ERROR; 367b4f9fe12SLen Brown 368b4f9fe12SLen Brown block = &wblock->gblock; 369b4f9fe12SLen Brown handle = wblock->handle; 370b4f9fe12SLen Brown 371b4f9fe12SLen Brown if (block->instance_count < instance) 372b4f9fe12SLen Brown return AE_BAD_PARAMETER; 373b4f9fe12SLen Brown 374b4f9fe12SLen Brown /* Check GUID is a data block */ 375b4f9fe12SLen Brown if (block->flags & (ACPI_WMI_EVENT | ACPI_WMI_METHOD)) 376b4f9fe12SLen Brown return AE_ERROR; 377b4f9fe12SLen Brown 378b4f9fe12SLen Brown input.count = 1; 379b4f9fe12SLen Brown input.pointer = wq_params; 380b4f9fe12SLen Brown wq_params[0].type = ACPI_TYPE_INTEGER; 381b4f9fe12SLen Brown wq_params[0].integer.value = instance; 382b4f9fe12SLen Brown 383b4f9fe12SLen Brown /* 384b4f9fe12SLen Brown * If ACPI_WMI_EXPENSIVE, call the relevant WCxx method first to 385b4f9fe12SLen Brown * enable collection. 386b4f9fe12SLen Brown */ 387b4f9fe12SLen Brown if (block->flags & ACPI_WMI_EXPENSIVE) { 388b4f9fe12SLen Brown wc_input.count = 1; 389b4f9fe12SLen Brown wc_input.pointer = wc_params; 390b4f9fe12SLen Brown wc_params[0].type = ACPI_TYPE_INTEGER; 391b4f9fe12SLen Brown wc_params[0].integer.value = 1; 392b4f9fe12SLen Brown 393b4f9fe12SLen Brown strncat(wc_method, block->object_id, 2); 394b4f9fe12SLen Brown 395b4f9fe12SLen Brown /* 396b4f9fe12SLen Brown * Some GUIDs break the specification by declaring themselves 397b4f9fe12SLen Brown * expensive, but have no corresponding WCxx method. So we 398b4f9fe12SLen Brown * should not fail if this happens. 399b4f9fe12SLen Brown */ 400b4f9fe12SLen Brown wc_status = acpi_get_handle(handle, wc_method, &wc_handle); 401b4f9fe12SLen Brown if (ACPI_SUCCESS(wc_status)) 402b4f9fe12SLen Brown wc_status = acpi_evaluate_object(handle, wc_method, 403b4f9fe12SLen Brown &wc_input, NULL); 404b4f9fe12SLen Brown } 405b4f9fe12SLen Brown 406b4f9fe12SLen Brown strcpy(method, "WQ"); 407b4f9fe12SLen Brown strncat(method, block->object_id, 2); 408b4f9fe12SLen Brown 409b4f9fe12SLen Brown status = acpi_evaluate_object(handle, method, &input, out); 410b4f9fe12SLen Brown 411b4f9fe12SLen Brown /* 412b4f9fe12SLen Brown * If ACPI_WMI_EXPENSIVE, call the relevant WCxx method, even if 413b4f9fe12SLen Brown * the WQxx method failed - we should disable collection anyway. 414b4f9fe12SLen Brown */ 415b4f9fe12SLen Brown if ((block->flags & ACPI_WMI_EXPENSIVE) && ACPI_SUCCESS(wc_status)) { 416b4f9fe12SLen Brown wc_params[0].integer.value = 0; 417b4f9fe12SLen Brown status = acpi_evaluate_object(handle, 418b4f9fe12SLen Brown wc_method, &wc_input, NULL); 419b4f9fe12SLen Brown } 420b4f9fe12SLen Brown 421b4f9fe12SLen Brown return status; 422b4f9fe12SLen Brown } 423b4f9fe12SLen Brown EXPORT_SYMBOL_GPL(wmi_query_block); 424b4f9fe12SLen Brown 425b4f9fe12SLen Brown /** 426b4f9fe12SLen Brown * wmi_set_block - Write to a WMI block 427b4f9fe12SLen Brown * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba 428b4f9fe12SLen Brown * @instance: Instance index 429b4f9fe12SLen Brown * &in: Buffer containing new values for the data block 430b4f9fe12SLen Brown * 431b4f9fe12SLen Brown * Write the contents of the input buffer to an ACPI-WMI data block 432b4f9fe12SLen Brown */ 433b4f9fe12SLen Brown acpi_status wmi_set_block(const char *guid_string, u8 instance, 434b4f9fe12SLen Brown const struct acpi_buffer *in) 435b4f9fe12SLen Brown { 436b4f9fe12SLen Brown struct guid_block *block = NULL; 437b4f9fe12SLen Brown struct wmi_block *wblock = NULL; 438b4f9fe12SLen Brown acpi_handle handle; 439b4f9fe12SLen Brown struct acpi_object_list input; 440b4f9fe12SLen Brown union acpi_object params[2]; 441f3d83e24SCostantino Leandro char method[5] = "WS"; 442b4f9fe12SLen Brown 443b4f9fe12SLen Brown if (!guid_string || !in) 444b4f9fe12SLen Brown return AE_BAD_DATA; 445b4f9fe12SLen Brown 446b4f9fe12SLen Brown if (!find_guid(guid_string, &wblock)) 447b4f9fe12SLen Brown return AE_ERROR; 448b4f9fe12SLen Brown 449b4f9fe12SLen Brown block = &wblock->gblock; 450b4f9fe12SLen Brown handle = wblock->handle; 451b4f9fe12SLen Brown 452b4f9fe12SLen Brown if (block->instance_count < instance) 453b4f9fe12SLen Brown return AE_BAD_PARAMETER; 454b4f9fe12SLen Brown 455b4f9fe12SLen Brown /* Check GUID is a data block */ 456b4f9fe12SLen Brown if (block->flags & (ACPI_WMI_EVENT | ACPI_WMI_METHOD)) 457b4f9fe12SLen Brown return AE_ERROR; 458b4f9fe12SLen Brown 459b4f9fe12SLen Brown input.count = 2; 460b4f9fe12SLen Brown input.pointer = params; 461b4f9fe12SLen Brown params[0].type = ACPI_TYPE_INTEGER; 462b4f9fe12SLen Brown params[0].integer.value = instance; 463b4f9fe12SLen Brown 464b4f9fe12SLen Brown if (block->flags & ACPI_WMI_STRING) { 465b4f9fe12SLen Brown params[1].type = ACPI_TYPE_STRING; 466b4f9fe12SLen Brown } else { 467b4f9fe12SLen Brown params[1].type = ACPI_TYPE_BUFFER; 468b4f9fe12SLen Brown } 469b4f9fe12SLen Brown params[1].buffer.length = in->length; 470b4f9fe12SLen Brown params[1].buffer.pointer = in->pointer; 471b4f9fe12SLen Brown 472b4f9fe12SLen Brown strncat(method, block->object_id, 2); 473b4f9fe12SLen Brown 474b4f9fe12SLen Brown return acpi_evaluate_object(handle, method, &input, NULL); 475b4f9fe12SLen Brown } 476b4f9fe12SLen Brown EXPORT_SYMBOL_GPL(wmi_set_block); 477b4f9fe12SLen Brown 47837830662SDmitry Torokhov static void wmi_dump_wdg(const struct guid_block *g) 479a929aae0SThomas Renninger { 480a929aae0SThomas Renninger char guid_string[37]; 481a929aae0SThomas Renninger 482a929aae0SThomas Renninger wmi_gtoa(g->guid, guid_string); 4838e07514dSDmitry Torokhov 4848e07514dSDmitry Torokhov pr_info("%s:\n", guid_string); 4858e07514dSDmitry Torokhov pr_info("\tobject_id: %c%c\n", g->object_id[0], g->object_id[1]); 4868e07514dSDmitry Torokhov pr_info("\tnotify_id: %02X\n", g->notify_id); 4878e07514dSDmitry Torokhov pr_info("\treserved: %02X\n", g->reserved); 4888e07514dSDmitry Torokhov pr_info("\tinstance_count: %d\n", g->instance_count); 4898e07514dSDmitry Torokhov pr_info("\tflags: %#x ", g->flags); 490a929aae0SThomas Renninger if (g->flags) { 491a929aae0SThomas Renninger if (g->flags & ACPI_WMI_EXPENSIVE) 4928e07514dSDmitry Torokhov pr_cont("ACPI_WMI_EXPENSIVE "); 493a929aae0SThomas Renninger if (g->flags & ACPI_WMI_METHOD) 4948e07514dSDmitry Torokhov pr_cont("ACPI_WMI_METHOD "); 495a929aae0SThomas Renninger if (g->flags & ACPI_WMI_STRING) 4968e07514dSDmitry Torokhov pr_cont("ACPI_WMI_STRING "); 497a929aae0SThomas Renninger if (g->flags & ACPI_WMI_EVENT) 4988e07514dSDmitry Torokhov pr_cont("ACPI_WMI_EVENT "); 499a929aae0SThomas Renninger } 5008e07514dSDmitry Torokhov pr_cont("\n"); 501a929aae0SThomas Renninger 502a929aae0SThomas Renninger } 503a929aae0SThomas Renninger 504fc3155b2SThomas Renninger static void wmi_notify_debug(u32 value, void *context) 505fc3155b2SThomas Renninger { 506fc3155b2SThomas Renninger struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL }; 507fc3155b2SThomas Renninger union acpi_object *obj; 5081492616aSAxel Lin acpi_status status; 509fc3155b2SThomas Renninger 5101492616aSAxel Lin status = wmi_get_event_data(value, &response); 5111492616aSAxel Lin if (status != AE_OK) { 5128e07514dSDmitry Torokhov pr_info("bad event status 0x%x\n", status); 5131492616aSAxel Lin return; 5141492616aSAxel Lin } 515fc3155b2SThomas Renninger 516fc3155b2SThomas Renninger obj = (union acpi_object *)response.pointer; 517fc3155b2SThomas Renninger 518fc3155b2SThomas Renninger if (!obj) 519fc3155b2SThomas Renninger return; 520fc3155b2SThomas Renninger 5218e07514dSDmitry Torokhov pr_info("DEBUG Event "); 522fc3155b2SThomas Renninger switch(obj->type) { 523fc3155b2SThomas Renninger case ACPI_TYPE_BUFFER: 5248e07514dSDmitry Torokhov pr_cont("BUFFER_TYPE - length %d\n", obj->buffer.length); 525fc3155b2SThomas Renninger break; 526fc3155b2SThomas Renninger case ACPI_TYPE_STRING: 5278e07514dSDmitry Torokhov pr_cont("STRING_TYPE - %s\n", obj->string.pointer); 528fc3155b2SThomas Renninger break; 529fc3155b2SThomas Renninger case ACPI_TYPE_INTEGER: 5308e07514dSDmitry Torokhov pr_cont("INTEGER_TYPE - %llu\n", obj->integer.value); 531fc3155b2SThomas Renninger break; 532fc3155b2SThomas Renninger case ACPI_TYPE_PACKAGE: 5338e07514dSDmitry Torokhov pr_cont("PACKAGE_TYPE - %d elements\n", obj->package.count); 534fc3155b2SThomas Renninger break; 535fc3155b2SThomas Renninger default: 5368e07514dSDmitry Torokhov pr_cont("object type 0x%X\n", obj->type); 537fc3155b2SThomas Renninger } 5381492616aSAxel Lin kfree(obj); 539fc3155b2SThomas Renninger } 540fc3155b2SThomas Renninger 541b4f9fe12SLen Brown /** 542b4f9fe12SLen Brown * wmi_install_notify_handler - Register handler for WMI events 543b4f9fe12SLen Brown * @handler: Function to handle notifications 544b4f9fe12SLen Brown * @data: Data to be returned to handler when event is fired 545b4f9fe12SLen Brown * 546b4f9fe12SLen Brown * Register a handler for events sent to the ACPI-WMI mapper device. 547b4f9fe12SLen Brown */ 548b4f9fe12SLen Brown acpi_status wmi_install_notify_handler(const char *guid, 549b4f9fe12SLen Brown wmi_notify_handler handler, void *data) 550b4f9fe12SLen Brown { 551b4f9fe12SLen Brown struct wmi_block *block; 552b4f9fe12SLen Brown acpi_status status; 553b4f9fe12SLen Brown 554b4f9fe12SLen Brown if (!guid || !handler) 555b4f9fe12SLen Brown return AE_BAD_PARAMETER; 556b4f9fe12SLen Brown 557c03b26a5SPaul Rolland if (!find_guid(guid, &block)) 558b4f9fe12SLen Brown return AE_NOT_EXIST; 559b4f9fe12SLen Brown 560fc3155b2SThomas Renninger if (block->handler && block->handler != wmi_notify_debug) 561b4f9fe12SLen Brown return AE_ALREADY_ACQUIRED; 562b4f9fe12SLen Brown 563b4f9fe12SLen Brown block->handler = handler; 564b4f9fe12SLen Brown block->handler_data = data; 565b4f9fe12SLen Brown 566b4f9fe12SLen Brown status = wmi_method_enable(block, 1); 567b4f9fe12SLen Brown 568b4f9fe12SLen Brown return status; 569b4f9fe12SLen Brown } 570b4f9fe12SLen Brown EXPORT_SYMBOL_GPL(wmi_install_notify_handler); 571b4f9fe12SLen Brown 572b4f9fe12SLen Brown /** 573b4f9fe12SLen Brown * wmi_uninstall_notify_handler - Unregister handler for WMI events 574b4f9fe12SLen Brown * 575b4f9fe12SLen Brown * Unregister handler for events sent to the ACPI-WMI mapper device. 576b4f9fe12SLen Brown */ 577b4f9fe12SLen Brown acpi_status wmi_remove_notify_handler(const char *guid) 578b4f9fe12SLen Brown { 579b4f9fe12SLen Brown struct wmi_block *block; 580fc3155b2SThomas Renninger acpi_status status = AE_OK; 581b4f9fe12SLen Brown 582b4f9fe12SLen Brown if (!guid) 583b4f9fe12SLen Brown return AE_BAD_PARAMETER; 584b4f9fe12SLen Brown 585c03b26a5SPaul Rolland if (!find_guid(guid, &block)) 586b4f9fe12SLen Brown return AE_NOT_EXIST; 587b4f9fe12SLen Brown 588fc3155b2SThomas Renninger if (!block->handler || block->handler == wmi_notify_debug) 589b4f9fe12SLen Brown return AE_NULL_ENTRY; 590b4f9fe12SLen Brown 591fc3155b2SThomas Renninger if (debug_event) { 592fc3155b2SThomas Renninger block->handler = wmi_notify_debug; 593fc3155b2SThomas Renninger } else { 594b4f9fe12SLen Brown status = wmi_method_enable(block, 0); 595b4f9fe12SLen Brown block->handler = NULL; 596b4f9fe12SLen Brown block->handler_data = NULL; 597fc3155b2SThomas Renninger } 598b4f9fe12SLen Brown return status; 599b4f9fe12SLen Brown } 600b4f9fe12SLen Brown EXPORT_SYMBOL_GPL(wmi_remove_notify_handler); 601b4f9fe12SLen Brown 602b4f9fe12SLen Brown /** 603b4f9fe12SLen Brown * wmi_get_event_data - Get WMI data associated with an event 604b4f9fe12SLen Brown * 6053e9b988eSAnisse Astier * @event: Event to find 6063e9b988eSAnisse Astier * @out: Buffer to hold event data. out->pointer should be freed with kfree() 607b4f9fe12SLen Brown * 608b4f9fe12SLen Brown * Returns extra data associated with an event in WMI. 609b4f9fe12SLen Brown */ 610b4f9fe12SLen Brown acpi_status wmi_get_event_data(u32 event, struct acpi_buffer *out) 611b4f9fe12SLen Brown { 612b4f9fe12SLen Brown struct acpi_object_list input; 613b4f9fe12SLen Brown union acpi_object params[1]; 614b4f9fe12SLen Brown struct guid_block *gblock; 615b4f9fe12SLen Brown struct wmi_block *wblock; 616b4f9fe12SLen Brown struct list_head *p; 617b4f9fe12SLen Brown 618b4f9fe12SLen Brown input.count = 1; 619b4f9fe12SLen Brown input.pointer = params; 620b4f9fe12SLen Brown params[0].type = ACPI_TYPE_INTEGER; 621b4f9fe12SLen Brown params[0].integer.value = event; 622b4f9fe12SLen Brown 623762e1a2fSDmitry Torokhov list_for_each(p, &wmi_block_list) { 624b4f9fe12SLen Brown wblock = list_entry(p, struct wmi_block, list); 625b4f9fe12SLen Brown gblock = &wblock->gblock; 626b4f9fe12SLen Brown 627b4f9fe12SLen Brown if ((gblock->flags & ACPI_WMI_EVENT) && 628b4f9fe12SLen Brown (gblock->notify_id == event)) 629b4f9fe12SLen Brown return acpi_evaluate_object(wblock->handle, "_WED", 630b4f9fe12SLen Brown &input, out); 631b4f9fe12SLen Brown } 632b4f9fe12SLen Brown 633b4f9fe12SLen Brown return AE_NOT_FOUND; 634b4f9fe12SLen Brown } 635b4f9fe12SLen Brown EXPORT_SYMBOL_GPL(wmi_get_event_data); 636b4f9fe12SLen Brown 637b4f9fe12SLen Brown /** 638b4f9fe12SLen Brown * wmi_has_guid - Check if a GUID is available 639b4f9fe12SLen Brown * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba 640b4f9fe12SLen Brown * 641b4f9fe12SLen Brown * Check if a given GUID is defined by _WDG 642b4f9fe12SLen Brown */ 643b4f9fe12SLen Brown bool wmi_has_guid(const char *guid_string) 644b4f9fe12SLen Brown { 645b4f9fe12SLen Brown return find_guid(guid_string, NULL); 646b4f9fe12SLen Brown } 647b4f9fe12SLen Brown EXPORT_SYMBOL_GPL(wmi_has_guid); 648b4f9fe12SLen Brown 649b4f9fe12SLen Brown /* 6501caab3c1SMatthew Garrett * sysfs interface 6511caab3c1SMatthew Garrett */ 652614ef432SDmitry Torokhov static ssize_t modalias_show(struct device *dev, struct device_attribute *attr, 6531caab3c1SMatthew Garrett char *buf) 6541caab3c1SMatthew Garrett { 6551caab3c1SMatthew Garrett char guid_string[37]; 6561caab3c1SMatthew Garrett struct wmi_block *wblock; 6571caab3c1SMatthew Garrett 6581caab3c1SMatthew Garrett wblock = dev_get_drvdata(dev); 6591caab3c1SMatthew Garrett if (!wblock) 6601caab3c1SMatthew Garrett return -ENOMEM; 6611caab3c1SMatthew Garrett 6621caab3c1SMatthew Garrett wmi_gtoa(wblock->gblock.guid, guid_string); 6631caab3c1SMatthew Garrett 6641caab3c1SMatthew Garrett return sprintf(buf, "wmi:%s\n", guid_string); 6651caab3c1SMatthew Garrett } 666614ef432SDmitry Torokhov 667614ef432SDmitry Torokhov static struct device_attribute wmi_dev_attrs[] = { 668614ef432SDmitry Torokhov __ATTR_RO(modalias), 669614ef432SDmitry Torokhov __ATTR_NULL 670614ef432SDmitry Torokhov }; 6711caab3c1SMatthew Garrett 6721caab3c1SMatthew Garrett static int wmi_dev_uevent(struct device *dev, struct kobj_uevent_env *env) 6731caab3c1SMatthew Garrett { 6741caab3c1SMatthew Garrett char guid_string[37]; 6751caab3c1SMatthew Garrett 6761caab3c1SMatthew Garrett struct wmi_block *wblock; 6771caab3c1SMatthew Garrett 6781caab3c1SMatthew Garrett if (add_uevent_var(env, "MODALIAS=")) 6791caab3c1SMatthew Garrett return -ENOMEM; 6801caab3c1SMatthew Garrett 6811caab3c1SMatthew Garrett wblock = dev_get_drvdata(dev); 6821caab3c1SMatthew Garrett if (!wblock) 6831caab3c1SMatthew Garrett return -ENOMEM; 6841caab3c1SMatthew Garrett 6851caab3c1SMatthew Garrett wmi_gtoa(wblock->gblock.guid, guid_string); 6861caab3c1SMatthew Garrett 6871caab3c1SMatthew Garrett strcpy(&env->buf[env->buflen - 1], "wmi:"); 6881caab3c1SMatthew Garrett memcpy(&env->buf[env->buflen - 1 + 4], guid_string, 36); 6891caab3c1SMatthew Garrett env->buflen += 40; 6901caab3c1SMatthew Garrett 6911caab3c1SMatthew Garrett return 0; 6921caab3c1SMatthew Garrett } 6931caab3c1SMatthew Garrett 6941caab3c1SMatthew Garrett static void wmi_dev_free(struct device *dev) 6951caab3c1SMatthew Garrett { 6961caab3c1SMatthew Garrett kfree(dev); 6971caab3c1SMatthew Garrett } 6981caab3c1SMatthew Garrett 6991caab3c1SMatthew Garrett static struct class wmi_class = { 7001caab3c1SMatthew Garrett .name = "wmi", 7011caab3c1SMatthew Garrett .dev_release = wmi_dev_free, 7021caab3c1SMatthew Garrett .dev_uevent = wmi_dev_uevent, 703614ef432SDmitry Torokhov .dev_attrs = wmi_dev_attrs, 7041caab3c1SMatthew Garrett }; 7051caab3c1SMatthew Garrett 7061caab3c1SMatthew Garrett static int wmi_create_devs(void) 7071caab3c1SMatthew Garrett { 7081caab3c1SMatthew Garrett int result; 7091caab3c1SMatthew Garrett char guid_string[37]; 7101caab3c1SMatthew Garrett struct guid_block *gblock; 7111caab3c1SMatthew Garrett struct wmi_block *wblock; 7121caab3c1SMatthew Garrett struct list_head *p; 7131caab3c1SMatthew Garrett struct device *guid_dev; 7141caab3c1SMatthew Garrett 7151caab3c1SMatthew Garrett /* Create devices for all the GUIDs */ 716762e1a2fSDmitry Torokhov list_for_each(p, &wmi_block_list) { 7171caab3c1SMatthew Garrett wblock = list_entry(p, struct wmi_block, list); 7181caab3c1SMatthew Garrett 7191caab3c1SMatthew Garrett guid_dev = kzalloc(sizeof(struct device), GFP_KERNEL); 7201caab3c1SMatthew Garrett if (!guid_dev) 7211caab3c1SMatthew Garrett return -ENOMEM; 7221caab3c1SMatthew Garrett 7231caab3c1SMatthew Garrett wblock->dev = guid_dev; 7241caab3c1SMatthew Garrett 7251caab3c1SMatthew Garrett guid_dev->class = &wmi_class; 7261caab3c1SMatthew Garrett dev_set_drvdata(guid_dev, wblock); 7271caab3c1SMatthew Garrett 7281caab3c1SMatthew Garrett gblock = &wblock->gblock; 7291caab3c1SMatthew Garrett 7301caab3c1SMatthew Garrett wmi_gtoa(gblock->guid, guid_string); 7311caab3c1SMatthew Garrett dev_set_name(guid_dev, guid_string); 7321caab3c1SMatthew Garrett 7331caab3c1SMatthew Garrett result = device_register(guid_dev); 7341caab3c1SMatthew Garrett if (result) 7351caab3c1SMatthew Garrett return result; 7361caab3c1SMatthew Garrett } 7371caab3c1SMatthew Garrett 7381caab3c1SMatthew Garrett return 0; 7391caab3c1SMatthew Garrett } 7401caab3c1SMatthew Garrett 7411caab3c1SMatthew Garrett static void wmi_remove_devs(void) 7421caab3c1SMatthew Garrett { 7431caab3c1SMatthew Garrett struct guid_block *gblock; 7441caab3c1SMatthew Garrett struct wmi_block *wblock; 7451caab3c1SMatthew Garrett struct list_head *p; 7461caab3c1SMatthew Garrett struct device *guid_dev; 7471caab3c1SMatthew Garrett 7481caab3c1SMatthew Garrett /* Delete devices for all the GUIDs */ 749762e1a2fSDmitry Torokhov list_for_each(p, &wmi_block_list) { 7501caab3c1SMatthew Garrett wblock = list_entry(p, struct wmi_block, list); 7511caab3c1SMatthew Garrett 7521caab3c1SMatthew Garrett guid_dev = wblock->dev; 7531caab3c1SMatthew Garrett gblock = &wblock->gblock; 7541caab3c1SMatthew Garrett 7551caab3c1SMatthew Garrett device_unregister(guid_dev); 7561caab3c1SMatthew Garrett } 7571caab3c1SMatthew Garrett } 7581caab3c1SMatthew Garrett 7591caab3c1SMatthew Garrett static void wmi_class_exit(void) 7601caab3c1SMatthew Garrett { 7611caab3c1SMatthew Garrett wmi_remove_devs(); 7621caab3c1SMatthew Garrett class_unregister(&wmi_class); 7631caab3c1SMatthew Garrett } 7641caab3c1SMatthew Garrett 7651caab3c1SMatthew Garrett static int wmi_class_init(void) 7661caab3c1SMatthew Garrett { 7671caab3c1SMatthew Garrett int ret; 7681caab3c1SMatthew Garrett 7691caab3c1SMatthew Garrett ret = class_register(&wmi_class); 7701caab3c1SMatthew Garrett if (ret) 7711caab3c1SMatthew Garrett return ret; 7721caab3c1SMatthew Garrett 7731caab3c1SMatthew Garrett ret = wmi_create_devs(); 7741caab3c1SMatthew Garrett if (ret) 7751caab3c1SMatthew Garrett wmi_class_exit(); 7761caab3c1SMatthew Garrett 7771caab3c1SMatthew Garrett return ret; 7781caab3c1SMatthew Garrett } 7791caab3c1SMatthew Garrett 780d1f9e497SCarlos Corbacho static bool guid_already_parsed(const char *guid_string) 781d1f9e497SCarlos Corbacho { 782d1f9e497SCarlos Corbacho struct guid_block *gblock; 783d1f9e497SCarlos Corbacho struct wmi_block *wblock; 784d1f9e497SCarlos Corbacho struct list_head *p; 785d1f9e497SCarlos Corbacho 786762e1a2fSDmitry Torokhov list_for_each(p, &wmi_block_list) { 787d1f9e497SCarlos Corbacho wblock = list_entry(p, struct wmi_block, list); 788d1f9e497SCarlos Corbacho gblock = &wblock->gblock; 789d1f9e497SCarlos Corbacho 790d1f9e497SCarlos Corbacho if (strncmp(gblock->guid, guid_string, 16) == 0) 791d1f9e497SCarlos Corbacho return true; 792d1f9e497SCarlos Corbacho } 793d1f9e497SCarlos Corbacho return false; 794d1f9e497SCarlos Corbacho } 795d1f9e497SCarlos Corbacho 7962d5ab555SDmitry Torokhov static void free_wmi_blocks(void) 7972d5ab555SDmitry Torokhov { 7982d5ab555SDmitry Torokhov struct wmi_block *wblock, *next; 7992d5ab555SDmitry Torokhov 800762e1a2fSDmitry Torokhov list_for_each_entry_safe(wblock, next, &wmi_block_list, list) { 8012d5ab555SDmitry Torokhov list_del(&wblock->list); 8022d5ab555SDmitry Torokhov kfree(wblock); 8032d5ab555SDmitry Torokhov } 8042d5ab555SDmitry Torokhov } 8052d5ab555SDmitry Torokhov 8061caab3c1SMatthew Garrett /* 807b4f9fe12SLen Brown * Parse the _WDG method for the GUID data blocks 808b4f9fe12SLen Brown */ 809925b1089SThomas Renninger static acpi_status parse_wdg(acpi_handle handle) 810b4f9fe12SLen Brown { 811b4f9fe12SLen Brown struct acpi_buffer out = {ACPI_ALLOCATE_BUFFER, NULL}; 812b4f9fe12SLen Brown union acpi_object *obj; 81337830662SDmitry Torokhov const struct guid_block *gblock; 814b4f9fe12SLen Brown struct wmi_block *wblock; 815d1f9e497SCarlos Corbacho char guid_string[37]; 816b4f9fe12SLen Brown acpi_status status; 817b4f9fe12SLen Brown u32 i, total; 818b4f9fe12SLen Brown 819b4f9fe12SLen Brown status = acpi_evaluate_object(handle, "_WDG", NULL, &out); 820b4f9fe12SLen Brown 821b4f9fe12SLen Brown if (ACPI_FAILURE(status)) 822b4f9fe12SLen Brown return status; 823b4f9fe12SLen Brown 824b4f9fe12SLen Brown obj = (union acpi_object *) out.pointer; 8253d2c63ebSDmitry Torokhov if (!obj) 8263d2c63ebSDmitry Torokhov return AE_ERROR; 827b4f9fe12SLen Brown 82864ed0ab8SDmitry Torokhov if (obj->type != ACPI_TYPE_BUFFER) { 82964ed0ab8SDmitry Torokhov status = AE_ERROR; 83064ed0ab8SDmitry Torokhov goto out_free_pointer; 83164ed0ab8SDmitry Torokhov } 832b4f9fe12SLen Brown 83337830662SDmitry Torokhov gblock = (const struct guid_block *)obj->buffer.pointer; 834b4f9fe12SLen Brown total = obj->buffer.length / sizeof(struct guid_block); 835b4f9fe12SLen Brown 836b4f9fe12SLen Brown for (i = 0; i < total; i++) { 837d1f9e497SCarlos Corbacho /* 838d1f9e497SCarlos Corbacho Some WMI devices, like those for nVidia hooks, have a 839d1f9e497SCarlos Corbacho duplicate GUID. It's not clear what we should do in this 840d1f9e497SCarlos Corbacho case yet, so for now, we'll just ignore the duplicate. 841d1f9e497SCarlos Corbacho Anyone who wants to add support for that device can come 842d1f9e497SCarlos Corbacho up with a better workaround for the mess then. 843d1f9e497SCarlos Corbacho */ 844d1f9e497SCarlos Corbacho if (guid_already_parsed(gblock[i].guid) == true) { 845d1f9e497SCarlos Corbacho wmi_gtoa(gblock[i].guid, guid_string); 8468e07514dSDmitry Torokhov pr_info("Skipping duplicate GUID %s\n", guid_string); 847d1f9e497SCarlos Corbacho continue; 848d1f9e497SCarlos Corbacho } 849a929aae0SThomas Renninger if (debug_dump_wdg) 850a929aae0SThomas Renninger wmi_dump_wdg(&gblock[i]); 851a929aae0SThomas Renninger 852b4f9fe12SLen Brown wblock = kzalloc(sizeof(struct wmi_block), GFP_KERNEL); 853a5167c5bSAxel Lin if (!wblock) { 854a5167c5bSAxel Lin status = AE_NO_MEMORY; 85537830662SDmitry Torokhov goto out_free_pointer; 856a5167c5bSAxel Lin } 857b4f9fe12SLen Brown 858b4f9fe12SLen Brown wblock->gblock = gblock[i]; 859b4f9fe12SLen Brown wblock->handle = handle; 860fc3155b2SThomas Renninger if (debug_event) { 861fc3155b2SThomas Renninger wblock->handler = wmi_notify_debug; 8622d5ab555SDmitry Torokhov wmi_method_enable(wblock, 1); 863fc3155b2SThomas Renninger } 864762e1a2fSDmitry Torokhov list_add_tail(&wblock->list, &wmi_block_list); 865b4f9fe12SLen Brown } 866b4f9fe12SLen Brown 867a5167c5bSAxel Lin out_free_pointer: 868a5167c5bSAxel Lin kfree(out.pointer); 869b4f9fe12SLen Brown 8702d5ab555SDmitry Torokhov if (ACPI_FAILURE(status)) 8712d5ab555SDmitry Torokhov free_wmi_blocks(); 8722d5ab555SDmitry Torokhov 873b4f9fe12SLen Brown return status; 874b4f9fe12SLen Brown } 875b4f9fe12SLen Brown 876b4f9fe12SLen Brown /* 877b4f9fe12SLen Brown * WMI can have EmbeddedControl access regions. In which case, we just want to 878b4f9fe12SLen Brown * hand these off to the EC driver. 879b4f9fe12SLen Brown */ 880b4f9fe12SLen Brown static acpi_status 881b4f9fe12SLen Brown acpi_wmi_ec_space_handler(u32 function, acpi_physical_address address, 882439913ffSLin Ming u32 bits, u64 *value, 883b4f9fe12SLen Brown void *handler_context, void *region_context) 884b4f9fe12SLen Brown { 885b4f9fe12SLen Brown int result = 0, i = 0; 886b4f9fe12SLen Brown u8 temp = 0; 887b4f9fe12SLen Brown 888b4f9fe12SLen Brown if ((address > 0xFF) || !value) 889b4f9fe12SLen Brown return AE_BAD_PARAMETER; 890b4f9fe12SLen Brown 891b4f9fe12SLen Brown if (function != ACPI_READ && function != ACPI_WRITE) 892b4f9fe12SLen Brown return AE_BAD_PARAMETER; 893b4f9fe12SLen Brown 894b4f9fe12SLen Brown if (bits != 8) 895b4f9fe12SLen Brown return AE_BAD_PARAMETER; 896b4f9fe12SLen Brown 897b4f9fe12SLen Brown if (function == ACPI_READ) { 898b4f9fe12SLen Brown result = ec_read(address, &temp); 899439913ffSLin Ming (*value) |= ((u64)temp) << i; 900b4f9fe12SLen Brown } else { 901b4f9fe12SLen Brown temp = 0xff & ((*value) >> i); 902b4f9fe12SLen Brown result = ec_write(address, temp); 903b4f9fe12SLen Brown } 904b4f9fe12SLen Brown 905b4f9fe12SLen Brown switch (result) { 906b4f9fe12SLen Brown case -EINVAL: 907b4f9fe12SLen Brown return AE_BAD_PARAMETER; 908b4f9fe12SLen Brown break; 909b4f9fe12SLen Brown case -ENODEV: 910b4f9fe12SLen Brown return AE_NOT_FOUND; 911b4f9fe12SLen Brown break; 912b4f9fe12SLen Brown case -ETIME: 913b4f9fe12SLen Brown return AE_TIME; 914b4f9fe12SLen Brown break; 915b4f9fe12SLen Brown default: 916b4f9fe12SLen Brown return AE_OK; 917b4f9fe12SLen Brown } 918b4f9fe12SLen Brown } 919b4f9fe12SLen Brown 920f61bb939SBjorn Helgaas static void acpi_wmi_notify(struct acpi_device *device, u32 event) 921b4f9fe12SLen Brown { 922b4f9fe12SLen Brown struct guid_block *block; 923b4f9fe12SLen Brown struct wmi_block *wblock; 924b4f9fe12SLen Brown struct list_head *p; 9257715348cSThomas Renninger char guid_string[37]; 926b4f9fe12SLen Brown 927762e1a2fSDmitry Torokhov list_for_each(p, &wmi_block_list) { 928b4f9fe12SLen Brown wblock = list_entry(p, struct wmi_block, list); 929b4f9fe12SLen Brown block = &wblock->gblock; 930b4f9fe12SLen Brown 931b4f9fe12SLen Brown if ((block->flags & ACPI_WMI_EVENT) && 932b4f9fe12SLen Brown (block->notify_id == event)) { 933b4f9fe12SLen Brown if (wblock->handler) 934b4f9fe12SLen Brown wblock->handler(event, wblock->handler_data); 9357715348cSThomas Renninger if (debug_event) { 9367715348cSThomas Renninger wmi_gtoa(wblock->gblock.guid, guid_string); 9378e07514dSDmitry Torokhov pr_info("DEBUG Event GUID: %s\n", guid_string); 9387715348cSThomas Renninger } 939b4f9fe12SLen Brown 940b4f9fe12SLen Brown acpi_bus_generate_netlink_event( 941b4f9fe12SLen Brown device->pnp.device_class, dev_name(&device->dev), 942b4f9fe12SLen Brown event, 0); 943b4f9fe12SLen Brown break; 944b4f9fe12SLen Brown } 945b4f9fe12SLen Brown } 946b4f9fe12SLen Brown } 947b4f9fe12SLen Brown 948b4f9fe12SLen Brown static int acpi_wmi_remove(struct acpi_device *device, int type) 949b4f9fe12SLen Brown { 950b4f9fe12SLen Brown acpi_remove_address_space_handler(device->handle, 951b4f9fe12SLen Brown ACPI_ADR_SPACE_EC, &acpi_wmi_ec_space_handler); 952b4f9fe12SLen Brown 953b4f9fe12SLen Brown return 0; 954b4f9fe12SLen Brown } 955b4f9fe12SLen Brown 956925b1089SThomas Renninger static int acpi_wmi_add(struct acpi_device *device) 957b4f9fe12SLen Brown { 958b4f9fe12SLen Brown acpi_status status; 959b4f9fe12SLen Brown int result = 0; 960b4f9fe12SLen Brown 961b4f9fe12SLen Brown status = acpi_install_address_space_handler(device->handle, 962b4f9fe12SLen Brown ACPI_ADR_SPACE_EC, 963b4f9fe12SLen Brown &acpi_wmi_ec_space_handler, 964b4f9fe12SLen Brown NULL, NULL); 9655212cd67SDmitry Torokhov if (ACPI_FAILURE(status)) { 9668e07514dSDmitry Torokhov pr_err("Error installing EC region handler\n"); 967b4f9fe12SLen Brown return -ENODEV; 9685212cd67SDmitry Torokhov } 969b4f9fe12SLen Brown 970b4f9fe12SLen Brown status = parse_wdg(device->handle); 971b4f9fe12SLen Brown if (ACPI_FAILURE(status)) { 9725212cd67SDmitry Torokhov acpi_remove_address_space_handler(device->handle, 9735212cd67SDmitry Torokhov ACPI_ADR_SPACE_EC, 9745212cd67SDmitry Torokhov &acpi_wmi_ec_space_handler); 9758e07514dSDmitry Torokhov pr_err("Failed to parse WDG method\n"); 976b4f9fe12SLen Brown return -ENODEV; 977b4f9fe12SLen Brown } 978b4f9fe12SLen Brown 979b4f9fe12SLen Brown return result; 980b4f9fe12SLen Brown } 981b4f9fe12SLen Brown 982b4f9fe12SLen Brown static int __init acpi_wmi_init(void) 983b4f9fe12SLen Brown { 984da511997SRoel Kluin int result; 985b4f9fe12SLen Brown 986b4f9fe12SLen Brown if (acpi_disabled) 987b4f9fe12SLen Brown return -ENODEV; 988b4f9fe12SLen Brown 989b4f9fe12SLen Brown result = acpi_bus_register_driver(&acpi_wmi_driver); 990b4f9fe12SLen Brown if (result < 0) { 9918e07514dSDmitry Torokhov pr_err("Error loading mapper\n"); 9921caab3c1SMatthew Garrett return -ENODEV; 993b4f9fe12SLen Brown } 994b4f9fe12SLen Brown 9951caab3c1SMatthew Garrett result = wmi_class_init(); 9961caab3c1SMatthew Garrett if (result) { 9971caab3c1SMatthew Garrett acpi_bus_unregister_driver(&acpi_wmi_driver); 9981caab3c1SMatthew Garrett return result; 9991caab3c1SMatthew Garrett } 10001caab3c1SMatthew Garrett 10018e07514dSDmitry Torokhov pr_info("Mapper loaded\n"); 10021caab3c1SMatthew Garrett 10038e07514dSDmitry Torokhov return 0; 1004b4f9fe12SLen Brown } 1005b4f9fe12SLen Brown 1006b4f9fe12SLen Brown static void __exit acpi_wmi_exit(void) 1007b4f9fe12SLen Brown { 10081caab3c1SMatthew Garrett wmi_class_exit(); 1009b4f9fe12SLen Brown acpi_bus_unregister_driver(&acpi_wmi_driver); 10102d5ab555SDmitry Torokhov free_wmi_blocks(); 1011b4f9fe12SLen Brown 10128e07514dSDmitry Torokhov pr_info("Mapper unloaded\n"); 1013b4f9fe12SLen Brown } 1014b4f9fe12SLen Brown 1015b4f9fe12SLen Brown subsys_initcall(acpi_wmi_init); 1016b4f9fe12SLen Brown module_exit(acpi_wmi_exit); 1017