|
15 | 15 | //! |
16 | 16 | //! [Flattened Device Tree (FDT)]: https://devicetree-specification.readthedocs.io/en/latest/chapter5-flattened-format.html |
17 | 17 |
|
| 18 | +use crate::error::{FdtError, FdtErrorKind}; |
| 19 | +mod node; |
| 20 | +use core::ffi::CStr; |
18 | 21 | use core::mem::offset_of; |
19 | 22 | use core::ptr; |
20 | 23 |
|
| 24 | +pub use node::FdtNode; |
21 | 25 | use zerocopy::byteorder::big_endian; |
22 | 26 | use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout, Unaligned}; |
23 | 27 |
|
24 | | -use crate::error::{FdtError, FdtErrorKind}; |
25 | | - |
26 | 28 | /// Version of the FDT specification supported by this library. |
27 | 29 | const FDT_VERSION: u32 = 17; |
| 30 | +pub(crate) const FDT_TAGSIZE: usize = size_of::<u32>(); |
28 | 31 | pub(crate) const FDT_MAGIC: u32 = 0xd00d_feed; |
| 32 | +pub(crate) const FDT_BEGIN_NODE: u32 = 0x1; |
| 33 | +pub(crate) const FDT_END_NODE: u32 = 0x2; |
| 34 | +pub(crate) const FDT_END: u32 = 0x9; |
| 35 | +pub(crate) const FDT_PROP: u32 = 0x3; |
| 36 | +pub(crate) const FDT_NOP: u32 = 0x4; |
29 | 37 |
|
30 | 38 | #[repr(C, packed)] |
31 | 39 | #[derive(Debug, Copy, Clone, FromBytes, IntoBytes, Unaligned, Immutable, KnownLayout)] |
@@ -100,6 +108,31 @@ pub struct Fdt<'a> { |
100 | 108 | pub(crate) data: &'a [u8], |
101 | 109 | } |
102 | 110 |
|
| 111 | +/// A token in the device tree structure. |
| 112 | +#[derive(Debug, PartialEq, Eq)] |
| 113 | +pub(crate) enum FdtToken { |
| 114 | + BeginNode, |
| 115 | + EndNode, |
| 116 | + Prop, |
| 117 | + Nop, |
| 118 | + End, |
| 119 | +} |
| 120 | + |
| 121 | +impl TryFrom<u32> for FdtToken { |
| 122 | + type Error = u32; |
| 123 | + |
| 124 | + fn try_from(value: u32) -> Result<Self, Self::Error> { |
| 125 | + match value { |
| 126 | + FDT_BEGIN_NODE => Ok(FdtToken::BeginNode), |
| 127 | + FDT_END_NODE => Ok(FdtToken::EndNode), |
| 128 | + FDT_PROP => Ok(FdtToken::Prop), |
| 129 | + FDT_NOP => Ok(FdtToken::Nop), |
| 130 | + FDT_END => Ok(FdtToken::End), |
| 131 | + _ => Err(value), |
| 132 | + } |
| 133 | + } |
| 134 | +} |
| 135 | + |
103 | 136 | impl<'a> Fdt<'a> { |
104 | 137 | /// Creates a new `Fdt` from the given byte slice. |
105 | 138 | /// |
@@ -276,6 +309,173 @@ impl<'a> Fdt<'a> { |
276 | 309 | pub fn boot_cpuid_phys(&self) -> u32 { |
277 | 310 | self.header().boot_cpuid_phys() |
278 | 311 | } |
| 312 | + |
| 313 | + /// Returns the root node of the device tree. |
| 314 | + /// |
| 315 | + /// # Errors |
| 316 | + /// |
| 317 | + /// Returns an [`FdtErrorKind::InvalidLength`] if the FDT structure is |
| 318 | + /// truncated or an [`FdtErrorKind::BadToken`] if the first token is not |
| 319 | + /// `FDT_BEGIN_NODE`. |
| 320 | + /// |
| 321 | + /// # Examples |
| 322 | + /// |
| 323 | + /// ``` |
| 324 | + /// # use dtoolkit::fdt::Fdt; |
| 325 | + /// # let dtb = include_bytes!("../../tests/dtb/test.dtb"); |
| 326 | + /// let fdt = Fdt::new(dtb).unwrap(); |
| 327 | + /// let root = fdt.root().unwrap(); |
| 328 | + /// assert_eq!(root.name().unwrap(), ""); |
| 329 | + /// ``` |
| 330 | + pub fn root(&self) -> Result<FdtNode<'_>, FdtError> { |
| 331 | + let offset = self.header().off_dt_struct() as usize; |
| 332 | + let token = self.read_token(offset)?; |
| 333 | + if token != FdtToken::BeginNode { |
| 334 | + return Err(FdtError::new( |
| 335 | + FdtErrorKind::BadToken(FDT_BEGIN_NODE), |
| 336 | + offset, |
| 337 | + )); |
| 338 | + } |
| 339 | + Ok(FdtNode { fdt: self, offset }) |
| 340 | + } |
| 341 | + |
| 342 | + /// Finds a node by its path. |
| 343 | + /// |
| 344 | + /// # Performance |
| 345 | + /// |
| 346 | + /// This method traverses the device tree and its performance is linear in |
| 347 | + /// the number of nodes in the path. If you need to call this often, |
| 348 | + /// consider using |
| 349 | + /// [`DeviceTree::from_fdt`](crate::model::DeviceTree::from_fdt) |
| 350 | + /// first. [`DeviceTree`](crate::model::DeviceTree) stores the nodes in a |
| 351 | + /// hash map for constant-time lookup. |
| 352 | + /// |
| 353 | + /// # Examples |
| 354 | + /// |
| 355 | + /// ``` |
| 356 | + /// # use dtoolkit::fdt::Fdt; |
| 357 | + /// # let dtb = include_bytes!("../../tests/dtb/test_traversal.dtb"); |
| 358 | + /// let fdt = Fdt::new(dtb).unwrap(); |
| 359 | + /// let node = fdt.find_node("/a/b/c").unwrap().unwrap(); |
| 360 | + /// assert_eq!(node.name().unwrap(), "c"); |
| 361 | + /// ``` |
| 362 | + #[must_use] |
| 363 | + pub fn find_node(&self, path: &str) -> Option<Result<FdtNode<'_>, FdtError>> { |
| 364 | + if !path.starts_with('/') { |
| 365 | + return None; |
| 366 | + } |
| 367 | + let mut current_node = match self.root() { |
| 368 | + Ok(node) => node, |
| 369 | + Err(e) => return Some(Err(e)), |
| 370 | + }; |
| 371 | + if path == "/" { |
| 372 | + return Some(Ok(current_node)); |
| 373 | + } |
| 374 | + for component in path.split('/').filter(|s| !s.is_empty()) { |
| 375 | + match current_node.children().find(|child| { |
| 376 | + child |
| 377 | + .as_ref() |
| 378 | + .is_ok_and(|c| c.name().is_ok_and(|n| n == component)) |
| 379 | + }) { |
| 380 | + Some(Ok(node)) => current_node = node, |
| 381 | + Some(Err(e)) => return Some(Err(e)), |
| 382 | + None => return None, |
| 383 | + } |
| 384 | + } |
| 385 | + Some(Ok(current_node)) |
| 386 | + } |
| 387 | + |
| 388 | + pub(crate) fn read_token(&self, offset: usize) -> Result<FdtToken, FdtError> { |
| 389 | + let val = big_endian::U32::ref_from_prefix(&self.data[offset..]) |
| 390 | + .map(|(val, _)| val.get()) |
| 391 | + .map_err(|_e| FdtError::new(FdtErrorKind::InvalidLength, offset))?; |
| 392 | + FdtToken::try_from(val).map_err(|t| FdtError::new(FdtErrorKind::BadToken(t), offset)) |
| 393 | + } |
| 394 | + |
| 395 | + /// Return a NUL-terminated string from a given offset. |
| 396 | + pub(crate) fn string_at_offset( |
| 397 | + &self, |
| 398 | + offset: usize, |
| 399 | + end: Option<usize>, |
| 400 | + ) -> Result<&'a str, FdtError> { |
| 401 | + let slice = match end { |
| 402 | + Some(end) => self.data.get(offset..end), |
| 403 | + None => self.data.get(offset..), |
| 404 | + }; |
| 405 | + let slice = slice.ok_or(FdtError::new(FdtErrorKind::InvalidOffset, offset))?; |
| 406 | + |
| 407 | + match CStr::from_bytes_until_nul(slice).map(|val| val.to_str()) { |
| 408 | + Ok(Ok(val)) => Ok(val), |
| 409 | + _ => Err(FdtError::new(FdtErrorKind::InvalidString, offset)), |
| 410 | + } |
| 411 | + } |
| 412 | + |
| 413 | + pub(crate) fn find_string_end(&self, start: usize) -> Result<usize, FdtError> { |
| 414 | + let mut offset = start; |
| 415 | + loop { |
| 416 | + match self.data.get(offset) { |
| 417 | + Some(0) => return Ok(offset + 1), |
| 418 | + Some(_) => {} |
| 419 | + None => return Err(FdtError::new(FdtErrorKind::InvalidString, start)), |
| 420 | + } |
| 421 | + offset += 1; |
| 422 | + } |
| 423 | + } |
| 424 | + |
| 425 | + pub(crate) fn next_sibling_offset(&self, mut offset: usize) -> Result<usize, FdtError> { |
| 426 | + offset += FDT_TAGSIZE; // Skip FDT_BEGIN_NODE |
| 427 | + |
| 428 | + // Skip node name |
| 429 | + offset = self.find_string_end(offset)?; |
| 430 | + offset = Self::align_tag_offset(offset); |
| 431 | + |
| 432 | + // Skip properties |
| 433 | + loop { |
| 434 | + let token = self.read_token(offset)?; |
| 435 | + match token { |
| 436 | + FdtToken::Prop => { |
| 437 | + offset += FDT_TAGSIZE; // skip FDT_PROP |
| 438 | + offset = self.next_property_offset(offset)?; |
| 439 | + } |
| 440 | + FdtToken::Nop => offset += FDT_TAGSIZE, |
| 441 | + _ => break, |
| 442 | + } |
| 443 | + } |
| 444 | + |
| 445 | + // Skip child nodes |
| 446 | + loop { |
| 447 | + let token = self.read_token(offset)?; |
| 448 | + match token { |
| 449 | + FdtToken::BeginNode => { |
| 450 | + offset = self.next_sibling_offset(offset)?; |
| 451 | + } |
| 452 | + FdtToken::EndNode => { |
| 453 | + offset += FDT_TAGSIZE; |
| 454 | + break; |
| 455 | + } |
| 456 | + FdtToken::Nop => offset += FDT_TAGSIZE, |
| 457 | + _ => {} |
| 458 | + } |
| 459 | + } |
| 460 | + |
| 461 | + Ok(offset) |
| 462 | + } |
| 463 | + |
| 464 | + pub(crate) fn next_property_offset(&self, mut offset: usize) -> Result<usize, FdtError> { |
| 465 | + let len = big_endian::U32::ref_from_prefix(&self.data[offset..]) |
| 466 | + .map(|(val, _)| val.get()) |
| 467 | + .map_err(|_e| FdtError::new(FdtErrorKind::InvalidLength, offset))? |
| 468 | + as usize; |
| 469 | + offset += FDT_TAGSIZE; // skip value length |
| 470 | + offset += FDT_TAGSIZE; // skip name offset |
| 471 | + offset += len; // skip property value |
| 472 | + |
| 473 | + Ok(Self::align_tag_offset(offset)) |
| 474 | + } |
| 475 | + |
| 476 | + pub(crate) fn align_tag_offset(offset: usize) -> usize { |
| 477 | + offset.next_multiple_of(FDT_TAGSIZE) |
| 478 | + } |
279 | 479 | } |
280 | 480 |
|
281 | 481 | #[cfg(test)] |
|
0 commit comments