mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-08-21 04:49:04 +00:00
usb/cdc: Better ring buffer implementation (#5209)
* begin adding ring512 implementation * refactor to make operation driven fuzz test * refactor USBCDC.Read to use ring512 * try txhandler separate to flush * working USBCDC with large packets * fix binary size * remove comment * documentation improvements
This commit is contained in:
committed by
deadprogram
parent
6bb6ea2432
commit
86cc3b6c12
@@ -44,7 +44,7 @@ func TestBinarySize(t *testing.T) {
|
|||||||
// microcontrollers
|
// microcontrollers
|
||||||
{"hifive1b", "examples/echo", 3668, 280, 0, 2244},
|
{"hifive1b", "examples/echo", 3668, 280, 0, 2244},
|
||||||
{"microbit", "examples/serial", 2694, 342, 8, 2248},
|
{"microbit", "examples/serial", 2694, 342, 8, 2248},
|
||||||
{"wioterminal", "examples/pininterrupt", 6837, 1491, 120, 6888},
|
{"wioterminal", "examples/pininterrupt", 7074, 1510, 120, 7248},
|
||||||
|
|
||||||
// TODO: also check wasm. Right now this is difficult, because
|
// TODO: also check wasm. Right now this is difficult, because
|
||||||
// wasm binaries are run through wasm-opt and therefore the
|
// wasm binaries are run through wasm-opt and therefore the
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"machine"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
time.Sleep(2 * time.Second) // connect via serial
|
||||||
|
buf1 := makeBuffer('|', 600)
|
||||||
|
buf2 := makeBuffer('/', 600)
|
||||||
|
println("start")
|
||||||
|
serialWrite(buf1)
|
||||||
|
serialWrite(buf2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeBuffer(sep byte, size int) []byte {
|
||||||
|
buf := make([]byte, size)
|
||||||
|
for i := 0; i < size-5; i += 5 {
|
||||||
|
buf[i] = sep
|
||||||
|
strconv.AppendInt(buf[i+1:i+1:i+5], int64(i), 10)
|
||||||
|
}
|
||||||
|
return buf
|
||||||
|
}
|
||||||
|
|
||||||
|
func serialWrite(b []byte) {
|
||||||
|
machine.Serial.Write(b)
|
||||||
|
}
|
||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
"device/sam"
|
"device/sam"
|
||||||
"errors"
|
"errors"
|
||||||
"internal/binary"
|
"internal/binary"
|
||||||
|
|
||||||
"runtime/interrupt"
|
"runtime/interrupt"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,119 +0,0 @@
|
|||||||
package cdc
|
|
||||||
|
|
||||||
import (
|
|
||||||
"runtime/volatile"
|
|
||||||
)
|
|
||||||
|
|
||||||
const rxRingBufferSize = 128
|
|
||||||
|
|
||||||
// rxRingBuffer is ring buffer implementation inspired by post at
|
|
||||||
// https://www.embeddedrelated.com/showthread/comp.arch.embedded/77084-1.php
|
|
||||||
type rxRingBuffer struct {
|
|
||||||
buffer [rxRingBufferSize]volatile.Register8
|
|
||||||
head volatile.Register8
|
|
||||||
tail volatile.Register8
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewRxRingBuffer returns a new ring buffer.
|
|
||||||
func NewRxRingBuffer() *rxRingBuffer {
|
|
||||||
return &rxRingBuffer{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Used returns how many bytes in buffer have been used.
|
|
||||||
func (rb *rxRingBuffer) Used() uint8 {
|
|
||||||
return uint8(rb.head.Get() - rb.tail.Get())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Put stores a byte in the buffer. If the buffer is already
|
|
||||||
// full, the method will return false.
|
|
||||||
func (rb *rxRingBuffer) Put(val byte) bool {
|
|
||||||
if rb.Used() != rxRingBufferSize {
|
|
||||||
rb.head.Set(rb.head.Get() + 1)
|
|
||||||
rb.buffer[rb.head.Get()%rxRingBufferSize].Set(val)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get returns a byte from the buffer. If the buffer is empty,
|
|
||||||
// the method will return a false as the second value.
|
|
||||||
func (rb *rxRingBuffer) Get() (byte, bool) {
|
|
||||||
if rb.Used() != 0 {
|
|
||||||
rb.tail.Set(rb.tail.Get() + 1)
|
|
||||||
return rb.buffer[rb.tail.Get()%rxRingBufferSize].Get(), true
|
|
||||||
}
|
|
||||||
return 0, false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear resets the head and tail pointer to zero.
|
|
||||||
func (rb *rxRingBuffer) Clear() {
|
|
||||||
rb.head.Set(0)
|
|
||||||
rb.tail.Set(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
const txRingBufferSize = 8
|
|
||||||
|
|
||||||
// txRingBuffer is ring buffer implementation inspired by post at
|
|
||||||
// https://www.embeddedrelated.com/showthread/comp.arch.embedded/77084-1.php
|
|
||||||
type txRingBuffer struct {
|
|
||||||
buffer [txRingBufferSize]struct {
|
|
||||||
buf [64]byte
|
|
||||||
size int
|
|
||||||
}
|
|
||||||
head volatile.Register8
|
|
||||||
tail volatile.Register8
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewTxRingBuffer returns a new ring buffer.
|
|
||||||
func NewTxRingBuffer() *txRingBuffer {
|
|
||||||
return &txRingBuffer{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Used returns how many bytes in buffer have been used.
|
|
||||||
func (rb *txRingBuffer) Used() uint8 {
|
|
||||||
return uint8(rb.head.Get() - rb.tail.Get())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Put stores a byte in the buffer. If the buffer is already
|
|
||||||
// full, the method will return false.
|
|
||||||
func (rb *txRingBuffer) Put(val []byte) bool {
|
|
||||||
if rb.Used() == txRingBufferSize {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if rb.Used() == 0 {
|
|
||||||
rb.head.Set(rb.head.Get() + 1)
|
|
||||||
rb.buffer[rb.head.Get()%txRingBufferSize].size = 0
|
|
||||||
}
|
|
||||||
buf := &rb.buffer[rb.head.Get()%txRingBufferSize]
|
|
||||||
|
|
||||||
for i := 0; i < len(val); i++ {
|
|
||||||
if buf.size == 64 {
|
|
||||||
// next
|
|
||||||
// TODO: Make sure that data is not corrupted even when the buffer is full
|
|
||||||
rb.head.Set(rb.head.Get() + 1)
|
|
||||||
buf = &rb.buffer[rb.head.Get()%txRingBufferSize]
|
|
||||||
rb.buffer[rb.head.Get()%txRingBufferSize].size = 0
|
|
||||||
}
|
|
||||||
buf.buf[buf.size] = val[i]
|
|
||||||
buf.size++
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get returns a byte from the buffer. If the buffer is empty,
|
|
||||||
// the method will return a false as the second value.
|
|
||||||
func (rb *txRingBuffer) Get() ([]byte, bool) {
|
|
||||||
if rb.Used() != 0 {
|
|
||||||
rb.tail.Set(rb.tail.Get() + 1)
|
|
||||||
size := rb.buffer[rb.tail.Get()%txRingBufferSize].size
|
|
||||||
return rb.buffer[rb.tail.Get()%txRingBufferSize].buf[:size], true
|
|
||||||
}
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear resets the head and tail pointer to zero.
|
|
||||||
func (rb *txRingBuffer) Clear() {
|
|
||||||
rb.head.Set(0)
|
|
||||||
rb.tail.Set(0)
|
|
||||||
}
|
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
//go:build baremetal
|
||||||
|
|
||||||
package cdc
|
package cdc
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -9,10 +11,7 @@ const (
|
|||||||
// New returns USBCDC struct.
|
// New returns USBCDC struct.
|
||||||
func New() *USBCDC {
|
func New() *USBCDC {
|
||||||
if USB == nil {
|
if USB == nil {
|
||||||
USB = &USBCDC{
|
USB = &USBCDC{}
|
||||||
rxBuffer: NewRxRingBuffer(),
|
|
||||||
txBuffer: NewTxRingBuffer(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return USB
|
return USB
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,98 @@
|
|||||||
|
package cdc
|
||||||
|
|
||||||
|
import "sync/atomic"
|
||||||
|
|
||||||
|
// ring512 is an interrupt/concurrent-safe ring buffer for a single-producer,
|
||||||
|
// single-consumer (SPSC) pair. The writer calls Put, the reader calls
|
||||||
|
// Peek/Discard. Reset may only be called when neither side is active.
|
||||||
|
//
|
||||||
|
// Implementation uses monotonic counters (head/tail) instead of bounded
|
||||||
|
// offsets. Unsigned subtraction (head - tail) always yields
|
||||||
|
// correct used count regardless of uint32 overflow.
|
||||||
|
type ring512 struct {
|
||||||
|
buf [ringBufLen]byte // power of 2 so compiler can optimize & mask.
|
||||||
|
// head counts total bytes written. Only the writer stores to head.
|
||||||
|
head atomic.Uint32
|
||||||
|
// tail counts total bytes read. Only the reader stores to tail.
|
||||||
|
tail atomic.Uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
ringBufLen = 512
|
||||||
|
ringMask = ringBufLen - 1 // 0x1FF
|
||||||
|
)
|
||||||
|
|
||||||
|
// Reset empties the ring buffer. Must not be called concurrently with
|
||||||
|
// Put, Peek, or Discard.
|
||||||
|
func (r *ring512) Reset() {
|
||||||
|
r.head.Store(0)
|
||||||
|
r.tail.Store(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Free returns number of bytes that can be written via Put.
|
||||||
|
func (r *ring512) Free() uint32 {
|
||||||
|
return ringBufLen - r.Used()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Used returns number of bytes ready to be peeked/discarded.
|
||||||
|
func (r *ring512) Used() uint32 {
|
||||||
|
return r.head.Load() - r.tail.Load()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Peek returns contiguous views into the readable portions of the buffer
|
||||||
|
// without advancing the read position. When data wraps around the end of
|
||||||
|
// the internal buffer, two segments are returned. Second data2 is nil on fully contiguous buffer.
|
||||||
|
// Returns nil,nil when empty.
|
||||||
|
func (r *ring512) Peek() (data1, data2 []byte) {
|
||||||
|
head, tail := r.lims()
|
||||||
|
used := head - tail
|
||||||
|
if used == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
pos := tail & ringMask
|
||||||
|
contig := ringBufLen - pos
|
||||||
|
if contig >= used {
|
||||||
|
return r.buf[pos : pos+used], nil
|
||||||
|
}
|
||||||
|
return r.buf[pos:], r.buf[:used-contig]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Discard marks numBytes as read, advancing the read position.
|
||||||
|
// Panics if numBytes exceeds Used (indicates a race violating SPSC)
|
||||||
|
func (r *ring512) Discard(numBytes uint32) {
|
||||||
|
if numBytes == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
head, tail := r.lims()
|
||||||
|
used := head - tail
|
||||||
|
if numBytes > used {
|
||||||
|
panic("ring: discard exceeds used")
|
||||||
|
}
|
||||||
|
r.tail.Store(tail + numBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put writes data into the ring buffer. Returns true if all data was
|
||||||
|
// written, false if insufficient free space (no partial writes).
|
||||||
|
func (r *ring512) Put(data []byte) bool {
|
||||||
|
wlen := uint32(len(data))
|
||||||
|
if wlen == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
head, tail := r.lims()
|
||||||
|
used := head - tail
|
||||||
|
free := uint32(ringBufLen) - used
|
||||||
|
if wlen > free {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
pos := head & ringMask
|
||||||
|
n := uint32(copy(r.buf[pos:], data))
|
||||||
|
if n < wlen {
|
||||||
|
copy(r.buf[:], data[n:])
|
||||||
|
}
|
||||||
|
r.head.Store(head + wlen)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ring512) lims() (head, tail uint32) {
|
||||||
|
return r.head.Load(), r.tail.Load()
|
||||||
|
}
|
||||||
@@ -0,0 +1,658 @@
|
|||||||
|
package cdc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// peekAll returns all readable data by concatenating both Peek segments.
|
||||||
|
func peekAll(r *ring512) []byte {
|
||||||
|
d1, d2 := r.Peek()
|
||||||
|
if len(d2) == 0 {
|
||||||
|
return d1
|
||||||
|
}
|
||||||
|
out := make([]byte, len(d1)+len(d2))
|
||||||
|
copy(out, d1)
|
||||||
|
copy(out[len(d1):], d2)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// drain reads all data from the ring, verifying Peek length matches Used.
|
||||||
|
func drain(t *testing.T, r *ring512) []byte {
|
||||||
|
t.Helper()
|
||||||
|
d1, d2 := r.Peek()
|
||||||
|
n := uint32(len(d1) + len(d2))
|
||||||
|
if n != r.Used() {
|
||||||
|
t.Fatalf("Peek returned %d bytes but Used()=%d", n, r.Used())
|
||||||
|
}
|
||||||
|
var out []byte
|
||||||
|
out = append(out, d1...)
|
||||||
|
out = append(out, d2...)
|
||||||
|
r.Discard(n)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Basic Functionality ---
|
||||||
|
|
||||||
|
func TestRing512_PutPeekDiscard(t *testing.T) {
|
||||||
|
var r ring512
|
||||||
|
data := []byte("hello world")
|
||||||
|
if !r.Put(data) {
|
||||||
|
t.Fatal("Put failed on empty buffer")
|
||||||
|
}
|
||||||
|
got := peekAll(&r)
|
||||||
|
if !bytes.Equal(got, data) {
|
||||||
|
t.Fatalf("Peek = %q, want %q", got, data)
|
||||||
|
}
|
||||||
|
if r.Used() != uint32(len(data)) {
|
||||||
|
t.Fatalf("Used = %d, want %d", r.Used(), len(data))
|
||||||
|
}
|
||||||
|
r.Discard(uint32(len(data)))
|
||||||
|
if r.Used() != 0 {
|
||||||
|
t.Fatalf("Used after full discard = %d, want 0", r.Used())
|
||||||
|
}
|
||||||
|
d1, d2 := r.Peek()
|
||||||
|
if d1 != nil || d2 != nil {
|
||||||
|
t.Fatalf("Peek after full discard = (%v, %v), want (nil, nil)", d1, d2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRing512_Reset(t *testing.T) {
|
||||||
|
var r ring512
|
||||||
|
r.Put([]byte("data"))
|
||||||
|
r.Reset()
|
||||||
|
if r.Used() != 0 {
|
||||||
|
t.Fatalf("Used after Reset = %d", r.Used())
|
||||||
|
}
|
||||||
|
if r.Free() != 512 {
|
||||||
|
t.Fatalf("Free after Reset = %d", r.Free())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRing512_PutEmpty(t *testing.T) {
|
||||||
|
var r ring512
|
||||||
|
if !r.Put(nil) {
|
||||||
|
t.Fatal("Put nil should succeed")
|
||||||
|
}
|
||||||
|
if !r.Put([]byte{}) {
|
||||||
|
t.Fatal("Put empty slice should succeed")
|
||||||
|
}
|
||||||
|
if r.Used() != 0 {
|
||||||
|
t.Fatalf("Used = %d after empty puts", r.Used())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRing512_PutFull(t *testing.T) {
|
||||||
|
var r ring512
|
||||||
|
data := make([]byte, 512)
|
||||||
|
for i := range data {
|
||||||
|
data[i] = byte(i)
|
||||||
|
}
|
||||||
|
if !r.Put(data) {
|
||||||
|
t.Fatal("Put 512 bytes failed on empty buffer")
|
||||||
|
}
|
||||||
|
if r.Free() != 0 {
|
||||||
|
t.Fatalf("Free after filling = %d", r.Free())
|
||||||
|
}
|
||||||
|
if r.Put([]byte{0x42}) {
|
||||||
|
t.Fatal("Put on full buffer should fail")
|
||||||
|
}
|
||||||
|
got := peekAll(&r)
|
||||||
|
if !bytes.Equal(got, data) {
|
||||||
|
t.Fatalf("Peek full buffer: got len %d, want 512", len(got))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRing512_PutExactFit(t *testing.T) {
|
||||||
|
var r ring512
|
||||||
|
data := make([]byte, 512)
|
||||||
|
for i := range data {
|
||||||
|
data[i] = byte(i)
|
||||||
|
}
|
||||||
|
if !r.Put(data) {
|
||||||
|
t.Fatal("Put exact fit failed")
|
||||||
|
}
|
||||||
|
if r.Used() != 512 {
|
||||||
|
t.Fatalf("Used = %d, want 512", r.Used())
|
||||||
|
}
|
||||||
|
r.Discard(512)
|
||||||
|
if r.Used() != 0 {
|
||||||
|
t.Fatal("buffer not empty after discard all")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Full buffer with wrapped position ---
|
||||||
|
|
||||||
|
func TestRing512_FullBufferWrapped(t *testing.T) {
|
||||||
|
var r ring512
|
||||||
|
|
||||||
|
r.Put(make([]byte, 200))
|
||||||
|
r.Discard(100) // tail=100, head=200, used=100
|
||||||
|
|
||||||
|
free := r.Free()
|
||||||
|
if free != 412 {
|
||||||
|
t.Fatalf("Free = %d, want 412", free)
|
||||||
|
}
|
||||||
|
fill := make([]byte, free)
|
||||||
|
for i := range fill {
|
||||||
|
fill[i] = byte(i)
|
||||||
|
}
|
||||||
|
if !r.Put(fill) {
|
||||||
|
t.Fatalf("Put(%d) into %d free space failed", free, free)
|
||||||
|
}
|
||||||
|
if r.Used() != 512 {
|
||||||
|
t.Fatalf("Used = %d, want 512 (full)", r.Used())
|
||||||
|
}
|
||||||
|
if r.Free() != 0 {
|
||||||
|
t.Fatalf("Free = %d, want 0 (full)", r.Free())
|
||||||
|
}
|
||||||
|
drained := drain(t, &r)
|
||||||
|
if len(drained) != 512 {
|
||||||
|
t.Fatalf("drained %d bytes, want 512", len(drained))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Wrapping Tests ---
|
||||||
|
|
||||||
|
func TestRing512_Wrap(t *testing.T) {
|
||||||
|
var r ring512
|
||||||
|
r.Put(make([]byte, 500))
|
||||||
|
r.Discard(490) // tail=490, head=500, used=10
|
||||||
|
|
||||||
|
wrapData := make([]byte, 30)
|
||||||
|
for i := range wrapData {
|
||||||
|
wrapData[i] = byte(i + 100)
|
||||||
|
}
|
||||||
|
if !r.Put(wrapData) {
|
||||||
|
t.Fatal("wrapped Put failed")
|
||||||
|
}
|
||||||
|
if r.Used() != 40 {
|
||||||
|
t.Fatalf("Used = %d, want 40", r.Used())
|
||||||
|
}
|
||||||
|
|
||||||
|
d1, d2 := r.Peek()
|
||||||
|
if len(d1)+len(d2) != 40 {
|
||||||
|
t.Fatalf("Peek total = %d, want 40", len(d1)+len(d2))
|
||||||
|
}
|
||||||
|
if d2 == nil {
|
||||||
|
t.Fatal("expected wrapped data in d2")
|
||||||
|
}
|
||||||
|
drained := drain(t, &r)
|
||||||
|
if len(drained) != 40 {
|
||||||
|
t.Fatalf("drained %d bytes, want 40", len(drained))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRing512_WrapDataIntegrity(t *testing.T) {
|
||||||
|
var r ring512
|
||||||
|
r.Put(make([]byte, 500))
|
||||||
|
r.Discard(500)
|
||||||
|
|
||||||
|
data := make([]byte, 100)
|
||||||
|
for i := range data {
|
||||||
|
data[i] = byte(i)
|
||||||
|
}
|
||||||
|
if !r.Put(data) {
|
||||||
|
t.Fatal("wrapped put failed")
|
||||||
|
}
|
||||||
|
got := drain(t, &r)
|
||||||
|
if !bytes.Equal(got, data) {
|
||||||
|
t.Fatal("data integrity failure across wrap")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Edge Cases ---
|
||||||
|
|
||||||
|
func TestRing512_DiscardPartial(t *testing.T) {
|
||||||
|
var r ring512
|
||||||
|
r.Put([]byte("abcdefgh"))
|
||||||
|
r.Discard(3)
|
||||||
|
got := peekAll(&r)
|
||||||
|
if !bytes.Equal(got, []byte("defgh")) {
|
||||||
|
t.Fatalf("after partial discard, Peek = %q, want %q", got, "defgh")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRing512_DiscardZero(t *testing.T) {
|
||||||
|
var r ring512
|
||||||
|
r.Discard(0)
|
||||||
|
r.Put([]byte("hi"))
|
||||||
|
r.Discard(0)
|
||||||
|
if r.Used() != 2 {
|
||||||
|
t.Fatalf("Used = %d after zero discard", r.Used())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRing512_DiscardPanicOnOverread(t *testing.T) {
|
||||||
|
var r ring512
|
||||||
|
r.Put([]byte("hi"))
|
||||||
|
defer func() {
|
||||||
|
if rec := recover(); rec == nil {
|
||||||
|
t.Fatal("expected panic on over-discard, got none")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
r.Discard(100)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRing512_FreeUsedInvariant(t *testing.T) {
|
||||||
|
var r ring512
|
||||||
|
check := func(label string) {
|
||||||
|
if r.Free()+r.Used() != 512 {
|
||||||
|
t.Fatalf("%s: Free(%d) + Used(%d) != 512", label, r.Free(), r.Used())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
check("empty")
|
||||||
|
r.Put(make([]byte, 200))
|
||||||
|
check("after put 200")
|
||||||
|
r.Discard(50)
|
||||||
|
check("after discard 50")
|
||||||
|
r.Put(make([]byte, 362))
|
||||||
|
check("after fill to full")
|
||||||
|
r.Discard(512)
|
||||||
|
check("after drain")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRing512_PutOversize(t *testing.T) {
|
||||||
|
var r ring512
|
||||||
|
if r.Put(make([]byte, 513)) {
|
||||||
|
t.Fatal("Put(513) should fail on empty 512 buffer")
|
||||||
|
}
|
||||||
|
r.Put(make([]byte, 1))
|
||||||
|
if r.Put(make([]byte, 512)) {
|
||||||
|
t.Fatal("Put(512) should fail with 1 byte used")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRing512_MultiplePutPeekDiscard(t *testing.T) {
|
||||||
|
var r ring512
|
||||||
|
for i := 0; i < 2000; i++ {
|
||||||
|
msg := []byte(fmt.Sprintf("msg%04d", i))
|
||||||
|
if !r.Put(msg) {
|
||||||
|
t.Fatalf("Put failed at iteration %d, Free=%d, Used=%d", i, r.Free(), r.Used())
|
||||||
|
}
|
||||||
|
got := drain(t, &r)
|
||||||
|
if !bytes.Equal(got, msg) {
|
||||||
|
t.Fatalf("iter %d: got %q, want %q", i, got, msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRing512_HeadTailOverflow(t *testing.T) {
|
||||||
|
var r ring512
|
||||||
|
near := uint32(0xFFFFFFFF - 100)
|
||||||
|
r.head.Store(near)
|
||||||
|
r.tail.Store(near)
|
||||||
|
|
||||||
|
if r.Used() != 0 || r.Free() != 512 {
|
||||||
|
t.Fatalf("Used=%d Free=%d, want 0/512", r.Used(), r.Free())
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < 300; i++ {
|
||||||
|
data := []byte{byte(i), byte(i + 1), byte(i + 2)}
|
||||||
|
if !r.Put(data) {
|
||||||
|
t.Fatalf("Put failed at iter %d (head=%d tail=%d)", i, r.head.Load(), r.tail.Load())
|
||||||
|
}
|
||||||
|
got := drain(t, &r)
|
||||||
|
if !bytes.Equal(got, data) {
|
||||||
|
t.Fatalf("iter %d: data mismatch", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Peek two-segment tests ---
|
||||||
|
|
||||||
|
func TestRing512_PeekNoWrap(t *testing.T) {
|
||||||
|
var r ring512
|
||||||
|
r.Put([]byte("hello"))
|
||||||
|
d1, d2 := r.Peek()
|
||||||
|
if !bytes.Equal(d1, []byte("hello")) {
|
||||||
|
t.Fatalf("d1 = %q, want %q", d1, "hello")
|
||||||
|
}
|
||||||
|
if d2 != nil {
|
||||||
|
t.Fatalf("d2 = %v, want nil", d2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRing512_PeekWrapped(t *testing.T) {
|
||||||
|
var r ring512
|
||||||
|
r.Put(make([]byte, 508))
|
||||||
|
r.Discard(508) // tail=508, head=508
|
||||||
|
|
||||||
|
data := []byte("abcdefghij") // 10 bytes: 4 at end, 6 at start
|
||||||
|
if !r.Put(data) {
|
||||||
|
t.Fatal("put failed")
|
||||||
|
}
|
||||||
|
d1, d2 := r.Peek()
|
||||||
|
if len(d1) != 4 {
|
||||||
|
t.Fatalf("d1 len = %d, want 4", len(d1))
|
||||||
|
}
|
||||||
|
if len(d2) != 6 {
|
||||||
|
t.Fatalf("d2 len = %d, want 6", len(d2))
|
||||||
|
}
|
||||||
|
var got []byte
|
||||||
|
got = append(got, d1...)
|
||||||
|
got = append(got, d2...)
|
||||||
|
if !bytes.Equal(got, data) {
|
||||||
|
t.Fatalf("got %q, want %q", got, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRing512_PeekEmpty(t *testing.T) {
|
||||||
|
var r ring512
|
||||||
|
d1, d2 := r.Peek()
|
||||||
|
if d1 != nil || d2 != nil {
|
||||||
|
t.Fatalf("Peek on empty = (%v, %v), want (nil, nil)", d1, d2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRing512_PeekTotalEqualsUsed(t *testing.T) {
|
||||||
|
var r ring512
|
||||||
|
// Test at many wrap positions.
|
||||||
|
for offset := 0; offset < 512; offset += 37 {
|
||||||
|
r.Reset()
|
||||||
|
if offset > 0 {
|
||||||
|
r.Put(make([]byte, offset))
|
||||||
|
r.Discard(uint32(offset))
|
||||||
|
}
|
||||||
|
sz := 200
|
||||||
|
r.Put(make([]byte, sz))
|
||||||
|
d1, d2 := r.Peek()
|
||||||
|
total := len(d1) + len(d2)
|
||||||
|
if total != sz {
|
||||||
|
t.Fatalf("offset=%d: Peek total=%d, want %d", offset, total, sz)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Concurrent SPSC Test ---
|
||||||
|
|
||||||
|
func TestRing512_SPSC(t *testing.T) {
|
||||||
|
for trial := 0; trial < 20; trial++ {
|
||||||
|
var r ring512
|
||||||
|
const totalBytes = 1 << 18
|
||||||
|
produced := make([]byte, totalBytes)
|
||||||
|
for i := range produced {
|
||||||
|
produced[i] = byte(i + trial)
|
||||||
|
}
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(2)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
sent := 0
|
||||||
|
for sent < totalBytes {
|
||||||
|
chunkSize := 1 + rand.Intn(128)
|
||||||
|
if sent+chunkSize > totalBytes {
|
||||||
|
chunkSize = totalBytes - sent
|
||||||
|
}
|
||||||
|
if r.Put(produced[sent : sent+chunkSize]) {
|
||||||
|
sent += chunkSize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
consumed := make([]byte, 0, totalBytes)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
for len(consumed) < totalBytes {
|
||||||
|
d1, d2 := r.Peek()
|
||||||
|
n := len(d1) + len(d2)
|
||||||
|
if n == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
consumed = append(consumed, d1...)
|
||||||
|
consumed = append(consumed, d2...)
|
||||||
|
r.Discard(uint32(n))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
if !bytes.Equal(consumed, produced) {
|
||||||
|
for i := range consumed {
|
||||||
|
if i >= len(produced) || consumed[i] != produced[i] {
|
||||||
|
t.Fatalf("trial %d: mismatch at byte %d", trial, i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.Fatalf("trial %d: length mismatch: got %d want %d", trial, len(consumed), len(produced))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRing512_SPSCSmallChunks(t *testing.T) {
|
||||||
|
var r ring512
|
||||||
|
const totalBytes = 1 << 16
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(2)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
for i := 0; i < totalBytes; i++ {
|
||||||
|
for !r.Put([]byte{byte(i)}) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
consumed := make([]byte, 0, totalBytes)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
for len(consumed) < totalBytes {
|
||||||
|
d1, d2 := r.Peek()
|
||||||
|
n := len(d1) + len(d2)
|
||||||
|
if n == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
consumed = append(consumed, d1...)
|
||||||
|
consumed = append(consumed, d2...)
|
||||||
|
r.Discard(uint32(n))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
for i, b := range consumed {
|
||||||
|
if b != byte(i) {
|
||||||
|
t.Fatalf("mismatch at %d: got %d want %d", i, b, byte(i))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Fuzz Testing ---
|
||||||
|
|
||||||
|
type refRing struct{ data []byte }
|
||||||
|
|
||||||
|
func (r *refRing) Put(d []byte) bool {
|
||||||
|
if len(r.data)+len(d) > 512 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
r.data = append(r.data, d...)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
func (r *refRing) Discard(n uint32) { r.data = r.data[n:] }
|
||||||
|
func (r *refRing) Used() uint32 { return uint32(len(r.data)) }
|
||||||
|
|
||||||
|
// FuzzRing512 compares ring512 against a trivially correct reference.
|
||||||
|
func FuzzRing512(f *testing.F) {
|
||||||
|
f.Add([]byte{0, 10, 1, 5, 2, 0, 10, 1, 10})
|
||||||
|
f.Add([]byte{0, 0})
|
||||||
|
f.Add([]byte{0, 255, 0, 255, 1, 255, 1, 255})
|
||||||
|
f.Add(bytes.Repeat([]byte{0, 64, 1, 64}, 50))
|
||||||
|
f.Add([]byte{0, 200, 1, 100, 0, 156, 0, 156})
|
||||||
|
|
||||||
|
f.Fuzz(func(t *testing.T, ops []byte) {
|
||||||
|
var ring ring512
|
||||||
|
var ref refRing
|
||||||
|
|
||||||
|
i := 0
|
||||||
|
for i+1 < len(ops) {
|
||||||
|
op := ops[i] % 4
|
||||||
|
arg := ops[i+1]
|
||||||
|
i += 2
|
||||||
|
|
||||||
|
switch op {
|
||||||
|
case 0: // Put
|
||||||
|
size := int(arg)
|
||||||
|
if size > 512 {
|
||||||
|
size = 512
|
||||||
|
}
|
||||||
|
data := make([]byte, size)
|
||||||
|
for j := range data {
|
||||||
|
data[j] = byte(j)
|
||||||
|
}
|
||||||
|
gotOK := ring.Put(data)
|
||||||
|
refOK := ref.Put(data)
|
||||||
|
if gotOK != refOK {
|
||||||
|
t.Fatalf("Put(%d): ring=%v ref=%v", size, gotOK, refOK)
|
||||||
|
}
|
||||||
|
|
||||||
|
case 1: // Discard
|
||||||
|
used := ring.Used()
|
||||||
|
if used != ref.Used() {
|
||||||
|
t.Fatalf("Used mismatch: ring=%d ref=%d", used, ref.Used())
|
||||||
|
}
|
||||||
|
if used == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
n := uint32(arg) % (used + 1)
|
||||||
|
ring.Discard(n)
|
||||||
|
ref.Discard(n)
|
||||||
|
|
||||||
|
case 2: // Peek + verify
|
||||||
|
rUsed := ring.Used()
|
||||||
|
if rUsed != ref.Used() {
|
||||||
|
t.Fatalf("Used mismatch: ring=%d ref=%d", rUsed, ref.Used())
|
||||||
|
}
|
||||||
|
if rUsed == 0 {
|
||||||
|
d1, d2 := ring.Peek()
|
||||||
|
if d1 != nil || d2 != nil {
|
||||||
|
t.Fatal("Peek non-nil on empty ring")
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
got := peekAll(&ring)
|
||||||
|
if uint32(len(got)) != rUsed {
|
||||||
|
t.Fatalf("Peek returned %d bytes, Used=%d", len(got), rUsed)
|
||||||
|
}
|
||||||
|
if !bytes.Equal(got, ref.data) {
|
||||||
|
t.Fatal("Peek data mismatch")
|
||||||
|
}
|
||||||
|
|
||||||
|
case 3: // Invariant
|
||||||
|
if ring.Free()+ring.Used() != 512 {
|
||||||
|
t.Fatalf("invariant: Free(%d)+Used(%d)!=512", ring.Free(), ring.Used())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ring.Used() != ref.Used() {
|
||||||
|
t.Fatalf("final Used mismatch: ring=%d ref=%d", ring.Used(), ref.Used())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// FuzzRing512_Op2 uses raw fuzz bytes as an operation stream with
|
||||||
|
// data integrity tracking.
|
||||||
|
func FuzzRing512_Op2(f *testing.F) {
|
||||||
|
f.Add([]byte{0, 255, 0, 255, 0, 2, 1, 255})
|
||||||
|
f.Add([]byte{0, 200, 1, 100, 0, 255, 0, 157, 1, 255, 1, 255})
|
||||||
|
seed := make([]byte, 40)
|
||||||
|
for i := range seed {
|
||||||
|
if i%4 < 2 {
|
||||||
|
seed[i] = 0
|
||||||
|
} else {
|
||||||
|
seed[i] = 1
|
||||||
|
}
|
||||||
|
if i%2 == 1 {
|
||||||
|
seed[i] = byte(3 + i%13)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f.Add(seed)
|
||||||
|
|
||||||
|
f.Fuzz(func(t *testing.T, ops []byte) {
|
||||||
|
const buflen = 512
|
||||||
|
const maxOps = 128
|
||||||
|
var ring ring512
|
||||||
|
var written []byte
|
||||||
|
totalRead := 0
|
||||||
|
|
||||||
|
i := 0
|
||||||
|
nops := 0
|
||||||
|
for i+1 < len(ops) && nops < maxOps {
|
||||||
|
op := ops[i] % 3
|
||||||
|
sz := int(ops[i+1])
|
||||||
|
i += 2
|
||||||
|
nops++
|
||||||
|
|
||||||
|
switch op {
|
||||||
|
case 0: // Put
|
||||||
|
if sz == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
free := int(ring.Free())
|
||||||
|
if sz > free {
|
||||||
|
sz = free
|
||||||
|
}
|
||||||
|
if sz == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
data := make([]byte, sz)
|
||||||
|
for j := range data {
|
||||||
|
data[j] = byte(len(written) + j)
|
||||||
|
}
|
||||||
|
if !ring.Put(data) {
|
||||||
|
t.Fatalf("Put(%d) failed with Free()=%d", sz, free)
|
||||||
|
}
|
||||||
|
written = append(written, data...)
|
||||||
|
|
||||||
|
case 1: // Read
|
||||||
|
used := int(ring.Used())
|
||||||
|
if used == 0 || sz == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if sz > used {
|
||||||
|
sz = used
|
||||||
|
}
|
||||||
|
d1, d2 := ring.Peek()
|
||||||
|
// Concatenate and take sz bytes.
|
||||||
|
var got []byte
|
||||||
|
if sz <= len(d1) {
|
||||||
|
got = d1[:sz]
|
||||||
|
} else {
|
||||||
|
got = make([]byte, sz)
|
||||||
|
copy(got, d1)
|
||||||
|
copy(got[len(d1):], d2)
|
||||||
|
}
|
||||||
|
expect := written[totalRead : totalRead+sz]
|
||||||
|
if !bytes.Equal(got, expect) {
|
||||||
|
t.Fatalf("data mismatch at read offset %d", totalRead)
|
||||||
|
}
|
||||||
|
ring.Discard(uint32(sz))
|
||||||
|
totalRead += sz
|
||||||
|
|
||||||
|
case 2: // Reset
|
||||||
|
ring.Reset()
|
||||||
|
totalRead = len(written)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ring.Free()+ring.Used() != buflen {
|
||||||
|
t.Fatalf("invariant: Free(%d)+Used(%d)!=%d", ring.Free(), ring.Used(), buflen)
|
||||||
|
}
|
||||||
|
if int(ring.Used()) != len(written)-totalRead {
|
||||||
|
t.Fatalf("Used()=%d expected %d", ring.Used(), len(written)-totalRead)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Final drain.
|
||||||
|
d1, d2 := ring.Peek()
|
||||||
|
var remaining []byte
|
||||||
|
remaining = append(remaining, d1...)
|
||||||
|
remaining = append(remaining, d2...)
|
||||||
|
expect := written[totalRead:]
|
||||||
|
if !bytes.Equal(remaining, expect) {
|
||||||
|
t.Fatalf("final drain mismatch: got %d bytes, want %d", len(remaining), len(expect))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -1,10 +1,13 @@
|
|||||||
|
//go:build baremetal
|
||||||
|
|
||||||
package cdc
|
package cdc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"machine"
|
"machine"
|
||||||
"machine/usb"
|
"machine/usb"
|
||||||
"runtime/interrupt"
|
"sync/atomic"
|
||||||
|
_ "unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -21,55 +24,13 @@ type cdcLineInfo struct {
|
|||||||
lineState uint8
|
lineState uint8
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read from the RX buffer.
|
|
||||||
func (usbcdc *USBCDC) Read(data []byte) (n int, err error) {
|
|
||||||
// check if RX buffer is empty
|
|
||||||
size := usbcdc.Buffered()
|
|
||||||
if size == 0 {
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure we do not read more from buffer than the data slice can hold.
|
|
||||||
if len(data) < size {
|
|
||||||
size = len(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// only read number of bytes used from buffer
|
|
||||||
for i := 0; i < size; i++ {
|
|
||||||
v, _ := usbcdc.ReadByte()
|
|
||||||
data[i] = v
|
|
||||||
}
|
|
||||||
|
|
||||||
return size, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadByte reads a single byte from the RX buffer.
|
|
||||||
// If there is no data in the buffer, returns an error.
|
|
||||||
func (usbcdc *USBCDC) ReadByte() (byte, error) {
|
|
||||||
// check if RX buffer is empty
|
|
||||||
buf, ok := usbcdc.rxBuffer.Get()
|
|
||||||
if !ok {
|
|
||||||
return 0, ErrBufferEmpty
|
|
||||||
}
|
|
||||||
return buf, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Buffered returns the number of bytes currently stored in the RX buffer.
|
|
||||||
func (usbcdc *USBCDC) Buffered() int {
|
|
||||||
return int(usbcdc.rxBuffer.Used())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Receive handles adding data to the UART's data buffer.
|
|
||||||
// Usually called by the IRQ handler for a machine.
|
|
||||||
func (usbcdc *USBCDC) Receive(data byte) {
|
|
||||||
usbcdc.rxBuffer.Put(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// USBCDC is the USB CDC aka serial over USB interface.
|
// USBCDC is the USB CDC aka serial over USB interface.
|
||||||
type USBCDC struct {
|
type USBCDC struct {
|
||||||
rxBuffer *rxRingBuffer
|
tx ring512
|
||||||
txBuffer *txRingBuffer
|
rx ring512
|
||||||
waitTxc bool
|
inflight atomic.Uint32
|
||||||
|
rbuf [1]byte
|
||||||
|
wbuf [1]byte
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -79,6 +40,40 @@ var (
|
|||||||
usbLineInfo = cdcLineInfo{115200, 0x00, 0x00, 0x08, 0x00}
|
usbLineInfo = cdcLineInfo{115200, 0x00, 0x00, 0x08, 0x00}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Read from the RX buffer.
|
||||||
|
func (usbcdc *USBCDC) Read(data []byte) (n int, err error) {
|
||||||
|
data1, data2 := usbcdc.rx.Peek()
|
||||||
|
n += copy(data, data1)
|
||||||
|
n += copy(data[n:], data2)
|
||||||
|
usbcdc.rx.Discard(uint32(n))
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadByte reads a single byte from the RX buffer.
|
||||||
|
// If there is no data in the buffer, returns an error.
|
||||||
|
func (usbcdc *USBCDC) ReadByte() (byte, error) {
|
||||||
|
// check if RX buffer is empty
|
||||||
|
b, _ := usbcdc.rx.Peek()
|
||||||
|
if len(b) > 0 {
|
||||||
|
c := b[0]
|
||||||
|
usbcdc.rx.Discard(1)
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
return 0, ErrBufferEmpty
|
||||||
|
}
|
||||||
|
|
||||||
|
// Buffered returns the number of bytes currently stored in the RX buffer.
|
||||||
|
func (usbcdc *USBCDC) Buffered() int {
|
||||||
|
return int(usbcdc.rx.Used())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Receive handles adding data to the UART's data buffer.
|
||||||
|
// Usually called by the IRQ handler for a machine.
|
||||||
|
func (usbcdc *USBCDC) Receive(data byte) {
|
||||||
|
usbcdc.rbuf[0] = data
|
||||||
|
usbcdc.rx.Put(usbcdc.rbuf[:])
|
||||||
|
}
|
||||||
|
|
||||||
// Configure the USB CDC interface. The config is here for compatibility with the UART interface.
|
// Configure the USB CDC interface. The config is here for compatibility with the UART interface.
|
||||||
func (usbcdc *USBCDC) Configure(config machine.UARTConfig) error {
|
func (usbcdc *USBCDC) Configure(config machine.UARTConfig) error {
|
||||||
return nil
|
return nil
|
||||||
@@ -86,32 +81,63 @@ func (usbcdc *USBCDC) Configure(config machine.UARTConfig) error {
|
|||||||
|
|
||||||
// Flush flushes buffered data.
|
// Flush flushes buffered data.
|
||||||
func (usbcdc *USBCDC) Flush() {
|
func (usbcdc *USBCDC) Flush() {
|
||||||
mask := interrupt.Disable()
|
for usbcdc.tx.Used() > 0 {
|
||||||
if b, ok := usbcdc.txBuffer.Get(); ok {
|
gosched()
|
||||||
machine.SendUSBInPacket(cdcEndpointIn, b)
|
|
||||||
} else {
|
|
||||||
usbcdc.waitTxc = false
|
|
||||||
}
|
}
|
||||||
interrupt.Restore(mask)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write data to the USBCDC.
|
// Write data to the USBCDC.
|
||||||
func (usbcdc *USBCDC) Write(data []byte) (n int, err error) {
|
func (usbcdc *USBCDC) Write(data []byte) (n int, err error) {
|
||||||
if usbLineInfo.lineState > 0 {
|
n = len(data)
|
||||||
mask := interrupt.Disable()
|
if usbLineInfo.lineState <= 0 {
|
||||||
usbcdc.txBuffer.Put(data)
|
return n, nil
|
||||||
if !usbcdc.waitTxc {
|
|
||||||
usbcdc.waitTxc = true
|
|
||||||
usbcdc.Flush()
|
|
||||||
}
|
}
|
||||||
interrupt.Restore(mask)
|
for len(data) > 0 {
|
||||||
|
tosend := min(len(data), int(usbcdc.tx.Free()))
|
||||||
|
if tosend == 0 {
|
||||||
|
gosched()
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
return len(data), nil
|
usbcdc.tx.Put(data[:tosend])
|
||||||
|
data = data[tosend:]
|
||||||
|
usbcdc.kickTx()
|
||||||
|
}
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// kickTx starts a transfer if none is in flight. Called from main context only.
|
||||||
|
func (usbcdc *USBCDC) kickTx() {
|
||||||
|
if usbcdc.inflight.Load() > 0 {
|
||||||
|
return // txhandler will chain the next packet.
|
||||||
|
}
|
||||||
|
usbcdc.sendFromRing()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (usbcdc *USBCDC) txhandler() {
|
||||||
|
inflight := usbcdc.inflight.Load()
|
||||||
|
usbcdc.inflight.Store(0)
|
||||||
|
usbcdc.tx.Discard(inflight)
|
||||||
|
usbcdc.sendFromRing()
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendFromRing sends one USB packet from the ring and sets inflight.
|
||||||
|
// Called from kickTx (main) or txhandler (ISR), but never concurrently
|
||||||
|
// because kickTx only runs when inflight==0 and txhandler only runs
|
||||||
|
// when inflight>0.
|
||||||
|
func (usbcdc *USBCDC) sendFromRing() {
|
||||||
|
d1, _ := usbcdc.tx.Peek()
|
||||||
|
if len(d1) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
chunk := d1[:min(usb.EndpointPacketSize, len(d1))]
|
||||||
|
usbcdc.inflight.Store(uint32(len(chunk)))
|
||||||
|
machine.SendUSBInPacket(cdcEndpointIn, chunk)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteByte writes a byte of data to the USB CDC interface.
|
// WriteByte writes a byte of data to the USB CDC interface.
|
||||||
func (usbcdc *USBCDC) WriteByte(c byte) error {
|
func (usbcdc *USBCDC) WriteByte(c byte) error {
|
||||||
usbcdc.Write([]byte{c})
|
usbcdc.wbuf[0] = c
|
||||||
|
usbcdc.Write(usbcdc.wbuf[:])
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,9 +150,8 @@ func (usbcdc *USBCDC) RTS() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func cdcCallbackRx(b []byte) {
|
func cdcCallbackRx(b []byte) {
|
||||||
for i := range b {
|
free := USB.rx.Free()
|
||||||
USB.Receive(b[i])
|
USB.rx.Put(b[:min(len(b), int(free))])
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var cdcSetupBuff [cdcLineInfoSize]byte
|
var cdcSetupBuff [cdcLineInfoSize]byte
|
||||||
@@ -187,5 +212,8 @@ func cdcSetup(setup usb.Setup) bool {
|
|||||||
|
|
||||||
func EnableUSBCDC() {
|
func EnableUSBCDC() {
|
||||||
machine.USBCDC = New()
|
machine.USBCDC = New()
|
||||||
machine.EnableCDC(USB.Flush, cdcCallbackRx, cdcSetup)
|
machine.EnableCDC(USB.txhandler, cdcCallbackRx, cdcSetup)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//go:linkname gosched runtime.Gosched
|
||||||
|
func gosched()
|
||||||
|
|||||||
Reference in New Issue
Block a user