summaryrefslogtreecommitdiff
path: root/src/portable
diff options
context:
space:
mode:
authorHiFiPHile <[email protected]>2026-08-25 09:27:48 +0200
committerHiFiPHile <[email protected]>2026-08-25 09:27:48 +0200
commitdfac26a272fa7bbbca2050fbe9f1ca09008e548e (patch)
treeefbc53f8f2c1e5d9c7f38e5fef6d774a20053cec /src/portable
parente590b45fcf51f9ddace73178074e4fe6d691e319 (diff)
parent5c0e31cdabaf37f14e1f5e988a020abfc1000495 (diff)
Merge master updates into the UAC1 host branch
Bring the audio work onto the current host core and build files before applying the remaining review fixes. Signed-off-by: HiFiPHile <[email protected]>
Diffstat (limited to 'src/portable')
-rw-r--r--src/portable/chipidea/ci_fs/ci_fs_type.h54
-rw-r--r--src/portable/chipidea/ci_fs/dcd_ci_fs.c49
-rw-r--r--src/portable/chipidea/ci_fs/hcd_ci_fs.c (renamed from src/portable/nxp/khci/hcd_khci.c)265
-rw-r--r--src/portable/chipidea/ci_hs/ci_hs_imxrt.h3
-rw-r--r--src/portable/chipidea/ci_hs/ci_hs_lpc18_43.h5
-rw-r--r--src/portable/chipidea/ci_hs/ci_hs_type.h17
-rw-r--r--src/portable/chipidea/ci_hs/dcd_ci_hs.c251
-rw-r--r--src/portable/chipidea/ci_hs/hcd_ci_hs.c4
-rw-r--r--src/portable/mentor/musb/dcd_musb.c73
-rw-r--r--src/portable/mentor/musb/musb_max32.h1
-rw-r--r--src/portable/mentor/musb/musb_py32.h79
-rw-r--r--src/portable/mentor/musb/musb_ti.h1
-rw-r--r--src/portable/mentor/musb/musb_type.h71
-rw-r--r--src/portable/microchip/samd/dcd_samd.c44
-rw-r--r--src/portable/nordic/nrf5x/dcd_nrf5x.c98
-rw-r--r--src/portable/nxp/khci/dcd_khci.c560
-rw-r--r--src/portable/nxp/lpc17_40/dcd_lpc17_40.c281
-rw-r--r--src/portable/nxp/lpc_ip3511/dcd_lpc_ip3511.c189
-rw-r--r--src/portable/raspberrypi/rp2040/dcd_rp2040.c46
-rw-r--r--src/portable/raspberrypi/rp2040/hcd_rp2040.c4
-rw-r--r--src/portable/raspberrypi/rp2040/rp2040_usb.c13
-rw-r--r--src/portable/raspberrypi/rp2040/rp2040_usb.h1
-rw-r--r--src/portable/renesas/rusb2/dcd_rusb2.c253
-rw-r--r--src/portable/renesas/rusb2/hcd_rusb2.c8
-rw-r--r--src/portable/renesas/rusb2/rusb2_ra.h42
-rw-r--r--src/portable/st/stm32_fsdev/dcd_stm32_fsdev.c12
-rw-r--r--src/portable/st/stm32_fsdev/fsdev_apm32.h77
-rw-r--r--src/portable/st/stm32_fsdev/fsdev_at32.h2
-rw-r--r--src/portable/st/stm32_fsdev/fsdev_common.c5
-rw-r--r--src/portable/st/stm32_fsdev/fsdev_common.h2
-rw-r--r--src/portable/synopsys/dwc2/dcd_dwc2.c41
-rw-r--r--src/portable/wch/ch32_usbfs_reg.h8
-rw-r--r--src/portable/wch/dcd_ch32_usbfs.c109
-rw-r--r--src/portable/wch/dcd_ch32_usbhs.c12
34 files changed, 1626 insertions, 1054 deletions
diff --git a/src/portable/chipidea/ci_fs/ci_fs_type.h b/src/portable/chipidea/ci_fs/ci_fs_type.h
index a525c96ca..857a75253 100644
--- a/src/portable/chipidea/ci_fs/ci_fs_type.h
+++ b/src/portable/chipidea/ci_fs/ci_fs_type.h
@@ -27,6 +27,60 @@ extern "C" {
// align 4 is used to get rid of reserved fields
#define _va32 volatile TU_ATTR_ALIGNED(4)
+//--------------------------------------------------------------------+
+// Buffer Descriptor Table (BDT) - shared by the device (dcd) and host (hcd) drivers
+// since both target the same ChipIdea-FS silicon. Keep the layout in one place so a
+// fix cannot silently drift between the two drivers.
+//--------------------------------------------------------------------+
+
+// Token PID values reported in the BDT tok_pid field / written to the TOKEN register.
+// The device driver only uses OUT/IN/SETUP; the rest are host-only.
+enum {
+ TOK_PID_OUT = 0x1u,
+ TOK_PID_IN = 0x9u,
+ TOK_PID_SETUP = 0xDu,
+ TOK_PID_DATA0 = 0x3u,
+ TOK_PID_DATA1 = 0xbu,
+ TOK_PID_ACK = 0x2u,
+ TOK_PID_STALL = 0xeu,
+ TOK_PID_NAK = 0xau,
+ TOK_PID_BUSTO = 0x0u,
+ TOK_PID_ERR = 0xfu,
+};
+
+// Note: this header is included before the CMSIS device header, so use plain `volatile`
+// rather than CMSIS `__IO`.
+typedef struct TU_ATTR_PACKED
+{
+ union {
+ uint32_t head;
+ struct {
+ union {
+ struct {
+ uint16_t : 2;
+ volatile uint16_t tok_pid : 4;
+ uint16_t data : 1;
+ volatile uint16_t own : 1;
+ uint16_t : 8;
+ };
+ struct {
+ uint16_t : 2;
+ uint16_t bdt_stall : 1;
+ uint16_t dts : 1;
+ uint16_t ninc : 1;
+ uint16_t keep : 1;
+ uint16_t : 10;
+ };
+ };
+ volatile uint16_t bc : 10;
+ uint16_t : 6;
+ };
+ };
+ uint8_t *addr;
+}buffer_descriptor_t;
+
+TU_VERIFY_STATIC( sizeof(buffer_descriptor_t) == 8, "size is not correct" );
+
typedef struct {
_va32 uint8_t PER_ID; // [00] Peripheral ID register
_va32 uint8_t ID_COMP; // [04] Peripheral ID complement register
diff --git a/src/portable/chipidea/ci_fs/dcd_ci_fs.c b/src/portable/chipidea/ci_fs/dcd_ci_fs.c
index 0f3675349..295f2e578 100644
--- a/src/portable/chipidea/ci_fs/dcd_ci_fs.c
+++ b/src/portable/chipidea/ci_fs/dcd_ci_fs.c
@@ -24,43 +24,7 @@
//--------------------------------------------------------------------+
// MACRO TYPEDEF CONSTANT ENUM DECLARATION
//--------------------------------------------------------------------+
-
-enum {
- TOK_PID_OUT = 0x1u,
- TOK_PID_IN = 0x9u,
- TOK_PID_SETUP = 0xDu,
-};
-
-typedef struct TU_ATTR_PACKED
-{
- union {
- uint32_t head;
- struct {
- union {
- struct {
- uint16_t : 2;
- __IO uint16_t tok_pid : 4;
- uint16_t data : 1;
- __IO uint16_t own : 1;
- uint16_t : 8;
- };
- struct {
- uint16_t : 2;
- uint16_t bdt_stall : 1;
- uint16_t dts : 1;
- uint16_t ninc : 1;
- uint16_t keep : 1;
- uint16_t : 10;
- };
- };
- __IO uint16_t bc : 10;
- uint16_t : 6;
- };
- };
- uint8_t *addr;
-}buffer_descriptor_t;
-
-TU_VERIFY_STATIC( sizeof(buffer_descriptor_t) == 8, "size is not correct" );
+// TOK_PID_* and buffer_descriptor_t are shared with the host driver in ci_fs_type.h
typedef struct TU_ATTR_PACKED
{
@@ -175,6 +139,17 @@ static void process_tokdne(uint8_t rhport)
return;
}
const unsigned length = ep->length;
+
+ /* Transfer is complete. For OUT, a multi-packet transfer speculatively arms the
+ * sibling (even/odd) BDT to avoid NAK. When the transfer ends early - e.g. the host
+ * sends a short packet before filling both buffers - that sibling is left armed
+ * (own=1). A leftover armed BDT desyncs the even/odd ping-pong so the next OUT
+ * packet lands in the wrong buffer half (buffer + max_packet_size instead of
+ * buffer), making the stack read stale data. Disarm it here. */
+ if (dir == TUSB_DIR_OUT) {
+ _dcd.bdt[epnum][dir][odd ^ 1].own = 0;
+ }
+
dcd_event_xfer_complete(rhport,
tu_edpt_addr(epnum, dir),
length - remaining, XFER_RESULT_SUCCESS, true);
diff --git a/src/portable/nxp/khci/hcd_khci.c b/src/portable/chipidea/ci_fs/hcd_ci_fs.c
index 209940656..a6f5405b5 100644
--- a/src/portable/nxp/khci/hcd_khci.c
+++ b/src/portable/chipidea/ci_fs/hcd_ci_fs.c
@@ -10,63 +10,28 @@
#if CFG_TUH_ENABLED && defined(TUP_USBIP_CHIPIDEA_FS)
-#ifdef TUP_USBIP_CHIPIDEA_FS_KINETIS
+#include "host/hcd.h"
+#include "host/usbh.h"
+#include "ci_fs_type.h"
+
+// Host is currently only available on NXP Kinetis. The ChipIdea-FS host controller
+// interface is register-compatible via ci_fs_regs_t. Unlike the device driver, the host
+// driver does not include the ci_fs_<family>.h header because those define the device
+// dcd_int_enable()/dcd_int_disable() functions, which would collide in a dual-role build.
+#if defined(TUP_USBIP_CHIPIDEA_FS_KINETIS)
#include "fsl_device_registers.h"
- #define KHCI USB0
+ #define CI_FS_REG(_port) ((ci_fs_regs_t*) USB0_BASE)
+ #define CI_FS_IRQN USB0_IRQn
#else
#error "MCU is not supported"
#endif
-#include "host/hcd.h"
-#include "host/usbh.h"
+#define CI_REG CI_FS_REG(0)
//--------------------------------------------------------------------+
// MACRO TYPEDEF CONSTANT ENUM DECLARATION
//--------------------------------------------------------------------+
-
-enum {
- TOK_PID_OUT = 0x1u,
- TOK_PID_IN = 0x9u,
- TOK_PID_SETUP = 0xDu,
- TOK_PID_DATA0 = 0x3u,
- TOK_PID_DATA1 = 0xbu,
- TOK_PID_ACK = 0x2u,
- TOK_PID_STALL = 0xeu,
- TOK_PID_NAK = 0xau,
- TOK_PID_BUSTO = 0x0u,
- TOK_PID_ERR = 0xfu,
-};
-
-typedef struct TU_ATTR_PACKED
-{
- union {
- uint32_t head;
- struct {
- union {
- struct {
- uint16_t : 2;
- __IO uint16_t tok_pid : 4;
- uint16_t data : 1;
- __IO uint16_t own : 1;
- uint16_t : 8;
- };
- struct {
- uint16_t : 2;
- uint16_t bdt_stall : 1;
- uint16_t dts : 1;
- uint16_t ninc : 1;
- uint16_t keep : 1;
- uint16_t : 10;
- };
- };
- __IO uint16_t bc : 10;
- uint16_t : 6;
- };
- };
- uint8_t *addr;
-}buffer_descriptor_t;
-
-TU_VERIFY_STATIC( sizeof(buffer_descriptor_t) == 8, "size is not correct" );
+// TOK_PID_* and buffer_descriptor_t are shared with the device driver in ci_fs_type.h
typedef struct TU_ATTR_PACKED
{
@@ -107,7 +72,10 @@ typedef struct
union {
/* [OUT,IN][EVEN,ODD] */
buffer_descriptor_t bdt[2][2];
- uint16_t bda[2*2];
+ /* bda aliases bdt for STAT-register indexing: STAT gives the byte-offset/2 of the
+ * completed BD, so it indexes bda[] in uint16_t units. Each buffer_descriptor_t is
+ * 4 uint16_t, hence 2*2*4 elements to span the whole table (must equal sizeof bdt). */
+ uint16_t bda[2*2*4];
};
endpoint_state_t endpoint[2];
pipe_state_t pipe[CFG_TUH_ENDPOINT_MAX * 2];
@@ -142,7 +110,9 @@ static int prepare_packets(int pipenum)
endpoint_state_t *ep = &_hcd.endpoint[dir_tx];
unsigned const odd = ep->odd;
buffer_descriptor_t *bd = _hcd.bdt[dir_tx];
- TU_ASSERT(0 == bd[odd].own, -1);
+ // The host shares a single BDT set across all pipes. If it is still owned by an
+ // in-flight transfer on another pipe, report busy so the caller can defer & retry.
+ if (bd[odd].own) return -1;
// TU_LOG1(" %p dir %d odd %d data %d\r\n", &bd[odd], dir_tx, odd, pipe->data);
@@ -200,11 +170,11 @@ static bool continue_transfer(int pipenum, buffer_descriptor_t *bd)
bd->bc = next_rem > pipe->max_packet_size ? pipe->max_packet_size: next_rem;
__DSB();
bd->own = 1; /* This bit must be set last */
- while (KHCI->CTL & USB_CTL_TXSUSPENDTOKENBUSY_MASK) ;
- KHCI->TOKEN = KHCI->TOKEN; /* Queue the same token as the last */
+ while (CI_REG->CTL & USB_CTL_TXSUSPENDTOKENBUSY_MASK) ;
+ CI_REG->TOKEN = CI_REG->TOKEN; /* Queue the same token as the last */
} else if (TUSB_DIR_IN == tu_edpt_dir(pipe->ep_addr)) { /* IN */
- while (KHCI->CTL & USB_CTL_TXSUSPENDTOKENBUSY_MASK) ;
- KHCI->TOKEN = KHCI->TOKEN;
+ while (CI_REG->CTL & USB_CTL_TXSUSPENDTOKENBUSY_MASK) ;
+ CI_REG->TOKEN = CI_REG->TOKEN;
}
return true;
}
@@ -215,13 +185,21 @@ static bool continue_transfer(int pipenum, buffer_descriptor_t *bd)
static bool resume_transfer(int pipenum)
{
int num_tokens = prepare_packets(pipenum);
- TU_ASSERT(0 <= num_tokens);
+ if (num_tokens < 0) {
+ // Shared BDT still owned by an in-flight transfer on another pipe. Defer this
+ // pipe and retry on the next SOF once the BDT is free (avoids dropping the
+ // transfer, which stalls e.g. a 2nd device enumerating behind a hub while the
+ // app issues concurrent control transfers).
+ _hcd.pending |= TU_BIT(pipenum);
+ CI_REG->INT_EN |= USB_ISTAT_SOFTOK_MASK;
+ return true;
+ }
- const unsigned ie = NVIC_GetEnableIRQ(USB0_IRQn);
- NVIC_DisableIRQ(USB0_IRQn);
+ const unsigned ie = NVIC_GetEnableIRQ(CI_FS_IRQN);
+ NVIC_DisableIRQ(CI_FS_IRQN);
pipe_state_t *pipe = &_hcd.pipe[pipenum];
- unsigned flags = KHCI->ENDPOINT[0].ENDPT & USB_ENDPT_HOSTWOHUB_MASK;
+ unsigned flags = CI_REG->EP[0].CTL & USB_ENDPT_HOSTWOHUB_MASK;
flags |= USB_ENDPT_EPRXEN_MASK | USB_ENDPT_EPTXEN_MASK;
switch (pipe->xfer) {
case TUSB_XFER_CONTROL:
@@ -236,16 +214,16 @@ static bool resume_transfer(int pipenum)
}
// TU_LOG1(" resume pipenum %d flags %x\r\n", pipenum, flags);
- KHCI->ENDPOINT[0].ENDPT = flags;
- KHCI->ADDR = (KHCI->ADDR & USB_ADDR_LSEN_MASK) | pipe->dev_addr;
+ CI_REG->EP[0].CTL = flags;
+ CI_REG->ADDR = (CI_REG->ADDR & USB_ADDR_LSEN_MASK) | pipe->dev_addr;
unsigned const token = tu_edpt_number(pipe->ep_addr) |
((tu_edpt_dir(pipe->ep_addr) ? TOK_PID_IN: TOK_PID_OUT) << USB_TOKEN_TOKENPID_SHIFT);
do {
- while (KHCI->CTL & USB_CTL_TXSUSPENDTOKENBUSY_MASK) ;
- KHCI->TOKEN = token;
+ while (CI_REG->CTL & USB_CTL_TXSUSPENDTOKENBUSY_MASK) ;
+ CI_REG->TOKEN = token;
} while (--num_tokens);
- if (ie) NVIC_EnableIRQ(USB0_IRQn);
+ if (ie) NVIC_EnableIRQ(CI_FS_IRQN);
return true;
}
@@ -253,19 +231,38 @@ static void suspend_transfer(int pipenum, buffer_descriptor_t *bd)
{
pipe_state_t *pipe = &_hcd.pipe[pipenum];
pipe->buffer = bd->addr;
- pipe->data = bd->data ^ 1;
+ // A NAK transfers no data, so the data toggle must be preserved for the retry.
+ // (Do NOT flip pipe->data here: flipping it makes the retried packet use the wrong
+ // DATA0/DATA1, which the device silently discards - breaking any bulk/interrupt
+ // transfer that is NAKed, e.g. the MSC CBW/CSW when the device is momentarily busy.)
if ((TUSB_XFER_INTERRUPT == pipe->xfer) ||
(TUSB_XFER_BULK == pipe->xfer)) {
_hcd.pending |= TU_BIT(pipenum);
- KHCI->INTEN |= USB_ISTAT_SOFTOK_MASK;
+ CI_REG->INT_EN |= USB_ISTAT_SOFTOK_MASK;
+ }
+}
+
+// Release the speculatively-armed sibling BDT of a multi-packet transfer.
+// prepare_packets arms the sibling (odd^1) BDT (own=1) so a multi-packet transfer can
+// ping-pong without NAKs. When the transfer ends - completes early on a short IN packet,
+// stalls/errors, or (for IN) is NAKed before the sibling's token is issued - that sibling
+// is left owned by the SIE. Because the host shares ONE BDT set across all pipes, a
+// leftover armed sibling blocks every other pipe forever (e.g. a 2nd device stuck
+// enumerating behind a hub). Release it - but ONLY for a multi-packet transfer: a
+// single-packet transfer never armed a sibling, so that BDT slot may legitimately belong
+// to another pipe's in-flight transfer.
+static inline void release_sibling_bd(unsigned s, const pipe_state_t *pipe)
+{
+ if (pipe->length > pipe->max_packet_size) {
+ ((buffer_descriptor_t *)&_hcd.bda[s ^ USB_STAT_ODD_MASK])->own = 0;
}
}
static void process_tokdne(uint8_t rhport)
{
(void)rhport;
- const unsigned s = KHCI->STAT;
- KHCI->ISTAT = USB_ISTAT_TOKDNE_MASK; /* fetch the next token if received */
+ const unsigned s = CI_REG->STAT;
+ CI_REG->INT_STAT = USB_ISTAT_TOKDNE_MASK; /* fetch the next token if received */
uint8_t const dir_in = (s & USB_STAT_TX_MASK) ? TUSB_DIR_OUT: TUSB_DIR_IN;
unsigned const odd = (s & USB_STAT_ODD_MASK) ? 1 : 0;
@@ -295,6 +292,12 @@ static void process_tokdne(uint8_t rhport)
result = XFER_RESULT_SUCCESS;
break;
case TOK_PID_NAK:
+ // Release the speculatively-armed sibling so the deferred retry (and any other pipe
+ // sharing the single BDT) can claim it; otherwise it stays own=1 forever and every
+ // same-direction transfer wedges. IN only: an IN issues just one token so the sibling
+ // was never put on the wire, whereas an OUT issues both tokens and its sibling may
+ // still be in flight - touching it there would race the SIE write-back.
+ if (TUSB_DIR_IN == dir_in) release_sibling_bd(s, &_hcd.pipe[pipenum]);
suspend_transfer(pipenum, bd);
next_pipenum = select_next_pipenum(pipenum);
if (0 <= next_pipenum)
@@ -310,8 +313,9 @@ static void process_tokdne(uint8_t rhport)
}
_hcd.in_progress &= ~TU_BIT(pipenum);
pipe_state_t *pipe = &_hcd.pipe[ep->pipenum];
+ release_sibling_bd(s, pipe);
hcd_event_xfer_complete(pipe->dev_addr,
- tu_edpt_addr(KHCI->TOKEN & USB_TOKEN_TOKENENDPT_MASK, dir_in),
+ tu_edpt_addr(CI_REG->TOKEN & USB_TOKEN_TOKENENDPT_MASK, dir_in),
pipe->length - pipe->remaining,
result, true);
next_pipenum = select_next_pipenum(pipenum);
@@ -321,29 +325,32 @@ static void process_tokdne(uint8_t rhport)
static void process_attach(uint8_t rhport)
{
- unsigned ctl = KHCI->CTL;
+ unsigned ctl = CI_REG->CTL;
if (!(ctl & USB_CTL_JSTATE_MASK)) {
/* The attached device is a low speed device. */
- KHCI->ADDR = USB_ADDR_LSEN_MASK;
- KHCI->ENDPOINT[0].ENDPT = USB_ENDPT_HOSTWOHUB_MASK;
+ CI_REG->ADDR = USB_ADDR_LSEN_MASK;
+ CI_REG->EP[0].CTL = USB_ENDPT_HOSTWOHUB_MASK;
}
hcd_event_device_attach(rhport, true);
}
static void process_bus_reset(uint8_t rhport)
{
- KHCI->ISTAT = USB_ISTAT_TOKDNE_MASK;
- KHCI->USBCTRL &= ~USB_USBCTRL_SUSP_MASK;
- KHCI->CTL &= ~USB_CTL_USBENSOFEN_MASK;
- KHCI->ADDR = 0;
- KHCI->ENDPOINT[0].ENDPT = 0;
+ CI_REG->INT_STAT = USB_ISTAT_TOKDNE_MASK;
+ CI_REG->USBCTRL &= ~USB_USBCTRL_SUSP_MASK;
+ CI_REG->CTL &= ~USB_CTL_USBENSOFEN_MASK;
+ CI_REG->ADDR = 0;
+ CI_REG->EP[0].CTL = 0;
hcd_event_device_remove(rhport, true);
_hcd.in_progress = 0;
_hcd.pending = 0;
+ // Clear the ENTIRE shared BDT (both directions, both even/odd). Clearing only the IN
+ // pair left a stale OUT/SETUP descriptor (own=1) after a disconnect mid-OUT, which then
+ // blocks the first control transfer on re-enumeration.
buffer_descriptor_t *bd = &_hcd.bdt[0][0];
- for (unsigned i = 0; i < 2; ++i, ++bd) {
+ for (unsigned i = 0; i < 2 * 2; ++i, ++bd) {
bd->head = 0;
}
}
@@ -354,31 +361,31 @@ static void process_bus_reset(uint8_t rhport)
bool hcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) {
(void) rhport;
(void) rh_init;
- KHCI->USBTRC0 |= USB_USBTRC0_USBRESET_MASK;
- while (KHCI->USBTRC0 & USB_USBTRC0_USBRESET_MASK);
+ CI_REG->USBTRC0 |= USB_USBTRC0_USBRESET_MASK;
+ while (CI_REG->USBTRC0 & USB_USBTRC0_USBRESET_MASK);
tu_memclr(&_hcd, sizeof(_hcd));
- KHCI->USBTRC0 |= TU_BIT(6); /* software must set this bit to 1 */
- KHCI->BDTPAGE1 = (uint8_t)((uintptr_t)_hcd.bdt >> 8);
- KHCI->BDTPAGE2 = (uint8_t)((uintptr_t)_hcd.bdt >> 16);
- KHCI->BDTPAGE3 = (uint8_t)((uintptr_t)_hcd.bdt >> 24);
+ CI_REG->USBTRC0 |= TU_BIT(6); /* software must set this bit to 1 */
+ CI_REG->BDT_PAGE1 = (uint8_t)((uintptr_t)_hcd.bdt >> 8);
+ CI_REG->BDT_PAGE2 = (uint8_t)((uintptr_t)_hcd.bdt >> 16);
+ CI_REG->BDT_PAGE3 = (uint8_t)((uintptr_t)_hcd.bdt >> 24);
- KHCI->USBCTRL &= ~USB_USBCTRL_SUSP_MASK;
- KHCI->CTL |= USB_CTL_ODDRST_MASK;
+ CI_REG->USBCTRL &= ~USB_USBCTRL_SUSP_MASK;
+ CI_REG->CTL |= USB_CTL_ODDRST_MASK;
for (unsigned i = 0; i < 16; ++i) {
- KHCI->ENDPOINT[i].ENDPT = 0;
+ CI_REG->EP[i].CTL = 0;
}
- KHCI->CTL &= ~USB_CTL_ODDRST_MASK;
+ CI_REG->CTL &= ~USB_CTL_ODDRST_MASK;
- KHCI->SOFTHLD = 74; /* for 64-byte packets */
- // KHCI->SOFTHLD = 144; /* for low speed 8-byte packets */
- KHCI->CTL = USB_CTL_HOSTMODEEN_MASK | USB_CTL_SE0_MASK;
- KHCI->USBCTRL = USB_USBCTRL_PDE_MASK;
+ CI_REG->SOF_THLD = 74; /* for 64-byte packets */
+ // CI_REG->SOF_THLD = 144; /* for low speed 8-byte packets */
+ CI_REG->CTL = USB_CTL_HOSTMODEEN_MASK | USB_CTL_SE0_MASK;
+ CI_REG->USBCTRL = USB_USBCTRL_PDE_MASK;
- NVIC_ClearPendingIRQ(USB0_IRQn);
- KHCI->INTEN = USB_INTEN_ATTACHEN_MASK | USB_INTEN_TOKDNEEN_MASK |
+ NVIC_ClearPendingIRQ(CI_FS_IRQN);
+ CI_REG->INT_EN = USB_INTEN_ATTACHEN_MASK | USB_INTEN_TOKDNEEN_MASK |
USB_INTEN_USBRSTEN_MASK | USB_INTEN_ERROREN_MASK | USB_INTEN_STALLEN_MASK;
- KHCI->ERREN = 0xff;
+ CI_REG->ERR_ENB = 0xff;
return true;
}
@@ -386,13 +393,13 @@ bool hcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) {
void hcd_int_enable(uint8_t rhport)
{
(void)rhport;
- NVIC_EnableIRQ(USB0_IRQn);
+ NVIC_EnableIRQ(CI_FS_IRQN);
}
void hcd_int_disable(uint8_t rhport)
{
(void)rhport;
- NVIC_DisableIRQ(USB0_IRQn);
+ NVIC_DisableIRQ(CI_FS_IRQN);
}
uint32_t hcd_frame_number(uint8_t rhport)
@@ -401,8 +408,8 @@ uint32_t hcd_frame_number(uint8_t rhport)
/* The device must be reset at least once after connection
* in order to start the frame counter. */
if (_hcd.need_reset) hcd_port_reset(rhport);
- uint32_t frmnum = KHCI->FRMNUML;
- frmnum |= KHCI->FRMNUMH << 8u;
+ uint32_t frmnum = CI_REG->FRM_NUML;
+ frmnum |= CI_REG->FRM_NUMH << 8u;
return frmnum;
}
@@ -412,7 +419,7 @@ uint32_t hcd_frame_number(uint8_t rhport)
bool hcd_port_connect_status(uint8_t rhport)
{
(void)rhport;
- if (KHCI->ISTAT & USB_ISTAT_ATTACH_MASK)
+ if (CI_REG->INT_STAT & USB_ISTAT_ATTACH_MASK)
return true;
return false;
}
@@ -420,12 +427,12 @@ bool hcd_port_connect_status(uint8_t rhport)
void hcd_port_reset(uint8_t rhport)
{
(void)rhport;
- KHCI->CTL &= ~USB_CTL_USBENSOFEN_MASK;
- KHCI->CTL |= USB_CTL_RESET_MASK;
+ CI_REG->CTL &= ~USB_CTL_USBENSOFEN_MASK;
+ CI_REG->CTL |= USB_CTL_RESET_MASK;
unsigned cnt = SystemCoreClock / 100;
while (cnt--) __NOP();
- KHCI->CTL &= ~USB_CTL_RESET_MASK;
- KHCI->CTL |= USB_CTL_USBENSOFEN_MASK;
+ CI_REG->CTL &= ~USB_CTL_RESET_MASK;
+ CI_REG->CTL |= USB_CTL_USBENSOFEN_MASK;
_hcd.need_reset = false;
}
@@ -437,26 +444,26 @@ tusb_speed_t hcd_port_speed_get(uint8_t rhport)
{
(void)rhport;
tusb_speed_t speed = TUSB_SPEED_FULL;
- const unsigned ie = NVIC_GetEnableIRQ(USB0_IRQn);
- NVIC_DisableIRQ(USB0_IRQn);
- if (KHCI->ADDR & USB_ADDR_LSEN_MASK)
+ const unsigned ie = NVIC_GetEnableIRQ(CI_FS_IRQN);
+ NVIC_DisableIRQ(CI_FS_IRQN);
+ if (CI_REG->ADDR & USB_ADDR_LSEN_MASK)
speed = TUSB_SPEED_LOW;
- if (ie) NVIC_EnableIRQ(USB0_IRQn);
+ if (ie) NVIC_EnableIRQ(CI_FS_IRQN);
return speed;
}
void hcd_device_close(uint8_t rhport, uint8_t dev_addr)
{
(void)rhport;
- const unsigned ie = NVIC_GetEnableIRQ(USB0_IRQn);
- NVIC_DisableIRQ(USB0_IRQn);
+ const unsigned ie = NVIC_GetEnableIRQ(CI_FS_IRQN);
+ NVIC_DisableIRQ(CI_FS_IRQN);
pipe_state_t *p = &_hcd.pipe[0];
pipe_state_t *end = &_hcd.pipe[CFG_TUH_ENDPOINT_MAX * 2];
for (;p != end; ++p) {
if (p->dev_addr == dev_addr)
tu_memclr(p, sizeof(*p));
}
- if (ie) NVIC_EnableIRQ(USB0_IRQn);
+ if (ie) NVIC_EnableIRQ(CI_FS_IRQN);
}
//--------------------------------------------------------------------+
@@ -483,12 +490,12 @@ bool hcd_setup_send(uint8_t rhport, uint8_t dev_addr, uint8_t const setup_packet
_hcd.in_progress |= TU_BIT(pipenum);
- unsigned hostwohub = KHCI->ENDPOINT[0].ENDPT & USB_ENDPT_HOSTWOHUB_MASK;
- KHCI->ENDPOINT[0].ENDPT = hostwohub |
+ unsigned hostwohub = CI_REG->EP[0].CTL & USB_ENDPT_HOSTWOHUB_MASK;
+ CI_REG->EP[0].CTL = hostwohub |
USB_ENDPT_EPHSHK_MASK | USB_ENDPT_EPRXEN_MASK | USB_ENDPT_EPTXEN_MASK;
- KHCI->ADDR = (KHCI->ADDR & USB_ADDR_LSEN_MASK) | dev_addr;
- while (KHCI->CTL & USB_CTL_TXSUSPENDTOKENBUSY_MASK) ;
- KHCI->TOKEN = (TOK_PID_SETUP << USB_TOKEN_TOKENPID_SHIFT);
+ CI_REG->ADDR = (CI_REG->ADDR & USB_ADDR_LSEN_MASK) | dev_addr;
+ while (CI_REG->CTL & USB_CTL_TXSUSPENDTOKENBUSY_MASK) ;
+ CI_REG->TOKEN = (TOK_PID_SETUP << USB_TOKEN_TOKENPID_SHIFT);
return true;
}
@@ -540,16 +547,16 @@ bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t *
TU_ASSERT(0 <= pipenum);
TU_ASSERT(0 == (_hcd.in_progress & TU_BIT(pipenum)));
- unsigned const ie = NVIC_GetEnableIRQ(USB0_IRQn);
- NVIC_DisableIRQ(USB0_IRQn);
+ unsigned const ie = NVIC_GetEnableIRQ(CI_FS_IRQN);
+ NVIC_DisableIRQ(CI_FS_IRQN);
pipe_state_t *pipe = &_hcd.pipe[pipenum];
pipe->buffer = buffer;
pipe->length = buflen;
pipe->remaining = buflen;
_hcd.in_progress |= TU_BIT(pipenum);
_hcd.pending |= TU_BIT(pipenum); /* Send at the next Frame */
- KHCI->INTEN |= USB_ISTAT_SOFTOK_MASK;
- if (ie) NVIC_EnableIRQ(USB0_IRQn);
+ CI_REG->INT_EN |= USB_ISTAT_SOFTOK_MASK;
+ if (ie) NVIC_EnableIRQ(CI_FS_IRQN);
return true;
}
@@ -577,42 +584,42 @@ bool hcd_edpt_clear_stall(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr) {
void hcd_int_handler(uint8_t rhport, bool in_isr)
{
(void) in_isr;
- uint32_t is = KHCI->ISTAT;
- uint32_t msk = KHCI->INTEN;
+ uint32_t is = CI_REG->INT_STAT;
+ uint32_t msk = CI_REG->INT_EN;
// TU_LOG1("S %lx\r\n", is);
/* clear disabled interrupts */
- KHCI->ISTAT = (is & ~msk & ~USB_ISTAT_TOKDNE_MASK) | USB_ISTAT_SOFTOK_MASK;
+ CI_REG->INT_STAT = (is & ~msk & ~USB_ISTAT_TOKDNE_MASK) | USB_ISTAT_SOFTOK_MASK;
is &= msk;
if (is & USB_ISTAT_ERROR_MASK) {
- unsigned err = KHCI->ERRSTAT;
+ unsigned err = CI_REG->ERR_STAT;
if (err) {
TU_LOG1(" ERR %x\r\n", err);
- KHCI->ERRSTAT = err;
+ CI_REG->ERR_STAT = err;
} else {
- KHCI->INTEN &= ~USB_ISTAT_ERROR_MASK;
+ CI_REG->INT_EN &= ~USB_ISTAT_ERROR_MASK;
}
}
if (is & USB_ISTAT_USBRST_MASK) {
- KHCI->INTEN = (msk & ~USB_INTEN_USBRSTEN_MASK) | USB_INTEN_ATTACHEN_MASK;
+ CI_REG->INT_EN = (msk & ~USB_INTEN_USBRSTEN_MASK) | USB_INTEN_ATTACHEN_MASK;
process_bus_reset(rhport);
return;
}
if (is & USB_ISTAT_ATTACH_MASK) {
- KHCI->INTEN = (msk & ~USB_INTEN_ATTACHEN_MASK) | USB_INTEN_USBRSTEN_MASK;
+ CI_REG->INT_EN = (msk & ~USB_INTEN_ATTACHEN_MASK) | USB_INTEN_USBRSTEN_MASK;
_hcd.need_reset = true;
process_attach(rhport);
return;
}
if (is & USB_ISTAT_STALL_MASK) {
- KHCI->ISTAT = USB_ISTAT_STALL_MASK;
+ CI_REG->INT_STAT = USB_ISTAT_STALL_MASK;
}
if (is & USB_ISTAT_SOFTOK_MASK) {
msk &= ~USB_ISTAT_SOFTOK_MASK;
- KHCI->INTEN = msk;
+ CI_REG->INT_EN = msk;
if (_hcd.pending) {
int pipenum = __builtin_ctz(_hcd.pending);
_hcd.pending = 0;
diff --git a/src/portable/chipidea/ci_hs/ci_hs_imxrt.h b/src/portable/chipidea/ci_hs/ci_hs_imxrt.h
index f0f918fe2..8f0d6083e 100644
--- a/src/portable/chipidea/ci_hs/ci_hs_imxrt.h
+++ b/src/portable/chipidea/ci_hs/ci_hs_imxrt.h
@@ -36,6 +36,9 @@ static const ci_hs_controller_t _ci_controller[] =
#define CI_HS_REG(_port) ((ci_hs_regs_t*) _ci_controller[_port].reg_base)
+// NXP recommends AHBBRST = INCR16 (remainder as unspecified-length bursts)
+#define CI_HS_SET_AHB_BURST(_p) (CI_HS_REG(_p)->SBUSCFG = SBUSCFG_AHBBRST_INCR16_UNSPEC)
+
//------------- DCD -------------//
#define CI_DCD_INT_ENABLE(_p) NVIC_EnableIRQ ((IRQn_Type)_ci_controller[_p].irqnum)
#define CI_DCD_INT_DISABLE(_p) NVIC_DisableIRQ((IRQn_Type)_ci_controller[_p].irqnum)
diff --git a/src/portable/chipidea/ci_hs/ci_hs_lpc18_43.h b/src/portable/chipidea/ci_hs/ci_hs_lpc18_43.h
index f2061bd7a..c7dc7e69f 100644
--- a/src/portable/chipidea/ci_hs/ci_hs_lpc18_43.h
+++ b/src/portable/chipidea/ci_hs/ci_hs_lpc18_43.h
@@ -34,4 +34,9 @@ static const ci_hs_controller_t _ci_controller[] =
#define CI_HCD_INT_ENABLE(_p) NVIC_EnableIRQ ((IRQn_Type)_ci_controller[_p].irqnum)
#define CI_HCD_INT_DISABLE(_p) NVIC_DisableIRQ((IRQn_Type)_ci_controller[_p].irqnum)
+// USB0 (high-speed) only: NXP recommends AHBBRST = INCR16 (remainder as
+// unspecified-length bursts)
+#define CI_HS_SET_AHB_BURST(_p) \
+ do { if ((_p) == 0) { CI_HS_REG(_p)->SBUSCFG = SBUSCFG_AHBBRST_INCR16_UNSPEC; } } while (0)
+
#endif
diff --git a/src/portable/chipidea/ci_hs/ci_hs_type.h b/src/portable/chipidea/ci_hs/ci_hs_type.h
index 70817a6e3..5baa14821 100644
--- a/src/portable/chipidea/ci_hs/ci_hs_type.h
+++ b/src/portable/chipidea/ci_hs/ci_hs_type.h
@@ -36,10 +36,18 @@ enum {
PORTSC1_CURRENT_CONNECT_STATUS = TU_BIT(0),
PORTSC1_FORCE_PORT_RESUME = TU_BIT(6),
PORTSC1_SUSPEND = TU_BIT(7),
+ PORTSC1_PORT_RESET = TU_BIT(8), // read-only in device mode: a reset is being driven
PORTSC1_FORCE_FULL_SPEED = TU_BIT(24),
PORTSC1_PORT_SPEED = TU_BIT(26) | TU_BIT(27)
};
+// PORTSC1 PSPD field values, once shifted down by PORTSC1_PORT_SPEED_POS. 3 is undefined.
+enum {
+ PORTSC1_PORT_SPEED_FULL = 0,
+ PORTSC1_PORT_SPEED_LOW = 1,
+ PORTSC1_PORT_SPEED_HIGH = 2,
+};
+
// OTGSC
enum {
OTGSC_VBUS_DISCHARGE = TU_BIT(0),
@@ -71,11 +79,18 @@ enum {
USBMODE_VBUS_POWER_SELECT = TU_BIT(5), // Need to be enabled for LPC18XX/43XX in host mode
};
+// SBUSCFG
+enum {
+ SBUSCFG_AHBBRST_INCR16_UNSPEC = 7, // INCR16 burst, remainder as unspecified-length bursts
+};
+
// Device Registers
typedef struct
{
//------------- ID + HW Parameter Registers-------------//
- volatile uint32_t TU_RESERVED[64]; ///< For iMX RT10xx, but not used by LPC18XX/LPC43XX
+ volatile uint32_t TU_RESERVED[36]; ///< ID/HW parameter registers, not used by this driver
+ volatile uint32_t SBUSCFG; ///< System Bus Interface Configuration (not present on every MCU)
+ volatile uint32_t TU_RESERVED[27];
//------------- Capability Registers-------------//
volatile uint8_t CAPLENGTH; ///< Capability Registers Length
diff --git a/src/portable/chipidea/ci_hs/dcd_ci_hs.c b/src/portable/chipidea/ci_hs/dcd_ci_hs.c
index 55906e678..6ab28e0be 100644
--- a/src/portable/chipidea/ci_hs/dcd_ci_hs.c
+++ b/src/portable/chipidea/ci_hs/dcd_ci_hs.c
@@ -154,6 +154,14 @@ TU_VERIFY_STATIC(sizeof(dcd_qhd_t) == 64, "size is not correct");
#define QTD_NEXT_INVALID 0x01
+// Bounded spin for register waits. The longest legitimate wait is a flush held off by a packet
+// already in progress: ~50 us for a full-speed 64-byte packet, a low thousands of dependent
+// register reads, so healthy hardware never approaches this bound. Exceeding it means the
+// controller has stopped responding, and the spin then only serves to keep an ISR (or an
+// IRQ-masked caller) from hanging outright - the 3 ms reset-cleanup window of IMXRT1060RM 42.5.6.2.1 (p.2394)
+// is already unreachable in that state, and the manual's remedy there is a controller reset.
+#define CI_HS_BUSY_SPIN 10000u
+
typedef struct {
// Must be at 2K alignment
// Each endpoint with direction (IN/OUT) occupies a queue head
@@ -164,6 +172,17 @@ typedef struct {
CFG_TUD_MEM_SECTION TU_ATTR_ALIGNED(2048) static dcd_data_t _dcd_data;
+// What the next Port Change Detect will be. Each one is preceded by the interrupt that causes it:
+// a reset interrupt for the end of a bus reset - where the speed first becomes final - or a
+// suspend interrupt for the resume that ends the suspend. A suspend itself raises no port change,
+// which is why there is no such value here. Indexed by rhport, which is 0 or 1 on every ci_hs
+// variant (NOT the controller count: mcx/rw61x map rhport 1 to controller 0).
+enum {
+ PORT_CHANGE_REASON_RESET = 0,
+ PORT_CHANGE_REASON_RESUME = 1,
+};
+static volatile uint8_t _port_change_reason[2];
+
//--------------------------------------------------------------------+
// Prototypes and Helper Functions
//--------------------------------------------------------------------+
@@ -172,12 +191,37 @@ TU_ATTR_ALWAYS_INLINE static inline uint8_t ci_ep_count(const ci_hs_regs_t *dcd_
return dcd_reg->DCCPARAMS & DCCPARAMS_DEN_MASK;
}
+static bool controller_reset(uint8_t rhport);
+
//--------------------------------------------------------------------+
// Controller API
//--------------------------------------------------------------------+
-/// follows LPC43xx User Manual 23.10.3
-static void bus_reset(uint8_t rhport) {
+// Flush endpoint buffers, following IMXRT1060RM 42.5.6.6.5 Flushing/De-priming an Endpoint
+// (p.2413): write ENDPTFLUSH, wait for the controller
+// to acknowledge, then confirm ENDPTSTAT went to zero. The controller refuses the flush when a
+// packet is in progress, and the manual requires the procedure be repeated until it takes.
+// Callers proceed regardless of the result; the bound only prevents an ISR-context hang on dead
+// hardware.
+static bool flush_endpoints(ci_hs_regs_t *dcd_reg, uint32_t mask) {
+ uint32_t guard = CI_HS_BUSY_SPIN;
+ do {
+ dcd_reg->ENDPTFLUSH = mask;
+ while (dcd_reg->ENDPTFLUSH & mask) {
+ if (!guard--) {
+ return false;
+ }
+ }
+ } while ((dcd_reg->ENDPTSTAT & mask) && guard--);
+
+ return !(dcd_reg->ENDPTSTAT & mask);
+}
+
+/// Everything the manual asks of the DCD when a reset is detected, in its order: clear the setup
+/// and completion semaphores, cancel every prime, check the reset is still being driven, and free
+/// the dTDs. All of it belongs inside the reset window (IMXRT1060RM 42.5.6.2.1, p.2394); nothing
+/// is left for the port change that ends the reset, which only reports the negotiated speed.
+static void bus_reset_begin(uint8_t rhport) {
ci_hs_regs_t *dcd_reg = CI_HS_REG(rhport);
// The reset value for all endpoint types is the control endpoint. If one endpoint
@@ -193,17 +237,24 @@ static void bus_reset(uint8_t rhport) {
//------------- Clear All Registers -------------//
dcd_reg->ENDPTNAK = dcd_reg->ENDPTNAK;
dcd_reg->ENDPTNAKEN = 0;
- dcd_reg->USBSTS = dcd_reg->USBSTS;
dcd_reg->ENDPTSETUPSTAT = dcd_reg->ENDPTSETUPSTAT;
dcd_reg->ENDPTCOMPLETE = dcd_reg->ENDPTCOMPLETE;
- while (dcd_reg->ENDPTPRIME) {}
- dcd_reg->ENDPTFLUSH = 0xFFFFFFFF;
- while (dcd_reg->ENDPTFLUSH) {}
+ uint32_t guard = CI_HS_BUSY_SPIN;
+ while (dcd_reg->ENDPTPRIME && guard--) {}
+ dcd_reg->ENDPTFLUSH = 0xFFFFFFFFUL;
- // read reset bit in portsc
+ // All of the above must land while the reset is still being driven - it lasts at least 3 ms.
+ // Arriving late leaves the controller in an undefined state, and the manual's remedy is to
+ // hardware-reset it. That clears Run/Stop, so the device detaches and the host will drive a
+ // fresh reset and enumeration - which is why nothing below this point is worth doing here.
+ if (!(dcd_reg->PORTSC1 & PORTSC1_PORT_RESET)) {
+ TU_LOG1("ci_hs: reset cleanup ran past the end of the reset, resetting controller\r\n");
+ controller_reset(rhport);
+ return; // the controller detached; the host's next reset redoes everything below
+ }
- //------------- Queue Head & Queue TD -------------//
+ //------------- Free all allocated dTDs: the controller will not execute them again -------------//
tu_memclr(&_dcd_data, sizeof(dcd_data_t));
//------------- Set up Control Endpoints (0 OUT, 1 IN) -------------//
@@ -216,27 +267,29 @@ static void bus_reset(uint8_t rhport) {
dcd_dcache_clean_invalidate(&_dcd_data, sizeof(dcd_data_t));
}
-bool dcd_init(uint8_t rhport, const tusb_rhport_init_t *rh_init) {
- (void)rh_init;
- tu_memclr(&_dcd_data, sizeof(dcd_data_t));
-
+/// Reset the controller and bring it back up in device mode. Also the manual's remedy when the
+/// reset cleanup misses its window: the controller reset clears Run/Stop and detaches the device,
+/// so it must be re-initialised completely afterwards (IMXRT1060RM 42.5.6.2.1, p.2394).
+static bool controller_reset(uint8_t rhport) {
ci_hs_regs_t *dcd_reg = CI_HS_REG(rhport);
- TU_ASSERT(ci_ep_count(dcd_reg) <= TUP_DCD_ENDPOINT_MAX);
-
- #if TU_CHECK_MCU(OPT_MCU_HPM)
- usb_phy_init((USB_Type *)dcd_reg, false);
- #endif
+ tu_memclr(&_dcd_data, sizeof(dcd_data_t));
// Reset controller
dcd_reg->USBCMD |= USBCMD_RESET;
- while (dcd_reg->USBCMD & USBCMD_RESET) {}
+ uint32_t guard = CI_HS_BUSY_SPIN;
+ while ((dcd_reg->USBCMD & USBCMD_RESET) && guard--) {}
+ TU_VERIFY(!(dcd_reg->USBCMD & USBCMD_RESET)); // reached from the ISR too, so never halt here
// Set mode to device, must be set immediately after reset
uint32_t usbmode = dcd_reg->USBMODE & ~USBMOD_CM_MASK;
usbmode |= USBMODE_CM_DEVICE;
dcd_reg->USBMODE = usbmode;
+ #ifdef CI_HS_SET_AHB_BURST
+ CI_HS_SET_AHB_BURST(rhport);
+ #endif
+
#ifdef CFG_TUD_CI_HS_VBUS_CHARGE
dcd_reg->OTGSC = OTGSC_VBUS_CHARGE | OTGSC_OTG_TERMINATION;
#else
@@ -253,9 +306,11 @@ bool dcd_init(uint8_t rhport, const tusb_rhport_init_t *rh_init) {
dcd_dcache_clean_invalidate(&_dcd_data, sizeof(dcd_data_t));
+ _port_change_reason[rhport] = PORT_CHANGE_REASON_RESET;
+
dcd_reg->ENDPTLISTADDR = (uint32_t)_dcd_data.qhd; // Endpoint List Address has to be 2K alignment
dcd_reg->USBSTS = dcd_reg->USBSTS;
- dcd_reg->USBINTR = INTR_USB | INTR_ERROR | INTR_PORT_CHANGE | INTR_SUSPEND;
+ dcd_reg->USBINTR = INTR_USB | INTR_ERROR | INTR_PORT_CHANGE | INTR_RESET | INTR_SUSPEND;
uint32_t usbcmd = dcd_reg->USBCMD;
usbcmd &= ~USBCMD_INTR_THRESHOLD_MASK; // Interrupt Threshold Interval = 0
@@ -266,8 +321,22 @@ bool dcd_init(uint8_t rhport, const tusb_rhport_init_t *rh_init) {
return true;
}
+bool dcd_init(uint8_t rhport, const tusb_rhport_init_t *rh_init) {
+ (void)rh_init;
+ ci_hs_regs_t *dcd_reg = CI_HS_REG(rhport);
+
+ TU_ASSERT(ci_ep_count(dcd_reg) <= TUP_DCD_ENDPOINT_MAX);
+
+ #if TU_CHECK_MCU(OPT_MCU_HPM)
+ usb_phy_init((USB_Type *)dcd_reg, false);
+ #endif
+
+ return controller_reset(rhport);
+}
+
bool dcd_deinit(uint8_t rhport) {
ci_hs_regs_t* dcd_reg = CI_HS_REG(rhport);
+ _port_change_reason[rhport] = PORT_CHANGE_REASON_RESET;
// disable all interrupt
dcd_reg->USBINTR = 0;
@@ -276,9 +345,9 @@ bool dcd_deinit(uint8_t rhport) {
dcd_reg->USBCMD &= ~USBCMD_RUN_STOP;
// flush all endpoints
- while (dcd_reg->ENDPTPRIME) {}
- dcd_reg->ENDPTFLUSH = 0xFFFFFFFF;
- while (dcd_reg->ENDPTFLUSH) {}
+ uint32_t guard = CI_HS_BUSY_SPIN;
+ while (dcd_reg->ENDPTPRIME && guard--) {}
+ flush_endpoints(dcd_reg, 0xFFFFFFFF);
return true;
}
@@ -292,11 +361,13 @@ void dcd_int_disable(uint8_t rhport) {
}
void dcd_set_address(uint8_t rhport, uint8_t dev_addr) {
- // Response with status first before changing device address
- dcd_edpt_xfer(rhport, tu_edpt_addr(0, TUSB_DIR_IN), NULL, 0, false);
-
- ci_hs_regs_t *dcd_reg = CI_HS_REG(rhport);
- dcd_reg->DEVICEADDR = (dev_addr << 25) | TU_BIT(24);
+ // Response with status first before changing device address. A refused prime means a new
+ // setup superseded this transfer; staging an address whose ACK will never arrive would
+ // leave the device answering on it, so only arm the address when the status went out.
+ if (dcd_edpt_xfer(rhport, tu_edpt_addr(0, TUSB_DIR_IN), NULL, 0, false)) {
+ ci_hs_regs_t *dcd_reg = CI_HS_REG(rhport);
+ dcd_reg->DEVICEADDR = (dev_addr << 25) | TU_BIT(24);
+ }
}
void dcd_remote_wakeup(uint8_t rhport) {
@@ -393,7 +464,8 @@ void dcd_edpt_stall(uint8_t rhport, uint8_t ep_addr) {
ci_hs_regs_t *dcd_reg = CI_HS_REG(rhport);
dcd_reg->ENDPTCTRL[epnum] |= ENDPTCTRL_STALL << (dir ? 16 : 0);
- // flush to abort any primed buffer
+ // flush to abort any primed buffer; the aborted transfer's dQH overlay can be left
+ // ACTIVE with mid-transfer state - qhd_start_xfer clears it before the next prime
dcd_reg->ENDPTFLUSH = TU_BIT(epnum + (dir ? 16 : 0));
}
@@ -463,9 +535,7 @@ bool dcd_edpt_iso_activate(uint8_t rhport, const tusb_desc_endpoint_t *desc_ep)
// dcd_dcache_clean_invalidate(&_dcd_data, sizeof(dcd_data_t));
// Flush EP
- const uint32_t flush_mask = TU_BIT(epnum + (dir ? 16 : 0));
- dcd_reg->ENDPTFLUSH = flush_mask;
- while (dcd_reg->ENDPTFLUSH & flush_mask) {}
+ flush_endpoints(dcd_reg, TU_BIT(epnum + (dir ? 16 : 0)));
// disable to change max packet size
ep_ctrl_clear(endptctrl, dir, ENDPTCTRL_ENABLE);
@@ -491,25 +561,35 @@ void dcd_edpt_close_all(uint8_t rhport) {
}
}
-static void qhd_start_xfer(uint8_t rhport, uint8_t epnum, uint8_t dir) {
+static bool qhd_start_xfer(uint8_t rhport, uint8_t epnum, uint8_t dir) {
ci_hs_regs_t *dcd_reg = CI_HS_REG(rhport);
dcd_qhd_t *p_qhd = &_dcd_data.qhd[epnum][dir];
dcd_qtd_t *p_qtd = &_dcd_data.qtd[epnum][dir];
p_qhd->qtd_overlay.halted = false; // clear any previous error
+ p_qhd->qtd_overlay.active = false; // a flushed prime leaves stale ACTIVE state; clear it so the fresh qtd loads
p_qhd->qtd_overlay.next = (uint32_t)p_qtd; // link qtd to qhd
// flush cache
dcd_dcache_clean_invalidate(&_dcd_data, sizeof(dcd_data_t));
if (epnum == 0) {
- // follows UM 24.10.8.1.1 Setup packet handling using setup lockout mechanism
- // wait until ENDPTSETUPSTAT before priming data/status in response TODO add time out
- while (dcd_reg->ENDPTSETUPSTAT & TU_BIT(0)) {}
+ // Setup lockout (IMXRT1060RM 42.5.6.4.2.1 Setup Phase, p.2403): never prime EP0 while a new
+ // SETUP is pending. The ISR
+ // normally consumes ENDPTSETUPSTAT quickly; if the guard trips, fail the transfer so usbd
+ // releases the endpoint (a pending SETUP supersedes this response anyway; without one, usbd
+ // stalls EP0 and the host recovers with a fresh control transfer).
+ uint32_t guard = CI_HS_BUSY_SPIN;
+ while (dcd_reg->ENDPTSETUPSTAT & TU_BIT(0)) {
+ if (!guard--) {
+ return false;
+ }
+ }
}
// start transfer
dcd_reg->ENDPTPRIME = TU_BIT(epnum + (dir ? 16 : 0));
+ return true;
}
bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t *buffer, uint16_t total_bytes, bool is_isr) {
@@ -525,9 +605,7 @@ bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t *buffer, uint16_t to
// Start qhd transfer
p_qhd->ff = NULL;
- qhd_start_xfer(rhport, epnum, dir);
-
- return true;
+ return qhd_start_xfer(rhport, epnum, dir);
}
#if !CFG_TUD_MEM_DCACHE_ENABLE
@@ -578,9 +656,7 @@ bool dcd_edpt_xfer_fifo(uint8_t rhport, uint8_t ep_addr, tu_fifo_t *ff, uint16_t
// Start qhd transfer
p_qhd->ff = ff;
- qhd_start_xfer(rhport, epnum, dir);
-
- return true;
+ return qhd_start_xfer(rhport, epnum, dir);
}
#endif
@@ -628,43 +704,43 @@ void dcd_int_handler(uint8_t rhport) {
return;
}
- // Set if the port controller enters the full or high-speed operational state.
- // either from Bus Reset or Suspended state
- if (int_status & INTR_PORT_CHANGE) {
- // TU_LOG2("PortChange %08lx\r\n", dcd_reg->PORTSC1);
+ const uint8_t pci_reason = _port_change_reason[rhport]; // save current pci_reason
- // Reset interrupt is not enabled, we manually check if Port Change is due
- // to connection / disconnection
- if (dcd_reg->USBSTS & INTR_RESET) {
- dcd_reg->USBSTS = INTR_RESET;
-
- if (dcd_reg->PORTSC1 & PORTSC1_CURRENT_CONNECT_STATUS) {
- const uint32_t speed = (dcd_reg->PORTSC1 & PORTSC1_PORT_SPEED) >> PORTSC1_PORT_SPEED_POS;
- bus_reset(rhport);
- dcd_event_bus_reset(rhport, (tusb_speed_t)speed, true);
- } else {
- dcd_event_bus_signal(rhport, DCD_EVENT_UNPLUGGED, true);
- }
- } else {
- // Triggered by resuming from suspended state
- if (!(dcd_reg->PORTSC1 & PORTSC1_SUSPEND)) {
- dcd_event_bus_signal(rhport, DCD_EVENT_RESUME, true);
- }
- }
+ if (int_status & INTR_SUSPEND) {
+ _port_change_reason[rhport] = PORT_CHANGE_REASON_RESUME; // next PCI is resume
+ dcd_event_bus_signal(rhport, DCD_EVENT_SUSPEND, true);
}
- if (int_status & INTR_SUSPEND) {
- // TU_LOG2("Suspend %08lx\r\n", dcd_reg->PORTSC1);
+ // USB Reset Received: register cleanup runs here within the reset window (IMXRT1060RM 42.5.6.2.1, p.2394)
+ // and BUS_RESET_START fires now; BUS_RESET_END, with the final speed, is triggered later by PCI.
+ if (int_status & INTR_RESET) {
+ _port_change_reason[rhport] = PORT_CHANGE_REASON_RESET;
+ bus_reset_begin(rhport);
+ dcd_event_bus_signal(rhport, DCD_EVENT_BUS_RESET_START, true);
+ }
- if (dcd_reg->PORTSC1 & PORTSC1_SUSPEND) {
- // Note: Host may delay more than 3 ms before and/or after bus reset before doing enumeration.
- // Skip suspend event if we are not addressed
- if ((dcd_reg->DEVICEADDR >> 25) & 0x0f) {
- dcd_event_bus_signal(rhport, DCD_EVENT_SUSPEND, true);
- }
+ // Port entered the full/high-speed operational state: the end of a bus reset, or a resume.
+ if (int_status & INTR_PORT_CHANGE) {
+ if (pci_reason == PORT_CHANGE_REASON_RESUME) {
+ dcd_event_bus_signal(rhport, DCD_EVENT_RESUME, true);
+ } else {
+ // the undefined encoding falls back to full speed
+ const uint32_t pspd = (dcd_reg->PORTSC1 & PORTSC1_PORT_SPEED) >> PORTSC1_PORT_SPEED_POS;
+ const tusb_speed_t speed = (pspd == PORTSC1_PORT_SPEED_LOW) ? TUSB_SPEED_LOW :
+ (pspd == PORTSC1_PORT_SPEED_HIGH) ? TUSB_SPEED_HIGH : TUSB_SPEED_FULL;
+ dcd_event_bus_reset(rhport, speed, true);
+ // This reset is over, so the next port change is a resume. Leaving it at RESET instead would
+ // dispatch every later resume as another end-of-reset, clearing the queue heads mid-session.
+ _port_change_reason[rhport] = PORT_CHANGE_REASON_RESUME;
}
}
+ // No unplug detection yet, by the manual rather than by omission: IMXRT1060RM 42.7.31 (p.2470) says a zero
+ // Current Connect Status means the device "did not attach successfully or was forcibly
+ // disconnected by the software writing a zero to the Run bit ... It does not state the device
+ // being disconnected or suspended", so a cable pull raises no port change at all. VBUS via
+ // OTGSC BSV is the manual's disconnect indicator, and it is board dependent.
+
if (int_status & INTR_USB) {
// Make sure we read the latest version of _dcd_data.
dcd_dcache_clean_invalidate(&_dcd_data, sizeof(dcd_data_t));
@@ -672,7 +748,7 @@ void dcd_int_handler(uint8_t rhport) {
const uint32_t edpt_complete = dcd_reg->ENDPTCOMPLETE;
dcd_reg->ENDPTCOMPLETE = edpt_complete; // acknowledge
- // 23.10.12.3 Failed QTD also get ENDPTCOMPLETE set
+ // 42.5.6.6.4 Transfer Completion (p.2413): a failed dTD also sets ENDPTCOMPLETE
// nothing to do, we will submit xfer as error to usbd
// if (int_status & INTR_ERROR) { }
@@ -688,12 +764,39 @@ void dcd_int_handler(uint8_t rhport) {
}
// Set up Received
- // 23.10.10.2 Operational model for setup transfers
+ // 42.5.6.4.2 Control Endpoint Operation Model (p.2403)
// Must be after normal transfer complete since it is possible to have both previous control status + new setup
// in the same frame and we should handle previous status first.
if (dcd_reg->ENDPTSETUPSTAT) {
+ // 42.5.6.4.2.1 Setup Phase (p.2403) steps 1-2: duplicate the setup payload BEFORE clearing
+ // ENDPTSETUPSTAT -
+ // the clear releases the setup lockout and a back-to-back SETUP (usbtest case 10) can
+ // overwrite the queue-head buffer immediately after. The copy is read through the volatile
+ // qualifier rather than memcpy'd because C orders volatile accesses only against each
+ // other: a plain copy may legally be sunk past the lockout-releasing store below.
+ union {
+ tusb_control_request_t request;
+ uint8_t byte[8];
+ } setup;
+ const volatile uint8_t *setup_src = (const volatile uint8_t *)&_dcd_data.qhd[0][0].setup_request;
+ for (uint8_t i = 0; i < sizeof(setup.request); i++) {
+ setup.byte[i] = setup_src[i];
+ }
dcd_reg->ENDPTSETUPSTAT = dcd_reg->ENDPTSETUPSTAT;
- dcd_event_setup_received(rhport, (uint8_t *)(uintptr_t)&_dcd_data.qhd[0][0].setup_request, true);
+
+ // Retire a status/handshake phase left primed by the previous control sequence
+ // (IMXRT1060RM 42.5.6.4.2.1, p.2403), which would otherwise retire the response the task is about to
+ // prime for this setup. Skipped when EP0 has nothing primed or priming, since the manual
+ // does not want the flush wait in an interrupt handler when it has nothing to do.
+ // One volatile read per statement: C leaves their order unspecified within a single
+ // expression, which IAR rejects outright (Pa082).
+ const uint32_t ep0_mask = TU_BIT(0) | TU_BIT(16);
+ const uint32_t ep0_stat = dcd_reg->ENDPTSTAT;
+ const uint32_t ep0_prime = dcd_reg->ENDPTPRIME;
+ if ((ep0_stat | ep0_prime) & ep0_mask) {
+ flush_endpoints(dcd_reg, ep0_mask);
+ }
+ dcd_event_setup_received(rhport, setup.byte, true);
}
}
diff --git a/src/portable/chipidea/ci_hs/hcd_ci_hs.c b/src/portable/chipidea/ci_hs/hcd_ci_hs.c
index 3cb69acfa..0f24f5bb6 100644
--- a/src/portable/chipidea/ci_hs/hcd_ci_hs.c
+++ b/src/portable/chipidea/ci_hs/hcd_ci_hs.c
@@ -82,6 +82,10 @@ bool hcd_init(uint8_t rhport, const tusb_rhport_init_t *rh_init) {
hcd_reg->USBMODE = USBMODE_CM_HOST;
#endif
+ #ifdef CI_HS_SET_AHB_BURST
+ CI_HS_SET_AHB_BURST(rhport);
+ #endif
+
#if !TUH_OPT_HIGH_SPEED
hcd_reg->PORTSC1 |= PORTSC1_FORCE_FULL_SPEED;
#endif
diff --git a/src/portable/mentor/musb/dcd_musb.c b/src/portable/mentor/musb/dcd_musb.c
index 1ebd1fe02..92c9fe92e 100644
--- a/src/portable/mentor/musb/dcd_musb.c
+++ b/src/portable/mentor/musb/dcd_musb.c
@@ -24,6 +24,8 @@
#include "musb_ti.h"
#elif defined(TUP_USBIP_MUSB_ADI)
#include "musb_max32.h"
+#elif defined(TUP_USBIP_MUSB_PY32)
+ #include "musb_py32.h"
#else
#error "Unsupported MCU"
#endif
@@ -45,6 +47,7 @@ typedef struct {
};
uint16_t length; /* the number of bytes in the buffer */
uint16_t remaining; /* the number of bytes remaining in the buffer */
+ uint16_t mps; /* maximum packet size */
bool armed; /* true while a transfer is posted */
bool use_fifo; /* true: buf is tu_fifo_t*; false: buf is plain byte pointer. */
} pipe_state_t;
@@ -159,6 +162,14 @@ TU_ATTR_ALWAYS_INLINE static inline pipe_state_t* pipe_get(uint8_t epnum, tusb_d
return &_dcd.pipe[idx];
}
+TU_ATTR_ALWAYS_INLINE static inline uint16_t musb_mps_to_maxp(uint16_t mps) {
+#if defined(TUP_USBIP_MUSB_PY32)
+ return (uint8_t) ((mps + 7u) / 8u);
+#else
+ return mps;
+#endif
+}
+
//--------------------------------------------------------------------
// HW FIFO Helper
// Note: Index register is already set by caller
@@ -223,7 +234,12 @@ TU_ATTR_ALWAYS_INLINE static inline bool hwfifo_config(musb_regs_t* musb, unsign
bool double_packet) {
(void) mps;
- #if defined(TUP_USBIP_MUSB_ADI)
+ #if defined(TUP_USBIP_MUSB_PY32)
+ (void) musb; (void) is_rx; (void) double_packet;
+ // Puya FIFO sizes: EP0 = 64 B, EP1 = 512 B, EP2..4 = 128 B, EP5 = 64 B, shared between IN and OUT.
+ static const uint16_t py32_fifo_size[] = { 64, 512, 128, 128, 128, 64 };
+ return epnum < TU_ARRAY_SIZE(py32_fifo_size) && mps <= py32_fifo_size[epnum];
+ #elif defined(TUP_USBIP_MUSB_ADI)
// AnalogDevice FIFO sizes: EP1..7 = 512 B, EP8..9 = 2048 B, EP10..11 = 4096 B.
// DPB requires FIFO >= 2 * MPS. For HS bulk (MPS=512) only EP >= 8 qualifies.
// Force single-buffered on EP < 8 even if the caller requested DPB.
@@ -266,7 +282,7 @@ TU_ATTR_ALWAYS_INLINE static inline void hwfifo_flush(musb_regs_t* musb, unsigne
// write to txfifo using pipe_state_t info
static void pipe_write(musb_regs_t* musb_regs, pipe_state_t* pipe, uint8_t epnum) {
musb_ep_csr_t* ep_csr = &musb_regs->indexed_csr;
- const uint16_t mps = ep_csr->tx_maxp & MUSB_TXMAXP_PACKET_SIZE_M;
+ const uint16_t mps = pipe->mps;
const uint16_t xact_len = tu_min16(mps, pipe->remaining);
volatile void *hwfifo = &musb_regs->fifo[epnum];
if (xact_len) {
@@ -292,6 +308,12 @@ static void process_epin_isr(uint8_t rhport, musb_regs_t *musb_regs, uint8_t epn
}
pipe_state_t* pipe = pipe_get(epnum, TUSB_DIR_IN);
+ // No active transfer: a halt/abort disarmed the pipe (armed=false) but may leave remaining>0.
+ // Do not keep loading the aborted transfer — that would re-fill the just-flushed FIFO and the
+ // next (re-armed) transfer's data would stack on top (host sees an oversized packet -> babble).
+ if (!pipe->armed) {
+ return;
+ }
if (pipe->remaining > 0) {
pipe_write(musb_regs, pipe, epnum);
} else {
@@ -314,7 +336,7 @@ static void process_epin_isr(uint8_t rhport, musb_regs_t *musb_regs, uint8_t epn
// release the FIFO slot by clearing RXRDY. return true if short packet
static bool pipe_read(musb_regs_t* musb_regs, pipe_state_t* pipe, uint8_t epnum) {
musb_ep_csr_t* ep_csr = &musb_regs->indexed_csr; // index already set in process_epout_isr()
- const uint16_t mps = ep_csr->rx_maxp & MUSB_RXMAXP_PACKET_SIZE_M;
+ const uint16_t mps = pipe->mps;
const uint16_t rx_count = ep_csr->rx_count;
const uint16_t xact_len = tu_min16(tu_min16(pipe->remaining, mps), rx_count);
volatile void *hwfifo = &musb_regs->fifo[epnum];
@@ -626,7 +648,11 @@ static void process_bus_reset_isr(uint8_t rhport) {
hwfifo_reset(musb, i, 0);
hwfifo_reset(musb, i, 1);
}
+#if defined(TUP_USBIP_MUSB_PY32)
+ dcd_event_bus_reset(rhport, TUSB_SPEED_FULL, true);
+#else
dcd_event_bus_reset(rhport, (musb->power & MUSB_POWER_HSMODE) ? TUSB_SPEED_HIGH : TUSB_SPEED_FULL, true);
+#endif
}
/*------------------------------------------------------------------
@@ -635,6 +661,11 @@ static void process_bus_reset_isr(uint8_t rhport) {
#if CFG_TUSB_DEBUG >= MUSB_DEBUG
static void print_musb_info(musb_regs_t* musb_regs) {
+#if defined(TUP_USBIP_MUSB_PY32)
+ (void) musb_regs;
+ // musb discovery fields not present
+ TU_LOG1("musb py32 fixed full-speed configuration\r\n");
+#else
// print version, epinfo, raminfo, config_data0, fifo_size
TU_LOG1("musb version = %u.%u\r\n", musb_regs->hwvers_bit.major, musb_regs->hwvers_bit.minor);
TU_LOG1("Number of endpoints: %u TX, %u RX\r\n", musb_regs->epinfo_bit.tx_ep_num, musb_regs->epinfo_bit.rx_ep_num);
@@ -651,6 +682,7 @@ static void print_musb_info(musb_regs_t* musb_regs) {
TU_LOG1("FIFO %u Size: TX %u RX %u\r\n", i, musb_regs->indexed_csr.fifo_size_bit.tx, musb_regs->indexed_csr.fifo_size_bit.rx);
}
#endif
+#endif
}
#endif
@@ -706,6 +738,18 @@ void dcd_remote_wakeup(uint8_t rhport) {
musb_regs->power &= ~MUSB_POWER_RESUME;
}
+#if defined(TUP_USBIP_MUSB_PY32)
+void dcd_connect(uint8_t rhport)
+{
+ (void) rhport;
+}
+
+void dcd_disconnect(uint8_t rhport)
+{
+ (void) rhport;
+}
+#else
+
// Connect by enabling internal pull-up resistor on D+/D-
void dcd_connect(uint8_t rhport)
{
@@ -721,6 +765,8 @@ void dcd_disconnect(uint8_t rhport)
musb_regs->power &= ~MUSB_POWER_SOFTCONN;
}
+#endif
+
void dcd_sof_enable(uint8_t rhport, bool en)
{
(void) rhport;
@@ -744,6 +790,7 @@ bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const * ep_desc) {
pipe->buf = NULL;
pipe->length = 0;
pipe->remaining = 0;
+ pipe->mps = (uint16_t) mps;
pipe->armed = false;
musb_regs_t* musb = MUSB_REGS(rhport);
@@ -751,7 +798,7 @@ bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const * ep_desc) {
const uint8_t is_rx = (1 - epdir);
musb_ep_maxp_csr_t* maxp_csr = &ep_csr->maxp_csr[is_rx];
- maxp_csr->maxp = mps;
+ maxp_csr->maxp = musb_mps_to_maxp((uint16_t) mps);
maxp_csr->csrh = 0;
#if MUSB_CFG_SHARED_FIFO
if (epdir) {
@@ -762,7 +809,7 @@ bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const * ep_desc) {
hwfifo_flush(musb, epn, is_rx, true);
TU_ASSERT(hwfifo_config(musb, epn, is_rx, mps, ep_desc->bmAttributes.xfer == TUSB_XFER_BULK));
- musb->intren_ep[is_rx] |= TU_BIT(epn);
+ musb->intren_ep[is_rx ^ MUSB_INTR_EP_TX_RX_SWAP] |= TU_BIT(epn);
return true;
}
@@ -773,8 +820,11 @@ bool dcd_edpt_iso_alloc(uint8_t rhport, uint8_t ep_addr, uint16_t largest_packet
musb_regs_t* musb = MUSB_REGS(rhport);
musb_ep_csr_t* ep_csr = get_ep_csr(musb, epn);
const uint8_t is_rx = 1 - dir_in;
+ pipe_state_t *pipe = pipe_get(epn, dir_in);
+ pipe->mps = largest_packet_size;
ep_csr->maxp_csr[is_rx].csrh = 0;
- return hwfifo_config(musb, epn, is_rx, largest_packet_size, true);
+ TU_ASSERT(hwfifo_config(musb, epn, is_rx, largest_packet_size, true));
+ return true;
}
bool dcd_edpt_iso_activate(uint8_t rhport, tusb_desc_endpoint_t const *ep_desc ) {
@@ -790,6 +840,7 @@ bool dcd_edpt_iso_activate(uint8_t rhport, tusb_desc_endpoint_t const *ep_desc )
pipe->buf = NULL;
pipe->length = 0;
pipe->remaining = 0;
+ pipe->mps = (uint16_t) mps;
pipe->armed = false;
musb_regs_t* musb = MUSB_REGS(rhport);
@@ -797,7 +848,7 @@ bool dcd_edpt_iso_activate(uint8_t rhport, tusb_desc_endpoint_t const *ep_desc )
const uint8_t is_rx = 1 - dir_in;
musb_ep_maxp_csr_t* maxp_csr = &ep_csr->maxp_csr[is_rx];
- maxp_csr->maxp = mps;
+ maxp_csr->maxp = musb_mps_to_maxp((uint16_t) mps);
maxp_csr->csrh |= MUSB_CSRH_ISO;
#if MUSB_CFG_SHARED_FIFO
if (dir_in) {
@@ -812,7 +863,7 @@ bool dcd_edpt_iso_activate(uint8_t rhport, tusb_desc_endpoint_t const *ep_desc )
musb->fifo_size[is_rx] = hwfifo_byte2size(mps) | MUSB_FIFOSZ_DOUBLE_PACKET;
#endif
- musb->intren_ep[is_rx] |= TU_BIT(epn);
+ musb->intren_ep[is_rx ^ MUSB_INTR_EP_TX_RX_SWAP] |= TU_BIT(epn);
if (ie) musb_dcd_int_enable(rhport);
@@ -833,7 +884,7 @@ void dcd_edpt_close_all(uint8_t rhport)
musb_ep_maxp_csr_t* maxp_csr = &ep_csr->maxp_csr[d];
hwfifo_flush(musb, i, d, true);
hwfifo_reset(musb, i, d);
- maxp_csr->maxp = 0;
+ maxp_csr->maxp = musb_mps_to_maxp(0);
maxp_csr->csrh = 0;
}
}
@@ -910,6 +961,10 @@ void dcd_edpt_stall(uint8_t rhport, uint8_t ep_addr) {
} else {
const tusb_dir_t ep_dir = tu_edpt_dir(ep_addr);
const uint8_t is_rx = (ep_dir == TUSB_DIR_OUT ? 1u : 0u);
+ // A halt aborts the transfer: flush staged FIFO packet(s) before stalling, else leftover TX data
+ // concatenates with the next transfer after un-halt -> host sees an oversized packet (babble).
+ // FLUSH must precede SEND_STALL, which clears the TXRDY that hwfifo_flush() gates on.
+ hwfifo_flush(musb_regs, epn, is_rx, false);
ep_csr->maxp_csr[is_rx].csrl = MUSB_CSRL_SEND_STALL(is_rx);
pipe_state_t* pipe = pipe_get(epn, ep_dir);
pipe->armed = false;
diff --git a/src/portable/mentor/musb/musb_max32.h b/src/portable/mentor/musb/musb_max32.h
index 628454216..9cf99126e 100644
--- a/src/portable/mentor/musb/musb_max32.h
+++ b/src/portable/mentor/musb/musb_max32.h
@@ -28,6 +28,7 @@ extern "C" {
#define MUSB_CFG_SHARED_FIFO 1 // shared FIFO for TX and RX endpoints
#define MUSB_CFG_DYNAMIC_FIFO 0 // dynamic EP FIFO sizing
+#define MUSB_INTR_EP_TX_RX_SWAP 0
static const uintptr_t MUSB_BASES[] = { MXC_BASE_USBHS };
diff --git a/src/portable/mentor/musb/musb_py32.h b/src/portable/mentor/musb/musb_py32.h
new file mode 100644
index 000000000..68d58ccb8
--- /dev/null
+++ b/src/portable/mentor/musb/musb_py32.h
@@ -0,0 +1,79 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2026, Ha Thach (tinyusb.org)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * This file is part of the TinyUSB stack.
+ */
+
+#ifndef TUSB_MUSB_PY32_H_
+#define TUSB_MUSB_PY32_H_
+
+#include "py32f0xx.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// PY32 does not expose generic MUSB shared FIFO allocation registers; this
+// selects the existing TX_MODE path required for IN endpoints.
+#define MUSB_CFG_SHARED_FIFO 1
+#define MUSB_CFG_DYNAMIC_FIFO 0
+#define MUSB_INTR_EP_TX_RX_SWAP 1
+
+static const uintptr_t MUSB_BASES[] = { USBD_BASE };
+static const IRQn_Type musb_irqs[] = { USB_IRQn };
+
+TU_ATTR_ALWAYS_INLINE static inline void musb_dcd_phy_init(uint8_t rhport) {
+ musb_regs_t* musb_regs = MUSB_REGS(rhport);
+
+ musb_regs->index = 0;
+ musb_regs->faddr = 0;
+ musb_regs->intr_usben = MUSB_IE_RESET | MUSB_IE_RESUME | MUSB_IE_SUSPND;
+ musb_regs->intr_txen = TU_BIT(0);
+ musb_regs->intr_rxen = 0;
+}
+
+TU_ATTR_ALWAYS_INLINE static inline void musb_dcd_int_enable(uint8_t rhport) {
+ NVIC_EnableIRQ(musb_irqs[rhport]);
+}
+
+TU_ATTR_ALWAYS_INLINE static inline void musb_dcd_int_disable(uint8_t rhport) {
+ NVIC_DisableIRQ(musb_irqs[rhport]);
+}
+
+TU_ATTR_ALWAYS_INLINE static inline void musb_dcd_int_clear(uint8_t rhport) {
+ NVIC_ClearPendingIRQ(musb_irqs[rhport]);
+}
+
+TU_ATTR_ALWAYS_INLINE static inline unsigned musb_dcd_get_int_enable(uint8_t rhport) {
+ return NVIC_GetEnableIRQ(musb_irqs[rhport]);
+}
+
+TU_ATTR_ALWAYS_INLINE static inline void musb_dcd_int_handler_enter(uint8_t rhport) {
+ (void) rhport;
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/portable/mentor/musb/musb_ti.h b/src/portable/mentor/musb/musb_ti.h
index db2e237d2..1cf26b1cb 100644
--- a/src/portable/mentor/musb/musb_ti.h
+++ b/src/portable/mentor/musb/musb_ti.h
@@ -30,6 +30,7 @@
#define MUSB_CFG_SHARED_FIFO 0
#define MUSB_CFG_DYNAMIC_FIFO 1
#define MUSB_CFG_DYNAMIC_FIFO_SIZE 4096
+#define MUSB_INTR_EP_TX_RX_SWAP 0
static const uintptr_t MUSB_BASES[] = { USB0_BASE };
diff --git a/src/portable/mentor/musb/musb_type.h b/src/portable/mentor/musb/musb_type.h
index 3c079cb25..1e8a761c0 100644
--- a/src/portable/mentor/musb/musb_type.h
+++ b/src/portable/mentor/musb/musb_type.h
@@ -64,6 +64,75 @@
#define __R volatile const
#endif
+#if defined(TUP_USBIP_MUSB_PY32)
+
+typedef struct TU_ATTR_PACKED {
+ __IO uint8_t csrh; // 0x04, 0x08: CSRH
+ __IO uint8_t csrl; // 0x05, 0x09: CSRL
+ __IO uint8_t maxp; // 0x06, 0x0A: MAXP
+ __I uint8_t reserved;
+} musb_ep_maxp_csr_t;
+
+TU_VERIFY_STATIC(sizeof(musb_ep_maxp_csr_t) == 4, "size is not correct");
+
+typedef struct TU_ATTR_PACKED {
+ __IO uint8_t csr0l; // 0x00: CSR0
+ __IO uint8_t count0; // 0x01: COUNT0
+ __I uint8_t reserved_0x02[2];
+
+ union {
+ struct {
+ __IO uint8_t tx_csrh; // 0x04: TX CSRH
+ __IO uint8_t tx_csrl; // 0x05: TX CSRL
+ __IO uint8_t tx_maxp; // 0x06: TX MAXP
+ __I uint8_t reserved_0x07;
+
+ __IO uint8_t rx_csrh; // 0x08: RX CSRH
+ __IO uint8_t rx_csrl; // 0x09: RX CSRL
+ __IO uint8_t rx_maxp; // 0x0A: RX MAXP
+ __I uint8_t reserved_0x0b;
+ };
+
+ musb_ep_maxp_csr_t maxp_csr[2];
+ };
+
+ __IO uint16_t rx_count; // 0x0C: RX COUNT
+ __I uint8_t reserved_0x0e[2];
+} musb_ep_csr_t;
+
+TU_VERIFY_STATIC(sizeof(musb_ep_csr_t) == 16, "size is not correct");
+
+typedef struct TU_ATTR_PACKED {
+ __IO uint8_t faddr; // 0x00: FADDR
+ __IO uint8_t power; // 0x01: POWER
+ __I uint8_t reserved_0x02[2];
+
+ __IO uint8_t intr_usb; // 0x04: INTRUSB
+ __IO uint8_t intr_rx; // 0x05: INTRRX
+ __IO uint8_t intr_tx; // 0x06: INTRTX
+ __I uint8_t reserved_0x07;
+
+ __IO uint8_t intr_usben; // 0x08: INTRUSBEN
+ union {
+ struct {
+ __IO uint8_t intr_rxen; // 0x09: INTRRXEN
+ __IO uint8_t intr_txen; // 0x0A: INTRTXEN
+ };
+
+ __IO uint8_t intren_ep[2]; // 0x09-0x0A: RX, TX
+ };
+ __I uint8_t reserved_0x0b;
+
+ __IO uint16_t frame; // 0x0C: FRAME
+ __IO uint8_t index; // 0x0E: INDEX
+ __I uint8_t reserved_0x0f;
+
+ musb_ep_csr_t indexed_csr; // 0x10-0x1F: Indexed CSR
+ __IO uint32_t fifo[16]; // 0x20-0x5F: FIFO 0-15
+} musb_regs_t;
+
+#else
+
typedef struct TU_ATTR_PACKED {
__IO uint16_t maxp; // 0x00, 0x04: MAXP
__IO uint8_t csrl; // 0x02, 0x06: CSRL
@@ -277,6 +346,8 @@ typedef struct {
TU_VERIFY_STATIC(sizeof(musb_regs_t) == 0x350, "size is not correct");
+#endif
+
//--------------------------------------------------------------------+
// Helper
//--------------------------------------------------------------------+
diff --git a/src/portable/microchip/samd/dcd_samd.c b/src/portable/microchip/samd/dcd_samd.c
index 32ddd3422..54ef34c8e 100644
--- a/src/portable/microchip/samd/dcd_samd.c
+++ b/src/portable/microchip/samd/dcd_samd.c
@@ -229,15 +229,49 @@ bool dcd_edpt_open (uint8_t rhport, tusb_desc_endpoint_t const * desc_edpt)
bool dcd_edpt_iso_alloc(uint8_t rhport, uint8_t ep_addr, uint16_t largest_packet_size) {
(void) rhport;
- (void) ep_addr;
- (void)largest_packet_size;
- return false;
+ uint8_t const epnum = tu_edpt_number(ep_addr);
+ uint8_t const dir = tu_edpt_dir(ep_addr);
+
+ // Reserve the endpoint bank with the largest packet size (persists across altsettings). The
+ // buffer address/count are filled per-transfer in dcd_edpt_xfer; only the SIZE bucket is fixed.
+ UsbDeviceDescBank* bank = &sram_registers[epnum][dir];
+ uint32_t size_value = 0;
+ while (size_value < 7) {
+ if (1 << (size_value + 3) >= largest_packet_size) {
+ break;
+ }
+ size_value++;
+ }
+ if ( size_value == 7 && largest_packet_size > 1023 ) return false;
+
+ bank->PCKSIZE.bit.SIZE = size_value;
+ return true;
}
bool dcd_edpt_iso_activate(uint8_t rhport, const tusb_desc_endpoint_t *desc_ep) {
(void)rhport;
- (void)desc_ep;
- return false;
+ uint8_t const epnum = tu_edpt_number(desc_ep->bEndpointAddress);
+ uint8_t const dir = tu_edpt_dir(desc_ep->bEndpointAddress);
+
+ // Configure and enable the ISO endpoint on altsetting selection (bank SIZE already reserved by
+ // dcd_edpt_iso_alloc). Mirrors the per-direction setup in dcd_edpt_open(), plus a bank scrub:
+ // under ISO_ALLOC the EP is never disabled on alt0 (usbd_edpt_close is a no-op), so the bank-ready
+ // state from the previous streaming session survives into re-activation. Leave the EP un-armed so
+ // a stale bank can't move a packet before dcd_edpt_xfer re-arms it (a leftover BK1RDY with a stale
+ // BYTE_COUNT would otherwise babble on the first IN token after re-selecting alt1).
+ UsbDeviceEndpoint* ep = &USB->DEVICE.DeviceEndpoint[epnum];
+ if ( dir == TUSB_DIR_OUT ) {
+ ep->EPCFG.bit.EPTYPE0 = desc_ep->bmAttributes.xfer + 1;
+ ep->EPSTATUSCLR.reg = USB_DEVICE_EPSTATUSCLR_STALLRQ0 | USB_DEVICE_EPSTATUSCLR_DTGLOUT;
+ ep->EPSTATUSSET.reg = USB_DEVICE_EPSTATUSSET_BK0RDY; // OUT: not ready to receive until armed
+ ep->EPINTENSET.bit.TRCPT0 = true;
+ } else {
+ ep->EPCFG.bit.EPTYPE1 = desc_ep->bmAttributes.xfer + 1;
+ ep->EPSTATUSCLR.reg = USB_DEVICE_EPSTATUSCLR_STALLRQ1 | USB_DEVICE_EPSTATUSCLR_DTGLIN |
+ USB_DEVICE_EPSTATUSCLR_BK1RDY; // IN: clear stale "loaded" bank
+ ep->EPINTENSET.bit.TRCPT1 = true;
+ }
+ return true;
}
void dcd_edpt_close_all (uint8_t rhport)
diff --git a/src/portable/nordic/nrf5x/dcd_nrf5x.c b/src/portable/nordic/nrf5x/dcd_nrf5x.c
index dc85ff78e..5170e0645 100644
--- a/src/portable/nordic/nrf5x/dcd_nrf5x.c
+++ b/src/portable/nordic/nrf5x/dcd_nrf5x.c
@@ -122,8 +122,22 @@ TU_ATTR_ALWAYS_INLINE static inline bool is_in_isr(void) {
return (SCB->ICSR & SCB_ICSR_VECTACTIVE_Msk) ? true : false;
}
+// Errata 199 "USBD cannot receive tasks during DMA": while an EasyDMA transfer is in progress the
+// controller may drop an incoming SETUP/IN/OUT token (lost event -> stuck EP0, esp. under rapid
+// back-to-back control transfers). The workaround latches an undocumented "DMA in progress" test
+// register (0x40027C1C) so tokens are held instead. Gated on the anomaly being present (all
+// nRF52840 revisions; absent on other nRF52 parts). Mirrors nrfx usbd_dma_pending_set/clear().
+#define NRF_USBD_ERRATA_199_REG (*((volatile uint32_t*) 0x40027C1CUL))
+
// helper to start DMA
static void start_dma(volatile uint32_t* reg_startep) {
+ // EP0STATUS / EP0RCVOUT take the EasyDMA slot but do not transfer data, so no ERRATA-199 latch.
+ const bool no_dma = (reg_startep == &NRF_USBD->TASKS_EP0STATUS) || (reg_startep == &NRF_USBD->TASKS_EP0RCVOUT);
+
+ if (!no_dma && nrf52_errata_199()) {
+ NRF_USBD_ERRATA_199_REG = 0x00000082UL;
+ }
+
(*reg_startep) = 1;
__ISB();
__DSB();
@@ -131,7 +145,7 @@ static void start_dma(volatile uint32_t* reg_startep) {
// TASKS_EP0STATUS, TASKS_EP0RCVOUT seem to need EasyDMA to be available
// However these don't trigger any DMA transfer and got ENDED event subsequently
// Therefore dma_pending is corrected right away
- if ((reg_startep == &NRF_USBD->TASKS_EP0STATUS) || (reg_startep == &NRF_USBD->TASKS_EP0RCVOUT)) {
+ if (no_dma) {
atomic_flag_clear(&_dcd.dma_running);
}
}
@@ -146,6 +160,10 @@ static void edpt_dma_start(volatile uint32_t* reg_startep) {
// DMA is complete
static void edpt_dma_end(void) {
+ // Clear the ERRATA-199 "DMA in progress" latch set in start_dma().
+ if (nrf52_errata_199()) {
+ NRF_USBD_ERRATA_199_REG = 0x00000000UL;
+ }
atomic_flag_clear(&_dcd.dma_running);
}
@@ -377,58 +395,52 @@ void dcd_edpt_close_all(uint8_t rhport) {
dcd_int_enable(rhport);
}
-void dcd_edpt_close(uint8_t rhport, uint8_t ep_addr) {
- (void) rhport;
+bool dcd_edpt_iso_alloc(uint8_t rhport, uint8_t ep_addr, uint16_t largest_packet_size) {
+ (void)rhport;
+ (void)largest_packet_size;
+ // nRF ISO endpoints are hardware-fixed to EP8 and use EasyDMA, so there is no packet buffer to
+ // pre-allocate here; the endpoint is enabled on dcd_edpt_iso_activate().
+ TU_ASSERT(tu_edpt_number(ep_addr) == EP_ISO_NUM);
+ return true;
+}
+bool dcd_edpt_iso_activate(uint8_t rhport, const tusb_desc_endpoint_t *desc_ep) {
+ (void)rhport;
+ uint8_t const ep_addr = desc_ep->bEndpointAddress;
uint8_t const epnum = tu_edpt_number(ep_addr);
uint8_t const dir = tu_edpt_dir(ep_addr);
+ TU_ASSERT(epnum == EP_ISO_NUM);
- if (epnum != EP_ISO_NUM) {
- // CBI
- if (dir == TUSB_DIR_OUT) {
- NRF_USBD->INTENCLR = TU_BIT(USBD_INTEN_ENDEPOUT0_Pos + epnum);
- NRF_USBD->EPOUTEN &= ~TU_BIT(epnum);
- } else {
- NRF_USBD->INTENCLR = TU_BIT(USBD_INTEN_ENDEPIN0_Pos + epnum);
- NRF_USBD->EPINEN &= ~TU_BIT(epnum);
- }
+ // A transfer armed before SET_INTERFACE survives to here (this port has no dcd close); usbd has
+ // just reset the endpoint's claim/busy state, so drop the stale descriptor too — otherwise the
+ // class's next arm trips TU_ASSERT(!xfer->started) in dcd_edpt_xfer().
+ _dcd.xfer[epnum][dir].started = false;
+ _dcd.xfer[epnum][dir].data_received = false;
+ _dcd.xfer[epnum][dir].iso_in_transfer_ready = false;
+
+ _dcd.xfer[epnum][dir].mps = tu_edpt_packet_size(desc_ep);
+
+ if (dir == TUSB_DIR_OUT) {
+ // SPLIT ISO buffer when the ISO IN endpoint is already active.
+ if (_dcd.xfer[EP_ISO_NUM][TUSB_DIR_IN].mps) NRF_USBD->ISOSPLIT = USBD_ISOSPLIT_SPLIT_HalfIN;
+ NRF_USBD->EVENTS_ENDISOOUT = 0;
+ if ((NRF_USBD->INTEN & USBD_INTEN_SOF_Msk) == 0) NRF_USBD->EVENTS_SOF = 0;
+ NRF_USBD->INTENSET = USBD_INTENSET_ENDISOOUT_Msk | USBD_INTENSET_SOF_Msk;
+ NRF_USBD->EPOUTEN |= USBD_EPOUTEN_ISOOUT_Msk;
} else {
- _dcd.xfer[EP_ISO_NUM][dir].mps = 0;
- // ISO
- if (dir == TUSB_DIR_OUT) {
- NRF_USBD->INTENCLR = USBD_INTENCLR_ENDISOOUT_Msk;
- NRF_USBD->EPOUTEN &= ~USBD_EPOUTEN_ISOOUT_Msk;
- NRF_USBD->EVENTS_ENDISOOUT = 0;
- } else {
- NRF_USBD->INTENCLR = USBD_INTENCLR_ENDISOIN_Msk;
- NRF_USBD->EPINEN &= ~USBD_EPINEN_ISOIN_Msk;
- }
- // One of the ISO endpoints closed, no need to split buffers any more.
- NRF_USBD->ISOSPLIT = USBD_ISOSPLIT_SPLIT_OneDir;
- // When both ISO endpoint are close there is no need for SOF any more.
- if (_dcd.xfer[EP_ISO_NUM][TUSB_DIR_IN].mps + _dcd.xfer[EP_ISO_NUM][TUSB_DIR_OUT].mps == 0)
- NRF_USBD->INTENCLR = USBD_INTENCLR_SOF_Msk;
+ NRF_USBD->EVENTS_ENDISOIN = 0;
+ // SPLIT ISO buffer when the ISO OUT endpoint is already active.
+ if (_dcd.xfer[EP_ISO_NUM][TUSB_DIR_OUT].mps) NRF_USBD->ISOSPLIT = USBD_ISOSPLIT_SPLIT_HalfIN;
+ if ((NRF_USBD->INTEN & USBD_INTEN_SOF_Msk) == 0) NRF_USBD->EVENTS_SOF = 0;
+ NRF_USBD->INTENSET = USBD_INTENSET_ENDISOIN_Msk | USBD_INTENSET_SOF_Msk;
+ NRF_USBD->EPINEN |= USBD_EPINEN_ISOIN_Msk;
}
- _dcd.xfer[epnum][dir].started = false;
+
__ISB();
__DSB();
+ return true;
}
-#if 0
-bool dcd_edpt_iso_alloc(uint8_t rhport, uint8_t ep_addr, uint16_t largest_packet_size) {
- (void)rhport;
- (void)ep_addr;
- (void)largest_packet_size;
- return false;
-}
-
-bool dcd_edpt_iso_activate(uint8_t rhport, const tusb_desc_endpoint_t *desc_ep) {
- (void)rhport;
- (void)desc_ep;
- return false;
-}
-#endif
-
bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t* buffer, uint16_t total_bytes, bool is_isr) {
(void) rhport;
(void) is_isr;
diff --git a/src/portable/nxp/khci/dcd_khci.c b/src/portable/nxp/khci/dcd_khci.c
deleted file mode 100644
index 9f61bd236..000000000
--- a/src/portable/nxp/khci/dcd_khci.c
+++ /dev/null
@@ -1,560 +0,0 @@
-/*
- * SPDX-FileCopyrightText: Copyright (c) 2020 Koji Kitayama
- * SPDX-FileCopyrightText: Copyright (c) 2020 Ha Thach (tinyusb.org)
- * SPDX-License-Identifier: MIT
- *
- * This file is part of the TinyUSB stack.
- */
-
-#include "tusb_option.h"
-
-#if CFG_TUD_ENABLED && defined(TUP_USBIP_CHIPIDEA_FS)
-
-#ifdef TUP_USBIP_CHIPIDEA_FS_KINETIS
- #include "fsl_device_registers.h"
- #define KHCI USB0
-#else
- #error "MCU is not supported"
-#endif
-
-#include "device/dcd.h"
-
-//--------------------------------------------------------------------+
-// MACRO TYPEDEF CONSTANT ENUM DECLARATION
-//--------------------------------------------------------------------+
-
-enum {
- TOK_PID_OUT = 0x1u,
- TOK_PID_IN = 0x9u,
- TOK_PID_SETUP = 0xDu,
-};
-
-typedef struct TU_ATTR_PACKED
-{
- union {
- uint32_t head;
- struct {
- union {
- struct {
- uint16_t : 2;
- __IO uint16_t tok_pid : 4;
- uint16_t data : 1;
- __IO uint16_t own : 1;
- uint16_t : 8;
- };
- struct {
- uint16_t : 2;
- uint16_t bdt_stall : 1;
- uint16_t dts : 1;
- uint16_t ninc : 1;
- uint16_t keep : 1;
- uint16_t : 10;
- };
- };
- __IO uint16_t bc : 10;
- uint16_t : 6;
- };
- };
- uint8_t *addr;
-}buffer_descriptor_t;
-
-TU_VERIFY_STATIC( sizeof(buffer_descriptor_t) == 8, "size is not correct" );
-
-typedef struct TU_ATTR_PACKED
-{
- union {
- uint32_t state;
- struct {
- uint32_t max_packet_size :11;
- uint32_t : 5;
- uint32_t odd : 1;
- uint32_t :15;
- };
- };
- uint16_t length;
- uint16_t remaining;
-}endpoint_state_t;
-
-TU_VERIFY_STATIC( sizeof(endpoint_state_t) == 8, "size is not correct" );
-
-typedef struct
-{
- union {
- /* [#EP][OUT,IN][EVEN,ODD] */
- buffer_descriptor_t bdt[16][2][2];
- uint16_t bda[512];
- };
- TU_ATTR_ALIGNED(4) union {
- endpoint_state_t endpoint[16][2];
- endpoint_state_t endpoint_unified[16 * 2];
- };
- uint8_t setup_packet[8];
- uint8_t addr;
-}dcd_data_t;
-
-//--------------------------------------------------------------------+
-// INTERNAL OBJECT & FUNCTION DECLARATION
-//--------------------------------------------------------------------+
-// BDT(Buffer Descriptor Table) must be 256-byte aligned
-CFG_TUD_MEM_SECTION TU_ATTR_ALIGNED(512) static dcd_data_t _dcd;
-
-TU_VERIFY_STATIC( sizeof(_dcd.bdt) == 512, "size is not correct" );
-
-static void prepare_next_setup_packet(uint8_t rhport)
-{
- const unsigned out_odd = _dcd.endpoint[0][0].odd;
- const unsigned in_odd = _dcd.endpoint[0][1].odd;
- TU_ASSERT(0 == _dcd.bdt[0][0][out_odd].own, );
-
- _dcd.bdt[0][0][out_odd].data = 0;
- _dcd.bdt[0][0][out_odd ^ 1].data = 1;
- _dcd.bdt[0][1][in_odd].data = 1;
- _dcd.bdt[0][1][in_odd ^ 1].data = 0;
- dcd_edpt_xfer(rhport, tu_edpt_addr(0, TUSB_DIR_OUT),
- _dcd.setup_packet, sizeof(_dcd.setup_packet), false);
-}
-
-static void process_stall(uint8_t rhport)
-{
- for (int i = 0; i < 16; ++i) {
- unsigned const endpt = KHCI->ENDPOINT[i].ENDPT;
-
- if (endpt & USB_ENDPT_EPSTALL_MASK) {
- // prepare next setup if endpoint0
- if ( i == 0 ) prepare_next_setup_packet(rhport);
-
- // clear stall bit
- KHCI->ENDPOINT[i].ENDPT = endpt & ~USB_ENDPT_EPSTALL_MASK;
- }
- }
-}
-
-static void process_tokdne(uint8_t rhport)
-{
- const unsigned s = KHCI->STAT;
- KHCI->ISTAT = USB_ISTAT_TOKDNE_MASK; /* fetch the next token if received */
-
- uint8_t const epnum = (s >> USB_STAT_ENDP_SHIFT);
- uint8_t const dir = (s & USB_STAT_TX_MASK) >> USB_STAT_TX_SHIFT;
- unsigned const odd = (s & USB_STAT_ODD_MASK) ? 1 : 0;
-
- buffer_descriptor_t *bd = (buffer_descriptor_t *)&_dcd.bda[s];
- endpoint_state_t *ep = &_dcd.endpoint_unified[s >> 3];
-
- /* fetch pid before discarded by the next steps */
- const unsigned pid = bd->tok_pid;
-
- /* reset values for a next transfer */
- bd->bdt_stall = 0;
- bd->dts = 1;
- bd->ninc = 0;
- bd->keep = 0;
- /* update the odd variable to prepare for the next transfer */
- ep->odd = odd ^ 1;
- if (pid == TOK_PID_SETUP) {
- dcd_event_setup_received(rhport, bd->addr, true);
- KHCI->CTL &= ~USB_CTL_TXSUSPENDTOKENBUSY_MASK;
- return;
- }
-
- const unsigned bc = bd->bc;
- const unsigned remaining = ep->remaining - bc;
- if (remaining && bc == ep->max_packet_size) {
- /* continue the transferring consecutive data */
- ep->remaining = remaining;
- const int next_remaining = remaining - ep->max_packet_size;
- if (next_remaining > 0) {
- /* prepare to the after next transfer */
- bd->addr += ep->max_packet_size * 2;
- bd->bc = next_remaining > ep->max_packet_size ? ep->max_packet_size: next_remaining;
- __DSB();
- bd->own = 1; /* the own bit must set after addr */
- }
- return;
- }
- const unsigned length = ep->length;
- dcd_event_xfer_complete(rhport,
- tu_edpt_addr(epnum, dir),
- length - remaining, XFER_RESULT_SUCCESS, true);
- if (0 == epnum && 0 == length) {
- /* After completion a ZLP of control transfer,
- * it prepares for the next steup transfer. */
- if (_dcd.addr) {
- /* When the transfer was the SetAddress,
- * the device address should be updated here. */
- KHCI->ADDR = _dcd.addr;
- _dcd.addr = 0;
- }
- prepare_next_setup_packet(rhport);
- }
-}
-
-static void process_bus_reset(uint8_t rhport)
-{
- KHCI->USBCTRL &= ~USB_USBCTRL_SUSP_MASK;
- KHCI->CTL |= USB_CTL_ODDRST_MASK;
- KHCI->ADDR = 0;
- KHCI->INTEN = USB_INTEN_USBRSTEN_MASK | USB_INTEN_TOKDNEEN_MASK | USB_INTEN_SLEEPEN_MASK |
- USB_INTEN_ERROREN_MASK | USB_INTEN_STALLEN_MASK;
-
- KHCI->ENDPOINT[0].ENDPT = USB_ENDPT_EPHSHK_MASK | USB_ENDPT_EPRXEN_MASK | USB_ENDPT_EPTXEN_MASK;
- for (unsigned i = 1; i < 16; ++i) {
- KHCI->ENDPOINT[i].ENDPT = 0;
- }
- buffer_descriptor_t *bd = _dcd.bdt[0][0];
- for (unsigned i = 0; i < sizeof(_dcd.bdt)/sizeof(*bd); ++i, ++bd) {
- bd->head = 0;
- }
- const endpoint_state_t ep0 = {
- .max_packet_size = CFG_TUD_ENDPOINT0_SIZE,
- .odd = 0,
- .length = 0,
- .remaining = 0,
- };
- _dcd.endpoint[0][0] = ep0;
- _dcd.endpoint[0][1] = ep0;
- tu_memclr(_dcd.endpoint[1], sizeof(_dcd.endpoint) - sizeof(_dcd.endpoint[0]));
- _dcd.addr = 0;
- prepare_next_setup_packet(rhport);
- KHCI->CTL &= ~USB_CTL_ODDRST_MASK;
- dcd_event_bus_reset(rhport, TUSB_SPEED_FULL, true);
-}
-
-static void process_bus_sleep(uint8_t rhport)
-{
- // Enable resume & disable suspend interrupt
- const unsigned inten = KHCI->INTEN;
-
- KHCI->INTEN = (inten & ~USB_INTEN_SLEEPEN_MASK) | USB_INTEN_RESUMEEN_MASK;
- KHCI->USBTRC0 |= USB_USBTRC0_USBRESMEN_MASK;
- KHCI->USBCTRL |= USB_USBCTRL_SUSP_MASK;
-
- dcd_event_bus_signal(rhport, DCD_EVENT_SUSPEND, true);
-}
-
-static void process_bus_resume(uint8_t rhport)
-{
- // Enable suspend & disable resume interrupt
- const unsigned inten = KHCI->INTEN;
-
- KHCI->USBCTRL &= ~USB_USBCTRL_SUSP_MASK; // will also clear USB_USBTRC0_USB_RESUME_INT_MASK
- KHCI->USBTRC0 &= ~USB_USBTRC0_USBRESMEN_MASK;
- KHCI->INTEN = (inten & ~USB_INTEN_RESUMEEN_MASK) | USB_INTEN_SLEEPEN_MASK;
-
- dcd_event_bus_signal(rhport, DCD_EVENT_RESUME, true);
-}
-
-/*------------------------------------------------------------------*/
-/* Device API
- *------------------------------------------------------------------*/
-bool dcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) {
- (void) rhport;
- (void) rh_init;
-
- // save crystal-less setting (if available)
- #if defined(FSL_FEATURE_USB_KHCI_IRC48M_MODULE_CLOCK_ENABLED) && FSL_FEATURE_USB_KHCI_IRC48M_MODULE_CLOCK_ENABLED == 1
- uint32_t clk_recover_irc_en = KHCI->CLK_RECOVER_IRC_EN;
- uint32_t clk_recover_ctrl = KHCI->CLK_RECOVER_CTRL;
- #endif
-
- KHCI->USBTRC0 |= USB_USBTRC0_USBRESET_MASK;
- while (KHCI->USBTRC0 & USB_USBTRC0_USBRESET_MASK);
-
- // restore crystal-less setting (if available)
- #if defined(FSL_FEATURE_USB_KHCI_IRC48M_MODULE_CLOCK_ENABLED) && FSL_FEATURE_USB_KHCI_IRC48M_MODULE_CLOCK_ENABLED == 1
- KHCI->CLK_RECOVER_IRC_EN = clk_recover_irc_en;
- KHCI->CLK_RECOVER_CTRL |= clk_recover_ctrl;
- #endif
-
- tu_memclr(&_dcd, sizeof(_dcd));
- KHCI->USBTRC0 |= TU_BIT(6); /* software must set this bit to 1 */
- KHCI->BDTPAGE1 = (uint8_t)((uintptr_t)_dcd.bdt >> 8);
- KHCI->BDTPAGE2 = (uint8_t)((uintptr_t)_dcd.bdt >> 16);
- KHCI->BDTPAGE3 = (uint8_t)((uintptr_t)_dcd.bdt >> 24);
-
- KHCI->INTEN = USB_INTEN_USBRSTEN_MASK;
-
- dcd_connect(rhport);
- NVIC_ClearPendingIRQ(USB0_IRQn);
-
- return true;
-}
-
-void dcd_int_enable(uint8_t rhport)
-{
- (void) rhport;
- NVIC_EnableIRQ(USB0_IRQn);
-}
-
-void dcd_int_disable(uint8_t rhport)
-{
- (void) rhport;
- NVIC_DisableIRQ(USB0_IRQn);
-}
-
-void dcd_set_address(uint8_t rhport, uint8_t dev_addr)
-{
- _dcd.addr = dev_addr & 0x7F;
- /* Response with status first before changing device address */
- dcd_edpt_xfer(rhport, tu_edpt_addr(0, TUSB_DIR_IN), NULL, 0, false);
-}
-
-void dcd_remote_wakeup(uint8_t rhport)
-{
- (void) rhport;
-
- KHCI->CTL |= USB_CTL_RESUME_MASK;
-
- unsigned cnt = SystemCoreClock / 1000;
- while (cnt--) __NOP();
-
- KHCI->CTL &= ~USB_CTL_RESUME_MASK;
-}
-
-void dcd_connect(uint8_t rhport)
-{
- (void) rhport;
- KHCI->USBCTRL = 0;
- KHCI->CONTROL |= USB_CONTROL_DPPULLUPNONOTG_MASK;
- KHCI->CTL |= USB_CTL_USBENSOFEN_MASK;
-}
-
-void dcd_disconnect(uint8_t rhport)
-{
- (void) rhport;
- KHCI->CTL = 0;
- KHCI->CONTROL &= ~USB_CONTROL_DPPULLUPNONOTG_MASK;
-}
-
-void dcd_sof_enable(uint8_t rhport, bool en)
-{
- (void) rhport;
- (void) en;
-
- // TODO implement later
-}
-
-//--------------------------------------------------------------------+
-// Endpoint API
-//--------------------------------------------------------------------+
-static bool edpt_open(uint8_t rhport, uint8_t ep_addr, uint16_t max_packet_size, tusb_xfer_type_t xfer) {
- (void)rhport;
-
- const unsigned epn = tu_edpt_number(ep_addr);
- const unsigned dir = tu_edpt_dir(ep_addr);
- endpoint_state_t *ep = &_dcd.endpoint[epn][dir];
- const unsigned odd = ep->odd;
- buffer_descriptor_t *bd = _dcd.bdt[epn][dir];
-
- /* No support for control transfer */
- TU_ASSERT(epn && (xfer != TUSB_XFER_CONTROL));
-
- ep->max_packet_size = max_packet_size;
- unsigned val = USB_ENDPT_EPCTLDIS_MASK;
- val |= (xfer != TUSB_XFER_ISOCHRONOUS) ? USB_ENDPT_EPHSHK_MASK : 0;
- val |= dir ? USB_ENDPT_EPTXEN_MASK : USB_ENDPT_EPRXEN_MASK;
- KHCI->ENDPOINT[epn].ENDPT |= val;
-
- if (xfer != TUSB_XFER_ISOCHRONOUS) {
- bd[odd].dts = 1;
- bd[odd].data = 0;
- bd[odd ^ 1].dts = 1;
- bd[odd ^ 1].data = 1;
- }
-
- return true;
-}
-
-bool dcd_edpt_open(uint8_t rhport, const tusb_desc_endpoint_t *ep_desc) {
- return edpt_open(rhport, ep_desc->bEndpointAddress, tu_edpt_packet_size(ep_desc), ep_desc->bmAttributes.xfer);
-}
-
-bool dcd_edpt_iso_alloc(uint8_t rhport, uint8_t ep_addr, uint16_t largest_packet_size) {
- return edpt_open(rhport, ep_addr, largest_packet_size, TUSB_XFER_ISOCHRONOUS);
-}
-
-bool dcd_edpt_iso_activate(uint8_t rhport, const tusb_desc_endpoint_t *ep_desc) {
- const unsigned epn = tu_edpt_number(ep_desc->bEndpointAddress);
- const unsigned dir = tu_edpt_dir(ep_desc->bEndpointAddress);
- endpoint_state_t *ep = &_dcd.endpoint[epn][dir];
-
- dcd_int_disable(rhport);
- ep->max_packet_size = tu_edpt_packet_size(ep_desc);
- dcd_int_enable(rhport);
-
- return true;
-}
-
-void dcd_edpt_close_all(uint8_t rhport)
-{
- (void) rhport;
- const unsigned ie = NVIC_GetEnableIRQ(USB0_IRQn);
- NVIC_DisableIRQ(USB0_IRQn);
- for (unsigned i = 1; i < 16; ++i) {
- KHCI->ENDPOINT[i].ENDPT = 0;
- }
- if (ie) NVIC_EnableIRQ(USB0_IRQn);
- buffer_descriptor_t *bd = _dcd.bdt[1][0];
- for (unsigned i = 2; i < sizeof(_dcd.bdt)/sizeof(*bd); ++i, ++bd) {
- bd->head = 0;
- }
- endpoint_state_t *ep = &_dcd.endpoint[1][0];
- for (unsigned i = 2; i < sizeof(_dcd.endpoint)/sizeof(*ep); ++i, ++ep) {
- /* Clear except the odd */
- ep->max_packet_size = 0;
- ep->length = 0;
- ep->remaining = 0;
- }
-}
-
-bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t * buffer, uint16_t total_bytes, bool is_isr)
-{
- (void) rhport;
- (void) is_isr;
- const unsigned epn = tu_edpt_number(ep_addr);
- const unsigned dir = tu_edpt_dir(ep_addr);
- endpoint_state_t *ep = &_dcd.endpoint[epn][dir];
- buffer_descriptor_t *bd = &_dcd.bdt[epn][dir][ep->odd];
- TU_ASSERT(0 == bd->own);
-
- const unsigned ie = NVIC_GetEnableIRQ(USB0_IRQn);
- NVIC_DisableIRQ(USB0_IRQn);
-
- ep->length = total_bytes;
- ep->remaining = total_bytes;
-
- const unsigned mps = ep->max_packet_size;
- if (total_bytes > mps) {
- buffer_descriptor_t *next = ep->odd ? bd - 1: bd + 1;
- /* When total_bytes is greater than the max packet size,
- * it prepares to the next transfer to avoid NAK in advance. */
- next->bc = total_bytes >= 2 * mps ? mps: total_bytes - mps;
- next->addr = buffer + mps;
- next->own = 1;
- }
- bd->bc = total_bytes >= mps ? mps: total_bytes;
- bd->addr = buffer;
- __DSB();
- bd->own = 1; /* This bit must be set last */
-
- if (ie) NVIC_EnableIRQ(USB0_IRQn);
- return true;
-}
-
-void dcd_edpt_stall(uint8_t rhport, uint8_t ep_addr)
-{
- (void) rhport;
- const unsigned epn = tu_edpt_number(ep_addr);
-
- if (0 == epn) {
- KHCI->ENDPOINT[epn].ENDPT |= USB_ENDPT_EPSTALL_MASK;
- } else {
- const unsigned dir = tu_edpt_dir(ep_addr);
- const unsigned odd = _dcd.endpoint[epn][dir].odd;
- buffer_descriptor_t *bd = &_dcd.bdt[epn][dir][odd];
- TU_ASSERT(0 == bd->own,);
-
- const unsigned ie = NVIC_GetEnableIRQ(USB0_IRQn);
- NVIC_DisableIRQ(USB0_IRQn);
-
- bd->bdt_stall = 1;
- __DSB();
- bd->own = 1; /* This bit must be set last */
-
- if (ie) NVIC_EnableIRQ(USB0_IRQn);
- }
-}
-
-void dcd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr)
-{
- (void) rhport;
- const unsigned epn = tu_edpt_number(ep_addr);
- TU_VERIFY(epn,);
- const unsigned dir = tu_edpt_dir(ep_addr);
- const unsigned odd = _dcd.endpoint[epn][dir].odd;
- buffer_descriptor_t *bd = _dcd.bdt[epn][dir];
- TU_VERIFY(bd[odd].own,);
-
- const unsigned ie = NVIC_GetEnableIRQ(USB0_IRQn);
- NVIC_DisableIRQ(USB0_IRQn);
-
- bd[odd].own = 0;
- __DSB();
-
- // clear stall
- bd[odd].bdt_stall = 0;
-
- // Reset data toggle
- bd[odd ].data = 0;
- bd[odd ^ 1].data = 1;
-
- // We already cleared this in ISR, but just clear it here to be safe
- const unsigned endpt = KHCI->ENDPOINT[epn].ENDPT;
- if (endpt & USB_ENDPT_EPSTALL_MASK) {
- KHCI->ENDPOINT[epn].ENDPT = endpt & ~USB_ENDPT_EPSTALL_MASK;
- }
-
- if (ie) NVIC_EnableIRQ(USB0_IRQn);
-}
-
-//--------------------------------------------------------------------+
-// ISR
-//--------------------------------------------------------------------+
-void dcd_int_handler(uint8_t rhport)
-{
- uint32_t is = KHCI->ISTAT;
- uint32_t msk = KHCI->INTEN;
-
- // clear non-enabled interrupts
- KHCI->ISTAT = is & ~msk;
- is &= msk;
-
- if (is & USB_ISTAT_ERROR_MASK) {
- /* TODO: */
- uint32_t es = KHCI->ERRSTAT;
- KHCI->ERRSTAT = es;
- KHCI->ISTAT = is; /* discard any pending events */
- }
-
- if (is & USB_ISTAT_USBRST_MASK) {
- KHCI->ISTAT = is; /* discard any pending events */
- process_bus_reset(rhport);
- }
-
- if (is & USB_ISTAT_SLEEP_MASK) {
- // TU_LOG3("Suspend: "); TU_LOG2_HEX(is);
-
- // Note Host usually has extra delay after bus reset (without SOF), which could falsely
- // detected as Sleep event. Though usbd has debouncing logic so we are good
- KHCI->ISTAT = USB_ISTAT_SLEEP_MASK;
- process_bus_sleep(rhport);
- }
-
-#if 0 // ISTAT_RESUME never trigger, probably for host mode ?
- if (is & USB_ISTAT_RESUME_MASK) {
- // TU_LOG2("ISTAT Resume: "); TU_LOG2_HEX(is);
- KHCI->ISTAT = USB_ISTAT_RESUME_MASK;
- process_bus_resume(rhport);
- }
-#endif
-
- if (KHCI->USBTRC0 & USB_USBTRC0_USB_RESUME_INT_MASK) {
- // TU_LOG2("USBTRC0 Resume: "); TU_LOG2_HEX(is); TU_LOG2_HEX(KHCI->USBTRC0);
- process_bus_resume(rhport);
- }
-
- if (is & USB_ISTAT_SOFTOK_MASK) {
- KHCI->ISTAT = USB_ISTAT_SOFTOK_MASK;
- dcd_event_sof(rhport, tu_u16(KHCI->FRMNUMH, KHCI->FRMNUML), true);
- }
-
- if (is & USB_ISTAT_STALL_MASK) {
- KHCI->ISTAT = USB_ISTAT_STALL_MASK;
- process_stall(rhport);
- }
-
- if (is & USB_ISTAT_TOKDNE_MASK) {
- process_tokdne(rhport);
- }
-}
-#endif
diff --git a/src/portable/nxp/lpc17_40/dcd_lpc17_40.c b/src/portable/nxp/lpc17_40/dcd_lpc17_40.c
index 182710016..b577d0e9f 100644
--- a/src/portable/nxp/lpc17_40/dcd_lpc17_40.c
+++ b/src/portable/nxp/lpc17_40/dcd_lpc17_40.c
@@ -19,6 +19,12 @@
//--------------------------------------------------------------------+
#define DCD_ENDPOINT_MAX 32
+// The iso machinery (5th DD word + packet-size memory) costs USB RAM on every build;
+// compile it only when a class that can open an iso endpoint is enabled. Keep this in
+// sync with the classes that actually arm an iso endpoint: audio, video, BTH (voice),
+// and vendor (its optional CFG_TUD_VENDOR_EP_ISO_* endpoints, exercised by usbtest).
+#define DCD_ISO_ENABLED (CFG_TUD_AUDIO || CFG_TUD_VIDEO || CFG_TUD_VENDOR || CFG_TUD_BTH)
+
typedef struct TU_ATTR_ALIGNED(4)
{
//------------- Word 0 -------------//
@@ -48,11 +54,37 @@ typedef struct TU_ATTR_ALIGNED(4)
volatile uint16_t present_count; // For non-iso : The number of bytes transferred by the DMA engine
// For iso : number of packets
+#if DCD_ISO_ENABLED
//------------- Word 4 -------------//
- // uint32_t iso_packet_size_addr; // iso only, can be omitted for non-iso
+ volatile uint32_t iso_packet_size_addr; // iso only: pointer into iso packet-size memory,
+ // advanced by hardware after each packet
+#endif
}dma_desc_t;
-TU_VERIFY_STATIC( sizeof(dma_desc_t) == 16, "size is not correct"); // TODO not support ISO for now
+TU_VERIFY_STATIC( sizeof(dma_desc_t) == (DCD_ISO_ENABLED ? 20 : 16), "size is not correct");
+
+// Hardware fixes endpoint type by number: 3, 6, 9, 12 are the iso-capable ones.
+// Constant per ep_id (= 2*epnum + dir) — unlike dd->isochronous, which dcd_edpt_xfer
+// transiently zeroes while rebuilding the DD, this is safe to dispatch on from the ISR.
+// TU_ATTR_UNUSED: every caller is under #if DCD_ISO_ENABLED, so non-iso builds don't
+// reference it and clang -Wunused-function (fatal) would otherwise reject the build.
+TU_ATTR_UNUSED TU_ATTR_ALWAYS_INLINE static inline bool ep_id_is_iso(uint8_t ep_id) {
+ uint8_t const epnum = (uint8_t)(ep_id >> 1);
+ return (epnum % 3) == 0 && (epnum != 0) && (epnum != 15);
+}
+
+#if DCD_ISO_ENABLED
+// Isochronous packet-size memory (UM10562 12.15.6.3): one word per packet.
+// IN : software fills Packet_length (bits 15:0), 0 = ZLP
+// OUT: hardware writes Frame_number (31:17) | Packet_valid (16) | Packet_length (15:0)
+// Iso-capable endpoint numbers are 3, 6, 9, 12 -> 8 slots (x2 directions).
+// One packet moves per FRAME, so a deep queue only adds latency: 8 frames is plenty.
+#define ISO_MAX_PACKETS 8
+#define ISO_SLOT_COUNT 8
+TU_ATTR_ALWAYS_INLINE static inline uint8_t iso_slot(uint8_t ep_id) {
+ return (uint8_t)(((ep_id / 6) - 1) * 2 + (ep_id & 1)); // ep_id = 2*epnum + dir, epnum in {3,6,9,12}
+}
+#endif
typedef struct
{
@@ -66,11 +98,17 @@ typedef struct
{
uint8_t* out_buffer;
uint8_t out_bytes;
+ volatile bool out_queued; // an OUT xfer is queued; out_buffer may legitimately be NULL (status ZLP)
volatile bool out_received; // indicate if data is already received in endpoint
uint8_t in_bytes;
} control;
+#if DCD_ISO_ENABLED
+ // iso packet-size memory, must be DMA-reachable like the DDs
+ volatile uint32_t iso_psize[ISO_SLOT_COUNT][ISO_MAX_PACKETS];
+#endif
+
} dcd_data_t;
CFG_TUD_MEM_SECTION TU_ATTR_ALIGNED(128) static dcd_data_t _dcd;
@@ -79,6 +117,29 @@ CFG_TUD_MEM_SECTION TU_ATTR_ALIGNED(128) static dcd_data_t _dcd;
//--------------------------------------------------------------------+
// SIE Command
//--------------------------------------------------------------------+
+
+// The SIE command protocol (CmdCode + CCEMPTY/CDFULL handshake) and the
+// slave-mode Ctrl/RxData/TxData registers are shared between thread-mode API
+// calls and dcd_int_handler, and are not reentrant: an ISR preempting a
+// thread-mode SIE sequence consumes its handshake flags and overwrites
+// CmdCode (symptom: EP0 wedges/answers stale data right after SET_INTERFACE
+// stall/clear-stall bursts overlapping bulk EOT interrupts). Mask only the
+// USB interrupt around those sequences; safe to nest, including from the ISR.
+static inline bool usb_irq_lock(void)
+{
+ bool const enabled = NVIC_GetEnableIRQ(USB_IRQn) != 0;
+ if (enabled)
+ {
+ NVIC_DisableIRQ(USB_IRQn); // CMSIS already ends this with DSB+ISB
+ }
+ return enabled;
+}
+
+static inline void usb_irq_unlock(bool enabled)
+{
+ if (enabled) NVIC_EnableIRQ(USB_IRQn);
+}
+
static void sie_cmd_code (sie_cmdphase_t phase, uint8_t code_data)
{
LPC_USB->DevIntClr = (DEV_INT_COMMAND_CODE_EMPTY_MASK | DEV_INT_COMMAND_DATA_FULL_MASK);
@@ -92,19 +153,28 @@ static void sie_cmd_code (sie_cmdphase_t phase, uint8_t code_data)
static void sie_write (uint8_t cmd_code, uint8_t data_len, uint8_t data)
{
+ bool const lock = usb_irq_lock();
+
sie_cmd_code(SIE_CMDPHASE_COMMAND, cmd_code);
if (data_len)
{
sie_cmd_code(SIE_CMDPHASE_WRITE, data);
}
+
+ usb_irq_unlock(lock);
}
static uint8_t sie_read (uint8_t cmd_code)
{
+ bool const lock = usb_irq_lock();
+
sie_cmd_code(SIE_CMDPHASE_COMMAND , cmd_code);
sie_cmd_code(SIE_CMDPHASE_READ , cmd_code);
- return (uint8_t) LPC_USB->CmdData;
+ uint8_t const data = (uint8_t) LPC_USB->CmdData;
+
+ usb_irq_unlock(lock);
+ return data;
}
//--------------------------------------------------------------------+
@@ -117,6 +187,11 @@ static inline uint8_t ep_addr2idx(uint8_t ep_addr)
static void set_ep_size(uint8_t ep_id, uint16_t max_packet_size)
{
+ // ReEp RMW + the EP_RLZED handshake share DevIntSt with the ISR: a bus reset
+ // from dcd_int_handler writes DevIntClr = 0xFFFFFFFF and would consume the
+ // flag this spin waits on, hanging it forever -> same lock as the SIE paths.
+ bool const lock = usb_irq_lock();
+
// follows example in 11.10.4.2
LPC_USB->ReEp |= TU_BIT(ep_id);
LPC_USB->EpInd = ep_id; // select index before setting packet size
@@ -124,6 +199,8 @@ static void set_ep_size(uint8_t ep_id, uint16_t max_packet_size)
while ((LPC_USB->DevIntSt & DEV_INT_ENDPOINT_REALIZED_MASK) == 0) {}
LPC_USB->DevIntClr = DEV_INT_ENDPOINT_REALIZED_MASK;
+
+ usb_irq_unlock(lock);
}
@@ -230,6 +307,7 @@ static inline uint8_t byte2dword(uint8_t bytes)
static void control_ep_write(void const * buffer, uint8_t len)
{
uint32_t const * buf32 = (uint32_t const *) buffer;
+ bool const lock = usb_irq_lock(); // Ctrl/TxData + SIE sequence must not interleave with the ISR
LPC_USB->Ctrl = USBCTRL_WRITE_ENABLE_MASK; // logical endpoint = 0
LPC_USB->TxPLen = (uint32_t) len;
@@ -245,10 +323,14 @@ static void control_ep_write(void const * buffer, uint8_t len)
// select control IN & validate the endpoint
sie_write(SIE_CMDCODE_ENDPOINT_SELECT+1, 0, 0);
sie_write(SIE_CMDCODE_BUFFER_VALIDATE , 0, 0);
+
+ usb_irq_unlock(lock);
}
static uint8_t control_ep_read(void * buffer, uint8_t len)
{
+ bool const lock = usb_irq_lock(); // Ctrl/RxData + SIE sequence must not interleave with the ISR
+
LPC_USB->Ctrl = USBCTRL_READ_ENABLE_MASK; // logical endpoint = 0
while ((LPC_USB->RxPLen & USBRXPLEN_PACKET_READY_MASK) == 0) {} // TODO blocking, should have timeout
@@ -267,6 +349,7 @@ static uint8_t control_ep_read(void * buffer, uint8_t len)
sie_write(SIE_CMDCODE_ENDPOINT_SELECT+0, 0, 0);
sie_write(SIE_CMDCODE_BUFFER_CLEAR , 0, 0);
+ usb_irq_unlock(lock);
return len;
}
@@ -281,8 +364,9 @@ bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const * p_endpoint_desc)
uint8_t const epnum = tu_edpt_number(p_endpoint_desc->bEndpointAddress);
uint8_t const ep_id = ep_addr2idx(p_endpoint_desc->bEndpointAddress);
- // Endpoint type is fixed to endpoint number
- // 1: interrupt, 2: Bulk, 3: Iso and so on
+ // Endpoint type is fixed to endpoint number (1 interrupt, 2 bulk, 3 iso, ...).
+ // Iso endpoints are armed via dcd_edpt_iso_alloc/activate, never through here
+ // (TUP_DCD_EDPT_ISO_ALLOC is defined for this IP), so only bulk/interrupt land here.
switch ( p_endpoint_desc->bmAttributes.xfer )
{
case TUSB_XFER_INTERRUPT:
@@ -293,10 +377,6 @@ bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const * p_endpoint_desc)
TU_ASSERT((epnum % 3) == 2 || (epnum == 15));
break;
- case TUSB_XFER_ISOCHRONOUS:
- TU_ASSERT((epnum % 3) == 0 && (epnum != 0) && (epnum != 15));
- break;
-
default:
break;
}
@@ -307,9 +387,7 @@ bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const * p_endpoint_desc)
//------------- first DD prepare -------------//
dma_desc_t* const dd = &_dcd.dd[ep_id];
- tu_memclr(dd, sizeof(dma_desc_t));
-
- dd->isochronous = (p_endpoint_desc->bmAttributes.xfer == TUSB_XFER_ISOCHRONOUS) ? 1 : 0;
+ tu_memclr(dd, sizeof(dma_desc_t)); // non-iso: isochronous stays 0
dd->max_packet_size = ep_size;
dd->retired = 1; // invalid at first
@@ -319,16 +397,54 @@ bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const * p_endpoint_desc)
}
bool dcd_edpt_iso_alloc(uint8_t rhport, uint8_t ep_addr, uint16_t largest_packet_size) {
+#if DCD_ISO_ENABLED
(void)rhport;
- (void)ep_addr;
- (void)largest_packet_size;
+ uint8_t const ep_id = ep_addr2idx(ep_addr);
+
+ // hardware fixes iso to endpoint numbers 3, 6, 9, 12
+ TU_ASSERT(ep_id_is_iso(ep_id));
+ TU_ASSERT(largest_packet_size > 0);
+
+ set_ep_size(ep_id, largest_packet_size);
+
+ dma_desc_t* const dd = &_dcd.dd[ep_id];
+ tu_memclr(dd, sizeof(dma_desc_t));
+ dd->isochronous = 1;
+ dd->max_packet_size = largest_packet_size;
+ dd->retired = 1; // invalid at first
+
+ sie_write(SIE_CMDCODE_ENDPOINT_SET_STATUS + ep_id, 1, 0);
+ return true;
+#else
+ (void)rhport; (void)ep_addr; (void)largest_packet_size;
return false;
+#endif
}
bool dcd_edpt_iso_activate(uint8_t rhport, const tusb_desc_endpoint_t *desc_ep) {
+#if DCD_ISO_ENABLED
(void)rhport;
- (void)desc_ep;
+ uint8_t const ep_id = ep_addr2idx(desc_ep->bEndpointAddress);
+ dma_desc_t* const dd = &_dcd.dd[ep_id];
+
+ // same fixed-number rule as alloc: without it a rejected-but-ignored alloc (classes
+ // discard that return) would set isochronous on a non-iso ep_id and underflow iso_slot()
+ TU_ASSERT(ep_id_is_iso(ep_id));
+
+ // kill any armed transfer from a previous alternate setting
+ LPC_USB->EpDMADis = TU_BIT(ep_id);
+ _dcd.udca[ep_id] = NULL;
+
+ dd->isochronous = 1;
+ dd->max_packet_size = tu_edpt_packet_size(desc_ep);
+ dd->retired = 1;
+
+ sie_write(SIE_CMDCODE_ENDPOINT_SET_STATUS + ep_id, 1, 0);
+ return true;
+#else
+ (void)rhport; (void)desc_ep;
return false;
+#endif
}
void dcd_edpt_close_all (uint8_t rhport)
@@ -369,19 +485,28 @@ static bool control_xact(uint8_t rhport, uint8_t dir, uint8_t * buffer, uint8_t
control_ep_write(buffer, len);
}else
{
+ // guard the out_received/out_buffer handshake against the EP0 OUT ISR
+ bool const lock = usb_irq_lock();
+
if ( _dcd.control.out_received )
{
// Already received the DATA OUT packet
_dcd.control.out_received = false;
- _dcd.control.out_buffer = NULL;
- _dcd.control.out_bytes = 0;
uint8_t received = control_ep_read(buffer, len);
+ // event queued with in_isr=true, which skips the queue's own locking: keep the
+ // USB IRQ masked across it, or a real ISR completion could interleave the write
dcd_event_xfer_complete(0, 0, received, XFER_RESULT_SUCCESS, true);
+ usb_irq_unlock(lock);
}else
{
+ // buffer is NULL for a status-stage ZLP: signal the pending xfer explicitly,
+ // NOT via out_buffer != NULL — a NULL-buffer queue mistaken for "nothing queued"
+ // leaves out_received stale and poisons the next control OUT data stage.
_dcd.control.out_buffer = buffer;
_dcd.control.out_bytes = len;
+ _dcd.control.out_queued = true;
+ usb_irq_unlock(lock);
}
}
@@ -406,26 +531,68 @@ bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t * buffer, uint16_t t
uint16_t const ep_size = dd->max_packet_size;
uint8_t is_iso = dd->isochronous;
- tu_memclr(dd, sizeof(dma_desc_t));
- dd->isochronous = is_iso;
- dd->max_packet_size = ep_size;
- dd->buffer = (uint32_t) buffer;
- dd->buflen = total_bytes;
+#if DCD_ISO_ENABLED
+ if ( is_iso )
+ {
+ // iso: buflen counts packets; per-packet sizes live in the packet-size memory.
+ // One packet moves per frame (UM10562 12.15.6: DMA request is raised for
+ // DMA-enabled iso endpoints on every FRAME interrupt, both directions).
+ // Validate BEFORE touching the DD: bailing out mid-rebuild would leave a
+ // zeroed (retired=0 -> serviceable) descriptor armed for the frame engine.
+ TU_ASSERT(ep_size > 0);
+ uint16_t const packets = (total_bytes > 0) ? (uint16_t) tu_div_ceil(total_bytes, ep_size) : 1;
+ TU_ASSERT(packets <= ISO_MAX_PACKETS);
+
+ uint8_t const slot = iso_slot(ep_id);
+ uint16_t remain = total_bytes;
+ for ( uint16_t i = 0; i < packets; i++ )
+ {
+ uint16_t const pkt_len = tu_min16(remain, ep_size);
+ // IN: length to send (0 = ZLP). OUT: hardware writes back
+ // Frame_number|Packet_valid|Packet_length -- prefill 0 so a frame the
+ // hardware never wrote (missed/invalid) cannot read back as data.
+ _dcd.iso_psize[slot][i] = (ep_id & 1) ? pkt_len : 0;
+ remain = (uint16_t)(remain - pkt_len);
+ }
- _dcd.udca[ep_id] = dd;
+ tu_memclr(dd, sizeof(dma_desc_t));
+ dd->isochronous = 1;
+ dd->max_packet_size = ep_size;
+ dd->buffer = (uint32_t) buffer;
+ dd->buflen = packets;
+ dd->iso_packet_size_addr = (uint32_t) &_dcd.iso_psize[slot][0];
- if ( ep_id % 2 )
+ _dcd.udca[ep_id] = dd;
+ LPC_USB->EpDMAEn = TU_BIT(ep_id); // frame-triggered: no DMARSet, no EpIntEn
+ }
+ else
+#else
+ (void) is_iso;
+#endif
{
- // Clear EP interrupt before Enable DMA
- LPC_USB->EpIntEn &= ~TU_BIT(ep_id);
- LPC_USB->EpDMAEn = TU_BIT(ep_id);
+ tu_memclr(dd, sizeof(dma_desc_t));
+ dd->max_packet_size = ep_size;
+ dd->buffer = (uint32_t) buffer;
+ dd->buflen = total_bytes;
- // endpoint IN need to actively raise DMA request
- LPC_USB->DMARSet = TU_BIT(ep_id);
- }else
- {
- // Enable DMA
- LPC_USB->EpDMAEn = TU_BIT(ep_id);
+ _dcd.udca[ep_id] = dd;
+
+ if ( ep_id % 2 )
+ {
+ // Clear EP interrupt before Enable DMA
+ // EpIntEn read-modify-write races the ISR's own RMWs -> lock
+ bool const lock = usb_irq_lock();
+ LPC_USB->EpIntEn &= ~TU_BIT(ep_id);
+ LPC_USB->EpDMAEn = TU_BIT(ep_id);
+ usb_irq_unlock(lock);
+
+ // endpoint IN need to actively raise DMA request
+ LPC_USB->DMARSet = TU_BIT(ep_id);
+ }else
+ {
+ // Enable DMA
+ LPC_USB->EpDMAEn = TU_BIT(ep_id);
+ }
}
return true;
@@ -451,13 +618,20 @@ static void control_xfer_isr(uint8_t rhport, uint32_t ep_int_status)
uint8_t setup_packet[8];
control_ep_read(setup_packet, 8); // TODO read before clear setup above
+ // a new SETUP voids any half-finished control state
+ _dcd.control.out_queued = false;
+ _dcd.control.out_received = false;
+ _dcd.control.out_buffer = NULL;
+ _dcd.control.out_bytes = 0;
+
dcd_event_setup_received(rhport, setup_packet, true);
}
- else if ( _dcd.control.out_buffer )
+ else if ( _dcd.control.out_queued )
{
- // software queued transfer previously
+ // software queued transfer previously (out_buffer NULL = status ZLP)
uint8_t received = control_ep_read(_dcd.control.out_buffer, _dcd.control.out_bytes);
+ _dcd.control.out_queued = false;
_dcd.control.out_buffer = NULL;
_dcd.control.out_bytes = 0;
@@ -513,7 +687,32 @@ static void dd_complete_isr(uint8_t rhport, uint8_t ep_id)
uint8_t result = (dd->status == DD_STATUS_NORMAL || dd->status == DD_STATUS_DATA_UNDERUN) ? XFER_RESULT_SUCCESS : XFER_RESULT_FAILED;
uint8_t const ep_addr = (ep_id / 2) | ((ep_id & 0x01) ? TUSB_DIR_IN_MASK : 0);
- dcd_event_xfer_complete(rhport, ep_addr, dd->present_count, result, true);
+ uint32_t xferred_bytes;
+#if DCD_ISO_ENABLED
+ if ( ep_id_is_iso(ep_id) )
+ {
+ // present_count is in packets; actual byte counts are in the packet-size memory
+ // (IN: as programmed by us, OUT: Packet_length written back by hardware,
+ // guarded by Packet_valid -- a frame with no packet must count as 0)
+ uint8_t const slot = iso_slot(ep_id);
+ uint16_t const packets = tu_min16(dd->present_count, ISO_MAX_PACKETS);
+ xferred_bytes = 0;
+ for (uint16_t i = 0; i < packets; i++)
+ {
+ uint32_t const psize = _dcd.iso_psize[slot][i];
+ if ( (ep_id & 1) || (psize & TU_BIT(16)) )
+ {
+ xferred_bytes += (psize & 0xFFFFu);
+ }
+ }
+ }
+ else
+#endif
+ {
+ xferred_bytes = dd->present_count;
+ }
+
+ dcd_event_xfer_complete(rhport, ep_addr, (uint16_t) xferred_bytes, result, true);
}
// main USB IRQ handler
@@ -569,6 +768,16 @@ void dcd_int_handler(uint8_t rhport)
{
if ( tu_bit_test(eot, ep_id) )
{
+ // dispatch on the hardware's fixed ep-number/type map, NOT dd->isochronous:
+ // thread-mode dcd_edpt_xfer transiently zeroes the DD while rebuilding it
+#if DCD_ISO_ENABLED
+ if ( ep_id_is_iso(ep_id) )
+ {
+ // iso: last packet already left with its frame; complete both directions here
+ dd_complete_isr(rhport, ep_id);
+ }
+ else
+#endif
if ( ep_id & 0x01 )
{
// IN enable EpInt for end of usb transfer
diff --git a/src/portable/nxp/lpc_ip3511/dcd_lpc_ip3511.c b/src/portable/nxp/lpc_ip3511/dcd_lpc_ip3511.c
index aa0307d25..42f6750b1 100644
--- a/src/portable/nxp/lpc_ip3511/dcd_lpc_ip3511.c
+++ b/src/portable/nxp/lpc_ip3511/dcd_lpc_ip3511.c
@@ -87,6 +87,10 @@ enum {
DEVCMDSTAT_SUSPEND_CHANGE_MASK = TU_BIT(25),
DEVCMDSTAT_RESET_CHANGE_MASK = TU_BIT(26),
DEVCMDSTAT_VBUS_DEBOUNCED_MASK = TU_BIT(28),
+
+ // write-1-to-clear latches
+ DEVCMDSTAT_W1C_MASK = DEVCMDSTAT_SETUP_RECEIVED_MASK | DEVCMDSTAT_CONNECT_CHANGE_MASK |
+ DEVCMDSTAT_SUSPEND_CHANGE_MASK | DEVCMDSTAT_RESET_CHANGE_MASK,
};
enum {
@@ -158,6 +162,10 @@ typedef struct
// - 55 usb0 (FS) has 5x2 endpoints, usb1 (HS) has 6x2 endpoints
#define MAX_EP_PAIRS 6
+// Bounded spin waiting for hardware to clear an EPSKIP bit when retiring a still-armed endpoint on
+// reopen (dcd_edpt_open). Hardware clears it within a (micro)frame; the guard only avoids a hang.
+#define IP3511_EPSKIP_SPIN 100000u
+
// NOTE data will be transferred as soon as dcd get request by dcd_pipe(_queue)_xfer using double buffering.
// current_td is used to keep track of number of remaining & xferred bytes of the current request.
typedef struct
@@ -167,7 +175,9 @@ typedef struct
ep_cmd_sts_t ep[2*MAX_EP_PAIRS][2];
xfer_dma_t dma[2*MAX_EP_PAIRS];
- TU_ATTR_ALIGNED(64) uint8_t setup_packet[8];
+ // volatile: the controller DMAs a new setup packet into this buffer as soon as the SETUP
+ // latch is cleared, so reads of it must stay ordered against the register accesses around them
+ TU_ATTR_ALIGNED(64) volatile uint8_t setup_packet[8];
}dcd_data_t;
// EP list must be 256-byte aligned
@@ -176,8 +186,12 @@ typedef struct
// Use CFG_TUD_MEM_SECTION to place it accordingly.
CFG_TUD_MEM_SECTION TU_ATTR_ALIGNED(256) static dcd_data_t _dcd;
-// Dummy buffer to fix ZLPs overwriting the buffer (probably an USB/DMA controller bug)
-// TODO find way to save memory
+// Dummy buffer to fix ZLPs overwriting the buffer: Errata LPC55S6x USB.5 / LPC55S2x USB.4 - the
+// HS device controller always DMA-writes OUT data in 8-byte units, so up to 7 bytes land past the
+// received length. This redirects the ZLP case; the general short-OUT case is unhandled here
+// (TinyUSB's own endpoint buffers are sized/aligned so the spill stays inside them, but a tight
+// caller buffer can be overrun by up to 7 bytes - the SDK's documented workaround is a bounce
+// buffer). TODO find way to save memory
CFG_TUD_MEM_SECTION TU_ATTR_ALIGNED(64) static uint8_t dummy[8];
//--------------------------------------------------------------------+
@@ -217,7 +231,7 @@ static const dcd_controller_t _dcd_controller[] = {
// INTERNAL OBJECT & FUNCTION DECLARATION
//--------------------------------------------------------------------+
-TU_ATTR_ALWAYS_INLINE static inline uint16_t get_buf_offset(void const * buffer) {
+TU_ATTR_ALWAYS_INLINE static inline uint16_t get_buf_offset(void const volatile * buffer) {
uint32_t addr = (uint32_t) buffer;
TU_ASSERT( (addr & 0x3f) == 0, 0 );
return ( (addr >> 6) & 0xFFFFUL ) ;
@@ -243,6 +257,16 @@ TU_ATTR_ALWAYS_INLINE static inline bool rhport_is_highspeed(uint8_t rhport) {
return _dcd_controller[rhport].is_highspeed;
}
+
+// DEVCMDSTAT mixes RW fields with write-1-to-clear latches (SETUP + the 3 change bits): a blind
+// RMW writes a pending latch back as 1 and silently clears it (a SETUP eaten this way strands
+// EP0). Mask the latches on every update; pass one in set_mask only to clear it.
+TU_ATTR_ALWAYS_INLINE static inline void devcmdstat_update(dcd_registers_t* dcd_reg,
+ uint32_t clear_mask, uint32_t set_mask) {
+ const uint32_t v = dcd_reg->DEVCMDSTAT & ~(DEVCMDSTAT_W1C_MASK | clear_mask);
+ dcd_reg->DEVCMDSTAT = v | set_mask;
+}
+
//--------------------------------------------------------------------+
// CONTROLLER API
//--------------------------------------------------------------------+
@@ -280,8 +304,10 @@ bool dcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) {
dcd_reg->DATABUFSTART = tu_align((uint32_t) &_dcd, TU_BIT(22)); // 22-bit alignment
dcd_reg->INTSTAT = dcd_reg->INTSTAT; // clear all pending interrupt
dcd_reg->INTEN = INT_DEVICE_STATUS_MASK;
- dcd_reg->DEVCMDSTAT |= DEVCMDSTAT_DEVICE_ENABLE_MASK | DEVCMDSTAT_DEVICE_CONNECT_MASK |
- DEVCMDSTAT_RESET_CHANGE_MASK | DEVCMDSTAT_CONNECT_CHANGE_MASK | DEVCMDSTAT_SUSPEND_CHANGE_MASK;
+ // deliberately clear every latch (incl. a SETUP left by a bootloader/warm start) for a
+ // deterministic init state
+ devcmdstat_update(dcd_reg, 0, DEVCMDSTAT_DEVICE_ENABLE_MASK | DEVCMDSTAT_DEVICE_CONNECT_MASK |
+ DEVCMDSTAT_W1C_MASK);
NVIC_ClearPendingIRQ(_dcd_controller[rhport].irqnum);
@@ -305,8 +331,7 @@ void dcd_set_address(uint8_t rhport, uint8_t dev_addr)
// Response with status first before changing device address
dcd_edpt_xfer(rhport, tu_edpt_addr(0, TUSB_DIR_IN), NULL, 0, false);
- dcd_reg->DEVCMDSTAT &= ~DEVCMDSTAT_DEVICE_ADDR_MASK;
- dcd_reg->DEVCMDSTAT |= dev_addr;
+ devcmdstat_update(dcd_reg, DEVCMDSTAT_DEVICE_ADDR_MASK, dev_addr);
}
void dcd_remote_wakeup(uint8_t rhport)
@@ -317,13 +342,13 @@ void dcd_remote_wakeup(uint8_t rhport)
void dcd_connect(uint8_t rhport)
{
dcd_registers_t* dcd_reg = _dcd_controller[rhport].regs;
- dcd_reg->DEVCMDSTAT |= DEVCMDSTAT_DEVICE_CONNECT_MASK;
+ devcmdstat_update(dcd_reg, 0, DEVCMDSTAT_DEVICE_CONNECT_MASK);
}
void dcd_disconnect(uint8_t rhport)
{
dcd_registers_t* dcd_reg = _dcd_controller[rhport].regs;
- dcd_reg->DEVCMDSTAT &= ~DEVCMDSTAT_DEVICE_CONNECT_MASK;
+ devcmdstat_update(dcd_reg, DEVCMDSTAT_DEVICE_CONNECT_MASK, 0);
}
void dcd_sof_enable(uint8_t rhport, bool en)
@@ -337,13 +362,37 @@ void dcd_sof_enable(uint8_t rhport, bool en)
//--------------------------------------------------------------------+
// DCD Endpoint Port
//--------------------------------------------------------------------+
+// Retire a still-armed (Active) endpoint before reconfiguring it (reopen across SET_INTERFACE).
+// UM11126 §41.7.6/§41.8.3: write EPSKIP and wait for hardware to clear the bit, then Active is
+// safe to clear. EPSKIP raises the endpoint interrupt as it clears Active, delivered as a
+// (partial) transfer completion. Here that is sanctioned — usbd_edpt_close() documents "in
+// progress transfers may be delivered after this call", and that completion is what clears the
+// stale usbd busy flag (ISO_ALLOC close is a no-op) so the class can re-arm the reopened
+// endpoint. NOT for the stall/iso-activate paths: there the class re-arms from the completion
+// callback and the endpoint ends up Active+Stall, which never sends a STALL handshake (usbtest
+// case 13 regression on LPC11u37) — those paths must clear Active directly instead.
+// Bounded: hardware clears EPSKIP within a (micro)frame.
+static void edpt_skip_active(uint8_t rhport, uint8_t ep_id) {
+ ep_cmd_sts_t* ep_cs = get_ep_cs(ep_id);
+ if ( ep_cs[0].cmd_sts.active || ep_cs[1].cmd_sts.active ) {
+ dcd_registers_t* dcd_reg = _dcd_controller[rhport].regs;
+ dcd_reg->EPSKIP |= TU_BIT(ep_id);
+ uint32_t guard = IP3511_EPSKIP_SPIN;
+ while ( (dcd_reg->EPSKIP & TU_BIT(ep_id)) && guard-- ) {}
+ }
+ ep_cs[0].cmd_sts.active = ep_cs[1].cmd_sts.active = 0;
+}
+
void dcd_edpt_stall(uint8_t rhport, uint8_t ep_addr)
{
(void) rhport;
-
// TODO cannot able to STALL Control OUT endpoint !!!!! FIXME try some walk-around
uint8_t const ep_id = ep_addr2id(ep_addr);
- _dcd.ep[ep_id][0].cmd_sts.stall = 1;
+ // Clear Active directly before setting Stall (no EPSKIP — see edpt_skip_active): the hardware
+ // services an armed buffer instead of returning STALL, so a halt requested while a transfer is
+ // queued would not actually stall the endpoint (usbtest case 13).
+ _dcd.ep[ep_id][0].cmd_sts.active = 0;
+ _dcd.ep[ep_id][0].cmd_sts.stall = 1;
}
void dcd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr)
@@ -352,9 +401,17 @@ void dcd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr)
uint8_t const ep_id = ep_addr2id(ep_addr);
+ // Preserve rf_tv: for non-control endpoints it is a TYPE bit, not the toggle value (UM11126:
+ // T=1 + RF 1/0 = interrupt/iso). Zeroing it here turned HS periodic interrupt endpoints into
+ // isochronous - no handshake on OUT, dead IN (usbtest cases 25/26 on lpc55 HS port).
+ // TODO implement the Errata LPC546xx USB.13 work-around (same semantics in UM11126): with RF/TV preserved at 1, TR
+ // loads the toggle from TV, so an HS interrupt endpoint restarts on DATA1 after clear-halt and
+ // the host discards one packet as a retransmission. The documented workaround needs an
+ // interrupt-on-NAK state machine (park as generic TR=1/TV=0, wait for a NAKed token to latch
+ // toggle 0 via EPTOGGLE, restore the type) - deferred; one lost packet beats the fully broken
+ // endpoint the old rf_tv clear caused.
_dcd.ep[ep_id][0].cmd_sts.stall = 0;
_dcd.ep[ep_id][0].cmd_sts.toggle_reset = 1;
- _dcd.ep[ep_id][0].cmd_sts.rf_tv = 0;
}
bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const * p_endpoint_desc)
@@ -362,9 +419,15 @@ bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const * p_endpoint_desc)
//------------- Prepare Queue Head -------------//
uint8_t ep_id = ep_addr2id(p_endpoint_desc->bEndpointAddress);
ep_cmd_sts_t* ep_cs = get_ep_cs(ep_id);
+ dcd_registers_t* dcd_reg = _dcd_controller[rhport].regs;
- // Check if endpoint is available
- TU_ASSERT( ep_cs[0].cmd_sts.disable && ep_cs[1].cmd_sts.disable );
+ // usbd_edpt_close() is a no-op on ISO_ALLOC ports, so an endpoint a class closed then reopened
+ // across SET_INTERFACE (e.g. the video notification or audio streaming endpoint) is still armed
+ // here rather than disabled. Retire it (edpt_skip_active) before reconfiguring.
+ if ( !(ep_cs[0].cmd_sts.disable && ep_cs[1].cmd_sts.disable) ) {
+ edpt_skip_active(rhport, ep_id);
+ ep_cs[0].cmd_sts.disable = ep_cs[1].cmd_sts.disable = 1;
+ }
edpt_reset(rhport, ep_id);
@@ -389,7 +452,6 @@ bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const * p_endpoint_desc)
}
// Enable EP interrupt
- dcd_registers_t* dcd_reg = _dcd_controller[rhport].regs;
dcd_reg->INTEN |= TU_BIT(ep_id);
return true;
@@ -399,34 +461,40 @@ void dcd_edpt_close_all (uint8_t rhport)
{
for (uint8_t ep_id = 0; ep_id < 2*_dcd_controller[rhport].ep_pairs; ++ep_id)
{
- _dcd.ep[ep_id][0].cmd_sts.active = _dcd.ep[ep_id][0].cmd_sts.active = 0; // TODO proper way is to EPSKIP then wait ep[][].active then write ep[][].disable (see table 778 in LPC55S69 Use Manual)
+ _dcd.ep[ep_id][0].cmd_sts.active = _dcd.ep[ep_id][1].cmd_sts.active = 0; // TODO proper way is to EPSKIP then wait ep[][].active then write ep[][].disable (see table 778 in LPC55S69 Use Manual)
_dcd.ep[ep_id][0].cmd_sts.disable = _dcd.ep[ep_id][1].cmd_sts.disable = 1;
}
}
-void dcd_edpt_close(uint8_t rhport, uint8_t ep_addr)
-{
- (void) rhport;
-
+bool dcd_edpt_iso_alloc(uint8_t rhport, uint8_t ep_addr, uint16_t largest_packet_size) {
+ (void) largest_packet_size;
+ // Reserve the endpoint command/status entry once (persists across altsetting changes); the
+ // buffer pointer is filled per-transfer, so nothing to pre-allocate. Mirrors the ISO branch of
+ // dcd_edpt_open().
uint8_t ep_id = ep_addr2id(ep_addr);
- _dcd.ep[ep_id][0].cmd_sts.active = _dcd.ep[ep_id][0].cmd_sts.active = 0; // TODO proper way is to EPSKIP then wait ep[][].active then write ep[][].disable (see table 778 in LPC55S69 Use Manual)
- _dcd.ep[ep_id][0].cmd_sts.disable = _dcd.ep[ep_id][1].cmd_sts.disable = 1;
-}
+ ep_cmd_sts_t* ep_cs = get_ep_cs(ep_id);
+ TU_ASSERT( ep_cs[0].cmd_sts.disable && ep_cs[1].cmd_sts.disable );
-#if 0
-bool dcd_edpt_iso_alloc(uint8_t rhport, uint8_t ep_addr, uint16_t largest_packet_size) {
- (void)rhport;
- (void)ep_addr;
- (void)largest_packet_size;
- return false;
+ edpt_reset(rhport, ep_id);
+ ep_cs[0].cmd_sts.type = 1; // ISO
+
+ dcd_registers_t* dcd_reg = _dcd_controller[rhport].regs;
+ dcd_reg->INTEN |= TU_BIT(ep_id);
+ return true;
}
bool dcd_edpt_iso_activate(uint8_t rhport, const tusb_desc_endpoint_t *desc_ep) {
- (void)rhport;
- (void)desc_ep;
- return false;
+ // (Re)activate on altsetting selection: abort a transfer still armed from the previous
+ // altsetting (the hardware keeps servicing an Active buffer across SET_INTERFACE, fighting the
+ // fresh transfer the class queues), clear stall and reset the data toggle. Direct Active=0, not
+ // EPSKIP (see edpt_skip_active). The class re-arms via dcd_edpt_xfer().
+ uint8_t ep_id = ep_addr2id(desc_ep->bEndpointAddress);
+ ep_cmd_sts_t* ep_cs = get_ep_cs(ep_id);
+ ep_cs[0].cmd_sts.active = 0;
+ ep_cs[1].cmd_sts.active = 0;
+ dcd_edpt_clear_stall(rhport, desc_ep->bEndpointAddress);
+ return true;
}
-#endif
static void prepare_ep_xfer(uint8_t rhport, uint8_t ep_id, uint16_t buf_offset, uint16_t total_bytes) {
uint16_t nbytes;
@@ -499,7 +567,7 @@ static void bus_reset(uint8_t rhport)
dcd_reg->EPSKIP = 0xFFFFFFFF;
dcd_reg->INTSTAT = dcd_reg->INTSTAT; // clear all pending interrupt
- dcd_reg->DEVCMDSTAT |= DEVCMDSTAT_SETUP_RECEIVED_MASK; // clear setup received interrupt
+ devcmdstat_update(dcd_reg, 0, DEVCMDSTAT_SETUP_RECEIVED_MASK); // clear setup received interrupt
dcd_reg->INTEN = INT_DEVICE_STATUS_MASK | TU_BIT(0) | TU_BIT(1); // enable device status & control endpoints
}
@@ -558,18 +626,25 @@ void dcd_int_handler(uint8_t rhport)
{
dcd_registers_t* dcd_reg = _dcd_controller[rhport].regs;
- uint32_t const cmd_stat = dcd_reg->DEVCMDSTAT;
-
uint32_t int_status = dcd_reg->INTSTAT;
- int_status &= dcd_reg->INTEN;
+ int_status &= dcd_reg->INTEN;
dcd_reg->INTSTAT = int_status; // Acknowledge handled interrupt
if (int_status == 0) return;
+ // Snapshot after the INTSTAT ack: latch bits persist (RWC) so nothing is lost, while the reverse
+ // order could consume INTSTAT bit0 for a SETUP not yet visible in the snapshot - stranding the
+ // SETUP (INTSTAT is edge-latched) and feeding bit0 to process_xfer_isr as a bogus completion.
+ uint32_t const cmd_stat = dcd_reg->DEVCMDSTAT;
+
//------------- Device Status -------------//
if ( int_status & INT_DEVICE_STATUS_MASK )
{
- dcd_reg->DEVCMDSTAT |= DEVCMDSTAT_RESET_CHANGE_MASK | DEVCMDSTAT_CONNECT_CHANGE_MASK | DEVCMDSTAT_SUSPEND_CHANGE_MASK;
+ // clear only the change latches observed in the snapshot: one latched by hardware between the
+ // snapshot and this write would be acknowledged unseen (its DEV_INT re-latches and dispatches
+ // next pass instead)
+ devcmdstat_update(dcd_reg, 0, cmd_stat &
+ (DEVCMDSTAT_RESET_CHANGE_MASK | DEVCMDSTAT_CONNECT_CHANGE_MASK | DEVCMDSTAT_SUSPEND_CHANGE_MASK));
if ( cmd_stat & DEVCMDSTAT_RESET_CHANGE_MASK) // bus reset
{
@@ -614,15 +689,43 @@ void dcd_int_handler(uint8_t rhport)
_dcd.ep[0][0].cmd_sts.active = _dcd.ep[1][0].cmd_sts.active = 0;
_dcd.ep[0][0].cmd_sts.stall = _dcd.ep[1][0].cmd_sts.stall = 0;
- dcd_reg->DEVCMDSTAT |= DEVCMDSTAT_SETUP_RECEIVED_MASK;
+ // UM flow: ack the latch FIRST, then read the payload. This IP has no setup lockout, so a
+ // back-to-back SETUP can overwrite _dcd.setup_packet at any time - but with the latch already
+ // released, any such overwrite re-latches SETUP_RECEIVED and is redelivered (worst case a
+ // superseded duplicate, absorbed by usbd's queued-setup counter). The reverse order can
+ // consume the newer SETUP's latch unseen and lose it.
+ devcmdstat_update(dcd_reg, 0, DEVCMDSTAT_SETUP_RECEIVED_MASK);
- dcd_event_setup_received(rhport, _dcd.setup_packet, true);
+ // UM11126 Fig 163 (control EP0 flowchart) requires clearing the EP0IN interrupt here: a
+ // control IN completion latched before this SETUP must not reach usbd after it, where it
+ // would be applied to the new request and arm its status stage early. EP0OUT goes with it -
+ // bit0 is set by SETUP reception too, and left set it would replay next pass as a phantom
+ // completion. Neither can discard live work: the SETUP latch NAKs all EP0 traffic until the
+ // update above, and both EP0 Active bits were cleared a few lines up.
+ dcd_reg->INTSTAT = TU_BIT(0) | TU_BIT(1);
+
+ // Copied a byte at a time rather than with memcpy: C orders volatile accesses only against
+ // each other, so a non-volatile copy of this buffer may be sunk below the guard read that
+ // follows - gcc does exactly that at -O2 and -O3, leaving only -Os correct.
+ uint8_t setup_copy[8];
+ for (uint8_t i = 0; i < sizeof(setup_copy); i++) {
+ setup_copy[i] = _dcd.setup_packet[i];
+ }
+
+ // a SETUP that raced in after the acks (its bit0 consumed above) makes this copy suspect:
+ // its latch is visible again, so re-raise the endpoint interrupt and let the next pass
+ // deliver the newer payload rather than passing up bytes that may be torn between the two
+ if (dcd_reg->DEVCMDSTAT & DEVCMDSTAT_SETUP_RECEIVED_MASK) {
+ dcd_reg->INTSETSTAT = TU_BIT(0);
+ } else {
+ dcd_event_setup_received(rhport, setup_copy, true);
+ }
// keep waiting for next setup
prepare_setup_packet(rhport);
- // clear bit0
- int_status = tu_bit_clear(int_status, 0);
+ // drop both EP0 bits: acked above, and neither belongs to the request this SETUP starts
+ int_status &= ~(TU_BIT(0) | TU_BIT(1));
}
// Endpoint transfer complete interrupt
diff --git a/src/portable/raspberrypi/rp2040/dcd_rp2040.c b/src/portable/raspberrypi/rp2040/dcd_rp2040.c
index 63097cd0a..564ded535 100644
--- a/src/portable/raspberrypi/rp2040/dcd_rp2040.c
+++ b/src/portable/raspberrypi/rp2040/dcd_rp2040.c
@@ -387,6 +387,9 @@ bool dcd_deinit(uint8_t rhport) {
reset_block(RESETS_RESET_USBCTRL_BITS);
unreset_block_wait(RESETS_RESET_USBCTRL_BITS);
+ // Release allocated resources
+ rp2usb_deinit();
+
return true;
}
@@ -550,9 +553,46 @@ void dcd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr) {
if (epnum != 0) {
struct hw_endpoint* ep = hw_endpoint_get(epnum, dir);
- ep->next_pid = 0; // reset data toggle
- io_rw_32 *buf_reg = get_buf_ctrl(epnum, dir);
- *buf_reg = 0;
+
+ if (ep->state == EPSTATE_ACTIVE) {
+ // Clear-halt on an endpoint with an in-flight transfer is used as a data-toggle reset
+ // (e.g. usbtest case 29) rather than to recover from a real stall (a stall aborts the
+ // transfer, leaving the endpoint IDLE). Abort and re-issue the transfer with the toggle
+ // reset to DATA0 so it still completes and releases the usbd claim, instead of silently
+ // dropping it and starving the endpoint. Save the buffer/length before the abort clears them.
+ uint8_t* user_buf = ep->user_buf;
+ uint16_t remaining = ep->remaining_len;
+ const uint16_t xferred = ep->xferred_len; // bytes already moved on this submission
+ io_rw_32 *ep_reg = get_ep_ctrl(epnum, dir);
+ io_rw_32 *buf_reg = get_buf_ctrl(epnum, dir);
+ // bufctrl_prepare16() subtracts each armed buffer's length from remaining_len when arming,
+ // for BOTH directions, before the host has drained (IN) or filled (OUT) it. The abort below
+ // discards those still-armed buffers, so rewind remaining_len by their lengths or the re-issue
+ // is short by 1-2 packets. IN additionally advances user_buf as packets are copied into DPRAM,
+ // so its pointer must rewind too; OUT copies out only on completion, so its pointer is intact.
+ const uint32_t bc = *buf_reg;
+ uint16_t staged = 0;
+ if (bc & USB_BUF_CTRL_AVAIL) {
+ staged = (uint16_t)(bc & USB_BUF_CTRL_LEN_MASK);
+ }
+ if ((bc >> 16) & USB_BUF_CTRL_AVAIL) {
+ staged = (uint16_t)(staged + ((bc >> 16) & USB_BUF_CTRL_LEN_MASK));
+ }
+ remaining = (uint16_t)(remaining + staged);
+ if (dir == TUSB_DIR_IN) {
+ user_buf -= staged;
+ }
+ hw_endpoint_abort_xfer(ep); // safe abort (handles RP2040-E2), resets ep transfer state
+ ep->next_pid = 0; // DATA0
+ rp2usb_xfer_start(ep, ep_reg, buf_reg, user_buf, NULL, remaining);
+ // rp2usb_xfer_start() zeroes xferred_len; add back what the aborted transfer already moved so
+ // the eventual completion reports the full length, not just the post-clear-halt remainder.
+ ep->xferred_len += xferred;
+ } else {
+ ep->next_pid = 0; // reset data toggle
+ io_rw_32 *buf_reg = get_buf_ctrl(epnum, dir);
+ *buf_reg = 0; // clear the stall response
+ }
}
}
diff --git a/src/portable/raspberrypi/rp2040/hcd_rp2040.c b/src/portable/raspberrypi/rp2040/hcd_rp2040.c
index 28dc7f93c..a04890835 100644
--- a/src/portable/raspberrypi/rp2040/hcd_rp2040.c
+++ b/src/portable/raspberrypi/rp2040/hcd_rp2040.c
@@ -427,6 +427,10 @@ bool hcd_deinit(uint8_t rhport) {
irq_remove_handler(USBCTRL_IRQ, hcd_rp2040_irq);
reset_block(RESETS_RESET_USBCTRL_BITS);
unreset_block_wait(RESETS_RESET_USBCTRL_BITS);
+
+ // Release allocated resources
+ rp2usb_deinit();
+
return true;
}
diff --git a/src/portable/raspberrypi/rp2040/rp2040_usb.c b/src/portable/raspberrypi/rp2040/rp2040_usb.c
index e4eb0184e..96b335bd3 100644
--- a/src/portable/raspberrypi/rp2040/rp2040_usb.c
+++ b/src/portable/raspberrypi/rp2040/rp2040_usb.c
@@ -85,6 +85,10 @@ void rp2usb_init(void) {
critical_section_init(&rp2usb_lock);
}
+void rp2usb_deinit(void) {
+ critical_section_deinit(&rp2usb_lock);
+}
+
void __tusb_irq_path_func(rp2usb_reset_transfer)(hw_endpoint_t *ep) {
ep->state = EPSTATE_IDLE;
ep->remaining_len = 0;
@@ -176,10 +180,15 @@ void __tusb_irq_path_func(rp2usb_buffer_start)(hw_endpoint_t *ep, io_rw_32 *ep_r
// 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 = (rp2usb_is_host_mode() && ep->interrupt_num > 0);
+ const bool force_single = is_iso || (rp2usb_is_host_mode() && ep->interrupt_num > 0);
#else
- const bool force_single = false;
+ const bool force_single = is_iso;
#endif
if (ep->remaining_len && !force_single) {
diff --git a/src/portable/raspberrypi/rp2040/rp2040_usb.h b/src/portable/raspberrypi/rp2040/rp2040_usb.h
index f4e85764d..e5a54007c 100644
--- a/src/portable/raspberrypi/rp2040/rp2040_usb.h
+++ b/src/portable/raspberrypi/rp2040/rp2040_usb.h
@@ -147,6 +147,7 @@ extern volatile uint32_t e15_last_sof;
#endif
void rp2usb_init(void);
+void rp2usb_deinit(void);
// if usb hardware is in host mode
TU_ATTR_ALWAYS_INLINE static inline bool rp2usb_is_host_mode(void) {
diff --git a/src/portable/renesas/rusb2/dcd_rusb2.c b/src/portable/renesas/rusb2/dcd_rusb2.c
index f0ef9738b..c93bab05e 100644
--- a/src/portable/renesas/rusb2/dcd_rusb2.c
+++ b/src/portable/renesas/rusb2/dcd_rusb2.c
@@ -28,6 +28,9 @@ typedef struct {
uint8_t ep; /* an assigned endpoint address */
uint8_t ff; /* `buf` is TU_FUFO or POD */
+ bool queued; /* a transfer is submitted and not yet completed (independent of `buf`, which is
+ NULL for a zero-length read) -- used to decide clear-stall re-arm */
+ bool zlp_pending; /* a zero-length IN packet couldn't be queued at submit (FIFO full); retry on BRDY */
} pipe_state_t;
typedef struct
@@ -40,6 +43,7 @@ typedef struct
static dcd_data_t _dcd;
+
//--------------------------------------------------------------------+
// INTERNAL OBJECT & FUNCTION DECLARATION
//--------------------------------------------------------------------+
@@ -121,9 +125,19 @@ static uint16_t edpt_max_packet_size(rusb2_reg_t *rusb, unsigned num) {
return rusb->PIPEMAXP;
}
-static inline void pipe_wait_for_ready(rusb2_reg_t * rusb, unsigned num) {
- while ( rusb->D0FIFOSEL_b.CURPIPE != num ) {}
- while ( !rusb->D0FIFOCTR_b.FRDY ) {}
+// Select the D0FIFO for `num` and wait until its buffer is ready for CPU access. Both flags
+// normally settle within a few cycles (the pipe was just armed, or a BRDY freed a plane). But an
+// IN pipe whose double buffer is already full stalls FRDY until the host drains it, and a
+// no-handshake iso IN endpoint the host has stopped polling never drains at all — so FRDY would
+// hang forever. This runs with the USB IRQ masked, so a naked spin freezes the whole stack; bound
+// it and let the caller abort the FIFO access. Returns false on timeout.
+#define RUSB2_FIFO_READY_SPIN 100000u
+static inline bool pipe_wait_for_ready(rusb2_reg_t *rusb, unsigned num) {
+ uint32_t spin = RUSB2_FIFO_READY_SPIN;
+ while ( rusb->D0FIFOSEL_b.CURPIPE != num ) { if (!spin--) return false; }
+ spin = RUSB2_FIFO_READY_SPIN;
+ while ( !rusb->D0FIFOCTR_b.FRDY ) { if (!spin--) return false; }
+ return true;
}
//--------------------------------------------------------------------+
@@ -177,6 +191,15 @@ static bool pipe0_xfer_out(rusb2_reg_t *rusb) {
pipe_state_t *pipe = &_dcd.pipe[0];
const unsigned rem = pipe->remaining;
+ // BRDY with no armed transfer: a back-to-back data-stage packet beat the PID=NAK below (the
+ // host has already ACKed it). Park it in the DCP buffer — an unread buffer NAKs further OUTs —
+ // and let process_pipe0_xfer deliver it when usbd arms the next chunk. BCLR here would silently
+ // drop the packet and shift every later chunk by one (usbtest ctrl_out corruption at ra4m1).
+ if (pipe->buf == NULL && rem == 0) {
+ rusb->DCPCTR = RUSB2_PIPE_CTR_PID_NAK;
+ return false;
+ }
+
const uint16_t mps = edpt0_max_packet_size(rusb);
const uint16_t vld = rusb->CFIFOCTR_b.DTLN;
const uint16_t len = tu_min16(tu_min16(rem, mps), vld);
@@ -201,6 +224,12 @@ static bool pipe0_xfer_out(rusb2_reg_t *rusb) {
pipe->remaining = rem - len;
if ((len < mps) || (rem == len)) {
pipe->buf = NULL;
+ // Flow-control the single-buffer control pipe: NAK further OUT until usbd arms the next
+ // data-stage chunk. usbd receives a multi-packet control-OUT one CFG_TUD_ENDPOINT0_SIZE
+ // packet per submit; without this the DCP auto-accepts the next back-to-back packet into the
+ // just-emptied buffer and the following BRDY (remaining==0) BCLR-discards it, dropping 64
+ // bytes mid-transfer (e.g. usbtest ctrl_out 512B). RA4M1 UM R01UH0887 DCPCTR.PID.
+ rusb->DCPCTR = RUSB2_PIPE_CTR_PID_NAK;
return true;
}
@@ -226,7 +255,12 @@ static bool pipe_xfer_in(rusb2_reg_t* rusb, unsigned num)
}
const uint16_t mps = edpt_max_packet_size(rusb, num);
- pipe_wait_for_ready(rusb, num);
+ if (!pipe_wait_for_ready(rusb, num)) {
+ // Buffer never came ready (double-buffered IN pipe full, host not draining). Drop this load;
+ // the transfer stays pending and is retried when a BRDY frees a plane or the pipe is re-armed.
+ rusb->D0FIFOSEL = 0;
+ return false;
+ }
uint16_t len = tu_min16(rem, mps);
void *buf = pipe->buf;
@@ -267,7 +301,10 @@ static bool pipe_xfer_out(rusb2_reg_t* rusb, unsigned num)
rusb->D0FIFOSEL = fifo_sel;
const uint16_t mps = edpt_max_packet_size(rusb, num);
- pipe_wait_for_ready(rusb, num);
+ if (!pipe_wait_for_ready(rusb, num)) {
+ rusb->D0FIFOSEL = 0;
+ return false; // FIFO not ready; leave the receive pending (BRDY re-enters when data arrives)
+ }
const uint16_t vld = (uint16_t)rusb->D0FIFOCTR_b.DTLN;
const uint16_t len = tu_min16(tu_min16(rem, mps), vld);
@@ -333,7 +370,16 @@ static void process_status_completion(uint8_t rhport)
dcd_event_xfer_complete(rhport, ep_addr, 0, XFER_RESULT_SUCCESS, true);
}
-static bool process_pipe0_xfer(rusb2_reg_t *rusb, int buffer_type, uint8_t ep_addr, void *buffer,
+// Report a completed transfer on `num` and reset its bookkeeping. Single completion path for the
+// BRDY handler and the EP0 parked-packet drain, so they can't diverge (e.g. on clearing `queued`).
+static void pipe_xfer_complete(uint8_t rhport, unsigned num, bool in_isr) {
+ pipe_state_t *pipe = &_dcd.pipe[num];
+ pipe->queued = false;
+ dcd_event_xfer_complete(rhport, pipe->ep, pipe->length - pipe->remaining,
+ XFER_RESULT_SUCCESS, in_isr);
+}
+
+static bool process_pipe0_xfer(uint8_t rhport, rusb2_reg_t *rusb, int buffer_type, uint8_t ep_addr, void *buffer,
uint16_t total_bytes) {
uint16_t fifo_sel =
(rusb2_is_highspeed_reg(rusb) ? RUSB2_FIFOSEL_MBW_32BIT : RUSB2_FIFOSEL_MBW_16BIT) | FIFOSEL_BIGEND;
@@ -359,6 +405,15 @@ static bool process_pipe0_xfer(rusb2_reg_t *rusb, int buffer_type, uint8_t ep_ad
/* IN */
TU_ASSERT(rusb->DCPCTR_b.BSTS && (rusb->USBREQ & 0x80));
pipe0_xfer_in(rusb);
+ } else if (rusb->CFIFOCTR_b.DTLN > 0) {
+ /* OUT: a back-to-back packet parked by pipe0_xfer_out already sits in the DCP buffer (its
+ BRDY has fired and been cleared) — deliver it into this chunk now; no new BRDY will come
+ for it. Runs with the USB IRQ masked (dcd_edpt_xfer). Detected via the hardware DTLN
+ rather than a driver flag: the BCLR at SETUP/bus-reset then self-heals any parked state. */
+ if (pipe0_xfer_out(rusb)) {
+ pipe_xfer_complete(rhport, 0, false);
+ return true; // PID stays NAK (set by pipe0_xfer_out) until the next chunk is armed
+ }
}
rusb->DCPCTR = RUSB2_PIPE_CTR_PID_BUF;
} else {
@@ -370,6 +425,24 @@ static bool process_pipe0_xfer(rusb2_reg_t *rusb, int buffer_type, uint8_t ep_ad
return true;
}
+// Queue a zero-length IN packet. Returns false if the FIFO buffer wasn't free (double-buffered pipe
+// full, host not draining) so BVAL couldn't be written -- the caller retries on the next BRDY.
+static bool pipe_zlp_in(rusb2_reg_t *rusb, unsigned num) {
+ rusb->D0FIFOSEL = (uint16_t) num;
+ const bool ready = pipe_wait_for_ready(rusb, num);
+ if (ready) {
+ rusb->D0FIFOCTR = RUSB2_CFIFOCTR_BVAL_Msk;
+ }
+ rusb->D0FIFOSEL = 0;
+ // deselect completes within a few bus cycles (not host-dependent), but bound it anyway: this
+ // runs with the USB IRQ masked, where any stuck spin freezes the whole stack
+ uint32_t spin = RUSB2_FIFO_READY_SPIN;
+ while (rusb->D0FIFOSEL_b.CURPIPE) {
+ if (!spin--) { break; }
+ }
+ return ready;
+}
+
static bool process_pipe_xfer(rusb2_reg_t* rusb, int buffer_type, uint8_t ep_addr, void* buffer, uint16_t total_bytes)
{
const unsigned epn = tu_edpt_number(ep_addr);
@@ -379,23 +452,20 @@ static bool process_pipe_xfer(rusb2_reg_t* rusb, int buffer_type, uint8_t ep_add
TU_ASSERT(num);
pipe_state_t *pipe = &_dcd.pipe[num];
- pipe->ff = buffer_type;
- pipe->buf = buffer;
- pipe->length = total_bytes;
- pipe->remaining = total_bytes;
+ pipe->ff = buffer_type;
+ pipe->buf = buffer;
+ pipe->length = total_bytes;
+ pipe->remaining = total_bytes;
+ pipe->queued = true;
+ pipe->zlp_pending = false;
if (dir) {
/* IN */
if (total_bytes) {
pipe_xfer_in(rusb, num);
} else {
- /* ZLP */
- rusb->D0FIFOSEL = num;
- pipe_wait_for_ready(rusb, num);
- rusb->D0FIFOCTR = RUSB2_CFIFOCTR_BVAL_Msk;
- rusb->D0FIFOSEL = 0;
- /* if CURPIPE bits changes, check written value */
- while (rusb->D0FIFOSEL_b.CURPIPE) {}
+ /* ZLP: if the FIFO buffer isn't free yet, defer the queue to the next BRDY (see process_pipe_brdy) */
+ pipe->zlp_pending = !pipe_zlp_in(rusb, num);
}
} else {
// OUT
@@ -418,11 +488,11 @@ static bool process_pipe_xfer(rusb2_reg_t* rusb, int buffer_type, uint8_t ep_add
return true;
}
-static bool process_edpt_xfer(rusb2_reg_t* rusb, int buffer_type, uint8_t ep_addr, void* buffer, uint16_t total_bytes)
+static bool process_edpt_xfer(uint8_t rhport, rusb2_reg_t* rusb, int buffer_type, uint8_t ep_addr, void* buffer, uint16_t total_bytes)
{
const unsigned epn = tu_edpt_number(ep_addr);
if (0 == epn) {
- return process_pipe0_xfer(rusb, buffer_type, ep_addr, buffer, total_bytes);
+ return process_pipe0_xfer(rhport, rusb, buffer_type, ep_addr, buffer, total_bytes);
} else {
return process_pipe_xfer(rusb, buffer_type, ep_addr, buffer, total_bytes);
}
@@ -448,7 +518,15 @@ static void process_pipe_brdy(uint8_t rhport, unsigned num)
if (dir) {
/* IN */
- completed = pipe_xfer_in(rusb, num);
+ if (pipe->zlp_pending) {
+ // The submit-time ZLP couldn't be queued (FIFO full); a freed buffer plane lets us queue it
+ // now. Don't report completion until the ZLP is actually queued (and then sent, next BRDY),
+ // otherwise a spurious BRDY would complete a zero-length IN the host never received.
+ pipe->zlp_pending = !pipe_zlp_in(rusb, num);
+ completed = false;
+ } else {
+ completed = pipe_xfer_in(rusb, num);
+ }
} else {
// OUT
if (num) {
@@ -458,9 +536,7 @@ static void process_pipe_brdy(uint8_t rhport, unsigned num)
}
}
if (completed) {
- dcd_event_xfer_complete(rhport, pipe->ep,
- pipe->length - pipe->remaining,
- XFER_RESULT_SUCCESS, true);
+ pipe_xfer_complete(rhport, num, true);
// TU_LOG1("C %d %d\r\n", num, pipe->length - pipe->remaining);
}
}
@@ -585,19 +661,8 @@ bool dcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) {
#ifdef RUSB2_SUPPORT_HIGHSPEED
if ( rusb2_is_highspeed_rhport(rhport) ) {
- rusb->SYSCFG_b.HSE = 1;
-
- // leave CLKSEL as default (0x11) 24Mhz
-
- // Power and reset UTMI Phy
- uint16_t physet = (rusb->PHYSET | RUSB2_PHYSET_PLLRESET_Msk) & ~RUSB2_PHYSET_DIRPD_Msk;
- rusb->PHYSET = physet;
- R_BSP_SoftwareDelay((uint32_t) 1, BSP_DELAY_UNITS_MILLISECONDS);
- rusb->PHYSET_b.PLLRESET = 0;
-
- // set UTMI to operating mode and wait for PLL lock confirmation
- rusb->LPSTS_b.SUSPENDM = 1;
- while (!rusb->PLLSTA_b.PLLLOCK) {}
+ rusb->SYSCFG_b.HSE = TUD_OPT_HIGH_SPEED ? 1 : 0; // FS-only build: no HS chirp
+ rusb2_utmi_phy_powerup(rusb);
rusb->SYSCFG_b.DRPD = 0;
rusb->SYSCFG_b.USBE = 1;
@@ -702,10 +767,20 @@ bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const * ep_desc)
if ( !rusb2_is_highspeed_rhport(rhport) && mps > 256) {
return false;
}
+ } else if (xfer == TUSB_XFER_INTERRUPT) {
+ // Interrupt pipes (6-9) have a fixed 64-byte buffer even in high speed (RA6M5 UM 29.1);
+ // a larger PIPEMAXP would enumerate, then silently truncate every transfer
+ TU_ASSERT(mps <= 64);
}
- const unsigned num = find_pipe(xfer);
- TU_ASSERT(num);
+ // Re-opening an endpoint must reuse its pipe: usbd_edpt_close() is a no-op on ISO_ALLOC ports,
+ // so a class's close/open across SET_INTERFACE (e.g. video's notification endpoint) would
+ // otherwise allocate a second pipe with the same EPNUM and leak pipes until exhaustion.
+ unsigned num = _dcd.ep[dir][epn];
+ if (num == 0) {
+ num = find_pipe(xfer);
+ TU_ASSERT(num);
+ }
_dcd.pipe[num].ep = ep_addr;
_dcd.ep[dir][epn] = num;
@@ -713,13 +788,12 @@ bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const * ep_desc)
/* setup pipe */
dcd_int_disable(rhport);
+ rusb->PIPESEL = num;
if ( rusb2_is_highspeed_rhport(rhport) ) {
- // FIXME shouldn't be after pipe selection and config, also the BUFNMB should be changed
- // depending on the allocation scheme
+ // PIPEBUF is PIPESEL-windowed (RA6M5 UM 29.2.35): write it after selecting the pipe.
+ // FIXME BUFNMB is a fixed 0x08 for every pipe; a real per-pipe allocation scheme is needed.
rusb->PIPEBUF = 0x7C08;
}
-
- rusb->PIPESEL = num;
rusb->PIPEMAXP = mps;
volatile uint16_t *ctr = get_pipectr(rusb, num);
*ctr = RUSB2_PIPE_CTR_ACLRM_Msk | RUSB2_PIPE_CTR_SQCLR_Msk;
@@ -748,6 +822,8 @@ bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const * ep_desc)
return true;
}
+static void edpt_close(uint8_t rhport, uint8_t ep_addr);
+
void dcd_edpt_close_all(uint8_t rhport)
{
unsigned i = TU_ARRAY_SIZE(_dcd.pipe);
@@ -757,12 +833,14 @@ void dcd_edpt_close_all(uint8_t rhport)
if (!ep_addr) {
continue;
}
- dcd_edpt_close(rhport, (uint8_t)ep_addr);
+ edpt_close(rhport, (uint8_t)ep_addr);
}
dcd_int_enable(rhport);
}
-void dcd_edpt_close(uint8_t rhport, uint8_t ep_addr)
+// Internal helper: on this (ISO_ALLOC) IP the stack no longer calls dcd_edpt_close(); only
+// dcd_edpt_close_all() uses it to tear down each pipe.
+static void edpt_close(uint8_t rhport, uint8_t ep_addr)
{
rusb2_reg_t * rusb = RUSB2_REG(rhport);
const unsigned epn = tu_edpt_number(ep_addr);
@@ -774,24 +852,73 @@ void dcd_edpt_close(uint8_t rhport, uint8_t ep_addr)
*ctr = 0;
rusb->PIPESEL = (uint16_t)num;
rusb->PIPECFG = 0;
- _dcd.pipe[num].ep = 0;
+ _dcd.pipe[num].ep = 0;
+ _dcd.pipe[num].queued = false;
+ _dcd.pipe[num].zlp_pending = false;
_dcd.ep[dir][epn] = 0;
}
-#if 0
bool dcd_edpt_iso_alloc(uint8_t rhport, uint8_t ep_addr, uint16_t largest_packet_size) {
- (void)rhport;
- (void)ep_addr;
- (void)largest_packet_size;
- return false;
+ rusb2_reg_t * rusb = RUSB2_REG(rhport);
+ const unsigned epn = tu_edpt_number(ep_addr);
+ const unsigned dir = tu_edpt_dir(ep_addr);
+
+ // Fullspeed ISO is limited to 256 bytes
+ if (!rusb2_is_highspeed_rhport(rhport) && largest_packet_size > 256) {
+ return false;
+ }
+
+ // Reserve an ISO-capable pipe (1 or 2) once; it persists across altsetting changes so
+ // dcd_edpt_iso_activate() only has to re-arm it in place (no pipe free/realloc, which on this
+ // shared-register IP would churn PIPESEL/PIPECFG and disturb the other pipes).
+ const unsigned num = find_pipe(TUSB_XFER_ISOCHRONOUS);
+ TU_ASSERT(num);
+ _dcd.pipe[num].ep = ep_addr;
+ _dcd.ep[dir][epn] = num;
+
+ dcd_int_disable(rhport);
+ rusb->PIPESEL = (uint16_t) num;
+ if (rusb2_is_highspeed_rhport(rhport)) {
+ // PIPEBUF is PIPESEL-windowed (RA6M5 UM 29.2.35): write it after selecting the pipe.
+ // FIXME (as in dcd_edpt_open): BUFNMB is a fixed 0x08 for every pipe; a real allocator is needed.
+ rusb->PIPEBUF = 0x7C08;
+ }
+ rusb->PIPEMAXP = largest_packet_size;
+ volatile uint16_t *ctr = get_pipectr(rusb, num);
+ *ctr = RUSB2_PIPE_CTR_ACLRM_Msk | RUSB2_PIPE_CTR_SQCLR_Msk;
+ *ctr = 0; // leave the pipe NAKing until activated
+ rusb->PIPECFG = (uint16_t) ((dir << 4) | epn | RUSB2_PIPECFG_TYPE_ISO | RUSB2_PIPECFG_DBLB_Msk);
+ rusb->BRDYSTS = (uint16_t) (0x3FFu ^ TU_BIT(num));
+ rusb->BRDYENB |= TU_BIT(num);
+ dcd_int_enable(rhport);
+ return true;
}
bool dcd_edpt_iso_activate(uint8_t rhport, const tusb_desc_endpoint_t *desc_ep) {
- (void)rhport;
- (void)desc_ep;
- return false;
+ rusb2_reg_t * rusb = RUSB2_REG(rhport);
+ const uint8_t ep_addr = desc_ep->bEndpointAddress;
+ const unsigned epn = tu_edpt_number(ep_addr);
+ const unsigned dir = tu_edpt_dir(ep_addr);
+ const unsigned num = _dcd.ep[dir][epn];
+ TU_ASSERT(num); // must have been iso-alloc'd
+
+ dcd_int_disable(rhport);
+ rusb->PIPESEL = (uint16_t) num;
+ rusb->PIPEMAXP = tu_edpt_packet_size(desc_ep);
+ volatile uint16_t *ctr = get_pipectr(rusb, num);
+ *ctr = RUSB2_PIPE_CTR_ACLRM_Msk | RUSB2_PIPE_CTR_SQCLR_Msk; // abort in-flight + reset data toggle
+ *ctr = 0;
+ // a transfer armed before SET_INTERFACE survives to here (no dcd close on this port): drop the
+ // stale bookkeeping so a BRDY firing before the class re-arms can't replay it
+ pipe_state_t *pipe = &_dcd.pipe[num];
+ pipe->buf = NULL;
+ pipe->remaining = 0;
+ pipe->queued = false;
+ pipe->zlp_pending = false;
+ *ctr = RUSB2_PIPE_CTR_PID_BUF; // enable
+ dcd_int_enable(rhport);
+ return true;
}
-#endif
bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t * buffer, uint16_t total_bytes, bool is_isr)
{
@@ -799,7 +926,7 @@ bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t * buffer, uint16_t t
rusb2_reg_t* rusb = RUSB2_REG(rhport);
dcd_int_disable(rhport);
- bool r = process_edpt_xfer(rusb, 0, ep_addr, buffer, total_bytes);
+ bool r = process_edpt_xfer(rhport, rusb, 0, ep_addr, buffer, total_bytes);
dcd_int_enable(rhport);
return r;
@@ -812,7 +939,7 @@ bool dcd_edpt_xfer_fifo(uint8_t rhport, uint8_t ep_addr, tu_fifo_t * ff, uint16_
rusb2_reg_t* rusb = RUSB2_REG(rhport);
dcd_int_disable(rhport);
- bool r = process_edpt_xfer(rusb, 1, ep_addr, ff, total_bytes);
+ bool r = process_edpt_xfer(rhport, rusb, 1, ep_addr, ff, total_bytes);
dcd_int_enable(rhport);
return r;
@@ -847,7 +974,19 @@ void dcd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr)
} else {
const unsigned num = _dcd.ep[0][tu_edpt_number(ep_addr)];
rusb->PIPESEL = (uint16_t)num;
- if (rusb->PIPECFG_b.TYPE != 1) {
+ // Drop any packet parked in the buffer while halted: a data-OUT packet the host sent before
+ // aborting its transfer would otherwise be delivered into the next read after recovery
+ // (BOT reset + clear-halt re-arms a 31-byte CBW read which then receives stale WRITE data,
+ // "SCSI CBW is not valid" -> stall -> reset loop; ra6m5 msc write wedge).
+ *ctr = RUSB2_PIPE_CTR_ACLRM_Msk;
+ *ctr = 0;
+ // Non-bulk OUT re-enables straight away. Bulk OUT is normally armed together with its transaction
+ // counter (TRE) by process_pipe_xfer(), so we don't blindly re-enable it here — but if a receive
+ // was already armed (still queued), SQCLR above just left it NAKing. Re-assert BUF so it keeps
+ // receiving; the class driver still considers that read submitted and never re-arms it, so
+ // otherwise the endpoint NAKs forever (usbtest toggle test 29 clears the halt on an armed pipe).
+ // `queued` (not `buf`) is the armed test: a zero-length OUT read has buf==NULL yet is armed.
+ if (rusb->PIPECFG_b.TYPE != 1 || _dcd.pipe[num].queued) {
*ctr = RUSB2_PIPE_CTR_PID_BUF;
}
}
diff --git a/src/portable/renesas/rusb2/hcd_rusb2.c b/src/portable/renesas/rusb2/hcd_rusb2.c
index 849551d27..489162e54 100644
--- a/src/portable/renesas/rusb2/hcd_rusb2.c
+++ b/src/portable/renesas/rusb2/hcd_rusb2.c
@@ -454,11 +454,9 @@ bool hcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) {
if (rusb2_is_highspeed_rhport(rhport) ) {
rusb->SYSCFG_b.HSE = 1;
rusb->PHYSET_b.HSEB = 0;
- rusb->PHYSET_b.DIRPD = 0;
- R_BSP_SoftwareDelay((uint32_t) 1, BSP_DELAY_UNITS_MILLISECONDS);
- rusb->PHYSET_b.PLLRESET = 0;
- rusb->LPSTS_b.SUSPENDM = 1;
- while ( !rusb->PLLSTA_b.PLLLOCK );
+ // same PHY reference-clock + power-up requirements as dcd_init: without CLKSEL matching the
+ // board XTAL the PLL never locks and the wait below would spin forever (e.g. EK-RA8M1, 20 MHz)
+ rusb2_utmi_phy_powerup(rusb);
rusb->SYSCFG_b.DRPD = 1;
rusb->SYSCFG_b.DCFM = 1;
rusb->SYSCFG_b.DPRPU = 0;
diff --git a/src/portable/renesas/rusb2/rusb2_ra.h b/src/portable/renesas/rusb2/rusb2_ra.h
index e5945ffe2..0954d0d25 100644
--- a/src/portable/renesas/rusb2/rusb2_ra.h
+++ b/src/portable/renesas/rusb2/rusb2_ra.h
@@ -49,6 +49,23 @@ typedef struct {
#define rusb2_is_highspeed_rhport(_p) (_p == 1)
#define rusb2_is_highspeed_reg(_reg) (_reg == RUSB2_REG(1))
+
+ // UTMI PHY reference clock is the main oscillator: PHYSET.CLKSEL must match the board XTAL
+ // before the PHY PLL is released (RA6M5 UM R01UH0891 29.2.17: 00=12 MHz, 10=20 MHz,
+ // 11=24 MHz reset default). EK-RA6M5 runs 24 MHz (default works); EK-RA8M1 runs 20 MHz and
+ // never locks/chirps on the default. A board with a non-standard USB clocking scheme can
+ // pre-define RUSB2_PHYSET_CLKSEL_VALUE to override this selection.
+ #ifndef RUSB2_PHYSET_CLKSEL_VALUE
+ #if BSP_CFG_XTAL_HZ == 12000000
+ #define RUSB2_PHYSET_CLKSEL_VALUE 0u
+ #elif BSP_CFG_XTAL_HZ == 20000000
+ #define RUSB2_PHYSET_CLKSEL_VALUE 2u
+ #elif BSP_CFG_XTAL_HZ == 24000000
+ #define RUSB2_PHYSET_CLKSEL_VALUE 3u
+ #else
+ #error "USBHS UTMI PHY: no PHYSET.CLKSEL encoding for this BSP_CFG_XTAL_HZ; define RUSB2_PHYSET_CLKSEL_VALUE"
+ #endif
+ #endif
#else
#define RUSB2_CONTROLLER_COUNT 1
@@ -84,6 +101,31 @@ TU_ATTR_ALWAYS_INLINE static inline void rusb2_int_disable(uint8_t rhport) {
TU_ATTR_ALWAYS_INLINE static inline void rusb2_phy_init(void) {
}
+#ifdef RUSB2_SUPPORT_HIGHSPEED
+// UTMI PHY power-up per the FSP reference sequence (r_usb_preg_access.c), shared by dcd_init and
+// hcd_init: program CLKSEL to the board XTAL while the PHY is powered down (DIRPD=1), 1 us,
+// release DIRPD, 1 ms, release PLLRESET, then wait for PLL lock. Changing CLKSEL as the PHY
+// powers up gets mis-sampled (EK-RA8M1, 20 MHz).
+static inline void rusb2_utmi_phy_powerup(rusb2_reg_t* rusb) {
+ uint16_t physet = rusb->PHYSET | RUSB2_PHYSET_DIRPD_Msk;
+ rusb->PHYSET = physet;
+ #ifdef RUSB2_PHYSET_CLKSEL_VALUE
+ physet = (uint16_t) ((physet & ~RUSB2_PHYSET_CLKSEL_Msk) |
+ (RUSB2_PHYSET_CLKSEL_VALUE << RUSB2_PHYSET_CLKSEL_Pos));
+ rusb->PHYSET = physet;
+ #endif
+ R_BSP_SoftwareDelay((uint32_t) 1, BSP_DELAY_UNITS_MICROSECONDS);
+ physet &= (uint16_t) ~RUSB2_PHYSET_DIRPD_Msk;
+ rusb->PHYSET = physet;
+ R_BSP_SoftwareDelay((uint32_t) 1, BSP_DELAY_UNITS_MILLISECONDS);
+ rusb->PHYSET_b.PLLRESET = 0;
+
+ // set UTMI to operating mode and wait for PLL lock confirmation
+ rusb->LPSTS_b.SUSPENDM = 1;
+ while (!rusb->PLLSTA_b.PLLLOCK) {}
+}
+#endif
+
#ifdef __cplusplus
}
#endif
diff --git a/src/portable/st/stm32_fsdev/dcd_stm32_fsdev.c b/src/portable/st/stm32_fsdev/dcd_stm32_fsdev.c
index aecd689b3..ba05818b6 100644
--- a/src/portable/st/stm32_fsdev/dcd_stm32_fsdev.c
+++ b/src/portable/st/stm32_fsdev/dcd_stm32_fsdev.c
@@ -835,7 +835,17 @@ void dcd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr) {
ep_reg &= U_EPREG_MASK | EP_STAT_MASK(dir) | EP_DTOG_MASK(dir);
if (!ep_is_iso(ep_reg)) {
- ep_change_status(&ep_reg, dir, EP_STAT_NAK);
+ // Only knock a genuinely STALLED endpoint down to NAK (the class then re-arms it). If the
+ // endpoint is armed (VALID) - e.g. a clear-halt used purely to reset the data toggle, as in
+ // usbtest case 29 - leave STAT untouched so the in-flight transfer isn't disarmed with no
+ // completion, which would leak the usbd claim and starve the endpoint. Masking the STAT bits
+ // to 0 writes no toggle, so an armed/idle endpoint keeps its current status.
+ const uint8_t stat_pos = (uint8_t) (U_EPTX_STAT_Pos + (dir == TUSB_DIR_IN ? 0u : 8u));
+ if (((ep_reg >> stat_pos) & 0x3u) == EP_STAT_STALL) {
+ ep_change_status(&ep_reg, dir, EP_STAT_NAK);
+ } else {
+ ep_reg &= ~EP_STAT_MASK(dir);
+ }
}
ep_change_dtog(&ep_reg, dir, 0); // Reset to DATA0
ep_write(ep_idx, ep_reg, true);
diff --git a/src/portable/st/stm32_fsdev/fsdev_apm32.h b/src/portable/st/stm32_fsdev/fsdev_apm32.h
new file mode 100644
index 000000000..5031445e8
--- /dev/null
+++ b/src/portable/st/stm32_fsdev/fsdev_apm32.h
@@ -0,0 +1,77 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2024, hathach (tinyusb.org)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+#ifndef TUSB_FSDEV_APM32_H
+#define TUSB_FSDEV_APM32_H
+
+#include "common/tusb_compiler.h"
+
+#if CFG_TUSB_MCU == OPT_MCU_APM32F0XX
+ #include "apm32f0xx.h"
+#endif
+
+#define FSDEV_USE_SBUF_ISO 0
+#define FSDEV_REG_BASE ((uint32_t)(USBD_BASE))
+#define FSDEV_PMA_BASE ((uint32_t)(USBD_BASE + 0x400UL))
+
+#ifndef CFG_TUD_FSDEV_DOUBLE_BUFFERED_ISO_EP
+ #define CFG_TUD_FSDEV_DOUBLE_BUFFERED_ISO_EP 0
+#endif
+
+//--------------------------------------------------------------------+
+//
+//--------------------------------------------------------------------+
+
+static const IRQn_Type fsdev_irq[] = {
+ USBD_IRQn
+};
+enum { FSDEV_IRQ_NUM = TU_ARRAY_SIZE(fsdev_irq) };
+
+TU_ATTR_ALWAYS_INLINE static inline void fsdev_int_enable(uint8_t rhport) {
+ (void)rhport;
+ for (uint8_t i = 0; i < FSDEV_IRQ_NUM; i++) {
+ NVIC_EnableIRQ(fsdev_irq[i]);
+ }
+}
+
+TU_ATTR_ALWAYS_INLINE static inline void fsdev_int_disable(uint8_t rhport) {
+ (void)rhport;
+ for (uint8_t i = 0; i < FSDEV_IRQ_NUM; i++) {
+ NVIC_DisableIRQ(fsdev_irq[i]);
+ }
+}
+
+TU_ATTR_ALWAYS_INLINE static inline void fsdev_disconnect(uint8_t rhport) {
+ (void) rhport;
+ FSDEV_REG->CNTR |= U_CNTR_PDWN;
+ FSDEV_REG->BCDR &= ~U_BCDR_DPPU;
+}
+
+TU_ATTR_ALWAYS_INLINE static inline void fsdev_connect(uint8_t rhport) {
+ (void) rhport;
+ FSDEV_REG->CNTR &= ~U_CNTR_PDWN;
+ FSDEV_REG->BCDR |= U_BCDR_DPPU;
+}
+
+#endif
diff --git a/src/portable/st/stm32_fsdev/fsdev_at32.h b/src/portable/st/stm32_fsdev/fsdev_at32.h
index 212c3b86d..9138dc101 100644
--- a/src/portable/st/stm32_fsdev/fsdev_at32.h
+++ b/src/portable/st/stm32_fsdev/fsdev_at32.h
@@ -17,7 +17,7 @@
#define FSDEV_USE_SBUF_ISO 0
#define FSDEV_REG_BASE (APB1PERIPH_BASE + 0x00005C00UL)
-#define FSDEV_PMA_BASE (APB1PERIPH_BASE + 0x00006000UL)
+#define FSDEV_PMA_BASE (APB1PERIPH_BASE + 0x00007800UL)
#ifndef CFG_TUD_FSDEV_DOUBLE_BUFFERED_ISO_EP
#define CFG_TUD_FSDEV_DOUBLE_BUFFERED_ISO_EP 0
diff --git a/src/portable/st/stm32_fsdev/fsdev_common.c b/src/portable/st/stm32_fsdev/fsdev_common.c
index 2b573899a..9c9233d12 100644
--- a/src/portable/st/stm32_fsdev/fsdev_common.c
+++ b/src/portable/st/stm32_fsdev/fsdev_common.c
@@ -33,6 +33,11 @@ void fsdev_core_reset(void) {
// Clear pending interrupts
FSDEV_REG->ISTR = 0;
+
+ #if (CFG_TUSB_MCU == OPT_MCU_AT32F403A_407) || (CFG_TUSB_MCU == OPT_MCU_AT32F413)
+ // Enable larger PMA area
+ CRM->misc1_bit.usbbufs = 1;
+ #endif
}
// De-initialize the USB Core
diff --git a/src/portable/st/stm32_fsdev/fsdev_common.h b/src/portable/st/stm32_fsdev/fsdev_common.h
index ebde44bf7..00d9b513a 100644
--- a/src/portable/st/stm32_fsdev/fsdev_common.h
+++ b/src/portable/st/stm32_fsdev/fsdev_common.h
@@ -284,6 +284,8 @@ typedef struct {
#include "fsdev_ch32.h"
#elif defined(TUP_USBIP_FSDEV_AT32)
#include "fsdev_at32.h"
+#elif defined(TUP_USBIP_FSDEV_APM32)
+ #include "fsdev_apm32.h"
#else
#error "Unknown USB IP"
#endif
diff --git a/src/portable/synopsys/dwc2/dcd_dwc2.c b/src/portable/synopsys/dwc2/dcd_dwc2.c
index 6c88b4f27..b2f1a93a4 100644
--- a/src/portable/synopsys/dwc2/dcd_dwc2.c
+++ b/src/portable/synopsys/dwc2/dcd_dwc2.c
@@ -393,10 +393,6 @@ static void edpt_schedule_packets(uint8_t rhport, const uint8_t epnum, const uin
}
dep->diepdma = (uintptr_t) xfer->buffer;
dep->diepctl = depctl.value; // enable endpoint
- // Advance buffer pointer for EP0
- if (epnum == 0) {
- xfer->buffer += total_bytes;
- }
} else
#endif
{
@@ -732,6 +728,8 @@ static void handle_bus_reset(uint8_t rhport) {
tu_memclr(xfer_status, sizeof(xfer_status));
+ _dcd_data.ep0_pending[TUSB_DIR_OUT] = 0;
+ _dcd_data.ep0_pending[TUSB_DIR_IN] = 0;
_dcd_data.sof_en = false;
_dcd_data.allocated_epin_count = 0;
@@ -1009,6 +1007,10 @@ static void handle_epout_dma(uint8_t rhport, uint8_t epnum, dwc2_doepint_t doepi
if (edpt_is_enabled(epin0)) {
edpt_disable(rhport, 0x80, false);
}
+ // a new SETUP aborts any in-progress control transfer: drop leftover EP0 chunking state so a
+ // stale latched completion cannot re-arm from it
+ _dcd_data.ep0_pending[TUSB_DIR_OUT] = 0;
+ _dcd_data.ep0_pending[TUSB_DIR_IN] = 0;
dcd_dcache_invalidate(_dcd_usbbuf.setup_buffer, sizeof(_dcd_usbbuf.setup_buffer));
@@ -1029,24 +1031,37 @@ static void handle_epout_dma(uint8_t rhport, uint8_t epnum, dwc2_doepint_t doepi
// only handle data skip if it is setup or status related
// Normal OUT transfer complete
if (!doepint_bm.status_phase_rx && !doepint_bm.setup_packet_rx) {
+ xfer_ctl_t* xfer = XFER_CTL_BASE(epnum, TUSB_DIR_OUT);
if ((epnum == 0) && _dcd_data.ep0_pending[TUSB_DIR_OUT]) {
- // EP0 can only handle one packet Schedule another packet to be received.
+ // EP0 can only handle one packet: invalidate and advance past the received bytes, then
+ // schedule the next.
+ if (xfer->buffer != NULL) {
+ dcd_dcache_invalidate(xfer->buffer, CFG_TUD_ENDPOINT0_SIZE);
+ xfer->buffer += CFG_TUD_ENDPOINT0_SIZE;
+ }
edpt_schedule_packets(rhport, epnum, TUSB_DIR_OUT);
} else {
dwc2_dep_t* epout = &dwc2->epout[epnum];
- xfer_ctl_t* xfer = XFER_CTL_BASE(epnum, TUSB_DIR_OUT);
// determine actual received bytes
const dwc2_ep_tsize_t tsiz = {.value = epout->tsiz};
const uint16_t remain = tsiz.xfer_size;
xfer->total_len -= remain;
+ // EP0 invalidates only this (final) chunk's DMA-written bytes: DOEPDMA "is incremented on
+ // every AHB transaction" (databook 7.1.83), i.e. it points past the last word written.
+ // Read it before dma_setup_prepare() re-targets it at the setup buffer
+ uint16_t inval_len = xfer->total_len;
+ if (epnum == 0) {
+ inval_len = (uint16_t)(epout->doepdma - (uintptr_t)xfer->buffer);
+ }
+
// prepare EP0 for next setup
if(epnum == 0) {
dma_setup_prepare(rhport);
}
- dcd_dcache_invalidate(xfer->buffer, xfer->total_len);
+ dcd_dcache_invalidate(xfer->buffer, inval_len);
dcd_event_xfer_complete(rhport, epnum, xfer->total_len, XFER_RESULT_SUCCESS, true);
}
}
@@ -1058,7 +1073,10 @@ static void handle_epin_dma(uint8_t rhport, uint8_t epnum, dwc2_diepint_t diepin
if (diepint_bm.xfer_complete) {
if ((epnum == 0) && _dcd_data.ep0_pending[TUSB_DIR_IN]) {
- // EP0 can only handle one packet. Schedule another packet to be transmitted.
+ // EP0 can only handle one packet: advance past the sent bytes, then schedule the next.
+ if (xfer->buffer != NULL) {
+ xfer->buffer += CFG_TUD_ENDPOINT0_SIZE;
+ }
edpt_schedule_packets(rhport, epnum, TUSB_DIR_IN);
} else {
dcd_event_xfer_complete(rhport, epnum | TUSB_DIR_IN_MASK, xfer->total_len, XFER_RESULT_SUCCESS, true);
@@ -1125,7 +1143,12 @@ static void handle_incomplete_iso_in(uint8_t rhport) {
xfer_ctl_t *xfer = XFER_CTL_BASE(epnum, TUSB_DIR_IN);
if (xfer->iso_retry > 0) {
xfer->iso_retry--;
- // Restart ISO transfe: re-write TSIZ and CTL
+ // Restart ISO transfer: re-write DMA address, TSIZ, and CTL
+ #if CFG_TUD_DWC2_DMA_ENABLE
+ if (dma_device_enabled(dwc2)) {
+ epin->diepdma = (uintptr_t) xfer->buffer;
+ }
+ #endif
dwc2_ep_tsize_t deptsiz = {.value = 0};
deptsiz.xfer_size = xfer->total_len;
deptsiz.packet_count = tu_div_ceil(xfer->total_len, xfer->max_size);
diff --git a/src/portable/wch/ch32_usbfs_reg.h b/src/portable/wch/ch32_usbfs_reg.h
index 8bac103fe..5b037281f 100644
--- a/src/portable/wch/ch32_usbfs_reg.h
+++ b/src/portable/wch/ch32_usbfs_reg.h
@@ -179,6 +179,14 @@
#endif
#endif
+// CH32V20x/V30x/F20x USBFS gives endpoint 3 a 1023-byte isochronous packet (CH32FV2x_V3xRM ch23:
+// every endpoint is 64 B except EP3 = 1023 B, from EP3's 10-bit R16_UEP3_T_LEN field plus a single
+// contiguous >=1023 B DMA buffer — NOT double-buffering, which only yields 2x64 B).
+// CH32V103/X035/CH58x cap every endpoint at 64 B. dcd_ch32_usbfs.c reads this to size EP3's buffer.
+#if CFG_TUSB_MCU == OPT_MCU_CH32V20X || CFG_TUSB_MCU == OPT_MCU_CH32V307 || CFG_TUSB_MCU == OPT_MCU_CH32F20X
+ #define CH32_USBFS_EP3_1023_BUFSIZE 1
+#endif
+
#ifdef __GNUC__
#pragma GCC diagnostic pop
#endif
diff --git a/src/portable/wch/dcd_ch32_usbfs.c b/src/portable/wch/dcd_ch32_usbfs.c
index a6458748a..ec521224e 100644
--- a/src/portable/wch/dcd_ch32_usbfs.c
+++ b/src/portable/wch/dcd_ch32_usbfs.c
@@ -16,6 +16,17 @@
/* private defines */
#define EP_MAX (8)
+ // EP3 IN buffer size. CH32V20x/V30x/F20x USBFS support full-speed iso packets up to 1023 B on
+ // endpoint 3 (every other endpoint is 64 B); those parts set CH32_USBFS_EP3_1023_BUFSIZE in
+ // ch32_usbfs_reg.h. V103/X035/CH58x cap every endpoint at 64 B. Overridable per project.
+ #ifndef CFG_TUD_WCH_USBFS_EP3_BUFSIZE
+ #ifdef CH32_USBFS_EP3_1023_BUFSIZE
+ #define CFG_TUD_WCH_USBFS_EP3_BUFSIZE 1023
+ #else
+ #define CFG_TUD_WCH_USBFS_EP3_BUFSIZE 64
+ #endif
+ #endif
+
// Struct-based EP register access (uniform layout). CH58X has a different register map and
// defines EP_DMA/EP_TX_LEN/EP_CTRL itself in ch32_usbfs_reg.h.
#if CFG_TUSB_MCU == OPT_MCU_CH583
@@ -107,7 +118,7 @@ struct usb_xfer {
static struct {
bool ep0_tog;
- bool isochronous[EP_MAX];
+ bool isochronous[EP_MAX][2]; // per [ep][dir]: an ep number may be iso in one direction
struct usb_xfer xfer[EP_MAX][2];
#ifdef CH32_USBFS_EP4_SHARES_EP0
// CH58X buffers laid out by hand so EP0/EP4 don't burn two unused buffer[] slots. EP0 and EP4
@@ -123,21 +134,23 @@ static struct {
TU_ATTR_ALIGNED(4) uint8_t ep6_buffer[2][64];
TU_ATTR_ALIGNED(4) uint8_t ep7_buffer[2][64];
#else
+ // Every endpoint gets a 64-byte OUT + 64-byte IN buffer.
TU_ATTR_ALIGNED(4) uint8_t buffer[EP_MAX][2][64];
- // EP3 IN gets an enlarged buffer for full-speed isochronous (packets up to 1023 B).
+ #if CFG_TUD_WCH_USBFS_EP3_BUFSIZE > 64
+ // ...except EP3, which supports full-speed iso packets up to 1023 B on CH32V20x/V30x/F20x, so its
+ // IN buffer is enlarged (OUT stays 64 B; an OUT transfer >64 B on EP3 would overwrite queued IN).
TU_ATTR_ALIGNED(4) struct {
- // OUT transfers >64 bytes will overwrite queued IN data!
uint8_t out[64];
- uint8_t in[1023];
+ uint8_t in[CFG_TUD_WCH_USBFS_EP3_BUFSIZE];
uint8_t pad;
} ep3_buffer;
+ #endif
#endif
} data;
// DMA / copy buffer pointers per endpoint. The WCH USBFS buffer holds OUT (RX) at offset 0 and
-// IN (TX) at +64; EP0 is half-duplex and reuses its OUT chunk for IN; EP3 has an enlarged IN
-// buffer for throughput. On CH58X, EP0/EP4 share ep0_ep4_buffer and the regular endpoints use
-// their own named buffer (see the struct above).
+// IN (TX) at +64; EP0 is half-duplex and reuses its OUT chunk for IN. On CH58X, EP0/EP4 share
+// ep0_ep4_buffer and the regular endpoints use their own named buffer (see the struct above).
#ifdef CH32_USBFS_EP4_SHARES_EP0
// OUT base of the regular CH58X endpoints (EP1/2/3/5/6/7; EP0/EP4 share ep0_ep4_buffer).
static inline uint8_t* ch58x_ep_buffer(uint8_t ep) {
@@ -157,7 +170,9 @@ static inline uint32_t ep_dma_addr(uint8_t ep) {
if (ep == 0 || ep == 4) { return (uint32_t) &data.ep0_ep4_buffer[0]; } // EP4 shares EP0's DMA
return (uint32_t) ch58x_ep_buffer(ep);
#else
- if (ep == 3) { return (uint32_t) &data.ep3_buffer.out[0]; }
+ #if CFG_TUD_WCH_USBFS_EP3_BUFSIZE > 64
+ if (ep == 3) { return (uint32_t) &data.ep3_buffer.out[0]; } // EP3 has an enlarged IN buffer
+ #endif
return (uint32_t) &data.buffer[ep][0];
#endif
}
@@ -168,7 +183,9 @@ static inline uint8_t* ep_out_buf(uint8_t ep) {
if (ep == 4) { return &data.ep0_ep4_buffer[64]; }
return ch58x_ep_buffer(ep);
#else
+ #if CFG_TUD_WCH_USBFS_EP3_BUFSIZE > 64
if (ep == 3) { return data.ep3_buffer.out; }
+ #endif
return data.buffer[ep][TUSB_DIR_OUT];
#endif
}
@@ -180,7 +197,9 @@ static inline uint8_t* ep_in_buf(uint8_t ep) {
return ch58x_ep_buffer(ep) + 64; // IN at +64 within the endpoint's 128-byte buffer
#else
if (ep == 0) { return data.buffer[0][TUSB_DIR_OUT]; } // EP0 half-duplex: IN reuses OUT chunk
- if (ep == 3) { return data.ep3_buffer.in; }
+ #if CFG_TUD_WCH_USBFS_EP3_BUFSIZE > 64
+ if (ep == 3) { return data.ep3_buffer.in; } // enlarged IN buffer for full-speed iso
+ #endif
return data.buffer[ep][TUSB_DIR_IN];
#endif
}
@@ -202,9 +221,8 @@ static void update_in(uint8_t rhport, uint8_t ep, bool force) {
if (force || xfer->len) {
size_t len = TU_MIN(xfer->max_size, xfer->len);
#if CFG_TUSB_MCU == OPT_MCU_CH583
- // Every CH58x endpoint buffer is 64 bytes. Isochronous (which would push max_size up to 1023)
- // is refused in dcd_edpt_iso_alloc(), but some classes (e.g. video) ignore that result, so cap
- // the copy here to guarantee we never write past the buffer into a neighbouring endpoint's.
+ // Every CH58x endpoint buffer is 64 bytes; cap the copy so an iso mps a class mistakenly set
+ // larger can't write past the buffer into a neighbouring endpoint's.
len = TU_MIN(len, 64u);
#endif
memcpy(ep_in_buf(ep), xfer->buffer, len);
@@ -216,7 +234,7 @@ static void update_in(uint8_t rhport, uint8_t ep, bool force) {
if (ep == 0) {
ep_tx_ctrl_set(0, USBFS_EP_T_RES_ACK | (data.ep0_tog ? USBFS_EP_T_TOG : 0));
data.ep0_tog = !data.ep0_tog;
- } else if (data.isochronous[ep]) {
+ } else if (data.isochronous[ep][TUSB_DIR_IN]) {
ep_tx_set_response(ep, USBFS_EP_T_RES_NYET);
} else {
ep_tx_set_response(ep, USBFS_EP_T_RES_ACK);
@@ -225,7 +243,7 @@ static void update_in(uint8_t rhport, uint8_t ep, bool force) {
xfer->valid = false;
if (ep == 0) {
ep_tx_ctrl_set(0, USBFS_EP_T_RES_NAK | (data.ep0_tog ? USBFS_EP_T_TOG : 0));
- } else if (!data.isochronous[ep]) {
+ } else if (!data.isochronous[ep][TUSB_DIR_IN]) {
ep_tx_set_response(ep, USBFS_EP_T_RES_NAK);
}
dcd_event_xfer_complete(rhport, ep | TUSB_DIR_IN_MASK, xfer->processed_len, XFER_RESULT_SUCCESS, true);
@@ -254,7 +272,7 @@ static void update_out(uint8_t rhport, uint8_t ep, size_t rx_len) {
ep_rx_set_response(0, USBFS_EP_R_RES_NAK);
} else {
uint8_t rx_res =
- data.isochronous[ep] ? USBFS_EP_R_RES_NYET : (xfer->valid ? USBFS_EP_R_RES_ACK : USBFS_EP_R_RES_NAK);
+ data.isochronous[ep][TUSB_DIR_OUT] ? USBFS_EP_R_RES_NYET : (xfer->valid ? USBFS_EP_R_RES_ACK : USBFS_EP_R_RES_NAK);
ep_rx_set_response(ep, rx_res);
}
}
@@ -319,12 +337,14 @@ void dcd_int_handler(uint8_t rhport) {
// Drop an OUT packet whose data toggle doesn't match what we expect -- a host retransmit
// after a lost ACK, or a host that doesn't alternate DATA0/DATA1. The hardware auto-toggle
// does not reject these on its own, so the check is needed on every variant. EP0 keeps its
- // own toggle via the SETUP/status flow and is exempt.
- if (ep != 0 && !(int_st & USBFS_INT_ST_TOG_OK)) { break; }
+ // own toggle via the SETUP/status flow and is exempt; isochronous is DATA0-only (no toggle),
+ // so its packets must not be toggle-checked.
+ if (ep != 0 && !data.isochronous[ep][TUSB_DIR_OUT] && !(int_st & USBFS_INT_ST_TOG_OK)) { break; }
#ifdef CH32_USBFS_EP_MANUAL_TOG
// CH58x has no hardware auto-toggle: advance the expected RX toggle after each accepted packet
// (EP0 included -- it also has no auto-toggle and a control-OUT data stage can span packets).
- EP_CTRL(ep) ^= USBFS_EPC_R_TOG;
+ // Iso endpoints are DATA0-only, so leave them alone (matches the PID_IN path).
+ if (!data.isochronous[ep][TUSB_DIR_OUT]) { EP_CTRL(ep) ^= USBFS_EPC_R_TOG; }
#endif
update_out(rhport, ep, rx_len);
break;
@@ -333,7 +353,8 @@ void dcd_int_handler(uint8_t rhport) {
case PID_IN:
#ifdef CH32_USBFS_EP_MANUAL_TOG
// Manual toggle: flip the TX toggle after each ACK'd IN packet (EP0 manages its own).
- if (ep != 0) { EP_CTRL(ep) ^= USBFS_EPC_T_TOG; }
+ // Isochronous transfers are DATA0-only (no toggle), so leave iso endpoints alone.
+ if (ep != 0 && !data.isochronous[ep][TUSB_DIR_IN]) { EP_CTRL(ep) ^= USBFS_EPC_T_TOG; }
#endif
update_in(rhport, ep, false);
break;
@@ -443,6 +464,7 @@ bool dcd_edpt_open(uint8_t rhport, const tusb_desc_endpoint_t *desc_ep) {
uint8_t dir = tu_edpt_dir(desc_ep->bEndpointAddress);
TU_ASSERT(ep < EP_MAX);
+ data.isochronous[ep][dir] = false; // (re)opening as a non-iso endpoint clears any stale iso flag
data.xfer[ep][dir].max_size = tu_edpt_packet_size(desc_ep);
if (ep != 0) {
@@ -464,31 +486,38 @@ void dcd_edpt_close_all(uint8_t rhport) {
bool dcd_edpt_iso_alloc(uint8_t rhport, uint8_t ep_addr, uint16_t largest_packet_size) {
(void)rhport;
- (void)ep_addr;
- (void)largest_packet_size;
-#if CFG_TUSB_MCU == OPT_MCU_CH583
- // No isochronous support on CH58x: its 8-bit T_LEN caps a packet at 255B and the endpoints use
- // plain 64-byte buffers, so accepting an iso max_size (up to 1023) would let update_in()/
- // update_out() run off the end of the buffer into neighbouring ones. Refuse it outright.
- return false;
-#else
uint8_t ep = tu_edpt_number(ep_addr);
uint8_t dir = tu_edpt_dir(ep_addr);
+ TU_ASSERT(ep < EP_MAX);
- data.isochronous[ep] = true;
+ // Endpoint buffers are 64 B, except EP3 IN which is enlarged for full-speed iso on the parts that
+ // support 1023-byte EP3 packets (CH32V20x/V30x/F20x; CFG_TUD_WCH_USBFS_EP3_BUFSIZE). Reject a
+ // larger mps rather than running off the end into the neighbouring endpoint's memory.
+ uint16_t max_packet = 64;
+#if CFG_TUD_WCH_USBFS_EP3_BUFSIZE > 64
+ if (ep == 3 && dir == TUSB_DIR_IN) { max_packet = CFG_TUD_WCH_USBFS_EP3_BUFSIZE; }
+#endif
+ TU_VERIFY(largest_packet_size <= max_packet);
+
+ data.isochronous[ep][dir] = true;
data.xfer[ep][dir].max_size = largest_packet_size;
return true;
-#endif
}
bool dcd_edpt_iso_activate(uint8_t rhport, const tusb_desc_endpoint_t *desc_ep) {
(void)rhport;
- (void)desc_ep;
-#if CFG_TUSB_MCU == OPT_MCU_CH583
- return false; // CH58x has no isochronous support (see dcd_edpt_iso_alloc)
-#else
+ const uint8_t ep = tu_edpt_number(desc_ep->bEndpointAddress);
+ const uint8_t dir = tu_edpt_dir(desc_ep->bEndpointAddress);
+
+ // a transfer armed before SET_INTERFACE survives to here (no dcd close on this port): drop the
+ // stale descriptor and NAK the endpoint so the ISR can't complete it against the old buffer
+ data.xfer[ep][dir].valid = false;
+ if (dir == TUSB_DIR_IN) {
+ ep_tx_set_response(ep, USBFS_EP_T_RES_NAK);
+ } else {
+ ep_rx_set_response(ep, USBFS_EP_R_RES_NAK);
+ }
return true;
-#endif
}
bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t *buffer, uint16_t total_bytes, bool is_isr) {
@@ -510,7 +539,7 @@ bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t *buffer, uint16_t to
if (dir == TUSB_DIR_IN) {
update_in(rhport, ep, true);
} else {
- uint8_t rx_res = data.isochronous[ep] ? USBFS_EP_R_RES_NYET : USBFS_EP_R_RES_ACK;
+ uint8_t rx_res = data.isochronous[ep][TUSB_DIR_OUT] ? USBFS_EP_R_RES_NYET : USBFS_EP_R_RES_ACK;
ep_rx_set_response(ep, rx_res);
}
dcd_int_enable(rhport);
@@ -546,9 +575,15 @@ void dcd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr) {
ep_rx_ctrl_set(0, USBFS_EP_R_RES_ACK);
}
} else {
- // clear-stall resets the toggle to DATA0 (USB spec); manual-toggle parts then re-sync via ISR
+ // clear-stall resets the toggle to DATA0 (USB spec); manual-toggle parts then re-sync via ISR.
+ // Preserve an in-flight receive: if a read is still armed (the class driver considers it
+ // submitted and won't re-arm), fall back to ACK, not NAK, or the endpoint NAKs forever and the
+ // host times out (usbtest toggle test 29 clears the halt between bulk writes on an armed EP).
if (dir == TUSB_DIR_OUT) {
- ep_rx_ctrl_set(ep, EP_R_AUTO_TOG | USBFS_EP_R_RES_NAK);
+ uint8_t res = data.xfer[ep][TUSB_DIR_OUT].valid
+ ? (data.isochronous[ep][TUSB_DIR_OUT] ? USBFS_EP_R_RES_NYET : USBFS_EP_R_RES_ACK)
+ : USBFS_EP_R_RES_NAK;
+ ep_rx_ctrl_set(ep, EP_R_AUTO_TOG | res);
} else {
ep_tx_ctrl_set(ep, EP_T_AUTO_TOG | USBFS_EP_T_RES_NAK);
}
diff --git a/src/portable/wch/dcd_ch32_usbhs.c b/src/portable/wch/dcd_ch32_usbhs.c
index 0c154f5ce..577f86582 100644
--- a/src/portable/wch/dcd_ch32_usbhs.c
+++ b/src/portable/wch/dcd_ch32_usbhs.c
@@ -348,8 +348,16 @@ void dcd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr) {
const tusb_dir_t dir = tu_edpt_dir(ep_addr);
if (dir == TUSB_DIR_OUT) {
- EP_RX_CTRL(ep_num) = USBHS_EP_R_RES_NAK | USBHS_EP_R_TOG_0;
- ep_data_tog[ep_num][TUSB_DIR_OUT] = false;
+ ep_data_tog[ep_num][TUSB_DIR_OUT] = false; // clear-halt resets the toggle to DATA0
+ xfer_ctl_t *xfer = XFER_CTL_BASE(ep_num, TUSB_DIR_OUT);
+ if (xfer->valid) {
+ // A receive is still armed (the class driver considers it submitted and won't re-arm it);
+ // re-queue it (ACK/NYET) instead of leaving it NAKing, or the endpoint NAKs forever after
+ // clear-halt (usbtest toggle test 29 clears the halt on an armed bulk-OUT pipe).
+ queue_out_packet(ep_num, xfer);
+ } else {
+ EP_RX_CTRL(ep_num) = USBHS_EP_R_RES_NAK | USBHS_EP_R_TOG_0;
+ }
} else {
EP_TX_CTRL(ep_num) = USBHS_EP_T_RES_NAK | USBHS_EP_T_TOG_0;
ep_data_tog[ep_num][TUSB_DIR_IN] = false;