wmi_acpi.c revision 1.18
1/*	$NetBSD: wmi_acpi.c,v 1.18 2021/04/24 23:36:52 thorpej Exp $	*/
2
3/*-
4 * Copyright (c) 2009, 2010 Jukka Ruohonen <jruohonen@iki.fi>
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 *
11 * 1. Redistributions of source code must retain the above copyright
12 *    notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 *    notice, this list of conditions and the following disclaimer in the
15 *    documentation and/or other materials provided with the distribution.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27 * SUCH DAMAGE.
28 */
29#include <sys/cdefs.h>
30__KERNEL_RCSID(0, "$NetBSD: wmi_acpi.c,v 1.18 2021/04/24 23:36:52 thorpej Exp $");
31
32#include <sys/param.h>
33#include <sys/device.h>
34#include <sys/endian.h>
35#include <sys/kmem.h>
36#include <sys/systm.h>
37#include <sys/module.h>
38
39#include <dev/acpi/acpireg.h>
40#include <dev/acpi/acpivar.h>
41#include <dev/acpi/acpi_ecvar.h>
42#include <dev/acpi/wmi/wmi_acpivar.h>
43
44#define _COMPONENT          ACPI_RESOURCE_COMPONENT
45ACPI_MODULE_NAME            ("wmi_acpi")
46
47/*
48 * This implements something called "Microsoft Windows Management
49 * Instrumentation" (WMI). This subset of ACPI is desribed in:
50 *
51 * http://www.microsoft.com/whdc/system/pnppwr/wmi/wmi-acpi.mspx
52 *
53 * (Obtained on Thu Feb 12 18:21:44 EET 2009.)
54 */
55
56static int		acpi_wmi_match(device_t, cfdata_t, void *);
57static void		acpi_wmi_attach(device_t, device_t, void *);
58static int		acpi_wmi_detach(device_t, int);
59static int		acpi_wmi_rescan(device_t, const char *, const int *);
60static void		acpi_wmi_childdet(device_t, device_t);
61static int		acpi_wmi_print(void *, const char *);
62static bool		acpi_wmi_init(struct acpi_wmi_softc *);
63static void		acpi_wmi_init_ec(struct acpi_wmi_softc *);
64static void		acpi_wmi_add(struct acpi_wmi_softc *, ACPI_OBJECT *);
65static void		acpi_wmi_del(struct acpi_wmi_softc *);
66static void		acpi_wmi_dump(struct acpi_wmi_softc *);
67static ACPI_STATUS	acpi_wmi_guid_get(struct acpi_wmi_softc *,
68				const char *, struct wmi_t **);
69static void		acpi_wmi_event_add(struct acpi_wmi_softc *);
70static void		acpi_wmi_event_del(struct acpi_wmi_softc *);
71static void		acpi_wmi_event_handler(ACPI_HANDLE, uint32_t, void *);
72static ACPI_STATUS	acpi_wmi_ec_handler(uint32_t, ACPI_PHYSICAL_ADDRESS,
73				uint32_t, ACPI_INTEGER *, void *, void *);
74static bool		acpi_wmi_suspend(device_t, const pmf_qual_t *);
75static bool		acpi_wmi_resume(device_t, const pmf_qual_t *);
76static ACPI_STATUS	acpi_wmi_enable_event(ACPI_HANDLE, uint8_t, bool);
77static ACPI_STATUS	acpi_wmi_enable_collection(ACPI_HANDLE, const char *, bool);
78static bool		acpi_wmi_input(struct wmi_t *, uint8_t, uint8_t);
79
80static const struct device_compatible_entry compat_data[] = {
81	{ .compat = "PNP0C14" },
82	{ .compat = "pnp0c14" },
83	DEVICE_COMPAT_EOL
84};
85
86CFATTACH_DECL2_NEW(acpiwmi, sizeof(struct acpi_wmi_softc),
87    acpi_wmi_match, acpi_wmi_attach, acpi_wmi_detach, NULL,
88    acpi_wmi_rescan, acpi_wmi_childdet);
89
90static int
91acpi_wmi_match(device_t parent, cfdata_t match, void *aux)
92{
93	struct acpi_attach_args *aa = aux;
94
95	return acpi_compatible_match(aa, compat_data);
96}
97
98static void
99acpi_wmi_attach(device_t parent, device_t self, void *aux)
100{
101	struct acpi_wmi_softc *sc = device_private(self);
102	struct acpi_attach_args *aa = aux;
103
104	sc->sc_dev = self;
105	sc->sc_node = aa->aa_node;
106
107	sc->sc_child = NULL;
108	sc->sc_ecdev = NULL;
109	sc->sc_handler = NULL;
110
111	aprint_naive("\n");
112	aprint_normal(": ACPI WMI Interface\n");
113
114	if (acpi_wmi_init(sc) != true)
115		return;
116
117	acpi_wmi_dump(sc);
118	acpi_wmi_init_ec(sc);
119	acpi_wmi_event_add(sc);
120	acpi_wmi_rescan(self, NULL, NULL);
121
122	(void)pmf_device_register(self, acpi_wmi_suspend, acpi_wmi_resume);
123}
124
125static int
126acpi_wmi_detach(device_t self, int flags)
127{
128	struct acpi_wmi_softc *sc = device_private(self);
129
130	acpi_wmi_event_del(sc);
131
132	if (sc->sc_ecdev != NULL) {
133
134		(void)AcpiRemoveAddressSpaceHandler(sc->sc_node->ad_handle,
135		    ACPI_ADR_SPACE_EC, acpi_wmi_ec_handler);
136	}
137
138	if (sc->sc_child != NULL)
139		(void)config_detach(sc->sc_child, flags);
140
141	acpi_wmi_del(sc);
142	pmf_device_deregister(self);
143
144	return 0;
145}
146
147static int
148acpi_wmi_rescan(device_t self, const char *ifattr, const int *locators)
149{
150	struct acpi_wmi_softc *sc = device_private(self);
151
152	if (sc->sc_child == NULL) {
153		sc->sc_child =
154		    config_found(self, NULL, acpi_wmi_print, CFARG_EOL);
155	}
156
157	return 0;
158}
159
160static void
161acpi_wmi_childdet(device_t self, device_t child)
162{
163	struct acpi_wmi_softc *sc = device_private(self);
164
165	if (sc->sc_child == child)
166		sc->sc_child = NULL;
167}
168
169static int
170acpi_wmi_print(void *aux, const char *pnp)
171{
172
173	if (pnp != NULL)
174		aprint_normal("acpiwmibus at %s", pnp);
175
176	return UNCONF;
177}
178
179static bool
180acpi_wmi_init(struct acpi_wmi_softc *sc)
181{
182	ACPI_OBJECT *obj;
183	ACPI_BUFFER buf;
184	ACPI_STATUS rv;
185	uint32_t len;
186
187	rv = acpi_eval_struct(sc->sc_node->ad_handle, "_WDG", &buf);
188
189	if (ACPI_FAILURE(rv))
190		goto fail;
191
192	obj = buf.Pointer;
193
194	if (obj->Type != ACPI_TYPE_BUFFER) {
195		rv = AE_TYPE;
196		goto fail;
197	}
198
199	len = obj->Buffer.Length;
200
201	if (len != obj->Package.Count) {
202		rv = AE_BAD_VALUE;
203		goto fail;
204	}
205
206	CTASSERT(sizeof(struct guid_t) == 20);
207
208	if (len < sizeof(struct guid_t) ||
209	    len % sizeof(struct guid_t) != 0) {
210		rv = AE_BAD_DATA;
211		goto fail;
212	}
213
214	acpi_wmi_add(sc, obj);
215	return true;
216
217fail:
218	aprint_error_dev(sc->sc_dev, "failed to evaluate _WDG: %s\n",
219	    AcpiFormatException(rv));
220
221	if (buf.Pointer != NULL)
222		ACPI_FREE(buf.Pointer);
223
224	return false;
225}
226
227static void
228acpi_wmi_add(struct acpi_wmi_softc *sc, ACPI_OBJECT *obj)
229{
230	struct wmi_t *wmi;
231	size_t i, n, offset, siz;
232
233	siz = sizeof(struct guid_t);
234	n = obj->Buffer.Length / siz;
235
236	SIMPLEQ_INIT(&sc->wmi_head);
237
238	for (i = offset = 0; i < n; ++i) {
239
240		wmi = kmem_zalloc(sizeof(*wmi), KM_SLEEP);
241		(void)memcpy(&wmi->guid, obj->Buffer.Pointer + offset, siz);
242
243		wmi->eevent = false;
244		offset = offset + siz;
245
246		SIMPLEQ_INSERT_TAIL(&sc->wmi_head, wmi, wmi_link);
247	}
248
249	ACPI_FREE(obj);
250}
251
252static void
253acpi_wmi_del(struct acpi_wmi_softc *sc)
254{
255	struct wmi_t *wmi;
256
257	while (SIMPLEQ_FIRST(&sc->wmi_head) != NULL) {
258		wmi = SIMPLEQ_FIRST(&sc->wmi_head);
259		SIMPLEQ_REMOVE_HEAD(&sc->wmi_head, wmi_link);
260		kmem_free(wmi, sizeof(*wmi));
261	}
262}
263
264static void
265acpi_wmi_dump(struct acpi_wmi_softc *sc)
266{
267	struct wmi_t *wmi;
268
269	KASSERT(SIMPLEQ_EMPTY(&sc->wmi_head) == 0);
270
271	SIMPLEQ_FOREACH(wmi, &sc->wmi_head, wmi_link) {
272
273		aprint_debug_dev(sc->sc_dev, "{%08X-%04X-%04X-",
274		    wmi->guid.data1, wmi->guid.data2, wmi->guid.data3);
275
276		aprint_debug("%02X%02X-%02X%02X%02X%02X%02X%02X} ",
277		    wmi->guid.data4[0], wmi->guid.data4[1],
278		    wmi->guid.data4[2], wmi->guid.data4[3],
279		    wmi->guid.data4[4], wmi->guid.data4[5],
280		    wmi->guid.data4[6], wmi->guid.data4[7]);
281
282		aprint_debug("oid %04X count %02X flags %02X\n",
283		    UGET16(wmi->guid.oid), wmi->guid.count, wmi->guid.flags);
284	}
285}
286
287static void
288acpi_wmi_init_ec(struct acpi_wmi_softc *sc)
289{
290	ACPI_STATUS rv;
291	deviter_t i;
292	device_t d;
293
294	d = deviter_first(&i, DEVITER_F_ROOT_FIRST);
295
296	for (; d != NULL; d = deviter_next(&i)) {
297
298		if (device_is_a(d, "acpiec") != false ||
299		    device_is_a(d, "acpiecdt") != false) {
300			sc->sc_ecdev = d;
301			break;
302		}
303	}
304
305	deviter_release(&i);
306
307	if (sc->sc_ecdev == NULL)
308		return;
309
310	rv = AcpiInstallAddressSpaceHandler(sc->sc_node->ad_handle,
311	    ACPI_ADR_SPACE_EC, acpi_wmi_ec_handler, NULL, sc);
312
313	if (ACPI_FAILURE(rv))
314		sc->sc_ecdev = NULL;
315}
316
317static ACPI_STATUS
318acpi_wmi_guid_get(struct acpi_wmi_softc *sc,
319    const char *src, struct wmi_t **out)
320{
321	struct wmi_t *wmi;
322	struct guid_t *guid;
323	char bin[16];
324	char hex[3];
325	const char *ptr;
326	uint8_t i;
327
328	if (sc == NULL || src == NULL || strlen(src) != 36)
329		return AE_BAD_PARAMETER;
330
331	for (ptr = src, i = 0; i < 16; i++) {
332
333		if (*ptr == '-')
334			ptr++;
335
336		(void)memcpy(hex, ptr, 2);
337		hex[2] = '\0';
338
339		if (HEXCHAR(hex[0]) == 0 || HEXCHAR(hex[1]) == 0)
340			return AE_BAD_HEX_CONSTANT;
341
342		bin[i] = strtoul(hex, NULL, 16) & 0xFF;
343
344		ptr++;
345		ptr++;
346	}
347
348	guid = (struct guid_t *)bin;
349	guid->data1 = be32toh(guid->data1);
350	guid->data2 = be16toh(guid->data2);
351	guid->data3 = be16toh(guid->data3);
352
353	SIMPLEQ_FOREACH(wmi, &sc->wmi_head, wmi_link) {
354
355		if (GUIDCMP(guid, &wmi->guid) != 0) {
356
357			if (out != NULL)
358				*out = wmi;
359
360			return AE_OK;
361		}
362	}
363
364	return AE_NOT_FOUND;
365}
366
367/*
368 * Checks if a GUID is present. Child devices
369 * can use this in their autoconf(9) routines.
370 */
371int
372acpi_wmi_guid_match(device_t self, const char *guid)
373{
374	struct acpi_wmi_softc *sc = device_private(self);
375	ACPI_STATUS rv;
376
377	rv = acpi_wmi_guid_get(sc, guid, NULL);
378
379	if (ACPI_SUCCESS(rv))
380		return 1;
381
382	return 0;
383}
384
385/*
386 * Adds internal event handler.
387 */
388static void
389acpi_wmi_event_add(struct acpi_wmi_softc *sc)
390{
391	struct wmi_t *wmi;
392	ACPI_STATUS rv;
393
394	if (acpi_register_notify(sc->sc_node, acpi_wmi_event_handler) != true)
395		return;
396
397	/*
398	 * Enable possible events, expensive or otherwise.
399	 */
400	SIMPLEQ_FOREACH(wmi, &sc->wmi_head, wmi_link) {
401
402		if ((wmi->guid.flags & ACPI_WMI_FLAG_EVENT) != 0) {
403
404			rv = acpi_wmi_enable_event(sc->sc_node->ad_handle,
405			    wmi->guid.nid, true);
406
407			if (ACPI_SUCCESS(rv)) {
408				wmi->eevent = true;
409				continue;
410			}
411
412			aprint_debug_dev(sc->sc_dev, "failed to enable "
413			    "expensive WExx: %s\n", AcpiFormatException(rv));
414		}
415	}
416}
417
418/*
419 * Removes the internal event handler.
420 */
421static void
422acpi_wmi_event_del(struct acpi_wmi_softc *sc)
423{
424	struct wmi_t *wmi;
425	ACPI_STATUS rv;
426
427	acpi_deregister_notify(sc->sc_node);
428
429	SIMPLEQ_FOREACH(wmi, &sc->wmi_head, wmi_link) {
430
431		if (wmi->eevent != true)
432			continue;
433
434		KASSERT((wmi->guid.flags & ACPI_WMI_FLAG_EVENT) != 0);
435
436		rv = acpi_wmi_enable_event(sc->sc_node->ad_handle,
437		    wmi->guid.nid, false);
438
439		if (ACPI_SUCCESS(rv)) {
440			wmi->eevent = false;
441			continue;
442		}
443
444		aprint_debug_dev(sc->sc_dev, "failed to disable "
445		    "expensive WExx: %s\n", AcpiFormatException(rv));
446	}
447}
448
449/*
450 * Returns extra information possibly associated with an event.
451 */
452ACPI_STATUS
453acpi_wmi_event_get(device_t self, uint32_t event, ACPI_BUFFER *obuf)
454{
455	struct acpi_wmi_softc *sc = device_private(self);
456	struct wmi_t *wmi;
457	ACPI_OBJECT_LIST arg;
458	ACPI_OBJECT obj;
459	ACPI_HANDLE hdl;
460
461	if (sc == NULL || obuf == NULL)
462		return AE_BAD_PARAMETER;
463
464	if (sc->sc_handler == NULL)
465		return AE_ABORT_METHOD;
466
467	hdl = sc->sc_node->ad_handle;
468
469	obj.Type = ACPI_TYPE_INTEGER;
470	obj.Integer.Value = event;
471
472	arg.Count = 0x01;
473	arg.Pointer = &obj;
474
475	obuf->Pointer = NULL;
476	obuf->Length = ACPI_ALLOCATE_LOCAL_BUFFER;
477
478	SIMPLEQ_FOREACH(wmi, &sc->wmi_head, wmi_link) {
479
480		if ((wmi->guid.flags & ACPI_WMI_FLAG_EVENT) == 0)
481			continue;
482
483		if (wmi->guid.nid != event)
484			continue;
485
486		return AcpiEvaluateObject(hdl, "_WED", &arg, obuf);
487	}
488
489	return AE_NOT_FOUND;
490}
491
492/*
493 * Forwards events to the external handler through the internal one.
494 */
495static void
496acpi_wmi_event_handler(ACPI_HANDLE hdl, uint32_t evt, void *aux)
497{
498	struct acpi_wmi_softc *sc;
499	device_t self = aux;
500
501	sc = device_private(self);
502
503	if (sc->sc_child == NULL)
504		return;
505
506	if (sc->sc_handler == NULL)
507		return;
508
509	(*sc->sc_handler)(NULL, evt, sc->sc_child);
510}
511
512ACPI_STATUS
513acpi_wmi_event_register(device_t self, ACPI_NOTIFY_HANDLER handler)
514{
515	struct acpi_wmi_softc *sc = device_private(self);
516
517	if (sc == NULL)
518		return AE_BAD_PARAMETER;
519
520	if (handler != NULL && sc->sc_handler != NULL)
521		return AE_ALREADY_EXISTS;
522
523	sc->sc_handler = handler;
524
525	return AE_OK;
526}
527
528ACPI_STATUS
529acpi_wmi_event_deregister(device_t self)
530{
531	return acpi_wmi_event_register(self, NULL);
532}
533
534/*
535 * Handler for EC regions, which may be embedded in WMI.
536 */
537static ACPI_STATUS
538acpi_wmi_ec_handler(uint32_t func, ACPI_PHYSICAL_ADDRESS addr,
539    uint32_t width, ACPI_INTEGER *val, void *setup, void *aux)
540{
541	struct acpi_wmi_softc *sc = aux;
542
543	if (aux == NULL || val == NULL)
544		return AE_BAD_PARAMETER;
545
546	if (addr > 0xFF || width % 8 != 0)
547		return AE_BAD_ADDRESS;
548
549	switch (func) {
550
551	case ACPI_READ:
552		(void)acpiec_bus_read(sc->sc_ecdev, addr, val, width);
553		break;
554
555	case ACPI_WRITE:
556		(void)acpiec_bus_write(sc->sc_ecdev, addr, *val, width);
557		break;
558
559	default:
560		return AE_BAD_PARAMETER;
561	}
562
563	return AE_OK;
564}
565
566/*
567 * As there is no prior knowledge about the expensive
568 * events that cause "significant overhead", try to
569 * disable (enable) these before suspending (resuming).
570 */
571static bool
572acpi_wmi_suspend(device_t self, const pmf_qual_t *qual)
573{
574	struct acpi_wmi_softc *sc = device_private(self);
575
576	acpi_wmi_event_del(sc);
577
578	return true;
579}
580
581static bool
582acpi_wmi_resume(device_t self, const pmf_qual_t *qual)
583{
584	struct acpi_wmi_softc *sc = device_private(self);
585
586	acpi_wmi_event_add(sc);
587
588	return true;
589}
590
591static ACPI_STATUS
592acpi_wmi_enable_event(ACPI_HANDLE hdl, uint8_t nid, bool flag)
593{
594	char path[5];
595
596	snprintf(path, sizeof(path), "WE%02X", nid);
597
598	return acpi_eval_set_integer(hdl, path, (flag != false) ? 0x01 : 0x00);
599}
600
601static ACPI_STATUS
602acpi_wmi_enable_collection(ACPI_HANDLE hdl, const char *oid, bool flag)
603{
604	char path[5];
605
606	strlcpy(path, "WC", sizeof(path));
607	strlcat(path, oid, sizeof(path));
608
609	return acpi_eval_set_integer(hdl, path, (flag != false) ? 0x01 : 0x00);
610}
611
612static bool
613acpi_wmi_input(struct wmi_t *wmi, uint8_t flag, uint8_t idx)
614{
615	/* A data block may have no flags at all */
616	if ((wmi->guid.flags & flag) == 0 &&
617	    (flag == ACPI_WMI_FLAG_DATA  &&
618	     (wmi->guid.flags & ~ACPI_WMI_FLAG_EXPENSIVE) != 0))
619		return false;
620
621	if (wmi->guid.count == 0x00)
622		return false;
623
624	if (wmi->guid.count < idx)
625		return false;
626
627	return true;
628}
629
630/*
631 * Makes a WMI data block query (WQxx). The corresponding control
632 * method for data collection will be invoked if it is available.
633 */
634ACPI_STATUS
635acpi_wmi_data_query(device_t self, const char *guid,
636    uint8_t idx, ACPI_BUFFER *obuf)
637{
638	struct acpi_wmi_softc *sc = device_private(self);
639	struct wmi_t *wmi;
640	char path[5] = "WQ";
641	ACPI_OBJECT_LIST arg;
642	ACPI_STATUS rv, rvxx;
643	ACPI_OBJECT obj;
644
645	rvxx = AE_SUPPORT;
646
647	if (obuf == NULL)
648		return AE_BAD_PARAMETER;
649
650	rv = acpi_wmi_guid_get(sc, guid, &wmi);
651
652	if (ACPI_FAILURE(rv))
653		return rv;
654
655	if (acpi_wmi_input(wmi, ACPI_WMI_FLAG_DATA, idx) != true)
656		return AE_BAD_DATA;
657
658	(void)strlcat(path, wmi->guid.oid, sizeof(path));
659
660	obj.Type = ACPI_TYPE_INTEGER;
661	obj.Integer.Value = idx;
662
663	arg.Count = 0x01;
664	arg.Pointer = &obj;
665
666	obuf->Pointer = NULL;
667	obuf->Length = ACPI_ALLOCATE_LOCAL_BUFFER;
668
669	/*
670	 * If the expensive flag is set, we should enable
671	 * data collection before evaluating the WQxx buffer.
672	 */
673	if ((wmi->guid.flags & ACPI_WMI_FLAG_EXPENSIVE) != 0) {
674
675		rvxx = acpi_wmi_enable_collection(sc->sc_node->ad_handle,
676		    wmi->guid.oid, true);
677	}
678
679	rv = AcpiEvaluateObject(sc->sc_node->ad_handle, path, &arg, obuf);
680
681	/* No longer needed. */
682	if (ACPI_SUCCESS(rvxx)) {
683
684		(void)acpi_wmi_enable_collection(sc->sc_node->ad_handle,
685		    wmi->guid.oid, false);
686	}
687
688#ifdef DIAGNOSTIC
689	/*
690	 * XXX: It appears that quite a few laptops have WQxx
691	 * methods that are declared as expensive, but lack the
692	 * corresponding WCxx control method.
693	 *
694	 * -- Acer Aspire One is one example <jruohonen@iki.fi>.
695	 */
696	if (ACPI_FAILURE(rvxx) && rvxx != AE_SUPPORT)
697		aprint_error_dev(sc->sc_dev, "failed to evaluate WCxx "
698		    "for %s: %s\n", path, AcpiFormatException(rvxx));
699#endif
700	return rv;
701}
702
703/*
704 * Writes to a data block (WSxx).
705 */
706ACPI_STATUS
707acpi_wmi_data_write(device_t self, const char *guid,
708    uint8_t idx, ACPI_BUFFER *ibuf)
709{
710	struct acpi_wmi_softc *sc = device_private(self);
711	struct wmi_t *wmi;
712	ACPI_OBJECT_LIST arg;
713	ACPI_OBJECT obj[2];
714	char path[5] = "WS";
715	ACPI_STATUS rv;
716
717	if (ibuf == NULL)
718		return AE_BAD_PARAMETER;
719
720	rv = acpi_wmi_guid_get(sc, guid, &wmi);
721
722	if (ACPI_FAILURE(rv))
723		return rv;
724
725	if (acpi_wmi_input(wmi, ACPI_WMI_FLAG_DATA, idx) != true)
726		return AE_BAD_DATA;
727
728	(void)strlcat(path, wmi->guid.oid, sizeof(path));
729
730	obj[0].Integer.Value = idx;
731	obj[0].Type = ACPI_TYPE_INTEGER;
732
733	obj[1].Buffer.Length = ibuf->Length;
734	obj[1].Buffer.Pointer = ibuf->Pointer;
735
736	obj[1].Type = ((wmi->guid.flags & ACPI_WMI_FLAG_STRING) != 0) ?
737	    ACPI_TYPE_STRING : ACPI_TYPE_BUFFER;
738
739	arg.Count = 0x02;
740	arg.Pointer = obj;
741
742	return AcpiEvaluateObject(sc->sc_node->ad_handle, path, &arg, NULL);
743}
744
745/*
746 * Executes a method (WMxx).
747 */
748ACPI_STATUS
749acpi_wmi_method(device_t self, const char *guid, uint8_t idx,
750    uint32_t mid, ACPI_BUFFER *ibuf, ACPI_BUFFER *obuf)
751{
752	struct acpi_wmi_softc *sc = device_private(self);
753	struct wmi_t *wmi;
754	ACPI_OBJECT_LIST arg;
755	ACPI_OBJECT obj[3];
756	char path[5] = "WM";
757	ACPI_STATUS rv;
758
759	if (ibuf == NULL || obuf == NULL)
760		return AE_BAD_PARAMETER;
761
762	rv = acpi_wmi_guid_get(sc, guid, &wmi);
763
764	if (ACPI_FAILURE(rv))
765		return rv;
766
767	if (acpi_wmi_input(wmi, ACPI_WMI_FLAG_METHOD, idx) != true)
768		return AE_BAD_DATA;
769
770	(void)strlcat(path, wmi->guid.oid, sizeof(path));
771
772	obj[0].Integer.Value = idx;
773	obj[1].Integer.Value = mid;
774	obj[0].Type = obj[1].Type = ACPI_TYPE_INTEGER;
775
776	obj[2].Buffer.Length = ibuf->Length;
777	obj[2].Buffer.Pointer = ibuf->Pointer;
778
779	obj[2].Type = ((wmi->guid.flags & ACPI_WMI_FLAG_STRING) != 0) ?
780	    ACPI_TYPE_STRING : ACPI_TYPE_BUFFER;
781
782	arg.Count = 0x03;
783	arg.Pointer = obj;
784
785	obuf->Pointer = NULL;
786	obuf->Length = ACPI_ALLOCATE_LOCAL_BUFFER;
787
788	return AcpiEvaluateObject(sc->sc_node->ad_handle, path, &arg, obuf);
789}
790
791MODULE(MODULE_CLASS_DRIVER, acpiwmi, NULL);
792
793#ifdef _MODULE
794#include "ioconf.c"
795#endif
796
797static int
798acpiwmi_modcmd(modcmd_t cmd, void *aux)
799{
800	int rv = 0;
801
802	switch (cmd) {
803
804	case MODULE_CMD_INIT:
805
806#ifdef _MODULE
807		rv = config_init_component(cfdriver_ioconf_acpiwmi,
808		    cfattach_ioconf_acpiwmi, cfdata_ioconf_acpiwmi);
809#endif
810		break;
811
812	case MODULE_CMD_FINI:
813
814#ifdef _MODULE
815		rv = config_fini_component(cfdriver_ioconf_acpiwmi,
816		    cfattach_ioconf_acpiwmi, cfdata_ioconf_acpiwmi);
817#endif
818		break;
819
820	default:
821		rv = ENOTTY;
822	}
823
824	return rv;
825}
826