blob: a59097be3c296fa0a901228762c273d4eb64847c [file] [log] [blame]
// Copyright 2019 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.
#ifndef SRC_DEVICES_USB_DRIVERS_XHCI_XHCI_TRANSFER_RING_H_
#define SRC_DEVICES_USB_DRIVERS_XHCI_XHCI_TRANSFER_RING_H_
#include <fuchsia/hardware/usb/request/c/banjo.h>
#include <lib/stdcompat/span.h>
#include <lib/zx/result.h>
#include <map>
#include "src/devices/usb/drivers/xhci/xhci-context.h"
#include "src/devices/usb/drivers/xhci/xhci-event-ring.h"
namespace usb_xhci {
class UsbXhci;
struct ContiguousTRBInfo {
cpp20::span<TRB> nop; // Optional page of NOPs
cpp20::span<TRB> trbs; // Contiguous TRBs
cpp20::span<TRB> first() {
if (!nop.empty()) {
return nop;
}
return trbs;
}
};
// Used for queueing transfers to the XHCI controller
class TransferRing {
public:
struct State {
TRB* trbs;
// Producer Cycle state
bool pcs;
};
TransferRing() : trb_context_allocator_(-1, true) {}
bool IsIsochronous() { return isochronous_; }
void SetIsochronous() { isochronous_ = true; }
zx_status_t AddTRB(const TRB& trb, std::unique_ptr<TRBContext> context);
// Assign a TRBContext to trb, and record that first_trb is the first TRB in the TD. trb should be
// the last TRB in the TD.
zx_status_t AssignContext(TRB* trb, std::unique_ptr<TRBContext> context, TRB* first_trb);
// Handles a short packet. This will walk the TRBs corresponding to the next pending TD. If it
// finds short_trb it will set short_transfer_len to the total transfer length which is equal to
// the length up each TRB up to and include short_trb minus short_length. Note that short_length
// is the residual number of bytes not transferred, as provided by a transfer event length field.
// last_trb is set to the TRB which corresponds to the TRBContext which was modified (i.e. the
// last TRB in the TD). Returns ZX_ERR_IO if the TRB is misaligned, or if short_trb isn't found.
zx_status_t HandleShortPacket(TRB* short_trb, size_t short_length, TRB** last_trb);
// Allocates a TRB but does not configure it.
// It is the caller's responsibility to fully configure the returned TRB.
// The caller may optionally rollback a transaction by calling Restore
// The Cycle bit will be passed via the status field of the TRB.
// The caller should store the Cycle bit locally and zero the status field
// prior to doing anything else with the TRB.
zx_status_t AllocateTRB(TRB** trb, State* state);
// Allocates physically contiguous TRBs.
// count is the number of TRBs to allocate (not the number of bytes)
// NOP TRBs will be allocated with interrupt on complete set to 0 in order to pad the allocation
// if not enough contiguous TRBs are available on the current page. If a contiguous allocation is
// possible without the Transfer Ring spanning a page boundary, nullptr will be returned in the
// nop field of the returned ContiguousTRBInfo. The pointer to the NOP TRBs will be returned in
// the nop field of the returned ContiguousTRBInfo. The caller is responsible for setting the
// Cycle bit to the correct value during the transaction commit stage. The pointer to the
// contiguous TRB range will be returned in the trb field of the returned ContiguousTRBInfo.
zx::result<ContiguousTRBInfo> AllocateContiguous(size_t count);
State SaveState();
void set_stall(bool stalled) {
fbl::AutoLock l(&mutex_);
stalled_ = stalled;
}
bool stalled() {
fbl::AutoLock l(&mutex_);
return stalled_;
}
State SaveStateLocked() __TA_REQUIRES(mutex_);
void CommitLocked() __TA_REQUIRES(mutex_);
// Commits the current page.
void Commit();
// Commits a multi-TRB transaction
void CommitTransaction(const State& start);
void Restore(const State& state);
void RestoreLocked(const State& state) __TA_REQUIRES(mutex_);
zx_status_t Init(size_t page_size, const zx::bti& bti, EventRing* ring, bool is_32bit,
fdf::MmioBuffer* mmio, UsbXhci* hci);
// Assumption: This function must ONLY be called from the interrupt
// thread. Otherwise thread-safety assumptions are violated.
zx_status_t DeinitIfActive() __TA_NO_THREAD_SAFETY_ANALYSIS;
zx_status_t Deinit();
bool active() {
fbl::AutoLock l(&mutex_);
return trbs_;
}
// Only called during initialization when no other threads are running.
// Would be pointless to hold the mutex here.
CRCR phys(uint8_t cap_length);
// Retrieves command ring control register value of the next TRB that would be returned
// by AllocateTRB.
zx::result<CRCR> PeekCommandRingControlRegister(uint8_t cap_length);
zx_paddr_t VirtToPhys(TRB* trb);
zx_paddr_t VirtToPhysLocked(TRB* trb) __TA_REQUIRES(mutex_);
TRB* PhysToVirt(zx_paddr_t paddr);
TRB* PhysToVirtLocked(zx_paddr_t paddr) __TA_REQUIRES(mutex_);
zx_status_t CompleteTRB(TRB* trb, std::unique_ptr<TRBContext>* context);
fbl::DoublyLinkedList<std::unique_ptr<TRBContext>> TakePendingTRBs();
fbl::DoublyLinkedList<std::unique_ptr<TRBContext>> TakePendingTRBsUntil(TRB* end);
std::unique_ptr<TRBContext> AllocateContext() {
fbl::AutoLock _(&mutex_);
auto ctx = trb_context_allocator_.New();
ctx->token = token_;
return ctx;
}
TRBPromise AddressDeviceCommand(uint8_t slot_id, uint8_t port_id, std::optional<HubInfo> hub_info,
bool bsr);
private:
bool AvailableSlots(size_t count) __TA_REQUIRES(mutex_);
zx_status_t AllocInternal(Control control) __TA_REQUIRES(mutex_);
fbl::DoublyLinkedList<std::unique_ptr<TRBContext>> pending_trbs_ __TA_GUARDED(mutex_);
// Allocates and initializes a buffer (transfer ring segment)
// and increases the size of the ERST if necessary.
// Each buffer can fit 1 page of TRBs
// The dma_buffer::Buffer is owned by this TransferRing.
zx_status_t AllocBuffer(dma_buffer::ContiguousBuffer** out) __TA_REQUIRES(mutex_);
// Advances the enqueue pointer after a new element has been added to the transfer ring
void AdvancePointer() __TA_REQUIRES(mutex_);
fbl::Mutex mutex_;
uint64_t token_ = 0;
AllocatorType trb_context_allocator_ __TA_GUARDED(mutex_);
fbl::DoublyLinkedList<std::unique_ptr<dma_buffer::ContiguousBuffer>> buffers_
__TA_GUARDED(mutex_);
std::map<zx_vaddr_t, dma_buffer::ContiguousBuffer*> virt_to_buffer_ __TA_GUARDED(mutex_);
std::map<zx_paddr_t, dma_buffer::ContiguousBuffer*> phys_to_buffer_ __TA_GUARDED(mutex_);
// Start of TRBs from perspective of enqueue pointer.
// This pointer is incremented along with the enqueue pointer.
TRB* trbs_ __TA_GUARDED(mutex_) = nullptr;
zx_paddr_t trb_start_phys_ = 0;
// Producer cycle bit (section 4.9.2)
bool pcs_ __TA_GUARDED(mutex_) = true;
TRB* dequeue_trb_ __TA_GUARDED(mutex_) = nullptr;
// Capacity (number of TRBs, including link TRBs in ring)
size_t capacity_ __TA_GUARDED(mutex_) = 0;
size_t page_size_ __TA_GUARDED(mutex_);
const zx::bti* bti_ __TA_GUARDED(mutex_);
EventRing* ring_ __TA_GUARDED(mutex_);
bool is_32_bit_ __TA_GUARDED(mutex_);
fdf::MmioBuffer* mmio_ __TA_GUARDED(mutex_);
// Whether or not this transfer ring is stalled.
// When a transfer ring is stalled, TRBs added to it
// will not be processed by the controller until a ResetEndpoint
// command TRB is placed on the command ring and the command ring doorbell is rang.
bool stalled_ __TA_GUARDED(mutex_);
// Not guarded by a mutex since this is synchronized by the event ring.
bool isochronous_ = false;
const UsbXhci* hci_;
};
// The singleton xHCI command ring
class CommandRing : public TransferRing {
public:
zx_status_t Init(size_t page_size, zx::bti* bti, EventRing* ring, bool is_32bit,
fdf::MmioBuffer* mmio, UsbXhci* hci) {
return TransferRing::Init(page_size, *bti, ring, is_32bit, mmio, hci);
}
// Generates a NOP command
TRB Nop() {
TRB retval;
Control::Get().FromValue(0).set_Type(Control::NopCommand).ToTrb(&retval);
return retval;
}
};
} // namespace usb_xhci
#endif // SRC_DEVICES_USB_DRIVERS_XHCI_XHCI_TRANSFER_RING_H_