Compare commits
8
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e8e4061564 | ||
|
|
10691a9e03 | ||
|
|
cebad0b583 | ||
|
|
53cc758e60 | ||
|
|
6c7619bc2c | ||
|
|
d318eca262 | ||
|
|
c28b9dd77f | ||
|
|
c0c56a9c84 |
No files matched your search
@@ -15,6 +15,7 @@ localenv.json
|
|||||||
dockerout.txt
|
dockerout.txt
|
||||||
AGENTS.md
|
AGENTS.md
|
||||||
lessons_learnt.md
|
lessons_learnt.md
|
||||||
|
/plan.md
|
||||||
*.log
|
*.log
|
||||||
wasuro
|
wasuro
|
||||||
src/core/core.version.pas
|
src/core/core.version.pas
|
||||||
|
|||||||
@@ -1,49 +1,85 @@
|
|||||||
# driver.storage.ctl.usb
|
<!--
|
||||||
|
Copyright 2026 Aaron Hance
|
||||||
|
Author: Aaron Hance <[email protected]>
|
||||||
|
|
||||||
USB Mass Storage Class driver stub.
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
## Overview
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
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.
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
-->
|
||||||
|
|
||||||
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.
|
# USB mass storage
|
||||||
|
|
||||||
## Boot Registration
|
`driver.storage.ctl.usb` implements SCSI transparent commands over USB Mass
|
||||||
|
Storage Bulk-Only Transport (interface class/subclass/protocol `08/06/50`).
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
## Dependencies
|
## Execution and ownership
|
||||||
|
|
||||||
- `boot.mgr`
|
An attachment task probes and registers each disk after scheduling starts; boot
|
||||||
|
initialization never waits on a worker that cannot run yet.
|
||||||
|
|
||||||
- `driver.mgr`
|
Storage dispatch only publishes a request. One persistent `usb.storage` worker
|
||||||
- `memory.heap`
|
performs I/O in task context, with one command active per device and a reusable,
|
||||||
- `driver.storage.types`
|
page-aligned 4 KiB bounce buffer. Caller buffers are never submitted for DMA.
|
||||||
- `io.syslog`
|
Commands are split at the bounce-buffer boundary; all LBA/count and buffer-end
|
||||||
- `debug.tracer`
|
arithmetic is checked before submission. CBW/CSW wire sizes, tags, status,
|
||||||
- `driver.bus.usb.core`
|
residue, and actual transfer lengths are validated.
|
||||||
- `driver.bus.usb.types`
|
|
||||||
- `core.util`, `arch.x86.util`
|
|
||||||
|
|
||||||
## Constants
|
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.
|
||||||
|
|
||||||
| Constant | Value | Description |
|
The worker performs reset recovery (class reset, clear bulk-IN halt, clear
|
||||||
|---|---|---|
|
bulk-OUT halt) after malformed status or phase errors. Failed SCSI commands
|
||||||
| `USB_CLASS_MASS_STORAGE` | `$08` | USB interface class for mass storage |
|
request sense and return storage errors, including write protection.
|
||||||
| `USB_SC_SCSI` | `$06` | SCSI transparent command set subclass |
|
|
||||||
| `USB_PROTO_BBB` | `$50` | Bulk-Only (BOT) transport protocol |
|
|
||||||
|
|
||||||
## Functions and Procedures
|
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.
|
||||||
|
|
||||||
### init
|
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.
|
||||||
|
|
||||||
```pascal
|
## Supported scope
|
||||||
procedure init;
|
|
||||||
```
|
|
||||||
|
|
||||||
Constructs a `TDeviceIdentifier` matching any USB device with `bInterfaceClass = $08`, `bInterfaceSubClass = $06`, `bInterfaceProtocol = $50`, and registers the `load` callback with `driver.mgr`.
|
- 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.
|
||||||
|
|
||||||
## Notes
|
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.
|
||||||
|
|
||||||
- The `load` callback currently returns true unconditionally after logging a detection message.
|
## Validation
|
||||||
- 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.
|
|
||||||
|
`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.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
Protocol: [USB-IF Bulk-Only Transport 1.0](https://www.usb.org/sites/default/files/usbmassbulk_10.pdf).
|
||||||
@@ -1,3 +1,20 @@
|
|||||||
|
<!--
|
||||||
|
Copyright 2026 Aaron Hance
|
||||||
|
Author: Aaron Hance <[email protected]>
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
-->
|
||||||
|
|
||||||
# driver.storage.mgr
|
# driver.storage.mgr
|
||||||
|
|
||||||
Physical storage device registry and I/O dispatch manager.
|
Physical storage device registry and I/O dispatch manager.
|
||||||
@@ -14,7 +31,7 @@ The I/O model operates as follows:
|
|||||||
- `storage_read` / `storage_write` are convenience wrappers that build a stack-local `TIORequest` and call `submit_io_wait`.
|
- `storage_read` / `storage_write` are convenience wrappers that build a stack-local `TIORequest` and call `submit_io_wait`.
|
||||||
- `storage_read_async` / `storage_write_async` build and submit a request with a caller-supplied callback, returning immediately.
|
- `storage_read_async` / `storage_write_async` build and submit a request with a caller-supplied callback, returning immediately.
|
||||||
|
|
||||||
Legacy `storage_read_legacy` / `storage_write_legacy` procedure wrappers are retained for callers not yet migrated to the function-returning API.
|
Blocking callers use `storage_read` / `storage_write` and check the returned `TError`.
|
||||||
|
|
||||||
## Dependencies
|
## Dependencies
|
||||||
|
|
||||||
@@ -205,15 +222,6 @@ function storage_write_async(device : PStorage_Device; addr : uint32; sectors :
|
|||||||
|
|
||||||
Non-blocking write. Analogous to `storage_read_async`.
|
Non-blocking write. Analogous to `storage_read_async`.
|
||||||
|
|
||||||
### storage_read_legacy / storage_write_legacy
|
|
||||||
|
|
||||||
```pascal
|
|
||||||
procedure storage_read_legacy(device : PStorage_Device; addr : uint32; sectors : uint32; buffer : puint32);
|
|
||||||
procedure storage_write_legacy(device : PStorage_Device; addr : uint32; sectors : uint32; buffer : puint32);
|
|
||||||
```
|
|
||||||
|
|
||||||
Drop-in procedure replacements for callers using the old void-returning API. Forward to `storage_read` / `storage_write` and discard the error code. Marked for removal once all callers are migrated.
|
|
||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
- `dispatch_next` is an internal procedure called with interrupts disabled. It dequeues the head of the CFIFO, allocates a heap copy via `ioreq_copy` (for ISR safety), and invokes the appropriate `dispatchRead` or `dispatchWrite` on the device. If neither dispatch pointer is set, the request is failed with `eDeviceNotFound`.
|
- `dispatch_next` is an internal procedure called with interrupts disabled. It dequeues the head of the CFIFO, allocates a heap copy via `ioreq_copy` (for ISR safety), and invokes the appropriate `dispatchRead` or `dispatchWrite` on the device. If neither dispatch pointer is set, the request is failed with `eDeviceNotFound`.
|
||||||
|
|||||||
+13
-11
@@ -1200,7 +1200,8 @@ begin
|
|||||||
vol := findVolumeForPartition(device, part.LBA_start);
|
vol := findVolumeForPartition(device, part.LBA_start);
|
||||||
if vol <> nil then begin
|
if vol <> nil then begin
|
||||||
freeB := uint64(vol^.freeSectors) * vol^.sectorSize;
|
freeB := uint64(vol^.freeSectors) * vol^.sectorSize;
|
||||||
freeStr := uiConcat(formatSizeStr(freeB), ' free');
|
if (vol^.lifecycleFlags and STORAGE_VOLUME_FLAG_FREE_UNKNOWN) <> 0 then freeStr := 'Free: unknown'
|
||||||
|
else freeStr := uiConcat(formatSizeStr(freeB), ' free');
|
||||||
lbl := lv_label_create(row);
|
lbl := lv_label_create(row);
|
||||||
lv_label_set_text(lbl, freeStr);
|
lv_label_set_text(lbl, freeStr);
|
||||||
lv_obj_set_style_text_color(lbl, lv_color_make(100, 180, 130), 0);
|
lv_obj_set_style_text_color(lbl, lv_color_make(100, 180, 130), 0);
|
||||||
@@ -1240,7 +1241,6 @@ var
|
|||||||
dw : sint32;
|
dw : sint32;
|
||||||
i : uint32;
|
i : uint32;
|
||||||
totalBytes : uint64;
|
totalBytes : uint64;
|
||||||
usedSectors : uint64;
|
|
||||||
freeBytes : uint64;
|
freeBytes : uint64;
|
||||||
volCnt : uint32;
|
volCnt : uint32;
|
||||||
devVolCnt : uint32;
|
devVolCnt : uint32;
|
||||||
@@ -1274,22 +1274,18 @@ begin
|
|||||||
totalBytes := uint64(device^.maxSectorCount) * device^.sectorSize;
|
totalBytes := uint64(device^.maxSectorCount) * device^.sectorSize;
|
||||||
addInfoRow(detail, dw, 'Total Size', formatSizePrecise(totalBytes));
|
addInfoRow(detail, dw, 'Total Size', formatSizePrecise(totalBytes));
|
||||||
|
|
||||||
{ Compute free space from in-memory volume data (no I/O) }
|
{ Use the same cached partition geometry as Add Partition. Volumes may
|
||||||
usedSectors := 0;
|
still be undergoing filesystem probing after the device is published. }
|
||||||
devVolCnt := 0;
|
devVolCnt := 0;
|
||||||
volCnt := driver.storage.vol.mgr.get_volume_count();
|
volCnt := driver.storage.vol.mgr.get_volume_count();
|
||||||
if volCnt > 0 then for i := 0 to volCnt - 1 do begin
|
if volCnt > 0 then for i := 0 to volCnt - 1 do begin
|
||||||
vol := driver.storage.vol.mgr.get_volume(i);
|
vol := driver.storage.vol.mgr.get_volume(i);
|
||||||
if (vol <> nil) and (vol^.device = device) then begin
|
if (vol <> nil) and (vol^.device = device) then begin
|
||||||
devVolCnt := devVolCnt + 1;
|
devVolCnt := devVolCnt + 1;
|
||||||
usedSectors := usedSectors + vol^.sectorCount;
|
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
if device^.maxSectorCount > usedSectors then
|
freeBytes := uint64(driver.storage.vol.mgr.get_free_sector_count(device)) * device^.sectorSize;
|
||||||
freeBytes := uint64(device^.maxSectorCount - usedSectors) * device^.sectorSize
|
addInfoRow(detail, dw, 'Unallocated Space', formatSizePrecise(freeBytes));
|
||||||
else
|
|
||||||
freeBytes := 0;
|
|
||||||
addInfoRow(detail, dw, 'Free Space', formatSizePrecise(freeBytes));
|
|
||||||
addInfoRow(detail, dw, 'Volumes', uiInt(devVolCnt));
|
addInfoRow(detail, dw, 'Volumes', uiInt(devVolCnt));
|
||||||
|
|
||||||
{ Usage bar }
|
{ Usage bar }
|
||||||
@@ -1352,7 +1348,8 @@ begin
|
|||||||
|
|
||||||
{ Free space }
|
{ Free space }
|
||||||
freeB := uint64(vol^.freeSectors) * vol^.sectorSize;
|
freeB := uint64(vol^.freeSectors) * vol^.sectorSize;
|
||||||
freeStr := uiConcat(formatSizeStr(freeB), ' free');
|
if (vol^.lifecycleFlags and STORAGE_VOLUME_FLAG_FREE_UNKNOWN) <> 0 then freeStr := 'Free: unknown'
|
||||||
|
else freeStr := uiConcat(formatSizeStr(freeB), ' free');
|
||||||
lbl := lv_label_create(row);
|
lbl := lv_label_create(row);
|
||||||
lv_label_set_text(lbl, freeStr);
|
lv_label_set_text(lbl, freeStr);
|
||||||
lv_obj_set_style_text_color(lbl, lv_color_make(100, 180, 130), 0);
|
lv_obj_set_style_text_color(lbl, lv_color_make(100, 180, 130), 0);
|
||||||
@@ -1494,6 +1491,10 @@ begin
|
|||||||
usedBytes := totalBytes - freeBytes;
|
usedBytes := totalBytes - freeBytes;
|
||||||
|
|
||||||
addInfoRow(detail, dw, 'Total Size', formatSizePrecise(totalBytes));
|
addInfoRow(detail, dw, 'Total Size', formatSizePrecise(totalBytes));
|
||||||
|
if (vol^.lifecycleFlags and STORAGE_VOLUME_FLAG_FREE_UNKNOWN) <> 0 then begin
|
||||||
|
addInfoRow(detail, dw, 'Free Space', 'Unknown');
|
||||||
|
addInfoRow(detail, dw, 'Used Space', 'Unknown');
|
||||||
|
end else begin
|
||||||
addInfoRow(detail, dw, 'Free Space', formatSizePrecise(freeBytes));
|
addInfoRow(detail, dw, 'Free Space', formatSizePrecise(freeBytes));
|
||||||
addInfoRow(detail, dw, 'Used Space', formatSizePrecise(usedBytes));
|
addInfoRow(detail, dw, 'Used Space', formatSizePrecise(usedBytes));
|
||||||
|
|
||||||
@@ -1502,6 +1503,7 @@ begin
|
|||||||
addInfoRow(detail, dw, 'Usage', uiConcat(uiInt(pctUsed), '%'));
|
addInfoRow(detail, dw, 'Usage', uiConcat(uiInt(pctUsed), '%'));
|
||||||
addUsageBar(detail, dw, usedBytes, totalBytes);
|
addUsageBar(detail, dw, usedBytes, totalBytes);
|
||||||
end;
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
addInfoRow(detail, dw, 'Boot Drive', boolStr(vol^.isBootDrive));
|
addInfoRow(detail, dw, 'Boot Drive', boolStr(vol^.isBootDrive));
|
||||||
|
|
||||||
|
|||||||
@@ -355,8 +355,10 @@ begin
|
|||||||
proc.mgr.kill(state^.pid);
|
proc.mgr.kill(state^.pid);
|
||||||
state^.pid := 0;
|
state^.pid := 0;
|
||||||
end;
|
end;
|
||||||
freeState(state);
|
if state^.ta <> nil then lv_group_remove_obj(state^.ta);
|
||||||
|
state^.ta := nil;
|
||||||
driver.video.windows.destroyWindow(wid);
|
driver.video.windows.destroyWindow(wid);
|
||||||
|
freeState(state);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
{ ============================================================
|
{ ============================================================
|
||||||
@@ -440,7 +442,7 @@ begin
|
|||||||
written := driver.storage.vfs.WriteFile(fHandle, 0, puint8(buf), len);
|
written := driver.storage.vfs.WriteFile(fHandle, 0, puint8(buf), len);
|
||||||
driver.storage.vfs.CloseFile(fHandle);
|
driver.storage.vfs.CloseFile(fHandle);
|
||||||
kfree(void(buf));
|
kfree(void(buf));
|
||||||
if written = 0 then begin
|
if written <> len then begin
|
||||||
io.syslog.logln('NOTEPAD', 'saveFile: WriteFile returned 0');
|
io.syslog.logln('NOTEPAD', 'saveFile: WriteFile returned 0');
|
||||||
showErrorMsgbox('Save Failed', 'File write failed. Check filesystem.');
|
showErrorMsgbox('Save Failed', 'File write failed. Check filesystem.');
|
||||||
updateStatusBar(state);
|
updateStatusBar(state);
|
||||||
|
|||||||
@@ -179,7 +179,9 @@ begin
|
|||||||
|
|
||||||
print('Size: '); printsize(vol^.sectorCount * vol^.sectorSize); println('');
|
print('Size: '); printsize(vol^.sectorCount * vol^.sectorSize); println('');
|
||||||
|
|
||||||
print('Free: '); printsize(vol^.freeSectors * vol^.sectorSize); println('');
|
print('Free: ');
|
||||||
|
if (vol^.lifecycleFlags and STORAGE_VOLUME_FLAG_FREE_UNKNOWN) <> 0 then println('Unknown')
|
||||||
|
else begin printsize(vol^.freeSectors * vol^.sectorSize); println(''); end;
|
||||||
print('Boot Drive: ');
|
print('Boot Drive: ');
|
||||||
if vol^.isBootDrive then println('yes') else println('no');
|
if vol^.isBootDrive then println('yes') else println('no');
|
||||||
|
|
||||||
|
|||||||
@@ -96,6 +96,7 @@ uses
|
|||||||
driver.net.dev.e1000,
|
driver.net.dev.e1000,
|
||||||
driver.bus.usb.ehci,
|
driver.bus.usb.ehci,
|
||||||
driver.storage.fs.fat32,
|
driver.storage.fs.fat32,
|
||||||
|
driver.storage.fs.ext2,
|
||||||
arch.x86.fault,
|
arch.x86.fault,
|
||||||
core.ds.fifo,
|
core.ds.fifo,
|
||||||
driver.storage.fs.mgr,
|
driver.storage.fs.mgr,
|
||||||
|
|||||||
@@ -1,6 +1,21 @@
|
|||||||
|
// Copyright 2026 Kieron Morris
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
{
|
{
|
||||||
Driver->Bus->driver.bus.usb->USBCore - driver.bus.usb Core Enumeration, Transfer API & HC Management.
|
Driver->Bus->driver.bus.usb->USBCore - driver.bus.usb Core Enumeration, Transfer API & HC Management.
|
||||||
|
|
||||||
|
@author(Aaron Hance <[email protected]>)
|
||||||
@author(Kieron Morris <[email protected]>)
|
@author(Kieron Morris <[email protected]>)
|
||||||
}
|
}
|
||||||
unit driver.bus.usb.core;
|
unit driver.bus.usb.core;
|
||||||
@@ -658,6 +673,7 @@ begin
|
|||||||
|
|
||||||
{ Step 2: Create device at address 0 }
|
{ Step 2: Create device at address 0 }
|
||||||
dev := PUSBDevice(kalloc(sizeof(TUSBDevice)));
|
dev := PUSBDevice(kalloc(sizeof(TUSBDevice)));
|
||||||
|
if dev = nil then begin pop_trace; exit; end;
|
||||||
dev^.Address := 0;
|
dev^.Address := 0;
|
||||||
dev^.Speed := speed;
|
dev^.Speed := speed;
|
||||||
dev^.MaxPacket0 := 8; { Default, will be updated from descriptor }
|
dev^.MaxPacket0 := 8; { Default, will be updated from descriptor }
|
||||||
@@ -667,6 +683,8 @@ begin
|
|||||||
dev^.ParentPort := parentPort;
|
dev^.ParentPort := parentPort;
|
||||||
dev^.NumEndpoints := 0;
|
dev^.NumEndpoints := 0;
|
||||||
dev^.Configured := false;
|
dev^.Configured := false;
|
||||||
|
dev^.InterfaceNumber := 0;
|
||||||
|
dev^.InterfaceCount := 0;
|
||||||
dev^.fnDisconnect := nil;
|
dev^.fnDisconnect := nil;
|
||||||
|
|
||||||
{ Step 3: Get first 8 bytes of device descriptor to learn MaxPacketSize0 }
|
{ Step 3: Get first 8 bytes of device descriptor to learn MaxPacketSize0 }
|
||||||
@@ -730,7 +748,14 @@ begin
|
|||||||
end;
|
end;
|
||||||
|
|
||||||
totalLen := cfgDesc.wTotalLength;
|
totalLen := cfgDesc.wTotalLength;
|
||||||
|
if totalLen < sizeof(TUSBConfigDescriptor) then begin
|
||||||
|
kfree(void(dev));
|
||||||
|
pop_trace;
|
||||||
|
exit;
|
||||||
|
end;
|
||||||
|
dev^.InterfaceCount := cfgDesc.bNumInterfaces;
|
||||||
cfgBuf := Pointer(kalloc(totalLen));
|
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);
|
status := usb_get_descriptor(dev, USB_DESC_CONFIGURATION, 0, cfgBuf, totalLen);
|
||||||
if status <> tsSuccess then begin
|
if status <> tsSuccess then begin
|
||||||
@@ -744,12 +769,14 @@ begin
|
|||||||
{ Step 7: Parse interface and endpoint descriptors }
|
{ Step 7: Parse interface and endpoint descriptors }
|
||||||
offset := sizeof(TUSBConfigDescriptor);
|
offset := sizeof(TUSBConfigDescriptor);
|
||||||
dev^.NumEndpoints := 0;
|
dev^.NumEndpoints := 0;
|
||||||
while offset < totalLen do begin
|
while offset + 2 <= totalLen do begin
|
||||||
{ Read descriptor length and type }
|
{ 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 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);
|
epDesc := PUSBEndpointDescriptor(uint32(cfgBuf) + offset);
|
||||||
dev^.Endpoints[dev^.NumEndpoints].Address := usb_ep_number(epDesc^.bEndpointAddress);
|
dev^.Endpoints[dev^.NumEndpoints].Address := usb_ep_number(epDesc^.bEndpointAddress);
|
||||||
dev^.Endpoints[dev^.NumEndpoints].Direction := usb_ep_direction(epDesc^.bEndpointAddress);
|
dev^.Endpoints[dev^.NumEndpoints].Direction := usb_ep_direction(epDesc^.bEndpointAddress);
|
||||||
@@ -780,16 +807,22 @@ begin
|
|||||||
{ Find the first interface descriptor for class/subclass/protocol }
|
{ Find the first interface descriptor for class/subclass/protocol }
|
||||||
offset := sizeof(TUSBConfigDescriptor);
|
offset := sizeof(TUSBConfigDescriptor);
|
||||||
ifDesc := nil;
|
ifDesc := nil;
|
||||||
while offset < totalLen do begin
|
while offset + 2 <= totalLen do begin
|
||||||
if PUint8(uint32(cfgBuf) + offset)^ = 0 then break;
|
if PUint8(uint32(cfgBuf) + offset)^ < 2 then break;
|
||||||
if PUint8(uint32(cfgBuf) + offset + 1)^ = USB_DESC_INTERFACE then begin
|
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);
|
ifDesc := PUSBInterfaceDescriptor(uint32(cfgBuf) + offset);
|
||||||
|
dev^.InterfaceNumber := ifDesc^.bInterfaceNumber;
|
||||||
break;
|
break;
|
||||||
end;
|
end;
|
||||||
offset := offset + PUint8(uint32(cfgBuf) + offset)^;
|
offset := offset + PUint8(uint32(cfgBuf) + offset)^;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
DevID := PDeviceIdentifier(kalloc(sizeof(TDeviceIdentifier)));
|
DevID := PDeviceIdentifier(kalloc(sizeof(TDeviceIdentifier)));
|
||||||
|
if DevID = nil then begin
|
||||||
|
kfree(void(cfgBuf)); kfree(void(dev)); pop_trace; exit;
|
||||||
|
end;
|
||||||
DevID^.Bus := biUSB;
|
DevID^.Bus := biUSB;
|
||||||
DevID^.id0 := (uint32(devDesc.idVendor) SHL 16) OR devDesc.idProduct;
|
DevID^.id0 := (uint32(devDesc.idVendor) SHL 16) OR devDesc.idProduct;
|
||||||
DevID^.id1 := devDesc.bDeviceClass;
|
DevID^.id1 := devDesc.bDeviceClass;
|
||||||
|
|||||||
@@ -1,3 +1,17 @@
|
|||||||
|
// Copyright 2026 Kieron Morris
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
{
|
{
|
||||||
Driver->Bus->driver.bus.usb->driver.bus.usb.ehci - Enhanced Host Controller Interface Driver.
|
Driver->Bus->driver.bus.usb->driver.bus.usb.ehci - Enhanced Host Controller Interface Driver.
|
||||||
|
|
||||||
@@ -8,6 +22,7 @@
|
|||||||
driver.bus.usb.ehci also handles companion controller (driver.bus.usb.uhci/driver.bus.usb.ohci) routing for
|
driver.bus.usb.ehci also handles companion controller (driver.bus.usb.uhci/driver.bus.usb.ohci) routing for
|
||||||
full/low-speed devices via the CONFIGFLAG and PORTSC port-owner bits.
|
full/low-speed devices via the CONFIGFLAG and PORTSC port-owner bits.
|
||||||
|
|
||||||
|
@author(Aaron Hance <ah@aaronhance.me>)
|
||||||
@author(Kieron Morris <kjm@kieronmorris.me>)
|
@author(Kieron Morris <kjm@kieronmorris.me>)
|
||||||
}
|
}
|
||||||
unit driver.bus.usb.ehci;
|
unit driver.bus.usb.ehci;
|
||||||
@@ -963,6 +978,8 @@ end;
|
|||||||
|
|
||||||
{ ========================= Submit Transfer ========================= }
|
{ ========================= Submit Transfer ========================= }
|
||||||
|
|
||||||
|
procedure ehci_free_qtd_chain(first : PEHCI_qTD); forward;
|
||||||
|
|
||||||
function ehci_submit_control(hc : PUSBHCDriver; transfer : PUSBTransfer) : boolean;
|
function ehci_submit_control(hc : PUSBHCDriver; transfer : PUSBTransfer) : boolean;
|
||||||
var
|
var
|
||||||
priv : PEHCI_PrivData;
|
priv : PEHCI_PrivData;
|
||||||
@@ -1004,7 +1021,7 @@ begin
|
|||||||
|
|
||||||
{ SETUP qTD }
|
{ SETUP qTD }
|
||||||
qtd := ehci_alloc_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;
|
firstqTD := qtd;
|
||||||
qtd^.Token := ehci_make_qtd_token(EHCI_QTD_PID_SETUP, false, 8, false, 3);
|
qtd^.Token := ehci_make_qtd_token(EHCI_QTD_PID_SETUP, false, 8, false, 3);
|
||||||
ehci_fill_qtd_buffers(qtd, @transfer^.Setup, 8);
|
ehci_fill_qtd_buffers(qtd, @transfer^.Setup, 8);
|
||||||
@@ -1021,7 +1038,7 @@ begin
|
|||||||
|
|
||||||
while remaining > 0 do begin
|
while remaining > 0 do begin
|
||||||
qtd := ehci_alloc_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;
|
||||||
|
|
||||||
if remaining > maxPkt then
|
if remaining > maxPkt then
|
||||||
pktLen := maxPkt
|
pktLen := maxPkt
|
||||||
@@ -1034,6 +1051,7 @@ begin
|
|||||||
pid := EHCI_QTD_PID_OUT;
|
pid := EHCI_QTD_PID_OUT;
|
||||||
|
|
||||||
qtd^.Token := ehci_make_qtd_token(pid, toggle, pktLen, false, 3);
|
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);
|
ehci_fill_qtd_buffers(qtd, Pointer(uint32(transfer^.Buffer) + offset), pktLen);
|
||||||
qtd^.NextqTD := EHCI_QTD_T;
|
qtd^.NextqTD := EHCI_QTD_T;
|
||||||
qtd^.AltNextqTD := EHCI_QTD_T;
|
qtd^.AltNextqTD := EHCI_QTD_T;
|
||||||
@@ -1052,7 +1070,7 @@ begin
|
|||||||
|
|
||||||
{ STATUS qTD (opposite direction, DATA1) }
|
{ STATUS qTD (opposite direction, DATA1) }
|
||||||
qtd := ehci_alloc_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;
|
||||||
|
|
||||||
if (transfer^.Setup.bmRequestType AND $80) <> 0 then
|
if (transfer^.Setup.bmRequestType AND $80) <> 0 then
|
||||||
pid := EHCI_QTD_PID_OUT
|
pid := EHCI_QTD_PID_OUT
|
||||||
@@ -1070,7 +1088,7 @@ begin
|
|||||||
|
|
||||||
{ Build QH }
|
{ Build QH }
|
||||||
qh := ehci_alloc_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,
|
qh^.EPChars := ehci_make_qh_epchars(devAddr, 0, speedBits, maxPkt,
|
||||||
true, false, isCtrlEP, 4);
|
true, false, isCtrlEP, 4);
|
||||||
@@ -1152,7 +1170,7 @@ begin
|
|||||||
|
|
||||||
repeat
|
repeat
|
||||||
qtd := ehci_alloc_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;
|
||||||
|
|
||||||
if firstqTD = nil then firstqTD := qtd;
|
if firstqTD = nil then firstqTD := qtd;
|
||||||
|
|
||||||
@@ -1164,6 +1182,7 @@ begin
|
|||||||
pktLen := 0;
|
pktLen := 0;
|
||||||
|
|
||||||
qtd^.Token := ehci_make_qtd_token(pid, toggle, pktLen, false, 3);
|
qtd^.Token := ehci_make_qtd_token(pid, toggle, pktLen, false, 3);
|
||||||
|
qtd^.SWPad0 := pktLen; { Original data length, excluding SETUP/status }
|
||||||
if pktLen > 0 then
|
if pktLen > 0 then
|
||||||
ehci_fill_qtd_buffers(qtd, Pointer(uint32(transfer^.Buffer) + offset), pktLen);
|
ehci_fill_qtd_buffers(qtd, Pointer(uint32(transfer^.Buffer) + offset), pktLen);
|
||||||
qtd^.NextqTD := EHCI_QTD_T;
|
qtd^.NextqTD := EHCI_QTD_T;
|
||||||
@@ -1200,7 +1219,7 @@ begin
|
|||||||
|
|
||||||
{ Build QH }
|
{ Build QH }
|
||||||
qh := ehci_alloc_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,
|
qh^.EPChars := ehci_make_qh_epchars(devAddr, epNum, speedBits, maxPkt,
|
||||||
true, false, false, 4);
|
true, false, false, 4);
|
||||||
@@ -1297,6 +1316,11 @@ begin
|
|||||||
ehci_transfer_done := true;
|
ehci_transfer_done := true;
|
||||||
exit;
|
exit;
|
||||||
end;
|
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;
|
qtd := qtd^.SWNext;
|
||||||
end;
|
end;
|
||||||
ehci_transfer_done := true;
|
ehci_transfer_done := true;
|
||||||
@@ -1385,6 +1409,8 @@ var
|
|||||||
transfer : PUSBTransfer;
|
transfer : PUSBTransfer;
|
||||||
head : PEHCI_QH;
|
head : PEHCI_QH;
|
||||||
listIdx : uint32;
|
listIdx : uint32;
|
||||||
|
qtd : PEHCI_qTD;
|
||||||
|
residual : uint32;
|
||||||
begin
|
begin
|
||||||
if (hc = nil) or (hc^.PrivData = nil) then exit;
|
if (hc = nil) or (hc^.PrivData = nil) then exit;
|
||||||
priv := PEHCI_PrivData(hc^.PrivData);
|
priv := PEHCI_PrivData(hc^.PrivData);
|
||||||
@@ -1422,7 +1448,15 @@ begin
|
|||||||
if transfer^.Status = tsInProgress then begin
|
if transfer^.Status = tsInProgress then begin
|
||||||
if ehci_transfer_done(qh) then begin
|
if ehci_transfer_done(qh) then begin
|
||||||
transfer^.Status := ehci_transfer_status(qh);
|
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 }
|
{ Remove QH from schedule and free qTD chain }
|
||||||
if listIdx = 0 then
|
if listIdx = 0 then
|
||||||
ehci_remove_async_qh(priv, qh)
|
ehci_remove_async_qh(priv, qh)
|
||||||
|
|||||||
File diff suppressed because it is too large.
Load diff
@@ -1,6 +1,21 @@
|
|||||||
|
// Copyright 2026 Kieron Morris
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
{
|
{
|
||||||
Driver->Bus->driver.bus.usb->USBTypes - Shared driver.bus.usb Types, Constants & HC Abstraction.
|
Driver->Bus->driver.bus.usb->USBTypes - Shared driver.bus.usb Types, Constants & HC Abstraction.
|
||||||
|
|
||||||
|
@author(Aaron Hance <ah@aaronhance.me>)
|
||||||
@author(Kieron Morris <kjm@kieronmorris.me>)
|
@author(Kieron Morris <kjm@kieronmorris.me>)
|
||||||
}
|
}
|
||||||
unit driver.bus.usb.types;
|
unit driver.bus.usb.types;
|
||||||
@@ -249,6 +264,8 @@ type
|
|||||||
Endpoints : array[0..USB_MAX_ENDPOINTS-1] of TUSBEndpoint;
|
Endpoints : array[0..USB_MAX_ENDPOINTS-1] of TUSBEndpoint;
|
||||||
NumEndpoints : uint8;
|
NumEndpoints : uint8;
|
||||||
Configured : boolean;
|
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 }
|
fnDisconnect : TUSBDisconnectCallback; { Called before device is freed on unplug }
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large.
Load diff
@@ -15,6 +15,7 @@ type
|
|||||||
TMock = record
|
TMock = record
|
||||||
Data : puint8;
|
Data : puint8;
|
||||||
Writes, FailWrite, Callbacks, Depth, MaxDepth : uint32;
|
Writes, FailWrite, Callbacks, Depth, MaxDepth : uint32;
|
||||||
|
MaxReadSectors, FailReadLBA : uint32;
|
||||||
Error : TError;
|
Error : TError;
|
||||||
OutOfBounds, FailRead : boolean;
|
OutOfBounds, FailRead : boolean;
|
||||||
end;
|
end;
|
||||||
@@ -27,7 +28,10 @@ begin
|
|||||||
(request^.SectorCount > device^.maxSectorCount-request^.LBA) then begin
|
(request^.SectorCount > device^.maxSectorCount-request^.LBA) then begin
|
||||||
m^.OutOfBounds := true; complete_io(request, false, eInvalidArgument); exit;
|
m^.OutOfBounds := true; complete_io(request, false, eInvalidArgument); exit;
|
||||||
end;
|
end;
|
||||||
if (request^.RequestType <> ioWrite) and m^.FailRead then begin
|
if request^.RequestType <> ioWrite then
|
||||||
|
if request^.SectorCount > m^.MaxReadSectors then m^.MaxReadSectors := request^.SectorCount;
|
||||||
|
if (request^.RequestType <> ioWrite) and (m^.FailRead or
|
||||||
|
((m^.FailReadLBA <> 0) and (request^.LBA = m^.FailReadLBA))) then begin
|
||||||
Inc(m^.Depth); if m^.Depth > m^.MaxDepth then m^.MaxDepth := m^.Depth;
|
Inc(m^.Depth); if m^.Depth > m^.MaxDepth then m^.MaxDepth := m^.Depth;
|
||||||
complete_io(request,false,eIOError); Dec(m^.Depth); exit;
|
complete_io(request,false,eIOError); Dec(m^.Depth); exit;
|
||||||
end;
|
end;
|
||||||
@@ -197,6 +201,53 @@ begin
|
|||||||
mock^.FailWrite := 0;
|
mock^.FailWrite := 0;
|
||||||
Assert(remove_partition(device,0) = eNone,'delete commits');
|
Assert(remove_partition(device,0) = eNone,'delete commits');
|
||||||
Assert(get_volume_count() = oldCount,'delete detaches volume after write');
|
Assert(get_volume_count() = oldCount,'delete detaches volume after write');
|
||||||
|
{ More than eight FAT sectors, with a partial final batch and FAT padding. }
|
||||||
|
kfree(void(mock^.Data));
|
||||||
|
mock^.Data := puint8(kalloc(4096*512));
|
||||||
|
if mock^.Data <> nil then begin
|
||||||
|
memset(uint32(mock^.Data),0,1500*512);
|
||||||
|
device^.maxSectorCount := 1500;
|
||||||
|
volume^.sectorStart := 0; volume^.sectorCount := 1500;
|
||||||
|
Assert(create_volume(volume,1500,0,@params) = eNone,'format multi-batch FAT fixture');
|
||||||
|
mock^.MaxReadSectors := 0;
|
||||||
|
Assert(identify_volume(volume),'identify multi-batch FAT');
|
||||||
|
Assert(volume^.freeSectors = 1454,'batched count excludes padding and allocated clusters');
|
||||||
|
Assert(mock^.MaxReadSectors = 8,'FAT scan batches eight sectors');
|
||||||
|
mock^.FailReadLBA := 40;
|
||||||
|
Assert(not identify_volume(volume),'failure in final FAT batch rejects probe');
|
||||||
|
mock^.FailReadLBA := 0;
|
||||||
|
Assert(not mock^.OutOfBounds,'batched scan stays within device');
|
||||||
|
device^.maxSectorCount := 4096; volume^.sectorCount := 4096;
|
||||||
|
Assert(create_volume(volume,4096,0,@params) = eNone,'format large FAT fixture');
|
||||||
|
mock^.FailReadLBA := 32; mock^.MaxReadSectors := 0;
|
||||||
|
Assert(identify_volume(volume),'large FAT mounts without reading allocation table');
|
||||||
|
Assert((volume^.lifecycleFlags and STORAGE_VOLUME_FLAG_FREE_UNKNOWN) <> 0,'missing FSInfo reports unknown free space');
|
||||||
|
Assert(mock^.MaxReadSectors = 1,'large volume discovery uses only metadata sector reads');
|
||||||
|
PBootRecord(mock^.Data)^.FSInfoCluster := 1;
|
||||||
|
puint32(uint32(mock^.Data)+512)^ := $41615252;
|
||||||
|
puint32(uint32(mock^.Data)+512+484)^ := $61417272;
|
||||||
|
puint32(uint32(mock^.Data)+512+488)^ := 100;
|
||||||
|
puint32(uint32(mock^.Data)+512+508)^ := $AA550000;
|
||||||
|
Assert(identify_volume(volume),'valid FSInfo avoids FAT scan');
|
||||||
|
Assert((volume^.freeSectors = 100) and
|
||||||
|
((volume^.lifecycleFlags and STORAGE_VOLUME_FLAG_FREE_UNKNOWN) = 0),'FSInfo count displayed');
|
||||||
|
puint32(uint32(mock^.Data)+512+488)^ := $FFFFFFFF;
|
||||||
|
Assert(identify_volume(volume),'unknown FSInfo count does not prevent mount');
|
||||||
|
Assert((volume^.lifecycleFlags and STORAGE_VOLUME_FLAG_FREE_UNKNOWN) <> 0,'unknown FSInfo count stays unknown');
|
||||||
|
puint32(uint32(mock^.Data)+512+488)^ := 4096;
|
||||||
|
Assert(identify_volume(volume),'out-of-range FSInfo count does not prevent mount');
|
||||||
|
Assert((volume^.lifecycleFlags and STORAGE_VOLUME_FLAG_FREE_UNKNOWN) <> 0,'out-of-range FSInfo count rejected');
|
||||||
|
puint32(uint32(mock^.Data)+512+488)^ := 0;
|
||||||
|
Assert(identify_volume(volume),'full volume FSInfo accepted');
|
||||||
|
Assert((volume^.freeSectors = 0) and
|
||||||
|
((volume^.lifecycleFlags and STORAGE_VOLUME_FLAG_FREE_UNKNOWN) = 0),'known zero differs from unknown');
|
||||||
|
puint32(uint32(mock^.Data)+512+508)^ := 0;
|
||||||
|
Assert(identify_volume(volume),'invalid FSInfo signature does not prevent mount');
|
||||||
|
Assert((volume^.lifecycleFlags and STORAGE_VOLUME_FLAG_FREE_UNKNOWN) <> 0,'invalid FSInfo signature rejected');
|
||||||
|
mock^.FailReadLBA := 1;
|
||||||
|
Assert(identify_volume(volume),'optional FSInfo read failure does not prevent mount');
|
||||||
|
mock^.FailReadLBA := 0;
|
||||||
|
end else Assert(false,'allocate multi-batch FAT fixture');
|
||||||
kfree(device^.cachedMBR); CFIFO_Free(device^.requestQueue);
|
kfree(device^.cachedMBR); CFIFO_Free(device^.requestQueue);
|
||||||
kfree(void(mock^.Data)); kfree(void(mock)); kfree(void(volume)); kfree(void(device)); kfree(void(br));
|
kfree(void(mock^.Data)); kfree(void(mock)); kfree(void(volume)); kfree(void(device)); kfree(void(br));
|
||||||
io.syslog.log('STORMETA','Passed: '); io.syslog.writeint(passed);
|
io.syslog.log('STORMETA','Passed: '); io.syslog.writeint(passed);
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
// Copyright 2021 Aaron Hance
|
// Copyright 2021 Aaron Hance
|
||||||
|
// Copyright 2026 Aaron Hance
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
@@ -19,14 +20,14 @@
|
|||||||
and provides the submit_io / complete_io request-based I/O path.
|
and provides the submit_io / complete_io request-based I/O path.
|
||||||
Device drivers call register_device() when they discover a new device.
|
Device drivers call register_device() when they discover a new device.
|
||||||
|
|
||||||
Phase 2 I/O model:
|
Request-based I/O model:
|
||||||
- submit_io enqueues a TIORequest into the device's per-device CFIFO,
|
- submit_io enqueues a TIORequest into the device's per-device CFIFO,
|
||||||
dispatches to hardware, and sleeps the calling task (proc_await).
|
dispatches to hardware, and returns without waiting for completion.
|
||||||
- complete_io is called from ISR/completion context, wakes the caller.
|
- complete_io delivers callbacks from the driver's completion context.
|
||||||
- storage_read / storage_write are thin wrappers that build a stack-local
|
- storage_read / storage_write are thin wrappers that build a stack-local
|
||||||
TIORequest, call submit_io, and return the resulting TError.
|
TIORequest, call submit_io_wait, and return the resulting TError.
|
||||||
- Legacy sync/async callbacks are still supported as fallback for drivers
|
- Drivers must provide the TDriverDispatch interface; there is no legacy
|
||||||
not yet migrated to the TDriverDispatch interface.
|
callback fallback.
|
||||||
|
|
||||||
@author(Aaron Hance <ah@aaronhance.me>)
|
@author(Aaron Hance <ah@aaronhance.me>)
|
||||||
}
|
}
|
||||||
@@ -112,12 +113,6 @@ function storage_read_async(device : PStorage_Device; addr : uint32; sectors : u
|
|||||||
function storage_write_async(device : PStorage_Device; addr : uint32; sectors : uint32;
|
function storage_write_async(device : PStorage_Device; addr : uint32; sectors : uint32;
|
||||||
buffer : puint32; callback : TIOCallback; callbackData : pointer) : TError;
|
buffer : puint32; callback : TIOCallback; callbackData : pointer) : TError;
|
||||||
|
|
||||||
{ Legacy wrappers — procedure signatures matching the old API.
|
|
||||||
Forward to the new functions, silently discard errors.
|
|
||||||
TODO: migrate all callers and remove these. }
|
|
||||||
procedure storage_read_legacy(device : PStorage_Device; addr : uint32; sectors : uint32; buffer : puint32);
|
|
||||||
procedure storage_write_legacy(device : PStorage_Device; addr : uint32; sectors : uint32; buffer : puint32);
|
|
||||||
|
|
||||||
implementation
|
implementation
|
||||||
|
|
||||||
uses
|
uses
|
||||||
@@ -744,17 +739,6 @@ begin
|
|||||||
storage_write_async := submit_io(@req);
|
storage_write_async := submit_io(@req);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
{ Legacy procedure wrappers — drop-in replacements for old callers }
|
|
||||||
procedure storage_read_legacy(device : PStorage_Device; addr : uint32; sectors : uint32; buffer : puint32);
|
|
||||||
begin
|
|
||||||
storage_read(device, addr, sectors, buffer);
|
|
||||||
end;
|
|
||||||
|
|
||||||
procedure storage_write_legacy(device : PStorage_Device; addr : uint32; sectors : uint32; buffer : puint32);
|
|
||||||
begin
|
|
||||||
storage_write(device, addr, sectors, buffer);
|
|
||||||
end;
|
|
||||||
|
|
||||||
initialization
|
initialization
|
||||||
boot.mgr.registerBoot('driver.storage.mgr', @init, 'Storage Manager', 'driver.storage.vfs');
|
boot.mgr.registerBoot('driver.storage.mgr', @init, 'Storage Manager', 'driver.storage.vfs');
|
||||||
|
|
||||||
|
|||||||
@@ -222,6 +222,8 @@ type
|
|||||||
to read/write hooks. Also returns the current file size through the
|
to read/write hooks. Also returns the current file size through the
|
||||||
var parameter. Returning nil is legal (disables per-call fast-path). }
|
var parameter. Returning nil is legal (disables per-call fast-path). }
|
||||||
PPOpenFileHook = function(volume : PStorage_Volume; directory : pchar; fileName : pchar; var fileSize : uint32) : pointer;
|
PPOpenFileHook = function(volume : PStorage_Volume; directory : pchar; fileName : pchar; var fileSize : uint32) : pointer;
|
||||||
|
{ Mode-aware open avoids creating a missing file during a read-only open. }
|
||||||
|
PPOpenFileModeHook = function(volume : PStorage_Volume; directory : pchar; fileName : pchar; allowCreate : boolean; var fileSize : uint32) : pointer;
|
||||||
{ Per-file close hook: frees the context allocated by openFileCallback. }
|
{ Per-file close hook: frees the context allocated by openFileCallback. }
|
||||||
PPCloseFileHook = procedure(ctx : pointer);
|
PPCloseFileHook = procedure(ctx : pointer);
|
||||||
|
|
||||||
@@ -324,6 +326,7 @@ type
|
|||||||
renameFileCallback : PPRenameFileHook;
|
renameFileCallback : PPRenameFileHook;
|
||||||
{ Per-file open/close — nil if FS does not cache per-file metadata }
|
{ Per-file open/close — nil if FS does not cache per-file metadata }
|
||||||
openFileCallback : PPOpenFileHook;
|
openFileCallback : PPOpenFileHook;
|
||||||
|
openFileModeCallback : PPOpenFileModeHook;
|
||||||
closeFileCallback : PPCloseFileHook;
|
closeFileCallback : PPCloseFileHook;
|
||||||
formatParamFlags : uint32;
|
formatParamFlags : uint32;
|
||||||
validateLabelCallback : PPValidateLabelHook;
|
validateLabelCallback : PPValidateLabelHook;
|
||||||
@@ -354,6 +357,7 @@ const
|
|||||||
STORAGE_VOLUME_FLAG_FORMATTING = 2;
|
STORAGE_VOLUME_FLAG_FORMATTING = 2;
|
||||||
STORAGE_VOLUME_FLAG_INVALIDATING = 4;
|
STORAGE_VOLUME_FLAG_INVALIDATING = 4;
|
||||||
STORAGE_VOLUME_FLAG_REMOVED = 8;
|
STORAGE_VOLUME_FLAG_REMOVED = 8;
|
||||||
|
STORAGE_VOLUME_FLAG_FREE_UNKNOWN = 16;
|
||||||
|
|
||||||
|
|
||||||
implementation
|
implementation
|
||||||
|
|||||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
Loaded 20 of 24 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user