From 9701729c447c14cb43707d4e443291084e5b8391 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bo=20St=C3=A5le=20Kopperud?= Date: Mon, 8 Jun 2026 20:19:22 +0200 Subject: [PATCH] usb2otg: track periodic-split state for INT/bulk transfers Add an explicit SSPLIT/CSPLIT state machine with the issuing microframe and a bounded CSPLIT NYET-retry window, and drive complete-splits and watchdog recovery from it. Stops periodic INT splits from flooding the active-too-long path and aborting transfers mid-sequence. --- .../2708/usb/usb2otg/usb2otg_intern.h | 8 +++ .../broadcom/2708/usb/usb2otg/usb2otg_intr.c | 66 ++++++++++++++++++- .../2708/usb/usb2otg/usb2otg_schedule.c | 32 ++++++++- 3 files changed, 102 insertions(+), 4 deletions(-) diff --git a/arch/arm-native/soc/broadcom/2708/usb/usb2otg/usb2otg_intern.h b/arch/arm-native/soc/broadcom/2708/usb/usb2otg/usb2otg_intern.h index 08e04feb919..0f4b9c44124 100644 --- a/arch/arm-native/soc/broadcom/2708/usb/usb2otg/usb2otg_intern.h +++ b/arch/arm-native/soc/broadcom/2708/usb/usb2otg/usb2otg_intern.h @@ -197,6 +197,9 @@ struct USB2OTGUnit UBYTE hc_WatchdogCount; /* Incremented each NakTimeout period while channel active */ UBYTE hc_SplitCSplitPending; /* SSPLIT was issued and request must not be resubmitted yet */ UBYTE hc_DeferCount; /* Consecutive watchdog defers; caps total defer time */ + UBYTE hc_CsplitRetry; /* CSPLIT NYET retries this interval; caps to TT result window */ + UBYTE hc_SplitState; /* periodic-split SM: USB2OTG_SPLIT_{IDLE,SS,CS} */ + UWORD hc_SplitSSUframe; /* HFNUM&0x3fff uframe the SSPLIT was issued in */ struct IOUsbHWReq * hc_DiagReq; /* Last bulk request tracked on this channel */ ULONG hc_DiagStartFrame; ULONG hc_DiagLastProgressFrame; @@ -333,6 +336,11 @@ struct USB2OTGDevice #define CHAN_INT_LAST CHAN_INT5 #define CHAN_BULK2 7 +/* Periodic-split sequencer state (hc_SplitState). */ +#define USB2OTG_SPLIT_IDLE 0 /* not a split, or split sequence finished */ +#define USB2OTG_SPLIT_SS 1 /* SSPLIT issued, awaiting ACK */ +#define USB2OTG_SPLIT_CS 2 /* CSPLIT issued/pending, awaiting completion */ + /* * Bulk-OUT per-packet throttle (busy-wait iterations before arming). * Some flash devices with tiny SRAM buffers go silent (no NYET/NAK) diff --git a/arch/arm-native/soc/broadcom/2708/usb/usb2otg/usb2otg_intr.c b/arch/arm-native/soc/broadcom/2708/usb/usb2otg/usb2otg_intr.c index 4fb4271db2c..ebdb8378433 100644 --- a/arch/arm-native/soc/broadcom/2708/usb/usb2otg/usb2otg_intr.c +++ b/arch/arm-native/soc/broadcom/2708/usb/usb2otg/usb2otg_intr.c @@ -634,13 +634,26 @@ void FNAME_DEV(GlobalIRQHandler)(struct USB2OTGUnit *USBUnit, struct ExecBase *S tmp |= 1 << 16; /* Set "do complete split" */ wr32le(USB2OTG_CHANNEL_REG(chan, SPLITCTRL), tmp); USBUnit->hu_Channel[chan].hc_SplitCSplitPending = 1; + /* Fresh CSPLIT sequence — reset the NYET retry window. */ + USBUnit->hu_Channel[chan].hc_CsplitRetry = 0; + USBUnit->hu_Channel[chan].hc_SplitState = USB2OTG_SPLIT_CS; if (req->iouh_Req.io_Command == UHCMD_BULKXFER) usb2otg_remove_bulk_queue_duplicates(USBUnit, req, chan); D(bug("[USB2OTG] Completing split transaction in interrupt: chan=%d intr=%04x SPLITCTRL=%08x\n", chan, intr, tmp)); - FNAME_DEV(StartChannel)(USBUnit, chan, 1); + + /* + * Periodic INT split: issue the CSPLIT one microframe + * later (delayed_channel[] ticks per SOF) so it lands in + * the TT result window instead of hammering the same + * uframe. Bulk keeps the immediate re-arm. + */ + if (chan >= CHAN_INT1 && chan <= CHAN_INT_LAST) + delayed_channel[chan] = 1; + else + FNAME_DEV(StartChannel)(USBUnit, chan, 1); } else if ((do_split == USB2OTG_HCSPLT_CSPLIT) && (req->iouh_Req.io_Command == UHCMD_BULKXFER) && @@ -714,7 +727,36 @@ void FNAME_DEV(GlobalIRQHandler)(struct USB2OTGUnit *USBUnit, struct ExecBase *S else { if (chan >= CHAN_INT1 && chan <= CHAN_INT_LAST) - FNAME_DEV(StartChannel)(USBUnit, chan, 1); + { + /* + * CSPLIT NYET = TT has not finished the + * LS/FS transaction yet. Retry the CSPLIT + * one microframe later (delayed_channel + * ticks per SOF = per microframe in HS) + * rather than re-arming in the same uframe. + * Bound to ~8 uframes from the SSPLIT + * (FreeBSD DWC_OTG_TT_SLOT_MAX); past that + * the TT result window is gone, so requeue + * for the next interval. + */ + if (USBUnit->hu_Channel[chan].hc_CsplitRetry < 8) + { + USBUnit->hu_Channel[chan].hc_CsplitRetry++; + delayed_channel[chan] = 1; + } + else + { + ULONG interval = req->iouh_Interval; + usb2otg_halt_channel_preserve_char(chan); + if ((req->iouh_Flags & UHFF_SPLITTRANS) && interval < 2) + interval = 2; + ULONG next = (frnm + interval) & 0x7ff; + req->iouh_DriverPrivate1 = (APTR)((frnm << 16) | next); + ADDHEAD(&USBUnit->hu_IntXFerQueue, req); + USBUnit->hu_Channel[chan].hc_Request = NULL; + req = NULL; + } + } else delayed_channel[chan] = 16; } @@ -1676,6 +1718,25 @@ static BOOL usb2otg_process_naktimeout(struct USB2OTGUnit *otg_Unit) continue; } + /* + * SOF scheduler owns active periodic-split channels: a CSPLIT + * re-arm is pending within microframes (delayed_channel != 0), + * so the channel is not stuck. Defer — the watchdog's lost-IRQ + * recovery would otherwise race the SS->CS handoff and corrupt + * CompSplt. The sequencer's bounded retry/requeue is the backstop. + */ + if (chan >= CHAN_INT1 && chan <= CHAN_INT_LAST && + otg_Unit->hu_Channel[chan].hc_SplitState != USB2OTG_SPLIT_IDLE && + delayed_channel[chan] != 0) + { +#if defined(__AROSEXEC_SMP__) + KrnSpinUnLock(&otg_Unit->hu_Lock); +#endif + Enable(); + otg_Unit->hu_Channel[chan].hc_WatchdogCount = 0; + continue; + } + ULONG charbase = rd32le(USB2OTG_CHANNEL_REG(chan, CHARBASE)); if (!(charbase & USB2OTG_HOSTCHAR_ENABLE)) @@ -2061,6 +2122,7 @@ static BOOL usb2otg_process_naktimeout(struct USB2OTGUnit *otg_Unit) { otg_Unit->hu_Channel[chan].hc_DeferCount++; otg_Unit->hu_Channel[chan].hc_WatchdogCount = 0; + D( { struct USB2OTGChannel *hc = &otg_Unit->hu_Channel[chan]; diff --git a/arch/arm-native/soc/broadcom/2708/usb/usb2otg/usb2otg_schedule.c b/arch/arm-native/soc/broadcom/2708/usb/usb2otg/usb2otg_schedule.c index 6c459e43df6..cefd1b1d369 100644 --- a/arch/arm-native/soc/broadcom/2708/usb/usb2otg/usb2otg_schedule.c +++ b/arch/arm-native/soc/broadcom/2708/usb/usb2otg/usb2otg_schedule.c @@ -318,6 +318,17 @@ BOOL FNAME_DEV(SetupChannel)(struct USB2OTGUnit *otg_Unit, int chan) /* Fresh request: clear watchdog defer counter. */ otg_Unit->hu_Channel[chan].hc_DeferCount = 0; + otg_Unit->hu_Channel[chan].hc_CsplitRetry = 0; + + /* + * A fresh arm is always a START-split — clear any CSPLIT-pending left + * over from a prior interval that requeued mid-split. If it leaks set, + * the splitpos logic below picks XactPos=0 (CSPLIT) instead of ALL, + * producing a malformed periodic SSPLIT that halts with bare CHHLTD. + * CSPLIT is only ever armed via StartChannel(quick=1) from the IRQ. + */ + otg_Unit->hu_Channel[chan].hc_SplitCSplitPending = 0; + otg_Unit->hu_Channel[chan].hc_SplitState = USB2OTG_SPLIT_IDLE; /* * Inherit PING state from per-EP bitmap (channel-local flag does @@ -464,8 +475,13 @@ BOOL FNAME_DEV(SetupChannel)(struct USB2OTGUnit *otg_Unit, int chan) /* If split transaction requested limit transfer size to max packet size or 188 bytes, whichever is less */ if (req->iouh_Flags & UHFF_SPLITTRANS) { - if (req->iouh_Req.io_Command == UHCMD_INTXFER || - req->iouh_Req.io_Command == UHCMD_ISOXFER) + /* + * HCCHAR.EC/MC = transactions per microframe; only high-bandwidth + * HS endpoints use >1. A low-/full-speed INT split must be EC=1 + * (matches Linux dwc2 multi_count=1) — EC=3 makes the core expect + * 3 transactions and deschedule the periodic split (bare CHHLTD). + */ + if (req->iouh_Req.io_Command == UHCMD_ISOXFER) reg |= USB2OTG_HOSTCHAR_EC(3); else reg |= USB2OTG_HOSTCHAR_EC(1); @@ -492,6 +508,18 @@ BOOL FNAME_DEV(SetupChannel)(struct USB2OTGUnit *otg_Unit, int chan) ((req->iouh_SplitHubPort & 0x0f))); } + /* + * Periodic-split sequencer: a fresh INT split arm is a START-split. + * Record the phase and the microframe it is issued in so the SS->CS + * handoff can be paced and the watchdog can defer to this scheduler. + */ + if (req->iouh_Req.io_Command == UHCMD_INTXFER) + { + otg_Unit->hu_Channel[chan].hc_SplitState = USB2OTG_SPLIT_SS; + otg_Unit->hu_Channel[chan].hc_SplitSSUframe = + (UWORD)(rd32le(USB2OTG_HOSTFRAMENO) & 0x3fff); + } + if (xfer_size > req->iouh_MaxPktSize) xfer_size = req->iouh_MaxPktSize;