blob: ed1334aa2d167ef75f6f212eeccc3ed252ec8e05 [file] [log] [blame]
// Copyright 2023 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 <fidl/fuchsia.boot/cpp/wire.h>
#include <fidl/fuchsia.component.decl/cpp/fidl.h>
#include <fidl/fuchsia.component.resolution/cpp/wire.h>
#include <fidl/fuchsia.device.manager/cpp/wire.h>
#include <fidl/fuchsia.diagnostics/cpp/fidl.h>
#include <fidl/fuchsia.driver.framework/cpp/wire.h>
#include <fidl/fuchsia.driver.test/cpp/fidl.h>
#include <fidl/fuchsia.io/cpp/wire.h>
#include <fidl/fuchsia.kernel/cpp/wire.h>
#include <fidl/fuchsia.pkg/cpp/wire.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/async/dispatcher.h>
#include <lib/component/incoming/cpp/clone.h>
#include <lib/component/incoming/cpp/protocol.h>
#include <lib/component/outgoing/cpp/outgoing_directory.h>
#include <lib/ddk/platform-defs.h>
#include <lib/fdio/directory.h>
#include <lib/stdcompat/string_view.h>
#include <lib/sys/component/cpp/testing/realm_builder.h>
#include <lib/syslog/cpp/macros.h>
#include <lib/syslog/global.h>
#include <lib/zbi-format/board.h>
#include <lib/zbi-format/zbi.h>
#include <lib/zx/job.h>
#include <lib/zx/time.h>
#include <lib/zx/vmo.h>
#include <zircon/status.h>
#include <memory>
#include <unordered_map>
#include <vector>
#include <ddk/metadata/test.h>
#include <fbl/string_printf.h>
#include <fbl/unique_fd.h>
#include "sdk/lib/driver_test_realm/driver_test_realm_config.h"
#include "src/lib/files/directory.h"
#include "src/lib/files/file.h"
#include "src/lib/fxl/strings/concatenate.h"
#include "src/lib/fxl/strings/join_strings.h"
#include "src/lib/fxl/strings/substitute.h"
#include "src/storage/lib/vfs/cpp/pseudo_dir.h"
#include "src/storage/lib/vfs/cpp/pseudo_file.h"
#include "src/storage/lib/vfs/cpp/synchronous_vfs.h"
namespace {
namespace fio = fuchsia_io;
namespace fdt = fuchsia_driver_test;
using namespace component_testing;
// This board driver knows how to interpret the metadata for which devices to
// spawn.
const zbi_platform_id_t kPlatformId = []() {
zbi_platform_id_t plat_id = {};
plat_id.vid = PDEV_VID_TEST;
plat_id.pid = PDEV_PID_PBUS_TEST;
strcpy(plat_id.board_name, "driver-integration-test");
return plat_id;
}();
#define BOARD_REVISION_TEST 42
const zbi_board_info_t kBoardInfo = []() {
zbi_board_info_t board_info = {};
board_info.revision = BOARD_REVISION_TEST;
return board_info;
}();
// This function is responsible for serializing driver data. It must be kept
// updated with the function that deserialized the data. This function
// is TestBoard::FetchAndDeserialize.
zx_status_t GetBootItem(const std::vector<board_test::DeviceEntry>& entries, uint32_t type,
std::string_view board_name, uint32_t extra, zx::vmo* out,
uint32_t* length) {
zx::vmo vmo;
switch (type) {
case ZBI_TYPE_PLATFORM_ID: {
zbi_platform_id_t platform_id = kPlatformId;
if (!board_name.empty()) {
strncpy(platform_id.board_name, board_name.data(), ZBI_BOARD_NAME_LEN - 1);
}
zx_status_t status = zx::vmo::create(sizeof(kPlatformId), 0, &vmo);
if (status != ZX_OK) {
return status;
}
status = vmo.write(&platform_id, 0, sizeof(kPlatformId));
if (status != ZX_OK) {
return status;
}
*length = sizeof(kPlatformId);
break;
}
case ZBI_TYPE_DRV_BOARD_INFO: {
zx_status_t status = zx::vmo::create(sizeof(kBoardInfo), 0, &vmo);
if (status != ZX_OK) {
return status;
}
status = vmo.write(&kBoardInfo, 0, sizeof(kBoardInfo));
if (status != ZX_OK) {
return status;
}
*length = sizeof(kBoardInfo);
break;
}
case ZBI_TYPE_DRV_BOARD_PRIVATE: {
size_t list_size = sizeof(board_test::DeviceList);
size_t entry_size = entries.size() * sizeof(board_test::DeviceEntry);
size_t metadata_size = 0;
for (const board_test::DeviceEntry& entry : entries) {
metadata_size += entry.metadata_size;
}
zx_status_t status = zx::vmo::create(list_size + entry_size + metadata_size, 0, &vmo);
if (status != ZX_OK) {
return status;
}
// Write DeviceList to vmo.
board_test::DeviceList list{.count = entries.size()};
status = vmo.write(&list, 0, sizeof(list));
if (status != ZX_OK) {
return status;
}
// Write DeviceEntries to vmo.
status = vmo.write(entries.data(), list_size, entry_size);
if (status != ZX_OK) {
return status;
}
// Write Metadata to vmo.
size_t write_offset = list_size + entry_size;
for (const board_test::DeviceEntry& entry : entries) {
status = vmo.write(entry.metadata, write_offset, entry.metadata_size);
if (status != ZX_OK) {
return status;
}
write_offset += entry.metadata_size;
}
*length = static_cast<uint32_t>(list_size + entry_size + metadata_size);
break;
}
default:
break;
}
*out = std::move(vmo);
return ZX_OK;
}
class FakeBootItems final : public fidl::WireServer<fuchsia_boot::Items> {
public:
void Get(GetRequestView request, GetCompleter::Sync& completer) override {
zx::vmo vmo;
uint32_t length = 0;
std::vector<board_test::DeviceEntry> entries = {};
zx_status_t status =
GetBootItem(entries, request->type, board_name_, request->extra, &vmo, &length);
if (status != ZX_OK) {
FX_SLOG(ERROR, "Failed to get boot items", FX_KV("status", status));
}
completer.Reply(std::move(vmo), length);
}
void Get2(Get2RequestView request, Get2Completer::Sync& completer) override {
FX_SLOG(ERROR, "Unsupported Get2 called.");
completer.Close(ZX_OK);
}
void GetBootloaderFile(GetBootloaderFileRequestView request,
GetBootloaderFileCompleter::Sync& completer) override {
completer.Reply(zx::vmo());
}
std::string board_name_;
};
class FakeSystemStateTransition final
: public fidl::WireServer<fuchsia_device_manager::SystemStateTransition> {
void GetTerminationSystemState(GetTerminationSystemStateCompleter::Sync& completer) override {
completer.Reply(fuchsia_device_manager::SystemPowerState::kFullyOn);
}
void GetMexecZbis(GetMexecZbisCompleter::Sync& completer) override {
completer.ReplyError(ZX_ERR_NOT_SUPPORTED);
}
};
class FakeRootJob final : public fidl::WireServer<fuchsia_kernel::RootJob> {
void Get(GetCompleter::Sync& completer) override {
zx::job job;
zx_status_t status = zx::job::default_job()->duplicate(ZX_RIGHT_SAME_RIGHTS, &job);
if (status != ZX_OK) {
FX_SLOG(ERROR, "Failed to duplicate job", FX_KV("status", status));
}
completer.Reply(std::move(job));
}
};
zx::result<fidl::ClientEnd<fio::Directory>> OpenPkgDir() {
auto endpoints = fidl::CreateEndpoints<fuchsia_io::Directory>();
if (endpoints.is_error()) {
return zx::error(ZX_ERR_INTERNAL);
}
zx_status_t status =
fdio_open("/pkg",
static_cast<uint32_t>(fuchsia_io::wire::OpenFlags::kDirectory |
fuchsia_io::wire::OpenFlags::kRightReadable |
fuchsia_io::wire::OpenFlags::kRightExecutable),
endpoints->server.TakeChannel().release());
if (status != ZX_OK) {
return zx::error(ZX_ERR_INTERNAL);
}
return zx::ok(std::move(endpoints->client));
}
class DriverTestRealm final : public fidl::Server<fuchsia_driver_test::Realm> {
public:
DriverTestRealm(component::OutgoingDirectory* outgoing, async_dispatcher_t* dispatcher,
driver_test_realm_config::Config config)
: outgoing_(outgoing), dispatcher_(dispatcher), config_(config) {}
zx::result<> Init() {
// We must connect capabilities up early as not all users wait for Start to complete before
// trying to access the capabilities. The lack of synchronization with simple variants of DTR
// in particular causes issues.
for (auto& [dir, _, server_end] : directories_) {
zx::result client_end = fidl::CreateEndpoints(&server_end);
if (client_end.is_error()) {
return client_end.take_error();
}
zx::result result = outgoing_->AddDirectory(std::move(client_end.value()), dir);
if (result.is_error()) {
FX_SLOG(ERROR, "Failed to add directory to outgoing directory", FX_KV("directory", dir));
return result.take_error();
}
}
const std::array<std::string, 3> kProtocols = {
"fuchsia.device.manager.Administrator",
"fuchsia.driver.development.Manager",
"fuchsia.driver.registrar.DriverRegistrar",
};
for (const auto& protocol : kProtocols) {
auto result = outgoing_->AddUnmanagedProtocol(
[this, protocol](zx::channel request) {
if (exposed_dir_.channel().is_valid()) {
fdio_service_connect_at(exposed_dir_.channel().get(), protocol.c_str(),
request.release());
} else {
// Queue these up to run later.
cb_queue_.push_back([this, protocol, request = std::move(request)]() mutable {
fdio_service_connect_at(exposed_dir_.channel().get(), protocol.c_str(),
request.release());
});
}
},
protocol);
if (result.is_error()) {
FX_SLOG(ERROR, "Failed to add protocol to outgoing directory",
FX_KV("protocol", protocol.c_str()));
return result.take_error();
}
}
// Hook up fuchsia.driver.test/Realm so we can proceed with the rest of initialization once
// |Start| is invoked
zx::result result = outgoing_->AddUnmanagedProtocol<fuchsia_driver_test::Realm>(
bindings_.CreateHandler(this, dispatcher_, fidl::kIgnoreBindingClosure));
if (result.is_error()) {
FX_SLOG(ERROR, "Failed to add protocol to outgoing directory",
FX_KV("protocol", "fuchsia.driver.test/Realm"));
return result.take_error();
}
return zx::ok();
}
void Start(StartRequest& request, StartCompleter::Sync& completer) override {
// Non-hermetic users will end up calling start several times as the component test framework
// invokes the binary multiple times, resulting in main running several times. We may be
// ignoring real issues by ignoreing the subsequent calls in the case that multiple parties
// are invoking start unknowingly. Comparing the args may be a way to avoid that issue.
// TODO(https://fxbug.dev/42073125): Remedy this situation
if (is_started_) {
completer.Reply(zx::ok());
return;
}
is_started_ = true;
// Tunnel fuchsia_boot::Items from parent to realm builder if |tunnel_boot_items| configuration
// is set. If not, provide fuchsia_boot::Items from local.
if (config_.tunnel_boot_items()) {
zx::result result = outgoing_->AddUnmanagedProtocol<fuchsia_boot::Items>(
[](fidl::ServerEnd<fuchsia_boot::Items> server_end) {
if (const zx::result status = component::Connect<fuchsia_boot::Items>(
std::move(server_end),
fidl::DiscoverableProtocolDefaultPath<fuchsia_boot::Items>);
status.is_error()) {
FX_LOGS(ERROR) << "Failed to connect to fuchsia_boot::Items"
<< status.status_string();
}
});
if (result.is_error()) {
completer.Reply(result.take_error());
return;
}
} else {
auto boot_items = std::make_unique<FakeBootItems>();
if (request.args().board_name().has_value()) {
boot_items->board_name_ = *request.args().board_name();
}
zx::result result = outgoing_->AddProtocol<fuchsia_boot::Items>(std::move(boot_items));
if (result.is_error()) {
completer.Reply(result.take_error());
return;
}
}
zx::result result = outgoing_->AddProtocol<fuchsia_device_manager::SystemStateTransition>(
std::make_unique<FakeSystemStateTransition>());
if (result.is_error()) {
completer.Reply(result.take_error());
return;
}
result = outgoing_->AddProtocol<fuchsia_kernel::RootJob>(std::make_unique<FakeRootJob>());
if (result.is_error()) {
completer.Reply(result.take_error());
return;
}
// Setup /boot
fidl::ClientEnd<fuchsia_io::Directory> boot_dir;
if (request.args().boot().has_value()) {
boot_dir = fidl::ClientEnd<fuchsia_io::Directory>(std::move(*request.args().boot()));
} else {
auto res = OpenPkgDir();
if (res.is_error()) {
completer.Reply(res.take_error());
return;
}
boot_dir = std::move(res.value());
}
// Setup /pkg_drivers
fidl::ClientEnd<fuchsia_io::Directory> pkg_drivers_dir;
if (request.args().pkg().has_value()) {
pkg_drivers_dir = fidl::ClientEnd<fuchsia_io::Directory>(std::move(*request.args().pkg()));
} else {
auto res = OpenPkgDir();
if (res.is_error()) {
completer.Reply(res.take_error());
return;
}
pkg_drivers_dir = std::move(res.value());
}
// We only index /pkg if it's not identical to /boot.
const bool create_pkg_config = request.args().pkg() || request.args().boot();
zx::result base_and_boot_configs = ConstructBootAndBaseConfig(
boot_dir,
create_pkg_config ? pkg_drivers_dir : fidl::UnownedClientEnd<fuchsia_io::Directory>({}));
if (base_and_boot_configs.is_error()) {
completer.Reply(base_and_boot_configs.take_error());
return;
}
result = outgoing_->AddDirectory(std::move(boot_dir), "boot");
if (result.is_error()) {
completer.Reply(result.take_error());
return;
}
result = outgoing_->AddDirectory(std::move(pkg_drivers_dir), "pkg_drivers");
if (result.is_error()) {
completer.Reply(result.take_error());
return;
}
// Add additional routes if specified.
std::unordered_map<fdt::Collection, std::vector<Ref>> kMap = {
{
fdt::Collection::kBootDrivers,
{
CollectionRef{"boot-drivers"},
},
},
{
fdt::Collection::kPackageDrivers,
{
CollectionRef{"pkg-drivers"},
CollectionRef{"full-pkg-drivers"},
},
},
};
if (request.args().offers().has_value()) {
for (const auto& offer : *request.args().offers()) {
realm_builder_.AddRoute(Route{.capabilities = {Protocol{offer.protocol_name()}},
.source = {ParentRef()},
.targets = kMap[offer.collection()]});
}
}
if (request.args().exposes().has_value()) {
for (const auto& expose : *request.args().exposes()) {
for (const auto& ref : kMap[expose.collection()]) {
realm_builder_.AddRoute(Route{.capabilities = {Service{expose.service_name()}},
.source = ref,
.targets = {ParentRef()}});
}
}
}
// Set driver-index config based on request.
const std::vector<std::string> kEmptyVec;
std::vector<component_testing::ConfigCapability> configurations;
configurations.push_back({
.name = "fuchsia.driver.BootDrivers",
.value = std::move(base_and_boot_configs->boot_drivers),
});
configurations.push_back({
.name = "fuchsia.driver.BaseDrivers",
.value = std::move(base_and_boot_configs->base_drivers),
});
configurations.push_back({
.name = "fuchsia.driver.BindEager",
.value = request.args().driver_bind_eager().value_or(kEmptyVec),
});
configurations.push_back({
.name = "fuchsia.driver.DisabledDrivers",
.value = request.args().driver_disable().value_or(kEmptyVec),
});
realm_builder_.AddConfiguration(std::move(configurations));
realm_builder_.AddRoute({
.capabilities =
{
component_testing::Config{.name = "fuchsia.driver.BootDrivers"},
component_testing::Config{.name = "fuchsia.driver.BaseDrivers"},
component_testing::Config{.name = "fuchsia.driver.BindEager"},
component_testing::Config{.name = "fuchsia.driver.DisabledDrivers"},
},
.source = component_testing::SelfRef{},
.targets = {component_testing::ChildRef{"driver-index"}},
});
// Set driver_manager config based on request.
configurations = std::vector<component_testing::ConfigCapability>();
const std::string default_root = "fuchsia-boot:///dtr#meta/test-parent-sys.cm";
configurations.push_back({
.name = "fuchsia.driver.manager.RootDriver",
.value = request.args().root_driver().value_or(default_root),
});
realm_builder_.AddConfiguration(std::move(configurations));
realm_builder_.AddRoute({
.capabilities =
{
component_testing::Config{.name = "fuchsia.driver.manager.RootDriver"},
},
.source = component_testing::SelfRef{},
.targets = {component_testing::ChildRef{"driver_manager"}},
});
realm_ = realm_builder_.SetRealmName("0").Build(dispatcher_);
// Forward all other protocols.
exposed_dir_ =
fidl::ClientEnd<fuchsia_io::Directory>(realm_->component().CloneExposedDir().TakeChannel());
for (auto& [dir, flags, server_end] : directories_) {
zx_status_t status = fdio_open_at(exposed_dir_.channel().get(), dir, flags,
server_end.TakeChannel().release());
if (status != ZX_OK) {
completer.Reply(zx::error(status));
return;
}
}
if (request.args().exposes().has_value()) {
for (const auto& expose : *request.args().exposes()) {
auto endpoints = fidl::CreateEndpoints<fuchsia_io::Directory>();
if (endpoints.is_error()) {
completer.Reply(endpoints.take_error());
return;
}
auto flags = static_cast<uint32_t>(fio::OpenFlags::kRightReadable |
fio::wire::OpenFlags::kDirectory);
zx_status_t status =
fdio_open_at(exposed_dir_.channel().get(), expose.service_name().c_str(), flags,
endpoints->server.TakeChannel().release());
if (status != ZX_OK) {
completer.Reply(zx::error(status));
return;
}
auto result =
outgoing_->AddDirectoryAt(std::move(endpoints->client), "svc", expose.service_name());
if (result.is_error()) {
completer.Reply(result.take_error());
return;
}
}
}
// Connect all requests that came in before Start was triggered.
while (cb_queue_.empty() == false) {
cb_queue_.back()();
cb_queue_.pop_back();
};
completer.Reply(zx::ok());
}
void handle_unknown_method(fidl::UnknownMethodMetadata<fuchsia_driver_test::Realm> metadata,
fidl::UnknownMethodCompleter::Sync& completer) override {
std::string method_type;
switch (metadata.unknown_method_type) {
case fidl::UnknownMethodType::kOneWay:
method_type = "one-way";
break;
case fidl::UnknownMethodType::kTwoWay:
method_type = "two-way";
break;
};
FX_SLOG(WARNING, "DriverDevelopmentService received unknown method.",
FX_KV("Direction", method_type.c_str()), FX_KV("Ordinal", metadata.method_ordinal));
}
private:
struct BootAndBaseConfigResult {
std::vector<std::string> boot_drivers;
std::vector<std::string> base_drivers;
};
static zx::result<BootAndBaseConfigResult> ConstructBootAndBaseConfig(
fidl::UnownedClientEnd<fuchsia_io::Directory> boot_dir,
fidl::UnownedClientEnd<fuchsia_io::Directory> pkg_drivers_dir) {
auto list = std::vector<
std::tuple<fidl::UnownedClientEnd<fuchsia_io::Directory>, std::string, std::string>>{
std::make_tuple(boot_dir, "fuchsia-boot:///", "boot"),
};
std::unordered_map<std::string, std::vector<std::string>> results;
if (pkg_drivers_dir.is_valid()) {
list.emplace_back(pkg_drivers_dir, "fuchsia-pkg://fuchsia.com/", "pkg");
} else {
results["pkg"] = {};
}
for (const auto& [dir, url_prefix, type] : list) {
// Check each manifest to see if it uses the driver runner.
zx::result cloned_dir = component::Clone(dir);
if (cloned_dir.is_error()) {
FX_SLOG(ERROR, "Unable to clone dir");
return zx::error(ZX_ERR_IO);
}
fbl::unique_fd dir_fd;
zx_status_t status =
fdio_fd_create(cloned_dir->TakeHandle().release(), dir_fd.reset_and_get_address());
if (status != ZX_OK) {
FX_SLOG(ERROR, "Failed to turn dir into fd");
return zx::error(ZX_ERR_IO);
}
std::vector<std::string> manifests;
if (!files::ReadDirContentsAt(dir_fd.get(), "meta", &manifests)) {
FX_SLOG(WARNING, "Unable to dir contents for ",
FX_KV("dir", fxl::Concatenate({"/", type, "/meta"})));
}
std::vector<std::string> driver_components;
for (const auto& manifest : manifests) {
std::string manifest_path = "meta/" + manifest;
if (!files::IsFileAt(dir_fd.get(), manifest_path) ||
!cpp20::ends_with(std::string_view(manifest), ".cm")) {
continue;
}
std::vector<uint8_t> manifest_bytes;
if (!files::ReadFileToVectorAt(dir_fd.get(), manifest_path, &manifest_bytes)) {
FX_SLOG(ERROR, "Unable to read file contents for", FX_KV("manifest", manifest_path));
return zx::error(ZX_ERR_IO);
}
fit::result component = fidl::Unpersist<fuchsia_component_decl::Component>(manifest_bytes);
if (component.is_error()) {
FX_SLOG(ERROR, "Unable to unpersist component manifest",
FX_KV("manifest", manifest_path));
return zx::error(ZX_ERR_IO);
}
if (!component->program() || !component->program()->runner() ||
*component->program()->runner() != "driver") {
continue;
}
// We add a fake package name of dtr to make it identifiable.
std::string entry = fxl::Substitute("$0dtr#meta/$1", url_prefix, manifest);
driver_components.push_back(entry);
}
results[type] = std::move(driver_components);
}
return zx::ok(BootAndBaseConfigResult{
.boot_drivers = std::move(results["boot"]),
.base_drivers = std::move(results["pkg"]),
});
}
bool is_started_ = false;
component::OutgoingDirectory* outgoing_;
async_dispatcher_t* dispatcher_;
fidl::ServerBindingGroup<fuchsia_driver_test::Realm> bindings_;
struct Directory {
const char* name;
uint32_t flags;
fidl::ServerEnd<fuchsia_io::Directory> server_end;
};
std::array<Directory, 2> directories_ = {
Directory{
.name = "dev-class",
.flags =
static_cast<uint32_t>(fio::OpenFlags::kRightReadable | fio::OpenFlags::kDirectory),
.server_end = {},
},
Directory{
.name = "dev-topological",
.flags =
static_cast<uint32_t>(fio::OpenFlags::kRightReadable | fio::OpenFlags::kDirectory),
.server_end = {},
},
};
component_testing::RealmBuilder realm_builder_ =
component_testing::RealmBuilder::CreateFromRelativeUrl("#meta/test_realm.cm");
std::optional<component_testing::RealmRoot> realm_;
fidl::ClientEnd<fuchsia_io::Directory> exposed_dir_;
// Queue of connection requests that need to be ran once exposed_dir_ is valid.
std::vector<fit::closure> cb_queue_;
driver_test_realm_config::Config config_;
};
} // namespace
int main(int argc, const char** argv) {
async::Loop loop(&kAsyncLoopConfigNeverAttachToThread);
component::OutgoingDirectory outgoing(loop.dispatcher());
auto config = driver_test_realm_config::Config::TakeFromStartupHandle();
DriverTestRealm dtr(&outgoing, loop.dispatcher(), config);
{
zx::result result = dtr.Init();
ZX_ASSERT(result.is_ok());
}
{
zx::result result = outgoing.ServeFromStartupInfo();
ZX_ASSERT(result.is_ok());
}
loop.Run();
return 0;
}