@@ -26,11 +26,19 @@ type cdcLineInfo struct {
2626
2727// USBCDC is the USB CDC aka serial over USB interface.
2828type USBCDC struct {
29- tx ring512
30- rx ring512
29+ tx ring512
30+ rx ring512
31+
32+ // inflight is the number of bytes currently submitted to the USB IN endpoint.
3133 inflight atomic.Uint32
32- rbuf [1 ]byte
33- wbuf [1 ]byte
34+
35+ // txActive serializes the USB CDC TX pump between Write and the TX
36+ // completion handler. This matters on multicore targets where they can run
37+ // concurrently.
38+ txActive atomic.Uint32
39+
40+ rbuf [1 ]byte
41+ wbuf [1 ]byte
3442}
3543
3644var (
@@ -81,7 +89,7 @@ func (usbcdc *USBCDC) Configure(config machine.UARTConfig) error {
8189
8290// Flush flushes buffered data.
8391func (usbcdc * USBCDC ) Flush () {
84- for usbcdc .tx .Used () > 0 {
92+ for usbcdc .tx .Used () > 0 || usbcdc . txActive . Load () != 0 {
8593 gosched ()
8694 }
8795}
@@ -105,33 +113,50 @@ func (usbcdc *USBCDC) Write(data []byte) (n int, err error) {
105113 return n , nil
106114}
107115
108- // kickTx starts a transfer if none is in flight. Called from main context only .
116+ // kickTx starts the TX pump if it is idle .
109117func (usbcdc * USBCDC ) kickTx () {
110- if usbcdc .inflight . Load () > 0 {
111- return // txhandler will chain the next packet.
118+ if ! usbcdc .txActive . CompareAndSwap ( 0 , 1 ) {
119+ return
112120 }
113121 usbcdc .sendFromRing ()
114122}
115123
116124func (usbcdc * USBCDC ) txhandler () {
117125 inflight := usbcdc .inflight .Load ()
118- usbcdc .inflight .Store (0 )
126+ if inflight == 0 {
127+ return
128+ }
119129 usbcdc .tx .Discard (inflight )
130+ usbcdc .inflight .Store (0 )
120131 usbcdc .sendFromRing ()
121132}
122133
123134// sendFromRing sends one USB packet from the ring and sets inflight.
124- // Called from kickTx (main) or txhandler (ISR), but never concurrently
125- // because kickTx only runs when inflight==0 and txhandler only runs
126- // when inflight>0 .
135+ //
136+ // The caller must own txActive. Ownership starts in kickTx and is kept across
137+ // TX completion interrupts until the TX ring is empty .
127138func (usbcdc * USBCDC ) sendFromRing () {
128- d1 , _ := usbcdc .tx .Peek ()
129- if len (d1 ) == 0 {
139+ for {
140+ d1 , _ := usbcdc .tx .Peek ()
141+ if len (d1 ) == 0 {
142+ usbcdc .txActive .Store (0 )
143+
144+ // Avoid a missed wakeup: Write may append data while txActive is
145+ // still set, causing kickTx to return without starting a transfer.
146+ if usbcdc .tx .Used () == 0 {
147+ return
148+ }
149+ if ! usbcdc .txActive .CompareAndSwap (0 , 1 ) {
150+ return
151+ }
152+ continue
153+ }
154+
155+ chunk := d1 [:min (usb .EndpointPacketSize , len (d1 ))]
156+ usbcdc .inflight .Store (uint32 (len (chunk )))
157+ machine .SendUSBInPacket (cdcEndpointIn , chunk )
130158 return
131159 }
132- chunk := d1 [:min (usb .EndpointPacketSize , len (d1 ))]
133- usbcdc .inflight .Store (uint32 (len (chunk )))
134- machine .SendUSBInPacket (cdcEndpointIn , chunk )
135160}
136161
137162// WriteByte writes a byte of data to the USB CDC interface.
0 commit comments