Feature/sotrage system overhaul #73

Open
Aaron wants to merge 21 commits from feature/sotrage-system-overhaul into develop
pull from: feature/sotrage-system-overhaul
7 changed files with 914 additions and 141 deletions
Showing only changes of commit c28b9dd77f - Show all commits

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).
+24 -6
View File
@@ -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;
1
+26 -7
View File
@@ -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
+2
View File
@@ -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;