From 8f8c6086f42f698f71c2e1e918d6b24b08c7ac78 Mon Sep 17 00:00:00 2001 From: NoboNobo Date: Tue, 21 Jul 2026 09:53:43 +0900 Subject: [PATCH 1/2] fix: prevent USB CDC TX stall on ring buffer wrap --- src/machine/usb/cdc/usbcdc.go | 34 ++++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/src/machine/usb/cdc/usbcdc.go b/src/machine/usb/cdc/usbcdc.go index 11b5a8f2e2..548b7d8c1e 100644 --- a/src/machine/usb/cdc/usbcdc.go +++ b/src/machine/usb/cdc/usbcdc.go @@ -145,7 +145,7 @@ func (usbcdc *USBCDC) txhandler() { // still held from the previous packet when entered via txhandler). func (usbcdc *USBCDC) sendFromRing() { for { - d1, _ := usbcdc.tx.Peek() + d1, d2 := usbcdc.tx.Peek() if len(d1) == 0 { // Release the pump, then re-scan the ring: closes the missed-wakeup // race where Write Put()s data and kickTx's CAS then fails (txActive @@ -165,7 +165,37 @@ func (usbcdc *USBCDC) sendFromRing() { continue // re-claimed; re-peek and keep pumping } - chunk := d1[:min(usb.EndpointPacketSize, len(d1))] + var chunk []byte + + // Prefer filling a full USB packet whenever possible. + // When the ring buffer wraps, the readable data may be split into two + // segments. Sending only the first segment can create a short packet + // before all pending data has been transmitted (for example, 7 bytes + // followed by 64 bytes). + // + // A short packet should normally only be generated at the end of the + // transfer, so combine wrapped segments to fill the endpoint packet size. + + // The first segment is large enough: use it directly without copying. + if len(d1) >= usb.EndpointPacketSize { + chunk = d1[:usb.EndpointPacketSize] + + // The data wraps around the ring buffer: combine segments. + } else if len(d1)+len(d2) >= usb.EndpointPacketSize { + var buf [usb.EndpointPacketSize]byte + + n := copy(buf[:], d1) + copy(buf[n:], d2) + + // sendUSBPacket() copies the data into USB DPRAM immediately. + // Do not keep this slice after returning from sendUSBPacket(). + chunk = buf[:usb.EndpointPacketSize] + + // Less than one full packet remains: send the final short packet. + } else { + chunk = d1 + } + usbcdc.inflight.Store(uint32(len(chunk))) machine.SendUSBInPacket(cdcEndpointIn, chunk) return // in flight; txActive stays set, txhandler continues From d88bdfd1e081933c4ca607819fbdc3b62372d6d4 Mon Sep 17 00:00:00 2001 From: NoboNobo Date: Wed, 22 Jul 2026 15:05:43 +0900 Subject: [PATCH 2/2] fix binary size for test --- builder/sizes_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builder/sizes_test.go b/builder/sizes_test.go index f6bb112fa0..0103bfb7c2 100644 --- a/builder/sizes_test.go +++ b/builder/sizes_test.go @@ -44,7 +44,7 @@ func TestBinarySize(t *testing.T) { // microcontrollers {"hifive1b", "examples/echo", 4277, 307, 0, 2260}, {"microbit", "examples/serial", 2836, 368, 8, 2256}, - {"wioterminal", "examples/pininterrupt", 8013, 1663, 132, 7488}, + {"wioterminal", "examples/pininterrupt", 8095, 1661, 132, 7488}, // TODO: also check wasm. Right now this is difficult, because // wasm binaries are run through wasm-opt and therefore the