Pin gadget MAC + self-heal dwc2 USB endpoint lockups causing intermittent connect failures - #31
Pin gadget MAC + self-heal dwc2 USB endpoint lockups causing intermittent connect failures#31jayofelony wants to merge 3 commits into
Conversation
g_ether picks a random host_addr/dev_addr on every module load, so Linux hosts (which key interface naming and NM/udev state off the MAC) see a "new" device every boot and can flake on DHCP. Derive stable, per-board MACs from the CPU serial and stamp them at install time. Also widen the CLIENT-mode DHCP timeout/probe window: arping only confirms the host's ICS gateway IP is up, not that its DHCP server is actually ready yet, so the old 6s timeout regularly lost that race and bounced back to SHARED before retrying.
Cold-boot connects intermittently die at the USB transport layer (dwc2 peripheral-mode OUT-endpoint lockup, host's usbnet 5s TX watchdog force- unlinking the URB) and stay dead until the gadget module is reloaded - not recoverable by replugging the cable or NM/dnsmasq retries alone. ics-watch now watches for the kernel's dwc2_hsotg_ep_stop_xfr/ txfifo_flush timeout messages, plus a fallback rx_packets-based watchdog for wedges that log nothing on the Pi side, and automatically reloads g_ether to recover within seconds instead of requiring a manual power cycle. Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01KLBU4XMz6w6bcupKe5Dg2T
Release with the dwc2 USB gadget endpoint self-heal, pinned gadget MACs and loosened CLIENT DHCP race, plus the NM profile / device-matching fixes accumulated since 1.0.6. Co-Authored-By: Claude Sonnet 5 <[email protected]>
|
Hit what looks like the same class of failure from a macOS host and posted the full data in #27 — short version: host→device delivery is essentially dead, device→host is fine. Counters sampled simultaneously on both sides over one 280s session, Pi Zero 2 W / trixie / kernel 6.18.39, Apple Silicon host:
One packet arrived out of 161 the host's driver reported sending. Opkts froze at exactly 161 across five separate re-enumerations. I can't yet confirm the Your commit 1 matches what I saw independently: six boots, six distinct gadget MACs, no One thing worth checking in that commit though. On this install
Since Happy to test a build of this branch on the setup above. |
|
Correcting my earlier comment on the serial, and reporting back on the dmesg capture. Serial: I said the awk in postinst comes back blank and guessed chroot. On the running board it isn't blank: So the serial is available at runtime, yet That changes what a fix needs to do. A guard that skips stamping on an empty serial protects fresh installs but does nothing for images that already shipped with a blank value, and those are the ones that will collide once dmesg: ran a full 6 minute capture with snapshots every 60s. No For what it's worth the shape is: host queues 161 packets with no errors, device receives 1 with no drops and no errors, carrier stays up the whole time, Still happy to test a build of this branch. |
|
Wow that's a lot to read. |
|
Following up on my two comments above. The host to device failure I reported is a macOS bug, not the lockup this PR handles, so please disregard that part. I re-tested with the dwc2 interrupt count sampled alongside the packet counters. Across all 36 ten second intervals of a 360s run, the device's dwc2 IRQ delta exactly matched its tx_packets delta, so every interrupt was an outbound completion and none came from an inbound transfer. The gadget's controller never saw the host's packets. macOS AppleUserECM stops transmitting after exactly 161 packets per enumeration with Oerrs 0 and nothing logged. Filed as FB24614121. That also explains why I could not find your The MAC part of my comments is unaffected and still stands. No Happy to test a build of this branch, though I can no longer offer a working macOS host as a test bed until Apple fixes theirs. |
Summary
Investigation into intermittent IPv4 failures over the USB gadget on Linux hosts (
enx...interface stuck at70 (connecting)/ falls back to IPv6-link-local / APIPA), starting from #27. Two fixes are included, addressing two distinct problems found along the way. Neither is a complete root-cause fix for the underlying hardware issue — see "What's still open" below.Commit 1: Pin gadget MAC addresses and loosen CLIENT DHCP race
Without
host_addr/dev_addrset ing_ether.conf,g_ether/u_ethergenerates a random MAC pair on every module load (every boot / gadget re-enumeration). Linux hosts name USB-Ethernet interfaces from the MAC (enx<mac>) and track NM/udev state per-device, so a new MAC every boot makes the host treat the gadget as a brand-new device each time.debian/rpi-usb-gadget.postinst: derives two stable, valid locally-administered/unicast MACs from the board's CPU serial (same pattern as the existing<serial>stamping) and stamps them intog_ether.confat install time.rpi-usb-gadget:CLIENT_DHCP_TIMEOUT6s → 15s;ics-watch.c:CLIENT_PROBE_WINDOW15s → 25s — gives DHCP more time to actually bind before the CLIENT/SHARED arbitration gives up and bounces back.This alone did not fix the core issue — see next commit.
Commit 2: Self-heal for dwc2 USB gadget endpoint lockups
What's actually happening
Correlating
dmesg -Ton both the Pi and a Linux host during a cold-boot repro (Pi freshly booted, host already running, then plugged in) shows this is a USB transport-layer failure, not a DHCP-readiness race:cdc_etherdriver — unlike Windows, which has no native ECM driver and always negotiates RNDIS instead, so the two OSes exercise different endpoint/transfer-handling code paths on the same dwc2 hardware).usbnetdriver has a hardcoded 5s TX watchdog (TX_TIMEOUT_JIFFIESindrivers/net/usb/usbnet.c). It fires repeatedly (NETDEV WATCHDOG: transmit queue 0 timed out, every ~5-6s) and force-unlinks the in-flight URB each time.dwc2_hsotg_ep_stop_xfr: timeout GINTSTS.GOUTNAKEFF/timeout DOEPCTL.EPDisable— the dwc2 driver's own attempt to cleanly disable the now-dead OUT endpoint also times out.device descriptor read/64, error -110,device not accepting address, error -71,unable to enumerate USB device). Previously the only known recovery was a full power cycle of the Pi.The fix
ics-watchnow watches for this condition and self-heals by reloadingg_ether(confirmed to fully recover the link within seconds — real DHCP lease obtained on the very next enumeration, no reboot needed):journalctl -kf) for the twodwc2_hsotg_ep_stop_xfr/dwc2_hsotg_txfifo_flushtimeout messages above, and reloads immediately on a match (guarded so it doesn't fire while carrier is already up and healthy — e.g. against a marker logged by its own prior recovery's teardown).rx_packetscounter: if carrier has been up forCARRIER_STUCK_TIMEOUT(10s) with zero packets actually received from the host, treats it as wedged. Deliberately RX-only, not RX+TX —ics-watch's own periodic ICS-gatewayarping()probes transmit from the same interface and were masking a genuinely dead link with self-generated TX "activity" during testing.A note on the recovery mechanism itself
A lighter alternative was tried during development — unbinding/rebinding just the dwc2 platform device via sysfs (
/sys/bus/platform/drivers/dwc2/{unbind,bind}) instead of a fullrmmod/modprobe, since it goes through the sameprobe()/remove()→dwc2_core_reset()path without unloading the kernel module. It worked in an isolated manual test, but caused the Pi to bootloop once deployed and triggered repeatedly (every ~10-15s, under a since-reverted, more aggressive trigger threshold). The exact cause wasn't confirmed — persistent journald logging wasn't enabled yet on the test device, so the crashing boot's logs were lost — but the correlation was clear enough to revert to the plainrmmod/modprobeapproach, which was validated across many real repro cycles without incident. Worth revisiting with proper crash logging in place if someone wants a lighter-weight recovery path.What's still open
This is a mitigation for the symptom, not a fix for the root cause in dwc2 itself:
g_rx_fifo_size/g_np_tx_fifo_size/g_tx_fifo_sizemodule params) as a possible mitigation for a FIFO-overflow-triggered lockup.ics-watch's own probe traffic cadence in the vulnerable first ~30s after carrier-up contributes to triggering this (it adds broadcast traffic right alongside the host's own DHCP retries, mDNS, IPv6 ND, etc.).g_etherdriver for alibcomposite/configfs-based ECM or RNDIS function, which has a different transfer-scheduling path that some community reports suggest doesn't hit this class of dwc2 hang. This would be a substantial change to how the postinst andg_ether.confset up the gadget, so treating it as a separate, larger follow-up rather than folding it into this PR.If anyone with deeper dwc2/USB gadget peripheral-mode expertise wants to dig into why the OUT endpoint wedges in the first place, that's the real prize here — this PR just makes sure it recovers automatically in the meantime instead of requiring a manual power cycle.
Testing
Reproduced live multiple times against a real Ubuntu host across several sessions (cold Pi boot into an already-running host, and cold host boot into an already-running Pi): confirmed the self-heal detects the wedge and automatically restores a working DHCP lease within seconds, with no manual intervention, across both the kmsg-marker fast path and the rx_packets fallback path independently.