Files
Asuro/doc/src/boot/boot.mgr.md
T
t3hn3rd 2d190c221a feature: boot.mgr replace hardcoded init sequence with dependency-driven boot manager
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
2026-03-13 15:37:43 +00:00

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), or nil for 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.

  1. Topological sort — topoSort resolves the dependency graph into a flat execution order using a two-pass algorithm (see below).
  2. Tree print — Logs the resolved boot tree to syslog for diagnostics.
  3. 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:

  1. Sub-pass 1 (non-barriers): Repeatedly scans all entries and emits every eligible non-barrier entry until no more progress is made.
  2. 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).
  • DependsOn can be an exact entry name, a glob pattern containing *, or nil (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 = nil are 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

  1. Each unit adds uses boot.mgr and an initialization section that calls registerBoot() with one of the BOOT_MGR_BARRIER_* constants as the dependency.
  2. kmain calls boot.mgr.run() to execute all registered entries.
  3. 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 .rodata section.
  • The MAX_BOOT_ENTRIES limit of 128 is generous for the current kernel. If the kernel grows beyond this, the constant can be increased.
  • The SortedOrder array is kept at module scope to avoid consuming kernel stack space during run().