blob: 02bd9d0a26d30166a3656086e7f6e24c07733a75 [file] [log] [blame]
// Copyright 2019 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 <inttypes.h>
#include <limits.h>
#include <sys/types.h>
#include <zircon/assert.h>
#include <zircon/compiler.h>
#include <zircon/process.h>
#include <zircon/syscalls.h>
#include <zircon/syscalls/iommu.h>
#include <zircon/syscalls/resource.h>
#include <zircon/threads.h>
#include <variant>
#include <vector>
#include <acpica/acpi.h>
#include <ddk/debug.h>
#include <ddk/protocol/acpi.h>
#include <ddk/protocol/pciroot.h>
#include <ddk/protocol/sysmem.h>
#include <fbl/auto_lock.h>
#include "acpi-private.h"
#include "acpi.h"
#include "dev.h"
#include "errors.h"
#include "i2c.h"
#include "iommu.h"
#include "methods.h"
#include "nhlt.h"
#include "pci.h"
#include "power.h"
#include "resources.h"
#include "sysmem.h"
#include "util.h"
namespace {
const std::string_view hid_from_acpi_devinfo(const ACPI_DEVICE_INFO& info) {
if ((info.Valid & ACPI_VALID_HID) && (info.HardwareId.Length > 0) &&
((info.HardwareId.Length - 1) <= sizeof(uint64_t))) {
// ACPICA string lengths include the NULL terminator.
return std::string_view{info.HardwareId.String, info.HardwareId.Length - 1};
}
return std::string_view{};
}
const std::string_view cid_from_acpi_devinfo(const ACPI_DEVICE_INFO& info) {
if ((info.Valid & ACPI_VALID_CID) && (info.CompatibleIdList.Count > 0) &&
(info.CompatibleIdList.Ids[0].Length > 0)) {
// ACPICA string lengths include the NULL terminator.
return std::string_view{info.CompatibleIdList.Ids[0].String,
info.CompatibleIdList.Ids[0].Length - 1};
}
return std::string_view{};
}
void acpi_apply_workarounds(ACPI_HANDLE object, ACPI_DEVICE_INFO* info) {
ACPI_STATUS acpi_status;
// Slate workaround: Turn on the HID controller.
if (!memcmp(&info->Name, "I2C0", 4)) {
ACPI_BUFFER buffer = {
.Length = ACPI_ALLOCATE_BUFFER,
.Pointer = nullptr,
};
acpi_status = AcpiEvaluateObject(object, (char*)"H00A._PR0", nullptr, &buffer);
if (acpi_status == AE_OK) {
ACPI_OBJECT* pkg = static_cast<ACPI_OBJECT*>(buffer.Pointer);
for (unsigned i = 0; i < pkg->Package.Count; i++) {
ACPI_OBJECT* ref = &pkg->Package.Elements[i];
if (ref->Type != ACPI_TYPE_LOCAL_REFERENCE) {
zxlogf(DEBUG, "acpi: Ignoring wrong type 0x%x", ref->Type);
} else {
zxlogf(DEBUG, "acpi: Enabling HID controller at I2C0.H00A._PR0[%u]", i);
acpi_status = AcpiEvaluateObject(ref->Reference.Handle, (char*)"_ON", nullptr, nullptr);
if (acpi_status != AE_OK) {
zxlogf(ERROR, "acpi: acpi error 0x%x in I2C0._PR0._ON", acpi_status);
}
}
}
AcpiOsFree(buffer.Pointer);
}
}
// Acer workaround: Turn on the HID controller.
else if (!memcmp(&info->Name, "I2C1", 4)) {
zxlogf(DEBUG, "acpi: Enabling HID controller at I2C1");
acpi_status = AcpiEvaluateObject(object, (char*)"_PS0", nullptr, nullptr);
if (acpi_status != AE_OK) {
zxlogf(ERROR, "acpi: acpi error in I2C1._PS0: 0x%x", acpi_status);
}
}
}
// A small lambda helper we will use in order to publish generic ACPI devices.
zx_device_t* PublishAcpiDevice(zx_device_t* acpi_root, zx_device_t* platform_bus, const char* name,
ACPI_HANDLE handle, ACPI_DEVICE_INFO* info) {
auto device = std::make_unique<acpi::Device>(acpi_root, handle, platform_bus);
std::array<zx_device_prop_t, 4> props;
if (zx_status_t status = device->DdkAdd(name, get_device_add_args(name, info, &props));
status != ZX_OK) {
zxlogf(ERROR, "acpi: error %d in DdkAdd, parent=%s(%p)", status, device_get_name(acpi_root),
acpi_root);
return nullptr;
} else {
zxlogf(INFO, "acpi: published device %s(%p), parent=%s(%p), handle=%p", name, device.get(),
device_get_name(acpi_root), acpi_root, device->acpi_handle());
// device_add takes ownership of device, but only on success.
return device.release()->zxdev();
}
}
// A small helper class we can use to track the BBN (either "Base Bus
// Number" or "Bios Bus Number") of the last PCI bus node we encountered while
// walking the ACPI namespace.
class LastPciBbnTracker {
public:
LastPciBbnTracker() = default;
// If we are ascending through the level where we noticed a valid PCI BBN,
// then we are no longer valid.
void Ascend(uint32_t level) {
if (valid_ && (level == level_)) {
valid_ = false;
}
}
zx_status_t Descend(uint32_t level, ACPI_HANDLE object, const ACPI_DEVICE_INFO& obj_info) {
// Are we descending into a device node which has a hardware ID, and does
// that hardware ID indicate a PCI/PCIe bus? If so, try to extract the base
// bus number and stash it as our last seen PCI bus number.
const std::string_view hid = hid_from_acpi_devinfo(obj_info);
if ((hid == PCI_EXPRESS_ROOT_HID_STRING) || (hid == PCI_ROOT_HID_STRING)) {
uint8_t bbn;
zx_status_t status = acpi_bbn_call(object, &bbn);
if (status == ZX_ERR_NOT_FOUND) {
zxlogf(WARNING, "acpi: PCI/PCIe device \"%s\" missing _BBN entry, defaulting to 0",
fourcc_to_string(obj_info.Name).str);
bbn = 0;
return ZX_OK;
} else if (status != ZX_OK) {
zxlogf(ERROR, "acpi: failed to fetch BBN for PCI/PCIe device \"%s\"",
fourcc_to_string(obj_info.Name).str);
return ZX_ERR_BAD_STATE;
}
if (valid_) {
zxlogf(ERROR,
"acpi: Nested PCI roots detected when descending into PCI/PCIe device \"%s\" (prev "
"bbn %u at level %u, child bbn %u at "
"level %u",
fourcc_to_string(obj_info.Name).str, bbn_, level_, bbn, level);
return ZX_ERR_BAD_STATE;
}
valid_ = true;
level_ = level;
bbn_ = bbn;
}
return ZX_OK;
}
bool has_value() const { return valid_; }
uint8_t bbn() const {
ZX_DEBUG_ASSERT(valid_);
return bbn_;
}
private:
bool valid_ = false;
uint32_t level_ = 0;
uint8_t bbn_ = 0;
};
} // namespace
namespace acpi {
fitx::result<ACPI_STATUS, UniquePtr<ACPI_DEVICE_INFO>> GetObjectInfo(ACPI_HANDLE obj) {
ACPI_DEVICE_INFO* raw = nullptr;
ACPI_STATUS acpi_status = AcpiGetObjectInfo(obj, &raw);
UniquePtr<ACPI_DEVICE_INFO> ret{raw};
if (ACPI_SUCCESS(acpi_status)) {
return fitx::ok(std::move(ret));
}
return fitx::error(acpi_status);
}
ACPI_STATUS Device::AddResource(ACPI_RESOURCE* res) {
if (resource_is_memory(res)) {
resource_memory_t mem;
zx_status_t st = resource_parse_memory(res, &mem);
// only expect fixed memory resource. resource_parse_memory sets minimum == maximum
// for this memory resource type.
if ((st != ZX_OK) || (mem.minimum != mem.maximum)) {
return AE_ERROR;
}
mmio_resources_.emplace_back(mem);
} else if (resource_is_address(res)) {
resource_address_t addr;
zx_status_t st = resource_parse_address(res, &addr);
if (st != ZX_OK) {
return AE_ERROR;
}
if ((addr.resource_type == RESOURCE_ADDRESS_MEMORY) && addr.min_address_fixed &&
addr.max_address_fixed && (addr.maximum < addr.minimum)) {
mmio_resources_.emplace_back(/* writeable= */ true, addr.min_address_fixed,
/* alignment= */ 0, static_cast<uint32_t>(addr.address_length));
}
} else if (resource_is_io(res)) {
resource_io_t io;
zx_status_t st = resource_parse_io(res, &io);
if (st != ZX_OK) {
return AE_ERROR;
}
pio_resources_.emplace_back(io);
} else if (resource_is_irq(res)) {
resource_irq_t irq;
zx_status_t st = resource_parse_irq(res, &irq);
if (st != ZX_OK) {
return AE_ERROR;
}
for (auto i = 0; i < irq.pin_count; i++) {
irqs_.emplace_back(irq, i);
}
}
return AE_OK;
}
zx_status_t Device::ReportCurrentResources() {
if (got_resources_) {
return ZX_OK;
}
// call _CRS to fill in resources
ACPI_STATUS acpi_status = AcpiWalkResources(
acpi_handle_, (char*)"_CRS",
[](ACPI_RESOURCE* res, void* ctx) {
return reinterpret_cast<Device*>(ctx)->AddResource(res);
},
this);
if ((acpi_status != AE_NOT_FOUND) && (acpi_status != AE_OK)) {
return acpi_to_zx_status(acpi_status);
}
zxlogf(DEBUG, "acpi-bus[%s]: found %zd port resources %zd memory resources %zx irqs",
device_get_name(zxdev_), pio_resources_.size(), mmio_resources_.size(), irqs_.size());
if (zxlog_level_enabled(TRACE)) {
zxlogf(TRACE, "port resources:");
for (size_t i = 0; i < pio_resources_.size(); i++) {
zxlogf(TRACE, " %02zd: addr=0x%x length=0x%x align=0x%x", i, pio_resources_[i].base_address,
pio_resources_[i].address_length, pio_resources_[i].alignment);
}
zxlogf(TRACE, "memory resources:");
for (size_t i = 0; i < mmio_resources_.size(); i++) {
zxlogf(TRACE, " %02zd: addr=0x%x length=0x%x align=0x%x writeable=%d", i,
mmio_resources_[i].base_address, mmio_resources_[i].address_length,
mmio_resources_[i].alignment, mmio_resources_[i].writeable);
}
zxlogf(TRACE, "irqs:");
for (size_t i = 0; i < irqs_.size(); i++) {
const char* trigger;
switch (irqs_[i].trigger) {
case ACPI_IRQ_TRIGGER_EDGE:
trigger = "edge";
break;
case ACPI_IRQ_TRIGGER_LEVEL:
trigger = "level";
break;
default:
trigger = "bad_trigger";
break;
}
const char* polarity;
switch (irqs_[i].polarity) {
case ACPI_IRQ_ACTIVE_BOTH:
polarity = "both";
break;
case ACPI_IRQ_ACTIVE_LOW:
polarity = "low";
break;
case ACPI_IRQ_ACTIVE_HIGH:
polarity = "high";
break;
default:
polarity = "bad_polarity";
break;
}
zxlogf(TRACE, " %02zd: pin=%u %s %s %s %s", i, irqs_[i].pin, trigger, polarity,
(irqs_[i].sharable == ACPI_IRQ_SHARED) ? "shared" : "exclusive",
irqs_[i].wake_capable ? "wake" : "nowake");
}
}
got_resources_ = true;
return ZX_OK;
}
zx_status_t Device::AcpiGetPio(uint32_t index, zx::resource* out_pio) {
fbl::AutoLock<fbl::Mutex> guard{&lock_};
zx_status_t st = ReportCurrentResources();
if (st != ZX_OK) {
return st;
}
if (index >= pio_resources_.size()) {
return ZX_ERR_NOT_FOUND;
}
const DevicePioResource& res = pio_resources_[index];
// Please do not use get_root_resource() in new code. See fxbug.dev/31358.
// TODO: figure out what to pass to name here
return zx::resource::create(*zx::unowned_resource{get_root_resource()}, ZX_RSRC_KIND_IOPORT,
res.base_address, res.address_length, device_get_name(zxdev_), 0,
out_pio);
}
zx_status_t Device::AcpiGetMmio(uint32_t index, acpi_mmio* out_mmio) {
fbl::AutoLock<fbl::Mutex> guard{&lock_};
zx_status_t st = ReportCurrentResources();
if (st != ZX_OK) {
return st;
}
if (index >= mmio_resources_.size()) {
return ZX_ERR_NOT_FOUND;
}
const DeviceMmioResource& res = mmio_resources_[index];
if (((res.base_address & (PAGE_SIZE - 1)) != 0) ||
((res.address_length & (PAGE_SIZE - 1)) != 0)) {
zxlogf(ERROR, "acpi-bus[%s]: memory id=%d addr=0x%08x len=0x%x is not page aligned",
device_get_name(zxdev_), index, res.base_address, res.address_length);
return ZX_ERR_NOT_FOUND;
}
zx_handle_t vmo;
size_t size{res.address_length};
// Please do not use get_root_resource() in new code. See fxbug.dev/31358.
st = zx_vmo_create_physical(get_root_resource(), res.base_address, size, &vmo);
if (st != ZX_OK) {
return st;
}
out_mmio->offset = 0;
out_mmio->size = size;
out_mmio->vmo = vmo;
return ZX_OK;
}
zx_status_t Device::AcpiMapInterrupt(int64_t which_irq, zx::interrupt* out_handle) {
fbl::AutoLock<fbl::Mutex> guard{&lock_};
zx_status_t st = ReportCurrentResources();
if (st != ZX_OK) {
return st;
}
if ((uint)which_irq >= irqs_.size()) {
return ZX_ERR_NOT_FOUND;
}
const DeviceIrqResource& irq = irqs_[which_irq];
uint32_t mode;
mode = ZX_INTERRUPT_MODE_DEFAULT;
st = ZX_OK;
switch (irq.trigger) {
case ACPI_IRQ_TRIGGER_EDGE:
switch (irq.polarity) {
case ACPI_IRQ_ACTIVE_BOTH:
mode = ZX_INTERRUPT_MODE_EDGE_BOTH;
break;
case ACPI_IRQ_ACTIVE_LOW:
mode = ZX_INTERRUPT_MODE_EDGE_LOW;
break;
case ACPI_IRQ_ACTIVE_HIGH:
mode = ZX_INTERRUPT_MODE_EDGE_HIGH;
break;
default:
st = ZX_ERR_INVALID_ARGS;
break;
}
break;
case ACPI_IRQ_TRIGGER_LEVEL:
switch (irq.polarity) {
case ACPI_IRQ_ACTIVE_LOW:
mode = ZX_INTERRUPT_MODE_LEVEL_LOW;
break;
case ACPI_IRQ_ACTIVE_HIGH:
mode = ZX_INTERRUPT_MODE_LEVEL_HIGH;
break;
default:
st = ZX_ERR_INVALID_ARGS;
break;
}
break;
default:
st = ZX_ERR_INVALID_ARGS;
break;
}
if (st != ZX_OK) {
return st;
}
// Please do not use get_root_resource() in new code. See fxbug.dev/31358.
return zx::interrupt::create(*zx::unowned_resource{get_root_resource()}, irq.pin,
ZX_INTERRUPT_REMAP_IRQ | mode, out_handle);
}
zx_status_t Device::AcpiGetBti(uint32_t bdf, uint32_t index, zx::bti* bti) {
// The x86 IOMMU world uses PCI BDFs as the hardware identifiers, so there
// will only be one BTI per device.
ZX_ASSERT(index == 0);
// For dummy IOMMUs, the bti_id just needs to be unique. For Intel IOMMUs,
// the bti_ids correspond to PCI BDFs.
zx_handle_t iommu_handle;
zx_status_t status = iommu_manager_iommu_for_bdf(bdf, &iommu_handle);
if (status != ZX_OK) {
return status;
}
return zx::bti::create(*zx::unowned_iommu{iommu_handle}, 0, bdf, bti);
}
zx_status_t Device::AcpiConnectSysmem(zx::channel connection) {
fbl::AutoLock<fbl::Mutex> guard{&lock_};
sysmem_protocol_t sysmem;
zx_status_t st = device_get_protocol(platform_bus_, ZX_PROTOCOL_SYSMEM, &sysmem);
if (st != ZX_OK) {
return st;
}
return sysmem_connect(&sysmem, connection.release());
}
zx_status_t Device::AcpiRegisterSysmemHeap(uint64_t heap, zx::channel connection) {
fbl::AutoLock<fbl::Mutex> guard{&lock_};
sysmem_protocol_t sysmem;
zx_status_t st = device_get_protocol(platform_bus_, ZX_PROTOCOL_SYSMEM, &sysmem);
if (st != ZX_OK) {
return st;
}
return sysmem_register_heap(&sysmem, heap, connection.release());
}
} // namespace acpi
device_add_args_t get_device_add_args(const char* name, ACPI_DEVICE_INFO* info,
std::array<zx_device_prop_t, 4>* out_props) {
uint32_t propcount = 0;
// Publish HID, and the first CID (if present), in device props
if (zx_status_t status = acpi::ExtractHidToDevProps(*info, *out_props, propcount);
status != ZX_OK) {
zxlogf(WARNING, "Failed to extract HID into dev_props for acpi device \"%s\" (status %d)\n",
fourcc_to_string(info->Name).str, status);
}
if (zx_status_t status = acpi::ExtractCidToDevProps(*info, *out_props, propcount);
status != ZX_OK) {
zxlogf(WARNING, "Failed to extract CID into dev_props for acpi device \"%s\" (status %d)\n",
fourcc_to_string(info->Name).str, status);
}
if (zxlog_level_enabled(TRACE)) {
// ACPI names are always 4 characters in a uint32
zxlogf(TRACE, "acpi: got device %s", fourcc_to_string(info->Name).str);
if (info->Valid & ACPI_VALID_HID) {
zxlogf(TRACE, " HID=%s", info->HardwareId.String);
} else {
zxlogf(TRACE, " HID=invalid");
}
if (info->Valid & ACPI_VALID_ADR) {
zxlogf(TRACE, " ADR=0x%" PRIx64 "", (uint64_t)info->Address);
} else {
zxlogf(TRACE, " ADR=invalid");
}
if (info->Valid & ACPI_VALID_CID) {
zxlogf(TRACE, " CIDS=%d", info->CompatibleIdList.Count);
for (uint i = 0; i < info->CompatibleIdList.Count; i++) {
zxlogf(TRACE, " [%u] %s", i, info->CompatibleIdList.Ids[i].String);
}
} else {
zxlogf(TRACE, " CID=invalid");
}
zxlogf(TRACE, " devprops:");
for (size_t i = 0; i < propcount; i++) {
zxlogf(TRACE, " [%zu] id=0x%08x value=0x%08x", i, (*out_props)[i].id,
(*out_props)[i].value);
}
}
return {.name = name,
.props = (propcount > 0) ? out_props->data() : nullptr,
.prop_count = propcount};
}
zx_status_t acpi_suspend(uint8_t requested_state, bool enable_wake, uint8_t suspend_reason,
uint8_t* out_state) {
switch (suspend_reason & DEVICE_MASK_SUSPEND_REASON) {
case DEVICE_SUSPEND_REASON_MEXEC: {
AcpiTerminate();
return ZX_OK;
}
case DEVICE_SUSPEND_REASON_REBOOT:
if (suspend_reason == DEVICE_SUSPEND_REASON_REBOOT_BOOTLOADER) {
reboot_bootloader();
} else if (suspend_reason == DEVICE_SUSPEND_REASON_REBOOT_RECOVERY) {
reboot_recovery();
} else {
reboot();
}
// Kill this driver so that the IPC channel gets closed; devmgr will
// perform a fallback that should shutdown or reboot the machine.
exit(0);
case DEVICE_SUSPEND_REASON_POWEROFF:
poweroff();
exit(0);
case DEVICE_SUSPEND_REASON_SUSPEND_RAM:
return suspend_to_ram();
default:
return ZX_ERR_NOT_SUPPORTED;
};
}
zx_status_t publish_acpi_devices(zx_device_t* platform_bus, zx_device_t* sys_root,
zx_device_t* acpi_root) {
zx_status_t status = pwrbtn_init(acpi_root);
if (status != ZX_OK) {
zxlogf(ERROR, "acpi: failed to initialize pwrbtn device: %d", status);
}
// Walk the devices in the ACPI tree, handling any device specific quirks as
// we go, and publishing any static metadata we need to publish before
// publishing any devices.
//
// TODO(fxbug.dev/56832): Remove this pass when we have a better way to manage
// driver dependencies on ACPI. Once drivers can access their metadata
// directly via a connection to the ACPI driver, we will not need to bother
// with publishing static metadata before we publish devices.
LastPciBbnTracker last_pci_bbn;
ACPI_STATUS acpi_status;
acpi_status = acpi::WalkNamespace(
ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT, MAX_NAMESPACE_DEPTH,
[sys_root, &last_pci_bbn](ACPI_HANDLE object, uint32_t level,
acpi::WalkDirection dir) -> ACPI_STATUS {
// If we are ascending, tell our PciBbn tracker so that it can properly
// invalidate our last BBN when needed.
if (dir == acpi::WalkDirection::Ascending) {
last_pci_bbn.Ascend(level);
return AE_OK;
}
// We are descending. Grab our object info.
acpi::UniquePtr<ACPI_DEVICE_INFO> info;
if (auto res = acpi::GetObjectInfo(object); res.is_error()) {
return res.error_value();
} else {
info = std::move(res.value());
}
// Apply any workarounds for quirks.
acpi_apply_workarounds(object, info.get());
// If this is a PCI node we are passing through, track it's BBN. We
// will need it in order to publish metadata for the devices we
// encounter. If we encounter a fatal condition, terminate the walk.
if (last_pci_bbn.Descend(level, object, *info) != ZX_OK) {
return AE_ERROR;
}
// Is this an HDAS (Intel HDA audio controller) or I2Cx (I2C bus) device node
// under PCI? If so, attempt to publish their relevant metadata so that the
// device driver can access it when the PCI device itself is finally
// published.
//
// TODO(fxbug.dev/56832): Remove this when we have a better way to manage driver
// dependencies on ACPI.
constexpr uint32_t kMAXL_Id = make_fourcc('M', 'A', 'X', 'L');
constexpr uint32_t kMAXR_Id = make_fourcc('M', 'A', 'X', 'R');
constexpr uint32_t kRT53_Id = make_fourcc('R', 'T', '5', '3');
constexpr uint32_t kRT54_Id = make_fourcc('R', 'T', '5', '4');
constexpr uint32_t kHDAS_Id = make_fourcc('H', 'D', 'A', 'S');
constexpr uint32_t kI2Cx_Id = make_fourcc('I', '2', 'C', 0);
constexpr uint32_t kI2Cx_Mask = make_fourcc(0xFF, 0xFF, 0xFF, 0x00);
if ((info->Name == kMAXL_Id) || (info->Name == kMAXR_Id) || (info->Name == kRT53_Id) ||
(info->Name == kRT54_Id) || (info->Name == kHDAS_Id) ||
((info->Name & kI2Cx_Mask) == kI2Cx_Id)) {
// We must have already seen at least one PCI root due to traversal order.
if (!last_pci_bbn.has_value()) {
zxlogf(WARNING,
"acpi: Found HDAS/I2Cx node (\"%s\"), but no prior PCI root was discovered!",
fourcc_to_string(info->Name).str);
} else if (!(info->Valid & ACPI_VALID_ADR)) {
zxlogf(WARNING, "acpi: no valid ADR found for device \"%s\"",
fourcc_to_string(info->Name).str);
} else {
if (info->Name == kHDAS_Id) {
// Attaching metadata to the HDAS device /dev/sys/pci/...
zx_status_t status = nhlt_publish_metadata(
sys_root, last_pci_bbn.bbn(), static_cast<uint64_t>(info->Address), object);
if ((status != ZX_OK) && (status != ZX_ERR_NOT_FOUND)) {
zxlogf(ERROR, "acpi: failed to publish NHLT metadata");
}
} else {
// Attaching metadata to the I2Cx device /dev/sys/pci/...
zx_status_t status =
I2cBusPublishMetadata(sys_root, last_pci_bbn.bbn(),
static_cast<uint64_t>(info->Address), *info, object);
if ((status != ZX_OK) && (status != ZX_ERR_NOT_FOUND)) {
zxlogf(ERROR, "acpi: failed to publish I2C metadata");
}
}
}
}
return AE_OK;
});
if (!ACPI_SUCCESS(acpi_status)) {
zxlogf(WARNING, "acpi: Error (%d) during fixup and metadata pass", acpi_status);
}
// Now walk the ACPI namespace looking for devices we understand, and publish
// them. For now, publish only the first PCI bus we encounter.
bool published_pci_bus = false;
acpi_status = acpi::WalkNamespace(
ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT, MAX_NAMESPACE_DEPTH,
[sys_root, acpi_root, platform_bus, &published_pci_bus](
ACPI_HANDLE object, uint32_t level, acpi::WalkDirection dir) -> ACPI_STATUS {
// We don't have anything useful to do during the ascent phase. Just
// skip it.
if (dir == acpi::WalkDirection::Ascending) {
return AE_OK;
}
// We are descending. Grab our object info.
acpi::UniquePtr<ACPI_DEVICE_INFO> info;
if (auto res = acpi::GetObjectInfo(object); res.is_error()) {
return res.error_value();
} else {
info = std::move(res.value());
}
// Extract pointers to the hardware ID and the compatible ID if present.
// If there is no hardware ID, just skip the device.
const std::string_view hid = hid_from_acpi_devinfo(*info);
const std::string_view cid = cid_from_acpi_devinfo(*info);
if (hid.empty()) {
return AE_OK;
}
// Now, if we recognize the HID, go ahead and deal with publishing the
// device.
if ((hid == PCI_EXPRESS_ROOT_HID_STRING) || (hid == PCI_ROOT_HID_STRING)) {
if (!published_pci_bus) {
if (pci_init(sys_root, platform_bus, object, info.get()) == ZX_OK) {
published_pci_bus = true;
} else {
zxlogf(WARNING, "Skipping extra PCI/PCIe bus \"%s\"",
fourcc_to_string(info->Name).str);
}
}
} else if (hid == BATTERY_HID_STRING) {
battery_init(acpi_root, object);
} else if (hid == LID_HID_STRING) {
lid_init(acpi_root, object);
} else if (hid == PWRSRC_HID_STRING) {
pwrsrc_init(acpi_root, object);
} else if (hid == EC_HID_STRING) {
ec_init(acpi_root, object);
} else if (hid == GOOGLE_TBMC_HID_STRING) {
tbmc_init(acpi_root, object);
} else if (hid == GOOGLE_CROS_EC_HID_STRING) {
cros_ec_lpc_init(acpi_root, object);
} else if (hid == DPTF_THERMAL_HID_STRING) {
thermal_init(acpi_root, info.get(), object);
} else if ((hid == I8042_HID_STRING) || (cid == I8042_HID_STRING)) {
PublishAcpiDevice(acpi_root, platform_bus, "i8042", object, info.get());
} else if ((hid == RTC_HID_STRING) || (cid == RTC_HID_STRING)) {
PublishAcpiDevice(acpi_root, platform_bus, "rtc", object, info.get());
} else if (hid == GOLDFISH_PIPE_HID_STRING) {
PublishAcpiDevice(acpi_root, platform_bus, "goldfish", object, info.get());
} else if (hid == SERIAL_HID_STRING) {
PublishAcpiDevice(acpi_root, platform_bus, "serial", object, info.get());
}
return AE_OK;
});
if (acpi_status != AE_OK) {
return ZX_ERR_BAD_STATE;
} else {
return ZX_OK;
}
}