From 40e51a3e832dc74d812c42375170db9a71c34b17 Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Mon, 1 Dec 2025 19:26:31 -0500 Subject: [PATCH 1/2] feat: add usb msc support for nrf52840 --- src/machine/machine_nrf52840_usb.go | 116 ++++++++++++++++++- src/machine/usb/msc/msc.go | 49 +++++++- src/machine/usb/msc/{disk.go => recorder.go} | 45 ------- 3 files changed, 160 insertions(+), 50 deletions(-) rename src/machine/usb/msc/{disk.go => recorder.go} (59%) 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 420a06ed98..7a35752cf7 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), @@ -166,6 +165,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) } @@ -298,3 +298,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 From 69c322249117152d59bcc6000a61c86d071c70a4 Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Sat, 20 Dec 2025 21:51:45 -0500 Subject: [PATCH 2/2] fix: make it work --- src/machine/machine_nrf52840_usb.go | 186 ++++++++++++++++++-------- src/machine/usb/msc/msc.go | 19 +-- src/machine/usb/msc/ramdisk.go | 76 +++++++++++ src/machine/usb/msc/scsi.go | 3 +- src/machine/usb/msc/scsi_readwrite.go | 38 ++++-- src/machine/usb/msc/setup.go | 9 +- 6 files changed, 253 insertions(+), 78 deletions(-) create mode 100644 src/machine/usb/msc/ramdisk.go diff --git a/src/machine/machine_nrf52840_usb.go b/src/machine/machine_nrf52840_usb.go index ba149783ba..de54ad2f96 100644 --- a/src/machine/machine_nrf52840_usb.go +++ b/src/machine/machine_nrf52840_usb.go @@ -3,7 +3,6 @@ package machine import ( - "device/arm" "device/nrf" "machine/usb" "runtime/interrupt" @@ -44,17 +43,18 @@ var ( } ) -// enterCriticalSection is used to protect access to easyDMA - only one thing -// can be done with it at a time -func enterCriticalSection() { - waitForEasyDMA() - easyDMABusy.SetBits(1) -} - -func waitForEasyDMA() { - for easyDMABusy.HasBits(1) { - arm.Asm("wfi") +// tryEnterCriticalSection attempts to claim EasyDMA access. +// It returns true if successful, false if EasyDMA is busy. +// Safe to call from both Thread and ISR context. +func tryEnterCriticalSection() bool { + state := interrupt.Disable() + if easyDMABusy.HasBits(1) { + interrupt.Restore(state) + return false } + easyDMABusy.SetBits(1) + interrupt.Restore(state) + return true } func exitCriticalSection() { @@ -109,17 +109,54 @@ func (dev *USBDevice) Configure(config UARTConfig) { dev.initcomplete = true } +func checkCompletions() { + // ENDEPOUT[n] events - handle completions first to free EasyDMA + for i := 0; i < len(endPoints); i++ { + if nrf.USBD.EVENTS_ENDEPOUT[i].Get() > 0 { + nrf.USBD.EVENTS_ENDEPOUT[i].Set(0) + exitCriticalSection() // Release lock before callback + + buf := handleEndpointRx(uint32(i)) + success := usbRxHandler[i] == nil || usbRxHandler[i](buf) + + if success { + AckUsbOutTransfer(uint32(i)) + } else { + // usbRxHandler returned false, so NAK further OUT packets until we're ready + epOutFlowControl[i].nak = true + // Do not re-arm the endpoint (do not write SIZE.EPOUT). + // This causes the hardware to NAK subsequent packets immediately. + // We will re-arm in AckUsbOutTransfer when the application is ready. + } + } + } + + // ENDEPIN[n] events + for i := 0; i < len(endPoints); i++ { + if nrf.USBD.EVENTS_ENDEPIN[i].Get() > 0 { + nrf.USBD.EVENTS_ENDEPIN[i].Set(0) + exitCriticalSection() + } + } +} + func handleUSBIRQ(interrupt.Interrupt) { if nrf.USBD.EVENTS_SOF.Get() == 1 { nrf.USBD.EVENTS_SOF.Set(0) + } - // if you want to blink LED showing traffic, this would be the place... + checkCompletions() + + busy := easyDMABusy.Get() + if busy > 0 { + return } // USBD ready event if nrf.USBD.EVENTS_USBEVENT.Get() == 1 { + cause := nrf.USBD.EVENTCAUSE.Get() nrf.USBD.EVENTS_USBEVENT.Set(0) - if (nrf.USBD.EVENTCAUSE.Get() & nrf.USBD_EVENTCAUSE_READY) > 0 { + if (cause & nrf.USBD_EVENTCAUSE_READY) > 0 { // Configure control endpoint initEndpoint(0, usb.ENDPOINT_TYPE_CONTROL) @@ -190,49 +227,49 @@ func handleUSBIRQ(interrupt.Interrupt) { if nrf.USBD.EVENTS_EPDATA.Get() > 0 { nrf.USBD.EVENTS_EPDATA.Set(0) epDataStatus := nrf.USBD.EPDATASTATUS.Get() - nrf.USBD.EPDATASTATUS.Set(epDataStatus) - var i uint32 - for i = 1; i < uint32(len(endPoints)); i++ { - // Check if endpoint has a pending interrupt - inDataDone := epDataStatus&(nrf.USBD_EPDATASTATUS_EPIN1<<(i-1)) > 0 - outDataDone := epDataStatus&(nrf.USBD_EPDATASTATUS_EPOUT1<<(i-1)) > 0 - if inDataDone { + processedBits := uint32(0) + + // 1. Process IN events (Tx Done) + for i := 1; i < len(endPoints); i++ { + mask := uint32(nrf.USBD_EPDATASTATUS_EPIN1 << (i - 1)) + if epDataStatus&mask > 0 { if usbTxHandler[i] != nil { usbTxHandler[i]() } - } else if outDataDone { - enterCriticalSection() - 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) - 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 - } + processedBits |= mask } } - } - // ENDEPOUT[n] events - for i := 0; i < len(endPoints); i++ { - if nrf.USBD.EVENTS_ENDEPOUT[i].Get() > 0 { - nrf.USBD.EVENTS_ENDEPOUT[i].Set(0) - 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) + // 2. Process OUT events (Rx Ready) + for i := 1; i < len(endPoints); i++ { + mask := uint32(nrf.USBD_EPDATASTATUS_EPOUT1 << (i - 1)) + if epDataStatus&mask > 0 { + nak := epOutFlowControl[i].nak + + // Try to start DMA + if tryEnterCriticalSection() { + 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) + if !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. + epOutFlowControl[i].dataPending = true + exitCriticalSection() + } + processedBits |= mask + } else { + // Lock busy. Skip this endpoint. Bit remains set in EPDATASTATUS. + // Interrupt will re-fire. + } } - exitCriticalSection() } + + // Clear only processed bits + nrf.USBD.EPDATASTATUS.Set(processedBits) } } @@ -313,12 +350,19 @@ func sendUSBPacket(ep uint32, data []byte, maxsize uint16) { count, ) } else { - copy(udd_ep_in_cache_buffer[ep][:], data[:count]) - sendViaEPIn( - ep, - &udd_ep_in_cache_buffer[ep][0], - count, - ) + if count > 0 { + sendViaEPIn( + ep, + &data[0], + count, + ) + } else { + sendViaEPIn( + ep, + nil, + 0, + ) + } } } @@ -337,6 +381,15 @@ func AckUsbOutTransfer(ep uint32) { // 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 { + inInterrupt := interrupt.In() + for !tryEnterCriticalSection() { + if !inInterrupt { + gosched() + } else { + checkCompletions() + } + } + epOutFlowControl[ep].dataPending = false // Prepare DMA to move data from HW Buffer -> RAM @@ -346,23 +399,44 @@ func AckUsbOutTransfer(ep uint32) { // Kick the DMA nrf.USBD.TASKS_STARTEPOUT[ep].Set(1) + + // We must release the critical section here because we are returning early. + exitCriticalSection() return } // Otherwise, just re-arm the endpoint to accept the NEXT packet nrf.USBD.SIZE.EPOUT[ep].Set(0) } - func SendZlp() { + inInterrupt := interrupt.In() + for !tryEnterCriticalSection() { + if !inInterrupt { + gosched() + } else { + checkCompletions() + } + } nrf.USBD.TASKS_EP0STATUS.Set(1) + // EP0STATUS doesn't trigger ENDEPIN/ENDEPOUT, so we clear lock immediately + exitCriticalSection() } func sendViaEPIn(ep uint32, ptr *byte, count int) { + inInterrupt := interrupt.In() + for !tryEnterCriticalSection() { + if !inInterrupt { + gosched() + } else { + checkCompletions() + } + } nrf.USBD.EPIN[ep].PTR.Set( uint32(uintptr(unsafe.Pointer(ptr))), ) nrf.USBD.EPIN[ep].MAXCNT.Set(uint32(count)) nrf.USBD.TASKS_STARTEPIN[ep].Set(1) + exitCriticalSection() } func enableEPOut(ep uint32) { @@ -373,6 +447,7 @@ func enableEPOut(ep uint32) { func enableEPIn(ep uint32) { epinen = epinen | (nrf.USBD_EPINEN_IN0 << ep) nrf.USBD.EPINEN.Set(epinen) + nrf.USBD.INTENSET.Set(nrf.USBD_INTENSET_ENDEPIN0 << ep) } func handleUSBSetAddress(setup usb.Setup) bool { @@ -433,6 +508,7 @@ func setEPDataPID(ep uint32, dataOne bool) { } else { val |= nrf.USBD_DTOGGLE_VALUE_Data0 << nrf.USBD_DTOGGLE_VALUE_Pos } + nrf.USBD.DTOGGLE.Set(ep) nrf.USBD.DTOGGLE.Set(val) } diff --git a/src/machine/usb/msc/msc.go b/src/machine/usb/msc/msc.go index 7a35752cf7..19aa25ab66 100644 --- a/src/machine/usb/msc/msc.go +++ b/src/machine/usb/msc/msc.go @@ -121,19 +121,22 @@ func (m *msc) processTasks() { for { if m.taskQueued { cmd := m.cbw.SCSICmd() + ack := true switch cmd.CmdType() { case scsi.CmdWrite: - m.scsiWrite(cmd, m.buf) + ack = m.scsiWrite(cmd, m.buf) case scsi.CmdUnmap: m.scsiUnmap(m.buf) } - // Acknowledge the received data from the host - m.queuedBytes = 0 m.taskQueued = false - machine.AckUsbOutTransfer(usb.MSC_ENDPOINT_OUT) + if ack { + // Acknowledge the received data from the host + m.queuedBytes = 0 + machine.AckUsbOutTransfer(usb.MSC_ENDPOINT_OUT) + } } - time.Sleep(100 * time.Microsecond) + time.Sleep(1 * time.Millisecond) } } @@ -160,8 +163,8 @@ func (m *msc) sendCSW(status csw.Status) { // Generate CSW packet into m.cswBuf and send it residue := uint32(0) expected := m.cbw.transferLength() - if expected >= m.sentBytes { - residue = expected - m.sentBytes + if expected >= m.sentBytes+m.queuedBytes { + residue = expected - (m.sentBytes + m.queuedBytes) } m.cbw.CSW(status, residue, m.cswBuf) m.state = mscStateStatusSent @@ -250,6 +253,7 @@ func (m *msc) run(b []byte, isEpOut bool) bool { m.queuedBytes = 0 m.sentBytes = 0 m.respStatus = csw.StatusPassed + m.sendZLP = false m.scsiCmdBegin() @@ -292,7 +296,6 @@ func (m *msc) run(b []byte, isEpOut bool) bool { m.sendUSBPacket(m.buf[:0]) } else { m.sendCSW(m.respStatus) - m.state = mscStateCmd } } diff --git a/src/machine/usb/msc/ramdisk.go b/src/machine/usb/msc/ramdisk.go new file mode 100644 index 0000000000..bf9b784883 --- /dev/null +++ b/src/machine/usb/msc/ramdisk.go @@ -0,0 +1,76 @@ +package msc + +import ( + "errors" + "time" +) + +// RamDisk implements machine.BlockDevice in memory. +type RamDisk struct { + Data []byte + BlockSize int64 + WriteDelay time.Duration // FIXME: Cleanup +} + +// NewRamDisk creates a new RamDisk with the given size. +func NewRamDisk(size int64) *RamDisk { + return &RamDisk{ + Data: make([]byte, size), + BlockSize: 512, + } +} + +// ReadAt reads the given number of bytes from the block device. +func (r *RamDisk) ReadAt(p []byte, off int64) (n int, err error) { + if off >= int64(len(r.Data)) { + return 0, errors.New("read beyond end of ramdisk") + } + n = copy(p, r.Data[off:]) + return n, nil +} + +// WriteAt writes the given number of bytes to the block device. +func (r *RamDisk) WriteAt(p []byte, off int64) (n int, err error) { + if off >= int64(len(r.Data)) { + return 0, errors.New("write beyond end of ramdisk") + } + n = copy(r.Data[off:], p) + time.Sleep(r.WriteDelay) // FIXME: Cleanup + if n < len(p) { + return n, errors.New("write beyond end of ramdisk") + } + return n, nil +} + +// Size returns the number of bytes in this block device. +func (r *RamDisk) Size() int64 { + return int64(len(r.Data)) +} + +// WriteBlockSize returns the block size in which data can be written to +// memory. +func (r *RamDisk) WriteBlockSize() int64 { + return r.BlockSize +} + +// EraseBlockSize returns the smallest erasable area on this particular chip +// in bytes. +func (r *RamDisk) EraseBlockSize() int64 { + return r.BlockSize +} + +// EraseBlocks erases the given number of blocks. +func (r *RamDisk) EraseBlocks(start, len int64) error { + // Convert block numbers to byte offsets + startOffset := start * r.EraseBlockSize() + lengthBytes := len * r.EraseBlockSize() + + if startOffset+lengthBytes > int64(cap(r.Data)) { + return errors.New("erase beyond end of ramdisk") + } + + for i := int64(0); i < lengthBytes; i++ { + r.Data[startOffset+i] = 0xFF + } + return nil +} diff --git a/src/machine/usb/msc/scsi.go b/src/machine/usb/msc/scsi.go index 4cec23e2f2..2ec6aba04b 100644 --- a/src/machine/usb/msc/scsi.go +++ b/src/machine/usb/msc/scsi.go @@ -50,6 +50,7 @@ func (m *msc) scsiCmdBegin() { default: // We don't support this command, error out m.sendScsiError(csw.StatusFailed, scsi.SenseIllegalRequest, scsi.SenseCodeInvalidCmdOpCode) + return } if len(m.buf) == 0 { @@ -93,6 +94,7 @@ func (m *msc) scsiDataTransfer(b []byte) bool { // Update our sent bytes count to include the just-confirmed bytes m.sentBytes += m.queuedBytes + m.queuedBytes = 0 if m.sentBytes >= m.transferBytes { // Transfer complete, send CSW after transfer confirmed @@ -287,7 +289,6 @@ func (m *msc) sendScsiError(status csw.Status, key scsi.Sense, code scsi.SenseCo } // Prepare to send CSW - m.sendZLP = true // Ensure the transaction is signaled as ended before a CSW is sent m.respStatus = status m.state = mscStateStatus diff --git a/src/machine/usb/msc/scsi_readwrite.go b/src/machine/usb/msc/scsi_readwrite.go index 8693566196..fadd984dd8 100644 --- a/src/machine/usb/msc/scsi_readwrite.go +++ b/src/machine/usb/msc/scsi_readwrite.go @@ -2,6 +2,8 @@ package msc import ( "errors" + "machine" + "machine/usb" "machine/usb/msc/csw" "machine/usb/msc/scsi" ) @@ -87,18 +89,28 @@ 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) { - return 0, invalidWriteError + if blockOffset == 0 && len(b) == int(m.blockSizeRaw) { + // Fast path: writing a full aligned block + return m.dev.WriteAt(b, blockStart) } - // Write the full block to the underlying device - n, err = m.dev.WriteAt(b, blockStart) - n -= int(blockOffset) - if n > len(b) { - n = len(b) + // Read-modify-write for unaligned/partial blocks + // Read the existing block + _, err = m.dev.ReadAt(m.blockCache, blockStart) + if err != nil { + return 0, err } - return n, err + // Modify the block with new data + copy(m.blockCache[blockOffset:], b) + + // Write the full block back + _, err = m.dev.WriteAt(m.blockCache, blockStart) + if err != nil { + return 0, err + } + + return len(b), nil } func (m *msc) scsiRead(cmd scsi.Cmd) { @@ -121,10 +133,10 @@ func (m *msc) scsiRead(cmd scsi.Cmd) { m.sendUSBPacket(m.buf) } -func (m *msc) scsiWrite(cmd scsi.Cmd, b []byte) { +func (m *msc) scsiWrite(cmd scsi.Cmd, b []byte) bool { if m.readOnly { m.sendScsiError(csw.StatusFailed, scsi.SenseDataProtect, scsi.SenseCodeWriteProtected) - return + return true } // Write data to the block device @@ -137,8 +149,14 @@ func (m *msc) scsiWrite(cmd scsi.Cmd, b []byte) { } if m.sentBytes >= m.transferBytes { + // Acknowledge the received data from the host + m.queuedBytes = 0 + machine.AckUsbOutTransfer(usb.MSC_ENDPOINT_OUT) + // Data transfer is complete, send CSW m.state = mscStateStatus m.run([]byte{}, true) + return false } + return true } diff --git a/src/machine/usb/msc/setup.go b/src/machine/usb/msc/setup.go index 00507aac69..7a8baf9a8a 100644 --- a/src/machine/usb/msc/setup.go +++ b/src/machine/usb/msc/setup.go @@ -74,15 +74,16 @@ func (m *msc) handleClearFeature(setup usb.Setup, wValue uint16) bool { m.clearStallEndpoint(usb.MSC_ENDPOINT_OUT) ok = true } + + if ok { + machine.SendZlp() + } + // Send a CSW if needed to resume after the IN endpoint stall is cleared if m.state == mscStateStatus && wIndex == usb.MSC_ENDPOINT_IN { m.sendCSW(m.respStatus) - ok = true } - if ok { - machine.SendZlp() - } return ok }