Files
Asuro/doc/src/driver/storage/driver.storage.mgr.md
T

264 lines
11 KiB
Markdown

<!--
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.
## Overview
The storage manager is the central hub of the Asuro storage subsystem. It maintains a global list of registered storage devices and owns the Phase 2 request-based I/O path. Hardware controller drivers call `register_device` when they discover a device; the manager assigns an ID, initialises the per-device CFIFO, determines whether the device is the boot device, and triggers volume discovery.
The I/O model operates as follows:
- `submit_io` enqueues a `TIORequest` into the device's CFIFO and calls `dispatch_next` to send the head request to hardware.
- `complete_io` is called from ISR or driver completion context to finalise a request, invoke its callback, free the heap copy, and dispatch the next queued request.
- `submit_io_wait` is a blocking wrapper that parks the calling process (`psAwaiting`) until `complete_io` wakes it.
- `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.
Blocking callers use `storage_read` / `storage_write` and check the returned `TError`.
## Dependencies
- `core.ds.cfifo`
- `core.ds.types`
- `driver.storage.iorequest`
- `core.ds.lists`
- `memory.heap`
- `driver.storage.vol.mbr`
- `proc.mgr`
- `proc.types`
- `driver.storage.types`
- `io.syslog`
- `debug.tracer`
- `core.util`, `arch.x86.util`
- `driver.storage.vol.mgr` (implementation uses)
## Boot Registration
Registered with `boot.mgr` as `driver.storage.mgr`, depending on `driver.storage.vfs`.
## Variables
| Name | Type | Description |
|---|---|---|
| `storageDevices` | `PDList` | Global list of all registered `TStorage_Device` records |
| `nextDeviceId` | `uint32` | Monotonically increasing device ID counter |
| `bootDriveByte` | `uint8` | BIOS drive byte set from multiboot info (`$80` = first HDD) |
| `hdCount` | `uint32` | Number of non-ATAPI devices registered so far |
| `atapiCount` | `uint32` | Number of ATAPI devices registered so far |
## Functions and Procedures
### init
```pascal
procedure init();
```
Initialises the storage device list and resets all counters. Must be called before any other storage manager function.
### set_boot_drive_byte
```pascal
procedure set_boot_drive_byte(driveByte : uint8);
```
Records the BIOS drive byte obtained from the multiboot info structure. Must be called once during early boot before any controller driver registers devices.
### get_boot_drive_byte
```pascal
function get_boot_drive_byte() : uint8;
```
Returns the stored BIOS drive byte (`$FF` if not yet set).
### register_device
```pascal
procedure register_device(device : PStorage_Device);
```
Registers a storage device discovered by a controller driver. Assigns a device ID, initialises the request queue and Phase 2 fields, determines whether the device is the boot device by comparing against `bootDriveByte`, and calls `driver.storage.vol.mgr.discover_volumes` to find and register partitions.
ATAPI devices are identified as the boot device if the BIOS drive byte does not correspond to any known HDD index (covers standard ISO boot). HDDs are matched by position (`$80` + HDD index).
### get_device_list
```pascal
function get_device_list() : PDList;
```
Returns the global device list.
### get_device_count
```pascal
function get_device_count() : uint32;
```
Returns the number of registered devices.
### get_device
```pascal
function get_device(index : uint32) : PStorage_Device;
```
Returns the device at `index`, or nil if out of range.
### controller_type_2_string
```pascal
function controller_type_2_string(controllerType : TControllerType) : pchar;
```
Converts a `TControllerType` value to a human-readable string (e.g., `'AHCI'`, `'ATAPI'`).
### read_mbr
```pascal
function read_mbr(device : PStorage_Device) : PMaster_Boot_Record;
```
Reads sector 0 from `device` into a heap-allocated buffer and updates the device's `cachedMBR`. The caller owns the returned pointer and must free it. Returns nil on I/O failure.
### write_mbr
```pascal
procedure write_mbr(device : PStorage_Device; mbr : PMaster_Boot_Record);
```
Writes an MBR record to sector 0 and updates the device cache. Does nothing if the device is not writable.
### get_cached_mbr
```pascal
function get_cached_mbr(device : PStorage_Device) : PMaster_Boot_Record;
```
Returns a pointer to the cached MBR without performing any I/O. The caller must not free the returned pointer. Returns nil if the cache is not populated or `device` is nil.
### write_mbr_async
```pascal
procedure write_mbr_async(device : PStorage_Device; mbr : PMaster_Boot_Record;
callback : TIOCallback; callbackData : pointer);
```
Updates the MBR cache and issues a non-blocking write. `callback` is fired from ISR context when the write completes. If the device is read-only, fires the callback immediately with `eReadOnly`.
### submit_io
```pascal
function submit_io(request : PIORequest) : TError;
```
Non-blocking: validates the request, enqueues it into the device's CFIFO, and calls `dispatch_next`. Returns `eNone` if successfully enqueued. The caller must have set `Callback`/`CallbackData` (async path) or `Caller`/`UserData` (blocking path) before calling. Disables interrupts around the enqueue and dispatch operations.
### submit_io_wait
```pascal
function submit_io_wait(request : PIORequest) : TError;
```
Blocking wrapper for task context only. Sets `sync_io_callback` as the completion callback, calls `submit_io`, parks the calling process in `psAwaiting`, and spins via pointer indirection until `complete_io` sets `Done = 1`. Returns the error code from the completed request.
### complete_io
```pascal
procedure complete_io(request : PIORequest; success : boolean; error : TError);
```
Called from ISR or driver completion context. Sets the request state and error, clears the device's `activeRequest`, invokes the completion callback, frees the heap copy of the request, and dispatches the next queued request.
### storage_read
```pascal
function storage_read(device : PStorage_Device; addr : uint32; sectors : uint32; buffer : puint32) : TError;
```
Blocking read. Builds a `TIORequest` and calls `submit_io_wait`. Must only be called from task context (interrupts enabled).
### storage_write
```pascal
function storage_write(device : PStorage_Device; addr : uint32; sectors : uint32; buffer : puint32) : TError;
```
Blocking write. Builds a `TIORequest` and calls `submit_io_wait`. Must only be called from task context.
### storage_read_async
```pascal
function storage_read_async(device : PStorage_Device; addr : uint32; sectors : uint32;
buffer : puint32; callback : TIOCallback; callbackData : pointer) : TError;
```
Non-blocking read. Enqueues a request and returns immediately. `callback` is fired from ISR context on completion. Safe to call from ISR or task context.
### storage_write_async
```pascal
function storage_write_async(device : PStorage_Device; addr : uint32; sectors : uint32;
buffer : puint32; callback : TIOCallback; callbackData : pointer) : TError;
```
Non-blocking write. Analogous to `storage_read_async`.
## 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`.
- 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.
## 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.