Skip to content

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

Merged
merged 1 commit into from
Apr 17, 2025

Conversation

mikesmitty
Copy link
Contributor

@mikesmitty mikesmitty commented Apr 11, 2025

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

@mikesmitty mikesmitty force-pushed the ms/expose-usb-stall branch 2 times, most recently from 713b9df to baff40b Compare April 11, 2025 23:18
@deadprogram
Copy link
Member

Now that #4856 has been merged, this PR is the next up @mikesmitty

Can you please resolve the merge conflicts with the dev branch?

@mikesmitty
Copy link
Contributor Author

Sure thing, done

@deadprogram
Copy link
Member

If there's any suggestions on which MCUs would make the most sense to add support next I'm happy to hear it

The SAMD21/SAMD51 are the next logical addition, in part because we also need more USB implementations! See #4751

@mikesmitty
Copy link
Contributor Author

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

@deadprogram
Copy link
Member

@eliasnaur any other feedback now on this PR? Looks good to me.

Copy link
Contributor

@eliasnaur eliasnaur left a 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.

@@ -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) {
Copy link
Contributor

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?

Copy link
Contributor Author

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:

  1. USB host sends a CBW (Command Block Wrapper) to the device containing a SCSI command (e.g. a READ command)
  2. 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
  3. 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:

  1. A "Bulk-Only Mass Storage Reset" request (class-specific)
  2. 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
  3. 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

Copy link
Contributor Author

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

Copy link
Contributor

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.

Copy link
Contributor Author

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

Copy link
Member

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!!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will do, for sure!

@mikesmitty
Copy link
Contributor Author

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

@@ -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) {
Copy link
Contributor

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.

@deadprogram
Copy link
Member

@mikesmitty looks like this PR needs to be rebased against the latest dev, please.

@mikesmitty mikesmitty force-pushed the ms/expose-usb-stall branch 2 times, most recently from cf78f4e to 624630d Compare April 16, 2025 14:06
@mikesmitty mikesmitty force-pushed the ms/expose-usb-stall branch from 7ec7ea8 to 9f7f4a1 Compare April 17, 2025 01:16
Copy link
Contributor

@eliasnaur eliasnaur left a 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)
Copy link
Contributor

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 {
Copy link
Contributor

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?

Copy link
Contributor Author

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

@deadprogram
Copy link
Member

Thanks for the work on this PR @mikesmitty and to @eliasnaur for the excellent feedback.

Now merging.

@deadprogram deadprogram merged commit febb390 into tinygo-org:dev Apr 17, 2025
18 checks passed
@eliasnaur
Copy link
Contributor

@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).

@mikesmitty mikesmitty deleted the ms/expose-usb-stall branch April 17, 2025 13:19
@mikesmitty
Copy link
Contributor Author

Sure, I'll make those changes in the main PR

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants