mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-08-07 04:23:41 +00:00
145 lines
4.5 KiB
Go
145 lines
4.5 KiB
Go
package msc
|
|
|
|
import (
|
|
"errors"
|
|
"machine/usb/msc/csw"
|
|
"machine/usb/msc/scsi"
|
|
)
|
|
|
|
var invalidWriteError = errors.New("invalid write offset or length")
|
|
|
|
func (m *msc) scsiCmdReadWrite(cmd scsi.Cmd) {
|
|
status := m.validateScsiReadWrite(cmd)
|
|
if status != csw.StatusPassed {
|
|
m.sendScsiError(status, scsi.SenseIllegalRequest, scsi.SenseCodeInvalidCmdOpCode)
|
|
} else if m.transferBytes > 0 {
|
|
if cmd.CmdType() == scsi.CmdRead {
|
|
m.scsiRead(cmd)
|
|
} else {
|
|
// WRITE(10) and UNMAP commands don't take any action until the data stage begins
|
|
}
|
|
} else {
|
|
// Zero byte transfer. No practical use case
|
|
m.state = mscStateStatus
|
|
}
|
|
}
|
|
|
|
// Validate SCSI READ(10) and WRITE(10) commands
|
|
func (m *msc) validateScsiReadWrite(cmd scsi.Cmd) csw.Status {
|
|
blockCount := cmd.BlockCount()
|
|
// CBW wrapper transfer length
|
|
if m.transferBytes == 0 {
|
|
// If the SCSI command's block count doesn't loosely match the wrapper's transfer length something's wrong
|
|
if blockCount > 0 {
|
|
return csw.StatusPhaseError
|
|
}
|
|
// Zero length transfer. No practical use case, but explicitly not an error according to the spec
|
|
return csw.StatusPassed
|
|
}
|
|
if (cmd.CmdType() == scsi.CmdRead && m.cbw.isOut()) || (cmd.CmdType() == scsi.CmdWrite && m.cbw.isIn()) {
|
|
// If the command is READ(10) and the data direction is from host to device that's a problem
|
|
// 6.7.3 The Thirteen Cases - Case 10 (Ho <> Di)
|
|
// If the command is WRITE(10) and the data direction is from device to host that's also a problem
|
|
// 6.7.2 The Thirteen Cases - Case 8 (Hi <> Do)
|
|
// https://usb.org/sites/default/files/usbmassbulk_10.pdf
|
|
return csw.StatusPhaseError
|
|
}
|
|
if blockCount == 0 {
|
|
// We already checked for zero length transfer above, so this is a problem
|
|
// 6.7.2 The Thirteen Cases - Case 4 (Hi > Dn)
|
|
// https://usb.org/sites/default/files/usbmassbulk_10.pdf
|
|
return csw.StatusFailed
|
|
}
|
|
if m.transferBytes/blockCount == 0 {
|
|
// Block size shouldn't be small enough to round to zero
|
|
// 6.7.2 The Thirteen Cases - Case 7 (Hi < Di) READ(10) or
|
|
// 6.7.3 The Thirteen Cases - Case 13 (Ho < Do) WRITE(10)
|
|
// https://usb.org/sites/default/files/usbmassbulk_10.pdf
|
|
return csw.StatusPhaseError
|
|
}
|
|
return csw.StatusPassed
|
|
}
|
|
|
|
func (m *msc) usbToRawOffset(lba, offset uint32) (int64, int64) {
|
|
// Convert the emulated block address to the underlying hardware block's start and offset
|
|
rawLBA := (lba*m.blockSizeUSB + offset) / m.blockSizeRaw
|
|
rawBlockOffset := int64((lba*m.blockSizeUSB + offset) % m.blockSizeRaw)
|
|
return int64(m.blockOffset + rawLBA*m.blockSizeRaw), rawBlockOffset
|
|
}
|
|
|
|
func (m *msc) readBlock(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)
|
|
|
|
// Read a full block from the underlying device into the block cache
|
|
n, err = m.dev.ReadAt(m.blockCache, blockStart)
|
|
n -= int(blockOffset)
|
|
if n > len(b) {
|
|
n = len(b)
|
|
}
|
|
|
|
copy(b, m.blockCache[blockOffset:])
|
|
|
|
return n, err
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
|
|
return n, err
|
|
}
|
|
|
|
func (m *msc) scsiRead(cmd scsi.Cmd) {
|
|
// Make sure we don't exceed the buffer size
|
|
readEnd := m.transferBytes - m.sentBytes
|
|
if readEnd > m.maxPacketSize {
|
|
readEnd = m.maxPacketSize
|
|
}
|
|
// Resize the buffer to fit the read size
|
|
m.resetBuffer(int(readEnd))
|
|
|
|
// Read data from the emulated block device
|
|
n, err := m.readBlock(m.buf[:readEnd], cmd.LBA(), m.sentBytes)
|
|
if err != nil || n == 0 {
|
|
m.sendScsiError(csw.StatusFailed, scsi.SenseNotReady, scsi.SenseCodeMediumNotPresent)
|
|
return
|
|
}
|
|
|
|
m.queuedBytes = readEnd
|
|
m.sendUSBPacket(m.buf)
|
|
}
|
|
|
|
func (m *msc) scsiWrite(cmd scsi.Cmd, b []byte) {
|
|
if m.readOnly {
|
|
m.sendScsiError(csw.StatusFailed, scsi.SenseDataProtect, scsi.SenseCodeWriteProtected)
|
|
return
|
|
}
|
|
|
|
// Write data to the block device
|
|
n, err := m.writeBlock(b, cmd.LBA(), m.sentBytes)
|
|
if err != nil || n < len(b) {
|
|
m.sentBytes += uint32(n)
|
|
m.sendScsiError(csw.StatusFailed, scsi.SenseNotReady, scsi.SenseCodeMediumNotPresent)
|
|
} else {
|
|
m.sentBytes += uint32(len(b))
|
|
}
|
|
|
|
if m.sentBytes >= m.transferBytes {
|
|
// Data transfer is complete, send CSW
|
|
m.state = mscStateStatus
|
|
m.run([]byte{}, true)
|
|
}
|
|
}
|