blob: f74a75f390389caf9ac3f8061d64dfca0d26efad [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 "usb-hub.h"
#include <lib/async-loop/cpp/loop.h>
#include <lib/async-loop/loop.h>
#include <lib/async/cpp/executor.h>
#include <lib/async/cpp/task.h>
#include <lib/fake_ddk/fake_ddk.h>
#include <lib/fit/function.h>
#include <lib/sync/completion.h>
#include <lib/synchronous-executor/executor.h>
#include <lib/zx/clock.h>
#include <lib/zx/time.h>
#include <string.h>
#include <unistd.h>
#include <zircon/compiler.h>
#include <zircon/errors.h>
#include <zircon/hw/usb.h>
#include <zircon/hw/usb/hub.h>
#include <zircon/process.h>
#include <zircon/time.h>
#include <array>
#include <atomic>
#include <cstdio>
#include <cstring>
#include <functional>
#include <memory>
#include <thread>
#include <variant>
#include <ddk/protocol/usb/hub.h>
#include <ddk/protocol/usb/request.h>
#include <ddktl/device.h>
#include <ddktl/protocol/usb.h>
#include <ddktl/protocol/usb/bus.h>
#include <ddktl/protocol/usb/hub.h>
#include <fbl/array.h>
#include <fbl/auto_call.h>
#include <fbl/auto_lock.h>
#include <fbl/condition_variable.h>
#include <fbl/intrusive_double_list.h>
#include <fbl/mutex.h>
#include <fbl/null_lock.h>
#include <fbl/ref_ptr.h>
#include <fbl/span.h>
#include <fbl/string.h>
#include <usb/request-cpp.h>
#include <usb/usb-request.h>
#include <zxtest/zxtest.h>
#include "fake-device.h"
namespace {
template <EmulationMode mode>
class UsbHarness : public zxtest::Test {
public:
void SetUp() override {
device_.emplace(mode);
usb_hub::UsbHubDevice::Bind(nullptr, reinterpret_cast<zx_device_t*>(&device_.value()));
device_->RunInit();
ASSERT_TRUE(device_->HasOps());
auto& queue = device_->GetStateChangeQueue();
bool powered_on = false;
bool initialized = false;
while (!(powered_on && initialized)) {
auto entry = queue.Wait();
switch (entry->type) {
case OperationType::kPowerOnEvent:
powered_on = true;
break;
case OperationType::kInitCompleteEvent:
initialized = true;
break;
default:
abort();
}
}
}
auto StartDispatching() {
dispatching_ = true;
device_->GetStateChangeQueue().StartThread(fit::bind_member(this, &UsbHarness::DispatchThread));
return fbl::AutoCall([this]() { StopDispatching(); });
}
void SetConnectCallback(fit::function<zx_status_t(uint32_t port, usb_speed_t speed)> callback) {
connect_callback_ = std::move(callback);
}
void DispatchThread() {
while (dispatching_) {
auto entry = device_->GetStateChangeQueue().Wait();
switch (entry->type) {
case OperationType::kUsbBusDeviceAdded:
entry->status = connect_callback_(entry->port, entry->speed);
Complete(std::move(entry));
break;
case OperationType::kUsbBusDeviceRemoved:
entry->status = connect_callback_(entry->port, -1);
Complete(std::move(entry));
break;
case OperationType::kExitEventLoop:
return;
default:
abort();
}
}
}
void ConnectDevice(uint8_t port, usb_speed_t speed) { device_->ConnectDevice(port, speed); }
void DisconnectDevice(uint8_t port) { device_->DisconnectDevice(port); }
zx_status_t ResetPort(uint8_t port) { return device_->ResetPort(port); }
bool ResetPending(uint8_t port) { return device_->ResetPending(port); }
void Interrupt() { device_->Interrupt(); }
usb_hub::UsbHubDevice* device() { return device_->device(); }
void TearDown() override {
device_->Unplug();
device_->Unbind();
device_->Release();
ASSERT_FALSE(device_->HasOps());
}
private:
void StopDispatching() {
dispatching_ = false;
device_->GetStateChangeQueue().Insert(MakeSyncEntry(OperationType::kExitEventLoop));
device_->GetStateChangeQueue().Join();
}
std::atomic_bool dispatching_ = false;
std::optional<FakeDevice> device_;
fit::function<zx_status_t(uint32_t port, usb_speed_t speed)> connect_callback_;
};
class SyntheticHarness : public zxtest::Test {
public:
void SetUp() override {
auto executor = std::make_unique<synchronous_executor::synchronous_executor>();
executor_ = executor.get();
device_.emplace(EmulationMode::Smays);
device_->SetSynthetic(true);
usb_hub::UsbHubDevice::Bind(std::move(executor),
reinterpret_cast<zx_device_t*>(&device_.value()));
ASSERT_TRUE(device_->HasOps());
}
void SetRequestCallback(fit::function<void(usb_request_t*, usb_request_complete_t)> callback) {
device_->SetRequestCallback(std::move(callback));
}
usb_hub::UsbHubDevice* device() { return device_->device(); }
zx_status_t RunSynchronously(fit::promise<void, zx_status_t> promise) {
bool ran = false;
zx_status_t status = ZX_OK;
executor_->schedule_task(std::move(promise).then([&](fit::result<void, zx_status_t>& result) {
ran = true;
if (result.is_error()) {
status = result.error();
}
return result;
}));
RunLoop();
if (!ran) {
status = ZX_ERR_INTERNAL;
}
return status;
}
void RunLoop() { executor_->run_until_idle(); }
void TearDown() override {
device_->Release();
ASSERT_FALSE(device_->HasOps());
}
private:
synchronous_executor::synchronous_executor* executor_;
std::optional<FakeDevice> device_;
};
class SmaysHarness : public UsbHarness<EmulationMode::Smays> {
public:
};
class UnbrandedHarness : public UsbHarness<EmulationMode::Unbranded> {
public:
};
class Binder : public fake_ddk::Bind {
public:
zx_status_t DeviceGetProtocol(const zx_device_t* device, uint32_t proto_id,
void* protocol) override {
auto context = const_cast<FakeDevice*>(reinterpret_cast<const FakeDevice*>(device));
return context->GetProtocol(proto_id, protocol);
}
zx_status_t DeviceRemove(zx_device_t* device) override { return ZX_OK; }
void DeviceInitReply(zx_device_t* device, zx_status_t status,
const device_init_reply_args_t* args) override {
auto context = const_cast<FakeDevice*>(reinterpret_cast<const FakeDevice*>(device));
context->InitComplete();
}
void DeviceUnbindReply(zx_device_t* device) override {
auto context = const_cast<FakeDevice*>(reinterpret_cast<const FakeDevice*>(device));
context->NotifyRemoved();
}
zx_status_t DeviceAdd(zx_driver_t* drv, zx_device_t* parent, device_add_args_t* args,
zx_device_t** out) override {
auto context = reinterpret_cast<FakeDevice*>(parent);
parent_ = context;
context->SetOpTable(args->ops, args->ctx);
if (context->IsSynthetic()) {
return ZX_OK;
}
// zx_status_t status = fake_ddk::Bind::DeviceAdd(drv, parent, args, out);
*out = parent;
return ZX_OK;
}
private:
FakeDevice* parent_;
};
static Binder bind;
TEST_F(SmaysHarness, Usb2Hub) {
auto dispatcher = StartDispatching();
// Enumeration might not happen in port order.
// See USB 2.0 specification revision 2.0 section 9.1.2.
sync_completion_t enum_complete;
uint8_t port_bitmask = 7;
usb_speed_t speeds[] = {USB_SPEED_HIGH, USB_SPEED_LOW, USB_SPEED_FULL};
SetConnectCallback([&enum_complete, &speeds, &port_bitmask](uint32_t port, usb_speed_t speed) {
ZX_ASSERT((speed - 1) < std::size(speeds));
ZX_ASSERT(speed < sizeof(int));
if ((speed != speeds[port - 1])) {
return ZX_ERR_INVALID_ARGS;
}
if (!(port_bitmask & (1 << (port - 1)))) {
return ZX_ERR_INVALID_ARGS;
}
port_bitmask &= ~(1 << (port - 1));
if (!port_bitmask) {
sync_completion_signal(&enum_complete);
}
return ZX_OK;
});
ConnectDevice(0, USB_SPEED_HIGH);
ConnectDevice(1, USB_SPEED_LOW);
ConnectDevice(2, USB_SPEED_FULL);
Interrupt();
sync_completion_wait(&enum_complete, ZX_TIME_INFINITE);
sync_completion_t disconnect_complete;
// Disconnect ordering doesn't matter (can happen in any order).
uint8_t port_remove_bitmask = 7;
SetConnectCallback([&](uint32_t port, usb_speed_t speed) {
if ((speed != static_cast<usb_speed_t>(-1))) {
return ZX_ERR_INVALID_ARGS;
}
port_remove_bitmask &= ~(1 << (port - 1));
if (port_remove_bitmask == 0) {
sync_completion_signal(&disconnect_complete);
}
return ZX_OK;
});
DisconnectDevice(0);
DisconnectDevice(1);
DisconnectDevice(2);
Interrupt();
sync_completion_wait(&disconnect_complete, ZX_TIME_INFINITE);
ASSERT_OK(ResetPort(1));
ASSERT_TRUE(ResetPending(1));
}
TEST_F(UnbrandedHarness, Usb3Hub) {
auto dispatcher = StartDispatching();
sync_completion_t enum_complete;
sync_completion_reset(&enum_complete);
uint8_t port_bitmask = 7;
SetConnectCallback([&](uint32_t port, usb_speed_t speed) {
if (speed != USB_SPEED_SUPER) {
return ZX_ERR_INVALID_ARGS;
}
if (!(port_bitmask & (1 << (port - 1)))) {
return ZX_ERR_INVALID_ARGS;
}
port_bitmask &= ~(1 << (port - 1));
if (!port_bitmask) {
sync_completion_signal(&enum_complete);
}
return ZX_OK;
});
ConnectDevice(0, USB_SPEED_SUPER);
ConnectDevice(1, USB_SPEED_SUPER);
ConnectDevice(2, USB_SPEED_SUPER);
Interrupt();
sync_completion_wait(&enum_complete, ZX_TIME_INFINITE);
sync_completion_t disconnect_complete;
// Disconnect ordering doesn't matter (can happen in any order).
uint8_t port_remove_bitmask = 7;
SetConnectCallback([&](uint32_t port, usb_speed_t speed) {
if ((speed != static_cast<usb_speed_t>(-1))) {
return ZX_ERR_INVALID_ARGS;
}
port_remove_bitmask &= ~(1 << (port - 1));
if (port_remove_bitmask == 0) {
sync_completion_signal(&disconnect_complete);
}
return ZX_OK;
});
DisconnectDevice(0);
DisconnectDevice(1);
DisconnectDevice(2);
Interrupt();
sync_completion_wait(&disconnect_complete, ZX_TIME_INFINITE);
ASSERT_OK(ResetPort(1));
ASSERT_TRUE(ResetPending(1));
}
TEST_F(SmaysHarness, Timeout) {
auto dev = device();
zx::time start = zx::clock::get_monotonic();
zx::time timeout = zx::deadline_after(zx::msec(30));
bool ran = false;
ASSERT_OK(dev->RunSynchronously(dev->Sleep(timeout).and_then([&]() {
ASSERT_GT((zx::clock::get_monotonic() - start).to_msecs(), 29);
ran = true;
})));
ASSERT_TRUE(ran);
}
TEST_F(SyntheticHarness, SetFeature) {
auto dev = device();
bool ran = false;
SetRequestCallback([&](usb_request_t* request, usb_request_complete_t completion) {
ASSERT_EQ(request->setup.bmRequestType, 3);
ASSERT_EQ(request->setup.bRequest, USB_REQ_SET_FEATURE);
ASSERT_EQ(request->setup.wIndex, 2);
ran = true;
usb_request_complete(request, ZX_OK, 0, &completion);
});
ASSERT_OK(RunSynchronously(dev->SetFeature(3, 7, 2)));
ASSERT_TRUE(ran);
}
TEST_F(SyntheticHarness, ClearFeature) {
auto dev = device();
bool ran = false;
SetRequestCallback([&](usb_request_t* request, usb_request_complete_t completion) {
ASSERT_EQ(request->setup.bmRequestType, 3);
ASSERT_EQ(request->setup.bRequest, USB_REQ_CLEAR_FEATURE);
ASSERT_EQ(request->setup.wIndex, 2);
ran = true;
usb_request_complete(request, ZX_OK, 0, &completion);
});
ASSERT_OK(RunSynchronously(dev->ClearFeature(3, 7, 2)));
ASSERT_TRUE(ran);
}
TEST_F(SyntheticHarness, GetPortStatus) {
auto dev = device();
// Run through all 127 permutations of port configuration states
// and ensure we set the correct bits for each one.
for (uint16_t i = 0; i < 127; i++) {
bool ran = false;
uint16_t features_cleared = 0;
SetRequestCallback([&](usb_request_t* request, usb_request_complete_t completion) {
switch (request->setup.bmRequestType) {
case USB_RECIP_PORT | USB_DIR_IN: {
usb_port_status_t* stat;
usb_request_mmap(request, reinterpret_cast<void**>(&stat));
stat->wPortChange = i;
usb_request_complete(request, ZX_OK, sizeof(usb_port_status_t), &completion);
return;
} break;
case USB_RECIP_PORT | USB_DIR_OUT: {
switch (request->setup.wValue) {
case USB_FEATURE_C_PORT_CONNECTION:
features_cleared |= USB_C_PORT_CONNECTION;
break;
case USB_FEATURE_C_PORT_ENABLE:
features_cleared |= USB_C_PORT_ENABLE;
break;
case USB_FEATURE_C_PORT_SUSPEND:
features_cleared |= USB_C_PORT_SUSPEND;
break;
case USB_FEATURE_C_PORT_OVER_CURRENT:
features_cleared |= USB_C_PORT_OVER_CURRENT;
break;
case USB_FEATURE_C_PORT_RESET:
features_cleared |= USB_C_PORT_RESET;
break;
case USB_FEATURE_C_BH_PORT_RESET:
features_cleared |= USB_C_BH_PORT_RESET;
break;
case USB_FEATURE_C_PORT_LINK_STATE:
features_cleared |= USB_C_PORT_LINK_STATE;
break;
case USB_FEATURE_C_PORT_CONFIG_ERROR:
features_cleared |= USB_C_PORT_CONFIG_ERROR;
break;
}
} break;
default:
ASSERT_TRUE(false);
}
usb_request_complete(request, ZX_OK, 0, &completion);
});
ASSERT_OK(RunSynchronously(dev->GetPortStatus(usb_hub::PortNumber(static_cast<uint8_t>(i)))
.and_then([&](usb_port_status_t& port_status) {
ran = true;
ASSERT_EQ(port_status.wPortChange, i);
})));
ASSERT_TRUE(ran);
ASSERT_EQ(features_cleared, i);
}
}
TEST_F(SyntheticHarness, BadDescriptorTest) {
auto dev = device();
SetRequestCallback([&](usb_request_t* request, usb_request_complete_t completion) {
usb_device_descriptor_t* devdesc;
usb_request_mmap(request, reinterpret_cast<void**>(&devdesc));
devdesc->bLength = sizeof(usb_device_descriptor_t);
usb_request_complete(request, ZX_OK, sizeof(usb_descriptor_header_t), &completion);
});
ASSERT_EQ(
RunSynchronously(dev->GetVariableLengthDescriptor<usb_device_descriptor_t>(0, 0, 0).and_then(
[=](usb_hub::VariableLengthDescriptor<usb_device_descriptor_t>& descriptor) {
return fit::ok();
})),
ZX_ERR_BAD_STATE);
}
} // namespace