| // Copyright 2022 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 "src/devices/usb/drivers/crg-udc/crg-udc.h" |
| |
| #include <lib/ddk/binding_driver.h> |
| #include <lib/ddk/hw/arch_ops.h> |
| #include <lib/ddk/metadata.h> |
| #include <lib/ddk/platform-defs.h> |
| #include <lib/sync/completion.h> |
| #include <lib/zx/clock.h> |
| #include <lib/zx/profile.h> |
| #include <lib/zx/time.h> |
| #include <threads.h> |
| #include <zircon/status.h> |
| #include <zircon/syscalls.h> |
| #include <zircon/threads.h> |
| |
| #include <fbl/algorithm.h> |
| #include <fbl/auto_lock.h> |
| #include <usb/usb-request.h> |
| #include <usb/usb.h> |
| |
| #include "src/devices/usb/drivers/crg-udc/crg_udc_regs.h" |
| |
| namespace crg_udc { |
| |
| // Put EP0 in protocol stall state |
| void CrgUdc::SetEp0Halt() { |
| auto* ep = &endpoints_[0]; |
| |
| if (ep->ep_state == EpState::kEpStateHalted || ep->ep_state == EpState::kEpStateDisabled) { |
| return; |
| } |
| |
| BuildEp0Status(ep, 0, 1); |
| ep->ep_state = EpState::kEpStateHalted; |
| } |
| |
| // Update dequeue pointer after processing a transfer event |
| void CrgUdc::UpdateDequeuePt(Endpoint* ep, TRBlock* event) { |
| uint32_t deq_pt_lo = event->dw0; |
| uint32_t deq_pt_hi = event->dw1; |
| uint64_t dq_pt_addr = static_cast<uint64_t>(deq_pt_lo) + (static_cast<uint64_t>(deq_pt_hi) << 32); |
| TRBlock* deq_pt = nullptr; |
| zx_paddr_t offset = 0; |
| |
| offset = TranTrbDmaToVirt(ep, static_cast<zx_paddr_t>(dq_pt_addr)); |
| deq_pt = ep->first_trb + offset; |
| deq_pt++; |
| |
| if (((deq_pt->dw3 >> TRB_TYPE_SHIFT) & TRB_TYPE_MASK) == TRB_TYPE_LINK) { |
| deq_pt = ep->first_trb; |
| } |
| ep->deq_pt = deq_pt; |
| } |
| |
| // Handle the completion status of a transfer event |
| void CrgUdc::HandleCompletionCode(Endpoint* ep, TRBlock* event) { |
| uint64_t trb_pt; |
| TRBlock* p_trb = nullptr; |
| zx_paddr_t offset = 0; |
| uint32_t trb_transfer_length; |
| |
| trb_pt = static_cast<uint64_t>(event->dw0) + (static_cast<uint64_t>(event->dw1) << 32); |
| offset = TranTrbDmaToVirt(ep, static_cast<zx_paddr_t>(trb_pt)); |
| p_trb = ep->first_trb + offset; |
| |
| if (((p_trb->dw3 >> TRB_CHAIN_BIT_SHIFT) & TRB_CHAIN_BIT_MASK) != 1) { |
| // chain bit is not set, which mean it is the end of a TD |
| trb_transfer_length = event->dw2 & TRB_TRANSFER_LEN_MASK; |
| ep->req_xfersize = ep->req_length - trb_transfer_length; |
| HandleTransferComplete(ep->ep_num); |
| |
| if (ep->type == USB_ENDPOINT_CONTROL) { |
| HandleEp0TransferComplete(); |
| } |
| } |
| } |
| |
| // Halt physical EP(s) |
| void CrgUdc::SetEpHalt(Endpoint* ep) { |
| auto* mmio = get_mmio(); |
| uint32_t param0; |
| uint32_t eprunning = 0; |
| |
| if (ep->ep_num == 0 || ep->ep_state == EpState::kEpStateDisabled || |
| ep->ep_state == EpState::kEpStateHalted) { |
| return; |
| } |
| |
| param0 = 0x1 << ep->ep_num; |
| IssueCmd(CmdType::kCrgCmdSetHalt, param0, 0); |
| do { |
| eprunning = EPRUN::Get().ReadFrom(mmio).reg_value(); |
| } while ((eprunning & param0) != 0); |
| |
| CompletePendingRequest(ep); |
| |
| ep->deq_pt = ep->enq_pt; |
| ep->transfer_ring_full = false; |
| ep->ep_state = EpState::kEpStateHalted; |
| } |
| |
| // Handle transfer event TRB |
| zx_status_t CrgUdc::HandleXferEvent(TRBlock* event) { |
| uint8_t ep_num = (event->dw3 >> EVE_TRB_ENDPOINT_ID_SHIFT) & EVE_TRB_ENDPOINT_ID_MASK; |
| auto* ep = &endpoints_[ep_num]; |
| TrbCmplCode completion_code; |
| bool trbs_dequeued = false; |
| |
| if (!ep->first_trb || ep->ep_state == EpState::kEpStateDisabled) { |
| zxlogf(ERROR, "The endpoint %d not enabled", ep_num); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| completion_code = |
| static_cast<TrbCmplCode>((event->dw2 >> EVE_TRB_COMPL_CODE_SHIFT) & EVE_TRB_COMPL_CODE_MASK); |
| if (completion_code == TrbCmplCode::kCmplCodeStopped || |
| completion_code == TrbCmplCode::kCmplCodeStoppedLengthInvalid || |
| completion_code == TrbCmplCode::kCmplCodeDisabled || |
| completion_code == TrbCmplCode::kCmplCodeDisabledLengthInvalid || |
| completion_code == TrbCmplCode::kCmplCodeHalted || |
| completion_code == TrbCmplCode::kCmplCodeHaltedLengthInvalid) { |
| zxlogf(INFO, "completion_code = %d(STOPPED/HALTED/DISABLED)", |
| static_cast<int>(completion_code)); |
| } else { |
| UpdateDequeuePt(ep, event); |
| } |
| |
| switch (completion_code) { |
| case TrbCmplCode::kCmplCodeSuccess: { |
| HandleCompletionCode(ep, event); |
| trbs_dequeued = true; |
| break; |
| } |
| case TrbCmplCode::kCmplCodeShortPkt: { |
| uint32_t trb_transfer_length; |
| |
| if (ep->dir_out) { |
| trb_transfer_length = event->dw2 & EVE_TRB_TRAN_LEN_MASK; |
| |
| ep->req_xfersize = ep->req_length - trb_transfer_length; |
| HandleTransferComplete(ep->ep_num); |
| } else { |
| zxlogf(INFO, "EP DIR IN"); |
| } |
| |
| trbs_dequeued = true; |
| break; |
| } |
| case TrbCmplCode::kCmplCodeTrbStall: { |
| fbl::AutoLock lock(&ep->lock); |
| if (ep->current_req != nullptr) { |
| usb_request_t* req = ep->current_req; |
| Request request(req, sizeof(usb_request_t)); |
| ep->current_req = nullptr; |
| ep->trbs_needed = 0; |
| request.Complete(ZX_ERR_IO_NOT_PRESENT, 0); |
| } |
| trbs_dequeued = true; |
| setup_state_ = SetupState::kWaitForSetup; |
| break; |
| } |
| case TrbCmplCode::kCmplCodeSetupTagMismatch: { |
| uint32_t enq_idx = ctrl_req_enq_idx_; |
| |
| if (ep->deq_pt == ep->enq_pt) { |
| fbl::AutoLock lock(&ep->lock); |
| if (ep->current_req != nullptr) { |
| usb_request_t* req = ep->current_req; |
| Request request(req, sizeof(usb_request_t)); |
| ep->current_req = nullptr; |
| request.Complete(ZX_ERR_IO_NOT_PRESENT, 0); |
| } |
| |
| setup_state_ = SetupState::kWaitForSetup; |
| if (enq_idx) { |
| SetupPacket* setup_pkt; |
| |
| setup_pkt = &ctrl_req_queue_[enq_idx - 1]; |
| memcpy(&cur_setup_, &setup_pkt->usbctrlreq, sizeof(cur_setup_)); |
| setup_tag_ = setup_pkt->setup_tag; |
| HandleEp0Setup(); |
| memset(ctrl_req_queue_, 0, sizeof(struct SetupPacket) * CTRL_REQ_QUEUE_DEPTH); |
| ctrl_req_enq_idx_ = 0; |
| } |
| } else { |
| zxlogf(DEBUG, "setuptag mismatch skp dpt!=ept"); |
| } |
| break; |
| } |
| case TrbCmplCode::kCmplCodeBabbleDetectedErr: |
| case TrbCmplCode::kCmplCodeInvalidStreamTypeErr: |
| case TrbCmplCode::kCmplCodeRingUnderrun: |
| case TrbCmplCode::kCmplCodeRingOverrun: |
| case TrbCmplCode::kCmplCodeIsochBufferOverrun: |
| case TrbCmplCode::kCmplCodeUsbTransErr: |
| case TrbCmplCode::kCmplCodeTrbErr: { |
| zxlogf(ERROR, "XFER event error, cmpl_code = 0x%x", |
| static_cast<unsigned int>(completion_code)); |
| SetEpHalt(ep); |
| break; |
| } |
| case TrbCmplCode::kCmplCodeStopped: |
| case TrbCmplCode::kCmplCodeStoppedLengthInvalid: { |
| zxlogf(ERROR, "STOP, cmpl_code = 0x%x", static_cast<unsigned int>(completion_code)); |
| break; |
| } |
| default: { |
| zxlogf(INFO, "UNKNOWN cmpl_code = 0x%x", static_cast<unsigned int>(completion_code)); |
| break; |
| } |
| } |
| |
| // queue the pending trbs |
| if (trbs_dequeued && ep->transfer_ring_full) { |
| ep->transfer_ring_full = false; |
| fbl::AutoLock al(&ep->lock); |
| StartTransfer(ep, ep->req_length_left); |
| } |
| |
| return ZX_OK; |
| } |
| |
| // Handle EP0 setup stage |
| void CrgUdc::HandleEp0Setup() { |
| auto* setup = &cur_setup_; |
| auto* ep = &endpoints_[0]; |
| |
| auto length = letoh16(setup->w_length); |
| bool is_in = ((setup->bm_request_type & USB_DIR_MASK) == USB_DIR_IN); |
| size_t actual = 0; |
| |
| // No data to read, can handle setup now |
| if (length == 0 || is_in) { |
| // TODO(voydanoff) stall if this fails (after we implement stalling) |
| [[maybe_unused]] zx_status_t _ = HandleSetupRequest(&actual); |
| } |
| |
| if (length > 0) { |
| if (is_in) { |
| setup_state_ = SetupState::kDataStageXfer; |
| // send data in |
| ep->dir_in = true; |
| ep->dir_out = false; |
| ep->req_offset = 0; |
| ep->req_length = static_cast<uint32_t>(actual); |
| fbl::AutoLock al(&ep->lock); |
| StartTransfer(ep, (ep->req_length > 127 ? ep->max_packet_size : ep->req_length)); |
| } else { |
| // queue a read for the data phase |
| setup_state_ = SetupState::kDataStageRecv; |
| ep->dir_in = false; |
| ep->dir_out = true; |
| ep->req_offset = 0; |
| ep->req_length = length; |
| fbl::AutoLock al(&ep->lock); |
| StartTransfer(ep, (length > 127 ? ep->max_packet_size : length)); |
| } |
| } else { |
| // no data phase |
| // status in IN direction |
| BuildEp0Status(ep, set_addr_, 0); |
| if (set_addr_ == 1) { |
| set_addr_ = 0; |
| } |
| } |
| } |
| |
| // Handles setup requests from the host. |
| zx_status_t CrgUdc::HandleSetupRequest(size_t* out_actual) { |
| zx_status_t status; |
| |
| auto* setup = &cur_setup_; |
| auto* buffer = ep0_buffer_.virt(); |
| zx::duration elapsed; |
| zx::time now; |
| |
| if (setup->bm_request_type == (USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_DEVICE)) { |
| // Handle some special setup requests in this driver |
| switch (setup->b_request) { |
| case USB_REQ_SET_ADDRESS: |
| zxlogf(SERIAL, "SET_ADDRESS %d", setup->w_value); |
| SetAddress(static_cast<uint8_t>(setup->w_value)); |
| now = zx::clock::get_monotonic(); |
| elapsed = now - irq_timestamp_; |
| zxlogf( |
| INFO, |
| "Took %i microseconds to reply to SET_ADDRESS interrupt\nStarted waiting at %lx\nGot " |
| "hardware IRQ at %lx\nFinished processing at %lx, context switch happened at %lx", |
| static_cast<int>(elapsed.to_usecs()), wait_start_time_.get(), irq_timestamp_.get(), |
| now.get(), irq_dispatch_timestamp_.get()); |
| if (elapsed.to_msecs() > 2) { |
| zxlogf(ERROR, "Handling SET_ADDRESS took greater than 2ms"); |
| } |
| out_actual = 0; |
| return ZX_OK; |
| case USB_REQ_SET_CONFIGURATION: |
| zxlogf(SERIAL, "SET_CONFIGURATION %d", setup->w_value); |
| configured_ = true; |
| if (device_state_ <= DeviceState::kUsbStateDefault) { |
| SetEp0Halt(); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| if (dci_intf_) { |
| status = dci_intf_->Control(setup, nullptr, 0, nullptr, 0, out_actual); |
| } else { |
| status = ZX_ERR_NOT_SUPPORTED; |
| } |
| if (status == ZX_OK && setup->w_value) { |
| setup_state_ = SetupState::kStatusStageXfer; |
| if (device_state_ == DeviceState::kUsbStateAddress) { |
| device_state_ = DeviceState::kUsbStateConfigured; |
| } |
| } else { |
| configured_ = false; |
| } |
| return status; |
| default: |
| // fall through to dci_intf_->Control() |
| break; |
| } |
| } |
| |
| bool is_in = ((setup->bm_request_type & USB_DIR_MASK) == USB_DIR_IN); |
| auto length = le16toh(setup->w_length); |
| |
| if (dci_intf_) { |
| if (length == 0) { |
| status = dci_intf_->Control(setup, nullptr, 0, nullptr, 0, out_actual); |
| } else if (is_in) { |
| status = dci_intf_->Control(setup, nullptr, 0, reinterpret_cast<uint8_t*>(buffer), length, |
| out_actual); |
| } else { |
| status = ZX_ERR_NOT_SUPPORTED; |
| } |
| } else { |
| status = ZX_ERR_NOT_SUPPORTED; |
| } |
| if (status == ZX_OK) { |
| auto* ep = &endpoints_[0]; |
| ep->req_offset = 0; |
| if (is_in) { |
| ep->req_length = static_cast<uint32_t>(*out_actual); |
| } |
| } |
| return status; |
| } |
| |
| // Update device status after setting the address |
| void CrgUdc::SetAddressCallback() { |
| if (device_state_ == DeviceState::kUsbStateDefault && dev_addr_ != 0) { |
| device_state_ = DeviceState::kUsbStateAddress; |
| } else if (device_state_ == DeviceState::kUsbStateAddress) { |
| if (dev_addr_ == 0) { |
| device_state_ = DeviceState::kUsbStateDefault; |
| } |
| } |
| } |
| |
| // fill the status stage TRB |
| void CrgUdc::SetupStatusTrb(TRBlock* p_trb, uint8_t pcs, uint8_t set_addr, uint8_t stall) { |
| uint32_t tmp; |
| uint32_t dir = 0; |
| |
| // Reserved |
| p_trb->dw0 = 0; |
| p_trb->dw1 = 0; |
| |
| // bit[22:31]: interrupt target |
| tmp = (0x0 & TRB_INTR_TARGET_MASK) << TRB_INTR_TARGET_SHIFT; |
| p_trb->dw2 = tmp; |
| |
| // bit0: cycle bit |
| // bit5: interrupt on complete |
| // bit[10:15]: trb type |
| tmp = pcs & TRB_CYCLE_BIT_MASK; |
| tmp |= 0x1 << TRB_INTR_ON_COMPLETION_SHIFT; |
| tmp |= (TRB_TYPE_XFER_STATUS_STAGE & TRB_TYPE_MASK) << TRB_TYPE_SHIFT; |
| |
| // bit16: direction |
| // bit[17:18]: setup tag |
| // bit19: stall state |
| // bit20: set address |
| dir = (setup_state_ == SetupState::kStatusStageXfer ? 0 : 1); |
| tmp |= (dir & DATA_STAGE_TRB_DIR_MASK) << DATA_STAGE_TRB_DIR_SHIFT; |
| tmp |= (setup_tag_ & TRB_SETUP_TAG_MASK) << TRB_SETUP_TAG_SHIFT; |
| tmp |= (stall & STATUS_STAGE_TRB_STALL_MASK) << STATUS_STAGE_TRB_STALL_SHIFT; |
| tmp |= (set_addr & STATUS_STAGE_TRB_SET_ADDR_MASK) << STATUS_STAGE_TRB_SET_ADDR_SHIFT; |
| p_trb->dw3 = tmp; |
| |
| // Make sure the TRB was built before starting the DMA transfer |
| hw_wmb(); |
| } |
| |
| // Build the status stage TRB |
| void CrgUdc::BuildEp0Status(Endpoint* ep, uint8_t set_addr, uint8_t stall) { |
| auto* enq_pt = ep->enq_pt; |
| |
| SetupStatusTrb(enq_pt, ep->pcs, set_addr, stall); |
| enq_pt++; |
| |
| if (((enq_pt->dw3 >> TRB_TYPE_SHIFT) & TRB_TYPE_MASK) == TRB_TYPE_LINK) { |
| if ((enq_pt->dw3 >> TRB_LINK_TOGGLE_CYCLE_SHIFT) & TRB_LINK_TOGGLE_CYCLE_MASK) { |
| enq_pt->dw3 &= ~(TRB_CYCLE_BIT_MASK << TRB_CYCLE_BIT_SHIFT); |
| enq_pt->dw3 |= (ep->pcs & TRB_CYCLE_BIT_MASK) << TRB_CYCLE_BIT_SHIFT; |
| ep->pcs ^= 0x1; |
| } |
| enq_pt = ep->first_trb; |
| } |
| ep->enq_pt = enq_pt; |
| KnockDoorbell(ep->ep_num); |
| } |
| |
| // Programs the device address received from the SET_ADDRESS command from the host |
| void CrgUdc::SetAddress(uint8_t address) { |
| if (((device_state_ == DeviceState::kUsbStateDefault) && address != 0) || |
| (device_state_ == DeviceState::kUsbStateAddress)) { |
| uint32_t param0; |
| |
| dev_addr_ = address; |
| param0 = address & 0xff; |
| IssueCmd(CmdType::kCrgCmdSetAddr, param0, 0); |
| set_addr_ = 1; |
| } |
| |
| setup_state_ = SetupState::kStatusStageXfer; |
| } |
| |
| // Queues the next USB request for the specified endpoint |
| void CrgUdc::QueueNextRequest(Endpoint* ep) { |
| std::optional<Request> req; |
| if (ep->current_req == nullptr) { |
| req = ep->queued_reqs.pop(); |
| } |
| |
| if (req.has_value()) { |
| auto* usb_req = req->take(); |
| ep->current_req = usb_req; |
| |
| phys_iter_t iter; |
| zx_paddr_t phys; |
| usb_request_physmap(usb_req, bti_.get()); |
| usb_request_phys_iter_init(&iter, usb_req, zx_system_get_page_size()); |
| usb_request_phys_iter_next(&iter, &phys); |
| ep->phys = phys; |
| |
| ep->req_offset = 0; |
| ep->req_length = static_cast<uint32_t>(usb_req->header.length); |
| ep->zlp = usb_req->header.send_zlp; |
| StartTransfer(ep, ep->req_length); |
| } |
| } |
| |
| // Get the free size from the transfer ring |
| uint32_t CrgUdc::RoomOnRing(uint32_t trbs_num, TRBlock* xfer_ring, TRBlock* enq_pt, |
| TRBlock* dq_pt) { |
| uint32_t i = 0; |
| |
| if (enq_pt == dq_pt) { |
| // ring is empty |
| return trbs_num - 1; |
| } |
| |
| while (enq_pt != dq_pt) { |
| i++; |
| enq_pt++; |
| |
| if (enq_pt->dw3 == TRB_TYPE_LINK) { |
| enq_pt = xfer_ring; |
| } |
| if (i > trbs_num) { |
| break; |
| } |
| } |
| return i - 1; |
| } |
| |
| // Fill the normal transfer TRB |
| void CrgUdc::SetupNormalTrb(TRBlock* p_trb, uint32_t xfer_len, uint64_t buf_addr, uint8_t td_size, |
| uint8_t pcs, uint8_t trb_type, uint8_t short_pkt, uint8_t chain_bit, |
| uint8_t intr_on_compl, bool setup_stage, uint8_t usb_dir, bool isoc, |
| uint16_t frame_i_d, uint8_t SIA, uint8_t AZP) { |
| uint32_t tmp = 0; |
| |
| // Pointing to the start address of data buffer associated with this TRB |
| p_trb->dw0 = LOWER_32_BITS(buf_addr); |
| p_trb->dw1 = UPPER_32_BITS(buf_addr); |
| |
| // bit[0:16]: size of data buffer in bytes |
| // bit[17:21]: indicating how many packets still need to be transferred |
| tmp = xfer_len & EVE_TRB_TRAN_LEN_MASK; |
| tmp |= (td_size & TRB_TD_SIZE_MASK) << TRB_TD_SIZE_SHIFT; |
| p_trb->dw2 = tmp; |
| |
| // bit0: mark the enqueue pointer of the transfer ring |
| // bit2: flag for shot packet |
| // bit4: chain bit for the same TD |
| // bit5: interrupt on complete |
| // bit7: append zero length packet |
| // bit[10:15]: TRB type |
| tmp = pcs & TRB_CYCLE_BIT_MASK; |
| tmp |= (short_pkt & TRB_INTR_ON_SHORT_PKT_MASK) << TRB_INTR_ON_SHORT_PKT_SHIFT; |
| tmp |= (chain_bit & TRB_CHAIN_BIT_MASK) << TRB_CHAIN_BIT_SHIFT; |
| tmp |= (intr_on_compl & TRB_INTR_ON_COMPLETION_MASK) << TRB_INTR_ON_COMPLETION_SHIFT; |
| tmp |= (AZP & TRB_APPEND_ZLP_MASK) << TRB_APPEND_ZLP_SHIFT; |
| tmp |= (trb_type & TRB_TYPE_MASK) << TRB_TYPE_SHIFT; |
| |
| if (setup_stage) { |
| tmp |= (usb_dir & DATA_STAGE_TRB_DIR_MASK) << DATA_STAGE_TRB_DIR_SHIFT; |
| } |
| |
| if (isoc) { |
| tmp |= (frame_i_d & ISOC_TRB_FRAME_ID_MASK) << ISOC_TRB_FRAME_ID_SHIFT; |
| tmp |= (SIA & ISOC_TRB_SIA_MASK) << ISOC_TRB_SIA_SHIFT; |
| } |
| p_trb->dw3 = tmp; |
| // Make sure the TRB was built before starting the DMA transfer |
| hw_wmb(); |
| } |
| |
| // Fill the data stage TRB |
| void CrgUdc::SetupDataStageTrb(Endpoint* ep, TRBlock* p_trb, uint8_t pcs, uint32_t transfer_length, |
| uint32_t td_size, uint8_t IOC, uint8_t AZP, uint8_t dir, |
| uint16_t setup_tag) { |
| uint32_t tmp; |
| |
| // Pointing to the start address of data buffer associated with this TRB |
| p_trb->dw0 = LOWER_32_BITS(static_cast<uint64_t>(ep0_buffer_.phys())); |
| p_trb->dw1 = UPPER_32_BITS(static_cast<uint64_t>(ep0_buffer_.phys())); |
| |
| // bit[0:16]: size of data buffer in bytes |
| // bit[17:21]: indicating how many packets still need to be transferred |
| tmp = transfer_length & TRB_TRANSFER_LEN_MASK; |
| tmp |= (td_size & TRB_TD_SIZE_MASK) << TRB_TD_SIZE_SHIFT; |
| p_trb->dw2 = tmp; |
| |
| // bit0: mark the enqueue pointer of the transfer ring |
| // bit2: flag for short packet |
| // bit5: interrupt on complete |
| // bit7: append zero length packet |
| // bit[10:15]: TRB type |
| // bit16: indicates the direction of data transfer |
| // bit[17:18]: setup tag |
| tmp = pcs & TRB_CYCLE_BIT_MASK; |
| tmp |= 0x1 << TRB_INTR_ON_SHORT_PKT_SHIFT; |
| tmp |= (IOC & TRB_INTR_ON_COMPLETION_MASK) << TRB_INTR_ON_COMPLETION_SHIFT; |
| tmp |= (TRB_TYPE_XFER_DATA_STAGE & TRB_TYPE_MASK) << TRB_TYPE_SHIFT; |
| tmp |= (AZP & TRB_APPEND_ZLP_MASK) << TRB_APPEND_ZLP_SHIFT; |
| tmp |= (dir & DATA_STAGE_TRB_DIR_MASK) << DATA_STAGE_TRB_DIR_SHIFT; |
| tmp |= (setup_tag & TRB_SETUP_TAG_MASK) << TRB_SETUP_TAG_SHIFT; |
| p_trb->dw3 = tmp; |
| |
| // Make sure the TRB was built before starting the DMA transfer |
| hw_wmb(); |
| } |
| |
| // Queue Control TRBs |
| void CrgUdc::UdcQueueCtrl(Endpoint* ep, uint32_t need_trbs_num) { |
| auto* enq_pt = ep->enq_pt; |
| auto* dq_pt = ep->deq_pt; |
| TRBlock* p_trb; |
| uint32_t transfer_length; |
| uint32_t td_size = 0; |
| uint8_t IOC; |
| uint8_t AZP; |
| uint8_t dir = 0; |
| |
| if (ep->ep_state != EpState::kEpStateRunning) { |
| zxlogf(ERROR, "UdcQueueCtrl: EP status = %d", static_cast<int>(ep->ep_state)); |
| return; |
| } |
| |
| if (enq_pt == dq_pt) { |
| uint32_t tmp = 0; |
| bool need_zlp = false; |
| |
| dir = (setup_state_ == SetupState::kDataStageXfer ? 0 : 1); |
| if (ep->zlp && (ep->req_length != 0) && (ep->req_length % ep->max_packet_size == 0)) { |
| need_zlp = true; |
| } |
| |
| for (uint32_t i = 0; i < need_trbs_num; i++) { |
| p_trb = enq_pt; |
| if (i < (need_trbs_num - 1)) { |
| transfer_length = TRB_MAX_BUFFER_SIZE; |
| IOC = 0; |
| AZP = 0; |
| } else { |
| tmp = TRB_MAX_BUFFER_SIZE * i; |
| transfer_length = ep->req_length - tmp; |
| IOC = 1; |
| AZP = (need_zlp ? 1 : 0); |
| } |
| SetupDataStageTrb(ep, p_trb, ep->pcs, transfer_length, td_size, IOC, AZP, dir, setup_tag_); |
| enq_pt++; |
| |
| if (((enq_pt->dw3 >> TRB_TYPE_SHIFT) & TRB_TYPE_MASK) == TRB_TYPE_LINK) { |
| if ((enq_pt->dw3 >> TRB_LINK_TOGGLE_CYCLE_SHIFT) & TRB_LINK_TOGGLE_CYCLE_MASK) { |
| enq_pt->dw3 &= ~(TRB_CYCLE_BIT_MASK << TRB_CYCLE_BIT_SHIFT); |
| enq_pt->dw3 |= (ep->pcs & TRB_CYCLE_BIT_MASK) << TRB_CYCLE_BIT_SHIFT; |
| ep->pcs ^= 0x1; |
| // Make sure the PCS was updated before resetting the enqueue pointer |
| hw_wmb(); |
| } |
| enq_pt = ep->first_trb; |
| } |
| } |
| |
| ep->enq_pt = enq_pt; |
| KnockDoorbell(ep->ep_num); |
| } else { |
| zxlogf(ERROR, "Eq = 0x%p != Dq = 0x%p", enq_pt, dq_pt); |
| } |
| } |
| |
| // Queue Transfer TRBs |
| void CrgUdc::UdcQueueTrbs(Endpoint* ep, uint32_t xfer_ring_size, uint32_t need_trbs_num, |
| uint32_t buffer_length) { |
| bool need_zlp = false; |
| bool full_td = true; |
| bool all_trbs_queued = false; |
| uint32_t free_trbs_num = 0; |
| uint32_t count; |
| uint32_t buffer_length_tmp = 0; |
| uint8_t short_pkt = 0; |
| uint8_t td_size; |
| uint8_t chain_bit = 1; |
| uint8_t intr_on_compl = 0; |
| uint32_t intr_rate; |
| uint32_t j = 1; |
| uint64_t req_buf = static_cast<uint64_t>(ep->phys) + ep->req_offset; |
| auto* enq_pt = ep->enq_pt; |
| |
| if (ep->zlp && (ep->req_length != 0) && (ep->req_length % ep->max_packet_size == 0)) { |
| need_zlp = true; |
| } |
| |
| td_size = static_cast<uint8_t>(need_trbs_num); |
| free_trbs_num = RoomOnRing(xfer_ring_size, ep->first_trb, ep->enq_pt, ep->deq_pt); |
| |
| if (ep->trbs_needed) { |
| req_buf += ep->req_length - ep->req_length_left; |
| } |
| |
| if (free_trbs_num > need_trbs_num) { |
| count = need_trbs_num; |
| } else { |
| count = free_trbs_num; |
| full_td = false; |
| ep->transfer_ring_full = true; |
| need_zlp = false; |
| } |
| |
| for (uint32_t i = 0; i < count; i++) { |
| buffer_length_tmp = (buffer_length > TRB_MAX_BUFFER_SIZE) ? TRB_MAX_BUFFER_SIZE : buffer_length; |
| buffer_length -= buffer_length_tmp; |
| |
| if (ep->dir_out) { |
| short_pkt = 1; |
| } |
| if (buffer_length == 0) { |
| chain_bit = 0; |
| intr_on_compl = 1; |
| all_trbs_queued = true; |
| } |
| intr_rate = 5; |
| if (!full_td && j == intr_rate) { |
| intr_on_compl = 1; |
| j = 0; |
| } |
| |
| uint8_t pcs = ep->pcs; |
| uint8_t AZP = 0; |
| if (all_trbs_queued) { |
| AZP = (need_zlp ? 1 : 0); |
| } |
| SetupNormalTrb(enq_pt, buffer_length_tmp, req_buf, td_size - 1, pcs, TRB_TYPE_XFER_NORMAL, |
| short_pkt, chain_bit, intr_on_compl, 0, 0, 0, 0, 0, AZP); |
| req_buf += buffer_length_tmp; |
| td_size--; |
| enq_pt++; |
| j++; |
| if (((enq_pt->dw3 >> TRB_TYPE_SHIFT) & TRB_TYPE_MASK) == TRB_TYPE_LINK) { |
| if ((enq_pt->dw3 >> TRB_LINK_TOGGLE_CYCLE_SHIFT) & TRB_LINK_TOGGLE_CYCLE_MASK) { |
| enq_pt->dw3 &= ~(TRB_CYCLE_BIT_MASK << TRB_CYCLE_BIT_SHIFT); |
| enq_pt->dw3 |= (ep->pcs & TRB_CYCLE_BIT_MASK) << TRB_CYCLE_BIT_SHIFT; |
| ep->pcs ^= 0x1; |
| // Make sure the PCS was updated before resetting the enqueue pointer |
| hw_wmb(); |
| enq_pt = ep->first_trb; |
| } |
| } |
| } |
| ep->enq_pt = enq_pt; |
| ep->req_length_left = buffer_length; |
| ep->trbs_needed = td_size; |
| } |
| |
| // Trigger the doorbell register to start DMA |
| void CrgUdc::KnockDoorbell(uint8_t ep_num) { |
| auto* mmio = get_mmio(); |
| |
| // Make sure all operation was finished bebore start the DMA transfer |
| hw_wmb(); |
| auto tmp = ep_num & 0x1f; |
| DOORBELL::Get().ReadFrom(mmio).set_db_target(tmp).WriteTo(mmio); |
| } |
| |
| // Build the transfer TD |
| void CrgUdc::BuildTransferTd(Endpoint* ep) { |
| uint32_t num_trbs_needed; |
| uint32_t ring_size = 0; |
| uint32_t buffer_length; |
| |
| if (ep->trbs_needed) { |
| // pending data of the previous request |
| buffer_length = ep->req_length_left; |
| num_trbs_needed = ep->trbs_needed; |
| } else { |
| buffer_length = ep->req_length; |
| num_trbs_needed = buffer_length / TRB_MAX_BUFFER_SIZE; |
| if (buffer_length == 0 || (buffer_length % TRB_MAX_BUFFER_SIZE)) { |
| num_trbs_needed += 1; |
| } |
| } |
| |
| if (ep->ep_num == 0) { |
| UdcQueueCtrl(ep, num_trbs_needed); |
| } else if (ep->type == USB_ENDPOINT_BULK || ep->type == USB_ENDPOINT_INTERRUPT) { |
| if (ep->type == USB_ENDPOINT_BULK) { |
| ring_size = CRGUDC_BULK_EP_TD_RING_SIZE; |
| } else if (ep->type == USB_ENDPOINT_INTERRUPT) { |
| ring_size = CRGUDC_INT_EP_TD_RING_SIZE; |
| } |
| |
| UdcQueueTrbs(ep, ring_size, num_trbs_needed, buffer_length); |
| KnockDoorbell(ep->ep_num); |
| } |
| } |
| |
| // Start to transfer data |
| void CrgUdc::StartTransfer(Endpoint* ep, uint32_t length) { |
| auto ep_num = ep->ep_num; |
| bool is_in = ep->dir_in; |
| |
| if (length > 0) { |
| if (is_in) { |
| if (ep_num == 0) { |
| ep0_buffer_.CacheFlush(ep->req_offset, length); |
| } else { |
| usb_request_cache_flush(ep->current_req, ep->req_offset, length); |
| } |
| } else { |
| if (ep_num == 0) { |
| ep0_buffer_.CacheFlushInvalidate(ep->req_offset, length); |
| } else { |
| usb_request_cache_flush_invalidate(ep->current_req, ep->req_offset, length); |
| } |
| } |
| } |
| |
| // Construct transfer TRB and queue to transfer ring |
| BuildTransferTd(ep); |
| } |
| |
| // Disable the Endpoint |
| void CrgUdc::DisableEp(uint8_t ep_num) { |
| auto* mmio = get_mmio(); |
| auto* ep = &endpoints_[ep_num]; |
| EpContext* ep_cx; |
| |
| fbl::AutoLock lock(&lock_); |
| |
| if (ep->ep_state == EpState::kEpStateDisabled) { |
| return; |
| } |
| |
| EPENABLED::Get().ReadFrom(mmio).set_ep_enabled(0x1 << ep_num).WriteTo(mmio); |
| enabled_eps_num_--; |
| |
| ep_cx = reinterpret_cast<EpContext*>(endpoint_context_.vaddr) + (ep_num - 2); |
| memset(ep_cx, 0, sizeof(struct EpContext)); |
| |
| if ((enabled_eps_num_ == 0) && (device_state_ == DeviceState::kUsbStateConfigured)) { |
| device_state_ = DeviceState::kUsbStateAddress; |
| } |
| ep->ep_state = EpState::kEpStateDisabled; |
| } |
| |
| // Handles transfer complete events for endpoint zero |
| void CrgUdc::HandleEp0TransferComplete() { |
| auto* ep = &endpoints_[0]; |
| |
| switch (setup_state_) { |
| case SetupState::kDataStageXfer: |
| setup_state_ = SetupState::kStatusStageRecv; |
| BuildEp0Status(ep, 0, 0); |
| break; |
| case SetupState::kDataStageRecv: |
| setup_state_ = SetupState::kStatusStageXfer; |
| BuildEp0Status(ep, 0, 0); |
| break; |
| default: |
| SetAddressCallback(); |
| setup_state_ = SetupState::kWaitForSetup; |
| break; |
| } |
| } |
| |
| // Handles transfer complete events for endpoints other than endpoint zero |
| void CrgUdc::HandleTransferComplete(uint8_t ep_num) { |
| auto* ep = &endpoints_[ep_num]; |
| |
| ep->lock.Acquire(); |
| |
| ep->req_offset += ep->req_xfersize; |
| |
| usb_request_t* req = ep->current_req; |
| if (req) { |
| Request request(req, sizeof(usb_request_t)); |
| // It is necessary to set current_req = nullptr |
| // in order to make this re-entrant safe and thread-safe. |
| // When we call request.Complete the callee may immediately re-queue this request. |
| // if it is already in current_req it could be completed twice (since QueueNextRequest |
| // would attempt to re-queue it, or CancelAll could take the lock on a separate thread and |
| // forcefully complete it after we've already completed it). |
| ep->current_req = nullptr; |
| ep->lock.Release(); |
| request.Complete(ZX_OK, ep->req_offset); |
| ep->lock.Acquire(); |
| |
| QueueNextRequest(ep); |
| } |
| ep->lock.Release(); |
| } |
| |
| // Clear the pending request |
| void CrgUdc::CompletePendingRequest(Endpoint* ep) { |
| RequestQueue complete_reqs; |
| |
| fbl::AutoLock lock(&ep->lock); |
| if (ep->current_req) { |
| complete_reqs.push(Request(ep->current_req, sizeof(usb_request_t))); |
| ep->current_req = nullptr; |
| } |
| for (auto req = ep->queued_reqs.pop(); req; req = ep->queued_reqs.pop()) { |
| complete_reqs.push(std::move(*req)); |
| } |
| |
| ep->enabled = false; |
| |
| // Requests must be completed outside of the lock. |
| for (auto req = complete_reqs.pop(); req; req = complete_reqs.pop()) { |
| req->Complete(ZX_ERR_IO_NOT_PRESENT, 0); |
| } |
| } |
| |
| // Free the dma buffer |
| void CrgUdc::DmaBufferFree(BufferInfo* dma_buf) { |
| if (dma_buf->vmo_handle != ZX_HANDLE_INVALID) { |
| if (dma_buf->pmt_handle != ZX_HANDLE_INVALID) { |
| zx_status_t status = zx_pmt_unpin(dma_buf->pmt_handle); |
| ZX_DEBUG_ASSERT(status == ZX_OK); |
| dma_buf->pmt_handle = ZX_HANDLE_INVALID; |
| } |
| |
| zx_vmar_unmap(zx_vmar_root_self(), (uintptr_t)dma_buf->vaddr, dma_buf->len); |
| zx_handle_close(dma_buf->vmo_handle); |
| dma_buf->vmo_handle = ZX_HANDLE_INVALID; |
| } |
| |
| dma_buf->vaddr = 0; |
| dma_buf->phys = 0; |
| dma_buf->len = 0; |
| } |
| |
| // Alloc the dma buffer |
| zx_status_t CrgUdc::DmaBufferAlloc(BufferInfo* dma_buf, uint32_t buf_size) { |
| zx_handle_t vmo_handle; |
| |
| zx_status_t status = ZX_OK; |
| status = zx_vmo_create_contiguous(bti_.get(), buf_size, 0, &vmo_handle); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "failed to allocate ring buffer vmo: %s", zx_status_get_string(status)); |
| return status; |
| } |
| |
| status = zx_vmo_set_cache_policy(vmo_handle, ZX_CACHE_POLICY_UNCACHED); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "zx_vmo_set_cache_policy failed: %s", zx_status_get_string(status)); |
| zx_handle_close(vmo_handle); |
| return status; |
| } |
| |
| zx_vaddr_t mapped_addr; |
| status = zx_vmar_map(zx_vmar_root_self(), ZX_VM_PERM_READ | ZX_VM_PERM_WRITE, 0, vmo_handle, 0, |
| buf_size, &mapped_addr); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "zx_vmar_map failed: %s", zx_status_get_string(status)); |
| zx_handle_close(vmo_handle); |
| return status; |
| } |
| |
| zx_paddr_t phys = 0; |
| zx_handle_t pmt_handle = ZX_HANDLE_INVALID; |
| uint32_t options = ZX_BTI_PERM_READ | ZX_BTI_PERM_WRITE; |
| if (buf_size > zx_system_get_page_size()) { |
| options |= ZX_BTI_CONTIGUOUS; |
| } |
| status = zx_bti_pin(bti_.get(), options, vmo_handle, 0, |
| fbl::round_up(buf_size, zx_system_get_page_size()), &phys, 1, &pmt_handle); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "zx_bti_pin failed:%s", zx_status_get_string(status)); |
| zx_vmar_unmap(zx_vmar_root_self(), mapped_addr, buf_size); |
| zx_handle_close(vmo_handle); |
| return status; |
| } |
| |
| dma_buf->vmo_handle = vmo_handle; |
| dma_buf->pmt_handle = pmt_handle; |
| dma_buf->vaddr = (void*)mapped_addr; |
| dma_buf->vmo_offset = 0; |
| dma_buf->len = buf_size; |
| dma_buf->phys = phys; |
| |
| return status; |
| } |
| |
| // Build the event ring |
| zx_status_t CrgUdc::InitEventRing() { |
| auto* mmio = get_mmio(); |
| UdcEvent* ring_info = &eventrings_[0]; |
| uint32_t alloc_len; |
| zx_status_t status = ZX_OK; |
| |
| // Create Event Ring Segment Table |
| if (ring_info->erst.vaddr == nullptr) { |
| alloc_len = sizeof(struct ErstData); |
| status = DmaBufferAlloc(&(ring_info->erst), alloc_len); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "InitEventRing: alloc dma buffer for Event Ring Segment Table:%s", |
| zx_status_get_string(status)); |
| return status; |
| } |
| } |
| ring_info->p_erst = reinterpret_cast<ErstData*>(ring_info->erst.vaddr); |
| |
| // Create Event Ring |
| if (ring_info->event_ring.vaddr == nullptr) { |
| alloc_len = CRG_UDC_EVENT_TRB_NUM * sizeof(struct TRBlock); |
| status = DmaBufferAlloc(&(ring_info->event_ring), alloc_len); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "InitEventRing: alloc dma buffer for Event Ring:%s", |
| zx_status_get_string(status)); |
| return status; |
| } |
| } |
| ring_info->evt_dq_pt = reinterpret_cast<TRBlock*>(ring_info->event_ring.vaddr); |
| ring_info->evt_seg0_last_trb = |
| reinterpret_cast<TRBlock*>(ring_info->event_ring.vaddr) + (CRG_UDC_EVENT_TRB_NUM - 1); |
| ring_info->CCS = 1; |
| ring_info->p_erst->seg_addr_lo = LOWER_32_BITS(static_cast<uint64_t>(ring_info->event_ring.phys)); |
| ring_info->p_erst->seg_addr_hi = UPPER_32_BITS(static_cast<uint64_t>(ring_info->event_ring.phys)); |
| ring_info->p_erst->seg_size = htole32(CRG_UDC_EVENT_TRB_NUM); |
| ring_info->p_erst->rsvd = 0; |
| // Make sure the physical address was allocated before setting the base address |
| hw_wmb(); |
| |
| ERSTSZ::Get().FromValue(0).set_erstsz(1).WriteTo(mmio); |
| // Event ring segment table base address |
| ERSTBALO::Get() |
| .FromValue(0) |
| .set_erstba_lo(LOWER_32_BITS(static_cast<uint64_t>(ring_info->erst.phys))) |
| .WriteTo(mmio); |
| ERSTBAHI::Get() |
| .FromValue(0) |
| .set_erstba_hi(UPPER_32_BITS(static_cast<uint64_t>(ring_info->erst.phys))) |
| .WriteTo(mmio); |
| // Event ring dequeue pointer register |
| ERDPLO::Get() |
| .FromValue(0) |
| .set_erdp_lo(LOWER_32_BITS(static_cast<uint64_t>(ring_info->event_ring.phys)) | 0x8) |
| .WriteTo(mmio); |
| ERDPHI::Get() |
| .FromValue(0) |
| .set_erdp_hi(UPPER_32_BITS(static_cast<uint64_t>(ring_info->event_ring.phys))) |
| .WriteTo(mmio); |
| |
| IMAN::Get().ReadFrom(mmio).set_ip(1).set_ie(1).WriteTo(mmio); |
| IMOD::Get().ReadFrom(mmio).set_imodi(4000).WriteTo(mmio); |
| |
| return status; |
| } |
| |
| // Build the device contexts |
| zx_status_t CrgUdc::InitDeviceContext() { |
| auto* mmio = get_mmio(); |
| zx_status_t status = ZX_OK; |
| uint32_t buf_size; |
| |
| // ep0 is not included in ep contexts in crg udc |
| if (endpoint_context_.vaddr == nullptr) { |
| buf_size = (CRG_UDC_MAX_EPS - 2) * sizeof(struct EpContext); |
| status = DmaBufferAlloc(&endpoint_context_, buf_size); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "InitDeviceContext: alloc dma buffer for device context:%s", |
| zx_status_get_string(status)); |
| return status; |
| } |
| } |
| |
| // Device context base address pointer |
| DCBAPLO::Get() |
| .FromValue(0) |
| .set_dcbap_lo(LOWER_32_BITS(static_cast<uint64_t>(endpoint_context_.phys))) |
| .WriteTo(mmio); |
| DCBAPHI::Get() |
| .FromValue(0) |
| .set_dcbap_hi(UPPER_32_BITS(static_cast<uint64_t>(endpoint_context_.phys))) |
| .WriteTo(mmio); |
| |
| return status; |
| } |
| |
| // Issue a command |
| zx_status_t CrgUdc::IssueCmd(enum CmdType type, uint32_t para0, uint32_t para1) { |
| auto* mmio = get_mmio(); |
| zx_status_t status = ZX_OK; |
| bool check_complete = false; |
| |
| auto value = COMMAND::Get().ReadFrom(mmio).start(); |
| if (value & 0x1) { |
| check_complete = true; |
| } |
| |
| if (check_complete) { |
| value = CMDCTRL::Get().ReadFrom(mmio).cmd_active(); |
| if (value & 0x1) { |
| zxlogf(ERROR, "IssueCmd: previous command is not complete!"); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| } |
| // Make sure the previous command was completed |
| hw_wmb(); |
| |
| CMDPARA0::Get().FromValue(0).set_cmd_para0(para0).WriteTo(mmio); |
| CMDPARA1::Get().FromValue(0).set_cmd_para1(para1).WriteTo(mmio); |
| |
| CMDCTRL::Get() |
| .ReadFrom(mmio) |
| .set_cmd_active(1) |
| .set_cmd_type(static_cast<uint8_t>(type)) |
| .WriteTo(mmio); |
| |
| if (check_complete) { |
| do { |
| value = CMDCTRL::Get().ReadFrom(mmio).cmd_active(); |
| } while (value & 0x1); |
| if (CMDCTRL::Get().ReadFrom(mmio).cmd_status()) { |
| zxlogf(ERROR, "Command Status: %d", CMDCTRL::Get().ReadFrom(mmio).cmd_status()); |
| return ZX_ERR_TIMED_OUT; |
| } |
| } |
| |
| return status; |
| } |
| |
| // Enable the EP0 port |
| zx_status_t CrgUdc::InitEp0() { |
| zx_status_t status = ZX_OK; |
| uint32_t buf_size; |
| |
| auto* ep = &endpoints_[0]; |
| buf_size = CRG_CONTROL_EP_TD_RING_SIZE * sizeof(struct TRBlock); |
| if (ep->dma_buf.vaddr == nullptr) { |
| status = DmaBufferAlloc(&(ep->dma_buf), buf_size); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "InitEp0: alloc dma buffer for transfer ring:%s", zx_status_get_string(status)); |
| return status; |
| } |
| } |
| |
| ep->first_trb = reinterpret_cast<TRBlock*>(ep->dma_buf.vaddr); |
| ep->last_trb = ep->first_trb + buf_size - 1; |
| |
| ep->enq_pt = ep->first_trb; |
| ep->deq_pt = ep->first_trb; |
| ep->pcs = 1; |
| ep->transfer_ring_full = false; |
| |
| // setup link TRB |
| ep->last_trb->dw0 = htole32(LOWER_32_BITS(ep->dma_buf.phys)); |
| ep->last_trb->dw1 = htole32(UPPER_32_BITS(ep->dma_buf.phys)); |
| ep->last_trb->dw2 = 0; |
| // TRB type and Toggle Cycle |
| uint32_t dw = (0x1 << TRB_LINK_TOGGLE_CYCLE_SHIFT) | (TRB_TYPE_LINK << TRB_TYPE_SHIFT); |
| ep->last_trb->dw3 = htole32(dw); |
| |
| auto para0 = (LOWER_32_BITS(ep->dma_buf.phys) & 0xfffffff0) | ep->pcs; |
| auto para1 = UPPER_32_BITS(ep->dma_buf.phys); |
| status = IssueCmd(CmdType::kCrgCmdIintEp0, para0, para1); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "InitEp0: alloc dma buffer for transfer ring:%s", zx_status_get_string(status)); |
| return status; |
| } |
| |
| ep->ep_state = EpState::kEpStateRunning; |
| |
| return status; |
| } |
| |
| // Enable interrupt and start the device |
| void CrgUdc::UdcStart() { |
| auto* mmio = get_mmio(); |
| |
| // interrupt related |
| CONFIG1::Get() |
| .ReadFrom(mmio) |
| .set_csc_event_en(1) |
| .set_pec_event_en(1) |
| .set_ppc_event_en(1) |
| .set_prc_event_en(1) |
| .set_plc_event_en(1) |
| .set_cec_event_en(1) |
| .WriteTo(mmio); |
| COMMAND::Get().ReadFrom(mmio).set_interrupt_en(1).set_sys_err_en(1).WriteTo(mmio); |
| // interrupt related end |
| |
| COMMAND::Get().ReadFrom(mmio).set_start(1).WriteTo(mmio); |
| } |
| |
| // Check the cable connect status |
| bool CrgUdc::CableIsConnected() { |
| auto* mmio = get_mmio(); |
| |
| auto val = PORTSC::Get().ReadFrom(mmio).pp(); |
| if (val) { |
| // make sure it is stable |
| zx::nanosleep(zx::deadline_after(zx::msec(100))); |
| val = PORTSC::Get().ReadFrom(mmio).pp(); |
| if (val) { |
| if (device_state_ < DeviceState::kUsbStatePowered) { |
| CONFIG0::Get().ReadFrom(mmio).set_usb3_dis_count_limit(15).WriteTo(mmio); |
| zx::nanosleep(zx::deadline_after(zx::msec(3))); |
| UdcStart(); |
| device_state_ = DeviceState::kUsbStatePowered; |
| } |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| // Check whether the event ring is empty |
| bool CrgUdc::EventRingEmpty() { |
| auto* event_ring = &eventrings_[0]; |
| |
| if (event_ring->evt_dq_pt) { |
| auto* event = event_ring->evt_dq_pt; |
| if ((event->dw3 & 0x1) != event_ring->CCS) { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| // Clear the port PM status |
| void CrgUdc::ClearPortPM() { |
| auto* mmio = get_mmio(); |
| |
| // USB3 port PM status and control |
| U3PORTPMSC::Get() |
| .ReadFrom(mmio) |
| .set_u1_initiate_en(0) |
| .set_u2_initiate_en(0) |
| .set_u1_timeout(0) |
| .set_u2_timeout(0) |
| .WriteTo(mmio); |
| } |
| |
| // reset the UDC device |
| zx_status_t CrgUdc::UdcReset() { |
| auto* mmio = get_mmio(); |
| |
| // reset the controller |
| COMMAND::Get().ReadFrom(mmio).set_soft_reset(1).WriteTo(mmio); |
| bool done = false; |
| for (int i = 0; i < 50; i++) { |
| zx::nanosleep(zx::deadline_after(zx::msec(10))); |
| if (COMMAND::Get().ReadFrom(mmio).soft_reset() == 0) { |
| done = true; |
| break; |
| } |
| } |
| if (!done) { |
| zxlogf(ERROR, "reset timeout"); |
| return ZX_ERR_TIMED_OUT; |
| } |
| |
| ClearPortPM(); |
| |
| setup_state_ = SetupState::kWaitForSetup; |
| device_state_ = DeviceState::kUsbStateAttached; |
| dev_addr_ = 0; |
| |
| // Complete any pending requests |
| for (uint32_t i = 0; i < CRG_UDC_MAX_EPS; i++) { |
| auto* ep = &endpoints_[i]; |
| CompletePendingRequest(ep); |
| ep->transfer_ring_full = false; |
| ep->ep_state = EpState::kEpStateDisabled; |
| } |
| enabled_eps_num_ = 0; |
| |
| ctrl_req_enq_idx_ = 0; |
| memset(ctrl_req_queue_, 0, sizeof(struct SetupPacket) * CTRL_REQ_QUEUE_DEPTH); |
| |
| return ZX_OK; |
| } |
| |
| // HW related operation |
| zx_status_t CrgUdc::ResetDataStruct() { |
| auto* mmio = get_mmio(); |
| zx_status_t status = ZX_OK; |
| |
| COMMAND::Get().ReadFrom(mmio).set_start(0).set_interrupt_en(0).WriteTo(mmio); |
| // High Speed |
| CONFIG0::Get().ReadFrom(mmio).set_max_speed(3).WriteTo(mmio); |
| |
| status = InitEventRing(); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "InitController: init evnet ring:%s", zx_status_get_string(status)); |
| return status; |
| } |
| |
| status = InitDeviceContext(); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "InitController: init device context:%s", zx_status_get_string(status)); |
| return status; |
| } |
| |
| return ZX_OK; |
| } |
| |
| // Reinit the UDC device |
| void CrgUdc::UdcReInit() { |
| auto* mmio = get_mmio(); |
| |
| setup_state_ = SetupState::kWaitForSetup; |
| device_state_ = DeviceState::kUsbStateReconnecting; |
| |
| auto ep_enabled = EPENABLED::Get().ReadFrom(mmio).reg_value(); |
| EPENABLED::Get().FromValue(0).set_reg_value(ep_enabled).WriteTo(mmio); |
| for (int i = 0; i < 50; i++) { |
| ep_enabled = EPENABLED::Get().ReadFrom(mmio).reg_value(); |
| if (ep_enabled == 0) { |
| break; |
| } |
| } |
| |
| for (uint32_t i = 2; i < CRG_UDC_MAX_EPS; i++) { |
| auto* ep = &endpoints_[i]; |
| ep->enabled = false; |
| CompletePendingRequest(ep); |
| ep->transfer_ring_full = false; |
| ep->ep_state = EpState::kEpStateDisabled; |
| } |
| enabled_eps_num_ = 0; |
| |
| if (dev_addr_ != 0) { |
| uint32_t param0 = 0; |
| IssueCmd(CmdType::kCrgCmdSetAddr, param0, 0); |
| dev_addr_ = 0; |
| } |
| ClearPortPM(); |
| } |
| |
| // Update max_packet_size by "Update EP0 config" command |
| void CrgUdc::UpdateEp0MaxPacketSize() { |
| uint16_t maxpacketsize; |
| uint32_t param0; |
| |
| if (device_speed_ >= USB_SPEED_SUPER) { |
| maxpacketsize = 512; |
| } else { |
| maxpacketsize = 64; |
| } |
| param0 = maxpacketsize << 16; |
| IssueCmd(CmdType::kCrgCmdUpdateEp0Cfg, param0, 0); |
| |
| auto* ep = &endpoints_[0]; |
| ep->max_packet_size = maxpacketsize; |
| } |
| |
| void CrgUdc::EnableSetup() { |
| auto* mmio = get_mmio(); |
| |
| CONFIG1::Get().ReadFrom(mmio).set_setup_event_en(1).WriteTo(mmio); |
| device_state_ = DeviceState::kUsbStateDefault; |
| setup_state_ = SetupState::kWaitForSetup; |
| } |
| |
| // Handle port status change event TRB |
| zx_status_t CrgUdc::HandlePortStatus() { |
| auto* mmio = get_mmio(); |
| uint32_t portsc_val; |
| zx_status_t status = ZX_OK; |
| uint32_t speed; |
| |
| // handle port reset |
| portsc_val = PORTSC::Get().ReadFrom(mmio).reg_value(); |
| PORTSC::Get().FromValue(0).set_reg_value(portsc_val).WriteTo(mmio); |
| |
| if (portsc_val & (0x1 << 21)) { |
| zx::nanosleep(zx::deadline_after(zx::msec(3))); |
| auto portsc = PORTSC::Get().ReadFrom(mmio); |
| if (portsc.prc()) { |
| zxlogf(INFO, "HandlePortStatus: RPC is still set"); |
| } else if (portsc.pr()) { |
| zxlogf(INFO, "HandlePortStatus: PRC is not set, but PR is set"); |
| } else { |
| if ((portsc.pls() != 0) || !(portsc.reg_value() & 0x2)) { |
| return ZX_OK; |
| } |
| |
| switch (portsc.speed()) { |
| case CRG_U3DC_PORTSC_SPEED_SSP: |
| speed = USB_SPEED_ENHANCED_SUPER; |
| break; |
| case CRG_U3DC_PORTSC_SPEED_SS: |
| speed = USB_SPEED_SUPER; |
| break; |
| case CRG_U3DC_PORTSC_SPEED_HS: |
| speed = USB_SPEED_HIGH; |
| break; |
| case CRG_U3DC_PORTSC_SPEED_FS: |
| speed = USB_SPEED_FULL; |
| break; |
| case CRG_U3DC_PORTSC_SPEED_LS: |
| speed = USB_SPEED_LOW; |
| break; |
| default: |
| speed = USB_SPEED_UNDEFINED; |
| return ZX_OK; |
| } |
| |
| if (device_state_ > DeviceState::kUsbStateDefault) { |
| UdcReInit(); |
| } |
| |
| device_speed_ = speed; |
| if (dci_intf_) { |
| dci_intf_->SetSpeed(USB_SPEED_HIGH); |
| } |
| UpdateEp0MaxPacketSize(); |
| SetConnected(true); |
| |
| if (device_state_ < DeviceState::kUsbStateReconnecting) { |
| EnableSetup(); |
| } |
| } |
| } |
| // handle port connection change |
| if (portsc_val & (0x1 << 17)) { |
| auto portsc = PORTSC::Get().ReadFrom(mmio); |
| if (portsc.ccs() && portsc.pp()) { |
| zxlogf(INFO, "HandlePortStatus: connect int checked"); |
| if ((portsc.pls() != 0) || !(portsc.reg_value() & 0x2)) { |
| return ZX_OK; |
| } |
| |
| switch (portsc.speed()) { |
| case CRG_U3DC_PORTSC_SPEED_SSP: |
| speed = USB_SPEED_ENHANCED_SUPER; |
| break; |
| case CRG_U3DC_PORTSC_SPEED_SS: |
| speed = USB_SPEED_SUPER; |
| break; |
| case CRG_U3DC_PORTSC_SPEED_HS: |
| speed = USB_SPEED_HIGH; |
| break; |
| case CRG_U3DC_PORTSC_SPEED_FS: |
| speed = USB_SPEED_FULL; |
| break; |
| case CRG_U3DC_PORTSC_SPEED_LS: |
| default: |
| speed = USB_SPEED_UNDEFINED; |
| return ZX_OK; |
| } |
| device_speed_ = speed; |
| if (dci_intf_) { |
| dci_intf_->SetSpeed(device_speed_); |
| } |
| UpdateEp0MaxPacketSize(); |
| SetConnected(true); |
| |
| if (device_state_ < DeviceState::kUsbStateReconnecting) { |
| EnableSetup(); |
| } |
| } else if (!portsc.ccs()) { |
| bool cable_connected; |
| uint32_t ccs_drop_ignore = 0; |
| |
| if ((portsc.pls() == 0x0) && (portsc.speed() < CRG_U3DC_PORTSC_SPEED_SS)) { |
| ccs_drop_ignore = 1; |
| zxlogf(INFO, "HandlePortStatus: ccs glitch detect on HS/FS"); |
| } |
| |
| if (ccs_drop_ignore == 0) { |
| device_speed_ = USB_SPEED_UNDEFINED; |
| } |
| zx::nanosleep(zx::deadline_after(zx::msec(150))); |
| cable_connected = CableIsConnected(); |
| |
| if (cable_connected && (ccs_drop_ignore == 0)) { |
| device_state_ = DeviceState::kUsbStatePowered; |
| UdcReInit(); |
| SetConnected(false); |
| } else if (!cable_connected) { |
| zxlogf(INFO, "HandlePortStatus: cable disconnected, rst controller"); |
| |
| UdcReset(); |
| ResetDataStruct(); |
| SetConnected(false); |
| InitEp0(); |
| device_state_ = DeviceState::kUsbStateAttached; |
| return ZX_ERR_INTERNAL; |
| } |
| } |
| } |
| |
| if (portsc_val & (0x1 << 22)) { |
| auto portsc = PORTSC::Get().ReadFrom(mmio); |
| if (portsc.pls() == 0xf) { |
| PORTSC::Get().FromValue(0).set_pls(0).WriteTo(mmio); |
| } else if (portsc.pls() == 0x3) { |
| // The USB cable is unplugged |
| SetConnected(false); |
| for (uint8_t i = 0; i < std::size(endpoints_); i++) { |
| auto* ep = &endpoints_[i]; |
| DmaBufferFree(&ep->dma_buf); |
| } |
| auto* event_ring = &eventrings_[0]; |
| DmaBufferFree(&event_ring->erst); |
| DmaBufferFree(&event_ring->event_ring); |
| DmaBufferFree(&endpoint_context_); |
| |
| UdcReset(); |
| ResetDataStruct(); |
| InitEp0(); |
| device_speed_ = USB_SPEED_UNDEFINED; |
| CableIsConnected(); |
| } |
| } |
| |
| return status; |
| } |
| |
| zx_paddr_t CrgUdc::TranTrbDmaToVirt(Endpoint* ep, zx_paddr_t phy) { |
| zx_paddr_t offset; |
| |
| offset = phy - ep->dma_buf.phys; |
| offset = offset / sizeof(struct TRBlock); |
| |
| return offset; |
| } |
| |
| zx_paddr_t CrgUdc::EventTrbVirtToDma(UdcEvent* event_ring, TRBlock* event) { |
| zx_paddr_t offset; |
| uint32_t trb_idx; |
| |
| trb_idx = static_cast<uint32_t>(event - reinterpret_cast<TRBlock*>(event_ring->event_ring.vaddr)); |
| offset = trb_idx * sizeof(struct TRBlock); |
| return event_ring->event_ring.phys + offset; |
| } |
| |
| // Issue command "Initialize EP0" to reset EP0 logic and initialize its transfer ring |
| zx_status_t CrgUdc::PrepareForSetup() { |
| uint32_t param0; |
| uint32_t param1; |
| |
| if (!EventRingEmpty() || portsc_on_reconnecting_ == 1) { |
| zxlogf(ERROR, "not ready for setup"); |
| return ZX_ERR_SHOULD_WAIT; |
| } |
| |
| auto* ep = &endpoints_[0]; |
| CompletePendingRequest(ep); |
| |
| ctrl_req_enq_idx_ = 0; |
| memset(ctrl_req_queue_, 0, sizeof(struct SetupPacket) * CTRL_REQ_QUEUE_DEPTH); |
| |
| param0 = (LOWER_32_BITS(ep->dma_buf.phys) & 0xfffffff0) | ep->pcs; |
| param1 = UPPER_32_BITS(ep->dma_buf.phys); |
| IssueCmd(CmdType::kCrgCmdIintEp0, param0, param1); |
| |
| ep->deq_pt = ep->enq_pt; |
| ep->transfer_ring_full = false; |
| |
| EnableSetup(); |
| |
| return ZX_OK; |
| } |
| |
| void CrgUdc::QueueSetupPkt(usb_setup_t* setup_pkt, uint16_t setup_tag) { |
| if (ctrl_req_enq_idx_ == CTRL_REQ_QUEUE_DEPTH) { |
| return; |
| } |
| memcpy(&(ctrl_req_queue_[ctrl_req_enq_idx_].usbctrlreq), setup_pkt, sizeof(struct usb_setup)); |
| ctrl_req_queue_[ctrl_req_enq_idx_].setup_tag = setup_tag; |
| |
| ctrl_req_enq_idx_++; |
| } |
| |
| // Handle the event TRB |
| zx_status_t CrgUdc::UdcHandleEvent(TRBlock* event) { |
| zx_status_t status = ZX_OK; |
| |
| // trb type |
| switch ((event->dw3 >> TRB_TYPE_SHIFT) & TRB_TYPE_MASK) { |
| case TRB_TYPE_EVT_PORT_STATUS_CHANGE: |
| if (device_state_ == DeviceState::kUsbStateReconnecting) { |
| portsc_on_reconnecting_ = 1; |
| break; |
| } |
| status = HandlePortStatus(); |
| break; |
| case TRB_TYPE_EVT_TRANSFER: |
| if (device_state_ < DeviceState::kUsbStateReconnecting) { |
| zxlogf(ERROR, "UdcHandleEvent: Xfer compl event rcved when kUsbStateReconnecting"); |
| break; |
| } |
| status = HandleXferEvent(event); |
| break; |
| case TRB_TYPE_EVT_SETUP_PKT: |
| usb_setup_t* setup_pkt; |
| uint16_t setup_tag; |
| |
| setup_pkt = reinterpret_cast<usb_setup_t*>(&event->dw0); |
| setup_tag = (event->dw3 >> EVE_TRB_SETUP_TAG_SHIFT) & EVE_TRB_SETUP_TAG_MASK; |
| if (setup_state_ != SetupState::kWaitForSetup) { |
| QueueSetupPkt(setup_pkt, setup_tag); |
| break; |
| } |
| |
| memcpy(&cur_setup_, setup_pkt, sizeof(cur_setup_)); |
| setup_tag_ = setup_tag; |
| HandleEp0Setup(); |
| break; |
| default: |
| zxlogf(ERROR, "UdcHandleEvent: unexpect TRB_TYPE"); |
| break; |
| } |
| |
| return status; |
| } |
| |
| // Process the event ring |
| zx_status_t CrgUdc::ProcessEventRing() { |
| auto* mmio = get_mmio(); |
| zx_status_t status = ZX_OK; |
| |
| IMAN::Get().ReadFrom(mmio).set_ip(1).WriteTo(mmio); |
| auto* event_ring = &eventrings_[0]; |
| while (event_ring->evt_dq_pt) { |
| hw_rmb(); |
| auto* event = event_ring->evt_dq_pt; |
| |
| if ((event->dw3 & EVE_TRB_CYCLE_BIT_MASK) != event_ring->CCS) { |
| break; |
| } |
| status = UdcHandleEvent(event); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "ProcessEventRing: handle event:%s", zx_status_get_string(status)); |
| return status; |
| } |
| |
| if (event == event_ring->evt_seg0_last_trb) { |
| event_ring->CCS = event_ring->CCS ? 0 : 1; |
| event_ring->evt_dq_pt = reinterpret_cast<TRBlock*>(event_ring->event_ring.vaddr); |
| } else { |
| event_ring->evt_dq_pt++; |
| } |
| } |
| |
| // update dequeue pointer |
| zx_paddr_t erdp = EventTrbVirtToDma(event_ring, event_ring->evt_dq_pt); |
| ERDPHI::Get().ReadFrom(mmio).set_erdp_hi(UPPER_32_BITS(erdp)).WriteTo(mmio); |
| ERDPLO::Get().ReadFrom(mmio).set_erdp_lo(LOWER_32_BITS(erdp) | (0x1 << 3)).WriteTo(mmio); |
| |
| return status; |
| } |
| |
| // Fill the device context for EPs |
| void CrgUdc::EpContextSetup(const usb_endpoint_descriptor_t* ep_desc, |
| const usb_ss_ep_comp_descriptor_t* ss_comp_desc) { |
| uint16_t maxburst = 0; |
| uint8_t maxstreams = 0; |
| uint32_t dw = 0; |
| uint32_t ep_type; |
| |
| uint8_t ep_num = CRG_UDC_ADDR_TO_INDEX(ep_desc->b_endpoint_address); |
| bool is_in = (ep_desc->b_endpoint_address & USB_DIR_MASK) == USB_DIR_IN; |
| |
| auto* ep = &endpoints_[ep_num]; |
| ep_type = usb_ep_type(ep_desc); |
| |
| uint16_t max_packet_size = usb_ep_max_packet(ep_desc); |
| if (device_speed_ >= USB_SPEED_SUPER) { |
| maxburst = ss_comp_desc->b_max_burst; |
| if (ep_type == USB_ENDPOINT_BULK) { |
| maxstreams = ss_comp_desc->bm_attributes & 0x1f; |
| } |
| } else if ((device_speed_ == USB_SPEED_HIGH || device_speed_ == USB_SPEED_FULL) && |
| (ep_type == USB_ENDPOINT_INTERRUPT)) { |
| if (device_speed_ == USB_SPEED_HIGH) { |
| maxburst = usb_ep_add_mf_transactions(ep_desc); |
| } |
| maxburst = (maxburst == 0x3) ? 0x2 : maxburst; |
| } |
| |
| // corigine gadget dir should be opposite to host dir |
| if (!is_in) { |
| ep_type = usb_ep_type(ep_desc) + EP_TYPE_INVALID2; |
| } |
| |
| if (maxstreams) { |
| zxlogf(INFO, " maxstream=%d is not expected", maxstreams); |
| } |
| // fill endpoint context |
| auto* epcx = reinterpret_cast<EpContext*>(endpoint_context_.vaddr) + (ep_num - 2); |
| // dw0: logical EP number - bit[3:6], Interval - bit[16:23] |
| dw = ((ep_num / 2) & EP_CX_LOGICAL_EP_NUM_MASK) << EP_CX_LOGICAL_EP_NUM_SHIFT; |
| dw |= (ep_desc->b_interval & EP_CX_INTERVAL_MASK) << EP_CX_INTERVAL_SHIFT; |
| epcx->dw0 = htole32(dw); |
| |
| // dw1: EP Type - bit[3:5], Max Burst Size - bit[8:15], Max Packet Size - bit[16:31] |
| dw = (static_cast<uint32_t>(ep_type) & EP_CX_EP_TYPE_MASK) << EP_CX_EP_TYPE_SHIFT; |
| dw |= (maxburst & EP_CX_MAX_BURST_SIZE_MASK) << EP_CX_MAX_BURST_SIZE_SHIFT; |
| dw |= (max_packet_size & EP_CX_MAX_PACKET_SIZE_MASK) << EP_CX_MAX_PACKET_SIZE_SHIFT; |
| epcx->dw1 = htole32(dw); |
| |
| // dw2: DCS - bit0, TR Dequeue Pointer Lo - [4:31] |
| dw = ep->pcs & EP_CX_DEQ_CYC_STATE_MASK; |
| dw |= LOWER_32_BITS(static_cast<uint64_t>(ep->dma_buf.phys)) & EP_CX_TR_DQPT_LO_MASK; |
| epcx->dw2 = htole32(dw); |
| |
| // dw3: TR Dequeue Pointer Hi - [0:31] |
| dw = UPPER_32_BITS(static_cast<uint64_t>(ep->dma_buf.phys)); |
| epcx->dw3 = htole32(dw); |
| |
| // Make sure the device context was build before starting the configuration command |
| hw_wmb(); |
| } |
| |
| zx_status_t CrgUdc::InitController() { |
| auto* mmio = get_mmio(); |
| uint32_t reg_val; |
| zx_status_t status = ZX_OK; |
| |
| // set controller to device role |
| reg_val = mmio->Read32(0x20fc); |
| reg_val |= 0x1; |
| mmio->Write32(reg_val, 0x20fc); |
| |
| status = UdcReset(); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "InitController: reset udc controller:%s", zx_status_get_string(status)); |
| return status; |
| } |
| |
| ClearPortPM(); |
| |
| status = ResetDataStruct(); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "InitController: reset data struct:%s", zx_status_get_string(status)); |
| return status; |
| } |
| |
| status = InitEp0(); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "InitController: init EP0:%s", zx_status_get_string(status)); |
| return status; |
| } |
| |
| return ZX_OK; |
| } |
| |
| void CrgUdc::SetConnected(bool connected) { |
| if (connected == connected_) { |
| return; |
| } |
| |
| if (dci_intf_) { |
| dci_intf_->SetConnected(connected); |
| } |
| if (usb_phy_) { |
| usb_phy_->ConnectStatusChanged(connected); |
| } |
| |
| if (!connected) { |
| // Complete any pending requests |
| RequestQueue complete_reqs; |
| |
| for (uint8_t i = 0; i < std::size(endpoints_); i++) { |
| auto* ep = &endpoints_[i]; |
| |
| fbl::AutoLock lock(&ep->lock); |
| if (ep->current_req) { |
| complete_reqs.push(Request(ep->current_req, sizeof(usb_request_t))); |
| ep->current_req = nullptr; |
| } |
| for (auto req = ep->queued_reqs.pop(); req; req = ep->queued_reqs.pop()) { |
| complete_reqs.push(std::move(*req)); |
| } |
| |
| ep->enabled = false; |
| } |
| |
| // Requests must be completed outside of the lock. |
| for (auto req = complete_reqs.pop(); req; req = complete_reqs.pop()) { |
| req->Complete(ZX_ERR_IO_NOT_PRESENT, 0); |
| } |
| } |
| |
| connected_ = connected; |
| } |
| |
| zx_status_t CrgUdc::Create(void* ctx, zx_device_t* parent) { |
| auto dev = std::make_unique<CrgUdc>(parent); |
| auto status = dev->Init(); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| // devmgr is now in charge of the device. |
| [[maybe_unused]] auto* _ = dev.release(); |
| return ZX_OK; |
| } |
| |
| zx_status_t CrgUdc::Init() { |
| pdev_ = ddk::PDevFidl::FromFragment(parent()); |
| if (!pdev_.is_valid()) { |
| zxlogf(ERROR, "CrgUdc::Create: could not get platform device protocol"); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| // USB PHY protocol is optional. |
| auto phy = usb_phy::UsbPhyClient::Create(parent(), "udc-phy"); |
| if (phy.is_ok()) { |
| usb_phy_.emplace(std::move(phy.value())); |
| } |
| |
| for (uint8_t i = 0; i < std::size(endpoints_); i++) { |
| auto* ep = &endpoints_[i]; |
| ep->ep_num = i; |
| } |
| |
| size_t actual; |
| auto status = DdkGetMetadata(DEVICE_METADATA_PRIVATE, &metadata_, sizeof(metadata_), &actual); |
| if (status != ZX_OK || actual != sizeof(metadata_)) { |
| zxlogf(ERROR, "CrgUdc::Init can't get driver metadata"); |
| return ZX_ERR_INTERNAL; |
| } |
| |
| status = pdev_.MapMmio(0, &mmio_); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "CrgUdc::Init MapMmio failed: %s", zx_status_get_string(status)); |
| return status; |
| } |
| |
| status = pdev_.GetInterrupt(0, &irq_); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "CrgUdc::Init GetInterrupt failed: %s", zx_status_get_string(status)); |
| return status; |
| } |
| |
| status = pdev_.GetBti(0, &bti_); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "CrgUdc::Init GetBti failed: %s", zx_status_get_string(status)); |
| return status; |
| } |
| |
| status = ep0_buffer_.Init(bti_.get(), UINT16_MAX, IO_BUFFER_RW | IO_BUFFER_CONTIG); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "CrgUdc::Init ep0_buffer_.Init failed: %s", zx_status_get_string(status)); |
| return status; |
| } |
| |
| status = ep0_buffer_.PhysMap(); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "CrgUdc::Init ep0_buffer_.PhysMap failed: %s", zx_status_get_string(status)); |
| return status; |
| } |
| |
| if ((status = InitController()) != ZX_OK) { |
| zxlogf(ERROR, "CrgUdc::Init InitController failed: %s", zx_status_get_string(status)); |
| return status; |
| } |
| |
| status = DdkAdd("udc"); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "CrgUdc::Init DdkAdd failed: %s", zx_status_get_string(status)); |
| return status; |
| } |
| |
| return ZX_OK; |
| } |
| |
| void CrgUdc::DdkInit(ddk::InitTxn txn) { |
| int rc = thrd_create_with_name( |
| &irq_thread_, [](void* arg) -> int { return reinterpret_cast<CrgUdc*>(arg)->IrqThread(); }, |
| reinterpret_cast<void*>(this), "udc-interrupt-thread"); |
| if (rc == thrd_success) { |
| thread_joinable_ = true; |
| txn.Reply(ZX_OK); |
| } else { |
| txn.Reply(ZX_ERR_INTERNAL); |
| } |
| } |
| |
| int CrgUdc::IrqThread() { |
| auto* mmio = get_mmio(); |
| const char* role_name = "fuchsia.devices.usb.drivers.crg-udc.interrupt"; |
| zx_status_t status = device_set_profile_by_role(parent_, thrd_get_zx_handle(thrd_current()), |
| role_name, strlen(role_name)); |
| if (status != ZX_OK) { |
| zxlogf(WARNING, "%s Failed to set role: %s", __FUNCTION__, zx_status_get_string(status)); |
| } |
| |
| if (!CableIsConnected()) { |
| zxlogf(INFO, "crg_udc: the cable is not connected"); |
| return 0; |
| } |
| |
| while (!thread_terminate_) { |
| wait_start_time_ = zx::clock::get_monotonic(); |
| auto wait_res = irq_.wait(&irq_timestamp_); |
| irq_dispatch_timestamp_ = zx::clock::get_monotonic(); |
| if (wait_res == ZX_ERR_CANCELED) { |
| break; |
| } else if (wait_res != ZX_OK) { |
| zxlogf(ERROR, "crg_udc: irq wait failed, retcode = %s", zx_status_get_string(wait_res)); |
| } |
| |
| auto usbstatus = STATUS::Get().ReadFrom(mmio); |
| |
| if (usbstatus.sys_err() == 1) { |
| zxlogf(ERROR, "crg_udc: system error"); |
| STATUS::Get().FromValue(0).set_sys_err(1).WriteTo(mmio); |
| break; |
| } |
| |
| if (usbstatus.eint() == 1) { |
| STATUS::Get().FromValue(0).set_eint(1).WriteTo(mmio); |
| // process event ring |
| ProcessEventRing(); |
| } |
| |
| if (device_state_ == DeviceState::kUsbStateReconnecting && portsc_on_reconnecting_ == 1 && |
| EventRingEmpty()) { |
| portsc_on_reconnecting_ = 0; |
| HandlePortStatus(); |
| } |
| |
| if (device_state_ == DeviceState::kUsbStateReconnecting && connected_) { |
| PrepareForSetup(); |
| } |
| } |
| |
| zxlogf(INFO, "crg_udc: irq thread finished"); |
| return 0; |
| } |
| |
| void CrgUdc::DdkUnbind(ddk::UnbindTxn txn) { |
| thread_terminate_ = true; |
| irq_.destroy(); |
| if (thread_joinable_) { |
| thread_joinable_ = false; |
| thrd_join(irq_thread_, nullptr); |
| } |
| txn.Reply(); |
| } |
| |
| void CrgUdc::DdkRelease() { delete this; } |
| |
| void CrgUdc::DdkSuspend(ddk::SuspendTxn txn) { |
| fbl::AutoLock lock(&lock_); |
| thread_terminate_ = true; |
| irq_.destroy(); |
| shutting_down_ = true; |
| lock.release(); |
| |
| if (thread_joinable_) { |
| thread_joinable_ = false; |
| thrd_join(irq_thread_, nullptr); |
| } |
| |
| // transfer ring |
| for (uint8_t i = 0; i < std::size(endpoints_); i++) { |
| auto* ep = &endpoints_[i]; |
| DmaBufferFree(&ep->dma_buf); |
| } |
| |
| // event ring |
| auto* event_ring = &eventrings_[0]; |
| DmaBufferFree(&event_ring->erst); |
| DmaBufferFree(&event_ring->event_ring); |
| |
| // device contexts |
| DmaBufferFree(&endpoint_context_); |
| |
| ep0_buffer_.release(); |
| txn.Reply(ZX_OK, 0); |
| } |
| |
| void CrgUdc::UsbDciRequestQueue(usb_request_t* req, const usb_request_complete_callback_t* cb) { |
| { |
| fbl::AutoLock lock(&lock_); |
| if (shutting_down_) { |
| lock.release(); |
| usb_request_complete(req, ZX_ERR_IO_NOT_PRESENT, 0, cb); |
| } |
| } |
| uint8_t ep_num = CRG_UDC_ADDR_TO_INDEX(req->header.ep_address); |
| if (ep_num == 0 || ep_num >= std::size(endpoints_)) { |
| zxlogf(ERROR, "CrgUdc::UsbDciRequestQueue: bad ep address 0x%02X", req->header.ep_address); |
| usb_request_complete(req, ZX_ERR_INVALID_ARGS, 0, cb); |
| return; |
| } |
| zxlogf(SERIAL, "UsbDciRequestQueue ep %u length %zu", ep_num, req->header.length); |
| |
| auto* ep = &endpoints_[ep_num]; |
| |
| if (!ep->enabled) { |
| usb_request_complete(req, ZX_ERR_BAD_STATE, 0, cb); |
| zxlogf(ERROR, "the endpoint %d not enabled", ep_num); |
| return; |
| } |
| |
| // OUT transactions must have length > 0 and multiple of max packet size |
| if (CRG_UDC_EP_IS_OUT(ep_num)) { |
| if (req->header.length == 0 || req->header.length % ep->max_packet_size != 0) { |
| zxlogf(ERROR, "udc_ep_queue: OUT transfers must be multiple of max packet size"); |
| usb_request_complete(req, ZX_ERR_INVALID_ARGS, 0, cb); |
| return; |
| } |
| } |
| |
| fbl::AutoLock lock(&ep->lock); |
| |
| if (!configured_) { |
| zxlogf(ERROR, "udc_ep_queue not configured!"); |
| usb_request_complete(req, ZX_ERR_BAD_STATE, 0, cb); |
| return; |
| } |
| |
| ep->queued_reqs.push(Request(req, *cb, sizeof(usb_request_t))); |
| QueueNextRequest(ep); |
| } |
| |
| zx_status_t CrgUdc::UsbDciSetInterface(const usb_dci_interface_protocol_t* interface) { |
| if (dci_intf_) { |
| zxlogf(ERROR, "dci_intf_ already set"); |
| return ZX_ERR_BAD_STATE; |
| } |
| |
| dci_intf_ = ddk::UsbDciInterfaceProtocolClient(interface); |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t CrgUdc::UsbDciConfigEp(const usb_endpoint_descriptor_t* ep_desc, |
| const usb_ss_ep_comp_descriptor_t* ss_comp_desc) { |
| uint32_t param0; |
| zx_status_t status = ZX_OK; |
| |
| uint8_t ep_num = CRG_UDC_ADDR_TO_INDEX(ep_desc->b_endpoint_address); |
| if (ep_num == 0 || ep_num == 1 || ep_num >= std::size(endpoints_)) { |
| zxlogf(ERROR, "CrgUdc::UsbDciConfigEp: bad ep address 0x%02X", ep_desc->b_endpoint_address); |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| bool is_in = (ep_desc->b_endpoint_address & USB_DIR_MASK) == USB_DIR_IN; |
| uint8_t ep_type = usb_ep_type(ep_desc); |
| uint16_t max_packet_size = usb_ep_max_packet(ep_desc); |
| |
| if (ep_type == USB_ENDPOINT_ISOCHRONOUS) { |
| zxlogf(ERROR, "CrgUdc::UsbDciConfigEp: isochronous endpoints are not supported"); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| auto* ep = &endpoints_[ep_num]; |
| fbl::AutoLock lock(&ep->lock); |
| |
| ep->type = ep_type; |
| ep->max_packet_size = max_packet_size; |
| if (is_in) { |
| ep->dir_in = true; |
| ep->dir_out = false; |
| } else { |
| ep->dir_in = false; |
| ep->dir_out = true; |
| } |
| |
| if (ep->ep_state != EpState::kEpStateDisabled) { |
| DisableEp(ep_num); |
| } |
| |
| if (ep->dma_buf.vaddr == nullptr) { |
| uint32_t ring_size = 0; |
| uint32_t alloc_len; |
| if (ep_type == USB_ENDPOINT_BULK) { |
| ring_size = CRGUDC_BULK_EP_TD_RING_SIZE; |
| } else if (ep_type == USB_ENDPOINT_INTERRUPT) { |
| ring_size = CRGUDC_INT_EP_TD_RING_SIZE; |
| } |
| alloc_len = ring_size * sizeof(struct TRBlock); |
| status = DmaBufferAlloc(&(ep->dma_buf), alloc_len); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "UsbDciConfigEp: alloc dma buffer for transfer ring:%s", |
| zx_status_get_string(status)); |
| return status; |
| } |
| ep->first_trb = reinterpret_cast<TRBlock*>(ep->dma_buf.vaddr); |
| ep->last_trb = ep->first_trb + ring_size - 1; |
| |
| // setup link trb |
| ep->last_trb->dw0 = LOWER_32_BITS(static_cast<uint64_t>(ep->dma_buf.phys)); |
| ep->last_trb->dw1 = UPPER_32_BITS(static_cast<uint64_t>(ep->dma_buf.phys)); |
| ep->last_trb->dw2 = 0; |
| uint32_t dw = (0x1 << TRB_LINK_TOGGLE_CYCLE_SHIFT) | (TRB_TYPE_LINK << TRB_TYPE_SHIFT); |
| ep->last_trb->dw3 = htole32(dw); |
| // Make sure the link TRB was build before setting enqueue/dequeue pointer |
| hw_wmb(); |
| |
| ep->enq_pt = ep->first_trb; |
| ep->deq_pt = ep->first_trb; |
| ep->pcs = 1; |
| ep->transfer_ring_full = false; |
| enabled_eps_num_++; |
| EpContextSetup(ep_desc, ss_comp_desc); |
| } |
| |
| param0 = 0x1 << ep_num; |
| IssueCmd(CmdType::kCrgCmdConfigEp, param0, 0); |
| |
| ep->enabled = true; |
| ep->ep_state = EpState::kEpStateRunning; |
| if (device_state_ == DeviceState::kUsbStateAddress) { |
| device_state_ = DeviceState::kUsbStateConfigured; |
| } |
| |
| if (configured_) { |
| QueueNextRequest(ep); |
| } |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t CrgUdc::UsbDciDisableEp(uint8_t ep_address) { |
| uint8_t ep_num = CRG_UDC_ADDR_TO_INDEX(ep_address); |
| if (ep_num == 0 || ep_num == 1 || ep_num >= std::size(endpoints_)) { |
| zxlogf(ERROR, "CrgUdc::UsbDciConfigEp: bad ep address 0x%02X", ep_address); |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| auto* ep = &endpoints_[ep_num]; |
| |
| fbl::AutoLock lock(&ep->lock); |
| |
| DisableEp(ep_num); |
| ep->enabled = false; |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t CrgUdc::UsbDciEpSetStall(uint8_t ep_address) { |
| // TODO(voydanoff) implement this |
| return ZX_OK; |
| } |
| |
| zx_status_t CrgUdc::UsbDciEpClearStall(uint8_t ep_address) { |
| // TODO(voydanoff) implement this |
| return ZX_OK; |
| } |
| |
| size_t CrgUdc::UsbDciGetRequestSize() { return Request::RequestSize(sizeof(usb_request_t)); } |
| |
| zx_status_t CrgUdc::UsbDciCancelAll(uint8_t epid) { |
| uint8_t ep_num = CRG_UDC_ADDR_TO_INDEX(epid); |
| auto* ep = &endpoints_[ep_num]; |
| |
| fbl::AutoLock lock(&ep->lock); |
| RequestQueue queue = std::move(ep->queued_reqs); |
| if (ep->current_req) { |
| queue.push(Request(ep->current_req, sizeof(usb_request_t))); |
| ep->current_req = nullptr; |
| } |
| lock.release(); |
| queue.CompleteAll(ZX_ERR_IO_NOT_PRESENT, 0); |
| return ZX_OK; |
| } |
| |
| static constexpr zx_driver_ops_t driver_ops = []() { |
| zx_driver_ops_t ops = {}; |
| ops.version = DRIVER_OPS_VERSION; |
| ops.bind = CrgUdc::Create; |
| return ops; |
| }(); |
| |
| } // namespace crg_udc |
| |
| ZIRCON_DRIVER(crg_udc, crg_udc::driver_ops, "zircon", "0.1"); |