blob: 640ebe1ba7de570299167186e1d4b22ee0a5ad6d [file]
// Copyright 2021 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 "src/devices/board/lib/acpi/manager.h"
#include <fidl/fuchsia.hardware.spi.businfo/cpp/wire.h>
#include <lib/ddk/binding_driver.h>
#include <lib/ddk/debug.h>
#include <lib/ddk/device.h>
#include <lib/zx/result.h>
#include <zircon/compiler.h>
#include <zircon/status.h>
#include <memory>
#include <acpica/acpi.h>
#include "src/devices/board/lib/acpi/acpi.h"
#include "src/devices/board/lib/acpi/pci.h"
#include "src/devices/board/lib/acpi/power-resource.h"
#include "src/devices/board/lib/acpi/util.h"
namespace acpi {
acpi::status<> Manager::DiscoverDevices() {
// Make sure our "ACPI root device" corresponds to the root of the ACPI tree.
auto root = acpi_->GetHandle(nullptr, "\\");
if (root.is_error()) {
zxlogf(WARNING, "Failed to get ACPI root object: %d", root.error_value());
return root.take_error();
}
devices_.emplace(root.value(), DeviceBuilder::MakeRootDevice(root.value(), acpi_root_));
uint32_t ignored_depth = Acpi::kMaxNamespaceDepth + 1;
return acpi_->WalkNamespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT, Acpi::kMaxNamespaceDepth,
[this, &ignored_depth](ACPI_HANDLE handle, uint32_t depth,
WalkDirection dir) -> acpi::status<> {
if (depth > ignored_depth) {
// If we're ignoring this branch of the tree, do nothing.
return acpi::ok();
}
if (dir == WalkDirection::Ascending) {
if (depth == ignored_depth) {
// We've come back to where we set 'ignored_depth', reset it.
ignored_depth = Acpi::kMaxNamespaceDepth + 1;
}
// Nothing to do when ascending the tree.
return acpi::ok();
}
auto status = DiscoverDevice(handle);
if (status.is_error()) {
auto path = acpi_->GetPath(handle).value_or("(get path failed)");
zxlogf(WARNING,
"Failed to discover device '%s': %d. Any children will "
"not be enumerated, the system may not function fully.",
path.data(), status.error_value());
// We failed to enumerate this device, so we don't enumerate any
// of its children.
ignored_depth = depth;
return acpi::ok();
}
if (status.value()) {
// This device is not present, so we should not enumerate its
// children. Mark this branch of the tree as ignored.
ignored_depth = depth;
}
return acpi::ok();
});
}
acpi::status<> Manager::ConfigureDiscoveredDevices() {
for (auto& handle : device_publish_order_) {
auto device = LookupDevice(handle);
auto result = device->GatherResources(
acpi_, this, [this](ACPI_HANDLE bus, BusType type, DeviceChildEntry child) -> size_t {
DeviceBuilder* b = LookupDevice(bus);
if (b == nullptr) {
// Silently ignore.
return -1;
}
b->SetBusType(type);
if (std::holds_alternative<fuchsia_hardware_i2c_businfo::I2CChannel>(child)) {
std::get<fuchsia_hardware_i2c_businfo::I2CChannel>(child).global_id() =
next_i2c_global_id_++;
} else if (std::holds_alternative<fuchsia_hardware_spi_businfo::SpiChannel>(child)) {
std::get<fuchsia_hardware_spi_businfo::SpiChannel>(child).global_id() =
next_spi_global_id_++;
}
size_t child_index = b->AddBusChild(child);
if (b->HasBusId()) {
return child_index;
}
// If this device is a bus, figure out its bus id.
// For PCI buses, we us the BBN ("BIOS Bus Number") from ACPI.
// For other buses, we simply have a counter of the number of that kind
// of bus we've encountered.
uint32_t bus_id;
if (type == BusType::kPci) {
// The first PCI bus we encounter is special.
auto bbn_result = acpi_->CallBbn(b->handle());
if (bbn_result.zx_status_value() == ZX_ERR_NOT_FOUND) {
bus_id = 0;
} else if (bbn_result.is_ok()) {
bus_id = bbn_result.value();
} else {
zxlogf(ERROR, "Failed to get BBN for PCI bus '%s'", b->name());
return -1;
}
} else {
bus_id = next_bus_ids_.emplace(type, 0).first->second++;
}
b->SetBusId(bus_id);
return child_index;
});
if (result.is_error()) {
zxlogf(WARNING, "Failed to InferBusTypes for %s: %d", device->name(), result.error_value());
}
}
return acpi::ok();
}
acpi::status<> Manager::PublishDevices(zx_device_t* platform_bus,
async_dispatcher_t* device_dispatcher) {
for (auto handle : device_publish_order_) {
DeviceBuilder* d = LookupDevice(handle);
if (d == nullptr) {
continue;
}
auto status = d->Build(this, device_dispatcher);
if (status.is_error()) {
return acpi::error(AE_ERROR);
}
zx_devices_.emplace(handle, status.value());
uint32_t bus_type = d->GetBusType();
if (bus_type == BusType::kPci) {
auto status = PublishPciBus(platform_bus, d);
if (status.is_error()) {
return status.take_error();
}
}
}
return acpi::ok();
}
uint32_t Manager::GetNextCompositeId(std::string composite) {
auto id = composite_list_[composite];
composite_list_[composite] += 1;
return id;
}
const PowerResource* Manager::AddPowerResource(ACPI_HANDLE power_resource_handle) {
std::scoped_lock lock(power_resource_lock_);
auto power_resource_entry = power_resources_.find(power_resource_handle);
if (power_resource_entry == power_resources_.end()) {
PowerResource power_resource = PowerResource(acpi_, power_resource_handle);
if (power_resource.Init() != ZX_OK) {
return nullptr;
}
power_resource_entry = power_resources_.insert({power_resource_handle, power_resource}).first;
}
return &power_resource_entry->second;
}
zx_status_t Manager::ReferencePowerResources(
const std::vector<ACPI_HANDLE>& power_resource_handles) {
std::scoped_lock lock(power_resource_lock_);
for (auto it = power_resource_handles.begin(); it != power_resource_handles.end(); ++it) {
auto power_resource = power_resources_.find(*it);
ZX_ASSERT_MSG(power_resource != power_resources_.end(),
"Attempting to reference an unknown power resource.");
zx_status_t status = power_resource->second.Reference();
if (status != ZX_OK) {
// Re-dereference all the successfully referenced power resources.
while (it != power_resource_handles.begin()) {
--it;
auto power_resource = power_resources_.find(*it);
power_resource->second.Dereference();
}
return status;
}
}
return ZX_OK;
}
zx_status_t Manager::DereferencePowerResources(
const std::vector<ACPI_HANDLE>& power_resource_handles) {
std::scoped_lock lock(power_resource_lock_);
for (auto rit = power_resource_handles.rbegin(); rit != power_resource_handles.rend(); ++rit) {
auto power_resource = power_resources_.find(*rit);
ZX_ASSERT_MSG(power_resource != power_resources_.end(),
"Attempting to dereference an unknown power resource.");
zx_status_t status = power_resource->second.Dereference();
if (status != ZX_OK) {
// Re-reference all the successfully dereferenced power resources.
while (rit != power_resource_handles.rbegin()) {
--rit;
auto power_resource = power_resources_.find(*rit);
power_resource->second.Reference();
}
return status;
}
}
return ZX_OK;
}
acpi::status<bool> Manager::DiscoverDevice(ACPI_HANDLE handle) {
auto result = acpi_->GetObjectInfo(handle);
if (result.is_error()) {
zxlogf(INFO, "get object info failed");
return result.take_error();
}
UniquePtr<ACPI_DEVICE_INFO> info = std::move(result.value());
std::string name(reinterpret_cast<char*>(&info->Name), sizeof(info->Name));
auto state_result = acpi_->EvaluateObject(handle, "_STA", std::nullopt);
bool examine_children;
uint64_t state;
if (state_result.zx_status_value() == ZX_ERR_NOT_FOUND) {
// No state means all bits are set.
state = ~0;
examine_children = true;
} else if (state_result.is_ok()) {
if (state_result->Type != ACPI_TYPE_INTEGER) {
zxlogf(ERROR, "%s returned incorrect object type for _STA", name.data());
return acpi::error(AE_BAD_VALUE);
}
state = state_result->Integer.Value;
examine_children = state & (ACPI_STA_DEVICE_PRESENT | ACPI_STA_DEVICE_FUNCTIONING);
} else {
return state_result.take_error();
}
// See ACPI 6.4, Table 6.66.
if (!examine_children) {
zxlogf(DEBUG, "device '%s' not present, so not enumerating.", name.data());
return acpi::ok(true);
}
acpi::status<ACPI_HANDLE> parent = acpi::ok(handle);
bool is_device = false;
do {
parent = acpi_->GetParent(*parent);
if (parent.is_error()) {
zxlogf(ERROR, "Device '%s' failed to get parent: %d", name.data(), parent.status_value());
return parent.take_error();
}
auto parent_info = acpi_->GetObjectInfo(*parent);
if (parent_info.is_error()) {
zxlogf(ERROR, "Failed to get object info: %d", parent_info.status_value());
return parent_info.take_error();
}
is_device = parent_info->Type == ACPI_TYPE_DEVICE;
} while (!is_device);
DeviceBuilder* parent_ptr = LookupDevice(parent.value());
if (parent_ptr == nullptr) {
// Our parent should have been visited before us (since we're descending down the tree),
// so this should never happen.
zxlogf(ERROR, "Device %s has no discovered parent? (%p)", name.data(), parent.value());
return acpi::error(AE_NOT_FOUND);
}
DeviceBuilder device(std::move(name), handle, parent_ptr, state, device_id_);
device_id_++;
if (info->Flags & ACPI_PCI_ROOT_BRIDGE) {
device.SetBusType(BusType::kPci);
}
device_publish_order_.emplace_back(handle);
devices_.emplace(handle, std::move(device));
return acpi::ok(false);
}
acpi::status<> Manager::PublishPciBus(zx_device_t* platform_bus, DeviceBuilder* device) {
if (published_pci_bus_) {
return acpi::ok();
}
// Publish the PCI bus. TODO(https://fxbug.dev/42158465): we might be able to move this out of the
// board driver when ACPI work is done. For now we do this here because we need to ensure
// that we've published the child metadata before the PCI bus driver sees it. This also
// means we have two devices that represent the PCI bus: one sits in the right place in the
// ACPI topology, and the other is the actual PCI bus that sits at /dev/sys/platform/pci.
auto info = acpi_->GetObjectInfo(device->handle());
if (info.is_error()) {
return info.take_error();
}
const DeviceChildData& children = device->GetBusChildren();
const std::vector<PciTopo>* vec_ptr = nullptr;
if (device->HasBusChildren()) {
vec_ptr = std::get_if<std::vector<PciTopo>>(&children);
if (!vec_ptr) {
zxlogf(ERROR, "PCI bus had non-PCI children.");
return acpi::error(AE_BAD_DATA);
}
}
std::vector<pci_bdf_t> bdfs;
if (vec_ptr) {
// If we have children, generate a list of them to pass to the PCI driver.
for (uint64_t df : *vec_ptr) {
uint32_t dev_id = (df & (0xffff0000)) >> 16;
uint32_t func_id = df & 0x0000ffff;
bdfs.emplace_back(pci_bdf_t{
.bus_id = static_cast<uint8_t>(device->GetBusId()),
.device_id = static_cast<uint8_t>(dev_id),
.function_id = static_cast<uint8_t>(func_id),
});
}
}
if (pci_init(platform_bus, device->handle(), std::move(info.value()), this, std::move(bdfs)) ==
ZX_OK) {
published_pci_bus_ = true;
}
return acpi::ok();
}
DeviceBuilder* Manager::LookupDevice(ACPI_HANDLE handle) {
auto result = devices_.find(handle);
if (result == devices_.end()) {
return nullptr;
}
return &result->second;
}
} // namespace acpi