blob: 2349b19fe38661635f2595030a8279991506ab05 [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 <fidl/fuchsia.hardware.serial/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/fdio/cpp/caller.h>
#include <lib/fdio/directory.h>
#include <lib/fdio/watcher.h>
#include <lib/hid/boot.h>
#include <lib/usb-virtual-bus-launcher/usb-virtual-bus-launcher.h>
#include <lib/zx/clock.h>
#include <lib/zx/time.h>
#include <sys/stat.h>
#include <unistd.h>
#include <zircon/errors.h>
#include <zircon/syscalls.h>
#include <fbl/string.h>
#include <usb/cdc.h>
#include <usb/usb.h>
#include <zxtest/zxtest.h>
namespace usb_virtual_bus {
namespace {
using usb_virtual::BusLauncher;
class UsbCdcAcmTest : public zxtest::Test {
public:
void SetUp() override {
auto bus = BusLauncher::Create();
ASSERT_OK(bus.status_value());
bus_ = std::move(bus.value());
ASSERT_NO_FATAL_FAILURE(InitUsbCdcAcm(devpath_));
}
void TearDown() override {
ASSERT_OK(bus_->ClearPeripheralDeviceFunctions());
ASSERT_OK(bus_->Disable());
}
protected:
// Initialize a USB CDC ACM device. Asserts on failure.
void InitUsbCdcAcm(fbl::String& devpath) {
namespace usb_peripheral = fuchsia_hardware_usb_peripheral;
using ConfigurationDescriptor =
::fidl::VectorView<fuchsia_hardware_usb_peripheral::wire::FunctionDescriptor>;
usb_peripheral::wire::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;
usb_peripheral::wire::FunctionDescriptor usb_cdc_acm_function_desc = {
.interface_class = USB_CLASS_COMM,
.interface_subclass = USB_CDC_SUBCLASS_ABSTRACT,
.interface_protocol = 0,
};
std::vector<usb_peripheral::wire::FunctionDescriptor> function_descs;
function_descs.push_back(usb_cdc_acm_function_desc);
std::vector<ConfigurationDescriptor> config_descs;
config_descs.emplace_back(
fidl::VectorView<usb_peripheral::wire::FunctionDescriptor>::FromExternal(function_descs));
ASSERT_OK(bus_->SetupPeripheralDevice(std::move(device_desc), std::move(config_descs)));
fbl::unique_fd fd;
ASSERT_OK(fdio_open_fd_at(bus_->GetRootFd(), "class/serial", 0, fd.reset_and_get_address()));
ASSERT_STATUS(fdio_watch_directory(fd.get(), WaitForAnyFile, ZX_TIME_INFINITE, &devpath),
ZX_ERR_STOP);
devpath = fbl::String::Concat({fbl::String("class/serial/"), devpath});
}
std::optional<BusLauncher> bus_;
fbl::String devpath_;
};
TEST_F(UsbCdcAcmTest, ReadAndWriteTest) {
zx::result result = component::ConnectAt<fuchsia_hardware_serial::DeviceProxy>(
fdio_cpp::UnownedFdioCaller(bus_->GetRootFd()).directory(), devpath_.c_str());
ASSERT_OK(result.status_value());
auto [client_end, server] = fidl::Endpoints<fuchsia_hardware_serial::Device>::Create();
ASSERT_OK(fidl::WireCall(result.value())->GetChannel(std::move(server)));
auto assert_read_with_timeout = [&client_end = client_end](cpp20::span<uint8_t> write_data) {
for (zx::time deadline = zx::deadline_after(zx::sec(5));
zx::clock::get_monotonic() < deadline;) {
const fidl::WireResult result = fidl::WireCall(client_end)->Read();
ASSERT_OK(result.status());
const fit::result response = result.value();
ASSERT_TRUE(response.is_ok(), "%s", zx_status_get_string(response.error_value()));
cpp20::span data = response.value()->data.get();
if (data.empty()) {
continue;
}
ASSERT_EQ(data.size_bytes(), write_data.size_bytes());
ASSERT_BYTES_EQ(data.data(), write_data.data(), write_data.size_bytes());
return;
}
FAIL("timed out");
};
{
uint8_t write_data[] = {1, 2, 3};
const fidl::WireResult result =
fidl::WireCall(client_end)->Write(fidl::VectorView<uint8_t>::FromExternal(write_data));
ASSERT_OK(result.status());
const fit::result response = result.value();
ASSERT_TRUE(response.is_ok(), "%s", zx_status_get_string(response.error_value()));
ASSERT_NO_FATAL_FAILURE(assert_read_with_timeout(cpp20::span(write_data)));
}
{
uint8_t write_data[] = {5, 4, 3, 2, 1};
const fidl::WireResult result =
fidl::WireCall(client_end)->Write(fidl::VectorView<uint8_t>::FromExternal(write_data));
ASSERT_OK(result.status());
const fit::result response = result.value();
ASSERT_TRUE(response.is_ok(), "%s", zx_status_get_string(response.error_value()));
ASSERT_NO_FATAL_FAILURE(assert_read_with_timeout(cpp20::span(write_data)));
}
{
// Writing just "0" to the fake USB driver will cause an empty response to be queued.
uint8_t write_data[] = {'0'};
const fidl::WireResult result =
fidl::WireCall(client_end)->Write(fidl::VectorView<uint8_t>::FromExternal(write_data));
ASSERT_OK(result.status());
const fit::result response = result.value();
ASSERT_TRUE(response.is_ok(), "%s", zx_status_get_string(response.error_value()));
for (zx::time deadline = zx::deadline_after(zx::sec(5));
zx::clock::get_monotonic() < deadline;) {
const fidl::WireResult result = fidl::WireCall(client_end)->Read();
ASSERT_OK(result.status());
const fit::result response = result.value();
ASSERT_TRUE(response.is_ok(), "%s", zx_status_get_string(response.error_value()));
cpp20::span data = response.value()->data.get();
ASSERT_TRUE(data.empty(), "%zu", data.size());
}
}
}
} // namespace
} // namespace usb_virtual_bus