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}