From 43d5eee1e614d00fd85a045d25fbf13244a5bf3f Mon Sep 17 00:00:00 2001 From: ardnew Date: Tue, 27 Apr 2021 10:14:23 -0500 Subject: [PATCH] begin isolating target-specific USB code --- ...imxrt1062_usb.go => machine_common_usb.go} | 0 src/machine/usb/dcd.go | 615 +++++++- src/machine/usb/dcd_mimxrt1062.go | 1338 ----------------- src/machine/usb/desc.go | 190 ++- src/machine/usb/desc_mimxrt1062.go | 64 +- src/machine/usb/dhw_mimxrt1062.go | 1073 +++++++++++++ src/machine/usb/hcd.go | 57 +- src/machine/usb/hcd_mimxrt1062.go | 130 -- src/machine/usb/hhw_mimxrt1062.go | 59 + src/machine/usb/uart.go | 30 +- src/machine/usb/usb.go | 143 +- src/machine/usb/util_arm.go | 11 + 12 files changed, 1968 insertions(+), 1742 deletions(-) rename src/machine/{machine_mimxrt1062_usb.go => machine_common_usb.go} (100%) delete mode 100644 src/machine/usb/dcd_mimxrt1062.go create mode 100644 src/machine/usb/dhw_mimxrt1062.go delete mode 100644 src/machine/usb/hcd_mimxrt1062.go create mode 100644 src/machine/usb/hhw_mimxrt1062.go diff --git a/src/machine/machine_mimxrt1062_usb.go b/src/machine/machine_common_usb.go similarity index 100% rename from src/machine/machine_mimxrt1062_usb.go rename to src/machine/machine_common_usb.go diff --git a/src/machine/usb/dcd.go b/src/machine/usb/dcd.go index d6527f9b0..ba4393439 100644 --- a/src/machine/usb/dcd.go +++ b/src/machine/usb/dcd.go @@ -1,50 +1,74 @@ package usb +// Implementation of 32-bit target-agnostic USB device controller driver (dcd). + import "unsafe" -type dcd interface { - class() class - init() status - enable(enable bool) status - critical(enter bool) status - interrupt() - receive(endpoint uint8, transfer *dcdTransfer) - transmit(endpoint uint8, transfer *dcdTransfer) - control(setup dcdSetup) +func init() { + if unsafe.Sizeof(uintptr(0)) > 4 { + panic("USB device controller is only supported on 32-bit systems") + } } -const ( - dcdEndpointSize = 64 // bytes - dcdTransferSize = 32 // - dcdSetupSize = 8 // -) +// dcdCount defines the number of USB cores to configure for device mode. It is +// computed as the sum of all declared device configuration descriptors. +const dcdCount = descCDCACMCount // + ... -type dcdEndpoint struct { - config uint32 // 4 - current *dcdTransfer // 4 *dcdTransfer - transfer dcdTransfer // 32 - setup dcdSetup // 8 - // Endpoints are 48-byte data structures. The remaining data extends this to - // 64-byte, and also makes it simpler for implementations to align endpoints - // allocated contiguously on 64-byte boundaries. - first *dcdTransfer // 4 *dcdTransfer - last *dcdTransfer // 4 *dcdTransfer - // After some discussion, perhaps the simplest change to support 64-bit (or - // future TinyGo versions that don't use 8 bytes to refer to a function) is to - // allocate a separate buffer of callbacks. The device controller would assign - // callbacks to unused elements in that buffer, and only the index of that - // callback would be stored here in the descriptor. - callback dcdTransferCallback // 8 +// dcdInstance provides statically-allocated instances of each USB device +// controller configured on this platform. +var dcdInstance [dcdCount]dcd + +// dhwInstance provides statically-allocated instances of each USB hardware +// abstraction for ports configured as device on this platform. +var dhwInstance [dcdCount]dhw + +// dcd implements a generic USB device controller driver (dcd) for 32-bit ARM +// targets. +type dcd struct { + *dhw // USB hardware abstraction layer + + core *core // Parent USB core this instance is attached to + port int // USB port index + cc class // USB device class + id int // USB device controller index } -type dcdTransferCallback func(transfer *dcdTransfer) -type dcdTransfer struct { - next *dcdTransfer // 4 *dcdTransfer - token uint32 // 4 - pointer [5]uintptr // 20 - param uint32 // 4 +// initDCD initializes and assigns a free device controller instance to the +// given USB port. Returns the initialized device controller or nil if no free +// device controller instances remain. +func initDCD(port int, class class) (*dcd, status) { + if 0 == dcdCount { + return nil, statusInvalid // Must have defined device controllers + } + switch class.id { + case classDeviceCDCACM: + if 0 == class.config || class.config > descCDCACMCount { + return nil, statusInvalid // Must have defined descriptors + } + default: + } + // Return the first instance whose assigned core is currently nil. + for i := range dcdInstance { + if nil == dcdInstance[i].core { + // Initialize device controller. + dcdInstance[i].dhw = allocDHW(port, i, &dcdInstance[i]) + dcdInstance[i].core = &coreInstance[port] + dcdInstance[i].port = port + dcdInstance[i].cc = class + dcdInstance[i].id = i + return &dcdInstance[i], statusOK + } + } + return nil, statusBusy // No free device controller instances available. } +// class returns the receiver's current device class configuration. +func (d *dcd) class() class { return d.cc } + +// dcdSetupSize defines the size (bytes) of a USB standard setup packet. +const dcdSetupSize = 8 // bytes + +// dcdSetup contains the USB standard setup packet used to configure a device. type dcdSetup struct { bmRequestType uint8 bRequest uint8 @@ -53,30 +77,507 @@ type dcdSetup struct { wLength uint16 } +// pack returns the receiver setup packet encoded as uint64. func (s dcdSetup) pack() uint64 { - return ((uint64(s.bmRequestType) & 0xFF) << 0) | // uint8 - ((uint64(s.bRequest) & 0xFF) << 8) | // uint8 - ((uint64(s.wValue) & 0xFFFF) << 16) | // uint16 - ((uint64(s.wIndex) & 0xFFFF) << 32) | // uint16 - ((uint64(s.wLength) & 0xFFFF) << 48) // uint16 + return ((uint64(s.bmRequestType) & 0xFF) << 0) | + ((uint64(s.bRequest) & 0xFF) << 8) | + ((uint64(s.wValue) & 0xFFFF) << 16) | + ((uint64(s.wIndex) & 0xFFFF) << 32) | + ((uint64(s.wLength) & 0xFFFF) << 48) } -var ( - // dcdPointerNil is a sentinel value used to indicate a pointer to an invalid - // value. Do not attempt to dereference! - dcdPointerNil = uintptr(0) - // dcdTransferEOL is a sentinel value used to indicate the final node in a - // linked list of transfer descriptors. - dcdTransferEOL = (*dcdTransfer)(unsafe.Pointer(dcdTransferPointerEOL)) - // dcdTransferPointerEOL is the notional memory address of dcdTransferEOL. - // The address does not refer to an actual dcdTransfer, so no attempt should - // be made to dereference it. - dcdTransferPointerEOL = uintptr(1) +// dcdEvent is used to describe virtual interrupts on the USB bus to a device +// controller. +// +// Since the device controller software is intended for use with multiple TinyGo +// targets, all of which may not have exactly the same USB bus interrupts, a +// "virtual interrupt" is defined that is common to all targets. The target's +// hardware implementation (type dhw) is responsible for translating real system +// interrupts it receives into the appropriate virtual interrupt code, defined +// below, and notifying the device controller via method (*dcd).event(dcdEvent). +type dcdEvent struct { + id uint8 + setup dcdSetup + mask uint32 +} + +// Enumerated constants for all possible USB device controller interrupt codes. +const ( + dcdEventInvalid uint8 = iota // Invalid interrupt + dcdEventStatusReset // USB reset received + dcdEventStatusRun // USB controller entered run state + dcdEventStatusSuspend // USB suspend received + dcdEventStatusError // USB error condition detected on bus + dcdEventControlSetup // USB setup received + dcdEventPeripheralReady // USB PHY powered and ready to _go_ + dcdEventTransactComplete // USB transaction complete + dcdEventTimer // USB (system) timer ) -// nextTransfer returns the next transfer descriptor pointed to by the receiver -// transfer descriptor, and whether or not that next descriptor is the final -// descriptor in the list. -func (t *dcdTransfer) nextTransfer() (*dcdTransfer, bool) { - return t.next, uintptr(unsafe.Pointer(t.next)) == dcdTransferPointerEOL +func (d *dcd) event(ev dcdEvent) { + + switch ev.id { + case dcdEventInvalid: + case dcdEventStatusReset: + d.endpointMask = 0 + + case dcdEventPeripheralReady: + // Configure and enable control endpoint 0 + d.endpointEnable(0, true, 0) + + case dcdEventStatusRun: + case dcdEventStatusSuspend: + case dcdEventStatusError: + case dcdEventControlSetup: + // On control endpoint 0 setup events, the ev.setup field will be defined + switch d.controlSetup(ev.setup) { + case dcdStageSetup: + case dcdStageData: + case dcdStageStatus: + d.controlStatus() + case dcdStageStall: + d.controlStall() + } + + case dcdEventTransactComplete: + case dcdEventTimer: + default: + } +} + +// dcdStage represents the USB transaction stage of a control request. +type dcdStage uint8 + +// Enumerated constants for all possible USB transaction stages. +const ( + dcdStageSetup dcdStage = iota // Indicates no stage transition required + dcdStageData // IN/OUT data transfer + dcdStageStatus // Setup request complete + dcdStageStall // Unhandled or invalid request +) + +// controlSetup handles setup messages on control endpoint 0. +func (d *dcd) controlSetup(sup dcdSetup) dcdStage { + + // Reset endpoint 0 notify mask + d.controlMask = 0 + + // First, switch on the type of request (standard, class, or vendor) + switch sup.bmRequestType & descRequestTypeTypeMsk { + + // === STANDARD REQUEST === + case descRequestTypeTypeStandard: + + // Switch on the recepient and direction of the request + switch sup.bmRequestType & + (descRequestTypeRecipientMsk | descRequestTypeDirMsk) { + + // --- DEVICE Rx (OUT) --- + case descRequestTypeRecipientDevice | descRequestTypeDirOut: + + // Identify which request was received + switch sup.bRequest { + + // SET ADDRESS (0x05): + case descRequestStandardSetAddress: + d.controlDeviceAddress(sup.wValue) + d.controlReceive(uintptr(0), 0, false) + return dcdStageSetup + + // SET CONFIGURATION (0x09): + case descRequestStandardSetConfiguration: + d.cc.config = int(sup.wValue) + if 0 == d.cc.config || d.cc.config > dcdCount { + // Use default if invalid index received + d.cc.config = 1 + } + + // Respond based on our device class configuration + switch d.cc.id { + + // CDC-ACM (single) + case classDeviceCDCACM: + d.uartConfigure() + d.controlReceive(uintptr(0), 0, false) + + default: + // Unhandled device class + } + return dcdStageSetup + + default: + // Unhandled request + } + + // --- DEVICE Tx (IN) --- + case descRequestTypeRecipientDevice | descRequestTypeDirIn: + + // Identify which request was received + switch sup.bRequest { + + // GET STATUS (0x00): + case descRequestStandardGetStatus: + d.controlReply[0] = 0 + d.controlReply[1] = 0 + d.controlTransmit( + uintptr(unsafe.Pointer(&d.controlReply[0])), 2, false) + return dcdStageSetup + + // GET DESCRIPTOR (0x06): + case descRequestStandardGetDescriptor: + d.controlDescriptor(sup) + return dcdStageSetup + + // GET CONFIGURATION (0x08): + case descRequestStandardGetConfiguration: + d.controlReply[0] = uint8(d.cc.config) + d.controlTransmit( + uintptr(unsafe.Pointer(&d.controlReply[0])), 1, false) + return dcdStageSetup + + default: + // Unhandled request + } + + // --- INTERFACE Tx (IN) --- + case descRequestTypeRecipientInterface | descRequestTypeDirIn: + + // Identify which request was received + switch sup.bRequest { + + // GET DESCRIPTOR (0x06): + case descRequestStandardGetDescriptor: + d.controlDescriptor(sup) + return dcdStageSetup + + default: + // Unhandled request + } + + // --- ENDPOINT Rx (OUT) --- + case descRequestTypeRecipientEndpoint | descRequestTypeDirOut: + + // Identify which request was received + switch sup.bRequest { + + // CLEAR FEATURE (0x01): + case descRequestStandardClearFeature: + // TODO + + // SET FEATURE (0x03): + case descRequestStandardSetFeature: + // TODO + + default: + // Unhandled request + } + + // --- ENDPOINT Tx (IN) --- + case descRequestTypeRecipientEndpoint | descRequestTypeDirIn: + + // Identify which request was received + switch sup.bRequest { + + // GET STATUS (0x00): + case descRequestStandardGetStatus: + status := d.endpointStatus(uint8(sup.wIndex)) + d.controlReply[0] = uint8(status) + d.controlReply[1] = uint8(status >> 8) + d.controlTransmit( + uintptr(unsafe.Pointer(&d.controlReply[0])), 2, false) + return dcdStageSetup + + default: + // Unhandled request + } + + default: + // Unhandled request recepient or direction + } + + // === CLASS REQUEST === + case descRequestTypeTypeClass: + + // Switch on the recepient and direction of the request + switch sup.bmRequestType & + (descRequestTypeRecipientMsk | descRequestTypeDirMsk) { + + // --- INTERFACE Rx (OUT) --- + case descRequestTypeRecipientInterface | descRequestTypeDirOut: + + // Identify which request was received + switch sup.bRequest { + + // CDC | SET LINE CODING (0x20): + case descCDCRequestSetLineCoding: + + // Respond based on our device class configuration + switch d.cc.id { + + // CDC-ACM (single) + case classDeviceCDCACM: + // line coding must contain exactly 7 bytes + if descCDCACMCodingSize == sup.wLength { + d.setup = sup + d.controlReceive( + uintptr(unsafe.Pointer(&descCDCACM[d.cc.config-1].cx[0])), + descCDCACMCodingSize, true) + return dcdStageSetup + } + + default: + // 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 notification + switch sup.wIndex { + + // Control/status interface: + case descCDCACMInterfaceCtrl: + d.controlReceive(uintptr(0), 0, false) + return dcdStageSetup + + default: + // Unhandled device interface + } + + default: + // Unhandled device class + } + + // CDC | SEND BREAK (0x23): + case descCDCRequestSendBreak: + + // Respond based on our device class configuration + switch d.cc.id { + + // CDC-ACM (single) + case classDeviceCDCACM: + d.controlReceive(uintptr(0), 0, false) + return dcdStageSetup + + default: + // Unhandled device class + } + + default: + // Unhandled request + } + + default: + // Unhandled request recepient or direction + } + + case descRequestTypeTypeVendor: + default: + // Unhandled request type + } + + // All successful requests return early. If we reach this point, the request + // was invalid or unhandled. Stall the endpoint. + return dcdStageStall +} + +// controlComplete handles the setup completion of control endpoint 0. +func (d *dcd) controlComplete(status uint32) { + + // Reset endpoint 0 notify mask + d.controlMask = 0 + + // First, switch on the type of request (standard, class, or vendor) + switch d.setup.bmRequestType & descRequestTypeTypeMsk { + + // === CLASS REQUEST === + case descRequestTypeTypeClass: + + // Switch on the recepient and direction of the request + switch d.setup.bmRequestType & + (descRequestTypeRecipientMsk | descRequestTypeDirMsk) { + + // --- INTERFACE Rx (OUT) --- + case descRequestTypeRecipientInterface | descRequestTypeDirOut: + + // Identify which request was received + switch d.setup.bRequest { + + // CDC | SET LINE CODING (0x20): + case descCDCRequestSetLineCoding: + + // Respond based on our device class configuration + switch d.cc.id { + + // CDC-ACM (single) + case classDeviceCDCACM: + acm := &descCDCACM[d.cc.config-1] + + // Determine interface destination of the notification + switch d.setup.wIndex { + + // Control/status interface: + case descCDCACMInterfaceCtrl: + + // Notify PHY to handle triggers like special baud rates, which + // signal to reboot into bootloader or begin receiving OTA updates + d.controlLineCoding(descCDCACMLineCoding{ + baud: packU32(acm.cx[:]), + stopBits: acm.cx[4], + parity: acm.cx[5], + numBits: acm.cx[6], + }) + + default: + // Unhandled device interface + } + + default: + // Unhandled device class + } + + default: + // Unhandled request + } + + default: + // Unhandled recepient or direction + } + + default: + // Unhandled request type + } +} + +func (d *dcd) controlDescriptor(sup dcdSetup) { + + // Respond based on our device class configuration + switch d.cc.id { + + // CDC-ACM (single) + case classDeviceCDCACM: + acm := &descCDCACM[d.cc.config-1] + dxn := uint8(0) + + // Determine the type of descriptor being requested + switch sup.wValue >> 8 { + + // Device descriptor + case descTypeDevice: + dxn = descLengthDevice + _ = copy(acm.dx[:], acm.device[:dxn]) + + // Configuration descriptor + case descTypeConfigure: + dxn = uint8(descCDCACMConfigSize) + _ = copy(acm.dx[:], acm.config[:dxn]) + + // String descriptor + case descTypeString: + if 0 == len(acm.locale) { + break // No string descriptors defined! + } + var sd []uint8 + if 0 == uint8(sup.wValue) { + + // setup.wIndex contains an arbitrary index referring to a collection of + // strings in some given language. This case (setup.wValue = [0x03]00) + // is a string request from the host to determine what that language is. + // + // In subsequent string requests, the host will populate setup.wIndex + // with the language code we return here in this string descriptor. + // + // This way all strings returned to the host are in the same language, + // whatever language that may be. + code := int(sup.wIndex) + if code >= len(acm.locale) { + code = 0 + } + sd = acm.locale[code].descriptor[sup.wValue&0xFF][:] + + } else { + + // setup.wIndex now contains a language code, which we specified 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 sup.wIndex == acm.locale[code].language { + // Found language, check if string descriptor at given index exists + if int(sup.wValue&0xFF) < len(acm.locale[code].descriptor) { + + // Found language with a string defined at the requested index. + // + // TODO: Add API methods to device controller that allows the user + // to provide these strings at/before driver initialization. + // + // For now, we just always use the descCommon* strings. + var s string + switch uint8(sup.wValue) { + case 1: + s = descCommonManufacturer + case 2: + s = descCommonProduct + case 3: + s = descCommonSerialNumber + } + + // Construct a string descriptor dynamically to be transmitted on + // the serial bus. + sd = acm.locale[code].descriptor[int(sup.wValue&0xFF)][:] + // String descriptor format is 2-byte header + 2-bytes per rune + sd[0] = uint8(2 + 2*len(s)) // header[0] = descriptor length + sd[1] = descTypeString // header[1] = descriptor type + // Copy UTF-8 string into string descriptor as UTF-16 + 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 + } + } + } + } + // Copy string descriptor into descriptor transmit buffer + if nil != sd && len(sd) >= 0 { + dxn = sd[0] + _ = copy(acm.dx[:], sd[:dxn]) + } + + // Device qualification descriptor + case descTypeQualification: + dxn = descLengthQualification + _ = copy(acm.dx[:], acm.qualif[:dxn]) + + // Alternate configuration descriptor + case descTypeOtherSpeedConfiguration: + // TODO + + default: + // Unhandled descriptor type + } + + if dxn > 0 { + if dxn > uint8(sup.wLength) { + dxn = uint8(sup.wLength) + } + flushCache( + uintptr(unsafe.Pointer(&acm.dx[0])), uintptr(dxn)) + d.controlTransmit( + uintptr(unsafe.Pointer(&acm.dx[0])), uint32(dxn), false) + } + + default: + // Unhandled device class + } + } diff --git a/src/machine/usb/dcd_mimxrt1062.go b/src/machine/usb/dcd_mimxrt1062.go deleted file mode 100644 index a66065cdc..000000000 --- a/src/machine/usb/dcd_mimxrt1062.go +++ /dev/null @@ -1,1338 +0,0 @@ -// +build mimxrt1062 - -package usb - -// Implementation of USB device controller interface (dcd) for NXP iMXRT1062. - -import ( - "device/arm" - "device/nxp" - "math/bits" - "runtime/interrupt" - "runtime/volatile" - "unsafe" -) - -// dcdCount defines the number of USB cores to configure for device mode. It is -// computed as the sum of all declared device configuration descriptors. -const dcdCount = descCDCACMCount - -// dcdInterruptPriority defines the priority for all USB device interrupts. -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 - cc class // USB device class - id int // deviceControllerInstance index - - bus *nxp.USB_Type - phy *nxp.USBPHY_Type - irq interrupt.Interrupt - - cri volatile.Register8 // set to 1 if in critical section, else 0 - ivm uintptr // interrupt state when entering critical section - - stat *dcdEndpoint // endpoint 0 Rx ("out" direction) - ctrl *dcdEndpoint // endpoint 0 Tx ("in" direction) - - timerInterrupt [2]func() - controlNotify uint32 - endpointNotify uint32 - sofUsage uint8 - rebootTimer uint8 - setup dcdSetup - controlReply [8]uint8 - - speed uint8 // bus speed (0=full, 1=low, 2=high, 4=super) -} - -// deviceControllerInstance provides statically-allocated instances of each USB -// device controller configured on this platform. -var deviceControllerInstance [dcdCount]deviceController - -// cycleCount uses the ARM debug cycle counter available on iMXRT1062 (enabled -// in runtime_mimxrt1062_time.go) to return the number of CPU cycles since boot. -//go:inline -func cycleCount() uint32 { - return (*volatile.Register32)(unsafe.Pointer(uintptr(0xe0001004))).Get() -} - -//go:linkname ticks runtime.ticks -func ticks() int64 - -// initDCD initializes and assigns a free device controller instance to the -// given USB port. Returns the initialized device controller or nil if no free -// device controller instances remain. -func initDCD(port int, class class) (dcd, status) { - if 0 == dcdCount { - return nil, statusInvalid // must have defined device controllers - } - switch class.id { - case classDeviceCDCACM: - if 0 == class.config || class.config > descCDCACMCount { - return nil, statusInvalid // must have defined descriptors - } - default: - } - // Return the first instance whose assigned core is currently nil. - for i := range deviceControllerInstance { - if nil == deviceControllerInstance[i].core { - // Initialize device controller. - deviceControllerInstance[i].core = &coreInstance[port] - deviceControllerInstance[i].port = port - deviceControllerInstance[i].cc = class - deviceControllerInstance[i].id = i - switch port { - case 0: - deviceControllerInstance[i].bus = nxp.USB1 - deviceControllerInstance[i].phy = nxp.USBPHY1 - deviceControllerInstance[i].irq = - interrupt.New(nxp.IRQ_USB_OTG1, - func(interrupt.Interrupt) { - coreInstance[0].dc.interrupt() - }) - - case 1: - deviceControllerInstance[i].bus = nxp.USB2 - deviceControllerInstance[i].phy = nxp.USBPHY2 - deviceControllerInstance[i].irq = - interrupt.New(nxp.IRQ_USB_OTG2, - func(interrupt.Interrupt) { - //coreInstance[1].dc.interrupt() - }) - } - 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) - dc.bus.USBCMD.SetBits(nxp.USB_USBCMD_RST) - for dc.bus.USBCMD.HasBits(nxp.USB_USBCMD_RST) { - } - // clear interrupts - m := arm.DisableInterrupts() - switch dc.port { - case 0: - arm.EnableInterrupts(m & ^uintptr(nxp.IRQ_USB_OTG1)) - case 1: - arm.EnableInterrupts(m & ^uintptr(nxp.IRQ_USB_OTG2)) - } - dc.phy.CTRL_CLR.Set(nxp.USBPHY_CTRL_CLKGATE | nxp.USBPHY_CTRL_SFTRST) - dc.phy.PWD.Set(0) - - // clear the controller mode field and set to device mode: - // controller mode (CM) 0x0=idle, 0x2=device-only, 0x3=host-only - dc.bus.USBMODE.ReplaceBits(nxp.USB_USBMODE_CM_CM_2, - nxp.USB_USBMODE_CM_Msk>>nxp.USB_USBMODE_CM_Pos, nxp.USB_USBMODE_CM_Pos) - - dc.bus.USBCMD.ClearBits(nxp.USB_USBCMD_ITC_Msk) // no interrupt threshold - dc.bus.USBMODE.SetBits(nxp.USB_USBMODE_SLOM_Msk) // disable setup lockout - dc.bus.USBMODE.ClearBits(nxp.USB_USBMODE_ES_Msk) // use little-endianness - - dc.stat = dc.endpointQueueHead(rxEndpoint(0)) - dc.ctrl = dc.endpointQueueHead(txEndpoint(0)) - dc.stat.config = (descEndptMaxPktSize << 16) | (1 << 15) - dc.ctrl.config = (descEndptMaxPktSize << 16) - - dc.bus.ASYNCLISTADDR.Set(uint32(uintptr(unsafe.Pointer(dc.stat)))) - - // clear installed timer callbacks - dc.timerInterrupt[0] = nil - dc.timerInterrupt[1] = nil - - // enable interrupts - dc.bus.USBINTR.Set( - nxp.USB_USBINTR_UE_Msk | // bus enable - nxp.USB_USBINTR_UEE_Msk | // bus error - nxp.USB_USBINTR_PCE_Msk | // port change detect - nxp.USB_USBINTR_URE_Msk | // bus reset - nxp.USB_USBINTR_SLE) // sleep enable - - // ensure D+ pulled down long enough for host to detect previous disconnect - udelay(5000) - - return statusOK -} - -func (dc *deviceController) enable(enable bool) status { - if enable { - dc.irq.SetPriority(dcdInterruptPriority) - dc.irq.Enable() - dc.bus.USBCMD.SetBits(nxp.USB_USBCMD_RS) - } else { - dc.bus.USBCMD.ClearBits(nxp.USB_USBCMD_RS) - dc.irq.Disable() - } - return statusOK -} - -func (dc *deviceController) critical(enter bool) status { - if enter { - // check if critical section already locked - if dc.cri.Get() != 0 { - return statusRetry - } - // lock critical section - dc.cri.Set(1) - // disable interrupts, storing state in receiver - dc.ivm = arm.DisableInterrupts() - } else { - // ensure critical section is locked - if dc.cri.Get() != 0 { - // re-enable interrupts, using state stored in receiver - arm.EnableInterrupts(dc.ivm) - // unlock critical section - dc.cri.Set(0) - } - } - return statusOK -} - -func (dc *deviceController) interrupt() { - // read and clear the interrupts that fired - status := dc.bus.USBSTS.Get() & dc.bus.USBINTR.Get() - dc.bus.USBSTS.Set(status) - - // USB Interrupt (USBINT) - R/WC - // This bit is set by the Host/Device Controller when the cause of an - // interrupt is a completion of a USB transaction where the Transfer - // Descriptor (TD) has an interrupt on complete (IOC) bit set. - // This bit is also set by the Host/Device Controller when a short packet is - // detected. A short packet is when the actual number of bytes received was - // less than the expected number of bytes. - if 0 != status&nxp.USB_USBSTS_UI { - - setupStatus := dc.bus.ENDPTSETUPSTAT.Get() - for 0 != setupStatus { - dc.bus.ENDPTSETUPSTAT.Set(setupStatus) - var setup dcdSetup - ready := false - for !ready { - dc.bus.USBCMD.SetBits(nxp.USB_USBCMD_SUTW) - setup = dc.stat.setup - ready = dc.bus.USBCMD.HasBits(nxp.USB_USBCMD_SUTW) - } - dc.bus.USBCMD.ClearBits(nxp.USB_USBCMD_SUTW) - // flush endpoint 0 (bit 0=Rx, 16=Tx) - dc.bus.ENDPTFLUSH.Set(0x00010001) - for dc.bus.ENDPTFLUSH.HasBits(0x00010001) { - } // wait for flush to complete - dc.controlNotify = 0 - dc.control(setup) - setupStatus = dc.bus.ENDPTSETUPSTAT.Get() - } - - completeStatus := dc.bus.ENDPTCOMPLETE.Get() - if 0 != completeStatus { - dc.bus.ENDPTCOMPLETE.Set(completeStatus) - if 0 != completeStatus&dc.controlNotify { - dc.controlNotify = 0 - dc.controlComplete() - } - completeStatus &= dc.endpointNotify - if 0 != completeStatus { - tx := completeStatus >> 16 - for 0 != tx { - num := uint8(bits.TrailingZeros32(tx)) - dc.endpointComplete(txEndpoint(num)) - tx &^= 1 << num - } - rx := completeStatus & 0xFFFF - for 0 != rx { - num := uint8(bits.TrailingZeros32(rx)) - dc.endpointComplete(rxEndpoint(num)) - rx &^= 1 << num - } - } - } - } - - // USB Reset Received - R/WC - // When the device controller detects a USB Reset and enters the default - // state, this bit will be set to a one. - // Software can write a 1 to this bit to clear the USB Reset Received status - // bit. - // Only used in device operation mode. - if 0 != status&nxp.USB_USBSTS_URI { - // clear all setup tokens - dc.bus.ENDPTSETUPSTAT.Set(dc.bus.ENDPTSETUPSTAT.Get()) - // clear all endpoint complete status - dc.bus.ENDPTCOMPLETE.Set(dc.bus.ENDPTCOMPLETE.Get()) - // wait on any endpoint priming - for 0 != dc.bus.ENDPTPRIME.Get() { - } - dc.bus.ENDPTFLUSH.Set(0xFFFFFFFF) - // if dc.bus.PORTSC1.HasBits(nxp.USB_PORTSC1_PR) { - // } - switch dc.cc.id { - case classDeviceCDCACM: - // TBD: reset CDC-ACM UART? - default: - } - dc.endpointNotify = 0 - } - - // General Purpose Timer Interrupt 0(GPTINT0) - R/WC - // This bit is set when the counter in the GPTIMER0CTRL register transitions - // to zero, writing a one to this bit clears it. - if 0 != status&nxp.USB_USBSTS_TI0 { - if nil != dc.timerInterrupt[0] { - dc.timerInterrupt[0]() - } - } - - // General Purpose Timer Interrupt 1(GPTINT1) - R/WC - // This bit is set when the counter in the GPTIMER1CTRL register transitions - // to zero, writing a one to this bit will clear it. - if 0 != status&nxp.USB_USBSTS_TI1 { - if nil != dc.timerInterrupt[1] { - dc.timerInterrupt[1]() - } - } - - // Port Change Detect - R/WC - // The Host Controller sets this bit to a one when on any port a Connect - // Status occurs, a Port Enable/Disable Change occurs, or the Force Port - // Resume bit is set as the result of a J-K transition on the suspended port. - // The Device Controller sets this bit to a one when the port controller - // enters the full or high-speed operational state. When the port controller - // exits the full or high-speed operation states due to Reset or Suspend - // events, the notification mechanisms are the USB Reset Received bit and the - // DCSuspend bits respectively. - if 0 != status&nxp.USB_USBSTS_PCI { - if dc.bus.PORTSC1.HasBits(nxp.USB_PORTSC1_HSP) { - dc.speed = descDeviceSpeedHigh // 480 Mbit/sec - } else { - dc.speed = descDeviceSpeedFull // 12 Mbit/sec - } - } - - // DCSuspend - R/WC - // When a controller enters a suspend state from an active state, this bit - // will be set to a one. The device controller clears the bit upon exiting - // from a suspend state. Only used in device operation mode. - if 0 != status&nxp.USB_USBSTS_SLI { - //println("suspend") - } - - // USB Error Interrupt (USBERRINT) - R/WC - // When completion of a USB transaction results in an error condition, this - // bit is set by the Host/Device Controller. This bit is set along with the - // USBINT bit, if the TD on which the error interrupt occurred also had its - // interrupt on complete (IOC) bit set. - // The device controller detects resume signaling only. - if 0 != status&nxp.USB_USBSTS_UEI { - //println("error") - } - - // SOF Received - R/WC - // When the device controller detects a Start Of (micro) Frame, this bit will - // be set to a one. When a SOF is extremely late, the device controller will - // automatically set this bit to indicate that an SOF was expected. - // Therefore, this bit will be set roughly every 1ms in device FS mode and - // every 125ms in HS mode and will be synchronized to the actual SOF that is - // received. - // Because the device controller is initialized to FS before connect, this bit - // will be set at an interval of 1ms during the prelude to connect and chirp. - // In host mode, this bit will be set every 125us and can be used by host - // controller driver as a time base. Software writes a 1 to this bit to clear - // it. - if dc.bus.USBINTR.HasBits(nxp.USB_USBINTR_SRE) && - 0 != status&nxp.USB_USBSTS_SRI { - if 0 != dc.rebootTimer { - dc.rebootTimer -= 1 - if 0 == dc.rebootTimer { - dc.enableSofInterrupts(false, descCDCACMInterfaceCount) - } - } - } -} - -func (dc *deviceController) enableSofInterrupts(enable bool, iface uint8) { - if enable { - ivm := arm.DisableInterrupts() - dc.sofUsage |= 1 << iface - if !dc.bus.USBINTR.HasBits(nxp.USB_USBINTR_SRE) { - dc.bus.USBSTS.Set(nxp.USB_USBSTS_SRI) - dc.bus.USBINTR.SetBits(nxp.USB_USBINTR_SRE) - } - arm.EnableInterrupts(ivm) - } else { - dc.sofUsage &^= 1 << iface - if 0 == dc.sofUsage { - dc.bus.USBINTR.ClearBits(nxp.USB_USBINTR_SRE) - } - } -} - -// receive schedules a receive (Rx, OUT) transfer on the given endpoint. -func (dc *deviceController) receive(endpoint uint8, transfer *dcdTransfer) { - if endpoint < descCDCACMEndpointStatus || - endpoint > descCDCACMEndpointCount { - return - } - ep := dc.endpointQueueHead(rxEndpoint(endpoint)) - em := (uint32(1) << endpoint) << descCDCACMConfigAttrRxPos - dc.transferSchedule(ep, em, transfer) -} - -// transmit schedules a transmit (Tx, IN) transfer on the given endpoint. -func (dc *deviceController) transmit(endpoint uint8, transfer *dcdTransfer) { - if endpoint < descCDCACMEndpointStatus || - endpoint > descCDCACMEndpointCount { - return - } - ep := dc.endpointQueueHead(txEndpoint(endpoint)) - em := (uint32(1) << endpoint) << descCDCACMConfigAttrTxPos - dc.transferSchedule(ep, em, transfer) -} - -// control handles setup messages on control endpoint 0. -func (dc *deviceController) control(setup dcdSetup) { - - // println(strconv.FormatUint(setup.pack(), 16)) - - // First, switch on the type of request (standard, class, or vendor) - switch setup.bmRequestType & descRequestTypeTypeMsk { - - // === STANDARD REQUEST === - case descRequestTypeTypeStandard: - - // Switch on the recepient and direction of the request - switch setup.bmRequestType & - (descRequestTypeRecipientMsk | descRequestTypeDirMsk) { - - // --- DEVICE Rx (OUT) --- - case descRequestTypeRecipientDevice | descRequestTypeDirOut: - - // Identify which request was received - switch setup.bRequest { - - // SET ADDRESS (0x05): - case descRequestStandardSetAddress: - dc.controlReceive(dcdPointerNil, 0, false) - dc.bus.DEVICEADDR.Set(nxp.USB_DEVICEADDR_USBADRA | - ((uint32(setup.wValue) << nxp.USB_DEVICEADDR_USBADR_Pos) & - nxp.USB_DEVICEADDR_USBADR_Msk)) - return - - // SET CONFIGURATION (0x09): - case descRequestStandardSetConfiguration: - dc.cc.config = int(setup.wValue) - if 0 == dc.cc.config || dc.cc.config > dcdCount { - // Use default if invalid index received - dc.cc.config = 1 - } - - // Respond based on our device class configuration - 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.uartConfigure() - dc.controlReceive(dcdPointerNil, 0, false) - - default: - // Unhandled device class - } - return - - default: - // Unhandled request - } - - // --- DEVICE Tx (IN) --- - case descRequestTypeRecipientDevice | descRequestTypeDirIn: - - // Identify which request was received - switch setup.bRequest { - - // GET STATUS (0x00): - case descRequestStandardGetStatus: - dc.controlReply[0] = 0 - dc.controlReply[1] = 0 - dc.controlTransmit( - uintptr(unsafe.Pointer(&dc.controlReply[0])), 2, false) - return - - // GET DESCRIPTOR (0x06): - case descRequestStandardGetDescriptor: - dc.controlDescriptor(setup) - return - - // GET CONFIGURATION (0x08): - case descRequestStandardGetConfiguration: - dc.controlReply[0] = uint8(dc.cc.config) - dc.controlTransmit( - uintptr(unsafe.Pointer(&dc.controlReply[0])), 1, false) - return - - default: - // Unhandled request - } - - // --- INTERFACE Tx (IN) --- - case descRequestTypeRecipientInterface | descRequestTypeDirIn: - - // Identify which request was received - switch setup.bRequest { - - // GET DESCRIPTOR (0x06): - case descRequestStandardGetDescriptor: - dc.controlDescriptor(setup) - return - - default: - // Unhandled request - } - - // --- ENDPOINT Rx (OUT) --- - case descRequestTypeRecipientEndpoint | descRequestTypeDirOut: - - // Identify which request was received - switch setup.bRequest { - - // CLEAR FEATURE (0x01): - case descRequestStandardClearFeature: - // TODO - - // SET FEATURE (0x03): - case descRequestStandardSetFeature: - // TODO - - default: - // Unhandled request - } - - // --- ENDPOINT Tx (IN) --- - case descRequestTypeRecipientEndpoint | descRequestTypeDirIn: - - // Identify which request was received - switch setup.bRequest { - - // GET STATUS (0x00): - case descRequestStandardGetStatus: - num, dir := unpackEndpoint(uint8(setup.wIndex)) - var reg *volatile.Register32 - switch num { - case 0: - reg = &dc.bus.ENDPTCTRL0 - case 1: - reg = &dc.bus.ENDPTCTRL1 - case 2: - reg = &dc.bus.ENDPTCTRL2 - case 3: - reg = &dc.bus.ENDPTCTRL3 - case 4: - reg = &dc.bus.ENDPTCTRL4 - case 5: - reg = &dc.bus.ENDPTCTRL5 - case 6: - reg = &dc.bus.ENDPTCTRL6 - case 7: - reg = &dc.bus.ENDPTCTRL7 - } - if nil != reg { - dc.controlReply[0] = 0 - dc.controlReply[1] = 0 - if ((0 != dir) && reg.HasBits(nxp.USB_ENDPTCTRL0_TXS)) || - ((0 == dir) && reg.HasBits(nxp.USB_ENDPTCTRL0_RXS)) { - dc.controlReply[0] = 1 - } - dc.controlTransmit( - uintptr(unsafe.Pointer(&dc.controlReply[0])), 2, false) - return - } - - default: - // Unhandled request - } - - default: - // Unhandled request recepient or direction - } - - // === CLASS REQUEST === - case descRequestTypeTypeClass: - - // Switch on the recepient and direction of the request - switch setup.bmRequestType & - (descRequestTypeRecipientMsk | descRequestTypeDirMsk) { - - // --- INTERFACE Rx (OUT) --- - case descRequestTypeRecipientInterface | descRequestTypeDirOut: - - // Identify which request was received - switch setup.bRequest { - - // CDC | SET LINE CODING (0x20): - case descCDCRequestSetLineCoding: - - // Respond based on our device class configuration - switch dc.cc.id { - - // CDC-ACM (single) - case classDeviceCDCACM: - // line coding must contain exactly 7 bytes - if descCDCACMCodingSize == setup.wLength { - dc.setup = setup - dc.controlReceive( - uintptr(unsafe.Pointer(&descCDCACM[dc.cc.config-1].cx[0])), - descCDCACMCodingSize, true) - return - } - - default: - // Unhandled device class - } - - // CDC | SET CONTROL LINE STATE (0x22): - case descCDCRequestSetControlLineState: - - // Respond based on our device class configuration - switch dc.cc.id { - - // CDC-ACM (single) - case classDeviceCDCACM: - - // Determine interface destination of the notification - switch setup.wIndex { - - // Control/status interface: - case descCDCACMInterfaceCtrl: - // 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 - - default: - // Unhandled device interface - } - - default: - // Unhandled device class - } - - // CDC | SEND BREAK (0x23): - case descCDCRequestSendBreak: - - // Respond based on our device class configuration - switch dc.cc.id { - - // CDC-ACM (single) - case classDeviceCDCACM: - dc.controlReceive(dcdPointerNil, 0, false) - return - - default: - // Unhandled device class - } - - default: - // Unhandled request - } - - default: - // Unhandled request recepient or direction - } - - case descRequestTypeTypeVendor: - default: - // Unhandled request type - } - - dc.bus.ENDPTCTRL0.Set(0x00010001) -} - -// controlComplete handles the setup completion of control endpoint 0. -func (dc *deviceController) controlComplete() { - - // First, switch on the type of request (standard, class, or vendor) - switch dc.setup.bmRequestType & descRequestTypeTypeMsk { - - // === CLASS REQUEST === - case descRequestTypeTypeClass: - - // Switch on the recepient and direction of the request - switch dc.setup.bmRequestType & - (descRequestTypeRecipientMsk | descRequestTypeDirMsk) { - - // --- INTERFACE Rx (OUT) --- - case descRequestTypeRecipientInterface | descRequestTypeDirOut: - - // Identify which request was received - switch dc.setup.bRequest { - - // CDC | SET LINE CODING (0x20): - case descCDCRequestSetLineCoding: - - // Respond based on our device class configuration - 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: - - acm.lineCoding.baud = packU32(acm.cx[:]) - acm.lineCoding.stopBits = acm.cx[4] - if 0 == acm.lineCoding.stopBits { - acm.lineCoding.stopBits = 1 - } - acm.lineCoding.parity = acm.cx[5] - acm.lineCoding.numBits = acm.cx[6] - - if 134 == acm.lineCoding.baud { - dc.enableSofInterrupts(true, descCDCACMInterfaceCount) - dc.rebootTimer = 80 - } - - default: - // Unhandled device interface - } - - default: - // Unhandled device class - } - - default: - // Unhandled request - } - - default: - // Unhandled recepient or direction - } - - default: - // Unhandled request type - } -} - -func (dc *deviceController) controlDescriptor(setup dcdSetup) { - - // Respond based on our device class configuration - switch dc.cc.id { - - // CDC-ACM (single) - case classDeviceCDCACM: - acm := &descCDCACM[dc.cc.config-1] - dxn := uint8(0) - - // Determine the type of descriptor being requested - switch setup.wValue >> 8 { - - // Device descriptor - case descTypeDevice: - dxn = descLengthDevice - _ = copy(acm.dx[:], acm.device[:dxn]) - - // Configuration descriptor - case descTypeConfigure: - dxn = uint8(descCDCACMConfigSize) - _ = copy(acm.dx[:], acm.config[:dxn]) - - // 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. - - // 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 - } - - // 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 - } - } - } - } - // Copy string descriptor into descriptor transmit buffer - if nil != sd && len(sd) >= 0 { - dxn = sd[0] - _ = copy(acm.dx[:], sd[:dxn]) - } - - // Device qualification descriptor - case descTypeQualification: - dxn = descLengthQualification - _ = copy(acm.dx[:], acm.qualif[:dxn]) - - // Alternate configuration descriptor - case descTypeOtherSpeedConfiguration: - // TODO - - default: - // Unhandled descriptor type - } - - if dxn > 0 { - if dxn > uint8(setup.wLength) { - dxn = uint8(setup.wLength) - } - nxp.FlushDeleteDcache( - uintptr(unsafe.Pointer(&acm.dx[0])), uintptr(dxn)) - dc.controlTransmit( - uintptr(unsafe.Pointer(&acm.dx[0])), uint32(dxn), false) - } - - default: - // Unhandled device class - } - -} - -// controlTransfers returns the data and ackowledgement transfer descriptors for -// the control endpoint (i.e., endpoint 0). -//go:inline -func (dc *deviceController) controlTransfers() (dat, ack *dcdTransfer) { - // control endpoint is device class-specific - switch dc.cc.id { - case classDeviceCDCACM: - return descCDCACM[dc.cc.config-1].cd, descCDCACM[dc.cc.config-1].ad - default: - return nil, nil - } -} - -// controlReceive receives (Rx, OUT) data on control endpoint 0. -func (dc *deviceController) controlReceive( - data uintptr, size uint32, notify bool) { - const ( - rm = uint32(1 << descCDCACMConfigAttrRxPos) - tm = uint32(1 << descCDCACMConfigAttrTxPos) - ) - cd, ad := dc.controlTransfers() - if size > 0 { - cd.next = dcdTransferEOL - cd.token = (size << 16) | (1 << 7) - for i := range cd.pointer { - cd.pointer[i] = data + uintptr(i)*4096 - } - // linked list is empty - qr := dc.endpointQueueHead(rxEndpoint(0)) - qr.transfer.next = cd - qr.transfer.token = 0 - dc.bus.ENDPTPRIME.SetBits(rm) - for 0 != dc.bus.ENDPTPRIME.Get() { - } // wait for endpoint finish priming - } - ad.next = dcdTransferEOL - ad.token = 1 << 7 - if notify { - ad.token |= 1 << 15 - } - ad.pointer[0] = 0 - qt := dc.endpointQueueHead(txEndpoint(0)) - qt.transfer.next = ad - qt.transfer.token = 0 - dc.bus.ENDPTCOMPLETE.Set(rm | tm) - dc.bus.ENDPTPRIME.SetBits(tm) - if notify { - dc.controlNotify = tm - } -} - -// controlTransmit transmits (Tx, IN) data on control endpoint 0. -func (dc *deviceController) controlTransmit( - data uintptr, size uint32, notify bool) { - const ( - rm = uint32(1 << descCDCACMConfigAttrRxPos) - tm = uint32(1 << descCDCACMConfigAttrTxPos) - ) - cd, ad := dc.controlTransfers() - if size > 0 { - cd.next = dcdTransferEOL - cd.token = (size << 16) | (1 << 7) - for i := range cd.pointer { - cd.pointer[i] = data + uintptr(i)*4096 - } - // linked list is empty - qt := dc.endpointQueueHead(txEndpoint(0)) - qt.transfer.next = cd - qt.transfer.token = 0 - dc.bus.ENDPTPRIME.SetBits(tm) - for 0 != dc.bus.ENDPTPRIME.Get() { - } // wait for endpoint finish priming - } - ad.next = dcdTransferEOL - ad.token = 1 << 7 - if notify { - ad.token |= 1 << 15 - } - ad.pointer[0] = 0 - qr := dc.endpointQueueHead(rxEndpoint(0)) - qr.transfer.next = ad - qr.transfer.token = 0 - dc.bus.ENDPTCOMPLETE.Set(rm | tm) - dc.bus.ENDPTPRIME.SetBits(rm) - if notify { - dc.controlNotify = rm - } -} - -// endpointQueueHead returns the queue head for the given endpoint address, -// encoded as direction D and endpoint number N with the 8-bit mask DxxxNNNN. -//go:inline -func (dc *deviceController) endpointQueueHead(endpoint uint8) *dcdEndpoint { - // endpoint queue head is device class-specific - switch dc.cc.id { - case classDeviceCDCACM: - return &descCDCACM[dc.cc.config-1].qh[endpointIndex(endpoint)] - default: - return nil - } -} - -func (dc *deviceController) endpointConfigure( - ep *dcdEndpoint, packetSize uint16, zlp bool, callback dcdTransferCallback) { - - ep.config = uint32(packetSize) << 16 - if !zlp { - ep.config |= 1 << 29 - } - ep.current = nil - ep.transfer.next = dcdTransferEOL - ep.transfer.token = 0 - for i := range ep.transfer.pointer { - ep.transfer.pointer[i] = 0 - } - ep.transfer.param = 0 - ep.setup.bmRequestType = 0 - ep.setup.bRequest = 0 - ep.setup.wValue = 0 - ep.setup.wIndex = 0 - ep.setup.wLength = 0 - ep.first = nil - ep.last = nil - ep.callback = callback -} - -func (dc *deviceController) endpointConfigureRx( - endpoint uint8, packetSize uint16, zlp bool, callback dcdTransferCallback) { - - if endpoint < descCDCACMEndpointStatus || - endpoint > descCDCACMEndpointCount { - return - } - ep := dc.endpointQueueHead(rxEndpoint(endpoint)) - dc.endpointConfigure(ep, packetSize, zlp, callback) - if nil != callback { - dc.endpointNotify |= (uint32(1) << endpoint) << descCDCACMConfigAttrRxPos - } -} - -func (dc *deviceController) endpointConfigureTx( - endpoint uint8, packetSize uint16, zlp bool, callback dcdTransferCallback) { - - if endpoint < descCDCACMEndpointStatus || - endpoint > descCDCACMEndpointCount { - return - } - ep := dc.endpointQueueHead(txEndpoint(endpoint)) - dc.endpointConfigure(ep, packetSize, zlp, callback) - if nil != callback { - dc.endpointNotify |= (uint32(1) << endpoint) << descCDCACMConfigAttrTxPos - } -} - -// endpointComplete handles transfer completion of a data endpoint. -func (dc *deviceController) endpointComplete(endpoint uint8) { - ep := dc.endpointQueueHead(endpoint) - if nil == ep.first { - return - } - count := 0 - first := ep.first - for t, eol := first, false; !eol; t, eol = t.nextTransfer() { - if eol { - // reached end of list, new list empty - ep.first = nil - ep.last = nil - } else { - if 0 != t.token&(1<<7) { - // active transfer, new list begins here - ep.first = t - break - } else { - count += 1 - } - } - } - // invoke all callbacks - for i := 0; i < count; i++ { - next := first.next - ep.callback(first) - first = next - } -} - -func (dc *deviceController) transferPrepare( - transfer *dcdTransfer, data *uint8, size uint16, param uint32) { - transfer.next = dcdTransferEOL - transfer.token = (uint32(size) << 16) | (1 << 7) - addr := uintptr(unsafe.Pointer(data)) - for i := range transfer.pointer { - transfer.pointer[i] = addr + uintptr(i)*4096 - } - transfer.param = param -} - -func (dc *deviceController) transferSchedule( - endpoint *dcdEndpoint, mask uint32, transfer *dcdTransfer) { - - if nil != endpoint.callback { - transfer.token |= 1 << 15 - } - ivm := arm.DisableInterrupts() - last := endpoint.last - if nil != last { - last.next = transfer - if dc.bus.ENDPTPRIME.HasBits(mask) { - goto endTransfer - } - start := cycleCount() - estat := uint32(0) - for !dc.bus.USBCMD.HasBits(nxp.USB_USBCMD_ATDTW) && - (cycleCount()-start < 2400) { - dc.bus.USBCMD.SetBits(nxp.USB_USBCMD_ATDTW) - estat = dc.bus.ENDPTSTAT.Get() - } - if 0 != estat&mask { - goto endTransfer - } - } - endpoint.transfer.next = transfer - endpoint.transfer.token = 0 - dc.bus.ENDPTPRIME.SetBits(mask) - endpoint.first = transfer -endTransfer: - endpoint.last = transfer - arm.EnableInterrupts(ivm) -} - -func (dc *deviceController) timerConfigure(timer int, usec uint32, fn func()) { - if timer < 0 || timer >= len(dc.timerInterrupt) { - return - } - dc.timerInterrupt[timer] = fn - switch timer { - case 0: - dc.bus.GPTIMER0CTRL.Set(0) - dc.bus.GPTIMER0LD.Set(usec - 1) - dc.bus.USBINTR.SetBits(nxp.USB_USBINTR_TIE0) - case 1: - dc.bus.GPTIMER1CTRL.Set(0) - dc.bus.GPTIMER1LD.Set(usec - 1) - dc.bus.USBINTR.SetBits(nxp.USB_USBINTR_TIE1) - } -} - -func (dc *deviceController) timerOneShot(timer int) { - switch timer { - case 0: - dc.bus.GPTIMER0CTRL.Set( - nxp.USB_GPTIMER0CTRL_GPTRUN | nxp.USB_GPTIMER0CTRL_GPTRST) - case 1: - dc.bus.GPTIMER1CTRL.Set( - nxp.USB_GPTIMER1CTRL_GPTRUN | nxp.USB_GPTIMER1CTRL_GPTRST) - } -} - -func (dc *deviceController) timerStop(timer int) { - switch timer { - case 0: - dc.bus.GPTIMER0CTRL.Set(0) - case 1: - dc.bus.GPTIMER1CTRL.Set(0) - } -} - -func (dc *deviceController) uartConfigure() { - acm := &descCDCACM[dc.cc.config-1] - switch dc.speed { - case descDeviceSpeedHigh: - acm.rxSize = descCDCACMDataRxHSPacketSize - acm.txSize = descCDCACMDataTxHSPacketSize - default: - acm.rxSize = descCDCACMDataRxFSPacketSize - acm.txSize = descCDCACMDataTxFSPacketSize - } - acm.txHead = 0 - acm.txFree = 0 - acm.rxHead = 0 - acm.rxTail = 0 - acm.rxFree = 0 - dc.endpointConfigureTx(descCDCACMEndpointStatus, - acm.cxSize, false, nil) - dc.endpointConfigureRx(descCDCACMEndpointDataRx, - acm.rxSize, false, dc.uartNotify) - dc.endpointConfigureTx(descCDCACMEndpointDataTx, - acm.txSize, true, nil) - for i := range acm.rd { - dc.uartReceive(uint8(i)) - } - dc.timerConfigure(0, descCDCACMTxSyncUs, dc.uartSync) -} - -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.uartReceive(uint8(p)) - } else { - // data packet - h := acm.rxHead - if h != acm.rxTail { - // previous packet is still buffered - q := acm.rxQueue[h] - n := acm.rxCount[q] - if len <= descCDCACMRxSize-n { - // previous buffer has enough free space for this packet's data - _ = copy(acm.rx[q*descCDCACMRxSize+n:], - acm.rx[p*descCDCACMRxSize:uint16(p)*descCDCACMRxSize+len]) - acm.rxCount[q] = n + len - acm.rxFree += len - dc.uartReceive(uint8(p)) - return - } - } - // add this packet to Rx buffer - acm.rxCount[p] = len - acm.rxIndex[p] = 0 - h += 1 - if h > descCDCACMRDCount { // should be >= - h = 0 - } - acm.rxQueue[h] = uint16(p) - acm.rxHead = h - acm.rxFree += len - } -} - -// 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) 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.cc.config-1] - if 0 == acm.txFree { - return - } - xfer := &acm.td[acm.txHead] - buff := &acm.tx[uint16(acm.txHead)*descCDCACMTxSize] - size := descCDCACMTxSize - acm.txFree - dc.transferPrepare(xfer, buff, size, 0) - nxp.FlushDeleteDcache(uintptr(unsafe.Pointer(buff)), uintptr(size)) - dc.transmit(descCDCACMEndpointDataTx, xfer) - acm.txHead += 1 - if acm.txHead >= descCDCACMTDCount { - acm.txHead = 0 - } - acm.txFree = 0 -} diff --git a/src/machine/usb/desc.go b/src/machine/usb/desc.go index 6f6d37948..192e93d47 100644 --- a/src/machine/usb/desc.go +++ b/src/machine/usb/desc.go @@ -135,6 +135,28 @@ const ( descDeviceCapExtAttrBESLPos = 2 ) +const ( + // Attributes of all endpoint descriptor configurations. + descEndptConfigAttr = descConfigAttrD7Msk | // Bit 7: reserved (1) + (1 << descConfigAttrSelfPoweredPos) | // Bit 6: self-powered + (0 << descConfigAttrRemoteWakeupPos) | // Bit 5: remote wakeup + 0 // Bits 0-4: reserved (0) + + descEndptConfigAttrRxPos = 0 + descEndptConfigAttrTxPos = 16 + descEndptConfigAttrRxMsk = (descEndptConfigAttr | descEndptAttrSyncTypeMsk) << descEndptConfigAttrRxPos + descEndptConfigAttrTxMsk = (descEndptConfigAttr | descEndptAttrSyncTypeMsk) << descEndptConfigAttrTxPos + + descEndptConfigAttrRxUnused = 0x02 << descEndptConfigAttrRxPos + descEndptConfigAttrTxUnused = 0x02 << descEndptConfigAttrTxPos + descEndptConfigAttrRxIsochronous = (descEndptAttrSyncTypeAsync | descEndptConfigAttr) << descEndptConfigAttrRxPos + descEndptConfigAttrTxIsochronous = (descEndptAttrSyncTypeAsync | descEndptConfigAttr) << descEndptConfigAttrTxPos + descEndptConfigAttrRxBulk = (descEndptAttrSyncTypeAdaptive | descEndptConfigAttr) << descEndptConfigAttrRxPos + descEndptConfigAttrTxBulk = (descEndptAttrSyncTypeAdaptive | descEndptConfigAttr) << descEndptConfigAttrTxPos + descEndptConfigAttrRxInterrupt = (descEndptAttrSyncTypeSync | descEndptConfigAttr) << descEndptConfigAttrRxPos + descEndptConfigAttrTxInterrupt = (descEndptAttrSyncTypeSync | descEndptConfigAttr) << descEndptConfigAttrTxPos +) + // USB CDC constants defined per specification. const ( @@ -282,53 +304,6 @@ const ( descCDCUARTStateOverrun = 0x40 // UART state OVERRUN ) -// Common configuration constants for the USB CDC-ACM (single) device class. -const ( - // String descriptor languages - descCDCACMLanguageCount = 1 - // Interfaces for all CDC-ACM configurations. - descCDCACMInterfaceCount = 2 - descCDCACMInterfaceCtrl = 0 - descCDCACMInterfaceData = 1 - // Endpoints for all CDC-ACM configurations. - descCDCACMEndpointCount = 4 - descCDCACMEndpointStatus = 2 // Communication/control interrupt input - descCDCACMEndpointDataRx = 3 // Bulk data output - descCDCACMEndpointDataTx = 4 // Bulk data input - // Endpoint configuration attributes for all CDC-ACM configurations. - descCDCACMConfigAttrStatus = (descCDCACMConfigAttrUnused << descCDCACMConfigAttrRxPos) | - (descCDCACMConfigAttrInterrupt << descCDCACMConfigAttrTxPos) - descCDCACMConfigAttrDataRx = (descCDCACMConfigAttrBulk << descCDCACMConfigAttrRxPos) | - (descCDCACMConfigAttrUnused << descCDCACMConfigAttrTxPos) - descCDCACMConfigAttrDataTx = (descCDCACMConfigAttrUnused << descCDCACMConfigAttrRxPos) | - (descCDCACMConfigAttrBulk << descCDCACMConfigAttrTxPos) - - // Size of all CDC-ACM configuration descriptors. - descCDCACMConfigSize = uint16( - descLengthConfigure + // configuration - descLengthInterface + // communication/control interface - descCDCFuncLengthHeader + // CDC header - descCDCFuncLengthCallManagement + // CDC call management - descCDCFuncLengthAbstractControl + // CDC abstract control - descCDCFuncLengthUnion + // CDC union - descLengthEndpoint + // communication/control input endpoint - descLengthInterface + // data interface - descLengthEndpoint + // data input endpoint - descLengthEndpoint) // data output endpoint - // Attributes of all CDC-ACM configuration descriptors. - descCDCACMConfigAttr = descConfigAttrD7Msk | // Bit 7: reserved (1) - (1 << descConfigAttrSelfPoweredPos) | // Bit 6: self-powered - (0 << descConfigAttrRemoteWakeupPos) | // Bit 5: remote wakeup - 0 // Bits 0-4: reserved (0) - - descCDCACMConfigAttrRxPos = 0 - descCDCACMConfigAttrTxPos = 16 - descCDCACMConfigAttrUnused = 0x02 // TBD: what is this? - descCDCACMConfigAttrIsochronous = descCDCACMConfigAttr | descEndptAttrSyncTypeAsync - descCDCACMConfigAttrBulk = descCDCACMConfigAttr | descEndptAttrSyncTypeAdaptive - descCDCACMConfigAttrInterrupt = descCDCACMConfigAttr | descEndptAttrSyncTypeSync -) - // descCDCACM0Device holds the default device descriptor for CDC-ACM[0], i.e., // configuration index 1. var descCDCACM0Device = [descLengthDevice]uint8{ @@ -367,6 +342,21 @@ var descCDCACM0Qualif = [descLengthQualification]uint8{ 0, // Reserved } +const ( + // Size of all CDC-ACM configuration descriptors. + descCDCACMConfigSize = uint16( + descLengthConfigure + // configuration + descLengthInterface + // communication/control interface + descCDCFuncLengthHeader + // CDC header + descCDCFuncLengthCallManagement + // CDC call management + descCDCFuncLengthAbstractControl + // CDC abstract control + descCDCFuncLengthUnion + // CDC union + descLengthEndpoint + // communication/control input endpoint + descLengthInterface + // data interface + descLengthEndpoint + // data input endpoint + descLengthEndpoint) // data output endpoint +) + // descCDCACM0Config holds the default configuration descriptors for CDC-ACM[0], // i.e., configuration index 1. var descCDCACM0Config = [descCDCACMConfigSize]uint8{ @@ -377,7 +367,7 @@ var descCDCACM0Config = [descCDCACMConfigSize]uint8{ descCDCACMInterfaceCount, // Number of interfaces supported by this configuration 1, // Value to use to select this configuration (1 = CDC-ACM[0]) 0, // Index of string descriptor describing this configuration - descCDCACMConfigAttr, // Configuration attributes + descEndptConfigAttr, // Configuration attributes descCDCACMMaxPower, // Max power consumption when fully-operational (2 mA units) // Communication/Control Interface Descriptor @@ -460,32 +450,6 @@ var descCDCACM0Config = [descCDCACMConfigSize]uint8{ 0, // Polling Interval } -// descCDCACMCodingSize defines the length of a CDC-ACM UART line coding buffer. -const descCDCACMCodingSize = 7 - -// 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 - rtsdtr uint8 -} - -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 @@ -504,19 +468,93 @@ type ( } ) +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. +) + // descCDCACM0String holds the default string descriptors for CDC-ACM[0], i.e., // configuration index 1. var descCDCACM0String = [descCDCACMLanguageCount]descStringLanguage{ - { // US English string descriptors + + { // [0x0409] US English language: descLanguageEnglish, descriptor: descStringIndex{ - { // Language (index 0) + { /* [0] Language */ 4, descTypeString, lsU8(descLanguageEnglish), msU8(descLanguageEnglish), }, // Actual string descriptors (index > 0) are copied into here at runtime! + // This allows for application- or even user-defined string descriptors. + { /* [1] Manufacturer */ }, + { /* [2] Product */ }, + { /* [3] Serial Number */ }, }, }, } + +// descCDCACMCodingSize defines the length of a CDC-ACM UART line coding buffer. +const descCDCACMCodingSize = 7 + +// descCDCACMLineCoding represents an emulated UART's line configuration. +type descCDCACMLineCoding struct { + baud uint32 + stopBits uint8 + parity uint8 + numBits uint8 +} + +// Common configuration constants for the USB CDC-ACM (single) device class. +const ( + // String descriptor languages available + descCDCACMLanguageCount = 1 + + // Interfaces for all CDC-ACM configurations. + descCDCACMInterfaceCount = 2 + descCDCACMInterfaceCtrl = 0 + descCDCACMInterfaceData = 1 + + // Endpoints for all CDC-ACM configurations. + descCDCACMEndpointCount = 4 + descCDCACMEndpointStatus = 2 // Communication/control interrupt input + descCDCACMEndpointDataRx = 3 // Bulk data output + descCDCACMEndpointDataTx = 4 // Bulk data input + + // Endpoint configuration attributes for all CDC-ACM configurations. + descCDCACMConfigAttrStatus = descEndptConfigAttrRxUnused | descEndptConfigAttrTxInterrupt + descCDCACMConfigAttrDataRx = descEndptConfigAttrRxBulk | descEndptConfigAttrTxUnused + descCDCACMConfigAttrDataTx = descEndptConfigAttrRxUnused | descEndptConfigAttrTxBulk +) + +// descCDCACMClass holds references to all descriptors, buffers, and control +// structures for the USB CDC-ACM (single) device class. +type descCDCACMClass struct { + *descCDCACMClassData // Target-defined, class-specific data + + locale *[descCDCACMLanguageCount]descStringLanguage // string descriptors + device *[descLengthDevice]uint8 // device descriptor + qualif *[descLengthQualification]uint8 // device qualification descriptor + config *[descCDCACMConfigSize]uint8 // configuration descriptor +} + +// descCDCACM holds statically-allocated instances for each of the CDC-ACM +// (single) device class configurations, ordered by index (offset by -1). +var descCDCACM = [descCDCACMCount]descCDCACMClass{ + + { // CDC-ACM (single) class configuration index 1 + descCDCACMClassData: &descCDCACMData[0], + + locale: &descCDCACM0String, + device: &descCDCACM0Device, + qualif: &descCDCACM0Qualif, + config: &descCDCACM0Config, + }, +} diff --git a/src/machine/usb/desc_mimxrt1062.go b/src/machine/usb/desc_mimxrt1062.go index 1b0108888..4fa7b5527 100644 --- a/src/machine/usb/desc_mimxrt1062.go +++ b/src/machine/usb/desc_mimxrt1062.go @@ -69,33 +69,39 @@ const ( // so the Device Controller can readily respond to incoming requests without // having to traverse a linked list. //go:align 4096 -var descCDCACM0QH [descCDCACMQHCount]dcdEndpoint +var descCDCACM0QH [descCDCACMQHCount]dhwEndpoint // descCDCACM0CD is the transfer descriptor for data messages transmitted or // received on the status/control endpoint 0 for the default CDC-ACM (single) // device class configuration (index 1). //go:align 32 -var descCDCACM0CD dcdTransfer +var descCDCACM0CD dhwTransfer // descCDCACM0AD is the transfer descriptor for ackowledgement (ACK) messages // transmitted or received on the status/control endpoint 0 for the default // CDC-ACM (single) device class configuration (index 1). //go:align 32 -var descCDCACM0AD dcdTransfer +var descCDCACM0AD dhwTransfer // descCDCACM0RD is an array of transfer descriptors for Rx (OUT) transfers, // which describe to the device controller the location and quantity of data // being received for a given transfer, for the default CDC-ACM (single) device // class configuration (index 1). //go:align 32 -var descCDCACM0RD [descCDCACMRDCount]dcdTransfer +var descCDCACM0RD [descCDCACMRDCount]dhwTransfer // descCDCACM0TD is an array of transfer descriptors for Tx (IN) transfers, // which describe to the device controller the location and quantity of data // being transmitted for a given transfer, for the default CDC-ACM (single) // device class configuration (index 1). //go:align 32 -var descCDCACM0TD [descCDCACMTDCount]dcdTransfer +var descCDCACM0TD [descCDCACMTDCount]dhwTransfer + +// descCDCACM0LineCoding holds the emulated UART line coding for the default +// CDC-ACM (single) device class configuration (index 1). +// i.e., configuration index 1. +//go:align 32 +var descCDCACM0LC descCDCACMLineCoding // descCDCACM0Cx is the buffer for control/status data received on endpoint 0 of // the default CDC-ACM (single) device class configuration (index 1). @@ -120,21 +126,22 @@ var descCDCACM0RDNum [descCDCACMRDCount]uint16 var descCDCACM0RDIdx [descCDCACMRDCount]uint16 var descCDCACM0RDQue [descCDCACMRDCount + 1]uint16 -type descCDCACMClass struct { - locale *[descCDCACMLanguageCount]descStringLanguage // string descriptors - device *[descLengthDevice]uint8 // device descriptor - qualif *[descLengthQualification]uint8 // device qualification descriptor - config *[descCDCACMConfigSize]uint8 // configuration descriptor +// descCDCACM holds the buffers and control states for all of the CDC-ACM +// (single) device class configurations, ordered by index (offset by -1), for +// iMXRT1062 targets only. +// +// Instances of this type (elements of descCDCACMData) are embedded in elements +// of the common/target-agnostic CDC-ACM class configurations (descCDCACM). +// Methods defined on this type implement target-specific functionality, and +// some of these methods are required by the common device controller driver. +// Thus, this type functions as an additional hardware abstraction layer. +type descCDCACMClassData struct { + qh *[descCDCACMQHCount]dhwEndpoint // endpoint queue heads - lineCoding *descCDCACMLineCoding // UART line coding active state - // lineActive int64 // time since last UART DTR/RTS - - qh *[descCDCACMQHCount]dcdEndpoint // endpoint queue heads - - cd *dcdTransfer // control endpoint 0 Rx/Tx data transfer descriptor - ad *dcdTransfer // control endpoint 0 Rx/Tx ACK transfer descriptor - rd *[descCDCACMRDCount]dcdTransfer // bulk data endpoint Rx (OUT) transfer descriptors - td *[descCDCACMTDCount]dcdTransfer // bulk data endpoint Tx (IN) transfer descriptors + cd *dhwTransfer // control endpoint 0 Rx/Tx data transfer descriptor + ad *dhwTransfer // control endpoint 0 Rx/Tx ACK transfer descriptor + rd *[descCDCACMRDCount]dhwTransfer // bulk data endpoint Rx (OUT) transfer descriptors + td *[descCDCACMTDCount]dhwTransfer // bulk data endpoint Tx (IN) transfer descriptors cx *[descCDCACMCxCount]uint8 // control endpoint 0 Rx/Tx transfer buffer rx *[descCDCACMRxCount]uint8 // bulk data endpoint Rx (OUT) transfer buffer @@ -156,21 +163,18 @@ type descCDCACMClass struct { rxCount *[descCDCACMRDCount]uint16 rxIndex *[descCDCACMRDCount]uint16 rxQueue *[descCDCACMRDCount + 1]uint16 + + _ [2]uint8 } -// 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). +// descCDCACMData holds statically-allocated instances for each of the target- +// specific (iMXRT1062) CDC-ACM (single) device class configurations' control +// and data structures, ordered by configuration index (offset by -1). Each +// element is embedded in a corresponding element of descCDCACM. //go:align 32 -var descCDCACM = [descCDCACMCount]descCDCACMClass{ - { - locale: &descCDCACM0String, - device: &descCDCACM0Device, - qualif: &descCDCACM0Qualif, - config: &descCDCACM0Config, - - lineCoding: &descCDCACM0LineCoding, +var descCDCACMData = [descCDCACMCount]descCDCACMClassData{ + { // CDC-ACM (single) class configuration index 1 data qh: &descCDCACM0QH, cd: &descCDCACM0CD, diff --git a/src/machine/usb/dhw_mimxrt1062.go b/src/machine/usb/dhw_mimxrt1062.go new file mode 100644 index 000000000..e2d95da40 --- /dev/null +++ b/src/machine/usb/dhw_mimxrt1062.go @@ -0,0 +1,1073 @@ +// +build mimxrt1062 + +package usb + +// Implementation of USB device controller hardware abstraction (dhw) for NXP +// iMXRT1062. + +import ( + "device/arm" + "device/nxp" + "math/bits" + "runtime/interrupt" + "runtime/volatile" + "unsafe" +) + +// dhwInterruptPriority defines the priority for all USB device interrupts. +const dhwInterruptPriority = 3 + +// dhw implements USB device controller hardware abstraction for iMXRT1062. +type dhw struct { + *dcd // USB device controller driver + + bus *nxp.USB_Type // USB core register + phy *nxp.USBPHY_Type // USB PHY register + irq interrupt.Interrupt // USB IRQ, only a single interrupt on iMXRT1062 + + stat *dhwEndpoint // endpoint 0 Rx ("out" direction) + ctrl *dhwEndpoint // endpoint 0 Tx ("in" direction) + + busSpeed uint8 // 0 = full, 1 = low, 2 = high, 4 = super + + controlReply [8]uint8 + controlMask uint32 + endpointMask uint32 + setup dcdSetup + + timerInterrupt [2]func() + timerReboot uint8 + sofUsage uint8 +} + +// cycleCount uses the ARM debug cycle counter available on iMXRT1062 (enabled +// in runtime_mimxrt1062_time.go) to return the number of CPU cycles since boot. +//go:inline +func cycleCount() uint32 { + return (*volatile.Register32)(unsafe.Pointer(uintptr(0xe0001004))).Get() +} + +// deleteCache deletes cached data without touching physical memory. Useful for +// receiving data via DMA, which writes directly to memory, as this will force +// subsequent reads to ignore cache and access physical memory. +func deleteCache(addr, size uintptr) { nxp.DeleteDcache(addr, size) } + +// flushCache immediately flushes cached data to physical memory. Useful for +// transmitting data via DMA, which reads directly from memory, as this will +// immediately flush data currently in cache to physical memory. This also +// purges the data from cache, since we no longer need to access it after +// priming DMA for transmission. +func flushCache(addr, size uintptr) { nxp.FlushDeleteDcache(addr, size) } + +// allocDHW returns a reference to the USB hardware abstraction for the given +// device controller driver. Should be called only one time and during device +// controller initialization. +func allocDHW(port, instance int, dc *dcd) *dhw { + switch port { + case 0: + dhwInstance[instance].dcd = dc + dhwInstance[instance].bus = nxp.USB1 + dhwInstance[instance].phy = nxp.USBPHY1 + dhwInstance[instance].irq = + interrupt.New(nxp.IRQ_USB_OTG1, + func(interrupt.Interrupt) { + coreInstance[0].dc.interrupt() + }) + + case 1: + dhwInstance[instance].dcd = dc + dhwInstance[instance].bus = nxp.USB2 + dhwInstance[instance].phy = nxp.USBPHY2 + dhwInstance[instance].irq = + interrupt.New(nxp.IRQ_USB_OTG2, + func(interrupt.Interrupt) { + //coreInstance[1].dc.interrupt() + }) + } + + return &dhwInstance[instance] +} + +// init configures the USB port for device mode operation by initializing all +// endpoint and transfer descriptor data structures, initializing core registers +// and interrupts, resetting the USB PHY, and enabling power on the bust. +func (d *dhw) init() status { + + // Reset the controller + d.phy.CTRL_SET.Set(nxp.USBPHY_CTRL_SFTRST) + d.bus.USBCMD.SetBits(nxp.USB_USBCMD_RST) + for d.bus.USBCMD.HasBits(nxp.USB_USBCMD_RST) { + } + + // Initialize USB interrupt priorities + d.irq.SetPriority(dhwInterruptPriority) + + // Clear interrupts + m := arm.DisableInterrupts() + switch d.port { + case 0: + m &^= uintptr(nxp.IRQ_USB_OTG1) + case 1: + m &^= uintptr(nxp.IRQ_USB_OTG2) + } + arm.EnableInterrupts(m) + + // Initiate reset, and enable PHY power supply + d.phy.CTRL_CLR.Set(nxp.USBPHY_CTRL_CLKGATE | nxp.USBPHY_CTRL_SFTRST) + d.phy.PWD.Set(0) // ["Power down"] 0 = Power enabled, 1 = Power disabled + + // Clear the controller mode field and set to device mode: + // Controller mode (CM) 0x0=idle, 0x2=device-only, 0x3=host-only + d.bus.USBMODE.ReplaceBits(nxp.USB_USBMODE_CM_CM_2, + nxp.USB_USBMODE_CM_Msk>>nxp.USB_USBMODE_CM_Pos, nxp.USB_USBMODE_CM_Pos) + + d.bus.USBCMD.ClearBits(nxp.USB_USBCMD_ITC_Msk) // No interrupt threshold + d.bus.USBMODE.SetBits(nxp.USB_USBMODE_SLOM_Msk) // Disable setup lockout + d.bus.USBMODE.ClearBits(nxp.USB_USBMODE_ES_Msk) // Use little-endianness + + // Initialize control endpoint 0 (stat = Rx/OUT, ctrl = Tx/IN) + d.stat = d.endpointQueueHead(rxEndpoint(0)) + d.ctrl = d.endpointQueueHead(txEndpoint(0)) + d.stat.config = (descEndptMaxPktSize << 16) | (1 << 15) + d.ctrl.config = (descEndptMaxPktSize << 16) + + // Install base address of endpoints + d.bus.ASYNCLISTADDR.Set(uint32(uintptr(unsafe.Pointer(d.stat)))) + + // Clear installed timer callbacks + d.timerInterrupt[0] = nil + d.timerInterrupt[1] = nil + + // Enable interrupts in USB core + d.bus.USBINTR.Set( + nxp.USB_USBINTR_UE_Msk | // bus enable + nxp.USB_USBINTR_UEE_Msk | // bus error + nxp.USB_USBINTR_PCE_Msk | // port change detect + nxp.USB_USBINTR_URE_Msk | // bus reset + nxp.USB_USBINTR_SLE) // sleep enable + + // Ensure D+ pulled down long enough for host to detect previous disconnect + udelay(5000) + + return statusOK +} + +// enable causes the USB core to enter (or exit) the normal run state and +// enables/disables all interrupts on the receiver's USB port. +func (d *dhw) enable(enable bool) { + if enable { + d.irq.Enable() // Enable USB interrupts + d.bus.USBCMD.SetBits(nxp.USB_USBCMD_RS) // Enable "run" state + } else { + d.bus.USBCMD.ClearBits(nxp.USB_USBCMD_RS) // Disable "run" state + d.irq.Disable() // Disable USB interrupts + } +} + +// enableInterrupts enables/disables all interrupts on the receiver's USB port. +func (d *dhw) enableInterrupts(enable bool) { + if enable { + d.irq.Enable() + } else { + d.irq.Disable() + } +} + +// enableSOF enables or disables start-of-frame (SOF) interrupts on the given +// USB device interface. +func (d *dhw) enableSOF(enable bool, iface uint8) { + if enable { + ivm := arm.DisableInterrupts() + d.sofUsage |= 1 << iface + if !d.bus.USBINTR.HasBits(nxp.USB_USBINTR_SRE) { + d.bus.USBSTS.Set(nxp.USB_USBSTS_SRI) + d.bus.USBINTR.SetBits(nxp.USB_USBINTR_SRE) + } + arm.EnableInterrupts(ivm) + } else { + d.sofUsage &^= 1 << iface + if 0 == d.sofUsage { + d.bus.USBINTR.ClearBits(nxp.USB_USBINTR_SRE) + } + } +} + +// interrupt handles the USB hardware interrupt events and notifies the device +// controller driver using a common "virtual interrupt" code. +func (d *dhw) interrupt() { + + // read and clear the interrupts that fired + status := d.bus.USBSTS.Get() & d.bus.USBINTR.Get() + d.bus.USBSTS.Set(status) + + // USB Interrupt (USBINT) - R/WC + // This bit is set by the Host/Device Controller when the cause of an + // interrupt is a completion of a USB transaction where the Transfer + // Descriptor (TD) has an interrupt on complete (IOC) bit set. + // This bit is also set by the Host/Device Controller when a short packet is + // detected. A short packet is when the actual number of bytes received was + // less than the expected number of bytes. + if 0 != status&nxp.USB_USBSTS_UI { + + setupStatus := d.bus.ENDPTSETUPSTAT.Get() + for 0 != setupStatus { + d.bus.ENDPTSETUPSTAT.Set(setupStatus) + var setup dcdSetup + ready := false + for !ready { + d.bus.USBCMD.SetBits(nxp.USB_USBCMD_SUTW) + setup = d.stat.setup + ready = d.bus.USBCMD.HasBits(nxp.USB_USBCMD_SUTW) + } + d.bus.USBCMD.ClearBits(nxp.USB_USBCMD_SUTW) + // flush endpoint 0 (bit 0=Rx, 16=Tx) + d.bus.ENDPTFLUSH.Set(0x00010001) + // wait for flush to complete + for d.bus.ENDPTFLUSH.HasBits(0x00010001) { + } + // Notify device controller driver + d.event(dcdEvent{ + id: dcdEventControlSetup, + setup: setup, + }) + setupStatus = d.bus.ENDPTSETUPSTAT.Get() + } + + completeStatus := d.bus.ENDPTCOMPLETE.Get() + if 0 != completeStatus { + d.bus.ENDPTCOMPLETE.Set(completeStatus) + if 0 != completeStatus&d.controlMask { + d.controlComplete(completeStatus) + } + completeStatus &= d.endpointMask + if 0 != completeStatus { + tx := completeStatus >> 16 + for 0 != tx { + num := uint8(bits.TrailingZeros32(tx)) + d.endpointComplete(txEndpoint(num)) + tx &^= 1 << num + } + rx := completeStatus & 0xFFFF + for 0 != rx { + num := uint8(bits.TrailingZeros32(rx)) + d.endpointComplete(rxEndpoint(num)) + rx &^= 1 << num + } + } + // Notify device controller driver + d.event(dcdEvent{ + id: dcdEventTransactComplete, + mask: completeStatus, + }) + } + } + + // USB Reset Received - R/WC + // When the device controller detects a USB Reset and enters the default + // state, this bit will be set to a one. + // Software can write a 1 to this bit to clear the USB Reset Received status + // bit. + // Only used in device operation mode. + if 0 != status&nxp.USB_USBSTS_URI { + // clear all setup tokens + d.bus.ENDPTSETUPSTAT.Set(d.bus.ENDPTSETUPSTAT.Get()) + // clear all endpoint complete status + d.bus.ENDPTCOMPLETE.Set(d.bus.ENDPTCOMPLETE.Get()) + // wait on any endpoint priming + for 0 != d.bus.ENDPTPRIME.Get() { + } + d.bus.ENDPTFLUSH.Set(0xFFFFFFFF) + d.event(dcdEvent{id: dcdEventStatusReset}) + } + + // General Purpose Timer Interrupt 0(GPTINT0) - R/WC + // This bit is set when the counter in the GPTIMER0CTRL register transitions + // to zero, writing a one to this bit clears it. + if 0 != status&nxp.USB_USBSTS_TI0 { + if nil != d.timerInterrupt[0] { + d.timerInterrupt[0]() + } + d.event(dcdEvent{id: dcdEventTimer, mask: 0}) + } + + // General Purpose Timer Interrupt 1(GPTINT1) - R/WC + // This bit is set when the counter in the GPTIMER1CTRL register transitions + // to zero, writing a one to this bit will clear it. + if 0 != status&nxp.USB_USBSTS_TI1 { + if nil != d.timerInterrupt[1] { + d.timerInterrupt[1]() + } + d.event(dcdEvent{id: dcdEventTimer, mask: 1}) + } + + // Port Change Detect - R/WC + // The Host Controller sets this bit to a one when on any port a Connect + // Status occurs, a Port Enable/Disable Change occurs, or the Force Port + // Resume bit is set as the result of a J-K transition on the suspended port. + // The Device Controller sets this bit to a one when the port controller + // enters the full or high-speed operational state. When the port controller + // exits the full or high-speed operation states due to Reset or Suspend + // events, the notification mechanisms are the USB Reset Received bit and the + // DCSuspend bits respectively. + if 0 != status&nxp.USB_USBSTS_PCI { + if d.bus.PORTSC1.HasBits(nxp.USB_PORTSC1_HSP) { + d.busSpeed = descDeviceSpeedHigh // 480 Mbit/sec + } else { + d.busSpeed = descDeviceSpeedFull // 12 Mbit/sec + } + d.event(dcdEvent{id: dcdEventStatusRun}) + } + + // DCSuspend - R/WC + // When a controller enters a suspend state from an active state, this bit + // will be set to a one. The device controller clears the bit upon exiting + // from a suspend state. Only used in device operation mode. + if 0 != status&nxp.USB_USBSTS_SLI { + d.event(dcdEvent{id: dcdEventStatusSuspend}) + } + + // USB Error Interrupt (USBERRINT) - R/WC + // When completion of a USB transaction results in an error condition, this + // bit is set by the Host/Device Controller. This bit is set along with the + // USBINT bit, if the TD on which the error interrupt occurred also had its + // interrupt on complete (IOC) bit set. + // The device controller detects resume signaling only. + if 0 != status&nxp.USB_USBSTS_UEI { + d.event(dcdEvent{id: dcdEventStatusError}) + } + + // SOF Received - R/WC + // When the device controller detects a Start Of (micro) Frame, this bit will + // be set to a one. When a SOF is extremely late, the device controller will + // automatically set this bit to indicate that an SOF was expected. + // Therefore, this bit will be set roughly every 1ms in device FS mode and + // every 125us in HS mode and will be synchronized to the actual SOF that is + // received. + // Because the device controller is initialized to FS before connect, this bit + // will be set at an interval of 1ms during the prelude to connect and chirp. + // In host mode, this bit will be set every 125us and can be used by host + // controller driver as a time base. Software writes a 1 to this bit to clear + // it. + if d.bus.USBINTR.HasBits(nxp.USB_USBINTR_SRE) && + 0 != status&nxp.USB_USBSTS_SRI { + if 0 != d.timerReboot { + d.timerReboot -= 1 + if 0 == d.timerReboot { + d.enableSOF(false, descCDCACMInterfaceCount) + } + } + } +} + +func (d *dhw) controlBusSpeed() uint8 { return d.busSpeed } + +func (d *dhw) controlDeviceAddress(addr uint16) { + d.bus.DEVICEADDR.Set(nxp.USB_DEVICEADDR_USBADRA | + ((uint32(addr) << nxp.USB_DEVICEADDR_USBADR_Pos) & + nxp.USB_DEVICEADDR_USBADR_Msk)) +} + +func (d *dhw) controlLineState(coding descCDCACMLineCoding, dtr, rts bool) { + // TBD: does the PHY need to handle on iMXRT1062 (e.g., Teensyduino Loader)? +} + +func (d *dhw) controlLineCoding(coding descCDCACMLineCoding) { + if 134 == coding.baud { + d.enableSOF(true, descCDCACMInterfaceCount) + } +} + +// controlStatus transitions transfers on control endpoint 0 into status stage. +func (d *dhw) controlStatus() { + // Not used on iMXRT1062 +} + +// controlStall stalls a transfer on control endpoint 0. To stall a transfer on +// any other endpoint, use method endpointStall(). +func (d *dhw) controlStall() { + d.endpointStall(0) +} + +// controlReceive receives (Rx, OUT) data on control endpoint 0. +func (d *dhw) controlReceive( + data uintptr, size uint32, notify bool) { + const ( + rm = uint32(1 << descEndptConfigAttrRxPos) + tm = uint32(1 << descEndptConfigAttrTxPos) + ) + cd, ad := d.transferControl() + if size > 0 { + cd.next = dhwTransferEOL + cd.token = (size << 16) | (1 << 7) + for i := range cd.pointer { + cd.pointer[i] = data + uintptr(i)*4096 + } + // linked list is empty + qr := d.endpointQueueHead(rxEndpoint(0)) + qr.transfer.next = cd + qr.transfer.token = 0 + d.bus.ENDPTPRIME.SetBits(rm) + for 0 != d.bus.ENDPTPRIME.Get() { + } // wait for endpoint finish priming + } + ad.next = dhwTransferEOL + ad.token = 1 << 7 + if notify { + ad.token |= 1 << 15 + } + ad.pointer[0] = 0 + qt := d.endpointQueueHead(txEndpoint(0)) + qt.transfer.next = ad + qt.transfer.token = 0 + d.bus.ENDPTCOMPLETE.Set(rm | tm) + d.bus.ENDPTPRIME.SetBits(tm) + if notify { + d.controlMask = tm + } +} + +// controlTransmit transmits (Tx, IN) data on control endpoint 0. +func (d *dhw) controlTransmit( + data uintptr, size uint32, notify bool) { + const ( + rm = uint32(1 << descEndptConfigAttrRxPos) + tm = uint32(1 << descEndptConfigAttrTxPos) + ) + cd, ad := d.transferControl() + if size > 0 { + cd.next = dhwTransferEOL + cd.token = (size << 16) | (1 << 7) + for i := range cd.pointer { + cd.pointer[i] = data + uintptr(i)*4096 + } + // linked list is empty + qt := d.endpointQueueHead(txEndpoint(0)) + qt.transfer.next = cd + qt.transfer.token = 0 + d.bus.ENDPTPRIME.SetBits(tm) + for 0 != d.bus.ENDPTPRIME.Get() { + } // wait for endpoint finish priming + } + ad.next = dhwTransferEOL + ad.token = 1 << 7 + if notify { + ad.token |= 1 << 15 + } + ad.pointer[0] = 0 + qr := d.endpointQueueHead(rxEndpoint(0)) + qr.transfer.next = ad + qr.transfer.token = 0 + d.bus.ENDPTCOMPLETE.Set(rm | tm) + d.bus.ENDPTPRIME.SetBits(rm) + if notify { + d.controlMask = rm + } +} + +// dhwEndpointSize defines the size (bytes) of a structure containing a USB +// standard endpoint. +const dhwEndpointSize = 64 // bytes + +// dhwEndpoint defines a USB standard endpoint, used as the general channel of +// communication between host and device. +type dhwEndpoint struct { + config uint32 + current *dhwTransfer + transfer dhwTransfer + setup dcdSetup + // Endpoints are 48-byte data structures. The remaining data extends this to + // 64-byte, and also makes it simpler to align contiguous endpoints on 64-byte + // boundaries. + first *dhwTransfer + last *dhwTransfer + // After some discussion, perhaps the simplest change to support 64-bit (or + // future TinyGo versions that don't use 8 bytes to refer to a function) is to + // allocate a separate buffer of callbacks. The device controller would assign + // callbacks to unused elements in that buffer, and only the index of that + // callback would be stored here in the endpoint. + callback func(transfer *dhwTransfer) +} + +// endpointQueueHead returns the queue head for the given endpoint address, +// encoded as direction D and endpoint number N with the 8-bit mask DxxxNNNN. +//go:inline +func (d *dhw) endpointQueueHead(endpoint uint8) *dhwEndpoint { + // endpoint queue head is device class-specific + switch d.cc.id { + case classDeviceCDCACM: + return &descCDCACM[d.cc.config-1].qh[endpointIndex(endpoint)] + default: + return nil + } +} + +func (d *dhw) endpointControlRegister(endpoint uint8) *volatile.Register32 { + num, _ := unpackEndpoint(endpoint) + switch num { + case 0: + return &d.bus.ENDPTCTRL0 + case 1: + return &d.bus.ENDPTCTRL1 + case 2: + return &d.bus.ENDPTCTRL2 + case 3: + return &d.bus.ENDPTCTRL3 + case 4: + return &d.bus.ENDPTCTRL4 + case 5: + return &d.bus.ENDPTCTRL5 + case 6: + return &d.bus.ENDPTCTRL6 + case 7: + return &d.bus.ENDPTCTRL7 + } + return nil +} + +func (d *dhw) endpointEnable(endpoint uint8, control bool, config uint32) { + // control endpoint 0 configured in dhw init + if !control || 0 != endpoint&descEndptAddrNumberMsk { + d.endpointControlRegister(endpoint & descEndptAddrNumberMsk).Set(config) + } +} + +func (d *dhw) endpointStatus(endpoint uint8) uint16 { + status := uint16(0) + switch endpoint { + case rxEndpoint(endpoint): + status |= uint16((d.endpointControlRegister(endpoint).Get() & + nxp.USB_ENDPTCTRL0_RXS_Msk) >> nxp.USB_ENDPTCTRL0_RXS_Pos) + case txEndpoint(endpoint): + status |= uint16((d.endpointControlRegister(endpoint).Get() & + nxp.USB_ENDPTCTRL0_TXS_Msk) >> nxp.USB_ENDPTCTRL0_TXS_Pos) + } + return status +} + +// endpointStall stalls a transfer on the given endpoint. +func (d *dhw) endpointStall(endpoint uint8) { + // RXS and TXS bits at same position in all endpoint control registers. + d.endpointControlRegister(endpoint).SetBits( + nxp.USB_ENDPTCTRL0_RXS | nxp.USB_ENDPTCTRL0_TXS, + ) +} + +// func (d *dhw) endpointPrime(mask uint32, transfer *dhwTransfer) { +// d.bus.ENDPTPRIME.Set(mask) +// } + +// func (d *dhw) endpointPrimed() uint32 { +// return d.bus.ENDPTPRIME.Get() +// } + +// func (d *dhw) endpointUnprime(mask uint32) { +// d.bus.ENDPTCOMPLETE.Set(mask) +// } + +// endpointConfigure configures the given bulk data endpoint for transfer. +func (d *dhw) endpointConfigure( + ep *dhwEndpoint, packetSize uint16, zlp bool, callback func(transfer *dhwTransfer)) { + + ep.config = uint32(packetSize) << 16 + if !zlp { + ep.config |= 1 << 29 + } + ep.current = nil + ep.transfer.next = dhwTransferEOL + ep.transfer.token = 0 + for i := range ep.transfer.pointer { + ep.transfer.pointer[i] = 0 + } + ep.transfer.param = 0 + ep.setup.bmRequestType = 0 + ep.setup.bRequest = 0 + ep.setup.wValue = 0 + ep.setup.wIndex = 0 + ep.setup.wLength = 0 + ep.first = nil + ep.last = nil + ep.callback = callback +} + +// endpointComplete handles transfer completion of a data endpoint. +func (d *dhw) endpointComplete(endpoint uint8) { + ep := d.endpointQueueHead(endpoint) + if nil == ep.first { + return + } + count := 0 + first := ep.first + for t, eol := first, false; !eol; t, eol = t.nextTransfer() { + if eol { + // reached end of list, new list empty + ep.first = nil + ep.last = nil + } else { + if 0 != t.token&(1<<7) { + // active transfer, new list begins here + ep.first = t + break + } else { + count += 1 + } + } + } + // invoke all callbacks + for i := 0; i < count; i++ { + next := first.next + ep.callback(first) + first = next + } +} + +// endpointConfigureRx configures the given bulk data receive (Rx, OUT) endpoint +// for transfer. +func (d *dhw) endpointConfigureRx( + endpoint uint8, packetSize uint16, zlp bool, callback func(transfer *dhwTransfer)) { + + // Configure based on our device class configuration + switch d.cc.id { + + // CDC-ACM (single) + case classDeviceCDCACM: + if endpoint < descCDCACMEndpointStatus || + endpoint > descCDCACMEndpointCount { + return + } + ep := d.endpointQueueHead(rxEndpoint(endpoint)) + d.endpointConfigure(ep, packetSize, zlp, callback) + if nil != callback { + d.endpointMask |= (uint32(1) << endpoint) << descEndptConfigAttrRxPos + } + + default: + // Unhandled device class + } +} + +// endpointConfigureTx configures the given bulk data transmit (Tx, IN) endpoint +// for transfer. +func (d *dhw) endpointConfigureTx( + endpoint uint8, packetSize uint16, zlp bool, callback func(transfer *dhwTransfer)) { + + // Configure based on our device class configuration + switch d.cc.id { + + // CDC-ACM (single) + case classDeviceCDCACM: + if endpoint < descCDCACMEndpointStatus || + endpoint > descCDCACMEndpointCount { + return + } + ep := d.endpointQueueHead(txEndpoint(endpoint)) + d.endpointConfigure(ep, packetSize, zlp, callback) + if nil != callback { + d.endpointMask |= (uint32(1) << endpoint) << descEndptConfigAttrTxPos + } + + default: + // Unhandled device class + } +} + +// endpointReceive schedules a receive (Rx, OUT) transfer on the given endpoint. +func (d *dhw) endpointReceive(endpoint uint8, transfer *dhwTransfer) { + + // Configure based on our device class configuration + switch d.cc.id { + + // CDC-ACM (single) + case classDeviceCDCACM: + if endpoint < descCDCACMEndpointStatus || + endpoint > descCDCACMEndpointCount { + return + } + ep := d.endpointQueueHead(rxEndpoint(endpoint)) + em := (uint32(1) << endpoint) << descEndptConfigAttrRxPos + d.transferSchedule(ep, em, transfer) + + default: + // Unhandled device class + } +} + +// endpointTransmit schedules a transmit (Tx, IN) transfer on the given +// endpoint. +func (d *dhw) endpointTransmit(endpoint uint8, transfer *dhwTransfer) { + + // Configure based on our device class configuration + switch d.cc.id { + + // CDC-ACM (single) + case classDeviceCDCACM: + if endpoint < descCDCACMEndpointStatus || + endpoint > descCDCACMEndpointCount { + return + } + ep := d.endpointQueueHead(txEndpoint(endpoint)) + em := (uint32(1) << endpoint) << descEndptConfigAttrTxPos + d.transferSchedule(ep, em, transfer) + + default: + // Unhandled device class + } +} + +// dhwTransferSize defines the size (bytes) of a USB standard transfer packet. +const dhwTransferSize = 32 // bytes + +// dhwTransfer describes the size and location of data to be transferred to or +// from a USB endpoint. +type dhwTransfer struct { + next *dhwTransfer + token uint32 + pointer [5]uintptr + param uint32 +} + +// dhwTransferEOL is a sentinel value used to indicate the final node in a +// linked list of transfer descriptors. +var dhwTransferEOL = (*dhwTransfer)(unsafe.Pointer(uintptr(1))) + +// nextTransfer returns the next transfer descriptor pointed to by the receiver +// transfer descriptor, and whether or not that next descriptor is the final +// descriptor in the list. +func (t dhwTransfer) nextTransfer() (*dhwTransfer, bool) { + return t.next, 1 == uintptr(unsafe.Pointer(t.next)) +} + +// transferControl returns the data and ackowledgement transfer descriptors for +// the control endpoint (i.e., endpoint 0). +//go:inline +func (d *dhw) transferControl() (dat, ack *dhwTransfer) { + // control endpoint is device class-specific + switch d.cc.id { + case classDeviceCDCACM: + return descCDCACM[d.cc.config-1].cd, descCDCACM[d.cc.config-1].ad + default: + return nil, nil + } +} + +func (d *dhw) transferPrepare( + transfer *dhwTransfer, data *uint8, size uint16, param uint32) { + + transfer.next = dhwTransferEOL + transfer.token = (uint32(size) << 16) | (1 << 7) + addr := uintptr(unsafe.Pointer(data)) + for i := range transfer.pointer { + transfer.pointer[i] = addr + uintptr(i)*4096 + } + transfer.param = param +} + +func (d *dhw) transferSchedule( + endpoint *dhwEndpoint, mask uint32, transfer *dhwTransfer) { + + if nil != endpoint.callback { + transfer.token |= 1 << 15 + } + ivm := arm.DisableInterrupts() + last := endpoint.last + if nil != last { + last.next = transfer + if d.bus.ENDPTPRIME.HasBits(mask) { + goto endTransfer + } + start := cycleCount() + estat := uint32(0) + for !d.bus.USBCMD.HasBits(nxp.USB_USBCMD_ATDTW) && + (cycleCount()-start < 2400) { + d.bus.USBCMD.SetBits(nxp.USB_USBCMD_ATDTW) + estat = d.bus.ENDPTSTAT.Get() + } + if 0 != estat&mask { + goto endTransfer + } + } + endpoint.transfer.next = transfer + endpoint.transfer.token = 0 + d.bus.ENDPTPRIME.SetBits(mask) + endpoint.first = transfer +endTransfer: + endpoint.last = transfer + arm.EnableInterrupts(ivm) +} + +func (d *dhw) timerConfigure(timer int, usec uint32, fn func()) { + if timer < 0 || timer >= len(d.timerInterrupt) { + return + } + d.timerInterrupt[timer] = fn + switch timer { + case 0: + d.bus.GPTIMER0CTRL.Set(0) + d.bus.GPTIMER0LD.Set(usec - 1) + d.bus.USBINTR.SetBits(nxp.USB_USBINTR_TIE0) + case 1: + d.bus.GPTIMER1CTRL.Set(0) + d.bus.GPTIMER1LD.Set(usec - 1) + d.bus.USBINTR.SetBits(nxp.USB_USBINTR_TIE1) + } +} + +func (d *dhw) timerOneShot(timer int) { + switch timer { + case 0: + d.bus.GPTIMER0CTRL.Set( + nxp.USB_GPTIMER0CTRL_GPTRUN | nxp.USB_GPTIMER0CTRL_GPTRST) + case 1: + d.bus.GPTIMER1CTRL.Set( + nxp.USB_GPTIMER1CTRL_GPTRUN | nxp.USB_GPTIMER1CTRL_GPTRST) + } +} + +func (d *dhw) timerStop(timer int) { + switch timer { + case 0: + d.bus.GPTIMER0CTRL.Set(0) + case 1: + d.bus.GPTIMER1CTRL.Set(0) + } +} + +func (d *dhw) uartConfigure() { + acm := &descCDCACM[d.cc.config-1] + switch d.controlBusSpeed() { + case descDeviceSpeedHigh: + acm.rxSize = descCDCACMDataRxHSPacketSize + acm.txSize = descCDCACMDataTxHSPacketSize + default: + acm.rxSize = descCDCACMDataRxFSPacketSize + acm.txSize = descCDCACMDataTxFSPacketSize + } + acm.txHead = 0 + acm.txFree = 0 + acm.rxHead = 0 + acm.rxTail = 0 + acm.rxFree = 0 + + d.endpointEnable(descCDCACMEndpointStatus, + false, descCDCACMConfigAttrStatus) + d.endpointEnable(descCDCACMEndpointDataRx, + false, descCDCACMConfigAttrDataRx) + d.endpointEnable(descCDCACMEndpointDataTx, + false, descCDCACMConfigAttrDataTx) + + d.endpointConfigureTx(descCDCACMEndpointStatus, + acm.cxSize, false, nil) + d.endpointConfigureRx(descCDCACMEndpointDataRx, + acm.rxSize, false, d.uartNotify) + d.endpointConfigureTx(descCDCACMEndpointDataTx, + acm.txSize, true, nil) + for i := range acm.rd { + d.uartReceive(uint8(i)) + } + d.timerConfigure(0, descCDCACMTxSyncUs, d.uartSync) +} + +func (d *dhw) uartReceive(endpoint uint8) { + acm := &descCDCACM[d.cc.config-1] + num := uint16(endpoint) & descEndptAddrNumberMsk + buf := &acm.rx[num*descCDCACMRxSize] + d.enableInterrupts(false) + d.transferPrepare(&acm.rd[num], buf, acm.rxSize, uint32(endpoint)) + deleteCache(uintptr(unsafe.Pointer(buf)), uintptr(acm.rxSize)) + d.endpointReceive(descCDCACMEndpointDataRx, &acm.rd[num]) + d.enableInterrupts(true) +} + +func (d *dhw) uartNotify(transfer *dhwTransfer) { + acm := &descCDCACM[d.cc.config-1] + len := acm.rxSize - (uint16(transfer.token>>16) & 0x7FFF) + p := transfer.param + if 0 == len { + // zero-length packet (ZLP) + d.uartReceive(uint8(p)) + } else { + // data packet + h := acm.rxHead + if h != acm.rxTail { + // previous packet is still buffered + q := acm.rxQueue[h] + n := acm.rxCount[q] + if len <= descCDCACMRxSize-n { + // previous buffer has enough free space for this packet's data + _ = copy(acm.rx[q*descCDCACMRxSize+n:], + acm.rx[p*descCDCACMRxSize:uint16(p)*descCDCACMRxSize+len]) + acm.rxCount[q] = n + len + acm.rxFree += len + d.uartReceive(uint8(p)) + return + } + } + // add this packet to Rx buffer + acm.rxCount[p] = len + acm.rxIndex[p] = 0 + h += 1 + if h > descCDCACMRDCount { // should be >= + h = 0 + } + acm.rxQueue[h] = uint16(p) + acm.rxHead = h + acm.rxFree += len + } +} + +// uartFlush discards all buffered input (Rx) data. +func (d *dhw) uartFlush() { + acm := &descCDCACM[d.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] + d.uartReceive(uint8(i)) + acm.rxTail = tail + } +} + +func (d *dhw) uartAvailable() int { + return int(descCDCACM[d.cc.config-1].rxFree) +} + +func (d *dhw) uartPeek() (uint8, bool) { + acm := &descCDCACM[d.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 (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)) + tail := acm.rxTail + dest := uint16(0) + d.enableInterrupts(false) + 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 + d.uartReceive(uint8(i)) + } + } + d.enableInterrupts(true) + return int(read) +} + +func (d *dhw) uartWriteByte(c uint8) bool { + return 1 == d.uartWrite([]uint8{c}) +} + +func (d *dhw) uartWrite(data []uint8) int { + acm := &descCDCACM[d.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] + d.transferPrepare(xfer, tx, descCDCACMTxSize, 0) + flushCache(uintptr(unsafe.Pointer(tx)), descCDCACMTxSize) + d.endpointTransmit(descCDCACMEndpointDataTx, xfer) + acm.txHead += 1 + if acm.txHead >= descCDCACMTDCount { + acm.txHead = 0 + } + size -= int(acm.txFree) + sent += int(acm.txFree) + acm.txFree = 0 + d.timerStop(0) + } else { + _ = copy(buff, data[:size]) + acm.txFree -= uint16(size) + sent += size + size = 0 + d.timerOneShot(0) + } + } + return sent +} + +func (d *dhw) uartSync() { + const autoFlushTx = true + if !autoFlushTx { + return + } + acm := &descCDCACM[d.cc.config-1] + if 0 == acm.txFree { + return + } + xfer := &acm.td[acm.txHead] + buff := &acm.tx[uint16(acm.txHead)*descCDCACMTxSize] + size := descCDCACMTxSize - acm.txFree + d.transferPrepare(xfer, buff, size, 0) + flushCache(uintptr(unsafe.Pointer(buff)), uintptr(size)) + d.endpointTransmit(descCDCACMEndpointDataTx, xfer) + acm.txHead += 1 + if acm.txHead >= descCDCACMTDCount { + acm.txHead = 0 + } + acm.txFree = 0 +} diff --git a/src/machine/usb/hcd.go b/src/machine/usb/hcd.go index 366bdcc9e..4e7f34d8d 100644 --- a/src/machine/usb/hcd.go +++ b/src/machine/usb/hcd.go @@ -1,10 +1,53 @@ package usb -type hcd interface { - class() class - init() status - enable(enable bool) status - critical(enter bool) status - interrupt() - udelay(micros uint32) +// Implementation of 32-bit target-agnostic USB host controller driver (hcd). + +// hcdCount defines the number of USB cores to configure for host mode. It is +// computed as the sum of all declared host configuration descriptors. +const hcdCount = 0 // + ... + +// hcdInstance provides statically-allocated instances of each USB host +// controller configured on this platform. +var hcdInstance [hcdCount]hcd + +// hhwInstance provides statically-allocated instances of each USB hardware +// abstraction for ports configured as host on this platform. +var hhwInstance [hcdCount]hhw + +// hcd implements USB host controller driver (hcd) interface. +type hcd struct { + *hhw // USB hardware abstraction layer + + core *core // Parent USB core this instance is attached to + port int // USB port index + cc class // USB host class + id int // USB host controller index } + +// initHCD initializes and assigns a free host controller instance to the given +// USB port. Returns the initialized host controller or nil if no free host +// controller instances remain. +func initHCD(port int, class class) (*hcd, status) { + if 0 == hcdCount { + return nil, statusInvalid // Must have defined host controllers + } + switch class.id { + default: + } + // Return the first instance whose assigned core is currently nil. + for i := range hcdInstance { + if nil == hcdInstance[i].core { + // Initialize host controller. + hcdInstance[i].hhw = allocHHW(port, i, &hcdInstance[i]) + hcdInstance[i].core = &coreInstance[port] + hcdInstance[i].port = port + hcdInstance[i].cc = class + hcdInstance[i].id = i + return &hcdInstance[i], statusOK + } + } + return nil, statusBusy // No free host controller instances available. +} + +// class returns the receiver's current host class configuration. +func (h *hcd) class() class { return h.cc } diff --git a/src/machine/usb/hcd_mimxrt1062.go b/src/machine/usb/hcd_mimxrt1062.go deleted file mode 100644 index 99d8b59f1..000000000 --- a/src/machine/usb/hcd_mimxrt1062.go +++ /dev/null @@ -1,130 +0,0 @@ -// +build mimxrt1062 - -package usb - -// Implementation of USB host controller driver (hcd) for NXP iMXRT1062. - -import ( - "device/arm" - "device/nxp" - "runtime/interrupt" - "runtime/volatile" -) - -// hcdCount defines the number of USB cores to configure for host mode. It is -// computed as the sum of all declared host configuration descriptors. -const hcdCount = 0 - -// hcdInterruptPriority defines the priority for all USB host interrupts. -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 - cc class // USB host class - id int // hostControllerInstance index - - bus *nxp.USB_Type - phy *nxp.USBPHY_Type - irq interrupt.Interrupt - - cri volatile.Register8 // set to 1 if in critical section, else 0 - ivm uintptr // interrupt state when entering critical section -} - -// hostControllerInstance provides statically-allocated instances of each USB -// host controller configured on this platform. -var hostControllerInstance [hcdCount]hostController - -// initHCD initializes and assigns a free host controller instance to the given -// USB port. Returns the initialized host controller or nil if no free host -// controller instances remain. -func initHCD(port int, class class) (hcd, status) { - if 0 == hcdCount { - return nil, statusInvalid // must have defined host controllers - } - // Return the first instance whose assigned core is currently nil. - for i := range hostControllerInstance { - if nil == hostControllerInstance[i].core { - // Initialize host controller. - hostControllerInstance[i].core = &coreInstance[port] - hostControllerInstance[i].port = port - hostControllerInstance[i].cc = class - hostControllerInstance[i].id = i - switch port { - case 0: - hostControllerInstance[i].bus = nxp.USB1 - hostControllerInstance[i].phy = nxp.USBPHY1 - //hostControllerInstance[i].irq = - // interrupt.New(nxp.IRQ_USB_OTG1, - // func(interrupt.Interrupt) { - // coreInstance[0].hc.interrupt() - // }) - - case 1: - hostControllerInstance[i].bus = nxp.USB2 - hostControllerInstance[i].phy = nxp.USBPHY2 - //hostControllerInstance[i].irq = - // interrupt.New(nxp.IRQ_USB_OTG2, - // func(interrupt.Interrupt) { - // //coreInstance[1].hc.interrupt() - // }) - } - return &hostControllerInstance[i], statusOK - } - } - return nil, statusBusy // No free host controller instances available. -} - -func (hc *hostController) class() class { return hc.cc } - -func (hc *hostController) init() status { - - return statusOK -} - -func (hc *hostController) enable(enable bool) status { - - hc.irq.SetPriority(hcdInterruptPriority) - hc.irq.Enable() - - return statusOK -} - -func (hc *hostController) critical(enter bool) status { - if enter { - // check if critical section already locked - if hc.cri.Get() != 0 { - return statusRetry - } - // lock critical section - hc.cri.Set(1) - // disable interrupts, storing state in receiver - hc.ivm = arm.DisableInterrupts() - } else { - // ensure critical section is locked - if hc.cri.Get() != 0 { - // re-enable interrupts, using state stored in receiver - arm.EnableInterrupts(hc.ivm) - // unlock critical section - hc.cri.Set(0) - } - } - return statusOK -} - -func (hc *hostController) interrupt() { - -} - -// udelay waits for the given number of microseconds before returning. -// We cannot use the sleep timer from this context (import cycle), but we need -// an approximate method to spin CPU cycles for short periods of time. -//go:inline -func (hc *hostController) udelay(microsec uint32) { - n := cycles(microsec, descCPUFrequencyHz) - for i := uint32(0); i < n; i++ { - arm.Asm(`nop`) - } -} diff --git a/src/machine/usb/hhw_mimxrt1062.go b/src/machine/usb/hhw_mimxrt1062.go new file mode 100644 index 000000000..52c188437 --- /dev/null +++ b/src/machine/usb/hhw_mimxrt1062.go @@ -0,0 +1,59 @@ +// +build mimxrt1062 + +package usb + +// Implementation of USB host controller driver (hcd) for NXP iMXRT1062. + +import ( + "device/nxp" + "runtime/interrupt" +) + +// hcdInterruptPriority defines the priority for all USB host interrupts. +const hcdInterruptPriority = 3 + +// hcd implements USB host controller driver (hcd) interface. +type hhw struct { + *hcd // USB host controller driver + + bus *nxp.USB_Type // USB core register + phy *nxp.USBPHY_Type // USB PHY register + irq interrupt.Interrupt // USB IRQ, only a single interrupt on iMXRT1062 +} + +// allocHHW returns a reference to the USB hardware abstraction for the given +// host controller driver. Should be called only one time and during host +// controller initialization. +func allocHHW(port, instance int, hc *hcd) *hhw { + switch port { + case 0: + hhwInstance[instance].hcd = hc + hhwInstance[instance].bus = nxp.USB1 + hhwInstance[instance].phy = nxp.USBPHY1 + + case 1: + hhwInstance[instance].hcd = hc + hhwInstance[instance].bus = nxp.USB2 + hhwInstance[instance].phy = nxp.USBPHY2 + } + + return &hhwInstance[instance] +} + +// init configures the USB port for host mode operation by initializing all +// endpoint and transfer descriptor data structures, initializing core registers +// and interrupts, resetting the USB PHY, and enabling power on the bust. +func (h *hhw) init() status { + + return statusOK +} + +// enable causes the USB core to enter (or exit) the normal run state and +// enables/disables all interrupts on the receiver's USB port. +func (h *hhw) enable(enable bool) { + if enable { + h.irq.Enable() // Enable USB interrupts + } else { + h.irq.Disable() // Disable USB interrupts + } +} diff --git a/src/machine/usb/uart.go b/src/machine/usb/uart.go index a5ea35c05..7c1ae2e90 100644 --- a/src/machine/usb/uart.go +++ b/src/machine/usb/uart.go @@ -41,21 +41,13 @@ func (uart *UART) Configure(config UARTConfig) error { // 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() + 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) { - dc, ok := uart.core.dc.(*deviceController) - if !ok { - return 0, ErrUARTInvalidCore - } - n, ok := dc.uartReadByte() + n, ok := uart.core.dc.uartReadByte() if !ok { return 0, ErrUARTEmptyBuffer } @@ -64,20 +56,12 @@ func (uart UART) ReadByte() (byte, error) { // 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 + return uart.core.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) { + if !uart.core.dc.uartWriteByte(c) { return ErrUARTWriteFailed } return nil @@ -85,9 +69,5 @@ func (uart UART) WriteByte(c byte) error { // 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 + return uart.core.dc.uartWrite(data), nil } diff --git a/src/machine/usb/usb.go b/src/machine/usb/usb.go index 4f3d4e2cf..40d325ddb 100644 --- a/src/machine/usb/usb.go +++ b/src/machine/usb/usb.go @@ -2,59 +2,6 @@ package usb // Hardware abstraction for USB ports configured as either host or device. -import "unsafe" - -func init() { - if unsafe.Sizeof(uintptr(0)) > 4 { - panic("USB is only supported on 32-bit systems") - } -} - -// core represents the core of a USB port configured as either host or device. -type core struct { - port int - mode int - dc dcd - hc hcd -} - -// Constant definitions for USB core operating modes. -const ( - modeIdle = 0 - modeDevice = 1 - modeHost = 2 -) - -// class represents the type of a host/device and its class configuration index. -// The first valid configuration index is 1. Index 0 is reserved and invalid. -type class struct { - id int - config int -} - -// Constant definitions for all host/device classes. -const ( - classDeviceCDCACM = 0 // The only currently-supported class (CDC-ACM) -) - -// mode returns the USB core operating mode of the receiver class cl. -//go:inline -func (cl class) mode() int { - switch cl.id { - case classDeviceCDCACM: - return modeDevice - default: - return modeIdle - } -} - -// 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 @@ -63,26 +10,29 @@ const CoreCount = dcdCount + hcdCount // configured on this platform. var coreInstance [CoreCount]core -// status represents the return code of a subroutine. -type status uint8 +// core represents the core of a USB port configured as either host or device. +type core struct { + port int + mode int + dc *dcd + hc *hcd +} -// Constant definitions for all status codes used within the package. +// Constant definitions for USB core operating modes. const ( - statusOK status = iota // Success - statusBusy // Busy - statusRetry // Retry - statusInvalid // Invalid argument + modeIdle = 0 // USB port has not been configured + modeDevice = 1 + modeHost = 2 ) -// ok returns true if and only if the receiver st equals statusOK. -//go:inline -func (st status) ok() bool { return statusOK == st } - // initCore initializes a free USB core with given operating mode on the USB // port at given index, if available. Returns a reference to the initialized // core or nil if the core is unavailable. func initCore(port int, class class) (*core, status) { + iv := disableInterrupts() + defer enableInterrupts(iv) + if port < 0 || port >= CoreCount || 0 == class.config { return nil, statusInvalid } @@ -90,9 +40,10 @@ func initCore(port int, class class) (*core, status) { 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. + // + // This will allow, for instance, 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: @@ -122,12 +73,7 @@ func initCore(port int, class class) (*core, status) { coreInstance[port].port = port coreInstance[port].mode = modeDevice coreInstance[port].dc = dc - // Enable interrupts and enter runtime - if st = dc.enable(true); !st.ok() { - coreInstance[port].mode = modeIdle - coreInstance[port].dc = nil - return nil, st - } + dc.enable(true) // Enable interrupts and enter runtime case modeHost: // Allocate a free host controller and install interrupts @@ -142,12 +88,7 @@ func initCore(port int, class class) (*core, status) { coreInstance[port].port = port coreInstance[port].mode = modeHost coreInstance[port].hc = hc - // Enable interrupts and enter runtime - if st = hc.enable(true); !st.ok() { - coreInstance[port].mode = modeIdle - coreInstance[port].hc = nil - return nil, st - } + hc.enable(true) // Enable interrupts and enter runtime default: return nil, statusInvalid @@ -155,3 +96,47 @@ func initCore(port int, class class) (*core, status) { return &coreInstance[port], statusOK } + +// class represents the type of a host/device and its class configuration index. +// The first valid configuration index is 1. Index 0 is reserved and invalid. +type class struct { + id int + config int +} + +// Enumerated constants for all host/device class configurations. +const ( + classDeviceCDCACM = 0 // The only currently-supported class (CDC-ACM) +) + +// mode returns the USB core operating mode of the receiver class c. +//go:inline +func (c class) mode() int { + switch c.id { + case classDeviceCDCACM: + return modeDevice + default: + return modeIdle + } +} + +// equals returns true if and only if all fields of the given class are equal to +// those of the receiver c. +//go:inline +func (c class) equals(class class) bool { + return c.id == class.id && c.config == class.config +} + +// status represents the return code of a subroutine. +type status uint8 + +// Constant definitions for all status codes used within the package. +const ( + statusOK status = iota // Success + statusBusy // Busy + statusInvalid // Invalid argument +) + +// ok returns true if and only if the receiver st equals statusOK. +//go:inline +func (s status) ok() bool { return statusOK == s } diff --git a/src/machine/usb/util_arm.go b/src/machine/usb/util_arm.go index 89ab4f646..750629357 100644 --- a/src/machine/usb/util_arm.go +++ b/src/machine/usb/util_arm.go @@ -4,6 +4,9 @@ package usb import "device/arm" +//go:linkname ticks runtime.ticks +func ticks() int64 + // udelay waits for the given number of microseconds before returning. // We cannot use the sleep timer from this context (import cycle), but we need // an approximate method to spin CPU cycles for short periods of time. @@ -14,3 +17,11 @@ func udelay(microsec uint32) { arm.Asm(`nop`) } } + +func disableInterrupts() uintptr { + return arm.DisableInterrupts() +} + +func enableInterrupts(mask uintptr) { + arm.EnableInterrupts(mask) +}