rp2040/rp23xx: implement endpoint stall queueing and GET_STATUS responses#19452
Open
ricardgb wants to merge 1 commit into
Open
rp2040/rp23xx: implement endpoint stall queueing and GET_STATUS responses#19452ricardgb wants to merge 1 commit into
ricardgb wants to merge 1 commit into
Conversation
Two device-controller defects that break standard host error recovery, found while root-causing why macOS never mounts a NuttX mass-storage function (full analysis and host traces in apache#19435): 1. epsubmit() aborted (-EBUSY) any IN request submitted while the endpoint was halted. The mass storage class halts bulk-IN for a failed device-to-host command (BOT-legal) and then submits the CSW; the CSW was dropped on the floor, so after the host's Clear-Halt the endpoint NAKed forever and the host timed out (macOS: 30 s, then a Bulk-Only-reset/device-reset spiral until it disables the port). This is exactly the race documented for years in the usbmsc_scsi.c header (David Hewson's analysis); the USBMSC_STALL_RACEWAR sleep workaround only survives hosts that clear the halt within 100 ms -- Linux does, macOS does not, which is why Linux testing never saw it. Implement the stall-queueing contract instead: requests submitted while an endpoint is halted are queued without arming the hardware (arming rewrites the buffer control word and would silently clear the STALL bit); a halt terminates any in-flight IN transfer (its hardware buffer is disarmed by the STALL write and would never complete); clearing the halt resets the data toggle and re-arms the head of the queue; stale buffer completions latched for transfers aborted by a halt are ignored. RP2040/RP23XX now select ARCH_USBDEV_STALLQUEUE, which also retires the RACEWAR's two 100 ms sleeps per failed command. 2. The USB_REQ_GETSTATUS handler in ep0setup() validated the request but never queued the two-byte response, for all three recipients (device/interface/endpoint), so EP0 NAKed the host's data stage forever and every GET STATUS timed out. macOS issues GetPipeStatus = GET STATUS(endpoint) on a halted pipe before running Bulk-Only reset recovery and hit this on every probe; lsusb -v's device-status query hangs on it as well. Send the response: endpoint recipient reports the halt bit, device recipient reports self-powered, interface reports zeros; the status stage is armed by handle_zlp() exactly as for class-dispatched IN transfers. Validated on RP2350 silicon (Raspberry Pi Pico 2 W, composite CDC-ACM + CDC-NCM + USBMSC): pre-fix, a raw-usbfs replay of macOS's sequence and timing reproduced both defects deterministically (CSW read ETIMEDOUT after a delayed clear-halt; all GET STATUS variants ETIMEDOUT). Post-fix: the CSW survives the halt and is delivered after Clear-Halt with correct tag/status/residue across a post-stall delay sweep of 0-1000 ms; all GET STATUS variants answer immediately with correct halt reporting; no regressions in the exact-length SCSI suite, Bulk-Only reset, FAT mount and full reads, CDC-NCM/ACM, or warm reboots; and macOS now mounts the volume (together with the companion usbmsc fixes). The rp2040 driver shares the code and receives the identical fix (build-tested). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Ricard Rosson <ricard@groundbits.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
PR 3 — rp2040/rp23xx: implement stall queueing and GET STATUS responses
Branch:
rp2040-rp23xx-stallqueue-getstatus(worktree~/nuttxspace/wt-pr-stallq)Base: apache/nuttx master (1b6f918)
References: #19435 (bugs 5 and 6 of that report)
Summary
Two device-controller (DCD) defects in
rp2040_usbdev.c/rp23xx_usbdev.cthat break standard host error recovery. Either one alone is fatal to macOS
mass-storage mounting; both are invisible to Linux hosts, whose SCSI stack
neither sends the triggering requests nor recovers with the triggering
timing.
Fix 1: stall queueing (the lost-CSW race)
epsubmit()aborted (-EBUSY) any IN request submitted while the endpoint washalted. The mass-storage class halts bulk-IN for a failed device-to-host
command (BOT-legal) and then submits the CSW — which was silently dropped, so
after the host's Clear-Halt the endpoint NAKed forever and the host timed out
(macOS: 30 s, then a Bulk-Only-reset/device-reset spiral until it disables
the port). This is exactly the race documented for years in the
usbmsc_scsi.cheader comment (David Hewson's analysis: "the subsequenttransfer for the CSW would be dropped on the floor (by the epsubmit()
function) if the end point was still stalled"). The
USBMSC_STALL_RACEWARsleep workaround only survives hosts that clear the halt within its 100 ms
window — Linux does, macOS does not.
This PR implements the stall-queueing contract that
CONFIG_ARCH_USBDEV_STALLQUEUEdocuments (and thatusbmsc_scsi.chas beenwaiting on):
epsubmit(): requests submitted while an endpoint is halted are queuedwithout arming the hardware (IN and OUT — arming an OUT endpoint rewrites
the DPSRAM buffer-control word and would silently clear the STALL bit).
epstall(stall): cancels any in-flight IN transfer (its hardware buffer isdisarmed by the STALL write and would otherwise never complete).
epstall(resume): resets the data toggle (USB 2.0 9.4.5) and re-arms thehead of the request queue — this is what delivers the queued CSW.
ignored instead of being attributed to post-halt queued requests.
arch/arm/Kconfig: RP2040/RP23XX nowselect ARCH_USBDEV_STALLQUEUE,which retires the RACEWAR's two 100 ms sleeps per failed command.
*_abortrequest()helpers are removed.Fix 2: GET STATUS was never answered
The
USB_REQ_GETSTATUScase in*_ep0setup()validated the request butnever queued the two-byte response — for all three recipients
(device/interface/endpoint). EP0 then NAKs the host's data-IN stage forever
and every GET STATUS times out. macOS issues GetPipeStatus = GET
STATUS(endpoint) on a halted pipe ~50 ms after the stall, before Clear-Halt,
and hit this on every probe;
lsusb -v's device-status query hangs on ittoo. The handler now sends the response (endpoint: halt bit; device:
self-powered; interface: zeros) via
epwrite(ep0, response, 2), with thestatus stage armed by
handle_zlp()exactly as for class-dispatched INtransfers.
Validation
USBMSC, Linux host, raw-usbfs replay of macOS's exact commands and
timing:
stall → wait 1 s → clear-halt → CSW read ETIMEDOUT (fix 1); all five
GET STATUS variants ETIMEDOUT after 1.5 s (fix 2).
correct tag/status/residue across a post-stall delay sweep of
0/30/50/80/150/500/1000 ms in macOS's recovery order (GetPipeStatus →
clear-halt → GET STATUS → CSW), with the halt bit correctly reported as
1 before and 0 after the clear; all GET STATUS variants answer in <1 ms.
regressions: exact-length SCSI suite, GET MAX LUN + Bulk-Only reset
10/10, FAT mount + full reads, CDC-NCM/ACM, warm reboots.
usbmsc fixes in the separate class-driver PR). The success log shows the
expected one-time transient bulk-IN halts each cleanly recovered, and no
EP0 timeouts/resets/port-disable.
(raspberrypi-pico:usbnsh and raspberrypi-pico:composite, the latter
compiling usbmsc with ARCH_USBDEV_STALLQUEUE in effect). No RP2040
hardware validation performed.
Full failure chronology, host traces, and the silicon A/B evidence are in
#19435.
Impact
Any class driver that stalls an endpoint and expects queued requests to
survive (mass storage is the canonical case). GET STATUS affects every
configuration — any host or tool that sends it got a control timeout.
With ARCH_USBDEV_STALLQUEUE selected, usbmsc also stops sleeping 200 ms per
failed command (RACEWAR retired).
AI disclosure
This investigation and the fixes were performed by an AI agent (Claude Code),
operated and directed by the submitter, and reviewed by the submitter before
posting.