xref: /openbmc/linux/drivers/platform/x86/wmi.c (revision a80e1053)
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 	acpi_status status;
256b4f9fe12SLen Brown 	acpi_handle handle;
257b4f9fe12SLen Brown 
258b4f9fe12SLen Brown 	block = &wblock->gblock;
259b4f9fe12SLen Brown 	handle = wblock->handle;
260b4f9fe12SLen Brown 
261b4f9fe12SLen Brown 	if (!block)
262b4f9fe12SLen Brown 		return AE_NOT_EXIST;
263b4f9fe12SLen Brown 
264b4f9fe12SLen Brown 
265b4f9fe12SLen Brown 	snprintf(method, 5, "WE%02X", block->notify_id);
2668122ab66SZhang Rui 	status = acpi_execute_simple_method(handle, method, enable);
267b4f9fe12SLen Brown 
268b4f9fe12SLen Brown 	if (status != AE_OK && status != AE_NOT_FOUND)
269b4f9fe12SLen Brown 		return status;
270b4f9fe12SLen Brown 	else
271b4f9fe12SLen Brown 		return AE_OK;
272b4f9fe12SLen Brown }
273b4f9fe12SLen Brown 
274b4f9fe12SLen Brown /*
275b4f9fe12SLen Brown  * Exported WMI functions
276b4f9fe12SLen Brown  */
277b4f9fe12SLen Brown /**
278b4f9fe12SLen Brown  * wmi_evaluate_method - Evaluate a WMI method
279b4f9fe12SLen Brown  * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba
280b4f9fe12SLen Brown  * @instance: Instance index
281b4f9fe12SLen Brown  * @method_id: Method ID to call
282b4f9fe12SLen Brown  * &in: Buffer containing input for the method call
283b4f9fe12SLen Brown  * &out: Empty buffer to return the method results
284b4f9fe12SLen Brown  *
285b4f9fe12SLen Brown  * Call an ACPI-WMI method
286b4f9fe12SLen Brown  */
287b4f9fe12SLen Brown acpi_status wmi_evaluate_method(const char *guid_string, u8 instance,
288b4f9fe12SLen Brown u32 method_id, const struct acpi_buffer *in, struct acpi_buffer *out)
289b4f9fe12SLen Brown {
290b4f9fe12SLen Brown 	struct guid_block *block = NULL;
291b4f9fe12SLen Brown 	struct wmi_block *wblock = NULL;
292b4f9fe12SLen Brown 	acpi_handle handle;
293b4f9fe12SLen Brown 	acpi_status status;
294b4f9fe12SLen Brown 	struct acpi_object_list input;
295b4f9fe12SLen Brown 	union acpi_object params[3];
296f3d83e24SCostantino Leandro 	char method[5] = "WM";
297b4f9fe12SLen Brown 
298b4f9fe12SLen Brown 	if (!find_guid(guid_string, &wblock))
299b4f9fe12SLen Brown 		return AE_ERROR;
300b4f9fe12SLen Brown 
301b4f9fe12SLen Brown 	block = &wblock->gblock;
302b4f9fe12SLen Brown 	handle = wblock->handle;
303b4f9fe12SLen Brown 
304b4f9fe12SLen Brown 	if (!(block->flags & ACPI_WMI_METHOD))
305b4f9fe12SLen Brown 		return AE_BAD_DATA;
306b4f9fe12SLen Brown 
307b4f9fe12SLen Brown 	if (block->instance_count < instance)
308b4f9fe12SLen Brown 		return AE_BAD_PARAMETER;
309b4f9fe12SLen Brown 
310b4f9fe12SLen Brown 	input.count = 2;
311b4f9fe12SLen Brown 	input.pointer = params;
312b4f9fe12SLen Brown 	params[0].type = ACPI_TYPE_INTEGER;
313b4f9fe12SLen Brown 	params[0].integer.value = instance;
314b4f9fe12SLen Brown 	params[1].type = ACPI_TYPE_INTEGER;
315b4f9fe12SLen Brown 	params[1].integer.value = method_id;
316b4f9fe12SLen Brown 
317b4f9fe12SLen Brown 	if (in) {
318b4f9fe12SLen Brown 		input.count = 3;
319b4f9fe12SLen Brown 
320b4f9fe12SLen Brown 		if (block->flags & ACPI_WMI_STRING) {
321b4f9fe12SLen Brown 			params[2].type = ACPI_TYPE_STRING;
322b4f9fe12SLen Brown 		} else {
323b4f9fe12SLen Brown 			params[2].type = ACPI_TYPE_BUFFER;
324b4f9fe12SLen Brown 		}
325b4f9fe12SLen Brown 		params[2].buffer.length = in->length;
326b4f9fe12SLen Brown 		params[2].buffer.pointer = in->pointer;
327b4f9fe12SLen Brown 	}
328b4f9fe12SLen Brown 
329b4f9fe12SLen Brown 	strncat(method, block->object_id, 2);
330b4f9fe12SLen Brown 
331b4f9fe12SLen Brown 	status = acpi_evaluate_object(handle, method, &input, out);
332b4f9fe12SLen Brown 
333b4f9fe12SLen Brown 	return status;
334b4f9fe12SLen Brown }
335b4f9fe12SLen Brown EXPORT_SYMBOL_GPL(wmi_evaluate_method);
336b4f9fe12SLen Brown 
337b4f9fe12SLen Brown /**
338b4f9fe12SLen Brown  * wmi_query_block - Return contents of a WMI block
339b4f9fe12SLen Brown  * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba
340b4f9fe12SLen Brown  * @instance: Instance index
341b4f9fe12SLen Brown  * &out: Empty buffer to return the contents of the data block to
342b4f9fe12SLen Brown  *
343b4f9fe12SLen Brown  * Return the contents of an ACPI-WMI data block to a buffer
344b4f9fe12SLen Brown  */
345b4f9fe12SLen Brown acpi_status wmi_query_block(const char *guid_string, u8 instance,
346b4f9fe12SLen Brown struct acpi_buffer *out)
347b4f9fe12SLen Brown {
348b4f9fe12SLen Brown 	struct guid_block *block = NULL;
349b4f9fe12SLen Brown 	struct wmi_block *wblock = NULL;
35054f14c27SZhang Rui 	acpi_handle handle;
351b4f9fe12SLen Brown 	acpi_status status, wc_status = AE_ERROR;
3528122ab66SZhang Rui 	struct acpi_object_list input;
3538122ab66SZhang Rui 	union acpi_object wq_params[1];
354f3d83e24SCostantino Leandro 	char method[5];
355f3d83e24SCostantino Leandro 	char wc_method[5] = "WC";
356b4f9fe12SLen Brown 
357b4f9fe12SLen Brown 	if (!guid_string || !out)
358b4f9fe12SLen Brown 		return AE_BAD_PARAMETER;
359b4f9fe12SLen Brown 
360b4f9fe12SLen Brown 	if (!find_guid(guid_string, &wblock))
361b4f9fe12SLen Brown 		return AE_ERROR;
362b4f9fe12SLen Brown 
363b4f9fe12SLen Brown 	block = &wblock->gblock;
364b4f9fe12SLen Brown 	handle = wblock->handle;
365b4f9fe12SLen Brown 
366b4f9fe12SLen Brown 	if (block->instance_count < instance)
367b4f9fe12SLen Brown 		return AE_BAD_PARAMETER;
368b4f9fe12SLen Brown 
369b4f9fe12SLen Brown 	/* Check GUID is a data block */
370b4f9fe12SLen Brown 	if (block->flags & (ACPI_WMI_EVENT | ACPI_WMI_METHOD))
371b4f9fe12SLen Brown 		return AE_ERROR;
372b4f9fe12SLen Brown 
373b4f9fe12SLen Brown 	input.count = 1;
374b4f9fe12SLen Brown 	input.pointer = wq_params;
375b4f9fe12SLen Brown 	wq_params[0].type = ACPI_TYPE_INTEGER;
376b4f9fe12SLen Brown 	wq_params[0].integer.value = instance;
377b4f9fe12SLen Brown 
378b4f9fe12SLen Brown 	/*
379b4f9fe12SLen Brown 	 * If ACPI_WMI_EXPENSIVE, call the relevant WCxx method first to
380b4f9fe12SLen Brown 	 * enable collection.
381b4f9fe12SLen Brown 	 */
382b4f9fe12SLen Brown 	if (block->flags & ACPI_WMI_EXPENSIVE) {
383b4f9fe12SLen Brown 		strncat(wc_method, block->object_id, 2);
384b4f9fe12SLen Brown 
385b4f9fe12SLen Brown 		/*
386b4f9fe12SLen Brown 		 * Some GUIDs break the specification by declaring themselves
387b4f9fe12SLen Brown 		 * expensive, but have no corresponding WCxx method. So we
388b4f9fe12SLen Brown 		 * should not fail if this happens.
389b4f9fe12SLen Brown 		 */
39054f14c27SZhang Rui 		if (acpi_has_method(handle, wc_method))
3918122ab66SZhang Rui 			wc_status = acpi_execute_simple_method(handle,
3928122ab66SZhang Rui 								wc_method, 1);
393b4f9fe12SLen Brown 	}
394b4f9fe12SLen Brown 
395b4f9fe12SLen Brown 	strcpy(method, "WQ");
396b4f9fe12SLen Brown 	strncat(method, block->object_id, 2);
397b4f9fe12SLen Brown 
398b4f9fe12SLen Brown 	status = acpi_evaluate_object(handle, method, &input, out);
399b4f9fe12SLen Brown 
400b4f9fe12SLen Brown 	/*
401b4f9fe12SLen Brown 	 * If ACPI_WMI_EXPENSIVE, call the relevant WCxx method, even if
402b4f9fe12SLen Brown 	 * the WQxx method failed - we should disable collection anyway.
403b4f9fe12SLen Brown 	 */
404b4f9fe12SLen Brown 	if ((block->flags & ACPI_WMI_EXPENSIVE) && ACPI_SUCCESS(wc_status)) {
4058122ab66SZhang Rui 		status = acpi_execute_simple_method(handle, wc_method, 0);
406b4f9fe12SLen Brown 	}
407b4f9fe12SLen Brown 
408b4f9fe12SLen Brown 	return status;
409b4f9fe12SLen Brown }
410b4f9fe12SLen Brown EXPORT_SYMBOL_GPL(wmi_query_block);
411b4f9fe12SLen Brown 
412b4f9fe12SLen Brown /**
413b4f9fe12SLen Brown  * wmi_set_block - Write to a WMI block
414b4f9fe12SLen Brown  * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba
415b4f9fe12SLen Brown  * @instance: Instance index
416b4f9fe12SLen Brown  * &in: Buffer containing new values for the data block
417b4f9fe12SLen Brown  *
418b4f9fe12SLen Brown  * Write the contents of the input buffer to an ACPI-WMI data block
419b4f9fe12SLen Brown  */
420b4f9fe12SLen Brown acpi_status wmi_set_block(const char *guid_string, u8 instance,
421b4f9fe12SLen Brown const struct acpi_buffer *in)
422b4f9fe12SLen Brown {
423b4f9fe12SLen Brown 	struct guid_block *block = NULL;
424b4f9fe12SLen Brown 	struct wmi_block *wblock = NULL;
425b4f9fe12SLen Brown 	acpi_handle handle;
426b4f9fe12SLen Brown 	struct acpi_object_list input;
427b4f9fe12SLen Brown 	union acpi_object params[2];
428f3d83e24SCostantino Leandro 	char method[5] = "WS";
429b4f9fe12SLen Brown 
430b4f9fe12SLen Brown 	if (!guid_string || !in)
431b4f9fe12SLen Brown 		return AE_BAD_DATA;
432b4f9fe12SLen Brown 
433b4f9fe12SLen Brown 	if (!find_guid(guid_string, &wblock))
434b4f9fe12SLen Brown 		return AE_ERROR;
435b4f9fe12SLen Brown 
436b4f9fe12SLen Brown 	block = &wblock->gblock;
437b4f9fe12SLen Brown 	handle = wblock->handle;
438b4f9fe12SLen Brown 
439b4f9fe12SLen Brown 	if (block->instance_count < instance)
440b4f9fe12SLen Brown 		return AE_BAD_PARAMETER;
441b4f9fe12SLen Brown 
442b4f9fe12SLen Brown 	/* Check GUID is a data block */
443b4f9fe12SLen Brown 	if (block->flags & (ACPI_WMI_EVENT | ACPI_WMI_METHOD))
444b4f9fe12SLen Brown 		return AE_ERROR;
445b4f9fe12SLen Brown 
446b4f9fe12SLen Brown 	input.count = 2;
447b4f9fe12SLen Brown 	input.pointer = params;
448b4f9fe12SLen Brown 	params[0].type = ACPI_TYPE_INTEGER;
449b4f9fe12SLen Brown 	params[0].integer.value = instance;
450b4f9fe12SLen Brown 
451b4f9fe12SLen Brown 	if (block->flags & ACPI_WMI_STRING) {
452b4f9fe12SLen Brown 		params[1].type = ACPI_TYPE_STRING;
453b4f9fe12SLen Brown 	} else {
454b4f9fe12SLen Brown 		params[1].type = ACPI_TYPE_BUFFER;
455b4f9fe12SLen Brown 	}
456b4f9fe12SLen Brown 	params[1].buffer.length = in->length;
457b4f9fe12SLen Brown 	params[1].buffer.pointer = in->pointer;
458b4f9fe12SLen Brown 
459b4f9fe12SLen Brown 	strncat(method, block->object_id, 2);
460b4f9fe12SLen Brown 
461b4f9fe12SLen Brown 	return acpi_evaluate_object(handle, method, &input, NULL);
462b4f9fe12SLen Brown }
463b4f9fe12SLen Brown EXPORT_SYMBOL_GPL(wmi_set_block);
464b4f9fe12SLen Brown 
46537830662SDmitry Torokhov static void wmi_dump_wdg(const struct guid_block *g)
466a929aae0SThomas Renninger {
467a929aae0SThomas Renninger 	char guid_string[37];
468a929aae0SThomas Renninger 
469a929aae0SThomas Renninger 	wmi_gtoa(g->guid, guid_string);
4708e07514dSDmitry Torokhov 
4718e07514dSDmitry Torokhov 	pr_info("%s:\n", guid_string);
4728e07514dSDmitry Torokhov 	pr_info("\tobject_id: %c%c\n", g->object_id[0], g->object_id[1]);
4738e07514dSDmitry Torokhov 	pr_info("\tnotify_id: %02X\n", g->notify_id);
4748e07514dSDmitry Torokhov 	pr_info("\treserved: %02X\n", g->reserved);
4758e07514dSDmitry Torokhov 	pr_info("\tinstance_count: %d\n", g->instance_count);
4768e07514dSDmitry Torokhov 	pr_info("\tflags: %#x", g->flags);
477a929aae0SThomas Renninger 	if (g->flags) {
478a929aae0SThomas Renninger 		if (g->flags & ACPI_WMI_EXPENSIVE)
4798e07514dSDmitry Torokhov 			pr_cont(" ACPI_WMI_EXPENSIVE");
480a929aae0SThomas Renninger 		if (g->flags & ACPI_WMI_METHOD)
4818e07514dSDmitry Torokhov 			pr_cont(" ACPI_WMI_METHOD");
482a929aae0SThomas Renninger 		if (g->flags & ACPI_WMI_STRING)
4838e07514dSDmitry Torokhov 			pr_cont(" ACPI_WMI_STRING");
484a929aae0SThomas Renninger 		if (g->flags & ACPI_WMI_EVENT)
4858e07514dSDmitry Torokhov 			pr_cont(" ACPI_WMI_EVENT");
486a929aae0SThomas Renninger 	}
4878e07514dSDmitry Torokhov 	pr_cont("\n");
488a929aae0SThomas Renninger 
489a929aae0SThomas Renninger }
490a929aae0SThomas Renninger 
491fc3155b2SThomas Renninger static void wmi_notify_debug(u32 value, void *context)
492fc3155b2SThomas Renninger {
493fc3155b2SThomas Renninger 	struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL };
494fc3155b2SThomas Renninger 	union acpi_object *obj;
4951492616aSAxel Lin 	acpi_status status;
496fc3155b2SThomas Renninger 
4971492616aSAxel Lin 	status = wmi_get_event_data(value, &response);
4981492616aSAxel Lin 	if (status != AE_OK) {
4998e07514dSDmitry Torokhov 		pr_info("bad event status 0x%x\n", status);
5001492616aSAxel Lin 		return;
5011492616aSAxel Lin 	}
502fc3155b2SThomas Renninger 
503fc3155b2SThomas Renninger 	obj = (union acpi_object *)response.pointer;
504fc3155b2SThomas Renninger 
505fc3155b2SThomas Renninger 	if (!obj)
506fc3155b2SThomas Renninger 		return;
507fc3155b2SThomas Renninger 
5088e07514dSDmitry Torokhov 	pr_info("DEBUG Event ");
509fc3155b2SThomas Renninger 	switch(obj->type) {
510fc3155b2SThomas Renninger 	case ACPI_TYPE_BUFFER:
5118e07514dSDmitry Torokhov 		pr_cont("BUFFER_TYPE - length %d\n", obj->buffer.length);
512fc3155b2SThomas Renninger 		break;
513fc3155b2SThomas Renninger 	case ACPI_TYPE_STRING:
5148e07514dSDmitry Torokhov 		pr_cont("STRING_TYPE - %s\n", obj->string.pointer);
515fc3155b2SThomas Renninger 		break;
516fc3155b2SThomas Renninger 	case ACPI_TYPE_INTEGER:
5178e07514dSDmitry Torokhov 		pr_cont("INTEGER_TYPE - %llu\n", obj->integer.value);
518fc3155b2SThomas Renninger 		break;
519fc3155b2SThomas Renninger 	case ACPI_TYPE_PACKAGE:
5208e07514dSDmitry Torokhov 		pr_cont("PACKAGE_TYPE - %d elements\n", obj->package.count);
521fc3155b2SThomas Renninger 		break;
522fc3155b2SThomas Renninger 	default:
5238e07514dSDmitry Torokhov 		pr_cont("object type 0x%X\n", obj->type);
524fc3155b2SThomas Renninger 	}
5251492616aSAxel Lin 	kfree(obj);
526fc3155b2SThomas Renninger }
527fc3155b2SThomas Renninger 
528b4f9fe12SLen Brown /**
529b4f9fe12SLen Brown  * wmi_install_notify_handler - Register handler for WMI events
530b4f9fe12SLen Brown  * @handler: Function to handle notifications
531b4f9fe12SLen Brown  * @data: Data to be returned to handler when event is fired
532b4f9fe12SLen Brown  *
533b4f9fe12SLen Brown  * Register a handler for events sent to the ACPI-WMI mapper device.
534b4f9fe12SLen Brown  */
535b4f9fe12SLen Brown acpi_status wmi_install_notify_handler(const char *guid,
536b4f9fe12SLen Brown wmi_notify_handler handler, void *data)
537b4f9fe12SLen Brown {
538b4f9fe12SLen Brown 	struct wmi_block *block;
53958f6425eSColin King 	acpi_status status = AE_NOT_EXIST;
54058f6425eSColin King 	char tmp[16], guid_input[16];
54158f6425eSColin King 	struct list_head *p;
542b4f9fe12SLen Brown 
543b4f9fe12SLen Brown 	if (!guid || !handler)
544b4f9fe12SLen Brown 		return AE_BAD_PARAMETER;
545b4f9fe12SLen Brown 
54658f6425eSColin King 	wmi_parse_guid(guid, tmp);
54758f6425eSColin King 	wmi_swap_bytes(tmp, guid_input);
548b4f9fe12SLen Brown 
54958f6425eSColin King 	list_for_each(p, &wmi_block_list) {
55058f6425eSColin King 		acpi_status wmi_status;
55158f6425eSColin King 		block = list_entry(p, struct wmi_block, list);
55258f6425eSColin King 
55358f6425eSColin King 		if (memcmp(block->gblock.guid, guid_input, 16) == 0) {
55458f6425eSColin King 			if (block->handler &&
55558f6425eSColin King 			    block->handler != wmi_notify_debug)
556b4f9fe12SLen Brown 				return AE_ALREADY_ACQUIRED;
557b4f9fe12SLen Brown 
558b4f9fe12SLen Brown 			block->handler = handler;
559b4f9fe12SLen Brown 			block->handler_data = data;
560b4f9fe12SLen Brown 
56158f6425eSColin King 			wmi_status = wmi_method_enable(block, 1);
56258f6425eSColin King 			if ((wmi_status != AE_OK) ||
56358f6425eSColin King 			    ((wmi_status == AE_OK) && (status == AE_NOT_EXIST)))
56458f6425eSColin King 				status = wmi_status;
56558f6425eSColin King 		}
56658f6425eSColin King 	}
567b4f9fe12SLen Brown 
568b4f9fe12SLen Brown 	return status;
569b4f9fe12SLen Brown }
570b4f9fe12SLen Brown EXPORT_SYMBOL_GPL(wmi_install_notify_handler);
571b4f9fe12SLen Brown 
572b4f9fe12SLen Brown /**
573b4f9fe12SLen Brown  * wmi_uninstall_notify_handler - Unregister handler for WMI events
574b4f9fe12SLen Brown  *
575b4f9fe12SLen Brown  * Unregister handler for events sent to the ACPI-WMI mapper device.
576b4f9fe12SLen Brown  */
577b4f9fe12SLen Brown acpi_status wmi_remove_notify_handler(const char *guid)
578b4f9fe12SLen Brown {
579b4f9fe12SLen Brown 	struct wmi_block *block;
58058f6425eSColin King 	acpi_status status = AE_NOT_EXIST;
58158f6425eSColin King 	char tmp[16], guid_input[16];
58258f6425eSColin King 	struct list_head *p;
583b4f9fe12SLen Brown 
584b4f9fe12SLen Brown 	if (!guid)
585b4f9fe12SLen Brown 		return AE_BAD_PARAMETER;
586b4f9fe12SLen Brown 
58758f6425eSColin King 	wmi_parse_guid(guid, tmp);
58858f6425eSColin King 	wmi_swap_bytes(tmp, guid_input);
589b4f9fe12SLen Brown 
59058f6425eSColin King 	list_for_each(p, &wmi_block_list) {
59158f6425eSColin King 		acpi_status wmi_status;
59258f6425eSColin King 		block = list_entry(p, struct wmi_block, list);
59358f6425eSColin King 
59458f6425eSColin King 		if (memcmp(block->gblock.guid, guid_input, 16) == 0) {
59558f6425eSColin King 			if (!block->handler ||
59658f6425eSColin King 			    block->handler == wmi_notify_debug)
597b4f9fe12SLen Brown 				return AE_NULL_ENTRY;
598b4f9fe12SLen Brown 
599fc3155b2SThomas Renninger 			if (debug_event) {
600fc3155b2SThomas Renninger 				block->handler = wmi_notify_debug;
60158f6425eSColin King 				status = AE_OK;
602fc3155b2SThomas Renninger 			} else {
60358f6425eSColin King 				wmi_status = wmi_method_enable(block, 0);
604b4f9fe12SLen Brown 				block->handler = NULL;
605b4f9fe12SLen Brown 				block->handler_data = NULL;
60658f6425eSColin King 				if ((wmi_status != AE_OK) ||
60758f6425eSColin King 				    ((wmi_status == AE_OK) &&
60858f6425eSColin King 				     (status == AE_NOT_EXIST)))
60958f6425eSColin King 					status = wmi_status;
610fc3155b2SThomas Renninger 			}
61158f6425eSColin King 		}
61258f6425eSColin King 	}
61358f6425eSColin King 
614b4f9fe12SLen Brown 	return status;
615b4f9fe12SLen Brown }
616b4f9fe12SLen Brown EXPORT_SYMBOL_GPL(wmi_remove_notify_handler);
617b4f9fe12SLen Brown 
618b4f9fe12SLen Brown /**
619b4f9fe12SLen Brown  * wmi_get_event_data - Get WMI data associated with an event
620b4f9fe12SLen Brown  *
6213e9b988eSAnisse Astier  * @event: Event to find
6223e9b988eSAnisse Astier  * @out: Buffer to hold event data. out->pointer should be freed with kfree()
623b4f9fe12SLen Brown  *
624b4f9fe12SLen Brown  * Returns extra data associated with an event in WMI.
625b4f9fe12SLen Brown  */
626b4f9fe12SLen Brown acpi_status wmi_get_event_data(u32 event, struct acpi_buffer *out)
627b4f9fe12SLen Brown {
628b4f9fe12SLen Brown 	struct acpi_object_list input;
629b4f9fe12SLen Brown 	union acpi_object params[1];
630b4f9fe12SLen Brown 	struct guid_block *gblock;
631b4f9fe12SLen Brown 	struct wmi_block *wblock;
632b4f9fe12SLen Brown 	struct list_head *p;
633b4f9fe12SLen Brown 
634b4f9fe12SLen Brown 	input.count = 1;
635b4f9fe12SLen Brown 	input.pointer = params;
636b4f9fe12SLen Brown 	params[0].type = ACPI_TYPE_INTEGER;
637b4f9fe12SLen Brown 	params[0].integer.value = event;
638b4f9fe12SLen Brown 
639762e1a2fSDmitry Torokhov 	list_for_each(p, &wmi_block_list) {
640b4f9fe12SLen Brown 		wblock = list_entry(p, struct wmi_block, list);
641b4f9fe12SLen Brown 		gblock = &wblock->gblock;
642b4f9fe12SLen Brown 
643b4f9fe12SLen Brown 		if ((gblock->flags & ACPI_WMI_EVENT) &&
644b4f9fe12SLen Brown 			(gblock->notify_id == event))
645b4f9fe12SLen Brown 			return acpi_evaluate_object(wblock->handle, "_WED",
646b4f9fe12SLen Brown 				&input, out);
647b4f9fe12SLen Brown 	}
648b4f9fe12SLen Brown 
649b4f9fe12SLen Brown 	return AE_NOT_FOUND;
650b4f9fe12SLen Brown }
651b4f9fe12SLen Brown EXPORT_SYMBOL_GPL(wmi_get_event_data);
652b4f9fe12SLen Brown 
653b4f9fe12SLen Brown /**
654b4f9fe12SLen Brown  * wmi_has_guid - Check if a GUID is available
655b4f9fe12SLen Brown  * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba
656b4f9fe12SLen Brown  *
657b4f9fe12SLen Brown  * Check if a given GUID is defined by _WDG
658b4f9fe12SLen Brown  */
659b4f9fe12SLen Brown bool wmi_has_guid(const char *guid_string)
660b4f9fe12SLen Brown {
661b4f9fe12SLen Brown 	return find_guid(guid_string, NULL);
662b4f9fe12SLen Brown }
663b4f9fe12SLen Brown EXPORT_SYMBOL_GPL(wmi_has_guid);
664b4f9fe12SLen Brown 
665b4f9fe12SLen Brown /*
6661caab3c1SMatthew Garrett  * sysfs interface
6671caab3c1SMatthew Garrett  */
668614ef432SDmitry Torokhov static ssize_t modalias_show(struct device *dev, struct device_attribute *attr,
6691caab3c1SMatthew Garrett 			     char *buf)
6701caab3c1SMatthew Garrett {
6711caab3c1SMatthew Garrett 	char guid_string[37];
6721caab3c1SMatthew Garrett 	struct wmi_block *wblock;
6731caab3c1SMatthew Garrett 
6741caab3c1SMatthew Garrett 	wblock = dev_get_drvdata(dev);
675a80e1053SPrarit Bhargava 	if (!wblock) {
676a80e1053SPrarit Bhargava 		strcat(buf, "\n");
677a80e1053SPrarit Bhargava 		return strlen(buf);
678a80e1053SPrarit Bhargava 	}
6791caab3c1SMatthew Garrett 
6801caab3c1SMatthew Garrett 	wmi_gtoa(wblock->gblock.guid, guid_string);
6811caab3c1SMatthew Garrett 
6821caab3c1SMatthew Garrett 	return sprintf(buf, "wmi:%s\n", guid_string);
6831caab3c1SMatthew Garrett }
684e80b89a5SGreg Kroah-Hartman static DEVICE_ATTR_RO(modalias);
685614ef432SDmitry Torokhov 
686e80b89a5SGreg Kroah-Hartman static struct attribute *wmi_attrs[] = {
687e80b89a5SGreg Kroah-Hartman 	&dev_attr_modalias.attr,
688e80b89a5SGreg Kroah-Hartman 	NULL,
689614ef432SDmitry Torokhov };
690e80b89a5SGreg Kroah-Hartman ATTRIBUTE_GROUPS(wmi);
6911caab3c1SMatthew Garrett 
6921caab3c1SMatthew Garrett static int wmi_dev_uevent(struct device *dev, struct kobj_uevent_env *env)
6931caab3c1SMatthew Garrett {
6941caab3c1SMatthew Garrett 	char guid_string[37];
6951caab3c1SMatthew Garrett 
6961caab3c1SMatthew Garrett 	struct wmi_block *wblock;
6971caab3c1SMatthew Garrett 
6981caab3c1SMatthew Garrett 	if (add_uevent_var(env, "MODALIAS="))
6991caab3c1SMatthew Garrett 		return -ENOMEM;
7001caab3c1SMatthew Garrett 
7011caab3c1SMatthew Garrett 	wblock = dev_get_drvdata(dev);
7021caab3c1SMatthew Garrett 	if (!wblock)
7031caab3c1SMatthew Garrett 		return -ENOMEM;
7041caab3c1SMatthew Garrett 
7051caab3c1SMatthew Garrett 	wmi_gtoa(wblock->gblock.guid, guid_string);
7061caab3c1SMatthew Garrett 
7071caab3c1SMatthew Garrett 	strcpy(&env->buf[env->buflen - 1], "wmi:");
7081caab3c1SMatthew Garrett 	memcpy(&env->buf[env->buflen - 1 + 4], guid_string, 36);
7091caab3c1SMatthew Garrett 	env->buflen += 40;
7101caab3c1SMatthew Garrett 
7111caab3c1SMatthew Garrett 	return 0;
7121caab3c1SMatthew Garrett }
7131caab3c1SMatthew Garrett 
7141caab3c1SMatthew Garrett static void wmi_dev_free(struct device *dev)
7151caab3c1SMatthew Garrett {
716c64eefd4SDmitry Torokhov 	struct wmi_block *wmi_block = container_of(dev, struct wmi_block, dev);
717c64eefd4SDmitry Torokhov 
718c64eefd4SDmitry Torokhov 	kfree(wmi_block);
7191caab3c1SMatthew Garrett }
7201caab3c1SMatthew Garrett 
7211caab3c1SMatthew Garrett static struct class wmi_class = {
7221caab3c1SMatthew Garrett 	.name = "wmi",
7231caab3c1SMatthew Garrett 	.dev_release = wmi_dev_free,
7241caab3c1SMatthew Garrett 	.dev_uevent = wmi_dev_uevent,
725e80b89a5SGreg Kroah-Hartman 	.dev_groups = wmi_groups,
7261caab3c1SMatthew Garrett };
7271caab3c1SMatthew Garrett 
72858f6425eSColin King static int wmi_create_device(const struct guid_block *gblock,
72958f6425eSColin King 			     struct wmi_block *wblock, acpi_handle handle)
7301caab3c1SMatthew Garrett {
731c64eefd4SDmitry Torokhov 	char guid_string[37];
7321caab3c1SMatthew Garrett 
733c64eefd4SDmitry Torokhov 	wblock->dev.class = &wmi_class;
7341caab3c1SMatthew Garrett 
7351caab3c1SMatthew Garrett 	wmi_gtoa(gblock->guid, guid_string);
73602aa2a37SKees Cook 	dev_set_name(&wblock->dev, "%s", guid_string);
7371caab3c1SMatthew Garrett 
738c64eefd4SDmitry Torokhov 	dev_set_drvdata(&wblock->dev, wblock);
739c64eefd4SDmitry Torokhov 
74058f6425eSColin King 	return device_register(&wblock->dev);
7411caab3c1SMatthew Garrett }
7421caab3c1SMatthew Garrett 
743c64eefd4SDmitry Torokhov static void wmi_free_devices(void)
7441caab3c1SMatthew Garrett {
745c64eefd4SDmitry Torokhov 	struct wmi_block *wblock, *next;
7461caab3c1SMatthew Garrett 
7471caab3c1SMatthew Garrett 	/* Delete devices for all the GUIDs */
748023b9565SDmitry Torokhov 	list_for_each_entry_safe(wblock, next, &wmi_block_list, list) {
749023b9565SDmitry Torokhov 		list_del(&wblock->list);
75058f6425eSColin King 		if (wblock->dev.class)
751c64eefd4SDmitry Torokhov 			device_unregister(&wblock->dev);
752023b9565SDmitry Torokhov 		else
753023b9565SDmitry Torokhov 			kfree(wblock);
754023b9565SDmitry Torokhov 	}
7551caab3c1SMatthew Garrett }
7561caab3c1SMatthew Garrett 
757d1f9e497SCarlos Corbacho static bool guid_already_parsed(const char *guid_string)
758d1f9e497SCarlos Corbacho {
759d1f9e497SCarlos Corbacho 	struct wmi_block *wblock;
760d1f9e497SCarlos Corbacho 
761c64eefd4SDmitry Torokhov 	list_for_each_entry(wblock, &wmi_block_list, list)
7628b14d7b2SThadeu Lima de Souza Cascardo 		if (memcmp(wblock->gblock.guid, guid_string, 16) == 0)
763d1f9e497SCarlos Corbacho 			return true;
764c64eefd4SDmitry Torokhov 
765d1f9e497SCarlos Corbacho 	return false;
766d1f9e497SCarlos Corbacho }
767d1f9e497SCarlos Corbacho 
7681caab3c1SMatthew Garrett /*
769b4f9fe12SLen Brown  * Parse the _WDG method for the GUID data blocks
770b4f9fe12SLen Brown  */
7710a018a68SDan Carpenter static int parse_wdg(acpi_handle handle)
772b4f9fe12SLen Brown {
773b4f9fe12SLen Brown 	struct acpi_buffer out = {ACPI_ALLOCATE_BUFFER, NULL};
774b4f9fe12SLen Brown 	union acpi_object *obj;
77537830662SDmitry Torokhov 	const struct guid_block *gblock;
776b4f9fe12SLen Brown 	struct wmi_block *wblock;
777b4f9fe12SLen Brown 	acpi_status status;
778c64eefd4SDmitry Torokhov 	int retval;
779b4f9fe12SLen Brown 	u32 i, total;
780b4f9fe12SLen Brown 
781b4f9fe12SLen Brown 	status = acpi_evaluate_object(handle, "_WDG", NULL, &out);
782b4f9fe12SLen Brown 	if (ACPI_FAILURE(status))
783c64eefd4SDmitry Torokhov 		return -ENXIO;
784b4f9fe12SLen Brown 
785b4f9fe12SLen Brown 	obj = (union acpi_object *) out.pointer;
7863d2c63ebSDmitry Torokhov 	if (!obj)
787c64eefd4SDmitry Torokhov 		return -ENXIO;
788b4f9fe12SLen Brown 
78964ed0ab8SDmitry Torokhov 	if (obj->type != ACPI_TYPE_BUFFER) {
790c64eefd4SDmitry Torokhov 		retval = -ENXIO;
79164ed0ab8SDmitry Torokhov 		goto out_free_pointer;
79264ed0ab8SDmitry Torokhov 	}
793b4f9fe12SLen Brown 
79437830662SDmitry Torokhov 	gblock = (const struct guid_block *)obj->buffer.pointer;
795b4f9fe12SLen Brown 	total = obj->buffer.length / sizeof(struct guid_block);
796b4f9fe12SLen Brown 
797b4f9fe12SLen Brown 	for (i = 0; i < total; i++) {
798a929aae0SThomas Renninger 		if (debug_dump_wdg)
799a929aae0SThomas Renninger 			wmi_dump_wdg(&gblock[i]);
800a929aae0SThomas Renninger 
80158f6425eSColin King 		wblock = kzalloc(sizeof(struct wmi_block), GFP_KERNEL);
80258f6425eSColin King 		if (!wblock)
8030a018a68SDan Carpenter 			return -ENOMEM;
80458f6425eSColin King 
80558f6425eSColin King 		wblock->handle = handle;
80658f6425eSColin King 		wblock->gblock = gblock[i];
80758f6425eSColin King 
80858f6425eSColin King 		/*
80958f6425eSColin King 		  Some WMI devices, like those for nVidia hooks, have a
81058f6425eSColin King 		  duplicate GUID. It's not clear what we should do in this
81158f6425eSColin King 		  case yet, so for now, we'll just ignore the duplicate
81258f6425eSColin King 		  for device creation.
81358f6425eSColin King 		*/
81458f6425eSColin King 		if (!guid_already_parsed(gblock[i].guid)) {
81558f6425eSColin King 			retval = wmi_create_device(&gblock[i], wblock, handle);
81658f6425eSColin King 			if (retval) {
817c64eefd4SDmitry Torokhov 				wmi_free_devices();
818e1e0dacbSDan Carpenter 				goto out_free_pointer;
819a5167c5bSAxel Lin 			}
82058f6425eSColin King 		}
82158f6425eSColin King 
82258f6425eSColin King 		list_add_tail(&wblock->list, &wmi_block_list);
823b4f9fe12SLen Brown 
824fc3155b2SThomas Renninger 		if (debug_event) {
825fc3155b2SThomas Renninger 			wblock->handler = wmi_notify_debug;
8262d5ab555SDmitry Torokhov 			wmi_method_enable(wblock, 1);
827fc3155b2SThomas Renninger 		}
828b4f9fe12SLen Brown 	}
829b4f9fe12SLen Brown 
830c64eefd4SDmitry Torokhov 	retval = 0;
831c64eefd4SDmitry Torokhov 
832a5167c5bSAxel Lin out_free_pointer:
833a5167c5bSAxel Lin 	kfree(out.pointer);
834b4f9fe12SLen Brown 
835c64eefd4SDmitry Torokhov 	return retval;
836b4f9fe12SLen Brown }
837b4f9fe12SLen Brown 
838b4f9fe12SLen Brown /*
839b4f9fe12SLen Brown  * WMI can have EmbeddedControl access regions. In which case, we just want to
840b4f9fe12SLen Brown  * hand these off to the EC driver.
841b4f9fe12SLen Brown  */
842b4f9fe12SLen Brown static acpi_status
843b4f9fe12SLen Brown acpi_wmi_ec_space_handler(u32 function, acpi_physical_address address,
844439913ffSLin Ming 		      u32 bits, u64 *value,
845b4f9fe12SLen Brown 		      void *handler_context, void *region_context)
846b4f9fe12SLen Brown {
847b4f9fe12SLen Brown 	int result = 0, i = 0;
848b4f9fe12SLen Brown 	u8 temp = 0;
849b4f9fe12SLen Brown 
850b4f9fe12SLen Brown 	if ((address > 0xFF) || !value)
851b4f9fe12SLen Brown 		return AE_BAD_PARAMETER;
852b4f9fe12SLen Brown 
853b4f9fe12SLen Brown 	if (function != ACPI_READ && function != ACPI_WRITE)
854b4f9fe12SLen Brown 		return AE_BAD_PARAMETER;
855b4f9fe12SLen Brown 
856b4f9fe12SLen Brown 	if (bits != 8)
857b4f9fe12SLen Brown 		return AE_BAD_PARAMETER;
858b4f9fe12SLen Brown 
859b4f9fe12SLen Brown 	if (function == ACPI_READ) {
860b4f9fe12SLen Brown 		result = ec_read(address, &temp);
861439913ffSLin Ming 		(*value) |= ((u64)temp) << i;
862b4f9fe12SLen Brown 	} else {
863b4f9fe12SLen Brown 		temp = 0xff & ((*value) >> i);
864b4f9fe12SLen Brown 		result = ec_write(address, temp);
865b4f9fe12SLen Brown 	}
866b4f9fe12SLen Brown 
867b4f9fe12SLen Brown 	switch (result) {
868b4f9fe12SLen Brown 	case -EINVAL:
869b4f9fe12SLen Brown 		return AE_BAD_PARAMETER;
870b4f9fe12SLen Brown 		break;
871b4f9fe12SLen Brown 	case -ENODEV:
872b4f9fe12SLen Brown 		return AE_NOT_FOUND;
873b4f9fe12SLen Brown 		break;
874b4f9fe12SLen Brown 	case -ETIME:
875b4f9fe12SLen Brown 		return AE_TIME;
876b4f9fe12SLen Brown 		break;
877b4f9fe12SLen Brown 	default:
878b4f9fe12SLen Brown 		return AE_OK;
879b4f9fe12SLen Brown 	}
880b4f9fe12SLen Brown }
881b4f9fe12SLen Brown 
882f61bb939SBjorn Helgaas static void acpi_wmi_notify(struct acpi_device *device, u32 event)
883b4f9fe12SLen Brown {
884b4f9fe12SLen Brown 	struct guid_block *block;
885b4f9fe12SLen Brown 	struct wmi_block *wblock;
886b4f9fe12SLen Brown 	struct list_head *p;
8877715348cSThomas Renninger 	char guid_string[37];
888b4f9fe12SLen Brown 
889762e1a2fSDmitry Torokhov 	list_for_each(p, &wmi_block_list) {
890b4f9fe12SLen Brown 		wblock = list_entry(p, struct wmi_block, list);
891b4f9fe12SLen Brown 		block = &wblock->gblock;
892b4f9fe12SLen Brown 
893b4f9fe12SLen Brown 		if ((block->flags & ACPI_WMI_EVENT) &&
894b4f9fe12SLen Brown 			(block->notify_id == event)) {
895b4f9fe12SLen Brown 			if (wblock->handler)
896b4f9fe12SLen Brown 				wblock->handler(event, wblock->handler_data);
8977715348cSThomas Renninger 			if (debug_event) {
8987715348cSThomas Renninger 				wmi_gtoa(wblock->gblock.guid, guid_string);
8998e07514dSDmitry Torokhov 				pr_info("DEBUG Event GUID: %s\n", guid_string);
9007715348cSThomas Renninger 			}
901b4f9fe12SLen Brown 
902b4f9fe12SLen Brown 			acpi_bus_generate_netlink_event(
903b4f9fe12SLen Brown 				device->pnp.device_class, dev_name(&device->dev),
904b4f9fe12SLen Brown 				event, 0);
905b4f9fe12SLen Brown 			break;
906b4f9fe12SLen Brown 		}
907b4f9fe12SLen Brown 	}
908b4f9fe12SLen Brown }
909b4f9fe12SLen Brown 
91051fac838SRafael J. Wysocki static int acpi_wmi_remove(struct acpi_device *device)
911b4f9fe12SLen Brown {
912b4f9fe12SLen Brown 	acpi_remove_address_space_handler(device->handle,
913b4f9fe12SLen Brown 				ACPI_ADR_SPACE_EC, &acpi_wmi_ec_space_handler);
914c64eefd4SDmitry Torokhov 	wmi_free_devices();
915b4f9fe12SLen Brown 
916b4f9fe12SLen Brown 	return 0;
917b4f9fe12SLen Brown }
918b4f9fe12SLen Brown 
919925b1089SThomas Renninger static int acpi_wmi_add(struct acpi_device *device)
920b4f9fe12SLen Brown {
921b4f9fe12SLen Brown 	acpi_status status;
922c64eefd4SDmitry Torokhov 	int error;
923b4f9fe12SLen Brown 
924b4f9fe12SLen Brown 	status = acpi_install_address_space_handler(device->handle,
925b4f9fe12SLen Brown 						    ACPI_ADR_SPACE_EC,
926b4f9fe12SLen Brown 						    &acpi_wmi_ec_space_handler,
927b4f9fe12SLen Brown 						    NULL, NULL);
9285212cd67SDmitry Torokhov 	if (ACPI_FAILURE(status)) {
9298e07514dSDmitry Torokhov 		pr_err("Error installing EC region handler\n");
930b4f9fe12SLen Brown 		return -ENODEV;
9315212cd67SDmitry Torokhov 	}
932b4f9fe12SLen Brown 
933c64eefd4SDmitry Torokhov 	error = parse_wdg(device->handle);
934c64eefd4SDmitry Torokhov 	if (error) {
9355212cd67SDmitry Torokhov 		acpi_remove_address_space_handler(device->handle,
9365212cd67SDmitry Torokhov 						  ACPI_ADR_SPACE_EC,
9375212cd67SDmitry Torokhov 						  &acpi_wmi_ec_space_handler);
9388e07514dSDmitry Torokhov 		pr_err("Failed to parse WDG method\n");
939c64eefd4SDmitry Torokhov 		return error;
940b4f9fe12SLen Brown 	}
941b4f9fe12SLen Brown 
942c64eefd4SDmitry Torokhov 	return 0;
943b4f9fe12SLen Brown }
944b4f9fe12SLen Brown 
945b4f9fe12SLen Brown static int __init acpi_wmi_init(void)
946b4f9fe12SLen Brown {
947c64eefd4SDmitry Torokhov 	int error;
948b4f9fe12SLen Brown 
949b4f9fe12SLen Brown 	if (acpi_disabled)
950b4f9fe12SLen Brown 		return -ENODEV;
951b4f9fe12SLen Brown 
952c64eefd4SDmitry Torokhov 	error = class_register(&wmi_class);
953c64eefd4SDmitry Torokhov 	if (error)
954c64eefd4SDmitry Torokhov 		return error;
955b4f9fe12SLen Brown 
956c64eefd4SDmitry Torokhov 	error = acpi_bus_register_driver(&acpi_wmi_driver);
957c64eefd4SDmitry Torokhov 	if (error) {
958c64eefd4SDmitry Torokhov 		pr_err("Error loading mapper\n");
959c64eefd4SDmitry Torokhov 		class_unregister(&wmi_class);
960c64eefd4SDmitry Torokhov 		return error;
9611caab3c1SMatthew Garrett 	}
9621caab3c1SMatthew Garrett 
9638e07514dSDmitry Torokhov 	pr_info("Mapper loaded\n");
9648e07514dSDmitry Torokhov 	return 0;
965b4f9fe12SLen Brown }
966b4f9fe12SLen Brown 
967b4f9fe12SLen Brown static void __exit acpi_wmi_exit(void)
968b4f9fe12SLen Brown {
969b4f9fe12SLen Brown 	acpi_bus_unregister_driver(&acpi_wmi_driver);
970c64eefd4SDmitry Torokhov 	class_unregister(&wmi_class);
971b4f9fe12SLen Brown 
9728e07514dSDmitry Torokhov 	pr_info("Mapper unloaded\n");
973b4f9fe12SLen Brown }
974b4f9fe12SLen Brown 
975b4f9fe12SLen Brown subsys_initcall(acpi_wmi_init);
976b4f9fe12SLen Brown module_exit(acpi_wmi_exit);
977