1 /* $NetBSD: wmi_acpi.c,v 1.7 2010/08/06 22:45:00 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.7 2010/08/06 22:45:00 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 38 #include <dev/acpi/acpireg.h> 39 #include <dev/acpi/acpivar.h> 40 #include <dev/acpi/wmi/wmi_acpivar.h> 41 42 #define _COMPONENT ACPI_RESOURCE_COMPONENT 43 ACPI_MODULE_NAME ("wmi_acpi") 44 45 /* 46 * This implements something called "Microsoft Windows Management 47 * Instrumentation" (WMI). This subset of ACPI is desribed in: 48 * 49 * http://www.microsoft.com/whdc/system/pnppwr/wmi/wmi-acpi.mspx 50 * 51 * (Obtained on Thu Feb 12 18:21:44 EET 2009.) 52 */ 53 54 static int acpi_wmi_match(device_t, cfdata_t, void *); 55 static void acpi_wmi_attach(device_t, device_t, void *); 56 static int acpi_wmi_detach(device_t, int); 57 static int acpi_wmi_print(void *, const char *); 58 static bool acpi_wmi_init(struct acpi_wmi_softc *); 59 static bool acpi_wmi_add(struct acpi_wmi_softc *, ACPI_OBJECT *); 60 static void acpi_wmi_del(struct acpi_wmi_softc *); 61 static void acpi_wmi_dump(struct acpi_wmi_softc *); 62 63 static ACPI_STATUS acpi_wmi_guid_get(struct acpi_wmi_softc *, 64 const char *, struct wmi_t **); 65 static void acpi_wmi_event_add(struct acpi_wmi_softc *); 66 static void acpi_wmi_event_del(struct acpi_wmi_softc *); 67 static void acpi_wmi_event_handler(ACPI_HANDLE, uint32_t, void *); 68 static bool acpi_wmi_suspend(device_t, const pmf_qual_t *); 69 static bool acpi_wmi_resume(device_t, const pmf_qual_t *); 70 static ACPI_STATUS acpi_wmi_enable(ACPI_HANDLE, const char *, bool, bool); 71 static bool acpi_wmi_input(struct wmi_t *, uint8_t, uint8_t); 72 73 const char * const acpi_wmi_ids[] = { 74 "PNP0C14", 75 "pnp0c14", 76 NULL 77 }; 78 79 CFATTACH_DECL_NEW(acpiwmi, sizeof(struct acpi_wmi_softc), 80 acpi_wmi_match, acpi_wmi_attach, acpi_wmi_detach, NULL); 81 82 static int 83 acpi_wmi_match(device_t parent, cfdata_t match, void *aux) 84 { 85 struct acpi_attach_args *aa = aux; 86 87 if (aa->aa_node->ad_type != ACPI_TYPE_DEVICE) 88 return 0; 89 90 return acpi_match_hid(aa->aa_node->ad_devinfo, acpi_wmi_ids); 91 } 92 93 static void 94 acpi_wmi_attach(device_t parent, device_t self, void *aux) 95 { 96 struct acpi_wmi_softc *sc = device_private(self); 97 struct acpi_attach_args *aa = aux; 98 99 sc->sc_dev = self; 100 sc->sc_node = aa->aa_node; 101 102 sc->sc_child = NULL; 103 sc->sc_handler = NULL; 104 105 aprint_naive("\n"); 106 aprint_normal(": ACPI WMI Interface\n"); 107 108 if (acpi_wmi_init(sc) != true) 109 return; 110 111 acpi_wmi_dump(sc); 112 acpi_wmi_event_add(sc); 113 114 sc->sc_child = config_found_ia(self, "acpiwmibus", 115 NULL, acpi_wmi_print); 116 117 (void)pmf_device_register(self, acpi_wmi_suspend, acpi_wmi_resume); 118 } 119 120 static int 121 acpi_wmi_detach(device_t self, int flags) 122 { 123 struct acpi_wmi_softc *sc = device_private(self); 124 125 acpi_wmi_event_del(sc); 126 127 if (sc->sc_child != NULL) 128 (void)config_detach(sc->sc_child, flags); 129 130 acpi_wmi_del(sc); 131 pmf_device_deregister(self); 132 133 return 0; 134 } 135 136 static int 137 acpi_wmi_print(void *aux, const char *pnp) 138 { 139 140 if (pnp != NULL) 141 aprint_normal("acpiwmibus at %s", pnp); 142 143 return UNCONF; 144 } 145 146 static bool 147 acpi_wmi_init(struct acpi_wmi_softc *sc) 148 { 149 ACPI_OBJECT *obj; 150 ACPI_BUFFER buf; 151 ACPI_STATUS rv; 152 uint32_t len; 153 154 rv = acpi_eval_struct(sc->sc_node->ad_handle, "_WDG", &buf); 155 156 if (ACPI_FAILURE(rv)) 157 goto fail; 158 159 obj = buf.Pointer; 160 161 if (obj->Type != ACPI_TYPE_BUFFER) { 162 rv = AE_TYPE; 163 goto fail; 164 } 165 166 len = obj->Buffer.Length; 167 168 if (len != obj->Package.Count) { 169 rv = AE_BAD_VALUE; 170 goto fail; 171 } 172 173 CTASSERT(sizeof(struct guid_t) == 20); 174 175 if (len < sizeof(struct guid_t) || 176 len % sizeof(struct guid_t) != 0) { 177 rv = AE_BAD_DATA; 178 goto fail; 179 } 180 181 return acpi_wmi_add(sc, obj); 182 183 fail: 184 aprint_error_dev(sc->sc_dev, "failed to evaluate _WDG: %s\n", 185 AcpiFormatException(rv)); 186 187 if (buf.Pointer != NULL) 188 ACPI_FREE(buf.Pointer); 189 190 return false; 191 } 192 193 static bool 194 acpi_wmi_add(struct acpi_wmi_softc *sc, ACPI_OBJECT *obj) 195 { 196 struct wmi_t *wmi; 197 size_t i, n, offset, siz; 198 199 siz = sizeof(struct guid_t); 200 n = obj->Buffer.Length / siz; 201 202 SIMPLEQ_INIT(&sc->wmi_head); 203 204 for (i = offset = 0; i < n; ++i) { 205 206 if ((wmi = kmem_zalloc(sizeof(*wmi), KM_NOSLEEP)) == NULL) 207 goto fail; 208 209 (void)memcpy(&wmi->guid, obj->Buffer.Pointer + offset, siz); 210 211 wmi->eevent = false; 212 offset = offset + siz; 213 214 SIMPLEQ_INSERT_TAIL(&sc->wmi_head, wmi, wmi_link); 215 } 216 217 ACPI_FREE(obj); 218 219 return true; 220 221 fail: 222 ACPI_FREE(obj); 223 acpi_wmi_del(sc); 224 225 return false; 226 } 227 228 static void 229 acpi_wmi_del(struct acpi_wmi_softc *sc) 230 { 231 struct wmi_t *wmi; 232 233 if (SIMPLEQ_EMPTY(&sc->wmi_head) != 0) 234 return; 235 236 while (SIMPLEQ_FIRST(&sc->wmi_head) != NULL) { 237 238 wmi = SIMPLEQ_FIRST(&sc->wmi_head); 239 SIMPLEQ_REMOVE_HEAD(&sc->wmi_head, wmi_link); 240 241 KASSERT(wmi != NULL); 242 243 kmem_free(wmi, sizeof(*wmi)); 244 } 245 } 246 247 static void 248 acpi_wmi_dump(struct acpi_wmi_softc *sc) 249 { 250 struct wmi_t *wmi; 251 252 KASSERT(SIMPLEQ_EMPTY(&sc->wmi_head) == 0); 253 254 SIMPLEQ_FOREACH(wmi, &sc->wmi_head, wmi_link) { 255 256 aprint_debug_dev(sc->sc_dev, "{%08X-%04X-%04X-", 257 wmi->guid.data1, wmi->guid.data2, wmi->guid.data3); 258 259 aprint_debug("%02X%02X-%02X%02X%02X%02X%02X%02X} ", 260 wmi->guid.data4[0], wmi->guid.data4[1], 261 wmi->guid.data4[2], wmi->guid.data4[3], 262 wmi->guid.data4[4], wmi->guid.data4[5], 263 wmi->guid.data4[6], wmi->guid.data4[7]); 264 265 aprint_debug("oid %04X count %02X flags %02X\n", 266 UGET16(wmi->guid.oid), wmi->guid.count, wmi->guid.flags); 267 } 268 } 269 270 static ACPI_STATUS 271 acpi_wmi_guid_get(struct acpi_wmi_softc *sc, 272 const char *src, struct wmi_t **out) 273 { 274 struct wmi_t *wmi; 275 struct guid_t *guid; 276 char bin[16]; 277 char hex[2]; 278 const char *ptr; 279 uint8_t i; 280 281 if (sc == NULL || src == NULL || strlen(src) != 36) 282 return AE_BAD_PARAMETER; 283 284 for (ptr = src, i = 0; i < 16; i++) { 285 286 if (*ptr == '-') 287 ptr++; 288 289 (void)memcpy(hex, ptr, 2); 290 291 if (HEXCHAR(hex[0]) == 0 || HEXCHAR(hex[1]) == 0) 292 return AE_BAD_HEX_CONSTANT; 293 294 bin[i] = strtoul(hex, NULL, 16) & 0xFF; 295 296 ptr++; 297 ptr++; 298 } 299 300 guid = (struct guid_t *)bin; 301 guid->data1 = be32toh(guid->data1); 302 guid->data2 = be16toh(guid->data2); 303 guid->data3 = be16toh(guid->data3); 304 305 SIMPLEQ_FOREACH(wmi, &sc->wmi_head, wmi_link) { 306 307 if (GUIDCMP(guid, &wmi->guid) != 0) { 308 309 if (out != NULL) 310 *out = wmi; 311 312 return AE_OK; 313 } 314 } 315 316 return AE_NOT_FOUND; 317 } 318 319 /* 320 * Checks if a GUID is present. Child devices 321 * can use this in their autoconf(9) routines. 322 */ 323 int 324 acpi_wmi_guid_match(device_t self, const char *guid) 325 { 326 struct acpi_wmi_softc *sc = device_private(self); 327 ACPI_STATUS rv; 328 329 rv = acpi_wmi_guid_get(sc, guid, NULL); 330 331 if (ACPI_SUCCESS(rv)) 332 return 1; 333 334 return 0; 335 } 336 337 /* 338 * Adds internal event handler. 339 */ 340 static void 341 acpi_wmi_event_add(struct acpi_wmi_softc *sc) 342 { 343 struct wmi_t *wmi; 344 ACPI_STATUS rv; 345 346 if (acpi_register_notify(sc->sc_node, acpi_wmi_event_handler) != true) 347 return; 348 349 /* 350 * Enable possible expensive events. 351 */ 352 SIMPLEQ_FOREACH(wmi, &sc->wmi_head, wmi_link) { 353 354 if ((wmi->guid.flags & ACPI_WMI_FLAG_EVENT) != 0 && 355 (wmi->guid.flags & ACPI_WMI_FLAG_EXPENSIVE) != 0) { 356 357 rv = acpi_wmi_enable(sc->sc_node->ad_handle, 358 wmi->guid.oid, false, true); 359 360 if (ACPI_SUCCESS(rv)) { 361 wmi->eevent = true; 362 continue; 363 } 364 365 aprint_debug_dev(sc->sc_dev, "failed to enable " 366 "expensive WExx: %s\n", AcpiFormatException(rv)); 367 } 368 } 369 } 370 371 /* 372 * Removes the internal event handler. 373 */ 374 static void 375 acpi_wmi_event_del(struct acpi_wmi_softc *sc) 376 { 377 struct wmi_t *wmi; 378 ACPI_STATUS rv; 379 380 acpi_deregister_notify(sc->sc_node); 381 382 SIMPLEQ_FOREACH(wmi, &sc->wmi_head, wmi_link) { 383 384 if (wmi->eevent != true) 385 continue; 386 387 KASSERT((wmi->guid.flags & ACPI_WMI_FLAG_EVENT) != 0); 388 KASSERT((wmi->guid.flags & ACPI_WMI_FLAG_EXPENSIVE) != 0); 389 390 rv = acpi_wmi_enable(sc->sc_node->ad_handle, 391 wmi->guid.oid, false, false); 392 393 if (ACPI_SUCCESS(rv)) { 394 wmi->eevent = false; 395 continue; 396 } 397 398 aprint_debug_dev(sc->sc_dev, "failed to disable " 399 "expensive WExx: %s\n", AcpiFormatException(rv)); 400 } 401 } 402 403 /* 404 * Returns extra information possibly associated with an event. 405 */ 406 ACPI_STATUS 407 acpi_wmi_event_get(device_t self, uint32_t event, ACPI_BUFFER *obuf) 408 { 409 struct acpi_wmi_softc *sc = device_private(self); 410 struct wmi_t *wmi; 411 ACPI_OBJECT_LIST arg; 412 ACPI_OBJECT obj; 413 ACPI_HANDLE hdl; 414 415 if (sc == NULL || obuf == NULL) 416 return AE_BAD_PARAMETER; 417 418 if (sc->sc_handler == NULL) 419 return AE_ABORT_METHOD; 420 421 hdl = sc->sc_node->ad_handle; 422 423 obj.Type = ACPI_TYPE_INTEGER; 424 obj.Integer.Value = event; 425 426 arg.Count = 0x01; 427 arg.Pointer = &obj; 428 429 obuf->Pointer = NULL; 430 obuf->Length = ACPI_ALLOCATE_LOCAL_BUFFER; 431 432 SIMPLEQ_FOREACH(wmi, &sc->wmi_head, wmi_link) { 433 434 if ((wmi->guid.flags & ACPI_WMI_FLAG_EVENT) == 0) 435 continue; 436 437 if (wmi->guid.nid != event) 438 continue; 439 440 return AcpiEvaluateObject(hdl, "_WED", &arg, obuf); 441 } 442 443 return AE_NOT_FOUND; 444 } 445 446 /* 447 * Forwards events to the external handler through the internal one. 448 */ 449 static void 450 acpi_wmi_event_handler(ACPI_HANDLE hdl, uint32_t evt, void *aux) 451 { 452 struct acpi_wmi_softc *sc; 453 device_t self = aux; 454 455 sc = device_private(self); 456 457 if (sc->sc_child == NULL) 458 return; 459 460 if (sc->sc_handler == NULL) 461 return; 462 463 (*sc->sc_handler)(NULL, evt, sc->sc_child); 464 } 465 466 ACPI_STATUS 467 acpi_wmi_event_register(device_t self, ACPI_NOTIFY_HANDLER handler) 468 { 469 struct acpi_wmi_softc *sc = device_private(self); 470 471 if (sc == NULL) 472 return AE_BAD_PARAMETER; 473 474 if (handler != NULL && sc->sc_handler != NULL) 475 return AE_ALREADY_EXISTS; 476 477 sc->sc_handler = handler; 478 479 return AE_OK; 480 } 481 482 ACPI_STATUS 483 acpi_wmi_event_deregister(device_t self) 484 { 485 return acpi_wmi_event_register(self, NULL); 486 } 487 488 /* 489 * As there is no prior knowledge about the expensive 490 * events that cause "significant overhead", try to 491 * disable (enable) these before suspending (resuming). 492 */ 493 static bool 494 acpi_wmi_suspend(device_t self, const pmf_qual_t *qual) 495 { 496 struct acpi_wmi_softc *sc = device_private(self); 497 498 acpi_wmi_event_del(sc); 499 500 return true; 501 } 502 503 static bool 504 acpi_wmi_resume(device_t self, const pmf_qual_t *qual) 505 { 506 struct acpi_wmi_softc *sc = device_private(self); 507 508 acpi_wmi_event_add(sc); 509 510 return true; 511 } 512 513 /* 514 * Enables or disables data collection (WCxx) or an event (WExx). 515 */ 516 static ACPI_STATUS 517 acpi_wmi_enable(ACPI_HANDLE hdl, const char *oid, bool data, bool flag) 518 { 519 char path[5]; 520 const char *str; 521 522 str = (data != false) ? "WC" : "WE"; 523 524 (void)strlcpy(path, str, sizeof(path)); 525 (void)strlcat(path, oid, sizeof(path)); 526 527 return acpi_eval_set_integer(hdl, path, (flag != false) ? 0x01 : 0x00); 528 } 529 530 static bool 531 acpi_wmi_input(struct wmi_t *wmi, uint8_t flag, uint8_t idx) 532 { 533 534 if ((wmi->guid.flags & flag) == 0) 535 return false; 536 537 if (wmi->guid.count == 0x00) 538 return false; 539 540 if (wmi->guid.count < idx) 541 return false; 542 543 return true; 544 } 545 546 /* 547 * Makes a WMI data block query (WQxx). The corresponding control 548 * method for data collection will be invoked if it is available. 549 */ 550 ACPI_STATUS 551 acpi_wmi_data_query(device_t self, const char *guid, 552 uint8_t idx, ACPI_BUFFER *obuf) 553 { 554 struct acpi_wmi_softc *sc = device_private(self); 555 struct wmi_t *wmi; 556 char path[5] = "WQ"; 557 ACPI_OBJECT_LIST arg; 558 ACPI_STATUS rv, rvxx; 559 ACPI_OBJECT obj; 560 561 rvxx = AE_SUPPORT; 562 563 if (obuf == NULL) 564 return AE_BAD_PARAMETER; 565 566 rv = acpi_wmi_guid_get(sc, guid, &wmi); 567 568 if (ACPI_FAILURE(rv)) 569 return rv; 570 571 if (acpi_wmi_input(wmi, ACPI_WMI_FLAG_DATA, idx) != true) 572 return AE_BAD_DATA; 573 574 (void)strlcat(path, wmi->guid.oid, sizeof(path)); 575 576 obj.Type = ACPI_TYPE_INTEGER; 577 obj.Integer.Value = idx; 578 579 arg.Count = 0x01; 580 arg.Pointer = &obj; 581 582 obuf->Pointer = NULL; 583 obuf->Length = ACPI_ALLOCATE_LOCAL_BUFFER; 584 585 /* 586 * If the expensive flag is set, we should enable 587 * data collection before evaluating the WQxx buffer. 588 */ 589 if ((wmi->guid.flags & ACPI_WMI_FLAG_EXPENSIVE) != 0) { 590 591 rvxx = acpi_wmi_enable(sc->sc_node->ad_handle, 592 wmi->guid.oid, true, true); 593 } 594 595 rv = AcpiEvaluateObject(sc->sc_node->ad_handle, path, &arg, obuf); 596 597 /* No longer needed. */ 598 if (ACPI_SUCCESS(rvxx)) { 599 600 (void)acpi_wmi_enable(sc->sc_node->ad_handle, 601 wmi->guid.oid, true, false); 602 } 603 604 #ifdef DIAGNOSTIC 605 /* 606 * XXX: It appears that quite a few laptops have WQxx 607 * methods that are declared as expensive, but lack the 608 * corresponding WCxx control method. 609 * 610 * -- Acer Aspire One is one example <jruohonen@iki.fi>. 611 */ 612 if (ACPI_FAILURE(rvxx) && rvxx != AE_SUPPORT) 613 aprint_error_dev(sc->sc_dev, "failed to evaluate WCxx " 614 "for %s: %s\n", path, AcpiFormatException(rvxx)); 615 #endif 616 return rv; 617 } 618 619 /* 620 * Writes to a data block (WSxx). 621 */ 622 ACPI_STATUS 623 acpi_wmi_data_write(device_t self, const char *guid, 624 uint8_t idx, ACPI_BUFFER *ibuf) 625 { 626 struct acpi_wmi_softc *sc = device_private(self); 627 struct wmi_t *wmi; 628 ACPI_OBJECT_LIST arg; 629 ACPI_OBJECT obj[2]; 630 char path[5] = "WS"; 631 ACPI_STATUS rv; 632 633 if (ibuf == NULL) 634 return AE_BAD_PARAMETER; 635 636 rv = acpi_wmi_guid_get(sc, guid, &wmi); 637 638 if (ACPI_FAILURE(rv)) 639 return rv; 640 641 if (acpi_wmi_input(wmi, ACPI_WMI_FLAG_DATA, idx) != true) 642 return AE_BAD_DATA; 643 644 (void)strlcat(path, wmi->guid.oid, sizeof(path)); 645 646 obj[0].Integer.Value = idx; 647 obj[0].Type = ACPI_TYPE_INTEGER; 648 649 obj[1].Buffer.Length = ibuf->Length; 650 obj[1].Buffer.Pointer = ibuf->Pointer; 651 652 obj[1].Type = ((wmi->guid.flags & ACPI_WMI_FLAG_STRING) != 0) ? 653 ACPI_TYPE_STRING : ACPI_TYPE_BUFFER; 654 655 arg.Count = 0x02; 656 arg.Pointer = obj; 657 658 return AcpiEvaluateObject(sc->sc_node->ad_handle, path, &arg, NULL); 659 } 660 661 /* 662 * Executes a method (WMxx). 663 */ 664 ACPI_STATUS 665 acpi_wmi_method(device_t self, const char *guid, uint8_t idx, 666 uint32_t mid, ACPI_BUFFER *ibuf, ACPI_BUFFER *obuf) 667 { 668 struct acpi_wmi_softc *sc = device_private(self); 669 struct wmi_t *wmi; 670 ACPI_OBJECT_LIST arg; 671 ACPI_OBJECT obj[3]; 672 char path[5] = "WM"; 673 ACPI_STATUS rv; 674 675 if (ibuf == NULL || obuf == NULL) 676 return AE_BAD_PARAMETER; 677 678 rv = acpi_wmi_guid_get(sc, guid, &wmi); 679 680 if (ACPI_FAILURE(rv)) 681 return rv; 682 683 if (acpi_wmi_input(wmi, ACPI_WMI_FLAG_METHOD, idx) != true) 684 return AE_BAD_DATA; 685 686 (void)strlcat(path, wmi->guid.oid, sizeof(path)); 687 688 obj[0].Integer.Value = idx; 689 obj[1].Integer.Value = mid; 690 obj[0].Type = obj[1].Type = ACPI_TYPE_INTEGER; 691 692 obj[2].Buffer.Length = ibuf->Length; 693 obj[2].Buffer.Pointer = ibuf->Pointer; 694 695 obj[2].Type = ((wmi->guid.flags & ACPI_WMI_FLAG_STRING) != 0) ? 696 ACPI_TYPE_STRING : ACPI_TYPE_BUFFER; 697 698 arg.Count = 0x03; 699 arg.Pointer = obj; 700 701 obuf->Pointer = NULL; 702 obuf->Length = ACPI_ALLOCATE_LOCAL_BUFFER; 703 704 return AcpiEvaluateObject(sc->sc_node->ad_handle, path, &arg, obuf); 705 } 706