summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorhathach <[email protected]>2026-07-09 23:38:11 +0700
committerhathach <[email protected]>2026-07-09 23:38:11 +0700
commitfd63ad6c2b102d3ea981047e75b43c8db723534e (patch)
tree4645b8900c5442e6f09a2083bf090b68885a0dd9
parent33cabfe3f045a89ffdcfadce6ed5331c8b7bc870 (diff)
usbd: forward vendor EP0 requests; clear ep state on iso activate
Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01HeF2gZ1M7GWkz6Av4BpKPg
-rw-r--r--src/device/usbd.c84
1 files changed, 55 insertions, 29 deletions
diff --git a/src/device/usbd.c b/src/device/usbd.c
index 7a6e13f8d..5471e132d 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);
@@ -1041,9 +1042,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 +1052,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 +1157,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 +1191,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;
@@ -1648,10 +1663,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) {