From 733da1226d86d2c7d8075e58fef24b397f3a4079 Mon Sep 17 00:00:00 2001 From: ardnew Date: Tue, 20 Apr 2021 16:35:59 -0500 Subject: [PATCH] USB CDC-ACM UART Rx/Tx functioning for baseline target (Teensy 4.0/4.1) --- src/machine/board_teensy40.go | 5 +- src/machine/machine_mimxrt1062_usb.go | 32 ++- src/machine/usb2/dcd.go | 1 + src/machine/usb2/dcd_mimxrt1062.go | 393 ++++++++++++++++---------- src/machine/usb2/desc.go | 128 +++++---- src/machine/usb2/desc_mimxrt1062.go | 35 ++- src/machine/usb2/hcd.go | 1 + src/machine/usb2/hcd_mimxrt1062.go | 11 +- src/machine/usb2/uart.go | 62 +++- src/machine/usb2/usb.go | 25 ++ src/runtime/runtime_mimxrt1062.go | 7 +- 11 files changed, 455 insertions(+), 245 deletions(-) diff --git a/src/machine/board_teensy40.go b/src/machine/board_teensy40.go index 0488e3b72..18c965a03 100644 --- a/src/machine/board_teensy40.go +++ b/src/machine/board_teensy40.go @@ -105,9 +105,8 @@ func init() { var ( // USBCDC is a legacy class being retained here as temporary wrapper. // See godoc comments on type USBCDC struct definition for details. - USBCDC0 = USBCDC{ - port: 0, // USB_OTG1 (Micro-B port on Teensy 4.0) - buff: NewRingBuffer(), + UART0 = USBCDC{ + port: 0, // USB_OTG1 (Micro-B port on Teensy 4.0/4.1) } ) diff --git a/src/machine/machine_mimxrt1062_usb.go b/src/machine/machine_mimxrt1062_usb.go index 627cb2b0c..696572842 100644 --- a/src/machine/machine_mimxrt1062_usb.go +++ b/src/machine/machine_mimxrt1062_usb.go @@ -1,10 +1,5 @@ // +build mimxrt1062 -// Compatibility wrapper for legacy type USBCDC, which provides USB CDC-ACM -// device class emulation for serial UART communication. -// This functionality is being replaced by a platform-agnostic type usb.UART in -// package "machine/usb". - package machine import ( @@ -18,7 +13,6 @@ import ( // be removed and usb.UART should be used directly instead. type USBCDC struct { port uint8 - buff *RingBuffer uart usb2.UART } @@ -27,3 +21,29 @@ type USBCDC struct { func (cdc *USBCDC) Configure(config UARTConfig) { cdc.uart.Configure(usb2.UARTConfig{BaudRate: config.BaudRate}) } + +// Buffered returns the number of bytes currently stored in the RX buffer. +func (cdc USBCDC) Buffered() int { + return cdc.uart.Buffered() +} + +// ReadByte reads a single byte from the RX buffer. +// If there is no data in the buffer, returns an error. +func (cdc USBCDC) ReadByte() (byte, error) { + return cdc.uart.ReadByte() +} + +// Read from the RX buffer. +func (cdc USBCDC) Read(data []byte) (n int, err error) { + return cdc.uart.Read(data) +} + +// WriteByte writes a single byte of data to the UART interface. +func (cdc USBCDC) WriteByte(c byte) error { + return cdc.uart.WriteByte(c) +} + +// Write data to the UART. +func (cdc USBCDC) Write(data []byte) (n int, err error) { + return cdc.uart.Write(data) +} diff --git a/src/machine/usb2/dcd.go b/src/machine/usb2/dcd.go index 510a9e95d..2c37dddb4 100644 --- a/src/machine/usb2/dcd.go +++ b/src/machine/usb2/dcd.go @@ -3,6 +3,7 @@ package usb2 import "unsafe" type dcd interface { + class() class init() status enable(enable bool) status critical(enter bool) status diff --git a/src/machine/usb2/dcd_mimxrt1062.go b/src/machine/usb2/dcd_mimxrt1062.go index 372952a47..aacd2c322 100644 --- a/src/machine/usb2/dcd_mimxrt1062.go +++ b/src/machine/usb2/dcd_mimxrt1062.go @@ -22,10 +22,10 @@ const dcdInterruptPriority = 3 // deviceController implements USB device controller driver (dcd) interface. type deviceController struct { - core *core // Parent USB core this instance is attached to - port int // USB port index - class class // USB device class - id int // deviceControllerInstance index + core *core // Parent USB core this instance is attached to + port int // USB port index + cc class // USB device class + id int // deviceControllerInstance index bus *nxp.USB_Type phy *nxp.USBPHY_Type @@ -37,8 +37,6 @@ type deviceController struct { stat *dcdEndpoint // endpoint 0 Rx ("out" direction) ctrl *dcdEndpoint // endpoint 0 Tx ("in" direction) - acm *descCDCACMClass - timerInterrupt [2]func() controlNotify uint32 endpointNotify uint32 @@ -84,7 +82,7 @@ func initDCD(port int, class class) (dcd, status) { // Initialize device controller. deviceControllerInstance[i].core = &coreInstance[port] deviceControllerInstance[i].port = port - deviceControllerInstance[i].class = class + deviceControllerInstance[i].cc = class deviceControllerInstance[i].id = i switch port { case 0: @@ -105,17 +103,14 @@ func initDCD(port int, class class) (dcd, status) { //coreInstance[1].dc.interrupt() }) } - switch class.id { - case classDeviceCDCACM: - deviceControllerInstance[i].acm = &descCDCACM[class.config-1] - default: - } return &deviceControllerInstance[i], statusOK } } return nil, statusBusy // No free device controller instances available. } +func (dc *deviceController) class() class { return dc.cc } + func (dc *deviceController) init() status { // reset the controller dc.phy.CTRL_SET.Set(nxp.USBPHY_CTRL_SFTRST) @@ -277,7 +272,7 @@ func (dc *deviceController) interrupt() { dc.bus.ENDPTFLUSH.Set(0xFFFFFFFF) // if dc.bus.PORTSC1.HasBits(nxp.USB_PORTSC1_PR) { // } - switch dc.class.id { + switch dc.cc.id { case classDeviceCDCACM: // TBD: reset CDC-ACM UART? default: @@ -431,21 +426,21 @@ func (dc *deviceController) control(setup dcdSetup) { // SET CONFIGURATION (0x09): case descRequestStandardSetConfiguration: - dc.class.config = int(setup.wValue) - if 0 == dc.class.config || dc.class.config > dcdCount { + dc.cc.config = int(setup.wValue) + if 0 == dc.cc.config || dc.cc.config > dcdCount { // Use default if invalid index received - dc.class.config = 1 + dc.cc.config = 1 } // Respond based on our device class configuration - switch dc.class.id { + switch dc.cc.id { // CDC-ACM (single) case classDeviceCDCACM: dc.bus.ENDPTCTRL2.Set(descCDCACMConfigAttrStatus) // Status Tx dc.bus.ENDPTCTRL3.Set(descCDCACMConfigAttrDataRx) // Bulk data Rx dc.bus.ENDPTCTRL4.Set(descCDCACMConfigAttrDataTx) // Bulk data Tx - dc.serialConfigure() + dc.uartConfigure() dc.controlReceive(dcdPointerNil, 0, false) default: @@ -478,7 +473,7 @@ func (dc *deviceController) control(setup dcdSetup) { // GET CONFIGURATION (0x08): case descRequestStandardGetConfiguration: - dc.controlReply[0] = uint8(dc.class.config) + dc.controlReply[0] = uint8(dc.cc.config) dc.controlTransmit( uintptr(unsafe.Pointer(&dc.controlReply[0])), 1, false) return @@ -585,7 +580,7 @@ func (dc *deviceController) control(setup dcdSetup) { case descCDCRequestSetLineCoding: // Respond based on our device class configuration - switch dc.class.id { + switch dc.cc.id { // CDC-ACM (single) case classDeviceCDCACM: @@ -593,7 +588,7 @@ func (dc *deviceController) control(setup dcdSetup) { if descCDCACMCodingSize == setup.wLength { dc.setup = setup dc.controlReceive( - uintptr(unsafe.Pointer(&descCDCACM[dc.class.config-1].cx[0])), + uintptr(unsafe.Pointer(&descCDCACM[dc.cc.config-1].cx[0])), descCDCACMCodingSize, true) return } @@ -606,7 +601,7 @@ func (dc *deviceController) control(setup dcdSetup) { case descCDCRequestSetControlLineState: // Respond based on our device class configuration - switch dc.class.id { + switch dc.cc.id { // CDC-ACM (single) case classDeviceCDCACM: @@ -616,9 +611,11 @@ func (dc *deviceController) control(setup dcdSetup) { // Control/status interface: case descCDCACMInterfaceCtrl: - acm := &descCDCACM[dc.class.config-1] - acm.cticks = ticks() - acm.rtsdtr = uint8(setup.wValue) + // acm := &descCDCACM[dc.cc.config-1] + // update our emulated UART terminal status + // acm.lineActive = ticks() + // acm.lineCoding.dtr = 0 != setup.wValue&0x01 + // acm.lineCoding.rts = 0 != setup.wValue&0x02 dc.controlReceive(dcdPointerNil, 0, false) return @@ -634,7 +631,7 @@ func (dc *deviceController) control(setup dcdSetup) { case descCDCRequestSendBreak: // Respond based on our device class configuration - switch dc.class.id { + switch dc.cc.id { // CDC-ACM (single) case classDeviceCDCACM: @@ -666,9 +663,9 @@ func (dc *deviceController) control(setup dcdSetup) { //go:inline func (dc *deviceController) controlTransfers() (dat, ack *dcdTransfer) { // control endpoint is device class-specific - switch dc.class.id { + switch dc.cc.id { case classDeviceCDCACM: - return descCDCACM[dc.class.config-1].cd, descCDCACM[dc.class.config-1].ad + return descCDCACM[dc.cc.config-1].cd, descCDCACM[dc.cc.config-1].ad default: return nil, nil } @@ -677,11 +674,11 @@ func (dc *deviceController) controlTransfers() (dat, ack *dcdTransfer) { func (dc *deviceController) controlDescriptor(setup dcdSetup) { // Respond based on our device class configuration - switch dc.class.id { + switch dc.cc.id { // CDC-ACM (single) case classDeviceCDCACM: - acm := &descCDCACM[dc.class.config-1] + acm := &descCDCACM[dc.cc.config-1] dxn := uint8(0) // Determine the type of descriptor being requested @@ -699,64 +696,61 @@ func (dc *deviceController) controlDescriptor(setup dcdSetup) { // String descriptor case descTypeString: + var sd []uint8 + if 0 == uint8(setup.wValue) { + // setup.wIndex contains an arbitrary index referring to a collection of + // strings in some given language. This (setup.wValue = 0x03[00]) is a + // request from the host to determine what that language is. Subsequent + // string requests will populate setup.wIndex with the language code + // returned here in this string descriptor. + sd = acm.locale[int(setup.wIndex)].descriptor[setup.wValue&0xFF][:] + } else { + // setup.wIndex now contains a language code, which we notified in a + // previous request (above: setup.wValue = 0x03[00]). We need to locate + // the set of strings whose language matches the language code given in + // this new setup.wIndex. + for code := range acm.locale { + if setup.wIndex == acm.locale[code].language { + // Found language, check if string descriptor at given index exists + if int(setup.wValue&0xFF) < len(acm.locale[code].descriptor) { + // Found language with a string defined at the requested index. + // Construct a string descriptor dynamically to be transmitted on + // the serial bus. - var s []uint8 + // TODO: Add fields to deviceController and design an API that + // allows the user to define and provide these strings + // prior to deviceController initialization. + // For now, we just always use the descCommon* strings. + var s string + switch uint8(setup.wValue) { + case 1: + s = descCommonManufacturer + case 2: + s = descCommonProduct + case 3: + s = descCommonSerialNumber + } - // Determine the string index requested - switch uint8(setup.wValue) { - - // Language - case 0: - if int(setup.wIndex) < len(acm.locstr) { - s = acm.locstr[setup.wIndex].index[0][:] - } - - // Manufacturer - case 1: - for i := range acm.locstr { - if acm.locstr[i].language == setup.wIndex { - s = acm.locstr[i].index[1][:] - // copy manufacturer string to uint8 buffer as UTF-16 - for n, c := range descManufacturer { - s[2+2*n] = uint8(c) - s[3+2*n] = 0 + // Copy string into string descriptor as UTF-16 + sd = acm.locale[code].descriptor[int(setup.wValue&0xFF)][:] + sd[0] = uint8(2 + 2*len(s)) + sd[1] = descTypeString + for n, c := range s { + if 2+2*n >= len(sd) { + break + } + sd[2+2*n] = uint8(c) + sd[3+2*n] = 0 + } + break // end search for matching language code } - break - } - } - - // Product - case 2: - for i := range acm.locstr { - if acm.locstr[i].language == setup.wIndex { - s = acm.locstr[i].index[2][:] - // copy product string to uint8 buffer as UTF-16 - for n, c := range descProduct { - s[2+2*n] = uint8(c) - s[3+2*n] = 0 - } - break - } - } - - // Serial number - case 3: - for i := range acm.locstr { - if acm.locstr[i].language == setup.wIndex { - s = acm.locstr[i].index[3][:] - // copy serial number string to uint8 buffer as UTF-16 - for n, c := range descSerialNumber { - s[2+2*n] = uint8(c) - s[3+2*n] = 0 - } - break } } } - - if nil != s && len(s) > 0 { - dxn = s[0] - _ = copy(acm.dx[:], s[:dxn]) + // Copy string descriptor into descriptor transmit buffer + if nil != sd && len(sd) >= 0 { + dxn = sd[0] + _ = copy(acm.dx[:], sd[:dxn]) } // Device qualification descriptor @@ -766,7 +760,10 @@ func (dc *deviceController) controlDescriptor(setup dcdSetup) { // Alternate configuration descriptor case descTypeOtherSpeedConfiguration: + // TODO + default: + // Unhandled descriptor type } if dxn > 0 { @@ -884,23 +881,19 @@ func (dc *deviceController) controlComplete() { case descCDCRequestSetLineCoding: // Respond based on our device class configuration - switch dc.class.id { + switch dc.cc.id { // CDC-ACM (single) case classDeviceCDCACM: + acm := &descCDCACM[dc.cc.config-1] // Determine interface destination of the notification switch dc.setup.wIndex { // Control/status interface: case descCDCACMInterfaceCtrl: - - _ = copy(descCDCACM[dc.class.config-1].coding[:], - // descCDCACM[dc.class.config-1].costat[:descCDCACMCodingSize]) - descCDCACM[dc.class.config-1].cx[:]) - var coding descCDCACMLineCoding - if coding.parse(descCDCACM[dc.class.config-1].coding[:]) { - if 134 == coding.baud { + if acm.lineCoding.parse(acm.cx[:]) { + if 134 == acm.lineCoding.baud { dc.enableSofInterrupts(true, descCDCACMInterfaceCount) dc.rebootTimer = 80 } @@ -925,46 +918,6 @@ func (dc *deviceController) controlComplete() { default: // Unhandled request type } - - // // determine interface destination of the notification - // switch dc.setup.wIndex { - // // communication/control interface: - // case descCDCACMInterfaceCtrl: - // // switch on the type and recepient of the request - // switch dc.setup.bmRequestType & - // (descRequestTypeTypeMsk | descRequestTypeRecipientMsk) { - // // interface class request: - // case descRequestTypeRecipientInterface | descRequestTypeTypeClass: - // // identify which request was received - // switch dc.setup.bRequest { - // // CDC_SET_LINE_CODING: - // case descCDCRequestSetLineCoding: - // // respond according to our device class - // switch dc.class.id { - // // CDC-ACM (single) - // case classDeviceCDCACM: - // _ = copy(descCDCACM[dc.class.config-1].coding[:], - // // descCDCACM[dc.class.config-1].costat[:descCDCACMCodingSize]) - // descCDCACM[dc.class.config-1].cx[:]) - // var coding descCDCACMLineCoding - // if coding.parse(descCDCACM[dc.class.config-1].coding[:]) { - // if 134 == coding.baud { - // dc.enableSofInterrupts(true, descCDCACMInterfaceCount) - // dc.rebootTimer = 80 - // } - // } - // default: - // // unhandled device class - // } - // default: - // // unhandled request - // } - // default: - // // unhandled request type or recepient - // } - // default: - // // unhandled interface - // } } // endpointQueueHead returns the queue head for the given endpoint address, @@ -972,9 +925,9 @@ func (dc *deviceController) controlComplete() { //go:inline func (dc *deviceController) endpointQueueHead(endpoint uint8) *dcdEndpoint { // endpoint queue head is device class-specific - switch dc.class.id { + switch dc.cc.id { case classDeviceCDCACM: - return &descCDCACM[dc.class.config-1].qh[endpointIndex(endpoint)] + return &descCDCACM[dc.cc.config-1].qh[endpointIndex(endpoint)] default: return nil } @@ -1144,8 +1097,8 @@ func (dc *deviceController) timerStop(timer int) { } } -func (dc *deviceController) serialConfigure() { - acm := &descCDCACM[dc.class.config-1] +func (dc *deviceController) uartConfigure() { + acm := &descCDCACM[dc.cc.config-1] switch dc.speed { case descDeviceSpeedHigh: acm.rxSize = descCDCACMDataRxHSPacketSize @@ -1162,22 +1115,33 @@ func (dc *deviceController) serialConfigure() { dc.endpointConfigureTx(descCDCACMEndpointStatus, acm.cxSize, false, nil) dc.endpointConfigureRx(descCDCACMEndpointDataRx, - acm.rxSize, false, dc.serialNotify) + acm.rxSize, false, dc.uartNotify) dc.endpointConfigureTx(descCDCACMEndpointDataTx, acm.txSize, true, nil) for i := range acm.rd { - dc.serialReceive(uint8(i)) + dc.uartReceive(uint8(i)) } - dc.timerConfigure(0, 75, dc.serialFlush) + dc.timerConfigure(0, descCDCACMTxSyncUs, dc.uartSync) } -func (dc *deviceController) serialNotify(transfer *dcdTransfer) { - acm := &descCDCACM[dc.class.config-1] +func (dc *deviceController) uartReceive(endpoint uint8) { + acm := &descCDCACM[dc.cc.config-1] + num := uint16(endpoint) & descEndptAddrNumberMsk + buf := &acm.rx[num*descCDCACMRxSize] + dc.irq.Disable() + dc.transferPrepare(&acm.rd[num], buf, acm.rxSize, uint32(endpoint)) + nxp.DeleteDcache(uintptr(unsafe.Pointer(buf)), uintptr(acm.rxSize)) + dc.receive(descCDCACMEndpointDataRx, &acm.rd[num]) + dc.irq.Enable() +} + +func (dc *deviceController) uartNotify(transfer *dcdTransfer) { + acm := &descCDCACM[dc.cc.config-1] len := acm.rxSize - (uint16(transfer.token>>16) & 0x7FFF) p := transfer.param if 0 == len { // zero-length packet (ZLP) - dc.serialReceive(uint8(p)) + dc.uartReceive(uint8(p)) } else { // data packet h := acm.rxHead @@ -1191,7 +1155,7 @@ func (dc *deviceController) serialNotify(transfer *dcdTransfer) { acm.rx[p*descCDCACMRxSize:uint16(p)*descCDCACMRxSize+len]) acm.rxCount[q] = n + len acm.rxFree += len - dc.serialReceive(uint8(p)) + dc.uartReceive(uint8(p)) return } } @@ -1208,23 +1172,148 @@ func (dc *deviceController) serialNotify(transfer *dcdTransfer) { } } -func (dc *deviceController) serialReceive(endpoint uint8) { - ivm := arm.DisableInterrupts() - num := uint16(endpoint) & descEndptAddrNumberMsk - acm := &descCDCACM[dc.class.config-1] - buf := &acm.rx[num*descCDCACMRxSize] - dc.transferPrepare(&acm.rd[num], buf, acm.rxSize, uint32(endpoint)) - nxp.DeleteDcache(uintptr(unsafe.Pointer(buf)), uintptr(acm.rxSize)) - dc.receive(descCDCACMEndpointDataRx, &acm.rd[num]) - arm.EnableInterrupts(ivm) +// uartFlush discards all buffered input (Rx) data. +func (dc *deviceController) uartFlush() { + acm := &descCDCACM[dc.cc.config-1] + tail := acm.rxTail + for tail != acm.rxHead { + tail += 1 + if tail > descCDCACMRDCount { + tail = 0 + } + i := acm.rxQueue[tail] + acm.rxFree -= acm.rxCount[i] - acm.rxIndex[i] + dc.uartReceive(uint8(i)) + acm.rxTail = tail + } } -func (dc *deviceController) serialFlush() { +func (dc *deviceController) uartAvailable() int { + return int(descCDCACM[dc.cc.config-1].rxFree) +} + +func (dc *deviceController) uartPeek() (uint8, bool) { + acm := &descCDCACM[dc.cc.config-1] + tail := acm.rxTail + if tail == acm.rxHead { + return 0, false + } + tail += 1 + if tail > descCDCACMRDCount { + tail = 0 + } + i := acm.rxQueue[tail] + return acm.rx[i*descCDCACMRxSize+acm.rxIndex[i]], true +} + +func (dc *deviceController) uartReadByte() (uint8, bool) { + b := []uint8{0} + ok := dc.uartRead(b) > 0 + return b[0], ok +} + +func (dc *deviceController) uartRead(data []uint8) int { + acm := &descCDCACM[dc.cc.config-1] + read := uint16(0) + size := uint16(len(data)) + tail := acm.rxTail + dest := uint16(0) + dc.irq.Disable() + for read < size && tail != acm.rxHead { + tail += 1 + if tail > descCDCACMRDCount { + tail = 0 + } + i := acm.rxQueue[tail] + count := uint16(size - read) + avail := acm.rxCount[i] - acm.rxIndex[i] + start := i*descCDCACMRxSize + acm.rxIndex[i] + if avail > count { + // partially consume packet + _ = copy(data[dest:], acm.rx[start:start+count]) + acm.rxFree -= count + acm.rxIndex[i] += count + read += count + } else { + // fully consume packet + _ = copy(data[dest:], acm.rx[start:start+avail]) + dest += avail //* uint16(unsafe.Sizeof(&data[0])) + read += avail + acm.rxFree -= avail + acm.rxTail = tail + dc.uartReceive(uint8(i)) + } + } + dc.irq.Enable() + return int(read) +} + +func (dc *deviceController) uartWriteByte(c uint8) bool { + return 1 == dc.uartWrite([]uint8{c}) +} + +func (dc *deviceController) uartWrite(data []uint8) int { + acm := &descCDCACM[dc.cc.config-1] + sent := 0 + size := len(data) + for size > 0 { + xfer := &acm.td[acm.txHead] + wait := false + when := int64(0) + for 0 == acm.txFree { + if 0 == xfer.token&0x80 { + if 0 != xfer.token&0x68 { + // TODO: token contains error, how to handle? + } + acm.txFree = descCDCACMTxSize + acm.txPrev = false + break + } + if !wait { + wait = true + when = ticks() + } + if acm.txPrev { + return sent + } + if ticks()-when > descCDCACMTxTimeoutMs { + acm.txPrev = true + return sent + } + } + buff := acm.tx[(int(acm.txHead)*descCDCACMTxSize)+ + (descCDCACMTxSize-int(acm.txFree)):] + if size > int(acm.txFree) { + _ = copy(buff, data[sent:sent+int(acm.txFree)]) + tx := &acm.tx[int(acm.txHead)*descCDCACMTxSize] + dc.transferPrepare(xfer, tx, descCDCACMTxSize, 0) + nxp.FlushDeleteDcache(uintptr(unsafe.Pointer(tx)), descCDCACMTxSize) + dc.transmit(descCDCACMEndpointDataTx, xfer) + acm.txHead += 1 + if acm.txHead >= descCDCACMTDCount { + acm.txHead = 0 + } + size -= int(acm.txFree) + sent += int(acm.txFree) + acm.txFree = 0 + dc.timerStop(0) + } else { + _ = copy(buff, data[:size]) + acm.txFree -= uint16(size) + sent += size + size = 0 + dc.timerOneShot(0) + } + } + return sent +} + +func (dc *deviceController) uartSync() { const autoFlushTx = true if !autoFlushTx { return } - acm := &descCDCACM[dc.class.config-1] + acm := &descCDCACM[dc.cc.config-1] if 0 == acm.txFree { return } diff --git a/src/machine/usb2/desc.go b/src/machine/usb2/desc.go index 4642a76f2..1b0858f70 100644 --- a/src/machine/usb2/desc.go +++ b/src/machine/usb2/desc.go @@ -4,12 +4,6 @@ const descUSBSpecVersion = uint16(0x0200) // USB 2.0 const descLanguageEnglish = uint16(0x0409) -type descIndexStrings [4][64]uint8 -type descLocalStrings struct { - language uint16 - index descIndexStrings // UTF-16, 32-character maximum length -} - // USB constants defined per specification. const ( @@ -335,55 +329,27 @@ const ( descCDCACMConfigAttrInterrupt = descCDCACMConfigAttr | descEndptAttrSyncTypeSync ) -// descCDCACM0String holds the default string descriptors for CDC-ACM[0], i.e., -// configuration index 1. -var descCDCACM0String = [descCDCACMLanguageCount]descLocalStrings{ - { - language: descLanguageEnglish, - index: descIndexStrings{ - { // 0: language string - 4, - descTypeString, - lsU8(descLanguageEnglish), - msU8(descLanguageEnglish), - }, - { // 1: manufacturer - uint8(2 + 2*len(descManufacturer)), - descTypeString, - }, - { // 2: product - uint8(2 + 2*len(descProduct)), - descTypeString, - }, - { // 3: serial number - uint8(2 + 2*len(descSerialNumber)), - descTypeString, - }, - }, - }, -} - // descCDCACM0Device holds the default device descriptor for CDC-ACM[0], i.e., // configuration index 1. var descCDCACM0Device = [descLengthDevice]uint8{ - descLengthDevice, // Size of this descriptor in bytes - descTypeDevice, // Descriptor Type - lsU8(descUSBSpecVersion), // USB Specification Release Number in BCD (low) - msU8(descUSBSpecVersion), // USB Specification Release Number in BCD (high) - descCDCTypeComm, // Class code (assigned by the USB-IF). - descCDCSubNone, // Subclass code (assigned by the USB-IF). - descCDCProtoNone, // Protocol code (assigned by the USB-IF). - descEndptMaxPktSize, // Maximum packet size for endpoint zero (8, 16, 32, or 64) - lsU8(descVendorID), // Vendor ID (low) (assigned by the USB-IF) - msU8(descVendorID), // Vendor ID (high) (assigned by the USB-IF) - lsU8(descProductID), // Product ID (low) (assigned by the manufacturer) - msU8(descProductID), // Product ID (high) (assigned by the manufacturer) - lsU8(descReleaseID), // Device release number in BCD (low) - msU8(descReleaseID), // Device release number in BCD (high) - 1, // Index of string descriptor describing manufacturer - 2, // Index of string descriptor describing product - 3, // Index of string descriptor describing the device's serial number - descCDCACMCount, // Number of possible configurations + descLengthDevice, // Size of this descriptor in bytes + descTypeDevice, // Descriptor Type + lsU8(descUSBSpecVersion), // USB Specification Release Number in BCD (low) + msU8(descUSBSpecVersion), // USB Specification Release Number in BCD (high) + descCDCTypeComm, // Class code (assigned by the USB-IF). + descCDCSubNone, // Subclass code (assigned by the USB-IF). + descCDCProtoNone, // Protocol code (assigned by the USB-IF). + descEndptMaxPktSize, // Maximum packet size for endpoint zero (8, 16, 32, or 64) + lsU8(descCommonVendorID), // Vendor ID (low) (assigned by the USB-IF) + msU8(descCommonVendorID), // Vendor ID (high) (assigned by the USB-IF) + lsU8(descCommonProductID), // Product ID (low) (assigned by the manufacturer) + msU8(descCommonProductID), // Product ID (high) (assigned by the manufacturer) + lsU8(descCommonReleaseID), // Device release number in BCD (low) + msU8(descCommonReleaseID), // Device release number in BCD (high) + 1, // Index of string descriptor describing manufacturer + 2, // Index of string descriptor describing product + 3, // Index of string descriptor describing the device's serial number + descCDCACMCount, // Number of possible configurations } // descCDCACM0Qualif holds the default device qualification descriptor for @@ -497,24 +463,24 @@ var descCDCACM0Config = [descCDCACMConfigSize]uint8{ // descCDCACMCodingSize defines the length of a CDC-ACM UART line coding buffer. const descCDCACMCodingSize = 7 -// descCDCACM0Coding holds the default UART line coding for CDC-ACM[0], i.e., -// configuration index 1. -var descCDCACM0Coding [descCDCACMCodingSize]uint8 +// descCDCACM0LineCoding holds the default UART line coding for CDC-ACM[0], +// i.e., configuration index 1. +var descCDCACM0LineCoding descCDCACMLineCoding type descCDCACMLineCoding struct { baud uint32 stopBits uint8 parity uint8 numBits uint8 - dtr bool - rts bool + rtsdtr uint8 } func (lc *descCDCACMLineCoding) parse(buffer []uint8) bool { if len(buffer) < descCDCACMCodingSize { return false } - lc.baud = packU32(buffer) + _ = copy(buffer[:], buffer) + lc.baud = packU32(buffer[:]) lc.stopBits = buffer[4] if 0 == lc.stopBits { lc.stopBits = 1 @@ -523,3 +489,49 @@ func (lc *descCDCACMLineCoding) parse(buffer []uint8) bool { lc.numBits = buffer[6] return true } + +const ( + descStringIndexCount = 4 // Language, Manufacturer, Product, Serial Number + descStringSize = 64 // (64-2)/2 = 31 chars each (UTF-16 code points) + // The maximum allowable string descriptor size is 255, or (255-2)/2 = 126 + // available UTF-16 code points. Considering we are allocating this storage at + // compile-time, it seems like an awful waste of space (255*4 = ~1 KiB) just + // to store four strings, which, in all likelihood, will not be modified by + // anyone other than TinyGo devs; 64*4 = 256 B (i.e., 31 UTF-16 code points + // for each string) seems a good compromise. +) + +type ( + // descString is the actual byte array used to hold string descriptors. The + // first two bytes are a USB-specified header (0=length, 1=type), and the + // remaining bytes are UTF-16 code points, ordered low byte-first. If you just + // want to use UTF-8 (or even ASCII), you still need to reserve 2 bytes for + // each symbol, but you can set all of their high bytes 0. + descString [descStringSize]uint8 + // descStringIndex defines an indexed collection of string descriptors for a + // given language. + descStringIndex [descStringIndexCount]descString + // descStringLanguage contains a language code and an indexed collection of + // string descriptors encoded in that language. + descStringLanguage struct { + language uint16 + descriptor descStringIndex + } +) + +// descCDCACM0String holds the default string descriptors for CDC-ACM[0], i.e., +// configuration index 1. +var descCDCACM0String = [descCDCACMLanguageCount]descStringLanguage{ + { // US English string descriptors + language: descLanguageEnglish, + descriptor: descStringIndex{ + { // Language (index 0) + 4, + descTypeString, + lsU8(descLanguageEnglish), + msU8(descLanguageEnglish), + }, + // Actual string descriptors (index > 0) are copied into here at runtime! + }, + }, +} diff --git a/src/machine/usb2/desc_mimxrt1062.go b/src/machine/usb2/desc_mimxrt1062.go index 985cc68ba..91b1a8935 100644 --- a/src/machine/usb2/desc_mimxrt1062.go +++ b/src/machine/usb2/desc_mimxrt1062.go @@ -7,13 +7,14 @@ const descCPUFrequencyHz = 600000000 // General USB device identification constants. const ( - descVendorID = 0x16C0 - descProductID = 0x0483 - descReleaseID = 0x0101 + descCommonVendorID = 0x16C0 + descCommonProductID = 0x0483 + descCommonReleaseID = 0x0101 // BCD (1.1) - descManufacturer = "NXP Semiconductors" - descProduct = "TinyGo USB" - descSerialNumber = "0000000000" + descCommonLanguage = descLanguageEnglish + descCommonManufacturer = "NXP Semiconductors" + descCommonProduct = "TinyGo USB" + descCommonSerialNumber = "1" ) // Constants for USB CDC-ACM device classes. @@ -31,6 +32,9 @@ const ( descCDCACMMaxPower = 50 // 100 mA + descCDCACMTxTimeoutMs = 120 // millisec + descCDCACMTxSyncUs = 75 // microsec + descCDCACMStatusPacketSize = 16 descCDCACMDataRxPacketSize = descCDCACMDataRxHSPacketSize // high-speed descCDCACMDataTxPacketSize = descCDCACMDataTxHSPacketSize // high-speed @@ -117,14 +121,13 @@ var descCDCACM0RDIdx [descCDCACMRDCount]uint16 var descCDCACM0RDQue [descCDCACMRDCount + 1]uint16 type descCDCACMClass struct { - locstr *[descCDCACMLanguageCount]descLocalStrings // string descriptors - device *[descLengthDevice]uint8 // device descriptor - qualif *[descLengthQualification]uint8 // device qualification descriptor - config *[descCDCACMConfigSize]uint8 // configuration descriptor + locale *[descCDCACMLanguageCount]descStringLanguage // string descriptors + device *[descLengthDevice]uint8 // device descriptor + qualif *[descLengthQualification]uint8 // device qualification descriptor + config *[descCDCACMConfigSize]uint8 // configuration descriptor - coding *[descCDCACMCodingSize]uint8 // UART line coding - cticks int64 - rtsdtr uint8 + lineCoding *descCDCACMLineCoding // UART line coding active state + // lineActive int64 // time since last UART DTR/RTS qh *[descCDCACMQHCount]dcdEndpoint // endpoint queue heads @@ -144,6 +147,7 @@ type descCDCACMClass struct { txHead uint8 txFree uint16 + txPrev bool rxHead uint8 rxTail uint8 @@ -157,14 +161,15 @@ type descCDCACMClass struct { // descCDCACM holds the configuration, endpoint, and transfer descriptors, along // with the buffers and control states, for all of the CDC-ACM (single) device // class configurations, ordered by configuration index (offset by -1). +//go:align 32 var descCDCACM = [descCDCACMCount]descCDCACMClass{ { - locstr: &descCDCACM0String, + locale: &descCDCACM0String, device: &descCDCACM0Device, qualif: &descCDCACM0Qualif, config: &descCDCACM0Config, - coding: &descCDCACM0Coding, + lineCoding: &descCDCACM0LineCoding, qh: &descCDCACM0QH, diff --git a/src/machine/usb2/hcd.go b/src/machine/usb2/hcd.go index dbbc43d06..21dc170b2 100644 --- a/src/machine/usb2/hcd.go +++ b/src/machine/usb2/hcd.go @@ -1,6 +1,7 @@ package usb2 type hcd interface { + class() class init() status enable(enable bool) status critical(enter bool) status diff --git a/src/machine/usb2/hcd_mimxrt1062.go b/src/machine/usb2/hcd_mimxrt1062.go index caf1cb9ab..7d39fa78a 100644 --- a/src/machine/usb2/hcd_mimxrt1062.go +++ b/src/machine/usb2/hcd_mimxrt1062.go @@ -20,10 +20,10 @@ const hcdInterruptPriority = 3 // hostController implements USB host controller driver (hcd) interface. type hostController struct { - core *core // Parent USB core this instance is attached to - port int // USB port index - class class // USB host class - id int // hostControllerInstance index + core *core // Parent USB core this instance is attached to + port int // USB port index + cc class // USB host class + id int // hostControllerInstance index bus *nxp.USB_Type phy *nxp.USBPHY_Type @@ -50,6 +50,7 @@ func initHCD(port int, class class) (hcd, status) { // Initialize host controller. hostControllerInstance[i].core = &coreInstance[port] hostControllerInstance[i].port = port + hostControllerInstance[i].cc = class hostControllerInstance[i].id = i switch port { case 0: @@ -76,6 +77,8 @@ func initHCD(port int, class class) (hcd, status) { return nil, statusBusy // No free host controller instances available. } +func (hc *hostController) class() class { return hc.cc } + func (hc *hostController) init() status { return statusOK diff --git a/src/machine/usb2/uart.go b/src/machine/usb2/uart.go index 2ea1b222b..a5e32ff0e 100644 --- a/src/machine/usb2/uart.go +++ b/src/machine/usb2/uart.go @@ -5,7 +5,10 @@ import ( ) var ( - ErrInvalidPort = errors.New("invalid USB port") + 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") ) type ( @@ -24,14 +27,67 @@ type ( func (uart *UART) Configure(config UARTConfig) error { if uart.port >= CoreCount || uart.port >= dcdCount { - return ErrInvalidPort + return ErrUARTInvalidPort } // verify we have a free USB port and take ownership of it var st status uart.core, st = initCore(uart.port, class{id: classDeviceCDCACM, config: 1}) if !st.ok() { - return ErrInvalidPort + return ErrUARTInvalidPort } return nil } + +// Buffered returns the number of bytes currently stored in the RX buffer. +func (uart UART) Buffered() int { + dc, ok := uart.core.dc.(*deviceController) + if !ok { + return 0 + } + return 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) { + dc, ok := uart.core.dc.(*deviceController) + if !ok { + return 0, ErrUARTInvalidCore + } + n, ok := dc.uartReadByte() + if !ok { + return 0, ErrUARTEmptyBuffer + } + return n, nil +} + +// Read from the RX buffer. +func (uart UART) Read(data []byte) (n int, err error) { + dc, ok := uart.core.dc.(*deviceController) + if !ok { + return 0, ErrUARTInvalidCore + } + return dc.uartRead(data), nil +} + +// WriteByte writes a single byte of data to the UART interface. +func (uart UART) WriteByte(c byte) error { + dc, ok := uart.core.dc.(*deviceController) + if !ok { + return ErrUARTInvalidCore + } + if !dc.uartWriteByte(c) { + return ErrUARTWriteFailed + } + return nil +} + +// Write data to the UART. +func (uart UART) Write(data []byte) (n int, err error) { + dc, ok := uart.core.dc.(*deviceController) + if !ok { + return 0, ErrUARTInvalidCore + } + return dc.uartWrite(data), nil +} diff --git a/src/machine/usb2/usb.go b/src/machine/usb2/usb.go index c5e88b10b..e78bdbd04 100644 --- a/src/machine/usb2/usb.go +++ b/src/machine/usb2/usb.go @@ -48,6 +48,13 @@ func (cl class) mode() int { } } +// equals returns true if and only if all fields of the given class are equal to +// those of the receiver cl. +//go:inline +func (cl class) equals(class class) bool { + return cl.id == class.id && cl.config == class.config +} + // CoreCount defines the total number of USB cores to configure in device or // host mode. const CoreCount = dcdCount + hcdCount @@ -79,7 +86,25 @@ func initCore(port int, class class) (*core, status) { if port < 0 || port >= CoreCount || 0 == class.config { return nil, statusInvalid } + if modeIdle != coreInstance[port].mode { + // Check if requested port is already configured as requested class. If so, + // just return a reference to the existing core instead of an error. + // For instance, this will allow TinyGo examples that try to reconfigure the + // USB (CDC-ACM) UART port (which is already configured by the runtime) to + // continue without error. + if coreInstance[port].mode == class.mode() { + switch class.mode() { + case modeDevice: + if coreInstance[port].dc.class().equals(class) { + return &coreInstance[port], statusOK + } + case modeHost: + if coreInstance[port].hc.class().equals(class) { + return &coreInstance[port], statusOK + } + } + } return nil, statusBusy } diff --git a/src/runtime/runtime_mimxrt1062.go b/src/runtime/runtime_mimxrt1062.go index 3831ce146..490627e40 100644 --- a/src/runtime/runtime_mimxrt1062.go +++ b/src/runtime/runtime_mimxrt1062.go @@ -125,13 +125,12 @@ func initUART() { } func initUSB() { - machine.USBCDC0.Configure(machine.UARTConfig{}) + machine.UART0.Configure(machine.UARTConfig{}) } func putchar(c byte) { - // ** TESTING: print byte to both serial UART interfaces ** - //machine.USBCDC0.WriteByte(c) // print to USB UART - machine.UART1.WriteByte(c) // print to hardware UART + machine.UART0.WriteByte(c) // print to USB UART + // machine.UART1.WriteByte(c) // print to hardware UART } func abort() {