| // 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 <ddk/binding.h> |
| #include <ddk/device.h> |
| #include <ddk/driver.h> |
| #include <ddk/protocol/pciroot.h> |
| |
| #include <inttypes.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <stdint.h> |
| #include <string.h> |
| #include <sys/stat.h> |
| |
| #include <zircon/compiler.h> |
| #include <zircon/process.h> |
| #include <zircon/processargs.h> |
| #include <zircon/syscalls.h> |
| #include <fdio/debug.h> |
| |
| #include "init.h" |
| #include "dev.h" |
| #include "pci.h" |
| #include "powerbtn.h" |
| #include "processor.h" |
| #include "power.h" |
| |
| #define MXDEBUG 0 |
| |
| #define MAX_NAMESPACE_DEPTH 100 |
| |
| #define HID_LENGTH 8 |
| |
| typedef struct acpi_device { |
| zx_device_t* mxdev; |
| |
| // handle to the corresponding ACPI node |
| ACPI_HANDLE ns_node; |
| } acpi_device_t; |
| |
| zx_handle_t root_resource_handle; |
| zx_handle_t rpc_handle; |
| |
| static int acpi_rpc_thread(void* arg) { |
| xprintf("bus-acpi: rpc thread starting\n"); |
| zx_status_t status = begin_processing(rpc_handle); |
| xprintf("bus-acpi: rpc thread returned %d\n", status); |
| return (status == ZX_OK) ? 0 : -1; |
| } |
| |
| static void acpi_device_release(void* ctx) { |
| acpi_device_t* dev = (acpi_device_t*)ctx; |
| free(dev); |
| } |
| |
| static zx_protocol_device_t acpi_device_proto = { |
| .version = DEVICE_OPS_VERSION, |
| .release = acpi_device_release, |
| }; |
| |
| static pciroot_protocol_ops_t pciroot_proto = { |
| }; |
| |
| static const char* hid_from_acpi_devinfo(ACPI_DEVICE_INFO* info) { |
| const char* hid = NULL; |
| if ((info->Valid & ACPI_VALID_HID) && |
| (info->HardwareId.Length > 0) && |
| ((info->HardwareId.Length - 1) <= sizeof(uint64_t))) { |
| hid = (const char*)info->HardwareId.String; |
| } |
| return hid; |
| } |
| |
| static zx_device_t* publish_device(zx_device_t* parent, |
| ACPI_HANDLE handle, |
| ACPI_DEVICE_INFO* info, |
| uint32_t protocol_id, |
| void* protocol_ops) { |
| #if MXDEBUG |
| if (!parent) { |
| xprintf("acpi-bus: parent is NULL\n"); |
| return NULL; |
| } |
| #endif |
| // ACPI names are always 4 characters in a uint32 |
| char name[5] = { 0 }; |
| memcpy(name, &info->Name, sizeof(name) - 1); |
| |
| zx_device_prop_t props[4]; |
| int propcount = 0; |
| |
| // Publish HID in device props |
| const char* hid = hid_from_acpi_devinfo(info); |
| if (hid) { |
| props[propcount].id = BIND_ACPI_HID_0_3; |
| props[propcount++].value = htobe32(*((uint32_t*)(hid))); |
| props[propcount].id = BIND_ACPI_HID_4_7; |
| props[propcount++].value = htobe32(*((uint32_t*)(hid + 4))); |
| } |
| |
| // Publish the first CID in device props |
| const char* cid = (const char*)info->CompatibleIdList.Ids[0].String; |
| if ((info->Valid & ACPI_VALID_CID) && |
| (info->CompatibleIdList.Count > 0) && |
| ((info->CompatibleIdList.Ids[0].Length - 1) <= sizeof(uint64_t))) { |
| props[propcount].id = BIND_ACPI_CID_0_3; |
| props[propcount++].value = htobe32(*((uint32_t*)(cid))); |
| props[propcount].id = BIND_ACPI_CID_4_7; |
| props[propcount++].value = htobe32(*((uint32_t*)(cid + 4))); |
| } |
| |
| #if MXDEBUG |
| printf("acpi-bus: got device %s\n", name); |
| if (info->Valid & ACPI_VALID_HID) { |
| printf(" HID=%s\n", info->HardwareId.String); |
| } else { |
| printf(" HID=invalid\n"); |
| } |
| if (info->Valid & ACPI_VALID_ADR) { |
| printf(" ADR=0x%" PRIx64 "\n", (uint64_t)info->Address); |
| } else { |
| printf(" ADR=invalid\n"); |
| } |
| if (info->Valid & ACPI_VALID_CID) { |
| printf(" CIDS=%d\n", info->CompatibleIdList.Count); |
| for (uint i = 0; i < info->CompatibleIdList.Count; i++) { |
| printf(" [%u] %s\n", i, info->CompatibleIdList.Ids[i].String); |
| } |
| } else { |
| printf(" CID=invalid\n"); |
| } |
| printf(" devprops:\n"); |
| for (int i = 0; i < propcount; i++) { |
| printf(" [%d] id=0x%08x value=0x%08x\n", i, props[i].id, props[i].value); |
| } |
| #endif |
| |
| // TODO: publish pciroot and other acpi devices in separate devhosts? |
| |
| acpi_device_t* dev = calloc(1, sizeof(acpi_device_t)); |
| if (!dev) { |
| return NULL; |
| } |
| |
| dev->ns_node = handle; |
| |
| device_add_args_t args = { |
| .version = DEVICE_ADD_ARGS_VERSION, |
| .name = name, |
| .ctx = dev, |
| .ops = &acpi_device_proto, |
| .proto_id = protocol_id, |
| .proto_ops = protocol_ops, |
| .props = (propcount > 0) ? props : NULL, |
| .prop_count = propcount, |
| }; |
| |
| zx_status_t status; |
| if ((status = device_add(parent, &args, &dev->mxdev)) != ZX_OK) { |
| xprintf("acpi-bus: error %d in device_add, parent=%s(%p)\n", status, device_get_name(parent), parent); |
| free(dev); |
| return NULL; |
| } else { |
| xprintf("acpi-bus: published device %s(%p), parent=%s(%p), handle=%p\n", |
| name, dev, device_get_name(parent), parent, (void*)dev->ns_node); |
| return dev->mxdev; |
| } |
| } |
| |
| static ACPI_STATUS acpi_ns_walk_callback(ACPI_HANDLE object, uint32_t nesting_level, |
| void* context, void** status) { |
| ACPI_DEVICE_INFO* info = NULL; |
| ACPI_STATUS acpi_status = AcpiGetObjectInfo(object, &info); |
| if (acpi_status != AE_OK) { |
| return acpi_status; |
| } |
| |
| // TODO: This is a temporary workaround until we have full ACPI device |
| // enumeration. If this is the I2C1 bus, we run _PS0 so the controller |
| // is active. |
| if (!memcmp(&info->Name, "I2C1", 4)) { |
| ACPI_STATUS acpi_status = AcpiEvaluateObject(object, (char*)"_PS0", NULL, NULL); |
| if (acpi_status != AE_OK) { |
| printf("acpi-bus: acpi error 0x%x in I2C1._PS0\n", acpi_status); |
| } |
| } |
| |
| xprintf("acpi-bus: handle %p nesting level %d\n", (void*)object, nesting_level); |
| |
| // Only publish PCIE/PCI roots |
| zx_device_t* parent = (zx_device_t*)context; |
| const char* hid = hid_from_acpi_devinfo(info); |
| if (hid == 0) { |
| goto out; |
| } |
| if (!memcmp(hid, PCI_EXPRESS_ROOT_HID_STRING, HID_LENGTH) || |
| !memcmp(hid, PCI_ROOT_HID_STRING, HID_LENGTH)) { |
| publish_device(parent, object, info, ZX_PROTOCOL_PCIROOT, &pciroot_proto); |
| } else if (!memcmp(hid, BATTERY_HID_STRING, HID_LENGTH)) { |
| battery_init(parent, object); |
| } else if (!memcmp(hid, PWRSRC_HID_STRING, HID_LENGTH)) { |
| pwrsrc_init(parent, object); |
| } else if (!memcmp(hid, EC_HID_STRING, HID_LENGTH)) { |
| ec_init(parent, object); |
| } |
| |
| out: |
| ACPI_FREE(info); |
| |
| return AE_OK; |
| } |
| |
| static zx_status_t publish_pci_roots(zx_device_t* parent) { |
| // Walk the ACPI namespace for devices and publish them |
| ACPI_STATUS acpi_status = AcpiWalkNamespace(ACPI_TYPE_DEVICE, |
| ACPI_ROOT_OBJECT, |
| MAX_NAMESPACE_DEPTH, |
| acpi_ns_walk_callback, |
| NULL, parent, NULL); |
| if (acpi_status != AE_OK) { |
| return ZX_ERR_BAD_STATE; |
| } else { |
| return ZX_OK; |
| } |
| } |
| |
| static zx_status_t acpi_drv_bind(void* ctx, zx_device_t* parent, void** cookie) { |
| // ACPI is the root driver for its devhost so run init in the bind thread. |
| xprintf("bus-acpi: bind to %s %p\n", device_get_name(parent), parent); |
| root_resource_handle = get_root_resource(); |
| |
| // Get RPC channel |
| rpc_handle = zx_get_startup_handle(PA_HND(PA_USER0, 10)); |
| if (rpc_handle == ZX_HANDLE_INVALID) { |
| xprintf("bus-acpi: no acpi rpc handle\n"); |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| if (init() != ZX_OK) { |
| xprintf("bus_acpi: failed to initialize ACPI\n"); |
| return ZX_ERR_INTERNAL; |
| } |
| |
| printf("acpi-bus: initialized\n"); |
| |
| zx_status_t status = install_powerbtn_handlers(); |
| if (status != ZX_OK) { |
| xprintf("acpi-bus: error %d in install_powerbtn_handlers\n", status); |
| return status; |
| } |
| |
| // Report current resources to kernel PCI driver |
| status = pci_report_current_resources(get_root_resource()); |
| if (status != ZX_OK) { |
| xprintf("acpi-bus: WARNING: ACPI failed to report all current resources!\n"); |
| } |
| |
| // Initialize kernel PCI driver |
| zx_pci_init_arg_t* arg; |
| uint32_t arg_size; |
| status = get_pci_init_arg(&arg, &arg_size); |
| if (status != ZX_OK) { |
| xprintf("acpi-bus: erorr %d in get_pci_init_arg\n", status); |
| return status; |
| } |
| |
| status = zx_pci_init(get_root_resource(), arg, arg_size); |
| if (status != ZX_OK) { |
| xprintf("acpi-bus: error %d in zx_pci_init\n", status); |
| return status; |
| } |
| |
| free(arg); |
| |
| // start rpc thread |
| // TODO: probably will be replaced with devmgr rpc mechanism |
| thrd_t rpc_thrd; |
| int rc = thrd_create_with_name(&rpc_thrd, acpi_rpc_thread, NULL, "acpi-rpc"); |
| if (rc != thrd_success) { |
| xprintf("acpi-bus: error %d in rpc thrd_create\n", rc); |
| return ZX_ERR_INTERNAL; |
| } |
| |
| // only publish the pci root. ACPI devices are managed by this driver. |
| publish_pci_roots(parent); |
| |
| return ZX_OK; |
| } |
| |
| static zx_driver_ops_t acpi_driver_ops = { |
| .version = DRIVER_OPS_VERSION, |
| .bind = acpi_drv_bind, |
| }; |
| |
| ZIRCON_DRIVER_BEGIN(acpi, acpi_driver_ops, "zircon", "0.1", 1) |
| BI_MATCH_IF(EQ, BIND_PROTOCOL, ZX_PROTOCOL_ACPI_BUS), |
| ZIRCON_DRIVER_END(acpi) |