summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorhathach <[email protected]>2026-08-19 12:29:45 +0700
committerhathach <[email protected]>2026-08-19 13:46:44 +0700
commit2ad3df61c0c554139a5fa414a14c8157c852e836 (patch)
tree599f9a2f6847b72d13004b1132eb39120e1c6ed1
parent9c202e8c657e001c8d16753aa90046a5f373575f (diff)
dcd(ci_hs): stage the device address before priming the status stageclaude/ci-hs-set-address-order
IMXRT1060RM 42.7.23 and UM10503 Table 478 both ask for the DEVICEADDR write with USBADRA=1 to happen after the SET_ADDRESS data phase and before the prime of the status stage, so the controller loads USBADR from its holding register when the status stage is ACKed. The driver did it the other way round, leaving a window between the ENDPTPRIME store and the DEVICEADDR store: an IN answered inside that window ACKs with USBADRA still 0, so the holding register is never consulted and the device keeps answering on address 0 while the host has moved to the new one. Instruction timing alone cannot open that window, but dcd_set_address() runs in task context, so any interrupt landing between the two stores stretches it past a microframe. Hardware discards a staged address on a SETUP or OUT to endpoint 0 and zeroes USBADR on a bus reset, which covers a superseded SET_ADDRESS. What it cannot cover is a SETUP latched before this write and still unconsumed after the full CI_HS_BUSY_SPIN spin, which refuses the prime: condition 2 already fired for that earlier SETUP, so the stage would survive and load USBADR on the next EP0 IN ACK of an unrelated transfer. USB 2.0 9.4.6 is explicit that "the USB device does not change its device address until after the Status stage of this request is completed successfully", so the refused-prime path restores the previous USBADR rather than leaving a stage armed. Restoring the previous value rather than writing zero keeps 9.4.6's Address-state row correct, where a device already at a non-zero address must stay there; on Linux that write is always a no-op, since hub_set_address only issues SET_ADDRESS from USB_STATE_DEFAULT. Cast dev_addr before the shift: it is uint8_t, promoted to int, so an address of 64 or more reached the sign bit of a 32-bit int. No errata applies: IMXRT1060CE_A Rev 1.3 lists only ERR050101 and ERR010661 for USB, IMXRT1060CE_B Rev 1.1 only ERR010661. Validated on mimxrt1064_evk: 18/19 device+host tests, 6x usbtest 30/30, and a 100-iteration forced re-enumeration A/B that is clean on both this change and its parent (0/100 each). All 19 ci_hs boards build; unit tests 63/63; PVS drops one diagnostic (the sign-bit shift) and adds none.
-rw-r--r--src/portable/chipidea/ci_hs/dcd_ci_hs.c17
1 files changed, 11 insertions, 6 deletions
diff --git a/src/portable/chipidea/ci_hs/dcd_ci_hs.c b/src/portable/chipidea/ci_hs/dcd_ci_hs.c
index 6ab28e0be..203a8087b 100644
--- a/src/portable/chipidea/ci_hs/dcd_ci_hs.c
+++ b/src/portable/chipidea/ci_hs/dcd_ci_hs.c
@@ -361,12 +361,17 @@ 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. 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);
+ ci_hs_regs_t *dcd_reg = CI_HS_REG(rhport);
+ const uint32_t prev = dcd_reg->DEVICEADDR & 0xFE000000u; // current USBADR (bits 31-25)
+
+ // IMXRT1060RM 42.7.23 / UM10503 Table 478: stage the address before priming the status stage so
+ // hardware loads USBADR at the status ACK. Priming first races that ACK against this write.
+ dcd_reg->DEVICEADDR = ((uint32_t)dev_addr << 25) | TU_BIT(24);
+
+ if (!dcd_edpt_xfer(rhport, tu_edpt_addr(0, TUSB_DIR_IN), NULL, 0, false)) {
+ // USB 2.0 9.4.6: the address changes only after the status stage completes successfully. The
+ // status never went out, so drop the stage - USBADRA=0 takes effect instantly.
+ dcd_reg->DEVICEADDR = prev;
}
}