blob: c01f2c06ff89bc47b1997b32a979dcf8d17c3691 [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.
#include "usb-transaction.h"
#include <ddk/debug.h>
#include <fbl/auto_lock.h>
#include <soc/mt8167/mt8167-usb.h>
namespace mt_usb_hci {
namespace regs = board_mt8167;
namespace {
// Read up to len bytes from the given endpoint-FIFO and write it to the output buffer.
size_t FifoRead(uint8_t ep, void* out, size_t len, ddk::MmioBuffer* usb) {
// It's possible the device returned less data than was requested. This is not an error.
auto actual = static_cast<size_t>(regs::RXCOUNT::Get(ep).ReadFrom(usb).rxcount());
size_t remaining = (actual < len) ? actual : len;
auto dest = static_cast<uint32_t*>(out);
while (remaining >= 4) {
*dest++ = regs::FIFO::Get(ep).ReadFrom(usb).fifo_data();
remaining -= 4;
}
auto dest_8 = reinterpret_cast<uint8_t*>(dest);
while (remaining > 0) {
*dest_8++ = regs::FIFO_8::Get(ep).ReadFrom(usb).fifo_data();
remaining--;
}
return actual;
}
// Write len bytes from the input buffer to the given endpoint-FIFO.
size_t FifoWrite(uint8_t ep, const void* in, size_t len, ddk::MmioBuffer* usb) {
auto remaining = len;
auto src = static_cast<const uint8_t*>(in);
auto fifo = regs::FIFO_8::Get(ep).FromValue(0);
while (remaining-- > 0) {
fifo.set_fifo_data(*src++).WriteTo(usb);
}
return len;
}
} // namespace
void Control::AbortAs(ControlState state) {
// To abort, flush the EP0-FIFO and clear all error-bits.
regs::CSR0_HOST::Get()
.ReadFrom(&usb_)
.set_flushfifo(1)
.set_error(0)
.set_naktimeout(0)
.set_rxstall(0)
.WriteTo(&usb_);
state_ = state;
}
bool Control::BusError() {
// TODO(hansens) implement proper control NAK-retry logic.
auto reg = regs::CSR0_HOST::Get().ReadFrom(&usb_);
if (reg.error())
zxlogf(ERROR, "usb device error");
if (reg.naktimeout())
zxlogf(ERROR, "usb device naktimeout");
if (reg.rxstall())
zxlogf(ERROR, "usb device rxstall");
return reg.error() || reg.naktimeout() || reg.rxstall();
}
void Control::Advance(bool interrupt) {
fbl::AutoLock _(&lock_);
// clang-format off
while (!terminal_ && (interrupt || !irq_wait_.load())) {
interrupt = false;
switch (state_) {
case ControlState::SETUP: AdvanceSetup(); break;
case ControlState::SETUP_IRQ: AdvanceSetupIrq(); break;
case ControlState::IN_DATA: AdvanceInData(); break;
case ControlState::IN_DATA_IRQ: AdvanceInDataIrq(); break;
case ControlState::OUT_DATA: AdvanceOutData(); break;
case ControlState::OUT_DATA_IRQ: AdvanceOutDataIrq(); break;
case ControlState::IN_STATUS: AdvanceInStatus(); break;
case ControlState::IN_STATUS_IRQ: AdvanceInStatusIrq(); break;
case ControlState::OUT_STATUS: AdvanceOutStatus(); break;
case ControlState::OUT_STATUS_IRQ: AdvanceOutStatusIrq(); break;
case ControlState::SUCCESS: AdvanceSuccess(); break;
case ControlState::ERROR: AdvanceError(); break;
case ControlState::CANCEL: AdvanceCancel(); break;
}
}
// clang-format on
}
void Control::Cancel() {
{
fbl::AutoLock _(&lock_);
if (state_.load() < ControlState::SUCCESS) { // Non-terminal.
irq_wait_ = false;
state_ = ControlState::CANCEL;
}
}
Advance();
}
void Control::AdvanceSetup() {
FifoWrite(0, &req_, sizeof(req_), &usb_);
regs::TXFUNCADDR::Get(0).FromValue(0).set_tx_func_addr(faddr_).WriteTo(&usb_);
regs::CSR0_HOST::Get().ReadFrom(&usb_).set_setuppkt(1).set_txpktrdy(1).set_disping(1).WriteTo(
&usb_);
state_ = ControlState::SETUP_IRQ;
irq_wait_ = true;
}
void Control::AdvanceSetupIrq() {
irq_wait_ = false;
if (BusError()) {
AbortAs(ControlState::ERROR);
return;
}
// clang-format off
switch (type_) {
case ControlType::ZERO: state_ = ControlState::IN_STATUS; break;
case ControlType::READ: state_ = ControlState::IN_DATA; break;
case ControlType::WRITE: state_ = ControlState::OUT_DATA; break;
}
// clang-format on
}
void Control::AdvanceInData() {
regs::CSR0_HOST::Get().ReadFrom(&usb_).set_reqpkt(1).WriteTo(&usb_);
state_ = ControlState::IN_DATA_IRQ;
irq_wait_ = true;
}
void Control::AdvanceInDataIrq() {
irq_wait_ = false;
if (BusError()) {
AbortAs(ControlState::ERROR);
return;
}
size_t remaining = len_ - actual_.load();
auto offset = reinterpret_cast<uintptr_t>(buffer_) + actual_.load();
auto read = FifoRead(0, reinterpret_cast<void*>(offset), remaining, &usb_);
actual_ += read;
regs::CSR0_HOST::Get().ReadFrom(&usb_).set_rxpktrdy(0).WriteTo(&usb_);
if (read < max_pkt_sz0_) {
// The device transferred a short packet signifying end of transmission.
state_ = ControlState::OUT_STATUS;
} else {
state_ = (actual_.load() < len_) ? ControlState::IN_DATA : ControlState::OUT_STATUS;
}
}
void Control::AdvanceOutData() {
// Write at most one packet's worth of data to the device.
size_t remaining = len_ - actual_.load();
size_t len = (remaining > max_pkt_sz0_) ? max_pkt_sz0_ : remaining;
auto offset = reinterpret_cast<uintptr_t>(buffer_) + actual_.load();
actual_ += FifoWrite(0, reinterpret_cast<void*>(offset), len, &usb_);
regs::CSR0_HOST::Get().ReadFrom(&usb_).set_txpktrdy(1).set_disping(1).WriteTo(&usb_);
state_ = ControlState::OUT_DATA_IRQ;
irq_wait_ = true;
}
void Control::AdvanceOutDataIrq() {
irq_wait_ = false;
if (BusError()) {
AbortAs(ControlState::ERROR);
return;
}
state_ = (actual_.load() < len_) ? ControlState::OUT_DATA : ControlState::IN_STATUS;
}
void Control::AdvanceInStatus() {
regs::CSR0_HOST::Get().ReadFrom(&usb_).set_statuspkt(1).set_reqpkt(1).WriteTo(&usb_);
state_ = ControlState::IN_STATUS_IRQ;
irq_wait_ = true;
}
void Control::AdvanceInStatusIrq() {
irq_wait_ = false;
if (BusError()) {
AbortAs(ControlState::ERROR);
return;
}
regs::CSR0_HOST::Get().ReadFrom(&usb_).set_statuspkt(0).set_rxpktrdy(0).WriteTo(&usb_);
state_ = ControlState::SUCCESS;
}
void Control::AdvanceOutStatus() {
regs::CSR0_HOST::Get().FromValue(0).set_statuspkt(1).set_txpktrdy(1).set_disping(1).WriteTo(
&usb_);
state_ = ControlState::OUT_STATUS_IRQ;
irq_wait_ = true;
}
void Control::AdvanceOutStatusIrq() {
irq_wait_ = false;
if (BusError()) {
AbortAs(ControlState::ERROR);
return;
}
state_ = ControlState::SUCCESS;
}
void Control::AdvanceSuccess() {
terminal_ = true;
sync_completion_signal(&complete_);
}
void Control::AdvanceError() {
terminal_ = true;
sync_completion_signal(&complete_);
}
void Control::AdvanceCancel() {
terminal_ = true;
sync_completion_signal(&complete_);
}
void BulkBase::AbortAs(BulkState state) {
// To abort, flush the endpoint-FIFO and clear all error-bits.
if (dir_ == BulkDirection::IN) {
auto csr = regs::RXCSR_HOST::Get(ep_).ReadFrom(&usb_);
if (csr.rxpktrdy()) {
csr.set_flushfifo(1).WriteTo(&usb_);
}
regs::RXCSR_HOST::Get(ep_)
.ReadFrom(&usb_)
.set_error(0)
.set_dataerr_naktimeout(0)
.set_rxstall(0)
.WriteTo(&usb_);
} else {
auto csr = regs::TXCSR_HOST::Get(ep_).ReadFrom(&usb_);
if (csr.txpktrdy()) {
csr.set_flushfifo(1).WriteTo(&usb_);
}
regs::TXCSR_HOST::Get(ep_)
.ReadFrom(&usb_)
.set_flushfifo(1)
.set_error(0)
.set_naktimeout_incomptx(0)
.set_rxstall(0)
.WriteTo(&usb_);
}
state_ = state;
}
bool BulkBase::BusError() {
bool ret;
if (dir_ == BulkDirection::IN) {
auto reg = regs::RXCSR_HOST::Get(ep_).ReadFrom(&usb_);
if (reg.error())
zxlogf(ERROR, "usb device RX error");
if (reg.dataerr_naktimeout())
zxlogf(ERROR, "usb device RX naktimeout");
if (reg.rxstall())
zxlogf(ERROR, "usb device RX rxstall");
ret = reg.error() || reg.dataerr_naktimeout() || reg.rxstall();
} else {
auto reg = regs::TXCSR_HOST::Get(ep_).ReadFrom(&usb_);
if (reg.error())
zxlogf(ERROR, "usb device TX error");
if (reg.naktimeout_incomptx())
zxlogf(ERROR, "usb device TX naktimeout");
if (reg.rxstall())
zxlogf(ERROR, "usb device TX rxstall");
ret = reg.error() || reg.naktimeout_incomptx() || reg.rxstall();
}
return ret;
}
void BulkBase::Advance(bool interrupt) {
fbl::AutoLock _(&lock_);
// clang-format off
while (!terminal_ && (interrupt || !irq_wait_.load())) {
interrupt = false;
switch (state_) {
case BulkState::SETUP: AdvanceSetup(); break;
case BulkState::SETUP_IN: AdvanceSetupIn(); break;
case BulkState::SETUP_OUT: AdvanceSetupOut(); break;
case BulkState::SEND: AdvanceSend(); break;
case BulkState::SEND_IRQ: AdvanceSendIrq(); break;
case BulkState::RECV: AdvanceRecv(); break;
case BulkState::RECV_IRQ: AdvanceRecvIrq(); break;
case BulkState::SUCCESS: AdvanceSuccess(); break;
case BulkState::ERROR: AdvanceError(); break;
case BulkState::CANCEL: AdvanceCancel(); break;
}
}
// clang-format on
}
void BulkBase::Cancel() {
{
fbl::AutoLock _(&lock_);
if (state_.load() < BulkState::SUCCESS) { // Non-terminal.
irq_wait_ = false;
state_ = BulkState::CANCEL;
}
}
Advance();
}
void BulkBase::AdvanceSetup() {
state_ = (dir_ == BulkDirection::IN) ? BulkState::SETUP_IN : BulkState::SETUP_OUT;
}
void BulkBase::AdvanceSend() {
// Transmit at most one packet's worth of data to the device.
size_t remaining = len_ - actual_.load();
size_t xfer_len = (remaining > max_pkt_sz_) ? max_pkt_sz_ : remaining;
auto offset = reinterpret_cast<uintptr_t>(buffer_) + actual_.load();
size_t written = FifoWrite(ep_, reinterpret_cast<void*>(offset), xfer_len, &usb_);
pkt_aligned_ = written == max_pkt_sz_;
actual_ += written;
regs::TXCSR_HOST::Get(ep_).ReadFrom(&usb_).set_txpktrdy(1).WriteTo(&usb_);
state_ = BulkState::SEND_IRQ;
irq_wait_ = true;
}
void BulkBase::AdvanceSendIrq() {
irq_wait_ = false;
if (BusError()) {
AbortAs(BulkState::ERROR);
return;
}
// Here, it's possible that the last chunk of data sent was exactly one packet in size. In this
// case, the receiving device may still be awaiting data. We need to send a short-packet
// (zero-length in this case) to tell the receiver we are done transmitting. This zero-length
// packet case is identified by actual_ == len_ and pkt_aligned_ == true.
state_ = (actual_.load() < len_ || pkt_aligned_) ? BulkState::SEND : BulkState::SUCCESS;
}
void BulkBase::AdvanceRecv() {
regs::RXCSR_HOST::Get(ep_).FromValue(0).set_reqpkt(1).WriteTo(&usb_);
state_ = BulkState::RECV_IRQ;
irq_wait_ = true;
}
void BulkBase::AdvanceRecvIrq() {
irq_wait_ = false;
if (BusError()) {
AbortAs(BulkState::ERROR);
return;
}
size_t remaining = len_ - actual_.load();
auto offset = reinterpret_cast<uintptr_t>(buffer_) + actual_.load();
size_t read = FifoRead(ep_, reinterpret_cast<void*>(offset), remaining, &usb_);
pkt_aligned_ = read == max_pkt_sz_;
actual_ += read;
regs::RXCSR_HOST::Get(ep_).ReadFrom(&usb_).set_rxpktrdy(0).WriteTo(&usb_);
// A short read indicates the device is done transmitting data.
if (read < max_pkt_sz_) {
// The device transferred a short packet signifying end of transmission.
state_ = BulkState::SUCCESS;
} else {
state_ = (actual_.load() < len_) ? BulkState::RECV : BulkState::SUCCESS;
}
}
void BulkBase::AdvanceSuccess() {
terminal_ = true;
sync_completion_signal(&complete_);
}
void BulkBase::AdvanceError() {
terminal_ = true;
sync_completion_signal(&complete_);
}
void BulkBase::AdvanceCancel() {
terminal_ = true;
sync_completion_signal(&complete_);
}
void Bulk::AdvanceSetupIn() {
regs::RXFUNCADDR::Get(ep_).FromValue(0).set_rx_func_addr(faddr_).WriteTo(&usb_);
regs::RXINTERVAL::Get(ep_).FromValue(0).set_rx_polling_interval_nak_limit_m(interval_).WriteTo(
&usb_);
regs::RXTYPE::Get(ep_)
.FromValue(0)
.set_rx_protocol(0x2) // Bulk-type.
.set_rx_target_ep_number(ep_)
.WriteTo(&usb_);
regs::RXMAP::Get(ep_)
.ReadFrom(&usb_)
.set_maximum_payload_transaction(static_cast<uint16_t>(max_pkt_sz_))
.WriteTo(&usb_);
// If double-buffering is enabled, we need to flush the RX-FIFO twice, see: MUSBMHDRC 22.2.1.1.
auto csr = regs::RXCSR_HOST::Get(ep_).ReadFrom(&usb_);
if (csr.rxpktrdy()) {
csr.set_flushfifo(1).WriteTo(&usb_);
}
if (csr.rxpktrdy()) {
csr.set_flushfifo(1).WriteTo(&usb_);
}
state_ = BulkState::RECV;
}
void Bulk::AdvanceSetupOut() {
regs::TXFUNCADDR::Get(ep_).FromValue(0).set_tx_func_addr(faddr_).WriteTo(&usb_);
regs::TXINTERVAL::Get(ep_).FromValue(0).set_tx_polling_interval_nak_limit_m(interval_).WriteTo(
&usb_);
regs::TXTYPE::Get(ep_)
.FromValue(0)
.set_tx_protocol(0x2) // Bulk-type.
.set_tx_target_ep_number(ep_)
.WriteTo(&usb_);
regs::TXMAP::Get(ep_)
.FromValue(0)
.set_maximum_payload_transaction(static_cast<uint16_t>(max_pkt_sz_))
.WriteTo(&usb_);
// If double-buffering is enabled, we need to flush the TX-FIFO twice, see: MUSBMHDRC 22.2.2.1.
auto csr = regs::TXCSR_HOST::Get(ep_).ReadFrom(&usb_);
if (csr.fifonotempty()) {
csr.set_flushfifo(1).WriteTo(&usb_);
}
if (csr.fifonotempty()) {
csr.set_flushfifo(1).WriteTo(&usb_);
}
state_ = BulkState::SEND;
}
void Interrupt::AdvanceSetupIn() {
regs::RXFUNCADDR::Get(ep_).FromValue(0).set_rx_func_addr(faddr_).WriteTo(&usb_);
regs::RXINTERVAL::Get(ep_).FromValue(0).set_rx_polling_interval_nak_limit_m(interval_).WriteTo(
&usb_);
regs::RXTYPE::Get(ep_)
.FromValue(0)
.set_rx_protocol(0x3) // Interrupt-type.
.set_rx_target_ep_number(ep_)
.WriteTo(&usb_);
regs::RXMAP::Get(ep_)
.FromValue(0)
.set_maximum_payload_transaction(static_cast<uint16_t>(max_pkt_sz_))
.WriteTo(&usb_);
// If double-buffering is enabled, we need to flush the RX-FIFO twice, see: MUSBMHDRC 22.2.1.1.
auto csr = regs::RXCSR_HOST::Get(ep_).ReadFrom(&usb_);
if (csr.rxpktrdy()) {
csr.set_flushfifo(1).WriteTo(&usb_);
}
if (csr.rxpktrdy()) {
csr.set_flushfifo(1).WriteTo(&usb_);
}
state_ = BulkState::RECV;
}
void Interrupt::AdvanceSetupOut() {
regs::TXFUNCADDR::Get(ep_).FromValue(0).set_tx_func_addr(faddr_).WriteTo(&usb_);
regs::TXINTERVAL::Get(ep_).FromValue(0).set_tx_polling_interval_nak_limit_m(interval_).WriteTo(
&usb_);
regs::TXTYPE::Get(ep_)
.FromValue(0)
.set_tx_protocol(0x3) // Interrupt-type.
.set_tx_target_ep_number(ep_)
.WriteTo(&usb_);
regs::TXMAP::Get(ep_)
.FromValue(0)
.set_maximum_payload_transaction(static_cast<uint16_t>(max_pkt_sz_))
.WriteTo(&usb_);
// If double-buffering is enabled, we need to flush the TX-FIFO twice, see: MUSBMHDRC 22.2.2.1.
auto csr = regs::TXCSR_HOST::Get(ep_).ReadFrom(&usb_);
if (csr.fifonotempty()) {
csr.set_flushfifo(1).WriteTo(&usb_);
}
if (csr.fifonotempty()) {
csr.set_flushfifo(1).WriteTo(&usb_);
}
state_ = BulkState::SEND;
}
} // namespace mt_usb_hci