blob: 4fac9647315e57cca01c2c178cd5a5f2a235ca78 [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 <dirent.h>
#include <endian.h>
#include <fcntl.h>
#include <fuchsia/device/llcpp/fidl.h>
#include <fuchsia/hardware/input/llcpp/fidl.h>
#include <fuchsia/hardware/usb/peripheral/llcpp/fidl.h>
#include <fuchsia/hardware/usb/virtual/bus/llcpp/fidl.h>
#include <lib/fdio/fdio.h>
#include <lib/fdio/watcher.h>
#include <lib/usb-virtual-bus-launcher/usb-virtual-bus-launcher.h>
#include <sys/stat.h>
#include <unistd.h>
#include <zircon/hw/usb.h>
#include <zircon/syscalls.h>
#include <ddk/platform-defs.h>
#include <fbl/auto_call.h>
#include <fbl/function.h>
#include <fbl/string.h>
#include <hid/boot.h>
#include <zxtest/zxtest.h>
namespace usb_virtual_bus {
namespace {
class USBVirtualBus : public usb_virtual_bus_base::USBVirtualBusBase {
public:
USBVirtualBus() {}
// Initialize a Usb HID device. Asserts on failure.
void InitUsbHid(fbl::String* devpath,
::llcpp::fuchsia::hardware::usb::peripheral::FunctionDescriptor desc);
// Unbinds Usb HID driver from host.
void Unbind(fbl::String devpath);
};
// Initialize a Usb HID device. Asserts on failure.
void USBVirtualBus::InitUsbHid(
fbl::String* devpath, ::llcpp::fuchsia::hardware::usb::peripheral::FunctionDescriptor desc) {
namespace usb_peripheral = ::llcpp::fuchsia::hardware::usb::peripheral;
using ConfigurationDescriptor =
::fidl::VectorView<::llcpp::fuchsia::hardware::usb::peripheral::FunctionDescriptor>;
usb_peripheral::DeviceDescriptor device_desc = {};
device_desc.bcd_usb = htole16(0x0200);
device_desc.b_max_packet_size0 = 64;
device_desc.bcd_device = htole16(0x0100);
device_desc.b_num_configurations = 1;
std::vector<usb_peripheral::FunctionDescriptor> function_descs;
function_descs.push_back(desc);
std::vector<ConfigurationDescriptor> config_descs;
config_descs.emplace_back(fidl::unowned_vec(function_descs));
ASSERT_NO_FATAL_FAILURES(SetupPeripheralDevice(std::move(device_desc), std::move(config_descs)));
fbl::unique_fd fd(openat(devmgr_.devfs_root().get(), "class/input", O_RDONLY));
while (fdio_watch_directory(fd.get(), WaitForAnyFile, ZX_TIME_INFINITE, devpath) != ZX_ERR_STOP) {
continue;
}
*devpath = fbl::String::Concat({fbl::String("class/input/"), *devpath});
}
// Unbinds HID driver from host.
void USBVirtualBus::Unbind(fbl::String devpath) {
fbl::unique_fd fd_input(openat(GetRootFd(), devpath.c_str(), O_RDWR));
ASSERT_GE(fd_input.get(), 0);
zx::channel input_channel;
ASSERT_OK(fdio_get_service_handle(fd_input.release(), input_channel.reset_and_get_address()));
auto hid_device_path_response = llcpp::fuchsia::device::Controller::Call::GetTopologicalPath(
zx::unowned_channel(input_channel));
ASSERT_OK(hid_device_path_response.status());
zx::channel usbhid_channel;
std::string hid_device_path = hid_device_path_response->result.response().path.data();
std::string DEV_CONST = "@/dev/";
std::string usb_hid_path = hid_device_path.substr(
DEV_CONST.length(), hid_device_path.find_last_of("/") - DEV_CONST.length());
fbl::unique_fd fd_usb_hid(openat(GetRootFd(), usb_hid_path.c_str(), O_RDONLY));
ASSERT_GE(fd_usb_hid.get(), 0);
ASSERT_OK(fdio_get_service_handle(fd_usb_hid.release(), usbhid_channel.reset_and_get_address()));
std::string ifc_path = usb_hid_path.substr(0, usb_hid_path.find_last_of('/'));
fbl::unique_fd fd_usb_hid_parent(openat(GetRootFd(), ifc_path.c_str(), O_DIRECTORY | O_RDONLY));
ASSERT_GE(fd_usb_hid_parent.get(), 0);
std::unique_ptr<devmgr_integration_test::DirWatcher> watcher;
ASSERT_OK(devmgr_integration_test::DirWatcher::Create(std::move(fd_usb_hid_parent), &watcher));
auto result =
llcpp::fuchsia::device::Controller::Call::ScheduleUnbind(zx::unowned_channel(usbhid_channel));
ASSERT_OK(result.status());
ASSERT_OK(watcher->WaitForRemoval("usb-hid", zx::duration::infinite()));
}
class UsbOneEndpointTest : public zxtest::Test {
public:
void SetUp() override {
::llcpp::fuchsia::hardware::usb::peripheral::FunctionDescriptor usb_hid_function_desc = {
.interface_class = USB_CLASS_HID,
.interface_subclass = 0,
.interface_protocol = USB_PROTOCOL_TEST_HID_ONE_ENDPOINT,
};
ASSERT_NO_FATAL_FAILURES(bus_.InitUsbHid(&devpath_, usb_hid_function_desc));
fbl::unique_fd fd_input(openat(bus_.GetRootFd(), devpath_.c_str(), O_RDWR));
ASSERT_GT(fd_input.get(), 0);
zx::channel input_channel;
ASSERT_OK(fdio_get_service_handle(fd_input.release(), input_channel.reset_and_get_address()));
sync_client_ = llcpp::fuchsia::hardware::input::Device::SyncClient(std::move(input_channel));
}
void TearDown() override {
ASSERT_NO_FATAL_FAILURES(bus_.ClearPeripheralDeviceFunctions());
auto result2 = bus_.virtual_bus().Disable();
ASSERT_NO_FATAL_FAILURES(ValidateResult(result2));
}
protected:
USBVirtualBus bus_;
fbl::String devpath_;
std::optional<llcpp::fuchsia::hardware::input::Device::SyncClient> sync_client_;
};
class UsbTwoEndpointTest : public zxtest::Test {
public:
void SetUp() override {
::llcpp::fuchsia::hardware::usb::peripheral::FunctionDescriptor usb_hid_function_desc = {
.interface_class = USB_CLASS_HID,
.interface_subclass = 0,
.interface_protocol = USB_PROTOCOL_TEST_HID_TWO_ENDPOINT,
};
ASSERT_NO_FATAL_FAILURES(bus_.InitUsbHid(&devpath_, usb_hid_function_desc));
fbl::unique_fd fd_input(openat(bus_.GetRootFd(), devpath_.c_str(), O_RDWR));
ASSERT_GT(fd_input.get(), 0);
zx::channel input_channel;
ASSERT_OK(fdio_get_service_handle(fd_input.release(), input_channel.reset_and_get_address()));
sync_client_ = llcpp::fuchsia::hardware::input::Device::SyncClient(std::move(input_channel));
}
void TearDown() override {
ASSERT_NO_FATAL_FAILURES(bus_.ClearPeripheralDeviceFunctions());
auto result2 = bus_.virtual_bus().Disable();
ASSERT_NO_FATAL_FAILURES(ValidateResult(result2));
}
protected:
USBVirtualBus bus_;
fbl::String devpath_;
std::optional<llcpp::fuchsia::hardware::input::Device::SyncClient> sync_client_;
};
TEST_F(UsbOneEndpointTest, SetAndGetReport) {
uint8_t buf[sizeof(hid_boot_mouse_report_t)] = {0xab, 0xbc, 0xde};
auto set_result = sync_client_->SetReport(::llcpp::fuchsia::hardware::input::ReportType::INPUT, 0,
fidl::unowned_vec(buf));
auto get_result =
sync_client_->GetReport(::llcpp::fuchsia::hardware::input::ReportType::INPUT, 0);
ASSERT_OK(set_result.status());
ASSERT_OK(set_result->status);
ASSERT_OK(get_result.status());
ASSERT_OK(get_result->status);
ASSERT_EQ(get_result->report.count(), sizeof(hid_boot_mouse_report_t));
ASSERT_EQ(0xab, get_result->report[0]);
ASSERT_EQ(0xbc, get_result->report[1]);
ASSERT_EQ(0xde, get_result->report[2]);
}
TEST_F(UsbOneEndpointTest, UnBind) { ASSERT_NO_FATAL_FAILURES(bus_.Unbind(devpath_)); }
TEST_F(UsbTwoEndpointTest, SetAndGetReport) {
uint8_t buf[sizeof(hid_boot_mouse_report_t)] = {0xab, 0xbc, 0xde};
auto set_result = sync_client_->SetReport(::llcpp::fuchsia::hardware::input::ReportType::INPUT, 0,
fidl::unowned_vec(buf));
auto get_result =
sync_client_->GetReport(::llcpp::fuchsia::hardware::input::ReportType::INPUT, 0);
ASSERT_OK(set_result.status());
ASSERT_OK(set_result->status);
ASSERT_OK(get_result.status());
ASSERT_OK(get_result->status);
ASSERT_EQ(get_result->report.count(), sizeof(hid_boot_mouse_report_t));
ASSERT_EQ(0xab, get_result->report[0]);
ASSERT_EQ(0xbc, get_result->report[1]);
ASSERT_EQ(0xde, get_result->report[2]);
}
TEST_F(UsbTwoEndpointTest, UnBind) { ASSERT_NO_FATAL_FAILURES(bus_.Unbind(devpath_)); }
} // namespace
} // namespace usb_virtual_bus