blob: e7607b3dce58c0617fbc2c1bfb8be67d99354b9b [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 <fuchsia/hardware/usb/dci/cpp/banjo.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/async-loop/default.h>
#include <thread>
#include <zxtest/zxtest.h>
#include "src/devices/testing/mock-ddk/mock-device.h"
#include "src/devices/usb/drivers/usb-virtual-bus/usb-virtual-bus.h"
namespace usb_virtual_bus {
TEST(VirtualBusUnitTest, DdkLifecycle) {
async::Loop loop{&kAsyncLoopConfigNeverAttachToThread};
auto fake_parent = MockDevice::FakeRootParent();
auto bus = new UsbVirtualBus(fake_parent.get(), loop.dispatcher());
ASSERT_NOT_NULL(bus);
ASSERT_OK(bus->DdkAdd("usb-virtual-bus"));
ASSERT_EQ(1, fake_parent->child_count());
auto* child = fake_parent->GetLatestChild();
child->InitOp();
ASSERT_OK(child->WaitUntilInitReplyCalled());
EXPECT_TRUE(child->InitReplyCalled());
device_async_remove(bus->zxdev());
mock_ddk::ReleaseFlaggedDevices(fake_parent.get());
}
class FakeDci : public ddk::UsbDciInterfaceProtocol<FakeDci> {
public:
explicit FakeDci()
: UsbDciInterfaceProtocol(), protocol_{&usb_dci_interface_protocol_ops_, this} {}
usb_dci_interface_protocol_t* get_proto() { return &protocol_; }
// UsbDciInterface implementation.
// This will block until the test calls |CompleteControlRequest|.
zx_status_t UsbDciInterfaceControl(const usb_setup_t* setup, const uint8_t* write_buffer,
size_t write_size, uint8_t* out_read_buffer, size_t read_size,
size_t* out_read_actual) {
sync_completion_signal(&control_start_sync_);
sync_completion_wait(&control_complete_sync_, ZX_TIME_INFINITE);
return ZX_OK;
}
// Blocks until FakeDci has received the control request.
void WaitForControlRequestStart() {
sync_completion_wait(&control_start_sync_, ZX_TIME_INFINITE);
}
// Signals FakeDci to complete the control request.
void CompleteControlRequest() { sync_completion_signal(&control_complete_sync_); }
void UsbDciInterfaceSetConnected(bool connected) {}
void UsbDciInterfaceSetSpeed(usb_speed_t speed) {}
private:
usb_dci_interface_protocol_t protocol_;
sync_completion_t control_start_sync_;
sync_completion_t control_complete_sync_;
};
// Tests unbinding the usb virtual bus while a control request is in progress.
TEST(VirtualBusUnitTest, UnbindDuringControlRequest) {
async::Loop loop{&kAsyncLoopConfigNeverAttachToThread};
auto fake_parent = MockDevice::FakeRootParent();
auto bus = new UsbVirtualBus(fake_parent.get(), loop.dispatcher());
ASSERT_NOT_NULL(bus);
ASSERT_OK(bus->DdkAdd("usb-virtual-bus"));
ASSERT_EQ(1, fake_parent->child_count());
auto* child = fake_parent->GetLatestChild();
child->InitOp();
ASSERT_OK(child->WaitUntilInitReplyCalled());
EXPECT_TRUE(child->InitReplyCalled());
// This needs to be true, otherwise requests will fail to be queued.
bus->SetConnected(true);
FakeDci fake_dci;
ASSERT_OK(bus->UsbDciSetInterface(fake_dci.get_proto()));
// This will be signalled by the control request completion callback.
sync_completion_t usb_req_sync;
// Start the control request before unbinding the device.
// Do this in a new thread as it is a blocking operation, and we will not
// request it be completed until after we begin unbinding.
std::thread req_thread([&] {
usb_request_complete_callback_t callback = {
.callback =
[](void* ctx, usb_request_t* req) {
sync_completion_t* sync = static_cast<sync_completion_t*>(ctx);
sync_completion_signal(sync);
usb_request_release(req);
},
.ctx = &usb_req_sync,
};
size_t parent_req_size = bus->UsbHciGetRequestSize();
usb_request_t* fake_req;
ASSERT_OK(usb_request_alloc(&fake_req, zx_system_get_page_size(), 0 /* ep_address */,
parent_req_size));
bus->UsbHciRequestQueue(fake_req, &callback);
});
fake_dci.WaitForControlRequestStart();
// Request the device begin unbinding.
// This should wake up the worker thread, which will block until the control request completes.
child->UnbindOp();
fake_dci.CompleteControlRequest();
// Wait for the control request to complete.
sync_completion_wait(&usb_req_sync, ZX_TIME_INFINITE);
req_thread.join();
// Check that unbind has replied.
EXPECT_EQ(ZX_OK, child->WaitUntilUnbindReplyCalled());
EXPECT_TRUE(child->UnbindReplyCalled());
}
} // namespace usb_virtual_bus