summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/CMakeLists.txt1
-rw-r--r--src/class/cdc/cdc_device.c4
-rw-r--r--src/class/dfu/dfu_device.c22
-rw-r--r--src/class/vendor/vendor_host.c127
-rw-r--r--src/class/vendor/vendor_host.h48
-rw-r--r--src/common/tusb_private.h2
-rw-r--r--src/device/usbd.c4
-rw-r--r--src/host/usbh.c128
-rw-r--r--src/portable/ohci/ohci.c83
-rw-r--r--src/portable/ohci/ohci.h4
-rw-r--r--src/portable/synopsys/dwc2/hcd_dwc2.c559
-rw-r--r--src/tinyusb.mk1
-rw-r--r--src/tusb.c3
-rw-r--r--src/tusb.h3
-rw-r--r--src/tusb_option.h3
-rw-r--r--src/typec/usbc.c15
-rw-r--r--src/typec/usbc.h1
17 files changed, 603 insertions, 405 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index b3e05f60f..e113f2d88 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -31,7 +31,6 @@ function(tinyusb_sources_get OUTPUT_VAR)
${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
${CMAKE_CURRENT_FUNCTION_LIST_DIR}/typec/usbc.c
PARENT_SCOPE
diff --git a/src/class/cdc/cdc_device.c b/src/class/cdc/cdc_device.c
index 56d4aeed9..ed050ad03 100644
--- a/src/class/cdc/cdc_device.c
+++ b/src/class/cdc/cdc_device.c
@@ -323,9 +323,7 @@ uint16_t cdcd_open(uint8_t rhport, const tusb_desc_interface_t* itf_desc, uint16
tu_edpt_stream_t *stream_tx = &p_cdc->tx_stream;
tu_edpt_stream_open(stream_tx, rhport, desc_ep, CFG_TUD_CDC_TX_EPSIZE);
- #if CFG_TUD_CDC_TX_PERSISTENT
- tu_edpt_stream_write_xfer(stream_tx); // flush pending data
- #else
+ #if !CFG_TUD_CDC_TX_PERSISTENT
tu_edpt_stream_clear(stream_tx);
#endif
} else {
diff --git a/src/class/dfu/dfu_device.c b/src/class/dfu/dfu_device.c
index 006a5bcb7..092abed03 100644
--- a/src/class/dfu/dfu_device.c
+++ b/src/class/dfu/dfu_device.c
@@ -42,9 +42,8 @@ typedef struct {
static dfu_state_ctx_t _dfu_ctx;
-#if CFG_TUD_DFU_XFER_BUFSIZE > CFG_TUD_ENDPOINT0_BUFSIZE
-TU_ATTR_ALIGNED(4) uint8_t _transfer_buf[CFG_TUD_DFU_XFER_BUFSIZE];
-#endif
+// Download data must remain valid across the following GETSTATUS control transfer
+TU_ATTR_ALIGNED(4) static uint8_t _transfer_buf[CFG_TUD_DFU_XFER_BUFSIZE];
static void reset_state(void) {
_dfu_ctx.state = DFU_IDLE;
@@ -52,15 +51,6 @@ static void reset_state(void) {
_dfu_ctx.flashing_in_progress = false;
}
-static inline uint8_t* get_xfer_buffer(void) {
- // Use EP0 buffer if it is large enough, otherwise use dedicated buffer
- #if CFG_TUD_DFU_XFER_BUFSIZE > CFG_TUD_ENDPOINT0_BUFSIZE
- return _transfer_buf;
- #else
- return usbd_get_ctrl_buf();
- #endif
-}
-
static bool reply_getstatus(uint8_t rhport, const tusb_control_request_t* request, dfu_state_t state, dfu_status_t status, uint32_t timeout);
static bool process_download_get_status(uint8_t rhport, uint8_t stage, const tusb_control_request_t* request);
static bool process_manifest_get_status(uint8_t rhport, uint8_t stage, const tusb_control_request_t* request);
@@ -276,10 +266,10 @@ bool dfu_moded_control_xfer_cb(uint8_t rhport, uint8_t stage, const tusb_control
TU_VERIFY(_dfu_ctx.attrs & DFU_ATTR_CAN_UPLOAD);
TU_VERIFY(request->wLength <= CFG_TUD_DFU_XFER_BUFSIZE);
- const uint16_t xfer_len = tud_dfu_upload_cb(_dfu_ctx.alt, request->wValue, get_xfer_buffer(),
+ const uint16_t xfer_len = tud_dfu_upload_cb(_dfu_ctx.alt, request->wValue, _transfer_buf,
request->wLength);
- return tud_control_xfer(rhport, request, get_xfer_buffer(), xfer_len);
+ return tud_control_xfer(rhport, request, _transfer_buf, xfer_len);
}
break;
@@ -299,7 +289,7 @@ bool dfu_moded_control_xfer_cb(uint8_t rhport, uint8_t stage, const tusb_control
if (request->wLength > 0) {
// Download with payload -> transition to DOWNLOAD SYNC
_dfu_ctx.state = DFU_DNLOAD_SYNC;
- return tud_control_xfer(rhport, request, get_xfer_buffer(), request->wLength);
+ return tud_control_xfer(rhport, request, _transfer_buf, request->wLength);
} else {
// Download is complete -> transition to MANIFEST SYNC
_dfu_ctx.state = DFU_MANIFEST_SYNC;
@@ -373,7 +363,7 @@ static bool process_download_get_status(uint8_t rhport, uint8_t stage, const tus
} else if (stage == CONTROL_STAGE_ACK) {
if (_dfu_ctx.flashing_in_progress) {
_dfu_ctx.state = DFU_DNBUSY;
- tud_dfu_download_cb(_dfu_ctx.alt, _dfu_ctx.block, get_xfer_buffer(), _dfu_ctx.length);
+ tud_dfu_download_cb(_dfu_ctx.alt, _dfu_ctx.block, _transfer_buf, _dfu_ctx.length);
} else {
_dfu_ctx.state = DFU_DNLOAD_IDLE;
}
diff --git a/src/class/vendor/vendor_host.c b/src/class/vendor/vendor_host.c
deleted file mode 100644
index dd2c5ac5d..000000000
--- a/src/class/vendor/vendor_host.c
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * SPDX-FileCopyrightText: Copyright (c) 2019 Ha Thach (tinyusb.org)
- * SPDX-License-Identifier: MIT
- *
- * This file is part of the TinyUSB stack.
- */
-
-#include "tusb_option.h"
-
-#if (CFG_TUH_ENABLED && CFG_TUH_VENDOR)
-
-//--------------------------------------------------------------------+
-// INCLUDE
-//--------------------------------------------------------------------+
-#include "host/usbh.h"
-#include "vendor_host.h"
-
-//--------------------------------------------------------------------+
-// MACRO CONSTANT TYPEDEF
-//--------------------------------------------------------------------+
-
-//--------------------------------------------------------------------+
-// INTERNAL OBJECT & FUNCTION DECLARATION
-//--------------------------------------------------------------------+
-custom_interface_info_t custom_interface[CFG_TUH_DEVICE_MAX];
-
-static tusb_error_t cush_validate_paras(uint8_t dev_addr, uint16_t vendor_id, uint16_t product_id, void * p_buffer, uint16_t length)
-{
- if ( !tusbh_custom_is_mounted(dev_addr, vendor_id, product_id) )
- {
- return TUSB_ERROR_DEVICE_NOT_READY;
- }
-
- TU_ASSERT( p_buffer != NULL && length != 0, TUSB_ERROR_INVALID_PARA);
-
- return TUSB_ERROR_NONE;
-}
-//--------------------------------------------------------------------+
-// APPLICATION API (need to check parameters)
-//--------------------------------------------------------------------+
-tusb_error_t tusbh_custom_read(uint8_t dev_addr, uint16_t vendor_id, uint16_t product_id, void * p_buffer, uint16_t length)
-{
- TU_ASSERT_ERR( cush_validate_paras(dev_addr, vendor_id, product_id, p_buffer, length) );
-
- if ( !hcd_pipe_is_idle(custom_interface[dev_addr-1].pipe_in) )
- {
- return TUSB_ERROR_INTERFACE_IS_BUSY;
- }
-
- (void) usbh_edpt_xfer( custom_interface[dev_addr-1].pipe_in, p_buffer, length);
-
- return TUSB_ERROR_NONE;
-}
-
-tusb_error_t tusbh_custom_write(uint8_t dev_addr, uint16_t vendor_id, uint16_t product_id, void const * p_data, uint16_t length)
-{
- TU_ASSERT_ERR( cush_validate_paras(dev_addr, vendor_id, product_id, p_data, length) );
-
- if ( !hcd_pipe_is_idle(custom_interface[dev_addr-1].pipe_out) )
- {
- return TUSB_ERROR_INTERFACE_IS_BUSY;
- }
-
- (void) usbh_edpt_xfer( custom_interface[dev_addr-1].pipe_out, p_data, length);
-
- return TUSB_ERROR_NONE;
-}
-
-//--------------------------------------------------------------------+
-// USBH-CLASS API
-//--------------------------------------------------------------------+
-void cush_init(void)
-{
- tu_memclr(&custom_interface, sizeof(custom_interface_info_t) * CFG_TUH_DEVICE_MAX);
-}
-
-tusb_error_t cush_open_subtask(uint8_t dev_addr, tusb_desc_interface_t const *p_interface_desc, uint16_t *p_length)
-{
- // FIXME quick hack to test lpc1k custom class with 2 bulk endpoints
- uint8_t const *p_desc = (uint8_t const *) p_interface_desc;
- p_desc = tu_desc_next(p_desc);
-
- //------------- Bulk Endpoints Descriptor -------------//
- for(uint32_t i=0; i<2; i++)
- {
- tusb_desc_endpoint_t const *p_endpoint = (tusb_desc_endpoint_t const *) p_desc;
- TU_ASSERT(TUSB_DESC_ENDPOINT == p_endpoint->bDescriptorType, TUSB_ERROR_INVALID_PARA);
-
- pipe_handle_t * p_pipe_hdl = ( p_endpoint->bEndpointAddress & TUSB_DIR_IN_MASK ) ?
- &custom_interface[dev_addr-1].pipe_in : &custom_interface[dev_addr-1].pipe_out;
- *p_pipe_hdl = usbh_edpt_open(dev_addr, p_endpoint, TUSB_CLASS_VENDOR_SPECIFIC);
- TU_ASSERT ( pipehandle_is_valid(*p_pipe_hdl), TUSB_ERROR_HCD_OPEN_PIPE_FAILED );
-
- p_desc = tu_desc_next(p_desc);
- }
-
- (*p_length) = sizeof(tusb_desc_interface_t) + 2*sizeof(tusb_desc_endpoint_t);
- return TUSB_ERROR_NONE;
-}
-
-void cush_isr(pipe_handle_t pipe_hdl, xfer_result_t event)
-{
-
-}
-
-void cush_close(uint8_t dev_addr)
-{
- tusb_error_t err1, err2;
- custom_interface_info_t * p_interface = &custom_interface[dev_addr-1];
-
- // TODO re-consider to check pipe valid before calling pipe_close
- if( pipehandle_is_valid( p_interface->pipe_in ) )
- {
- err1 = hcd_pipe_close( p_interface->pipe_in );
- }
-
- if ( pipehandle_is_valid( p_interface->pipe_out ) )
- {
- err2 = hcd_pipe_close( p_interface->pipe_out );
- }
-
- tu_memclr(p_interface, sizeof(custom_interface_info_t));
-
- TU_ASSERT(err1 == TUSB_ERROR_NONE && err2 == TUSB_ERROR_NONE, (void) 0 );
-}
-
-#endif
diff --git a/src/class/vendor/vendor_host.h b/src/class/vendor/vendor_host.h
deleted file mode 100644
index dc55663b9..000000000
--- a/src/class/vendor/vendor_host.h
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * SPDX-FileCopyrightText: Copyright (c) 2019 Ha Thach (tinyusb.org)
- * SPDX-License-Identifier: MIT
- *
- * This file is part of the TinyUSB stack.
- */
-
-#ifndef TUSB_VENDOR_HOST_H_
-#define TUSB_VENDOR_HOST_H_
-
-#include "common/tusb_common.h"
-
-#ifdef __cplusplus
- extern "C" {
-#endif
-
-typedef struct {
- pipe_handle_t pipe_in;
- pipe_handle_t pipe_out;
-}custom_interface_info_t;
-
-//--------------------------------------------------------------------+
-// USBH-CLASS DRIVER API
-//--------------------------------------------------------------------+
-static inline bool tusbh_custom_is_mounted(uint8_t dev_addr, uint16_t vendor_id, uint16_t product_id)
-{
- (void) vendor_id; // TODO check this later
- (void) product_id;
-// return (tusbh_device_get_mounted_class_flag(dev_addr) & TU_BIT(TUSB_CLASS_MAPPED_INDEX_END-1) ) != 0;
- return false;
-}
-
-bool tusbh_custom_read(uint8_t dev_addr, uint16_t vendor_id, uint16_t product_id, void * p_buffer, uint16_t length);
-bool tusbh_custom_write(uint8_t dev_addr, uint16_t vendor_id, uint16_t product_id, void const * p_data, uint16_t length);
-
-//--------------------------------------------------------------------+
-// Internal Class Driver API
-//--------------------------------------------------------------------+
-void cush_init(void);
-bool cush_open_subtask(uint8_t dev_addr, tusb_desc_interface_t const *p_interface_desc, uint16_t *p_length);
-void cush_isr(pipe_handle_t pipe_hdl, xfer_result_t event);
-void cush_close(uint8_t dev_addr);
-
-#ifdef __cplusplus
- }
-#endif
-
-#endif /* TUSB_VENDOR_HOST_H_ */
diff --git a/src/common/tusb_private.h b/src/common/tusb_private.h
index 0bbc119fd..b91fc0608 100644
--- a/src/common/tusb_private.h
+++ b/src/common/tusb_private.h
@@ -63,7 +63,7 @@ TU_ATTR_ALWAYS_INLINE static inline bool tu_edpt_validate(const tusb_desc_endpoi
// Bind drivers to all interfaces and endpoints in the provided configuration descriptor
bool tu_bind_driver_to_ep_itf(uint8_t driver_id, uint8_t ep2drv[][2], uint8_t itf2drv[], uint8_t itf_max,
- const uint8_t *p_desc, uint16_t desc_len);
+ uint8_t ep_max, const uint8_t *p_desc, uint16_t desc_len);
// Claim an endpoint with provided mutex
bool tu_edpt_claim(volatile uint8_t* ep_state, osal_mutex_t mutex);
diff --git a/src/device/usbd.c b/src/device/usbd.c
index e84d72fa4..21d865cf7 100644
--- a/src/device/usbd.c
+++ b/src/device/usbd.c
@@ -1296,8 +1296,8 @@ static bool process_set_config(uint8_t rhport, uint8_t cfg_num) {
TU_LOG_USBD(" %s opened\r\n", driver->name);
// bind found driver to all interfaces and endpoint within drv_len
- TU_ASSERT(tu_bind_driver_to_ep_itf(drv_id, _usbd_dev.ep2drv, _usbd_dev.itf2drv, CFG_TUD_INTERFACE_MAX, p_desc,
- drv_len));
+ TU_ASSERT(tu_bind_driver_to_ep_itf(drv_id, _usbd_dev.ep2drv, _usbd_dev.itf2drv, CFG_TUD_INTERFACE_MAX,
+ CFG_TUD_ENDPPOINT_MAX, p_desc, drv_len));
p_desc += drv_len; // next Interface
break; // exit driver find loop
diff --git a/src/host/usbh.c b/src/host/usbh.c
index e307bb5e5..94d075813 100644
--- a/src/host/usbh.c
+++ b/src/host/usbh.c
@@ -306,17 +306,6 @@ static usbh_class_driver_t const usbh_class_drivers[] = {
},
#endif
- #if CFG_TUH_VENDOR
- {
- .name = DRIVER_NAME("VENDOR"),
- .init = cush_init,
- .deinit = cush_deinit,
- .open = cush_open,
- .set_config = cush_set_config,
- .xfer_cb = cush_isr,
- .close = cush_close
- }
- #endif
};
// Additional class drivers implemented by application
@@ -397,12 +386,6 @@ TU_ATTR_ALWAYS_INLINE static inline void usbh_device_close(uint8_t rhport, uint8
_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
if (daddr == _usbh_data.enumerating_daddr) {
_usbh_data.enumerating_daddr = TUSB_INDEX_INVALID_8;
@@ -411,6 +394,12 @@ TU_ATTR_ALWAYS_INLINE static inline void usbh_device_close(uint8_t rhport, uint8
_usbh_data.call_after.func = NULL;
}
}
+
+ // 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);
+ }
}
//--------------------------------------------------------------------+
@@ -1111,7 +1100,10 @@ static bool usbh_control_xfer_cb (uint8_t daddr, uint8_t ep_addr, xfer_result_t
// DATA stage: initial data toggle is always 1
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));
+ if (!hcd_edpt_xfer(rhport, daddr, ep_data, ctrl_info->buffer, request->wLength)) {
+ control_xfer_complete(daddr, XFER_RESULT_FAILED);
+ return false;
+ }
return true;
}
TU_ATTR_FALLTHROUGH;
@@ -1126,7 +1118,10 @@ static bool usbh_control_xfer_cb (uint8_t daddr, uint8_t ep_addr, xfer_result_t
// ACK stage: toggle is always 1
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));
+ if (!hcd_edpt_xfer(rhport, daddr, ep_status, NULL, 0)) {
+ control_xfer_complete(daddr, XFER_RESULT_FAILED);
+ return false;
+ }
break;
}
@@ -1728,8 +1723,10 @@ static void enum_delay_async(uintptr_t state) {
if (dev0_bus->hub_addr != 0) {
// connected via hub
TU_VERIFY(dev0_bus->hub_port != 0, );
- TU_ASSERT(hub_port_get_status(dev0_bus->hub_addr, dev0_bus->hub_port, NULL, process_enumeration,
- ENUM_HUB_RERSET), );
+ if (!hub_port_get_status(dev0_bus->hub_addr, dev0_bus->hub_port, NULL, process_enumeration,
+ ENUM_HUB_RERSET)) {
+ enum_full_complete(false);
+ }
} else
#endif
{
@@ -1772,9 +1769,11 @@ static void enum_delay_async(uintptr_t state) {
case ENUM_AFTER_RESET_HUB_DELAY:
case ENUM_AFTER_RESET_HUB_DELAY_RETRY:
// get status after reset complete to check for reset change
- TU_ASSERT(hub_port_get_status(dev0_bus->hub_addr, dev0_bus->hub_port, NULL, process_enumeration,
- state == ENUM_AFTER_RESET_HUB_DELAY ? ENUM_HUB_CLEAR_RESET
- : ENUM_HUB_CLEAR_RESET_RETRY), );
+ if (!hub_port_get_status(dev0_bus->hub_addr, dev0_bus->hub_port, NULL, process_enumeration,
+ state == ENUM_AFTER_RESET_HUB_DELAY ? ENUM_HUB_CLEAR_RESET
+ : ENUM_HUB_CLEAR_RESET_RETRY)) {
+ enum_full_complete(false);
+ }
break;
#endif
@@ -1787,7 +1786,9 @@ static void enum_delay_async(uintptr_t state) {
}
// Get first 8 bytes of device descriptor for control endpoint size
TU_LOG_USBH("Get 8 byte of Device Descriptor\r\n");
- TU_ASSERT(tuh_descriptor_get_device(0, _usbh_epbuf.ctrl, 8, process_enumeration, ENUM_SET_ADDR), );
+ if (!tuh_descriptor_get_device(0, _usbh_epbuf.ctrl, 8, process_enumeration, ENUM_SET_ADDR)) {
+ enum_full_complete(false);
+ }
break;
case ENUM_AFTER_SET_ADDRESS_RECOVERY_DELAY: {
@@ -1796,13 +1797,14 @@ static void enum_delay_async(uintptr_t state) {
TU_ASSERT(new_dev, );
if (!usbh_edpt_control_open(new_addr, new_dev->desc_device.bMaxPacketSize0)) {
TU_LOG_USBH("Failed to open new device's control endpoint\r\n");
- clear_device(new_dev);
enum_full_complete(false);
return;
}
TU_LOG_USBH("Get Device Descriptor\r\n");
- TU_ASSERT(tuh_descriptor_get_device(new_addr, _usbh_epbuf.ctrl, sizeof(tusb_desc_device_t), process_enumeration,
- ENUM_GET_STRING_LANGUAGE_ID_LEN), );
+ if (!tuh_descriptor_get_device(new_addr, _usbh_epbuf.ctrl, sizeof(tusb_desc_device_t), process_enumeration,
+ ENUM_GET_STRING_LANGUAGE_ID_LEN)) {
+ enum_full_complete(false);
+ }
break;
}
@@ -1847,8 +1849,8 @@ static void process_enumeration(tuh_xfer_t *xfer) {
TU_LOG_USBH("Device unplugged from hub while debouncing\r\n");
is_enum_failed = true;
} else {
- TU_ASSERT(hub_port_reset(dev0_bus->hub_addr, dev0_bus->hub_port, process_enumeration,
- ENUM_HUB_RESET_COMPLETE), );
+ is_enum_failed = !hub_port_reset(dev0_bus->hub_addr, dev0_bus->hub_port, process_enumeration,
+ ENUM_HUB_RESET_COMPLETE);
}
break;
}
@@ -1865,8 +1867,8 @@ static void process_enumeration(tuh_xfer_t *xfer) {
if (1 == port_status.change.reset) {
// Acknowledge Port Reset Change
- TU_ASSERT(hub_port_clear_reset_change(dev0_bus->hub_addr, dev0_bus->hub_port, process_enumeration,
- ENUM_HUB_CLEAR_RESET_COMPLETE), );
+ is_enum_failed = !hub_port_clear_reset_change(dev0_bus->hub_addr, dev0_bus->hub_port, process_enumeration,
+ ENUM_HUB_CLEAR_RESET_COMPLETE);
} else if (state == ENUM_HUB_CLEAR_RESET) {
// retry one more time if reset change not set yet
usbh_defer_func_ms_async(ENUM_RESET_HUB_DELAY_MS, enum_delay_async, ENUM_AFTER_RESET_HUB_DELAY_RETRY);
@@ -1911,10 +1913,9 @@ static void process_enumeration(tuh_xfer_t *xfer) {
usbh_device_t* new_dev = get_device(new_addr);
new_dev->bus_info = *dev0_bus;
- new_dev->connected = 1;
new_dev->desc_device.bMaxPacketSize0 = desc_device->bMaxPacketSize0;
- TU_ASSERT(tuh_address_set(0, new_addr, process_enumeration, ENUM_GET_DEVICE_DESC), );
+ is_enum_failed = !tuh_address_set(0, new_addr, process_enumeration, ENUM_GET_DEVICE_DESC);
break;
}
@@ -1922,6 +1923,7 @@ static void process_enumeration(tuh_xfer_t *xfer) {
const uint8_t new_addr = (uint8_t)tu_le16toh(xfer->setup->wValue);
usbh_device_t *new_dev = get_device(new_addr);
TU_ASSERT(new_dev, );
+ new_dev->connected = 1;
new_dev->addressed = 1;
_usbh_data.enumerating_daddr = new_addr;
@@ -1939,15 +1941,15 @@ static void process_enumeration(tuh_xfer_t *xfer) {
memcpy(&dev->desc_device, (const uint8_t*) desc_device + offsetof(tusb_desc_device_t, bcdUSB), sizeof(desc_device_noheader_t));
tuh_enum_descriptor_device_cb(daddr, desc_device); // callback
- tuh_descriptor_get_string_langid(daddr, _usbh_epbuf.ctrl, 2,
- process_enumeration, ENUM_GET_STRING_LANGUAGE_ID);
+ is_enum_failed = !tuh_descriptor_get_string_langid(daddr, _usbh_epbuf.ctrl, 2,
+ process_enumeration, ENUM_GET_STRING_LANGUAGE_ID);
break;
}
case ENUM_GET_STRING_LANGUAGE_ID: {
const uint8_t str_len = xfer->buffer[0];
- tuh_descriptor_get_string_langid(daddr, _usbh_epbuf.ctrl, str_len,
- process_enumeration, ENUM_GET_STRING_MANUFACTURER_LEN);
+ is_enum_failed = !tuh_descriptor_get_string_langid(daddr, _usbh_epbuf.ctrl, str_len,
+ process_enumeration, ENUM_GET_STRING_MANUFACTURER_LEN);
break;
}
@@ -1957,8 +1959,8 @@ static void process_enumeration(tuh_xfer_t *xfer) {
langid = tu_le16toh(desc_langid->utf16le[0]); // previous request is langid
}
if (dev->desc_device.iManufacturer != 0) {
- tuh_descriptor_get_string(daddr, dev->desc_device.iManufacturer, langid, _usbh_epbuf.ctrl, 2,
- process_enumeration, ENUM_GET_STRING_MANUFACTURER);
+ is_enum_failed = !tuh_descriptor_get_string(daddr, dev->desc_device.iManufacturer, langid, _usbh_epbuf.ctrl, 2,
+ process_enumeration, ENUM_GET_STRING_MANUFACTURER);
break;
}
TU_ATTR_FALLTHROUGH;
@@ -1968,8 +1970,8 @@ static void process_enumeration(tuh_xfer_t *xfer) {
if (dev->desc_device.iManufacturer != 0) {
langid = tu_le16toh(xfer->setup->wIndex); // langid from length's request
const uint8_t str_len = xfer->buffer[0];
- tuh_descriptor_get_string(daddr, dev->desc_device.iManufacturer, langid, _usbh_epbuf.ctrl, str_len,
- process_enumeration, ENUM_GET_STRING_PRODUCT_LEN);
+ is_enum_failed = !tuh_descriptor_get_string(daddr, dev->desc_device.iManufacturer, langid, _usbh_epbuf.ctrl,
+ str_len, process_enumeration, ENUM_GET_STRING_PRODUCT_LEN);
break;
}
TU_ATTR_FALLTHROUGH;
@@ -1980,8 +1982,8 @@ static void process_enumeration(tuh_xfer_t *xfer) {
if (state == ENUM_GET_STRING_PRODUCT_LEN) {
langid = tu_le16toh(xfer->setup->wIndex); // get langid from previous setup packet if not fall through
}
- tuh_descriptor_get_string(
- daddr, dev->desc_device.iProduct, langid, _usbh_epbuf.ctrl, 2, process_enumeration, ENUM_GET_STRING_PRODUCT);
+ is_enum_failed = !tuh_descriptor_get_string(daddr, dev->desc_device.iProduct, langid, _usbh_epbuf.ctrl, 2,
+ process_enumeration, ENUM_GET_STRING_PRODUCT);
break;
}
TU_ATTR_FALLTHROUGH;
@@ -1991,8 +1993,8 @@ static void process_enumeration(tuh_xfer_t *xfer) {
if (dev->desc_device.iProduct != 0) {
langid = tu_le16toh(xfer->setup->wIndex); // langid from length's request
const uint8_t str_len = xfer->buffer[0];
- tuh_descriptor_get_string(daddr, dev->desc_device.iProduct, langid, _usbh_epbuf.ctrl, str_len,
- process_enumeration, ENUM_GET_STRING_SERIAL_LEN);
+ is_enum_failed = !tuh_descriptor_get_string(daddr, dev->desc_device.iProduct, langid, _usbh_epbuf.ctrl, str_len,
+ process_enumeration, ENUM_GET_STRING_SERIAL_LEN);
break;
}
TU_ATTR_FALLTHROUGH;
@@ -2003,8 +2005,8 @@ static void process_enumeration(tuh_xfer_t *xfer) {
if (state == ENUM_GET_STRING_SERIAL_LEN) {
langid = tu_le16toh(xfer->setup->wIndex); // get langid from previous setup packet if not fall through
}
- tuh_descriptor_get_string(
- daddr, dev->desc_device.iSerialNumber, langid, _usbh_epbuf.ctrl, 2, process_enumeration, ENUM_GET_STRING_SERIAL);
+ is_enum_failed = !tuh_descriptor_get_string(daddr, dev->desc_device.iSerialNumber, langid, _usbh_epbuf.ctrl, 2,
+ process_enumeration, ENUM_GET_STRING_SERIAL);
break;
}
TU_ATTR_FALLTHROUGH;
@@ -2014,8 +2016,8 @@ static void process_enumeration(tuh_xfer_t *xfer) {
if (dev->desc_device.iSerialNumber != 0) {
langid = tu_le16toh(xfer->setup->wIndex); // langid from length's request
const uint8_t str_len = xfer->buffer[0];
- tuh_descriptor_get_string(daddr, dev->desc_device.iSerialNumber, langid, _usbh_epbuf.ctrl, str_len,
- process_enumeration, ENUM_GET_9BYTE_CONFIG_DESC);
+ is_enum_failed = !tuh_descriptor_get_string(daddr, dev->desc_device.iSerialNumber, langid, _usbh_epbuf.ctrl,
+ str_len, process_enumeration, ENUM_GET_9BYTE_CONFIG_DESC);
break;
}
TU_ATTR_FALLTHROUGH;
@@ -2025,8 +2027,8 @@ static void process_enumeration(tuh_xfer_t *xfer) {
// Get 9-byte for total length
uint8_t const config_idx = 0;
TU_LOG_USBH("Get Configuration[%u] Descriptor (9 bytes)\r\n", config_idx);
- TU_ASSERT(tuh_descriptor_get_configuration(daddr, config_idx, _usbh_epbuf.ctrl, 9,
- process_enumeration, ENUM_GET_FULL_CONFIG_DESC),);
+ is_enum_failed = !tuh_descriptor_get_configuration(daddr, config_idx, _usbh_epbuf.ctrl, 9,
+ process_enumeration, ENUM_GET_FULL_CONFIG_DESC);
break;
}
@@ -2042,21 +2044,21 @@ static void process_enumeration(tuh_xfer_t *xfer) {
// Get full configuration descriptor
uint8_t const config_idx = (uint8_t) tu_le16toh(xfer->setup->wIndex);
TU_LOG_USBH("Get Configuration[%u] Descriptor\r\n", config_idx);
- TU_ASSERT(tuh_descriptor_get_configuration(daddr, config_idx, _usbh_epbuf.ctrl, total_len,
- process_enumeration, ENUM_SET_CONFIG),);
+ is_enum_failed = !tuh_descriptor_get_configuration(daddr, config_idx, _usbh_epbuf.ctrl, total_len,
+ process_enumeration, ENUM_SET_CONFIG);
break;
}
case ENUM_SET_CONFIG: {
uint8_t config_idx = (uint8_t) tu_le16toh(xfer->setup->wIndex);
if (tuh_enum_descriptor_configuration_cb(daddr, config_idx, (const tusb_desc_configuration_t*) _usbh_epbuf.ctrl)) {
- TU_ASSERT(tuh_configuration_set(daddr, config_idx+1u, process_enumeration, ENUM_CONFIG_DRIVER),);
+ is_enum_failed = !tuh_configuration_set(daddr, config_idx+1u, process_enumeration, ENUM_CONFIG_DRIVER);
} else {
config_idx++;
TU_ASSERT(config_idx < dev->desc_device.bNumConfigurations,);
TU_LOG_USBH("Get Configuration[%u] Descriptor (9 bytes)\r\n", config_idx);
- TU_ASSERT(tuh_descriptor_get_configuration(daddr, config_idx, _usbh_epbuf.ctrl, 9,
- process_enumeration, ENUM_GET_FULL_CONFIG_DESC),);
+ is_enum_failed = !tuh_descriptor_get_configuration(daddr, config_idx, _usbh_epbuf.ctrl, 9,
+ process_enumeration, ENUM_GET_FULL_CONFIG_DESC);
}
break;
}
@@ -2160,7 +2162,8 @@ static bool enum_parse_configuration_desc(uint8_t dev_addr, tusb_desc_configurat
TU_LOG_USBH(" %s opened\r\n", driver->name);
// bind found driver to all interfaces and endpoint within drv_len
- tu_bind_driver_to_ep_itf(drv_id, dev->ep2drv, dev->itf2drv, CFG_TUH_INTERFACE_MAX, p_desc, drv_len);
+ TU_ASSERT(tu_bind_driver_to_ep_itf(drv_id, dev->ep2drv, dev->itf2drv, CFG_TUH_INTERFACE_MAX,
+ CFG_TUH_ENDPOINT_MAX, p_desc, drv_len));
p_desc += drv_len; // next Interface
break; // exit driver find loop
@@ -2210,12 +2213,19 @@ void usbh_driver_set_config_complete(uint8_t dev_addr, uint8_t itf_num) {
}
static void enum_full_complete(bool success) {
- (void)success;
TU_LOG_USBH("Enumeration complete: success = %u\r\n", success);
+ const uint8_t daddr = _usbh_data.enumerating_daddr;
_usbh_data.enumerating_daddr = TUSB_INDEX_INVALID_8; // mark enumeration as complete
_usbh_data.call_after.func = NULL;
+ if (!success && daddr <= TOTAL_DEVICES) {
+ usbh_device_close(_usbh_data.dev0_bus.rhport, daddr);
+ if (daddr > 0) {
+ clear_device(get_device(daddr));
+ }
+ }
+
#if CFG_TUH_HUB
// Hub status is already requested in case of successful enumeration
if (!success && _usbh_data.dev0_bus.hub_addr != 0) {
diff --git a/src/portable/ohci/ohci.c b/src/portable/ohci/ohci.c
index e2c5956b3..2a174e46c 100644
--- a/src/portable/ohci/ohci.c
+++ b/src/portable/ohci/ohci.c
@@ -378,7 +378,7 @@ static void ed_list_remove_by_addr(ohci_ed_t * p_head, uint8_t dev_addr) {
ohci_ed_t* p_prev = p_head;
while (p_prev->next) {
- ohci_ed_t* ed = (ohci_ed_t*)_virt_addr((void*)p_prev->next);
+ ohci_ed_t* ed = hcd_dcache_uncached((ohci_ed_t*)_virt_addr((void*)p_prev->next));
if (ed->w0.dev_addr == dev_addr) {
// Prevent Host Controller from processing this ED while we remove it
@@ -387,12 +387,28 @@ static void ed_list_remove_by_addr(ohci_ed_t * p_head, uint8_t dev_addr) {
// unlink ed, will also move up p_prev
p_prev->next = ed->next;
- // point the removed ED's next pointer to list head to make sure HC can always safely move away from this ED
- ed->next = (uint32_t)_phys_addr(p_head);
- ed->w0.used = 0;
- ed->w0.skip = 0;
+ // Control endpoints (EP number 0) are statically allocated with the device which are only reused
+ // after connection of another device long after HC has finished with them now, these can be freed immediately.
+ if (ed->w0.ep_number != 0) {
+ // Wait until the next frame before reclaiming the ED and its TDs. Set the deadline before
+ // publishing is_reclaiming so a pending SOF IRQ cannot use an older deadline for this ED.
+ ohci_data.reclaim_frame = (uint16_t)(OHCI_REG->frame_number + 1);
+ ed->w0.is_reclaiming = 1;
+
+ // 5.2.7.1.2 Removing. Disable list processing for bulk
+ if (p_head == p_ed_head[TUSB_XFER_BULK]) {
+ OHCI_REG->control &= ~OHCI_CONTROL_LIST_BULK_ENABLE_MASK;
+ }
+
+ // Temporarily enable SOF IRQ. Clear any pending SOF first to wait for the next frame.
+ OHCI_REG->interrupt_status = OHCI_INT_SOF_MASK;
+ OHCI_REG->interrupt_enable = OHCI_INT_SOF_MASK;
+ } else {
+ ed->w0.used = 0;
+ ed->w0.skip = 0;
+ }
} else {
- p_prev = (ohci_ed_t*)_virt_addr((void*)p_prev->next);
+ p_prev = ed;
}
}
}
@@ -400,6 +416,7 @@ static void ed_list_remove_by_addr(ohci_ed_t * p_head, uint8_t dev_addr) {
static ohci_gtd_t* gtd_find_free(void) {
for (uint8_t i = 0; i < GTD_MAX; i++) {
if (!ohci_data.gtd_pool[i].used) {
+ ohci_data.gtd_pool[i].used = 1;
return &ohci_data.gtd_pool[i];
}
}
@@ -652,6 +669,60 @@ void hcd_int_handler(uint8_t hostid, bool in_isr) {
// Disable MIE as per OHCI spec 5.3
OHCI_REG->interrupt_disable = OHCI_INT_MASTER_ENABLE_MASK;
+ // Start of frame (SOF). Signed subtraction handles frame number rollover and delayed interrupts.
+ if ((int_status & OHCI_INT_SOF_MASK) &&
+ ((int16_t)((uint16_t)OHCI_REG->frame_number - ohci_data.reclaim_frame) >= 0)) {
+ OHCI_REG->interrupt_disable = OHCI_INT_SOF_MASK;
+
+ bool re_enable_lists = false;
+
+ for (size_t i = 0; i < ED_MAX; i++) {
+ ohci_ed_t* ed = hcd_dcache_uncached(&ohci_data.ed_pool[i]);
+ if (ed->w0.used && ed->w0.is_reclaiming) {
+ TU_ASSERT(ed->w0.skip == 1, );
+ TU_ASSERT(ed->w0.ep_number != 0, );
+
+ // Reclaim orphaned TDs
+ uint32_t td_addr = ed->td_head.address & ~0x0F;
+ while (td_addr) {
+ if (!ed->w0.is_iso) {
+ ohci_gtd_t *gtd = (ohci_gtd_t*)_virt_addr((void*)(uintptr_t)td_addr);
+ gtd->used = 0;
+ } else {
+ // TODO: Free ITD once implemented
+ }
+
+ if (td_addr == ed->td_tail) {
+ break;
+ }
+ td_addr = ((ohci_td_item_t*)_virt_addr((void*)(uintptr_t)td_addr))->next;
+ }
+
+ ed->w0.is_reclaiming = 0;
+ ed->w0.used = 0;
+ ed->w0.skip = 0;
+
+ re_enable_lists = true;
+ }
+ }
+
+ if (re_enable_lists) {
+ // 5.2.7.1.2 Removing
+ // Reset current ED pointers and re-enable lists
+ // Once the next frame has started, the HcControlCurrentED or HcBulkCurrentED register should be adjusted so
+ // that it does not point to the Endpoint Descriptor being removed (for simplicity you may just write
+ // a zero to the register);
+ if (!(OHCI_REG->control & OHCI_CONTROL_LIST_CONTROL_ENABLE_MASK)) {
+ OHCI_REG->control_current_ed = 0;
+ OHCI_REG->control |= OHCI_CONTROL_LIST_CONTROL_ENABLE_MASK;
+ }
+ if (!(OHCI_REG->control & OHCI_CONTROL_LIST_BULK_ENABLE_MASK)) {
+ OHCI_REG->bulk_current_ed = 0;
+ OHCI_REG->control |= OHCI_CONTROL_LIST_BULK_ENABLE_MASK;
+ }
+ }
+ }
+
// Frame number overflow
if (int_status & OHCI_INT_FRAME_OVERFLOW_MASK) {
ohci_data.frame_number_hi++;
diff --git a/src/portable/ohci/ohci.h b/src/portable/ohci/ohci.h
index 84ae04b0f..e28c6404f 100644
--- a/src/portable/ohci/ohci.h
+++ b/src/portable/ohci/ohci.h
@@ -107,7 +107,8 @@ typedef union {
// HCD: make use of 5 reserved bits
uint32_t used : 1;
uint32_t is_interrupt_xfer : 1;
- uint32_t : 3;
+ uint32_t is_reclaiming : 1;
+ uint32_t : 2;
};
uint32_t value;
} ohci_ed_word0_t;
@@ -182,6 +183,7 @@ typedef struct TU_ATTR_ALIGNED(256) {
gtd_extra_data_t gtd_extra[GTD_MAX];
volatile uint16_t frame_number_hi;
+ volatile uint16_t reclaim_frame;
} ohci_data_t;
//--------------------------------------------------------------------+
diff --git a/src/portable/synopsys/dwc2/hcd_dwc2.c b/src/portable/synopsys/dwc2/hcd_dwc2.c
index 089b839ae..5a171f80e 100644
--- a/src/portable/synopsys/dwc2/hcd_dwc2.c
+++ b/src/portable/synopsys/dwc2/hcd_dwc2.c
@@ -26,6 +26,12 @@
#endif
#define DWC2_CHANNEL_COUNT_MAX 16u // absolute max channel count
+
+ // Conservative time budget for enabling a slave-mode periodic OUT channel and writing its first packet before the
+ // current (micro)frame ends. HFNUM.FrRem is measured in PHY clocks; 1024 clocks are 17.1 us at 60 MHz, 21.3 us at
+ // 48 MHz, or 34.1 us at 30 MHz. Defer to SOF when less time remains.
+ #define DWC2_PERIODIC_OUT_MIN_FRREM 1024u
+
TU_VERIFY_STATIC(CFG_TUH_DWC2_ENDPOINT_MAX <= 255, "currently only use 8-bit for index");
enum {
@@ -37,7 +43,9 @@ enum {
};
enum {
- HCD_XFER_PERIOD_SPLIT_NYET_MAX = 3
+ HCD_XFER_PERIOD_SPLIT_NYET_MAX = 3,
+ HCD_FRAME_NUMBER_MASK = 0x3fff,
+ HCD_FRAME_COUNT = HCD_FRAME_NUMBER_MASK + 1
};
//--------------------------------------------------------------------
@@ -56,18 +64,22 @@ typedef struct {
};
struct TU_ATTR_PACKED {
- uint32_t uframe_interval : 18; // micro-frame interval
+ uint32_t uframe_interval : 19; // micro-frame interval
uint32_t speed : 2;
uint32_t next_pid : 2; // PID for next transfer
uint32_t next_do_ping : 1; // Do PING for next transfer if possible (highspeed OUT)
uint32_t closing : 1; // endpoint is closing
- // uint32_t : 8;
+ uint32_t aborting : 1; // periodic DMA channel is waiting for its automatic halt
+ uint32_t periodic_phase : 1; // periodic transfer phase is established
+ uint32_t xfer_pending : 1; // periodic transfer waiting for its service interval
+ // uint32_t : 4;
};
- uint32_t uframe_countdown; // micro-frame count down to transfer for periodic, only need 18-bit
+ uint32_t uframe_countdown; // micro-frame count down to transfer for periodic, only need 19-bit
uint8_t* buffer;
uint16_t buflen;
+ uint16_t periodic_frame; // frame/microframe number of the last scheduled periodic transaction
} hcd_endpoint_t;
// Additional info for each channel when it is active
@@ -86,6 +98,7 @@ typedef struct {
// 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
+ volatile bool aborting; // periodic DMA abort waiting for the channel's automatic halt
} hcd_xfer_t;
typedef struct {
@@ -187,7 +200,7 @@ TU_ATTR_ALWAYS_INLINE static inline bool channel_disable(const dwc2_regs_t* dwc2
// the worst case), the controller generates a channel halted and disables the channel automatically.
// - For split enabled channels (both non-periodic and periodic), channel disable must not be programmed randomly.
// However, channel disable can be programmed for specific scenarios such as NAK and FrmOvrn.
- if (is_period && (channel->hcsplt & HCSPLT_SPLITEN)) {
+ if (is_period) {
return true;
}
} else {
@@ -200,13 +213,86 @@ TU_ATTR_ALWAYS_INLINE static inline bool channel_disable(const dwc2_regs_t* dwc2
return true;
}
-// attempt to send IN token to receive data
-TU_ATTR_ALWAYS_INLINE static inline bool channel_send_in_token(const dwc2_regs_t* dwc2, dwc2_channel_t* channel) {
+// Retire all active host channels on root-port disconnect without waiting for
+// Channel Halted interrupts.
+// stop new channel/FIFO interrupts, flush queued slave requests, request a
+// halt for enabled channels, then clear their interrupt and software state.
+static void channel_cleanup_on_disconnect(dwc2_regs_t *dwc2) {
+ const uint32_t xfer_ints = GINTSTS_NPTX_FIFO_EMPTY | GINTSTS_PTX_FIFO_EMPTY | GINTSTS_HCINT;
+ dwc2->gintmsk &= ~xfer_ints;
+ dwc2->gintsts = xfer_ints;
+ dwc2->haintmsk = 0;
+
+ const uint8_t max_channel = dwc2_channel_count(dwc2);
+ #if CFG_TUH_DWC2_SLAVE_ENABLE
+ if (!dma_host_enabled(dwc2)) {
+ // With CHENA clear, CHDIS flushes a posted request without consuming
+ // request-queue space. Clear EPDIR as required for this flush operation.
+ for (uint8_t ch_id = 0; ch_id < max_channel; ch_id++) {
+ if (_hcd_data.xfer[ch_id].allocated) {
+ dwc2_channel_t *channel = &dwc2->channel[ch_id];
+ const uint32_t hcchar = channel->hcchar;
+ if (hcchar & HCCHAR_CHENA) {
+ channel->hcchar = (hcchar & ~(HCCHAR_CHENA | HCCHAR_EPDIR)) | HCCHAR_CHDIS;
+ }
+ }
+ }
+ }
+ #endif
+
+ for (uint8_t ch_id = 0; ch_id < max_channel; ch_id++) {
+ if (_hcd_data.xfer[ch_id].allocated) {
+ dwc2_channel_t *channel = &dwc2->channel[ch_id];
+ const uint32_t hcchar = channel->hcchar;
+ if (hcchar & HCCHAR_CHENA) {
+ channel->hcchar = hcchar | HCCHAR_CHDIS;
+ }
+ channel->hcintmsk = 0;
+ channel->hcint = 0xFFFFFFFFU;
+ }
+ }
+
+ tu_memclr(_hcd_data.xfer, sizeof(_hcd_data.xfer));
+ for (uint8_t ep_id = 0; ep_id < CFG_TUH_DWC2_ENDPOINT_MAX; ep_id++) {
+ hcd_endpoint_t *edpt = &_hcd_data.edpt[ep_id];
+ if (edpt->hcchar_bm.enable) {
+ edpt->closing = 1;
+ edpt->xfer_pending = 0;
+ }
+ }
+}
+
+// Enable a channel, selecting the following frame for a new periodic transfer.
+// Return that frame from the same HFNUM sample used for ODDFRM selection.
+// Clear CHDIS explicitly: a halted channel may retain it in HCCHAR.
+TU_ATTR_ALWAYS_INLINE static inline uint16_t channel_enable(dwc2_regs_t* dwc2, dwc2_channel_t* channel,
+ bool next_periodic_frame) {
+ uint32_t hcchar = channel->hcchar & ~HCCHAR_CHDIS;
+ uint16_t periodic_frame = 0;
+ if (next_periodic_frame) {
+ // Prevent the USB interrupt from consuming the selected frame before
+ // HCCHAR.CHENA is written. Queue-space waits happen before this helper.
+ const uint32_t gahbcfg = dwc2->gahbcfg;
+ dwc2->gahbcfg = gahbcfg & ~GAHBCFG_GINT;
+ const uint32_t hfnum = dwc2->hfnum;
+ hcchar = (hcchar & ~HCCHAR_ODDFRM) | (((hfnum & 1u) ^ 1u) << HCCHAR_ODDFRM_Pos);
+ channel->hcchar = hcchar | HCCHAR_CHENA;
+ periodic_frame = (uint16_t) ((hfnum + 1u) & HCD_FRAME_NUMBER_MASK);
+ dwc2->gahbcfg = gahbcfg;
+ } else {
+ channel->hcchar = hcchar | HCCHAR_CHENA;
+ }
+ return periodic_frame;
+}
+
+// Attempt to send an IN token to receive data. For a new periodic transfer,
+// select its frame only after request-queue space is available.
+TU_ATTR_ALWAYS_INLINE static inline uint16_t channel_send_in_token(dwc2_regs_t* dwc2, dwc2_channel_t* channel,
+ bool next_periodic_frame) {
while (0 == req_queue_avail(dwc2, channel_is_periodic(channel->hcchar))) {
// blocking wait for request queue available
}
- channel->hcchar |= HCCHAR_CHENA;
- return true;
+ return channel_enable(dwc2, channel, next_periodic_frame);
}
// Find currently enabled channel. Note: EP0 is bidirectional
@@ -262,11 +348,13 @@ static void edpt_close(dwc2_regs_t *dwc2, uint8_t ep_id) {
// Find an endpoint that is opened previously with hcd_edpt_open()
// Note: EP0 is bidirectional
-TU_ATTR_ALWAYS_INLINE static inline uint8_t edpt_find_opened(uint8_t dev_addr, uint8_t ep_num, uint8_t ep_dir) {
+TU_ATTR_ALWAYS_INLINE static inline uint8_t edpt_find_opened(uint8_t dev_addr, uint8_t ep_num, uint8_t ep_dir,
+ bool include_closing) {
for (uint8_t i = 0; i < (uint8_t)CFG_TUH_DWC2_ENDPOINT_MAX; i++) {
const hcd_endpoint_t *edpt = &_hcd_data.edpt[i];
const dwc2_channel_char_t hcchar_bm = edpt->hcchar_bm;
- if (hcchar_bm.enable && hcchar_bm.dev_addr == dev_addr && hcchar_bm.ep_num == ep_num &&
+ if (hcchar_bm.enable && (include_closing || !edpt->closing) && hcchar_bm.dev_addr == dev_addr &&
+ hcchar_bm.ep_num == ep_num &&
(ep_num == 0 || hcchar_bm.ep_dir == ep_dir)) {
return i;
}
@@ -336,13 +424,13 @@ TU_ATTR_ALWAYS_INLINE static inline uint8_t cal_next_pid(uint8_t pid, uint8_t pa
static void dfifo_host_init(uint8_t rhport, bool is_hs_phy) {
const dwc2_controller_t* dwc2_controller = &_dwc2_controller[rhport];
dwc2_regs_t* dwc2 = DWC2_REG(rhport);
- const dwc2_ghwcfg2_t ghwcfg2 = {.value = dwc2->ghwcfg2};
+ const uint8_t channel_count = dwc2_channel_count(dwc2);
// 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->otg_dfifo_depth;
if (is_dma) {
- dfifo_top -= ghwcfg2.num_host_ch;
+ dfifo_top -= channel_count;
}
// fixed allocation for now, improve later:
@@ -358,13 +446,12 @@ static void dfifo_host_init(uint8_t rhport, bool is_hs_phy) {
}
uint16_t nptxfsiz = 2 * nptx_largest;
- uint16_t rxfsiz = 2 * (ptx_largest + 2) + ghwcfg2.num_host_ch;
+ uint16_t rxfsiz = 2 * (ptx_largest + 2) + channel_count;
TU_ASSERT(dfifo_top >= (nptxfsiz + rxfsiz),);
uint16_t ptxfsiz = dfifo_top - (nptxfsiz + rxfsiz);
dwc2->gdfifocfg = (dfifo_top << GDFIFOCFG_EPINFOBASE_SHIFT) | dfifo_top;
- dfifo_top -= rxfsiz;
dwc2->grxfsiz = rxfsiz;
dfifo_top -= nptxfsiz;
@@ -548,7 +635,7 @@ bool hcd_edpt_open(uint8_t rhport, uint8_t dev_addr, const tusb_desc_endpoint_t*
edpt->next_pid = HCTSIZ_PID_DATA0;
switch (desc_ep->bmAttributes.xfer) {
case TUSB_XFER_ISOCHRONOUS:
- edpt->uframe_interval = 1 << (desc_ep->bInterval - 1);
+ edpt->uframe_interval = 1u << (desc_ep->bInterval - 1);
if (bus_info.speed == TUSB_SPEED_FULL) {
edpt->uframe_interval <<= 3;
}
@@ -556,7 +643,7 @@ bool hcd_edpt_open(uint8_t rhport, uint8_t dev_addr, const tusb_desc_endpoint_t*
case TUSB_XFER_INTERRUPT:
if (bus_info.speed == TUSB_SPEED_HIGH) {
- edpt->uframe_interval = 1 << (desc_ep->bInterval - 1);
+ edpt->uframe_interval = 1u << (desc_ep->bInterval - 1);
} else {
edpt->uframe_interval = desc_ep->bInterval << 3;
}
@@ -566,6 +653,13 @@ bool hcd_edpt_open(uint8_t rhport, uint8_t dev_addr, const tusb_desc_endpoint_t*
break;
}
+ if (channel_is_periodic(edpt->hcchar)) {
+ // HFNUM cannot distinguish elapsed periods longer than one counter cycle. USB permits the host to provide a
+ // shorter period, so bound the selected period to the history available from HFNUM.
+ const uint32_t ucount = (rh_speed == TUSB_SPEED_HIGH) ? 1u : 8u;
+ edpt->uframe_interval = tu_min32(edpt->uframe_interval, HCD_FRAME_COUNT * ucount);
+ }
+
return true;
}
@@ -573,7 +667,7 @@ bool hcd_edpt_close(uint8_t rhport, uint8_t daddr, uint8_t ep_addr) {
dwc2_regs_t *dwc2 = DWC2_REG(rhport);
const uint8_t ep_num = tu_edpt_number(ep_addr);
const uint8_t ep_dir = tu_edpt_dir(ep_addr);
- const uint8_t ep_id = edpt_find_opened(daddr, ep_num, ep_dir);
+ const uint8_t ep_id = edpt_find_opened(daddr, ep_num, ep_dir, true);
TU_ASSERT(ep_id < CFG_TUH_DWC2_ENDPOINT_MAX);
edpt_close(dwc2, ep_id);
@@ -588,7 +682,10 @@ static void channel_xfer_out_wrapup(dwc2_regs_t* dwc2, uint8_t ch_id) {
hcd_endpoint_t* edpt = &_hcd_data.edpt[xfer->ep_id];
const dwc2_channel_tsize_t hctsiz = {.value = channel->hctsiz};
- edpt->next_pid = hctsiz.pid; // save PID
+ const dwc2_channel_char_t hcchar = {.value = channel->hcchar};
+ if (hcchar.ep_type != HCCHAR_EPTYPE_ISOCHRONOUS) {
+ edpt->next_pid = hctsiz.pid; // save PID
+ }
/* Since hctsiz.xfersize field reflects the number of bytes transferred via the AHB, not the USB)
* For IN: we can use hctsiz.xfersize as remaining bytes.
@@ -597,7 +694,6 @@ static void channel_xfer_out_wrapup(dwc2_regs_t* dwc2, uint8_t ch_id) {
* transfer was halted before its normal completion.
*/
const uint16_t remain_packets = hctsiz.packet_count;
- const dwc2_channel_char_t hcchar = {.value = channel->hcchar};
const uint16_t total_packets = cal_packet_count(edpt->buflen, hcchar.ep_size);
const uint16_t actual_bytes = (total_packets - remain_packets) * hcchar.ep_size;
@@ -607,20 +703,26 @@ static void channel_xfer_out_wrapup(dwc2_regs_t* dwc2, uint8_t ch_id) {
edpt->buflen -= actual_bytes;
}
-static bool channel_xfer_start(dwc2_regs_t* dwc2, uint8_t ch_id) {
+#if CFG_TUH_DWC2_SLAVE_ENABLE
+static bool channel_txfifo_write(dwc2_regs_t* dwc2, uint8_t ch_id, bool is_periodic);
+#endif
+static void periodic_xfer_defer(dwc2_regs_t* dwc2, hcd_endpoint_t* edpt, uint32_t uframe_countdown);
+
+static bool channel_xfer_start(dwc2_regs_t* dwc2, uint8_t ch_id, bool defer_periodic_out) {
hcd_xfer_t* xfer = &_hcd_data.xfer[ch_id];
hcd_endpoint_t* edpt = &_hcd_data.edpt[xfer->ep_id];
dwc2_channel_char_t* hcchar_bm = &edpt->hcchar_bm;
dwc2_channel_t* channel = &dwc2->channel[ch_id];
bool const is_period = channel_is_periodic(edpt->hcchar);
-
+#if CFG_TUH_DWC2_SLAVE_ENABLE
+ const uint8_t saved_pid = edpt->next_pid;
+ const uint8_t saved_do_ping = edpt->next_do_ping;
+#endif
+ uint16_t periodic_frame = 0;
// clear previous state
xfer->fifo_bytes = 0;
// hchar: restore but don't enable yet
- if (is_period) {
- hcchar_bm->odd_frame = 1 - (dwc2->hfnum & 1); // transfer on next frame
- }
channel->hcchar = (edpt->hcchar & ~HCCHAR_CHENA);
// hctsiz: zero length packet still count as 1
@@ -636,15 +738,17 @@ static bool channel_xfer_start(dwc2_regs_t* dwc2, uint8_t ch_id) {
channel->hctsiz = hctsiz.value;
edpt->next_do_ping = 0;
- // pre-calculate next PID based on packet count, adjusted in transfer complete interrupt if short packet
+ // Single-transaction isochronous endpoints always use DATA0. Pre-calculate the next PID for other endpoints,
+ // adjusted in the transfer-complete interrupt if a short packet is received.
if (hcchar_bm->ep_num == 0) {
edpt->next_pid = HCTSIZ_PID_DATA1; // control data and status stage always start with DATA1
- } else {
+ } else if (hcchar_bm->ep_type != HCCHAR_EPTYPE_ISOCHRONOUS) {
edpt->next_pid = cal_next_pid(edpt->next_pid, packet_count);
}
channel->hcsplt = edpt->hcsplt;
channel->hcint = 0xFFFFFFFFU; // clear all channel interrupts
+ dwc2->gintmsk |= GINTSTS_HCINT;
if (dma_host_enabled(dwc2)) {
channel->hcintmsk = HCINT_HALTED;
@@ -653,13 +757,19 @@ static bool channel_xfer_start(dwc2_regs_t* dwc2, uint8_t ch_id) {
channel->hcdma = (uint32_t) edpt->buffer;
if (hcchar_bm->ep_dir == TUSB_DIR_IN) {
- channel_send_in_token(dwc2, channel);
+ periodic_frame = channel_send_in_token(dwc2, channel, is_period);
} else {
hcd_dcache_clean(edpt->buffer, edpt->buflen);
- channel->hcchar |= HCCHAR_CHENA;
+ periodic_frame = channel_enable(dwc2, channel, is_period);
+ }
+ }
+#if CFG_TUH_DWC2_SLAVE_ENABLE
+ else {
+ uint32_t hcintmsk = HCINT_NAK | HCINT_XACT_ERR | HCINT_STALL |
+ HCINT_XFER_COMPLETE | HCINT_DATATOGGLE_ERR;
+ if (is_period) {
+ hcintmsk |= HCINT_FARME_OVERRUN;
}
- } else {
- uint32_t hcintmsk = HCINT_NAK | HCINT_XACT_ERR | HCINT_STALL | HCINT_XFER_COMPLETE | HCINT_DATATOGGLE_ERR;
if (hcchar_bm->ep_dir == TUSB_DIR_IN) {
hcintmsk |= HCINT_BABBLE_ERR | HCINT_DATATOGGLE_ERR | HCINT_ACK;
} else {
@@ -677,16 +787,36 @@ static bool channel_xfer_start(dwc2_regs_t* dwc2, uint8_t ch_id) {
// IN Token. If we got NAK, we have to re-enable the channel again in the interrupt. Due to the way usbh stack only
// call hcd_edpt_xfer() once, we will need to manage de-allocate/re-allocate IN channel dynamically.
if (hcchar_bm->ep_dir == TUSB_DIR_IN) {
- channel_send_in_token(dwc2, channel);
+ periodic_frame = channel_send_in_token(dwc2, channel, is_period);
} else {
- channel->hcchar |= HCCHAR_CHENA;
- if (edpt->buflen > 0) {
- // To prevent conflict with other channel, we will enable periodic/non-periodic FIFO empty interrupt accordingly
- // And write packet in the interrupt handler
+ // The final FIFO word creates the OUT request. Keep CHENA and that write
+ // atomic with respect to this controller's ISR.
+ // This region never waits for FIFO or queue space.
+ const uint32_t gahbcfg = dwc2->gahbcfg;
+ dwc2->gahbcfg = gahbcfg & ~GAHBCFG_GINT;
+ if (defer_periodic_out && is_period) {
+ const dwc2_hfnum_t hfnum = {.value = dwc2->hfnum};
+ if (hfnum.remainning < DWC2_PERIODIC_OUT_MIN_FRREM) {
+ edpt->next_pid = saved_pid;
+ edpt->next_do_ping = saved_do_ping;
+ dwc2->gahbcfg = gahbcfg;
+ return false;
+ }
+ }
+ periodic_frame = channel_enable(dwc2, channel, is_period);
+ if (edpt->buflen > 0 && channel_txfifo_write(dwc2, ch_id, is_period)) {
+ // The FIFO-empty interrupt handles only work that did not fit in the
+ // initial synchronous write.
dwc2->gintmsk |= (is_period ? GINTSTS_PTX_FIFO_EMPTY : GINTSTS_NPTX_FIFO_EMPTY);
}
+ dwc2->gahbcfg = gahbcfg;
}
}
+#endif
+
+ if (is_period && defer_periodic_out) {
+ edpt->periodic_frame = periodic_frame;
+ }
return true;
}
@@ -698,8 +828,48 @@ static bool edpt_xfer_kickoff(dwc2_regs_t* dwc2, uint8_t ep_id) {
hcd_xfer_t* xfer = &_hcd_data.xfer[ch_id];
xfer->ep_id = ep_id;
xfer->result = XFER_RESULT_INVALID;
+ hcd_endpoint_t* edpt = &_hcd_data.edpt[ep_id];
+ const bool result = channel_xfer_start(dwc2, ch_id, true);
+ if (!result) {
+ channel_dealloc(dwc2, ch_id);
+ periodic_xfer_defer(dwc2, edpt, 0);
+ return true;
+ }
+ if (channel_is_periodic(_hcd_data.edpt[ep_id].hcchar)) {
+ edpt->periodic_phase = 1;
+ edpt->xfer_pending = 0;
+ }
+ return result;
+}
+
+static uint32_t periodic_xfer_countdown(dwc2_regs_t* dwc2, hcd_endpoint_t const* edpt) {
+ const uint32_t ucount = (hprt_speed_get(dwc2) == TUSB_SPEED_HIGH) ? 1u : 8u;
+ const uint16_t frame = (uint16_t) (dwc2->hfnum & HCD_FRAME_NUMBER_MASK);
+ const uint16_t elapsed_frames = (uint16_t) (frame - edpt->periodic_frame) & HCD_FRAME_NUMBER_MASK;
+ const uint32_t elapsed_uframes = (uint32_t) elapsed_frames * ucount;
+
+ if (elapsed_uframes < edpt->uframe_interval) {
+ return edpt->uframe_interval - elapsed_uframes - ucount;
+ }
+
+ // The service opportunity was missed. Keep the established phase and use
+ // the next interval rather than starting a new interval from this request.
+ return edpt->uframe_interval - (elapsed_uframes % edpt->uframe_interval) - ucount;
+}
+
+static void periodic_xfer_defer(dwc2_regs_t* dwc2, hcd_endpoint_t* edpt, uint32_t uframe_countdown) {
+ const uint32_t gahbcfg = dwc2->gahbcfg;
+ dwc2->gahbcfg = gahbcfg & ~GAHBCFG_GINT;
- return channel_xfer_start(dwc2, ch_id);
+ edpt->uframe_countdown = uframe_countdown;
+ edpt->xfer_pending = 1;
+
+ if (0 == (dwc2->gintmsk & GINTMSK_SOFM)) {
+ dwc2->gintsts = GINTSTS_SOF;
+ dwc2->gintmsk |= GINTMSK_SOFM;
+ }
+
+ dwc2->gahbcfg = gahbcfg;
}
bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t * buffer, uint16_t buflen) {
@@ -707,10 +877,10 @@ bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t *
const uint8_t ep_num = tu_edpt_number(ep_addr);
const uint8_t ep_dir = tu_edpt_dir(ep_addr);
- uint8_t ep_id = edpt_find_opened(dev_addr, ep_num, ep_dir);
- TU_ASSERT(ep_id < CFG_TUH_DWC2_ENDPOINT_MAX);
+ uint8_t ep_id = edpt_find_opened(dev_addr, ep_num, ep_dir, false);
+ TU_VERIFY(ep_id < CFG_TUH_DWC2_ENDPOINT_MAX);
hcd_endpoint_t *edpt = &_hcd_data.edpt[ep_id];
- TU_VERIFY(edpt->closing == 0); // skip if endpoint is closing
+ TU_VERIFY(edpt->closing == 0 && edpt->aborting == 0); // skip if endpoint is closing or aborting
edpt->buffer = buffer;
edpt->buflen = buflen;
@@ -720,6 +890,26 @@ bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t *
edpt->hcchar_bm.ep_dir = ep_dir;
}
+ if (channel_is_periodic(edpt->hcchar)) {
+ const uint32_t ucount = (hprt_speed_get(dwc2) == TUSB_SPEED_HIGH) ? 1u : 8u;
+#if CFG_TUH_DWC2_SLAVE_ENABLE
+ // Establish a slower slave-mode OUT schedule from SOF. bInterval=1 must be queued immediately to avoid
+ // losing every other service opportunity.
+ if (!dma_host_enabled(dwc2) && ep_dir == TUSB_DIR_OUT && !edpt->periodic_phase &&
+ edpt->uframe_interval > ucount) {
+ periodic_xfer_defer(dwc2, edpt, 0);
+ return true;
+ }
+#endif
+ if (edpt->periodic_phase && edpt->uframe_interval > ucount) {
+ const uint32_t countdown = periodic_xfer_countdown(dwc2, edpt);
+ if (countdown > 0) {
+ periodic_xfer_defer(dwc2, edpt, countdown);
+ return true;
+ }
+ }
+ }
+
return edpt_xfer_kickoff(dwc2, ep_id);
}
@@ -729,11 +919,39 @@ bool hcd_edpt_abort_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr) {
dwc2_regs_t* dwc2 = DWC2_REG(rhport);
const uint8_t ep_num = tu_edpt_number(ep_addr);
const uint8_t ep_dir = tu_edpt_dir(ep_addr);
- const uint8_t ep_id = edpt_find_opened(dev_addr, ep_num, ep_dir);
+ const uint8_t ep_id = edpt_find_opened(dev_addr, ep_num, ep_dir, false);
TU_VERIFY(ep_id < CFG_TUH_DWC2_ENDPOINT_MAX);
+ hcd_endpoint_t* edpt = &_hcd_data.edpt[ep_id];
+
+ hcd_int_disable(rhport);
+
+ const bool xfer_pending = edpt->xfer_pending;
+ if (xfer_pending) {
+ edpt->xfer_pending = 0;
+ edpt->uframe_countdown = 0;
+ }
+
+ if (xfer_pending) {
+ hcd_int_enable(rhport);
+ return true;
+ }
+
+ // A periodic DMA channel must halt naturally at the next service boundary. Prevent a replacement transfer until the
+ // halt ISR retires the channel, and suppress completion for the aborted transfer.
+ if (dma_host_enabled(dwc2) && channel_is_periodic(edpt->hcchar)) {
+ const uint8_t ch_id = channel_find_enabled(dwc2, dev_addr, ep_num, ep_dir);
+ if (ch_id < 16) {
+ hcd_xfer_t* xfer = &_hcd_data.xfer[ch_id];
+ edpt->aborting = 1;
+ xfer->aborting = true;
+ hcd_int_enable(rhport);
+ return true;
+ }
+ }
- // hcd_int_disable(rhport);
+ hcd_int_enable(rhport);
+ // Channel disable may wait for request-queue space in slave mode.
// Find enabled channeled and disable it, channel will be de-allocated in the interrupt handler
const uint8_t ch_id = channel_find_enabled(dwc2, dev_addr, ep_num, ep_dir);
if (ch_id < 16) {
@@ -741,15 +959,13 @@ bool hcd_edpt_abort_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr) {
channel_disable(dwc2, channel);
}
- // hcd_int_enable(rhport);
-
return true;
}
// Submit a special transfer to send 8-byte Setup Packet, when complete hcd_event_xfer_complete() must be invoked
bool hcd_setup_send(uint8_t rhport, uint8_t dev_addr, const uint8_t setup_packet[8]) {
- uint8_t ep_id = edpt_find_opened(dev_addr, 0, TUSB_DIR_OUT);
- TU_ASSERT(ep_id < CFG_TUH_DWC2_ENDPOINT_MAX); // no opened endpoint
+ uint8_t ep_id = edpt_find_opened(dev_addr, 0, TUSB_DIR_OUT, false);
+ TU_VERIFY(ep_id < CFG_TUH_DWC2_ENDPOINT_MAX); // endpoint can close asynchronously on disconnect
hcd_endpoint_t* edpt = &_hcd_data.edpt[ep_id];
edpt->next_pid = HCTSIZ_PID_SETUP;
@@ -761,7 +977,7 @@ bool hcd_edpt_clear_stall(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr) {
(void) rhport;
const uint8_t ep_num = tu_edpt_number(ep_addr);
const uint8_t ep_dir = tu_edpt_dir(ep_addr);
- const uint8_t ep_id = edpt_find_opened(dev_addr, ep_num, ep_dir);
+ const uint8_t ep_id = edpt_find_opened(dev_addr, ep_num, ep_dir, false);
TU_VERIFY(ep_id < CFG_TUH_DWC2_ENDPOINT_MAX);
hcd_endpoint_t* edpt = &_hcd_data.edpt[ep_id];
@@ -790,7 +1006,7 @@ static void channel_xfer_in_retry(dwc2_regs_t* dwc2, uint8_t ch_id, uint32_t hci
if (xfer->period_split_nyet_count < HCD_XFER_PERIOD_SPLIT_NYET_MAX) {
hcchar.odd_frame = 1 - (dwc2->hfnum & 1); // transfer on next frame
channel->hcchar = hcchar.value;
- channel_send_in_token(dwc2, channel);
+ channel_send_in_token(dwc2, channel, false);
return;
} else {
// too many NYET, de-allocate channel with below code
@@ -803,23 +1019,20 @@ static void channel_xfer_in_retry(dwc2_regs_t* dwc2, uint8_t ch_id, uint32_t hci
// retry on next frame if bInterval is 1
hcchar.odd_frame = 1 - (dwc2->hfnum & 1);
channel->hcchar = hcchar.value;
- channel_send_in_token(dwc2, channel);
+ channel_send_in_token(dwc2, channel, false);
} else {
// otherwise, de-allocate channel, enable SOF set frame counter for later transfer
const dwc2_channel_tsize_t hctsiz = {.value = channel->hctsiz};
- edpt->next_pid = hctsiz.pid; // save PID
- edpt->uframe_countdown = edpt->uframe_interval - ucount;
- // enable SOF interrupt if not already enabled
- if (0 == (dwc2->gintmsk & GINTMSK_SOFM)) {
- dwc2->gintsts = GINTSTS_SOF;
- dwc2->gintmsk |= GINTMSK_SOFM;
+ if (hcchar.ep_type != HCCHAR_EPTYPE_ISOCHRONOUS) {
+ edpt->next_pid = hctsiz.pid; // save PID
}
+ periodic_xfer_defer(dwc2, edpt, periodic_xfer_countdown(dwc2, edpt));
// already halted, de-allocate channel (called from DMA isr)
channel_dealloc(dwc2, ch_id);
}
} else {
// for control/bulk: retry immediately
- channel_send_in_token(dwc2, channel);
+ channel_send_in_token(dwc2, channel, false);
}
}
@@ -854,6 +1067,13 @@ static void handle_rxflvl_irq(uint8_t rhport) {
// In packet received, pop this entry --> ACK interrupt
const uint16_t byte_count = grxstsp.byte_count;
hcd_xfer_t* xfer = &_hcd_data.xfer[ch_id];
+ if (!xfer->allocated) {
+ // Discard data for a channel retired by disconnect.
+ for (uint16_t count = 0; count < byte_count; count += sizeof(uint32_t)) {
+ (void) dwc2->fifo[0][0];
+ }
+ break;
+ }
TU_ASSERT(xfer->ep_id < CFG_TUH_DWC2_ENDPOINT_MAX,);
hcd_endpoint_t* edpt = &_hcd_data.edpt[xfer->ep_id];
@@ -883,38 +1103,50 @@ static void handle_rxflvl_irq(uint8_t rhport) {
}
}
-// return true if there is still pending data and need more ISR
+// Return true if data remains for a later FIFO-empty interrupt.
+static bool channel_txfifo_write(dwc2_regs_t* dwc2, uint8_t ch_id, bool is_periodic) {
+ hcd_xfer_t* xfer = &_hcd_data.xfer[ch_id];
+ dwc2_channel_t* channel = &dwc2->channel[ch_id];
+ const dwc2_channel_char_t hcchar = {.value = channel->hcchar};
+ TU_ASSERT(xfer->ep_id < CFG_TUH_DWC2_ENDPOINT_MAX);
+ hcd_endpoint_t* edpt = &_hcd_data.edpt[xfer->ep_id];
+ const dwc2_channel_tsize_t hctsiz = {.value = channel->hctsiz};
+ const uint16_t remain_packets = hctsiz.packet_count;
+
+ for (uint16_t i = 0; i < remain_packets; i++) {
+ const uint16_t remain_bytes = edpt->buflen - xfer->fifo_bytes;
+ const uint16_t xact_bytes = tu_min16(remain_bytes, hcchar.ep_size);
+
+ // The packet's last FIFO word creates its request-queue entry.
+ // HNPTXSTS differs by one request-queue bit, which is outside these fields.
+ 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;
+ }
+
+ tu_hwfifo_write(dwc2->fifo[ch_id], edpt->buffer + xfer->fifo_bytes, xact_bytes, NULL);
+ xfer->fifo_bytes += xact_bytes;
+ }
+
+ return false;
+}
+
+// Return true if at least one matching channel needs another interrupt.
static bool handle_txfifo_empty(dwc2_regs_t* dwc2, bool is_periodic) {
const uint8_t max_channel = dwc2_channel_count(dwc2);
for (uint8_t ch_id = 0; ch_id < max_channel; ch_id++) {
+ hcd_xfer_t* xfer = &_hcd_data.xfer[ch_id];
dwc2_channel_t* channel = &dwc2->channel[ch_id];
const dwc2_channel_char_t hcchar = {.value = channel->hcchar};
- // skip writing to FIFO if channel is expecting halted.
- if (0 == (channel->hcintmsk & HCINT_HALTED) && (hcchar.ep_dir == TUSB_DIR_OUT)) {
- hcd_xfer_t *xfer = &_hcd_data.xfer[ch_id];
- TU_ASSERT(xfer->ep_id < CFG_TUH_DWC2_ENDPOINT_MAX);
- hcd_endpoint_t* edpt = &_hcd_data.edpt[xfer->ep_id];
- const dwc2_channel_tsize_t hctsiz = {.value = channel->hctsiz};
- const uint16_t remain_packets = hctsiz.packet_count;
- for (uint16_t i = 0; i < remain_packets; i++) {
- const uint16_t remain_bytes = edpt->buflen - xfer->fifo_bytes;
- const uint16_t xact_bytes = tu_min16(remain_bytes, hcchar.ep_size);
-
- // 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;
- }
-
- tu_hwfifo_write(dwc2->fifo[ch_id], edpt->buffer + xfer->fifo_bytes, xact_bytes, NULL);
- xfer->fifo_bytes += xact_bytes;
+ if (xfer->allocated && channel_is_periodic(hcchar.value) == is_periodic &&
+ 0 == (channel->hcintmsk & HCINT_HALTED) && hcchar.ep_dir == TUSB_DIR_OUT) {
+ if (channel_txfifo_write(dwc2, ch_id, is_periodic)) {
+ return true;
}
}
}
- return false; // no channel has pending data
+ return false;
}
static bool handle_channel_in_slave(dwc2_regs_t* dwc2, uint8_t ch_id, uint32_t hcint) {
@@ -932,7 +1164,8 @@ static bool handle_channel_in_slave(dwc2_regs_t* dwc2, uint8_t ch_id, uint32_t h
// }
if (hcint & HCINT_XFER_COMPLETE) {
- if (edpt->hcchar_bm.ep_num != 0) {
+ if (edpt->hcchar_bm.ep_num != 0 &&
+ edpt->hcchar_bm.ep_type != HCCHAR_EPTYPE_ISOCHRONOUS) {
edpt->next_pid = hctsiz.pid; // save pid (already toggled)
}
@@ -945,6 +1178,17 @@ static bool handle_channel_in_slave(dwc2_regs_t* dwc2, uint8_t ch_id, uint32_t h
xfer->result = XFER_RESULT_SUCCESS;
}
+ if (channel_is_periodic(channel->hcchar) && remain_packets == 0) {
+ // The core has already halted a completed periodic IN channel. Complete
+ // it now so the next interval can be submitted without another halt IRQ.
+ is_done = true;
+ } else {
+ channel_disable(dwc2, channel);
+ }
+ } else if (hcint & HCINT_FARME_OVERRUN) {
+ if (edpt->hcchar_bm.ep_type == HCCHAR_EPTYPE_ISOCHRONOUS) {
+ xfer->result = XFER_RESULT_FAILED;
+ }
channel_disable(dwc2, channel);
} else if (hcint & (HCINT_XACT_ERR | HCINT_BABBLE_ERR | HCINT_STALL)) {
if (hcint & HCINT_STALL) {
@@ -982,7 +1226,7 @@ static bool handle_channel_in_slave(dwc2_regs_t* dwc2, uint8_t ch_id, uint32_t h
channel->hcintmsk |= HCINT_NYET;
hcsplt.split_compl = 1;
channel->hcsplt = hcsplt.value;
- channel_send_in_token(dwc2, channel);
+ channel_send_in_token(dwc2, channel, false);
} else {
// do nothing for complete split with DATA, this will trigger XferComplete and handled there
}
@@ -993,7 +1237,7 @@ static bool handle_channel_in_slave(dwc2_regs_t* dwc2, uint8_t ch_id, uint32_t h
// still more packet to receive, also reset to start split
hcsplt.split_compl = 0;
channel->hcsplt = hcsplt.value;
- channel_send_in_token(dwc2, channel);
+ channel_send_in_token(dwc2, channel, false);
}
}
} else if (hcint & HCINT_HALTED) {
@@ -1039,6 +1283,12 @@ static bool handle_channel_out_slave(dwc2_regs_t* dwc2, uint8_t ch_id, uint32_t
} else if (hcint & HCINT_STALL) {
xfer->result = XFER_RESULT_STALLED;
channel_disable(dwc2, channel);
+ } else if (hcint & HCINT_FARME_OVERRUN) {
+ channel_xfer_out_wrapup(dwc2, ch_id);
+ if (edpt->hcchar_bm.ep_type == HCCHAR_EPTYPE_ISOCHRONOUS) {
+ xfer->result = XFER_RESULT_FAILED;
+ }
+ channel_disable(dwc2, channel);
} else if (hcint & HCINT_NYET) {
xfer->err_count = 0;
if (hcsplt.split_en == 1u) {
@@ -1074,7 +1324,7 @@ static bool handle_channel_out_slave(dwc2_regs_t* dwc2, uint8_t ch_id, uint32_t
is_done = true;
} else {
// Got here due to NAK or NYET
- TU_ASSERT(channel_xfer_start(dwc2, ch_id));
+ TU_ASSERT(channel_xfer_start(dwc2, ch_id, false));
}
} else if (hcint & HCINT_ACK) {
xfer->err_count = 0;
@@ -1126,9 +1376,13 @@ static bool handle_channel_in_dma(dwc2_regs_t* dwc2, uint8_t ch_id, uint32_t hci
if (xfer->closing) {
is_done = true;
} else {
- channel_send_in_token(dwc2, channel);
+ channel_send_in_token(dwc2, channel, false);
}
} else if (hcint & (HCINT_XFER_COMPLETE | HCINT_STALL | HCINT_BABBLE_ERR)) {
+ if (edpt->hcchar_bm.ep_num != 0 && (hcint & HCINT_XFER_COMPLETE)) {
+ edpt->next_pid = hctsiz.pid; // save pid (already toggled)
+ }
+
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;
@@ -1187,7 +1441,7 @@ static bool handle_channel_in_dma(dwc2_regs_t* dwc2, uint8_t ch_id, uint32_t hci
hcchar.odd_frame = 1 - (dwc2->hfnum & 1); // transfer on next frame
channel->hcchar = hcchar.value;
}
- channel_send_in_token(dwc2, channel);
+ channel_send_in_token(dwc2, channel, false);
}
} else if (hcint & (HCINT_NAK | HCINT_DATATOGGLE_ERR)) {
xfer->err_count = 0;
@@ -1204,8 +1458,12 @@ static bool handle_channel_in_dma(dwc2_regs_t* dwc2, uint8_t ch_id, uint32_t hci
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);
+ if (hcchar.ep_type == HCCHAR_EPTYPE_ISOCHRONOUS) {
+ xfer->result = XFER_RESULT_FAILED;
+ is_done = true;
+ } else {
+ channel_xfer_in_retry(dwc2, ch_id, hcint);
+ }
}
if (xfer->closing == 1) {
@@ -1234,7 +1492,7 @@ static bool handle_channel_out_dma(dwc2_regs_t* dwc2, uint8_t ch_id, uint32_t hc
if (xfer->closing) {
is_done = true;
} else {
- channel_xfer_start(dwc2, ch_id);
+ channel_xfer_start(dwc2, ch_id, false);
}
} else if (hcint & (HCINT_XFER_COMPLETE | HCINT_STALL)) {
is_done = true;
@@ -1248,30 +1506,38 @@ static bool handle_channel_out_dma(dwc2_regs_t* dwc2, uint8_t ch_id, uint32_t hc
}
channel->hcintmsk &= ~HCINT_ACK;
} else if (hcint & HCINT_XACT_ERR) {
- if (hcint & (HCINT_NAK | HCINT_NYET | HCINT_ACK)) {
- xfer->err_count = 0;
- // clean up transfer so far and start again
- channel_xfer_out_wrapup(dwc2, ch_id);
- channel_xfer_start(dwc2, ch_id);
- } else {
- xfer->err_count++;
- if (xfer->err_count >= HCD_XFER_ERROR_MAX) {
- xfer->result = XFER_RESULT_FAILED;
- is_done = true;
- } else {
- // 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);
- if (hcsplt.split_en && !channel_is_periodic(channel->hcchar)) {
- xfer->retry_disabled = 1;
- channel_disable(dwc2, channel);
- } else {
- channel_xfer_start(dwc2, ch_id);
- }
- }
- }
+ if (hcint & (HCINT_NAK | HCINT_NYET | HCINT_ACK)) {
+ xfer->err_count = 0;
+ // clean up transfer so far and start again
+ channel_xfer_out_wrapup(dwc2, ch_id);
+ channel_xfer_start(dwc2, ch_id, false);
+ } else {
+ xfer->err_count++;
+ if (xfer->err_count >= HCD_XFER_ERROR_MAX) {
+ xfer->result = XFER_RESULT_FAILED;
+ is_done = true;
+ } else {
+ // 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);
+ if (hcsplt.split_en && !channel_is_periodic(channel->hcchar)) {
+ xfer->retry_disabled = 1;
+ channel_disable(dwc2, channel);
+ } else {
+ channel_xfer_start(dwc2, ch_id, false);
+ }
+ }
+ }
+ } else if (hcint & HCINT_FARME_OVERRUN) {
+ channel_xfer_out_wrapup(dwc2, ch_id);
+ if (edpt->hcchar_bm.ep_type == HCCHAR_EPTYPE_ISOCHRONOUS) {
+ xfer->result = XFER_RESULT_FAILED;
+ is_done = true;
+ } else {
+ channel_xfer_start(dwc2, ch_id, false);
+ }
} else if (hcint & HCINT_NYET) {
if (hcsplt.split_en && hcsplt.split_compl) {
// split not yet mean hub has no data, retry complete split
@@ -1292,7 +1558,7 @@ static bool handle_channel_out_dma(dwc2_regs_t* dwc2, uint8_t ch_id, uint32_t hc
// 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);
+ channel_xfer_start(dwc2, ch_id, false);
}
if (xfer->closing == 1) {
@@ -1320,7 +1586,29 @@ static void handle_channel_irq(uint8_t rhport, bool in_isr) {
dwc2_channel_char_t hcchar = {.value = channel->hcchar};
const uint32_t hcint = channel->hcint;
- channel->hcint = hcint; // clear interrupt
+ // Slave handlers process one cause per pass. If ChHltd arrived with
+ // another cause, leave it pending so the next pass retires the halt.
+ const uint32_t hcint_clear = (!is_dma && (hcint & ~HCINT_HALTED)) ? (hcint & ~HCINT_HALTED) : hcint;
+ channel->hcint = hcint_clear;
+
+ if (is_dma && xfer->aborting && (hcint & HCINT_HALTED)) {
+ hcd_endpoint_t* edpt = &_hcd_data.edpt[xfer->ep_id];
+ const bool closing = xfer->closing;
+ // channel_xfer_start() predicts the PID after all requested packets;
+ // an aborted transfer may have completed fewer.
+ if (hcchar.ep_type != HCCHAR_EPTYPE_ISOCHRONOUS) {
+ const dwc2_channel_tsize_t hctsiz = {.value = channel->hctsiz};
+ edpt->next_pid = hctsiz.pid;
+ }
+ xfer->aborting = false;
+ channel_dealloc(dwc2, ch_id);
+ if (closing) {
+ edpt_dealloc(edpt);
+ } else {
+ edpt->aborting = 0;
+ }
+ continue;
+ }
bool is_done = false;
if (is_dma) {
@@ -1373,15 +1661,17 @@ static bool handle_sof_irq(uint8_t rhport, bool in_isr) {
for(uint8_t ep_id = 0; ep_id < CFG_TUH_DWC2_ENDPOINT_MAX; ep_id++) {
hcd_endpoint_t *edpt = &_hcd_data.edpt[ep_id];
if (edpt->closing == 0) {
- if (edpt->hcchar_bm.enable && channel_is_periodic(edpt->hcchar) && edpt->uframe_countdown > 0) {
- edpt->uframe_countdown -= tu_min32(ucount, edpt->uframe_countdown);
+ if (edpt->hcchar_bm.enable && channel_is_periodic(edpt->hcchar) && edpt->xfer_pending) {
+ if (edpt->uframe_countdown > 0) {
+ edpt->uframe_countdown -= tu_min32(ucount, edpt->uframe_countdown);
+ }
if (edpt->uframe_countdown == 0) {
if (!edpt_xfer_kickoff(dwc2, ep_id)) {
edpt->uframe_countdown = ucount; // failed to start, try again next frame
}
}
- more_isr = true;
+ more_isr = more_isr || edpt->xfer_pending;
}
}
}
@@ -1501,25 +1791,23 @@ void hcd_int_handler(uint8_t rhport, bool in_isr) {
}
}
- if (gintsts & GINTSTS_HPRTINT) {
- // Host port interrupt: source is cleared in HPRT register
- // TU_LOG1_HEX(dwc2->hprt);
- handle_hprt_irq(rhport, in_isr);
- }
-
- if (gintsts & GINTSTS_HCINT) {
- // Host Channel interrupt: source is cleared in HCINT register
- // must be handled after TX FIFO empty
- handle_channel_irq(rhport, in_isr);
- }
-
if (gintsts & GINTSTS_DISCINT) {
- // Device disconnected
dwc2->gintsts = GINTSTS_DISCINT;
+ channel_cleanup_on_disconnect(dwc2);
+ hcd_event_device_remove(rhport, in_isr);
- if (0 == (dwc2->hprt & HPRT_CONN_STATUS)) {
- hcd_event_device_remove(rhport, in_isr);
+ // A fast replug can be visible without a pending connect-detect interrupt.
+ const uint32_t hprt = dwc2->hprt;
+ if (!(hprt & HPRT_CONN_DETECT) && (hprt & HPRT_CONN_STATUS)) {
+ hcd_event_device_attach(rhport, in_isr);
}
+ return;
+ }
+
+ if (gintsts & GINTSTS_HPRTINT) {
+ // Host port interrupt: source is cleared in HPRT register
+ // TU_LOG1_HEX(dwc2->hprt);
+ handle_hprt_irq(rhport, in_isr);
}
#if CFG_TUH_DWC2_SLAVE_ENABLE
@@ -1553,6 +1841,13 @@ void hcd_int_handler(uint8_t rhport, bool in_isr) {
}
}
#endif
+
+ // Draining the RxFIFO completion status can assert HCINT.XferCompl. Read
+ // the live status here so the completion is handled in this ISR invocation.
+ if ((dwc2->gintsts & dwc2->gintmsk) & GINTSTS_HCINT) {
+ handle_channel_irq(rhport, in_isr);
+ }
+
}
#endif
diff --git a/src/tinyusb.mk b/src/tinyusb.mk
index 365043927..941791670 100644
--- a/src/tinyusb.mk
+++ b/src/tinyusb.mk
@@ -26,4 +26,3 @@ TINYUSB_SRC_C += \
src/class/midi/midi_host.c \
src/class/midi/midi2_host.c \
src/class/msc/msc_host.c \
- src/class/vendor/vendor_host.c \
diff --git a/src/tusb.c b/src/tusb.c
index 78ee7aeda..e1548e8c1 100644
--- a/src/tusb.c
+++ b/src/tusb.c
@@ -274,7 +274,7 @@ bool tu_edpt_validate(const tusb_desc_endpoint_t *desc_ep, tusb_speed_t speed) {
#endif
bool tu_bind_driver_to_ep_itf(uint8_t driver_id, uint8_t ep2drv[][2], uint8_t itf2drv[], uint8_t itf_max,
- const uint8_t *p_desc, uint16_t desc_len) {
+ uint8_t ep_max, const uint8_t *p_desc, uint16_t desc_len) {
const uint8_t *desc_end = p_desc + desc_len;
while (tu_desc_in_bounds(p_desc, desc_end)) {
const uint8_t desc_type = tu_desc_type(p_desc);
@@ -283,6 +283,7 @@ bool tu_bind_driver_to_ep_itf(uint8_t driver_id, uint8_t ep2drv[][2], uint8_t it
const uint8_t ep_addr = ((const tusb_desc_endpoint_t *)p_desc)->bEndpointAddress;
const uint8_t ep_num = tu_edpt_number(ep_addr);
const uint8_t ep_dir = tu_edpt_dir(ep_addr);
+ TU_ASSERT(ep_num < ep_max);
ep2drv[ep_num][ep_dir] = driver_id;
} else if (desc_type == TUSB_DESC_INTERFACE) {
const tusb_desc_interface_t *desc_itf = (const tusb_desc_interface_t *)p_desc;
diff --git a/src/tusb.h b/src/tusb.h
index 6a30f7c13..cdf6f8171 100644
--- a/src/tusb.h
+++ b/src/tusb.h
@@ -48,9 +48,6 @@
#include "class/midi/midi2_host.h"
#endif
- #if CFG_TUH_VENDOR
- #include "class/vendor/vendor_host.h"
- #endif
#else
#ifndef tuh_int_handler
#define tuh_int_handler(...)
diff --git a/src/tusb_option.h b/src/tusb_option.h
index 24f802b73..1eb23fb00 100644
--- a/src/tusb_option.h
+++ b/src/tusb_option.h
@@ -894,9 +894,6 @@
#define CFG_TUH_MSC 0
#endif
-#ifndef CFG_TUH_VENDOR
- #define CFG_TUH_VENDOR 0
-#endif
#ifndef CFG_TUH_API_EDPT_XFER
#define CFG_TUH_API_EDPT_XFER 0
diff --git a/src/typec/usbc.c b/src/typec/usbc.c
index dc59b35be..5b0e4423a 100644
--- a/src/typec/usbc.c
+++ b/src/typec/usbc.c
@@ -31,6 +31,7 @@ static bool _usbc_inited = false;
// if port is initialized
static bool _port_inited[TUP_TYPEC_RHPORTS_NUM];
+static bool _port_attached[TUP_TYPEC_RHPORTS_NUM];
// Max possible PD size is 262 bytes
static uint8_t _rx_buf[64] TU_ATTR_ALIGNED(4);
@@ -57,6 +58,11 @@ TU_ATTR_WEAK bool tuc_pd_control_received_cb(uint8_t rhport, pd_header_t const*
return false;
}
+TU_ATTR_WEAK void tuc_attach_changed_cb(uint8_t rhport, bool attached) {
+ (void) rhport;
+ (void) attached;
+}
+
TU_ATTR_WEAK void tcd_connect(uint8_t rhport) {
(void) rhport;
}
@@ -90,6 +96,7 @@ bool tuc_init(uint8_t rhport, uint32_t port_type) {
// Initialize stack
if (!_usbc_inited) {
tu_memclr(_port_inited, sizeof(_port_inited));
+ tu_memclr(_port_attached, sizeof(_port_attached));
_usbc_q = osal_queue_create(&_usbc_qdef);
TU_ASSERT(_usbc_q != NULL);
@@ -124,8 +131,14 @@ void tuc_task_ext(uint32_t timeout_ms, bool in_isr) {
if (!osal_queue_receive(_usbc_q, &event, timeout_ms)) return;
switch (event.event_id) {
- case TCD_EVENT_CC_CHANGED:
+ case TCD_EVENT_CC_CHANGED: {
+ bool const attached = event.cc_changed.cc_state[0] != 0 || event.cc_changed.cc_state[1] != 0;
+ if (_port_attached[event.rhport] != attached) {
+ _port_attached[event.rhport] = attached;
+ tuc_attach_changed_cb(event.rhport, attached);
+ }
break;
+ }
case TCD_EVENT_RX_COMPLETE:
// TODO process message here in ISR, move to thread later
diff --git a/src/typec/usbc.h b/src/typec/usbc.h
index 9fca7da0d..fc4773b07 100644
--- a/src/typec/usbc.h
+++ b/src/typec/usbc.h
@@ -65,6 +65,7 @@ extern void tcd_int_handler(uint8_t rhport);
bool tuc_pd_data_received_cb(uint8_t rhport, pd_header_t const* header, uint8_t const* dobj, uint8_t const* p_end);
bool tuc_pd_control_received_cb(uint8_t rhport, pd_header_t const* header);
+void tuc_attach_changed_cb(uint8_t rhport, bool attached);
//--------------------------------------------------------------------+
//