Skip to content

Commit 9a1ba8b

Browse files
committed
usb: add USB mass storage class support
1 parent febb390 commit 9a1ba8b

File tree

14 files changed

+1496
-3
lines changed

14 files changed

+1496
-3
lines changed

src/machine/usb.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,8 @@ var (
134134
usb.HID_ENDPOINT_OUT: (usb.ENDPOINT_TYPE_DISABLE), // Interrupt Out
135135
usb.MIDI_ENDPOINT_IN: (usb.ENDPOINT_TYPE_DISABLE), // Bulk In
136136
usb.MIDI_ENDPOINT_OUT: (usb.ENDPOINT_TYPE_DISABLE), // Bulk Out
137+
usb.MSC_ENDPOINT_IN: (usb.ENDPOINT_TYPE_DISABLE), // Bulk In
138+
usb.MSC_ENDPOINT_OUT: (usb.ENDPOINT_TYPE_DISABLE), // Bulk Out
137139
}
138140
)
139141

src/machine/usb/descriptor/endpoint.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,17 @@ import (
44
"internal/binary"
55
)
66

7+
/* Endpoint Descriptor
8+
USB 2.0 Specification: 9.6.6 Endpoint
9+
*/
10+
11+
const (
12+
TransferTypeControl uint8 = iota
13+
TransferTypeIsochronous
14+
TransferTypeBulk
15+
TransferTypeInterrupt
16+
)
17+
718
var endpointEP1IN = [endpointTypeLen]byte{
819
endpointTypeLen,
920
TypeEndpoint,
@@ -74,6 +85,36 @@ var EndpointEP5OUT = EndpointType{
7485
data: endpointEP5OUT[:],
7586
}
7687

88+
// Mass Storage Class bulk in endpoint
89+
var endpointEP8IN = [endpointTypeLen]byte{
90+
endpointTypeLen,
91+
TypeEndpoint,
92+
0x88, // EndpointAddress
93+
TransferTypeBulk, // Attributes
94+
0x40, // MaxPacketSizeL (64 bytes)
95+
0x00, // MaxPacketSizeH
96+
0x00, // Interval
97+
}
98+
99+
var EndpointEP8IN = EndpointType{
100+
data: endpointEP8IN[:],
101+
}
102+
103+
// Mass Storage Class bulk out endpoint
104+
var endpointEP9OUT = [endpointTypeLen]byte{
105+
endpointTypeLen,
106+
TypeEndpoint,
107+
0x09, // EndpointAddress
108+
TransferTypeBulk, // Attributes
109+
0x40, // MaxPacketSizeL (64 bytes)
110+
0x00, // MaxPacketSizeH
111+
0x00, // Interval
112+
}
113+
114+
var EndpointEP9OUT = EndpointType{
115+
data: endpointEP9OUT[:],
116+
}
117+
77118
const (
78119
endpointTypeLen = 7
79120
)
@@ -109,3 +150,7 @@ func (d EndpointType) MaxPacketSize(v uint16) {
109150
func (d EndpointType) Interval(v uint8) {
110151
d.data[6] = byte(v)
111152
}
153+
154+
func (d EndpointType) GetMaxPacketSize() uint16 {
155+
return binary.LittleEndian.Uint16(d.data[4:6])
156+
}

src/machine/usb/descriptor/msc.go

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package descriptor
2+
3+
const (
4+
interfaceClassMSC = 0x08
5+
mscSubclassSCSI = 0x06
6+
mscProtocolBOT = 0x50
7+
)
8+
9+
var interfaceAssociationMSC = [interfaceAssociationTypeLen]byte{
10+
interfaceAssociationTypeLen,
11+
TypeInterfaceAssociation,
12+
0x02, // FirstInterface
13+
0x01, // InterfaceCount
14+
interfaceClassMSC, // FunctionClass
15+
mscSubclassSCSI, // FunctionSubClass
16+
mscProtocolBOT, // FunctionProtocol
17+
0x00, // Function
18+
}
19+
20+
var InterfaceAssociationMSC = InterfaceAssociationType{
21+
data: interfaceAssociationMSC[:],
22+
}
23+
24+
var interfaceMSC = [interfaceTypeLen]byte{
25+
interfaceTypeLen, // Length
26+
TypeInterface, // DescriptorType
27+
0x02, // InterfaceNumber
28+
0x00, // AlternateSetting
29+
0x02, // NumEndpoints
30+
interfaceClassMSC, // InterfaceClass (Mass Storage)
31+
mscSubclassSCSI, // InterfaceSubClass (SCSI Transparent)
32+
mscProtocolBOT, // InterfaceProtocol (Bulk-Only Transport)
33+
0x00, // Interface
34+
}
35+
36+
var InterfaceMSC = InterfaceType{
37+
data: interfaceMSC[:],
38+
}
39+
40+
var configurationMSC = [configurationTypeLen]byte{
41+
configurationTypeLen,
42+
TypeConfiguration,
43+
0x6a, 0x00, // wTotalLength
44+
0x03, // number of interfaces (bNumInterfaces)
45+
0x01, // configuration value (bConfigurationValue)
46+
0x00, // index to string description (iConfiguration)
47+
0xa0, // attributes (bmAttributes)
48+
0x32, // maxpower (100 mA) (bMaxPower)
49+
}
50+
51+
var ConfigurationMSC = ConfigurationType{
52+
data: configurationMSC[:],
53+
}
54+
55+
// Mass Storage Class
56+
var MSC = Descriptor{
57+
Device: DeviceCDC.Bytes(),
58+
Configuration: Append([][]byte{
59+
ConfigurationMSC.Bytes(),
60+
InterfaceAssociationCDC.Bytes(),
61+
InterfaceCDCControl.Bytes(),
62+
ClassSpecificCDCHeader.Bytes(),
63+
ClassSpecificCDCACM.Bytes(),
64+
ClassSpecificCDCUnion.Bytes(),
65+
ClassSpecificCDCCallManagement.Bytes(),
66+
EndpointEP1IN.Bytes(),
67+
InterfaceCDCData.Bytes(),
68+
EndpointEP2OUT.Bytes(),
69+
EndpointEP3IN.Bytes(),
70+
InterfaceAssociationMSC.Bytes(),
71+
InterfaceMSC.Bytes(),
72+
EndpointEP8IN.Bytes(),
73+
EndpointEP9OUT.Bytes(),
74+
}),
75+
}

src/machine/usb/msc/cbw.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package msc
2+
3+
import (
4+
"encoding/binary"
5+
"machine/usb/msc/csw"
6+
"machine/usb/msc/scsi"
7+
)
8+
9+
const (
10+
cbwMsgLen = 31 // Command Block Wrapper (CBW) message length
11+
Signature = 0x43425355 // "USBC" in little endian
12+
)
13+
14+
type CBW struct {
15+
Data []byte
16+
}
17+
18+
func (c *CBW) length() int {
19+
return len(c.Data)
20+
}
21+
22+
func (c *CBW) validLength() bool {
23+
return len(c.Data) == cbwMsgLen
24+
}
25+
26+
func (c *CBW) validSignature() bool {
27+
return binary.LittleEndian.Uint32(c.Data[:4]) == Signature
28+
}
29+
30+
func (c *CBW) scsiCmd() scsi.Cmd {
31+
return scsi.Cmd{Data: c.Data[15:]}
32+
}
33+
34+
func (c *CBW) transferLength() uint32 {
35+
return binary.LittleEndian.Uint32(c.Data[8:12])
36+
}
37+
38+
// isIn returns true if the command direction is from the device to the host.
39+
func (c *CBW) isIn() bool {
40+
return c.Data[12]>>7 != 0
41+
}
42+
43+
// isOut returns true if the command direction is from the host to the device.
44+
func (c *CBW) isOut() bool {
45+
return !c.isIn()
46+
}
47+
48+
func (c *CBW) CSW(status csw.Status, residue uint32, b []byte) {
49+
// Signature: "USBS" 53425355h (little endian)
50+
binary.LittleEndian.PutUint32(b[:4], csw.Signature)
51+
// Tag: (same as CBW)
52+
copy(b[4:8], c.Data[4:8])
53+
// Data Residue: (untransferred bytes)
54+
binary.LittleEndian.PutUint32(b[8:12], residue)
55+
// Status:
56+
b[12] = byte(status)
57+
}

src/machine/usb/msc/csw/csw.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package csw
2+
3+
type Status uint8
4+
5+
const (
6+
StatusPassed Status = iota
7+
StatusFailed
8+
StatusPhaseError
9+
)
10+
11+
const (
12+
MsgLen = 13
13+
Signature = 0x53425355 // "USBS" in little endian
14+
)

src/machine/usb/msc/disk.go

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package msc
2+
3+
import (
4+
"encoding/binary"
5+
"machine"
6+
)
7+
8+
var _ machine.BlockDevice = (*DefaultDisk)(nil)
9+
10+
// DefaultDisk is a placeholder disk implementation
11+
type DefaultDisk struct {
12+
}
13+
14+
// NewDefaultDisk creates a new DefaultDisk instance
15+
func NewDefaultDisk() *DefaultDisk {
16+
return &DefaultDisk{}
17+
}
18+
19+
func (d *DefaultDisk) Size() int64 {
20+
return 4096 * int64(d.WriteBlockSize()) // 2MB
21+
}
22+
23+
func (d *DefaultDisk) WriteBlockSize() int64 {
24+
return 512 // 512 bytes
25+
}
26+
27+
func (d *DefaultDisk) EraseBlockSize() int64 {
28+
return 2048 // 4 blocks of 512 bytes
29+
}
30+
31+
func (d *DefaultDisk) EraseBlocks(startBlock, numBlocks int64) error {
32+
return nil
33+
}
34+
35+
func (d *DefaultDisk) ReadAt(buffer []byte, offset int64) (int, error) {
36+
n := uint8(offset)
37+
for i := range buffer {
38+
n++
39+
buffer[i] = n
40+
}
41+
return len(buffer), nil
42+
}
43+
44+
func (d *DefaultDisk) WriteAt(buffer []byte, offset int64) (int, error) {
45+
return len(buffer), nil
46+
}
47+
48+
// RegisterBlockDevice registers a BlockDevice provider with the MSC driver
49+
func (m *msc) RegisterBlockDevice(dev machine.BlockDevice) {
50+
m.dev = dev
51+
52+
// Set VPD UNMAP fields
53+
for i := range vpdPages {
54+
if vpdPages[i].PageCode == 0xb0 {
55+
// 0xb0 - 5.4.5 Block Limits VPD page (B0h)
56+
if len(vpdPages[i].Data) >= 28 {
57+
// Set the OPTIMAL UNMAP GRANULARITY (write blocks per erase block)
58+
granularity := uint32(dev.EraseBlockSize()) / uint32(dev.WriteBlockSize())
59+
binary.BigEndian.PutUint32(vpdPages[i].Data[24:28], granularity)
60+
}
61+
/* TODO: Add method for working out the optimal unmap granularity alignment
62+
if len(vpdPages[i].Data) >= 32 {
63+
// Set the UNMAP GRANULARITY ALIGNMENT (first sector of first full erase block)
64+
// The unmap granularity alignment is used to calculate an optimal unmap request starting LBA as follows:
65+
// optimal unmap request starting LBA = (n * OPTIMAL UNMAP GRANULARITY) + UNMAP GRANULARITY ALIGNMENT
66+
// where n is zero or any positive integer value
67+
// https://www.seagate.com/files/staticfiles/support/docs/manual/Interface%20manuals/100293068j.pdf
68+
}
69+
*/
70+
break
71+
}
72+
}
73+
}

0 commit comments

Comments
 (0)