blob: 4d0f43cded3b470392817653e3bb05aa17589abd [file] [log] [blame]
// Copyright 2020 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 "src/devices/usb/drivers/usb-peripheral/usb-peripheral.h"
#include <fidl/fuchsia.hardware.usb.dci/cpp/wire.h>
#include <fidl/fuchsia.hardware.usb.descriptor/cpp/wire.h>
#include <fuchsia/hardware/usb/dci/c/banjo.h>
#include <fuchsia/hardware/usb/dci/cpp/banjo.h>
#include <lib/async_patterns/testing/cpp/dispatcher_bound.h>
#include <lib/component/outgoing/cpp/outgoing_directory.h>
#include <lib/driver/testing/cpp/driver_runtime.h>
#include <lib/driver/testing/cpp/driver_test.h>
#include <lib/zx/clock.h>
#include <lib/zx/interrupt.h>
#include <zircon/errors.h>
#include <zircon/syscalls.h>
#include <cstring>
#include <memory>
#include <vector>
#include <gtest/gtest.h>
#include <usb/usb.h>
#include "src/devices/usb/drivers/usb-peripheral/usb-function.h"
#include "src/lib/testing/predicates/status.h"
namespace fdci = fuchsia_hardware_usb_dci;
namespace fdescriptor = fuchsia_hardware_usb_descriptor;
namespace usb_peripheral::test {
class FakeDevice : public ddk::UsbDciProtocol<FakeDevice>, public fidl::WireServer<fdci::UsbDci> {
public:
FakeDevice() : proto_({&usb_dci_protocol_ops_, this}) {}
fdci::UsbDciService::InstanceHandler GetHandler() {
return fdci::UsbDciService::InstanceHandler(
{.device = bindings_.CreateHandler(this, fdf::Dispatcher::GetCurrent()->async_dispatcher(),
fidl::kIgnoreBindingClosure)});
}
// USB DCI protocol implementation (No longer used).
void UsbDciRequestQueue(usb_request_t* req, const usb_request_complete_callback_t* cb) {}
zx_status_t UsbDciSetInterface(const usb_dci_interface_protocol_t* interface) {
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t UsbDciConfigEp(const usb_endpoint_descriptor_t* ep_desc,
const usb_ss_ep_comp_descriptor_t* ss_comp_desc) {
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t UsbDciDisableEp(uint8_t ep_address) { return ZX_ERR_NOT_SUPPORTED; }
zx_status_t UsbDciEpSetStall(uint8_t ep_address) { return ZX_ERR_NOT_SUPPORTED; }
zx_status_t UsbDciEpClearStall(uint8_t ep_address) { return ZX_ERR_NOT_SUPPORTED; }
size_t UsbDciGetRequestSize() { return sizeof(usb_request_t); }
zx_status_t UsbDciCancelAll(uint8_t ep_address) { return ZX_OK; }
// fuchsia_hardware_usb_dci::UsbDci protocol.
void ConnectToEndpoint(ConnectToEndpointRequestView req,
ConnectToEndpointCompleter::Sync& completer) override {
completer.ReplyError(ZX_ERR_NOT_SUPPORTED);
}
void SetInterface(SetInterfaceRequestView req, SetInterfaceCompleter::Sync& completer) override {
fidl::Arena arena;
client_.emplace(std::move(req->interface));
completer.buffer(arena).ReplySuccess();
sync_completion_signal(&set_interface_called_);
}
void StartController(StartControllerCompleter::Sync& completer) override {
completer.ReplySuccess();
}
void StopController(StopControllerCompleter::Sync& completer) override {
completer.ReplySuccess();
}
void ConfigureEndpoint(ConfigureEndpointRequestView req,
ConfigureEndpointCompleter::Sync& completer) override {
completer.ReplyError(ZX_ERR_NOT_SUPPORTED);
}
void DisableEndpoint(DisableEndpointRequestView req,
DisableEndpointCompleter::Sync& completer) override {
completer.ReplyError(ZX_ERR_NOT_SUPPORTED);
}
void EndpointSetStall(EndpointSetStallRequestView req,
EndpointSetStallCompleter::Sync& completer) override {
completer.ReplyError(ZX_ERR_NOT_SUPPORTED);
}
void EndpointClearStall(EndpointClearStallRequestView req,
EndpointClearStallCompleter::Sync& completer) override {
completer.ReplyError(ZX_ERR_NOT_SUPPORTED);
}
void CancelAll(CancelAllRequestView req, CancelAllCompleter::Sync& completer) override {
completer.ReplyError(ZX_ERR_NOT_SUPPORTED);
}
void handle_unknown_method(fidl::UnknownMethodMetadata<fuchsia_hardware_usb_dci::UsbDci> metadata,
fidl::UnknownMethodCompleter::Sync& completer) override {}
usb_dci_protocol_t* proto() { return &proto_; }
fidl::ClientEnd<fdci::UsbDciInterface> TakeClient() {
sync_completion_wait(&set_interface_called_, ZX_TIME_INFINITE);
auto client = std::move(client_);
EXPECT_TRUE(client.has_value());
return std::move(client.value());
}
compat::DeviceServer::BanjoConfig GetBanjoConfig() {
compat::DeviceServer::BanjoConfig config{ZX_PROTOCOL_USB_DCI};
config.callbacks[ZX_PROTOCOL_USB_DCI] = banjo_server_.callback();
return config;
}
private:
usb_dci_protocol_t proto_;
sync_completion_t set_interface_called_;
fidl::ServerBindingGroup<fdci::UsbDci> bindings_;
std::optional<fidl::ClientEnd<fdci::UsbDciInterface>> client_;
compat::BanjoServer banjo_server_{ZX_PROTOCOL_USB_DCI, this, &usb_dci_protocol_ops_};
};
class UsbPeripheralTestEnvironment : public fdf_testing::Environment {
public:
void Init(std::string_view serial_number) {
fuchsia_boot_metadata::SerialNumberMetadata metadata{{.serial_number{serial_number}}};
ASSERT_OK(serial_number_metadata_server_.SetMetadata(metadata));
device_server_.Initialize("default", std::nullopt, dci_.GetBanjoConfig());
}
zx::result<> Serve(fdf::OutgoingDirectory& to_driver_vfs) override {
if (zx_status_t status =
device_server_.Serve(fdf::Dispatcher::GetCurrent()->async_dispatcher(), &to_driver_vfs);
status != ZX_OK) {
return zx::error(status);
}
if (zx::result result = serial_number_metadata_server_.Serve(
to_driver_vfs, fdf::Dispatcher::GetCurrent()->async_dispatcher());
result.is_error()) {
return result.take_error();
}
if (zx::result result = to_driver_vfs.AddService<fdci::UsbDciService>(dci_.GetHandler());
result.is_error()) {
return result.take_error();
}
return zx::ok();
}
fidl::ClientEnd<fdci::UsbDciInterface> TakeDciClient() { return dci_.TakeClient(); }
private:
FakeDevice dci_;
fdf_metadata::MetadataServer<fuchsia_boot_metadata::SerialNumberMetadata>
serial_number_metadata_server_;
compat::DeviceServer device_server_;
};
class UsbPeripheralTestConfig {
public:
using DriverType = UsbPeripheral;
using EnvironmentType = UsbPeripheralTestEnvironment;
};
class UsbPeripheralHarness : public ::testing::Test {
public:
void SetUp() override {
driver_test_.RunInEnvironmentTypeContext([&](auto& env) { env.Init(kSerialNumber); });
ASSERT_OK(driver_test_.StartDriverWithCustomStartArgs([](auto& start_args) {
start_args.config().emplace(usb_peripheral_config::Config{}.ToVmo());
}));
dci_.Bind(driver_test_.RunInEnvironmentTypeContext<fidl::ClientEnd<fdci::UsbDciInterface>>(
[](auto& env) { return env.TakeDciClient(); }));
}
void TearDown() override { ASSERT_OK(driver_test_.StopDriver()); }
protected:
static constexpr std::string_view kSerialNumber = "Test serial number";
fidl::WireSyncClient<fdci::UsbDciInterface>& dci() { return dci_; }
private:
fidl::WireSyncClient<fdci::UsbDciInterface> dci_;
fdf_testing::BackgroundDriverTest<UsbPeripheralTestConfig> driver_test_;
};
TEST_F(UsbPeripheralHarness, AddsCorrectSerialNumberMetadata) {
fdescriptor::wire::UsbSetup setup;
setup.w_length = 256;
setup.w_value = 0x3 | (USB_DT_STRING << 8);
setup.bm_request_type = USB_DIR_IN | USB_RECIP_DEVICE | USB_TYPE_STANDARD;
setup.b_request = USB_REQ_GET_DESCRIPTOR;
fidl::Arena arena;
std::vector<uint8_t> unused;
auto result =
dci().buffer(arena)->Control(setup, fidl::VectorView<uint8_t>::FromExternal(unused));
ASSERT_TRUE(result->is_ok());
auto& serial = result.value()->read;
EXPECT_EQ(serial[0], (kSerialNumber.size() + 1) * 2);
EXPECT_EQ(serial[1], USB_DT_STRING);
for (size_t i = 0; i < sizeof(kSerialNumber) - 1; i++) {
EXPECT_EQ(serial[2 + (i * 2)], kSerialNumber[i]);
}
}
TEST_F(UsbPeripheralHarness, WorksWithVendorSpecificCommandWhenConfigurationIsZero) {
fdescriptor::wire::UsbSetup setup;
setup.w_length = 256;
setup.w_value = 0x3 | (USB_DT_STRING << 8);
setup.bm_request_type = USB_DIR_IN | USB_RECIP_DEVICE | USB_TYPE_VENDOR;
setup.b_request = USB_REQ_GET_DESCRIPTOR;
fidl::Arena arena;
std::vector<uint8_t> unused;
auto result =
dci().buffer(arena)->Control(setup, fidl::VectorView<uint8_t>::FromExternal(unused));
ASSERT_TRUE(result->is_error());
ASSERT_EQ(ZX_ERR_BAD_STATE, result->error_value());
}
} // namespace usb_peripheral::test