xref: /freebsd/sys/dev/acpi_support/acpi_wmi.c (revision 02e9120893770924227138ba49df1edb3896112a)
1 /*-
2  * Copyright (c) 2009 Michael Gmelin <freebsd@grem.de>
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24  * SUCH DAMAGE.
25  */
26 
27 #include <sys/cdefs.h>
28 /*
29  * Driver for acpi-wmi mapping, provides an interface for vendor specific
30  * implementations (e.g. HP and Acer laptops).
31  * Inspired by the ACPI-WMI mapping driver (c) 2008-2008 Carlos Corbacho which
32  * implements this functionality for Linux.
33  *
34  * WMI and ACPI: http://www.microsoft.com/whdc/system/pnppwr/wmi/wmi-acpi.mspx
35  * acpi-wmi for Linux: http://www.kernel.org
36  */
37 
38 #include "opt_acpi.h"
39 #include <sys/param.h>
40 #include <sys/conf.h>
41 #include <sys/uio.h>
42 #include <sys/proc.h>
43 #include <sys/kernel.h>
44 #include <sys/malloc.h>
45 #include <sys/sbuf.h>
46 #include <sys/module.h>
47 #include <sys/bus.h>
48 
49 #include <contrib/dev/acpica/include/acpi.h>
50 #include <contrib/dev/acpica/include/accommon.h>
51 #include <dev/acpica/acpivar.h>
52 #include "acpi_wmi_if.h"
53 
54 static MALLOC_DEFINE(M_ACPIWMI, "acpiwmi", "ACPI-WMI mapping");
55 
56 #define _COMPONENT	ACPI_OEM
57 ACPI_MODULE_NAME("ACPI_WMI");
58 
59 #define ACPI_WMI_REGFLAG_EXPENSIVE	0x1 /* GUID flag: Expensive operation */
60 #define ACPI_WMI_REGFLAG_METHOD		0x2	/* GUID flag: Method call */
61 #define ACPI_WMI_REGFLAG_STRING		0x4	/* GUID flag: String */
62 #define ACPI_WMI_REGFLAG_EVENT		0x8	/* GUID flag: Event */
63 #define ACPI_WMI_BMOF_UUID "05901221-D566-11D1-B2F0-00A0C9062910"
64 
65 /*
66  * acpi_wmi driver private structure
67  */
68 struct acpi_wmi_softc {
69 	device_t	wmi_dev;	/* wmi device id */
70 	ACPI_HANDLE	wmi_handle;	/* handle of the PNP0C14 node */
71 	device_t	ec_dev;		/* acpi_ec0 */
72 	struct cdev	*wmistat_dev_t;	/* wmistat device handle */
73 	struct sbuf	wmistat_sbuf;	/* sbuf for /dev/wmistat output */
74 	pid_t		wmistat_open_pid; /* pid operating on /dev/wmistat */
75 	int		wmistat_bufptr;	/* /dev/wmistat ptr to buffer position */
76 	char 	        *mofbuf;
77 
78 	TAILQ_HEAD(wmi_info_list_head, wmi_info) wmi_info_list;
79 };
80 
81 /*
82  * Struct that holds information about
83  * about a single GUID entry in _WDG
84  */
85 struct guid_info {
86 	char	guid[16];	/* 16 byte non human readable GUID */
87 	char	oid[2];		/* object id or event notify id (first byte) */
88 	UINT8	max_instance;	/* highest instance known for this GUID */
89 	UINT8	flags;		/* ACPI_WMI_REGFLAG_%s */
90 };
91 
92 /* WExx event generation state (on/off) */
93 enum event_generation_state {
94 	EVENT_GENERATION_ON = 1,
95 	EVENT_GENERATION_OFF = 0
96 };
97 
98 /*
99  * Information about one entry in _WDG.
100  * List of those is used to lookup information by GUID.
101  */
102 struct wmi_info {
103 	TAILQ_ENTRY(wmi_info)	wmi_list;
104 	struct guid_info	ginfo;		/* information on guid */
105 	ACPI_NOTIFY_HANDLER	event_handler;/* client provided event handler */
106 	void			*event_handler_user_data; /* ev handler cookie  */
107 };
108 
109 ACPI_SERIAL_DECL(acpi_wmi, "ACPI-WMI Mapping");
110 
111 /* public interface - declaration */
112 /* standard device interface*/
113 static int		acpi_wmi_probe(device_t dev);
114 static int		acpi_wmi_attach(device_t dev);
115 static int		acpi_wmi_detach(device_t dev);
116 /* see acpi_wmi_if.m */
117 static int		acpi_wmi_provides_guid_string_method(device_t dev,
118 			    const char *guid_string);
119 static ACPI_STATUS	acpi_wmi_evaluate_call_method(device_t dev,
120 			    const char *guid_string, UINT8 instance,
121 			    UINT32 method_id, const ACPI_BUFFER *in,
122 			    ACPI_BUFFER *out);
123 static ACPI_STATUS	acpi_wmi_install_event_handler_method(device_t dev,
124 			    const char *guid_string, ACPI_NOTIFY_HANDLER handler,
125 			    void *data);
126 static ACPI_STATUS	acpi_wmi_remove_event_handler_method(device_t dev,
127 			    const char *guid_string);
128 static ACPI_STATUS	acpi_wmi_get_event_data_method(device_t dev,
129 			    UINT32 event_id, ACPI_BUFFER *out);
130 static ACPI_STATUS	acpi_wmi_get_block_method(device_t dev,
131 			    const char *guid_string,
132 			    UINT8 instance, ACPI_BUFFER *out);
133 static ACPI_STATUS	acpi_wmi_set_block_method(device_t dev,
134 			    const char *guid_string,
135 			    UINT8 instance, const ACPI_BUFFER *in);
136 /* private interface - declaration */
137 /* callbacks */
138 static void		acpi_wmi_notify_handler(ACPI_HANDLE h, UINT32 notify,
139 			    void *context);
140 static ACPI_STATUS	acpi_wmi_ec_handler(UINT32 function,
141 			    ACPI_PHYSICAL_ADDRESS address, UINT32 width,
142 			    UINT64 *value, void *context,
143 			    void *region_context);
144 /* helpers */
145 static ACPI_STATUS	acpi_wmi_read_wdg_blocks(struct acpi_wmi_softc *sc, ACPI_HANDLE h);
146 static ACPI_STATUS	acpi_wmi_toggle_we_event_generation(device_t dev,
147 			    struct wmi_info *winfo,
148 			    enum event_generation_state state);
149 static int		acpi_wmi_guid_string_to_guid(const UINT8 *guid_string,
150 			    UINT8 *guid);
151 static struct wmi_info* acpi_wmi_lookup_wmi_info_by_guid_string(struct acpi_wmi_softc *sc,
152 			    const char *guid_string);
153 
154 static d_open_t acpi_wmi_wmistat_open;
155 static d_close_t acpi_wmi_wmistat_close;
156 static d_read_t acpi_wmi_wmistat_read;
157 
158 /* handler /dev/wmistat device */
159 static struct cdevsw wmistat_cdevsw = {
160 	.d_version = D_VERSION,
161 	.d_open = acpi_wmi_wmistat_open,
162 	.d_close = acpi_wmi_wmistat_close,
163 	.d_read = acpi_wmi_wmistat_read,
164 	.d_name = "wmistat",
165 };
166 
167 static device_method_t acpi_wmi_methods[] = {
168 	/* Device interface */
169 	DEVMETHOD(device_probe,	acpi_wmi_probe),
170 	DEVMETHOD(device_attach, acpi_wmi_attach),
171 	DEVMETHOD(device_detach, acpi_wmi_detach),
172 
173 	/* bus interface */
174 	DEVMETHOD(bus_add_child,	bus_generic_add_child),
175 
176 	/* acpi_wmi interface */
177 	DEVMETHOD(acpi_wmi_provides_guid_string,
178 		    acpi_wmi_provides_guid_string_method),
179 	DEVMETHOD(acpi_wmi_evaluate_call, acpi_wmi_evaluate_call_method),
180 	DEVMETHOD(acpi_wmi_install_event_handler,
181 		    acpi_wmi_install_event_handler_method),
182 	DEVMETHOD(acpi_wmi_remove_event_handler,
183 		    acpi_wmi_remove_event_handler_method),
184 	DEVMETHOD(acpi_wmi_get_event_data, acpi_wmi_get_event_data_method),
185 	DEVMETHOD(acpi_wmi_get_block, acpi_wmi_get_block_method),
186 	DEVMETHOD(acpi_wmi_set_block, acpi_wmi_set_block_method),
187 
188 	DEVMETHOD_END
189 };
190 
191 static driver_t acpi_wmi_driver = {
192 	"acpi_wmi",
193 	acpi_wmi_methods,
194 	sizeof(struct acpi_wmi_softc),
195 };
196 
197 DRIVER_MODULE(acpi_wmi, acpi, acpi_wmi_driver, 0, 0);
198 MODULE_VERSION(acpi_wmi, 1);
199 MODULE_DEPEND(acpi_wmi, acpi, 1, 1, 1);
200 static char *wmi_ids[] = {"PNP0C14", NULL};
201 ACPI_PNP_INFO(wmi_ids);
202 
203 /*
204  * Probe for the PNP0C14 ACPI node
205  */
206 static int
207 acpi_wmi_probe(device_t dev)
208 {
209 	int rv;
210 
211 	if (acpi_disabled("wmi"))
212 		return (ENXIO);
213 	rv = ACPI_ID_PROBE(device_get_parent(dev), dev, wmi_ids, NULL);
214 	if (rv <= 0)
215 		device_set_desc(dev, "ACPI-WMI mapping");
216 
217 	return (rv);
218 }
219 
220 /*
221  * Attach the device by:
222  * - Looking for the first ACPI EC device
223  * - Install the notify handler
224  * - Install the EC address space handler
225  * - Look for the _WDG node and read GUID information blocks
226  */
227 static int
228 acpi_wmi_attach(device_t dev)
229 {
230 	struct acpi_wmi_softc *sc;
231 	int ret;
232 	ACPI_STATUS status;
233 
234 	ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
235 	sc = device_get_softc(dev);
236 	ret = ENXIO;
237 
238 	ACPI_SERIAL_BEGIN(acpi_wmi);
239 	sc->wmi_dev = dev;
240 	sc->wmi_handle = acpi_get_handle(dev);
241 	TAILQ_INIT(&sc->wmi_info_list);
242 	/* XXX Only works with one EC, but nearly all systems only have one. */
243 	if ((sc->ec_dev = devclass_get_device(devclass_find("acpi_ec"), 0))
244 	    == NULL)
245 		device_printf(dev, "cannot find EC device\n");
246 	if (ACPI_FAILURE((status = AcpiInstallNotifyHandler(sc->wmi_handle,
247 		    ACPI_DEVICE_NOTIFY, acpi_wmi_notify_handler, sc))))
248 		device_printf(sc->wmi_dev, "couldn't install notify handler - %s\n",
249 		    AcpiFormatException(status));
250 	else if (ACPI_FAILURE((status = AcpiInstallAddressSpaceHandler(
251 		    sc->wmi_handle, ACPI_ADR_SPACE_EC, acpi_wmi_ec_handler,
252 		    NULL, sc)))) {
253 		device_printf(sc->wmi_dev, "couldn't install EC handler - %s\n",
254 		    AcpiFormatException(status));
255 		AcpiRemoveNotifyHandler(sc->wmi_handle, ACPI_DEVICE_NOTIFY,
256 		    acpi_wmi_notify_handler);
257 	} else if (ACPI_FAILURE((status = acpi_wmi_read_wdg_blocks(sc,
258 		    sc->wmi_handle)))) {
259 		device_printf(sc->wmi_dev, "couldn't parse _WDG - %s\n",
260 		    AcpiFormatException(status));
261 		AcpiRemoveNotifyHandler(sc->wmi_handle, ACPI_DEVICE_NOTIFY,
262 		    acpi_wmi_notify_handler);
263 		AcpiRemoveAddressSpaceHandler(sc->wmi_handle, ACPI_ADR_SPACE_EC,
264 		    acpi_wmi_ec_handler);
265 	} else {
266 		sc->wmistat_dev_t = make_dev(&wmistat_cdevsw, 0, UID_ROOT,
267 		    GID_WHEEL, 0644, "wmistat%d", device_get_unit(dev));
268 		sc->wmistat_dev_t->si_drv1 = sc;
269 		sc->wmistat_open_pid = 0;
270 		sc->wmistat_bufptr = -1;
271 		ret = 0;
272 	}
273 	ACPI_SERIAL_END(acpi_wmi);
274 
275 	if (acpi_wmi_provides_guid_string_method(dev, ACPI_WMI_BMOF_UUID)) {
276 		ACPI_BUFFER out = { ACPI_ALLOCATE_BUFFER, NULL };
277 		ACPI_OBJECT *obj;
278 
279 		device_printf(dev, "Embedded MOF found\n");
280 		status = acpi_wmi_get_block_method(dev,  ACPI_WMI_BMOF_UUID,
281 		    0, &out);
282 		if (ACPI_SUCCESS(status)) {
283 			obj = out.Pointer;
284 			if (obj && obj->Type == ACPI_TYPE_BUFFER) {
285 				SYSCTL_ADD_OPAQUE(device_get_sysctl_ctx(dev),
286 				    SYSCTL_CHILDREN(
287 				        device_get_sysctl_tree(dev)),
288 				    OID_AUTO, "bmof",
289 				    CTLFLAG_RD | CTLFLAG_MPSAFE,
290 				    obj->Buffer.Pointer,
291 				    obj->Buffer.Length,
292 				    "A", "MOF Blob");
293 			}
294 		}
295 		sc->mofbuf = out.Pointer;
296 	}
297 
298 	if (ret == 0) {
299 		bus_generic_probe(dev);
300 		ret = bus_generic_attach(dev);
301 	}
302 
303 	return (ret);
304 }
305 
306 /*
307  * Detach the driver by:
308  * - Removing notification handler
309  * - Removing address space handler
310  * - Turning off event generation for all WExx event activated by
311  *   child drivers
312  */
313 static int
314 acpi_wmi_detach(device_t dev)
315 {
316 	struct wmi_info *winfo, *tmp;
317 	struct acpi_wmi_softc *sc;
318 	int ret;
319 
320 	ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
321 	sc = device_get_softc(dev);
322 	ACPI_SERIAL_BEGIN(acpi_wmi);
323 
324 	if (sc->wmistat_open_pid != 0) {
325 		ret = EBUSY;
326 	} else {
327 		AcpiRemoveNotifyHandler(sc->wmi_handle, ACPI_DEVICE_NOTIFY,
328 		    acpi_wmi_notify_handler);
329 		AcpiRemoveAddressSpaceHandler(sc->wmi_handle,
330 		    ACPI_ADR_SPACE_EC, acpi_wmi_ec_handler);
331 		TAILQ_FOREACH_SAFE(winfo, &sc->wmi_info_list, wmi_list, tmp) {
332 			if (winfo->event_handler)
333 				acpi_wmi_toggle_we_event_generation(dev,
334 				    winfo, EVENT_GENERATION_OFF);
335 			TAILQ_REMOVE(&sc->wmi_info_list, winfo, wmi_list);
336 			free(winfo, M_ACPIWMI);
337 		}
338 		if (sc->wmistat_bufptr != -1) {
339 			sbuf_delete(&sc->wmistat_sbuf);
340 			sc->wmistat_bufptr = -1;
341 		}
342 		sc->wmistat_open_pid = 0;
343 		destroy_dev(sc->wmistat_dev_t);
344 		ret = 0;
345 		AcpiOsFree(sc->mofbuf);
346 	}
347 	ACPI_SERIAL_END(acpi_wmi);
348 
349 	return (ret);
350 }
351 
352 /*
353  * Check if the given GUID string (human readable format
354  * AABBCCDD-EEFF-GGHH-IIJJ-KKLLMMNNOOPP)
355  * exists within _WDG
356  */
357 static int
358 acpi_wmi_provides_guid_string_method(device_t dev, const char *guid_string)
359 {
360 	struct acpi_wmi_softc *sc;
361 	struct wmi_info *winfo;
362 	int ret;
363 
364 	sc = device_get_softc(dev);
365 	ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
366 	ACPI_SERIAL_BEGIN(acpi_wmi);
367 	winfo = acpi_wmi_lookup_wmi_info_by_guid_string(sc, guid_string);
368 	ret = (winfo == NULL)?0:winfo->ginfo.max_instance+1;
369 	ACPI_SERIAL_END(acpi_wmi);
370 
371 	return (ret);
372 }
373 
374 /*
375  * Call a method "method_id" on the given GUID block
376  * write result into user provided output buffer
377  */
378 static ACPI_STATUS
379 acpi_wmi_evaluate_call_method(device_t dev, const char *guid_string,
380     UINT8 instance, UINT32 method_id, const ACPI_BUFFER *in, ACPI_BUFFER *out)
381 {
382 	ACPI_OBJECT params[3];
383 	ACPI_OBJECT_LIST input;
384 	char method[5] = "WMxx";
385 	struct wmi_info *winfo;
386 	struct acpi_wmi_softc *sc;
387 	ACPI_STATUS status;
388 
389 	ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
390 
391 	sc = device_get_softc(dev);
392 	ACPI_SERIAL_BEGIN(acpi_wmi);
393 	if ((winfo = acpi_wmi_lookup_wmi_info_by_guid_string(sc, guid_string))
394 		    == NULL)
395 		status = AE_NOT_FOUND;
396 	else if (!(winfo->ginfo.flags & ACPI_WMI_REGFLAG_METHOD))
397 		status = AE_BAD_DATA;
398 	else if (instance > winfo->ginfo.max_instance)
399 		status = AE_BAD_PARAMETER;
400 	else {
401 		params[0].Type = ACPI_TYPE_INTEGER;
402 		params[0].Integer.Value = instance;
403 		params[1].Type = ACPI_TYPE_INTEGER;
404 		params[1].Integer.Value = method_id;
405 		input.Pointer = params;
406 		input.Count = 2;
407 		if (in) {
408 			params[2].Type =
409 			    (winfo->ginfo.flags & ACPI_WMI_REGFLAG_STRING)
410 			    ?ACPI_TYPE_STRING:ACPI_TYPE_BUFFER;
411 			params[2].Buffer.Length = in->Length;
412 			params[2].Buffer.Pointer = in->Pointer;
413 			input.Count = 3;
414 		}
415 		method[2] = winfo->ginfo.oid[0];
416 		method[3] = winfo->ginfo.oid[1];
417 		status = AcpiEvaluateObject(sc->wmi_handle, method,
418 			    &input, out);
419 	}
420 	ACPI_SERIAL_END(acpi_wmi);
421 
422 	return (status);
423 }
424 
425 /*
426  * Install a user provided event_handler on the given GUID
427  * provided *data will be passed on callback
428  * If there is already an existing event handler registered it will be silently
429  * discarded
430  */
431 static ACPI_STATUS
432 acpi_wmi_install_event_handler_method(device_t dev, const char *guid_string,
433     ACPI_NOTIFY_HANDLER event_handler, void *data)
434 {
435 	struct acpi_wmi_softc *sc = device_get_softc(dev);
436 	struct wmi_info *winfo;
437 	ACPI_STATUS status;
438 
439 	ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
440 
441 	status = AE_OK;
442 	ACPI_SERIAL_BEGIN(acpi_wmi);
443 	if (guid_string == NULL || event_handler == NULL)
444 		status = AE_BAD_PARAMETER;
445 	else if ((winfo = acpi_wmi_lookup_wmi_info_by_guid_string(sc, guid_string))
446 		    == NULL)
447 		status = AE_NOT_EXIST;
448 	else if (winfo->event_handler != NULL ||
449 		(status = acpi_wmi_toggle_we_event_generation(dev, winfo,
450 		    EVENT_GENERATION_ON)) == AE_OK) {
451 		winfo->event_handler = event_handler;
452 		winfo->event_handler_user_data = data;
453 	}
454 	ACPI_SERIAL_END(acpi_wmi);
455 
456 	return (status);
457 }
458 
459 /*
460  * Remove a previously installed event handler from the given GUID
461  * If there was none installed, this call is silently discarded and
462  * reported as AE_OK
463  */
464 static ACPI_STATUS
465 acpi_wmi_remove_event_handler_method(device_t dev, const char *guid_string)
466 {
467 	struct acpi_wmi_softc *sc = device_get_softc(dev);
468 	struct wmi_info *winfo;
469 	ACPI_STATUS status;
470 
471 	ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
472 
473 	status = AE_OK;
474 	ACPI_SERIAL_BEGIN(acpi_wmi);
475 	if (guid_string &&
476 	    (winfo = acpi_wmi_lookup_wmi_info_by_guid_string(sc, guid_string))
477 	    != NULL && winfo->event_handler) {
478 		status = acpi_wmi_toggle_we_event_generation(dev, winfo,
479 			    EVENT_GENERATION_OFF);
480 		winfo->event_handler = NULL;
481 		winfo->event_handler_user_data = NULL;
482 	}
483 	ACPI_SERIAL_END(acpi_wmi);
484 
485 	return (status);
486 }
487 
488 /*
489  * Get details on an event received through a callback registered
490  * through ACPI_WMI_REMOVE_EVENT_HANDLER into a user provided output buffer.
491  * (event_id equals "notify" passed in the callback)
492  */
493 static ACPI_STATUS
494 acpi_wmi_get_event_data_method(device_t dev, UINT32 event_id, ACPI_BUFFER *out)
495 {
496 	ACPI_OBJECT_LIST input;
497 	ACPI_OBJECT params[1];
498 	struct acpi_wmi_softc *sc;
499 	struct wmi_info *winfo;
500 	ACPI_STATUS status;
501 
502 	ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
503 
504 	sc = device_get_softc(dev);
505 	status = AE_NOT_FOUND;
506 	ACPI_SERIAL_BEGIN(acpi_wmi);
507 	params[0].Type = ACPI_TYPE_INTEGER;
508 	params[0].Integer.Value = event_id;
509 	input.Pointer = params;
510 	input.Count = 1;
511 	TAILQ_FOREACH(winfo, &sc->wmi_info_list, wmi_list) {
512 		if ((winfo->ginfo.flags & ACPI_WMI_REGFLAG_EVENT) &&
513 		    ((UINT8) winfo->ginfo.oid[0] == event_id)) {
514 			status = AcpiEvaluateObject(sc->wmi_handle, "_WED",
515 				    &input, out);
516 			break;
517 		}
518 	}
519 	ACPI_SERIAL_END(acpi_wmi);
520 
521 	return (status);
522 }
523 
524 /*
525  * Read a block of data from the given GUID (using WQxx (query))
526  * Will be returned in a user provided buffer (out).
527  * If the method is marked as expensive (ACPI_WMI_REGFLAG_EXPENSIVE)
528  * we will first call the WCxx control method to lock the node to
529  * lock the node for data collection and release it afterwards.
530  * (Failed WCxx calls are ignored to "support" broken implementations)
531  */
532 static ACPI_STATUS
533 acpi_wmi_get_block_method(device_t dev, const char *guid_string, UINT8 instance,
534 	ACPI_BUFFER *out)
535 {
536 	char wc_method[5] = "WCxx";
537 	char wq_method[5] = "WQxx";
538 	ACPI_OBJECT_LIST wc_input;
539 	ACPI_OBJECT_LIST wq_input;
540 	ACPI_OBJECT wc_params[1];
541 	ACPI_OBJECT wq_params[1];
542 	ACPI_HANDLE wc_handle;
543 	struct acpi_wmi_softc *sc;
544 	struct wmi_info *winfo;
545 	ACPI_STATUS status;
546 	ACPI_STATUS wc_status;
547 
548 	ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
549 
550 	sc = device_get_softc(dev);
551 	wc_status = AE_ERROR;
552 	ACPI_SERIAL_BEGIN(acpi_wmi);
553 	if (guid_string == NULL || out == NULL)
554 		status = AE_BAD_PARAMETER;
555 	else if ((winfo = acpi_wmi_lookup_wmi_info_by_guid_string(sc, guid_string))
556 		    == NULL)
557 		status = AE_ERROR;
558 	else if (instance > winfo->ginfo.max_instance)
559 		status = AE_BAD_PARAMETER;
560 	else if ((winfo->ginfo.flags & ACPI_WMI_REGFLAG_EVENT) ||
561 	    (winfo->ginfo.flags & ACPI_WMI_REGFLAG_METHOD))
562 		status = AE_ERROR;
563 	else {
564 		wq_params[0].Type = ACPI_TYPE_INTEGER;
565 		wq_params[0].Integer.Value = instance;
566 		wq_input.Pointer = wq_params;
567 		wq_input.Count = 1;
568 		if (winfo->ginfo.flags & ACPI_WMI_REGFLAG_EXPENSIVE) {
569 			wc_params[0].Type = ACPI_TYPE_INTEGER;
570 			wc_params[0].Integer.Value = 1;
571 			wc_input.Pointer = wc_params;
572 			wc_input.Count = 1;
573 			wc_method[2] = winfo->ginfo.oid[0];
574 			wc_method[3] = winfo->ginfo.oid[1];
575 			wc_status = AcpiGetHandle(sc->wmi_handle, wc_method,
576 				    &wc_handle);
577 			if (ACPI_SUCCESS(wc_status))
578 				wc_status = AcpiEvaluateObject(wc_handle,
579 						wc_method, &wc_input, NULL);
580 		}
581 		wq_method[2] = winfo->ginfo.oid[0];
582 		wq_method[3] = winfo->ginfo.oid[1];
583 		status = AcpiEvaluateObject(sc->wmi_handle, wq_method,
584 			    &wq_input, out);
585 		if ((winfo->ginfo.flags & ACPI_WMI_REGFLAG_EXPENSIVE)
586 		    && ACPI_SUCCESS(wc_status)) {
587 			wc_params[0].Integer.Value = 0;
588 			status = AcpiEvaluateObject(wc_handle, wc_method,
589 				    &wc_input, NULL);  /* XXX this might be
590 				    			 the wrong status to
591 				    			 return? */
592 		}
593 	}
594 	ACPI_SERIAL_END(acpi_wmi);
595 
596 	return (status);
597 }
598 
599 /*
600  * Write a block of data to the given GUID (using WSxx)
601  */
602 static ACPI_STATUS
603 acpi_wmi_set_block_method(device_t dev, const char *guid_string, UINT8 instance,
604 	const ACPI_BUFFER *in)
605 {
606 	char method[5] = "WSxx";
607 	ACPI_OBJECT_LIST input;
608 	ACPI_OBJECT params[2];
609 	struct wmi_info *winfo;
610 	struct acpi_wmi_softc *sc;
611 	ACPI_STATUS status;
612 
613 	ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
614 
615 	sc = device_get_softc(dev);
616 	ACPI_SERIAL_BEGIN(acpi_wmi);
617 	if (guid_string == NULL || in == NULL)
618 		status = AE_BAD_DATA;
619 	else if ((winfo = acpi_wmi_lookup_wmi_info_by_guid_string(sc, guid_string))
620 		    == NULL)
621 		status = AE_ERROR;
622 	else if (instance > winfo->ginfo.max_instance)
623 		status = AE_BAD_PARAMETER;
624 	else if ((winfo->ginfo.flags & ACPI_WMI_REGFLAG_EVENT) ||
625 		    (winfo->ginfo.flags & ACPI_WMI_REGFLAG_METHOD))
626 		status = AE_ERROR;
627 	else {
628 		params[0].Type = ACPI_TYPE_INTEGER;
629 		params[0].Integer.Value = instance;
630 		input.Pointer = params;
631 		input.Count = 2;
632 		params[1].Type = (winfo->ginfo.flags & ACPI_WMI_REGFLAG_STRING)
633 		    ?ACPI_TYPE_STRING:ACPI_TYPE_BUFFER;
634 		params[1].Buffer.Length = in->Length;
635 		params[1].Buffer.Pointer = in->Pointer;
636 		method[2] = winfo->ginfo.oid[0];
637 		method[3] = winfo->ginfo.oid[1];
638 		status = AcpiEvaluateObject(sc->wmi_handle, method,
639 			    &input, NULL);
640 	}
641 	ACPI_SERIAL_END(acpi_wmi);
642 
643 	return (status);
644 }
645 
646 /*
647  * Handle events received and dispatch them to
648  * stakeholders that registered through ACPI_WMI_INSTALL_EVENT_HANDLER
649  */
650 static void
651 acpi_wmi_notify_handler(ACPI_HANDLE h, UINT32 notify, void *context)
652 {
653 	struct acpi_wmi_softc *sc = context;
654 	ACPI_NOTIFY_HANDLER handler;
655 	void *handler_data;
656 	struct wmi_info *winfo;
657 
658 	ACPI_FUNCTION_TRACE_U32((char *)(uintptr_t)__func__, notify);
659 
660 	handler = NULL;
661 	handler_data = NULL;
662 	ACPI_SERIAL_BEGIN(acpi_wmi);
663 	TAILQ_FOREACH(winfo, &sc->wmi_info_list, wmi_list) {
664 		if ((winfo->ginfo.flags & ACPI_WMI_REGFLAG_EVENT) &&
665 				((UINT8) winfo->ginfo.oid[0] == notify)) {
666 			if (winfo->event_handler) {
667 				handler = winfo->event_handler;
668 				handler_data = winfo->event_handler_user_data;
669 				break;
670 			}
671 		}
672 	}
673 	ACPI_SERIAL_END(acpi_wmi);
674 	if (handler) {
675 		handler(h, notify, handler_data);
676 	}
677 }
678 
679 /*
680  * Handle EC address space notifications reveived on the WDG node
681  * (this mimics EcAddressSpaceHandler in acpi_ec.c)
682  */
683 static ACPI_STATUS
684 acpi_wmi_ec_handler(UINT32 function, ACPI_PHYSICAL_ADDRESS address,
685     UINT32 width, UINT64 *value, void *context,
686     void *region_context)
687 {
688 	struct acpi_wmi_softc *sc;
689 	int i;
690 	UINT64 ec_data;
691 	UINT8 ec_addr;
692 	ACPI_STATUS status;
693 
694 	ACPI_FUNCTION_TRACE_U32((char *)(uintptr_t)__func__, (UINT32)address);
695 
696 	sc = (struct acpi_wmi_softc *)context;
697 	if (width % 8 != 0 || value == NULL || context == NULL)
698 		return (AE_BAD_PARAMETER);
699 	if (address + (width / 8) - 1 > 0xFF)
700 		return (AE_BAD_ADDRESS);
701 	if (sc->ec_dev == NULL)
702 		return (AE_NOT_FOUND);
703 	if (function == ACPI_READ)
704 		*value = 0;
705 	ec_addr = address;
706 	status = AE_ERROR;
707 
708 	for (i = 0; i < width; i += 8, ++ec_addr) {
709 		switch (function) {
710 		case ACPI_READ:
711 			status = ACPI_EC_READ(sc->ec_dev, ec_addr, &ec_data, 1);
712 			if (ACPI_SUCCESS(status))
713 				*value |= ((UINT64)ec_data) << i;
714 		break;
715 		case ACPI_WRITE:
716 			ec_data = (UINT8)((*value) >> i);
717 			status = ACPI_EC_WRITE(sc->ec_dev, ec_addr, ec_data, 1);
718 			break;
719 		default:
720 			device_printf(sc->wmi_dev,
721 			    "invalid acpi_wmi_ec_handler function %d\n",
722 			    function);
723 			status = AE_BAD_PARAMETER;
724 			break;
725 		}
726 		if (ACPI_FAILURE(status))
727 			break;
728 	}
729 
730 	return (status);
731 }
732 
733 /*
734  * Read GUID blocks from the _WDG node
735  * into wmi_info_list.
736  */
737 static ACPI_STATUS
738 acpi_wmi_read_wdg_blocks(struct acpi_wmi_softc *sc, ACPI_HANDLE h)
739 {
740 	ACPI_BUFFER out = {ACPI_ALLOCATE_BUFFER, NULL};
741 	struct guid_info *ginfo;
742 	ACPI_OBJECT *obj;
743 	struct wmi_info *winfo;
744 	UINT32 i;
745 	UINT32 wdg_block_count;
746 	ACPI_STATUS status;
747 
748 	ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
749 
750 	ACPI_SERIAL_ASSERT(acpi_wmi);
751 	if (ACPI_FAILURE(status = AcpiEvaluateObject(h, "_WDG", NULL, &out)))
752 		return (status);
753 	obj = (ACPI_OBJECT*) out.Pointer;
754 	wdg_block_count = obj->Buffer.Length / sizeof(struct guid_info);
755 	if ((ginfo = malloc(obj->Buffer.Length, M_ACPIWMI, M_NOWAIT))
756 		    == NULL) {
757 		AcpiOsFree(out.Pointer);
758 		return (AE_NO_MEMORY);
759 	}
760 	memcpy(ginfo, obj->Buffer.Pointer, obj->Buffer.Length);
761 	for (i = 0; i < wdg_block_count; ++i) {
762 		if ((winfo = malloc(sizeof(struct wmi_info), M_ACPIWMI,
763 			    M_NOWAIT | M_ZERO)) == NULL) {
764 			AcpiOsFree(out.Pointer);
765 			free(ginfo, M_ACPIWMI);
766 			return (AE_NO_MEMORY);
767 		}
768 		winfo->ginfo = ginfo[i];
769 		TAILQ_INSERT_TAIL(&sc->wmi_info_list, winfo, wmi_list);
770 	}
771 	AcpiOsFree(out.Pointer);
772 	free(ginfo, M_ACPIWMI);
773 
774 	return (status);
775 }
776 
777 /*
778  * Toggle event generation in for the given GUID (passed by winfo)
779  * Turn on to get notified (through acpi_wmi_notify_handler) if events happen
780  * on the given GUID.
781  */
782 static ACPI_STATUS
783 acpi_wmi_toggle_we_event_generation(device_t dev, struct wmi_info *winfo,
784     enum event_generation_state state)
785 {
786 	char method[5] = "WExx";
787 	ACPI_OBJECT_LIST input;
788 	ACPI_OBJECT params[1];
789 	struct acpi_wmi_softc *sc;
790 	ACPI_STATUS status;
791 
792 	ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
793 
794 	sc = device_get_softc(dev);
795 	ACPI_SERIAL_ASSERT(acpi_wmi);
796 	params[0].Type = ACPI_TYPE_INTEGER;
797 	params[0].Integer.Value = state==EVENT_GENERATION_ON?1:0;
798 	input.Pointer = params;
799 	input.Count = 1;
800 
801 	UINT8 hi = ((UINT8) winfo->ginfo.oid[0]) >> 4;
802 	UINT8 lo = ((UINT8) winfo->ginfo.oid[0]) & 0xf;
803 	method[2] = (hi > 9 ? hi + 55: hi + 48);
804 	method[3] = (lo > 9 ? lo + 55: lo + 48);
805 	status = AcpiEvaluateObject(sc->wmi_handle, method, &input, NULL);
806 	if (status == AE_NOT_FOUND) status = AE_OK;
807 
808 	return (status);
809 }
810 
811 /*
812  * Convert given two digit hex string (hexin) to an UINT8 referenced
813  * by byteout.
814  * Return != 0 if the was a problem (invalid input)
815  */
816 static __inline int acpi_wmi_hex_to_int(const UINT8 *hexin, UINT8 *byteout)
817 {
818 	unsigned int hi;
819 	unsigned int lo;
820 
821 	hi = hexin[0];
822 	lo = hexin[1];
823 	if ('0' <= hi && hi <= '9')
824 		hi -= '0';
825 	else if ('A' <= hi && hi <= 'F')
826 		hi -= ('A' - 10);
827 	else if ('a' <= hi && hi <= 'f')
828 		hi -= ('a' - 10);
829 	else
830 		return (1);
831 	if ('0' <= lo && lo <= '9')
832 		lo -= '0';
833 	else if ('A' <= lo && lo <= 'F')
834 		lo -= ('A' - 10);
835 	else if ('a' <= lo && lo <= 'f')
836 		lo -= ('a' - 10);
837 	else
838 		return (1);
839 	*byteout = (hi << 4) + lo;
840 
841 	return (0);
842 }
843 
844 /*
845  * Convert a human readable 36 character GUID into a 16byte
846  * machine readable one.
847  * The basic algorithm looks as follows:
848  * Input:  AABBCCDD-EEFF-GGHH-IIJJ-KKLLMMNNOOPP
849  * Output: DCBAFEHGIJKLMNOP
850  * (AA BB CC etc. represent two digit hex numbers == bytes)
851  * Return != 0 if passed guid string is invalid
852  */
853 static int
854 acpi_wmi_guid_string_to_guid(const UINT8 *guid_string, UINT8 *guid)
855 {
856 	static const int mapping[20] = {3, 2, 1, 0, -1, 5, 4, -1, 7, 6, -1,
857 	    8, 9, -1, 10, 11, 12, 13, 14, 15};
858 	int i;
859 
860 	for (i = 0; i < 20; ++i, ++guid_string) {
861 		if (mapping[i] >= 0) {
862 			if (acpi_wmi_hex_to_int(guid_string,
863 			    &guid[mapping[i]]))
864 				return (-1);
865 			++guid_string;
866 		} else if (*guid_string != '-')
867 			return (-1);
868 	}
869 
870 	return (0);
871 }
872 
873 /*
874  * Lookup a wmi_info structure in wmi_list based on a
875  * human readable GUID
876  * Return NULL if the GUID is unknown in the _WDG
877  */
878 static struct wmi_info*
879 acpi_wmi_lookup_wmi_info_by_guid_string(struct acpi_wmi_softc *sc, const char *guid_string)
880 {
881 	char guid[16];
882 	struct wmi_info *winfo;
883 
884 	ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
885 
886 	ACPI_SERIAL_ASSERT(acpi_wmi);
887 
888 	if (!acpi_wmi_guid_string_to_guid(guid_string, guid)) {
889 		TAILQ_FOREACH(winfo, &sc->wmi_info_list, wmi_list) {
890 			if (!memcmp(winfo->ginfo.guid, guid, 16)) {
891 				return (winfo);
892 			}
893 		}
894 	}
895 
896 	return (NULL);
897 }
898 
899 /*
900  * open wmistat device
901  */
902 static int
903 acpi_wmi_wmistat_open(struct cdev* dev, int flags, int mode, struct thread *td)
904 {
905 	struct acpi_wmi_softc *sc;
906 	int ret;
907 
908 	if (dev == NULL || dev->si_drv1 == NULL)
909 		return (EBADF);
910 	sc = dev->si_drv1;
911 
912 	ACPI_SERIAL_BEGIN(acpi_wmi);
913 	if (sc->wmistat_open_pid != 0) {
914 		ret = EBUSY;
915 	}
916 	else {
917 		if (sbuf_new(&sc->wmistat_sbuf, NULL, 4096, SBUF_AUTOEXTEND)
918 			    == NULL) {
919 			ret = ENXIO;
920 		} else {
921 			sc->wmistat_open_pid = td->td_proc->p_pid;
922 			sc->wmistat_bufptr = 0;
923 			ret = 0;
924 		}
925 	}
926 	ACPI_SERIAL_END(acpi_wmi);
927 
928 	return (ret);
929 }
930 
931 /*
932  * close wmistat device
933  */
934 static int
935 acpi_wmi_wmistat_close(struct cdev* dev, int flags, int mode,
936     struct thread *td)
937 {
938 	struct acpi_wmi_softc *sc;
939 	int ret;
940 
941 	if (dev == NULL || dev->si_drv1 == NULL)
942 		return (EBADF);
943 	sc = dev->si_drv1;
944 
945 	ACPI_SERIAL_BEGIN(acpi_wmi);
946 	if (sc->wmistat_open_pid == 0) {
947 		ret = EBADF;
948 	}
949 	else {
950 		if (sc->wmistat_bufptr != -1) {
951 			sbuf_delete(&sc->wmistat_sbuf);
952 			sc->wmistat_bufptr = -1;
953 		}
954 		sc->wmistat_open_pid = 0;
955 		ret = 0;
956 	}
957 	ACPI_SERIAL_END(acpi_wmi);
958 
959 	return (ret);
960 }
961 
962 /*
963  * Read from wmistat guid information
964  */
965 static int
966 acpi_wmi_wmistat_read(struct cdev *dev, struct uio *buf, int flag)
967 {
968 	struct acpi_wmi_softc *sc;
969 	struct wmi_info *winfo;
970 	int l;
971 	int ret;
972 	UINT8* guid;
973 
974 	if (dev == NULL || dev->si_drv1 == NULL)
975 		return (EBADF);
976 	sc = dev->si_drv1;
977 
978 	ACPI_SERIAL_BEGIN(acpi_wmi);
979 	if (sc->wmistat_bufptr == -1) {
980 		ret = EBADF;
981 	}
982 	else {
983 		if (!sbuf_done(&sc->wmistat_sbuf)) {
984 			sbuf_printf(&sc->wmistat_sbuf, "GUID                 "
985 				    "                 INST EXPE METH STR "
986 				    "EVENT OID\n");
987 			TAILQ_FOREACH(winfo, &sc->wmi_info_list, wmi_list) {
988 				guid = (UINT8*)winfo->ginfo.guid;
989 				sbuf_printf(&sc->wmistat_sbuf,
990 					    "{%02X%02X%02X%02X-%02X%02X-"
991 					    "%02X%02X-%02X%02X-%02X%02X"
992 					    "%02X%02X%02X%02X} %3d %-5s",
993 					guid[3], guid[2], guid[1], guid[0],
994 					guid[5], guid[4],
995 					guid[7], guid[6],
996 					guid[8], guid[9],
997 					guid[10], guid[11], guid[12],
998 					guid[13], guid[14], guid[15],
999 					winfo->ginfo.max_instance,
1000 					(winfo->ginfo.flags&
1001 						ACPI_WMI_REGFLAG_EXPENSIVE)?
1002 						"YES":"NO"
1003 					);
1004 				if (winfo->ginfo.flags&ACPI_WMI_REGFLAG_METHOD)
1005 					sbuf_printf(&sc->wmistat_sbuf,
1006 						    "WM%c%c ",
1007 						    winfo->ginfo.oid[0],
1008 						    winfo->ginfo.oid[1]);
1009 				else
1010 					sbuf_printf(&sc->wmistat_sbuf, "NO   ");
1011 				sbuf_printf(&sc->wmistat_sbuf, "%-4s",
1012 					    (winfo->ginfo.flags&
1013 					    ACPI_WMI_REGFLAG_STRING)?"YES":"NO"
1014 					);
1015 				if (winfo->ginfo.flags&ACPI_WMI_REGFLAG_EVENT)
1016 					sbuf_printf(&sc->wmistat_sbuf,
1017 						    "0x%02X%s -\n",
1018 						    (UINT8)winfo->ginfo.oid[0],
1019 						    winfo->event_handler==NULL?
1020 						    " ":"+");
1021 				else
1022 					sbuf_printf(&sc->wmistat_sbuf,
1023 						    "NO    %c%c\n",
1024 						    winfo->ginfo.oid[0],
1025 						    winfo->ginfo.oid[1]);
1026 			}
1027 			sbuf_finish(&sc->wmistat_sbuf);
1028 		}
1029 		if (sbuf_len(&sc->wmistat_sbuf) <= 0) {
1030 			sbuf_delete(&sc->wmistat_sbuf);
1031 			sc->wmistat_bufptr = -1;
1032 			sc->wmistat_open_pid = 0;
1033 			ret = ENOMEM;
1034 		} else {
1035 			l = min(buf->uio_resid, sbuf_len(&sc->wmistat_sbuf) -
1036 				    sc->wmistat_bufptr);
1037 			ret = (l > 0)?uiomove(sbuf_data(&sc->wmistat_sbuf) +
1038 				    sc->wmistat_bufptr, l, buf) : 0;
1039 			sc->wmistat_bufptr += l;
1040 		}
1041 	}
1042 	ACPI_SERIAL_END(acpi_wmi);
1043 
1044 	return (ret);
1045 }
1046