rp2040: avoid device-mode state machine hang

Don't mark IN buffers as available during the last 200us of a full-speed
frame. This avoids a situation seen with the USB2.0 hub on a Raspberry
Pi 4 where a late IN token before the next full-speed SOF can cause port
babble and a corrupt ACK packet. The nature of the data corruption has a
chance to cause device lockup.

Use the next SOF to mark delayed buffers as available. This reduces
available Bulk IN bandwidth by approximately 20%, and requires that the
SOF interrupt is enabled while these transfers are ongoing.

Inherit the top-level enable from the corresponding Pico-SDK flag.
Applications that will not use the device in a situation where it could
be plugged into a Pi 4 or Pi 400 (for example, when directly connected
to a commodity hub or other host) can turn off the flag in the SDK.

v2: use a field in hw_endpoint to mark pending.

v3: Partial rewrite following review comments

- Stub functions out if the workaround is not required
- Only force-enable SOF while any vulnerable endpoints are active
- Respect dcd_sof_enable() functionality
- Get rid of all but necessary ifdef hackery
- Fix a bug where the "endpoint lock" was used with an uninitialised pointer.
This commit is contained in:
Jonathan Bell
2023-01-05 13:36:51 +00:00
parent c3e47c31cc
commit 73b0047efc
4 changed files with 96 additions and 8 deletions

View File

@@ -246,13 +246,32 @@ static void __tusb_irq_path_func(dcd_rp2040_irq)(void)
{
uint32_t const status = usb_hw->ints;
uint32_t handled = 0;
bool keep_sof_alive = false;
if (status & USB_INTF_DEV_SOF_BITS)
{
handled |= USB_INTF_DEV_SOF_BITS;
#if TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX
last_sof = time_us_32();
for (uint8_t i = 0; i < USB_MAX_ENDPOINTS; i++) {
struct hw_endpoint *ep = hw_endpoint_get_by_num(i, TUSB_DIR_IN);
hw_endpoint_lock_update(ep, 1);
// Bulk IN endpoint in a transfer?
if (rp2040_ep_needs_sof(ep) && ep->active)
keep_sof_alive = true;
// Deferred enable?
if (ep->pending) {
hw_endpoint_start_next_buffer(ep);
ep->pending = 0;
}
hw_endpoint_lock_update(ep, -1);
}
#endif
// disable SOF interrupt if it is used for RESUME in remote wakeup
if (!_sof_enable) usb_hw_clear->inte = USB_INTS_DEV_SOF_BITS;
if (!keep_sof_alive && !_sof_enable)
usb_hw_clear->inte = USB_INTS_DEV_SOF_BITS;
dcd_event_sof(0, usb_hw->sof_rd & USB_SOF_RD_BITS, true);
}
@@ -449,7 +468,11 @@ void dcd_sof_enable(uint8_t rhport, bool en)
usb_hw_set->inte = USB_INTS_DEV_SOF_BITS;
}else
{
// Don't clear immediately if the SOF workaround is in use.
// The SOF handler will conditionally disable the interrupt.
#if !TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX
usb_hw_clear->inte = USB_INTS_DEV_SOF_BITS;
#endif
}
}