blob: ec7ac55f1f3e28dd0d911bd74138f69ba23631b4 [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_EVENT_RING_H_
#define SRC_DEVICES_USB_DRIVERS_XHCI_XHCI_EVENT_RING_H_
#include <lib/dma-buffer/buffer.h>
#include <lib/fpromise/promise.h>
#include <lib/mmio/mmio.h>
#include <lib/synchronous-executor/executor.h>
#include <lib/trace/event.h>
#include <lib/zx/bti.h>
#include <zircon/errors.h>
#include <optional>
#include <fbl/auto_lock.h>
#include <fbl/intrusive_double_list.h>
#include <fbl/mutex.h>
#include <usb/usb.h>
#include "src/devices/usb/drivers/xhci/xhci-context.h"
#include "src/devices/usb/drivers/xhci/xhci-hub.h"
#include "src/devices/usb/drivers/xhci/xhci-port-state.h"
#include "zircon/system/ulib/inspect/include/lib/inspect/cpp/vmo/types.h"
namespace usb_xhci {
// Event Ring Segment table entry (6.5)
struct ERSTEntry {
uint32_t address_low;
uint32_t address_high;
uint32_t size;
uint32_t rsvd;
};
// Used for managing event ring segments.
// This table can be expanded and shrunk as event ring
// segments are added and removed.
class EventRingSegmentTable {
public:
zx_status_t Init(size_t page_size, const zx::bti& bti, bool is_32bit, uint32_t erst_max,
ERSTSZ erst_size, const dma_buffer::BufferFactory& factory,
fdf::MmioBuffer* mmio);
zx_status_t AddSegment(zx_paddr_t paddr);
ERSTEntry* entries() { return entries_; }
zx_paddr_t erst() { return erst_->phys()[0]; }
// Returns the number of segments in this ERST
uint32_t SegmentCount() const { return offset_; }
uint64_t TrbCount() const { return (SegmentCount() * page_size_) / sizeof(TRB); }
bool CanGrow() const { return offset_ < count_; }
void AddPressure() { erst_pressure_++; }
size_t Pressure() const { return erst_pressure_; }
void RemovePressure() { erst_pressure_--; }
private:
size_t erst_pressure_ = 0;
ERSTSZ erst_size_;
std::unique_ptr<dma_buffer::PagedBuffer> erst_;
// Entries in the event ring segment table.
// This is valid after Init() is called which
// allocates the event ring segment table.
ERSTEntry* entries_;
// Number of ERST entries
size_t count_ = 0;
// Offset in ERST table
uint32_t offset_ = 0;
// BTI used for obtaining physical memory addresses.
// This is valid for the lifetime of the UsbXhci driver,
// and is owned by UsbXhci.
const zx::bti* bti_;
size_t page_size_;
bool is_32bit_;
std::optional<fdf::MmioView> mmio_;
};
struct PortStatusChangeState : public fbl::RefCounted<PortStatusChangeState> {
size_t port_index;
size_t port_count;
PortStatusChangeState(size_t i, size_t port_count) : port_index(i), port_count(port_count) {}
};
// Keeps track of events received from the XHCI controller
class UsbXhci;
class CommandRing;
class EventRing {
public:
// Adds a segment to the event ring.
zx_status_t Init(size_t page_size, const zx::bti& bti, fdf::MmioBuffer* buffer, bool is_32bit,
uint32_t erst_max, ERSTSZ erst_size, ERDP erdp_reg, IMAN iman_reg,
uint8_t cap_length, HCSPARAMS1 hcs_params_1, CommandRing* command_ring,
DoorbellOffset doorbell_offset, UsbXhci* hci, HCCPARAMS1 hcc_params_1,
uint64_t* dcbaa, uint16_t interrupter, inspect::Node* interrupter_node);
// Disable thread safety analysis here.
// We don't need to hold the mutex just to read the ERST
// paddr, as this will never change (it is effectively a constant).
// We don't need to incurr the overhead of acquiring the mutex for this.
zx_paddr_t erst() __TA_NO_THREAD_SAFETY_ANALYSIS { return segments_.erst(); }
void RemovePressure();
size_t GetPressure();
zx_status_t AddSegmentIfNoneLock() {
fbl::AutoLock l(&segment_mutex_);
return AddSegmentIfNone();
}
zx_paddr_t erdp_phys() { return erdp_phys_; }
TRB* erdp_virt() { return erdp_virt_; }
zx_status_t HandleIRQ();
zx_status_t Ring0Bringup();
void ScheduleTask(TRBPromise promise) { ScheduleTask(promise.discard_value()); }
void ScheduleTask(fpromise::promise<void, zx_status_t> promise);
void RunUntilIdle();
private:
void SchedulePortStatusChange(uint8_t port_id, bool preempt = false);
fpromise::promise<void, zx_status_t> HandlePortStatusChangeEvent(uint8_t port_id);
fpromise::promise<void, zx_status_t> WaitForPortStatusChange(uint8_t port_id);
fpromise::promise<void, zx_status_t> LinkUp(uint8_t port_id);
void CallPortStatusChanged(fbl::RefPtr<PortStatusChangeState> state);
// Advance ERDP according to Section 4.9.4.1. Evaluates the validity of the current TRB and the
// direction of the next TRB. Possible directions:
// - If not at the end of the segment, next TRB is consecutive in address.
// - If we are in a new segment, CCS bit is invalid. Evaluate if we stop by checking the
// next TRB's completion code. Only continue moving on if completion code is not INVALID.
// - If at the end of a segment
// - When there are no new segments, next TRB is the beginning of the next segment..
// - When there are new segments, but we're not about to enter into the new segment, next
// TRB is still the beginning of the next segment.
// - When there are new segments and we are about to enter into the new segment, checks if
// the new segment is being used.
// - If the new segment is being used, go to the beginning of the new segment.
// - If the new segment is not being used yet, check if the event ring is empty.
// - If the event ring is not empty, go to the beginning of the 0th segment (because
// the new segment is always the last segment)
// - If the event ring is empty, reevaluate next time (by setting the reevaluate_ bit
// to true). In this case ERDP points not to the next TRB to be evaluated (as we
// usually expect), but the current TRB already evaluated.
// Returns the next TRB pointed to by ERDP.
void AdvanceErdp();
zx_paddr_t UpdateErdpReg(zx_paddr_t last_phys, size_t processed_trb_count);
std::optional<Control> CurrentErdp();
// Interrupt handlers.
void HandlePortStatusChangeInterrupt();
zx_status_t HandleCommandCompletionInterrupt();
void HandleTransferInterrupt();
// USB 3.0 device attach
void Usb3DeviceAttach(uint16_t port_id);
// USB 2.0 device attach
void Usb2DeviceAttach(uint16_t port_id);
zx_status_t AddSegmentIfNone() __TA_REQUIRES(segment_mutex_);
zx_status_t AddSegment(bool initialization = false) __TA_REQUIRES(segment_mutex_);
bool StallWorkaroundForDefectiveHubs(std::unique_ptr<TRBContext>& context);
struct SegmentBuf : fbl::DoublyLinkedListable<std::unique_ptr<SegmentBuf>> {
SegmentBuf(std::unique_ptr<dma_buffer::ContiguousBuffer> b, bool n)
: buf(std::move(b)), new_segment(n) {}
std::unique_ptr<dma_buffer::ContiguousBuffer> buf;
bool new_segment;
};
fbl::DoublyLinkedList<std::unique_ptr<SegmentBuf>> buffers_ __TA_GUARDED(segment_mutex_);
fbl::DoublyLinkedList<std::unique_ptr<SegmentBuf>>::iterator buffers_it_
__TA_GUARDED(segment_mutex_);
// List of pending enumeration tasks
fbl::DoublyLinkedList<std::unique_ptr<TRBContext>> enumeration_queue_;
// Whether or not we're currently enumerating a device
bool enumerating_ = false;
synchronous_executor::synchronous_executor executor_;
// Virtual address of the event ring dequeue pointer
TRB* erdp_virt_ = nullptr;
// Event ring dequeue pointer (physical address)
zx_paddr_t erdp_phys_ = 0;
// Current Cycle State
bool ccs_ = true;
fbl::Mutex segment_mutex_;
EventRingSegmentTable segments_ __TA_GUARDED(segment_mutex_);
// BTI used for obtaining physical memory addresses.
// This is valid for the lifetime of the UsbXhci driver,
// and is owned by UsbXhci.
const zx::bti* bti_;
size_t page_size_;
bool is_32bit_;
// Pointer to the MMIO buffer for writing to xHCI registers
// This is valid for the lifetime of the UsbXhci driver,
// and is owned by UsbXhci.
fdf::MmioBuffer* mmio_;
// Event ring dequeue pointer register
ERDP erdp_reg_;
// Interrupt management register
IMAN iman_reg_;
uint8_t segment_index_ __TA_GUARDED(segment_mutex_) = 0;
UsbXhci* hci_;
uint8_t cap_length_;
HCSPARAMS1 hcs_params_1_;
CommandRing* command_ring_;
DoorbellOffset doorbell_offset_;
HCCPARAMS1 hcc_params_1_;
// Device context base address array
// This is a pointer into the buffer
// owned by UsbXhci, which this is a child of.
// When xHCI shuts down, this pointer will be invalid.
uint64_t* dcbaa_;
uint16_t interrupter_;
bool reevaluate_ = false;
bool resynchronize_ = false;
// inspect properties do not allow us to read the values we have published,
// nor do they provide any sort of "max" functionality (only add, subtract,
// and set). Because of this, we need to keep our own shadow copy of our max
// observed value in order to properly update the published value.
inspect::UintProperty total_event_trbs_;
inspect::UintProperty max_single_irq_event_trbs_;
uint64_t max_single_irq_event_trbs_value_{0};
inspect::Node events_;
std::optional<inspect::UintProperty> port_status_change_event_;
std::optional<inspect::LinearUintHistogram> command_completion_event_;
std::optional<inspect::LinearUintHistogram> transfer_event_;
std::optional<inspect::UintProperty> mf_index_wrap_event_;
std::optional<inspect::UintProperty> host_controller_event_;
std::optional<inspect::LinearUintHistogram> unhandled_events_;
std::optional<trace_async_id_t> async_id_;
};
} // namespace usb_xhci
#endif // SRC_DEVICES_USB_DRIVERS_XHCI_XHCI_EVENT_RING_H_