Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion docs/reference/class/host.rst
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,10 @@ Common configuration options
* - ``CFG_TUH_ENUM_ATTEMPT_MAX``
- ``2``
- Maximum enumeration attempts per attachment after core enumeration
transfers fail or time out; ``1`` disables retries.
transfers fail or time out; ``1`` disables retries. Each attempt resets
the port. Within an attempt, a request the device fails is first sent
again up to twice, 100 ms apart, without a port reset; a timed-out
request is not.
* - ``CFG_TUH_ENUM_RESET_ROOT_POST_DELAY_MS``
- ``2``
- Delay in milliseconds after a root-port reset before reading the port
Expand Down
115 changes: 90 additions & 25 deletions src/host/usbh.c
Original file line number Diff line number Diff line change
Expand Up @@ -221,13 +221,16 @@ TU_FIFO_DEF(_usbh_pending_ctrl_q, CFG_TUH_CONTROL_PENDING_QUEUE_SZ * sizeof(usbh
typedef struct {
uint8_t enumerating_daddr; // device address of the device being enumerated
uint8_t enum_attempt; // enumeration attempts used for the current attach
uint8_t enum_xfer_retry; // resends used for the current enumeration request, see enum_xfer_retry()
uint8_t enum_gen; // bumped when an enumeration attempt starts, carried by its requests, see enum_ud()
uint8_t attach_debouncing_bm; // bitmask for roothub port attach debouncing
uint8_t attach_teardown; // an attach event is tearing down what was on its port
uint8_t root_retry_count; // root port re-enumerations used for the current attachment
uint8_t root_retry_rhport; // the port root_retry_count belongs to
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;
usbh_pending_ctrl_t enum_xfer; // the failed enumeration request enum_xfer_retry() sends again
// 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
Expand Down Expand Up @@ -1871,8 +1874,13 @@ enum { // USB 2.0 specs 7.1.7 for timing
ENUM_RESET_ROOT_DELAY_MS = 50, // T(DRSTr) minimum 50 ms for reset from root port
ENUM_RESET_ROOT_POST_DELAY_MS = CFG_TUH_ENUM_RESET_ROOT_POST_DELAY_MS, // delay after root port reset before getting speed/status
ENUM_RESET_HUB_DELAY_MS = 20, // T(DRST) 10-20 ms for hub reset
ENUM_RESET_RECOVERY_DELAY_MS = 10, // T(RSTRCY) minimum 10 ms for reset recovery
ENUM_SET_ADDRESS_RECOVERY_DELAY_MS = 2, // USB 2.0 Spec 9.2.6.3 min is 2 ms
ENUM_RESET_RECOVERY_DELAY_MS = 50, // T(RSTRCY) minimum 10 ms; 50 as Linux (hub_port_reset), many devices need more
ENUM_SET_ADDRESS_RECOVERY_DELAY_MS = 10, // USB 2.0 Spec 9.2.6.3 min is 2 ms; 10 as Linux (hub_port_init)
ENUM_XFER_RETRY_DELAY_MS = 100, // gap before a failed enumeration request is sent again, as Linux
};

enum {
ENUM_XFER_ATTEMPT_MAX = 3, // tries of one enumeration request before a port reset, as Linux usb_get_descriptor()
};

enum {
Expand Down Expand Up @@ -1903,8 +1911,16 @@ static uint8_t enum_get_new_address(bool is_hub);
static bool enum_parse_configuration_desc(uint8_t dev_addr, const tusb_desc_configuration_t *desc_cfg);
static void enum_full_complete(bool success);
static bool enum_retry(void);
static bool enum_xfer_retry(const tuh_xfer_t *xfer);
static void process_enumeration(tuh_xfer_t *xfer);

// user_data of an enumeration request: the state that handles its completion, tagged with the attempt that sent it.
// A request queued behind another device's control transfer outlives an unplug; the pending queue then completes it
// as FAILED, and that must not count against the attempt enumerating the next device.
TU_ATTR_ALWAYS_INLINE static inline uintptr_t enum_ud(uintptr_t state) {
return state | ((uintptr_t) _usbh_data.enum_gen << 8);
}

enum {
ENUM_AFTER_DEBOUNCING_DELAY,
ENUM_AFTER_RESET_ROOT_DELAY,
Expand All @@ -1913,6 +1929,7 @@ enum {
ENUM_AFTER_RESET_HUB_DELAY_RETRY,
ENUM_AFTER_RESET_RECOVERY_DELAY,
ENUM_AFTER_SET_ADDRESS_RECOVERY_DELAY,
ENUM_AFTER_XFER_RETRY_DELAY,
};

// process async delay in enumeration
Expand All @@ -1925,7 +1942,7 @@ static void enum_delay_async(uintptr_t state) {
// connected via hub
TU_VERIFY(dev0_bus->hub_port != 0, );
if (!hub_port_get_status(dev0_bus->hub_addr, dev0_bus->hub_port, NULL, process_enumeration,
ENUM_HUB_RERSET)) {
enum_ud(ENUM_HUB_RERSET))) {
enum_full_complete(false);
}
} else
Expand Down Expand Up @@ -1962,7 +1979,7 @@ static void enum_delay_async(uintptr_t state) {
tuh_xfer_t xfer;
xfer.daddr = 0;
xfer.result = XFER_RESULT_SUCCESS;
xfer.user_data = ENUM_ADDR0_DEVICE_DESC;
xfer.user_data = enum_ud(ENUM_ADDR0_DEVICE_DESC);
process_enumeration(&xfer);
break;

Expand All @@ -1971,8 +1988,8 @@ static void enum_delay_async(uintptr_t state) {
case ENUM_AFTER_RESET_HUB_DELAY_RETRY:
// get status after reset complete to check for reset change
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_ud(state == ENUM_AFTER_RESET_HUB_DELAY ? ENUM_HUB_CLEAR_RESET
: ENUM_HUB_CLEAR_RESET_RETRY))) {
enum_full_complete(false);
}
break;
Expand All @@ -1987,7 +2004,7 @@ 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");
if (!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_ud(ENUM_SET_ADDR))) {
enum_full_complete(false);
}
break;
Expand All @@ -2003,7 +2020,23 @@ static void enum_delay_async(uintptr_t state) {
}
TU_LOG_USBH("Get Device Descriptor\r\n");
if (!tuh_descriptor_get_device(new_addr, _usbh_epbuf.ctrl, sizeof(tusb_desc_device_t), process_enumeration,
ENUM_GET_STRING_LANGUAGE_ID_LEN)) {
enum_ud(ENUM_GET_STRING_LANGUAGE_ID_LEN))) {
enum_full_complete(false);
}
break;
}

case ENUM_AFTER_XFER_RETRY_DELAY: {
const usbh_pending_ctrl_t *saved = &_usbh_data.enum_xfer;
tuh_xfer_t retry_xfer = {
.daddr = saved->daddr,
.ep_addr = 0,
.setup = &saved->setup,
.buffer = saved->buffer,
.complete_cb = process_enumeration,
.user_data = saved->user_data,
};
if (!tuh_control_xfer(&retry_xfer) && !enum_retry()) {
enum_full_complete(false);
}
break;
Expand All @@ -2021,20 +2054,28 @@ static void enum_new_device(hcd_event_t *event) {
dev0_bus->hub_addr = event->connection.hub_addr;
dev0_bus->hub_port = event->connection.hub_port;
_usbh_data.enum_attempt = 0;
_usbh_data.enum_xfer_retry = 0;
_usbh_data.enum_gen++;
usbh_defer_func_ms_async(ENUM_DEBOUNCING_DELAY_MS, enum_delay_async, ENUM_AFTER_DEBOUNCING_DELAY);
}

// process device enumeration
static void process_enumeration(tuh_xfer_t *xfer) {
if ((uint8_t) (xfer->user_data >> 8) != _usbh_data.enum_gen) {
TU_LOG_USBH("Drop a request of an earlier enumeration attempt\r\n");
return;
}

if (XFER_RESULT_FAILED == xfer->result || XFER_RESULT_TIMEOUT == xfer->result) {
if (!enum_retry()) {
if (!enum_xfer_retry(xfer) && !enum_retry()) {
enum_full_complete(false);
}
return;
}
_usbh_data.enum_xfer_retry = 0; // the device answered: the next request gets its own tries

const uint8_t daddr = xfer->daddr;
const uintptr_t state = xfer->user_data;
const uintptr_t state = xfer->user_data & 0xFFu;
usbh_device_t *dev = get_device(daddr);
tuh_bus_info_t *dev0_bus = &_usbh_data.dev0_bus;
if (daddr > 0) {
Expand All @@ -2058,7 +2099,7 @@ static void process_enumeration(tuh_xfer_t *xfer) {
is_enum_failed = true;
} else {
is_enum_failed = !hub_port_reset(dev0_bus->hub_addr, dev0_bus->hub_port, process_enumeration,
ENUM_HUB_RESET_COMPLETE);
enum_ud(ENUM_HUB_RESET_COMPLETE));
}
break;
}
Expand All @@ -2080,7 +2121,7 @@ static void process_enumeration(tuh_xfer_t *xfer) {
if (1 == port_status.change.reset) {
// Acknowledge Port Reset Change
is_enum_failed = !hub_port_clear_reset_change(dev0_bus->hub_addr, dev0_bus->hub_port, process_enumeration,
ENUM_HUB_CLEAR_RESET_COMPLETE);
enum_ud(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);
Expand Down Expand Up @@ -2127,7 +2168,7 @@ static void process_enumeration(tuh_xfer_t *xfer) {
new_dev->bus_info = *dev0_bus;
new_dev->desc_device.bMaxPacketSize0 = desc_device->bMaxPacketSize0;

is_enum_failed = !tuh_address_set(0, new_addr, process_enumeration, ENUM_GET_DEVICE_DESC);
is_enum_failed = !tuh_address_set(0, new_addr, process_enumeration, enum_ud(ENUM_GET_DEVICE_DESC));
break;
}

Expand All @@ -2154,14 +2195,14 @@ static void process_enumeration(tuh_xfer_t *xfer) {

tuh_enum_descriptor_device_cb(daddr, desc_device); // callback
is_enum_failed = !tuh_descriptor_get_string_langid(daddr, _usbh_epbuf.ctrl, 2,
process_enumeration, ENUM_GET_STRING_LANGUAGE_ID);
process_enumeration, enum_ud(ENUM_GET_STRING_LANGUAGE_ID));
break;
}

case ENUM_GET_STRING_LANGUAGE_ID: {
const uint8_t str_len = xfer->buffer[0];
is_enum_failed = !tuh_descriptor_get_string_langid(daddr, _usbh_epbuf.ctrl, str_len,
process_enumeration, ENUM_GET_STRING_MANUFACTURER_LEN);
process_enumeration, enum_ud(ENUM_GET_STRING_MANUFACTURER_LEN));
break;
}

Expand All @@ -2172,7 +2213,7 @@ static void process_enumeration(tuh_xfer_t *xfer) {
}
if (dev->desc_device.iManufacturer != 0) {
is_enum_failed = !tuh_descriptor_get_string(daddr, dev->desc_device.iManufacturer, langid, _usbh_epbuf.ctrl, 2,
process_enumeration, ENUM_GET_STRING_MANUFACTURER);
process_enumeration, enum_ud(ENUM_GET_STRING_MANUFACTURER));
break;
}
TU_ATTR_FALLTHROUGH;
Expand All @@ -2183,7 +2224,7 @@ static void process_enumeration(tuh_xfer_t *xfer) {
langid = tu_le16toh(xfer->setup->wIndex); // langid from length's request
const uint8_t str_len = xfer->buffer[0];
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);
str_len, process_enumeration, enum_ud(ENUM_GET_STRING_PRODUCT_LEN));
break;
}
TU_ATTR_FALLTHROUGH;
Expand All @@ -2195,7 +2236,7 @@ static void process_enumeration(tuh_xfer_t *xfer) {
langid = tu_le16toh(xfer->setup->wIndex); // get langid from previous setup packet if not fall through
}
is_enum_failed = !tuh_descriptor_get_string(daddr, dev->desc_device.iProduct, langid, _usbh_epbuf.ctrl, 2,
process_enumeration, ENUM_GET_STRING_PRODUCT);
process_enumeration, enum_ud(ENUM_GET_STRING_PRODUCT));
break;
}
TU_ATTR_FALLTHROUGH;
Expand All @@ -2206,7 +2247,7 @@ static void process_enumeration(tuh_xfer_t *xfer) {
langid = tu_le16toh(xfer->setup->wIndex); // langid from length's request
const uint8_t str_len = xfer->buffer[0];
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);
process_enumeration, enum_ud(ENUM_GET_STRING_SERIAL_LEN));
break;
}
TU_ATTR_FALLTHROUGH;
Expand All @@ -2218,7 +2259,7 @@ static void process_enumeration(tuh_xfer_t *xfer) {
langid = tu_le16toh(xfer->setup->wIndex); // get langid from previous setup packet if not fall through
}
is_enum_failed = !tuh_descriptor_get_string(daddr, dev->desc_device.iSerialNumber, langid, _usbh_epbuf.ctrl, 2,
process_enumeration, ENUM_GET_STRING_SERIAL);
process_enumeration, enum_ud(ENUM_GET_STRING_SERIAL));
break;
}
TU_ATTR_FALLTHROUGH;
Expand All @@ -2229,7 +2270,7 @@ static void process_enumeration(tuh_xfer_t *xfer) {
langid = tu_le16toh(xfer->setup->wIndex); // langid from length's request
const uint8_t str_len = xfer->buffer[0];
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);
str_len, process_enumeration, enum_ud(ENUM_GET_9BYTE_CONFIG_DESC));
break;
}
TU_ATTR_FALLTHROUGH;
Expand All @@ -2240,7 +2281,7 @@ static void process_enumeration(tuh_xfer_t *xfer) {
uint8_t const config_idx = 0;
TU_LOG_USBH("Get Configuration[%u] Descriptor (9 bytes)\r\n", config_idx);
is_enum_failed = !tuh_descriptor_get_configuration(daddr, config_idx, _usbh_epbuf.ctrl, 9,
process_enumeration, ENUM_GET_FULL_CONFIG_DESC);
process_enumeration, enum_ud(ENUM_GET_FULL_CONFIG_DESC));
break;
}

