xref: /openbmc/linux/drivers/platform/x86/wmi.c (revision 844af950)
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>
40844af950SAndy Lutomirski #include <linux/wmi.h>
41538d7eb8SAndy Shevchenko #include <linux/uuid.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 
48762e1a2fSDmitry Torokhov static LIST_HEAD(wmi_block_list);
49b4f9fe12SLen Brown 
50b4f9fe12SLen Brown struct guid_block {
51b4f9fe12SLen Brown 	char guid[16];
52b4f9fe12SLen Brown 	union {
53b4f9fe12SLen Brown 		char object_id[2];
54b4f9fe12SLen Brown 		struct {
55b4f9fe12SLen Brown 			unsigned char notify_id;
56b4f9fe12SLen Brown 			unsigned char reserved;
57b4f9fe12SLen Brown 		};
58b4f9fe12SLen Brown 	};
59b4f9fe12SLen Brown 	u8 instance_count;
60b4f9fe12SLen Brown 	u8 flags;
61b4f9fe12SLen Brown };
62b4f9fe12SLen Brown 
63b4f9fe12SLen Brown struct wmi_block {
64844af950SAndy Lutomirski 	struct wmi_device dev;
65b4f9fe12SLen Brown 	struct list_head list;
66b4f9fe12SLen Brown 	struct guid_block gblock;
67b0e86302SAndy Lutomirski 	struct acpi_device *acpi_device;
68b4f9fe12SLen Brown 	wmi_notify_handler handler;
69b4f9fe12SLen Brown 	void *handler_data;
70b4f9fe12SLen Brown };
71b4f9fe12SLen Brown 
72b4f9fe12SLen Brown 
73b4f9fe12SLen Brown /*
74b4f9fe12SLen Brown  * If the GUID data block is marked as expensive, we must enable and
75b4f9fe12SLen Brown  * explicitily disable data collection.
76b4f9fe12SLen Brown  */
77b4f9fe12SLen Brown #define ACPI_WMI_EXPENSIVE   0x1
78b4f9fe12SLen Brown #define ACPI_WMI_METHOD      0x2	/* GUID is a method */
79b4f9fe12SLen Brown #define ACPI_WMI_STRING      0x4	/* GUID takes & returns a string */
80b4f9fe12SLen Brown #define ACPI_WMI_EVENT       0x8	/* GUID is an event */
81b4f9fe12SLen Brown 
8290ab5ee9SRusty Russell static bool debug_event;
83fc3155b2SThomas Renninger module_param(debug_event, bool, 0444);
84fc3155b2SThomas Renninger MODULE_PARM_DESC(debug_event,
85fc3155b2SThomas Renninger 		 "Log WMI Events [0/1]");
86fc3155b2SThomas Renninger 
8790ab5ee9SRusty Russell static bool debug_dump_wdg;
88a929aae0SThomas Renninger module_param(debug_dump_wdg, bool, 0444);
89a929aae0SThomas Renninger MODULE_PARM_DESC(debug_dump_wdg,
90a929aae0SThomas Renninger 		 "Dump available WMI interfaces [0/1]");
91a929aae0SThomas Renninger 
9251fac838SRafael J. Wysocki static int acpi_wmi_remove(struct acpi_device *device);
93b4f9fe12SLen Brown static int acpi_wmi_add(struct acpi_device *device);
94f61bb939SBjorn Helgaas static void acpi_wmi_notify(struct acpi_device *device, u32 event);
95b4f9fe12SLen Brown 
96b4f9fe12SLen Brown static const struct acpi_device_id wmi_device_ids[] = {
97b4f9fe12SLen Brown 	{"PNP0C14", 0},
98b4f9fe12SLen Brown 	{"pnp0c14", 0},
99b4f9fe12SLen Brown 	{"", 0},
100b4f9fe12SLen Brown };
101b4f9fe12SLen Brown MODULE_DEVICE_TABLE(acpi, wmi_device_ids);
102b4f9fe12SLen Brown 
103b4f9fe12SLen Brown static struct acpi_driver acpi_wmi_driver = {
104844af950SAndy Lutomirski 	.name = "acpi-wmi",
105844af950SAndy Lutomirski 	.owner = THIS_MODULE,
106b4f9fe12SLen Brown 	.ids = wmi_device_ids,
107b4f9fe12SLen Brown 	.ops = {
108b4f9fe12SLen Brown 		.add = acpi_wmi_add,
109b4f9fe12SLen Brown 		.remove = acpi_wmi_remove,
110f61bb939SBjorn Helgaas 		.notify = acpi_wmi_notify,
111b4f9fe12SLen Brown 	},
112b4f9fe12SLen Brown };
113b4f9fe12SLen Brown 
114b4f9fe12SLen Brown /*
115b4f9fe12SLen Brown  * GUID parsing functions
116b4f9fe12SLen Brown  */
117b4f9fe12SLen Brown 
118b4f9fe12SLen Brown static bool find_guid(const char *guid_string, struct wmi_block **out)
119b4f9fe12SLen Brown {
120538d7eb8SAndy Shevchenko 	uuid_le guid_input;
121b4f9fe12SLen Brown 	struct wmi_block *wblock;
122b4f9fe12SLen Brown 	struct guid_block *block;
123b4f9fe12SLen Brown 	struct list_head *p;
124b4f9fe12SLen Brown 
125538d7eb8SAndy Shevchenko 	if (uuid_le_to_bin(guid_string, &guid_input))
126538d7eb8SAndy Shevchenko 		return false;
127b4f9fe12SLen Brown 
128762e1a2fSDmitry Torokhov 	list_for_each(p, &wmi_block_list) {
129b4f9fe12SLen Brown 		wblock = list_entry(p, struct wmi_block, list);
130b4f9fe12SLen Brown 		block = &wblock->gblock;
131b4f9fe12SLen Brown 
132538d7eb8SAndy Shevchenko 		if (memcmp(block->guid, &guid_input, 16) == 0) {
133b4f9fe12SLen Brown 			if (out)
134b4f9fe12SLen Brown 				*out = wblock;
135097c27fcSJoe Perches 			return true;
136b4f9fe12SLen Brown 		}
137b4f9fe12SLen Brown 	}
138097c27fcSJoe Perches 	return false;
139b4f9fe12SLen Brown }
140b4f9fe12SLen Brown 
141b4f9fe12SLen Brown static acpi_status wmi_method_enable(struct wmi_block *wblock, int enable)
142b4f9fe12SLen Brown {
143b4f9fe12SLen Brown 	struct guid_block *block = NULL;
144b4f9fe12SLen Brown 	char method[5];
145b4f9fe12SLen Brown 	acpi_status status;
146b4f9fe12SLen Brown 	acpi_handle handle;
147b4f9fe12SLen Brown 
148b4f9fe12SLen Brown 	block = &wblock->gblock;
149b0e86302SAndy Lutomirski 	handle = wblock->acpi_device->handle;
150b4f9fe12SLen Brown 
151b4f9fe12SLen Brown 	snprintf(method, 5, "WE%02X", block->notify_id);
1528122ab66SZhang Rui 	status = acpi_execute_simple_method(handle, method, enable);
153b4f9fe12SLen Brown 
154b4f9fe12SLen Brown 	if (status != AE_OK && status != AE_NOT_FOUND)
155b4f9fe12SLen Brown 		return status;
156b4f9fe12SLen Brown 	else
157b4f9fe12SLen Brown 		return AE_OK;
158b4f9fe12SLen Brown }
159b4f9fe12SLen Brown 
160b4f9fe12SLen Brown /*
161b4f9fe12SLen Brown  * Exported WMI functions
162b4f9fe12SLen Brown  */
163b4f9fe12SLen Brown /**
164b4f9fe12SLen Brown  * wmi_evaluate_method - Evaluate a WMI method
165b4f9fe12SLen Brown  * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba
166b4f9fe12SLen Brown  * @instance: Instance index
167b4f9fe12SLen Brown  * @method_id: Method ID to call
168b4f9fe12SLen Brown  * &in: Buffer containing input for the method call
169b4f9fe12SLen Brown  * &out: Empty buffer to return the method results
170b4f9fe12SLen Brown  *
171b4f9fe12SLen Brown  * Call an ACPI-WMI method
172b4f9fe12SLen Brown  */
173b4f9fe12SLen Brown acpi_status wmi_evaluate_method(const char *guid_string, u8 instance,
174b4f9fe12SLen Brown u32 method_id, const struct acpi_buffer *in, struct acpi_buffer *out)
175b4f9fe12SLen Brown {
176b4f9fe12SLen Brown 	struct guid_block *block = NULL;
177b4f9fe12SLen Brown 	struct wmi_block *wblock = NULL;
178b4f9fe12SLen Brown 	acpi_handle handle;
179b4f9fe12SLen Brown 	acpi_status status;
180b4f9fe12SLen Brown 	struct acpi_object_list input;
181b4f9fe12SLen Brown 	union acpi_object params[3];
182f3d83e24SCostantino Leandro 	char method[5] = "WM";
183b4f9fe12SLen Brown 
184b4f9fe12SLen Brown 	if (!find_guid(guid_string, &wblock))
185b4f9fe12SLen Brown 		return AE_ERROR;
186b4f9fe12SLen Brown 
187b4f9fe12SLen Brown 	block = &wblock->gblock;
188b0e86302SAndy Lutomirski 	handle = wblock->acpi_device->handle;
189b4f9fe12SLen Brown 
190b4f9fe12SLen Brown 	if (!(block->flags & ACPI_WMI_METHOD))
191b4f9fe12SLen Brown 		return AE_BAD_DATA;
192b4f9fe12SLen Brown 
193b4f9fe12SLen Brown 	if (block->instance_count < instance)
194b4f9fe12SLen Brown 		return AE_BAD_PARAMETER;
195b4f9fe12SLen Brown 
196b4f9fe12SLen Brown 	input.count = 2;
197b4f9fe12SLen Brown 	input.pointer = params;
198b4f9fe12SLen Brown 	params[0].type = ACPI_TYPE_INTEGER;
199b4f9fe12SLen Brown 	params[0].integer.value = instance;
200b4f9fe12SLen Brown 	params[1].type = ACPI_TYPE_INTEGER;
201b4f9fe12SLen Brown 	params[1].integer.value = method_id;
202b4f9fe12SLen Brown 
203b4f9fe12SLen Brown 	if (in) {
204b4f9fe12SLen Brown 		input.count = 3;
205b4f9fe12SLen Brown 
206b4f9fe12SLen Brown 		if (block->flags & ACPI_WMI_STRING) {
207b4f9fe12SLen Brown 			params[2].type = ACPI_TYPE_STRING;
208b4f9fe12SLen Brown 		} else {
209b4f9fe12SLen Brown 			params[2].type = ACPI_TYPE_BUFFER;
210b4f9fe12SLen Brown 		}
211b4f9fe12SLen Brown 		params[2].buffer.length = in->length;
212b4f9fe12SLen Brown 		params[2].buffer.pointer = in->pointer;
213b4f9fe12SLen Brown 	}
214b4f9fe12SLen Brown 
215b4f9fe12SLen Brown 	strncat(method, block->object_id, 2);
216b4f9fe12SLen Brown 
217b4f9fe12SLen Brown 	status = acpi_evaluate_object(handle, method, &input, out);
218b4f9fe12SLen Brown 
219b4f9fe12SLen Brown 	return status;
220b4f9fe12SLen Brown }
221b4f9fe12SLen Brown EXPORT_SYMBOL_GPL(wmi_evaluate_method);
222b4f9fe12SLen Brown 
223b4f9fe12SLen Brown /**
224b4f9fe12SLen Brown  * wmi_query_block - Return contents of a WMI block
225b4f9fe12SLen Brown  * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba
226b4f9fe12SLen Brown  * @instance: Instance index
227b4f9fe12SLen Brown  * &out: Empty buffer to return the contents of the data block to
228b4f9fe12SLen Brown  *
229b4f9fe12SLen Brown  * Return the contents of an ACPI-WMI data block to a buffer
230b4f9fe12SLen Brown  */
231b4f9fe12SLen Brown acpi_status wmi_query_block(const char *guid_string, u8 instance,
232b4f9fe12SLen Brown struct acpi_buffer *out)
233b4f9fe12SLen Brown {
234b4f9fe12SLen Brown 	struct guid_block *block = NULL;
235b4f9fe12SLen Brown 	struct wmi_block *wblock = NULL;
23654f14c27SZhang Rui 	acpi_handle handle;
237b4f9fe12SLen Brown 	acpi_status status, wc_status = AE_ERROR;
2388122ab66SZhang Rui 	struct acpi_object_list input;
2398122ab66SZhang Rui 	union acpi_object wq_params[1];
240f3d83e24SCostantino Leandro 	char method[5];
241f3d83e24SCostantino Leandro 	char wc_method[5] = "WC";
242b4f9fe12SLen Brown 
243b4f9fe12SLen Brown 	if (!guid_string || !out)
244b4f9fe12SLen Brown 		return AE_BAD_PARAMETER;
245b4f9fe12SLen Brown 
246b4f9fe12SLen Brown 	if (!find_guid(guid_string, &wblock))
247b4f9fe12SLen Brown 		return AE_ERROR;
248b4f9fe12SLen Brown 
249b4f9fe12SLen Brown 	block = &wblock->gblock;
250b0e86302SAndy Lutomirski 	handle = wblock->acpi_device->handle;
251b4f9fe12SLen Brown 
252b4f9fe12SLen Brown 	if (block->instance_count < instance)
253b4f9fe12SLen Brown 		return AE_BAD_PARAMETER;
254b4f9fe12SLen Brown 
255b4f9fe12SLen Brown 	/* Check GUID is a data block */
256b4f9fe12SLen Brown 	if (block->flags & (ACPI_WMI_EVENT | ACPI_WMI_METHOD))
257b4f9fe12SLen Brown 		return AE_ERROR;
258b4f9fe12SLen Brown 
259b4f9fe12SLen Brown 	input.count = 1;
260b4f9fe12SLen Brown 	input.pointer = wq_params;
261b4f9fe12SLen Brown 	wq_params[0].type = ACPI_TYPE_INTEGER;
262b4f9fe12SLen Brown 	wq_params[0].integer.value = instance;
263b4f9fe12SLen Brown 
264b4f9fe12SLen Brown 	/*
265b4f9fe12SLen Brown 	 * If ACPI_WMI_EXPENSIVE, call the relevant WCxx method first to
266b4f9fe12SLen Brown 	 * enable collection.
267b4f9fe12SLen Brown 	 */
268b4f9fe12SLen Brown 	if (block->flags & ACPI_WMI_EXPENSIVE) {
269b4f9fe12SLen Brown 		strncat(wc_method, block->object_id, 2);
270b4f9fe12SLen Brown 
271b4f9fe12SLen Brown 		/*
272b4f9fe12SLen Brown 		 * Some GUIDs break the specification by declaring themselves
273b4f9fe12SLen Brown 		 * expensive, but have no corresponding WCxx method. So we
274b4f9fe12SLen Brown 		 * should not fail if this happens.
275b4f9fe12SLen Brown 		 */
27654f14c27SZhang Rui 		if (acpi_has_method(handle, wc_method))
2778122ab66SZhang Rui 			wc_status = acpi_execute_simple_method(handle,
2788122ab66SZhang Rui 								wc_method, 1);
279b4f9fe12SLen Brown 	}
280b4f9fe12SLen Brown 
281b4f9fe12SLen Brown 	strcpy(method, "WQ");
282b4f9fe12SLen Brown 	strncat(method, block->object_id, 2);
283b4f9fe12SLen Brown 
284b4f9fe12SLen Brown 	status = acpi_evaluate_object(handle, method, &input, out);
285b4f9fe12SLen Brown 
286b4f9fe12SLen Brown 	/*
287b4f9fe12SLen Brown 	 * If ACPI_WMI_EXPENSIVE, call the relevant WCxx method, even if
288b4f9fe12SLen Brown 	 * the WQxx method failed - we should disable collection anyway.
289b4f9fe12SLen Brown 	 */
290b4f9fe12SLen Brown 	if ((block->flags & ACPI_WMI_EXPENSIVE) && ACPI_SUCCESS(wc_status)) {
2918122ab66SZhang Rui 		status = acpi_execute_simple_method(handle, wc_method, 0);
292b4f9fe12SLen Brown 	}
293b4f9fe12SLen Brown 
294b4f9fe12SLen Brown 	return status;
295b4f9fe12SLen Brown }
296b4f9fe12SLen Brown EXPORT_SYMBOL_GPL(wmi_query_block);
297b4f9fe12SLen Brown 
298b4f9fe12SLen Brown /**
299b4f9fe12SLen Brown  * wmi_set_block - Write to a WMI block
300b4f9fe12SLen Brown  * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba
301b4f9fe12SLen Brown  * @instance: Instance index
302b4f9fe12SLen Brown  * &in: Buffer containing new values for the data block
303b4f9fe12SLen Brown  *
304b4f9fe12SLen Brown  * Write the contents of the input buffer to an ACPI-WMI data block
305b4f9fe12SLen Brown  */
306b4f9fe12SLen Brown acpi_status wmi_set_block(const char *guid_string, u8 instance,
307b4f9fe12SLen Brown const struct acpi_buffer *in)
308b4f9fe12SLen Brown {
309b4f9fe12SLen Brown 	struct guid_block *block = NULL;
310b4f9fe12SLen Brown 	struct wmi_block *wblock = NULL;
311b4f9fe12SLen Brown 	acpi_handle handle;
312b4f9fe12SLen Brown 	struct acpi_object_list input;
313b4f9fe12SLen Brown 	union acpi_object params[2];
314f3d83e24SCostantino Leandro 	char method[5] = "WS";
315b4f9fe12SLen Brown 
316b4f9fe12SLen Brown 	if (!guid_string || !in)
317b4f9fe12SLen Brown 		return AE_BAD_DATA;
318b4f9fe12SLen Brown 
319b4f9fe12SLen Brown 	if (!find_guid(guid_string, &wblock))
320b4f9fe12SLen Brown 		return AE_ERROR;
321b4f9fe12SLen Brown 
322b4f9fe12SLen Brown 	block = &wblock->gblock;
323b0e86302SAndy Lutomirski 	handle = wblock->acpi_device->handle;
324b4f9fe12SLen Brown 
325b4f9fe12SLen Brown 	if (block->instance_count < instance)
326b4f9fe12SLen Brown 		return AE_BAD_PARAMETER;
327b4f9fe12SLen Brown 
328b4f9fe12SLen Brown 	/* Check GUID is a data block */
329b4f9fe12SLen Brown 	if (block->flags & (ACPI_WMI_EVENT | ACPI_WMI_METHOD))
330b4f9fe12SLen Brown 		return AE_ERROR;
331b4f9fe12SLen Brown 
332b4f9fe12SLen Brown 	input.count = 2;
333b4f9fe12SLen Brown 	input.pointer = params;
334b4f9fe12SLen Brown 	params[0].type = ACPI_TYPE_INTEGER;
335b4f9fe12SLen Brown 	params[0].integer.value = instance;
336b4f9fe12SLen Brown 
337b4f9fe12SLen Brown 	if (block->flags & ACPI_WMI_STRING) {
338b4f9fe12SLen Brown 		params[1].type = ACPI_TYPE_STRING;
339b4f9fe12SLen Brown 	} else {
340b4f9fe12SLen Brown 		params[1].type = ACPI_TYPE_BUFFER;
341b4f9fe12SLen Brown 	}
342b4f9fe12SLen Brown 	params[1].buffer.length = in->length;
343b4f9fe12SLen Brown 	params[1].buffer.pointer = in->pointer;
344b4f9fe12SLen Brown 
345b4f9fe12SLen Brown 	strncat(method, block->object_id, 2);
346b4f9fe12SLen Brown 
347b4f9fe12SLen Brown 	return acpi_evaluate_object(handle, method, &input, NULL);
348b4f9fe12SLen Brown }
349b4f9fe12SLen Brown EXPORT_SYMBOL_GPL(wmi_set_block);
350b4f9fe12SLen Brown 
35137830662SDmitry Torokhov static void wmi_dump_wdg(const struct guid_block *g)
352a929aae0SThomas Renninger {
35385b4e4ebSRasmus Villemoes 	pr_info("%pUL:\n", g->guid);
3548e07514dSDmitry Torokhov 	pr_info("\tobject_id: %c%c\n", g->object_id[0], g->object_id[1]);
3558e07514dSDmitry Torokhov 	pr_info("\tnotify_id: %02X\n", g->notify_id);
3568e07514dSDmitry Torokhov 	pr_info("\treserved: %02X\n", g->reserved);
3578e07514dSDmitry Torokhov 	pr_info("\tinstance_count: %d\n", g->instance_count);
3588e07514dSDmitry Torokhov 	pr_info("\tflags: %#x", g->flags);
359a929aae0SThomas Renninger 	if (g->flags) {
360a929aae0SThomas Renninger 		if (g->flags & ACPI_WMI_EXPENSIVE)
3618e07514dSDmitry Torokhov 			pr_cont(" ACPI_WMI_EXPENSIVE");
362a929aae0SThomas Renninger 		if (g->flags & ACPI_WMI_METHOD)
3638e07514dSDmitry Torokhov 			pr_cont(" ACPI_WMI_METHOD");
364a929aae0SThomas Renninger 		if (g->flags & ACPI_WMI_STRING)
3658e07514dSDmitry Torokhov 			pr_cont(" ACPI_WMI_STRING");
366a929aae0SThomas Renninger 		if (g->flags & ACPI_WMI_EVENT)
3678e07514dSDmitry Torokhov 			pr_cont(" ACPI_WMI_EVENT");
368a929aae0SThomas Renninger 	}
3698e07514dSDmitry Torokhov 	pr_cont("\n");
370a929aae0SThomas Renninger 
371a929aae0SThomas Renninger }
372a929aae0SThomas Renninger 
373fc3155b2SThomas Renninger static void wmi_notify_debug(u32 value, void *context)
374fc3155b2SThomas Renninger {
375fc3155b2SThomas Renninger 	struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL };
376fc3155b2SThomas Renninger 	union acpi_object *obj;
3771492616aSAxel Lin 	acpi_status status;
378fc3155b2SThomas Renninger 
3791492616aSAxel Lin 	status = wmi_get_event_data(value, &response);
3801492616aSAxel Lin 	if (status != AE_OK) {
3818e07514dSDmitry Torokhov 		pr_info("bad event status 0x%x\n", status);
3821492616aSAxel Lin 		return;
3831492616aSAxel Lin 	}
384fc3155b2SThomas Renninger 
385fc3155b2SThomas Renninger 	obj = (union acpi_object *)response.pointer;
386fc3155b2SThomas Renninger 
387fc3155b2SThomas Renninger 	if (!obj)
388fc3155b2SThomas Renninger 		return;
389fc3155b2SThomas Renninger 
3908e07514dSDmitry Torokhov 	pr_info("DEBUG Event ");
391fc3155b2SThomas Renninger 	switch(obj->type) {
392fc3155b2SThomas Renninger 	case ACPI_TYPE_BUFFER:
3938e07514dSDmitry Torokhov 		pr_cont("BUFFER_TYPE - length %d\n", obj->buffer.length);
394fc3155b2SThomas Renninger 		break;
395fc3155b2SThomas Renninger 	case ACPI_TYPE_STRING:
3968e07514dSDmitry Torokhov 		pr_cont("STRING_TYPE - %s\n", obj->string.pointer);
397fc3155b2SThomas Renninger 		break;
398fc3155b2SThomas Renninger 	case ACPI_TYPE_INTEGER:
3998e07514dSDmitry Torokhov 		pr_cont("INTEGER_TYPE - %llu\n", obj->integer.value);
400fc3155b2SThomas Renninger 		break;
401fc3155b2SThomas Renninger 	case ACPI_TYPE_PACKAGE:
4028e07514dSDmitry Torokhov 		pr_cont("PACKAGE_TYPE - %d elements\n", obj->package.count);
403fc3155b2SThomas Renninger 		break;
404fc3155b2SThomas Renninger 	default:
4058e07514dSDmitry Torokhov 		pr_cont("object type 0x%X\n", obj->type);
406fc3155b2SThomas Renninger 	}
4071492616aSAxel Lin 	kfree(obj);
408fc3155b2SThomas Renninger }
409fc3155b2SThomas Renninger 
410b4f9fe12SLen Brown /**
411b4f9fe12SLen Brown  * wmi_install_notify_handler - Register handler for WMI events
412b4f9fe12SLen Brown  * @handler: Function to handle notifications
413b4f9fe12SLen Brown  * @data: Data to be returned to handler when event is fired
414b4f9fe12SLen Brown  *
415b4f9fe12SLen Brown  * Register a handler for events sent to the ACPI-WMI mapper device.
416b4f9fe12SLen Brown  */
417b4f9fe12SLen Brown acpi_status wmi_install_notify_handler(const char *guid,
418b4f9fe12SLen Brown wmi_notify_handler handler, void *data)
419b4f9fe12SLen Brown {
420b4f9fe12SLen Brown 	struct wmi_block *block;
42158f6425eSColin King 	acpi_status status = AE_NOT_EXIST;
422538d7eb8SAndy Shevchenko 	uuid_le guid_input;
42358f6425eSColin King 	struct list_head *p;
424b4f9fe12SLen Brown 
425b4f9fe12SLen Brown 	if (!guid || !handler)
426b4f9fe12SLen Brown 		return AE_BAD_PARAMETER;
427b4f9fe12SLen Brown 
428538d7eb8SAndy Shevchenko 	if (uuid_le_to_bin(guid, &guid_input))
429538d7eb8SAndy Shevchenko 		return AE_BAD_PARAMETER;
430b4f9fe12SLen Brown 
43158f6425eSColin King 	list_for_each(p, &wmi_block_list) {
43258f6425eSColin King 		acpi_status wmi_status;
43358f6425eSColin King 		block = list_entry(p, struct wmi_block, list);
43458f6425eSColin King 
435538d7eb8SAndy Shevchenko 		if (memcmp(block->gblock.guid, &guid_input, 16) == 0) {
43658f6425eSColin King 			if (block->handler &&
43758f6425eSColin King 			    block->handler != wmi_notify_debug)
438b4f9fe12SLen Brown 				return AE_ALREADY_ACQUIRED;
439b4f9fe12SLen Brown 
440b4f9fe12SLen Brown 			block->handler = handler;
441b4f9fe12SLen Brown 			block->handler_data = data;
442b4f9fe12SLen Brown 
44358f6425eSColin King 			wmi_status = wmi_method_enable(block, 1);
44458f6425eSColin King 			if ((wmi_status != AE_OK) ||
44558f6425eSColin King 			    ((wmi_status == AE_OK) && (status == AE_NOT_EXIST)))
44658f6425eSColin King 				status = wmi_status;
44758f6425eSColin King 		}
44858f6425eSColin King 	}
449b4f9fe12SLen Brown 
450b4f9fe12SLen Brown 	return status;
451b4f9fe12SLen Brown }
452b4f9fe12SLen Brown EXPORT_SYMBOL_GPL(wmi_install_notify_handler);
453b4f9fe12SLen Brown 
454b4f9fe12SLen Brown /**
455b4f9fe12SLen Brown  * wmi_uninstall_notify_handler - Unregister handler for WMI events
456b4f9fe12SLen Brown  *
457b4f9fe12SLen Brown  * Unregister handler for events sent to the ACPI-WMI mapper device.
458b4f9fe12SLen Brown  */
459b4f9fe12SLen Brown acpi_status wmi_remove_notify_handler(const char *guid)
460b4f9fe12SLen Brown {
461b4f9fe12SLen Brown 	struct wmi_block *block;
46258f6425eSColin King 	acpi_status status = AE_NOT_EXIST;
463538d7eb8SAndy Shevchenko 	uuid_le guid_input;
46458f6425eSColin King 	struct list_head *p;
465b4f9fe12SLen Brown 
466b4f9fe12SLen Brown 	if (!guid)
467b4f9fe12SLen Brown 		return AE_BAD_PARAMETER;
468b4f9fe12SLen Brown 
469538d7eb8SAndy Shevchenko 	if (uuid_le_to_bin(guid, &guid_input))
470538d7eb8SAndy Shevchenko 		return AE_BAD_PARAMETER;
471b4f9fe12SLen Brown 
47258f6425eSColin King 	list_for_each(p, &wmi_block_list) {
47358f6425eSColin King 		acpi_status wmi_status;
47458f6425eSColin King 		block = list_entry(p, struct wmi_block, list);
47558f6425eSColin King 
476538d7eb8SAndy Shevchenko 		if (memcmp(block->gblock.guid, &guid_input, 16) == 0) {
47758f6425eSColin King 			if (!block->handler ||
47858f6425eSColin King 			    block->handler == wmi_notify_debug)
479b4f9fe12SLen Brown 				return AE_NULL_ENTRY;
480b4f9fe12SLen Brown 
481fc3155b2SThomas Renninger 			if (debug_event) {
482fc3155b2SThomas Renninger 				block->handler = wmi_notify_debug;
48358f6425eSColin King 				status = AE_OK;
484fc3155b2SThomas Renninger 			} else {
48558f6425eSColin King 				wmi_status = wmi_method_enable(block, 0);
486b4f9fe12SLen Brown 				block->handler = NULL;
487b4f9fe12SLen Brown 				block->handler_data = NULL;
48858f6425eSColin King 				if ((wmi_status != AE_OK) ||
48958f6425eSColin King 				    ((wmi_status == AE_OK) &&
49058f6425eSColin King 				     (status == AE_NOT_EXIST)))
49158f6425eSColin King 					status = wmi_status;
492fc3155b2SThomas Renninger 			}
49358f6425eSColin King 		}
49458f6425eSColin King 	}
49558f6425eSColin King 
496b4f9fe12SLen Brown 	return status;
497b4f9fe12SLen Brown }
498b4f9fe12SLen Brown EXPORT_SYMBOL_GPL(wmi_remove_notify_handler);
499b4f9fe12SLen Brown 
500b4f9fe12SLen Brown /**
501b4f9fe12SLen Brown  * wmi_get_event_data - Get WMI data associated with an event
502b4f9fe12SLen Brown  *
5033e9b988eSAnisse Astier  * @event: Event to find
5043e9b988eSAnisse Astier  * @out: Buffer to hold event data. out->pointer should be freed with kfree()
505b4f9fe12SLen Brown  *
506b4f9fe12SLen Brown  * Returns extra data associated with an event in WMI.
507b4f9fe12SLen Brown  */
508b4f9fe12SLen Brown acpi_status wmi_get_event_data(u32 event, struct acpi_buffer *out)
509b4f9fe12SLen Brown {
510b4f9fe12SLen Brown 	struct acpi_object_list input;
511b4f9fe12SLen Brown 	union acpi_object params[1];
512b4f9fe12SLen Brown 	struct guid_block *gblock;
513b4f9fe12SLen Brown 	struct wmi_block *wblock;
514b4f9fe12SLen Brown 	struct list_head *p;
515b4f9fe12SLen Brown 
516b4f9fe12SLen Brown 	input.count = 1;
517b4f9fe12SLen Brown 	input.pointer = params;
518b4f9fe12SLen Brown 	params[0].type = ACPI_TYPE_INTEGER;
519b4f9fe12SLen Brown 	params[0].integer.value = event;
520b4f9fe12SLen Brown 
521762e1a2fSDmitry Torokhov 	list_for_each(p, &wmi_block_list) {
522b4f9fe12SLen Brown 		wblock = list_entry(p, struct wmi_block, list);
523b4f9fe12SLen Brown 		gblock = &wblock->gblock;
524b4f9fe12SLen Brown 
525b4f9fe12SLen Brown 		if ((gblock->flags & ACPI_WMI_EVENT) &&
526b4f9fe12SLen Brown 			(gblock->notify_id == event))
527b0e86302SAndy Lutomirski 			return acpi_evaluate_object(wblock->acpi_device->handle,
528b0e86302SAndy Lutomirski 				"_WED", &input, out);
529b4f9fe12SLen Brown 	}
530b4f9fe12SLen Brown 
531b4f9fe12SLen Brown 	return AE_NOT_FOUND;
532b4f9fe12SLen Brown }
533b4f9fe12SLen Brown EXPORT_SYMBOL_GPL(wmi_get_event_data);
534b4f9fe12SLen Brown 
535b4f9fe12SLen Brown /**
536b4f9fe12SLen Brown  * wmi_has_guid - Check if a GUID is available
537b4f9fe12SLen Brown  * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba
538b4f9fe12SLen Brown  *
539b4f9fe12SLen Brown  * Check if a given GUID is defined by _WDG
540b4f9fe12SLen Brown  */
541b4f9fe12SLen Brown bool wmi_has_guid(const char *guid_string)
542b4f9fe12SLen Brown {
543b4f9fe12SLen Brown 	return find_guid(guid_string, NULL);
544b4f9fe12SLen Brown }
545b4f9fe12SLen Brown EXPORT_SYMBOL_GPL(wmi_has_guid);
546b4f9fe12SLen Brown 
547844af950SAndy Lutomirski static struct wmi_block *dev_to_wblock(struct device *dev)
548844af950SAndy Lutomirski {
549844af950SAndy Lutomirski 	return container_of(dev, struct wmi_block, dev.dev);
550844af950SAndy Lutomirski }
551844af950SAndy Lutomirski 
552844af950SAndy Lutomirski static struct wmi_device *dev_to_wdev(struct device *dev)
553844af950SAndy Lutomirski {
554844af950SAndy Lutomirski 	return container_of(dev, struct wmi_device, dev);
555844af950SAndy Lutomirski }
556844af950SAndy Lutomirski 
557b4f9fe12SLen Brown /*
5581caab3c1SMatthew Garrett  * sysfs interface
5591caab3c1SMatthew Garrett  */
560614ef432SDmitry Torokhov static ssize_t modalias_show(struct device *dev, struct device_attribute *attr,
5611caab3c1SMatthew Garrett 			     char *buf)
5621caab3c1SMatthew Garrett {
563844af950SAndy Lutomirski 	struct wmi_block *wblock = dev_to_wblock(dev);
5641caab3c1SMatthew Garrett 
56585b4e4ebSRasmus Villemoes 	return sprintf(buf, "wmi:%pUL\n", wblock->gblock.guid);
5661caab3c1SMatthew Garrett }
567e80b89a5SGreg Kroah-Hartman static DEVICE_ATTR_RO(modalias);
568614ef432SDmitry Torokhov 
569844af950SAndy Lutomirski static ssize_t guid_show(struct device *dev, struct device_attribute *attr,
570844af950SAndy Lutomirski 			 char *buf)
571844af950SAndy Lutomirski {
572844af950SAndy Lutomirski 	struct wmi_block *wblock = dev_to_wblock(dev);
573844af950SAndy Lutomirski 
574844af950SAndy Lutomirski 	return sprintf(buf, "%pUL\n", wblock->gblock.guid);
575844af950SAndy Lutomirski }
576844af950SAndy Lutomirski static DEVICE_ATTR_RO(guid);
577844af950SAndy Lutomirski 
578e80b89a5SGreg Kroah-Hartman static struct attribute *wmi_attrs[] = {
579e80b89a5SGreg Kroah-Hartman 	&dev_attr_modalias.attr,
580844af950SAndy Lutomirski 	&dev_attr_guid.attr,
581e80b89a5SGreg Kroah-Hartman 	NULL,
582614ef432SDmitry Torokhov };
583e80b89a5SGreg Kroah-Hartman ATTRIBUTE_GROUPS(wmi);
5841caab3c1SMatthew Garrett 
5851caab3c1SMatthew Garrett static int wmi_dev_uevent(struct device *dev, struct kobj_uevent_env *env)
5861caab3c1SMatthew Garrett {
587844af950SAndy Lutomirski 	struct wmi_block *wblock = dev_to_wblock(dev);
5881caab3c1SMatthew Garrett 
589844af950SAndy Lutomirski 	if (add_uevent_var(env, "MODALIAS=wmi:%pUL", wblock->gblock.guid))
5901caab3c1SMatthew Garrett 		return -ENOMEM;
5911caab3c1SMatthew Garrett 
592844af950SAndy Lutomirski 	if (add_uevent_var(env, "WMI_GUID=%pUL", wblock->gblock.guid))
5931caab3c1SMatthew Garrett 		return -ENOMEM;
5941caab3c1SMatthew Garrett 
5951caab3c1SMatthew Garrett 	return 0;
5961caab3c1SMatthew Garrett }
5971caab3c1SMatthew Garrett 
598844af950SAndy Lutomirski static void wmi_dev_release(struct device *dev)
5991caab3c1SMatthew Garrett {
600844af950SAndy Lutomirski 	struct wmi_block *wblock = dev_to_wblock(dev);
601c64eefd4SDmitry Torokhov 
602844af950SAndy Lutomirski 	kfree(wblock);
6031caab3c1SMatthew Garrett }
6041caab3c1SMatthew Garrett 
605844af950SAndy Lutomirski static int wmi_dev_match(struct device *dev, struct device_driver *driver)
606844af950SAndy Lutomirski {
607844af950SAndy Lutomirski 	struct wmi_driver *wmi_driver =
608844af950SAndy Lutomirski 		container_of(driver, struct wmi_driver, driver);
609844af950SAndy Lutomirski 	struct wmi_block *wblock = dev_to_wblock(dev);
610844af950SAndy Lutomirski 	const struct wmi_device_id *id = wmi_driver->id_table;
611844af950SAndy Lutomirski 
612844af950SAndy Lutomirski 	while (id->guid_string) {
613844af950SAndy Lutomirski 		uuid_le driver_guid;
614844af950SAndy Lutomirski 
615844af950SAndy Lutomirski 		if (WARN_ON(uuid_le_to_bin(id->guid_string, &driver_guid)))
616844af950SAndy Lutomirski 			continue;
617844af950SAndy Lutomirski 		if (!memcmp(&driver_guid, wblock->gblock.guid, 16))
618844af950SAndy Lutomirski 			return 1;
619844af950SAndy Lutomirski 
620844af950SAndy Lutomirski 		id++;
621844af950SAndy Lutomirski 	}
622844af950SAndy Lutomirski 
623844af950SAndy Lutomirski 	return 0;
624844af950SAndy Lutomirski }
625844af950SAndy Lutomirski 
626844af950SAndy Lutomirski static int wmi_dev_probe(struct device *dev)
627844af950SAndy Lutomirski {
628844af950SAndy Lutomirski 	struct wmi_block *wblock = dev_to_wblock(dev);
629844af950SAndy Lutomirski 	struct wmi_driver *wdriver =
630844af950SAndy Lutomirski 		container_of(dev->driver, struct wmi_driver, driver);
631844af950SAndy Lutomirski 	int ret = 0;
632844af950SAndy Lutomirski 
633844af950SAndy Lutomirski 	if (ACPI_FAILURE(wmi_method_enable(wblock, 1)))
634844af950SAndy Lutomirski 		dev_warn(dev, "failed to enable device -- probing anyway\n");
635844af950SAndy Lutomirski 
636844af950SAndy Lutomirski 	if (wdriver->probe) {
637844af950SAndy Lutomirski 		ret = wdriver->probe(dev_to_wdev(dev));
638844af950SAndy Lutomirski 		if (ret != 0 && ACPI_FAILURE(wmi_method_enable(wblock, 0)))
639844af950SAndy Lutomirski 			dev_warn(dev, "failed to disable device\n");
640844af950SAndy Lutomirski 	}
641844af950SAndy Lutomirski 
642844af950SAndy Lutomirski 	return ret;
643844af950SAndy Lutomirski }
644844af950SAndy Lutomirski 
645844af950SAndy Lutomirski static int wmi_dev_remove(struct device *dev)
646844af950SAndy Lutomirski {
647844af950SAndy Lutomirski 	struct wmi_block *wblock = dev_to_wblock(dev);
648844af950SAndy Lutomirski 	struct wmi_driver *wdriver =
649844af950SAndy Lutomirski 		container_of(dev->driver, struct wmi_driver, driver);
650844af950SAndy Lutomirski 	int ret = 0;
651844af950SAndy Lutomirski 
652844af950SAndy Lutomirski 	if (wdriver->remove)
653844af950SAndy Lutomirski 		ret = wdriver->remove(dev_to_wdev(dev));
654844af950SAndy Lutomirski 
655844af950SAndy Lutomirski 	if (ACPI_FAILURE(wmi_method_enable(wblock, 0)))
656844af950SAndy Lutomirski 		dev_warn(dev, "failed to disable device\n");
657844af950SAndy Lutomirski 
658844af950SAndy Lutomirski 	return ret;
659844af950SAndy Lutomirski }
660844af950SAndy Lutomirski 
661844af950SAndy Lutomirski static struct class wmi_bus_class = {
662844af950SAndy Lutomirski 	.name = "wmi_bus",
6631caab3c1SMatthew Garrett };
6641caab3c1SMatthew Garrett 
665844af950SAndy Lutomirski static struct bus_type wmi_bus_type = {
666844af950SAndy Lutomirski 	.name = "wmi",
667844af950SAndy Lutomirski 	.dev_groups = wmi_groups,
668844af950SAndy Lutomirski 	.match = wmi_dev_match,
669844af950SAndy Lutomirski 	.uevent = wmi_dev_uevent,
670844af950SAndy Lutomirski 	.probe = wmi_dev_probe,
671844af950SAndy Lutomirski 	.remove = wmi_dev_remove,
672844af950SAndy Lutomirski };
673844af950SAndy Lutomirski 
674844af950SAndy Lutomirski static int wmi_create_device(struct device *wmi_bus_dev,
675844af950SAndy Lutomirski 			     const struct guid_block *gblock,
6767f5809bfSAndy Lutomirski 			     struct wmi_block *wblock,
6777f5809bfSAndy Lutomirski 			     struct acpi_device *device)
6781caab3c1SMatthew Garrett {
679844af950SAndy Lutomirski 	wblock->dev.dev.bus = &wmi_bus_type;
680844af950SAndy Lutomirski 	wblock->dev.dev.parent = wmi_bus_dev;
6811caab3c1SMatthew Garrett 
682844af950SAndy Lutomirski 	dev_set_name(&wblock->dev.dev, "%pUL", gblock->guid);
6831caab3c1SMatthew Garrett 
684844af950SAndy Lutomirski 	wblock->dev.dev.release = wmi_dev_release;
685c64eefd4SDmitry Torokhov 
686844af950SAndy Lutomirski 	return device_register(&wblock->dev.dev);
6871caab3c1SMatthew Garrett }
6881caab3c1SMatthew Garrett 
689b0e86302SAndy Lutomirski static void wmi_free_devices(struct acpi_device *device)
6901caab3c1SMatthew Garrett {
691c64eefd4SDmitry Torokhov 	struct wmi_block *wblock, *next;
6921caab3c1SMatthew Garrett 
6931caab3c1SMatthew Garrett 	/* Delete devices for all the GUIDs */
694023b9565SDmitry Torokhov 	list_for_each_entry_safe(wblock, next, &wmi_block_list, list) {
695b0e86302SAndy Lutomirski 		if (wblock->acpi_device == device) {
696023b9565SDmitry Torokhov 			list_del(&wblock->list);
697844af950SAndy Lutomirski 			if (wblock->dev.dev.bus)
698844af950SAndy Lutomirski 				device_unregister(&wblock->dev.dev);
699023b9565SDmitry Torokhov 			else
700023b9565SDmitry Torokhov 				kfree(wblock);
701023b9565SDmitry Torokhov 		}
7021caab3c1SMatthew Garrett 	}
703b0e86302SAndy Lutomirski }
7041caab3c1SMatthew Garrett 
705b0e86302SAndy Lutomirski static bool guid_already_parsed(struct acpi_device *device,
706b0e86302SAndy Lutomirski 				const u8 *guid)
707d1f9e497SCarlos Corbacho {
708d1f9e497SCarlos Corbacho 	struct wmi_block *wblock;
709d1f9e497SCarlos Corbacho 
710b0e86302SAndy Lutomirski 	list_for_each_entry(wblock, &wmi_block_list, list) {
711b0e86302SAndy Lutomirski 		if (memcmp(wblock->gblock.guid, guid, 16) == 0) {
712b0e86302SAndy Lutomirski 			/*
713b0e86302SAndy Lutomirski 			 * Because we historically didn't track the relationship
714b0e86302SAndy Lutomirski 			 * between GUIDs and ACPI nodes, we don't know whether
715b0e86302SAndy Lutomirski 			 * we need to suppress GUIDs that are unique on a
716b0e86302SAndy Lutomirski 			 * given node but duplicated across nodes.
717b0e86302SAndy Lutomirski 			 */
718b0e86302SAndy Lutomirski 			dev_warn(&device->dev, "duplicate WMI GUID %pUL (first instance was on %s)\n",
719b0e86302SAndy Lutomirski 				 guid, dev_name(&wblock->acpi_device->dev));
720d1f9e497SCarlos Corbacho 			return true;
721b0e86302SAndy Lutomirski 		}
722b0e86302SAndy Lutomirski 	}
723c64eefd4SDmitry Torokhov 
724d1f9e497SCarlos Corbacho 	return false;
725d1f9e497SCarlos Corbacho }
726d1f9e497SCarlos Corbacho 
7271caab3c1SMatthew Garrett /*
728b4f9fe12SLen Brown  * Parse the _WDG method for the GUID data blocks
729b4f9fe12SLen Brown  */
730844af950SAndy Lutomirski static int parse_wdg(struct device *wmi_bus_dev, struct acpi_device *device)
731b4f9fe12SLen Brown {
732b4f9fe12SLen Brown 	struct acpi_buffer out = {ACPI_ALLOCATE_BUFFER, NULL};
733b4f9fe12SLen Brown 	union acpi_object *obj;
73437830662SDmitry Torokhov 	const struct guid_block *gblock;
735b4f9fe12SLen Brown 	struct wmi_block *wblock;
736b4f9fe12SLen Brown 	acpi_status status;
737c64eefd4SDmitry Torokhov 	int retval;
738b4f9fe12SLen Brown 	u32 i, total;
739b4f9fe12SLen Brown 
7407f5809bfSAndy Lutomirski 	status = acpi_evaluate_object(device->handle, "_WDG", NULL, &out);
741b4f9fe12SLen Brown 	if (ACPI_FAILURE(status))
742c64eefd4SDmitry Torokhov 		return -ENXIO;
743b4f9fe12SLen Brown 
744b4f9fe12SLen Brown 	obj = (union acpi_object *) out.pointer;
7453d2c63ebSDmitry Torokhov 	if (!obj)
746c64eefd4SDmitry Torokhov 		return -ENXIO;
747b4f9fe12SLen Brown 
74864ed0ab8SDmitry Torokhov 	if (obj->type != ACPI_TYPE_BUFFER) {
749c64eefd4SDmitry Torokhov 		retval = -ENXIO;
75064ed0ab8SDmitry Torokhov 		goto out_free_pointer;
75164ed0ab8SDmitry Torokhov 	}
752b4f9fe12SLen Brown 
75337830662SDmitry Torokhov 	gblock = (const struct guid_block *)obj->buffer.pointer;
754b4f9fe12SLen Brown 	total = obj->buffer.length / sizeof(struct guid_block);
755b4f9fe12SLen Brown 
756b4f9fe12SLen Brown 	for (i = 0; i < total; i++) {
757a929aae0SThomas Renninger 		if (debug_dump_wdg)
758a929aae0SThomas Renninger 			wmi_dump_wdg(&gblock[i]);
759a929aae0SThomas Renninger 
76058f6425eSColin King 		wblock = kzalloc(sizeof(struct wmi_block), GFP_KERNEL);
76158f6425eSColin King 		if (!wblock)
7620a018a68SDan Carpenter 			return -ENOMEM;
76358f6425eSColin King 
764b0e86302SAndy Lutomirski 		wblock->acpi_device = device;
76558f6425eSColin King 		wblock->gblock = gblock[i];
76658f6425eSColin King 
76758f6425eSColin King 		/*
76858f6425eSColin King 		  Some WMI devices, like those for nVidia hooks, have a
76958f6425eSColin King 		  duplicate GUID. It's not clear what we should do in this
77058f6425eSColin King 		  case yet, so for now, we'll just ignore the duplicate
77158f6425eSColin King 		  for device creation.
77258f6425eSColin King 		*/
773b0e86302SAndy Lutomirski 		if (!guid_already_parsed(device, gblock[i].guid)) {
774844af950SAndy Lutomirski 			retval = wmi_create_device(wmi_bus_dev, &gblock[i],
775844af950SAndy Lutomirski 						   wblock, device);
77658f6425eSColin King 			if (retval) {
777b0e86302SAndy Lutomirski 				wmi_free_devices(device);
778e1e0dacbSDan Carpenter 				goto out_free_pointer;
779a5167c5bSAxel Lin 			}
78058f6425eSColin King 		}
78158f6425eSColin King 
78258f6425eSColin King 		list_add_tail(&wblock->list, &wmi_block_list);
783b4f9fe12SLen Brown 
784fc3155b2SThomas Renninger 		if (debug_event) {
785fc3155b2SThomas Renninger 			wblock->handler = wmi_notify_debug;
7862d5ab555SDmitry Torokhov 			wmi_method_enable(wblock, 1);
787fc3155b2SThomas Renninger 		}
788b4f9fe12SLen Brown 	}
789b4f9fe12SLen Brown 
790c64eefd4SDmitry Torokhov 	retval = 0;
791c64eefd4SDmitry Torokhov 
792a5167c5bSAxel Lin out_free_pointer:
793a5167c5bSAxel Lin 	kfree(out.pointer);
794b4f9fe12SLen Brown 
795c64eefd4SDmitry Torokhov 	return retval;
796b4f9fe12SLen Brown }
797b4f9fe12SLen Brown 
798b4f9fe12SLen Brown /*
799b4f9fe12SLen Brown  * WMI can have EmbeddedControl access regions. In which case, we just want to
800b4f9fe12SLen Brown  * hand these off to the EC driver.
801b4f9fe12SLen Brown  */
802b4f9fe12SLen Brown static acpi_status
803b4f9fe12SLen Brown acpi_wmi_ec_space_handler(u32 function, acpi_physical_address address,
804439913ffSLin Ming 		      u32 bits, u64 *value,
805b4f9fe12SLen Brown 		      void *handler_context, void *region_context)
806b4f9fe12SLen Brown {
807b4f9fe12SLen Brown 	int result = 0, i = 0;
808b4f9fe12SLen Brown 	u8 temp = 0;
809b4f9fe12SLen Brown 
810b4f9fe12SLen Brown 	if ((address > 0xFF) || !value)
811b4f9fe12SLen Brown 		return AE_BAD_PARAMETER;
812b4f9fe12SLen Brown 
813b4f9fe12SLen Brown 	if (function != ACPI_READ && function != ACPI_WRITE)
814b4f9fe12SLen Brown 		return AE_BAD_PARAMETER;
815b4f9fe12SLen Brown 
816b4f9fe12SLen Brown 	if (bits != 8)
817b4f9fe12SLen Brown 		return AE_BAD_PARAMETER;
818b4f9fe12SLen Brown 
819b4f9fe12SLen Brown 	if (function == ACPI_READ) {
820b4f9fe12SLen Brown 		result = ec_read(address, &temp);
821439913ffSLin Ming 		(*value) |= ((u64)temp) << i;
822b4f9fe12SLen Brown 	} else {
823b4f9fe12SLen Brown 		temp = 0xff & ((*value) >> i);
824b4f9fe12SLen Brown 		result = ec_write(address, temp);
825b4f9fe12SLen Brown 	}
826b4f9fe12SLen Brown 
827b4f9fe12SLen Brown 	switch (result) {
828b4f9fe12SLen Brown 	case -EINVAL:
829b4f9fe12SLen Brown 		return AE_BAD_PARAMETER;
830b4f9fe12SLen Brown 		break;
831b4f9fe12SLen Brown 	case -ENODEV:
832b4f9fe12SLen Brown 		return AE_NOT_FOUND;
833b4f9fe12SLen Brown 		break;
834b4f9fe12SLen Brown 	case -ETIME:
835b4f9fe12SLen Brown 		return AE_TIME;
836b4f9fe12SLen Brown 		break;
837b4f9fe12SLen Brown 	default:
838b4f9fe12SLen Brown 		return AE_OK;
839b4f9fe12SLen Brown 	}
840b4f9fe12SLen Brown }
841b4f9fe12SLen Brown 
842f61bb939SBjorn Helgaas static void acpi_wmi_notify(struct acpi_device *device, u32 event)
843b4f9fe12SLen Brown {
844b4f9fe12SLen Brown 	struct guid_block *block;
845b4f9fe12SLen Brown 	struct wmi_block *wblock;
846b4f9fe12SLen Brown 	struct list_head *p;
847b4f9fe12SLen Brown 
848762e1a2fSDmitry Torokhov 	list_for_each(p, &wmi_block_list) {
849b4f9fe12SLen Brown 		wblock = list_entry(p, struct wmi_block, list);
850b4f9fe12SLen Brown 		block = &wblock->gblock;
851b4f9fe12SLen Brown 
852b0e86302SAndy Lutomirski 		if (wblock->acpi_device == device &&
853b0e86302SAndy Lutomirski 		    (block->flags & ACPI_WMI_EVENT) &&
854b4f9fe12SLen Brown 		    (block->notify_id == event)) {
855b4f9fe12SLen Brown 			if (wblock->handler)
856b4f9fe12SLen Brown 				wblock->handler(event, wblock->handler_data);
8577715348cSThomas Renninger 			if (debug_event) {
85885b4e4ebSRasmus Villemoes 				pr_info("DEBUG Event GUID: %pUL\n",
85985b4e4ebSRasmus Villemoes 					wblock->gblock.guid);
8607715348cSThomas Renninger 			}
861b4f9fe12SLen Brown 
862b4f9fe12SLen Brown 			acpi_bus_generate_netlink_event(
863b4f9fe12SLen Brown 				device->pnp.device_class, dev_name(&device->dev),
864b4f9fe12SLen Brown 				event, 0);
865b4f9fe12SLen Brown 			break;
866b4f9fe12SLen Brown 		}
867b4f9fe12SLen Brown 	}
868b4f9fe12SLen Brown }
869b4f9fe12SLen Brown 
87051fac838SRafael J. Wysocki static int acpi_wmi_remove(struct acpi_device *device)
871b4f9fe12SLen Brown {
872b4f9fe12SLen Brown 	acpi_remove_address_space_handler(device->handle,
873b4f9fe12SLen Brown 				ACPI_ADR_SPACE_EC, &acpi_wmi_ec_space_handler);
874b0e86302SAndy Lutomirski 	wmi_free_devices(device);
875844af950SAndy Lutomirski 	device_unregister((struct device *)acpi_driver_data(device));
876844af950SAndy Lutomirski 	device->driver_data = NULL;
877b4f9fe12SLen Brown 
878b4f9fe12SLen Brown 	return 0;
879b4f9fe12SLen Brown }
880b4f9fe12SLen Brown 
881925b1089SThomas Renninger static int acpi_wmi_add(struct acpi_device *device)
882b4f9fe12SLen Brown {
883844af950SAndy Lutomirski 	struct device *wmi_bus_dev;
884b4f9fe12SLen Brown 	acpi_status status;
885c64eefd4SDmitry Torokhov 	int error;
886b4f9fe12SLen Brown 
887b4f9fe12SLen Brown 	status = acpi_install_address_space_handler(device->handle,
888b4f9fe12SLen Brown 						    ACPI_ADR_SPACE_EC,
889b4f9fe12SLen Brown 						    &acpi_wmi_ec_space_handler,
890b4f9fe12SLen Brown 						    NULL, NULL);
8915212cd67SDmitry Torokhov 	if (ACPI_FAILURE(status)) {
89246492ee4SAndy Lutomirski 		dev_err(&device->dev, "Error installing EC region handler\n");
893b4f9fe12SLen Brown 		return -ENODEV;
8945212cd67SDmitry Torokhov 	}
895b4f9fe12SLen Brown 
896844af950SAndy Lutomirski 	wmi_bus_dev = device_create(&wmi_bus_class, &device->dev, MKDEV(0, 0),
897844af950SAndy Lutomirski 				    NULL, "wmi_bus-%s", dev_name(&device->dev));
898844af950SAndy Lutomirski 	if (IS_ERR(wmi_bus_dev)) {
899844af950SAndy Lutomirski 		error = PTR_ERR(wmi_bus_dev);
900844af950SAndy Lutomirski 		goto err_remove_handler;
901844af950SAndy Lutomirski 	}
902844af950SAndy Lutomirski 	device->driver_data = wmi_bus_dev;
903844af950SAndy Lutomirski 
904844af950SAndy Lutomirski 	error = parse_wdg(wmi_bus_dev, device);
905c64eefd4SDmitry Torokhov 	if (error) {
9068e07514dSDmitry Torokhov 		pr_err("Failed to parse WDG method\n");
907844af950SAndy Lutomirski 		goto err_remove_busdev;
908b4f9fe12SLen Brown 	}
909b4f9fe12SLen Brown 
910c64eefd4SDmitry Torokhov 	return 0;
91146492ee4SAndy Lutomirski 
912844af950SAndy Lutomirski err_remove_busdev:
913844af950SAndy Lutomirski 	device_unregister(wmi_bus_dev);
914844af950SAndy Lutomirski 
91546492ee4SAndy Lutomirski err_remove_handler:
91646492ee4SAndy Lutomirski 	acpi_remove_address_space_handler(device->handle,
91746492ee4SAndy Lutomirski 					  ACPI_ADR_SPACE_EC,
91846492ee4SAndy Lutomirski 					  &acpi_wmi_ec_space_handler);
91946492ee4SAndy Lutomirski 
92046492ee4SAndy Lutomirski 	return error;
921b4f9fe12SLen Brown }
922b4f9fe12SLen Brown 
923844af950SAndy Lutomirski int __must_check __wmi_driver_register(struct wmi_driver *driver,
924844af950SAndy Lutomirski 				       struct module *owner)
925844af950SAndy Lutomirski {
926844af950SAndy Lutomirski 	driver->driver.owner = owner;
927844af950SAndy Lutomirski 	driver->driver.bus = &wmi_bus_type;
928844af950SAndy Lutomirski 
929844af950SAndy Lutomirski 	return driver_register(&driver->driver);
930844af950SAndy Lutomirski }
931844af950SAndy Lutomirski EXPORT_SYMBOL(__wmi_driver_register);
932844af950SAndy Lutomirski 
933844af950SAndy Lutomirski void wmi_driver_unregister(struct wmi_driver *driver)
934844af950SAndy Lutomirski {
935844af950SAndy Lutomirski 	driver_unregister(&driver->driver);
936844af950SAndy Lutomirski }
937844af950SAndy Lutomirski EXPORT_SYMBOL(wmi_driver_unregister);
938844af950SAndy Lutomirski 
939b4f9fe12SLen Brown static int __init acpi_wmi_init(void)
940b4f9fe12SLen Brown {
941c64eefd4SDmitry Torokhov 	int error;
942b4f9fe12SLen Brown 
943b4f9fe12SLen Brown 	if (acpi_disabled)
944b4f9fe12SLen Brown 		return -ENODEV;
945b4f9fe12SLen Brown 
946844af950SAndy Lutomirski 	error = class_register(&wmi_bus_class);
947c64eefd4SDmitry Torokhov 	if (error)
948c64eefd4SDmitry Torokhov 		return error;
949b4f9fe12SLen Brown 
950844af950SAndy Lutomirski 	error = bus_register(&wmi_bus_type);
951844af950SAndy Lutomirski 	if (error)
952844af950SAndy Lutomirski 		goto err_unreg_class;
953844af950SAndy Lutomirski 
954c64eefd4SDmitry Torokhov 	error = acpi_bus_register_driver(&acpi_wmi_driver);
955c64eefd4SDmitry Torokhov 	if (error) {
956c64eefd4SDmitry Torokhov 		pr_err("Error loading mapper\n");
957844af950SAndy Lutomirski 		goto err_unreg_bus;
9581caab3c1SMatthew Garrett 	}
9591caab3c1SMatthew Garrett 
9608e07514dSDmitry Torokhov 	return 0;
961844af950SAndy Lutomirski 
962844af950SAndy Lutomirski err_unreg_class:
963844af950SAndy Lutomirski 	class_unregister(&wmi_bus_class);
964844af950SAndy Lutomirski 
965844af950SAndy Lutomirski err_unreg_bus:
966844af950SAndy Lutomirski 	bus_unregister(&wmi_bus_type);
967844af950SAndy Lutomirski 
968844af950SAndy Lutomirski 	return error;
969b4f9fe12SLen Brown }
970b4f9fe12SLen Brown 
971b4f9fe12SLen Brown static void __exit acpi_wmi_exit(void)
972b4f9fe12SLen Brown {
973b4f9fe12SLen Brown 	acpi_bus_unregister_driver(&acpi_wmi_driver);
974844af950SAndy Lutomirski 	class_unregister(&wmi_bus_class);
975844af950SAndy Lutomirski 	bus_unregister(&wmi_bus_type);
976b4f9fe12SLen Brown }
977b4f9fe12SLen Brown 
978b4f9fe12SLen Brown subsys_initcall(acpi_wmi_init);
979b4f9fe12SLen Brown module_exit(acpi_wmi_exit);
980