Skip to main content

component/
lib.rs

1//! Component initialization system for Strat9-OS.
2//!
3//! Provides modular component registration and dependency-ordered initialization.
4//! Components are registered at compile time via `#[init_component]` and discovered
5//! at runtime through the `.component_entries` linker section.
6//!
7//! # Usage
8//!
9//! ```rust,no_run
10//! #[component::init_component(bootstrap, priority = 1)]
11//! fn vfs_init() -> Result<(), component::ComponentInitError> {
12//!     vfs::init();
13//!     Ok(())
14//! }
15//!
16//! #[component::init_component(kthread, priority = 2, depends_on = vfs_init)]
17//! fn fs_ext4_init() -> Result<(), component::ComponentInitError> {
18//!     fs_ext4::init();
19//!     Ok(())
20//! }
21//! ```
22//!
23//! Call `component::init_all(InitStage::Bootstrap)` at the appropriate point
24//! in `kernel_main` to run all registered components in dependency order.
25
26#![no_std]
27#![allow(unsafe_code)]
28#![allow(unsafe_op_in_unsafe_fn)]
29
30extern crate alloc;
31
32use alloc::{collections::BTreeMap, string::String, vec, vec::Vec};
33
34pub use component_macro::{init_component, parse_components_toml};
35
36// ========== Stage ==================================================
37
38/// Initialization stages for components.
39///
40/// - `Bootstrap` : Early kernel initialization, before SMP.
41/// - `Kthread`   : After SMP enabled, in kernel-thread context.
42/// - `Hardware`  : After scheduler started, device/driver probing.
43/// - `Process`   : After first user process created.
44#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
45#[repr(u8)]
46pub enum InitStage {
47    Bootstrap = 0,
48    Kthread = 1,
49    Hardware = 2,
50    Process = 3,
51}
52
53// ========== Error ==================================================
54
55/// Errors that a component initializer may return.
56#[derive(Debug, Clone, PartialEq, Eq)]
57pub enum ComponentInitError {
58    UninitializedDependencies(String),
59    InitFailed(&'static str),
60    Unknown,
61}
62
63impl core::fmt::Display for ComponentInitError {
64    /// Performs the fmt operation.
65    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
66        match self {
67            Self::UninitializedDependencies(dep) => {
68                write!(f, "Uninitialized dependency: {dep}")
69            }
70            Self::InitFailed(msg) => write!(f, "Init failed: {msg}"),
71            Self::Unknown => write!(f, "Unknown error"),
72        }
73    }
74}
75
76// ========== ComponentEntry ====================
77
78/// Entry placed in `.component_entries` by `#[init_component]`.
79///
80/// All fields are `'static` because this struct lives in a `#[used] static`.
81///
82/// # Memory layout
83///
84/// `#[repr(C)]` ensures a stable layout so the linker-section scan in
85/// `init_all()` can iterate entries with `ptr::add(1)`.
86#[repr(C)]
87pub struct ComponentEntry {
88    /// Function name as written in source : used for dependency resolution.
89    pub name: &'static str,
90    /// Lifecycle stage this component belongs to.
91    pub stage: InitStage,
92    /// The registered initializer.
93    pub init_fn: fn() -> Result<(), ComponentInitError>,
94    /// `"file!():fn_name"` : for log messages only.
95    pub path: &'static str,
96    /// Lower value = earlier init within the same topological level.
97    pub priority: u32,
98    /// Names of same-stage functions that must complete before this one.
99    pub depends_on: &'static [&'static str],
100}
101
102impl ComponentEntry {
103    /// Construct a `ComponentEntry` (usable in `const` / `static` contexts).
104    pub const fn new(
105        name: &'static str,
106        stage: InitStage,
107        init_fn: fn() -> Result<(), ComponentInitError>,
108        path: &'static str,
109        priority: u32,
110        depends_on: &'static [&'static str],
111    ) -> Self {
112        Self {
113            name,
114            stage,
115            init_fn,
116            path,
117            priority,
118            depends_on,
119        }
120    }
121}
122
123// ========== ComponentInfo ====================
124
125/// Human-readable component metadata (not stored in the linker section).
126#[derive(Debug)]
127pub struct ComponentInfo {
128    pub name: String,
129    pub path: String,
130    pub priority: u32,
131}
132
133impl ComponentInfo {
134    /// Creates a new instance.
135    pub fn new(name: &'static str, path: &'static str, priority: u32) -> Self {
136        Self {
137            name: String::from(name),
138            path: String::from(path),
139            priority,
140        }
141    }
142}
143
144impl PartialEq for ComponentInfo {
145    /// Performs the eq operation.
146    fn eq(&self, o: &Self) -> bool {
147        self.priority == o.priority
148    }
149}
150impl Eq for ComponentInfo {}
151impl Ord for ComponentInfo {
152    /// Performs the cmp operation.
153    fn cmp(&self, o: &Self) -> core::cmp::Ordering {
154        self.priority.cmp(&o.priority)
155    }
156}
157impl PartialOrd for ComponentInfo {
158    /// Performs the partial cmp operation.
159    fn partial_cmp(&self, o: &Self) -> Option<core::cmp::Ordering> {
160        Some(self.cmp(o))
161    }
162}
163
164// ========== Linker section symbols ========================================
165
166// SAFETY: symbols defined by the kernel linker script; bracket the `.component_entries`
167// section.  All objects between them are `ComponentEntry` structs placed by the
168// `#[init_component]` macro.
169#[allow(improper_ctypes)]
170extern "C" {
171    static __start_component_entries: ComponentEntry;
172    static __stop_component_entries: ComponentEntry;
173}
174
175// ========== init_all ========================================
176
177/// Initialize all components registered for `stage` in dependency order.
178///
179/// ## Algorithm (Kahn's topological sort)
180///
181/// 1. Collect every `ComponentEntry` in `.component_entries` that matches the
182///    requested stage.
183/// 2. Build a directed graph from `depends_on` edges (A=>B: A must run first).
184/// 3. Topological sort with `priority` as the tiebreaker when multiple
185///    components become ready at the same time (lower number = earlier).
186/// 4. Execute each initializer in the computed order.
187///
188/// Cross-stage dependencies (names not found in the current stage) are warned
189/// about and skipped : they are assumed to have already run in a prior stage.
190///
191/// Detected cycles are logged as errors; cyclic components are appended in
192/// priority order after the acyclic ones (best-effort fallback).
193#[allow(unsafe_code)]
194pub fn init_all(stage: InitStage) -> Result<(), ComponentInitError> {
195    //  Collect entries for this stage
196    let mut components: Vec<&'static ComponentEntry> = Vec::new();
197
198    // SAFETY: linker guarantees the section boundaries are valid and all
199    // objects in the section are `ComponentEntry` structs placed by the macro.
200    unsafe {
201        let start = &raw const __start_component_entries;
202        let stop = &raw const __stop_component_entries;
203        let mut cur = start;
204        while cur < stop {
205            let entry = &*cur;
206            if entry.stage == stage {
207                components.push(entry);
208            }
209            cur = cur.add(1);
210        }
211    }
212
213    if components.is_empty() {
214        log::info!("[component] No components registered for {:?} stage", stage);
215        return Ok(());
216    }
217
218    //  Build dependency graph ==========================================
219    let n = components.len();
220
221    // name => index in `components`
222    let name_to_idx: BTreeMap<&str, usize> = components
223        .iter()
224        .enumerate()
225        .map(|(i, e)| (e.name, i))
226        .collect();
227
228    // in_degree[i] = number of unresolved same-stage deps of component i
229    let mut in_degree = vec![0usize; n];
230    // adj[i] = indices of components that must run AFTER component i
231    let mut adj: Vec<Vec<usize>> = (0..n).map(|_| Vec::new()).collect();
232
233    for (i, entry) in components.iter().enumerate() {
234        for dep_name in entry.depends_on {
235            if let Some(&dep_idx) = name_to_idx.get(dep_name) {
236                adj[dep_idx].push(i);
237                in_degree[i] += 1;
238            } else {
239                // Not in this stage : assumed handled in a prior stage.
240                log::warn!(
241                    "[component] '{}': dep '{}' not in {:?} stage (cross-stage, skipped)",
242                    entry.name,
243                    dep_name,
244                    stage
245                );
246            }
247        }
248    }
249
250    //  =================== Kahn's topological sort with priority tiebreaker ==================
251    //
252    // Kahn’s Algorithm is a Breadth-First Search (BFS) based approach for performing Topological Sorting on a Directed Acyclic Graph (DAG).
253    //
254    // Instead of recursion (like in DFS), Kahn’s Algorithm uses the concept of in-degree and a queue to determine the order of nodes.
255    // `ready` is sorted ascending by priority so `remove(0)` always gives the component with the smallest priority number (= earliest boot precedence).
256    //
257    let mut ready: Vec<usize> = (0..n).filter(|&i| in_degree[i] == 0).collect();
258    ready.sort_by_key(|&i| components[i].priority);
259
260    let mut ordered: Vec<usize> = Vec::with_capacity(n);
261
262    while !ready.is_empty() {
263        let idx = ready.remove(0);
264        ordered.push(idx);
265
266        let mut newly_ready: Vec<usize> = Vec::new();
267        for &succ in &adj[idx] {
268            in_degree[succ] -= 1;
269            if in_degree[succ] == 0 {
270                newly_ready.push(succ);
271            }
272        }
273
274        // Merge newly-ready nodes while maintaining sort by priority.
275        for nr in newly_ready {
276            let pos = ready.partition_point(|&i| components[i].priority <= components[nr].priority);
277            ready.insert(pos, nr);
278        }
279    }
280
281    //  Cycle detection fallback ====================
282    if ordered.len() != n {
283        log::error!(
284            "[component] Dependency cycle in {:?} stage : cyclic components will run last",
285            stage
286        );
287        let mut remaining: Vec<usize> = (0..n).filter(|i| !ordered.contains(i)).collect();
288        remaining.sort_by_key(|&i| components[i].priority);
289        ordered.extend(remaining);
290    }
291
292    //  5. Execute ====================
293    log::info!(
294        "[component] {:?} stage : {} component(s) to initialize",
295        stage,
296        ordered.len()
297    );
298
299    for idx in &ordered {
300        let e = components[*idx];
301        log::info!(
302            "[component]  [{:3}] {}{}",
303            e.priority,
304            e.name,
305            if e.depends_on.is_empty() {
306                String::new()
307            } else {
308                alloc::format!(" (after: {})", e.depends_on.join(", "))
309            }
310        );
311    }
312
313    let mut failed = 0usize;
314    for idx in ordered {
315        let entry = components[idx];
316        match (entry.init_fn)() {
317            Ok(()) => log::info!("[component]   OK  {}", entry.name),
318            Err(e) => {
319                log::error!("[component]   ERR {}: {}", entry.name, e);
320                failed += 1;
321            }
322        }
323    }
324
325    log::info!("[component] {:?} stage complete ({} failed)", stage, failed);
326    Ok(())
327}
328
329// ========== list_components ==========
330
331/// Return all registered components sorted by stage then priority (for debug).
332#[allow(unsafe_code)]
333pub fn list_components() -> Vec<&'static ComponentEntry> {
334    let mut components: Vec<&'static ComponentEntry> = Vec::new();
335
336    // SAFETY: linker guarantees the section boundaries are valid and all
337    // objects in the section are `ComponentEntry` structs placed by the macro.
338    unsafe {
339        let start = &raw const __start_component_entries;
340        let stop = &raw const __stop_component_entries;
341        let mut cur = start;
342        while cur < stop {
343            components.push(&*cur);
344            cur = cur.add(1);
345        }
346    }
347
348    components.sort_by(|a, b| {
349        a.stage
350            .cmp(&b.stage)
351            .then_with(|| a.priority.cmp(&b.priority))
352            .then_with(|| a.name.cmp(b.name))
353    });
354
355    components
356}
357
358// ========== parse_metadata ==========
359
360/// Parse `Components.toml` at compile time and return the dependency metadata.
361///
362/// Delegates to [`parse_components_toml!`] which searches for `Components.toml`
363/// starting from the calling crate's directory.
364///
365/// Returns `&'static [(&'static str, &'static [&'static str])]`
366/// where each element is `(component_name, &[dep1, dep2, ...])`.
367#[macro_export]
368macro_rules! parse_metadata {
369    () => {
370        component::parse_components_toml!()
371    };
372}