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