summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHa Thach <[email protected]>2026-06-24 13:27:18 +0700
committerGitHub <[email protected]>2026-06-24 13:27:18 +0700
commit467f638e98d5b76ecb970272912a1b1f63a7ca19 (patch)
treefd273fbfc72ebd1faa28ae17f76166337616bd07
parente7cf9583f84731770316cf87f09685ec53d4d8ed (diff)
parent2e34ef283585ed0fe1dcf5b52d7ff79ef007b69b (diff)
Merge pull request #3648 from hathach/ch32_fsdev
dcd/stm32_fsdev: workaround CH32 EP0 premature OUT ACK
-rw-r--r--src/portable/st/stm32_fsdev/dcd_stm32_fsdev.c74
1 files changed, 71 insertions, 3 deletions
diff --git a/src/portable/st/stm32_fsdev/dcd_stm32_fsdev.c b/src/portable/st/stm32_fsdev/dcd_stm32_fsdev.c
index 6f7f490a8..f5a8beb65 100644
--- a/src/portable/st/stm32_fsdev/dcd_stm32_fsdev.c
+++ b/src/portable/st/stm32_fsdev/dcd_stm32_fsdev.c
@@ -125,6 +125,10 @@ typedef struct {
static xfer_ctl_t xfer_status[CFG_TUD_ENDPPOINT_MAX][2];
static ep_alloc_t ep_alloc_status[FSDEV_EP_COUNT];
static uint8_t remoteWakeCountdown; // When wake is requested
+#if defined(TUP_USBIP_FSDEV_CH32)
+static bool ep0_ctrl_dir_in;
+static bool ep0_ctrl_has_data;
+#endif
//--------------------------------------------------------------------+
// Prototypes
@@ -154,6 +158,23 @@ TU_ATTR_ALWAYS_INLINE static inline xfer_ctl_t *xfer_ctl_ptr(uint8_t epnum, uint
return &xfer_status[epnum][dir];
}
+#if defined(TUP_USBIP_FSDEV_CH32)
+// CH32 EP0 workaround: gate handshakes by switching EP0 type CONTROL<->BULK.
+// need_exclusive brackets the read-modify-write so the USB ISR can't race it.
+TU_ATTR_ALWAYS_INLINE static inline void ep0_set_type(uint32_t ep_type, bool need_exclusive) {
+ if (need_exclusive) {
+ fsdev_int_disable(0);
+ }
+ uint32_t ep_reg = ep_read(0) | U_EP_CTR_TX | U_EP_CTR_RX;
+ ep_reg &= U_EPREG_MASK;
+ ep_reg = (ep_reg & ~U_EP_T_FIELD) | ep_type;
+ ep_write(0, ep_reg, false);
+ if (need_exclusive) {
+ fsdev_int_enable(0);
+ }
+}
+#endif
+
//--------------------------------------------------------------------+
// Controller API
//--------------------------------------------------------------------+
@@ -231,6 +252,11 @@ static void handle_bus_reset(uint8_t rhport) {
// Reset PMA allocation
ep_buf_ptr = FSDEV_BTABLE_BASE + 8 * FSDEV_EP_COUNT;
+#if defined(TUP_USBIP_FSDEV_CH32)
+ ep0_ctrl_dir_in = false;
+ ep0_ctrl_has_data = false;
+#endif
+
edpt0_open(rhport); // open control endpoint (both IN & OUT)
FSDEV_REG->DADDR = U_DADDR_EF; // Enable USB Function
@@ -262,6 +288,12 @@ static void handle_ctr_tx(uint32_t ep_id) {
if (xfer->total_len != xfer->queued_len) {
dcd_transmit_packet(xfer, (uint16_t)ep_id);
} else {
+#if defined(TUP_USBIP_FSDEV_CH32)
+ // Control read: block unsolicited EP0 OUT ACK.
+ if ((ep_num == 0u) && ep0_ctrl_dir_in && ep0_ctrl_has_data) {
+ ep0_set_type(U_EP_BULK, false);
+ }
+#endif
dcd_event_xfer_complete(0, ep_num | TUSB_DIR_IN_MASK, xfer->queued_len, XFER_RESULT_SUCCESS, true);
}
}
@@ -278,6 +310,16 @@ static void handle_ctr_setup(uint32_t ep_id) {
// Setup packet should always be 8 bytes. If not, we probably missed the packet
if (rx_count == 8) {
+#if defined(TUP_USBIP_FSDEV_CH32)
+ tusb_control_request_t const *request = (tusb_control_request_t const *) (void *) setup_packet;
+ ep0_ctrl_dir_in = (request->bmRequestType_bit.direction == TUSB_DIR_IN);
+ ep0_ctrl_has_data = (request->wLength != 0u);
+
+ // For control write, block unsolicited EP0 OUT ACK until transfer is armed in edpt_xfer().
+ if (!ep0_ctrl_dir_in && ep0_ctrl_has_data) {
+ ep0_set_type(U_EP_BULK, false);
+ }
+#endif
dcd_event_setup_received(0, (uint8_t *)setup_packet, true);
// Hardware should reset EP0 RX/TX to NAK and both toggle to 1
} else {
@@ -294,6 +336,14 @@ static void handle_ctr_rx(uint32_t ep_id) {
const bool is_iso = ep_is_iso(ep_reg);
xfer_ctl_t *xfer = xfer_ctl_ptr(ep_num, TUSB_DIR_OUT);
+#if defined(TUP_USBIP_FSDEV_CH32)
+ // Control write: re-lock EP0 OUT after each DATA OUT packet until next edpt_xfer().
+ if ((ep_num == 0u) && !ep0_ctrl_dir_in && ep0_ctrl_has_data) {
+ ep0_set_type(U_EP_BULK, false);
+ ep_reg = (ep_reg & ~U_EP_T_FIELD) | U_EP_BULK;
+ }
+#endif
+
uint8_t buf_id;
#if FSDEV_USE_SBUF_ISO == 0
bool const dbl_buf = is_iso;
@@ -432,8 +482,6 @@ void dcd_edpt0_status_complete(uint8_t rhport, const tusb_control_request_t *req
const uint8_t dev_addr = (uint8_t)request->wValue;
FSDEV_REG->DADDR = (U_DADDR_EF | dev_addr);
}
-
- edpt0_prepare_setup();
}
/***
@@ -706,8 +754,13 @@ static bool edpt_xfer(uint8_t rhport, uint8_t ep_num, tusb_dir_t dir) {
xfer_ctl_t *xfer = xfer_ctl_ptr(ep_num, dir);
const uint8_t ep_idx = xfer->ep_idx;
-
if (dir == TUSB_DIR_IN) {
+#if defined(TUP_USBIP_FSDEV_CH32)
+ // Safe to restore CONTROL before arming IN: no pending OUT for the errata to blind-ACK.
+ if (ep_num == 0u) {
+ ep0_set_type(U_EP_CONTROL, true);
+ }
+#endif
dcd_transmit_packet(xfer, ep_idx);
} else {
uint32_t ep_reg = ep_read(ep_idx) | U_EP_CTR_TX | U_EP_CTR_RX; // reserve CTR
@@ -727,6 +780,13 @@ static bool edpt_xfer(uint8_t rhport, uint8_t ep_num, tusb_dir_t dir) {
btable_set_rx_bufsize(ep_idx, BTABLE_BUF_RX, cnt);
}
+#if defined(TUP_USBIP_FSDEV_CH32)
+ // Restore CONTROL in the same write as STAT_RX=VALID (after bufsize): a separate earlier
+ // write would re-enable the blind OUT ACK while still NAK'd with a stale buffer.
+ if (ep_num == 0u) {
+ ep_reg = (ep_reg & ~U_EP_T_FIELD) | U_EP_CONTROL;
+ }
+#endif
ep_change_status(&ep_reg, dir, EP_STAT_VALID);
ep_write(ep_idx, ep_reg, true);
}
@@ -773,6 +833,14 @@ void dcd_edpt_stall(uint8_t rhport, uint8_t ep_addr) {
ep_reg &= U_EPREG_MASK | EP_STAT_MASK(dir);
ep_change_status(&ep_reg, dir, EP_STAT_STALL);
+#if defined(TUP_USBIP_FSDEV_CH32)
+ // Stall ends the transfer without edpt_xfer() (the only other CONTROL restore); else a rejected
+ // control-write leaves EP0 typed BULK and the host's recovery SETUP is ignored until bus reset.
+ if (ep_num == 0u) {
+ ep_reg = (ep_reg & ~U_EP_T_FIELD) | U_EP_CONTROL;
+ }
+#endif
+
ep_write(ep_idx, ep_reg, true);
}