blob: 4766929af580522ca5e1775ddd25bf2adcac6be0 [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_REWRITE_USB_XHCI_H_
#define SRC_DEVICES_USB_DRIVERS_XHCI_REWRITE_USB_XHCI_H_
#include <lib/async-loop/cpp/loop.h>
#include <lib/async/cpp/executor.h>
#include <lib/device-protocol/pci.h>
#include <lib/device-protocol/pdev.h>
#include <lib/fit/function.h>
#include <lib/fit/single_threaded_executor.h>
#include <lib/mmio/mmio.h>
#include <lib/sync/completion.h>
#include <lib/zx/profile.h>
#include <threads.h>
#include <unistd.h>
#include <algorithm>
#include <atomic>
#include <memory>
#include <thread>
#include <ddktl/device.h>
#include <ddktl/protocol/composite.h>
#include <ddktl/protocol/pci.h>
#include <ddktl/protocol/platform/device.h>
#include <ddktl/protocol/usb/bus.h>
#include <ddktl/protocol/usb/hci.h>
#include <ddktl/protocol/usb/phy.h>
#include <fbl/auto_lock.h>
#include <fbl/mutex.h>
#include "lib/dma-buffer/buffer.h"
#include "registers.h"
#include "synchronous_executor.h"
#include "xhci-context.h"
#include "xhci-device-state.h"
#include "xhci-enumeration.h"
#include "xhci-event-ring.h"
#include "xhci-hub.h"
#include "xhci-interrupter.h"
#include "xhci-port-state.h"
#include "xhci-transfer-ring.h"
namespace usb_xhci {
// Obtains the slot index for a specified endpoint.
__UNUSED static uint8_t XhciEndpointIndex(uint8_t ep_address) {
if (ep_address == 0)
return 0;
uint8_t index = static_cast<uint8_t>(2 * (ep_address & ~USB_ENDPOINT_DIR_MASK));
if ((ep_address & USB_ENDPOINT_DIR_MASK) == USB_ENDPOINT_OUT)
index--;
return index;
}
__UNUSED static uint32_t Log2(uint32_t value) { return 31 - __builtin_clz(value); }
static inline void InvalidatePageCache(void* addr, uint32_t options) {
uintptr_t page = reinterpret_cast<uintptr_t>(addr);
page = fbl::round_down(page, static_cast<uintptr_t>(PAGE_SIZE));
zx_cache_flush(reinterpret_cast<void*>(page), PAGE_SIZE, options);
}
// This is the main class for the USB XHCI host controller driver.
// Refer to 3.1 for general architectural information on xHCI.
class UsbXhci;
using UsbXhciType = ddk::Device<UsbXhci, ddk::Suspendable, ddk::UnbindableNew>;
class UsbXhci : public UsbXhciType, public ddk::UsbHciProtocol<UsbXhci, ddk::base_protocol> {
public:
explicit UsbXhci(zx_device_t* parent)
: UsbXhciType(parent),
pci_(parent),
pdev_(parent),
composite_(parent),
ddk_interaction_loop_(&kAsyncLoopConfigNeverAttachToThread),
ddk_interaction_executor_(ddk_interaction_loop_.dispatcher()) {}
// Constructor for unit testing (to allow interception of MMIO read/write)
explicit UsbXhci(zx_device_t* parent, ddk::MmioBuffer buffer)
: UsbXhciType(parent),
ddk_interaction_loop_(&kAsyncLoopConfigNeverAttachToThread),
ddk_interaction_executor_(ddk_interaction_loop_.dispatcher()) {}
static zx_status_t Create(void* ctx, zx_device_t* parent);
// Forces an immediate shutdown of the HCI
// This should only be called for critical errors that cannot
// be recovered from.
void Shutdown(zx_status_t status);
// Device protocol implementation.
void DdkSuspend(ddk::SuspendTxn txn);
void DdkUnbindNew(ddk::UnbindTxn txn);
void DdkRelease();
// Queues a control request
void UsbHciControlRequestQueue(Request request);
// Queues a normal request
void UsbHciNormalRequestQueue(Request request);
// USB HCI protocol implementation.
void UsbHciRequestQueue(usb_request_t* usb_request, const usb_request_complete_t* complete_cb);
// Queues a USB request (compatibility shim for usb::CallbackRequest in unit test)
void RequestQueue(usb_request_t* usb_request, const usb_request_complete_t* complete_cb) {
UsbHciRequestQueue(usb_request, complete_cb);
}
// Queues a request and returns a promise
fit::promise<OwnedRequest, void> UsbHciRequestQueue(OwnedRequest usb_request);
void UsbHciSetBusInterface(const usb_bus_interface_protocol_t* bus_intf);
// Retrieves the max number of device slots supported by this host controller
size_t UsbHciGetMaxDeviceCount();
zx_status_t UsbHciEnableEndpoint(uint32_t device_id, const usb_endpoint_descriptor_t* ep_desc,
const usb_ss_ep_comp_descriptor_t* ss_com_desc, bool enable);
TRBPromise UsbHciEnableEndpoint(uint32_t device_id, const usb_endpoint_descriptor_t* ep_desc,
const usb_ss_ep_comp_descriptor_t* ss_com_desc);
TRBPromise UsbHciDisableEndpoint(uint32_t device_id, const usb_endpoint_descriptor_t* ep_desc,
const usb_ss_ep_comp_descriptor_t* ss_com_desc);
uint64_t UsbHciGetCurrentFrame();
zx_status_t UsbHciConfigureHub(uint32_t device_id, usb_speed_t speed,
const usb_hub_descriptor_t* desc, bool multi_tt);
zx_status_t UsbHciHubDeviceAdded(uint32_t device_id, uint32_t port, usb_speed_t speed);
zx_status_t UsbHciHubDeviceRemoved(uint32_t device_id, uint32_t port);
zx_status_t UsbHciHubDeviceReset(uint32_t device_id, uint32_t port);
zx_status_t UsbHciResetEndpoint(uint32_t device_id, uint8_t ep_address);
bool Running() { return running_; }
zx_status_t UsbHciResetDevice(uint32_t hub_address, uint32_t device_id);
size_t UsbHciGetMaxTransferSize(uint32_t device_id, uint8_t ep_address);
zx_status_t UsbHciCancelAll(uint32_t device_id, uint8_t ep_address);
TRBPromise UsbHciCancelAllAsync(uint32_t device_id, uint8_t ep_address);
size_t UsbHciGetRequestSize();
// Offlines a device slot, removing its device node from the topology.
TRBPromise DeviceOffline(uint32_t slot, TRB* continuation);
// Onlines a device, publishing a device node in the DDK.
zx_status_t DeviceOnline(uint32_t slot, uint16_t port, usb_speed_t speed);
// Returns whether or not a device is connected to the root hub.
// Always returns true for devices attached via a hub.
bool IsDeviceConnected(uint8_t slot) {
auto& state = device_state_[slot - 1];
fbl::AutoLock _(&state.transaction_lock());
return !state.IsDisconnecting();
}
// Disables a slot
TRBPromise DisableSlotCommand(uint32_t slot_id);
TRBPromise EnableSlotCommand();
TRBPromise AddressDeviceCommand(uint8_t slot_id, uint8_t port_id, std::optional<HubInfo> hub_info,
bool bsr);
TRBPromise AddressDeviceCommand(uint8_t slot_id);
TRBPromise SetMaxPacketSizeCommand(uint8_t slot_id, uint8_t bMaxPacketSize0);
usb_speed_t GetDeviceSpeed(uint8_t slot_id);
uint8_t GetPortSpeed(uint8_t port_id) const;
// Returns the CSZ bit from HCCPARAMS1
bool CSZ() const { return hcc_.CSZ(); }
// Returns the value in the CAPLENGTH register
uint8_t CapLength() const { return cap_length_; }
uint8_t DeviceIdToSlotId(uint8_t device_id) const { return static_cast<uint8_t>(device_id + 1); }
void SetDeviceInformation(uint8_t slot, uint8_t port, const std::optional<HubInfo>& hub);
// MfIndex wrapper handler. The previous driver used this to increment
// the mfindex wrap value. This caused race conditions that resulted
// in incorrect values for the mfindex wrap value.
// This function is left empty as a placeholder
// for future use of the MFIndex wrap event. It is unclear at the moment
// what, if anything this callback should be used for.
void MfIndexWrapped() {}
zx::profile& get_profile() { return profile_; }
template <typename T>
zx_status_t PostCallback(T callback) {
ddk_interaction_executor_.schedule_task(fit::make_ok_promise().then(
[this, cb = std::move(callback)](fit::result<void, void>& result) mutable { cb(bus_); }));
return ZX_OK;
}
uint8_t get_port_count() { return static_cast<uint8_t>(params_.MaxPorts()); }
TRBPromise UsbHciHubDeviceAddedAsync(uint32_t device_id, uint32_t port, usb_speed_t speed);
TRBPromise ConfigureHubAsync(uint32_t device_id, usb_speed_t speed,
const usb_hub_descriptor_t* desc, bool multi_tt);
// Resets a port. Not to be confused with ResetDevice.
void ResetPort(uint16_t port);
// Waits for xHCI bringup to complete
void WaitForBringup() { sync_completion_wait(&bringup_, ZX_TIME_INFINITE); }
CommandRing* get_command_ring() { return &command_ring_; }
DeviceState* get_device_state() { return device_state_.get(); }
PortState* get_port_state() { return port_state_.get(); }
// Indicates whether or not the controller supports cache coherency
// for transfers.
bool HasCoherentCache() const { return has_coherent_cache_; }
// Indicates whether or not the controller has a cache coherent state.
// Currently, this is the same as HasCoherentCache, but the spec
// leaves open the possibility that a controller may have a coherent cache,
// but not a coherent state.
bool HasCoherentState() const { return HasCoherentCache(); }
// Returns whether or not we are running in Qemu. Quirks need to be applied
// where the emulated controller violates the xHCI specification.
bool IsQemu() { return qemu_quirk_; }
// Converts a TRB promise to a USB request promise
fit::promise<OwnedRequest, void> TRBToUSBRequestPromise(TRBPromise promise, OwnedRequest request);
// Converts a USB request promise to a TRB promise. The returned TRB pointer
// will be nullptr.
TRBPromise USBRequestToTRBPromise(fit::promise<OwnedRequest, void> promise);
TRBPromise ResultToTRBPromise(const fit::result<TRB*, zx_status_t>& result) const {
return fit::make_result_promise(result).box();
}
fit::promise<OwnedRequest, void> ResultToUSBRequestPromise(
fit::result<OwnedRequest, void> result) {
return fit::make_result_promise(std::move(result)).box();
}
// Schedules a promise for execution on the executor
void ScheduleTask(TRBPromise promise) {
interrupters_[0].ring().ScheduleTask(std::move(promise));
}
// Schedules the promise for execution and synchronously waits for it to complete
zx_status_t TRBWait(fit::promise<TRB*, zx_status_t> promise) {
sync_completion_t completion;
zx_status_t completion_code;
auto continuation = promise.then([&](fit::result<TRB*, zx_status_t>& result) {
if (result.is_ok()) {
completion_code = ZX_OK;
sync_completion_signal(&completion);
} else {
completion_code = result.error();
sync_completion_signal(&completion);
}
return result;
});
ScheduleTask(continuation.box());
RunUntilIdle();
sync_completion_wait(&completion, ZX_TIME_INFINITE);
return completion_code;
}
// Returns an empty promise
TRBPromise EmptyPromise() {
fit::bridge<TRB*, zx_status_t> bridge;
auto promise = bridge.consumer.promise().then(
[](fit::result<TRB*, zx_status_t>& result) { return result; });
bridge.completer.complete_ok(nullptr);
return promise.box();
}
// Creates a promise that resolves after a timeout
TRBPromise Timeout(zx::time deadline) { return interrupters_[0].Timeout(deadline); }
// Provides a barrier for promises.
// After this method is invoked,
// all pending promises will be flushed.
void RunUntilIdle() { interrupters_[0].ring().RunUntilIdle(); }
// Initialization thread method. This is invoked from a separate detached thread
// when xHCI binds
zx_status_t InitThread(std::unique_ptr<dma_buffer::BufferFactory> buffer_factory);
// Resets the xHCI controller. This should only be called during initialization.
void ResetController();
// Initializes PCI
zx_status_t InitPci();
// Initializes MMIO
zx_status_t InitMmio();
// Performs the handoff from the BIOS to the xHCI driver
void BiosHandoff();
// Performs platform-specific initialization functions
void InitQuirks();
zx_status_t HciFinalize();
const zx::bti& bti() const { return bti_; }
size_t get_page_size() const { return page_size_; }
bool get_is_32_bit_controller() const { return is_32bit_; }
// Asynchronously submits a command to the command queue.
TRBPromise SubmitCommand(const TRB& command, std::unique_ptr<TRBContext> trb_context);
// Retrieves the current test harness
void* get_test_harness() const { return test_harness_; }
// Sets the test harness
void set_test_harness(void* harness) { test_harness_ = harness; }
dma_buffer::BufferFactory& buffer_factory() const { return *buffer_factory_; }
private:
DISALLOW_COPY_ASSIGN_AND_MOVE(UsbXhci);
// Tracks the state of a USB request
// This state is passed around to the various transfer request
// queueing methods, and its lifetime should not outlast
// the lifetime of the transaction. This struct should be
// stack-allocated.
// None of the values in this field should be accessed
// after the USB transaction has been sent to hardware.
struct UsbRequestState {
// Invokes the completion callback if the request was marked as completed.
// Returns true if the completer was called, false otherwise.
bool Complete();
// Request status
zx_status_t status;
// Number of bytes transferred
size_t bytes_transferred = 0;
// Whether or not the request is complete
bool complete = false;
// Size of the slot
size_t slot_size;
// Max burst size (value of the max burst size register + 1, since it is zero-based)
uint32_t burst_size;
// Max packet size
uint32_t max_packet_size;
// True if the current transfer is isochronous
bool is_isochronous_transfer;
// First TRB in the transfer
// This is owned by the transfer ring.
TRB* first_trb = nullptr;
// Value to set the cycle bit on the first TRB to
bool first_cycle;
// TransferRing transaction state
TransferRing::State transaction;
// The transfer ring to post transactions to
// This is owned by UsbXhci and is valid for
// the duration of this transaction.
TransferRing* transfer_ring;
// Index of the transfer ring
uint8_t index;
// Transfer context
std::unique_ptr<TRBContext> context;
// The number of packets in the transfer
size_t packet_count = 0;
// The slot ID of the transfer
uint8_t slot;
// Total length of the transfer
uint32_t total_len = 0;
// The setup TRB
// This is owned by the transfer ring.
TRB* setup;
// The interrupter to use
uint8_t interrupter = 0;
// Pointer to the status TRB
// This is owned by the transfer ring.
TRB* status_trb_ptr = nullptr;
// Cycle bit of the setup TRB during the allocation phase
bool setup_cycle;
// Last TRB in the transfer
// This is owned by the transfer ring.
TRB* last_trb;
};
// Waits for a time interval when it is suitable to schedule an isochronous transfer
void WaitForIsochronousReady(UsbRequestState* state);
// Starts a normal transfer
void StartNormalTransaction(UsbRequestState* state);
// Continues a normal transfer
void ContinueNormalTransaction(UsbRequestState* state);
// Commits a normal transfer
void CommitNormalTransaction(UsbRequestState* state);
// Performs the allocation phase of the control request
// (allocates TRBs for the request)
void ControlRequestAllocationPhase(UsbRequestState* state);
// Performs the status phase of a control request
static void ControlRequestStatusPhase(UsbRequestState* state);
// Performs the data transfer phase of a control request
void ControlRequestDataPhase(UsbRequestState* state);
// Performs the setup phase of a control request
static void ControlRequestSetupPhase(UsbRequestState* state);
// Starts the transfer of a control request
void ControlRequestCommit(UsbRequestState* state);
// Global scheduler lock. This should be held when adding or removing
// interrupters, and; eventually dynamically assigning transfer rings
// to interrupters.
fbl::Mutex scheduler_lock_;
// This is a high-priority profile used for increasing the priority
// of the interrupt thread. This is currently necessary to mitigate
// fxb/34507, and can be removed once the scheduling problem is fixed.
zx::profile profile_;
// Performs the initialization sequence defined in section
// 4.2 of the xHCI specification.
zx_status_t Init();
// PCI protocol client (if x86)
ddk::Pci pci_;
// PDev (if ARM)
ddk::PDev pdev_;
// Composite device protocol client used for communicating with the USB PHY
// on certain ARM boards which support USB OTG.
ddk::CompositeProtocolClient composite_;
// MMIO buffer for communicating with the physical hardware
// Must be optional to allow for asynchronous initialization,
// since an MmioBuffer has no default constructor.
std::optional<ddk::MmioBuffer> mmio_;
// The number of IRQs supported by the HCI
uint32_t irq_count_;
// Array of interrupters, which service interrupts from the HCI
std::unique_ptr<Interrupter[]> interrupters_;
// Pointer to the start of the device context base address array
// See xHCI section 6.1 for more information.
uint64_t* dcbaa_;
// IO buffer for the device context base address array
std::unique_ptr<dma_buffer::PagedBuffer> dcbaa_buffer_;
// BTI for retrieving physical memory addresses from IO buffers.
zx::bti bti_;
// xHCI scratchpad buffers (see xHCI section 4.20)
std::unique_ptr<std::unique_ptr<dma_buffer::ContiguousBuffer>[]> scratchpad_buffers_;
// IO buffer for the scratchpad buffer array
std::unique_ptr<dma_buffer::PagedBuffer> scratchpad_buffer_array_;
std::unique_ptr<dma_buffer::BufferFactory> buffer_factory_;
// Page size of the HCI
size_t page_size_;
// xHCI command ring (see xHCI section 4.6.1)
CommandRing command_ring_;
// Whether or not the host controller is 32 bit
bool is_32bit_ = false;
// Whether or not the HCI's cache is coherent with the CPU
bool has_coherent_cache_ = false;
// Offset to the doorbells. See xHCI section 5.3.7
DoorbellOffset doorbell_offset_;
// The number of enabled interrupters
uint32_t active_interrupters_ __TA_GUARDED(scheduler_lock_);
// The value in the CAPLENGTH register (see xHCI section 5.3.1)
uint8_t cap_length_;
// The last recorded MFINDEX value
std::atomic<uint32_t> last_mfindex_ = 0;
// Runtime register offset (see xHCI section 5.3.8)
RuntimeRegisterOffset runtime_offset_;
// Status information on connected devices
std::unique_ptr<DeviceState[]> device_state_;
// Status information for each port in the system
std::unique_ptr<PortState[]> port_state_;
// Completion which is signalled when the bus interface is bound
sync_completion_t bus_completion;
// Completion which is signalled when xHCI enters an operational state
sync_completion_t bringup_;
// HCSPARAMS1 register (see xHCI section 5.3.3)
HCSPARAMS1 params_;
// HCCPARAMS1 register (see xHCI section 5.3.6)
HCCPARAMS1 hcc_;
// Number of slots supported by the HCI
size_t max_slots_;
// Whether or not we are running on Qemu
bool qemu_quirk_ = false;
// Number of times the MFINDEX has wrapped
std::atomic_uint64_t wrap_count_ = 0;
// USB bus protocol client
ddk::UsbBusInterfaceProtocolClient bus_;
async::Loop ddk_interaction_loop_;
// Pending DDK callbacks that need to be ran on the dedicated DDK interaction thread
async::Executor ddk_interaction_executor_;
// Thread for interacting with the Devhost thread (main event loop)
std::optional<thrd_t> ddk_interaction_thread_;
// Whether or not the HCI instance is currently active
std::atomic_bool running_ = true;
// PHY protocol
ddk::UsbPhyProtocolClient phy_;
// Pointer to the test harness when being called from a unit test
// This is an opaque pointer that is managed by the test.
void* test_harness_;
// Completion event which is signalled when driver initialization finishes
sync_completion_t init_complete_;
std::optional<thrd_t> init_thread_;
};
} // namespace usb_xhci
#endif // SRC_DEVICES_USB_DRIVERS_XHCI_REWRITE_USB_XHCI_H_