blob: bb8f21a5c73736c886898186f180955148ac3096 [file] [log] [blame]
// 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 "usb-virtual-bus.h"
#include <fcntl.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/async-loop/default.h>
#include <lib/devmgr-integration-test/fixture.h>
#include <lib/fdio/directory.h>
#include <lib/fdio/fd.h>
#include <lib/fdio/fdio.h>
#include <lib/fdio/namespace.h>
#include <lib/fdio/spawn.h>
#include <lib/fdio/unsafe.h>
#include <lib/fdio/watcher.h>
#include <lib/fidl-async/cpp/bind.h>
#include <sys/stat.h>
#include <unistd.h>
#include <zircon/device/vfs.h>
#include <zircon/hw/usb.h>
#include <zircon/processargs.h>
#include <zircon/syscalls.h>
#include <gtest/gtest.h>
namespace usb_virtual_bus {
USBVirtualBusBase::USBVirtualBusBase(std::string pkg_url, std::string svc_name) {
// create a IsolatedDevmgr through test component
auto ctx = sys::ComponentContext::CreateAndServeOutgoingDirectory();
fidl::InterfacePtr<fuchsia::sys::Launcher> launcher;
ctx->svc()->Connect(launcher.NewRequest());
zx::channel req;
services_ = sys::ServiceDirectory::CreateWithRequest(&req);
fuchsia::sys::LaunchInfo info;
info.directory_request = std::move(req);
info.url = pkg_url;
launcher->CreateComponent(std::move(info), ctlr_.NewRequest());
ctlr_.set_error_handler([](zx_status_t err) { FAIL() << "Controller shouldn't exit"; });
zx::channel devfs_server_end, devfs_client_end;
zx::channel::create(0, &devfs_server_end, &devfs_client_end);
services_->Connect(svc_name, std::move(devfs_server_end));
int fd_devfs;
fdio_fd_create(devfs_client_end.release(), &fd_devfs);
devfs_.reset(fd_devfs);
}
void USBVirtualBusBase::InitPeripheral() {
fbl::unique_fd fd;
devmgr_integration_test::RecursiveWaitForFile(devfs_root(),
"sys/platform/11:03:0/usb-virtual-bus", &fd);
ASSERT_EQ(fd.is_valid(), true);
zx::channel virtual_bus;
ASSERT_EQ(fdio_get_service_handle(fd.release(), virtual_bus.reset_and_get_address()), ZX_OK);
virtual_bus_.emplace(std::move(virtual_bus));
auto enable_result = virtual_bus_->Enable();
ASSERT_EQ(enable_result.status(), ZX_OK);
ASSERT_EQ(enable_result.value().status, ZX_OK);
char peripheral_str[] = "usb-peripheral";
fd.reset(openat(devfs_root().get(), "class", O_RDONLY));
while (fdio_watch_directory(fd.get(), usb_virtual_bus_helper::WaitForFile, ZX_TIME_INFINITE,
&peripheral_str) != ZX_ERR_STOP)
continue;
fd.reset(openat(devfs_root().get(), "class/usb-peripheral", O_RDONLY));
fbl::String devpath;
while (fdio_watch_directory(fd.get(), usb_virtual_bus_helper::WaitForAnyFile, ZX_TIME_INFINITE,
&devpath) != ZX_ERR_STOP)
continue;
devpath = fbl::String::Concat({fbl::String("class/usb-peripheral/"), fbl::String(devpath)});
fd.reset(openat(devfs_root().get(), devpath.c_str(), O_RDWR));
zx::channel peripheral;
ASSERT_EQ(fdio_get_service_handle(fd.release(), peripheral.reset_and_get_address()), ZX_OK);
peripheral_.emplace(std::move(peripheral));
ASSERT_NO_FATAL_FAILURE(ClearPeripheralDeviceFunctions());
}
int USBVirtualBusBase::GetRootFd() { return devfs_root().get(); }
class EventWatcher : public ::llcpp::fuchsia::hardware::usb::peripheral::Events::Interface {
public:
explicit EventWatcher(async::Loop* loop, zx::channel svc, size_t functions)
: loop_(loop), functions_(functions) {
fidl::Bind(loop->dispatcher(), std::move(svc), this);
}
void FunctionRegistered(FunctionRegisteredCompleter::Sync completer);
void FunctionsCleared(FunctionsClearedCompleter::Sync completer);
bool all_functions_registered() { return functions_registered_ == functions_; }
bool all_functions_cleared() { return all_functions_cleared_; }
private:
async::Loop* loop_;
const size_t functions_;
size_t functions_registered_ = 0;
bool all_functions_cleared_ = false;
};
void EventWatcher::FunctionRegistered(FunctionRegisteredCompleter::Sync completer) {
functions_registered_++;
if (all_functions_registered()) {
loop_->Quit();
completer.Close(ZX_ERR_CANCELED);
} else {
completer.Reply();
}
}
void EventWatcher::FunctionsCleared(FunctionsClearedCompleter::Sync completer) {
all_functions_cleared_ = true;
loop_->Quit();
completer.Close(ZX_ERR_CANCELED);
}
void USBVirtualBusBase::SetupPeripheralDevice(DeviceDescriptor&& device_desc,
std::vector<FunctionDescriptor> function_descs) {
zx::channel state_change_sender, state_change_receiver;
ASSERT_EQ(zx::channel::create(0, &state_change_sender, &state_change_receiver), ZX_OK);
auto set_result = peripheral_->SetStateChangeListener(std::move(state_change_receiver));
ASSERT_EQ(set_result.status(), ZX_OK);
auto set_config =
peripheral_->SetConfiguration(std::move(device_desc), ::fidl::unowned_vec(function_descs));
ASSERT_EQ(set_config.status(), ZX_OK);
ASSERT_FALSE(set_config->result.is_err());
async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread);
EventWatcher watcher(&loop, std::move(state_change_sender), function_descs.size());
loop.Run();
ASSERT_TRUE(watcher.all_functions_registered());
auto connect_result = virtual_bus_->Connect();
ASSERT_EQ(connect_result.status(), ZX_OK);
ASSERT_EQ(connect_result.value().status, ZX_OK);
}
void USBVirtualBusBase::ClearPeripheralDeviceFunctions() {
zx::channel handles[2];
ASSERT_EQ(zx::channel::create(0, handles, handles + 1), ZX_OK);
auto set_result = peripheral_->SetStateChangeListener(std::move(handles[1]));
ASSERT_EQ(set_result.status(), ZX_OK);
auto clear_functions = peripheral_->ClearFunctions();
ASSERT_EQ(clear_functions.status(), ZX_OK);
async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread);
EventWatcher watcher(&loop, std::move(handles[0]), 0);
loop.Run();
ASSERT_TRUE(watcher.all_functions_cleared());
}
} // namespace usb_virtual_bus