blob: 39a27967343e91e8b2123199d878dc1cdd286ea3 [file] [log] [blame]
// Copyright 2022 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 "adb-function.h"
#include <fidl/fuchsia.hardware.adb/cpp/fidl.h>
#include <fuchsia/hardware/usb/function/cpp/banjo-mock.h>
#include <lib/async-loop/default.h>
#include <lib/async/default.h>
#include <lib/driver/outgoing/cpp/outgoing_directory.h>
#include <lib/driver/testing/cpp/driver_test.h>
#include <lib/sync/completion.h>
#include <map>
#include <vector>
#include <usb/usb-request.h>
#include <zxtest/zxtest.h>
#include "lib/driver/compat/cpp/banjo_server.h"
#include "src/devices/usb/lib/usb-endpoint/testing/fake-usb-endpoint-server.h"
bool operator==(const usb_request_complete_callback_t& lhs,
const usb_request_complete_callback_t& rhs) {
// Comparison of these struct is not useful. Return true always.
return true;
}
bool operator==(const usb_ss_ep_comp_descriptor_t& lhs, const usb_ss_ep_comp_descriptor_t& rhs) {
// Comparison of these struct is not useful. Return true always.
return true;
}
bool operator==(const usb_endpoint_descriptor_t& lhs, const usb_endpoint_descriptor_t& rhs) {
// Comparison of these struct is not useful. Return true always.
return true;
}
bool operator==(const usb_request_t& lhs, const usb_request_t& rhs) {
// Only comparing endpoint address. Use ExpectCallWithMatcher for more specific
// comparisons.
return lhs.header.ep_address == rhs.header.ep_address;
}
bool operator==(const usb_function_interface_protocol_t& lhs,
const usb_function_interface_protocol_t& rhs) {
// Comparison of these struct is not useful. Return true always.
return true;
}
namespace usb_adb_function {
static constexpr uint32_t kBulkOutEp = 1;
static constexpr uint32_t kBulkInEp = 2;
typedef struct {
usb_request_t* usb_request;
const usb_request_complete_callback_t* complete_cb;
} mock_usb_request_t;
class MockUsbFunction : public ddk::MockUsbFunction {
public:
zx_status_t UsbFunctionCancelAll(uint8_t ep_address) override {
while (!usb_request_queues[ep_address].empty()) {
const mock_usb_request_t r = usb_request_queues[ep_address].back();
r.complete_cb->callback(r.complete_cb->ctx, r.usb_request);
usb_request_queues[ep_address].pop_back();
}
return ddk::MockUsbFunction::UsbFunctionCancelAll(ep_address);
}
zx_status_t UsbFunctionSetInterface(const usb_function_interface_protocol_t* interface) override {
// Overriding method to store the interface passed.
function = *interface;
if (!on_set_interface_.empty()) {
on_set_interface_.front()(*this);
on_set_interface_.pop();
}
return ddk::MockUsbFunction::UsbFunctionSetInterface(interface);
}
zx_status_t UsbFunctionConfigEp(const usb_endpoint_descriptor_t* ep_desc,
const usb_ss_ep_comp_descriptor_t* ss_comp_desc) override {
// Overriding method to handle valid cases where nullptr is passed. The generated mock tries to
// dereference it without checking.
usb_endpoint_descriptor_t ep{};
usb_ss_ep_comp_descriptor_t ss{};
const usb_endpoint_descriptor_t* arg1 = ep_desc ? ep_desc : &ep;
const usb_ss_ep_comp_descriptor_t* arg2 = ss_comp_desc ? ss_comp_desc : &ss;
return ddk::MockUsbFunction::UsbFunctionConfigEp(arg1, arg2);
}
void UsbFunctionRequestQueue(usb_request_t* usb_request,
const usb_request_complete_callback_t* complete_cb) override {
// Override to store requests.
const uint8_t ep = usb_request->header.ep_address;
auto queue = usb_request_queues.find(ep);
if (queue == usb_request_queues.end()) {
usb_request_queues[ep] = {};
}
usb_request_queues[ep].push_back({usb_request, complete_cb});
mock_request_queue_.Call(*usb_request, *complete_cb);
}
compat::DeviceServer::BanjoConfig GetBanjoConfig() {
compat::DeviceServer::BanjoConfig config{.default_proto_id = ZX_PROTOCOL_USB_FUNCTION};
config.callbacks[ZX_PROTOCOL_USB_FUNCTION] = banjo_server.callback();
return config;
}
compat::BanjoServer banjo_server{ZX_PROTOCOL_USB_FUNCTION, this, GetProto()->ops};
usb_function_interface_protocol_t function;
// Store request queues for each endpoint.
std::map<uint8_t, std::vector<mock_usb_request_t>> usb_request_queues;
std::queue<fit::callback<void(MockUsbFunction&)>> on_set_interface_;
};
using FakeUsb = fake_usb_endpoint::FakeUsbFidlProvider<fuchsia_hardware_usb_function::UsbFunction,
fake_usb_endpoint::FakeEndpoint>;
class UsbAdbEnvironment : public fdf_testing::Environment {
public:
zx::result<> Serve(fdf::OutgoingDirectory& to_driver_vfs) override {
async_dispatcher_t* dispatcher = fdf::Dispatcher::GetCurrent()->async_dispatcher();
device_server_.Initialize("default", std::nullopt, mock_usb_.GetBanjoConfig());
EXPECT_EQ(ZX_OK, device_server_.Serve(dispatcher, &to_driver_vfs));
fuchsia_hardware_usb_function::UsbFunctionService::InstanceHandler handler({
.device = usb_function_bindings_.CreateHandler(&fake_dev_, dispatcher,
fidl::kIgnoreBindingClosure),
});
EXPECT_OK(to_driver_vfs.AddService<fuchsia_hardware_usb_function::UsbFunctionService>(
std::move(handler)));
return zx::ok();
}
// Call set_configured of usb adb to bring the interface online.
void EnableUsb() {
mock_usb_.ExpectConfigEp(ZX_OK, {}, {});
mock_usb_.ExpectConfigEp(ZX_OK, {}, {});
mock_usb_.function.ops->set_configured(mock_usb_.function.ctx, true, USB_SPEED_FULL);
}
void CancelAllUsbRxRequests() {
for (size_t i = 0; i < kBulkRxCount; i++) {
fake_dev_.fake_endpoint(kBulkOutEp).RequestComplete(ZX_ERR_CANCELED, 0);
}
}
// Expect that the driver will call SetInterface, and when it does so, call
// CancelAllUsbRxRequests.
//
// We call CancelAllUsbRxRequests only _after_ the driver calls SetInterface
// in order to avoid a race condition where we cancel a request, only to have
// the driver process the cancellation and send it back out again before
// `StopAdb()` gets processed.
void ExpectSetInterfaceAndCancelAllRxRequests() {
mock_usb_.ExpectSetInterface(ZX_OK, {});
mock_usb_.on_set_interface_.push(
[this](MockUsbFunction& mock_usb) { CancelAllUsbRxRequests(); });
}
compat::DeviceServer device_server_;
MockUsbFunction mock_usb_;
FakeUsb fake_dev_ = FakeUsb(fdf::Dispatcher::GetCurrent()->async_dispatcher());
fidl::ServerBindingGroup<fuchsia_hardware_usb_function::UsbFunction> usb_function_bindings_;
};
class UsbAdbTestConfig final {
public:
using DriverType = UsbAdbDevice;
using EnvironmentType = UsbAdbEnvironment;
};
class UsbAdbTest : public zxtest::Test {
public:
fidl::WireSyncClient<fadb::UsbAdbImpl> NormalStartAdb() {
auto [client_end, server_end] = fidl::Endpoints<fadb::UsbAdbImpl>::Create();
EXPECT_OK(client_->StartAdb(std::move(server_end)));
driver_test_.RunInEnvironmentTypeContext([](UsbAdbEnvironment& env) { env.EnableUsb(); });
return fidl::WireSyncClient<fadb::UsbAdbImpl>(std::move(client_end));
}
void NormalStopDriver() {
driver_test_.RunInEnvironmentTypeContext(
[](UsbAdbEnvironment& env) { env.ExpectSetInterfaceAndCancelAllRxRequests(); });
EXPECT_OK(driver_test_.StopDriver());
}
void SetUp() override {
// Expect calls from UsbAdbDevice initialization
driver_test_.RunInEnvironmentTypeContext([](UsbAdbEnvironment& env) {
env.mock_usb_.ExpectAllocInterface(ZX_OK, 1);
env.mock_usb_.ExpectAllocEp(ZX_OK, USB_DIR_OUT, kBulkOutEp);
env.mock_usb_.ExpectAllocEp(ZX_OK, USB_DIR_IN, kBulkInEp);
env.mock_usb_.ExpectSetInterface(ZX_OK, {});
env.fake_dev_.ExpectConnectToEndpoint(kBulkOutEp);
env.fake_dev_.ExpectConnectToEndpoint(kBulkInEp);
});
ASSERT_OK(driver_test_.StartDriver().status_value());
auto device = driver_test_.Connect<fadb::Service::Adb>();
EXPECT_OK(device.status_value());
client_.Bind(std::move(device.value()));
}
void TearDown() override {
driver_test_.RunInEnvironmentTypeContext(
[](UsbAdbEnvironment& env) { env.mock_usb_.VerifyAndClear(); });
}
void SendTestData(fidl::WireSyncClient<fadb::UsbAdbImpl>& usb_impl, size_t size) {
uint8_t test_data[size];
driver_test_.RunInEnvironmentTypeContext([&](UsbAdbEnvironment& env) {
for (uint32_t i = 0; i < sizeof(test_data) / kVmoDataSize; i++) {
env.fake_dev_.fake_endpoint(kBulkInEp).RequestComplete(ZX_OK, kVmoDataSize);
}
if (sizeof(test_data) % kVmoDataSize) {
env.fake_dev_.fake_endpoint(kBulkInEp).RequestComplete(ZX_OK,
sizeof(test_data) % kVmoDataSize);
}
});
auto result =
usb_impl->QueueTx(fidl::VectorView<uint8_t>::FromExternal(test_data, sizeof(test_data)));
ASSERT_TRUE(result.ok());
ASSERT_TRUE(result->is_ok());
driver_test_.RunInEnvironmentTypeContext([&](UsbAdbEnvironment& env) {
EXPECT_EQ(env.fake_dev_.fake_endpoint(kBulkInEp).pending_request_count(), 0);
});
}
void ExpectReceiveData(size_t size) {
// Invoke request completion on bulk out endpoint.
driver_test_.RunInEnvironmentTypeContext([&](UsbAdbEnvironment& env) {
env.fake_dev_.fake_endpoint(kBulkOutEp).RequestComplete(ZX_OK, size);
});
}
fdf_testing::BackgroundDriverTest<UsbAdbTestConfig> driver_test_;
fidl::WireSyncClient<fadb::Device> client_;
};
class EventHandler : public fidl::WireSyncEventHandler<fadb::UsbAdbImpl> {
public:
~EventHandler() { EXPECT_TRUE(expected_statuses_.empty()); }
void OnStatusChanged(fidl::WireEvent<fadb::UsbAdbImpl::OnStatusChanged>* event) override {
ASSERT_FALSE(expected_statuses_.empty());
EXPECT_EQ(event->status, expected_statuses_.front());
expected_statuses_.pop();
}
std::queue<fadb::StatusFlags> expected_statuses_;
};
TEST_F(UsbAdbTest, StopBeforeUsbStartsUp) {
// Expect disconnect.
driver_test_.RunInEnvironmentTypeContext(
[](UsbAdbEnvironment& env) { env.mock_usb_.ExpectSetInterface(ZX_OK, {}); });
EXPECT_OK(driver_test_.StopDriver());
}
TEST_F(UsbAdbTest, StartStop) {
auto [client_end, server_end] = fidl::Endpoints<fadb::UsbAdbImpl>::Create();
EXPECT_OK(client_->StartAdb(std::move(server_end)));
auto usb_impl = fidl::WireSyncClient<fadb::UsbAdbImpl>(std::move(client_end));
EventHandler handler;
// TODO(https://fxbug.dev/398918059): Enable this assertion when
// HandleOneEvent supports a deadline.
//
// We don't expect an "online" event until after USB comes up.
// EXPECT_EQ(usb_impl.HandleOneEvent(handler, zx::deadline_after(zx::msec(1))).status(),
// ZX_ERR_TIMED_OUT);
driver_test_.RunInEnvironmentTypeContext([](UsbAdbEnvironment& env) { env.EnableUsb(); });
// Now we should get the event.
handler.expected_statuses_.push(fadb::StatusFlags::kOnline);
EXPECT_OK(usb_impl.HandleOneEvent(handler));
libsync::Completion stop_requested;
driver_test_.RunInEnvironmentTypeContext([&](UsbAdbEnvironment& env) {
env.mock_usb_.ExpectSetInterface(ZX_OK, {});
env.mock_usb_.on_set_interface_.emplace(
[&stop_requested](MockUsbFunction& mock_usb) { stop_requested.Signal(); });
});
// Request a USB reset.
libsync::Completion stop_finished;
std::thread t([&]() {
EXPECT_OK(client_->StopAdb());
stop_finished.Signal();
});
// TODO(https://fxbug.dev/398918059): Enable this assertion when
// HandleOneEvent supports a deadline.
//
// We don't expect an "offline" event or for StopAdb to complete until USB is shut down.
// EXPECT_EQ(usb_impl.HandleOneEvent(handler, zx::deadline_after(zx::msec(1))).status(),
// ZX_ERR_TIMED_OUT);
EXPECT_EQ(stop_finished.Wait(zx::deadline_after(zx::msec(1))), ZX_ERR_TIMED_OUT);
// We call CancelAllUsbRxRequests only _after_ the driver calls SetInterface
// in order to avoid a race condition where we cancel a request, only to have
// the driver process the cancellation and send it back out again before
// `StopAdb()` gets processed.
stop_requested.Wait();
driver_test_.RunInEnvironmentTypeContext([&](UsbAdbEnvironment& env) {
env.CancelAllUsbRxRequests();
env.mock_usb_.ExpectSetInterface(ZX_OK, {});
});
handler.expected_statuses_.emplace(0);
EXPECT_OK(usb_impl.HandleOneEvent(handler));
EXPECT_EQ(usb_impl.HandleOneEvent(handler).status(), ZX_ERR_PEER_CLOSED);
stop_finished.Wait();
t.join();
driver_test_.RunInEnvironmentTypeContext(
[](UsbAdbEnvironment& env) { env.mock_usb_.ExpectSetInterface(ZX_OK, {}); });
EXPECT_OK(driver_test_.StopDriver());
}
TEST_F(UsbAdbTest, StopDriverWhileConnected) {
auto usb_impl = NormalStartAdb();
EventHandler handler;
handler.expected_statuses_.emplace(fadb::StatusFlags::kOnline);
EXPECT_OK(usb_impl.HandleOneEvent(handler));
driver_test_.RunInEnvironmentTypeContext(
[&](UsbAdbEnvironment& env) { env.ExpectSetInterfaceAndCancelAllRxRequests(); });
EXPECT_OK(driver_test_.StopDriver());
handler.expected_statuses_.emplace(0);
EXPECT_OK(usb_impl.HandleOneEvent(handler));
}
TEST_F(UsbAdbTest, UsbStackRequestsStop) {
auto usb_impl = NormalStartAdb();
EventHandler handler;
handler.expected_statuses_.emplace(fadb::StatusFlags::kOnline);
EXPECT_OK(usb_impl.HandleOneEvent(handler));
driver_test_.RunInEnvironmentTypeContext([](UsbAdbEnvironment& env) {
// After we call SetConfigured(), the driver will request USB stop and then
// start.
env.mock_usb_.ExpectSetInterface(ZX_OK, {});
env.mock_usb_.ExpectSetInterface(ZX_OK, {});
env.mock_usb_.function.ops->set_configured(env.mock_usb_.function.ctx, false, 0);
env.CancelAllUsbRxRequests();
});
handler.expected_statuses_.emplace(0);
EXPECT_OK(usb_impl.HandleOneEvent(handler));
driver_test_.RunInEnvironmentTypeContext(
[](UsbAdbEnvironment& env) { env.mock_usb_.ExpectSetInterface(ZX_OK, {}); });
EXPECT_OK(driver_test_.StopDriver());
}
TEST_F(UsbAdbTest, StartStopStartStop) {
{
EventHandler handler;
auto usb_impl = NormalStartAdb();
handler.expected_statuses_.push(fadb::StatusFlags::kOnline);
EXPECT_OK(usb_impl.HandleOneEvent(handler));
driver_test_.RunInEnvironmentTypeContext([](UsbAdbEnvironment& env) {
env.ExpectSetInterfaceAndCancelAllRxRequests();
env.mock_usb_.ExpectSetInterface(ZX_OK, {});
});
EXPECT_OK(client_->StopAdb());
handler.expected_statuses_.emplace(0);
EXPECT_OK(usb_impl.HandleOneEvent(handler));
EXPECT_EQ(usb_impl.HandleOneEvent(handler).status(), ZX_ERR_PEER_CLOSED);
}
{
EventHandler handler;
auto usb_impl = NormalStartAdb();
handler.expected_statuses_.push(fadb::StatusFlags::kOnline);
EXPECT_OK(usb_impl.HandleOneEvent(handler));
driver_test_.RunInEnvironmentTypeContext([](UsbAdbEnvironment& env) {
env.ExpectSetInterfaceAndCancelAllRxRequests();
env.mock_usb_.ExpectSetInterface(ZX_OK, {});
});
EXPECT_OK(client_->StopAdb());
handler.expected_statuses_.emplace(0);
EXPECT_OK(usb_impl.HandleOneEvent(handler));
EXPECT_EQ(usb_impl.HandleOneEvent(handler).status(), ZX_ERR_PEER_CLOSED);
}
driver_test_.RunInEnvironmentTypeContext(
[](UsbAdbEnvironment& env) { env.mock_usb_.ExpectSetInterface(ZX_OK, {}); });
EXPECT_OK(driver_test_.StopDriver());
}
TEST_F(UsbAdbTest, StartAdbAfterUsbConnectionEstablished) {
driver_test_.RunInEnvironmentTypeContext([](UsbAdbEnvironment& env) { env.EnableUsb(); });
auto [client_end, server_end] = fidl::Endpoints<fadb::UsbAdbImpl>::Create();
EXPECT_OK(client_->StartAdb(std::move(server_end)));
auto usb_impl = fidl::WireSyncClient<fadb::UsbAdbImpl>(std::move(client_end));
// We should get kOnline immediately, because we're already connected.
EventHandler handler;
handler.expected_statuses_.push(fadb::StatusFlags::kOnline);
EXPECT_OK(usb_impl.HandleOneEvent(handler));
ASSERT_NO_FATAL_FAILURE(NormalStopDriver());
}
TEST_F(UsbAdbTest, SendAdbMessage) {
auto usb_impl = NormalStartAdb();
// Sending data that fits within a single VMO request
ASSERT_NO_FATAL_FAILURE(SendTestData(usb_impl, kVmoDataSize - 2));
// Sending data that is exactly fills up a single VMO request
ASSERT_NO_FATAL_FAILURE(SendTestData(usb_impl, kVmoDataSize));
// Sending data that exceeds a single VMO request
ASSERT_NO_FATAL_FAILURE(SendTestData(usb_impl, kVmoDataSize + 2));
// Sending data that exceeds kBulkTxRxCount VMO requests (the last packet should be stored in
// queue)
ASSERT_NO_FATAL_FAILURE(SendTestData(usb_impl, kVmoDataSize * kBulkTxCount + 2));
// Sending data that exceeds kBulkTxRxCount + 1 VMO requests (probably unneeded test, but added
// for good measure.)
ASSERT_NO_FATAL_FAILURE(SendTestData(usb_impl, kVmoDataSize * (kBulkTxCount + 1) + 2));
ASSERT_NO_FATAL_FAILURE(NormalStopDriver());
}
TEST_F(UsbAdbTest, RecvAdbMessage) {
constexpr uint32_t kReceiveSize = kVmoDataSize - 2;
auto usb_impl = NormalStartAdb();
// Queue a receive request before the data is available. The request will not get an immediate
// reply. Data fits within a single VMO request.
std::thread t([&]() {
auto response = usb_impl->Receive();
ASSERT_OK(response.status());
ASSERT_EQ(response.value().value()->data.count(), kReceiveSize);
});
// Wait to make it so (most likely) the `Receive` request arrives first. This is
// just a test coverage thing - it won't flake if the `ExpectReceiveData`
// happens first.
zx::nanosleep(zx::deadline_after(zx::msec(1)));
ASSERT_NO_FATAL_FAILURE(ExpectReceiveData(kReceiveSize));
t.join();
ASSERT_NO_FATAL_FAILURE(NormalStopDriver());
}
} // namespace usb_adb_function