diff --git a/src/machine/machine_nrf52840_usb.go b/src/machine/machine_nrf52840_usb.go index 1fa46945fa..ba149783ba 100644 --- a/src/machine/machine_nrf52840_usb.go +++ b/src/machine/machine_nrf52840_usb.go @@ -22,6 +22,15 @@ var ( epinen uint32 epouten uint32 easyDMABusy volatile.Register8 + // epOutFlowControl contains the flow control state of the USB OUT endpoints. + epOutFlowControl [NumberOfUSBEndpoints]struct { + // nak indicates that we are NAKing any further OUT packets because the rxHandler isn't ready yet. + // When this is true, we do not restart the DMA for the endpoint, effectively pausing it. + nak bool + // dataPending indicates that we have data in the hardware buffer that hasn't been handled yet. + // Having one in the buffer is what generates the NAK responses, this is a signal to handle it. + dataPending bool + } endPoints = []uint32{ usb.CONTROL_ENDPOINT: usb.ENDPOINT_TYPE_CONTROL, @@ -196,7 +205,16 @@ func handleUSBIRQ(interrupt.Interrupt) { nrf.USBD.EPOUT[i].PTR.Set(uint32(uintptr(unsafe.Pointer(&udd_ep_out_cache_buffer[i])))) count := nrf.USBD.SIZE.EPOUT[i].Get() nrf.USBD.EPOUT[i].MAXCNT.Set(count) - nrf.USBD.TASKS_STARTEPOUT[i].Set(1) + if !epOutFlowControl[i].nak { + // Normal case: We want data, so start DMA immediately + nrf.USBD.TASKS_STARTEPOUT[i].Set(1) + epOutFlowControl[i].dataPending = false + } else { + // NAK case: We want to NAK, so DO NOT start DMA. + // The data stays in HW buffer. Host receives NAKs. + // Mark that we have data waiting so we can fetch it later. + epOutFlowControl[i].dataPending = true + } } } } @@ -208,6 +226,10 @@ func handleUSBIRQ(interrupt.Interrupt) { buf := handleEndpointRx(uint32(i)) if usbRxHandler[i] == nil || usbRxHandler[i](buf) { AckUsbOutTransfer(uint32(i)) + } else { + // usbRxHandler returned false, so NAK further OUT packets until we're ready + epOutFlowControl[i].nak = true + nrf.USBD.SIZE.EPOUT[i].Set(0) } exitCriticalSection() } @@ -229,19 +251,23 @@ func initEndpoint(ep, config uint32) { switch config { case usb.ENDPOINT_TYPE_INTERRUPT | usb.EndpointIn: enableEPIn(ep) + setEPDataPID(ep|usb.EndpointIn, false) case usb.ENDPOINT_TYPE_BULK | usb.EndpointOut: nrf.USBD.INTENSET.Set(nrf.USBD_INTENSET_ENDEPOUT0 << ep) nrf.USBD.SIZE.EPOUT[ep].Set(0) enableEPOut(ep) + setEPDataPID(ep, false) case usb.ENDPOINT_TYPE_INTERRUPT | usb.EndpointOut: nrf.USBD.INTENSET.Set(nrf.USBD_INTENSET_ENDEPOUT0 << ep) nrf.USBD.SIZE.EPOUT[ep].Set(0) enableEPOut(ep) + setEPDataPID(ep, false) case usb.ENDPOINT_TYPE_BULK | usb.EndpointIn: enableEPIn(ep) + setEPDataPID(ep|usb.EndpointIn, false) case usb.ENDPOINT_TYPE_CONTROL: enableEPIn(0) @@ -259,7 +285,7 @@ func SendUSBInPacket(ep uint32, data []byte) bool { sendUSBPacket(ep, data, 0) // clear transfer complete flag - nrf.USBD.INTENCLR.Set(nrf.USBD_INTENCLR_ENDEPOUT0 << 4) + nrf.USBD.INTENCLR.Set(nrf.USBD_INTENCLR_ENDEPOUT0 << ep) return true } @@ -304,8 +330,26 @@ func handleEndpointRx(ep uint32) []byte { } // AckUsbOutTransfer is called to acknowledge the completion of a USB OUT transfer. +// It also clears the NAK state and resumes data flow if it was paused. func AckUsbOutTransfer(ep uint32) { - // set ready for next data + epOutFlowControl[ep].nak = false + + // If we ignored a packet earlier (Buffer Full strategy), we must manually + // trigger the DMA now to pull it from the HW buffer. + if epOutFlowControl[ep].dataPending { + epOutFlowControl[ep].dataPending = false + + // Prepare DMA to move data from HW Buffer -> RAM + nrf.USBD.EPOUT[ep].PTR.Set(uint32(uintptr(unsafe.Pointer(&udd_ep_out_cache_buffer[ep])))) + count := nrf.USBD.SIZE.EPOUT[ep].Get() + nrf.USBD.EPOUT[ep].MAXCNT.Set(count) + + // Kick the DMA + nrf.USBD.TASKS_STARTEPOUT[ep].Set(1) + return + } + + // Otherwise, just re-arm the endpoint to accept the NEXT packet nrf.USBD.SIZE.EPOUT[ep].Set(0) } @@ -379,3 +423,69 @@ func ReceiveUSBControlPacket() ([cdcLineInfoSize]byte, error) { return b, nil } + +// Set the USB endpoint Packet ID to DATA0 or DATA1. +// In endpoints must have bit 7 (0x80) set. +func setEPDataPID(ep uint32, dataOne bool) { + val := ep + if dataOne { + val |= nrf.USBD_DTOGGLE_VALUE_Data1 << nrf.USBD_DTOGGLE_VALUE_Pos + } else { + val |= nrf.USBD_DTOGGLE_VALUE_Data0 << nrf.USBD_DTOGGLE_VALUE_Pos + } + nrf.USBD.DTOGGLE.Set(val) +} + +// Set ENDPOINT_HALT/stall status on a USB IN endpoint. +func (dev *USBDevice) SetStallEPIn(ep uint32) { + if ep&0x7F == 0 { + nrf.USBD.TASKS_EP0STALL.Set(1) + } else if ep&0x7F < NumberOfUSBEndpoints { + // Stall In Endpoint + val := 0x100 | 0x80 | ep + nrf.USBD.EPSTALL.Set(val) + } +} + +// Set ENDPOINT_HALT/stall status on a USB OUT endpoint. +func (dev *USBDevice) SetStallEPOut(ep uint32) { + if ep == 0 { + nrf.USBD.TASKS_EP0STALL.Set(1) + } else if ep < NumberOfUSBEndpoints { + // Stall Out Endpoint + val := 0x100 | 0x00 | ep + nrf.USBD.EPSTALL.Set(val) + } +} + +// Clear the ENDPOINT_HALT/stall on a USB IN endpoint. +func (dev *USBDevice) ClearStallEPIn(ep uint32) { + if ep&0x7F == 0 { + nrf.USBD.TASKS_EP0STALL.Set(0) + } else if ep&0x7F < NumberOfUSBEndpoints { + // Reset the endpoint data PID to DATA0 + ep |= 0x80 // Set endpoint direction bit + setEPDataPID(ep, false) + + // No-stall In Endpoint + val := 0x000 | 0x80 | ep + nrf.USBD.EPSTALL.Set(val) + } +} + +// Clear the ENDPOINT_HALT/stall on a USB OUT endpoint. +func (dev *USBDevice) ClearStallEPOut(ep uint32) { + if ep == 0 { + nrf.USBD.TASKS_EP0STALL.Set(0) + } else if ep < NumberOfUSBEndpoints { + // Reset the endpoint data PID to DATA0 + setEPDataPID(ep, false) + + // No-stall Out Endpoint + val := 0x000 | 0x00 | ep + nrf.USBD.EPSTALL.Set(val) + + // Write a value to the SIZE register to allow nRF to ACK/accept data + nrf.USBD.SIZE.EPOUT[ep].Set(0) + } +} diff --git a/src/machine/usb/msc/msc.go b/src/machine/usb/msc/msc.go index d3bf8d6e29..cff39e0e7e 100644 --- a/src/machine/usb/msc/msc.go +++ b/src/machine/usb/msc/msc.go @@ -1,6 +1,7 @@ package msc import ( + "encoding/binary" "machine" "machine/usb" "machine/usb/descriptor" @@ -72,9 +73,7 @@ func newMSC(dev machine.BlockDevice) *msc { maxPacketSize := descriptor.EndpointMSCIN.GetMaxPacketSize() m := &msc{ // Some platforms require reads/writes to be aligned to the full underlying hardware block - blockCache: make([]byte, dev.WriteBlockSize()), blockSizeUSB: 512, - buf: make([]byte, dev.WriteBlockSize()), cswBuf: make([]byte, csw.MsgLen), cbw: &CBW{Data: make([]byte, 31)}, maxPacketSize: uint32(maxPacketSize), @@ -165,6 +164,7 @@ func (m *msc) sendCSW(status csw.Status) { } m.cbw.CSW(status, residue, m.cswBuf) m.state = mscStateStatusSent + m.queuedBytes = csw.MsgLen m.sendUSBPacket(m.cswBuf) } @@ -297,3 +297,48 @@ func (m *msc) run(b []byte, isEpOut bool) bool { return ack } + +// RegisterBlockDevice registers a BlockDevice provider with the MSC driver +func (m *msc) RegisterBlockDevice(dev machine.BlockDevice) { + m.dev = dev + + bufSize := max(dev.WriteBlockSize(), int64(m.maxPacketSize)) + + if cap(m.blockCache) != int(bufSize) { + m.blockCache = make([]byte, bufSize) + m.buf = make([]byte, bufSize) + } + + m.blockSizeRaw = uint32(m.dev.WriteBlockSize()) + m.blockCount = uint32(m.dev.Size()) / m.blockSizeUSB + // Read/write/erase operations must be aligned to the underlying hardware blocks. In order to align + // them we assume the provided block device is aligned to the end of the underlying hardware block + // device and offset all reads/writes by the remaining bytes that don't make up a full block. + m.blockOffset = uint32(m.dev.Size()) % m.blockSizeUSB + + // Set VPD UNMAP fields + for i := range vpdPages { + if vpdPages[i].PageCode == 0xb0 { + // 0xb0 - 5.4.5 Block Limits VPD page (B0h) + if len(vpdPages[i].Data) >= 28 { + // Set the OPTIMAL UNMAP GRANULARITY (write blocks per erase block) + granularity := uint32(dev.EraseBlockSize()) / m.blockSizeUSB + binary.BigEndian.PutUint32(vpdPages[i].Data[24:28], granularity) + } + if len(vpdPages[i].Data) >= 32 { + // Set the UNMAP GRANULARITY ALIGNMENT (first sector of first full erase block) + // The unmap granularity alignment is used to calculate an optimal unmap request starting LBA as follows: + // optimal unmap request starting LBA = (n * OPTIMAL UNMAP GRANULARITY) + UNMAP GRANULARITY ALIGNMENT + // where n is zero or any positive integer value + // https://www.seagate.com/files/staticfiles/support/docs/manual/Interface%20manuals/100293068j.pdf + + // We assume the block device is aligned to the end of the underlying block device + blockOffset := uint32(dev.EraseBlockSize()) % m.blockSizeUSB + // Set the UGAVALID bit to indicate that the UNMAP GRANULARITY ALIGNMENT is valid + blockOffset |= 0x80000000 + binary.BigEndian.PutUint32(vpdPages[i].Data[28:32], blockOffset) + } + break + } + } +} diff --git a/src/machine/usb/msc/disk.go b/src/machine/usb/msc/recorder.go similarity index 59% rename from src/machine/usb/msc/disk.go rename to src/machine/usb/msc/recorder.go index 6624d38c01..b1fddccb3e 100644 --- a/src/machine/usb/msc/disk.go +++ b/src/machine/usb/msc/recorder.go @@ -1,7 +1,6 @@ package msc import ( - "encoding/binary" "errors" "fmt" "machine" @@ -12,50 +11,6 @@ var ( errWriteOutOfBounds = errors.New("WriteAt offset out of bounds") ) -// RegisterBlockDevice registers a BlockDevice provider with the MSC driver -func (m *msc) RegisterBlockDevice(dev machine.BlockDevice) { - m.dev = dev - - if cap(m.blockCache) != int(dev.WriteBlockSize()) { - m.blockCache = make([]byte, dev.WriteBlockSize()) - m.buf = make([]byte, dev.WriteBlockSize()) - } - - m.blockSizeRaw = uint32(m.dev.WriteBlockSize()) - m.blockCount = uint32(m.dev.Size()) / m.blockSizeUSB - // Read/write/erase operations must be aligned to the underlying hardware blocks. In order to align - // them we assume the provided block device is aligned to the end of the underlying hardware block - // device and offset all reads/writes by the remaining bytes that don't make up a full block. - m.blockOffset = uint32(m.dev.Size()) % m.blockSizeUSB - // FIXME: Figure out what to do if the emulated write block size is larger than the erase block size - - // Set VPD UNMAP fields - for i := range vpdPages { - if vpdPages[i].PageCode == 0xb0 { - // 0xb0 - 5.4.5 Block Limits VPD page (B0h) - if len(vpdPages[i].Data) >= 28 { - // Set the OPTIMAL UNMAP GRANULARITY (write blocks per erase block) - granularity := uint32(dev.EraseBlockSize()) / m.blockSizeUSB - binary.BigEndian.PutUint32(vpdPages[i].Data[24:28], granularity) - } - if len(vpdPages[i].Data) >= 32 { - // Set the UNMAP GRANULARITY ALIGNMENT (first sector of first full erase block) - // The unmap granularity alignment is used to calculate an optimal unmap request starting LBA as follows: - // optimal unmap request starting LBA = (n * OPTIMAL UNMAP GRANULARITY) + UNMAP GRANULARITY ALIGNMENT - // where n is zero or any positive integer value - // https://www.seagate.com/files/staticfiles/support/docs/manual/Interface%20manuals/100293068j.pdf - - // We assume the block device is aligned to the end of the underlying block device - blockOffset := uint32(dev.EraseBlockSize()) % m.blockSizeUSB - // Set the UGAVALID bit to indicate that the UNMAP GRANULARITY ALIGNMENT is valid - blockOffset |= 0x80000000 - binary.BigEndian.PutUint32(vpdPages[i].Data[28:32], blockOffset) - } - break - } - } -} - var _ machine.BlockDevice = (*RecorderDisk)(nil) // RecorderDisk is a block device that records actions taken on it diff --git a/src/machine/usb/msc/scsi.go b/src/machine/usb/msc/scsi.go index d7266ed40f..1b5fc63d83 100644 --- a/src/machine/usb/msc/scsi.go +++ b/src/machine/usb/msc/scsi.go @@ -245,6 +245,7 @@ func (m *msc) scsiCmdUnmap(cmd scsi.Cmd) { func (m *msc) scsiQueueTask(cmdType scsi.CmdType, b []byte) bool { // Check if the incoming data is larger than our buffer if int(m.queuedBytes)+len(b) > cap(m.buf) { + println("scsiQueueTask: OVERFLOW! queued:", m.queuedBytes, "len(b):", len(b), "cap:", cap(m.buf)) m.sendScsiError(csw.StatusFailed, scsi.SenseIllegalRequest, scsi.SenseCodeInvalidFieldInCDB) return true } @@ -266,7 +267,8 @@ func (m *msc) scsiQueueTask(cmdType scsi.CmdType, b []byte) bool { switch cmdType { case scsi.CmdWrite: // If we're writing data wait until we have a full write block of data that can be processed. - if m.queuedBytes == uint32(cap(m.blockCache)) { + // Or if we've received all the data we're expecting. + if m.queuedBytes == uint32(cap(m.blockCache)) || m.sentBytes+m.queuedBytes >= m.totalBytes { m.taskQueued = true } case scsi.CmdUnmap: diff --git a/src/machine/usb/msc/scsi_readwrite.go b/src/machine/usb/msc/scsi_readwrite.go index 1b09e13418..2d5ed4aa2d 100644 --- a/src/machine/usb/msc/scsi_readwrite.go +++ b/src/machine/usb/msc/scsi_readwrite.go @@ -87,7 +87,7 @@ func (m *msc) writeBlock(b []byte, lba, offset uint32) (n int, err error) { // Convert the emulated block address to the underlying hardware block's start and offset blockStart, blockOffset := m.usbToRawOffset(lba, offset) - if blockOffset != 0 || len(b) != int(m.blockSizeRaw) { + if blockOffset != 0 || len(b)%int(m.blockSizeRaw) != 0 { return 0, invalidWriteError }