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