Compare commits

...
Author SHA1 Message Date
Aaron 720ba46630 Improve disk benchmark suites and timing, and fix command launch race 2026-10-31 19:06:46 +00:00
Aaron 8e0ab3da8b Improve storage performance with read pipelining, run lookup, request merging, and counters 2026-10-31 17:32:59 +00:00
Aaron 3d4f846a8f fix(ahci): harden command submission, DMA ownership, and timeout recovery 2026-10-31 16:56:18 +00:00
Aaron d8d51ba5df feat(storage): use volume labels for default mount folders 2026-10-31 16:39:43 +00:00
Aaron d1ecd3fc41 fix(storage): harden disk operations, add volume labels, and stabilize AHCI discovery 2026-10-31 16:30:40 +00:00
Aaron 7e99bc2db1 fat
continuous-integration/drone/push Build is passing
2026-03-24 18:10:04 +00:00
Aaron 096db148d0 fat32: fix pre-alloc cluster leak; add write-handle ref-count
- Remove FAT_WRITE_PREALLOC_BYTES (redundant at typical cluster sizes)
- Simplify fatPreallocClusterCount to always return MAX_CLUSTERS
- FAT32CloseFile: truncate pre-allocated tail clusters back to ByteSize
  on close, freeing FAT chain entries beyond the file's real size
- Add TFATWriteRef + WriteRefs[32] in TFATVolumeCtx and Registered flag
  on TFATOpenFile to track concurrent write handles per dir entry
- fatRegisterWriteHandle / fatUnregisterWriteHandle: CLI-guarded slot scan
  keyed by (DirLoc.SectorLBA, DirLoc.EntryIdx)
- Register on first FAT chain extension in FAT32EnsureCapacityForWrite
- Truncation in FAT32CloseFile skipped unless this is the last writer
  (prevents RunMap corruption in concurrent-open scenarios)
