summaryrefslogtreecommitdiff
path: root/src/device
diff options
context:
space:
mode:
authorHiFiPHile <[email protected]>2026-08-25 09:27:48 +0200
committerHiFiPHile <[email protected]>2026-08-25 09:27:48 +0200
commitdfac26a272fa7bbbca2050fbe9f1ca09008e548e (patch)
treeefbc53f8f2c1e5d9c7f38e5fef6d774a20053cec /src/device
parente590b45fcf51f9ddace73178074e4fe6d691e319 (diff)
parent5c0e31cdabaf37f14e1f5e988a020abfc1000495 (diff)
Merge master updates into the UAC1 host branch
Bring the audio work onto the current host core and build files before applying the remaining review fixes. Signed-off-by: HiFiPHile <[email protected]>
Diffstat (limited to 'src/device')
-rw-r--r--src/device/dcd.h26
-rw-r--r--src/device/usbd.c135
-rw-r--r--src/device/usbd.h2
3 files changed, 113 insertions, 50 deletions
diff --git a/src/device/dcd.h b/src/device/dcd.h
index f005e9620..a4006ae0c 100644
--- a/src/device/dcd.h
+++ b/src/device/dcd.h
@@ -20,19 +20,27 @@
// MACRO CONSTANT TYPEDEF PROTYPES
//--------------------------------------------------------------------+
+// Bus reset is reported as two edges. BUS_RESET_START is optional: a controller that
+// cannot tell the edges apart emits only BUS_RESET_END, which stays self-sufficient (it
+// performs the full teardown with or without a preceding START). Emit START when reset
+// signaling is detected - the link is unusable and the speed is not negotiated yet - so
+// the stack stops using endpoints immediately instead of at the end of the reset.
typedef enum {
- DCD_EVENT_INVALID = 0, // 0
- DCD_EVENT_BUS_RESET, // 1
- DCD_EVENT_UNPLUGGED, // 2
- DCD_EVENT_SOF, // 3
- DCD_EVENT_SUSPEND, // 4 TODO LPM Sleep L1 support
- DCD_EVENT_RESUME, // 5
- DCD_EVENT_SETUP_RECEIVED, // 6
- DCD_EVENT_XFER_COMPLETE, // 7
- USBD_EVENT_FUNC_CALL, // 8 Not an DCD event, just a convenient way to defer ISR function
+ DCD_EVENT_INVALID = 0, // 0
+ DCD_EVENT_BUS_RESET_START, // 1
+ DCD_EVENT_BUS_RESET_END, // 2 with negotiated speed
+ DCD_EVENT_UNPLUGGED, // 3
+ DCD_EVENT_SOF, // 4
+ DCD_EVENT_SUSPEND, // 5 TODO LPM Sleep L1 support
+ DCD_EVENT_RESUME, // 6
+ DCD_EVENT_SETUP_RECEIVED, // 7
+ DCD_EVENT_XFER_COMPLETE, // 8
+ USBD_EVENT_FUNC_CALL, // 9 Not an DCD event, just a convenient way to defer ISR function
DCD_EVENT_COUNT
} dcd_eventid_t;
+#define DCD_EVENT_BUS_RESET DCD_EVENT_BUS_RESET_END // backward compatibility
+
typedef struct TU_ATTR_ALIGNED(4) {
uint8_t rhport;
uint8_t event_id;
diff --git a/src/device/usbd.c b/src/device/usbd.c
index 7a6e13f8d..e84d72fa4 100644
--- a/src/device/usbd.c
+++ b/src/device/usbd.c
@@ -256,7 +256,7 @@ static const usbd_class_driver_t _usbd_driver[] = {
.deinit = vendord_deinit,
.reset = vendord_reset,
.open = vendord_open,
- .control_xfer_cb = tud_vendor_control_xfer_cb,
+ .control_xfer_cb = vendord_control_xfer_cb,
.xfer_cb = vendord_xfer_cb,
.xfer_isr = NULL,
.sof = NULL
@@ -418,6 +418,7 @@ TU_ATTR_ALWAYS_INLINE static inline bool queue_event(dcd_event_t const * event,
//--------------------------------------------------------------------+
static bool usbd_control_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes);
static bool process_setup_received(uint8_t rhport, tusb_control_request_t const * p_request);
+static bool process_get_status(uint8_t rhport, tusb_control_request_t const * request, uint16_t status);
static bool process_set_config(uint8_t rhport, uint8_t cfg_num);
static bool process_get_descriptor(uint8_t rhport, tusb_control_request_t const * p_request);
@@ -455,7 +456,8 @@ TU_ATTR_WEAK bool dcd_configure(uint8_t rhport, uint32_t cfg_id, const void* cfg
#if CFG_TUSB_DEBUG >= CFG_TUD_LOG_LEVEL
static char const *const _usbd_event_str[DCD_EVENT_COUNT] = {
"Invalid",
- "Bus Reset",
+ "Bus Reset Start",
+ "Bus Reset End",
"Unplugged",
"SOF",
"Suspend",
@@ -641,6 +643,8 @@ static void configuration_reset(uint8_t rhport) {
static void usbd_reset(uint8_t rhport) {
configuration_reset(rhport);
+ // discard any pre-reset SETUP still counted: a stale count skips post-reset SETUPs
+ _usbd_queued_setup = 0;
}
bool tud_task_event_ready(void) {
@@ -694,8 +698,15 @@ void tud_task_ext(uint32_t timeout_ms, bool in_isr) {
#endif
switch (event.event_id) {
- case DCD_EVENT_BUS_RESET:
+ case DCD_EVENT_BUS_RESET_START:
+ TU_LOG_USBD("\r\n");
+ usbd_reset(event.rhport);
+ break;
+
+ case DCD_EVENT_BUS_RESET_END:
TU_LOG_USBD(": %s Speed\r\n", tu_str_speed[event.bus_reset.speed]);
+ // TODO a DCD that reports both edges pays for two teardowns: track a per-rhport
+ // "start seen" flag and skip this reset, keeping it for the single-event DCDs.
usbd_reset(event.rhport);
_usbd_dev.speed = event.bus_reset.speed;
break;
@@ -746,7 +757,14 @@ void tud_task_ext(uint32_t timeout_ms, bool in_isr) {
_usbd_dev.ep_status[epnum][ep_dir] &= (uint8_t) ~(TU_EDPT_STATE_BUSY | TU_EDPT_STATE_CLAIMED);
if (0 == epnum) {
- usbd_control_xfer_cb(event.rhport, ep_addr, (xfer_result_t) event.xfer_complete.result, event.xfer_complete.len);
+ // Not stalled on failure: a DCD refuses an EP0 prime when a newer setup is already
+ // latched, and EP0 stalls are cleared by hardware when that setup arrives - so a stall
+ // issued here lands after the auto-clear and would stall the transfer that superseded
+ // this one. The pending setup re-drives EP0 by itself.
+ if (!usbd_control_xfer_cb(event.rhport, ep_addr, (xfer_result_t) event.xfer_complete.result,
+ event.xfer_complete.len)) {
+ TU_LOG_USBD(" Control stage not continued\r\n");
+ }
} else {
usbd_class_driver_t const* driver = get_driver(_usbd_dev.ep2drv[epnum][ep_dir]);
TU_ASSERT(driver,);
@@ -864,10 +882,10 @@ bool tud_control_xfer(uint8_t rhport, const tusb_control_request_t* request, voi
if (ctrl_xfer->data_len > 0U) {
TU_ASSERT(buffer);
}
- TU_ASSERT(data_stage_xact(rhport));
+ TU_VERIFY(data_stage_xact(rhport));
} else {
// wLength == 0: Status stage is always IN per USB 2.0 §9.3.1
- TU_ASSERT(status_stage_xact(rhport, TU_EP0_IN));
+ TU_VERIFY(status_stage_xact(rhport, TU_EP0_IN));
}
return true;
@@ -918,7 +936,7 @@ static bool usbd_control_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t
}
if (is_ok) {
- TU_ASSERT(status_stage_xact(rhport, ep_status));
+ TU_VERIFY(status_stage_xact(rhport, ep_status));
} else {
// Stall both IN and OUT control endpoint
dcd_edpt_stall(rhport, TU_EP0_OUT);
@@ -926,7 +944,7 @@ static bool usbd_control_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t
}
} else {
// More data to transfer
- TU_ASSERT(data_stage_xact(rhport));
+ TU_VERIFY(data_stage_xact(rhport));
}
return true;
@@ -1041,9 +1059,7 @@ static bool process_std_device_request(uint8_t rhport, tusb_control_request_t co
// Device status bit mask
// - Bit 0: Self Powered TODO must invoke callback to get actual status
// - Bit 1: Remote Wakeup enabled
- uint16_t status = (uint16_t) _usbd_dev.dev_state_bm;
- tud_control_xfer(rhport, p_request, &status, 2);
- return true;
+ return process_get_status(rhport, p_request, (uint16_t) _usbd_dev.dev_state_bm);
}
default:
@@ -1053,6 +1069,14 @@ static bool process_std_device_request(uint8_t rhport, tusb_control_request_t co
}
+// Reply to a standard GET_STATUS (device/interface/endpoint) with its 2-byte status word.
+// GET_STATUS is Device-to-host only; reject a mis-directed (OUT) request rather than handing
+// usbd the address of a stack local to write host data into after this frame has returned.
+static bool process_get_status(uint8_t rhport, tusb_control_request_t const * request, uint16_t status) {
+ TU_VERIFY(request->bmRequestType_bit.direction == TUSB_DIR_IN);
+ return tud_control_xfer(rhport, request, &status, 2);
+}
+
// This handles the actual request and its response.
// Returns false if unable to complete the request, causing caller to stall control endpoints.
static bool process_setup_received(uint8_t rhport, tusb_control_request_t const * p_request) {
@@ -1150,9 +1174,18 @@ static bool process_setup_received(uint8_t rhport, tusb_control_request_t const
}
case TUSB_REQ_SET_INTERFACE:
+ // A class that implements altsettings handles SET_INTERFACE itself and returns true,
+ // so reaching here means the class does not — where only alt 0 is valid. Any non-zero
+ // alt (unimplemented, or rejected as invalid by the class) is a Request Error (stall).
+ TU_VERIFY(tu_u16_low(p_request->wValue) == 0);
tud_control_status(rhport, p_request);
break;
+ case TUSB_REQ_GET_STATUS:
+ // USB 2.0 9.4.5: interface GET_STATUS returns 2 reserved (zero) bytes
+ TU_VERIFY(process_get_status(rhport, p_request, 0x0000));
+ break;
+
default: return false;
}
}
@@ -1175,35 +1208,34 @@ static bool process_setup_received(uint8_t rhport, tusb_control_request_t const
} else {
// Handle STD request to endpoint
switch (p_request->bRequest) { //-V2520
- case TUSB_REQ_GET_STATUS: {
- uint16_t status = usbd_edpt_stalled(rhport, ep_addr) ? 0x0001u : 0x0000u;
- tud_control_xfer(rhport, p_request, &status, 2);
- }
- break;
+ case TUSB_REQ_GET_STATUS:
+ // USB 2.0 9.4.5: endpoint GET_STATUS bit 0 = Halt
+ TU_VERIFY(process_get_status(rhport, p_request, usbd_edpt_stalled(rhport, ep_addr) ? 0x0001u : 0x0000u));
+ break;
case TUSB_REQ_CLEAR_FEATURE:
case TUSB_REQ_SET_FEATURE: {
- if ( TUSB_REQ_FEATURE_EDPT_HALT == p_request->wValue ) {
- if ( TUSB_REQ_CLEAR_FEATURE == p_request->bRequest ) {
- usbd_edpt_clear_stall(rhport, ep_addr);
- }else {
- usbd_edpt_stall(rhport, ep_addr);
- }
- }
+ // ENDPOINT_HALT is the only endpoint feature; it exists only on a non-control endpoint
+ // that an interface actually owns. Any other selector, the control endpoint (EP0 has no
+ // Halt feature, USB 2.0 9.4.9), or an endpoint no driver owns is a Request Error (stall).
+ TU_VERIFY(TUSB_REQ_FEATURE_EDPT_HALT == p_request->wValue);
+ TU_VERIFY(ep_num != 0);
+ TU_VERIFY(driver != NULL);
- if (driver != NULL) {
- // Some classes such as USBTMC needs to clear/re-init its buffer when receiving CLEAR_FEATURE request
- // We will also forward std request targeted endpoint to class drivers as well
+ if ( TUSB_REQ_CLEAR_FEATURE == p_request->bRequest ) {
+ usbd_edpt_clear_stall(rhport, ep_addr);
+ } else {
+ usbd_edpt_stall(rhport, ep_addr);
+ }
- // STD request must always be ACKed regardless of driver returned value
- // Also clear complete callback if driver set since it can also stall the request.
- (void) invoke_class_control(rhport, driver, p_request);
- ctrl_xfer->complete_cb = NULL;
+ // Some classes such as USBTMC need to clear/re-init their buffer on CLEAR_FEATURE.
+ // Clear complete callback if driver set since it can also stall the request.
+ (void) invoke_class_control(rhport, driver, p_request);
+ ctrl_xfer->complete_cb = NULL;
- // skip ZLP status if driver already did that
- if (!(_usbd_dev.ep_status[0][TUSB_DIR_IN] & TU_EDPT_STATE_BUSY)) {
- tud_control_status(rhport, p_request);
- }
+ // STD request must always be ACKed; skip ZLP status if driver already did that.
+ if (!(_usbd_dev.ep_status[0][TUSB_DIR_IN] & TU_EDPT_STATE_BUSY)) {
+ tud_control_status(rhport, p_request);
}
}
break;
@@ -1458,8 +1490,18 @@ TU_ATTR_FAST_FUNC void dcd_event_handler(dcd_event_t const* event, bool in_isr)
break;
}
- if (send) {
- queue_event(event, in_isr);
+ if (send && !queue_event(event, in_isr)) {
+ // event dropped by a full queue: undo state that would otherwise wedge permanently
+ if (event->event_id == DCD_EVENT_SETUP_RECEIVED) {
+ // undo the increment, else every later SETUP is skipped as "other SETUP in queue"
+ // and EP0 is deaf until re-init
+ _usbd_queued_setup--;
+ } else if (event->event_id == DCD_EVENT_XFER_COMPLETE) {
+ // clear busy + claimed, else the endpoint can never be claimed or re-armed again
+ uint8_t const epnum = tu_edpt_number(event->xfer_complete.ep_addr);
+ uint8_t const ep_dir = tu_edpt_dir(event->xfer_complete.ep_addr);
+ _usbd_dev.ep_status[epnum][ep_dir] &= (uint8_t) ~(TU_EDPT_STATE_BUSY | TU_EDPT_STATE_CLAIMED);
+ }
}
}
@@ -1573,10 +1615,12 @@ bool usbd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t* buffer, uint16_t t
if (dcd_edpt_xfer(rhport, ep_addr, buffer, total_bytes, is_isr)) {
return true;
} else {
- // DCD error, mark endpoint as ready to allow next transfer
+ // Driver refused the transfer, mark endpoint as ready to allow next transfer. This is a
+ // recoverable condition (e.g. a new setup superseding a control response), not a bug, so
+ // do not break into the debugger - TU_BREAKPOINT() halts the CPU whenever a probe is
+ // attached, which on a test rig is always.
_usbd_dev.ep_status[epnum][dir] &= (uint8_t) ~(TU_EDPT_STATE_BUSY | TU_EDPT_STATE_CLAIMED);
TU_LOG_USBD("FAILED\r\n");
- TU_BREAKPOINT();
return false;
}
}
@@ -1648,10 +1692,21 @@ void usbd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr) {
uint8_t const epnum = tu_edpt_number(ep_addr);
uint8_t const dir = tu_edpt_dir(ep_addr);
- // only clear if currently stalled
TU_LOG_USBD(" Clear Stall EP %02X\r\n", ep_addr);
+ const bool was_stalled = (_usbd_dev.ep_status[epnum][dir] & TU_EDPT_STATE_STALLED) != 0;
dcd_edpt_clear_stall(rhport, ep_addr);
- _usbd_dev.ep_status[epnum][dir] &= (uint8_t) ~(TU_EDPT_STATE_STALLED | TU_EDPT_STATE_BUSY);
+ // Clear STALLED|BUSY unconditionally (long-standing behavior; some classes, e.g. audio's
+ // set-interface, call this on a non-stalled endpoint solely to drop a leftover BUSY bit).
+ // Only release the CLAIMED ownership bit when the endpoint was actually stalled: the stall
+ // aborts the in-flight transfer in the dcd with no completion event to release the claim, so
+ // clearing it here prevents starvation. On a non-stalled clear (e.g. a data-toggle reset) a
+ // transfer may still be legitimately claimed by another task, so keep CLAIMED to preserve the
+ // claim->xfer mutual exclusion.
+ uint8_t clear_mask = TU_EDPT_STATE_STALLED | TU_EDPT_STATE_BUSY;
+ if (was_stalled) {
+ clear_mask |= TU_EDPT_STATE_CLAIMED;
+ }
+ _usbd_dev.ep_status[epnum][dir] &= (uint8_t) ~clear_mask;
}
bool usbd_edpt_stalled(uint8_t rhport, uint8_t ep_addr) {
diff --git a/src/device/usbd.h b/src/device/usbd.h
index 9be12ed0a..2015d1869 100644
--- a/src/device/usbd.h
+++ b/src/device/usbd.h
@@ -229,7 +229,7 @@ bool tud_vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_requ
// Config number, interface count, string index, total length, attribute, power in mA
#define TUD_CONFIG_DESCRIPTOR(config_num, _itfcount, _stridx, _total_len, _attribute, _power_ma) \
- 9, TUSB_DESC_CONFIGURATION, U16_TO_U8S_LE(_total_len), _itfcount, config_num, _stridx, TU_BIT(7) | _attribute, (_power_ma)/2
+ 9, TUSB_DESC_CONFIGURATION, U16_TO_U8S_LE(_total_len), _itfcount, config_num, _stridx, TU_BIT(7) | _attribute, (uint8_t)TU_MIN((_power_ma)/2, UINT8_MAX)
//--------------------------------------------------------------------+
// CDC Descriptor Templates