summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorhathach <[email protected]>2026-06-18 15:31:03 +0700
committerhathach <[email protected]>2026-06-18 15:31:03 +0700
commit953abfb3931c67b834838c7fe0b31834cb0d42d6 (patch)
tree6d238832fb486839d7c24d7810ef5c37f56a3966 /src
parentaca8c7a97c4fc93e8883cffdd0a7a22d552cbdc5 (diff)
parent941d63e39a529593ee86caf7ea85e04839680d59 (diff)
Merge remote-tracking branch 'origin/master' into add-ch58x-usbfs
Diffstat (limited to 'src')
-rw-r--r--src/CMakeLists.txt3
-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.c11
-rw-r--r--src/class/midi/midi_host.h7
-rw-r--r--src/class/mtp/mtp.h58
-rw-r--r--src/class/mtp/mtp_device.c15
-rw-r--r--src/class/mtp/mtp_device.h4
-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.c102
-rw-r--r--src/class/net/net_device.h16
-rw-r--r--src/class/printer/printer_device.c1
-rw-r--r--src/common/tusb_compiler.h9
-rw-r--r--src/common/tusb_fifo.c8
-rw-r--r--src/common/tusb_mcu.h67
-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.c525
-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.c382
-rw-r--r--src/osal/osal.h39
-rw-r--r--src/osal/osal_freertos.h13
-rw-r--r--src/osal/osal_mynewt.h33
-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.c800
-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/dcd_rp2040.c294
-rw-r--r--src/portable/raspberrypi/rp2040/hcd_rp2040.c835
-rw-r--r--src/portable/raspberrypi/rp2040/rp2040_usb.c490
-rw-r--r--src/portable/raspberrypi/rp2040/rp2040_usb.h159
-rw-r--r--src/portable/renesas/rusb2/dcd_rusb2.c34
-rw-r--r--src/portable/st/stm32_fsdev/dcd_stm32_fsdev.c45
-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.h64
-rw-r--r--src/portable/st/stm32_fsdev/hcd_stm32_fsdev.c57
-rw-r--r--src/portable/synopsys/dwc2/dcd_dwc2.c190
-rw-r--r--src/portable/synopsys/dwc2/dwc2_at32.h26
-rw-r--r--src/portable/synopsys/dwc2/dwc2_bcm.h8
-rw-r--r--src/portable/synopsys/dwc2/dwc2_common.h16
-rw-r--r--src/portable/synopsys/dwc2/dwc2_efm32.h8
-rw-r--r--src/portable/synopsys/dwc2/dwc2_esp32.h26
-rw-r--r--src/portable/synopsys/dwc2/dwc2_gd32.h8
-rw-r--r--src/portable/synopsys/dwc2/dwc2_info.md116
-rwxr-xr-xsrc/portable/synopsys/dwc2/dwc2_info.py5
-rw-r--r--src/portable/synopsys/dwc2/dwc2_nrf.h95
-rw-r--r--src/portable/synopsys/dwc2/dwc2_stm32.h48
-rw-r--r--src/portable/synopsys/dwc2/dwc2_type.h3
-rw-r--r--src/portable/synopsys/dwc2/dwc2_xmc.h8
-rw-r--r--src/portable/synopsys/dwc2/hcd_dwc2.c81
-rw-r--r--src/portable/wch/ch32_usbfs_reg.h140
-rw-r--r--src/portable/wch/dcd_ch32_usbfs.c270
-rw-r--r--src/portable/wch/dcd_ch32_usbhs.c434
-rw-r--r--src/tinyusb.mk4
-rw-r--r--src/tusb.c17
-rw-r--r--src/tusb.h8
-rw-r--r--src/tusb_option.h34
83 files changed, 5496 insertions, 2515 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/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.c b/src/class/midi/midi_host.c
index 8d59dfc66..a53546404 100644
--- a/src/class/midi/midi_host.c
+++ b/src/class/midi/midi_host.c
@@ -461,7 +461,7 @@ uint32_t tuh_midi_stream_write(uint8_t idx, uint8_t cable_num, uint8_t const *bu
if (data >= MIDI_STATUS_SYSREAL_TIMING_CLOCK) {
// real-time messages need to be sent right away
midi_driver_stream_t streamrt;
- streamrt.buffer[0] = MIDI_CIN_SYSEX_END_1BYTE;
+ streamrt.buffer[0] = (uint8_t)((cable_num << 4) | MIDI_CIN_SYSEX_END_1BYTE);
streamrt.buffer[1] = data;
streamrt.index = 2;
streamrt.total = 2;
@@ -476,9 +476,9 @@ uint32_t tuh_midi_stream_write(uint8_t idx, uint8_t cable_num, uint8_t const *bu
stream->buffer[1] = data;
// Check to see if we're still in a SysEx transmit.
- if (stream->buffer[0] == MIDI_CIN_SYSEX_START) {
+ if ((stream->buffer[0] & 0xF) == MIDI_CIN_SYSEX_START) {
if (data == MIDI_STATUS_SYSEX_END) {
- stream->buffer[0] = MIDI_CIN_SYSEX_END_1BYTE;
+ stream->buffer[0] = (uint8_t)((cable_num << 4) | MIDI_CIN_SYSEX_END_1BYTE);
stream->total = 2;
} else {
stream->total = 4;
@@ -506,6 +506,7 @@ uint32_t tuh_midi_stream_write(uint8_t idx, uint8_t cable_num, uint8_t const *bu
stream->buffer[0] = MIDI_CIN_SYSEX_END_1BYTE;
stream->total = 2;
}
+ stream->buffer[0] |= (uint8_t)(cable_num << 4);
} else {
// Pack individual bytes if we don't support packing them into words.
stream->buffer[0] = (uint8_t) (cable_num << 4 | 0xf);
@@ -520,8 +521,8 @@ uint32_t tuh_midi_stream_write(uint8_t idx, uint8_t cable_num, uint8_t const *bu
stream->buffer[stream->index] = data;
stream->index++;
// See if this byte ends a SysEx.
- if (stream->buffer[0] == MIDI_CIN_SYSEX_START && data == MIDI_STATUS_SYSEX_END) {
- stream->buffer[0] = MIDI_CIN_SYSEX_START + (stream->index - 1);
+ if ((stream->buffer[0] & 0xF) == MIDI_CIN_SYSEX_START && data == MIDI_STATUS_SYSEX_END) {
+ stream->buffer[0] = (uint8_t)((cable_num << 4) | (MIDI_CIN_SYSEX_START + (stream->index - 1)));
stream->total = stream->index;
}
}
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.h b/src/class/mtp/mtp.h
index 236cf98e0..7b22837cd 100644
--- a/src/class/mtp/mtp.h
+++ b/src/class/mtp/mtp.h
@@ -798,45 +798,65 @@ TU_ATTR_ALWAYS_INLINE static inline uint32_t mtp_container_add_array(mtp_contain
}
TU_ATTR_ALWAYS_INLINE static inline uint32_t mtp_container_add_string(mtp_container_info_t* p_container, uint16_t* utf16) {
- uint8_t count = 0;
+ uint32_t count = 0;
while (utf16[count] != 0u) {
count++;
}
- const uint32_t added_len = 1u + (uint32_t) count * 2u;
- TU_ASSERT(p_container->header->len + added_len < CFG_TUD_MTP_EP_BUFSIZE, 0);
+ // MTP strings store length in a single uint8_t, including trailing null.
+ TU_ASSERT(count < UINT8_MAX, 0);
+ count++;
+
uint8_t* buf = p_container->payload + p_container->header->len - sizeof(mtp_container_header_t);
- *buf++ = count;
+ if (count == 1) {
+ // empty string (size only): single zero byte
+ TU_ASSERT(p_container->header->len + 1 < CFG_TUD_MTP_EP_BUFSIZE, 0);
+ *buf = 0;
+ p_container->header->len++;
+ return 1u;
+ }
+
+ const uint32_t added_len = 1u + count * 2u;
+ TU_ASSERT(p_container->header->len + added_len < CFG_TUD_MTP_EP_BUFSIZE, 0);
+
+ *buf++ = (uint8_t) count;
p_container->header->len++;
- memcpy(buf, utf16, 2u * (uint32_t) count);
+ memcpy(buf, utf16, 2u * count);
p_container->header->len += 2u * count;
return added_len;
}
TU_ATTR_ALWAYS_INLINE static inline uint32_t mtp_container_add_cstring(mtp_container_info_t* p_container, const char* str) {
- const uint8_t len = (uint8_t) (strlen(str) + 1); // include null
- TU_ASSERT(p_container->header->len + 1 + 2 * len < CFG_TUD_MTP_EP_BUFSIZE, 0);
+ const size_t cstr_len = strlen(str);
+ // MTP strings store length in a single uint8_t, including trailing null.
+ TU_ASSERT(cstr_len < UINT8_MAX, 0);
+
+ const uint32_t count = (uint32_t) cstr_len + 1u; // include null
uint8_t* buf = p_container->payload + p_container->header->len - sizeof(mtp_container_header_t);
- if (len == 1) {
- // empty string (null only): single zero byte
+ if (count == 1u) {
+ // empty string (size only): single zero byte
+ TU_ASSERT(p_container->header->len + 1 < CFG_TUD_MTP_EP_BUFSIZE, 0);
*buf = 0;
p_container->header->len++;
return 1u;
- } else {
- *buf++ = len;
- p_container->header->len++;
+ }
- for (uint8_t i = 0; i < len; i++) {
- buf[0] = str[i];
- buf[1] = 0;
- buf += 2;
- p_container->header->len += 2;
- }
- return 1u + 2u * len;
+ const uint32_t added_len = 1u + 2u * count;
+ TU_ASSERT(p_container->header->len + added_len < CFG_TUD_MTP_EP_BUFSIZE, 0);
+
+ *buf++ = (uint8_t) count;
+ p_container->header->len++;
+
+ for (uint32_t i = 0; i < count; i++) {
+ *buf++ = (uint8_t) str[i];
+ *buf++ = 0;
}
+ p_container->header->len += 2u * count;
+
+ return added_len;
}
TU_ATTR_ALWAYS_INLINE static inline uint32_t mtp_container_add_uint8(mtp_container_info_t* p_container, uint8_t data) {
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/mtp/mtp_device.h b/src/class/mtp/mtp_device.h
index 6cce7efbb..f2c5cef7f 100644
--- a/src/class/mtp/mtp_device.h
+++ b/src/class/mtp/mtp_device.h
@@ -78,6 +78,10 @@ typedef struct {
mtp_auint16_t(_capture_count) capture_formats; \
mtp_auint16_t(_playback_count) playback_formats; \
/* string fields will be added using append function */ \
+ /* mtp_string_t() Manufacturer */ \
+ /* mtp_string_t() Model */ \
+ /* mtp_string_t() Device Version */ \
+ /* mtp_string_t() Serial Number */ \
}
typedef MTP_DEVICE_INFO_STRUCT( //-V2586 [MISRA-C-18.7] Flexible array members should not be declared
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..a78e472c2 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,13 @@ 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;
+}
+
/**
* This is the NTB parameter structure
*
@@ -340,7 +354,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 +423,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 +723,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,6 +843,9 @@ 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;
}
@@ -878,10 +896,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 +927,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 +977,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 +1016,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..332df09b3 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,9 @@ 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);
+
// 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/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 dfeae3fbb..8408d3ae0 100644
--- a/src/common/tusb_mcu.h
+++ b/src/common/tusb_mcu.h
@@ -166,6 +166,7 @@
#define TUP_USBIP_DWC2
#define TUP_USBIP_DWC2_NRF
#define TUP_DCD_ENDPOINT_MAX 16
+ #define TUP_RHPORT_HIGHSPEED 1
#define CFG_TUH_DWC2_DMA_ENABLE_DEFAULT 0
//--------------------------------------------------------------------+
@@ -176,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
@@ -189,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
//--------------------------------------------------------------------+
@@ -202,6 +203,13 @@
#elif TU_CHECK_MCU(OPT_MCU_STM32C0)
#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_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)
@@ -278,6 +286,7 @@
#elif TU_CHECK_MCU(OPT_MCU_STM32G0)
#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_STM32G4)
@@ -293,6 +302,7 @@
#elif TU_CHECK_MCU(OPT_MCU_STM32H5)
#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_STM32H7)
@@ -366,6 +376,7 @@
#elif TU_CHECK_MCU(OPT_MCU_STM32U3)
#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_STM32U5)
@@ -373,6 +384,7 @@
#if defined(STM32U535xx) || defined(STM32U545xx)
#define TUP_USBIP_FSDEV
#define TUP_USBIP_FSDEV_STM32
+ #define TUP_USBIP_FSDEV_DRD
#define CFG_TUSB_FSDEV_PMA_SIZE 2048u
#else
#define TUP_USBIP_DWC2
@@ -405,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
@@ -449,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
@@ -470,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))
@@ -541,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
@@ -594,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
@@ -607,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)
@@ -651,7 +681,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
@@ -735,3 +765,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 42903576c..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);
@@ -803,33 +815,281 @@ void tud_task_ext(uint32_t timeout_ms, bool in_isr) {
break;
}
-#if CFG_TUSB_OS != OPT_OS_NONE && CFG_TUSB_OS != OPT_OS_PICO
- // return if there is no more events, for application to run other background
- if (osal_queue_empty(_usbd_q)) { return; }
-#endif
+ // allow to exit tud_task() if there is no event in the next run
+ timeout_ms = 0;
}
}
//--------------------------------------------------------------------+
+// 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);
}
@@ -862,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: {
@@ -1006,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: {
@@ -1064,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);
}
}
@@ -1146,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 );
@@ -1155,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));
}
}
@@ -1214,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
@@ -1307,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);
}
}
}
@@ -1405,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) {
@@ -1415,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) {
@@ -1437,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;
@@ -1469,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;
@@ -1502,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) {
@@ -1514,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) {
@@ -1527,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) {
@@ -1537,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;
}
/**
@@ -1557,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;
@@ -1604,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 75df6bf60..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,30 +398,29 @@ 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);
_usbh_data.call_after.func = func;
_usbh_data.call_after.arg = param;
- _usbh_data.call_after.at_ms = tusb_time_millis_api() + ms;
+ // add one to ensure we wait at least 'ms' milliseconds
+ _usbh_data.call_after.at_ms = tusb_time_millis_api() + ms + 1;
return true;
}
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
@@ -445,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) {
@@ -472,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) {
@@ -534,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++) {
@@ -552,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);
@@ -567,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);
@@ -591,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);
@@ -610,11 +666,25 @@ bool tuh_task_event_ready(void) {
}
#if CFG_TUH_HUB
- if (!osal_queue_empty(_usbh_daq)) {
+ if (_usbh_data.enumerating_daddr == TUSB_INDEX_INVALID_8 &&
+ !osal_queue_empty(_usbh_daq)) {
return true;
}
#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) {
+ return true;
+ }
+ }
+
return false;
}
@@ -642,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
@@ -674,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
@@ -735,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);
@@ -797,73 +884,179 @@ void tuh_task_ext(uint32_t timeout_ms, bool in_isr) {
// Control transfer
//--------------------------------------------------------------------+
-static void _control_blocking_complete_cb(tuh_xfer_t* xfer) {
- // update result
- *((xfer_result_t*) xfer->user_data) = xfer->result;
+// Carries both fields the sync waiter cares about — capturing from xfer_temp
+// (snapshot taken before release_slot resets ctrl_info for the next pending
+// entry) so the waiter sees this xfer's data, not the next dispatched one's.
+typedef struct {
+ volatile xfer_result_t result;
+ volatile uint32_t actual_len;
+} control_xfer_sync_param_t;
+
+static void control_xfer_sync_complete(tuh_xfer_t* xfer) {
+ control_xfer_sync_param_t* s = (control_xfer_sync_param_t*) xfer->user_data;
+ s->actual_len = xfer->actual_len;
+ s->result = xfer->result;
}
// TODO timeout_ms is not supported yet
bool tuh_control_xfer (tuh_xfer_t* xfer) {
- TU_VERIFY(xfer->ep_addr == 0 && xfer->setup); // EP0 with setup packet
const uint8_t daddr = xfer->daddr;
- TU_VERIFY(tuh_connected(daddr));
-
+ TU_VERIFY(daddr <= TOTAL_DEVICES && xfer->ep_addr == 0 && xfer->setup); // EP0 with setup packet
usbh_ctrl_xfer_info_t* ctrl_info = &_usbh_data.ctrl_xfer_info;
- TU_VERIFY(ctrl_info->stage == CONTROL_STAGE_IDLE); // pre-check to help reducing mutex lock
- (void) osal_mutex_lock(_usbh_mutex, OSAL_TIMEOUT_WAIT_FOREVER);
- bool const is_idle = (ctrl_info->stage == CONTROL_STAGE_IDLE);
- if (is_idle) {
- ctrl_info->stage = CONTROL_STAGE_SETUP;
- ctrl_info->daddr = daddr;
- ctrl_info->actual_len = 0;
- ctrl_info->failed_count = 0;
+#if CFG_TUSB_OS_HAS_SCHEDULER
+ // Sync (complete_cb == NULL) from a host-stack callback is forbidden on
+ // RTOS targets — the event-loop driver can't block on its own pending xfer
+ // (deadlock if other control xfers are queued behind). Use async with a
+ // chained cb instead. OS_NONE / OS_PICO are exempt: they have a single
+ // execution context and the recursive-drive path is the only way to wait.
+ TU_ASSERT(!(xfer->complete_cb == NULL &&
+ osal_task_get_current_handle() == _usbh_data.task_hdl));
+#endif
- ctrl_info->buffer = xfer->buffer;
- ctrl_info->complete_cb = xfer->complete_cb;
- ctrl_info->user_data = xfer->user_data;
- _usbh_epbuf.request = (*xfer->setup);
- }
- (void) osal_mutex_unlock(_usbh_mutex);
+ // Slot is single-threaded — when busy, sync callers block until it frees
+ // (blocking semantics require the result); async callers get queued in the
+ // pending FIFO and submitted by control_xfer_complete() when the slot
+ // drains. The test-and-{claim|enqueue} is one critical section so a slot
+ // that becomes IDLE between the check and the enqueue can't strand an async
+ // request in a queue nothing else drains.
+ const bool is_nonblocking = (xfer->complete_cb != NULL);
+ while (true) {
+ TU_VERIFY(tuh_connected(daddr));
+ bool claimed = false;
+ bool is_queued = false;
+ (void) osal_mutex_lock(_usbh_mutex, OSAL_TIMEOUT_WAIT_FOREVER);
+ if (ctrl_info->stage == CONTROL_STAGE_IDLE) {
+ ctrl_info->stage = CONTROL_STAGE_SETUP;
+ ctrl_info->daddr = daddr;
+ ctrl_info->actual_len = 0;
+ ctrl_info->failed_count = 0;
+
+ ctrl_info->buffer = xfer->buffer;
+ ctrl_info->complete_cb = xfer->complete_cb;
+ ctrl_info->user_data = xfer->user_data;
+ _usbh_epbuf.request = (*xfer->setup);
+ claimed = true;
+ } else if (is_nonblocking) {
+ // Async + busy: queue the transfer.
+ const usbh_pending_ctrl_t entry = {
+ .setup = *xfer->setup,
+ .buffer = xfer->buffer,
+ .complete_cb = xfer->complete_cb,
+ .user_data = xfer->user_data,
+ .daddr = daddr,
+ .daddr_gen = _usbh_data.daddr_gen[daddr]
+ };
+ is_queued = tu_fifo_write_n(&_usbh_pending_ctrl_q, &entry, sizeof(entry)) == sizeof(entry);
+ }
+
+ (void) osal_mutex_unlock(_usbh_mutex);
+
+ if (claimed) {
+ break;
+ }
+
+ if (is_nonblocking) {
+ return is_queued;
+ }
- TU_VERIFY(is_idle);
+ // - OS_HAS_SCHEDULER: delay 1 ms
+ // - Otherwise: single execution context; drive the loop ourselves to progress the in-flight transfer.
+#if CFG_TUSB_OS_HAS_SCHEDULER
+ osal_task_delay(1);
+#else
+ tuh_task_ext(0, false);
+#endif
+ }
TU_LOG_USBH("[%u:%u] %s: ", usbh_get_rhport(daddr), daddr,
(xfer->setup->bmRequestType_bit.type == TUSB_REQ_TYPE_STANDARD && xfer->setup->bRequest <= TUSB_REQ_SYNCH_FRAME) ?
tu_str_std_request[xfer->setup->bRequest] : "Class Request");
TU_LOG_BUF_USBH(xfer->setup, 8);
- if (xfer->complete_cb != NULL) {
- TU_ASSERT(usbh_setup_send(daddr, (uint8_t const *) &_usbh_epbuf.request));
- }else {
- // blocking if complete callback is not provided
- // change callback to internal blocking, and result as user argument
- volatile xfer_result_t result = XFER_RESULT_INVALID;
-
- // use user_data to point to xfer_result_t
- ctrl_info->user_data = (uintptr_t) &result;
- ctrl_info->complete_cb = _control_blocking_complete_cb;
+ // Sync: wire control_xfer_sync_complete BEFORE submit so a fast completion
+ // event has the cb in place. control_xfer_complete() captures both result
+ // and actual_len through this cb before release_slot overwrites ctrl_info.
+ volatile control_xfer_sync_param_t sync_state;
+ if (!is_nonblocking) {
+ sync_state.result = XFER_RESULT_INVALID;
+ sync_state.actual_len = 0;
+ ctrl_info->user_data = (uintptr_t) &sync_state;
+ ctrl_info->complete_cb = control_xfer_sync_complete;
+ }
- TU_ASSERT(usbh_setup_send(daddr, (uint8_t const *) &_usbh_epbuf.request));
+ if (!hcd_setup_send(usbh_get_rhport(daddr), daddr, (uint8_t const *) &_usbh_epbuf.request)) {
+ control_xfer_set_stage(CONTROL_STAGE_IDLE);
+ return false;
+ }
- while (result == XFER_RESULT_INVALID) {
- // Note: this can be called within an callback ie. part of tuh_task()
- // therefore even with RTOS tuh_task_ext() still need to be invoked
+ if (!is_nonblocking) {
+ // No tuh_connected() escape needed: usbh_device_close() routes through
+ // control_xfer_complete(daddr, FAILED) on disconnect, which fires
+ // sync_complete and unblocks this poll.
+ while (sync_state.result == XFER_RESULT_INVALID) {
+#if CFG_TUSB_OS_HAS_SCHEDULER
+ osal_task_delay(1);
+#else
tuh_task_ext(0, false);
- // TODO probably some timeout to prevent hanged
+#endif
}
- // update transfer result, user_data is expected to point to xfer_result_t
+ // Forward to caller (xfer->user_data, if set, is a xfer_result_t pointer).
if (xfer->user_data != 0) {
- *((xfer_result_t*) xfer->user_data) = result;
+ *((xfer_result_t*) xfer->user_data) = sync_state.result;
}
- xfer->result = result;
- xfer->actual_len = ctrl_info->actual_len;
+ xfer->result = sync_state.result;
+ xfer->actual_len = sync_state.actual_len;
}
return true;
}
-static void _control_xfer_complete(uint8_t daddr, xfer_result_t result) {
+// Start control transfer from pending fifo
+static void control_xfer_dispatch_pending(void) {
+ usbh_ctrl_xfer_info_t* ctrl_info = &_usbh_data.ctrl_xfer_info;
+
+ while (true) {
+ usbh_pending_ctrl_t xfer;
+ bool has_xfer = false;
+
+ (void) osal_mutex_lock(_usbh_mutex, OSAL_TIMEOUT_WAIT_FOREVER);
+ if (ctrl_info->stage == CONTROL_STAGE_IDLE &&
+ tu_fifo_read_n(&_usbh_pending_ctrl_q, &xfer, sizeof(xfer)) == sizeof(xfer)) {
+ ctrl_info->stage = CONTROL_STAGE_SETUP;
+ ctrl_info->daddr = xfer.daddr;
+ ctrl_info->actual_len = 0;
+ ctrl_info->failed_count = 0;
+ ctrl_info->buffer = xfer.buffer;
+ ctrl_info->complete_cb = xfer.complete_cb;
+ ctrl_info->user_data = xfer.user_data;
+ _usbh_epbuf.request = xfer.setup;
+ has_xfer = true;
+ }
+ (void) osal_mutex_unlock(_usbh_mutex);
+
+ if (!has_xfer) {
+ return; // nothing to do
+ }
+
+ // mismatched daddr_gen means pending transfer is stale due to the device got disconnected while in the FIFO
+ // Note: the address can be re-allocated to another device at this point.
+ if (xfer.daddr_gen == _usbh_data.daddr_gen[xfer.daddr]) {
+ TU_LOG_USBH("[%u:%u] %s: ", usbh_get_rhport(xfer.daddr), xfer.daddr,
+ (xfer.setup.bmRequestType_bit.type == TUSB_REQ_TYPE_STANDARD && xfer.setup.bRequest <= TUSB_REQ_SYNCH_FRAME) ?
+ tu_str_std_request[xfer.setup.bRequest] : "Class Request");
+ TU_LOG_BUF_USBH(&xfer.setup, 8);
+ if (hcd_setup_send(usbh_get_rhport(xfer.daddr), xfer.daddr, (uint8_t const *) &_usbh_epbuf.request)) {
+ return; // transfer kicked-off, we are done
+ }
+ }
+
+ // complete callback as FAILED and continue with next pending xfer
+ control_xfer_complete(xfer.daddr, XFER_RESULT_FAILED);
+ }
+}
+
+static void control_xfer_complete(uint8_t daddr, xfer_result_t result) {
TU_LOG_USBH("\r\n");
usbh_ctrl_xfer_info_t* ctrl_info = &_usbh_data.ctrl_xfer_info;
@@ -880,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);
@@ -894,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:
@@ -910,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;
@@ -923,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;
@@ -938,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;
@@ -955,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;
}
@@ -1002,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);
}
@@ -1034,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);
}
}
@@ -1101,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;
@@ -1121,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;
@@ -1169,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 94124ca81..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) );
}
@@ -123,6 +129,24 @@ TU_ATTR_ALWAYS_INLINE static inline bool osal_mutex_unlock(osal_mutex_t mutex_hd
return os_mutex_release(mutex_hdl) == OS_OK;
}
+TU_ATTR_ALWAYS_INLINE static inline os_time_t _osal_ms2tick(uint32_t msec) {
+ if (msec == OSAL_TIMEOUT_WAIT_FOREVER) {
+ return OS_TIMEOUT_NEVER;
+ }
+ if (msec == 0) {
+ return 0;
+ }
+
+ os_time_t ticks = os_time_ms_to_ticks32(msec);
+
+ // If 1 tick > 1 ms, still wait at least 1 tick for non-zero timeout.
+ if (ticks == 0) {
+ ticks = 1;
+ }
+
+ return ticks;
+}
+
//--------------------------------------------------------------------+
// QUEUE API
//--------------------------------------------------------------------+
@@ -161,10 +185,11 @@ TU_ATTR_ALWAYS_INLINE static inline bool osal_queue_delete(osal_queue_t qhdl) {
}
TU_ATTR_ALWAYS_INLINE static inline bool osal_queue_receive(osal_queue_t qhdl, void* data, uint32_t msec) {
- (void) msec; // os_eventq_get() does not take timeout, always behave as msec = WAIT_FOREVER
-
- struct os_event* ev;
- ev = os_eventq_get(&qhdl->evq);
+ struct os_eventq* evq = &qhdl->evq;
+ struct os_event* ev = os_eventq_poll(&evq, 1, _osal_ms2tick(msec));
+ if (!ev) {
+ return false;
+ }
memcpy(data, ev->ev_arg, qhdl->item_sz); // copy message
os_memblock_put(&qhdl->mpool, ev->ev_arg); // put back mem block
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 d329285e9..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,315 +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);
+ if (ep_csr->rx_csrl & MUSB_RXCSRL1_STALLED) {
+ ep_csr->rx_csrl &= ~(MUSB_RXCSRL1_STALLED | MUSB_RXCSRL1_OVER);
+ return; // sent STALL, do nothing
+ }
- TU_ASSERT(ep_csr->rx_csrl & MUSB_RXCSRL1_RXRDY);
+ // 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);
- void *buf = pipe->buf;
- 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;
}
- 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);
}
- ep_csr->rx_csrl = 0; /* Clear RXRDY bit */
- 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;
@@ -494,7 +652,7 @@ static void process_bus_reset(uint8_t rhport) {
*------------------------------------------------------------------*/
#if CFG_TUSB_DEBUG >= MUSB_DEBUG
-void print_musb_info(musb_regs_t* musb_regs) {
+static void print_musb_info(musb_regs_t* musb_regs) {
// print version, epinfo, raminfo, config_data0, fifo_size
TU_LOG1("musb version = %u.%u\r\n", musb_regs->hwvers_bit.major, musb_regs->hwvers_bit.minor);
TU_LOG1("Number of endpoints: %u TX, %u RX\r\n", musb_regs->epinfo_bit.tx_ep_num, musb_regs->epinfo_bit.rx_ep_num);
@@ -537,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
@@ -588,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;
@@ -639,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);
@@ -706,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;
}
@@ -729,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;
}
@@ -753,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);
@@ -805,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);
@@ -815,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/dcd_rp2040.c b/src/portable/raspberrypi/rp2040/dcd_rp2040.c
index 240e6c727..2b6bbc43b 100644
--- a/src/portable/raspberrypi/rp2040/dcd_rp2040.c
+++ b/src/portable/raspberrypi/rp2040/dcd_rp2040.c
@@ -69,63 +69,69 @@ TU_ATTR_ALWAYS_INLINE static inline hw_endpoint_t *hw_endpoint_get_by_addr(uint8
return hw_endpoint_get(num, dir);
}
-// main processing for dcd_edpt_iso_activate
-static void hw_endpoint_init(hw_endpoint_t *ep, uint8_t ep_addr, uint16_t wMaxPacketSize, uint8_t transfer_type) {
- ep->ep_addr = ep_addr;
- ep->next_pid = 0u;
- ep->wMaxPacketSize = wMaxPacketSize;
+TU_ATTR_ALWAYS_INLINE static inline io_rw_32 *get_ep_ctrl(uint8_t epnum, tusb_dir_t dir) {
+ if (epnum == 0) {
+ // EP0 has no endpoint control register because the buffer offsets are fixed and always enabled
+ return NULL;
+ }
+ struct usb_device_dpram_ep_ctrl *ep_ctrl = &usb_dpram->ep_ctrl[epnum - 1];
+ return (dir == TUSB_DIR_IN) ? &ep_ctrl->in : &ep_ctrl->out;
+}
+
+TU_ATTR_ALWAYS_INLINE static inline io_rw_32 *get_buf_ctrl(uint8_t epnum, tusb_dir_t dir) {
+ struct usb_device_dpram_ep_buf_ctrl *buf_ctrl = &usb_dpram->ep_buf_ctrl[epnum];
+ return (dir == TUSB_DIR_IN) ? &buf_ctrl->in : &buf_ctrl->out;
+}
+
+// Init and enable endpoint
+static void hw_endpoint_open(uint8_t ep_addr, uint16_t wMaxPacketSize, uint8_t transfer_type, bool ep_enabled) {
+ const uint8_t epnum = tu_edpt_number(ep_addr);
+ const tusb_dir_t dir = tu_edpt_dir(ep_addr);
+
+ hw_endpoint_t *ep = hw_endpoint_get(epnum, dir);
+ ep->ep_addr = ep_addr;
+ ep->next_pid = 0u;
+ ep->max_packet_size = wMaxPacketSize;
// Clear existing buffer control state
- io_rw_32 *buf_ctrl_reg = hwbuf_ctrl_reg_device(ep);
- *buf_ctrl_reg = 0;
+ io_rw_32 *buf_reg = get_buf_ctrl(epnum, dir);
+ *buf_reg = 0;
// allocated hw buffer
- const uint8_t epnum = tu_edpt_number(ep_addr);
if (epnum == 0) {
- // Buffer offset is fixed (also double buffered)
- ep->hw_data_buf = (uint8_t*) &usb_dpram->ep0_buf_a[0];
+ // Buffer offset is fixed (2 buffer allocated).
+ // Note: Only single buffer for EP since Double buffered RX can be troublesome with future data.
+ ep->dpram_buf = (uint8_t *)&usb_dpram->ep0_buf_a[0];
} else {
+ uint32_t ep_ctrl = EP_CTRL_INTERRUPT_PER_BUFFER | ((uint32_t)transfer_type << EP_CTRL_BUFFER_TYPE_LSB);
+
// round up size to multiple of 64
uint16_t size = (uint16_t)tu_round_up(wMaxPacketSize, 64);
// double buffered Bulk endpoint
if (transfer_type == TUSB_XFER_BULK) {
size *= 2u;
-
- #if TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX
- if (tu_edpt_dir(ep_addr) == TUSB_DIR_IN) {
+ #if CFG_TUSB_RP2_ERRATA_E15
+ if (dir == TUSB_DIR_IN) {
ep->e15_bulk_in = true;
}
#endif
}
// assign buffer
- ep->hw_data_buf = hw_buffer_ptr;
+ ep->dpram_buf = hw_buffer_ptr;
hw_buffer_ptr += size;
- hard_assert(hw_buffer_ptr < usb_dpram->epx_data + sizeof(usb_dpram->epx_data));
- pico_info(" Allocated %d bytes (0x%p)\r\n", size, ep->hw_data_buf);
- }
-}
-
-static void hw_endpoint_enable(hw_endpoint_t *ep, uint8_t transfer_type) {
- io_rw_32 *ctrl_reg = hwep_ctrl_reg_device(ep);
- // Set endpoint control register to enable (EP0 has no endpoint control register)
- if (ctrl_reg != NULL) {
- const uint32_t ctrl_value =
- EP_CTRL_ENABLE_BITS | ((uint32_t)transfer_type << EP_CTRL_BUFFER_TYPE_LSB) | hw_data_offset(ep->hw_data_buf);
- *ctrl_reg = ctrl_value;
- }
-}
+ ep_ctrl |= hw_data_offset(ep->dpram_buf);
+ if (ep_enabled) {
+ ep_ctrl |= EP_CTRL_ENABLE_BITS;
+ }
-// Init and enable endpoint
-static void hw_endpoint_open(uint8_t ep_addr, uint16_t wMaxPacketSize, uint8_t transfer_type) {
- const uint8_t epnum = tu_edpt_number(ep_addr);
- const tusb_dir_t dir = tu_edpt_dir(ep_addr);
- hw_endpoint_t *ep = hw_endpoint_get(epnum, dir);
+ *get_ep_ctrl(epnum, dir) = ep_ctrl;
- hw_endpoint_init(ep, ep_addr, wMaxPacketSize, transfer_type);
- hw_endpoint_enable(ep, transfer_type);
+ hard_assert(hw_buffer_ptr < usb_dpram->epx_data + sizeof(usb_dpram->epx_data));
+ pico_info(" Allocated %d bytes (0x%p)\r\n", size, ep->dpram_buf);
+ }
}
static void hw_endpoint_abort_xfer(struct hw_endpoint* ep) {
@@ -141,14 +147,9 @@ static void hw_endpoint_abort_xfer(struct hw_endpoint* ep) {
while ((usb_hw->abort_done & abort_mask) != abort_mask) {}
}
- uint32_t buf_ctrl = USB_BUF_CTRL_SEL; // reset to buffer 0
- if (ep->next_pid) {
- buf_ctrl |= USB_BUF_CTRL_DATA1_PID;
- }
-
- io_rw_32 *buf_ctrl_reg = hwbuf_ctrl_reg_device(ep);
- hwbuf_ctrl_set(buf_ctrl_reg, buf_ctrl);
- hw_endpoint_reset_transfer(ep);
+ io_rw_32 *buf_reg = get_buf_ctrl(epnum, dir);
+ *buf_reg = 0; // clear buffer control
+ rp2usb_reset_transfer(ep);
if (rp2040_chip_version() >= 2) {
usb_hw_clear->abort_done = abort_mask;
@@ -157,29 +158,33 @@ static void hw_endpoint_abort_xfer(struct hw_endpoint* ep) {
}
static void __tusb_irq_path_func(handle_hw_buff_status)(void) {
- uint32_t remaining_buffers = usb_hw->buf_status;
- pico_trace("buf_status = 0x%08lx\r\n", remaining_buffers);
- uint bit = 1u;
- for (uint8_t i = 0; remaining_buffers && i < USB_MAX_ENDPOINTS * 2; i++) {
- if (remaining_buffers & bit) {
- // clear this in advance
- usb_hw_clear->buf_status = bit;
+ uint32_t buf_status = usb_hw->buf_status;
+ pico_trace("buf_status = 0x%08lx\r\n", buf_status);
+ while (buf_status) {
+ // ctz/clz is faster than loop which has only a few bit set in general
+ const uint8_t i = (uint8_t) __builtin_ctz(buf_status);
+ const uint32_t bit = TU_BIT(i);
+
+ // IN transfer for even i, OUT transfer for odd i
+ const uint8_t epnum = i >> 1u;
+ const tusb_dir_t dir = (i & 1u) ? TUSB_DIR_OUT : TUSB_DIR_IN;
+ hw_endpoint_t *ep = hw_endpoint_get(epnum, dir);
+ io_rw_32 *ep_reg = get_ep_ctrl(epnum, dir);
+ io_rw_32 *buf_reg = get_buf_ctrl(epnum, dir);
- // IN transfer for even i, OUT transfer for odd i
- const uint8_t epnum = i >> 1u;
- const tusb_dir_t dir = (i & 1u) ? TUSB_DIR_OUT : TUSB_DIR_IN;
- hw_endpoint_t *ep = hw_endpoint_get(epnum, dir);
+ // Double-buffered: if both buffers completed at once, buf_status re-sets
+ // immediately after clearing (datasheet Table 406). Process the second buffer too.
+ while (usb_hw->buf_status & bit) {
+ const uint8_t buf_id = (usb_hw->buf_cpu_should_handle & bit) ? 1 : 0; // before clear buf_status
+ usb_hw_clear->buf_status = bit;
+ buf_status &= ~bit;
- const bool done = hw_endpoint_xfer_continue(ep);
- if (done) {
- // Notify usbd
+ if (rp2usb_xfer_continue(ep, ep_reg, buf_reg, buf_id, dir == TUSB_DIR_OUT)) {
const uint16_t xferred_len = ep->xferred_len;
- hw_endpoint_reset_transfer(ep);
+ rp2usb_reset_transfer(ep);
dcd_event_xfer_complete(0, ep->ep_addr, xferred_len, XFER_RESULT_SUCCESS, true);
}
- remaining_buffers &= ~bit;
}
- bit <<= 1u;
}
}
@@ -189,7 +194,7 @@ TU_ATTR_ALWAYS_INLINE static inline void reset_ep0(void) {
for (uint8_t dir = 0; dir < 2; dir++) {
struct hw_endpoint *ep = hw_endpoint_get(0, dir);
ep->next_pid = 1u;
- if (ep->active) {
+ if (ep->state == EPSTATE_ACTIVE) {
hw_endpoint_abort_xfer(ep); // Abort any pending transfer per USB specs
}
}
@@ -211,52 +216,25 @@ static void __tusb_irq_path_func(reset_non_control_endpoints)(void) {
static void __tusb_irq_path_func(dcd_rp2040_irq)(void) {
const uint32_t status = usb_hw->ints;
- uint32_t handled = 0;
if (status & USB_INTF_DEV_SOF_BITS) {
- bool keep_sof_alive = false;
-
- handled |= USB_INTF_DEV_SOF_BITS;
-
-#if TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX
- // Errata 15 workaround for Device Bulk-In endpoint
- e15_last_sof = time_us_32();
-
- for (uint8_t i = 0; i < USB_MAX_ENDPOINTS; i++) {
- struct hw_endpoint *ep = hw_endpoint_get(i, TUSB_DIR_IN);
-
- // Active Bulk IN endpoint requires SOF
- if (ep->e15_bulk_in && ep->active) {
- keep_sof_alive = true;
-
- hw_endpoint_lock_update(ep, 1);
- if (ep->pending) {
- ep->pending = 0;
- hw_endpoint_start_next_buffer(ep);
- }
- hw_endpoint_lock_update(ep, -1);
- }
- }
-#endif
+ uint32_t sof_count = usb_hw->sof_rd & USB_SOF_RD_BITS; // clear interrupt by reading SOF_RD
- // disable SOF interrupt if it is used for RESUME in remote wakeup
- if (!keep_sof_alive && !_sof_enable) {
- usb_hw_clear->inte = USB_INTS_DEV_SOF_BITS;
- }
+ #if CFG_TUSB_RP2_ERRATA_E15
+ e15_last_sof = time_us_32(); // timing critical
+ #endif
- dcd_event_sof(0, usb_hw->sof_rd & USB_SOF_RD_BITS, true);
+ dcd_event_sof(0, sof_count, true);
}
// xfer events are handled before setup req. So if a transfer completes immediately
// before closing the EP, the events will be delivered in same order.
if (status & USB_INTS_BUFF_STATUS_BITS) {
- handled |= USB_INTS_BUFF_STATUS_BITS;
handle_hw_buff_status();
}
if (status & USB_INTS_SETUP_REQ_BITS) {
- handled |= USB_INTS_SETUP_REQ_BITS;
- uint8_t const* setup = remove_volatile_cast(uint8_t const*, &usb_dpram->setup_packet);
+ const uint8_t *setup = remove_volatile_cast(const uint8_t *, &usb_dpram->setup_packet);
// reset pid to both 1 (data and ack)
reset_ep0();
@@ -266,19 +244,68 @@ static void __tusb_irq_path_func(dcd_rp2040_irq)(void) {
usb_hw_clear->sie_status = USB_SIE_STATUS_SETUP_REC_BITS;
}
-#if FORCE_VBUS_DETECT == 0
+ // Errata 15 workaround for Device Bulk-In endpoint, must be after BUF_STATUS interrupt to sync buf control first
+ if (status & USB_INTF_DEV_SOF_BITS) {
+ bool keep_sof_alive = false;
+
+ #if CFG_TUSB_RP2_ERRATA_E15
+ for (uint8_t i = 0; i < USB_MAX_ENDPOINTS; i++) {
+ struct hw_endpoint *ep = hw_endpoint_get(i, TUSB_DIR_IN);
+
+ // Active Bulk IN endpoint requires SOF
+ if (ep->e15_bulk_in && ep->state >= EPSTATE_ACTIVE) {
+ keep_sof_alive = true;
+ hw_endpoint_lock_update(ep, 1);
+
+ if (ep->state == EPSTATE_PENDING) {
+ ep->state = EPSTATE_ACTIVE;
+
+ io_rw_32 *buf_reg32 = get_buf_ctrl(i, TUSB_DIR_IN);
+ io_rw_16 *buf_reg16 = (io_rw_16 *)buf_reg32;
+
+ // Check each buffer half: idle when both FULL and AVAIL are clear.
+ // Use 16-bit writes to avoid clobbering the other half (DPSRAM concurrent access).
+ enum {
+ BUSY_MASK = USB_BUF_CTRL_FULL | USB_BUF_CTRL_AVAIL
+ };
+
+ const bool buf0_idle = !(buf_reg16[0] & BUSY_MASK);
+ const bool buf1_idle = (ep->remaining_len > 0) && !(buf_reg16[1] & BUSY_MASK);
+
+ if (buf0_idle && buf1_idle) {
+ // both are idle, start fresh
+ io_rw_32 *ep_reg = get_ep_ctrl(i, TUSB_DIR_IN);
+ rp2usb_buffer_start(ep, ep_reg, buf_reg32, false);
+ } else if (buf0_idle) {
+ uint16_t buf0 = bufctrl_prepare16(ep, ep->dpram_buf, false);
+ bufctrl_write16(buf_reg16, buf0);
+ } else if (buf1_idle) {
+ uint16_t buf1 = bufctrl_prepare16(ep, ep->dpram_buf + 64, false);
+ bufctrl_write16(buf_reg16 + 1, buf1);
+ }
+ }
+
+ hw_endpoint_lock_update(ep, -1);
+ }
+ }
+ #endif
+
+ // disable SOF interrupt if it is used for RESUME in remote wakeup
+ if (!keep_sof_alive && !_sof_enable) {
+ usb_hw_clear->inte = USB_INTS_DEV_SOF_BITS;
+ }
+ }
+
+ #if FORCE_VBUS_DETECT == 0
// Since we force VBUS detect On, device will always think it is connected and
// couldn't distinguish between disconnect and suspend
if (status & USB_INTS_DEV_CONN_DIS_BITS) {
- handled |= USB_INTS_DEV_CONN_DIS_BITS;
-
if (usb_hw->sie_status & USB_SIE_STATUS_CONNECTED_BITS) {
// Connected: nothing to do
} else {
// Disconnected
dcd_event_bus_signal(0, DCD_EVENT_UNPLUGGED, true);
}
-
usb_hw_clear->sie_status = USB_SIE_STATUS_CONNECTED_BITS;
}
#endif
@@ -286,9 +313,6 @@ static void __tusb_irq_path_func(dcd_rp2040_irq)(void) {
// SE0 for 2.5 us or more (will last at least 10ms)
if (status & USB_INTS_BUS_RESET_BITS) {
pico_trace("BUS RESET\r\n");
-
- handled |= USB_INTS_BUS_RESET_BITS;
-
usb_hw->dev_addr_ctrl = 0;
reset_non_control_endpoints();
dcd_event_bus_reset(0, TUSB_SPEED_FULL, true);
@@ -311,20 +335,15 @@ static void __tusb_irq_path_func(dcd_rp2040_irq)(void) {
* being disconnected and suspended.
*/
if (status & USB_INTS_DEV_SUSPEND_BITS) {
- handled |= USB_INTS_DEV_SUSPEND_BITS;
dcd_event_bus_signal(0, DCD_EVENT_SUSPEND, true);
usb_hw_clear->sie_status = USB_SIE_STATUS_SUSPENDED_BITS;
}
if (status & USB_INTS_DEV_RESUME_FROM_HOST_BITS) {
- handled |= USB_INTS_DEV_RESUME_FROM_HOST_BITS;
dcd_event_bus_signal(0, DCD_EVENT_RESUME, true);
usb_hw_clear->sie_status = USB_SIE_STATUS_RESUME_BITS;
}
- if (status ^ handled) {
- panic("Unhandled IRQ 0x%x\n", (uint) (status ^ handled));
- }
}
/*------------------------------------------------------------------*/
@@ -340,7 +359,7 @@ bool dcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) {
(void) rh_init;
assert(rhport == 0);
- TU_LOG(2, "Chip Version B%u\r\n", rp2040_chip_version());
+ // TU_LOG(1, "Chip Version B%u\r\n", rp2040_chip_version());
// Reset hardware to default state
rp2usb_init();
@@ -354,8 +373,8 @@ bool dcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) {
// Init control endpoints
tu_memclr(hw_endpoints[0], 2 * sizeof(hw_endpoint_t));
- hw_endpoint_open(0x0, 64, TUSB_XFER_CONTROL);
- hw_endpoint_open(0x80, 64, TUSB_XFER_CONTROL);
+ hw_endpoint_open(0x0, 64, TUSB_XFER_CONTROL, false);
+ hw_endpoint_open(0x80, 64, TUSB_XFER_CONTROL, false);
// Init non-control endpoints
reset_non_control_endpoints();
@@ -436,7 +455,7 @@ void dcd_sof_enable(uint8_t rhport, bool en) {
if (en) {
usb_hw_set->inte = USB_INTS_DEV_SOF_BITS;
}
-#if !TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX
+ #if !CFG_TUSB_RP2_ERRATA_E15
else {
// Don't clear immediately if the SOF workaround is in use.
// The SOF handler will conditionally disable the interrupt.
@@ -461,7 +480,7 @@ void dcd_edpt0_status_complete(uint8_t rhport, tusb_control_request_t const* req
bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const* desc_edpt) {
(void) rhport;
const uint8_t xfer_type = desc_edpt->bmAttributes.xfer;
- hw_endpoint_open(desc_edpt->bEndpointAddress, tu_edpt_packet_size(desc_edpt), xfer_type);
+ hw_endpoint_open(desc_edpt->bEndpointAddress, tu_edpt_packet_size(desc_edpt), xfer_type, true);
return true;
}
@@ -469,8 +488,7 @@ bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const* desc_edpt) {
// Some MCU need manual packet buffer allocation, we allocate the largest size to avoid clustering
bool dcd_edpt_iso_alloc(uint8_t rhport, uint8_t ep_addr, uint16_t largest_packet_size) {
(void)rhport;
- struct hw_endpoint *ep = hw_endpoint_get_by_addr(ep_addr);
- hw_endpoint_init(ep, ep_addr, largest_packet_size, TUSB_XFER_ISOCHRONOUS);
+ hw_endpoint_open(ep_addr, largest_packet_size, TUSB_XFER_ISOCHRONOUS, false);
return true;
}
@@ -480,14 +498,18 @@ bool dcd_edpt_iso_activate(uint8_t rhport, const tusb_desc_endpoint_t *ep_desc)
const uint8_t epnum = tu_edpt_number(ep_desc->bEndpointAddress);
const tusb_dir_t dir = tu_edpt_dir(ep_desc->bEndpointAddress);
struct hw_endpoint *ep = hw_endpoint_get(epnum, dir);
- TU_ASSERT(ep->hw_data_buf != NULL); // must be inited and allocated previously
+ TU_ASSERT(ep->dpram_buf != NULL); // must be inited and allocated previously
- if (ep->active) {
+ if (ep->state == EPSTATE_ACTIVE) {
hw_endpoint_abort_xfer(ep); // abort any pending transfer
}
- ep->wMaxPacketSize = ep_desc->wMaxPacketSize;
+ ep->max_packet_size = ep_desc->wMaxPacketSize;
- hw_endpoint_enable(ep, TUSB_XFER_ISOCHRONOUS);
+ // enable endpoint
+ io_rw_32 *ep_reg = get_ep_ctrl(epnum, dir);
+ if (ep_reg != NULL) {
+ *ep_reg |= EP_CTRL_ENABLE_BITS;
+ }
return true;
}
@@ -500,18 +522,27 @@ void dcd_edpt_close_all(uint8_t rhport) {
bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t *buffer, uint16_t total_bytes, bool is_isr) {
(void)rhport;
(void)is_isr;
- hw_endpoint_t *ep = hw_endpoint_get_by_addr(ep_addr);
- hw_endpoint_xfer_start(ep, buffer, NULL, total_bytes);
+ const uint8_t epnum = tu_edpt_number(ep_addr);
+ const tusb_dir_t dir = tu_edpt_dir(ep_addr);
+
+ hw_endpoint_t *ep = hw_endpoint_get(epnum, dir);
+ io_rw_32 *ep_reg = get_ep_ctrl(epnum, dir);
+ io_rw_32 *buf_reg = get_buf_ctrl(epnum, dir);
+ rp2usb_xfer_start(ep, ep_reg, buf_reg, buffer, NULL, total_bytes);
return true;
}
+#if CFG_TUD_EDPT_DEDICATED_HWFIFO
bool dcd_edpt_xfer_fifo(uint8_t rhport, uint8_t ep_addr, tu_fifo_t *ff, uint16_t total_bytes, bool is_isr) {
(void)rhport;
(void)is_isr;
- hw_endpoint_t *ep = hw_endpoint_get_by_addr(ep_addr);
- hw_endpoint_xfer_start(ep, NULL, ff, total_bytes);
+ hw_endpoint_t *ep = hw_endpoint_get(epnum, dir);
+ io_rw_32 *ep_reg = get_ep_ctrl(epnum, dir);
+ io_rw_32 *buf_reg = get_buf_ctrl(epnum, dir);
+ rp2usb_xfer_start(ep, ep_reg, buf_reg, NULL, ff, total_bytes);
return true;
}
+#endif
void dcd_edpt_stall(uint8_t rhport, uint8_t ep_addr) {
(void)rhport;
@@ -524,21 +555,22 @@ void dcd_edpt_stall(uint8_t rhport, uint8_t ep_addr) {
usb_hw_set->ep_stall_arm = (dir == TUSB_DIR_IN) ? USB_EP_STALL_ARM_EP0_IN_BITS : USB_EP_STALL_ARM_EP0_OUT_BITS;
}
- // stall and clear current pending buffer, may need to use EP_ABORT
- io_rw_32 *buf_ctrl_reg = hwbuf_ctrl_reg_device(ep);
- hwbuf_ctrl_set(buf_ctrl_reg, USB_BUF_CTRL_STALL);
+ // abort first then stall and clear current pending buffer
+ hw_endpoint_abort_xfer(ep);
+ io_rw_32 *buf_reg = get_buf_ctrl(epnum, dir);
+ *buf_reg = USB_BUF_CTRL_STALL;
}
void dcd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr) {
(void) rhport;
+ const uint8_t epnum = tu_edpt_number(ep_addr);
+ const tusb_dir_t dir = tu_edpt_dir(ep_addr);
- if (tu_edpt_number(ep_addr)) {
- struct hw_endpoint* ep = hw_endpoint_get_by_addr(ep_addr);
-
- // clear stall also reset toggle to DATA0, ready for next transfer
- ep->next_pid = 0;
- io_rw_32 *buf_ctrl_reg = hwbuf_ctrl_reg_device(ep);
- hwbuf_ctrl_clear_mask(buf_ctrl_reg, USB_BUF_CTRL_STALL);
+ if (epnum != 0) {
+ struct hw_endpoint* ep = hw_endpoint_get(epnum, dir);
+ ep->next_pid = 0; // reset data toggle
+ io_rw_32 *buf_reg = get_buf_ctrl(epnum, dir);
+ *buf_reg = 0;
}
}
diff --git a/src/portable/raspberrypi/rp2040/hcd_rp2040.c b/src/portable/raspberrypi/rp2040/hcd_rp2040.c
index 06c0ce340..064834efb 100644
--- a/src/portable/raspberrypi/rp2040/hcd_rp2040.c
+++ b/src/portable/raspberrypi/rp2040/hcd_rp2040.c
@@ -1,3 +1,4 @@
+
/*
* The MIT License (MIT)
*
@@ -29,48 +30,65 @@
#if CFG_TUH_ENABLED && (CFG_TUSB_MCU == OPT_MCU_RP2040) && !CFG_TUH_RPI_PIO_USB && !CFG_TUH_MAX3421
-#include "pico.h"
-#include "rp2040_usb.h"
-
-//--------------------------------------------------------------------+
-// INCLUDE
-//--------------------------------------------------------------------+
-#include "osal/osal.h"
+ #include "pico.h"
-#include "host/hcd.h"
-#include "host/usbh.h"
+ #if defined(PICO_RP2350) && PICO_RP2350 == 1
+ #define HAS_STOP_EPX_ON_NAK
+ #endif
// port 0 is native USB port, other is counted as software PIO
-#define RHPORT_NATIVE 0
+ #define RHPORT_NATIVE 0
+
+ //--------------------------------------------------------------------+
+ // INCLUDE
+ //--------------------------------------------------------------------+
+ #include "rp2040_usb.h"
+ #include "osal/osal.h"
+
+ #include "host/hcd.h"
+ #include "host/usbh.h"
//--------------------------------------------------------------------+
-// Low level rp2040 controller functions
+//
//--------------------------------------------------------------------+
-#ifndef PICO_USB_HOST_INTERRUPT_ENDPOINTS
-#define PICO_USB_HOST_INTERRUPT_ENDPOINTS (USB_MAX_ENDPOINTS - 1)
-#endif
-static_assert(PICO_USB_HOST_INTERRUPT_ENDPOINTS <= USB_MAX_ENDPOINTS, "");
-
// Host mode uses one shared endpoint register for non-interrupt endpoint
-static struct hw_endpoint ep_pool[1 + PICO_USB_HOST_INTERRUPT_ENDPOINTS];
-#define epx (ep_pool[0])
+static hw_endpoint_t ep_pool[USB_MAX_ENDPOINTS];
+static hw_endpoint_t *epx = &ep_pool[0]; // current active endpoint
+
+ #ifndef HAS_STOP_EPX_ON_NAK
+static volatile bool epx_switch_request = false;
+ #endif
+
+enum {
+ SIE_CTRL_SPEED_DISCONNECT = 0,
+ SIE_CTRL_SPEED_LOW = 1,
+ SIE_CTRL_SPEED_FULL = 2,
+};
-// Flags we set by default in sie_ctrl (we add other bits on top)
enum {
- SIE_CTRL_BASE = USB_SIE_CTRL_SOF_EN_BITS | USB_SIE_CTRL_KEEP_ALIVE_EN_BITS |
- USB_SIE_CTRL_PULLDOWN_EN_BITS | USB_SIE_CTRL_EP0_INT_1BUF_BITS
+ EPX_CTRL_DEFAULT = EP_CTRL_ENABLE_BITS | EP_CTRL_INTERRUPT_PER_BUFFER | offsetof(usb_host_dpram_t, epx_data)
};
-static struct hw_endpoint *get_dev_ep(uint8_t dev_addr, uint8_t ep_addr) {
- uint8_t num = tu_edpt_number(ep_addr);
- if (num == 0) {
- return &epx;
+//--------------------------------------------------------------------+
+//
+//--------------------------------------------------------------------+
+
+static hw_endpoint_t *edpt_alloc(void) {
+ for (uint i = 1; i < TU_ARRAY_SIZE(ep_pool); i++) {
+ hw_endpoint_t *ep = &ep_pool[i];
+ if (ep->max_packet_size == 0) {
+ return ep;
+ }
}
+ return NULL;
+}
- for (uint32_t i = 1; i < TU_ARRAY_SIZE(ep_pool); i++) {
- struct hw_endpoint *ep = &ep_pool[i];
- if (ep->configured && (ep->dev_addr == dev_addr) && (ep->ep_addr == ep_addr)) {
+static hw_endpoint_t *edpt_find(uint8_t daddr, uint8_t ep_addr) {
+ for (uint32_t i = 0; i < TU_ARRAY_SIZE(ep_pool); i++) {
+ hw_endpoint_t *ep = &ep_pool[i];
+ if ((ep->dev_addr == daddr) && (ep->max_packet_size > 0) &&
+ (ep->ep_addr == ep_addr || (tu_edpt_number(ep_addr) == 0 && tu_edpt_number(ep->ep_addr) == 0))) {
return ep;
}
}
@@ -78,6 +96,18 @@ static struct hw_endpoint *get_dev_ep(uint8_t dev_addr, uint8_t ep_addr) {
return NULL;
}
+TU_ATTR_ALWAYS_INLINE static inline io_rw_32 *dpram_int_ep_ctrl(uint8_t int_num) {
+ return &usbh_dpram->int_ep_ctrl[int_num - 1].ctrl;
+}
+
+TU_ATTR_ALWAYS_INLINE static inline io_rw_32 *dpram_int_ep_buffer_ctrl(uint8_t int_num) {
+ return &usbh_dpram->int_ep_buffer_ctrl[int_num - 1].ctrl;
+}
+
+//--------------------------------------------------------------------+
+//
+//--------------------------------------------------------------------+
+
TU_ATTR_ALWAYS_INLINE static inline uint8_t dev_speed(void) {
return (usb_hw->sie_status & USB_SIE_STATUS_SPEED_BITS) >> USB_SIE_STATUS_SPEED_LSB;
}
@@ -88,253 +118,300 @@ TU_ATTR_ALWAYS_INLINE static inline bool need_pre(uint8_t dev_addr) {
return hcd_port_speed_get(0) != tuh_speed_get(dev_addr);
}
-static void __tusb_irq_path_func(hw_xfer_complete)(struct hw_endpoint *ep, xfer_result_t xfer_result) {
- // Mark transfer as done before we tell the tinyusb stack
- uint8_t dev_addr = ep->dev_addr;
- uint8_t ep_addr = ep->ep_addr;
- uint xferred_len = ep->xferred_len;
- hw_endpoint_reset_transfer(ep);
- hcd_event_xfer_complete(dev_addr, ep_addr, xferred_len, xfer_result, true);
+//--------------------------------------------------------------------+
+// EPX
+//--------------------------------------------------------------------+
+TU_ATTR_ALWAYS_INLINE static inline void sie_stop_xfer(void) {
+ uint32_t sie_ctrl = (usb_hw->sie_ctrl & SIE_CTRL_BASE_MASK) | USB_SIE_CTRL_STOP_TRANS_BITS;
+ usb_hw->sie_ctrl = sie_ctrl;
+ while (usb_hw->sie_ctrl & USB_SIE_CTRL_STOP_TRANS_BITS) {}
}
-static void __tusb_irq_path_func(handle_hwbuf_status_bit)(uint bit, struct hw_endpoint *ep) {
- usb_hw_clear->buf_status = bit;
- const bool done = hw_endpoint_xfer_continue(ep);
- if (done) {
- hw_xfer_complete(ep, XFER_RESULT_SUCCESS);
+static void __tusb_irq_path_func(sie_start_xfer)(bool send_setup, bool is_rx, bool need_pre) {
+ uint32_t sie_ctrl = usb_hw->sie_ctrl & SIE_CTRL_BASE_MASK; // preserve base bits
+ if (send_setup) {
+ sie_ctrl |= USB_SIE_CTRL_SEND_SETUP_BITS;
+ } else {
+ sie_ctrl |= (is_rx ? USB_SIE_CTRL_RECEIVE_DATA_BITS : USB_SIE_CTRL_SEND_DATA_BITS);
+ }
+ if (need_pre) {
+ sie_ctrl |= USB_SIE_CTRL_PREAMBLE_EN_BITS;
}
+
+ // START_TRANS bit on SIE_CTRL has the same behavior as the AVAILABLE bit
+ // described in RP2040 Datasheet, release 2.1, section "4.1.2.5.1. Concurrent access".!
+ // We write everything except the START_TRANS bit first, then wait some cycles.
+ usb_hw->sie_ctrl = sie_ctrl;
+ busy_wait_at_least_cycles(12);
+ usb_hw->sie_ctrl = sie_ctrl | USB_SIE_CTRL_START_TRANS_BITS;
}
-static void __tusb_irq_path_func(handle_hwbuf_status)(void) {
- uint32_t buf_status = usb_hw->buf_status;
- pico_trace("buf_status 0x%08lx\n", buf_status);
+// prepare epx_ctrl register for new endpoint
+TU_ATTR_ALWAYS_INLINE static inline void epx_ctrl_prepare(uint8_t transfer_type) {
+ usbh_dpram->epx_ctrl = EPX_CTRL_DEFAULT | ((uint32_t)transfer_type << EP_CTRL_BUFFER_TYPE_LSB);
+}
- // Check EPX first
- uint32_t bit = 1u;
- if (buf_status & bit) {
- buf_status &= ~bit;
- struct hw_endpoint * ep = &epx;
- handle_hwbuf_status_bit(bit, ep);
- }
+// Save buffer context for EPX preemption (called after STOP_TRANS).
+// Undo PID toggle and buffer accounting for buffers NOT completed on the wire.
+// A buffer completed on wire means: controller reached STATUS phase (ACK received).
+// OUT completed: FULL cleared to 0 in STATUS phase (was 1 when armed)
+// IN completed: FULL set to 1 in STATUS phase (was 0 when armed)
+// So undo when: AVAIL=1 (never started), or (OUT: FULL=1) or (IN: FULL=0)
+static void __tusb_irq_path_func(epx_save_context)(hw_endpoint_t *ep) {
+ uint32_t buf_ctrl = usbh_dpram->epx_buf_ctrl;
+ const bool is_out = (tu_edpt_dir(ep->ep_addr) == TUSB_DIR_OUT);
- // Check "interrupt" (asynchronous) endpoints for both IN and OUT
- for (uint i = 1; i <= USB_HOST_INTERRUPT_ENDPOINTS && buf_status; i++) {
- // EPX is bit 0 & 1
- // IEP1 IN is bit 2
- // IEP1 OUT is bit 3
- // IEP2 IN is bit 4
- // IEP2 OUT is bit 5
- // IEP3 IN is bit 6
- // IEP3 OUT is bit 7
- // etc
- for (uint j = 0; j < 2; j++) {
- bit = 1 << (i * 2 + j);
- if (buf_status & bit) {
- buf_status &= ~bit;
- handle_hwbuf_status_bit(bit, &ep_pool[i]);
+ do {
+ const uint16_t bc16 = (uint16_t)buf_ctrl;
+ if (bc16) {
+ const bool avail = (bc16 & USB_BUF_CTRL_AVAIL);
+ const bool full = (bc16 & USB_BUF_CTRL_FULL);
+ if (avail || (is_out ? full : !full)) {
+ const uint16_t buf_len = bc16 & USB_BUF_CTRL_LEN_MASK;
+ ep->remaining_len += buf_len;
+ ep->next_pid ^= 1u;
+ if (is_out) {
+ ep->user_buf -= buf_len;
+ }
}
}
- }
- if (buf_status) {
- panic("Unhandled buffer %d\n", buf_status);
- }
-}
+ if (usbh_dpram->epx_ctrl & EP_CTRL_DOUBLE_BUFFERED_BITS) {
+ buf_ctrl >>= 16;
+ } else {
+ buf_ctrl = 0;
+ }
+ } while (buf_ctrl > 0);
-static void __tusb_irq_path_func(hw_trans_complete)(void)
-{
- if (usb_hw->sie_ctrl & USB_SIE_CTRL_SEND_SETUP_BITS)
- {
- pico_trace("Sent setup packet\n");
- struct hw_endpoint *ep = &epx;
- assert(ep->active);
- // Set transferred length to 8 for a setup packet
- ep->xferred_len = 8;
- hw_xfer_complete(ep, XFER_RESULT_SUCCESS);
- }
- else
- {
- // Don't care. Will handle this in buff status
- return;
- }
+ usbh_dpram->epx_buf_ctrl = 0;
+
+ ep->state = EPSTATE_PENDING;
}
-static void __tusb_irq_path_func(hcd_rp2040_irq)(void)
-{
- uint32_t status = usb_hw->ints;
- uint32_t handled = 0;
+// switch epx to new endpoint and start the transfer
+static void __tusb_irq_path_func(epx_switch_ep)(hw_endpoint_t *ep) {
+ const bool is_setup = (ep->state == EPSTATE_PENDING_SETUP);
- if ( status & USB_INTS_HOST_CONN_DIS_BITS )
- {
- handled |= USB_INTS_HOST_CONN_DIS_BITS;
+ epx = ep; // switch pointer
+ ep->state = EPSTATE_ACTIVE;
- if ( dev_speed() )
- {
- hcd_event_device_attach(RHPORT_NATIVE, true);
- }
- else
- {
- hcd_event_device_remove(RHPORT_NATIVE, true);
- }
+ if (is_setup) {
+ // panic("new setup \n");
+ usb_hw->dev_addr_ctrl = ep->dev_addr;
+ sie_start_xfer(true, false, ep->need_pre);
+ } else {
+ const bool is_rx = (tu_edpt_dir(ep->ep_addr) == TUSB_DIR_IN);
+ io_rw_32 *ep_reg = &usbh_dpram->epx_ctrl;
+ io_rw_32 *buf_reg = &usbh_dpram->epx_buf_ctrl;
- // Clear speed change interrupt
- usb_hw_clear->sie_status = USB_SIE_STATUS_SPEED_BITS;
- }
+ epx_ctrl_prepare(ep->transfer_type);
+ rp2usb_buffer_start(ep, ep_reg, buf_reg, is_rx);
- if ( status & USB_INTS_STALL_BITS )
- {
- // We have rx'd a stall from the device
- // NOTE THIS SHOULD HAVE PRIORITY OVER BUFF_STATUS
- // AND TRANS_COMPLETE as the stall is an alternative response
- // to one of those events
- pico_trace("Stall REC\n");
- handled |= USB_INTS_STALL_BITS;
- usb_hw_clear->sie_status = USB_SIE_STATUS_STALL_REC_BITS;
- hw_xfer_complete(&epx, XFER_RESULT_STALLED);
+ usb_hw->dev_addr_ctrl = (uint32_t)(ep->dev_addr | (tu_edpt_number(ep->ep_addr) << USB_ADDR_ENDP_ENDPOINT_LSB));
+ sie_start_xfer(is_setup, is_rx, ep->need_pre);
}
+}
- if ( status & USB_INTS_BUFF_STATUS_BITS )
- {
- handled |= USB_INTS_BUFF_STATUS_BITS;
- TU_LOG(2, "Buffer complete\r\n");
- handle_hwbuf_status();
+// Round-robin find next pending ep after current epx
+static hw_endpoint_t *__tusb_irq_path_func(epx_next_pending)(hw_endpoint_t *cur_ep) {
+ const uint cur_idx = (uint)(cur_ep - &ep_pool[0]);
+ for (uint i = cur_idx + 1; i < TU_ARRAY_SIZE(ep_pool); i++) {
+ if (ep_pool[i].state >= EPSTATE_PENDING) {
+ return &ep_pool[i];
+ }
}
-
- if ( status & USB_INTS_TRANS_COMPLETE_BITS )
- {
- handled |= USB_INTS_TRANS_COMPLETE_BITS;
- usb_hw_clear->sie_status = USB_SIE_STATUS_TRANS_COMPLETE_BITS;
- TU_LOG(2, "Transfer complete\r\n");
- hw_trans_complete();
+ for (uint i = 0; i < cur_idx; i++) {
+ if (ep_pool[i].state >= EPSTATE_PENDING) {
+ return &ep_pool[i];
+ }
}
+ return NULL;
+}
- if ( status & USB_INTS_ERROR_RX_TIMEOUT_BITS )
- {
- handled |= USB_INTS_ERROR_RX_TIMEOUT_BITS;
- usb_hw_clear->sie_status = USB_SIE_STATUS_RX_TIMEOUT_BITS;
- }
- if ( status & USB_INTS_ERROR_DATA_SEQ_BITS )
- {
- usb_hw_clear->sie_status = USB_SIE_STATUS_DATA_SEQ_ERROR_BITS;
- TU_LOG(3, " Seq Error: [0] = 0x%04u [1] = 0x%04x\r\n", tu_u32_low16(*hwbuf_ctrl_reg_host(&epx)),
- tu_u32_high16(*hwbuf_ctrl_reg_host(&epx)));
- panic("Data Seq Error \n");
- }
+//--------------------------------------------------------------------+
+// Interrupt handlers
+//--------------------------------------------------------------------+
+static void __tusb_irq_path_func(xfer_complete_isr)(hw_endpoint_t *ep, xfer_result_t xfer_result, bool is_more) {
+ // Mark transfer as done before we tell the tinyusb stack
+ uint32_t xferred_len = ep->xferred_len;
+ rp2usb_reset_transfer(ep);
+ hcd_event_xfer_complete(ep->dev_addr, ep->ep_addr, xferred_len, xfer_result, true);
- if ( status ^ handled )
- {
- panic("Unhandled IRQ 0x%x\n", (uint) (status ^ handled));
+ // Carry more transfer on epx
+ if (is_more) {
+ hw_endpoint_t *next_ep = epx_next_pending(epx);
+ if (next_ep != NULL) {
+ epx_switch_ep(next_ep);
+ }
}
}
-void __tusb_irq_path_func(hcd_int_handler)(uint8_t rhport, bool in_isr) {
- (void) rhport;
- (void) in_isr;
- hcd_rp2040_irq();
-}
+static void __tusb_irq_path_func(handle_buf_status_isr)(void) {
+ pico_trace("buf_status 0x%08lx\n", buf_status);
+ enum {
+ BUF_STATUS_EPX = 1u
+ };
-static struct hw_endpoint *_next_free_interrupt_ep(void)
-{
- struct hw_endpoint * ep = NULL;
- for ( uint i = 1; i < TU_ARRAY_SIZE(ep_pool); i++ )
- {
- ep = &ep_pool[i];
- if ( !ep->configured )
- {
- // Will be configured by hw_endpoint_init / hw_endpoint_allocate
- ep->interrupt_num = (uint8_t) (i - 1);
- return ep;
+ // Check EPX first (bit 0).
+ // Double-buffered: if both buffers completed at once, buf_status re-sets
+ // immediately after clearing (datasheet Table 406). Process the second buffer too.
+ while (usb_hw->buf_status & BUF_STATUS_EPX) {
+ const uint8_t buf_id = (usb_hw->buf_cpu_should_handle & BUF_STATUS_EPX) ? 1 : 0;
+ usb_hw_clear->buf_status = 1u; // clear
+
+ io_rw_32 *ep_reg = &usbh_dpram->epx_ctrl;
+ io_rw_32 *buf_reg = &usbh_dpram->epx_buf_ctrl;
+ #ifndef HAS_STOP_EPX_ON_NAK
+ // Any packet completion (mid-transfer or final) means data is flowing.
+ // Clear switch request so the 2-SOF fallback only fires for NAK-retrying endpoints.
+ epx_switch_request = false;
+ #endif
+ if (rp2usb_xfer_continue(epx, ep_reg, buf_reg, buf_id, tu_edpt_dir(epx->ep_addr) == TUSB_DIR_IN)) {
+ xfer_complete_isr(epx, XFER_RESULT_SUCCESS, true);
}
}
- return ep;
-}
-static hw_endpoint_t *hw_endpoint_allocate(uint8_t transfer_type) {
- hw_endpoint_t *ep = NULL;
+ // Check "interrupt" (asynchronous) endpoints for both IN and OUT
+ uint32_t buf_status = usb_hw->buf_status & ~(uint32_t)BUF_STATUS_EPX;
+ while (buf_status) {
+ // ctz/clz is faster than loop which has only a few bit set in general
+ const uint8_t idx = (uint8_t)__builtin_ctz(buf_status);
+ const uint32_t bit = TU_BIT(idx);
+ usb_hw_clear->buf_status = bit;
+ buf_status &= ~bit;
- if (transfer_type == TUSB_XFER_CONTROL) {
- ep = &epx;
- ep->hw_data_buf = &usbh_dpram->epx_data[0];
- } else {
- // Note: even though datasheet name these "Interrupt" endpoints. These are actually
- // "Asynchronous" endpoints and can be used for other type such as: Bulk (ISO need confirmation)
- ep = _next_free_interrupt_ep();
- pico_info("Allocate %s ep %d\n", tu_edpt_type_str(transfer_type), ep->interrupt_num);
- assert(ep);
- // 0 for epx (double buffered): TODO increase to 1024 for ISO
- // 2x64 for intep0
- // 3x64 for intep1
- // etc
- ep->hw_data_buf = &usbh_dpram->epx_data[64 * (ep->interrupt_num + 2)];
+ // IN transfer for even i, OUT transfer for odd i
+ // EPX is bit 0. Bit 1 is not used
+ // IEP1 IN/OUT is bit 2, 3
+ // IEP2 IN/OUT is bit 4, 5 etc
+ const uint8_t epnum = idx >> 1u;
+ for (size_t e = 0; e < TU_ARRAY_SIZE(ep_pool); e++) {
+ hw_endpoint_t *ep = &ep_pool[e];
+ if (ep->interrupt_num == epnum) {
+ io_rw_32 *ep_reg = dpram_int_ep_ctrl(ep->interrupt_num);
+ io_rw_32 *buf_reg = dpram_int_ep_buffer_ctrl(ep->interrupt_num);
+ const bool done = rp2usb_xfer_continue(ep, ep_reg, buf_reg, 0, tu_edpt_dir(ep->ep_addr) == TUSB_DIR_IN);
+ if (done) {
+ xfer_complete_isr(ep, XFER_RESULT_SUCCESS, false);
+ }
+ break;
+ }
+ }
}
-
- return ep;
}
-static void hw_endpoint_init(struct hw_endpoint *ep, uint8_t dev_addr, uint8_t ep_addr, uint16_t wMaxPacketSize,
- uint8_t transfer_type, uint8_t bmInterval) {
- // Already has data buffer, endpoint control, and buffer control allocated at this point
- assert(ep->hw_data_buf);
+static void __tusb_irq_path_func(hcd_rp2040_irq)(void) {
+ const uint32_t status = usb_hw->ints;
- uint8_t const num = tu_edpt_number(ep_addr);
- tusb_dir_t const dir = tu_edpt_dir(ep_addr);
+ if (status & USB_INTS_HOST_CONN_DIS_BITS) {
+ uint8_t speed = dev_speed();
+ if (speed == SIE_CTRL_SPEED_DISCONNECT) {
+ hcd_event_device_remove(RHPORT_NATIVE, true);
+ } else {
+ if (speed == SIE_CTRL_SPEED_LOW) {
+ usb_hw->sie_ctrl = SIE_CTRL_BASE | USB_SIE_CTRL_KEEP_ALIVE_EN_BITS;
+ } else {
+ usb_hw->sie_ctrl = SIE_CTRL_BASE | USB_SIE_CTRL_SOF_EN_BITS;
+ }
+ hcd_event_device_attach(RHPORT_NATIVE, true);
+ }
+ usb_hw_clear->sie_status = USB_SIE_STATUS_SPEED_BITS;
+ }
- ep->ep_addr = ep_addr;
- ep->dev_addr = dev_addr;
+ if (status & USB_INTS_STALL_BITS) {
+ usb_hw_clear->sie_status = USB_SIE_STATUS_STALL_REC_BITS;
+ xfer_complete_isr(epx, XFER_RESULT_STALLED, true);
+ }
- // Response to a setup packet on EP0 starts with pid of 1
- ep->next_pid = (num == 0 ? 1u : 0u);
- ep->wMaxPacketSize = wMaxPacketSize;
+ if (status & USB_INTS_ERROR_RX_TIMEOUT_BITS) {
+ usb_hw_clear->sie_status = USB_SIE_STATUS_RX_TIMEOUT_BITS;
- pico_trace("hw_endpoint_init dev %d ep %02X xfer %d\n", ep->dev_addr, ep->ep_addr, transfer_type);
- pico_trace("dev %d ep %02X setup buffer @ 0x%p\n", ep->dev_addr, ep->ep_addr, ep->hw_data_buf);
- uint dpram_offset = hw_data_offset(ep->hw_data_buf);
- // Bits 0-5 should be 0
- assert(!(dpram_offset & 0b111111));
+ const uint32_t sie_ctrl = (usb_hw->sie_ctrl & SIE_CTRL_BASE_MASK) | USB_SIE_CTRL_STOP_TRANS_BITS;
+ usb_hw->sie_ctrl = sie_ctrl;
+ // while (usb_hw->sie_ctrl & USB_SIE_CTRL_STOP_TRANS_BITS) {}
- // Fill in endpoint control register with buffer offset
- uint32_t ctrl_value = EP_CTRL_ENABLE_BITS | EP_CTRL_INTERRUPT_PER_BUFFER |
- ((uint32_t)transfer_type << EP_CTRL_BUFFER_TYPE_LSB) | dpram_offset;
- if (bmInterval) {
- ctrl_value |= (uint32_t)((bmInterval - 1) << EP_CTRL_HOST_INTERRUPT_INTERVAL_LSB);
+ // Even if STOP_TRANS bit is clear, controller maybe in middle of retrying and may re-raise timeout once extra time
+ // Only handle if epx is active, don't carry more epx transfer since STOP_TRANS is raced and not safe.
+ if (epx->state == EPSTATE_ACTIVE) {
+ xfer_complete_isr(epx, XFER_RESULT_FAILED, false);
+ }
}
- io_rw_32 *ctrl_reg = hwep_ctrl_reg_host(ep);
- *ctrl_reg = ctrl_value;
- pico_trace("endpoint control (0x%p) <- 0x%lx\n", ctrl_reg, ctrl_value);
- ep->configured = true;
+ if (status & USB_INTS_TRANS_COMPLETE_BITS) {
+ // only applies for epx, interrupt endpoint does not seem to raise this
+ usb_hw_clear->sie_status = USB_SIE_STATUS_TRANS_COMPLETE_BITS;
+ if (usb_hw->sie_ctrl & USB_SIE_CTRL_SEND_SETUP_BITS) {
+ uint32_t sie_ctrl = usb_hw->sie_ctrl & SIE_CTRL_BASE_MASK;
+ usb_hw->sie_ctrl = sie_ctrl; // clear setup bit
+ epx->xferred_len = 8;
+ xfer_complete_isr(epx, XFER_RESULT_SUCCESS, true);
+ }
+ }
- if (ep != &epx) {
- // Endpoint has its own addr_endp and interrupt bits to be setup!
- // This is an interrupt/async endpoint. so need to set up ADDR_ENDP register with:
- // - device address
- // - endpoint number / direction
- // - preamble
- uint32_t reg = (uint32_t)(dev_addr | (num << USB_ADDR_ENDP1_ENDPOINT_LSB));
+ if (status & USB_INTS_BUFF_STATUS_BITS) {
+ handle_buf_status_isr();
+ }
- if (dir == TUSB_DIR_OUT) {
- reg |= USB_ADDR_ENDP1_INTEP_DIR_BITS;
+ // SOF-based round-robin MUST run BEFORE BUFF_STATUS to avoid processing
+ // buf_status on the wrong EPX after a completion+switch in handle_buf_status_isr.
+ #ifdef HAS_STOP_EPX_ON_NAK
+ if (status & USB_INTS_EPX_STOPPED_ON_NAK_BITS) {
+ usb_hw_clear->nak_poll = USB_NAK_POLL_EPX_STOPPED_ON_NAK_BITS;
+ hw_endpoint_t *next_ep = epx_next_pending(epx);
+ if (next_ep != NULL) {
+ epx_save_context(epx);
+ epx_switch_ep(next_ep);
+ } else {
+ usb_hw_clear->nak_poll = USB_NAK_POLL_STOP_EPX_ON_NAK_BITS;
+ sie_start_xfer(false, TUSB_DIR_IN == tu_edpt_dir(epx->ep_addr), epx->need_pre);
}
-
- if (need_pre(dev_addr)) {
- reg |= USB_ADDR_ENDP1_INTEP_PREAMBLE_BITS;
+ }
+ #else
+ // RP2040: on SOF, switch EPX if another endpoint is pending.
+ // First SOF sets epx_switch_request. If a transfer completes before next SOF, the flag is
+ // cleared (data is flowing, no need to force-switch). Second SOF with flag still set means
+ // no data exchanged (endpoint NAK-retrying): STOP_TRANS is safe and we switch.
+ // This avoids stopping mid-data-transfer which corrupts double-buffered PID tracking.
+ if (status & USB_INTS_HOST_SOF_BITS) {
+ (void)usb_hw->sof_rd; // clear SOF by reading SOF_RD
+ hw_endpoint_t *next_ep = epx_next_pending(epx);
+ if (next_ep == NULL) {
+ usb_hw_clear->inte = USB_INTE_HOST_SOF_BITS;
+ usb_hw->nak_poll = USB_NAK_POLL_RESET;
+ epx_switch_request = false;
+ } else if (epx->state == EPSTATE_ACTIVE) {
+ if (epx_switch_request) {
+ // Second SOF with no transfer completion: endpoint is NAK-retrying, safe to switch.
+ epx_switch_request = false;
+ sie_stop_xfer();
+ epx_save_context(epx);
+ epx_switch_ep(next_ep);
+ } else {
+ epx_switch_request = true;
+ }
}
- usb_hw->int_ep_addr_ctrl[ep->interrupt_num] = reg;
-
- // Finally, enable interrupt that endpoint
- usb_hw_set->int_ep_ctrl = 1 << (ep->interrupt_num + 1);
+ }
+ #endif
- // If it's an interrupt endpoint we need to set up the buffer control register
+ if (status & USB_INTS_ERROR_DATA_SEQ_BITS) {
+ usb_hw_clear->sie_status = USB_SIE_STATUS_DATA_SEQ_ERROR_BITS;
+ panic("Data Seq Error \n");
}
}
+void __tusb_irq_path_func(hcd_int_handler)(uint8_t rhport, bool in_isr) {
+ (void)rhport;
+ (void)in_isr;
+ hcd_rp2040_irq();
+}
+
//--------------------------------------------------------------------+
// HCD API
//--------------------------------------------------------------------+
-bool hcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) {
- (void) rhport;
- (void) rh_init;
+bool hcd_init(uint8_t rhport, const tusb_rhport_init_t *rh_init) {
+ (void)rhport;
+ (void)rh_init;
pico_trace("hcd_init %d\n", rhport);
assert(rhport == 0);
@@ -346,7 +423,6 @@ bool hcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) {
// Remove shared irq if it was previously added so as not to fill up shared irq slots
irq_remove_handler(USBCTRL_IRQ, hcd_rp2040_irq);
-
irq_add_shared_handler(USBCTRL_IRQ, hcd_rp2040_irq, PICO_SHARED_IRQ_HANDLER_HIGHEST_ORDER_PRIORITY);
// clear epx and interrupt eps
@@ -354,97 +430,83 @@ bool hcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) {
// Enable in host mode with SOF / Keep alive on
usb_hw->main_ctrl = USB_MAIN_CTRL_CONTROLLER_EN_BITS | USB_MAIN_CTRL_HOST_NDEVICE_BITS;
- usb_hw->sie_ctrl = SIE_CTRL_BASE;
- usb_hw->inte = USB_INTE_BUFF_STATUS_BITS |
- USB_INTE_HOST_CONN_DIS_BITS |
- USB_INTE_HOST_RESUME_BITS |
- USB_INTE_STALL_BITS |
- USB_INTE_TRANS_COMPLETE_BITS |
- USB_INTE_ERROR_RX_TIMEOUT_BITS |
- USB_INTE_ERROR_DATA_SEQ_BITS ;
+ usb_hw->sie_ctrl = SIE_CTRL_BASE;
+ usb_hw->inte = USB_INTE_BUFF_STATUS_BITS | USB_INTE_HOST_CONN_DIS_BITS | USB_INTE_HOST_RESUME_BITS |
+ USB_INTE_STALL_BITS | USB_INTE_TRANS_COMPLETE_BITS | USB_INTE_ERROR_RX_TIMEOUT_BITS |
+ USB_INTE_ERROR_DATA_SEQ_BITS;
+
+ #ifdef HAS_STOP_EPX_ON_NAK
+ usb_hw_set->inte = USB_INTE_EPX_STOPPED_ON_NAK_BITS;
+ #endif
return true;
}
bool hcd_deinit(uint8_t rhport) {
- (void) rhport;
-
+ (void)rhport;
irq_remove_handler(USBCTRL_IRQ, hcd_rp2040_irq);
reset_block(RESETS_RESET_USBCTRL_BITS);
unreset_block_wait(RESETS_RESET_USBCTRL_BITS);
-
return true;
}
-void hcd_port_reset(uint8_t rhport)
-{
- (void) rhport;
- pico_trace("hcd_port_reset\n");
- assert(rhport == 0);
+void hcd_port_reset(uint8_t rhport) {
+ (void)rhport;
// TODO: Nothing to do here yet. Perhaps need to reset some state?
}
-void hcd_port_reset_end(uint8_t rhport)
-{
- (void) rhport;
+void hcd_port_reset_end(uint8_t rhport) {
+ (void)rhport;
}
-bool hcd_port_connect_status(uint8_t rhport)
-{
- (void) rhport;
- pico_trace("hcd_port_connect_status\n");
- assert(rhport == 0);
+bool hcd_port_connect_status(uint8_t rhport) {
+ (void)rhport;
return usb_hw->sie_status & USB_SIE_STATUS_SPEED_BITS;
}
-tusb_speed_t hcd_port_speed_get(uint8_t rhport)
-{
- (void) rhport;
- assert(rhport == 0);
-
- // TODO: Should enumval this register
- switch ( dev_speed() )
- {
- case 1:
+tusb_speed_t hcd_port_speed_get(uint8_t rhport) {
+ (void)rhport;
+ switch (dev_speed()) {
+ case SIE_CTRL_SPEED_LOW:
return TUSB_SPEED_LOW;
- case 2:
+ case SIE_CTRL_SPEED_FULL:
return TUSB_SPEED_FULL;
default:
- panic("Invalid speed\n");
- // return TUSB_SPEED_INVALID;
+ return TUSB_SPEED_INVALID;
}
}
// Close all opened endpoint belong to this device
void hcd_device_close(uint8_t rhport, uint8_t dev_addr) {
- pico_trace("hcd_device_close %d\n", dev_addr);
- (void) rhport;
+ (void)rhport;
- // reset epx if it is currently active with unplugged device
- if (epx.configured && epx.active && epx.dev_addr == dev_addr) {
- epx.configured = false;
- *hwep_ctrl_reg_host(&epx) = 0;
- *hwbuf_ctrl_reg_host(&epx) = 0;
- hw_endpoint_reset_transfer(&epx);
+ if (dev_addr == 0) {
+ return; // address 0 is for device enumeration
}
- // dev0 only has ep0
- if (dev_addr != 0) {
- for (size_t i = 1; i < TU_ARRAY_SIZE(ep_pool); i++) {
- hw_endpoint_t *ep = &ep_pool[i];
- if (ep->dev_addr == dev_addr && ep->configured) {
- // in case it is an interrupt endpoint, disable it
- usb_hw_clear->int_ep_ctrl = (1 << (ep->interrupt_num + 1));
- usb_hw->int_ep_addr_ctrl[ep->interrupt_num] = 0;
+ rp2usb_critical_enter();
- // unconfigure the endpoint
- ep->configured = false;
- *hwep_ctrl_reg_host(ep) = 0;
- *hwbuf_ctrl_reg_host(ep) = 0;
- hw_endpoint_reset_transfer(ep);
+ for (size_t i = 0; i < TU_ARRAY_SIZE(ep_pool); i++) {
+ hw_endpoint_t *ep = &ep_pool[i];
+ if (ep->dev_addr == dev_addr && ep->max_packet_size > 0) {
+ ep->state = EPSTATE_IDLE; // clear any pending transfer
+
+ if (ep->interrupt_num > 0) {
+ // disable interrupt endpoint
+ usb_hw_clear->int_ep_ctrl = TU_BIT(ep->interrupt_num);
+ usb_hw->int_ep_addr_ctrl[ep->interrupt_num - 1] = 0;
+
+ io_rw_32 *ep_reg = dpram_int_ep_ctrl(ep->interrupt_num);
+ io_rw_32 *buf_reg = dpram_int_ep_buffer_ctrl(ep->interrupt_num);
+ *buf_reg = 0;
+ *ep_reg = 0;
}
+
+ ep->max_packet_size = 0; // mark as unused
}
}
+
+ rp2usb_critical_exit();
}
uint32_t hcd_frame_number(uint8_t rhport) {
@@ -469,122 +531,177 @@ void hcd_int_disable(uint8_t rhport) {
bool hcd_edpt_open(uint8_t rhport, uint8_t dev_addr, const tusb_desc_endpoint_t *ep_desc) {
(void)rhport;
pico_trace("hcd_edpt_open dev_addr %d, ep_addr %d\n", dev_addr, ep_desc->bEndpointAddress);
- hw_endpoint_t *ep = hw_endpoint_allocate(ep_desc->bmAttributes.xfer);
+ hw_endpoint_t *ep;
+ if (dev_addr == 0) {
+ ep = &ep_pool[0];
+ } else {
+ ep = edpt_alloc();
+ }
TU_ASSERT(ep);
- hw_endpoint_init(ep, dev_addr, ep_desc->bEndpointAddress, tu_edpt_packet_size(ep_desc), ep_desc->bmAttributes.xfer,
- ep_desc->bInterval);
+ const uint8_t ep_addr = ep_desc->bEndpointAddress;
+ const uint16_t max_packet_size = tu_edpt_packet_size(ep_desc);
+
+ ep->max_packet_size = max_packet_size;
+ ep->ep_addr = ep_addr;
+ ep->dev_addr = dev_addr;
+ ep->transfer_type = ep_desc->bmAttributes.xfer;
+ ep->need_pre = need_pre(dev_addr);
+ ep->next_pid = 0u;
+
+ if (ep->transfer_type != TUSB_XFER_INTERRUPT) {
+ ep->dpram_buf = usbh_dpram->epx_data;
+ } else {
+ // from 15 interrupt endpoints pool
+ uint8_t int_idx;
+ for (int_idx = 0; int_idx < USB_HOST_INTERRUPT_ENDPOINTS; int_idx++) {
+ if (!tu_bit_test(usb_hw->int_ep_ctrl, 1 + int_idx)) {
+ ep->interrupt_num = int_idx + 1;
+ break;
+ }
+ }
+ assert(int_idx < USB_HOST_INTERRUPT_ENDPOINTS);
+ assert(ep_desc->bInterval > 0);
+
+ //------------- dpram buf -------------//
+ // 15x64 last bytes of DPRAM for interrupt endpoint buffers
+ ep->dpram_buf = (uint8_t *)(USBCTRL_DPRAM_BASE + USB_DPRAM_MAX - (int_idx + 1u) * 64u);
+ uint32_t ep_ctrl = EP_CTRL_ENABLE_BITS | EP_CTRL_INTERRUPT_PER_BUFFER |
+ (TUSB_XFER_INTERRUPT << EP_CTRL_BUFFER_TYPE_LSB) | hw_data_offset(ep->dpram_buf) |
+ ((uint32_t)(ep_desc->bInterval - 1) << EP_CTRL_HOST_INTERRUPT_INTERVAL_LSB);
+ usbh_dpram->int_ep_ctrl[int_idx].ctrl = ep_ctrl;
+
+ //------------- address control -------------//
+ const uint8_t epnum = tu_edpt_number(ep_addr);
+ uint32_t addr_ctrl = (uint32_t)(dev_addr | (epnum << USB_ADDR_ENDP1_ENDPOINT_LSB));
+ if (tu_edpt_dir(ep_addr) == TUSB_DIR_OUT) {
+ addr_ctrl |= USB_ADDR_ENDP1_INTEP_DIR_BITS;
+ }
+ if (ep->need_pre) {
+ addr_ctrl |= USB_ADDR_ENDP1_INTEP_PREAMBLE_BITS;
+ }
+ usb_hw->int_ep_addr_ctrl[int_idx] = addr_ctrl;
+
+ // Finally, activate interrupt endpoint
+ usb_hw_set->int_ep_ctrl = TU_BIT(ep->interrupt_num);
+ }
return true;
}
bool hcd_edpt_close(uint8_t rhport, uint8_t daddr, uint8_t ep_addr) {
- (void) rhport; (void) daddr; (void) ep_addr;
+ (void)rhport;
+ (void)daddr;
+ (void)ep_addr;
return false; // TODO not implemented yet
}
-bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t *buffer, uint16_t buflen) {
- (void) rhport;
-
- pico_trace("hcd_edpt_xfer dev_addr %d, ep_addr 0x%x, len %d\n", dev_addr, ep_addr, buflen);
-
- const uint8_t ep_num = tu_edpt_number(ep_addr);
- tusb_dir_t const ep_dir = tu_edpt_dir(ep_addr);
+bool hcd_edpt_abort_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr) {
+ (void)rhport;
+ (void)dev_addr;
+ (void)ep_addr;
+ // TODO not implemented yet
+ return false;
+}
- // Get appropriate ep. Either EPX or interrupt endpoint
- struct hw_endpoint *ep = get_dev_ep(dev_addr, ep_addr);
+bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t *buffer, uint16_t buflen) {
+ (void)rhport;
+ hw_endpoint_t *ep = edpt_find(dev_addr, ep_addr);
TU_ASSERT(ep);
- // EP should be inactive
- assert(!ep->active);
-
- // Control endpoint can change direction 0x00 <-> 0x80
- if (ep_addr != ep->ep_addr) {
- assert(ep_num == 0);
+ if (ep->interrupt_num > 0) {
+ // For interrupt endpoint control and buffer is already configured
+ // Note: Interrupt is single buffered only
+ io_rw_32 *ep_reg = dpram_int_ep_ctrl(ep->interrupt_num);
+ 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 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;
+ }
- // Direction has flipped on endpoint control so re init it but with same properties
- hw_endpoint_init(ep, dev_addr, ep_addr, ep->wMaxPacketSize, TUSB_XFER_CONTROL, 0);
- }
+ // If EPX is busy with another transfer, mark as pending
+ rp2usb_critical_enter();
+ if (epx->state == EPSTATE_ACTIVE) {
+ ep->user_buf = buffer;
+ ep->remaining_len = buflen;
+ ep->state = EPSTATE_PENDING;
- // If a normal transfer (non-interrupt) then initiate using
- // sie ctrl registers. Otherwise, interrupt ep registers should
- // already be configured
- if (ep == &epx) {
- hw_endpoint_xfer_start(ep, buffer, NULL, buflen);
+ #ifdef HAS_STOP_EPX_ON_NAK
+ usb_hw_set->nak_poll = USB_NAK_POLL_STOP_EPX_ON_NAK_BITS;
+ #else
+ // Only enable SOF round-robin for non-control endpoints
+ usb_hw->nak_poll = (300 << USB_NAK_POLL_DELAY_FS_LSB) | (300 << USB_NAK_POLL_DELAY_LS_LSB);
+ usb_hw_set->inte = USB_INTE_HOST_SOF_BITS;
+ #endif
+ } else {
+ io_rw_32 *ep_reg = &usbh_dpram->epx_ctrl;
+ io_rw_32 *buf_reg = &usbh_dpram->epx_buf_ctrl;
- // That has set up buffer control, endpoint control etc
- // for host we have to initiate the transfer
- usb_hw->dev_addr_ctrl = (uint32_t) (dev_addr | (ep_num << USB_ADDR_ENDP_ENDPOINT_LSB));
+ epx = ep;
- uint32_t flags = USB_SIE_CTRL_START_TRANS_BITS | SIE_CTRL_BASE |
- (ep_dir ? USB_SIE_CTRL_RECEIVE_DATA_BITS : USB_SIE_CTRL_SEND_DATA_BITS) |
- (need_pre(dev_addr) ? USB_SIE_CTRL_PREAMBLE_EN_BITS : 0);
- // START_TRANS bit on SIE_CTRL seems to exhibit the same behavior as the AVAILABLE bit
- // described in RP2040 Datasheet, release 2.1, section "4.1.2.5.1. Concurrent access".
- // We write everything except the START_TRANS bit first, then wait some cycles.
- usb_hw->sie_ctrl = flags & ~USB_SIE_CTRL_START_TRANS_BITS;
- busy_wait_at_least_cycles(12);
- usb_hw->sie_ctrl = flags;
- } else {
- hw_endpoint_xfer_start(ep, buffer, NULL, buflen);
+ epx_ctrl_prepare(ep->transfer_type);
+ rp2usb_xfer_start(ep, ep_reg, buf_reg, buffer, NULL, buflen); // prepare bufctrl
+ usb_hw->dev_addr_ctrl = (uint32_t)(ep->dev_addr | (tu_edpt_number(ep->ep_addr) << USB_ADDR_ENDP_ENDPOINT_LSB));
+ sie_start_xfer(false, tu_edpt_dir(ep->ep_addr) == TUSB_DIR_IN, ep->need_pre);
+ }
+ rp2usb_critical_exit();
}
return true;
}
-bool hcd_edpt_abort_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr) {
- (void) rhport;
- (void) dev_addr;
- (void) ep_addr;
- // TODO not implemented yet
- return false;
-}
+bool hcd_setup_send(uint8_t rhport, uint8_t dev_addr, const uint8_t setup_packet[8]) {
+ (void)rhport;
+
+ hw_endpoint_t *ep = edpt_find(dev_addr, 0x00);
+ TU_ASSERT(ep);
-bool hcd_setup_send(uint8_t rhport, uint8_t dev_addr, uint8_t const setup_packet[8])
-{
- (void) rhport;
+ rp2usb_critical_enter();
- // Copy data into setup packet buffer
+ // Copy data into setup packet buffer (usbh only schedules one setup at a time)
for (uint8_t i = 0; i < 8; i++) {
usbh_dpram->setup_packet[i] = setup_packet[i];
}
- // Configure EP0 struct with setup info for the trans complete
- hw_endpoint_t *ep = hw_endpoint_allocate((uint8_t)TUSB_XFER_CONTROL);
- TU_ASSERT(ep);
-
- // EPX should be inactive
- assert(!ep->active);
-
- // EP0 out
- hw_endpoint_init(ep, dev_addr, 0x00, ep->wMaxPacketSize, 0, 0);
- assert(ep->configured);
-
+ ep->ep_addr = 0; // setup is OUT
ep->remaining_len = 8;
- ep->active = true;
-
- // Set device address
- usb_hw->dev_addr_ctrl = dev_addr;
+ ep->xferred_len = 0;
- // Set pre if we are a low speed device on full speed hub
- uint32_t const flags = SIE_CTRL_BASE | USB_SIE_CTRL_SEND_SETUP_BITS | USB_SIE_CTRL_START_TRANS_BITS |
- (need_pre(dev_addr) ? USB_SIE_CTRL_PREAMBLE_EN_BITS : 0);
+ // If EPX is busy, mark as pending setup (DPRAM already has the packet)
+ if (epx->state == EPSTATE_ACTIVE) {
+ ep->state = EPSTATE_PENDING_SETUP;
+ #ifdef HAS_STOP_EPX_ON_NAK
+ usb_hw_set->nak_poll = USB_NAK_POLL_STOP_EPX_ON_NAK_BITS;
+ #else
+ usb_hw->nak_poll = (300 << USB_NAK_POLL_DELAY_FS_LSB) | (300 << USB_NAK_POLL_DELAY_LS_LSB);
+ usb_hw_set->inte = USB_INTE_HOST_SOF_BITS;
+ #endif
+ } else {
+ epx = ep;
+ ep->state = EPSTATE_ACTIVE;
- // START_TRANS bit on SIE_CTRL seems to exhibit the same behavior as the AVAILABLE bit
- // described in RP2040 Datasheet, release 2.1, section "4.1.2.5.1. Concurrent access".
- // We write everything except the START_TRANS bit first, then wait some cycles.
- usb_hw->sie_ctrl = flags & ~USB_SIE_CTRL_START_TRANS_BITS;
- busy_wait_at_least_cycles(12);
- usb_hw->sie_ctrl = flags;
+ usb_hw->dev_addr_ctrl = ep->dev_addr;
+ sie_start_xfer(true, tu_edpt_dir(ep->ep_addr) == TUSB_DIR_IN, ep->need_pre);
+ }
+ rp2usb_critical_exit();
return true;
}
bool hcd_edpt_clear_stall(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr) {
- (void) rhport;
- (void) dev_addr;
- (void) ep_addr;
+ (void)rhport;
+ (void)dev_addr;
+ (void)ep_addr;
panic("hcd_clear_stall");
// return true;
diff --git a/src/portable/raspberrypi/rp2040/rp2040_usb.c b/src/portable/raspberrypi/rp2040/rp2040_usb.c
index 3b65f57a4..83ac48ec2 100644
--- a/src/portable/raspberrypi/rp2040/rp2040_usb.c
+++ b/src/portable/raspberrypi/rp2040/rp2040_usb.c
@@ -27,32 +27,42 @@
#include "tusb_option.h"
-#if CFG_TUSB_MCU == OPT_MCU_RP2040
+#if CFG_TUSB_MCU == OPT_MCU_RP2040 && (CFG_TUD_ENABLED || CFG_TUH_ENABLED)
-#include <stdlib.h>
-#include "rp2040_usb.h"
+ #include <stdlib.h>
+ #include "rp2040_usb.h"
+
+ #include "device/dcd.h"
+ #include "host/hcd.h"
//--------------------------------------------------------------------+
// MACRO CONSTANT TYPEDEF PROTOTYPE
//--------------------------------------------------------------------+
-static void sync_xfer(hw_endpoint_t *ep);
+ #if CFG_TUSB_RP2_ERRATA_E15
+static bool e15_is_critical_frame_period(void);
+ #endif
- #if TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX
-static bool e15_is_critical_frame_period(struct hw_endpoint *ep);
- #else
- #define e15_is_critical_frame_period(x) (false)
+ #if CFG_TUSB_RP2_ERRATA_E2
+static uint8_t rp2040_chipversion = 2;
#endif
+critical_section_t rp2usb_lock;
+
//--------------------------------------------------------------------+
// Implementation
//--------------------------------------------------------------------+
-// Provide own byte by byte memcpy as not all copies are aligned
+// Provide own byte by byte memcpy as not all copies are aligned.
+// Use volatile to prevent compiler from widening to 16/32-bit accesses
+// which cause hard fault on RP2350 when dst/src point to USB DPRAM.
static void unaligned_memcpy(uint8_t *dst, const uint8_t *src, size_t n) {
+ volatile uint8_t *vdst = dst;
+ const volatile uint8_t *vsrc = src;
while (n--) {
- *dst++ = *src++;
+ *vdst++ = *vsrc++;
}
}
+ #if CFG_TUD_EDPT_DEDICATED_HWFIFO
void tu_hwfifo_write(volatile void *hwfifo, const uint8_t *src, uint16_t len, const tu_hwfifo_access_t *access_mode) {
(void)access_mode;
unaligned_memcpy((uint8_t *)(uintptr_t)hwfifo, src, len);
@@ -62,216 +72,222 @@ void tu_hwfifo_read(const volatile void *hwfifo, uint8_t *dest, uint16_t len, co
(void)access_mode;
unaligned_memcpy(dest, (const uint8_t *)(uintptr_t)hwfifo, len);
}
+ #endif
void rp2usb_init(void) {
// Reset usb controller
reset_block(RESETS_RESET_USBCTRL_BITS);
unreset_block_wait(RESETS_RESET_USBCTRL_BITS);
-#ifdef __GNUC__
- // Clear any previous state just in case
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Warray-bounds"
-#if __GNUC__ > 6
-#pragma GCC diagnostic ignored "-Wstringop-overflow"
-#endif
-#endif
+ #ifdef __GNUC__
+ // Clear any previous state just in case
+ #pragma GCC diagnostic push
+ #pragma GCC diagnostic ignored "-Warray-bounds"
+ #if __GNUC__ > 6
+ #pragma GCC diagnostic ignored "-Wstringop-overflow"
+ #endif
+ #endif
memset(usb_dpram, 0, sizeof(*usb_dpram));
-#ifdef __GNUC__
-#pragma GCC diagnostic pop
-#endif
+ #ifdef __GNUC__
+ #pragma GCC diagnostic pop
+ #endif
// Mux the controller to the onboard usb phy
usb_hw->muxing = USB_USB_MUXING_TO_PHY_BITS | USB_USB_MUXING_SOFTCON_BITS;
+ #if CFG_TUSB_RP2_ERRATA_E2
+ rp2040_chipversion = rp2040_chip_version();
+ #endif
+
TU_LOG2_INT(sizeof(hw_endpoint_t));
+
+ critical_section_init(&rp2usb_lock);
}
-void __tusb_irq_path_func(hw_endpoint_reset_transfer)(struct hw_endpoint* ep) {
- ep->active = false;
+void __tusb_irq_path_func(rp2usb_reset_transfer)(hw_endpoint_t *ep) {
+ ep->state = EPSTATE_IDLE;
ep->remaining_len = 0;
- ep->xferred_len = 0;
- ep->user_buf = 0;
+ ep->xferred_len = 0;
+ ep->user_buf = 0;
+#if CFG_TUD_EDPT_DEDICATED_HWFIFO
+ ep->is_xfer_fifo = false;
+#endif
}
-void __tusb_irq_path_func(hwbuf_ctrl_update)(io_rw_32 *buf_ctrl_reg, uint32_t and_mask, uint32_t or_mask) {
- const bool is_host = rp2usb_is_host_mode();
- uint32_t value = 0;
- uint32_t buf_ctrl = *buf_ctrl_reg;
+void __tusb_irq_path_func(bufctrl_write32)(io_rw_32 *buf_reg, uint32_t value) {
+ const uint32_t current = *buf_reg;
+ const uint32_t avail_mask = USB_BUF_CTRL_AVAIL | (USB_BUF_CTRL_AVAIL << 16);
+ if (current & value & avail_mask) {
+ panic("buf_ctrl @ 0x%lX already available", (uintptr_t)buf_reg);
+ }
+ *buf_reg = value & ~(USB_BUF_CTRL_AVAIL | (USB_BUF_CTRL_AVAIL << 16)); // write other bits first
- if (and_mask) {
- value = buf_ctrl & and_mask;
+ // Section 4.1.2.7.1 (rp2040) / 12.7.3.7.1 (rp2350) Concurrent access: after write to buffer control,
+ // wait for USB controller to see the update before setting AVAILABLE.
+ // Don't need delay in host mode as host is in charge of when to start the transaction.
+ if (value & (USB_BUF_CTRL_AVAIL | (USB_BUF_CTRL_AVAIL << 16))) {
+ if (!rp2usb_is_host_mode()) {
+ busy_wait_at_least_cycles(12);
+ }
+ *buf_reg = value; // then set AVAILABLE bit last
}
+}
- if (or_mask) {
- value |= or_mask;
- if (or_mask & USB_BUF_CTRL_AVAIL) {
- if (buf_ctrl & USB_BUF_CTRL_AVAIL) {
- panic("buf_ctrl @ 0x%lX already available", (uintptr_t)buf_ctrl_reg);
- }
- *buf_ctrl_reg = value & ~USB_BUF_CTRL_AVAIL;
+void __tusb_irq_path_func(bufctrl_write16)(io_rw_16 *buf_reg16, uint16_t value) {
+ const uint16_t current = *buf_reg16;
+ if (current & value & USB_BUF_CTRL_AVAIL) {
+ panic("buf_ctrl @ 0x%lX already available", (uintptr_t)buf_reg16);
+ }
+ *buf_reg16 = value & (uint16_t)~USB_BUF_CTRL_AVAIL; // write other bits first
- // Section 4.1.2.7.1 (rp2040) / 12.7.3.7.1 (rp2350) Concurrent access: after write to buffer control, we need to
- // wait at least 1/48 mhz (usb clock), 12 cycles should be good for 48*12Mhz = 576Mhz.
- // Don't need delay in host mode as host is in charge
- if (!is_host) {
- busy_wait_at_least_cycles(12);
- }
+ // Section 4.1.2.7.1 (rp2040) / 12.7.3.7.1 (rp2350) Concurrent access
+ if (value & USB_BUF_CTRL_AVAIL) {
+ if (!rp2usb_is_host_mode()) {
+ busy_wait_at_least_cycles(12);
}
+ *buf_reg16 = value; // then set AVAILABLE bit last
}
-
- *buf_ctrl_reg = value;
}
// prepare buffer, move data if tx, return buffer control
-static uint32_t __tusb_irq_path_func(prepare_ep_buffer)(struct hw_endpoint *ep, uint8_t buf_id, bool is_rx) {
- const uint16_t buflen = tu_min16(ep->remaining_len, ep->wMaxPacketSize);
- ep->remaining_len = (uint16_t) (ep->remaining_len - buflen);
-
- uint32_t buf_ctrl = buflen | USB_BUF_CTRL_AVAIL;
+uint16_t __tusb_irq_path_func(bufctrl_prepare16)(hw_endpoint_t *ep, uint8_t *dpram_buf, bool is_rx) {
+ const uint16_t buflen = tu_min16(ep->remaining_len, ep->max_packet_size);
+ ep->remaining_len -= buflen;
- // PID
- buf_ctrl |= ep->next_pid ? USB_BUF_CTRL_DATA1_PID : USB_BUF_CTRL_DATA0_PID;
+ uint16_t buf_ctrl = buflen | USB_BUF_CTRL_AVAIL;
+ if (ep->next_pid) {
+ buf_ctrl |= USB_BUF_CTRL_DATA1_PID;
+ }
ep->next_pid ^= 1u;
if (!is_rx) {
if (buflen) {
- // Copy data from user buffer/fifo to hw buffer
- uint8_t *hw_buf = ep->hw_data_buf + buf_id * 64;
+ // Copy data from user buffer/fifo to hw buffer
+ #if CFG_TUD_EDPT_DEDICATED_HWFIFO
if (ep->is_xfer_fifo) {
// not in sram, may mess up timing with E15 workaround
- tu_hwfifo_write_from_fifo(hw_buf, ep->user_fifo, buflen, NULL);
- } else {
- unaligned_memcpy(hw_buf, ep->user_buf, buflen);
+ tu_hwfifo_write_from_fifo(dpram_buf, ep->user_fifo, buflen, NULL);
+ } else
+ #endif
+ {
+ unaligned_memcpy(dpram_buf, ep->user_buf, buflen);
ep->user_buf += buflen;
}
}
- // Mark as full
buf_ctrl |= USB_BUF_CTRL_FULL;
}
- // Is this the last buffer? Only really matters for host mode. Will trigger
- // the trans complete irq but also stop it polling. We only really care about
- // trans complete for setup packets being sent
+ // Is this the last buffer? Will trigger the trans complete irq but also stop it polling.
+ // This is used to detect setup packets being sent in host mode
if (ep->remaining_len == 0) {
buf_ctrl |= USB_BUF_CTRL_LAST;
}
- if (buf_id) {
- buf_ctrl = buf_ctrl << 16;
- }
-
return buf_ctrl;
}
-// Prepare buffer control register value
-void __tusb_irq_path_func(hw_endpoint_start_next_buffer)(struct hw_endpoint* ep) {
- const tusb_dir_t dir = tu_edpt_dir(ep->ep_addr);
- bool is_rx;
- bool is_host = false;
- io_rw_32 *ep_ctrl_reg;
- io_rw_32 *buf_ctrl_reg;
+// Start transaction on hw buffer
+void __tusb_irq_path_func(rp2usb_buffer_start)(hw_endpoint_t *ep, io_rw_32 *ep_reg, io_rw_32 *buf_reg, bool is_rx) {
+ // always compute and start with buffer 0
+ uint32_t buf_ctrl = bufctrl_prepare16(ep, ep->dpram_buf, is_rx) | USB_BUF_CTRL_SEL;
+ // Note: device EP0 does not have an endpoint control register
+ if (ep_reg != NULL) {
+ uint32_t ep_ctrl = *ep_reg;
#if CFG_TUH_ENABLED
- is_host = rp2usb_is_host_mode();
- if (is_host) {
- buf_ctrl_reg = hwbuf_ctrl_reg_host(ep);
- ep_ctrl_reg = hwep_ctrl_reg_host(ep);
- is_rx = (dir == TUSB_DIR_IN);
- } else
+ const bool force_single = (rp2usb_is_host_mode() && ep->interrupt_num > 0);
+ #else
+ const bool force_single = false;
#endif
- {
- buf_ctrl_reg = hwbuf_ctrl_reg_device(ep);
- ep_ctrl_reg = hwep_ctrl_reg_device(ep);
- is_rx = (dir == TUSB_DIR_OUT);
- }
-
- // always compute and start with buffer 0
- uint32_t buf_ctrl = prepare_ep_buffer(ep, 0, is_rx) | USB_BUF_CTRL_SEL;
-
- // EP0 has no endpoint control register, also usbd only schedule 1 packet at a time (single buffer)
- if (ep_ctrl_reg != NULL) {
- uint32_t ep_ctrl = *ep_ctrl_reg;
-
- // For now: skip double buffered for RX e.g OUT endpoint in Device mode, since host could send < 64 bytes and cause
- // short packet on buffer0
- // NOTE: this could happen to Host mode IN endpoint Also, Host mode "interrupt" endpoint hardware is only single
- // buffered,
- // NOTE2: Currently Host bulk is implemented using "interrupt" endpoint
- const bool force_single = (!is_host && is_rx) || (is_host && tu_edpt_number(ep->ep_addr) != 0);
if (ep->remaining_len && !force_single) {
// Use buffer 1 (double buffered) if there is still data
- // TODO: Isochronous for buffer1 bit-field is different than CBI (control bulk, interrupt)
-
- buf_ctrl |= prepare_ep_buffer(ep, 1, is_rx);
-
- // Set endpoint control double buffered bit if needed
- ep_ctrl &= ~EP_CTRL_INTERRUPT_PER_BUFFER;
- ep_ctrl |= EP_CTRL_DOUBLE_BUFFERED_BITS | EP_CTRL_INTERRUPT_PER_DOUBLE_BUFFER;
+ buf_ctrl |= (uint32_t)bufctrl_prepare16(ep, ep->dpram_buf + 64, is_rx) << 16;
+ ep_ctrl |= EP_CTRL_DOUBLE_BUFFERED_BITS;
} else {
- // Single buffered since 1 is enough
- ep_ctrl &= ~(EP_CTRL_DOUBLE_BUFFERED_BITS | EP_CTRL_INTERRUPT_PER_DOUBLE_BUFFER);
- ep_ctrl |= EP_CTRL_INTERRUPT_PER_BUFFER;
+ // Only buf0 used: clear DOUBLE_BUFFERED so controller doesn't toggle buffer selector
+ ep_ctrl &= ~(uint32_t)EP_CTRL_DOUBLE_BUFFERED_BITS;
}
-
- *ep_ctrl_reg = ep_ctrl;
+ *ep_reg = ep_ctrl;
}
- TU_LOG(3, " Prepare BufCtrl: [0] = 0x%04x [1] = 0x%04x\r\n", tu_u32_low16(buf_ctrl), tu_u32_high16(buf_ctrl));
-
- // Finally, write to buffer_control which will trigger the transfer
- // the next time the controller polls this dpram address
- hwbuf_ctrl_set(buf_ctrl_reg, buf_ctrl);
+ // Finally, write to buffer control which will trigger the transfer the next time the controller polls this endpoint
+ bufctrl_write32(buf_reg, buf_ctrl);
}
-void hw_endpoint_xfer_start(struct hw_endpoint *ep, uint8_t *buffer, tu_fifo_t *ff, uint16_t total_len) {
+void rp2usb_xfer_start(hw_endpoint_t *ep, io_rw_32 *ep_reg, io_rw_32 *buf_reg, uint8_t *buffer, tu_fifo_t *ff,
+ uint16_t total_len) {
+ (void)ff;
hw_endpoint_lock_update(ep, 1);
- if (ep->active) {
- // TODO: Is this acceptable for interrupt packets?
+ if (ep->state == EPSTATE_ACTIVE) {
TU_LOG(1, "WARN: starting new transfer on already active ep %02X\r\n", ep->ep_addr);
- hw_endpoint_reset_transfer(ep);
+ rp2usb_reset_transfer(ep);
}
// Fill in info now that we're kicking off the hw
ep->remaining_len = total_len;
- ep->xferred_len = 0;
- ep->active = true;
+ ep->xferred_len = 0;
+ ep->state = EPSTATE_ACTIVE;
+ #if CFG_TUD_EDPT_DEDICATED_HWFIFO
if (ff != NULL) {
ep->user_fifo = ff;
ep->is_xfer_fifo = true;
- } else {
- ep->user_buf = buffer;
+ } else
+ #endif
+ {
+ ep->user_buf = buffer;
+ #if CFG_TUD_EDPT_DEDICATED_HWFIFO
ep->is_xfer_fifo = false;
+ #endif
}
- #if TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX
+ const bool is_host = rp2usb_is_host_mode();
+ const bool is_rx = (is_host == (tu_edpt_dir(ep->ep_addr) == TUSB_DIR_IN));
+
+ #if CFG_TUD_ENABLED
+ if (!is_host && ep->future_len > 0) {
+ // Device only: previous short-packet abort saved data from the other buffer
+ const uint8_t future_len = ep->future_len;
+ memcpy(ep->user_buf, ep->dpram_buf + (ep->future_bufid << 6), future_len);
+ ep->xferred_len += future_len;
+ ep->remaining_len -= future_len;
+ ep->user_buf += future_len;
+ ep->future_len = 0;
+ ep->future_bufid = 0;
+
+ if (ep->remaining_len == 0) {
+ const uint16_t xferred_len = ep->xferred_len;
+ rp2usb_reset_transfer(ep);
+ dcd_event_xfer_complete(0, ep->ep_addr, xferred_len, XFER_RESULT_SUCCESS, false);
+ hw_endpoint_lock_update(ep, -1);
+ return;
+ }
+ }
+
+ #if CFG_TUSB_RP2_ERRATA_E15
if (ep->e15_bulk_in) {
usb_hw_set->inte = USB_INTS_DEV_SOF_BITS;
- }
- if (e15_is_critical_frame_period(ep)) {
- ep->pending = 1; // skip transfer if we are in critical frame period
- } else
- #endif
- {
- hw_endpoint_start_next_buffer(ep);
+ // skip transfer if we are in critical frame period
+ if (e15_is_critical_frame_period()) {
+ ep->state = EPSTATE_PENDING;
+ hw_endpoint_lock_update(ep, -1);
+ return;
+ }
}
+ #endif // CFG_TUSB_RP2_ERRATA_E15
+ #endif // CFG_TUD_ENABLED
+ rp2usb_buffer_start(ep, ep_reg, buf_reg, is_rx);
hw_endpoint_lock_update(ep, -1);
}
// sync endpoint buffer and return transferred bytes
-static uint16_t __tusb_irq_path_func(sync_ep_buffer)(hw_endpoint_t *ep, io_rw_32 *buf_ctrl_reg, uint8_t buf_id,
- bool is_rx) {
- uint32_t buf_ctrl = *buf_ctrl_reg;
- if (buf_id) {
- buf_ctrl = buf_ctrl >> 16;
- }
-
+static uint16_t __tusb_irq_path_func(bufctrl_sync16)(hw_endpoint_t *ep, bool is_rx, uint16_t buf_ctrl,
+ uint8_t *dpram_buf) {
const uint16_t xferred_bytes = buf_ctrl & USB_BUF_CTRL_LEN_MASK;
if (!is_rx) {
@@ -282,20 +298,21 @@ static uint16_t __tusb_irq_path_func(sync_ep_buffer)(hw_endpoint_t *ep, io_rw_32
// If we have received some data, so can increase the length
// we have received AFTER we have copied it to the user buffer at the appropriate offset
assert(buf_ctrl & USB_BUF_CTRL_FULL);
-
- uint8_t *hw_buf = ep->hw_data_buf + buf_id * 64;
+ #if CFG_TUD_EDPT_DEDICATED_HWFIFO
if (ep->is_xfer_fifo) {
// not in sram, may mess up timing with E15 workaround
- tu_hwfifo_read_to_fifo(hw_buf, ep->user_fifo, xferred_bytes, NULL);
- } else {
- unaligned_memcpy(ep->user_buf, hw_buf, xferred_bytes);
+ tu_hwfifo_read_to_fifo(dpram_buf, ep->user_fifo, xferred_bytes, NULL);
+ } else
+ #endif
+ {
+ unaligned_memcpy(ep->user_buf, dpram_buf, xferred_bytes);
ep->user_buf += xferred_bytes;
}
}
ep->xferred_len += xferred_bytes;
// Short packet
- if (xferred_bytes < ep->wMaxPacketSize) {
+ if (xferred_bytes < ep->max_packet_size) {
// Reduce total length as this is last packet
ep->remaining_len = 0;
}
@@ -303,109 +320,126 @@ static uint16_t __tusb_irq_path_func(sync_ep_buffer)(hw_endpoint_t *ep, io_rw_32
return xferred_bytes;
}
-// Update hw endpoint struct with info from hardware after a buff status interrupt
-static void __tusb_irq_path_func(sync_xfer)(hw_endpoint_t *ep) {
- // const uint8_t ep_num = tu_edpt_number(ep->ep_addr);
- const tusb_dir_t dir = tu_edpt_dir(ep->ep_addr);
-
- io_rw_32 *buf_ctrl_reg;
- io_rw_32 *ep_ctrl_reg;
- bool is_rx;
+// Returns true if transfer is complete.
+// buf_id: which buffer completed (from BUFF_CPU_SHOULD_HANDLE, only used for double-buffered).
+bool __tusb_irq_path_func(rp2usb_xfer_continue)(hw_endpoint_t *ep, io_rw_32 *ep_reg, io_rw_32 *buf_reg, uint8_t buf_id,
+ bool is_rx) {
+ hw_endpoint_lock_update(ep, 1);
- #if CFG_TUH_ENABLED
- const bool is_host = rp2usb_is_host_mode();
- if (is_host) {
- buf_ctrl_reg = hwbuf_ctrl_reg_host(ep);
- ep_ctrl_reg = hwep_ctrl_reg_host(ep);
- is_rx = (dir == TUSB_DIR_IN);
- } else
- #endif
- {
- buf_ctrl_reg = hwbuf_ctrl_reg_device(ep);
- ep_ctrl_reg = hwep_ctrl_reg_device(ep);
- is_rx = (dir == TUSB_DIR_OUT);
+ if (ep->state == EPSTATE_IDLE) {
+ // probably land here due to short packet on rx with double buffered
+ hw_endpoint_lock_update(ep, -1);
+ return false;
}
- TU_LOG(3, " Sync BufCtrl: [0] = 0x%04x [1] = 0x%04x\r\n", tu_u32_low16(*buf_ctrl_reg),
- tu_u32_high16(*buf_ctrl_reg));
- uint16_t buf0_bytes = sync_ep_buffer(ep, buf_ctrl_reg, 0, is_rx); // always sync buffer 0
+ const bool is_host = rp2usb_is_host_mode();
+ const bool is_double = (ep_reg != NULL && ((*ep_reg) & EP_CTRL_DOUBLE_BUFFERED_BITS));
- // sync buffer 1 if double buffered
- if (ep_ctrl_reg != NULL && (*ep_ctrl_reg) & EP_CTRL_DOUBLE_BUFFERED_BITS) {
- if (buf0_bytes == ep->wMaxPacketSize) {
- // sync buffer 1 if not short packet
- sync_ep_buffer(ep, buf_ctrl_reg, 1, is_rx);
- } else {
- // short packet on buffer 0
- // TODO couldn't figure out how to handle this case which happen with net_lwip_webserver example
- // At this time (currently trigger per 2 buffer), the buffer1 is probably filled with data from
- // the next transfer (not current one). For now we disable double buffered for device OUT
- // NOTE this could happen to Host IN
-#if 0
- uint8_t const ep_num = tu_edpt_number(ep->ep_addr);
- uint8_t const dir = (uint8_t) tu_edpt_dir(ep->ep_addr);
- uint8_t const ep_id = 2*ep_num + (dir ? 0 : 1);
-
- // abort queued transfer on buffer 1
- usb_hw->abort |= TU_BIT(ep_id);
-
- while ( !(usb_hw->abort_done & TU_BIT(ep_id)) ) {}
+ // Double-buffered: buf_id from BUFF_CPU_SHOULD_HANDLE indicates which buffer completed.
+ // RP2040-E4 (host only): in single-buffered multi-packet transfers, the controller may write completion status to
+ // BUF1 half instead of BUF0. The side effect is that controller can execute an extra packet after writing to BUF1
+ // since it leaves BUF0 intact, which can be polled before buf_status interrupt is triggered.
+ uint8_t *dpram_buf = ep->dpram_buf;
+ if (buf_id) {
+ #if CFG_TUSB_RP2_ERRATA_E4
+ if (!(is_host && !is_double)) // E4 bug: incorrect buf_id, buffer data is still buf0
+ #endif
+ {
+ dpram_buf += 64; // buf1 offset
+ }
+ }
- uint32_t ep_ctrl = *ep->endpoint_control;
- ep_ctrl &= ~(EP_CTRL_DOUBLE_BUFFERED_BITS | EP_CTRL_INTERRUPT_PER_DOUBLE_BUFFER);
- ep_ctrl |= EP_CTRL_INTERRUPT_PER_BUFFER;
+ io_rw_16 *buf_reg16 = (io_rw_16 *)buf_reg;
+ uint16_t buf_ctrl16 = *(buf_reg16 + buf_id);
- io_rw_32 *buf_ctrl_reg = is_host ? hwbuf_ctrl_reg_host(ep) : hwbuf_ctrl_reg_device(ep);
- hwbuf_ctrl_set(buf_ctrl_reg, 0);
+ const uint16_t xact_bytes = bufctrl_sync16(ep, is_rx, buf_ctrl16, dpram_buf);
+ const bool is_last = buf_ctrl16 & USB_BUF_CTRL_LAST;
+ const bool is_short = xact_bytes < ep->max_packet_size;
+ const bool is_done = is_short || is_last;
- usb_hw->abort &= ~TU_BIT(ep_id);
+ // Short packet on rx with double buffer: abort the other half (if not last) and reset the buffer control.
+ // The other buffer may be: (a) still AVAIL, (b) in-progress (controller receiving), or (c) already completed.
+ // We must abort to safely reclaim it. If it has valid data (FULL), save as future for the next transfer.
+ // Note: Host mode current does not save next transfer data due to shared epx --> potential issue. However, RP2040-E4
+ // causes more or less of the same issue since it write to buf1 and next time it continues to transfer on buf0 (stale)
+ if (is_short && is_double && is_rx && !is_last) {
+ const uint32_t abort_bit = TU_BIT(tu_edpt_number(ep->ep_addr) << 1); // abort is device only -> IN endpoint
- TU_LOG(3, "----SHORT PACKET buffer0 on EP %02X:\r\n", ep->ep_addr);
- TU_LOG(3, " BufCtrl: [0] = 0x%04x [1] = 0x%04x\r\n", tu_u32_low16(buf_ctrl), tu_u32_high16(buf_ctrl));
-#endif
+ if (is_host) {
+ // host stop current transfer, not safe, can be racing
+ const uint32_t sie_ctrl = (usb_hw->sie_ctrl & SIE_CTRL_BASE_MASK) | USB_SIE_CTRL_STOP_TRANS_BITS;
+ usb_hw->sie_ctrl = sie_ctrl;
+ while (usb_hw->sie_ctrl & USB_SIE_CTRL_STOP_TRANS_BITS) {}
+ } else {
+ // device abort current transfer
+ #if CFG_TUSB_RP2_ERRATA_E2
+ if (rp2040_chipversion >= 2)
+ #endif
+ {
+ usb_hw_set->abort = abort_bit;
+ while ((usb_hw->abort_done & abort_bit) != abort_bit) {}
+ }
}
- }
-}
-// Returns true if transfer is complete
-bool __tusb_irq_path_func(hw_endpoint_xfer_continue)(struct hw_endpoint* ep) {
- hw_endpoint_lock_update(ep, 1);
+ // After abort, check if the other buffer received valid data
+ io_rw_16 *buf_reg16_other = buf_reg16 + (buf_id ^ 1);
+ const uint16_t buf_ctrl16_other = *buf_reg16_other;
+ if (buf_ctrl16_other & USB_BUF_CTRL_FULL) {
+ // Data already sent into this buffer. Save it for the next transfer.
+ // buff_status will be clear by the next run
+ #if CFG_TUD_ENABLED
+ if (!is_host) {
+ ep->future_len = (uint8_t)(buf_ctrl16_other & USB_BUF_CTRL_LEN_MASK);
+ ep->future_bufid = buf_id ^ 1;
+ }
+ #endif
+ } else {
+ ep->next_pid ^= 1u; // roll back pid if aborted
+ }
- // Part way through a transfer
- if (!ep->active) {
- panic("Can't continue xfer on inactive ep %02X", ep->ep_addr);
- }
+ *buf_reg = 0; // reset buffer control
- sync_xfer(ep); // Update EP struct from hardware state
+ if (!is_host) {
+ #if CFG_TUSB_RP2_ERRATA_E2
+ if (rp2040_chipversion >= 2)
+ #endif
+ {
+ usb_hw_clear->abort_done = abort_bit;
+ usb_hw_clear->abort = abort_bit;
+ }
+ }
- // Now we have synced our state with the hardware. Is there more data to transfer?
- // If we are done then notify tinyusb
- if (ep->remaining_len == 0) {
- pico_trace("Completed transfer of %d bytes on ep %02X\r\n", ep->xferred_len, ep->ep_addr);
- // Notify caller we are done so it can notify the tinyusb stack
hw_endpoint_lock_update(ep, -1);
return true;
- } else {
- #if TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX
- if (e15_is_critical_frame_period(ep)) {
- ep->pending = 1;
+ }
+
+ if (!is_done && ep->remaining_len > 0) {
+ #if CFG_TUSB_RP2_ERRATA_E15
+ const bool need_e15 = ep->e15_bulk_in;
+ if (need_e15 && e15_is_critical_frame_period()) {
+ // mark as pending if matches E15 condition
+ ep->state = EPSTATE_PENDING;
+ } else if (need_e15 && ep->state == EPSTATE_PENDING) {
+ // if already pending, meaning the other buf completes first, don't arm buffer, let SOF handle it
+ // do nothing
} else
#endif
{
- hw_endpoint_start_next_buffer(ep);
+ // ping-pong: arm the completed buffer with new data
+ const uint16_t buf_ctrl16_new = bufctrl_prepare16(ep, dpram_buf, is_rx);
+ bufctrl_write16(buf_reg16 + buf_id, buf_ctrl16_new);
}
}
hw_endpoint_lock_update(ep, -1);
- // More work to do
- return false;
+ return is_done;
}
//--------------------------------------------------------------------+
// Errata 15
//--------------------------------------------------------------------+
-#if TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX
+ #if CFG_TUSB_RP2_ERRATA_E15
// E15 is fixed with RP2350
/* Don't mark IN buffers as available during the last 200us of a full-speed
@@ -426,25 +460,19 @@ bool __tusb_irq_path_func(hw_endpoint_xfer_continue)(struct hw_endpoint* ep) {
volatile uint32_t e15_last_sof = 0;
-// check if we need to apply Errata 15 workaround : i.e
-// Endpoint is BULK IN and is currently in critical frame period i.e 20% of last usb frame
-static bool __tusb_irq_path_func(e15_is_critical_frame_period)(struct hw_endpoint* ep) {
- if (!ep->e15_bulk_in) {
- return false;
- }
-
+// check if it is currently in critical frame period i.e 20% of last usb frame
+static bool __tusb_irq_path_func(e15_is_critical_frame_period)(void) {
/* Avoid the last 200us (uframe 6.5-7) of a frame, up to the EOF2 point.
* The device state machine cannot recover from receiving an incorrect PID
- * when it is expecting an ACK.
- */
+ * when it is expecting an ACK. */
uint32_t delta = time_us_32() - e15_last_sof;
if (delta < 800 || delta > 998) {
return false;
}
- TU_LOG(3, "Avoiding sof %lu now %lu last %lu\r\n", (usb_hw->sof_rd + 1) & USB_SOF_RD_BITS, time_us_32(),
- e15_last_sof);
+ // TU_LOG(3, "Avoiding sof %lu now %lu last %lu\r\n", (usb_hw->sof_rd + 1) & USB_SOF_RD_BITS, time_us_32(),
+ // e15_last_sof);
return true;
}
-#endif // TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX
+ #endif
#endif
diff --git a/src/portable/raspberrypi/rp2040/rp2040_usb.h b/src/portable/raspberrypi/rp2040/rp2040_usb.h
index c03dc34b2..8ebc3e3fc 100644
--- a/src/portable/raspberrypi/rp2040/rp2040_usb.h
+++ b/src/portable/raspberrypi/rp2040/rp2040_usb.h
@@ -7,6 +7,8 @@
#include "hardware/resets.h"
#include "hardware/timer.h"
+#include "pico/critical_section.h"
+
#include "common/tusb_common.h"
#include "osal/osal.h"
#include "common/tusb_fifo.h"
@@ -15,42 +17,66 @@
#error TinyUSB device and host mode not supported at the same time
#endif
-// E5 and E15 only apply to RP2040
#if defined(PICO_RP2040) && PICO_RP2040 == 1
- // RP2040 E5: USB device fails to exit RESET state on busy USB bus.
+ // RP2040-E2 USB device endpoint abort is not cleared.
+ #define CFG_TUSB_RP2_ERRATA_E2 1
+
+ // RP2040-E4: USB host writes to upper half of buffer status in single buffered mode.
+ #define CFG_TUSB_RP2_ERRATA_E4 1
+
+ // RP2040-E5: USB device fails to exit RESET state on busy USB bus.
#if defined(PICO_RP2040_USB_DEVICE_ENUMERATION_FIX) && !defined(TUD_OPT_RP2040_USB_DEVICE_ENUMERATION_FIX)
#define TUD_OPT_RP2040_USB_DEVICE_ENUMERATION_FIX PICO_RP2040_USB_DEVICE_ENUMERATION_FIX
#endif
- // RP2040 E15: USB Device controller will hang if certain bus errors occur during an IN transfer.
- #if defined(PICO_RP2040_USB_DEVICE_UFRAME_FIX) && !defined(TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX)
- #define TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX PICO_RP2040_USB_DEVICE_UFRAME_FIX
+ // RP2040-E15: USB Device controller will hang if certain bus errors occur during an IN transfer.
+ #ifndef CFG_TUSB_RP2_ERRATA_E15
+ #if defined(PICO_RP2040_USB_DEVICE_UFRAME_FIX)
+ #define CFG_TUSB_RP2_ERRATA_E15 (CFG_TUD_ENABLED && PICO_RP2040_USB_DEVICE_UFRAME_FIX)
+ #elif defined(TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX)
+ #define CFG_TUSB_RP2_ERRATA_E15 (CFG_TUD_ENABLED && TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX)
+ #endif
#endif
#endif
+#ifndef CFG_TUSB_RP2_ERRATA_E2
+ #define CFG_TUSB_RP2_ERRATA_E2 0
+#endif
+
+#ifndef CFG_TUSB_RP2_ERRATA_E4
+ #define CFG_TUSB_RP2_ERRATA_E4 0
+#endif
+
#ifndef TUD_OPT_RP2040_USB_DEVICE_ENUMERATION_FIX
#define TUD_OPT_RP2040_USB_DEVICE_ENUMERATION_FIX 0
#endif
-#ifndef TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX
- #define TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX 0
+#ifndef CFG_TUSB_RP2_ERRATA_E15
+ #define CFG_TUSB_RP2_ERRATA_E15 0
#endif
-#if TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX
+#if CFG_TUSB_RP2_ERRATA_E15
#undef PICO_RP2040_USB_FAST_IRQ
#define PICO_RP2040_USB_FAST_IRQ 1
#endif
#ifndef PICO_RP2040_USB_FAST_IRQ
-#define PICO_RP2040_USB_FAST_IRQ 0
+ #define PICO_RP2040_USB_FAST_IRQ 0
#endif
#if PICO_RP2040_USB_FAST_IRQ
-#define __tusb_irq_path_func(x) __no_inline_not_in_flash_func(x)
+ #define __tusb_irq_path_func(x) __no_inline_not_in_flash_func(x)
#else
-#define __tusb_irq_path_func(x) x
+ #define __tusb_irq_path_func(x) x
#endif
+// Flags we set by default in sie_ctrl (we add other bits on top)
+enum {
+ SIE_CTRL_BASE = USB_SIE_CTRL_PULLDOWN_EN_BITS | USB_SIE_CTRL_EP0_INT_1BUF_BITS,
+ SIE_CTRL_BASE_MASK = USB_SIE_CTRL_PULLDOWN_EN_BITS | USB_SIE_CTRL_EP0_INT_1BUF_BITS | USB_SIE_CTRL_SOF_EN_BITS |
+ USB_SIE_CTRL_KEEP_ALIVE_EN_BITS
+};
+
//--------------------------------------------------------------------+
//
//--------------------------------------------------------------------+
@@ -60,26 +86,43 @@
#define pico_info(...) TU_LOG(2, __VA_ARGS__)
#define pico_trace(...) TU_LOG(3, __VA_ARGS__)
+enum {
+ EPSTATE_IDLE = 0,
+ EPSTATE_ACTIVE,
+ EPSTATE_PENDING,
+ EPSTATE_PENDING_SETUP
+};
+
// Hardware information per endpoint
typedef struct hw_endpoint {
uint8_t ep_addr;
uint8_t next_pid;
- bool active; // transferring data
- bool is_xfer_fifo; // transfer using fifo
+ uint8_t state;
-#if TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX
- bool e15_bulk_in; // Errata15 device bulk in
- uint8_t pending; // Transfer scheduled but not active
+#if CFG_TUD_EDPT_DEDICATED_HWFIFO
+ bool is_xfer_fifo; // transfer using fifo
+#endif
+
+#if CFG_TUD_ENABLED
+ uint8_t future_bufid; // which buffer holds next data
+ uint8_t future_len; // next data len
+#endif
+
+#if CFG_TUSB_RP2_ERRATA_E15
+ bool e15_bulk_in; // Errata15 device bulk in
#endif
#if CFG_TUH_ENABLED
- bool configured; // Is this a valid struct
uint8_t dev_addr;
- uint8_t interrupt_num; // for host interrupt endpoints
+ uint8_t interrupt_num; // 1-15 for interrupt endpoints
+ struct TU_ATTR_PACKED {
+ uint8_t transfer_type : 2;
+ uint8_t need_pre : 1; // preamble for low-speed device behind full speed hub
+ };
#endif
- uint16_t wMaxPacketSize;
- uint8_t *hw_data_buf; // Buffer pointer in usb dpram
+ uint16_t max_packet_size; // max packet size also indicates configured
+ uint8_t *dpram_buf; // Buffer pointer in usb dpram
// transfer info
union {
@@ -91,7 +134,7 @@ typedef struct hw_endpoint {
} hw_endpoint_t;
-#if TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX
+#if CFG_TUSB_RP2_ERRATA_E15
extern volatile uint32_t e15_last_sof;
#endif
@@ -102,69 +145,39 @@ TU_ATTR_ALWAYS_INLINE static inline bool rp2usb_is_host_mode(void) {
return (usb_hw->main_ctrl & USB_MAIN_CTRL_HOST_NDEVICE_BITS) ? true : false;
}
-void hw_endpoint_xfer_start(struct hw_endpoint *ep, uint8_t *buffer, tu_fifo_t *ff, uint16_t total_len);
-bool hw_endpoint_xfer_continue(struct hw_endpoint *ep);
-void hw_endpoint_reset_transfer(struct hw_endpoint *ep);
-void hw_endpoint_start_next_buffer(struct hw_endpoint *ep);
-
-TU_ATTR_ALWAYS_INLINE static inline void hw_endpoint_lock_update(__unused struct hw_endpoint * ep, __unused int delta) {
- // todo add critsec as necessary to prevent issues between worker and IRQ...
- // note that this is perhaps as simple as disabling IRQs because it would make
- // sense to have worker and IRQ on same core, however I think using critsec is about equivalent.
-}
-
-// #if CFG_TUD_ENABLED
-TU_ATTR_ALWAYS_INLINE static inline io_rw_32 *hwep_ctrl_reg_device(struct hw_endpoint *ep) {
- uint8_t const epnum = tu_edpt_number(ep->ep_addr);
- const uint8_t dir = (uint8_t)tu_edpt_dir(ep->ep_addr);
- if (epnum == 0) {
- // EP0 has no endpoint control register because the buffer offsets are fixed and always enabled
- return NULL;
- }
- return (dir == TUSB_DIR_IN) ? &usb_dpram->ep_ctrl[epnum - 1].in : &usb_dpram->ep_ctrl[epnum - 1].out;
-}
+extern critical_section_t rp2usb_lock;
-TU_ATTR_ALWAYS_INLINE static inline io_rw_32 *hwbuf_ctrl_reg_device(struct hw_endpoint *ep) {
- const uint8_t epnum = tu_edpt_number(ep->ep_addr);
- const uint8_t dir = (uint8_t)tu_edpt_dir(ep->ep_addr);
- return (dir == TUSB_DIR_IN) ? &usb_dpram->ep_buf_ctrl[epnum].in : &usb_dpram->ep_buf_ctrl[epnum].out;
+TU_ATTR_ALWAYS_INLINE static inline void rp2usb_critical_enter(void) {
+ critical_section_enter_blocking(&rp2usb_lock);
}
-// #endif
-
-#if CFG_TUH_ENABLED
-TU_ATTR_ALWAYS_INLINE static inline io_rw_32 *hwep_ctrl_reg_host(struct hw_endpoint *ep) {
- if (tu_edpt_number(ep->ep_addr) == 0) {
- return &usbh_dpram->epx_ctrl;
- }
- return &usbh_dpram->int_ep_ctrl[ep->interrupt_num].ctrl;
-}
-
-TU_ATTR_ALWAYS_INLINE static inline io_rw_32 *hwbuf_ctrl_reg_host(struct hw_endpoint *ep) {
- if (tu_edpt_number(ep->ep_addr) == 0) {
- return &usbh_dpram->epx_buf_ctrl;
- }
- return &usbh_dpram->int_ep_buffer_ctrl[ep->interrupt_num].ctrl;
+TU_ATTR_ALWAYS_INLINE static inline void rp2usb_critical_exit(void) {
+ critical_section_exit(&rp2usb_lock);
}
-#endif
//--------------------------------------------------------------------+
-//
+// Hardware Endpoint
//--------------------------------------------------------------------+
-void hwbuf_ctrl_update(io_rw_32 *buf_ctrl_reg, uint32_t and_mask, uint32_t or_mask);
+void rp2usb_xfer_start(hw_endpoint_t *ep, io_rw_32 *ep_reg, io_rw_32 *buf_reg, uint8_t *buffer, tu_fifo_t *ff,
+ uint16_t total_len);
+bool rp2usb_xfer_continue(hw_endpoint_t *ep, io_rw_32 *ep_reg, io_rw_32 *buf_reg, uint8_t buf_id, bool is_rx);
+void rp2usb_buffer_start(hw_endpoint_t *ep, io_rw_32 *ep_reg, io_rw_32 *buf_reg, bool is_rx);
+void rp2usb_reset_transfer(hw_endpoint_t *ep);
-TU_ATTR_ALWAYS_INLINE static inline void hwbuf_ctrl_set(io_rw_32 *buf_ctrl_reg, uint32_t value) {
- hwbuf_ctrl_update(buf_ctrl_reg, 0, value);
-}
-TU_ATTR_ALWAYS_INLINE static inline void hwbuf_ctrl_set_mask(io_rw_32 *buf_ctrl_reg, uint32_t value) {
- hwbuf_ctrl_update(buf_ctrl_reg, ~value, value);
+TU_ATTR_ALWAYS_INLINE static inline void hw_endpoint_lock_update(__unused struct hw_endpoint *ep, __unused int delta) {
+ // todo add critsec as necessary to prevent issues between worker and IRQ...
+ // note that this is perhaps as simple as disabling IRQs because it would make
+ // sense to have worker and IRQ on same core, however I think using critsec is about equivalent.
}
-TU_ATTR_ALWAYS_INLINE static inline void hwbuf_ctrl_clear_mask(io_rw_32 *buf_ctrl_reg, uint32_t value) {
- hwbuf_ctrl_update(buf_ctrl_reg, ~value, 0);
-}
+//--------------------------------------------------------------------+
+// Hardware Buffer
+//--------------------------------------------------------------------+
+void bufctrl_write32(io_rw_32 *buf_reg, uint32_t value);
+void bufctrl_write16(io_rw_16 *buf_reg16, uint16_t value);
+uint16_t bufctrl_prepare16(hw_endpoint_t *ep, uint8_t *dpram_buf, bool is_rx);
-static inline uintptr_t hw_data_offset(uint8_t *buf) {
+TU_ATTR_ALWAYS_INLINE static inline uintptr_t hw_data_offset(uint8_t *buf) {
// Remove usb base from buffer pointer
return (uintptr_t)buf ^ (uintptr_t)usb_dpram;
}
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 13d1a6cb4..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);
@@ -612,7 +595,7 @@ void dcd_edpt_close_all(uint8_t rhport) {
dcd_int_enable(rhport);
// Reset PMA allocation
- ep_buf_ptr = FSDEV_BTABLE_BASE + 8 * CFG_TUD_ENDPPOINT_MAX + 2 * CFG_TUD_ENDPOINT0_SIZE;
+ ep_buf_ptr = FSDEV_BTABLE_BASE + 8 * FSDEV_EP_COUNT + 2 * CFG_TUD_ENDPOINT0_SIZE;
}
bool dcd_edpt_iso_alloc(uint8_t rhport, uint8_t ep_addr, uint16_t largest_packet_size) {
@@ -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..b15c95302 100644
--- a/src/portable/st/stm32_fsdev/fsdev_stm32.h
+++ b/src/portable/st/stm32_fsdev/fsdev_stm32.h
@@ -36,6 +36,10 @@
#include "stm32c0xx.h"
#define FSDEV_HAS_SBUF_ISO 1
+#elif CFG_TUSB_MCU == OPT_MCU_STM32C5
+ #include "stm32c5xx.h"
+ #define FSDEV_HAS_SBUF_ISO 1
+
#elif CFG_TUSB_MCU == OPT_MCU_STM32F0
#include "stm32f0xx.h"
#define FSDEV_HAS_SBUF_ISO 0
@@ -177,7 +181,7 @@ static const IRQn_Type fsdev_irq[] = {
USB_IRQn,
#elif TU_CHECK_MCU(OPT_MCU_STM32L5, OPT_MCU_STM32U3)
USB_FS_IRQn,
- #elif TU_CHECK_MCU(OPT_MCU_STM32C0, OPT_MCU_STM32H5, OPT_MCU_STM32U0)
+ #elif TU_CHECK_MCU(OPT_MCU_STM32C0, OPT_MCU_STM32C5, OPT_MCU_STM32H5, OPT_MCU_STM32U0)
USB_DRD_FS_IRQn,
#elif CFG_TUSB_MCU == OPT_MCU_STM32G0
#ifdef STM32G0B0xx
@@ -252,6 +256,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 dd86b052a..f9201651a 100644
--- a/src/portable/st/stm32_fsdev/hcd_stm32_fsdev.c
+++ b/src/portable/st/stm32_fsdev/hcd_stm32_fsdev.c
@@ -38,8 +38,7 @@
#include "tusb_option.h"
-#if CFG_TUH_ENABLED && defined(TUP_USBIP_FSDEV) && \
- TU_CHECK_MCU(OPT_MCU_STM32C0, OPT_MCU_STM32G0, OPT_MCU_STM32H5, OPT_MCU_STM32U5)
+#if CFG_TUH_ENABLED && defined(TUP_USBIP_FSDEV) && defined(TUP_USBIP_FSDEV_DRD)
#include "host/hcd.h"
#include "host/usbh.h"
@@ -59,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,
@@ -166,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);
}
@@ -238,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);
}
@@ -672,10 +627,8 @@ bool hcd_setup_send(uint8_t rhport, uint8_t dev_addr, uint8_t const setup_packet
// Clear stall, data toggle is also reset to DATA0
bool hcd_edpt_clear_stall(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr) {
(void) rhport;
- (void) dev_addr;
- (void) ep_addr;
- uint8_t const ep_id = endpoint_find(dev_addr, 0);
+ uint8_t const ep_id = endpoint_find(dev_addr, ep_addr);
TU_ASSERT(ep_id != TUSB_INDEX_INVALID_8);
hcd_endpoint_t *edpt = &_hcd_data.edpt[ep_id];
diff --git a/src/portable/synopsys/dwc2/dcd_dwc2.c b/src/portable/synopsys/dwc2/dcd_dwc2.c
index 8685ec6dc..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;
}
@@ -148,22 +155,23 @@ static void dma_setup_prepare(uint8_t rhport) {
/* Device Data FIFO scheme
+ The controller has a single SPRAM of otg_dfifo_depth 32-bit words shared between all FIFOs and optional DMA metadata.
+ otg_dfifo_depth = ghwcfg3.dfifo_depth + EP_LOC_CNT. It is split up into:
- The FIFO is split up into
- - EPInfo: for storing DMA metadata, only required when use DMA. Maximum size is called
- EP_LOC_CNT = ep_fifo_size - ghwcfg3.dfifo_depth. For value less than EP_LOC_CNT, gdfifocfg must be configured before
- gahbcfg.dmaen is set
- - Buffer mode: 1 word per endpoint direction
- - Scatter/Gather DMA: 4 words per endpoint direction
+ - EPInfo: for storing DMA address registers (DxEPDMAn), only required when DMA is used.
+ gdfifocfg.EPINFOBASE and gdfifocfg.GDFIFOCfg must be configured before gahbcfg.dmaen is set.
+ The number of words needed per endpoint direction depends on the DMA mode used at runtime:
+ - Buffer DMA mode: 1 word per endpoint direction
+ - Scatter/Gather DMA mode: 4 words per endpoint direction
- TX FIFO: one fifo for each IN endpoint. Size is dynamic depending on packet size, starting from top with EP0 IN.
- Shared RX FIFO: a shared fifo for all OUT endpoints. Typically, can hold up to 2 packets of the largest EP size.
- We allocated TX FIFO from top to bottom (using top pointer), this to allow the RX FIFO to grow dynamically which is
+ We allocate TX FIFOs from top to bottom (using a top pointer), this to allow the RX FIFO to grow dynamically, which is
possible since the free space is located between the RX and TX FIFOs.
- ---------------- ep_fifo_size
- | DxEPIDMAn |
- |-------------|-- gdfifocfg.EPINFOBASE (max is ghwcfg3.dfifo_depth)
+ --------------- otg_dfifo_depth
+ | EPInfo | DxEPDMAn (DMA only, sized per runtime DMA mode)
+ |-------------|-- gdfifocfg.EPINFOBASE (start of EPInfo; FIFO space sized by GDFIFOCFG)
| IN FIFO 0 | control EP
|-------------|
| IN FIFO 1 |
@@ -184,13 +192,13 @@ static void dma_setup_prepare(uint8_t rhport) {
- 13 for setup packets + control words (up to 3 setup packets).
- 1 for global NAK (not required/used here).
- Largest-EPsize/4 + 1. (FS: 64 bytes, HS: 512 bytes). Recommended is "2 x (Largest-EPsize/4 + 1)"
- - 2 for each used OUT endpoint
+ - 2 for each used OUT endpoint.
- Therefore GRXFSIZ = 13 + 1 + 2 x (Largest-EPsize/4 + 1) + 2 x EPOUTnum
+ Therefore, GRXFSIZ = 13 + 1 + 2 x (Largest-EPsize/4 + 1) + 2 x EPOUTnum
*/
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) {
@@ -202,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);
@@ -249,7 +257,7 @@ static void dfifo_device_init(uint8_t rhport) {
// Scatter/Gather DMA mode is not yet supported. Buffer DMA only need 1 words per endpoint direction
const bool is_dma = dma_device_enabled(dwc2);
- _dcd_data.dfifo_top = dwc2_controller->ep_fifo_size/4;
+ _dcd_data.dfifo_top = dwc2_controller->otg_dfifo_depth;
if (is_dma) {
_dcd_data.dfifo_top -= 2 * dwc2_controller->ep_count;
}
@@ -345,6 +353,11 @@ static void edpt_disable(uint8_t rhport, uint8_t ep_addr, bool stall) {
dwc2->dctl |= DCTL_CGONAK;
}
}
+
+ // Clear ActEP
+ if (!stall && epnum != 0) {
+ dep->ctl &= ~EPCTL_USBAEP;
+ }
}
// Since this function returns void, it is not possible to return a boolean success message
@@ -365,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
}
@@ -436,12 +449,12 @@ bool dcd_configure(uint8_t rhport, uint32_t cfg_id, const void* cfg_param) {
}
bool dcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) {
- (void) rh_init;
- dwc2_regs_t* dwc2 = DWC2_REG(rhport);
+ dwc2_clock_init(rhport, rh_init->role);
tu_memclr(&_dcd_data, sizeof(_dcd_data));
// Core Initialization
+ dwc2_regs_t* dwc2 = DWC2_REG(rhport);
const bool is_hs_phy = dwc2_core_is_highspeed_phy(dwc2, TUD_OPT_HIGH_SPEED);
const bool is_dma = dma_device_enabled(dwc2);
TU_ASSERT(dwc2_core_init(rhport, is_hs_phy, is_dma));
@@ -535,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;
@@ -554,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;
@@ -785,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) {
@@ -875,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
@@ -927,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
}
}
@@ -1000,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;
}
@@ -1027,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);
}
@@ -1048,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);
}
}
@@ -1091,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
}
@@ -1128,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);
}
}
@@ -1198,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;
@@ -1211,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) {
@@ -1225,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_at32.h b/src/portable/synopsys/dwc2/dwc2_at32.h
index fa6d10c12..85fa9b20b 100644
--- a/src/portable/synopsys/dwc2/dwc2_at32.h
+++ b/src/portable/synopsys/dwc2/dwc2_at32.h
@@ -32,38 +32,38 @@
#if CFG_TUSB_MCU == OPT_MCU_AT32F415
#include <at32f415.h>
- #define OTG1_FIFO_SIZE 1280
+ #define OTG1_DFIFO_DEPTH 320
#define OTG1_IRQn OTGFS1_IRQn
#define DWC2_OTG1_REG_BASE 0x50000000UL
#elif CFG_TUSB_MCU == OPT_MCU_AT32F435_437
#include <at32f435_437.h>
- #define OTG1_FIFO_SIZE 1280
- #define OTG2_FIFO_SIZE 1280
+ #define OTG1_DFIFO_DEPTH 320
+ #define OTG2_DFIFO_DEPTH 320
#define OTG1_IRQn OTGFS1_IRQn
#define OTG2_IRQn OTGFS2_IRQn
#define DWC2_OTG1_REG_BASE 0x50000000UL
#define DWC2_OTG2_REG_BASE 0x40040000UL
#elif CFG_TUSB_MCU == OPT_MCU_AT32F423
#include <at32f423.h>
- #define OTG1_FIFO_SIZE 1280
+ #define OTG1_DFIFO_DEPTH 320
#define OTG1_IRQn OTGFS1_IRQn
#define DWC2_OTG1_REG_BASE 0x50000000UL
#elif CFG_TUSB_MCU == OPT_MCU_AT32F402_405
#include <at32f402_405.h>
- #define OTG1_FIFO_SIZE 1280
- #define OTG2_FIFO_SIZE 4096
+ #define OTG1_DFIFO_DEPTH 320
+ #define OTG2_DFIFO_DEPTH 1024
#define OTG1_IRQn OTGFS1_IRQn
#define OTG2_IRQn OTGHS_IRQn
#define DWC2_OTG1_REG_BASE 0x50000000UL
#define DWC2_OTG2_REG_BASE 0x40040000UL //OTGHS
#elif CFG_TUSB_MCU == OPT_MCU_AT32F425
#include <at32f425.h>
- #define OTG1_FIFO_SIZE 1280
+ #define OTG1_DFIFO_DEPTH 320
#define OTG1_IRQn OTGFS1_IRQn
#define DWC2_OTG1_REG_BASE 0x50000000UL
#elif CFG_TUSB_MCU == OPT_MCU_AT32F45X
#include <at32f45x.h>
- #define OTG1_FIFO_SIZE 1280
+ #define OTG1_DFIFO_DEPTH 320
#define OTG1_IRQn OTGFS1_IRQn
#define DWC2_OTG1_REG_BASE 0x50000000UL
#endif
@@ -73,12 +73,18 @@ extern "C" {
#endif
static const dwc2_controller_t _dwc2_controller[] = {
- {.reg_base = DWC2_OTG1_REG_BASE, .irqnum = OTG1_IRQn, .ep_count = DWC2_EP_MAX, .ep_fifo_size = OTG1_FIFO_SIZE},
+ {.reg_base = DWC2_OTG1_REG_BASE, .irqnum = OTG1_IRQn, .ep_count = DWC2_EP_MAX, .otg_dfifo_depth = OTG1_DFIFO_DEPTH},
#if defined DWC2_OTG2_REG_BASE
- {.reg_base = DWC2_OTG2_REG_BASE, .irqnum = OTG2_IRQn, .ep_count = DWC2_EP_MAX, .ep_fifo_size = OTG2_FIFO_SIZE}
+ {.reg_base = DWC2_OTG2_REG_BASE, .irqnum = OTG2_IRQn, .ep_count = DWC2_EP_MAX, .otg_dfifo_depth = OTG2_DFIFO_DEPTH}
#endif
};
+// MCU specific to enable dwc2 clock/power before any access to register
+TU_ATTR_ALWAYS_INLINE static inline void dwc2_clock_init(uint8_t rhport, tusb_role_t role) {
+ (void) rhport;
+ (void) role;
+}
+
TU_ATTR_ALWAYS_INLINE static inline void dwc2_int_set(uint8_t rhport, tusb_role_t role, bool enabled) {
(void) role;
const IRQn_Type irqn = (IRQn_Type) _dwc2_controller[rhport].irqnum;
diff --git a/src/portable/synopsys/dwc2/dwc2_bcm.h b/src/portable/synopsys/dwc2/dwc2_bcm.h
index 00842bba2..91cae821e 100644
--- a/src/portable/synopsys/dwc2/dwc2_bcm.h
+++ b/src/portable/synopsys/dwc2/dwc2_bcm.h
@@ -39,13 +39,19 @@
static const dwc2_controller_t _dwc2_controller[] =
{
- { .reg_base = USB_OTG_GLOBAL_BASE, .irqnum = USB_IRQn, .ep_count = DWC2_EP_MAX, .ep_fifo_size = 16384 }
+ { .reg_base = USB_OTG_GLOBAL_BASE, .irqnum = USB_IRQn, .ep_count = DWC2_EP_MAX, .otg_dfifo_depth = 4096 }
};
#define dcache_clean(_addr, _size) data_clean(_addr, _size)
#define dcache_invalidate(_addr, _size) data_invalidate(_addr, _size)
#define dcache_clean_invalidate(_addr, _size) data_clean_and_invalidate(_addr, _size)
+// MCU specific to enable dwc2 clock/power before any access to register
+TU_ATTR_ALWAYS_INLINE static inline void dwc2_clock_init(uint8_t rhport, tusb_role_t role) {
+ (void) rhport;
+ (void) role;
+}
+
TU_ATTR_ALWAYS_INLINE
static inline void dwc2_dcd_int_enable(uint8_t rhport)
{
diff --git a/src/portable/synopsys/dwc2/dwc2_common.h b/src/portable/synopsys/dwc2/dwc2_common.h
index 9f28ab2e0..5c4798d21 100644
--- a/src/portable/synopsys/dwc2/dwc2_common.h
+++ b/src/portable/synopsys/dwc2/dwc2_common.h
@@ -38,13 +38,15 @@
#include "host/hcd.h"
#endif
-// Following symbols must be defined by port header
-// - _dwc2_controller[]: array of controllers
-// - DWC2_EP_MAX: largest EP counts of all controllers
-// - dwc2_phy_init/dwc2_phy_update: phy init called before and after core reset
-// - dwc2_phy_deinit(dwc2, hs_phy_type): phy deinit to disable PHY power, only deinit the phy used by core
-// - dwc2_dcd_int_enable/dwc2_dcd_int_disable
-// - dwc2_remote_wakeup_delay
+/* Following symbols must be defined by port header
+ - _dwc2_controller[]: array of controllers
+ - DWC2_EP_MAX: largest EP counts of all controllers
+ - dwc2_clock_init(): clock init call before
+ - dwc2_phy_init/dwc2_phy_update: phy init called before and after core reset
+ - dwc2_phy_deinit(dwc2, hs_phy_type): phy deinit to disable PHY power, only deinit the phy used by core
+ - dwc2_dcd_int_enable/dwc2_dcd_int_disable
+ - dwc2_remote_wakeup_delay
+*/
#if defined(TUP_USBIP_DWC2_STM32)
#include "dwc2_stm32.h"
diff --git a/src/portable/synopsys/dwc2/dwc2_efm32.h b/src/portable/synopsys/dwc2/dwc2_efm32.h
index e1cb7c769..063360873 100644
--- a/src/portable/synopsys/dwc2/dwc2_efm32.h
+++ b/src/portable/synopsys/dwc2/dwc2_efm32.h
@@ -40,9 +40,15 @@
static const dwc2_controller_t _dwc2_controller[] =
{
- { .reg_base = DWC2_REG_BASE, .irqnum = USB_IRQn, .ep_count = DWC2_EP_MAX, .ep_fifo_size = 2048 }
+ { .reg_base = DWC2_REG_BASE, .irqnum = USB_IRQn, .ep_count = DWC2_EP_MAX, .otg_dfifo_depth = 512 }
};
+// MCU specific to enable dwc2 clock/power before any access to register
+TU_ATTR_ALWAYS_INLINE static inline void dwc2_clock_init(uint8_t rhport, tusb_role_t role) {
+ (void) rhport;
+ (void) role;
+}
+
TU_ATTR_ALWAYS_INLINE
static inline void dwc2_dcd_int_enable(uint8_t rhport)
{
diff --git a/src/portable/synopsys/dwc2/dwc2_esp32.h b/src/portable/synopsys/dwc2/dwc2_esp32.h
index ff9f216bd..436f8dc30 100644
--- a/src/portable/synopsys/dwc2/dwc2_esp32.h
+++ b/src/portable/synopsys/dwc2/dwc2_esp32.h
@@ -37,14 +37,18 @@
#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
#define DWC2_EP_MAX 7
static const dwc2_controller_t _dwc2_controller[] = {
- { .reg_base = DWC2_FS_REG_BASE, .irqnum = ETS_USB_INTR_SOURCE, .ep_count = 7, .ep_in_count = 5, .ep_fifo_size = 1024 }
+ { .reg_base = DWC2_FS_REG_BASE, .irqnum = ETS_USB_INTR_SOURCE, .ep_count = 7, .ep_in_count = 5, .otg_dfifo_depth = 256 }
};
#elif TU_CHECK_MCU(OPT_MCU_ESP32H4)
@@ -61,7 +65,7 @@ static const dwc2_controller_t _dwc2_controller[] = {
#define DWC2_EP_MAX 7
static const dwc2_controller_t _dwc2_controller[] = {
- { .reg_base = DWC2_FS_REG_BASE, .irqnum = ETS_USB_OTG11_INTR_SOURCE, .ep_count = 7, .ep_in_count = 5, .ep_fifo_size = 1024 }
+ { .reg_base = DWC2_FS_REG_BASE, .irqnum = ETS_USB_OTG11_INTR_SOURCE, .ep_count = 7, .ep_in_count = 5, .otg_dfifo_depth = 256 }
};
#elif TU_CHECK_MCU(OPT_MCU_ESP32P4)
@@ -72,8 +76,16 @@ static const dwc2_controller_t _dwc2_controller[] = {
// On ESP32 for consistency we associate
// - Port0 to OTG_FS, and Port1 to OTG_HS
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, .ep_fifo_size = 1024 },
- { .reg_base = DWC2_HS_REG_BASE, .irqnum = ETS_USB_OTG_INTR_SOURCE, .ep_count = 16, .ep_in_count = 8, .ep_fifo_size = 4096 }
+ { .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
@@ -97,6 +109,12 @@ static void dwc2_int_handler_wrap(void* arg) {
#endif
}
+// MCU specific to enable dwc2 clock/power before any access to register
+TU_ATTR_ALWAYS_INLINE static inline void dwc2_clock_init(uint8_t rhport, tusb_role_t role) {
+ (void) rhport;
+ (void) role;
+}
+
TU_ATTR_ALWAYS_INLINE static inline void dwc2_int_set(uint8_t rhport, tusb_role_t role, bool enabled) {
if (enabled) {
esp_intr_alloc(_dwc2_controller[rhport].irqnum, ESP_INTR_FLAG_LOWMED,
diff --git a/src/portable/synopsys/dwc2/dwc2_gd32.h b/src/portable/synopsys/dwc2/dwc2_gd32.h
index ccbf93a76..28c3fde5e 100644
--- a/src/portable/synopsys/dwc2/dwc2_gd32.h
+++ b/src/portable/synopsys/dwc2/dwc2_gd32.h
@@ -37,7 +37,7 @@
static const dwc2_controller_t _dwc2_controller[] =
{
- { .reg_base = DWC2_REG_BASE, .irqnum = 86, .ep_count = DWC2_EP_MAX, .ep_fifo_size = 1280 }
+ { .reg_base = DWC2_REG_BASE, .irqnum = 86, .ep_count = DWC2_EP_MAX, .otg_dfifo_depth = 320 }
};
extern uint32_t SystemCoreClock;
@@ -57,6 +57,12 @@ static inline void __eclic_disable_interrupt (uint32_t irq){
*(volatile uint8_t*)(ECLIC_INTERRUPT_ENABLE_BASE + (irq * 4)) = 0;
}
+// MCU specific to enable dwc2 clock/power before any access to register
+TU_ATTR_ALWAYS_INLINE static inline void dwc2_clock_init(uint8_t rhport, tusb_role_t role) {
+ (void) rhport;
+ (void) role;
+}
+
TU_ATTR_ALWAYS_INLINE
static inline void dwc2_dcd_int_enable(uint8_t rhport)
{
diff --git a/src/portable/synopsys/dwc2/dwc2_info.md b/src/portable/synopsys/dwc2/dwc2_info.md
index f83007b8c..205684e4b 100644
--- a/src/portable/synopsys/dwc2/dwc2_info.md
+++ b/src/portable/synopsys/dwc2/dwc2_info.md
@@ -1,58 +1,58 @@
-| | AT32 F405 FS | AT32 F405 HS | AT32 F415 | BCM2711 (Pi4) | EFM32GG | ESP32-S2/S3 | ESP32-P4 | nRF54 | ST F407/429 HS | ST F207/F407/411/429 FS | ST L476 FS | ST F412/76x FS | ST F76x HS | ST H743/H750 | ST F723/L4P5 FS | ST F723 HS | ST H7RS FS | ST U5A5/H7RS/N6 HS | XMC4500 | GD32VF103 |
-|:---------------------------|:---------------|:---------------|:------------|:----------------|:-------------|:--------------|:-------------|:-------------|:-----------------|:--------------------------|:-------------|:-----------------|:-------------|:---------------|:------------------|:-------------|:-------------|:---------------------|:-------------|:------------|
-| GUID | 0x00002000 | 0x00000000 | 0x00001000 | 0x2708A000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00001100 | 0x00001200 | 0x00002000 | 0x00002000 | 0x00002100 | 0x00002300 | 0x00003000 | 0x00003100 | 0x00004000 | 0x00005000 | 0x00AEC000 | 0x00001000 |
-| GSNPSID | 0x4F54400A | 0x4F54400A | 0x4F54400A | 0x4F54280A | 0x4F54330A | 0x4F54400A | 0x4F54400A | 0x4F54430A | 0x4F54281A | 0x4F54281A | 0x4F54310A | 0x4F54320A | 0x4F54320A | 0x4F54330A | 0x4F54330A | 0x4F54330A | 0x4F54411A | 0x4F54411A | 0x4F54292A | 0x00000000 |
-| - specs version | 4.00a | 4.00a | 4.00a | 2.80a | 3.30a | 4.00a | 4.00a | 4.30a | 2.81a | 2.81a | 3.10a | 3.20a | 3.20a | 3.30a | 3.30a | 3.30a | 4.11a | 4.11a | 2.92a | 0.00W |
-| GHWCFG1 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0xAA555000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 |
-| GHWCFG2 | 0x228FDD00 | 0x229FDDD0 | 0x228DCD00 | 0x228DDD50 | 0x228F5910 | 0x224DD930 | 0x215FFFD0 | 0x228BFC72 | 0x229ED590 | 0x229DCD20 | 0x229ED520 | 0x229ED520 | 0x229FE190 | 0x229FE190 | 0x229ED520 | 0x229FE1D0 | 0x229ED522 | 0x228FE052 | 0x228F5930 | 0x00000000 |
-| - op_mode | HNP SRP | HNP SRP | HNP SRP | HNP SRP | HNP SRP | HNP SRP | HNP SRP | noHNP noSRP | HNP SRP | HNP SRP | HNP SRP | HNP SRP | HNP SRP | HNP SRP | HNP SRP | HNP SRP | noHNP noSRP | noHNP noSRP | HNP SRP | HNP SRP |
-| - arch | Slave only | DMA internal | Slave only | DMA internal | DMA internal | DMA internal | DMA internal | DMA internal | DMA internal | Slave only | Slave only | Slave only | DMA internal | DMA internal | Slave only | DMA internal | Slave only | DMA internal | DMA internal | Slave only |
-| - single_point | hub | hub | hub | hub | hub | n/a | hub | n/a | hub | n/a | n/a | n/a | hub | hub | n/a | hub | n/a | hub | n/a | hub |
-| - hs_phy_type | n/a | UTMI+/ULPI | n/a | UTMI+ | n/a | n/a | UTMI+/ULPI | UTMI+ | ULPI | n/a | n/a | n/a | ULPI | ULPI | n/a | UTMI+/ULPI | n/a | UTMI+ | n/a | n/a |
-| - fs_phy_type | Dedicated | Dedicated | Dedicated | Dedicated | Dedicated | Dedicated | Shared ULPI | n/a | Dedicated | Dedicated | Dedicated | Dedicated | Dedicated | Dedicated | Dedicated | Dedicated | Dedicated | n/a | Dedicated | n/a |
-| - num_dev_ep | 7 | 7 | 3 | 7 | 6 | 6 | 15 | 15 | 5 | 3 | 5 | 5 | 8 | 8 | 5 | 8 | 5 | 8 | 6 | 0 |
-| - num_host_ch | 15 | 15 | 7 | 7 | 13 | 7 | 15 | 15 | 11 | 7 | 11 | 11 | 15 | 15 | 11 | 15 | 11 | 15 | 13 | 0 |
-| - period_channel_support | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 |
-| - enable_dynamic_fifo | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 |
-| - mul_proc_intrpt | 0 | 1 | 0 | 0 | 0 | 0 | 1 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 0 |
-| - reserved21 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
-| - nptx_q_depth | 8 | 8 | 8 | 8 | 8 | 4 | 4 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 2 |
-| - ptx_q_depth | 8 | 8 | 8 | 8 | 8 | 8 | 4 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 2 |
-| - token_q_depth | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 0 |
-| - otg_enable_ic_usb | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
-| GHWCFG3 | 0x020004E8 | 0x03F006E8 | 0x020004E8 | 0x0FF000E8 | 0x01F204E8 | 0x00C804B5 | 0x03805EB5 | 0x0BEAC0E8 | 0x03F403E8 | 0x020001E8 | 0x0200D1E8 | 0x0200D1E8 | 0x03EED2E8 | 0x03B8D2E8 | 0x0200D1E8 | 0x03EED2E8 | 0x020081E8 | 0x03B882E8 | 0x027A01E5 | 0x00000000 |
-| - xfer_size_width | 8 | 8 | 8 | 8 | 8 | 5 | 5 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 5 | 0 |
-| - packet_size_width | 6 | 6 | 6 | 6 | 6 | 3 | 3 | 6 | 6 | 6 | 6 | 6 | 6 | 6 | 6 | 6 | 6 | 6 | 6 | 0 |
-| - otg_enable | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 |
-| - i2c_enable | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 1 | 0 | 0 | 1 | 0 | 1 | 0 | 1 | 0 |
-| - vendor_ctrl_itf | 0 | 1 | 0 | 0 | 0 | 0 | 1 | 0 | 1 | 0 | 0 | 0 | 1 | 1 | 0 | 1 | 0 | 1 | 0 | 0 |
-| - optional_feature_removed | 1 | 1 | 1 | 0 | 1 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
-| - synch_reset | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
-| - otg_adp_support | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 0 | 0 |
-| - otg_enable_hsic | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
-| - battery_charger_support | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 0 | 0 |
-| - lpm_mode | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 0 |
-| - dfifo_depth | 512 | 1008 | 512 | 4080 | 498 | 200 | 896 | 3050 | 1012 | 512 | 512 | 512 | 1006 | 952 | 512 | 1006 | 512 | 952 | 634 | 0 |
-| GHWCFG4 | 0x1FF0A020 | 0x1FF0A020 | 0x0000000F | 0x1FF00020 | 0x1BF08030 | 0xD3F0A030 | 0xDFF1A030 | 0x1E10AA60 | 0x17F00030 | 0x0FF08030 | 0x17F08030 | 0x17F08030 | 0x23F00030 | 0xE3F00030 | 0x17F08030 | 0x23F00030 | 0x1610B230 | 0xE2103E30 | 0xDBF08030 | 0x00000000 |
-| - num_dev_period_in_ep | 0 | 0 | 15 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
-| - partial_powerdown | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 |
-| - ahb_freq_min | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 |
-| - hibernation | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
-| - extended_hibernation | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
-| - reserved8 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
-| - enhanced_lpm_support1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 0 |
-| - service_interval_flow | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 |
-| - ipg_isoc_support | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 |
-| - acg_support | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 0 |
-| - enhanced_lpm_support | 1 | 1 | 0 | 0 | 0 | 1 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 0 |
-| - phy_data_width | 8/16 bit | 8/16 bit | 8 bit | 8 bit | 8/16 bit | 8/16 bit | 8/16 bit | 8/16 bit | 8 bit | 8/16 bit | 8/16 bit | 8/16 bit | 8 bit | 8 bit | 8/16 bit | 8 bit | 8/16 bit | 8 bit | 8/16 bit | 8 bit |
-| - ctrl_ep_num | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
-| - iddg_filter | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 |
-| - vbus_valid_filter | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 1 | 0 |
-| - a_valid_filter | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 1 | 0 |
-| - b_valid_filter | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 1 | 0 |
-| - session_end_filter | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 1 | 0 |
-| - dedicated_fifos | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 |
-| - num_dev_in_eps | 7 | 7 | 0 | 7 | 6 | 4 | 7 | 7 | 5 | 3 | 5 | 5 | 8 | 8 | 5 | 8 | 5 | 8 | 6 | 0 |
-| - dma_desc_enable | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 1 | 1 | 0 |
-| - dma_desc_dynamic | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 1 | 1 | 0 |
+| | AT32 F405 FS | AT32 F405 HS | AT32 F415 | BCM2711 (Pi4) | EFM32GG | ESP32-S2/S3 | ESP32-P4 | nRF54H20 | nRF54LM20 | ST F407/429 HS | ST F207/F407/411/429 FS | ST L476 FS | ST F412/76x FS | ST F76x HS | ST H743/H750 | ST F723/L4P5 FS | ST F723 HS | ST H7RS FS | ST U5A5/H7RS/N6 HS | XMC4500 | GD32VF103 |
+|:---------------------------|:---------------|:---------------|:------------|:----------------|:-------------|:--------------|:-------------|:-------------|:-------------|:-----------------|:--------------------------|:-------------|:-----------------|:-------------|:---------------|:------------------|:-------------|:-------------|:---------------------|:-------------|:------------|
+| GUID | 0x00002000 | 0x00000000 | 0x00001000 | 0x2708A000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00001100 | 0x00001200 | 0x00002000 | 0x00002000 | 0x00002100 | 0x00002300 | 0x00003000 | 0x00003100 | 0x00004000 | 0x00005000 | 0x00AEC000 | 0x00001000 |
+| GSNPSID | 0x4F54400A | 0x4F54400A | 0x4F54400A | 0x4F54280A | 0x4F54330A | 0x4F54400A | 0x4F54400A | 0x4F54430A | 0x4F54500B | 0x4F54281A | 0x4F54281A | 0x4F54310A | 0x4F54320A | 0x4F54320A | 0x4F54330A | 0x4F54330A | 0x4F54330A | 0x4F54411A | 0x4F54411A | 0x4F54292A | 0x00000000 |
+| - specs version | 4.00a | 4.00a | 4.00a | 2.80a | 3.30a | 4.00a | 4.00a | 4.30a | 5.00b | 2.81a | 2.81a | 3.10a | 3.20a | 3.20a | 3.30a | 3.30a | 3.30a | 4.11a | 4.11a | 2.92a | 0.00W |
+| GHWCFG1 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0xAA555000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 |
+| GHWCFG2 | 0x228FDD00 | 0x229FDDD0 | 0x228DCD00 | 0x228DDD50 | 0x228F5910 | 0x224DD930 | 0x215FFFD0 | 0x228BFC72 | 0x22AFFC52 | 0x229ED590 | 0x229DCD20 | 0x229ED520 | 0x229ED520 | 0x229FE190 | 0x229FE190 | 0x229ED520 | 0x229FE1D0 | 0x229ED522 | 0x228FE052 | 0x228F5930 | 0x00000000 |
+| - op_mode | HNP SRP | HNP SRP | HNP SRP | HNP SRP | HNP SRP | HNP SRP | HNP SRP | noHNP noSRP | noHNP noSRP | HNP SRP | HNP SRP | HNP SRP | HNP SRP | HNP SRP | HNP SRP | HNP SRP | HNP SRP | noHNP noSRP | noHNP noSRP | HNP SRP | HNP SRP |
+| - arch | Slave only | DMA internal | Slave only | DMA internal | DMA internal | DMA internal | DMA internal | DMA internal | DMA internal | DMA internal | Slave only | Slave only | Slave only | DMA internal | DMA internal | Slave only | DMA internal | Slave only | DMA internal | DMA internal | Slave only |
+| - single_point | hub | hub | hub | hub | hub | n/a | hub | n/a | hub | hub | n/a | n/a | n/a | hub | hub | n/a | hub | n/a | hub | n/a | hub |
+| - hs_phy_type | n/a | UTMI+/ULPI | n/a | UTMI+ | n/a | n/a | UTMI+/ULPI | UTMI+ | UTMI+ | ULPI | n/a | n/a | n/a | ULPI | ULPI | n/a | UTMI+/ULPI | n/a | UTMI+ | n/a | n/a |
+| - fs_phy_type | Dedicated | Dedicated | Dedicated | Dedicated | Dedicated | Dedicated | Shared ULPI | n/a | n/a | Dedicated | Dedicated | Dedicated | Dedicated | Dedicated | Dedicated | Dedicated | Dedicated | Dedicated | n/a | Dedicated | n/a |
+| - num_dev_ep | 7 | 7 | 3 | 7 | 6 | 6 | 15 | 15 | 15 | 5 | 3 | 5 | 5 | 8 | 8 | 5 | 8 | 5 | 8 | 6 | 0 |
+| - num_host_ch | 15 | 15 | 7 | 7 | 13 | 7 | 15 | 15 | 15 | 11 | 7 | 11 | 11 | 15 | 15 | 11 | 15 | 11 | 15 | 13 | 0 |
+| - period_channel_support | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 |
+| - enable_dynamic_fifo | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 |
+| - mul_proc_intrpt | 0 | 1 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 0 |
+| - reserved21 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
+| - nptx_q_depth | 8 | 8 | 8 | 8 | 8 | 4 | 4 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 2 |
+| - ptx_q_depth | 8 | 8 | 8 | 8 | 8 | 8 | 4 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 2 |
+| - token_q_depth | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 0 |
+| - otg_enable_ic_usb | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
+| GHWCFG3 | 0x020004E8 | 0x03F006E8 | 0x020004E8 | 0x0FF000E8 | 0x01F204E8 | 0x00C804B5 | 0x03805EB5 | 0x0BEAC0E8 | 0x0BE0C0E8 | 0x03F403E8 | 0x020001E8 | 0x0200D1E8 | 0x0200D1E8 | 0x03EED2E8 | 0x03B8D2E8 | 0x0200D1E8 | 0x03EED2E8 | 0x020081E8 | 0x03B882E8 | 0x027A01E5 | 0x00000000 |
+| - xfer_size_width | 8 | 8 | 8 | 8 | 8 | 5 | 5 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 5 | 0 |
+| - packet_size_width | 6 | 6 | 6 | 6 | 6 | 3 | 3 | 6 | 6 | 6 | 6 | 6 | 6 | 6 | 6 | 6 | 6 | 6 | 6 | 6 | 0 |
+| - otg_enable | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 |
+| - i2c_enable | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 1 | 0 | 0 | 1 | 0 | 1 | 0 | 1 | 0 |
+| - vendor_ctrl_itf | 0 | 1 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 1 | 0 | 0 | 0 | 1 | 1 | 0 | 1 | 0 | 1 | 0 | 0 |
+| - optional_feature_removed | 1 | 1 | 1 | 0 | 1 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
+| - synch_reset | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
+| - otg_adp_support | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 0 | 0 |
+| - otg_enable_hsic | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
+| - battery_charger_support | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 0 | 0 |
+| - lpm_mode | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 0 |
+| - dfifo_depth | 512 | 1008 | 512 | 4080 | 498 | 200 | 896 | 3050 | 3040 | 1012 | 512 | 512 | 512 | 1006 | 952 | 512 | 1006 | 512 | 952 | 634 | 0 |
+| GHWCFG4 | 0x1FF0A020 | 0x1FF0A020 | 0x0000000F | 0x1FF00020 | 0x1BF08030 | 0xD3F0A030 | 0xDFF1A030 | 0x1E10AA60 | 0x3E10AA60 | 0x17F00030 | 0x0FF08030 | 0x17F08030 | 0x17F08030 | 0x23F00030 | 0xE3F00030 | 0x17F08030 | 0x23F00030 | 0x1610B230 | 0xE2103E30 | 0xDBF08030 | 0x00000000 |
+| - num_dev_period_in_ep | 0 | 0 | 15 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
+| - partial_powerdown | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 |
+| - ahb_freq_min | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 |
+| - hibernation | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
+| - extended_hibernation | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
+| - reserved8 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
+| - enhanced_lpm_support1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 0 |
+| - service_interval_flow | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 |
+| - ipg_isoc_support | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 |
+| - acg_support | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 0 |
+| - enhanced_lpm_support | 1 | 1 | 0 | 0 | 0 | 1 | 1 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 0 |
+| - phy_data_width | 8/16 bit | 8/16 bit | 8 bit | 8 bit | 8/16 bit | 8/16 bit | 8/16 bit | 8/16 bit | 8/16 bit | 8 bit | 8/16 bit | 8/16 bit | 8/16 bit | 8 bit | 8 bit | 8/16 bit | 8 bit | 8/16 bit | 8 bit | 8/16 bit | 8 bit |
+| - ctrl_ep_num | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
+| - iddg_filter | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 |
+| - vbus_valid_filter | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 1 | 0 |
+| - a_valid_filter | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 1 | 0 |
+| - b_valid_filter | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 1 | 0 |
+| - session_end_filter | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 1 | 0 |
+| - dedicated_fifos | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 |
+| - num_dev_in_eps | 7 | 7 | 0 | 7 | 6 | 4 | 7 | 7 | 15 | 5 | 3 | 5 | 5 | 8 | 8 | 5 | 8 | 5 | 8 | 6 | 0 |
+| - dma_desc_enable | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 1 | 1 | 0 |
+| - dma_desc_dynamic | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 1 | 1 | 0 |
diff --git a/src/portable/synopsys/dwc2/dwc2_info.py b/src/portable/synopsys/dwc2/dwc2_info.py
index e6601f482..8ee04e179 100755
--- a/src/portable/synopsys/dwc2/dwc2_info.py
+++ b/src/portable/synopsys/dwc2/dwc2_info.py
@@ -9,13 +9,14 @@ import pandas as pd
dwc2_reg_list = ['GUID', 'GSNPSID', 'GHWCFG1', 'GHWCFG2', 'GHWCFG3', 'GHWCFG4']
dwc2_reg_value = {
'AT32 F405 FS': [0x00002000, 0x4F54400A, 0x00000000, 0x228FDD00, 0x020004E8, 0x1FF0A020],
- 'AT32 F405 HS': [0x00000000, 0x4F54400A, 0x00000000, 0x229FDDD0, 0x03F006E8, 0x1FF0A020],
+ 'AT32 F405 HS': [0, 0x4F54400A, 0x00000000, 0x229FDDD0, 0x03F006E8, 0x1FF0A020],
'AT32 F415': [0x00001000, 0x4F54400A, 0x00000000, 0x228DCD00, 0x020004E8, 0x0F],
'BCM2711 (Pi4)': [0x2708A000, 0x4F54280A, 0, 0x228DDD50, 0xFF000E8, 0x1FF00020],
'EFM32GG': [0, 0x4F54330A, 0, 0x228F5910, 0x01F204E8, 0x1BF08030],
'ESP32-S2/S3': [0, 0x4F54400A, 0, 0x224DD930, 0x0C804B5, 0xD3F0A030],
'ESP32-P4': [0, 0x4F54400A, 0, 0x215FFFD0, 0x03805EB5, 0xDFF1A030],
- 'nRF54': [0, 0x4F54430A, 0xAA555000, 0x228BFC72, 0x0BEAC0E8, 0x1E10AA60],
+ 'nRF54H20': [0, 0x4F54430A, 0xAA555000, 0x228BFC72, 0x0BEAC0E8, 0x1E10AA60], # base on Preliminary Datasheet v0.7
+ 'nRF54LM20': [0, 0x4F54500B, 0x00000000, 0x22AFFC52, 0x0BE0C0E8, 0x3E10AA60],
# ST sort by GUID
'ST F407/429 HS': [0x1100, 0x4F54281A, 0, 0x229ED590, 0x03F403E8, 0x17F00030],
'ST F207/F407/411/429 FS': [0x1200, 0x4F54281A, 0, 0x229DCD20, 0x020001E8, 0x0FF08030],
diff --git a/src/portable/synopsys/dwc2/dwc2_nrf.h b/src/portable/synopsys/dwc2/dwc2_nrf.h
index 51f2d684f..a1bf692f8 100644
--- a/src/portable/synopsys/dwc2/dwc2_nrf.h
+++ b/src/portable/synopsys/dwc2/dwc2_nrf.h
@@ -22,22 +22,88 @@
* THE SOFTWARE.
*
* This file is part of the TinyUSB stack.
+ *
+ * Modification
+ * - Gabriel Koppenstein add nRF54LM20 support
*/
#ifndef TUSB_DWC2_NRF_H
#define TUSB_DWC2_NRF_H
+// NRF is device only without OTG support
#include "nrf.h"
+#ifdef __GNUC__
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wcast-align"
+#endif
+
+#include <soc/nrfx_coredep.h>
+
+#ifdef __GNUC__
+#pragma GCC diagnostic pop
+#endif
+
#define DWC2_EP_MAX 16
+// Use the auto-resolving peripheral pointer (respects TrustZone secure/non-secure mapping)
+#if defined(NRF54LM20A_ENGA_XXAA)
+ #define DWC2_REG_BASE ((uintptr_t)NRF_USBHSCORE)
+#else
+ #define DWC2_REG_BASE ((uintptr_t)NRF_USBHSCORE0)
+#endif
+
static const dwc2_controller_t _dwc2_controller[] = {
- { .reg_base = NRF_USBHSCORE0_NS_BASE, .irqnum = USBHS_IRQn, .ep_count = 16, .ep_fifo_size = 12288 },
+ {.reg_base = DWC2_REG_BASE, .irqnum = USBHS_IRQn, .ep_count = 16, .otg_dfifo_depth = 3072},
};
-TU_ATTR_ALWAYS_INLINE static inline void dwc2_int_set(uint8_t rhport, tusb_role_t role, bool enabled) {
+// MCU specific to enable dwc2 clock/power before any access to register
+TU_ATTR_ALWAYS_INLINE static inline void dwc2_clock_init(uint8_t rhport, tusb_role_t role) {
(void) rhport;
(void) role;
- (void) enabled;
+
+ #if defined(NRF54LM20A_ENGA_XXAA)
+ // Start the USB voltage regulator
+ NRF_VREGUSB->TASKS_START = VREGUSB_TASKS_START_TASKS_START_Trigger;
+
+ // Based on Zephyr usbhs_enable_core() in drivers/usb/udc/udc_dwc2_vendor_quirks.h
+ // Step 1: Power up core only (PHY not yet)
+ NRF_USBHS->ENABLE = USBHS_ENABLE_CORE_Msk;
+
+ // Step 2: Override ID=Device (bit 31), and temporarily override VBUSVALID
+ NRF_USBHS->PHY.OVERRIDEVALUES = (USBHS_PHY_OVERRIDEVALUES_ID_Device << USBHS_PHY_OVERRIDEVALUES_ID_Pos);
+ NRF_USBHS->PHY.INPUTOVERRIDE = USBHS_PHY_INPUTOVERRIDE_ID_Msk | USBHS_PHY_INPUTOVERRIDE_VBUSVALID_Msk;
+
+ // Step 3: Release PHY power-on reset by enabling PHY
+ NRF_USBHS->ENABLE = USBHS_ENABLE_PHY_Msk | USBHS_ENABLE_CORE_Msk;
+
+ // Step 4: Wait 45us for PHY clock to start
+ nrfx_coredep_delay_us(45);
+
+ // Step 5: Release DWC2 reset
+ NRF_USBHS->TASKS_START = USBHS_TASKS_START_TASKS_START_Trigger;
+
+ // Step 6: Wait for clock to start to avoid hang on too early register read
+ nrfx_coredep_delay_us(2);
+
+ // Step 7: Clear VBUSVALID override (keep ID=Device override)
+ // DWC2 is now in Non-Driving opmode; D+ pull-up will activate when DWC2 clears DCTL SftDiscon
+ NRF_USBHS->PHY.INPUTOVERRIDE = USBHS_PHY_INPUTOVERRIDE_ID_Msk;
+
+ // Barrier: USBHS wrapper (0x5005A000) and USBHSCORE (0x50020000) are separate
+ // peripheral blocks. Ensure the ENABLE/TASKS_START writes have propagated from
+ // the Cortex-M33 write buffer to hardware before anyone reads DWC2 core regs.
+ __DSB();
+ #endif
+}
+
+TU_ATTR_ALWAYS_INLINE static inline void dwc2_int_set(uint8_t rhport, tusb_role_t role, bool enabled) {
+ (void)rhport;
+ (void)role;
+ if (enabled) {
+ NVIC_EnableIRQ(USBHS_IRQn);
+ } else {
+ NVIC_DisableIRQ(USBHS_IRQn);
+ }
}
#define dwc2_dcd_int_enable(_rhport) dwc2_int_set(_rhport, TUSB_ROLE_DEVICE, true)
@@ -47,21 +113,38 @@ TU_ATTR_ALWAYS_INLINE static inline void dwc2_remote_wakeup_delay(void) {
}
// MCU specific PHY init, called BEFORE core reset
-TU_ATTR_ALWAYS_INLINE static inline void dwc2_phy_init(dwc2_regs_t* dwc2, uint8_t hs_phy_type) {
+TU_ATTR_ALWAYS_INLINE static inline void dwc2_phy_init(dwc2_regs_t *dwc2, uint8_t hs_phy_type) {
(void)dwc2;
(void)hs_phy_type;
}
// MCU specific PHY deinit, disable PHY power
-TU_ATTR_ALWAYS_INLINE static inline void dwc2_phy_deinit(dwc2_regs_t* dwc2, uint8_t hs_phy_type) {
+TU_ATTR_ALWAYS_INLINE static inline void dwc2_phy_deinit(dwc2_regs_t *dwc2, uint8_t hs_phy_type) {
(void)dwc2;
(void)hs_phy_type;
}
// MCU specific PHY update, it is called AFTER init() and core reset
-TU_ATTR_ALWAYS_INLINE static inline void dwc2_phy_update(dwc2_regs_t* dwc2, uint8_t hs_phy_type) {
+TU_ATTR_ALWAYS_INLINE static inline void dwc2_phy_update(dwc2_regs_t *dwc2, uint8_t hs_phy_type) {
(void)dwc2;
(void)hs_phy_type;
}
+// nRF54 Cortex-M33 has no D-cache, provide no-op stubs for DMA mode
+TU_ATTR_ALWAYS_INLINE static inline bool dwc2_dcache_clean(const void *addr, uint32_t data_size) {
+ (void)addr;
+ (void)data_size;
+ return true;
+}
+TU_ATTR_ALWAYS_INLINE static inline bool dwc2_dcache_invalidate(const void *addr, uint32_t data_size) {
+ (void)addr;
+ (void)data_size;
+ return true;
+}
+TU_ATTR_ALWAYS_INLINE static inline bool dwc2_dcache_clean_invalidate(const void *addr, uint32_t data_size) {
+ (void)addr;
+ (void)data_size;
+ return true;
+}
+
#endif
diff --git a/src/portable/synopsys/dwc2/dwc2_stm32.h b/src/portable/synopsys/dwc2/dwc2_stm32.h
index 259ad21b9..126632fec 100644
--- a/src/portable/synopsys/dwc2/dwc2_stm32.h
+++ b/src/portable/synopsys/dwc2/dwc2_stm32.h
@@ -31,36 +31,36 @@
extern "C" {
#endif
-// EP_MAX : Max number of bi-directional endpoints including EP0
-// EP_FIFO_SIZE : Size of dedicated USB SRAM
+// EP_MAX : Max number of bi-directional endpoints including EP0
+// DFIFO_DEPTH_FS/HS : DFIFO depth in 32-bit words (OTG_DFIFO_DEPTH)
#if CFG_TUSB_MCU == OPT_MCU_STM32F1
#include "stm32f1xx.h"
#define EP_MAX_FS 4
- #define EP_FIFO_SIZE_FS 1280
+ #define DFIFO_DEPTH_FS 320
#elif CFG_TUSB_MCU == OPT_MCU_STM32F2
#include "stm32f2xx.h"
#define EP_MAX_FS USB_OTG_FS_MAX_IN_ENDPOINTS
- #define EP_FIFO_SIZE_FS USB_OTG_FS_TOTAL_FIFO_SIZE
+ #define DFIFO_DEPTH_FS 320
#define EP_MAX_HS USB_OTG_HS_MAX_IN_ENDPOINTS
- #define EP_FIFO_SIZE_HS USB_OTG_HS_TOTAL_FIFO_SIZE
+ #define DFIFO_DEPTH_HS 1024
#elif CFG_TUSB_MCU == OPT_MCU_STM32F4
#include "stm32f4xx.h"
#define EP_MAX_FS USB_OTG_FS_MAX_IN_ENDPOINTS
- #define EP_FIFO_SIZE_FS USB_OTG_FS_TOTAL_FIFO_SIZE
+ #define DFIFO_DEPTH_FS 320
#define EP_MAX_HS USB_OTG_HS_MAX_IN_ENDPOINTS
- #define EP_FIFO_SIZE_HS USB_OTG_HS_TOTAL_FIFO_SIZE
+ #define DFIFO_DEPTH_HS 1024
#elif CFG_TUSB_MCU == OPT_MCU_STM32H7
#include "stm32h7xx.h"
#define EP_MAX_FS 9
- #define EP_FIFO_SIZE_FS 4096
+ #define DFIFO_DEPTH_FS 1024
#define EP_MAX_HS 9
- #define EP_FIFO_SIZE_HS 4096
+ #define DFIFO_DEPTH_HS 1024
// NOTE: H7 with only 1 USB port: H72x / H73x / H7Ax / H7Bx
// USB_OTG_FS_PERIPH_BASE and OTG_FS_IRQn not defined
@@ -72,18 +72,18 @@ extern "C" {
#elif CFG_TUSB_MCU == OPT_MCU_STM32H7RS
#include "stm32h7rsxx.h"
#define EP_MAX_FS 6
- #define EP_FIFO_SIZE_FS 1280
+ #define DFIFO_DEPTH_FS 320
#define EP_MAX_HS 9
- #define EP_FIFO_SIZE_HS 4096
+ #define DFIFO_DEPTH_HS 1024
#elif CFG_TUSB_MCU == OPT_MCU_STM32N6
#include "stm32n6xx.h"
#define EP_MAX_FS 9
- #define EP_FIFO_SIZE_FS 4096
+ #define DFIFO_DEPTH_FS 1024
#define EP_MAX_HS 9
- #define EP_FIFO_SIZE_HS 4096
+ #define DFIFO_DEPTH_HS 1024
#define USB_OTG_FS_PERIPH_BASE USB1_OTG_HS_BASE
#define OTG_FS_IRQn USB1_OTG_HS_IRQn
@@ -94,15 +94,15 @@ extern "C" {
#elif CFG_TUSB_MCU == OPT_MCU_STM32F7
#include "stm32f7xx.h"
#define EP_MAX_FS 6
- #define EP_FIFO_SIZE_FS 1280
+ #define DFIFO_DEPTH_FS 320
#define EP_MAX_HS 9
- #define EP_FIFO_SIZE_HS 4096
+ #define DFIFO_DEPTH_HS 1024
#elif CFG_TUSB_MCU == OPT_MCU_STM32L4
#include "stm32l4xx.h"
#define EP_MAX_FS 6
- #define EP_FIFO_SIZE_FS 1280
+ #define DFIFO_DEPTH_FS 320
#elif CFG_TUSB_MCU == OPT_MCU_STM32U5
#include "stm32u5xx.h"
@@ -110,11 +110,11 @@ extern "C" {
#ifdef USB_OTG_FS
#define USB_OTG_FS_PERIPH_BASE USB_OTG_FS_BASE
#define EP_MAX_FS 6
- #define EP_FIFO_SIZE_FS 1280
+ #define DFIFO_DEPTH_FS 320
#else
#define USB_OTG_HS_PERIPH_BASE USB_OTG_HS_BASE
#define EP_MAX_HS 9
- #define EP_FIFO_SIZE_HS 4096
+ #define DFIFO_DEPTH_HS 1024
#endif
#elif CFG_TUSB_MCU == OPT_MCU_STM32WBA
@@ -131,7 +131,7 @@ extern "C" {
#define USB_OTG_HS_PERIPH_BASE USB_OTG_HS_BASE_NS
#define OTG_HS_IRQn USB_OTG_HS_IRQn
#define EP_MAX_HS 9
- #define EP_FIFO_SIZE_HS 4096
+ #define DFIFO_DEPTH_HS 1024
#else
#error "Unsupported MCUs"
#endif
@@ -147,11 +147,11 @@ extern "C" {
// - Port0 to OTG_FS, and Port1 to OTG_HS
static const dwc2_controller_t _dwc2_controller[] = {
#ifdef USB_OTG_FS_PERIPH_BASE
- { .reg_base = USB_OTG_FS_PERIPH_BASE, .irqnum = OTG_FS_IRQn, .ep_count = EP_MAX_FS, .ep_fifo_size = EP_FIFO_SIZE_FS },
+ { .reg_base = USB_OTG_FS_PERIPH_BASE, .irqnum = OTG_FS_IRQn, .ep_count = EP_MAX_FS, .otg_dfifo_depth = DFIFO_DEPTH_FS },
#endif
#ifdef USB_OTG_HS_PERIPH_BASE
- { .reg_base = USB_OTG_HS_PERIPH_BASE, .irqnum = OTG_HS_IRQn, .ep_count = EP_MAX_HS, .ep_fifo_size = EP_FIFO_SIZE_HS },
+ { .reg_base = USB_OTG_HS_PERIPH_BASE, .irqnum = OTG_HS_IRQn, .ep_count = EP_MAX_HS, .otg_dfifo_depth = DFIFO_DEPTH_HS },
#endif
};
@@ -162,6 +162,12 @@ static const dwc2_controller_t _dwc2_controller[] = {
// SystemCoreClock is already included by family header
// extern uint32_t SystemCoreClock;
+// MCU specific to enable dwc2 clock/power before any access to register
+TU_ATTR_ALWAYS_INLINE static inline void dwc2_clock_init(uint8_t rhport, tusb_role_t role) {
+ (void) rhport;
+ (void) role;
+}
+
TU_ATTR_ALWAYS_INLINE static inline void dwc2_int_set(uint8_t rhport, tusb_role_t role, bool enabled) {
(void) role;
const IRQn_Type irqn = (IRQn_Type) _dwc2_controller[rhport].irqnum;
diff --git a/src/portable/synopsys/dwc2/dwc2_type.h b/src/portable/synopsys/dwc2/dwc2_type.h
index 596bd0b34..7c03a4a91 100644
--- a/src/portable/synopsys/dwc2/dwc2_type.h
+++ b/src/portable/synopsys/dwc2/dwc2_type.h
@@ -47,7 +47,7 @@ typedef struct
uint32_t irqnum;
uint8_t ep_count;
uint8_t ep_in_count;
- uint32_t ep_fifo_size;
+ uint16_t otg_dfifo_depth; // total SPRAM in 32-bit words = ghwcfg3.dfifo_depth + EP_LOC_CNT
}dwc2_controller_t;
// DWC OTG HW Release versions
@@ -63,6 +63,7 @@ typedef struct
#define DWC2_CORE_REV_4_00a 0x4f54400a
#define DWC2_CORE_REV_4_11a 0x4f54411a
#define DWC2_CORE_REV_4_20a 0x4f54420a
+#define DWC2_CORE_REV_5_00b 0x4F54500b
#define DWC2_FS_IOT_REV_1_00a 0x5531100a
#define DWC2_HS_IOT_REV_1_00a 0x5532100a
#define DWC2_CORE_REV_MASK 0x0000ffff
diff --git a/src/portable/synopsys/dwc2/dwc2_xmc.h b/src/portable/synopsys/dwc2/dwc2_xmc.h
index aca3873df..85b2a2633 100644
--- a/src/portable/synopsys/dwc2/dwc2_xmc.h
+++ b/src/portable/synopsys/dwc2/dwc2_xmc.h
@@ -39,9 +39,15 @@
static const dwc2_controller_t _dwc2_controller[] =
{
// Note: XMC has some custom control registers before DWC registers
- { .reg_base = USB0_BASE, .irqnum = USB0_0_IRQn, .ep_count = DWC2_EP_MAX, .ep_fifo_size = 2048 }
+ { .reg_base = USB0_BASE, .irqnum = USB0_0_IRQn, .ep_count = DWC2_EP_MAX, .otg_dfifo_depth = 512 }
};
+// MCU specific to enable dwc2 clock/power before any access to register
+TU_ATTR_ALWAYS_INLINE static inline void dwc2_clock_init(uint8_t rhport, tusb_role_t role) {
+ (void) rhport;
+ (void) role;
+}
+
TU_ATTR_ALWAYS_INLINE
static inline void dwc2_dcd_int_enable(uint8_t rhport)
{
diff --git a/src/portable/synopsys/dwc2/hcd_dwc2.c b/src/portable/synopsys/dwc2/hcd_dwc2.c
index e12e44a41..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 {
@@ -323,9 +324,9 @@ TU_ATTR_ALWAYS_INLINE static inline uint8_t cal_next_pid(uint8_t pid, uint8_t pa
We allocated TX FIFO from top to bottom (using top pointer), this to allow the RX FIFO to grow dynamically which is
possible since the free space is located between the RX and TX FIFOs.
- ----------------- ep_fifo_size
- | HCDMAn |
- |--------------|-- gdfifocfg.EPINFOBASE (max is ghwcfg3.dfifo_depth)
+ ----------------- otg_dfifo_depth
+ | HCDMAn | (DMA only, sized per runtime DMA mode)
+ |--------------|-- gdfifocfg.EPINFOBASE (= gdfifocfg.GDFIFOCfg)
| Non-Periodic |
| TX FIFO |
|--------------|--- GNPTXFSIZ.addr (fixed size)
@@ -358,15 +359,22 @@ static void dfifo_host_init(uint8_t rhport, bool is_hs_phy) {
// Scatter/Gather DMA mode is not yet supported. Buffer DMA only need 1 words per channel
const bool is_dma = dma_host_enabled(dwc2);
- uint16_t dfifo_top = dwc2_controller->ep_fifo_size/4;
+ uint16_t dfifo_top = dwc2_controller->otg_dfifo_depth;
if (is_dma) {
dfifo_top -= ghwcfg2.num_host_ch;
}
// fixed allocation for now, improve later:
- // - ptx_largest is limited to 256 for FS since most FS core only has 1024 bytes total
- uint32_t nptx_largest = is_hs_phy ? TUSB_EPSIZE_BULK_HS / 4 : TUSB_EPSIZE_BULK_FS / 4;
- uint32_t ptx_largest = is_hs_phy ? TUSB_EPSIZE_ISO_HS_MAX / 4 : 256 / 4;
+ // - ptx_largest is limited to 64 words for FS since most FS core only has 256-320 words total
+ uint32_t nptx_largest;
+ uint32_t ptx_largest;
+ if (is_hs_phy) {
+ nptx_largest = TUSB_EPSIZE_BULK_HS / 4;
+ ptx_largest = TUSB_EPSIZE_ISO_HS_MAX / 4;
+ } else {
+ nptx_largest = TUSB_EPSIZE_BULK_FS / 4;
+ ptx_largest = 256 / 4;
+ }
uint16_t nptxfsiz = 2 * nptx_largest;
uint16_t rxfsiz = 2 * (ptx_largest + 2) + ghwcfg2.num_host_ch;
@@ -400,10 +408,12 @@ bool hcd_configure(uint8_t rhport, uint32_t cfg_id, const void* cfg_param) {
// Initialize controller to host mode
bool hcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) {
- dwc2_regs_t* dwc2 = DWC2_REG(rhport);
+ dwc2_clock_init(rhport, rh_init->role);
+
tu_memclr(&_hcd_data, sizeof(_hcd_data));
// Core Initialization
+ dwc2_regs_t* dwc2 = DWC2_REG(rhport);
const bool is_hs_phy = dwc2_core_is_highspeed_phy(dwc2, _tuh_cfg.use_hs_phy);
const bool is_dma = dma_host_enabled(dwc2);
TU_ASSERT(dwc2_core_init(rhport, is_hs_phy, is_dma));
@@ -894,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];
@@ -914,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;
}
@@ -1129,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;
@@ -1195,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);
@@ -1220,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) {
@@ -1243,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) {
@@ -1263,6 +1306,12 @@ static bool handle_channel_out_dma(dwc2_regs_t* dwc2, uint8_t ch_id, uint32_t hc
channel->hcsplt = hcsplt.value;
channel->hcchar |= HCCHAR_CHENA;
}
+ } else if ((hcint & HCINT_NAK) && hcsplt.split_en) {
+ // Split OUT NAK: rewind + retry the start-split, else the channel stalls (Programming Guide 5.1.4.2).
+ // Non-split OUT NAK is core-handled (5.1.2.2), so this is split-only.
+ xfer->err_count = 0;
+ channel_xfer_out_wrapup(dwc2, ch_id);
+ channel_xfer_start(dwc2, ch_id);
}
if (xfer->closing == 1) {
diff --git a/src/portable/wch/ch32_usbfs_reg.h b/src/portable/wch/ch32_usbfs_reg.h
index 68be64f5e..7ffdc6cef 100644
--- a/src/portable/wch/ch32_usbfs_reg.h
+++ b/src/portable/wch/ch32_usbfs_reg.h
@@ -39,59 +39,92 @@
#include <ch32f20x.h>
#elif CFG_TUSB_MCU == OPT_MCU_CH32V103
#include <ch32v10x.h>
+ // Newer-IP layout (separate UEPn_TX_CTRL/UEPn_RX_CTRL). The older IP (CH32V103) has a single
+ // combined control register at the UEPn_TX_CTRL offset, with UEPn_RX_CTRL reserved; the union
+ // exposes that same byte as UEPn_CTRL. Offsets are byte offsets from the peripheral base.
+ // TODO unify into a single struct shared by all WCH USBFS parts.
typedef struct
{
- __IO uint8_t BASE_CTRL;
- __IO uint8_t UDEV_CTRL;
- __IO uint8_t INT_EN;
- __IO uint8_t DEV_ADDR;
- __IO uint8_t Reserve0;
- __IO uint8_t MIS_ST;
- __IO uint8_t INT_FG;
- __IO uint8_t INT_ST;
- __IO uint32_t RX_LEN;
- __IO uint8_t UEP4_1_MOD;
- __IO uint8_t UEP2_3_MOD;
- __IO uint8_t UEP5_6_MOD;
- __IO uint8_t UEP7_MOD;
- __IO uint32_t UEP0_DMA;
- __IO uint32_t UEP1_DMA;
- __IO uint32_t UEP2_DMA;
- __IO uint32_t UEP3_DMA;
- __IO uint32_t UEP4_DMA;
- __IO uint32_t UEP5_DMA;
- __IO uint32_t UEP6_DMA;
- __IO uint32_t UEP7_DMA;
- __IO uint16_t UEP0_TX_LEN;
- __IO uint8_t UEP0_TX_CTRL;
- __IO uint8_t UEP0_RX_CTRL;
- __IO uint16_t UEP1_TX_LEN;
- __IO uint8_t UEP1_TX_CTRL;
- __IO uint8_t UEP1_RX_CTRL;
- __IO uint16_t UEP2_TX_LEN;
- __IO uint8_t UEP2_TX_CTRL;
- __IO uint8_t UEP2_RX_CTRL;
- __IO uint16_t UEP3_TX_LEN;
- __IO uint8_t UEP3_TX_CTRL;
- __IO uint8_t UEP3_RX_CTRL;
- __IO uint16_t UEP4_TX_LEN;
- __IO uint8_t UEP4_TX_CTRL;
- __IO uint8_t UEP4_RX_CTRL;
- __IO uint16_t UEP5_TX_LEN;
- __IO uint8_t UEP5_TX_CTRL;
- __IO uint8_t UEP5_RX_CTRL;
- __IO uint16_t UEP6_TX_LEN;
- __IO uint8_t UEP6_TX_CTRL;
- __IO uint8_t UEP6_RX_CTRL;
- __IO uint16_t UEP7_TX_LEN;
- __IO uint8_t UEP7_TX_CTRL;
- __IO uint8_t UEP7_RX_CTRL;
- __IO uint32_t Reserve1;
- __IO uint32_t OTG_CR;
- __IO uint32_t OTG_SR;
+ __IO uint8_t BASE_CTRL; // 0x00
+ __IO uint8_t UDEV_CTRL; // 0x01
+ __IO uint8_t INT_EN; // 0x02
+ __IO uint8_t DEV_ADDR; // 0x03
+ __IO uint8_t Reserve0; // 0x04
+ __IO uint8_t MIS_ST; // 0x05
+ __IO uint8_t INT_FG; // 0x06
+ __IO uint8_t INT_ST; // 0x07
+ __IO uint32_t RX_LEN; // 0x08
+ __IO uint8_t UEP4_1_MOD; // 0x0C
+ __IO uint8_t UEP2_3_MOD; // 0x0D
+ __IO uint8_t UEP5_6_MOD; // 0x0E
+ __IO uint8_t UEP7_MOD; // 0x0F
+ __IO uint32_t UEP0_DMA; // 0x10
+ __IO uint32_t UEP1_DMA; // 0x14
+ __IO uint32_t UEP2_DMA; // 0x18
+ __IO uint32_t UEP3_DMA; // 0x1C
+ __IO uint32_t UEP4_DMA; // 0x20
+ __IO uint32_t UEP5_DMA; // 0x24
+ __IO uint32_t UEP6_DMA; // 0x28
+ __IO uint32_t UEP7_DMA; // 0x2C
+ __IO uint16_t UEP0_TX_LEN; // 0x30
+ union {
+ __IO uint8_t UEP0_TX_CTRL;
+ __IO uint8_t UEP0_CTRL;
+ }; // 0x32 (TX_CTRL: IN | CTRL: combined)
+ __IO uint8_t UEP0_RX_CTRL; // 0x33 (OUT ctrl; reserved on combined IP)
+ __IO uint16_t UEP1_TX_LEN; // 0x34
+ union {
+ __IO uint8_t UEP1_TX_CTRL;
+ __IO uint8_t UEP1_CTRL;
+ }; // 0x36
+ __IO uint8_t UEP1_RX_CTRL; // 0x37
+ __IO uint16_t UEP2_TX_LEN; // 0x38
+ union {
+ __IO uint8_t UEP2_TX_CTRL;
+ __IO uint8_t UEP2_CTRL;
+ }; // 0x3A
+ __IO uint8_t UEP2_RX_CTRL; // 0x3B
+ __IO uint16_t UEP3_TX_LEN; // 0x3C
+ union {
+ __IO uint8_t UEP3_TX_CTRL;
+ __IO uint8_t UEP3_CTRL;
+ }; // 0x3E
+ __IO uint8_t UEP3_RX_CTRL; // 0x3F
+ __IO uint16_t UEP4_TX_LEN; // 0x40
+ union {
+ __IO uint8_t UEP4_TX_CTRL;
+ __IO uint8_t UEP4_CTRL;
+ }; // 0x42
+ __IO uint8_t UEP4_RX_CTRL; // 0x43
+ __IO uint16_t UEP5_TX_LEN; // 0x44
+ union {
+ __IO uint8_t UEP5_TX_CTRL;
+ __IO uint8_t UEP5_CTRL;
+ }; // 0x46
+ __IO uint8_t UEP5_RX_CTRL; // 0x47
+ __IO uint16_t UEP6_TX_LEN; // 0x48
+ union {
+ __IO uint8_t UEP6_TX_CTRL;
+ __IO uint8_t UEP6_CTRL;
+ }; // 0x4A
+ __IO uint8_t UEP6_RX_CTRL; // 0x4B
+ __IO uint16_t UEP7_TX_LEN; // 0x4C
+ union {
+ __IO uint8_t UEP7_TX_CTRL;
+ __IO uint8_t UEP7_CTRL;
+ }; // 0x4E
+ __IO uint8_t UEP7_RX_CTRL; // 0x4F
+ __IO uint32_t Reserve1; // 0x50
+ __IO uint32_t OTG_CR; // 0x54
+ __IO uint32_t OTG_SR; // 0x58
} USBOTG_FS_TypeDef;
#define USBOTG_FS ((USBOTG_FS_TypeDef *) 0x40023400)
+
+ // CH32V103 has the older USBFS IP: a single combined control register per endpoint
+ // (UEPn_CTRL) instead of separate TX_CTRL/RX_CTRL bytes. The struct's UEPn_TX_CTRL field
+ // aliases that combined register (same address); UEPn_RX_CTRL maps to unused padding.
+ #define CH32_USBFS_EP_CTRL_COMBINED 1
#elif CFG_TUSB_MCU == OPT_MCU_CH32V20X
#include <ch32v20x.h>
#elif CFG_TUSB_MCU == OPT_MCU_CH32V307
@@ -166,6 +199,17 @@
#define USBFS_EP_R_RES_NAK (2 << 0)
#define USBFS_EP_R_RES_STALL (3 << 0)
+#ifdef CH32_USBFS_EP_CTRL_COMBINED
+// Combined per-endpoint control register (older IP, e.g. CH32V103): IN response in
+// bits [1:0], OUT response in bits [3:2], shared auto-toggle, separate IN/OUT toggle.
+#define USBFS_EPC_T_RES_MASK 0x03
+#define USBFS_EPC_R_RES_MASK 0x0C
+#define USBFS_EPC_R_RES_SHIFT 2
+#define USBFS_EPC_AUTO_TOG 0x10
+#define USBFS_EPC_T_TOG 0x40
+#define USBFS_EPC_R_TOG 0x80
+#endif
+
// token PID
#define PID_OUT 0
#define PID_SOF 1
diff --git a/src/portable/wch/dcd_ch32_usbfs.c b/src/portable/wch/dcd_ch32_usbfs.c
index 5cd25e33e..dae31da91 100644
--- a/src/portable/wch/dcd_ch32_usbfs.c
+++ b/src/portable/wch/dcd_ch32_usbfs.c
@@ -29,29 +29,75 @@
#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])
+ #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])
+
+// Endpoint control register access. The newer USBFS IP (CH32V20x/V307/X035) has separate
+// TX_CTRL and RX_CTRL bytes per endpoint; the older IP (CH32V103) has a single combined
+// UEPn_CTRL register. These helpers hide the difference so the rest of the driver is shared.
+// Values use the newer-IP encoding (USBFS_EP_T_*/USBFS_EP_R_*); the combined path remaps them.
+#ifdef CH32_USBFS_EP_CTRL_COMBINED
+ #define EP_CTRL(ep) EP_TX_CTRL(ep) // UEPn_TX_CTRL field aliases the combined UEPn_CTRL register
+
+ static inline uint8_t ep_tx_to_comb(uint8_t v) {
+ uint8_t c = v & USBFS_EP_T_RES_MASK; // IN response: bits [1:0] in both encodings
+ if (v & USBFS_EP_T_TOG) { c |= USBFS_EPC_T_TOG; }
+ if (v & USBFS_EP_T_AUTO_TOG) { c |= USBFS_EPC_AUTO_TOG; }
+ return c;
+ }
+ static inline uint8_t ep_rx_to_comb(uint8_t v) {
+ uint8_t c = (uint8_t) ((v & USBFS_EP_R_RES_MASK) << USBFS_EPC_R_RES_SHIFT); // OUT response -> bits [3:2]
+ if (v & USBFS_EP_R_TOG) { c |= USBFS_EPC_R_TOG; }
+ if (v & USBFS_EP_R_AUTO_TOG) { c |= USBFS_EPC_AUTO_TOG; }
+ return c;
+ }
+ // Set IN side (response/toggle/auto-tog), preserving the OUT response + OUT toggle.
+ static inline void ep_tx_ctrl_set(uint8_t ep, uint8_t v) {
+ EP_CTRL(ep) = (uint8_t) ((EP_CTRL(ep) & (USBFS_EPC_R_RES_MASK | USBFS_EPC_R_TOG)) | ep_tx_to_comb(v));
+ }
+ // Set OUT side, preserving the IN response + IN toggle.
+ static inline void ep_rx_ctrl_set(uint8_t ep, uint8_t v) {
+ EP_CTRL(ep) = (uint8_t) ((EP_CTRL(ep) & (USBFS_EPC_T_RES_MASK | USBFS_EPC_T_TOG)) | ep_rx_to_comb(v));
+ }
+ static inline void ep_tx_set_response(uint8_t ep, uint8_t res) {
+ EP_CTRL(ep) = (uint8_t) ((EP_CTRL(ep) & ~USBFS_EPC_T_RES_MASK) | (res & USBFS_EP_T_RES_MASK));
+ }
+ static inline void ep_rx_set_response(uint8_t ep, uint8_t res) {
+ EP_CTRL(ep) = (uint8_t) ((EP_CTRL(ep) & ~USBFS_EPC_R_RES_MASK) | ((res & USBFS_EP_R_RES_MASK) << USBFS_EPC_R_RES_SHIFT));
+ }
+ #define EP0_SETUP_RX_TOG USBFS_EP_R_TOG // combined IP: data/status stage after SETUP is DATA1
+#else
+ static inline void ep_tx_ctrl_set(uint8_t ep, uint8_t v) { EP_TX_CTRL(ep) = v; }
+ static inline void ep_rx_ctrl_set(uint8_t ep, uint8_t v) { EP_RX_CTRL(ep) = v; }
+ static inline void ep_tx_set_response(uint8_t ep, uint8_t res) {
+ EP_TX_CTRL(ep) = (uint8_t) ((EP_TX_CTRL(ep) & ~USBFS_EP_T_RES_MASK) | res);
+ }
+ static inline void ep_rx_set_response(uint8_t ep, uint8_t res) {
+ EP_RX_CTRL(ep) = (uint8_t) ((EP_RX_CTRL(ep) & ~USBFS_EP_R_RES_MASK) | res);
+ }
+ #define EP0_SETUP_RX_TOG 0
+#endif
/* private data */
struct usb_xfer {
- bool valid;
- 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];
TU_ATTR_ALIGNED(4) uint8_t buffer[EP_MAX][2][64];
TU_ATTR_ALIGNED(4) struct {
@@ -64,7 +110,7 @@ static struct {
/* 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);
@@ -81,25 +127,27 @@ static void update_in(uint8_t rhport, uint8_t ep, bool force) {
EP_TX_LEN(ep) = len;
if (ep == 0) {
- EP_TX_CTRL(0) = USBFS_EP_T_RES_ACK | (data.ep0_tog ? USBFS_EP_T_TOG : 0);
- 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) {
@@ -117,41 +165,49 @@ 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++) {
+ EP_DMA(ep) = (uint32_t)&data.buffer[ep][0];
+ EP_TX_LEN(ep) = 0;
+ ep_tx_ctrl_set(ep, USBFS_EP_T_AUTO_TOG | USBFS_EP_T_RES_NYET);
+ ep_rx_ctrl_set(ep, USBFS_EP_R_AUTO_TOG | USBFS_EP_R_RES_NYET);
+ }
+ EP_DMA(3) = (uint32_t)&data.ep3_buffer.out[0];
+}
+
/* 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;
+ EP_DMA(0) = (uint32_t)&data.buffer[0][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;
USBOTG_FS->UEP5_6_MOD = 0xCC;
- USBOTG_FS->UEP7_MOD = 0x0C;
+ USBOTG_FS->UEP7_MOD = 0x0C;
- 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,15 +215,15 @@ 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 ep = USBFS_INT_ST_MASK_UIS_ENDP(USBOTG_FS->INT_ST);
+ uint8_t token = USBFS_INT_ST_MASK_UIS_TOKEN(USBOTG_FS->INT_ST);
+ uint16_t rx_len = USBOTG_FS->RX_LEN;
switch (token) {
case PID_OUT: {
- uint16_t rx_len = USBOTG_FS->RX_LEN;
update_out(rhport, ep, rx_len);
break;
}
@@ -178,25 +234,32 @@ void dcd_int_handler(uint8_t rhport) {
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;
+
+ const tusb_control_request_t *setup = (const tusb_control_request_t *)&data.buffer[0][TUSB_DIR_OUT][0];
+ // 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);
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) {
@@ -207,148 +270,147 @@ void dcd_int_handler(uint8_t rhport) {
}
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) {
+ USBOTG_FS->DEV_ADDR = (uint8_t)request->wValue;
}
- 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) {
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, USBFS_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, USBFS_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;
- return false;
+ 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;
}
bool dcd_edpt_iso_activate(uint8_t rhport, const tusb_desc_endpoint_t *desc_ep) {
(void)rhport;
(void)desc_ep;
- return false;
+ return true;
}
-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];
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);
}
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 {
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, USBFS_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, USBFS_EP_T_AUTO_TOG | USBFS_EP_T_RES_NAK);
}
}
}
diff --git a/src/portable/wch/dcd_ch32_usbhs.c b/src/portable/wch/dcd_ch32_usbhs.c
index 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 e7d1c0b5b..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,6 +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 \
- src/typec/usbc.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 c75908e87..9170d1205 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
@@ -384,7 +386,7 @@
#endif
#if (CFG_TUSB_MCU == OPT_MCU_RP2040) && !CFG_TUD_RPI_PIO_USB
- #define CFG_TUD_EDPT_DEDICATED_HWFIFO 1
+ #define CFG_TUD_EDPT_DEDICATED_HWFIFO 0
#define CFG_TUSB_FIFO_HWFIFO_DATA_STRIDE 1
#define CFG_TUSB_FIFO_HWFIFO_ADDR_STRIDE 1
#define CFG_TUSB_FIFO_HWFIFO_CUSTOM_WRITE
@@ -537,8 +539,16 @@
#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
- #define CFG_TUSB_OS_HAS_SCHEDULER (CFG_TUSB_OS != OPT_OS_NONE && CFG_TUSB_OS != OPT_OS_PICO)
+ #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
@@ -652,6 +662,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
@@ -821,6 +835,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