Implement USB mass storage and batch OHCI bulk transfers
This commit is contained in:
1 parent
c0c56a9c84
commit
c28b9dd77f
7 files changed
+914
-141
No files matched your search
@@ -1,49 +1,68 @@
|
||||
# driver.storage.ctl.usb
|
||||
# USB mass storage
|
||||
|
||||
USB Mass Storage Class driver stub.
|
||||
`driver.storage.ctl.usb` implements SCSI transparent commands over USB Mass
|
||||
Storage Bulk-Only Transport (interface class/subclass/protocol `08/06/50`).
|
||||
|
||||
## Overview
|
||||
It probes INQUIRY, TEST UNIT READY and READ CAPACITY(10), registers LUN 0 with
|
||||
the storage manager, and exposes READ(10), WRITE(10), REQUEST SENSE and
|
||||
SYNCHRONIZE CACHE(10). Existing partition discovery and filesystem mounting
|
||||
apply to the resulting USB disk. Writes complete only after cache synchronization.
|
||||
|
||||
This unit registers a USB class driver with the driver manager (`driver.mgr`) to claim USB Mass Storage Class interfaces (class `$08`, subclass `$06` SCSI, protocol `$50` Bulk-Only). When the USB stack enumerates a matching interface, the `load` callback is invoked. Currently the callback only logs a detection message; no I/O infrastructure is established.
|
||||
## Execution and ownership
|
||||
|
||||
Full Bulk-Only Transport (BOT) support — including Max LUN query, endpoint setup, CBW/CSW command exchange, and `driver.storage.mgr` device registration — is planned but not yet implemented.
|
||||
An attachment task probes and registers each disk after scheduling starts; boot
|
||||
initialization never waits on a worker that cannot run yet.
|
||||
|
||||
## Boot Registration
|
||||
Storage dispatch only publishes a request. One persistent `usb.storage` worker
|
||||
performs I/O in task context, with one command active per device and a reusable,
|
||||
page-aligned 4 KiB bounce buffer. Caller buffers are never submitted for DMA.
|
||||
Commands are split at the bounce-buffer boundary; all LBA/count and buffer-end
|
||||
arithmetic is checked before submission. CBW/CSW wire sizes, tags, status,
|
||||
residue, and actual transfer lengths are validated.
|
||||
|
||||
Registered with `boot.mgr` as `driver.storage.ctl.usb` at the `device` barrier. The barrier order places `device` after `bus`, ensuring `driver.bus.usb.core.init` has run before the storage class driver registers with the driver manager.
|
||||
OHCI batches bulk data into spans of at most 4 KiB per transfer descriptor,
|
||||
using the endpoint's hardware toggle carry across packets. A 4 KiB full-speed
|
||||
read uses one data descriptor instead of 64. Short packets still halt the chain
|
||||
and report the actual byte count; interrupt endpoint packet sizing is unchanged.
|
||||
|
||||
## Dependencies
|
||||
The worker performs reset recovery (class reset, clear bulk-IN halt, clear
|
||||
bulk-OUT halt) after malformed status or phase errors. Failed SCSI commands
|
||||
request sense and return storage errors, including write protection.
|
||||
|
||||
- `boot.mgr`
|
||||
On disconnect the disk is marked removed immediately. Accepted requests are
|
||||
completed with errors, and idle volumes are invalidated/unmounted by the worker;
|
||||
busy volumes are retried. Storage-manager records remain stable tombstones.
|
||||
Class-driver buffers and context are freed after pending hardware work drains.
|
||||
|
||||
- `driver.mgr`
|
||||
- `memory.heap`
|
||||
- `driver.storage.types`
|
||||
- `io.syslog`
|
||||
- `debug.tracer`
|
||||
- `driver.bus.usb.core`
|
||||
- `driver.bus.usb.types`
|
||||
- `core.util`, `arch.x86.util`
|
||||
Host controllers currently have no cancellation API. A timed-out transfer is
|
||||
therefore quarantined with its private device snapshot and DMA memory. No further
|
||||
commands are issued to that device. If hardware never completes, those allocations
|
||||
remain retained until reboot; freeing them would permit DMA into reused memory.
|
||||
The timeout uses the PIT's actual frequency and a bounded polling fallback.
|
||||
|
||||
## Constants
|
||||
## Supported scope
|
||||
|
||||
| Constant | Value | Description |
|
||||
|---|---|---|
|
||||
| `USB_CLASS_MASS_STORAGE` | `$08` | USB interface class for mass storage |
|
||||
| `USB_SC_SCSI` | `$06` | SCSI transparent command set subclass |
|
||||
| `USB_PROTO_BBB` | `$50` | Bulk-Only (BOT) transport protocol |
|
||||
- LUN 0 on a single-interface device with one bulk-IN and one bulk-OUT endpoint.
|
||||
- Direct-access disks with power-of-two sectors from 512 through 4096 bytes.
|
||||
- Capacity representable by the existing 32-bit storage-sector API; the
|
||||
READ CAPACITY(16) sentinel is rejected.
|
||||
- OHCI, EHCI and xHCI bulk transfers. OHCI/EHCI report actual data lengths and
|
||||
terminate bulk reads on short packets.
|
||||
|
||||
## Functions and Procedures
|
||||
UAS, additional LUNs, composite/alternate-setting interfaces, UHCI storage,
|
||||
optical devices, and media replacement without USB reconnect are not supported.
|
||||
The current xHCI driver lacks endpoint-ring reset support: a halted bulk endpoint
|
||||
fails the device closed rather than attempting an unsafe ring reuse.
|
||||
|
||||
### init
|
||||
## Validation
|
||||
|
||||
```pascal
|
||||
procedure init;
|
||||
```
|
||||
`UnitTest` is called by the boot test suite and is available as `USBMSCTEST`.
|
||||
Tests use a memory-only mock host controller: wire layout, byte order, capacity
|
||||
limits, reads, writes, sense errors, short transfers, status stalls, reset ordering,
|
||||
and request bounds. They perform no disk I/O.
|
||||
|
||||
Constructs a `TDeviceIdentifier` matching any USB device with `bInterfaceClass = $08`, `bInterfaceSubClass = $06`, `bInterfaceProtocol = $50`, and registers the `load` callback with `driver.mgr`.
|
||||
For hardware/VM checks, boot with a disposable USB disk, confirm `USBMSC` discovery
|
||||
and filesystem mounting, test file read/write/read-back, then unplug while idle
|
||||
and during I/O. Do not run destructive disk tests against a disk containing data.
|
||||
|
||||
## Notes
|
||||
|
||||
- The `load` callback currently returns true unconditionally after logging a detection message.
|
||||
- Once full BOT support is implemented, `load` should query the Max LUN, allocate endpoint structures, and call `driver.storage.mgr.register_device` for each logical unit.
|
||||
Protocol: [USB-IF Bulk-Only Transport 1.0](https://www.usb.org/sites/default/files/usbmassbulk_10.pdf).
|
||||
@@ -658,6 +658,7 @@ begin
|
||||
|
||||
{ Step 2: Create device at address 0 }
|
||||
dev := PUSBDevice(kalloc(sizeof(TUSBDevice)));
|
||||
if dev = nil then begin pop_trace; exit; end;
|
||||
dev^.Address := 0;
|
||||
dev^.Speed := speed;
|
||||
dev^.MaxPacket0 := 8; { Default, will be updated from descriptor }
|
||||
@@ -667,6 +668,8 @@ begin
|
||||
dev^.ParentPort := parentPort;
|
||||
dev^.NumEndpoints := 0;
|
||||
dev^.Configured := false;
|
||||
dev^.InterfaceNumber := 0;
|
||||
dev^.InterfaceCount := 0;
|
||||
dev^.fnDisconnect := nil;
|
||||
|
||||
{ Step 3: Get first 8 bytes of device descriptor to learn MaxPacketSize0 }
|
||||
@@ -730,7 +733,14 @@ begin
|
||||
end;
|
||||
|
||||
totalLen := cfgDesc.wTotalLength;
|
||||
if totalLen < sizeof(TUSBConfigDescriptor) then begin
|
||||
kfree(void(dev));
|
||||
pop_trace;
|
||||
exit;
|
||||
end;
|
||||
dev^.InterfaceCount := cfgDesc.bNumInterfaces;
|
||||
cfgBuf := Pointer(kalloc(totalLen));
|
||||
if cfgBuf = nil then begin kfree(void(dev)); pop_trace; exit; end;
|
||||
|
||||
status := usb_get_descriptor(dev, USB_DESC_CONFIGURATION, 0, cfgBuf, totalLen);
|
||||
if status <> tsSuccess then begin
|
||||
@@ -744,12 +754,14 @@ begin
|
||||
{ Step 7: Parse interface and endpoint descriptors }
|
||||
offset := sizeof(TUSBConfigDescriptor);
|
||||
dev^.NumEndpoints := 0;
|
||||
while offset < totalLen do begin
|
||||
while offset + 2 <= totalLen do begin
|
||||
{ Read descriptor length and type }
|
||||
if PUint8(uint32(cfgBuf) + offset)^ = 0 then break; { Safety: zero-length = corrupt }
|
||||
if PUint8(uint32(cfgBuf) + offset)^ < 2 then break;
|
||||
if offset + PUint8(uint32(cfgBuf) + offset)^ > totalLen then break;
|
||||
|
||||
if PUint8(uint32(cfgBuf) + offset + 1)^ = USB_DESC_ENDPOINT then begin
|
||||
if dev^.NumEndpoints < USB_MAX_ENDPOINTS then begin
|
||||
if (PUint8(uint32(cfgBuf) + offset)^ >= sizeof(TUSBEndpointDescriptor)) and
|
||||
(dev^.NumEndpoints < USB_MAX_ENDPOINTS) then begin
|
||||
epDesc := PUSBEndpointDescriptor(uint32(cfgBuf) + offset);
|
||||
dev^.Endpoints[dev^.NumEndpoints].Address := usb_ep_number(epDesc^.bEndpointAddress);
|
||||
dev^.Endpoints[dev^.NumEndpoints].Direction := usb_ep_direction(epDesc^.bEndpointAddress);
|
||||
@@ -780,16 +792,22 @@ begin
|
||||
{ Find the first interface descriptor for class/subclass/protocol }
|
||||
offset := sizeof(TUSBConfigDescriptor);
|
||||
ifDesc := nil;
|
||||
while offset < totalLen do begin
|
||||
if PUint8(uint32(cfgBuf) + offset)^ = 0 then break;
|
||||
if PUint8(uint32(cfgBuf) + offset + 1)^ = USB_DESC_INTERFACE then begin
|
||||
while offset + 2 <= totalLen do begin
|
||||
if PUint8(uint32(cfgBuf) + offset)^ < 2 then break;
|
||||
if offset + PUint8(uint32(cfgBuf) + offset)^ > totalLen then break;
|
||||
if (PUint8(uint32(cfgBuf) + offset + 1)^ = USB_DESC_INTERFACE) and
|
||||
(PUint8(uint32(cfgBuf) + offset)^ >= sizeof(TUSBInterfaceDescriptor)) then begin
|
||||
ifDesc := PUSBInterfaceDescriptor(uint32(cfgBuf) + offset);
|
||||
dev^.InterfaceNumber := ifDesc^.bInterfaceNumber;
|
||||
break;
|
||||
end;
|
||||
offset := offset + PUint8(uint32(cfgBuf) + offset)^;
|
||||
end;
|
||||
|
||||
DevID := PDeviceIdentifier(kalloc(sizeof(TDeviceIdentifier)));
|
||||
if DevID = nil then begin
|
||||
kfree(void(cfgBuf)); kfree(void(dev)); pop_trace; exit;
|
||||
end;
|
||||
DevID^.Bus := biUSB;
|
||||
DevID^.id0 := (uint32(devDesc.idVendor) SHL 16) OR devDesc.idProduct;
|
||||
DevID^.id1 := devDesc.bDeviceClass;
|
||||
|
||||
@@ -963,6 +963,8 @@ end;
|
||||
|
||||
{ ========================= Submit Transfer ========================= }
|
||||
|
||||
procedure ehci_free_qtd_chain(first : PEHCI_qTD); forward;
|
||||
|
||||
function ehci_submit_control(hc : PUSBHCDriver; transfer : PUSBTransfer) : boolean;
|
||||
var
|
||||
priv : PEHCI_PrivData;
|
||||
@@ -1004,7 +1006,7 @@ begin
|
||||
|
||||
{ SETUP qTD }
|
||||
qtd := ehci_alloc_qtd;
|
||||
if qtd = nil then begin pop_trace; exit; end;
|
||||
if qtd = nil then begin ehci_free_qtd_chain(firstqTD); pop_trace; exit; end;
|
||||
firstqTD := qtd;
|
||||
qtd^.Token := ehci_make_qtd_token(EHCI_QTD_PID_SETUP, false, 8, false, 3);
|
||||
ehci_fill_qtd_buffers(qtd, @transfer^.Setup, 8);
|
||||
@@ -1021,7 +1023,7 @@ begin
|
||||
|
||||
while remaining > 0 do begin
|
||||
qtd := ehci_alloc_qtd;
|
||||
if qtd = nil then begin pop_trace; exit; end;
|
||||
if qtd = nil then begin ehci_free_qtd_chain(firstqTD); pop_trace; exit; end;
|
||||
|
||||
if remaining > maxPkt then
|
||||
pktLen := maxPkt
|
||||
@@ -1034,6 +1036,7 @@ begin
|
||||
pid := EHCI_QTD_PID_OUT;
|
||||
|
||||
qtd^.Token := ehci_make_qtd_token(pid, toggle, pktLen, false, 3);
|
||||
qtd^.SWPad0 := pktLen; { Original data length, excluding SETUP/status }
|
||||
ehci_fill_qtd_buffers(qtd, Pointer(uint32(transfer^.Buffer) + offset), pktLen);
|
||||
qtd^.NextqTD := EHCI_QTD_T;
|
||||
qtd^.AltNextqTD := EHCI_QTD_T;
|
||||
@@ -1052,7 +1055,7 @@ begin
|
||||
|
||||
{ STATUS qTD (opposite direction, DATA1) }
|
||||
qtd := ehci_alloc_qtd;
|
||||
if qtd = nil then begin pop_trace; exit; end;
|
||||
if qtd = nil then begin ehci_free_qtd_chain(firstqTD); pop_trace; exit; end;
|
||||
|
||||
if (transfer^.Setup.bmRequestType AND $80) <> 0 then
|
||||
pid := EHCI_QTD_PID_OUT
|
||||
@@ -1070,7 +1073,7 @@ begin
|
||||
|
||||
{ Build QH }
|
||||
qh := ehci_alloc_qh;
|
||||
if qh = nil then begin pop_trace; exit; end;
|
||||
if qh = nil then begin ehci_free_qtd_chain(firstqTD); pop_trace; exit; end;
|
||||
|
||||
qh^.EPChars := ehci_make_qh_epchars(devAddr, 0, speedBits, maxPkt,
|
||||
true, false, isCtrlEP, 4);
|
||||
@@ -1152,7 +1155,7 @@ begin
|
||||
|
||||
repeat
|
||||
qtd := ehci_alloc_qtd;
|
||||
if qtd = nil then begin pop_trace; exit; end;
|
||||
if qtd = nil then begin ehci_free_qtd_chain(firstqTD); pop_trace; exit; end;
|
||||
|
||||
if firstqTD = nil then firstqTD := qtd;
|
||||
|
||||
@@ -1164,6 +1167,7 @@ begin
|
||||
pktLen := 0;
|
||||
|
||||
qtd^.Token := ehci_make_qtd_token(pid, toggle, pktLen, false, 3);
|
||||
qtd^.SWPad0 := pktLen; { Original data length, excluding SETUP/status }
|
||||
if pktLen > 0 then
|
||||
ehci_fill_qtd_buffers(qtd, Pointer(uint32(transfer^.Buffer) + offset), pktLen);
|
||||
qtd^.NextqTD := EHCI_QTD_T;
|
||||
@@ -1200,7 +1204,7 @@ begin
|
||||
|
||||
{ Build QH }
|
||||
qh := ehci_alloc_qh;
|
||||
if qh = nil then begin pop_trace; exit; end;
|
||||
if qh = nil then begin ehci_free_qtd_chain(firstqTD); pop_trace; exit; end;
|
||||
|
||||
qh^.EPChars := ehci_make_qh_epchars(devAddr, epNum, speedBits, maxPkt,
|
||||
true, false, false, 4);
|
||||
@@ -1297,6 +1301,11 @@ begin
|
||||
ehci_transfer_done := true;
|
||||
exit;
|
||||
end;
|
||||
if (qh^.SWTransfer <> nil) and
|
||||
(PUSBTransfer(qh^.SWTransfer)^.PipeType = ptBulk) and
|
||||
(((qtd^.Token SHR 16) AND $7FFF) <> 0) then begin
|
||||
ehci_transfer_done := true; exit;
|
||||
end;
|
||||
qtd := qtd^.SWNext;
|
||||
end;
|
||||
ehci_transfer_done := true;
|
||||
@@ -1385,6 +1394,8 @@ var
|
||||
transfer : PUSBTransfer;
|
||||
head : PEHCI_QH;
|
||||
listIdx : uint32;
|
||||
qtd : PEHCI_qTD;
|
||||
residual : uint32;
|
||||
begin
|
||||
if (hc = nil) or (hc^.PrivData = nil) then exit;
|
||||
priv := PEHCI_PrivData(hc^.PrivData);
|
||||
@@ -1422,7 +1433,15 @@ begin
|
||||
if transfer^.Status = tsInProgress then begin
|
||||
if ehci_transfer_done(qh) then begin
|
||||
transfer^.Status := ehci_transfer_status(qh);
|
||||
transfer^.ActualLen := transfer^.BufferLen;
|
||||
transfer^.ActualLen := 0;
|
||||
qtd := qh^.SWFirstqTD;
|
||||
while qtd <> nil do begin
|
||||
if (qtd^.Token AND EHCI_QTD_STS_ACTIVE) <> 0 then break;
|
||||
residual := (qtd^.Token SHR 16) AND $7FFF;
|
||||
if (qtd^.SWPad0 > 0) and (residual <= qtd^.SWPad0) then
|
||||
inc(transfer^.ActualLen, qtd^.SWPad0 - residual);
|
||||
qtd := qtd^.SWNext;
|
||||
end;
|
||||
{ Remove QH from schedule and free qTD chain }
|
||||
if listIdx = 0 then
|
||||
ehci_remove_async_qh(priv, qh)
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -249,6 +249,8 @@ type
|
||||
Endpoints : array[0..USB_MAX_ENDPOINTS-1] of TUSBEndpoint;
|
||||
NumEndpoints : uint8;
|
||||
Configured : boolean;
|
||||
InterfaceNumber : uint8; { First interface, alternate setting zero }
|
||||
InterfaceCount : uint8; { Class drivers may reject composite devices }
|
||||
fnDisconnect : TUSBDisconnectCallback; { Called before device is freed on unplug }
|
||||
end;
|
||||
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -37,6 +37,7 @@ uses
|
||||
driver.storage.metatest,
|
||||
driver.storage.perftest,
|
||||
driver.storage.ctl.ahci,
|
||||
driver.storage.ctl.usb,
|
||||
core.enc.fnv1a,
|
||||
core.enc.djb2,
|
||||
core.ds.bloom,
|
||||
@@ -76,6 +77,7 @@ begin
|
||||
driver.storage.metatest.UnitTest;
|
||||
driver.storage.perftest.UnitTest;
|
||||
driver.storage.ctl.ahci.UnitTest;
|
||||
driver.storage.ctl.usb.UnitTest;
|
||||
core.enc.fnv1a.UnitTest;
|
||||
core.enc.djb2.UnitTest;
|
||||
core.ds.bloom.UnitTest;
|
||||
|
||||
Reference in new issue
Block a user