summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorhathach <[email protected]>2026-06-11 17:20:15 +0700
committerhathach <[email protected]>2026-06-11 17:20:15 +0700
commit5145b67f7946763ca04954f5494bfa88eec2acad (patch)
tree9130540204c2404d532613adab01e88f4e918daa /src
parentf52d9c7b27dc0234fb4738762a8a6da01817e2bf (diff)
parent0244a4f12e019406a4b73c75cd96f8efa096bbdc (diff)
Merge remote-tracking branch 'origin/master' into stm32c5
# Conflicts: # README.rst
Diffstat (limited to 'src')
-rw-r--r--src/class/cdc/cdc_host.c92
-rw-r--r--src/class/mtp/mtp_device.c13
-rw-r--r--src/common/tusb_types.h1
-rw-r--r--src/host/usbh.c334
-rw-r--r--src/osal/osal.h39
-rw-r--r--src/osal/osal_freertos.h13
-rw-r--r--src/osal/osal_mynewt.h6
-rw-r--r--src/osal/osal_none.h16
-rw-r--r--src/osal/osal_pico.h7
-rw-r--r--src/osal/osal_rtthread.h6
-rw-r--r--src/osal/osal_rtx4.h6
-rw-r--r--src/osal/osal_threadx.h5
-rw-r--r--src/osal/osal_zephyr.h6
-rw-r--r--src/portable/synopsys/dwc2/dcd_dwc2.c135
-rw-r--r--src/portable/synopsys/dwc2/hcd_dwc2.c51
-rw-r--r--src/portable/wch/dcd_ch32_usbhs.c2
-rw-r--r--src/tusb.c2
-rw-r--r--src/tusb_option.h12
18 files changed, 538 insertions, 208 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_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/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/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/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 70549816f..415e083c9 100644
--- a/src/tusb_option.h
+++ b/src/tusb_option.h
@@ -536,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