blob: 7c4810e131dc1ecdefd2784b775812067a8f4e08 [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_DWC2_DWC2_H_
#define SRC_DEVICES_USB_DRIVERS_DWC2_DWC2_H_
#include <fidl/fuchsia.hardware.usb.dci/cpp/fidl.h>
#include <fuchsia/hardware/platform/device/cpp/banjo.h>
#include <fuchsia/hardware/usb/dci/cpp/banjo.h>
#include <fuchsia/hardware/usb/phy/cpp/banjo.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/component/outgoing/cpp/outgoing_directory.h>
#include <lib/ddk/debug.h>
#include <lib/ddk/device.h>
#include <lib/device-protocol/pdev-fidl.h>
#include <lib/mmio/mmio.h>
#include <lib/sync/completion.h>
#include <threads.h>
#include <atomic>
#include <queue>
#include <ddktl/device.h>
#include <fbl/mutex.h>
#include <usb/dwc2/metadata.h>
#include <usb/request-cpp.h>
#include <usb/usb.h>
#include "src/devices/usb/drivers/dwc2/usb_dwc_regs.h"
#include "src/devices/usb/lib/usb-endpoint/include/usb-endpoint/usb-endpoint-server.h"
#include "src/devices/usb/lib/usb-phy/include/usb-phy/usb-phy.h"
namespace dwc2 {
class Dwc2;
using Dwc2Type = ddk::Device<Dwc2, ddk::Initializable, ddk::Unbindable, ddk::Suspendable>;
class Dwc2 : public Dwc2Type,
public ddk::UsbDciProtocol<Dwc2, ddk::base_protocol>,
public fidl::Server<fuchsia_hardware_usb_dci::UsbDci> {
public:
explicit Dwc2(zx_device_t* parent, async_dispatcher_t* dispatcher)
: Dwc2Type(parent), dispatcher_(dispatcher), outgoing_(dispatcher) {}
static zx_status_t Create(void* ctx, zx_device_t* parent);
zx_status_t Init();
int IrqThread();
// Device protocol implementation.
void DdkInit(ddk::InitTxn txn);
void DdkUnbind(ddk::UnbindTxn txn);
void DdkRelease();
void DdkSuspend(ddk::SuspendTxn txn);
// USB DCI protocol implementation.
void UsbDciRequestQueue(usb_request_t* req, const usb_request_complete_callback_t* cb);
zx_status_t UsbDciSetInterface(const usb_dci_interface_protocol_t* interface);
zx_status_t UsbDciConfigEp(const usb_endpoint_descriptor_t* ep_desc,
const usb_ss_ep_comp_descriptor_t* ss_comp_desc);
zx_status_t UsbDciDisableEp(uint8_t ep_address);
zx_status_t UsbDciEpSetStall(uint8_t ep_address);
zx_status_t UsbDciEpClearStall(uint8_t ep_address);
size_t UsbDciGetRequestSize();
zx_status_t UsbDciCancelAll(uint8_t ep_address);
// fuchsia_hardware_usb_dci::UsbDci protocol implementation.
void ConnectToEndpoint(ConnectToEndpointRequest& request,
ConnectToEndpointCompleter::Sync& completer) override;
// Allows tests to configure a fake interrupt.
void SetInterrupt(zx::interrupt irq) { irq_ = std::move(irq); }
const zx::bti& bti() const { return bti_; }
private:
enum class Ep0State {
DISCONNECTED,
IDLE,
DATA,
STATUS,
STALL,
TIMEOUT_RECOVERY,
};
// clang-format off
const char* Ep0StateToStr(Ep0State s) {
switch (s) {
case Ep0State::DISCONNECTED: return "DISCONNECTED";
case Ep0State::IDLE: return "IDLE";
case Ep0State::DATA: return "DATA";
case Ep0State::STATUS: return "STATUS";
case Ep0State::STALL: return "STALL";
case Ep0State::TIMEOUT_RECOVERY: return "TIMEOUT_RECOVERY";
}
}
// clang-format on
class Endpoint : public usb_endpoint::UsbEndpoint {
public:
Endpoint(uint8_t ep_num, Dwc2* dwc2)
: usb_endpoint::UsbEndpoint(dwc2->bti_, ep_num), dwc2_(dwc2) {
loop_.StartThread("dwc2-enpdoint-loop");
}
// fuchsia_hardware_usb_endpoint::Endpoint protocol implementation.
void GetInfo(GetInfoCompleter::Sync& completer) override {
completer.Reply(fit::as_error(ZX_ERR_NOT_SUPPORTED));
}
void QueueRequests(QueueRequestsRequest& request,
QueueRequestsCompleter::Sync& completer) override;
void CancelAll(CancelAllCompleter::Sync& completer) override {
CancelAll();
completer.Reply(fit::ok());
}
void QueueRequest(usb_endpoint::RequestVariant request);
void CancelAll();
async_dispatcher_t* dispatcher() { return loop_.dispatcher(); }
// Requests waiting to be processed.
std::queue<usb_endpoint::RequestVariant> queued_reqs __TA_GUARDED(lock);
// Request currently being processed.
std::optional<usb_endpoint::RequestVariant> current_req __TA_GUARDED(lock);
// Values for current USB request
uint32_t req_offset = 0;
uint32_t req_xfersize = 0;
uint32_t req_length = 0;
uint32_t phys = 0;
// Used for synchronizing endpoint state and ep specific hardware registers.
// This should be acquired before Dwc2.lock_ if acquiring both locks.
fbl::Mutex lock;
uint16_t max_packet_size = 0;
bool enabled = false;
private:
async::Loop loop_{&kAsyncLoopConfigNeverAttachToThread};
Dwc2* dwc2_;
};
DISALLOW_COPY_ASSIGN_AND_MOVE(Dwc2);
void FlushTxFifo(uint32_t fifo_num);
void FlushRxFifo();
void FlushTxFifoRetryIndefinite(uint32_t fifo_num);
void FlushRxFifoRetryIndefinite();
zx_status_t InitController();
void SetConnected(bool connected);
void StartEp0();
void StartEndpoints();
void HandleEp0Setup();
void HandleEp0Status(bool is_in);
void HandleEp0TimeoutRecovery();
void HandleEp0TransferComplete(bool is_in);
void HandleTransferComplete(uint8_t ep_num);
void EnableEp(uint8_t ep_num, bool enable);
void QueueNextRequest(Endpoint* ep) __TA_REQUIRES(ep->lock);
void StartTransfer(Endpoint* ep, uint32_t length) __TA_REQUIRES(ep->lock);
void SoftDisconnect() __TA_REQUIRES(lock_);
uint32_t ReadTransfered(Endpoint* ep);
// Interrupt handlers
void HandleReset();
void HandleSuspend();
void HandleEnumDone();
void HandleInEpInterrupt();
void HandleOutEpInterrupt();
zx_status_t HandleSetupRequest(size_t* out_actual);
void SetAddress(uint8_t address);
inline fdf::MmioBuffer* get_mmio() { return &*mmio_; }
// Used for debugging.
void dump_regs();
std::optional<Endpoint> endpoints_[DWC_MAX_EPS];
// Used for synchronizing global state
// and non ep specific hardware registers.
// Endpoint.lock should be acquired first
// when acquiring both locks.
fbl::Mutex lock_;
zx::bti bti_;
// DMA buffer for endpoint zero requests
ddk::IoBuffer ep0_buffer_;
// Current endpoint zero request
usb_setup_t cur_setup_ = {};
Ep0State ep0_state_ = Ep0State::DISCONNECTED;
ddk::PDevFidl pdev_;
std::optional<ddk::UsbDciInterfaceProtocolClient> dci_intf_;
std::optional<usb_phy::UsbPhyClient> usb_phy_;
std::optional<fdf::MmioBuffer> mmio_;
zx::interrupt irq_;
thrd_t irq_thread_;
// True if |irq_thread_| can be joined.
std::atomic_bool irq_thread_started_ = false;
dwc2_metadata_t metadata_;
bool connected_ = false;
bool configured_ = false;
// The length of the last IN-data sent to the host.
uint32_t last_transmission_len_;
// Raw IRQ timestamp from kernel
zx::time irq_timestamp_;
// Timestamp we were dispatched at
zx::time irq_dispatch_timestamp_;
// Timestamp when we started waiting for the interrupt
zx::time wait_start_time_;
bool shutting_down_ __TA_GUARDED(lock_) = false;
async_dispatcher_t* dispatcher_;
component::OutgoingDirectory outgoing_;
fidl::ServerBindingGroup<fuchsia_hardware_usb_dci::UsbDci> bindings_;
};
} // namespace dwc2
#endif // SRC_DEVICES_USB_DRIVERS_DWC2_DWC2_H_