diff --git a/kernel/lib/device-tree/src/driver/interrupt.rs b/kernel/lib/device-tree/src/driver/interrupt.rs index b8a4fa8c..dfe446d5 100644 --- a/kernel/lib/device-tree/src/driver/interrupt.rs +++ b/kernel/lib/device-tree/src/driver/interrupt.rs @@ -1,3 +1,4 @@ +//! Device-tree interrupt controller handling use alloc::{collections::btree_map::BTreeMap, sync::Arc}; use device_api::interrupt::FullIrq; use fdt_rs::spec::Phandle; @@ -10,12 +11,18 @@ use super::Node; pub(crate) static INTERRUPT_CONTROLLERS: IrqSafeRwLock>> = IrqSafeRwLock::new(BTreeMap::new()); +/// Looks up an `interrupt-controller` node by its phandle. Additionally, the function lazily +/// probes the node by running [Node::probe]. +/// +/// The function returns [None] if the node is found, but no driver/device is available. pub fn interrupt_controller(phandle: Phandle) -> Option> { let interrupt_controller = INTERRUPT_CONTROLLERS.read().get(&phandle).cloned()?; interrupt_controller.clone().probe()?; Some(interrupt_controller) } +/// Reads interrupt information, as interpreted by `interrupt_controller`, from `property` at a +/// given `offset`. pub fn map_interrupt_at( interrupt_controller: &Arc, property: &TProp, @@ -25,6 +32,8 @@ pub fn map_interrupt_at( interrupt_controller.map_interrupt(property, offset) } +/// Same as [map_interrupt_at], but uses a phandle to address the `interrupt-controller` node +/// and a scaled `index`. pub fn map_interrupt(phandle: Phandle, property: &TProp, index: usize) -> Option { let interrupt_controller = interrupt_controller(phandle)?; let interrupt_cells = interrupt_controller.self_interrupt_cells()?; diff --git a/kernel/lib/device-tree/src/driver/macros.rs b/kernel/lib/device-tree/src/driver/macros.rs index 7f3bf3a8..9a0d0d78 100644 --- a/kernel/lib/device-tree/src/driver/macros.rs +++ b/kernel/lib/device-tree/src/driver/macros.rs @@ -1,3 +1,12 @@ +//! Device tree driver macros + +/// Performs registration of a device tree driver. +/// +/// The `compatible` list should contain strings, ordered from the most specific to the most +/// generic implementation supported. +/// +/// The registration happens in runtime, after the kernel sets up memory management, through the +/// `.init_array`-based mechanism. pub macro device_tree_driver( compatible: [$($compatible:literal),+ $(,)?], driver: $driver:tt diff --git a/kernel/lib/device-tree/src/driver/mod.rs b/kernel/lib/device-tree/src/driver/mod.rs index b5157be0..e5884ebb 100644 --- a/kernel/lib/device-tree/src/driver/mod.rs +++ b/kernel/lib/device-tree/src/driver/mod.rs @@ -15,6 +15,11 @@ pub use registry::register_driver; pub use traits::{DeviceTreeInterruptController, Driver, ProbeContext}; pub use tree::{find_node, unflatten_device_tree, walk_device_tree, Node}; +/// Performs a walk of the device tree and initializes nodes which haven't already been +/// initialized/probed. +/// +/// Reports successful initializations via `success_handler` and +/// failures via `error_handler`. pub fn lazy_init), F: Fn(&Arc, Error)>( success_handler: S, error_handler: F, @@ -29,6 +34,11 @@ pub fn lazy_init), F: Fn(&Arc, Error)>( }); } +/// Performs a walk of the device and initializes IRQs for nodes. Additionally performs lazy +/// initialization (if need to enable IRQs, but a device is not initialized). +/// +/// Reports successful initializations via `success_handler` and +/// failures via `error_handler`. pub fn init_irqs), F: Fn(&Arc, Error)>( success_handler: S, error_handler: F, diff --git a/kernel/lib/device-tree/src/driver/registry.rs b/kernel/lib/device-tree/src/driver/registry.rs index 4fabc128..0e9dd178 100644 --- a/kernel/lib/device-tree/src/driver/registry.rs +++ b/kernel/lib/device-tree/src/driver/registry.rs @@ -1,3 +1,4 @@ +//! Device tree driver registration use alloc::{sync::Arc, vec::Vec}; use libk_util::{sync::spin_rwlock::IrqSafeRwLock, OneTimeInit}; @@ -5,9 +6,9 @@ use crate::DeviceTree; use super::{Driver, Node}; -pub struct DriverRegistration { - pub compatible: &'static [&'static str], - pub imp: &'static dyn Driver, +pub(crate) struct DriverRegistration { + pub(crate) compatible: &'static [&'static str], + pub(crate) imp: &'static dyn Driver, } pub(crate) static DRIVERS: IrqSafeRwLock> = IrqSafeRwLock::new(Vec::new()); @@ -20,6 +21,7 @@ impl DriverRegistration { } } +#[doc(hidden)] pub fn register_driver(compatible: &'static [&'static str], driver: &'static dyn Driver) { DRIVERS.write().push(DriverRegistration { compatible, diff --git a/kernel/lib/device-tree/src/driver/traits.rs b/kernel/lib/device-tree/src/driver/traits.rs index e672179e..a5db8ac0 100644 --- a/kernel/lib/device-tree/src/driver/traits.rs +++ b/kernel/lib/device-tree/src/driver/traits.rs @@ -1,3 +1,4 @@ +//! Device tree driver interfaces use core::ops::Range; use alloc::sync::{Arc, Weak}; @@ -7,19 +8,26 @@ use crate::TProp; use super::Node; +/// Device tree driver interface pub trait Driver: Sync { + /// Constructs a [Device] for a given matching node fn probe(&self, node: &Arc, context: &ProbeContext) -> Option>; } +/// Device-tree based interrupt mapper/controller interface pub trait DeviceTreeInterruptController { + /// Reads interrupt information from `property` at given `offset` and maps it to + /// the interrupt used by the controller fn map_interrupt(&self, property: &TProp, offset: usize) -> Option; } +/// Context passed to the driver's `probe` function pub struct ProbeContext { pub(crate) bus: Option>, } impl ProbeContext { + /// See [Node::map_range] pub fn map_range(&self, range: Range) -> Option> { if let Some(bus) = self.bus.as_ref().and_then(Weak::upgrade) { // Use parent bus to map the address diff --git a/kernel/lib/device-tree/src/driver/tree.rs b/kernel/lib/device-tree/src/driver/tree.rs index 908d03dd..6f15b028 100644 --- a/kernel/lib/device-tree/src/driver/tree.rs +++ b/kernel/lib/device-tree/src/driver/tree.rs @@ -1,3 +1,4 @@ +//! Device hierachy management code use core::ops::Range; use alloc::{ @@ -19,6 +20,12 @@ use super::{ DeviceTreeInterruptController, Driver, ProbeContext, }; +/// Represents a single node in the device tree, which may or may not: +/// +/// * Have a device probed (see [Node::probe]) +/// * Have a device initialized (see [Node::lazy_init]) +/// * Be a bus +/// * Be an interrupt-controller pub struct Node { // Info from device tree dt_node: TNode<'static>, @@ -86,19 +93,40 @@ impl Node { (device, bus) } + /// Performs a "probe" of the node by looking up a matching driver. If the node + /// has a parent, its parent is probed first. + /// + /// If the node has already been probed, the device reference is just returned instead. pub fn probe(self: Arc) -> Option> { let (device, _) = self.probe_upwards(); device } + /// When called from an interrupt-controller driver, informs the node of its capability as + /// an interrupt controller, allowing any other nodes which refer to it to map their + /// interrupts. + /// + /// This function has to be called in the driver's `probe` impl to function properly. pub fn make_interrupt_controller(&self, intc: Arc) { self.interrupt_controller.init(intc); } + /// Returns the device driver associated with this node, if any was probed. pub fn driver(&self) -> Option<&'static dyn Driver> { Some(self.device.try_get()?.driver) } + /// Performs a lazy initialization of the node: + /// + /// 1. If the node hasn't yet been probed for a device, a driver is looked up + /// (see [Node::probe]). + /// 2. If the node hasn't yet been initialized, initialization is performed. + /// + /// Returns: + /// + /// * `Some(Ok(()))` - device was probed and initialized (now or before). + /// * `Some(Err(...))` - device was probed, but initialization failed. + /// * `None` - no driver matched the device. pub fn lazy_init(self: Arc) -> Option> { let device = self.clone().probe()?; let result = self.init_token.or_try_init_with(|| { @@ -111,6 +139,12 @@ impl Node { } } + /// Performs an IRQ initialization for the node's device: + /// + /// 1. Does the same thing that [Node::lazy_init] does. + /// 2. If [Node::lazy_init] succeeds and a device exists, calls [Device::init_irq]. + /// + /// Return value is the same as in [Node::lazy_init]. pub fn init_irqs(self: Arc) -> Option> { match self.clone().lazy_init() { Some(Ok(())) => { @@ -122,6 +156,17 @@ impl Node { } } + /// "Forces" an initialization of the device. This is a stricter version of [Node::lazy_init] + /// which enforces that: + /// + /// 1. A device/driver actually exists for this node. + /// 2. The node's device hasn't been initialized before. + /// + /// Returns: + /// + /// * `Ok(())` - if probe/init succeeded. + /// * `Err(Error::DoesNotExist)` - couldn't find a device/driver for this node. + /// * `Err(other)` - initialization failed. pub fn force_init(self: Arc) -> Result<(), Error> { let device = self.clone().probe().ok_or(Error::DoesNotExist)?; @@ -132,43 +177,59 @@ impl Node { Ok(()) } + /// Returns an iterator over the node's children pub fn children(&self) -> impl Iterator> { self.children.get().iter() } // Properties + /// Returns the node's name string pub fn name(&self) -> Option<&'static str> { self.name } + /// Returns the addresses by which some part of the device is represented on its parent bus. + /// The bus address is taken from `reg[index]` property and is expected to be a pair + /// of `(#address-cells, #size-cells)`-sized elements. pub fn bus_range(&self, index: usize) -> Option> { let reg = self.dt_node.property("reg")?; let (base, size) = reg.read_cells(index, (self.bus_address_cells, self.bus_size_cells))?; Some(base..base + size) } + /// Returns the physical-address range used by some part of the device. + /// + /// This function is a result of [Node::bus_range], passed through its' parent bus mapper + /// (or returned as-is if the device has no parent bus). pub fn map_range(&self, context: &ProbeContext, index: usize) -> Option> { let bus_range = self.bus_range(index)?; context.map_range(bus_range) } + /// Same as [Node::map_range], but only returns the base of the range, cast to + /// [PhysicalAddress]. pub fn map_base(&self, context: &ProbeContext, index: usize) -> Option { self.map_range(context, index) .map(|range| PhysicalAddress::from_u64(range.start)) } + /// Reads interrupt information from `interrupts[index]` property, mapped by the node's + /// `interrupt-parent`. pub fn interrupt(&self, index: usize) -> Option { let interrupts = self.property("interrupts")?; let phandle = self.interrupt_parent?; map_interrupt(phandle, &interrupts, index) } + /// Same as [Node::interrupt], but allows specifying other property to read the information + /// from. pub fn interrupt_from(&self, property: &TProp, index: usize) -> Option { let phandle = self.interrupt_parent?; map_interrupt(phandle, property, index) } + /// Returns a node reference to the device's `interrupt-parent`. pub fn interrupt_controller(&self) -> Option> { INTERRUPT_CONTROLLERS .read() @@ -176,28 +237,33 @@ impl Node { .cloned() } + /// Returns the `#address-cells` value of the node's parent bus pub fn bus_address_cells(&self) -> usize { self.bus_address_cells } + /// Returns the `#interrupt-cells` of this node, if any pub fn self_interrupt_cells(&self) -> Option { self.property("#interrupt-cells") .and_then(|v| v.read_cell(0, 1)) .map(|v| v as usize) } + /// Returns the `#address-cells` of this node, if any pub fn self_address_cells(&self) -> Option { self.property("#address-cells") .and_then(|v| v.read_cell(0, 1)) .map(|v| v as usize) } + /// Returns the `#size-cells` of this node, if any pub fn self_size_cells(&self) -> Option { self.property("#size-cells") .and_then(|v| v.read_cell(0, 1)) .map(|v| v as usize) } + /// Looks up a property `name` in this node pub fn property(&self, name: &str) -> Option> { self.dt_node.property(name) } @@ -266,6 +332,8 @@ fn unflatten_node( node } +/// Sets up node hierarchy from a [DeviceTree] reference. This is a requirement to be able to +/// probe/initialize/manage devices using a device-tree approach. pub fn unflatten_device_tree(dt: &'static DeviceTree) { const DEFAULT_ADDRESS_CELLS: u32 = 2; const DEFAULT_SIZE_CELLS: u32 = 1; @@ -298,6 +366,8 @@ fn walk_node) -> Option>(node: &Arc, visitor: &m None } +/// Performs a walk of the device (in no particular order), passing each node to the visitor and +/// aborting if `Some(...)` is returned by it. pub fn walk_device_tree) -> Option>(mut visitor: V) -> Option { if let Some(root) = ROOT.try_get() { walk_node(root, &mut visitor) @@ -306,6 +376,7 @@ pub fn walk_device_tree) -> Option>(mut visitor: V) -> } } +/// Finds a node in the device tree by its absolute path or alias. pub fn find_node(name: &str) -> Option> { let dt = *DEVICE_TREE.try_get()?; let mut path = dt.resolve_alias(name)?.trim_matches('/'); diff --git a/kernel/lib/device-tree/src/lib.rs b/kernel/lib/device-tree/src/lib.rs index c6ad40a0..54ba9c92 100644 --- a/kernel/lib/device-tree/src/lib.rs +++ b/kernel/lib/device-tree/src/lib.rs @@ -1,6 +1,18 @@ +//! High level device tree handling library. +//! +//! Main functionality: +//! +//! * Creation of device tree-based drivers for devices +//! * Collection, initialization and hierarchy for device tree nodes. +//! +//! For example drivers, see the following code in the kernel: +//! +//! * `pl011` - basic peripheral usage with `reg` and `interrupts` +//! * `gic` in aarch64 - interrupt controller/mapper implementation #![cfg_attr(any(not(test), rust_analyzer), no_std)] #![feature(trait_alias, let_chains, decl_macro)] #![allow(clippy::type_complexity)] +#![warn(missing_docs)] extern crate alloc; diff --git a/kernel/lib/device-tree/src/node.rs b/kernel/lib/device-tree/src/node.rs index a3fe44b6..61f9eb9e 100644 --- a/kernel/lib/device-tree/src/node.rs +++ b/kernel/lib/device-tree/src/node.rs @@ -1,3 +1,4 @@ +//! Device tree node utilities use fdt_rs::{prelude::PropReader, spec::Phandle}; use crate::{ @@ -5,28 +6,39 @@ use crate::{ tree::{DeviceTree, TNode, TProp}, }; +/// Accessor trait for a device tree node pub trait DeviceTreeNodeExt<'a> { + /// Looks up a property with given `name` in the node fn property(&self, name: &str) -> Option>; + /// Looks up a child with given `name` in the node fn child(&self, name: &str) -> Option>; + /// Looks up a property with given `name` in the nearest parent. fn parent_property(&self, name: &str) -> Option>; + /// Returns a property `name`, interpreted as a single string value fn prop_string(&self, name: &str) -> Option<&'a str>; + /// Returns a property `name`, interpreted as a cell of given `size` fn prop_cell(&self, name: &str, size: usize) -> Option; + /// Returns a property `name`, interpreted as a single cell, cast to [usize] fn prop_cell_usize(&self, name: &str) -> Option { self.prop_cell(name, 1).map(|v| v as _) } + /// Returns a property `name`, interpreted as a phandle fn prop_phandle(&self, name: &str) -> Option { self.prop_cell(name, 1).map(|v| v as _) } + /// Returns `true` if the node contains a property `name` fn has_property(&self, name: &str) -> bool { self.property(name).is_some() } + /// Returns the `#address-cells` value of the nearest parent fn parent_address_cells(&self) -> usize { self.parent_property("#address-cells") .and_then(|property| property.read_cell(0, 1)) .unwrap_or(DeviceTree::DEFAULT_ADDRESS_CELLS as u64) as usize } + /// Returns the `#size-cells` value of the nearest parent fn parent_size_cells(&self) -> usize { self.parent_property("#size-cells") .and_then(|property| property.read_cell(0, 1)) diff --git a/kernel/lib/device-tree/src/property.rs b/kernel/lib/device-tree/src/property.rs index cc319435..c6527e3f 100644 --- a/kernel/lib/device-tree/src/property.rs +++ b/kernel/lib/device-tree/src/property.rs @@ -1,9 +1,11 @@ +//! Device tree nodes' property accessors use fdt_rs::{ base::iters::StringPropIter, index::DevTreeIndexProp, prelude::{FallibleIterator, PropReader}, }; +#[doc(hidden)] pub trait CellTuple: Sized { type Sizes: Copy; @@ -15,20 +17,33 @@ pub trait CellTuple: Sized { fn entry_size(sizes: Self::Sizes) -> usize; } +/// Accessor trait for a device tree property pub trait DeviceTreePropertyRead { + /// Reads a cell value of `size` at a given `offset`. + /// + /// **NOTE** supported cell sizes are 1 and 2. If the size is 3 and larger, + /// perform multiple split reads instead. fn read_cell(&self, offset: usize, size: usize) -> Option; + /// Reads the property as a single string fn as_str(&self) -> Option<&str>; + /// Reads the property as an iterator over a `` fn as_str_list(&self) -> impl Iterator; + /// Returns the length of the property in bytes fn len(&self) -> usize; + /// Reads a cell tuple from the property. The property's value is assumed to be + /// an uniform array of such tuples, specified by the `sizes` parameter. fn read_cells(&self, index: usize, sizes: T::Sizes) -> Option { self.read_cells_at(index * T::entry_size(sizes), sizes) } + /// Same as [DeviceTreePropertyRead::read_cells], but uses an unscaled `offset` instead. fn read_cells_at(&self, offset: usize, sizes: T::Sizes) -> Option { T::read(self, offset, sizes) } + /// Reads the property as an iterator over uniformly-sized tuples, specified by the + /// `sizes` parameter. fn iter_cells(&self, sizes: T::Sizes) -> CellTupleIter { CellTupleIter { property: self, @@ -37,17 +52,20 @@ pub trait DeviceTreePropertyRead { } } + /// Returns `true` if `self.len() == 0` fn is_empty(&self) -> bool { self.len() == 0 } } +/// An iterator over the tuple cells of some device tree node's property pub struct CellTupleIter<'a, P: DeviceTreePropertyRead + ?Sized, T: CellTuple> { property: &'a P, offset: usize, sizes: T::Sizes, } +/// An iterator over the string list propoerty pub struct StringListIter<'a> { inner: StringPropIter<'a>, } diff --git a/kernel/lib/device-tree/src/tree.rs b/kernel/lib/device-tree/src/tree.rs index 032b6c4c..77327424 100644 --- a/kernel/lib/device-tree/src/tree.rs +++ b/kernel/lib/device-tree/src/tree.rs @@ -1,3 +1,4 @@ +//! Raw device tree handling structures use core::ptr; use fdt_rs::{ @@ -14,9 +15,12 @@ use crate::{ const FDT_INDEX_BUFFER_SIZE: usize = 65536; +/// Helper alias for a device tree node. pub type TNode<'a> = DevTreeIndexNode<'a, 'a, 'a>; +/// Helper alias for a device tree node's property. pub type TProp<'a> = DevTreeIndexProp<'a, 'a, 'a>; +/// Main data structure for interacting with a raw device tree pub struct DeviceTree<'a> { tree: DevTree<'a>, index: DevTreeIndex<'a, 'a>, @@ -26,10 +30,14 @@ pub struct DeviceTree<'a> { struct FdtIndexBuffer([u8; FDT_INDEX_BUFFER_SIZE]); impl<'a> DeviceTree<'a> { + /// Minimum possible FDT header size pub const MIN_HEADER_SIZE: usize = DevTree::MIN_HEADER_SIZE; + /// Default `#address-cells` value assumed if one is absent pub const DEFAULT_ADDRESS_CELLS: usize = 2; + /// Default `#size-cells` value assumed if one is absent pub const DEFAULT_SIZE_CELLS: usize = 1; + /// Constructs a device tree from a raw tree and an index pub fn from_parts(tree: DevTree<'a>, index: DevTreeIndex<'a, 'a>) -> Self { Self { tree, index } } @@ -57,14 +65,17 @@ impl<'a> DeviceTree<'a> { Self::from_raw_with_index(address, &mut BUFFER.0[..]) } + /// Returns the total size in bytes of the underlying FDT blob pub fn size(&self) -> usize { self.tree.totalsize() } + /// Returns the root node of this device tree pub fn root(&self) -> TNode { self.index.root() } + /// Looks up a node by its `/absolute/path`. pub fn find_absolute(&'a self, path: &str) -> Option> { let mut path = path.trim_start_matches('/'); let mut current = self.root(); @@ -86,6 +97,8 @@ impl<'a> DeviceTree<'a> { } } + /// Resolves an alias to its real path by looking it up in the `/aliases` node of the + /// device tree. pub fn resolve_alias(&'a self, name: &'a str) -> Option<&'a str> { if name.starts_with('/') { Some(name) @@ -114,6 +127,12 @@ impl<'a> DeviceTree<'a> { } } + /// Returns the start and end address of the initrd specified by this device tree. + /// + /// The initrd info is read from: + /// + /// * `/chosen/linux,initrd-start` + /// * `/chosen/linux,initrd-end` pub fn chosen_initrd(&self) -> Option<(PhysicalAddress, PhysicalAddress)> { let root = self.root(); let chosen = root.child("chosen")?; @@ -135,6 +154,7 @@ impl<'a> DeviceTree<'a> { )) } + /// Returns an iterator over the memory regions specified by this device tree pub fn memory_regions(&self) -> DeviceTreeMemoryRegionIter { DeviceTreeMemoryRegionIter::new(self) } diff --git a/kernel/lib/device-tree/src/util.rs b/kernel/lib/device-tree/src/util.rs index c2377c1e..7bd9eedd 100644 --- a/kernel/lib/device-tree/src/util.rs +++ b/kernel/lib/device-tree/src/util.rs @@ -1,3 +1,4 @@ +//! Utility functions for device tree handling use fdt_rs::index::iters::DevTreeIndexNodeSiblingIter; use libk_mm::{address::PhysicalAddress, phys::PhysicalMemoryRegion};