Skip to content

Commit ce5c04a

Browse files
author
rdon
committed
usb/cdc: serialize TX pump across cores
1 parent e9d78a7 commit ce5c04a

2 files changed

Lines changed: 43 additions & 18 deletions

File tree

builder/sizes_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ func TestBinarySize(t *testing.T) {
4444
// microcontrollers
4545
{"hifive1b", "examples/echo", 3680, 280, 0, 2252},
4646
{"microbit", "examples/serial", 2694, 342, 8, 2248},
47-
{"wioterminal", "examples/pininterrupt", 7074, 1510, 120, 7248},
47+
{"wioterminal", "examples/pininterrupt", 7184, 1508, 120, 7256},
4848

4949
// TODO: also check wasm. Right now this is difficult, because
5050
// wasm binaries are run through wasm-opt and therefore the

src/machine/usb/cdc/usbcdc.go

Lines changed: 42 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,19 @@ type cdcLineInfo struct {
2626

2727
// USBCDC is the USB CDC aka serial over USB interface.
2828
type 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

3644
var (
@@ -81,7 +89,7 @@ func (usbcdc *USBCDC) Configure(config machine.UARTConfig) error {
8189

8290
// Flush flushes buffered data.
8391
func (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.
109117
func (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

116124
func (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.
127138
func (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

Comments
 (0)