/* * SPDX-FileCopyrightText: Copyright (c) 2020 Raspberry Pi (Trading) Ltd. * SPDX-FileCopyrightText: Copyright (c) 2021 Ha Thach (tinyusb.org) for Double Buffered * SPDX-License-Identifier: MIT * * This file is part of the TinyUSB stack. */ #include "tusb_option.h" #if CFG_TUSB_MCU == OPT_MCU_RP2040 && (CFG_TUD_ENABLED || CFG_TUH_ENABLED) #include #include "rp2040_usb.h" #include "device/dcd.h" #include "host/hcd.h" //--------------------------------------------------------------------+ // MACRO CONSTANT TYPEDEF PROTOTYPE //--------------------------------------------------------------------+ #if CFG_TUSB_RP2_ERRATA_E15 static bool e15_is_critical_frame_period(void); #endif #if CFG_TUSB_RP2_ERRATA_E2 static uint8_t rp2040_chipversion = 2; #endif critical_section_t rp2usb_lock; //--------------------------------------------------------------------+ // Implementation //--------------------------------------------------------------------+ // Provide own byte by byte memcpy as not all copies are aligned. // Use volatile to prevent compiler from widening to 16/32-bit accesses // which cause hard fault on RP2350 when dst/src point to USB DPRAM. static void unaligned_memcpy(uint8_t *dst, const uint8_t *src, size_t n) { volatile uint8_t *vdst = dst; const volatile uint8_t *vsrc = src; while (n--) { *vdst++ = *vsrc++; } } #if CFG_TUD_EDPT_DEDICATED_HWFIFO void tu_hwfifo_write(volatile void *hwfifo, const uint8_t *src, uint16_t len, const tu_hwfifo_access_t *access_mode) { (void)access_mode; unaligned_memcpy((uint8_t *)(uintptr_t)hwfifo, src, len); } void tu_hwfifo_read(const volatile void *hwfifo, uint8_t *dest, uint16_t len, const tu_hwfifo_access_t *access_mode) { (void)access_mode; unaligned_memcpy(dest, (const uint8_t *)(uintptr_t)hwfifo, len); } #endif void rp2usb_init(void) { // Reset usb controller reset_block(RESETS_RESET_USBCTRL_BITS); unreset_block_wait(RESETS_RESET_USBCTRL_BITS); #ifdef __GNUC__ // Clear any previous state just in case #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Warray-bounds" #if __GNUC__ > 6 #pragma GCC diagnostic ignored "-Wstringop-overflow" #endif #endif memset(usb_dpram, 0, sizeof(*usb_dpram)); #ifdef __GNUC__ #pragma GCC diagnostic pop #endif // Mux the controller to the onboard usb phy usb_hw->muxing = USB_USB_MUXING_TO_PHY_BITS | USB_USB_MUXING_SOFTCON_BITS; #if CFG_TUSB_RP2_ERRATA_E2 rp2040_chipversion = rp2040_chip_version(); #endif TU_LOG2_INT(sizeof(hw_endpoint_t)); critical_section_init(&rp2usb_lock); } void __tusb_irq_path_func(rp2usb_reset_transfer)(hw_endpoint_t *ep) { ep->state = EPSTATE_IDLE; ep->remaining_len = 0; ep->xferred_len = 0; ep->user_buf = 0; #if CFG_TUD_EDPT_DEDICATED_HWFIFO ep->is_xfer_fifo = false; #endif } void __tusb_irq_path_func(bufctrl_write32)(io_rw_32 *buf_reg, uint32_t value) { const uint32_t current = *buf_reg; const uint32_t avail_mask = USB_BUF_CTRL_AVAIL | (USB_BUF_CTRL_AVAIL << 16); if (current & value & avail_mask) { panic("buf_ctrl @ 0x%lX already available", (uintptr_t)buf_reg); } *buf_reg = value & ~(USB_BUF_CTRL_AVAIL | (USB_BUF_CTRL_AVAIL << 16)); // write other bits first // Section 4.1.2.7.1 (rp2040) / 12.7.3.7.1 (rp2350) Concurrent access: after write to buffer control, // wait for USB controller to see the update before setting AVAILABLE. // Don't need delay in host mode as host is in charge of when to start the transaction. if (value & (USB_BUF_CTRL_AVAIL | (USB_BUF_CTRL_AVAIL << 16))) { if (!rp2usb_is_host_mode()) { busy_wait_at_least_cycles(12); } *buf_reg = value; // then set AVAILABLE bit last } } void __tusb_irq_path_func(bufctrl_write16)(io_rw_16 *buf_reg16, uint16_t value) { const uint16_t current = *buf_reg16; if (current & value & USB_BUF_CTRL_AVAIL) { panic("buf_ctrl @ 0x%lX already available", (uintptr_t)buf_reg16); } *buf_reg16 = value & (uint16_t)~USB_BUF_CTRL_AVAIL; // write other bits first // Section 4.1.2.7.1 (rp2040) / 12.7.3.7.1 (rp2350) Concurrent access if (value & USB_BUF_CTRL_AVAIL) { if (!rp2usb_is_host_mode()) { busy_wait_at_least_cycles(12); } *buf_reg16 = value; // then set AVAILABLE bit last } } // prepare buffer, move data if tx, return buffer control uint16_t __tusb_irq_path_func(bufctrl_prepare16)(hw_endpoint_t *ep, uint8_t *dpram_buf, bool is_rx) { const uint16_t buflen = tu_min16(ep->remaining_len, ep->max_packet_size); ep->remaining_len -= buflen; uint16_t buf_ctrl = buflen | USB_BUF_CTRL_AVAIL; if (ep->next_pid) { buf_ctrl |= USB_BUF_CTRL_DATA1_PID; } ep->next_pid ^= 1u; if (!is_rx) { if (buflen) { // Copy data from user buffer/fifo to hw buffer #if CFG_TUD_EDPT_DEDICATED_HWFIFO if (ep->is_xfer_fifo) { // not in sram, may mess up timing with E15 workaround tu_hwfifo_write_from_fifo(dpram_buf, ep->user_fifo, buflen, NULL); } else #endif { unaligned_memcpy(dpram_buf, ep->user_buf, buflen); ep->user_buf += buflen; } } buf_ctrl |= USB_BUF_CTRL_FULL; } // Is this the last buffer? Will trigger the trans complete irq but also stop it polling. // This is used to detect setup packets being sent in host mode if (ep->remaining_len == 0) { buf_ctrl |= USB_BUF_CTRL_LAST; } return buf_ctrl; } // Start transaction on hw buffer void __tusb_irq_path_func(rp2usb_buffer_start)(hw_endpoint_t *ep, io_rw_32 *ep_reg, io_rw_32 *buf_reg, bool is_rx) { // always compute and start with buffer 0 uint32_t buf_ctrl = bufctrl_prepare16(ep, ep->dpram_buf, is_rx) | USB_BUF_CTRL_SEL; // Note: device EP0 does not have an endpoint control register if (ep_reg != NULL) { uint32_t ep_ctrl = *ep_reg; // Isochronous endpoints get a single DPRAM buffer (hw_endpoint_open only double-sizes BULK), so // they must never be double-buffered here even when a transfer spans multiple packets, or buffer // 1 (at dpram_buf+64) would spill into the next endpoint's DPRAM. (Never true for BULK, so the // double-buffered bulk path is unaffected.) const bool is_iso = (((ep_ctrl >> EP_CTRL_BUFFER_TYPE_LSB) & 0x3u) == TUSB_XFER_ISOCHRONOUS); #if CFG_TUH_ENABLED const bool force_single = is_iso || (rp2usb_is_host_mode() && ep->interrupt_num > 0); #else const bool force_single = is_iso; #endif if (ep->remaining_len && !force_single) { // Use buffer 1 (double buffered) if there is still data buf_ctrl |= (uint32_t)bufctrl_prepare16(ep, ep->dpram_buf + 64, is_rx) << 16; ep_ctrl |= EP_CTRL_DOUBLE_BUFFERED_BITS; } else { // Only buf0 used: clear DOUBLE_BUFFERED so controller doesn't toggle buffer selector ep_ctrl &= ~(uint32_t)EP_CTRL_DOUBLE_BUFFERED_BITS; } *ep_reg = ep_ctrl; } // Finally, write to buffer control which will trigger the transfer the next time the controller polls this endpoint bufctrl_write32(buf_reg, buf_ctrl); } void rp2usb_xfer_start(hw_endpoint_t *ep, io_rw_32 *ep_reg, io_rw_32 *buf_reg, uint8_t *buffer, tu_fifo_t *ff, uint16_t total_len) { (void)ff; hw_endpoint_lock_update(ep, 1); if (ep->state == EPSTATE_ACTIVE) { TU_LOG(1, "WARN: starting new transfer on already active ep %02X\r\n", ep->ep_addr); rp2usb_reset_transfer(ep); } // Fill in info now that we're kicking off the hw ep->remaining_len = total_len; ep->xferred_len = 0; ep->state = EPSTATE_ACTIVE; #if CFG_TUD_EDPT_DEDICATED_HWFIFO if (ff != NULL) { ep->user_fifo = ff; ep->is_xfer_fifo = true; } else #endif { ep->user_buf = buffer; #if CFG_TUD_EDPT_DEDICATED_HWFIFO ep->is_xfer_fifo = false; #endif } const bool is_host = rp2usb_is_host_mode(); const bool is_rx = (is_host == (tu_edpt_dir(ep->ep_addr) == TUSB_DIR_IN)); #if CFG_TUD_ENABLED if (!is_host && ep->future_len > 0) { // Device only: previous short-packet abort saved data from the other buffer const uint8_t future_len = ep->future_len; memcpy(ep->user_buf, ep->dpram_buf + (ep->future_bufid << 6), future_len); ep->xferred_len += future_len; ep->remaining_len -= future_len; ep->user_buf += future_len; ep->future_len = 0; ep->future_bufid = 0; if (ep->remaining_len == 0) { const uint16_t xferred_len = ep->xferred_len; rp2usb_reset_transfer(ep); dcd_event_xfer_complete(0, ep->ep_addr, xferred_len, XFER_RESULT_SUCCESS, false); hw_endpoint_lock_update(ep, -1); return; } } #if CFG_TUSB_RP2_ERRATA_E15 if (ep->e15_bulk_in) { usb_hw_set->inte = USB_INTS_DEV_SOF_BITS; // skip transfer if we are in critical frame period if (e15_is_critical_frame_period()) { ep->state = EPSTATE_PENDING; hw_endpoint_lock_update(ep, -1); return; } } #endif // CFG_TUSB_RP2_ERRATA_E15 #endif // CFG_TUD_ENABLED rp2usb_buffer_start(ep, ep_reg, buf_reg, is_rx); hw_endpoint_lock_update(ep, -1); } // sync endpoint buffer and return transferred bytes static uint16_t __tusb_irq_path_func(bufctrl_sync16)(hw_endpoint_t *ep, bool is_rx, uint16_t buf_ctrl, uint8_t *dpram_buf) { const uint16_t xferred_bytes = buf_ctrl & USB_BUF_CTRL_LEN_MASK; if (!is_rx) { // We are continuing a transfer here. If we are TX, we have successfully // sent some data can increase the length we have sent assert(!(buf_ctrl & USB_BUF_CTRL_FULL)); } else { // If we have received some data, so can increase the length // we have received AFTER we have copied it to the user buffer at the appropriate offset assert(buf_ctrl & USB_BUF_CTRL_FULL); #if CFG_TUD_EDPT_DEDICATED_HWFIFO if (ep->is_xfer_fifo) { // not in sram, may mess up timing with E15 workaround tu_hwfifo_read_to_fifo(dpram_buf, ep->user_fifo, xferred_bytes, NULL); } else #endif { unaligned_memcpy(ep->user_buf, dpram_buf, xferred_bytes); ep->user_buf += xferred_bytes; } } ep->xferred_len += xferred_bytes; // Short packet if (xferred_bytes < ep->max_packet_size) { // Reduce total length as this is last packet ep->remaining_len = 0; } return xferred_bytes; } // Returns true if transfer is complete. // buf_id: which buffer completed (from BUFF_CPU_SHOULD_HANDLE, only used for double-buffered). bool __tusb_irq_path_func(rp2usb_xfer_continue)(hw_endpoint_t *ep, io_rw_32 *ep_reg, io_rw_32 *buf_reg, uint8_t buf_id, bool is_rx) { hw_endpoint_lock_update(ep, 1); if (ep->state == EPSTATE_IDLE) { // probably land here due to short packet on rx with double buffered hw_endpoint_lock_update(ep, -1); return false; } const bool is_host = rp2usb_is_host_mode(); const bool is_double = (ep_reg != NULL && ((*ep_reg) & EP_CTRL_DOUBLE_BUFFERED_BITS)); // Double-buffered: buf_id from BUFF_CPU_SHOULD_HANDLE indicates which buffer completed. // RP2040-E4 (host only): in single-buffered multi-packet transfers, the controller may write completion status to // BUF1 half instead of BUF0. The side effect is that controller can execute an extra packet after writing to BUF1 // since it leaves BUF0 intact, which can be polled before buf_status interrupt is triggered. uint8_t *dpram_buf = ep->dpram_buf; if (buf_id) { #if CFG_TUSB_RP2_ERRATA_E4 if (!(is_host && !is_double)) // E4 bug: incorrect buf_id, buffer data is still buf0 #endif { dpram_buf += 64; // buf1 offset } } io_rw_16 *buf_reg16 = (io_rw_16 *)buf_reg; uint16_t buf_ctrl16 = *(buf_reg16 + buf_id); const uint16_t xact_bytes = bufctrl_sync16(ep, is_rx, buf_ctrl16, dpram_buf); const bool is_last = buf_ctrl16 & USB_BUF_CTRL_LAST; const bool is_short = xact_bytes < ep->max_packet_size; const bool is_done = is_short || is_last; // Short packet on rx with double buffer: abort the other half (if not last) and reset the buffer control. // The other buffer may be: (a) still AVAIL, (b) in-progress (controller receiving), or (c) already completed. // We must abort to safely reclaim it. If it has valid data (FULL), save as future for the next transfer. // Note: Host mode current does not save next transfer data due to shared epx --> potential issue. However, RP2040-E4 // causes more or less of the same issue since it write to buf1 and next time it continues to transfer on buf0 (stale) if (is_short && is_double && is_rx && !is_last) { const uint32_t abort_bit = TU_BIT(tu_edpt_number(ep->ep_addr) << 1); // abort is device only -> IN endpoint if (is_host) { // host stop current transfer, not safe, can be racing const uint32_t sie_ctrl = (usb_hw->sie_ctrl & SIE_CTRL_BASE_MASK) | USB_SIE_CTRL_STOP_TRANS_BITS; usb_hw->sie_ctrl = sie_ctrl; while (usb_hw->sie_ctrl & USB_SIE_CTRL_STOP_TRANS_BITS) {} } else { // device abort current transfer #if CFG_TUSB_RP2_ERRATA_E2 if (rp2040_chipversion >= 2) #endif { usb_hw_set->abort = abort_bit; while ((usb_hw->abort_done & abort_bit) != abort_bit) {} } } // After abort, check if the other buffer received valid data io_rw_16 *buf_reg16_other = buf_reg16 + (buf_id ^ 1); const uint16_t buf_ctrl16_other = *buf_reg16_other; if (buf_ctrl16_other & USB_BUF_CTRL_FULL) { // Data already sent into this buffer. Save it for the next transfer. // buff_status will be clear by the next run #if CFG_TUD_ENABLED if (!is_host) { ep->future_len = (uint8_t)(buf_ctrl16_other & USB_BUF_CTRL_LEN_MASK); ep->future_bufid = buf_id ^ 1; } #endif } else { ep->next_pid ^= 1u; // roll back pid if aborted } *buf_reg = 0; // reset buffer control if (!is_host) { #if CFG_TUSB_RP2_ERRATA_E2 if (rp2040_chipversion >= 2) #endif { usb_hw_clear->abort_done = abort_bit; usb_hw_clear->abort = abort_bit; } } hw_endpoint_lock_update(ep, -1); return true; } if (!is_done && ep->remaining_len > 0) { #if CFG_TUSB_RP2_ERRATA_E15 const bool need_e15 = ep->e15_bulk_in; if (need_e15 && e15_is_critical_frame_period()) { // mark as pending if matches E15 condition ep->state = EPSTATE_PENDING; } else if (need_e15 && ep->state == EPSTATE_PENDING) { // if already pending, meaning the other buf completes first, don't arm buffer, let SOF handle it // do nothing } else #endif { // ping-pong: arm the completed buffer with new data const uint16_t buf_ctrl16_new = bufctrl_prepare16(ep, dpram_buf, is_rx); bufctrl_write16(buf_reg16 + buf_id, buf_ctrl16_new); } } hw_endpoint_lock_update(ep, -1); return is_done; } //--------------------------------------------------------------------+ // Errata 15 //--------------------------------------------------------------------+ #if CFG_TUSB_RP2_ERRATA_E15 // E15 is fixed with RP2350 /* Don't mark IN buffers as available during the last 200us of a full-speed frame. This avoids a situation seen with the USB2.0 hub on a Raspberry Pi 4 where a late IN token before the next full-speed SOF can cause port babble and a corrupt ACK packet. The nature of the data corruption has a chance to cause device lockup. Use the next SOF to mark delayed buffers as available. This reduces available Bulk IN bandwidth by approximately 20%, and requires that the SOF interrupt is enabled while these transfers are ongoing. Inherit the top-level enable from the corresponding Pico-SDK flag. Applications that will not use the device in a situation where it could be plugged into a Pi 4 or Pi 400 (for example, when directly connected to a commodity hub or other host) can turn off the flag in the SDK. */ volatile uint32_t e15_last_sof = 0; // check if it is currently in critical frame period i.e 20% of last usb frame static bool __tusb_irq_path_func(e15_is_critical_frame_period)(void) { /* Avoid the last 200us (uframe 6.5-7) of a frame, up to the EOF2 point. * The device state machine cannot recover from receiving an incorrect PID * when it is expecting an ACK. */ uint32_t delta = time_us_32() - e15_last_sof; if (delta < 800 || delta > 998) { return false; } // TU_LOG(3, "Avoiding sof %lu now %lu last %lu\r\n", (usb_hw->sof_rd + 1) & USB_SOF_RD_BITS, time_us_32(), // e15_last_sof); return true; } #endif #endif