kernel/device/
fdt.rs

1//! Flattened Device Tree (FDT) management module.
2//!
3//! This module provides comprehensive functionality for managing the Flattened Device Tree (FDT),
4//! which is a data structure used to describe the hardware components of a system in
5//! various architectures including RISC-V, ARM, and PowerPC.
6//!
7//! # Overview
8//!
9//! The FDT is passed by the bootloader to the kernel and contains critical information about
10//! the hardware configuration of the system. This module parses, relocates, and provides
11//! access to that information through a unified interface that integrates with the kernel's
12//! BootInfo structure.
13//!
14//! # Core Components
15//!
16//! - **`FdtManager`**: A singleton that manages access to the parsed FDT with relocation support
17//! - **`init_fdt()`**: Function to initialize the FDT subsystem from bootloader-provided address
18//! - **`relocate_fdt()`**: Function to safely relocate FDT to kernel-managed memory
19//! - **`create_bootinfo_from_fdt()`**: Function to extract BootInfo from FDT data
20//!
21//! # Boot Integration
22//!
23//! The module integrates with the kernel boot process through the BootInfo structure:
24//!
25//! 1. **Initialization**: Architecture code calls `init_fdt()` with bootloader-provided address
26//! 2. **Relocation**: FDT is moved to safe memory using `relocate_fdt()`
27//! 3. **BootInfo Creation**: `create_bootinfo_from_fdt()` extracts system information
28//! 4. **Kernel Handoff**: BootInfo is passed to `start_kernel()` for unified initialization
29//!
30//! # Memory Management
31//!
32//! The module provides advanced memory management for FDT data:
33//! - **Safe Relocation**: Copies FDT to kernel-controlled memory to prevent corruption
34//! - **Initramfs Handling**: Automatically relocates initramfs to prevent memory conflicts
35//! - **Memory Area Calculation**: Computes usable memory areas excluding kernel and FDT regions
36//!
37//! # Architecture Support
38//!
39//! This module is architecture-agnostic and supports any platform using FDT:
40//! - **RISC-V**: Primary device tree platform
41//! - **ARM/AArch32**: Standard hardware description method
42//! - **AArch64**: Alternative to UEFI for hardware description
43//! - **PowerPC**: Traditional FDT usage
44//! - **Other FDT platforms**: Any architecture supporting device trees
45//!
46//! # Usage
47//!
48//! ## Basic Initialization
49//!
50//! ```rust
51//! // Initialize FDT from bootloader-provided address
52//! init_fdt(fdt_addr);
53//!
54//! // Relocate to safe memory
55//! let dest_ptr = safe_memory_area as *mut u8;
56//! let relocated_area = relocate_fdt(dest_ptr);
57//!
58//! // Create BootInfo with FDT data
59//! let bootinfo = create_bootinfo_from_fdt(cpu_id, relocated_area.start);
60//! ```
61//!
62//! ## FDT Data Access
63//!
64//! ```rust
65//! let fdt_manager = FdtManager::get_manager();
66//! if let Some(fdt) = fdt_manager.get_fdt() {
67//!     // Access FDT nodes and properties
68//!     let memory_node = fdt.find_node("/memory");
69//!     let chosen_node = fdt.find_node("/chosen");
70//! }
71//! ```
72//!
73//! # Hardware Information Extraction
74//!
75//! The module extracts essential hardware information:
76//! - **Memory Layout**: Total system memory from `/memory` nodes
77//! - **Initramfs Location**: Initial filesystem from `/chosen` node
78//! - **Command Line**: Boot arguments from `/chosen/bootargs`
79//! - **Device Tree**: Complete hardware description for device enumeration
80//!
81//! # Safety and Error Handling
82//!
83//! The module provides robust error handling:
84//! - **Validation**: All FDT operations include proper error checking
85//! - **Memory Safety**: Relocation prevents use-after-free and corruption
86//! - **Graceful Degradation**: Missing optional components (like initramfs) are handled gracefully
87//! - **Panic Conditions**: Clear documentation of when functions may panic
88//!
89//! # Implementation Details
90//!
91//! The module uses the `fdt` crate for low-level parsing while providing high-level
92//! abstractions for kernel integration. It maintains a static global manager instance
93//! to provide access throughout the kernel, with careful synchronization to prevent
94//! data races during initialization.
95
96use core::panic;
97use core::result::Result;
98
99use fdt::{Fdt, FdtError};
100
101use crate::early_println;
102use crate::vm::vmem::MemoryArea;
103use crate::{BootInfo, DeviceSource};
104
105use core::cell::UnsafeCell;
106use spin::Once;
107
108struct SyncUnsafeCell<T>(UnsafeCell<T>);
109
110// SAFETY: FDT is initialized during single-threaded boot before SMP is enabled.
111// After initialization, only read-only access is used through get_manager().
112unsafe impl<T> Sync for SyncUnsafeCell<T> {}
113
114// SAFETY: FDT is initialized during single-threaded boot before SMP is enabled.
115// After initialization, only read-only access is used through get_manager().
116static MANAGER: SyncUnsafeCell<FdtManager<'static>> =
117    SyncUnsafeCell(UnsafeCell::new(FdtManager::new()));
118static MANAGER_INITIALIZED: Once = Once::new();
119
120pub struct FdtManager<'a> {
121    fdt: Option<Fdt<'a>>,
122    relocated: bool,
123    original_addr: Option<usize>,
124}
125
126impl<'a> FdtManager<'a> {
127    const fn new() -> Self {
128        FdtManager {
129            fdt: None,
130            relocated: false,
131            original_addr: None,
132        }
133    }
134
135    pub fn init(&mut self, ptr: *const u8) -> Result<(), FdtError> {
136        match unsafe { Fdt::from_ptr(ptr) } {
137            Ok(fdt) => {
138                self.fdt = Some(fdt);
139                self.original_addr = Some(ptr as usize);
140            }
141            Err(e) => return Err(e),
142        }
143        Ok(())
144    }
145
146    pub fn get_fdt(&self) -> Option<&Fdt<'a>> {
147        self.fdt.as_ref()
148    }
149
150    /// Returns a mutable reference to the FdtManager for initialization.
151    ///
152    /// # Safety
153    /// This function is only safe to call during single-threaded boot initialization.
154    /// After SMP is enabled, this should not be called.
155    pub unsafe fn get_mut_manager() -> &'static mut FdtManager<'static> {
156        // SAFETY: We're in single-threaded boot context
157        &mut *MANAGER.0.get()
158    }
159
160    /// Returns a reference to the FdtManager.
161    ///
162    /// # Returns
163    /// A reference to the static FdtManager instance.
164    ///
165    /// # Panics
166    /// Panics if called before FdtManager is initialized via `init_fdt()`.
167    pub fn get_manager() -> &'static FdtManager<'static> {
168        // Wait for initialization to complete
169        if !MANAGER_INITIALIZED.is_completed() {
170            panic!("FdtManager::get_manager() called before init_fdt()");
171        }
172        // SAFETY: After initialization, only read-only access occurs.
173        // The Once guarantees happens-before relationship with init_fdt().
174        unsafe { &*MANAGER.0.get() }
175    }
176
177    /// Relocates the FDT to a new address.
178    ///
179    /// # Safety
180    /// This function modifies the static FDT address and reinitializes the FdtManager.
181    /// Ensure that the new address is valid
182    /// and does not cause memory corruption.
183    ///
184    /// # Parameters
185    /// - `ptr`: The pointer to the new FDT address.
186    ///
187    /// # Panics
188    ///
189    /// This function will panic if the FDT has already been relocated.
190    ///
191    unsafe fn relocate_fdt(&mut self, ptr: *mut u8) {
192        if self.relocated {
193            panic!("FDT already relocated");
194        }
195        // Copy the FDT to the new address
196        let fdt = self.get_fdt().unwrap();
197        let size = fdt.total_size();
198        let old_ptr = self
199            .original_addr
200            .expect("Original FDT address not recorded") as *const u8;
201        unsafe { core::ptr::copy_nonoverlapping(old_ptr, ptr, size) };
202
203        // Reinitialize the FDT with the new address
204        match self.init(ptr) {
205            Ok(_) => {
206                self.relocated = true;
207                early_println!("FDT relocated to address: {:#x}", ptr as usize);
208            }
209            Err(e) => {
210                panic!("Failed to relocate FDT: {:?}", e);
211            }
212        }
213    }
214
215    /// Get the initramfs memory area from the device tree
216    ///
217    /// This function searches for the initramfs memory region in the device tree.
218    /// It looks for the initrd-start/end or linux,initrd-start/end properties
219    /// in the /chosen node.
220    ///
221    /// # Returns
222    /// Option<MemoryArea>: If the initramfs region is found, returns Some(MemoryArea),
223    /// otherwise returns None.
224    pub fn get_initramfs(&self) -> Option<MemoryArea> {
225        let fdt = self.get_fdt()?;
226
227        // Find the /chosen node which contains initramfs information
228        let chosen_node = fdt.find_node("/chosen")?;
229
230        // Try to find initramfs start address
231        // First check for "linux,initrd-start" property
232        let start_addr = if let Some(prop) = chosen_node.property("linux,initrd-start") {
233            if prop.value.len() == 8 {
234                let val = u64::from_be_bytes([
235                    prop.value[0],
236                    prop.value[1],
237                    prop.value[2],
238                    prop.value[3],
239                    prop.value[4],
240                    prop.value[5],
241                    prop.value[6],
242                    prop.value[7],
243                ]);
244                Some(val as usize)
245            } else if prop.value.len() == 4 {
246                let val = u32::from_be_bytes([
247                    prop.value[0],
248                    prop.value[1],
249                    prop.value[2],
250                    prop.value[3],
251                ]);
252                Some(val as usize)
253            } else {
254                None
255            }
256        // Then check for "initrd-start" property
257        } else if let Some(prop) = chosen_node.property("initrd-start") {
258            if prop.value.len() >= 4 {
259                let val = u32::from_be_bytes([
260                    prop.value[0],
261                    prop.value[1],
262                    prop.value[2],
263                    prop.value[3],
264                ]);
265                Some(val as usize)
266            } else {
267                None
268            }
269        } else {
270            None
271        };
272
273        // Try to find initramfs end address
274        // First check for "linux,initrd-end" property
275        let end_addr = if let Some(prop) = chosen_node.property("linux,initrd-end") {
276            if prop.value.len() == 8 {
277                let val = u64::from_be_bytes([
278                    prop.value[0],
279                    prop.value[1],
280                    prop.value[2],
281                    prop.value[3],
282                    prop.value[4],
283                    prop.value[5],
284                    prop.value[6],
285                    prop.value[7],
286                ]);
287                Some(val as usize)
288            } else if prop.value.len() == 4 {
289                let val = u32::from_be_bytes([
290                    prop.value[0],
291                    prop.value[1],
292                    prop.value[2],
293                    prop.value[3],
294                ]);
295                Some(val as usize)
296            } else {
297                None
298            }
299        // Then check for "initrd-end" property
300        } else if let Some(prop) = chosen_node.property("initrd-end") {
301            if prop.value.len() >= 4 {
302                let val = u32::from_be_bytes([
303                    prop.value[0],
304                    prop.value[1],
305                    prop.value[2],
306                    prop.value[3],
307                ]);
308                Some(val as usize)
309            } else {
310                None
311            }
312        } else {
313            None
314        };
315
316        // If we have both start and end addresses, create a memory area
317        if let (Some(start), Some(end)) = (start_addr, end_addr) {
318            if end <= start {
319                return None;
320            }
321
322            let memory_area = MemoryArea::new(start, end - 1);
323            return Some(memory_area);
324        }
325
326        None
327    }
328
329    pub fn get_dram_memoryarea(&self) -> Option<MemoryArea> {
330        let fdt = self.get_fdt()?;
331        let memory_node = fdt.find_node("/memory")?;
332
333        let reg = memory_node.property("reg")?;
334        if reg.value.len() < 16 {
335            return None;
336        }
337        let reg_start = u64::from_be_bytes([
338            reg.value[0],
339            reg.value[1],
340            reg.value[2],
341            reg.value[3],
342            reg.value[4],
343            reg.value[5],
344            reg.value[6],
345            reg.value[7],
346        ]);
347        let start = reg_start as usize;
348        let size = u64::from_be_bytes([
349            reg.value[8],
350            reg.value[9],
351            reg.value[10],
352            reg.value[11],
353            reg.value[12],
354            reg.value[13],
355            reg.value[14],
356            reg.value[15],
357        ]) as usize;
358        Some(
359            MemoryArea::new(start as usize, start + size - 1), // end is inclusive
360        )
361    }
362
363    pub fn get_cpu_count(&self) -> Option<usize> {
364        let fdt = self.get_fdt()?;
365        let cpus = fdt.find_node("/cpus")?;
366
367        let mut count = 0usize;
368        for cpu in cpus.children() {
369            // Only count nodes that explicitly declare `device_type = "cpu"`.
370            let dev_type = match cpu.property("device_type") {
371                Some(dev_type) => dev_type,
372                None => continue,
373            };
374
375            let is_cpu = bytes_to_cstr(dev_type.value)
376                .map(|s| s == "cpu")
377                .unwrap_or(false);
378
379            if !is_cpu {
380                continue;
381            }
382            count += 1;
383        }
384
385        if count == 0 { None } else { Some(count) }
386    }
387}
388
389/// Initializes the FDT subsystem with the given address.
390///
391/// # Arguments
392///
393/// * `addr`: The address of the FDT.
394///
395/// # Safety
396///
397/// This function modifies a static variable that holds the FDT address.
398/// Ensure that this function is called before any other FDT-related functions
399/// to avoid undefined behavior.
400pub fn init_fdt(addr: usize) {
401    let fdt_manager = unsafe { FdtManager::get_mut_manager() };
402    let fdt_ptr = addr as *const u8;
403    match fdt_manager.init(fdt_ptr) {
404        Ok(_) => {
405            early_println!("FDT initialized");
406            let fdt = fdt_manager.get_fdt().unwrap();
407
408            match fdt.chosen().bootargs() {
409                Some(bootargs) => early_println!("Bootargs: {}", bootargs),
410                None => early_println!("No bootargs found"),
411            }
412            let model = fdt.root().model();
413            early_println!("Model: {}", model);
414
415            // Mark initialization as complete to establish happens-before relationship
416            MANAGER_INITIALIZED.call_once(|| {});
417        }
418        Err(e) => {
419            early_println!("FDT error: {:?}", e);
420        }
421    }
422}
423
424/// Relocates the FDT to safe memory.
425///
426/// This function allocates memory for the FDT and relocates it to that address.
427///
428/// # Panic
429///
430/// This function will panic if the FDT has already been relocated or if
431/// the memory allocation fails.
432///
433pub fn relocate_fdt(dest_ptr: *mut u8) -> MemoryArea {
434    let fdt_manager = unsafe { FdtManager::get_mut_manager() };
435    if fdt_manager.relocated {
436        panic!("FDT already relocated");
437    }
438    let size = fdt_manager.get_fdt().unwrap().total_size();
439    unsafe { fdt_manager.relocate_fdt(dest_ptr) };
440    MemoryArea::new(dest_ptr as usize, dest_ptr as usize + size - 1) // return the memory area
441}
442
443/// Create BootInfo from FDT data
444///
445/// This function creates a comprehensive BootInfo structure by extracting essential
446/// system information from the Flattened Device Tree (FDT). It serves as the bridge
447/// between FDT-based boot protocols and the kernel's unified boot interface.
448///
449/// # Architecture Compatibility
450///
451/// This function is architecture-agnostic and can be used by any architecture that
452/// uses FDT for hardware description:
453/// - **RISC-V**: Primary boot protocol
454/// - **ARM/AArch32**: Standard boot method  
455/// - **AArch64**: Alternative to UEFI
456/// - **PowerPC**: Traditional FDT usage
457/// - **Other architectures**: Any FDT-capable platform
458///
459/// # Boot Information Extraction
460///
461/// The function extracts the following information from FDT:
462/// - **Memory Layout**: DRAM size and location from `/memory` node
463/// - **Usable Memory**: Calculates available memory excluding kernel image
464/// - **Initramfs**: Relocates and provides access to initial filesystem
465/// - **Command Line**: Extracts bootargs from `/chosen` node
466/// - **Device Source**: Creates FDT-based device source reference
467///
468/// # Memory Management
469///
470/// The function performs automatic memory management:
471/// 1. **DRAM Discovery**: Parses memory nodes to find total system memory
472/// 2. **Kernel Exclusion**: Calculates usable memory starting after kernel image
473/// 3. **Initramfs Relocation**: Moves initramfs to safe memory location
474/// 4. **Memory Area Updates**: Adjusts usable memory to account for relocations
475///
476/// # Initramfs Handling
477///
478/// If initramfs is present in the FDT `/chosen` node:
479/// - Automatically relocates to prevent overlap with kernel heap
480/// - Updates usable memory area to exclude relocated initramfs
481/// - Provides relocated address in BootInfo for VFS initialization
482///
483/// # Arguments
484///
485/// * `cpu_id` - ID of the current CPU/Hart performing boot
486/// * `relocated_fdt_addr` - Physical address of the relocated FDT in memory
487///
488/// # Returns
489///
490/// A complete BootInfo structure containing all essential boot parameters
491/// extracted from the FDT, ready for use by `start_kernel()`.
492///
493/// # Panics
494///
495/// This function will panic if:
496/// - FDT manager is not properly initialized
497/// - Required memory nodes are missing from FDT
498/// - Memory layout is invalid or corrupted
499///
500/// # Example
501///
502/// ```rust
503/// // Called from architecture-specific boot code
504/// let bootinfo = create_bootinfo_from_fdt(hartid, relocated_fdt_area.start);
505/// start_kernel(&bootinfo);
506/// ```
507///
508pub fn create_bootinfo_from_fdt(cpu_id: usize, relocated_fdt_addr: usize) -> BootInfo {
509    let fdt_manager = FdtManager::get_manager();
510
511    // Get DRAM area
512    let dram_area = fdt_manager
513        .get_dram_memoryarea()
514        .expect("Memory area not found");
515
516    // Calculate usable memory area (simplified for now)
517    let kernel_end = unsafe { &crate::mem::__KERNEL_SPACE_END as *const usize as usize };
518    let mut usable_memory = MemoryArea::new(kernel_end, dram_area.end);
519
520    // Relocate initramfs
521    crate::early_println!("Relocating initramfs...");
522
523    let relocated_initramfs =
524        match crate::fs::vfs_v2::drivers::initramfs::relocate_initramfs(&mut usable_memory) {
525            Ok(area) => Some(area),
526            Err(_e) => None,
527        };
528
529    // Get command line
530    let cmdline = fdt_manager
531        .get_fdt()
532        .and_then(|fdt| fdt.chosen().bootargs());
533
534    let cpu_count = fdt_manager.get_cpu_count().unwrap_or(1);
535
536    BootInfo::new(
537        cpu_id,
538        cpu_count,
539        usable_memory,
540        relocated_initramfs,
541        cmdline,
542        DeviceSource::Fdt(relocated_fdt_addr),
543    )
544}
545
546fn bytes_to_cstr(bytes: &[u8]) -> Option<&str> {
547    let len = bytes.iter().position(|&b| b == 0).unwrap_or(bytes.len());
548    core::str::from_utf8(&bytes[..len]).ok()
549}