mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-08-07 20:43:40 +00:00
functional USB CDC-ACM for SAMx51
This commit is contained in:
+27
-2
@@ -532,8 +532,8 @@ func (d *dcd) controlSetup(sup dcdSetup) dcdStage {
|
||||
|
||||
// Control/status interface:
|
||||
case descCDCACMInterfaceCtrl:
|
||||
// DTR is bit 0 (mask 0x01), RTS is bit 1 (mask 0x02)
|
||||
d.uartSetLineState(sup.wValue)
|
||||
// CDC Control Line State packet receipt handling occurs in method
|
||||
// controlComplete().
|
||||
d.controlReceive(uintptr(0), 0, false)
|
||||
return dcdStageStatusOut
|
||||
|
||||
@@ -695,6 +695,31 @@ func (d *dcd) controlComplete() {
|
||||
// Unhandled device class
|
||||
}
|
||||
|
||||
// CDC | SET CONTROL LINE STATE (0x22):
|
||||
case descCDCRequestSetControlLineState:
|
||||
|
||||
// Respond based on our device class configuration
|
||||
switch d.cc.id {
|
||||
|
||||
// CDC-ACM (single)
|
||||
case classDeviceCDCACM:
|
||||
|
||||
// Determine interface destination of the request
|
||||
switch d.setup.wIndex {
|
||||
|
||||
// Control/status interface:
|
||||
case descCDCACMInterfaceCtrl:
|
||||
// DTR is bit 0 (mask 0x01), RTS is bit 1 (mask 0x02)
|
||||
d.uartSetLineState(d.setup.wValue)
|
||||
|
||||
default:
|
||||
// Unhandled device interface
|
||||
}
|
||||
|
||||
default:
|
||||
// Unhandled device class
|
||||
}
|
||||
|
||||
// HID | SET REPORT (0x09)
|
||||
case descHIDRequestSetReport:
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package usb
|
||||
|
||||
import "runtime/volatile"
|
||||
|
||||
const descUSBSpecVersion = uint16(0x0200) // USB 2.0
|
||||
|
||||
const descLanguageEnglish = uint16(0x0409) // (US) English
|
||||
@@ -481,6 +483,26 @@ func (s *descCDCACMLineState) parse(v uint16) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
type descCDCACMState uint8
|
||||
|
||||
const (
|
||||
descCDCACMStateConfigured descCDCACMState = iota // Received SET_CONFIGURATION class request
|
||||
descCDCACMStateLineState // Received SET_LINE_STATE after Configured state
|
||||
descCDCACMStateLineCoding // Received SET_LINE_CODING after LineState state
|
||||
)
|
||||
|
||||
func (s *descCDCACMState) set(state descCDCACMState) {
|
||||
if state > *s {
|
||||
// state must be incremented in-order. Otherwise, reset to initial state.
|
||||
if state == *s+1 {
|
||||
*s = state
|
||||
} else {
|
||||
var init descCDCACMState // Reset to zero-value of type.
|
||||
*s = init
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Common configuration constants for the USB CDC-ACM (single) device class.
|
||||
const (
|
||||
descCDCACMLanguageCount = 1 // String descriptor languages available
|
||||
@@ -510,6 +532,14 @@ type descCDCACMClass struct {
|
||||
device *[descLengthDevice]uint8 // device descriptor
|
||||
qualif *[descLengthQualification]uint8 // device qualification descriptor
|
||||
config *[descCDCACMConfigSize]uint8 // configuration descriptor
|
||||
|
||||
state volatile.Register8
|
||||
}
|
||||
|
||||
func (c *descCDCACMClass) setState(state descCDCACMState) {
|
||||
s := descCDCACMState(c.state.Get())
|
||||
s.set(state)
|
||||
c.state.Set(uint8(s))
|
||||
}
|
||||
|
||||
// descCDCACM holds statically-allocated instances for each of the CDC-ACM
|
||||
|
||||
@@ -66,8 +66,8 @@ const (
|
||||
|
||||
// CDC-ACM Data Buffers
|
||||
|
||||
descCDCACMRxSize = 1 * descCDCACMDataRxPacketSize
|
||||
descCDCACMTxSize = 1 * descCDCACMDataTxPacketSize
|
||||
descCDCACMRxSize = descCDCACMDataRxPacketSize
|
||||
descCDCACMTxSize = descCDCACMDataTxPacketSize
|
||||
|
||||
descCDCACMTxTimeoutMs = 120 // millisec
|
||||
descCDCACMTxSyncUs = 75 // microsec
|
||||
@@ -218,6 +218,16 @@ var descCDCACM0Rx [descCDCACMRxSize]uint8
|
||||
//go:align 32
|
||||
var descCDCACM0Tx [descCDCACMTxSize]uint8
|
||||
|
||||
// descCDCACM0Rq is the receive (Rx) transfer buffer for the default CDC-ACM
|
||||
// (single) device class configuration (index 1).
|
||||
//go:align 32
|
||||
var descCDCACM0Rq [descCDCACMRxSize]uint8
|
||||
|
||||
// descCDCACM0Tq is the transmit (Tx) transfer buffer for the default CDC-ACM
|
||||
// (single) device class configuration (index 1).
|
||||
//go:align 32
|
||||
var descCDCACM0Tq [descCDCACMTxSize]uint8
|
||||
|
||||
// descCDCACM0LC is the emulated UART's line coding configuration for the
|
||||
// default CDC-ACM (single) device class configuration (index 1).
|
||||
//go:align 32
|
||||
@@ -252,6 +262,9 @@ type descCDCACMClassData struct {
|
||||
rx *[descCDCACMRxSize]uint8 // bulk data endpoint Rx (OUT) transfer buffer
|
||||
tx *[descCDCACMTxSize]uint8 // bulk data endpoint Tx (IN) transfer buffer
|
||||
|
||||
rxq *[descCDCACMRxSize]uint8
|
||||
txq *[descCDCACMTxSize]uint8
|
||||
|
||||
lc *descCDCACMLineCoding // UART line coding
|
||||
ls *descCDCACMLineState // UART line state
|
||||
|
||||
@@ -284,6 +297,9 @@ var descCDCACMData = [dcdCount]descCDCACMClassData{
|
||||
rx: &descCDCACM0Rx,
|
||||
tx: &descCDCACM0Tx,
|
||||
|
||||
rxq: &descCDCACM0Rq,
|
||||
txq: &descCDCACM0Tq,
|
||||
|
||||
lc: &descCDCACM0LC,
|
||||
ls: &descCDCACM0LS,
|
||||
|
||||
|
||||
+116
-41
@@ -1334,17 +1334,19 @@ func (d *dhw) uartConfigure() {
|
||||
|
||||
acm := &descCDCACM[d.cc.config-1]
|
||||
|
||||
acm.setState(descCDCACMStateConfigured)
|
||||
|
||||
// SAMx51 only supports USB full-speed (FS) operation
|
||||
acm.sxSize = descCDCACMStatusFSPacketSize
|
||||
acm.rxSize = descCDCACMDataRxFSPacketSize
|
||||
acm.txSize = descCDCACMDataTxFSPacketSize
|
||||
|
||||
rq := acm.rx[:]
|
||||
tq := acm.tx[:]
|
||||
rq := acm.rxq[:]
|
||||
tq := acm.txq[:]
|
||||
|
||||
// Rx gives priority to incoming data, Tx gives priority to outgoing data
|
||||
acm.rq.Init(&rq, descCDCACMRxSize, QueueFullDiscardFirst)
|
||||
acm.tq.Init(&tq, descCDCACMTxSize, QueueFullDiscardLast)
|
||||
acm.rq.Init(&rq, int(acm.rxSize), QueueFullDiscardFirst)
|
||||
acm.tq.Init(&tq, int(acm.txSize), QueueFullDiscardLast)
|
||||
|
||||
d.endpointEnable(txEndpoint(descCDCACMEndpointStatus),
|
||||
false, descCDCACMConfigAttrStatus)
|
||||
@@ -1358,13 +1360,14 @@ func (d *dhw) uartConfigure() {
|
||||
d.endpointConfigure(rxEndpoint(descCDCACMEndpointDataRx),
|
||||
d.uartReceiveComplete)
|
||||
d.endpointConfigure(txEndpoint(descCDCACMEndpointDataTx),
|
||||
nil)
|
||||
d.uartTransmitComplete)
|
||||
|
||||
d.uartReceive(descCDCACMEndpointDataRx)
|
||||
d.uartReceiveStart(rxEndpoint(descCDCACMEndpointDataRx))
|
||||
}
|
||||
|
||||
func (d *dhw) uartSetLineState(state uint16) {
|
||||
acm := &descCDCACM[d.cc.config-1]
|
||||
acm.setState(descCDCACMStateLineState)
|
||||
if acm.ls.parse(state) {
|
||||
// TBD: respond to changes in line state?
|
||||
}
|
||||
@@ -1372,6 +1375,7 @@ func (d *dhw) uartSetLineState(state uint16) {
|
||||
|
||||
func (d *dhw) uartSetLineCoding(coding []uint8) {
|
||||
acm := &descCDCACM[d.cc.config-1]
|
||||
acm.setState(descCDCACMStateLineCoding)
|
||||
if acm.lc.parse(coding) {
|
||||
switch acm.lc.baud {
|
||||
case 1200:
|
||||
@@ -1383,10 +1387,24 @@ func (d *dhw) uartSetLineCoding(coding []uint8) {
|
||||
}
|
||||
|
||||
func (d *dhw) uartReady() bool {
|
||||
return d.dcd.state() == dcdStateConfigured
|
||||
acm := &descCDCACM[d.cc.config-1]
|
||||
// Ensure we have received SET_CONFIGURATION class request, and then both
|
||||
// SET_LINE_STATE and SET_LINE_CODING CDC requests (in that order).
|
||||
//
|
||||
// Many USB hosts will send a default SET_LINE_CODING prior to SET_LINE_STATE,
|
||||
// and then another SET_LINE_CODING containing the actual terminal settings.
|
||||
//
|
||||
// We do not want to start UART Rx/Tx transactions until after we have
|
||||
// received the final SET_LINE_CODING with the intended terminal settings.
|
||||
//
|
||||
// The "set" method on type descCDCACMState defines this incremental state
|
||||
// machine, with the UART's current state stored in the volatile.Register8
|
||||
// field "state" of descCDCACMClass.
|
||||
return d.state() == dcdStateConfigured && //acm.ls.dataTerminalReady &&
|
||||
acm.state.Get() == uint8(descCDCACMStateLineCoding)
|
||||
}
|
||||
|
||||
func (d *dhw) uartReceive(endpoint uint8) {
|
||||
func (d *dhw) uartReceiveStart(endpoint uint8) {
|
||||
acm := &descCDCACM[d.cc.config-1]
|
||||
num := uint16(endpoint) & descEndptAddrNumberMsk
|
||||
|
||||
@@ -1396,8 +1414,7 @@ func (d *dhw) uartReceive(endpoint uint8) {
|
||||
if xfer, ok := d.ep[num][descDirRx].pendingTransfer(); ok {
|
||||
// Update the active transfer descriptor on the corresponding endpoint.
|
||||
d.ep[num][descDirRx].setActiveTransfer(xfer)
|
||||
next := xfer.packetStart(xfer.data, xfer.size)
|
||||
d.endpointTransfer(endpoint, xfer.data, next)
|
||||
d.endpointTransfer(endpoint, xfer.data, xfer.size)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1405,65 +1422,123 @@ func (d *dhw) uartReceive(endpoint uint8) {
|
||||
func (d *dhw) uartReceiveComplete(endpoint uint8, size uint32) {
|
||||
acm := &descCDCACM[d.cc.config-1]
|
||||
num := uint16(endpoint) & descEndptAddrNumberMsk
|
||||
_ = acm // TODO(ardnew): elaborate stub
|
||||
|
||||
if xfer, ok := d.ep[num][descDirRx].activeTransfer(); ok {
|
||||
for ptr := xfer.data + uintptr(xfer.sent); ptr < xfer.data+uintptr(size); ptr++ {
|
||||
for ptr := xfer.data; ptr < xfer.data+uintptr(size); ptr++ {
|
||||
acm.rq.Enq(*(*uint8)(unsafe.Pointer(ptr)))
|
||||
}
|
||||
if data, size := xfer.packetComplete(size); size > 0 {
|
||||
d.endpointTransfer(endpoint, data, size)
|
||||
return
|
||||
}
|
||||
}
|
||||
d.ep[num][descDirRx].setActiveTransfer(nil)
|
||||
d.uartReceive(endpoint)
|
||||
d.uartReceiveStart(endpoint)
|
||||
}
|
||||
|
||||
func (d *dhw) uartTransmitStart(endpoint uint8) {
|
||||
acm := &descCDCACM[d.cc.config-1]
|
||||
num := uint16(endpoint) & descEndptAddrNumberMsk
|
||||
|
||||
// BULK data endpoints can simply use a single time slot in the schedule, and
|
||||
// repeatedly transfer from the same transmit buffer (acm.tx) as soon as the
|
||||
// transaction complete callback has been called for a prior transaction.
|
||||
|
||||
// Do not schedule another transfer if one is already active, or if our Tx
|
||||
// FIFO is currently empty.
|
||||
if d.ep[num][descDirTx].hasActiveTransfer() || acm.tq.Len() == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if send, err := acm.tq.Read(acm.tx[:]); err == nil && send > 0 {
|
||||
ready, _ := d.ep[num][descDirTx].scheduleTransfer(
|
||||
uintptr(unsafe.Pointer(&acm.tx[0])), uint32(send))
|
||||
if ready {
|
||||
if xfer, ok := d.ep[num][descDirTx].pendingTransfer(); ok {
|
||||
d.ep[num][descDirTx].setActiveTransfer(xfer)
|
||||
d.endpointTransfer(endpoint, xfer.data, xfer.size)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (d *dhw) uartTransmitComplete(endpoint uint8, size uint32) {
|
||||
acm := &descCDCACM[d.cc.config-1]
|
||||
num := uint16(endpoint) & descEndptAddrNumberMsk
|
||||
if size > 0 && size%acm.txSize == 0 {
|
||||
// Send ZLP if transfer length is a multiple of max packet size.
|
||||
d.endpointTransfer(endpoint, 0, 0)
|
||||
}
|
||||
d.ep[num][descDirTx].setActiveTransfer(nil)
|
||||
d.uartTransmitStart(endpoint)
|
||||
}
|
||||
|
||||
// uartFlush discards all buffered input (Rx) data.
|
||||
func (d *dhw) uartFlush() {
|
||||
acm := &descCDCACM[d.cc.config-1]
|
||||
_ = acm // TODO(ardnew): elaborate stub
|
||||
acm.rq.Reset(int(acm.rxSize))
|
||||
}
|
||||
|
||||
func (d *dhw) uartAvailable() int {
|
||||
return 0
|
||||
acm := &descCDCACM[d.cc.config-1]
|
||||
return acm.rq.Len()
|
||||
}
|
||||
|
||||
func (d *dhw) uartPeek() (uint8, bool) {
|
||||
acm := &descCDCACM[d.cc.config-1]
|
||||
_ = acm // TODO(ardnew): elaborate stub
|
||||
return 0, false
|
||||
return acm.rq.Front()
|
||||
}
|
||||
|
||||
func (d *dhw) uartReadByte() (uint8, bool) {
|
||||
b := []uint8{0}
|
||||
ok := d.uartRead(b) > 0
|
||||
return b[0], ok
|
||||
}
|
||||
|
||||
func (d *dhw) uartRead(data []uint8) int {
|
||||
acm := &descCDCACM[d.cc.config-1]
|
||||
read := uint16(0)
|
||||
size := uint16(len(data))
|
||||
_, _ = acm, size // TODO(ardnew): elaborate stub
|
||||
return int(read)
|
||||
return acm.rq.Deq()
|
||||
}
|
||||
|
||||
func (d *dhw) uartWriteByte(c uint8) bool {
|
||||
return d.uartWrite([]uint8{c}) == 1
|
||||
}
|
||||
|
||||
func (d *dhw) uartWrite(data []uint8) int {
|
||||
func (d *dhw) uartRead(data []uint8) (int, error) {
|
||||
acm := &descCDCACM[d.cc.config-1]
|
||||
sent := 0
|
||||
size := len(data)
|
||||
_, _ = acm, size // TODO(ardnew): elaborate stub
|
||||
return sent
|
||||
return acm.rq.Read(data)
|
||||
}
|
||||
|
||||
func (d *dhw) uartSync() {
|
||||
func (d *dhw) uartWriteByte(c uint8) error {
|
||||
_, err := d.uartWrite([]uint8{c})
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *dhw) uartWrite(data []uint8) (int, error) {
|
||||
|
||||
acm := &descCDCACM[d.cc.config-1]
|
||||
num := uint16(descCDCACMEndpointDataTx) & descEndptAddrNumberMsk
|
||||
|
||||
var sent int
|
||||
var werr error
|
||||
for off := 0; off < len(data); off += int(acm.txSize) {
|
||||
|
||||
cnt := len(data[off:])
|
||||
if cnt > int(acm.txSize) {
|
||||
cnt = int(acm.txSize)
|
||||
}
|
||||
|
||||
// Block until we have room in the Tx FIFO. Space will become available once
|
||||
// the endpoint transaction complete interrupt is raised for the Tx BULK data
|
||||
// endpoint, and then the uartTransmitComplete callback has dequeued data from
|
||||
// the Tx FIFO (acm.tq) into the Tx transmit buffer (acm.tx).
|
||||
for acm.tq.Rem() < cnt {
|
||||
}
|
||||
|
||||
// Add data to Tx FIFO
|
||||
add, err := acm.tq.Write(data[off : off+cnt])
|
||||
if err != nil {
|
||||
werr = err
|
||||
break
|
||||
}
|
||||
sent += add
|
||||
|
||||
if d.ep[num][descDirTx].hasActiveTransfer() {
|
||||
// If there is already a transmit in-progress, wait for its callback to
|
||||
// detect new data in the FIFO and continue the transfer automatically.
|
||||
} else {
|
||||
// Otherwise, initiate a new data transfer.
|
||||
d.uartTransmitStart(txEndpoint(descCDCACMEndpointDataTx))
|
||||
}
|
||||
}
|
||||
|
||||
return sent, werr
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
|
||||
@@ -21,11 +21,11 @@ type Queue struct {
|
||||
}
|
||||
|
||||
var (
|
||||
ErrReadBuffer = errors.New("cannot copy into read buffer")
|
||||
ErrWriteBuffer = errors.New("cannot copy from write buffer")
|
||||
ErrQueueEmpty = errors.New("data queue empty") // Read Error
|
||||
ErrQueueFull = errors.New("data queue full") // Write error
|
||||
ErrQueueNoMode = errors.New("unknown FIFO copy mode")
|
||||
ErrQueueReadZero = errors.New("copy into zero-length buffer")
|
||||
ErrQueueWriteZero = errors.New("copy from zero-length buffer")
|
||||
ErrQueueEmpty = errors.New("buffer empty") // Read underrun
|
||||
ErrQueueFull = errors.New("buffer full") // Write overrun
|
||||
ErrQueueDiscardMode = errors.New("unknown discard mode")
|
||||
)
|
||||
|
||||
// Init initializes the receiver queue's backing data store with the given byte
|
||||
@@ -117,7 +117,7 @@ func (q *Queue) Read(data []uint8) (int, error) {
|
||||
|
||||
less := uint32(len(data))
|
||||
if less == 0 {
|
||||
return 0, ErrReadBuffer
|
||||
return 0, ErrQueueReadZero
|
||||
} // nothing to copy into
|
||||
|
||||
head := q.head.Get()
|
||||
@@ -152,7 +152,7 @@ func (q *Queue) Write(data []uint8) (int, error) {
|
||||
|
||||
// Nothing to copy from is an error regardless of mode.
|
||||
if more == 0 {
|
||||
return 0, ErrWriteBuffer
|
||||
return 0, ErrQueueWriteZero
|
||||
}
|
||||
|
||||
switch q.mode {
|
||||
@@ -167,7 +167,7 @@ func (q *Queue) Write(data []uint8) (int, error) {
|
||||
return 0, ErrQueueFull
|
||||
}
|
||||
|
||||
// xOnly put to unused space.
|
||||
// Only put to unused space.
|
||||
if used+more > q.size.Get() {
|
||||
more = q.size.Get() - used
|
||||
}
|
||||
@@ -210,11 +210,15 @@ func (q *Queue) Write(data []uint8) (int, error) {
|
||||
// Copy a potentially-limited number of elements from data, depending on the
|
||||
// current length of FIFO.
|
||||
for i := uint32(0); i < more; i++ {
|
||||
(*q.fifo)[tail%q.size.Get()] = data[i]
|
||||
(*q.fifo)[tail%q.size.Get()] = data[from+i]
|
||||
tail++
|
||||
}
|
||||
q.tail.Set(tail)
|
||||
|
||||
return int(more), nil
|
||||
}
|
||||
|
||||
return 0, ErrQueueDiscardMode
|
||||
}
|
||||
|
||||
// Front returns the next element that would be dequeued from the receiver FIFO
|
||||
|
||||
+12
-7
@@ -6,9 +6,7 @@ import (
|
||||
|
||||
var (
|
||||
ErrUARTInvalidPort = errors.New("invalid USB port")
|
||||
ErrUARTInvalidCore = errors.New("invalid USB core")
|
||||
ErrUARTEmptyBuffer = errors.New("USB receive buffer empty")
|
||||
ErrUARTWriteFailed = errors.New("USB write failure")
|
||||
)
|
||||
|
||||
// UART represents a virtual serial (UART) device emulation using the USB
|
||||
@@ -43,12 +41,16 @@ func (uart *UART) Ready() bool {
|
||||
|
||||
// Buffered returns the number of bytes currently stored in the RX buffer.
|
||||
func (uart *UART) Buffered() int {
|
||||
for !uart.Ready() {
|
||||
}
|
||||
return uart.core.dc.uartAvailable()
|
||||
}
|
||||
|
||||
// ReadByte reads a single byte from the RX buffer.
|
||||
// If there is no data in the buffer, returns an error.
|
||||
func (uart *UART) ReadByte() (byte, error) {
|
||||
for !uart.Ready() {
|
||||
}
|
||||
n, ok := uart.core.dc.uartReadByte()
|
||||
if !ok {
|
||||
return 0, ErrUARTEmptyBuffer
|
||||
@@ -58,18 +60,21 @@ func (uart *UART) ReadByte() (byte, error) {
|
||||
|
||||
// Read from the RX buffer.
|
||||
func (uart *UART) Read(data []byte) (n int, err error) {
|
||||
return uart.core.dc.uartRead(data), nil
|
||||
for !uart.Ready() {
|
||||
}
|
||||
return uart.core.dc.uartRead(data)
|
||||
}
|
||||
|
||||
// WriteByte writes a single byte of data to the UART interface.
|
||||
func (uart *UART) WriteByte(c byte) error {
|
||||
if !uart.core.dc.uartWriteByte(c) {
|
||||
return ErrUARTWriteFailed
|
||||
for !uart.Ready() {
|
||||
}
|
||||
return nil
|
||||
return uart.core.dc.uartWriteByte(c)
|
||||
}
|
||||
|
||||
// Write data to the UART.
|
||||
func (uart *UART) Write(data []byte) (n int, err error) {
|
||||
return uart.core.dc.uartWrite(data), nil
|
||||
for !uart.Ready() {
|
||||
}
|
||||
return uart.core.dc.uartWrite(data)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user