summaryrefslogtreecommitdiff
path: root/kernel/src/device_tree.rs
diff options
context:
space:
mode:
Diffstat (limited to 'kernel/src/device_tree.rs')
-rw-r--r--kernel/src/device_tree.rs641
1 files changed, 0 insertions, 641 deletions
diff --git a/kernel/src/device_tree.rs b/kernel/src/device_tree.rs
deleted file mode 100644
index c045cfd..0000000
--- a/kernel/src/device_tree.rs
+++ /dev/null
@@ -1,641 +0,0 @@
-//! Support for the DeviceTree format.
-
-use crate::{collections::stack_linked_list::StackLinkedList, prelude::*, util::FromEndianBytes};
-use bstr::BStr;
-use contracts::requires;
-use core::{
- fmt, iter,
- num::ParseIntError,
- ops::Range,
- slice,
- str::{self, Utf8Error},
-};
-use either::Either;
-
-/// A reference to a flattened DeviceTree (DTB) in memory.
-pub struct FlattenedDeviceTree<'dt> {
- header: &'dt FdtHeader,
- struct_block: &'dt [u32],
- strings_block: &'dt BStr,
- memrsv_block: &'dt [FdtMemRsv],
-}
-
-impl<'dt> FlattenedDeviceTree<'dt> {
- /// Looks for a DeviceTree at the given address, and returns it if it looks valid.
- ///
- /// # Safety
- ///
- /// - `ptr` must point to a spec-compliant flattened DeviceTree.
- /// - The memory contained by the flattened DeviceTree must be valid for the duration of
- /// lifetime `'dt`.
- /// - The memory contained by the flattened DeviceTree must not be mutated for the duration of
- /// lifetime `'dt`.
- #[requires((ptr as *const FdtHeader).is_aligned())]
- pub unsafe fn from_ptr(ptr: *const u8) -> Result<FlattenedDeviceTree<'dt>, DeviceTreeError> {
- // Check that the header appears to point to a valid flattened DeviceTree.
- let header: &'dt FdtHeader = &*(ptr as *const FdtHeader);
- let magic = u32::from_be(header.magic);
- if magic != 0xd00dfeed {
- return Err(DeviceTreeError::BadMagic(magic));
- }
- let version = u32::from_be(header.version);
- let last_comp_version = u32::from_be(header.last_comp_version);
- if last_comp_version > 17 {
- return Err(DeviceTreeError::IncompatibleVersion(
- version,
- last_comp_version,
- ));
- }
-
- // Get pointers to each block.
- let off_dt_struct = u32::from_be(header.off_dt_struct) as usize;
- let size_dt_struct = u32::from_be(header.size_dt_struct) as usize;
- let off_dt_strings = u32::from_be(header.off_dt_strings) as usize;
- let size_dt_strings = u32::from_be(header.size_dt_strings) as usize;
- let off_mem_rsvmap = u32::from_be(header.off_mem_rsvmap) as usize;
-
- // Check that the structure block has an aligned size.
- if (size_dt_struct & 0b11) != 0 {
- return Err(DeviceTreeError::InvalidDeviceTree);
- }
-
- // Extract the structure and strings blocks.
- let struct_block: &[u32] =
- slice::from_raw_parts(ptr.add(off_dt_struct).cast(), size_dt_struct / 4);
- let strings_block = BStr::new(slice::from_raw_parts(
- ptr.add(off_dt_strings),
- size_dt_strings,
- ));
-
- // Read memory reservations until the terminating one is found, then construct the block of
- // the appropriate length.
- let mut memrsv_count = 0;
- let memrsv_ptr: *const FdtMemRsv = ptr.add(off_mem_rsvmap).cast();
- let memrsv_block = loop {
- let memrsv = *memrsv_ptr.add(memrsv_count);
-
- // We can skip the endian conversion, since we're just testing against zero.
- if memrsv == (FdtMemRsv { addr: 0, size: 0 }) {
- break slice::from_raw_parts(memrsv_ptr, memrsv_count);
- }
-
- memrsv_count += 1;
- };
-
- // 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.
- fn get_string(&self, offset: u32) -> Result<&BStr, DeviceTreeError> {
- let out = &self.strings_block[offset as usize..];
- let i = out
- .iter()
- .position(|&b| b == b'\0')
- .ok_or(DeviceTreeError::StringMissingNulTerminator(offset))?;
- Ok(&out[..i])
- }
-
- /// 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 reserved ranges of addresses.
- ///
- /// Addresses may be reserved by:
- ///
- /// - Overlapping with the DeviceTree itself.
- /// - Overlapping with any memory reservations in the DeviceTree.
- pub fn iter_reserved(&self) -> impl '_ + Iterator<Item = Range<usize>> {
- // Make the address range of the DeviceTree.
- let dt_start = self.header as *const FdtHeader as usize;
- let dt_end = dt_start + u32::from_be(self.header.totalsize) as usize;
-
- iter::once(dt_start..dt_end).chain(self.memrsv_block.iter().map(|memrsv| {
- let addr = u64::from_be(memrsv.addr) as usize;
- let size = u64::from_be(memrsv.size) as usize;
- addr..addr + size
- }))
- }
-
- /// 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 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 = (&self.struct_block[offset as usize]) as *const u32 as *const u8;
-
- // 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.
- let mut name_len = 0;
- while unsafe { *name_ptr.add(name_len) } != b'\0' {
- name_len += 1;
- }
-
- // Create the name as a BStr.
- //
- // SAFETY: Well, we already accessed this memory above when finding the null
- // terminator... But yes, this being valid is guaranteed by this being a
- // flattened DeviceTree.
- let name = BStr::new(unsafe { slice::from_raw_parts(name_ptr, name_len) });
-
- // Parse the name.
- let Ok(name) = FdtNodeName::parse(name) else {
- return Some(Err(DeviceTreeError::InvalidNodeName));
- };
-
- // Compute the count and advance the offset.
- offset += (name_len as u32 + 4) >> 2;
-
- // 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 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(
- &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) => return Some(Err(err)),
- };
-
- // Create the event.
- FdtStructEvent::Prop(name, data)
- }
- // FDT_NOP
- 0x00000004 => continue,
- // FDT_END
- 0x00000009 => return None,
- _ => return Some(Err(DeviceTreeError::InvalidTokenType(token_type))),
- };
-
- // Return the new offset and the event.
- return Some(Ok((offset, event)));
- }
- }
-}
-
-impl<'dt> fmt::Debug for FlattenedDeviceTree<'dt> {
- fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
- let addr = self.header as *const FdtHeader;
- // Intentionally not using `fmt.debug_tuple`, it's wasted space here.
- write!(fmt, "FlattenedDeviceTree::from_ptr({addr:p})")
- }
-}
-
-/// An error encountered when reading a DeviceTree.
-#[derive(Debug)]
-pub enum DeviceTreeError {
- BadMagic(u32),
- IncompatibleVersion(u32, u32),
- InvalidDeviceTree,
- InvalidNodeName,
- InvalidTokenType(u32),
- StringMissingNulTerminator(u32),
-
- UnexpectedEndOfStructBlock,
- UnexpectedEvent,
-}
-
-/// The flattened DeviceTree header.
-///
-/// All fields are big-endian.
-#[derive(Debug)]
-#[repr(C)]
-struct FdtHeader {
- magic: u32,
- totalsize: u32,
- off_dt_struct: u32,
- off_dt_strings: u32,
- off_mem_rsvmap: u32,
- version: u32,
- last_comp_version: u32,
- boot_cpuid_phys: u32,
- size_dt_strings: u32,
- size_dt_struct: u32,
-}
-
-/// A memory reservation from the appropriate block of the DeviceTree.
-///
-/// All fields are big-endian.
-#[derive(Clone, Copy, Debug, Eq, PartialEq)]
-#[repr(C)]
-struct FdtMemRsv {
- addr: u64,
- size: u64,
-}
-
-/// An event returned from iterating over the structure block of a flattened DeviceTree.
-#[derive(Clone, Copy, Debug)]
-enum FdtStructEvent<'dt> {
- BeginNode(FdtNodeName<'dt>),
- EndNode,
- Prop(&'dt BStr, &'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]
- }
-
- /// 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 {
- 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, '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()
- }
-}
-
-/// 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)?;
- }
-}