blob: 19378d1db0e4ed1c10756781fc0a1cb6f775de14 [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 <fidl/fuchsia.device/cpp/wire.h>
#include <fidl/fuchsia.hardware.input/cpp/wire.h>
#include <fidl/fuchsia.hardware.usb.peripheral/cpp/wire.h>
#include <fidl/fuchsia.hardware.usb.virtual.bus/cpp/wire.h>
#include <lib/component/incoming/cpp/protocol.h>
#include <lib/ddk/platform-defs.h>
#include <lib/device-watcher/cpp/device-watcher.h>
#include <lib/fdio/cpp/caller.h>
#include <lib/fdio/directory.h>
#include <lib/fit/function.h>
#include <lib/hid/boot.h>
#include <lib/usb-virtual-bus-launcher/usb-virtual-bus-launcher.h>
#include <sys/stat.h>
#include <unistd.h>
#include <zircon/status.h>
#include <zircon/syscalls.h>
#include <fbl/string.h>
#include <usb/usb.h>
#include <zxtest/zxtest.h>
namespace usb_virtual_bus {
namespace {
using usb_virtual::BusLauncher;
std::string GetDeviceControllerPath(std::string_view dev_path) {
auto dev_path_modified = std::string(dev_path);
return dev_path_modified.append("/device_controller");
}
class UsbHidTest : public zxtest::Test {
public:
void SetUp() override {
auto bus = BusLauncher::Create();
ASSERT_OK(bus.status_value());
bus_ = std::move(bus.value());
auto usb_hid_function_desc = GetConfigDescriptor();
ASSERT_NO_FATAL_FAILURE(InitUsbHid(&devpath_, usb_hid_function_desc));
fdio_cpp::UnownedFdioCaller caller(bus_->GetRootFd());
zx::result controller =
component::ConnectAt<fuchsia_hardware_input::Controller>(caller.directory(), devpath_);
ASSERT_OK(controller);
auto [device, server] = fidl::Endpoints<fuchsia_hardware_input::Device>::Create();
ASSERT_OK(fidl::WireCall(controller.value())->OpenSession(std::move(server)));
sync_client_ = fidl::WireSyncClient<fuchsia_hardware_input::Device>(std::move(device));
}
void TearDown() override {
ASSERT_OK(bus_->ClearPeripheralDeviceFunctions());
ASSERT_OK(bus_->Disable());
}
protected:
virtual fuchsia_hardware_usb_peripheral::wire::FunctionDescriptor GetConfigDescriptor() = 0;
// Initialize a Usb HID device. Asserts on failure.
void InitUsbHid(fbl::String* dev_path,
fuchsia_hardware_usb_peripheral::wire::FunctionDescriptor desc) {
namespace usb_peripheral = fuchsia_hardware_usb_peripheral;
std::vector<usb_peripheral::wire::FunctionDescriptor> function_descs = {desc};
ASSERT_OK(bus_->SetupPeripheralDevice(
{
.bcd_usb = htole16(0x0200),
.b_max_packet_size0 = 64,
.id_vendor = htole16(0x18d1),
.id_product = htole16(0xaf10),
.bcd_device = htole16(0x0100),
.b_num_configurations = 1,
},
{fidl::VectorView<usb_peripheral::wire::FunctionDescriptor>::FromExternal(
function_descs)}));
fdio_cpp::UnownedFdioCaller caller(bus_->GetRootFd());
{
zx::result directory =
component::ConnectAt<fuchsia_io::Directory>(caller.directory(), "class/input");
ASSERT_OK(directory);
zx::result watch_result = device_watcher::WatchDirectoryForItems(
directory.value(), [&dev_path](std::string_view devpath) {
*dev_path = fbl::String::Concat({"class/input/", devpath});
return std::monostate{};
});
ASSERT_OK(watch_result);
}
}
// Unbinds Usb HID driver from host.
void Unbind(const fbl::String& devpath) {
fdio_cpp::UnownedFdioCaller caller(bus_->GetRootFd());
zx::result input_controller = component::ConnectAt<fuchsia_device::Controller>(
caller.directory(), GetDeviceControllerPath(devpath_.data()));
ASSERT_OK(input_controller);
const fidl::WireResult result = fidl::WireCall(input_controller.value())->GetTopologicalPath();
ASSERT_OK(result);
const fit::result response = result.value();
ASSERT_TRUE(response.is_ok(), "%s", zx_status_get_string(response.error_value()));
const std::string_view hid_device_abspath = response->path.get();
constexpr std::string_view kDev = "/dev/";
ASSERT_TRUE(cpp20::starts_with(hid_device_abspath, kDev));
const std::string_view hid_device_relpath = hid_device_abspath.substr(kDev.size());
const std::string_view usb_hid_relpath =
hid_device_relpath.substr(0, hid_device_relpath.find_last_of('/'));
zx::result usb_hid_controller = component::ConnectAt<fuchsia_device::Controller>(
caller.directory(), GetDeviceControllerPath(usb_hid_relpath));
ASSERT_OK(usb_hid_controller);
const size_t last_slash = usb_hid_relpath.find_last_of('/');
const std::string_view suffix = usb_hid_relpath.substr(last_slash + 1);
std::string ifc_path{usb_hid_relpath.substr(0, last_slash)};
auto [client_end, server_end] = fidl::Endpoints<fuchsia_io::Directory>::Create();
ASSERT_OK(fdio_open_at(caller.directory().channel()->get(), ifc_path.c_str(),
static_cast<uint32_t>(fuchsia_io::OpenFlags::kDirectory),
server_end.TakeChannel().release()));
zx::result watcher = device_watcher::DirWatcher::Create(client_end);
ASSERT_OK(watcher);
{
const fidl::WireResult result = fidl::WireCall(usb_hid_controller.value())->ScheduleUnbind();
ASSERT_OK(result.status());
const fit::result response = result.value();
ASSERT_TRUE(response.is_ok(), "%s", zx_status_get_string(response.error_value()));
}
ASSERT_OK(watcher->WaitForRemoval(suffix, zx::duration::infinite()));
}
std::optional<BusLauncher> bus_;
fbl::String devpath_;
fidl::WireSyncClient<fuchsia_hardware_input::Device> sync_client_;
};
class UsbOneEndpointTest : public UsbHidTest {
protected:
fuchsia_hardware_usb_peripheral::wire::FunctionDescriptor GetConfigDescriptor() override {
return fuchsia_hardware_usb_peripheral::wire::FunctionDescriptor{
.interface_class = USB_CLASS_HID,
.interface_subclass = 0,
.interface_protocol = USB_PROTOCOL_TEST_HID_ONE_ENDPOINT,
};
}
};
// TODO(b/316176095): Re-enable test after ensuring it works with DFv2.
TEST_F(UsbOneEndpointTest, DISABLED_GetDeviceIdsVidPid) {
// Check USB device descriptor VID/PID plumbing.
auto result = sync_client_->GetDeviceIds();
ASSERT_OK(result.status());
EXPECT_EQ(0x18d1, result.value().ids.vendor_id);
EXPECT_EQ(0xaf10, result.value().ids.product_id);
}
// TODO(b/316176095): Re-enable test after ensuring it works with DFv2.
TEST_F(UsbOneEndpointTest, DISABLED_SetAndGetReport) {
uint8_t buf[sizeof(hid_boot_mouse_report_t)] = {0xab, 0xbc, 0xde};
auto set_result = sync_client_->SetReport(fuchsia_hardware_input::wire::ReportType::kInput, 0,
fidl::VectorView<uint8_t>::FromExternal(buf));
auto get_result = sync_client_->GetReport(fuchsia_hardware_input::wire::ReportType::kInput, 0);
ASSERT_OK(set_result.status());
ASSERT_OK(set_result.value().status);
ASSERT_OK(get_result.status());
ASSERT_OK(get_result.value().status);
ASSERT_EQ(get_result.value().report.count(), sizeof(hid_boot_mouse_report_t));
ASSERT_EQ(0xab, get_result.value().report[0]);
ASSERT_EQ(0xbc, get_result.value().report[1]);
ASSERT_EQ(0xde, get_result.value().report[2]);
}
// TODO(b/316176095): Re-enable test after ensuring it works with DFv2.
TEST_F(UsbOneEndpointTest, DISABLED_UnBind) { ASSERT_NO_FATAL_FAILURE(Unbind(devpath_)); }
class UsbTwoEndpointTest : public UsbHidTest {
protected:
fuchsia_hardware_usb_peripheral::wire::FunctionDescriptor GetConfigDescriptor() override {
return fuchsia_hardware_usb_peripheral::wire::FunctionDescriptor{
.interface_class = USB_CLASS_HID,
.interface_subclass = 0,
.interface_protocol = USB_PROTOCOL_TEST_HID_TWO_ENDPOINT,
};
}
};
// TODO(b/316176095): Re-enable test after ensuring it works with DFv2.
TEST_F(UsbTwoEndpointTest, DISABLED_SetAndGetReport) {
uint8_t buf[sizeof(hid_boot_mouse_report_t)] = {0xab, 0xbc, 0xde};
auto set_result = sync_client_->SetReport(fuchsia_hardware_input::wire::ReportType::kInput, 0,
fidl::VectorView<uint8_t>::FromExternal(buf));
auto get_result = sync_client_->GetReport(fuchsia_hardware_input::wire::ReportType::kInput, 0);
ASSERT_OK(set_result.status());
ASSERT_OK(set_result.value().status);
ASSERT_OK(get_result.status());
ASSERT_OK(get_result.value().status);
ASSERT_EQ(get_result.value().report.count(), sizeof(hid_boot_mouse_report_t));
ASSERT_EQ(0xab, get_result.value().report[0]);
ASSERT_EQ(0xbc, get_result.value().report[1]);
ASSERT_EQ(0xde, get_result.value().report[2]);
}
// TODO(b/316176095): Re-enable test after ensuring it works with DFv2.
TEST_F(UsbTwoEndpointTest, DISABLED_UnBind) { ASSERT_NO_FATAL_FAILURE(Unbind(devpath_)); }
} // namespace
} // namespace usb_virtual_bus