blob: 6e141a1c7f19328ae060ee3a7dc76d4fa50e64e2 [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 "src/devices/bus/drivers/platform/platform-bus.h"
#include <assert.h>
#include <lib/ddk/binding_driver.h>
#include <lib/ddk/debug.h>
#include <lib/ddk/driver.h>
#include <lib/ddk/metadata.h>
#include <lib/ddk/platform-defs.h>
#include <lib/driver/outgoing/cpp/handlers.h>
#include <lib/fdf/dispatcher.h>
#include <lib/zbi-format/board.h>
#include <lib/zbi-format/driver-config.h>
#include <lib/zbi-format/zbi.h>
#include <zircon/errors.h>
#include <zircon/process.h>
#include <zircon/status.h>
#include <zircon/syscalls/iommu.h>
#include <algorithm>
#include <bind/fuchsia/cpp/bind.h>
#include <bind/fuchsia/platform/cpp/bind.h>
#include <ddktl/fidl.h>
#include <fbl/algorithm.h>
#include <fbl/auto_lock.h>
#include "src/devices/bus/drivers/platform/node-util.h"
namespace {
// Adds a passthrough device which forwards all banjo connections to the parent device.
// The device will be added as a child of |parent| with the name |name|, and |props| will
// be applied to the new device's add_args.
// Returns ZX_OK if the device is successfully added.
zx_status_t AddProtocolPassthrough(const char* name, cpp20::span<const zx_device_prop_t> props,
platform_bus::PlatformBus* parent, zx_device_t** out_device) {
if (!parent || !name) {
return ZX_ERR_INVALID_ARGS;
}
static zx_protocol_device_t passthrough_proto = {
.version = DEVICE_OPS_VERSION,
.get_protocol =
[](void* ctx, uint32_t id, void* proto) {
return device_get_protocol(reinterpret_cast<platform_bus::PlatformBus*>(ctx)->zxdev(),
id, proto);
},
.release = [](void* ctx) {},
};
fuchsia_hardware_platform_bus::Service::InstanceHandler handler({
.platform_bus = parent->bindings().CreateHandler(parent, fdf::Dispatcher::GetCurrent()->get(),
fidl::kIgnoreBindingClosure),
});
auto status =
parent->outgoing().AddService<fuchsia_hardware_platform_bus::Service>(std::move(handler));
if (status.is_error()) {
return status.error_value();
}
auto endpoints = fidl::CreateEndpoints<fuchsia_io::Directory>();
if (endpoints.is_error()) {
return endpoints.status_value();
}
auto result = parent->outgoing().Serve(std::move(endpoints->server));
if (result.is_error()) {
return result.error_value();
}
std::array offers = {
fuchsia_hardware_platform_bus::Service::Name,
};
device_add_args_t args = {
.version = DEVICE_ADD_ARGS_VERSION,
.name = name,
.ctx = parent,
.ops = &passthrough_proto,
.props = props.data(),
.prop_count = static_cast<uint32_t>(props.size()),
.runtime_service_offers = offers.data(),
.runtime_service_offer_count = offers.size(),
.outgoing_dir_channel = endpoints->client.TakeChannel().release(),
};
return device_add(parent->zxdev(), &args, out_device);
}
device_bind_prop_key_t ConvertFidlPropertyKey(
const fuchsia_driver_framework::NodePropertyKey& key) {
switch (key.Which()) {
case fuchsia_driver_framework::NodePropertyKey::Tag::kIntValue:
return device_bind_prop_int_key(key.int_value().value());
case fuchsia_driver_framework::NodePropertyKey::Tag::kStringValue:
return device_bind_prop_str_key(key.string_value().value().c_str());
}
}
zx::result<device_bind_prop_value_t> ConvertFidlPropertyValue(
const fuchsia_driver_framework::NodePropertyValue& value) {
switch (value.Which()) {
case fuchsia_driver_framework::NodePropertyValue::Tag::kIntValue:
return zx::ok(device_bind_prop_int_val(value.int_value().value()));
case fuchsia_driver_framework::NodePropertyValue::Tag::kStringValue:
return zx::ok(device_bind_prop_str_val(value.string_value().value().c_str()));
case fuchsia_driver_framework::NodePropertyValue::Tag::kBoolValue:
return zx::ok(device_bind_prop_bool_val(value.bool_value().value()));
case fuchsia_driver_framework::NodePropertyValue::Tag::kEnumValue:
return zx::ok(device_bind_prop_enum_val(value.enum_value().value().c_str()));
default:
return zx::error(ZX_ERR_INVALID_ARGS);
}
}
zx::result<ddk::BindRule> ConvertFidlBindRule(const fuchsia_driver_framework::BindRule& fidl_rule) {
auto key = ConvertFidlPropertyKey(fidl_rule.key());
std::vector<device_bind_prop_value_t> values;
values.reserve(fidl_rule.values().size());
for (const auto& fidl_value : fidl_rule.values()) {
auto property_value = ConvertFidlPropertyValue(fidl_value);
if (property_value.is_error()) {
return property_value.take_error();
}
values.push_back(property_value.value());
}
device_bind_rule_condition condition;
switch (fidl_rule.condition()) {
case fuchsia_driver_framework::Condition::kAccept:
condition = DEVICE_BIND_RULE_CONDITION_ACCEPT;
break;
case fuchsia_driver_framework::Condition::kReject:
condition = DEVICE_BIND_RULE_CONDITION_REJECT;
break;
case fuchsia_driver_framework::Condition::kUnknown:
return zx::error(ZX_ERR_INVALID_ARGS);
}
return zx::ok(ddk::BindRule(key, condition, values));
}
zx::result<> AppendParentSpecs(ddk::CompositeNodeSpec& spec,
const std::vector<fuchsia_driver_framework::ParentSpec>& parents) {
for (const auto& parent : parents) {
if (parent.bind_rules().empty()) {
zxlogf(ERROR, "Parent spec bind rules cannot be empty");
return zx::error(ZX_ERR_INVALID_ARGS);
}
if (parent.properties().empty()) {
zxlogf(ERROR, "Parent spec properties cannot be empty");
return zx::error(ZX_ERR_INVALID_ARGS);
}
std::vector<ddk::BindRule> rules;
rules.reserve(parent.bind_rules().size());
for (const auto& bind_rule : parent.bind_rules()) {
auto result = ConvertFidlBindRule(bind_rule);
if (result.is_error()) {
return zx::error(result.status_value());
}
rules.push_back(result.value());
}
std::vector<device_bind_prop_t> properties;
properties.reserve(parent.properties().size());
for (const auto& property : parent.properties()) {
auto property_value = ConvertFidlPropertyValue(property.value());
if (property_value.is_error()) {
zxlogf(ERROR, "Invalid property value");
return zx::error(ZX_ERR_INVALID_ARGS);
}
properties.push_back(device_bind_prop_t{
.key = ConvertFidlPropertyKey(property.key()),
.value = property_value.value(),
});
}
spec.AddParentSpec(rules, properties);
}
return zx::ok();
}
} // anonymous namespace
namespace platform_bus {
zx_status_t PlatformBus::IommuGetBti(uint32_t iommu_index, uint32_t bti_id, zx::bti* out_bti) {
if (iommu_index != 0) {
return ZX_ERR_OUT_OF_RANGE;
}
std::pair key(iommu_index, bti_id);
auto bti = cached_btis_.find(key);
if (bti == cached_btis_.end()) {
zx::bti new_bti;
zx_status_t status = zx::bti::create(iommu_handle_, 0, bti_id, &new_bti);
if (status != ZX_OK) {
return status;
}
char name[ZX_MAX_NAME_LEN]{};
snprintf(name, std::size(name) - 1, "pbus bti %02x:%02x", iommu_index, bti_id);
status = new_bti.set_property(ZX_PROP_NAME, name, std::size(name));
if (status != ZX_OK) {
zxlogf(WARNING, "Couldn't set name for BTI '%s': %s", name, zx_status_get_string(status));
}
auto [iter, _] = cached_btis_.emplace(key, std::move(new_bti));
bti = iter;
}
return bti->second.duplicate(ZX_RIGHT_SAME_RIGHTS, out_bti);
}
void PlatformBus::NodeAdd(NodeAddRequestView request, fdf::Arena& arena,
NodeAddCompleter::Sync& completer) {
if (!request->node.has_name()) {
completer.buffer(arena).ReplyError(ZX_ERR_INVALID_ARGS);
return;
}
auto natural = fidl::ToNatural(request->node);
completer.buffer(arena).Reply(NodeAddInternal(natural));
}
zx::result<> PlatformBus::NodeAddInternal(fuchsia_hardware_platform_bus::Node& node) {
auto result = ValidateResources(node);
if (result.is_error()) {
return result.take_error();
}
std::unique_ptr<platform_bus::PlatformDevice> dev;
auto status =
PlatformDevice::Create(std::move(node), zxdev(), this, PlatformDevice::Isolated, &dev);
if (status != ZX_OK) {
return zx::error(status);
}
status = dev->Start();
if (status != ZX_OK) {
return zx::error(status);
}
// devmgr is now in charge of the device.
[[maybe_unused]] auto* dummy = dev.release();
return zx::ok();
}
void PlatformBus::GetBoardName(GetBoardNameCompleter::Sync& completer) {
fbl::AutoLock lock(&board_info_lock_);
// Reply immediately if board_name is valid.
if (!board_info_.board_name().empty()) {
completer.Reply(ZX_OK, fidl::StringView::FromExternal(board_info_.board_name()));
return;
}
// Cache the requests until board_name becomes valid.
board_name_completer_.push_back(completer.ToAsync());
}
void PlatformBus::GetBoardRevision(GetBoardRevisionCompleter::Sync& completer) {
fbl::AutoLock lock(&board_info_lock_);
completer.Reply(ZX_OK, board_info_.board_revision());
}
void PlatformBus::GetBootloaderVendor(GetBootloaderVendorCompleter::Sync& completer) {
fbl::AutoLock lock(&bootloader_info_lock_);
// Reply immediately if vendor is valid.
if (bootloader_info_.vendor() != std::nullopt) {
completer.Reply(ZX_OK, fidl::StringView::FromExternal(bootloader_info_.vendor().value()));
return;
}
// Cache the requests until vendor becomes valid.
bootloader_vendor_completer_.push_back(completer.ToAsync());
}
void PlatformBus::GetInterruptControllerInfo(GetInterruptControllerInfoCompleter::Sync& completer) {
fuchsia_sysinfo::wire::InterruptControllerInfo info = {
.type = interrupt_controller_type_,
};
completer.Reply(
ZX_OK, fidl::ObjectView<fuchsia_sysinfo::wire::InterruptControllerInfo>::FromExternal(&info));
}
void PlatformBus::GetSerialNumber(GetSerialNumberCompleter::Sync& completer) {
auto result = GetBootItem(ZBI_TYPE_SERIAL_NUMBER, 0);
if (result.is_error()) {
zxlogf(INFO, "Boot Item ZBI_TYPE_SERIAL_NUMBER not found");
completer.ReplyError(result.error_value());
return;
}
auto& [vmo, length] = *result;
if (length > fuchsia_sysinfo::wire::kSerialNumberLen) {
completer.ReplyError(ZX_ERR_BUFFER_TOO_SMALL);
return;
}
char serial[fuchsia_sysinfo::wire::kSerialNumberLen];
zx_status_t status = vmo.read(serial, 0, length);
if (status != ZX_OK) {
zxlogf(ERROR, "Failed to read serial number VMO %d", status);
completer.ReplyError(status);
return;
}
completer.ReplySuccess(fidl::StringView::FromExternal(serial, length));
}
void PlatformBus::GetBoardInfo(fdf::Arena& arena, GetBoardInfoCompleter::Sync& completer) {
fbl::AutoLock lock(&board_info_lock_);
fidl::Arena<> fidl_arena;
completer.buffer(arena).ReplySuccess(fidl::ToWire(fidl_arena, board_info_));
}
void PlatformBus::SetBoardInfo(SetBoardInfoRequestView request, fdf::Arena& arena,
SetBoardInfoCompleter::Sync& completer) {
fbl::AutoLock lock(&board_info_lock_);
auto& info = request->info;
if (info.has_board_name()) {
board_info_.board_name() = info.board_name().get();
zxlogf(INFO, "PlatformBus: set board name to \"%s\"", board_info_.board_name().data());
std::vector<GetBoardNameCompleter::Async> completer_tmp_;
// Respond to pending boardname requests, if any.
board_name_completer_.swap(completer_tmp_);
while (!completer_tmp_.empty()) {
completer_tmp_.back().Reply(ZX_OK, fidl::StringView::FromExternal(board_info_.board_name()));
completer_tmp_.pop_back();
}
}
if (info.has_board_revision()) {
board_info_.board_revision() = info.board_revision();
}
completer.buffer(arena).ReplySuccess();
}
void PlatformBus::SetBootloaderInfo(SetBootloaderInfoRequestView request, fdf::Arena& arena,
SetBootloaderInfoCompleter::Sync& completer) {
fbl::AutoLock lock(&bootloader_info_lock_);
auto& info = request->info;
if (info.has_vendor()) {
bootloader_info_.vendor() = info.vendor().get();
zxlogf(INFO, "PlatformBus: set bootloader vendor to \"%s\"", bootloader_info_.vendor()->data());
std::vector<GetBootloaderVendorCompleter::Async> completer_tmp_;
// Respond to pending boardname requests, if any.
bootloader_vendor_completer_.swap(completer_tmp_);
while (!completer_tmp_.empty()) {
completer_tmp_.back().Reply(
ZX_OK, fidl::StringView::FromExternal(bootloader_info_.vendor().value()));
completer_tmp_.pop_back();
}
}
completer.buffer(arena).ReplySuccess();
}
void PlatformBus::RegisterSysSuspendCallback(RegisterSysSuspendCallbackRequestView request,
fdf::Arena& arena,
RegisterSysSuspendCallbackCompleter::Sync& completer) {
suspend_cb_.Bind(std::move(request->suspend_cb),
fdf::Dispatcher::GetCurrent()->async_dispatcher());
completer.buffer(arena).ReplySuccess();
}
void PlatformBus::AddCompositeNodeSpec(AddCompositeNodeSpecRequestView request, fdf::Arena& arena,
AddCompositeNodeSpecCompleter::Sync& completer) {
// Create the pdev fragments
auto vid = request->node.has_vid() ? request->node.vid() : 0;
auto pid = request->node.has_pid() ? request->node.pid() : 0;
auto did = request->node.has_did() ? request->node.did() : 0;
auto instance_id = request->node.has_instance_id() ? request->node.instance_id() : 0;
const ddk::BindRule kPDevBindRules[] = {
ddk::MakeAcceptBindRule(bind_fuchsia::PROTOCOL, bind_fuchsia_platform::BIND_PROTOCOL_DEVICE),
ddk::MakeAcceptBindRule(bind_fuchsia::PLATFORM_DEV_VID, vid),
ddk::MakeAcceptBindRule(bind_fuchsia::PLATFORM_DEV_PID, pid),
ddk::MakeAcceptBindRule(bind_fuchsia::PLATFORM_DEV_DID, did),
ddk::MakeAcceptBindRule(bind_fuchsia::PLATFORM_DEV_INSTANCE_ID, instance_id),
};
const device_bind_prop_t kPDevProperties[] = {
ddk::MakeProperty(bind_fuchsia::PROTOCOL, bind_fuchsia_platform::BIND_PROTOCOL_DEVICE),
ddk::MakeProperty(bind_fuchsia::PLATFORM_DEV_VID, vid),
ddk::MakeProperty(bind_fuchsia::PLATFORM_DEV_PID, pid),
ddk::MakeProperty(bind_fuchsia::PLATFORM_DEV_DID, did),
ddk::MakeProperty(bind_fuchsia::PLATFORM_DEV_INSTANCE_ID, instance_id),
};
auto composite_node_spec = ddk::CompositeNodeSpec(kPDevBindRules, kPDevProperties);
auto fidl_spec = fidl::ToNatural(request->spec);
if (fidl_spec.parents().has_value()) {
auto result = AppendParentSpecs(composite_node_spec, fidl_spec.parents().value());
if (result.is_error()) {
completer.buffer(arena).ReplyError(result.status_value());
return;
}
}
auto status = DdkAddCompositeNodeSpec(fidl_spec.name()->c_str(), composite_node_spec);
if (status != ZX_OK) {
zxlogf(ERROR, "DdkAddCompositeNodeSpec failed %s", zx_status_get_string(status));
completer.buffer(arena).ReplyError(status);
return;
}
// Create a platform device for the node.
std::unique_ptr<platform_bus::PlatformDevice> dev;
auto natural = fidl::ToNatural(request->node);
auto valid = ValidateResources(natural);
if (valid.is_error()) {
completer.buffer(arena).ReplyError(valid.error_value());
return;
}
status =
PlatformDevice::Create(std::move(natural), zxdev(), this, PlatformDevice::Fragment, &dev);
if (status != ZX_OK) {
completer.buffer(arena).ReplyError(status);
return;
}
status = dev->Start();
if (status != ZX_OK) {
completer.buffer(arena).ReplyError(status);
return;
}
// devmgr is now in charge of the device.
[[maybe_unused]] auto* dev_ptr = dev.release();
completer.buffer(arena).ReplySuccess();
}
zx::result<PlatformBus::BootItemResult> PlatformBus::GetBootItem(uint32_t type, uint32_t extra) {
auto result = fidl::WireCall(items_svc_)->Get(type, extra);
if (!result.ok()) {
return zx::error(result.status());
}
if (!result->payload.is_valid()) {
return zx::error(ZX_ERR_NOT_FOUND);
}
return zx::ok(PlatformBus::BootItemResult{
.vmo = std::move(result->payload),
.length = result->length,
});
}
zx::result<fbl::Array<uint8_t>> PlatformBus::GetBootItemArray(uint32_t type, uint32_t extra) {
zx::result result = GetBootItem(type, extra);
if (result.is_error()) {
return result.take_error();
}
auto& [vmo, length] = *result;
fbl::Array<uint8_t> data(new uint8_t[length], length);
zx_status_t status = vmo.read(data.data(), 0, data.size());
if (status != ZX_OK) {
return zx::error(status);
}
return zx::ok(std::move(data));
}
void PlatformBus::DdkRelease() { delete this; }
typedef struct {
void* pbus_instance;
zx_device_t* sys_root;
} sysdev_suspend_t;
static void sys_device_suspend(void* ctx, uint8_t requested_state, bool enable_wake,
uint8_t suspend_reason) {
auto* p = reinterpret_cast<sysdev_suspend_t*>(ctx);
auto* pbus = reinterpret_cast<class PlatformBus*>(p->pbus_instance);
if (pbus != nullptr) {
auto& suspend_cb = pbus->suspend_cb();
if (suspend_cb.is_valid()) {
suspend_cb->Callback(requested_state, enable_wake, suspend_reason)
.ThenExactlyOnce(
[sys_root = p->sys_root](
fidl::WireUnownedResult<fuchsia_hardware_platform_bus::SysSuspend::Callback>&
status) {
if (!status.ok()) {
device_suspend_reply(sys_root, status.status(), DEV_POWER_STATE_D0);
return;
}
device_suspend_reply(sys_root, status->out_status, status->out_state);
});
return;
}
}
device_suspend_reply(p->sys_root, ZX_OK, 0);
}
static void sys_device_child_pre_release(void* ctx, void* child_ctx) {
auto* p = reinterpret_cast<sysdev_suspend_t*>(ctx);
if (child_ctx == p->pbus_instance) {
p->pbus_instance = nullptr;
}
}
static void sys_device_release(void* ctx) {
auto* p = reinterpret_cast<sysdev_suspend_t*>(ctx);
delete p;
}
static zx_protocol_device_t sys_device_proto = []() {
zx_protocol_device_t result = {};
result.version = DEVICE_OPS_VERSION;
result.suspend = sys_device_suspend;
result.child_pre_release = sys_device_child_pre_release;
result.release = sys_device_release;
return result;
}();
zx_status_t PlatformBus::Create(zx_device_t* parent, const char* name, zx::channel items_svc) {
// This creates the "sys" device.
sys_device_proto.version = DEVICE_OPS_VERSION;
// The suspend op needs to get access to the PBus instance, to be able to
// callback the ACPI suspend hook. Introducing a level of indirection here
// to allow us to update the PBus instance in the device context after creating
// the device.
fbl::AllocChecker ac;
std::unique_ptr<sysdev_suspend_t> suspend(new (&ac) sysdev_suspend_t);
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
suspend->pbus_instance = nullptr;
device_add_args_t args = {
.version = DEVICE_ADD_ARGS_VERSION,
.name = "sys",
.ctx = suspend.get(),
.ops = &sys_device_proto,
.flags = DEVICE_ADD_NON_BINDABLE,
};
// Create /dev/sys.
if (zx_status_t status = device_add(parent, &args, &suspend->sys_root); status != ZX_OK) {
return status;
}
sysdev_suspend_t* suspend_ptr = suspend.release();
// Add child of sys for the board driver to bind to.
std::unique_ptr<platform_bus::PlatformBus> bus(
new (&ac) platform_bus::PlatformBus(suspend_ptr->sys_root, std::move(items_svc)));
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
if (zx_status_t status = bus->Init(); status != ZX_OK) {
zxlogf(ERROR, "failed to init: %s", zx_status_get_string(status));
return status;
}
// devmgr is now in charge of the device.
platform_bus::PlatformBus* bus_ptr = bus.release();
suspend_ptr->pbus_instance = bus_ptr;
return ZX_OK;
}
PlatformBus::PlatformBus(zx_device_t* parent, zx::channel items_svc)
: PlatformBusType(parent),
items_svc_(fidl::ClientEnd<fuchsia_boot::Items>(std::move(items_svc))),
outgoing_(fdf::OutgoingDirectory::Create(fdf::Dispatcher::GetCurrent()->get())) {}
zx::result<zbi_board_info_t> PlatformBus::GetBoardInfo() {
zx::result result = GetBootItem(ZBI_TYPE_DRV_BOARD_INFO, 0);
if (result.is_error()) {
// This is expected on some boards.
zxlogf(INFO, "Boot Item ZBI_TYPE_DRV_BOARD_INFO not found");
return result.take_error();
}
auto& [vmo, length] = *result;
if (length != sizeof(zbi_board_info_t)) {
return zx::error(ZX_ERR_INTERNAL);
}
zbi_board_info_t board_info;
zx_status_t status = vmo.read(&board_info, 0, length);
if (status != ZX_OK) {
zxlogf(ERROR, "Failed to read zbi_board_info_t VMO");
return zx::error(status);
}
return zx::ok(board_info);
}
zx_status_t PlatformBus::Init() {
zx_status_t status;
// Set up a dummy IOMMU protocol to use in the case where our board driver
// does not set a real one.
zx_iommu_desc_dummy_t desc;
zx::unowned_resource iommu_resource(get_iommu_resource(parent()));
if (iommu_resource->is_valid()) {
status = zx::iommu::create(*iommu_resource, ZX_IOMMU_TYPE_DUMMY, &desc, sizeof(desc),
&iommu_handle_);
if (status != ZX_OK) {
return status;
}
}
// Read kernel driver.
#if __x86_64__
interrupt_controller_type_ = fuchsia_sysinfo::wire::InterruptControllerType::kApic;
#else
std::array<std::pair<zbi_kernel_driver_t, fuchsia_sysinfo::wire::InterruptControllerType>, 3>
interrupt_driver_type_mapping = {
{{ZBI_KERNEL_DRIVER_ARM_GIC_V2, fuchsia_sysinfo::wire::InterruptControllerType::kGicV2},
{ZBI_KERNEL_DRIVER_ARM_GIC_V3, fuchsia_sysinfo::wire::InterruptControllerType::kGicV3},
{ZBI_KERNEL_DRIVER_RISCV_PLIC, fuchsia_sysinfo::wire::InterruptControllerType::kPlic}},
};
for (const auto& [driver, controller] : interrupt_driver_type_mapping) {
auto boot_item = GetBootItem(ZBI_TYPE_KERNEL_DRIVER, driver);
if (boot_item.is_error() && boot_item.status_value() != ZX_ERR_NOT_FOUND) {
return boot_item.status_value();
}
if (boot_item.is_ok()) {
interrupt_controller_type_ = controller;
}
}
#endif
// Read platform ID.
zx::result platform_id_result = GetBootItem(ZBI_TYPE_PLATFORM_ID, 0);
if (platform_id_result.is_error() && platform_id_result.status_value() != ZX_ERR_NOT_FOUND) {
return platform_id_result.status_value();
}
#if __aarch64__
{
// For arm64, we do not expect a board to set the bootloader info.
fbl::AutoLock lock(&bootloader_info_lock_);
bootloader_info_.vendor() = "<unknown>";
}
#endif
fbl::AutoLock lock(&board_info_lock_);
if (platform_id_result.is_ok()) {
if (platform_id_result->length != sizeof(zbi_platform_id_t)) {
return ZX_ERR_INTERNAL;
}
zbi_platform_id_t platform_id;
status = platform_id_result->vmo.read(&platform_id, 0, sizeof(platform_id));
if (status != ZX_OK) {
return status;
}
zxlogf(INFO, "platform bus: VID: %u PID: %u board: \"%s\"", platform_id.vid, platform_id.pid,
platform_id.board_name);
board_info_.vid() = platform_id.vid;
board_info_.pid() = platform_id.pid;
board_info_.board_name() = platform_id.board_name;
} else {
#if __x86_64__
// For x64, we might not find the ZBI_TYPE_PLATFORM_ID, old bootloaders
// won't support this, for example. If this is the case, cons up the VID/PID
// here to allow the acpi board driver to load and bind.
board_info_.vid() = PDEV_VID_INTEL;
board_info_.pid() = PDEV_PID_X86;
#else
zxlogf(ERROR, "platform_bus: ZBI_TYPE_PLATFORM_ID not found");
return ZX_ERR_INTERNAL;
#endif
}
// Set default board_revision.
zx::result zbi_board_info = GetBoardInfo();
if (zbi_board_info.is_ok()) {
board_info_.board_revision() = zbi_board_info->revision;
}
// Then we attach the platform-bus device below it.
status = DdkAdd(ddk::DeviceAddArgs("platform").set_flags(DEVICE_ADD_NON_BINDABLE));
if (status != ZX_OK) {
return status;
}
zx_device_prop_t passthrough_props[] = {
{BIND_PLATFORM_DEV_VID, 0, board_info_.vid()},
{BIND_PLATFORM_DEV_PID, 0, board_info_.pid()},
};
status = AddProtocolPassthrough("pt", passthrough_props, this, &protocol_passthrough_);
if (status != ZX_OK) {
// We log the error but we do nothing as we've already added the device successfully.
zxlogf(ERROR, "Error while adding pt: %s", zx_status_get_string(status));
}
return ZX_OK;
}
zx::result<> PlatformBus::ValidateResources(fuchsia_hardware_platform_bus::Node& node) {
if (node.name() == std::nullopt) {
zxlogf(ERROR, "Node has no name?");
return zx::error(ZX_ERR_INVALID_ARGS);
}
if (node.mmio() != std::nullopt) {
for (size_t i = 0; i < node.mmio()->size(); i++) {
if (!IsValid(node.mmio().value()[i])) {
zxlogf(ERROR, "node '%s' has invalid mmio %zu", node.name()->data(), i);
return zx::error(ZX_ERR_INVALID_ARGS);
}
}
}
if (node.irq() != std::nullopt) {
for (size_t i = 0; i < node.irq()->size(); i++) {
if (!IsValid(node.irq().value()[i])) {
zxlogf(ERROR, "node '%s' has invalid irq %zu", node.name()->data(), i);
return zx::error(ZX_ERR_INVALID_ARGS);
}
}
}
if (node.bti() != std::nullopt) {
for (size_t i = 0; i < node.bti()->size(); i++) {
if (!IsValid(node.bti().value()[i])) {
zxlogf(ERROR, "node '%s' has invalid bti %zu", node.name()->data(), i);
return zx::error(ZX_ERR_INVALID_ARGS);
}
}
}
if (node.smc() != std::nullopt) {
for (size_t i = 0; i < node.smc()->size(); i++) {
if (!IsValid(node.smc().value()[i])) {
zxlogf(ERROR, "node '%s' has invalid smc %zu", node.name()->data(), i);
return zx::error(ZX_ERR_INVALID_ARGS);
}
}
}
if (node.metadata() != std::nullopt) {
for (size_t i = 0; i < node.metadata()->size(); i++) {
if (!IsValid(node.metadata().value()[i])) {
zxlogf(ERROR, "node '%s' has invalid metadata %zu", node.name()->data(), i);
return zx::error(ZX_ERR_INVALID_ARGS);
}
}
}
if (node.boot_metadata() != std::nullopt) {
for (size_t i = 0; i < node.boot_metadata()->size(); i++) {
if (!IsValid(node.boot_metadata().value()[i])) {
zxlogf(ERROR, "node '%s' has invalid boot metadata %zu", node.name()->data(), i);
return zx::error(ZX_ERR_INVALID_ARGS);
}
}
}
return zx::ok();
}
void PlatformBus::DdkInit(ddk::InitTxn txn) {
zx::result board_data = GetBootItemArray(ZBI_TYPE_DRV_BOARD_PRIVATE, 0);
if (board_data.is_error() && board_data.status_value() != ZX_ERR_NOT_FOUND) {
return txn.Reply(board_data.status_value());
}
if (board_data.is_ok()) {
zx_status_t status = device_add_metadata(protocol_passthrough_, DEVICE_METADATA_BOARD_PRIVATE,
board_data->data(), board_data->size());
if (status != ZX_OK) {
return txn.Reply(status);
}
}
fuchsia_hardware_platform_bus::Node device = {};
device.name() = "ram-disk";
device.vid() = PDEV_VID_GENERIC;
device.pid() = PDEV_PID_GENERIC;
device.did() = PDEV_DID_RAM_DISK;
auto status = NodeAddInternal(device);
if (status.is_error()) {
return txn.Reply(status.error_value());
}
device.name() = "ram-nand";
device.vid() = PDEV_VID_GENERIC;
device.pid() = PDEV_PID_GENERIC;
device.did() = PDEV_DID_RAM_NAND;
status = NodeAddInternal(device);
if (status.is_error()) {
return txn.Reply(status.error_value());
}
device.name() = "virtual-audio";
device.vid() = PDEV_VID_GENERIC;
device.pid() = PDEV_PID_GENERIC;
device.did() = PDEV_DID_VIRTUAL_AUDIO;
status = NodeAddInternal(device);
if (status.is_error()) {
return txn.Reply(status.error_value());
}
device.name() = "bt-hci-emulator";
device.vid() = PDEV_VID_GENERIC;
device.pid() = PDEV_PID_GENERIC;
device.did() = PDEV_DID_BT_HCI_EMULATOR;
status = NodeAddInternal(device);
if (status.is_error()) {
return txn.Reply(status.error_value());
}
device.name() = "fake-battery";
device.vid() = PDEV_VID_GENERIC;
device.pid() = PDEV_PID_GENERIC;
device.did() = PDEV_DID_FAKE_BATTERY;
status = NodeAddInternal(device);
if (status.is_error()) {
return txn.Reply(status.error_value());
}
device.name() = "fake-ac";
device.vid() = PDEV_VID_GENERIC;
device.pid() = PDEV_PID_GENERIC;
device.did() = PDEV_DID_FAKE_AC;
status = NodeAddInternal(device);
if (status.is_error()) {
return txn.Reply(status.error_value());
}
return txn.Reply(ZX_OK); // This will make the device visible and able to be unbound.
}
zx_status_t platform_bus_create(void* ctx, zx_device_t* parent, const char* name,
zx_handle_t handle) {
return platform_bus::PlatformBus::Create(parent, name, zx::channel(handle));
}
static constexpr zx_driver_ops_t driver_ops = []() {
zx_driver_ops_t ops = {};
ops.version = DRIVER_OPS_VERSION;
ops.create = platform_bus_create;
return ops;
}();
} // namespace platform_bus
ZIRCON_DRIVER(platform_bus, platform_bus::driver_ops, "zircon", "0.1");