Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e8e4061564 | ||
|
|
10691a9e03 | ||
|
|
cebad0b583 | ||
|
|
53cc758e60 |
No files matched your search
@@ -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.
|
||||
-->
|
||||
|
||||
# USB mass storage
|
||||
|
||||
`driver.storage.ctl.usb` implements SCSI transparent commands over USB Mass
|
||||
|
||||
@@ -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
|
||||
|
||||
Physical storage device registry and I/O dispatch manager.
|
||||
|
||||
+13
-11
@@ -1200,7 +1200,8 @@ begin
|
||||
vol := findVolumeForPartition(device, part.LBA_start);
|
||||
if vol <> nil then begin
|
||||
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);
|
||||
lv_label_set_text(lbl, freeStr);
|
||||
lv_obj_set_style_text_color(lbl, lv_color_make(100, 180, 130), 0);
|
||||
@@ -1240,7 +1241,6 @@ var
|
||||
dw : sint32;
|
||||
i : uint32;
|
||||
totalBytes : uint64;
|
||||
usedSectors : uint64;
|
||||
freeBytes : uint64;
|
||||
volCnt : uint32;
|
||||
devVolCnt : uint32;
|
||||
@@ -1274,22 +1274,18 @@ begin
|
||||
totalBytes := uint64(device^.maxSectorCount) * device^.sectorSize;
|
||||
addInfoRow(detail, dw, 'Total Size', formatSizePrecise(totalBytes));
|
||||
|
||||
{ Compute free space from in-memory volume data (no I/O) }
|
||||
usedSectors := 0;
|
||||
{ Use the same cached partition geometry as Add Partition. Volumes may
|
||||
still be undergoing filesystem probing after the device is published. }
|
||||
devVolCnt := 0;
|
||||
volCnt := driver.storage.vol.mgr.get_volume_count();
|
||||
if volCnt > 0 then for i := 0 to volCnt - 1 do begin
|
||||
vol := driver.storage.vol.mgr.get_volume(i);
|
||||
if (vol <> nil) and (vol^.device = device) then begin
|
||||
devVolCnt := devVolCnt + 1;
|
||||
usedSectors := usedSectors + vol^.sectorCount;
|
||||
end;
|
||||
end;
|
||||
if device^.maxSectorCount > usedSectors then
|
||||
freeBytes := uint64(device^.maxSectorCount - usedSectors) * device^.sectorSize
|
||||
else
|
||||
freeBytes := 0;
|
||||
addInfoRow(detail, dw, 'Free Space', formatSizePrecise(freeBytes));
|
||||
freeBytes := uint64(driver.storage.vol.mgr.get_free_sector_count(device)) * device^.sectorSize;
|
||||
addInfoRow(detail, dw, 'Unallocated Space', formatSizePrecise(freeBytes));
|
||||
addInfoRow(detail, dw, 'Volumes', uiInt(devVolCnt));
|
||||
|
||||
{ Usage bar }
|
||||
@@ -1352,7 +1348,8 @@ begin
|
||||
|
||||
{ Free space }
|
||||
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);
|
||||
lv_label_set_text(lbl, freeStr);
|
||||
lv_obj_set_style_text_color(lbl, lv_color_make(100, 180, 130), 0);
|
||||
@@ -1494,6 +1491,10 @@ begin
|
||||
usedBytes := totalBytes - freeBytes;
|
||||
|
||||
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, 'Used Space', formatSizePrecise(usedBytes));
|
||||
|
||||
@@ -1502,6 +1503,7 @@ begin
|
||||
addInfoRow(detail, dw, 'Usage', uiConcat(uiInt(pctUsed), '%'));
|
||||
addUsageBar(detail, dw, usedBytes, totalBytes);
|
||||
end;
|
||||
end;
|
||||
|
||||
addInfoRow(detail, dw, 'Boot Drive', boolStr(vol^.isBootDrive));
|
||||
|
||||
|
||||
@@ -355,8 +355,10 @@ begin
|
||||
proc.mgr.kill(state^.pid);
|
||||
state^.pid := 0;
|
||||
end;
|
||||
freeState(state);
|
||||
if state^.ta <> nil then lv_group_remove_obj(state^.ta);
|
||||
state^.ta := nil;
|
||||
driver.video.windows.destroyWindow(wid);
|
||||
freeState(state);
|
||||
end;
|
||||
|
||||
{ ============================================================
|
||||
@@ -440,7 +442,7 @@ begin
|
||||
written := driver.storage.vfs.WriteFile(fHandle, 0, puint8(buf), len);
|
||||
driver.storage.vfs.CloseFile(fHandle);
|
||||
kfree(void(buf));
|
||||
if written = 0 then begin
|
||||
if written <> len then begin
|
||||
io.syslog.logln('NOTEPAD', 'saveFile: WriteFile returned 0');
|
||||
showErrorMsgbox('Save Failed', 'File write failed. Check filesystem.');
|
||||
updateStatusBar(state);
|
||||
|
||||
@@ -179,7 +179,9 @@ begin
|
||||
|
||||
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: ');
|
||||
if vol^.isBootDrive then println('yes') else println('no');
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@author(Aaron Hance <[email protected]>)
|
||||
@author(Kieron Morris <[email protected]>)
|
||||
}
|
||||
unit driver.bus.usb.core;
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -8,6 +22,7 @@
|
||||
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.
|
||||
|
||||
@author(Aaron Hance <[email protected]>)
|
||||
@author(Kieron Morris <[email protected]>)
|
||||
}
|
||||
unit driver.bus.usb.ehci;
|
||||
|
||||
@@ -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.ohci - Open Host Controller Interface Driver.
|
||||
|
||||
@@ -5,6 +19,7 @@
|
||||
driver.bus.usb.ohci uses Endpoint Descriptors (ED), Transfer Descriptors (TD), and a
|
||||
Host Controller Communication Area (HCCA) for scheduling and completion.
|
||||
|
||||
@author(Aaron Hance <ah@aaronhance.me>)
|
||||
@author(Kieron Morris <kjm@kieronmorris.me>)
|
||||
}
|
||||
unit driver.bus.usb.ohci;
|
||||
|
||||
@@ -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.
|
||||
|
||||
@author(Aaron Hance <ah@aaronhance.me>)
|
||||
@author(Kieron Morris <kjm@kieronmorris.me>)
|
||||
}
|
||||
unit driver.bus.usb.types;
|
||||
|
||||
@@ -1,7 +1,25 @@
|
||||
// Copyright 2021 Aaron Hance
|
||||
// Copyright 2026 Aaron Hance
|
||||
//
|
||||
// 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.
|
||||
|
||||
{ USB Mass Storage: SCSI transparent commands over Bulk-Only Transport.
|
||||
LUN 0, one interface, READ/WRITE(10), 512..4096 byte sectors.
|
||||
A persistent worker owns all storage I/O. No caller buffer is ever DMA-visible.
|
||||
Protocol reference: https://www.usb.org/sites/default/files/usbmassbulk_10.pdf }
|
||||
{
|
||||
@author(Aaron Hance <ah@aaronhance.me>)
|
||||
}
|
||||
unit driver.storage.ctl.usb;
|
||||
|
||||
interface
|
||||
@@ -48,6 +66,7 @@ type
|
||||
Request : PIORequest;
|
||||
Tag : uint32;
|
||||
Offline, Ready, InFlight, Attaching, VolumesDetached : boolean;
|
||||
SyncUnsupported : boolean;
|
||||
end;
|
||||
|
||||
var
|
||||
@@ -258,6 +277,9 @@ begin
|
||||
if transport(c, actual) <> 0 then exit;
|
||||
if actual < 14 then exit;
|
||||
if (puint8(c^.Buffer)[0] and $7E) <> $70 then exit;
|
||||
io.syslog.log('USBMSC', 'SCSI sense key='); io.syslog.writeint(puint8(c^.Buffer)[2] and $0F);
|
||||
io.syslog.writestring(' ASC='); io.syslog.writeint(puint8(c^.Buffer)[12]);
|
||||
io.syslog.writestring(' ASCQ='); io.syslog.writeintln(puint8(c^.Buffer)[13]);
|
||||
case puint8(c^.Buffer)[2] and $0F of
|
||||
2: sense := eDeviceNotReady;
|
||||
3: sense := eBadSector;
|
||||
@@ -268,14 +290,36 @@ begin
|
||||
end;
|
||||
|
||||
function execute(c : PMSDevice; required : uint32) : TError;
|
||||
var actual : uint32; resultCode : uint8;
|
||||
var actual : uint32; resultCode, opcode : uint8;
|
||||
begin
|
||||
opcode := c^.CBW.Command[0];
|
||||
resultCode := transport(c, actual);
|
||||
execute := eIOError;
|
||||
if resultCode = 1 then execute := sense(c)
|
||||
else if (resultCode = 0) and (actual = required) then execute := eNone;
|
||||
if c^.Offline then execute := eIOTimeout;
|
||||
if c^.Original = nil then execute := eDeviceRemoved;
|
||||
if execute <> eNone then begin
|
||||
io.syslog.log('USBMSC', 'Command failed: opcode='); io.syslog.writeint(opcode);
|
||||
io.syslog.writestring(' error='); io.syslog.writeintln(ord(execute));
|
||||
end;
|
||||
end;
|
||||
|
||||
function sync_cache(c : PMSDevice) : TError;
|
||||
begin
|
||||
sync_cache := eNone;
|
||||
if c^.SyncUnsupported then exit;
|
||||
command(c, $35, 10, 0, false);
|
||||
sync_cache := execute(c, 0);
|
||||
{ Some removable flash devices reject SYNCHRONIZE CACHE with ILLEGAL
|
||||
REQUEST despite accepting WRITE(10). This optional command must not turn
|
||||
an acknowledged data write into a zero-byte failure. Other errors remain
|
||||
failures; do not retry or suppress an unsuccessful WRITE(10). }
|
||||
if sync_cache = eNotSupported then begin
|
||||
c^.SyncUnsupported := true;
|
||||
io.syslog.logln('USBMSC', 'SYNCHRONIZE CACHE unsupported; using device write completion.');
|
||||
sync_cache := eNone;
|
||||
end;
|
||||
end;
|
||||
|
||||
function probe(c : PMSDevice; var sectors, sectorSize : uint32) : boolean;
|
||||
@@ -375,10 +419,10 @@ begin
|
||||
inc(doneBlocks, blocks); inc(offset, bytes);
|
||||
end;
|
||||
end;
|
||||
{ Complete writes only after the device has committed its cache. }
|
||||
{ Flush devices which support it; unsupported removable media use the
|
||||
WRITE command completion contract. }
|
||||
if (error = eNone) and (r^.RequestType = ioWrite) then begin
|
||||
command(c, $35, 10, 0, false);
|
||||
error := execute(c, 0);
|
||||
error := sync_cache(c);
|
||||
end;
|
||||
if error = eReadOnly then c^.Disk^.writable := false;
|
||||
r^.ByteCount := offset;
|
||||
@@ -579,6 +623,7 @@ type
|
||||
Request : TIORequest;
|
||||
C : TMSDevice;
|
||||
Stage, Fault, Controls, DataBytes : uint32;
|
||||
SenseKey : uint8;
|
||||
GoodWrite, GoodRecovery : boolean;
|
||||
end;
|
||||
|
||||
@@ -611,7 +656,8 @@ begin
|
||||
case m^.C.CBW.Command[0] of
|
||||
$12: puint8(t^.Buffer)[0] := 0;
|
||||
$25: begin put32(puint8(t^.Buffer), 8191); put32(puint8(uint32(t^.Buffer)+4), 512); end;
|
||||
$03: begin memset(uint32(t^.Buffer),0,t^.BufferLen); puint8(t^.Buffer)[0]:=$70; puint8(t^.Buffer)[2]:=7; end;
|
||||
$03: begin memset(uint32(t^.Buffer),0,t^.BufferLen); puint8(t^.Buffer)[0]:=$70;
|
||||
if m^.SenseKey = 0 then puint8(t^.Buffer)[2]:=7 else puint8(t^.Buffer)[2]:=m^.SenseKey; end;
|
||||
end;
|
||||
end else begin
|
||||
m^.GoodWrite := true;
|
||||
@@ -708,6 +754,16 @@ begin
|
||||
Assert(transport(@m^.C,actual) = 2, 'short CSW rejected');
|
||||
m^.Fault := 0; command(@m^.C, $35, 10, 0, false);
|
||||
Assert(execute(@m^.C,0) = eNone, 'cache synchronization without data stage');
|
||||
m^.Fault := 3; m^.SenseKey := 3;
|
||||
Assert(sync_cache(@m^.C) = eBadSector, 'flush media errors propagate');
|
||||
Assert(not m^.C.SyncUnsupported, 'media error does not disable flush');
|
||||
m^.Fault := 3; m^.SenseKey := 5;
|
||||
Assert(sync_cache(@m^.C) = eNone, 'unsupported flush accepts completed write');
|
||||
sectors := m^.C.Tag;
|
||||
Assert((sync_cache(@m^.C) = eNone) and (m^.C.Tag = sectors), 'unsupported flush is not repeatedly submitted');
|
||||
m^.Fault := 3; command(@m^.C, $2A, 10, 512, false);
|
||||
Assert(execute(@m^.C,512) = eNotSupported, 'unsupported data write remains a failure');
|
||||
m^.Fault := 0; m^.SenseKey := 0;
|
||||
m^.Disk.sectorSize := 512; m^.Disk.maxSectorCount := 8192;
|
||||
m^.Request.Buffer := m^.C.Buffer; m^.Request.LBA := 8191; m^.Request.SectorCount := 1;
|
||||
Assert(valid_request(@m^.Disk,@m^.Request), 'last sector valid');
|
||||
|
||||
@@ -15,6 +15,7 @@ type
|
||||
TMock = record
|
||||
Data : puint8;
|
||||
Writes, FailWrite, Callbacks, Depth, MaxDepth : uint32;
|
||||
MaxReadSectors, FailReadLBA : uint32;
|
||||
Error : TError;
|
||||
OutOfBounds, FailRead : boolean;
|
||||
end;
|
||||
@@ -27,7 +28,10 @@ begin
|
||||
(request^.SectorCount > device^.maxSectorCount-request^.LBA) then begin
|
||||
m^.OutOfBounds := true; complete_io(request, false, eInvalidArgument); exit;
|
||||
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;
|
||||
complete_io(request,false,eIOError); Dec(m^.Depth); exit;
|
||||
end;
|
||||
@@ -197,6 +201,53 @@ begin
|
||||
mock^.FailWrite := 0;
|
||||
Assert(remove_partition(device,0) = eNone,'delete commits');
|
||||
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(void(mock^.Data)); kfree(void(mock)); kfree(void(volume)); kfree(void(device)); kfree(void(br));
|
||||
io.syslog.log('STORMETA','Passed: '); io.syslog.writeint(passed);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// Copyright 2021 Aaron Hance
|
||||
// Copyright 2026 Aaron Hance
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -357,6 +357,7 @@ const
|
||||
STORAGE_VOLUME_FLAG_FORMATTING = 2;
|
||||
STORAGE_VOLUME_FLAG_INVALIDATING = 4;
|
||||
STORAGE_VOLUME_FLAG_REMOVED = 8;
|
||||
STORAGE_VOLUME_FLAG_FREE_UNKNOWN = 16;
|
||||
|
||||
|
||||
implementation
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// Copyright 2021 Kieron Morris
|
||||
// Copyright 2026 Aaron Hance
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@@ -1500,6 +1501,10 @@ begin
|
||||
asm popf end;
|
||||
while puint32(@wait.Done)^ = 0 do asm hlt end;
|
||||
if wait.Error = eNone then TransferFileSync := wait.BytesDone;
|
||||
if Writing and (wait.Error <> eNone) then begin
|
||||
io.syslog.log('VFS', 'Write failed: error='); io.syslog.writeint(ord(wait.Error));
|
||||
io.syslog.writestring(' bytes='); io.syslog.writeintln(wait.BytesDone);
|
||||
end;
|
||||
end;
|
||||
|
||||
function WriteFile(FileHandle : TFileHandle; Position : uint32; Buffer : puint8; Length : uint32) : uint32;
|
||||
@@ -3287,6 +3292,22 @@ begin
|
||||
debug.tracer.push_trace('driver.storage.vfs.VFS_COMMAND_UMOUNT.exit');
|
||||
end;
|
||||
|
||||
{ Installed at the boot mount phase, after the VFS is ready. Registration
|
||||
happens in the discovery task (usb.attach for USB), not an IRQ or UI callback.
|
||||
Mounting only creates VFS objects; filesystem probing has already completed. }
|
||||
var autoMountWatch : uint32 = 0;
|
||||
|
||||
procedure mount_added_volume(event : TStorageLifecycleEvent; device : PStorage_Device;
|
||||
volume : PStorage_Volume; error : TError; userdata : pointer);
|
||||
begin
|
||||
if (event <> sleVolumeAdded) or (error <> eNone) then exit;
|
||||
if (device = nil) or (volume = nil) then exit;
|
||||
if device^.removed or (volume^.filesystem = nil) then exit;
|
||||
if driver.storage.vol.mgr.volume_is_busy(volume) then exit;
|
||||
if mountDefaultVolume(volume) <> eNone then
|
||||
io.syslog.logln('VFS','Could not automatically mount newly discovered volume.');
|
||||
end;
|
||||
|
||||
{ Auto-mount all discovered volumes into /disk/ and honour persistent mounts }
|
||||
|
||||
procedure auto_mount_volumes();
|
||||
@@ -3311,6 +3332,11 @@ var
|
||||
begin
|
||||
debug.tracer.push_trace('driver.storage.vfs.auto_mount_volumes.enter');
|
||||
|
||||
{ Subscribe even when no disks were present at boot. Avoid duplicate watches
|
||||
if the boot mount pass is invoked again. }
|
||||
if autoMountWatch = 0 then
|
||||
autoMountWatch := driver.storage.vol.mgr.watch_lifecycle(@mount_added_volume, nil);
|
||||
|
||||
volCount := driver.storage.vol.mgr.get_volume_count();
|
||||
if volCount = 0 then begin
|
||||
debug.tracer.push_trace('driver.storage.vfs.auto_mount_volumes.exit');
|
||||
@@ -3657,7 +3683,25 @@ begin
|
||||
obj := GetObjectFromPath('/disk/__RENAMED_TEST__');
|
||||
Assert((obj <> nil) and (obj^.Reference = void(mountVolA)),'renamed mount points to same volume');
|
||||
InvalidateVolume(mountVolA); InvalidateVolume(mountVolB);
|
||||
kfree(void(mountVolA)); kfree(void(mountVolB)); kfree(void(mountFS));
|
||||
{ Exercise real registration notifications after the boot mount pass. }
|
||||
mountDevice := PStorage_Device(kalloc(sizeof(TStorage_Device)));
|
||||
memset(uint32(mountDevice),0,sizeof(TStorage_Device));
|
||||
mountDevice^.writable := true;
|
||||
mountVolA^.device := mountDevice; mountVolB^.device := mountDevice;
|
||||
memcpy(uint32(pchar('__HOTPLUG_TEST__')),uint32(@mountVolA^.labelText[0]),17);
|
||||
mountVolB^.labelText := mountVolA^.labelText;
|
||||
mountVolA^.filesystem := nil;
|
||||
driver.storage.vol.mgr.register_volume(mountDevice,mountVolA);
|
||||
Assert(GetObjectFromPath('/disk/__HOTPLUG_TEST__') = nil,'unrecognized hotplug volume is not mounted');
|
||||
mountVolB^.filesystem := mountFS;
|
||||
driver.storage.vol.mgr.register_volume(mountDevice,mountVolB);
|
||||
obj := GetObjectFromPath('/disk/__HOTPLUG_TEST__');
|
||||
Assert((obj <> nil) and (obj^.Reference = void(mountVolB)),'registration automatically mounts recognized volume');
|
||||
Assert((mountVolB^.lifecycleFlags and STORAGE_VOLUME_FLAG_MOUNTED) <> 0,'hotplug mount publishes mounted state');
|
||||
driver.storage.vol.mgr.delete_volume(mountVolA);
|
||||
driver.storage.vol.mgr.delete_volume(mountVolB);
|
||||
Assert(GetObjectFromPath('/disk/__HOTPLUG_TEST__') = nil,'hotplug removal clears mount');
|
||||
kfree(void(mountDevice)); kfree(void(mountFS));
|
||||
|
||||
watchFired := 0;
|
||||
io.syslog.logln('VFS', 'Unit tests starting...');
|
||||
|
||||
@@ -62,24 +62,47 @@ begin
|
||||
end;
|
||||
|
||||
function countFreeFATClusters(volume : PStorage_Volume; br : PBootRecord) : uint32;
|
||||
var limit, sectorIdx, entryIdx, clusterNum, count : uint32; buffer : puint32;
|
||||
var limit, sectorIdx, entryIdx, clusterNum, count, sectors, batch : uint32; buffer : puint32;
|
||||
begin
|
||||
countFreeFATClusters := $FFFFFFFF;
|
||||
limit := FAT32ClusterLimit(br);
|
||||
if limit <= 2 then exit;
|
||||
buffer := puint32(kalloc(512));
|
||||
volume^.lifecycleFlags := volume^.lifecycleFlags and (not STORAGE_VOLUME_FLAG_FREE_UNKNOWN);
|
||||
{ Match the USB storage transfer buffer: eight sectors per command instead
|
||||
of a CBW/data/CSW round trip for every 512 bytes of a large FAT. }
|
||||
buffer := puint32(kalloc(4096));
|
||||
if buffer = nil then exit;
|
||||
{ FSInfo is optional, and its count is advisory. Validate every signature,
|
||||
the reserved-sector location, and the count before displaying it. }
|
||||
if (br^.FSInfoCluster > 0) and (br^.FSInfoCluster < br^.rsvSectors) then begin
|
||||
if storage_read(volume^.device, volume^.sectorStart + br^.FSInfoCluster, 1, buffer) = eNone then
|
||||
if (buffer[0] = $41615252) and (buffer[121] = $61417272) and
|
||||
(buffer[127] = $AA550000) and (buffer[122] <= limit - 2) then begin
|
||||
countFreeFATClusters := buffer[122]; kfree(buffer); exit;
|
||||
end;
|
||||
end;
|
||||
count := 0; sectorIdx := 0;
|
||||
while sectorIdx * 128 < limit do begin
|
||||
if storage_read(volume^.device, volume^.sectorStart + br^.rsvSectors + sectorIdx, 1, buffer) <> eNone then begin
|
||||
sectors := (limit + 127) div 128;
|
||||
{ Mounting must not wait for an O(disk-size) allocation scan. Small FATs
|
||||
remain cheap to count; larger volumes without FSInfo mount with unknown
|
||||
free space. Allocation always consults the actual FAT, never this hint. }
|
||||
if sectors > 16 then begin
|
||||
volume^.lifecycleFlags := volume^.lifecycleFlags or STORAGE_VOLUME_FLAG_FREE_UNKNOWN;
|
||||
countFreeFATClusters := 0;
|
||||
kfree(buffer); exit;
|
||||
end;
|
||||
while sectorIdx < sectors do begin
|
||||
batch := sectors - sectorIdx;
|
||||
if batch > 8 then batch := 8;
|
||||
if storage_read(volume^.device, volume^.sectorStart + br^.rsvSectors + sectorIdx, batch, buffer) <> eNone then begin
|
||||
kfree(buffer); exit;
|
||||
end;
|
||||
for entryIdx := 0 to 127 do begin
|
||||
for entryIdx := 0 to batch * 128 - 1 do begin
|
||||
clusterNum := sectorIdx * 128 + entryIdx;
|
||||
if clusterNum >= limit then break;
|
||||
if (clusterNum >= 2) and ((buffer[entryIdx] and $0FFFFFFF) = 0) then Inc(count);
|
||||
end;
|
||||
Inc(sectorIdx);
|
||||
Inc(sectorIdx, batch);
|
||||
end;
|
||||
kfree(buffer);
|
||||
countFreeFATClusters := count;
|
||||
|
||||
@@ -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.
|
||||
|
||||
{
|
||||
ProcessManager - Preemptive process management for the Asuro kernel.
|
||||
|
||||
|
||||
@@ -1,3 +1,20 @@
|
||||
// Copyright 2026 Aaron Hance
|
||||
//
|
||||
// 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.
|
||||
|
||||
{
|
||||
@author(Aaron Hance <ah@aaronhance.me>)
|
||||
}
|
||||
unit test;
|
||||
|
||||
interface
|
||||
|
||||
Reference in new issue
Block a user