USB CDC-ACM UART Rx/Tx functioning for baseline target (Teensy 4.0/4.1)

This commit is contained in:
ardnew
2021-04-20 16:35:59 -05:00
parent 84e61c183a
commit 733da1226d
11 changed files with 455 additions and 245 deletions
+2 -3
View File
@@ -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)
}
)
+26 -6
View File
@@ -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)
}
+1
View File
@@ -3,6 +3,7 @@ package usb2
import "unsafe"
type dcd interface {
class() class
init() status
enable(enable bool) status
critical(enter bool) status
+241 -152
View File
@@ -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
}
+70 -58
View File
@@ -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!
},
},
}
+20 -15
View File
@@ -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,
+1
View File
@@ -1,6 +1,7 @@
package usb2
type hcd interface {
class() class
init() status
enable(enable bool) status
critical(enter bool) status
+7 -4
View File
@@ -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
+59 -3
View File
@@ -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
}
+25
View File
@@ -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
}
+3 -4
View File
@@ -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() {