Skip to content

usb: add USB mass storage class support #4844

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions src/machine/machine_rp2_usb.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,23 +152,25 @@ func sendViaEPIn(ep uint32, data []byte, count int) {

// Set ENDPOINT_HALT/stall status on a USB IN endpoint.
func (dev *USBDevice) SetStallEPIn(ep uint32) {
ep = ep & 0x7F
// Prepare buffer control register value
if ep == 0 {
armEPZeroStall()
}
val := uint32(usbBuf0CtrlFull)
_usbDPSRAM.EPxBufferControl[ep&0x7F].In.Set(val)
_usbDPSRAM.EPxBufferControl[ep].In.Set(val)
val |= uint32(usbBuf0CtrlStall)
_usbDPSRAM.EPxBufferControl[ep&0x7F].In.Set(val)
_usbDPSRAM.EPxBufferControl[ep].In.Set(val)
}

// Set ENDPOINT_HALT/stall status on a USB OUT endpoint.
func (dev *USBDevice) SetStallEPOut(ep uint32) {
ep = ep & 0x7F
if ep == 0 {
panic("SetStallEPOut: EP0 OUT not valid")
}
val := uint32(usbBuf0CtrlStall)
_usbDPSRAM.EPxBufferControl[ep&0x7F].Out.Set(val)
_usbDPSRAM.EPxBufferControl[ep].Out.Set(val)
}

// Clear the ENDPOINT_HALT/stall on a USB IN endpoint.
Expand All @@ -178,7 +180,7 @@ func (dev *USBDevice) ClearStallEPIn(ep uint32) {
_usbDPSRAM.EPxBufferControl[ep].In.ClearBits(val)
if epXPIDReset[ep] {
// Reset the PID to DATA0
setEPDataPID(ep&0x7F, false)
setEPDataPID(ep, false)
}
}

Expand Down
2 changes: 2 additions & 0 deletions src/machine/usb.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,8 @@ var (
usb.HID_ENDPOINT_OUT: (usb.ENDPOINT_TYPE_DISABLE), // Interrupt Out
usb.MIDI_ENDPOINT_IN: (usb.ENDPOINT_TYPE_DISABLE), // Bulk In
usb.MIDI_ENDPOINT_OUT: (usb.ENDPOINT_TYPE_DISABLE), // Bulk Out
usb.MSC_ENDPOINT_IN: (usb.ENDPOINT_TYPE_DISABLE), // Bulk In
usb.MSC_ENDPOINT_OUT: (usb.ENDPOINT_TYPE_DISABLE), // Bulk Out
}
)

Expand Down
45 changes: 45 additions & 0 deletions src/machine/usb/descriptor/endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -74,6 +85,36 @@ var EndpointEP5OUT = EndpointType{
data: endpointEP5OUT[:],
}

// Mass Storage Class bulk in endpoint
var endpointEP8IN = [endpointTypeLen]byte{
endpointTypeLen,
TypeEndpoint,
0x88, // EndpointAddress
TransferTypeBulk, // Attributes
0x40, // MaxPacketSizeL (64 bytes)
0x00, // MaxPacketSizeH
0x00, // Interval
}

var EndpointEP8IN = EndpointType{
data: endpointEP8IN[:],
}

// Mass Storage Class bulk out endpoint
var endpointEP9OUT = [endpointTypeLen]byte{
endpointTypeLen,
TypeEndpoint,
0x09, // EndpointAddress
TransferTypeBulk, // Attributes
0x40, // MaxPacketSizeL (64 bytes)
0x00, // MaxPacketSizeH
0x00, // Interval
}

var EndpointEP9OUT = EndpointType{
data: endpointEP9OUT[:],
}

const (
endpointTypeLen = 7
)
Expand Down Expand Up @@ -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 changes: 75 additions & 0 deletions src/machine/usb/descriptor/msc.go
Original file line number Diff line number Diff line change
@@ -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(),
EndpointEP8IN.Bytes(),
EndpointEP9OUT.Bytes(),
}),
}
57 changes: 57 additions & 0 deletions src/machine/usb/msc/cbw.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
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 {
Data []byte
}

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 changes: 14 additions & 0 deletions src/machine/usb/msc/csw/csw.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package csw

type Status uint8

const (
StatusPassed Status = iota
StatusFailed
StatusPhaseError
)

const (
MsgLen = 13
Signature = 0x53425355 // "USBS" in little endian
)
73 changes: 73 additions & 0 deletions src/machine/usb/msc/disk.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package msc

import (
"encoding/binary"
"machine"
)

var _ machine.BlockDevice = (*DefaultDisk)(nil)

// DefaultDisk is a placeholder disk implementation
type DefaultDisk struct {
}

// NewDefaultDisk creates a new DefaultDisk instance
func NewDefaultDisk() *DefaultDisk {
return &DefaultDisk{}
}

func (d *DefaultDisk) Size() int64 {
return 4096 * int64(d.WriteBlockSize()) // 2MB
}

func (d *DefaultDisk) WriteBlockSize() int64 {
return 512 // 512 bytes
}

func (d *DefaultDisk) EraseBlockSize() int64 {
return 2048 // 4 blocks of 512 bytes
}

func (d *DefaultDisk) EraseBlocks(startBlock, numBlocks int64) error {
return nil
}

func (d *DefaultDisk) ReadAt(buffer []byte, offset int64) (int, error) {
n := uint8(offset)
for i := range buffer {
n++
buffer[i] = n
}
return len(buffer), nil
}

func (d *DefaultDisk) WriteAt(buffer []byte, offset int64) (int, error) {
return len(buffer), nil
}

// RegisterBlockDevice registers a BlockDevice provider with the MSC driver
func (m *msc) RegisterBlockDevice(dev machine.BlockDevice) {
m.dev = dev

// 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()) / uint32(dev.WriteBlockSize())
binary.BigEndian.PutUint32(vpdPages[i].Data[24:28], granularity)
}
/* TODO: Add method for working out the optimal unmap granularity alignment
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
}
*/
break
}
}
}
Loading
Loading