[garnet][lib] Introduce usb-virtual-bus

Library for setting up usb-virtual-bus, usb-peripheral as well as providing methods for usb device integration tests

Code has been tested in https://fuchsia-review.googlesource.com/c/fuchsia/+/296016

Change-Id: I8bc6c8b61c3511952d5b5c9734312b3182895997
diff --git a/src/lib/isolated_devmgr/BUILD.gn b/src/lib/isolated_devmgr/BUILD.gn
index 18ccabb..2faeee7 100644
--- a/src/lib/isolated_devmgr/BUILD.gn
+++ b/src/lib/isolated_devmgr/BUILD.gn
@@ -53,6 +53,36 @@
   ]
 }
 
+static_library("usb-virtual-bus") {
+  testonly = true
+  sources = [
+    "usb-virtual-bus-helper.cc",
+    "usb-virtual-bus-helper.h",
+    "usb-virtual-bus.cc",
+    "usb-virtual-bus.h",
+  ]
+  public_deps = [
+    "//garnet/public/lib/gtest",
+    "//zircon/public/fidl/fuchsia-hardware-usb-peripheral:fuchsia-hardware-usb-peripheral_llcpp",
+    "//zircon/public/fidl/fuchsia-hardware-usb-virtual-bus:fuchsia-hardware-usb-virtual-bus_llcpp",
+    "//zircon/public/lib/devmgr-integration-test",
+  ]
+  deps = [
+    "//sdk/fidl/fuchsia.sys",
+    "//sdk/lib/sys/cpp",
+    "//zircon/public/lib/async-loop-cpp",
+    "//zircon/public/lib/ddk",
+    "//zircon/public/lib/devmgr-launcher",
+    "//zircon/public/lib/fbl",
+    "//zircon/public/lib/fdio",
+    "//zircon/public/lib/fidl-async-cpp",
+    "//zircon/public/lib/fit",
+    "//zircon/public/lib/zx",
+  ]
+  configs += [ "//build/config/fuchsia:enable_zircon_asserts" ]
+  configs += [ "//build/config/fuchsia:static_cpp_standard_library" ]
+}
+
 executable("test_bin") {
   testonly = true
 
diff --git a/src/lib/isolated_devmgr/usb-virtual-bus-helper.cc b/src/lib/isolated_devmgr/usb-virtual-bus-helper.cc
new file mode 100644
index 0000000..32bdad3
--- /dev/null
+++ b/src/lib/isolated_devmgr/usb-virtual-bus-helper.cc
@@ -0,0 +1,33 @@
+// 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-helper.h"
+
+#include <lib/fdio/watcher.h>
+
+#include <fbl/function.h>
+#include <fbl/string.h>
+
+namespace usb_virtual_bus_helper {
+
+zx_status_t WaitForAnyFile(int dirfd, int event, const char* name, void* cookie) {
+  if (event != WATCH_EVENT_ADD_FILE) {
+    return ZX_OK;
+  }
+  if (*name) {
+    *reinterpret_cast<fbl::String*>(cookie) = fbl::String(name);
+    return ZX_ERR_STOP;
+  } else {
+    return ZX_OK;
+  }
+}
+
+zx_status_t WaitForFile(int dirfd, int event, const char* fn, void* name) {
+  if (event != WATCH_EVENT_ADD_FILE) {
+    return ZX_OK;
+  }
+  return strcmp(static_cast<const char*>(name), fn) ? ZX_OK : ZX_ERR_STOP;
+}
+
+}  // namespace usb_virtual_bus_helper
diff --git a/src/lib/isolated_devmgr/usb-virtual-bus-helper.h b/src/lib/isolated_devmgr/usb-virtual-bus-helper.h
new file mode 100644
index 0000000..799a8fd
--- /dev/null
+++ b/src/lib/isolated_devmgr/usb-virtual-bus-helper.h
@@ -0,0 +1,14 @@
+// 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.
+
+#ifndef SRC_LIB_ISOLATED_DEVMGR_USB_VIRTUAL_BUS_HELPER_H_
+#define SRC_LIB_ISOLATED_DEVMGR_USB_VIRTUAL_BUS_HELPER_H_
+#include <zircon/types.h>
+namespace usb_virtual_bus_helper {
+
+zx_status_t WaitForAnyFile(int dirfd, int event, const char* name, void* cookie);
+
+zx_status_t WaitForFile(int dirfd, int event, const char* fn, void* name);
+}  // namespace usb_virtual_bus_helper
+#endif  // SRC_LIB_ISOLATED_DEVMGR_USB_VIRTUAL_BUS_HELPER_H_
diff --git a/src/lib/isolated_devmgr/usb-virtual-bus.cc b/src/lib/isolated_devmgr/usb-virtual-bus.cc
new file mode 100644
index 0000000..c323e68
--- /dev/null
+++ b/src/lib/isolated_devmgr/usb-virtual-bus.cc
@@ -0,0 +1,143 @@
+// 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/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::Create();
+  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));
+
+  auto clear_result = peripheral_->ClearFunctions();
+  ASSERT_EQ(clear_result.status(), ZX_OK);
+  ASSERT_EQ(clear_result.value().result.is_err(), false);
+}
+
+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);
+
+  bool all_functions_registered() { return functions_registered_ == functions_; }
+
+ private:
+  async::Loop* loop_;
+  const size_t functions_;
+  size_t functions_registered_ = 0;
+
+  bool state_changed_ = false;
+};
+
+void EventWatcher::FunctionRegistered(FunctionRegisteredCompleter::Sync completer) {
+  functions_registered_++;
+  if (all_functions_registered()) {
+    state_changed_ = true;
+    loop_->Quit();
+    completer.Close(ZX_ERR_CANCELED);
+  } else {
+    completer.Reply();
+  }
+}
+
+void USBVirtualBusBase::SetupPeripheralDevice(const 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(
+      device_desc, ::fidl::VectorView(function_descs.size(), function_descs.data()));
+  ASSERT_EQ(set_config.status(), ZX_OK);
+  ASSERT_FALSE(set_config->result.is_err());
+
+  async::Loop loop(&kAsyncLoopConfigNoAttachToThread);
+  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);
+}
+
+}  // namespace usb_virtual_bus
diff --git a/src/lib/isolated_devmgr/usb-virtual-bus.h b/src/lib/isolated_devmgr/usb-virtual-bus.h
new file mode 100644
index 0000000..ed9dd43
--- /dev/null
+++ b/src/lib/isolated_devmgr/usb-virtual-bus.h
@@ -0,0 +1,50 @@
+// 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.
+
+#ifndef SRC_LIB_ISOLATED_DEVMGR_USB_VIRTUAL_BUS_H_
+#define SRC_LIB_ISOLATED_DEVMGR_USB_VIRTUAL_BUS_H_
+
+#include <fuchsia/hardware/usb/peripheral/llcpp/fidl.h>
+#include <fuchsia/hardware/usb/virtual/bus/llcpp/fidl.h>
+#include <fuchsia/sys/cpp/fidl.h>
+#include <lib/sys/cpp/component_context.h>
+#include <zircon/types.h>
+
+#include <optional>
+#include <vector>
+
+#include <fbl/unique_fd.h>
+
+#include "usb-virtual-bus-helper.h"
+
+namespace usb_virtual_bus {
+using llcpp::fuchsia::hardware::usb::peripheral::DeviceDescriptor;
+using llcpp::fuchsia::hardware::usb::peripheral::FunctionDescriptor;
+class USBVirtualBusBase {
+ public:
+  USBVirtualBusBase(std::string pkg_url, std::string svc_name);
+  void InitPeripheral();
+  void SetupPeripheralDevice(const DeviceDescriptor& device_desc,
+                             std::vector<FunctionDescriptor> function_descs);
+  int GetRootFd();
+  fbl::unique_fd& devfs_root() { return devfs_; };
+  llcpp::fuchsia::hardware::usb::peripheral::Device::SyncClient& peripheral() {
+    return peripheral_.value();
+  }
+  llcpp::fuchsia::hardware::usb::virtual_::bus::Bus::SyncClient& virtual_bus() {
+    return virtual_bus_.value();
+  }
+
+ private:
+  fbl::unique_fd devfs_;
+  std::shared_ptr<sys::ServiceDirectory> services_;
+  fidl::InterfacePtr<fuchsia::sys::ComponentController> ctlr_;
+  std::optional<llcpp::fuchsia::hardware::usb::peripheral::Device::SyncClient> peripheral_;
+  std::optional<llcpp::fuchsia::hardware::usb::virtual_::bus::Bus::SyncClient> virtual_bus_;
+  DISALLOW_COPY_ASSIGN_AND_MOVE(USBVirtualBusBase);
+};
+
+}  // namespace usb_virtual_bus
+
+#endif  // SRC_LIB_ISOLATED_DEVMGR_USB_VIRTUAL_BUS_H_