summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/CMakeLists.txt3
-rw-r--r--src/class/audio/audio.h32
-rw-r--r--src/class/cdc/cdc_host.c92
-rw-r--r--src/class/hid/hid_host.c33
-rw-r--r--src/class/hid/hid_host.h2
-rw-r--r--src/class/midi/midi.h18
-rw-r--r--src/class/midi/midi2_device.c769
-rw-r--r--src/class/midi/midi2_device.h193
-rw-r--r--src/class/midi/midi2_host.c635
-rw-r--r--src/class/midi/midi2_host.h107
-rw-r--r--src/class/midi/midi_host.h7
-rw-r--r--src/class/mtp/mtp_device.c15
-rw-r--r--src/class/net/ecm_rndis_device.c22
-rw-r--r--src/class/net/ncm.h14
-rw-r--r--src/class/net/ncm_device.c117
-rw-r--r--src/class/net/net_device.h20
-rw-r--r--src/class/printer/printer_device.c1
-rw-r--r--src/class/video/video_device.c2
-rw-r--r--src/common/tusb_compiler.h9
-rw-r--r--src/common/tusb_fifo.c8
-rw-r--r--src/common/tusb_mcu.h74
-rw-r--r--src/common/tusb_private.h19
-rw-r--r--src/common/tusb_types.h34
-rw-r--r--src/device/dcd.h5
-rw-r--r--src/device/usbd.c519
-rw-r--r--src/device/usbd.h53
-rw-r--r--src/device/usbd_control.c217
-rw-r--r--src/device/usbd_pvt.h4
-rw-r--r--src/host/usbh.c369
-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/chipidea/ci_fs/dcd_ci_fs.c14
-rw-r--r--src/portable/dialog/da146xx/dcd_da146xx.c42
-rw-r--r--src/portable/mentor/musb/dcd_musb.c801
-rw-r--r--src/portable/mentor/musb/musb_max32.h2
-rw-r--r--src/portable/mentor/musb/musb_ti.h2
-rw-r--r--src/portable/mentor/musb/musb_type.h14
-rw-r--r--src/portable/microchip/samg/dcd_samg.c4
-rw-r--r--src/portable/nordic/nrf5x/dcd_nrf5x.c10
-rw-r--r--src/portable/nuvoton/nuc120/dcd_nuc120.c8
-rw-r--r--src/portable/nuvoton/nuc121/dcd_nuc121.c8
-rw-r--r--src/portable/nuvoton/nuc505/dcd_nuc505.c6
-rw-r--r--src/portable/nxp/lpc17_40/dcd_lpc17_40.c4
-rw-r--r--src/portable/nxp/lpc_ip3511/dcd_lpc_ip3511.c10
-rw-r--r--src/portable/raspberrypi/rp2040/hcd_rp2040.c12
-rw-r--r--src/portable/renesas/rusb2/dcd_rusb2.c34
-rw-r--r--src/portable/st/stm32_fsdev/dcd_stm32_fsdev.c43
-rw-r--r--src/portable/st/stm32_fsdev/fsdev_common.c4
-rw-r--r--src/portable/st/stm32_fsdev/fsdev_common.h4
-rw-r--r--src/portable/st/stm32_fsdev/fsdev_stm32.h86
-rw-r--r--src/portable/st/stm32_fsdev/hcd_stm32_fsdev.c50
-rw-r--r--src/portable/synopsys/dwc2/dcd_dwc2.c154
-rw-r--r--src/portable/synopsys/dwc2/dwc2_esp32.h12
-rw-r--r--src/portable/synopsys/dwc2/hcd_dwc2.c56
-rw-r--r--src/portable/wch/ch32_usbfs_reg.h211
-rw-r--r--src/portable/wch/dcd_ch32_usbfs.c457
-rw-r--r--src/portable/wch/dcd_ch32_usbhs.c434
-rw-r--r--src/tinyusb.mk3
-rw-r--r--src/tusb.c17
-rw-r--r--src/tusb.h8
-rw-r--r--src/tusb_option.h36
68 files changed, 4441 insertions, 1602 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 00f466007..b3e05f60f 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -8,13 +8,13 @@ function(tinyusb_sources_get OUTPUT_VAR)
${CMAKE_CURRENT_FUNCTION_LIST_DIR}/common/tusb_fifo.c
# device
${CMAKE_CURRENT_FUNCTION_LIST_DIR}/device/usbd.c
- ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/device/usbd_control.c
${CMAKE_CURRENT_FUNCTION_LIST_DIR}/class/audio/audio_device.c
${CMAKE_CURRENT_FUNCTION_LIST_DIR}/class/cdc/cdc_device.c
${CMAKE_CURRENT_FUNCTION_LIST_DIR}/class/dfu/dfu_device.c
${CMAKE_CURRENT_FUNCTION_LIST_DIR}/class/dfu/dfu_rt_device.c
${CMAKE_CURRENT_FUNCTION_LIST_DIR}/class/hid/hid_device.c
${CMAKE_CURRENT_FUNCTION_LIST_DIR}/class/midi/midi_device.c
+ ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/class/midi/midi2_device.c
${CMAKE_CURRENT_FUNCTION_LIST_DIR}/class/msc/msc_device.c
${CMAKE_CURRENT_FUNCTION_LIST_DIR}/class/mtp/mtp_device.c
${CMAKE_CURRENT_FUNCTION_LIST_DIR}/class/net/ecm_rndis_device.c
@@ -29,6 +29,7 @@ function(tinyusb_sources_get OUTPUT_VAR)
${CMAKE_CURRENT_FUNCTION_LIST_DIR}/class/cdc/cdc_host.c
${CMAKE_CURRENT_FUNCTION_LIST_DIR}/class/hid/hid_host.c
${CMAKE_CURRENT_FUNCTION_LIST_DIR}/class/midi/midi_host.c
+ ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/class/midi/midi2_host.c
${CMAKE_CURRENT_FUNCTION_LIST_DIR}/class/msc/msc_host.c
${CMAKE_CURRENT_FUNCTION_LIST_DIR}/class/vendor/vendor_host.c
# typec
diff --git a/src/class/audio/audio.h b/src/class/audio/audio.h
index cff38cc22..428391bb2 100644
--- a/src/class/audio/audio.h
+++ b/src/class/audio/audio.h
@@ -490,10 +490,19 @@ typedef struct TU_ATTR_PACKED {
uint8_t bDescriptorType; ///< Descriptor Type. Value: TUSB_DESC_ENDPOINT.
uint8_t bEndpointAddress;///< The address of the endpoint on the USB device described by this descriptor.
struct TU_ATTR_PACKED {
+#if (TU_BITFIELD_ORDER == TU_BITFIELD_LE)
uint8_t xfer : 2; // Control, ISO, Bulk, Interrupt
uint8_t sync : 2; // None, Asynchronous, Adaptive, Synchronous
uint8_t usage : 2; // Data, Feedback, Implicit feedback
uint8_t : 2;
+#elif (TU_BITFIELD_ORDER == TU_BITFIELD_BE)
+ uint8_t : 2;
+ uint8_t usage : 2; // Data, Feedback, Implicit feedback
+ uint8_t sync : 2; // None, Asynchronous, Adaptive, Synchronous
+ uint8_t xfer : 2; // Control, ISO, Bulk, Interrupt
+#else
+ #error "Please define TU_BITFIELD_ORDER as TU_BITFIELD_LE or TU_BITFIELD_BE"
+#endif
} bmAttributes;
uint16_t wMaxPacketSize; ///< Maximum packet size this endpoint is capable of sending or receiving when this configuration is selected.
uint8_t bInterval; ///< Interval for polling endpoint for data transfers.
@@ -1177,29 +1186,6 @@ typedef struct TU_ATTR_PACKED {
uint16_t wLockDelay; ///< Indicates the time it takes this endpoint to reliably lock its internal clock recovery circuitry. Units used depend on the value of the bLockDelayUnits field.
} audio20_desc_cs_as_iso_data_ep_t;
-// 5.2.2 Control Request Layout
-typedef struct TU_ATTR_PACKED {
- union {
- struct TU_ATTR_PACKED {
- uint8_t recipient : 5;///< Recipient type tusb_request_recipient_t.
- uint8_t type : 2; ///< Request type tusb_request_type_t.
- uint8_t direction : 1;///< Direction type. tusb_dir_t
- } bmRequestType_bit;
-
- uint8_t bmRequestType;
- };
-
- uint8_t bRequest;///< Request type audio_cs_req_t
- uint8_t bChannelNumber;
- uint8_t bControlSelector;
- union {
- uint8_t bInterface;
- uint8_t bEndpoint;
- };
- uint8_t bEntityID;
- uint16_t wLength;
-} audio20_control_request_t;
-
//// 5.2.3 Control Request Parameter Block Layout
// 5.2.3.1 1-byte Control CUR Parameter Block
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/hid/hid_host.c b/src/class/hid/hid_host.c
index 7935b84d3..fc7704258 100644
--- a/src/class/hid/hid_host.c
+++ b/src/class/hid/hid_host.c
@@ -74,44 +74,31 @@ static uint8_t _hidh_default_protocol = HID_PROTOCOL_BOOT;
// Weak stubs: invoked if no strong implementation is available
//--------------------------------------------------------------------+
TU_ATTR_WEAK void tuh_hid_mount_cb(uint8_t dev_addr, uint8_t idx, uint8_t const* report_desc, uint16_t desc_len) {
- (void) dev_addr;
- (void) idx;
- (void) report_desc;
- (void) desc_len;
+ (void) dev_addr; (void) idx; (void) report_desc; (void) desc_len;
}
TU_ATTR_WEAK void tuh_hid_umount_cb(uint8_t dev_addr, uint8_t idx) {
- (void) dev_addr;
- (void) idx;
+ (void) dev_addr; (void) idx;
+}
+
+TU_ATTR_WEAK void tuh_hid_report_received_cb(uint8_t dev_addr, uint8_t idx, const uint8_t *report, uint16_t len) {
+ (void) dev_addr; (void) idx; (void) report; (void) len;
}
TU_ATTR_WEAK void tuh_hid_report_sent_cb(uint8_t dev_addr, uint8_t idx, uint8_t const* report, uint16_t len) {
- (void) dev_addr;
- (void) idx;
- (void) report;
- (void) len;
+ (void) dev_addr; (void) idx; (void) report; (void) len;
}
TU_ATTR_WEAK void tuh_hid_get_report_complete_cb(uint8_t dev_addr, uint8_t idx, uint8_t report_id, uint8_t report_type, uint16_t len) {
- (void) dev_addr;
- (void) idx;
- (void) report_id;
- (void) report_type;
- (void) len;
+ (void) dev_addr; (void) idx; (void) report_id; (void) report_type; (void) len;
}
TU_ATTR_WEAK void tuh_hid_set_report_complete_cb(uint8_t dev_addr, uint8_t idx, uint8_t report_id, uint8_t report_type, uint16_t len) {
- (void) dev_addr;
- (void) idx;
- (void) report_id;
- (void) report_type;
- (void) len;
+ (void) dev_addr; (void) idx; (void) report_id; (void) report_type; (void) len;
}
TU_ATTR_WEAK void tuh_hid_set_protocol_complete_cb(uint8_t dev_addr, uint8_t idx, uint8_t protocol) {
- (void) dev_addr;
- (void) idx;
- (void) protocol;
+ (void) dev_addr; (void) idx; (void) protocol;
}
//--------------------------------------------------------------------+
diff --git a/src/class/hid/hid_host.h b/src/class/hid/hid_host.h
index 922848fc2..95ba859ad 100644
--- a/src/class/hid/hid_host.h
+++ b/src/class/hid/hid_host.h
@@ -140,7 +140,7 @@ bool tuh_hid_send_ready(uint8_t dev_addr, uint8_t idx);
bool tuh_hid_send_report(uint8_t dev_addr, uint8_t idx, uint8_t report_id, const void *report, uint16_t len);
//--------------------------------------------------------------------+
-// Callbacks (Weak is optional)
+// Callbacks (optional)
//--------------------------------------------------------------------+
// Invoked when device with hid interface is mounted
diff --git a/src/class/midi/midi.h b/src/class/midi/midi.h
index cd67640e4..8121ec016 100644
--- a/src/class/midi/midi.h
+++ b/src/class/midi/midi.h
@@ -186,6 +186,24 @@ typedef midi_desc_cs_endpoint_n_t(1) midi_desc_cs_endpoint_1jack_t;
TU_VERIFY_STATIC(sizeof(midi_desc_cs_endpoint_1jack_t) == 4+1, "size is not correct");
//--------------------------------------------------------------------+
+// MIDI 2.0 UMP Helpers
+//--------------------------------------------------------------------+
+
+// Return the number of 32-bit words for a UMP message given its Message Type
+static inline uint8_t midi2_ump_word_count(uint8_t mt) {
+ switch (mt) {
+ case 0x0: case 0x1: case 0x2: case 0x6: case 0x7:
+ return 1;
+ case 0x3: case 0x4: case 0x8: case 0x9: case 0xA:
+ return 2;
+ case 0xB: case 0xC:
+ return 3;
+ default: // 0x5, 0xD, 0xE, 0xF
+ return 4;
+ }
+}
+
+//--------------------------------------------------------------------+
// For Internal Driver Use
//--------------------------------------------------------------------+
typedef struct {
diff --git a/src/class/midi/midi2_device.c b/src/class/midi/midi2_device.c
new file mode 100644
index 000000000..b14f439f8
--- /dev/null
+++ b/src/class/midi/midi2_device.c
@@ -0,0 +1,769 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2026 Saulo Verissimo
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * This file is part of the TinyUSB stack.
+ */
+
+#include "tusb_option.h"
+
+#if CFG_TUD_ENABLED && CFG_TUD_MIDI2
+
+#include <string.h>
+
+#include "device/usbd.h"
+#include "device/usbd_pvt.h"
+#include "midi2_device.h"
+
+//--------------------------------------------------------------------+
+// Weak stubs
+//--------------------------------------------------------------------+
+TU_ATTR_WEAK void tud_midi2_rx_cb(uint8_t itf) { (void) itf; }
+TU_ATTR_WEAK void tud_midi2_set_itf_cb(uint8_t itf, uint8_t alt) { (void) itf; (void) alt; }
+TU_ATTR_WEAK bool tud_midi2_get_req_itf_cb(uint8_t rhport, const tusb_control_request_t* request) {
+ (void) rhport; (void) request; return false;
+}
+TU_ATTR_WEAK uint8_t tud_midi2_num_groups_cb(uint8_t itf) {
+ (void) itf; return CFG_TUD_MIDI2_NUM_GROUPS;
+}
+TU_ATTR_WEAK uint8_t tud_midi2_num_function_blocks_cb(uint8_t itf) {
+ (void) itf; return CFG_TUD_MIDI2_NUM_FUNCTION_BLOCKS;
+}
+TU_ATTR_WEAK const char* tud_midi2_ep_name_cb(uint8_t itf) {
+ (void) itf; return CFG_TUD_MIDI2_EP_NAME;
+}
+TU_ATTR_WEAK const char* tud_midi2_product_id_cb(uint8_t itf) {
+ (void) itf; return CFG_TUD_MIDI2_PRODUCT_ID;
+}
+
+//--------------------------------------------------------------------+
+// Byte order note
+//--------------------------------------------------------------------+
+// Per USB-MIDI 2.0 Section 3.2.2, each 32-bit UMP word is transmitted with the
+// least significant byte first. This driver reads and writes UMP words as
+// native uint32_t through tu_edpt_stream_read/write. All TinyUSB targets are
+// little-endian, so the in-memory layout already matches the wire order and no
+// swap is needed. If a big-endian target is ever supported, wrap access with
+// tu_htole32 / tu_le32toh at the buffer boundary.
+
+//--------------------------------------------------------------------+
+// UMP Stream Message Constants
+//--------------------------------------------------------------------+
+// UMP Message Type for Stream messages (bits 31:28)
+enum {
+ MT_STREAM = 0x0F,
+};
+
+// UMP Stream Status values (10-bit, bits 25:16)
+enum {
+ STREAM_ENDPOINT_DISCOVERY = 0x000,
+ STREAM_ENDPOINT_INFO = 0x001,
+ STREAM_EP_NAME = 0x003,
+ STREAM_PROD_INSTANCE_ID = 0x004,
+ STREAM_CONFIG_REQUEST = 0x005,
+ STREAM_CONFIG_NOTIFY = 0x006,
+ STREAM_FB_DISCOVERY = 0x010,
+ STREAM_FB_INFO = 0x011,
+};
+
+enum {
+ UMP_VER_MAJOR = 1,
+ UMP_VER_MINOR = 1,
+};
+
+// Group Terminal Block descriptor types (USB-MIDI 2.0)
+enum {
+ MIDI2_CS_GRP_TRM_BLOCK = 0x26,
+ MIDI2_GRP_TRM_BLOCK_HEADER = 0x01,
+ MIDI2_GRP_TRM_BLOCK_ENTRY = 0x02,
+};
+
+//--------------------------------------------------------------------+
+// MACRO CONSTANT TYPEDEF
+//--------------------------------------------------------------------+
+typedef struct {
+ uint8_t ep_addr;
+ uint16_t mps;
+ tu_fifo_t ff;
+
+#if CFG_TUD_EDPT_DEDICATED_HWFIFO == 0
+ uint8_t* ep_buf;
+#endif
+} midi2d_tx_t;
+
+typedef struct {
+ uint8_t rhport;
+ uint8_t itf_num;
+ uint8_t alt_setting;
+ uint8_t protocol;
+ bool negotiated;
+
+ /*------------- From this point, data is not cleared by bus reset -------------*/
+ struct {
+ midi2d_tx_t tx;
+ tu_edpt_stream_t rx;
+
+ uint8_t rx_ff_buf[CFG_TUD_MIDI2_RX_BUFSIZE];
+ uint8_t tx_ff_buf[CFG_TUD_MIDI2_TX_BUFSIZE];
+ } ep_stream;
+} midi2d_interface_t;
+
+// Skip local EP buffer if dedicated hw FIFO is supported
+#if CFG_TUD_EDPT_DEDICATED_HWFIFO == 0
+typedef struct {
+ TUD_EPBUF_DEF(epin, CFG_TUD_MIDI2_TX_EPSIZE);
+ TUD_EPBUF_DEF(epout, CFG_TUD_MIDI2_RX_EPSIZE);
+} midi2d_epbuf_t;
+
+CFG_TUD_MEM_SECTION static midi2d_epbuf_t _midi2d_epbuf[CFG_TUD_MIDI2];
+#endif
+
+TU_VERIFY_STATIC(CFG_TUD_MIDI2_NUM_GROUPS >= 1 && CFG_TUD_MIDI2_NUM_GROUPS <= 16,
+ "CFG_TUD_MIDI2_NUM_GROUPS must be 1..16");
+TU_VERIFY_STATIC(CFG_TUD_MIDI2_NUM_FUNCTION_BLOCKS >= 1 && CFG_TUD_MIDI2_NUM_FUNCTION_BLOCKS <= 32,
+ "CFG_TUD_MIDI2_NUM_FUNCTION_BLOCKS must be 1..32");
+
+#define ITF_MEM_RESET_SIZE offsetof(midi2d_interface_t, ep_stream)
+
+static midi2d_interface_t _midi2d_itf[CFG_TUD_MIDI2];
+
+// Default Group Terminal Block descriptor (USB-MIDI 2.0 spec, Table 5-5/5-6)
+static const uint8_t _default_gtb_desc[] = {
+ // GTB Header (5 bytes)
+ 5, // bLength
+ MIDI2_CS_GRP_TRM_BLOCK, // bDescriptorType
+ MIDI2_GRP_TRM_BLOCK_HEADER, // bDescriptorSubtype
+ U16_TO_U8S_LE(18), // wTotalLength (5 + 13 = 18)
+
+ // GTB Entry (13 bytes)
+ 13, // bLength
+ MIDI2_CS_GRP_TRM_BLOCK, // bDescriptorType
+ MIDI2_GRP_TRM_BLOCK_ENTRY, // bDescriptorSubtype
+ 1, // bGrpTrmBlkID
+ 0x00, // bGrpTrmBlkType: bidirectional
+ 0x00, // nGroupTrm: first group (0)
+ CFG_TUD_MIDI2_NUM_GROUPS, // nNumGroupTrm
+ CFG_TUD_MIDI2_BLOCK_STRIDX, // iBlockItem: string descriptor index (0 = none)
+ 0x00, // bMIDIProtocol: unknown/not fixed
+ 0, 0, // wMaxInputBandwidth: unknown
+ 0, 0 // wMaxOutputBandwidth: unknown
+};
+
+//--------------------------------------------------------------------+
+// Common utility functions
+//--------------------------------------------------------------------+
+
+static inline uint8_t _itf_idx(const midi2d_interface_t* p_midi) {
+ return (uint8_t)(p_midi - _midi2d_itf);
+}
+
+static inline bool _tx_opened(const midi2d_interface_t* p_midi) {
+ return p_midi->ep_stream.tx.ep_addr != 0;
+}
+
+static uint8_t _tx_byte_at(const tu_fifo_buffer_info_t* info, uint16_t offset) {
+ if (offset < info->linear.len) {
+ return info->linear.ptr[offset];
+ }
+
+ offset = (uint16_t) (offset - info->linear.len);
+ if (offset < info->wrapped.len) {
+ return info->wrapped.ptr[offset];
+ }
+
+ return 0;
+}
+
+// Calculate the largest byte count that contains only whole UMP packets and
+// fits in one USB transfer (<= mps).
+static uint16_t _tx_nonseg_len_to_mps(midi2d_tx_t* tx) {
+ tu_fifo_buffer_info_t info;
+ tu_fifo_get_read_info(&tx->ff, &info);
+
+ const uint16_t available = (uint16_t) (info.linear.len + info.wrapped.len);
+ uint16_t bytes = 0;
+
+ while (bytes < tx->mps) {
+ if ((uint16_t) (available - bytes) < 4) break;
+
+ uint8_t mt = (uint8_t)((_tx_byte_at(&info, (uint16_t) (bytes + 3)) >> 4) & 0x0F);
+ uint8_t pkt_words = midi2_ump_word_count(mt);
+ uint16_t pkt_bytes = (uint16_t) pkt_words * 4;
+
+ if (pkt_bytes == 0) break;
+ if ((uint16_t) (available - bytes) < pkt_bytes) break;
+ if ((uint16_t) (bytes + pkt_bytes) > tx->mps) break;
+
+ bytes = (uint16_t) (bytes + pkt_bytes);
+ }
+
+ return bytes;
+}
+
+// Start one IN transfer capped at mps, return number of bytes queued to the controller, or 0 if nothing was queued.
+static uint16_t _tx_start_xfer(midi2d_interface_t* p_midi) {
+ midi2d_tx_t* tx = &p_midi->ep_stream.tx;
+ uint16_t ff_count = tu_fifo_count(&tx->ff);
+
+ if (ff_count == 0) return 0;
+
+ if (!usbd_edpt_claim(p_midi->rhport, tx->ep_addr)) return 0;
+
+ uint16_t bytes;
+ if (p_midi->alt_setting == 1) {
+ bytes = _tx_nonseg_len_to_mps(tx);
+ } else {
+ bytes = tu_min16(tu_fifo_count(&tx->ff), tx->mps);
+ }
+ if (bytes == 0) {
+ usbd_edpt_release(p_midi->rhport, tx->ep_addr);
+ return 0;
+ }
+
+#if CFG_TUD_EDPT_DEDICATED_HWFIFO
+ TU_ASSERT(usbd_edpt_xfer_fifo(p_midi->rhport, tx->ep_addr, &tx->ff, bytes, false), 0);
+#else
+ tu_fifo_read_n(&tx->ff, tx->ep_buf, bytes);
+ TU_ASSERT(usbd_edpt_xfer(p_midi->rhport, tx->ep_addr, tx->ep_buf, bytes, false), 0);
+#endif
+
+ return bytes;
+}
+
+static uint32_t _tx_ump_write(midi2d_interface_t* p_midi, const uint32_t* words, uint32_t count) {
+ uint32_t written = 0;
+ while (written < count) {
+ uint8_t mt = (uint8_t)((words[written] >> 28) & 0x0F);
+ uint8_t pkt_words = midi2_ump_word_count(mt);
+ uint16_t pkt_bytes = (uint16_t) pkt_words * 4;
+
+ if (written + pkt_words > count) break;
+ if (tu_fifo_remaining(&p_midi->ep_stream.tx.ff) < pkt_bytes) break;
+
+ if (tu_fifo_write_n(&p_midi->ep_stream.tx.ff, &words[written], pkt_bytes) != pkt_bytes) break;
+ written += pkt_words;
+ }
+
+ (void) _tx_start_xfer(p_midi);
+ return written;
+}
+
+//--------------------------------------------------------------------+
+// Protocol Negotiation
+//--------------------------------------------------------------------+
+static void _nego_send_ump(midi2d_interface_t* p_midi, const uint32_t* words, uint8_t count) {
+ if (!_tx_opened(p_midi)) return;
+ if (tu_fifo_remaining(&p_midi->ep_stream.tx.ff) < (uint32_t) count * 4) return;
+ (void) _tx_ump_write(p_midi, words, count);
+}
+
+static void _nego_send_endpoint_info(midi2d_interface_t* p_midi) {
+ uint32_t msg[4] = {0};
+ msg[0] = ((uint32_t) MT_STREAM << 28)
+ | ((uint32_t) STREAM_ENDPOINT_INFO << 16)
+ | ((uint32_t) UMP_VER_MAJOR << 8)
+ | (uint32_t) UMP_VER_MINOR;
+ msg[1] = (UINT32_C(1) << 31) // Static Function Blocks flag
+ | ((uint32_t)(tud_midi2_num_function_blocks_cb(_itf_idx(p_midi)) & 0x7F) << 24)
+ | (UINT32_C(1) << 9) // MIDI 2.0 Protocol capability
+ | (UINT32_C(1) << 8); // MIDI 1.0 Protocol capability
+ _nego_send_ump(p_midi, msg, 4);
+}
+
+static void _nego_send_stream_text(midi2d_interface_t* p_midi, uint16_t status, const char* str) {
+ if (!str || str[0] == '\0') return;
+
+ uint16_t total_len = (uint16_t) strlen(str);
+ uint16_t offset = 0;
+
+ while (offset < total_len) {
+ uint16_t remaining = total_len - offset;
+ uint8_t n = (uint8_t)((remaining > 14) ? 14 : remaining);
+ bool is_first = (offset == 0);
+ bool is_last = (remaining <= 14);
+
+ uint8_t form;
+ if (is_first && is_last) form = 0;
+ else if (is_first) form = 1;
+ else if (is_last) form = 3;
+ else form = 2;
+
+ uint32_t msg[4] = {0};
+ msg[0] = ((uint32_t) MT_STREAM << 28)
+ | ((uint32_t) form << 26)
+ | ((uint32_t) status << 16);
+
+ const char* p = str + offset;
+ if (n > 0) msg[0] |= ((uint32_t)(uint8_t) p[0] << 8);
+ if (n > 1) msg[0] |= (uint32_t)(uint8_t) p[1];
+ for (uint8_t i = 2; i < n; i++) {
+ uint8_t word_idx = (uint8_t)(1 + (i - 2) / 4);
+ uint8_t shift = (uint8_t)(24 - ((i - 2) % 4) * 8);
+ msg[word_idx] |= ((uint32_t)(uint8_t) p[i] << shift);
+ }
+
+ _nego_send_ump(p_midi, msg, 4);
+ offset += n;
+ }
+}
+
+static void _nego_send_config_notify(midi2d_interface_t* p_midi, uint8_t protocol) {
+ uint32_t msg[4] = {0};
+ msg[0] = ((uint32_t) MT_STREAM << 28)
+ | ((uint32_t) STREAM_CONFIG_NOTIFY << 16)
+ | ((uint32_t) protocol << 8);
+ _nego_send_ump(p_midi, msg, 4);
+}
+
+static void _nego_send_fb_info(midi2d_interface_t* p_midi, uint8_t fb_idx) {
+ uint32_t msg[4] = {0};
+ msg[0] = ((uint32_t) MT_STREAM << 28)
+ | ((uint32_t) STREAM_FB_INFO << 16)
+ | (UINT32_C(1) << 15)
+ | ((uint32_t) fb_idx << 8)
+ | 0x02; // bDirection: bidirectional
+ msg[1] = ((uint32_t) 0 << 24) // bFirstGroup
+ | ((uint32_t) tud_midi2_num_groups_cb(_itf_idx(p_midi)) << 16);
+ _nego_send_ump(p_midi, msg, 4);
+}
+
+static void _nego_handle_stream_msg(midi2d_interface_t* p_midi, const uint32_t* words) {
+ uint16_t status = (words[0] >> 16) & 0x3FF;
+
+ switch (status) {
+ case STREAM_ENDPOINT_DISCOVERY:
+ _nego_send_endpoint_info(p_midi);
+ _nego_send_stream_text(p_midi, STREAM_EP_NAME, tud_midi2_ep_name_cb(_itf_idx(p_midi)));
+ _nego_send_stream_text(p_midi, STREAM_PROD_INSTANCE_ID, tud_midi2_product_id_cb(_itf_idx(p_midi)));
+ break;
+
+ case STREAM_CONFIG_REQUEST: {
+ uint8_t req_proto = (words[0] >> 8) & 0xFF;
+ if (req_proto == MIDI_PROTOCOL_MIDI1 || req_proto == MIDI_PROTOCOL_MIDI2) {
+ p_midi->protocol = req_proto;
+ }
+ _nego_send_config_notify(p_midi, p_midi->protocol);
+ p_midi->negotiated = true;
+ break;
+ }
+
+ case STREAM_FB_DISCOVERY: {
+ uint8_t fb_idx = (words[0] >> 8) & 0xFF;
+ uint8_t fb_count = tud_midi2_num_function_blocks_cb(_itf_idx(p_midi));
+ if (fb_idx == 0xFF) {
+ for (uint8_t f = 0; f < fb_count; f++) {
+ _nego_send_fb_info(p_midi, f);
+ }
+ } else if (fb_idx < fb_count) {
+ _nego_send_fb_info(p_midi, fb_idx);
+ }
+ break;
+ }
+
+ default:
+ break;
+ }
+}
+
+static void _nego_process_rx(midi2d_interface_t* p_midi) {
+ tu_edpt_stream_t* ep_rx = &p_midi->ep_stream.rx;
+ uint8_t word_bytes[4];
+
+ while (tu_fifo_peek_n(&ep_rx->ff, word_bytes, 4) == 4) {
+ // UMP words travel LSB-first on the wire and in LE memory, so MT is in
+ // the high nibble of byte 3, not byte 0.
+ uint8_t mt = (word_bytes[3] >> 4) & 0x0F;
+ uint8_t pkt_words = midi2_ump_word_count(mt);
+ uint32_t pkt_bytes = (uint32_t)pkt_words * 4;
+
+ if (mt != MT_STREAM) break;
+ if (tu_edpt_stream_read_available(ep_rx) < pkt_bytes) break;
+
+ uint32_t buf[4] = {0};
+ tu_edpt_stream_read(ep_rx, buf, pkt_bytes);
+ _nego_handle_stream_msg(p_midi, buf);
+ }
+}
+
+//--------------------------------------------------------------------+
+// READ API
+//--------------------------------------------------------------------+
+bool tud_midi2_n_mounted(uint8_t itf) {
+ TU_VERIFY(itf < CFG_TUD_MIDI2, false);
+ midi2d_interface_t* p_midi = &_midi2d_itf[itf];
+ return _tx_opened(p_midi) &&
+ tu_edpt_stream_is_opened(&p_midi->ep_stream.rx);
+}
+
+uint32_t tud_midi2_n_available(uint8_t itf) {
+ TU_VERIFY(itf < CFG_TUD_MIDI2, 0);
+ midi2d_interface_t* p_midi = &_midi2d_itf[itf];
+ return tu_edpt_stream_read_available(&p_midi->ep_stream.rx) / 4;
+}
+
+uint32_t tud_midi2_n_ump_read(uint8_t itf, uint32_t* words, uint32_t max_words) {
+ TU_VERIFY(itf < CFG_TUD_MIDI2 && words != NULL && max_words > 0, 0);
+ midi2d_interface_t* p_midi = &_midi2d_itf[itf];
+
+ // UMP API is only valid on Alt Setting 1 (USB-MIDI 2.0).
+ // Alt 0 carries USB-MIDI 1.0 32-bit Event Packets, not UMP words.
+ if (p_midi->alt_setting != 1) { return 0; }
+
+ tu_edpt_stream_t* ep_rx = &p_midi->ep_stream.rx;
+
+ uint32_t total_read = 0;
+ while (total_read < max_words) {
+ uint8_t word_bytes[4];
+ if (tu_fifo_peek_n(&ep_rx->ff, word_bytes, 4) < 4) break;
+
+ // UMP words travel LSB-first; MT is the high nibble of byte 3, not byte 0.
+ uint8_t mt = (word_bytes[3] >> 4) & 0x0F;
+ uint8_t pkt_words = midi2_ump_word_count(mt);
+
+ if (total_read + pkt_words > max_words) break;
+ if (tu_edpt_stream_read_available(ep_rx) < (uint32_t)pkt_words * 4) break;
+
+ tu_edpt_stream_read(ep_rx, &words[total_read], pkt_words * 4);
+ total_read += pkt_words;
+ }
+
+ return total_read;
+}
+
+uint32_t tud_midi2_n_packet_read(uint8_t itf, uint8_t packets[], uint32_t max_packets) {
+ TU_VERIFY(itf < CFG_TUD_MIDI2 && packets != NULL && max_packets > 0, 0);
+ midi2d_interface_t* p_midi = &_midi2d_itf[itf];
+ return tu_edpt_stream_read(&p_midi->ep_stream.rx, packets, max_packets * 4u) >> 2u;
+}
+
+//--------------------------------------------------------------------+
+// WRITE API
+//--------------------------------------------------------------------+
+uint32_t tud_midi2_n_ump_write(uint8_t itf, const uint32_t* words, uint32_t count) {
+ TU_VERIFY(itf < CFG_TUD_MIDI2 && words != NULL && count > 0, 0);
+ midi2d_interface_t* p_midi = &_midi2d_itf[itf];
+
+ // UMP API is only valid on Alt Setting 1 (USB-MIDI 2.0).
+ // Alt 0 carries USB-MIDI 1.0 32-bit Event Packets, not UMP words.
+ if (p_midi->alt_setting != 1) { return 0; }
+ TU_VERIFY(_tx_opened(p_midi), 0);
+
+ return _tx_ump_write(p_midi, words, count);
+}
+
+uint32_t tud_midi2_n_packet_write(uint8_t itf, const uint8_t packets[], uint32_t count) {
+ TU_VERIFY(itf < CFG_TUD_MIDI2 && packets != NULL && count > 0, 0);
+ midi2d_interface_t* p_midi = &_midi2d_itf[itf];
+ midi2d_tx_t* tx = &p_midi->ep_stream.tx;
+
+ // Packet API is for Alt Setting 0 (USB-MIDI 1.0) event packets.
+ TU_VERIFY(p_midi->alt_setting == 0, 0);
+ TU_VERIFY(_tx_opened(p_midi), 0);
+
+ uint32_t written = 0;
+ while (written < count) {
+ if (tu_fifo_remaining(&tx->ff) < 4) break;
+
+ if (tu_fifo_write_n(&tx->ff, packets + written * 4u, 4) != 4) break;
+ written++;
+ }
+
+ (void) _tx_start_xfer(p_midi);
+
+ return written;
+}
+
+//--------------------------------------------------------------------+
+// STATE GETTERS
+//--------------------------------------------------------------------+
+uint8_t tud_midi2_n_alt_setting(uint8_t itf) {
+ TU_VERIFY(itf < CFG_TUD_MIDI2, 0);
+ return _midi2d_itf[itf].alt_setting;
+}
+
+bool tud_midi2_n_negotiated(uint8_t itf) {
+ TU_VERIFY(itf < CFG_TUD_MIDI2, false);
+ return _midi2d_itf[itf].negotiated;
+}
+
+uint8_t tud_midi2_n_protocol(uint8_t itf) {
+ TU_VERIFY(itf < CFG_TUD_MIDI2, 0);
+ return _midi2d_itf[itf].protocol;
+}
+
+//--------------------------------------------------------------------+
+// USBD Driver API
+//--------------------------------------------------------------------+
+void midi2d_init(void) {
+ tu_memclr(_midi2d_itf, sizeof(_midi2d_itf));
+ for (uint8_t i = 0; i < CFG_TUD_MIDI2; i++) {
+ midi2d_interface_t* p_midi = &_midi2d_itf[i];
+ p_midi->protocol = MIDI_PROTOCOL_MIDI2;
+
+ #if CFG_TUD_EDPT_DEDICATED_HWFIFO
+ uint8_t *epout_buf = NULL;
+ uint8_t *epin_buf = NULL;
+ #else
+ uint8_t *epout_buf = _midi2d_epbuf[i].epout;
+ uint8_t *epin_buf = _midi2d_epbuf[i].epin;
+ #endif
+
+ tu_edpt_stream_init(&p_midi->ep_stream.rx, false, false, false,
+ p_midi->ep_stream.rx_ff_buf, CFG_TUD_MIDI2_RX_BUFSIZE, epout_buf);
+
+ midi2d_tx_t* tx = &p_midi->ep_stream.tx;
+ (void) tu_fifo_config(&tx->ff, p_midi->ep_stream.tx_ff_buf, CFG_TUD_MIDI2_TX_BUFSIZE, false);
+#if CFG_TUD_EDPT_DEDICATED_HWFIFO == 0
+ tx->ep_buf = epin_buf;
+#else
+ (void) epin_buf;
+#endif
+ }
+}
+
+bool midi2d_deinit(void) {
+ for (uint8_t i = 0; i < CFG_TUD_MIDI2; i++) {
+ midi2d_interface_t* p_midi = &_midi2d_itf[i];
+ tu_edpt_stream_deinit(&p_midi->ep_stream.rx);
+ }
+ return true;
+}
+
+void midi2d_reset(uint8_t rhport) {
+ (void) rhport;
+ for (uint8_t i = 0; i < CFG_TUD_MIDI2; i++) {
+ midi2d_interface_t* p_midi = &_midi2d_itf[i];
+ tu_memclr(p_midi, ITF_MEM_RESET_SIZE);
+
+ tu_edpt_stream_clear(&p_midi->ep_stream.rx);
+ tu_edpt_stream_close(&p_midi->ep_stream.rx);
+
+ tu_fifo_clear(&p_midi->ep_stream.tx.ff);
+ p_midi->ep_stream.tx.ep_addr = 0;
+ }
+}
+
+TU_ATTR_ALWAYS_INLINE static inline uint8_t find_midi2_itf(uint8_t ep_addr) {
+ for (uint8_t idx = 0; idx < CFG_TUD_MIDI2; idx++) {
+ const midi2d_interface_t* p_midi = &_midi2d_itf[idx];
+ if (ep_addr == p_midi->ep_stream.rx.ep_addr || ep_addr == p_midi->ep_stream.tx.ep_addr) {
+ return idx;
+ }
+ }
+ return TUSB_INDEX_INVALID_8;
+}
+
+static uint8_t find_midi2_itf_by_num(uint8_t itf_num) {
+ for (uint8_t idx = 0; idx < CFG_TUD_MIDI2; idx++) {
+ if (_midi2d_itf[idx].itf_num == itf_num) return idx;
+ }
+ return TUSB_INDEX_INVALID_8;
+}
+
+uint16_t midi2d_open(uint8_t rhport, const tusb_desc_interface_t* desc_itf, uint16_t max_len) {
+ const uint8_t* p_desc = (const uint8_t*) desc_itf;
+ const uint8_t* desc_end = p_desc + max_len;
+
+ // 1st Interface: Audio Control v1 (optional)
+ if (TUSB_CLASS_AUDIO == desc_itf->bInterfaceClass &&
+ AUDIO_SUBCLASS_CONTROL == desc_itf->bInterfaceSubClass &&
+ AUDIO_FUNC_PROTOCOL_CODE_UNDEF == desc_itf->bInterfaceProtocol) {
+ p_desc = tu_desc_next(desc_itf);
+ while (tu_desc_in_bounds(p_desc, desc_end) && TUSB_DESC_CS_INTERFACE == tu_desc_type(p_desc)) {
+ p_desc = tu_desc_next(p_desc);
+ }
+ }
+
+ // 2nd Interface: MIDI Streaming
+ TU_VERIFY(TUSB_DESC_INTERFACE == tu_desc_type(p_desc), 0);
+ const tusb_desc_interface_t* desc_midi = (const tusb_desc_interface_t*) p_desc;
+
+ TU_VERIFY(TUSB_CLASS_AUDIO == desc_midi->bInterfaceClass &&
+ AUDIO_SUBCLASS_MIDI_STREAMING == desc_midi->bInterfaceSubClass &&
+ AUDIO_FUNC_PROTOCOL_CODE_UNDEF == desc_midi->bInterfaceProtocol,
+ 0);
+
+ uint8_t idx = find_midi2_itf(0);
+ TU_ASSERT(idx < CFG_TUD_MIDI2, 0);
+ midi2d_interface_t* p_midi = &_midi2d_itf[idx];
+
+ p_midi->rhport = rhport;
+ p_midi->itf_num = desc_midi->bInterfaceNumber;
+ p_midi->alt_setting = 0;
+ p_midi->protocol = MIDI_PROTOCOL_MIDI2;
+ p_midi->negotiated = false;
+
+ p_desc = tu_desc_next(p_desc);
+
+ // Skip class-specific descriptors
+ while (tu_desc_in_bounds(p_desc, desc_end) && TUSB_DESC_CS_INTERFACE == tu_desc_type(p_desc)) {
+ p_desc = tu_desc_next(p_desc);
+ }
+
+ // Find and open endpoint descriptors
+ uint8_t found_ep = 0;
+ while ((found_ep < desc_midi->bNumEndpoints) && tu_desc_in_bounds(p_desc, desc_end)) {
+ if (TUSB_DESC_ENDPOINT == tu_desc_type(p_desc)) {
+ const tusb_desc_endpoint_t* desc_ep = (const tusb_desc_endpoint_t*) p_desc;
+ TU_ASSERT(usbd_edpt_open(rhport, desc_ep), 0);
+ const uint8_t ep_addr = desc_ep->bEndpointAddress;
+
+ if (tu_edpt_dir(ep_addr) == TUSB_DIR_IN) {
+ p_midi->ep_stream.tx.ep_addr = ep_addr;
+ p_midi->ep_stream.tx.mps = tu_edpt_packet_size(desc_ep);
+ tu_fifo_clear(&p_midi->ep_stream.tx.ff);
+ } else {
+ tu_edpt_stream_open(&p_midi->ep_stream.rx, rhport, desc_ep, tu_edpt_packet_size(desc_ep));
+ tu_edpt_stream_clear(&p_midi->ep_stream.rx);
+ TU_ASSERT(tu_edpt_stream_read_xfer(&p_midi->ep_stream.rx) > 0, 0);
+ }
+
+ found_ep++;
+ }
+
+ p_desc = tu_desc_next(p_desc);
+ }
+
+ // Skip remaining descriptors (alt setting 1, CS endpoints, GTB)
+ // Stop at any interface descriptor that is not our MIDI Streaming alt setting
+ while (tu_desc_in_bounds(p_desc, desc_end)) {
+ uint8_t dtype = tu_desc_type(p_desc);
+
+ if (dtype == TUSB_DESC_INTERFACE) {
+ const tusb_desc_interface_t* next_itf = (const tusb_desc_interface_t*) p_desc;
+ // Continue only if this is an alternate setting of our own interface
+ if (next_itf->bInterfaceNumber != desc_midi->bInterfaceNumber) break;
+ } else if (dtype != TUSB_DESC_CS_INTERFACE && dtype != TUSB_DESC_CS_ENDPOINT &&
+ dtype != TUSB_DESC_ENDPOINT) {
+ break;
+ }
+
+ p_desc = tu_desc_next(p_desc);
+ }
+
+ return (uint16_t)(p_desc - (const uint8_t*) desc_itf);
+}
+
+bool midi2d_control_xfer_cb(uint8_t rhport, uint8_t stage, const tusb_control_request_t* request) {
+ TU_LOG2("MIDI2 ctrl: stage=%u bRequest=0x%02X wValue=0x%04X wIndex=0x%04X wLength=%u\r\n",
+ stage, request->bRequest, request->wValue, request->wIndex, request->wLength);
+
+ if (stage != CONTROL_STAGE_SETUP) return true;
+
+ switch (request->bRequest) {
+ case TUSB_REQ_SET_INTERFACE: {
+ uint8_t itf_num = tu_u16_low(request->wIndex);
+ uint8_t alt = tu_u16_low(request->wValue);
+
+ // Only Alt Setting 0 (MIDI 1.0) and 1 (UMP) are valid
+ if (alt > 1) return false;
+
+ uint8_t idx = find_midi2_itf_by_num(itf_num);
+ if (idx >= CFG_TUD_MIDI2) return false;
+
+ midi2d_interface_t* p_midi = &_midi2d_itf[idx];
+ p_midi->alt_setting = alt;
+
+ tu_edpt_stream_clear(&p_midi->ep_stream.rx);
+ tu_fifo_clear(&p_midi->ep_stream.tx.ff);
+
+ if (alt == 1) {
+ p_midi->negotiated = false;
+ p_midi->protocol = MIDI_PROTOCOL_MIDI2;
+ }
+
+ // Re-arm RX endpoint for receiving data after alt setting change
+ tu_edpt_stream_read_xfer(&p_midi->ep_stream.rx);
+
+ tud_midi2_set_itf_cb(idx, alt);
+ tud_control_status(rhport, request);
+ return true;
+ }
+
+ case TUSB_REQ_GET_DESCRIPTOR: {
+ // USB-MIDI 2.0 Section 6: GTB descriptor retrieval
+ // bmRequestType = 0x81 (Device-to-Host, Standard, Interface)
+ // wValue = CS_GR_TRM_BLOCK (0x26) in high byte, alt setting in low byte
+ // wIndex = interface number
+ if (request->bmRequestType_bit.direction != TUSB_DIR_IN) return false;
+ if (request->bmRequestType_bit.type != TUSB_REQ_TYPE_STANDARD) return false;
+ if (request->bmRequestType_bit.recipient != TUSB_REQ_RCPT_INTERFACE) return false;
+ if (tu_u16_high(request->wValue) != MIDI2_CS_GRP_TRM_BLOCK) return false;
+
+ uint8_t itf_num = tu_u16_low(request->wIndex);
+ uint8_t idx = find_midi2_itf_by_num(itf_num);
+ if (idx >= CFG_TUD_MIDI2) return false;
+
+ // Only Alt Setting 1 exposes Group Terminal Block descriptors.
+ if (tu_u16_low(request->wValue) != 0x01) return false;
+
+ if (tud_midi2_get_req_itf_cb(rhport, request)) return true;
+
+ uint16_t len = request->wLength;
+ if (len > sizeof(_default_gtb_desc)) {
+ len = sizeof(_default_gtb_desc);
+ }
+ tud_control_xfer(rhport, request, (void*)(uintptr_t) _default_gtb_desc, len);
+ return true;
+ }
+
+ default:
+ return false;
+ }
+}
+
+bool midi2d_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) {
+ (void) rhport;
+
+ uint8_t idx = find_midi2_itf(ep_addr);
+ TU_ASSERT(idx < CFG_TUD_MIDI2);
+ midi2d_interface_t* p_midi = &_midi2d_itf[idx];
+
+ tu_edpt_stream_t* ep_rx = &p_midi->ep_stream.rx;
+ midi2d_tx_t* ep_tx = &p_midi->ep_stream.tx;
+
+ if (ep_addr == ep_rx->ep_addr) {
+ if (result == XFER_RESULT_SUCCESS) {
+ tu_edpt_stream_read_xfer_complete(ep_rx, xferred_bytes);
+ if (p_midi->alt_setting == 1) {
+ _nego_process_rx(p_midi);
+ }
+ tud_midi2_rx_cb(idx);
+ }
+ tu_edpt_stream_read_xfer(ep_rx);
+ } else if (ep_addr == ep_tx->ep_addr && result == XFER_RESULT_SUCCESS) {
+ uint16_t queued = _tx_start_xfer(p_midi);
+ // Send ZLP if no more data is queued but the last transfer was exactly mps
+ if (queued == 0 && tu_fifo_count(&ep_tx->ff) == 0 && xferred_bytes > 0 &&
+ (0 == (xferred_bytes & (ep_tx->mps - 1)))) {
+ if (usbd_edpt_claim(rhport, ep_tx->ep_addr)) {
+ usbd_edpt_xfer(rhport, ep_tx->ep_addr, NULL, 0, false);
+ }
+ }
+ } else {
+ return false;
+ }
+
+ return true;
+}
+
+#endif
diff --git a/src/class/midi/midi2_device.h b/src/class/midi/midi2_device.h
new file mode 100644
index 000000000..e53535693
--- /dev/null
+++ b/src/class/midi/midi2_device.h
@@ -0,0 +1,193 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2026 Saulo Verissimo
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * This file is part of the TinyUSB stack.
+ */
+
+#ifndef TUSB_MIDI2_DEVICE_H_
+#define TUSB_MIDI2_DEVICE_H_
+
+#include "class/audio/audio.h"
+#include "midi.h"
+
+//--------------------------------------------------------------------+
+// Class Driver Configuration
+//--------------------------------------------------------------------+
+
+// Config defaults are in tusb_option.h:
+// CFG_TUD_MIDI2_RX_EPSIZE, CFG_TUD_MIDI2_TX_EPSIZE,
+// CFG_TUD_MIDI2_RX_BUFSIZE, CFG_TUD_MIDI2_TX_BUFSIZE,
+// CFG_TUD_MIDI2_NUM_GROUPS, CFG_TUD_MIDI2_NUM_FUNCTION_BLOCKS,
+// CFG_TUD_MIDI2_EP_NAME, CFG_TUD_MIDI2_PRODUCT_ID
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+//--------------------------------------------------------------------+
+// Class Driver Configuration
+//--------------------------------------------------------------------+
+
+#ifndef CFG_TUD_MIDI2_TX_EPSIZE
+ #define CFG_TUD_MIDI2_TX_EPSIZE TUD_EPSIZE_BULK_MAX
+#endif
+
+#ifndef CFG_TUD_MIDI2_RX_EPSIZE
+ #define CFG_TUD_MIDI2_RX_EPSIZE TUD_EPSIZE_BULK_MAX
+#endif
+
+#ifndef CFG_TUD_MIDI2_TX_BUFSIZE
+ #define CFG_TUD_MIDI2_TX_BUFSIZE CFG_TUD_MIDI2_TX_EPSIZE
+#endif
+
+#ifndef CFG_TUD_MIDI2_RX_BUFSIZE
+ #define CFG_TUD_MIDI2_RX_BUFSIZE CFG_TUD_MIDI2_RX_EPSIZE
+#endif
+
+#ifndef CFG_TUD_MIDI2_NUM_GROUPS
+ #define CFG_TUD_MIDI2_NUM_GROUPS 1
+#endif
+
+#ifndef CFG_TUD_MIDI2_NUM_FUNCTION_BLOCKS
+ #define CFG_TUD_MIDI2_NUM_FUNCTION_BLOCKS 1
+#endif
+
+#ifndef CFG_TUD_MIDI2_EP_NAME
+ #define CFG_TUD_MIDI2_EP_NAME "TinyUSB MIDI 2.0"
+#endif
+
+#ifndef CFG_TUD_MIDI2_PRODUCT_ID
+ #define CFG_TUD_MIDI2_PRODUCT_ID "TinyUSB-MIDI2"
+#endif
+
+// String descriptor index for the Group Terminal Block (iBlockItem, Table 5-6).
+// 0 = no string descriptor (default, spec-allowed).
+#ifndef CFG_TUD_MIDI2_BLOCK_STRIDX
+ #define CFG_TUD_MIDI2_BLOCK_STRIDX 0
+#endif
+
+//--------------------------------------------------------------------+
+// MIDI Protocol Values (returned by tud_midi2_n_protocol)
+//--------------------------------------------------------------------+
+
+// Per USB-MIDI 2.0 spec, UMP Stream Configuration messages.
+enum {
+ MIDI_PROTOCOL_MIDI1 = 0x01,
+ MIDI_PROTOCOL_MIDI2 = 0x02,
+};
+
+//--------------------------------------------------------------------+
+// Application Callback API (weak, optional)
+//--------------------------------------------------------------------+
+void tud_midi2_rx_cb(uint8_t itf);
+void tud_midi2_set_itf_cb(uint8_t itf, uint8_t alt);
+bool tud_midi2_get_req_itf_cb(uint8_t rhport, const tusb_control_request_t* request);
+
+// Per-interface UMP Stream config (override for per-itf values).
+uint8_t tud_midi2_num_groups_cb(uint8_t itf);
+uint8_t tud_midi2_num_function_blocks_cb(uint8_t itf);
+const char* tud_midi2_ep_name_cb(uint8_t itf);
+const char* tud_midi2_product_id_cb(uint8_t itf);
+
+//--------------------------------------------------------------------+
+// Application API (Multiple Interfaces)
+//--------------------------------------------------------------------+
+
+bool tud_midi2_n_mounted(uint8_t itf);
+uint32_t tud_midi2_n_available(uint8_t itf);
+uint8_t tud_midi2_n_alt_setting(uint8_t itf);
+bool tud_midi2_n_negotiated(uint8_t itf);
+uint8_t tud_midi2_n_protocol(uint8_t itf);
+
+// Read up to max_words UMP words from the RX FIFO. Returns the number of
+// words actually read (0 if FIFO is empty).
+//
+// NOTE: this function returns when max_words is reached or when the FIFO is
+// empty, whichever comes first. Applications should invoke it in a loop
+// until it returns 0 to guarantee the RX FIFO is fully drained per
+// tud_midi2_rx_cb callback. Leaving words in the FIFO across callbacks can
+// prevent subsequent bulk OUT transfers from landing.
+uint32_t tud_midi2_n_ump_read(uint8_t itf, uint32_t* words, uint32_t max_words);
+uint32_t tud_midi2_n_ump_write(uint8_t itf, const uint32_t* words, uint32_t count);
+
+uint32_t tud_midi2_n_packet_read(uint8_t itf, uint8_t packets[], uint32_t max_packets);
+uint32_t tud_midi2_n_packet_write(uint8_t itf, const uint8_t packets[], uint32_t count);
+
+//--------------------------------------------------------------------+
+// Application API (Single Interface)
+//--------------------------------------------------------------------+
+TU_ATTR_ALWAYS_INLINE static inline bool tud_midi2_mounted(void) {
+ return tud_midi2_n_mounted(0);
+}
+
+TU_ATTR_ALWAYS_INLINE static inline uint32_t tud_midi2_available(void) {
+ return tud_midi2_n_available(0);
+}
+
+TU_ATTR_ALWAYS_INLINE static inline uint8_t tud_midi2_alt_setting(void) {
+ return tud_midi2_n_alt_setting(0);
+}
+
+TU_ATTR_ALWAYS_INLINE static inline bool tud_midi2_negotiated(void) {
+ return tud_midi2_n_negotiated(0);
+}
+
+TU_ATTR_ALWAYS_INLINE static inline uint8_t tud_midi2_protocol(void) {
+ return tud_midi2_n_protocol(0);
+}
+
+TU_ATTR_ALWAYS_INLINE static inline uint32_t
+tud_midi2_ump_read(uint32_t* words, uint32_t max_words) {
+ return tud_midi2_n_ump_read(0, words, max_words);
+}
+
+TU_ATTR_ALWAYS_INLINE static inline uint32_t
+tud_midi2_ump_write(const uint32_t* words, uint32_t count) {
+ return tud_midi2_n_ump_write(0, words, count);
+}
+
+TU_ATTR_ALWAYS_INLINE static inline uint32_t
+tud_midi2_packet_read(uint8_t packets[], uint32_t max_packets) {
+ return tud_midi2_n_packet_read(0, packets, max_packets);
+}
+
+TU_ATTR_ALWAYS_INLINE static inline uint32_t
+tud_midi2_packet_write(const uint8_t packets[], uint32_t count) {
+ return tud_midi2_n_packet_write(0, packets, count);
+}
+
+//--------------------------------------------------------------------+
+// Internal Class Driver API
+//--------------------------------------------------------------------+
+void midi2d_init(void);
+bool midi2d_deinit(void);
+void midi2d_reset(uint8_t rhport);
+uint16_t midi2d_open(uint8_t rhport, const tusb_desc_interface_t* itf_desc, uint16_t max_len);
+bool midi2d_control_xfer_cb(uint8_t rhport, uint8_t stage, const tusb_control_request_t* request);
+bool midi2d_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/class/midi/midi2_host.c b/src/class/midi/midi2_host.c
new file mode 100644
index 000000000..6d8861f4b
--- /dev/null
+++ b/src/class/midi/midi2_host.c
@@ -0,0 +1,635 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2026 Saulo Verissimo
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * This file is part of the TinyUSB stack.
+ */
+
+#include "tusb_option.h"
+
+#if (CFG_TUH_ENABLED && CFG_TUH_MIDI2)
+
+#include "host/usbh.h"
+#include "host/usbh_pvt.h"
+#include "midi2_host.h"
+
+#define TU_LOG_DRV(...) TU_LOG(CFG_TUH_MIDI2_LOG_LEVEL, __VA_ARGS__)
+
+//--------------------------------------------------------------------+
+// Weak stubs for application callbacks
+//--------------------------------------------------------------------+
+
+TU_ATTR_WEAK void tuh_midi2_descriptor_cb(uint8_t idx, const tuh_midi2_descriptor_cb_t *desc_cb_data) {
+ (void) idx; (void) desc_cb_data;
+}
+
+TU_ATTR_WEAK void tuh_midi2_mount_cb(uint8_t idx, const tuh_midi2_mount_cb_t *mount_cb_data) {
+ (void) idx; (void) mount_cb_data;
+}
+
+TU_ATTR_WEAK void tuh_midi2_rx_cb(uint8_t idx, uint32_t xferred_bytes) {
+ (void) idx; (void) xferred_bytes;
+}
+
+TU_ATTR_WEAK void tuh_midi2_tx_cb(uint8_t idx, uint32_t xferred_bytes) {
+ (void) idx; (void) xferred_bytes;
+}
+
+TU_ATTR_WEAK void tuh_midi2_umount_cb(uint8_t idx) {
+ (void) idx;
+}
+
+//--------------------------------------------------------------------+
+// Internal structure and state
+//--------------------------------------------------------------------+
+
+typedef struct {
+ uint8_t ep_addr;
+ uint16_t mps;
+ tu_fifo_t ff;
+ uint8_t* ep_buf;
+} midih2_tx_t;
+
+typedef struct {
+ uint8_t daddr;
+ uint8_t bInterfaceNumber;
+
+ uint8_t alt_setting_current;
+
+ uint8_t protocol_version;
+ uint8_t bcdMSC_hi, bcdMSC_lo;
+ uint8_t rx_cable_count_alt0;
+ uint8_t tx_cable_count_alt0;
+ uint8_t rx_cable_count_alt1;
+ uint8_t tx_cable_count_alt1;
+
+ struct {
+ midih2_tx_t tx;
+ tu_edpt_stream_t rx;
+
+ uint8_t rx_ff_buf[CFG_TUH_MIDI2_RX_BUFSIZE];
+ uint8_t tx_ff_buf[CFG_TUH_MIDI2_TX_BUFSIZE];
+ } ep_stream;
+
+ bool mounted;
+} midih2_interface_t;
+
+static midih2_interface_t _midi2_host[CFG_TUH_MIDI2];
+
+typedef struct {
+ TUH_EPBUF_DEF(tx, TUH_EPSIZE_BULK_MAX);
+ TUH_EPBUF_DEF(rx, TUH_EPSIZE_BULK_MAX);
+} midih2_epbuf_t;
+
+CFG_TUH_MEM_SECTION static midih2_epbuf_t _midi2_epbuf[CFG_TUH_MIDI2];
+
+//--------------------------------------------------------------------+
+// Helper functions
+//--------------------------------------------------------------------+
+
+static inline uint8_t find_new_midi2_index(void) {
+ for (uint8_t idx = 0; idx < CFG_TUH_MIDI2; idx++) {
+ if (_midi2_host[idx].daddr == 0) {
+ return idx;
+ }
+ }
+ return TUSB_INDEX_INVALID_8;
+}
+
+static inline uint8_t get_idx_by_ep_addr(uint8_t daddr, uint8_t ep_addr) {
+ for (uint8_t idx = 0; idx < CFG_TUH_MIDI2; idx++) {
+ const midih2_interface_t *p_midi = &_midi2_host[idx];
+ if ((p_midi->daddr == daddr) &&
+ (ep_addr == p_midi->ep_stream.rx.ep_addr || ep_addr == p_midi->ep_stream.tx.ep_addr)) {
+ return idx;
+ }
+ }
+ return TUSB_INDEX_INVALID_8;
+}
+
+static inline bool _tuh_tx_opened(const midih2_interface_t* p_midi) {
+ return p_midi->ep_stream.tx.ep_addr != 0;
+}
+
+static uint8_t _tuh_tx_byte_at(const tu_fifo_buffer_info_t* info, uint16_t offset) {
+ if (offset < info->linear.len) {
+ return info->linear.ptr[offset];
+ }
+ offset = (uint16_t)(offset - info->linear.len);
+ if (offset < info->wrapped.len) {
+ return info->wrapped.ptr[offset];
+ }
+ return 0;
+}
+
+// Largest byte count containing only whole UMP packets and fitting one xfer.
+static uint16_t _tuh_tx_nonseg_len_to_mps(midih2_tx_t* tx) {
+ tu_fifo_buffer_info_t info;
+ tu_fifo_get_read_info(&tx->ff, &info);
+
+ const uint16_t available = (uint16_t)(info.linear.len + info.wrapped.len);
+ uint16_t bytes = 0;
+
+ while (bytes < tx->mps) {
+ if ((uint16_t)(available - bytes) < 4) break;
+
+ uint8_t mt = (uint8_t)((_tuh_tx_byte_at(&info, (uint16_t)(bytes + 3)) >> 4) & 0x0F);
+ uint8_t pkt_words = midi2_ump_word_count(mt);
+ uint16_t pkt_bytes = (uint16_t)(pkt_words * 4);
+
+ if (pkt_bytes == 0) break;
+ if ((uint16_t)(available - bytes) < pkt_bytes) break;
+ if ((uint16_t)(bytes + pkt_bytes) > tx->mps) break;
+
+ bytes = (uint16_t)(bytes + pkt_bytes);
+ }
+
+ return bytes;
+}
+
+// Start one OUT transfer capped at mps. Returns bytes queued, or 0 if nothing.
+static uint16_t _tuh_tx_start_xfer(midih2_interface_t* p_midi) {
+ midih2_tx_t* tx = &p_midi->ep_stream.tx;
+ uint16_t ff_count = tu_fifo_count(&tx->ff);
+ if (ff_count == 0) return 0;
+ if (!usbh_edpt_claim(p_midi->daddr, tx->ep_addr)) return 0;
+
+ uint16_t bytes;
+ if (p_midi->alt_setting_current == 1) {
+ bytes = _tuh_tx_nonseg_len_to_mps(tx);
+ } else {
+ bytes = tu_min16(tu_fifo_count(&tx->ff), tx->mps);
+ }
+ if (bytes == 0) {
+ usbh_edpt_release(p_midi->daddr, tx->ep_addr);
+ return 0;
+ }
+
+ tu_fifo_read_n(&tx->ff, tx->ep_buf, bytes);
+ TU_ASSERT(usbh_edpt_xfer(p_midi->daddr, tx->ep_addr, tx->ep_buf, bytes), 0);
+ return bytes;
+}
+
+static uint32_t _tuh_tx_ump_write(midih2_interface_t* p_midi, const uint32_t* words, uint32_t count) {
+ uint32_t written = 0;
+ while (written < count) {
+ uint8_t mt = (uint8_t)((words[written] >> 28) & 0x0F);
+ uint8_t pkt_words = midi2_ump_word_count(mt);
+ uint16_t pkt_bytes = (uint16_t)(pkt_words * 4);
+
+ if (written + pkt_words > count) break;
+ if (tu_fifo_remaining(&p_midi->ep_stream.tx.ff) < pkt_bytes) break;
+ if (tu_fifo_write_n(&p_midi->ep_stream.tx.ff, &words[written], pkt_bytes) != pkt_bytes) break;
+ written += pkt_words;
+ }
+
+ (void) _tuh_tx_start_xfer(p_midi);
+ return written;
+}
+
+//--------------------------------------------------------------------+
+// Descriptor parsing
+//--------------------------------------------------------------------+
+
+// Parse Alt Setting 0 (MIDI 1.0) descriptors. Returns pointer past last consumed descriptor.
+static const uint8_t* midih2_parse_descriptors_alt0(midih2_interface_t *p_midi,
+ const tusb_desc_interface_t *desc_itf, const uint8_t *desc_end) {
+ TU_VERIFY(AUDIO_SUBCLASS_MIDI_STREAMING == desc_itf->bInterfaceSubClass, NULL);
+
+ p_midi->bInterfaceNumber = desc_itf->bInterfaceNumber;
+
+ const uint8_t *p_desc = (const uint8_t *) desc_itf;
+ p_desc = tu_desc_next(p_desc);
+
+ uint8_t rx_cable_count = 0;
+ uint8_t tx_cable_count = 0;
+ bool found_new_interface = false;
+
+ while (tu_desc_in_bounds(p_desc, desc_end) && !found_new_interface) {
+ switch (tu_desc_type(p_desc)) {
+ case TUSB_DESC_INTERFACE:
+ found_new_interface = true;
+ break;
+
+ case TUSB_DESC_ENDPOINT: {
+ const tusb_desc_endpoint_t *p_ep = (const tusb_desc_endpoint_t *) p_desc;
+
+ TU_ASSERT(tuh_edpt_open(p_midi->daddr, p_ep), NULL);
+ if (tu_edpt_dir(p_ep->bEndpointAddress) == TUSB_DIR_IN) {
+ tu_edpt_stream_open(&p_midi->ep_stream.rx, p_midi->daddr, p_ep, tu_edpt_packet_size(p_ep));
+ tu_edpt_stream_clear(&p_midi->ep_stream.rx);
+ } else {
+ p_midi->ep_stream.tx.ep_addr = p_ep->bEndpointAddress;
+ p_midi->ep_stream.tx.mps = tu_edpt_packet_size(p_ep);
+ tu_fifo_clear(&p_midi->ep_stream.tx.ff);
+ }
+
+ p_desc = tu_desc_next(p_desc);
+ if (tu_desc_in_bounds(p_desc, desc_end) && tu_desc_type(p_desc) == TUSB_DESC_CS_ENDPOINT) {
+ const midi_desc_cs_endpoint_t *p_csep = (const midi_desc_cs_endpoint_t *) p_desc;
+ if (tu_edpt_dir(p_ep->bEndpointAddress) == TUSB_DIR_OUT) {
+ tx_cable_count = p_csep->bNumEmbMIDIJack;
+ } else {
+ rx_cable_count = p_csep->bNumEmbMIDIJack;
+ }
+ }
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ if (!found_new_interface) {
+ p_desc = tu_desc_next(p_desc);
+ }
+ }
+
+ p_midi->rx_cable_count_alt0 = rx_cable_count;
+ p_midi->tx_cable_count_alt0 = tx_cable_count;
+ return p_desc;
+}
+
+// Parse Alt Setting 1 (MIDI 2.0 UMP) descriptors. Returns pointer past last consumed descriptor.
+static const uint8_t* midih2_parse_descriptors_alt1(midih2_interface_t *p_midi,
+ const tusb_desc_interface_t *desc_itf, const uint8_t *desc_end) {
+ TU_VERIFY(AUDIO_SUBCLASS_MIDI_STREAMING == desc_itf->bInterfaceSubClass, NULL);
+ TU_VERIFY(desc_itf->bAlternateSetting == 1, NULL);
+
+ const uint8_t *p_desc = (const uint8_t *) desc_itf;
+ p_desc = tu_desc_next(p_desc);
+
+ uint8_t rx_cable_count = 0;
+ uint8_t tx_cable_count = 0;
+ bool found_new_interface = false;
+
+ while (tu_desc_in_bounds(p_desc, desc_end) && !found_new_interface) {
+ switch (tu_desc_type(p_desc)) {
+ case TUSB_DESC_INTERFACE:
+ found_new_interface = true;
+ break;
+
+ case TUSB_DESC_CS_INTERFACE:
+ if (tu_desc_subtype(p_desc) == MIDI_CS_INTERFACE_HEADER) {
+ // bcdMSC at offset 3-4 in CS Interface Header
+ const uint8_t *bcd_ptr = p_desc + 3;
+ p_midi->bcdMSC_lo = bcd_ptr[0];
+ p_midi->bcdMSC_hi = bcd_ptr[1];
+ if (p_midi->bcdMSC_hi == 0x02) { // bcdMSC 0x0200 = USB-MIDI 2.0
+ p_midi->protocol_version = 1;
+ }
+ }
+ break;
+
+ case TUSB_DESC_ENDPOINT: {
+ const tusb_desc_endpoint_t *p_ep = (const tusb_desc_endpoint_t *) p_desc;
+ p_desc = tu_desc_next(p_desc);
+
+ if (tu_desc_in_bounds(p_desc, desc_end) && tu_desc_type(p_desc) == TUSB_DESC_CS_ENDPOINT) {
+ // MIDI 2.0 CS Endpoint General 2.0: bNumGrpTrmBlk at offset 3
+ if (p_desc[0] >= 4 && p_desc[2] == MIDI_CS_ENDPOINT_GENERAL_2_0) {
+ uint8_t num_grp_trm_blk = p_desc[3];
+ if (tu_edpt_dir(p_ep->bEndpointAddress) == TUSB_DIR_OUT) {
+ tx_cable_count = num_grp_trm_blk;
+ } else {
+ rx_cable_count = num_grp_trm_blk;
+ }
+ }
+ }
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ if (!found_new_interface) {
+ p_desc = tu_desc_next(p_desc);
+ }
+ }
+
+ p_midi->rx_cable_count_alt1 = rx_cable_count;
+ p_midi->tx_cable_count_alt1 = tx_cable_count;
+ return p_desc;
+}
+
+//--------------------------------------------------------------------+
+// Auto-selection logic
+//--------------------------------------------------------------------+
+
+static void midih2_auto_select_alt_setting(midih2_interface_t *p_midi) {
+ p_midi->alt_setting_current = 0;
+ if (p_midi->protocol_version == 1) {
+ p_midi->alt_setting_current = 1;
+ }
+}
+
+//--------------------------------------------------------------------+
+// Init/Deinit
+//--------------------------------------------------------------------+
+
+bool midih2_init(void) {
+ tu_memclr(&_midi2_host, sizeof(_midi2_host));
+ for (int inst = 0; inst < CFG_TUH_MIDI2; inst++) {
+ midih2_interface_t *p_midi = &_midi2_host[inst];
+
+ tu_edpt_stream_init(&p_midi->ep_stream.rx, true, false, false,
+ p_midi->ep_stream.rx_ff_buf, CFG_TUH_MIDI2_RX_BUFSIZE, _midi2_epbuf[inst].rx);
+
+ // TX uses raw tu_fifo + direct usbh_edpt_xfer (no FIFO wrapper) to preserve
+ // UMP packet boundaries across USB transfers.
+ midih2_tx_t* tx = &p_midi->ep_stream.tx;
+ (void) tu_fifo_config(&tx->ff, p_midi->ep_stream.tx_ff_buf, CFG_TUH_MIDI2_TX_BUFSIZE, false);
+ tx->ep_buf = _midi2_epbuf[inst].tx;
+ }
+ return true;
+}
+
+bool midih2_deinit(void) {
+ for (size_t i = 0; i < CFG_TUH_MIDI2; i++) {
+ midih2_interface_t* p_midi = &_midi2_host[i];
+ tu_edpt_stream_deinit(&p_midi->ep_stream.rx);
+ }
+ return true;
+}
+
+//--------------------------------------------------------------------+
+// Class driver callbacks
+//--------------------------------------------------------------------+
+
+uint16_t midih2_open(uint8_t rhport, uint8_t dev_addr, const tusb_desc_interface_t *desc_itf, uint16_t max_len) {
+ (void) rhport;
+
+ TU_VERIFY(TUSB_CLASS_AUDIO == desc_itf->bInterfaceClass, 0);
+
+ // For Alt Setting 1, reuse existing slot for same device+interface
+ uint8_t idx = TUSB_INDEX_INVALID_8;
+ if (desc_itf->bAlternateSetting > 0) {
+ for (uint8_t i = 0; i < CFG_TUH_MIDI2; i++) {
+ if (_midi2_host[i].daddr == dev_addr &&
+ _midi2_host[i].bInterfaceNumber == desc_itf->bInterfaceNumber) {
+ idx = i;
+ break;
+ }
+ }
+ }
+ if (idx == TUSB_INDEX_INVALID_8) {
+ idx = find_new_midi2_index();
+ }
+ TU_VERIFY(idx < CFG_TUH_MIDI2, 0);
+
+ midih2_interface_t *p_midi = &_midi2_host[idx];
+ p_midi->daddr = dev_addr;
+
+ const uint8_t *desc_start = (const uint8_t *) desc_itf;
+ const uint8_t *desc_end = desc_start + max_len;
+
+ // Skip Audio Control interface and any non-MIDI-Streaming descriptors
+ // (following midi_host.c pattern from Ha Thach)
+ if (AUDIO_SUBCLASS_CONTROL == desc_itf->bInterfaceSubClass) {
+ const uint8_t *p_desc = tu_desc_next((const uint8_t *)desc_itf);
+ // Skip CS_INTERFACE header
+ TU_VERIFY(tu_desc_type(p_desc) == TUSB_DESC_CS_INTERFACE, 0);
+ p_desc = tu_desc_next(p_desc);
+ desc_itf = (const tusb_desc_interface_t *) p_desc;
+ // Skip until we find MIDI Streaming interface
+ while (tu_desc_in_bounds(p_desc, desc_end) &&
+ (desc_itf->bDescriptorType != TUSB_DESC_INTERFACE ||
+ (desc_itf->bInterfaceClass == TUSB_CLASS_AUDIO &&
+ desc_itf->bInterfaceSubClass != AUDIO_SUBCLASS_MIDI_STREAMING))) {
+ p_desc = tu_desc_next(p_desc);
+ desc_itf = (const tusb_desc_interface_t *) p_desc;
+ }
+ TU_VERIFY(tu_desc_in_bounds(p_desc, desc_end), 0);
+ TU_VERIFY(TUSB_CLASS_AUDIO == desc_itf->bInterfaceClass, 0);
+ }
+
+ TU_VERIFY(AUDIO_SUBCLASS_MIDI_STREAMING == desc_itf->bInterfaceSubClass, 0);
+
+ TU_LOG_DRV("MIDI2 opening Interface %u Alt %u (addr = %u)\r\n",
+ desc_itf->bInterfaceNumber, desc_itf->bAlternateSetting, dev_addr);
+
+ // Dispatch to appropriate parser based on Alt Setting
+ const uint8_t *p_end = NULL;
+ if (desc_itf->bAlternateSetting == 0) {
+ p_end = midih2_parse_descriptors_alt0(p_midi, desc_itf, desc_end);
+ } else if (desc_itf->bAlternateSetting == 1) {
+ p_end = midih2_parse_descriptors_alt1(p_midi, desc_itf, desc_end);
+ }
+
+ // Return number of bytes consumed (following midi_host.c pattern)
+ uint16_t const parsed_len = (p_end != NULL) ? (uint16_t)(p_end - desc_start) : 0;
+ return parsed_len;
+}
+
+static void midih2_set_config_complete(midih2_interface_t *p_midi, uint8_t idx) {
+ uint8_t dev_addr = p_midi->daddr;
+
+ // Invoke descriptor_cb
+ tuh_midi2_descriptor_cb_t desc_cb = {
+ .protocol_version = p_midi->protocol_version,
+ .bcdMSC_hi = p_midi->bcdMSC_hi,
+ .bcdMSC_lo = p_midi->bcdMSC_lo,
+ .rx_cable_count = (p_midi->alt_setting_current == 0) ?
+ p_midi->rx_cable_count_alt0 : p_midi->rx_cable_count_alt1,
+ .tx_cable_count = (p_midi->alt_setting_current == 0) ?
+ p_midi->tx_cable_count_alt0 : p_midi->tx_cable_count_alt1,
+ };
+ tuh_midi2_descriptor_cb(idx, &desc_cb);
+
+ // Mark as mounted
+ TU_LOG_DRV("MIDI2 mounted addr = %u, alt = %u, protocol = %u\r\n",
+ dev_addr, p_midi->alt_setting_current, p_midi->protocol_version);
+ p_midi->mounted = true;
+
+ // Invoke mount_cb
+ tuh_midi2_mount_cb_t mount_cb = {
+ .daddr = dev_addr,
+ .bInterfaceNumber = p_midi->bInterfaceNumber,
+ .protocol_version = p_midi->protocol_version,
+ .alt_setting_active = p_midi->alt_setting_current,
+ .rx_cable_count = desc_cb.rx_cable_count,
+ .tx_cable_count = desc_cb.tx_cable_count,
+ };
+ tuh_midi2_mount_cb(idx, &mount_cb);
+
+ // Prepare RX transfer
+ tu_edpt_stream_read_xfer(&p_midi->ep_stream.rx);
+
+ // Signal USBH that configuration is complete
+ usbh_driver_set_config_complete(dev_addr, p_midi->bInterfaceNumber);
+}
+
+static void midih2_set_interface_cb(tuh_xfer_t *xfer) {
+ uint8_t const dev_addr = xfer->daddr;
+ uint8_t const itf_num = (uint8_t) tu_le16toh(xfer->setup->wIndex);
+
+ // Find our interface
+ for (uint8_t idx = 0; idx < CFG_TUH_MIDI2; idx++) {
+ if (_midi2_host[idx].daddr == dev_addr && _midi2_host[idx].bInterfaceNumber == itf_num) {
+ if (xfer->result == XFER_RESULT_SUCCESS) {
+ midih2_set_config_complete(&_midi2_host[idx], idx);
+ } else {
+ // SET_INTERFACE failed, fall back to alt 0
+ TU_LOG_DRV("MIDI2 SET_INTERFACE failed, falling back to alt 0\r\n");
+ _midi2_host[idx].alt_setting_current = 0;
+ midih2_set_config_complete(&_midi2_host[idx], idx);
+ }
+ return;
+ }
+ }
+}
+
+bool midih2_set_config(uint8_t dev_addr, uint8_t itf_num) {
+ uint8_t idx = 0;
+ for (idx = 0; idx < CFG_TUH_MIDI2; idx++) {
+ if (_midi2_host[idx].daddr == dev_addr && _midi2_host[idx].bInterfaceNumber == itf_num) {
+ break;
+ }
+ }
+
+ if (idx >= CFG_TUH_MIDI2) {
+ // Not our interface (e.g. Audio Control) - pass through to next
+ usbh_driver_set_config_complete(dev_addr, itf_num);
+ return true;
+ }
+
+ midih2_interface_t *p_midi = &_midi2_host[idx];
+
+ // Auto-select alt setting
+ midih2_auto_select_alt_setting(p_midi);
+
+ // If MIDI 2.0 detected, issue SET_INTERFACE to activate Alt Setting 1
+ if (p_midi->alt_setting_current == 1) {
+ TU_LOG_DRV("MIDI2 requesting SET_INTERFACE alt 1 for itf %u\r\n", itf_num);
+ TU_ASSERT(tuh_interface_set(dev_addr, itf_num, 1, midih2_set_interface_cb, 0));
+ } else {
+ // MIDI 1.0 only, complete immediately
+ midih2_set_config_complete(p_midi, idx);
+ }
+
+ return true;
+}
+
+void midih2_close(uint8_t dev_addr) {
+ for (uint8_t idx = 0; idx < CFG_TUH_MIDI2; idx++) {
+ midih2_interface_t *p_midi = &_midi2_host[idx];
+ if (p_midi->daddr == dev_addr) {
+ TU_LOG_DRV(" MIDI2 close addr = %u index = %u\r\n", dev_addr, idx);
+ tu_edpt_stream_close(&p_midi->ep_stream.rx);
+ tu_fifo_clear(&p_midi->ep_stream.tx.ff);
+ p_midi->ep_stream.tx.ep_addr = 0;
+ tuh_midi2_umount_cb(idx);
+ tu_memclr(p_midi, sizeof(midih2_interface_t));
+ }
+ }
+}
+
+bool midih2_xfer_cb(uint8_t dev_addr, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) {
+ uint8_t idx = get_idx_by_ep_addr(dev_addr, ep_addr);
+ TU_VERIFY(idx < CFG_TUH_MIDI2);
+
+ midih2_interface_t *p_midi = &_midi2_host[idx];
+ midih2_tx_t* ep_tx = &p_midi->ep_stream.tx;
+
+ if (ep_addr == p_midi->ep_stream.rx.ep_addr) {
+ if (result == XFER_RESULT_SUCCESS && xferred_bytes > 0) {
+ tu_edpt_stream_read_xfer_complete(&p_midi->ep_stream.rx, xferred_bytes);
+ tuh_midi2_rx_cb(idx, xferred_bytes);
+ }
+ tu_edpt_stream_read_xfer(&p_midi->ep_stream.rx);
+ } else if (ep_addr == ep_tx->ep_addr) {
+ tuh_midi2_tx_cb(idx, xferred_bytes);
+ if (result == XFER_RESULT_SUCCESS) {
+ uint16_t queued = _tuh_tx_start_xfer(p_midi);
+ // Send ZLP if no more data is queued but the last transfer was exactly mps
+ if (queued == 0 && tu_fifo_count(&ep_tx->ff) == 0 && xferred_bytes > 0 &&
+ (0 == (xferred_bytes & (ep_tx->mps - 1)))) {
+ if (usbh_edpt_claim(dev_addr, ep_tx->ep_addr)) {
+ usbh_edpt_xfer(dev_addr, ep_tx->ep_addr, NULL, 0);
+ }
+ }
+ }
+ }
+
+ return true;
+}
+
+//--------------------------------------------------------------------+
+// Public API
+//--------------------------------------------------------------------+
+
+bool tuh_midi2_mounted(uint8_t idx) {
+ TU_VERIFY(idx < CFG_TUH_MIDI2);
+ return _midi2_host[idx].mounted;
+}
+
+uint8_t tuh_midi2_get_protocol_version(uint8_t idx) {
+ TU_VERIFY(idx < CFG_TUH_MIDI2);
+ return _midi2_host[idx].protocol_version;
+}
+
+uint8_t tuh_midi2_get_alt_setting_active(uint8_t idx) {
+ TU_VERIFY(idx < CFG_TUH_MIDI2);
+ return _midi2_host[idx].alt_setting_current;
+}
+
+uint8_t tuh_midi2_get_cable_count(uint8_t idx) {
+ TU_VERIFY(idx < CFG_TUH_MIDI2);
+ return (_midi2_host[idx].alt_setting_current == 0) ?
+ _midi2_host[idx].rx_cable_count_alt0 : _midi2_host[idx].rx_cable_count_alt1;
+}
+
+uint32_t tuh_midi2_ump_read(uint8_t idx, uint32_t* words, uint32_t max_words) {
+ TU_VERIFY(idx < CFG_TUH_MIDI2 && words && max_words);
+
+ midih2_interface_t *p_midi = &_midi2_host[idx];
+ tu_edpt_stream_t *ep_rx = &p_midi->ep_stream.rx;
+
+ uint32_t n_words = 0;
+ for (uint32_t i = 0; i < max_words; i++) {
+ if (tu_edpt_stream_read_available(ep_rx) >= 4) {
+ tu_edpt_stream_read(ep_rx, (uint8_t *) &words[i], 4);
+ n_words++;
+ } else {
+ break;
+ }
+ }
+
+ return n_words;
+}
+
+uint32_t tuh_midi2_ump_write(uint8_t idx, const uint32_t* words, uint32_t count) {
+ TU_VERIFY(idx < CFG_TUH_MIDI2 && words && count);
+
+ midih2_interface_t *p_midi = &_midi2_host[idx];
+ TU_VERIFY(_tuh_tx_opened(p_midi), 0);
+
+ return _tuh_tx_ump_write(p_midi, words, count);
+}
+
+uint32_t tuh_midi2_write_flush(uint8_t idx) {
+ TU_VERIFY(idx < CFG_TUH_MIDI2);
+ return _tuh_tx_start_xfer(&_midi2_host[idx]);
+}
+
+#endif // CFG_TUH_ENABLED && CFG_TUH_MIDI2
diff --git a/src/class/midi/midi2_host.h b/src/class/midi/midi2_host.h
new file mode 100644
index 000000000..47039eb85
--- /dev/null
+++ b/src/class/midi/midi2_host.h
@@ -0,0 +1,107 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2026 Saulo Verissimo
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * This file is part of the TinyUSB stack.
+ */
+
+#ifndef TUSB_MIDI2_HOST_H_
+#define TUSB_MIDI2_HOST_H_
+
+#include "class/audio/audio.h"
+#include "midi.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+//--------------------------------------------------------------------+
+// Callback Type Definitions
+//--------------------------------------------------------------------+
+
+typedef struct {
+ uint8_t protocol_version; // 0 = MIDI 1.0 only, 1 = MIDI 2.0
+ uint8_t bcdMSC_hi, bcdMSC_lo; // MIDI version from descriptor
+ uint8_t rx_cable_count; // For both alt settings (same for Alt 0 and Alt 1)
+ uint8_t tx_cable_count;
+} tuh_midi2_descriptor_cb_t;
+
+typedef struct {
+ uint8_t daddr;
+ uint8_t bInterfaceNumber;
+ uint8_t protocol_version; // 0 = MIDI 1.0, 1 = MIDI 2.0
+ uint8_t alt_setting_active; // 0 or 1
+ uint8_t rx_cable_count;
+ uint8_t tx_cable_count;
+} tuh_midi2_mount_cb_t;
+
+//--------------------------------------------------------------------+
+// Application Callback API (weak, optional)
+//--------------------------------------------------------------------+
+
+void tuh_midi2_descriptor_cb(uint8_t idx, const tuh_midi2_descriptor_cb_t *desc_cb_data);
+void tuh_midi2_mount_cb(uint8_t idx, const tuh_midi2_mount_cb_t *mount_cb_data);
+void tuh_midi2_rx_cb(uint8_t idx, uint32_t xferred_bytes);
+void tuh_midi2_tx_cb(uint8_t idx, uint32_t xferred_bytes);
+void tuh_midi2_umount_cb(uint8_t idx);
+
+//--------------------------------------------------------------------+
+// Application API - Query
+//--------------------------------------------------------------------+
+
+bool tuh_midi2_mounted(uint8_t idx);
+uint8_t tuh_midi2_get_protocol_version(uint8_t idx);
+uint8_t tuh_midi2_get_alt_setting_active(uint8_t idx);
+uint8_t tuh_midi2_get_cable_count(uint8_t idx);
+
+//--------------------------------------------------------------------+
+// Application API - I/O
+//--------------------------------------------------------------------+
+
+// Read up to max_words UMP words from the RX FIFO. Returns the number of
+// words actually read (0 if FIFO is empty).
+//
+// NOTE: this function returns when max_words is reached or when the FIFO is
+// empty, whichever comes first. Applications should invoke it in a loop
+// until it returns 0 to guarantee the RX FIFO is fully drained per
+// tuh_midi2_rx_cb callback. Leaving words in the FIFO across callbacks can
+// prevent subsequent bulk IN transfers from landing.
+uint32_t tuh_midi2_ump_read(uint8_t idx, uint32_t* words, uint32_t max_words);
+uint32_t tuh_midi2_ump_write(uint8_t idx, const uint32_t* words, uint32_t count);
+uint32_t tuh_midi2_write_flush(uint8_t idx);
+
+//--------------------------------------------------------------------+
+// Internal Class Driver API
+//--------------------------------------------------------------------+
+
+bool midih2_init(void);
+bool midih2_deinit(void);
+bool midih2_set_config(uint8_t dev_addr, uint8_t itf_num);
+void midih2_close(uint8_t dev_addr);
+uint16_t midih2_open(uint8_t rhport, uint8_t dev_addr, const tusb_desc_interface_t *desc_itf, uint16_t max_len);
+bool midih2_xfer_cb(uint8_t dev_addr, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/class/midi/midi_host.h b/src/class/midi/midi_host.h
index b9ab0130d..8fdfd8966 100644
--- a/src/class/midi/midi_host.h
+++ b/src/class/midi/midi_host.h
@@ -150,6 +150,13 @@ uint32_t tuh_midi_stream_write(uint8_t idx, uint8_t cable_num, const uint8_t *p_
// Note that this function ignores the CIN field of the MIDI packet
// because a number of commercial devices out there do not encode
// it properly.
+//
+// NOTE: this function terminates when it encounters an event whose cable
+// number differs from the one being returned. Applications should invoke
+// it in a loop until it returns 0 (or until tuh_midi_read_available()
+// returns 0) to guarantee the stream FIFO is fully drained per callback.
+// Leaving bytes in the FIFO across callbacks can prevent subsequent bulk
+// IN transfers from landing.
uint32_t tuh_midi_stream_read(uint8_t idx, uint8_t *p_cable_num, uint8_t *p_buffer, uint16_t bufsize);
#endif
diff --git a/src/class/mtp/mtp_device.c b/src/class/mtp/mtp_device.c
index 59096e476..1f76dfcc7 100644
--- a/src/class/mtp/mtp_device.c
+++ b/src/class/mtp/mtp_device.c
@@ -321,7 +321,7 @@ bool mtpd_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t
.session_id = p_mtp->session_id,
.request = request,
.buf = p_mtp->control_buf,
- .bufsize = tu_le16toh(request->wLength),
+ .bufsize = request->wLength,
};
switch (request->bRequest) {
@@ -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/class/net/ecm_rndis_device.c b/src/class/net/ecm_rndis_device.c
index eaa82c187..643bcfbcd 100644
--- a/src/class/net/ecm_rndis_device.c
+++ b/src/class/net/ecm_rndis_device.c
@@ -48,9 +48,10 @@ typedef struct {
uint8_t itf_num; // Index number of Management Interface, +1 for Data Interface
uint8_t itf_data_alt; // Alternate setting of Data Interface. 0 : inactive, 1 : active
- uint8_t ep_notif;
uint8_t ep_in;
uint8_t ep_out;
+ uint16_t ep_size; // bulk endpoint max packet size (IN and OUT assumed equal)
+ uint8_t ep_notif;
bool ecm_mode;
@@ -81,6 +82,13 @@ CFG_TUD_MEM_SECTION static netd_epbuf_t _netd_epbuf;
static bool can_xmit;
static bool ecm_link_is_up = true; // Store link state for ECM mode
+//--------------------------------------------------------------------+
+// Weak stubs: invoked if no strong implementation is available
+//--------------------------------------------------------------------+
+TU_ATTR_WEAK void tud_network_set_packet_filter_cb(uint16_t packet_filter) {
+ (void) packet_filter;
+}
+
void tud_network_recv_renew(void) {
usbd_edpt_xfer(0, _netd_itf.ep_out, _netd_epbuf.rx, NETD_PACKET_SIZE, false);
}
@@ -176,6 +184,9 @@ uint16_t netd_open(uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint1
// Pair of endpoints
TU_ASSERT(TUSB_DESC_ENDPOINT == tu_desc_type(p_desc), 0);
+ // Save the actual bulk endpoint size (IN and OUT assumed equal)
+ _netd_itf.ep_size = tu_edpt_packet_size((tusb_desc_endpoint_t const *) p_desc);
+
if (_netd_itf.ecm_mode) {
// ECM by default is in-active, save the endpoint attribute
// to open later when received setInterface
@@ -206,14 +217,15 @@ static void ecm_report(bool nc) {
},
};
+ const uint32_t link_bps = (tud_speed_get() == TUSB_SPEED_HIGH) ? 480000000U : 12000000U;
const ecm_notify_t ecm_notify_csc = {
.header = {
.bmRequestType = 0xA1,
.bRequest = 0x2A, /* CONNECTION_SPEED_CHANGE aka ConnectionSpeedChange */
.wLength = 8,
},
- .downlink = 9728000,
- .uplink = 9728000,
+ .downlink = link_bps,
+ .uplink = link_bps,
};
ecm_notify_t notify = (nc) ? ecm_notify_nc : ecm_notify_csc;
@@ -285,6 +297,7 @@ bool netd_control_xfer_cb (uint8_t rhport, uint8_t stage, tusb_control_request_t
if (_netd_itf.ecm_mode) {
/* the only required CDC-ECM Management Element Request is SetEthernetPacketFilter */
if (0x43 /* SET_ETHERNET_PACKET_FILTER */ == request->bRequest) {
+ tud_network_set_packet_filter_cb(request->wValue);
tud_control_xfer(rhport, request, NULL, 0);
// Only send connection notification if link is up
if (ecm_link_is_up) {
@@ -356,8 +369,7 @@ bool netd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_
/* data transmission finished */
if (ep_addr == _netd_itf.ep_in) {
/* TinyUSB requires the class driver to implement ZLP (since ZLP usage is class-specific) */
-
- if (xferred_bytes && (0 == (xferred_bytes % CFG_TUD_NET_ENDPOINT_SIZE))) {
+ if (xferred_bytes > 0 && 0 == (xferred_bytes & (_netd_itf.ep_size-1))) {
do_in_xfer(NULL, 0); /* a ZLP is needed */
} else {
/* we're finally finished */
diff --git a/src/class/net/ncm.h b/src/class/net/ncm.h
index 8989fe0b4..27ff89b72 100644
--- a/src/class/net/ncm.h
+++ b/src/class/net/ncm.h
@@ -161,4 +161,18 @@ typedef struct {
uint32_t uplink;
} ncm_notify_t;
+typedef struct TU_ATTR_PACKED {
+ uint8_t bFunctionLength;
+ uint8_t bDescriptorType;
+ uint8_t bDescriptorSubType;
+ uint16_t bcdNcmVersion;
+ uint8_t bmCapabilities;
+} tusb_desc_cdc_ncm_func_t;
+
+typedef struct TU_ATTR_PACKED {
+ uint32_t dwNtbInMaxSize;
+ uint16_t wNtbInMaxDatagrams;
+ uint16_t wReserved;
+} ncm_ntb_input_size_t;
+
#endif
diff --git a/src/class/net/ncm_device.c b/src/class/net/ncm_device.c
index 405e4467b..e5f441300 100644
--- a/src/class/net/ncm_device.c
+++ b/src/class/net/ncm_device.c
@@ -83,6 +83,7 @@ typedef struct {
uint8_t itf_num; // interface number
uint8_t itf_data_alt; // ==0 -> no endpoints, i.e. no network traffic, ==1 -> normal operation with two endpoints (spec, chapter 5.3)
uint8_t rhport; // storage of \a rhport because some callbacks are done without it
+ uint16_t ep_size; // bulk endpoint max packet size (IN and OUT assumed equal)
// recv handling
recv_ntb_t *recv_free_ntb[RECV_NTB_N]; // free list of recv NTBs
@@ -118,6 +119,12 @@ typedef struct {
bool notification_xmit_is_running; // notification is currently transmitted
bool link_is_up; // current link state
+ // host-configured transmit limits
+ uint8_t bm_capabilities;
+ uint16_t xmit_max_ntb_size; // maximum NTB size device may send
+ uint16_t xmit_max_datagrams; // maximum datagrams per NTB device may send
+ ncm_ntb_input_size_t ntb_input_size;
+
// misc
bool tud_network_recv_renew_active; // tud_network_recv_renew() is active (avoid recursive invocations)
bool tud_network_recv_renew_process_again; // tud_network_recv_renew() should process again
@@ -138,6 +145,21 @@ typedef struct {
static ncm_interface_t ncm_interface;
CFG_TUD_MEM_SECTION static ncm_epbuf_t ncm_epbuf;
+//--------------------------------------------------------------------+
+// Weak stubs: invoked if no strong implementation is available
+//--------------------------------------------------------------------+
+TU_ATTR_WEAK void tud_network_set_packet_filter_cb(uint16_t packet_filter) {
+ (void) packet_filter;
+}
+
+TU_ATTR_WEAK bool tud_network_default_link_state_cb(void) {
+ #ifdef CFG_TUD_NCM_DEFAULT_LINK_UP
+ return CFG_TUD_NCM_DEFAULT_LINK_UP;
+ #else
+ return true;
+ #endif
+}
+
/**
* This is the NTB parameter structure
*
@@ -340,7 +362,8 @@ static xmit_ntb_t *xmit_get_next_ready_ntb(void) {
static bool xmit_insert_required_zlp(uint8_t rhport, uint32_t xferred_bytes) {
TU_LOG_DRV("xmit_insert_required_zlp(%d,%ld)\n", rhport, xferred_bytes);
- if (xferred_bytes == 0 || xferred_bytes % CFG_TUD_NET_ENDPOINT_SIZE != 0) {
+ uint16_t const ep_size = ncm_interface.ep_size;
+ if (xferred_bytes == 0 || (xferred_bytes & (ep_size-1)) != 0) {
return false;
}
@@ -408,10 +431,10 @@ static bool xmit_requested_datagram_fits_into_current_ntb(uint16_t datagram_size
if (ncm_interface.xmit_glue_ntb == NULL) {
return false;
}
- if (ncm_interface.xmit_glue_ntb_datagram_ndx >= CFG_TUD_NCM_IN_MAX_DATAGRAMS_PER_NTB) {
+ if (ncm_interface.xmit_glue_ntb_datagram_ndx >= ncm_interface.xmit_max_datagrams) {
return false;
}
- if (ncm_interface.xmit_glue_ntb->nth.wBlockLength + datagram_size + XMIT_ALIGN_OFFSET(datagram_size) > CFG_TUD_NCM_IN_NTB_MAX_SIZE) {
+ if (ncm_interface.xmit_glue_ntb->nth.wBlockLength + datagram_size + (uint32_t)XMIT_ALIGN_OFFSET(datagram_size) > (uint32_t)ncm_interface.xmit_max_ntb_size) {
return false;
}
return true;
@@ -708,7 +731,7 @@ static void recv_transfer_datagram_to_glue_logic(void) {
bool tud_network_can_xmit(uint16_t size) {
TU_LOG_DRV("tud_network_can_xmit(%d)\n", size);
- TU_ASSERT(size <= CFG_TUD_NCM_IN_NTB_MAX_SIZE - (sizeof(nth16_t) + sizeof(ndp16_t) + 2 * sizeof(ndp16_datagram_t)), false);
+ TU_ASSERT(size <= ncm_interface.xmit_max_ntb_size - (sizeof(nth16_t) + sizeof(ndp16_t) + 2 * sizeof(ndp16_datagram_t)), false);
if (xmit_requested_datagram_fits_into_current_ntb(size) || xmit_setup_next_glue_ntb()) {
// -> everything is fine
@@ -828,18 +851,16 @@ void netd_init(void) {
memset(&ncm_interface, 0, sizeof(ncm_interface));
+ ncm_interface.xmit_max_ntb_size = CFG_TUD_NCM_IN_NTB_MAX_SIZE;
+ ncm_interface.xmit_max_datagrams = CFG_TUD_NCM_IN_MAX_DATAGRAMS_PER_NTB;
+
for (int i = 0; i < XMIT_NTB_N; ++i) {
ncm_interface.xmit_free_ntb[i] = &ncm_epbuf.xmit[i].ntb;
}
for (int i = 0; i < RECV_NTB_N; ++i) {
ncm_interface.recv_free_ntb[i] = &ncm_epbuf.recv[i].ntb;
}
- // Default link state - can be configured via CFG_TUD_NCM_DEFAULT_LINK_UP
- #ifdef CFG_TUD_NCM_DEFAULT_LINK_UP
- ncm_interface.link_is_up = CFG_TUD_NCM_DEFAULT_LINK_UP;
- #else
- ncm_interface.link_is_up = true; // Default to link up if not set.
- #endif
+ ncm_interface.link_is_up = tud_network_default_link_state_cb();
} // netd_init
/**
@@ -878,10 +899,14 @@ uint16_t netd_open(uint8_t rhport, tusb_desc_interface_t const *itf_desc, uint16
ncm_interface.itf_num = itf_desc->bInterfaceNumber;// management interface
- // skip the two first entries and the following TUSB_DESC_CS_INTERFACE entries
uint16_t drv_len = sizeof(tusb_desc_interface_t);
uint8_t const *p_desc = tu_desc_next(itf_desc);
while (tu_desc_type(p_desc) == TUSB_DESC_CS_INTERFACE && drv_len <= max_len) {
+ if (tu_desc_subtype(p_desc) == CDC_FUNC_DESC_NCM) {
+ TU_ASSERT(tu_desc_len(p_desc) >= sizeof(tusb_desc_cdc_ncm_func_t), 0);
+ tusb_desc_cdc_ncm_func_t const *ncm_func = (tusb_desc_cdc_ncm_func_t const *) p_desc;
+ ncm_interface.bm_capabilities = ncm_func->bmCapabilities;
+ }
drv_len += tu_desc_len(p_desc);
p_desc = tu_desc_next(p_desc);
}
@@ -905,6 +930,7 @@ uint16_t netd_open(uint8_t rhport, tusb_desc_interface_t const *itf_desc, uint16
// a TUSB_DESC_ENDPOINT (actually two) must follow, open these endpoints
TU_ASSERT(tu_desc_type(p_desc) == TUSB_DESC_ENDPOINT, 0);
TU_ASSERT(usbd_open_edpt_pair(rhport, p_desc, 2, TUSB_XFER_BULK, &ncm_interface.ep_out, &ncm_interface.ep_in));
+ ncm_interface.ep_size = tu_edpt_packet_size((tusb_desc_endpoint_t const *) p_desc);
drv_len += 2 * sizeof(tusb_desc_endpoint_t);
return drv_len;
@@ -954,12 +980,12 @@ bool netd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_
* At startup transmission of notification packets are done here.
*/
bool netd_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const *request) {
- if (stage != CONTROL_STAGE_SETUP) {
- return true;
- }
switch (request->bmRequestType_bit.type) {
case TUSB_REQ_TYPE_STANDARD:
+ if (stage != CONTROL_STAGE_SETUP) {
+ return true;
+ }
switch (request->bRequest) {
case TUSB_REQ_GET_INTERFACE: {
@@ -993,16 +1019,75 @@ bool netd_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t
TU_VERIFY(ncm_interface.itf_num == request->wIndex, false);
switch (request->bRequest) {
case NCM_GET_NTB_PARAMETERS: {
+ if (stage != CONTROL_STAGE_SETUP) {
+ return true;
+ }
// transfer NTB parameters to host.
tud_control_xfer(rhport, request, (void *) (uintptr_t) &ntb_parameters, sizeof(ntb_parameters));
} break;
- // unsupported request
+ case NCM_SET_ETHERNET_PACKET_FILTER: {
+ if (stage != CONTROL_STAGE_SETUP) {
+ return true;
+ }
+
+ // Some hosts issue this request even if ETH_FILTER is not advertised,
+ // see https://bugzilla.kernel.org/show_bug.cgi?id=217290
+
+ tud_network_set_packet_filter_cb(request->wValue);
+ tud_control_xfer(rhport, request, NULL, 0);
+ } break;
+
+ case NCM_GET_NTB_INPUT_SIZE: {
+ if (stage != CONTROL_STAGE_SETUP) {
+ return true;
+ }
+
+ TU_VERIFY(request->wLength >=4, false);
+
+ uint8_t resp_len = (request->wLength >= 8 && (ncm_interface.bm_capabilities & NCM_NETWORK_CAPS_NTB_INPUT_SIZE)) ? 8 : 4;
+
+ ncm_ntb_input_size_t ntb_input_size = {
+ .dwNtbInMaxSize = ncm_interface.xmit_max_ntb_size,
+ .wNtbInMaxDatagrams = ncm_interface.xmit_max_datagrams
+ };
+ tud_control_xfer(rhport, request, &ntb_input_size, resp_len);
+ } break;
+
+ case NCM_SET_NTB_INPUT_SIZE: {
+ if (stage == CONTROL_STAGE_SETUP) {
+ /* wLength == 8 -> the NTB Input Size Structure (if NCM_NETWORK_CAPS_NTB_INPUT_SIZE is set)
+ wLength == 4 -> dwNtbInMaxSize field of the NTB Input Size Structure. */
+ TU_VERIFY(request->wLength == 4 || request->wLength == 8, false);
+ if (request->wLength == 8) {
+ TU_VERIFY(ncm_interface.bm_capabilities & NCM_NETWORK_CAPS_NTB_INPUT_SIZE, false);
+ }
+
+ tu_memclr(&ncm_interface.ntb_input_size, sizeof(ncm_interface.ntb_input_size));
+ tud_control_xfer(rhport, request, &ncm_interface.ntb_input_size, request->wLength);
+ } else if (stage == CONTROL_STAGE_DATA) {
+ /* CDC-NCM 1.0 Table 6-4, up to NTB16 size */
+ const uint32_t requested_size = ncm_interface.ntb_input_size.dwNtbInMaxSize;
+ if (requested_size < 2048u || requested_size > 65535u) {
+ return false;
+ }
+ ncm_interface.xmit_max_ntb_size = tu_min16(requested_size, CFG_TUD_NCM_IN_NTB_MAX_SIZE);
+
+ if (ncm_interface.ntb_input_size.wNtbInMaxDatagrams == 0 || ncm_interface.ntb_input_size.wNtbInMaxDatagrams > CFG_TUD_NCM_IN_MAX_DATAGRAMS_PER_NTB) {
+ ncm_interface.xmit_max_datagrams = CFG_TUD_NCM_IN_MAX_DATAGRAMS_PER_NTB;
+ } else {
+ ncm_interface.xmit_max_datagrams = ncm_interface.ntb_input_size.wNtbInMaxDatagrams;
+ }
+ }
+ } break;
+
+ // unsupported request
default:
return false;
}
break;
- // unsupported request
+
+ // unsupported request
default:
return false;
}
diff --git a/src/class/net/net_device.h b/src/class/net/net_device.h
index 96c03fd61..1ad069d92 100644
--- a/src/class/net/net_device.h
+++ b/src/class/net/net_device.h
@@ -35,9 +35,6 @@
#error "Cannot enable both ECM_RNDIS and NCM network drivers"
#endif
-/* declared here, NOT in usb_descriptors.c, so that the driver can intelligently ZLP as needed */
-#define CFG_TUD_NET_ENDPOINT_SIZE (TUD_OPT_HIGH_SPEED ? 512 : 64)
-
/* Maximum Transmission Unit (in bytes) of the network, including Ethernet header */
#ifndef CFG_TUD_NET_MTU
#define CFG_TUD_NET_MTU 1514
@@ -50,6 +47,16 @@ typedef enum
NCM_DATA_PROTOCOL_NETWORK_TRANSFER_BLOCK = 0x01
} ncm_data_interface_protocol_code_t;
+// Table 5.2 bmNetworkCapabilities bits
+typedef enum {
+ NCM_NETWORK_CAPS_NONE = 0x00,
+ NCM_NETWORK_CAPS_ETH_FILTER = (1 << 0),
+ NCM_NETWORK_CAPS_NET_ADDRESS = (1 << 1),
+ NCM_NETWORK_CAPS_ENCAP_COMMAND = (1 << 2),
+ NCM_NETWORK_CAPS_MAX_DATAGRAM_SIZE = (1 << 3),
+ NCM_NETWORK_CAPS_CRC_MODE = (1 << 4),
+ NCM_NETWORK_CAPS_NTB_INPUT_SIZE = (1 << 5)
+} ncm_network_capabilities_t;
#ifdef __cplusplus
extern "C" {
@@ -96,6 +103,13 @@ extern uint8_t tud_network_mac_address[6];
//------------- NCM -------------//
+// Optional callback: informs the application about host requested packet filter bits
+void tud_network_set_packet_filter_cb(uint16_t packet_filter);
+
+// Optional callback: called during netd_init() to get the initial link state.
+// Override to return the actual physical link state instead of the compile-time default.
+bool tud_network_default_link_state_cb(void);
+
// Set the network link state (up/down) and notify the host
void tud_network_link_state(uint8_t rhport, bool is_up);
diff --git a/src/class/printer/printer_device.c b/src/class/printer/printer_device.c
index d2dc9b163..158455fc9 100644
--- a/src/class/printer/printer_device.c
+++ b/src/class/printer/printer_device.c
@@ -41,7 +41,6 @@ typedef struct {
uint8_t itf_num;
/*------------- From this point, data is not cleared by bus reset -------------*/
-
tu_edpt_stream_t rx_stream;
tu_edpt_stream_t tx_stream;
diff --git a/src/class/video/video_device.c b/src/class/video/video_device.c
index bbcfe45d5..e31ab4194 100644
--- a/src/class/video/video_device.c
+++ b/src/class/video/video_device.c
@@ -865,7 +865,7 @@ static bool _open_vs_itf(uint8_t rhport, videod_streaming_interface_t *stm, uint
/* FS must be less than or equal to max packet size */
TU_VERIFY (tu_edpt_packet_size(ep) >= max_size);
#ifdef TUP_DCD_EDPT_ISO_ALLOC
- usbd_edpt_iso_activate(rhport, ep);
+ TU_ASSERT(usbd_edpt_iso_activate(rhport, ep));
#else
TU_ASSERT(usbd_edpt_open(rhport, ep));
#endif
diff --git a/src/common/tusb_compiler.h b/src/common/tusb_compiler.h
index f20834cea..a8971c3df 100644
--- a/src/common/tusb_compiler.h
+++ b/src/common/tusb_compiler.h
@@ -66,6 +66,9 @@
#define TU_LITTLE_ENDIAN (0x12u)
#define TU_BIG_ENDIAN (0x21u)
+#define TU_BITFIELD_LE (0x34u)
+#define TU_BITFIELD_BE (0x43u)
+
/*------------------------------------------------------------------*/
/* Count number of arguments of __VA_ARGS__
* - reference www.stackoverflow.com/questions/2124339/c-preprocessor-va-args-number-of-arguments
@@ -167,8 +170,10 @@
// For TI ARM compiler, __BYTE_ORDER__ is not defined for MSP430 but still LE
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ || defined(__MSP430__)
#define TU_BYTE_ORDER TU_LITTLE_ENDIAN
+ #define TU_BITFIELD_ORDER TU_BITFIELD_LE
#else
#define TU_BYTE_ORDER TU_BIG_ENDIAN
+ #define TU_BITFIELD_ORDER TU_BITFIELD_BE
#endif
// Unfortunately XC16 doesn't provide builtins for 32bit endian conversion
@@ -212,8 +217,10 @@
// Endian conversion use well-known host to network (big endian) naming
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
#define TU_BYTE_ORDER TU_LITTLE_ENDIAN
+ #define TU_BITFIELD_ORDER TU_BITFIELD_LE
#else
#define TU_BYTE_ORDER TU_BIG_ENDIAN
+ #define TU_BITFIELD_ORDER TU_BITFIELD_BE
#endif
#define TU_BSWAP16(u16) (__iar_builtin_REV16(u16))
@@ -239,8 +246,10 @@
// Endian conversion use well-known host to network (big endian) naming
#if defined(__LIT)
#define TU_BYTE_ORDER TU_LITTLE_ENDIAN
+ #define TU_BITFIELD_ORDER TU_BITFIELD_LE
#else
#define TU_BYTE_ORDER TU_BIG_ENDIAN
+ #define TU_BITFIELD_ORDER TU_BITFIELD_BE
#endif
#define TU_BSWAP16(u16) ((unsigned short)_builtin_revw((unsigned long)u16))
diff --git a/src/common/tusb_fifo.c b/src/common/tusb_fifo.c
index 06d25d131..a8ac99fd2 100644
--- a/src/common/tusb_fifo.c
+++ b/src/common/tusb_fifo.c
@@ -281,14 +281,14 @@ static void hwff_push_n(const tu_fifo_t *f, const void *app_buf, uint16_t n, uin
// Write full words to the linear part of the buffer
const uint8_t data_stride = access_mode->data_stride;
const uint32_t odd_mask = data_stride - 1;
- uint16_t lin_even = lin_bytes & ~odd_mask;
+ uint16_t lin_even = (uint16_t)(lin_bytes & ~odd_mask);
tu_hwfifo_read(hwfifo, ff_buf, lin_even, access_mode);
HWFIFO_ADDR_NEXT_N(hwfifo, const, lin_even * HWFIFO_ADDR_DATA_RATIO);
ff_buf += lin_even;
// There could be an odd 1 byte (16bit) or 1-3 bytes (32bit) before the wrap-around boundary
// combine it with the wrapped part to form a full word for data stride
- const uint8_t lin_odd = lin_bytes & odd_mask;
+ const uint8_t lin_odd = (uint8_t)(lin_bytes & odd_mask);
if (lin_odd > 0) {
const uint8_t wrap_odd = (uint8_t)tu_min16(wrap_bytes, data_stride - lin_odd);
uint8_t buf_temp[4];
@@ -338,13 +338,13 @@ static void hwff_pull_n(const tu_fifo_t *f, void *app_buf, uint16_t n, uint16_t
// Read full words from linear part
const uint8_t data_stride = access_mode->data_stride;
const uint32_t odd_mask = data_stride - 1;
- uint16_t lin_even = lin_bytes & ~odd_mask;
+ uint16_t lin_even = (uint16_t)(lin_bytes & ~odd_mask);
tu_hwfifo_write(hwfifo, ff_buf, lin_even, access_mode);
HWFIFO_ADDR_NEXT_N(hwfifo, , lin_even * HWFIFO_ADDR_DATA_RATIO);
ff_buf += lin_even;
// There could be odd 1 byte (16bit) or 1-3 bytes (32bit) before the wrap-around boundary
- const uint8_t lin_odd = lin_bytes & odd_mask;
+ const uint8_t lin_odd = (uint8_t)(lin_bytes & odd_mask);
if (lin_odd > 0) {
const uint8_t wrap_odd = (uint8_t)tu_min16(wrap_bytes, data_stride - lin_odd);
diff --git a/src/common/tusb_mcu.h b/src/common/tusb_mcu.h
index 77a0bbf1d..b5390a59d 100644
--- a/src/common/tusb_mcu.h
+++ b/src/common/tusb_mcu.h
@@ -177,12 +177,12 @@
#elif TU_CHECK_MCU(OPT_MCU_SAMG)
#define TUP_DCD_ENDPOINT_MAX 6
- #define TUD_ENDPOINT_ONE_DIRECTION_ONLY
+ #define CFG_TUD_ENDPOINT_ONE_DIRECTION_ONLY 1
#elif TU_CHECK_MCU(OPT_MCU_SAMX7X)
#define TUP_DCD_ENDPOINT_MAX 10
#define TUP_RHPORT_HIGHSPEED 1
- #define TUD_ENDPOINT_ONE_DIRECTION_ONLY
+ #define CFG_TUD_ENDPOINT_ONE_DIRECTION_ONLY 1
// Enable dcache if DMA is enabled
#define CFG_TUD_MEM_DCACHE_ENABLE_DEFAULT CFG_TUD_SAMX7X_DMA_ENABLE
@@ -190,11 +190,11 @@
#elif TU_CHECK_MCU(OPT_MCU_PIC32MZ)
#define TUP_DCD_ENDPOINT_MAX 8
- #define TUD_ENDPOINT_ONE_DIRECTION_ONLY
+ #define CFG_TUD_ENDPOINT_ONE_DIRECTION_ONLY 1
#elif TU_CHECK_MCU(OPT_MCU_PIC32MX, OPT_MCU_PIC32MM, OPT_MCU_PIC32MK) || TU_CHECK_MCU(OPT_MCU_PIC24, OPT_MCU_DSPIC33)
#define TUP_DCD_ENDPOINT_MAX 16
- #define TUD_ENDPOINT_ONE_DIRECTION_ONLY
+ #define CFG_TUD_ENDPOINT_ONE_DIRECTION_ONLY 1
#define TUP_DCD_EDPT_CLOSE_API
//--------------------------------------------------------------------+
@@ -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
@@ -411,7 +417,7 @@
#elif TU_CHECK_MCU(OPT_MCU_CXD56)
#define TUP_DCD_ENDPOINT_MAX 7
#define TUP_RHPORT_HIGHSPEED 1
- #define TUD_ENDPOINT_ONE_DIRECTION_ONLY
+ #define CFG_TUD_ENDPOINT_ONE_DIRECTION_ONLY 1
//--------------------------------------------------------------------+
// TI
@@ -455,7 +461,7 @@
#define CFG_TUSB_OS_INC_PATH_DEFAULT freertos/
// clang-format on
- #if CFG_TUSB_MCU == OPT_MCU_ESP32S3
+ #if CFG_TUSB_MCU == OPT_MCU_ESP32S3 || CFG_TUSB_MCU == OPT_MCU_ESP32H4
#define TUP_MCU_MULTIPLE_CORE 1
#endif
@@ -476,6 +482,22 @@
#define CFG_TUH_MEM_DCACHE_ENABLE_DEFAULT CFG_TUH_DWC2_DMA_ENABLE
#define CFG_TUSB_MEM_DCACHE_LINE_SIZE_DEFAULT 64
+#elif TU_CHECK_MCU(OPT_MCU_ESP32S31)
+ #define TUP_USBIP_DWC2
+ #define TUP_USBIP_DWC2_ESP32
+ #define TUP_RHPORT_HIGHSPEED 1
+ #define TUP_DCD_ENDPOINT_MAX 16
+
+ // clang-format off
+ #define CFG_TUSB_OS_INC_PATH_DEFAULT freertos/
+ // clang-format on
+
+ #define TUP_MCU_MULTIPLE_CORE 1
+
+ // Disable slave if DMA is enabled
+ #define CFG_TUD_DWC2_SLAVE_ENABLE_DEFAULT !CFG_TUD_DWC2_DMA_ENABLE
+ #define CFG_TUH_DWC2_SLAVE_ENABLE_DEFAULT !CFG_TUH_DWC2_DMA_ENABLE
+
#elif TU_CHECK_MCU(OPT_MCU_ESP32, OPT_MCU_ESP32C2, OPT_MCU_ESP32C3, OPT_MCU_ESP32C5, OPT_MCU_ESP32C6, \
OPT_MCU_ESP32C61, OPT_MCU_ESP32H2)
#if (CFG_TUD_ENABLED || !(defined(CFG_TUH_MAX3421) && CFG_TUH_MAX3421))
@@ -547,12 +569,12 @@
#elif TU_CHECK_MCU(OPT_MCU_FT90X)
#define TUP_DCD_ENDPOINT_MAX 8
#define TUP_RHPORT_HIGHSPEED 1
- #define TUD_ENDPOINT_ONE_DIRECTION_ONLY
+ #define CFG_TUD_ENDPOINT_ONE_DIRECTION_ONLY 1
#elif TU_CHECK_MCU(OPT_MCU_FT93X)
#define TUP_DCD_ENDPOINT_MAX 16
#define TUP_RHPORT_HIGHSPEED 1
- #define TUD_ENDPOINT_ONE_DIRECTION_ONLY
+ #define CFG_TUD_ENDPOINT_ONE_DIRECTION_ONLY 1
//--------------------------------------------------------------------+
// Allwinner
@@ -600,10 +622,6 @@
#define CFG_TUH_WCH_USBIP_USBFS 1
#endif
- #define TUP_USBIP_FSDEV
- #define TUP_USBIP_FSDEV_CH32
- #define CFG_TUSB_FSDEV_PMA_SIZE 512u
-
// default to FSDEV for device
#if !defined(CFG_TUD_WCH_USBIP_USBFS)
#define CFG_TUD_WCH_USBIP_USBFS 0
@@ -613,6 +631,12 @@
#define CFG_TUD_WCH_USBIP_FSDEV (CFG_TUD_WCH_USBIP_USBFS ? 0 : 1)
#endif
+ #if CFG_TUD_WCH_USBIP_FSDEV
+ #define TUP_USBIP_FSDEV
+ #define TUP_USBIP_FSDEV_CH32
+ #define CFG_TUSB_FSDEV_PMA_SIZE 512u
+ #endif
+
#define TUP_DCD_ENDPOINT_MAX 8
#elif TU_CHECK_MCU(OPT_MCU_CH32V307)
@@ -635,6 +659,19 @@
#define TUP_DCD_EDPT_CLOSE_API
#endif
+#elif TU_CHECK_MCU(OPT_MCU_CH583)
+ // CH582/583 USBFS: older WCH USBFS IP with a single combined per-endpoint control register
+ // (like CH32V103), driven by the shared dcd_ch32_usbfs.c on USB0 (rhport 0). Device only:
+ // the shared hcd_ch32_usbfs.c is CH32V20x-specific and does not support CH58x, so host /
+ // USB2 (rhport 1) is not provided here.
+ #define TUP_USBIP_WCH_USBFS
+
+ #ifndef CFG_TUD_WCH_USBIP_USBFS
+ #define CFG_TUD_WCH_USBIP_USBFS 1
+ #endif
+
+ #define TUP_DCD_ENDPOINT_MAX 8
+
//--------------------------------------------------------------------+
// Analog Devices
//--------------------------------------------------------------------+
@@ -643,7 +680,7 @@
#define TUP_USBIP_MUSB_ADI
#define TUP_DCD_ENDPOINT_MAX 12
#define TUP_RHPORT_HIGHSPEED 1
- #define TUD_ENDPOINT_ONE_DIRECTION_ONLY
+ #define CFG_TUD_ENDPOINT_ONE_DIRECTION_ONLY 1
//--------------------------------------------------------------------+
// ArteryTek
@@ -727,3 +764,14 @@
#ifndef TUP_DCD_EDPT_CLOSE_API
#define TUP_DCD_EDPT_ISO_ALLOC
#endif
+
+// Some USBIPs (SAMG, SAMX7X, PIC32, MAX3266x/MAX78002) cannot assign the same endpoint
+// number to both IN and OUT. Default to 0 (same endpoint number may be used for IN and OUT).
+#ifndef CFG_TUD_ENDPOINT_ONE_DIRECTION_ONLY
+ #define CFG_TUD_ENDPOINT_ONE_DIRECTION_ONLY 0
+#endif
+
+// Backward-compatible alias: legacy code only tests defined(TUD_ENDPOINT_ONE_DIRECTION_ONLY)
+#if CFG_TUD_ENDPOINT_ONE_DIRECTION_ONLY && !defined(TUD_ENDPOINT_ONE_DIRECTION_ONLY)
+ #define TUD_ENDPOINT_ONE_DIRECTION_ONLY
+#endif
diff --git a/src/common/tusb_private.h b/src/common/tusb_private.h
index 91d213755..a31bf7b03 100644
--- a/src/common/tusb_private.h
+++ b/src/common/tusb_private.h
@@ -46,17 +46,10 @@ extern tusb_role_t _tusb_rhport_role[TUP_USBIP_CONTROLLER_NUM];
// Endpoint
//--------------------------------------------------------------------+
-enum {
- TU_EDPT_STATE_BUSY = 0x01,
- TU_EDPT_STATE_STALLED = 0x02,
- TU_EDPT_STATE_CLAIMED = 0x04,
-};
-
-typedef struct TU_ATTR_PACKED {
- volatile uint8_t busy : 1;
- volatile uint8_t stalled : 1;
- volatile uint8_t claimed : 1;
-} tu_edpt_state_t;
+// Endpoint state bits — manipulate the bare uint8_t with these masks.
+#define TU_EDPT_STATE_BUSY 0x01u
+#define TU_EDPT_STATE_STALLED 0x02u
+#define TU_EDPT_STATE_CLAIMED 0x04u
typedef struct {
uint8_t hwid; // device: rhport, host: daddr
@@ -92,10 +85,10 @@ bool tu_bind_driver_to_ep_itf(uint8_t driver_id, uint8_t ep2drv[][2], uint8_t it
const uint8_t *p_desc, uint16_t desc_len);
// Claim an endpoint with provided mutex
-bool tu_edpt_claim(tu_edpt_state_t* ep_state, osal_mutex_t mutex);
+bool tu_edpt_claim(volatile uint8_t* ep_state, osal_mutex_t mutex);
// Release an endpoint with provided mutex
-bool tu_edpt_release(tu_edpt_state_t* ep_state, osal_mutex_t mutex);
+bool tu_edpt_release(volatile uint8_t* ep_state, osal_mutex_t mutex);
//--------------------------------------------------------------------+
// Endpoint Stream
diff --git a/src/common/tusb_types.h b/src/common/tusb_types.h
index a18f9feb7..cb06b89bb 100644
--- a/src/common/tusb_types.h
+++ b/src/common/tusb_types.h
@@ -100,12 +100,14 @@ typedef enum {
} tusb_xfer_type_t;
typedef enum {
- TUSB_DIR_OUT = 0,
- TUSB_DIR_IN = 1,
+ TUSB_DIR_OUT = 0u,
+ TUSB_DIR_IN = 1u,
+} tusb_dir_t;
- TUSB_EPNUM_MASK = 0x0F,
+enum {
+ TUSB_EPNUM_MASK = 0x0F,
TUSB_DIR_IN_MASK = 0x80
-} tusb_dir_t;
+};
enum {
TUSB_EPSIZE_BULK_FS = 64,
@@ -280,6 +282,7 @@ typedef enum {
XFER_RESULT_FAILED,
XFER_RESULT_STALLED,
XFER_RESULT_TIMEOUT,
+ XFER_RESULT_ABORTED,
XFER_RESULT_INVALID
} xfer_result_t;
@@ -319,6 +322,12 @@ enum {
TUSB_INDEX_INVALID_8 = 0xFF
};
+enum {
+ TU_EP0_OUT = 0x00,
+ TU_EP0_IN = 0x80
+};
+
+
//--------------------------------------------------------------------+
//
//--------------------------------------------------------------------+
@@ -409,10 +418,19 @@ typedef struct TU_ATTR_PACKED {
uint8_t bEndpointAddress ; // The address of the endpoint
struct TU_ATTR_PACKED {
+#if (TU_BITFIELD_ORDER == TU_BITFIELD_LE)
uint8_t xfer : 2; // Control, ISO, Bulk, Interrupt
uint8_t sync : 2; // None, Asynchronous, Adaptive, Synchronous
uint8_t usage : 2; // Data, Feedback, Implicit feedback
uint8_t : 2;
+#elif (TU_BITFIELD_ORDER == TU_BITFIELD_BE)
+ uint8_t : 2;
+ uint8_t usage : 2;
+ uint8_t sync : 2;
+ uint8_t xfer : 2;
+#else
+ #error "Please define TU_BITFIELD_ORDER as TU_BITFIELD_LE or TU_BITFIELD_BE"
+#endif
} bmAttributes;
uint16_t wMaxPacketSize ; // Bit 10..0 : max packet size, bit 12..11 additional transaction per highspeed micro-frame
@@ -522,9 +540,17 @@ typedef struct TU_ATTR_PACKED {
typedef struct TU_ATTR_PACKED {
union {
struct TU_ATTR_PACKED {
+#if (TU_BITFIELD_ORDER == TU_BITFIELD_LE)
uint8_t recipient : 5; ///< Recipient type tusb_request_recipient_t.
uint8_t type : 2; ///< Request type tusb_request_type_t.
uint8_t direction : 1; ///< Direction type. tusb_dir_t
+#elif (TU_BITFIELD_ORDER == TU_BITFIELD_BE)
+ uint8_t direction : 1; ///< Direction type. tusb_dir_t
+ uint8_t type : 2; ///< Request type tusb_request_type_t.
+ uint8_t recipient : 5; ///< Recipient type tusb_request_recipient_t.
+#else
+ #error "Please define TU_BITFIELD_ORDER as TU_BITFIELD_LE or TU_BITFIELD_BE"
+#endif
} bmRequestType_bit;
uint8_t bmRequestType;
diff --git a/src/device/dcd.h b/src/device/dcd.h
index 850c37bc2..f861eb258 100644
--- a/src/device/dcd.h
+++ b/src/device/dcd.h
@@ -219,6 +219,11 @@ TU_ATTR_ALWAYS_INLINE static inline void dcd_event_setup_received(uint8_t rhport
event.rhport = rhport;
event.event_id = DCD_EVENT_SETUP_RECEIVED;
(void) memcpy(&event.setup_received, setup, sizeof(tusb_control_request_t));
+ // USB wire format is little-endian. Convert multi-byte fields to host byte order
+ // so the stack always sees correct values regardless of CPU endianness.
+ event.setup_received.wValue = tu_le16toh(event.setup_received.wValue);
+ event.setup_received.wIndex = tu_le16toh(event.setup_received.wIndex);
+ event.setup_received.wLength = tu_le16toh(event.setup_received.wLength);
dcd_event_handler(&event, in_isr);
}
diff --git a/src/device/usbd.c b/src/device/usbd.c
index 3c14175f6..f87b63111 100644
--- a/src/device/usbd.c
+++ b/src/device/usbd.c
@@ -115,7 +115,20 @@ TU_ATTR_WEAK bool dcd_dcache_clean_invalidate(const void* addr, uint32_t data_si
//--------------------------------------------------------------------+
// Device Data
//--------------------------------------------------------------------+
+
+// Per-control-transfer state: populated at process_setup_received() entry,
+// consumed asynchronously by usbd_control_xfer_cb() when the EP0 transfer completes.
+typedef struct {
+ tusb_control_request_t request;
+ uint8_t* buffer;
+ uint16_t data_len;
+ uint16_t total_xferred;
+ usbd_control_xfer_cb_t complete_cb;
+} usbd_control_xfer_t;
+
typedef struct {
+ usbd_control_xfer_t ctrl_xfer;
+
// Note: these may share an enum state
volatile uint8_t connected;
volatile uint8_t addressed;
@@ -136,12 +149,16 @@ typedef struct {
uint8_t itf2drv[CFG_TUD_INTERFACE_MAX]; // map interface number to driver (0xff is invalid)
uint8_t ep2drv[CFG_TUD_ENDPPOINT_MAX][2]; // map endpoint to driver ( 0xff is invalid ), can use only 4-bit each
- tu_edpt_state_t ep_status[CFG_TUD_ENDPPOINT_MAX][2];
+ volatile uint8_t ep_status[CFG_TUD_ENDPPOINT_MAX][2];
} usbd_device_t;
static usbd_device_t _usbd_dev;
static volatile uint8_t _usbd_queued_setup;
+CFG_TUD_MEM_SECTION static struct {
+ TUD_EPBUF_DEF(buf, CFG_TUD_ENDPOINT0_BUFSIZE);
+} _ctrl_epbuf;
+
//--------------------------------------------------------------------+
// Class Driver
//--------------------------------------------------------------------+
@@ -237,6 +254,20 @@ static const usbd_class_driver_t _usbd_driver[] = {
},
#endif
+ #if CFG_TUD_MIDI2
+ {
+ .name = DRIVER_NAME("MIDI2"),
+ .init = midi2d_init,
+ .deinit = midi2d_deinit,
+ .open = midi2d_open,
+ .reset = midi2d_reset,
+ .control_xfer_cb = midi2d_control_xfer_cb,
+ .xfer_cb = midi2d_xfer_cb,
+ .xfer_isr = NULL,
+ .sof = NULL
+ },
+ #endif
+
#if CFG_TUD_VENDOR
{
.name = DRIVER_NAME("VENDOR"),
@@ -405,7 +436,8 @@ TU_ATTR_ALWAYS_INLINE static inline bool queue_event(dcd_event_t const * event,
//--------------------------------------------------------------------+
// Prototypes
//--------------------------------------------------------------------+
-static bool process_control_request(uint8_t rhport, tusb_control_request_t const * p_request);
+static bool usbd_control_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes);
+static bool process_setup_received(uint8_t rhport, tusb_control_request_t const * p_request);
static bool process_set_config(uint8_t rhport, uint8_t cfg_num);
static bool process_get_descriptor(uint8_t rhport, tusb_control_request_t const * p_request);
@@ -419,12 +451,6 @@ static bool process_test_mode_cb(uint8_t rhport, uint8_t stage, tusb_control_req
}
#endif
-// from usbd_control.c
-void usbd_control_reset(void);
-void usbd_control_set_request(tusb_control_request_t const *request);
-void usbd_control_set_complete_callback( usbd_control_xfer_cb_t fp );
-bool usbd_control_xfer_cb (uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t xferred_bytes);
-
//--------------------------------------------------------------------+
// Weak stubs: invoked if no strong implementation is available
//--------------------------------------------------------------------+
@@ -459,17 +485,6 @@ static char const *const _usbd_event_str[DCD_EVENT_COUNT] = {
"Func Call"
};
-// for usbd_control to print the name of control complete driver
-void usbd_driver_print_control_complete_name(usbd_control_xfer_cb_t callback) {
- for (uint8_t i = 0; i < TOTAL_DRIVER_COUNT; i++) {
- usbd_class_driver_t const* driver = get_driver(i);
- if (driver && driver->control_xfer_cb == callback) {
- TU_LOG_USBD("%s control complete\r\n", driver->name);
- return;
- }
- }
-}
-
#endif
//--------------------------------------------------------------------+
@@ -609,9 +624,7 @@ bool tud_deinit(uint8_t rhport) {
}
}
- // Clear device data
- tu_varclr(&_usbd_dev);
- usbd_control_reset();
+ tu_varclr(&_usbd_dev); // Clear device data
// Deinit device queue & task
osal_queue_delete(_usbd_q);
@@ -646,7 +659,6 @@ static void configuration_reset(uint8_t rhport) {
static void usbd_reset(uint8_t rhport) {
configuration_reset(rhport);
- usbd_control_reset();
}
bool tud_task_event_ready(void) {
@@ -713,7 +725,9 @@ void tud_task_ext(uint32_t timeout_ms, bool in_isr) {
break;
case DCD_EVENT_SETUP_RECEIVED:
- TU_ASSERT(_usbd_queued_setup > 0,);
+ if (_usbd_queued_setup == 0) {
+ break;
+ }
_usbd_queued_setup--;
TU_LOG_BUF(CFG_TUD_LOG_LEVEL, &event.setup_received, 8);
if (_usbd_queued_setup != 0) {
@@ -725,18 +739,16 @@ void tud_task_ext(uint32_t timeout_ms, bool in_isr) {
// But it is easier to set it every time instead of wasting time to check then set
_usbd_dev.connected = 1;
- // mark both in & out control as free
- _usbd_dev.ep_status[0][TUSB_DIR_OUT].busy = 0;
- _usbd_dev.ep_status[0][TUSB_DIR_OUT].claimed = 0;
- _usbd_dev.ep_status[0][TUSB_DIR_IN].busy = 0;
- _usbd_dev.ep_status[0][TUSB_DIR_IN].claimed = 0;
+ // reset ep state
+ _usbd_dev.ep_status[0][TUSB_DIR_OUT] = 0;
+ _usbd_dev.ep_status[0][TUSB_DIR_IN] = 0;
// Process control request
- if (!process_control_request(event.rhport, &event.setup_received)) {
+ if (!process_setup_received(event.rhport, &event.setup_received)) {
TU_LOG_USBD(" Stall EP0\r\n");
// Failed -> stall both control endpoint IN and OUT
- dcd_edpt_stall(event.rhport, 0);
- dcd_edpt_stall(event.rhport, 0 | TUSB_DIR_IN_MASK);
+ dcd_edpt_stall(event.rhport, TU_EP0_OUT);
+ dcd_edpt_stall(event.rhport, TU_EP0_IN);
}
break;
@@ -748,8 +760,8 @@ void tud_task_ext(uint32_t timeout_ms, bool in_isr) {
TU_LOG_USBD("on EP %02X with %u bytes\r\n", ep_addr, (unsigned int) event.xfer_complete.len);
- _usbd_dev.ep_status[epnum][ep_dir].busy = 0;
- _usbd_dev.ep_status[epnum][ep_dir].claimed = 0;
+ // Clear busy + claimed
+ _usbd_dev.ep_status[epnum][ep_dir] &= (uint8_t) ~(TU_EDPT_STATE_BUSY | TU_EDPT_STATE_CLAIMED);
if (0 == epnum) {
usbd_control_xfer_cb(event.rhport, ep_addr, (xfer_result_t) event.xfer_complete.result, event.xfer_complete.len);
@@ -809,25 +821,275 @@ void tud_task_ext(uint32_t timeout_ms, bool in_isr) {
}
//--------------------------------------------------------------------+
+// Control Endpoint
+//--------------------------------------------------------------------+
+
+// Weak hook: invoked when the control transfer's status stage completes
+TU_ATTR_WEAK void dcd_edpt0_status_complete(uint8_t rhport, const tusb_control_request_t* request) {
+ (void) rhport;
+ (void) request;
+}
+
+uint8_t* usbd_get_ctrl_buf(void) {
+ return _ctrl_epbuf.buf;
+}
+
+// Endpoint used for the Status stage of a control transfer.
+// Per USB 2.0 §9.3.1, when wLength == 0 the Direction bit is ignored and the Status
+// stage is always IN. Otherwise the Status stage is opposite of the Data stage direction.
+TU_ATTR_ALWAYS_INLINE static inline uint8_t status_stage_ep(const tusb_control_request_t* request) {
+ return (request->wLength != 0 && request->bmRequestType_bit.direction) ? TU_EP0_OUT : TU_EP0_IN;
+}
+
+// Queue ZLP status transaction
+TU_ATTR_ALWAYS_INLINE static inline bool status_stage_xact(uint8_t rhport, uint8_t ep_status) {
+ return usbd_edpt_xfer(rhport, ep_status, NULL, 0, false);
+}
+
+// Queue a transaction in Data Stage. Each transaction has up to Endpoint0's max
+// packet size. This function can also transfer a zero-length packet.
+static bool data_stage_xact(uint8_t rhport) {
+ usbd_control_xfer_t* const ctrl_xfer = &_usbd_dev.ctrl_xfer;
+ const uint16_t xact_len = tu_min16(ctrl_xfer->data_len - ctrl_xfer->total_xferred, CFG_TUD_ENDPOINT0_BUFSIZE);
+ uint8_t ep_addr = TU_EP0_OUT;
+
+ if (ctrl_xfer->request.bmRequestType_bit.direction == TUSB_DIR_IN) {
+ ep_addr = TU_EP0_IN;
+ if (0u != xact_len && ctrl_xfer->buffer != _ctrl_epbuf.buf) {
+ TU_VERIFY(0 == tu_memcpy_s(_ctrl_epbuf.buf, CFG_TUD_ENDPOINT0_BUFSIZE, ctrl_xfer->buffer, xact_len));
+ }
+ }
+
+ return usbd_edpt_xfer(rhport, ep_addr, xact_len ? _ctrl_epbuf.buf : NULL, xact_len, false);
+}
+
+// Status phase
+bool tud_control_status(uint8_t rhport, const tusb_control_request_t* request) {
+ // _usbd_dev.ctrl_xfer fields are pre-initialized at process_setup_received entry
+ (void) request;
+ return status_stage_xact(rhport, status_stage_ep(&_usbd_dev.ctrl_xfer.request));
+}
+
+// Transmit data to/from the control endpoint. If wLength is zero, a status packet is sent instead.
+bool tud_control_xfer(uint8_t rhport, const tusb_control_request_t* request, void* buffer, uint16_t len) {
+ // _usbd_dev.ctrl_xfer.request and reset fields are pre-initialized at process_setup_received entry
+ (void) request;
+ usbd_control_xfer_t* const ctrl_xfer = &_usbd_dev.ctrl_xfer;
+ ctrl_xfer->buffer = (uint8_t*) buffer;
+ ctrl_xfer->data_len = tu_min16(len, ctrl_xfer->request.wLength);
+
+ if (ctrl_xfer->request.wLength > 0U) {
+ if (ctrl_xfer->data_len > 0U) {
+ TU_ASSERT(buffer);
+ }
+ TU_ASSERT(data_stage_xact(rhport));
+ } else {
+ // wLength == 0: Status stage is always IN per USB 2.0 §9.3.1
+ TU_ASSERT(status_stage_xact(rhport, TU_EP0_IN));
+ }
+
+ return true;
+}
+
+// Callback when a transaction completes on the DATA stage or Status stage of EP0
+static bool usbd_control_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) {
+ (void) result;
+ usbd_control_xfer_t* const ctrl_xfer = &_usbd_dev.ctrl_xfer;
+
+ // Status Stage complete: ep_addr matches the resolved Status stage endpoint
+ uint8_t const ep_status = status_stage_ep(&ctrl_xfer->request);
+ if (ep_addr == ep_status) {
+ TU_ASSERT(0 == xferred_bytes);
+
+ // invoke optional dcd hook if available
+ dcd_edpt0_status_complete(rhport, &ctrl_xfer->request);
+
+ if (NULL != ctrl_xfer->complete_cb) {
+ ctrl_xfer->complete_cb(rhport, CONTROL_STAGE_ACK, &ctrl_xfer->request);
+ }
+
+ return true;
+ }
+
+ // 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);
+ }
+ TU_LOG_MEM(CFG_TUD_LOG_LEVEL, ctrl_xfer->buffer, xferred_bytes, 2);
+ }
+
+ ctrl_xfer->total_xferred += (uint16_t) xferred_bytes;
+ ctrl_xfer->buffer += xferred_bytes;
+
+ // Data Stage complete when wLength reached or short packet (incl. ZLP) seen
+ if ((ctrl_xfer->request.wLength == ctrl_xfer->total_xferred) ||
+ (xferred_bytes < CFG_TUD_ENDPOINT0_BUFSIZE)) {
+ bool is_ok = true;
+
+ if (NULL != ctrl_xfer->complete_cb) {
+ // Callback can still stall control in status phase, e.g. OUT data doesn't make sense
+ is_ok = ctrl_xfer->complete_cb(rhport, CONTROL_STAGE_DATA, &ctrl_xfer->request);
+ }
+
+ if (is_ok) {
+ TU_ASSERT(status_stage_xact(rhport, ep_status));
+ } else {
+ // Stall both IN and OUT control endpoint
+ dcd_edpt_stall(rhport, TU_EP0_OUT);
+ dcd_edpt_stall(rhport, TU_EP0_IN);
+ }
+ } else {
+ // More data to transfer
+ TU_ASSERT(data_stage_xact(rhport));
+ }
+
+ return true;
+}
+
+//--------------------------------------------------------------------+
// Control Request Parser & Handling
//--------------------------------------------------------------------+
// Helper to invoke class driver control request handler
static bool invoke_class_control(uint8_t rhport, usbd_class_driver_t const * driver, tusb_control_request_t const * request) {
- usbd_control_set_complete_callback(driver->control_xfer_cb);
+ _usbd_dev.ctrl_xfer.complete_cb = driver->control_xfer_cb;
TU_LOG_USBD(" %s control request\r\n", driver->name);
return driver->control_xfer_cb(rhport, CONTROL_STAGE_SETUP, request);
}
+// Process a standard request to the device recipient.
+static bool process_std_device_request(uint8_t rhport, tusb_control_request_t const * p_request) {
+ switch (p_request->bRequest) { //-V2520
+ case TUSB_REQ_SET_ADDRESS:
+ // Depending on mcu, status phase could be sent either before or after changing device address,
+ // or even require stack to not response with status at all
+ // Therefore DCD must take full responsibility to response and include zlp status packet if needed.
+ dcd_set_address(rhport, (uint8_t) p_request->wValue);
+ _usbd_dev.addressed = 1;
+ return true;
+
+ case TUSB_REQ_GET_CONFIGURATION: {
+ uint8_t cfg_num = _usbd_dev.cfg_num;
+ tud_control_xfer(rhport, p_request, &cfg_num, 1);
+ return true;
+ }
+
+ case TUSB_REQ_SET_CONFIGURATION: {
+ uint8_t const cfg_num = (uint8_t) p_request->wValue;
+
+ // Only process if new configure is different
+ if (_usbd_dev.cfg_num != cfg_num) {
+ if (_usbd_dev.cfg_num != 0) {
+ // already configured: need to clear all endpoints and driver first
+ TU_LOG_USBD(" Clear current Configuration (%u) before switching\r\n", _usbd_dev.cfg_num);
+
+ dcd_sof_enable(rhport, false);
+ dcd_edpt_close_all(rhport);
+
+ // close all drivers and current configured state except bus speed
+ const uint8_t speed = _usbd_dev.speed;
+ configuration_reset(rhport);
+
+ _usbd_dev.speed = speed; // restore speed
+ }
+
+ _usbd_dev.cfg_num = cfg_num;
+
+ // Handle the new configuration
+ if (cfg_num == 0) {
+ tud_umount_cb();
+ } else {
+ if (!process_set_config(rhport, cfg_num)) {
+ _usbd_dev.cfg_num = 0;
+ TU_ASSERT(false);
+ }
+ tud_mount_cb();
+ }
+ }
+
+ tud_control_status(rhport, p_request);
+ return true;
+ }
+
+ case TUSB_REQ_GET_DESCRIPTOR:
+ return process_get_descriptor(rhport, p_request);
+
+ case TUSB_REQ_SET_FEATURE:
+ switch (p_request->wValue) { //-V2520
+ case TUSB_REQ_FEATURE_REMOTE_WAKEUP:
+ TU_LOG_USBD(" Enable Remote Wakeup\r\n");
+ // Host may enable remote wake up before suspending especially HID device
+ _usbd_dev.remote_wakeup_en = 1;
+ tud_control_status(rhport, p_request);
+ return true;
+
+ #if CFG_TUD_TEST_MODE
+ case TUSB_REQ_FEATURE_TEST_MODE: {
+ // Only handle the test mode if supported and valid
+ TU_VERIFY(0 == tu_u16_low(p_request->wIndex));
+
+ uint8_t const selector = tu_u16_high(p_request->wIndex);
+ TU_VERIFY(TUSB_FEATURE_TEST_J <= selector && selector <= TUSB_FEATURE_TEST_FORCE_ENABLE);
+
+ _usbd_dev.ctrl_xfer.complete_cb = process_test_mode_cb;
+ tud_control_status(rhport, p_request);
+ return true;
+ }
+ #endif
+
+ // Stall unsupported feature selector
+ default: return false;
+ }
+
+ case TUSB_REQ_CLEAR_FEATURE:
+ // Only support remote wakeup for device feature
+ TU_VERIFY(TUSB_REQ_FEATURE_REMOTE_WAKEUP == p_request->wValue);
+ TU_LOG_USBD(" Disable Remote Wakeup\r\n");
+
+ // Host may disable remote wake up after resuming
+ _usbd_dev.remote_wakeup_en = 0;
+ tud_control_status(rhport, p_request);
+ return true;
+
+ case TUSB_REQ_GET_STATUS: {
+ // Device status bit mask
+ // - Bit 0: Self Powered TODO must invoke callback to get actual status
+ // - Bit 1: Remote Wakeup enabled
+ uint16_t status = (uint16_t) _usbd_dev.dev_state_bm;
+ tud_control_xfer(rhport, p_request, &status, 2);
+ return true;
+ }
+
+ default:
+ TU_BREAKPOINT();
+ return false;
+ }
+}
+
+
// This handles the actual request and its response.
// Returns false if unable to complete the request, causing caller to stall control endpoints.
-static bool process_control_request(uint8_t rhport, tusb_control_request_t const * p_request) {
- usbd_control_set_complete_callback(NULL);
+static bool process_setup_received(uint8_t rhport, tusb_control_request_t const * p_request) {
+ // Initialize control transfer state for this request. The request copy must be
+ // visible to usbd_control_xfer_cb when the (asynchronous) status ZLP completes,
+ // since the SETUP packet event has already gone out of scope by then.
+ usbd_control_xfer_t* const ctrl_xfer = &_usbd_dev.ctrl_xfer;
+ ctrl_xfer->request = *p_request;
+ ctrl_xfer->buffer = NULL;
+ ctrl_xfer->total_xferred = 0;
+ ctrl_xfer->data_len = 0;
+ ctrl_xfer->complete_cb = NULL;
+
+ p_request = &ctrl_xfer->request; // re-direct request pointer to internal copy (modifiable for hacking)
TU_ASSERT(p_request->bmRequestType_bit.type < TUSB_REQ_TYPE_INVALID);
// Vendor request
if ( p_request->bmRequestType_bit.type == TUSB_REQ_TYPE_VENDOR ) {
- usbd_control_set_complete_callback(tud_vendor_control_xfer_cb);
+ ctrl_xfer->complete_cb = tud_vendor_control_xfer_cb;
return tud_vendor_control_xfer_cb(rhport, CONTROL_STAGE_SETUP, p_request);
}
@@ -860,115 +1122,7 @@ static bool process_control_request(uint8_t rhport, tusb_control_request_t const
return false;
}
- switch (p_request->bRequest) { //-V2520
- case TUSB_REQ_SET_ADDRESS:
- // Depending on mcu, status phase could be sent either before or after changing device address,
- // or even require stack to not response with status at all
- // Therefore DCD must take full responsibility to response and include zlp status packet if needed.
- usbd_control_set_request(p_request); // set request since DCD has no access to tud_control_status() API
- dcd_set_address(rhport, (uint8_t) p_request->wValue);
- // skip tud_control_status()
- _usbd_dev.addressed = 1;
- break;
-
- case TUSB_REQ_GET_CONFIGURATION: {
- uint8_t cfg_num = _usbd_dev.cfg_num;
- tud_control_xfer(rhport, p_request, &cfg_num, 1);
- }
- break;
-
- case TUSB_REQ_SET_CONFIGURATION: {
- uint8_t const cfg_num = (uint8_t) p_request->wValue;
-
- // Only process if new configure is different
- if (_usbd_dev.cfg_num != cfg_num) {
- if (_usbd_dev.cfg_num != 0) {
- // already configured: need to clear all endpoints and driver first
- TU_LOG_USBD(" Clear current Configuration (%u) before switching\r\n", _usbd_dev.cfg_num);
-
- dcd_sof_enable(rhport, false);
- dcd_edpt_close_all(rhport);
-
- // close all drivers and current configured state except bus speed
- const uint8_t speed = _usbd_dev.speed;
- configuration_reset(rhport);
-
- _usbd_dev.speed = speed; // restore speed
- }
-
- _usbd_dev.cfg_num = cfg_num;
-
- // Handle the new configuration
- if (cfg_num == 0) {
- tud_umount_cb();
- } else {
- if (!process_set_config(rhport, cfg_num)) {
- _usbd_dev.cfg_num = 0;
- TU_ASSERT(false);
- }
- tud_mount_cb();
- }
- }
-
- tud_control_status(rhport, p_request);
- }
- break;
-
- case TUSB_REQ_GET_DESCRIPTOR:
- TU_VERIFY(process_get_descriptor(rhport, p_request));
- break;
-
- case TUSB_REQ_SET_FEATURE:
- switch(p_request->wValue) { //-V2520
- case TUSB_REQ_FEATURE_REMOTE_WAKEUP:
- TU_LOG_USBD(" Enable Remote Wakeup\r\n");
- // Host may enable remote wake up before suspending especially HID device
- _usbd_dev.remote_wakeup_en = 1;
- tud_control_status(rhport, p_request);
- break;
-
- #if CFG_TUD_TEST_MODE
- case TUSB_REQ_FEATURE_TEST_MODE: {
- // Only handle the test mode if supported and valid
- TU_VERIFY(0 == tu_u16_low(p_request->wIndex));
-
- uint8_t const selector = tu_u16_high(p_request->wIndex);
- TU_VERIFY(TUSB_FEATURE_TEST_J <= selector && selector <= TUSB_FEATURE_TEST_FORCE_ENABLE);
-
- usbd_control_set_complete_callback(process_test_mode_cb);
- tud_control_status(rhport, p_request);
- break;
- }
- #endif
-
- // Stall unsupported feature selector
- default: return false;
- }
- break;
-
- case TUSB_REQ_CLEAR_FEATURE:
- // Only support remote wakeup for device feature
- TU_VERIFY(TUSB_REQ_FEATURE_REMOTE_WAKEUP == p_request->wValue);
- TU_LOG_USBD(" Disable Remote Wakeup\r\n");
-
- // Host may disable remote wake up after resuming
- _usbd_dev.remote_wakeup_en = 0;
- tud_control_status(rhport, p_request);
- break;
-
- case TUSB_REQ_GET_STATUS: {
- // Device status bit mask
- // - Bit 0: Self Powered TODO must invoke callback to get actual status
- // - Bit 1: Remote Wakeup enabled
- uint16_t status = (uint16_t)_usbd_dev.dev_state_bm;
- tud_control_xfer(rhport, p_request, &status, 2);
- break;
- }
-
- // Unknown/Unsupported request
- default: TU_BREAKPOINT(); return false;
- }
- break;
+ return process_std_device_request(rhport, p_request);
//------------- Class/Interface Specific Request -------------//
case TUSB_REQ_RCPT_INTERFACE: {
@@ -1004,7 +1158,7 @@ static bool process_control_request(uint8_t rhport, tusb_control_request_t const
TU_VERIFY(TUSB_REQ_TYPE_STANDARD == p_request->bmRequestType_bit.type);
// Clear complete callback if driver set since it can also stall the request.
- usbd_control_set_complete_callback(NULL);
+ ctrl_xfer->complete_cb = NULL;
switch (p_request->bRequest) { //-V2520
case TUSB_REQ_GET_INTERFACE: {
@@ -1062,10 +1216,10 @@ static bool process_control_request(uint8_t rhport, tusb_control_request_t const
// STD request must always be ACKed regardless of driver returned value
// Also clear complete callback if driver set since it can also stall the request.
(void) invoke_class_control(rhport, driver, p_request);
- usbd_control_set_complete_callback(NULL);
+ ctrl_xfer->complete_cb = NULL;
// skip ZLP status if driver already did that
- if (!_usbd_dev.ep_status[0][TUSB_DIR_IN].busy) {
+ if (!(_usbd_dev.ep_status[0][TUSB_DIR_IN] & TU_EDPT_STATE_BUSY)) {
tud_control_status(rhport, p_request);
}
}
@@ -1144,8 +1298,7 @@ static bool process_set_config(uint8_t rhport, uint8_t cfg_num) {
}
// return descriptor's buffer and update desc_len
-static bool process_get_descriptor(uint8_t rhport, tusb_control_request_t const * p_request)
-{
+static bool process_get_descriptor(uint8_t rhport, tusb_control_request_t const * p_request) {
tusb_desc_type_t const desc_type = (tusb_desc_type_t) tu_u16_high(p_request->wValue);
uint8_t const desc_index = tu_u16_low( p_request->wValue );
@@ -1153,20 +1306,18 @@ static bool process_get_descriptor(uint8_t rhport, tusb_control_request_t const
case TUSB_DESC_DEVICE: {
TU_LOG_USBD(" Device\r\n");
- void* desc_device = (void*) (uintptr_t) tud_descriptor_device_cb();
+ void *desc_device = (void *)(uintptr_t)tud_descriptor_device_cb();
TU_ASSERT(desc_device);
// Only response with exactly 1 Packet if: not addressed and host requested more data than device descriptor has.
// This only happens with the very first get device descriptor and EP0 size = 8 or 16.
if ((CFG_TUD_ENDPOINT0_SIZE < sizeof(tusb_desc_device_t)) && !_usbd_dev.addressed &&
- ((tusb_control_request_t const*) p_request)->wLength > sizeof(tusb_desc_device_t)) {
+ p_request->wLength > sizeof(tusb_desc_device_t)) {
// Hack here: we modify the request length to prevent usbd_control response with zlp
// since we are responding with 1 packet & less data than wLength.
- tusb_control_request_t mod_request = *p_request;
- mod_request.wLength = CFG_TUD_ENDPOINT0_SIZE;
-
- return tud_control_xfer(rhport, &mod_request, desc_device, CFG_TUD_ENDPOINT0_SIZE);
- }else {
+ ((tusb_control_request_t *)(uintptr_t)p_request)->wLength = CFG_TUD_ENDPOINT0_SIZE;
+ return tud_control_xfer(rhport, p_request, desc_device, CFG_TUD_ENDPOINT0_SIZE);
+ } else {
return tud_control_xfer(rhport, p_request, desc_device, sizeof(tusb_desc_device_t));
}
}
@@ -1212,7 +1363,7 @@ static bool process_get_descriptor(uint8_t rhport, tusb_control_request_t const
TU_LOG_USBD(" String[%u]\r\n", desc_index);
// String Descriptor always uses the desc set from user
- uint8_t const* desc_str = (uint8_t const*) tud_descriptor_string_cb(desc_index, tu_le16toh(p_request->wIndex));
+ uint8_t const* desc_str = (uint8_t const*) tud_descriptor_string_cb(desc_index, p_request->wIndex);
TU_VERIFY(desc_str);
// first byte of descriptor is its size
@@ -1305,15 +1456,15 @@ TU_ATTR_FAST_FUNC void dcd_event_handler(dcd_event_t const* event, bool in_isr)
usbd_class_driver_t const* driver = get_driver(_usbd_dev.ep2drv[epnum][ep_dir]);
if (driver && driver->xfer_isr) {
- _usbd_dev.ep_status[epnum][ep_dir].busy = 0;
- _usbd_dev.ep_status[epnum][ep_dir].claimed = 0;
+ // Clear busy + claimed
+ _usbd_dev.ep_status[epnum][ep_dir] &= (uint8_t) ~(TU_EDPT_STATE_BUSY | TU_EDPT_STATE_CLAIMED);
send = !driver->xfer_isr(event->rhport, ep_addr, (xfer_result_t) event->xfer_complete.result, event->xfer_complete.len);
// xfer_isr() is deferred to xfer_cb(), revert busy/claimed status
if (send) {
- _usbd_dev.ep_status[epnum][ep_dir].busy = 1;
- _usbd_dev.ep_status[epnum][ep_dir].claimed = 1;
+ // set busy + claimed
+ _usbd_dev.ep_status[epnum][ep_dir] |= (TU_EDPT_STATE_BUSY | TU_EDPT_STATE_CLAIMED);
}
}
}
@@ -1403,9 +1554,7 @@ bool usbd_edpt_claim(uint8_t rhport, uint8_t ep_addr) {
uint8_t const epnum = tu_edpt_number(ep_addr);
uint8_t const dir = tu_edpt_dir(ep_addr);
- tu_edpt_state_t* ep_state = &_usbd_dev.ep_status[epnum][dir];
-
- return tu_edpt_claim(ep_state, _usbd_mutex);
+ return tu_edpt_claim(&_usbd_dev.ep_status[epnum][dir], _usbd_mutex);
}
bool usbd_edpt_release(uint8_t rhport, uint8_t ep_addr) {
@@ -1413,9 +1562,7 @@ bool usbd_edpt_release(uint8_t rhport, uint8_t ep_addr) {
uint8_t const epnum = tu_edpt_number(ep_addr);
uint8_t const dir = tu_edpt_dir(ep_addr);
- tu_edpt_state_t* ep_state = &_usbd_dev.ep_status[epnum][dir];
-
- return tu_edpt_release(ep_state, _usbd_mutex);
+ return tu_edpt_release(&_usbd_dev.ep_status[epnum][dir], _usbd_mutex);
}
bool usbd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t* buffer, uint16_t total_bytes, bool is_isr) {
@@ -1435,18 +1582,17 @@ bool usbd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t* buffer, uint16_t t
#endif
// Attempt to transfer on a busy endpoint, sound like an race condition !
- TU_ASSERT(_usbd_dev.ep_status[epnum][dir].busy == 0);
+ TU_ASSERT((_usbd_dev.ep_status[epnum][dir] & TU_EDPT_STATE_BUSY) == 0);
// Set busy first since the actual transfer can be complete before dcd_edpt_xfer()
// could return and USBD task can preempt and clear the busy
- _usbd_dev.ep_status[epnum][dir].busy = 1;
+ _usbd_dev.ep_status[epnum][dir] |= TU_EDPT_STATE_BUSY;
if (dcd_edpt_xfer(rhport, ep_addr, buffer, total_bytes, is_isr)) {
return true;
} else {
// DCD error, mark endpoint as ready to allow next transfer
- _usbd_dev.ep_status[epnum][dir].busy = 0;
- _usbd_dev.ep_status[epnum][dir].claimed = 0;
+ _usbd_dev.ep_status[epnum][dir] &= (uint8_t) ~(TU_EDPT_STATE_BUSY | TU_EDPT_STATE_CLAIMED);
TU_LOG_USBD("FAILED\r\n");
TU_BREAKPOINT();
return false;
@@ -1467,19 +1613,18 @@ bool usbd_edpt_xfer_fifo(uint8_t rhport, uint8_t ep_addr, tu_fifo_t* ff, uint16_
TU_LOG_USBD(" Queue FIFO EP %02X with %u bytes ... ", ep_addr, total_bytes);
// Attempt to transfer on a busy endpoint, sound like a race condition !
- TU_ASSERT(_usbd_dev.ep_status[epnum][dir].busy == 0);
+ TU_ASSERT((_usbd_dev.ep_status[epnum][dir] & TU_EDPT_STATE_BUSY) == 0);
// Set busy first since the actual transfer can be complete before dcd_edpt_xfer() could return
// and usbd task can preempt and clear the busy
- _usbd_dev.ep_status[epnum][dir].busy = 1;
+ _usbd_dev.ep_status[epnum][dir] |= TU_EDPT_STATE_BUSY;
if (dcd_edpt_xfer_fifo(rhport, ep_addr, ff, total_bytes, is_isr)) {
TU_LOG_USBD("OK\r\n");
return true;
} else {
// DCD error, mark endpoint as ready to allow next transfer
- _usbd_dev.ep_status[epnum][dir].busy = 0;
- _usbd_dev.ep_status[epnum][dir].claimed = 0;
+ _usbd_dev.ep_status[epnum][dir] &= (uint8_t) ~(TU_EDPT_STATE_BUSY | TU_EDPT_STATE_CLAIMED);
TU_LOG_USBD("failed\r\n");
TU_BREAKPOINT();
return false;
@@ -1500,7 +1645,7 @@ bool usbd_edpt_busy(uint8_t rhport, uint8_t ep_addr) {
uint8_t const epnum = tu_edpt_number(ep_addr);
uint8_t const dir = tu_edpt_dir(ep_addr);
- return _usbd_dev.ep_status[epnum][dir].busy;
+ return (_usbd_dev.ep_status[epnum][dir] & TU_EDPT_STATE_BUSY) != 0;
}
void usbd_edpt_stall(uint8_t rhport, uint8_t ep_addr) {
@@ -1512,8 +1657,7 @@ void usbd_edpt_stall(uint8_t rhport, uint8_t ep_addr) {
// only stalled if currently cleared
TU_LOG_USBD(" Stall EP %02X\r\n", ep_addr);
dcd_edpt_stall(rhport, ep_addr);
- _usbd_dev.ep_status[epnum][dir].stalled = 1;
- _usbd_dev.ep_status[epnum][dir].busy = 1;
+ _usbd_dev.ep_status[epnum][dir] |= (TU_EDPT_STATE_STALLED | TU_EDPT_STATE_BUSY);
}
void usbd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr) {
@@ -1525,8 +1669,7 @@ void usbd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr) {
// only clear if currently stalled
TU_LOG_USBD(" Clear Stall EP %02X\r\n", ep_addr);
dcd_edpt_clear_stall(rhport, ep_addr);
- _usbd_dev.ep_status[epnum][dir].stalled = 0;
- _usbd_dev.ep_status[epnum][dir].busy = 0;
+ _usbd_dev.ep_status[epnum][dir] &= (uint8_t) ~(TU_EDPT_STATE_STALLED | TU_EDPT_STATE_BUSY);
}
bool usbd_edpt_stalled(uint8_t rhport, uint8_t ep_addr) {
@@ -1535,7 +1678,7 @@ bool usbd_edpt_stalled(uint8_t rhport, uint8_t ep_addr) {
uint8_t const epnum = tu_edpt_number(ep_addr);
uint8_t const dir = tu_edpt_dir(ep_addr);
- return _usbd_dev.ep_status[epnum][dir].stalled;
+ return (_usbd_dev.ep_status[epnum][dir] & TU_EDPT_STATE_STALLED) != 0;
}
/**
@@ -1555,9 +1698,7 @@ void usbd_edpt_close(uint8_t rhport, uint8_t ep_addr) {
uint8_t const dir = tu_edpt_dir(ep_addr);
dcd_edpt_close(rhport, ep_addr);
- _usbd_dev.ep_status[epnum][dir].stalled = 0;
- _usbd_dev.ep_status[epnum][dir].busy = 0;
- _usbd_dev.ep_status[epnum][dir].claimed = 0;
+ _usbd_dev.ep_status[epnum][dir] = 0;
#endif
return;
@@ -1602,9 +1743,7 @@ bool usbd_edpt_iso_activate(uint8_t rhport, tusb_desc_endpoint_t const* desc_ep)
TU_ASSERT(epnum < CFG_TUD_ENDPPOINT_MAX);
TU_ASSERT(tu_edpt_validate(desc_ep, (tusb_speed_t)_usbd_dev.speed));
- _usbd_dev.ep_status[epnum][dir].stalled = 0;
- _usbd_dev.ep_status[epnum][dir].busy = 0;
- _usbd_dev.ep_status[epnum][dir].claimed = 0;
+ _usbd_dev.ep_status[epnum][dir] = 0;
return dcd_edpt_iso_activate(rhport, desc_ep);
#else
(void) rhport; (void) desc_ep;
diff --git a/src/device/usbd.h b/src/device/usbd.h
index d3a6dccbb..abce5a887 100644
--- a/src/device/usbd.h
+++ b/src/device/usbd.h
@@ -425,6 +425,43 @@ bool tud_vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_requ
TUD_MIDI_JACKID_OUT_EMB(1)
//--------------------------------------------------------------------+
+// MIDI 2.0 Descriptor Templates (USB-MIDI 2.0)
+//--------------------------------------------------------------------+
+
+// Alt Setting 1: MS Interface + MS Header (bcdMSC=0x0200)
+// Per USB-MIDI 2.0 Table 5-2: wTotalLength in the MS Header is not used in 2.0
+// and shall be set to match bLength (= 0x0007) for conformity with USB-MIDI 1.0.
+#define TUD_MIDI2_DESC_ALT1_HEAD_LEN (9 + 7)
+#define TUD_MIDI2_DESC_ALT1_HEAD(_itfnum, _stridx) \
+ /* MIDI Streaming Interface, Alt Setting 1 */\
+ 9, TUSB_DESC_INTERFACE, (uint8_t)((_itfnum) + 1), 1, 2, TUSB_CLASS_AUDIO, AUDIO_SUBCLASS_MIDI_STREAMING, AUDIO_FUNC_PROTOCOL_CODE_UNDEF, 0,\
+ /* MS Header (MIDI 2.0): wTotalLength = bLength per spec */\
+ 7, TUSB_DESC_CS_INTERFACE, MIDI_CS_INTERFACE_HEADER, U16_TO_U8S_LE(0x0200), U16_TO_U8S_LE(0x0007)
+
+// Alt Setting 1: Standard USB Endpoint (7 bytes) + CS Endpoint General 2.0
+#define TUD_MIDI2_DESC_ALT1_EP_LEN(_numgtbs) (7 + 4 + (_numgtbs))
+#define TUD_MIDI2_DESC_ALT1_EP(_ep, _epsize, _numgtbs, ...) \
+ 7, TUSB_DESC_ENDPOINT, _ep, TUSB_XFER_BULK, U16_TO_U8S_LE(_epsize), 0, \
+ (uint8_t)(4 + (_numgtbs)), TUSB_DESC_CS_ENDPOINT, MIDI_CS_ENDPOINT_GENERAL_2_0, _numgtbs, ## __VA_ARGS__
+
+// Total length: Alt 0 (MIDI 1.0) + Alt 1 (UMP)
+#define TUD_MIDI2_DESC_LEN (TUD_MIDI_DESC_LEN + TUD_MIDI2_DESC_ALT1_HEAD_LEN + TUD_MIDI2_DESC_ALT1_EP_LEN(1) * 2)
+
+// Complete MIDI 2.0 descriptor with both alternate settings (single cable/GTB)
+#define TUD_MIDI2_DESCRIPTOR(_itfnum, _stridx, _epout, _epin, _epsize) \
+ /* Alt Setting 0 (MIDI 1.0) */\
+ TUD_MIDI_DESC_HEAD(_itfnum, _stridx, 1),\
+ TUD_MIDI_DESC_JACK_DESC(1, 0),\
+ TUD_MIDI_DESC_EP(_epout, _epsize, 1),\
+ TUD_MIDI_JACKID_IN_EMB(1),\
+ TUD_MIDI_DESC_EP(_epin, _epsize, 1),\
+ TUD_MIDI_JACKID_OUT_EMB(1),\
+ /* Alt Setting 1 (UMP) */\
+ TUD_MIDI2_DESC_ALT1_HEAD(_itfnum, _stridx),\
+ TUD_MIDI2_DESC_ALT1_EP(_epout, _epsize, 1, 1 /* bAssoGrpTrmBlkID */),\
+ TUD_MIDI2_DESC_ALT1_EP(_epin, _epsize, 1, 1 /* bAssoGrpTrmBlkID */)
+
+//--------------------------------------------------------------------+
// Audio Descriptor Templates
//--------------------------------------------------------------------+
@@ -1026,23 +1063,23 @@ bool tud_vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_requ
// Length of template descriptor
#define TUD_CDC_NCM_DESC_LEN (8+9+5+5+13+6+7+9+9+7+7)
-// CDC-ECM Descriptor Template
-// Interface number, description string index, MAC address string index, EP notification address and size, EP data address (out, in), and size, max segment size.
-#define TUD_CDC_NCM_DESCRIPTOR(_itfnum, _desc_stridx, _mac_stridx, _ep_notif, _ep_notif_size, _epout, _epin, _epsize, _maxsegmentsize) \
+// CDC-NCM Descriptor Template
+// Interface number, description string index, MAC address string index, EP notification address and size, EP data address (out, in), and size, max segment size, EP notification bInterval, capability.
+#define TUD_CDC_NCM_DESCRIPTOR(_itfnum, _desc_stridx, _mac_stridx, _ep_notif, _ep_notif_size, _epout, _epin, _epsize, _maxsegmentsize, _ep_notif_interval, _capability) \
/* Interface Association */\
8, TUSB_DESC_INTERFACE_ASSOCIATION, _itfnum, 2, TUSB_CLASS_CDC, CDC_COMM_SUBCLASS_NETWORK_CONTROL_MODEL, 0, 0,\
/* CDC Control Interface */\
9, TUSB_DESC_INTERFACE, _itfnum, 0, 1, TUSB_CLASS_CDC, CDC_COMM_SUBCLASS_NETWORK_CONTROL_MODEL, 0, _desc_stridx,\
- /* CDC-NCM Header */\
+ /* CDC Header */\
5, TUSB_DESC_CS_INTERFACE, CDC_FUNC_DESC_HEADER, U16_TO_U8S_LE(0x0110),\
- /* CDC-NCM Union */\
+ /* CDC Union */\
5, TUSB_DESC_CS_INTERFACE, CDC_FUNC_DESC_UNION, _itfnum, (uint8_t)((_itfnum) + 1),\
- /* CDC-NCM Functional Descriptor */\
+ /* CDC Ethernet Networking Descriptor */\
13, TUSB_DESC_CS_INTERFACE, CDC_FUNC_DESC_ETHERNET_NETWORKING, _mac_stridx, 0, 0, 0, 0, U16_TO_U8S_LE(_maxsegmentsize), U16_TO_U8S_LE(0), 0, \
/* CDC-NCM Functional Descriptor */\
- 6, TUSB_DESC_CS_INTERFACE, CDC_FUNC_DESC_NCM, U16_TO_U8S_LE(0x0100), 0, \
+ 6, TUSB_DESC_CS_INTERFACE, CDC_FUNC_DESC_NCM, U16_TO_U8S_LE(0x0100), _capability, \
/* Endpoint Notification */\
- 7, TUSB_DESC_ENDPOINT, _ep_notif, TUSB_XFER_INTERRUPT, U16_TO_U8S_LE(_ep_notif_size), 50,\
+ 7, TUSB_DESC_ENDPOINT, _ep_notif, TUSB_XFER_INTERRUPT, U16_TO_U8S_LE(_ep_notif_size), _ep_notif_interval,\
/* CDC Data Interface (default inactive) */\
9, TUSB_DESC_INTERFACE, (uint8_t)((_itfnum)+1), 0, 0, TUSB_CLASS_CDC_DATA, 0, NCM_DATA_PROTOCOL_NETWORK_TRANSFER_BLOCK, 0,\
/* CDC Data Interface (alternative active) */\
diff --git a/src/device/usbd_control.c b/src/device/usbd_control.c
deleted file mode 100644
index 87593d4a7..000000000
--- a/src/device/usbd_control.c
+++ /dev/null
@@ -1,217 +0,0 @@
-/*
- * The MIT License (MIT)
- *
- * Copyright (c) 2019 Ha Thach (tinyusb.org)
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- *
- * This file is part of the TinyUSB stack.
- */
-
-#include "tusb_option.h"
-
-#if CFG_TUD_ENABLED
-
-#include "dcd.h"
-#include "tusb.h"
-#include "device/usbd_pvt.h"
-
-//--------------------------------------------------------------------+
-// Callback weak stubs (called if application does not provide)
-//--------------------------------------------------------------------+
-TU_ATTR_WEAK void dcd_edpt0_status_complete(uint8_t rhport, const tusb_control_request_t* request) {
- (void) rhport;
- (void) request;
-}
-
-//--------------------------------------------------------------------+
-// MACRO CONSTANT TYPEDEF
-//--------------------------------------------------------------------+
-
-enum {
- EDPT_CTRL_OUT = 0x00,
- EDPT_CTRL_IN = 0x80
-};
-
-typedef struct {
- tusb_control_request_t request;
- uint8_t* buffer;
- uint16_t data_len;
- uint16_t total_xferred;
- usbd_control_xfer_cb_t complete_cb;
-} usbd_control_xfer_t;
-
-static usbd_control_xfer_t _ctrl_xfer;
-
-CFG_TUD_MEM_SECTION static struct {
- TUD_EPBUF_DEF(buf, CFG_TUD_ENDPOINT0_BUFSIZE);
-} _ctrl_epbuf;
-
-uint8_t* usbd_get_ctrl_buf(void) {
- return _ctrl_epbuf.buf;
-}
-
-//--------------------------------------------------------------------+
-// Application API
-//--------------------------------------------------------------------+
-
-// Queue ZLP status transaction
-static inline bool status_stage_xact(uint8_t rhport, const tusb_control_request_t* request) {
- // Opposite to endpoint in Data Phase
- const uint8_t ep_addr = request->bmRequestType_bit.direction ? EDPT_CTRL_OUT : EDPT_CTRL_IN;
- return usbd_edpt_xfer(rhport, ep_addr, NULL, 0, false);
-}
-
-// Status phase
-bool tud_control_status(uint8_t rhport, const tusb_control_request_t* request) {
- _ctrl_xfer.request = (*request);
- _ctrl_xfer.buffer = NULL;
- _ctrl_xfer.total_xferred = 0;
- _ctrl_xfer.data_len = 0;
-
- return status_stage_xact(rhport, request);
-}
-
-// Queue a transaction in Data Stage
-// Each transaction has up to Endpoint0's max packet size.
-// This function can also transfer an zero-length packet
-static bool data_stage_xact(uint8_t rhport) {
- const uint16_t xact_len = tu_min16(_ctrl_xfer.data_len - _ctrl_xfer.total_xferred, CFG_TUD_ENDPOINT0_BUFSIZE);
- uint8_t ep_addr = EDPT_CTRL_OUT;
-
- if (_ctrl_xfer.request.bmRequestType_bit.direction == TUSB_DIR_IN) {
- ep_addr = EDPT_CTRL_IN;
- if (0u != xact_len && _ctrl_xfer.buffer != _ctrl_epbuf.buf) {
- TU_VERIFY(0 == tu_memcpy_s(_ctrl_epbuf.buf, CFG_TUD_ENDPOINT0_BUFSIZE, _ctrl_xfer.buffer, xact_len));
- }
- }
-
- return usbd_edpt_xfer(rhport, ep_addr, xact_len ? _ctrl_epbuf.buf : NULL, xact_len, false);
-}
-
-// Transmit data to/from the control endpoint.
-// If the request's wLength is zero, a status packet is sent instead.
-bool tud_control_xfer(uint8_t rhport, const tusb_control_request_t* request, void* buffer, uint16_t len) {
- _ctrl_xfer.request = (*request);
- _ctrl_xfer.buffer = (uint8_t*) buffer;
- _ctrl_xfer.total_xferred = 0U;
- _ctrl_xfer.data_len = tu_min16(len, request->wLength);
-
- if (request->wLength > 0U) {
- if (_ctrl_xfer.data_len > 0U) {
- TU_ASSERT(buffer);
- }
- TU_ASSERT(data_stage_xact(rhport));
- } else {
- TU_ASSERT(status_stage_xact(rhport, request));
- }
-
- return true;
-}
-
-//--------------------------------------------------------------------+
-// USBD API
-//--------------------------------------------------------------------+
-void usbd_control_reset(void);
-void usbd_control_set_request(const tusb_control_request_t* request);
-void usbd_control_set_complete_callback(usbd_control_xfer_cb_t fp);
-bool usbd_control_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes);
-
-void usbd_control_reset(void) {
- tu_varclr(&_ctrl_xfer);
-}
-
-// Set complete callback
-void usbd_control_set_complete_callback(usbd_control_xfer_cb_t fp) {
- _ctrl_xfer.complete_cb = fp;
-}
-
-// for dcd_set_address where DCD is responsible for status response
-void usbd_control_set_request(const tusb_control_request_t* request) {
- _ctrl_xfer.request = (*request);
- _ctrl_xfer.buffer = NULL;
- _ctrl_xfer.total_xferred = 0;
- _ctrl_xfer.data_len = 0;
-}
-
-// callback when a transaction complete on
-// - DATA stage of control endpoint or
-// - Status stage
-bool usbd_control_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) {
- (void) result;
-
- // Endpoint Address is opposite to direction bit, this is Status Stage complete event
- if (tu_edpt_dir(ep_addr) != _ctrl_xfer.request.bmRequestType_bit.direction) {
- TU_ASSERT(0 == xferred_bytes);
-
- // invoke optional dcd hook if available
- dcd_edpt0_status_complete(rhport, &_ctrl_xfer.request);
-
- if (NULL != _ctrl_xfer.complete_cb) {
- // TODO refactor with usbd_driver_print_control_complete_name
- _ctrl_xfer.complete_cb(rhport, CONTROL_STAGE_ACK, &_ctrl_xfer.request);
- }
-
- return true;
- }
-
- if (_ctrl_xfer.request.bmRequestType_bit.direction == TUSB_DIR_OUT) {
- TU_VERIFY(_ctrl_xfer.buffer);
- if (_ctrl_xfer.buffer != _ctrl_epbuf.buf) {
- memcpy(_ctrl_xfer.buffer, _ctrl_epbuf.buf, xferred_bytes);
- }
- TU_LOG_MEM(CFG_TUD_LOG_LEVEL, _ctrl_xfer.buffer, xferred_bytes, 2);
- }
-
- _ctrl_xfer.total_xferred += (uint16_t) xferred_bytes;
- _ctrl_xfer.buffer += xferred_bytes;
-
- // Data Stage is complete when all request's length are transferred or
- // a short packet is sent including zero-length packet.
- if ((_ctrl_xfer.request.wLength == _ctrl_xfer.total_xferred) ||
- (xferred_bytes < CFG_TUD_ENDPOINT0_BUFSIZE)) {
- // DATA stage is complete
- bool is_ok = true;
-
- // invoke complete callback if set
- // callback can still stall control in status phase e.g out data does not make sense
- if (NULL != _ctrl_xfer.complete_cb) {
- #if CFG_TUSB_DEBUG >= CFG_TUD_LOG_LEVEL
- usbd_driver_print_control_complete_name(_ctrl_xfer.complete_cb);
- #endif
-
- is_ok = _ctrl_xfer.complete_cb(rhport, CONTROL_STAGE_DATA, &_ctrl_xfer.request);
- }
-
- if (is_ok) {
- TU_ASSERT(status_stage_xact(rhport, &_ctrl_xfer.request));
- } else {
- // Stall both IN and OUT control endpoint
- dcd_edpt_stall(rhport, EDPT_CTRL_OUT);
- dcd_edpt_stall(rhport, EDPT_CTRL_IN);
- }
- } else {
- // More data to transfer
- TU_ASSERT(data_stage_xact(rhport));
- }
-
- return true;
-}
-
-#endif
diff --git a/src/device/usbd_pvt.h b/src/device/usbd_pvt.h
index 5f11ea481..be778f9af 100644
--- a/src/device/usbd_pvt.h
+++ b/src/device/usbd_pvt.h
@@ -130,10 +130,6 @@ void usbd_sof_enable(uint8_t rhport, sof_consumer_t consumer, bool en);
bool usbd_open_edpt_pair(uint8_t rhport, uint8_t const* p_desc, uint8_t ep_count, uint8_t xfer_type, uint8_t* ep_out, uint8_t* ep_in);
void usbd_defer_func(osal_task_func_t func, void *param, bool in_isr);
-#if CFG_TUSB_DEBUG >= CFG_TUD_LOG_LEVEL
-void usbd_driver_print_control_complete_name(usbd_control_xfer_cb_t callback);
-#endif
-
#ifdef __cplusplus
}
#endif
diff --git a/src/host/usbh.c b/src/host/usbh.c
index 8f80800e9..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
@@ -140,7 +148,7 @@ typedef struct {
uint8_t itf2drv[CFG_TUH_INTERFACE_MAX]; // map interface number to driver (0xff is invalid)
uint8_t ep2drv[CFG_TUH_ENDPOINT_MAX][2]; // map endpoint to driver ( 0xff is invalid ), can use only 4-bit each
- tu_edpt_state_t ep_status[CFG_TUH_ENDPOINT_MAX][2];
+ volatile uint8_t ep_status[CFG_TUH_ENDPOINT_MAX][2];
#if CFG_TUH_API_EDPT_XFER
// TODO array can be CFG_TUH_ENDPOINT_MAX-1
@@ -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);
@@ -278,6 +301,18 @@ static usbh_class_driver_t const usbh_class_drivers[] = {
},
#endif
+ #if CFG_TUH_MIDI2
+ {
+ .name = DRIVER_NAME("MIDI2"),
+ .init = midih2_init,
+ .deinit = midih2_deinit,
+ .open = midih2_open,
+ .set_config = midih2_set_config,
+ .xfer_cb = midih2_xfer_cb,
+ .close = midih2_close
+ },
+ #endif
+
#if CFG_TUH_HUB
{
.name = DRIVER_NAME("HUB"),
@@ -334,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);
@@ -352,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;
@@ -360,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);
@@ -382,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
@@ -446,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) {
@@ -473,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) {
@@ -535,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++) {
@@ -553,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);
@@ -568,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);
@@ -592,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);
@@ -617,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) {
@@ -651,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
@@ -683,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
@@ -744,8 +822,8 @@ void tuh_task_ext(uint32_t timeout_ms, bool in_isr) {
usbh_device_t* dev = get_device(event.dev_addr);
TU_VERIFY(dev && dev->connected,);
- dev->ep_status[epnum][ep_dir].busy = 0;
- dev->ep_status[epnum][ep_dir].claimed = 0;
+ // clear busy and claimed
+ dev->ep_status[epnum][ep_dir] &= (uint8_t) ~(TU_EDPT_STATE_BUSY | TU_EDPT_STATE_CLAIMED);
if (0 == epnum) {
usbh_control_xfer_cb(event.dev_addr, ep_addr, (xfer_result_t) event.xfer_complete.result, event.xfer_complete.len);
@@ -806,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;
+ }
- TU_VERIFY(is_idle);
+ if (is_nonblocking) {
+ return is_queued;
+ }
+
+ // - 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;
@@ -889,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);
@@ -903,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:
@@ -919,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;
@@ -932,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;
@@ -947,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;
@@ -964,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;
}
@@ -1011,15 +1205,15 @@ 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);
- TU_VERIFY(dev->ep_status[epnum][dir].busy); // non-control skip if not busy
+ TU_VERIFY(dev->ep_status[epnum][dir] & TU_EDPT_STATE_BUSY); // non-control skip if not busy
// abort then mark as ready and release endpoint
hcd_edpt_abort_xfer(dev->bus_info.rhport, daddr, ep_addr);
- dev->ep_status[epnum][dir].busy = false;
+ dev->ep_status[epnum][dir] &= (uint8_t) ~TU_EDPT_STATE_BUSY; // clear busy
tu_edpt_release(&dev->ep_status[epnum][dir], _usbh_mutex);
}
@@ -1043,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);
}
}
@@ -1110,16 +1304,16 @@ bool usbh_edpt_xfer_with_callback(uint8_t dev_addr, uint8_t ep_addr, uint8_t* bu
uint8_t const epnum = tu_edpt_number(ep_addr);
uint8_t const dir = tu_edpt_dir(ep_addr);
- tu_edpt_state_t* ep_state = &dev->ep_status[epnum][dir];
+ volatile uint8_t* ep_state = &dev->ep_status[epnum][dir];
TU_LOG_USBH(" Queue EP %02X with %u bytes ... \r\n", ep_addr, total_bytes);
// Attempt to transfer on a busy endpoint, sound like an race condition !
- TU_ASSERT(ep_state->busy == 0);
+ TU_ASSERT((*ep_state & TU_EDPT_STATE_BUSY) == 0);
// Set busy first since the actual transfer can be complete before hcd_edpt_xfer()
// could return and USBH task can preempt and clear the busy
- ep_state->busy = 1;
+ *ep_state |= TU_EDPT_STATE_BUSY;
#if CFG_TUH_API_EDPT_XFER
dev->ep_callback[epnum][dir].complete_cb = complete_cb;
@@ -1130,9 +1324,8 @@ bool usbh_edpt_xfer_with_callback(uint8_t dev_addr, uint8_t ep_addr, uint8_t* bu
TU_LOG_USBH("OK\r\n");
return true;
} else {
- // HCD error, mark endpoint as ready to allow next transfer
- ep_state->busy = 0;
- ep_state->claimed = 0;
+ // HCD error, clear busy and claimed to allow next transfer
+ *ep_state &= (uint8_t) ~(TU_EDPT_STATE_BUSY | TU_EDPT_STATE_CLAIMED);
TU_LOG1("Failed\r\n");
// TU_BREAKPOINT();
return false;
@@ -1178,7 +1371,7 @@ bool usbh_edpt_busy(uint8_t dev_addr, uint8_t ep_addr) {
uint8_t const epnum = tu_edpt_number(ep_addr);
uint8_t const dir = tu_edpt_dir(ep_addr);
- return dev->ep_status[epnum][dir].busy;
+ return (dev->ep_status[epnum][dir] & TU_EDPT_STATE_BUSY) != 0;
}
//--------------------------------------------------------------------+
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/chipidea/ci_fs/dcd_ci_fs.c b/src/portable/chipidea/ci_fs/dcd_ci_fs.c
index 312a98299..62df1a6d5 100644
--- a/src/portable/chipidea/ci_fs/dcd_ci_fs.c
+++ b/src/portable/chipidea/ci_fs/dcd_ci_fs.c
@@ -360,7 +360,7 @@ static bool edpt_open(uint8_t rhport, uint8_t ep_addr, uint16_t max_packet_size,
unsigned val = USB_ENDPT_EPCTLDIS_MASK;
val |= (xfer != TUSB_XFER_ISOCHRONOUS) ? USB_ENDPT_EPHSHK_MASK : 0;
val |= dir ? USB_ENDPT_EPTXEN_MASK : USB_ENDPT_EPRXEN_MASK;
- CI_REG->EP[epn].CTL |= val;
+ CI_REG->EP[epn].CTL |= (uint8_t)val;
if (xfer != TUSB_XFER_ISOCHRONOUS) {
bd[odd].dts = 1;
@@ -434,11 +434,11 @@ bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t * buffer, uint16_t t
buffer_descriptor_t *next = ep->odd ? bd - 1: bd + 1;
/* When total_bytes is greater than the max packet size,
* it prepares to the next transfer to avoid NAK in advance. */
- next->bc = total_bytes >= 2 * mps ? mps: total_bytes - mps;
+ next->bc = (total_bytes >= 2 * mps) ? mps : (total_bytes - mps);
next->addr = buffer + mps;
next->own = 1;
}
- bd->bc = total_bytes >= mps ? mps: total_bytes;
+ bd->bc = (total_bytes >= mps ? mps : total_bytes);
bd->addr = buffer;
__DSB();
bd->own = 1; /* This bit must be set last */
@@ -506,16 +506,16 @@ void dcd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr)
//--------------------------------------------------------------------+
void dcd_int_handler(uint8_t rhport)
{
- uint32_t is = CI_REG->INT_STAT;
- uint32_t msk = CI_REG->INT_EN;
+ uint8_t is = CI_REG->INT_STAT;
+ uint8_t msk = CI_REG->INT_EN;
// clear non-enabled interrupts
- CI_REG->INT_STAT = is & ~msk;
+ CI_REG->INT_STAT = (uint8_t)(is & ~msk);
is &= msk;
if (is & USB_ISTAT_ERROR_MASK) {
/* TODO: */
- uint32_t es = CI_REG->ERR_STAT;
+ uint8_t es = CI_REG->ERR_STAT;
CI_REG->ERR_STAT = es;
CI_REG->INT_STAT = is; /* discard any pending events */
}
diff --git a/src/portable/dialog/da146xx/dcd_da146xx.c b/src/portable/dialog/da146xx/dcd_da146xx.c
index a283c8362..7d90b1f94 100644
--- a/src/portable/dialog/da146xx/dcd_da146xx.c
+++ b/src/portable/dialog/da146xx/dcd_da146xx.c
@@ -148,8 +148,8 @@ typedef struct
#ifndef TU_DA146XX_DMA_RX_CHANNEL
#define TU_DA146XX_DMA_RX_CHANNEL 6
#endif
-#define DA146XX_DMA_USB_MUX (0x6 << (TU_DA146XX_DMA_RX_CHANNEL * 2))
-#define DA146XX_DMA_USB_MUX_MASK (0xF << (TU_DA146XX_DMA_RX_CHANNEL * 2))
+#define DA146XX_DMA_USB_MUX (0x6u << (TU_DA146XX_DMA_RX_CHANNEL * 2))
+#define DA146XX_DMA_USB_MUX_MASK (0xFu << (TU_DA146XX_DMA_RX_CHANNEL * 2))
typedef struct
{
@@ -311,12 +311,12 @@ static void fill_tx_fifo(xfer_ctl_t * xfer)
// Max packet size is set to value greater then FIFO. Enable fifo level warning
// to handle larger packets.
regs->txc |= (3 << USB_USB_TXC1_REG_USB_TFWL_Pos);
- USB->USB_FWMSK_REG |= 1 << (epnum - 1 + USB_USB_FWMSK_REG_USB_M_TXWARN31_Pos);
+ USB->USB_FWMSK_REG |= 1u << (epnum - 1 + USB_USB_FWMSK_REG_USB_M_TXWARN31_Pos);
}
else
{
regs->txc &= ~USB_USB_TXC1_REG_USB_TFWL_Msk;
- USB->USB_FWMSK_REG &= ~(1 << (epnum - 1 + USB_USB_FWMSK_REG_USB_M_TXWARN31_Pos));
+ USB->USB_FWMSK_REG &= ~(1u << (epnum - 1 + USB_USB_FWMSK_REG_USB_M_TXWARN31_Pos));
// Whole packet already in fifo, no need to refill it later. Mark last.
regs->txc |= USB_USB_TXC1_REG_USB_LAST_Msk;
}
@@ -371,14 +371,14 @@ static void start_rx_packet(xfer_ctl_t *xfer)
// For endpoint size greater than FIFO size enable FIFO level warning interrupt
// when FIFO has less than 17 bytes free.
regs->rxc |= USB_USB_RXC1_REG_USB_RFWL_Msk;
- USB->USB_FWMSK_REG |= 1 << (epnum - 1 + USB_USB_FWMSK_REG_USB_M_RXWARN31_Pos);
+ USB->USB_FWMSK_REG |= 1u << (epnum - 1 + USB_USB_FWMSK_REG_USB_M_RXWARN31_Pos);
}
}
else if (epnum != 0)
{
// If max_packet_size would fit in FIFO no need for FIFO level warning interrupt.
regs->rxc &= ~USB_USB_RXC1_REG_USB_RFWL_Msk;
- USB->USB_FWMSK_REG &= ~(1 << (epnum - 1 + USB_USB_FWMSK_REG_USB_M_RXWARN31_Pos));
+ USB->USB_FWMSK_REG &= ~(1u << (epnum - 1 + USB_USB_FWMSK_REG_USB_M_RXWARN31_Pos));
}
regs->rxc |= USB_USB_RXC1_REG_USB_RX_EN_Msk;
}
@@ -388,7 +388,7 @@ static void start_tx_dma(void *src, volatile void *dst, uint16_t size)
// Setup SRC and DST registers
TX_DMA_REGS->DMAx_A_START_REG = (uint32_t)src;
TX_DMA_REGS->DMAx_B_START_REG = (uint32_t)dst;
- // Interrupt not needed
+ // Interrupt is not needed
TX_DMA_REGS->DMAx_INT_REG = size;
TX_DMA_REGS->DMAx_LEN_REG = size - 1;
TX_DMA_REGS->DMAx_CTRL_REG = TX_DMA_START;
@@ -430,7 +430,9 @@ static uint16_t read_rx_fifo(xfer_ctl_t *xfer, uint16_t bytes_in_fifo)
uint8_t *buf = xfer->buffer + xfer->transferred + xfer->last_packet_size;
- for (int i = 0; i < receive_this_time; ++i) buf[i] = regs->rxd;
+ for (int i = 0; i < receive_this_time; ++i) {
+ buf[i] = (uint8_t)regs->rxd;
+ }
xfer->last_packet_size += receive_this_time;
@@ -449,7 +451,9 @@ static void handle_ep0_rx(void)
{
xfer_ctl_t *xfer_in = XFER_CTL_BASE(0, TUSB_DIR_IN);
// Setup packet is in
- for (int i = 0; i < fifo_bytes; ++i) _setup_packet[i] = USB->USB_RXD0_REG;
+ for (int i = 0; i < fifo_bytes; ++i) {
+ _setup_packet[i] = (uint8_t)USB->USB_RXD0_REG;
+ }
xfer->stall = 0;
xfer->data1 = 1;
@@ -469,7 +473,7 @@ static void handle_ep0_rx(void)
}
else
{
- read_rx_fifo(xfer, fifo_bytes);
+ read_rx_fifo(xfer, (uint16_t)fifo_bytes);
if (rxs0 & USB_USB_RXS0_REG_USB_RX_LAST_Msk)
{
xfer->transferred += xfer->last_packet_size;
@@ -553,7 +557,7 @@ static void handle_epx_rx_ev(uint8_t ep)
{
// Disable DMA and update last_packet_size with what DMA reported.
RX_DMA_REGS->DMAx_CTRL_REG &= ~DMA_DMA0_CTRL_REG_DMA_ON_Msk;
- xfer->last_packet_size = RX_DMA_REGS->DMAx_IDX_REG;
+ xfer->last_packet_size = (uint16_t)RX_DMA_REGS->DMAx_IDX_REG;
// When DMA did not finished (packet was smaller then MPS), DMAx_IDX_REG holds exact number of bytes transmitted.
// When DMA finished value in DMAx_IDX_REG is one less then actual number of transmitted bytes.
if (xfer->last_packet_size == RX_DMA_REGS->DMAx_LEN_REG) xfer->last_packet_size++;
@@ -564,7 +568,7 @@ static void handle_epx_rx_ev(uint8_t ep)
// FIFO maybe empty if DMA read it before or it's final iteration and function already read all that was to read.
if (fifo_bytes > 0)
{
- fifo_bytes = read_rx_fifo(xfer, fifo_bytes);
+ fifo_bytes = read_rx_fifo(xfer, (uint16_t)fifo_bytes);
}
if (GET_BIT(rxs, USB_USB_RXS1_REG_USB_RX_LAST))
{
@@ -624,7 +628,7 @@ static void handle_epx_tx_ev(xfer_ctl_t *xfer)
{
// Disable DMA and update last_packet_size with what DMA reported.
TX_DMA_REGS->DMAx_CTRL_REG &= ~DMA_DMA1_CTRL_REG_DMA_ON_Msk;
- xfer->last_packet_size = TX_DMA_REGS->DMAx_IDX_REG + 1;
+ xfer->last_packet_size = (uint16_t)(TX_DMA_REGS->DMAx_IDX_REG + 1);
// Release DMA to used by other endpoints.
_dcd.dma_ep[TUSB_DIR_IN] = 0;
}
@@ -954,13 +958,13 @@ bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const * desc_edpt)
if (dir == TUSB_DIR_OUT)
{
regs->epc_out = epnum | USB_USB_EPC1_REG_USB_EP_EN_Msk | iso_mask;
- USB->USB_RXMSK_REG |= 0x11 << (epnum - 1);
+ USB->USB_RXMSK_REG |= 0x11u << (epnum - 1);
REG_SET_BIT(USB_MAMSK_REG, USB_M_RX_EV);
}
else
{
regs->epc_in = epnum | USB_USB_EPC1_REG_USB_EP_EN_Msk | iso_mask;
- USB->USB_TXMSK_REG |= 0x11 << (epnum - 1);
+ USB->USB_TXMSK_REG |= 0x11u << (epnum - 1);
REG_SET_BIT(USB_MAMSK_REG, USB_M_TX_EV);
}
}
@@ -974,8 +978,8 @@ void dcd_edpt_close_all (uint8_t rhport)
for (int epnum = 1; epnum < EP_MAX; ++epnum)
{
- dcd_edpt_close(0, epnum | TUSB_DIR_OUT);
- dcd_edpt_close(0, epnum | TUSB_DIR_IN);
+ dcd_edpt_close(0, (uint8_t)(epnum | TUSB_DIR_OUT));
+ dcd_edpt_close(0, (uint8_t)(epnum | TUSB_DIR_IN));
}
}
@@ -1001,7 +1005,7 @@ void dcd_edpt_close(uint8_t rhport, uint8_t ep_addr)
{
regs->rxc = USB_USB_RXC1_REG_USB_FLUSH_Msk;
regs->epc_out = 0;
- USB->USB_RXMSK_REG &= ~(0x11 << (epnum - 1));
+ USB->USB_RXMSK_REG &= ~(0x11u << (epnum - 1));
// Release DMA if needed
if (_dcd.dma_ep[TUSB_DIR_OUT] == epnum)
{
@@ -1013,7 +1017,7 @@ void dcd_edpt_close(uint8_t rhport, uint8_t ep_addr)
{
regs->txc = USB_USB_TXC1_REG_USB_FLUSH_Msk;
regs->epc_in = 0;
- USB->USB_TXMSK_REG &= ~(0x11 << (epnum - 1));
+ USB->USB_TXMSK_REG &= ~(0x11u << (epnum - 1));
// Release DMA if needed
if (_dcd.dma_ep[TUSB_DIR_IN] == epnum)
{
diff --git a/src/portable/mentor/musb/dcd_musb.c b/src/portable/mentor/musb/dcd_musb.c
index 64f9ebacf..1d1280bf4 100644
--- a/src/portable/mentor/musb/dcd_musb.c
+++ b/src/portable/mentor/musb/dcd_musb.c
@@ -50,36 +50,133 @@
* MACRO TYPEDEF CONSTANT ENUM DECLARATION
*------------------------------------------------------------------*/
-#define REQUEST_TYPE_INVALID (0xFFu)
-
typedef union {
volatile uint8_t u8;
volatile uint16_t u16;
volatile uint32_t u32;
} hw_fifo_t;
-typedef struct TU_ATTR_PACKED
-{
- void *buf; /* the start address of a transfer data buffer */
+typedef struct {
+ union {
+ uint8_t *buf; /* the start address of a transfer data buffer */
+ tu_fifo_t *fifo;
+ };
uint16_t length; /* the number of bytes in the buffer */
uint16_t remaining; /* the number of bytes remaining in the buffer */
+ bool armed; /* true while a transfer is posted */
+ bool use_fifo; /* true: buf is tu_fifo_t*; false: buf is plain byte pointer. */
} pipe_state_t;
-typedef struct
-{
- union {
- tusb_control_request_t setup_packet;
- uint32_t setup_buffer[2];
- };
- uint16_t remaining_ctrl; /* The number of bytes remaining in data stage of control transfer. */
- int8_t status_out;
- pipe_state_t pipe0;
- pipe_state_t pipe[2][TUP_DCD_ENDPOINT_MAX-1]; /* pipe[direction][endpoint number - 1] */
- uint16_t pipe_buf_is_fifo[2]; /* Bitmap. Each bit means whether 1:TU_FIFO or 0:POD. */
+// Pipe array layout (N = TUP_DCD_ENDPOINT_MAX). EP0 has its own scalars in
+// dcd_data_t and does not occupy a pipe slot.
+// One-direction-only IPs (CFG_TUD_ENDPOINT_ONE_DIRECTION_ONLY=1):
+// [0..N-2] : EP1..N-1 (single slot per endpoint)
+// Bidirectional-capable IPs:
+// [0..N-2 ] : EP1..N-1 OUT
+// [N-1..2*N-3 ] : EP1..N-1 IN
+#if CFG_TUD_ENDPOINT_ONE_DIRECTION_ONLY
+ #define MUSB_PIPE_COUNT (TUP_DCD_ENDPOINT_MAX - 1u)
+#else
+ #define MUSB_PIPE_COUNT (2u * (TUP_DCD_ENDPOINT_MAX - 1u))
+#endif
+
+enum {
+ PIPE0_STATE_IDLE = 0, // no active control transfer
+ 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_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 {
+ 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;
+#if CFG_TUD_ENDPOINT_ONE_DIRECTION_ONLY
+ (void) epdir;
+#else
+ if (epdir == TUSB_DIR_IN) {
+ idx += TUP_DCD_ENDPOINT_MAX - 1u;
+ }
+#endif
+ return &_dcd.pipe[idx];
+}
+
//--------------------------------------------------------------------
// HW FIFO Helper
// Note: Index register is already set by caller
@@ -110,7 +207,6 @@ TU_ATTR_ALWAYS_INLINE static inline void hwfifo_reset(musb_regs_t* musb, unsigne
TU_ATTR_ALWAYS_INLINE static inline bool hwfifo_config(musb_regs_t* musb, unsigned epnum, unsigned is_rx, unsigned mps,
bool double_packet) {
- (void) epnum;
uint8_t ffsize = hwfifo_byte2size(mps);
mps = 8 << ffsize; // round up to the next power of 2
@@ -123,6 +219,13 @@ TU_ATTR_ALWAYS_INLINE static inline bool hwfifo_config(musb_regs_t* musb, unsign
musb->fifo_addr[is_rx] = alloced_fifo_bytes / 8;
musb->fifo_size[is_rx] = ffsize;
+ volatile uint16_t* dp_disable = is_rx ? &musb->rx_doulbe_packet_disable : &musb->tx_double_packet_disable;
+ if (double_packet) {
+ *dp_disable &= ~(1u << epnum);
+ } else {
+ *dp_disable |= (1u << epnum);
+ }
+
alloced_fifo_bytes += mps;
return true;
}
@@ -136,18 +239,29 @@ TU_ATTR_ALWAYS_INLINE static inline void hwfifo_reset(musb_regs_t* musb, unsigne
TU_ATTR_ALWAYS_INLINE static inline bool hwfifo_config(musb_regs_t* musb, unsigned epnum, unsigned is_rx, unsigned mps,
bool double_packet) {
- (void) epnum; (void) mps;
- if (!double_packet) {
- #if defined(TUP_USBIP_MUSB_ADI)
- musb->indexed_csr.maxp_csr[is_rx].csrh |= MUSB_CSRH_DISABLE_DOUBLE_PACKET(is_rx);
- #else
- if (is_rx) {
- musb->rx_doulbe_packet_disable |= 1u << epnum;
- } else {
- musb->tx_double_packet_disable |= 1u << epnum;
- }
- #endif
+ (void) mps;
+
+ #if defined(TUP_USBIP_MUSB_ADI)
+ // AnalogDevice FIFO sizes: EP1..7 = 512 B, EP8..9 = 2048 B, EP10..11 = 4096 B.
+ // DPB requires FIFO >= 2 * MPS. For HS bulk (MPS=512) only EP >= 8 qualifies.
+ // Force single-buffered on EP < 8 even if the caller requested DPB.
+ if (epnum < 8 && (musb->power & MUSB_POWER_HSMODE)) {
+ double_packet = false;
+ }
+ volatile uint8_t* csrh = &musb->indexed_csr.maxp_csr[is_rx].csrh;
+ if (double_packet) {
+ *csrh &= ~MUSB_CSRH_DISABLE_DOUBLE_PACKET;
+ } else {
+ *csrh |= MUSB_CSRH_DISABLE_DOUBLE_PACKET;
}
+ #else
+ volatile uint16_t* dp_disable = is_rx ? &musb->rx_doulbe_packet_disable : &musb->tx_double_packet_disable;
+ if (double_packet) {
+ *dp_disable &= ~(1u << epnum);
+ } else {
+ *dp_disable |= (1u << epnum);
+ }
+ #endif
return true;
}
@@ -167,322 +281,359 @@ TU_ATTR_ALWAYS_INLINE static inline void hwfifo_flush(musb_regs_t* musb, unsigne
}
}
-static void process_setup_packet(uint8_t rhport) {
- musb_regs_t* musb_regs = MUSB_REGS(rhport);
-
- // Read setup packet
- _dcd.setup_buffer[0] = musb_regs->fifo[0];
- _dcd.setup_buffer[1] = musb_regs->fifo[0];
-
- _dcd.pipe0.buf = NULL;
- _dcd.pipe0.length = 0;
- _dcd.pipe0.remaining = 0;
- dcd_event_setup_received(rhport, (const uint8_t*)(uintptr_t)&_dcd.setup_packet, true);
-
- const unsigned len = _dcd.setup_packet.wLength;
- _dcd.remaining_ctrl = len;
- const unsigned dir_in = tu_edpt_dir(_dcd.setup_packet.bmRequestType);
- /* Clear RX FIFO and reverse the transaction direction */
- if (len && dir_in) {
- musb_ep_csr_t* ep_csr = get_ep_csr(musb_regs, 0);
- ep_csr->csr0l = MUSB_CSRL0_RXRDYC;
+// write to txfifo using pipe_state_t info
+static void pipe_write(musb_regs_t* musb_regs, pipe_state_t* pipe, uint8_t epnum) {
+ musb_ep_csr_t* ep_csr = &musb_regs->indexed_csr;
+ const uint16_t mps = ep_csr->tx_maxp & MUSB_TXMAXP_PACKET_SIZE_M;
+ const uint16_t xact_len = tu_min16(mps, pipe->remaining);
+ volatile void *hwfifo = &musb_regs->fifo[epnum];
+ if (xact_len) {
+ if (pipe->use_fifo) {
+ tu_hwfifo_write_from_fifo(hwfifo, pipe->fifo, xact_len, NULL);
+ } else {
+ tu_hwfifo_write(hwfifo, pipe->buf, xact_len, NULL);
+ pipe->buf += xact_len;
+ }
+ pipe->remaining -= xact_len;
}
+ ep_csr->tx_csrl = MUSB_TXCSRL1_TXRDY;
}
-static bool handle_xfer_in(uint8_t rhport, uint_fast8_t ep_addr) {
- unsigned epnum = tu_edpt_number(ep_addr);
- unsigned epnum_minus1 = epnum - 1;
- pipe_state_t *pipe = &_dcd.pipe[tu_edpt_dir(ep_addr)][epnum_minus1];
- const unsigned rem = pipe->remaining;
+// Called from the TX interrupt. If the last queued packet finished the transfer,
+// signal completion; otherwise queue the next packet.
+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) {
+ ep_csr->tx_csrl &= ~(MUSB_TXCSRL1_STALLED | MUSB_TXCSRL1_UNDRN);
+ return; // sent STALL, do nothing
+ }
- if (rem == 0 && pipe->length > 0) {
+ pipe_state_t* pipe = pipe_get(epnum, TUSB_DIR_IN);
+ if (pipe->remaining > 0) {
+ pipe_write(musb_regs, pipe, epnum);
+ } else {
+ // All bytes have been loaded into the FIFO. With double-packet buffering a
+ // second packet may still be waiting in the FIFO when this IRQ fires (the
+ // hardware signals TXRDY clear as soon as a slot frees, not when the wire
+ // transfer finishes). Defer completion until FIFONE == 0 so we don't emit
+ // a duplicate xfer_complete before the final packet has been sent.
+ if (csrl & MUSB_TXCSRL1_FIFONE) {
+ return;
+ }
+ const uint16_t xferred_len = pipe->length;
pipe->buf = NULL;
- return true;
+ pipe->armed = false;
+ dcd_event_xfer_complete(rhport, tu_edpt_addr(epnum, TUSB_DIR_IN), xferred_len, XFER_RESULT_SUCCESS, true);
}
+}
- musb_regs_t* musb_regs = MUSB_REGS(rhport);
- musb_ep_csr_t* ep_csr = get_ep_csr(musb_regs, epnum);
- const unsigned mps = ep_csr->tx_maxp;
- const unsigned len = TU_MIN(mps, rem);
- void *buf = pipe->buf;
- volatile void *fifo_ptr = &musb_regs->fifo[epnum];
- // TU_LOG1(" %p mps %d len %d rem %d\r\n", buf, mps, len, rem);
- if (len) {
- if (_dcd.pipe_buf_is_fifo[TUSB_DIR_IN] & TU_BIT(epnum_minus1)) {
- tu_hwfifo_write_from_fifo(fifo_ptr, (tu_fifo_t *)buf, len, NULL);
+// 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_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);
+ volatile void *hwfifo = &musb_regs->fifo[epnum];
+ if (xact_len) {
+ if (pipe->use_fifo) {
+ tu_hwfifo_read_to_fifo(hwfifo, pipe->fifo, xact_len, NULL);
} else {
- tu_hwfifo_write(fifo_ptr, buf, len, NULL);
- pipe->buf = (uint8_t*)buf + len;
+ tu_hwfifo_read(hwfifo, pipe->buf, xact_len, NULL);
+ pipe->buf += xact_len;
}
- pipe->remaining = rem - len;
+ pipe->remaining -= xact_len;
}
- ep_csr->tx_csrl = MUSB_TXCSRL1_TXRDY;
- // TU_LOG1(" TXCSRL%d = %x %d\r\n", epnum, ep_csr->tx_csrl, rem - len);
- return false;
+ ep_csr->rx_csrl = 0; /* Clear RXRDY - release this FIFO slot */
+
+ return (xact_len < mps);
}
-static bool handle_xfer_out(uint8_t rhport, uint_fast8_t ep_addr)
-{
- unsigned epnum = tu_edpt_number(ep_addr);
- unsigned epnum_minus1 = epnum - 1;
- pipe_state_t *pipe = &_dcd.pipe[tu_edpt_dir(ep_addr)][epnum_minus1];
- musb_regs_t* musb_regs = MUSB_REGS(rhport);
+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);
- // TU_LOG1(" RXCSRL%d = %x\r\n", epnum_minus1 + 1, ep_csr->rx_csrl);
-
- //Fail gracefully. Spurious interrupt.
- if (!(ep_csr->rx_csrl & MUSB_RXCSRL1_RXRDY)) return false;
+ if (ep_csr->rx_csrl & MUSB_RXCSRL1_STALLED) {
+ ep_csr->rx_csrl &= ~(MUSB_RXCSRL1_STALLED | MUSB_RXCSRL1_OVER);
+ return; // sent STALL, do nothing
+ }
- void *buf = pipe->buf;
- if (buf == NULL) {
- ep_csr->rx_csrl = MUSB_RXCSRL1_FLUSH;
- return false;
+ // Fail gracefully. Spurious interrupt.
+ if (!(ep_csr->rx_csrl & MUSB_RXCSRL1_RXRDY)) {
+ return;
}
- const unsigned mps = ep_csr->rx_maxp;
- const unsigned rem = pipe->remaining;
- const unsigned vld = ep_csr->rx_count;
- const unsigned len = TU_MIN(TU_MIN(rem, mps), vld);
- volatile void *fifo_ptr = &musb_regs->fifo[epnum];
- if (len) {
- if (_dcd.pipe_buf_is_fifo[TUSB_DIR_OUT] & TU_BIT(epnum_minus1)) {
- tu_hwfifo_read_to_fifo(fifo_ptr, (tu_fifo_t *)buf, len, NULL);
- } else {
- tu_hwfifo_read(fifo_ptr, buf, len, NULL);
- pipe->buf = (uint8_t*)buf + len;
- }
- pipe->remaining = rem - len;
+ pipe_state_t *pipe = pipe_get(epnum, TUSB_DIR_OUT);
+ if (!pipe->armed) {
+ // Packet is already ACK'd by hardware and sitting in the Rx FIFO, but no transfer is
+ // posted. Do NOT flush (per MUSB spec §3.3.11 FlushFIFO) - that would silently drop
+ // acknowledged data. Mask this endpoint's Rx interrupt so the ISR stops re-firing;
+ // the FIFO stays occupied so hardware NAKs further OUT tokens (natural backpressure).
+ // The next dcd_edpt_xfer() on this endpoint will drain the staged packet.
+ musb_regs->intr_rxen &= (uint16_t) ~TU_BIT(epnum);
+ return;
}
- ep_csr->rx_csrl = 0; /* Always Clear RXRDY bit */
- if ((len < mps) || (rem == len)) {
+ const bool is_short = pipe_read(musb_regs, pipe, epnum);
+
+ // Transfer completes on a short packet or when the rx buffer is filled.
+ if (is_short || pipe->remaining == 0) {
+ const uint16_t xferred_len = pipe->length - pipe->remaining;
pipe->buf = NULL;
- return NULL != buf;
+ pipe->armed = false;
+ dcd_event_xfer_complete(rhport, epnum, xferred_len, XFER_RESULT_SUCCESS, is_isr);
}
- return false;
}
-static bool edpt_n_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t *buffer, uint16_t total_bytes)
-{
- unsigned epnum = tu_edpt_number(ep_addr);
- unsigned epnum_minus1 = epnum - 1;
- unsigned dir_in = tu_edpt_dir(ep_addr);
+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 tusb_dir_t dir_in = tu_edpt_dir(ep_addr);
+
+ pipe_state_t *pipe = pipe_get(epnum, dir_in);
+ if (use_fifo) {
+ pipe->fifo = (tu_fifo_t *)buffer;
+ } else {
+ pipe->buf = (uint8_t *)buffer;
+ }
+ pipe->length = total_bytes;
+ pipe->remaining = total_bytes;
+ pipe->use_fifo = use_fifo;
+ pipe->armed = true;
- pipe_state_t *pipe = &_dcd.pipe[dir_in][epnum_minus1];
- pipe->buf = buffer;
- pipe->length = total_bytes;
- pipe->remaining = total_bytes;
+ musb_regs_t *musb_regs = MUSB_REGS(rhport);
+ musb_ep_csr_t *ep_csr = get_ep_csr(musb_regs, epnum);
if (dir_in) {
- handle_xfer_in(rhport, ep_addr);
+ pipe_write(musb_regs, pipe, epnum);
} else {
- musb_regs_t* musb_regs = MUSB_REGS(rhport);
- musb_ep_csr_t* ep_csr = get_ep_csr(musb_regs, epnum);
- if (ep_csr->rx_csrl & MUSB_RXCSRL1_RXRDY) ep_csr->rx_csrl = 0;
+ // 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_isr() fires dcd_event_xfer_complete() itself if the drain completes.
+ if (ep_csr->rx_csrl & MUSB_RXCSRL1_RXRDY) {
+ process_epout_isr(rhport, musb_regs, epnum, is_isr);
+ }
}
return true;
}
-static bool edpt0_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t *buffer, uint16_t total_bytes)
-{
- (void)rhport;
- TU_ASSERT(total_bytes <= 64); /* Current implementation supports for only up to 64 bytes. */
+static bool edpt0_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t *buffer, uint16_t total_bytes, bool is_isr) {
+ 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);
- const unsigned req = _dcd.setup_packet.bmRequestType;
- TU_ASSERT(req != REQUEST_TYPE_INVALID || total_bytes == 0);
-
- if (req == REQUEST_TYPE_INVALID || _dcd.status_out) {
- /* STATUS OUT stage.
- * MUSB controller automatically handles STATUS OUT packets without
- * software helps. We do not have to do anything. And STATUS stage
- * may have already finished and received the next setup packet
- * without calling this function, so we have no choice but to
- * invoke the callback function of status packet here. */
- // TU_LOG1(" STATUS OUT ep_csr->csr0l = %x\r\n", ep_csr->csr0l);
- _dcd.status_out = 0;
- if (req == REQUEST_TYPE_INVALID) {
- dcd_event_xfer_complete(rhport, ep_addr, total_bytes, XFER_RESULT_SUCCESS, false);
- } else {
- /* The next setup packet has already been received, it aborts
- * invoking callback function to avoid confusing TUSB stack. */
- TU_LOG1("Drop CONTROL_STAGE_ACK\r\n");
- }
- return true;
- }
+ pipe0_state_t* pipe0 = &_dcd.pipe0;
const unsigned dir_in = tu_edpt_dir(ep_addr);
- if (tu_edpt_dir(req) == dir_in) { /* DATA stage */
- TU_ASSERT(total_bytes <= _dcd.remaining_ctrl);
- const unsigned rem = _dcd.remaining_ctrl;
- const unsigned len = TU_MIN(TU_MIN(rem, 64), total_bytes);
- volatile void *fifo_ptr = &musb_regs->fifo[0];
- if (dir_in) {
- tu_hwfifo_write(fifo_ptr, buffer, len, NULL);
- _dcd.pipe0.buf = buffer + len;
- _dcd.pipe0.length = len;
- _dcd.pipe0.remaining = 0;
-
- _dcd.remaining_ctrl = rem - len;
- if ((len < 64) || (rem == len)) {
- _dcd.setup_packet.bmRequestType = REQUEST_TYPE_INVALID; /* Change to STATUS/SETUP stage */
- _dcd.status_out = 1;
- /* Flush TX FIFO and reverse the transaction direction. */
+ 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; /* Flush TX FIFO to return ACK. */
+ ep_csr->csr0l = MUSB_CSRL0_TXRDY;
}
- // TU_LOG1(" IN ep_csr->csr0l = %x\r\n", ep_csr->csr0l);
- } else {
- // TU_LOG1(" OUT ep_csr->csr0l = %x\r\n", ep_csr->csr0l);
- _dcd.pipe0.buf = buffer;
- _dcd.pipe0.length = len;
- _dcd.pipe0.remaining = len;
- ep_csr->csr0l = MUSB_CSRL0_RXRDYC; /* Clear RX FIFO to return ACK. */
- }
- } else if (dir_in) {
- // TU_LOG1(" STATUS IN ep_csr->csr0l = %x\r\n", ep_csr->csr0l);
- _dcd.pipe0.buf = NULL;
- _dcd.pipe0.length = 0;
- _dcd.pipe0.remaining = 0;
- /* Clear RX FIFO and reverse the transaction direction */
- ep_csr->csr0l = MUSB_CSRL0_RXRDYC | MUSB_CSRL0_DATAEND;
+ 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.
+ pipe0->state = PIPE0_STATE_STATUS_OUT_PENDING_XFER;
+ break;
+
+ 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;
}
+
return true;
}
-static void process_ep0(uint8_t rhport)
-{
+// 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_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;
- // TU_LOG1(" EP0 ep_csr->csr0l = %x\r\n", csrl);
- // 21.1.5: endpoint 0 service routine as peripheral
-
+ // 21.1.5: SentStall and SetupEnd must be checked before anything else.
if (csrl & MUSB_CSRL0_STALLED) {
- /* Returned STALL packet to HOST. */
- ep_csr->csr0l = 0; /* Clear STALL */
+ ep_csr->csr0l = 0;
+ pipe0->state = PIPE0_STATE_IDLE;
+ pipe0->deferred_setup_valid = false;
+ pipe0->rxrdy_consumed = false;
return;
}
- unsigned req = _dcd.setup_packet.bmRequestType;
if (csrl & MUSB_CSRL0_SETEND) {
- TU_LOG1(" ABORT by the next packets\r\n");
+ // 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;
- if (req != REQUEST_TYPE_INVALID && _dcd.pipe0.buf) {
- /* DATA stage was aborted by receiving STATUS or SETUP packet. */
- _dcd.pipe0.buf = NULL;
- _dcd.setup_packet.bmRequestType = REQUEST_TYPE_INVALID;
- dcd_event_xfer_complete(rhport,
- req & TUSB_DIR_IN_MASK,
- _dcd.pipe0.length - _dcd.pipe0.remaining,
- XFER_RESULT_SUCCESS, true);
+ 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 */
}
- req = REQUEST_TYPE_INVALID;
- if (!(csrl & MUSB_CSRL0_RXRDY)) return; /* Received SETUP packet */
}
+ // Receive Data (Setup or OUT)
if (csrl & MUSB_CSRL0_RXRDY) {
- /* Received SETUP or DATA OUT packet */
- if (req == REQUEST_TYPE_INVALID) {
- /* SETUP */
- TU_ASSERT(sizeof(tusb_control_request_t) == ep_csr->count0,);
- process_setup_packet(rhport);
- return;
+ if (pipe0->rxrdy_consumed) {
+ return; // stale latched IRQ: this RXRDY's packet was already drained
}
- if (_dcd.pipe0.buf) {
- /* DATA OUT */
- const unsigned vld = ep_csr->count0;
- const unsigned rem = _dcd.pipe0.remaining;
- const unsigned len = TU_MIN(TU_MIN(rem, 64), vld);
- volatile void *fifo_ptr = &musb_regs->fifo[0];
- tu_hwfifo_read(fifo_ptr, _dcd.pipe0.buf, len, NULL);
+ 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;
+ }
- _dcd.pipe0.remaining = rem - len;
- _dcd.remaining_ctrl -= len;
+ 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_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
+ }
+ // 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;
+ }
- _dcd.pipe0.buf = NULL;
- dcd_event_xfer_complete(rhport,
- tu_edpt_addr(0, TUSB_DIR_OUT),
- _dcd.pipe0.length - _dcd.pipe0.remaining,
- XFER_RESULT_SUCCESS, true);
- }
- return;
- }
+ // 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;
- /* When CSRL0 is zero, it means that completion of sending a any length packet
- * or receiving a zero length packet. */
- if (req != REQUEST_TYPE_INVALID && !tu_edpt_dir(req)) {
- /* STATUS IN */
- if (*(const uint16_t*)(uintptr_t)&_dcd.setup_packet == 0x0500) {
- /* The address must be changed on completion of the control transfer. */
- musb_regs->faddr = (uint8_t)_dcd.setup_packet.wValue;
+ default: break;
}
- _dcd.setup_packet.bmRequestType = REQUEST_TYPE_INVALID;
- dcd_event_xfer_complete(rhport,
- tu_edpt_addr(0, TUSB_DIR_IN),
- _dcd.pipe0.length - _dcd.pipe0.remaining,
- XFER_RESULT_SUCCESS, true);
+
return;
}
- if (_dcd.pipe0.buf) {
- /* DATA IN */
- _dcd.pipe0.buf = NULL;
- dcd_event_xfer_complete(rhport,
- tu_edpt_addr(0, TUSB_DIR_IN),
- _dcd.pipe0.length - _dcd.pipe0.remaining,
- XFER_RESULT_SUCCESS, true);
- }
-}
-static void process_edpt_n(uint8_t rhport, uint_fast8_t ep_addr)
-{
- bool completed;
- const unsigned dir_in = tu_edpt_dir(ep_addr);
- const unsigned epn = tu_edpt_number(ep_addr);
- const unsigned epn_minus1 = epn - 1;
-
- musb_regs_t* musb_regs = MUSB_REGS(rhport);
- musb_ep_csr_t* ep_csr = get_ep_csr(musb_regs, epn);
- if (dir_in) {
- // TU_LOG1(" TX CSRL%d = %x\r\n", epn, ep_csr->tx_csrl);
- if (ep_csr->tx_csrl & MUSB_TXCSRL1_STALLED) {
- ep_csr->tx_csrl &= ~(MUSB_TXCSRL1_STALLED | MUSB_TXCSRL1_UNDRN);
- return;
- }
- completed = handle_xfer_in(rhport, ep_addr);
- } else {
- // TU_LOG1(" RX CSRL%d = %x\r\n", epn, ep_csr->rx_csrl);
- if (ep_csr->rx_csrl & MUSB_RXCSRL1_STALLED) {
- ep_csr->rx_csrl &= ~(MUSB_RXCSRL1_STALLED | MUSB_RXCSRL1_OVER);
- return;
- }
- completed = handle_xfer_out(rhport, ep_addr);
+ 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;
}
- if (completed) {
- pipe_state_t *pipe = &_dcd.pipe[dir_in][epn_minus1];
- dcd_event_xfer_complete(rhport, ep_addr,
- pipe->length - pipe->remaining,
- XFER_RESULT_SUCCESS, true);
- }
+ /* 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 */
+ 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
- /* When bmRequestType is REQUEST_TYPE_INVALID(0xFF), a control transfer state is SETUP or STATUS stage. */
- _dcd.setup_packet.bmRequestType = REQUEST_TYPE_INVALID;
- _dcd.status_out = 0;
- /* When pipe0.buf has not NULL, DATA stage works in progress. */
- _dcd.pipe0.buf = NULL;
+ 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;
@@ -544,18 +695,22 @@ void dcd_int_disable(uint8_t rhport) {
musb_dcd_int_disable(rhport);
}
-// Receive Set Address request, mcu port must also include status IN response
+// Receive Set Address request. Stash the new address here; hardware faddr is
+// 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)
{
- (void)dev_addr;
musb_regs_t* musb_regs = MUSB_REGS(rhport);
musb_ep_csr_t* ep_csr = get_ep_csr(musb_regs, 0);
- _dcd.pipe0.buf = NULL;
- _dcd.pipe0.length = 0;
- _dcd.pipe0.remaining = 0;
- /* Clear RX FIFO to return ACK. */
+ 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
@@ -595,39 +750,36 @@ void dcd_sof_enable(uint8_t rhport, bool en)
//--------------------------------------------------------------------+
// Endpoint API
//--------------------------------------------------------------------+
-// static void edpt_setup(musb_regs_t* musb, uint8_t ep_addr, uint8_t ep_type, uint16_t ep_size){
-// const unsigned epn = tu_edpt_number(ep_addr);
-// const unsigned dir_in = tu_edpt_dir(ep_addr);
-// }
// Configure endpoint's registers according to descriptor
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 dir_in = 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 = &_dcd.pipe[dir_in][epn - 1];
+ pipe_state_t *pipe = pipe_get(epn, epdir);
pipe->buf = NULL;
pipe->length = 0;
pipe->remaining = 0;
+ pipe->armed = false;
musb_regs_t* musb = MUSB_REGS(rhport);
musb_ep_csr_t* ep_csr = get_ep_csr(musb, epn);
- const uint8_t is_rx = 1 - dir_in;
+ const uint8_t is_rx = (1 - epdir);
musb_ep_maxp_csr_t* maxp_csr = &ep_csr->maxp_csr[is_rx];
maxp_csr->maxp = mps;
maxp_csr->csrh = 0;
#if MUSB_CFG_SHARED_FIFO
- if (dir_in) {
+ if (epdir) {
maxp_csr->csrh |= MUSB_CSRH_TX_MODE;
}
#endif
hwfifo_flush(musb, epn, is_rx, true);
- TU_ASSERT(hwfifo_config(musb, epn, is_rx, mps, false));
+ TU_ASSERT(hwfifo_config(musb, epn, is_rx, mps, ep_desc->bmAttributes.xfer == TUSB_XFER_BULK));
musb->intren_ep[is_rx] |= TU_BIT(epn);
return true;
@@ -646,16 +798,17 @@ 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);
musb_dcd_int_disable(rhport);
- pipe_state_t *pipe = &_dcd.pipe[dir_in][epn - 1];
+ pipe_state_t *pipe = pipe_get(epn, dir_in);
pipe->buf = NULL;
pipe->length = 0;
pipe->remaining = 0;
+ pipe->armed = false;
musb_regs_t* musb = MUSB_REGS(rhport);
musb_ep_csr_t* ep_csr = get_ep_csr(musb, epn);
@@ -713,22 +866,22 @@ void dcd_edpt_close_all(uint8_t rhport)
// Submit a transfer, When complete dcd_event_xfer_complete() is invoked to notify the stack
bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t * buffer, uint16_t total_bytes, bool is_isr)
{
- (void) is_isr;
(void)rhport;
bool ret;
- // TU_LOG1("X %x %d\r\n", ep_addr, total_bytes);
unsigned const epnum = tu_edpt_number(ep_addr);
unsigned const ie = musb_dcd_get_int_enable(rhport);
musb_dcd_int_disable(rhport);
if (epnum) {
- _dcd.pipe_buf_is_fifo[tu_edpt_dir(ep_addr)] &= ~TU_BIT(epnum - 1);
- ret = edpt_n_xfer(rhport, ep_addr, buffer, total_bytes);
+ ret = edpt_n_xfer(rhport, ep_addr, buffer, total_bytes, false, is_isr);
} else {
- ret = edpt0_xfer(rhport, ep_addr, buffer, total_bytes);
+ (void) is_isr;
+ ret = edpt0_xfer(rhport, ep_addr, buffer, total_bytes, is_isr);
}
- if (ie) musb_dcd_int_enable(rhport);
+ if (ie) {
+ musb_dcd_int_enable(rhport);
+ }
return ret;
}
@@ -736,16 +889,13 @@ bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t * buffer, uint16_t t
// - optional, however, must be listed in usbd.c
bool dcd_edpt_xfer_fifo(uint8_t rhport, uint8_t ep_addr, tu_fifo_t * ff, uint16_t total_bytes, bool is_isr)
{
- (void) is_isr;
(void)rhport;
bool ret;
- // TU_LOG1("X %x %d\r\n", ep_addr, total_bytes);
unsigned const epnum = tu_edpt_number(ep_addr);
TU_ASSERT(epnum);
unsigned const ie = musb_dcd_get_int_enable(rhport);
musb_dcd_int_disable(rhport);
- _dcd.pipe_buf_is_fifo[tu_edpt_dir(ep_addr)] |= TU_BIT(epnum - 1);
- ret = edpt_n_xfer(rhport, ep_addr, (uint8_t*)ff, total_bytes);
+ ret = edpt_n_xfer(rhport, ep_addr, ff, total_bytes, true, is_isr);
if (ie) musb_dcd_int_enable(rhport);
return ret;
}
@@ -760,14 +910,27 @@ 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) { /* Ignore EP80 */
- _dcd.setup_packet.bmRequestType = REQUEST_TYPE_INVALID;
- _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 uint8_t is_rx = 1 - tu_edpt_dir(ep_addr);
+ const tusb_dir_t ep_dir = tu_edpt_dir(ep_addr);
+ const uint8_t is_rx = (ep_dir == TUSB_DIR_OUT ? 1u : 0u);
ep_csr->maxp_csr[is_rx].csrl = MUSB_CSRL_SEND_STALL(is_rx);
+ pipe_state_t* pipe = pipe_get(epn, ep_dir);
+ pipe->armed = false;
}
if (ie) musb_dcd_int_enable(rhport);
@@ -812,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);
@@ -822,21 +985,35 @@ void dcd_int_handler(uint8_t rhport) {
}
intr_tx &= musb_regs->intr_txen; /* Clear disabled interrupts */
- if (intr_tx & TU_BIT(0)) {
- process_ep0(rhport);
- intr_tx &= ~TU_BIT(0);
- }
+
while (intr_tx) {
- unsigned const num = __builtin_ctz(intr_tx);
- process_edpt_n(rhport, tu_edpt_addr(num, TUSB_DIR_IN));
- intr_tx &= ~TU_BIT(num);
+ const unsigned epnum = __builtin_ctz(intr_tx);
+ if (epnum == 0) {
+ process_ep0_isr(rhport); // EP0 has its own state machine (control transfers)
+ } else {
+ process_epin_isr(rhport, musb_regs, epnum);
+ }
+ intr_tx &= ~TU_BIT(epnum);
+
+ // Double packet endpoint: TxPktRdy is clear, and interrupt is generated immediately when 1st packet is written.
+ // Also catches EP0 SETUP arriving during bulk processing.
+ uint_fast8_t new_intr_tx = musb_regs->intr_tx;
+ new_intr_tx &= musb_regs->intr_txen;
+
+ intr_tx |= new_intr_tx;
}
intr_rx &= musb_regs->intr_rxen; /* Clear disabled interrupts */
while (intr_rx) {
- unsigned const num = __builtin_ctz(intr_rx);
- process_edpt_n(rhport, tu_edpt_addr(num, TUSB_DIR_OUT));
- intr_rx &= ~TU_BIT(num);
+ unsigned const epnum = __builtin_ctz(intr_rx);
+ 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
+ uint_fast8_t new_intr_rx = musb_regs->intr_rx;
+ new_intr_rx &= musb_regs->intr_rxen;
+
+ intr_rx |= new_intr_rx;
}
musb_regs->index = saved_index; // restore endpoint index
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/mentor/musb/musb_type.h b/src/portable/mentor/musb/musb_type.h
index b2f6492fa..3d3c3c834 100644
--- a/src/portable/mentor/musb/musb_type.h
+++ b/src/portable/mentor/musb/musb_type.h
@@ -300,7 +300,7 @@ TU_VERIFY_STATIC(sizeof(musb_regs_t) == 0x350, "size is not correct");
// Helper
//--------------------------------------------------------------------+
TU_ATTR_ALWAYS_INLINE static inline musb_ep_csr_t* get_ep_csr(musb_regs_t* musb_regs, unsigned epnum) {
- musb_regs->index = epnum;
+ musb_regs->index = (uint8_t)epnum;
return &musb_regs->indexed_csr;
}
@@ -336,7 +336,7 @@ TU_ATTR_ALWAYS_INLINE static inline musb_ep_csr_t* get_ep_csr(musb_regs_t* musb_
#define MUSB_CSRL_CLEAR_DATA_TOGGLE(_rx) (1u << ((_rx) ? 7 : 6))
// 0x13, 0x17: TX/RX CSRH
-#define MUSB_CSRH_DISABLE_DOUBLE_PACKET(_rx) (1u << 1)
+#define MUSB_CSRH_DISABLE_DOUBLE_PACKET (1u << 1)
#define MUSB_CSRH_TX_MODE (1u << 5) // 1 = TX, 0 = RX. only relevant for SHARED FIFO
#define MUSB_CSRH_ISO (1u << 6)
@@ -568,6 +568,16 @@ TU_ATTR_ALWAYS_INLINE static inline musb_ep_csr_t* get_ep_csr(musb_regs_t* musb_
//*****************************************************************************
//
+// The following are defines for the bit fields in the MUSB_O_TXMAXP / MUSB_O_RXMAXP
+// registers. Bits [10:0] carry the maximum packet size; bits [15:11] carry
+// numpackminus1 (HB-iso / HS-bulk multiplier - 1).
+//
+//*****************************************************************************
+#define MUSB_TXMAXP_PACKET_SIZE_M 0x07FFu
+#define MUSB_RXMAXP_PACKET_SIZE_M 0x07FFu
+
+//*****************************************************************************
+//
// The following are defines for the bit fields in the MUSB_O_TXCSRL1 register.
//
//*****************************************************************************
diff --git a/src/portable/microchip/samg/dcd_samg.c b/src/portable/microchip/samg/dcd_samg.c
index 4115eecc5..f8980b775 100644
--- a/src/portable/microchip/samg/dcd_samg.c
+++ b/src/portable/microchip/samg/dcd_samg.c
@@ -352,8 +352,8 @@ void dcd_edpt_clear_stall (uint8_t rhport, uint8_t ep_addr)
csr_clear(epnum, UDP_CSR_FORCESTALL_Msk);
// must also reset EP to clear data toggle
- UDP->UDP_RST_EP |= (1 << epnum);
- UDP->UDP_RST_EP &= ~(1 << epnum);
+ UDP->UDP_RST_EP |= (1u << epnum);
+ UDP->UDP_RST_EP &= ~(1u << epnum);
}
//--------------------------------------------------------------------+
diff --git a/src/portable/nordic/nrf5x/dcd_nrf5x.c b/src/portable/nordic/nrf5x/dcd_nrf5x.c
index 8a41c4790..befbaa338 100644
--- a/src/portable/nordic/nrf5x/dcd_nrf5x.c
+++ b/src/portable/nordic/nrf5x/dcd_nrf5x.c
@@ -36,6 +36,8 @@
#pragma GCC diagnostic ignored "-Wcast-qual"
#pragma GCC diagnostic ignored "-Wcast-align"
#pragma GCC diagnostic ignored "-Wunused-parameter"
+#pragma GCC diagnostic ignored "-Wconversion"
+#pragma GCC diagnostic ignored "-Wsign-conversion"
#endif
#include "nrf.h"
@@ -461,7 +463,7 @@ bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t* buffer, uint16_t to
xfer->actual_len = 0;
// Control endpoint with zero-length packet and opposite direction to 1st request byte --> status stage
- bool const control_status = (epnum == 0 && total_bytes == 0 && dir != tu_edpt_dir(NRF_USBD->BMREQUESTTYPE));
+ bool const control_status = (epnum == 0 && total_bytes == 0 && dir != tu_edpt_dir((uint8_t)NRF_USBD->BMREQUESTTYPE));
if (control_status) {
// The nRF doesn't interrupt on status transmit so we queue up a success response.
@@ -1047,6 +1049,12 @@ void tusb_hal_nrf_power_event(uint32_t event) {
NVIC_EnableIRQ(USBD_IRQn);
}
+ // Ensure HFCLK is requested in the current context. The hfclk_enable() in
+ // USB_EVT_DETECTED may have been pre-SoftDevice. After Softdevice is
+ // enabled, HFXO is physically off again. So any caller that fires
+ // USB_EVT_READY post-SD would hang here.
+ hfclk_enable();
+
// Wait for HFCLK
while (!hfclk_running()) {}
diff --git a/src/portable/nuvoton/nuc120/dcd_nuc120.c b/src/portable/nuvoton/nuc120/dcd_nuc120.c
index d9a0e3fa8..2edb1bc7a 100644
--- a/src/portable/nuvoton/nuc120/dcd_nuc120.c
+++ b/src/portable/nuvoton/nuc120/dcd_nuc120.c
@@ -253,13 +253,13 @@ bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const * p_endpoint_desc)
/* mine the data for the information we need */
int const dir = tu_edpt_dir(p_endpoint_desc->bEndpointAddress);
- int const size = tu_edpt_packet_size(p_endpoint_desc);
+ uint16_t const size = tu_edpt_packet_size(p_endpoint_desc);
tusb_xfer_type_t const type = (tusb_xfer_type_t) p_endpoint_desc->bmAttributes.xfer;
struct xfer_ctl_t *xfer = &xfer_table[ep - USBD->EP];
/* allocate buffer from USB RAM */
ep->BUFSEG = bufseg_addr;
- bufseg_addr += size;
+ bufseg_addr += (uint32_t)size;
TU_ASSERT(bufseg_addr <= USBD_BUF_SIZE);
/* construct USB Configuration Register value and then write it */
@@ -435,7 +435,7 @@ void dcd_int_handler(uint8_t rhport)
/* given ACK from host has happened, we can now set the address (if not already done) */
if((USBD->FADDR != assigned_address) && (USBD->FADDR == 0)) USBD->FADDR = assigned_address;
- uint16_t const available_bytes = USBD->EP[PERIPH_EP0].MXPLD;
+ uint16_t const available_bytes = (uint16_t)USBD->EP[PERIPH_EP0].MXPLD;
active_ep0_xfer = (available_bytes == xfer_table[PERIPH_EP0].max_packet_size);
@@ -453,7 +453,7 @@ void dcd_int_handler(uint8_t rhport)
{
USBD->INTSTS = mask;
- uint16_t const available_bytes = ep->MXPLD;
+ uint16_t const available_bytes = (uint16_t)ep->MXPLD;
uint8_t const ep_addr = decode_ep_addr(ep);
bool const out_ep = !(ep_addr & TUSB_DIR_IN_MASK);
diff --git a/src/portable/nuvoton/nuc121/dcd_nuc121.c b/src/portable/nuvoton/nuc121/dcd_nuc121.c
index 42fb58a0a..008c9df6b 100644
--- a/src/portable/nuvoton/nuc121/dcd_nuc121.c
+++ b/src/portable/nuvoton/nuc121/dcd_nuc121.c
@@ -42,6 +42,8 @@
#ifdef __GNUC__
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wredundant-decls"
+#pragma GCC diagnostic ignored "-Wconversion"
+#pragma GCC diagnostic ignored "-Wsign-conversion"
#endif
#include "NuMicro.h"
@@ -291,7 +293,7 @@ bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const * p_endpoint_desc)
/* mine the data for the information we need */
int const dir = tu_edpt_dir(p_endpoint_desc->bEndpointAddress);
- int const size = tu_edpt_packet_size(p_endpoint_desc);
+ uint16_t const size = tu_edpt_packet_size(p_endpoint_desc);
tusb_xfer_type_t const type = (tusb_xfer_type_t) p_endpoint_desc->bmAttributes.xfer;
struct xfer_ctl_t *xfer = &xfer_table[ep - USBD->EP];
@@ -478,7 +480,7 @@ void dcd_int_handler(uint8_t rhport)
{
if (status & USBD_INTSTS_EPEVT0_Msk) /* PERIPH_EP0 (EP0_IN) event: this is treated separately from the rest */
{
- uint16_t const available_bytes = USBD->EP[PERIPH_EP0].MXPLD;
+ uint16_t const available_bytes = (uint16_t)USBD->EP[PERIPH_EP0].MXPLD;
active_ep0_xfer = (available_bytes == xfer_table[PERIPH_EP0].max_packet_size);
@@ -496,7 +498,7 @@ void dcd_int_handler(uint8_t rhport)
{
USBD->INTSTS = mask;
- uint16_t const available_bytes = ep->MXPLD;
+ uint16_t const available_bytes = (uint16_t)ep->MXPLD;
uint8_t const ep_addr = decode_ep_addr(ep);
bool const out_ep = !(ep_addr & TUSB_DIR_IN_MASK);
diff --git a/src/portable/nuvoton/nuc505/dcd_nuc505.c b/src/portable/nuvoton/nuc505/dcd_nuc505.c
index ca17d6251..a0f3d4c3f 100644
--- a/src/portable/nuvoton/nuc505/dcd_nuc505.c
+++ b/src/portable/nuvoton/nuc505/dcd_nuc505.c
@@ -42,6 +42,8 @@
#ifdef __GNUC__
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wredundant-decls"
+#pragma GCC diagnostic ignored "-Wconversion"
+#pragma GCC diagnostic ignored "-Wsign-conversion"
#endif
#include "NUC505Series.h"
@@ -338,13 +340,13 @@ bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const * p_endpoint_desc)
/* mine the data for the information we need */
int const dir = tu_edpt_dir(p_endpoint_desc->bEndpointAddress);
- int const size = tu_edpt_packet_size(p_endpoint_desc);
+ uint16_t const size = tu_edpt_packet_size(p_endpoint_desc);
tusb_xfer_type_t const type = p_endpoint_desc->bmAttributes.xfer;
struct xfer_ctl_t *xfer = &xfer_table[ep - USBD->EP];
/* allocate buffer from USB RAM */
ep->EPBUFSTART = bufseg_addr;
- bufseg_addr += size;
+ bufseg_addr += (uint32_t)size;
ep->EPBUFEND = bufseg_addr - 1;
TU_ASSERT(bufseg_addr <= USBD_BUF_SIZE);
diff --git a/src/portable/nxp/lpc17_40/dcd_lpc17_40.c b/src/portable/nxp/lpc17_40/dcd_lpc17_40.c
index 349229c8d..2840c6d5e 100644
--- a/src/portable/nxp/lpc17_40/dcd_lpc17_40.c
+++ b/src/portable/nxp/lpc17_40/dcd_lpc17_40.c
@@ -131,7 +131,7 @@ static uint8_t sie_read (uint8_t cmd_code)
//--------------------------------------------------------------------+
static inline uint8_t ep_addr2idx(uint8_t ep_addr)
{
- return 2*(ep_addr & 0x0F) + ((ep_addr & TUSB_DIR_IN_MASK) ? 1 : 0);
+ return (uint8_t)(2*(ep_addr & 0x0F) + ((ep_addr & TUSB_DIR_IN_MASK) ? 1 : 0));
}
static void set_ep_size(uint8_t ep_id, uint16_t max_packet_size)
@@ -243,7 +243,7 @@ void dcd_sof_enable(uint8_t rhport, bool en)
//--------------------------------------------------------------------+
static inline uint8_t byte2dword(uint8_t bytes)
{
- return (bytes + 3) / 4; // length in dwords
+ return (uint8_t)((bytes + 3) / 4); // length in dwords
}
static void control_ep_write(void const * buffer, uint8_t len)
diff --git a/src/portable/nxp/lpc_ip3511/dcd_lpc_ip3511.c b/src/portable/nxp/lpc_ip3511/dcd_lpc_ip3511.c
index 5f4a441dc..8adf0f840 100644
--- a/src/portable/nxp/lpc_ip3511/dcd_lpc_ip3511.c
+++ b/src/portable/nxp/lpc_ip3511/dcd_lpc_ip3511.c
@@ -243,7 +243,7 @@ TU_ATTR_ALWAYS_INLINE static inline uint16_t get_buf_offset(void const * buffer)
}
TU_ATTR_ALWAYS_INLINE static inline uint8_t ep_addr2id(uint8_t ep_addr) {
- return 2*(ep_addr & 0x0F) + ((ep_addr & TUSB_DIR_IN_MASK) ? 1 : 0);
+ return (uint8_t)(2*(ep_addr & 0x0F) + ((ep_addr & TUSB_DIR_IN_MASK) ? 1 : 0));
}
TU_ATTR_ALWAYS_INLINE static inline bool ep_is_iso(ep_cmd_sts_t* ep_cs, bool is_highspeed) {
@@ -539,8 +539,8 @@ static void process_xfer_isr(uint8_t rhport, uint32_t int_status) {
uint16_t buf_nbytes;
if ( rhport_is_highspeed(rhport) ) {
- buf_offset = ep_cs->buffer_hs.offset;
- buf_nbytes = ep_cs->buffer_hs.nbytes;
+ buf_offset = (uint16_t)ep_cs->buffer_hs.offset;
+ buf_nbytes = (uint16_t)ep_cs->buffer_hs.nbytes;
#if TU_CHECK_MCU(OPT_MCU_LPC54)
// LPC54 Errata USB.2: In USB high-speed device mode, the NBytes field is not correct after BULK IN transfer
@@ -550,8 +550,8 @@ static void process_xfer_isr(uint8_t rhport, uint32_t int_status) {
}
#endif
} else {
- buf_offset = ep_cs->buffer_fs.offset;
- buf_nbytes = ep_cs->buffer_fs.nbytes;
+ buf_offset = (uint16_t)ep_cs->buffer_fs.offset;
+ buf_nbytes = (uint16_t)ep_cs->buffer_fs.nbytes;
}
xfer_dma->xferred_bytes += xfer_dma->nbytes - buf_nbytes;
diff --git a/src/portable/raspberrypi/rp2040/hcd_rp2040.c b/src/portable/raspberrypi/rp2040/hcd_rp2040.c
index 02a4e055e..064834efb 100644
--- a/src/portable/raspberrypi/rp2040/hcd_rp2040.c
+++ b/src/portable/raspberrypi/rp2040/hcd_rp2040.c
@@ -617,10 +617,16 @@ bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t *b
io_rw_32 *buf_reg = dpram_int_ep_buffer_ctrl(ep->interrupt_num);
rp2usb_xfer_start(ep, ep_reg, buf_reg, buffer, NULL, buflen);
} else {
- // Control endpoint can change direction 0x00 <-> 0x80 when changing stages
- if (ep_addr != ep->ep_addr) {
+ // Control transfer data and status stages always start with DATA1, regardless of
+ // whether the direction changed since the previous stage. SET_REPORT (and any other
+ // host-to-device class request with an OUT data stage) keeps the same direction
+ // across SETUP -> DATA, so we cannot key off "direction changed" -- we must reset
+ // next_pid every time hcd_edpt_xfer is invoked on ep 0. Without this, the data stage
+ // of SET_REPORT goes out as DATA0 because ep->next_pid is still 0 from hcd_edpt_open(),
+ // which strict devices treat as a protocol violation and disconnect.
+ if (tu_edpt_number(ep_addr) == 0) {
ep->ep_addr = ep_addr;
- ep->next_pid = 1; // data and status stage start with DATA1
+ ep->next_pid = 1;
}
// If EPX is busy with another transfer, mark as pending
diff --git a/src/portable/renesas/rusb2/dcd_rusb2.c b/src/portable/renesas/rusb2/dcd_rusb2.c
index e2a51a5ca..adbb53787 100644
--- a/src/portable/renesas/rusb2/dcd_rusb2.c
+++ b/src/portable/renesas/rusb2/dcd_rusb2.c
@@ -93,7 +93,9 @@ static unsigned find_pipe(unsigned xfer_type) {
const uint8_t idx_last = pipe_idx_arr[xfer_type][1];
for (int i = idx_last; i >= idx_first; i--) {
- if (0 == _dcd.pipe[i].ep) return i;
+ if (0 == _dcd.pipe[i].ep) {
+ return (unsigned)i;
+ }
}
return 0;
@@ -117,10 +119,10 @@ static volatile reg_pipetre_t* get_pipetre(rusb2_reg_t *rusb, unsigned num) {
static volatile uint16_t* ep_addr_to_pipectr(uint8_t rhport, unsigned ep_addr) {
rusb2_reg_t *rusb = RUSB2_REG(rhport);
- const unsigned epn = tu_edpt_number(ep_addr);
+ const unsigned epn = tu_edpt_number((uint8_t)ep_addr);
if (epn) {
- const unsigned dir = tu_edpt_dir(ep_addr);
+ const unsigned dir = tu_edpt_dir((uint8_t)ep_addr);
const unsigned num = _dcd.ep[dir][epn];
return get_pipectr(rusb, num);
} else {
@@ -129,11 +131,11 @@ static volatile uint16_t* ep_addr_to_pipectr(uint8_t rhport, unsigned ep_addr) {
}
static uint16_t edpt0_max_packet_size(rusb2_reg_t* rusb) {
- return rusb->DCPMAXP_b.MXPS;
+ return (uint16_t)rusb->DCPMAXP_b.MXPS;
}
static uint16_t edpt_max_packet_size(rusb2_reg_t *rusb, unsigned num) {
- rusb->PIPESEL = num;
+ rusb->PIPESEL = (uint16_t)num;
return rusb->PIPEMAXP;
}
@@ -285,7 +287,7 @@ static bool pipe_xfer_out(rusb2_reg_t* rusb, unsigned num)
const uint16_t mps = edpt_max_packet_size(rusb, num);
pipe_wait_for_ready(rusb, num);
- const uint16_t vld = rusb->D0FIFOCTR_b.DTLN;
+ const uint16_t vld = (uint16_t)rusb->D0FIFOCTR_b.DTLN;
const uint16_t len = tu_min16(tu_min16(rem, mps), vld);
void *buf = pipe->buf;
@@ -498,7 +500,7 @@ static void process_bus_reset(uint8_t rhport)
volatile uint16_t *ctr = (volatile uint16_t*)((uintptr_t) (&rusb->PIPE_CTR[0]));
volatile uint16_t *tre = (volatile uint16_t*)((uintptr_t) (&rusb->PIPE_TR[0].E));
- for (int i = 1; i <= 5; ++i) {
+ for (uint16_t i = 1; i <= 5; ++i) {
rusb->PIPESEL = i;
rusb->PIPECFG = 0;
*ctr = RUSB2_PIPE_CTR_ACLRM_Msk;
@@ -508,7 +510,7 @@ static void process_bus_reset(uint8_t rhport)
tre += 2;
}
- for (int i = 6; i <= 9; ++i) {
+ for (uint16_t i = 6; i <= 9; ++i) {
rusb->PIPESEL = i;
rusb->PIPECFG = 0;
*ctr = RUSB2_PIPE_CTR_ACLRM_Msk;
@@ -542,7 +544,7 @@ static void process_bus_reset(uint8_t rhport)
static void process_set_address(uint8_t rhport)
{
rusb2_reg_t* rusb = RUSB2_REG(rhport);
- const uint16_t addr = rusb->USBADDR_b.USBADDR;
+ const uint16_t addr = (uint16_t)rusb->USBADDR_b.USBADDR;
if (!addr) {
return;
}
@@ -706,7 +708,7 @@ bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const * ep_desc)
(void)rhport;
rusb2_reg_t * rusb = RUSB2_REG(rhport);
- const unsigned ep_addr = ep_desc->bEndpointAddress;
+ const uint8_t ep_addr = ep_desc->bEndpointAddress;
const unsigned epn = tu_edpt_number(ep_addr);
const unsigned dir = tu_edpt_dir(ep_addr);
const unsigned xfer = ep_desc->bmAttributes.xfer;
@@ -770,8 +772,10 @@ void dcd_edpt_close_all(uint8_t rhport)
dcd_int_disable(rhport);
while (--i) { /* Close all pipes except 0 */
const unsigned ep_addr = _dcd.pipe[i].ep;
- if (!ep_addr) continue;
- dcd_edpt_close(rhport, ep_addr);
+ if (!ep_addr) {
+ continue;
+ }
+ dcd_edpt_close(rhport, (uint8_t)ep_addr);
}
dcd_int_enable(rhport);
}
@@ -783,10 +787,10 @@ void dcd_edpt_close(uint8_t rhport, uint8_t ep_addr)
const unsigned dir = tu_edpt_dir(ep_addr);
const unsigned num = _dcd.ep[dir][epn];
- rusb->BRDYENB &= ~TU_BIT(num);
+ rusb->BRDYENB &= (uint16_t)~TU_BIT(num);
volatile uint16_t *ctr = get_pipectr(rusb, num);
*ctr = 0;
- rusb->PIPESEL = num;
+ rusb->PIPESEL = (uint16_t)num;
rusb->PIPECFG = 0;
_dcd.pipe[num].ep = 0;
_dcd.ep[dir][epn] = 0;
@@ -860,7 +864,7 @@ void dcd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr)
*ctr = RUSB2_PIPE_CTR_PID_BUF;
} else {
const unsigned num = _dcd.ep[0][tu_edpt_number(ep_addr)];
- rusb->PIPESEL = num;
+ rusb->PIPESEL = (uint16_t)num;
if (rusb->PIPECFG_b.TYPE != 1) {
*ctr = RUSB2_PIPE_CTR_PID_BUF;
}
diff --git a/src/portable/st/stm32_fsdev/dcd_stm32_fsdev.c b/src/portable/st/stm32_fsdev/dcd_stm32_fsdev.c
index 8b4719b21..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
@@ -259,7 +260,7 @@ static void handle_ctr_tx(uint32_t ep_id) {
}
if (xfer->total_len != xfer->queued_len) {
- dcd_transmit_packet(xfer, ep_id);
+ dcd_transmit_packet(xfer, (uint16_t)ep_id);
} else {
dcd_event_xfer_complete(0, ep_num | TUSB_DIR_IN_MASK, xfer->queued_len, XFER_RESULT_SUCCESS, true);
}
@@ -267,7 +268,7 @@ static void handle_ctr_tx(uint32_t ep_id) {
static void handle_ctr_setup(uint32_t ep_id) {
uint16_t rx_count = btable_get_count(ep_id, BTABLE_BUF_RX);
- uint16_t rx_addr = btable_get_addr(ep_id, BTABLE_BUF_RX);
+ uint16_t rx_addr = (uint16_t)btable_get_addr(ep_id, BTABLE_BUF_RX);
uint8_t setup_packet[8] TU_ATTR_ALIGNED(4);
tu_hwfifo_read(PMA_BUF_AT(rx_addr), setup_packet, rx_count, NULL);
@@ -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);
}
@@ -393,26 +394,8 @@ void dcd_int_handler(uint8_t rhport) {
const uint32_t ep_reg = ep_read(ep_id);
if (ep_reg & U_EP_CTR_RX) {
- #ifdef CFG_TUSB_FSDEV_32BIT
- /* https://www.st.com/resource/en/errata_sheet/es0561-stm32h503cbebkbrb-device-errata-stmicroelectronics.pdf
- * https://www.st.com/resource/en/errata_sheet/es0587-stm32u535xx-and-stm32u545xx-device-errata-stmicroelectronics.pdf
- * From H503/U535 errata: Buffer description table update completes after CTR interrupt triggers
- * Description:
- * - During OUT transfers, the correct transfer interrupt (CTR) is triggered a little before the last USB SRAM
- * accesses have completed. If the software responds quickly to the interrupt, the full buffer contents may not be
- * correct. Workaround:
- * - Software should ensure that a small delay is included before accessing the SRAM contents. This delay
- * should be 800 ns in Full Speed mode and 6.4 μs in Low Speed mode
- * - Since H5 can run up to 250Mhz -> 1 cycle = 4ns. Per errata, we need to wait 200 cycles. Though executing code
- * also takes time, so we'll wait 60 cycles (count = 20).
- * - Since Low Speed mode is not supported/popular, we will ignore it for now.
- *
- * Note: this errata may also apply to G0, U5, H5 etc.
- */
- volatile uint32_t cycle_count = 20; // defined as PCD_RX_PMA_CNT in stm32 hal_driver
- while (cycle_count > 0U) {
- cycle_count--; // each count take 3 cycles (1 for sub, jump, and compare)
- }
+ #if defined(TUP_USBIP_FSDEV_STM32) && defined(CFG_TUSB_FSDEV_32BIT)
+ fsdev_btable_workaround_delay(false);
#endif
if (ep_reg & U_EP_SETUP) {
@@ -531,8 +514,8 @@ void edpt0_open(uint8_t rhport) {
xfer_status[0][1].max_packet_size = CFG_TUD_ENDPOINT0_SIZE;
xfer_status[0][1].ep_idx = 0;
- uint16_t pma_addr0 = dcd_pma_alloc(CFG_TUD_ENDPOINT0_SIZE, false);
- uint16_t pma_addr1 = dcd_pma_alloc(CFG_TUD_ENDPOINT0_SIZE, false);
+ uint16_t pma_addr0 = (uint16_t)dcd_pma_alloc(CFG_TUD_ENDPOINT0_SIZE, false);
+ uint16_t pma_addr1 = (uint16_t)dcd_pma_alloc(CFG_TUD_ENDPOINT0_SIZE, false);
btable_set_addr(0, BTABLE_BUF_RX, pma_addr0);
btable_set_addr(0, BTABLE_BUF_TX, pma_addr1);
@@ -574,7 +557,7 @@ bool dcd_edpt_open(uint8_t rhport, const tusb_desc_endpoint_t *desc_ep) {
}
/* Create a packet memory buffer area. */
- uint16_t pma_addr = dcd_pma_alloc(packet_size, false);
+ uint16_t pma_addr = (uint16_t)dcd_pma_alloc(packet_size, false);
btable_set_addr(ep_idx, dir == TUSB_DIR_IN ? BTABLE_BUF_TX : BTABLE_BUF_RX, pma_addr);
xfer_ctl_t *xfer = xfer_ctl_ptr(ep_num, dir);
@@ -624,17 +607,17 @@ bool dcd_edpt_iso_alloc(uint8_t rhport, uint8_t ep_addr, uint16_t largest_packet
#if CFG_TUD_FSDEV_DOUBLE_BUFFERED_ISO_EP != 0
uint32_t pma_addr = dcd_pma_alloc(largest_packet_size, true);
- uint16_t pma_addr2 = pma_addr >> 16;
+ uint16_t pma_addr2 = (uint16_t)(pma_addr >> 16);
#else
uint32_t pma_addr = dcd_pma_alloc(largest_packet_size, false);
- uint16_t pma_addr2 = pma_addr;
+ uint16_t pma_addr2 = (uint16_t)pma_addr;
#endif
#if FSDEV_USE_SBUF_ISO == 0
- btable_set_addr(ep_idx, 0, pma_addr);
+ btable_set_addr(ep_idx, 0, (uint16_t)pma_addr);
btable_set_addr(ep_idx, 1, pma_addr2);
#else
- btable_set_addr(ep_idx, dir == TUSB_DIR_IN ? BTABLE_BUF_TX : BTABLE_BUF_RX, pma_addr);
+ btable_set_addr(ep_idx, dir == TUSB_DIR_IN ? BTABLE_BUF_TX : BTABLE_BUF_RX, (uint16_t)pma_addr);
(void)pma_addr2;
#endif
diff --git a/src/portable/st/stm32_fsdev/fsdev_common.c b/src/portable/st/stm32_fsdev/fsdev_common.c
index 003bcd069..7c4572a1e 100644
--- a/src/portable/st/stm32_fsdev/fsdev_common.c
+++ b/src/portable/st/stm32_fsdev/fsdev_common.c
@@ -81,11 +81,11 @@ uint16_t pma_align_buffer_size(uint16_t size, uint8_t* blsize, uint8_t* num_bloc
if (size > 62) {
block_in_bytes = 32;
*blsize = 1;
- *num_block = tu_div_ceil(size, 32);
+ *num_block = (uint8_t)tu_div_ceil(size, 32);
} else {
block_in_bytes = 2;
*blsize = 0;
- *num_block = tu_div_ceil(size, 2);
+ *num_block = (uint8_t)tu_div_ceil(size, 2);
}
return (*num_block) * block_in_bytes;
diff --git a/src/portable/st/stm32_fsdev/fsdev_common.h b/src/portable/st/stm32_fsdev/fsdev_common.h
index 140ff1d61..af84b8b97 100644
--- a/src/portable/st/stm32_fsdev/fsdev_common.h
+++ b/src/portable/st/stm32_fsdev/fsdev_common.h
@@ -345,7 +345,7 @@ TU_ATTR_ALWAYS_INLINE static inline void ep_write_clear_ctr(uint32_t ep_id, tusb
uint32_t reg = FSDEV_REG->ep[ep_id].reg;
reg |= U_EP_CTR_TX | U_EP_CTR_RX;
reg &= U_EPREG_MASK;
- reg &= ~(1 << (U_EP_CTR_TX_Pos + (dir == TUSB_DIR_IN ? 0 : 8)));
+ reg &= ~(1u << (U_EP_CTR_TX_Pos + (dir == TUSB_DIR_IN ? 0u : 8u)));
ep_write(ep_id, reg, false);
}
@@ -378,7 +378,7 @@ TU_ATTR_ALWAYS_INLINE static inline void ch_write_clear_ctr(uint32_t ch_id, tusb
uint32_t reg = FSDEV_REG->ep[ch_id].reg;
reg |= U_EP_CTR_TX | U_EP_CTR_RX;
reg &= U_EPREG_MASK;
- reg &= ~(1 << (U_EP_CTR_TX_Pos + (dir == TUSB_DIR_IN ? 8 : 0)));
+ reg &= ~(1u << (U_EP_CTR_TX_Pos + (dir == TUSB_DIR_IN ? 8u : 0u)));
ep_write(ch_id, reg, false);
}
diff --git a/src/portable/st/stm32_fsdev/fsdev_stm32.h b/src/portable/st/stm32_fsdev/fsdev_stm32.h
index a63592c5d..93cdac808 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
@@ -164,20 +168,24 @@
#define FSDEV_USE_SBUF_ISO 0
#endif
-//--------------------------------------------------------------------+
-//
-//--------------------------------------------------------------------+
-
+// STM32L1 calls it USB_FS_WKUP_IRQn; alias so the commented USBWakeUp_IRQn below
+// can be uncommented as-is.
#if TU_CHECK_MCU(OPT_MCU_STM32L1) && !defined(USBWakeUp_IRQn)
#define USBWakeUp_IRQn USB_FS_WKUP_IRQn
#endif
+// USB interrupt vectors to enable in NVIC. The EXTI-line USB wakeup interrupt
+// (USBWakeUp_IRQn, and USBWakeUp_RMP_IRQn on F3) is left commented out: resume is
+// handled in-band via ISTR.WKUP in the USB_LP/HP ISR; the EXTI line is only needed to
+// wake the core from STOP mode, which this driver does not implement (it never arms or
+// clears that EXTI line, so enabling its NVIC vector can only spuriously fire/freeze).
+// TODO: uncomment USBWakeUp_IRQn (+ arm/clear its EXTI line) when adding STOP-mode wakeup.
static const IRQn_Type fsdev_irq[] = {
#if TU_CHECK_MCU(OPT_MCU_STM32F0, OPT_MCU_STM32L0, OPT_MCU_STM32L4, OPT_MCU_STM32U5)
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
@@ -188,15 +196,15 @@ static const IRQn_Type fsdev_irq[] = {
#elif CFG_TUSB_MCU == OPT_MCU_STM32F1
USB_HP_CAN1_TX_IRQn,
USB_LP_CAN1_RX0_IRQn,
- USBWakeUp_IRQn,
+ //USBWakeUp_IRQn,
#elif CFG_TUSB_MCU == OPT_MCU_STM32F3
USB_HP_CAN_TX_IRQn,
USB_LP_CAN_RX0_IRQn,
- USBWakeUp_IRQn,
+ //USBWakeUp_IRQn,
#elif TU_CHECK_MCU(OPT_MCU_STM32G4, OPT_MCU_STM32L1)
USB_HP_IRQn,
USB_LP_IRQn,
- USBWakeUp_IRQn,
+ //USBWakeUp_IRQn,
#elif CFG_TUSB_MCU == OPT_MCU_STM32WB
USB_HP_IRQn,
USB_LP_IRQn,
@@ -219,7 +227,7 @@ TU_ATTR_ALWAYS_INLINE static inline void fsdev_int_enable(uint8_t rhport) {
if (SYSCFG->CFGR1 & SYSCFG_CFGR1_USB_IT_RMP) {
NVIC_EnableIRQ(USB_HP_IRQn);
NVIC_EnableIRQ(USB_LP_IRQn);
- NVIC_EnableIRQ(USBWakeUp_RMP_IRQn);
+ //NVIC_EnableIRQ(USBWakeUp_RMP_IRQn);
} else
#endif
{
@@ -239,7 +247,7 @@ TU_ATTR_ALWAYS_INLINE static inline void fsdev_int_disable(uint8_t rhport) {
if (SYSCFG->CFGR1 & SYSCFG_CFGR1_USB_IT_RMP) {
NVIC_DisableIRQ(USB_HP_IRQn);
NVIC_DisableIRQ(USB_LP_IRQn);
- NVIC_DisableIRQ(USBWakeUp_RMP_IRQn);
+ //NVIC_DisableIRQ(USBWakeUp_RMP_IRQn);
} else
#endif
{
@@ -252,6 +260,64 @@ TU_ATTR_ALWAYS_INLINE static inline void fsdev_int_disable(uint8_t rhport) {
}
//--------------------------------------------------------------------+
+// STM32 FSDEV PMA Buffer Description Table errata workaround
+//--------------------------------------------------------------------+
+
+#ifdef CFG_TUSB_FSDEV_32BIT
+/* Errata: Buffer description table update completes after CTR interrupt triggers
+ * https://www.st.com/resource/en/errata_sheet/es0561-stm32h503cbebkbrb-device-errata-stmicroelectronics.pdf
+ * https://www.st.com/resource/en/errata_sheet/es0587-stm32u535xx-and-stm32u545xx-device-errata-stmicroelectronics.pdf
+ *
+ * CTR may trigger before final PMA SRAM accesses complete on OUT transfers.
+ * Insert delay before reading PMA count/data.
+ * Max CPU frequency in Hz, used to derive conservative FSDEV PMA delay defaults.
+ */
+#if CFG_TUSB_MCU == OPT_MCU_STM32H5
+ #define FSDEV_STM32_CPU_HZ 250000000U
+#elif CFG_TUSB_MCU == OPT_MCU_STM32U5
+ #define FSDEV_STM32_CPU_HZ 160000000U
+#elif CFG_TUSB_MCU == OPT_MCU_STM32U3
+ #define FSDEV_STM32_CPU_HZ 96000000U
+#elif CFG_TUSB_MCU == OPT_MCU_STM32U0
+ #define FSDEV_STM32_CPU_HZ 56000000U
+#elif CFG_TUSB_MCU == OPT_MCU_STM32G0
+ #define FSDEV_STM32_CPU_HZ 64000000U
+#elif CFG_TUSB_MCU == OPT_MCU_STM32C0
+ #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_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_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) {
+ cycle_count--;
+ }
+}
+#endif
+
+//--------------------------------------------------------------------+
// Connect / Disconnect
//--------------------------------------------------------------------+
diff --git a/src/portable/st/stm32_fsdev/hcd_stm32_fsdev.c b/src/portable/st/stm32_fsdev/hcd_stm32_fsdev.c
index 18685dbdc..f9201651a 100644
--- a/src/portable/st/stm32_fsdev/hcd_stm32_fsdev.c
+++ b/src/portable/st/stm32_fsdev/hcd_stm32_fsdev.c
@@ -58,20 +58,6 @@
TU_VERIFY_STATIC(CFG_TUH_FSDEV_ENDPOINT_MAX <= 255, "currently only use 8-bit for index");
-#if CFG_TUSB_MCU == OPT_MCU_STM32H5
- #define CPU_FREQUENCY_MHZ 250U
-#elif CFG_TUSB_MCU == OPT_MCU_STM32U5
- #define CPU_FREQUENCY_MHZ 160U
-#elif CFG_TUSB_MCU == OPT_MCU_STM32U3
- #define CPU_FREQUENCY_MHZ 96U
-#elif CFG_TUSB_MCU == OPT_MCU_STM32G0
- #define CPU_FREQUENCY_MHZ 64U
-#elif CFG_TUSB_MCU == OPT_MCU_STM32C0
- #define CPU_FREQUENCY_MHZ 48U
-#else
- #error "CPU_FREQUENCY_MHZ not defined for this STM32 MCU"
-#endif
-
enum {
HCD_XFER_ERROR_MAX = 3,
HCD_XFER_NAK_MAX = 15,
@@ -165,35 +151,9 @@ static inline void channel_write_status(uint8_t ch_id, uint32_t ch_reg, tusb_dir
}
static inline uint16_t channel_get_rx_count(uint8_t ch_id) {
- /* https://www.st.com/resource/en/errata_sheet/es0561-stm32h503cbebkbrb-device-errata-stmicroelectronics.pdf
- * https://www.st.com/resource/en/errata_sheet/es0587-stm32u535xx-and-stm32u545xx-device-errata-stmicroelectronics.pdf
- * From H503/U535 errata: Buffer description table update completes after CTR interrupt triggers
- * Description:
- * - During OUT transfers, the correct transfer interrupt (CTR) is triggered a little before the last USB SRAM accesses
- * have completed. If the software responds quickly to the interrupt, the full buffer contents may not be correct.
- * Workaround:
- * - Software should ensure that a small delay is included before accessing the SRAM contents. This delay
- * should be 800 ns in Full Speed mode and 6.4 μs in Low Speed mode
- *
- * Note: this errata may also apply to G0, U5, H5 etc.
- *
- * We choose the delay count based on max CPU frequency (in MHz) to ensure the delay is at least the required time.
- */
-
uint32_t ch_reg = ch_read(ch_id);
- if (FSDEV_REG->ISTR & U_ISTR_LS_DCONN || ch_reg & U_EP_LSEP) {
- // Low speed mode: 6.4 us delay -> about 2 cycles per MHz
- volatile uint32_t cycle_count = CPU_FREQUENCY_MHZ * 2U;
- while (cycle_count > 0U) {
- cycle_count--; // each count take 3 cycles (1 for sub, jump, and compare)
- }
- } else {
- // Full speed mode: 800 ns delay -> about 0.25 cycles per MHz
- volatile uint32_t cycle_count = CPU_FREQUENCY_MHZ / 4U;
- while (cycle_count > 0U) {
- cycle_count--; // each count take 3 cycles (1 for sub, jump, and compare)
- }
- }
+ const bool is_low_speed = (FSDEV_REG->ISTR & U_ISTR_LS_DCONN) || (ch_reg & U_EP_LSEP);
+ fsdev_btable_workaround_delay(is_low_speed);
return btable_get_count(ch_id, BTABLE_BUF_RX);
}
@@ -237,11 +197,7 @@ bool hcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) {
// If DCON_STAT is already set, the controller sometimes misses the initial connection interrupt
if (FSDEV_REG->ISTR & U_ISTR_DCON_STAT) {
- // Wait DP/DM stabilize time
- volatile uint32_t cycle_count = CPU_FREQUENCY_MHZ / 4U;
- while (cycle_count > 0U) {
- cycle_count--;
- }
+ tusb_time_delay_ms_api(2);
port_status_handler(rhport, false);
}
diff --git a/src/portable/synopsys/dwc2/dcd_dwc2.c b/src/portable/synopsys/dwc2/dcd_dwc2.c
index 9a9c734a0..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;
}
@@ -191,7 +198,7 @@ static void dma_setup_prepare(uint8_t rhport) {
*/
TU_ATTR_ALWAYS_INLINE static inline uint16_t calc_device_grxfsiz(uint16_t largest_ep_size, uint8_t ep_count) {
- return 13 + 1 + 2 * ((largest_ep_size / 4) + 1) + 2 * ep_count;
+ return (uint16_t)(13 + 1 + 2 * ((largest_ep_size / 4) + 1) + 2 * ep_count);
}
static bool dfifo_alloc(uint8_t rhport, uint8_t ep_addr, uint16_t packet_size, bool is_bulk) {
@@ -203,7 +210,7 @@ static bool dfifo_alloc(uint8_t rhport, uint8_t ep_addr, uint16_t packet_size, b
TU_ASSERT(epnum < ep_count);
- uint16_t fifo_size = tu_div_ceil(packet_size, 4);
+ uint16_t fifo_size = (uint16_t)tu_div_ceil(packet_size, 4);
if (dir == TUSB_DIR_OUT) {
// Calculate required size of RX FIFO
const uint16_t new_sz = calc_device_grxfsiz(4 * fifo_size, ep_count);
@@ -371,7 +378,7 @@ static void edpt_schedule_packets(uint8_t rhport, const uint8_t epnum, const uin
num_packets = 1;
} else {
total_bytes = xfer->total_len;
- num_packets = tu_div_ceil(total_bytes, xfer->max_size);
+ num_packets = (uint16_t)tu_div_ceil(total_bytes, xfer->max_size);
if (num_packets == 0) {
num_packets = 1; // zero length packet still count as 1
}
@@ -541,8 +548,9 @@ void dcd_remote_wakeup(uint8_t rhport) {
void dcd_connect(uint8_t rhport) {
dwc2_regs_t* dwc2 = DWC2_REG(rhport);
-#ifdef TUP_USBIP_DWC2_ESP32
- // On ESP32-P4 HS PHY, do not write to USB_WRAP register which belongs to FS PHY
+#if defined(TUP_USBIP_DWC2_ESP32) && !TU_CHECK_MCU(OPT_MCU_ESP32S31)
+ // S31 is excluded at compile time (no USB_WRAP peripheral).
+ // On P4, the HS PHY (port 1) must not touch USB_WRAP which belongs to the FS PHY.
if (rhport == 0) {
usb_wrap_otg_conf_reg_t conf = USB_WRAP.otg_conf;
conf.pad_pull_override = 0;
@@ -560,8 +568,9 @@ void dcd_connect(uint8_t rhport) {
void dcd_disconnect(uint8_t rhport) {
dwc2_regs_t* dwc2 = DWC2_REG(rhport);
-#ifdef TUP_USBIP_DWC2_ESP32
- // On ESP32-P4 HS PHY, do not write to USB_WRAP register which belongs to FS PHY
+#if defined(TUP_USBIP_DWC2_ESP32) && !TU_CHECK_MCU(OPT_MCU_ESP32S31)
+ // S31 is excluded at compile time (no USB_WRAP peripheral).
+ // On P4, the HS PHY (port 1) must not touch USB_WRAP which belongs to the FS PHY.
if (rhport == 0) {
usb_wrap_otg_conf_reg_t conf = USB_WRAP.otg_conf;
conf.pad_pull_override = 1;
@@ -791,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) {
@@ -881,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
@@ -933,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
}
}
@@ -1006,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;
}
@@ -1033,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);
}
@@ -1054,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);
}
}
@@ -1097,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
}
@@ -1134,8 +1157,9 @@ static void handle_incomplete_iso_in(uint8_t rhport) {
}
epin->diepctl = depctl.value;
} else {
- // too many retries, give up
+ // too many retries, give up, but keep endpoint activated
edpt_disable(rhport, epnum | TUSB_DIR_IN_MASK, false);
+ epin->diepctl |= DIEPCTL_USBAEP;
dcd_event_xfer_complete(rhport, epnum | TUSB_DIR_IN_MASK, 0, XFER_RESULT_FAILED, true);
}
}
@@ -1204,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;
@@ -1217,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) {
@@ -1231,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/dwc2_esp32.h b/src/portable/synopsys/dwc2/dwc2_esp32.h
index 6a10dc7f8..436f8dc30 100644
--- a/src/portable/synopsys/dwc2/dwc2_esp32.h
+++ b/src/portable/synopsys/dwc2/dwc2_esp32.h
@@ -37,7 +37,11 @@
#include "esp_intr_alloc.h"
#include "soc/periph_defs.h"
+
+// ESP32-S31 does not have USB_WRAP peripheral (HS-only with UTMI PHY)
+#if !TU_CHECK_MCU(OPT_MCU_ESP32S31)
#include "soc/usb_wrap_struct.h"
+#endif
#if TU_CHECK_MCU(OPT_MCU_ESP32S2, OPT_MCU_ESP32S3)
#define DWC2_FS_REG_BASE 0x60080000UL
@@ -75,6 +79,14 @@ static const dwc2_controller_t _dwc2_controller[] = {
{ .reg_base = DWC2_FS_REG_BASE, .irqnum = ETS_USB_OTG11_CH0_INTR_SOURCE, .ep_count = 7, .ep_in_count = 5, .otg_dfifo_depth = 256 },
{ .reg_base = DWC2_HS_REG_BASE, .irqnum = ETS_USB_OTG_INTR_SOURCE, .ep_count = 16, .ep_in_count = 8, .otg_dfifo_depth = 1024 }
};
+
+#elif TU_CHECK_MCU(OPT_MCU_ESP32S31)
+#define DWC2_HS_REG_BASE 0x20300000UL
+#define DWC2_EP_MAX 16
+
+static const dwc2_controller_t _dwc2_controller[] = {
+ { .reg_base = DWC2_HS_REG_BASE, .irqnum = ETS_USB_OTGHS_INTR_SOURCE, .ep_count = 16, .ep_in_count = 8, .otg_dfifo_depth = 1024 }
+};
#endif
//--------------------------------------------------------------------+
diff --git a/src/portable/synopsys/dwc2/hcd_dwc2.c b/src/portable/synopsys/dwc2/hcd_dwc2.c
index 6098d6eaa..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 {
@@ -903,9 +904,6 @@ static void handle_rxflvl_irq(uint8_t rhport) {
// return true if there is still pending data and need more ISR
static bool handle_txfifo_empty(dwc2_regs_t* dwc2, bool is_periodic) {
- // Use period txsts for both p/np to get request queue space available (1-bit difference, it is small enough)
- const dwc2_hptxsts_t txsts = {.value = (is_periodic ? dwc2->hptxsts : dwc2->hnptxsts)};
-
const uint8_t max_channel = dwc2_channel_count(dwc2);
for (uint8_t ch_id = 0; ch_id < max_channel; ch_id++) {
dwc2_channel_t* channel = &dwc2->channel[ch_id];
@@ -923,6 +921,8 @@ static bool handle_txfifo_empty(dwc2_regs_t* dwc2, bool is_periodic) {
// skip if there is not enough space in FIFO and RequestQueue.
// Packet's last word written to FIFO will trigger a request queue
+ // Use period txsts for both p/np to get request queue space available (1-bit difference, it is small enough)
+ const dwc2_hptxsts_t txsts = {.value = (is_periodic ? dwc2->hptxsts : dwc2->hnptxsts)};
if ((xact_bytes > (txsts.fifo_available << 2)) || (txsts.req_queue_available == 0)) {
return true;
}
@@ -1138,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;
@@ -1204,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);
@@ -1229,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) {
@@ -1252,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) {
@@ -1272,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..415a015dc 100644
--- a/src/portable/wch/ch32_usbfs_reg.h
+++ b/src/portable/wch/ch32_usbfs_reg.h
@@ -39,64 +39,163 @@
#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
#include <ch32v30x.h>
#define USBHD_IRQn OTG_FS_IRQn
+#elif CFG_TUSB_MCU == OPT_MCU_CH583
+ #include "CH58x_common.h"
+ // CH582/583 USBFS device controller: same combined per-endpoint control register as
+ // CH32V103 (IN response bits[1:0], OUT response bits[3:2]) but a different register map -
+ // the EP control/length block sits lower (EP0_CTRL @ +0x22), EP5-7 are split out, EP4
+ // shares EP0's DMA buffer, and EP5/6/7 mode bits live in one UEP567_MOD. The control/status
+ // block matches CH32. Two FS controllers exist (USB @ 0x40008000, USB2 @ 0x40008400); the
+ // device uses USB0. Full register map per CH583/582 datasheet Table 17-2; the parameterized
+ // EP_* macros below index off these named fields.
+ #define CH58X_USBFS_BASE 0x40008000u
+ // Per-endpoint register slots, 4-byte stride each; the EP_* macros index arrays of these.
+ typedef struct {
+ __IO uint16_t DMA; // R16_UEPn_DMA: endpoint n buffer start address
+ __IO uint16_t reserved;
+ } ch58x_ep_dma_t;
+ typedef struct {
+ __IO uint8_t T_LEN; // R8_UEPn_T_LEN (+0): transmit length
+ __IO uint8_t reserved0;
+ __IO uint8_t CTRL; // R8_UEPn_CTRL (+2): endpoint control
+ __IO uint8_t reserved1;
+ } ch58x_ep_ctrl_t;
+ typedef struct {
+ __IO uint8_t BASE_CTRL; // 0x00 R8_USB_CTRL
+ __IO uint8_t UDEV_CTRL; // 0x01 R8_UDEV_CTRL
+ __IO uint8_t INT_EN; // 0x02 R8_USB_INT_EN
+ __IO uint8_t DEV_ADDR; // 0x03 R8_USB_DEV_AD
+ __IO uint8_t Reserve0; // 0x04
+ __IO uint8_t MIS_ST; // 0x05 R8_USB_MIS_ST
+ __IO uint8_t INT_FG; // 0x06 R8_USB_INT_FG
+ __IO uint8_t INT_ST; // 0x07 R8_USB_INT_ST
+ __IO uint8_t RX_LEN; // 0x08 R8_USB_RX_LEN (8-bit on CH58X)
+ __IO uint8_t Reserve1[3]; // 0x09..0x0B
+ __IO uint8_t UEP4_1_MOD; // 0x0C R8_UEP4_1_MOD
+ __IO uint8_t UEP2_3_MOD; // 0x0D R8_UEP2_3_MOD
+ __IO uint8_t UEP567_MOD; // 0x0E R8_UEP567_MOD
+ __IO uint8_t Reserve2; // 0x0F
+ ch58x_ep_dma_t EP_DMA_0_3[4]; // 0x10 EP0-3 DMA (EP4 has no DMA reg; it shares EP0's, index 0)
+ ch58x_ep_ctrl_t EP_CTRL_0_4[5]; // 0x20 EP0-4 length/control
+ __IO uint8_t Reserve3[0x54u - 0x34u]; // 0x34..0x53
+ ch58x_ep_dma_t EP_DMA_5_7[3]; // 0x54 EP5-7 DMA
+ __IO uint8_t Reserve4[0x64u - 0x60u]; // 0x60..0x63
+ ch58x_ep_ctrl_t EP_CTRL_5_7[3]; // 0x64 EP5-7 length/control
+ } USBOTG_FS_TypeDef;
+ #define USBOTG_FS ((USBOTG_FS_TypeDef *) CH58X_USBFS_BASE)
+
+ // 4-byte slot stride + these block offsets pin every EP register to its datasheet address.
+ TU_VERIFY_STATIC(sizeof(ch58x_ep_dma_t) == 4, "CH58x EP DMA slot must be 4 bytes");
+ TU_VERIFY_STATIC(sizeof(ch58x_ep_ctrl_t) == 4, "CH58x EP ctrl slot must be 4 bytes");
+ TU_VERIFY_STATIC(offsetof(USBOTG_FS_TypeDef, EP_DMA_0_3) == 0x10, "CH58x EP_DMA_0_3 @0x10");
+ TU_VERIFY_STATIC(offsetof(USBOTG_FS_TypeDef, EP_CTRL_0_4) == 0x20, "CH58x EP_CTRL_0_4 @0x20");
+ TU_VERIFY_STATIC(offsetof(USBOTG_FS_TypeDef, EP_DMA_5_7) == 0x54, "CH58x EP_DMA_5_7 @0x54");
+ TU_VERIFY_STATIC(offsetof(USBOTG_FS_TypeDef, EP_CTRL_5_7) == 0x64, "CH58x EP_CTRL_5_7 @0x64");
+
+ #define CH32_USBFS_EP_CTRL_COMBINED 1
+ // CH58x's hardware AUTO_TOG does not stay in sync (notably across clear-stall and multi-packet
+ // bulk transfers), causing data-toggle mismatch and bus resets. Drive the toggle manually in
+ // the ISR instead. CH32V103/V20x/V307 keep AUTO_TOG (this macro is undefined for them).
+ #define CH32_USBFS_EP_MANUAL_TOG 1
+ // CH58x EP4 has no DMA register of its own: it overlays EP0's DMA region as
+ // EP0[0:63] + EP4_OUT[64:127] + EP4_IN[128:191], so EP0 needs a 192-byte buffer.
+ #define CH32_USBFS_EP4_SHARES_EP0 1
+ #define USBHD_IRQn USB_IRQn
+ #ifndef NVIC_EnableIRQ
+ #define NVIC_EnableIRQ(n) PFIC_EnableIRQ(n)
+ #define NVIC_DisableIRQ(n) PFIC_DisableIRQ(n)
+ #endif
#endif
#ifdef __GNUC__
@@ -134,9 +233,14 @@
#define USBFS_INT_FG_TOG_OK (1 << 6)
#define USBFS_INT_FG_IS_NAK (1 << 7)
+// MIS_ST: the SUSPEND interrupt fires on both suspend and resume; this bit (R8_USB_MIS_ST) is 1
+// while the bus is suspended and 0 once it has resumed, so it tells the two apart.
+#define USBFS_MIS_ST_SUSPEND (1 << 2)
+
// INT_ST
#define USBFS_INT_ST_MASK_UIS_ENDP(x) (((x) >> 0) & 0x0F)
#define USBFS_INT_ST_MASK_UIS_TOKEN(x) (((x) >> 4) & 0x03)
+#define USBFS_INT_ST_TOG_OK (1 << 6) // received packet's data toggle matched expectation
// UDEV_CTRL
#define USBFS_UDEV_CTRL_PORT_EN (1 << 0)
@@ -166,6 +270,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 5cd25e33e..ece9cde07 100644
--- a/src/portable/wch/dcd_ch32_usbfs.c
+++ b/src/portable/wch/dcd_ch32_usbfs.c
@@ -29,84 +29,237 @@
#if CFG_TUD_ENABLED && defined(TUP_USBIP_WCH_USBFS) && CFG_TUD_WCH_USBIP_USBFS
-#include "device/dcd.h"
-#include "ch32_usbfs_reg.h"
+ #include "device/dcd.h"
+ #include "ch32_usbfs_reg.h"
-/* private defines */
-#define EP_MAX (8)
+ /* private defines */
+ #define EP_MAX (8)
-#define EP_DMA(ep) ((&USBOTG_FS->UEP0_DMA)[ep])
-#define EP_TX_LEN(ep) ((&USBOTG_FS->UEP0_TX_LEN)[2 * ep])
-#define EP_TX_CTRL(ep) ((&USBOTG_FS->UEP0_TX_CTRL)[4 * ep])
-#define EP_RX_CTRL(ep) ((&USBOTG_FS->UEP0_RX_CTRL)[4 * ep])
+ // Struct-based EP register access (uniform layout). CH58X has a different register map and
+ // defines EP_DMA/EP_TX_LEN/EP_CTRL itself in ch32_usbfs_reg.h.
+ #if CFG_TUSB_MCU == OPT_MCU_CH583
+ // CH58X EP registers split into a low block (EP0-4) and a high block (EP5-7). Walk from each
+ // block's first slot by the 4-byte slot stride (pointer arithmetic off slot 0, so the unused
+ // ternary branch's index can't trip -Warray-bounds). EP4 has no DMA register of its own (it
+ // shares EP0's, slot 0) and is never written (see ep_shares_ep0_dma()).
+ #define EP_TX_LEN(ep) (*((ep) <= 4u ? &USBOTG_FS->EP_CTRL_0_4[0].T_LEN + (ep) * 4u \
+ : &USBOTG_FS->EP_CTRL_5_7[0].T_LEN + ((ep) - 5u) * 4u))
+ #define EP_CTRL(ep) (*((ep) <= 4u ? &USBOTG_FS->EP_CTRL_0_4[0].CTRL + (ep) * 4u \
+ : &USBOTG_FS->EP_CTRL_5_7[0].CTRL + ((ep) - 5u) * 4u))
+ #define EP_DMA(ep) (*((ep) <= 3u ? &USBOTG_FS->EP_DMA_0_3[0].DMA + (ep) * 2u \
+ : (ep) == 4u ? &USBOTG_FS->EP_DMA_0_3[0].DMA \
+ : &USBOTG_FS->EP_DMA_5_7[0].DMA + ((ep) - 5u) * 2u))
+ #else
+ #define EP_DMA(ep) ((&USBOTG_FS->UEP0_DMA)[ep])
+ #define EP_TX_LEN(ep) ((&USBOTG_FS->UEP0_TX_LEN)[2 * ep])
+ #define EP_TX_CTRL(ep) ((&USBOTG_FS->UEP0_TX_CTRL)[4 * ep])
+ #define EP_RX_CTRL(ep) ((&USBOTG_FS->UEP0_RX_CTRL)[4 * ep])
+ #endif
+
+// 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
+ #ifndef EP_CTRL // parts with a custom register map (CH58X) define EP_CTRL directly in reg.h
+ #define EP_CTRL(ep) EP_TX_CTRL(ep) // UEPn_TX_CTRL field aliases the combined UEPn_CTRL register
+ #endif
+
+ 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
+
+// Hardware auto data-toggle flag. Parts whose AUTO_TOG is reliable OR it into the EP setup so the
+// controller flips DATA0/DATA1 itself; CH58x (CH32_USBFS_EP_MANUAL_TOG) leaves it clear and the
+// ISR flips the toggle bit after each packet instead.
+#ifdef CH32_USBFS_EP_MANUAL_TOG
+ #define EP_T_AUTO_TOG 0
+ #define EP_R_AUTO_TOG 0
+#else
+ #define EP_T_AUTO_TOG USBFS_EP_T_AUTO_TOG
+ #define EP_R_AUTO_TOG USBFS_EP_R_AUTO_TOG
+#endif
/* private data */
struct usb_xfer {
- bool valid;
- uint8_t* buffer;
- size_t len;
- size_t processed_len;
- size_t max_size;
+ bool valid;
+ uint8_t *buffer;
+ size_t len;
+ size_t processed_len;
+ size_t max_size;
};
static struct {
- bool ep0_tog;
- bool isochronous[EP_MAX];
+ bool ep0_tog;
+ bool isochronous[EP_MAX];
struct usb_xfer xfer[EP_MAX][2];
+#ifdef CH32_USBFS_EP4_SHARES_EP0
+ // CH58X buffers laid out by hand so EP0/EP4 don't burn two unused buffer[] slots. EP0 and EP4
+ // share one contiguous 192-byte DMA region (EP4 has no DMA register of its own):
+ // EP0 [0:63] (half-duplex OUT+IN) + EP4 OUT [64:127] + EP4 IN [128:191]. Every other endpoint
+ // (incl. EP3, which is bulk-only here — CH58X has no isochronous support) gets a plain 128-byte
+ // OUT+IN buffer, so no oversized EP3 buffer is needed.
+ TU_ATTR_ALIGNED(4) uint8_t ep0_ep4_buffer[3 * 64];
+ TU_ATTR_ALIGNED(4) uint8_t ep1_buffer[2][64];
+ TU_ATTR_ALIGNED(4) uint8_t ep2_buffer[2][64];
+ TU_ATTR_ALIGNED(4) uint8_t ep3_buffer[2][64];
+ TU_ATTR_ALIGNED(4) uint8_t ep5_buffer[2][64];
+ TU_ATTR_ALIGNED(4) uint8_t ep6_buffer[2][64];
+ TU_ATTR_ALIGNED(4) uint8_t ep7_buffer[2][64];
+#else
TU_ATTR_ALIGNED(4) uint8_t buffer[EP_MAX][2][64];
+ // EP3 IN gets an enlarged buffer for full-speed isochronous (packets up to 1023 B).
TU_ATTR_ALIGNED(4) struct {
// OUT transfers >64 bytes will overwrite queued IN data!
uint8_t out[64];
uint8_t in[1023];
uint8_t pad;
} ep3_buffer;
+#endif
} data;
+// DMA / copy buffer pointers per endpoint. The WCH USBFS buffer holds OUT (RX) at offset 0 and
+// IN (TX) at +64; EP0 is half-duplex and reuses its OUT chunk for IN; EP3 has an enlarged IN
+// buffer for throughput. On CH58X, EP0/EP4 share ep0_ep4_buffer and the regular endpoints use
+// their own named buffer (see the struct above).
+#ifdef CH32_USBFS_EP4_SHARES_EP0
+// OUT base of the regular CH58X endpoints (EP1/2/3/5/6/7; EP0/EP4 share ep0_ep4_buffer).
+static inline uint8_t* ch58x_ep_buffer(uint8_t ep) {
+ switch (ep) {
+ case 1: return data.ep1_buffer[0];
+ case 2: return data.ep2_buffer[0];
+ case 3: return data.ep3_buffer[0];
+ case 5: return data.ep5_buffer[0];
+ case 6: return data.ep6_buffer[0];
+ default: return data.ep7_buffer[0]; // ep == 7
+ }
+}
+#endif
+
+static inline uint32_t ep_dma_addr(uint8_t ep) {
+#ifdef CH32_USBFS_EP4_SHARES_EP0
+ if (ep == 0 || ep == 4) { return (uint32_t) &data.ep0_ep4_buffer[0]; } // EP4 shares EP0's DMA
+ return (uint32_t) ch58x_ep_buffer(ep);
+#else
+ if (ep == 3) { return (uint32_t) &data.ep3_buffer.out[0]; }
+ return (uint32_t) &data.buffer[ep][0];
+#endif
+}
+
+static inline uint8_t* ep_out_buf(uint8_t ep) {
+#ifdef CH32_USBFS_EP4_SHARES_EP0
+ if (ep == 0) { return &data.ep0_ep4_buffer[0]; }
+ if (ep == 4) { return &data.ep0_ep4_buffer[64]; }
+ return ch58x_ep_buffer(ep);
+#else
+ if (ep == 3) { return data.ep3_buffer.out; }
+ return data.buffer[ep][TUSB_DIR_OUT];
+#endif
+}
+
+static inline uint8_t* ep_in_buf(uint8_t ep) {
+#ifdef CH32_USBFS_EP4_SHARES_EP0
+ if (ep == 0) { return &data.ep0_ep4_buffer[0]; } // EP0 half-duplex: IN reuses OUT chunk
+ if (ep == 4) { return &data.ep0_ep4_buffer[128]; }
+ return ch58x_ep_buffer(ep) + 64; // IN at +64 within the endpoint's 128-byte buffer
+#else
+ if (ep == 0) { return data.buffer[0][TUSB_DIR_OUT]; } // EP0 half-duplex: IN reuses OUT chunk
+ if (ep == 3) { return data.ep3_buffer.in; }
+ return data.buffer[ep][TUSB_DIR_IN];
+#endif
+}
+
+// EP4 on CH58X has no DMA register (shares EP0's); skip its EP_DMA() write.
+static inline bool ep_shares_ep0_dma(uint8_t ep) {
+#ifdef CH32_USBFS_EP4_SHARES_EP0
+ return ep == 4;
+#else
+ (void) ep;
+ return false;
+#endif
+}
+
/* private helpers */
static void update_in(uint8_t rhport, uint8_t ep, bool force) {
- struct usb_xfer* xfer = &data.xfer[ep][TUSB_DIR_IN];
+ struct usb_xfer *xfer = &data.xfer[ep][TUSB_DIR_IN];
if (xfer->valid) {
if (force || xfer->len) {
size_t len = TU_MIN(xfer->max_size, xfer->len);
- if (ep == 0) {
- memcpy(data.buffer[ep][TUSB_DIR_OUT], xfer->buffer, len); // ep0 uses same chunk
- } else if (ep == 3) {
- memcpy(data.ep3_buffer.in, xfer->buffer, len);
- } else {
- memcpy(data.buffer[ep][TUSB_DIR_IN], xfer->buffer, len);
- }
+#if CFG_TUSB_MCU == OPT_MCU_CH583
+ // Every CH58x endpoint buffer is 64 bytes. Isochronous (which would push max_size up to 1023)
+ // is refused in dcd_edpt_iso_alloc(), but some classes (e.g. video) ignore that result, so cap
+ // the copy here to guarantee we never write past the buffer into a neighbouring endpoint's.
+ len = TU_MIN(len, 64u);
+#endif
+ memcpy(ep_in_buf(ep), xfer->buffer, len);
xfer->buffer += len;
xfer->len -= len;
xfer->processed_len += len;
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);
- data.ep0_tog = !data.ep0_tog;
+ 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;
- EP_TX_CTRL(ep) = (EP_TX_CTRL(ep) & ~(USBFS_EP_T_RES_MASK)) | USBFS_EP_T_RES_NAK;
- dcd_event_xfer_complete(
- rhport, ep | TUSB_DIR_IN_MASK, xfer->processed_len,
- XFER_RESULT_SUCCESS, true);
+ if (ep == 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_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);
}
}
}
static void update_out(uint8_t rhport, uint8_t ep, size_t rx_len) {
- struct usb_xfer* xfer = &data.xfer[ep][TUSB_DIR_OUT];
+ struct usb_xfer *xfer = &data.xfer[ep][TUSB_DIR_OUT];
if (xfer->valid) {
size_t len = TU_MIN(xfer->max_size, TU_MIN(xfer->len, rx_len));
- if (ep == 3) {
- memcpy(xfer->buffer, data.ep3_buffer.out, len);
- } else {
- memcpy(xfer->buffer, data.buffer[ep][TUSB_DIR_OUT], len);
- }
+#if CFG_TUSB_MCU == OPT_MCU_CH583
+ len = TU_MIN(len, 64u); // cap to the 64-byte EP buffer (see update_in)
+#endif
+ memcpy(xfer->buffer, ep_out_buf(ep), len);
xfer->buffer += len;
xfer->len -= len;
xfer->processed_len += len;
@@ -117,41 +270,54 @@ 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_ACK;
+ 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_set_response(ep, rx_res);
}
}
}
+static void reset_ep_ctrls(void) {
+ for (uint8_t ep = 1; ep < EP_MAX; ep++) {
+ if (!ep_shares_ep0_dma(ep)) { EP_DMA(ep) = ep_dma_addr(ep); }
+ EP_TX_LEN(ep) = 0;
+ ep_tx_ctrl_set(ep, EP_T_AUTO_TOG | USBFS_EP_T_RES_NYET);
+ ep_rx_ctrl_set(ep, EP_R_AUTO_TOG | USBFS_EP_R_RES_NYET);
+ }
+}
+
/* public functions */
-bool dcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) {
- (void) rh_init;
+bool dcd_init(uint8_t rhport, const tusb_rhport_init_t *rh_init) {
+ (void)rh_init;
// init registers
USBOTG_FS->BASE_CTRL = USBFS_CTRL_SYS_CTRL | USBFS_CTRL_INT_BUSY | USBFS_CTRL_DMA_EN;
USBOTG_FS->UDEV_CTRL = USBFS_UDEV_CTRL_PD_DIS | USBFS_UDEV_CTRL_PORT_EN;
- USBOTG_FS->DEV_ADDR = 0x00;
+ USBOTG_FS->DEV_ADDR = 0x00;
USBOTG_FS->INT_FG = 0xFF;
USBOTG_FS->INT_EN = USBFS_INT_EN_BUS_RST | USBFS_INT_EN_TRANSFER | USBFS_INT_EN_SUSPEND;
- // 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;
+ // setup endpoint 0 (also backs EP4's buffer on CH58X via the shared DMA region)
+ EP_DMA(0) = ep_dma_addr(0);
+ EP_TX_LEN(0) = 0;
+ 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;
USBOTG_FS->UEP2_3_MOD = 0xCC;
+#if CFG_TUSB_MCU == OPT_MCU_CH583
+ // CH58X: a single mode register enables EP5/6/7 RX+TX (different bit layout than CH32).
+ USBOTG_FS->UEP567_MOD = RB_UEP5_RX_EN | RB_UEP5_TX_EN | RB_UEP6_RX_EN | RB_UEP6_TX_EN |
+ RB_UEP7_RX_EN | RB_UEP7_TX_EN;
+#else
USBOTG_FS->UEP5_6_MOD = 0xCC;
- USBOTG_FS->UEP7_MOD = 0x0C;
+ USBOTG_FS->UEP7_MOD = 0x0C;
+#endif
- 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_NAK;
- EP_RX_CTRL(ep) = USBFS_EP_R_AUTO_TOG | USBFS_EP_R_RES_NAK;
- }
- EP_DMA(3) = (uint32_t) &data.ep3_buffer.out[0];
+ reset_ep_ctrls();
dcd_connect(rhport);
@@ -159,196 +325,251 @@ bool dcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) {
}
void dcd_int_handler(uint8_t rhport) {
- (void) rhport;
+ (void)rhport;
uint8_t status = USBOTG_FS->INT_FG;
if (status & USBFS_INT_FG_TRANSFER) {
- uint8_t ep = USBFS_INT_ST_MASK_UIS_ENDP(USBOTG_FS->INT_ST);
- uint8_t token = USBFS_INT_ST_MASK_UIS_TOKEN(USBOTG_FS->INT_ST);
+ uint8_t int_st = USBOTG_FS->INT_ST;
+ uint8_t ep = USBFS_INT_ST_MASK_UIS_ENDP(int_st);
+ uint8_t token = USBFS_INT_ST_MASK_UIS_TOKEN(int_st);
+ uint16_t rx_len = USBOTG_FS->RX_LEN;
switch (token) {
case PID_OUT: {
- uint16_t rx_len = USBOTG_FS->RX_LEN;
+ // Drop an OUT packet whose data toggle doesn't match what we expect -- a host retransmit
+ // after a lost ACK, or a host that doesn't alternate DATA0/DATA1. The hardware auto-toggle
+ // does not reject these on its own, so the check is needed on every variant. EP0 keeps its
+ // own toggle via the SETUP/status flow and is exempt.
+ if (ep != 0 && !(int_st & USBFS_INT_ST_TOG_OK)) { break; }
+#ifdef CH32_USBFS_EP_MANUAL_TOG
+ // CH58x has no hardware auto-toggle: advance the expected RX toggle after each accepted packet
+ // (EP0 included -- it also has no auto-toggle and a control-OUT data stage can span packets).
+ EP_CTRL(ep) ^= USBFS_EPC_R_TOG;
+#endif
update_out(rhport, ep, rx_len);
break;
}
case PID_IN:
+#ifdef CH32_USBFS_EP_MANUAL_TOG
+ // Manual toggle: flip the TX toggle after each ACK'd IN packet (EP0 manages its own).
+ if (ep != 0) { EP_CTRL(ep) ^= USBFS_EPC_T_TOG; }
+#endif
update_in(rhport, ep, false);
break;
case PID_SETUP:
// setup clears stall
- 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);
+ data.ep0_tog = true;
+ // A new SETUP supersedes any control transfer still in flight; drop its stale EP0 state so a
+ // spurious EP0 IN/OUT can't run update_in()/update_out() against the previous request.
+ data.xfer[0][TUSB_DIR_OUT].valid = false;
+ data.xfer[0][TUSB_DIR_IN].valid = false;
+
+ uint8_t *ep0_out = ep_out_buf(0);
+ const tusb_control_request_t *setup = (const tusb_control_request_t *)ep0_out;
+ // 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);
- data.ep0_tog = true;
- dcd_event_setup_received(rhport, &data.buffer[0][TUSB_DIR_OUT][0], true);
+ dcd_event_setup_received(rhport, ep0_out, true);
break;
}
USBOTG_FS->INT_FG = USBFS_INT_FG_TRANSFER;
} else if (status & USBFS_INT_FG_BUS_RST) {
- data.ep0_tog = true;
+ data.ep0_tog = true;
data.xfer[0][TUSB_DIR_OUT].max_size = 64;
- data.xfer[0][TUSB_DIR_IN].max_size = 64;
+ data.xfer[0][TUSB_DIR_IN].max_size = 64;
- //dcd_event_bus_reset(rhport, (USBOTG_FS->BASE_CTRL & USBFS_CTRL_LOW_SPEED) ? TUSB_SPEED_LOW : TUSB_SPEED_FULL, true);
- dcd_event_bus_reset(rhport, (USBOTG_FS->UDEV_CTRL & USBFS_UDEV_CTRL_LOW_SPEED) ? TUSB_SPEED_LOW : TUSB_SPEED_FULL, true);
+ // dcd_event_bus_reset(rhport, (USBOTG_FS->BASE_CTRL & USBFS_CTRL_LOW_SPEED) ? TUSB_SPEED_LOW : TUSB_SPEED_FULL,
+ // true);
+ dcd_event_bus_reset(rhport, (USBOTG_FS->UDEV_CTRL & USBFS_UDEV_CTRL_LOW_SPEED) ? TUSB_SPEED_LOW : TUSB_SPEED_FULL,
+ 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();
USBOTG_FS->INT_FG = USBFS_INT_FG_BUS_RST;
} else if (status & USBFS_INT_FG_SUSPEND) {
+#if CFG_TUSB_MCU == OPT_MCU_CH583
+ // CH58x raises this single interrupt for both suspend and resume; MIS_ST's suspend bit tells
+ // them apart (set while suspended, clear once resumed) so tud_resume_cb() actually fires.
+ dcd_event_t event = {.rhport = rhport,
+ .event_id = (USBOTG_FS->MIS_ST & USBFS_MIS_ST_SUSPEND) ? DCD_EVENT_SUSPEND : DCD_EVENT_RESUME};
+#else
dcd_event_t event = {.rhport = rhport, .event_id = DCD_EVENT_SUSPEND};
+#endif
dcd_event_handler(&event, true);
USBOTG_FS->INT_FG = USBFS_INT_FG_SUSPEND;
}
}
void dcd_int_enable(uint8_t rhport) {
- (void) rhport;
+ (void)rhport;
NVIC_EnableIRQ(USBHD_IRQn);
}
void dcd_int_disable(uint8_t rhport) {
- (void) rhport;
+ (void)rhport;
NVIC_DisableIRQ(USBHD_IRQn);
}
void dcd_set_address(uint8_t rhport, uint8_t dev_addr) {
- (void) dev_addr;
+ (void)dev_addr;
dcd_edpt_xfer(rhport, 0x80, NULL, 0, false); // zlp status response
}
void dcd_remote_wakeup(uint8_t rhport) {
- (void) rhport;
+ (void)rhport;
// TODO optional
}
void dcd_connect(uint8_t rhport) {
- (void) rhport;
+ (void)rhport;
USBOTG_FS->BASE_CTRL |= USBFS_CTRL_DEV_PUEN;
}
void dcd_disconnect(uint8_t rhport) {
- (void) rhport;
+ (void)rhport;
USBOTG_FS->BASE_CTRL &= ~USBFS_CTRL_DEV_PUEN;
}
void dcd_sof_enable(uint8_t rhport, bool en) {
- (void) rhport;
- (void) en;
+ (void)rhport;
+ (void)en;
// TODO implement later
}
-void dcd_edpt0_status_complete(uint8_t rhport, tusb_control_request_t const* request) {
- (void) rhport;
+void dcd_edpt0_status_complete(uint8_t rhport, const tusb_control_request_t *request) {
+ (void)rhport;
if (request->bmRequestType_bit.recipient == TUSB_REQ_RCPT_DEVICE &&
- request->bmRequestType_bit.type == TUSB_REQ_TYPE_STANDARD &&
- request->bRequest == TUSB_REQ_SET_ADDRESS) {
- USBOTG_FS->DEV_ADDR = (uint8_t) request->wValue;
+ request->bmRequestType_bit.type == TUSB_REQ_TYPE_STANDARD && request->bRequest == TUSB_REQ_SET_ADDRESS) {
+#if CFG_TUSB_MCU == OPT_MCU_CH583
+ // On CH58x R8_USB_DEV_AD bit 7 is a user general-purpose flag; only bits [6:0] are the address.
+ USBOTG_FS->DEV_ADDR = (uint8_t)((USBOTG_FS->DEV_ADDR & 0x80u) | (request->wValue & 0x7Fu));
+#else
+ USBOTG_FS->DEV_ADDR = (uint8_t)request->wValue;
+#endif
}
- EP_TX_CTRL(0) = USBFS_EP_T_RES_NAK;
- EP_RX_CTRL(0) = USBFS_EP_R_RES_ACK;
}
-bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const* desc_ep) {
- (void) rhport;
- uint8_t ep = tu_edpt_number(desc_ep->bEndpointAddress);
+bool dcd_edpt_open(uint8_t rhport, const tusb_desc_endpoint_t *desc_ep) {
+ (void)rhport;
+ uint8_t ep = tu_edpt_number(desc_ep->bEndpointAddress);
uint8_t dir = tu_edpt_dir(desc_ep->bEndpointAddress);
TU_ASSERT(ep < EP_MAX);
- data.isochronous[ep] = desc_ep->bmAttributes.xfer == TUSB_XFER_ISOCHRONOUS;
data.xfer[ep][dir].max_size = tu_edpt_packet_size(desc_ep);
if (ep != 0) {
+ // Opening clears the toggle to DATA0 (ep_*_ctrl_set writes the toggle bit clear since v has no
+ // R/T_TOG); with manual toggle EP_*_AUTO_TOG is 0 so the ISR owns subsequent toggling.
if (dir == TUSB_DIR_OUT) {
- if (data.isochronous[ep]) {
- EP_RX_CTRL(ep) = USBFS_EP_R_AUTO_TOG | USBFS_EP_R_RES_NYET;
- } else {
- EP_RX_CTRL(ep) = USBFS_EP_R_AUTO_TOG | USBFS_EP_R_RES_ACK;
- }
+ ep_rx_ctrl_set(ep, EP_R_AUTO_TOG | USBFS_EP_R_RES_NAK);
} else {
- EP_TX_LEN(ep) = 0;
- EP_TX_CTRL(ep) = USBFS_EP_T_AUTO_TOG | USBFS_EP_T_RES_NAK;
+ ep_tx_ctrl_set(ep, EP_T_AUTO_TOG | USBFS_EP_T_RES_NAK);
}
}
return true;
}
void dcd_edpt_close_all(uint8_t rhport) {
- (void) rhport;
+ (void)rhport;
// TODO optional
}
bool dcd_edpt_iso_alloc(uint8_t rhport, uint8_t ep_addr, uint16_t largest_packet_size) {
- (void) rhport;
- (void) ep_addr;
+ (void)rhport;
+ (void)ep_addr;
(void)largest_packet_size;
+#if CFG_TUSB_MCU == OPT_MCU_CH583
+ // No isochronous support on CH58x: its 8-bit T_LEN caps a packet at 255B and the endpoints use
+ // plain 64-byte buffers, so accepting an iso max_size (up to 1023) would let update_in()/
+ // update_out() run off the end of the buffer into neighbouring ones. Refuse it outright.
return false;
+#else
+ uint8_t ep = tu_edpt_number(ep_addr);
+ uint8_t dir = tu_edpt_dir(ep_addr);
+
+ data.isochronous[ep] = true;
+ data.xfer[ep][dir].max_size = largest_packet_size;
+ return true;
+#endif
}
bool dcd_edpt_iso_activate(uint8_t rhport, const tusb_desc_endpoint_t *desc_ep) {
(void)rhport;
(void)desc_ep;
- return false;
+#if CFG_TUSB_MCU == OPT_MCU_CH583
+ return false; // CH58x has no isochronous support (see dcd_edpt_iso_alloc)
+#else
+ return true;
+#endif
}
-bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t * buffer, uint16_t total_bytes, bool is_isr) {
- (void) is_isr;
- (void) rhport;
- uint8_t ep = tu_edpt_number(ep_addr);
+bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t *buffer, uint16_t total_bytes, bool is_isr) {
+ (void)is_isr;
+ (void)rhport;
+ uint8_t ep = tu_edpt_number(ep_addr);
uint8_t dir = tu_edpt_dir(ep_addr);
- struct usb_xfer* xfer = &data.xfer[ep][dir];
+ struct usb_xfer *xfer = &data.xfer[ep][dir];
+ // Keep the IRQ masked across the whole arming sequence: update_in()/ep_rx_set_response() do a
+ // read-modify-write of the (combined) EP control register, which the ISR also RMWs to flip the
+ // manual data toggle; re-enabling before they run lets a transfer IRQ clobber that toggle.
dcd_int_disable(rhport);
- xfer->valid = true;
- xfer->buffer = buffer;
- xfer->len = total_bytes;
+ xfer->valid = true;
+ xfer->buffer = buffer;
+ xfer->len = total_bytes;
xfer->processed_len = 0;
- dcd_int_enable(rhport);
if (dir == TUSB_DIR_IN) {
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_set_response(ep, rx_res);
}
+ dcd_int_enable(rhport);
return true;
}
void dcd_edpt_stall(uint8_t rhport, uint8_t ep_addr) {
- (void) rhport;
- uint8_t ep = tu_edpt_number(ep_addr);
+ (void)rhport;
+ uint8_t ep = tu_edpt_number(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_LEN(0) = 0;
+ 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);
}
}
}
void dcd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr) {
- (void) rhport;
- uint8_t ep = tu_edpt_number(ep_addr);
+ (void)rhport;
+ uint8_t ep = tu_edpt_number(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 {
+ // clear-stall resets the toggle to DATA0 (USB spec); manual-toggle parts then re-sync via ISR
if (dir == TUSB_DIR_OUT) {
- EP_RX_CTRL(ep) = (EP_RX_CTRL(ep) & ~(USBFS_EP_R_RES_MASK | USBFS_EP_R_TOG)) | USBFS_EP_R_RES_ACK;
+ ep_rx_ctrl_set(ep, EP_R_AUTO_TOG | USBFS_EP_R_RES_NAK);
} else {
- EP_TX_CTRL(ep) = (EP_TX_CTRL(ep) & ~(USBFS_EP_T_RES_MASK | USBFS_EP_T_TOG)) | USBFS_EP_T_RES_NAK;
+ ep_tx_ctrl_set(ep, 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 11734de37..ea3b052ad 100644
--- a/src/portable/wch/dcd_ch32_usbhs.c
+++ b/src/portable/wch/dcd_ch32_usbhs.c
@@ -24,7 +24,6 @@
*
* This file is part of the TinyUSB stack.
*/
-
#include "tusb_option.h"
#if CFG_TUD_ENABLED && defined(TUP_USBIP_WCH_USBHS) && defined(CFG_TUD_WCH_USBIP_USBHS) && \
@@ -37,138 +36,182 @@
#define EP_MAX 16
typedef struct {
- uint8_t* buffer;
+ uint8_t *buffer;
uint16_t total_len;
uint16_t queued_len;
uint16_t max_size;
- bool is_last_packet;
- bool is_iso;
+ bool is_iso;
+ bool valid;
} xfer_ctl_t;
-typedef enum {
- EP_RESPONSE_ACK,
- EP_RESPONSE_NAK,
-} ep_response_list_t;
-
-#define XFER_CTL_BASE(_ep, _dir) &xfer_status[_ep][_dir]
+ #define XFER_CTL_BASE(_ep, _dir) &xfer_status[_ep][_dir]
static xfer_ctl_t xfer_status[EP_MAX][2];
-#define EP_TX_LEN(ep) *(volatile uint16_t *)((volatile uint16_t *)&(USBHSD->UEP0_TX_LEN) + (ep) * 2)
-#define EP_TX_CTRL(ep) *(volatile uint8_t *)((volatile uint8_t *)&(USBHSD->UEP0_TX_CTRL) + (ep) * 4)
-#define EP_RX_CTRL(ep) *(volatile uint8_t *)((volatile uint8_t *)&(USBHSD->UEP0_RX_CTRL) + (ep) * 4)
-#define EP_RX_MAX_LEN(ep) *(volatile uint16_t *)((volatile uint16_t *)&(USBHSD->UEP0_MAX_LEN) + (ep) * 2)
+ #define EP_TX_LEN(ep) *(volatile uint16_t *)((volatile uint16_t *)&(USBHSD->UEP0_TX_LEN) + (ep) * 2)
+ #define EP_TX_CTRL(ep) *(volatile uint8_t *)((volatile uint8_t *)&(USBHSD->UEP0_TX_CTRL) + (ep) * 4)
+ #define EP_RX_CTRL(ep) *(volatile uint8_t *)((volatile uint8_t *)&(USBHSD->UEP0_RX_CTRL) + (ep) * 4)
+ #define EP_RX_MAX_LEN(ep) *(volatile uint16_t *)((volatile uint16_t *)&(USBHSD->UEP0_MAX_LEN) + (ep) * 2)
-#define EP_TX_DMA_ADDR(ep) *(volatile uint32_t *)((volatile uint32_t *)&(USBHSD->UEP1_TX_DMA) + (ep - 1))
-#define EP_RX_DMA_ADDR(ep) *(volatile uint32_t *)((volatile uint32_t *)&(USBHSD->UEP1_RX_DMA) + (ep - 1))
+ #define EP_TX_DMA_ADDR(ep) *(volatile uint32_t *)((volatile uint32_t *)&(USBHSD->UEP1_TX_DMA) + (ep - 1))
+ #define EP_RX_DMA_ADDR(ep) *(volatile uint32_t *)((volatile uint32_t *)&(USBHSD->UEP1_RX_DMA) + (ep - 1))
/* Endpoint Buffer */
TU_ATTR_ALIGNED(4) static uint8_t ep0_buffer[CFG_TUD_ENDPOINT0_SIZE];
+static bool ep0_tog;
+static bool ep_data_tog[EP_MAX][2];
-static void ep_set_response_and_toggle(uint8_t ep_num, tusb_dir_t ep_dir, ep_response_list_t response_type) {
+static void set_ep_toggle(uint8_t ep_num, tusb_dir_t ep_dir, bool data1) {
if (ep_dir == TUSB_DIR_IN) {
- uint8_t response = (response_type == EP_RESPONSE_ACK) ? USBHS_EP_T_RES_ACK : USBHS_EP_T_RES_NAK;
- if (ep_num == 0) {
- if (response_type == EP_RESPONSE_ACK) {
- if (EP_TX_LEN(ep_num) == 0) {
- EP_TX_CTRL(ep_num) |= USBHS_EP_T_TOG_1;
- } else {
- EP_TX_CTRL(ep_num) ^= USBHS_EP_T_TOG_1;
- }
- }
- }
- if (xfer_status[ep_num][TUSB_DIR_IN].is_iso == true) {
- EP_TX_CTRL(ep_num) = USBHS_EP_T_AUTOTOG;
- } else {
- EP_TX_CTRL(ep_num) = (EP_TX_CTRL(ep_num) & ~(USBHS_EP_T_RES_MASK)) | response;
- }
+ EP_TX_CTRL(ep_num) = (EP_TX_CTRL(ep_num) & ~(USBHS_EP_T_TOG_MASK)) |
+ (data1 ? USBHS_EP_T_TOG_1 : USBHS_EP_T_TOG_0);
} else {
- uint8_t response = (response_type == EP_RESPONSE_ACK) ? USBHS_EP_R_RES_ACK : USBHS_EP_R_RES_NAK;
- if (ep_num == 0) {
- if (response_type == EP_RESPONSE_ACK) {
- if (xfer_status[ep_num][TUSB_DIR_OUT].queued_len == 0) {
- EP_RX_CTRL(ep_num) |= USBHS_EP_R_TOG_1;
- }
- } else {
- EP_RX_CTRL(ep_num) ^= USBHS_EP_R_TOG_1;
- }
- }
- EP_RX_CTRL(ep_num) = (EP_RX_CTRL(ep_num) & ~(USBHS_EP_R_RES_MASK)) | response;
+ EP_RX_CTRL(ep_num) = (EP_RX_CTRL(ep_num) & ~(USBHS_EP_R_TOG_MASK)) |
+ (data1 ? USBHS_EP_R_TOG_1 : USBHS_EP_R_TOG_0);
}
}
-static void xfer_data_packet(uint8_t ep_num, tusb_dir_t ep_dir, xfer_ctl_t* xfer) {
- if (ep_dir == TUSB_DIR_IN) {
- uint16_t remaining = xfer->total_len - xfer->queued_len;
- uint16_t next_tx_size = TU_MIN(remaining, xfer->max_size);
+static void queue_in_packet(uint8_t ep_num, xfer_ctl_t* xfer) {
+ uint16_t remaining = xfer->total_len - xfer->queued_len;
+ uint16_t tx_len = TU_MIN(remaining, xfer->max_size);
+
+ if (ep_num == 0) {
+ memcpy(ep0_buffer, &xfer->buffer[xfer->queued_len], tx_len);
+ } else {
+ EP_TX_DMA_ADDR(ep_num) = (uint32_t) &xfer->buffer[xfer->queued_len];
+ }
+
+ EP_TX_LEN(ep_num) = tx_len;
+ xfer->queued_len += tx_len;
+
+ if (ep_num == 0) {
+ EP_TX_CTRL(0) = USBHS_EP_T_RES_ACK | (ep0_tog ? USBHS_EP_T_TOG_1 : USBHS_EP_T_TOG_0);
+ ep0_tog = !ep0_tog;
+ } else if (xfer->is_iso) {
+ EP_TX_CTRL(ep_num) = (EP_TX_CTRL(ep_num) & ~(USBHS_EP_T_RES_MASK)) | USBHS_EP_T_RES_NYET;
+ } else {
+ set_ep_toggle(ep_num, TUSB_DIR_IN, ep_data_tog[ep_num][TUSB_DIR_IN]);
+ EP_TX_CTRL(ep_num) = (EP_TX_CTRL(ep_num) & ~(USBHS_EP_T_RES_MASK)) | USBHS_EP_T_RES_ACK;
+ }
+}
+
+static void queue_out_packet(uint8_t ep_num, xfer_ctl_t* xfer) {
+ uint16_t remaining = xfer->total_len - xfer->queued_len;
+ uint16_t rx_len = TU_MIN(remaining, xfer->max_size);
+
+ if (ep_num > 0) {
+ EP_RX_DMA_ADDR(ep_num) = (uint32_t) &xfer->buffer[xfer->queued_len];
+ EP_RX_MAX_LEN(ep_num) = rx_len;
+ }
+
+ if (ep_num == 0) {
+ EP_RX_CTRL(0) = (EP_RX_CTRL(0) & ~(USBHS_EP_R_RES_MASK)) | USBHS_EP_R_RES_ACK;
+ } else if (xfer->is_iso) {
+ EP_RX_CTRL(ep_num) = (EP_RX_CTRL(ep_num) & ~(USBHS_EP_R_RES_MASK)) | USBHS_EP_R_RES_NYET;
+ } else {
+ set_ep_toggle(ep_num, TUSB_DIR_OUT, ep_data_tog[ep_num][TUSB_DIR_OUT]);
+ EP_RX_CTRL(ep_num) = (EP_RX_CTRL(ep_num) & ~(USBHS_EP_R_RES_MASK)) | USBHS_EP_R_RES_ACK;
+ }
+}
+static void update_in(uint8_t rhport, uint8_t ep_num, bool force) {
+ xfer_ctl_t* xfer = XFER_CTL_BASE(ep_num, TUSB_DIR_IN);
+ if (!xfer->valid) {
+ return;
+ }
+
+ if (!force && ep_num != 0 && !xfer->is_iso) {
+ ep_data_tog[ep_num][TUSB_DIR_IN] = !ep_data_tog[ep_num][TUSB_DIR_IN];
+ }
+
+ if (force || (xfer->total_len > xfer->queued_len)) {
+ queue_in_packet(ep_num, xfer);
+ } else {
+ xfer->valid = false;
if (ep_num == 0) {
- memcpy(ep0_buffer, &xfer->buffer[xfer->queued_len], next_tx_size);
+ EP_TX_CTRL(0) = USBHS_EP_T_RES_NAK | (ep0_tog ? USBHS_EP_T_TOG_1 : USBHS_EP_T_TOG_0);
} else {
- EP_TX_DMA_ADDR(ep_num) = (uint32_t) &xfer->buffer[xfer->queued_len];
+ EP_TX_CTRL(ep_num) = (EP_TX_CTRL(ep_num) & ~(USBHS_EP_T_RES_MASK)) | USBHS_EP_T_RES_NAK;
}
+ dcd_event_xfer_complete(rhport, ep_num | TUSB_DIR_IN_MASK, xfer->queued_len, XFER_RESULT_SUCCESS, true);
+ }
+}
- EP_TX_LEN(ep_num) = next_tx_size;
- xfer->queued_len += next_tx_size;
- if (xfer->queued_len == xfer->total_len) {
- xfer->is_last_packet = true;
- }
- if (xfer->is_iso == true) {
- /* Enable EP to generate ISA_ACT interrupt */
- USBHSD->ENDP_CONFIG |= (USBHS_EP0_T_EN << ep_num);
- }
- } else { /* TUSB_DIR_OUT */
- uint16_t left_to_receive = xfer->total_len - xfer->queued_len;
- uint16_t max_possible_rx_size = TU_MIN(xfer->max_size, left_to_receive);
+static void update_out(uint8_t rhport, uint8_t ep_num, uint16_t rx_len) {
+ xfer_ctl_t* xfer = XFER_CTL_BASE(ep_num, TUSB_DIR_OUT);
+ if (!xfer->valid) {
+ return;
+ }
+
+ uint16_t remaining = xfer->total_len - xfer->queued_len;
+ uint16_t len = TU_MIN(rx_len, TU_MIN(remaining, xfer->max_size));
+
+ if (ep_num == 0) {
+ memcpy(&xfer->buffer[xfer->queued_len], ep0_buffer, len);
+ }
- if (max_possible_rx_size == left_to_receive) {
- xfer->is_last_packet = true;
+ xfer->queued_len += len;
+
+ if (ep_num != 0 && !xfer->is_iso) {
+ ep_data_tog[ep_num][TUSB_DIR_OUT] = !ep_data_tog[ep_num][TUSB_DIR_OUT];
+ }
+
+ if ((xfer->queued_len == xfer->total_len) || (len < xfer->max_size)) {
+ xfer->valid = false;
+ if (ep_num == 0) {
+ EP_RX_CTRL(0) = (EP_RX_CTRL(0) & ~(USBHS_EP_R_RES_MASK)) | USBHS_EP_R_RES_NAK;
}
+ dcd_event_xfer_complete(rhport, ep_num, xfer->queued_len, XFER_RESULT_SUCCESS, true);
+ }
- if (ep_num > 0) {
- EP_RX_DMA_ADDR(ep_num) = (uint32_t) &xfer->buffer[xfer->queued_len];
- EP_RX_MAX_LEN(ep_num) = max_possible_rx_size;
+ if (ep_num != 0) {
+ if (xfer->valid) {
+ queue_out_packet(ep_num, xfer);
+ } else {
+ uint8_t rx_res = xfer->is_iso ? USBHS_EP_R_RES_NYET : USBHS_EP_R_RES_NAK;
+ EP_RX_CTRL(ep_num) = (EP_RX_CTRL(ep_num) & ~(USBHS_EP_R_RES_MASK)) | rx_res;
}
}
- ep_set_response_and_toggle(ep_num, ep_dir, USBHS_EP_R_RES_ACK);
}
-bool dcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) {
- (void) rhport;
- (void) rh_init;
+bool dcd_init(uint8_t rhport, const tusb_rhport_init_t *rh_init) {
+ (void)rhport;
+ (void)rh_init;
memset(&xfer_status, 0, sizeof(xfer_status));
+ memset(ep_data_tog, 0, sizeof(ep_data_tog));
+ ep0_tog = true;
USBHSD->HOST_CTRL = 0x00;
USBHSD->HOST_CTRL = USBHS_PHY_SUSPENDM;
USBHSD->CONTROL = 0;
-#if TUD_OPT_HIGH_SPEED
+ #if TUD_OPT_HIGH_SPEED
USBHSD->CONTROL = USBHS_DMA_EN | USBHS_INT_BUSY_EN | USBHS_HIGH_SPEED;
-#else
- #error OPT_MODE_FULL_SPEED not currently supported on CH32
+ #else
+ #error OPT_MODE_FULL_SPEED not currently supported on CH32
USBHSD->CONTROL = USBHS_DMA_EN | USBHS_INT_BUSY_EN | USBHS_FULL_SPEED;
-#endif
+ #endif
USBHSD->INT_EN = 0;
- USBHSD->INT_EN = USBHS_SETUP_ACT_EN | USBHS_TRANSFER_EN | USBHS_BUS_RST_EN | USBHS_SUSPEND_EN | USBHS_ISO_ACT_EN;
+ USBHSD->INT_EN = USBHS_SETUP_ACT_EN | USBHS_TRANSFER_EN | USBHS_BUS_RST_EN | USBHS_SUSPEND_EN;
USBHSD->ENDP_CONFIG = USBHS_EP0_T_EN | USBHS_EP0_R_EN;
- USBHSD->ENDP_TYPE = 0x00;
- USBHSD->BUF_MODE = 0x00;
+ USBHSD->ENDP_TYPE = 0x00;
+ USBHSD->BUF_MODE = 0x00;
for (int ep = 0; ep < EP_MAX; ep++) {
- EP_TX_LEN(ep) = 0;
- EP_TX_CTRL(ep) = USBHS_EP_T_AUTOTOG | USBHS_EP_T_RES_NAK;
- EP_RX_CTRL(ep) = USBHS_EP_R_AUTOTOG | USBHS_EP_R_RES_NAK;
+ EP_TX_LEN(ep) = 0;
+ EP_TX_CTRL(ep) = USBHS_EP_T_RES_NAK | USBHS_EP_T_TOG_0;
+ EP_RX_CTRL(ep) = USBHS_EP_R_RES_NAK | USBHS_EP_R_TOG_0;
EP_RX_MAX_LEN(ep) = 0;
}
- USBHSD->UEP0_DMA = (uint32_t) ep0_buffer;
- USBHSD->UEP0_MAX_LEN = CFG_TUD_ENDPOINT0_SIZE;
+ USBHSD->UEP0_DMA = (uint32_t)ep0_buffer;
+ USBHSD->UEP0_MAX_LEN = CFG_TUD_ENDPOINT0_SIZE;
xfer_status[0][TUSB_DIR_OUT].max_size = CFG_TUD_ENDPOINT0_SIZE;
- xfer_status[0][TUSB_DIR_IN].max_size = CFG_TUD_ENDPOINT0_SIZE;
+ xfer_status[0][TUSB_DIR_IN].max_size = CFG_TUD_ENDPOINT0_SIZE;
USBHSD->DEV_AD = 0;
USBHSD->CONTROL |= USBHS_DEV_PU_EN;
@@ -177,22 +220,24 @@ bool dcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) {
}
void dcd_int_enable(uint8_t rhport) {
- (void) rhport;
+ (void)rhport;
NVIC_EnableIRQ(USBHS_IRQn);
}
void dcd_int_disable(uint8_t rhport) {
- (void) rhport;
+ (void)rhport;
NVIC_DisableIRQ(USBHS_IRQn);
}
void dcd_edpt_close_all(uint8_t rhport) {
- (void) rhport;
+ (void)rhport;
+
+ memset(ep_data_tog, 0, sizeof(ep_data_tog));
for (size_t ep = 1; ep < EP_MAX; ep++) {
- EP_TX_LEN(ep) = 0;
- EP_TX_CTRL(ep) = USBHS_EP_T_AUTOTOG | USBHS_EP_T_RES_NAK;
- EP_RX_CTRL(ep) = USBHS_EP_R_AUTOTOG | USBHS_EP_R_RES_NAK;
+ EP_TX_LEN(ep) = 0;
+ EP_TX_CTRL(ep) = USBHS_EP_T_RES_NAK | USBHS_EP_T_TOG_0;
+ EP_RX_CTRL(ep) = USBHS_EP_R_RES_NAK | USBHS_EP_R_TOG_0;
EP_RX_MAX_LEN(ep) = 0;
}
@@ -201,18 +246,18 @@ void dcd_edpt_close_all(uint8_t rhport) {
}
void dcd_set_address(uint8_t rhport, uint8_t dev_addr) {
- (void) dev_addr;
+ (void)dev_addr;
// Response with zlp status
dcd_edpt_xfer(rhport, 0x80, NULL, 0, false);
}
void dcd_remote_wakeup(uint8_t rhport) {
- (void) rhport;
+ (void)rhport;
}
void dcd_sof_enable(uint8_t rhport, bool en) {
- (void) rhport;
+ (void)rhport;
if (en) {
USBHSD->INT_EN |= USBHS_SOF_ACT_EN;
} else {
@@ -220,24 +265,19 @@ void dcd_sof_enable(uint8_t rhport, bool en) {
}
}
-void dcd_edpt0_status_complete(uint8_t rhport, tusb_control_request_t const* request) {
- (void) rhport;
-
+void dcd_edpt0_status_complete(uint8_t rhport, const tusb_control_request_t *request) {
+ (void)rhport;
if (request->bmRequestType_bit.recipient == TUSB_REQ_RCPT_DEVICE &&
- request->bmRequestType_bit.type == TUSB_REQ_TYPE_STANDARD &&
- request->bRequest == TUSB_REQ_SET_ADDRESS) {
- USBHSD->DEV_AD = (uint8_t) request->wValue;
+ request->bmRequestType_bit.type == TUSB_REQ_TYPE_STANDARD && request->bRequest == TUSB_REQ_SET_ADDRESS) {
+ USBHSD->DEV_AD = (uint8_t)request->wValue;
}
-
- EP_TX_CTRL(0) = USBHS_EP_T_RES_NAK | USBHS_EP_T_TOG_0;
- EP_RX_CTRL(0) = USBHS_EP_R_RES_NAK | USBHS_EP_R_TOG_0;
}
-bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const* desc_edpt) {
- (void) rhport;
+bool dcd_edpt_open(uint8_t rhport, const tusb_desc_endpoint_t *desc_edpt) {
+ (void)rhport;
- uint8_t const ep_num = tu_edpt_number(desc_edpt->bEndpointAddress);
- tusb_dir_t const dir = tu_edpt_dir(desc_edpt->bEndpointAddress);
+ const uint8_t ep_num = tu_edpt_number(desc_edpt->bEndpointAddress);
+ const tusb_dir_t dir = tu_edpt_dir(desc_edpt->bEndpointAddress);
TU_ASSERT(ep_num < EP_MAX);
@@ -245,13 +285,14 @@ bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const* desc_edpt) {
return true;
}
- xfer_ctl_t* xfer = XFER_CTL_BASE(ep_num, dir);
- xfer->max_size = tu_edpt_packet_size(desc_edpt);
+ xfer_ctl_t *xfer = XFER_CTL_BASE(ep_num, dir);
+ xfer->max_size = tu_edpt_packet_size(desc_edpt);
+ ep_data_tog[ep_num][dir] = false;
xfer->is_iso = (desc_edpt->bmAttributes.xfer == TUSB_XFER_ISOCHRONOUS);
if (dir == TUSB_DIR_OUT) {
USBHSD->ENDP_CONFIG |= (USBHS_EP0_R_EN << ep_num);
- EP_RX_CTRL(ep_num) = USBHS_EP_R_AUTOTOG | USBHS_EP_R_RES_NAK;
+ EP_RX_CTRL(ep_num) = USBHS_EP_R_RES_NAK | USBHS_EP_R_TOG_0;
if (xfer->is_iso == true) {
USBHSD->ENDP_TYPE |= (USBHS_EP0_R_TYP << ep_num);
}
@@ -259,31 +300,31 @@ bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const* desc_edpt) {
} else {
if (xfer->is_iso == true) {
USBHSD->ENDP_TYPE |= (USBHS_EP0_T_TYP << ep_num);
- } else {
- /* Enable all types except Isochronous to avoid ISO_ACT interrupt generation */
- USBHSD->ENDP_CONFIG |= (USBHS_EP0_T_EN << ep_num);
}
- EP_TX_LEN(ep_num) = 0;
- EP_TX_CTRL(ep_num) = USBHS_EP_T_AUTOTOG | USBHS_EP_T_RES_NAK | USBHS_EP_T_TOG_0;
+ USBHSD->ENDP_CONFIG |= (USBHS_EP0_T_EN << ep_num);
+ EP_TX_LEN(ep_num) = 0;
+ EP_TX_CTRL(ep_num) = USBHS_EP_T_RES_NAK | USBHS_EP_T_TOG_0;
}
return true;
}
void dcd_edpt_close(uint8_t rhport, uint8_t ep_addr) {
- (void) rhport;
+ (void)rhport;
- uint8_t const ep_num = tu_edpt_number(ep_addr);
- tusb_dir_t const dir = tu_edpt_dir(ep_addr);
+ const uint8_t ep_num = tu_edpt_number(ep_addr);
+ const tusb_dir_t dir = tu_edpt_dir(ep_addr);
if (dir == TUSB_DIR_OUT) {
- EP_RX_CTRL(ep_num) = USBHS_EP_R_AUTOTOG | USBHS_EP_R_RES_NAK;
+ EP_RX_CTRL(ep_num) = USBHS_EP_R_RES_NAK | USBHS_EP_R_TOG_0;
EP_RX_MAX_LEN(ep_num) = 0;
+ ep_data_tog[ep_num][TUSB_DIR_OUT] = false;
USBHSD->ENDP_TYPE &= ~(USBHS_EP0_R_TYP << ep_num);
USBHSD->ENDP_CONFIG &= ~(USBHS_EP0_R_EN << ep_num);
- } else { // TUSB_DIR_IN
- EP_TX_CTRL(ep_num) = USBHS_EP_T_AUTOTOG | USBHS_EP_T_RES_NAK | USBHS_EP_T_TOG_0;
- EP_TX_LEN(ep_num) = 0;
+ } else { // TUSB_DIR_IN
+ EP_TX_CTRL(ep_num) = USBHS_EP_T_RES_NAK | USBHS_EP_T_TOG_0;
+ EP_TX_LEN(ep_num) = 0;
+ ep_data_tog[ep_num][TUSB_DIR_IN] = false;
USBHSD->ENDP_TYPE &= ~(USBHS_EP0_T_TYP << ep_num);
USBHSD->ENDP_CONFIG &= ~(USBHS_EP0_T_EN << ep_num);
}
@@ -305,128 +346,120 @@ bool dcd_edpt_iso_activate(uint8_t rhport, tusb_desc_endpoint_t const * desc_ep)
#endif
void dcd_edpt_stall(uint8_t rhport, uint8_t ep_addr) {
- (void) rhport;
+ (void)rhport;
- uint8_t const ep_num = tu_edpt_number(ep_addr);
- tusb_dir_t const dir = tu_edpt_dir(ep_addr);
+ const uint8_t ep_num = tu_edpt_number(ep_addr);
+ const tusb_dir_t dir = tu_edpt_dir(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;
}
}
void dcd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr) {
- (void) rhport;
+ (void)rhport;
- uint8_t const ep_num = tu_edpt_number(ep_addr);
- tusb_dir_t const dir = tu_edpt_dir(ep_addr);
+ const uint8_t ep_num = tu_edpt_number(ep_addr);
+ const tusb_dir_t dir = tu_edpt_dir(ep_addr);
if (dir == TUSB_DIR_OUT) {
- EP_RX_CTRL(ep_num) = USBHS_EP_R_AUTOTOG | USBHS_EP_R_RES_NAK;
+ EP_RX_CTRL(ep_num) = USBHS_EP_R_RES_NAK | USBHS_EP_R_TOG_0;
+ ep_data_tog[ep_num][TUSB_DIR_OUT] = false;
} else {
- EP_TX_CTRL(ep_num) = USBHS_EP_T_AUTOTOG | USBHS_EP_R_RES_NAK;
+ EP_TX_CTRL(ep_num) = USBHS_EP_T_RES_NAK | USBHS_EP_T_TOG_0;
+ ep_data_tog[ep_num][TUSB_DIR_IN] = false;
}
}
-bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t * buffer, uint16_t total_bytes, bool is_isr) {
- (void) is_isr;
- (void) rhport;
- uint8_t const ep_num = tu_edpt_number(ep_addr);
- tusb_dir_t const dir = tu_edpt_dir(ep_addr);
+bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t *buffer, uint16_t total_bytes, bool is_isr) {
+ (void)is_isr;
+ (void)rhport;
+ const uint8_t ep_num = tu_edpt_number(ep_addr);
+ const tusb_dir_t dir = tu_edpt_dir(ep_addr);
+
+ xfer_ctl_t *xfer = XFER_CTL_BASE(ep_num, dir);
+ xfer->buffer = buffer;
+ xfer->total_len = total_bytes;
+ xfer->queued_len = 0;
+ xfer->valid = true;
- xfer_ctl_t* xfer = XFER_CTL_BASE(ep_num, dir);
- xfer->buffer = buffer;
- xfer->total_len = total_bytes;
- xfer->queued_len = 0;
- xfer->is_last_packet = false;
+ if (ep_num == 0 && dir == TUSB_DIR_OUT) {
+ if (total_bytes == 0) {
+ EP_RX_CTRL(0) = (EP_RX_CTRL(0) & ~(USBHS_EP_R_TOG_MASK)) | USBHS_EP_R_TOG_1;
+ } else {
+ EP_RX_CTRL(0) ^= USBHS_EP_R_TOG_1;
+ }
+ }
- xfer_data_packet(ep_num, dir, xfer);
+ if (dir == TUSB_DIR_IN) {
+ update_in(rhport, ep_num, true);
+ } else {
+ queue_out_packet(ep_num, xfer);
+ }
return true;
}
void dcd_int_handler(uint8_t rhport) {
- (void) rhport;
+ (void)rhport;
- uint8_t int_flag = USBHSD->INT_FG;
+ uint8_t int_flag = USBHSD->INT_FG;
uint8_t int_status = USBHSD->INT_ST;
- if (int_flag & (USBHS_ISO_ACT_FLAG | USBHS_TRANSFER_FLAG)) {
- uint8_t const token = int_status & MASK_UIS_TOKEN;
+ if (int_flag & USBHS_TRANSFER_FLAG) {
+ const uint8_t token = int_status & MASK_UIS_TOKEN;
+ const uint8_t ep_num = int_status & MASK_UIS_ENDP;
+ const uint16_t len = USBHSD->RX_LEN;
if (token == USBHS_TOKEN_PID_SOF) {
uint32_t frame_count = USBHSD->FRAME_NO & USBHS_FRAME_NO_NUM_MASK;
dcd_event_sof(rhport, frame_count, true);
- }else {
- uint8_t const ep_num = int_status & MASK_UIS_ENDP;
- tusb_dir_t const ep_dir = (token == USBHS_TOKEN_PID_IN) ? TUSB_DIR_IN : TUSB_DIR_OUT;
- uint8_t const ep_addr = tu_edpt_addr(ep_num, ep_dir);
- xfer_ctl_t* xfer = XFER_CTL_BASE(ep_num, ep_dir);
-
- if (token == USBHS_TOKEN_PID_OUT) {
- uint16_t rx_len = USBHSD->RX_LEN;
-
- if (ep_num == 0) {
- memcpy(&xfer->buffer[xfer->queued_len], ep0_buffer, rx_len);
- }
-
- xfer->queued_len += rx_len;
- if (rx_len < xfer->max_size) {
- xfer->is_last_packet = true;
- }
- } else if (token == USBHS_TOKEN_PID_IN) {
- if (xfer->is_iso && xfer->is_last_packet) {
- /* Disable EP to avoid ISO_ACT interrupt generation */
- USBHSD->ENDP_CONFIG &= ~(USBHS_EP0_T_EN << ep_num);
- } else {
- // Do nothing, no need to update xfer->is_last_packet, it is already updated in xfer_data_packet
- }
- }
-
- if (xfer->is_last_packet == true) {
- ep_set_response_and_toggle(ep_num, ep_dir, EP_RESPONSE_NAK);
- dcd_event_xfer_complete(0, ep_addr, xfer->queued_len, XFER_RESULT_SUCCESS, true);
- } else {
- /* prepare next part of packet to xref */
- xfer_data_packet(ep_num, ep_dir, xfer);
- }
+ } else if (token == USBHS_TOKEN_PID_OUT) {
+ update_out(rhport, ep_num, len);
+ } else if (token == USBHS_TOKEN_PID_IN) {
+ update_in(rhport, ep_num, false);
}
-
- USBHSD->INT_FG = (int_flag & (USBHS_ISO_ACT_FLAG | USBHS_TRANSFER_FLAG)); /* Clear flag */
+ USBHSD->INT_FG = (int_flag & USBHS_TRANSFER_FLAG); /* Clear flag */
} else if (int_flag & USBHS_SETUP_FLAG) {
- ep_set_response_and_toggle(0, TUSB_DIR_IN, EP_RESPONSE_NAK);
- ep_set_response_and_toggle(0, TUSB_DIR_OUT, EP_RESPONSE_NAK);
+ tusb_control_request_t const* setup =
+ (tusb_control_request_t const*) ep0_buffer;
+ ep0_tog = true;
+ EP_RX_CTRL(0) = (setup->wLength == 0) ? USBHS_EP_R_RES_ACK : USBHS_EP_R_RES_NAK;
+ EP_TX_CTRL(0) = USBHS_EP_T_RES_NAK;
+
dcd_event_setup_received(0, ep0_buffer, true);
USBHSD->INT_FG = USBHS_SETUP_FLAG; /* Clear flag */
} else if (int_flag & USBHS_BUS_RST_FLAG) {
// TODO CH32 does not detect actual speed at this time (should be known at end of reset)
// This interrupt probably triggered at start of bus reset
-// tusb_speed_t actual_speed;
-// switch(USBHSD->SPEED_TYPE & USBHS_SPEED_TYPE_MASK){
-// case USBHS_SPEED_TYPE_HIGH:
-// actual_speed = TUSB_SPEED_HIGH;
-// break;
-// case USBHS_SPEED_TYPE_FULL:
-// actual_speed = TUSB_SPEED_FULL;
-// break;
-// case USBHS_SPEED_TYPE_LOW:
-// actual_speed = TUSB_SPEED_LOW;
-// break;
-// default:
-// TU_ASSERT(0,);
-// break;
-// }
-// dcd_event_bus_reset(0, actual_speed, true);
+ // tusb_speed_t actual_speed;
+ // switch(USBHSD->SPEED_TYPE & USBHS_SPEED_TYPE_MASK){
+ // case USBHS_SPEED_TYPE_HIGH:
+ // actual_speed = TUSB_SPEED_HIGH;
+ // break;
+ // case USBHS_SPEED_TYPE_FULL:
+ // actual_speed = TUSB_SPEED_FULL;
+ // break;
+ // case USBHS_SPEED_TYPE_LOW:
+ // actual_speed = TUSB_SPEED_LOW;
+ // break;
+ // default:
+ // TU_ASSERT(0,);
+ // break;
+ // }
+ // dcd_event_bus_reset(0, actual_speed, true);
dcd_event_bus_reset(0, TUSB_SPEED_HIGH, true);
USBHSD->DEV_AD = 0;
- EP_RX_CTRL(0) = USBHS_EP_R_RES_ACK | USBHS_EP_R_TOG_0;
- EP_TX_CTRL(0) = USBHS_EP_T_RES_NAK | USBHS_EP_T_TOG_0;
+ memset(ep_data_tog, 0, sizeof(ep_data_tog));
+ ep0_tog = true;
+ EP_RX_CTRL(0) = USBHS_EP_R_RES_ACK | USBHS_EP_R_TOG_0;
+ EP_TX_CTRL(0) = USBHS_EP_T_RES_NAK | USBHS_EP_T_TOG_0;
USBHSD->INT_FG = USBHS_BUS_RST_FLAG; /* Clear flag */
} else if (int_flag & USBHS_SUSPEND_FLAG) {
@@ -434,6 +467,9 @@ void dcd_int_handler(uint8_t rhport) {
dcd_event_handler(&event, true);
USBHSD->INT_FG = USBHS_SUSPEND_FLAG; /* Clear flag */
+ } else {
+ // Unhandled interrupt
+ USBHSD->INT_FG = int_flag; /* Clear all flags */
}
}
#endif
diff --git a/src/tinyusb.mk b/src/tinyusb.mk
index 169098016..365043927 100644
--- a/src/tinyusb.mk
+++ b/src/tinyusb.mk
@@ -3,7 +3,6 @@ TINYUSB_SRC_C += \
src/tusb.c \
src/common/tusb_fifo.c \
src/device/usbd.c \
- src/device/usbd_control.c \
src/typec/usbc.c \
src/class/audio/audio_device.c \
src/class/cdc/cdc_device.c \
@@ -11,6 +10,7 @@ TINYUSB_SRC_C += \
src/class/dfu/dfu_rt_device.c \
src/class/hid/hid_device.c \
src/class/midi/midi_device.c \
+ src/class/midi/midi2_device.c \
src/class/msc/msc_device.c \
src/class/mtp/mtp_device.c \
src/class/net/ecm_rndis_device.c \
@@ -24,5 +24,6 @@ TINYUSB_SRC_C += \
src/class/cdc/cdc_host.c \
src/class/hid/hid_host.c \
src/class/midi/midi_host.c \
+ src/class/midi/midi2_host.c \
src/class/msc/msc_host.c \
src/class/vendor/vendor_host.c \
diff --git a/src/tusb.c b/src/tusb.c
index 5e4422e41..634cbc10b 100644
--- a/src/tusb.c
+++ b/src/tusb.c
@@ -224,32 +224,31 @@ uint8_t const* tu_desc_find3(uint8_t const* desc, uint8_t const* end, uint8_t by
// Endpoint Helper for both Host and Device stack
//--------------------------------------------------------------------+
-bool tu_edpt_claim(tu_edpt_state_t* ep_state, osal_mutex_t mutex) {
+bool tu_edpt_claim(volatile uint8_t* ep_state, osal_mutex_t mutex) {
(void) mutex;
// pre-check to help reducing mutex lock
- TU_VERIFY(ep_state->busy == 0);
- TU_VERIFY(ep_state->claimed == 0);
+ TU_VERIFY((*ep_state & (TU_EDPT_STATE_BUSY | TU_EDPT_STATE_CLAIMED)) == 0);
(void) osal_mutex_lock(mutex, OSAL_TIMEOUT_WAIT_FOREVER);
// can only claim the endpoint if it is not busy and not claimed yet.
- bool const available = (ep_state->busy == 0) && (ep_state->claimed == 0);
+ bool const available = (*ep_state & (TU_EDPT_STATE_BUSY | TU_EDPT_STATE_CLAIMED)) == 0;
if (available) {
- ep_state->claimed = 1;
+ *ep_state |= TU_EDPT_STATE_CLAIMED;
}
(void) osal_mutex_unlock(mutex);
return available;
}
-bool tu_edpt_release(tu_edpt_state_t* ep_state, osal_mutex_t mutex) {
+bool tu_edpt_release(volatile uint8_t* ep_state, osal_mutex_t mutex) {
(void) mutex;
(void) osal_mutex_lock(mutex, OSAL_TIMEOUT_WAIT_FOREVER);
// can only release the endpoint if it is claimed and not busy
- bool const ret = (ep_state->claimed == 1) && (ep_state->busy == 0);
+ bool const ret = (*ep_state & (TU_EDPT_STATE_CLAIMED | TU_EDPT_STATE_BUSY)) == TU_EDPT_STATE_CLAIMED;
if (ret) {
- ep_state->claimed = 0;
+ *ep_state &= (uint8_t) ~TU_EDPT_STATE_CLAIMED;
}
(void) osal_mutex_unlock(mutex);
@@ -498,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.h b/src/tusb.h
index c80c8433c..aa6b461e5 100644
--- a/src/tusb.h
+++ b/src/tusb.h
@@ -63,6 +63,10 @@
#include "class/midi/midi_host.h"
#endif
+ #if CFG_TUH_MIDI2
+ #include "class/midi/midi2_host.h"
+ #endif
+
#if CFG_TUH_VENDOR
#include "class/vendor/vendor_host.h"
#endif
@@ -108,6 +112,10 @@
#include "class/midi/midi_device.h"
#endif
+ #if CFG_TUD_MIDI2
+ #include "class/midi/midi2_device.h"
+ #endif
+
#if CFG_TUD_VENDOR
#include "class/vendor/vendor_device.h"
#endif
diff --git a/src/tusb_option.h b/src/tusb_option.h
index dd7af76f6..b5457fe8a 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
@@ -134,6 +135,7 @@
#define OPT_MCU_ESP32C5 908 ///< Espressif ESP32-C5
#define OPT_MCU_ESP32C61 909 ///< Espressif ESP32-C61
#define OPT_MCU_ESP32H4 910 ///< Espressif ESP32-H4
+#define OPT_MCU_ESP32S31 911 ///< Espressif ESP32-S31
// Dialog
#define OPT_MCU_DA1469X 1000 ///< Dialog Semiconductor DA1469x
@@ -193,6 +195,8 @@
#define OPT_MCU_CH32F20X 2210 ///< WCH CH32F20x
#define OPT_MCU_CH32V20X 2220 ///< WCH CH32V20X
#define OPT_MCU_CH32V103 2230 ///< WCH CH32V103
+#define OPT_MCU_CH583 2240 ///< WCH CH583
+#define OPT_MCU_CH582 OPT_MCU_CH583 ///< WCH CH582 (alias, same USB IP as CH583)
// NXP LPC MCX
#define OPT_MCU_MCXN9 2300 ///< NXP MCX N9 Series
@@ -534,6 +538,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
@@ -645,6 +661,10 @@
#define CFG_TUD_MIDI 0
#endif
+#ifndef CFG_TUD_MIDI2
+ #define CFG_TUD_MIDI2 0
+#endif
+
#ifndef CFG_TUD_VENDOR
#define CFG_TUD_VENDOR 0
#endif
@@ -814,6 +834,22 @@
#define CFG_TUH_MIDI 0
#endif
+#ifndef CFG_TUH_MIDI2
+ #define CFG_TUH_MIDI2 0
+#endif
+
+#ifndef CFG_TUH_MIDI2_RX_BUFSIZE
+ #define CFG_TUH_MIDI2_RX_BUFSIZE TUH_EPSIZE_BULK_MAX
+#endif
+
+#ifndef CFG_TUH_MIDI2_TX_BUFSIZE
+ #define CFG_TUH_MIDI2_TX_BUFSIZE TUH_EPSIZE_BULK_MAX
+#endif
+
+#ifndef CFG_TUH_MIDI2_LOG_LEVEL
+ #define CFG_TUH_MIDI2_LOG_LEVEL CFG_TUH_LOG_LEVEL
+#endif
+
#ifndef CFG_TUH_MSC
#define CFG_TUH_MSC 0
#endif