| // Copyright 2019 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 "lib/usb-virtual-bus-launcher/usb-virtual-bus-launcher.h" |
| |
| #include <fidl/fuchsia.driver.test/cpp/fidl.h> |
| #include <lib/async-loop/cpp/loop.h> |
| #include <lib/async-loop/loop.h> |
| #include <lib/component/incoming/cpp/directory.h> |
| #include <lib/component/incoming/cpp/protocol.h> |
| #include <lib/component/incoming/cpp/service_member_watcher.h> |
| #include <lib/ddk/platform-defs.h> |
| #include <lib/device-watcher/cpp/device-watcher.h> |
| #include <lib/fdio/cpp/caller.h> |
| #include <lib/fdio/watcher.h> |
| #include <lib/usb-peripheral-utils/event-watcher.h> |
| #include <zircon/status.h> |
| |
| #include <iostream> |
| |
| #include <fbl/string.h> |
| #include <fbl/unique_fd.h> |
| #include <usb/usb.h> |
| |
| namespace usb_virtual { |
| |
| zx::result<BusLauncher> BusLauncher::Create( |
| std::vector<fuchsia_component_test::Capability> exposes) { |
| auto realm_builder = component_testing::RealmBuilder::Create(); |
| driver_test_realm::Setup(realm_builder); |
| async::Loop loop(&kAsyncLoopConfigNeverAttachToThread); |
| exposes.emplace_back(fuchsia_component_test::Capability::WithService( |
| fuchsia_component_test::Service{{.name = fuchsia_hardware_usb_peripheral::Service::Name}})); |
| driver_test_realm::AddDtrExposes(realm_builder, exposes); |
| auto realm = realm_builder.Build(loop.dispatcher()); |
| |
| auto client = realm.component().Connect<fuchsia_driver_test::Realm>(); |
| if (client.is_error()) { |
| std::cerr << "Failed to connect to fuchsia.driver.test/Realm: " << client.status_string() |
| << '\n'; |
| return client.take_error(); |
| } |
| |
| auto realm_args = fuchsia_driver_test::RealmArgs(); |
| realm_args.root_driver("fuchsia-boot:///dtr#meta/usb-virtual-bus.cm"); |
| realm_args.dtr_exposes(exposes); |
| fidl::Result result = fidl::Call(*client)->Start(std::move(realm_args)); |
| if (result.is_error()) { |
| std::cerr << "Failed to connect to start driver test realm: " << result.error_value() << '\n'; |
| if (result.error_value().is_domain_error()) { |
| return zx::error(result.error_value().domain_error()); |
| } |
| if (result.error_value().is_framework_error()) { |
| return zx::error(result.error_value().framework_error().status()); |
| } |
| } |
| |
| // Open dev. |
| auto [dev_client, dev_server] = fidl::Endpoints<fuchsia_io::Directory>::Create(); |
| zx_status_t status = realm.component().exposed()->Open( |
| "dev-topological", fuchsia::io::PERM_READABLE, {}, dev_server.TakeChannel()); |
| if (status != ZX_OK) { |
| std::cerr << "Failed to open dev-topological: " << zx_status_get_string(status) << '\n'; |
| return zx::error(status); |
| } |
| fbl::unique_fd dev_fd; |
| status = fdio_fd_create(dev_client.TakeChannel().release(), dev_fd.reset_and_get_address()); |
| if (status != ZX_OK) { |
| std::cerr << "Failed to create fd: " << zx_status_get_string(status) << '\n'; |
| return zx::error(status); |
| } |
| |
| BusLauncher launcher(std::move(realm), std::move(dev_fd)); |
| |
| status = fdio_watch_directory( |
| launcher.GetRootFd(), |
| [](int, int event, const char* fn, void*) { |
| if (event != WATCH_EVENT_ADD_FILE) { |
| return ZX_OK; |
| } |
| return strcmp("usb-virtual-bus", fn) ? ZX_OK : ZX_ERR_STOP; |
| }, |
| ZX_TIME_INFINITE, nullptr); |
| if (status != ZX_ERR_STOP) { |
| std::cerr << "Failed to wait for usb-virtual-bus: " << zx_status_get_string(status) << '\n'; |
| return zx::error(status); |
| } |
| |
| fdio_cpp::UnownedFdioCaller caller(launcher.devfs_root_); |
| |
| auto virtual_bus = component::ConnectAt<fuchsia_hardware_usb_virtual_bus::Bus>(caller.directory(), |
| "usb-virtual-bus"); |
| if (virtual_bus.is_error()) { |
| std::cerr << "Failed to wait for usb-virtual-bus: " << virtual_bus.status_string() << '\n'; |
| return virtual_bus.take_error(); |
| } |
| launcher.virtual_bus_.Bind(std::move(*virtual_bus)); |
| |
| const fidl::WireResult enable_result = launcher.virtual_bus_->Enable(); |
| if (!enable_result.ok()) { |
| std::cerr << "virtual_bus_->Enable(): " << enable_result.FormatDescription() << '\n'; |
| return zx::error(enable_result.status()); |
| } |
| const fidl::WireResponse enable_response = enable_result.value(); |
| if (zx_status_t status = enable_response.status; status != ZX_OK) { |
| std::cerr << "virtual_bus_->Enable(): " << zx_status_get_string(status) << '\n'; |
| return zx::error(status); |
| } |
| fidl::UnownedClientEnd<fuchsia_io::Directory> svc = launcher.GetExposedDir(); |
| component::SyncServiceMemberWatcher<fuchsia_hardware_usb_peripheral::Service::Device> watcher( |
| svc); |
| // Wait indefinitely until a service instance appears in the service directory |
| zx::result<fidl::ClientEnd<fuchsia_hardware_usb_peripheral::Device>> peripheral = |
| watcher.GetNextInstance(false); |
| if (peripheral.is_error()) { |
| std::cerr << "Failed to get USB peripheral service: " << peripheral.status_string() << '\n'; |
| return peripheral.take_error(); |
| } |
| launcher.peripheral_.Bind(std::move(peripheral.value())); |
| |
| if (zx_status_t status = launcher.ClearPeripheralDeviceFunctions(); status != ZX_OK) { |
| std::cerr << "launcher.ClearPeripheralDeviceFunctions(): " << zx_status_get_string(status) |
| << '\n'; |
| return zx::error(status); |
| } |
| |
| return zx::ok(std::move(launcher)); |
| } |
| |
| zx_status_t BusLauncher::SetupPeripheralDevice(DeviceDescriptor&& device_desc, |
| std::vector<ConfigurationDescriptor> config_descs) { |
| auto [client, server] = fidl::Endpoints<fuchsia_hardware_usb_peripheral::Events>::Create(); |
| |
| if (const fidl::Status result = peripheral_->SetStateChangeListener(std::move(client)); |
| !result.ok()) { |
| std::cerr << "peripheral_->SetStateChangeListener(): " << result.FormatDescription() << '\n'; |
| return result.status(); |
| } |
| |
| const fidl::WireResult result = peripheral_->SetConfiguration( |
| device_desc, fidl::VectorView<ConfigurationDescriptor>::FromExternal(config_descs)); |
| if (!result.ok()) { |
| std::cerr << "peripheral_->SetConfiguration(): " << result.FormatDescription() << '\n'; |
| return result.status(); |
| } |
| const fit::result response = result.value(); |
| if (response.is_error()) { |
| std::cerr << "peripheral_->SetConfiguration(): " << zx_status_get_string(response.error_value()) |
| << '\n'; |
| return response.error_value(); |
| } |
| |
| async::Loop loop(&kAsyncLoopConfigNeverAttachToThread); |
| usb_peripheral_utils::EventWatcher watcher(loop, std::move(server), 1); |
| |
| if (zx_status_t status = loop.Run(); status != ZX_ERR_CANCELED) { |
| std::cerr << "loop.Run(): " << zx_status_get_string(status) << '\n'; |
| return status; |
| } |
| if (!watcher.all_functions_registered()) { |
| std::cerr << "watcher.all_functions_registered() returned false" << '\n'; |
| return ZX_ERR_INTERNAL; |
| } |
| |
| auto connect_result = virtual_bus_->Connect(); |
| if (connect_result.status() != ZX_OK) { |
| std::cerr << "virtual_bus_->Connect(): " << zx_status_get_string(connect_result.status()) |
| << '\n'; |
| return connect_result.status(); |
| } |
| if (connect_result.value().status != ZX_OK) { |
| std::cerr << "virtual_bus_->Connect() returned status: " |
| << zx_status_get_string(connect_result.value().status) << '\n'; |
| return connect_result.value().status; |
| } |
| return ZX_OK; |
| } |
| |
| zx_status_t BusLauncher::ClearPeripheralDeviceFunctions() { |
| if (!peripheral_.is_valid()) { |
| return ZX_ERR_INTERNAL; |
| } |
| auto [client, server] = fidl::Endpoints<fuchsia_hardware_usb_peripheral::Events>::Create(); |
| |
| if (const fidl::Status result = peripheral_->SetStateChangeListener(std::move(client)); |
| !result.ok()) { |
| std::cerr << "peripheral_->SetStateChangeListener(): " << result.FormatDescription() << '\n'; |
| return result.status(); |
| } |
| |
| const fidl::WireResult result = peripheral_->ClearFunctions(); |
| if (!result.ok()) { |
| std::cerr << "peripheral_->ClearFunctions(): " << result.FormatDescription() << '\n'; |
| return result.status(); |
| } |
| |
| async::Loop loop(&kAsyncLoopConfigNeverAttachToThread); |
| usb_peripheral_utils::EventWatcher watcher(loop, std::move(server), 1); |
| |
| if (zx_status_t status = loop.Run(); status != ZX_ERR_CANCELED) { |
| std::cerr << "loop.Run(): " << zx_status_get_string(status) << '\n'; |
| return status; |
| } |
| if (!watcher.all_functions_cleared()) { |
| std::cerr << "watcher.all_functions_cleared() returned false" << '\n'; |
| return ZX_ERR_INTERNAL; |
| } |
| return ZX_OK; |
| } |
| |
| int BusLauncher::GetRootFd() { return devfs_root_.get(); } |
| |
| fidl::UnownedClientEnd<fuchsia_io::Directory> BusLauncher::GetExposedDir() { |
| return fidl::UnownedClientEnd<fuchsia_io::Directory>( |
| realm_.component().exposed().unowned_channel()->get()); |
| } |
| |
| zx_status_t BusLauncher::Disable() { |
| auto result = virtual_bus_->Disable(); |
| if (result.status() != ZX_OK) { |
| std::cerr << "virtual_bus_->Disable(): " << zx_status_get_string(result.status()) << '\n'; |
| return result.status(); |
| } |
| if (result.value().status != ZX_OK) { |
| std::cerr << "virtual_bus_->Disable() returned status: " |
| << zx_status_get_string(result.value().status) << '\n'; |
| return result.value().status; |
| } |
| return result.value().status; |
| } |
| |
| zx_status_t BusLauncher::Disconnect() { |
| auto result = virtual_bus_->Disconnect(); |
| if (result.status() != ZX_OK) { |
| std::cerr << "virtual_bus_->Disconnect(): " << zx_status_get_string(result.status()) << '\n'; |
| return result.status(); |
| } |
| if (result.value().status != ZX_OK) { |
| std::cerr << "virtual_bus_->Disconnect() returned status: " |
| << zx_status_get_string(result.value().status) << '\n'; |
| return result.value().status; |
| } |
| return ZX_OK; |
| } |
| |
| } // namespace usb_virtual |