summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHiFiPhile <[email protected]>2026-05-24 15:43:17 +0200
committerHiFiPhile <[email protected]>2026-05-24 16:02:39 +0200
commit30627e9d265bb093ccd2b41e3c715176ed62375a (patch)
tree9ca7fad5aa90481115816fa37e7e3d89aa7fcad5
parent65fb8dfbadbb0b5970a1e984c290b5d6a5bcb315 (diff)
dcd/stm32_fsdev: workaround CH32 EP0 premature OUT ACK
Track EP0 control transfer state from SETUP (direction and wLength) and gate CH32 EP0 handshakes accordingly. Switch EP0 type away from control to bulk to block unsolicited OUT ACKs during control read/write gaps, then restore control type when EP0 transfer is explicitly armed. Signed-off-by: HiFiPhile <[email protected]>
-rw-r--r--src/portable/st/stm32_fsdev/dcd_stm32_fsdev.c52
1 files changed, 50 insertions, 2 deletions
diff --git a/src/portable/st/stm32_fsdev/dcd_stm32_fsdev.c b/src/portable/st/stm32_fsdev/dcd_stm32_fsdev.c
index 41da3ddd0..92845061b 100644
--- a/src/portable/st/stm32_fsdev/dcd_stm32_fsdev.c
+++ b/src/portable/st/stm32_fsdev/dcd_stm32_fsdev.c
@@ -124,6 +124,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
@@ -153,6 +157,16 @@ 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 FSDEV workaround: gate EP0 handshakes by switching type between CONTROL and BULK.
+TU_ATTR_ALWAYS_INLINE static inline void ep0_set_type(uint32_t ep_type, bool need_exclusive) {
+ 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, need_exclusive);
+}
+#endif
+
//--------------------------------------------------------------------+
// Controller API
//--------------------------------------------------------------------+
@@ -230,6 +244,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
@@ -261,6 +280,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);
}
}
@@ -277,6 +302,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)
+ uint16_t const setup_w_length = (uint16_t) setup_packet[6] | ((uint16_t) setup_packet[7] << 8);
+ ep0_ctrl_dir_in = (setup_packet[0] & TUSB_DIR_IN_MASK) != 0u;
+ ep0_ctrl_has_data = (setup_w_length != 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 {
@@ -293,6 +328,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;
@@ -315,6 +358,7 @@ static void handle_ctr_rx(uint32_t ep_id) {
}
xfer->queued_len += rx_count;
+
if ((rx_count < xfer->max_packet_size) || (xfer->queued_len >= xfer->total_len)) {
// all bytes received or short packet
@@ -431,8 +475,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();
}
/***
@@ -705,6 +747,12 @@ 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 defined(TUP_USBIP_FSDEV_CH32)
+ // Re-enable normal control transfer semantics when EP0 transfer is explicitly armed.
+ if (ep_num == 0u) {
+ ep0_set_type(U_EP_CONTROL, true);
+ }
+#endif
if (dir == TUSB_DIR_IN) {
dcd_transmit_packet(xfer, ep_idx);