Expand All @@ -2258,7 +2299,7 @@ static void process_enumeration(tuh_xfer_t *xfer) {
uint8_t const config_idx = (uint8_t) (tu_le16toh(xfer->setup->wValue) & 0xFF);
TU_LOG_USBH("Get Configuration[%u] Descriptor\r\n", config_idx);
is_enum_failed = !tuh_descriptor_get_configuration(daddr, config_idx, _usbh_epbuf.ctrl, total_len,
process_enumeration, ENUM_SET_CONFIG);
process_enumeration, enum_ud(ENUM_SET_CONFIG));
break;
}

Expand All @@ -2279,7 +2320,7 @@ static void process_enumeration(tuh_xfer_t *xfer) {
}

if (tuh_enum_descriptor_configuration_cb(daddr, config_idx, (const tusb_desc_configuration_t*) _usbh_epbuf.ctrl)) {
is_enum_failed = !tuh_configuration_set(daddr, desc_config->bConfigurationValue, process_enumeration, ENUM_CONFIG_DRIVER);
is_enum_failed = !tuh_configuration_set(daddr, desc_config->bConfigurationValue, process_enumeration, enum_ud(ENUM_CONFIG_DRIVER));
} else {
config_idx++;
if (config_idx >= dev->desc_device.bNumConfigurations) {
Expand All @@ -2289,7 +2330,7 @@ static void process_enumeration(tuh_xfer_t *xfer) {
}
TU_LOG_USBH("Get Configuration[%u] Descriptor (9 bytes)\r\n", config_idx);
is_enum_failed = !tuh_descriptor_get_configuration(daddr, config_idx, _usbh_epbuf.ctrl, 9,
process_enumeration, ENUM_GET_FULL_CONFIG_DESC);
process_enumeration, enum_ud(ENUM_GET_FULL_CONFIG_DESC));
}
break;
}
Expand Down Expand Up @@ -2533,6 +2574,8 @@ static bool enum_retry(void) {
}
enum_teardown(false);
_usbh_data.enum_attempt++;
_usbh_data.enum_xfer_retry = 0;
_usbh_data.enum_gen++;
TU_LOG_USBH("Enumeration failed, retry attempt %u/%u\r\n", _usbh_data.enum_attempt + 1, CFG_TUH_ENUM_ATTEMPT_MAX);
_usbh_data.enumerating_daddr = 0;
usbh_defer_func_ms_async(ENUM_DEBOUNCING_DELAY_MS, enum_delay_async, ENUM_AFTER_DEBOUNCING_DELAY);
Expand All @@ -2542,4 +2585,26 @@ static bool enum_retry(void) {
#endif
}

