diff options
| author | hathach <[email protected]> | 2026-06-18 15:55:55 +0700 |
|---|---|---|
| committer | hathach <[email protected]> | 2026-06-18 15:55:55 +0700 |
| commit | 4498f65c460874ae64306d01638f52285394efc5 (patch) | |
| tree | b8c36311a2cd5c41d0583ca9b3be92b1f870b89e /src | |
| parent | 072c02e3684e886a93681ecb37f830be5b8c6b53 (diff) | |
| parent | 941d63e39a529593ee86caf7ea85e04839680d59 (diff) | |
Merge remote-tracking branch 'origin/master' into pr-3618
Diffstat (limited to 'src')
27 files changed, 986 insertions, 417 deletions
diff --git a/src/class/cdc/cdc_host.c b/src/class/cdc/cdc_host.c index 62c313b83..4441222c8 100644 --- a/src/class/cdc/cdc_host.c +++ b/src/class/cdc/cdc_host.c @@ -99,6 +99,7 @@ typedef struct { typedef struct { TUH_EPBUF_DEF(tx, CFG_TUH_CDC_TX_EPSIZE); TUH_EPBUF_DEF(rx, CFG_TUH_CDC_RX_EPSIZE); + TUH_EPBUF_DEF(ctrl, 8); } cdch_epbuf_t; static cdch_interface_t cdch_data[CFG_TUH_CDC]; @@ -1003,15 +1004,16 @@ static bool acm_set_line_coding(cdch_interface_t *p_cdc, tuh_xfer_cb_t complete_ .wLength = tu_htole16((uint16_t) sizeof(cdc_line_coding_t)) }; - // use usbh enum buf to hold line coding since user line_coding variable does not live long enough - uint8_t *enum_buf = usbh_get_enum_buf(); - memcpy(enum_buf, &p_cdc->requested_line.coding, sizeof(cdc_line_coding_t)); + // use local ctrl buf to hold line coding since user line_coding variable does not live long enough + uint8_t const idx = get_idx_by_ptr(p_cdc); + uint8_t *ctrl_buf = cdch_epbuf[idx].ctrl; + memcpy(ctrl_buf, &p_cdc->requested_line.coding, sizeof(cdc_line_coding_t)); tuh_xfer_t xfer = { .daddr = p_cdc->daddr, .ep_addr = 0, .setup = &request, - .buffer = enum_buf, + .buffer = ctrl_buf, .complete_cb = complete_cb, .user_data = user_data }; @@ -1491,7 +1493,7 @@ static inline uint32_t ftdi_get_divisor(cdch_interface_t *p_cdc) { //------------- Control Request -------------// static bool cp210x_set_request(cdch_interface_t * p_cdc, uint8_t command, uint16_t value, - uint8_t * buffer, uint16_t length, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + uint8_t const * buffer, uint16_t length, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { tusb_control_request_t const request = { .bmRequestType_bit = { .recipient = TUSB_REQ_RCPT_INTERFACE, @@ -1504,19 +1506,20 @@ static bool cp210x_set_request(cdch_interface_t * p_cdc, uint8_t command, uint16 .wLength = tu_htole16(length) }; - // use usbh enum buf since application variable does not live long enough - uint8_t * enum_buf = NULL; + // use local ctrl buf since application variable does not live long enough + uint8_t * ctrl_buf = NULL; if (buffer && length > 0) { - enum_buf = usbh_get_enum_buf(); - tu_memcpy_s(enum_buf, CFG_TUH_ENUMERATION_BUFSIZE, buffer, length); + uint8_t const idx = get_idx_by_ptr(p_cdc); + ctrl_buf = cdch_epbuf[idx].ctrl; + tu_memcpy_s(ctrl_buf, sizeof(cdch_epbuf[idx].ctrl), buffer, length); } tuh_xfer_t xfer = { .daddr = p_cdc->daddr, .ep_addr = 0, .setup = &request, - .buffer = enum_buf, + .buffer = ctrl_buf, .complete_cb = complete_cb, .user_data = user_data }; @@ -1563,7 +1566,7 @@ static void cp210x_internal_control_complete(cdch_interface_t *p_cdc, tuh_xfer_t static bool cp210x_set_baudrate(cdch_interface_t *p_cdc, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { // Not every baud rate is supported. See datasheets and AN205 "CP210x Baud Rate Support" uint32_t baud_le = tu_htole32(p_cdc->requested_line.coding.bit_rate); - return cp210x_set_request(p_cdc, CP210X_SET_BAUDRATE, 0, (uint8_t *) &baud_le, 4, complete_cb, user_data); + return cp210x_set_request(p_cdc, CP210X_SET_BAUDRATE, 0, (uint8_t const *) &baud_le, 4, complete_cb, user_data); } static bool cp210x_set_data_format(cdch_interface_t *p_cdc, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { @@ -1640,7 +1643,7 @@ static uint16_t ch34x_get_divisor_prescaler(cdch_interface_t *p_cdc); //------------- Control Request -------------// static bool ch34x_set_request(cdch_interface_t *p_cdc, uint8_t direction, uint8_t request, - uint16_t value, uint16_t index, uint8_t *buffer, uint16_t length, + uint16_t value, uint16_t index, uint8_t const *buffer, uint16_t length, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { tusb_control_request_t const request_setup = { .bmRequestType_bit = { @@ -1654,13 +1657,14 @@ static bool ch34x_set_request(cdch_interface_t *p_cdc, uint8_t direction, uint8_ .wLength = tu_htole16(length) }; - // use usbh enum buf since application variable does not live long enough - uint8_t *enum_buf = NULL; + // use local ctrl buf since application variable does not live long enough + uint8_t *ctrl_buf = NULL; - if (buffer && length > 0) { - enum_buf = usbh_get_enum_buf(); - if (direction == TUSB_DIR_OUT) { - tu_memcpy_s(enum_buf, CFG_TUH_ENUMERATION_BUFSIZE, buffer, length); + if (length > 0) { + uint8_t const idx = get_idx_by_ptr(p_cdc); + ctrl_buf = cdch_epbuf[idx].ctrl; + if (buffer && direction == TUSB_DIR_OUT) { + tu_memcpy_s(ctrl_buf, sizeof(cdch_epbuf[idx].ctrl), buffer, length); } } @@ -1668,7 +1672,7 @@ static bool ch34x_set_request(cdch_interface_t *p_cdc, uint8_t direction, uint8_ .daddr = p_cdc->daddr, .ep_addr = 0, .setup = &request_setup, - .buffer = enum_buf, + .buffer = ctrl_buf, .complete_cb = complete_cb, .user_data = user_data }; @@ -1682,8 +1686,8 @@ TU_ATTR_ALWAYS_INLINE static inline bool ch34x_control_out(cdch_interface_t *p_c } TU_ATTR_ALWAYS_INLINE static inline bool ch34x_control_in(cdch_interface_t *p_cdc, uint8_t request, uint16_t value, uint16_t index, - uint8_t *buffer, uint16_t buffersize, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { - return ch34x_set_request(p_cdc, TUSB_DIR_IN, request, value, index, buffer, buffersize, + uint16_t buffersize, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + return ch34x_set_request(p_cdc, TUSB_DIR_IN, request, value, index, NULL, buffersize, complete_cb, user_data); } @@ -1692,12 +1696,6 @@ TU_ATTR_ALWAYS_INLINE static inline bool ch34x_write_reg(cdch_interface_t *p_cdc return ch34x_control_out(p_cdc, CH34X_REQ_WRITE_REG, reg, reg_value, complete_cb, user_data); } -//static bool ch34x_read_reg_request ( cdch_interface_t * p_cdc, uint16_t reg, -// uint8_t *buffer, uint16_t buffersize, tuh_xfer_cb_t complete_cb, uintptr_t user_data ) -//{ -// return ch34x_control_in ( p_cdc, CH34X_REQ_READ_REG, reg, 0, buffer, buffersize, complete_cb, user_data ); -//} - //------------- Driver API -------------// // internal control complete to update state such as line state, encoding @@ -1794,8 +1792,7 @@ static bool ch34x_process_set_config(cdch_interface_t *p_cdc, tuh_xfer_t *xfer) switch (state) { case CONFIG_CH34X_READ_VERSION: { - uint8_t* enum_buf = usbh_get_enum_buf(); - TU_ASSERT(ch34x_control_in(p_cdc, CH34X_REQ_READ_VERSION, 0, 0, enum_buf, 2, + TU_ASSERT(ch34x_control_in(p_cdc, CH34X_REQ_READ_VERSION, 0, 0, 2, cdch_process_set_config, CONFIG_CH34X_SERIAL_INIT)); break; } @@ -1950,7 +1947,7 @@ static bool pl2303_encode_baud_rate(cdch_interface_t *p_cdc, uint8_t buf[PL2303_ //------------- Control Request -------------// static bool pl2303_set_request(cdch_interface_t *p_cdc, uint8_t request, uint8_t requesttype, - uint16_t value, uint16_t index, uint8_t *buffer, uint16_t length, + uint16_t value, uint16_t index, uint8_t const *buffer, uint16_t length, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { tusb_control_request_t const request_setup = { .bmRequestType = requesttype, @@ -1960,13 +1957,14 @@ static bool pl2303_set_request(cdch_interface_t *p_cdc, uint8_t request, uint8_t .wLength = tu_htole16(length) }; - // use usbh enum buf since application variable does not live long enough - uint8_t *enum_buf = NULL; + // use local ctrl buf since application variable does not live long enough + uint8_t *ctrl_buf = NULL; - if (buffer && length > 0) { - enum_buf = usbh_get_enum_buf(); - if (request_setup.bmRequestType_bit.direction == TUSB_DIR_OUT) { - tu_memcpy_s(enum_buf, CFG_TUH_ENUMERATION_BUFSIZE, buffer, length); + if (length > 0) { + uint8_t const idx = get_idx_by_ptr(p_cdc); + ctrl_buf = cdch_epbuf[idx].ctrl; + if (buffer && request_setup.bmRequestType_bit.direction == TUSB_DIR_OUT) { + tu_memcpy_s(ctrl_buf, sizeof(cdch_epbuf[idx].ctrl), buffer, length); } } @@ -1974,7 +1972,7 @@ static bool pl2303_set_request(cdch_interface_t *p_cdc, uint8_t request, uint8_t .daddr = p_cdc->daddr, .ep_addr = 0, .setup = &request_setup, - .buffer = enum_buf, + .buffer = ctrl_buf, .complete_cb = complete_cb, .user_data = user_data }; @@ -1982,10 +1980,10 @@ static bool pl2303_set_request(cdch_interface_t *p_cdc, uint8_t request, uint8_t return tuh_control_xfer(&xfer); } -static bool pl2303_vendor_read(cdch_interface_t *p_cdc, uint16_t value, uint8_t *buf, +static bool pl2303_vendor_read(cdch_interface_t *p_cdc, uint16_t value, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { uint8_t request = p_cdc->pl2303.type == PL2303_TYPE_HXN ? PL2303_VENDOR_READ_NREQUEST : PL2303_VENDOR_READ_REQUEST; - return pl2303_set_request(p_cdc, request, PL2303_VENDOR_READ_REQUEST_TYPE, value, 0, buf, 1, complete_cb, user_data); + return pl2303_set_request(p_cdc, request, PL2303_VENDOR_READ_REQUEST_TYPE, value, 0, NULL, 1, complete_cb, user_data); } static bool pl2303_vendor_write(cdch_interface_t *p_cdc, uint16_t value, uint16_t index, @@ -1995,9 +1993,8 @@ static bool pl2303_vendor_write(cdch_interface_t *p_cdc, uint16_t value, uint16_ } static inline bool pl2303_supports_hx_status(cdch_interface_t *p_cdc, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { - uint8_t buf = 0; return pl2303_set_request(p_cdc, PL2303_VENDOR_READ_REQUEST, PL2303_VENDOR_READ_REQUEST_TYPE, PL2303_READ_TYPE_HX_STATUS, 0, - &buf, 1, complete_cb, user_data); + NULL, 1, complete_cb, user_data); } //static bool pl2303_get_line_request(cdch_interface_t * p_cdc, uint8_t buf[PL2303_LINE_CODING_BUFSIZE]) { @@ -2131,7 +2128,6 @@ static bool pl2303_process_set_config(cdch_interface_t *p_cdc, tuh_xfer_t *xfer) // state CONFIG_PL2303_READ1 may have no success due to expected stall by pl2303_supports_hx_status() const uintptr_t state = xfer->user_data; TU_ASSERT(xfer->result == XFER_RESULT_SUCCESS || state == CONFIG_PL2303_READ1); - uint8_t* enum_buf = usbh_get_enum_buf(); pl2303_type_t type; switch (state) { @@ -2162,7 +2158,7 @@ static bool pl2303_process_set_config(cdch_interface_t *p_cdc, tuh_xfer_t *xfer) // purpose unknown, overtaken from Linux Kernel driver if (p_cdc->pl2303.type != PL2303_TYPE_HXN) { - TU_ASSERT(pl2303_vendor_read(p_cdc, 0x8484, enum_buf, cdch_process_set_config, CONFIG_PL2303_WRITE1)); + TU_ASSERT(pl2303_vendor_read(p_cdc, 0x8484, cdch_process_set_config, CONFIG_PL2303_WRITE1)); break; }// else: continue with next step TU_ATTR_FALLTHROUGH; @@ -2178,7 +2174,7 @@ static bool pl2303_process_set_config(cdch_interface_t *p_cdc, tuh_xfer_t *xfer) case CONFIG_PL2303_READ2: // purpose unknown, overtaken from Linux Kernel driver if (p_cdc->pl2303.type != PL2303_TYPE_HXN) { - TU_ASSERT(pl2303_vendor_read(p_cdc, 0x8484, enum_buf, cdch_process_set_config, CONFIG_PL2303_READ3)); + TU_ASSERT(pl2303_vendor_read(p_cdc, 0x8484, cdch_process_set_config, CONFIG_PL2303_READ3)); break; }// else: continue with next step TU_ATTR_FALLTHROUGH; @@ -2186,7 +2182,7 @@ static bool pl2303_process_set_config(cdch_interface_t *p_cdc, tuh_xfer_t *xfer) case CONFIG_PL2303_READ3: // purpose unknown, overtaken from Linux Kernel driver if (p_cdc->pl2303.type != PL2303_TYPE_HXN) { - TU_ASSERT(pl2303_vendor_read(p_cdc, 0x8383, enum_buf, cdch_process_set_config, CONFIG_PL2303_READ4)); + TU_ASSERT(pl2303_vendor_read(p_cdc, 0x8383, cdch_process_set_config, CONFIG_PL2303_READ4)); break; }// else: continue with next step TU_ATTR_FALLTHROUGH; @@ -2194,7 +2190,7 @@ static bool pl2303_process_set_config(cdch_interface_t *p_cdc, tuh_xfer_t *xfer) case CONFIG_PL2303_READ4: // purpose unknown, overtaken from Linux Kernel driver if (p_cdc->pl2303.type != PL2303_TYPE_HXN) { - TU_ASSERT(pl2303_vendor_read(p_cdc, 0x8484, enum_buf, cdch_process_set_config, CONFIG_PL2303_WRITE2)); + TU_ASSERT(pl2303_vendor_read(p_cdc, 0x8484, cdch_process_set_config, CONFIG_PL2303_WRITE2)); break; }// else: continue with next step TU_ATTR_FALLTHROUGH; @@ -2210,7 +2206,7 @@ static bool pl2303_process_set_config(cdch_interface_t *p_cdc, tuh_xfer_t *xfer) case CONFIG_PL2303_READ5: // purpose unknown, overtaken from Linux Kernel driver if (p_cdc->pl2303.type != PL2303_TYPE_HXN) { - TU_ASSERT(pl2303_vendor_read(p_cdc, 0x8484, enum_buf, cdch_process_set_config, CONFIG_PL2303_READ6)); + TU_ASSERT(pl2303_vendor_read(p_cdc, 0x8484, cdch_process_set_config, CONFIG_PL2303_READ6)); break; }// else: continue with next step TU_ATTR_FALLTHROUGH; @@ -2218,7 +2214,7 @@ static bool pl2303_process_set_config(cdch_interface_t *p_cdc, tuh_xfer_t *xfer) case CONFIG_PL2303_READ6: // purpose unknown, overtaken from Linux Kernel driver if (p_cdc->pl2303.type != PL2303_TYPE_HXN) { - TU_ASSERT(pl2303_vendor_read(p_cdc, 0x8383, enum_buf, cdch_process_set_config, CONFIG_PL2303_WRITE3)); + TU_ASSERT(pl2303_vendor_read(p_cdc, 0x8383, cdch_process_set_config, CONFIG_PL2303_WRITE3)); break; }// else: continue with next step TU_ATTR_FALLTHROUGH; diff --git a/src/class/mtp/mtp_device.c b/src/class/mtp/mtp_device.c index 0da984f4a..1f76dfcc7 100644 --- a/src/class/mtp/mtp_device.c +++ b/src/class/mtp/mtp_device.c @@ -441,9 +441,16 @@ bool mtpd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t threshold = CFG_TUD_MTP_EP_BUFSIZE; } - // Check completion: ZLP, short packet, or total length reached - const bool is_complete = - (xferred_bytes == 0 || xferred_bytes < threshold || p_mtp->xferred_len >= p_mtp->total_len); + // Check completion for IN and OUT separately + bool is_complete; + if (is_data_in) { + // IN completion: short packet, ZLP, or reaching total_len + is_complete = (xferred_bytes == 0 || xferred_bytes < threshold || p_mtp->xferred_len >= p_mtp->total_len); + } else { + // OUT completion: reaching total_len or ZLP only. A short packet does NOT end the phase + // (an early short packet before total_len is the cancel case, not normal completion). + is_complete = (p_mtp->xferred_len >= p_mtp->total_len) || ((xferred_bytes == 0 && p_mtp->xferred_len > 0)); + } TU_LOG_DRV(" MTP Data %s CB: xferred_bytes=%lu, xferred_len/total_len=%lu/%lu, is_complete=%d\r\n", is_data_in ? "IN" : "OUT", xferred_bytes, p_mtp->xferred_len, p_mtp->total_len, is_complete ? 1 : 0); diff --git a/src/common/tusb_mcu.h b/src/common/tusb_mcu.h index 413ac4850..cc9837dcd 100644 --- a/src/common/tusb_mcu.h +++ b/src/common/tusb_mcu.h @@ -206,6 +206,12 @@ #define TUP_USBIP_FSDEV_DRD #define CFG_TUSB_FSDEV_PMA_SIZE 2048u +#elif TU_CHECK_MCU(OPT_MCU_STM32C5) + #define TUP_USBIP_FSDEV + #define TUP_USBIP_FSDEV_STM32 + #define TUP_USBIP_FSDEV_DRD + #define CFG_TUSB_FSDEV_PMA_SIZE 2048u + #elif TU_CHECK_MCU(OPT_MCU_STM32F0) #define TUP_USBIP_FSDEV #define TUP_USBIP_FSDEV_STM32 diff --git a/src/common/tusb_types.h b/src/common/tusb_types.h index 959fc129a..cb06b89bb 100644 --- a/src/common/tusb_types.h +++ b/src/common/tusb_types.h @@ -282,6 +282,7 @@ typedef enum { XFER_RESULT_FAILED, XFER_RESULT_STALLED, XFER_RESULT_TIMEOUT, + XFER_RESULT_ABORTED, XFER_RESULT_INVALID } xfer_result_t; diff --git a/src/device/usbd.c b/src/device/usbd.c index 55ad330c1..f87b63111 100644 --- a/src/device/usbd.c +++ b/src/device/usbd.c @@ -914,6 +914,8 @@ static bool usbd_control_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t // Data stage progress if (ctrl_xfer->request.bmRequestType_bit.direction == TUSB_DIR_OUT) { TU_VERIFY(ctrl_xfer->buffer); + // Clamp host overrun to remaining capacity (data_len) so memcpy can't overflow the caller buffer + xferred_bytes = tu_min32(xferred_bytes, ctrl_xfer->data_len - ctrl_xfer->total_xferred); if (ctrl_xfer->buffer != _ctrl_epbuf.buf) { memcpy(ctrl_xfer->buffer, _ctrl_epbuf.buf, xferred_bytes); } diff --git a/src/host/usbh.c b/src/host/usbh.c index 2e3c93c5e..9d159985e 100644 --- a/src/host/usbh.c +++ b/src/host/usbh.c @@ -40,6 +40,14 @@ #define CFG_TUH_TASK_QUEUE_SZ 16 #endif +#ifndef CFG_TUH_CONTROL_PENDING_QUEUE_SZ + #if CFG_TUH_HUB + #define CFG_TUH_CONTROL_PENDING_QUEUE_SZ 4 + #else + #define CFG_TUH_CONTROL_PENDING_QUEUE_SZ 2 + #endif +#endif + #ifndef CFG_TUH_INTERFACE_MAX #define CFG_TUH_INTERFACE_MAX 8 #endif @@ -175,11 +183,11 @@ static OSAL_SPINLOCK_DEF(_usbh_spin, usbh_int_set); OSAL_QUEUE_DEF(usbh_int_set, _usbh_qdef, CFG_TUH_TASK_QUEUE_SZ, hcd_event_t); static osal_queue_t _usbh_q; - #if CFG_TUH_HUB +#if CFG_TUH_HUB // Deferred attachment queue, only needed when using hub OSAL_QUEUE_DEF(usbh_int_set, _usbh_daqdef, CFG_TUH_HUB, hcd_event_t); static osal_queue_t _usbh_daq; - #endif +#endif // Control transfers: since most controllers do not support multiple control transfers // on multiple devices concurrently and control transfers are not used much except for @@ -189,9 +197,9 @@ typedef struct { tuh_xfer_cb_t complete_cb; uintptr_t user_data; + volatile uint16_t actual_len; volatile uint8_t stage; uint8_t daddr; - volatile uint16_t actual_len; uint8_t failed_count; } usbh_ctrl_xfer_info_t; @@ -202,17 +210,32 @@ typedef struct { } usbh_call_after_t; typedef struct { - uint8_t controller_id; // controller ID + tusb_control_request_t setup; + uint8_t* buffer; + tuh_xfer_cb_t complete_cb; + uintptr_t user_data; + uint8_t daddr; + uint8_t daddr_gen; +} usbh_pending_ctrl_t; + +// FIFO for pending async control transfers since we only execute 1 control transfer at a time +TU_FIFO_DEF(_usbh_pending_ctrl_q, CFG_TUH_CONTROL_PENDING_QUEUE_SZ * sizeof(usbh_pending_ctrl_t), false); + +typedef struct { uint8_t enumerating_daddr; // device address of the device being enumerated uint8_t attach_debouncing_bm; // bitmask for roothub port attach debouncing tuh_bus_info_t dev0_bus; // bus info for dev0 in enumeration usbh_ctrl_xfer_info_t ctrl_xfer_info; // control transfer usbh_call_after_t call_after; + // Per-daddr generation counter — bumped on usbh_device_close() to identify stale pending control transfer + uint8_t daddr_gen[TOTAL_DEVICES + 1]; +#if CFG_TUSB_OS_HAS_SCHEDULER + osal_task_handle_t task_hdl; // host task handle, lazy-captured on first tuh_task_ext() +#endif } usbh_data_t; -static usbh_data_t _usbh_data = { - .controller_id = TUSB_INDEX_INVALID_8, -}; +static uint8_t _usbh_controller_id = TUSB_INDEX_INVALID_8; +static usbh_data_t _usbh_data; typedef struct { TUH_EPBUF_TYPE_DEF(tusb_control_request_t, request); @@ -346,8 +369,11 @@ static void enum_new_device(hcd_event_t* event); static void enum_delay_async(uintptr_t state); static void process_remove_event(hcd_event_t *event); static void remove_device_tree(uint8_t rhport, uint8_t hub_addr, uint8_t hub_port); + static bool usbh_edpt_control_open(uint8_t dev_addr, uint8_t max_packet_size); static bool usbh_control_xfer_cb (uint8_t daddr, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes); +static void control_xfer_dispatch_pending(void); +static void control_xfer_complete(uint8_t daddr, xfer_result_t result); TU_ATTR_ALWAYS_INLINE static inline usbh_device_t* get_device(uint8_t dev_addr) { TU_VERIFY(dev_addr > 0 && dev_addr <= TOTAL_DEVICES, NULL); @@ -364,7 +390,7 @@ TU_ATTR_ALWAYS_INLINE static inline bool queue_event(hcd_event_t const * event, return true; } -TU_ATTR_ALWAYS_INLINE static inline void _control_set_xfer_stage(uint8_t stage) { +TU_ATTR_ALWAYS_INLINE static inline void control_xfer_set_stage(uint8_t stage) { if (_usbh_data.ctrl_xfer_info.stage != stage) { (void) osal_mutex_lock(_usbh_mutex, OSAL_TIMEOUT_WAIT_FOREVER); _usbh_data.ctrl_xfer_info.stage = stage; @@ -372,15 +398,6 @@ TU_ATTR_ALWAYS_INLINE static inline void _control_set_xfer_stage(uint8_t stage) } } -TU_ATTR_ALWAYS_INLINE static inline bool usbh_setup_send(uint8_t daddr, const uint8_t setup_packet[8]) { - const uint8_t rhport = usbh_get_rhport(daddr); - const bool ret = hcd_setup_send(rhport, daddr, setup_packet); - if (!ret) { - _control_set_xfer_stage(CONTROL_STAGE_IDLE); - } - return ret; -} - bool usbh_defer_func_ms_async(uint32_t ms, tusb_defer_func_t func, uintptr_t param) { TU_ASSERT(_usbh_data.call_after.func == NULL); TU_LOG_USBH("USBH schedule function after %u ms\r\n", (unsigned int)ms); @@ -394,9 +411,16 @@ bool usbh_defer_func_ms_async(uint32_t ms, tusb_defer_func_t func, uintptr_t par TU_ATTR_ALWAYS_INLINE static inline void usbh_device_close(uint8_t rhport, uint8_t daddr) { hcd_device_close(rhport, daddr); - // abort any ongoing control transfer - if (daddr == _usbh_data.ctrl_xfer_info.daddr) { - _control_set_xfer_stage(CONTROL_STAGE_IDLE); + // Bump the generation under the mutex so a concurrent producer in + // tuh_control_xfer stamps a value that is strictly monotonic w.r.t. close. + (void) osal_mutex_lock(_usbh_mutex, OSAL_TIMEOUT_WAIT_FOREVER); + _usbh_data.daddr_gen[daddr]++; + (void) osal_mutex_unlock(_usbh_mutex); + + // If this device has in-flight control xfer, complete as FAILED + usbh_ctrl_xfer_info_t* ctrl_info = &_usbh_data.ctrl_xfer_info; + if (daddr == ctrl_info->daddr && ctrl_info->stage != CONTROL_STAGE_IDLE) { + control_xfer_complete(daddr, XFER_RESULT_FAILED); } // invalidate if enumerating @@ -458,7 +482,7 @@ tusb_speed_t tuh_speed_get(uint8_t daddr) { } bool tuh_rhport_is_active(uint8_t rhport) { - return _usbh_data.controller_id == rhport; + return _usbh_controller_id == rhport; } bool tuh_rhport_reset_bus(uint8_t rhport, bool active) { @@ -485,7 +509,7 @@ static void clear_device(usbh_device_t* dev) { } bool tuh_inited(void) { - return _usbh_data.controller_id != TUSB_INDEX_INVALID_8; + return _usbh_controller_id != TUSB_INDEX_INVALID_8; } bool tuh_rhport_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) { @@ -547,7 +571,7 @@ bool tuh_rhport_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) { tu_memclr(_usbh_devices, sizeof(_usbh_devices)); tu_memclr(&_usbh_data, sizeof(_usbh_data)); - _usbh_data.controller_id = TUSB_INDEX_INVALID_8; + _usbh_controller_id = TUSB_INDEX_INVALID_8; _usbh_data.enumerating_daddr = TUSB_INDEX_INVALID_8; for (uint8_t i = 0; i < TOTAL_DEVICES; i++) { @@ -565,7 +589,7 @@ bool tuh_rhport_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) { } // Init host controller - _usbh_data.controller_id = rhport; + _usbh_controller_id = rhport; TU_ASSERT(hcd_init(rhport, rh_init)); hcd_int_enable(rhport); @@ -580,7 +604,7 @@ bool tuh_deinit(uint8_t rhport) { // deinit host controller hcd_int_disable(rhport); TU_ASSERT(hcd_deinit(rhport)); - _usbh_data.controller_id = TUSB_INDEX_INVALID_8; + _usbh_controller_id = TUSB_INDEX_INVALID_8; // remove all devices on this rhport (hub_addr = 0, hub_port = 0) remove_device_tree(rhport, 0, 0); @@ -604,6 +628,25 @@ bool tuh_deinit(uint8_t rhport) { _usbh_daq = NULL; #endif + // Fire FAILED cb for any queued async control xfer so callers aren't stranded. + usbh_pending_ctrl_t pending; + while (tu_fifo_read_n(&_usbh_pending_ctrl_q, &pending, sizeof(pending)) == sizeof(pending)) { + if (pending.complete_cb) { + tuh_xfer_t x = { + .daddr = pending.daddr, + .ep_addr = 0, + .result = XFER_RESULT_FAILED, + .actual_len = 0, + .setup = &pending.setup, + .buffer = pending.buffer, + .complete_cb = pending.complete_cb, + .user_data = pending.user_data, + }; + pending.complete_cb(&x); + } + } + tu_fifo_clear(&_usbh_pending_ctrl_q); + #if OSAL_MUTEX_REQUIRED // TODO make sure there is no task waiting on this mutex osal_mutex_delete(_usbh_mutex); @@ -629,6 +672,12 @@ bool tuh_task_event_ready(void) { } #endif + // Pending control xfer waiting for an idle slot + if (_usbh_data.ctrl_xfer_info.stage == CONTROL_STAGE_IDLE && + !tu_fifo_empty(&_usbh_pending_ctrl_q)) { + return true; + } + if (_usbh_data.call_after.func) { int32_t remain_ms = (int32_t)(_usbh_data.call_after.at_ms - tusb_time_millis_api()); if (remain_ms <= 0) { @@ -663,6 +712,13 @@ void tuh_task_ext(uint32_t timeout_ms, bool in_isr) { (void) in_isr; // not implemented yet +#if CFG_TUSB_OS_HAS_SCHEDULER + // Save task handle on 1st run + if (_usbh_data.task_hdl == NULL) { + _usbh_data.task_hdl = osal_task_get_current_handle(); + } +#endif + // Loop until there are no more events in the queue or CFG_TUH_TASK_EVENTS_PER_RUN is reached for (unsigned epr = 0;; epr++) { #if CFG_TUH_TASK_EVENTS_PER_RUN > 0 @@ -695,6 +751,16 @@ void tuh_task_ext(uint32_t timeout_ms, bool in_isr) { } } + // Drain pending async control xfers. Slot transitions and dispatch are + // decoupled: completion / abort / device_close set stage = IDLE via + // control_xfer_set_stage() and the actual FIFO drain happens here in the + // event loop. The check is a fast non-mutex sanity gate; the dispatcher + // itself re-checks under the mutex. + if (_usbh_data.ctrl_xfer_info.stage == CONTROL_STAGE_IDLE && + !tu_fifo_empty(&_usbh_pending_ctrl_q)) { + control_xfer_dispatch_pending(); + } + hcd_event_t event; #if CFG_TUH_HUB @@ -818,73 +884,179 @@ void tuh_task_ext(uint32_t timeout_ms, bool in_isr) { // Control transfer //--------------------------------------------------------------------+ -static void _control_blocking_complete_cb(tuh_xfer_t* xfer) { - // update result - *((xfer_result_t*) xfer->user_data) = xfer->result; +// Carries both fields the sync waiter cares about — capturing from xfer_temp +// (snapshot taken before release_slot resets ctrl_info for the next pending +// entry) so the waiter sees this xfer's data, not the next dispatched one's. +typedef struct { + volatile xfer_result_t result; + volatile uint32_t actual_len; +} control_xfer_sync_param_t; + +static void control_xfer_sync_complete(tuh_xfer_t* xfer) { + control_xfer_sync_param_t* s = (control_xfer_sync_param_t*) xfer->user_data; + s->actual_len = xfer->actual_len; + s->result = xfer->result; } // TODO timeout_ms is not supported yet bool tuh_control_xfer (tuh_xfer_t* xfer) { - TU_VERIFY(xfer->ep_addr == 0 && xfer->setup); // EP0 with setup packet const uint8_t daddr = xfer->daddr; - TU_VERIFY(tuh_connected(daddr)); - + TU_VERIFY(daddr <= TOTAL_DEVICES && xfer->ep_addr == 0 && xfer->setup); // EP0 with setup packet usbh_ctrl_xfer_info_t* ctrl_info = &_usbh_data.ctrl_xfer_info; - TU_VERIFY(ctrl_info->stage == CONTROL_STAGE_IDLE); // pre-check to help reducing mutex lock - (void) osal_mutex_lock(_usbh_mutex, OSAL_TIMEOUT_WAIT_FOREVER); - bool const is_idle = (ctrl_info->stage == CONTROL_STAGE_IDLE); - if (is_idle) { - ctrl_info->stage = CONTROL_STAGE_SETUP; - ctrl_info->daddr = daddr; - ctrl_info->actual_len = 0; - ctrl_info->failed_count = 0; +#if CFG_TUSB_OS_HAS_SCHEDULER + // Sync (complete_cb == NULL) from a host-stack callback is forbidden on + // RTOS targets — the event-loop driver can't block on its own pending xfer + // (deadlock if other control xfers are queued behind). Use async with a + // chained cb instead. OS_NONE / OS_PICO are exempt: they have a single + // execution context and the recursive-drive path is the only way to wait. + TU_ASSERT(!(xfer->complete_cb == NULL && + osal_task_get_current_handle() == _usbh_data.task_hdl)); +#endif - ctrl_info->buffer = xfer->buffer; - ctrl_info->complete_cb = xfer->complete_cb; - ctrl_info->user_data = xfer->user_data; - _usbh_epbuf.request = (*xfer->setup); - } - (void) osal_mutex_unlock(_usbh_mutex); + // Slot is single-threaded — when busy, sync callers block until it frees + // (blocking semantics require the result); async callers get queued in the + // pending FIFO and submitted by control_xfer_complete() when the slot + // drains. The test-and-{claim|enqueue} is one critical section so a slot + // that becomes IDLE between the check and the enqueue can't strand an async + // request in a queue nothing else drains. + const bool is_nonblocking = (xfer->complete_cb != NULL); + while (true) { + TU_VERIFY(tuh_connected(daddr)); + bool claimed = false; + bool is_queued = false; + (void) osal_mutex_lock(_usbh_mutex, OSAL_TIMEOUT_WAIT_FOREVER); + if (ctrl_info->stage == CONTROL_STAGE_IDLE) { + ctrl_info->stage = CONTROL_STAGE_SETUP; + ctrl_info->daddr = daddr; + ctrl_info->actual_len = 0; + ctrl_info->failed_count = 0; + + ctrl_info->buffer = xfer->buffer; + ctrl_info->complete_cb = xfer->complete_cb; + ctrl_info->user_data = xfer->user_data; + _usbh_epbuf.request = (*xfer->setup); + claimed = true; + } else if (is_nonblocking) { + // Async + busy: queue the transfer. + const usbh_pending_ctrl_t entry = { + .setup = *xfer->setup, + .buffer = xfer->buffer, + .complete_cb = xfer->complete_cb, + .user_data = xfer->user_data, + .daddr = daddr, + .daddr_gen = _usbh_data.daddr_gen[daddr] + }; + is_queued = tu_fifo_write_n(&_usbh_pending_ctrl_q, &entry, sizeof(entry)) == sizeof(entry); + } + + (void) osal_mutex_unlock(_usbh_mutex); + + if (claimed) { + break; + } + + if (is_nonblocking) { + return is_queued; + } - TU_VERIFY(is_idle); + // - OS_HAS_SCHEDULER: delay 1 ms + // - Otherwise: single execution context; drive the loop ourselves to progress the in-flight transfer. +#if CFG_TUSB_OS_HAS_SCHEDULER + osal_task_delay(1); +#else + tuh_task_ext(0, false); +#endif + } TU_LOG_USBH("[%u:%u] %s: ", usbh_get_rhport(daddr), daddr, (xfer->setup->bmRequestType_bit.type == TUSB_REQ_TYPE_STANDARD && xfer->setup->bRequest <= TUSB_REQ_SYNCH_FRAME) ? tu_str_std_request[xfer->setup->bRequest] : "Class Request"); TU_LOG_BUF_USBH(xfer->setup, 8); - if (xfer->complete_cb != NULL) { - TU_ASSERT(usbh_setup_send(daddr, (uint8_t const *) &_usbh_epbuf.request)); - }else { - // blocking if complete callback is not provided - // change callback to internal blocking, and result as user argument - volatile xfer_result_t result = XFER_RESULT_INVALID; - - // use user_data to point to xfer_result_t - ctrl_info->user_data = (uintptr_t) &result; - ctrl_info->complete_cb = _control_blocking_complete_cb; + // Sync: wire control_xfer_sync_complete BEFORE submit so a fast completion + // event has the cb in place. control_xfer_complete() captures both result + // and actual_len through this cb before release_slot overwrites ctrl_info. + volatile control_xfer_sync_param_t sync_state; + if (!is_nonblocking) { + sync_state.result = XFER_RESULT_INVALID; + sync_state.actual_len = 0; + ctrl_info->user_data = (uintptr_t) &sync_state; + ctrl_info->complete_cb = control_xfer_sync_complete; + } - TU_ASSERT(usbh_setup_send(daddr, (uint8_t const *) &_usbh_epbuf.request)); + if (!hcd_setup_send(usbh_get_rhport(daddr), daddr, (uint8_t const *) &_usbh_epbuf.request)) { + control_xfer_set_stage(CONTROL_STAGE_IDLE); + return false; + } - while (result == XFER_RESULT_INVALID) { - // Note: this can be called within an callback ie. part of tuh_task() - // therefore even with RTOS tuh_task_ext() still need to be invoked + if (!is_nonblocking) { + // No tuh_connected() escape needed: usbh_device_close() routes through + // control_xfer_complete(daddr, FAILED) on disconnect, which fires + // sync_complete and unblocks this poll. + while (sync_state.result == XFER_RESULT_INVALID) { +#if CFG_TUSB_OS_HAS_SCHEDULER + osal_task_delay(1); +#else tuh_task_ext(0, false); - // TODO probably some timeout to prevent hanged +#endif } - // update transfer result, user_data is expected to point to xfer_result_t + // Forward to caller (xfer->user_data, if set, is a xfer_result_t pointer). if (xfer->user_data != 0) { - *((xfer_result_t*) xfer->user_data) = result; + *((xfer_result_t*) xfer->user_data) = sync_state.result; } - xfer->result = result; - xfer->actual_len = ctrl_info->actual_len; + xfer->result = sync_state.result; + xfer->actual_len = sync_state.actual_len; } return true; } -static void _control_xfer_complete(uint8_t daddr, xfer_result_t result) { +// Start control transfer from pending fifo +static void control_xfer_dispatch_pending(void) { + usbh_ctrl_xfer_info_t* ctrl_info = &_usbh_data.ctrl_xfer_info; + + while (true) { + usbh_pending_ctrl_t xfer; + bool has_xfer = false; + + (void) osal_mutex_lock(_usbh_mutex, OSAL_TIMEOUT_WAIT_FOREVER); + if (ctrl_info->stage == CONTROL_STAGE_IDLE && + tu_fifo_read_n(&_usbh_pending_ctrl_q, &xfer, sizeof(xfer)) == sizeof(xfer)) { + ctrl_info->stage = CONTROL_STAGE_SETUP; + ctrl_info->daddr = xfer.daddr; + ctrl_info->actual_len = 0; + ctrl_info->failed_count = 0; + ctrl_info->buffer = xfer.buffer; + ctrl_info->complete_cb = xfer.complete_cb; + ctrl_info->user_data = xfer.user_data; + _usbh_epbuf.request = xfer.setup; + has_xfer = true; + } + (void) osal_mutex_unlock(_usbh_mutex); + + if (!has_xfer) { + return; // nothing to do + } + + // mismatched daddr_gen means pending transfer is stale due to the device got disconnected while in the FIFO + // Note: the address can be re-allocated to another device at this point. + if (xfer.daddr_gen == _usbh_data.daddr_gen[xfer.daddr]) { + TU_LOG_USBH("[%u:%u] %s: ", usbh_get_rhport(xfer.daddr), xfer.daddr, + (xfer.setup.bmRequestType_bit.type == TUSB_REQ_TYPE_STANDARD && xfer.setup.bRequest <= TUSB_REQ_SYNCH_FRAME) ? + tu_str_std_request[xfer.setup.bRequest] : "Class Request"); + TU_LOG_BUF_USBH(&xfer.setup, 8); + if (hcd_setup_send(usbh_get_rhport(xfer.daddr), xfer.daddr, (uint8_t const *) &_usbh_epbuf.request)) { + return; // transfer kicked-off, we are done + } + } + + // complete callback as FAILED and continue with next pending xfer + control_xfer_complete(xfer.daddr, XFER_RESULT_FAILED); + } +} + +static void control_xfer_complete(uint8_t daddr, xfer_result_t result) { TU_LOG_USBH("\r\n"); usbh_ctrl_xfer_info_t* ctrl_info = &_usbh_data.ctrl_xfer_info; @@ -901,7 +1073,8 @@ static void _control_xfer_complete(uint8_t daddr, xfer_result_t result) { .user_data = ctrl_info->user_data }; - _control_set_xfer_stage(CONTROL_STAGE_IDLE); + // set to IDLE before callback since cb can invoke another transfer + control_xfer_set_stage(CONTROL_STAGE_IDLE); if (xfer_temp.complete_cb != NULL) { xfer_temp.complete_cb(&xfer_temp); @@ -915,11 +1088,17 @@ static bool usbh_control_xfer_cb (uint8_t daddr, uint8_t ep_addr, xfer_result_t tusb_control_request_t const * request = &_usbh_epbuf.request; usbh_ctrl_xfer_info_t* ctrl_info = &_usbh_data.ctrl_xfer_info; + // Drop stale completions: slot already released (abort/close fired its cb) + // or now owns a different device's xfer (a pending entry was dispatched). + if (ctrl_info->stage == CONTROL_STAGE_IDLE || ctrl_info->daddr != daddr) { + return true; + } + switch (result) { case XFER_RESULT_STALLED: TU_LOG_USBH("[%u:%u] Control STALLED, xferred_bytes = %" PRIu32 "\r\n", rhport, daddr, xferred_bytes); TU_LOG_BUF_USBH(request, 8); - _control_xfer_complete(daddr, result); + control_xfer_complete(daddr, result); break; case XFER_RESULT_FAILED: @@ -931,11 +1110,14 @@ static bool usbh_control_xfer_cb (uint8_t daddr, uint8_t ep_addr, xfer_result_t ctrl_info->actual_len = 0; // reset actual_len (void) osal_mutex_unlock(_usbh_mutex); - TU_ASSERT(usbh_setup_send(daddr, (uint8_t const *) request)); + if (!hcd_setup_send(rhport, daddr, (uint8_t const *) request)) { + control_xfer_complete(daddr, XFER_RESULT_FAILED); + return false; + } } else { TU_LOG_USBH("[%u:%u] Control FAILED, xferred_bytes = %" PRIu32 "\r\n", rhport, daddr, xferred_bytes); TU_LOG_BUF_USBH(request, 8); - _control_xfer_complete(daddr, result); + control_xfer_complete(daddr, result); } break; @@ -944,7 +1126,7 @@ static bool usbh_control_xfer_cb (uint8_t daddr, uint8_t ep_addr, xfer_result_t case CONTROL_STAGE_SETUP: if (request->wLength > 0) { // DATA stage: initial data toggle is always 1 - _control_set_xfer_stage(CONTROL_STAGE_DATA); + control_xfer_set_stage(CONTROL_STAGE_DATA); const uint8_t ep_data = tu_edpt_addr(0, request->bmRequestType_bit.direction); TU_ASSERT(hcd_edpt_xfer(rhport, daddr, ep_data, ctrl_info->buffer, request->wLength)); return true; @@ -959,7 +1141,7 @@ static bool usbh_control_xfer_cb (uint8_t daddr, uint8_t ep_addr, xfer_result_t ctrl_info->actual_len = (uint16_t) xferred_bytes; // ACK stage: toggle is always 1 - _control_set_xfer_stage(CONTROL_STAGE_ACK); + control_xfer_set_stage(CONTROL_STAGE_ACK); const uint8_t ep_status = tu_edpt_addr(0, 1 - request->bmRequestType_bit.direction); TU_ASSERT(hcd_edpt_xfer(rhport, daddr, ep_status, NULL, 0)); break; @@ -976,7 +1158,7 @@ static bool usbh_control_xfer_cb (uint8_t daddr, uint8_t ep_addr, xfer_result_t } } - _control_xfer_complete(daddr, result); + control_xfer_complete(daddr, result); break; } @@ -1023,7 +1205,7 @@ bool tuh_edpt_abort_xfer(uint8_t daddr, uint8_t ep_addr) { const usbh_ctrl_xfer_info_t* ctrl_info = &_usbh_data.ctrl_xfer_info; TU_VERIFY(daddr == ctrl_info->daddr && ctrl_info->stage != CONTROL_STAGE_IDLE); hcd_edpt_abort_xfer(rhport, daddr, ep_addr); - _control_set_xfer_stage(CONTROL_STAGE_IDLE); // reset control transfer state to idle + control_xfer_complete(daddr, XFER_RESULT_ABORTED); } else { usbh_device_t* dev = get_device(daddr); TU_VERIFY(dev); @@ -1055,9 +1237,9 @@ uint8_t *usbh_get_enum_buf(void) { void usbh_int_set(bool enabled) { // TODO all host controller if multiple are used since they shared the same event queue if (enabled) { - hcd_int_enable(_usbh_data.controller_id); + hcd_int_enable(_usbh_controller_id); } else { - hcd_int_disable(_usbh_data.controller_id); + hcd_int_disable(_usbh_controller_id); } } diff --git a/src/osal/osal.h b/src/osal/osal.h index 4840463f3..69cb356d4 100644 --- a/src/osal/osal.h +++ b/src/osal/osal.h @@ -76,28 +76,31 @@ typedef void (*osal_task_func_t)(void* param); /*-------------------------------------------------------------------- OSAL Porting API Should be implemented as static inline function in osal_port.h header - uint32_t osal_time_millis(void); + uint32_t osal_time_millis(void); - void osal_spin_init(osal_spinlock_t *ctx); - void osal_spin_lock(osal_spinlock_t *ctx, bool in_isr) - void osal_spin_unlock(osal_spinlock_t *ctx, bool in_isr); + void osal_task_delay(uint32_t msec); + osal_task_handle_t osal_task_get_current_handle(void); - osal_semaphore_t osal_semaphore_create(osal_semaphore_def_t* semdef); - bool osal_semaphore_delete(osal_semaphore_t semd_hdl); - bool osal_semaphore_post(osal_semaphore_t sem_hdl, bool in_isr); - bool osal_semaphore_wait(osal_semaphore_t sem_hdl, uint32_t msec); - void osal_semaphore_reset(osal_semaphore_t sem_hdl); + void osal_spin_init(osal_spinlock_t *ctx); + void osal_spin_lock(osal_spinlock_t *ctx, bool in_isr) + void osal_spin_unlock(osal_spinlock_t *ctx, bool in_isr); - osal_mutex_t osal_mutex_create(osal_mutex_def_t* mdef); - bool osal_mutex_delete(osal_mutex_t mutex_hdl) - bool osal_mutex_lock (osal_mutex_t sem_hdl, uint32_t msec); - bool osal_mutex_unlock(osal_mutex_t mutex_hdl); + osal_semaphore_t osal_semaphore_create(osal_semaphore_def_t* semdef); + bool osal_semaphore_delete(osal_semaphore_t semd_hdl); + bool osal_semaphore_post(osal_semaphore_t sem_hdl, bool in_isr); + bool osal_semaphore_wait(osal_semaphore_t sem_hdl, uint32_t msec); + void osal_semaphore_reset(osal_semaphore_t sem_hdl); - osal_queue_t osal_queue_create(osal_queue_def_t* qdef); - bool osal_queue_delete(osal_queue_t qhdl); - bool osal_queue_receive(osal_queue_t qhdl, void* data, uint32_t msec); - bool osal_queue_send(osal_queue_t qhdl, void const * data, bool in_isr); - bool osal_queue_empty(osal_queue_t qhdl); + osal_mutex_t osal_mutex_create(osal_mutex_def_t* mdef); + bool osal_mutex_delete(osal_mutex_t mutex_hdl) + bool osal_mutex_lock (osal_mutex_t sem_hdl, uint32_t msec); + bool osal_mutex_unlock(osal_mutex_t mutex_hdl); + + osal_queue_t osal_queue_create(osal_queue_def_t* qdef); + bool osal_queue_delete(osal_queue_t qhdl); + bool osal_queue_receive(osal_queue_t qhdl, void* data, uint32_t msec); + bool osal_queue_send(osal_queue_t qhdl, void const * data, bool in_isr); + bool osal_queue_empty(osal_queue_t qhdl); --------------------------------------------------------------------------*/ diff --git a/src/osal/osal_freertos.h b/src/osal/osal_freertos.h index 898edd4ed..2f36aa9e8 100644 --- a/src/osal/osal_freertos.h +++ b/src/osal/osal_freertos.h @@ -83,6 +83,19 @@ typedef struct { //--------------------------------------------------------------------+ // TASK API //--------------------------------------------------------------------+ +typedef TaskHandle_t osal_task_handle_t; + +// Requires INCLUDE_xTaskGetCurrentTaskHandle == 1 in FreeRTOSConfig.h. FreeRTOS +// also exposes the symbol when configUSE_MUTEXES == 1, so accept either. +#if !defined(INCLUDE_xTaskGetCurrentTaskHandle) || (INCLUDE_xTaskGetCurrentTaskHandle == 0) + #if !defined(configUSE_MUTEXES) || (configUSE_MUTEXES == 0) + #error "TinyUSB host stack requires INCLUDE_xTaskGetCurrentTaskHandle or configUSE_MUTEXES to be enabled in FreeRTOSConfig.h" + #endif +#endif +TU_ATTR_ALWAYS_INLINE static inline osal_task_handle_t osal_task_get_current_handle(void) { + return xTaskGetCurrentTaskHandle(); +} + TU_ATTR_ALWAYS_INLINE static inline uint32_t _osal_ms2tick(uint32_t msec) { if (msec == OSAL_TIMEOUT_WAIT_FOREVER) { return portMAX_DELAY; } if (msec == 0) { return 0; } diff --git a/src/osal/osal_mynewt.h b/src/osal/osal_mynewt.h index 335d53491..d1fa77ecb 100644 --- a/src/osal/osal_mynewt.h +++ b/src/osal/osal_mynewt.h @@ -36,6 +36,12 @@ //--------------------------------------------------------------------+ // TASK API //--------------------------------------------------------------------+ +typedef struct os_task* osal_task_handle_t; + +TU_ATTR_ALWAYS_INLINE static inline osal_task_handle_t osal_task_get_current_handle(void) { + return os_sched_get_current_task(); +} + TU_ATTR_ALWAYS_INLINE static inline void osal_task_delay(uint32_t msec) { os_time_delay( os_time_ms_to_ticks32(msec) ); } diff --git a/src/osal/osal_none.h b/src/osal/osal_none.h index 7bf6029d6..e174d3518 100644 --- a/src/osal/osal_none.h +++ b/src/osal/osal_none.h @@ -34,6 +34,22 @@ extern "C" { // osal_time_millis() is not provided, tusb_time_millis_api() must be implemented by user application //--------------------------------------------------------------------+ +// TASK API +//--------------------------------------------------------------------+ +// Bare-metal single context: return a non-NULL sentinel so equality compares true. +typedef void* osal_task_handle_t; + +TU_ATTR_ALWAYS_INLINE static inline osal_task_handle_t osal_task_get_current_handle(void) { + return (osal_task_handle_t) 1; +} + +// Bare-metal has no scheduler to yield to; this is dead code in practice because +// callers gate it on running outside the host task, which can't happen here. +TU_ATTR_ALWAYS_INLINE static inline void osal_task_delay(uint32_t msec) { + (void) msec; +} + +//--------------------------------------------------------------------+ // Spinlock API //--------------------------------------------------------------------+ // Note: This implementation is designed for bare-metal single-core systems without RTOS. diff --git a/src/osal/osal_pico.h b/src/osal/osal_pico.h index 6a0a21bb3..364c38b01 100644 --- a/src/osal/osal_pico.h +++ b/src/osal/osal_pico.h @@ -39,6 +39,13 @@ extern "C" { //--------------------------------------------------------------------+ // TASK API //--------------------------------------------------------------------+ +// Bare-metal single context: return a non-NULL sentinel so equality compares true. +typedef void* osal_task_handle_t; + +TU_ATTR_ALWAYS_INLINE static inline osal_task_handle_t osal_task_get_current_handle(void) { + return (osal_task_handle_t) 1; +} + TU_ATTR_ALWAYS_INLINE static inline void osal_task_delay(uint32_t msec) { sleep_ms(msec); } diff --git a/src/osal/osal_rtthread.h b/src/osal/osal_rtthread.h index f560281c5..a151a7d70 100644 --- a/src/osal/osal_rtthread.h +++ b/src/osal/osal_rtthread.h @@ -38,6 +38,12 @@ extern "C" { //--------------------------------------------------------------------+ // TASK API //--------------------------------------------------------------------+ +typedef rt_thread_t osal_task_handle_t; + +TU_ATTR_ALWAYS_INLINE static inline osal_task_handle_t osal_task_get_current_handle(void) { + return rt_thread_self(); +} + TU_ATTR_ALWAYS_INLINE static inline void osal_task_delay(uint32_t msec) { rt_thread_mdelay(msec); } diff --git a/src/osal/osal_rtx4.h b/src/osal/osal_rtx4.h index e1930c96c..e5b708a2c 100644 --- a/src/osal/osal_rtx4.h +++ b/src/osal/osal_rtx4.h @@ -37,6 +37,12 @@ extern "C" { //--------------------------------------------------------------------+ // TASK API //--------------------------------------------------------------------+ +typedef OS_TID osal_task_handle_t; + +TU_ATTR_ALWAYS_INLINE static inline osal_task_handle_t osal_task_get_current_handle(void) { + return os_tsk_self(); +} + TU_ATTR_ALWAYS_INLINE static inline void osal_task_delay(uint32_t msec) { uint16_t hi = msec >> 16; uint16_t lo = msec; diff --git a/src/osal/osal_threadx.h b/src/osal/osal_threadx.h index 6bcf9c5ab..cca4eb487 100644 --- a/src/osal/osal_threadx.h +++ b/src/osal/osal_threadx.h @@ -37,6 +37,11 @@ extern "C" { //--------------------------------------------------------------------+ // TASK API //--------------------------------------------------------------------+ +typedef TX_THREAD* osal_task_handle_t; + +TU_ATTR_ALWAYS_INLINE static inline osal_task_handle_t osal_task_get_current_handle(void) { + return tx_thread_identify(); +} TU_ATTR_ALWAYS_INLINE static inline uint32_t _osal_ms2tick(uint32_t msec) { if ( msec == TX_WAIT_FOREVER ) { diff --git a/src/osal/osal_zephyr.h b/src/osal/osal_zephyr.h index 900ac786c..6ea45131e 100644 --- a/src/osal/osal_zephyr.h +++ b/src/osal/osal_zephyr.h @@ -31,6 +31,12 @@ //--------------------------------------------------------------------+ // TASK API //--------------------------------------------------------------------+ +typedef k_tid_t osal_task_handle_t; + +TU_ATTR_ALWAYS_INLINE static inline osal_task_handle_t osal_task_get_current_handle(void) { + return k_current_get(); +} + TU_ATTR_ALWAYS_INLINE static inline void osal_task_delay(uint32_t msec) { k_msleep(msec); } diff --git a/src/portable/mentor/musb/dcd_musb.c b/src/portable/mentor/musb/dcd_musb.c index 56429ac1f..1d1280bf4 100644 --- a/src/portable/mentor/musb/dcd_musb.c +++ b/src/portable/mentor/musb/dcd_musb.c @@ -82,25 +82,88 @@ typedef struct { enum { PIPE0_STATE_IDLE = 0, // no active control transfer - PIPE0_STATE_DATA, // DATA stage (IN or OUT — direction implied by CSR/dir) + PIPE0_STATE_DATA_IN, // DATA IN stage + PIPE0_STATE_DATA_OUT, // DATA OUT stage PIPE0_STATE_STATUS_IN, // STATUS IN — device sends IN-ZLP; awaits send-ACK IRQ PIPE0_STATE_STATUS_OUT, // post-DATAEND, neither edpt0_xfer(STATUS OUT) nor confirmation IRQ has happened yet - PIPE0_STATE_STATUS_OUT_PENDING, // one of {edpt0_xfer(STATUS OUT), confirmation IRQ} has happened; the other fires xfer_complete + PIPE0_STATE_STATUS_OUT_PENDING_XFER, // edpt0_xfer(STATUS OUT) called first; the confirmation IRQ fires xfer_complete + PIPE0_STATE_STATUS_OUT_PENDING_IRQ, // confirmation IRQ seen (or synthesized) first; edpt0_xfer(STATUS OUT) fires xfer_complete }; +// EP0 control-transfer state (own scalars, not a pipe[] slot). typedef struct { - struct { - uint8_t *buf; // DATA OUT drain target (only valid while EP0 is in DATA OUT stage) - uint16_t xact_len; // chunk length most recently armed via edpt0_xfer; reported in xfer_complete - uint16_t remain_wlength; // bytes remaining in the control transfer's DATA stage - uint8_t state; - uint8_t pending_addr; // new USB address latched by dcd_set_address; applied when STATUS IN completes - } pipe0; + uint8_t *buf; // DATA OUT drain target (only valid while EP0 is in DATA OUT stage) + uint16_t xact_len; // DATA IN chunk length armed via edpt0_xfer; reported in its xfer_complete (OUT reports count0) + uint16_t remain_wlength; // bytes remaining in the control transfer's DATA stage + uint8_t state; + uint8_t pending_addr; // new USB address latched by dcd_set_address; applied when STATUS IN completes + bool rxrdy_consumed; // RxPktRdy left set in hw for an already-consumed packet (NAK flow control); + // RXRDY events are stale while set. Cleared when RXRDYC is written. + bool deferred_setup_valid; + uint32_t deferred_setup[2]; // raw SETUP words, replayed via pipe0_start_setup +} pipe0_state_t; + +typedef struct { + pipe0_state_t pipe0; pipe_state_t pipe[MUSB_PIPE_COUNT]; } dcd_data_t; static dcd_data_t _dcd; +// Read the 8-byte SETUP packet (2 words) from the EP0 FIFO into setup[]. Does not ack RxPktRdy. +static bool pipe0_read_setup(musb_regs_t* musb_regs, musb_ep_csr_t* ep_csr, uint32_t setup[2]) { + TU_ASSERT(sizeof(tusb_control_request_t) == ep_csr->count0); + setup[0] = musb_regs->fifo[0]; + setup[1] = musb_regs->fifo[0]; + return true; +} + +static void pipe0_start_setup(uint8_t rhport, musb_ep_csr_t* ep_csr, + const uint32_t setup[2], bool is_isr) { + tusb_control_request_t const* req = (tusb_control_request_t const*) setup; + pipe0_state_t* pipe0 = &_dcd.pipe0; + pipe0->remain_wlength = req->wLength; + + if (req->wLength == 0) { + // Leave RXRDY set; edpt0_xfer(STATUS IN) acks it together with DATAEND. + pipe0->state = PIPE0_STATE_STATUS_IN; + pipe0->rxrdy_consumed = true; + } else { + if (req->bmRequestType & TUSB_DIR_IN_MASK) { + pipe0->state = PIPE0_STATE_DATA_IN; + // On a deferred replay the packet's RXRDY stays parked until the edpt0_xfer(DATA IN) arm + // acks it — a stale latched EP0 IRQ in between is gated by rxrdy_consumed. + if (!pipe0->rxrdy_consumed) { + ep_csr->csr0l = MUSB_CSRL0_RXRDYC; + } + } else { + // If OUT (rx) direction, let edpt0_xfer() clear RXRDY when it's ready to receive data. + // Deliberate deviation from the databook's canonical flow (ack right after unload), + // used as NAK flow control until usbd arms the drain buffer. + pipe0->state = PIPE0_STATE_DATA_OUT; + pipe0->rxrdy_consumed = true; + } + } + + dcd_event_setup_received(rhport, (const uint8_t *) setup, is_isr); +} + +// Replay a previously deferred SETUP, if any. +static void pipe0_try_deferred_setup(uint8_t rhport, musb_ep_csr_t* ep_csr, bool is_isr) { + pipe0_state_t* pipe0 = &_dcd.pipe0; + if (!pipe0->deferred_setup_valid) { + return; + } + + pipe0->deferred_setup_valid = false; + pipe0_start_setup(rhport, ep_csr, pipe0->deferred_setup, is_isr); +} + +// Last DATA packet: wLength satisfied, or a short packet (incl. ZLP) ends the stage. +TU_ATTR_ALWAYS_INLINE static inline bool pipe0_data_stage_done(uint16_t xfer_len) { + return _dcd.pipe0.remain_wlength == 0 || xfer_len < CFG_TUD_ENDPOINT0_SIZE; +} + // EP0 must not call this — it has its own scalars in dcd_data_t. TU_ATTR_ALWAYS_INLINE static inline pipe_state_t* pipe_get(uint8_t epnum, tusb_dir_t epdir) { size_t idx = epnum - 1u; @@ -238,7 +301,7 @@ static void pipe_write(musb_regs_t* musb_regs, pipe_state_t* pipe, uint8_t epnum // Called from the TX interrupt. If the last queued packet finished the transfer, // signal completion; otherwise queue the next packet. -static void process_epin(uint8_t rhport, musb_regs_t *musb_regs, uint8_t epnum) { +static void process_epin_isr(uint8_t rhport, musb_regs_t *musb_regs, uint8_t epnum) { musb_ep_csr_t* ep_csr = get_ep_csr(musb_regs, epnum); const uint_fast8_t csrl = ep_csr->tx_csrl; if (csrl & MUSB_TXCSRL1_STALLED) { @@ -268,7 +331,7 @@ static void process_epin(uint8_t rhport, musb_regs_t *musb_regs, uint8_t epnum) // Drain one packet from the Rx FIFO into pipe->buf/fifo, update pipe state, and // 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() + 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 rx_count = ep_csr->rx_count; const uint16_t xact_len = tu_min16(tu_min16(pipe->remaining, mps), rx_count); @@ -287,7 +350,7 @@ static bool pipe_read(musb_regs_t* musb_regs, pipe_state_t* pipe, uint8_t epnum) return (xact_len < mps); } -static void process_epout(uint8_t rhport, musb_regs_t *musb_regs, uint8_t epnum, bool is_isr) { +static void process_epout_isr(uint8_t rhport, musb_regs_t *musb_regs, uint8_t epnum, bool is_isr) { musb_ep_csr_t* ep_csr = get_ep_csr(musb_regs, epnum); if (ep_csr->rx_csrl & MUSB_RXCSRL1_STALLED) { ep_csr->rx_csrl &= ~(MUSB_RXCSRL1_STALLED | MUSB_RXCSRL1_OVER); @@ -323,7 +386,7 @@ static void process_epout(uint8_t rhport, musb_regs_t *musb_regs, uint8_t epnum, static bool edpt_n_xfer(uint8_t rhport, uint8_t ep_addr, void *buffer, uint16_t total_bytes, bool use_fifo, bool is_isr) { const uint8_t epnum = tu_edpt_number(ep_addr); - const unsigned dir_in = tu_edpt_dir(ep_addr); + const tusb_dir_t dir_in = tu_edpt_dir(ep_addr); pipe_state_t *pipe = pipe_get(epnum, dir_in); if (use_fifo) { @@ -342,13 +405,13 @@ static bool edpt_n_xfer(uint8_t rhport, uint8_t ep_addr, void *buffer, uint16_t if (dir_in) { pipe_write(musb_regs, pipe, epnum); } else { - // Re-enable Rx interrupt (may have been masked by the no-buffer path in process_epout) + // Re-enable Rx interrupt (may have been masked by the no-buffer path in process_epout_isr) musb_regs->intr_rxen |= (uint16_t)TU_BIT(epnum); // Drain any packet staged in the Rx FIFO from a prior no-buffer interrupt. - // process_epout() fires dcd_event_xfer_complete() itself if the drain completes. + // process_epout_isr() fires dcd_event_xfer_complete() itself if the drain completes. if (ep_csr->rx_csrl & MUSB_RXCSRL1_RXRDY) { - process_epout(rhport, musb_regs, epnum, is_isr); + process_epout_isr(rhport, musb_regs, epnum, is_isr); } } return true; @@ -358,44 +421,54 @@ static bool edpt0_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t *buffer, uint16_ TU_ASSERT(total_bytes <= CFG_TUD_ENDPOINT0_SIZE); /* EP0 only supports 1 packet per dcd_edpt_xfer()*/ musb_regs_t* musb_regs = MUSB_REGS(rhport); musb_ep_csr_t* ep_csr = get_ep_csr(musb_regs, 0); + pipe0_state_t* pipe0 = &_dcd.pipe0; const unsigned dir_in = tu_edpt_dir(ep_addr); - switch (_dcd.pipe0.state) { - case PIPE0_STATE_DATA: { - _dcd.pipe0.xact_len = total_bytes; - if (dir_in) { - // DATA IN: load FIFO, set TXRDY. Add DATAEND on the last chunk - // (ep0_remain_datalen == 0 after this load) to end the data stage. - tu_hwfifo_write(&musb_regs->fifo[0], buffer, total_bytes, NULL); - _dcd.pipe0.remain_wlength -= total_bytes; - if (_dcd.pipe0.remain_wlength == 0) { - ep_csr->csr0l = MUSB_CSRL0_TXRDY | MUSB_CSRL0_DATAEND; - } else { - ep_csr->csr0l = MUSB_CSRL0_TXRDY; - } - } else { - // DATA OUT: arm drain target, ack RXRDY so host can send DATA OUT. - _dcd.pipe0.buf = buffer; + switch (pipe0->state) { + // DATA stage exits on its last packet, so state matches the call direction here. + case PIPE0_STATE_DATA_IN: + TU_ASSERT(dir_in); + pipe0->xact_len = total_bytes; + if (pipe0->rxrdy_consumed) { // replayed SETUP: ack its parked RXRDY before loading the FIFO ep_csr->csr0l = MUSB_CSRL0_RXRDYC; + pipe0->rxrdy_consumed = false; + } + tu_hwfifo_write(&musb_regs->fifo[0], buffer, total_bytes, NULL); + pipe0->remain_wlength -= total_bytes; + // Add DATAEND on the last packet to end the data stage. + if (pipe0_data_stage_done(total_bytes)) { + ep_csr->csr0l = MUSB_CSRL0_TXRDY | MUSB_CSRL0_DATAEND; + } else { + ep_csr->csr0l = MUSB_CSRL0_TXRDY; } break; - } + + case PIPE0_STATE_DATA_OUT: + TU_ASSERT(!dir_in); + pipe0->xact_len = total_bytes; + pipe0->buf = buffer; // arm drain target, ack RXRDY so host can send DATA OUT + ep_csr->csr0l = MUSB_CSRL0_RXRDYC; + pipe0->rxrdy_consumed = false; + break; case PIPE0_STATE_STATUS_IN: TU_ASSERT(dir_in && total_bytes == 0); // only STATUS IN allowed ep_csr->csr0l = MUSB_CSRL0_RXRDYC | MUSB_CSRL0_DATAEND; + pipe0->rxrdy_consumed = false; break; case PIPE0_STATE_STATUS_OUT: TU_ASSERT(!dir_in && total_bytes == 0); // only STATUS OUT allowed // First event of the STATUS OUT pair — wait for the IRQ to fire complete. - _dcd.pipe0.state = PIPE0_STATE_STATUS_OUT_PENDING; + pipe0->state = PIPE0_STATE_STATUS_OUT_PENDING_XFER; break; - case PIPE0_STATE_STATUS_OUT_PENDING: - // Second event — IRQ already arrived, fire complete now. - _dcd.pipe0.state = PIPE0_STATE_IDLE; + case PIPE0_STATE_STATUS_OUT_PENDING_IRQ: + // Second event — IRQ already arrived, fire complete now. The old transfer is retired here, + // so a deferred SETUP can be replayed safely. + pipe0->state = PIPE0_STATE_IDLE; dcd_event_xfer_complete(rhport, ep_addr, 0, XFER_RESULT_SUCCESS, is_isr); + pipe0_try_deferred_setup(rhport, ep_csr, is_isr); break; default: break; @@ -404,15 +477,65 @@ static bool edpt0_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t *buffer, uint16_ return true; } +// Advance EP0's status-stage state machine on a tail event: the csrl==0 confirmation IRQ, or such a +// confirmation combined with a new SETUP (caller sets deferred_setup_valid first). ISR context only. +static void pipe0_process_xfer_state_isr(uint8_t rhport, musb_regs_t* musb_regs, musb_ep_csr_t* ep_csr) { + pipe0_state_t* pipe0 = &_dcd.pipe0; + switch (pipe0->state) { + case PIPE0_STATE_DATA_IN: + if (pipe0_data_stage_done(pipe0->xact_len)) { + if (pipe0->deferred_setup_valid) { + pipe0->state = PIPE0_STATE_STATUS_OUT_PENDING_IRQ; // status confirm coalesced with deferred SETUP + } else { + pipe0->state = PIPE0_STATE_STATUS_OUT; // await host's STATUS-OUT ZLP IRQ + } + } + dcd_event_xfer_complete(rhport, TU_EP0_IN, pipe0->xact_len, XFER_RESULT_SUCCESS, true); + break; + + case PIPE0_STATE_STATUS_OUT: + // Confirmation seen — await edpt0_xfer(STATUS OUT) to fire complete. + pipe0->state = PIPE0_STATE_STATUS_OUT_PENDING_IRQ; + break; + + case PIPE0_STATE_STATUS_OUT_PENDING_XFER: + // edpt0_xfer(STATUS OUT) already called — fire complete and replay now. + pipe0->state = PIPE0_STATE_IDLE; + dcd_event_xfer_complete(rhport, TU_EP0_OUT, 0, XFER_RESULT_SUCCESS, true); + pipe0_try_deferred_setup(rhport, ep_csr, true); + break; + + case PIPE0_STATE_STATUS_OUT_PENDING_IRQ: + // Confirmation already accounted for — the pairing edpt0_xfer(STATUS OUT) fires complete. + break; + + case PIPE0_STATE_STATUS_IN: + if (pipe0->pending_addr) { + musb_regs->faddr = pipe0->pending_addr; + pipe0->pending_addr = 0; + } + pipe0->state = PIPE0_STATE_IDLE; + dcd_event_xfer_complete(rhport, TU_EP0_IN, 0, XFER_RESULT_SUCCESS, true); + pipe0_try_deferred_setup(rhport, ep_csr, true); + break; + + default: break; + } +} + // 21.1.5: endpoint 0 service routine as peripheral -static void process_ep0(uint8_t rhport) { +static void process_ep0_isr(uint8_t rhport) { musb_regs_t* musb_regs = MUSB_REGS(rhport); musb_ep_csr_t* ep_csr = get_ep_csr(musb_regs, 0); + pipe0_state_t* pipe0 = &_dcd.pipe0; uint_fast8_t csrl = ep_csr->csr0l; + // 21.1.5: SentStall and SetupEnd must be checked before anything else. if (csrl & MUSB_CSRL0_STALLED) { ep_csr->csr0l = 0; - _dcd.pipe0.state = PIPE0_STATE_IDLE; + pipe0->state = PIPE0_STATE_IDLE; + pipe0->deferred_setup_valid = false; + pipe0->rxrdy_consumed = false; return; } @@ -420,7 +543,9 @@ static void process_ep0(uint8_t rhport) { // Host aborted the current control transfer (new SETUP or premature STATUS). // do nothing, it is probably another setup packet, usbd will reset its state. ep_csr->csr0l = MUSB_CSRL0_SETENDC; - _dcd.pipe0.state = PIPE0_STATE_IDLE; + pipe0->state = PIPE0_STATE_IDLE; + pipe0->deferred_setup_valid = false; + pipe0->rxrdy_consumed = false; if (!(csrl & MUSB_CSRL0_RXRDY)) { return; /* no SETUP waiting behind it */ } @@ -428,105 +553,87 @@ static void process_ep0(uint8_t rhport) { // Receive Data (Setup or OUT) if (csrl & MUSB_CSRL0_RXRDY) { - const uint16_t count0 = ep_csr->count0; - switch (_dcd.pipe0.state) { - case PIPE0_STATE_IDLE: - TU_ASSERT(sizeof(tusb_control_request_t) == count0, ); - union { - tusb_control_request_t req; - uint32_t u32[2]; - } setup_packet; - setup_packet.u32[0] = musb_regs->fifo[0]; - setup_packet.u32[1] = musb_regs->fifo[0]; - - _dcd.pipe0.remain_wlength = setup_packet.req.wLength; - - if (setup_packet.req.wLength == 0) { - _dcd.pipe0.state = PIPE0_STATE_STATUS_IN; - } else { - _dcd.pipe0.state = PIPE0_STATE_DATA; - // If OUT (rx) direction, let edpt0_xfer() clear RXRDY when it's ready to receive data. - if (setup_packet.req.bmRequestType & TUSB_DIR_IN_MASK) { - ep_csr->csr0l = MUSB_CSRL0_RXRDYC; - } - } - dcd_event_setup_received(rhport, (const uint8_t *)&setup_packet.req, true); + if (pipe0->rxrdy_consumed) { + return; // stale latched IRQ: this RXRDY's packet was already drained + } + switch (pipe0->state) { + case PIPE0_STATE_IDLE: { + uint32_t setup[2]; + TU_VERIFY(pipe0_read_setup(musb_regs, ep_csr, setup), ); + pipe0_start_setup(rhport, ep_csr, setup, true); break; + } - case PIPE0_STATE_DATA: { + case PIPE0_STATE_DATA_OUT: { // EP0 OUT is single-packet (TU_ASSERT total_bytes <= EP0_SIZE in edpt0_xfer) // so the whole packet drains in one shot. + const uint16_t count0 = ep_csr->count0; if (count0) { - tu_hwfifo_read(&musb_regs->fifo[0], _dcd.pipe0.buf, count0, NULL); - _dcd.pipe0.remain_wlength -= count0; + TU_ASSERT(pipe0->buf, ); + tu_hwfifo_read(&musb_regs->fifo[0], pipe0->buf, count0, NULL); + pipe0->remain_wlength -= tu_min16(count0, pipe0->remain_wlength); // clamp: host may overrun } - if (_dcd.pipe0.remain_wlength == 0) { - // last packet: change state and leave RXRDY for edpt0_xfer(STATUS IN) to ack - _dcd.pipe0.state = PIPE0_STATE_STATUS_IN; - } else { - ep_csr->csr0l = MUSB_CSRL0_RXRDYC; + // RXRDY stays set until the next edpt0_xfer arm acks it (NAK flow control): + // edpt0_xfer(DATA OUT) for a mid-stream packet, edpt0_xfer(STATUS IN) for the last. + pipe0->rxrdy_consumed = true; + if (pipe0_data_stage_done(count0)) { + pipe0->state = PIPE0_STATE_STATUS_IN; } dcd_event_xfer_complete(rhport, TU_EP0_OUT, count0, XFER_RESULT_SUCCESS, true); break; } + // New SETUP arrived while the old control transfer's tail events are still in flight (IRQs + // combined under high CPU load): the old transfer's status confirm and this SETUP land together. + case PIPE0_STATE_DATA_IN: + case PIPE0_STATE_STATUS_OUT: + case PIPE0_STATE_STATUS_OUT_PENDING_XFER: + case PIPE0_STATE_STATUS_OUT_PENDING_IRQ: + case PIPE0_STATE_STATUS_IN: + // Save it, then finish the old transfer's tail event; deferred_setup_valid makes + // pipe0_process_xfer_state_isr() synthesize the coalesced status confirm and replay the SETUP + // once the old transfer is retired. Its RXRDY stays parked so a stale IRQ can't re-process it. + TU_VERIFY(pipe0_read_setup(musb_regs, ep_csr, pipe0->deferred_setup), ); + pipe0->deferred_setup_valid = true; + pipe0->rxrdy_consumed = true; + pipe0_process_xfer_state_isr(rhport, musb_regs, ep_csr); + break; + default: break; } return; } + if (csrl & MUSB_CSRL0_DATAEND) { + // Last DATA IN chunk / STATUS IN arm wrote TXRDY|DATAEND and the status stage has not completed + // yet — nothing to service. DataEnd is CPU-set-only per the CSR access table; whether it ever + // reads back 1 is vendor-dependent (on cores where it reads 0 this guard is dead code). + return; + } + /* When CSRL0 is zero, it means that either * - completion of sending any length packet TxPktRdy clear * - or status stage is complete (ZLP) after DataEnd is set */ - switch (_dcd.pipe0.state) { - case PIPE0_STATE_DATA: - // csrl == 0 in DATA state = TXRDY just cleared, i.e. a DATA IN packet was successfully sent. If the just-sent - // packet was the last (DATAEND was set when ep0_remain_datalen hit zero), transition - // to STATUS_OUT to await the host's STATUS-OUT ZLP confirmation IRQ. - if (_dcd.pipe0.remain_wlength == 0) { - _dcd.pipe0.state = PIPE0_STATE_STATUS_OUT; - } - dcd_event_xfer_complete(rhport, TU_EP0_IN, _dcd.pipe0.xact_len, XFER_RESULT_SUCCESS, true); - break; - - case PIPE0_STATE_STATUS_OUT: - // First event of the STATUS OUT pair — wait for edpt0_xfer(STATUS OUT) to fire complete. - _dcd.pipe0.state = PIPE0_STATE_STATUS_OUT_PENDING; - break; - - case PIPE0_STATE_STATUS_OUT_PENDING: - // Second event — edpt0_xfer(STATUS OUT) already called, fire complete now. - _dcd.pipe0.state = PIPE0_STATE_IDLE; - dcd_event_xfer_complete(rhport, TU_EP0_OUT, 0, XFER_RESULT_SUCCESS, true); - break; - - case PIPE0_STATE_STATUS_IN: - if (_dcd.pipe0.pending_addr) { - musb_regs->faddr = _dcd.pipe0.pending_addr; - _dcd.pipe0.pending_addr = 0; - } - _dcd.pipe0.state = PIPE0_STATE_IDLE; - dcd_event_xfer_complete(rhport, TU_EP0_IN, 0, XFER_RESULT_SUCCESS, true); - break; - - default: break; - } + pipe0_process_xfer_state_isr(rhport, musb_regs, ep_csr); } // Upon BUS RESET is detected, hardware havs already done: // faddr = 0, index = 0, flushes all ep fifos, clears all ep csr, enabled all ep interrupts -static void process_bus_reset(uint8_t rhport) { +static void process_bus_reset_isr(uint8_t rhport) { musb_regs_t* musb = MUSB_REGS(rhport); #if MUSB_CFG_DYNAMIC_FIFO alloced_fifo_bytes = CFG_TUD_ENDPOINT0_SIZE; #endif - _dcd.pipe0.state = PIPE0_STATE_IDLE; - _dcd.pipe0.buf = NULL; - _dcd.pipe0.xact_len = 0; - _dcd.pipe0.remain_wlength = 0; + pipe0_state_t* pipe0 = &_dcd.pipe0; + pipe0->state = PIPE0_STATE_IDLE; + pipe0->buf = NULL; + pipe0->xact_len = 0; + pipe0->remain_wlength = 0; + pipe0->deferred_setup_valid = false; + pipe0->rxrdy_consumed = false; musb->intr_txen = 1; /* Enable only EP0 */ musb->intr_rxen = 0; @@ -589,19 +696,21 @@ void dcd_int_disable(uint8_t rhport) { } // Receive Set Address request. Stash the new address here; hardware faddr is -// latched from pending_addr in process_ep0 once the STATUS IN completes (per +// latched from pending_addr in process_ep0_isr once the STATUS IN completes (per // USB spec, address must only take effect after the status stage). void dcd_set_address(uint8_t rhport, uint8_t dev_addr) { musb_regs_t* musb_regs = MUSB_REGS(rhport); musb_ep_csr_t* ep_csr = get_ep_csr(musb_regs, 0); - _dcd.pipe0.pending_addr = dev_addr; - _dcd.pipe0.buf = NULL; - _dcd.pipe0.xact_len = 0; - _dcd.pipe0.state = PIPE0_STATE_STATUS_IN; + pipe0_state_t* pipe0 = &_dcd.pipe0; + pipe0->pending_addr = dev_addr; + pipe0->buf = NULL; + pipe0->xact_len = 0; + pipe0->state = PIPE0_STATE_STATUS_IN; /* Send STATUS IN ZLP with DATAEND; host ACK fires the confirmation IRQ. */ ep_csr->csr0l = MUSB_CSRL0_RXRDYC | MUSB_CSRL0_DATAEND; + pipe0->rxrdy_consumed = false; } // Wake up host @@ -646,7 +755,7 @@ void dcd_sof_enable(uint8_t rhport, bool en) bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const * ep_desc) { const unsigned ep_addr = ep_desc->bEndpointAddress; const unsigned epn = tu_edpt_number(ep_addr); - const unsigned epdir = tu_edpt_dir(ep_addr); + const tusb_dir_t epdir = tu_edpt_dir(ep_addr); const unsigned mps = tu_edpt_packet_size(ep_desc); pipe_state_t *pipe = pipe_get(epn, epdir); @@ -689,7 +798,7 @@ bool dcd_edpt_iso_alloc(uint8_t rhport, uint8_t ep_addr, uint16_t largest_packet bool dcd_edpt_iso_activate(uint8_t rhport, tusb_desc_endpoint_t const *ep_desc ) { const unsigned ep_addr = ep_desc->bEndpointAddress; const unsigned epn = tu_edpt_number(ep_addr); - const unsigned dir_in = tu_edpt_dir(ep_addr); + const tusb_dir_t dir_in = tu_edpt_dir(ep_addr); const unsigned mps = tu_edpt_packet_size(ep_desc); unsigned const ie = musb_dcd_get_int_enable(rhport); @@ -801,10 +910,20 @@ void dcd_edpt_stall(uint8_t rhport, uint8_t ep_addr) { musb_ep_csr_t* ep_csr = get_ep_csr(musb_regs, epn); if (0 == epn) { - if (ep_addr == TU_EP0_OUT) { /* Ignore EP0 OUT */ - _dcd.pipe0.state = PIPE0_STATE_IDLE; - _dcd.pipe0.buf = NULL; - ep_csr->csr0l = MUSB_CSRL0_STALL; + if (ep_addr == TU_EP0_OUT) { /* Ignore EP0 IN */ + pipe0_state_t* pipe0 = &_dcd.pipe0; + pipe0->state = PIPE0_STATE_IDLE; + pipe0->buf = NULL; + if (pipe0->deferred_setup_valid) { + // A deferred SETUP means the stalled transfer already ended on the wire and the host's next + // request was ACKed — SendStall would hit that innocent request. Replay it instead of stalling. + pipe0_try_deferred_setup(rhport, ep_csr, false); + } else { + // Forcing EP0 to IDLE: any RXRDY parked by the aborted transfer's flow control is stale, + // clear it so the next SETUP IRQ is not gated off. + pipe0->rxrdy_consumed = false; + ep_csr->csr0l = MUSB_CSRL0_STALL; + } } } else { const tusb_dir_t ep_dir = tu_edpt_dir(ep_addr); @@ -856,7 +975,7 @@ void dcd_int_handler(uint8_t rhport) { dcd_event_bus_signal(rhport, DCD_EVENT_SOF, true); } if (intr_usb & MUSB_IS_RESET) { - process_bus_reset(rhport); + process_bus_reset_isr(rhport); } if (intr_usb & MUSB_IS_RESUME) { dcd_event_bus_signal(rhport, DCD_EVENT_RESUME, true); @@ -870,9 +989,9 @@ void dcd_int_handler(uint8_t rhport) { while (intr_tx) { const unsigned epnum = __builtin_ctz(intr_tx); if (epnum == 0) { - process_ep0(rhport); // EP0 has its own state machine (control transfers) + process_ep0_isr(rhport); // EP0 has its own state machine (control transfers) } else { - process_epin(rhport, musb_regs, epnum); + process_epin_isr(rhport, musb_regs, epnum); } intr_tx &= ~TU_BIT(epnum); @@ -887,7 +1006,7 @@ void dcd_int_handler(uint8_t rhport) { intr_rx &= musb_regs->intr_rxen; /* Clear disabled interrupts */ while (intr_rx) { unsigned const epnum = __builtin_ctz(intr_rx); - process_epout(rhport, musb_regs, epnum, true); + process_epout_isr(rhport, musb_regs, epnum, true); intr_rx &= ~TU_BIT(epnum); // Double packet endpoint: RxPktRdy is set and interrupt is generated immediately if 2nd packet is received diff --git a/src/portable/mentor/musb/musb_max32.h b/src/portable/mentor/musb/musb_max32.h index 599de2ca1..134b47122 100644 --- a/src/portable/mentor/musb/musb_max32.h +++ b/src/portable/mentor/musb/musb_max32.h @@ -47,7 +47,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 -const uintptr_t MUSB_BASES[] = { MXC_BASE_USBHS }; +static const uintptr_t MUSB_BASES[] = { MXC_BASE_USBHS }; #if CFG_TUD_ENABLED #define USBHS_M31_CLOCK_RECOVERY diff --git a/src/portable/mentor/musb/musb_ti.h b/src/portable/mentor/musb/musb_ti.h index 68e89d77d..deaea8017 100644 --- a/src/portable/mentor/musb/musb_ti.h +++ b/src/portable/mentor/musb/musb_ti.h @@ -49,7 +49,7 @@ #define MUSB_CFG_DYNAMIC_FIFO 1 #define MUSB_CFG_DYNAMIC_FIFO_SIZE 4096 -const uintptr_t MUSB_BASES[] = { USB0_BASE }; +static const uintptr_t MUSB_BASES[] = { USB0_BASE }; // Header supports both device and host modes. Only include what's necessary #if CFG_TUD_ENABLED diff --git a/src/portable/st/stm32_fsdev/dcd_stm32_fsdev.c b/src/portable/st/stm32_fsdev/dcd_stm32_fsdev.c index 41da3ddd0..6f7f490a8 100644 --- a/src/portable/st/stm32_fsdev/dcd_stm32_fsdev.c +++ b/src/portable/st/stm32_fsdev/dcd_stm32_fsdev.c @@ -41,6 +41,7 @@ * F302xB/C, F303xB/C, F373 512 byte buffer; no internal D+ pull-up * F302x6/8, F302xD/E2, F303xD/E 1024 byte buffer; no internal D+ pull-up * C0 2048 byte buffer; 32-bit bus; host mode + * C5 2048 byte buffer; 32-bit bus; host mode * G0 2048 byte buffer; 32-bit bus; host mode * G4 1024 byte buffer * H5 2048 byte buffer; 32-bit bus; host mode @@ -342,7 +343,7 @@ void dcd_int_handler(uint8_t rhport) { uint32_t int_status = FSDEV_REG->ISTR; /* Put SOF flag at the beginning of ISR in case to get least amount of jitter if it is used for timing purposes */ - if (int_status & U_ISTR_SOF) { + if ((int_status & U_ISTR_SOF) && (FSDEV_REG->CNTR & U_CNTR_SOFM)) { FSDEV_REG->ISTR = (fsdev_bus_t)~U_ISTR_SOF; dcd_event_sof(0, FSDEV_REG->FNR & U_FNR_FN, true); } diff --git a/src/portable/st/stm32_fsdev/fsdev_stm32.h b/src/portable/st/stm32_fsdev/fsdev_stm32.h index 070aa00ec..b15c95302 100644 --- a/src/portable/st/stm32_fsdev/fsdev_stm32.h +++ b/src/portable/st/stm32_fsdev/fsdev_stm32.h @@ -36,6 +36,10 @@ #include "stm32c0xx.h" #define FSDEV_HAS_SBUF_ISO 1 +#elif CFG_TUSB_MCU == OPT_MCU_STM32C5 + #include "stm32c5xx.h" + #define FSDEV_HAS_SBUF_ISO 1 + #elif CFG_TUSB_MCU == OPT_MCU_STM32F0 #include "stm32f0xx.h" #define FSDEV_HAS_SBUF_ISO 0 @@ -177,7 +181,7 @@ static const IRQn_Type fsdev_irq[] = { USB_IRQn, #elif TU_CHECK_MCU(OPT_MCU_STM32L5, OPT_MCU_STM32U3) USB_FS_IRQn, - #elif TU_CHECK_MCU(OPT_MCU_STM32C0, OPT_MCU_STM32H5, OPT_MCU_STM32U0) + #elif TU_CHECK_MCU(OPT_MCU_STM32C0, OPT_MCU_STM32C5, OPT_MCU_STM32H5, OPT_MCU_STM32U0) USB_DRD_FS_IRQn, #elif CFG_TUSB_MCU == OPT_MCU_STM32G0 #ifdef STM32G0B0xx @@ -262,30 +266,45 @@ TU_ATTR_ALWAYS_INLINE static inline void fsdev_int_disable(uint8_t rhport) { * * CTR may trigger before final PMA SRAM accesses complete on OUT transfers. * Insert delay before reading PMA count/data. - * Max CPU frequency in MHz, used to derive conservative FSDEV PMA delay defaults. + * Max CPU frequency in Hz, used to derive conservative FSDEV PMA delay defaults. */ #if CFG_TUSB_MCU == OPT_MCU_STM32H5 - #define FSDEV_STM32_CPU_MHZ 250U + #define FSDEV_STM32_CPU_HZ 250000000U #elif CFG_TUSB_MCU == OPT_MCU_STM32U5 - #define FSDEV_STM32_CPU_MHZ 160U + #define FSDEV_STM32_CPU_HZ 160000000U #elif CFG_TUSB_MCU == OPT_MCU_STM32U3 - #define FSDEV_STM32_CPU_MHZ 96U + #define FSDEV_STM32_CPU_HZ 96000000U #elif CFG_TUSB_MCU == OPT_MCU_STM32U0 - #define FSDEV_STM32_CPU_MHZ 56U + #define FSDEV_STM32_CPU_HZ 56000000U #elif CFG_TUSB_MCU == OPT_MCU_STM32G0 - #define FSDEV_STM32_CPU_MHZ 64U + #define FSDEV_STM32_CPU_HZ 64000000U #elif CFG_TUSB_MCU == OPT_MCU_STM32C0 - #define FSDEV_STM32_CPU_MHZ 48U + #define FSDEV_STM32_CPU_HZ 48000000U +#elif CFG_TUSB_MCU == OPT_MCU_STM32C5 + #define FSDEV_STM32_CPU_HZ 144000000U #endif +// 11 cycles / 800ns = ~13750000 cycles per second, used to derive conservative FSDEV PMA delay defaults #ifndef CFG_TUSB_FSDEV_BTABLE_FS_DELAY_COUNT - #define CFG_TUSB_FSDEV_BTABLE_FS_DELAY_COUNT (FSDEV_STM32_CPU_MHZ / 4U) + #define CFG_TUSB_FSDEV_BTABLE_FS_DELAY_COUNT (FSDEV_STM32_CPU_HZ / 13750000U) #endif +// 11 cycles / 6.4us = ~1718750 cycles per second, used to derive conservative FSDEV PMA delay defaults #ifndef CFG_TUSB_FSDEV_BTABLE_LS_DELAY_COUNT - #define CFG_TUSB_FSDEV_BTABLE_LS_DELAY_COUNT (FSDEV_STM32_CPU_MHZ * 2U) + #define CFG_TUSB_FSDEV_BTABLE_LS_DELAY_COUNT (FSDEV_STM32_CPU_HZ / 1718750U) #endif +/** + * LDR from SP-relative: 2 cycles + * SUBS: 1 cycle + * STR to SP-relative: 2 cycles + * LDR from SP-relative: 2 cycles + * CMP: 1 cycle + * BNE: + * taken: 3 cycles total (often shown as 1 + pipeline refill) + * not taken: 1 cycle + * Total cycles if delay is needed: 11 cycles + */ TU_ATTR_ALWAYS_INLINE static inline void fsdev_btable_workaround_delay(bool low_speed) { volatile uint32_t cycle_count = low_speed ? CFG_TUSB_FSDEV_BTABLE_LS_DELAY_COUNT : CFG_TUSB_FSDEV_BTABLE_FS_DELAY_COUNT; while (cycle_count > 0U) { diff --git a/src/portable/synopsys/dwc2/dcd_dwc2.c b/src/portable/synopsys/dwc2/dcd_dwc2.c index 1d0ef45d2..e1a2f6cf2 100644 --- a/src/portable/synopsys/dwc2/dcd_dwc2.c +++ b/src/portable/synopsys/dwc2/dcd_dwc2.c @@ -73,8 +73,15 @@ typedef struct { static dcd_data_t _dcd_data; +// DMA receives up to 3 back-to-back SETUP packets (3 x 8 bytes), Slave mode only needs 1 packet (8 bytes) +#if CFG_TUD_DWC2_DMA_ENABLE + #define DWC2_SETUP_BUFFER_SIZE 24 +#else + #define DWC2_SETUP_BUFFER_SIZE 8 +#endif + CFG_TUD_MEM_SECTION static struct { - TUD_EPBUF_DEF(setup_packet, 8); + TUD_EPBUF_DEF(setup_buffer, DWC2_SETUP_BUFFER_SIZE); } _dcd_usbbuf; static tud_configure_dwc2_t _tud_cfg = CFG_TUD_CONFIGURE_DWC2_DEFAULT; @@ -136,9 +143,9 @@ static void dma_setup_prepare(uint8_t rhport) { } } - // Receive only 1 packet - dwc2->epout[0].doeptsiz = (1 << DOEPTSIZ_STUPCNT_Pos) | (1 << DOEPTSIZ_PKTCNT_Pos) | (8 << DOEPTSIZ_XFRSIZ_Pos); - dwc2->epout[0].doepdma = (uintptr_t) _dcd_usbbuf.setup_packet; + // Receive back-to-back setup packets + dwc2->epout[0].doeptsiz = (3 << DOEPTSIZ_STUPCNT_Pos); + dwc2->epout[0].doepdma = (uintptr_t) _dcd_usbbuf.setup_buffer; dwc2->epout[0].doepctl |= DOEPCTL_EPENA | DOEPCTL_USBAEP; } @@ -793,13 +800,15 @@ static void handle_bus_reset(uint8_t rhport) { xfer_status[0][TUSB_DIR_OUT].max_size = CFG_TUD_ENDPOINT0_SIZE; xfer_status[0][TUSB_DIR_IN].max_size = CFG_TUD_ENDPOINT0_SIZE; + uint32_t gintmsk = GINTMSK_OTGINT | GINTMSK_IEPINT | GINTMSK_IISOIXFRM; if(dma_device_enabled(dwc2)) { + gintmsk |= GINTMSK_OEPINT; dma_setup_prepare(rhport); } else { dwc2->epout[0].doeptsiz |= (3 << DOEPTSIZ_STUPCNT_Pos); } - dwc2->gintmsk |= GINTMSK_OTGINT | GINTMSK_OEPINT | GINTMSK_IEPINT | GINTMSK_IISOIXFRM; + dwc2->gintmsk |= gintmsk; } static void handle_enum_done(uint8_t rhport) { @@ -883,31 +892,47 @@ static void handle_rxflvl_irq(uint8_t rhport) { dwc2_regs_t* dwc2 = DWC2_REG(rhport); const volatile uint32_t* rx_fifo = dwc2->fifo[0]; + // DWC2 v3.10a (e.g. STM32L476) emits an extra EP0 RX_COMPLETE that is NOT a real OUT data transfer completion, in two + // situations - each flagged by a DOEPINT bit set on that word: + // - DOEPINT.STPKTRX (Setup Packet Received): pushed between SETUP_RX and SETUP_DONE of every control transfer. + // - DOEPINT.STSPHSRX (Status Phase Received for control write): pushed after the OUT data stage when the host + // starts the IN status phase. + // Both are dropped in the RX_COMPLETE case below, clearing the flag (W1C) so a latched STSPHSRX + // does not block the core from emitting the next SETUP_DONE. usbd still processes the real OUT data + // and queues the IN status ZLP itself - the core does not auto-complete the control-write status. + const bool quirk_v310a = (dwc2->gsnpsid == DWC2_CORE_REV_3_10a); + // Pop control word off FIFO const dwc2_grxstsp_t grxstsp = {.value = dwc2->grxstsp}; + const uint8_t packet_status = grxstsp.packet_status; const uint8_t epnum = grxstsp.ep_ch_num; dwc2_dep_t* epout = &dwc2->epout[epnum]; - switch (grxstsp.packet_status) { + switch (packet_status) { case GRXSTS_PKTSTS_GLOBAL_OUT_NAK: // Global OUT NAK: do nothing break; case GRXSTS_PKTSTS_SETUP_RX: { // Setup packet received - uint32_t* setup = (uint32_t*)(uintptr_t) _dcd_usbbuf.setup_packet; + uint32_t * setup = (uint32_t*)(uintptr_t) _dcd_usbbuf.setup_buffer; // We can receive up to three setup packets in succession, but only the last one is valid. setup[0] = (*rx_fifo); setup[1] = (*rx_fifo); break; } - case GRXSTS_PKTSTS_SETUP_DONE: - // Setup packet done: - // After popping this out, dwc2 asserts a DOEPINT_SETUP interrupt which is handled by handle_epout_irq() + case GRXSTS_PKTSTS_SETUP_DONE: { + // Pop this word causes the Setup interrupt epout->doeptsiz |= (3 << DOEPTSIZ_STUPCNT_Pos); + epout->doepint = DOEPINT_SETUP | DOEPINT_STPKTRX; // Clear SETUP interrupt, required for core to re-write this control word + if (edpt_is_enabled(&dwc2->epin[0])) { + edpt_disable(rhport, 0x80, false); + } + dcd_event_setup_received(rhport, _dcd_usbbuf.setup_buffer, true); break; + } case GRXSTS_PKTSTS_RX_DATA: { // Out packet received @@ -935,41 +960,31 @@ static void handle_rxflvl_irq(uint8_t rhport) { break; } - case GRXSTS_PKTSTS_RX_COMPLETE: - // Out packet done - // After this entry is popped from the receive FIFO, dwc2 asserts a Transfer Completed interrupt on - // the specified OUT endpoint which will be handled by handle_epout_irq() - break; + case GRXSTS_PKTSTS_RX_COMPLETE: { + // Pop this word causes the xfer complete interrupt + const uint32_t doepint = epout->doepint; + epout->doepint = DOEPINT_XFRC; - default: break; // nothing to do - } -} - -static void handle_epout_slave(uint8_t rhport, uint8_t epnum, dwc2_doepint_t doepint_bm) { - if (doepint_bm.setup_phase_done) { - // Cleanup previous pending EP0 IN transfer if any - dwc2_dep_t* epin0 = &DWC2_REG(rhport)->epin[0]; - if (edpt_is_enabled(epin0)) { - edpt_disable(rhport, 0x80, false); - } - dcd_event_setup_received(rhport, _dcd_usbbuf.setup_packet, true); - return; - } + // v3.10a quirk (see top of function): the extra RX_COMPLETE flagged with Setup Packet Received (STPKTRX) or + // Status Phase Received for control write (STSPHSRX) is not a real OUT completion. Drop it + if (quirk_v310a) { + if (doepint & (DOEPINT_STPKTRX | DOEPINT_STSPHSRX)) { + epout->doepint = DOEPINT_STPKTRX | DOEPINT_STSPHSRX; + break; + } + } - // Normal OUT transfer complete - if (doepint_bm.xfer_complete) { - // only handle data skip if it is setup or status related - // Note: even though (xfer_complete + status_phase_rx) is for buffered DMA only, for STM32L47x (dwc2 v3.00a) they - // can is set when GRXSTS_PKTSTS_SETUP_RX is popped therefore they can bet set before/together with setup_phase_done - 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. - edpt_schedule_packets(rhport, epnum, TUSB_DIR_OUT); + if (epnum == 0 && _dcd_data.ep0_pending[TUSB_DIR_OUT] > 0) { + // EP0 can only handle one packet, schedule another packet to be received. + edpt_schedule_packets(rhport, 0, TUSB_DIR_OUT); } else { dcd_event_xfer_complete(rhport, epnum, xfer->total_len, XFER_RESULT_SUCCESS, true); } + break; } + + default: break; // nothing to do } } @@ -1008,13 +1023,23 @@ static void handle_epout_dma(uint8_t rhport, uint8_t epnum, dwc2_doepint_t doepi if (doepint_bm.setup_phase_done) { // Cleanup previous pending EP0 IN transfer if any - dwc2_dep_t* epin0 = &DWC2_REG(rhport)->epin[0]; + dwc2_dep_t* epin0 = &dwc2->epin[0]; + dwc2_dep_t* epout0 = &dwc2->epout[0]; if (edpt_is_enabled(epin0)) { edpt_disable(rhport, 0x80, false); } - dma_setup_prepare(rhport); - dcd_dcache_invalidate(_dcd_usbbuf.setup_packet, 8); - dcd_event_setup_received(rhport, _dcd_usbbuf.setup_packet, true); + + dcd_dcache_invalidate(_dcd_usbbuf.setup_buffer, sizeof(_dcd_usbbuf.setup_buffer)); + + // DOEPDMA0 has advanced past the last received SETUP packet; back up one packet to the latest valid one + // (Programming Guide v4.20a section 9.1.2.1: "DOEPDMAn-8 provides the pointer to the last valid SETUP data") + tusb_control_request_t *setup_packet = (tusb_control_request_t *) (uintptr_t) (epout0->doepdma - sizeof(tusb_control_request_t)); + dcd_event_setup_received(rhport, (uint8_t*)setup_packet, true); + + // Prepare EP0 for next setup if this setup has no data stage + if (setup_packet->wLength == 0) { + dma_setup_prepare(rhport); + } return; } @@ -1035,9 +1060,8 @@ static void handle_epout_dma(uint8_t rhport, uint8_t epnum, dwc2_doepint_t doepi const uint16_t remain = tsiz.xfer_size; xfer->total_len -= remain; - // this is ZLP, so prepare EP0 for next setup - // TODO use status phase rx - if(epnum == 0 && xfer->total_len == 0) { + // prepare EP0 for next setup + if(epnum == 0) { dma_setup_prepare(rhport); } @@ -1056,9 +1080,6 @@ static void handle_epin_dma(uint8_t rhport, uint8_t epnum, dwc2_diepint_t diepin // EP0 can only handle one packet. Schedule another packet to be transmitted. edpt_schedule_packets(rhport, epnum, TUSB_DIR_IN); } else { - if(epnum == 0) { - dma_setup_prepare(rhport); - } dcd_event_xfer_complete(rhport, epnum | TUSB_DIR_IN_MASK, xfer->total_len, XFER_RESULT_SUCCESS, true); } } @@ -1099,7 +1120,7 @@ static void handle_ep_irq(uint8_t rhport, uint8_t dir) { if (dir == TUSB_DIR_IN) { handle_epin_slave(rhport, epnum, intr.diepint_bm); } else { - handle_epout_slave(rhport, epnum, intr.doepint_bm); + // epout is handled in handle_rxflvl_irq } #endif } @@ -1207,7 +1228,7 @@ void dcd_int_handler(uint8_t rhport) { dwc2->gotgint = otg_int; } - if(gintsts & GINTSTS_SOF) { + if(gintsts & GINTSTS_SOF && dwc2->gintmsk & GINTMSK_SOFM) { dwc2->gintsts = GINTSTS_SOF; dwc2->gintmsk |= GINTMSK_USBSUSPM; const uint32_t frame = (dwc2->dsts & DSTS_FNSOF) >> DSTS_FNSOF_Pos; @@ -1220,6 +1241,12 @@ void dcd_int_handler(uint8_t rhport) { dcd_event_sof(rhport, frame, true); } + // IN endpoint interrupt handling. + if (gintsts & GINTSTS_IEPINT) { + // IEPINT bit read-only, clear using DIEPINTn + handle_ep_irq(rhport, TUSB_DIR_IN); + } + #if CFG_TUD_DWC2_SLAVE_ENABLE // RxFIFO non-empty interrupt handling. if (gintsts & GINTSTS_RXFLVL) { @@ -1234,17 +1261,13 @@ void dcd_int_handler(uint8_t rhport) { } #endif +#if CFG_TUD_DWC2_DMA_ENABLE // OUT endpoint interrupt handling. if (gintsts & GINTSTS_OEPINT) { // OEPINT is read-only, clear using DOEPINTn handle_ep_irq(rhport, TUSB_DIR_OUT); } - - // IN endpoint interrupt handling. - if (gintsts & GINTSTS_IEPINT) { - // IEPINT bit read-only, clear using DIEPINTn - handle_ep_irq(rhport, TUSB_DIR_IN); - } +#endif // Incomplete isochronous IN transfer interrupt handling. if (gintsts & GINTSTS_IISOIXFR) { diff --git a/src/portable/synopsys/dwc2/hcd_dwc2.c b/src/portable/synopsys/dwc2/hcd_dwc2.c index 9ea5f33c5..84a0c6afd 100644 --- a/src/portable/synopsys/dwc2/hcd_dwc2.c +++ b/src/portable/synopsys/dwc2/hcd_dwc2.c @@ -104,6 +104,7 @@ typedef struct { uint16_t xferred_bytes; // bytes that accumulate transferred though USB bus for the whole hcd_edpt_xfer(), which can // be composed of multiple channel_xfer_start() (retry with NAK/NYET) uint16_t fifo_bytes; // bytes written/read from/to FIFO (may not be transferred on USB bus). + uint8_t retry_disabled; // 1: channel was disabled to throttle a split retry (NAK in / XactErr out); re-arm on its halt } hcd_xfer_t; typedef struct { @@ -1137,7 +1138,16 @@ static bool handle_channel_in_dma(dwc2_regs_t* dwc2, uint8_t ch_id, uint32_t hci // TU_LOG1("in hcint = %02lX\r\n", hcint); if (hcint & HCINT_HALTED) { - if (hcint & (HCINT_XFER_COMPLETE | HCINT_STALL | HCINT_BABBLE_ERR)) { + if (xfer->retry_disabled) { + // Halt from our split-NAK throttle disable (below): re-arm the start-split, or let teardown finish + // if the endpoint is closing. Programming Guide 3.5 "Halting a Channel" (p73). + xfer->retry_disabled = 0; + if (xfer->closing) { + is_done = true; + } else { + channel_send_in_token(dwc2, channel); + } + } else if (hcint & (HCINT_XFER_COMPLETE | HCINT_STALL | HCINT_BABBLE_ERR)) { const uint16_t remain_bytes = (uint16_t) hctsiz.xfer_size; const uint16_t remain_packets = hctsiz.packet_count; const uint16_t actual_len = edpt->buflen - remain_bytes; @@ -1203,7 +1213,15 @@ static bool handle_channel_in_dma(dwc2_regs_t* dwc2, uint8_t ch_id, uint32_t hci channel->hcintmsk &= ~(HCINT_NAK | HCINT_DATATOGGLE_ERR); hcsplt.split_compl = 0; // restart with start-split channel->hcsplt = hcsplt.value; - channel_xfer_in_retry(dwc2, ch_id, hcint); + // Persistent split bulk/control IN NAK (e.g. idle polled endpoint): re-enabling immediately storms + // the ISR and starves the task. Disable + re-arm on the resulting halt to throttle (like the slave + // path); no frame deferral. Programming Guide 3.5 (p73) Note permits disable on NAK/FrmOvrn splits. + if ((hcint & HCINT_NAK) && hcsplt.split_en && !channel_is_periodic(channel->hcchar)) { + xfer->retry_disabled = 1; + channel_disable(dwc2, channel); + } else { + channel_xfer_in_retry(dwc2, ch_id, hcint); + } } else if (hcint & HCINT_FARME_OVERRUN) { // retry start-split in next binterval channel_xfer_in_retry(dwc2, ch_id, hcint); @@ -1228,7 +1246,16 @@ static bool handle_channel_out_dma(dwc2_regs_t* dwc2, uint8_t ch_id, uint32_t hc // TU_LOG1("out hcint = %02lX\r\n", hcint); if (hcint & HCINT_HALTED) { - if (hcint & (HCINT_XFER_COMPLETE | HCINT_STALL)) { + if (xfer->retry_disabled) { + // Halt from our split-XactErr throttle disable (below): re-issue the start-split (pointers already + // rewound), giving the hub TT a recovery gap. Programming Guide 3.5 "Halting a Channel" (p73). + xfer->retry_disabled = 0; + if (xfer->closing) { + is_done = true; + } else { + channel_xfer_start(dwc2, ch_id); + } + } else if (hcint & (HCINT_XFER_COMPLETE | HCINT_STALL)) { is_done = true; xfer->err_count = 0; if (hcint & HCINT_XFER_COMPLETE) { @@ -1251,9 +1278,17 @@ static bool handle_channel_out_dma(dwc2_regs_t* dwc2, uint8_t ch_id, uint32_t hc xfer->result = XFER_RESULT_FAILED; is_done = true; } else { - // clean up transfer so far and start again + // Rewind, then retry the start-split. Non-periodic SPLIT throttles via channel_disable + re-arm on + // the halt (immediate re-fire exhausts the retry budget; the disable gives the hub TT a recovery + // gap, like slave). Periodic split is excluded: channel_disable() is a no-op for it, so the halt + // never fires and the channel would wedge. Non-split re-inits immediately (Programming Guide 5.1.2.3). channel_xfer_out_wrapup(dwc2, ch_id); - channel_xfer_start(dwc2, ch_id); + if (hcsplt.split_en && !channel_is_periodic(channel->hcchar)) { + xfer->retry_disabled = 1; + channel_disable(dwc2, channel); + } else { + channel_xfer_start(dwc2, ch_id); + } } } } else if (hcint & HCINT_NYET) { @@ -1271,6 +1306,12 @@ static bool handle_channel_out_dma(dwc2_regs_t* dwc2, uint8_t ch_id, uint32_t hc channel->hcsplt = hcsplt.value; channel->hcchar |= HCCHAR_CHENA; } + } else if ((hcint & HCINT_NAK) && hcsplt.split_en) { + // Split OUT NAK: rewind + retry the start-split, else the channel stalls (Programming Guide 5.1.4.2). + // Non-split OUT NAK is core-handled (5.1.2.2), so this is split-only. + xfer->err_count = 0; + channel_xfer_out_wrapup(dwc2, ch_id); + channel_xfer_start(dwc2, ch_id); } if (xfer->closing == 1) { diff --git a/src/portable/wch/ch32_usbfs_reg.h b/src/portable/wch/ch32_usbfs_reg.h index 68be64f5e..7ffdc6cef 100644 --- a/src/portable/wch/ch32_usbfs_reg.h +++ b/src/portable/wch/ch32_usbfs_reg.h @@ -39,59 +39,92 @@ #include <ch32f20x.h> #elif CFG_TUSB_MCU == OPT_MCU_CH32V103 #include <ch32v10x.h> + // Newer-IP layout (separate UEPn_TX_CTRL/UEPn_RX_CTRL). The older IP (CH32V103) has a single + // combined control register at the UEPn_TX_CTRL offset, with UEPn_RX_CTRL reserved; the union + // exposes that same byte as UEPn_CTRL. Offsets are byte offsets from the peripheral base. + // TODO unify into a single struct shared by all WCH USBFS parts. typedef struct { - __IO uint8_t BASE_CTRL; - __IO uint8_t UDEV_CTRL; - __IO uint8_t INT_EN; - __IO uint8_t DEV_ADDR; - __IO uint8_t Reserve0; - __IO uint8_t MIS_ST; - __IO uint8_t INT_FG; - __IO uint8_t INT_ST; - __IO uint32_t RX_LEN; - __IO uint8_t UEP4_1_MOD; - __IO uint8_t UEP2_3_MOD; - __IO uint8_t UEP5_6_MOD; - __IO uint8_t UEP7_MOD; - __IO uint32_t UEP0_DMA; - __IO uint32_t UEP1_DMA; - __IO uint32_t UEP2_DMA; - __IO uint32_t UEP3_DMA; - __IO uint32_t UEP4_DMA; - __IO uint32_t UEP5_DMA; - __IO uint32_t UEP6_DMA; - __IO uint32_t UEP7_DMA; - __IO uint16_t UEP0_TX_LEN; - __IO uint8_t UEP0_TX_CTRL; - __IO uint8_t UEP0_RX_CTRL; - __IO uint16_t UEP1_TX_LEN; - __IO uint8_t UEP1_TX_CTRL; - __IO uint8_t UEP1_RX_CTRL; - __IO uint16_t UEP2_TX_LEN; - __IO uint8_t UEP2_TX_CTRL; - __IO uint8_t UEP2_RX_CTRL; - __IO uint16_t UEP3_TX_LEN; - __IO uint8_t UEP3_TX_CTRL; - __IO uint8_t UEP3_RX_CTRL; - __IO uint16_t UEP4_TX_LEN; - __IO uint8_t UEP4_TX_CTRL; - __IO uint8_t UEP4_RX_CTRL; - __IO uint16_t UEP5_TX_LEN; - __IO uint8_t UEP5_TX_CTRL; - __IO uint8_t UEP5_RX_CTRL; - __IO uint16_t UEP6_TX_LEN; - __IO uint8_t UEP6_TX_CTRL; - __IO uint8_t UEP6_RX_CTRL; - __IO uint16_t UEP7_TX_LEN; - __IO uint8_t UEP7_TX_CTRL; - __IO uint8_t UEP7_RX_CTRL; - __IO uint32_t Reserve1; - __IO uint32_t OTG_CR; - __IO uint32_t OTG_SR; + __IO uint8_t BASE_CTRL; // 0x00 + __IO uint8_t UDEV_CTRL; // 0x01 + __IO uint8_t INT_EN; // 0x02 + __IO uint8_t DEV_ADDR; // 0x03 + __IO uint8_t Reserve0; // 0x04 + __IO uint8_t MIS_ST; // 0x05 + __IO uint8_t INT_FG; // 0x06 + __IO uint8_t INT_ST; // 0x07 + __IO uint32_t RX_LEN; // 0x08 + __IO uint8_t UEP4_1_MOD; // 0x0C + __IO uint8_t UEP2_3_MOD; // 0x0D + __IO uint8_t UEP5_6_MOD; // 0x0E + __IO uint8_t UEP7_MOD; // 0x0F + __IO uint32_t UEP0_DMA; // 0x10 + __IO uint32_t UEP1_DMA; // 0x14 + __IO uint32_t UEP2_DMA; // 0x18 + __IO uint32_t UEP3_DMA; // 0x1C + __IO uint32_t UEP4_DMA; // 0x20 + __IO uint32_t UEP5_DMA; // 0x24 + __IO uint32_t UEP6_DMA; // 0x28 + __IO uint32_t UEP7_DMA; // 0x2C + __IO uint16_t UEP0_TX_LEN; // 0x30 + union { + __IO uint8_t UEP0_TX_CTRL; + __IO uint8_t UEP0_CTRL; + }; // 0x32 (TX_CTRL: IN | CTRL: combined) + __IO uint8_t UEP0_RX_CTRL; // 0x33 (OUT ctrl; reserved on combined IP) + __IO uint16_t UEP1_TX_LEN; // 0x34 + union { + __IO uint8_t UEP1_TX_CTRL; + __IO uint8_t UEP1_CTRL; + }; // 0x36 + __IO uint8_t UEP1_RX_CTRL; // 0x37 + __IO uint16_t UEP2_TX_LEN; // 0x38 + union { + __IO uint8_t UEP2_TX_CTRL; + __IO uint8_t UEP2_CTRL; + }; // 0x3A + __IO uint8_t UEP2_RX_CTRL; // 0x3B + __IO uint16_t UEP3_TX_LEN; // 0x3C + union { + __IO uint8_t UEP3_TX_CTRL; + __IO uint8_t UEP3_CTRL; + }; // 0x3E + __IO uint8_t UEP3_RX_CTRL; // 0x3F + __IO uint16_t UEP4_TX_LEN; // 0x40 + union { + __IO uint8_t UEP4_TX_CTRL; + __IO uint8_t UEP4_CTRL; + }; // 0x42 + __IO uint8_t UEP4_RX_CTRL; // 0x43 + __IO uint16_t UEP5_TX_LEN; // 0x44 + union { + __IO uint8_t UEP5_TX_CTRL; + __IO uint8_t UEP5_CTRL; + }; // 0x46 + __IO uint8_t UEP5_RX_CTRL; // 0x47 + __IO uint16_t UEP6_TX_LEN; // 0x48 + union { + __IO uint8_t UEP6_TX_CTRL; + __IO uint8_t UEP6_CTRL; + }; // 0x4A + __IO uint8_t UEP6_RX_CTRL; // 0x4B + __IO uint16_t UEP7_TX_LEN; // 0x4C + union { + __IO uint8_t UEP7_TX_CTRL; + __IO uint8_t UEP7_CTRL; + }; // 0x4E + __IO uint8_t UEP7_RX_CTRL; // 0x4F + __IO uint32_t Reserve1; // 0x50 + __IO uint32_t OTG_CR; // 0x54 + __IO uint32_t OTG_SR; // 0x58 } USBOTG_FS_TypeDef; #define USBOTG_FS ((USBOTG_FS_TypeDef *) 0x40023400) + + // CH32V103 has the older USBFS IP: a single combined control register per endpoint + // (UEPn_CTRL) instead of separate TX_CTRL/RX_CTRL bytes. The struct's UEPn_TX_CTRL field + // aliases that combined register (same address); UEPn_RX_CTRL maps to unused padding. + #define CH32_USBFS_EP_CTRL_COMBINED 1 #elif CFG_TUSB_MCU == OPT_MCU_CH32V20X #include <ch32v20x.h> #elif CFG_TUSB_MCU == OPT_MCU_CH32V307 @@ -166,6 +199,17 @@ #define USBFS_EP_R_RES_NAK (2 << 0) #define USBFS_EP_R_RES_STALL (3 << 0) +#ifdef CH32_USBFS_EP_CTRL_COMBINED +// Combined per-endpoint control register (older IP, e.g. CH32V103): IN response in +// bits [1:0], OUT response in bits [3:2], shared auto-toggle, separate IN/OUT toggle. +#define USBFS_EPC_T_RES_MASK 0x03 +#define USBFS_EPC_R_RES_MASK 0x0C +#define USBFS_EPC_R_RES_SHIFT 2 +#define USBFS_EPC_AUTO_TOG 0x10 +#define USBFS_EPC_T_TOG 0x40 +#define USBFS_EPC_R_TOG 0x80 +#endif + // token PID #define PID_OUT 0 #define PID_SOF 1 diff --git a/src/portable/wch/dcd_ch32_usbfs.c b/src/portable/wch/dcd_ch32_usbfs.c index af0f17785..dae31da91 100644 --- a/src/portable/wch/dcd_ch32_usbfs.c +++ b/src/portable/wch/dcd_ch32_usbfs.c @@ -40,6 +40,52 @@ #define EP_TX_CTRL(ep) ((&USBOTG_FS->UEP0_TX_CTRL)[4 * ep]) #define EP_RX_CTRL(ep) ((&USBOTG_FS->UEP0_RX_CTRL)[4 * ep]) +// Endpoint control register access. The newer USBFS IP (CH32V20x/V307/X035) has separate +// TX_CTRL and RX_CTRL bytes per endpoint; the older IP (CH32V103) has a single combined +// UEPn_CTRL register. These helpers hide the difference so the rest of the driver is shared. +// Values use the newer-IP encoding (USBFS_EP_T_*/USBFS_EP_R_*); the combined path remaps them. +#ifdef CH32_USBFS_EP_CTRL_COMBINED + #define EP_CTRL(ep) EP_TX_CTRL(ep) // UEPn_TX_CTRL field aliases the combined UEPn_CTRL register + + static inline uint8_t ep_tx_to_comb(uint8_t v) { + uint8_t c = v & USBFS_EP_T_RES_MASK; // IN response: bits [1:0] in both encodings + if (v & USBFS_EP_T_TOG) { c |= USBFS_EPC_T_TOG; } + if (v & USBFS_EP_T_AUTO_TOG) { c |= USBFS_EPC_AUTO_TOG; } + return c; + } + static inline uint8_t ep_rx_to_comb(uint8_t v) { + uint8_t c = (uint8_t) ((v & USBFS_EP_R_RES_MASK) << USBFS_EPC_R_RES_SHIFT); // OUT response -> bits [3:2] + if (v & USBFS_EP_R_TOG) { c |= USBFS_EPC_R_TOG; } + if (v & USBFS_EP_R_AUTO_TOG) { c |= USBFS_EPC_AUTO_TOG; } + return c; + } + // Set IN side (response/toggle/auto-tog), preserving the OUT response + OUT toggle. + static inline void ep_tx_ctrl_set(uint8_t ep, uint8_t v) { + EP_CTRL(ep) = (uint8_t) ((EP_CTRL(ep) & (USBFS_EPC_R_RES_MASK | USBFS_EPC_R_TOG)) | ep_tx_to_comb(v)); + } + // Set OUT side, preserving the IN response + IN toggle. + static inline void ep_rx_ctrl_set(uint8_t ep, uint8_t v) { + EP_CTRL(ep) = (uint8_t) ((EP_CTRL(ep) & (USBFS_EPC_T_RES_MASK | USBFS_EPC_T_TOG)) | ep_rx_to_comb(v)); + } + static inline void ep_tx_set_response(uint8_t ep, uint8_t res) { + EP_CTRL(ep) = (uint8_t) ((EP_CTRL(ep) & ~USBFS_EPC_T_RES_MASK) | (res & USBFS_EP_T_RES_MASK)); + } + static inline void ep_rx_set_response(uint8_t ep, uint8_t res) { + EP_CTRL(ep) = (uint8_t) ((EP_CTRL(ep) & ~USBFS_EPC_R_RES_MASK) | ((res & USBFS_EP_R_RES_MASK) << USBFS_EPC_R_RES_SHIFT)); + } + #define EP0_SETUP_RX_TOG USBFS_EP_R_TOG // combined IP: data/status stage after SETUP is DATA1 +#else + static inline void ep_tx_ctrl_set(uint8_t ep, uint8_t v) { EP_TX_CTRL(ep) = v; } + static inline void ep_rx_ctrl_set(uint8_t ep, uint8_t v) { EP_RX_CTRL(ep) = v; } + static inline void ep_tx_set_response(uint8_t ep, uint8_t res) { + EP_TX_CTRL(ep) = (uint8_t) ((EP_TX_CTRL(ep) & ~USBFS_EP_T_RES_MASK) | res); + } + static inline void ep_rx_set_response(uint8_t ep, uint8_t res) { + EP_RX_CTRL(ep) = (uint8_t) ((EP_RX_CTRL(ep) & ~USBFS_EP_R_RES_MASK) | res); + } + #define EP0_SETUP_RX_TOG 0 +#endif + /* private data */ struct usb_xfer { bool valid; @@ -81,19 +127,19 @@ static void update_in(uint8_t rhport, uint8_t ep, bool force) { EP_TX_LEN(ep) = len; if (ep == 0) { - EP_TX_CTRL(0) = USBFS_EP_T_RES_ACK | (data.ep0_tog ? USBFS_EP_T_TOG : 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]) { - EP_TX_CTRL(ep) = (EP_TX_CTRL(ep) & ~(USBFS_EP_T_RES_MASK)) | USBFS_EP_T_RES_NYET; + ep_tx_set_response(ep, USBFS_EP_T_RES_NYET); } else { - EP_TX_CTRL(ep) = (EP_TX_CTRL(ep) & ~(USBFS_EP_T_RES_MASK)) | USBFS_EP_T_RES_ACK; + ep_tx_set_response(ep, USBFS_EP_T_RES_ACK); } } else { xfer->valid = false; if (ep == 0) { - EP_TX_CTRL(0) = USBFS_EP_T_RES_NAK | (data.ep0_tog ? USBFS_EP_T_TOG : 0); + ep_tx_ctrl_set(0, USBFS_EP_T_RES_NAK | (data.ep0_tog ? USBFS_EP_T_TOG : 0)); } else if (!data.isochronous[ep]) { - EP_TX_CTRL(ep) = (EP_TX_CTRL(ep) & ~(USBFS_EP_T_RES_MASK)) | USBFS_EP_T_RES_NAK; + 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); } @@ -119,11 +165,11 @@ static void update_out(uint8_t rhport, uint8_t ep, size_t rx_len) { } if (ep == 0) { - EP_RX_CTRL(0) = USBFS_EP_R_RES_NAK; + 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); - EP_RX_CTRL(ep) = (EP_RX_CTRL(ep) & ~USBFS_EP_R_RES_MASK) | rx_res; + ep_rx_set_response(ep, rx_res); } } } @@ -132,8 +178,8 @@ static void reset_ep_ctrls(void) { for (uint8_t ep = 1; ep < EP_MAX; ep++) { EP_DMA(ep) = (uint32_t)&data.buffer[ep][0]; EP_TX_LEN(ep) = 0; - EP_TX_CTRL(ep) = USBFS_EP_T_AUTO_TOG | USBFS_EP_T_RES_NYET; - EP_RX_CTRL(ep) = USBFS_EP_R_AUTO_TOG | USBFS_EP_R_RES_NYET; + ep_tx_ctrl_set(ep, USBFS_EP_T_AUTO_TOG | USBFS_EP_T_RES_NYET); + ep_rx_ctrl_set(ep, USBFS_EP_R_AUTO_TOG | USBFS_EP_R_RES_NYET); } EP_DMA(3) = (uint32_t)&data.ep3_buffer.out[0]; } @@ -152,8 +198,8 @@ bool dcd_init(uint8_t rhport, const tusb_rhport_init_t *rh_init) { // setup endpoint 0 EP_DMA(0) = (uint32_t)&data.buffer[0][0]; EP_TX_LEN(0) = 0; - EP_TX_CTRL(0) = USBFS_EP_T_RES_NAK; - EP_RX_CTRL(0) = USBFS_EP_R_RES_ACK; + ep_tx_ctrl_set(0, USBFS_EP_T_RES_NAK); + ep_rx_ctrl_set(0, USBFS_EP_R_RES_ACK); // enable other endpoints but NAK everything USBOTG_FS->UEP4_1_MOD = 0xCC; @@ -188,11 +234,12 @@ void dcd_int_handler(uint8_t rhport) { case PID_SETUP: // setup clears stall - EP_TX_CTRL(0) = USBFS_EP_T_RES_NAK; + ep_tx_ctrl_set(0, USBFS_EP_T_RES_NAK); data.ep0_tog = true; const tusb_control_request_t *setup = (const tusb_control_request_t *)&data.buffer[0][TUSB_DIR_OUT][0]; - EP_RX_CTRL(0) = (setup->wLength == 0) ? USBFS_EP_R_RES_ACK : USBFS_EP_R_RES_NAK; + // EP0_SETUP_RX_TOG arms the data/status stage at DATA1 on the combined-control IP + ep_rx_ctrl_set(0, ((setup->wLength == 0) ? USBFS_EP_R_RES_ACK : USBFS_EP_R_RES_NAK) | EP0_SETUP_RX_TOG); dcd_event_setup_received(rhport, &data.buffer[0][TUSB_DIR_OUT][0], true); break; @@ -210,7 +257,7 @@ void dcd_int_handler(uint8_t rhport) { true); USBOTG_FS->DEV_ADDR = 0x00; - EP_RX_CTRL(0) = USBFS_EP_R_RES_ACK; + ep_rx_ctrl_set(0, USBFS_EP_R_RES_ACK); reset_ep_ctrls(); @@ -277,9 +324,9 @@ bool dcd_edpt_open(uint8_t rhport, const tusb_desc_endpoint_t *desc_ep) { if (ep != 0) { if (dir == TUSB_DIR_OUT) { - EP_RX_CTRL(ep) = USBFS_EP_R_AUTO_TOG | USBFS_EP_T_RES_NAK; + ep_rx_ctrl_set(ep, USBFS_EP_R_AUTO_TOG | USBFS_EP_R_RES_NAK); } else { - EP_TX_CTRL(ep) = USBFS_EP_T_AUTO_TOG | USBFS_EP_T_RES_NAK; + ep_tx_ctrl_set(ep, USBFS_EP_T_AUTO_TOG | USBFS_EP_T_RES_NAK); } } return true; @@ -326,7 +373,7 @@ bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t *buffer, uint16_t to update_in(rhport, ep, true); } else { uint8_t rx_res = data.isochronous[ep] ? USBFS_EP_R_RES_NYET : USBFS_EP_R_RES_ACK; - EP_RX_CTRL(ep) = (EP_RX_CTRL(ep) & ~USBFS_EP_R_RES_MASK) | rx_res; + ep_rx_set_response(ep, rx_res); } return true; } @@ -337,16 +384,16 @@ void dcd_edpt_stall(uint8_t rhport, uint8_t ep_addr) { uint8_t dir = tu_edpt_dir(ep_addr); if (ep == 0) { if (dir == TUSB_DIR_OUT) { - EP_RX_CTRL(0) = USBFS_EP_R_RES_STALL; + ep_rx_ctrl_set(0, USBFS_EP_R_RES_STALL); } else { EP_TX_LEN(0) = 0; - EP_TX_CTRL(0) = USBFS_EP_T_RES_STALL; + ep_tx_ctrl_set(0, USBFS_EP_T_RES_STALL); } } else { if (dir == TUSB_DIR_OUT) { - EP_RX_CTRL(ep) = (EP_RX_CTRL(ep) & ~USBFS_EP_R_RES_MASK) | USBFS_EP_R_RES_STALL; + ep_rx_set_response(ep, USBFS_EP_R_RES_STALL); } else { - EP_TX_CTRL(ep) = (EP_TX_CTRL(ep) & ~USBFS_EP_T_RES_MASK) | USBFS_EP_T_RES_STALL; + ep_tx_set_response(ep, USBFS_EP_T_RES_STALL); } } } @@ -357,13 +404,13 @@ void dcd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr) { uint8_t dir = tu_edpt_dir(ep_addr); if (ep == 0) { if (dir == TUSB_DIR_OUT) { - EP_RX_CTRL(0) = USBFS_EP_R_RES_ACK; + ep_rx_ctrl_set(0, USBFS_EP_R_RES_ACK); } } else { if (dir == TUSB_DIR_OUT) { - EP_RX_CTRL(ep) = USBFS_EP_R_AUTO_TOG | USBFS_EP_R_RES_NAK; + ep_rx_ctrl_set(ep, USBFS_EP_R_AUTO_TOG | USBFS_EP_R_RES_NAK); } else { - EP_TX_CTRL(ep) = USBFS_EP_T_AUTO_TOG | USBFS_EP_T_RES_NAK; + ep_tx_ctrl_set(ep, USBFS_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 a6dd5bb79..ea3b052ad 100644 --- a/src/portable/wch/dcd_ch32_usbhs.c +++ b/src/portable/wch/dcd_ch32_usbhs.c @@ -354,7 +354,7 @@ void dcd_edpt_stall(uint8_t rhport, uint8_t ep_addr) { if (dir == TUSB_DIR_OUT) { EP_RX_CTRL(ep_num) = USBHS_EP_R_RES_STALL; } else { - EP_TX_LEN(0) = 0; + EP_TX_LEN(ep_num) = 0; EP_TX_CTRL(ep_num) = USBHS_EP_T_RES_STALL; } } diff --git a/src/tusb.c b/src/tusb.c index 5d656fb8c..634cbc10b 100644 --- a/src/tusb.c +++ b/src/tusb.c @@ -497,7 +497,7 @@ char const* const tu_str_std_request[] = { }; char const* const tu_str_xfer_result[] = { - "OK", "FAILED", "STALLED", "TIMEOUT" + "OK", "FAILED", "STALLED", "TIMEOUT", "ABORTED", "INVALID" }; #endif diff --git a/src/tusb_option.h b/src/tusb_option.h index 74eb8cc06..415e083c9 100644 --- a/src/tusb_option.h +++ b/src/tusb_option.h @@ -98,6 +98,7 @@ #define OPT_MCU_STM32N6 319 ///< ST N6 #define OPT_MCU_STM32WBA 320 ///< ST WBA #define OPT_MCU_STM32U3 321 ///< ST U3 +#define OPT_MCU_STM32C5 322 ///< ST C5 // Sony #define OPT_MCU_CXD56 400 ///< SONY CXD56 @@ -535,6 +536,18 @@ #define CFG_TUSB_OS OPT_OS_NONE #endif +// 1 when CFG_TUSB_OS provides a preemptive scheduler with distinct tasks +// (FreeRTOS, Zephyr, ThreadX, etc.); 0 when the application is single-context +// (bare-metal OS_NONE or Pico SDK). Sync host control xfers from the host +// task are forbidden when this is 1. +#ifndef CFG_TUSB_OS_HAS_SCHEDULER + #if CFG_TUSB_OS == OPT_OS_NONE || CFG_TUSB_OS == OPT_OS_PICO + #define CFG_TUSB_OS_HAS_SCHEDULER 0 + #else + #define CFG_TUSB_OS_HAS_SCHEDULER 1 + #endif +#endif + #ifndef CFG_TUSB_OS_INC_PATH #ifndef CFG_TUSB_OS_INC_PATH_DEFAULT #define CFG_TUSB_OS_INC_PATH_DEFAULT |
