summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorZixun LI <[email protected]>2026-07-29 14:27:59 +0200
committerZixun LI <[email protected]>2026-07-29 15:56:12 +0200
commit8f287a9e8ea6073f1970a4bf6993ade3a9db620d (patch)
tree7404b73c6002d752333a76bb24ccdb47988877b9
parent529fd0532ae6eeb13b1ac8dc87381c6560e3a995 (diff)
raw_gadget: fix MSC recovery after endpoint haltraw_gadget_review
Allow opt-in DCDs to queue transfers behind a halted endpoint without changing hardware DCD behavior. Retry dummy_hcd's transient EP_SET_HALT failure, queue MSC BOT status and recovery requests behind the halt, and clean up Raw Gadget endpoint workers across bus reset. Signed-off-by: Zixun LI <[email protected]>
-rw-r--r--src/class/msc/msc_device.c31
-rw-r--r--src/common/tusb_mcu.h1
-rw-r--r--src/device/usbd.c10
-rw-r--r--src/portable/linux/raw_gadget/raw_gadget_context.c2
-rw-r--r--src/portable/linux/raw_gadget/raw_gadget_endpoint.c86
-rw-r--r--src/portable/linux/raw_gadget/raw_gadget_event.c15
-rw-r--r--src/portable/linux/raw_gadget/raw_gadget_hal.c6
-rw-r--r--src/portable/linux/raw_gadget/raw_gadget_private.h1
-rw-r--r--src/portable/linux/raw_gadget/raw_gadget_transfer.c6
-rw-r--r--src/tusb_option.h10
10 files changed, 156 insertions, 12 deletions
diff --git a/src/class/msc/msc_device.c b/src/class/msc/msc_device.c
index 8485105e4..6523c0ae5 100644
--- a/src/class/msc/msc_device.c
+++ b/src/class/msc/msc_device.c
@@ -96,7 +96,15 @@ TU_ATTR_ALWAYS_INLINE static inline bool send_csw(mscd_interface_t* p_msc) {
p_msc->csw.data_residue = p_msc->cbw.total_bytes - p_msc->xferred_len;
p_msc->stage = MSC_STAGE_STATUS_SENT;
memcpy(_mscd_epbuf.buf, &p_msc->csw, sizeof(msc_csw_t)); //-V1086
+ #if CFG_TUD_ENDPOINT_XFER_BEHIND_HALT
+ bool const queued = usbd_edpt_xfer(rhport, p_msc->ep_in , _mscd_epbuf.buf, sizeof(msc_csw_t), false);
+ if (!queued) {
+ p_msc->stage = MSC_STAGE_STATUS;
+ }
+ return queued;
+ #else
return usbd_edpt_xfer(rhport, p_msc->ep_in , _mscd_epbuf.buf, sizeof(msc_csw_t), false);
+ #endif
}
TU_ATTR_ALWAYS_INLINE static inline bool prepare_cbw(mscd_interface_t* p_msc) {
@@ -185,6 +193,16 @@ static bool proc_stage_status(mscd_interface_t *p_msc) {
uint8_t rhport = p_msc->rhport;
msc_cbw_t const *p_cbw = &p_msc->cbw;
+ #if CFG_TUD_ENDPOINT_XFER_BEHIND_HALT
+ // Failed and phase-error commands already stalled the remaining data endpoint in fail_scsi_op().
+ if ((p_msc->csw.status == MSC_CSW_STATUS_PASSED) &&
+ (p_cbw->total_bytes > p_msc->xferred_len) && is_data_in(p_cbw->dir)) {
+ // 6.7 The 13 Cases: case 5 (Hi > Di): STALL before status
+ // TU_LOG_DRV(" SCSI case 5 (Hi > Di): %lu > %lu\r\n", p_cbw->total_bytes, p_msc->xferred_len);
+ usbd_edpt_stall(rhport, p_msc->ep_in);
+ }
+ TU_ASSERT(send_csw(p_msc));
+ #else
// skip status if epin is currently stalled, will do it when received Clear Stall request
if (!usbd_edpt_stalled(rhport, p_msc->ep_in)) {
if ((p_cbw->total_bytes > p_msc->xferred_len) && is_data_in(p_cbw->dir)) {
@@ -195,6 +213,7 @@ static bool proc_stage_status(mscd_interface_t *p_msc) {
TU_ASSERT(send_csw(p_msc));
}
}
+ #endif
#if TU_CHECK_MCU(OPT_MCU_CXD56)
// WORKAROUND: cxd56 has its own nuttx usb stack which does not forward Set/ClearFeature(Endpoint) to DCD.
@@ -429,7 +448,15 @@ bool mscd_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t
case MSC_REQ_RESET:
TU_LOG_DRV(" MSC BOT Reset\r\n");
TU_VERIFY(request->wValue == 0 && request->wLength == 0);
+ #if CFG_TUD_ENDPOINT_XFER_BEHIND_HALT
+ bool const reset_recovery = p_msc->stage == MSC_STAGE_NEED_RESET;
+ #endif
proc_bot_reset(p_msc); // driver state reset
+ #if CFG_TUD_ENDPOINT_XFER_BEHIND_HALT
+ if (reset_recovery) {
+ TU_ASSERT(prepare_cbw(p_msc));
+ }
+ #endif
tud_control_status(rhport, request);
break;
@@ -628,11 +655,15 @@ bool mscd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t
break;
}
+ #if CFG_TUD_ENDPOINT_XFER_BEHIND_HALT
+ TU_ASSERT(prepare_cbw(p_msc));
+ #else
if (!usbd_edpt_stalled(rhport, p_msc->ep_out)) {
TU_ASSERT(prepare_cbw(p_msc));
} else {
p_msc->stage = MSC_STAGE_CMD;
}
+ #endif
} else {
// Any xfer ended here is considered unknown error, ignore it
TU_LOG1(" Warning expect SCSI Status but received unknown data\r\n");
diff --git a/src/common/tusb_mcu.h b/src/common/tusb_mcu.h
index 7ef589bf0..a1f799ed9 100644
--- a/src/common/tusb_mcu.h
+++ b/src/common/tusb_mcu.h
@@ -730,6 +730,7 @@
#elif TU_CHECK_MCU(OPT_MCU_LINUX_RAW_GADGET)
#define TUP_DCD_ENDPOINT_MAX 16
#define TUP_RHPORT_HIGHSPEED 1
+ #define CFG_TUD_ENDPOINT_XFER_BEHIND_HALT_DEFAULT 1
#endif
// External USB controller
diff --git a/src/device/usbd.c b/src/device/usbd.c
index 5471e132d..5b123bbb4 100644
--- a/src/device/usbd.c
+++ b/src/device/usbd.c
@@ -1654,7 +1654,12 @@ void usbd_edpt_stall(uint8_t rhport, uint8_t ep_addr) {
// only stalled if currently cleared
TU_LOG_USBD(" Stall EP %02X\r\n", ep_addr);
dcd_edpt_stall(rhport, ep_addr);
+ #if CFG_TUD_ENDPOINT_XFER_BEHIND_HALT
+ _usbd_dev.ep_status[epnum][dir] &= (uint8_t) ~(TU_EDPT_STATE_BUSY | TU_EDPT_STATE_CLAIMED);
+ _usbd_dev.ep_status[epnum][dir] |= TU_EDPT_STATE_STALLED;
+ #else
_usbd_dev.ep_status[epnum][dir] |= (TU_EDPT_STATE_STALLED | TU_EDPT_STATE_BUSY);
+ #endif
}
void usbd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr) {
@@ -1664,6 +1669,10 @@ void usbd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr) {
uint8_t const dir = tu_edpt_dir(ep_addr);
TU_LOG_USBD(" Clear Stall EP %02X\r\n", ep_addr);
+ #if CFG_TUD_ENDPOINT_XFER_BEHIND_HALT
+ dcd_edpt_clear_stall(rhport, ep_addr);
+ _usbd_dev.ep_status[epnum][dir] &= (uint8_t) ~TU_EDPT_STATE_STALLED;
+ #else
const bool was_stalled = (_usbd_dev.ep_status[epnum][dir] & TU_EDPT_STATE_STALLED) != 0;
dcd_edpt_clear_stall(rhport, ep_addr);
// Clear STALLED|BUSY unconditionally (long-standing behavior; some classes, e.g. audio's
@@ -1678,6 +1687,7 @@ void usbd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr) {
clear_mask |= TU_EDPT_STATE_CLAIMED;
}
_usbd_dev.ep_status[epnum][dir] &= (uint8_t) ~clear_mask;
+ #endif
}
bool usbd_edpt_stalled(uint8_t rhport, uint8_t ep_addr) {
diff --git a/src/portable/linux/raw_gadget/raw_gadget_context.c b/src/portable/linux/raw_gadget/raw_gadget_context.c
index e45125a57..9d7b1a516 100644
--- a/src/portable/linux/raw_gadget/raw_gadget_context.c
+++ b/src/portable/linux/raw_gadget/raw_gadget_context.c
@@ -106,6 +106,7 @@ raw_gadget_result_t raw_gadget_context_table_create(size_t context_count)
context->available = false;
context->initialized = false;
context->shutting_down = false;
+ context->resetting = false;
context->event_thread_created = false;
context->handle = (raw_gadget_handle_t) index;
context->speed = RAW_GADGET_SPEED_INVALID;
@@ -210,6 +211,7 @@ void raw_gadget_context_reset(raw_gadget_context_t *context)
context->initialized = false;
context->configured = false;
context->shutting_down = false;
+ context->resetting = false;
context->event_thread_created = false;
context->speed = RAW_GADGET_SPEED_INVALID;
context->file_descriptor = RAW_GADGET_INVALID_FILE_DESCRIPTOR;
diff --git a/src/portable/linux/raw_gadget/raw_gadget_endpoint.c b/src/portable/linux/raw_gadget/raw_gadget_endpoint.c
index a7d0aae19..8952e53b1 100644
--- a/src/portable/linux/raw_gadget/raw_gadget_endpoint.c
+++ b/src/portable/linux/raw_gadget/raw_gadget_endpoint.c
@@ -10,6 +10,7 @@
#include <errno.h>
#include <string.h>
#include <sys/ioctl.h>
+#include <time.h>
#include <linux/usb/ch9.h>
@@ -61,6 +62,11 @@ static raw_gadget_result_t raw_gadget_endpoint_disable_locked(
USB_RAW_IOCTL_EP_DISABLE,
(unsigned long)kernel_handle) < 0)
{
+ TU_LOG1("Raw Gadget: EP_DISABLE failed: ep=%02x handle=%u errno=%d (%s)\r\n",
+ endpoint->address,
+ kernel_handle,
+ errno,
+ strerror(errno));
return RAW_GADGET_RESULT_IO_ERROR;
}
@@ -120,6 +126,12 @@ raw_gadget_result_t raw_gadget_endpoint_open(raw_gadget_handle_t handle,
return RAW_GADGET_RESULT_INTERNAL_ERROR;
}
+ if (context->resetting || context->shutting_down)
+ {
+ (void) pthread_mutex_unlock(&context->mutex);
+ return RAW_GADGET_RESULT_NOT_AVAILABLE;
+ }
+
endpoint = raw_gadget_endpoint_get(context, endpoint_address);
if (endpoint->enabled)
{
@@ -231,7 +243,9 @@ raw_gadget_result_t raw_gadget_endpoint_stall(raw_gadget_handle_t handle,
raw_gadget_context_t *context;
raw_gadget_endpoint_t *endpoint;
raw_gadget_result_t result;
+ uint32_t generation;
uint32_t kernel_handle;
+ int file_descriptor;
context = raw_gadget_context_get(handle);
result = raw_gadget_endpoint_context_validate(context);
@@ -245,6 +259,12 @@ raw_gadget_result_t raw_gadget_endpoint_stall(raw_gadget_handle_t handle,
return RAW_GADGET_RESULT_INTERNAL_ERROR;
}
+ if (context->resetting || context->shutting_down)
+ {
+ (void) pthread_mutex_unlock(&context->mutex);
+ return RAW_GADGET_RESULT_NOT_AVAILABLE;
+ }
+
if ((endpoint_address & USB_ENDPOINT_NUMBER_MASK) == 0u)
{
if (!context->ep0_request_active)
@@ -284,21 +304,59 @@ raw_gadget_result_t raw_gadget_endpoint_stall(raw_gadget_handle_t handle,
}
kernel_handle = endpoint->kernel_handle;
+ generation = context->transfer_generation;
+ file_descriptor = context->file_descriptor;
- if (ioctl(context->file_descriptor,
- USB_RAW_IOCTL_EP_SET_HALT,
- (unsigned long)kernel_handle) < 0)
- {
- result = RAW_GADGET_RESULT_IO_ERROR;
- }
- else
+ while (true)
{
- result = RAW_GADGET_RESULT_SUCCESS;
- }
+ int const ioctl_result =
+ ioctl(file_descriptor,
+ USB_RAW_IOCTL_EP_SET_HALT,
+ (unsigned long) kernel_handle);
+ int const error_number = errno;
- (void) pthread_mutex_unlock(&context->mutex);
+ (void) pthread_mutex_unlock(&context->mutex);
- return result;
+ if (ioctl_result == 0)
+ {
+ return RAW_GADGET_RESULT_SUCCESS;
+ }
+
+ if (error_number != EAGAIN)
+ {
+ TU_LOG1("Raw Gadget: EP_SET_HALT failed: ep=%02x errno=%d (%s)\r\n",
+ endpoint_address,
+ error_number,
+ strerror(error_number));
+ return RAW_GADGET_RESULT_IO_ERROR;
+ }
+
+ // dummy_hcd can retain its small-IN FIFO request briefly after reporting completion.
+ struct timespec retry_delay = {
+ .tv_sec = 0,
+ .tv_nsec = 10000000L
+ };
+ while ((nanosleep(&retry_delay, &retry_delay) < 0) && (errno == EINTR))
+ {
+ }
+
+ if (pthread_mutex_lock(&context->mutex) != 0)
+ {
+ return RAW_GADGET_RESULT_INTERNAL_ERROR;
+ }
+
+ if (!context->initialized ||
+ context->resetting ||
+ context->shutting_down ||
+ (context->file_descriptor != file_descriptor) ||
+ (context->transfer_generation != generation) ||
+ !endpoint->enabled ||
+ (endpoint->kernel_handle != kernel_handle))
+ {
+ (void) pthread_mutex_unlock(&context->mutex);
+ return RAW_GADGET_RESULT_NOT_AVAILABLE;
+ }
+ }
}
raw_gadget_result_t raw_gadget_endpoint_clear_stall(
@@ -328,6 +386,12 @@ raw_gadget_result_t raw_gadget_endpoint_clear_stall(
return RAW_GADGET_RESULT_INTERNAL_ERROR;
}
+ if (context->resetting || context->shutting_down)
+ {
+ (void) pthread_mutex_unlock(&context->mutex);
+ return RAW_GADGET_RESULT_NOT_AVAILABLE;
+ }
+
endpoint = raw_gadget_endpoint_get(context, endpoint_address);
if (!endpoint->enabled)
{
diff --git a/src/portable/linux/raw_gadget/raw_gadget_event.c b/src/portable/linux/raw_gadget/raw_gadget_event.c
index 742099723..ec691c74c 100644
--- a/src/portable/linux/raw_gadget/raw_gadget_event.c
+++ b/src/portable/linux/raw_gadget/raw_gadget_event.c
@@ -357,6 +357,7 @@ void raw_gadget_bus_reset_prepare(raw_gadget_context_t *context)
return;
}
+ context->resetting = true;
context->configured = false;
context->transfer_generation++;
context->ep0_request_active = false;
@@ -367,5 +368,17 @@ void raw_gadget_bus_reset_prepare(raw_gadget_context_t *context)
(void) pthread_mutex_unlock(&context->mutex);
- raw_gadget_transfer_cancel_all(context);
+ raw_gadget_result_t const result =
+ raw_gadget_endpoint_close_all(context->handle);
+
+ if (pthread_mutex_lock(&context->mutex) == 0)
+ {
+ context->resetting = false;
+ (void) pthread_mutex_unlock(&context->mutex);
+ }
+
+ if (result != RAW_GADGET_RESULT_SUCCESS)
+ {
+ TU_LOG1("Raw Gadget: endpoint reset failed: result=%d\r\n", result);
+ }
}
diff --git a/src/portable/linux/raw_gadget/raw_gadget_hal.c b/src/portable/linux/raw_gadget/raw_gadget_hal.c
index 9212d219b..faaefc980 100644
--- a/src/portable/linux/raw_gadget/raw_gadget_hal.c
+++ b/src/portable/linux/raw_gadget/raw_gadget_hal.c
@@ -292,6 +292,12 @@ raw_gadget_result_t raw_gadget_configure(raw_gadget_handle_t handle)
return RAW_GADGET_RESULT_INTERNAL_ERROR;
}
+ if (context->resetting || context->shutting_down)
+ {
+ (void) pthread_mutex_unlock(&context->mutex);
+ return RAW_GADGET_RESULT_NOT_AVAILABLE;
+ }
+
if (context->configured)
{
(void) pthread_mutex_unlock(&context->mutex);
diff --git a/src/portable/linux/raw_gadget/raw_gadget_private.h b/src/portable/linux/raw_gadget/raw_gadget_private.h
index eedc43c9e..f36d0871e 100644
--- a/src/portable/linux/raw_gadget/raw_gadget_private.h
+++ b/src/portable/linux/raw_gadget/raw_gadget_private.h
@@ -73,6 +73,7 @@ typedef struct
bool available;
bool initialized;
bool shutting_down;
+ bool resetting;
bool event_thread_created;
raw_gadget_handle_t handle;
diff --git a/src/portable/linux/raw_gadget/raw_gadget_transfer.c b/src/portable/linux/raw_gadget/raw_gadget_transfer.c
index aa1a6f4b7..9525ce28e 100644
--- a/src/portable/linux/raw_gadget/raw_gadget_transfer.c
+++ b/src/portable/linux/raw_gadget/raw_gadget_transfer.c
@@ -330,6 +330,12 @@ raw_gadget_result_t raw_gadget_endpoint_transfer(raw_gadget_handle_t handle,
return RAW_GADGET_RESULT_INTERNAL_ERROR;
}
+ if (context->resetting || context->shutting_down)
+ {
+ (void) pthread_mutex_unlock(&context->mutex);
+ return RAW_GADGET_RESULT_NOT_AVAILABLE;
+ }
+
if (endpoint_zero && context->ep0_request_active)
{
ep0_status_stage = (length == 0u) &&
diff --git a/src/tusb_option.h b/src/tusb_option.h
index 614a78803..05e46ffff 100644
--- a/src/tusb_option.h
+++ b/src/tusb_option.h
@@ -652,6 +652,16 @@
#error "CFG_TUD_ENDPPOINT_MAX must be less than or equal to TUP_DCD_ENDPOINT_MAX"
#endif
+// Allow transfers to be queued while an endpoint is halted. The DCD must preserve each transfer
+// for delivery after the halt is cleared, though it may report completion after taking ownership.
+// If ClearFeature is handled below USBD, the software stalled state remains the last observed state.
+#ifndef CFG_TUD_ENDPOINT_XFER_BEHIND_HALT
+ #ifndef CFG_TUD_ENDPOINT_XFER_BEHIND_HALT_DEFAULT
+ #define CFG_TUD_ENDPOINT_XFER_BEHIND_HALT_DEFAULT 0
+ #endif
+ #define CFG_TUD_ENDPOINT_XFER_BEHIND_HALT CFG_TUD_ENDPOINT_XFER_BEHIND_HALT_DEFAULT
+#endif
+
// USB 2.0 7.1.20: compliance test mode support
#ifndef CFG_TUD_TEST_MODE
#define CFG_TUD_TEST_MODE 0