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