From 3b892cbe41875b672251a93972532169c9c53f0e Mon Sep 17 00:00:00 2001 From: ardnew Date: Sat, 19 Feb 2022 23:27:35 -0600 Subject: [PATCH] functional USB CDC-ACM for SAMx51 --- src/machine/usb/dcd.go | 29 +++++- src/machine/usb/desc.go | 30 ++++++ src/machine/usb/desc_atsamd51.go | 20 +++- src/machine/usb/dhw_atsamd51.go | 157 +++++++++++++++++++++++-------- src/machine/usb/queue.go | 22 +++-- src/machine/usb/uart.go | 19 ++-- 6 files changed, 216 insertions(+), 61 deletions(-) diff --git a/src/machine/usb/dcd.go b/src/machine/usb/dcd.go index 630cbd9d6..8936f3be2 100644 --- a/src/machine/usb/dcd.go +++ b/src/machine/usb/dcd.go @@ -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: diff --git a/src/machine/usb/desc.go b/src/machine/usb/desc.go index c417939f6..355a2d1b1 100644 --- a/src/machine/usb/desc.go +++ b/src/machine/usb/desc.go @@ -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 diff --git a/src/machine/usb/desc_atsamd51.go b/src/machine/usb/desc_atsamd51.go index 5f0bf6b84..d7c363ac1 100644 --- a/src/machine/usb/desc_atsamd51.go +++ b/src/machine/usb/desc_atsamd51.go @@ -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, diff --git a/src/machine/usb/dhw_atsamd51.go b/src/machine/usb/dhw_atsamd51.go index 90b77ee5f..d518ad7fb 100644 --- a/src/machine/usb/dhw_atsamd51.go +++ b/src/machine/usb/dhw_atsamd51.go @@ -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 } // ============================================================================= diff --git a/src/machine/usb/queue.go b/src/machine/usb/queue.go index 074d58265..fcd1b4143 100644 --- a/src/machine/usb/queue.go +++ b/src/machine/usb/queue.go @@ -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 diff --git a/src/machine/usb/uart.go b/src/machine/usb/uart.go index c5b776ac0..3dfdc4ed0 100644 --- a/src/machine/usb/uart.go +++ b/src/machine/usb/uart.go @@ -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) }