device_tree_parser 0.4.0

High-performance Device Tree Blob (DTB) parser with zero-copy parsing and ergonomic APIs for embedded systems
Documentation

Device Tree Parser

A Rust library for parsing device tree files, supporting both binary Device Tree Blob (DTB) format and device tree source files. This library is designed for embedded systems and provides no_std compatibility with alloc support.

Features

  • Binary DTB Parsing: Parse Device Tree Blob files with full structure validation
  • Memory Reservation Support: Handle memory reservation blocks in DTB files
  • Property Interpolation: Support for device tree property value parsing and interpolation
  • Address Translation (v0.4.0+): Full support for device tree address translation between bus domains
  • Ergonomic API (v0.3.0+): Modern Rust trait implementations for intuitive usage
    • Index traits for property and child access: node["property"], node[0]
    • IntoIterator for natural iteration: for child in &node
    • TryFrom for type-safe value conversions: u32::try_from(&value)
    • Display for pretty-printing nodes and properties
  • Iterator Interface: Convenient tree traversal with NodeIterator
  • No-std Compatible: Works in embedded environments with heap allocation
  • Integration Tested: Validated against real QEMU-generated DTB files

Installation

Add this to your Cargo.toml:

[dependencies]
device_tree_parser = "0.4.0"

Usage

Basic DTB Parsing

use device_tree_parser::{DeviceTreeParser, DtbHeader};

// Load your DTB data
let dtb_data = std::fs::read("path/to/your.dtb")?;

// Create a parser
let parser = DeviceTreeParser::new(&dtb_data);

// Parse the header
let (_remaining, header) = DtbHeader::parse(&dtb_data)?;
println!("DTB Version: {}", header.version());

Tree Traversal

use device_tree_parser::{DeviceTreeParser, NodeIterator};

let dtb_data = std::fs::read("your.dtb")?;
let parser = DeviceTreeParser::new(&dtb_data);

// Parse the device tree
let tree = parser.parse_tree()?;

// Iterate through child nodes using ergonomic IntoIterator trait
for child in &tree {
    println!("Node: {}", child.name);
    
    // Access properties using Index trait
    if child.has_property("reg") {
        println!("  Register: {}", child["reg"].value);
    }
    
    // Type-safe property value conversion using TryFrom
    if let Some(prop) = child.find_property("reg") {
        if let Ok(values) = Vec::<u32>::try_from(&prop.value) {
            println!("  Register values: {:?}", values);
        }
    }
}

Ergonomic API Features (v0.3.0+)

use std::convert::TryFrom;
use device_tree_parser::DeviceTreeParser;

let dtb_data = std::fs::read("your.dtb")?;
let parser = DeviceTreeParser::new(&dtb_data);
let tree = parser.parse_tree()?;

// Property access using Index trait
let model = &tree["model"];  // Instead of tree.find_property("model")

// Child access using Index trait  
let first_child = &tree[0];  // Access first child node

// Natural iteration over children
for child in &tree {
    println!("Child: {}", child.name);
}

// Type-safe conversions using TryFrom
if let Some(prop) = tree.find_property("reg") {
    let address: u32 = u32::try_from(&prop.value)?;
    let byte_data: &[u8] = <&[u8]>::try_from(&prop.value)?;
    let string_val: &str = <&str>::try_from(&prop.value)?;
}

Device Tree Address Translation (v0.4.0+)

Device trees often contain complex bus hierarchies where device addresses need to be translated to CPU-visible addresses. This library provides comprehensive address translation support:

use device_tree_parser::{DeviceTreeParser, DtbError};

let dtb_data = std::fs::read("your.dtb")?;
let parser = DeviceTreeParser::new(&dtb_data);
let tree = parser.parse_tree()?;

// Enhanced MMIO region discovery with address translation
let raw_regions = parser.discover_mmio_regions_translated(false)?;      // Device addresses
let cpu_regions = parser.discover_mmio_regions_translated(true)?;       // CPU addresses

for ((device_addr, size), (cpu_addr, _)) in raw_regions.iter().zip(cpu_regions.iter()) {
    println!("Device 0x{:x} -> CPU 0x{:x} (size: {} bytes)", 
             device_addr, cpu_addr, size);
}

// Find a specific device and translate its registers
let uart_nodes = tree.find_compatible_nodes("arm,pl011");
for uart in uart_nodes {
    // Get CPU-visible MMIO regions for this UART
    let mmio_regions = uart.mmio_regions(None)?;
    for (addr, size) in mmio_regions {
        println!("UART MMIO: 0x{:x} - 0x{:x}", addr, addr + size - 1);
    }
    
    // Manual address translation for specific addresses
    if uart.has_property("ranges") {
        // Try translating a specific device address
        let device_addr = 0x1000;
        match uart.translate_address(device_addr, None, 2) {
            Ok(cpu_addr) => println!("Device 0x{:x} -> CPU 0x{:x}", device_addr, cpu_addr),
            Err(e) => println!("Translation failed: {}", e),
        }
        
        // For complex hierarchies, use recursive translation
        match uart.translate_address_recursive(device_addr, 2, 10) {
            Ok(cpu_addr) => println!("Recursive: 0x{:x} -> 0x{:x}", device_addr, cpu_addr),
            Err(e) => println!("Recursive translation failed: {}", e),
        }
    }
}

