blob: 88ebf62db7612019a8074801368ee3317849b80c [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-hub/usb-hub.h"
#include <fuchsia/hardware/usb/hub/c/banjo.h>
#include <fuchsia/hardware/usb/hubdescriptor/c/banjo.h>
#include <fuchsia/hardware/usb/request/c/banjo.h>
#include <lib/fit/defer.h>
#include <lib/stdcompat/span.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/process.h>
#include <zircon/time.h>
#include <array>
#include <atomic>
#include <cstring>
#include <functional>
#include <thread>
#include <variant>
#include <fbl/condition_variable.h>
#include <fbl/ref_ptr.h>
#include <zxtest/zxtest.h>
#include "lib/fpromise/promise.h"
#include "src/devices/testing/mock-ddk/mock-device.h"
#include "src/devices/usb/drivers/usb-hub/fake-device.h"
namespace {
template <EmulationMode mode>
class UsbHarness : public zxtest::Test {
public:
void SetUp() override {
parent_ = MockDevice::FakeRootParent();
device_.emplace(mode);
usb_protocol_t usb_proto;
usb_bus_protocol_t bus_proto;
ASSERT_OK(device_->GetProtocol(ZX_PROTOCOL_USB, &usb_proto));
ASSERT_OK(device_->GetProtocol(ZX_PROTOCOL_USB_BUS, &bus_proto));
parent_->AddProtocol(ZX_PROTOCOL_USB, usb_proto.ops, usb_proto.ctx);
parent_->AddProtocol(ZX_PROTOCOL_USB_BUS, bus_proto.ops, bus_proto.ctx);
usb_hub::UsbHubDevice::Bind(nullptr, reinterpret_cast<zx_device_t*>(parent_.get()));
device_->SetChildContext(parent_->GetLatestChild()->GetDeviceContext<usb_hub::UsbHubDevice>());
device()->DdkInit(ddk::InitTxn(device()->zxdev()));
parent_->GetLatestChild()->WaitUntilInitReplyCalled();
device_->InitComplete();
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 fit::defer([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()->DdkUnbind(ddk::UnbindTxn(device()->zxdev()));
parent_->GetLatestChild()->WaitUntilUnbindReplyCalled();
device_->Unplug();
}
private:
std::shared_ptr<MockDevice> parent_;
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_;
};
// The SyntheticHarness tests currently assume DdkInit() has not been invoked.
class SyntheticHarness : public zxtest::Test {
public:
SyntheticHarness() : loop_(&kAsyncLoopConfigNeverAttachToThread) {}
void SetUp() override {
zx_status_t status = loop_.StartThread();
if (status != ZX_OK) {
return;
}
auto executor = std::make_unique<async::Executor>(loop_.dispatcher());
executor_ = executor.get();
parent_ = MockDevice::FakeRootParent();
device_.emplace(EmulationMode::Smays);
device_->SetSynthetic(true);
usb_protocol_t usb_proto;
usb_bus_protocol_t bus_proto;
ASSERT_OK(device_->GetProtocol(ZX_PROTOCOL_USB, &usb_proto));
ASSERT_OK(device_->GetProtocol(ZX_PROTOCOL_USB_BUS, &bus_proto));
parent_->AddProtocol(ZX_PROTOCOL_USB, usb_proto.ops, usb_proto.ctx);
parent_->AddProtocol(ZX_PROTOCOL_USB_BUS, bus_proto.ops, bus_proto.ctx);
usb_hub::UsbHubDevice::Bind(std::move(executor), reinterpret_cast<zx_device_t*>(parent_.get()));
device_->SetChildContext(parent_->GetLatestChild()->GetDeviceContext<usb_hub::UsbHubDevice>());
ASSERT_TRUE(device_->HasOps());
}
void SetRequestCallback(
fit::function<void(usb_request_t*, usb_request_complete_callback_t)> callback) {
device_->SetRequestCallback(std::move(callback));
}
usb_hub::UsbHubDevice* device() { return device_->device(); }
void TearDown() override {
device()->DdkUnbind(ddk::UnbindTxn(device()->zxdev()));
parent_->GetLatestChild()->WaitUntilUnbindReplyCalled();
loop_.Shutdown();
}
private:
std::shared_ptr<MockDevice> parent_;
std::optional<FakeDevice> device_;
async::Loop loop_;
fpromise::executor* executor_;
};
class SmaysHarness : public UsbHarness<EmulationMode::Smays> {
public:
};
class UnbrandedHarness : public UsbHarness<EmulationMode::Unbranded> {
public:
};
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(UnbrandedHarness, UnbindHubDisconnects) {
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;
});
ASSERT_OK(device()->Shutdown());
sync_completion_wait(&disconnect_complete, ZX_TIME_INFINITE);
}
TEST_F(SyntheticHarness, SetFeature) {
auto dev = device();
bool ran = false;
SetRequestCallback([&](usb_request_t* request, usb_request_complete_callback_t completion) {
ASSERT_EQ(request->setup.bm_request_type, 3);
ASSERT_EQ(request->setup.b_request, USB_REQ_SET_FEATURE);
ASSERT_EQ(request->setup.w_index, 2);
ran = true;
usb_request_complete(request, ZX_OK, 0, &completion);
});
ASSERT_OK(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_callback_t completion) {
ASSERT_EQ(request->setup.bm_request_type, 3);
ASSERT_EQ(request->setup.b_request, USB_REQ_CLEAR_FEATURE);
ASSERT_EQ(request->setup.w_index, 2);
ran = true;
usb_request_complete(request, ZX_OK, 0, &completion);
});
ASSERT_OK(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++) {
uint16_t features_cleared = 0;
SetRequestCallback([&](usb_request_t* request, usb_request_complete_callback_t completion) {
switch (request->setup.bm_request_type) {
case USB_RECIP_PORT | USB_DIR_IN: {
usb_port_status_t* stat;
usb_request_mmap(request, reinterpret_cast<void**>(&stat));
stat->w_port_change = 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.w_value) {
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);
});
auto result = dev->GetPortStatus(usb_hub::PortNumber(static_cast<uint8_t>(i)));
usb_port_status_t port_status = result.value();
ASSERT_EQ(port_status.w_port_change, i);
ASSERT_EQ(features_cleared, i);
}
}
// This test checks that GetUsbHubDescriptor will fail on invalid sized descriptors
TEST_F(SyntheticHarness, BadDescriptorTest) {
auto dev = device();
SetRequestCallback([&](usb_request_t* request, usb_request_complete_callback_t completion) {
usb_device_descriptor_t* devdesc;
usb_request_mmap(request, reinterpret_cast<void**>(&devdesc));
devdesc->b_length = sizeof(usb_device_descriptor_t);
usb_request_complete(request, ZX_OK, sizeof(usb_descriptor_header_t), &completion);
});
auto result = dev->GetUsbHubDescriptor(0);
ASSERT_EQ(result.error_value(), ZX_ERR_BAD_STATE);
SetRequestCallback([&](usb_request_t* request, usb_request_complete_callback_t completion) {
usb_device_descriptor_t* devdesc;
usb_request_mmap(request, reinterpret_cast<void**>(&devdesc));
devdesc->b_length = sizeof(usb_device_descriptor_t);
usb_request_complete(request, ZX_OK, sizeof(usb_device_descriptor_t), &completion);
});
result = dev->GetUsbHubDescriptor(USB_HUB_DESC_TYPE_SS);
ASSERT_EQ(result.error_value(), ZX_ERR_NO_MEMORY);
}
TEST_F(SyntheticHarness, GoodDescriptorTest) {
auto dev = device();
SetRequestCallback([&](usb_request_t* request, usb_request_complete_callback_t completion) {
usb_device_descriptor_t* devdesc;
usb_request_mmap(request, reinterpret_cast<void**>(&devdesc));
devdesc->b_length = sizeof(usb_descriptor_header_t);
usb_request_complete(request, ZX_OK, sizeof(usb_descriptor_header_t), &completion);
});
auto result = dev->GetUsbHubDescriptor(USB_HUB_DESC_TYPE_SS);
ASSERT_OK(result);
}
} // namespace