| // Copyright 2017 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "dev.h" |
| |
| #include <hw/inout.h> |
| #include <zircon/syscalls.h> |
| #include <zircon/types.h> |
| #include <fdio/debug.h> |
| |
| #include "errors.h" |
| |
| #define MXDEBUG 0 |
| |
| /* EC commands */ |
| #define EC_CMD_QUERY 0x84 |
| |
| /* EC status register bits */ |
| #define EC_SC_SCI_EVT (1 << 5) |
| #define EC_SC_IBF (1 << 1) |
| #define EC_SC_OBF (1 << 0) |
| |
| /* Thread shutdown signals */ |
| #define EC_THREAD_SHUTDOWN ZX_USER_SIGNAL_0 |
| #define EC_THREAD_SHUTDOWN_DONE ZX_USER_SIGNAL_1 |
| |
| typedef struct acpi_ec_device { |
| zx_device_t* mxdev; |
| |
| ACPI_HANDLE acpi_handle; |
| |
| // PIO addresses for EC device |
| uint16_t cmd_port; |
| uint16_t data_port; |
| |
| // GPE for EC events |
| ACPI_HANDLE gpe_block; |
| UINT32 gpe; |
| |
| // thread for processing System Control Interrupts |
| thrd_t sci_thread; |
| zx_handle_t pending_sci_evt; |
| |
| bool gpe_setup : 1; |
| bool thread_setup : 1; |
| } acpi_ec_device_t; |
| |
| static ACPI_STATUS get_ec_handle(ACPI_HANDLE, UINT32, void*, void**); |
| static ACPI_STATUS get_ec_gpe_info(ACPI_HANDLE, ACPI_HANDLE*, UINT32*); |
| static ACPI_STATUS get_ec_ports(ACPI_HANDLE, uint16_t*, uint16_t*); |
| |
| static int acpi_ec_thread(void* arg) { |
| acpi_ec_device_t* dev = arg; |
| |
| while (1) { |
| uint32_t pending; |
| zx_status_t zx_status = zx_object_wait_one(dev->pending_sci_evt, |
| ZX_EVENT_SIGNALED | EC_THREAD_SHUTDOWN, |
| ZX_TIME_INFINITE, |
| &pending); |
| if (zx_status != ZX_OK) { |
| printf("acpi-ec: thread wait failed: %d\n", zx_status); |
| break; |
| } |
| |
| if (pending & EC_THREAD_SHUTDOWN) { |
| zx_object_signal(dev->pending_sci_evt, 0, EC_THREAD_SHUTDOWN_DONE); |
| break; |
| } |
| |
| zx_object_signal(dev->pending_sci_evt, ZX_EVENT_SIGNALED, 0); |
| |
| UINT32 global_lock; |
| while (AcpiAcquireGlobalLock(0xFFFF, &global_lock) != AE_OK) |
| ; |
| |
| uint8_t status; |
| do { |
| status = inp(dev->cmd_port); |
| /* Read the status out of the command/status port */ |
| if (!(status & EC_SC_SCI_EVT)) { |
| break; |
| } |
| |
| /* Query EC command */ |
| outp(dev->cmd_port, EC_CMD_QUERY); |
| |
| /* Wait for EC to read the command */ |
| while (!((status = inp(dev->cmd_port)) & EC_SC_IBF)) |
| ; |
| |
| /* Wait for EC to respond */ |
| while (!((status = inp(dev->cmd_port)) & EC_SC_OBF)) |
| ; |
| |
| while ((status = inp(dev->cmd_port)) & EC_SC_OBF) { |
| /* Read until the output buffer is empty */ |
| uint8_t event_code = inp(dev->data_port); |
| char method[5] = {0}; |
| snprintf(method, sizeof(method), "_Q%02x", event_code); |
| xprintf("acpi-ec: Invoking method %s\n", method); |
| AcpiEvaluateObject(dev->acpi_handle, method, NULL, NULL); |
| xprintf("acpi-ec: Invoked method %s\n", method); |
| } |
| } while (status & EC_SC_SCI_EVT); |
| |
| AcpiReleaseGlobalLock(global_lock); |
| } |
| |
| xprintf("acpi-ec: thread terminated\n"); |
| return 0; |
| } |
| |
| static uint32_t raw_ec_event_gpe_handler(ACPI_HANDLE gpe_dev, uint32_t gpe_num, void* ctx) { |
| acpi_ec_device_t* dev = ctx; |
| zx_object_signal(dev->pending_sci_evt, 0, ZX_EVENT_SIGNALED); |
| return ACPI_REENABLE_GPE; |
| } |
| |
| static ACPI_STATUS get_ec_handle( |
| ACPI_HANDLE object, |
| UINT32 nesting_level, |
| void* context, |
| void** ret) { |
| |
| *(ACPI_HANDLE*)context = object; |
| return AE_OK; |
| } |
| |
| static ACPI_STATUS get_ec_gpe_info( |
| ACPI_HANDLE ec_handle, ACPI_HANDLE* gpe_block, UINT32* gpe) { |
| ACPI_BUFFER buffer = { |
| .Length = ACPI_ALLOCATE_BUFFER, |
| .Pointer = NULL, |
| }; |
| ACPI_STATUS status = AcpiEvaluateObject( |
| ec_handle, (char*)"_GPE", NULL, &buffer); |
| if (status != AE_OK) { |
| return status; |
| } |
| |
| /* According to section 12.11 of ACPI v6.1, a _GPE object on this device |
| * evaluates to either an integer specifying bit in the GPEx_STS blocks |
| * to use, or a package specifying which GPE block and which bit inside |
| * that block to use. */ |
| ACPI_OBJECT* gpe_obj = buffer.Pointer; |
| if (gpe_obj->Type == ACPI_TYPE_INTEGER) { |
| *gpe_block = NULL; |
| *gpe = gpe_obj->Integer.Value; |
| } else if (gpe_obj->Type == ACPI_TYPE_PACKAGE) { |
| if (gpe_obj->Package.Count != 2) { |
| goto bailout; |
| } |
| ACPI_OBJECT* block_obj = &gpe_obj->Package.Elements[0]; |
| ACPI_OBJECT* gpe_num_obj = &gpe_obj->Package.Elements[1]; |
| if (block_obj->Type != ACPI_TYPE_LOCAL_REFERENCE) { |
| goto bailout; |
| } |
| if (gpe_num_obj->Type != ACPI_TYPE_INTEGER) { |
| goto bailout; |
| } |
| *gpe_block = block_obj->Reference.Handle; |
| *gpe = gpe_num_obj->Integer.Value; |
| } else { |
| goto bailout; |
| } |
| ACPI_FREE(buffer.Pointer); |
| return AE_OK; |
| |
| bailout: |
| xprintf("Failed to intepret EC GPE number"); |
| ACPI_FREE(buffer.Pointer); |
| return AE_BAD_DATA; |
| } |
| |
| struct ec_ports_callback_ctx { |
| uint16_t* data_port; |
| uint16_t* cmd_port; |
| unsigned int resource_num; |
| }; |
| |
| static ACPI_STATUS get_ec_ports_callback( |
| ACPI_RESOURCE* Resource, void* Context) { |
| struct ec_ports_callback_ctx* ctx = Context; |
| |
| if (Resource->Type == ACPI_RESOURCE_TYPE_END_TAG) { |
| return AE_OK; |
| } |
| |
| /* The spec says there will be at most 3 resources */ |
| if (ctx->resource_num >= 3) { |
| return AE_BAD_DATA; |
| } |
| /* The third resource only exists on HW-Reduced platforms, which we don't |
| * support at the moment. */ |
| if (ctx->resource_num == 2) { |
| xprintf("RESOURCE TYPE %d\n", Resource->Type); |
| return AE_NOT_IMPLEMENTED; |
| } |
| |
| /* The two resources we're expecting are both address regions. First the |
| * data one, then the command one. We assume they're single IO ports. */ |
| if (Resource->Type != ACPI_RESOURCE_TYPE_IO) { |
| return AE_SUPPORT; |
| } |
| if (Resource->Data.Io.Maximum != Resource->Data.Io.Minimum) { |
| return AE_SUPPORT; |
| } |
| |
| uint16_t port = Resource->Data.Io.Minimum; |
| if (ctx->resource_num == 0) { |
| *ctx->data_port = port; |
| } else { |
| *ctx->cmd_port = port; |
| } |
| |
| ctx->resource_num++; |
| return AE_OK; |
| } |
| |
| static ACPI_STATUS get_ec_ports( |
| ACPI_HANDLE ec_handle, uint16_t* data_port, uint16_t* cmd_port) { |
| struct ec_ports_callback_ctx ctx = { |
| .data_port = data_port, |
| .cmd_port = cmd_port, |
| .resource_num = 0, |
| }; |
| |
| return AcpiWalkResources(ec_handle, (char*)"_CRS", get_ec_ports_callback, &ctx); |
| } |
| |
| static void acpi_ec_release(void* ctx) { |
| acpi_ec_device_t* dev = ctx; |
| |
| if (dev->pending_sci_evt != ZX_HANDLE_INVALID) { |
| if (dev->thread_setup) { |
| /* Shutdown the EC thread */ |
| zx_object_signal(dev->pending_sci_evt, 0, EC_THREAD_SHUTDOWN); |
| zx_object_wait_one(dev->pending_sci_evt, EC_THREAD_SHUTDOWN_DONE, ZX_TIME_INFINITE, NULL); |
| thrd_join(dev->sci_thread, NULL); |
| } |
| |
| zx_handle_close(dev->pending_sci_evt); |
| } |
| |
| if (dev->gpe_setup) { |
| AcpiDisableGpe(dev->gpe_block, dev->gpe); |
| AcpiRemoveGpeHandler(dev->gpe_block, dev->gpe, raw_ec_event_gpe_handler); |
| } |
| |
| free(dev); |
| } |
| |
| static zx_protocol_device_t acpi_ec_device_proto = { |
| .version = DEVICE_OPS_VERSION, |
| .release = acpi_ec_release, |
| }; |
| |
| zx_status_t ec_init(zx_device_t* parent, ACPI_HANDLE acpi_handle) { |
| xprintf("acpi-ec: init\n"); |
| |
| acpi_ec_device_t* dev = calloc(1, sizeof(acpi_ec_device_t)); |
| if (!dev) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| dev->acpi_handle = acpi_handle; |
| |
| zx_status_t err = zx_event_create(0, &dev->pending_sci_evt); |
| if (err != ZX_OK) { |
| xprintf("acpi-ec: Failed to create event: %d\n", err); |
| acpi_ec_release(dev); |
| return err; |
| } |
| |
| ACPI_STATUS status = get_ec_gpe_info(acpi_handle, &dev->gpe_block, &dev->gpe); |
| if (status != AE_OK) { |
| xprintf("acpi-ec: Failed to decode GPE info: %d\n", status); |
| goto acpi_error; |
| } |
| |
| status = get_ec_ports( |
| acpi_handle, &dev->data_port, &dev->cmd_port); |
| if (status != AE_OK) { |
| xprintf("acpi-ec: Failed to decode comm info: %d\n", status); |
| goto acpi_error; |
| } |
| |
| /* Setup GPE handling */ |
| status = AcpiInstallGpeHandler( |
| dev->gpe_block, dev->gpe, ACPI_GPE_EDGE_TRIGGERED, |
| raw_ec_event_gpe_handler, dev); |
| if (status != AE_OK) { |
| xprintf("acpi-ec: Failed to install GPE %d: %x\n", dev->gpe, status); |
| goto acpi_error; |
| } |
| status = AcpiEnableGpe(dev->gpe_block, dev->gpe); |
| if (status != AE_OK) { |
| xprintf("acpi-ec: Failed to enable GPE %d: %x\n", dev->gpe, status); |
| AcpiRemoveGpeHandler(dev->gpe_block, dev->gpe, raw_ec_event_gpe_handler); |
| goto acpi_error; |
| } |
| dev->gpe_setup = true; |
| |
| int ret = thrd_create_with_name(&dev->sci_thread, acpi_ec_thread, dev, "acpi-ec-sci"); |
| if (ret != thrd_success) { |
| xprintf("acpi-ec: Failed to create thread\n"); |
| acpi_ec_release(dev); |
| return ZX_ERR_INTERNAL; |
| } |
| dev->thread_setup = true; |
| |
| device_add_args_t args = { |
| .version = DEVICE_ADD_ARGS_VERSION, |
| .name = "acpi-ec", |
| .ctx = dev, |
| .ops = &acpi_ec_device_proto, |
| .proto_id = ZX_PROTOCOL_MISC, |
| }; |
| |
| status = device_add(parent, &args, &dev->mxdev); |
| if (status != ZX_OK) { |
| xprintf("acpi-ec: could not add device! err=%d\n", status); |
| acpi_ec_release(dev); |
| return status; |
| } |
| |
| printf("acpi-ec: initialized\n"); |
| return ZX_OK; |
| |
| acpi_error: |
| acpi_ec_release(dev); |
| return acpi_to_zx_status(status); |
| } |