-
Notifications
You must be signed in to change notification settings - Fork 948
machine/rp2: expose usb endpoint stall handling #4850
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
713b9df
to
baff40b
Compare
Now that #4856 has been merged, this PR is the next up @mikesmitty Can you please resolve the merge conflicts with the |
baff40b
to
763f846
Compare
Sure thing, done |
The SAMD21/SAMD51 are the next logical addition, in part because we also need more USB implementations! See #4751 |
Sounds good, coincidentally I started looking at the USB support in the atsamd21 the other day. I'm still working through the FAT12 emulation layer right now, but I'll come back to it when I'm done |
@eliasnaur any other feedback now on this PR? Looks good to me. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some comments. I don't know the USB protocol nor the rpxxxx USB hardware, so my comments may well be misguided.
src/machine/machine_rp2_usb.go
Outdated
@@ -138,7 +150,8 @@ func sendViaEPIn(ep uint32, data []byte, count int) { | |||
_usbDPSRAM.EPxBufferControl[ep&0x7F].In.Set(val) | |||
} | |||
|
|||
func sendStallViaEPIn(ep uint32) { | |||
// Enable ENDPOINT_HALT/stall on a USB IN endpoint | |||
func SetStallEPIn(ep uint32) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you explain why the SetStall
* functions must be exported? Can't you infer their calls from the nil-ness of ep.StallHandler
below?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, the short version is that a USB endpoint halt/stall is a form of flow control/out-of-band communication that specific protocols on the device can use to signal to the host when something goes wrong. Individual USB drivers on the device are assigned endpoints (akin to network ports) and the mass storage driver needs to be able to stall those endpoints to notify the host of fatal errors so just the mass storage connection can be reset when needed. In response the host sends a secret handshake to the mass storage driver via a series of USB Setup Packets, the mass storage driver sees this and recognizes the host is back in sync, un-stalls its endpoints and mass storage communication can resume.
If you're interested in more detail about the flow, this is the long version:
In the mass storage Bulk-Only Transfer (BOT) protocol for example the typical communication flow goes like this:
- USB host sends a CBW (Command Block Wrapper) to the device containing a SCSI command (e.g. a READ command)
- USB device validates the CBW and if everything checks out it immediately starts sending data from the requested sector/block, packet by packet as the host ACKs them. Each sector/block takes 8 full packets to send so the host will keep accepting raw packets as the requested data until it gets the number of bytes it requested in the original CBW
- When the device finishes transferring the data it finishes the transaction by sending a CSW (Command Status Wrapper) with the final status of the command and a few other details, then starts waiting for the next CBW to arrive
If the CBW gets corrupted in transit however, the device won't starting sending the requested data to ensure data integrity. There's no frame encapsulating the data, it's just full buffers of data so sending a CSW immediately would be interpreted by the host as part of the response to its READ command. Instead, the device stalls its TX and RX endpoints and refuses to perform any commands. The stall is an actively negotiated status between the host and the device by the underlying host/device USB firmwares so the host will stop listening for the response data and perform a BOT Reset Recovery process (or sometimes take the easy way out, power-cycling the device haha). The Reset Recovery process is three requests sent to the control endpoint (endpoint 0) in this specific order:
- A "Bulk-Only Mass Storage Reset" request (class-specific)
- A standard CLEAR_FEATURE request with the ENDPOINT_HALT function, addressed to the stalled mass storage IN (device TX) endpoint. CLEAR_FEATURE is also used for other features, but in this case it's for
- Another standard CLEAR_FEATURE request, addressed to the stalled mass storage OUT (device RX) endpoint
This is where the Setup Packets come in, all three of these commands get sent using USB Setup Packets. Typically just for initial negotiation/configuration, but also for recovery procedures. Since the first request is marked as a class-specific request and addressed to the mass storage interface id it gets routed through the usb.SetupConfig.Handler function, but since CLEAR_FEATURE is a standard request it gets routed to machine.handleStandardSetup() where most standard requests are handled. Endpoint 0 stalls/un-stalls a lot in the course of its normal processes so that makes sense, but these specific requests are addressed to the stalled mass storage endpoints with the wIndex field and the mass storage driver needs to receive them so it knows when to un-stall its endpoints. That's the reason behind adding the usb.EndpointConfig.StallHandler
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or to put it less verbosely (apologies for the novel), the mass storage driver calls SetStall*()
to signal the host of an issue then waits for the host to acknowledge it via Setup Packets to the usb.SetupConfig.Handler and usb.EndpointConfig.StallHandler functions, then the mass storage driver can resume the communications by clearing the stall conditions
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice explanation, thank you! (now that you went through the trouble, consider adding (some of) your explanation somewhere in the documentation).
I think I understand the need for the exports now. However, I don't understand why they're not methods on USBDevice
. I see there's already machine.ReceiveUSBControlPacket
, so I suppose it's not a new pattern. I do worry locking us into a singleton USBDevice
will bite our behinds in future.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, that makes sense. I'll update that here in a sec
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
consider adding (some of) your explanation somewhere in the documentation).
Yes please!!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will do, for sure!
It's interesting that the sizes_test is failing again. I just updated the size for that test from 7359 to 7395 in the previous PR and now it's flipped back |
src/machine/machine_rp2_usb.go
Outdated
@@ -138,7 +150,8 @@ func sendViaEPIn(ep uint32, data []byte, count int) { | |||
_usbDPSRAM.EPxBufferControl[ep&0x7F].In.Set(val) | |||
} | |||
|
|||
func sendStallViaEPIn(ep uint32) { | |||
// Enable ENDPOINT_HALT/stall on a USB IN endpoint | |||
func SetStallEPIn(ep uint32) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice explanation, thank you! (now that you went through the trouble, consider adding (some of) your explanation somewhere in the documentation).
I think I understand the need for the exports now. However, I don't understand why they're not methods on USBDevice
. I see there's already machine.ReceiveUSBControlPacket
, so I suppose it's not a new pattern. I do worry locking us into a singleton USBDevice
will bite our behinds in future.
@mikesmitty looks like this PR needs to be rebased against the latest |
cf78f4e
to
624630d
Compare
7ec7ea8
to
9f7f4a1
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A question and another nit. LGTM, with the caveat that I don't speak USB :)
_usbDPSRAM.EPxBufferControl[ep].In.ClearBits(val) | ||
if epXPIDReset[ep] { | ||
// Reset the PID to DATA0 | ||
setEPDataPID(ep&0x7F, false) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: the masking is redundant here.
@@ -149,6 +162,37 @@ func sendStallViaEPIn(ep uint32) { | |||
_usbDPSRAM.EPxBufferControl[ep&0x7F].In.Set(val) | |||
} | |||
|
|||
// Set ENDPOINT_HALT/stall status on a USB OUT endpoint. | |||
func (dev *USBDevice) SetStallEPOut(ep uint32) { | |||
if ep == 0 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should ep
be masked before comparing with 0?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The 0 endpoint is a special case. It's both an in and out endpoint in one so there's no direction bit applied for it, but couldn't hurt
Thanks for the work on this PR @mikesmitty and to @eliasnaur for the excellent feedback. Now merging. |
@mikesmitty I'd still like to resolve the two comments above. Sorry for approving too soon (my intention was to say that I found no more issues with the PR). |
Sure, I'll make those changes in the main PR |
Since the USB mass storage PR (#4844) is getting a bit monolithic I figured I'd break out smaller parts of it as I can. I figure I'll work my way through the MCUs that support USB natively, but here's the RP2 changes to start. If there's any suggestions on which MCUs would make the most sense to add support next I'm happy to hear it