// Device failed an enumeration request (after the immediate USBH_CONTROL_RETRY_MAX resends): send it again after
// ENUM_XFER_RETRY_DELAY_MS without a port reset, so a device still recovering from the reset gets more time; a reset
// would restart its recovery. A timeout is not retried, the device already NAKed for CFG_TUH_CONTROL_TIMEOUT_MS.
// False (nothing changed) when the request's tries are used up or an unplug already invalidated the device.
static bool enum_xfer_retry(const tuh_xfer_t *xfer) {
if (XFER_RESULT_FAILED != xfer->result || _usbh_data.enumerating_daddr == TUSB_INDEX_INVALID_8 ||
_usbh_data.enum_xfer_retry + 1 >= ENUM_XFER_ATTEMPT_MAX) {
Comment thread
greptile-apps[bot] marked this conversation as resolved.
Resolved
return false;
}
usbh_pending_ctrl_t *saved = &_usbh_data.enum_xfer;
saved->setup = *xfer->setup;
saved->buffer = xfer->buffer;
saved->user_data = xfer->user_data;
saved->daddr = xfer->daddr;
TU_VERIFY(usbh_defer_func_ms_async(ENUM_XFER_RETRY_DELAY_MS, enum_delay_async, ENUM_AFTER_XFER_RETRY_DELAY));

_usbh_data.enum_xfer_retry++;
TU_LOG_USBH("Enumeration request failed, try %u/%u in %u ms\r\n", _usbh_data.enum_xfer_retry + 1,
ENUM_XFER_ATTEMPT_MAX, ENUM_XFER_RETRY_DELAY_MS);
return true;
}

#endif
4 changes: 4 additions & 0 deletions test/unit-test/project.yml
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,10 @@
- CFG_TUH_DEVICE_MAX=3
- CFG_TUH_HUB=2
- CFG_TUSB_DEBUG=0
:test_usbh_enum_xfer_retry:
- CFG_TUD_ENABLED=0
- CFG_TUH_ENABLED=1
- CFG_TUSB_DEBUG=0
:test_midi2_device_gtb_default:
- CFG_TUD_MSC=0
- CFG_TUD_MIDI2=1
Expand Down
Loading
Loading