blob: 554496b7de3eb38a2708ee2b9c7d1a27716c4b5e [file] [log] [blame]
// 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)