// Parse address/size cell specifications
for node in tree.iter_nodes() {
    if node.has_property("ranges") {
        let address_cells = node.address_cells()?;
        let size_cells = node.size_cells()?;
        println!("Node '{}': {}+{} address cells", 
                 node.name, address_cells, size_cells);
        
        // Parse the ranges property
        let ranges = node.ranges(None, address_cells)?;
        for range in ranges {
            println!("  Range: 0x{:x} -> 0x{:x} (size: 0x{:x})",
                     range.child_address(), range.parent_address(), range.size());
        }
    }
}

Address Translation Features

  • Single-level translation: translate_address() for direct parent-child translation
  • Multi-level translation: translate_address_recursive() for complex bus hierarchies
  • Automatic cell parsing: Handles variable address/size cell configurations
  • Range validation: Comprehensive bounds checking and overflow protection
  • Error handling: Detailed error types for translation failures
  • Helper methods: Convenient APIs for common translation scenarios
  • Integration tested: Validated against real ARM SoC device trees

Documentation

📚 Complete Documentation Suite

  • Device Tree Introduction - New to device trees? Start here for a comprehensive introduction to concepts, terminology, and real-world examples.

  • Device Tree Specification Reference - Detailed mapping of our implementation to the official Device Tree Specification, including compliance notes and specification references.

  • Migration Guide - Upgrading from v0.1.x? Complete guide with before/after examples for the v0.2.0 zero-copy API changes.

  • Performance & Benchmarks - Comprehensive benchmark documentation and performance analysis of the zero-copy implementation.

🔗 External References

Building

Prerequisites

This project uses:

  • Rust 2024 edition
  • Nix flake for reproducible development environment (optional)

Build Commands

# Build the library
cargo build

# Build with release optimizations
cargo build --release

# Quick syntax checking
cargo check

Development Environment (Nix)

If you have Nix installed, you can use the provided flake:

# Enter development shell
nix develop

# Or run commands directly
nix run .#cargo -- build

Testing

Unit Tests

# Run all tests
cargo test

# Run specific test
cargo test test_parser_creation

# Compile tests without running
cargo test --no-run

Integration Tests

The project includes integration tests using real DTB files generated by QEMU:

# Run integration tests specifically
cargo test integration_tests

# Run tests with output
cargo test -- --nocapture

Test Data

Test data is located in test-data/ and includes:

  • virt.dtb: QEMU virtual machine device tree for testing

Benchmarks

The library includes comprehensive benchmarks to measure parsing performance:

# Run all benchmarks
cargo bench

# Quick benchmarks (faster, less accurate)
./bench.sh quick

# Specific benchmark categories
./bench.sh parsing    # Core parsing performance
./bench.sh properties # Property access performance
./bench.sh api        # High-level API performance

# View HTML reports
./bench.sh report

See BENCHMARKS.md for detailed information on benchmark categories and interpretation.

Code Quality

Pre-commit Hook

A pre-commit hook is automatically set up to ensure code quality. It runs:

  • cargo fmt --check to verify code formatting
  • cargo clippy to catch common issues and enforce best practices

The hook automatically runs when you commit and will prevent commits if checks fail.

Manual Code Quality Checks

# Format code
cargo fmt

# Run lints (if clippy is available)
cargo clippy --all-targets --all-features

# Check formatting without modifying files
cargo fmt --check

Architecture

The library is structured as follows:

  • src/lib.rs - Main library interface and public API
  • src/dtb/ - Device Tree Blob parsing implementation
    • header.rs - DTB header parsing and validation
    • parser.rs - Core DTB parsing logic
    • tokens.rs - DTB token structure parsing
    • tree.rs - Device tree node and property structures
    • memory.rs - Memory reservation block handling
    • error.rs - Error types and handling
  • src/parser.rs - General parsing utilities
  • src/integration_tests.rs - Real-world DTB parsing tests

API Reference

Main Types

  • DeviceTreeParser - Main parser interface
  • DtbHeader - Device tree blob header
  • DeviceTreeNode - Individual device tree nodes
  • Property - Device tree properties
  • PropertyValue - Typed property values with ergonomic trait implementations
  • MemoryReservation - Memory reservation entries
  • NodeIterator - Iterator for tree traversal
  • AddressSpec - Address/size cell specifications for device tree addressing
  • AddressRange - Address translation range mapping between bus domains

Ergonomic Traits (v0.3.0+)

  • Index<&str> for DeviceTreeNode - Property access: node["property_name"]
  • Index<usize> for DeviceTreeNode - Child access: node[0]
  • IntoIterator for &DeviceTreeNode - Natural iteration: for child in &node
  • TryFrom<&PropertyValue> for u32, u64, Vec<u32>, &str, &[u8] - Type-safe conversions
  • Display for DeviceTreeNode and Property - Pretty-printing
  • Default for creating empty instances

Error Handling

The library uses DtbError for comprehensive error reporting during parsing operations.

No-std Support

This library is no_std compatible and only requires alloc for heap allocation. It's suitable for embedded systems and bare-metal environments.

Contributing

  1. Ensure all tests pass: cargo test
  2. Format code: cargo fmt
  3. Run lints: cargo clippy
  4. Follow the existing code style and patterns
  5. Add tests for new functionality

License

This project is licensed under the MIT License - see the LICENSE file for details.

Changelog

See CHANGELOG.md for detailed version history and release notes.