Introduce boot.mgr - a self-registering, dependency-ordered boot initialization framework with barrier-based phasing and glob pattern matching. Units now declare their own init procedures and dependencies via registerBoot()/registerBarrier() in their initialization sections, boot.mgr.run() topologically sorts and executes them. - Add src/boot/boot.mgr.pas with topoSort, glob matching, barrier two-pass algorithm, and boot tree printer - Gut src/asuro.pas (kmain): now just stores multiboot info, calls System.init(), boot.mgr.run, and yield() - Add initialization sections with registerBoot() to 53 existing units - Add new units: core.pas, test.pas, driver.storage.boot.pas, app.wasm.pas, app.bsod.pas (factored out of asuro.pas) - Define 9 barrier phases: immediate -> early -> middle -> storage -> bus -> device -> bus.late -> late -> final - Fix xHCI ISR: exclude PRC/WRC from PORTSC change bit clearing to prevent race with port reset busy-wait - Fix driver.bus.usb.core fire_completion_hooks: guard against uint32 underflow when CompletionHookCount = 0 - Move auto_mount_volumes from device barrier to late barrier so it runs after PCI scan discovers storage controllers - Add toolchain/compile_initcalls.sh for boot entry extraction - Add Boot Registration sections to all 51 doc files; create 4 new doc files; rewrite boot.mgr.md; update mkdocs.yml nav
11 KiB
boot.mgr
Boot initialization manager providing self-registering, dependency-ordered unit initialization with barrier-based phasing and glob pattern matching.
Overview
boot.mgr replaces the hardcoded initialization sequence in kmain with a dynamic, dependency-driven boot process. Units register themselves during their Pascal initialization sections by calling registerBoot() with a name, init procedure, splash text, and an optional dependency. When boot.mgr.run() is called from kmain, it performs a topological sort of registrations and executes them in dependency order, respecting barrier boundaries.
Because registration happens before kmain (via FPC_INITIALIZEUNITS), and therefore before the heap exists, all storage uses static allocation. This is one of the justified cases for static arrays in the kernel.
Dependencies
boot.mgr's interface has no uses clause — it relies only on built-in types from the system unit to avoid circular dependencies. The implementation section uses io.syslog and boot.splash for progress output, and includes all silo units that must be compiled into the kernel.
Constants
MAX_BOOT_ENTRIES
128 — Maximum number of boot entries that can be registered. Sized as an upper bound for the total number of kernel units. If exceeded during registration, the system halts immediately (fatal misconfiguration).
Barrier Constants
Named barrier phases that define the boot ordering. Each barrier is registered via registerBarrier in the initialization section of boot.mgr itself, chained so they execute in the order listed:
| Constant | Value | Purpose |
|---|---|---|
BOOT_MGR_BARRIER_IMMEDIATE |
'immediate' |
First phase — no dependency (nil). |
BOOT_MGR_BARRIER_EARLY |
'early' |
Depends on immediate. |
BOOT_MGR_BARRIER_MID |
'middle' |
Depends on early. |
BOOT_MGR_BARRIER_STORAGE |
'storage' |
Depends on middle. Storage subsystem init. |
BOOT_MGR_BARRIER_BUS |
'bus' |
Depends on storage. Bus drivers (PCI, USB core). |
BOOT_MGR_BARRIER_DEVICE |
'device' |
Depends on bus. Class/device drivers (HID, mass storage). |
BOOT_MGR_BARRIER_BUS_LATE |
'bus.late' |
Depends on device. PCI scan and HC loading. |
BOOT_MGR_BARRIER_LATE |
'late' |
Depends on bus.late. Late init (VFS mount, tests). |
BOOT_MGR_BARRIER_FINAL |
'final' |
Depends on late. Final phase (WASM, desktop). |
Execution order: immediate → early → middle → storage → bus → device → bus.late → late → final.
Between each barrier, all non-barrier entries depending on the previous barrier run first. The topological sort's two-pass algorithm ensures this interleaving.
Types
TBootProc / PBootProc
TBootProc = procedure;
Parameterless procedure pointer for boot init callbacks. Units with parameterized init() procedures should create a local wrapper that captures the needed arguments.
TBootEntry / PBootEntry
TBootEntry = record
Name : PChar;
InitProc : TBootProc;
Status : PChar;
DependsOn : PChar;
Barrier : Boolean;
end;
| Field | Description |
|---|---|
| Name | Unique identifier for this entry (e.g. 'arch.x86.gdt'). Used for dependency lookups. |
| InitProc | Parameterless procedure to call during boot. |
| Status | Text displayed on the splash screen while this entry runs. |
| DependsOn | Name of another entry that must run before this one, a glob pattern, or nil for entries with no dependency (roots). |
| Barrier | If true, this entry is deferred until all non-barrier entries at the same dependency level have been emitted. Set by registerBarrier. |
Variables
Entries
var Entries : array[0..MAX_BOOT_ENTRIES - 1] of TBootEntry;
Static registration table populated during initialization sections. BSS-zeroed at startup.
EntryCount
var EntryCount : uint32;
Number of entries currently registered.
SortedOrder
var SortedOrder : TOrderArray;
Module-level scratch array holding the resolved execution order (indices into Entries[]). Kept at module scope rather than on the stack because 512 bytes on the 16 KB kernel stack would leave too little headroom for deeply-nested driver init chains and their ISRs.
Procedures
registerBoot
procedure registerBoot(Name: PChar; InitProc: TBootProc; Status: PChar; DependsOn: PChar);
Register a boot entry. Intended to be called from unit initialization sections, which run before kmain via FPC_INITIALIZEUNITS. Appends the entry to the static Entries array with Barrier := false. Halts the system if MAX_BOOT_ENTRIES is exceeded.
Parameters:
Name— Unique string identifier (must be a string literal or constant).InitProc— Parameterless procedure to call.Status— Splash screen text shown while this entry executes.DependsOn— Name of the entry this depends on, a glob pattern (see below), ornilfor no dependency.
Example:
uses boot.mgr;
procedure boot_init;
begin
{ ... actual init work ... }
end;
initialization
registerBoot('driver.io.serial', @boot_init, 'Initializing serial ports...', nil);
Example with barrier dependency:
initialization
registerBoot('io.syslog', @boot_init, 'Initializing syslog...', BOOT_MGR_BARRIER_EARLY);
registerBarrier
procedure registerBarrier(Name: PChar; InitProc: TBootProc; Status: PChar; DependsOn: PChar);
Register a barrier entry. Identical to registerBoot except the entry's Barrier field is set to true, which causes the topological sort to defer it until all non-barrier entries at the same dependency level have been emitted.
Barriers define ordering phases — e.g. the bus barrier ensures all entries depending on storage have completed before any bus-dependent entries begin. The built-in barriers are registered by boot.mgr itself during its initialization section.
run
procedure run;
Execute all registered boot entries in dependency order. Called once from kmain.
- Topological sort —
topoSortresolves the dependency graph into a flat execution order using a two-pass algorithm (see below). - Tree print — Logs the resolved boot tree to syslog for diagnostics.
- Execute — Walks the sorted order and calls each entry's
InitProc, updating the splash screen.
If a dependency cycle or missing dependency is detected, the affected entries are skipped (they will not execute) and a warning is logged.
Internal Functions
pcharEqual
function pcharEqual(a, b: PChar): Boolean;
Null-safe PChar equality comparison. Does not allocate memory. Used by the dependency resolver to match entry names.
pcharLen
function pcharLen(s: PChar): uint32;
Returns the length of a null-terminated PChar string.
isGlob
function isGlob(pattern: PChar): Boolean;
Returns true if the given pattern contains any * character.
globMatches
function globMatches(name, pattern: PChar): Boolean;
Matches a name against a glob pattern. * matches zero or more of any character and may appear multiple times (e.g. 'arch.*.memory.*'). Uses a greedy two-cursor algorithm with backtracking. No heap allocation.
allGlobEmitted
function allGlobEmitted(pattern: PChar; var Emitted: array of Boolean): Boolean;
Returns true when every entry whose name matches the glob pattern has been emitted. Returns true vacuously if no entries match (nothing to wait for).
isEligible
function isEligible(i: uint32; var Emitted: array of Boolean): Boolean;
Checks whether entry i can be emitted: it must not already be emitted, and its dependency must be satisfied — either nil (no dependency), all glob matches emitted, or the exact named dependency emitted.
findEntry
function findEntry(Name: PChar): uint32;
Looks up a boot entry index by name. Returns the index, or $FFFFFFFF if not found.
topoSort
function topoSort(var Order: TOrderArray): uint32;
Performs topological sort of Entries[] into the output Order[] array. Uses a repeated-scan approach (O(n²), no heap required) with a two-pass barrier algorithm:
- Sub-pass 1 (non-barriers): Repeatedly scans all entries and emits every eligible non-barrier entry until no more progress is made.
- Sub-pass 2 (one barrier): Emits exactly one eligible barrier entry, then returns to sub-pass 1 so that non-barrier entries unlocked by this barrier run before the next barrier fires.
This interleaving prevents barriers from cascading (e.g. immediate → early → middle → late all in one shot) and ensures non-barrier entries are always scheduled between barrier phases.
Returns the number of entries placed into Order[].
entryDepth
function entryDepth(idx: uint32): uint32;
Returns the dependency chain depth of an entry by following its DependsOn links iteratively. Glob dependencies count as depth 1. Capped at 16 to guard against cycles. Used by the tree printer for indentation.
printBootEntriesTree
Logs the resolved boot order as an indented tree to syslog, showing the execution sequence and dependency structure.
Dependency Model
The dependency model is a single-parent tree with optional glob matching:
- Each entry has at most one dependency (
DependsOn). DependsOncan be an exact entry name, a glob pattern containing*, ornil(root).- Transitive ordering is resolved naturally — if A depends on B, and B depends on C, then C runs first, then B, then A.
- Glob dependencies (e.g.
DependsOn = 'arch.*') wait for all matching entries to complete before becoming eligible. This is useful for barrier-like grouping. - Barrier entries are deferred until all non-barrier entries at the same level have run. This creates clean phase boundaries.
- Entries with
DependsOn = nilare roots and can execute in any order relative to each other.
Barrier chain:
nil ← immediate ← early ← middle ← storage ← bus ← device ← bus.late ← late ← final
Example dependency tree:
nil ← driver.io.serial ← io.syslog ← arch.x86.gdt ← arch.x86.idt
nil ← driver.video ← driver.video.lvgl ← boot.splash
immediate ←(barrier)← early ←(barrier)← middle ← [entries at MID phase]
Usage Pattern
- Each unit adds
uses boot.mgrand aninitializationsection that callsregisterBoot()with one of theBOOT_MGR_BARRIER_*constants as the dependency. kmaincallsboot.mgr.run()to execute all registered entries.- The barrier chain guarantees phase ordering: storage controllers init before bus drivers, bus drivers before device/class drivers, etc.
Notes
- Registration order does not matter — the topological sort resolves execution order from dependency declarations.
- All string parameters (
Name,Status,DependsOn) must point to string literals or constants, as no copying is performed. This is safe because Pascal string literals live in the.rodatasection. - The
MAX_BOOT_ENTRIESlimit of 128 is generous for the current kernel. If the kernel grows beyond this, the constant can be increased. - The
SortedOrderarray is kept at module scope to avoid consuming kernel stack space duringrun().