| // 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.driver.development/cpp/fidl.h> |
| #include <fidl/fuchsia.driver.test/cpp/fidl.h> |
| #include <lib/async-loop/cpp/loop.h> |
| #include <lib/component/incoming/cpp/protocol.h> |
| #include <lib/component/outgoing/cpp/outgoing_directory.h> |
| #include <lib/driver_test_realm/src/boot_items.h> |
| #include <lib/driver_test_realm/src/internal_server.h> |
| #include <lib/driver_test_realm/src/root_job.h> |
| #include <lib/driver_test_realm/src/system_state.h> |
| #include <lib/fdio/directory.h> |
| #include <lib/sys/component/cpp/testing/realm_builder.h> |
| #include <lib/syslog/cpp/log_settings.h> |
| #include <lib/syslog/cpp/macros.h> |
| |
| #include <sdk/lib/driver_test_realm/driver_test_realm_config.h> |
| |
| namespace { |
| |
| namespace fio = fuchsia_io; |
| namespace fdt = fuchsia_driver_test; |
| namespace fres = fuchsia_component_resolution; |
| |
| using namespace component_testing; |
| |
| 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() { |
| // Set up realm_builder_exposed_dir now so that we can queue up requests before `Start` has been |
| // called. When `Start` has been called, we'll connect the directory to the exposed directory |
| // of the started realm. |
| { |
| constexpr std::string_view kRealmBuilderExposedDir = "realm_builder_exposed_dir"; |
| |
| zx::result client_end = fidl::CreateEndpoints(&realm_builder_exposed_dir_); |
| if (client_end.is_error()) { |
| return client_end.take_error(); |
| } |
| |
| zx::result result = |
| outgoing_->AddDirectory(std::move(client_end.value()), kRealmBuilderExposedDir); |
| if (result.is_error()) { |
| FX_LOG_KV(ERROR, "Failed to add directory to outgoing directory", |
| FX_KV("directory", kRealmBuilderExposedDir)); |
| 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_LOG_KV(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 ignoring 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; |
| |
| if (request.args().board_name().has_value()) { |
| boot_items_.SetBoardName(*request.args().board_name()); |
| } |
| |
| if (request.args().devicetree().has_value()) { |
| boot_items_.SetDeviceTree(std::move(*request.args().devicetree())); |
| } |
| |
| if (request.args().platform_vid().has_value()) { |
| boot_items_.SetVid(std::move(*request.args().platform_vid())); |
| } |
| |
| if (request.args().platform_pid().has_value()) { |
| boot_items_.SetPid(std::move(*request.args().platform_pid())); |
| } |
| |
| zx::result result = outgoing_->AddUnmanagedProtocol<fuchsia_boot::Items>( |
| [this, tunnel_boot_items = |
| config_.tunnel_boot_items()](fidl::ServerEnd<fuchsia_boot::Items> server_end) { |
| auto result = boot_items_.Serve(dispatcher_, std::move(server_end), tunnel_boot_items); |
| if (result.is_error()) { |
| FX_LOGS(ERROR) << "Failed to tunnel fuchsia_boot::Items" << result.status_string(); |
| } |
| }); |
| |
| result = outgoing_->AddUnmanagedProtocol<fuchsia_system_state::SystemStateTransition>( |
| [this](fidl::ServerEnd<fuchsia_system_state::SystemStateTransition> server_end) { |
| system_state_.Serve(dispatcher_, std::move(server_end)); |
| }); |
| |
| if (result.is_error()) { |
| completer.Reply(result.take_error()); |
| return; |
| } |
| |
| result = outgoing_->AddUnmanagedProtocol<fuchsia_kernel::RootJob>( |
| [this](fidl::ServerEnd<fuchsia_kernel::RootJob> server_end) { |
| root_job_.Serve(dispatcher_, std::move(server_end)); |
| }); |
| if (result.is_error()) { |
| completer.Reply(result.take_error()); |
| return; |
| } |
| |
| 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())); |
| } |
| |
| fidl::ClientEnd<fuchsia_io::Directory> pkg_dir; |
| if (request.args().pkg().has_value()) { |
| pkg_dir = fidl::ClientEnd<fuchsia_io::Directory>(std::move(*request.args().pkg())); |
| } |
| |
| // Look at the test's component package and subpackages. |
| fidl::ClientEnd<fuchsia_io::Directory> test_pkg_dir; |
| std::optional<fres::Context> test_resolution_context; |
| if (request.args().test_component().has_value()) { |
| test_resolution_context = request.args().test_component()->resolution_context(); |
| test_pkg_dir = std::move(*request.args().test_component()->package()->directory()); |
| } |
| |
| internal_server_.emplace(std::move(boot_dir), std::move(pkg_dir), std::move(test_pkg_dir), |
| test_resolution_context, request.args().boot_driver_components()); |
| |
| result = outgoing_->AddUnmanagedProtocol<fuchsia_driver_test::Internal>( |
| [this](fidl::ServerEnd<fuchsia_driver_test::Internal> server_end) { |
| internal_server_->Serve(dispatcher_, std::move(server_end)); |
| }); |
| 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{"base-drivers"}, |
| CollectionRef{"full-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().dtr_offers()) { |
| for (const auto& offer_cap : *request.args().dtr_offers()) { |
| std::optional<Capability> converted; |
| switch (offer_cap.Which()) { |
| case fuchsia_component_test::Capability::Tag::kProtocol: { |
| const auto& offer_cap_proto = offer_cap.protocol().value(); |
| converted.emplace(Protocol{ |
| .name = offer_cap_proto.name().value(), |
| .as = offer_cap_proto.as(), |
| .type = offer_cap_proto.type().has_value() |
| ? std::make_optional( |
| static_cast<DependencyType>(offer_cap_proto.type().value())) |
| : std::nullopt, |
| .path = offer_cap_proto.path(), |
| }); |
| break; |
| } |
| case fuchsia_component_test::Capability::Tag::kConfig: { |
| const auto& offer_cap_config = offer_cap.config().value(); |
| converted.emplace(Config{ |
| .name = offer_cap_config.name().value(), |
| .as = offer_cap_config.as(), |
| }); |
| break; |
| } |
| case fuchsia_component_test::Capability::Tag::kDirectory: |
| case fuchsia_component_test::Capability::Tag::kStorage: |
| case fuchsia_component_test::Capability::Tag::kService: |
| case fuchsia_component_test::Capability::Tag::kEventStream: |
| case fuchsia_component_test::Capability::Tag::kDictionary: |
| default: |
| FX_LOG_KV(WARNING, "Skipping unsupported offer capability.", |
| FX_KV("type", static_cast<uint64_t>(offer_cap.Which()))); |
| break; |
| } |
| |
| if (converted.has_value()) { |
| realm_builder_.AddRoute(Route{ |
| .capabilities = {converted.value()}, |
| .source = {ParentRef()}, |
| .targets = |
| { |
| CollectionRef{"boot-drivers"}, |
| CollectionRef{"base-drivers"}, |
| CollectionRef{"full-drivers"}, |
| }, |
| }); |
| } |
| } |
| } |
| |
| 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()}}); |
| } |
| } |
| } |
| |
| if (request.args().dtr_exposes()) { |
| for (const auto& expose_cap : *request.args().dtr_exposes()) { |
| std::optional<Capability> converted; |
| switch (expose_cap.Which()) { |
| case fuchsia_component_test::Capability::Tag::kService: { |
| const auto& expose_cap_service = expose_cap.service().value(); |
| converted.emplace(Service{ |
| .name = expose_cap_service.name().value(), |
| .as = expose_cap_service.as(), |
| .path = expose_cap_service.path(), |
| }); |
| break; |
| } |
| case fuchsia_component_test::Capability::Tag::kProtocol: |
| case fuchsia_component_test::Capability::Tag::kDirectory: |
| case fuchsia_component_test::Capability::Tag::kStorage: |
| case fuchsia_component_test::Capability::Tag::kEventStream: |
| case fuchsia_component_test::Capability::Tag::kConfig: |
| case fuchsia_component_test::Capability::Tag::kDictionary: |
| default: |
| FX_LOG_KV(WARNING, "Skipping unsupported expose capability.", |
| FX_KV("type", static_cast<uint64_t>(expose_cap.Which()))); |
| break; |
| } |
| |
| if (converted.has_value()) { |
| realm_builder_.AddRoute(Route{ |
| .capabilities = {converted.value()}, |
| .source = |
| { |
| CollectionRef{"boot-drivers"}, |
| }, |
| .targets = {ParentRef()}, |
| }); |
| realm_builder_.AddRoute(Route{ |
| .capabilities = {converted.value()}, |
| .source = |
| { |
| CollectionRef{"base-drivers"}, |
| }, |
| .targets = {ParentRef()}, |
| }); |
| realm_builder_.AddRoute(Route{ |
| .capabilities = {converted.value()}, |
| .source = |
| { |
| CollectionRef{"full-drivers"}, |
| }, |
| .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 = kEmptyVec, |
| }); |
| configurations.push_back({ |
| .name = "fuchsia.driver.BaseDrivers", |
| .value = kEmptyVec, |
| }); |
| 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), |
| }); |
| configurations.push_back({ |
| .name = "fuchsia.driver.index.StopOnIdleTimeoutMillis", |
| .value = ConfigValue::Int64(request.args().driver_index_stop_timeout_millis().value_or(-1)), |
| }); |
| 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"}, |
| component_testing::Config{.name = "fuchsia.driver.index.StopOnIdleTimeoutMillis"}, |
| }, |
| .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"}}, |
| }); |
| |
| // Set platform bus config based on request. |
| configurations = std::vector<component_testing::ConfigCapability>(); |
| component_testing::Ref source; |
| if (request.args().software_devices()) { |
| source = component_testing::SelfRef(); |
| std::vector<std::string> device_names; |
| std::vector<uint32_t> device_ids; |
| for (const auto& device : *request.args().software_devices()) { |
| device_names.push_back(device.device_name()); |
| device_ids.push_back(device.device_id()); |
| } |
| configurations.push_back({ |
| .name = "fuchsia.platform.bus.SoftwareDeviceNames", |
| .value = ConfigValue(device_names), |
| }); |
| configurations.push_back({ |
| .name = "fuchsia.platform.bus.SoftwareDeviceIds", |
| .value = ConfigValue(device_ids), |
| }); |
| } else { |
| source = component_testing::VoidRef(); |
| } |
| realm_builder_.AddConfiguration(std::move(configurations)); |
| realm_builder_.AddRoute({ |
| .capabilities = |
| { |
| component_testing::Config{.name = "fuchsia.platform.bus.SoftwareDeviceNames"}, |
| component_testing::Config{.name = "fuchsia.platform.bus.SoftwareDeviceIds"}, |
| }, |
| .source = source, |
| .targets = {component_testing::CollectionRef{"boot-drivers"}}, |
| }); |
| |
| realm_ = realm_builder_.SetRealmName("0").Build(dispatcher_); |
| |
| // Forward exposes. |
| auto exposed_dir = realm_->component().exposed().unowned_channel()->get(); |
| 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<uint64_t>(fio::kPermReadable | fio::wire::Flags::kProtocolDirectory); |
| zx_status_t status = fdio_open3_at(exposed_dir, 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 realm_builder_exposed_dir. |
| if (zx_status_t status = |
| fdio_open3_at(exposed_dir, ".", |
| static_cast<uint64_t>(fio::kPermReadable | fio::Flags::kPermInheritWrite | |
| fio::Flags::kPermInheritExecute), |
| realm_builder_exposed_dir_.TakeChannel().release()); |
| status != ZX_OK) { |
| completer.Reply(zx::error(status)); |
| return; |
| } |
| |
| // Connect to the driver manager and wait for bootup to complete before returning. |
| auto manager = component::ConnectAt<fuchsia_driver_development::Manager>( |
| fidl::UnownedClientEnd<fuchsia_io::Directory>( |
| realm_->component().exposed().unowned_channel())); |
| if (manager.is_error()) { |
| completer.Reply(manager.take_error()); |
| return; |
| } |
| |
| development_manager_client_.Bind(*std::move(manager), dispatcher_); |
| |
| development_manager_client_->WaitForBootup().Then( |
| [completer = completer.ToAsync()]( |
| fidl::Result<fuchsia_driver_development::Manager::WaitForBootup>& wait_result) mutable { |
| if (wait_result.is_error()) { |
| completer.Reply(zx::error(wait_result.error_value().status())); |
| return; |
| } |
| |
| 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_LOG_KV(WARNING, "DriverDevelopmentService received unknown method.", |
| FX_KV("Direction", method_type.c_str()), FX_KV("Ordinal", metadata.method_ordinal)); |
| } |
| |
| private: |
| bool is_started_ = false; |
| component::OutgoingDirectory* outgoing_; |
| async_dispatcher_t* dispatcher_; |
| fidl::ServerBindingGroup<fuchsia_driver_test::Realm> bindings_; |
| fidl::Client<fuchsia_driver_development::Manager> development_manager_client_; |
| |
| struct Directory { |
| const char* name; |
| uint32_t flags; |
| fidl::ServerEnd<fuchsia_io::Directory> server_end; |
| }; |
| |
| component_testing::RealmBuilder realm_builder_ = |
| component_testing::RealmBuilder::CreateFromRelativeUrl("#meta/test_realm.cm"); |
| std::optional<component_testing::RealmRoot> realm_; |
| driver_test_realm_config::Config config_; |
| fidl::ServerEnd<fuchsia_io::Directory> realm_builder_exposed_dir_; |
| driver_test_realm::BootItems boot_items_; |
| driver_test_realm::SystemStateTransition system_state_; |
| driver_test_realm::RootJob root_job_; |
| std::optional<driver_test_realm::InternalServer> internal_server_; |
| }; |
| |
| } // namespace |
| |
| int main(int argc, const char** argv) { |
| async::Loop loop(&kAsyncLoopConfigNeverAttachToThread); |
| fuchsia_logging::LogSettingsBuilder builder; |
| builder.WithDispatcher(loop.dispatcher()).BuildAndInitialize(); |
| 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; |
| } |