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