diff options
author | Nathan Ringo <nathan@remexre.com> | 2024-08-26 20:53:46 -0500 |
---|---|---|
committer | Nathan Ringo <nathan@remexre.com> | 2024-08-26 20:53:46 -0500 |
commit | d10960f7fc664189f70fb634581958a4a1fd99fa (patch) | |
tree | fa39b8939fff7694f7e9cbcdf4bbd70e53500b6f /kernel/src/device_tree.rs | |
parent | 76f0764cebe313a75b9b170fa23fa940d9e5738a (diff) |
Refactor DeviceTree parsing.
Diffstat (limited to 'kernel/src/device_tree.rs')
-rw-r--r-- | kernel/src/device_tree.rs | 521 |
1 files changed, 414 insertions, 107 deletions
diff --git a/kernel/src/device_tree.rs b/kernel/src/device_tree.rs index d843234..5fdc3e9 100644 --- a/kernel/src/device_tree.rs +++ b/kernel/src/device_tree.rs @@ -1,9 +1,14 @@ //! Support for the DeviceTree format. -use crate::collections::stack_linked_list::StackLinkedList; +use crate::{collections::stack_linked_list::StackLinkedList, prelude::*, util::FromEndianBytes}; use bstr::BStr; use contracts::requires; -use core::{fmt, iter, slice}; +use core::{ + fmt, iter, + num::ParseIntError, + slice, + str::{self, Utf8Error}, +}; use either::Either; /// A reference to a flattened DeviceTree (DTB) in memory. @@ -77,16 +82,26 @@ impl<'dt> FlattenedDeviceTree<'dt> { memrsv_count += 1; }; - Ok(FlattenedDeviceTree { + // Try parsing all of the events in the structure block, so we can report errors from doing + // so before anything outside this module can get their hands on them. + let fdt = FlattenedDeviceTree { header, struct_block, strings_block, memrsv_block, - }) + }; + fdt.iter_struct_events_from(0) + .try_for_each(|r| r.map(|_| ()))?; + + // Check that the overall structure of the structure block is correct, so we don't need to + // worry about errors later. + for_each_node(&fdt, &mut |_| Ok(()), &|err| err, 0, StackLinkedList::NIL)?; + + Ok(fdt) } /// Returns the string at the given offset of the strings block. - pub fn get_string(&self, offset: u32) -> Result<&BStr, DeviceTreeError> { + fn get_string(&self, offset: u32) -> Result<&BStr, DeviceTreeError> { let out = &self.strings_block[offset as usize..]; let i = out .iter() @@ -95,31 +110,66 @@ impl<'dt> FlattenedDeviceTree<'dt> { Ok(&out[..i]) } - /// Iterates over the properties stored in the DeviceTree, only allocating memory on the stack. - pub fn for_each_property<'iter: 'dt, E>( - &'iter self, - mut func: impl for<'a> FnMut(FdtNodePath<'dt, 'a>, &'dt BStr, &'dt BStr) -> Result<(), E>, - ) -> Result<(), Either<DeviceTreeError, E>> { - for_each_property( - &mut self.struct_events().peekable(), + /// Parses nodes out of a flattened DeviceTree and calls the function with each one. This does + /// not allocate memory, and may be called before the allocator is configured (at the cost of + /// decreased performance). + pub fn for_each_node<E>( + &'dt self, + mut func: impl for<'a> FnMut(FdtNode<'dt, 'a>) -> Result<(), E>, + ) -> Result<(), E> { + for_each_node( + self, &mut func, + &|_| unreachable!("checked in FlattenedDeviceTree::from_ptr"), + 0, StackLinkedList::NIL, - ) + )?; + Ok(()) } - /// Returns an iterator over the events in the flattened DeviceTree's structure block. - pub fn struct_events<'iter: 'dt>( + /// Returns an iterator over the events in the structure block of the DeviceTree, starting at + /// the current offset from the start of the structure block. + /// + /// Panics if the offset is out-of-bounds. + fn iter_struct_events_from<'iter: 'dt>( &'iter self, + start: u32, ) -> impl 'iter + Iterator<Item = Result<FdtStructEvent<'dt>, DeviceTreeError>> { - let mut ptr = self.struct_block; - iter::from_fn(move || loop { - break match u32::from_be(ptr[0]) { + let mut i = start; + iter::from_fn(move || match self.next_struct_event_from(i) { + Some(Ok((next, event))) => { + let prev = i; + assert!(prev < next); + i = next; + Some(Ok(event)) + } + Some(Err(err)) => Some(Err(err)), + None => None, + }) + } + + /// Parses a single point in the structure block of the DeviceTree, returning the offset to + /// parse at next and the parse event. + /// + /// Panics if the offset is out-of-bounds. + fn next_struct_event_from<'iter: 'dt>( + &'iter self, + mut offset: u32, + ) -> Option<Result<(u32, FdtStructEvent<'dt>), DeviceTreeError>> { + loop { + // Read the token type and advance the offset. + let token_type = u32::from_be(*self.struct_block.get(offset as usize)?); + offset += 1; + + // Branch on the token type to recognize an event. + let event = match token_type { // FDT_BEGIN_NODE 0x00000001 => { // Save a pointer to the start of the extra data. - let name_ptr = (&ptr[1]) as *const u32 as *const u8; + let name_ptr = (&self.struct_block[offset as usize]) as *const u32 as *const u8; - // Look for a null terminator. + // Look for a null terminator. `name_len` is the length of the name, _without_ + // the null terminator. // // SAFETY: This method can only be called when the FlattenedDeviceTree was // constructed from a valid flattened DeviceTree. @@ -135,58 +185,62 @@ impl<'dt> FlattenedDeviceTree<'dt> { // flattened DeviceTree. let name = BStr::new(unsafe { slice::from_raw_parts(name_ptr, name_len) }); - // Advance the pointer. - let extra_data_count = (name_len + 4) >> 2; - ptr = &ptr[1 + extra_data_count..]; + // Parse the name. + let Ok(name) = FdtNodeName::parse(name) else { + return Some(Err(DeviceTreeError::InvalidNodeName)); + }; - // Return the event. - Some(Ok(FdtStructEvent::BeginNode(name))) - } - // FDT_END_NODE - 0x00000002 => { - // Advance the pointer. - ptr = &ptr[1..]; + // Compute the count and advance the offset. + offset += (name_len as u32 + 4) >> 2; - // Return the event. - Some(Ok(FdtStructEvent::EndNode)) + // Create the event. + FdtStructEvent::BeginNode(name) } + // FDT_END_NODE + 0x00000002 => FdtStructEvent::EndNode, // FDT_PROP 0x00000003 => { // Get the length of the property data and the offset of the name in the // strings block. - let len = u32::from_be(ptr[1]) as usize; - let name_off = u32::from_be(ptr[2]); + let data_len = u32::from_be(self.struct_block[offset as usize]) as usize; + let name_off = u32::from_be(self.struct_block[offset as usize + 1]); + offset += 2; // Get the property data as a BStr. // // SAFETY: This is valid by the requirements of a flattened DeviceTree. + // + // TODO: We could refactor this out to a to_bytes call plus a bounds-checked + // slicing operation to get a _bit_ of safety back, at least... let data = BStr::new(unsafe { - slice::from_raw_parts(&ptr[3] as *const u32 as *const u8, len) + slice::from_raw_parts( + &self.struct_block[offset as usize] as *const u32 as *const u8, + data_len, + ) }); + // Advance past the property data. + offset += (data_len as u32 + 3) >> 2; + // Get the property name as a BStr. let name = match self.get_string(name_off) { Ok(name) => name, - Err(err) => break Some(Err(err)), + Err(err) => return Some(Err(err)), }; - // Advance the pointer. - let data_count = (len + 3) >> 2; - ptr = &ptr[3 + data_count..]; - - // Return the event. - Some(Ok(FdtStructEvent::Prop(name, data))) + // Create the event. + FdtStructEvent::Prop(name, data) } // FDT_NOP - 0x00000004 => { - ptr = &ptr[1..]; - continue; - } + 0x00000004 => continue, // FDT_END - 0x00000009 => None, - token_type => Some(Err(DeviceTreeError::InvalidTokenType(token_type))), + 0x00000009 => return None, + _ => return Some(Err(DeviceTreeError::InvalidTokenType(token_type))), }; - }) + + // Return the new offset and the event. + return Some(Ok((offset, event))); + } } } @@ -196,6 +250,7 @@ pub enum DeviceTreeError { BadMagic(u32), IncompatibleVersion(u32, u32), InvalidDeviceTree, + InvalidNodeName, InvalidTokenType(u32), StringMissingNulTerminator(u32), @@ -203,43 +258,6 @@ pub enum DeviceTreeError { UnexpectedEvent, } -fn for_each_property<'dt, E>( - events: &mut iter::Peekable<impl Iterator<Item = Result<FdtStructEvent<'dt>, DeviceTreeError>>>, - func: &mut impl for<'a> FnMut(FdtNodePath<'dt, 'a>, &'dt BStr, &'dt BStr) -> Result<(), E>, - node: StackLinkedList<&'dt BStr>, -) -> Result<(), Either<DeviceTreeError, E>> { - // Read the first event, which should be the BeginNode starting the node we're trying to read. - let event = events - .next() - .ok_or(Either::Left(DeviceTreeError::UnexpectedEndOfStructBlock))? - .map_err(Either::Left)?; - let FdtStructEvent::BeginNode(node_name) = event else { - return Err(Either::Left(DeviceTreeError::UnexpectedEvent)); - }; - let node = node.cons(node_name); - - // Parse properties and subnodes until we get to the end. - loop { - if matches!(events.peek(), Some(Ok(FdtStructEvent::BeginNode(_)))) { - for_each_property(events, func, node)?; - } else { - let event = events - .next() - .ok_or(Either::Left(DeviceTreeError::UnexpectedEndOfStructBlock))? - .map_err(Either::Left)?; - match event { - FdtStructEvent::BeginNode(_) => { - return Err(Either::Left(DeviceTreeError::UnexpectedEvent)) - } - FdtStructEvent::EndNode => break Ok(()), - FdtStructEvent::Prop(prop, value) => { - func(FdtNodePath(node), prop, value).map_err(Either::Right)? - } - } - } - } -} - /// The flattened DeviceTree header. /// /// All fields are big-endian. @@ -270,39 +288,328 @@ struct FdtMemRsv { /// An event returned from iterating over the structure block of a flattened DeviceTree. #[derive(Clone, Copy, Debug)] -pub enum FdtStructEvent<'dt> { - BeginNode(&'dt BStr), +enum FdtStructEvent<'dt> { + BeginNode(FdtNodeName<'dt>), EndNode, Prop(&'dt BStr, &'dt BStr), } -/// The path to a node. Note that the list this contains is in _reverse_ order. -#[derive(Debug, Eq, PartialEq)] -pub struct FdtNodePath<'dt, 'list>(pub StackLinkedList<'list, &'dt BStr>); +/// The name of a node. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct FdtNodeName<'dt> { + /// The full name, including the unit address. + pub full_name: &'dt BStr, + + // The length of the unit name (i.e., the part of the name before the unit address). + unit_name_len: usize, + + /// The address of the node. + pub unit_address: Option<u64>, +} + +impl<'dt> FdtNodeName<'dt> { + /// Returns the unit name; i.e., the part of the name that does not include the unit address. + pub fn unit_name(&self) -> &'dt BStr { + &self.full_name[..self.unit_name_len] + } -impl<'dt, 'list> fmt::Display for FdtNodePath<'dt, 'list> { + /// Parses an `FdtNodeName` from bytes. + pub fn parse(bytes: &'dt BStr) -> Result<FdtNodeName<'dt>, Either<ParseIntError, Utf8Error>> { + if let Some(at_sign_index) = bytes.iter().position(|&b| b == b'@') { + let unit_address = + str::from_utf8(&bytes[at_sign_index + 1..]).map_err(Either::Right)?; + let unit_address = u64::from_str_radix(unit_address, 16).map_err(Either::Left)?; + Ok(FdtNodeName { + full_name: bytes, + unit_name_len: at_sign_index, + unit_address: Some(unit_address), + }) + } else { + Ok(FdtNodeName { + full_name: bytes, + unit_name_len: bytes.len(), + unit_address: None, + }) + } + } +} + +impl<'dt> fmt::Display for FdtNodeName<'dt> { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { - match (self.0).0 { - Some((hd, tl)) => write!(fmt, "{}{}/", FdtNodePath(*tl), hd), - None => Ok(()), + write!(fmt, "{}", self.full_name) + } +} + +/// A reference to a node in a flattened DeviceTree. +#[derive(Clone, Copy)] +pub struct FdtNode<'dt, 'iter> { + fdt: &'dt FlattenedDeviceTree<'dt>, + index: u32, + parents: StackLinkedList<'iter, u32>, +} + +impl<'dt, 'iter> FdtNode<'dt, 'iter> { + /// A constructor that checks the type's invariants. + #[requires(matches!( + fdt.next_struct_event_from(index), + Some(Ok((_, FdtStructEvent::BeginNode(_))))) + )] + #[requires(parents.into_iter().all(|parent| { + matches!( + fdt.next_struct_event_from(parent), + Some(Ok((_, FdtStructEvent::BeginNode(_)))) + ) + }))] + fn new( + fdt: &'dt FlattenedDeviceTree<'dt>, + index: u32, + parents: StackLinkedList<'iter, u32>, + ) -> FdtNode<'dt, 'iter> { + FdtNode { + fdt, + index, + parents, + } + } + + /// Returns the value corresponding to a prop name for this node. + pub fn get_prop<U>(&self, name: U) -> Option<&'dt BStr> + where + BStr: PartialEq<U>, + { + self.iter_props().find(|&(k, _)| k == &name).map(|(_, v)| v) + } + + /// Returns the value corresponding to a prop name for this node as a big-endian `u32`. + pub fn get_prop_u32<U>(&self, name: U) -> Option<u32> + where + BStr: PartialEq<U>, + U: Copy + fmt::Display, + { + let value = self.get_prop(name)?; + if value.len() != 4 { + warn!( + "{}{} was expected to be 4 bytes, but was {:?}", + self.name(), + name, + value + ); + return None; + } + let value = value.first_chunk()?; + Some(u32::from_be_bytes(*value)) + } + + /// Returns the `reg` property of this node as an iterator over `(addr, size)` pairs. + pub fn get_reg(&self) -> Option<impl Iterator<Item = (&'dt BStr, &'dt BStr)>> { + // Get the parent node. + let Some(parent) = self.parent() else { + warn!("{} did not have a parent", self.name()); + return None; + }; + + // Get the size of addresses and sizes from the parent. + let Some(address_cells) = parent.get_prop_u32("#address-cells") else { + warn!( + "{} did not have a valid #address-cells property", + parent.name() + ); + return None; + }; + let Some(size_cells) = parent.get_prop_u32("#size-cells") else { + warn!( + "{} did not have a valid #size-cells property", + parent.name() + ); + return None; + }; + + // Convert the numbers to bytes. + let address_size = (address_cells << 2) as usize; + let size_size = (size_cells << 2) as usize; + + // Get the `reg` property's value. + let value = self.get_prop("reg")?; + if value.len() % (address_size + size_size) != 0 { + warn!( + "{}reg was expected to be a multiple of ({} + {}) bytes, but was {:?}", + self.name(), + address_size, + size_size, + value, + ); + return None; + } + + Some( + (0..value.len()) + .step_by(address_size + size_size) + .map(move |i| { + ( + &value[i..i + address_size], + &value[i + address_size..i + address_size + size_size], + ) + }), + ) + } + + /// Returns the `reg` property of this node as an iterator over `(addr, size)` pairs, requiring + /// that they're correctly-sized for `usize`s. + pub fn get_reg_usize(&self) -> Option<impl 'dt + Iterator<Item = (usize, usize)>> { + let iter = self.get_reg()?.map(|(addr, size)| { + ( + usize::from_big_endian_bytes(addr), + usize::from_big_endian_bytes(size), + ) + }); + Some(iter) + } + + /// Returns whether the unit path (i.e., the path to this node, only taking unit names) matches + /// the argument. + pub fn is_unit<U>(&self, name: &[U]) -> bool + where + BStr: PartialEq<U>, + { + self.iter_names_rev() + .map(|name| name.unit_name()) + .eq(name.iter().rev()) + } + + /// Returns an iterator over the properties of the node. + pub fn iter_props(&self) -> impl '_ + Iterator<Item = (&'dt BStr, &'dt BStr)> { + // Skip the BeginNode. + let offset = match self.fdt.next_struct_event_from(self.index) { + Some(Ok((offset, FdtStructEvent::BeginNode(_)))) => offset, + _ => unreachable!("checked in FlattenedDeviceTree::from_ptr"), + }; + + // Yield Prop nodes as long as we can get them. + self.fdt + .iter_struct_events_from(offset) + .map_while(|r| match r { + Ok(FdtStructEvent::Prop(key, value)) => Some((key, value)), + Ok(FdtStructEvent::BeginNode(_) | FdtStructEvent::EndNode) => None, + Err(_) => unreachable!("checked in FlattenedDeviceTree::from_ptr"), + }) + } + + /// Returns a value that can be `Display`ed as the fully-qualified name of the node. + pub fn name(&self) -> impl '_ + fmt::Display { + struct NameDisplay<'a, 'dt, 'iter>(&'a FdtNode<'dt, 'iter>); + + impl<'a, 'dt, 'iter> fmt::Display for NameDisplay<'a, 'dt, 'iter> { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + fn fmt_name_rev<'dt>( + fmt: &mut fmt::Formatter, + mut iter: impl Iterator<Item = FdtNodeName<'dt>>, + ) -> fmt::Result { + match iter.next() { + Some(name) => { + fmt_name_rev(fmt, iter)?; + write!(fmt, "{name}/") + } + None => Ok(()), + } + } + + fmt_name_rev(fmt, self.0.iter_names_rev()) + } } + + NameDisplay(self) + } + + /// Returns the parent of this node. + pub fn parent(&self) -> Option<FdtNode<'dt, 'iter>> { + let (&hd, &tl) = self.parents.uncons()?; + Some(FdtNode::new(self.fdt, hd, tl)) + } + + /// Returns an iterator over the names of the node and its parents, in reverse order. + fn iter_names_rev(&self) -> impl '_ + Clone + Iterator<Item = FdtNodeName<'dt>> { + self.parents.cons(self.index).into_iter().map(move |i| { + match self.fdt.next_struct_event_from(i) { + Some(Ok((_, FdtStructEvent::BeginNode(name)))) => name, + _ => unreachable!("checked in FlattenedDeviceTree::from_ptr"), + } + }) } } -impl<'dt, 'list, U> PartialEq<[U]> for FdtNodePath<'dt, 'list> -where - &'dt BStr: PartialEq<U>, -{ - fn eq(&self, other: &[U]) -> bool { - self.0.iter().eq(other.iter().rev()) +impl<'dt, 'iter> fmt::Debug for FdtNode<'dt, 'iter> { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + write!(fmt, "{} ", self.name())?; + fmt.debug_map().entries(self.iter_props()).finish() } } -impl<'dt, 'list, U, const N: usize> PartialEq<[U; N]> for FdtNodePath<'dt, 'list> -where - &'dt BStr: PartialEq<U>, -{ - fn eq(&self, other: &[U; N]) -> bool { - self.0.iter().eq(other.iter().rev()) +/// Parses nodes out of a flattened DeviceTree and calls the function with each one. This does not +/// allocate memory, and may be called before the allocator is configured (at the cost of decreased +/// performance). Returns the offset after parsing the one starting at `index`. +/// +/// - `'dt` is the lifetime of the DeviceTree. +/// - `'a` is the lifetime of this function call. +/// - `'b` is the lifetime of the recursive call to this function that parses its children. +/// +/// - `E` is the type of errors. We do a pass in `FlattenedDeviceTree::from_ptr` with this as +/// `DeviceTreeError` (and a `func` that's a no-op) to find any errors we might encounter. In +/// actual user invocations of this function, it's replaced with the actual user error type (and +/// `on_error` panics). +/// +/// - `fdt` is flattened DeviceTree we're parsing inside. +/// - `func` is the callback to call with nodes. +/// - `on_error` is a function to call to convert a `DeviceTreeError` to an `E`. +/// - `index` is the index of the word in the structure block to start parsing at. +/// - `parents` is an accumulated list of the indices of nodes that are parents of this one. +fn for_each_node<'dt, 'iter, E>( + fdt: &'dt FlattenedDeviceTree<'dt>, + func: &mut impl for<'a> FnMut(FdtNode<'dt, 'a>) -> Result<(), E>, + on_error: &impl Fn(DeviceTreeError) -> E, + index: u32, + parents: StackLinkedList<'iter, u32>, +) -> Result<u32, E> { + // Read the first event, which should be the BeginNode starting the node we're trying to read. + // We need to ensure that `index` refers to a `BeginNode` so that methods on `FdtNode` (e.g. + // `iter_names_rev`) can rely on this. We also take as an inductive invariant that every index + // in `parents` refers to a valid `BeginNode`. + let (mut offset, event) = fdt + .next_struct_event_from(index) + .ok_or_else(|| on_error(DeviceTreeError::UnexpectedEndOfStructBlock))? + .unwrap_or_else(|_| unreachable!("checked in FlattenedDeviceTree::from_ptr")); + if !matches!(event, FdtStructEvent::BeginNode(_)) { + dbg!((event, index, parents)); + return Err(on_error(DeviceTreeError::UnexpectedEvent)); + } + + // Create the node and call the callback with it. + func(FdtNode::new(fdt, index, parents))?; + + // Create a new parents list that includes this node. + let new_parents = parents.cons(index); + + // Advance past any prop events. + loop { + let (next_offset, event) = fdt + .next_struct_event_from(offset) + .ok_or_else(|| on_error(DeviceTreeError::UnexpectedEndOfStructBlock))? + .unwrap_or_else(|_| unreachable!("checked in FlattenedDeviceTree::from_ptr")); + if !matches!(event, FdtStructEvent::Prop(_, _)) { + break; + } + offset = next_offset; + } + + // Until we find an end node, parse child nodes. + loop { + let (next_offset, event) = fdt + .next_struct_event_from(offset) + .ok_or_else(|| on_error(DeviceTreeError::UnexpectedEndOfStructBlock))? + .unwrap_or_else(|_| unreachable!("checked in FlattenedDeviceTree::from_ptr")); + if matches!(event, FdtStructEvent::EndNode) { + break Ok(next_offset); + } + + offset = for_each_node(fdt, func, on_error, offset, new_parents)?; } } |