blob: 7ffd4e10ec94b32d556972d2bc74bea28a39e164 [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/device-builder.h"
#include <lib/ddk/debug.h>
#include <zircon/compiler.h>
#include <bind/fuchsia/acpi/cpp/bind.h>
#include <bind/fuchsia/cpp/bind.h>
#include <bind/fuchsia/pci/cpp/bind.h>
#include <fbl/string_printf.h>
#ifdef __Fuchsia__
#include "src/devices/board/lib/acpi/device.h"
#include "src/devices/board/lib/acpi/irq-fragment.h"
#else
#include "src/devices/board/lib/acpi/device-for-host.h"
#endif
#include "src/devices/board/lib/acpi/manager.h"
#include "src/devices/board/lib/acpi/resources.h"
#include "src/devices/lib/acpi/util.h"
namespace acpi {
namespace {
device_bind_prop_t MakeProperty(zx_device_str_prop_t str_prop) {
switch (str_prop.property_value.data_type) {
case ZX_DEVICE_PROPERTY_VALUE_STRING:
return ddk::MakeProperty(str_prop.key, str_prop.property_value.data.str_val);
case ZX_DEVICE_PROPERTY_VALUE_BOOL:
return ddk::MakeProperty(str_prop.key, str_prop.property_value.data.bool_val);
case ZX_DEVICE_PROPERTY_VALUE_INT:
return ddk::MakeProperty(str_prop.key, str_prop.property_value.data.int_val);
case ZX_DEVICE_PROPERTY_VALUE_ENUM:
return ddk::MakeProperty(str_prop.key, str_prop.property_value.data.enum_val);
default:
ZX_PANIC("Unknown property type: %d", str_prop.property_value.data_type);
}
}
ddk::BindRule MakeAcceptBindRule(zx_device_str_prop_t str_prop) {
device_bind_prop_value_t value;
switch (str_prop.property_value.data_type) {
case ZX_DEVICE_PROPERTY_VALUE_STRING:
value = device_bind_prop_str_val(str_prop.property_value.data.str_val);
break;
case ZX_DEVICE_PROPERTY_VALUE_BOOL:
value = device_bind_prop_bool_val(str_prop.property_value.data.bool_val);
break;
case ZX_DEVICE_PROPERTY_VALUE_INT:
value = device_bind_prop_int_val(str_prop.property_value.data.int_val);
break;
case ZX_DEVICE_PROPERTY_VALUE_ENUM:
value = device_bind_prop_enum_val(str_prop.property_value.data.enum_val);
break;
default:
ZX_PANIC("Unknown property type: %d", str_prop.property_value.data_type);
}
return ddk::BindRule(device_bind_prop_str_key(str_prop.key), DEVICE_BIND_RULE_CONDITION_ACCEPT,
value);
}
} // namespace
acpi::status<> DeviceBuilder::GatherResources(acpi::Acpi* acpi, acpi::Manager* manager,
GatherResourcesCallback callback) {
if (!handle_ || !parent_) {
// Skip the root device.
return acpi::ok();
}
// Don't decode resources if the ENABLED bit is not set.
// See ACPI v6.4 section 6.3.7
if (!(state_ & ACPI_STA_DEVICE_ENABLED)) {
return acpi::ok();
}
// TODO(https://fxbug.dev/42158705): Handle other resources like serial buses.
auto result = acpi->WalkResources(
handle_, "_CRS", [this, acpi, manager, &callback](ACPI_RESOURCE* res) -> acpi::status<> {
ACPI_HANDLE bus_parent = nullptr;
BusType type = BusType::kUnknown;
DeviceChildEntry entry;
const char* bus_id_prop;
if (resource_is_spi(res)) {
type = BusType::kSpi;
auto result = resource_parse_spi(acpi, handle_, res, &bus_parent);
if (result.is_error()) {
zxlogf(WARNING, "Failed to parse SPI resource: %d", result.error_value());
return result.take_error();
}
entry = result.value();
bus_id_prop = bind_fuchsia::ID;
str_props_.emplace_back(OwnedStringProp(bind_fuchsia::NAME, "spi"));
} else if (resource_is_i2c(res)) {
type = BusType::kI2c;
auto result = resource_parse_i2c(acpi, handle_, res, &bus_parent);
if (result.is_error()) {
zxlogf(WARNING, "Failed to parse I2C resource: %d", result.error_value());
return result.take_error();
}
entry = result.value();
bus_id_prop = bind_fuchsia::ID;
str_props_.emplace_back(OwnedStringProp(bind_fuchsia::NAME, "i2c"));
} else if (resource_is_irq(res)) {
irq_count_++;
}
if (bus_parent) {
size_t bus_index = callback(bus_parent, type, entry);
DeviceBuilder* b = manager->LookupDevice(bus_parent);
buses_.emplace_back(b, bus_index);
str_props_.emplace_back(OwnedStringProp(bus_id_prop, b->GetBusId()));
has_address_ = true;
}
return acpi::ok();
});
if (result.is_error() && result.zx_status_value() != ZX_ERR_NOT_FOUND) {
return result.take_error();
}
auto info = acpi->GetObjectInfo(handle_);
if (info.is_error()) {
zxlogf(WARNING, "Failed to get object info: %d", info.status_value());
return info.take_error();
}
// PCI is special, and PCI devices don't have an explicit resource. Instead, we need to check
// _ADR for PCI addressing info.
if (parent_->bus_type_ == BusType::kPci) {
if (info->Valid & ACPI_VALID_ADR) {
callback(parent_->handle_, BusType::kPci, DeviceChildEntry(info->Address));
// Set up some bind properties for ourselves. callback() should set HasBusId.
ZX_ASSERT(parent_->HasBusId());
uint32_t bus_id = parent_->GetBusId();
uint32_t device = (info->Address & (0xffff0000)) >> 16;
uint32_t func = info->Address & 0x0000ffff;
str_props_.emplace_back(
OwnedStringProp(bind_fuchsia::PCI_TOPO, BIND_PCI_TOPO_PACK(bus_id, device, func)));
// Should we buses_.emplace_back() here? The PCI bus driver currently publishes PCI
// composites, so having a device on a PCI bus that uses other buses resources can't be
// represented. Such devices don't seem to exist, but if we ever encounter one, it will need
// to be handled somehow.
has_address_ = true;
}
}
bool has_devicetree_cid = false;
// Add HID and CID properties, if present.
if (info->Valid & ACPI_VALID_HID) {
if (!strcmp(info->HardwareId.String, kDeviceTreeLinkID)) {
has_devicetree_cid = CheckForDeviceTreeCompatible(acpi);
} else {
str_props_.emplace_back(OwnedStringProp(bind_fuchsia_acpi::HID, info->HardwareId.String));
}
}
if (!has_devicetree_cid && info->Valid & ACPI_VALID_CID && info->CompatibleIdList.Count > 0) {
auto& first = info->CompatibleIdList.Ids[0];
if (!strcmp(first.String, kDeviceTreeLinkID)) {
has_devicetree_cid = CheckForDeviceTreeCompatible(acpi);
} else {
// We only expose the first CID.
str_props_.emplace_back(OwnedStringProp(bind_fuchsia_acpi::FIRST_CID, first.String));
}
}
// If our parent has a bus type, and we have an address on that bus, then we'll expose it in our
// bind properties.
if (parent_->GetBusType() != BusType::kUnknown && has_address_) {
str_props_.emplace_back(OwnedStringProp(bind_fuchsia::ACPI_BUS_TYPE, parent_->GetBusType()));
}
if (result.status_value() == AE_NOT_FOUND) {
return acpi::ok();
}
return result;
}
zx::result<zx_device_t*> DeviceBuilder::Build(acpi::Manager* manager,
async_dispatcher_t* device_dispatcher) {
if (parent_->zx_device_ == nullptr) {
zxlogf(ERROR, "Parent has not been added to the tree yet!");
return zx::error(ZX_ERR_BAD_STATE);
}
if (zx_device_ != nullptr) {
zxlogf(ERROR, "This device (%s) has already been built!", name());
return zx::error(ZX_ERR_BAD_STATE);
}
DeviceArgs device_args(parent_->zx_device_, manager, device_dispatcher, handle_);
if (HasBusId() && bus_type_ != BusType::kPci) {
zx::result metadata = GetMetadata();
if (metadata.is_error()) {
zxlogf(ERROR, "Failed to get metadata for '%s': %d", name(), metadata.status_value());
return metadata.take_error();
}
device_args.SetBusMetadata(std::move(*metadata), bus_type_, GetBusId());
}
auto device = std::make_unique<Device>(std::move(device_args));
// Narrow our custom type down to zx_device_str_prop_t.
// Any strings in zx_device_str_prop_t will still point at their equivalents
// in the original str_props_ array.
std::vector<zx_device_str_prop_t> str_props_for_ddkadd;
for (auto& str_prop : str_props_) {
str_props_for_ddkadd.emplace_back(str_prop);
}
uint32_t add_flags = DEVICE_ADD_MUST_ISOLATE;
if ((state_ & (ACPI_STA_DEVICE_FUNCTIONING | ACPI_STA_DEVICE_PRESENT)) ==
ACPI_STA_DEVICE_FUNCTIONING) {
// Don't bind drivers to this device if it is functioning but not present.
// See ACPI 6.4 section 6.3.7.
add_flags |= DEVICE_ADD_NON_BINDABLE;
}
auto result = device->AddDevice(name(), cpp20::span(str_props_for_ddkadd), add_flags);
if (result.is_error()) {
zxlogf(ERROR, "failed to publish acpi device '%s' (parent=%s): %d", name(), parent_->name(),
result.status_value());
return result.take_error();
}
auto* acpi_dev = device.release();
zx_device_ = acpi_dev->zxdev();
for (uint32_t i = 0; i < irq_count_; i++) {
#ifdef __Fuchsia__
auto result = IrqFragment::Create(device_dispatcher, *acpi_dev, i, device_id_);
if (result.is_error()) {
zxlogf(ERROR, "Failed to construct IRQ fragment: %d", result.status_value());
return result.take_error();
}
#endif
}
auto status = BuildComposite(manager, str_props_for_ddkadd, device_dispatcher);
if (status.is_error()) {
zxlogf(WARNING, "failed to publish composite acpi device '%s-composite': %d", name(),
status.error_value());
return status.take_error();
}
return zx::ok(zx_device_);
}
size_t DeviceBuilder::AddBusChild(DeviceChildEntry d) {
return std::visit(
[this](auto&& arg) {
using T = std::decay_t<decltype(arg)>;
// If we haven't initialised the vector yet, populate it.
auto pval_empty = std::get_if<std::monostate>(&bus_children_);
if (pval_empty) {
auto tmp = DeviceChildData(std::vector<T>());
bus_children_.swap(tmp);
}
auto pval = std::get_if<std::vector<T>>(&bus_children_);
ZX_ASSERT_MSG(pval, "Bus %s had unexpected child type vector", name());
pval->emplace_back(arg);
return pval->size() - 1;
},
d);
}
zx::result<BusMetadata> DeviceBuilder::GetMetadata() {
#ifdef __Fuchsia__
using SpiChannel = fuchsia_hardware_spi_businfo::SpiChannel;
using I2CChannel = fuchsia_hardware_i2c_businfo::I2CChannel;
return std::visit(
[this](DeviceChildData&& arg) -> zx::result<BusMetadata> {
if (std::holds_alternative<std::monostate>(arg)) {
return zx::ok(std::monostate{});
}
if (std::holds_alternative<std::vector<SpiChannel>>(arg)) {
ZX_ASSERT(HasBusId()); // Bus ID should get set when a child device is added.
return zx::ok(fuchsia_hardware_spi_businfo::SpiBusMetadata{{
.channels = std::get<std::vector<SpiChannel>>(arg),
.bus_id = GetBusId(),
}});
}
if (std::holds_alternative<std::vector<I2CChannel>>(arg)) {
ZX_ASSERT(HasBusId()); // Bus ID should get set when a child device is added.
return zx::ok(fuchsia_hardware_i2c_businfo::I2CBusMetadata{{
.channels = std::get<std::vector<I2CChannel>>(arg),
.bus_id = GetBusId(),
}});
}
return zx::error(ZX_ERR_NOT_SUPPORTED);
},
bus_children_);
#else // __Fuchsia__
return zx::error(ZX_ERR_NOT_SUPPORTED);
#endif
}
zx::result<> DeviceBuilder::BuildComposite(acpi::Manager* manager,
std::vector<zx_device_str_prop_t>& str_props,
async_dispatcher_t* device_dispatcher) {
if (parent_->GetBusType() == BusType::kPci) {
// If a device is on a PCI bus, the PCI bus driver will publish a composite device, so we
// don't try to publish a composite.
return zx::ok();
}
auto [acpi_bind_rules, acpi_properties] = GetFragmentBindRulesAndPropertiesForSelf();
for (const auto& str_prop : str_props) {
acpi_properties.emplace_back(MakeProperty(str_prop));
}
auto composite_node_spec = ddk::CompositeNodeSpec(acpi_bind_rules, acpi_properties);
// Generate composite node spec parent for every device we use.
for (auto& pair : buses_) {
DeviceBuilder* parent = pair.first;
size_t child_index = pair.second;
auto [bind_rules, properties] = parent->GetFragmentBindRulesAndPropertiesForChild(child_index);
composite_node_spec.AddParentSpec(bind_rules, properties);
}
for (uint32_t i = 0; i < irq_count_; i++) {
auto bind_platform_dev_interrupt_id = i + 1;
composite_node_spec.AddParentSpec(
std::vector<ddk::BindRule>{
ddk::MakeAcceptBindRule(bind_fuchsia::ACPI_ID, device_id_),
ddk::MakeAcceptBindRule(bind_fuchsia::PLATFORM_DEV_INTERRUPT_ID,
bind_platform_dev_interrupt_id),
ddk::MakeAcceptBindRule(bind_fuchsia::SERVICE, "fuchsia.hardware.interrupt.Service")},
std::vector<device_bind_prop_t>{
ddk::MakeProperty(bind_fuchsia::ACPI_ID, device_id_),
ddk::MakeProperty(bind_fuchsia::PLATFORM_DEV_INTERRUPT_ID,
bind_platform_dev_interrupt_id),
ddk::MakeProperty(bind_fuchsia::SERVICE, "fuchsia.hardware.interrupt.Service")});
}
#if !defined(IS_TEST)
// TODO(https://fxbug.dev/42160209): re-enable this in tests once mock_ddk supports composites.
fbl::String spec_name;
uint32_t id = manager->GetNextCompositeId(name());
if (id == 0) {
spec_name = fbl::StringPrintf("%s-composite-spec", name());
} else {
spec_name = fbl::StringPrintf("%s-%i-composite-spec", name(), id);
}
DeviceArgs composite_node_spec_args(parent_->zx_device_, manager, device_dispatcher, handle_);
auto composite_node_spec_device = std::make_unique<Device>(composite_node_spec_args);
zx_status_t status =
composite_node_spec_device->DdkAddCompositeNodeSpec(spec_name.data(), composite_node_spec);
if (status != ZX_OK) {
#ifdef __Fuchsia__
zxlogf(ERROR, "Failed to add composite node spec: %s", zx_status_get_string(status));
#else
zxlogf(ERROR, "Failed to add composite node spec");
#endif
return zx::error(status);
}
// The DDK takes ownership of the device.
[[maybe_unused]] auto* unused = composite_node_spec_device.release();
#endif
return zx::ok();
}
std::pair<std::vector<ddk::BindRule>, std::vector<device_bind_prop_t>>
DeviceBuilder::GetFragmentBindRulesAndPropertiesForChild(size_t child_index) {
std::vector<ddk::BindRule> bind_rules;
std::vector<device_bind_prop_t> properties;
switch (bus_type_) {
case BusType::kPci:
bind_rules.emplace_back(
ddk::MakeAcceptBindRule(bind_fuchsia::PROTOCOL, bind_fuchsia_pci::BIND_PROTOCOL_DEVICE));
properties.emplace_back(
ddk::MakeProperty(bind_fuchsia::PROTOCOL, bind_fuchsia_pci::BIND_PROTOCOL_DEVICE));
break;
case BusType::kI2c:
// No Banjo protocol needed for I2C.
break;
case BusType::kSpi:
bind_rules.emplace_back(
ddk::MakeAcceptBindRule(bind_fuchsia::SERVICE, "fuchsia.hardware.spi.Service"));
properties.emplace_back(
ddk::MakeProperty(bind_fuchsia::SERVICE, "fuchsia.hardware.spi.Service"));
break;
case BusType::kUnknown:
ZX_PANIC("Bus type is unknown");
}
std::visit(
[&bind_rules, &properties, child_index](auto&& arg) {
using T = std::decay_t<decltype(arg)>;
using SpiChannel = fuchsia_hardware_spi_businfo::SpiChannel;
using I2CChannel = fuchsia_hardware_i2c_businfo::I2CChannel;
if constexpr (std::is_same_v<T, std::monostate>) {
ZX_PANIC("Bus should have children");
} else if constexpr (std::is_same_v<T, std::vector<SpiChannel>>) {
SpiChannel& chan = arg[child_index];
if (chan.global_id().has_value()) {
bind_rules.emplace_back(ddk::MakeAcceptBindRule(bind_fuchsia::ID, *chan.global_id()));
properties.emplace_back(ddk::MakeProperty(bind_fuchsia::ID, *chan.global_id()));
}
properties.emplace_back(ddk::MakeProperty(bind_fuchsia::NAME, "spi"));
} else if constexpr (std::is_same_v<T, std::vector<I2CChannel>>) {
I2CChannel& chan = arg[child_index];
if (chan.global_id().has_value()) {
bind_rules.emplace_back(ddk::MakeAcceptBindRule(bind_fuchsia::ID, *chan.global_id()));
properties.emplace_back(ddk::MakeProperty(bind_fuchsia::ID, *chan.global_id()));
}
bind_rules.emplace_back(
ddk::MakeAcceptBindRule(bind_fuchsia::SERVICE, "fuchsia.hardware.i2c.Service"));
properties.emplace_back(
ddk::MakeProperty(bind_fuchsia::SERVICE, "fuchsia.hardware.i2c.Service"));
properties.emplace_back(ddk::MakeProperty(bind_fuchsia::NAME, "i2c"));
}
},
bus_children_);
return {bind_rules, properties};
}
std::pair<std::vector<ddk::BindRule>, std::vector<device_bind_prop_t>>
DeviceBuilder::GetFragmentBindRulesAndPropertiesForSelf() {
std::vector<ddk::BindRule> bind_rules = {
ddk::MakeAcceptBindRule(bind_fuchsia::PROTOCOL, bind_fuchsia_acpi::BIND_PROTOCOL_DEVICE),
};
std::vector<device_bind_prop_t> properties = {
ddk::MakeProperty(bind_fuchsia::PROTOCOL, bind_fuchsia_acpi::BIND_PROTOCOL_DEVICE),
};
for (auto& prop : str_props_) {
bind_rules.emplace_back(MakeAcceptBindRule(prop));
properties.emplace_back(MakeProperty(prop));
}
return {bind_rules, properties};
}
bool DeviceBuilder::CheckForDeviceTreeCompatible(acpi::Acpi* acpi) {
// UUID defined in "Device Properties UUID for _DSD", Revision 2.0, Section 2.1
// https://uefi.org/sites/default/files/resources/_DSD-device-properties-UUID.pdf
static constexpr Uuid kDevicePropertiesUuid =
Uuid::Create(0xdaffd814, 0x6eba, 0x4d8c, 0x8a91, 0xbc9bbf4aa301);
auto result = acpi->EvaluateObject(handle_, "_DSD", std::nullopt);
if (result.is_error()) {
if (result.zx_status_value() != ZX_ERR_NOT_FOUND) {
zxlogf(WARNING, "Get _DSD for '%s' failed: %d", name(), result.error_value());
}
return false;
}
auto value = std::move(result.value());
if (value->Type != ACPI_TYPE_PACKAGE) {
zxlogf(WARNING, "'%s': Badly formed _DSD return value - wrong data type", name());
return false;
}
// The package is an array of pairs. The first item in each pair is a UUID, and the second is the
// value of that UUID.
ACPI_OBJECT* properties = nullptr;
for (size_t i = 0; (i + 1) < value->Package.Count; i += 2) {
ACPI_OBJECT* uuid_buffer = &value->Package.Elements[i];
if (uuid_buffer->Type != ACPI_TYPE_BUFFER || uuid_buffer->Buffer.Length != acpi::kUuidBytes) {
zxlogf(WARNING, "'%s': _DSD entry %zu has invalid UUID.", name(), i);
continue;
}
if (!memcmp(uuid_buffer->Buffer.Pointer, kDevicePropertiesUuid.bytes, acpi::kUuidBytes)) {
properties = &value->Package.Elements[i + 1];
break;
}
}
if (!properties) {
return false;
}
if (properties->Type != ACPI_TYPE_PACKAGE) {
zxlogf(WARNING, "'%s': Device Properties _DSD value is not a package.", name());
return false;
}
// properties should be a list of packages, which are each a key/value pair.
for (size_t i = 0; i < properties->Package.Count; i++) {
ACPI_OBJECT* pair = &properties->Package.Elements[i];
if (pair->Type != ACPI_TYPE_PACKAGE || pair->Package.Count != 2) {
continue;
}
ACPI_OBJECT* key = &pair->Package.Elements[0];
ACPI_OBJECT* value = &pair->Package.Elements[1];
if (key->Type != ACPI_TYPE_STRING || key->String.Length < sizeof("compatible") - 1) {
continue;
}
if (!strcmp("compatible", key->String.Pointer) && value->Type == ACPI_TYPE_STRING) {
str_props_.emplace_back(OwnedStringProp{"fuchsia.acpi.FIRST_CID", value->String.Pointer});
return true;
}
}
return false;
}
} // namespace acpi