From bc37ed6e3e9158a811ed07479638d3db8437344e Mon Sep 17 00:00:00 2001 From: hathach Date: Tue, 6 May 2025 15:23:23 +0700 Subject: [PATCH] usbh: force removed device in the same bus info, before setting address. usbh: move code around hub: queue status endpoint for detach/remove event --- src/host/hub.c | 3 +- src/host/usbh.c | 233 +++++++++++++++++++++++------------------------- 2 files changed, 115 insertions(+), 121 deletions(-) diff --git a/src/host/hub.c b/src/host/hub.c index c87289a14..0ed0e0c42 100644 --- a/src/host/hub.c +++ b/src/host/hub.c @@ -459,7 +459,8 @@ static void process_new_status(tuh_xfer_t* xfer) { } }; hcd_event_handler(&event, false); - processed = true; // usbh queue status after handled this in (de)enumeration + // skip status for attach event, usbh will do it after handled this enumeration + processed = (event.event_id == HCD_EVENT_DEVICE_ATTACH); break; } diff --git a/src/host/usbh.c b/src/host/usbh.c index fbb61e10e..92c254a9e 100644 --- a/src/host/usbh.c +++ b/src/host/usbh.c @@ -34,7 +34,7 @@ #include "hub.h" //--------------------------------------------------------------------+ -// USBH Configuration +// Configuration //--------------------------------------------------------------------+ #ifndef CFG_TUH_TASK_QUEUE_SZ #define CFG_TUH_TASK_QUEUE_SZ 16 @@ -89,7 +89,7 @@ TU_ATTR_WEAK bool hcd_dcache_clean_invalidate(const void* addr, uint32_t data_si } //--------------------------------------------------------------------+ -// USBH-HCD common data structure +// Data Structure //--------------------------------------------------------------------+ typedef struct { tuh_bus_info_t bus_info; @@ -131,8 +131,60 @@ typedef struct { } usbh_device_t; +// sum of end device + hub +#define TOTAL_DEVICES (CFG_TUH_DEVICE_MAX + CFG_TUH_HUB) + +// all devices excluding zero-address +// hub address start from CFG_TUH_DEVICE_MAX+1 +// TODO: hub can has its own simpler struct to save memory +static usbh_device_t _usbh_devices[TOTAL_DEVICES]; + +// Mutex for claiming endpoint +#if OSAL_MUTEX_REQUIRED +static osal_mutex_def_t _usbh_mutexdef; +static osal_mutex_t _usbh_mutex; +#else +#define _usbh_mutex NULL +#endif + +// Event queue: usbh_int_set() is used as mutex in OS NONE config +OSAL_QUEUE_DEF(usbh_int_set, _usbh_qdef, CFG_TUH_TASK_QUEUE_SZ, hcd_event_t); +static osal_queue_t _usbh_q; + +// Control transfers: since most controllers do not support multiple control transfers +// on multiple devices concurrently and control transfers are not used much except for +// enumeration, we will only execute control transfers one at a time. +typedef struct { + uint8_t* buffer; + tuh_xfer_cb_t complete_cb; + uintptr_t user_data; + + volatile uint8_t stage; + uint8_t daddr; + volatile uint16_t actual_len; + uint8_t failed_count; +} usbh_ctrl_xfer_info_t; + +typedef struct { + uint8_t controller_id; // controller ID + 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_data_t; + +static usbh_data_t _usbh_data = { + .controller_id = TUSB_INDEX_INVALID_8, +}; + +typedef struct { + TUH_EPBUF_TYPE_DEF(tusb_control_request_t, request); + TUH_EPBUF_DEF(ctrl, CFG_TUH_ENUMERATION_BUFSIZE); +} usbh_epbuf_t; +CFG_TUH_MEM_SECTION static usbh_epbuf_t _usbh_epbuf; + //--------------------------------------------------------------------+ -// MACRO CONSTANT TYPEDEF +// Class Driver //--------------------------------------------------------------------+ #if CFG_TUSB_DEBUG >= CFG_TUH_LOG_LEVEL #define DRIVER_NAME(_name) _name @@ -235,82 +287,58 @@ static inline usbh_class_driver_t const *get_driver(uint8_t drv_id) { } //--------------------------------------------------------------------+ -// INTERNAL OBJECT & FUNCTION DECLARATION +// Function Inline and Prototypes //--------------------------------------------------------------------+ - -// sum of end device + hub -#define TOTAL_DEVICES (CFG_TUH_DEVICE_MAX + CFG_TUH_HUB) - -// all devices excluding zero-address -// hub address start from CFG_TUH_DEVICE_MAX+1 -// TODO: hub can has its own simpler struct to save memory -static usbh_device_t _usbh_devices[TOTAL_DEVICES]; - -// Mutex for claiming endpoint -#if OSAL_MUTEX_REQUIRED - static osal_mutex_def_t _usbh_mutexdef; - static osal_mutex_t _usbh_mutex; -#else - #define _usbh_mutex NULL -#endif - -// Event queue: usbh_int_set() is used as mutex in OS NONE config -OSAL_QUEUE_DEF(usbh_int_set, _usbh_qdef, CFG_TUH_TASK_QUEUE_SZ, hcd_event_t); -static osal_queue_t _usbh_q; - -// Control transfers: since most controllers do not support multiple control transfers -// on multiple devices concurrently and control transfers are not used much except for -// enumeration, we will only execute control transfers one at a time. -typedef struct { - uint8_t* buffer; - tuh_xfer_cb_t complete_cb; - uintptr_t user_data; - - volatile uint8_t stage; - uint8_t daddr; - volatile uint16_t actual_len; - uint8_t failed_count; -} usbh_ctrl_xfer_info_t; - -typedef struct { - uint8_t controller_id; // controller ID - 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_data_t; - -static usbh_data_t _usbh_data = { - .controller_id = TUSB_INDEX_INVALID_8, -}; - -typedef struct { - TUH_EPBUF_TYPE_DEF(tusb_control_request_t, request); - TUH_EPBUF_DEF(ctrl, CFG_TUH_ENUMERATION_BUFSIZE); -} usbh_epbuf_t; -CFG_TUH_MEM_SECTION static usbh_epbuf_t _usbh_epbuf; - -//------------- Helper Function -------------// -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); - return &_usbh_devices[dev_addr-1]; -} - static bool enum_new_device(hcd_event_t* event); static void process_removed_device(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); +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); + return &_usbh_devices[dev_addr-1]; +} + TU_ATTR_ALWAYS_INLINE static inline bool queue_event(hcd_event_t const * event, bool in_isr) { TU_ASSERT(osal_queue_send(_usbh_q, event, in_isr)); tuh_event_hook_cb(event->rhport, event->event_id, in_isr); return true; } +TU_ATTR_ALWAYS_INLINE static inline void _control_set_xfer_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; + (void) osal_mutex_unlock(_usbh_mutex); + } +} + +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; +} + +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); + } + + // invalidate if enumerating + if (daddr == _usbh_data.enumerating_daddr) { + _usbh_data.enumerating_daddr = TUSB_INDEX_INVALID_8; + } +} + //--------------------------------------------------------------------+ // Device API //--------------------------------------------------------------------+ - bool tuh_mounted(uint8_t dev_addr) { usbh_device_t *dev = get_device(dev_addr); TU_VERIFY(dev); @@ -530,16 +558,16 @@ void tuh_task_ext(uint32_t timeout_ms, bool in_isr) { break; case HCD_EVENT_DEVICE_REMOVE: - TU_LOG_USBH("[%u:%u:%u] USBH DEVICE REMOVED\r\n", event.rhport, event.connection.hub_addr, event.connection.hub_port); - process_removed_device(event.rhport, event.connection.hub_addr, event.connection.hub_port); - - #if CFG_TUH_HUB - // TODO remove - if (event.connection.hub_addr != 0 && event.connection.hub_port != 0) { - // done with hub, waiting for next data on status pipe - (void) hub_edpt_status_xfer(event.connection.hub_addr); + TU_LOG1("[%u:%u:%u] USBH DEVICE REMOVED\r\n", event.rhport, event.connection.hub_addr, event.connection.hub_port); + if (_usbh_data.enumerating_daddr == 0 && + event.rhport == _usbh_data.dev0_bus.rhport && + event.connection.hub_addr == _usbh_data.dev0_bus.hub_addr && + event.connection.hub_port == _usbh_data.dev0_bus.hub_port) { + // dev0 is unplugged while enumerating (not yet assigned an address) + usbh_device_close(_usbh_data.dev0_bus.rhport, 0); + } else { + process_removed_device(event.rhport, event.connection.hub_addr, event.connection.hub_port); } - #endif break; case HCD_EVENT_XFER_COMPLETE: { @@ -623,23 +651,6 @@ static void _control_blocking_complete_cb(tuh_xfer_t* xfer) { *((xfer_result_t*) xfer->user_data) = xfer->result; } -TU_ATTR_ALWAYS_INLINE static inline void _control_set_xfer_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; - (void) osal_mutex_unlock(_usbh_mutex); - } -} - -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; -} - // 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 @@ -1270,21 +1281,8 @@ TU_ATTR_ALWAYS_INLINE static inline bool is_hub_addr(uint8_t daddr) { // a device unplugged from rhport:hub_addr:hub_port static void process_removed_device(uint8_t rhport, uint8_t hub_addr, uint8_t hub_port) { - // if dev0 is unplugged while enumerating (not yet assigned an address) - if (_usbh_data.enumerating_daddr == 0) { - const tuh_bus_info_t* dev0_bus = &_usbh_data.dev0_bus; - if ((rhport == dev0_bus->rhport) && (hub_addr == dev0_bus->hub_addr) && (hub_port == dev0_bus->hub_port)) { - hcd_device_close(dev0_bus->rhport, 0); - if (_usbh_data.ctrl_xfer_info.daddr == 0) { - _control_set_xfer_stage(CONTROL_STAGE_IDLE); - } - _usbh_data.enumerating_daddr = TUSB_INDEX_INVALID_8; - return; - } - } - - //------------- find the all devices (star-network) under port that is unplugged -------------// - uint32_t removing_hubs = 0; + // Find the all devices (star-network) under port that is unplugged + uint32_t removing_hubs_bm = 0; do { for (uint8_t dev_id = 0; dev_id < TOTAL_DEVICES; dev_id++) { usbh_device_t* dev = &_usbh_devices[dev_id]; @@ -1298,7 +1296,7 @@ static void process_removed_device(uint8_t rhport, uint8_t hub_addr, uint8_t hub if (is_hub_addr(daddr)) { TU_LOG_USBH(" is a HUB device %u\r\n", daddr); - removing_hubs |= TU_BIT(dev_id - CFG_TUH_DEVICE_MAX); + removing_hubs_bm |= TU_BIT(dev_id - CFG_TUH_DEVICE_MAX); } else { // Invoke callback before closing driver (maybe call it later ?) if (tuh_umount_cb) { @@ -1314,30 +1312,21 @@ static void process_removed_device(uint8_t rhport, uint8_t hub_addr, uint8_t hub } } - hcd_device_close(rhport, daddr); + usbh_device_close(rhport, daddr); clear_device(dev); - - // abort ongoing control xfer on this device if any - if (daddr == _usbh_data.ctrl_xfer_info.daddr) { - _control_set_xfer_stage(CONTROL_STAGE_IDLE); - } - - if (daddr == _usbh_data.enumerating_daddr) { - _usbh_data.enumerating_daddr = TUSB_INDEX_INVALID_8; - } } } // if removing a hub, we need to remove all of its downstream devices #if CFG_TUH_HUB - if (removing_hubs == 0) { + if (removing_hubs_bm == 0) { break; } // find a marked hub to process for (uint8_t h_id = 0; h_id < CFG_TUH_HUB; h_id++) { - if (tu_bit_test(removing_hubs, h_id)) { - removing_hubs &= ~TU_BIT(h_id); + if (tu_bit_test(removing_hubs_bm, h_id)) { + removing_hubs_bm &= ~TU_BIT(h_id); // update hub_addr and hub_port for next loop hub_addr = h_id + 1 + CFG_TUH_DEVICE_MAX; @@ -1560,6 +1549,10 @@ static void process_enumeration(tuh_xfer_t* xfer) { } case ENUM_SET_ADDR: { + // Due to physical debouncing, some devices can cause multiple attaches (actually reset) without detach event + // Force remove currently mounted with the same bus info (rhport, hub addr, hub port) if exists + process_removed_device(dev0_bus->rhport, dev0_bus->hub_addr, dev0_bus->hub_port); + const tusb_desc_device_t *desc_device = (const tusb_desc_device_t *) _usbh_epbuf.ctrl; const uint8_t new_addr = enum_get_new_address(desc_device->bDeviceClass == TUSB_CLASS_HUB); TU_ASSERT(new_addr != 0,); @@ -1582,7 +1575,7 @@ static void process_enumeration(tuh_xfer_t* xfer) { new_dev->addressed = 1; _usbh_data.enumerating_daddr = new_addr; - hcd_device_close(dev0_bus->rhport, 0); // close dev0 + usbh_device_close(dev0_bus->rhport, 0); // close dev0 TU_ASSERT(usbh_edpt_control_open(new_addr, new_dev->ep0_size),); // open new control endpoint