- Add forward decl for fatFreeClusterChain to fix ordering issue
- app.iotest: update default path to /disk/vol2/IOTEST.BIN
2026-03-18 21:05:36 +00:00
Aaron 663bfbb0e4 storage: lifecycle watch system, PFSFormatParams, vol busy/format flags, diskutil improvements
- Add TStorageLifecycleWatch with watch_lifecycle/unwatch_lifecycle API (up to 32 watchers)
- Add notify_lifecycle to broadcast mount/unmount/format events to registered callbacks
- Replace raw puint32 config in format_volume with typed PFSFormatParams
- Add volume_is_busy, volume_is_formatting, volume_set_mounted helpers
- Add lifecycleFlags field to TStorage_Volume for tracking busy/formatting state
- Expand app.diskutil with improved commands and volume state reporting
- Minor improvements to core.util, arch.x86.util, proc.mgr, VFS, and flatfs
- Remove obsolete driver.storage.fs.fat32.old.pas
2026-03-16 23:03:43 +00:00
Aaron aadc59f831 fat32 rewrite and storage stability fixes 2026-03-16 21:18:44 +00:00
Aaron 5d7aacc28f more improvements 2026-03-15 23:58:52 +00:00
Aaron 4810617922 FAT improvements and switch to 2-way associative cache 2026-03-15 19:46:31 +00:00
Aaron 666a011a9f refactor fat32 2026-03-15 15:11:00 +00:00
Aaron a6e81bf5d3 VFS improvements, IO fixes, IO performance improvements. 2026-03-15 14:02:17 +00:00
admin 7ed8012c7e Merge pull request 'feature/boot.mgr' (#71) from feature/boot.mgr into develop
continuous-integration/drone/push Build is passing
Reviewed-on: #71
Reviewed-by: Aaron Hance <[email protected]>
2026-03-14 20:38:39 +00:00
43 changed files with 9903 additions and 4412 deletions

No files matched your search

@@ -219,3 +219,37 @@ Drop-in procedure replacements for callers using the old void-returning API. For
- `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`.
- The blocking path relies on pointer-dereferenced spin (`puint32(@wait.Done)^`) to prevent FPC from caching the `Done` flag in a register across the `hlt` instruction. - The blocking path relies on pointer-dereferenced spin (`puint32(@wait.Done)^`) to prevent FPC from caching the `Done` flag in a register across the `hlt` instruction.
- Devices without `dispatchRead`/`dispatchWrite` set (i.e., drivers not yet migrated to Phase 2) will have `submit_io` return `eDeviceNotFound`. Legacy drivers must be migrated before they can use the new I/O path. - Devices without `dispatchRead`/`dispatchWrite` set (i.e., drivers not yet migrated to Phase 2) will have `submit_io` return `eDeviceNotFound`. Legacy drivers must be migrated before they can use the new I/O path.
## Bounded performance path
The manager merges up to 32 consecutive queued requests when operation, device,
LBA range, and virtual buffer range are contiguous. It never searches past the
queue head or delays a lone request to create a merge. Drivers opt in through
`mergeMaxSectors`; SATA AHCI advertises 65,536 sectors, while other drivers default
to zero (disabled). A merged command delivers one completion to each original
request, in FIFO order. A command error fails every member conservatively.
`storage_get_stats` takes an interrupt-protected snapshot of the device counters.
`DISK info <index>` displays accepted/completed/failed logical requests, hardware
commands, merged requests, queue-full rejections, queue and active high-water
marks, successful read/write sectors, request-size buckets, queue/service ticks,
and FAT metadata cache hits/misses. Queue ticks are accumulated per logical
request; service ticks are accumulated per hardware command. Tick values use
`Counters.c32`, not milliseconds. Counters are unsigned 32-bit values, start at
device registration, and wrap; use snapshot differences over bounded benchmark
intervals. Size buckets are <=8, 9..256, and >256 sectors.
FAT32 blocking and async reads share a pooled transfer engine with batches of up
to four outstanding reads, capped by device queue depth. Destination slices are
disjoint. On any error, the engine drains every accepted request before releasing
its context and reports only the contiguous successful byte prefix. Unaligned
edges and odd DMA addresses use the context's private scratch sector. Writes
remain sequential. Run maps store cumulative file-cluster offsets; an open-file
cursor handles sequential locality and binary search handles random seeks.
`STORPERF` runs memory-backed regression checks (also included in boot unit tests)
for reverse completion order, failures, unaligned reads, run lookup, queue
rejection, merging limits, and callback accounting. It does not access real disks
or measure hardware throughput. Compare counters alongside `STORBENCH` and file
benchmarks on contiguous and fragmented files for actual performance validation.
@@ -58,3 +58,111 @@ Registers the `STORTEST` and `DISKTEST` shell commands with `io.stdio`. Must be
- `run_tests` exercises `makeAbsolutePathFrom`, `resolvePathFrom`, `changeDirectoryFrom`, `GetDirectoryListingFrom`, `newVirtualDirectory`, and `PathValid` including `.` and `..` traversal. - `run_tests` exercises `makeAbsolutePathFrom`, `resolvePathFrom`, `changeDirectoryFrom`, `GetDirectoryListingFrom`, `newVirtualDirectory`, and `PathValid` including `.` and `..` traversal.
- `run_disk_tests` is destructive and will irreversibly wipe the partition table of disk 0. It is never called automatically. - `run_disk_tests` is destructive and will irreversibly wipe the partition table of disk 0. It is never called automatically.
- The `Assert` helper used in both procedures increments `passed` or `failed` counters and logs the test name on failure; passing tests produce no output to avoid noise. - The `Assert` helper used in both procedures increments `passed` or `failed` counters and logs the test name on failure; passing tests produce no output to avoid noise.
## Raw read benchmark
`STORBENCH` is implemented and registered by `driver.storage.bench`. It performs
read-only raw-device I/O; it does not invoke the destructive `DISKTEST` path.
```text
STORBENCH [disk] [MiB]
STORBENCH list
STORBENCH run [disk] [MiB] [KiB/request] [depth] [seq|random] [start_lba]
STORBENCH compare [disk] [MiB]
STORBENCH help
```
Manual (`run`) defaults are disk 0, 128 MiB, 1024 KiB/request, sequential reads, and a depth of
`min(4, hardware depth)` (at least one). Examples:
```text
STORBENCH 0 128 64 4
STORBENCH 0 128 4 16 random
STORBENCH 0 256 1024 1 seq 2048
STORBENCH compare 0 128
```
The comparison runs sequential reads at 4, 64, and 1024 KiB/request, each at depths
1, 4, 16, and 32. The MiB argument applies to each of the twelve cases. Queue depth
may exceed hardware depth to expose software queuing and request merging.
Limits are 1..16384 MiB, 1..1024 KiB/request, and depth 1..32. Request size must be
an exact number of device sectors. Buffers are allocated once per case, with a
separate slice for every outstanding request and a maximum total size of 32 MiB.
The transfer range is clamped to the device. Without an explicit LBA, reads start
at sector 2048 when the full requested range fits there; otherwise they start at
zero. Random mode uses a fixed xorshift seed and samples that range with replacement,
so total bytes read are bounded but unique coverage is not guaranteed.
Reports include successful bytes, elapsed microseconds, MiB/s, IOPS, accepted and
completed request counts, peak outstanding requests, average/p95/max completion
latency, and per-run storage-counter deltas. Latencies are measured from submission
to callback. The p95 is an upper bound from logarithmic microsecond buckets, not an exact
microsecond percentile. Runs shorter than 10 ms are excluded from suite speed comparisons.
No console output occurs inside the measured loop. Any failure stops new work;
already accepted DMA is drained before buffers are freed, and failed requests
contribute no bytes or successful IOPS.
Timing prefers the Hyper-V-compatible VM reference counter (100 ns ticks), detected
through its Hv#1 interface and reference-counter privilege. It needs no calibration.
Other platforms use an invariant TSC calibrated against two complete RTC seconds,
with up to three attempts to tolerate transient RTC updates or scheduling delays.
Calibration requires agreement within 1%; failures include the specific reason.
Unlike delivered timer interrupts, this clock advances while interrupts are masked.
Queue submission restores interrupts between requests. Filesystem caches are
bypassed, but drive and virtual-machine/host caches may still affect results.
Virtual or sparse disks can report throughput above physical-media speed.
Device counter deltas may include concurrent I/O from other processes;
benchmark-owned byte, request, and latency counters do not.
`driver.storage.bench.UnitTest` runs memory-only parser, arithmetic, range,
queue-depth, completion-order, and error-accounting tests at boot. It never reads
or writes a real disk. The clock tests also verify that an advertised VM reference
counter advances with interrupts masked; they never calibrate the RTC at boot.
### Automatic suite and table output
`STORBENCH` without arguments, or `STORBENCH auto [disk] [MiB]`, runs six
read-only profiles. Auto defaults to disk 0 and **32 MiB per profile** (up to
192 MiB total before device-size clamping). One or two numeric arguments select the auto suite: `STORBENCH 2 512` tests
disk 2 with 512 MiB per profile. `STORBENCH list` lists disk indices, capacities,
controllers, sector sizes, and hardware queue depths. Three or more numeric
arguments retain the legacy manual syntax; `run` selects manual mode explicitly.
| Profile | Request size | Queue depth |
| --- | --- | --- |
| Seq large | 1024 KiB | 1 |
| Seq queued | 1024 KiB | queued |
| Seq medium | 64 KiB | queued |
| Random small | 4 KiB | 1 |
| Random queued | 4 KiB | queued |
| Random medium | 64 KiB | queued |
Queued depth is the device hardware depth clamped to 4..16, allowing software
queue testing even on serial devices. A request smaller than a device sector is
increased to one sector. Each profile samples the same byte-span size, with the
existing fixed random seed and range clamping rules.
Auto and compare modes print an aligned ASCII table with request KiB, queue
depth, MiB/s, IOPS, average latency, p95 latency upper bound, and status. Rows are
printed outside the measured loop. Successful runs shorter than 10 ms show `-` metrics and
`SHORT`, and are excluded from best-result and queue-gain summaries. Errors show
`FAILED`, stop the suite after accepted DMA has drained, and leave remaining
profiles unrun. The summary includes successful profiles, bytes actually read,
measured time, best sequential throughput, peak random IOPS, and (in auto mode)
queued/QD1 throughput ratios. These ratios describe the measured cases, including
any controller-cache effects; they are not a hardware rating.
```text
STORBENCH
STORBENCH list
STORBENCH 2 512
STORBENCH auto 0 64
STORBENCH compare 0 32
```
Profiles reuse the same raw device range. Host/drive caches are not flushed, so
later profiles may read warm data. These results are observed raw read throughput,
not a claim of uncached physical-media performance.
+500
View File
File diff suppressed because it is too large. Load diff
+11
View File
@@ -150,6 +150,7 @@ var
device : PStorage_Device; device : PStorage_Device;
volCount : uint32; volCount : uint32;
freeSec : uint32; freeSec : uint32;
stats : TStorageStats;
begin begin
if paramCount(params) < 2 then begin if paramCount(params) < 2 then begin
println('Usage: DISK info <disk>'); println('Usage: DISK info <disk>');
@@ -191,6 +192,16 @@ begin
else else
volCount := 0; volCount := 0;
print('Partitions: '); printint(volCount); println(''); print('Partitions: '); printint(volCount); println('');
storage_get_stats(device,stats);
print('I/O submitted/completed/failed: '); printint(stats.Submitted); print(' / '); printint(stats.Completed); print(' / '); printint(stats.Failed); println('');
print('Commands / merged requests: '); printint(stats.Commands); print(' / '); printint(stats.Merged); println('');
print('Queue peak / active peak / queue full: '); printint(stats.QueuePeak); print(' / '); printint(stats.ActivePeak); print(' / '); printint(stats.QueueFull); println('');
print('Read / written sectors: '); printint(stats.ReadSectors); print(' / '); printint(stats.WriteSectors); println('');
print('Requests <=8 / <=256 / >256 sectors: '); printint(stats.SmallRequests); print(' / '); printint(stats.MediumRequests); print(' / '); printint(stats.LargeRequests); println('');
print('Queue ticks total / max: '); printint(stats.QueueTicks); print(' / '); printint(stats.MaxQueueTicks); println('');
print('Service ticks total / max: '); printint(stats.ServiceTicks); print(' / '); printint(stats.MaxServiceTicks); println('');
print('FAT cache hits / misses: '); printint(stats.FatCacheHits); print(' / '); printint(stats.FatCacheMisses); println('');
end; end;
{ ---------- DISK WIPE ---------- } { ---------- DISK WIPE ---------- }
+642 -167
View File
File diff suppressed because it is too large. Load diff
+69 -9
View File
@@ -195,6 +195,7 @@ procedure do_refresh_load(state: PFileBrowserState); forward;
procedure do_refresh_build(state: PFileBrowserState); forward; procedure do_refresh_build(state: PFileBrowserState); forward;
procedure schedule_refresh(state: PFileBrowserState); forward; procedure schedule_refresh(state: PFileBrowserState); forward;
procedure fb_refresh_poll_cb(tmr: Plv_timer); cdecl; forward; procedure fb_refresh_poll_cb(tmr: Plv_timer); cdecl; forward;
function fb_error_name(err : TError) : pchar; forward;
procedure fb_collect_cb(key: pchar; data: void; ud: void); forward; procedure fb_collect_cb(key: pchar; data: void; ud: void); forward;
procedure do_refresh(state: PFileBrowserState); forward; procedure do_refresh(state: PFileBrowserState); forward;
procedure navigate_to(state: PFileBrowserState; newDir: pchar); forward; procedure navigate_to(state: PFileBrowserState; newDir: pchar); forward;
@@ -582,6 +583,51 @@ begin
puint32(@state^.refresh_pending)^ := 1; puint32(@state^.refresh_pending)^ := 1;
end; end;
function fb_error_name(err : TError) : pchar;
begin
case err of
eNone: fb_error_name := 'eNone';
eUnknown: fb_error_name := 'eUnknown';
eNotSupported: fb_error_name := 'eNotSupported';
eOutOfMemory: fb_error_name := 'eOutOfMemory';
eInvalidArgument: fb_error_name := 'eInvalidArgument';
eFileInUse: fb_error_name := 'eFileInUse';
eFileDoesNotExist: fb_error_name := 'eFileDoesNotExist';
eInvalidFileName: fb_error_name := 'eInvalidFileName';
eInvalidFileExtension: fb_error_name := 'eInvalidFileExtension';
eFilenameTooLong: fb_error_name := 'eFilenameTooLong';
eDirectoryDoesNotExist: fb_error_name := 'eDirectoryDoesNotExist';
eDirectoryAlreadyExists: fb_error_name := 'eDirectoryAlreadyExists';
eDirectoryNotEmpty: fb_error_name := 'eDirectoryNotEmpty';
eDirectoryFull: fb_error_name := 'eDirectoryFull';
eNotADirectory: fb_error_name := 'eNotADirectory';
eWriteOnly: fb_error_name := 'eWriteOnly';
eReadOnly: fb_error_name := 'eReadOnly';
ePermissionDenied: fb_error_name := 'ePermissionDenied';
eInvalidPath: fb_error_name := 'eInvalidPath';
eTooManyOpenFiles: fb_error_name := 'eTooManyOpenFiles';
eInvalidHandle: fb_error_name := 'eInvalidHandle';
eFileNotLoaded: fb_error_name := 'eFileNotLoaded';
eAlreadyExists: fb_error_name := 'eAlreadyExists';
eDiskFull: fb_error_name := 'eDiskFull';
eIOError: fb_error_name := 'eIOError';
eIOTimeout: fb_error_name := 'eIOTimeout';
eIOCancelled: fb_error_name := 'eIOCancelled';
eDeviceNotReady: fb_error_name := 'eDeviceNotReady';
eDeviceRemoved: fb_error_name := 'eDeviceRemoved';
eDeviceNotFound: fb_error_name := 'eDeviceNotFound';
eQueueFull: fb_error_name := 'eQueueFull';
eNoFreeSlot: fb_error_name := 'eNoFreeSlot';
eCorruptFilesystem: fb_error_name := 'eCorruptFilesystem';
eBadSector: fb_error_name := 'eBadSector';
eAlreadyMounted: fb_error_name := 'eAlreadyMounted';
eNotMounted: fb_error_name := 'eNotMounted';
eUnsupportedFilesystem: fb_error_name := 'eUnsupportedFilesystem';
eInvalidPartitionTable: fb_error_name := 'eInvalidPartitionTable';
eVolumeNotFound: fb_error_name := 'eVolumeNotFound';
end;
end;
{ fb_refresh_poll_cb — 50ms LVGL poll timer; when rd_ready=1, build UI } { fb_refresh_poll_cb — 50ms LVGL poll timer; when rd_ready=1, build UI }
procedure fb_refresh_poll_cb(tmr: Plv_timer); cdecl; procedure fb_refresh_poll_cb(tmr: Plv_timer); cdecl;
var var
@@ -604,9 +650,12 @@ begin
case state^.wop_kind of case state^.wop_kind of
1: lv_msgbox_add_text(mbox, 'Could not create folder.'); 1: lv_msgbox_add_text(mbox, 'Could not create folder.');
2: lv_msgbox_add_text(mbox, 'Could not rename item.'); 2: lv_msgbox_add_text(mbox, 'Could not rename item.');
3: lv_msgbox_add_text(mbox, 'Could not create file.');
4: lv_msgbox_add_text(mbox, 'Could not delete item.');
else else
lv_msgbox_add_text(mbox, 'Operation failed.'); lv_msgbox_add_text(mbox, 'Operation failed.');
end; end;
lv_msgbox_add_text(mbox, fb_error_name(state^.wop_result));
ok_b := lv_msgbox_add_footer_button(mbox, 'OK'); ok_b := lv_msgbox_add_footer_button(mbox, 'OK');
lv_obj_add_event_cb(ok_b, @fb_mbox_close_cb, LV_EVENT_CLICKED, mbox); lv_obj_add_event_cb(ok_b, @fb_mbox_close_cb, LV_EVENT_CLICKED, mbox);
end; end;
@@ -619,8 +668,8 @@ begin
kfree(void(state^.wop_path2)); kfree(void(state^.wop_path2));
state^.wop_path2 := nil; state^.wop_path2 := nil;
end; end;
{ For rename, clear selection } { For rename/delete, clear selection }
if state^.wop_kind = 2 then if (state^.wop_kind = 2) or (state^.wop_kind = 4) then
clear_selection(state); clear_selection(state);
schedule_refresh(state); schedule_refresh(state);
end; end;
@@ -1591,15 +1640,16 @@ begin
@fb_delete_done, state); @fb_delete_done, state);
end; end;
{ Async completion callback — runs in ISR/worker context, defer to LVGL } { Async completion callback — runs outside the LVGL thread, so only set flags. }
procedure fb_delete_done(error: TError; userdata: pointer); procedure fb_delete_done(error: TError; userdata: pointer);
var var
state: PFileBrowserState; state: PFileBrowserState;
begin begin
state := PFileBrowserState(userdata); state := PFileBrowserState(userdata);
if state = nil then exit; if state = nil then exit;
state^.op_err := error; state^.wop_kind := 4;
lv_timer_create(@fb_delete_done_timer, 1, state); state^.wop_result := error;
puint32(@state^.wop_done)^ := 1;
end; end;
{ Deferred UI update after delete } { Deferred UI update after delete }
@@ -1860,15 +1910,20 @@ begin
if (error = eNone) and (nfctx^.handle <> 0) then begin if (error = eNone) and (nfctx^.handle <> 0) then begin
{ WriteFileAsync needs a non-nil buffer; Length=0 so it is never read } { WriteFileAsync needs a non-nil buffer; Length=0 so it is never read }
driver.storage.vfs.WriteFileAsync(nfctx^.handle, 0, driver.storage.vfs.WriteFileAsync(nfctx^.handle, 0,
puint8(@nfctx^.err), 0, @fb_newfile_written, nfctx); puint8(@nfctx^.err), 0, nil, @fb_newfile_written, nfctx);
end else begin end else begin
if nfctx^.handle <> 0 then if nfctx^.handle <> 0 then
driver.storage.vfs.CloseFile(nfctx^.handle); driver.storage.vfs.CloseFile(nfctx^.handle);
lv_timer_create(@fb_newfile_done_timer, 1, nfctx); if nfctx^.state <> nil then begin
nfctx^.state^.wop_kind := 3;
nfctx^.state^.wop_result := nfctx^.err;
puint32(@nfctx^.state^.wop_done)^ := 1;
end;
kfree(void(nfctx));
end; end;
end; end;
{ Async step 2: write complete — close handle and schedule UI refresh } { Async step 2: write complete — close handle and defer UI work via poll flags }
procedure fb_newfile_written(error: TError; userdata: pointer); procedure fb_newfile_written(error: TError; userdata: pointer);
var var
nfctx : PNewFileCtx; nfctx : PNewFileCtx;
@@ -1878,7 +1933,12 @@ begin
if error <> eNone then nfctx^.err := error; if error <> eNone then nfctx^.err := error;
if nfctx^.handle <> 0 then if nfctx^.handle <> 0 then
driver.storage.vfs.CloseFile(nfctx^.handle); driver.storage.vfs.CloseFile(nfctx^.handle);
lv_timer_create(@fb_newfile_done_timer, 1, nfctx); if nfctx^.state <> nil then begin
nfctx^.state^.wop_kind := 3;
nfctx^.state^.wop_result := nfctx^.err;
puint32(@nfctx^.state^.wop_done)^ := 1;
end;
kfree(void(nfctx));
end; end;
procedure fb_newfile_done_timer(tmr: Plv_timer); cdecl; procedure fb_newfile_done_timer(tmr: Plv_timer); cdecl;
File diff suppressed because it is too large. Load diff
+2 -1
View File
@@ -26,7 +26,7 @@ uses
boot.mgr, boot.mgr,
debug.tracer, io.stdio, proc.mgr, debug.tracer, io.stdio, proc.mgr,
//progs //progs
app.base64, app.md5sum, app.dhclient, app.vbeinfo, app.testcmd, app.ping, app.meminfo, app.setres, app.base64, app.md5sum, app.dhclient, app.vbeinfo, app.testcmd, app.ping, app.meminfo, app.setres, app.iotest,
//drivers //drivers
driver.storage.ctl.ram, driver.mgr, driver.storage.ctl.ram, driver.mgr,
//network //network
@@ -92,6 +92,7 @@ begin
app.bsod.init(); app.bsod.init();
app.vterminal.init; app.vterminal.init;
app.filebrowser.init(); app.filebrowser.init();
app.iotest.init();
end; end;
Initialization Initialization
+17 -8
View File
@@ -288,7 +288,9 @@ begin
if bootrecord <> nil then begin if bootrecord <> nil then begin
if bootrecord^.boot_sector <> $AA55 then begin if bootrecord^.boot_sector <> $AA55 then begin
println('No valid mbr found, initialising disk...'); println('No valid mbr found, initialising disk...');
driver.storage.vol.mgr.init_disk(device); if driver.storage.vol.mgr.init_disk(device) <> eNone then begin
println('Error: disk initialization failed.'); exit;
end;
end; end;
kfree(puint32(bootrecord)); kfree(puint32(bootrecord));
end; end;
@@ -335,7 +337,9 @@ begin
driver.storage.vol.mbr.setup_partition(@part, lba, sectors); driver.storage.vol.mbr.setup_partition(@part, lba, sectors);
part.system_id := 0; part.system_id := 0;
driver.storage.vol.mgr.add_partition(device, slot, part); if driver.storage.vol.mgr.add_partition(device, slot, part) <> eNone then begin
println('Error: partition creation failed.'); exit;
end;
print('Partition added in slot '); print('Partition added in slot ');
printint(slot); printint(slot);
@@ -407,7 +411,9 @@ begin
exit; exit;
end; end;
driver.storage.vol.mgr.remove_partition(device, slot); if driver.storage.vol.mgr.remove_partition(device, slot) <> eNone then begin
println('Error: partition removal failed.'); exit;
end;
print('Partition slot '); print('Partition slot ');
printint(slot); printint(slot);
@@ -523,11 +529,14 @@ begin
exit; exit;
end; end;
vol^.filesystem := fs; i := 0;
fs^.createCallback(vol, vol^.sectorCount, vol^.sectorStart, nil); while i < driver.storage.vol.mgr.get_volume_count() do begin
if driver.storage.vol.mgr.get_volume(i) = vol then break;
{ Re-probe filesystem so identify picks up the new signature } Inc(i);
driver.storage.fs.mgr.probe_volume(vol); end;
if not driver.storage.vol.mgr.format_volume(device, i, fsName, nil) then begin
println('Error: format failed; the volume was not reported ready.'); exit;
end;
print('Partition '); print('Partition ');
printint(slot); printint(slot);
+14
View File
@@ -44,6 +44,7 @@ function inl(port : uint16) : uint32;
procedure io_wait; procedure io_wait;
procedure __SSE_128_memcpy(source : uint32; dest : uint32); procedure __SSE_128_memcpy(source : uint32; dest : uint32);
procedure __REP_MOVSB_memcpy(source : uint32; dest : uint32; size : uint32);
procedure halt_and_catch_fire(); procedure halt_and_catch_fire();
procedure halt_and_dont_catch_fire(); procedure halt_and_dont_catch_fire();
@@ -113,6 +114,19 @@ asm
MOVAPS [EAX], XMM1 MOVAPS [EAX], XMM1
end; end;
procedure __REP_MOVSB_memcpy(source : uint32; dest : uint32; size : uint32); assembler;
asm
push esi
push edi
cld
mov esi, source
mov edi, dest
mov ecx, size
rep movsb
pop edi
pop esi
end;
function getESP : uint32; function getESP : uint32;
var var
tmp: uint32; tmp: uint32;
+5 -1
View File
@@ -27,6 +27,10 @@ uses
arch.x86.isr.mgr, arch.x86.isr.mgr,
arch.x86.idt; arch.x86.idt;
const
PIT_INPUT_HZ = 1193182;
PIT_DIVISOR = 149;
procedure register(); procedure register();
procedure hook(hook_method : uint32); procedure hook(hook_method : uint32);
procedure unhook(hook_method : uint32); procedure unhook(hook_method : uint32);
@@ -54,7 +58,7 @@ procedure register();
begin begin
if not registered then begin if not registered then begin
asm asm
mov ax, 149 mov ax, PIT_DIVISOR
out $40, al out $40, al
mov al, ah mov al, ah
out $40, al out $40, al
+13
View File
@@ -271,6 +271,7 @@ procedure fpc_shortstr_assign(len: longint; sstr, dstr: Pointer); compilerproc;
any int64/uint64 arithmetic occurs (e.g. sint32 * uint32 promotion). any int64/uint64 arithmetic occurs (e.g. sint32 * uint32 promotion).
Must live in the system unit so the compiler can resolve it. } Must live in the system unit so the compiler can resolve it. }
function fpc_mul_int64(f1, f2: int64): int64; compilerproc; function fpc_mul_int64(f1, f2: int64): int64; compilerproc;
function fpc_mul_dword_to_qword(f1, f2: uint32): qword; compilerproc;
function fpc_div_int64(d, n: int64): int64; compilerproc; function fpc_div_int64(d, n: int64): int64; compilerproc;
function fpc_mod_int64(d, n: int64): int64; compilerproc; function fpc_mod_int64(d, n: int64): int64; compilerproc;
function fpc_mod_qword(d, n: qword): qword; compilerproc; function fpc_mod_qword(d, n: qword): qword; compilerproc;
@@ -523,6 +524,18 @@ begin
fpc_mul_int64 := res; fpc_mul_int64 := res;
end; end;
function fpc_mul_dword_to_qword(f1, f2: uint32): qword; [public, alias: 'FPC_MUL_DWORD_TO_QWORD']; compilerproc;
var res: qword;
begin
asm
MOV EAX, f1
MUL f2
MOV DWORD [res], EAX
MOV DWORD [res+4], EDX
end;
fpc_mul_dword_to_qword := res;
end;
{ ---------- 64-bit division / modulo ---------- } { ---------- 64-bit division / modulo ---------- }
{ Helper: unsigned 64÷64→64 using shift-subtract (binary long division). { Helper: unsigned 64÷64→64 using shift-subtract (binary long division).
+28 -9
View File
@@ -42,6 +42,26 @@ function HexCharToDecimal(hex : char) : uint8;
function abs(x : sint32) : uint32; function abs(x : sint32) : uint32;
implementation implementation
{$ifdef CPU386}
uses
arch.x86.util;
const
MEMCPY_X86_SMALL_COPY_THRESHOLD = 16;
{$endif}
procedure scalar_memcpy(source : uint32; dest : uint32; size : uint32);
var
src, dst : puint8;
i : uint32;
begin
if size = 0 then exit;
for i := 0 to size - 1 do begin
src := puint8(source + i);
dst := puint8(dest + i);
dst^ := src^;
end;
end;
function abs(x : sint32) : uint32; function abs(x : sint32) : uint32;
var var
@@ -123,17 +143,16 @@ begin
end; end;
procedure memcpy(source : uint32; dest : uint32; size : uint32); procedure memcpy(source : uint32; dest : uint32; size : uint32);
var
src, dst : puint8;
i : uint32;
begin begin
if size = 0 then exit; if size = 0 then exit;
for i:=0 to size-1 do begin {$ifdef CPU386}
src:= puint8(source + i); if size < MEMCPY_X86_SMALL_COPY_THRESHOLD then
dst:= puint8(dest + i); scalar_memcpy(source, dest, size)
dst^:= src^; else
end; arch.x86.util.__REP_MOVSB_memcpy(source, dest, size);
{$else}
scalar_memcpy(source, dest, size);
{$endif}
end; end;
function getWord(i : uint32; hi : boolean) : uint16; function getWord(i : uint32; hi : boolean) : uint16;
File diff suppressed because it is too large. Load diff
@@ -21,6 +21,7 @@ unit driver.storage.ctl.ahci.types;
interface interface
uses uses
io.syslog, io.syslog,
driver.storage.types,
driver.mgr, driver.mgr,
driver.types, driver.types,
driver.storage.ctl.ide.types, driver.storage.ctl.ide.types,
@@ -251,11 +252,13 @@ type
{ Callback signature fired from IRQ context when an async I/O command completes. { Callback signature fired from IRQ context when an async I/O command completes.
Must be very short — set a flag or post to a queue. Must be very short — set a flag or post to a queue.
Never call kalloc/kfree/VFS from within this callback. } Never call kalloc/kfree/VFS from within this callback. }
TIOCompletion = procedure(success : boolean; userdata : puint32); TIOCompletion = procedure(error : TError; userdata : puint32);
{ Per-slot pending operation — one entry per HBA command slot } { Per-slot pending operation — one entry per HBA command slot }
TPendingOp = record TPendingOp = record
inUse : boolean; inUse : boolean;
issued : boolean;
started : uint32;
completion : TIOCompletion; completion : TIOCompletion;
userdata : puint32; userdata : puint32;
end; end;
@@ -267,8 +270,16 @@ type
} }
TAHCI_Device = record TAHCI_Device = record
port : PHBA_Port; port : PHBA_Port;
owner : pointer; { stable PAHCI_Controller }
offline : boolean;
recovering : boolean;
recoveryTick : uint32;
recoveryError: TError;
device_type : TDeviceType; device_type : TDeviceType;
ata_info : TIdentResponse; ata_info : TIdentResponse;
supportsNCQ : boolean;
queueDepth : uint32; { safe active depth for this device }
slotCount : uint32; { controller slot count from CAP.NCS + 1 }
command_list : PHBA_CMD_HEADER; command_list : PHBA_CMD_HEADER;
fis : PHBA_FIS; fis : PHBA_FIS;
command_table: PHBA_CMD_TABLE; command_table: PHBA_CMD_TABLE;
@@ -285,6 +296,7 @@ type
} }
TAHCI_Controller = record TAHCI_Controller = record
pci_device : PPCI_Device; pci_device : PPCI_Device;
offline : boolean;
mio: PHBA_Memory; mio: PHBA_Memory;
ata_info : TIdentResponse; ata_info : TIdentResponse;
devices : array[0..31] of TAHCI_Device; devices : array[0..31] of TAHCI_Device;
@@ -335,4 +347,3 @@ end;
// end; // end;
end. end.
@@ -82,38 +82,12 @@ end;
{ ---- Filesystem hook callbacks ---- } { ---- Filesystem hook callbacks ---- }
{ PPReadHook — load entire file into a new kalloc'd buffer.
Sets buffer^ to the data pointer, bytecount^ to the size.
Returns 0 on success, 1 on failure. }
function rd_Read(volume : PStorage_Volume; directory : pchar;
fileName : pchar; buffer : puint32;
bytecount : puint32) : uint32;
var
clean : pchar;
idx : sint32;
dest : puint8;
begin
rd_Read := 1;
clean := stripLeadingSlash(fileName);
if clean = nil then exit;
idx := findFileByName(clean);
if idx < 0 then exit;
{ VFS expects a fresh buffer it can own }
dest := puint8(kalloc(Files[idx].Size));
if dest = nil then exit;
memcpy(uint32(Files[idx].Data), uint32(dest), Files[idx].Size);
buffer^ := uint32(dest);
bytecount^ := Files[idx].Size;
rd_Read := 0;
end;
{ PPReadOffsetHook — read byteCount bytes at offset into caller buffer. { PPReadOffsetHook — read byteCount bytes at offset into caller buffer.
Returns actual bytes copied. } Returns actual bytes copied. }
function rd_ReadOffset(volume : PStorage_Volume; directory : pchar; function rd_ReadOffset(volume : PStorage_Volume; directory : pchar;
fileName : pchar; offset : uint32; fileName : pchar; offset : uint32;
buffer : puint32; byteCount : uint32) : uint32; buffer : puint32; byteCount : uint32;
ctx : pointer) : uint32;
var var
clean : pchar; clean : pchar;
idx : sint32; idx : sint32;
@@ -133,6 +107,20 @@ begin
rd_ReadOffset := copyLen; rd_ReadOffset := copyLen;
end; end;
function rd_FileSize(volume : PStorage_Volume; directory : pchar;
fileName : pchar) : uint32;
var
clean : pchar;
idx : sint32;
begin
rd_FileSize := 0;
clean := stripLeadingSlash(fileName);
if clean = nil then exit;
idx := findFileByName(clean);
if idx < 0 then exit;
rd_FileSize := Files[idx].Size;
end;
{ PPReadDirHook — return a linked list of TDirectory_Entry for all files. { PPReadDirHook — return a linked list of TDirectory_Entry for all files.
VFS owns the returned list and frees each entry^.fileName. } VFS owns the returned list and frees each entry^.fileName. }
function rd_ReadDir(volume : PStorage_Volume; directory : pchar; function rd_ReadDir(volume : PStorage_Volume; directory : pchar;
@@ -269,9 +257,9 @@ begin
{ Set up a minimal filesystem descriptor with our callbacks } { Set up a minimal filesystem descriptor with our callbacks }
memset(uint32(@RAMFS), 0, sizeof(TFilesystem)); memset(uint32(@RAMFS), 0, sizeof(TFilesystem));
RAMFS.sName := 'ramfs'; RAMFS.sName := 'ramfs';
RAMFS.readCallback := @rd_Read;
RAMFS.readDirCallback := @rd_ReadDir; RAMFS.readDirCallback := @rd_ReadDir;
RAMFS.readOffsetCallback := @rd_ReadOffset; RAMFS.readOffsetCallback := @rd_ReadOffset;
RAMFS.fileSizeCallback := @rd_FileSize;
RAMFS.identifyCallback := @rd_Identify; RAMFS.identifyCallback := @rd_Identify;
{ Set up a virtual volume backed by our filesystem } { Set up a virtual volume backed by our filesystem }
@@ -45,10 +45,12 @@ const
ATA_CMD_READ_PIO_EXT = $24; ATA_CMD_READ_PIO_EXT = $24;
ATA_CMD_READ_DMA = $C8; ATA_CMD_READ_DMA = $C8;
ATA_CMD_READ_DMA_EXT = $25; ATA_CMD_READ_DMA_EXT = $25;
ATA_CMD_READ_FPDMA_QUEUED = $60;
ATA_CMD_WRITE_PIO = $30; ATA_CMD_WRITE_PIO = $30;
ATA_CMD_WRITE_PIO_EXT = $34; ATA_CMD_WRITE_PIO_EXT = $34;
ATA_CMD_WRITE_DMA = $CA; ATA_CMD_WRITE_DMA = $CA;
ATA_CMD_WRITE_DMA_EXT = $35; ATA_CMD_WRITE_DMA_EXT = $35;
ATA_CMD_WRITE_FPDMA_QUEUED = $61;
ATA_CMD_CACHE_FLUSH = $E7; ATA_CMD_CACHE_FLUSH = $E7;
ATA_CMD_CACHE_FLUSH_EXT = $EA; ATA_CMD_CACHE_FLUSH_EXT = $EA;
ATA_CMD_PACKET = $A0; ATA_CMD_PACKET = $A0;
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+61 -10
View File
@@ -37,17 +37,17 @@ type
PFileDescriptor = ^TFileDescriptor; PFileDescriptor = ^TFileDescriptor;
TFileDescriptor = record TFileDescriptor = record
InUse : boolean; InUse : boolean;
AsyncRefs : uint32; { in-flight async ops using this descriptor }
Volume : PStorage_Volume; Volume : PStorage_Volume;
Directory : pchar; { directory path within the volume } Directory : pchar; { directory path within the volume }
FileName : pchar; { filename within that directory } FileName : pchar; { filename within that directory }
VFSDir : pchar; { full VFS parent directory path, e.g. '/sys' } VFSDir : pchar; { full VFS parent directory path, e.g. '/sys' }
OpenMode : uint8; { TOpenMode ordinal — avoids VFS type dependency } OpenMode : uint8; { TOpenMode ordinal — avoids VFS type dependency }
DataBuffer : puint32; { pre-loaded data for small files, nil otherwise } DataSize : uint32; { cached file size in bytes (from fileSizeCallback) }
DataSize : uint32; { size of pre-loaded data in bytes }
Loaded : boolean; { true if pre-loaded into DataBuffer }
StreamOff : uint32; { current byte offset for streaming / on-demand reads }
DeviceOps : pointer; { PVFSDeviceOps — non-nil for device FDs } DeviceOps : pointer; { PVFSDeviceOps — non-nil for device FDs }
DeviceData : pointer; { opaque data passed to device callbacks } DeviceData : pointer; { opaque data passed to device callbacks }
FSPrivate : pointer; { opaque FS-level per-file metadata (from openFileCallback) }
FSCloseHook : pointer; { procedure(pointer) — frees FSPrivate on close }
end; end;
PFDTable = ^TFDTable; PFDTable = ^TFDTable;
@@ -71,6 +71,9 @@ function fd_alloc(table : PFDTable) : uint32;
{ Get a pointer to the descriptor for a 1-based handle. { Get a pointer to the descriptor for a 1-based handle.
Returns nil if handle is out of range or the slot is not in use. } Returns nil if handle is out of range or the slot is not in use. }
function fd_get(table : PFDTable; handle : uint32) : PFileDescriptor; function fd_get(table : PFDTable; handle : uint32) : PFileDescriptor;
function fd_begin_async(entry : PFileDescriptor) : boolean;
procedure fd_end_async(entry : PFileDescriptor);
function fd_has_async(entry : PFileDescriptor) : boolean;
{ Close a single file descriptor: free its buffers and core.strings, { Close a single file descriptor: free its buffers and core.strings,
mark the slot as not-in-use. mark the slot as not-in-use.
@@ -81,6 +84,10 @@ function fd_close(table : PFDTable; handle : uint32) : boolean;
Used during process cleanup. } Used during process cleanup. }
procedure fd_close_all(table : PFDTable); procedure fd_close_all(table : PFDTable);
{ Close all file descriptors in the table that reference a specific volume.
Called when a volume is removed to prevent dangling pointers. }
procedure fd_close_for_volume(table : PFDTable; vol : PStorage_Volume);
implementation implementation
function fd_table_new : PFDTable; function fd_table_new : PFDTable;
@@ -93,15 +100,20 @@ begin
fd_table_new := tbl; fd_table_new := tbl;
end; end;
type
TFSPrivateClose = procedure(data : pointer);
procedure fd_free_entry(entry : PFileDescriptor); procedure fd_free_entry(entry : PFileDescriptor);
begin begin
if entry = nil then exit; if entry = nil then exit;
if not entry^.InUse then exit; if not entry^.InUse then exit;
if entry^.DataBuffer <> nil then begin { Free FS-level per-file metadata first }
kfree(entry^.DataBuffer); if (entry^.FSPrivate <> nil) and (entry^.FSCloseHook <> nil) then
entry^.DataBuffer := nil; TFSPrivateClose(entry^.FSCloseHook)(entry^.FSPrivate);
end; entry^.FSPrivate := nil;
entry^.FSCloseHook := nil;
if entry^.Directory <> nil then begin if entry^.Directory <> nil then begin
kfree(void(entry^.Directory)); kfree(void(entry^.Directory));
entry^.Directory := nil; entry^.Directory := nil;
@@ -116,9 +128,8 @@ begin
end; end;
entry^.InUse := false; entry^.InUse := false;
entry^.Loaded := false; entry^.AsyncRefs := 0;
entry^.DataSize := 0; entry^.DataSize := 0;
entry^.StreamOff := 0;
entry^.DeviceOps := nil; entry^.DeviceOps := nil;
entry^.DeviceData := nil; entry^.DeviceData := nil;
end; end;
@@ -153,6 +164,34 @@ begin
fd_get := @table^.Entries[handle - 1]; fd_get := @table^.Entries[handle - 1];
end; end;
function fd_begin_async(entry : PFileDescriptor) : boolean;
begin
fd_begin_async := false;
if entry = nil then exit;
asm pushf; cli end;
if entry^.InUse then begin
entry^.AsyncRefs := entry^.AsyncRefs + 1;
fd_begin_async := true;
end;
asm popf end;
end;
procedure fd_end_async(entry : PFileDescriptor);
begin
if entry = nil then exit;
asm pushf; cli end;
if entry^.AsyncRefs > 0 then
entry^.AsyncRefs := entry^.AsyncRefs - 1;
asm popf end;
end;
function fd_has_async(entry : PFileDescriptor) : boolean;
begin
fd_has_async := false;
if entry = nil then exit;
fd_has_async := entry^.AsyncRefs > 0;
end;
function fd_close(table : PFDTable; handle : uint32) : boolean; function fd_close(table : PFDTable; handle : uint32) : boolean;
begin begin
fd_close := false; fd_close := false;
@@ -174,4 +213,16 @@ begin
end; end;
end; end;
procedure fd_close_for_volume(table : PFDTable; vol : PStorage_Volume);
var
i : uint32;
begin
if table = nil then exit;
if vol = nil then exit;
for i := 0 to MAX_FDS - 1 do begin
if table^.Entries[i].InUse and (table^.Entries[i].Volume = vol) then
fd_free_entry(@table^.Entries[i]);
end;
end;
end. end.
Loaded 20 of 43 files, more files were not shown because too many files have changed in this diff. Show more