11a59d1b8SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later 2b4f9fe12SLen Brown /* 3b4f9fe12SLen Brown * ACPI-WMI mapping driver 4b4f9fe12SLen Brown * 5b4f9fe12SLen Brown * Copyright (C) 2007-2008 Carlos Corbacho <carlos@strangeworlds.co.uk> 6b4f9fe12SLen Brown * 7b4f9fe12SLen Brown * GUID parsing code from ldm.c is: 8b4f9fe12SLen Brown * Copyright (C) 2001,2002 Richard Russon <ldm@flatcap.org> 9b4f9fe12SLen Brown * Copyright (c) 2001-2007 Anton Altaparmakov 10b4f9fe12SLen Brown * Copyright (C) 2001,2002 Jakob Kemi <jakob.kemi@telia.com> 11b4f9fe12SLen Brown * 122c9c5664SDarren Hart (VMware) * WMI bus infrastructure by Andrew Lutomirski and Darren Hart: 132c9c5664SDarren Hart (VMware) * Copyright (C) 2015 Andrew Lutomirski 142c9c5664SDarren Hart (VMware) * Copyright (C) 2017 VMware, Inc. All Rights Reserved. 15b4f9fe12SLen Brown */ 16b4f9fe12SLen Brown 178e07514dSDmitry Torokhov #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 188e07514dSDmitry Torokhov 19b4f9fe12SLen Brown #include <linux/acpi.h> 201c95ace7SBarnabás Pőcze #include <linux/bits.h> 21dea878d8SBarnabás Pőcze #include <linux/build_bug.h> 22b60ee4e0SMario Limonciello #include <linux/device.h> 23b60ee4e0SMario Limonciello #include <linux/init.h> 24b60ee4e0SMario Limonciello #include <linux/kernel.h> 25b60ee4e0SMario Limonciello #include <linux/list.h> 2644b6b766SMario Limonciello #include <linux/miscdevice.h> 277c52d551SPaul Gortmaker #include <linux/module.h> 289599ed91SAndy Lutomirski #include <linux/platform_device.h> 29b60ee4e0SMario Limonciello #include <linux/slab.h> 306133913aSBarnabás Pőcze #include <linux/sysfs.h> 31b60ee4e0SMario Limonciello #include <linux/types.h> 3244b6b766SMario Limonciello #include <linux/uaccess.h> 33538d7eb8SAndy Shevchenko #include <linux/uuid.h> 34b60ee4e0SMario Limonciello #include <linux/wmi.h> 35df23e2beSPeter Zijlstra #include <linux/fs.h> 3644b6b766SMario Limonciello #include <uapi/linux/wmi.h> 37b4f9fe12SLen Brown 38b4f9fe12SLen Brown MODULE_AUTHOR("Carlos Corbacho"); 39b4f9fe12SLen Brown MODULE_DESCRIPTION("ACPI-WMI Mapping Driver"); 40b4f9fe12SLen Brown MODULE_LICENSE("GPL"); 41b4f9fe12SLen Brown 42762e1a2fSDmitry Torokhov static LIST_HEAD(wmi_block_list); 43b4f9fe12SLen Brown 44b4f9fe12SLen Brown struct guid_block { 4567f472fdSBarnabás Pőcze guid_t guid; 46b4f9fe12SLen Brown union { 47b4f9fe12SLen Brown char object_id[2]; 48b4f9fe12SLen Brown struct { 49b4f9fe12SLen Brown unsigned char notify_id; 50b4f9fe12SLen Brown unsigned char reserved; 51b4f9fe12SLen Brown }; 52b4f9fe12SLen Brown }; 53b4f9fe12SLen Brown u8 instance_count; 54b4f9fe12SLen Brown u8 flags; 55dea878d8SBarnabás Pőcze } __packed; 56dea878d8SBarnabás Pőcze static_assert(sizeof(typeof_member(struct guid_block, guid)) == 16); 57dea878d8SBarnabás Pőcze static_assert(sizeof(struct guid_block) == 20); 58dea878d8SBarnabás Pőcze static_assert(__alignof__(struct guid_block) == 1); 59b4f9fe12SLen Brown 60b4f9fe12SLen Brown struct wmi_block { 61844af950SAndy Lutomirski struct wmi_device dev; 62b4f9fe12SLen Brown struct list_head list; 63b4f9fe12SLen Brown struct guid_block gblock; 6444b6b766SMario Limonciello struct miscdevice char_dev; 6544b6b766SMario Limonciello struct mutex char_mutex; 66b0e86302SAndy Lutomirski struct acpi_device *acpi_device; 67b4f9fe12SLen Brown wmi_notify_handler handler; 68b4f9fe12SLen Brown void *handler_data; 6944b6b766SMario Limonciello u64 req_buf_size; 70d4fc91adSAndy Lutomirski 71fd70da6aSDarren Hart (VMware) bool read_takes_no_args; 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 */ 791c95ace7SBarnabás Pőcze #define ACPI_WMI_EXPENSIVE BIT(0) 801c95ace7SBarnabás Pőcze #define ACPI_WMI_METHOD BIT(1) /* GUID is a method */ 811c95ace7SBarnabás Pőcze #define ACPI_WMI_STRING BIT(2) /* GUID takes & returns a string */ 821c95ace7SBarnabás Pőcze #define ACPI_WMI_EVENT BIT(3) /* GUID is an event */ 83b4f9fe12SLen Brown 8490ab5ee9SRusty Russell static bool 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 8990ab5ee9SRusty Russell static bool 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 949599ed91SAndy Lutomirski static int acpi_wmi_remove(struct platform_device *device); 959599ed91SAndy Lutomirski static int acpi_wmi_probe(struct platform_device *device); 96b4f9fe12SLen Brown 97b4f9fe12SLen Brown static const struct acpi_device_id wmi_device_ids[] = { 98b4f9fe12SLen Brown {"PNP0C14", 0}, 99b4f9fe12SLen Brown {"pnp0c14", 0}, 1009bf9ca95SBarnabás Pőcze { } 101b4f9fe12SLen Brown }; 102b4f9fe12SLen Brown MODULE_DEVICE_TABLE(acpi, wmi_device_ids); 103b4f9fe12SLen Brown 1049599ed91SAndy Lutomirski static struct platform_driver acpi_wmi_driver = { 1059599ed91SAndy Lutomirski .driver = { 106844af950SAndy Lutomirski .name = "acpi-wmi", 1079599ed91SAndy Lutomirski .acpi_match_table = wmi_device_ids, 108b4f9fe12SLen Brown }, 1099599ed91SAndy Lutomirski .probe = acpi_wmi_probe, 1109599ed91SAndy Lutomirski .remove = acpi_wmi_remove, 111b4f9fe12SLen Brown }; 112b4f9fe12SLen Brown 113b4f9fe12SLen Brown /* 114b4f9fe12SLen Brown * GUID parsing functions 115b4f9fe12SLen Brown */ 116b4f9fe12SLen Brown 117b4f9fe12SLen Brown static bool find_guid(const char *guid_string, struct wmi_block **out) 118b4f9fe12SLen Brown { 119f9dffc14SAndy Shevchenko guid_t guid_input; 120b4f9fe12SLen Brown struct wmi_block *wblock; 121b4f9fe12SLen Brown 122f9dffc14SAndy Shevchenko if (guid_parse(guid_string, &guid_input)) 123538d7eb8SAndy Shevchenko return false; 124b4f9fe12SLen Brown 125cedb3b2aSAndy Shevchenko list_for_each_entry(wblock, &wmi_block_list, list) { 1261ce69d2bSBarnabás Pőcze if (guid_equal(&wblock->gblock.guid, &guid_input)) { 127b4f9fe12SLen Brown if (out) 128b4f9fe12SLen Brown *out = wblock; 129097c27fcSJoe Perches return true; 130b4f9fe12SLen Brown } 131b4f9fe12SLen Brown } 132097c27fcSJoe Perches return false; 133b4f9fe12SLen Brown } 134b4f9fe12SLen Brown 135a48e2338SMattias Jacobsson static const void *find_guid_context(struct wmi_block *wblock, 136a48e2338SMattias Jacobsson struct wmi_driver *wdriver) 137a48e2338SMattias Jacobsson { 138a48e2338SMattias Jacobsson const struct wmi_device_id *id; 139a48e2338SMattias Jacobsson 1406e0bc588SBarnabás Pőcze id = wdriver->id_table; 1416e0bc588SBarnabás Pőcze if (!id) 142a48e2338SMattias Jacobsson return NULL; 143a48e2338SMattias Jacobsson 144a48e2338SMattias Jacobsson while (*id->guid_string) { 145f5431bf1SBarnabás Pőcze guid_t guid_input; 146f5431bf1SBarnabás Pőcze 147f9dffc14SAndy Shevchenko if (guid_parse(id->guid_string, &guid_input)) 148a48e2338SMattias Jacobsson continue; 14967f472fdSBarnabás Pőcze if (guid_equal(&wblock->gblock.guid, &guid_input)) 150a48e2338SMattias Jacobsson return id->context; 151a48e2338SMattias Jacobsson id++; 152a48e2338SMattias Jacobsson } 153a48e2338SMattias Jacobsson return NULL; 154a48e2338SMattias Jacobsson } 155a48e2338SMattias Jacobsson 156d4fc91adSAndy Lutomirski static int get_subobj_info(acpi_handle handle, const char *pathname, 157d4fc91adSAndy Lutomirski struct acpi_device_info **info) 158d4fc91adSAndy Lutomirski { 159d4fc91adSAndy Lutomirski struct acpi_device_info *dummy_info, **info_ptr; 160d4fc91adSAndy Lutomirski acpi_handle subobj_handle; 161d4fc91adSAndy Lutomirski acpi_status status; 162d4fc91adSAndy Lutomirski 163d4fc91adSAndy Lutomirski status = acpi_get_handle(handle, (char *)pathname, &subobj_handle); 164d4fc91adSAndy Lutomirski if (status == AE_NOT_FOUND) 165d4fc91adSAndy Lutomirski return -ENOENT; 166d4fc91adSAndy Lutomirski else if (ACPI_FAILURE(status)) 167d4fc91adSAndy Lutomirski return -EIO; 168d4fc91adSAndy Lutomirski 169d4fc91adSAndy Lutomirski info_ptr = info ? info : &dummy_info; 170d4fc91adSAndy Lutomirski status = acpi_get_object_info(subobj_handle, info_ptr); 171d4fc91adSAndy Lutomirski if (ACPI_FAILURE(status)) 172d4fc91adSAndy Lutomirski return -EIO; 173d4fc91adSAndy Lutomirski 174d4fc91adSAndy Lutomirski if (!info) 175d4fc91adSAndy Lutomirski kfree(dummy_info); 176d4fc91adSAndy Lutomirski 177d4fc91adSAndy Lutomirski return 0; 178d4fc91adSAndy Lutomirski } 179d4fc91adSAndy Lutomirski 180285dd01aSBarnabás Pőcze static acpi_status wmi_method_enable(struct wmi_block *wblock, bool enable) 181b4f9fe12SLen Brown { 18243aacf83SBarnabás Pőcze struct guid_block *block; 183b4f9fe12SLen Brown char method[5]; 184b4f9fe12SLen Brown acpi_status status; 185b4f9fe12SLen Brown acpi_handle handle; 186b4f9fe12SLen Brown 187b4f9fe12SLen Brown block = &wblock->gblock; 188b0e86302SAndy Lutomirski handle = wblock->acpi_device->handle; 189b4f9fe12SLen Brown 190b4f9fe12SLen Brown snprintf(method, 5, "WE%02X", block->notify_id); 1918122ab66SZhang Rui status = acpi_execute_simple_method(handle, method, enable); 192736b48aaSBarnabás Pőcze if (status == AE_NOT_FOUND) 193b4f9fe12SLen Brown return AE_OK; 194736b48aaSBarnabás Pőcze 195736b48aaSBarnabás Pőcze return status; 196b4f9fe12SLen Brown } 197b4f9fe12SLen Brown 19857f2ce89SBarnabás Pőcze #define WMI_ACPI_METHOD_NAME_SIZE 5 19957f2ce89SBarnabás Pőcze 20057f2ce89SBarnabás Pőcze static inline void get_acpi_method_name(const struct wmi_block *wblock, 20157f2ce89SBarnabás Pőcze const char method, 20257f2ce89SBarnabás Pőcze char buffer[static WMI_ACPI_METHOD_NAME_SIZE]) 20357f2ce89SBarnabás Pőcze { 20457f2ce89SBarnabás Pőcze static_assert(ARRAY_SIZE(wblock->gblock.object_id) == 2); 20557f2ce89SBarnabás Pőcze static_assert(WMI_ACPI_METHOD_NAME_SIZE >= 5); 20657f2ce89SBarnabás Pőcze 20757f2ce89SBarnabás Pőcze buffer[0] = 'W'; 20857f2ce89SBarnabás Pőcze buffer[1] = method; 20957f2ce89SBarnabás Pőcze buffer[2] = wblock->gblock.object_id[0]; 21057f2ce89SBarnabás Pőcze buffer[3] = wblock->gblock.object_id[1]; 21157f2ce89SBarnabás Pőcze buffer[4] = '\0'; 21257f2ce89SBarnabás Pőcze } 21357f2ce89SBarnabás Pőcze 21451142a08SBarnabás Pőcze static inline acpi_object_type get_param_acpi_type(const struct wmi_block *wblock) 21551142a08SBarnabás Pőcze { 21651142a08SBarnabás Pőcze if (wblock->gblock.flags & ACPI_WMI_STRING) 21751142a08SBarnabás Pőcze return ACPI_TYPE_STRING; 21851142a08SBarnabás Pőcze else 21951142a08SBarnabás Pőcze return ACPI_TYPE_BUFFER; 22051142a08SBarnabás Pőcze } 22151142a08SBarnabás Pőcze 222*25be44f6SBarnabás Pőcze static acpi_status get_event_data(const struct wmi_block *wblock, struct acpi_buffer *out) 223*25be44f6SBarnabás Pőcze { 224*25be44f6SBarnabás Pőcze union acpi_object param = { 225*25be44f6SBarnabás Pőcze .integer = { 226*25be44f6SBarnabás Pőcze .type = ACPI_TYPE_INTEGER, 227*25be44f6SBarnabás Pőcze .value = wblock->gblock.notify_id, 228*25be44f6SBarnabás Pőcze } 229*25be44f6SBarnabás Pőcze }; 230*25be44f6SBarnabás Pőcze struct acpi_object_list input = { 231*25be44f6SBarnabás Pőcze .count = 1, 232*25be44f6SBarnabás Pőcze .pointer = ¶m, 233*25be44f6SBarnabás Pőcze }; 234*25be44f6SBarnabás Pőcze 235*25be44f6SBarnabás Pőcze return acpi_evaluate_object(wblock->acpi_device->handle, "_WED", &input, out); 236*25be44f6SBarnabás Pőcze } 237*25be44f6SBarnabás Pőcze 238b4f9fe12SLen Brown /* 239b4f9fe12SLen Brown * Exported WMI functions 240b4f9fe12SLen Brown */ 24144b6b766SMario Limonciello 24244b6b766SMario Limonciello /** 24344b6b766SMario Limonciello * set_required_buffer_size - Sets the buffer size needed for performing IOCTL 24444b6b766SMario Limonciello * @wdev: A wmi bus device from a driver 2455a707af1SAndy Shevchenko * @length: Required buffer size 24644b6b766SMario Limonciello * 24744b6b766SMario Limonciello * Allocates memory needed for buffer, stores the buffer size in that memory 24844b6b766SMario Limonciello */ 24944b6b766SMario Limonciello int set_required_buffer_size(struct wmi_device *wdev, u64 length) 25044b6b766SMario Limonciello { 25144b6b766SMario Limonciello struct wmi_block *wblock; 25244b6b766SMario Limonciello 25344b6b766SMario Limonciello wblock = container_of(wdev, struct wmi_block, dev); 25444b6b766SMario Limonciello wblock->req_buf_size = length; 25544b6b766SMario Limonciello 25644b6b766SMario Limonciello return 0; 25744b6b766SMario Limonciello } 25844b6b766SMario Limonciello EXPORT_SYMBOL_GPL(set_required_buffer_size); 25944b6b766SMario Limonciello 260b4f9fe12SLen Brown /** 261b4f9fe12SLen Brown * wmi_evaluate_method - Evaluate a WMI method 262b4f9fe12SLen Brown * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba 263b4f9fe12SLen Brown * @instance: Instance index 264b4f9fe12SLen Brown * @method_id: Method ID to call 2655a707af1SAndy Shevchenko * @in: Buffer containing input for the method call 2665a707af1SAndy Shevchenko * @out: Empty buffer to return the method results 267b4f9fe12SLen Brown * 268b4f9fe12SLen Brown * Call an ACPI-WMI method 269b4f9fe12SLen Brown */ 270bba08f35SBarnabás Pőcze acpi_status wmi_evaluate_method(const char *guid_string, u8 instance, u32 method_id, 271bba08f35SBarnabás Pőcze const struct acpi_buffer *in, struct acpi_buffer *out) 272b4f9fe12SLen Brown { 273722c856dSMario Limonciello struct wmi_block *wblock = NULL; 274722c856dSMario Limonciello 275722c856dSMario Limonciello if (!find_guid(guid_string, &wblock)) 276722c856dSMario Limonciello return AE_ERROR; 277722c856dSMario Limonciello return wmidev_evaluate_method(&wblock->dev, instance, method_id, 278722c856dSMario Limonciello in, out); 279722c856dSMario Limonciello } 280722c856dSMario Limonciello EXPORT_SYMBOL_GPL(wmi_evaluate_method); 281722c856dSMario Limonciello 282722c856dSMario Limonciello /** 283722c856dSMario Limonciello * wmidev_evaluate_method - Evaluate a WMI method 284722c856dSMario Limonciello * @wdev: A wmi bus device from a driver 285722c856dSMario Limonciello * @instance: Instance index 286722c856dSMario Limonciello * @method_id: Method ID to call 2875a707af1SAndy Shevchenko * @in: Buffer containing input for the method call 2885a707af1SAndy Shevchenko * @out: Empty buffer to return the method results 289722c856dSMario Limonciello * 290722c856dSMario Limonciello * Call an ACPI-WMI method 291722c856dSMario Limonciello */ 292bba08f35SBarnabás Pőcze acpi_status wmidev_evaluate_method(struct wmi_device *wdev, u8 instance, u32 method_id, 293bba08f35SBarnabás Pőcze const struct acpi_buffer *in, struct acpi_buffer *out) 294722c856dSMario Limonciello { 29543aacf83SBarnabás Pőcze struct guid_block *block; 29643aacf83SBarnabás Pőcze struct wmi_block *wblock; 297b4f9fe12SLen Brown acpi_handle handle; 298b4f9fe12SLen Brown struct acpi_object_list input; 299b4f9fe12SLen Brown union acpi_object params[3]; 30057f2ce89SBarnabás Pőcze char method[WMI_ACPI_METHOD_NAME_SIZE]; 301b4f9fe12SLen Brown 302722c856dSMario Limonciello wblock = container_of(wdev, struct wmi_block, dev); 303b4f9fe12SLen Brown block = &wblock->gblock; 304b0e86302SAndy Lutomirski handle = wblock->acpi_device->handle; 305b4f9fe12SLen Brown 306b4f9fe12SLen Brown if (!(block->flags & ACPI_WMI_METHOD)) 307b4f9fe12SLen Brown return AE_BAD_DATA; 308b4f9fe12SLen Brown 3096afa1e2aSPali Rohár if (block->instance_count <= instance) 310b4f9fe12SLen Brown return AE_BAD_PARAMETER; 311b4f9fe12SLen Brown 312b4f9fe12SLen Brown input.count = 2; 313b4f9fe12SLen Brown input.pointer = params; 314b4f9fe12SLen Brown params[0].type = ACPI_TYPE_INTEGER; 315b4f9fe12SLen Brown params[0].integer.value = instance; 316b4f9fe12SLen Brown params[1].type = ACPI_TYPE_INTEGER; 317b4f9fe12SLen Brown params[1].integer.value = method_id; 318b4f9fe12SLen Brown 319b4f9fe12SLen Brown if (in) { 320b4f9fe12SLen Brown input.count = 3; 321b4f9fe12SLen Brown 32251142a08SBarnabás Pőcze params[2].type = get_param_acpi_type(wblock); 323b4f9fe12SLen Brown params[2].buffer.length = in->length; 324b4f9fe12SLen Brown params[2].buffer.pointer = in->pointer; 325b4f9fe12SLen Brown } 326b4f9fe12SLen Brown 32757f2ce89SBarnabás Pőcze get_acpi_method_name(wblock, 'M', method); 328b4f9fe12SLen Brown 32921397cacSBarnabás Pőcze return acpi_evaluate_object(handle, method, &input, out); 330b4f9fe12SLen Brown } 331722c856dSMario Limonciello EXPORT_SYMBOL_GPL(wmidev_evaluate_method); 332b4f9fe12SLen Brown 33356a37025SAndy Lutomirski static acpi_status __query_block(struct wmi_block *wblock, u8 instance, 334b4f9fe12SLen Brown struct acpi_buffer *out) 335b4f9fe12SLen Brown { 33643aacf83SBarnabás Pőcze struct guid_block *block; 33754f14c27SZhang Rui acpi_handle handle; 338b4f9fe12SLen Brown acpi_status status, wc_status = AE_ERROR; 3398122ab66SZhang Rui struct acpi_object_list input; 3408122ab66SZhang Rui union acpi_object wq_params[1]; 34157f2ce89SBarnabás Pőcze char wc_method[WMI_ACPI_METHOD_NAME_SIZE]; 34257f2ce89SBarnabás Pőcze char method[WMI_ACPI_METHOD_NAME_SIZE]; 343b4f9fe12SLen Brown 34456a37025SAndy Lutomirski if (!out) 345b4f9fe12SLen Brown return AE_BAD_PARAMETER; 346b4f9fe12SLen Brown 347b4f9fe12SLen Brown block = &wblock->gblock; 348b0e86302SAndy Lutomirski handle = wblock->acpi_device->handle; 349b4f9fe12SLen Brown 3506afa1e2aSPali Rohár if (block->instance_count <= instance) 351b4f9fe12SLen Brown return AE_BAD_PARAMETER; 352b4f9fe12SLen Brown 353b4f9fe12SLen Brown /* Check GUID is a data block */ 354b4f9fe12SLen Brown if (block->flags & (ACPI_WMI_EVENT | ACPI_WMI_METHOD)) 355b4f9fe12SLen Brown return AE_ERROR; 356b4f9fe12SLen Brown 357b4f9fe12SLen Brown input.count = 1; 358b4f9fe12SLen Brown input.pointer = wq_params; 359b4f9fe12SLen Brown wq_params[0].type = ACPI_TYPE_INTEGER; 360b4f9fe12SLen Brown wq_params[0].integer.value = instance; 361b4f9fe12SLen Brown 362d4fc91adSAndy Lutomirski if (instance == 0 && wblock->read_takes_no_args) 363d4fc91adSAndy Lutomirski input.count = 0; 364d4fc91adSAndy Lutomirski 365b4f9fe12SLen Brown /* 366b4f9fe12SLen Brown * If ACPI_WMI_EXPENSIVE, call the relevant WCxx method first to 367b4f9fe12SLen Brown * enable collection. 368b4f9fe12SLen Brown */ 369b4f9fe12SLen Brown if (block->flags & ACPI_WMI_EXPENSIVE) { 37057f2ce89SBarnabás Pőcze get_acpi_method_name(wblock, 'C', wc_method); 371b4f9fe12SLen Brown 372b4f9fe12SLen Brown /* 373b4f9fe12SLen Brown * Some GUIDs break the specification by declaring themselves 374b4f9fe12SLen Brown * expensive, but have no corresponding WCxx method. So we 375b4f9fe12SLen Brown * should not fail if this happens. 376b4f9fe12SLen Brown */ 377bad9da86SKelsey Skunberg wc_status = acpi_execute_simple_method(handle, wc_method, 1); 378b4f9fe12SLen Brown } 379b4f9fe12SLen Brown 38057f2ce89SBarnabás Pőcze get_acpi_method_name(wblock, 'Q', method); 381b4f9fe12SLen Brown status = acpi_evaluate_object(handle, method, &input, out); 382b4f9fe12SLen Brown 383b4f9fe12SLen Brown /* 384b4f9fe12SLen Brown * If ACPI_WMI_EXPENSIVE, call the relevant WCxx method, even if 385b4f9fe12SLen Brown * the WQxx method failed - we should disable collection anyway. 386b4f9fe12SLen Brown */ 387b4f9fe12SLen Brown if ((block->flags & ACPI_WMI_EXPENSIVE) && ACPI_SUCCESS(wc_status)) { 3881975718cSBarnabás Pőcze /* 3891975718cSBarnabás Pőcze * Ignore whether this WCxx call succeeds or not since 3901975718cSBarnabás Pőcze * the previously executed WQxx method call might have 3911975718cSBarnabás Pőcze * succeeded, and returning the failing status code 3921975718cSBarnabás Pőcze * of this call would throw away the result of the WQxx 3931975718cSBarnabás Pőcze * call, potentially leaking memory. 3941975718cSBarnabás Pőcze */ 3951975718cSBarnabás Pőcze acpi_execute_simple_method(handle, wc_method, 0); 396b4f9fe12SLen Brown } 397b4f9fe12SLen Brown 398b4f9fe12SLen Brown return status; 399b4f9fe12SLen Brown } 40056a37025SAndy Lutomirski 40156a37025SAndy Lutomirski /** 40256a37025SAndy Lutomirski * wmi_query_block - Return contents of a WMI block (deprecated) 40356a37025SAndy Lutomirski * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba 40456a37025SAndy Lutomirski * @instance: Instance index 4055a707af1SAndy Shevchenko * @out: Empty buffer to return the contents of the data block to 40656a37025SAndy Lutomirski * 40756a37025SAndy Lutomirski * Return the contents of an ACPI-WMI data block to a buffer 40856a37025SAndy Lutomirski */ 40956a37025SAndy Lutomirski acpi_status wmi_query_block(const char *guid_string, u8 instance, 41056a37025SAndy Lutomirski struct acpi_buffer *out) 41156a37025SAndy Lutomirski { 41256a37025SAndy Lutomirski struct wmi_block *wblock; 41356a37025SAndy Lutomirski 41456a37025SAndy Lutomirski if (!guid_string) 41556a37025SAndy Lutomirski return AE_BAD_PARAMETER; 41656a37025SAndy Lutomirski 41756a37025SAndy Lutomirski if (!find_guid(guid_string, &wblock)) 41856a37025SAndy Lutomirski return AE_ERROR; 41956a37025SAndy Lutomirski 42056a37025SAndy Lutomirski return __query_block(wblock, instance, out); 42156a37025SAndy Lutomirski } 422b4f9fe12SLen Brown EXPORT_SYMBOL_GPL(wmi_query_block); 423b4f9fe12SLen Brown 42456a37025SAndy Lutomirski union acpi_object *wmidev_block_query(struct wmi_device *wdev, u8 instance) 42556a37025SAndy Lutomirski { 42656a37025SAndy Lutomirski struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL }; 42756a37025SAndy Lutomirski struct wmi_block *wblock = container_of(wdev, struct wmi_block, dev); 42856a37025SAndy Lutomirski 42956a37025SAndy Lutomirski if (ACPI_FAILURE(__query_block(wblock, instance, &out))) 43056a37025SAndy Lutomirski return NULL; 43156a37025SAndy Lutomirski 432c06a2fdeSBarnabás Pőcze return out.pointer; 43356a37025SAndy Lutomirski } 43456a37025SAndy Lutomirski EXPORT_SYMBOL_GPL(wmidev_block_query); 43556a37025SAndy Lutomirski 436b4f9fe12SLen Brown /** 437b4f9fe12SLen Brown * wmi_set_block - Write to a WMI block 438b4f9fe12SLen Brown * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba 439b4f9fe12SLen Brown * @instance: Instance index 4405a707af1SAndy Shevchenko * @in: Buffer containing new values for the data block 441b4f9fe12SLen Brown * 442b4f9fe12SLen Brown * Write the contents of the input buffer to an ACPI-WMI data block 443b4f9fe12SLen Brown */ 444b4f9fe12SLen Brown acpi_status wmi_set_block(const char *guid_string, u8 instance, 445b4f9fe12SLen Brown const struct acpi_buffer *in) 446b4f9fe12SLen Brown { 447b4f9fe12SLen Brown struct wmi_block *wblock = NULL; 44843aacf83SBarnabás Pőcze struct guid_block *block; 449b4f9fe12SLen Brown acpi_handle handle; 450b4f9fe12SLen Brown struct acpi_object_list input; 451b4f9fe12SLen Brown union acpi_object params[2]; 45257f2ce89SBarnabás Pőcze char method[WMI_ACPI_METHOD_NAME_SIZE]; 453b4f9fe12SLen Brown 454b4f9fe12SLen Brown if (!guid_string || !in) 455b4f9fe12SLen Brown return AE_BAD_DATA; 456b4f9fe12SLen Brown 457b4f9fe12SLen Brown if (!find_guid(guid_string, &wblock)) 458b4f9fe12SLen Brown return AE_ERROR; 459b4f9fe12SLen Brown 460b4f9fe12SLen Brown block = &wblock->gblock; 461b0e86302SAndy Lutomirski handle = wblock->acpi_device->handle; 462b4f9fe12SLen Brown 4636afa1e2aSPali Rohár if (block->instance_count <= instance) 464b4f9fe12SLen Brown return AE_BAD_PARAMETER; 465b4f9fe12SLen Brown 466b4f9fe12SLen Brown /* Check GUID is a data block */ 467b4f9fe12SLen Brown if (block->flags & (ACPI_WMI_EVENT | ACPI_WMI_METHOD)) 468b4f9fe12SLen Brown return AE_ERROR; 469b4f9fe12SLen Brown 470b4f9fe12SLen Brown input.count = 2; 471b4f9fe12SLen Brown input.pointer = params; 472b4f9fe12SLen Brown params[0].type = ACPI_TYPE_INTEGER; 473b4f9fe12SLen Brown params[0].integer.value = instance; 47451142a08SBarnabás Pőcze params[1].type = get_param_acpi_type(wblock); 475b4f9fe12SLen Brown params[1].buffer.length = in->length; 476b4f9fe12SLen Brown params[1].buffer.pointer = in->pointer; 477b4f9fe12SLen Brown 47857f2ce89SBarnabás Pőcze get_acpi_method_name(wblock, 'S', method); 479b4f9fe12SLen Brown 480b4f9fe12SLen Brown return acpi_evaluate_object(handle, method, &input, NULL); 481b4f9fe12SLen Brown } 482b4f9fe12SLen Brown EXPORT_SYMBOL_GPL(wmi_set_block); 483b4f9fe12SLen Brown 48437830662SDmitry Torokhov static void wmi_dump_wdg(const struct guid_block *g) 485a929aae0SThomas Renninger { 48667f472fdSBarnabás Pőcze pr_info("%pUL:\n", &g->guid); 487cd3921f8SPali Rohár if (g->flags & ACPI_WMI_EVENT) 488cd3921f8SPali Rohár pr_info("\tnotify_id: 0x%02X\n", g->notify_id); 489cd3921f8SPali Rohár else 490cd3921f8SPali Rohár pr_info("\tobject_id: %2pE\n", g->object_id); 4918e07514dSDmitry Torokhov pr_info("\tinstance_count: %d\n", g->instance_count); 4928e07514dSDmitry Torokhov pr_info("\tflags: %#x", g->flags); 493a929aae0SThomas Renninger if (g->flags) { 494a929aae0SThomas Renninger if (g->flags & ACPI_WMI_EXPENSIVE) 4958e07514dSDmitry Torokhov pr_cont(" ACPI_WMI_EXPENSIVE"); 496a929aae0SThomas Renninger if (g->flags & ACPI_WMI_METHOD) 4978e07514dSDmitry Torokhov pr_cont(" ACPI_WMI_METHOD"); 498a929aae0SThomas Renninger if (g->flags & ACPI_WMI_STRING) 4998e07514dSDmitry Torokhov pr_cont(" ACPI_WMI_STRING"); 500a929aae0SThomas Renninger if (g->flags & ACPI_WMI_EVENT) 5018e07514dSDmitry Torokhov pr_cont(" ACPI_WMI_EVENT"); 502a929aae0SThomas Renninger } 5038e07514dSDmitry Torokhov pr_cont("\n"); 504a929aae0SThomas Renninger 505a929aae0SThomas Renninger } 506a929aae0SThomas Renninger 507fc3155b2SThomas Renninger static void wmi_notify_debug(u32 value, void *context) 508fc3155b2SThomas Renninger { 509fc3155b2SThomas Renninger struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL }; 510fc3155b2SThomas Renninger union acpi_object *obj; 5111492616aSAxel Lin acpi_status status; 512fc3155b2SThomas Renninger 5131492616aSAxel Lin status = wmi_get_event_data(value, &response); 5141492616aSAxel Lin if (status != AE_OK) { 5158e07514dSDmitry Torokhov pr_info("bad event status 0x%x\n", status); 5161492616aSAxel Lin return; 5171492616aSAxel Lin } 518fc3155b2SThomas Renninger 519c06a2fdeSBarnabás Pőcze obj = response.pointer; 520fc3155b2SThomas Renninger if (!obj) 521fc3155b2SThomas Renninger return; 522fc3155b2SThomas Renninger 5231c23ab91SBarnabás Pőcze pr_info("DEBUG: event 0x%02X ", value); 524fc3155b2SThomas Renninger switch (obj->type) { 525fc3155b2SThomas Renninger case ACPI_TYPE_BUFFER: 5261c23ab91SBarnabás Pőcze pr_cont("BUFFER_TYPE - length %u\n", obj->buffer.length); 527fc3155b2SThomas Renninger break; 528fc3155b2SThomas Renninger case ACPI_TYPE_STRING: 5298e07514dSDmitry Torokhov pr_cont("STRING_TYPE - %s\n", obj->string.pointer); 530fc3155b2SThomas Renninger break; 531fc3155b2SThomas Renninger case ACPI_TYPE_INTEGER: 5328e07514dSDmitry Torokhov pr_cont("INTEGER_TYPE - %llu\n", obj->integer.value); 533fc3155b2SThomas Renninger break; 534fc3155b2SThomas Renninger case ACPI_TYPE_PACKAGE: 5351c23ab91SBarnabás Pőcze pr_cont("PACKAGE_TYPE - %u elements\n", obj->package.count); 536fc3155b2SThomas Renninger break; 537fc3155b2SThomas Renninger default: 5388e07514dSDmitry Torokhov pr_cont("object type 0x%X\n", obj->type); 539fc3155b2SThomas Renninger } 5401492616aSAxel Lin kfree(obj); 541fc3155b2SThomas Renninger } 542fc3155b2SThomas Renninger 543b4f9fe12SLen Brown /** 544b4f9fe12SLen Brown * wmi_install_notify_handler - Register handler for WMI events 5455a707af1SAndy Shevchenko * @guid: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba 546b4f9fe12SLen Brown * @handler: Function to handle notifications 547b4f9fe12SLen Brown * @data: Data to be returned to handler when event is fired 548b4f9fe12SLen Brown * 549b4f9fe12SLen Brown * Register a handler for events sent to the ACPI-WMI mapper device. 550b4f9fe12SLen Brown */ 551b4f9fe12SLen Brown acpi_status wmi_install_notify_handler(const char *guid, 552bba08f35SBarnabás Pőcze wmi_notify_handler handler, 553bba08f35SBarnabás Pőcze void *data) 554b4f9fe12SLen Brown { 555b4f9fe12SLen Brown struct wmi_block *block; 55658f6425eSColin King acpi_status status = AE_NOT_EXIST; 557f9dffc14SAndy Shevchenko guid_t guid_input; 558b4f9fe12SLen Brown 559b4f9fe12SLen Brown if (!guid || !handler) 560b4f9fe12SLen Brown return AE_BAD_PARAMETER; 561b4f9fe12SLen Brown 562f9dffc14SAndy Shevchenko if (guid_parse(guid, &guid_input)) 563538d7eb8SAndy Shevchenko return AE_BAD_PARAMETER; 564b4f9fe12SLen Brown 565cedb3b2aSAndy Shevchenko list_for_each_entry(block, &wmi_block_list, list) { 56658f6425eSColin King acpi_status wmi_status; 56758f6425eSColin King 56867f472fdSBarnabás Pőcze if (guid_equal(&block->gblock.guid, &guid_input)) { 56958f6425eSColin King if (block->handler && 57058f6425eSColin King block->handler != wmi_notify_debug) 571b4f9fe12SLen Brown return AE_ALREADY_ACQUIRED; 572b4f9fe12SLen Brown 573b4f9fe12SLen Brown block->handler = handler; 574b4f9fe12SLen Brown block->handler_data = data; 575b4f9fe12SLen Brown 576285dd01aSBarnabás Pőcze wmi_status = wmi_method_enable(block, true); 57758f6425eSColin King if ((wmi_status != AE_OK) || 57858f6425eSColin King ((wmi_status == AE_OK) && (status == AE_NOT_EXIST))) 57958f6425eSColin King status = wmi_status; 58058f6425eSColin King } 58158f6425eSColin King } 582b4f9fe12SLen Brown 583b4f9fe12SLen Brown return status; 584b4f9fe12SLen Brown } 585b4f9fe12SLen Brown EXPORT_SYMBOL_GPL(wmi_install_notify_handler); 586b4f9fe12SLen Brown 587b4f9fe12SLen Brown /** 58807ce4cfdSBarnabás Pőcze * wmi_remove_notify_handler - Unregister handler for WMI events 5895a707af1SAndy Shevchenko * @guid: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba 590b4f9fe12SLen Brown * 591b4f9fe12SLen Brown * Unregister handler for events sent to the ACPI-WMI mapper device. 592b4f9fe12SLen Brown */ 593b4f9fe12SLen Brown acpi_status wmi_remove_notify_handler(const char *guid) 594b4f9fe12SLen Brown { 595b4f9fe12SLen Brown struct wmi_block *block; 59658f6425eSColin King acpi_status status = AE_NOT_EXIST; 597f9dffc14SAndy Shevchenko guid_t guid_input; 598b4f9fe12SLen Brown 599b4f9fe12SLen Brown if (!guid) 600b4f9fe12SLen Brown return AE_BAD_PARAMETER; 601b4f9fe12SLen Brown 602f9dffc14SAndy Shevchenko if (guid_parse(guid, &guid_input)) 603538d7eb8SAndy Shevchenko return AE_BAD_PARAMETER; 604b4f9fe12SLen Brown 605cedb3b2aSAndy Shevchenko list_for_each_entry(block, &wmi_block_list, list) { 60658f6425eSColin King acpi_status wmi_status; 60758f6425eSColin King 60867f472fdSBarnabás Pőcze if (guid_equal(&block->gblock.guid, &guid_input)) { 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 { 617285dd01aSBarnabás Pőcze wmi_status = wmi_method_enable(block, false); 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 wmi_block *wblock; 643b4f9fe12SLen Brown 644cedb3b2aSAndy Shevchenko list_for_each_entry(wblock, &wmi_block_list, list) { 645f5431bf1SBarnabás Pőcze struct guid_block *gblock = &wblock->gblock; 646b4f9fe12SLen Brown 647*25be44f6SBarnabás Pőcze if ((gblock->flags & ACPI_WMI_EVENT) && gblock->notify_id == event) 648*25be44f6SBarnabás Pőcze return get_event_data(wblock, out); 649b4f9fe12SLen Brown } 650b4f9fe12SLen Brown 651b4f9fe12SLen Brown return AE_NOT_FOUND; 652b4f9fe12SLen Brown } 653b4f9fe12SLen Brown EXPORT_SYMBOL_GPL(wmi_get_event_data); 654b4f9fe12SLen Brown 655b4f9fe12SLen Brown /** 656b4f9fe12SLen Brown * wmi_has_guid - Check if a GUID is available 657b4f9fe12SLen Brown * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba 658b4f9fe12SLen Brown * 659b4f9fe12SLen Brown * Check if a given GUID is defined by _WDG 660b4f9fe12SLen Brown */ 661b4f9fe12SLen Brown bool wmi_has_guid(const char *guid_string) 662b4f9fe12SLen Brown { 663b4f9fe12SLen Brown return find_guid(guid_string, NULL); 664b4f9fe12SLen Brown } 665b4f9fe12SLen Brown EXPORT_SYMBOL_GPL(wmi_has_guid); 666b4f9fe12SLen Brown 667e7488e58SYurii Pavlovskyi /** 668e7488e58SYurii Pavlovskyi * wmi_get_acpi_device_uid() - Get _UID name of ACPI device that defines GUID 669e7488e58SYurii Pavlovskyi * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba 670e7488e58SYurii Pavlovskyi * 671e7488e58SYurii Pavlovskyi * Find the _UID of ACPI device associated with this WMI GUID. 672e7488e58SYurii Pavlovskyi * 673e7488e58SYurii Pavlovskyi * Return: The ACPI _UID field value or NULL if the WMI GUID was not found 674e7488e58SYurii Pavlovskyi */ 675e7488e58SYurii Pavlovskyi char *wmi_get_acpi_device_uid(const char *guid_string) 676e7488e58SYurii Pavlovskyi { 677e7488e58SYurii Pavlovskyi struct wmi_block *wblock = NULL; 678e7488e58SYurii Pavlovskyi 679e7488e58SYurii Pavlovskyi if (!find_guid(guid_string, &wblock)) 680e7488e58SYurii Pavlovskyi return NULL; 681e7488e58SYurii Pavlovskyi 682e7488e58SYurii Pavlovskyi return acpi_device_uid(wblock->acpi_device); 683e7488e58SYurii Pavlovskyi } 684e7488e58SYurii Pavlovskyi EXPORT_SYMBOL_GPL(wmi_get_acpi_device_uid); 685e7488e58SYurii Pavlovskyi 686844af950SAndy Lutomirski static struct wmi_block *dev_to_wblock(struct device *dev) 687844af950SAndy Lutomirski { 688844af950SAndy Lutomirski return container_of(dev, struct wmi_block, dev.dev); 689844af950SAndy Lutomirski } 690844af950SAndy Lutomirski 691844af950SAndy Lutomirski static struct wmi_device *dev_to_wdev(struct device *dev) 692844af950SAndy Lutomirski { 693844af950SAndy Lutomirski return container_of(dev, struct wmi_device, dev); 694844af950SAndy Lutomirski } 695844af950SAndy Lutomirski 696e7b2e334SBarnabás Pőcze static inline struct wmi_driver *drv_to_wdrv(struct device_driver *drv) 697e7b2e334SBarnabás Pőcze { 698e7b2e334SBarnabás Pőcze return container_of(drv, struct wmi_driver, driver); 699e7b2e334SBarnabás Pőcze } 700e7b2e334SBarnabás Pőcze 701b4f9fe12SLen Brown /* 7021caab3c1SMatthew Garrett * sysfs interface 7031caab3c1SMatthew Garrett */ 704614ef432SDmitry Torokhov static ssize_t modalias_show(struct device *dev, struct device_attribute *attr, 7051caab3c1SMatthew Garrett char *buf) 7061caab3c1SMatthew Garrett { 707844af950SAndy Lutomirski struct wmi_block *wblock = dev_to_wblock(dev); 7081caab3c1SMatthew Garrett 7096133913aSBarnabás Pőcze return sysfs_emit(buf, "wmi:%pUL\n", &wblock->gblock.guid); 7101caab3c1SMatthew Garrett } 711e80b89a5SGreg Kroah-Hartman static DEVICE_ATTR_RO(modalias); 712614ef432SDmitry Torokhov 713844af950SAndy Lutomirski static ssize_t guid_show(struct device *dev, struct device_attribute *attr, 714844af950SAndy Lutomirski char *buf) 715844af950SAndy Lutomirski { 716844af950SAndy Lutomirski struct wmi_block *wblock = dev_to_wblock(dev); 717844af950SAndy Lutomirski 7186133913aSBarnabás Pőcze return sysfs_emit(buf, "%pUL\n", &wblock->gblock.guid); 719844af950SAndy Lutomirski } 720844af950SAndy Lutomirski static DEVICE_ATTR_RO(guid); 721844af950SAndy Lutomirski 722d79b1074SAndy Lutomirski static ssize_t instance_count_show(struct device *dev, 723d79b1074SAndy Lutomirski struct device_attribute *attr, char *buf) 724d79b1074SAndy Lutomirski { 725d79b1074SAndy Lutomirski struct wmi_block *wblock = dev_to_wblock(dev); 726d79b1074SAndy Lutomirski 7276133913aSBarnabás Pőcze return sysfs_emit(buf, "%d\n", (int)wblock->gblock.instance_count); 728d79b1074SAndy Lutomirski } 729d79b1074SAndy Lutomirski static DEVICE_ATTR_RO(instance_count); 730d79b1074SAndy Lutomirski 731d79b1074SAndy Lutomirski static ssize_t expensive_show(struct device *dev, 732d79b1074SAndy Lutomirski struct device_attribute *attr, char *buf) 733d79b1074SAndy Lutomirski { 734d79b1074SAndy Lutomirski struct wmi_block *wblock = dev_to_wblock(dev); 735d79b1074SAndy Lutomirski 7366133913aSBarnabás Pőcze return sysfs_emit(buf, "%d\n", 737d79b1074SAndy Lutomirski (wblock->gblock.flags & ACPI_WMI_EXPENSIVE) != 0); 738d79b1074SAndy Lutomirski } 739d79b1074SAndy Lutomirski static DEVICE_ATTR_RO(expensive); 740d79b1074SAndy Lutomirski 741e80b89a5SGreg Kroah-Hartman static struct attribute *wmi_attrs[] = { 742e80b89a5SGreg Kroah-Hartman &dev_attr_modalias.attr, 743844af950SAndy Lutomirski &dev_attr_guid.attr, 744d79b1074SAndy Lutomirski &dev_attr_instance_count.attr, 745d79b1074SAndy Lutomirski &dev_attr_expensive.attr, 746cd3e3d29SBarnabás Pőcze NULL 747614ef432SDmitry Torokhov }; 748e80b89a5SGreg Kroah-Hartman ATTRIBUTE_GROUPS(wmi); 7491caab3c1SMatthew Garrett 750d79b1074SAndy Lutomirski static ssize_t notify_id_show(struct device *dev, struct device_attribute *attr, 751d79b1074SAndy Lutomirski char *buf) 752d79b1074SAndy Lutomirski { 753d79b1074SAndy Lutomirski struct wmi_block *wblock = dev_to_wblock(dev); 754d79b1074SAndy Lutomirski 7556133913aSBarnabás Pőcze return sysfs_emit(buf, "%02X\n", (unsigned int)wblock->gblock.notify_id); 756d79b1074SAndy Lutomirski } 757d79b1074SAndy Lutomirski static DEVICE_ATTR_RO(notify_id); 758d79b1074SAndy Lutomirski 759d79b1074SAndy Lutomirski static struct attribute *wmi_event_attrs[] = { 760d79b1074SAndy Lutomirski &dev_attr_notify_id.attr, 761cd3e3d29SBarnabás Pőcze NULL 762d79b1074SAndy Lutomirski }; 763d79b1074SAndy Lutomirski ATTRIBUTE_GROUPS(wmi_event); 764d79b1074SAndy Lutomirski 765d79b1074SAndy Lutomirski static ssize_t object_id_show(struct device *dev, struct device_attribute *attr, 766d79b1074SAndy Lutomirski char *buf) 767d79b1074SAndy Lutomirski { 768d79b1074SAndy Lutomirski struct wmi_block *wblock = dev_to_wblock(dev); 769d79b1074SAndy Lutomirski 7706133913aSBarnabás Pőcze return sysfs_emit(buf, "%c%c\n", wblock->gblock.object_id[0], 771d79b1074SAndy Lutomirski wblock->gblock.object_id[1]); 772d79b1074SAndy Lutomirski } 773d79b1074SAndy Lutomirski static DEVICE_ATTR_RO(object_id); 774d79b1074SAndy Lutomirski 775fd70da6aSDarren Hart (VMware) static ssize_t setable_show(struct device *dev, struct device_attribute *attr, 776d4fc91adSAndy Lutomirski char *buf) 777d4fc91adSAndy Lutomirski { 778d4fc91adSAndy Lutomirski struct wmi_device *wdev = dev_to_wdev(dev); 779d4fc91adSAndy Lutomirski 7806133913aSBarnabás Pőcze return sysfs_emit(buf, "%d\n", (int)wdev->setable); 781d4fc91adSAndy Lutomirski } 782fd70da6aSDarren Hart (VMware) static DEVICE_ATTR_RO(setable); 783d4fc91adSAndy Lutomirski 784d4fc91adSAndy Lutomirski static struct attribute *wmi_data_attrs[] = { 785d4fc91adSAndy Lutomirski &dev_attr_object_id.attr, 786fd70da6aSDarren Hart (VMware) &dev_attr_setable.attr, 787cd3e3d29SBarnabás Pőcze NULL 788d4fc91adSAndy Lutomirski }; 789d4fc91adSAndy Lutomirski ATTRIBUTE_GROUPS(wmi_data); 790d4fc91adSAndy Lutomirski 791d4fc91adSAndy Lutomirski static struct attribute *wmi_method_attrs[] = { 792d79b1074SAndy Lutomirski &dev_attr_object_id.attr, 793cd3e3d29SBarnabás Pőcze NULL 794d79b1074SAndy Lutomirski }; 795d4fc91adSAndy Lutomirski ATTRIBUTE_GROUPS(wmi_method); 796d79b1074SAndy Lutomirski 7971caab3c1SMatthew Garrett static int wmi_dev_uevent(struct device *dev, struct kobj_uevent_env *env) 7981caab3c1SMatthew Garrett { 799844af950SAndy Lutomirski struct wmi_block *wblock = dev_to_wblock(dev); 8001caab3c1SMatthew Garrett 80167f472fdSBarnabás Pőcze if (add_uevent_var(env, "MODALIAS=wmi:%pUL", &wblock->gblock.guid)) 8021caab3c1SMatthew Garrett return -ENOMEM; 8031caab3c1SMatthew Garrett 80467f472fdSBarnabás Pőcze if (add_uevent_var(env, "WMI_GUID=%pUL", &wblock->gblock.guid)) 8051caab3c1SMatthew Garrett return -ENOMEM; 8061caab3c1SMatthew Garrett 8071caab3c1SMatthew Garrett return 0; 8081caab3c1SMatthew Garrett } 8091caab3c1SMatthew Garrett 810844af950SAndy Lutomirski static void wmi_dev_release(struct device *dev) 8111caab3c1SMatthew Garrett { 812844af950SAndy Lutomirski struct wmi_block *wblock = dev_to_wblock(dev); 813c64eefd4SDmitry Torokhov 814844af950SAndy Lutomirski kfree(wblock); 8151caab3c1SMatthew Garrett } 8161caab3c1SMatthew Garrett 817844af950SAndy Lutomirski static int wmi_dev_match(struct device *dev, struct device_driver *driver) 818844af950SAndy Lutomirski { 819e7b2e334SBarnabás Pőcze struct wmi_driver *wmi_driver = drv_to_wdrv(driver); 820844af950SAndy Lutomirski struct wmi_block *wblock = dev_to_wblock(dev); 821844af950SAndy Lutomirski const struct wmi_device_id *id = wmi_driver->id_table; 822844af950SAndy Lutomirski 823c355ec65SMattias Jacobsson if (id == NULL) 824c355ec65SMattias Jacobsson return 0; 825c355ec65SMattias Jacobsson 826eacc95eaSMattias Jacobsson while (*id->guid_string) { 827f9dffc14SAndy Shevchenko guid_t driver_guid; 828844af950SAndy Lutomirski 829f9dffc14SAndy Shevchenko if (WARN_ON(guid_parse(id->guid_string, &driver_guid))) 830844af950SAndy Lutomirski continue; 83167f472fdSBarnabás Pőcze if (guid_equal(&driver_guid, &wblock->gblock.guid)) 832844af950SAndy Lutomirski return 1; 833844af950SAndy Lutomirski 834844af950SAndy Lutomirski id++; 835844af950SAndy Lutomirski } 836844af950SAndy Lutomirski 837844af950SAndy Lutomirski return 0; 838844af950SAndy Lutomirski } 83944b6b766SMario Limonciello static int wmi_char_open(struct inode *inode, struct file *filp) 84044b6b766SMario Limonciello { 84144b6b766SMario Limonciello const char *driver_name = filp->f_path.dentry->d_iname; 84243aacf83SBarnabás Pőcze struct wmi_block *wblock; 84343aacf83SBarnabás Pőcze struct wmi_block *next; 84444b6b766SMario Limonciello 84544b6b766SMario Limonciello list_for_each_entry_safe(wblock, next, &wmi_block_list, list) { 84644b6b766SMario Limonciello if (!wblock->dev.dev.driver) 84744b6b766SMario Limonciello continue; 84844b6b766SMario Limonciello if (strcmp(driver_name, wblock->dev.dev.driver->name) == 0) { 84944b6b766SMario Limonciello filp->private_data = wblock; 85044b6b766SMario Limonciello break; 85144b6b766SMario Limonciello } 85244b6b766SMario Limonciello } 85344b6b766SMario Limonciello 85444b6b766SMario Limonciello if (!filp->private_data) 85544b6b766SMario Limonciello return -ENODEV; 85644b6b766SMario Limonciello 85744b6b766SMario Limonciello return nonseekable_open(inode, filp); 85844b6b766SMario Limonciello } 85944b6b766SMario Limonciello 86044b6b766SMario Limonciello static ssize_t wmi_char_read(struct file *filp, char __user *buffer, 86144b6b766SMario Limonciello size_t length, loff_t *offset) 86244b6b766SMario Limonciello { 86344b6b766SMario Limonciello struct wmi_block *wblock = filp->private_data; 86444b6b766SMario Limonciello 86544b6b766SMario Limonciello return simple_read_from_buffer(buffer, length, offset, 86644b6b766SMario Limonciello &wblock->req_buf_size, 86744b6b766SMario Limonciello sizeof(wblock->req_buf_size)); 86844b6b766SMario Limonciello } 86944b6b766SMario Limonciello 87044b6b766SMario Limonciello static long wmi_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) 87144b6b766SMario Limonciello { 87244b6b766SMario Limonciello struct wmi_ioctl_buffer __user *input = 87344b6b766SMario Limonciello (struct wmi_ioctl_buffer __user *) arg; 87444b6b766SMario Limonciello struct wmi_block *wblock = filp->private_data; 87543aacf83SBarnabás Pőcze struct wmi_ioctl_buffer *buf; 87643aacf83SBarnabás Pőcze struct wmi_driver *wdriver; 87744b6b766SMario Limonciello int ret; 87844b6b766SMario Limonciello 87944b6b766SMario Limonciello if (_IOC_TYPE(cmd) != WMI_IOC) 88044b6b766SMario Limonciello return -ENOTTY; 88144b6b766SMario Limonciello 88244b6b766SMario Limonciello /* make sure we're not calling a higher instance than exists*/ 88344b6b766SMario Limonciello if (_IOC_NR(cmd) >= wblock->gblock.instance_count) 88444b6b766SMario Limonciello return -EINVAL; 88544b6b766SMario Limonciello 88644b6b766SMario Limonciello mutex_lock(&wblock->char_mutex); 88744b6b766SMario Limonciello buf = wblock->handler_data; 88844b6b766SMario Limonciello if (get_user(buf->length, &input->length)) { 88944b6b766SMario Limonciello dev_dbg(&wblock->dev.dev, "Read length from user failed\n"); 89044b6b766SMario Limonciello ret = -EFAULT; 89144b6b766SMario Limonciello goto out_ioctl; 89244b6b766SMario Limonciello } 89344b6b766SMario Limonciello /* if it's too small, abort */ 89444b6b766SMario Limonciello if (buf->length < wblock->req_buf_size) { 89544b6b766SMario Limonciello dev_err(&wblock->dev.dev, 89644b6b766SMario Limonciello "Buffer %lld too small, need at least %lld\n", 89744b6b766SMario Limonciello buf->length, wblock->req_buf_size); 89844b6b766SMario Limonciello ret = -EINVAL; 89944b6b766SMario Limonciello goto out_ioctl; 90044b6b766SMario Limonciello } 90144b6b766SMario Limonciello /* if it's too big, warn, driver will only use what is needed */ 90244b6b766SMario Limonciello if (buf->length > wblock->req_buf_size) 90344b6b766SMario Limonciello dev_warn(&wblock->dev.dev, 90444b6b766SMario Limonciello "Buffer %lld is bigger than required %lld\n", 90544b6b766SMario Limonciello buf->length, wblock->req_buf_size); 90644b6b766SMario Limonciello 90744b6b766SMario Limonciello /* copy the structure from userspace */ 90844b6b766SMario Limonciello if (copy_from_user(buf, input, wblock->req_buf_size)) { 90944b6b766SMario Limonciello dev_dbg(&wblock->dev.dev, "Copy %llu from user failed\n", 91044b6b766SMario Limonciello wblock->req_buf_size); 91144b6b766SMario Limonciello ret = -EFAULT; 91244b6b766SMario Limonciello goto out_ioctl; 91344b6b766SMario Limonciello } 91444b6b766SMario Limonciello 91544b6b766SMario Limonciello /* let the driver do any filtering and do the call */ 916e7b2e334SBarnabás Pőcze wdriver = drv_to_wdrv(wblock->dev.dev.driver); 9175e3e2297SMario Limonciello if (!try_module_get(wdriver->driver.owner)) { 9185e3e2297SMario Limonciello ret = -EBUSY; 9195e3e2297SMario Limonciello goto out_ioctl; 9205e3e2297SMario Limonciello } 92144b6b766SMario Limonciello ret = wdriver->filter_callback(&wblock->dev, cmd, buf); 92244b6b766SMario Limonciello module_put(wdriver->driver.owner); 92344b6b766SMario Limonciello if (ret) 92444b6b766SMario Limonciello goto out_ioctl; 92544b6b766SMario Limonciello 92644b6b766SMario Limonciello /* return the result (only up to our internal buffer size) */ 92744b6b766SMario Limonciello if (copy_to_user(input, buf, wblock->req_buf_size)) { 92844b6b766SMario Limonciello dev_dbg(&wblock->dev.dev, "Copy %llu to user failed\n", 92944b6b766SMario Limonciello wblock->req_buf_size); 93044b6b766SMario Limonciello ret = -EFAULT; 93144b6b766SMario Limonciello } 93244b6b766SMario Limonciello 93344b6b766SMario Limonciello out_ioctl: 93444b6b766SMario Limonciello mutex_unlock(&wblock->char_mutex); 93544b6b766SMario Limonciello return ret; 93644b6b766SMario Limonciello } 93744b6b766SMario Limonciello 93844b6b766SMario Limonciello static const struct file_operations wmi_fops = { 93944b6b766SMario Limonciello .owner = THIS_MODULE, 94044b6b766SMario Limonciello .read = wmi_char_read, 94144b6b766SMario Limonciello .open = wmi_char_open, 94244b6b766SMario Limonciello .unlocked_ioctl = wmi_ioctl, 9431832f2d8SArnd Bergmann .compat_ioctl = compat_ptr_ioctl, 94444b6b766SMario Limonciello }; 945844af950SAndy Lutomirski 946844af950SAndy Lutomirski static int wmi_dev_probe(struct device *dev) 947844af950SAndy Lutomirski { 948844af950SAndy Lutomirski struct wmi_block *wblock = dev_to_wblock(dev); 949e7b2e334SBarnabás Pőcze struct wmi_driver *wdriver = drv_to_wdrv(dev->driver); 950844af950SAndy Lutomirski int ret = 0; 95144b6b766SMario Limonciello char *buf; 952844af950SAndy Lutomirski 953285dd01aSBarnabás Pőcze if (ACPI_FAILURE(wmi_method_enable(wblock, true))) 954844af950SAndy Lutomirski dev_warn(dev, "failed to enable device -- probing anyway\n"); 955844af950SAndy Lutomirski 956844af950SAndy Lutomirski if (wdriver->probe) { 957440c4983SMattias Jacobsson ret = wdriver->probe(dev_to_wdev(dev), 958440c4983SMattias Jacobsson find_guid_context(wblock, wdriver)); 95944b6b766SMario Limonciello if (ret != 0) 96044b6b766SMario Limonciello goto probe_failure; 961844af950SAndy Lutomirski } 962844af950SAndy Lutomirski 96344b6b766SMario Limonciello /* driver wants a character device made */ 96444b6b766SMario Limonciello if (wdriver->filter_callback) { 96544b6b766SMario Limonciello /* check that required buffer size declared by driver or MOF */ 96644b6b766SMario Limonciello if (!wblock->req_buf_size) { 96744b6b766SMario Limonciello dev_err(&wblock->dev.dev, 96844b6b766SMario Limonciello "Required buffer size not set\n"); 96944b6b766SMario Limonciello ret = -EINVAL; 97044b6b766SMario Limonciello goto probe_failure; 97144b6b766SMario Limonciello } 97244b6b766SMario Limonciello 9736fb74107SKees Cook wblock->handler_data = kmalloc(wblock->req_buf_size, 9746fb74107SKees Cook GFP_KERNEL); 97544b6b766SMario Limonciello if (!wblock->handler_data) { 97644b6b766SMario Limonciello ret = -ENOMEM; 97744b6b766SMario Limonciello goto probe_failure; 97844b6b766SMario Limonciello } 97944b6b766SMario Limonciello 9807f166addSAndy Shevchenko buf = kasprintf(GFP_KERNEL, "wmi/%s", wdriver->driver.name); 98144b6b766SMario Limonciello if (!buf) { 98244b6b766SMario Limonciello ret = -ENOMEM; 98344b6b766SMario Limonciello goto probe_string_failure; 98444b6b766SMario Limonciello } 98544b6b766SMario Limonciello wblock->char_dev.minor = MISC_DYNAMIC_MINOR; 98644b6b766SMario Limonciello wblock->char_dev.name = buf; 98744b6b766SMario Limonciello wblock->char_dev.fops = &wmi_fops; 98844b6b766SMario Limonciello wblock->char_dev.mode = 0444; 98944b6b766SMario Limonciello ret = misc_register(&wblock->char_dev); 99044b6b766SMario Limonciello if (ret) { 991501f7e52SJoe Perches dev_warn(dev, "failed to register char dev: %d\n", ret); 99244b6b766SMario Limonciello ret = -ENOMEM; 99344b6b766SMario Limonciello goto probe_misc_failure; 99444b6b766SMario Limonciello } 99544b6b766SMario Limonciello } 99644b6b766SMario Limonciello 99744b6b766SMario Limonciello return 0; 99844b6b766SMario Limonciello 99944b6b766SMario Limonciello probe_misc_failure: 100044b6b766SMario Limonciello kfree(buf); 100144b6b766SMario Limonciello probe_string_failure: 100244b6b766SMario Limonciello kfree(wblock->handler_data); 100344b6b766SMario Limonciello probe_failure: 1004285dd01aSBarnabás Pőcze if (ACPI_FAILURE(wmi_method_enable(wblock, false))) 100544b6b766SMario Limonciello dev_warn(dev, "failed to disable device\n"); 1006844af950SAndy Lutomirski return ret; 1007844af950SAndy Lutomirski } 1008844af950SAndy Lutomirski 1009fc7a6209SUwe Kleine-König static void wmi_dev_remove(struct device *dev) 1010844af950SAndy Lutomirski { 1011844af950SAndy Lutomirski struct wmi_block *wblock = dev_to_wblock(dev); 1012e7b2e334SBarnabás Pőcze struct wmi_driver *wdriver = drv_to_wdrv(dev->driver); 1013844af950SAndy Lutomirski 101444b6b766SMario Limonciello if (wdriver->filter_callback) { 101544b6b766SMario Limonciello misc_deregister(&wblock->char_dev); 101644b6b766SMario Limonciello kfree(wblock->char_dev.name); 10176fb74107SKees Cook kfree(wblock->handler_data); 101844b6b766SMario Limonciello } 101944b6b766SMario Limonciello 1020844af950SAndy Lutomirski if (wdriver->remove) 10212b329f56SUwe Kleine-König wdriver->remove(dev_to_wdev(dev)); 1022844af950SAndy Lutomirski 1023285dd01aSBarnabás Pőcze if (ACPI_FAILURE(wmi_method_enable(wblock, false))) 1024844af950SAndy Lutomirski dev_warn(dev, "failed to disable device\n"); 1025844af950SAndy Lutomirski } 1026844af950SAndy Lutomirski 1027844af950SAndy Lutomirski static struct class wmi_bus_class = { 1028844af950SAndy Lutomirski .name = "wmi_bus", 10291caab3c1SMatthew Garrett }; 10301caab3c1SMatthew Garrett 1031844af950SAndy Lutomirski static struct bus_type wmi_bus_type = { 1032844af950SAndy Lutomirski .name = "wmi", 1033844af950SAndy Lutomirski .dev_groups = wmi_groups, 1034844af950SAndy Lutomirski .match = wmi_dev_match, 1035844af950SAndy Lutomirski .uevent = wmi_dev_uevent, 1036844af950SAndy Lutomirski .probe = wmi_dev_probe, 1037844af950SAndy Lutomirski .remove = wmi_dev_remove, 1038844af950SAndy Lutomirski }; 1039844af950SAndy Lutomirski 104069372c1dSBhumika Goyal static const struct device_type wmi_type_event = { 1041d79b1074SAndy Lutomirski .name = "event", 1042d79b1074SAndy Lutomirski .groups = wmi_event_groups, 1043d79b1074SAndy Lutomirski .release = wmi_dev_release, 1044d79b1074SAndy Lutomirski }; 1045d79b1074SAndy Lutomirski 104669372c1dSBhumika Goyal static const struct device_type wmi_type_method = { 1047d79b1074SAndy Lutomirski .name = "method", 1048d4fc91adSAndy Lutomirski .groups = wmi_method_groups, 1049d79b1074SAndy Lutomirski .release = wmi_dev_release, 1050d79b1074SAndy Lutomirski }; 1051d79b1074SAndy Lutomirski 105269372c1dSBhumika Goyal static const struct device_type wmi_type_data = { 1053d79b1074SAndy Lutomirski .name = "data", 1054d4fc91adSAndy Lutomirski .groups = wmi_data_groups, 1055d79b1074SAndy Lutomirski .release = wmi_dev_release, 1056d79b1074SAndy Lutomirski }; 1057d79b1074SAndy Lutomirski 1058fd70da6aSDarren Hart (VMware) static int wmi_create_device(struct device *wmi_bus_dev, 10597f5809bfSAndy Lutomirski struct wmi_block *wblock, 10607f5809bfSAndy Lutomirski struct acpi_device *device) 10611caab3c1SMatthew Garrett { 1062d4fc91adSAndy Lutomirski struct acpi_device_info *info; 106357f2ce89SBarnabás Pőcze char method[WMI_ACPI_METHOD_NAME_SIZE]; 1064d4fc91adSAndy Lutomirski int result; 1065d4fc91adSAndy Lutomirski 106684eacf7eSBarnabás Pőcze if (wblock->gblock.flags & ACPI_WMI_EVENT) { 1067fd70da6aSDarren Hart (VMware) wblock->dev.dev.type = &wmi_type_event; 1068fd70da6aSDarren Hart (VMware) goto out_init; 1069fd70da6aSDarren Hart (VMware) } 1070d4fc91adSAndy Lutomirski 107184eacf7eSBarnabás Pőcze if (wblock->gblock.flags & ACPI_WMI_METHOD) { 1072fd70da6aSDarren Hart (VMware) wblock->dev.dev.type = &wmi_type_method; 107344b6b766SMario Limonciello mutex_init(&wblock->char_mutex); 1074fd70da6aSDarren Hart (VMware) goto out_init; 1075fd70da6aSDarren Hart (VMware) } 1076fd70da6aSDarren Hart (VMware) 1077fd70da6aSDarren Hart (VMware) /* 1078fd70da6aSDarren Hart (VMware) * Data Block Query Control Method (WQxx by convention) is 1079fd70da6aSDarren Hart (VMware) * required per the WMI documentation. If it is not present, 1080fd70da6aSDarren Hart (VMware) * we ignore this data block. 1081fd70da6aSDarren Hart (VMware) */ 108257f2ce89SBarnabás Pőcze get_acpi_method_name(wblock, 'Q', method); 1083d4fc91adSAndy Lutomirski result = get_subobj_info(device->handle, method, &info); 1084d4fc91adSAndy Lutomirski 1085fd70da6aSDarren Hart (VMware) if (result) { 1086fd70da6aSDarren Hart (VMware) dev_warn(wmi_bus_dev, 1087501f7e52SJoe Perches "%s data block query control method not found\n", 1088fd70da6aSDarren Hart (VMware) method); 1089fd70da6aSDarren Hart (VMware) return result; 1090fd70da6aSDarren Hart (VMware) } 1091fd70da6aSDarren Hart (VMware) 1092fd70da6aSDarren Hart (VMware) wblock->dev.dev.type = &wmi_type_data; 1093d4fc91adSAndy Lutomirski 1094d4fc91adSAndy Lutomirski /* 1095d4fc91adSAndy Lutomirski * The Microsoft documentation specifically states: 1096d4fc91adSAndy Lutomirski * 1097d4fc91adSAndy Lutomirski * Data blocks registered with only a single instance 1098d4fc91adSAndy Lutomirski * can ignore the parameter. 1099d4fc91adSAndy Lutomirski * 1100fd70da6aSDarren Hart (VMware) * ACPICA will get mad at us if we call the method with the wrong number 1101fd70da6aSDarren Hart (VMware) * of arguments, so check what our method expects. (On some Dell 1102fd70da6aSDarren Hart (VMware) * laptops, WQxx may not be a method at all.) 1103d4fc91adSAndy Lutomirski */ 1104fd70da6aSDarren Hart (VMware) if (info->type != ACPI_TYPE_METHOD || info->param_count == 0) 1105d4fc91adSAndy Lutomirski wblock->read_takes_no_args = true; 1106d4fc91adSAndy Lutomirski 1107d4fc91adSAndy Lutomirski kfree(info); 1108d4fc91adSAndy Lutomirski 110957f2ce89SBarnabás Pőcze get_acpi_method_name(wblock, 'S', method); 1110d4fc91adSAndy Lutomirski result = get_subobj_info(device->handle, method, NULL); 1111d4fc91adSAndy Lutomirski 1112fd70da6aSDarren Hart (VMware) if (result == 0) 1113fd70da6aSDarren Hart (VMware) wblock->dev.setable = true; 1114d4fc91adSAndy Lutomirski 1115fd70da6aSDarren Hart (VMware) out_init: 1116fd70da6aSDarren Hart (VMware) wblock->dev.dev.bus = &wmi_bus_type; 1117fd70da6aSDarren Hart (VMware) wblock->dev.dev.parent = wmi_bus_dev; 1118fd70da6aSDarren Hart (VMware) 111967f472fdSBarnabás Pőcze dev_set_name(&wblock->dev.dev, "%pUL", &wblock->gblock.guid); 1120c64eefd4SDmitry Torokhov 11216ee50aaaSDarren Hart (VMware) device_initialize(&wblock->dev.dev); 1122fd70da6aSDarren Hart (VMware) 1123fd70da6aSDarren Hart (VMware) return 0; 11241caab3c1SMatthew Garrett } 11251caab3c1SMatthew Garrett 1126b0e86302SAndy Lutomirski static void wmi_free_devices(struct acpi_device *device) 11271caab3c1SMatthew Garrett { 1128c64eefd4SDmitry Torokhov struct wmi_block *wblock, *next; 11291caab3c1SMatthew Garrett 11301caab3c1SMatthew Garrett /* Delete devices for all the GUIDs */ 1131023b9565SDmitry Torokhov list_for_each_entry_safe(wblock, next, &wmi_block_list, list) { 1132b0e86302SAndy Lutomirski if (wblock->acpi_device == device) { 1133023b9565SDmitry Torokhov list_del(&wblock->list); 1134844af950SAndy Lutomirski device_unregister(&wblock->dev.dev); 1135023b9565SDmitry Torokhov } 11361caab3c1SMatthew Garrett } 1137b0e86302SAndy Lutomirski } 11381caab3c1SMatthew Garrett 113967f472fdSBarnabás Pőcze static bool guid_already_parsed(struct acpi_device *device, const guid_t *guid) 1140d1f9e497SCarlos Corbacho { 1141d1f9e497SCarlos Corbacho struct wmi_block *wblock; 1142d1f9e497SCarlos Corbacho 1143b0e86302SAndy Lutomirski list_for_each_entry(wblock, &wmi_block_list, list) { 114467f472fdSBarnabás Pőcze if (guid_equal(&wblock->gblock.guid, guid)) { 1145b0e86302SAndy Lutomirski /* 1146b0e86302SAndy Lutomirski * Because we historically didn't track the relationship 1147b0e86302SAndy Lutomirski * between GUIDs and ACPI nodes, we don't know whether 1148b0e86302SAndy Lutomirski * we need to suppress GUIDs that are unique on a 1149b0e86302SAndy Lutomirski * given node but duplicated across nodes. 1150b0e86302SAndy Lutomirski */ 1151b0e86302SAndy Lutomirski dev_warn(&device->dev, "duplicate WMI GUID %pUL (first instance was on %s)\n", 1152b0e86302SAndy Lutomirski guid, dev_name(&wblock->acpi_device->dev)); 1153d1f9e497SCarlos Corbacho return true; 1154b0e86302SAndy Lutomirski } 1155b0e86302SAndy Lutomirski } 1156c64eefd4SDmitry Torokhov 1157d1f9e497SCarlos Corbacho return false; 1158d1f9e497SCarlos Corbacho } 1159d1f9e497SCarlos Corbacho 11601caab3c1SMatthew Garrett /* 1161b4f9fe12SLen Brown * Parse the _WDG method for the GUID data blocks 1162b4f9fe12SLen Brown */ 1163844af950SAndy Lutomirski static int parse_wdg(struct device *wmi_bus_dev, struct acpi_device *device) 1164b4f9fe12SLen Brown { 1165b4f9fe12SLen Brown struct acpi_buffer out = {ACPI_ALLOCATE_BUFFER, NULL}; 116637830662SDmitry Torokhov const struct guid_block *gblock; 11676ee50aaaSDarren Hart (VMware) struct wmi_block *wblock, *next; 11686ee50aaaSDarren Hart (VMware) union acpi_object *obj; 1169b4f9fe12SLen Brown acpi_status status; 11706ee50aaaSDarren Hart (VMware) int retval = 0; 1171b4f9fe12SLen Brown u32 i, total; 1172b4f9fe12SLen Brown 11737f5809bfSAndy Lutomirski status = acpi_evaluate_object(device->handle, "_WDG", NULL, &out); 1174b4f9fe12SLen Brown if (ACPI_FAILURE(status)) 1175c64eefd4SDmitry Torokhov return -ENXIO; 1176b4f9fe12SLen Brown 1177c06a2fdeSBarnabás Pőcze obj = out.pointer; 11783d2c63ebSDmitry Torokhov if (!obj) 1179c64eefd4SDmitry Torokhov return -ENXIO; 1180b4f9fe12SLen Brown 118164ed0ab8SDmitry Torokhov if (obj->type != ACPI_TYPE_BUFFER) { 1182c64eefd4SDmitry Torokhov retval = -ENXIO; 118364ed0ab8SDmitry Torokhov goto out_free_pointer; 118464ed0ab8SDmitry Torokhov } 1185b4f9fe12SLen Brown 118637830662SDmitry Torokhov gblock = (const struct guid_block *)obj->buffer.pointer; 1187b4f9fe12SLen Brown total = obj->buffer.length / sizeof(struct guid_block); 1188b4f9fe12SLen Brown 1189b4f9fe12SLen Brown for (i = 0; i < total; i++) { 1190a929aae0SThomas Renninger if (debug_dump_wdg) 1191a929aae0SThomas Renninger wmi_dump_wdg(&gblock[i]); 1192a929aae0SThomas Renninger 1193a1c31bcdSAndy Lutomirski /* 1194a1c31bcdSAndy Lutomirski * Some WMI devices, like those for nVidia hooks, have a 1195a1c31bcdSAndy Lutomirski * duplicate GUID. It's not clear what we should do in this 1196a1c31bcdSAndy Lutomirski * case yet, so for now, we'll just ignore the duplicate 1197a1c31bcdSAndy Lutomirski * for device creation. 1198a1c31bcdSAndy Lutomirski */ 119967f472fdSBarnabás Pőcze if (guid_already_parsed(device, &gblock[i].guid)) 1200a1c31bcdSAndy Lutomirski continue; 1201a1c31bcdSAndy Lutomirski 12027410b8e6SBarnabás Pőcze wblock = kzalloc(sizeof(*wblock), GFP_KERNEL); 12036ee50aaaSDarren Hart (VMware) if (!wblock) { 12046ee50aaaSDarren Hart (VMware) retval = -ENOMEM; 12056ee50aaaSDarren Hart (VMware) break; 12066ee50aaaSDarren Hart (VMware) } 120758f6425eSColin King 1208b0e86302SAndy Lutomirski wblock->acpi_device = device; 120958f6425eSColin King wblock->gblock = gblock[i]; 121058f6425eSColin King 121184eacf7eSBarnabás Pőcze retval = wmi_create_device(wmi_bus_dev, wblock, device); 1212fd70da6aSDarren Hart (VMware) if (retval) { 1213fd70da6aSDarren Hart (VMware) kfree(wblock); 1214fd70da6aSDarren Hart (VMware) continue; 1215fd70da6aSDarren Hart (VMware) } 121658f6425eSColin King 121758f6425eSColin King list_add_tail(&wblock->list, &wmi_block_list); 1218b4f9fe12SLen Brown 1219fc3155b2SThomas Renninger if (debug_event) { 1220fc3155b2SThomas Renninger wblock->handler = wmi_notify_debug; 1221285dd01aSBarnabás Pőcze wmi_method_enable(wblock, true); 1222fc3155b2SThomas Renninger } 1223b4f9fe12SLen Brown } 1224b4f9fe12SLen Brown 12256ee50aaaSDarren Hart (VMware) /* 12266ee50aaaSDarren Hart (VMware) * Now that all of the devices are created, add them to the 12276ee50aaaSDarren Hart (VMware) * device tree and probe subdrivers. 12286ee50aaaSDarren Hart (VMware) */ 12296ee50aaaSDarren Hart (VMware) list_for_each_entry_safe(wblock, next, &wmi_block_list, list) { 12306ee50aaaSDarren Hart (VMware) if (wblock->acpi_device != device) 12316ee50aaaSDarren Hart (VMware) continue; 12326ee50aaaSDarren Hart (VMware) 12336ee50aaaSDarren Hart (VMware) retval = device_add(&wblock->dev.dev); 12346ee50aaaSDarren Hart (VMware) if (retval) { 1235501f7e52SJoe Perches dev_err(wmi_bus_dev, "failed to register %pUL\n", 123667f472fdSBarnabás Pőcze &wblock->gblock.guid); 12376ee50aaaSDarren Hart (VMware) if (debug_event) 1238285dd01aSBarnabás Pőcze wmi_method_enable(wblock, false); 12396ee50aaaSDarren Hart (VMware) list_del(&wblock->list); 12406ee50aaaSDarren Hart (VMware) put_device(&wblock->dev.dev); 12416ee50aaaSDarren Hart (VMware) } 12426ee50aaaSDarren Hart (VMware) } 1243c64eefd4SDmitry Torokhov 1244a5167c5bSAxel Lin out_free_pointer: 1245a5167c5bSAxel Lin kfree(out.pointer); 1246c64eefd4SDmitry Torokhov return retval; 1247b4f9fe12SLen Brown } 1248b4f9fe12SLen Brown 1249b4f9fe12SLen Brown /* 1250b4f9fe12SLen Brown * WMI can have EmbeddedControl access regions. In which case, we just want to 1251b4f9fe12SLen Brown * hand these off to the EC driver. 1252b4f9fe12SLen Brown */ 1253b4f9fe12SLen Brown static acpi_status 1254b4f9fe12SLen Brown acpi_wmi_ec_space_handler(u32 function, acpi_physical_address address, 1255439913ffSLin Ming u32 bits, u64 *value, 1256b4f9fe12SLen Brown void *handler_context, void *region_context) 1257b4f9fe12SLen Brown { 1258b4f9fe12SLen Brown int result = 0, i = 0; 1259b4f9fe12SLen Brown u8 temp = 0; 1260b4f9fe12SLen Brown 1261b4f9fe12SLen Brown if ((address > 0xFF) || !value) 1262b4f9fe12SLen Brown return AE_BAD_PARAMETER; 1263b4f9fe12SLen Brown 1264b4f9fe12SLen Brown if (function != ACPI_READ && function != ACPI_WRITE) 1265b4f9fe12SLen Brown return AE_BAD_PARAMETER; 1266b4f9fe12SLen Brown 1267b4f9fe12SLen Brown if (bits != 8) 1268b4f9fe12SLen Brown return AE_BAD_PARAMETER; 1269b4f9fe12SLen Brown 1270b4f9fe12SLen Brown if (function == ACPI_READ) { 1271b4f9fe12SLen Brown result = ec_read(address, &temp); 1272439913ffSLin Ming (*value) |= ((u64)temp) << i; 1273b4f9fe12SLen Brown } else { 1274b4f9fe12SLen Brown temp = 0xff & ((*value) >> i); 1275b4f9fe12SLen Brown result = ec_write(address, temp); 1276b4f9fe12SLen Brown } 1277b4f9fe12SLen Brown 1278b4f9fe12SLen Brown switch (result) { 1279b4f9fe12SLen Brown case -EINVAL: 1280b4f9fe12SLen Brown return AE_BAD_PARAMETER; 1281b4f9fe12SLen Brown case -ENODEV: 1282b4f9fe12SLen Brown return AE_NOT_FOUND; 1283b4f9fe12SLen Brown case -ETIME: 1284b4f9fe12SLen Brown return AE_TIME; 1285b4f9fe12SLen Brown default: 1286b4f9fe12SLen Brown return AE_OK; 1287b4f9fe12SLen Brown } 1288b4f9fe12SLen Brown } 1289b4f9fe12SLen Brown 12901686f544SAndy Lutomirski static void acpi_wmi_notify_handler(acpi_handle handle, u32 event, 12911686f544SAndy Lutomirski void *context) 1292b4f9fe12SLen Brown { 1293b4f9fe12SLen Brown struct wmi_block *wblock; 12941686f544SAndy Lutomirski bool found_it = false; 1295b4f9fe12SLen Brown 1296cedb3b2aSAndy Shevchenko list_for_each_entry(wblock, &wmi_block_list, list) { 1297f5431bf1SBarnabás Pőcze struct guid_block *block = &wblock->gblock; 1298b4f9fe12SLen Brown 12991686f544SAndy Lutomirski if (wblock->acpi_device->handle == handle && 1300b0e86302SAndy Lutomirski (block->flags & ACPI_WMI_EVENT) && 13013ecace31SBarnabás Pőcze (block->notify_id == event)) { 13021686f544SAndy Lutomirski found_it = true; 13031686f544SAndy Lutomirski break; 13041686f544SAndy Lutomirski } 13051686f544SAndy Lutomirski } 13061686f544SAndy Lutomirski 13071686f544SAndy Lutomirski if (!found_it) 13081686f544SAndy Lutomirski return; 13091686f544SAndy Lutomirski 13101686f544SAndy Lutomirski /* If a driver is bound, then notify the driver. */ 13111686f544SAndy Lutomirski if (wblock->dev.dev.driver) { 1312e7b2e334SBarnabás Pőcze struct wmi_driver *driver = drv_to_wdrv(wblock->dev.dev.driver); 13131686f544SAndy Lutomirski struct acpi_buffer evdata = { ACPI_ALLOCATE_BUFFER, NULL }; 13141686f544SAndy Lutomirski acpi_status status; 13151686f544SAndy Lutomirski 1316*25be44f6SBarnabás Pőcze status = get_event_data(wblock, &evdata); 13171686f544SAndy Lutomirski if (ACPI_FAILURE(status)) { 1318*25be44f6SBarnabás Pőcze dev_warn(&wblock->dev.dev, "failed to get event data\n"); 13191686f544SAndy Lutomirski return; 13201686f544SAndy Lutomirski } 13211686f544SAndy Lutomirski 13221686f544SAndy Lutomirski if (driver->notify) 1323c06a2fdeSBarnabás Pőcze driver->notify(&wblock->dev, evdata.pointer); 13241686f544SAndy Lutomirski 13251686f544SAndy Lutomirski kfree(evdata.pointer); 13261686f544SAndy Lutomirski } else if (wblock->handler) { 13271686f544SAndy Lutomirski /* Legacy handler */ 1328b4f9fe12SLen Brown wblock->handler(event, wblock->handler_data); 13291686f544SAndy Lutomirski } 13301686f544SAndy Lutomirski 13316701cc8fSAndy Shevchenko if (debug_event) 13321c23ab91SBarnabás Pőcze pr_info("DEBUG: GUID %pUL event 0x%02X\n", &wblock->gblock.guid, event); 1333b4f9fe12SLen Brown 1334b4f9fe12SLen Brown acpi_bus_generate_netlink_event( 13351686f544SAndy Lutomirski wblock->acpi_device->pnp.device_class, 13361686f544SAndy Lutomirski dev_name(&wblock->dev.dev), 1337b4f9fe12SLen Brown event, 0); 1338b4f9fe12SLen Brown } 1339b4f9fe12SLen Brown 13409599ed91SAndy Lutomirski static int acpi_wmi_remove(struct platform_device *device) 1341b4f9fe12SLen Brown { 13429599ed91SAndy Lutomirski struct acpi_device *acpi_device = ACPI_COMPANION(&device->dev); 13439599ed91SAndy Lutomirski 13449599ed91SAndy Lutomirski acpi_remove_notify_handler(acpi_device->handle, ACPI_DEVICE_NOTIFY, 13451686f544SAndy Lutomirski acpi_wmi_notify_handler); 13469599ed91SAndy Lutomirski acpi_remove_address_space_handler(acpi_device->handle, 1347b4f9fe12SLen Brown ACPI_ADR_SPACE_EC, &acpi_wmi_ec_space_handler); 13489599ed91SAndy Lutomirski wmi_free_devices(acpi_device); 1349c06a2fdeSBarnabás Pőcze device_unregister(dev_get_drvdata(&device->dev)); 1350b4f9fe12SLen Brown 1351b4f9fe12SLen Brown return 0; 1352b4f9fe12SLen Brown } 1353b4f9fe12SLen Brown 13549599ed91SAndy Lutomirski static int acpi_wmi_probe(struct platform_device *device) 1355b4f9fe12SLen Brown { 13569599ed91SAndy Lutomirski struct acpi_device *acpi_device; 1357844af950SAndy Lutomirski struct device *wmi_bus_dev; 1358b4f9fe12SLen Brown acpi_status status; 1359c64eefd4SDmitry Torokhov int error; 1360b4f9fe12SLen Brown 13619599ed91SAndy Lutomirski acpi_device = ACPI_COMPANION(&device->dev); 13629599ed91SAndy Lutomirski if (!acpi_device) { 13639599ed91SAndy Lutomirski dev_err(&device->dev, "ACPI companion is missing\n"); 13649599ed91SAndy Lutomirski return -ENODEV; 13659599ed91SAndy Lutomirski } 13669599ed91SAndy Lutomirski 13679599ed91SAndy Lutomirski status = acpi_install_address_space_handler(acpi_device->handle, 1368b4f9fe12SLen Brown ACPI_ADR_SPACE_EC, 1369b4f9fe12SLen Brown &acpi_wmi_ec_space_handler, 1370b4f9fe12SLen Brown NULL, NULL); 13715212cd67SDmitry Torokhov if (ACPI_FAILURE(status)) { 137246492ee4SAndy Lutomirski dev_err(&device->dev, "Error installing EC region handler\n"); 1373b4f9fe12SLen Brown return -ENODEV; 13745212cd67SDmitry Torokhov } 1375b4f9fe12SLen Brown 13769599ed91SAndy Lutomirski status = acpi_install_notify_handler(acpi_device->handle, 13779599ed91SAndy Lutomirski ACPI_DEVICE_NOTIFY, 13781686f544SAndy Lutomirski acpi_wmi_notify_handler, 13791686f544SAndy Lutomirski NULL); 13801686f544SAndy Lutomirski if (ACPI_FAILURE(status)) { 13811686f544SAndy Lutomirski dev_err(&device->dev, "Error installing notify handler\n"); 13821686f544SAndy Lutomirski error = -ENODEV; 13831686f544SAndy Lutomirski goto err_remove_ec_handler; 13841686f544SAndy Lutomirski } 13851686f544SAndy Lutomirski 1386844af950SAndy Lutomirski wmi_bus_dev = device_create(&wmi_bus_class, &device->dev, MKDEV(0, 0), 1387844af950SAndy Lutomirski NULL, "wmi_bus-%s", dev_name(&device->dev)); 1388844af950SAndy Lutomirski if (IS_ERR(wmi_bus_dev)) { 1389844af950SAndy Lutomirski error = PTR_ERR(wmi_bus_dev); 13901686f544SAndy Lutomirski goto err_remove_notify_handler; 1391844af950SAndy Lutomirski } 13929599ed91SAndy Lutomirski dev_set_drvdata(&device->dev, wmi_bus_dev); 1393844af950SAndy Lutomirski 13949599ed91SAndy Lutomirski error = parse_wdg(wmi_bus_dev, acpi_device); 1395c64eefd4SDmitry Torokhov if (error) { 13968e07514dSDmitry Torokhov pr_err("Failed to parse WDG method\n"); 1397844af950SAndy Lutomirski goto err_remove_busdev; 1398b4f9fe12SLen Brown } 1399b4f9fe12SLen Brown 1400c64eefd4SDmitry Torokhov return 0; 140146492ee4SAndy Lutomirski 1402844af950SAndy Lutomirski err_remove_busdev: 140356afb8d4SYongxin Liu device_unregister(wmi_bus_dev); 1404844af950SAndy Lutomirski 14051686f544SAndy Lutomirski err_remove_notify_handler: 14069599ed91SAndy Lutomirski acpi_remove_notify_handler(acpi_device->handle, ACPI_DEVICE_NOTIFY, 14071686f544SAndy Lutomirski acpi_wmi_notify_handler); 14081686f544SAndy Lutomirski 14091686f544SAndy Lutomirski err_remove_ec_handler: 14109599ed91SAndy Lutomirski acpi_remove_address_space_handler(acpi_device->handle, 141146492ee4SAndy Lutomirski ACPI_ADR_SPACE_EC, 141246492ee4SAndy Lutomirski &acpi_wmi_ec_space_handler); 141346492ee4SAndy Lutomirski 141446492ee4SAndy Lutomirski return error; 1415b4f9fe12SLen Brown } 1416b4f9fe12SLen Brown 1417844af950SAndy Lutomirski int __must_check __wmi_driver_register(struct wmi_driver *driver, 1418844af950SAndy Lutomirski struct module *owner) 1419844af950SAndy Lutomirski { 1420844af950SAndy Lutomirski driver->driver.owner = owner; 1421844af950SAndy Lutomirski driver->driver.bus = &wmi_bus_type; 1422844af950SAndy Lutomirski 1423844af950SAndy Lutomirski return driver_register(&driver->driver); 1424844af950SAndy Lutomirski } 1425844af950SAndy Lutomirski EXPORT_SYMBOL(__wmi_driver_register); 1426844af950SAndy Lutomirski 1427844af950SAndy Lutomirski void wmi_driver_unregister(struct wmi_driver *driver) 1428844af950SAndy Lutomirski { 1429844af950SAndy Lutomirski driver_unregister(&driver->driver); 1430844af950SAndy Lutomirski } 1431844af950SAndy Lutomirski EXPORT_SYMBOL(wmi_driver_unregister); 1432844af950SAndy Lutomirski 1433b4f9fe12SLen Brown static int __init acpi_wmi_init(void) 1434b4f9fe12SLen Brown { 1435c64eefd4SDmitry Torokhov int error; 1436b4f9fe12SLen Brown 1437b4f9fe12SLen Brown if (acpi_disabled) 1438b4f9fe12SLen Brown return -ENODEV; 1439b4f9fe12SLen Brown 1440844af950SAndy Lutomirski error = class_register(&wmi_bus_class); 1441c64eefd4SDmitry Torokhov if (error) 1442c64eefd4SDmitry Torokhov return error; 1443b4f9fe12SLen Brown 1444844af950SAndy Lutomirski error = bus_register(&wmi_bus_type); 1445844af950SAndy Lutomirski if (error) 1446844af950SAndy Lutomirski goto err_unreg_class; 1447844af950SAndy Lutomirski 14489599ed91SAndy Lutomirski error = platform_driver_register(&acpi_wmi_driver); 1449c64eefd4SDmitry Torokhov if (error) { 1450c64eefd4SDmitry Torokhov pr_err("Error loading mapper\n"); 1451844af950SAndy Lutomirski goto err_unreg_bus; 14521caab3c1SMatthew Garrett } 14531caab3c1SMatthew Garrett 14548e07514dSDmitry Torokhov return 0; 1455844af950SAndy Lutomirski 1456844af950SAndy Lutomirski err_unreg_bus: 1457844af950SAndy Lutomirski bus_unregister(&wmi_bus_type); 1458844af950SAndy Lutomirski 145997277717SAlexey Khoroshilov err_unreg_class: 146097277717SAlexey Khoroshilov class_unregister(&wmi_bus_class); 146197277717SAlexey Khoroshilov 1462844af950SAndy Lutomirski return error; 1463b4f9fe12SLen Brown } 1464b4f9fe12SLen Brown 1465b4f9fe12SLen Brown static void __exit acpi_wmi_exit(void) 1466b4f9fe12SLen Brown { 14679599ed91SAndy Lutomirski platform_driver_unregister(&acpi_wmi_driver); 1468844af950SAndy Lutomirski bus_unregister(&wmi_bus_type); 1469303d1fccSMario Limonciello class_unregister(&wmi_bus_class); 1470b4f9fe12SLen Brown } 1471b4f9fe12SLen Brown 147298b8e4e5SRafael J. Wysocki subsys_initcall_sync(acpi_wmi_init); 1473b4f9fe12SLen Brown module_exit(acpi_wmi_exit); 1474