1// Copyright 2017 The Fuchsia Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "dev.h" 6 7#include <hw/inout.h> 8#include <ddk/debug.h> 9#include <ddk/driver.h> 10#include <zircon/syscalls.h> 11#include <zircon/types.h> 12#include <threads.h> 13 14#include "errors.h" 15 16#define xprintf(fmt...) zxlogf(SPEW, fmt) 17 18/* EC commands */ 19#define EC_CMD_READ 0x80 20#define EC_CMD_WRITE 0x81 21#define EC_CMD_QUERY 0x84 22 23/* EC status register bits */ 24#define EC_SC_SCI_EVT (1 << 5) 25#define EC_SC_IBF (1 << 1) 26#define EC_SC_OBF (1 << 0) 27 28/* Thread signals */ 29#define IRQ_RECEIVED ZX_EVENT_SIGNALED 30#define EC_THREAD_SHUTDOWN ZX_USER_SIGNAL_0 31#define EC_THREAD_SHUTDOWN_DONE ZX_USER_SIGNAL_1 32 33typedef struct acpi_ec_device { 34 zx_device_t* zxdev; 35 36 ACPI_HANDLE acpi_handle; 37 38 // PIO addresses for EC device 39 uint16_t cmd_port; 40 uint16_t data_port; 41 42 // GPE for EC events 43 ACPI_HANDLE gpe_block; 44 UINT32 gpe; 45 46 // thread for processing events from the EC 47 thrd_t evt_thread; 48 49 zx_handle_t interrupt_event; 50 51 bool gpe_setup : 1; 52 bool thread_setup : 1; 53 bool ec_space_setup : 1; 54} acpi_ec_device_t; 55 56static ACPI_STATUS get_ec_handle(ACPI_HANDLE, UINT32, void*, void**); 57static ACPI_STATUS get_ec_gpe_info(ACPI_HANDLE, ACPI_HANDLE*, UINT32*); 58static ACPI_STATUS get_ec_ports(ACPI_HANDLE, uint16_t*, uint16_t*); 59 60static ACPI_STATUS ec_space_setup_handler(ACPI_HANDLE Region, UINT32 Function, 61 void* HandlerContext, void** ReturnContext); 62static ACPI_STATUS ec_space_request_handler(UINT32 Function, ACPI_PHYSICAL_ADDRESS Address, 63 UINT32 BitWidth, UINT64* Value, 64 void* HandlerContext, void* RegionContext); 65 66static zx_status_t wait_for_interrupt(acpi_ec_device_t* dev); 67static zx_status_t execute_read_op(acpi_ec_device_t* dev, uint8_t addr, uint8_t* val); 68static zx_status_t execute_write_op(acpi_ec_device_t* dev, uint8_t addr, uint8_t val); 69static zx_status_t execute_query_op(acpi_ec_device_t* dev, uint8_t* val); 70 71// Execute the EC_CMD_READ operation. Requires the ACPI global lock be held. 72static zx_status_t execute_read_op(acpi_ec_device_t* dev, uint8_t addr, uint8_t* val) { 73 // Issue EC command 74 outp(dev->cmd_port, EC_CMD_READ); 75 76 // Wait for EC to read the command so we can write the address 77 while (inp(dev->cmd_port) & EC_SC_IBF) { 78 zx_status_t status = wait_for_interrupt(dev); 79 if (status != ZX_OK) { 80 return status; 81 } 82 } 83 84 // Specify the address 85 outp(dev->data_port, addr); 86 87 // Wait for EC to read the address and write a response 88 while ((inp(dev->cmd_port) & (EC_SC_OBF | EC_SC_IBF)) != EC_SC_OBF) { 89 zx_status_t status = wait_for_interrupt(dev); 90 if (status != ZX_OK) { 91 return status; 92 } 93 } 94 95 // Read the response 96 *val = inp(dev->data_port); 97 return ZX_OK; 98} 99 100// Execute the EC_CMD_WRITE operation. Requires the ACPI global lock be held. 101static zx_status_t execute_write_op(acpi_ec_device_t* dev, uint8_t addr, uint8_t val) { 102 // Issue EC command 103 outp(dev->cmd_port, EC_CMD_WRITE); 104 105 // Wait for EC to read the command so we can write the address 106 while (inp(dev->cmd_port) & EC_SC_IBF) { 107 zx_status_t status = wait_for_interrupt(dev); 108 if (status != ZX_OK) { 109 return status; 110 } 111 } 112 113 // Specify the address 114 outp(dev->data_port, addr); 115 116 // Wait for EC to read the address 117 while (inp(dev->cmd_port) & EC_SC_IBF) { 118 zx_status_t status = wait_for_interrupt(dev); 119 if (status != ZX_OK) { 120 return status; 121 } 122 } 123 124 // Write the data 125 outp(dev->data_port, val); 126 127 // Wait for EC to read the data 128 while (inp(dev->cmd_port) & EC_SC_IBF) { 129 zx_status_t status = wait_for_interrupt(dev); 130 if (status != ZX_OK) { 131 return status; 132 } 133 } 134 135 return ZX_OK; 136} 137 138// Execute the EC_CMD_QUERY operation. Requires the ACPI global lock be held. 139static zx_status_t execute_query_op(acpi_ec_device_t* dev, uint8_t* event) { 140 // Query EC command 141 outp(dev->cmd_port, EC_CMD_QUERY); 142 143 // Wait for EC to respond 144 while ((inp(dev->cmd_port) & (EC_SC_OBF | EC_SC_IBF)) != EC_SC_OBF) { 145 zx_status_t status = wait_for_interrupt(dev); 146 if (status != ZX_OK) { 147 return status; 148 } 149 } 150 151 *event = inp(dev->data_port); 152 return ZX_OK; 153} 154 155static ACPI_STATUS ec_space_setup_handler(ACPI_HANDLE Region, UINT32 Function, 156 void* HandlerContext, void** ReturnContext) { 157 acpi_ec_device_t* dev = HandlerContext; 158 *ReturnContext = dev; 159 160 if (Function == ACPI_REGION_ACTIVATE) { 161 xprintf("acpi-ec: Setting up EC region\n"); 162 return AE_OK; 163 } else if (Function == ACPI_REGION_DEACTIVATE) { 164 xprintf("acpi-ec: Tearing down EC region\n"); 165 return AE_OK; 166 } else { 167 return AE_SUPPORT; 168 } 169} 170 171static ACPI_STATUS ec_space_request_handler(UINT32 Function, ACPI_PHYSICAL_ADDRESS Address, 172 UINT32 BitWidth, UINT64* Value, 173 void* HandlerContext, void* RegionContext) { 174 acpi_ec_device_t* dev = HandlerContext; 175 176 if (BitWidth != 8 && BitWidth != 16 && BitWidth != 32 && BitWidth != 64) { 177 return AE_BAD_PARAMETER; 178 } 179 if (Address > UINT8_MAX || Address - 1 + BitWidth / 8 > UINT8_MAX) { 180 return AE_BAD_PARAMETER; 181 } 182 183 UINT32 global_lock; 184 while (AcpiAcquireGlobalLock(0xFFFF, &global_lock) != AE_OK) 185 ; 186 187 // NB: The processing of the read/write ops below will generate interrupts, 188 // which will unfortunately cause spurious wakeups on the event thread. One 189 // design that would avoid this is to have that thread responsible for 190 // processing these EC address space requests, but an attempt at an 191 // implementation failed due to apparent deadlocks against the Global Lock. 192 193 const size_t bytes = BitWidth / 8; 194 ACPI_STATUS status = AE_OK; 195 uint8_t* value_bytes = (uint8_t*)Value; 196 if (Function == ACPI_WRITE) { 197 for (size_t i = 0; i < bytes; ++i) { 198 zx_status_t zx_status = execute_write_op(dev, Address + i, value_bytes[i]); 199 if (zx_status != ZX_OK) { 200 status = AE_ERROR; 201 goto finish; 202 } 203 } 204 } else { 205 *Value = 0; 206 for (size_t i = 0; i < bytes; ++i) { 207 zx_status_t zx_status = execute_read_op(dev, Address + i, value_bytes + i); 208 if (zx_status != ZX_OK) { 209 status = AE_ERROR; 210 goto finish; 211 } 212 } 213 } 214 215finish: 216 AcpiReleaseGlobalLock(global_lock); 217 return status; 218} 219 220static zx_status_t wait_for_interrupt(acpi_ec_device_t* dev) { 221 uint32_t pending; 222 zx_status_t status = zx_object_wait_one(dev->interrupt_event, 223 IRQ_RECEIVED | EC_THREAD_SHUTDOWN, 224 ZX_TIME_INFINITE, 225 &pending); 226 if (status != ZX_OK) { 227 printf("acpi-ec: thread wait failed: %d\n", status); 228 zx_object_signal(dev->interrupt_event, 0, EC_THREAD_SHUTDOWN_DONE); 229 return status; 230 } 231 232 if (pending & EC_THREAD_SHUTDOWN) { 233 zx_object_signal(dev->interrupt_event, 0, EC_THREAD_SHUTDOWN_DONE); 234 return ZX_ERR_STOP; 235 } 236 237 /* Clear interrupt */ 238 zx_object_signal(dev->interrupt_event, IRQ_RECEIVED, 0); 239 return ZX_OK; 240} 241 242static int acpi_ec_thread(void* arg) { 243 acpi_ec_device_t* dev = arg; 244 UINT32 global_lock; 245 246 while (1) { 247 zx_status_t zx_status = wait_for_interrupt(dev); 248 if (zx_status != ZX_OK) { 249 goto exiting_without_lock; 250 } 251 252 while (AcpiAcquireGlobalLock(0xFFFF, &global_lock) != AE_OK) 253 ; 254 255 uint8_t status; 256 bool processed_evt = false; 257 while ((status = inp(dev->cmd_port)) & EC_SC_SCI_EVT) { 258 uint8_t event_code; 259 zx_status_t zx_status = execute_query_op(dev, &event_code); 260 if (zx_status != ZX_OK) { 261 goto exiting_with_lock; 262 } 263 264 if (event_code != 0) { 265 char method[5] = {0}; 266 snprintf(method, sizeof(method), "_Q%02x", event_code); 267 xprintf("acpi-ec: Invoking method %s\n", method); 268 AcpiEvaluateObject(dev->acpi_handle, method, NULL, NULL); 269 xprintf("acpi-ec: Invoked method %s\n", method); 270 } else { 271 xprintf("acpi-ec: Spurious event?\n"); 272 } 273 274 processed_evt = true; 275 276 /* Clear interrupt before we check EVT again, to prevent a spurious 277 * interrupt later. There could be two sources of that spurious 278 * wakeup: Either we handled two events back-to-back, or we didn't 279 * wait for the OBF interrupt above. */ 280 zx_object_signal(dev->interrupt_event, IRQ_RECEIVED, 0); 281 } 282 283 if (!processed_evt) { 284 xprintf("acpi-ec: Spurious wakeup, no evt: %#x\n", status); 285 } 286 287 AcpiReleaseGlobalLock(global_lock); 288 } 289 290exiting_with_lock: 291 AcpiReleaseGlobalLock(global_lock); 292exiting_without_lock: 293 xprintf("acpi-ec: thread terminated\n"); 294 return 0; 295} 296 297static uint32_t raw_ec_event_gpe_handler(ACPI_HANDLE gpe_dev, uint32_t gpe_num, void* ctx) { 298 acpi_ec_device_t* dev = ctx; 299 zx_object_signal(dev->interrupt_event, 0, IRQ_RECEIVED); 300 return ACPI_REENABLE_GPE; 301} 302 303static ACPI_STATUS get_ec_handle( 304 ACPI_HANDLE object, 305 UINT32 nesting_level, 306 void* context, 307 void** ret) { 308 309 *(ACPI_HANDLE*)context = object; 310 return AE_OK; 311} 312 313static ACPI_STATUS get_ec_gpe_info( 314 ACPI_HANDLE ec_handle, ACPI_HANDLE* gpe_block, UINT32* gpe) { 315 ACPI_BUFFER buffer = { 316 .Length = ACPI_ALLOCATE_BUFFER, 317 .Pointer = NULL, 318 }; 319 ACPI_STATUS status = AcpiEvaluateObject( 320 ec_handle, (char*)"_GPE", NULL, &buffer); 321 if (status != AE_OK) { 322 return status; 323 } 324 325 /* According to section 12.11 of ACPI v6.1, a _GPE object on this device 326 * evaluates to either an integer specifying bit in the GPEx_STS blocks 327 * to use, or a package specifying which GPE block and which bit inside 328 * that block to use. */ 329 ACPI_OBJECT* gpe_obj = buffer.Pointer; 330 if (gpe_obj->Type == ACPI_TYPE_INTEGER) { 331 *gpe_block = NULL; 332 *gpe = gpe_obj->Integer.Value; 333 } else if (gpe_obj->Type == ACPI_TYPE_PACKAGE) { 334 if (gpe_obj->Package.Count != 2) { 335 goto bailout; 336 } 337 ACPI_OBJECT* block_obj = &gpe_obj->Package.Elements[0]; 338 ACPI_OBJECT* gpe_num_obj = &gpe_obj->Package.Elements[1]; 339 if (block_obj->Type != ACPI_TYPE_LOCAL_REFERENCE) { 340 goto bailout; 341 } 342 if (gpe_num_obj->Type != ACPI_TYPE_INTEGER) { 343 goto bailout; 344 } 345 *gpe_block = block_obj->Reference.Handle; 346 *gpe = gpe_num_obj->Integer.Value; 347 } else { 348 goto bailout; 349 } 350 ACPI_FREE(buffer.Pointer); 351 return AE_OK; 352 353bailout: 354 xprintf("Failed to intepret EC GPE number"); 355 ACPI_FREE(buffer.Pointer); 356 return AE_BAD_DATA; 357} 358 359struct ec_ports_callback_ctx { 360 uint16_t* data_port; 361 uint16_t* cmd_port; 362 unsigned int resource_num; 363}; 364 365static ACPI_STATUS get_ec_ports_callback( 366 ACPI_RESOURCE* Resource, void* Context) { 367 struct ec_ports_callback_ctx* ctx = Context; 368 369 if (Resource->Type == ACPI_RESOURCE_TYPE_END_TAG) { 370 return AE_OK; 371 } 372 373 /* The spec says there will be at most 3 resources */ 374 if (ctx->resource_num >= 3) { 375 return AE_BAD_DATA; 376 } 377 /* The third resource only exists on HW-Reduced platforms, which we don't 378 * support at the moment. */ 379 if (ctx->resource_num == 2) { 380 xprintf("RESOURCE TYPE %d\n", Resource->Type); 381 return AE_NOT_IMPLEMENTED; 382 } 383 384 /* The two resources we're expecting are both address regions. First the 385 * data one, then the command one. We assume they're single IO ports. */ 386 if (Resource->Type != ACPI_RESOURCE_TYPE_IO) { 387 return AE_SUPPORT; 388 } 389 if (Resource->Data.Io.Maximum != Resource->Data.Io.Minimum) { 390 return AE_SUPPORT; 391 } 392 393 uint16_t port = Resource->Data.Io.Minimum; 394 if (ctx->resource_num == 0) { 395 *ctx->data_port = port; 396 } else { 397 *ctx->cmd_port = port; 398 } 399 400 ctx->resource_num++; 401 return AE_OK; 402} 403 404static ACPI_STATUS get_ec_ports( 405 ACPI_HANDLE ec_handle, uint16_t* data_port, uint16_t* cmd_port) { 406 struct ec_ports_callback_ctx ctx = { 407 .data_port = data_port, 408 .cmd_port = cmd_port, 409 .resource_num = 0, 410 }; 411 412 return AcpiWalkResources(ec_handle, (char*)"_CRS", get_ec_ports_callback, &ctx); 413} 414 415static void acpi_ec_release(void* ctx) { 416 acpi_ec_device_t* dev = ctx; 417 418 if (dev->ec_space_setup) { 419 AcpiRemoveAddressSpaceHandler(ACPI_ROOT_OBJECT, ACPI_ADR_SPACE_EC, ec_space_request_handler); 420 } 421 422 if (dev->gpe_setup) { 423 AcpiDisableGpe(dev->gpe_block, dev->gpe); 424 AcpiRemoveGpeHandler(dev->gpe_block, dev->gpe, raw_ec_event_gpe_handler); 425 } 426 427 if (dev->interrupt_event != ZX_HANDLE_INVALID) { 428 if (dev->thread_setup) { 429 /* Shutdown the EC thread */ 430 zx_object_signal(dev->interrupt_event, 0, EC_THREAD_SHUTDOWN); 431 zx_object_wait_one(dev->interrupt_event, EC_THREAD_SHUTDOWN_DONE, ZX_TIME_INFINITE, NULL); 432 thrd_join(dev->evt_thread, NULL); 433 } 434 435 zx_handle_close(dev->interrupt_event); 436 } 437 438 free(dev); 439} 440 441static zx_status_t acpi_ec_suspend(void* ctx, uint32_t flags) { 442 acpi_ec_device_t* dev = ctx; 443 444 if (flags != DEVICE_SUSPEND_FLAG_MEXEC) { 445 return ZX_ERR_NOT_SUPPORTED; 446 } 447 448 AcpiRemoveAddressSpaceHandler(ACPI_ROOT_OBJECT, ACPI_ADR_SPACE_EC, ec_space_request_handler); 449 dev->ec_space_setup = false; 450 451 AcpiDisableGpe(dev->gpe_block, dev->gpe); 452 AcpiRemoveGpeHandler(dev->gpe_block, dev->gpe, raw_ec_event_gpe_handler); 453 dev->gpe_setup = false; 454 455 zx_object_signal(dev->interrupt_event, 0, EC_THREAD_SHUTDOWN); 456 zx_object_wait_one(dev->interrupt_event, EC_THREAD_SHUTDOWN_DONE, ZX_TIME_INFINITE, NULL); 457 thrd_join(dev->evt_thread, NULL); 458 zx_handle_close(dev->interrupt_event); 459 dev->interrupt_event = ZX_HANDLE_INVALID; 460 return ZX_OK; 461} 462 463static zx_protocol_device_t acpi_ec_device_proto = { 464 .version = DEVICE_OPS_VERSION, 465 .release = acpi_ec_release, 466 .suspend = acpi_ec_suspend, 467}; 468 469zx_status_t ec_init(zx_device_t* parent, ACPI_HANDLE acpi_handle) { 470 xprintf("acpi-ec: init\n"); 471 472 acpi_ec_device_t* dev = calloc(1, sizeof(acpi_ec_device_t)); 473 if (!dev) { 474 return ZX_ERR_NO_MEMORY; 475 } 476 dev->acpi_handle = acpi_handle; 477 478 zx_status_t err = zx_event_create(0, &dev->interrupt_event); 479 if (err != ZX_OK) { 480 xprintf("acpi-ec: Failed to create event: %d\n", err); 481 acpi_ec_release(dev); 482 return err; 483 } 484 485 ACPI_STATUS status = get_ec_gpe_info(acpi_handle, &dev->gpe_block, &dev->gpe); 486 if (status != AE_OK) { 487 xprintf("acpi-ec: Failed to decode GPE info: %d\n", status); 488 goto acpi_error; 489 } 490 491 status = get_ec_ports( 492 acpi_handle, &dev->data_port, &dev->cmd_port); 493 if (status != AE_OK) { 494 xprintf("acpi-ec: Failed to decode comm info: %d\n", status); 495 goto acpi_error; 496 } 497 498 /* Setup GPE handling */ 499 status = AcpiInstallGpeHandler( 500 dev->gpe_block, dev->gpe, ACPI_GPE_EDGE_TRIGGERED, 501 raw_ec_event_gpe_handler, dev); 502 if (status != AE_OK) { 503 xprintf("acpi-ec: Failed to install GPE %d: %x\n", dev->gpe, status); 504 goto acpi_error; 505 } 506 status = AcpiEnableGpe(dev->gpe_block, dev->gpe); 507 if (status != AE_OK) { 508 xprintf("acpi-ec: Failed to enable GPE %d: %x\n", dev->gpe, status); 509 AcpiRemoveGpeHandler(dev->gpe_block, dev->gpe, raw_ec_event_gpe_handler); 510 goto acpi_error; 511 } 512 dev->gpe_setup = true; 513 514 /* TODO(teisenbe): This thread should ideally be at a high priority, since 515 it takes the ACPI global lock which is shared with SMM. */ 516 int ret = thrd_create_with_name(&dev->evt_thread, acpi_ec_thread, dev, "acpi-ec-evt"); 517 if (ret != thrd_success) { 518 xprintf("acpi-ec: Failed to create thread\n"); 519 acpi_ec_release(dev); 520 return ZX_ERR_INTERNAL; 521 } 522 dev->thread_setup = true; 523 524 status = AcpiInstallAddressSpaceHandler(ACPI_ROOT_OBJECT, ACPI_ADR_SPACE_EC, 525 ec_space_request_handler, 526 ec_space_setup_handler, 527 dev); 528 if (status != AE_OK) { 529 xprintf("acpi-ec: Failed to install ec space handler\n"); 530 acpi_ec_release(dev); 531 return acpi_to_zx_status(status); 532 } 533 dev->ec_space_setup = true; 534 535 device_add_args_t args = { 536 .version = DEVICE_ADD_ARGS_VERSION, 537 .name = "acpi-ec", 538 .ctx = dev, 539 .ops = &acpi_ec_device_proto, 540 .proto_id = ZX_PROTOCOL_MISC, 541 }; 542 543 status = device_add(parent, &args, &dev->zxdev); 544 if (status != ZX_OK) { 545 xprintf("acpi-ec: could not add device! err=%d\n", status); 546 acpi_ec_release(dev); 547 return status; 548 } 549 550 printf("acpi-ec: initialized\n"); 551 return ZX_OK; 552 553acpi_error: 554 acpi_ec_release(dev); 555 return acpi_to_zx_status(status); 556} 557