1#![no_std]
53
54#[cfg(test)]
55mod tests;
56
57pub mod node;
58mod parsing;
59pub mod standard_nodes;
60
61#[cfg(feature = "pretty-printing")]
62mod pretty_print;
63
64use node::MemoryReservation;
65use parsing::{BigEndianU32, CStr, FdtData};
66use standard_nodes::{Aliases, Chosen, Cpu, Memory, MemoryRegion, Root};
67
68#[derive(Debug, Clone, Copy, PartialEq)]
70pub enum FdtError {
71 BadMagic,
73 BadPtr,
75 BufferTooSmall,
78}
79
80impl core::fmt::Display for FdtError {
81 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
82 match self {
83 FdtError::BadMagic => write!(f, "bad FDT magic value"),
84 FdtError::BadPtr => write!(f, "an invalid pointer was passed"),
85 FdtError::BufferTooSmall => {
86 write!(f, "the given buffer was too small to contain a FDT header")
87 }
88 }
89 }
90}
91
92#[derive(Clone, Copy)]
98pub struct Fdt<'a> {
99 data: &'a [u8],
100 header: FdtHeader,
101}
102
103impl core::fmt::Debug for Fdt<'_> {
104 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
105 #[cfg(feature = "pretty-printing")]
106 pretty_print::print_node(f, self.root().node, 0)?;
107
108 #[cfg(not(feature = "pretty-printing"))]
109 f.debug_struct("Fdt").finish_non_exhaustive()?;
110
111 Ok(())
112 }
113}
114
115#[derive(Debug, Clone, Copy)]
116#[repr(C)]
117struct FdtHeader {
118 magic: BigEndianU32,
120 totalsize: BigEndianU32,
122 off_dt_struct: BigEndianU32,
124 off_dt_strings: BigEndianU32,
126 off_mem_rsvmap: BigEndianU32,
129 version: BigEndianU32,
131 last_comp_version: BigEndianU32,
133 boot_cpuid_phys: BigEndianU32,
135 size_dt_strings: BigEndianU32,
137 size_dt_struct: BigEndianU32,
139}
140
141impl FdtHeader {
142 fn valid_magic(&self) -> bool {
143 self.magic.get() == 0xd00dfeed
144 }
145
146 fn struct_range(&self) -> core::ops::Range<usize> {
147 let start = self.off_dt_struct.get() as usize;
148 let end = start + self.size_dt_struct.get() as usize;
149
150 start..end
151 }
152
153 fn strings_range(&self) -> core::ops::Range<usize> {
154 let start = self.off_dt_strings.get() as usize;
155 let end = start + self.size_dt_strings.get() as usize;
156
157 start..end
158 }
159
160 fn from_bytes(bytes: &mut FdtData<'_>) -> Option<Self> {
161 Some(Self {
162 magic: bytes.u32()?,
163 totalsize: bytes.u32()?,
164 off_dt_struct: bytes.u32()?,
165 off_dt_strings: bytes.u32()?,
166 off_mem_rsvmap: bytes.u32()?,
167 version: bytes.u32()?,
168 last_comp_version: bytes.u32()?,
169 boot_cpuid_phys: bytes.u32()?,
170 size_dt_strings: bytes.u32()?,
171 size_dt_struct: bytes.u32()?,
172 })
173 }
174}
175
176impl<'a> Fdt<'a> {
177 pub fn new(data: &'a [u8]) -> Result<Self, FdtError> {
182 let mut stream = FdtData::new(data);
183 let header = FdtHeader::from_bytes(&mut stream).ok_or(FdtError::BufferTooSmall)?;
184
185 if !header.valid_magic() {
186 return Err(FdtError::BadMagic);
187 } else if data.len() < header.totalsize.get() as usize {
188 return Err(FdtError::BufferTooSmall);
189 }
190
191 Ok(Self { data, header })
192 }
193
194 pub unsafe fn from_ptr(ptr: *const u8) -> Result<Self, FdtError> {
201 if ptr.is_null() {
202 return Err(FdtError::BadPtr);
203 }
204
205 let tmp_header = core::slice::from_raw_parts(ptr, core::mem::size_of::<FdtHeader>());
206 let real_size =
207 FdtHeader::from_bytes(&mut FdtData::new(tmp_header)).unwrap().totalsize.get() as usize;
208
209 Self::new(core::slice::from_raw_parts(ptr, real_size))
210 }
211
212 pub fn aliases(&self) -> Option<Aliases<'_, 'a>> {
214 Some(Aliases {
215 node: node::find_node(&mut FdtData::new(self.structs_block()), "/aliases", self, None)?,
216 header: self,
217 })
218 }
219
220 pub fn chosen(&self) -> Chosen<'_, 'a> {
222 node::find_node(&mut FdtData::new(self.structs_block()), "/chosen", self, None)
223 .map(|node| Chosen { node })
224 .expect("/chosen is required")
225 }
226
227 pub fn cpus(&self) -> impl Iterator<Item = Cpu<'_, 'a>> {
229 let parent = self.find_node("/cpus").expect("/cpus is a required node");
230
231 parent
232 .children()
233 .filter(|c| c.name.split('@').next().unwrap() == "cpu")
234 .map(move |cpu| Cpu { parent, node: cpu })
235 }
236
237 pub fn memory(&self) -> Memory<'_, 'a> {
239 Memory { node: self.find_node("/memory").expect("requires memory node") }
240 }
241
242 pub fn memory_reservations(&self) -> impl Iterator<Item = MemoryReservation> + 'a {
244 let mut stream = FdtData::new(&self.data[self.header.off_mem_rsvmap.get() as usize..]);
245 let mut done = false;
246
247 core::iter::from_fn(move || {
248 if stream.is_empty() || done {
249 return None;
250 }
251
252 let res = MemoryReservation::from_bytes(&mut stream)?;
253
254 if res.address() as usize == 0 && res.size() == 0 {
255 done = true;
256 return None;
257 }
258
259 Some(res)
260 })
261 }
262
263 pub fn root(&self) -> Root<'_, 'a> {
265 Root { node: self.find_node("/").expect("/ is a required node") }
266 }
267
268 pub fn find_node(&self, path: &str) -> Option<node::FdtNode<'_, 'a>> {
280 let node = node::find_node(&mut FdtData::new(self.structs_block()), path, self, None);
281 node.or_else(|| self.aliases()?.resolve_node(path))
282 }
283
284 pub fn find_compatible(&self, with: &[&str]) -> Option<node::FdtNode<'_, 'a>> {
287 self.all_nodes().find(|n| {
288 n.compatible().and_then(|compats| compats.all().find(|c| with.contains(c))).is_some()
289 })
290 }
291
292 pub fn find_phandle(&self, phandle: u32) -> Option<node::FdtNode<'_, 'a>> {
294 self.all_nodes().find(|n| {
295 n.properties()
296 .find(|p| p.name == "phandle")
297 .and_then(|p| Some(BigEndianU32::from_bytes(p.value)?.get() == phandle))
298 .unwrap_or(false)
299 })
300 }
301
302 pub fn find_all_nodes(&self, path: &'a str) -> impl Iterator<Item = node::FdtNode<'_, 'a>> {
330 let mut done = false;
331 let only_root = path == "/";
332 let valid_path = path.chars().fold(0, |acc, c| acc + if c == '/' { 1 } else { 0 }) >= 1;
333
334 let mut path_split = path.rsplitn(2, '/');
335 let child_name = path_split.next().unwrap();
336 let parent = match path_split.next() {
337 Some("") => Some(self.root().node),
338 Some(s) => node::find_node(&mut FdtData::new(self.structs_block()), s, self, None),
339 None => None,
340 };
341
342 let (parent, bad_parent) = match parent {
343 Some(parent) => (parent, false),
344 None => (self.find_node("/").unwrap(), true),
345 };
346
347 let mut child_iter = parent.children();
348
349 core::iter::from_fn(move || {
350 if done || !valid_path || bad_parent {
351 return None;
352 }
353
354 if only_root {
355 done = true;
356 return self.find_node("/");
357 }
358
359 let mut ret = None;
360
361 #[allow(clippy::while_let_on_iterator)]
362 while let Some(child) = child_iter.next() {
363 if child.name.split('@').next()? == child_name {
364 ret = Some(child);
365 break;
366 }
367 }
368
369 ret
370 })
371 }
372
373 pub fn all_nodes(&self) -> impl Iterator<Item = node::FdtNode<'_, 'a>> {
375 node::all_nodes(self)
376 }
377
378 pub fn strings(&self) -> impl Iterator<Item = &'a str> {
380 let mut block = self.strings_block();
381
382 core::iter::from_fn(move || {
383 if block.is_empty() {
384 return None;
385 }
386
387 let cstr = CStr::new(block)?;
388
389 block = &block[cstr.len() + 1..];
390
391 cstr.as_str()
392 })
393 }
394
395 pub fn total_size(&self) -> usize {
397 self.header.totalsize.get() as usize
398 }
399
400 fn cstr_at_offset(&self, offset: usize) -> CStr<'a> {
401 CStr::new(&self.strings_block()[offset..]).expect("no null terminating string on C str?")
402 }
403
404 fn str_at_offset(&self, offset: usize) -> &'a str {
405 self.cstr_at_offset(offset).as_str().expect("not utf-8 cstr")
406 }
407
408 fn strings_block(&self) -> &'a [u8] {
409 &self.data[self.header.strings_range()]
410 }
411
412 fn structs_block(&self) -> &'a [u8] {
413 &self.data[self.header.struct_range()]
414 }
415}