Skip to content

Commit 8f8c608

Browse files
committed
fix: prevent USB CDC TX stall on ring buffer wrap
1 parent cf5bed8 commit 8f8c608

1 file changed

Lines changed: 32 additions & 2 deletions

File tree

src/machine/usb/cdc/usbcdc.go

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ func (usbcdc *USBCDC) txhandler() {
145145
// still held from the previous packet when entered via txhandler).
146146
func (usbcdc *USBCDC) sendFromRing() {
147147
for {
148-
d1, _ := usbcdc.tx.Peek()
148+
d1, d2 := usbcdc.tx.Peek()
149149
if len(d1) == 0 {
150150
// Release the pump, then re-scan the ring: closes the missed-wakeup
151151
// race where Write Put()s data and kickTx's CAS then fails (txActive
@@ -165,7 +165,37 @@ func (usbcdc *USBCDC) sendFromRing() {
165165
continue // re-claimed; re-peek and keep pumping
166166
}
167167

168-
chunk := d1[:min(usb.EndpointPacketSize, len(d1))]
168+
var chunk []byte
169+
170+
// Prefer filling a full USB packet whenever possible.
171+
// When the ring buffer wraps, the readable data may be split into two
172+
// segments. Sending only the first segment can create a short packet
173+
// before all pending data has been transmitted (for example, 7 bytes
174+
// followed by 64 bytes).
175+
//
176+
// A short packet should normally only be generated at the end of the
177+
// transfer, so combine wrapped segments to fill the endpoint packet size.
178+
179+
// The first segment is large enough: use it directly without copying.
180+
if len(d1) >= usb.EndpointPacketSize {
181+
chunk = d1[:usb.EndpointPacketSize]
182+
183+
// The data wraps around the ring buffer: combine segments.
184+
} else if len(d1)+len(d2) >= usb.EndpointPacketSize {
185+
var buf [usb.EndpointPacketSize]byte
186+
187+
n := copy(buf[:], d1)
188+
copy(buf[n:], d2)
189+
190+
// sendUSBPacket() copies the data into USB DPRAM immediately.
191+
// Do not keep this slice after returning from sendUSBPacket().
192+
chunk = buf[:usb.EndpointPacketSize]
193+
194+
// Less than one full packet remains: send the final short packet.
195+
} else {
196+
chunk = d1
197+
}
198+
169199
usbcdc.inflight.Store(uint32(len(chunk)))
170200
machine.SendUSBInPacket(cdcEndpointIn, chunk)
171201
return // in flight; txActive stays set, txhandler continues

0 commit comments

Comments
 (0)