xref: /openbmc/linux/drivers/platform/x86/wmi.c (revision a1c31bcd)
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 
760a1c31bcdSAndy Lutomirski 		/*
761a1c31bcdSAndy Lutomirski 		 * Some WMI devices, like those for nVidia hooks, have a
762a1c31bcdSAndy Lutomirski 		 * duplicate GUID. It's not clear what we should do in this
763a1c31bcdSAndy Lutomirski 		 * case yet, so for now, we'll just ignore the duplicate
764a1c31bcdSAndy Lutomirski 		 * for device creation.
765a1c31bcdSAndy Lutomirski 		 */
766a1c31bcdSAndy Lutomirski 		if (guid_already_parsed(device, gblock[i].guid))
767a1c31bcdSAndy Lutomirski 			continue;
768a1c31bcdSAndy Lutomirski 
76958f6425eSColin King 		wblock = kzalloc(sizeof(struct wmi_block), GFP_KERNEL);
77058f6425eSColin King 		if (!wblock)
7710a018a68SDan Carpenter 			return -ENOMEM;
77258f6425eSColin King 
773b0e86302SAndy Lutomirski 		wblock->acpi_device = device;
77458f6425eSColin King 		wblock->gblock = gblock[i];
77558f6425eSColin King 
776844af950SAndy Lutomirski 		retval = wmi_create_device(wmi_bus_dev, &gblock[i],
777844af950SAndy Lutomirski 					   wblock, device);
77858f6425eSColin King 		if (retval) {
779a1c31bcdSAndy Lutomirski 			put_device(&wblock->dev.dev);
780b0e86302SAndy Lutomirski 			wmi_free_devices(device);
781e1e0dacbSDan Carpenter 			goto out_free_pointer;
782a5167c5bSAxel Lin 		}
78358f6425eSColin King 
78458f6425eSColin King 		list_add_tail(&wblock->list, &wmi_block_list);
785b4f9fe12SLen Brown 
786fc3155b2SThomas Renninger 		if (debug_event) {
787fc3155b2SThomas Renninger 			wblock->handler = wmi_notify_debug;
7882d5ab555SDmitry Torokhov 			wmi_method_enable(wblock, 1);
789fc3155b2SThomas Renninger 		}
790b4f9fe12SLen Brown 	}
791b4f9fe12SLen Brown 
792c64eefd4SDmitry Torokhov 	retval = 0;
793c64eefd4SDmitry Torokhov 
794a5167c5bSAxel Lin out_free_pointer:
795a5167c5bSAxel Lin 	kfree(out.pointer);
796b4f9fe12SLen Brown 
797c64eefd4SDmitry Torokhov 	return retval;
798b4f9fe12SLen Brown }
799b4f9fe12SLen Brown 
800b4f9fe12SLen Brown /*
801b4f9fe12SLen Brown  * WMI can have EmbeddedControl access regions. In which case, we just want to
802b4f9fe12SLen Brown  * hand these off to the EC driver.
803b4f9fe12SLen Brown  */
804b4f9fe12SLen Brown static acpi_status
805b4f9fe12SLen Brown acpi_wmi_ec_space_handler(u32 function, acpi_physical_address address,
806439913ffSLin Ming 		      u32 bits, u64 *value,
807b4f9fe12SLen Brown 		      void *handler_context, void *region_context)
808b4f9fe12SLen Brown {
809b4f9fe12SLen Brown 	int result = 0, i = 0;
810b4f9fe12SLen Brown 	u8 temp = 0;
811b4f9fe12SLen Brown 
812b4f9fe12SLen Brown 	if ((address > 0xFF) || !value)
813b4f9fe12SLen Brown 		return AE_BAD_PARAMETER;
814b4f9fe12SLen Brown 
815b4f9fe12SLen Brown 	if (function != ACPI_READ && function != ACPI_WRITE)
816b4f9fe12SLen Brown 		return AE_BAD_PARAMETER;
817b4f9fe12SLen Brown 
818b4f9fe12SLen Brown 	if (bits != 8)
819b4f9fe12SLen Brown 		return AE_BAD_PARAMETER;
820b4f9fe12SLen Brown 
821b4f9fe12SLen Brown 	if (function == ACPI_READ) {
822b4f9fe12SLen Brown 		result = ec_read(address, &temp);
823439913ffSLin Ming 		(*value) |= ((u64)temp) << i;
824b4f9fe12SLen Brown 	} else {
825b4f9fe12SLen Brown 		temp = 0xff & ((*value) >> i);
826b4f9fe12SLen Brown 		result = ec_write(address, temp);
827b4f9fe12SLen Brown 	}
828b4f9fe12SLen Brown 
829b4f9fe12SLen Brown 	switch (result) {
830b4f9fe12SLen Brown 	case -EINVAL:
831b4f9fe12SLen Brown 		return AE_BAD_PARAMETER;
832b4f9fe12SLen Brown 		break;
833b4f9fe12SLen Brown 	case -ENODEV:
834b4f9fe12SLen Brown 		return AE_NOT_FOUND;
835b4f9fe12SLen Brown 		break;
836b4f9fe12SLen Brown 	case -ETIME:
837b4f9fe12SLen Brown 		return AE_TIME;
838b4f9fe12SLen Brown 		break;
839b4f9fe12SLen Brown 	default:
840b4f9fe12SLen Brown 		return AE_OK;
841b4f9fe12SLen Brown 	}
842b4f9fe12SLen Brown }
843b4f9fe12SLen Brown 
844f61bb939SBjorn Helgaas static void acpi_wmi_notify(struct acpi_device *device, u32 event)
845b4f9fe12SLen Brown {
846b4f9fe12SLen Brown 	struct guid_block *block;
847b4f9fe12SLen Brown 	struct wmi_block *wblock;
848b4f9fe12SLen Brown 	struct list_head *p;
849b4f9fe12SLen Brown 
850762e1a2fSDmitry Torokhov 	list_for_each(p, &wmi_block_list) {
851b4f9fe12SLen Brown 		wblock = list_entry(p, struct wmi_block, list);
852b4f9fe12SLen Brown 		block = &wblock->gblock;
853b4f9fe12SLen Brown 
854b0e86302SAndy Lutomirski 		if (wblock->acpi_device == device &&
855b0e86302SAndy Lutomirski 		    (block->flags & ACPI_WMI_EVENT) &&
856b4f9fe12SLen Brown 		    (block->notify_id == event)) {
857b4f9fe12SLen Brown 			if (wblock->handler)
858b4f9fe12SLen Brown 				wblock->handler(event, wblock->handler_data);
8597715348cSThomas Renninger 			if (debug_event) {
86085b4e4ebSRasmus Villemoes 				pr_info("DEBUG Event GUID: %pUL\n",
86185b4e4ebSRasmus Villemoes 					wblock->gblock.guid);
8627715348cSThomas Renninger 			}
863b4f9fe12SLen Brown 
864b4f9fe12SLen Brown 			acpi_bus_generate_netlink_event(
865b4f9fe12SLen Brown 				device->pnp.device_class, dev_name(&device->dev),
866b4f9fe12SLen Brown 				event, 0);
867b4f9fe12SLen Brown 			break;
868b4f9fe12SLen Brown 		}
869b4f9fe12SLen Brown 	}
870b4f9fe12SLen Brown }
871b4f9fe12SLen Brown 
87251fac838SRafael J. Wysocki static int acpi_wmi_remove(struct acpi_device *device)
873b4f9fe12SLen Brown {
874b4f9fe12SLen Brown 	acpi_remove_address_space_handler(device->handle,
875b4f9fe12SLen Brown 				ACPI_ADR_SPACE_EC, &acpi_wmi_ec_space_handler);
876b0e86302SAndy Lutomirski 	wmi_free_devices(device);
877844af950SAndy Lutomirski 	device_unregister((struct device *)acpi_driver_data(device));
878844af950SAndy Lutomirski 	device->driver_data = NULL;
879b4f9fe12SLen Brown 
880b4f9fe12SLen Brown 	return 0;
881b4f9fe12SLen Brown }
882b4f9fe12SLen Brown 
883925b1089SThomas Renninger static int acpi_wmi_add(struct acpi_device *device)
884b4f9fe12SLen Brown {
885844af950SAndy Lutomirski 	struct device *wmi_bus_dev;
886b4f9fe12SLen Brown 	acpi_status status;
887c64eefd4SDmitry Torokhov 	int error;
888b4f9fe12SLen Brown 
889b4f9fe12SLen Brown 	status = acpi_install_address_space_handler(device->handle,
890b4f9fe12SLen Brown 						    ACPI_ADR_SPACE_EC,
891b4f9fe12SLen Brown 						    &acpi_wmi_ec_space_handler,
892b4f9fe12SLen Brown 						    NULL, NULL);
8935212cd67SDmitry Torokhov 	if (ACPI_FAILURE(status)) {
89446492ee4SAndy Lutomirski 		dev_err(&device->dev, "Error installing EC region handler\n");
895b4f9fe12SLen Brown 		return -ENODEV;
8965212cd67SDmitry Torokhov 	}
897b4f9fe12SLen Brown 
898844af950SAndy Lutomirski 	wmi_bus_dev = device_create(&wmi_bus_class, &device->dev, MKDEV(0, 0),
899844af950SAndy Lutomirski 				    NULL, "wmi_bus-%s", dev_name(&device->dev));
900844af950SAndy Lutomirski 	if (IS_ERR(wmi_bus_dev)) {
901844af950SAndy Lutomirski 		error = PTR_ERR(wmi_bus_dev);
902844af950SAndy Lutomirski 		goto err_remove_handler;
903844af950SAndy Lutomirski 	}
904844af950SAndy Lutomirski 	device->driver_data = wmi_bus_dev;
905844af950SAndy Lutomirski 
906844af950SAndy Lutomirski 	error = parse_wdg(wmi_bus_dev, device);
907c64eefd4SDmitry Torokhov 	if (error) {
9088e07514dSDmitry Torokhov 		pr_err("Failed to parse WDG method\n");
909844af950SAndy Lutomirski 		goto err_remove_busdev;
910b4f9fe12SLen Brown 	}
911b4f9fe12SLen Brown 
912c64eefd4SDmitry Torokhov 	return 0;
91346492ee4SAndy Lutomirski 
914844af950SAndy Lutomirski err_remove_busdev:
915844af950SAndy Lutomirski 	device_unregister(wmi_bus_dev);
916844af950SAndy Lutomirski 
91746492ee4SAndy Lutomirski err_remove_handler:
91846492ee4SAndy Lutomirski 	acpi_remove_address_space_handler(device->handle,
91946492ee4SAndy Lutomirski 					  ACPI_ADR_SPACE_EC,
92046492ee4SAndy Lutomirski 					  &acpi_wmi_ec_space_handler);
92146492ee4SAndy Lutomirski 
92246492ee4SAndy Lutomirski 	return error;
923b4f9fe12SLen Brown }
924b4f9fe12SLen Brown 
925844af950SAndy Lutomirski int __must_check __wmi_driver_register(struct wmi_driver *driver,
926844af950SAndy Lutomirski 				       struct module *owner)
927844af950SAndy Lutomirski {
928844af950SAndy Lutomirski 	driver->driver.owner = owner;
929844af950SAndy Lutomirski 	driver->driver.bus = &wmi_bus_type;
930844af950SAndy Lutomirski 
931844af950SAndy Lutomirski 	return driver_register(&driver->driver);
932844af950SAndy Lutomirski }
933844af950SAndy Lutomirski EXPORT_SYMBOL(__wmi_driver_register);
934844af950SAndy Lutomirski 
935844af950SAndy Lutomirski void wmi_driver_unregister(struct wmi_driver *driver)
936844af950SAndy Lutomirski {
937844af950SAndy Lutomirski 	driver_unregister(&driver->driver);
938844af950SAndy Lutomirski }
939844af950SAndy Lutomirski EXPORT_SYMBOL(wmi_driver_unregister);
940844af950SAndy Lutomirski 
941b4f9fe12SLen Brown static int __init acpi_wmi_init(void)
942b4f9fe12SLen Brown {
943c64eefd4SDmitry Torokhov 	int error;
944b4f9fe12SLen Brown 
945b4f9fe12SLen Brown 	if (acpi_disabled)
946b4f9fe12SLen Brown 		return -ENODEV;
947b4f9fe12SLen Brown 
948844af950SAndy Lutomirski 	error = class_register(&wmi_bus_class);
949c64eefd4SDmitry Torokhov 	if (error)
950c64eefd4SDmitry Torokhov 		return error;
951b4f9fe12SLen Brown 
952844af950SAndy Lutomirski 	error = bus_register(&wmi_bus_type);
953844af950SAndy Lutomirski 	if (error)
954844af950SAndy Lutomirski 		goto err_unreg_class;
955844af950SAndy Lutomirski 
956c64eefd4SDmitry Torokhov 	error = acpi_bus_register_driver(&acpi_wmi_driver);
957c64eefd4SDmitry Torokhov 	if (error) {
958c64eefd4SDmitry Torokhov 		pr_err("Error loading mapper\n");
959844af950SAndy Lutomirski 		goto err_unreg_bus;
9601caab3c1SMatthew Garrett 	}
9611caab3c1SMatthew Garrett 
9628e07514dSDmitry Torokhov 	return 0;
963844af950SAndy Lutomirski 
964844af950SAndy Lutomirski err_unreg_class:
965844af950SAndy Lutomirski 	class_unregister(&wmi_bus_class);
966844af950SAndy Lutomirski 
967844af950SAndy Lutomirski err_unreg_bus:
968844af950SAndy Lutomirski 	bus_unregister(&wmi_bus_type);
969844af950SAndy Lutomirski 
970844af950SAndy Lutomirski 	return error;
971b4f9fe12SLen Brown }
972b4f9fe12SLen Brown 
973b4f9fe12SLen Brown static void __exit acpi_wmi_exit(void)
974b4f9fe12SLen Brown {
975b4f9fe12SLen Brown 	acpi_bus_unregister_driver(&acpi_wmi_driver);
976844af950SAndy Lutomirski 	class_unregister(&wmi_bus_class);
977844af950SAndy Lutomirski 	bus_unregister(&wmi_bus_type);
978b4f9fe12SLen Brown }
979b4f9fe12SLen Brown 
980b4f9fe12SLen Brown subsys_initcall(acpi_wmi_init);
981b4f9fe12SLen Brown module_exit(acpi_wmi_exit);
982