blob: c24c4c838eb5537bfe2844e57bbe8ac0759c14e2 [file] [log] [blame]
// Copyright 2017 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_DWC3_DWC3_H_
#define SRC_DEVICES_USB_DRIVERS_DWC3_DWC3_H_
#include <lib/ddk/io-buffer.h>
#include <lib/device-protocol/pdev-fidl.h>
#include <lib/mmio/mmio.h>
#include <lib/zircon-internal/thread_annotations.h>
#include <lib/zx/result.h>
#include <array>
#include <ddktl/device.h>
#include <fbl/auto_lock.h>
#include <fbl/intrusive_double_list.h>
#include <fbl/mutex.h>
#include <usb/request-cpp.h>
#include <usb/usb.h>
#include "fuchsia/hardware/usb/dci/cpp/banjo.h"
#include "fuchsia/hardware/usb/descriptor/cpp/banjo.h"
#include "src/devices/usb/drivers/dwc3/dwc3-types.h"
namespace dwc3 {
class Dwc3;
using Dwc3Type = ddk::Device<Dwc3, ddk::Initializable, ddk::Unbindable>;
class Dwc3 : public Dwc3Type, public ddk::UsbDciProtocol<Dwc3, ddk::base_protocol> {
public:
explicit Dwc3(zx_device_t* parent) : Dwc3Type(parent) {}
static zx_status_t Create(void* ctx, zx_device_t* parent);
// Device protocol implementation.
void DdkInit(ddk::InitTxn txn);
void DdkUnbind(ddk::UnbindTxn txn);
void DdkRelease();
// 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);
private:
static inline const uint32_t kEventBufferSize = zx_system_get_page_size();
// physical endpoint numbers. We use 0 and 1 for EP0, and let the device-mode
// driver use the rest.
static inline constexpr uint8_t kEp0Out = 0;
static inline constexpr uint8_t kEp0In = 1;
static inline constexpr uint8_t kUserEndpointStartNum = 2;
static inline constexpr size_t kEp0MaxPacketSize = 512;
static inline constexpr zx::duration kHwResetTimeout{zx::msec(50)};
using Request = usb::BorrowedRequest<void>;
using RequestQueue = usb::BorrowedRequestQueue<void>;
enum class IrqSignal : uint32_t {
Invalid = 0,
Exit = 1,
Wakeup = 2,
};
struct Fifo {
static inline const uint32_t kFifoSize = zx_system_get_page_size();
zx_status_t Init(zx::bti& bti);
void Release();
zx_paddr_t GetTrbPhys(dwc3_trb_t* trb) const {
ZX_DEBUG_ASSERT((trb >= first) && (trb <= last));
return buffer.phys() + ((trb - first) * sizeof(*trb));
}
ddk::IoBuffer buffer;
dwc3_trb_t* first{nullptr}; // first TRB in the fifo
dwc3_trb_t* next{nullptr}; // next free TRB in the fifo
dwc3_trb_t* current{nullptr}; // TRB for currently pending transaction
dwc3_trb_t* last{nullptr}; // last TRB in the fifo (link TRB)
};
struct Endpoint {
Endpoint() = default;
explicit Endpoint(uint8_t ep_num) : ep_num(ep_num) {}
Endpoint(const Endpoint&) = delete;
Endpoint& operator=(const Endpoint&) = delete;
Endpoint(Endpoint&&) = delete;
Endpoint& operator=(Endpoint&&) = delete;
static inline constexpr bool IsOutput(uint8_t ep_num) { return (ep_num & 0x1) == 0; }
static inline constexpr bool IsInput(uint8_t ep_num) { return (ep_num & 0x1) == 1; }
bool IsOutput() const { return IsOutput(ep_num); }
bool IsInput() const { return IsInput(ep_num); }
RequestQueue queued_reqs; // requests waiting to be processed
usb_request_t* current_req{nullptr}; // request currently being processed
uint32_t rsrc_id{0}; // resource ID for current_req
const uint8_t ep_num{0};
uint8_t type{0}; // control, bulk, interrupt or isochronous
uint8_t interval{0};
uint16_t max_packet_size{0};
bool enabled{false};
// TODO(voydanoff) USB 3 specific stuff here
bool got_not_ready{false};
bool stalled{false};
};
struct UserEndpoint {
UserEndpoint() = default;
UserEndpoint(const UserEndpoint&) = delete;
UserEndpoint& operator=(const UserEndpoint&) = delete;
UserEndpoint(UserEndpoint&&) = delete;
UserEndpoint& operator=(UserEndpoint&&) = delete;
// Used for synchronizing endpoint state and ep specific hardware registers
// This should be acquired before Dwc3::lock_ if acquiring both locks.
fbl::Mutex lock;
TA_GUARDED(lock) Fifo fifo;
TA_GUARDED(lock) Endpoint ep;
};
// A small helper class which basically allows us to have a collection of user
// endpoints which is dynamically allocated at startup, but which will never
// change in size. std::array is not an option here, as it is sized at
// compile time, while std::vector would force us to make user endpoints
// movable objects (which we really don't want to do). Basically, this is a
// lot of typing to get a C-style array which knows its size and supports
// range based iteration.
class UserEndpointCollection {
public:
void Init(size_t count) {
ZX_DEBUG_ASSERT(count <= (std::numeric_limits<uint8_t>::max() - kUserEndpointStartNum));
ZX_DEBUG_ASSERT(count_ == 0);
ZX_DEBUG_ASSERT(endpoints_.get() == nullptr);
count_ = count;
endpoints_.reset(new UserEndpoint[count_]);
for (size_t i = 0; i < count_; ++i) {
UserEndpoint& uep = endpoints_[i];
fbl::AutoLock lock(&uep.lock);
const_cast<uint8_t&>(uep.ep.ep_num) = static_cast<uint8_t>(i) + kUserEndpointStartNum;
}
}
// Standard size and index-operator
size_t size() const { return count_; }
UserEndpoint& operator[](size_t ndx) {
ZX_DEBUG_ASSERT(ndx < count_);
return endpoints_[ndx];
}
// Support for range-based for loops.
UserEndpoint* begin() { return endpoints_.get() + 0; }
UserEndpoint* end() { return endpoints_.get() + count_; }
const UserEndpoint* begin() const { return endpoints_.get() + 0; }
const UserEndpoint* end() const { return endpoints_.get() + count_; }
private:
size_t count_{0};
std::unique_ptr<UserEndpoint[]> endpoints_;
};
struct Ep0 {
Ep0() : out(kEp0Out), in(kEp0In) {}
Ep0(const Ep0&) = delete;
Ep0& operator=(const Ep0&) = delete;
Ep0(Ep0&&) = delete;
Ep0& operator=(Ep0&&) = delete;
enum class State {
None,
Setup, // Queued setup phase
DataOut, // Queued data on EP0_OUT
DataIn, // Queued data on EP0_IN
WaitNrdyOut, // Waiting for not-ready on EP0_OUT
WaitNrdyIn, // Waiting for not-ready on EP0_IN
Status, // Waiting for status to complete
};
fbl::Mutex lock;
TA_GUARDED(lock) Fifo shared_fifo;
TA_GUARDED(lock) ddk::IoBuffer buffer;
TA_GUARDED(lock) State state{Ep0::State::None};
TA_GUARDED(lock) Endpoint out;
TA_GUARDED(lock) Endpoint in;
TA_GUARDED(lock) usb_setup_t cur_setup; // current setup request
TA_GUARDED(lock) usb_speed_t cur_speed = USB_SPEED_UNDEFINED;
};
constexpr bool is_ep0_num(uint8_t ep_num) { return ((ep_num == kEp0Out) || (ep_num == kEp0In)); }
UserEndpoint* get_user_endpoint(uint8_t ep_num) {
if (ep_num >= kUserEndpointStartNum) {
const uint8_t ndx = ep_num - kUserEndpointStartNum;
return (ndx < user_endpoints_.size()) ? &user_endpoints_[ndx] : nullptr;
}
return nullptr;
}
fdf::MmioBuffer* get_mmio() { return &*mmio_; }
static uint8_t UsbAddressToEpNum(uint8_t addr) {
return static_cast<uint8_t>(((addr & 0xF) << 1) | !!(addr & USB_DIR_IN));
}
zx_status_t AcquirePDevResources();
zx_status_t Init();
void ReleaseResources();
// The IRQ thread and its two top level event decoders.
int IrqThread();
void HandleEvent(uint32_t event);
void HandleEpEvent(uint32_t event);
[[nodiscard]] zx_status_t SignalIrqThread(IrqSignal signal) {
if (!irq_bound_to_port_) {
return ZX_ERR_BAD_STATE;
}
zx_port_packet_t pkt{
.key = 0,
.type = ZX_PKT_TYPE_USER,
.status = ZX_OK,
};
pkt.user.u32[0] = static_cast<std::underlying_type_t<decltype(signal)>>(signal);
return irq_port_.queue(&pkt);
}
IrqSignal GetIrqSignal(const zx_port_packet_t& pkt) {
if (pkt.type != ZX_PKT_TYPE_USER) {
return IrqSignal::Invalid;
}
return static_cast<IrqSignal>(pkt.user.u32[0]);
}
// Handlers for global events posted to the event buffer by the controller HW.
void HandleResetEvent() TA_EXCL(lock_);
void HandleConnectionDoneEvent() TA_EXCL(lock_);
void HandleDisconnectedEvent() TA_EXCL(lock_);
// Handlers for end-point specific events posted to the event buffer by the controller HW.
void HandleEpTransferCompleteEvent(uint8_t ep_num) TA_EXCL(lock_);
void HandleEpTransferNotReadyEvent(uint8_t ep_num, uint32_t stage) TA_EXCL(lock_);
void HandleEpTransferStartedEvent(uint8_t ep_num, uint32_t rsrc_id) TA_EXCL(lock_);
[[nodiscard]] zx_status_t CheckHwVersion() TA_REQ(lock_);
[[nodiscard]] zx_status_t ResetHw() TA_REQ(lock_);
void StartEvents() TA_REQ(lock_);
void SetDeviceAddress(uint32_t address) TA_REQ(lock_);
// EP0 stuff
zx_status_t Ep0Init() TA_EXCL(lock_);
void Ep0Reset() TA_EXCL(lock_);
void Ep0Start() TA_EXCL(lock_);
void Ep0QueueSetupLocked() TA_REQ(ep0_.lock) TA_EXCL(lock_);
void Ep0StartEndpoints() TA_REQ(ep0_.lock) TA_EXCL(lock_);
zx::result<size_t> HandleEp0Setup(const usb_setup_t& setup, void* buffer, size_t length)
TA_REQ(ep0_.lock) TA_EXCL(lock_);
void HandleEp0TransferCompleteEvent(uint8_t ep_num) TA_EXCL(lock_, ep0_.lock);
void HandleEp0TransferNotReadyEvent(uint8_t ep_num, uint32_t stage) TA_EXCL(lock_, ep0_.lock);
// General EP stuff
void EpEnable(const Endpoint& ep, bool enable) TA_EXCL(lock_);
void EpSetConfig(Endpoint& ep, bool enable) TA_EXCL(lock_);
zx_status_t EpSetStall(Endpoint& ep, bool stall) TA_EXCL(lock_);
void EpStartTransfer(Endpoint& ep, Fifo& fifo, uint32_t type, zx_paddr_t buffer, size_t length,
bool send_zlp) TA_EXCL(lock_);
void EpEndTransfers(Endpoint& ep, zx_status_t reason) TA_EXCL(lock_);
void EpReadTrb(Endpoint& ep, Fifo& fifo, const dwc3_trb_t* src, dwc3_trb_t* dst) TA_EXCL(lock_);
// Methods specific to user endpoints
void UserEpQueueNext(UserEndpoint& uep) TA_REQ(uep.lock) TA_EXCL(lock_);
zx_status_t UserEpCancelAll(UserEndpoint& uep) TA_EXCL(lock_, uep.lock);
// Cancel all currently in flight requests, and return a list of requests
// which were in-flight. Note that these requests have not been completed
// yet. It is the responsibility of the caller to (eventually) take care of
// this once the lock has been dropped.
[[nodiscard]] RequestQueue UserEpCancelAllLocked(UserEndpoint& uep) TA_REQ(uep.lock)
TA_EXCL(lock_);
// Commands
void CmdStartNewConfig(const Endpoint& ep, uint32_t rsrc_id) TA_EXCL(lock_);
void CmdEpSetConfig(const Endpoint& ep, bool modify) TA_EXCL(lock_);
void CmdEpTransferConfig(const Endpoint& ep) TA_EXCL(lock_);
void CmdEpStartTransfer(const Endpoint& ep, zx_paddr_t trb_phys) TA_EXCL(lock_);
void CmdEpEndTransfer(const Endpoint& ep) TA_EXCL(lock_);
void CmdEpSetStall(const Endpoint& ep) TA_EXCL(lock_);
void CmdEpClearStall(const Endpoint& ep) TA_EXCL(lock_);
// Start to operate in peripheral mode.
void StartPeripheralMode() TA_EXCL(lock_);
void ResetConfiguration() TA_EXCL(lock_);
fbl::Mutex lock_;
fbl::Mutex dci_lock_;
ddk::PDevFidl pdev_;
TA_GUARDED(dci_lock_) std::optional<ddk::UsbDciInterfaceProtocolClient> dci_intf_;
std::optional<ddk::MmioBuffer> mmio_;
zx::bti bti_;
bool has_pinned_memory_{false};
zx::interrupt irq_;
zx::port irq_port_;
bool irq_bound_to_port_{false};
thrd_t irq_thread_;
std::atomic<bool> irq_thread_started_{false};
ddk::IoBuffer event_buffer_;
Ep0 ep0_;
UserEndpointCollection user_endpoints_;
RequestQueue pending_completions_;
// TODO(johngro): What lock protects this? Right now, it is effectively
// endpoints_[0].lock(), but how do we express this?
bool configured_ = false;
};
} // namespace dwc3
#endif // SRC_DEVICES_USB_DRIVERS_DWC3_DWC3_H_