blob: 945cc77d2ba0ac823dd8ca55e8e48a93fa27628a [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.
#include <lib/fit/defer.h>
#include "src/devices/usb/drivers/dwc3/dwc3-regs.h"
#include "src/devices/usb/drivers/dwc3/dwc3-types.h"
#include "src/devices/usb/drivers/dwc3/dwc3.h"
namespace dwc3 {
void Dwc3::HandleEpEvent(uint32_t event) {
const uint32_t type = DEPEVT_TYPE(event);
const uint8_t ep_num = DEPEVT_PHYS_EP(event);
const uint32_t status = DEPEVT_STATUS(event);
switch (type) {
case DEPEVT_XFER_COMPLETE:
zxlogf(SERIAL, "ep[%u] DEPEVT_XFER_COMPLETE", ep_num);
HandleEpTransferCompleteEvent(ep_num);
break;
case DEPEVT_XFER_IN_PROGRESS:
zxlogf(SERIAL, "ep[%u] DEPEVT_XFER_IN_PROGRESS: status %u", ep_num, status);
break;
case DEPEVT_XFER_NOT_READY:
zxlogf(SERIAL, "ep[%u] DEPEVT_XFER_NOT_READY", ep_num);
HandleEpTransferNotReadyEvent(ep_num, DEPEVT_XFER_NOT_READY_STAGE(event));
break;
case DEPEVT_STREAM_EVT:
zxlogf(SERIAL, "ep[%u] DEPEVT_STREAM_EVT ep_num: status %u", ep_num, status);
break;
case DEPEVT_CMD_CMPLT: {
uint32_t cmd_type = DEPEVT_CMD_CMPLT_CMD_TYPE(event);
uint32_t rsrc_id = DEPEVT_CMD_CMPLT_RSRC_ID(event);
zxlogf(SERIAL, "ep[%u] DEPEVT_CMD_COMPLETE: type %u rsrc_id %u", ep_num, cmd_type, rsrc_id);
if (cmd_type == DEPCMD::DEPSTRTXFER) {
HandleEpTransferStartedEvent(ep_num, rsrc_id);
}
break;
}
default:
zxlogf(ERROR, "dwc3_handle_ep_event: unknown event type %u", type);
break;
}
}
void Dwc3::HandleEvent(uint32_t event) {
if (!(event & DEPEVT_NON_EP)) {
HandleEpEvent(event);
return;
}
uint32_t type = DEVT_TYPE(event);
uint32_t info = DEVT_INFO(event);
switch (type) {
case DEVT_DISCONNECT:
zxlogf(SERIAL, "DEVT_DISCONNECT");
break;
case DEVT_USB_RESET:
zxlogf(SERIAL, "DEVT_USB_RESET");
HandleResetEvent();
break;
case DEVT_CONNECTION_DONE:
zxlogf(SERIAL, "DEVT_CONNECTION_DONE");
HandleConnectionDoneEvent();
break;
case DEVT_LINK_STATE_CHANGE:
zxlogf(SERIAL, "DEVT_LINK_STATE_CHANGE: ");
switch (info) {
case DSTS::USBLNKST_U0 | DEVT_LINK_STATE_CHANGE_SS:
zxlogf(SERIAL, "DSTS::USBLNKST_U0");
break;
case DSTS::USBLNKST_U1 | DEVT_LINK_STATE_CHANGE_SS:
zxlogf(SERIAL, "DSTS_USBLNKST_U1");
break;
case DSTS::USBLNKST_U2 | DEVT_LINK_STATE_CHANGE_SS:
zxlogf(SERIAL, "DSTS_USBLNKST_U2");
break;
case DSTS::USBLNKST_U3 | DEVT_LINK_STATE_CHANGE_SS:
zxlogf(SERIAL, "DSTS_USBLNKST_U3");
break;
case DSTS::USBLNKST_ESS_DIS | DEVT_LINK_STATE_CHANGE_SS:
zxlogf(SERIAL, "DSTS_USBLNKST_ESS_DIS");
break;
case DSTS::USBLNKST_RX_DET | DEVT_LINK_STATE_CHANGE_SS:
zxlogf(SERIAL, "DSTS_USBLNKST_RX_DET");
break;
case DSTS::USBLNKST_ESS_INACT | DEVT_LINK_STATE_CHANGE_SS:
zxlogf(SERIAL, "DSTS_USBLNKST_ESS_INACT");
break;
case DSTS::USBLNKST_POLL | DEVT_LINK_STATE_CHANGE_SS:
zxlogf(SERIAL, "DSTS_USBLNKST_POLL");
break;
case DSTS::USBLNKST_RECOV | DEVT_LINK_STATE_CHANGE_SS:
zxlogf(SERIAL, "DSTS_USBLNKST_RECOV");
break;
case DSTS::USBLNKST_HRESET | DEVT_LINK_STATE_CHANGE_SS:
zxlogf(SERIAL, "DSTS_USBLNKST_HRESET");
break;
case DSTS::USBLNKST_CMPLY | DEVT_LINK_STATE_CHANGE_SS:
zxlogf(SERIAL, "DSTS_USBLNKST_CMPLY");
break;
case DSTS::USBLNKST_LPBK | DEVT_LINK_STATE_CHANGE_SS:
zxlogf(SERIAL, "DSTS_USBLNKST_LPBK");
break;
case DSTS::USBLNKST_RESUME_RESET | DEVT_LINK_STATE_CHANGE_SS:
zxlogf(SERIAL, "DSTS_USBLNKST_RESUME_RESET");
break;
case DSTS::USBLNKST_ON:
zxlogf(SERIAL, "DSTS_USBLNKST_ON");
break;
case DSTS::USBLNKST_SLEEP:
zxlogf(SERIAL, "DSTS_USBLNKST_SLEEP");
break;
case DSTS::USBLNKST_SUSPEND:
zxlogf(SERIAL, "DSTS_USBLNKST_SUSPEND");
break;
case DSTS::USBLNKST_DISCONNECTED:
zxlogf(SERIAL, "DSTS_USBLNKST_DISCONNECTED");
break;
case DSTS::USBLNKST_EARLY_SUSPEND:
zxlogf(SERIAL, "DSTS_USBLNKST_EARLY_SUSPEND");
break;
case DSTS::USBLNKST_RESET:
zxlogf(SERIAL, "DSTS_USBLNKST_RESET");
break;
case DSTS::USBLNKST_RESUME:
zxlogf(SERIAL, "DSTS_USBLNKST_RESUME");
break;
default:
zxlogf(ERROR, "unknown state %d", info);
break;
}
break;
case DEVT_REMOTE_WAKEUP:
zxlogf(SERIAL, "DEVT_REMOTE_WAKEUP");
break;
case DEVT_HIBERNATE_REQUEST:
zxlogf(SERIAL, "DEVT_HIBERNATE_REQUEST");
break;
case DEVT_SUSPEND_ENTRY:
zxlogf(SERIAL, "DEVT_SUSPEND_ENTRY");
// TODO(voydanoff) is this the best way to detect disconnect?
HandleDisconnectedEvent();
break;
case DEVT_SOF:
zxlogf(SERIAL, "DEVT_SOF");
break;
case DEVT_ERRATIC_ERROR:
zxlogf(SERIAL, "DEVT_ERRATIC_ERROR");
break;
case DEVT_COMMAND_COMPLETE:
zxlogf(SERIAL, "DEVT_COMMAND_COMPLETE");
break;
case DEVT_EVENT_BUF_OVERFLOW:
zxlogf(SERIAL, "DEVT_EVENT_BUF_OVERFLOW");
break;
case DEVT_VENDOR_TEST_LMP:
zxlogf(SERIAL, "DEVT_VENDOR_TEST_LMP");
break;
case DEVT_STOPPED_DISCONNECT:
zxlogf(SERIAL, "DEVT_STOPPED_DISCONNECT");
break;
case DEVT_L1_RESUME_DETECT:
zxlogf(SERIAL, "DEVT_L1_RESUME_DETECT");
break;
case DEVT_LDM_RESPONSE:
zxlogf(SERIAL, "DEVT_LDM_RESPONSE");
break;
default:
zxlogf(ERROR, "dwc3_handle_event: unknown event type %u", type);
break;
}
}
int Dwc3::IrqThread() {
auto* mmio = get_mmio();
const uint32_t* const ring_start = static_cast<const uint32_t*>(event_buffer_.virt());
const uint32_t* const ring_end = ring_start + (kEventBufferSize / sizeof(*ring_start));
const uint32_t* ring_cur = ring_start;
bool shutdown_now = false;
while (!shutdown_now) {
// Perform the callbacks for any requests which are pending completion.
for (auto req = pending_completions_.pop(); req; req = pending_completions_.pop()) {
const zx_status_t status = req->request()->response.status;
const zx_off_t actual = req->request()->response.actual;
req->Complete(status, actual);
}
// Wait for a new interrupt.
zx_port_packet_t wakeup_pkt;
if (zx_status_t status = irq_port_.wait(zx::time::infinite(), &wakeup_pkt); status != ZX_OK) {
zxlogf(ERROR, "Dwc3::IrqThread: zx_port_wait returned %s", zx_status_get_string(status));
shutdown_now = true;
continue;
}
// Was this an actual HW interrupt? If so, process any new events in the
// event buffer.
if (wakeup_pkt.type == ZX_PKT_TYPE_INTERRUPT) {
// Our interrupt should be edge triggered, so go ahead and ack and re-enable
// it now so that we don't accidentally miss any new interrupts while
// process these.
irq_.ack();
uint32_t event_count;
while ((event_count = GEVNTCOUNT::Get(0).ReadFrom(mmio).EVNTCOUNT()) > 0) {
// invalidate cache so we can read fresh events
const zx_off_t offset = (ring_cur - ring_start) * sizeof(*ring_cur);
const size_t todo = std::min<size_t>(ring_end - ring_cur, event_count);
event_buffer_.CacheFlushInvalidate(offset, todo * sizeof(*ring_cur));
if (event_count > todo) {
event_buffer_.CacheFlushInvalidate(0, (event_count - todo) * sizeof(*ring_cur));
}
for (uint32_t i = 0; i < event_count; i += sizeof(uint32_t)) {
uint32_t event = *ring_cur++;
if (ring_cur == ring_end) {
ring_cur = ring_start;
}
HandleEvent(event);
}
// acknowledge the events we have processed
GEVNTCOUNT::Get(0).FromValue(0).set_EVNTCOUNT(event_count).WriteTo(mmio);
}
} else if (wakeup_pkt.type == ZX_PKT_TYPE_USER) {
const IrqSignal signal = GetIrqSignal(wakeup_pkt);
switch (signal) {
case IrqSignal::Wakeup:
// Nothing to do here, just loop around and process the pending
// completion queue.
break;
case IrqSignal::Exit:
zxlogf(INFO, "Dwc3::IrqThread: shutting down");
shutdown_now = true;
break;
default:
zxlogf(ERROR, "Dwc3::IrqThread: got invalid signal value %u",
static_cast<std::underlying_type_t<decltype(signal)>>(signal));
shutdown_now = true;
break;
}
shutdown_now = true;
continue;
} else {
zxlogf(ERROR, "Dwc3::IrqThread: unrecognized packet type %u", wakeup_pkt.type);
shutdown_now = true;
continue;
}
}
return 0;
}
void Dwc3::StartEvents() {
auto* mmio = get_mmio();
// set event buffer pointer and size
// keep interrupts masked until we are ready
zx_paddr_t paddr = event_buffer_.phys();
ZX_DEBUG_ASSERT(paddr != 0);
GEVNTADR::Get(0).FromValue(0).set_EVNTADR(paddr).WriteTo(mmio);
GEVNTSIZ::Get(0).FromValue(0).set_EVENTSIZ(kEventBufferSize).set_EVNTINTRPTMASK(0).WriteTo(mmio);
GEVNTCOUNT::Get(0).FromValue(0).set_EVNTCOUNT(0).WriteTo(mmio);
// enable events
DEVTEN::Get()
.FromValue(0)
.set_L1SUSPEN(1)
.set_U3L2L1SuspEn(1)
.set_CONNECTDONEEVTEN(1)
.set_USBRSTEVTEN(1)
.set_DISSCONNEVTEN(1)
.WriteTo(mmio);
}
} // namespace dwc3