usb: add USB mass storage class support

This commit is contained in:
Michael Smith
2025-06-09 11:17:23 -04:00
committed by Ron Evans
parent ba274008d5
commit 87153e9a02
15 changed files with 1714 additions and 3 deletions
+4
View File
@@ -794,6 +794,10 @@ endif
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=feather-nrf52840 examples/usb-midi
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pico examples/usb-storage
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pico2 examples/usb-storage
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=nrf52840-s140v6-uf2-generic examples/machinetest
@$(MD5SUM) test.hex
ifneq ($(STM32), 0)
+15
View File
@@ -0,0 +1,15 @@
package main
import (
"machine"
"machine/usb/msc"
"time"
)
func main() {
msc.Port(machine.Flash)
for {
time.Sleep(2 * time.Second)
}
}
+45
View File
@@ -4,6 +4,17 @@ import (
"internal/binary"
)
/* Endpoint Descriptor
USB 2.0 Specification: 9.6.6 Endpoint
*/
const (
TransferTypeControl uint8 = iota
TransferTypeIsochronous
TransferTypeBulk
TransferTypeInterrupt
)
var endpointEP1IN = [endpointTypeLen]byte{
endpointTypeLen,
TypeEndpoint,
@@ -74,6 +85,36 @@ var EndpointEP5OUT = EndpointType{
data: endpointEP5OUT[:],
}
// Mass Storage Class bulk in endpoint
var endpointMSCIN = [endpointTypeLen]byte{
endpointTypeLen,
TypeEndpoint,
0x86, // EndpointAddress
TransferTypeBulk, // Attributes
0x40, // MaxPacketSizeL (64 bytes)
0x00, // MaxPacketSizeH
0x00, // Interval
}
var EndpointMSCIN = EndpointType{
data: endpointMSCIN[:],
}
// Mass Storage Class bulk out endpoint
var endpointMSCOUT = [endpointTypeLen]byte{
endpointTypeLen,
TypeEndpoint,
0x07, // EndpointAddress
TransferTypeBulk, // Attributes
0x40, // MaxPacketSizeL (64 bytes)
0x00, // MaxPacketSizeH
0x00, // Interval
}
var EndpointMSCOUT = EndpointType{
data: endpointMSCOUT[:],
}
const (
endpointTypeLen = 7
)
@@ -109,3 +150,7 @@ func (d EndpointType) MaxPacketSize(v uint16) {
func (d EndpointType) Interval(v uint8) {
d.data[6] = byte(v)
}
func (d EndpointType) GetMaxPacketSize() uint16 {
return binary.LittleEndian.Uint16(d.data[4:6])
}
+75
View File
@@ -0,0 +1,75 @@
package descriptor
const (
interfaceClassMSC = 0x08
mscSubclassSCSI = 0x06
mscProtocolBOT = 0x50
)
var interfaceAssociationMSC = [interfaceAssociationTypeLen]byte{
interfaceAssociationTypeLen,
TypeInterfaceAssociation,
0x02, // FirstInterface
0x01, // InterfaceCount
interfaceClassMSC, // FunctionClass
mscSubclassSCSI, // FunctionSubClass
mscProtocolBOT, // FunctionProtocol
0x00, // Function
}
var InterfaceAssociationMSC = InterfaceAssociationType{
data: interfaceAssociationMSC[:],
}
var interfaceMSC = [interfaceTypeLen]byte{
interfaceTypeLen, // Length
TypeInterface, // DescriptorType
0x02, // InterfaceNumber
0x00, // AlternateSetting
0x02, // NumEndpoints
interfaceClassMSC, // InterfaceClass (Mass Storage)
mscSubclassSCSI, // InterfaceSubClass (SCSI Transparent)
mscProtocolBOT, // InterfaceProtocol (Bulk-Only Transport)
0x00, // Interface
}
var InterfaceMSC = InterfaceType{
data: interfaceMSC[:],
}
var configurationMSC = [configurationTypeLen]byte{
configurationTypeLen,
TypeConfiguration,
0x6a, 0x00, // wTotalLength
0x03, // number of interfaces (bNumInterfaces)
0x01, // configuration value (bConfigurationValue)
0x00, // index to string description (iConfiguration)
0xa0, // attributes (bmAttributes)
0x32, // maxpower (100 mA) (bMaxPower)
}
var ConfigurationMSC = ConfigurationType{
data: configurationMSC[:],
}
// Mass Storage Class
var MSC = Descriptor{
Device: DeviceCDC.Bytes(),
Configuration: Append([][]byte{
ConfigurationMSC.Bytes(),
InterfaceAssociationCDC.Bytes(),
InterfaceCDCControl.Bytes(),
ClassSpecificCDCHeader.Bytes(),
ClassSpecificCDCACM.Bytes(),
ClassSpecificCDCUnion.Bytes(),
ClassSpecificCDCCallManagement.Bytes(),
EndpointEP1IN.Bytes(),
InterfaceCDCData.Bytes(),
EndpointEP2OUT.Bytes(),
EndpointEP3IN.Bytes(),
InterfaceAssociationMSC.Bytes(),
InterfaceMSC.Bytes(),
EndpointMSCIN.Bytes(),
EndpointMSCOUT.Bytes(),
}),
}
+62
View File
@@ -0,0 +1,62 @@
package msc
import (
"encoding/binary"
"machine/usb/msc/csw"
"machine/usb/msc/scsi"
)
const (
cbwMsgLen = 31 // Command Block Wrapper (CBW) message length
Signature = 0x43425355 // "USBC" in little endian
)
type CBW struct {
HasCmd bool
Data []byte
}
func (c *CBW) Tag() uint32 {
return binary.LittleEndian.Uint32(c.Data[4:8])
}
func (c *CBW) length() int {
return len(c.Data)
}
func (c *CBW) validLength() bool {
return len(c.Data) == cbwMsgLen
}
func (c *CBW) validSignature() bool {
return binary.LittleEndian.Uint32(c.Data[:4]) == Signature
}
func (c *CBW) SCSICmd() scsi.Cmd {
return scsi.Cmd{Data: c.Data[15:]}
}
func (c *CBW) transferLength() uint32 {
return binary.LittleEndian.Uint32(c.Data[8:12])
}
// isIn returns true if the command direction is from the device to the host.
func (c *CBW) isIn() bool {
return c.Data[12]>>7 != 0
}
// isOut returns true if the command direction is from the host to the device.
func (c *CBW) isOut() bool {
return !c.isIn()
}
func (c *CBW) CSW(status csw.Status, residue uint32, b []byte) {
// Signature: "USBS" 53425355h (little endian)
binary.LittleEndian.PutUint32(b[:4], csw.Signature)
// Tag: (same as CBW)
copy(b[4:8], c.Data[4:8])
// Data Residue: (untransferred bytes)
binary.LittleEndian.PutUint32(b[8:12], residue)
// Status:
b[12] = byte(status)
}
+14
View File
@@ -0,0 +1,14 @@
package csw
type Status uint8
const (
StatusPassed Status = iota
StatusFailed
StatusPhaseError
)
const (
MsgLen = 13
Signature = 0x53425355 // "USBS" in little endian
)
+180
View File
@@ -0,0 +1,180 @@
package msc
import (
"encoding/binary"
"errors"
"fmt"
"machine"
"time"
)
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
type RecorderDisk struct {
dev machine.BlockDevice
log []RecorderRecord
last time.Time
time time.Time
}
type RecorderRecord struct {
OpCode RecorderOpCode
Offset int64
Length int
Data []byte
Time int64
valid bool
}
type RecorderOpCode uint8
const (
RecorderOpCodeRead RecorderOpCode = iota
RecorderOpCodeWrite
RecorderOpCodeEraseBlocks
)
// NewRecorderDisk creates a new RecorderDisk instance
func NewRecorderDisk(dev machine.BlockDevice, count int) *RecorderDisk {
d := &RecorderDisk{
dev: dev,
log: make([]RecorderRecord, 0, count),
last: time.Now(),
}
for i := 0; i < count; i++ {
d.log = append(d.log, RecorderRecord{
OpCode: RecorderOpCodeRead,
Offset: 0,
Length: 0,
Data: make([]byte, dev.WriteBlockSize()),
Time: 0,
})
}
return d
}
func (d *RecorderDisk) Size() int64 {
return d.dev.Size()
}
func (d *RecorderDisk) WriteBlockSize() int64 {
return d.dev.WriteBlockSize()
}
func (d *RecorderDisk) EraseBlockSize() int64 {
return d.dev.EraseBlockSize()
}
func (d *RecorderDisk) EraseBlocks(startBlock, numBlocks int64) error {
d.Record(RecorderOpCodeEraseBlocks, startBlock, int(numBlocks), []byte{})
return d.dev.EraseBlocks(startBlock, numBlocks)
}
func (d *RecorderDisk) ReadAt(buffer []byte, offset int64) (int, error) {
n, err := d.dev.ReadAt(buffer, offset)
d.Record(RecorderOpCodeRead, offset, n, buffer)
return n, err
}
func (d *RecorderDisk) WriteAt(buffer []byte, offset int64) (int, error) {
n, err := d.dev.WriteAt(buffer, offset)
d.Record(RecorderOpCodeWrite, offset, n, buffer)
return n, err
}
func (d *RecorderDisk) Record(opCode RecorderOpCode, offset int64, length int, data []byte) {
n := len(d.log) - 1
// Shift the log entries up to make room for a new entry
for i := 0; i < n; i++ {
d.log[i].OpCode = d.log[i+1].OpCode
d.log[i].Offset = d.log[i+1].Offset
d.log[i].Length = d.log[i+1].Length
d.log[i].Data = d.log[i].Data[:len(d.log[i+1].Data)]
copy(d.log[i].Data, d.log[i+1].Data)
d.log[i].Time = d.log[i+1].Time
d.log[i].valid = d.log[i+1].valid
}
// Append the new record
d.log[n].OpCode = opCode
d.log[n].Offset = offset
d.log[n].Length = length
d.log[n].Data = d.log[n].Data[:len(data)]
copy(d.log[n].Data, data)
d.log[n].Time = time.Since(d.time).Microseconds()
d.time = d.time.Add(time.Since(d.time))
d.log[n].valid = true
}
func (d *RecorderDisk) ClearLog() {
for i := range d.log {
d.log[i].valid = false
}
d.time = time.Now()
}
func (d *RecorderDisk) GetLog() []RecorderRecord {
return d.log
}
func (r *RecorderRecord) String() (string, bool) {
opCode := "Unknown"
switch r.OpCode {
case RecorderOpCodeRead:
opCode = "Read"
case RecorderOpCodeWrite:
opCode = "Write"
case RecorderOpCodeEraseBlocks:
opCode = "EraseBlocks"
}
return fmt.Sprintf("%s: %05d+%02d t:%d | % 0x", opCode, r.Offset, r.Length, r.Time, r.Data), r.valid
}
+299
View File
@@ -0,0 +1,299 @@
package msc
import (
"machine"
"machine/usb"
"machine/usb/descriptor"
"machine/usb/msc/csw"
"machine/usb/msc/scsi"
"time"
)
type mscState uint8
const (
mscStateCmd mscState = iota
mscStateData
mscStateStatus
mscStateStatusSent
mscStateNeedReset
)
const (
mscInterface = 2
)
var MSC *msc
type msc struct {
buf []byte // Buffer for incoming/outgoing data
blockCache []byte // Buffer for block read/write data
taskQueued bool // Flag to indicate if the buffer has a task queued
rxStalled bool // Flag to indicate if the RX endpoint is stalled
txStalled bool // Flag to indicate if the TX endpoint is stalled
maxPacketSize uint32 // Maximum packet size for the IN endpoint
respStatus csw.Status // Response status for the last command
sendZLP bool // Flag to indicate if a zero-length packet should be sent before sending CSW
cbw *CBW // Last received Command Block Wrapper
queuedBytes uint32 // Number of bytes queued for sending
sentBytes uint32 // Number of bytes sent
totalBytes uint32 // Total bytes to send
cswBuf []byte // CSW response buffer
state mscState
maxLUN uint8 // Maximum Logical Unit Number (n-1 for n LUNs)
dev machine.BlockDevice
blockCount uint32 // Number of blocks in the device
blockOffset uint32 // Byte offset of the first block in the device for aligned writes
blockSizeUSB uint32 // Write block size as presented to the host over USB
blockSizeRaw uint32 // Write block size of the underlying device hardware
readOnly bool
vendorID [8]byte // Max 8 ASCII characters
productID [16]byte // Max 16 ASCII characters
productRev [4]byte // Max 4 ASCII characters
senseKey scsi.Sense
addlSenseCode scsi.SenseCode
addlSenseQualifier uint8
}
// Port returns the USB Mass Storage port
func Port(dev machine.BlockDevice) *msc {
if MSC == nil {
MSC = newMSC(dev)
}
return MSC
}
func newMSC(dev machine.BlockDevice) *msc {
// Size our buffer to match the maximum packet size of the IN endpoint
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),
}
m.RegisterBlockDevice(dev)
// Set default inquiry data fields
m.SetVendorID("TinyGo")
m.SetProductID("Mass Storage")
m.SetProductRev("1.0")
// Initialize the USB Mass Storage Class (MSC) port
machine.ConfigureUSBEndpoint(descriptor.MSC,
[]usb.EndpointConfig{
{
Index: usb.MSC_ENDPOINT_IN,
IsIn: true,
Type: usb.ENDPOINT_TYPE_BULK,
TxHandler: txHandler,
StallHandler: setupPacketHandler,
},
{
Index: usb.MSC_ENDPOINT_OUT,
IsIn: false,
Type: usb.ENDPOINT_TYPE_BULK,
DelayRxHandler: rxHandler,
StallHandler: setupPacketHandler,
},
},
[]usb.SetupConfig{
{
Index: mscInterface,
Handler: setupPacketHandler,
},
},
)
go m.processTasks()
return m
}
func (m *msc) processTasks() {
// Process tasks that cannot be done in an interrupt context
for {
if m.taskQueued {
cmd := m.cbw.SCSICmd()
switch cmd.CmdType() {
case scsi.CmdWrite:
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)
}
time.Sleep(100 * time.Microsecond)
}
}
func (m *msc) ready() bool {
return m.dev != nil
}
func (m *msc) resetBuffer(length int) {
// Reset the buffer to the specified length
m.buf = m.buf[:length]
for i := 0; i < length; i++ {
m.buf[i] = 0
}
}
func (m *msc) sendUSBPacket(b []byte) {
if machine.USBDev.InitEndpointComplete {
// Send the USB packet
machine.SendUSBInPacket(usb.MSC_ENDPOINT_IN, b)
}
}
func (m *msc) sendCSW(status csw.Status) {
// Generate CSW packet into m.cswBuf and send it
residue := uint32(0)
if m.totalBytes >= m.sentBytes {
residue = m.totalBytes - m.sentBytes
}
m.cbw.CSW(status, residue, m.cswBuf)
m.state = mscStateStatusSent
m.sendUSBPacket(m.cswBuf)
}
func txHandler() {
if MSC != nil {
MSC.txHandler()
}
}
func (m *msc) txHandler() {
m.run([]byte{}, false)
}
func rxHandler(b []byte) bool {
ack := true
if MSC != nil {
ack = MSC.run(b, true)
}
return ack
}
/*
Connection Happy Path Overview:
0. MSC starts out in mscStateCmd status.
1. Host sends CBW (Command Block Wrapper) packet to MSC.
- CBW contains the SCSI command to be executed, the length of the data to be transferred, etc.
2. MSC receives CBW.
- CBW is validated and saved.
- State is changed to mscStateData.
- MSC routes the command to the appropriate SCSI command handler.
3. The MSC SCSI command handler responds with the initial data packet (if applicable).
- If no data packet is needed, state is changed to mscStateStatus and step 4 is skipped.
4. The host acks the data packet and MSC calls m.scsiDataTransfer() to continue sending (or
receiving) data.
- This cycle continues until all data requested in the CBW is sent/received.
- State is changed to mscStateStatus.
- MSC waits for the host to ACK the final data packet.
5. MSC then sends a CSW (Command Status Wrapper) to the host to report the final status of the
command execution and moves to mscStateStatusSent.
6. The host ACKs the CSW and the MSC moves back to mscStateCmd, waiting for the next CBW.
*/
func (m *msc) run(b []byte, isEpOut bool) bool {
ack := true
switch m.state {
case mscStateCmd:
// Receiving a new command block wrapper (CBW)
// IN endpoint transfer complete confirmation, no action needed
if !isEpOut {
return ack
}
// Create a temporary CBW wrapper to validate the incoming data. Has to be temporary
// to avoid it escaping into the heap since we're in interrupt context
cbw := CBW{Data: b}
// Verify size and signature
if !cbw.validLength() || !cbw.validSignature() {
// 6.6.1 CBW Not Valid
// https://usb.org/sites/default/files/usbmassbulk_10.pdf
m.state = mscStateNeedReset
m.stallEndpoint(usb.MSC_ENDPOINT_IN)
m.stallEndpoint(usb.MSC_ENDPOINT_OUT)
m.stallEndpoint(usb.CONTROL_ENDPOINT)
return ack
}
// Save the validated CBW for later reference
copy(m.cbw.Data, b)
// Move on to the data transfer phase next go around (after sending the first message)
m.state = mscStateData
m.totalBytes = cbw.transferLength()
m.queuedBytes = 0
m.sentBytes = 0
m.respStatus = csw.StatusPassed
m.scsiCmdBegin()
case mscStateData:
// Transfer data
ack = m.scsiDataTransfer(b)
case mscStateStatus:
// Sending CSW status response
// Placed after the switch statement so we can send the CSW without having to send a packet
// to cycle back through this block, e.g. with TEST UNIT READY which sends only a CSW after
// setting the sense key/add'l code/qualifier internally
case mscStateStatusSent:
// Wait for the status phase to complete
if !isEpOut && m.queuedBytes == csw.MsgLen {
// Status confirmed sent, wait for next CBW
m.state = mscStateCmd
} else {
// We're not expecting any data here, ignore it. Original log line:
// TU_LOG1(" Warning expect SCSI Status but received unknown data\r\n");
}
case mscStateNeedReset:
// Received an invalid CBW message, stop everything until we get reset
}
// Send CSW status response
// Placed after the switch statement so we can send the CSW without having to send a packet
// to cycle back through this block, e.g. with TEST UNIT READY which sends only a CSW after
// setting the sense key/add'l code/qualifier internally
if m.state == mscStateStatus && !m.txStalled {
if m.totalBytes > m.sentBytes && m.cbw.isIn() {
// 6.7.2 The Thirteen Cases - Case 5 (Hi > Di): STALL before status
m.stallEndpoint(usb.MSC_ENDPOINT_IN)
} else if m.sendZLP {
// Send a zero-length packet to force the end of the transfer before we send a CSW
m.queuedBytes = 0
m.sendZLP = false
m.sendUSBPacket(m.buf[:0])
} else {
m.sendCSW(m.respStatus)
m.state = mscStateCmd
}
}
return ack
}
+301
View File
@@ -0,0 +1,301 @@
package msc
import (
"encoding/binary"
"machine/usb"
"machine/usb/msc/csw"
"machine/usb/msc/scsi"
)
func (m *msc) scsiCmdBegin() {
cmd := m.cbw.SCSICmd()
cmdType := cmd.CmdType()
// Handle multi-packet commands
switch cmdType {
case scsi.CmdRead, scsi.CmdWrite:
m.scsiCmdReadWrite(cmd)
return
case scsi.CmdUnmap:
m.scsiCmdUnmap(cmd)
return
}
if m.totalBytes > 0 && m.cbw.isOut() {
// Reject any other multi-packet commands
if m.totalBytes > m.maxPacketSize {
m.sendScsiError(csw.StatusFailed, scsi.SenseIllegalRequest, scsi.SenseCodeInvalidCmdOpCode)
return
} else {
// Original comment from TinyUSB:
// Didn't check for case 9 (Ho > Dn), which requires examining scsi command first
// but it is OK to just receive data then responded with failed status
}
}
switch cmdType {
case scsi.CmdTestUnitReady:
m.scsiTestUnitReady()
case scsi.CmdReadCapacity:
m.scsiCmdReadCapacity(cmd)
case scsi.CmdReadFormatCapacity:
m.scsiCmdReadFormatCapacity(cmd)
case scsi.CmdInquiry:
m.scsiCmdInquiry(cmd)
case scsi.CmdModeSense6, scsi.CmdModeSense10:
m.scsiCmdModeSense(cmd)
case scsi.CmdRequestSense:
m.scsiCmdRequestSense()
case scsi.CmdPreventAllowMediumRemoval:
m.scsiCmdPreventAllowMediumRemoval(cmd)
default:
// We don't support this command, error out
m.sendScsiError(csw.StatusFailed, scsi.SenseIllegalRequest, scsi.SenseCodeInvalidCmdOpCode)
}
if len(m.buf) == 0 {
if m.totalBytes > 0 {
// 6.7.2 The Thirteen Cases - Case 4 (Hi > Dn)
// https://usb.org/sites/default/files/usbmassbulk_10.pdf
m.sendScsiError(csw.StatusFailed, scsi.SenseIllegalRequest, 0)
} else {
// 6.7.1 The Thirteen Cases - Case 1 Hn = Dn: all good
// https://usb.org/sites/default/files/usbmassbulk_10.pdf
m.state = mscStateStatus
}
} else {
if m.totalBytes == 0 {
// 6.7.1 The Thirteen Cases - Case 2 (Hn < Di)
// https://usb.org/sites/default/files/usbmassbulk_10.pdf
m.sendScsiError(csw.StatusFailed, scsi.SenseIllegalRequest, 0)
} else {
// Make sure we don't return more data than the host is expecting
if m.cbw.transferLength() < uint32(len(m.buf)) {
m.buf = m.buf[:m.cbw.transferLength()]
}
m.sendUSBPacket(m.buf)
}
}
}
func (m *msc) scsiDataTransfer(b []byte) bool {
cmd := m.cbw.SCSICmd()
cmdType := cmd.CmdType()
switch cmdType {
case scsi.CmdWrite, scsi.CmdUnmap:
if m.readOnly {
m.sendScsiError(csw.StatusFailed, scsi.SenseDataProtect, scsi.SenseCodeWriteProtected)
return true
}
return m.scsiQueueTask(cmdType, b)
}
// Update our sent bytes count to include the just-confirmed bytes
m.sentBytes += m.queuedBytes
if m.sentBytes >= m.totalBytes {
// Transfer complete, send CSW after transfer confirmed
m.state = mscStateStatus
} else if cmdType == scsi.CmdRead {
m.scsiRead(cmd)
} else {
// Other multi-packet commands are rejected in m.scsiCmdBegin()
}
return true
}
func (m *msc) scsiTestUnitReady() {
m.resetBuffer(0)
m.queuedBytes = 0
// Check if the device is ready
if !m.ready() {
// If not ready set sense data
m.senseKey = scsi.SenseNotReady
m.addlSenseCode = scsi.SenseCodeMediumNotPresent
m.addlSenseQualifier = 0x00
} else {
m.senseKey = 0
m.addlSenseCode = 0
m.addlSenseQualifier = 0
}
}
func (m *msc) scsiCmdReadCapacity(cmd scsi.Cmd) {
m.resetBuffer(scsi.ReadCapacityRespLen)
m.queuedBytes = scsi.ReadCapacityRespLen
// Last LBA address (big endian)
binary.BigEndian.PutUint32(m.buf[:4], m.blockCount-1)
// Block size (big endian)
binary.BigEndian.PutUint32(m.buf[4:8], m.blockSizeUSB)
}
func (m *msc) scsiCmdReadFormatCapacity(cmd scsi.Cmd) {
m.resetBuffer(scsi.ReadFormatCapacityRespLen)
m.queuedBytes = scsi.ReadFormatCapacityRespLen
// bytes 0-2 - reserved
m.buf[3] = 8 // Capacity list length
// Number of blocks (big endian)
binary.BigEndian.PutUint32(m.buf[4:8], m.blockCount)
// Block size (24-bit, big endian)
binary.BigEndian.PutUint32(m.buf[8:12], m.blockSizeUSB)
// Descriptor Type - formatted media
m.buf[8] = 2
}
// MODE SENSE(6) / MODE SENSE(10) - Only used here to indicate that the device is write protected
func (m *msc) scsiCmdModeSense(cmd scsi.Cmd) {
respLen := uint32(scsi.ModeSense6RespLen)
if cmd.CmdType() == scsi.CmdModeSense10 {
respLen = scsi.ModeSense10RespLen
}
m.resetBuffer(int(respLen))
m.queuedBytes = respLen
// The host allows a good amount of leeway in response size
// Reset total bytes to what we'll actually send
if m.totalBytes > respLen {
m.totalBytes = respLen
m.sendZLP = true
}
readOnly := byte(0)
if m.readOnly {
readOnly = 0x80
}
switch cmd.CmdType() {
case scsi.CmdModeSense6:
// byte 0 - Number of bytes after this one
m.buf[0] = byte(respLen) - 1
// byte 1 - Medium type (0x00 for direct access block device)
// Bit 7 indicates write protected
m.buf[2] = readOnly
// byte 3 - Block descriptor length: 0 (not supported)
case scsi.CmdModeSense10:
// bytes 0-1 - Number of bytes after this one
m.buf[1] = byte(respLen) - 2
// byte 2 - Medium type (0x00 for direct access block device)
// Bit 7 indicates write protected
m.buf[3] = readOnly
}
}
// PREVENT/ALLOW MEDIUM REMOVAL - A flash drive doesn't have a removable medium, so this is a no-op
func (m *msc) scsiCmdPreventAllowMediumRemoval(cmd scsi.Cmd) {
m.resetBuffer(0)
m.queuedBytes = 0
// Check if the device is ready
if !m.ready() {
// If not ready set sense data
m.senseKey = scsi.SenseNotReady
m.addlSenseCode = scsi.SenseCodeMediumNotPresent
m.addlSenseQualifier = 0x00
} else {
m.senseKey = 0
m.addlSenseCode = 0
m.addlSenseQualifier = 0
}
m.state = mscStateStatus
}
// REQUEST SENSE - Returns error status codes when an error status is sent
func (m *msc) scsiCmdRequestSense() {
// Set the buffer size to the SCSI sense message size and clear
m.resetBuffer(scsi.RequestSenseRespLen)
m.queuedBytes = scsi.RequestSenseRespLen
m.totalBytes = scsi.RequestSenseRespLen
// 0x70 - current error, 0x71 - deferred error (not used)
m.buf[0] = 0xF0 // 0x70 for current error plus 0x80 for valid flag bit
// byte 1 - reserved
m.buf[2] = uint8(m.senseKey) & 0x0F // Incorrect Length Indicator bit not supported
// bytes 3-6 - Information (not used)
// byte 7 - Additional Sense Length (bytes remaining in the message)
m.buf[7] = scsi.RequestSenseRespLen - 8
// bytes 8-11 - Command Specific Information (not used)
m.buf[12] = byte(m.addlSenseCode) // Additional Sense Code (optional)
m.buf[13] = m.addlSenseQualifier // Additional Sense Code Qualifier (optional)
// bytes 14-17 - reserved
// Clear sense data after copied to buffer
m.senseKey = 0
m.addlSenseCode = 0
m.addlSenseQualifier = 0
}
func (m *msc) scsiCmdUnmap(cmd scsi.Cmd) {
// Unmap sends a header in the CBW and a parameter list in the data stage
// The parameter list has an 8 byte header and 16 bytes per item. If it's less than 24 bytes it's
// not the format we're expecting and we won't be able to decode it. Same for if there isn't a
// 8 byte header plus multiples of 16 bytes after that
paramLen := binary.BigEndian.Uint16(m.cbw.Data[7:9])
if paramLen < 24 || (paramLen-8)%16 != 0 {
m.sendScsiError(csw.StatusFailed, scsi.SenseIllegalRequest, scsi.SenseCodeInvalidFieldInCDB)
return
}
}
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) {
m.sendScsiError(csw.StatusFailed, scsi.SenseIllegalRequest, scsi.SenseCodeInvalidFieldInCDB)
return true
}
// Save the incoming data in our buffer for processing outside of interrupt context.
if m.taskQueued {
// If we already have a full task queue we can't accept this data
m.sendScsiError(csw.StatusFailed, scsi.SenseAbortedCommand, scsi.SenseCodeMsgReject)
return true
}
// Copy the queued task data into our buffer
start := m.queuedBytes
end := start + uint32(len(b))
m.buf = m.buf[:end]
copy(m.buf[start:end], b)
m.queuedBytes += uint32(len(b))
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)) {
m.taskQueued = true
}
case scsi.CmdUnmap:
m.taskQueued = true
}
// Don't acknowledge the incoming data until we can process it.
return !m.taskQueued
}
func (m *msc) sendScsiError(status csw.Status, key scsi.Sense, code scsi.SenseCode) {
// Generate CSW into m.cswBuf
residue := m.totalBytes - m.sentBytes
// 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
// Set the sense data
m.senseKey = key
m.addlSenseCode = code
m.addlSenseQualifier = 0x00 // Not used
if m.totalBytes > 0 && residue > 0 {
if m.cbw.isIn() {
m.stallEndpoint(usb.MSC_ENDPOINT_IN)
} else {
m.stallEndpoint(usb.MSC_ENDPOINT_OUT)
}
}
}
+140
View File
@@ -0,0 +1,140 @@
package scsi
import (
"encoding/binary"
"fmt"
)
type Cmd struct {
Data []byte
}
func (c *Cmd) CmdType() CmdType {
return CmdType(c.Data[0])
}
func (c *Cmd) BlockCount() uint32 {
return uint32(binary.BigEndian.Uint16(c.Data[7:9]))
}
func (c *Cmd) LBA() uint32 {
return binary.BigEndian.Uint32(c.Data[2:6])
}
func (c Cmd) String() string {
cmdType := c.CmdType()
switch cmdType {
case CmdRead:
return fmt.Sprintf("%-28s LBA: % 3d, Block Count: %d", cmdType, c.LBA(), c.BlockCount())
case CmdWrite:
return fmt.Sprintf("%-28s LBA: % 3d, Block Count: %d", cmdType, c.LBA(), c.BlockCount())
default:
return fmt.Sprintf("%-28s % x", cmdType, c.Data)
}
}
type CmdType uint8
const (
CmdTestUnitReady CmdType = 0x00 // TEST UNIT READY is used to determine if a device is ready to transfer data (read/write). The device does not perform a self-test operation
CmdRequestSense CmdType = 0x03 // REQUEST SENSE returns the current sense data (status or error information)
CmdInquiry CmdType = 0x12 // INQUIRY is used to obtain basic information from a target device
CmdModeSelect6 CmdType = 0x15 // MODE SELECT (6) provides a means for the application client to specify medium, logical unit, or peripheral device parameters to the device server
CmdModeSelect10 CmdType = 0x55 // MODE SELECT (10) provides a means for the application client to specify medium, logical unit, or peripheral device parameters to the device server
CmdModeSense6 CmdType = 0x1A // MODE SENSE (6) provides a means for a device server to report parameters to an application client
CmdModeSense10 CmdType = 0x5A // MODE SENSE (10) provides a means for a device server to report parameters to an application client with 64-bit logical block addressing
CmdStartStopUnit CmdType = 0x1B // START STOP UNIT is used to start or stop the medium in a device server
CmdPreventAllowMediumRemoval CmdType = 0x1E // PREVENT ALLOW MEDIUM REMOVAL is used to prevent or allow the removal of storage medium from a device server
CmdReadFormatCapacity CmdType = 0x23 // READ FORMAT CAPACITY allows the Host to request a list of the possible format capacities for an installed writable media
CmdReadCapacity CmdType = 0x25 // READ CAPACITY command is used to obtain data capacity information from a target device
CmdRead CmdType = 0x28 // READ (10) requests that the device server read the specified logical block(s) and transfer them to the data-in buffer
CmdWrite CmdType = 0x2A // WRITE (10) requests that the device server transfer the specified logical block(s) from the data-out buffer and write them
CmdUnmap CmdType = 0x42 // UNMAP command is used to inform the device server that the specified logical block(s) are no longer in use
)
func (c CmdType) String() string {
switch c {
case CmdTestUnitReady:
return "TEST UNIT READY"
case CmdRequestSense:
return "REQUEST SENSE"
case CmdInquiry:
return "INQUIRY"
case CmdModeSelect6:
return "MODE SELECT (6)"
case CmdModeSelect10:
return "MODE SELECT (10)"
case CmdModeSense6:
return "MODE SENSE (6)"
case CmdModeSense10:
return "MODE SENSE (10)"
case CmdStartStopUnit:
return "START STOP UNIT"
case CmdPreventAllowMediumRemoval:
return "PREVENT ALLOW MEDIUM REMOVAL"
case CmdReadFormatCapacity:
return "READ FORMAT CAPACITY"
case CmdReadCapacity:
return "READ CAPACITY"
case CmdRead:
return "READ (10)"
case CmdWrite:
return "WRITE (10)"
case CmdUnmap:
return "UNMAP"
default:
return fmt.Sprintf("Unknown Command (0x%0x)", byte(c))
}
}
type Sense uint8
const (
// 4.5.6 Sense key and sense code definitions
// https://www.t10.org/ftp/t10/document.08/08-309r0.pdf
SenseNone Sense = 0x00 // No specific Sense Key. This indicates no error condition
SenseRecoveredError Sense = 0x01 // The last command completed successfully, but with some recovery action performed
SenseNotReady Sense = 0x02 // The LUN addressed is not ready to be accessed
SenseMediumError Sense = 0x03 // The command terminated with an unrecoverable error condition
SenseHardwareError Sense = 0x04 // The drive detected an unrecoverable hardware failure while performing the command or during a self test
SenseIllegalRequest Sense = 0x05 // An illegal parameter was provided in the command descriptor block or the additional parameters
SenseUnitAttention Sense = 0x06 // The disk drive may have been reset
SenseDataProtect Sense = 0x07 // A read or write command was attempted on a block that is protected from this operation and was not performed
SenseBlankCheck Sense = 0x08 // A write-once device or a sequential-access device encountered blank medium or format-defined end-of-data indication while reading or that a write-once device encountered a non-blank medium while writing
SenseFirmwareError Sense = 0x09 // Vendor specific sense key
SenseAbortedCommand Sense = 0x0B // The disk drive aborted the command
SenseVolumeOverflow Sense = 0x0D // A buffered peripheral device has reached the end of medium partition and data remains in the buffer that has not been written to the medium
SenseMiscompare Sense = 0x0E // The source data did not match the data read from the medium
)
type SenseCode uint8
const (
// SenseNotReady
SenseCodeMediumNotPresent SenseCode = 0x3A // The storage medium is not present in the device (e.g. empty CD-ROM drive or flash card reader)
// SenseIllegalRequest
SenseCodeInvalidCmdOpCode SenseCode = 0x20 // The command operation code is not supported by the device
SenseCodeInvalidFieldInCDB SenseCode = 0x24 // The command descriptor block (CDB) contains an invalid field
// SenseDataProtect
SenseCodeWriteProtected SenseCode = 0x27 // The media is write protected
// SenseAbortedCommand
SenseCodeLUNCommFailure SenseCode = 0x08 // LUN communication failure
SenseCodeAbortedCmd SenseCode = 0x0B // The command was aborted by the device
SenseCodeMsgReject SenseCode = 0x43 // The command was rejected by the device
SenseCodeOverlapCmdAttempted SenseCode = 0x4E // The command was rejected by the device because it was overlapped by another command
// SenseVolumeOverflow
SenseCodeLBAOutOfRange SenseCode = 0x21 // The logical block address (LBA) is beyond the end of the volume
)
const (
InquiryRespLen = 36
ModeSense6RespLen = 4
ModeSense10RespLen = 8
ReadCapacityRespLen = 8
ReadFormatCapacityRespLen = 12
RequestSenseRespLen = 18
)
+164
View File
@@ -0,0 +1,164 @@
package msc
import (
"encoding/binary"
"machine/usb/msc/csw"
"machine/usb/msc/scsi"
)
type vpdPage struct {
PageCode uint8
PageLength uint8
// Page data
// First four bytes are always Device Type, Page Code, and Page Length (2 bytes) and are omitted here
Data []byte
}
// These must be sorted in ascending order by PageCode
var vpdPages = []vpdPage{
{
// 0xb0 - 5.4.5 Block Limits VPD page (B0h)
// https://www.seagate.com/files/staticfiles/support/docs/manual/Interface%20manuals/100293068j.pdf
PageCode: 0xb0,
PageLength: 0x3c, // 60 bytes
Data: []byte{
0x00, 0x00, // WSNZ, MAXIMUM COMPARE AND WRITE LENGTH - Not supported
0x00, 0x00, // OPTIMAL TRANSFER LENGTH GRANULARITY - Not supported
0x00, 0x00, 0x00, 0x00, // MAXIMUM TRANSFER LENGTH - Not supported
0x00, 0x00, 0x00, 0x00, // OPTIMAL TRANSFER LENGTH - Not supported
0x00, 0x00, 0x00, 0x00, // MAXIMUM PREFETCH LENGTH - Not supported
0xFF, 0xFF, 0xFF, 0xFF, // MAXIMUM UNMAP LBA COUNT - Maximum count supported
0x00, 0x00, 0x00, 0x03, // MAXIMUM UNMAP BLOCK DESCRIPTOR COUNT - Max 3 descriptors
0x00, 0x00, 0x00, 0x00, // OPTIMAL UNMAP GRANULARITY
0x00, 0x00, 0x00, 0x00, // UNMAP GRANULARITY ALIGNMENT (bit 7 on byte 28 sets UGAVALID)
// From here on all bytes are zero and can be omitted from the response
// 0x00, 0x00, 0x00, 0x00, // MAXIMUM WRITE SAME LENGTH - Not supported
// 0x00, 0x00, 0x00, 0x00, // (8-bytes)
// 0x00, 0x00, 0x00, 0x00, // MAXIMUM ATOMIC TRANSFER LENGTH - Not supported
// 0x00, 0x00, 0x00, 0x00, // ATOMIC ALIGNMENT - Not supported
// 0x00, 0x00, 0x00, 0x00, // ATOMIC TRANSFER LENGTH GRANULARITY - Not supported
// 0x00, 0x00, 0x00, 0x00, // MAXIMUM ATOMIC TRANSFER LENGTH WITH ATOMIC BOUNDARY - Not supported
// 0x00, 0x00, 0x00, 0x00, // MAXIMUM ATOMIC BOUNDARY SIZE - Not supported
},
},
{
// 0xb1 - 5.4.3 Block Device Characteristics VPD page (B1h)
// https://www.seagate.com/files/staticfiles/support/docs/manual/Interface%20manuals/100293068j.pdf
PageCode: 0xb1,
PageLength: 0x3c, // 60 bytes (bytes 9+ are all reserved/zero)
Data: []byte{
0x00, 0x01, // Rotation rate (0x0001 - non-rotating medium)
0x00, // Product type - 0x00: Not indicated, 0x04: MMC/eMMC, 0x05: SD card
0x00, // WABEREQ/WACEREQ/Form Factor - Not specified
0x00, // ZBC/BOCS/FUAB/VBULS
// Reserved (55 bytes)
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00,
},
},
{
// 0xb2 - 5.4.13 Logical Block Provisioning VPD page (B2h)
// https://www.seagate.com/files/staticfiles/support/docs/manual/Interface%20manuals/100293068j.pdf
PageCode: 0xB2,
PageLength: 0x04,
Data: []byte{
0x00, // Logical Block Provisioning Threshold Exponent
0x80, // 0x80 - LBPU (UNMAP command supported)
0x00, // Minimum percentage/Provisioning type - Not specified
0x00, // Threshold percentage - Not supported
},
},
}
func (m *msc) scsiCmdInquiry(cmd scsi.Cmd) {
evpd := cmd.Data[1] & 0x01
pageCode := cmd.Data[2]
// PAGE CODE (byte 2) can't be set if the EVPD bit is not set
if evpd == 0 {
if pageCode == 0 {
// Standard INQUIRY command
m.scsiStdInquiry(cmd)
} else {
// 3.6.1 INQUIRY command introduction
// https://www.seagate.com/files/staticfiles/support/docs/manual/Interface%20manuals/100293068j.pdf
m.sendScsiError(csw.StatusFailed, scsi.SenseIllegalRequest, scsi.SenseCodeInvalidFieldInCDB)
return
}
} else {
m.scsiEvpdInquiry(cmd, pageCode)
}
}
func (m *msc) scsiEvpdInquiry(cmd scsi.Cmd, pageCode uint8) {
var pageLength int
switch pageCode {
case 0x00:
// 5.4.18 Supported Vital Product Data pages (00h)
// https://www.seagate.com/files/staticfiles/support/docs/manual/Interface%20manuals/100293068j.pdf
pageLength = len(vpdPages) + 1 // Number of pages + 1 for 0x00 (excluded from vpdPages[])
m.resetBuffer(pageLength + 4) // n+4 supported VPD pages
// bytes 4+ - Supported VPD pages in ascending order
for i := 0; i < len(vpdPages); i++ {
m.buf[4+i] = vpdPages[i].PageCode
}
default:
found := false
for i := range vpdPages {
if vpdPages[i].PageCode == pageCode {
// Our advertised page length is "for entertainment use only". Some pages have dozens of
// reserved (zero) bytes at the end that don't actually need to be sent. If we omit them
// from our response they are (correctly) presumed to be zero bytes by the host
pageLength = int(vpdPages[i].PageLength)
// We actually just send the length of the bytes we have plus the same four byte header,
// but declare the length of the response according to the spec as appropriate
m.resetBuffer(len(vpdPages[i].Data) + 4)
copy(m.buf[4:], vpdPages[i].Data)
found = true
break
}
}
if !found {
// VPD page not found, send error
m.sendScsiError(csw.StatusFailed, scsi.SenseIllegalRequest, scsi.SenseCodeInvalidFieldInCDB)
return
}
}
// byte 0 - Peripheral Qualifier/Peripheral Device Type (0x00 for direct access block device)
m.buf[1] = pageCode
binary.BigEndian.PutUint16(m.buf[2:4], uint16(pageLength))
// Set total bytes to the length of our response
m.queuedBytes = uint32(len(m.buf))
m.totalBytes = uint32(len(m.buf))
}
func (m *msc) scsiStdInquiry(cmd scsi.Cmd) {
m.resetBuffer(scsi.InquiryRespLen)
m.queuedBytes = scsi.InquiryRespLen
m.totalBytes = scsi.InquiryRespLen
// byte 0 - Device Type (0x00 for direct access block device)
// byte 1 - Removable media bit
m.buf[1] = 0x80
// byte 2 - Version 0x00 - We claim conformance to no standard
// byte 3 - Response data format
m.buf[3] = 2
// byte 4 - Additional length (number of bytes after this one)
m.buf[4] = scsi.InquiryRespLen - 5
// byte 5 - Not used
// byte 6 - Not used
// byte 7 - Not used
// bytes 8-15 - Vendor ID
copy(m.buf[8:16], m.vendorID[:])
// bytes 16-31 - Product ID
copy(m.buf[16:32], m.productID[:])
// bytes 32-35 - Product revision level
copy(m.buf[32:36], m.productRev[:])
}
+144
View File
@@ -0,0 +1,144 @@
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.totalBytes > 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.totalBytes == 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.totalBytes/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.totalBytes - 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.totalBytes {
// Data transfer is complete, send CSW
m.state = mscStateStatus
m.run([]byte{}, true)
}
}
+91
View File
@@ -0,0 +1,91 @@
package msc
import (
"encoding/binary"
"machine/usb/msc/csw"
"machine/usb/msc/scsi"
)
type Error int
const (
errorLBAOutOfRange Error = iota
)
func (e Error) Error() string {
switch e {
case errorLBAOutOfRange:
return "LBA out of range"
default:
return "unknown error"
}
}
func (m *msc) scsiUnmap(b []byte) {
// Execute Order 66 (0x42) to wipe out the blocks
// 3.54 Unmap Command (SBC-4)
// https://www.seagate.com/files/staticfiles/support/docs/manual/Interface%20manuals/100293068j.pdf
if m.readOnly {
m.sendScsiError(csw.StatusFailed, scsi.SenseDataProtect, scsi.SenseCodeWriteProtected)
return
}
// blockDescLen is the remaining length of block descriptors in the message, offset 8 bytes from
// the start of this packet
var blockDescLen uint16
// Decode the parameter list
msgLen := binary.BigEndian.Uint16(b[:2])
// Length of the block descriptor portion of the message
blockDescLen = binary.BigEndian.Uint16(b[2:4])
// Do some sanity checks on the message lengths (max 3 block descriptors to fit in one 64 byte packet)
if msgLen < 8 || blockDescLen < 16 || msgLen-blockDescLen != 6 || blockDescLen > (3*16) {
m.sendScsiError(csw.StatusFailed, scsi.SenseIllegalRequest, scsi.SenseCodeInvalidFieldInCDB)
return
}
// descEnd marks the end of the last full block descriptor in this packet
descEnd := int(blockDescLen + 8)
// Unmap the blocks we can from this packet
for i := 8; i < descEnd; i += 16 {
err := m.unmapBlocksFromDescriptor(b[i:], uint64(m.blockCount))
if err != nil {
// TODO: Might need a better error code here for device errors?
m.sendScsiError(csw.StatusFailed, scsi.SenseVolumeOverflow, scsi.SenseCodeLBAOutOfRange)
return
}
}
// FIXME: We need to handle erase block alignment
m.sentBytes += uint32(len(b))
if m.sentBytes >= m.totalBytes {
// Order 66 complete, send CSW to establish galactic empire
m.state = mscStateStatus
m.run([]byte{}, true)
}
}
func (m *msc) unmapBlocksFromDescriptor(b []byte, numBlocks uint64) error {
blockCount := binary.BigEndian.Uint32(b[8:12])
if blockCount == 0 {
// No blocks to unmap. Explicitly not an error per the spec
return nil
}
// This is technically a 64-bit LBA, but we can't address that many bytes
// let alone blocks, so we just use the lower 32 bits
lba := binary.BigEndian.Uint32(b[4:8])
// Make sure the unmap command doesn't extend past the end of the volume
if lba+blockCount > m.blockCount {
return errorLBAOutOfRange
}
// Convert the emulated block size to the underlying hardware erase block size
blockStart := int64(lba*m.blockSizeUSB) / m.dev.EraseBlockSize()
rawBlockCount := int64(blockCount*m.blockSizeUSB) / m.dev.EraseBlockSize()
// Unmap the blocks
return m.dev.EraseBlocks(blockStart, rawBlockCount)
}
+172
View File
@@ -0,0 +1,172 @@
package msc
import (
"machine"
"machine/usb"
"machine/usb/msc/csw"
)
func setupPacketHandler(setup usb.Setup) bool {
if MSC != nil {
return MSC.setupPacketHandler(setup)
}
return false
}
func (m *msc) setupPacketHandler(setup usb.Setup) bool {
ok := false
wValue := (uint16(setup.WValueH) << 8) | uint16(setup.WValueL)
switch setup.BRequest {
case usb.CLEAR_FEATURE:
ok = m.handleClearFeature(setup, wValue)
case usb.GET_MAX_LUN:
ok = m.handleGetMaxLun(setup, wValue)
case usb.MSC_RESET:
ok = m.handleReset(setup, wValue)
}
return ok
}
// Handles the CLEAR_FEATURE request for clearing ENDPOINT_HALT/stall
func (m *msc) handleClearFeature(setup usb.Setup, wValue uint16) bool {
ok := false
// wValue is the feature selector (0x00 for ENDPOINT_HALT)
// We aren't handling any other feature selectors
// https://wiki.osdev.org/Universal_Serial_Bus#CLEAR_FEATURE
if wValue != 0 {
return ok
}
// Clearing the stall is not enough, continue stalling until a reset is received first
// 6.6.1 CBW Not Valid
// If the CBW is not valid, the device shall STALL the Bulk-In pipe. Also, the device
// shall either STALL the Bulk-Out pipe, or the device shall accept and discard any
// Bulk-Out data. The device shall maintain this state until a Reset Recovery
// For Reset Recovery the host shall issue in the following order: :
// (a) a Bulk-Only Mass Storage Reset (handleReset())
// (b) a Clear Feature HALT to the Bulk-In endpoint (clear stall IN)
// (c) a Clear Feature HALT to the Bulk-Out endpoint (clear stall OUT)
// https://usb.org/sites/default/files/usbmassbulk_10.pdf
if m.state == mscStateNeedReset {
wIndex := setup.WIndex & 0x7F // Clear the direction bit from the endpoint address for comparison
if wIndex == usb.MSC_ENDPOINT_IN {
m.stallEndpoint(usb.MSC_ENDPOINT_IN)
} else if wIndex == usb.MSC_ENDPOINT_OUT {
m.stallEndpoint(usb.MSC_ENDPOINT_OUT)
}
return ok
}
// Clear the direction bit from the endpoint address for comparison
wIndex := setup.WIndex & 0x7F
// Clear the IN/OUT stalls if addressed to the endpoint, or both if addressed to the interface
if wIndex == usb.MSC_ENDPOINT_IN || wIndex == mscInterface {
m.clearStallEndpoint(usb.MSC_ENDPOINT_IN)
ok = true
}
if wIndex == usb.MSC_ENDPOINT_OUT || wIndex == mscInterface {
m.clearStallEndpoint(usb.MSC_ENDPOINT_OUT)
ok = true
}
// 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(csw.StatusPassed)
ok = true
}
if ok {
machine.SendZlp()
}
return ok
}
// 3.2 Get Max LUN
// https://usb.org/sites/default/files/usbmassbulk_10.pdf
func (m *msc) handleGetMaxLun(setup usb.Setup, wValue uint16) bool {
if setup.WIndex != mscInterface || setup.WLength != 1 || wValue != 0 {
return false
}
// Send the maximum LUN ID number (zero-indexed, so n-1) supported by the device
m.resetBuffer(1) // Shrink buffer to 1 byte
m.buf[0] = m.maxLUN
return machine.SendUSBInPacket(usb.CONTROL_ENDPOINT, m.buf)
}
// 3.1 Bulk-Only Mass Storage Reset
// https://usb.org/sites/default/files/usbmassbulk_10.pdf
func (m *msc) handleReset(setup usb.Setup, wValue uint16) bool {
if setup.WIndex != mscInterface || setup.WLength != 0 || wValue != 0 {
return false
}
// Reset to command waiting state
m.state = mscStateCmd
// Reset transfer state
m.resetBuffer(0)
m.senseKey = 0
m.addlSenseCode = 0
m.addlSenseQualifier = 0
// Send a zero-length packet (ZLP) to indicate the reset is complete
machine.SendZlp()
// Return true to indicate successful reset
return true
}
func (m *msc) stallEndpoint(ep uint8) {
if ep == usb.MSC_ENDPOINT_IN {
m.txStalled = true
machine.USBDev.SetStallEPIn(usb.MSC_ENDPOINT_IN)
} else if ep == usb.MSC_ENDPOINT_OUT {
m.rxStalled = true
machine.USBDev.SetStallEPOut(usb.MSC_ENDPOINT_OUT)
} else if ep == usb.CONTROL_ENDPOINT {
machine.USBDev.SetStallEPIn(usb.CONTROL_ENDPOINT)
}
}
func (m *msc) clearStallEndpoint(ep uint8) {
if ep == usb.MSC_ENDPOINT_IN {
machine.USBDev.ClearStallEPIn(usb.MSC_ENDPOINT_IN)
m.txStalled = false
} else if ep == usb.MSC_ENDPOINT_OUT {
machine.USBDev.ClearStallEPOut(usb.MSC_ENDPOINT_OUT)
m.rxStalled = false
}
}
func (m *msc) setStringField(field []byte, value string) {
copy(field, []byte(value))
for i := len(value); i < len(field); i++ {
field[i] = 0x20 // Fill remaining bytes with spaces
}
}
func (m *msc) SetVendorID(vendorID string) {
m.setStringField(m.vendorID[:], vendorID)
}
func (m *msc) SetProductID(productID string) {
m.setStringField(m.productID[:], productID)
}
func (m *msc) SetProductRev(productRev string) {
m.setStringField(m.productRev[:], productRev)
}
func SetVendorID(vendorID string) {
if MSC != nil {
MSC.SetVendorID(vendorID)
}
}
func SetProductID(productID string) {
if MSC != nil {
MSC.SetProductID(productID)
}
}
func SetProductRev(productRev string) {
if MSC != nil {
MSC.SetProductRev(productRev)
}
}
+8 -3
View File
@@ -28,7 +28,7 @@ const (
EndpointPacketSize = 64 // 64 for Full Speed, EPT size max is 1024
// standard requests
// bRequest - standard requests
GET_STATUS = 0
CLEAR_FEATURE = 1
SET_FEATURE = 3
@@ -40,7 +40,7 @@ const (
GET_INTERFACE = 10
SET_INTERFACE = 11
// non standard requests
// bRequest - HID class-specific requests
GET_REPORT = 1
GET_IDLE = 2
GET_PROTOCOL = 3
@@ -49,6 +49,10 @@ const (
SET_PROTOCOL = 11
SET_REPORT_TYPE = 33
// bRequest - MSC class-specific requests
GET_MAX_LUN = 0xFE
MSC_RESET = 0xFF
DEVICE_CLASS_COMMUNICATIONS = 0x02
DEVICE_CLASS_HUMAN_INTERFACE = 0x03
DEVICE_CLASS_STORAGE = 0x08
@@ -75,7 +79,8 @@ const (
HID_ENDPOINT_OUT = 5 // for Interrupt Out
MIDI_ENDPOINT_IN = 6 // for Bulk In
MIDI_ENDPOINT_OUT = 7 // for Bulk Out
NumberOfEndpoints = 8
MSC_ENDPOINT_IN = 6 // for Bulk In
MSC_ENDPOINT_OUT = 7 // for Bulk Out
// bmRequestType
REQUEST_HOSTTODEVICE = 0x00