blob: 45a72347fa330ae8cbec2734b995c85004eccb55 [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/xhci/xhci-transfer-ring.h"
#include <lib/fpromise/bridge.h>
#include <lib/fpromise/promise.h>
#include <zircon/syscalls.h>
#include <zircon/types.h>
#include <atomic>
#include <memory>
#include <thread>
#include <fake-dma-buffer/fake-dma-buffer.h>
#include <fake-mmio-reg/fake-mmio-reg.h>
#include <fbl/algorithm.h>
#include <zxtest/zxtest.h>
#include "src/devices/testing/mock-ddk/mock-device.h"
#include "src/devices/usb/drivers/xhci/usb-xhci.h"
#include "src/devices/usb/drivers/xhci/xhci-event-ring.h"
namespace usb_xhci {
const zx::bti kFakeBti(42);
class TransferRingHarness : public zxtest::Test {
public:
TransferRingHarness()
: trb_context_allocator_(-1, true),
hci_(root_.get(), ddk_fake::CreateBufferFactory(), loop_.dispatcher()) {}
void SetUp() override {
constexpr auto kOffset = 6 * sizeof(uint32_t);
constexpr auto kErdp = 2062 * sizeof(uint32_t);
region_.emplace(sizeof(uint32_t), 4096);
buffer_.emplace(region_->GetMmioBuffer());
(*region_)[kOffset].SetReadCallback([=]() { return 0x2000; });
(*region_)[kErdp].SetReadCallback([=]() { return erdp_; });
(*region_)[kErdp].SetWriteCallback([=](uint64_t value) {
ERDP reg;
reg.set_reg_value(value);
erdp_ = reg.Pointer();
});
hci_.SetTestHarness(this);
ASSERT_OK(hci_.InitThread());
}
void TearDown() override {}
using TestRequest = usb::CallbackRequest<sizeof(max_align_t)>;
template <typename Callback>
zx_status_t AllocateRequest(std::optional<TestRequest>* request, uint32_t device_id,
uint64_t data_size, uint8_t endpoint, Callback callback) {
return TestRequest::Alloc(request, data_size, endpoint, hci_.UsbHciGetRequestSize(),
std::move(callback));
}
void RequestQueue(usb_request_t* usb_request,
const usb_request_complete_callback_t* complete_cb) {
pending_req_ = Request(usb_request, *complete_cb, sizeof(usb_request_t));
}
Request Borrow(TestRequest request) {
request.Queue(*this);
return std::move(*pending_req_);
}
TransferRing* ring() { return ring_; }
void SetRing(TransferRing* ring) { ring_ = ring; }
std::unique_ptr<TRBContext> AllocateContext() { return trb_context_allocator_.New(); }
EventRing& event_ring() { return event_ring_; }
private:
async::Loop loop_{&kAsyncLoopConfigNeverAttachToThread};
using AllocatorTraits = fbl::InstancedSlabAllocatorTraits<std::unique_ptr<TRBContext>, 4096U>;
using AllocatorType = fbl::SlabAllocator<AllocatorTraits>;
AllocatorType trb_context_allocator_;
std::shared_ptr<MockDevice> root_ = MockDevice::FakeRootParent();
std::optional<Request> pending_req_;
fbl::DoublyLinkedList<std::unique_ptr<TRBContext>> pending_contexts_;
std::optional<fdf::MmioBuffer> buffer_;
UsbXhci hci_;
TransferRing* ring_;
EventRing event_ring_;
CommandRing command_ring_;
uint64_t erdp_;
std::optional<ddk_fake::FakeMmioRegRegion> region_;
};
void UsbXhci::ConnectToEndpoint(ConnectToEndpointRequest& request,
ConnectToEndpointCompleter::Sync& completer) {
completer.Reply(fit::as_error(ZX_ERR_NOT_SUPPORTED));
}
void UsbXhci::UsbHciSetBusInterface(const usb_bus_interface_protocol_t* bus_intf) {}
size_t UsbXhci::UsbHciGetMaxDeviceCount() { return 0; }
zx_status_t UsbXhci::UsbHciEnableEndpoint(uint32_t device_id,
const usb_endpoint_descriptor_t* ep_desc,
const usb_ss_ep_comp_descriptor_t* ss_com_desc,
bool enable) {
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t UsbXhci::InitThread() {
fbl::AllocChecker ac;
interrupters_ = fbl::MakeArray<Interrupter>(&ac, 1);
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
max_slots_ = 32;
device_state_ = fbl::MakeArray<fbl::RefPtr<DeviceState>>(&ac, max_slots_);
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
for (size_t i = 0; i < max_slots_; i++) {
device_state_[i] = fbl::MakeRefCounted<DeviceState>(static_cast<uint32_t>(i), this);
fbl::AutoLock l(&device_state_[i]->transaction_lock());
for (size_t c = 0; c < max_slots_; c++) {
zx_status_t status = device_state_[i]->InitEndpoint(
static_cast<uint8_t>(c),
&static_cast<TransferRingHarness*>(GetTestHarness())->event_ring(), nullptr);
if (status != ZX_OK) {
return status;
}
}
}
fbl::AutoLock l(&device_state_[0]->transaction_lock());
static_cast<TransferRingHarness*>(GetTestHarness())
->SetRing(&device_state_[0]->GetEndpoint(0).transfer_ring());
return ZX_OK;
}
uint64_t UsbXhci::UsbHciGetCurrentFrame() { return 0; }
zx_status_t UsbXhci::UsbHciConfigureHub(uint32_t device_id, usb_speed_t speed,
const usb_hub_descriptor_t* desc, bool multi_tt) {
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t UsbXhci::UsbHciHubDeviceAdded(uint32_t device_id, uint32_t port, usb_speed_t speed) {
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t UsbXhci::UsbHciHubDeviceRemoved(uint32_t hub_id, uint32_t port) {
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t UsbXhci::UsbHciHubDeviceReset(uint32_t device_id, uint32_t port) {
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t UsbXhci::UsbHciResetEndpoint(uint32_t device_id, uint8_t ep_address) {
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t UsbXhci::UsbHciResetDevice(uint32_t hub_address, uint32_t device_id) {
return ZX_ERR_NOT_SUPPORTED;
}
size_t UsbXhci::UsbHciGetMaxTransferSize(uint32_t device_id, uint8_t ep_address) { return 0; }
zx_status_t UsbXhci::UsbHciCancelAll(uint32_t device_id, uint8_t ep_address) {
return ZX_ERR_NOT_SUPPORTED;
}
size_t UsbXhci::UsbHciGetRequestSize() { return Request::RequestSize(sizeof(usb_request_t)); }
void UsbXhci::UsbHciRequestQueue(usb_request_t* usb_request,
const usb_request_complete_callback_t* complete_cb) {}
zx_status_t EventRing::AddSegmentIfNone() { return ZX_OK; }
void UsbXhci::Shutdown(zx_status_t status) {}
void EventRing::RemovePressure() {}
fpromise::promise<void, zx_status_t> UsbXhci::DeviceOffline(uint32_t slot) {
return fpromise::make_error_promise<zx_status_t>(ZX_ERR_NOT_SUPPORTED);
}
fpromise::promise<void, zx_status_t> UsbXhci::UsbHciDisableEndpoint(uint32_t device_id,
uint8_t ep_addr) {
return fpromise::make_error_promise<zx_status_t>(ZX_ERR_NOT_SUPPORTED);
}
fpromise::promise<void, zx_status_t> EnumerateDevice(UsbXhci* hci, uint8_t port,
std::optional<HubInfo> hub_info) {
return fpromise::make_error_promise<zx_status_t>(ZX_ERR_NOT_SUPPORTED);
}
Endpoint::Endpoint(UsbXhci* hci, uint32_t device_id, uint8_t address)
: usb_endpoint::UsbEndpoint(hci->bti(), address), hci_(hci) {}
zx_status_t Endpoint::Init(EventRing* event_ring, fdf::MmioBuffer* mmio) {
return transfer_ring_.Init(zx_system_get_page_size(), kFakeBti, event_ring, false, mmio, hci_);
}
void Endpoint::QueueRequests(QueueRequestsRequest& request,
QueueRequestsCompleter::Sync& completer) {}
void Endpoint::CancelAll(CancelAllCompleter::Sync& completer) {
completer.Reply(fit::as_error(ZX_ERR_NOT_SUPPORTED));
}
void Endpoint::OnUnbound(fidl::UnbindInfo info,
fidl::ServerEnd<fuchsia_hardware_usb_endpoint::Endpoint> server_end) {}
DeviceState::~DeviceState() = default;
zx_status_t DeviceState::InitEndpoint(uint8_t ep_addr, EventRing* event_ring, fdf::MmioBuffer* mmio)
__TA_REQUIRES(transaction_lock_) {
rings_[ep_addr].emplace(hci_, device_id_, ep_addr);
return rings_[ep_addr]->Init(event_ring, mmio);
}
TEST_F(TransferRingHarness, EmptyShortTransferTest) {
auto ring = this->ring();
ASSERT_EQ(ring->HandleShortPacket(nullptr, 0, nullptr), ZX_ERR_IO);
}
TEST_F(TransferRingHarness, CorruptedTransferRingShortTransferTest) {
auto ring = this->ring();
Normal trb;
{
auto context = ring->AllocateContext();
ASSERT_OK(ring->AddTRB(trb, std::move(context)));
}
ASSERT_EQ(ring->HandleShortPacket(nullptr, 0, nullptr), ZX_ERR_IO);
ring->TakePendingTRBs();
}
TEST_F(TransferRingHarness, MultiPageShortTransferTest) {
constexpr size_t kNumTrbs = 510;
constexpr size_t kTrbLength = 20;
constexpr size_t kShortLength = 4;
auto ring = this->ring();
TRB* first = nullptr;
TRB* last;
for (size_t i = 0; i < kNumTrbs; i++) {
TRB* ptr;
ASSERT_OK(ring->AllocateTRB(&ptr, nullptr));
if (first == nullptr) {
first = ptr;
}
Control::FromTRB(ptr).set_Type(Control::Normal).ToTrb(ptr);
static_cast<Normal*>(ptr)->set_LENGTH(kTrbLength);
last = ptr;
}
ASSERT_OK(ring->AssignContext(last, ring->AllocateContext(), first));
TRB* last_trb;
ASSERT_OK(ring->HandleShortPacket(last - 1, kShortLength, &last_trb));
EXPECT_EQ(last_trb, last);
std::unique_ptr<TRBContext> context;
ASSERT_OK(ring->CompleteTRB(last, &context));
EXPECT_EQ(context->short_transfer_len.value(), ((kNumTrbs - 1) * kTrbLength) - kShortLength);
EXPECT_EQ(context->first_trb, first);
EXPECT_EQ(context->trb, last);
}
TEST_F(TransferRingHarness, SetStall) {
auto ring = this->ring();
ASSERT_FALSE(ring->stalled());
ring->set_stall(true);
ASSERT_TRUE(ring->stalled());
ring->set_stall(false);
ASSERT_FALSE(ring->stalled());
}
TEST_F(TransferRingHarness, AllocateContiguousFailsIfNotEnoughContiguousPhysicalMemoryExists) {
constexpr auto kOverAllocateAmount = 9001;
auto ring = this->ring();
ASSERT_EQ(ring->AllocateContiguous(kOverAllocateAmount).error_value(), ZX_ERR_NO_MEMORY);
}
TEST_F(TransferRingHarness, AllocateContiguousAllocatesContiguousBlocks) {
auto ring = this->ring();
constexpr auto kContiguousCount = 42;
constexpr auto kIterationCount = 512;
for (size_t i = 0; i < kIterationCount; i++) {
auto result = ring->AllocateContiguous(kContiguousCount);
ASSERT_TRUE(result.is_ok());
ASSERT_EQ(result->trbs.size(), kContiguousCount);
auto trb_start = result->trbs.data();
for (size_t c = 0; c < kContiguousCount; c++) {
ASSERT_NE(Control::FromTRB(trb_start + c).Type(), Control::Link);
}
}
}
TEST_F(TransferRingHarness, CanHandleConsecutiveLinks) {
auto ring = this->ring();
const size_t trb_per_segment = zx_system_get_page_size() / sizeof(TRB);
// 1 TRB is the link TRB, and TransferRing::AllocInternal() will allocate if there's not 2
// available TRBs.
const size_t trb_per_segment_no_alloc = trb_per_segment - 3;
std::deque<TRB*> pending_trbs;
// Fill up a segment.
for (size_t i = 0; i < trb_per_segment_no_alloc; i++) {
auto context = ring->AllocateContext();
TRBContext* ref = context.get();
ASSERT_OK(ring->AddTRB(TRB(), std::move(context)));
pending_trbs.emplace_back(ref->trb);
}
// Move the dequeue pointer forward two steps, to TRB 2.
for (size_t i = 0; i < 3; i++) {
std::unique_ptr<TRBContext> ctx;
ring->CompleteTRB(pending_trbs.front(), &ctx);
pending_trbs.pop_front();
}
// Finish filling up the segment. This will allocate a new link TRB (TRB 0) and we'll start
// filling up a new segment.
for (size_t i = 0; i < 4; i++) {
auto context = ring->AllocateContext();
TRBContext* ref = context.get();
ASSERT_OK(ring->AddTRB(TRB(), std::move(context)));
pending_trbs.emplace_back(ref->trb);
}
// Move the dequeue pointer forward again - to TRB 3.
for (size_t i = 0; i < 1; i++) {
std::unique_ptr<TRBContext> ctx;
ring->CompleteTRB(pending_trbs.front(), &ctx);
pending_trbs.pop_front();
}
// This will allocate a new link TRB at 1.
for (size_t i = 0; i < trb_per_segment_no_alloc; i++) {
auto context = ring->AllocateContext();
TRBContext* ref = context.get();
ASSERT_OK(ring->AddTRB(TRB(), std::move(context)));
pending_trbs.emplace_back(ref->trb);
}
// At this stage, we have 3 TRB segments.
// Segment 0 looks like this:
// 0 // link to seg#1,0
// 1 // link to seg#2,0
// ...
// 255 // link to seg#0,0
//
// Segment 1 looks like this:
// 0
// ...
// 255 // link to seg#0,1
//
// Segment 2 looks like this:
// 0
// ...
// 255 // link to seg#1,2
//
// Notice that there are two consecutive links here - one between seg#1,255 -> seg#0,1 and then
// seg#0,1 -> seg#2,0.
// Clean up all the pending TRBs.
while (!pending_trbs.empty()) {
std::unique_ptr<TRBContext> ctx;
ASSERT_OK(ring->CompleteTRB(pending_trbs.front(), &ctx));
pending_trbs.pop_front();
}
// Move through, allocating and deallocating TRBs.
// This will eventually hit the consecutive links.
for (size_t i = 0; i < 3 * trb_per_segment; i++) {
auto context = ring->AllocateContext();
TRBContext* ref = context.get();
ASSERT_OK(ring->AddTRB(TRB(), std::move(context)));
std::unique_ptr<TRBContext> ctx;
ASSERT_OK(ring->CompleteTRB(ref->trb, &ctx));
}
}
TEST_F(TransferRingHarness, Peek) {
auto ring = this->ring();
TRB* trb;
ring->AllocateTRB(&trb, nullptr);
auto result = ring->PeekCommandRingControlRegister(0);
ASSERT_EQ(trb + 1, ring->PhysToVirt(result->PTR()));
ASSERT_TRUE(result->RCS());
}
TEST_F(TransferRingHarness, First) {
ContiguousTRBInfo info;
TRB a;
TRB b;
info.trbs = cpp20::span(&a, 1);
ASSERT_EQ(info.first().data(), &a);
info.nop = cpp20::span(&b, 1);
ASSERT_EQ(info.first().data(), &b);
}
} // namespace usb_xhci