blob: 6839128cf9a70ea9125227ec9019bc631f9e6866 [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 <lib/fake_ddk/fake_ddk.h>
#include <thread>
#include <ddktl/protocol/usb/dci.h>
#include <zxtest/zxtest.h>
#include "usb-virtual-bus.h"
namespace usb_virtual_bus {
TEST(VirtualBusUnitTest, DdkLifecycle) {
fake_ddk::Bind ddk;
auto bus = new UsbVirtualBus(fake_ddk::kFakeParent);
ASSERT_NOT_NULL(bus);
ASSERT_OK(bus->DdkAdd("usb-virtual-bus"));
ASSERT_OK(ddk.WaitUntilInitComplete());
bus->DdkAsyncRemove();
// Check that unbind has replied.
ASSERT_OK(ddk.WaitUntilRemove());
ASSERT_TRUE(ddk.Ok());
// This will join with the device thread and delete the bus object.
bus->DdkRelease();
}
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 void* write_buffer,
size_t write_size, void* 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) {
fake_ddk::Bind ddk;
auto bus = new UsbVirtualBus(fake_ddk::kFakeParent);
ASSERT_NOT_NULL(bus);
ASSERT_OK(bus->DdkAdd("usb-virtual-bus"));
ASSERT_OK(ddk.WaitUntilInitComplete());
// 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_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, 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.
bus->DdkUnbind(ddk::UnbindTxn(fake_ddk::kFakeDevice));
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.
ASSERT_OK(ddk.WaitUntilRemove());
ASSERT_TRUE(ddk.Ok());
// This will join with the device thread and delete the bus object.
bus->DdkRelease();
}
} // namespace usb_virtual_bus