blob: e1f3d23ae71cafec83aa90cb7fbc4e6fde011bce [file] [log] [blame]
// 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 <fidl/fuchsia.hardware.spi.businfo/cpp/wire.h>
#include <lib/ddk/debug.h>
#include <lib/ddk/driver.h>
#include <zircon/compiler.h>
#include <fbl/string_printf.h>
#include "src/devices/board/lib/acpi/acpi.h"
#ifdef __Fuchsia__
#include "src/devices/board/lib/acpi/device.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 {
static const zx_bind_inst_t kSysmemFragment[] = {
BI_MATCH_IF(EQ, BIND_PROTOCOL, ZX_PROTOCOL_SYSMEM),
};
template <typename T>
zx::status<std::vector<uint8_t>> DoFidlEncode(T data) {
fidl::unstable::OwnedEncodedMessage<T> encoded(fidl::internal::WireFormatVersion::kV2, &data);
if (!encoded.ok()) {
return zx::error(encoded.status());
}
auto message = encoded.GetOutgoingMessage().CopyBytes();
std::vector<uint8_t> result(message.size());
memcpy(result.data(), message.data(), message.size());
return zx::ok(std::move(result));
}
} // namespace
acpi::status<> DeviceBuilder::InferBusTypes(acpi::Acpi* acpi, fidl::AnyArena& allocator,
acpi::Manager* manager, InferBusTypeCallback 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(fxbug.dev/78565): Handle other resources like serial buses.
auto result = acpi->WalkResources(
handle_, "_CRS",
[this, acpi, manager, &allocator, &callback](ACPI_RESOURCE* res) -> acpi::status<> {
ACPI_HANDLE bus_parent = nullptr;
BusType type = BusType::kUnknown;
DeviceChildEntry entry;
uint16_t bus_id_prop;
if (resource_is_spi(res)) {
type = BusType::kSpi;
auto result = resource_parse_spi(acpi, handle_, res, allocator, &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_SPI_BUS_ID;
dev_props_.emplace_back(
zx_device_prop_t{.id = BIND_SPI_CHIP_SELECT, .value = result.value().cs()});
} else if (resource_is_i2c(res)) {
type = BusType::kI2c;
auto result = resource_parse_i2c(acpi, handle_, res, allocator, &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_I2C_BUS_ID;
dev_props_.emplace_back(
zx_device_prop_t{.id = BIND_I2C_ADDRESS, .value = result.value().address()});
}
if (bus_parent) {
size_t bus_index = callback(bus_parent, type, entry);
DeviceBuilder* b = manager->LookupDevice(bus_parent);
buses_.emplace_back(b, bus_index);
dev_props_.emplace_back(zx_device_prop_t{.id = bus_id_prop, .value = 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;
dev_props_.emplace_back(zx_device_prop_t{
.id = BIND_PCI_TOPO,
.value = 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("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("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_) {
dev_props_.emplace_back(zx_device_prop_t{
.id = BIND_ACPI_BUS_TYPE,
.value = parent_->GetBusType(),
});
}
if (result.status_value() == AE_NOT_FOUND) {
return acpi::ok();
}
return result;
}
zx::status<zx_device_t*> DeviceBuilder::Build(acpi::Manager* manager) {
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(manager->acpi_root(), manager, handle_);
if (HasBusId() && bus_type_ != BusType::kPci) {
zx::status<std::vector<uint8_t>> metadata = FidlEncodeMetadata();
if (metadata.is_error()) {
zxlogf(ERROR, "Error while encoding 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(dev_props_),
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();
}
zx_device_ = device.release()->zxdev();
auto status = BuildComposite(manager, str_props_for_ddkadd);
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::status<std::vector<uint8_t>> DeviceBuilder::FidlEncodeMetadata() {
#ifdef __Fuchsia__
using SpiChannel = fuchsia_hardware_spi_businfo::wire::SpiChannel;
using I2CChannel = fuchsia_hardware_i2c_businfo::wire::I2CChannel;
fidl::Arena<> allocator;
return std::visit(
[this, &allocator](auto&& arg) -> zx::status<std::vector<uint8_t>> {
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, std::monostate>) {
return zx::ok(std::vector<uint8_t>());
} else if constexpr (std::is_same_v<T, std::vector<SpiChannel>>) {
ZX_ASSERT(HasBusId()); // Bus ID should get set when a child device is added.
fuchsia_hardware_spi_businfo::wire::SpiBusMetadata metadata(allocator);
for (auto& chan : arg) {
chan.set_bus_id(GetBusId());
}
auto channels = fidl::VectorView<SpiChannel>::FromExternal(arg);
metadata.set_channels(allocator, channels);
return DoFidlEncode(metadata);
} else if constexpr (std::is_same_v<T, std::vector<I2CChannel>>) {
ZX_ASSERT(HasBusId()); // Bus ID should get set when a child device is added.
fuchsia_hardware_i2c_businfo::wire::I2CBusMetadata metadata(allocator);
for (auto& chan : arg) {
chan.set_bus_id(GetBusId());
}
auto channels = fidl::VectorView<I2CChannel>::FromExternal(arg);
metadata.set_channels(allocator, channels);
return DoFidlEncode(metadata);
} else {
return zx::error(ZX_ERR_NOT_SUPPORTED);
}
},
bus_children_);
#else // __Fuchsia__
return zx::error(ZX_ERR_NOT_SUPPORTED);
#endif
}
zx::status<> DeviceBuilder::BuildComposite(acpi::Manager* manager,
std::vector<zx_device_str_prop_t>& str_props) {
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();
}
size_t fragment_count = buses_.size() + 2;
// Bookkeeping.
// We use fixed-size arrays here rather than std::vector because we don't want
// pointers to array members to become invalidated when the vector resizes.
// While we could use vector.reserve(), there's no way to guarantee that future bugs won't be
// caused by someone adding an extra fragment without first updating the reserve() call.
auto bind_insns = std::make_unique<std::vector<zx_bind_inst_t>[]>(fragment_count);
auto fragment_names = std::make_unique<fbl::String[]>(fragment_count);
auto fragment_parts = std::make_unique<device_fragment_part_t[]>(fragment_count);
auto fragments = std::make_unique<device_fragment_t[]>(fragment_count);
std::unordered_map<BusType, uint32_t> parent_types;
size_t bus_index = 0;
// Generate fragments for every device we use.
for (auto& pair : buses_) {
DeviceBuilder* parent = pair.first;
size_t child_index = pair.second;
BusType type = parent->GetBusType();
// Fragments are named <protocol>NNN, e.g. "i2c000", "i2c001".
fragment_names[bus_index] = fbl::StringPrintf("%s%03u", BusTypeToString(type),
parent_types.emplace(type, 0).first->second++);
std::vector<zx_bind_inst_t> insns = parent->GetFragmentBindInsnsForChild(child_index);
bind_insns[bus_index] = std::move(insns);
fragment_parts[bus_index] = device_fragment_part_t{
.instruction_count = static_cast<uint32_t>(bind_insns[bus_index].size()),
.match_program = bind_insns[bus_index].data(),
};
fragments[bus_index] = device_fragment_t{
.name = fragment_names[bus_index].data(),
.parts_count = 1,
.parts = &fragment_parts[bus_index],
};
bus_index++;
}
// Generate the ACPI fragment.
std::vector<zx_bind_inst_t> insns = GetFragmentBindInsnsForSelf();
bind_insns[bus_index] = std::move(insns);
fragment_parts[bus_index] = device_fragment_part_t{
.instruction_count = static_cast<uint32_t>(bind_insns[bus_index].size()),
.match_program = bind_insns[bus_index].data(),
};
fragments[bus_index] = device_fragment_t{
.name = "acpi",
.parts_count = 1,
.parts = &fragment_parts[bus_index],
};
bus_index++;
// Generate the sysmem fragment.
fragment_parts[bus_index] = device_fragment_part_t{
.instruction_count = sizeof(kSysmemFragment) / sizeof(kSysmemFragment[0]),
.match_program = kSysmemFragment,
};
fragments[bus_index] = device_fragment_t{
.name = "sysmem",
.parts_count = 1,
.parts = &fragment_parts[bus_index],
};
__UNUSED composite_device_desc_t composite_desc = {
.props = dev_props_.data(),
.props_count = dev_props_.size(),
.str_props = str_props.data(),
.str_props_count = str_props.size(),
.fragments = fragments.get(),
.fragments_count = fragment_count,
.primary_fragment = "acpi",
.spawn_colocated = true,
};
zx_status_t status = ZX_OK;
#if !defined(IS_TEST)
bool is_dfv2 = device_is_dfv2(parent_->zx_device_);
// TODO(fxbug.dev/93333): For DFv2, we don't add composite device fragments yet.
if (!is_dfv2) {
// TODO(fxbug.dev/79923): re-enable this in tests once mock_ddk supports composites.
auto composite_name = fbl::StringPrintf("%s-composite", name());
// Don't worry about any metadata, since it's present in the "acpi" parent.
DeviceArgs args(parent_->zx_device_, manager, handle_);
auto composite_device = std::make_unique<Device>(args);
status = composite_device->DdkAddComposite(composite_name.data(), &composite_desc);
if (status == ZX_OK) {
// The DDK takes ownership of the device, but only if DdkAddComposite succeeded.
__UNUSED auto unused = composite_device.release();
}
}
#endif
return zx::make_status(status);
}
std::vector<zx_bind_inst_t> DeviceBuilder::GetFragmentBindInsnsForChild(size_t child_index) {
std::vector<zx_bind_inst_t> ret;
uint32_t protocol = UINT32_MAX;
switch (bus_type_) {
case BusType::kPci:
protocol = ZX_PROTOCOL_PCI;
break;
case BusType::kI2c:
// TODO(fxbug.dev/96293): Update this once the I2C core driver no longer supports Banjo.
protocol = ZX_PROTOCOL_MISC;
break;
case BusType::kSpi:
protocol = ZX_PROTOCOL_SPI;
break;
case BusType::kUnknown:
ZX_ASSERT(bus_type_ != BusType::kUnknown);
}
ret.push_back(BI_ABORT_IF(NE, BIND_PROTOCOL, protocol));
std::visit(
[&ret, child_index](auto&& arg) {
using T = std::decay_t<decltype(arg)>;
using SpiChannel = fuchsia_hardware_spi_businfo::wire::SpiChannel;
using I2CChannel = fuchsia_hardware_i2c_businfo::wire::I2CChannel;
if constexpr (std::is_same_v<T, std::monostate>) {
ZX_ASSERT_MSG(false, "bus should have children");
} else if constexpr (std::is_same_v<T, std::vector<SpiChannel>>) {
SpiChannel& chan = arg[child_index];
ret.push_back(BI_ABORT_IF(NE, BIND_SPI_BUS_ID, chan.bus_id()));
ret.push_back(BI_ABORT_IF(NE, BIND_SPI_CHIP_SELECT, chan.cs()));
} else if constexpr (std::is_same_v<T, std::vector<I2CChannel>>) {
I2CChannel& chan = arg[child_index];
ret.push_back(BI_ABORT_IF(NE, BIND_I2C_BUS_ID, chan.bus_id()));
ret.push_back(BI_ABORT_IF(NE, BIND_I2C_ADDRESS, chan.address()));
ret.push_back(BI_ABORT_IF(NE, BIND_FIDL_PROTOCOL, ZX_FIDL_PROTOCOL_I2C));
}
},
bus_children_);
// Only bind to the non-composite device.
ret.push_back(BI_ABORT_IF(NE, BIND_COMPOSITE, 0));
ret.push_back(BI_MATCH());
return ret;
}
std::vector<zx_bind_inst_t> DeviceBuilder::GetFragmentBindInsnsForSelf() {
std::vector<zx_bind_inst_t> ret;
ret.push_back(BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_ACPI));
for (auto& prop : dev_props_) {
ret.push_back(BI_ABORT_IF(NE, static_cast<uint32_t>(prop.id), prop.value));
}
// Only bind to the non-composite device.
ret.push_back(BI_ABORT_IF(NE, BIND_COMPOSITE, 0));
ret.push_back(BI_MATCH());
return ret;
}
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