diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/class/msc/msc_device.c | 31 | ||||
| -rw-r--r-- | src/common/tusb_mcu.h | 1 | ||||
| -rw-r--r-- | src/device/usbd.c | 10 | ||||
| -rw-r--r-- | src/portable/linux/raw_gadget/raw_gadget_context.c | 2 | ||||
| -rw-r--r-- | src/portable/linux/raw_gadget/raw_gadget_endpoint.c | 86 | ||||
| -rw-r--r-- | src/portable/linux/raw_gadget/raw_gadget_event.c | 15 | ||||
| -rw-r--r-- | src/portable/linux/raw_gadget/raw_gadget_hal.c | 6 | ||||
| -rw-r--r-- | src/portable/linux/raw_gadget/raw_gadget_private.h | 1 | ||||
| -rw-r--r-- | src/portable/linux/raw_gadget/raw_gadget_transfer.c | 6 | ||||
| -rw-r--r-- | src/tusb_option.h | 10 |
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 |
