kernel/device/fdt.rs
1//! Flattened Device Tree (FDT) management module.
2//!
3//! This module provides functionality for managing the Flattened Device Tree (FDT),
4//! which is a data structure used to describe the hardware components of a system.
5//!
6//! # Overview
7//!
8//! The FDT is passed by the bootloader to the kernel and contains information about
9//! the hardware configuration of the system. This module parses and provides access
10//! to that information.
11//!
12//! # Core Components
13//!
14//! - `FdtManager`: A singleton that manages access to the parsed FDT
15//! - `init_fdt()`: Function to initialize the FDT subsystem
16//!
17//! # Usage
18//!
19//! Before using the FDT functions, you must:
20//! 1. Set the FDT address using `FdtManager::set_fdt_addr()`
21//! 2. Call `init_fdt()` to parse the FDT
22//!
23//! After initialization, you can access the FDT using the static manager:
24//! ```
25//! let fdt_manager = FdtManager::get_manager();
26//! if let Some(fdt) = fdt_manager.get_fdt() {
27//! // Use the FDT data
28//! }
29//! ```
30//!
31//! # Implementation Details
32//!
33//! The module uses the `fdt` crate to parse the device tree. It maintains a static
34//! global manager instance to provide access throughout the kernel. The FDT address
35//! is stored in a static variable that must be set before initialization.
36
37
38use core::panic;
39use core::result::Result;
40
41use fdt::{Fdt, FdtError};
42
43use crate::early_println;
44use crate::vm::vmem::MemoryArea;
45
46#[unsafe(link_section = ".data")]
47static mut FDT_ADDR: usize = 0;
48
49static mut MANAGER: FdtManager = FdtManager::new();
50
51
52pub struct FdtManager<'a> {
53 fdt: Option<Fdt<'a>>,
54 relocated: bool,
55}
56
57impl<'a> FdtManager<'a> {
58 const fn new() -> Self {
59 FdtManager {
60 fdt: None,
61 relocated: false,
62 }
63 }
64
65 pub fn init(&mut self, ptr: *const u8) -> Result<(), FdtError> {
66 match unsafe { Fdt::from_ptr(ptr) } {
67 Ok(fdt) => {
68 self.fdt = Some(fdt);
69 }
70 Err(e) => return Err(e),
71 }
72 Ok(())
73 }
74
75 /// Sets the FDT address.
76 ///
77 /// # Safety
78 /// This function modifies a static variable that holds the FDT address.
79 /// Ensure that this function is called before any other FDT-related functions
80 /// to avoid undefined behavior.
81 ///
82 /// # Arguments
83 ///
84 /// * `addr`: The address of the FDT.
85 ///
86 /// # Notes
87 ///
88 /// This function must be called before initializing the FDT manager.
89 /// After once FdtManager is initialized, you cannot change the address.
90 ///
91 pub unsafe fn set_fdt_addr(addr: usize) {
92 unsafe {
93 FDT_ADDR = addr;
94 }
95 }
96
97 pub fn get_fdt(&self) -> Option<&Fdt<'a>> {
98 self.fdt.as_ref()
99 }
100
101 /// Returns a mutable reference to the FdtManager.
102 /// This is unsafe because it allows mutable access to a static variable.
103 /// It should be used with caution to avoid data races.
104 ///
105 /// # Safety
106 /// This function provides mutable access to the static FdtManager instance.
107 /// Ensure that no other references to the manager are active to prevent data races.
108 ///
109 /// # Returns
110 /// A mutable reference to the static FdtManager instance.
111 #[allow(static_mut_refs)]
112 pub unsafe fn get_mut_manager() -> &'static mut FdtManager<'a> {
113 unsafe { &mut MANAGER }
114 }
115
116 /// Returns a reference to the FdtManager.
117 ///
118 /// # Returns
119 /// A reference to the static FdtManager instance.
120 #[allow(static_mut_refs)]
121 pub fn get_manager() -> &'static FdtManager<'a> {
122 unsafe { &MANAGER }
123 }
124
125 /// Relocates the FDT to a new address.
126 ///
127 /// # Safety
128 /// This function modifies the static FDT address and reinitializes the FdtManager.
129 /// Ensure that the new address is valid
130 /// and does not cause memory corruption.
131 ///
132 /// # Parameters
133 /// - `ptr`: The pointer to the new FDT address.
134 ///
135 /// # Panics
136 ///
137 /// This function will panic if the FDT has already been relocated.
138 ///
139 unsafe fn relocate_fdt(&mut self, ptr: *mut u8) {
140 if self.relocated {
141 panic!("FDT already relocated");
142 }
143 // Copy the FDT to the new address
144 let size = self.get_fdt().unwrap().total_size();
145 let old_ptr = unsafe { FDT_ADDR } as *const u8;
146 unsafe { core::ptr::copy_nonoverlapping(old_ptr, ptr, size) };
147
148 // Reinitialize the FDT with the new address
149 match self.init(ptr) {
150 Ok(_) => {
151 self.relocated = true;
152 early_println!("FDT relocated to address: {:#x}", ptr as usize);
153 }
154 Err(e) => {
155 panic!("Failed to relocate FDT: {:?}", e);
156 }
157 }
158 }
159
160 /// Get the initramfs memory area from the device tree
161 ///
162 /// This function searches for the initramfs memory region in the device tree.
163 /// It looks for the initrd-start/end or linux,initrd-start/end properties
164 /// in the /chosen node.
165 ///
166 /// # Returns
167 /// Option<MemoryArea>: If the initramfs region is found, returns Some(MemoryArea),
168 /// otherwise returns None.
169 pub fn get_initramfs(&self) -> Option<MemoryArea> {
170 let fdt = self.get_fdt()?;
171
172 // Find the /chosen node which contains initramfs information
173 let chosen_node = fdt.find_node("/chosen")?;
174
175 // Try to find initramfs start address
176 // First check for "linux,initrd-start" property
177 let start_addr = if let Some(prop) = chosen_node.property("linux,initrd-start") {
178 if prop.value.len() == 8 {
179 let val = u64::from_be_bytes([
180 prop.value[0],
181 prop.value[1],
182 prop.value[2],
183 prop.value[3],
184 prop.value[4],
185 prop.value[5],
186 prop.value[6],
187 prop.value[7],
188 ]);
189 Some(val as usize)
190 } else if prop.value.len() == 4 {
191 let val = u32::from_be_bytes([
192 prop.value[0],
193 prop.value[1],
194 prop.value[2],
195 prop.value[3],
196 ]);
197 Some(val as usize)
198 } else {
199 None
200 }
201 // Then check for "initrd-start" property
202 } else if let Some(prop) = chosen_node.property("initrd-start") {
203 if prop.value.len() >= 4 {
204 let val = u32::from_be_bytes([
205 prop.value[0],
206 prop.value[1],
207 prop.value[2],
208 prop.value[3],
209 ]);
210 Some(val as usize)
211 } else {
212 None
213 }
214 } else {
215 None
216 };
217
218 // Try to find initramfs end address
219 // First check for "linux,initrd-end" property
220 let end_addr = if let Some(prop) = chosen_node.property("linux,initrd-end") {
221 if prop.value.len() == 8 {
222 let val = u64::from_be_bytes([
223 prop.value[0],
224 prop.value[1],
225 prop.value[2],
226 prop.value[3],
227 prop.value[4],
228 prop.value[5],
229 prop.value[6],
230 prop.value[7],
231 ]);
232 Some(val as usize)
233 } else if prop.value.len() == 4 {
234 let val = u32::from_be_bytes([
235 prop.value[0],
236 prop.value[1],
237 prop.value[2],
238 prop.value[3],
239 ]);
240 Some(val as usize)
241 } else {
242 None
243 }
244 // Then check for "initrd-end" property
245 } else if let Some(prop) = chosen_node.property("initrd-end") {
246 if prop.value.len() >= 4 {
247 let val = u32::from_be_bytes([
248 prop.value[0],
249 prop.value[1],
250 prop.value[2],
251 prop.value[3],
252 ]);
253 Some(val as usize)
254 } else {
255 None
256 }
257 } else {
258 None
259 };
260
261 // If we have both start and end addresses, create a memory area
262 if let (Some(start), Some(end)) = (start_addr, end_addr) {
263 if end <= start {
264 return None;
265 }
266
267 let size = end - start;
268 early_println!("[InitRamFS] Found initramfs: start={:#x}, end={:#x}, size={} bytes",
269 start, end, size);
270
271 let memory_area = MemoryArea::new(start, end - 1);
272 return Some(memory_area);
273 }
274
275 early_println!("[InitRamFS] No initramfs found in device tree");
276 None
277 }
278
279 pub fn get_dram_memoryarea(&self) -> Option<MemoryArea> {
280 let fdt = self.get_fdt()?;
281 let memory_node = fdt.find_node("/memory")?;
282
283
284 let reg = memory_node.property("reg")?;
285 if reg.value.len() < 16 {
286 return None;
287 }
288 let reg_start = u64::from_be_bytes([
289 reg.value[0],
290 reg.value[1],
291 reg.value[2],
292 reg.value[3],
293 reg.value[4],
294 reg.value[5],
295 reg.value[6],
296 reg.value[7],
297 ]);
298 let start = reg_start as usize;
299 let size = u64::from_be_bytes([
300 reg.value[8],
301 reg.value[9],
302 reg.value[10],
303 reg.value[11],
304 reg.value[12],
305 reg.value[13],
306 reg.value[14],
307 reg.value[15],
308 ]) as usize;
309 Some(
310 MemoryArea::new(start as usize, start + size - 1) // end is inclusive
311 )
312 }
313
314}
315
316/// Initializes the FDT subsystem.
317pub fn init_fdt() {
318 let fdt_manager = unsafe { FdtManager::get_mut_manager() };
319 let fdt_ptr = unsafe { FDT_ADDR as *const u8 };
320 match fdt_manager.init(fdt_ptr) {
321 Ok(_) => {
322 early_println!("FDT initialized");
323 let fdt = fdt_manager.get_fdt().unwrap();
324
325 match fdt.chosen().bootargs() {
326 Some(bootargs) => early_println!("Bootargs: {}", bootargs),
327 None => early_println!("No bootargs found"),
328 }
329 let model = fdt.root().model();
330 early_println!("Model: {}", model);
331 }
332 Err(e) => {
333 early_println!("FDT error: {:?}", e);
334 }
335 }
336}
337
338/// Relocates the FDT to safe memory.
339///
340/// This function allocates memory for the FDT and relocates it to that address.
341///
342/// # Panic
343///
344/// This function will panic if the FDT has already been relocated or if
345/// the memory allocation fails.
346///
347pub fn relocate_fdt(dest_ptr: *mut u8) -> MemoryArea {
348 let fdt_manager = unsafe { FdtManager::get_mut_manager() };
349 if fdt_manager.relocated {
350 panic!("FDT already relocated");
351 }
352 let size = fdt_manager.get_fdt().unwrap().total_size();
353 unsafe { fdt_manager.relocate_fdt(dest_ptr) };
354 MemoryArea::new(dest_ptr as usize, dest_ptr as usize + size - 1) // return the memory area
355}