usb: accept replacement SETUP after an abandoned control transfer

A host can replace an unfinished control transfer with a new SETUP. ARC
and DesignWare flush EP0 without reporting completion callbacks, leaving
the core waiting for a data/status packet that will never arrive.

Explicitly notify the core after both directions and stale completion
bits have been cancelled. Return an abandoned data/status stage to READY.
If a queued or running handler still owns the request and data buffer,
keep that ownership and suppress its response; dispatch only the latest
replacement when it returns. A driver-owned SET_ADDRESS can also cancel
a deferred request without passing its SETUP to the core.

Do not overwrite an arbitrary busy state in usb_core_setup_received().
ARC cancellation is a prerequisite in the preceding patch; DesignWare
cancels both EP0 directions before notifying and rearming reception.

Change-Id: Ia8e0a68fe47ccab952168da24a2f76e311ca2b15
This commit is contained in:
Paul Sauro 2026-09-20 17:21:42 +02:00 • committed by Solomon Peachy
parent b12ef5e6f8
commit 9aa2d7fe94
4 changed files with 45 additions and 0 deletions

View file

@ -901,7 +901,11 @@ static void usb_dw_handle_setup_received(void)
#endif
struct usb_ctrlrequest req;
memcpy(&req, ep0_buffer, sizeof(struct usb_ctrlrequest));
/* Abort both old data/status directions before delivering the new SETUP.
* Flush does not report completions, so notify the core explicitly once. */
usb_dw_flush_endpoint(0, USB_DW_EPDIR_IN);
usb_dw_flush_endpoint(0, USB_DW_EPDIR_OUT);
usb_core_control_cancelled();
usb_dw_ep0_recv();
if(!handle_set_address(&req))
@ -1004,6 +1008,9 @@ static void usb_dw_oepint(int ep)
if (epints & STUP)
{
usb_dw_handle_setup_received();
/* SETUP cancelled the old transfer, including any XFRC captured
* in this interrupt snapshot. Do not complete the new request. */
return;
}
if (epints & XFRC)

View file

@ -55,6 +55,9 @@ struct usb_class_driver;
void usb_core_init(void);
void usb_core_exit(void);
/* IRQ context, after both EP0 directions and their stale completions have
* been cancelled. Does not release a request buffer still owned by a handler. */
void usb_core_control_cancelled(void);
void usb_core_setup_received(struct usb_ctrlrequest* req);
void usb_core_control_response(enum usb_control_response response, const void* data, size_t size);
void usb_core_transfer_complete(int endpoint,int dir,int status,int length);

View file

@ -1102,6 +1102,8 @@ static void setup_received(void)
}
}
usb_core_control_cancelled();
struct usb_ctrlrequest *req = (struct usb_ctrlrequest*)tmp;
/* A new setup packet supersedes any unfinished SET_ADDRESS request. */

View file

@ -225,6 +225,7 @@ enum {
EP0_EXPECT_RX_STATUS_COMP, /* sending status */
};
static volatile int ep0_state;
static bool control_cancelled;
#define TRACE_EP0_STATE 0
#if TRACE_EP0_STATE == 1
@ -1193,6 +1194,7 @@ static void signal_xfer_complete(int ep, struct usb_ctrlrequest* req, int status
}
static void process_setup_request(struct usb_ctrlrequest* req) {
control_cancelled = false;
set_ep0_state(req->bRequestType & USB_DIR_IN ? EP0_HANDLING_TX_CONTROL : EP0_HANDLING_RX_CONTROL);
if(ep0_state == EP0_HANDLING_TX_CONTROL || req->wLength == 0) {
@ -1204,6 +1206,7 @@ static void process_setup_request(struct usb_ctrlrequest* req) {
if(req->wLength > sizeof(usb_control_data)) {
logf("usb_core: control write too large %u > %u", req->wLength, sizeof(usb_control_data));
usb_drv_stall(EP_CONTROL, true, false);
set_ep0_state(EP0_READY);
return;
}
set_ep0_state(EP0_EXPECT_RX_DATA_COMP);
@ -1320,17 +1323,32 @@ void usb_core_handle_notify(long id, intptr_t data)
}
}
void usb_core_control_cancelled(void)
{
/* A newer SETUP supersedes even a request deferred behind a handler. */
have_pending_request = false;
/* A queued/running handler still owns handling_request and the shared
* buffer. Suppress its response; a later SETUP may replace it on return. */
if(ep0_state == EP0_HANDLING_TX_CONTROL || ep0_state == EP0_HANDLING_RX_CONTROL)
control_cancelled = true;
else
set_ep0_state(EP0_READY);
}
void usb_core_setup_received(struct usb_ctrlrequest* req) {
if(bus_reset_pending) {
logf("usb_core: bus resetting tick=%lu", current_tick);
return;
}
/* Drivers cancel abandoned EP0 transfers before delivering replacement
* SETUP. A handler may still own the request buffer; defer until it returns. */
if(ep0_state != EP0_READY) {
logf("usb_core: control pending tick=%lu", current_tick);
pending_request = *req;
have_pending_request = true;
return;
}
have_pending_request = false;
handling_request = *req;
process_setup_request(&handling_request);
}
@ -1338,13 +1356,27 @@ void usb_core_setup_received(struct usb_ctrlrequest* req) {
void usb_core_control_response(enum usb_control_response response, const void* data, size_t size) {
logf("usb_core: response ack=%d size=%u ep0_state=%d tick=%lu", response, size, ep0_state, current_tick);
int oldlevel = disable_irq_save();
if(bus_reset_pending) {
restore_irq(oldlevel);
return;
}
if((ep0_state == EP0_HANDLING_TX_CONTROL || ep0_state == EP0_HANDLING_RX_CONTROL) && check_for_new_setup()) {
restore_irq(oldlevel);
return;
}
if(control_cancelled) {
control_cancelled = false;
set_ep0_state(EP0_READY);
restore_irq(oldlevel);
return;
}
if(response == USB_CONTROL_STALL) {
set_ep0_state(EP0_READY);
usb_drv_stall(EP_CONTROL, true, true);
restore_irq(oldlevel);
return;
}
@ -1381,6 +1413,7 @@ void usb_core_control_response(enum usb_control_response response, const void* d
panicf("usb_core: invalid control response ep_state=%d", ep0_state);
break;
}
restore_irq(oldlevel);
}
void usb_core_notify_set_address(uint8_t addr)