| // 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_ |