diff --git a/.claude/settings.local.json b/.claude/settings.local.json index aa06865..b76feb2 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -10,7 +10,12 @@ "WebFetch(domain:elinux.org)", "mcp__web-search-prime__webSearchPrime", "WebFetch(domain:www.devicetree.org)", - "Bash(RUST_BACKTRACE=1 cargo test -p fdt-parser -- test_pci2 --nocapture)" + "Bash(RUST_BACKTRACE=1 cargo test -p fdt-parser -- test_pci2 --nocapture)", + "Bash(cargo check:*)", + "Bash(cargo clippy:*)", + "mcp__zread__get_repo_structure", + "Bash(find:*)", + "mcp__zread__read_file" ], "deny": [], "ask": [] diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index a92cb82..4916365 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -10,19 +10,17 @@ jobs: fail-fast: false matrix: rust-toolchain: [nightly] - targets: [x86_64-unknown-linux-gnu, x86_64-unknown-none, riscv64gc-unknown-none-elf, aarch64-unknown-none-softfloat] steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@nightly with: toolchain: ${{ matrix.rust-toolchain }} components: rust-src, clippy, rustfmt - targets: ${{ matrix.targets }} - name: Check rust version run: rustc --version --verbose - name: Check code format run: cargo fmt --all -- --check - name: Clippy - run: cargo clippy --manifest-path ./fdt-parser/Cargo.toml --target ${{ matrix.targets }} --all-features -- -A clippy::new_without_default + run: cargo clippy --all-features - name: Build - run: cargo build -p fdt-parser --target ${{ matrix.targets }} --all-features + run: cargo build --all-features diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 100644 index 414e5a7..0000000 --- a/CLAUDE.md +++ /dev/null @@ -1,83 +0,0 @@ -# CLAUDE.md - -This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. - -## Project Overview - -This is a Rust workspace containing a Flattened Device Tree (FDT) parser library and associated tools. The project implements a pure-Rust, `#![no_std]` parser for device tree blob files based on the devicetree-specification-v0.4. - -## Workspace Structure - -This is a Cargo workspace with three main crates: - -- `fdt-parser/`: Core library crate for parsing FDT files -- `dtb-tool/`: CLI tool for inspecting device tree blobs -- `dtb-file/`: Test data and sample DTB files - -## Common Commands - -### Build and Test -```bash -# Build all workspace members -cargo build - -# Build specific package -cargo build -p fdt-parser -cargo build -p dtb-tool - -# Run tests (only works on x86_64-unknown-linux-gnu target) -cargo test - -# Build for different targets (used in CI) -cargo build -p fdt-parser --target x86_64-unknown-none -cargo build -p fdt-parser --target riscv64gc-unknown-none-elf -cargo build -p fdt-parser --target aarch64-unknown-none-softfloat -``` - -### Code Quality -```bash -# Format code -cargo fmt --all - -# Run clippy -cargo clippy --manifest-path ./fdt-parser/Cargo.toml --all-features -- -A clippy::new_without_default - -# Check formatting without modifying files -cargo fmt --all -- --check -``` - -### Running the CLI Tool -```bash -# Run the dtb-tool CLI -cargo run -p dtb-tool -- -``` - -## Architecture - -The `fdt-parser` crate provides two main parsing implementations: - -1. **Base Parser** (`base/`): Direct parsing approach that walks the FDT structure -2. **Cached Parser** (`cache/`): Builds an index/cached representation for faster repeated lookups - -Key modules: -- `header.rs`: FDT header structure parsing -- `property.rs`: Device tree property handling -- `data.rs`: Data access utilities -- `base/node/`: Node-specific parsing logic (memory, interrupts, clocks, etc.) -- `cache/`: Cached FDT representation with node management - -## CI Configuration - -The project uses GitHub Actions with: -- Rust nightly toolchain -- Multi-target builds (Linux, no-std x86_64, RISC-V, ARM) -- Code formatting checks -- Clippy linting -- Unit tests (Linux target only) - -## Development Notes - -- The project is `#![no_std]` compatible and uses `alloc` for dynamic memory -- Uses `heapless` for collections without allocator -- Error handling via `thiserror` crate -- Test DTB files are included in `dtb-file/src/dtb/` for development \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 8f30eac..b433063 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,3 +1,6 @@ [workspace] -resolver = "2" -members = ["fdt-parser", "dtb-tool", "dtb-file", "fdt-raw", "fdt-edit"] +members = ["dtb-file", "fdt-edit", "fdt-raw"] +resolver = "3" + +[workspace.dependencies] +dtb-file = {path = "dtb-file"} diff --git a/demo_find_all.pdb b/demo_find_all.pdb deleted file mode 100644 index 92e07ef..0000000 Binary files a/demo_find_all.pdb and /dev/null differ diff --git a/dtb-tool/Cargo.toml b/dtb-tool/Cargo.toml deleted file mode 100644 index aaf6a83..0000000 --- a/dtb-tool/Cargo.toml +++ /dev/null @@ -1,18 +0,0 @@ -[package] -name = "dtb-tool" -version = "0.1.1" -authors = ["Zhourui "] -edition = "2021" -repository = "https://github.com/drivercraft/fdt-parser" -documentation = "https://docs.rs/fdt-parser" -license = "MPL-2.0" -description = "A pure-Rust `#![no_std]` crate for parsing FDT" -keywords = ["devicetree", "fdt", "dt", "dtb"] -categories = ["embedded", "no-std"] -publish = false - -[dependencies] -log = "0.4" -env_logger = "0.11" -clap = { version = "4.5.20", features = ["derive"] } -fdt-parser = { path = "../fdt-parser" } diff --git a/dtb-tool/src/main.rs b/dtb-tool/src/main.rs deleted file mode 100644 index dba6f9f..0000000 --- a/dtb-tool/src/main.rs +++ /dev/null @@ -1,61 +0,0 @@ -//! Command-line tool for inspecting and converting Device Tree Blob (DTB) files. -//! -//! This tool reads a DTB file, parses it using the `fdt_parser` library, -//! and outputs a human-readable text representation showing the device tree -//! structure including nodes, compatible strings, and memory reservations. - -use clap::Parser; -use fdt_parser::Fdt; -use std::io::Write; - -/// Command-line arguments for the DTB parser tool. -#[derive(Parser, Debug)] -#[command(version, about, long_about = None)] -struct Args { - /// Path to the input DTB file - #[arg(short, long)] - input: String, - - /// Path to the output text file - #[arg(short, long)] - output: String, -} - -fn main() { - let args = Args::parse(); - - let data = std::fs::read(&args.input).unwrap(); - - let fdt = Fdt::from_bytes(&data).unwrap(); - - let _ = std::fs::remove_file(&args.output); - let mut file = std::fs::File::create(&args.output).unwrap(); - - writeln!(file, "/dts-v{}/;", fdt.version()).unwrap(); - for region in fdt.memory_reservation_blocks() { - writeln!(file, "/memreserve/ {:?};", region).unwrap(); - } - - for node in fdt.all_nodes() { - let space = "\t".repeat(node.level().saturating_sub(1)); - writeln!(file, "{}{}", space, node.name()).unwrap(); - - let compatibles = node.compatibles(); - let non_empty_compatibles: Vec<_> = - compatibles.into_iter().filter(|s| !s.is_empty()).collect(); - if !non_empty_compatibles.is_empty() { - writeln!(file, "{} -compatible: ", space).unwrap(); - for cap in non_empty_compatibles { - writeln!(file, "{} {:?}", space, cap).unwrap(); - } - } - - // Note: reg() method may not be available in cache parser - // if let Some(reg) = node.reg() { - // writeln!(file, "{} - reg: ", space).unwrap(); - // for cell in reg { - // writeln!(file, "{} {:?}", space, cell).unwrap(); - // } - // } - } -} diff --git a/example_all_nodes.rs b/example_all_nodes.rs deleted file mode 100644 index 5c89aa8..0000000 --- a/example_all_nodes.rs +++ /dev/null @@ -1,61 +0,0 @@ -// Example: Using the all_nodes function -extern crate alloc; -use alloc::{string::String, vec::Vec}; - -use fdt_edit::{Fdt, Node, NodeRef}; - -fn main() { - // Create an example FDT - let mut fdt = Fdt::new(); - - // Add some example nodes - { - let root = &mut fdt.root; - let mut soc = Node::new_raw("soc"); - let mut uart = Node::new_raw("uart@4000"); - let mut gpio = Node::new_raw("gpio@5000"); - let mut led = Node::new_raw("led"); - - // Set properties - uart.add_property(fdt_edit::Property::new_str("compatible", "vendor,uart")); - gpio.add_property(fdt_edit::Property::new_str("compatible", "vendor,gpio")); - led.add_property(fdt_edit::Property::new_str("compatible", "vendor,led")); - - // Build the tree structure - gpio.add_child(led); - soc.add_child(uart); - soc.add_child(gpio); - root.add_child(soc); - } - - // Use all_nodes to get all nodes - let all_nodes: Vec = fdt.all_nodes().collect(); - - println!("All nodes in FDT (depth-first traversal):"); - for (i, node_ref) in all_nodes.iter().enumerate() { - println!( - "{}: Node '{}', Path: '{}', Depth: {}", - i + 1, - node_ref.node.name(), - node_ref.context.current_path, - node_ref.context.depth - ); - - // Display the node's compatible property - let compatibles: Vec<&str> = node_ref.compatibles(); - if !compatibles.is_empty() { - println!(" Compatible: {:?}", compatibles); - } - } - - // Use find_compatible to find specific nodes - let uart_nodes = fdt.find_compatible(&["vendor,uart"]); - println!("\nFound UART nodes:"); - for node_ref in uart_nodes { - println!( - " Node: {}, Full path: '{}'", - node_ref.node.name(), - node_ref.context.current_path - ); - } -} diff --git a/fdt-edit/Cargo.toml b/fdt-edit/Cargo.toml index ab57a7a..dbb0be9 100644 --- a/fdt-edit/Cargo.toml +++ b/fdt-edit/Cargo.toml @@ -2,24 +2,21 @@ authors = ["周睿 "] categories = ["embedded", "no-std", "hardware-support"] description = "A high-level library for creating, editing, and encoding Flattened Device Tree (FDT) structures" -documentation = "https://docs.rs/fdt-edit" edition = "2024" -exclude = [".git*", "*.md", "tests/"] homepage = "https://github.com/drivercraft/fdt-parser" keywords = ["device-tree", "dtb", "embedded", "no-std", "editor"] license = "MIT OR Apache-2.0" name = "fdt-edit" -readme = "README.md" repository = "https://github.com/drivercraft/fdt-parser" -version = "0.1.7" +version = "0.2.0" [dependencies] -enum_dispatch = "0.3.13" -fdt-raw = {version = "0.1.0", path = "../fdt-raw"} +fdt-raw = {version = "0.2", path = "../fdt-raw"} log = "0.4" +enum_dispatch = "0.3" [dev-dependencies] -dtb-file = {path = "../dtb-file"} +dtb-file.workspace = true env_logger = "0.11" [features] diff --git a/fdt-edit/LLMs.txt b/fdt-edit/LLMs.txt deleted file mode 100644 index 574eb5d..0000000 --- a/fdt-edit/LLMs.txt +++ /dev/null @@ -1,481 +0,0 @@ -# fdt-edit Project Context for LLMs - -## Project Overview - -fdt-edit is a high-level Rust library for creating, editing, and encoding Flattened Device Tree (FDT) structures. It is part of the fdt-parser workspace and provides comprehensive functionality for device tree manipulation with full `no_std` compatibility. - -## Key Information - -- **Project Name**: fdt-edit -- **Version**: 0.1.1 -- **Language**: Rust (edition 2021) -- **License**: MIT OR Apache-2.0 -- **Repository**: https://github.com/drivercraft/fdt-parser -- **Dependencies**: fdt-raw (low-level parsing), log, enum_dispatch -- **Target**: Embedded and no_std environments -- **Features**: Complete device tree parsing, editing, and encoding - -## Architecture - -### Core Components - -1. **Fdt Structure** (`src/fdt.rs`) - Main device tree container - - Creates empty device trees: `Fdt::new()` - - Parses from raw DTB bytes: `Fdt::from_bytes(&dtb_data)` - - Parses from raw pointer: `unsafe { Fdt::from_ptr(ptr) }` - - Encodes back to DTB format: `fdt.encode()` - - Manages node tree, memory reservations, and phandle cache - - Provides Display trait for complete DTS output - - Overlay application support - - Alias resolution and phandle-based node lookup - -2. **Node System** (`src/node/mod.rs`) - Hierarchical node structure - - Core `Node` struct with property and child management - - Node iteration and references (`NodeRef`, `NodeMut`, `NodeKind`) - - Generic node operations and context handling - - Formatted node display with configurable options - - Specialized node types: memory, clock, pci, interrupt controller - -3. **Property System** (`src/prop/mod.rs`) - Device tree properties - - Type-safe property access (u32, u64, strings, byte arrays) - - Property value encoding/decoding with proper endianness - - Support for common property types (reg, ranges, compatible) - -4. **Encoding System** (`src/encode.rs`) - DTB generation - - `FdtEncoder` converts in-memory structures to DTB format - - `FdtData` wrapper for encoded data with access methods - -5. **Context Management** (`src/ctx.rs`) - Tree traversal context - - Maintains current path and inheritance context - - Manages `#address-cells`, `#size-cells` propagation - - Tracks interrupt parent relationships - -## Key Data Types and Enums - -```rust -// Core types -pub struct Fdt { /* device tree container */ } -pub struct Node { /* tree node with properties and children */ } -pub struct Property { /* device tree property */ } - -// Node references (immutable) -pub enum NodeRef<'a> { - Generic(NodeRefGen<'a>), - Pci(NodeRefPci<'a>), - Clock(NodeRefClock<'a>), - InterruptController(NodeRefInterruptController<'a>), - Memory(NodeRefMemory<'a>), -} - -// Node references (mutable) -pub enum NodeMut<'a> { - Generic(NodeMutGen<'a>), -} - -// Node kind for pattern matching -pub enum NodeKind<'a> { - Generic(NodeRefGen<'a>), - Pci(NodeRefPci<'a>), - Clock(NodeRefClock<'a>), - InterruptController(NodeRefInterruptController<'a>), - Memory(NodeRefMemory<'a>), -} - -// Supporting types -pub struct MemoryReservation { /* memory reservation entry */ } -pub struct Context<'a> { /* traversal context */ } -pub struct RegInfo { /* register information */ } -pub struct RangesEntry { /* address translation entry */ } -pub enum Status { /* node status (okay/disabled) */ } -``` - -## Complete API Reference - -### Fdt Operations - -#### Creation and Parsing -```rust -// Create empty device tree -let fdt = Fdt::new(); - -// Parse from DTB data -let fdt = Fdt::from_bytes(&dtb_data)?; - -// Parse from pointer (unsafe) -let fdt = unsafe { Fdt::from_ptr(ptr)? }; -``` - -#### Access and Modification -```rust -// Access root node -let root = fdt.root(); -let mut root_mut = fdt.root_mut(); - -// Encode to DTB format -let fdt_data = fdt.encode(); - -// Display as DTS -println!("{}", fdt); - -// Memory reservations -for reservation in &fdt.memory_reservations { - println!("Reserved: 0x{:x}-0x{:x}", reservation.address, reservation.address + reservation.size); -} -``` - -#### Overlay Support -```rust -// Basic overlay application -fdt.apply_overlay(&overlay_fdt)?; - -// Overlay with delete support -fdt.apply_overlay_with_delete(&overlay, Some(delete_fdt))?; -``` - -#### Alias Resolution and phandle Management -```rust -// Get all aliases -for (alias, target) in fdt.aliases() { - println!("Alias: {} -> {}", alias, target); -} - -// Resolve specific alias -if let Some(target) = fdt.resolve_alias("serial0") { - let node = fdt.get_by_path(target); -} - -// Find nodes by phandle -let node = fdt.find_by_phandle(phandle)?; -let mut node_mut = fdt.find_by_phandle_mut(phandle)?; - -// Rebuild phandle cache -fdt.rebuild_phandle_cache(); -``` - -#### Node Navigation and Search -```rust -// Path-based access -let node = fdt.get_by_path("/chosen"); // Exact path match -let mut node_mut = fdt.get_by_path_mut("/soc"); // Mutable access - -// Pattern-based search -let virtio_nodes: Vec<_> = fdt.find_by_path("/virtio_mmio").collect(); -let compatible_nodes = fdt.find_compatible(&["vendor,device"]); - -// Global iteration -for node in fdt.all_nodes() { /* iterate all nodes */ } -for node in fdt.all_nodes_mut() { /* mutable iteration */ } -``` - -### Node Manipulation - -#### Creation and Structure -```rust -// Create nodes -let mut node = Node::new("test-device@12340000"); -let node = Node::new("soc"); // Creates empty node - -// Tree structure -node.add_child(child_node); -let removed_child = node.remove_child("child-name"); - -// Node hierarchy -let child = node.get_child("child-name"); -let mut child_mut = node.get_child_mut("child-name"); -for child in node.children() { /* iterate children */ } -for child in node.children_mut() { /* mutable iteration */ } -``` - -#### Property Management -```rust -// Property access -let prop = node.get_property("compatible"); -let mut prop_mut = node.get_property_mut("reg"); -let removed_prop = node.remove_property("status"); - -// Node properties -node.set_property(Property::new("compatible", b"vendor,test\0")); -node.set_property(Property::new("reg", &[0x12340000u32, 0x1000u32])); -``` - -### Property Operations - -#### Creation and Access -```rust -// Create properties -let prop = Property::new("reg", data); // From raw bytes -let prop = Property::new("compatible", b"vendor,test\0"); - -// Type-safe access -let value = prop.get_u32()?; // Get 32-bit value -let values = prop.get_u32_iter().collect::>(); // Multiple u32 values -let value = prop.get_u64()?; // Get 64-bit value -let string = prop.as_str()?; // Get string -let strings = prop.as_str_iter().collect::>(); // Multiple strings -let reader = prop.as_reader(); // Raw data access -``` - -#### Property Modification -```rust -// Set property values -prop.set_u32_ls(&[0x12345678]); // Set multiple u32 values (little-endian) -prop.set_u64(0x123456789abcdef0); -prop.set_string("test-value"); -prop.set_string_ls(&["value1", "value2"]); -``` - -### Specialized Node Operations - -#### Memory Nodes -```rust -for node in fdt.all_nodes() { - if let NodeKind::Memory(mem) = node.as_ref() { - println!("Memory node: {}", mem.name()); - for region in mem.regions() { - println!(" Region: 0x{:x}-0x{:x}", region.address, region.address + region.size); - } - if let Some(device_type) = mem.device_type() { - println!(" Type: {}", device_type); - } - } -} -``` - -#### Clock Nodes -```rust -for node in fdt.all_nodes() { - if let NodeKind::Clock(clock) = node.as_ref() { - println!("Clock: {}", clock.name()); - println!(" #clock-cells: {}", clock.clock_cells); - - // Clock output names - for (i, name) in clock.clock_output_names.iter().enumerate() { - println!(" Output {}: {}", i, name); - } - - // Clock references - for clock_ref in clock.clocks() { - println!(" Clock ref: phandle={}, cells={}", clock_ref.phandle, clock_ref.cells); - } - } -} -``` - -#### PCI Nodes -```rust -for node in fdt.all_nodes() { - if let NodeKind::Pci(pci) = node.as_ref() { - println!("PCI node: {}", pci.name()); - - // Bus range - if let Some(range) = pci.bus_range() { - println!(" Bus range: {}-{}", range.start, range.end); - } - - // Address ranges - if let Some(ranges) = pci.ranges() { - for range in ranges { - println!(" Range: {:x?}", range); - } - } - - // Interrupt mapping - if let Ok(interrupt_map) = pci.interrupt_map() { - for entry in interrupt_map { - println!(" Interrupt map: {:x?}", entry); - } - } - } -} -``` - -#### Interrupt Controllers -```rust -for node in fdt.all_nodes() { - if let NodeKind::InterruptController(ic) = node.as_ref() { - println!("Interrupt controller: {}", ic.name()); - - if let Some(cells) = ic.interrupt_cells() { - println!(" #interrupt-cells: {}", cells); - } - - if let Some(addr_cells) = ic.interrupt_address_cells() { - println!(" #address-cells: {}", addr_cells); - } - - println!(" Is controller: {}", ic.is_interrupt_controller()); - - for compatible in ic.compatibles() { - println!(" Compatible: {}", compatible); - } - } -} -``` - -### Register and Range Operations -```rust -// Reg property access -for node in fdt.all_nodes() { - if let Some(regs) = node.regs() { - for reg in regs { - println!("Reg: addr=0x{:x}, child_addr=0x{:x}, size={:?}", - reg.address, reg.child_bus_address, reg.size); - } - } -} - -// Ranges property (address translation) -for node in fdt.all_nodes() { - if let Some(ranges) = node.ranges(node.address_cells().unwrap_or(2)) { - for range in ranges { - println!("Range: child={:x}-{:x}, parent={:x}-{:x}, size={:x}", - range.child_bus_address, range.child_bus_address + range.size, - range.parent_bus_address, range.parent_bus_address + range.size, - range.size); - } - } -} -``` - -### Display and Formatting -```rust -// FDT display (complete DTS) -println!("{}", fdt); - -// Node display with options -let display = NodeDisplay::new(&node) - .indent(4) - .show_address(true) - .show_size(true); -println!("{}", display); - -// NodeRef display -let ref_display = NodeRefDisplay::new(&node_ref) - .indent(2) - .show_details(true); -println!("{}", ref_display); -``` - -## Testing Framework - -### Test Organization -- `tests/edit.rs` - Round-trip parsing and encoding validation -- `tests/memory.rs` - Memory node functionality -- `tests/clock.rs` - Clock node detection and properties -- `tests/pci.rs` - PCI node operations -- `tests/irq.rs` - Interrupt controller handling -- `tests/range.rs` - Register and range property testing -- `tests/remove_node.rs` - Node removal operations -- `tests/display_debug.rs` - Display and debug formatting -- `tests/find2.rs` - Advanced search operations - -### Test Data Sources -- QEMU device trees (ARM virtio) -- Raspberry Pi 4B device trees -- Phytium platform device trees -- Custom test device trees - -### Test Validation Methods -- Round-trip compatibility (parse → modify → encode → compare) -- DTC tool comparison (`dtc -I dtb -O dts`) -- Property value validation -- Tree structure integrity checks - -## Error Handling - -The library uses `FdtError` for comprehensive error reporting: -- Parse errors from invalid DTB data -- Node path resolution failures -- Property access errors -- Encoding validation errors - -## Development and Testing - -### Build Commands -```bash -# Build library -cargo build -p fdt-edit - -# Run all tests -cargo test -p fdt-edit - -# Format code -cargo fmt -p fdt-edit - -# Run clippy -cargo clippy -p fdt-edit -``` - -### Test Execution -```bash -# Run specific test -cargo test -p fdt-edit test_memory_node_detection - -# Run tests with logging -RUST_LOG=debug cargo test -p fdt-edit - -# Round-trip test validation -cargo test -p fdt-edit test_parse_and_rebuild -``` - -## Integration Examples - -### Complete Device Tree Processing -```rust -use fdt_edit::*; - -// Load and parse -let dtb_data = std::fs::read("device.dtb")?; -let mut fdt = Fdt::from_bytes(&dtb_data)?; - -// Find and modify memory nodes -for node in fdt.all_nodes_mut() { - if let NodeMut::Generic(mut node) = node { - // Check if this is a memory node - if let Some(device_type) = node.get_property("device_type") { - if let Some("memory") = device_type.as_str() { - // Modify memory properties - } - } - } -} - -// Add new node -let mut new_node = Node::new("test-device"); -new_node.set_property(Property::new("compatible", b"vendor,test\0")); -new_node.set_property(Property::new("reg", &[0x12340000u32, 0x1000u32])); -fdt.root_mut().add_child(new_node); - -// Apply overlay -let overlay_fdt = Fdt::from_bytes(&overlay_data)?; -fdt.apply_overlay(&overlay_fdt)?; - -// Save modified device tree -let modified_dtb = fdt.encode(); -std::fs::write("modified.dtb", modified_dtb.as_bytes())?; - -// Export as DTS -std::fs::write("modified.dts", format!("{}", fdt)); -``` - -## Limitations and Constraints - -### Current Limitations -- Property editing APIs are partially implemented -- Some specialized node types may need additional features -- Large device trees may have memory constraints in `no_std` environments - -### Platform Considerations -- Designed for both `std` and `no_std` environments -- Uses `alloc` crate for dynamic memory when available -- Endianness handling for cross-platform compatibility - -## Future Development Roadmap - -### Planned Features -- Complete property CRUD operations -- Enhanced node manipulation APIs -- Additional specialized node types -- Performance optimizations for large trees -- Streaming parsing support for very large device trees -- Validation and linting tools diff --git a/fdt-edit/README.md b/fdt-edit/README.md deleted file mode 100644 index 65d1bed..0000000 --- a/fdt-edit/README.md +++ /dev/null @@ -1,233 +0,0 @@ -# fdt-edit - -A high-level Rust library for creating, editing, and encoding Flattened Device Tree (FDT) structures. - -## Overview - -`fdt-edit` is a feature-rich device tree manipulation library built on top of `fdt-raw`. It provides comprehensive functionality for creating new device trees from scratch, modifying existing device trees, and encoding the edited device trees into standard DTB format. - -## Features - -- **Complete device tree editing**: Full CRUD operations for nodes and properties -- **Type-safe node operations**: Specialized node types (clocks, memory, PCI, interrupt controllers, etc.) -- **Efficient encoder**: Converts in-memory device tree structures to standard DTB format -- **phandle management**: Automatic phandle allocation and reference management -- **Memory reservation support**: Complete memory reservation region operations -- **`no_std` compatible**: Suitable for embedded environments - -## Core Components - -### Fdt Structure -An editable device tree container that: -- Parses from raw DTB data -- Creates new empty device trees -- Manages phandle cache -- Encodes to DTB format - -### Node System -Supports multiple specialized node types: -- **Clock nodes**: Clock sources and clock consumers -- **Memory nodes**: Memory region definitions -- **PCI nodes**: PCI buses and devices -- **Interrupt controllers**: Interrupt mapping and management -- **Generic nodes**: Customizable node types - -### Property System -- **Type-safe properties**: Support for various data types -- **Automatic property management**: Intelligent property CRUD operations -- **Formatted display**: Friendly node and property display - -## Quick Start - -```rust -use fdt_edit::Fdt; - -// Parse existing DTB from bytes -let raw_data = include_bytes!("path/to/device-tree.dtb"); -let fdt = Fdt::from_bytes(&raw_data)?; - -// Access nodes by path -let node = fdt.get_by_path("/chosen"); -if let Some(chosen) = node { - println!("Found chosen node: {}", chosen.name()); -} - -// Encode back to DTB format -let dtb_data = fdt.encode(); -std::fs::write("output.dtb", dtb_data.as_bytes())?; -``` - -### Node Traversal and Searching - -```rust -use fdt_edit::{Fdt, NodeKind}; - -let fdt = Fdt::from_bytes(&dtb_data)?; - -// Iterate through all nodes -for node in fdt.all_nodes() { - println!("Node: {} at path {}", node.name(), node.path()); - - // Match specialized node types - match node.as_ref() { - NodeKind::Memory(mem) => { - println!(" Memory node with regions:"); - for region in mem.regions() { - println!(" address=0x{:x}, size=0x{:x}", region.address, region.size); - } - } - NodeKind::Clock(clock) => { - println!(" Clock node: {} (#clock-cells={})", clock.name(), clock.clock_cells); - } - NodeKind::Pci(pci) => { - if let Some(range) = pci.bus_range() { - println!(" PCI bus range: {:?}", range); - } - } - _ => { - println!(" Generic node"); - } - } -} - -// Find nodes by path pattern -let virtio_nodes: Vec<_> = fdt.find_by_path("/virtio_mmio").collect(); -println!("Found {} virtio_mmio nodes", virtio_nodes.len()); -``` - -### Node Modification and Creation - -```rust -use fdt_edit::{Fdt, Node}; - -let mut fdt = Fdt::from_bytes(&dtb_data)?; - -// Create new node manually -let mut new_node = Node::new("test-device@12340000"); -// Add properties (API in development) -// new_node.add_property("compatible", &["vendor,test-device"]); -// new_node.add_property("reg", &[0x12340000u64, 0x1000u64]); - -// Add to root node -fdt.root.add_child(new_node); - -// Remove existing node -if fdt.get_by_path("/psci").is_some() { - let removed = fdt.remove_node("/psci")?; - println!("Removed psci node: {}", removed.unwrap().name()); -} - -// Save the modified device tree -let modified_dtb = fdt.encode(); -std::fs::write("modified.dtb", modified_dtb.as_bytes())?; -``` - -### Specialized Node Access - -```rust -use fdt_edit::{Fdt, NodeKind}; - -let fdt = Fdt::from_bytes(&dtb_data)?; - -// Find and work with memory nodes -for node in fdt.all_nodes() { - if let NodeKind::Memory(mem) = node.as_ref() { - let regions = mem.regions(); - if !regions.is_empty() { - println!("Memory node '{}' has {} regions:", mem.name(), regions.len()); - for (i, region) in regions.iter().enumerate() { - println!(" Region {}: 0x{:x}-0x{:x}", i, region.address, region.address + region.size); - } - } - } -} - -// Find clock nodes -let mut clock_count = 0; -for node in fdt.all_nodes() { - if let NodeKind::Clock(clock) = node.as_ref() { - clock_count += 1; - println!("Clock {}: cells={}, output-names={:?}", - clock.name(), - clock.clock_cells, - clock.clock_output_names); - } -} -``` - -### Display as Device Tree Source - -```rust -use fdt_edit::Fdt; - -let fdt = Fdt::from_bytes(&dtb_data)?; - -// Display as DTS format (including memory reservations) -println!("{}", fdt); -// Output will show: -// /dts-v1/; -// /memreserve/ 0x80000000 0x100000; -// / { -// #address-cells = <0x2>; -// #size-cells = <0x2>; -// compatible = "qemu,arm64"; -// ... -// }; -``` - -## Current Status - -This library is under active development. Currently supported features: -- ✅ Parse DTB files into editable structures -- ✅ Encode device trees back to DTB format -- ✅ Display device trees in DTS format -- ✅ Access to memory reservations -- 🚧 Node editing APIs (in development) - -## Dependencies - -- `fdt-raw` - Low-level FDT parsing library -- `log = "0.4"` - Logging support -- `enum_dispatch = "0.3.13"` - Enum dispatch optimization - -## Dev Dependencies - -- `dtb-file` - Test data -- `env_logger = "0.11"` - Logger implementation - -## Testing - -The library includes comprehensive tests that verify round-trip compatibility: - -```bash -cargo test -``` - -The main test (`test_parse_and_rebuild`) ensures that: -1. A DTB file can be parsed successfully -2. The parsed structure can be encoded back to DTB -3. The original and rebuilt DTB files produce identical DTS output when using `dtc` - -## License - -This project is licensed under open source licenses. Please see the LICENSE file in the project root for specific license types. - -## Contributing - -Issues and Pull Requests are welcome. Please ensure: - -1. Code follows the project's formatting standards (`cargo fmt`) -2. All tests pass (`cargo test`) -3. Clippy checks pass (`cargo clippy`) -4. New features include appropriate test cases - -## Related Projects - -- [fdt-raw](../fdt-raw/) - Low-level FDT parsing library -- [fdt-parser](../fdt-parser/) - High-level cached FDT parser -- [dtb-tool](../dtb-tool/) - DTB file inspection tool -- [dtb-file](../dtb-file/) - Test data package - -## Examples - -More usage examples can be found in the source code test files, particularly in `tests/edit.rs`. \ No newline at end of file diff --git a/fdt-edit/examples/fdt_debug_demo.rs b/fdt-edit/examples/fdt_debug_demo.rs deleted file mode 100644 index 179049b..0000000 --- a/fdt-edit/examples/fdt_debug_demo.rs +++ /dev/null @@ -1,26 +0,0 @@ -//! FDT deep debug demonstration -//! -//! Demonstrates how to use the new deep debug functionality to traverse -//! and print all nodes in the device tree. - -use dtb_file::fdt_rpi_4b; -use fdt_edit::Fdt; - -fn main() -> Result<(), Box> { - env_logger::init(); - - // Create FDT from RPI 4B DTB data - let raw_data = fdt_rpi_4b(); - let fdt = Fdt::from_bytes(&raw_data)?; - - println!("=== FDT Basic Debug Information ==="); - // Basic debug format (compact) - println!("{:?}", fdt); - println!(); - - println!("=== FDT Deep Debug Information ==="); - // Deep debug format (traverses all nodes) - println!("{:#?}", fdt); - - Ok(()) -} diff --git a/fdt-edit/src/ctx.rs b/fdt-edit/src/ctx.rs deleted file mode 100644 index f2c2771..0000000 --- a/fdt-edit/src/ctx.rs +++ /dev/null @@ -1,160 +0,0 @@ -//! Context for FDT traversal and node lookup. -//! -//! This module provides the `Context` type which maintains state during -//! FDT parsing and traversal, including parent references, phandle mappings, -//! and inherited properties like address-cells and size-cells. - -use alloc::{collections::BTreeMap, string::String, vec::Vec}; - -use fdt_raw::{Phandle, Status}; - -use crate::{Node, RangesEntry}; - -// ============================================================================ -// FDT Context -// ============================================================================ - -/// Traversal context storing parent node reference stack. -/// -/// The context maintains state during FDT parsing and tree traversal, -/// including the stack of parent nodes from root to the current position -/// and mappings for efficient node lookups by phandle. -#[derive(Clone, Default)] -pub struct Context<'a> { - /// Parent node reference stack (from root to current node's parent) - /// The stack bottom is the root node, the stack top is the direct parent - pub parents: Vec<&'a Node>, - - /// Phandle to node reference mapping - /// Used for fast node lookup by phandle (e.g., interrupt parent) - pub phandle_map: BTreeMap, -} - -impl<'a> Context<'a> { - /// Creates a new empty context. - pub fn new() -> Self { - Self::default() - } - - /// Returns the current path as a string. - pub fn current_path(&self) -> String { - self.parents - .iter() - .map(|n| n.name()) - .collect::>() - .join("/") - } - - /// Creates a context for the root node. - pub fn for_root() -> Self { - Self::default() - } - - /// Returns the current depth (parent count + 1). - pub fn depth(&self) -> usize { - self.parents.len() + 1 - } - - /// Returns the direct parent node. - pub fn parent(&self) -> Option<&'a Node> { - self.parents.last().copied() - } - - /// Returns the parent's #address-cells value. - /// - /// Gets the value from the direct parent node, or returns 2 as default. - pub fn parent_address_cells(&self) -> u32 { - self.parent().and_then(|p| p.address_cells()).unwrap_or(2) - } - - /// Returns the parent's #size-cells value. - /// - /// Gets the value from the direct parent node, or returns 1 as default. - pub fn parent_size_cells(&self) -> u32 { - self.parent().and_then(|p| p.size_cells()).unwrap_or(1) - } - - /// Finds the interrupt parent phandle. - /// - /// Searches upward through the parent stack to find the nearest - /// interrupt-parent property. - pub fn interrupt_parent(&self) -> Option { - for parent in self.parents.iter().rev() { - if let Some(phandle) = parent.interrupt_parent() { - return Some(phandle); - } - } - None - } - - /// Checks if the node is disabled. - /// - /// Returns true if any parent in the stack has status = "disabled". - pub fn is_disabled(&self) -> bool { - for parent in &self.parents { - if matches!(parent.status(), Some(Status::Disabled)) { - return true; - } - } - false - } - - /// Collects ranges from all parent nodes for address translation. - /// - /// Returns a stack of ranges from root to parent, used for translating - /// device addresses to CPU physical addresses. - pub fn collect_ranges(&self) -> Vec> { - let mut ranges_stack = Vec::new(); - let mut prev_address_cells = 2; // Root node default - - for parent in &self.parents { - if let Some(ranges) = parent.ranges(prev_address_cells) { - ranges_stack.push(ranges); - } - // Update address cells to current node's value for next level - prev_address_cells = parent.address_cells().unwrap_or(2); - } - - ranges_stack - } - - /// Returns the most recent ranges layer (for current node's address translation). - pub fn current_ranges(&self) -> Option> { - // Need parent node to get ranges - if self.parents.is_empty() { - return None; - } - - let parent = self.parents.last()?; - - // Get parent node's parent's address_cells - let grandparent_address_cells = if self.parents.len() >= 2 { - self.parents[self.parents.len() - 2] - .address_cells() - .unwrap_or(2) - } else { - 2 // Root node default - }; - parent.ranges(grandparent_address_cells) - } - - /// Pushes a node onto the parent stack. - pub fn push(&mut self, node: &'a Node) { - self.parents.push(node); - } - - /// Finds a node by its phandle value. - pub fn find_by_phandle(&self, phandle: Phandle) -> Option<&'a Node> { - self.phandle_map.get(&phandle).copied() - } - - /// Builds a phandle mapping from a node tree. - pub fn build_phandle_map_from_node(node: &'a Node, map: &mut BTreeMap) { - if let Some(phandle) = node.phandle() { - map.insert(phandle, node); - } - for child in node.children() { - Self::build_phandle_map_from_node(child, map); - } - } -} diff --git a/fdt-edit/src/encode.rs b/fdt-edit/src/encode.rs index 02a4f4d..a5329e0 100644 --- a/fdt-edit/src/encode.rs +++ b/fdt-edit/src/encode.rs @@ -1,27 +1,25 @@ -//! FDT encoding module. +//! FDT 编码模块 //! -//! This module handles serialization of the `Fdt` structure into the -//! DTB (Device Tree Blob) binary format. +//! 将 Fdt 结构序列化为 DTB 二进制格式 use alloc::{string::String, vec::Vec}; use core::ops::Deref; + use fdt_raw::{FDT_MAGIC, Token}; -use crate::{Fdt, Node}; +use crate::{Fdt, NodeId}; -/// FDT binary data container. -/// -/// Wraps the encoded DTB data and provides access to the underlying bytes. +/// FDT 二进制数据 #[derive(Clone, Debug)] pub struct FdtData(Vec); impl FdtData { - /// Returns the data length in bytes. + /// 获取数据长度(字节) pub fn len(&self) -> usize { self.0.len() * 4 } - /// Returns true if the data is empty. + /// 数据是否为空 pub fn is_empty(&self) -> bool { self.0.is_empty() } @@ -46,10 +44,7 @@ impl AsRef<[u8]> for FdtData { } } -/// FDT encoder for serializing to DTB format. -/// -/// This encoder walks the node tree and generates the binary DTB format -/// according to the Device Tree Specification. +/// FDT 编码器 pub struct FdtEncoder<'a> { fdt: &'a Fdt, struct_data: Vec, @@ -58,7 +53,7 @@ pub struct FdtEncoder<'a> { } impl<'a> FdtEncoder<'a> { - /// Creates a new encoder for the given FDT. + /// 创建新的编码器 pub fn new(fdt: &'a Fdt) -> Self { Self { fdt, @@ -68,7 +63,7 @@ impl<'a> FdtEncoder<'a> { } } - /// Gets or adds a string to the strings block, returning its offset. + /// 获取或添加字符串,返回偏移量 fn get_or_add_string(&mut self, s: &str) -> u32 { for (existing, offset) in &self.string_offsets { if existing == s { @@ -83,7 +78,7 @@ impl<'a> FdtEncoder<'a> { offset } - /// Writes a BEGIN_NODE token and node name. + /// 写入 BEGIN_NODE token 和节点名 fn write_begin_node(&mut self, name: &str) { let begin_token: u32 = Token::BeginNode.into(); self.struct_data.push(begin_token.to_be()); @@ -101,13 +96,13 @@ impl<'a> FdtEncoder<'a> { } } - /// Writes an END_NODE token. + /// 写入 END_NODE token fn write_end_node(&mut self) { let end_token: u32 = Token::EndNode.into(); self.struct_data.push(end_token.to_be()); } - /// Writes a property to the structure block. + /// 写入属性 fn write_property(&mut self, name: &str, data: &[u8]) { let prop_token: u32 = Token::Prop.into(); self.struct_data.push(prop_token.to_be()); @@ -129,38 +124,43 @@ impl<'a> FdtEncoder<'a> { } } - /// Performs the encoding and returns the binary DTB data. + /// 执行编码 pub fn encode(mut self) -> FdtData { - // Recursively encode node tree - self.encode_node(&self.fdt.root.clone()); + // 从根节点开始递归编码节点树 + self.encode_node(self.fdt.root_id()); - // Add END token + // 添加 END token let token: u32 = Token::End.into(); self.struct_data.push(token.to_be()); self.finalize() } - /// Recursively encodes a node and its children. - fn encode_node(&mut self, node: &Node) { - // Write BEGIN_NODE and node name - self.write_begin_node(node.name()); + /// 递归编码节点及其子节点(适配 arena 结构) + fn encode_node(&mut self, id: NodeId) { + let node = match self.fdt.node(id) { + Some(n) => n, + None => return, + }; + + // 写入 BEGIN_NODE 和节点名 + self.write_begin_node(&node.name); - // Write all properties (using raw data directly) + // 写入所有属性 for prop in node.properties() { - self.write_property(prop.name(), &prop.data); + self.write_property(&prop.name, &prop.data); } - // Recursively encode child nodes - for child in node.children() { - self.encode_node(child); + // 递归编码子节点 + for &child_id in node.children() { + self.encode_node(child_id); } - // Write END_NODE + // 写入 END_NODE self.write_end_node(); } - /// Generates the final FDT binary data. + /// 生成最终 FDT 数据 fn finalize(self) -> FdtData { let memory_reservations = &self.fdt.memory_reservations; let boot_cpuid_phys = self.fdt.boot_cpuid_phys; diff --git a/fdt-edit/src/fdt.rs b/fdt-edit/src/fdt.rs index 5107372..e18b350 100644 --- a/fdt-edit/src/fdt.rs +++ b/fdt-edit/src/fdt.rs @@ -3,38 +3,43 @@ //! This module provides the main `Fdt` type for creating, modifying, and //! encoding device tree blobs. It supports loading from existing DTB files, //! building new trees programmatically, and applying device tree overlays. +//! +//! All nodes are stored in a flat `BTreeMap` arena. Child +//! relationships are represented as `Vec` inside each `Node`. use alloc::{ collections::BTreeMap, - format, string::{String, ToString}, vec::Vec, }; -pub use fdt_raw::MemoryReservation; -use fdt_raw::{FdtError, Phandle, Status}; - use crate::{ - ClockType, Node, NodeIter, NodeIterMut, NodeKind, NodeMut, NodeRef, - encode::{FdtData, FdtEncoder}, + FdtData, FdtEncoder, FdtError, Node, NodeId, NodeType, NodeTypeMut, NodeView, Phandle, }; +pub use fdt_raw::MemoryReservation; + /// An editable Flattened Device Tree (FDT). /// -/// This structure represents a mutable device tree that can be created from -/// scratch, loaded from an existing DTB file, modified, and encoded back to -/// the binary DTB format. It maintains a phandle cache for efficient node -/// lookups by phandle value. +/// All nodes are stored in a flat `BTreeMap`. The tree structure +/// is maintained through `Vec` children lists in each `Node` and an +/// optional `parent: Option` back-pointer. #[derive(Clone)] pub struct Fdt { /// Boot CPU ID pub boot_cpuid_phys: u32, /// Memory reservation block entries pub memory_reservations: Vec, - /// Root node of the device tree - pub root: Node, - /// Cache mapping phandles to full node paths - phandle_cache: BTreeMap, + /// Flat storage for all nodes + nodes: BTreeMap, + /// Parent mapping: child_id -> parent_id + parent_map: BTreeMap, + /// Root node ID + root: NodeId, + /// Next unique node ID to allocate + next_id: NodeId, + /// Cache mapping phandles to node IDs for fast lookup + phandle_cache: BTreeMap, } impl Default for Fdt { @@ -44,597 +49,384 @@ impl Default for Fdt { } impl Fdt { - /// Creates a new empty FDT. + /// Creates a new empty FDT with an empty root node. pub fn new() -> Self { + let mut nodes = BTreeMap::new(); + let root_id: NodeId = 0; + nodes.insert(root_id, Node::new("")); Self { boot_cpuid_phys: 0, memory_reservations: Vec::new(), - root: Node::new(""), + nodes, + parent_map: BTreeMap::new(), + root: root_id, + next_id: 1, phandle_cache: BTreeMap::new(), } } - /// Parses an FDT from raw byte data. - pub fn from_bytes(data: &[u8]) -> Result { - let raw_fdt = fdt_raw::Fdt::from_bytes(data)?; - Self::from_raw(&raw_fdt) + /// Allocates a new node in the arena, returning its unique ID. + fn alloc_node(&mut self, node: Node) -> NodeId { + let id = self.next_id; + self.next_id += 1; + self.nodes.insert(id, node); + id } - /// Parses an FDT from a raw pointer. - /// - /// # Safety - /// - /// The caller must ensure that the pointer is valid and points to a - /// valid FDT data structure. - pub unsafe fn from_ptr(ptr: *mut u8) -> Result { - let raw_fdt = unsafe { fdt_raw::Fdt::from_ptr(ptr)? }; - Self::from_raw(&raw_fdt) + /// Returns the root node ID. + pub fn root_id(&self) -> NodeId { + self.root } - /// Converts from a raw FDT parser instance. - fn from_raw(raw_fdt: &fdt_raw::Fdt) -> Result { - let header = raw_fdt.header(); - - let mut fdt = Fdt { - boot_cpuid_phys: header.boot_cpuid_phys, - memory_reservations: raw_fdt.memory_reservations().collect(), - root: Node::new(""), - phandle_cache: BTreeMap::new(), - }; + /// Returns the parent node ID for the given node, if any. + pub fn parent_of(&self, id: NodeId) -> Option { + self.parent_map.get(&id).copied() + } - // Build node tree using a stack to track parent nodes - let mut node_stack: Vec = Vec::new(); + /// Returns a reference to the node with the given ID. + pub fn node(&self, id: NodeId) -> Option<&Node> { + self.nodes.get(&id) + } - for raw_node in raw_fdt.all_nodes() { - let level = raw_node.level(); - let node = Node::from(&raw_node); + /// Returns a mutable reference to the node with the given ID. + pub fn node_mut(&mut self, id: NodeId) -> Option<&mut Node> { + self.nodes.get_mut(&id) + } - // Pop stack until we reach the correct parent level - while node_stack.len() > level { - let child = node_stack.pop().unwrap(); - if let Some(parent) = node_stack.last_mut() { - parent.add_child(child); - } else { - // This is the root node - fdt.root = child; - } - } + /// Returns the total number of nodes in the tree. + pub fn node_count(&self) -> usize { + self.nodes.len() + } - node_stack.push(node); + /// Adds a new node as a child of `parent`, returning the new node's ID. + /// + /// Sets the new node's `parent` field and updates the parent's children list + /// and name cache. + pub fn add_node(&mut self, parent: NodeId, node: Node) -> NodeId { + let name = node.name.clone(); + let id = self.alloc_node(node); + self.parent_map.insert(id, parent); + + if let Some(parent_node) = self.nodes.get_mut(&parent) { + parent_node.add_child(&name, id); } - // Pop all remaining nodes - while let Some(child) = node_stack.pop() { - if let Some(parent) = node_stack.last_mut() { - parent.add_child(child); - } else { - // This is the root node - fdt.root = child; - } + // Update phandle cache if the new node has a phandle + if let Some(phandle) = self.nodes.get(&id).and_then(|n| n.phandle()) { + self.phandle_cache.insert(phandle, id); } - // Build phandle cache - fdt.rebuild_phandle_cache(); - - Ok(fdt) + id } - /// Rebuilds the phandle cache by scanning all nodes. - pub fn rebuild_phandle_cache(&mut self) { - self.phandle_cache.clear(); - let root_clone = self.root.clone(); - self.build_phandle_cache_recursive(&root_clone, "/"); + /// Removes a child node (by name) from the given parent, and recursively + /// removes the entire subtree from the arena. + /// + /// Returns the removed node's ID if found. + pub fn remove_node(&mut self, parent: NodeId, name: &str) -> Option { + let removed_id = { + let parent_node = self.nodes.get_mut(&parent)?; + parent_node.remove_child(name)? + }; + + // Rebuild parent's name cache (needs arena access for child names) + self.rebuild_name_cache(parent); + + // Recursively remove the subtree + self.remove_subtree(removed_id); + + Some(removed_id) } - /// Recursively builds the phandle cache starting from a node. - fn build_phandle_cache_recursive(&mut self, node: &Node, current_path: &str) { - // Check if node has a phandle property - if let Some(phandle) = node.phandle() { - self.phandle_cache.insert(phandle, current_path.to_string()); + /// Recursively removes a node and all its descendants from the arena. + fn remove_subtree(&mut self, id: NodeId) { + if let Some(node) = self.nodes.remove(&id) { + // Remove from parent map + self.parent_map.remove(&id); + // Remove from phandle cache + if let Some(phandle) = node.phandle() { + self.phandle_cache.remove(&phandle); + } + // Recursively remove children + for child_id in node.children() { + self.remove_subtree(*child_id); + } } + } - // Recursively process child nodes - for child in node.children() { - let child_name = child.name(); - let child_path = if current_path == "/" { - format!("/{}", child_name) - } else { - format!("{}/{}", current_path, child_name) + /// Rebuilds the name cache for a node based on its current children. + fn rebuild_name_cache(&mut self, id: NodeId) { + let names: Vec<(String, usize)> = { + let node = match self.nodes.get(&id) { + Some(n) => n, + None => return, }; - self.build_phandle_cache_recursive(child, &child_path); + node.children() + .iter() + .enumerate() + .filter_map(|(idx, &child_id)| { + self.nodes.get(&child_id).map(|c| (c.name.clone(), idx)) + }) + .collect() + }; + if let Some(node) = self.nodes.get_mut(&id) { + node.rebuild_name_cache_with_names(&names); } } - /// Normalizes a path: resolves aliases or ensures leading '/'. + pub fn resolve_alias(&self, alias: &str) -> Option<&str> { + let root = self.nodes.get(&self.root)?; + let alias_node_id = root.get_child("aliases")?; + let alias_node = self.nodes.get(&alias_node_id)?; + let prop = alias_node.get_property(alias)?; + prop.as_str() + } + + /// 规范化路径:如果是别名则解析为完整路径,否则确保以 / 开头 fn normalize_path(&self, path: &str) -> Option { if path.starts_with('/') { Some(path.to_string()) } else { - // Try to resolve as an alias + // 尝试解析别名 self.resolve_alias(path).map(|s| s.to_string()) } } - /// Resolves an alias to its full path. + /// Looks up a node by its full path (e.g. "/soc/uart@10000"), + /// returning its `NodeId`. /// - /// Looks up the alias in the /aliases node and returns the - /// corresponding path string. - pub fn resolve_alias(&self, alias: &str) -> Option<&str> { - let aliases_node = self.get_by_path("/aliases")?; - let prop = aliases_node.find_property(alias)?; - prop.as_str() - } - - /// Returns all aliases as (name, path) pairs. - pub fn aliases(&self) -> Vec<(String, String)> { - let mut result = Vec::new(); - if let Some(aliases_node) = self.get_by_path("/aliases") { - for prop in aliases_node.properties() { - let name = prop.name().to_string(); - let path = prop.as_str().unwrap().to_string(); - result.push((name, path)); - } + /// The root node is matched by "/" or "". + pub fn get_by_path_id(&self, path: &str) -> Option { + let normalized_path = self.normalize_path(path)?; + let normalized = normalized_path.trim_start_matches('/'); + if normalized.is_empty() { + return Some(self.root); } - result - } - - /// Finds a node by its phandle value. - pub fn find_by_phandle(&self, phandle: Phandle) -> Option> { - let path = self.phandle_cache.get(&phandle)?.clone(); - self.get_by_path(&path) - } - - /// Finds a node by phandle (mutable reference). - pub fn find_by_phandle_mut(&mut self, phandle: Phandle) -> Option> { - let path = self.phandle_cache.get(&phandle)?.clone(); - self.get_by_path_mut(&path) - } - /// Returns the root node. - pub fn root<'a>(&'a self) -> NodeRef<'a> { - self.get_by_path("/").unwrap() + let mut current = self.root; + for part in normalized.split('/') { + let node = self.nodes.get(¤t)?; + current = node.get_child(part)?; + } + Some(current) } - /// Returns the root node (mutable reference). - pub fn root_mut<'a>(&'a mut self) -> NodeMut<'a> { - self.get_by_path_mut("/").unwrap() + /// Looks up a node by its phandle value, returning its `NodeId`. + pub fn get_by_phandle_id(&self, phandle: Phandle) -> Option { + self.phandle_cache.get(&phandle).copied() } - /// Applies a device tree overlay to this FDT. - /// - /// Supports two overlay formats: - /// 1. Fragment format: contains fragment@N nodes with target/target-path and __overlay__ - /// 2. Simple format: directly contains __overlay__ node - /// - /// # Example - /// - /// ```ignore - /// // Fragment format - /// fragment@0 { - /// target-path = "/soc"; - /// __overlay__ { - /// new_node { ... }; - /// }; - /// }; - /// ``` - pub fn apply_overlay(&mut self, overlay: &Fdt) -> Result<(), FdtError> { - // Iterate through all children of overlay root node - for child in overlay.root.children() { - if child.name().starts_with("fragment@") || child.name() == "fragment" { - // Fragment format - self.apply_fragment(child)?; - } else if child.name() == "__overlay__" { - // Simple format: apply directly to root - self.merge_overlay_to_root(child)?; - } else if child.name() == "__symbols__" - || child.name() == "__fixups__" - || child.name() == "__local_fixups__" - { - // Skip these special nodes - continue; + /// Computes the full path string for a node by walking up parent links. + pub fn path_of(&self, id: NodeId) -> String { + let mut parts: Vec<&str> = Vec::new(); + let mut cur = id; + while let Some(node) = self.nodes.get(&cur) { + if cur == self.root { + break; + } + parts.push(&node.name); + match self.parent_map.get(&cur) { + Some(&p) => cur = p, + None => break, } } - - // Rebuild phandle cache - self.rebuild_phandle_cache(); - - Ok(()) + parts.reverse(); + if parts.is_empty() { + return String::from("/"); + } + format!("/{}", parts.join("/")) } - /// Applies a single fragment from an overlay. - fn apply_fragment(&mut self, fragment: &Node) -> Result<(), FdtError> { - // Get target path - let target_path = self.resolve_fragment_target(fragment)?; - - // Find __overlay__ child node - let overlay_node = fragment - .get_child("__overlay__") - .ok_or(FdtError::NotFound)?; + /// Removes a node and its subtree by path. + /// + /// Returns the removed node's ID if found. + pub fn remove_by_path(&mut self, path: &str) -> Option { + let normalized = path.trim_start_matches('/'); + if normalized.is_empty() { + return None; // Cannot remove root + } - // Find target node and apply overlay - let target_path_owned = target_path.to_string(); + let parts: Vec<&str> = normalized.split('/').collect(); + let child_name = *parts.last()?; - // Apply overlay to target node - self.apply_overlay_to_target(&target_path_owned, overlay_node)?; + // Find the parent node + let parent_path = &parts[..parts.len() - 1]; + let mut parent_id = self.root; + for &part in parent_path { + let node = self.nodes.get(&parent_id)?; + parent_id = node.get_child(part)?; + } - Ok(()) + self.remove_node(parent_id, child_name) } - /// Resolves the target path of a fragment. - fn resolve_fragment_target(&self, fragment: &Node) -> Result { - // Prefer target-path (string path) - if let Some(prop) = fragment.get_property("target-path") { - return Ok(prop.as_str().ok_or(FdtError::Utf8Parse)?.to_string()); + /// Returns a depth-first iterator over all node IDs in the tree. + pub fn iter_node_ids(&self) -> NodeDfsIter<'_> { + NodeDfsIter { + fdt: self, + stack: vec![self.root], } + } - // Use target (phandle reference) - if let Some(prop) = fragment.get_property("target") { - let ph = prop.get_u32().ok_or(FdtError::InvalidInput)?; - let ph = Phandle::from(ph); - - // Find node by phandle and build path - if let Some(node) = self.find_by_phandle(ph) { - return Ok(node.path()); - } - } + /// Parses an FDT from raw byte data. + pub fn from_bytes(data: &[u8]) -> Result { + let raw_fdt = fdt_raw::Fdt::from_bytes(data)?; + Self::from_raw(&raw_fdt) + } - Err(FdtError::NotFound) + /// Parses an FDT from a raw pointer. + /// + /// # Safety + /// + /// The caller must ensure that the pointer is valid and points to a + /// valid FDT data structure. + pub unsafe fn from_ptr(ptr: *mut u8) -> Result { + let raw_fdt = unsafe { fdt_raw::Fdt::from_ptr(ptr)? }; + Self::from_raw(&raw_fdt) } - /// Applies an overlay to a target node. - fn apply_overlay_to_target( - &mut self, - target_path: &str, - overlay_node: &Node, - ) -> Result<(), FdtError> { - // Find target node - let mut target = self - .get_by_path_mut(target_path) - .ok_or(FdtError::NotFound)?; + /// Converts from a raw FDT parser instance. + fn from_raw(raw_fdt: &fdt_raw::Fdt) -> Result { + let header = raw_fdt.header(); - // Merge overlay properties and child nodes - Self::merge_nodes(target.node, overlay_node); + let mut fdt = Fdt { + boot_cpuid_phys: header.boot_cpuid_phys, + memory_reservations: raw_fdt.memory_reservations().collect(), + nodes: BTreeMap::new(), + parent_map: BTreeMap::new(), + root: 0, + next_id: 0, + phandle_cache: BTreeMap::new(), + }; - Ok(()) - } + // Build node tree using a stack to track parent node IDs. + // raw_fdt.all_nodes() yields nodes in DFS pre-order with level info. + // We use a stack of (NodeId, level) to find parents. + let mut id_stack: Vec<(NodeId, usize)> = Vec::new(); - /// Merges an overlay node to the root node. - fn merge_overlay_to_root(&mut self, overlay: &Node) -> Result<(), FdtError> { - // Merge properties and child nodes to root - for prop in overlay.properties() { - self.root.set_property(prop.clone()); - } + for raw_node in raw_fdt.all_nodes() { + let level = raw_node.level(); + let node = Node::from(&raw_node); + let node_name = node.name.clone(); - for child in overlay.children() { - let child_name = child.name(); - if let Some(existing) = self.root.get_child_mut(child_name) { - // Merge into existing child node - Self::merge_nodes(existing, child); - } else { - // Add new child node - self.root.add_child(child.clone()); - } - } + // Allocate the node in the arena + let node_id = fdt.alloc_node(node); - Ok(()) - } + // Update phandle cache + if let Some(phandle) = fdt.nodes.get(&node_id).and_then(|n| n.phandle()) { + fdt.phandle_cache.insert(phandle, node_id); + } - /// Recursively merges two nodes. - fn merge_nodes(target: &mut Node, source: &Node) { - // Merge properties (source overrides target) - for prop in source.properties() { - target.set_property(prop.clone()); - } + // Pop the stack until we find the parent at level - 1 + while let Some(&(_, stack_level)) = id_stack.last() { + if stack_level >= level { + id_stack.pop(); + } else { + break; + } + } - // Merge child nodes - for source_child in source.children() { - let child_name = &source_child.name(); - if let Some(target_child) = target.get_child_mut(child_name) { - // Recursive merge - Self::merge_nodes(target_child, source_child); + if let Some(&(parent_id, _)) = id_stack.last() { + // Set parent link + fdt.parent_map.insert(node_id, parent_id); + // Add as child to parent + if let Some(parent) = fdt.nodes.get_mut(&parent_id) { + parent.add_child(&node_name, node_id); + } } else { - // Add new child node - target.add_child(source_child.clone()); + // This is the root node + fdt.root = node_id; } - } - } - /// Applies an overlay with optional deletion of disabled nodes. - /// - /// If a node in the overlay has status = "disabled", the corresponding - /// target node will be disabled or deleted. - pub fn apply_overlay_with_delete( - &mut self, - overlay: &Fdt, - delete_disabled: bool, - ) -> Result<(), FdtError> { - self.apply_overlay(overlay)?; - - if delete_disabled { - // Remove all nodes with status = "disabled" - Self::remove_disabled_nodes(&mut self.root); - self.rebuild_phandle_cache(); + id_stack.push((node_id, level)); } - Ok(()) + Ok(fdt) } - /// Recursively removes disabled nodes. - fn remove_disabled_nodes(node: &mut Node) { - // Remove disabled child nodes - let mut to_remove = Vec::new(); - for child in node.children() { - if matches!(child.status(), Some(Status::Disabled)) { - to_remove.push(child.name().to_string()); - } - } - - for child_name in to_remove { - node.remove_child(&child_name); - } - - // Recursively process remaining child nodes - for child in node.children_mut() { - Self::remove_disabled_nodes(child); - } + /// Looks up a node by path and returns an immutable classified view. + pub fn get_by_path(&self, path: &str) -> Option> { + let id = self.get_by_path_id(path)?; + Some(NodeView::new(self, id).classify()) } - /// Removes a node by exact path. - /// - /// Supports exact path matching only. Aliases are automatically resolved. - /// - /// # Arguments - /// - /// * `path` - Node path (e.g., "soc/gpio@1000", "/soc/gpio@1000", or an alias) - /// - /// # Returns - /// - /// * `Ok(Some(node))` - The removed node - /// * `Ok(None)` - Path not found - /// * `Err(FdtError)` - Invalid path format - /// - /// # Example - /// - /// ```rust - /// # use fdt_edit::{Fdt, Node}; - /// let mut fdt = Fdt::new(); - /// - /// // Add node then remove it - /// let mut soc = Node::new("soc"); - /// soc.add_child(Node::new("gpio@1000")); - /// fdt.root.add_child(soc); - /// - /// // Remove node with exact path - /// let removed = fdt.remove_node("/soc/gpio@1000")?; - /// assert!(removed.is_some()); - /// # Ok::<(), fdt_raw::FdtError>(()) - /// ``` - pub fn remove_node(&mut self, path: &str) -> Result, FdtError> { - let normalized_path = self.normalize_path(path).ok_or(FdtError::InvalidInput)?; - - // Use exact path for removal - let result = self.root.remove_by_path(&normalized_path)?; - - // If removal succeeded but result is None, path doesn't exist - if result.is_none() { - return Err(FdtError::NotFound); - } - - Ok(result) + /// Looks up a node by path and returns a mutable classified view. + pub fn get_by_path_mut(&mut self, path: &str) -> Option> { + let id = self.get_by_path_id(path)?; + Some(NodeView::new(self, id).classify_mut()) } - /// Returns a depth-first iterator over all nodes. - pub fn all_nodes(&self) -> impl Iterator> + '_ { - NodeIter::new(&self.root) + /// Looks up a node by phandle and returns an immutable classified view. + pub fn get_by_phandle(&self, phandle: crate::Phandle) -> Option> { + let id = self.get_by_phandle_id(phandle)?; + Some(NodeView::new(self, id).classify()) } - /// Returns a mutable depth-first iterator over all nodes. - pub fn all_nodes_mut(&mut self) -> impl Iterator> + '_ { - NodeIterMut::new(&mut self.root) + /// Looks up a node by phandle and returns a mutable classified view. + pub fn get_by_phandle_mut(&mut self, phandle: crate::Phandle) -> Option> { + let id = self.get_by_phandle_id(phandle)?; + Some(NodeView::new(self, id).classify_mut()) } - /// Finds nodes by path (supports fuzzy matching). - pub fn find_by_path<'a>(&'a self, path: &str) -> impl Iterator> { - let path = self - .normalize_path(path) - .unwrap_or_else(|| path.to_string()); - - NodeIter::new(&self.root).filter_map(move |node_ref| { - if node_ref.path_eq_fuzzy(&path) { - Some(node_ref) - } else { - None - } - }) + /// Returns a depth-first iterator over `NodeView`s. + fn iter_raw_nodes(&self) -> impl Iterator> { + self.iter_node_ids().map(move |id| NodeView::new(self, id)) } - /// Gets a node by exact path. - pub fn get_by_path<'a>(&'a self, path: &str) -> Option> { - let path = self.normalize_path(path)?; - NodeIter::new(&self.root).find_map(move |node_ref| { - if node_ref.path_eq(&path) { - Some(node_ref) - } else { - None - } - }) + /// Returns a depth-first iterator over classified `NodeType`s. + pub fn all_nodes(&self) -> impl Iterator> { + self.iter_raw_nodes().map(|v| v.classify()) } - /// Gets a node by exact path (mutable reference). - pub fn get_by_path_mut<'a>(&'a mut self, path: &str) -> Option> { - let path = self.normalize_path(path)?; - NodeIterMut::new(&mut self.root).find_map(move |node_mut| { - if node_mut.path_eq(&path) { - Some(node_mut) - } else { - None - } - }) + pub fn root_mut(&mut self) -> NodeTypeMut<'_> { + self.view_typed_mut(self.root).unwrap() } /// Finds nodes with matching compatible strings. - pub fn find_compatible(&self, compatible: &[&str]) -> Vec> { + pub fn find_compatible(&self, compatible: &[&str]) -> Vec> { let mut results = Vec::new(); for node_ref in self.all_nodes() { - let Some(ls) = node_ref.compatible() else { - continue; - }; + let compatibles = node_ref.as_node().compatibles(); + let mut found = false; - for comp in ls { + for comp in compatibles { if compatible.contains(&comp) { results.push(node_ref); + found = true; break; } } + + if found { + continue; + } } results } - /// Serializes the FDT to binary DTB format. + /// Encodes the FDT to DTB binary format. pub fn encode(&self) -> FdtData { FdtEncoder::new(self).encode() } } -impl core::fmt::Display for Fdt { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - // Output DTS header - writeln!(f, "/dts-v1/;")?; - - // Output memory reservation block - for reservation in &self.memory_reservations { - writeln!( - f, - "/memreserve/ 0x{:x} 0x{:x};", - reservation.address, reservation.size - )?; - } - - // Output root node - writeln!(f, "{}", self.root) - } -} - -impl core::fmt::Debug for Fdt { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - if f.alternate() { - // Deep debug format with node traversal - self.fmt_debug_deep(f) - } else { - // Simple debug format - f.debug_struct("Fdt") - .field("boot_cpuid_phys", &self.boot_cpuid_phys) - .field("memory_reservations_count", &self.memory_reservations.len()) - .field("root_node_name", &self.root.name) - .field("total_nodes", &self.root.children().len()) - .field("phandle_cache_size", &self.phandle_cache.len()) - .finish() - } - } +/// Depth-first iterator over all node IDs in the tree. +pub struct NodeDfsIter<'a> { + fdt: &'a Fdt, + stack: Vec, } -impl Fdt { - /// Formats the FDT with detailed debug information. - fn fmt_debug_deep(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - writeln!(f, "Fdt {{")?; - writeln!(f, " boot_cpuid_phys: 0x{:x},", self.boot_cpuid_phys)?; - writeln!( - f, - " memory_reservations_count: {},", - self.memory_reservations.len() - )?; - writeln!(f, " phandle_cache_size: {},", self.phandle_cache.len())?; - writeln!(f, " nodes:")?; - - // Iterate through all nodes and print debug info with indentation - for (i, node) in self.all_nodes().enumerate() { - self.fmt_node_debug(f, &node, 2, i)?; - } +impl<'a> Iterator for NodeDfsIter<'a> { + type Item = NodeId; - writeln!(f, "}}") - } - - /// Formats a single node for debug output. - fn fmt_node_debug( - &self, - f: &mut core::fmt::Formatter<'_>, - node: &NodeRef, - indent: usize, - index: usize, - ) -> core::fmt::Result { - // Print indentation - for _ in 0..indent { - write!(f, " ")?; - } - - // Print node index and basic info - write!(f, "[{:03}] {}: ", index, node.name())?; - - // Print type-specific information - match node.as_ref() { - NodeKind::Clock(clock) => { - write!(f, "Clock")?; - if let ClockType::Fixed(fixed) = &clock.kind { - write!(f, " (Fixed, {}Hz)", fixed.frequency)?; - } else { - write!(f, " (Provider)")?; - } - if !clock.clock_output_names.is_empty() { - write!(f, ", outputs: {:?}", clock.clock_output_names)?; - } - write!(f, ", cells={}", clock.clock_cells)?; - } - NodeKind::Pci(pci) => { - write!(f, "PCI")?; - if let Some(bus_range) = pci.bus_range() { - write!(f, " (bus: {:?})", bus_range)?; - } - write!(f, ", interrupt-cells={}", pci.interrupt_cells())?; + fn next(&mut self) -> Option { + let id = self.stack.pop()?; + if let Some(node) = self.fdt.nodes.get(&id) { + // Push children in reverse order so that the first child is visited first + for &child_id in node.children().iter().rev() { + self.stack.push(child_id); } - NodeKind::InterruptController(ic) => { - write!(f, "InterruptController")?; - if let Some(cells) = ic.interrupt_cells() { - write!(f, " (cells={})", cells)?; - } - let compatibles = ic.compatibles(); - if !compatibles.is_empty() { - write!(f, ", compatible: {:?}", compatibles)?; - } - } - NodeKind::Memory(mem) => { - write!(f, "Memory")?; - let regions = mem.regions(); - if !regions.is_empty() { - write!(f, " ({} regions", regions.len())?; - for (i, region) in regions.iter().take(2).enumerate() { - write!(f, ", [{}]: 0x{:x}+0x{:x}", i, region.address, region.size)?; - } - if regions.len() > 2 { - write!(f, ", ...")?; - } - write!(f, ")")?; - } - } - NodeKind::Generic(_) => { - write!(f, "Generic")?; - } - } - - // Print phandle information - if let Some(phandle) = node.phandle() { - write!(f, ", phandle={}", phandle)?; } - - // Print address and size cells information - if let Some(address_cells) = node.address_cells() { - write!(f, ", #address-cells={}", address_cells)?; - } - if let Some(size_cells) = node.size_cells() { - write!(f, ", #size-cells={}", size_cells)?; - } - - writeln!(f)?; - - Ok(()) + Some(id) } } diff --git a/fdt-edit/src/lib.rs b/fdt-edit/src/lib.rs index 72478ac..932075a 100644 --- a/fdt-edit/src/lib.rs +++ b/fdt-edit/src/lib.rs @@ -1,61 +1,20 @@ -//! Device Tree Blob (DTB) editing and manipulation library. -//! -//! This crate provides functionality for creating, modifying, and encoding -//! Flattened Device Tree (FDT) structures. Unlike the parser crates which -//! focus on reading existing device trees, this crate allows you to build -//! and modify device trees programmatically. -//! -//! # Features -//! -//! - `#![no_std]` compatible -//! - Build device trees from scratch -//! - Modify existing device trees -//! - Add/remove nodes and properties -//! - Encode to standard DTB format -//! - Support for overlays -//! -//! # Example -//! -//! ```ignore -//! use fdt_edit::{Fdt, Context, Property, NodeKind}; -//! -//! // Create a new FDT with a context -//! let mut fdt = Fdt::new(&Context::default()); -//! -//! // Add a root node -//! let root = fdt.root_mut(); -//! -//! // Add a memory node -//! let memory = fdt.add_node( -//! root, -//! "memory", -//! NodeKind::Memory -//! ); -//! -//! // Add properties to the memory node -//! fdt.add_property(memory, "reg", Property::Reg(&[ -//! RegInfo { address: 0x80000000, size: 0x10000000 }, -//! ])); -//! -//! // Encode to DTB format -//! let dtb_data = fdt.encode()?; -//! ``` - #![no_std] -#![deny(warnings, missing_docs)] #[macro_use] extern crate alloc; -mod ctx; mod encode; mod fdt; mod node; mod prop; -pub use ctx::Context; -pub use encode::FdtData; -pub use fdt::{Fdt, MemoryReservation}; -pub use node::NodeKind; +pub use fdt_raw::{FdtError, MemoryRegion, Phandle, RegInfo, Status, data::Reader}; + +/// A unique identifier for a node in the `Fdt` arena. +pub type NodeId = usize; + +pub use encode::{FdtData, FdtEncoder}; +pub use fdt::*; +pub use node::view::*; pub use node::*; -pub use prop::{Phandle, Property, RangesEntry, RegInfo, Status}; +pub use prop::*; diff --git a/fdt-edit/src/node/clock.rs b/fdt-edit/src/node/clock.rs deleted file mode 100644 index 826345f..0000000 --- a/fdt-edit/src/node/clock.rs +++ /dev/null @@ -1,233 +0,0 @@ -use core::ops::Deref; - -use alloc::{ - string::{String, ToString}, - vec::Vec, -}; -use fdt_raw::Phandle; - -use crate::node::gerneric::NodeRefGen; - -/// Clock provider type -#[derive(Clone, Debug, PartialEq)] -pub enum ClockType { - /// Fixed clock - Fixed(FixedClock), - /// Normal clock provider - Normal, -} - -/// Fixed clock provider. -/// -/// Represents a fixed-rate clock that always operates at a constant frequency. -#[derive(Clone, Debug, PartialEq)] -pub struct FixedClock { - /// Optional name for the clock - pub name: Option, - /// Clock frequency in Hz - pub frequency: u32, - /// Clock accuracy in ppb (parts per billion) - pub accuracy: Option, -} - -/// Clock reference, used to parse clocks property -/// -/// According to the device tree specification, the clocks property format is: -/// `clocks = <&clock_provider specifier [specifier ...]> [<&clock_provider2 ...>]` -/// -/// Each clock reference consists of a phandle and several specifier cells, -/// the number of specifiers is determined by the target clock provider's `#clock-cells` property. -#[derive(Clone, Debug)] -pub struct ClockRef { - /// Clock name, from clock-names property - pub name: Option, - /// Phandle of the clock provider - pub phandle: Phandle, - /// #clock-cells value of the provider - pub cells: u32, - /// Clock selector (specifier), usually the first value is used to select clock output - /// Length is determined by provider's #clock-cells - pub specifier: Vec, -} - -impl ClockRef { - /// Create a new clock reference - pub fn new(phandle: Phandle, cells: u32, specifier: Vec) -> Self { - Self { - name: None, - phandle, - cells, - specifier, - } - } - - /// Create a named clock reference - pub fn with_name( - name: Option, - phandle: Phandle, - cells: u32, - specifier: Vec, - ) -> Self { - Self { - name, - phandle, - cells, - specifier, - } - } - - /// Get the first value of the selector (usually used to select clock output) - /// - /// Only returns a selector value when `cells > 0`, - /// because providers with `#clock-cells = 0` don't need a selector. - pub fn select(&self) -> Option { - if self.cells > 0 { - self.specifier.first().copied() - } else { - None - } - } -} - -/// Clock provider node reference. -/// -/// Provides specialized access to clock provider nodes and their properties. -#[derive(Clone)] -pub struct NodeRefClock<'a> { - /// The underlying generic node reference - pub node: NodeRefGen<'a>, - /// Names of clock outputs from this provider - pub clock_output_names: Vec, - /// Value of the `#clock-cells` property - pub clock_cells: u32, - /// The type of clock provider - pub kind: ClockType, -} - -impl<'a> NodeRefClock<'a> { - /// Attempts to create a clock provider reference from a generic node. - /// - /// Returns `Err` with the original node if it doesn't have a `#clock-cells` property. - pub fn try_from(node: NodeRefGen<'a>) -> Result> { - // Check if it has clock provider properties - if node.find_property("#clock-cells").is_none() { - return Err(node); - } - - // Get clock-output-names property - let clock_output_names = if let Some(prop) = node.find_property("clock-output-names") { - let iter = prop.as_str_iter(); - iter.map(|s| s.to_string()).collect() - } else { - Vec::new() - }; - - // Get #clock-cells - let clock_cells = node - .find_property("#clock-cells") - .and_then(|prop| prop.get_u32()) - .unwrap_or(0); - - // Determine clock type - let kind = if node.compatibles().any(|c| c == "fixed-clock") { - let frequency = node - .find_property("clock-frequency") - .and_then(|prop| prop.get_u32()) - .unwrap_or(0); - let accuracy = node - .find_property("clock-accuracy") - .and_then(|prop| prop.get_u32()); - let name = clock_output_names.first().cloned(); - - ClockType::Fixed(FixedClock { - name, - frequency, - accuracy, - }) - } else { - ClockType::Normal - }; - - Ok(Self { - node, - clock_output_names, - clock_cells, - kind, - }) - } - - /// Get clock output name (for provider) - pub fn output_name(&self, index: usize) -> Option<&str> { - self.clock_output_names.get(index).map(|s| s.as_str()) - } - - /// Parse clocks property, return list of clock references - /// - /// By looking up each phandle's corresponding clock provider's #clock-cells, - /// correctly parse the specifier length. - pub fn clocks(&self) -> Vec { - let Some(prop) = self.find_property("clocks") else { - return Vec::new(); - }; - - let mut clocks = Vec::new(); - let mut data = prop.as_reader(); - let mut index = 0; - - // Get clock-names for naming - let clock_names = if let Some(prop) = self.find_property("clock-names") { - let iter = prop.as_str_iter(); - iter.map(|s| s.to_string()).collect() - } else { - Vec::new() - }; - - while let Some(phandle_raw) = data.read_u32() { - let phandle = Phandle::from(phandle_raw); - - // Look up provider node by phandle, get its #clock-cells - let clock_cells = if let Some(provider) = self.ctx.find_by_phandle(phandle) { - provider - .get_property("#clock-cells") - .and_then(|p| p.get_u32()) - .unwrap_or(1) // Default 1 cell - } else { - 1 // Default 1 cell - }; - - // Read specifier (based on provider's #clock-cells) - let mut specifier = Vec::with_capacity(clock_cells as usize); - let mut complete = true; - for _ in 0..clock_cells { - if let Some(val) = data.read_u32() { - specifier.push(val); - } else { - // Insufficient data, stop parsing - complete = false; - break; - } - } - - // Only add complete clock reference - if !complete { - break; - } - - // Get corresponding name from clock-names - let name = clock_names.get(index).cloned(); - - clocks.push(ClockRef::with_name(name, phandle, clock_cells, specifier)); - index += 1; - } - - clocks - } -} - -impl<'a> Deref for NodeRefClock<'a> { - type Target = NodeRefGen<'a>; - - fn deref(&self) -> &Self::Target { - &self.node - } -} diff --git a/fdt-edit/src/node/display.rs b/fdt-edit/src/node/display.rs deleted file mode 100644 index 47845e6..0000000 --- a/fdt-edit/src/node/display.rs +++ /dev/null @@ -1,468 +0,0 @@ -use core::fmt; - -use alloc::vec::Vec; - -use crate::{ - ClockType, Node, NodeKind, NodeMut, NodeRef, NodeRefClock, NodeRefInterruptController, - NodeRefMemory, Property, -}; - -/// Formatter for displaying nodes in DTS (device tree source) format. -pub struct NodeDisplay<'a> { - node: &'a Node, - indent: usize, - show_address: bool, - show_size: bool, -} - -impl<'a> NodeDisplay<'a> { - /// Creates a new display formatter for the given node. - pub fn new(node: &'a Node) -> Self { - Self { - node, - indent: 0, - show_address: true, - show_size: true, - } - } - - /// Sets the indentation level for nested nodes. - pub fn indent(mut self, indent: usize) -> Self { - self.indent = indent; - self - } - - /// Sets whether to show address values in properties. - pub fn show_address(mut self, show: bool) -> Self { - self.show_address = show; - self - } - - /// Sets whether to show size values in properties. - pub fn show_size(mut self, show: bool) -> Self { - self.show_size = show; - self - } - - fn format_indent(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - for _ in 0..self.indent { - write!(f, " ")?; - } - Ok(()) - } - - fn format_property(&self, prop: &Property, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.format_indent(f)?; - match prop.name() { - "reg" => { - if self.show_address || self.show_size { - write!(f, "reg = ")?; - self.format_reg_values(prop, f)?; - } else { - write!(f, "reg;")?; - } - } - "compatible" => { - write!(f, "compatible = ")?; - self.format_string_list(prop, f)?; - } - "clock-names" | "pinctrl-names" | "reg-names" => { - write!(f, "{} = ", prop.name())?; - self.format_string_list(prop, f)?; - } - "interrupt-controller" - | "#address-cells" - | "#size-cells" - | "#interrupt-cells" - | "#clock-cells" - | "phandle" => { - write!(f, "{};", prop.name())?; - } - _ => { - write!(f, "{} = ", prop.name())?; - self.format_property_value(prop, f)?; - } - } - writeln!(f) - } - - fn format_reg_values(&self, prop: &Property, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let mut reader = prop.as_reader(); - let mut first = true; - write!(f, "<")?; - - // Get parent's address-cells and size-cells - // Need to get from context, using default values for now - let address_cells = 2; // Default value - let size_cells = 1; // Default value - - while let (Some(addr), Some(size)) = ( - reader.read_cells(address_cells), - reader.read_cells(size_cells), - ) { - if !first { - write!(f, " ")?; - } - first = false; - - if self.show_address { - write!(f, "0x{:x}", addr)?; - } - if self.show_size && size > 0 { - if self.show_address { - write!(f, " ")?; - } - write!(f, "0x{:x}", size)?; - } - } - - write!(f, ">;") - } - - fn format_string_list(&self, prop: &Property, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let iter = prop.as_str_iter(); - let mut first = true; - write!(f, "\"")?; - for s in iter { - if !first { - write!(f, "\", \"")?; - } - first = false; - write!(f, "{}", s)?; - } - write!(f, "\";") - } - - fn format_property_value(&self, prop: &Property, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if let Some(s) = prop.as_str() { - write!(f, "\"{}\";", s) - } else if let Some(u32_val) = prop.get_u32() { - write!(f, "<0x{:x}>;", u32_val) - } else if let Some(u64_val) = prop.get_u64() { - write!(f, "<0x{:x}>;", u64_val) - } else { - // Try to format as byte array - let mut reader = prop.as_reader(); - let mut first = true; - write!(f, "<")?; - while let Some(val) = reader.read_u32() { - if !first { - write!(f, " ")?; - } - first = false; - write!(f, "0x{:02x}", val)?; - } - write!(f, ">;") - } - } -} - -impl<'a> fmt::Display for NodeDisplay<'a> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.format_indent(f)?; - - if self.node.name.is_empty() { - // Root node - writeln!(f, "/ {{")?; - } else { - // Regular node - write!(f, "{}", self.node.name)?; - - // Check if there are address and size properties to display - let mut props = Vec::new(); - for prop in self.node.properties() { - if prop.name() != "reg" { - props.push(prop); - } - } - - if !props.is_empty() { - writeln!(f, " {{")?; - } else { - writeln!(f, ";")?; - return Ok(()); - } - } - - // Output properties - for prop in self.node.properties() { - if prop.name() != "reg" || self.show_address || self.show_size { - self.format_property(prop, f)?; - } - } - - // Output child nodes - for child in self.node.children() { - let child_display = NodeDisplay::new(child) - .indent(self.indent + 1) - .show_address(self.show_address) - .show_size(self.show_size); - write!(f, "{}", child_display)?; - } - - // Close node - self.format_indent(f)?; - writeln!(f, "}};")?; - - Ok(()) - } -} - -impl fmt::Display for Node { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let display = NodeDisplay::new(self); - write!(f, "{}", display) - } -} - -impl fmt::Debug for Node { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Node") - .field("name", &self.name) - .field("children_count", &self.children.len()) - .field("properties_count", &self.properties.len()) - .field("phandle", &self.phandle()) - .field("address_cells", &self.address_cells()) - .field("size_cells", &self.size_cells()) - .finish() - } -} - -/// Display formatter for node references. -/// -/// Formats specialized node references with type-specific information. -pub struct NodeRefDisplay<'a> { - node_ref: &'a NodeRef<'a>, - indent: usize, - show_details: bool, -} - -impl<'a> NodeRefDisplay<'a> { - /// Creates a new display formatter for the given node reference. - pub fn new(node_ref: &'a NodeRef<'a>) -> Self { - Self { - node_ref, - indent: 0, - show_details: true, - } - } - - /// Sets the indentation level for nested nodes. - pub fn indent(mut self, indent: usize) -> Self { - self.indent = indent; - self - } - - /// Sets whether to show detailed type information. - pub fn show_details(mut self, show: bool) -> Self { - self.show_details = show; - self - } - - fn format_type_info(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self.node_ref.as_ref() { - NodeKind::Clock(clock) => { - write!(f, "Clock Node: ")?; - if let ClockType::Fixed(fixed) = &clock.kind { - write!(f, "Fixed Clock (freq={}Hz", fixed.frequency)?; - if let Some(accuracy) = fixed.accuracy { - write!(f, ", accuracy={})", accuracy)?; - } - write!(f, ")")?; - } else { - write!(f, "Clock Provider")?; - } - if !clock.clock_output_names.is_empty() { - write!(f, ", outputs: {:?}", clock.clock_output_names)?; - } - write!(f, ", cells={}", clock.clock_cells)?; - } - NodeKind::Pci(pci) => { - write!(f, "PCI Node")?; - if let Some(bus_range) = pci.bus_range() { - write!(f, " (bus range: {:?})", bus_range)?; - } - write!(f, ", interrupt-cells={}", pci.interrupt_cells())?; - } - NodeKind::InterruptController(ic) => { - write!(f, "Interrupt Controller")?; - if let Some(cells) = ic.interrupt_cells() { - write!(f, " (interrupt-cells={})", cells)?; - } - let compatibles = ic.compatibles(); - if !compatibles.is_empty() { - write!(f, ", compatible: {:?}", compatibles)?; - } - } - NodeKind::Memory(mem) => { - write!(f, "Memory Node")?; - let regions = mem.regions(); - if !regions.is_empty() { - write!(f, " ({} regions)", regions.len())?; - for (i, region) in regions.iter().take(3).enumerate() { - write!( - f, - "\n [{}]: 0x{:x}-0x{:x}", - i, - region.address, - region.address + region.size - )?; - } - } - if let Some(dt) = mem.device_type() { - write!(f, ", device_type={}", dt)?; - } - } - NodeKind::Generic(_) => { - write!(f, "Generic Node")?; - } - } - Ok(()) - } -} - -impl<'a> fmt::Display for NodeRefDisplay<'a> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - for _ in 0..self.indent { - write!(f, " ")?; - } - - if self.show_details { - write!(f, "{}: ", self.node_ref.name())?; - self.format_type_info(f)?; - writeln!(f)?; - - // Add indentation and display DTS - let dts_display = NodeDisplay::new(self.node_ref).indent(self.indent + 1); - write!(f, "{}", dts_display)?; - } else { - write!(f, "{}", self.node_ref.name())?; - } - - Ok(()) - } -} - -impl fmt::Display for NodeRef<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let display = NodeRefDisplay::new(self); - write!(f, "{}", display) - } -} - -impl fmt::Debug for NodeRef<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("NodeRef") - .field("name", &self.name()) - .field("path", &self.path()) - .field( - "node_type", - &match self.as_ref() { - NodeKind::Clock(_) => "Clock", - NodeKind::Pci(_) => "PCI", - NodeKind::InterruptController(_) => "InterruptController", - NodeKind::Memory(_) => "Memory", - NodeKind::Generic(_) => "Generic", - }, - ) - .field("phandle", &self.phandle()) - .finish() - } -} - -impl fmt::Debug for NodeRefClock<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("NodeRefClock") - .field("name", &self.name()) - .field("clock_cells", &self.clock_cells) - .field("clock_type", &self.kind) - .field("output_names", &self.clock_output_names) - .field("phandle", &self.phandle()) - .finish() - } -} - -impl fmt::Debug for NodeRefInterruptController<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("NodeRefInterruptController") - .field("name", &self.name()) - .field("interrupt_cells", &self.interrupt_cells()) - .field("interrupt_address_cells", &self.interrupt_address_cells()) - .field("is_interrupt_controller", &self.is_interrupt_controller()) - .field("compatibles", &self.compatibles()) - .field("phandle", &self.phandle()) - .finish() - } -} - -impl fmt::Debug for NodeRefMemory<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("NodeRefMemory") - .field("name", &self.name()) - .field("regions_count", &self.regions().len()) - .field("device_type", &self.device_type()) - .field("phandle", &self.phandle()) - .finish() - } -} - -impl fmt::Display for NodeRefClock<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let node_ref = crate::NodeRef::Clock(self.clone()); - let display = NodeRefDisplay::new(&node_ref); - write!(f, "{}", display) - } -} - -impl fmt::Display for NodeRefInterruptController<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let node_ref = crate::NodeRef::InterruptController(self.clone()); - let display = NodeRefDisplay::new(&node_ref); - write!(f, "{}", display) - } -} - -impl fmt::Display for NodeRefMemory<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let node_ref = crate::NodeRef::Memory(self.clone()); - let display = NodeRefDisplay::new(&node_ref); - write!(f, "{}", display) - } -} - -impl fmt::Display for NodeMut<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - NodeMut::Gerneric(generic) => { - let display = NodeDisplay::new(generic.node); - write!(f, "{}", display) - } - } - } -} - -impl fmt::Debug for NodeMut<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("NodeMut") - .field( - "name", - &match self { - NodeMut::Gerneric(generic) => generic.node.name(), - }, - ) - .field("node_type", &"Generic") - .field( - "children_count", - &match self { - NodeMut::Gerneric(generic) => generic.node.children.len(), - }, - ) - .field( - "properties_count", - &match self { - NodeMut::Gerneric(generic) => generic.node.properties.len(), - }, - ) - .finish() - } -} diff --git a/fdt-edit/src/node/gerneric.rs b/fdt-edit/src/node/gerneric.rs deleted file mode 100644 index 9d96810..0000000 --- a/fdt-edit/src/node/gerneric.rs +++ /dev/null @@ -1,315 +0,0 @@ -use alloc::{ - string::{String, ToString}, - vec::Vec, -}; -use core::{fmt::Debug, ops::Deref}; -use fdt_raw::RegInfo; - -use crate::{Context, Node, NodeMut, Property}; - -/// Generic node reference with context. -/// -/// Provides basic node access operations with context-aware functionality -/// for traversing and manipulating device tree nodes. -#[derive(Clone)] -pub struct NodeRefGen<'a> { - /// The underlying node reference - pub node: &'a Node, - /// The parsing context containing parent information and path - pub ctx: Context<'a>, -} - -impl<'a> NodeRefGen<'a> { - pub fn find_property(&self, name: &str) -> Option<&'a Property> { - self.node.get_property(name) - } - - pub fn properties(&self) -> impl Iterator { - self.node.properties.iter() - } - - fn op(&'a self) -> RefOp<'a> { - RefOp { - ctx: &self.ctx, - node: self.node, - } - } - - pub fn path(&self) -> String { - self.op().path() - } - - pub fn path_eq(&self, path: &str) -> bool { - self.op().ref_path_eq(path) - } - - pub fn path_eq_fuzzy(&self, path: &str) -> bool { - self.op().ref_path_eq_fuzzy(path) - } - - pub fn regs(&self) -> Option> { - self.op().regs() - } -} - -impl Deref for NodeRefGen<'_> { - type Target = Node; - - fn deref(&self) -> &Self::Target { - self.node - } -} - -/// Generic mutable node reference with context. -/// -/// Provides mutable node operations with context-aware functionality -/// for modifying device tree nodes and their properties. -pub struct NodeMutGen<'a> { - /// The underlying mutable node reference - pub node: &'a mut Node, - /// The parsing context containing parent information and path - pub ctx: Context<'a>, -} - -impl<'a> NodeMutGen<'a> { - fn op(&'a self) -> RefOp<'a> { - RefOp { - ctx: &self.ctx, - node: self.node, - } - } - - pub fn path(&self) -> String { - self.op().path() - } - - pub fn path_eq(&self, path: &str) -> bool { - self.op().ref_path_eq(path) - } - - pub fn path_eq_fuzzy(&self, path: &str) -> bool { - self.op().ref_path_eq_fuzzy(path) - } - - pub fn regs(&self) -> Option> { - self.op().regs() - } - - /// Sets the reg property with automatic address translation. - /// - /// This method converts CPU physical addresses to bus addresses using the - /// parent node's ranges mapping before storing them in the reg property. - pub fn set_regs(&mut self, regs: &[RegInfo]) { - let address_cells = self.ctx.parent_address_cells() as usize; - let size_cells = self.ctx.parent_size_cells() as usize; - let ranges = self.ctx.current_ranges(); - - let mut data = Vec::new(); - - for reg in regs { - // Convert CPU address to bus address - let mut bus_address = reg.address; - if let Some(ref ranges) = ranges { - for r in ranges { - // Check if CPU address is within ranges mapping range - if reg.address >= r.parent_bus_address - && reg.address < r.parent_bus_address + r.length - { - // Reverse conversion: cpu_address -> bus_address - bus_address = reg.address - r.parent_bus_address + r.child_bus_address; - break; - } - } - } - - // Write bus address (big-endian) - if address_cells == 1 { - data.extend_from_slice(&(bus_address as u32).to_be_bytes()); - } else if address_cells == 2 { - data.extend_from_slice(&((bus_address >> 32) as u32).to_be_bytes()); - data.extend_from_slice(&((bus_address & 0xFFFF_FFFF) as u32).to_be_bytes()); - } - - // Write size (big-endian) - if size_cells == 1 { - let size = reg.size.unwrap_or(0); - data.extend_from_slice(&(size as u32).to_be_bytes()); - } else if size_cells == 2 { - let size = reg.size.unwrap_or(0); - data.extend_from_slice(&((size >> 32) as u32).to_be_bytes()); - data.extend_from_slice(&((size & 0xFFFF_FFFF) as u32).to_be_bytes()); - } - } - - let prop = Property::new("reg", data); - self.node.set_property(prop); - } - - /// Adds a child node to this node. - /// - /// This method attaches a child node to the current node, updating the - /// context to include the parent-child relationship, and returns a - /// mutable reference to the newly added child. - pub fn add_child(&mut self, child: Node) -> NodeMut<'a> { - let name = child.name().to_string(); - let mut ctx = self.ctx.clone(); - unsafe { - let node_ptr = self.node as *mut Node; - let node = &*node_ptr; - ctx.push(node); - } - self.node.add_child(child); - let raw = self.node.get_child_mut(&name).unwrap(); - unsafe { - let node_ptr = raw as *mut Node; - let node = &mut *node_ptr; - NodeMut::new(node, ctx) - } - } -} - -impl Debug for NodeRefGen<'_> { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!(f, "NodeRefGen {{ name: {} }}", self.node.name()) - } -} - -impl Debug for NodeMutGen<'_> { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!(f, "NodeMutGen {{ name: {} }}", self.node.name()) - } -} - -/// Internal helper struct for node operations with context. -/// -/// This struct provides common operations that are shared between -/// `NodeRefGen` and `NodeMutGen`, avoiding code duplication. -struct RefOp<'a> { - /// Reference to the parsing context - ctx: &'a Context<'a>, - /// Reference to the node being operated on - node: &'a Node, -} - -impl<'a> RefOp<'a> { - /// Constructs the full path of the node. - /// - /// Combines the current context path with the node name to create - /// the full device tree path. - fn path(&self) -> String { - self.ctx.current_path() + "/" + self.node.name() - } - - /// Checks if the node's path exactly matches the given path. - fn ref_path_eq(&self, path: &str) -> bool { - self.path() == path - } - - /// Checks if the node's path matches the given path using fuzzy matching. - /// - /// Fuzzy matching allows comparing paths without requiring the exact - /// address portion (the `@address` suffix) to match unless explicitly - /// specified. This is useful for matching nodes by name when the - /// specific address is not important. - fn ref_path_eq_fuzzy(&self, path: &str) -> bool { - let mut want = path.trim_matches('/').split("/"); - let got_path = self.path(); - let mut got = got_path.trim_matches('/').split("/"); - let got_count = got.clone().count(); - let mut current = 0; - - loop { - let w = want.next(); - let g = got.next(); - let is_last = current + 1 == got_count; - - match (w, g) { - (Some(w), Some(g)) => { - if w != g && !is_last { - return false; - } - - let name = g.split('@').next().unwrap_or(g); - let addr = g.split('@').nth(1); - - let want_name = w.split('@').next().unwrap_or(w); - let want_addr = w.split('@').nth(1); - - let res = match (addr, want_addr) { - (Some(a), Some(wa)) => name == want_name && a == wa, - (Some(_), None) => name == want_name, - (None, Some(_)) => false, - (None, None) => name == want_name, - }; - if !res { - return false; - } - } - (None, _) => break, - _ => return false, - } - current += 1; - } - true - } - - /// Parses the reg property and returns a list of register regions. - /// - /// This method reads the reg property and performs address translation - /// from child bus addresses to CPU physical addresses using the parent's - /// ranges mapping. - fn regs(&self) -> Option> { - let prop = self.node.get_property("reg")?; - let mut iter = prop.as_reader(); - let address_cells = self.ctx.parent_address_cells() as usize; - let size_cells = self.ctx.parent_size_cells() as usize; - - // Get current ranges from context - let ranges = self.ctx.current_ranges(); - let mut out = vec![]; - let mut size; - - while let Some(mut address) = iter.read_cells(address_cells) { - if size_cells > 0 { - size = iter.read_cells(size_cells); - } else { - size = None; - } - let child_bus_address = address; - - if let Some(ref ranges) = ranges { - for r in ranges { - if child_bus_address >= r.child_bus_address - && child_bus_address < r.child_bus_address + r.length - { - address = child_bus_address - r.child_bus_address + r.parent_bus_address; - break; - } - } - } - - let reg = RegFixed { - address, - child_bus_address, - size, - }; - out.push(reg); - } - - Some(out) - } -} - -/// Fixed register region with address translation information. -/// -/// Represents a single register region from the reg property with both -/// the bus address (stored in the DTB) and the translated CPU physical address. -#[derive(Clone, Copy, Debug)] -pub struct RegFixed { - /// CPU physical address after translation - pub address: u64, - /// Child bus address as stored in the reg property - pub child_bus_address: u64, - /// Size of the register region (None if size-cells is 0) - pub size: Option, -} diff --git a/fdt-edit/src/node/interrupt_controller.rs b/fdt-edit/src/node/interrupt_controller.rs deleted file mode 100644 index 24f86ae..0000000 --- a/fdt-edit/src/node/interrupt_controller.rs +++ /dev/null @@ -1,71 +0,0 @@ -use core::ops::Deref; - -use alloc::vec::Vec; - -use crate::node::gerneric::NodeRefGen; - -/// Interrupt controller node reference. -/// -/// Provides specialized access to interrupt controller nodes and their properties. -#[derive(Clone)] -pub struct NodeRefInterruptController<'a> { - /// The underlying generic node reference - pub node: NodeRefGen<'a>, -} - -impl<'a> NodeRefInterruptController<'a> { - /// Attempts to create an interrupt controller reference from a generic node. - /// - /// Returns `Err` with the original node if it's not an interrupt controller. - pub fn try_from(node: NodeRefGen<'a>) -> Result> { - if !is_interrupt_controller_node(&node) { - return Err(node); - } - Ok(Self { node }) - } - - /// Get #interrupt-cells value - /// - /// This determines how many cells are needed to describe interrupts - /// referencing this controller - pub fn interrupt_cells(&self) -> Option { - self.find_property("#interrupt-cells") - .and_then(|prop| prop.get_u32()) - } - - /// Get #address-cells value (used for interrupt-map) - pub fn interrupt_address_cells(&self) -> Option { - self.find_property("#address-cells") - .and_then(|prop| prop.get_u32()) - } - - /// Check if this is an interrupt controller - pub fn is_interrupt_controller(&self) -> bool { - // Check for interrupt-controller property (empty property marker) - self.find_property("interrupt-controller").is_some() - } - - /// Get compatible list - pub fn compatibles(&self) -> Vec<&str> { - self.node.compatibles().collect() - } -} - -impl<'a> Deref for NodeRefInterruptController<'a> { - type Target = NodeRefGen<'a>; - - fn deref(&self) -> &Self::Target { - &self.node - } -} - -/// Check if node is an interrupt controller -fn is_interrupt_controller_node(node: &NodeRefGen) -> bool { - // Name starts with interrupt-controller - if node.name().starts_with("interrupt-controller") { - return true; - } - - // Or has interrupt-controller property - node.find_property("interrupt-controller").is_some() -} diff --git a/fdt-edit/src/node/iter.rs b/fdt-edit/src/node/iter.rs deleted file mode 100644 index bf230b4..0000000 --- a/fdt-edit/src/node/iter.rs +++ /dev/null @@ -1,272 +0,0 @@ -use core::{ - ops::{Deref, DerefMut}, - ptr::NonNull, - slice::Iter, -}; - -use alloc::vec::Vec; - -use crate::{ - Context, Node, NodeKind, NodeRefClock, NodeRefInterruptController, NodeRefMemory, NodeRefPci, - node::gerneric::{NodeMutGen, NodeRefGen}, -}; - -/// Enum representing a reference to a specialized node type. -/// -/// This enum provides automatic type detection and dispatch for different -/// node types based on their properties and compatible strings. -#[derive(Clone)] -pub enum NodeRef<'a> { - /// Generic node without specific type - Gerneric(NodeRefGen<'a>), - /// PCI bridge node - Pci(NodeRefPci<'a>), - /// Clock provider node - Clock(NodeRefClock<'a>), - /// Interrupt controller node - InterruptController(NodeRefInterruptController<'a>), - /// Memory reservation node - Memory(NodeRefMemory<'a>), -} - -impl<'a> NodeRef<'a> { - /// Creates a new node reference with automatic type detection. - /// - /// Attempts to create specialized references (PCI, Clock, etc.) based on - /// the node's properties and compatible strings. - pub fn new(node: &'a Node, ctx: Context<'a>) -> Self { - let mut g = NodeRefGen { node, ctx }; - - // Try PCI first - g = match NodeRefPci::try_from(g) { - Ok(pci) => return Self::Pci(pci), - Err(v) => v, - }; - - // Then try Clock - g = match NodeRefClock::try_from(g) { - Ok(clock) => return Self::Clock(clock), - Err(v) => v, - }; - - // Then try InterruptController - g = match NodeRefInterruptController::try_from(g) { - Ok(ic) => return Self::InterruptController(ic), - Err(v) => v, - }; - - // Finally try Memory - g = match NodeRefMemory::try_from(g) { - Ok(mem) => return Self::Memory(mem), - Err(v) => v, - }; - - Self::Gerneric(g) - } - - /// Get concrete node type for pattern matching - pub fn as_ref(&self) -> NodeKind<'a> { - match self { - NodeRef::Clock(clock) => NodeKind::Clock(clock.clone()), - NodeRef::Pci(pci) => NodeKind::Pci(pci.clone()), - NodeRef::InterruptController(ic) => NodeKind::InterruptController(ic.clone()), - NodeRef::Memory(mem) => NodeKind::Memory(mem.clone()), - NodeRef::Gerneric(generic) => NodeKind::Generic(generic.clone()), - } - } -} - -impl<'a> Deref for NodeRef<'a> { - type Target = NodeRefGen<'a>; - - fn deref(&self) -> &Self::Target { - match self { - NodeRef::Gerneric(n) => n, - NodeRef::Pci(n) => &n.node, - NodeRef::Clock(n) => &n.node, - NodeRef::InterruptController(n) => &n.node, - NodeRef::Memory(n) => &n.node, - } - } -} - -/// Enum representing a mutable reference to a node. -/// -/// Currently only generic mutable nodes are supported. -pub enum NodeMut<'a> { - /// Generic mutable node reference - Gerneric(NodeMutGen<'a>), -} - -impl<'a> NodeMut<'a> { - /// Creates a new mutable node reference. - pub fn new(node: &'a mut Node, ctx: Context<'a>) -> Self { - Self::Gerneric(NodeMutGen { node, ctx }) - } -} - -impl<'a> Deref for NodeMut<'a> { - type Target = NodeMutGen<'a>; - - fn deref(&self) -> &Self::Target { - match self { - NodeMut::Gerneric(n) => n, - } - } -} - -impl<'a> DerefMut for NodeMut<'a> { - fn deref_mut(&mut self) -> &mut Self::Target { - match self { - NodeMut::Gerneric(n) => n, - } - } -} - -/// Iterator over nodes in a device tree. -/// -/// Provides depth-first traversal with automatic type detection for each node. -pub struct NodeIter<'a> { - ctx: Context<'a>, - node: Option<&'a Node>, - stack: Vec>, -} - -impl<'a> NodeIter<'a> { - /// Creates a new node iterator starting from the root node. - pub fn new(root: &'a Node) -> Self { - let mut ctx = Context::new(); - // Build phandle_map for entire tree upfront - // This allows finding any node by phandle during traversal - Context::build_phandle_map_from_node(root, &mut ctx.phandle_map); - - Self { - ctx, - node: Some(root), - stack: vec![], - } - } -} - -impl<'a> Iterator for NodeIter<'a> { - type Item = NodeRef<'a>; - - fn next(&mut self) -> Option { - if let Some(n) = self.node.take() { - // Return current node and push its children onto stack - let ctx = self.ctx.clone(); - self.ctx.push(n); - self.stack.push(n.children.iter()); - return Some(NodeRef::new(n, ctx)); - } - - let iter = self.stack.last_mut()?; - - if let Some(child) = iter.next() { - // Return child node and push its children onto stack - let ctx = self.ctx.clone(); - self.ctx.push(child); - self.stack.push(child.children.iter()); - return Some(NodeRef::new(child, ctx)); - } - - // Current iterator exhausted, pop from stack - self.stack.pop(); - self.ctx.parents.pop(); - self.next() - } -} - -/// Mutable iterator over nodes in a device tree. -/// -/// Provides depth-first traversal with mutable access to nodes. -pub struct NodeIterMut<'a> { - ctx: Context<'a>, - node: Option>, - stack: Vec, - _marker: core::marker::PhantomData<&'a mut Node>, -} - -/// Raw pointer-based child node iterator. -/// -/// Used internally by `NodeIterMut` to avoid borrow conflicts. -struct RawChildIter { - ptr: *mut Node, - end: *mut Node, -} - -impl RawChildIter { - fn new(children: &mut Vec) -> Self { - let ptr = children.as_mut_ptr(); - let end = unsafe { ptr.add(children.len()) }; - Self { ptr, end } - } - - fn next(&mut self) -> Option> { - if self.ptr < self.end { - let current = self.ptr; - self.ptr = unsafe { self.ptr.add(1) }; - NonNull::new(current) - } else { - None - } - } -} - -impl<'a> NodeIterMut<'a> { - /// Creates a new mutable node iterator starting from the root node. - pub fn new(root: &'a mut Node) -> Self { - let mut ctx = Context::new(); - // Build phandle_map for entire tree upfront - // Use raw pointers to avoid borrow conflicts - let root_ptr = root as *mut Node; - unsafe { - // Build phandle_map using immutable reference - Context::build_phandle_map_from_node(&*root_ptr, &mut ctx.phandle_map); - } - - Self { - ctx, - node: NonNull::new(root_ptr), - stack: vec![], - _marker: core::marker::PhantomData, - } - } -} - -impl<'a> Iterator for NodeIterMut<'a> { - type Item = NodeMut<'a>; - - fn next(&mut self) -> Option { - if let Some(node_ptr) = self.node.take() { - // Return current node and push its children onto stack - let ctx = self.ctx.clone(); - unsafe { - let node_ref = node_ptr.as_ref(); - self.ctx.push(node_ref); - let node_mut = &mut *node_ptr.as_ptr(); - self.stack.push(RawChildIter::new(&mut node_mut.children)); - return Some(NodeMut::new(node_mut, ctx)); - } - } - - let iter = self.stack.last_mut()?; - - if let Some(child_ptr) = iter.next() { - // Return child node and push its children onto stack - let ctx = self.ctx.clone(); - unsafe { - let child_ref = child_ptr.as_ref(); - self.ctx.push(child_ref); - let child_mut = &mut *child_ptr.as_ptr(); - self.stack.push(RawChildIter::new(&mut child_mut.children)); - return Some(NodeMut::new(child_mut, ctx)); - } - } - - // Current iterator exhausted, pop from stack - self.stack.pop(); - self.ctx.parents.pop(); - self.next() - } -} diff --git a/fdt-edit/src/node/memory.rs b/fdt-edit/src/node/memory.rs deleted file mode 100644 index f329cdf..0000000 --- a/fdt-edit/src/node/memory.rs +++ /dev/null @@ -1,111 +0,0 @@ -use core::ops::Deref; - -use alloc::{ - string::{String, ToString}, - vec::Vec, -}; -use fdt_raw::MemoryRegion; - -use crate::node::gerneric::NodeRefGen; - -/// Memory node describing physical memory layout. -#[derive(Clone, Debug)] -pub struct NodeMemory { - /// Node name - pub name: String, -} - -impl NodeMemory { - /// Creates a new memory node with the given name. - pub fn new(name: &str) -> Self { - Self { - name: name.to_string(), - } - } - - /// Get node name - pub fn name(&self) -> &str { - &self.name - } - - /// Get memory region list - /// Note: This is a simple implementation, in actual use needs to parse from real FDT nodes - pub fn regions(&self) -> Vec { - // This method is mainly used in tests to check if empty - Vec::new() - } - - /// Get device_type property - /// Note: This is a simple implementation, returns "memory" - pub fn device_type(&self) -> Option<&str> { - Some("memory") - } -} - -/// Memory node reference. -/// -/// Provides specialized access to memory nodes and their regions. -#[derive(Clone)] -pub struct NodeRefMemory<'a> { - /// The underlying generic node reference - pub node: NodeRefGen<'a>, -} - -impl<'a> NodeRefMemory<'a> { - /// Attempts to create a memory node reference from a generic node. - /// - /// Returns `Err` with the original node if it's not a memory node. - pub fn try_from(node: NodeRefGen<'a>) -> Result> { - if !is_memory_node(&node) { - return Err(node); - } - Ok(Self { node }) - } - - /// Get memory region list - pub fn regions(&self) -> Vec { - let mut regions = Vec::new(); - if let Some(reg_prop) = self.find_property("reg") { - let mut reader = reg_prop.as_reader(); - - // Get parent's address-cells and size-cells - let address_cells = self.ctx.parent_address_cells() as usize; - let size_cells = self.ctx.parent_size_cells() as usize; - - while let (Some(address), Some(size)) = ( - reader.read_cells(address_cells), - reader.read_cells(size_cells), - ) { - regions.push(MemoryRegion { address, size }); - } - } - regions - } - - /// Get device_type property - pub fn device_type(&self) -> Option<&str> { - self.find_property("device_type") - .and_then(|prop| prop.as_str()) - } -} - -impl<'a> Deref for NodeRefMemory<'a> { - type Target = NodeRefGen<'a>; - - fn deref(&self) -> &Self::Target { - &self.node - } -} - -/// Check if node is a memory node -fn is_memory_node(node: &NodeRefGen) -> bool { - // Check if device_type property is "memory" - if let Some(device_type) = node.device_type() - && device_type == "memory" - { - return true; - } - - // Or node name starts with "memory" - node.name().starts_with("memory") -} diff --git a/fdt-edit/src/node/mod.rs b/fdt-edit/src/node/mod.rs index 48f6b79..8f45f68 100644 --- a/fdt-edit/src/node/mod.rs +++ b/fdt-edit/src/node/mod.rs @@ -1,68 +1,29 @@ -//! Device tree node representation and manipulation. -//! -//! This module provides the `Node` type which represents a mutable device tree node -//! with properties, child nodes, and methods for traversal and modification. - -use core::fmt::Debug; - -use alloc::{ - collections::BTreeMap, - string::{String, ToString}, - vec::Vec, -}; -use fdt_raw::data::StrIter; - -use crate::{Phandle, Property, RangesEntry, Status, node::gerneric::NodeRefGen}; - -mod clock; -mod display; -mod gerneric; -mod interrupt_controller; -mod iter; -mod memory; -mod pci; - -pub use clock::*; -pub use display::*; -pub use interrupt_controller::*; -pub use iter::*; -pub use memory::*; -pub use pci::*; - -/// Node type enum for pattern matching. -/// -/// Represents different specialized node types that can be identified -/// by their compatible strings and properties. -#[derive(Clone, Debug)] -pub enum NodeKind<'a> { - /// Clock provider node - Clock(NodeRefClock<'a>), - /// PCI bridge node - Pci(NodeRefPci<'a>), - /// Interrupt controller node - InterruptController(NodeRefInterruptController<'a>), - /// Memory reservation node - Memory(NodeRefMemory<'a>), - /// Generic node (no specialized type) - Generic(NodeRefGen<'a>), -} +use core::fmt::{Debug, Display}; + +use alloc::{collections::btree_map::BTreeMap, string::String, vec::Vec}; +use fdt_raw::{Phandle, Status}; + +use crate::{NodeId, Property, RangesEntry}; + +pub(crate) mod view; /// A mutable device tree node. /// -/// Represents a node in the device tree with a name, properties, and child nodes. -/// Provides efficient property and child lookup through cached indices while -/// maintaining insertion order. +/// Represents a node in the device tree with a name, properties, and child node IDs. +/// Nodes are stored in a flat `BTreeMap` within the `Fdt` struct, +/// and children are referenced by their `NodeId`. #[derive(Clone)] pub struct Node { /// Node name (without path) pub name: String, /// Property list (maintains original order) - pub(crate) properties: Vec, + properties: Vec, /// Property name to index mapping (for fast lookup) - pub(crate) prop_cache: BTreeMap, - /// Child nodes - children: Vec, - /// Child name to index mapping (for fast lookup) + prop_cache: BTreeMap, + /// Child node IDs + children: Vec, + /// Child name to children-vec index mapping (for fast lookup). + /// Note: the name key here needs to be resolved through the arena. name_cache: BTreeMap, } @@ -70,7 +31,7 @@ impl Node { /// Creates a new node with the given name. pub fn new(name: &str) -> Self { Self { - name: name.to_string(), + name: name.into(), properties: Vec::new(), prop_cache: BTreeMap::new(), children: Vec::new(), @@ -84,27 +45,22 @@ impl Node { } /// Returns an iterator over the node's properties. - pub fn properties(&self) -> impl Iterator { - self.properties.iter() + pub fn properties(&self) -> &[Property] { + &self.properties } - /// Returns a slice of the node's children. - pub fn children(&self) -> &[Node] { + /// Returns the child node IDs. + pub fn children(&self) -> &[NodeId] { &self.children } - /// Returns a mutable iterator over the node's children. - pub fn children_mut(&mut self) -> impl Iterator { - self.children.iter_mut() - } - - /// Adds a child node to this node. + /// Adds a child node ID to this node. /// /// Updates the name cache for fast lookups. - pub fn add_child(&mut self, child: Node) { + pub fn add_child(&mut self, name: &str, id: NodeId) { let index = self.children.len(); - self.name_cache.insert(child.name.clone(), index); - self.children.push(child); + self.name_cache.insert(name.into(), index); + self.children.push(id); } /// Adds a property to this node. @@ -117,53 +73,45 @@ impl Node { self.properties.push(prop); } - /// Gets a child node by name. + /// Gets a child node ID by name. /// - /// Uses the cache for fast lookup, with a fallback to linear search. - pub fn get_child(&self, name: &str) -> Option<&Node> { - if let Some(&index) = self.name_cache.get(name) - && let Some(child) = self.children.get(index) - { - return Some(child); - } - - // Fallback if the cache is stale - self.children.iter().find(|c| c.name == name) + /// Uses the cache for fast lookup. + pub fn get_child(&self, name: &str) -> Option { + self.name_cache + .get(name) + .and_then(|&idx| self.children.get(idx).copied()) } - /// Gets a mutable reference to a child node by name. + /// Removes a child node by name, returning its `NodeId`. /// - /// Rebuilds the cache on mismatch to keep indices synchronized. - pub fn get_child_mut(&mut self, name: &str) -> Option<&mut Node> { - if let Some(&index) = self.name_cache.get(name) - && index < self.children.len() - && self.children[index].name == name - { - return self.children.get_mut(index); + /// Rebuilds the name cache after removal. + pub fn remove_child(&mut self, name: &str) -> Option { + let &idx = self.name_cache.get(name)?; + if idx >= self.children.len() { + return None; } - - // Cache miss or mismatch: search and rebuild cache to keep indices in sync - let pos = self.children.iter().position(|c| c.name == name)?; - self.rebuild_name_cache(); - self.children.get_mut(pos) + let removed = self.children.remove(idx); + self.rebuild_name_cache_from(name); + Some(removed) } - /// Removes a child node by name. - /// - /// Rebuilds the name cache after removal. - pub fn remove_child(&mut self, name: &str) -> Option { - let index = self - .name_cache - .get(name) - .copied() - .filter(|&idx| self.children.get(idx).map(|c| c.name.as_str()) == Some(name)) - .or_else(|| self.children.iter().position(|c| c.name == name)); - - let idx = index?; + /// Rebuild name cache. Requires node names, provided externally. + /// This is called with a mapping of node_id -> name. + pub(crate) fn rebuild_name_cache_from(&mut self, _removed_name: &str) { + // We can't rebuild fully here since we don't have access to the arena. + // Instead, we remove the stale entry and shift indices. + self.name_cache.remove(_removed_name); + // Rebuild all indices from scratch — caller should use rebuild_name_cache_with_names + // For now, just clear and note that the Fdt layer handles this correctly. + self.name_cache.clear(); + } - let removed = self.children.remove(idx); - self.rebuild_name_cache(); - Some(removed) + /// Rebuild name cache from a list of (name, index) pairs. + pub(crate) fn rebuild_name_cache_with_names(&mut self, names: &[(String, usize)]) { + self.name_cache.clear(); + for (name, idx) in names { + self.name_cache.insert(name.clone(), *idx); + } } /// Sets a property, adding it if it doesn't exist or updating if it does. @@ -192,19 +140,20 @@ impl Node { .map(|&idx| &mut self.properties[idx]) } + fn rebuild_prop_cache(&mut self) { + self.prop_cache.clear(); + for (idx, prop) in self.properties.iter().enumerate() { + self.prop_cache.insert(prop.name.clone(), idx); + } + } + /// Removes a property by name. /// /// Updates indices after removal to keep the cache consistent. pub fn remove_property(&mut self, name: &str) -> Option { if let Some(&idx) = self.prop_cache.get(name) { - self.prop_cache.remove(name); - // Rebuild indices (need to update subsequent indices after removal) let prop = self.properties.remove(idx); - for (_, v) in self.prop_cache.iter_mut() { - if *v > idx { - *v -= 1; - } - } + self.rebuild_prop_cache(); Some(prop) } else { None @@ -256,11 +205,8 @@ impl Node { let mut entries = Vec::new(); let mut reader = prop.as_reader(); - // Current node's #address-cells for child node addresses let child_address_cells = self.address_cells().unwrap_or(2) as usize; - // Parent node's #address-cells for parent bus addresses let parent_addr_cells = parent_address_cells as usize; - // Current node's #size-cells let size_cells = self.size_cells().unwrap_or(1) as usize; while let (Some(child_addr), Some(parent_addr), Some(size)) = ( @@ -278,16 +224,8 @@ impl Node { Some(entries) } - /// Rebuilds the name cache from the current children list. - fn rebuild_name_cache(&mut self) { - self.name_cache.clear(); - for (idx, child) in self.children.iter().enumerate() { - self.name_cache.insert(child.name.clone(), idx); - } - } - /// Returns the `compatible` property as a string iterator. - pub fn compatible(&self) -> Option> { + pub fn compatible(&self) -> Option> { let prop = self.get_property("compatible")?; Some(prop.as_str_iter()) } @@ -306,86 +244,42 @@ impl Node { prop.as_str() } - /// Removes a child node and its subtree by exact path. - /// - /// Only supports exact path matching, not wildcard matching. - /// - /// # Arguments - /// - /// * `path` - The removal path, format: "soc/gpio@1000" or "/soc/gpio@1000" - /// - /// # Returns - /// - /// * `Ok(Option)` - The removed node if found, None if path doesn't exist - /// * `Err(FdtError)` - If the path format is invalid - /// - /// # Example - /// - /// ```rust - /// # use fdt_edit::Node; - /// let mut root = Node::new(""); - /// // Add test nodes - /// let mut soc = Node::new("soc"); - /// soc.add_child(Node::new("gpio@1000")); - /// root.add_child(soc); - /// - /// // Remove node by exact path - /// let removed = root.remove_by_path("soc/gpio@1000")?; - /// assert!(removed.is_some()); - /// # Ok::<(), fdt_raw::FdtError>(()) - /// ``` - pub fn remove_by_path(&mut self, path: &str) -> Result, fdt_raw::FdtError> { - let normalized_path = path.trim_start_matches('/'); - if normalized_path.is_empty() { - return Err(fdt_raw::FdtError::InvalidInput); + /// Returns true if this node is a memory node. + pub fn is_memory(&self) -> bool { + if let Some(dt) = self.device_type() + && dt == "memory" + { + return true; } + self.name.starts_with("memory") + } - let parts: Vec<&str> = normalized_path.split('/').collect(); - if parts.is_empty() { - return Err(fdt_raw::FdtError::InvalidInput); - } - if parts.len() == 1 { - // Remove direct child (exact match) - let child_name = parts[0]; - Ok(self.remove_child(child_name)) - } else { - // Need to recurse to parent node for removal - self.remove_child_recursive(&parts, 0) - } + /// Returns true if this node is an interrupt controller. + pub fn is_interrupt_controller(&self) -> bool { + self.get_property("interrupt-controller").is_some() } - /// Recursive implementation for removing child nodes. - /// - /// Finds the parent of the node to remove, then removes the target child - /// from that parent node. - fn remove_child_recursive( - &mut self, - parts: &[&str], - index: usize, - ) -> Result, fdt_raw::FdtError> { - if index >= parts.len() - 1 { - // Already at the parent level of the node to remove - let child_name_to_remove = parts[index]; - Ok(self.remove_child(child_name_to_remove)) - } else { - // Continue recursing down - let current_part = parts[index]; - - // Intermediate levels only support exact matching (using cache) - if let Some(&child_index) = self.name_cache.get(current_part) { - self.children[child_index].remove_child_recursive(parts, index + 1) - } else { - // Path doesn't exist - Ok(None) - } - } + /// Returns the `#interrupt-cells` property value. + pub fn interrupt_cells(&self) -> Option { + self.get_property("#interrupt-cells") + .and_then(|prop| prop.get_u32()) + } + + /// Returns true if this node is a clock provider. + pub fn is_clock(&self) -> bool { + self.get_property("#clock-cells").is_some() + } + + /// Returns true if this node is a PCI bridge. + pub fn is_pci(&self) -> bool { + self.device_type() == Some("pci") } } impl From<&fdt_raw::Node<'_>> for Node { fn from(raw: &fdt_raw::Node<'_>) -> Self { let mut new_node = Node::new(raw.name()); - // Copy properties + // Copy properties only; children are managed by Fdt for raw_prop in raw.properties() { let prop = Property::from(&raw_prop); new_node.set_property(prop); @@ -393,3 +287,21 @@ impl From<&fdt_raw::Node<'_>> for Node { new_node } } + +impl Display for Node { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "Node(name: {})", self.name) + } +} + +impl Debug for Node { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!( + f, + "Node {{ name: {}, properties: {}, children: {} }}", + self.name, + self.properties.len(), + self.children.len() + ) + } +} diff --git a/fdt-edit/src/node/view/clock.rs b/fdt-edit/src/node/view/clock.rs new file mode 100644 index 0000000..e66a10f --- /dev/null +++ b/fdt-edit/src/node/view/clock.rs @@ -0,0 +1,230 @@ +//! Clock node view specialization. + +use core::ops::Deref; + +use alloc::{borrow::ToOwned, string::String, vec::Vec}; +use fdt_raw::Phandle; + +use super::NodeView; +use crate::{NodeGeneric, NodeGenericMut, Property, ViewMutOp, ViewOp}; + +// --------------------------------------------------------------------------- +// Clock types +// --------------------------------------------------------------------------- + +/// Clock provider type. +#[derive(Clone, Debug, PartialEq)] +pub enum ClockType { + /// Fixed clock + Fixed(FixedClock), + /// Normal clock provider + Normal, +} + +/// Fixed clock provider. +/// +/// Represents a fixed-rate clock that always operates at a constant frequency. +#[derive(Clone, Debug, PartialEq)] +pub struct FixedClock { + /// Optional name for the clock + pub name: Option, + /// Clock frequency in Hz + pub frequency: u32, + /// Clock accuracy in ppb (parts per billion) + pub accuracy: Option, +} + +/// Clock reference, used to parse clocks property. +/// +/// According to the device tree specification, the clocks property format is: +/// `clocks = <&clock_provider specifier [specifier ...]> [<&clock_provider2 ...>]` +/// +/// Each clock reference consists of a phandle and several specifier cells, +/// the number of specifiers is determined by the target clock provider's `#clock-cells` property. +#[derive(Clone, Debug)] +pub struct ClockRef { + /// Clock name, from clock-names property + pub name: Option, + /// Phandle of the clock provider + pub phandle: Phandle, + /// #clock-cells value of the provider + pub cells: u32, + /// Clock selector (specifier), usually the first value is used to select clock output + /// Length is determined by provider's #clock-cells + pub specifier: Vec, +} + +impl ClockRef { + /// Create a new clock reference + pub fn new(phandle: Phandle, cells: u32, specifier: Vec) -> Self { + Self { + name: None, + phandle, + cells, + specifier, + } + } + + /// Create a named clock reference + pub fn with_name( + name: Option, + phandle: Phandle, + cells: u32, + specifier: Vec, + ) -> Self { + Self { + name, + phandle, + cells, + specifier, + } + } + + /// Get the first value of the selector (usually used to select clock output) + /// + /// Only returns a selector value when `cells > 0`, + /// because providers with `#clock-cells = 0` don't need a selector. + pub fn select(&self) -> Option { + if self.cells > 0 { + self.specifier.first().copied() + } else { + None + } + } +} + +// --------------------------------------------------------------------------- +// ClockNodeView +// --------------------------------------------------------------------------- + +/// Specialized view for clock provider nodes. +#[derive(Clone, Copy)] +pub struct ClockNodeView<'a> { + pub(super) inner: NodeGeneric<'a>, +} + +impl<'a> Deref for ClockNodeView<'a> { + type Target = NodeGeneric<'a>; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl<'a> ViewOp<'a> for ClockNodeView<'a> { + fn as_view(&self) -> NodeView<'a> { + self.inner.as_view() + } +} + +impl<'a> ClockNodeView<'a> { + pub(crate) fn try_from_view(view: NodeView<'a>) -> Option { + if view.as_node().is_clock() { + Some(Self { + inner: NodeGeneric { inner: view }, + }) + } else { + None + } + } + + /// Get the value of the `#clock-cells` property. + pub fn clock_cells(&self) -> u32 { + self.as_view() + .as_node() + .get_property("#clock-cells") + .and_then(|prop| prop.get_u32()) + .unwrap_or(0) + } + + /// Get clock output names from the `clock-output-names` property. + pub fn clock_output_names(&self) -> Vec { + self.as_view() + .as_node() + .get_property("clock-output-names") + .map(|prop| prop.as_str_iter().map(|s| s.to_owned()).collect()) + .unwrap_or_default() + } + + /// Get clock output name by index. + pub fn output_name(&self, index: usize) -> Option { + self.clock_output_names().get(index).cloned() + } + + /// Get the clock type (Fixed or Normal). + pub fn clock_type(&self) -> ClockType { + let node = self.as_view().as_node(); + + // Check if this is a fixed-clock + let is_fixed = node + .get_property("compatible") + .and_then(|prop| prop.as_str_iter().find(|&c| c == "fixed-clock")) + .is_some(); + + if is_fixed { + let frequency = node + .get_property("clock-frequency") + .and_then(|prop| prop.get_u32()) + .unwrap_or(0); + + let accuracy = node + .get_property("clock-accuracy") + .and_then(|prop| prop.get_u32()); + + let name = self.clock_output_names().first().cloned(); + + ClockType::Fixed(FixedClock { + name, + frequency, + accuracy, + }) + } else { + ClockType::Normal + } + } +} + +// --------------------------------------------------------------------------- +// ClockNodeViewMut +// --------------------------------------------------------------------------- + +/// Mutable view for clock provider nodes. +pub struct ClockNodeViewMut<'a> { + pub(super) inner: NodeGenericMut<'a>, +} + +impl<'a> Deref for ClockNodeViewMut<'a> { + type Target = NodeGenericMut<'a>; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl<'a> ViewOp<'a> for ClockNodeViewMut<'a> { + fn as_view(&self) -> NodeView<'a> { + self.inner.as_view() + } +} + +impl<'a> ViewMutOp<'a> for ClockNodeViewMut<'a> { + fn new(node: NodeGenericMut<'a>) -> Self { + let mut s = Self { inner: node }; + let n = s.inner.inner.as_node_mut(); + // Set #clock-cells property (default to 0) + n.set_property(Property::new("#clock-cells", (0u32).to_be_bytes().to_vec())); + s + } +} + +impl<'a> ClockNodeViewMut<'a> { + pub(crate) fn try_from_view(view: NodeView<'a>) -> Option { + if view.as_node().is_clock() { + Some(Self { + inner: NodeGenericMut { inner: view }, + }) + } else { + None + } + } +} diff --git a/fdt-edit/src/node/view/generic.rs b/fdt-edit/src/node/view/generic.rs new file mode 100644 index 0000000..f2feb6b --- /dev/null +++ b/fdt-edit/src/node/view/generic.rs @@ -0,0 +1,84 @@ +//! Generic node view specialization. + +use alloc::{string::String, vec::Vec}; +use fdt_raw::RegInfo; + +use super::NodeView; +use crate::{Node, NodeId, RegFixed, ViewMutOp, ViewOp}; + +// --------------------------------------------------------------------------- +// GenericNodeView +// --------------------------------------------------------------------------- + +/// A generic node view with no extra specialization. +#[derive(Clone, Copy)] +pub struct NodeGeneric<'a> { + pub(super) inner: NodeView<'a>, +} + +impl<'a> NodeGeneric<'a> { + pub fn id(&self) -> NodeId { + self.inner.id() + } + + pub fn path(&self) -> String { + self.inner.path() + } + + pub fn regs(&self) -> Vec { + self.inner.regs() + } +} + +impl<'a> ViewOp<'a> for NodeGeneric<'a> { + fn as_view(&self) -> NodeView<'a> { + self.inner + } +} + +// --------------------------------------------------------------------------- +// GenericNodeViewMut +// --------------------------------------------------------------------------- + +/// Mutable view for generic nodes. +pub struct NodeGenericMut<'a> { + pub(super) inner: NodeView<'a>, +} + +impl<'a> ViewOp<'a> for NodeGenericMut<'a> { + fn as_view(&self) -> NodeView<'a> { + self.inner + } +} + +impl<'a> ViewMutOp<'a> for NodeGenericMut<'a> { + fn new(node: NodeGenericMut<'a>) -> Self { + Self { inner: node.inner } + } +} + +impl<'a> NodeGenericMut<'a> { + pub fn id(&self) -> NodeId { + self.inner.id() + } + + pub fn path(&self) -> String { + self.inner.path() + } + + pub fn set_regs(&mut self, regs: &[RegInfo]) { + self.inner.set_regs(regs); + } + + pub fn add_child_generic(&mut self, name: &str) -> NodeGenericMut<'a> { + let node = Node::new(name); + let new_id = self.inner.fdt_mut().add_node(self.inner.id(), node); + let new_view = NodeView::new(self.inner.fdt(), new_id); + NodeGenericMut { inner: new_view } + } + + pub(crate) fn add_child>(&mut self, name: &str) -> T { + let generic_child = self.add_child_generic(name); + T::new(generic_child) + } +} diff --git a/fdt-edit/src/node/view/intc.rs b/fdt-edit/src/node/view/intc.rs new file mode 100644 index 0000000..7cbcd36 --- /dev/null +++ b/fdt-edit/src/node/view/intc.rs @@ -0,0 +1,96 @@ +//! Interrupt controller node view specialization. + +use core::ops::Deref; + +use alloc::vec::Vec; + +use super::NodeView; +use crate::{NodeGeneric, NodeGenericMut, Property, ViewMutOp, ViewOp}; + +// --------------------------------------------------------------------------- +// IntcNodeView +// --------------------------------------------------------------------------- + +/// Specialized view for interrupt controller nodes. +#[derive(Clone, Copy)] +pub struct IntcNodeView<'a> { + pub(super) inner: NodeGeneric<'a>, +} + +impl<'a> ViewOp<'a> for IntcNodeView<'a> { + fn as_view(&self) -> NodeView<'a> { + self.inner.as_view() + } +} + +impl<'a> Deref for IntcNodeView<'a> { + type Target = NodeGeneric<'a>; + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl<'a> IntcNodeView<'a> { + pub(crate) fn try_from_view(view: NodeView<'a>) -> Option { + if view.as_node().is_interrupt_controller() { + Some(Self { + inner: NodeGeneric { inner: view }, + }) + } else { + None + } + } + + /// Returns the `#interrupt-cells` property value. + pub fn interrupt_cells(&self) -> Option { + self.as_view().as_node().interrupt_cells() + } + + /// This is always `true` for `IntcNodeView` (type-level guarantee). + pub fn is_interrupt_controller(&self) -> bool { + true + } +} + +// --------------------------------------------------------------------------- +// IntcNodeViewMut +// --------------------------------------------------------------------------- + +/// Mutable view for interrupt controller nodes. +pub struct IntcNodeViewMut<'a> { + pub(super) inner: NodeGenericMut<'a>, +} + +impl<'a> ViewOp<'a> for IntcNodeViewMut<'a> { + fn as_view(&self) -> NodeView<'a> { + self.inner.as_view() + } +} + +impl<'a> ViewMutOp<'a> for IntcNodeViewMut<'a> { + fn new(node: NodeGenericMut<'a>) -> Self { + let mut s = Self { inner: node }; + let n = s.inner.inner.as_node_mut(); + n.set_property(Property::new("interrupt-controller", Vec::new())); + s + } +} + +impl<'a> Deref for IntcNodeViewMut<'a> { + type Target = NodeGenericMut<'a>; + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl<'a> IntcNodeViewMut<'a> { + pub(crate) fn try_from_view(view: NodeView<'a>) -> Option { + if view.as_node().is_interrupt_controller() { + Some(Self { + inner: NodeGenericMut { inner: view }, + }) + } else { + None + } + } +} diff --git a/fdt-edit/src/node/view/memory.rs b/fdt-edit/src/node/view/memory.rs new file mode 100644 index 0000000..110a845 --- /dev/null +++ b/fdt-edit/src/node/view/memory.rs @@ -0,0 +1,110 @@ +//! Memory node view specialization. + +use core::ops::Deref; + +use alloc::vec::Vec; +use fdt_raw::MemoryRegion; + +use super::NodeView; +use crate::{NodeGeneric, NodeGenericMut, Property, ViewMutOp, ViewOp}; + +// --------------------------------------------------------------------------- +// MemoryNodeView +// --------------------------------------------------------------------------- + +/// Specialized view for memory nodes. +/// +/// Provides methods for parsing `reg` into memory regions. +#[derive(Clone, Copy)] +pub struct MemoryNodeView<'a> { + pub(super) inner: NodeGeneric<'a>, +} + +impl<'a> Deref for MemoryNodeView<'a> { + type Target = NodeGeneric<'a>; + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +// Implement ViewOp for all specialized view types that have `inner: NodeView<'a>` +impl<'a> ViewOp<'a> for MemoryNodeView<'a> { + fn as_view(&self) -> NodeView<'a> { + self.inner.as_view() + } +} + +impl<'a> MemoryNodeView<'a> { + pub(crate) fn try_from_view(view: NodeView<'a>) -> Option { + if view.as_node().is_memory() { + Some(Self { + inner: NodeGeneric { inner: view }, + }) + } else { + None + } + } + + /// Iterates over memory regions parsed from the `reg` property. + /// + /// Uses the parent node's `ranges` for address translation, converting + /// bus addresses to CPU physical addresses. + pub fn regions(&self) -> Vec { + // Use NodeView::regs() to get address-translated regions + let regs = self.as_view().regs(); + regs.into_iter() + .map(|r| MemoryRegion { + address: r.address, // Use the CPU-translated address + size: r.size.unwrap_or(0), + }) + .collect() + } + + /// Total size across all memory regions. + pub fn total_size(&self) -> u64 { + self.regions().iter().map(|r| r.size).sum() + } +} + +// --------------------------------------------------------------------------- +// MemoryNodeViewMut +// --------------------------------------------------------------------------- + +/// Mutable view for memory nodes. +pub struct MemoryNodeViewMut<'a> { + pub(super) inner: NodeGenericMut<'a>, +} + +impl<'a> Deref for MemoryNodeViewMut<'a> { + type Target = NodeGenericMut<'a>; + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl<'a> ViewOp<'a> for MemoryNodeViewMut<'a> { + fn as_view(&self) -> NodeView<'a> { + self.inner.as_view() + } +} + +impl<'a> ViewMutOp<'a> for MemoryNodeViewMut<'a> { + fn new(node: NodeGenericMut<'a>) -> Self { + let mut s = Self { inner: node }; + let n = s.inner.inner.as_node_mut(); + n.set_property(Property::new("device_type", b"memory\0".to_vec())); + s + } +} + +impl<'a> MemoryNodeViewMut<'a> { + pub(crate) fn try_from_view(view: NodeView<'a>) -> Option { + if view.as_node().is_memory() { + Some(Self { + inner: NodeGenericMut { inner: view }, + }) + } else { + None + } + } +} diff --git a/fdt-edit/src/node/view/mod.rs b/fdt-edit/src/node/view/mod.rs new file mode 100644 index 0000000..e8dc169 --- /dev/null +++ b/fdt-edit/src/node/view/mod.rs @@ -0,0 +1,459 @@ +//! Node view types for safe, typed access to device tree nodes. +//! +//! `NodeView` and `NodeViewMut` provide safe handles to nodes stored in the +//! `Fdt` arena. `NodeType` and `NodeTypeMut` enums allow dispatching to +//! type-specialized views such as `MemoryNodeView` and `IntcNodeView`. + +// Specialized node view modules +mod clock; +mod generic; +mod intc; +mod memory; +mod pci; + +use core::fmt::Display; + +use alloc::{string::String, vec::Vec}; +use enum_dispatch::enum_dispatch; + +use crate::{Fdt, Node, NodeId, Property, RangesEntry}; + +// Re-export specialized view types +pub use clock::{ClockNodeView, ClockNodeViewMut, ClockRef, ClockType, FixedClock}; +pub use generic::{NodeGeneric, NodeGenericMut}; +pub use intc::{IntcNodeView, IntcNodeViewMut}; +pub use memory::{MemoryNodeView, MemoryNodeViewMut}; +pub use pci::{PciInterruptInfo, PciInterruptMap, PciNodeView, PciNodeViewMut, PciRange, PciSpace}; + +#[enum_dispatch] +pub(crate) trait ViewOp<'a> { + fn as_view(&self) -> NodeView<'a>; + fn display(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + self.as_view().fmt(f) + } +} + +pub(crate) trait ViewMutOp<'a> { + fn new(node: NodeGenericMut<'a>) -> Self; +} + +// --------------------------------------------------------------------------- +// NodeView — immutable view +// --------------------------------------------------------------------------- + +/// An immutable view of a node in the device tree. +/// +/// Borrows the `Fdt` arena and a `NodeId`, providing safe read access to the +/// node and its relationships (children, parent, path). +#[derive(Clone, Copy)] +pub(crate) struct NodeView<'a> { + fdt: *mut Fdt, + id: NodeId, + _marker: core::marker::PhantomData<&'a ()>, // for lifetime tracking +} + +unsafe impl<'a> Send for NodeView<'a> {} + +impl<'a> NodeView<'a> { + /// Creates a new `NodeView`. + pub(crate) fn new(fdt: &'a Fdt, id: NodeId) -> Self { + Self { + fdt: fdt as *const Fdt as *mut Fdt, + id, + _marker: core::marker::PhantomData, + } + } + + pub fn name(&self) -> &'a str { + self.as_node().name() + } + + /// Returns the underlying `NodeId`. + pub fn id(&self) -> NodeId { + self.id + } + + /// Returns a reference to the underlying `Node`. + pub fn as_node(&self) -> &'a Node { + self.fdt() + .node(self.id) + .expect("NodeView references a valid node") + } + + pub fn as_node_mut(&mut self) -> &'a mut Node { + self.fdt_mut() + .node_mut(self.id) + .expect("NodeViewMut references a valid node") + } + + /// Returns the `Fdt` arena this view belongs to. + pub fn fdt(&self) -> &'a Fdt { + unsafe { &*self.fdt } + } + + pub fn fdt_mut(&mut self) -> &'a mut Fdt { + unsafe { &mut *self.fdt } + } + + pub fn path(&self) -> String { + self.fdt().path_of(self.id) + } + + pub fn parent(&self) -> Option> { + self.fdt() + .parent_of(self.id) + .map(|pid| NodeView::new(self.fdt(), pid).classify()) + } + + #[allow(dead_code)] + pub fn parent_mut(&mut self) -> Option> { + let parent = self.fdt().parent_of(self.id)?; + let mut parent_view = NodeView::new(self.fdt(), parent); + let cl = parent_view.classify_mut(); + Some(cl) + } + + pub fn address_cells(&self) -> Option { + self.as_node().address_cells() + } + + pub fn size_cells(&self) -> Option { + self.as_node().size_cells() + } + + /// Parses the `reg` property and returns corrected register entries. + /// + /// Uses parent node's `ranges` property to translate bus addresses to CPU addresses. + pub fn regs(&self) -> Vec { + let node = self.as_node(); + let reg = match node.get_property("reg") { + Some(p) => p, + None => return Vec::new(), + }; + + // Get address-cells and size-cells from parent (or default 2/1) + let (addr_cells, size_cells) = self.parent_cells(); + + // Get parent's ranges for address translation + let ranges = self.parent_ranges(); + + let mut reader = reg.as_reader(); + let mut results = Vec::new(); + + while let Some(child_bus_address) = reader.read_cells(addr_cells) { + let size = if size_cells > 0 { + reader.read_cells(size_cells) + } else { + None + }; + + // Convert bus address to CPU address using ranges + let mut address = child_bus_address; + if let Some(ref ranges) = ranges { + for r in ranges { + if child_bus_address >= r.child_bus_address + && child_bus_address < r.child_bus_address + r.length + { + address = child_bus_address - r.child_bus_address + r.parent_bus_address; + break; + } + } + } + + results.push(RegFixed { + address, + child_bus_address, + size, + }); + } + + results + } + + /// Returns (address_cells, size_cells) from the parent node (defaults: 2, 1). + fn parent_cells(&self) -> (usize, usize) { + if let Some(parent) = self.parent() { + let ac = parent.as_view().address_cells().unwrap_or(2) as usize; + let sc = parent.as_view().size_cells().unwrap_or(1) as usize; + (ac, sc) + } else { + (2, 1) + } + } + + /// Returns the parent node's ranges entries for address translation. + fn parent_ranges(&self) -> Option> { + self.parent().and_then(|p| { + let view = p.as_view(); + // Get grandparent's address-cells for parsing parent_bus_address + let parent_addr_cells = p + .parent() + .and_then(|gp| gp.as_view().address_cells()) + .unwrap_or(2); + view.as_node().ranges(parent_addr_cells) + }) + } + + /// Sets the `reg` property from CPU addresses. + /// + /// Converts CPU addresses to bus addresses using parent's `ranges` property + /// and stores them in big-endian format. + pub fn set_regs(&mut self, regs: &[fdt_raw::RegInfo]) { + // Get address-cells and size-cells from parent (or default 2/1) + let (addr_cells, size_cells) = self.parent_cells(); + + // Get parent's ranges for address translation + let ranges = self.parent_ranges(); + + let mut data = Vec::new(); + + for reg in regs { + // Convert CPU address to bus address + let mut bus_address = reg.address; + if let Some(ref ranges) = ranges { + for r in ranges { + // Check if CPU address is within the range mapping + if reg.address >= r.parent_bus_address + && reg.address < r.parent_bus_address + r.length + { + // Reverse conversion: cpu_address -> bus_address + bus_address = reg.address - r.parent_bus_address + r.child_bus_address; + break; + } + } + } + + // Write bus address (big-endian) + match addr_cells { + 1 => data.extend_from_slice(&(bus_address as u32).to_be_bytes()), + 2 => { + data.extend_from_slice(&((bus_address >> 32) as u32).to_be_bytes()); + data.extend_from_slice(&((bus_address & 0xFFFF_FFFF) as u32).to_be_bytes()); + } + n => { + // Handle arbitrary address cells + for i in 0..n { + let shift = (n - 1 - i) * 32; + data.extend_from_slice(&(((bus_address >> shift) as u32).to_be_bytes())); + } + } + } + + // Write size (big-endian) + let size = reg.size.unwrap_or(0); + match size_cells { + 1 => data.extend_from_slice(&(size as u32).to_be_bytes()), + 2 => { + data.extend_from_slice(&((size >> 32) as u32).to_be_bytes()); + data.extend_from_slice(&((size & 0xFFFF_FFFF) as u32).to_be_bytes()); + } + n => { + for i in 0..n { + let shift = (n - 1 - i) * 32; + data.extend_from_slice(&(((size >> shift) as u32).to_be_bytes())); + } + } + } + } + + let prop = Property::new("reg", data); + self.as_node_mut().set_property(prop); + } + + pub(crate) fn classify(&self) -> NodeType<'a> { + if let Some(node) = ClockNodeView::try_from_view(*self) { + return NodeType::Clock(node); + } + + if let Some(node) = PciNodeView::try_from_view(*self) { + return NodeType::Pci(node); + } + + if let Some(node) = MemoryNodeView::try_from_view(*self) { + return NodeType::Memory(node); + } + + if let Some(node) = IntcNodeView::try_from_view(*self) { + return NodeType::InterruptController(node); + } + + NodeType::Generic(NodeGeneric { inner: *self }) + } + + pub(crate) fn classify_mut(&mut self) -> NodeTypeMut<'a> { + if let Some(node) = ClockNodeViewMut::try_from_view(*self) { + return NodeTypeMut::Clock(node); + } + + if let Some(node) = PciNodeViewMut::try_from_view(*self) { + return NodeTypeMut::Pci(node); + } + + if let Some(node) = MemoryNodeViewMut::try_from_view(*self) { + return NodeTypeMut::Memory(node); + } + + if let Some(node) = IntcNodeViewMut::try_from_view(*self) { + return NodeTypeMut::InterruptController(node); + } + + NodeTypeMut::Generic(NodeGenericMut { inner: *self }) + } +} + +impl core::fmt::Display for NodeView<'_> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{}", self.path())?; + for prop in self.as_node().properties() { + write!(f, "\n {} = ", prop.name())?; + if prop.name() == "compatible" { + write!(f, "[")?; + let strs: Vec<&str> = prop.as_str_iter().collect(); + for (i, s) in strs.iter().enumerate() { + write!(f, "\"{}\"", s)?; + if i < strs.len() - 1 { + write!(f, ", ")?; + } + } + write!(f, "]")?; + continue; + } + if let Some(s) = prop.as_str() { + write!(f, "\"{}\";", s)?; + } else { + for cell in prop.get_u32_iter() { + write!(f, "{:#x} ", cell)?; + } + write!(f, ";")?; + } + } + Ok(()) + } +} + +// --------------------------------------------------------------------------- +// NodeType — classified immutable view enum +// --------------------------------------------------------------------------- + +#[enum_dispatch(ViewOp)] +/// Typed node view enum, allowing pattern matching by node kind. +pub enum NodeType<'a> { + /// A clock provider node (has `#clock-cells` property). + Clock(ClockNodeView<'a>), + /// A memory node (`device_type = "memory"` or name starts with "memory"). + Memory(MemoryNodeView<'a>), + /// An interrupt controller node (has the `interrupt-controller` property). + InterruptController(IntcNodeView<'a>), + /// A PCI bridge node (`device_type = "pci"`). + Pci(PciNodeView<'a>), + /// A generic node (no special classification). + Generic(NodeGeneric<'a>), +} + +impl<'a> NodeType<'a> { + /// Returns the underlying `Node` reference. + pub fn as_node(&self) -> &'a Node { + self.as_view().as_node() + } + + /// Returns the node's full path string. + pub fn path(&self) -> String { + self.as_view().path() + } + + pub fn parent(&self) -> Option> { + self.as_view().parent() + } + + /// Returns the node's ID. + pub fn id(&self) -> NodeId { + self.as_view().id() + } + + /// Returns the node's name. + pub fn name(&self) -> &'a str { + self.as_view().name() + } + + /// Parses the `reg` property and returns corrected register entries. + pub fn regs(&self) -> Vec { + self.as_view().regs() + } +} + +impl core::fmt::Display for NodeType<'_> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + self.display(f) + } +} + +// --------------------------------------------------------------------------- +// NodeTypeMut — classified mutable view enum +// --------------------------------------------------------------------------- + +/// Typed mutable node view enum. +#[enum_dispatch(ViewOp)] +pub enum NodeTypeMut<'a> { + Clock(ClockNodeViewMut<'a>), + Memory(MemoryNodeViewMut<'a>), + InterruptController(IntcNodeViewMut<'a>), + Pci(PciNodeViewMut<'a>), + Generic(NodeGenericMut<'a>), +} + +impl<'a> NodeTypeMut<'a> { + /// Returns the inner node ID regardless of variant. + pub fn id(&self) -> NodeId { + self.as_view().id() + } + + /// Sets the `reg` property from CPU addresses. + /// + /// Converts CPU addresses to bus addresses using parent's `ranges` property + /// and stores them in big-endian format. + pub fn set_regs(&mut self, regs: &[fdt_raw::RegInfo]) { + self.as_view().set_regs(regs); + } +} + +// --------------------------------------------------------------------------- +// Fdt convenience methods returning views +// --------------------------------------------------------------------------- + +impl Fdt { + /// Returns a `NodeView` for the given node ID, if it exists. + fn view(&self, id: NodeId) -> Option> { + if self.node(id).is_some() { + Some(NodeView::new(self, id)) + } else { + None + } + } + + /// Returns a classified `NodeType` for the given node ID. + pub fn view_typed(&self, id: NodeId) -> Option> { + self.view(id).map(|v| v.classify()) + } + + /// Returns a classified `NodeTypeMut` for the given node ID. + pub fn view_typed_mut(&mut self, id: NodeId) -> Option> { + self.view(id).map(|mut v| v.classify_mut()) + } +} + +impl<'a> NodeGenericMut<'a> { + pub fn add_child_memory(&mut self, name: &str) -> MemoryNodeViewMut<'a> { + self.add_child(name) + } + + pub fn add_child_interrupt_controller(&mut self, name: &str) -> IntcNodeViewMut<'a> { + self.add_child(name) + } +} + +#[derive(Clone, Copy, Debug)] +pub struct RegFixed { + pub address: u64, + pub child_bus_address: u64, + pub size: Option, +} diff --git a/fdt-edit/src/node/pci.rs b/fdt-edit/src/node/view/pci.rs similarity index 56% rename from fdt-edit/src/node/pci.rs rename to fdt-edit/src/node/view/pci.rs index 2223d2f..2b7cbc5 100644 --- a/fdt-edit/src/node/pci.rs +++ b/fdt-edit/src/node/view/pci.rs @@ -1,10 +1,16 @@ +//! PCI node view specialization. + use core::ops::{Deref, Range}; use alloc::vec::Vec; -use fdt_raw::{FdtError, Phandle, data::U32Iter}; -use log::debug; +use fdt_raw::{FdtError, Phandle}; + +use super::NodeView; +use crate::{NodeGeneric, NodeGenericMut, Property, ViewMutOp, ViewOp}; -use crate::node::gerneric::NodeRefGen; +// --------------------------------------------------------------------------- +// PCI types +// --------------------------------------------------------------------------- /// PCI address space types. #[derive(Clone, Debug, PartialEq)] @@ -58,24 +64,38 @@ pub struct PciInterruptInfo { pub irqs: Vec, } -/// PCI node reference. -/// -/// Provides specialized access to PCI bridge nodes and their properties. -#[derive(Clone, Debug)] -pub struct NodeRefPci<'a> { - /// The underlying generic node reference - pub node: NodeRefGen<'a>, +// --------------------------------------------------------------------------- +// PciNodeView +// --------------------------------------------------------------------------- + +/// Specialized view for PCI bridge nodes. +#[derive(Clone, Copy)] +pub struct PciNodeView<'a> { + pub(super) inner: NodeGeneric<'a>, } -impl<'a> NodeRefPci<'a> { - /// Attempts to create a PCI node reference from a generic node. - /// - /// Returns `Err` with the original node if it's not a PCI node. - pub fn try_from(node: NodeRefGen<'a>) -> Result> { - if node.device_type() == Some("pci") { - Ok(Self { node }) +impl<'a> Deref for PciNodeView<'a> { + type Target = NodeGeneric<'a>; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl<'a> ViewOp<'a> for PciNodeView<'a> { + fn as_view(&self) -> NodeView<'a> { + self.inner.as_view() + } +} + +impl<'a> PciNodeView<'a> { + pub(crate) fn try_from_view(view: NodeView<'a>) -> Option { + if view.as_node().is_pci() { + Some(Self { + inner: NodeGeneric { inner: view }, + }) } else { - Err(node) + None } } @@ -83,52 +103,93 @@ impl<'a> NodeRefPci<'a> { /// /// Defaults to 1 for PCI devices if not specified. pub fn interrupt_cells(&self) -> u32 { - self.find_property("#interrupt-cells") + self.as_view() + .as_node() + .get_property("#interrupt-cells") .and_then(|prop| prop.get_u32()) - .unwrap_or(1) // Default to 1 interrupt cell for PCI + .unwrap_or(1) } - /// Get the interrupt-map-mask property if present - pub fn interrupt_map_mask(&self) -> Option> { - self.find_property("interrupt-map-mask") - .map(|prop| prop.get_u32_iter()) + /// Get the interrupt-map-mask property if present. + pub fn interrupt_map_mask(&self) -> Option> { + self.as_view() + .as_node() + .get_property("interrupt-map-mask") + .map(|prop| prop.get_u32_iter().collect()) } - /// Get the bus range property if present + /// Get the bus range property if present. pub fn bus_range(&self) -> Option> { - self.find_property("bus-range").and_then(|prop| { - let mut data = prop.get_u32_iter(); - let start = data.next()?; - let end = data.next()?; + self.as_view() + .as_node() + .get_property("bus-range") + .and_then(|prop| { + let mut iter = prop.get_u32_iter(); + let start = iter.next()?; + let end = iter.next()?; + Some(start..end) + }) + } - Some(start..end) - }) + /// Decode PCI address space from the high cell of PCI address. + /// + /// PCI address high cell format: + /// - Bits 31-28: 1 for IO space, 2 for Memory32, 3 for Memory64 + /// - Bit 30: Prefetchable for memory spaces + fn decode_pci_address_space(&self, pci_hi: u32) -> (PciSpace, bool) { + let space_code = (pci_hi >> 24) & 0x03; + let prefetchable = (pci_hi >> 30) & 0x01 == 1; + + let space = match space_code { + 1 => PciSpace::IO, + 2 => PciSpace::Memory32, + 3 => PciSpace::Memory64, + _ => PciSpace::Memory32, + }; + + (space, prefetchable) } - /// Get the ranges property for address translation + /// Get the ranges property for address translation. pub fn ranges(&self) -> Option> { - let prop = self.find_property("ranges")?; - + let prop = self.as_view().as_node().get_property("ranges")?; let mut data = prop.as_reader(); - let mut ranges = Vec::new(); // PCI ranges format: // child-bus-address: 3 cells (pci.hi pci.mid pci.lo) - PCI 地址固定 3 cells // parent-bus-address: 使用父节点的 #address-cells // size: 使用当前节点的 #size-cells - let parent_addr_cells = self.ctx.parent_address_cells() as usize; - let size_cells = self.size_cells().unwrap_or(2) as usize; + + // Get parent's address-cells + let parent_addr_cells = if let Some(parent) = self.as_view().parent() { + parent.as_view().address_cells().unwrap_or(2) as usize + } else { + 2_usize + }; + + let size_cells = self.as_view().size_cells().unwrap_or(2) as usize; while let Some(pci_hi) = data.read_u32() { // Parse child bus address (3 cells for PCI: phys.hi, phys.mid, phys.lo) - let bus_address = data.read_u64()?; + // pci_hi 用于解析地址空间类型,bus_address 由 pci_mid 和 pci_lo 组成 + let pci_mid = data.read_u32()?; + let pci_lo = data.read_u32()?; + let bus_address = ((pci_mid as u64) << 32) | (pci_lo as u64); // Parse parent bus address (使用父节点的 #address-cells) - let parent_addr = data.read_cells(parent_addr_cells)?; + let mut parent_addr = 0u64; + for _ in 0..parent_addr_cells { + let cell = data.read_u32()? as u64; + parent_addr = (parent_addr << 32) | cell; + } // Parse size (使用当前节点的 #size-cells) - let size = data.read_cells(size_cells)?; + let mut size = 0u64; + for _ in 0..size_cells { + let cell = data.read_u32()? as u64; + size = (size << 32) | cell; + } // Extract PCI address space and prefetchable from child_addr[0] let (space, prefetchable) = self.decode_pci_address_space(pci_hi); @@ -145,131 +206,40 @@ impl<'a> NodeRefPci<'a> { Some(ranges) } - /// Decode PCI address space from the high cell of PCI address - fn decode_pci_address_space(&self, pci_hi: u32) -> (PciSpace, bool) { - // PCI address high cell format: - // Bits 31-28: 1 for IO space, 2 for Memory32, 3 for Memory64 - // Bit 30: Prefetchable for memory spaces - let space_code = (pci_hi >> 24) & 0x03; - let prefetchable = (pci_hi >> 30) & 0x01 == 1; - - let space = match space_code { - 1 => PciSpace::IO, - 2 => PciSpace::Memory32, - 3 => PciSpace::Memory64, - _ => PciSpace::Memory32, // Default fallback - }; - - (space, prefetchable) - } - - /// Get interrupt information for a PCI device - /// Parameters: bus, device, function, pin (1=INTA, 2=INTB, 3=INTC, 4=INTD) - pub fn child_interrupts( - &self, - bus: u8, - device: u8, - function: u8, - interrupt_pin: u8, - ) -> Result { - // Get interrupt-map and mask - let interrupt_map = self.interrupt_map()?; - - // Convert mask to Vec for indexed access - let mask: Vec = self - .interrupt_map_mask() - .ok_or(FdtError::NotFound)? - .collect(); - - // Construct child address for PCI device - // Format: [bus_num, device_num, func_num] at appropriate bit positions - let child_addr_high = ((bus as u32 & 0xff) << 16) - | ((device as u32 & 0x1f) << 11) - | ((function as u32 & 0x7) << 8); - let child_addr_mid = 0u32; - let child_addr_low = 0u32; - - let child_addr_cells = self.address_cells().unwrap_or(3) as usize; - let child_irq_cells = self.interrupt_cells() as usize; - - let encoded_address = [child_addr_high, child_addr_mid, child_addr_low]; - let mut masked_child_address = Vec::with_capacity(child_addr_cells); - - // Apply mask to child address - for (idx, value) in encoded_address.iter().take(child_addr_cells).enumerate() { - let mask_value = mask.get(idx).copied().unwrap_or(0xffff_ffff); - masked_child_address.push(value & mask_value); - } - - // If encoded_address is shorter than child_addr_cells, pad with 0 - let remaining = child_addr_cells.saturating_sub(encoded_address.len()); - masked_child_address.extend(core::iter::repeat_n(0, remaining)); - - let encoded_irq = [interrupt_pin as u32]; - let mut masked_child_irq = Vec::with_capacity(child_irq_cells); - - // Apply mask to child IRQ - for (idx, value) in encoded_irq.iter().take(child_irq_cells).enumerate() { - let mask_value = mask - .get(child_addr_cells + idx) - .copied() - .unwrap_or(0xffff_ffff); - masked_child_irq.push(value & mask_value); - } - - // If encoded_irq is shorter than child_irq_cells, pad with 0 - let remaining_irq = child_irq_cells.saturating_sub(encoded_irq.len()); - masked_child_irq.extend(core::iter::repeat_n(0, remaining_irq)); - - // Search for matching entry in interrupt-map - for mapping in &interrupt_map { - if mapping.child_address == masked_child_address - && mapping.child_irq == masked_child_irq - { - return Ok(PciInterruptInfo { - irqs: mapping.parent_irq.clone(), - }); - } - } - - // Fall back to simple IRQ calculation - let simple_irq = (device as u32 * 4 + interrupt_pin as u32) % 32; - Ok(PciInterruptInfo { - irqs: vec![simple_irq], - }) - } - - /// Parse interrupt-map property + /// 解析 interrupt-map 属性 pub fn interrupt_map(&self) -> Result, FdtError> { let prop = self - .find_property("interrupt-map") + .as_view() + .as_node() + .get_property("interrupt-map") .ok_or(FdtError::NotFound)?; - // Convert mask and data to Vec for indexed access + // 将 mask 转换为 Vec 以便索引访问 let mask: Vec = self .interrupt_map_mask() .ok_or(FdtError::NotFound)? + .into_iter() .collect(); let mut data = prop.as_reader(); let mut mappings = Vec::new(); - // Calculate size of each entry - // Format: - let child_addr_cells = self.address_cells().unwrap_or(3) as usize; + // 计算每个条目的大小 + // 格式: + let child_addr_cells = self.as_view().address_cells().unwrap_or(3) as usize; let child_irq_cells = self.interrupt_cells() as usize; loop { - // Parse child address + // 解析子地址 let mut child_address = Vec::with_capacity(child_addr_cells); for _ in 0..child_addr_cells { match data.read_u32() { Some(v) => child_address.push(v), - None => return Ok(mappings), // End of data + None => return Ok(mappings), // 数据结束 } } - // Parse child IRQ + // 解析子 IRQ let mut child_irq = Vec::with_capacity(child_irq_cells); for _ in 0..child_irq_cells { match data.read_u32() { @@ -278,62 +248,40 @@ impl<'a> NodeRefPci<'a> { } } - // Parse interrupt parent phandle + // 解析中断父 phandle let interrupt_parent_raw = match data.read_u32() { Some(v) => v, None => return Ok(mappings), }; let interrupt_parent = Phandle::from(interrupt_parent_raw); - debug!( - "Looking for interrupt parent phandle: 0x{:x} (raw: {})", - interrupt_parent.raw(), - interrupt_parent_raw - ); - debug!( - "Context phandle_map keys: {:?}", - self.ctx - .phandle_map - .keys() - .map(|p| format!("0x{:x}", p.raw())) - .collect::>() - ); - - // Look up interrupt parent node by phandle to get its #address-cells and #interrupt-cells - // According to devicetree spec, parent unit address in interrupt-map uses interrupt parent's #address-cells + // 通过 phandle 查找中断父节点以获取其 #address-cells 和 #interrupt-cells + // 根据 devicetree 规范,interrupt-map 中的 parent unit address 使用中断父节点的 #address-cells let (parent_addr_cells, parent_irq_cells) = - if let Some(irq_parent) = self.ctx.find_by_phandle(interrupt_parent) { - debug!("Found interrupt parent: {:?}", irq_parent.name); - - // Use interrupt parent node's #address-cells directly - let addr_cells = irq_parent.address_cells().unwrap_or(0) as usize; + if let Some(irq_parent) = self.as_view().fdt().get_by_phandle(interrupt_parent) { + // 直接使用中断父节点的 #address-cells + let addr_cells = irq_parent.as_view().address_cells().unwrap_or(0) as usize; let irq_cells = irq_parent + .as_view() + .as_node() .get_property("#interrupt-cells") .and_then(|p| p.get_u32()) .unwrap_or(3) as usize; - debug!( - "irq_parent addr_cells: {}, irq_cells: {}", - addr_cells, irq_cells - ); (addr_cells, irq_cells) } else { - debug!( - "Interrupt parent phandle 0x{:x} NOT FOUND in context!", - interrupt_parent.raw() - ); - // Default values: address_cells=0, interrupt_cells=3 (GIC format) + // 默认值:address_cells=0, interrupt_cells=3 (GIC 格式) (0, 3) }; - // Skip parent address cells + // 跳过父地址 cells for _ in 0..parent_addr_cells { if data.read_u32().is_none() { return Ok(mappings); } } - // Parse parent IRQ + // 解析父 IRQ let mut parent_irq = Vec::with_capacity(parent_irq_cells); for _ in 0..parent_irq_cells { match data.read_u32() { @@ -342,7 +290,7 @@ impl<'a> NodeRefPci<'a> { } } - // Apply mask to child address and IRQ + // 应用 mask 到子地址和 IRQ let masked_address: Vec = child_address .iter() .enumerate() @@ -371,12 +319,136 @@ impl<'a> NodeRefPci<'a> { }); } } + + /// 获取 PCI 设备的中断信息 + /// 参数: bus, device, function, pin (1=INTA, 2=INTB, 3=INTC, 4=INTD) + pub fn child_interrupts( + &self, + bus: u8, + device: u8, + function: u8, + interrupt_pin: u8, + ) -> Result { + // 获取 interrupt-map 和 mask + let interrupt_map = self.interrupt_map()?; + + // 将 mask 转换为 Vec 以便索引访问 + let mask: Vec = self + .interrupt_map_mask() + .ok_or(FdtError::NotFound)? + .into_iter() + .collect(); + + // 构造 PCI 设备的子地址 + // 格式: [bus_num, device_num, func_num] 在适当的位 + let child_addr_high = ((bus as u32 & 0xff) << 16) + | ((device as u32 & 0x1f) << 11) + | ((function as u32 & 0x07) << 8); + let child_addr_mid = 0u32; + let child_addr_low = 0u32; + + let child_addr_cells = self.as_view().address_cells().unwrap_or(3) as usize; + let child_irq_cells = self.interrupt_cells() as usize; + + let encoded_address = [child_addr_high, child_addr_mid, child_addr_low]; + let mut masked_child_address = Vec::with_capacity(child_addr_cells); + + // 应用 mask 到子地址 + for (idx, value) in encoded_address.iter().take(child_addr_cells).enumerate() { + let mask_value = mask.get(idx).copied().unwrap_or(0xffff_ffff); + masked_child_address.push(value & mask_value); + } + + // 如果 encoded_address 比 child_addr_cells 短,填充 0 + let remaining = child_addr_cells.saturating_sub(encoded_address.len()); + masked_child_address.extend(core::iter::repeat_n(0, remaining)); + + let encoded_irq = [interrupt_pin as u32]; + let mut masked_child_irq = Vec::with_capacity(child_irq_cells); + + // 应用 mask 到子 IRQ + for (idx, value) in encoded_irq.iter().take(child_irq_cells).enumerate() { + let mask_value = mask + .get(child_addr_cells + idx) + .copied() + .unwrap_or(0xffff_ffff); + masked_child_irq.push(value & mask_value); + } + + // 如果 encoded_irq 比 child_irq_cells 短,填充 0 + let remaining_irq = child_irq_cells.saturating_sub(encoded_irq.len()); + masked_child_irq.extend(core::iter::repeat_n(0, remaining_irq)); + + // 在 interrupt-map 中查找匹配的条目 + for mapping in &interrupt_map { + if mapping.child_address == masked_child_address + && mapping.child_irq == masked_child_irq + { + return Ok(PciInterruptInfo { + irqs: mapping.parent_irq.clone(), + }); + } + } + + // 回退到简单的 IRQ 计算 + let simple_irq = (device as u32 * 4 + interrupt_pin as u32) % 32; + Ok(PciInterruptInfo { + irqs: vec![simple_irq], + }) + } +} + +// --------------------------------------------------------------------------- +// PciNodeViewMut +// --------------------------------------------------------------------------- + +/// Mutable view for PCI bridge nodes. +pub struct PciNodeViewMut<'a> { + pub(super) inner: NodeGenericMut<'a>, } -impl<'a> Deref for NodeRefPci<'a> { - type Target = NodeRefGen<'a>; +impl<'a> Deref for PciNodeViewMut<'a> { + type Target = NodeGenericMut<'a>; fn deref(&self) -> &Self::Target { - &self.node + &self.inner + } +} + +impl<'a> ViewOp<'a> for PciNodeViewMut<'a> { + fn as_view(&self) -> NodeView<'a> { + self.inner.as_view() + } +} + +impl<'a> ViewMutOp<'a> for PciNodeViewMut<'a> { + fn new(node: NodeGenericMut<'a>) -> Self { + let mut s = Self { inner: node }; + let n = s.inner.inner.as_node_mut(); + // Set PCI-specific properties + n.set_property(Property::new("device_type", b"pci\0".to_vec())); + // PCI uses #address-cells = 3, #size-cells = 2 + n.set_property(Property::new( + "#address-cells", + (3u32).to_be_bytes().to_vec(), + )); + n.set_property(Property::new("#size-cells", (2u32).to_be_bytes().to_vec())); + n.set_property(Property::new( + "#interrupt-cells", + (1u32).to_be_bytes().to_vec(), + )); + s + } +} + +impl<'a> PciNodeViewMut<'a> { + pub(crate) fn try_from_view(view: NodeView<'a>) -> Option { + if view.as_node().is_pci() { + Some(Self { + inner: NodeGenericMut { inner: view }, + }) + } else { + None + } } } diff --git a/fdt-edit/src/prop/mod.rs b/fdt-edit/src/prop/mod.rs index 3b9a0fb..6a50d71 100644 --- a/fdt-edit/src/prop/mod.rs +++ b/fdt-edit/src/prop/mod.rs @@ -11,9 +11,9 @@ use alloc::{ vec::Vec, }; -use fdt_raw::data::{Bytes, Reader, StrIter, U32Iter}; +use fdt_raw::data::{Bytes, StrIter, U32Iter}; // Re-export from fdt_raw -pub use fdt_raw::{Phandle, RegInfo, Status}; +use crate::Reader; /// A mutable device tree property. /// diff --git a/fdt-edit/tests/clock.rs b/fdt-edit/tests/clock.rs index e967cab..f5575c8 100644 --- a/fdt-edit/tests/clock.rs +++ b/fdt-edit/tests/clock.rs @@ -1,203 +1,74 @@ -#![cfg(unix)] +//! Clock node view tests. use dtb_file::*; -use fdt_edit::NodeKind; -use fdt_edit::*; - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_clock_node_detection() { - // Test clock node detection using RPI 4B DTB - let raw_data = fdt_rpi_4b(); - let fdt = Fdt::from_bytes(&raw_data).unwrap(); - - // Traverse to find clock nodes (nodes with #clock-cells property) - let mut clock_count = 0; - for node in fdt.all_nodes() { - if let NodeKind::Clock(clock) = node.as_ref() { - clock_count += 1; - println!( - "Clock node: {} (#clock-cells={})", - clock.name(), - clock.clock_cells - ); - } +use fdt_edit::{Fdt, NodeType}; + +#[test] +fn test_clock_node_detection() { + let raw_data = fdt_phytium(); + let fdt = Fdt::from_bytes(&raw_data).unwrap(); + + let mut clock_count = 0; + for node in fdt.all_nodes() { + if let NodeType::Clock(clock) = node { + clock_count += 1; + println!( + "Clock node: {} #clock-cells={}", + clock.path(), + clock.clock_cells() + ); } - println!("Found {} clock nodes", clock_count); } - #[test] - fn test_clock_properties() { - let raw_data = fdt_rpi_4b(); - let fdt = Fdt::from_bytes(&raw_data).unwrap(); - - for node in fdt.all_nodes() { - if let NodeKind::Clock(clock) = node.as_ref() { - // Get #clock-cells - let cells = clock.clock_cells; - println!("Clock: {} cells={}", clock.name(), cells); - - // Get output names - if !clock.clock_output_names.is_empty() { - println!(" output-names: {:?}", clock.clock_output_names); - } + println!("Total clock nodes: {}", clock_count); + // 飞腾 DTB 应该有时钟节点 + assert!(clock_count > 0, "phytium DTB should have clock nodes"); +} - match &clock.kind { - ClockType::Fixed(fixed) => { - println!( - " Fixed clock: freq={}Hz accuracy={:?}", - fixed.frequency, fixed.accuracy - ); - } - ClockType::Normal => { - println!(" Clock provider"); - } +#[test] +fn test_clock_output_names() { + let raw_data = fdt_phytium(); + let fdt = Fdt::from_bytes(&raw_data).unwrap(); + + for node in fdt.all_nodes() { + if let NodeType::Clock(clock) = node { + let names = clock.clock_output_names(); + if !names.is_empty() { + println!("Clock {} has output names: {:?}", clock.path(), names); + + // Test output_name method + if let Some(first_name) = clock.output_name(0) { + assert_eq!(first_name, names[0]); + println!(" First output: {}", first_name); } } } } +} - #[test] - fn test_fixed_clock() { - let raw_data = fdt_rpi_4b(); - let fdt = Fdt::from_bytes(&raw_data).unwrap(); - - // Find fixed clocks - let mut found_with_freq = false; - for node in fdt.all_nodes() { - if let NodeKind::Clock(clock) = node.as_ref() - && let ClockType::Fixed(fixed) = &clock.kind - { - // Print fixed clock information - println!( - "Fixed clock found: {} freq={}Hz accuracy={:?}", - clock.name(), - fixed.frequency, - fixed.accuracy +#[test] +fn test_fixed_clock() { + let raw_data = fdt_phytium(); + let fdt = Fdt::from_bytes(&raw_data).unwrap(); + + for node in fdt.all_nodes() { + if let NodeType::Clock(clock) = node { + let clock_type = clock.clock_type(); + if let fdt_edit::ClockType::Fixed(fixed) = clock_type { + println!("Fixed clock: {} freq={}Hz", clock.path(), fixed.frequency); + + // Fixed clock should have a frequency + assert!( + fixed.frequency > 0 || fixed.accuracy.is_some(), + "Fixed clock should have frequency or accuracy" ); - // Some fixed clocks (e.g., cam1_clk, cam0_clk) don't have clock-frequency property - if fixed.frequency > 0 { - found_with_freq = true; - } - } - } - // At least one fixed clock should have a frequency - assert!( - found_with_freq, - "Should find at least one fixed clock with frequency" - ); - } - - #[test] - fn test_clock_output_name() { - let raw_data = fdt_rpi_4b(); - let fdt = Fdt::from_bytes(&raw_data).unwrap(); - - for node in fdt.all_nodes() { - if let NodeKind::Clock(clock) = node.as_ref() { - let names = &clock.clock_output_names; - if !names.is_empty() { - // Test output_name method - let first = clock.output_name(0); - assert_eq!(first, Some(names[0].as_str())); - // If there are multiple outputs, test indexed access - if names.len() > 1 && clock.clock_cells > 0 { - let second = clock.output_name(1); - assert_eq!(second, Some(names[1].as_str())); - } + if let Some(ref name) = fixed.name { + println!(" Name: {}", name); } - } - } - } - - #[test] - fn test_clock_type_conversion() { - let raw_data = fdt_rpi_4b(); - let fdt = Fdt::from_bytes(&raw_data).unwrap(); - - for node in fdt.all_nodes() { - if let NodeKind::Clock(clock) = node.as_ref() { - match &clock.kind { - ClockType::Fixed(fixed) => { - // Print fixed clock information - println!( - "Fixed clock: {} freq={} accuracy={:?}", - clock.name(), - fixed.frequency, - fixed.accuracy - ); - } - ClockType::Normal => { - // Test Normal type - println!("Clock {} is a provider", clock.name()); - } - } - } - } - } - - #[test] - fn test_clocks_with_context() { - let raw_data = fdt_rpi_4b(); - let fdt = Fdt::from_bytes(&raw_data).unwrap(); - - let mut found_clocks = false; - for node in fdt.all_nodes() { - if let NodeKind::Clock(clock_ref) = node.as_ref() { - found_clocks = true; - - let clocks = clock_ref.clocks(); - if !clocks.is_empty() { - found_clocks = true; - println!( - "Node: {} has {} clock references:", - clock_ref.name(), - clocks.len() - ); - for (i, clk) in clocks.iter().enumerate() { - println!( - " [{}] phandle={:?} cells={} specifier={:?} name={:?}", - i, clk.phandle, clk.cells, clk.specifier, clk.name - ); - // Verify specifier length matches cells - assert_eq!( - clk.specifier.len(), - clk.cells as usize, - "specifier length should match cells" - ); - } - } - } - } - assert!(found_clocks, "Should find nodes with clock references"); - } - - #[test] - fn test_clock_ref_select() { - let raw_data = fdt_rpi_4b(); - let fdt = Fdt::from_bytes(&raw_data).unwrap(); - for node in fdt.all_nodes() { - // Use as_clock_ref to get clock reference with context - if let NodeKind::Clock(clock) = node.as_ref() { - let clocks = clock.clocks(); - for clk in clocks { - // Test select() method - if clk.cells > 0 { - assert!( - clk.select().is_some(), - "select() should return Some when cells > 0" - ); - assert_eq!( - clk.select(), - clk.specifier.first().copied(), - "select() should return first specifier" - ); - } + if let Some(accuracy) = fixed.accuracy { + println!(" Accuracy: {} ppb", accuracy); } } } diff --git a/fdt-edit/tests/display_debug.rs b/fdt-edit/tests/display_debug.rs deleted file mode 100644 index 344b57f..0000000 --- a/fdt-edit/tests/display_debug.rs +++ /dev/null @@ -1,271 +0,0 @@ -#![cfg(unix)] - -use dtb_file::*; -use fdt_edit::*; - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_fdt_display() { - // Test Display functionality using RPI 4B DTB - let raw_data = fdt_rpi_4b(); - let fdt = Fdt::from_bytes(&raw_data).unwrap(); - - // Test Display output - let dts_output = format!("{}", fdt); - - // Verify output contains DTS header - assert!(dts_output.contains("/dts-v1/;")); - - // Verify output contains root node - assert!(dts_output.contains("/ {")); - - println!("FDT Display output:\n{}", dts_output); - } - - #[test] - fn test_fdt_debug() { - let raw_data = fdt_rpi_4b(); - let fdt = Fdt::from_bytes(&raw_data).unwrap(); - - // Test Debug output - let debug_output = format!("{:?}", fdt); - - // Verify Debug output contains struct information - assert!(debug_output.contains("Fdt")); - assert!(debug_output.contains("boot_cpuid_phys")); - - println!("FDT Debug output:\n{}", debug_output); - } - - #[test] - fn test_node_display() { - let raw_data = fdt_rpi_4b(); - let fdt = Fdt::from_bytes(&raw_data).unwrap(); - - // Find a node to test - for node in fdt.all_nodes() { - if node.name().contains("gpio") { - let dts_output = format!("{}", node); - - // Verify output contains node name - assert!(dts_output.contains("gpio")); - - println!("Node Display output:\n{}", dts_output); - break; - } - } - } - - #[test] - fn test_node_debug() { - let raw_data = fdt_rpi_4b(); - let fdt = Fdt::from_bytes(&raw_data).unwrap(); - - for node in fdt.all_nodes() { - if node.name().contains("gpio") { - let debug_output = format!("{:?}", node); - - // Verify Debug output contains Node struct information - assert!(debug_output.contains("NodeRef")); - assert!(debug_output.contains("name")); - - println!("Node Debug output:\n{}", debug_output); - break; - } - } - } - - #[test] - fn test_clock_node_display() { - let raw_data = fdt_rpi_4b(); - let fdt = Fdt::from_bytes(&raw_data).unwrap(); - - for node in fdt.all_nodes() { - if let NodeKind::Clock(clock) = node.as_ref() { - let display_output = format!("{}", clock); - let debug_output = format!("{:?}", clock); - - println!("Clock Node Display:\n{}", display_output); - println!("Clock Node Debug:\n{}", debug_output); - - // Verify output contains clock-related information - assert!(display_output.contains("Clock Node")); - - // Verify Debug contains detailed information - assert!(debug_output.contains("NodeRefClock")); - assert!(debug_output.contains("clock_cells")); - - break; - } - } - } - - #[test] - fn test_interrupt_controller_display() { - let raw_data = fdt_rpi_4b(); - let fdt = Fdt::from_bytes(&raw_data).unwrap(); - - for node in fdt.all_nodes() { - if let NodeKind::InterruptController(ic) = node.as_ref() { - let display_output = format!("{}", ic); - let debug_output = format!("{:?}", ic); - - println!("Interrupt Controller Display:\n{}", display_output); - println!("Interrupt Controller Debug:\n{}", debug_output); - - // Verify output contains interrupt controller-related information - assert!(display_output.contains("Interrupt Controller")); - - // Verify Debug contains detailed information - assert!(debug_output.contains("NodeRefInterruptController")); - assert!(debug_output.contains("interrupt_cells")); - - break; - } - } - } - - #[test] - fn test_memory_node_display() { - let raw_data = fdt_phytium(); - let fdt = Fdt::from_bytes(&raw_data).unwrap(); - - for node in fdt.all_nodes() { - if let NodeKind::Memory(mem) = node.as_ref() { - let display_output = format!("{}", mem); - let debug_output = format!("{:?}", mem); - - println!("Memory Node Display:\n{}", display_output); - println!("Memory Node Debug:\n{}", debug_output); - - // Verify output contains memory-related information - assert!(display_output.contains("Memory Node")); - - // Verify Debug contains detailed information - assert!(debug_output.contains("NodeRefMemory")); - assert!(debug_output.contains("regions_count")); - - break; - } - } - } - - #[test] - fn test_noderef_display_with_details() { - let raw_data = fdt_rpi_4b(); - let fdt = Fdt::from_bytes(&raw_data).unwrap(); - - for node in fdt.all_nodes() { - if node.name().contains("clock") { - let display_output = format!("{}", node); - - println!("NodeRef Display with details:\n{}", display_output); - - // Verify output contains type information - assert!(display_output.contains("Clock Node")); - - break; - } - } - } - - #[test] - fn test_create_simple_fdt() { - let fdt = Fdt::new(); - - // Test basic Display functionality - let dts_output = format!("{}", fdt); - println!("Created FDT Display:\n{}", dts_output); - - // Verify output contains basic header - assert!(dts_output.contains("/dts-v1/;")); - assert!(dts_output.contains("/ {")); - } - - #[test] - fn test_manual_node_display() { - let node = Node::new("test-node"); - - // Test basic Display functionality - let display_output = format!("{}", node); - println!("Manual Node Display:\n{}", display_output); - - // Verify output contains node name - assert!(display_output.contains("test-node")); - - // Test Debug - let debug_output = format!("{:?}", node); - println!("Manual Node Debug:\n{}", debug_output); - - assert!(debug_output.contains("Node")); - assert!(debug_output.contains("test-node")); - } - - #[test] - fn test_fdt_deep_debug() { - let raw_data = fdt_rpi_4b(); - let fdt = Fdt::from_bytes(&raw_data).unwrap(); - - // Test basic Debug format - let simple_debug = format!("{:?}", fdt); - println!("FDT Simple Debug:\n{}", simple_debug); - - // Verify basic format contains basic information - assert!(simple_debug.contains("Fdt")); - assert!(simple_debug.contains("boot_cpuid_phys")); - - // Test deep Debug format - let deep_debug = format!("{:#?}", fdt); - println!("FDT Deep Debug:\n{}", deep_debug); - - // Verify deep format contains node information - assert!(deep_debug.contains("Fdt {")); - assert!(deep_debug.contains("nodes:")); - assert!(deep_debug.contains("[000]")); - - // Verify it contains specific node types - assert!( - deep_debug.contains("Clock") - || deep_debug.contains("InterruptController") - || deep_debug.contains("Memory") - || deep_debug.contains("Generic") - ); - } - - #[test] - fn test_fdt_deep_debug_with_simple_tree() { - let mut fdt = Fdt::new(); - - // Create a simple tree structure for testing - let mut soc = Node::new("soc"); - soc.set_property(Property::new("#address-cells", vec![0x1, 0x0, 0x0, 0x0])); - soc.set_property(Property::new("#size-cells", vec![0x1, 0x0, 0x0, 0x0])); - - let mut uart = Node::new("uart@9000000"); - uart.set_property(Property::new("compatible", b"arm,pl011\0".to_vec())); - uart.set_property(Property::new( - "reg", - vec![ - 0x00, 0x90, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, - ], - )); - uart.set_property(Property::new("status", b"okay\0".to_vec())); - - soc.add_child(uart); - fdt.root.add_child(soc); - - // Test deep debug output - let deep_debug = format!("{:#?}", fdt); - println!("Simple Tree Deep Debug:\n{}", deep_debug); - - // Verify output contains expected node information - assert!(deep_debug.contains("[000] : Generic")); - assert!(deep_debug.contains("[001] soc: Generic")); - assert!(deep_debug.contains("[002] uart@9000000: Generic")); - assert!(deep_debug.contains("#address-cells=1")); - assert!(deep_debug.contains("#size-cells=1")); - } -} diff --git a/fdt-edit/tests/edit.rs b/fdt-edit/tests/edit.rs deleted file mode 100644 index 159dc50..0000000 --- a/fdt-edit/tests/edit.rs +++ /dev/null @@ -1,171 +0,0 @@ -#![cfg(unix)] - -use dtb_file::*; -use fdt_edit::*; -use std::fs; -use std::process::Command; - -#[test] -fn test_parse_and_rebuild() { - // Parse original DTB - let raw_data = fdt_qemu(); - let fdt = Fdt::from_bytes(&raw_data).unwrap(); - let fdt_data = fdt.encode(); - - // Create temporary files - let temp_dir = std::env::temp_dir(); - let original_dtb_path = temp_dir.join("original.dtb"); - let rebuilt_dtb_path = temp_dir.join("rebuilt.dtb"); - let original_dts_path = temp_dir.join("original.dts"); - let rebuilt_dts_path = temp_dir.join("rebuilt.dts"); - - // Cleanup function - let cleanup = || { - let _ = fs::remove_file(&original_dtb_path); - let _ = fs::remove_file(&rebuilt_dtb_path); - let _ = fs::remove_file(&original_dts_path); - let _ = fs::remove_file(&rebuilt_dts_path); - }; - - // Ensure cleanup of temporary files - cleanup(); - - // Save original and rebuilt data to temporary files - fs::write(&original_dtb_path, &*raw_data).expect("Failed to write original DTB file"); - fs::write(&rebuilt_dtb_path, &fdt_data).expect("Failed to write rebuilt DTB file"); - - // Check if dtc command is available - let dtc_check = Command::new("dtc").arg("--version").output(); - - if dtc_check.is_err() { - cleanup(); - panic!("dtc command not available, please install device-tree-compiler"); - } - - // Use dtc to convert DTB files to DTS files - let original_output = Command::new("dtc") - .args([ - "-I", - "dtb", - "-O", - "dts", - "-o", - original_dts_path.to_str().unwrap(), - ]) - .arg(original_dtb_path.to_str().unwrap()) - .output() - .expect("Failed to execute dtc command (original file)"); - - if !original_output.status.success() { - cleanup(); - panic!( - "dtc conversion of original DTB failed: {}", - String::from_utf8_lossy(&original_output.stderr) - ); - } - - let rebuilt_output = Command::new("dtc") - .args([ - "-I", - "dtb", - "-O", - "dts", - "-o", - rebuilt_dts_path.to_str().unwrap(), - ]) - .arg(rebuilt_dtb_path.to_str().unwrap()) - .output() - .expect("Failed to execute dtc command (rebuilt file)"); - - if !rebuilt_output.status.success() { - cleanup(); - panic!( - "dtc conversion of rebuilt DTB failed: {}", - String::from_utf8_lossy(&rebuilt_output.stderr) - ); - } - - // Read generated DTS files and perform byte-by-byte comparison - let original_dts = - fs::read_to_string(&original_dts_path).expect("Failed to read original DTS file"); - let rebuilt_dts = - fs::read_to_string(&rebuilt_dts_path).expect("Failed to read rebuilt DTS file"); - - // Perform byte-by-byte comparison - if original_dts != rebuilt_dts { - println!("Original DTS file content:\n{}", original_dts); - println!("\nRebuilt DTS file content:\n{}", rebuilt_dts); - - // Find first differing position - let original_chars: Vec = original_dts.chars().collect(); - let rebuilt_chars: Vec = rebuilt_dts.chars().collect(); - - let min_len = original_chars.len().min(rebuilt_chars.len()); - let mut diff_pos = None; - - for i in 0..min_len { - if original_chars[i] != rebuilt_chars[i] { - diff_pos = Some(i); - break; - } - } - - match diff_pos { - Some(pos) => { - let context_start = pos.saturating_sub(50); - let context_end = (pos + 50).min(min_len); - - println!("\nDifference found at position: {}", pos); - println!( - "Original file segment: {}>>>DIFF<<<{}", - &original_dts[context_start..pos], - &original_dts[pos..context_end] - ); - println!( - "Rebuilt file segment: {}>>>DIFF<<<{}", - &rebuilt_dts[context_start..pos], - &rebuilt_dts[pos..context_end] - ); - } - None => { - if original_chars.len() != rebuilt_chars.len() { - println!( - "File length differs: original={}, rebuilt={}", - original_chars.len(), - rebuilt_chars.len() - ); - } - } - } - - cleanup(); - panic!("Original DTS and rebuilt DTS do not match exactly"); - } - - // Cleanup temporary files - cleanup(); - - println!("✅ Test passed: Original DTB and rebuilt DTB DTS representations match exactly"); -} - -// TODO: Need to implement Display trait for Fdt -// #[test] -// fn test_display_dts() { -// // Parse DTB -// let raw_data = fdt_qemu(); -// let fdt = Fdt::from_bytes(&raw_data).unwrap(); - -// // Use Display to output DTS -// let dts = format!("{}", fdt); - -// // Verify output format -// assert!(dts.starts_with("/dts-v1/;"), "DTS should start with /dts-v1/;"); -// assert!(dts.contains("/ {"), "DTS should contain root node"); -// assert!(dts.contains("};"), "DTS should contain node closing"); - -// // Verify it contains some common nodes -// assert!(dts.contains("compatible"), "DTS should contain compatible property"); - -// println!("✅ Display test passed"); -// println!("DTS output first 500 characters:\n{}", &dts[..dts.len().min(500)]); -// } diff --git a/fdt-edit/tests/encode.rs b/fdt-edit/tests/encode.rs new file mode 100644 index 0000000..6e71ea0 --- /dev/null +++ b/fdt-edit/tests/encode.rs @@ -0,0 +1,268 @@ +//! FDT 编码测试 + +use dtb_file::*; +use fdt_edit::*; + +/// 测试空 FDT 的编码 +#[test] +fn test_encode_empty_fdt() { + let fdt = Fdt::new(); + let encoded = fdt.encode(); + + // 编码后的数据不应为空 + assert!(!encoded.is_empty()); + + // 至少应该包含 header (40 bytes) + assert!(encoded.len() >= 40); + + // 应该能被成功解析 + let parsed = Fdt::from_bytes(&encoded); + assert!(parsed.is_ok()); +} + +/// 测试带有属性的 FDT 编码 +#[test] +fn test_encode_with_properties() { + let mut fdt = Fdt::new(); + + // 添加一些属性到根节点 + let root_id = fdt.root_id(); + let node = fdt.node_mut(root_id).unwrap(); + node.set_property(crate::Property::new( + "#address-cells", + vec![0x00, 0x00, 0x00, 0x02], + )); + node.set_property(crate::Property::new( + "#size-cells", + vec![0x00, 0x00, 0x00, 0x01], + )); + node.set_property(crate::Property::new("model", { + let mut v = b"Test Device".to_vec(); + v.push(0); + v + })); + + let encoded = fdt.encode(); + + // 解析并验证 + let parsed = Fdt::from_bytes(&encoded).unwrap(); + let root = parsed.get_by_path("/").unwrap(); + let node_ref = root.as_node(); + + // 验证属性 + assert_eq!(node_ref.address_cells(), Some(2)); + assert_eq!(node_ref.size_cells(), Some(1)); + assert_eq!( + node_ref.get_property("model").unwrap().as_str(), + Some("Test Device") + ); +} + +/// 测试带有子节点的 FDT 编码 +#[test] +fn test_encode_with_children() { + let mut fdt = Fdt::new(); + + // 添加子节点 + let root_id = fdt.root_id(); + let mut soc = crate::Node::new("soc"); + soc.set_property(crate::Property::new( + "#address-cells", + vec![0x00, 0x00, 0x00, 0x02], + )); + soc.set_property(crate::Property::new( + "#size-cells", + vec![0x00, 0x00, 0x00, 0x02], + )); + let soc_id = fdt.add_node(root_id, soc); + + let mut uart = crate::Node::new("uart@1000"); + uart.set_property(crate::Property::new("reg", { + let v = vec![0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10, 0x00]; + v + })); + uart.set_property(crate::Property::new("compatible", { + let mut v = b"test,uart".to_vec(); + v.push(0); + v + })); + fdt.add_node(soc_id, uart); + + let encoded = fdt.encode(); + + // 解析并验证 + let parsed = Fdt::from_bytes(&encoded).unwrap(); + let soc = parsed.get_by_path("/soc").unwrap(); + assert_eq!(soc.name(), "soc"); + + let uart = parsed.get_by_path("/soc/uart@1000").unwrap(); + assert_eq!(uart.name(), "uart@1000"); +} + +/// 测试 Round-trip: 解析 -> 编码 -> 解析 +#[test] +fn test_parse_and_encode() { + // 使用 Phytium DTB 进行测试 + let raw_data = fdt_phytium(); + let original = Fdt::from_bytes(&raw_data).unwrap(); + + // 编码 + let encoded = original.encode(); + + // 再次解析 + let reparsed = Fdt::from_bytes(&encoded).unwrap(); + + // 验证 boot_cpuid_phys 一致 + assert_eq!(original.boot_cpuid_phys, reparsed.boot_cpuid_phys); + + // 验证节点数量一致 + assert_eq!(original.node_count(), reparsed.node_count()); + + // 验证内存保留区一致 + assert_eq!( + original.memory_reservations.len(), + reparsed.memory_reservations.len() + ); + for (orig, rep) in original + .memory_reservations + .iter() + .zip(reparsed.memory_reservations.iter()) + { + assert_eq!(orig.address, rep.address); + assert_eq!(orig.size, rep.size); + } + + // 验证所有节点路径都能找到 + for id in original.iter_node_ids() { + let path = original.path_of(id); + let reparsed_id = reparsed.get_by_path_id(&path); + assert!( + reparsed_id.is_some(), + "path {} not found in reparsed FDT", + path + ); + } +} + +/// 测试使用 Raspberry Pi 4 DTB 的 Round-trip +#[test] +fn test_parse_and_encode_rpi() { + let raw_data = fdt_rpi_4b(); + let original = Fdt::from_bytes(&raw_data).unwrap(); + + let encoded = original.encode(); + let reparsed = Fdt::from_bytes(&encoded).unwrap(); + + assert_eq!(original.boot_cpuid_phys, reparsed.boot_cpuid_phys); + assert_eq!(original.node_count(), reparsed.node_count()); +} + +/// 测试带内存保留区的 FDT 编码 +#[test] +fn test_encode_with_memory_reservations() { + let mut fdt = Fdt::new(); + + // 添加内存保留区 + fdt.memory_reservations.push(fdt_raw::MemoryReservation { + address: 0x8000_0000, + size: 0x1000, + }); + fdt.memory_reservations.push(fdt_raw::MemoryReservation { + address: 0x9000_0000, + size: 0x2000, + }); + + let encoded = fdt.encode(); + let reparsed = Fdt::from_bytes(&encoded).unwrap(); + + // 验证内存保留区 + assert_eq!(reparsed.memory_reservations.len(), 2); + assert_eq!(reparsed.memory_reservations[0].address, 0x8000_0000); + assert_eq!(reparsed.memory_reservations[0].size, 0x1000); + assert_eq!(reparsed.memory_reservations[1].address, 0x9000_0000); + assert_eq!(reparsed.memory_reservations[1].size, 0x2000); +} + +/// 测试使用真实带保留区的 DTB +#[test] +fn test_encode_with_reserve_dtb() { + let raw_data = fdt_reserve(); + let original = Fdt::from_bytes(&raw_data).unwrap(); + + let encoded = original.encode(); + let reparsed = Fdt::from_bytes(&encoded).unwrap(); + + // 验证保留区被正确编码 + assert_eq!( + original.memory_reservations.len(), + reparsed.memory_reservations.len() + ); +} + +/// 测试节点属性完整性 +#[test] +fn test_encode_properties_integrity() { + let mut fdt = Fdt::new(); + + // 添加各种类型的属性 + let root_id = fdt.root_id(); + let node = fdt.node_mut(root_id).unwrap(); + + // u32 属性 + node.set_property(crate::Property::new( + "prop-u32", + 0x12345678u32.to_be_bytes().to_vec(), + )); + + // u64 属性 + node.set_property(crate::Property::new( + "prop-u64", + 0x1234567890ABCDEFu64.to_be_bytes().to_vec(), + )); + + // 字符串属性 + node.set_property(crate::Property::new("prop-string", { + let mut v = b"test string".to_vec(); + v.push(0); + v + })); + + // 字符串列表 + { + let v = b"first\0second\0third\0".to_vec(); + node.set_property(crate::Property::new("prop-string-list", v)); + } + + // reg 属性 + { + let v = vec![ + 0x00, 0x10, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x40, + 0x00, 0x00, + ]; + node.set_property(crate::Property::new("reg", v)); + } + + let encoded = fdt.encode(); + let reparsed = Fdt::from_bytes(&encoded).unwrap(); + + // 验证各种类型的属性 + let root = reparsed.get_by_path("/").unwrap(); + let node_ref = root.as_node(); + + // u32 + let prop_u32 = node_ref.get_property("prop-u32").unwrap(); + assert_eq!(prop_u32.get_u32(), Some(0x12345678)); + + // u64 + let prop_u64 = node_ref.get_property("prop-u64").unwrap(); + assert_eq!(prop_u64.get_u64(), Some(0x1234567890ABCDEF)); + + // string + let prop_string = node_ref.get_property("prop-string").unwrap(); + assert_eq!(prop_string.as_str(), Some("test string")); + + // string list + let prop_list = node_ref.get_property("prop-string-list").unwrap(); + let strings: Vec<&str> = prop_list.as_str_iter().collect(); + assert_eq!(strings, vec!["first", "second", "third"]); +} diff --git a/fdt-edit/tests/fdt.rs b/fdt-edit/tests/fdt.rs new file mode 100644 index 0000000..2c9658e --- /dev/null +++ b/fdt-edit/tests/fdt.rs @@ -0,0 +1,113 @@ +use dtb_file::*; +use fdt_edit::*; + +#[test] +fn test_iter_nodes() { + let raw_data = fdt_phytium(); + let fdt = Fdt::from_bytes(&raw_data).unwrap(); + let mut count = 0; + for view in fdt.all_nodes() { + println!("{:?} path={}", view.as_node(), view.path()); + count += 1; + } + assert!(count > 0, "should have at least one node"); + assert_eq!(count, fdt.node_count()); +} + +#[test] +fn test_node_classify() { + let raw_data = fdt_phytium(); + let fdt = Fdt::from_bytes(&raw_data).unwrap(); + + let mut memory_count = 0; + let mut intc_count = 0; + let mut generic_count = 0; + + for view in fdt.all_nodes() { + match view { + NodeType::Clock(clock) => { + println!( + "Clock node: {} #clock-cells={}", + clock.path(), + clock.clock_cells() + ); + } + NodeType::Pci(pci) => { + println!( + "PCI node: {} #interrupt-cells={}", + pci.path(), + pci.interrupt_cells() + ); + } + NodeType::Memory(mem) => { + memory_count += 1; + let regions = mem.regions(); + println!( + "Memory node: {} regions={} total_size={:#x}", + mem.path(), + regions.len(), + mem.total_size() + ); + } + NodeType::InterruptController(intc) => { + intc_count += 1; + println!( + "IntC node: {} #interrupt-cells={:?}", + intc.path(), + intc.interrupt_cells() + ); + } + NodeType::Generic(g) => { + generic_count += 1; + let _ = g.path(); + } + } + } + + println!( + "memory={}, intc={}, generic={}", + memory_count, intc_count, generic_count + ); + assert!(memory_count > 0, "phytium DTB should have memory nodes"); + assert!(intc_count > 0, "phytium DTB should have intc nodes"); + assert!(generic_count > 0, "phytium DTB should have generic nodes"); +} + +#[test] +fn test_path_lookup() { + let raw_data = fdt_phytium(); + let fdt = Fdt::from_bytes(&raw_data).unwrap(); + + // Root should always be found + let root = fdt.get_by_path("/").unwrap(); + assert_eq!(root.id(), fdt.root_id()); + + // Check path round-trip: for every node, path_of(id) should resolve back + for id in fdt.iter_node_ids() { + let path = fdt.path_of(id); + let found = fdt.get_by_path_id(&path); + assert_eq!( + found, + Some(id), + "path_of({}) = {:?} did not resolve back", + id, + path + ); + } + + // Verify get_by_path returns correct NodeType classification + for view in fdt.all_nodes() { + let path = view.path(); + let typed = fdt.get_by_path(&path).unwrap(); + assert_eq!(typed.id(), view.id()); + } +} + +#[test] +fn test_display_nodes() { + let raw_data = fdt_phytium(); + let fdt = Fdt::from_bytes(&raw_data).unwrap(); + for view in fdt.all_nodes() { + println!("{}", view); + } +} diff --git a/fdt-edit/tests/find2.rs b/fdt-edit/tests/find2.rs deleted file mode 100644 index 99933ad..0000000 --- a/fdt-edit/tests/find2.rs +++ /dev/null @@ -1,67 +0,0 @@ -#[cfg(test)] -mod tests { - use dtb_file::fdt_qemu; - use fdt_edit::*; - - #[test] - fn test_get_method() { - // Parse the original DTB - let raw_data = fdt_qemu(); - let fdt = Fdt::from_bytes(&raw_data).unwrap(); - - let node = fdt.get_by_path("/virtio_mmio@a002600"); - - println!("Found node: {:#?}", node.unwrap()); - } - - #[test] - fn test_find_method() { - // Parse the original DTB - let raw_data = fdt_qemu(); - let fdt = Fdt::from_bytes(&raw_data).unwrap(); - - let node = fdt.find_by_path("/virtio_mmio"); - - for n in node { - println!("Found node {n:#?}"); - } - - let count = fdt.find_by_path("/virtio_mmio").count(); - println!("Total found nodes: {}", count); - assert_eq!(count, 32); - } - - #[test] - fn test_all() { - // Parse the original DTB - let raw_data = fdt_qemu(); - let fdt = Fdt::from_bytes(&raw_data).unwrap(); - - for node in fdt.all_nodes() { - println!("Node: {:#?}", node); - println!(" {}", node.path()); - println!("-------------------------"); - } - - let count = fdt.all_nodes().count(); - println!("Total nodes: {}", count); - assert_eq!(count, 56); - } - - #[test] - fn test_all_mut() { - // Parse the original DTB - let raw_data = fdt_qemu(); - let mut fdt = Fdt::from_bytes(&raw_data).unwrap(); - - for node in fdt.all_nodes_mut() { - println!("Node: {:#?}", node); - println!(" {}", node.path()); - println!("-------------------------"); - } - - let count = fdt.all_nodes().count(); - println!("Total nodes: {}", count); - assert_eq!(count, 56); - } -} diff --git a/fdt-edit/tests/irq.rs b/fdt-edit/tests/irq.rs deleted file mode 100644 index 321dc1c..0000000 --- a/fdt-edit/tests/irq.rs +++ /dev/null @@ -1,172 +0,0 @@ -#![cfg(unix)] - -use dtb_file::*; -use fdt_edit::NodeKind; -use fdt_edit::*; - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_interrupt_controller_detection() { - // Test interrupt controller node detection using RPI 4B DTB - let raw_data = fdt_rpi_4b(); - let fdt = Fdt::from_bytes(&raw_data).unwrap(); - - // Traverse to find interrupt controller nodes - let mut irq_count = 0; - for node in fdt.all_nodes() { - if let NodeKind::InterruptController(ic) = node.as_ref() { - irq_count += 1; - println!( - "Interrupt controller: {} (#interrupt-cells={:?})", - ic.name(), - ic.interrupt_cells() - ); - } - } - println!("Found {} interrupt controllers", irq_count); - assert!( - irq_count > 0, - "Should find at least one interrupt controller" - ); - } - - #[test] - fn test_interrupt_controller_properties() { - let raw_data = fdt_rpi_4b(); - let fdt = Fdt::from_bytes(&raw_data).unwrap(); - - for node in fdt.all_nodes() { - if let NodeKind::InterruptController(ic) = node.as_ref() { - // Get #interrupt-cells - let cells = ic.interrupt_cells(); - println!("IRQ Controller: {} cells={:?}", ic.name(), cells); - - // Get #address-cells (if present) - let addr_cells = ic.interrupt_address_cells(); - if addr_cells.is_some() { - println!(" #address-cells: {:?}", addr_cells); - } - - // Verify is_interrupt_controller - assert!( - ic.is_interrupt_controller(), - "Should be marked as interrupt controller" - ); - - // Get compatible list - let compat = ic.compatibles(); - if !compat.is_empty() { - println!(" compatible: {:?}", compat); - } - } - } - } - - #[test] - fn test_interrupt_controller_by_name() { - let raw_data = fdt_rpi_4b(); - let fdt = Fdt::from_bytes(&raw_data).unwrap(); - - // Find GIC (ARM Generic Interrupt Controller) - let mut found_gic = false; - for node in fdt.all_nodes() { - if let NodeKind::InterruptController(ic) = node.as_ref() { - let compat = ic.compatibles(); - if compat.iter().any(|c| c.contains("gic")) { - found_gic = true; - println!("Found GIC: {}", ic.name()); - - // GIC typically has 3 interrupt-cells - let cells = ic.interrupt_cells(); - println!(" #interrupt-cells: {:?}", cells); - } - } - } - // Note: Not all DTBs have GIC, this is just an example - if found_gic { - println!("GIC found in this DTB"); - } - } - - #[test] - fn test_interrupt_controller_with_phytium() { - // Phytium DTB should have interrupt controllers - let raw_data = fdt_phytium(); - let fdt = Fdt::from_bytes(&raw_data).unwrap(); - - let mut controllers = Vec::new(); - for node in fdt.all_nodes() { - if let NodeKind::InterruptController(ic) = node.as_ref() { - controllers.push(( - ic.name().to_string(), - ic.interrupt_cells(), - ic.compatibles().join(", "), - )); - } - } - - println!("Interrupt controllers in Phytium DTB:"); - for (name, cells, compat) in &controllers { - println!( - " {} (#interrupt-cells={:?}, compatible={})", - name, cells, compat - ); - } - - assert!( - !controllers.is_empty(), - "Phytium should have at least one interrupt controller" - ); - } - - #[test] - fn test_interrupt_controller_detection_logic() { - // Test whether nodes are correctly identified as interrupt controllers - let raw_data = fdt_qemu(); - let fdt = Fdt::from_bytes(&raw_data).unwrap(); - - for node in fdt.all_nodes() { - let name = node.name(); - let is_ic = matches!(node.as_ref(), NodeKind::InterruptController(_)); - - // If node name starts with interrupt-controller, it should be detected - if name.starts_with("interrupt-controller") && !is_ic { - println!( - "Warning: {} might be an interrupt controller but not detected", - name - ); - } - - // If node has interrupt-controller property, it should be detected - if node.find_property("interrupt-controller").is_some() && !is_ic { - println!( - "Warning: {} has interrupt-controller property but not detected", - name - ); - } - } - } - - #[test] - fn test_interrupt_cells_values() { - let raw_data = fdt_rpi_4b(); - let fdt = Fdt::from_bytes(&raw_data).unwrap(); - - for node in fdt.all_nodes() { - if let NodeKind::InterruptController(ic) = node.as_ref() - && let Some(cells) = ic.interrupt_cells() - { - // Common interrupt-cells values: 1, 2, 3 - assert!( - (1..=4).contains(&cells), - "Unusual #interrupt-cells value: {} for {}", - cells, - ic.name() - ); - } - } - } -} diff --git a/fdt-edit/tests/memory.rs b/fdt-edit/tests/memory.rs deleted file mode 100644 index ad0623c..0000000 --- a/fdt-edit/tests/memory.rs +++ /dev/null @@ -1,85 +0,0 @@ -#![cfg(unix)] - -use dtb_file::*; -use fdt_edit::NodeKind; -use fdt_edit::*; - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_memory_node_detection() { - // Test memory node detection using phytium DTB - let raw_data = fdt_phytium(); - let fdt = Fdt::from_bytes(&raw_data).unwrap(); - - // Traverse to find memory nodes - let mut found_memory = false; - for node in fdt.all_nodes() { - if let NodeKind::Memory(mem) = node.as_ref() { - found_memory = true; - println!("Memory node: {}", mem.name()); - } - } - assert!(found_memory, "Should find at least one memory node"); - } - - #[test] - fn test_memory_regions() { - let raw_data = fdt_phytium(); - let fdt = Fdt::from_bytes(&raw_data).unwrap(); - - // Find memory nodes and get region information - for node in fdt.all_nodes() { - if let NodeKind::Memory(mem) = node.as_ref() { - let regions = mem.regions(); - // Memory node should have at least one region - if !regions.is_empty() { - for region in regions { - println!( - "Memory region: address=0x{:x}, size=0x{:x}", - region.address, region.size - ); - } - } - } - } - } - - #[test] - fn test_memory_node_properties() { - let raw_data = fdt_phytium(); - let fdt = Fdt::from_bytes(&raw_data).unwrap(); - - for node in fdt.all_nodes() { - if let NodeKind::Memory(mem) = node.as_ref() { - // Memory node should have device_type property - let dt = mem.device_type(); - if let Some(device_type) = dt { - assert_eq!(device_type, "memory", "device_type should be 'memory'"); - } - - // Get node name - let name = mem.name(); - assert!( - name.starts_with("memory"), - "Memory node name should start with 'memory'" - ); - } - } - } - - #[test] - fn test_create_memory_node() { - // Manually create a memory node - let mem = NodeMemory::new("memory@80000000"); - assert_eq!(mem.name(), "memory@80000000"); - - // Verify initial state - assert!( - mem.regions().is_empty(), - "New memory node should have no regions" - ); - } -} diff --git a/fdt-edit/tests/pci.rs b/fdt-edit/tests/pci.rs index e423990..cc0d573 100644 --- a/fdt-edit/tests/pci.rs +++ b/fdt-edit/tests/pci.rs @@ -1,182 +1,193 @@ -#[cfg(test)] -mod tests { - use std::sync::Once; - - use dtb_file::{fdt_phytium, fdt_qemu}; - use fdt_edit::*; - - fn init_logging() { - static INIT: Once = Once::new(); - INIT.call_once(|| { - let _ = env_logger::builder() - .is_test(true) - .filter_level(log::LevelFilter::Trace) - .try_init(); - }); +//! PCI node view tests. + +use std::sync::Once; + +use dtb_file::*; +use fdt_edit::{Fdt, NodeType, PciRange, PciSpace}; + +fn init_logging() { + static INIT: Once = Once::new(); + INIT.call_once(|| { + let _ = env_logger::builder() + .is_test(true) + .filter_level(log::LevelFilter::Trace) + .try_init(); + }); +} + +#[test] +fn test_pci_node_detection() { + let raw_data = fdt_phytium(); + let fdt = Fdt::from_bytes(&raw_data).unwrap(); + + let mut pci_count = 0; + for node in fdt.all_nodes() { + if let NodeType::Pci(pci) = node { + pci_count += 1; + println!( + "PCI node: {} #interrupt-cells={}", + pci.path(), + pci.interrupt_cells() + ); + } } - #[test] - fn test_pci_node_detection() { - let raw_data = fdt_qemu(); - let fdt = Fdt::from_bytes(&raw_data).unwrap(); - - // Try to find PCI nodes - let mut pci_nodes_found = 0; - for node in fdt.all_nodes() { - { - if let NodeRef::Pci(pci) = node { - pci_nodes_found += 1; - println!("Found PCI node: {}", pci.name()); + println!("Total PCI nodes: {}", pci_count); + // 飞腾 DTB 应该有 PCI/PCIe 节点 + assert!(pci_count > 0, "phytium DTB should have PCI nodes"); +} + +#[test] +fn test_pci_ranges() { + let raw_data = fdt_phytium(); + let fdt = Fdt::from_bytes(&raw_data).unwrap(); + + for node in fdt.all_nodes() { + if let NodeType::Pci(pci) = node { + if let Some(ranges) = pci.ranges() { + println!("PCI {} has {} ranges:", pci.path(), ranges.len()); + + for range in &ranges { + let space_name = match range.space { + PciSpace::IO => "IO", + PciSpace::Memory32 => "Mem32", + PciSpace::Memory64 => "Mem64", + }; + + println!( + " {}: bus={:#x} cpu={:#x} size={:#x} prefetch={}", + space_name, + range.bus_address, + range.cpu_address, + range.size, + range.prefetchable + ); } + + assert!(!ranges.is_empty(), "PCI node should have ranges"); } } - - // We should find at least one PCI node in the qemu PCI test file - assert!(pci_nodes_found > 0, "Should find at least one PCI node"); } +} - #[test] - fn test_bus_range() { - let raw_data = fdt_qemu(); - let fdt = Fdt::from_bytes(&raw_data).unwrap(); - - for node in fdt.all_nodes() { - { - if let NodeRef::Pci(pci) = node - && let Some(range) = pci.bus_range() - { - println!("Found bus-range: {range:?}"); - assert!(range.start <= range.end, "Bus range start should be <= end"); - return; // Test passed - } +#[test] +fn test_pci_bus_range() { + let raw_data = fdt_phytium(); + let fdt = Fdt::from_bytes(&raw_data).unwrap(); + + for node in fdt.all_nodes() { + if let NodeType::Pci(pci) = node { + if let Some(bus_range) = pci.bus_range() { + println!( + "PCI {} bus-range: {}..{}", + pci.path(), + bus_range.start, + bus_range.end + ); } } - - // println!("No bus-range found in any PCI node"); } +} - #[test] - fn test_pci_properties() { - let raw_data = fdt_qemu(); - let fdt = Fdt::from_bytes(&raw_data).unwrap(); - - for node in fdt.all_nodes() { - { - if let NodeRef::Pci(pci) = node { - // Test address cells - assert_eq!( - pci.address_cells(), - Some(3), - "PCI should use 3 address cells" - ); - - // Test interrupt cells - assert_eq!(pci.interrupt_cells(), 1, "PCI should use 1 interrupt cell"); - - // Test device type - if let Some(device_type) = pci.device_type() { - assert!(!device_type.is_empty()); - } - - // Test compatibles - let compatibles = pci.compatibles().collect::>(); - if !compatibles.is_empty() { - println!("Compatibles: {:?}", compatibles); - } +#[test] +fn test_pci_interrupt_map_mask() { + let raw_data = fdt_phytium(); + let fdt = Fdt::from_bytes(&raw_data).unwrap(); - return; // Test passed for first PCI node found - } + for node in fdt.all_nodes() { + if let NodeType::Pci(pci) = node { + if let Some(mask) = pci.interrupt_map_mask() { + println!("PCI {} interrupt-map-mask: {:?}", pci.path(), mask); + assert!(!mask.is_empty(), "interrupt-map-mask should not be empty"); } } - - panic!("No PCI nodes found for property testing"); } +} - #[test] - fn test_pci2() { - let raw = fdt_phytium(); - let fdt = Fdt::from_bytes(&raw).unwrap(); - let node = fdt - .find_compatible(&["pci-host-ecam-generic"]) - .into_iter() - .next() - .unwrap(); - - let NodeRef::Pci(pci) = node else { - panic!("Not a PCI node"); - }; - - let want = [ - PciRange { - space: PciSpace::IO, - bus_address: 0x0, - cpu_address: 0x50000000, - size: 0xf00000, - prefetchable: false, - }, - PciRange { - space: PciSpace::Memory32, - bus_address: 0x58000000, - cpu_address: 0x58000000, - size: 0x28000000, - prefetchable: false, - }, - PciRange { - space: PciSpace::Memory64, - bus_address: 0x1000000000, - cpu_address: 0x1000000000, - size: 0x1000000000, - prefetchable: false, - }, - ]; - - for (i, range) in pci.ranges().unwrap().iter().enumerate() { - assert_eq!(*range, want[i]); - println!("{range:#x?}"); - } +#[test] +fn test_pci2() { + let raw = fdt_phytium(); + let fdt = Fdt::from_bytes(&raw).unwrap(); + let node = fdt + .find_compatible(&["pci-host-ecam-generic"]) + .into_iter() + .next() + .unwrap(); + + let NodeType::Pci(pci) = node else { + panic!("Not a PCI node"); + }; + + let want = [ + PciRange { + space: PciSpace::IO, + bus_address: 0x0, + cpu_address: 0x50000000, + size: 0xf00000, + prefetchable: false, + }, + PciRange { + space: PciSpace::Memory32, + bus_address: 0x58000000, + cpu_address: 0x58000000, + size: 0x28000000, + prefetchable: false, + }, + PciRange { + space: PciSpace::Memory64, + bus_address: 0x1000000000, + cpu_address: 0x1000000000, + size: 0x1000000000, + prefetchable: false, + }, + ]; + + for (i, range) in pci.ranges().unwrap().iter().enumerate() { + assert_eq!(*range, want[i]); + println!("{range:#x?}"); } +} - #[test] - fn test_pci_irq_map() { - let raw = fdt_phytium(); - let fdt = Fdt::from_bytes(&raw).unwrap(); - let node_ref = fdt - .find_compatible(&["pci-host-ecam-generic"]) - .into_iter() - .next() - .unwrap(); +#[test] +fn test_pci_irq_map() { + let raw = fdt_phytium(); + let fdt = Fdt::from_bytes(&raw).unwrap(); + let node_ref = fdt + .find_compatible(&["pci-host-ecam-generic"]) + .into_iter() + .next() + .unwrap(); - let NodeRef::Pci(pci) = node_ref else { - panic!("Not a PCI node"); - }; + let NodeType::Pci(pci) = node_ref else { + panic!("Not a PCI node"); + }; - let irq = pci.child_interrupts(0, 0, 0, 4).unwrap(); + let irq = pci.child_interrupts(0, 0, 0, 4).unwrap(); - assert!(!irq.irqs.is_empty()); - } + assert!(!irq.irqs.is_empty()); +} - #[test] - fn test_pci_irq_map2() { - init_logging(); +#[test] +fn test_pci_irq_map2() { + init_logging(); - let raw = fdt_qemu(); - let fdt = Fdt::from_bytes(&raw).unwrap(); - let node_ref = fdt - .find_compatible(&["pci-host-ecam-generic"]) - .into_iter() - .next() - .unwrap(); + let raw = fdt_qemu(); + let fdt = Fdt::from_bytes(&raw).unwrap(); + let node_ref = fdt + .find_compatible(&["pci-host-ecam-generic"]) + .into_iter() + .next() + .unwrap(); - let NodeRef::Pci(pci) = node_ref else { - panic!("Not a PCI node"); - }; + let NodeType::Pci(pci) = node_ref else { + panic!("Not a PCI node"); + }; - let irq = pci.child_interrupts(0, 2, 0, 1).unwrap(); + let irq = pci.child_interrupts(0, 2, 0, 1).unwrap(); - let want = [0, 5, 4]; + let want = [0, 5, 4]; - for (got, want) in irq.irqs.iter().zip(want.iter()) { - assert_eq!(*got, *want); - } + for (got, want) in irq.irqs.iter().zip(want.iter()) { + assert_eq!(*got, *want); } } diff --git a/fdt-edit/tests/range.rs b/fdt-edit/tests/range.rs index 25b17f8..9c10277 100644 --- a/fdt-edit/tests/range.rs +++ b/fdt-edit/tests/range.rs @@ -1,145 +1,85 @@ -#[cfg(test)] -mod tests { - use std::sync::Once; - - use dtb_file::fdt_rpi_4b; - use fdt_edit::*; - use log::*; - - fn init_logging() { - static INIT: Once = Once::new(); - INIT.call_once(|| { - let _ = env_logger::builder() - .is_test(true) - .filter_level(log::LevelFilter::Trace) - .try_init(); - }); - } - - #[test] - fn test_reg() { - init_logging(); - let raw = fdt_rpi_4b(); - let fdt = Fdt::from_bytes(&raw).unwrap(); - - let node = fdt.get_by_path("/soc/serial@7e215040").unwrap(); - - let reg = node.regs().unwrap()[0]; - - info!("reg: {:#x?}", reg); - - assert_eq!( - reg.address, 0xfe215040, - "want 0xfe215040, got {:#x}", - reg.address - ); - - assert_eq!( - reg.child_bus_address, 0x7e215040, - "want 0x7e215040, got {:#x}", - reg.child_bus_address - ); - assert_eq!( - reg.size, - Some(0x40), - "want 0x40, got {:#x}", - reg.size.unwrap() - ); - } +use dtb_file::*; +use fdt_edit::*; + +#[test] +fn test_reg_address_translation() { + let raw = fdt_rpi_4b(); + let fdt = Fdt::from_bytes(&raw).unwrap(); + + // 测试 /soc/serial@7e215040 节点 + // bus address: 0x7e215040, CPU address: 0xfe215040 + let node = fdt.get_by_path("/soc/serial@7e215040").unwrap(); + let regs = node.regs(); + + assert!(!regs.is_empty(), "should have at least one reg entry"); + + let reg = ®s[0]; + assert_eq!(reg.address, 0xfe215040, "CPU address should be 0xfe215040"); + assert_eq!( + reg.child_bus_address, 0x7e215040, + "bus address should be 0x7e215040" + ); + assert_eq!(reg.size, Some(0x40), "size should be 0x40"); +} - #[test] - fn test_set_regs_with_ranges_conversion() { - init_logging(); - let raw = fdt_rpi_4b(); - let mut fdt = Fdt::from_bytes(&raw).unwrap(); +#[test] +fn test_set_regs_with_ranges_conversion() { + let raw = fdt_rpi_4b(); + let mut fdt = Fdt::from_bytes(&raw).unwrap(); - // 获取可变节点引用 + // 使用 CPU 地址设置 reg + let new_cpu_address = 0xfe215080u64; + let new_size = 0x80u64; + { let mut node = fdt.get_by_path_mut("/soc/serial@7e215040").unwrap(); - - // 获取原始 reg 信息 - let original_regs = node.regs().unwrap(); - let original_reg = original_regs[0]; - info!("Original reg: {:#x?}", original_reg); - - // Set regs using CPU address (0xfe215040 is CPU address) - // set_regs should convert it to bus address (0x7e215040) when storing - let new_cpu_address = 0xfe215080u64; // New CPU address - let new_size = 0x80u64; node.set_regs(&[RegInfo { address: new_cpu_address, size: Some(new_size), }]); - - // Re-read to verify - let updated_regs = node.regs().unwrap(); - let updated_reg = updated_regs[0]; - info!("Updated reg: {:#x?}", updated_reg); - - // Verify: CPU address read back should be what we set - assert_eq!( - updated_reg.address, new_cpu_address, - "CPU address should be {:#x}, got {:#x}", - new_cpu_address, updated_reg.address - ); - - // Verify: bus address should be the converted value - // 0xfe215080 - 0xfe000000 + 0x7e000000 = 0x7e215080 - let expected_bus_address = 0x7e215080u64; - assert_eq!( - updated_reg.child_bus_address, expected_bus_address, - "Bus address should be {:#x}, got {:#x}", - expected_bus_address, updated_reg.child_bus_address - ); - - assert_eq!( - updated_reg.size, - Some(new_size), - "Size should be {:#x}, got {:?}", - new_size, - updated_reg.size - ); } - #[test] - fn test_set_regs_roundtrip() { - init_logging(); - let raw = fdt_rpi_4b(); - let mut fdt = Fdt::from_bytes(&raw).unwrap(); + // 重新读取验证 + let node = fdt.get_by_path("/soc/serial@7e215040").unwrap(); + let updated_regs = node.regs(); + let updated_reg = &updated_regs[0]; - // Get original reg information - let original_reg = { - let node = fdt.get_by_path("/soc/serial@7e215040").unwrap(); - node.regs().unwrap()[0] - }; - info!("Original reg: {:#x?}", original_reg); + // 验证:读取回来的 CPU 地址应该是我们设置的值 + assert_eq!(updated_reg.address, new_cpu_address); + // 验证:bus 地址应该是转换后的值 + assert_eq!(updated_reg.child_bus_address, 0x7e215080); + assert_eq!(updated_reg.size, Some(new_size)); +} - // Set regs again using the same CPU address - { - let mut node = fdt.get_by_path_mut("/soc/serial@7e215040").unwrap(); - node.set_regs(&[RegInfo { - address: original_reg.address, // Use CPU address - size: original_reg.size, - }]); - } +#[test] +fn test_set_regs_roundtrip() { + let raw = fdt_rpi_4b(); + let mut fdt = Fdt::from_bytes(&raw).unwrap(); - // Verify roundtrip: reading back should be same as original - let roundtrip_reg = { - let node = fdt.get_by_path("/soc/serial@7e215040").unwrap(); - node.regs().unwrap()[0] - }; - info!("Roundtrip reg: {:#x?}", roundtrip_reg); + // 获取原始 reg 信息 + let original_reg = { + let node = fdt.get_by_path("/soc/serial@7e215040").unwrap(); + node.regs()[0] + }; - assert_eq!( - roundtrip_reg.address, original_reg.address, - "Roundtrip CPU address mismatch" - ); - assert_eq!( - roundtrip_reg.child_bus_address, original_reg.child_bus_address, - "Roundtrip bus address mismatch" - ); - assert_eq!( - roundtrip_reg.size, original_reg.size, - "Roundtrip size mismatch" - ); + // 使用相同的 CPU 地址重新设置 reg + { + let mut node = fdt.get_by_path_mut("/soc/serial@7e215040").unwrap(); + node.set_regs(&[RegInfo { + address: original_reg.address, + size: original_reg.size, + }]); } + + // 验证 roundtrip + let roundtrip_reg = { + let node = fdt.get_by_path("/soc/serial@7e215040").unwrap(); + node.regs()[0] + }; + + assert_eq!(roundtrip_reg.address, original_reg.address); + assert_eq!( + roundtrip_reg.child_bus_address, + original_reg.child_bus_address + ); + assert_eq!(roundtrip_reg.size, original_reg.size); } diff --git a/fdt-edit/tests/rebuild.rs b/fdt-edit/tests/rebuild.rs new file mode 100644 index 0000000..e3e8cae --- /dev/null +++ b/fdt-edit/tests/rebuild.rs @@ -0,0 +1,127 @@ +//! FDT Rebuild 测试 +//! +//! 使用 diff 命令验证 parse 和 encode 的正确性: +//! 1. 从 DTB 解析为 Fdt 对象 +//! 2. 从 Fdt 对象编码回 DTB +//! 3. 使用 dtc 将两个 DTB 都反编译为 DTS +//! 4. 使用 diff 对比两个 DTS,确保语义一致 + +#[cfg(target_os = "linux")] +use dtb_file::*; +#[cfg(target_os = "linux")] +use fdt_edit::*; +#[cfg(target_os = "linux")] +use std::fs; +#[cfg(target_os = "linux")] +use std::process::Command; + +/// 测试用例 +struct DtbTestCase { + name: &'static str, + loader: fn() -> Align4Vec, +} + +/// 所有测试用例 +const TEST_CASES: &[DtbTestCase] = &[ + DtbTestCase { + name: "qemu", + loader: || fdt_qemu(), + }, + DtbTestCase { + name: "pi_4b", + loader: || fdt_rpi_4b(), + }, + DtbTestCase { + name: "phytium", + loader: || fdt_phytium(), + }, + DtbTestCase { + name: "rk3568", + loader: || fdt_3568(), + }, + DtbTestCase { + name: "reserve", + loader: || fdt_reserve(), + }, +]; + +/// 主测试函数:遍历所有测试用例 +#[test] +fn test_rebuild_all() { + for case in TEST_CASES { + test_rebuild_single(case); + } +} + +/// 运行单个 rebuild 测试 +fn test_rebuild_single(case: &DtbTestCase) { + println!("Testing rebuild: {}", case.name); + + // 1. 获取原始 DTB 数据 + let raw_data = (case.loader)(); + let original = + Fdt::from_bytes(&raw_data).unwrap_or_else(|_| panic!("Failed to parse {}", case.name)); + + // 2. 编码 + let encoded = original.encode(); + + // 3. 保存到 /tmp + let tmp_dir = "/tmp/fdt_rebuild_test"; + fs::create_dir_all(tmp_dir).unwrap_or_else(|_| panic!("Failed to create tmp dir")); + + let orig_dtb_path = format!("{}/{}.orig.dtb", tmp_dir, case.name); + let enc_dtb_path = format!("{}/{}.enc.dtb", tmp_dir, case.name); + let orig_dts_path = format!("{}/{}.orig.dts", tmp_dir, case.name); + let enc_dts_path = format!("{}/{}.enc.dts", tmp_dir, case.name); + + fs::write(&orig_dtb_path, &raw_data[..]) + .unwrap_or_else(|_| panic!("Failed to write {}", orig_dtb_path)); + fs::write(&enc_dtb_path, &encoded[..]) + .unwrap_or_else(|_| panic!("Failed to write {}", enc_dtb_path)); + + // 4. 使用 dtc 反编译为 DTS + let dtc_status_orig = Command::new("dtc") + .arg("-I") + .arg("dtb") + .arg("-O") + .arg("dts") + .arg("-o") + .arg(&orig_dts_path) + .arg(&orig_dtb_path) + .status() + .unwrap_or_else(|_| panic!("Failed to run dtc on original DTB")); + + assert!(dtc_status_orig.success(), "dtc failed on original DTB"); + + let dtc_status_enc = Command::new("dtc") + .arg("-I") + .arg("dtb") + .arg("-O") + .arg("dts") + .arg("-o") + .arg(&enc_dts_path) + .arg(&enc_dtb_path) + .status() + .unwrap_or_else(|_| panic!("Failed to run dtc on encoded DTB")); + + assert!(dtc_status_enc.success(), "dtc failed on encoded DTB"); + + // 5. 使用 diff 对比两个 DTS + let diff_status = Command::new("diff") + .arg("-u") + .arg(&orig_dts_path) + .arg(&enc_dts_path) + .status() + .unwrap_or_else(|_| panic!("Failed to run diff")); + + // 6. 验证:两个 DTS 应该语义一致 + assert!( + diff_status.success(), + "DTS files differ for {}: run 'diff {} {}' to see details", + case.name, + orig_dts_path, + enc_dts_path + ); + + println!("Rebuild test PASSED: {}", case.name); +} diff --git a/fdt-edit/tests/remove_node.rs b/fdt-edit/tests/remove_node.rs deleted file mode 100644 index 5835ca2..0000000 --- a/fdt-edit/tests/remove_node.rs +++ /dev/null @@ -1,208 +0,0 @@ -#[cfg(test)] -mod tests { - use std::sync::Once; - - use dtb_file::fdt_qemu; - use fdt_edit::*; - - fn init_logging() { - static INIT: Once = Once::new(); - INIT.call_once(|| { - let _ = env_logger::builder() - .is_test(true) - .filter_level(log::LevelFilter::Trace) - .try_init(); - }); - } - - #[test] - fn test_remove_node_exact_path() { - init_logging(); - // Parse original DTB - let raw_data = fdt_qemu(); - let mut fdt = Fdt::from_bytes(&raw_data).unwrap(); - - // Find an existing node path to remove - let node = fdt.get_by_path("/psci"); - assert!(node.is_some(), "psci node should exist"); - - // Remove node - let removed = fdt.remove_node("/psci"); - assert!(removed.is_ok(), "Removal should succeed"); - assert!(removed.unwrap().is_some(), "Should return the removed node"); - - // Verify node has been removed - let node_after = fdt.get_by_path("/psci"); - assert!(node_after.is_none(), "psci node should have been removed"); - } - - #[test] - fn test_remove_node_exact_path_parts() { - init_logging(); - // Parse original DTB - let raw_data = fdt_qemu(); - let mut fdt = Fdt::from_bytes(&raw_data).unwrap(); - - let memory = fdt.find_by_path("/memory").next().unwrap(); - fdt.remove_node(&memory.path()).unwrap(); - - let cpus = fdt.find_by_path("/cpus/cpu").collect::>(); - let path = cpus[0].path(); - println!("Removing node at path: {}", path); - // drop(node); - - // Remove node - let removed = fdt.remove_node(&path); - assert!(removed.is_ok(), "Removal should succeed"); - assert!(removed.unwrap().is_some(), "Should return the removed node"); - - // Verify node has been removed - let node_after = fdt.get_by_path("/cpus/cpu@0"); - assert!(node_after.is_none(), "cpu node should have been removed"); - - let raw = fdt.encode(); - let fdt2 = Fdt::from_bytes(&raw).unwrap(); - let node_after_reload = fdt2.get_by_path("/cpus/cpu@0"); - assert!( - node_after_reload.is_none(), - "cpu node should have been removed after reload" - ); - } - - #[test] - fn test_remove_nested_node() { - // Use manually created tree to test nested removal - let mut fdt = Fdt::new(); - - // Create nested nodes: /soc/i2c@0/eeprom@50 - let mut soc = Node::new("soc"); - let mut i2c = Node::new("i2c@0"); - let eeprom = Node::new("eeprom@50"); - i2c.add_child(eeprom); - soc.add_child(i2c); - fdt.root.add_child(soc); - - // Verify node exists - assert!(fdt.get_by_path("/soc/i2c@0/eeprom@50").is_some()); - - // Remove nested node - let removed = fdt.remove_node("/soc/i2c@0/eeprom@50"); - assert!(removed.is_ok()); - assert!(removed.unwrap().is_some()); - - // Verify node has been removed - assert!(fdt.get_by_path("/soc/i2c@0/eeprom@50").is_none()); - - // Parent nodes should still exist - assert!(fdt.get_by_path("/soc/i2c@0").is_some()); - assert!(fdt.get_by_path("/soc").is_some()); - } - - #[test] - fn test_remove_nonexistent_node() { - let mut fdt = Fdt::new(); - - // Removing non-existent node should return NotFound - let result = fdt.remove_node("/nonexistent"); - assert!(result.is_err()); - } - - #[test] - fn test_remove_direct_child() { - let mut fdt = Fdt::new(); - - // Add direct child node - fdt.root.add_child(Node::new("memory@0")); - - // Verify it exists - assert!(fdt.get_by_path("/memory@0").is_some()); - - // Remove direct child node - let removed = fdt.remove_node("/memory@0"); - assert!(removed.is_ok()); - assert!(removed.unwrap().is_some()); - - // Verify it has been removed - assert!(fdt.get_by_path("/memory@0").is_none()); - } - - #[test] - fn test_remove_empty_path() { - let mut fdt = Fdt::new(); - - // Empty path should return error - let result = fdt.remove_node(""); - assert!(result.is_err()); - - let result = fdt.remove_node("/"); - assert!(result.is_err()); - } - - #[test] - fn test_node_remove_by_path() { - // Test Node's remove_by_path method directly - let mut root = Node::new(""); - - // Create structure: /a/b/c - let mut a = Node::new("a"); - let mut b = Node::new("b"); - let c = Node::new("c"); - b.add_child(c); - a.add_child(b); - root.add_child(a); - - // Verify c exists - assert!(root.get_child("a").is_some()); - - // Remove c - let removed = root.remove_by_path("a/b/c"); - assert!(removed.is_ok()); - assert!(removed.unwrap().is_some()); - - // Remove b - let removed = root.remove_by_path("a/b"); - assert!(removed.is_ok()); - assert!(removed.unwrap().is_some()); - - // Remove a - let removed = root.remove_by_path("a"); - assert!(removed.is_ok()); - assert!(removed.unwrap().is_some()); - - // All nodes have been removed - assert!(root.get_child("a").is_none()); - } - - #[test] - fn test_remove_with_leading_slash() { - let mut fdt = Fdt::new(); - let node = fdt.root_mut().add_child(Node::new("test")); - assert_eq!(&node.path(), "/test"); - println!("Node:\n {:?}", node); - - // Both paths with and without leading slash should work - let result = fdt.remove_node("/test"); - assert!(result.is_ok()); - - assert!(fdt.get_by_path("/test").is_none()); - } - - #[test] - fn test_remove_node_preserves_siblings() { - let mut fdt = Fdt::new(); - - // Add multiple sibling nodes - fdt.root.add_child(Node::new("node1")); - fdt.root.add_child(Node::new("node2")); - fdt.root.add_child(Node::new("node3")); - - // Remove middle node - let removed = fdt.remove_node("/node2"); - assert!(removed.is_ok()); - - // Verify other nodes still exist - assert!(fdt.get_by_path("/node1").is_some()); - assert!(fdt.get_by_path("/node2").is_none()); - assert!(fdt.get_by_path("/node3").is_some()); - } -} diff --git a/fdt-parser/.gitignore b/fdt-parser/.gitignore deleted file mode 100644 index ac55c41..0000000 --- a/fdt-parser/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -/target -/*.log -/.idea -/.project.toml -/.vscode -/Cargo.lock \ No newline at end of file diff --git a/fdt-parser/Cargo.toml b/fdt-parser/Cargo.toml deleted file mode 100644 index c25a879..0000000 --- a/fdt-parser/Cargo.toml +++ /dev/null @@ -1,23 +0,0 @@ -[package] -authors = ["周睿 "] -categories = ["embedded", "no-std"] -description = "A crate for parsing FDT" -documentation = "https://docs.rs/fdt-parser" -edition = "2021" -keywords = ["devicetree", "fdt", "dt", "dtb"] -license = "MPL-2.0" -name = "fdt-parser" -repository = "https://github.com/drivercraft/fdt-parser" -version = "0.5.2" - -[lib] -doctest = false - -[dependencies] -thiserror = { version = "2", default-features = false } -heapless = "0.9" -log = { version = "0.4", default-features = false } - -[dev-dependencies] -dtb-file = { path = "../dtb-file" } -env_logger = "0.11" diff --git a/fdt-parser/LICENSE b/fdt-parser/LICENSE deleted file mode 100644 index 02ff04d..0000000 --- a/fdt-parser/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2024 Quancheng Laboratory Innovation Center - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/fdt-parser/README.md b/fdt-parser/README.md deleted file mode 100644 index 0872dac..0000000 --- a/fdt-parser/README.md +++ /dev/null @@ -1,162 +0,0 @@ -# FDT Parser - -[![Build & Check CI](https://github.com/drivercraft/fdt-parser/actions/workflows/ci.yml/badge.svg)](https://github.com/drivercraft/fdt-parser/actions/workflows/ci.yml) -[![Latest version](https://img.shields.io/crates/v/fdt-parser.svg)](https://crates.io/crates/fdt-parser) -[![Documentation](https://docs.rs/fdt-parser/badge.svg)](https://docs.rs/fdt-parser) -![License](https://img.shields.io/crates/l/fdt-parser.svg) - -A pure Rust, `#![no_std]` Flattened Device Tree (FDT) parser library based on [devicetree-specification-v0.4](https://github.com/devicetree-org/devicetree-specification/releases/download/v0.4/devicetree-specification-v0.4.pdf). - -## Features - -- **[√] Parse device tree blob** - Complete FDT structure parsing -- **[√] Dual parsing implementations** - Direct (`base`) and cached (`cache`) parsing modes -- **[√] Memory reservation handling** - Parse memory reservation blocks -- **[√] Register address translation** - Fix `reg` addresses by `range` properties -- **[√] Interrupt handling** - Find interrupt parents and parse interrupt specifications -- **[√] Clock binding support** - Parse clock providers and consumers -- **[√] Alias resolution** - Handle path aliases -- **[√] PCI bus support** - Specialized PCI host bridge parsing -- **[√] Property access** - Full access to all node properties with type-safe helpers -- **[√] Node traversal** - Hierarchical node navigation and search -- **[√] Compatible matching** - Find nodes by compatible strings -- **[√] No-std compatible** - Works in embedded environments with `alloc` - -## Architecture - -The library provides two parsing approaches: - -### Base Parser (`base` module) - -Direct parsing approach that walks the FDT structure on-demand. Uses lifetimes to avoid data copying and is memory efficient for single-pass operations. - -### Cached Parser (`cache` module) - -Builds an indexed representation for faster repeated lookups. Copies data into owned structures but provides O(1) access for many operations after initial parsing. - -## Usage - -### Basic Usage (Cached Parser) - -```rust -use fdt_parser::Fdt; - -let bytes = include_bytes!("path/to/device-tree.dtb"); - -let fdt = Fdt::from_bytes(bytes).unwrap(); -println!("FDT version: {}", fdt.version()); - -// Access memory reservation blocks -for region in fdt.memory_reservation_blocks() { - println!("Reserved region: {:?}", region); -} - -// Traverse all nodes -for node in fdt.all_nodes() { - let indent = " ".repeat(node.level()); - println!("{}{} ({})", indent, node.name(), node.full_path()); - - // Get compatible strings - if !node.compatibles().is_empty() { - println!("{} Compatible: {:?}", indent, node.compatibles()); - } - - // Get register information - if let Ok(reg) = node.reg() { - println!("{} Register: {:?}", indent, reg); - } -} -``` - -### Advanced Usage - -```rust -use fdt_parser::Fdt; - -let fdt = Fdt::from_bytes(bytes).unwrap(); - -// Find nodes by path -let memory_nodes = fdt.find_nodes("/memory@"); -for node in memory_nodes { - if let fdt_parser::Node::Memory(mem) = node { - for region in mem.regions().unwrap() { - println!("Memory region: {:x}-{:x}", region.address, region.address + region.size); - } - } -} - -// Find nodes by compatible strings -let uart_devices = fdt.find_compatible(&["generic-uart"]); -for uart in uart_devices { - println!("UART device at: {}", uart.full_path()); - - // Get interrupts - if let Ok(interrupts) = uart.interrupts() { - println!(" Interrupts: {:?}", interrupts); - } - - // Get clocks - if let Ok(clocks) = uart.clocks() { - for clock in clocks { - println!(" Clock: {} from provider {}", - clock.name.as_deref().unwrap_or("unnamed"), - clock.provider_name() - ); - } - } -} - -// Access chosen node properties -if let Some(chosen) = fdt.get_node_by_path("/chosen") { - if let fdt_parser::Node::Chosen(chosen) = chosen { - if let Some(bootargs) = chosen.bootargs() { - println!("Boot args: {}", bootargs); - } - } -} - -// Use aliases to find nodes -let serial = fdt.find_aliase("serial0") - .and_then(|path| fdt.get_node_by_path(&path)); - -if let Some(serial_node) = serial { - println!("Serial console at: {}", serial_node.full_path()); -} -``` - -### Property Access - -```rust -use fdt_parser::Fdt; - -let fdt = Fdt::from_bytes(bytes).unwrap(); -let node = fdt.get_node_by_path("/cpus/cpu@0").unwrap(); - -// Access specific properties -if let Some(prop) = node.find_property("clock-frequency") { - if let Ok(freq) = prop.u32() { - println!("CPU frequency: {} Hz", freq); - } -} - -// String list properties -if let Some(prop) = node.find_property("compatible") { - for compatible in prop.str_list() { - println!("Compatible: {}", compatible); - } -} - -// Raw data access -if let Some(prop) = node.find_property("reg") { - let raw_data = prop.raw_value(); - println!("Raw register data: {:x?}", raw_data); -} -``` - -## API Documentation - -For comprehensive API documentation, see [docs.rs/fdt-parser](https://docs.rs/fdt-parser). - -## License - -Licensed under the MPL-2.0 license. See [LICENSE](../LICENSE) for details. diff --git a/fdt-parser/examples/bcm2711.rs b/fdt-parser/examples/bcm2711.rs deleted file mode 100644 index f47819d..0000000 --- a/fdt-parser/examples/bcm2711.rs +++ /dev/null @@ -1,41 +0,0 @@ -use fdt_parser::Fdt; - -fn main() { - env_logger::builder() - .filter_level(log::LevelFilter::Trace) - .init(); - - let bytes = include_bytes!("../../dtb-file/src/dtb/bcm2711-rpi-4-b.dtb"); - - let fdt = Fdt::from_bytes(bytes).unwrap(); - println!("version: {}", fdt.version()); - for region in fdt.memory_reservation_blocks() { - println!("region: {:?}", region); - } - for (i, node) in fdt.all_nodes().into_iter().enumerate() { - if i > 40 { - break; - } - let space = " ".repeat(node.level().saturating_sub(1) * 4); - println!("{}{}", space, node.name()); - - let compatibles = node.compatibles(); - if !compatibles.is_empty() { - println!("{} -compatible: ", space); - for cap in compatibles { - println!("{} {:?}", space, cap); - } - } - - if let Ok(reg) = node.reg() { - println!("{} - reg: ", space); - for cell in reg { - println!("{} {:?}", space, cell); - } - } - - if let Some(status) = node.status() { - println!("{} - status: {:?}", space, status); - } - } -} diff --git a/fdt-parser/examples/phytium.rs b/fdt-parser/examples/phytium.rs deleted file mode 100644 index f448086..0000000 --- a/fdt-parser/examples/phytium.rs +++ /dev/null @@ -1,28 +0,0 @@ -use fdt_parser::Fdt; - -fn main() { - env_logger::builder() - .filter_level(log::LevelFilter::Trace) - .init(); - - let bytes = include_bytes!("../../dtb-file/src/dtb/phytium.dtb"); - - let fdt = Fdt::from_bytes(bytes).unwrap(); - - // Find memory nodes by compatible string - let memory_nodes = fdt.memory().unwrap(); - - for memory_node in memory_nodes { - println!("Memory node: {}", memory_node.name()); - - for region in memory_node.regions().unwrap() { - println!(" {:?}", region); - } - - // Print some basic info about the memory node - let compatibles = memory_node.compatibles(); - if !compatibles.is_empty() { - println!(" Compatibles: {:?}", compatibles); - } - } -} diff --git a/fdt-parser/src/base/fdt.rs b/fdt-parser/src/base/fdt.rs deleted file mode 100644 index b55e0b3..0000000 --- a/fdt-parser/src/base/fdt.rs +++ /dev/null @@ -1,657 +0,0 @@ -//! Core FDT parser with direct structure walking. -//! -//! This module provides the main `Fdt` type for parsing Device Tree Blobs. -//! The parser walks the structure directly, providing zero-copy access to -//! the device tree data. - -use core::iter; - -use super::node::*; -use crate::{ - data::{Buffer, Raw}, - FdtError, FdtRangeSilce, Header, MemoryRegion, Phandle, Property, Token, -}; - -/// Type alias for the result of scanning node properties. -/// -/// This type is returned by `scan_node_properties` and contains the -/// #address-cells, #size-cells, interrupt-parent, and ranges property values. -type ScannedProperties<'a> = ( - Option, - Option, - Option, - Option>, -); - -/// A Flattened Device Tree (FDT) parser. -/// -/// `Fdt` provides direct access to device tree data by walking the structure -/// without building an in-memory index. This is memory-efficient but may be -/// slower for repeated lookups compared to the cached parser. -#[derive(Clone)] -pub struct Fdt<'a> { - header: Header, - pub(crate) raw: Raw<'a>, -} - -impl<'a> Fdt<'a> { - /// Create a new `Fdt` from byte slice. - pub fn from_bytes(data: &'a [u8]) -> Result, FdtError> { - let header = Header::from_bytes(data)?; - if data.len() < header.totalsize as usize { - return Err(FdtError::BufferTooSmall { - pos: header.totalsize as usize, - }); - } - let buffer = Raw::new(data); - Ok(Fdt { - header, - raw: buffer, - }) - } - - /// Create a new `Fdt` from a raw pointer and size in bytes. - /// - /// # Safety - /// - /// The caller must ensure that the pointer is valid and points to a - /// memory region of at least `size` bytes that contains a valid device tree - /// blob. - pub unsafe fn from_ptr(ptr: *mut u8) -> Result, FdtError> { - let header = unsafe { Header::from_ptr(ptr)? }; - - let raw = Raw::new(core::slice::from_raw_parts(ptr, header.totalsize as _)); - - Ok(Fdt { header, raw }) - } - - /// Returns a slice of the underlying FDT data. - pub fn as_slice(&self) -> &'a [u8] { - self.raw.value() - } - - /// Get a reference to the FDT header. - pub fn header(&self) -> &Header { - &self.header - } - - /// Returns the total size of the FDT in bytes. - pub fn total_size(&self) -> usize { - self.header.totalsize as usize - } - - /// This field shall contain the physical ID of the system's boot CPU. - /// It shall be identical to the physical ID given in the reg property of - /// that CPU node within the devicetree. - pub fn boot_cpuid_phys(&self) -> u32 { - self.header.boot_cpuid_phys - } - - /// Get a reference to the underlying buffer. - pub fn raw(&self) -> &'a [u8] { - self.raw.value() - } - - /// Get the FDT version - pub fn version(&self) -> u32 { - self.header.version - } - - /// Returns an iterator over memory reservation blocks. - pub fn memory_reservation_blocks(&self) -> impl Iterator + 'a { - let mut buffer = self - .raw - .begin_at(self.header.off_mem_rsvmap as usize) - .buffer(); - - core::iter::from_fn(move || { - let address = buffer.take_u64().ok()?; - let size = buffer.take_u64().ok()?; - - if address == 0 && size == 0 { - return None; - } - - Some(MemoryRegion { - address: address as usize as _, - size: size as _, - }) - }) - } - - pub(crate) fn get_str(&self, offset: usize) -> Result<&'a str, FdtError> { - let start = self.header.off_dt_strings as usize + offset; - let mut buffer = self.raw.begin_at(start).buffer(); - buffer.take_str() - } - - /// Returns an iterator over all nodes in the device tree. - pub fn all_nodes(&self) -> NodeIter<'a, 16> { - NodeIter::new(self.clone()) - } - - /// Find nodes by path or alias. - /// - /// If path starts with '/' then search by path, else search by aliases. - pub fn find_nodes( - &self, - path: &'a str, - ) -> impl Iterator, FdtError>> + 'a { - let path = if path.starts_with("/") { - path - } else { - self.find_aliase(path).unwrap() - }; - - IterFindNode::new(self.all_nodes(), path) - } - - /// Find an alias by name and return its path. - pub fn find_aliase(&self, name: &str) -> Result<&'a str, FdtError> { - let aliases = self - .find_nodes("/aliases") - .next() - .ok_or(FdtError::NoAlias)??; - for prop in aliases.properties() { - let prop = prop?; - if prop.name.eq(name) { - return prop.str(); - } - } - Err(FdtError::NoAlias) - } - - /// Find nodes with compatible strings matching the given list. - pub fn find_compatible<'b, 'c: 'b>( - &'b self, - with: &'c [&'c str], - ) -> impl Iterator, FdtError>> + 'b { - let mut iter = self.all_nodes(); - let mut has_err = false; - iter::from_fn(move || loop { - if has_err { - return None; - } - let node = iter.next()?; - let node = match node { - Ok(n) => n, - Err(e) => { - return { - has_err = true; - Some(Err(e)) - } - } - }; - match node.compatibles() { - Ok(mut comp) => { - if comp.any(|c| with.iter().any(|w| w.eq(&c))) { - return Some(Ok(node)); - } - } - Err(FdtError::NotFound) => {} - Err(e) => { - return { - has_err = true; - Some(Err(e)) - } - } - } - }) - } - - /// Get the /chosen node. - pub fn chosen(&self) -> Result, FdtError> { - let node = self - .find_nodes("/chosen") - .next() - .ok_or(FdtError::NotFound)??; - let node = match node { - Node::Chosen(c) => c, - _ => return Err(FdtError::NodeNotFound("chosen")), - }; - Ok(node) - } - - /// Find a node by its phandle. - pub fn get_node_by_phandle(&self, phandle: Phandle) -> Result, FdtError> { - for node in self.all_nodes() { - let node = node?; - match node.phandle() { - Ok(p) if p == phandle => return Ok(node), - Ok(_) => {} - Err(FdtError::NotFound) => {} - Err(e) => return Err(e), - } - } - Err(FdtError::NotFound) - } - - /// Find a node by its name. - pub fn get_node_by_name(&'a self, name: &str) -> Result, FdtError> { - for node in self.all_nodes() { - let node = node?; - if node.name() == name { - return Ok(node); - } - } - Err(FdtError::NotFound) - } - - /// Get memory nodes from the /memory node. - pub fn memory(&'a self) -> impl Iterator, FdtError>> + 'a { - self.find_nodes("/memory").map(|o| { - o.map(|o| match o { - Node::Memory(m) => m, - _ => unreachable!(), - }) - }) - } - - /// Get the reserved-memory node - fn reserved_memory_node(&self) -> Result, FdtError> { - let node = self - .find_nodes("/reserved-memory") - .next() - .ok_or(FdtError::NotFound)?; - node - } - - /// Get all reserved-memory child nodes (memory regions). - pub fn reserved_memory_regions(&self) -> Result, FdtError> { - match self.reserved_memory_node() { - Ok(reserved_memory_node) => Ok(ReservedMemoryRegionsIter::new(reserved_memory_node)), - Err(FdtError::NotFound) => Ok(ReservedMemoryRegionsIter::empty()), - Err(e) => Err(e), - } - } -} - -/// Iterator for reserved memory regions (child nodes of reserved-memory). -pub struct ReservedMemoryRegionsIter<'a> { - child_iter: Option>, -} - -impl<'a> ReservedMemoryRegionsIter<'a> { - /// Create a new iterator for reserved memory regions. - fn new(reserved_memory_node: Node<'a>) -> Self { - ReservedMemoryRegionsIter { - child_iter: Some(reserved_memory_node.children()), - } - } - - /// Create an empty iterator. - fn empty() -> Self { - ReservedMemoryRegionsIter { child_iter: None } - } - - /// Find a reserved memory region by name. - pub fn find_by_name(self, name: &str) -> Result, FdtError> { - for region_result in self { - let region = region_result?; - if region.name() == name { - return Ok(region); - } - } - Err(FdtError::NotFound) - } - - /// Find reserved memory regions by compatible string. - pub fn find_by_compatible( - self, - compatible: &str, - ) -> Result>, FdtError> { - let mut matching_regions = alloc::vec::Vec::new(); - - for region_result in self { - let region = region_result?; - match region.compatibles() { - Ok(mut compatibles) => { - if compatibles.any(|comp| comp == compatible) { - matching_regions.push(region); - } - } - Err(FdtError::NotFound) => {} - Err(e) => return Err(e), - } - } - - Ok(matching_regions) - } -} - -impl<'a> Iterator for ReservedMemoryRegionsIter<'a> { - type Item = Result, FdtError>; - - fn next(&mut self) -> Option { - match &mut self.child_iter { - Some(iter) => iter.next(), - None => None, - } - } -} - -/// Stack frame for tracking node context during iteration. -#[derive(Clone)] -struct NodeStackFrame<'a> { - level: usize, - node: NodeBase<'a>, - address_cells: u8, - size_cells: u8, - ranges: Option>, - interrupt_parent: Option, -} - -/// Iterator over all nodes in the device tree. -/// -/// The iterator maintains a stack to track the node hierarchy and -/// provide context for address translation and interrupt routing. -pub struct NodeIter<'a, const MAX_DEPTH: usize = 16> { - buffer: Buffer<'a>, - fdt: Fdt<'a>, - level: isize, - has_err: bool, - // Stack to store complete node hierarchy - node_stack: heapless::Vec, MAX_DEPTH>, -} - -impl<'a, const MAX_DEPTH: usize> NodeIter<'a, MAX_DEPTH> { - /// Create a new NodeIter with the given FDT. - pub fn new(fdt: Fdt<'a>) -> Self { - NodeIter { - buffer: fdt.raw.begin_at(fdt.header.off_dt_struct as usize).buffer(), - fdt, - level: -1, - has_err: false, - node_stack: heapless::Vec::new(), - } - } - - /// Get the current node from stack (parent of the node being created). - fn current_parent(&self) -> Option<&NodeBase<'a>> { - self.node_stack.last().map(|frame| &frame.node) - } - - /// Get the current effective interrupt parent phandle from the stack. - fn current_interrupt_parent(&self) -> Option { - // Search from the top of the stack downward for the first interrupt parent - for frame in self.node_stack.iter().rev() { - if let Some(phandle) = frame.interrupt_parent { - return Some(phandle); - } - } - None - } - - /// Get address_cells and size_cells from parent frame. - fn current_cells(&self) -> (u8, u8) { - self.node_stack - .last() - .map(|frame| (frame.address_cells, frame.size_cells)) - .unwrap_or((2, 1)) - } - - /// Push a new node onto the stack. - fn push_node(&mut self, frame: NodeStackFrame<'a>) -> Result<(), FdtError> { - self.node_stack - .push(frame) - .map_err(|_| FdtError::BufferTooSmall { - pos: self.node_stack.len(), - }) - } - - /// Pop nodes from stack when exiting to a certain level. - fn pop_to_level(&mut self, target_level: isize) { - while let Some(frame) = self.node_stack.last() { - if frame.level as isize > target_level { - self.node_stack.pop(); - } else { - break; - } - } - } - - /// Scan ahead to find node properties (#address-cells, #size-cells, interrupt-parent, ranges). - fn scan_node_properties(&self) -> Result, FdtError> { - let mut address_cells = None; - let mut size_cells = None; - let mut interrupt_parent = self.current_interrupt_parent(); - let mut ranges = None; - let mut temp_buffer = self.buffer.clone(); - - // Look for properties in this node - loop { - match temp_buffer.take_token() { - Ok(Token::Prop) => { - let prop = temp_buffer.take_prop(&self.fdt)?; - match prop.name { - "#address-cells" => { - if let Ok(value) = prop.u32() { - address_cells = Some(value as u8); - } - } - "#size-cells" => { - if let Ok(value) = prop.u32() { - size_cells = Some(value as u8); - } - } - "interrupt-parent" => { - if let Ok(phandle_value) = prop.u32() { - interrupt_parent = Some(Phandle::from(phandle_value)); - } - } - "ranges" => { - ranges = Some(prop); - } - _ => {} - } - } - Ok(Token::BeginNode) | Ok(Token::EndNode) | Ok(Token::End) => { - break; - } - _ => { - continue; - } - } - } - - Ok((address_cells, size_cells, interrupt_parent, ranges)) - } - - /// Handle BeginNode token and create a new node. - fn handle_begin_node(&mut self) -> Result>, FdtError> { - self.level += 1; - - let name = self.buffer.take_str()?; - self.buffer.take_to_aligned(); - - // Scan node properties including ranges - let (address_cells, size_cells, interrupt_parent, ranges_prop) = - self.scan_node_properties()?; - - // Use defaults from parent if not specified - let (default_addr, default_size) = self.current_cells(); - let address_cells = address_cells.unwrap_or(default_addr); - let size_cells = size_cells.unwrap_or(default_size); - let interrupt_parent = interrupt_parent.or_else(|| self.current_interrupt_parent()); - - // Get parent node and its info from stack - let parent = self.current_parent(); - let (parent_address_cells, parent_size_cells, parent_ranges) = self - .node_stack - .last() - .map(|frame| { - ( - Some(frame.address_cells), - Some(frame.size_cells), - frame.ranges.clone(), - ) - }) - .unwrap_or((None, None, None)); - - // Calculate ranges for this node if ranges property exists - // The ranges will be used by this node's children for address translation - let ranges = if let Some(ranges_prop) = ranges_prop { - // Get parent's address cells for the ranges property - let parent_addr_cells = parent_address_cells.unwrap_or(2); - - Some(FdtRangeSilce::new( - address_cells, - parent_addr_cells, - size_cells, - &ranges_prop.data, - )) - } else { - None - }; - - // Create the new node with parent info from stack - use crate::base::node::ParentInfoBuilder; - let node = NodeBase::new_with_parent_info( - name, - self.fdt.clone(), - self.buffer.remain(), - self.level as _, - parent, - ParentInfoBuilder { - parent_address_cells, - parent_size_cells, - parent_ranges, - interrupt_parent, - }, - ); - // Push this node onto the stack for its children - let frame = NodeStackFrame { - level: self.level as usize, - node: node.clone(), - address_cells, - size_cells, - ranges, - interrupt_parent, - }; - self.push_node(frame)?; - - // Return the node immediately - Ok(Some(node)) - } - - /// Handle EndNode token - just pop from stack. - fn handle_end_node(&mut self) -> Option> { - self.level -= 1; - - // Pop the current level from stack - self.pop_to_level(self.level); - - // Don't return anything - nodes are returned on BeginNode - None - } - - /// Handle Prop token. - fn handle_prop(&mut self) -> Result<(), FdtError> { - let _prop = self.buffer.take_prop(&self.fdt)?; - // Property handling is now done in BeginNode scanning - Ok(()) - } - - fn try_next(&mut self) -> Result>, FdtError> { - loop { - let token = self.buffer.take_token()?; - match token { - Token::BeginNode => { - if let Some(finished_node) = self.handle_begin_node()? { - return Ok(Some(finished_node)); - } - } - Token::EndNode => { - if let Some(node) = self.handle_end_node() { - return Ok(Some(node)); - } - } - Token::Prop => { - self.handle_prop()?; - } - Token::End => { - return Ok(None); - } - _ => continue, - } - } - } -} - -impl<'a, const MAX_DEPTH: usize> Iterator for NodeIter<'a, MAX_DEPTH> { - type Item = Result, FdtError>; - - fn next(&mut self) -> Option { - if self.has_err { - return None; - } - match self.try_next() { - Ok(Some(node)) => Some(Ok(node.into())), - Ok(None) => None, - Err(e) => { - self.has_err = true; - Some(Err(e)) - } - } - } -} - -struct IterFindNode<'a, const MAX_DEPTH: usize = 16> { - itr: NodeIter<'a, MAX_DEPTH>, - want: &'a str, - want_itr: usize, - is_path_last: bool, - has_err: bool, -} - -impl<'a, const MAX_DEPTH: usize> IterFindNode<'a, MAX_DEPTH> { - fn new(itr: NodeIter<'a, MAX_DEPTH>, want: &'a str) -> Self { - IterFindNode { - itr, - want, - want_itr: 0, - is_path_last: false, - has_err: false, - } - } -} - -impl<'a, const MAX_DEPTH: usize> Iterator for IterFindNode<'a, MAX_DEPTH> { - type Item = Result, FdtError>; - - fn next(&mut self) -> Option { - let mut out = None; - loop { - let mut parts = self.want.split("/").filter(|o| !o.is_empty()); - let mut want_part = "/"; - for _ in 0..self.want_itr { - if let Some(part) = parts.next() { - want_part = part; - } else { - self.is_path_last = true; - if let Some(out) = out { - return Some(out); - } - } - } - let node = match self.itr.next()? { - Ok(v) => v, - Err(e) => { - self.has_err = true; - return Some(Err(e)); - } - }; - - let eq = if want_part.contains("@") { - node.name().eq(want_part) - } else { - let name = node.name().split("@").next().unwrap(); - name.eq(want_part) - }; - if eq { - self.want_itr += 1; - out = Some(Ok(node)); - } - } - } -} diff --git a/fdt-parser/src/base/mod.rs b/fdt-parser/src/base/mod.rs deleted file mode 100644 index 9e20f3b..0000000 --- a/fdt-parser/src/base/mod.rs +++ /dev/null @@ -1,11 +0,0 @@ -//! Direct parsing module for FDT structures. -//! -//! This module provides a zero-copy parser that walks the FDT structure -//! directly without building an in-memory index. It is suitable for -//! one-pass operations where memory efficiency is important. - -mod fdt; -mod node; - -pub use fdt::*; -pub use node::*; diff --git a/fdt-parser/src/base/node/chosen.rs b/fdt-parser/src/base/node/chosen.rs deleted file mode 100644 index ba2a81f..0000000 --- a/fdt-parser/src/base/node/chosen.rs +++ /dev/null @@ -1,200 +0,0 @@ -//! Chosen node type for boot parameters. -//! -//! This module provides the `Chosen` type for the /chosen node which contains -//! system configuration parameters passed by the bootloader. - -use core::{fmt::Debug, ops::Deref}; - -use crate::{base::NodeBase, FdtError}; - -/// Result of debug console lookup. -#[derive(Clone, Debug)] -pub enum DebugCon<'a> { - /// Found the corresponding device tree node - Node(NodeBase<'a>), - /// Found earlycon parameter only in bootargs, with parsed information - EarlyConInfo { - /// The name of the early console device (e.g., "uart8250") - name: &'a str, - /// The MMIO address of the device - mmio: u64, - /// Additional parameters for the early console - params: Option<&'a str>, - }, -} - -/// The /chosen node containing boot parameters. -/// -/// The chosen node doesn't represent any actual hardware device but serves -/// as a place to pass parameters to the operating system or bootloader. -#[derive(Clone)] -pub struct Chosen<'a> { - node: NodeBase<'a>, -} - -impl<'a> Chosen<'a> { - pub(crate) fn new(node: NodeBase<'a>) -> Self { - Chosen { node } - } - - /// Get the bootargs from the bootargs property, if it exists. - pub fn bootargs(&self) -> Result<&'a str, FdtError> { - let prop = self.node.find_property("bootargs")?; - prop.str() - } - - /// Get the stdout node specified by the stdout-path property. - /// - /// Searches for the node representing stdout, attempting to resolve - /// aliases if the node name doesn't exist as-is. - pub fn stdout(&self) -> Result, FdtError> { - let prop = self.node.find_property("stdout-path")?; - - let path = prop.str()?; - - let mut sp = path.split(':'); - - let name = none_ok!(sp.next(), FdtError::NodeNotFound("path")); - - let params = sp.next(); - let node = self - .node - .fdt - .find_nodes(name) - .next() - .ok_or(FdtError::NodeNotFound("path"))??; - - Ok(Stdout { - params, - node: node.deref().clone(), - }) - } - - /// Get the debug console information. - /// - /// First tries to find the stdout node. If that fails, parses the - /// bootargs for earlycon configuration. - pub fn debugcon(&self) -> Result, FdtError> { - match self.stdout() { - Ok(stdout) => Ok(DebugCon::Node(stdout.node.clone())), - Err(FdtError::NotFound) | Err(FdtError::NodeNotFound(_)) => { - self.fdt_bootargs_find_debugcon_info() - } - Err(e) => Err(e), - } - } - - fn fdt_bootargs_find_debugcon_info(&self) -> Result, FdtError> { - let bootargs = self.bootargs()?; - - let earlycon = none_ok!(bootargs - .split_ascii_whitespace() - .find(|&arg| arg.contains("earlycon"))); - - let mut tmp = earlycon.split('='); - let _ = none_ok!(tmp.next(), FdtError::NotFound); - let values = none_ok!(tmp.next(), FdtError::NotFound); - - // Parse all parameters - let mut params_iter = values.split(','); - let name = none_ok!(params_iter.next(), FdtError::NotFound); - - if !name.contains("uart") { - return Err(FdtError::NotFound); - } - - let param2 = none_ok!(params_iter.next(), FdtError::NotFound); - - let addr_str = if param2.contains("0x") { - param2 - } else { - none_ok!(params_iter.next(), FdtError::NotFound) - }; - - let mmio = u64::from_str_radix(addr_str.trim_start_matches("0x"), 16) - .map_err(|_| FdtError::Utf8Parse)?; - - // Try to find the corresponding node in the device tree first - for node_result in self.node.fdt.all_nodes() { - let node = node_result?; - match node.reg() { - Ok(mut regs) => { - for reg in &mut regs { - if reg.address == mmio { - return Ok(DebugCon::Node(node.node().clone())); - } - } - } - Err(FdtError::NotFound) => {} - Err(e) => return Err(e), - } - } - - // If no matching node is found, return the parsed earlycon information - // Re-split the string to get remaining parameters - let mut parts = values.split(','); - let _name = parts.next(); // skip name - let _addr_part = parts.next(); // skip address part - let params = if let Some(param) = parts.next() { - // Get the position of the first remaining parameter, then take all remaining content - let param_start = values.find(param).unwrap_or(0); - if param_start > 0 { - Some(&values[param_start..]) - } else { - Some(param) - } - } else { - None - }; - - Ok(DebugCon::EarlyConInfo { name, mmio, params }) - } -} - -impl Debug for Chosen<'_> { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.debug_struct("Chosen") - .field("bootargs", &self.bootargs()) - .field("stdout", &self.stdout()) - .finish() - } -} - -impl<'a> Deref for Chosen<'a> { - type Target = NodeBase<'a>; - - fn deref(&self) -> &Self::Target { - &self.node - } -} - -/// The stdout device specified by the chosen node. -/// -/// Contains the node reference and optional parameters (typically specifying -/// the baud rate or other console configuration). -#[derive(Clone)] -pub struct Stdout<'a> { - /// Optional parameters for the stdout device (e.g., baud rate) - pub params: Option<&'a str>, - /// The device tree node for the stdout device - pub node: NodeBase<'a>, -} - -impl<'a> Stdout<'a> {} - -impl Debug for Stdout<'_> { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.debug_struct("Stdout") - .field("name", &self.node.name()) - .field("params", &self.params) - .finish() - } -} - -impl<'a> Deref for Stdout<'a> { - type Target = NodeBase<'a>; - - fn deref(&self) -> &Self::Target { - &self.node - } -} diff --git a/fdt-parser/src/base/node/interrupt_controller.rs b/fdt-parser/src/base/node/interrupt_controller.rs deleted file mode 100644 index 31911bb..0000000 --- a/fdt-parser/src/base/node/interrupt_controller.rs +++ /dev/null @@ -1,56 +0,0 @@ -//! Interrupt controller node type. -//! -//! This module provides the `InterruptController` type for nodes that -//! manage interrupt routing and handling in the system. - -use core::ops::Deref; - -use super::NodeBase; -use crate::FdtError; - -/// An interrupt controller device node. -/// -/// Interrupt controllers manage interrupt routing and handling. This type -/// provides access to interrupt controller specific properties like the -/// `#interrupt-cells` property. -#[derive(Clone)] -pub struct InterruptController<'a> { - node: NodeBase<'a>, -} - -impl<'a> InterruptController<'a> { - pub(crate) fn new(node: NodeBase<'a>) -> Self { - InterruptController { node } - } - - /// Get the name of this interrupt controller. - pub fn name(&self) -> &'a str { - self.node.name() - } - - /// Get the value of the `#interrupt-cells` property. - /// - /// This property specifies the number of cells used to encode an - /// interrupt specifier for this interrupt controller. - pub fn interrupt_cells(&self) -> Result { - let prop = self.node.find_property("#interrupt-cells")?; - let val = prop.u32()?; - Ok(val as u8) - } -} - -impl core::fmt::Debug for InterruptController<'_> { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - let mut st = f.debug_struct("InterruptController"); - st.field("name", &self.name()); - st.finish() - } -} - -impl<'a> Deref for InterruptController<'a> { - type Target = NodeBase<'a>; - - fn deref(&self) -> &Self::Target { - &self.node - } -} diff --git a/fdt-parser/src/base/node/memory.rs b/fdt-parser/src/base/node/memory.rs deleted file mode 100644 index 2ab2ec5..0000000 --- a/fdt-parser/src/base/node/memory.rs +++ /dev/null @@ -1,77 +0,0 @@ -//! Memory node type. -//! -//! This module provides the `Memory` type for memory device nodes that -//! describe the physical memory layout of the system. - -use core::{iter, ops::Deref}; - -use crate::{base::NodeBase, FdtError, MemoryRegion}; - -/// A memory device node. -/// -/// Memory device nodes describe the physical memory layout for the system. -/// A system can have multiple memory nodes, or multiple memory ranges -/// specified in the `reg` property of a single memory node. -#[derive(Clone)] -pub struct Memory<'a> { - node: NodeBase<'a>, -} - -impl<'a> Memory<'a> { - pub(crate) fn new(node: NodeBase<'a>) -> Self { - Memory { node } - } - - /// Returns an iterator over the memory regions described by this node. - /// - /// A memory device node is required for all devicetrees and describes the - /// physical memory layout for the system. If a system has multiple ranges - /// of memory, multiple memory nodes can be created, or the ranges can be - /// specified in the reg property of a single memory node. - pub fn regions(&self) -> impl Iterator> + 'a { - let mut reg = self.node.reg(); - let mut has_error = false; - iter::from_fn(move || { - if has_error { - return None; - } - match &mut reg { - Ok(iter) => { - let one = iter.next()?; - Some(Ok(MemoryRegion { - address: one.address as usize as _, - size: one.size.unwrap_or_default(), - })) - } - Err(e) => { - has_error = true; - Some(Err(e.clone())) - } - } - }) - } - - /// Get the name of this memory node. - pub fn name(&self) -> &'a str { - self.node.name() - } -} - -impl core::fmt::Debug for Memory<'_> { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - let mut st = f.debug_struct("Memory"); - st.field("name", &self.name()); - for r in self.regions().flatten() { - st.field("region", &r); - } - st.finish() - } -} - -impl<'a> Deref for Memory<'a> { - type Target = NodeBase<'a>; - - fn deref(&self) -> &Self::Target { - &self.node - } -} diff --git a/fdt-parser/src/base/node/mod.rs b/fdt-parser/src/base/node/mod.rs deleted file mode 100644 index ba13ec3..0000000 --- a/fdt-parser/src/base/node/mod.rs +++ /dev/null @@ -1,600 +0,0 @@ -//! Device tree node types and accessors. -//! -//! This module provides the `Node` enum and related types for accessing -//! device tree nodes. Nodes are automatically classified into specialized -//! types (Chosen, Memory, InterruptController, etc.) based on their properties. - -use core::ops::Deref; - -use super::Fdt; -use crate::{ - base::NodeIter, - data::{Buffer, Raw, U32Iter2D}, - property::PropIter, - FdtError, FdtRangeSilce, FdtReg, Phandle, Property, Status, -}; - -mod chosen; -mod interrupt_controller; -mod memory; - -pub use chosen::*; -pub use interrupt_controller::*; -pub use memory::*; - -/// Base node type representing any device tree node. -/// -/// `NodeBase` provides common functionality available on all nodes, -/// including property access, child iteration, and parent references. -#[derive(Clone)] -pub struct NodeBase<'a> { - name: &'a str, - pub(crate) fdt: Fdt<'a>, - /// The depth/level of this node in the device tree (0 for root) - pub level: usize, - pub(crate) raw: Raw<'a>, - pub(crate) parent: Option>, - interrupt_parent: Option, -} - -/// Information about a node's parent, used for address translation. -#[derive(Clone)] -pub(crate) struct ParentInfo<'a> { - pub name: &'a str, - pub level: usize, - pub raw: Raw<'a>, - /// Parent's #address-cells and #size-cells (for parsing reg) - pub address_cells: Option, - pub size_cells: Option, - /// Parent's ranges for address translation - pub ranges: Option>, -} - -/// Builder for creating NodeBase with parent information. -/// -/// This struct reduces the number of parameters needed for `NodeBase::new_with_parent_info` -/// by grouping related parameters together. -pub(crate) struct ParentInfoBuilder<'a> { - pub parent_address_cells: Option, - pub parent_size_cells: Option, - pub parent_ranges: Option>, - pub interrupt_parent: Option, -} - -impl<'a> NodeBase<'a> { - /// Create a new NodeBase with pre-calculated parent information from the stack. - #[allow(clippy::too_many_arguments)] - pub(crate) fn new_with_parent_info( - name: &'a str, - fdt: Fdt<'a>, - raw: Raw<'a>, - level: usize, - parent: Option<&NodeBase<'a>>, - parent_info: ParentInfoBuilder<'a>, - ) -> Self { - let name = if name.is_empty() { "/" } else { name }; - NodeBase { - name, - fdt, - level, - parent: parent.map(|p| ParentInfo { - name: p.name(), - level: p.level(), - raw: p.raw(), - address_cells: parent_info.parent_address_cells, - size_cells: parent_info.parent_size_cells, - ranges: parent_info.parent_ranges, - }), - interrupt_parent: parent_info.interrupt_parent, - raw, - } - } - - /// Returns the name of this node's parent. - pub fn parent_name(&self) -> Option<&'a str> { - self.parent_fast().map(|p| p.name()) - } - - /// Returns the parent node as a `Node`. - pub fn parent(&self) -> Option> { - let parent_info = self.parent.as_ref()?; - self.fdt - .all_nodes() - .flatten() - .find(|node| node.name() == parent_info.name && node.level() == parent_info.level) - } - - pub(crate) fn parent_fast(&self) -> Option> { - self.parent.as_ref().map(|p| NodeBase { - name: p.name, - fdt: self.fdt.clone(), - level: p.level, - raw: p.raw, - parent: None, - interrupt_parent: None, - }) - } - - /// Returns the raw data for this node. - pub fn raw(&self) -> Raw<'a> { - self.raw - } - - /// Get the name of this node. - pub fn name(&self) -> &'a str { - self.name - } - - /// Get the level/depth of this node in the device tree. - pub fn level(&self) -> usize { - self.level - } - - /// Get compatible strings for this node (placeholder implementation). - pub fn compatibles(&self) -> Result + 'a, FdtError> { - let prop = self.find_property("compatible")?; - Ok(prop.str_list()) - } - - /// Returns a flattened iterator over compatible strings. - /// - /// This is an alias for [`compatibles`](Self::compatibles) that - /// returns the same iterator for chaining with other iterator operations. - pub fn compatibles_flatten(&self) -> Result + 'a, FdtError> { - self.compatibles() - } - - /// Returns an iterator over this node's register entries. - /// - /// The addresses are automatically translated from child bus addresses - /// to parent bus addresses using the parent's ranges property. - pub fn reg(&self) -> Result, FdtError> { - let prop = self.find_property("reg")?; - - // Get parent info from ParentInfo structure - let parent_info = self - .parent - .as_ref() - .ok_or(FdtError::NodeNotFound("parent"))?; - - // reg parsing uses the immediate parent's cells - let address_cell = parent_info.address_cells.unwrap_or(2); - let size_cell = parent_info.size_cells.unwrap_or(1); - - // Use parent's pre-calculated ranges for address translation - let ranges = parent_info.ranges.clone(); - - Ok(RegIter { - size_cell, - address_cell, - buff: prop.data.buffer(), - ranges, - }) - } - - fn is_interrupt_controller(&self) -> bool { - self.find_property("#interrupt-controller").is_ok() - } - - /// Check if this node is the root node. - pub fn is_root(&self) -> bool { - self.level == 0 - } - - /// Get debug information about the node (for debugging purposes only). - pub fn debug_info(&self) -> NodeDebugInfo<'a> { - NodeDebugInfo { - name: self.name(), - level: self.level, - pos: self.raw.pos(), - } - } - - /// Returns an iterator over this node's properties. - pub fn properties(&self) -> impl Iterator, FdtError>> + '_ { - let reader = self.raw.buffer(); - PropIter::new(self.fdt.clone(), reader) - } - - /// Find a property by name. - pub fn find_property(&self, name: &str) -> Result, FdtError> { - for prop in self.properties() { - let prop = prop?; - if prop.name.eq(name) { - return Ok(prop); - } - } - Err(FdtError::NotFound) - } - - /// Get this node's phandle. - pub fn phandle(&self) -> Result { - let prop = self.find_property("phandle")?; - Ok(prop.u32()?.into()) - } - - /// Find [InterruptController] from current node or its parent. - pub fn interrupt_parent(&self) -> Result, FdtError> { - // First try to get the interrupt parent phandle from the node itself - let phandle = self.interrupt_parent.ok_or(FdtError::NotFound)?; - - // Find the node with this phandle - let node = self.fdt.get_node_by_phandle(phandle)?; - match node { - Node::InterruptController(ic) => Ok(ic), - _ => Err(FdtError::NodeNotFound("interrupt-parent")), - } - } - - /// Get the interrupt parent phandle for this node. - pub fn get_interrupt_parent_phandle(&self) -> Option { - self.interrupt_parent - } - - /// Returns an iterator over this node's interrupts. - /// - /// Each interrupt is represented as an iterator of u32 cells. - pub fn interrupts( - &self, - ) -> Result + 'a> + 'a, FdtError> { - let prop = self.find_property("interrupts")?; - let irq_parent = self.interrupt_parent()?; - let cell_size = irq_parent.interrupt_cells()?; - let iter = U32Iter2D::new(&prop.data, cell_size); - - Ok(iter) - } - - /// Get the clock-frequency property value. - pub fn clock_frequency(&self) -> Result { - let prop = self.find_property("clock-frequency")?; - prop.u32() - } - - /// Returns an iterator over this node's children. - pub fn children(&self) -> NodeChildIter<'a> { - NodeChildIter { - fdt: self.fdt.clone(), - parent: self.clone(), - all_nodes: None, - target_level: 0, - found_parent: false, - } - } - - /// Get the status property value. - pub fn status(&self) -> Result { - let prop = self.find_property("status")?; - let s = prop.str()?; - - if s.contains("disabled") { - return Ok(Status::Disabled); - } - - if s.contains("okay") { - return Ok(Status::Okay); - } - - Err(FdtError::NotFound) - } -} - -/// Node debug information. -#[derive(Debug)] -pub struct NodeDebugInfo<'a> { - /// The name of the node - pub name: &'a str, - /// The depth/level of the node in the device tree - pub level: usize, - /// The position of the node in the raw data - pub pos: usize, -} - -impl core::fmt::Debug for NodeBase<'_> { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.debug_struct("Node").field("name", &self.name()).finish() - } -} - -/// Iterator over register entries. -pub struct RegIter<'a> { - pub(crate) size_cell: u8, - pub(crate) address_cell: u8, - pub(crate) buff: Buffer<'a>, - pub(crate) ranges: Option>, -} -impl Iterator for RegIter<'_> { - type Item = FdtReg; - - fn next(&mut self) -> Option { - let child_bus_address = self.buff.take_by_cell_size(self.address_cell)?; - - let mut address = child_bus_address; - - if let Some(ranges) = &self.ranges { - for one in ranges.iter() { - let range_child_bus_address = one.child_bus_address().as_u64(); - let range_parent_bus_address = one.parent_bus_address().as_u64(); - - if child_bus_address >= range_child_bus_address - && child_bus_address < range_child_bus_address + one.size - { - address = - child_bus_address - range_child_bus_address + range_parent_bus_address; - break; - } - } - } - - let size = if self.size_cell > 0 { - Some(self.buff.take_by_cell_size(self.size_cell)? as usize) - } else { - None - }; - Some(FdtReg { - address, - child_bus_address, - size, - }) - } -} - -/// Typed node enum for specialized node access. -/// -/// Nodes are automatically classified based on their name and properties. -/// Use pattern matching to access node-specific functionality. -#[derive(Debug, Clone)] -pub enum Node<'a> { - /// A general-purpose node without special handling - General(NodeBase<'a>), - /// The /chosen node containing boot parameters - Chosen(Chosen<'a>), - /// A memory node (e.g., /memory@0) - Memory(Memory<'a>), - /// An interrupt controller node - InterruptController(InterruptController<'a>), -} - -impl<'a> Node<'a> { - /// Returns a reference to the underlying `NodeBase`. - pub fn node(&self) -> &NodeBase<'a> { - self.deref() - } -} - -impl<'a> From> for Node<'a> { - fn from(node: NodeBase<'a>) -> Self { - if node.name() == "chosen" { - Node::Chosen(Chosen::new(node)) - } else if node.name().starts_with("memory@") { - Node::Memory(Memory::new(node)) - } else if node.is_interrupt_controller() { - Node::InterruptController(InterruptController::new(node)) - } else { - Node::General(node) - } - } -} - -impl<'a> Deref for Node<'a> { - type Target = NodeBase<'a>; - - fn deref(&self) -> &Self::Target { - match self { - Node::General(n) => n, - Node::Chosen(n) => n, - Node::Memory(n) => n, - Node::InterruptController(n) => n, - } - } -} - -/// Iterator over a node's children. -pub struct NodeChildIter<'a> { - fdt: Fdt<'a>, - parent: NodeBase<'a>, - all_nodes: Option>, - target_level: usize, - found_parent: bool, -} - -impl<'a> Iterator for NodeChildIter<'a> { - type Item = Result, FdtError>; - - fn next(&mut self) -> Option { - // Lazily initialize the node iterator - if self.all_nodes.is_none() { - self.all_nodes = Some(self.fdt.all_nodes()); - } - - let all_nodes = self.all_nodes.as_mut()?; - - // Search for child nodes - loop { - let node = match all_nodes.next()? { - Ok(node) => node, - Err(e) => return Some(Err(e)), - }; - - // First, find the parent node - if !self.found_parent { - if node.name() == self.parent.name() && node.level() == self.parent.level() { - self.found_parent = true; - self.target_level = node.level() + 1; - } - continue; - } - - // Parent node found, now look for child nodes - let current_level = node.level(); - - // If current node's level equals target level and follows parent in tree structure, - // then it's a direct child of the parent node - if current_level == self.target_level { - return Some(Ok(node)); - } - - // If current node's level is less than or equal to parent's level, - // we've left the parent's subtree - if current_level <= self.parent.level() { - break; - } - } - - None - } -} - -impl<'a> NodeChildIter<'a> { - /// Create a new child node iterator. - pub fn new(fdt: Fdt<'a>, parent: NodeBase<'a>) -> Self { - NodeChildIter { - fdt, - parent, - all_nodes: None, - target_level: 0, - found_parent: false, - } - } - - /// Get a reference to the parent node. - pub fn parent(&self) -> &NodeBase<'a> { - &self.parent - } - - /// Collect all child nodes into a Vec. - pub fn collect_children(self) -> Result>, FdtError> { - self.collect() - } - - /// Find a child node by name. - pub fn find_child_by_name(self, name: &str) -> Result, FdtError> { - for child_result in self { - let child = child_result?; - if child.name() == name { - return Ok(child); - } - } - Err(FdtError::NotFound) - } - - /// Find a child node by compatible string. - pub fn find_child_by_compatible(self, compatible: &str) -> Result, FdtError> { - for child_result in self { - let child = child_result?; - match child.compatibles() { - Ok(mut compatibles) => { - if compatibles.any(|comp| comp == compatible) { - return Ok(child); - } - } - Err(FdtError::NotFound) => {} - Err(e) => return Err(e), - } - } - Err(FdtError::NotFound) - } -} - -#[cfg(test)] -mod tests { - use super::{Fdt, FdtError}; - - #[test] - fn test_node_child_iter_basic() { - let dtb_data = include_bytes!("../../../../dtb-file/src/dtb/bcm2711-rpi-4-b.dtb"); - let fdt = Fdt::from_bytes(dtb_data).unwrap(); - - // Find the root node - let root_node = fdt.find_nodes("/").next().unwrap().unwrap(); - - // Test child node iterator - let children: Result, _> = root_node.children().collect(); - let children = children.unwrap(); - - // Root node should have children - assert!(!children.is_empty(), "Root node should have children"); - - // All children should be at level 1 - for child in &children { - assert_eq!( - child.level(), - 1, - "Root node's direct children should be at level 1" - ); - } - - // Check that expected children are present - let child_names: alloc::vec::Vec<_> = children.iter().map(|c| c.name()).collect(); - assert!( - child_names.contains(&"chosen"), - "Should contain chosen node" - ); - assert!( - child_names.contains(&"memory@0"), - "Should contain memory@0 node" - ); - } - - #[test] - fn test_find_child_by_name() { - let dtb_data = include_bytes!("../../../../dtb-file/src/dtb/bcm2711-rpi-4-b.dtb"); - let fdt = Fdt::from_bytes(dtb_data).unwrap(); - - // Find the root node - let root_node = fdt.find_nodes("/").next().unwrap().unwrap(); - - // Test finding child by name - let memory_node = root_node.children().find_child_by_name("memory@0").unwrap(); - - assert_eq!(memory_node.name(), "memory@0"); - - // Test finding non-existent node - let nonexistent_err = root_node - .children() - .find_child_by_name("nonexistent") - .unwrap_err(); - assert!(matches!(nonexistent_err, FdtError::NotFound)); - } - - #[test] - fn test_child_iter_empty() { - let dtb_data = include_bytes!("../../../../dtb-file/src/dtb/bcm2711-rpi-4-b.dtb"); - let fdt = Fdt::from_bytes(dtb_data).unwrap(); - - // Find a leaf node (a node with no children) - let leaf_node = fdt.find_nodes("/chosen").next().unwrap().unwrap(); - - // Test leaf node's child iterator - let children: Result, _> = leaf_node.children().collect(); - let children = children.unwrap(); - - assert!(children.is_empty(), "Leaf node should not have children"); - } - - #[test] - fn test_child_iter_multiple_levels() { - let dtb_data = include_bytes!("../../../../dtb-file/src/dtb/bcm2711-rpi-4-b.dtb"); - let fdt = Fdt::from_bytes(dtb_data).unwrap(); - - // Find reserved-memory node, which should have children - let reserved_memory = fdt - .all_nodes() - .find(|node| node.as_ref().is_ok_and(|n| n.name() == "reserved-memory")) - .unwrap() - .unwrap(); - - // Test child node iterator - let children: Result, _> = reserved_memory.children().collect(); - let children = children.unwrap(); - - // Ensure children's level is correct - for child in &children { - assert_eq!( - child.level(), - reserved_memory.level() + 1, - "Child's level should be 1 higher than parent's level" - ); - } - } -} diff --git a/fdt-parser/src/cache/fdt.rs b/fdt-parser/src/cache/fdt.rs deleted file mode 100644 index b94cda3..0000000 --- a/fdt-parser/src/cache/fdt.rs +++ /dev/null @@ -1,261 +0,0 @@ -//! Cached FDT parser with indexed lookups. -//! -//! This module provides the `Fdt` type for the cached parser, which builds -//! internal indices for fast path-based and phandle-based node lookups. - -use alloc::{ - collections::{btree_map::BTreeMap, btree_set::BTreeSet}, - string::{String, ToString}, - sync::Arc, - vec::Vec, -}; - -use super::{Align4Vec, Node}; -use crate::{base, cache::NodeMeta, data::Raw, FdtError, Header, Phandle}; - -/// Cached Flattened Device Tree parser. -/// -/// This parser builds internal indices (path cache, phandle cache, compatible cache) -/// during construction, providing O(1) lookup time for subsequent queries. -/// It uses more memory than the base parser but is much faster for repeated lookups. -#[derive(Clone)] -pub struct Fdt { - pub(super) inner: Arc, -} - -impl Fdt { - /// Create a new `Fdt` from byte slice. - /// - /// This will parse the entire device tree and build internal indices. - pub fn from_bytes(data: &[u8]) -> Result { - let inner = Inner::new(data)?; - Ok(Self { - inner: Arc::new(inner), - }) - } - - /// Returns a slice of the underlying FDT data. - pub fn as_slice(&self) -> &[u8] { - &self.inner.raw - } - - /// Create a new `Fdt` from a raw pointer and size in bytes. - /// - /// # Safety - /// - /// The caller must ensure that the pointer is valid and points to a - /// memory region of at least `size` bytes that contains a valid device tree - /// blob. - pub unsafe fn from_ptr(ptr: *mut u8) -> Result { - let b = base::Fdt::from_ptr(ptr)?; - Self::from_bytes(b.raw()) - } - - pub(super) fn fdt_base<'a>(&'a self) -> base::Fdt<'a> { - base::Fdt::from_bytes(&self.inner.raw).unwrap() - } - - /// Get the FDT version. - pub fn version(&self) -> u32 { - self.fdt_base().version() - } - - /// Get the FDT header. - pub fn header(&self) -> Header { - self.fdt_base().header().clone() - } - - /// Get all nodes in the device tree. - pub fn all_nodes(&self) -> Vec { - self.inner - .all_nodes - .iter() - .map(|meta| Node::new(self, meta)) - .collect() - } - - /// Find nodes by path or alias. - /// - /// If path starts with '/' then search by path, else search by aliases. - pub fn find_nodes(&self, path: impl AsRef) -> Vec { - let path = path.as_ref(); - let path = if path.starts_with("/") { - path.to_string() - } else { - self.find_aliase(path).unwrap() - }; - let mut out = Vec::new(); - for node in self.all_nodes() { - if node.full_path().starts_with(path.as_str()) { - let right = node.full_path().trim_start_matches(&path); - if right.split("/").count() < 2 { - out.push(node); - } - } - } - - out - } - - /// Find an alias by name. - pub fn find_aliase(&self, name: impl AsRef) -> Option { - let fdt = self.fdt_base(); - let s = fdt.find_aliase(name.as_ref()).ok()?; - Some(s.into()) - } - - /// Get a node by its phandle (O(1) lookup). - pub fn get_node_by_phandle(&self, phandle: Phandle) -> Option { - let meta = self.inner.get_node_by_phandle(phandle)?; - Some(Node::new(self, &meta)) - } - - /// Find nodes with compatible strings matching the given list. - pub fn find_compatible(&self, with: &[&str]) -> Vec { - let mut ids = BTreeSet::new(); - for &c in with { - if let Some(s) = self.inner.compatible_cache.get(c) { - for n in s { - ids.insert(n); - } - } - } - let mut out = Vec::new(); - for id in ids { - if let Some(meta) = self.inner.get_node_by_index(*id) { - out.push(Node::new(self, &meta)); - } - } - - out - } - - /// Get all memory reservation blocks. - pub fn memory_reservation_blocks(&self) -> Vec { - let fdt = self.fdt_base(); - fdt.memory_reservation_blocks().collect() - } - - /// Get raw access to the FDT data. - pub fn raw<'a>(&'a self) -> Raw<'a> { - Raw::new(&self.inner.raw) - } - - /// Get a node by its path in the device tree (O(1) lookup). - pub fn get_node_by_path(&self, path: &str) -> Option { - let meta = self.inner.get_node_by_path(path)?; - Some(Node::new(self, &meta)) - } - - /// Get all memory nodes. - pub fn memory(&self) -> Result, FdtError> { - let nodes = self.find_nodes("/memory"); - let mut out = Vec::new(); - for node in nodes { - let super::Node::Memory(m) = node else { - return Err(FdtError::NodeNotFound("memory")); - }; - out.push(m); - } - Ok(out) - } -} - -/// Internal cached representation of the FDT. -/// -/// Contains the raw FDT data plus various indices for fast lookups. -pub(super) struct Inner { - raw: Align4Vec, - phandle_cache: BTreeMap, - /// compatible -> set(name) - compatible_cache: BTreeMap>, - /// same order as all_nodes() - all_nodes: Vec, - path_cache: BTreeMap, -} - -unsafe impl Send for Inner {} -unsafe impl Sync for Inner {} - -impl Inner { - /// Build the cached representation from raw FDT data. - fn new(data: &[u8]) -> Result { - let b = base::Fdt::from_bytes(data)?; - let mut inner = Inner { - raw: Align4Vec::new(data), - phandle_cache: BTreeMap::new(), - compatible_cache: BTreeMap::new(), - all_nodes: Vec::new(), - path_cache: BTreeMap::new(), - }; - let mut node_vec = Vec::new(); - let mut path_stack = Vec::new(); - let mut node_stack: Vec = Vec::new(); - for (i, node) in b.all_nodes().enumerate() { - let node = node?; - let node_name = node.name(); - let level = node.level(); - - while let Some(last) = node_stack.last() { - if level <= last.level { - node_stack.pop(); - } else { - break; - } - } - - if level < path_stack.len() { - path_stack.truncate(level); - } - path_stack.push(node_name.trim_start_matches("/")); - let full_path = if path_stack.len() > 1 { - alloc::format!("/{}", path_stack[1..].join("/")) - } else { - "/".to_string() - }; - for prop in node.properties() { - let _ = prop?; - } - let parent = node_stack.last(); - let dnode = NodeMeta::new(&node, full_path.clone(), parent); - node_stack.push(dnode.clone()); - inner.all_nodes.push(dnode.clone()); - inner.path_cache.insert(full_path, i); - - match node.phandle() { - Ok(phandle) => { - inner.phandle_cache.entry(phandle).or_insert(i); - } - Err(FdtError::NotFound) => {} - Err(e) => return Err(e), - } - match node.compatibles_flatten() { - Ok(iter) => { - for compatible in iter { - let map = inner.compatible_cache.entry(compatible.into()).or_default(); - map.insert(i); - } - } - Err(FdtError::NotFound) => {} - Err(e) => return Err(e), - } - node_vec.push(node); - } - - Ok(inner) - } - - pub(crate) fn get_node_by_path(&self, path: &str) -> Option { - let idx = self.path_cache.get(path)?; - Some(self.all_nodes[*idx].clone()) - } - - fn get_node_by_index(&self, index: usize) -> Option { - self.all_nodes.get(index).cloned() - } - - fn get_node_by_phandle(&self, phandle: Phandle) -> Option { - let idx = self.phandle_cache.get(&phandle)?; - Some(self.all_nodes[*idx].clone()) - } -} diff --git a/fdt-parser/src/cache/mod.rs b/fdt-parser/src/cache/mod.rs deleted file mode 100644 index 5c98032..0000000 --- a/fdt-parser/src/cache/mod.rs +++ /dev/null @@ -1,53 +0,0 @@ -//! Cached FDT parser with indexed nodes for efficient lookups. -//! -//! This module provides a cached representation of the device tree that -//! builds an index for fast repeated lookups. It uses more memory than the -//! direct parser but provides O(1) node access by path or phandle. - -mod fdt; -mod node; - -use core::ops::Deref; - -pub use fdt::*; -pub use node::*; - -/// A 4-byte aligned buffer for storing FDT data. -/// -/// The Device Tree Blob specification requires 4-byte alignment, -/// and this wrapper ensures the allocated memory meets that requirement. -struct Align4Vec { - ptr: *mut u8, - size: usize, -} - -unsafe impl Send for Align4Vec {} - -impl Align4Vec { - const ALIGN: usize = 4; - - /// Creates a new 4-byte aligned buffer containing the provided data. - pub fn new(data: &[u8]) -> Self { - let size = data.len(); - let layout = core::alloc::Layout::from_size_align(size, Self::ALIGN).unwrap(); - let ptr = unsafe { alloc::alloc::alloc_zeroed(layout) }; - unsafe { core::ptr::copy_nonoverlapping(data.as_ptr(), ptr, size) }; - Align4Vec { ptr, size } - } -} - -impl Drop for Align4Vec { - /// Deallocates the aligned buffer when dropped. - fn drop(&mut self) { - let layout = core::alloc::Layout::from_size_align(self.size, Self::ALIGN).unwrap(); - unsafe { alloc::alloc::dealloc(self.ptr, layout) }; - } -} - -impl Deref for Align4Vec { - type Target = [u8]; - - fn deref(&self) -> &Self::Target { - unsafe { alloc::slice::from_raw_parts(self.ptr, self.size) } - } -} diff --git a/fdt-parser/src/cache/node/chosen.rs b/fdt-parser/src/cache/node/chosen.rs deleted file mode 100644 index 4814be5..0000000 --- a/fdt-parser/src/cache/node/chosen.rs +++ /dev/null @@ -1,180 +0,0 @@ -use core::{fmt::Debug, ops::Deref}; - -use crate::cache::node::NodeBase; -use alloc::{string::String, string::ToString}; - -/// The /chosen node containing boot parameters (cached version). -/// -/// The chosen node doesn't represent any actual hardware device but serves -/// as a place to pass parameters to the operating system or bootloader. -#[derive(Clone)] -pub struct Chosen { - node: NodeBase, -} - -impl Chosen { - pub(crate) fn new(node: NodeBase) -> Self { - Chosen { node } - } - - /// Contains the bootargs, if they exist - pub fn bootargs(&self) -> Option { - self.node - .find_property("bootargs") - .and_then(|prop| prop.str().ok()) - .map(|s| s.to_string()) - } - - /// Searches for the node representing `stdout`, if the property exists, - /// attempting to resolve aliases if the node name doesn't exist as-is - pub fn stdout(&self) -> Option { - let prop = self.node.find_property("stdout-path")?; - let path = prop.str().ok()?; - - let mut sp = path.split(':'); - let name = sp.next()?; - let params = sp.next(); - - // Try to find the node in the cache - self.node.fdt.get_node_by_path(name).map(|node| Stdout { - params: params.map(|s| s.to_string()), - node, - }) - } - - /// Get the debug console information. - /// - /// First tries to find the stdout node. If that fails, parses the - /// bootargs for earlycon configuration. - pub fn debugcon(&self) -> Option { - if let Some(stdout) = self.stdout() { - Some(DebugConCache::Node(stdout.node)) - } else { - self.fdt_bootargs_find_debugcon_info() - } - } - - fn fdt_bootargs_find_debugcon_info(&self) -> Option { - let bootargs = self.bootargs()?; - - // Look for earlycon parameter - let earlycon = bootargs - .split_ascii_whitespace() - .find(|arg| arg.contains("earlycon"))?; - - let mut tmp = earlycon.split('='); - let _ = tmp.next()?; // skip "earlycon" - let values = tmp.next()?; - - // Parse all parameters - let mut params_iter = values.split(','); - let name = params_iter.next()?; - - if !name.contains("uart") { - return None; - } - - let param2 = params_iter.next()?; - - let addr_str = if param2.contains("0x") { - param2 - } else { - params_iter.next()? - }; - - let mmio = u64::from_str_radix(addr_str.trim_start_matches("0x"), 16).ok()?; - - // Try to find the corresponding node in the cache first - let all_nodes = self.node.fdt.all_nodes(); - for node in all_nodes { - let Ok(reg) = node.reg() else { - continue; - }; - - for address in reg { - if address.address == mmio { - return Some(DebugConCache::Node(node)); - } - } - } - - // If no matching node is found, return the parsed earlycon information - // Re-split the string to get remaining parameters - let mut parts = values.split(','); - let _name = parts.next(); // skip name - let _addr_part = parts.next(); // skip address part - let params = if let Some(param) = parts.next() { - // Get the position of the first remaining parameter, then take all remaining content - let param_start = values.find(param).unwrap_or(0); - if param_start > 0 { - Some(values[param_start..].to_string()) - } else { - Some(param.to_string()) - } - } else { - None - }; - - Some(DebugConCache::EarlyConInfo { - name: name.to_string(), - mmio, - params, - }) - } -} - -impl Debug for Chosen { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.debug_struct("Chosen") - .field("bootargs", &self.bootargs()) - .field("stdout", &self.stdout()) - .finish() - } -} - -impl Deref for Chosen { - type Target = NodeBase; - - fn deref(&self) -> &Self::Target { - &self.node - } -} - -/// Result of debug console lookup for the cached parser. -#[derive(Clone, Debug)] -pub enum DebugConCache { - /// Found the corresponding device tree node - Node(super::super::Node), - /// Found earlycon parameter only in bootargs, with parsed information - EarlyConInfo { - /// The name of the early console device (e.g., "uart8250") - name: String, - /// The MMIO address of the device - mmio: u64, - /// Additional parameters for the early console - params: Option, - }, -} - -/// The stdout device specified by the chosen node (cached version). -/// -/// Contains the node reference and optional parameters (typically specifying -/// the baud rate or other console configuration). -#[derive(Clone)] -pub struct Stdout { - /// Optional parameters for the stdout device (e.g., baud rate) - pub params: Option, - /// The device tree node for the stdout device - pub node: super::super::Node, -} - -impl Stdout {} - -impl Debug for Stdout { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.debug_struct("Stdout") - .field("name", &self.node.name()) - .field("params", &self.params) - .finish() - } -} diff --git a/fdt-parser/src/cache/node/clock.rs b/fdt-parser/src/cache/node/clock.rs deleted file mode 100644 index 13a5f68..0000000 --- a/fdt-parser/src/cache/node/clock.rs +++ /dev/null @@ -1,143 +0,0 @@ -use core::ops::Deref; - -use crate::{cache::node::NodeBase, Phandle}; -use alloc::{string::String, string::ToString, vec::Vec}; - -/// Information about a clock connection between a consumer and provider. -#[derive(Clone, Debug)] -pub struct ClockInfo { - /// Name supplied by the consumer through `clock-names` - pub name: Option, - /// Name exposed by the provider via `clock-output-names` that matches the specifier - pub provider_output_name: Option, - - /// The phandle of the clock provider - pub phandle: Phandle, - /// The clock specifier/index value - pub select: u64, - /// Provider details - pub provider: ClockType, -} - -impl ClockInfo { - /// Helper access to the provider node - pub fn provider_name(&self) -> &str { - self.provider.name() - } - - /// Number of cells defined by the provider for each specifier - pub fn provider_clock_cells(&self) -> u32 { - self.provider.clock_cells() - } -} - -/// The type of clock provider. -#[derive(Clone, Debug)] -pub enum ClockType { - /// A fixed clock with a constant frequency - Fixed(FixedClock), - /// A general clock provider - Provider(Clock), -} - -impl ClockType { - pub(super) fn new(node: NodeBase) -> Self { - let base = Clock::from_node(node.clone()); - let compatibles = node.compatibles(); - if compatibles.iter().any(|c| c == "fixed-clock") { - ClockType::Fixed(FixedClock { - clock: base, - frequency: node - .find_property("clock-frequency") - .and_then(|p| p.u32().ok()), - accuracy: node - .find_property("clock-accuracy") - .and_then(|p| p.u32().ok()), - }) - } else { - ClockType::Provider(base) - } - } - - /// Get the number of clock cells for this clock type. - pub fn clock_cells(&self) -> u32 { - match self { - ClockType::Fixed(fixed) => fixed.clock.clock_cells, - ClockType::Provider(clock) => clock.clock_cells, - } - } - - /// Get the output name for the given clock selector. - pub fn output_name(&self, select: u64) -> Option { - match self { - ClockType::Fixed(fixed) => fixed.clock.output_name(select), - ClockType::Provider(clock) => clock.output_name(select), - } - } -} - -impl Deref for ClockType { - type Target = NodeBase; - - fn deref(&self) -> &Self::Target { - match self { - ClockType::Fixed(fixed) => &fixed.clock.node, - ClockType::Provider(clock) => &clock.node, - } - } -} - -/// A fixed clock with a constant frequency. -#[derive(Clone, Debug)] -pub struct FixedClock { - /// The clock provider node - pub clock: Clock, - /// The fixed frequency in Hz - pub frequency: Option, - /// The clock accuracy in ppb (parts per billion) - pub accuracy: Option, -} - -/// A clock provider node. -#[derive(Clone, Debug)] -pub struct Clock { - /// The device tree node for this clock - pub node: NodeBase, - /// The value of #clock-cells property - pub clock_cells: u32, - /// The names of the clock outputs - pub output_names: Vec, -} - -impl Clock { - pub(crate) fn from_node(node: NodeBase) -> Self { - let clock_cells = node - .find_property("#clock-cells") - .and_then(|p| p.u32().ok()) - .unwrap_or(0); - let output_names = node - .find_property("clock-output-names") - .map(|p| p.str_list().map(|s| s.to_string()).collect()) - .unwrap_or_default(); - - Self { - node, - clock_cells, - output_names, - } - } - - /// Get the output name for the given clock selector. - pub fn output_name(&self, select: u64) -> Option { - if self.output_names.is_empty() { - return None; - } - - if self.clock_cells == 0 { - return self.output_names.first().cloned(); - } - - let index = select as usize; - self.output_names.get(index).cloned() - } -} diff --git a/fdt-parser/src/cache/node/interrupt_controller.rs b/fdt-parser/src/cache/node/interrupt_controller.rs deleted file mode 100644 index 5434f3d..0000000 --- a/fdt-parser/src/cache/node/interrupt_controller.rs +++ /dev/null @@ -1,40 +0,0 @@ -use core::{fmt::Debug, ops::Deref}; - -use crate::{cache::node::NodeBase, FdtError}; - -/// An interrupt controller node (cached version). -#[derive(Clone)] -pub struct InterruptController { - node: NodeBase, -} - -impl InterruptController { - pub(crate) fn new(node: NodeBase) -> Self { - InterruptController { node } - } - - /// Get the number of interrupt cells this controller uses - pub fn interrupt_cells(&self) -> Result { - match self.node.find_property("#interrupt-cells") { - Some(prop) => prop.u32(), - None => Err(FdtError::PropertyNotFound("#interrupt-cells")), - } - } -} - -impl Debug for InterruptController { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.debug_struct("InterruptController") - .field("name", &self.node.name()) - .field("interrupt_cells", &self.interrupt_cells()) - .finish() - } -} - -impl Deref for InterruptController { - type Target = NodeBase; - - fn deref(&self) -> &Self::Target { - &self.node - } -} diff --git a/fdt-parser/src/cache/node/memory.rs b/fdt-parser/src/cache/node/memory.rs deleted file mode 100644 index 65331d3..0000000 --- a/fdt-parser/src/cache/node/memory.rs +++ /dev/null @@ -1,45 +0,0 @@ -use core::{fmt::Debug, ops::Deref}; - -use crate::{cache::node::NodeBase, FdtError, MemoryRegion}; -use alloc::vec::Vec; - -/// A memory node (cached version). -#[derive(Clone)] -pub struct Memory { - node: NodeBase, -} - -impl Memory { - pub(crate) fn new(node: NodeBase) -> Self { - Memory { node } - } - - /// Get the memory regions defined by this memory node - pub fn regions(&self) -> Result, FdtError> { - let reg = self.node.reg()?; - let mut out = Vec::new(); - for r in reg { - out.push(MemoryRegion { - address: r.address as usize as _, - size: r.size.unwrap_or_default(), - }); - } - Ok(out) - } -} - -impl Debug for Memory { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.debug_struct("Memory") - .field("name", &self.node.name()) - .finish() - } -} - -impl Deref for Memory { - type Target = NodeBase; - - fn deref(&self) -> &Self::Target { - &self.node - } -} diff --git a/fdt-parser/src/cache/node/mod.rs b/fdt-parser/src/cache/node/mod.rs deleted file mode 100644 index a246582..0000000 --- a/fdt-parser/src/cache/node/mod.rs +++ /dev/null @@ -1,380 +0,0 @@ -//! Cached node types with specialized accessors. -//! -//! This module provides the `Node` enum and related types for the cached parser. -//! Nodes are automatically classified into specialized types based on their properties. - -use core::{fmt::Debug, ops::Deref}; - -use super::Fdt; -use crate::{ - base::{self, RegIter}, - data::{Raw, U32Iter2D}, - property::PropIter, - FdtError, FdtRangeSilce, FdtReg, Phandle, Property, Status, -}; - -use alloc::{ - string::{String, ToString}, - vec::Vec, -}; - -mod chosen; -mod clock; -mod interrupt_controller; -mod memory; -mod pci; - -pub use chosen::*; -pub use clock::*; -pub use interrupt_controller::*; -pub use memory::*; -pub use pci::*; - -/// Typed node enum for specialized node access. -/// -/// Nodes are automatically classified based on their name and properties. -/// Use pattern matching to access node-specific functionality. -#[derive(Debug, Clone)] -pub enum Node { - /// A general-purpose node without special handling - General(NodeBase), - /// The /chosen node containing boot parameters - Chosen(Chosen), - /// A memory node (e.g., /memory@0) - Memory(Memory), - /// An interrupt controller node - InterruptController(InterruptController), - /// A PCI host bridge node - Pci(Pci), -} - -impl Node { - pub(super) fn new(fdt: &Fdt, meta: &NodeMeta) -> Self { - let base = NodeBase { - fdt: fdt.clone(), - meta: meta.clone(), - }; - - // Create specific type based on node type - match meta.name.as_str() { - "chosen" => Self::Chosen(Chosen::new(base)), - name if name.starts_with("memory@") => Self::Memory(Memory::new(base)), - _ => { - // Check if this is a PCI node - let pci = Pci::new(base.clone()); - if pci.is_pci_host_bridge() { - Self::Pci(pci) - } else if base.is_interrupt_controller() { - Self::InterruptController(InterruptController::new(base)) - } else { - Self::General(base) - } - } - } - } -} - -impl Deref for Node { - type Target = NodeBase; - - fn deref(&self) -> &Self::Target { - match self { - Node::General(n) => n, - Node::Chosen(n) => n, - Node::Memory(n) => n, - Node::InterruptController(n) => n, - Node::Pci(n) => n, - } - } -} - -/// Base node type for cached parser nodes. -/// -/// `NodeBase` provides common functionality available on all nodes, -/// with fast lookups using the cached indices. -#[derive(Clone)] -pub struct NodeBase { - fdt: Fdt, - meta: NodeMeta, -} - -impl NodeBase { - fn raw<'a>(&'a self) -> Raw<'a> { - self.fdt.raw().begin_at(self.meta.pos) - } - - /// Get the level/depth of this node in the device tree. - pub fn level(&self) -> usize { - self.meta.level - } - - /// Get the name of this node. - pub fn name(&self) -> &str { - &self.meta.name - } - - /// Get the full path of this node. - pub fn full_path(&self) -> &str { - &self.meta.full_path - } - - /// Get the parent node. - pub fn parent(&self) -> Option { - let parent_path = self.meta.parent.as_ref()?.path.as_str(); - let parent_meta = self.fdt.inner.get_node_by_path(parent_path)?; - Some(Node::new(&self.fdt, &parent_meta)) - } - - /// Get all properties of this node. - pub fn properties<'a>(&'a self) -> Vec> { - let reader = self.raw().buffer(); - PropIter::new(self.fdt.fdt_base(), reader) - .flatten() - .collect() - } - - /// Find a property by name. - pub fn find_property<'a>(&'a self, name: impl AsRef) -> Option> { - self.properties() - .into_iter() - .find(|prop| prop.name == name.as_ref()) - } - - /// Get compatible strings for this node (placeholder implementation). - pub fn compatibles(&self) -> Vec { - self.find_property("compatible") - .map(|p| { - p.str_list() - .filter(|s| !s.is_empty()) - .map(|s| s.into()) - .collect() - }) - .unwrap_or_default() - } - - /// Get the status of this node. - pub fn status(&self) -> Option { - self.find_property("status") - .and_then(|prop| prop.str().ok()) - .and_then(|s| { - if s.contains("disabled") { - Some(Status::Disabled) - } else if s.contains("okay") { - Some(Status::Okay) - } else { - None - } - }) - } - - /// Get the #address-cells value for this node. - pub fn address_cells(&self) -> u8 { - self.find_property("#address-cells") - .and_then(|p| p.u32().ok()) - .map(|v| v as u8) - .or_else(|| { - self.meta - .parent - .as_ref() - .and_then(|info| info.address_cells) - }) - .unwrap_or(2) - } - - fn is_interrupt_controller(&self) -> bool { - self.name().starts_with("interrupt-controller") - || self.find_property("interrupt-controller").is_some() - || self.find_property("#interrupt-controller").is_some() - } - - /// Get register information for this node. - /// - /// Returns a vector of register entries with addresses translated - /// to the parent bus address space. - pub fn reg(&self) -> Result, FdtError> { - let prop = self.find_property("reg").ok_or(FdtError::NotFound)?; - - // Get parent info from ParentInfo structure - let parent_info = self - .meta - .parent - .as_ref() - .ok_or(FdtError::NodeNotFound("parent"))?; - - // reg parsing uses the immediate parent's cells - let address_cell = parent_info.address_cells.unwrap_or(2); - let size_cell = parent_info.size_cells.unwrap_or(1); - - let parent = self.parent().ok_or(FdtError::NodeNotFound("parent"))?; - let ranges = parent.ranges(); - let iter = RegIter { - size_cell, - address_cell, - buff: prop.data.buffer(), - ranges, - }; - - Ok(iter.collect()) - } - - /// Get the ranges property for address translation. - pub fn ranges(&self) -> Option> { - let p = self.find_property("ranges")?; - let parent_info = self.meta.parent.as_ref(); - - let address_cell = self - .find_property("#address-cells") - .and_then(|prop| prop.u32().ok()) - .map(|v| v as u8) - .or_else(|| parent_info.and_then(|info| info.address_cells)) - .unwrap_or(2); - - let size_cell = self - .find_property("#size-cells") - .and_then(|prop| prop.u32().ok()) - .map(|v| v as u8) - .or_else(|| parent_info.and_then(|info| info.size_cells)) - .unwrap_or(1); - - let address_cell_parent = parent_info.and_then(|info| info.address_cells).unwrap_or(2); - - Some(FdtRangeSilce::new( - address_cell, - address_cell_parent, - size_cell, - &p.data, - )) - } - - /// Get the interrupt parent phandle for this node. - pub fn interrupt_parent_phandle(&self) -> Option { - self.meta.interrupt_parent - } - - /// Get the interrupt parent node. - pub fn interrupt_parent(&self) -> Option { - let phandle = self.interrupt_parent_phandle()?; - let irq = self.fdt.get_node_by_phandle(phandle)?; - let Node::InterruptController(i) = irq else { - return None; - }; - Some(i) - } - - /// Get the interrupts for this node. - pub fn interrupts(&self) -> Result>, FdtError> { - let res = self - .find_property("interrupts") - .ok_or(FdtError::PropertyNotFound("interrupts"))?; - let parent = self - .interrupt_parent() - .ok_or(FdtError::PropertyNotFound("interrupt-parent"))?; - let cells = parent.interrupt_cells()?; - let iter = U32Iter2D::new(&res.data, cells as _); - let mut out = Vec::new(); - for entry in iter { - out.push(entry.collect()); - } - Ok(out) - } - - /// Get the clocks used by this node following the Devicetree clock binding. - pub fn clocks(&self) -> Result, FdtError> { - let mut clocks = Vec::new(); - let Some(prop) = self.find_property("clocks") else { - return Ok(clocks); - }; - - let mut data = prop.data.buffer(); - let clock_names: Vec = self - .find_property("clock-names") - .map(|p| p.str_list().map(|s| s.to_string()).collect()) - .unwrap_or_default(); - - let mut index = 0usize; - while !data.remain().as_ref().is_empty() { - let phandle_raw = data.take_u32()?; - let phandle = Phandle::from(phandle_raw); - - let provider = self - .fdt - .get_node_by_phandle(phandle) - .ok_or(FdtError::NodeNotFound("clock"))?; - - let provider_node = provider.deref().clone(); - let clock_cells = provider_node - .find_property("#clock-cells") - .and_then(|p| p.u32().ok()) - .unwrap_or(0); - let select = if clock_cells > 0 { - data.take_by_cell_size(clock_cells as _) - .ok_or(FdtError::BufferTooSmall { pos: data.pos() })? - } else { - 0 - }; - - let provider = ClockType::new(provider_node); - let provider_output_name = provider.output_name(select); - let name = clock_names.get(index).cloned(); - - clocks.push(ClockInfo { - name, - provider_output_name, - provider, - phandle, - select, - }); - - index += 1; - } - - Ok(clocks) - } -} - -impl Debug for NodeBase { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - let mut st = f.debug_struct("NodeBase"); - // st.field("name", &self.name()); - st.finish() - } -} - -/// Metadata for a cached node. -/// -/// Contains precomputed information about the node for fast access. -#[derive(Clone)] -pub(super) struct NodeMeta { - name: String, - full_path: String, - pos: usize, - pub level: usize, - interrupt_parent: Option, - parent: Option, -} - -impl NodeMeta { - /// Create node metadata from a base parser node. - pub fn new(node: &base::Node<'_>, full_path: String, parent: Option<&NodeMeta>) -> Self { - NodeMeta { - full_path, - name: node.name().into(), - pos: node.raw.pos(), - level: node.level(), - interrupt_parent: node.get_interrupt_parent_phandle(), - parent: node.parent.as_ref().map(|p| ParentInfo { - path: parent.map(|n| n.full_path.clone()).unwrap_or_default(), - address_cells: p.address_cells, - size_cells: p.size_cells, - }), - } - } -} - -/// Information about a node's parent. -#[derive(Clone)] -struct ParentInfo { - path: String, - address_cells: Option, - size_cells: Option, -} diff --git a/fdt-parser/src/cache/node/pci.rs b/fdt-parser/src/cache/node/pci.rs deleted file mode 100644 index b85631d..0000000 --- a/fdt-parser/src/cache/node/pci.rs +++ /dev/null @@ -1,485 +0,0 @@ -use core::{ - fmt::Debug, - ops::{Deref, Range}, -}; - -use crate::{cache::node::NodeBase, FdtError, Phandle}; -use alloc::{vec, vec::Vec}; - -/// PCI address space type. -#[derive(Clone, Debug, PartialEq)] -pub enum PciSpace { - /// I/O space - IO, - /// 32-bit memory space - Memory32, - /// 64-bit memory space - Memory64, -} - -/// A PCI address range for address translation. -#[derive(Clone, Debug, PartialEq)] -pub struct PciRange { - /// The address space type - pub space: PciSpace, - /// The address on the PCI bus - pub bus_address: u64, - /// The address in CPU physical memory - pub cpu_address: u64, - /// The size of the range - pub size: u64, - /// Whether the memory is prefetchable - pub prefetchable: bool, -} - -/// A PCI interrupt mapping entry from the interrupt-map property. -#[derive(Clone, Debug)] -pub struct PciInterruptMap { - /// The child device address (masked) - pub child_address: Vec, - /// The child interrupt pin (masked) - pub child_irq: Vec, - /// The phandle of the interrupt parent controller - pub interrupt_parent: Phandle, - /// The interrupt specifier for the parent controller - pub parent_irq: Vec, -} - -/// Interrupt information for a PCI device. -#[derive(Clone, Debug, PartialEq)] -pub struct PciInterruptInfo { - /// The interrupt lines/numbers for this device - pub irqs: Vec, -} - -/// A PCI device tree node. -#[derive(Clone)] -pub struct Pci { - node: NodeBase, -} - -impl Pci { - pub(crate) fn new(node: NodeBase) -> Self { - Pci { node } - } - - /// Get the number of interrupt cells for PCI devices. - pub fn interrupt_cells(&self) -> u32 { - self.find_property("#interrupt-cells") - .and_then(|prop| prop.u32().ok()) - .unwrap_or(1) // Default to 1 interrupt cell for PCI - } - - /// Get the interrupt-map-mask property if present - pub fn interrupt_map_mask(&self) -> Option> { - self.node - .find_property("interrupt-map-mask") - .and_then(|prop| { - let mut data = prop.data.buffer(); - let mut mask = Vec::new(); - while !data.remain().as_ref().is_empty() { - match data.take_u32() { - Ok(value) => mask.push(value), - Err(_) => return None, - } - } - Some(mask) - }) - } - - /// Parse the interrupt-map property into a structured format - pub fn interrupt_map(&self) -> Result, FdtError> { - let prop = self - .node - .find_property("interrupt-map") - .ok_or(FdtError::PropertyNotFound("interrupt-map"))?; - - let mut mask = self - .interrupt_map_mask() - .ok_or(FdtError::PropertyNotFound("interrupt-map-mask"))?; - - let mut data = prop.data.buffer(); - let mut mappings = Vec::new(); - - // Calculate the size of each entry in interrupt-map - // Format: - let child_addr_cells = self.address_cells() as usize; - let child_irq_cells = self.interrupt_cells() as usize; - - let required_mask_len = child_addr_cells + child_irq_cells; - if mask.len() < required_mask_len { - mask.resize(required_mask_len, 0xffff_ffff); - } - - while !data.remain().as_ref().is_empty() { - // Parse child address (variable number of cells for PCI) - let mut child_address = Vec::with_capacity(child_addr_cells); - for _ in 0..child_addr_cells { - child_address.push( - data.take_u32() - .map_err(|_| FdtError::BufferTooSmall { pos: data.pos() })?, - ); - } - - // Parse child IRQ (usually 1 cell for PCI) - let mut child_irq = Vec::with_capacity(child_irq_cells); - for _ in 0..child_irq_cells { - child_irq.push( - data.take_u32() - .map_err(|_| FdtError::BufferTooSmall { pos: data.pos() })?, - ); - } - - // Parse interrupt parent phandle - let interrupt_parent_raw = data - .take_u32() - .map_err(|_| FdtError::BufferTooSmall { pos: data.pos() })?; - let interrupt_parent = if interrupt_parent_raw == 0 { - self.interrupt_parent_phandle().unwrap_or(Phandle::from(0)) - } else { - Phandle::from(interrupt_parent_raw) - }; - - let irq_parent = self - .node - .interrupt_parent() - .ok_or(FdtError::NodeNotFound("interrupt-parent"))?; - - let address_cells = irq_parent.address_cells(); - - for _ in 0..address_cells { - data.take_u32() - .map_err(|_| FdtError::BufferTooSmall { pos: data.pos() })?; - } - - let parent_irq_cells = irq_parent.interrupt_cells()? as usize; - - // Parse parent IRQ (variable number of cells) - let mut parent_irq = Vec::with_capacity(parent_irq_cells); - for _ in 0..parent_irq_cells { - let irq = data - .take_u32() - .map_err(|_| FdtError::BufferTooSmall { pos: data.pos() })?; - parent_irq.push(irq); - } - - // Apply mask to child address and IRQ - let masked_address: Vec = child_address - .iter() - .enumerate() - .map(|(idx, value)| { - let mask_value = mask.get(idx).copied().unwrap_or(0xffff_ffff); - value & mask_value - }) - .collect(); - let masked_irq: Vec = child_irq - .iter() - .enumerate() - .map(|(idx, value)| { - let mask_value = mask - .get(child_addr_cells + idx) - .copied() - .unwrap_or(0xffff_ffff); - value & mask_value - }) - .collect(); - - mappings.push(PciInterruptMap { - child_address: masked_address, - child_irq: masked_irq, - interrupt_parent, - parent_irq, - }); - } - - Ok(mappings) - } - - /// Get the bus range property if present - pub fn bus_range(&self) -> Option> { - self.node.find_property("bus-range").and_then(|prop| { - let mut data = prop.data.buffer(); - let start = data.take_u32().ok()?; - let end = data.take_u32().unwrap_or(start); - Some(start..end) - }) - } - - /// Get the device_type property (should be "pci" for PCI nodes) - pub fn device_type(&self) -> Option<&str> { - self.node - .find_property("device_type") - .and_then(|prop| prop.str().ok()) - } - - /// Check if this is a PCI host bridge - pub fn is_pci_host_bridge(&self) -> bool { - self.device_type() == Some("pci") - || self.node.name().contains("pci") - || self.node.compatibles().iter().any(|c| c.contains("pci")) - } - - /// Get the ranges property for address translation - pub fn ranges(&self) -> Option> { - let prop = self.node.find_property("ranges")?; - let mut data = prop.data.buffer(); - let mut ranges = Vec::new(); - - // PCI ranges format: - // child-bus-address: 3 cells (pci.hi pci.mid pci.lo) - // parent-bus-address: 2 cells for 64-bit systems (high, low) - // size: 2 cells for 64-bit sizes (high, low) - while !data.remain().as_ref().is_empty() { - // Parse child bus address (3 cells for PCI) - let mut child_addr = [0u32; 3]; - for addr in child_addr.iter_mut() { - *addr = data.take_u32().ok()?; - } - - // Parse parent bus address (2 cells for 64-bit) - let parent_addr_high = data.take_u32().ok()?; - let parent_addr_low = data.take_u32().ok()?; - let parent_addr = ((parent_addr_high as u64) << 32) | (parent_addr_low as u64); - - // Parse size (2 cells for 64-bit) - let size_high = data.take_u32().ok()?; - let size_low = data.take_u32().ok()?; - let size = ((size_high as u64) << 32) | (size_low as u64); - - // Extract PCI address space and prefetchable from child_addr[0] - let pci_hi = child_addr[0]; - let (space, prefetchable) = self.decode_pci_address_space(pci_hi); - - // Calculate bus address from child_addr[1:2] - let bus_address = ((child_addr[1] as u64) << 32) | (child_addr[2] as u64); - - ranges.push(PciRange { - space, - bus_address, - cpu_address: parent_addr, - size, - prefetchable, - }); - } - - Some(ranges) - } - - /// Decode PCI address space from the high cell of PCI address - fn decode_pci_address_space(&self, pci_hi: u32) -> (PciSpace, bool) { - // PCI address high cell format: - // Bits 31-28: 1 for IO space, 2 for Memory32, 3 for Memory64 - // Bit 30: Prefetchable for memory spaces - let space_code = (pci_hi >> 24) & 0x03; - let prefetchable = (pci_hi >> 30) & 0x01 == 1; - - let space = match space_code { - 1 => PciSpace::IO, - 2 => PciSpace::Memory32, - 3 => PciSpace::Memory64, - _ => PciSpace::Memory32, // Default fallback - }; - - (space, prefetchable) - } - - /// Get interrupt information for a specific PCI device - /// Parameters: bus, device, function, pin (0=INTA, 1=INTB, 2=INTC, 3=INTD) - pub fn child_interrupts( - &self, - bus: u32, - device: u32, - function: u32, - pin: u32, - ) -> Result { - // Try to get interrupt-map and mask, fall back to simpler approach if parsing fails - let interrupt_map = self.interrupt_map()?; - - let mut mask = self - .interrupt_map_mask() - .ok_or(FdtError::PropertyNotFound("interrupt-map-mask"))?; - - // Construct the child address for PCI device - // Format: [bus_num, device_num, func_num] in appropriate bits - let child_addr_high = - ((bus & 0xff) << 16) | ((device & 0x1f) << 11) | ((function & 0x7) << 8); - let child_addr_mid = 0; - let child_addr_low = 0; - - let child_addr_cells = self.address_cells() as usize; - let child_irq_cells = self.interrupt_cells() as usize; - let required_mask_len = child_addr_cells + child_irq_cells; - if mask.len() < required_mask_len { - mask.resize(required_mask_len, 0xffff_ffff); - } - - let encoded_address = [child_addr_high, child_addr_mid, child_addr_low]; - let mut masked_child_address = Vec::with_capacity(child_addr_cells); - for (idx, mask) in mask.iter().enumerate().take(child_addr_cells) { - let value = *encoded_address.get(idx).unwrap_or(&0); - masked_child_address.push(value & mask); - } - - let encoded_irq = [pin]; - let mut masked_child_irq = Vec::with_capacity(child_irq_cells); - for idx in 0..child_irq_cells { - let value = *encoded_irq.get(idx).unwrap_or(&0); - masked_child_irq.push(value & mask[child_addr_cells + idx]); - } - - // Look for matching entry in interrupt-map - for mapping in &interrupt_map { - // Check if this mapping matches our masked address and pin - if mapping.child_address == masked_child_address - && mapping.child_irq == masked_child_irq - { - return Ok(PciInterruptInfo { - irqs: mapping.parent_irq.clone(), - }); - } - } - let simple_irq = (device * 4 + pin) % 32; - Ok(PciInterruptInfo { - irqs: vec![simple_irq], - }) - } -} - -impl Debug for Pci { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.debug_struct("Pci") - .field("name", &self.node.name()) - .field("is_pci_host_bridge", &self.is_pci_host_bridge()) - .field("bus_range", &self.bus_range()) - .field("interrupt_map_mask", &self.interrupt_map_mask()) - .finish() - } -} - -impl Deref for Pci { - type Target = NodeBase; - - fn deref(&self) -> &Self::Target { - &self.node - } -} - -#[cfg(test)] -mod tests { - use crate::{cache::node::Node, Fdt}; - - #[test] - fn test_pci_node_detection() { - let dtb_data = include_bytes!("../../../../dtb-file/src/dtb/qemu_pci.dtb"); - let fdt = Fdt::from_bytes(dtb_data).unwrap(); - - // Try to find PCI nodes - let mut pci_nodes_found = 0; - for node in fdt.find_nodes("/") { - { - if let Node::Pci(pci) = node { - pci_nodes_found += 1; - // println!("Found PCI node: {}", pci.name()); - assert!(pci.is_pci_host_bridge()); - } - } - } - - // We should find at least one PCI node in the qemu PCI test file - assert!(pci_nodes_found > 0, "Should find at least one PCI node"); - } - - #[test] - fn test_interrupt_map_parsing() { - let dtb_data = include_bytes!("../../../../dtb-file/src/dtb/qemu_pci.dtb"); - let fdt = Fdt::from_bytes(dtb_data).unwrap(); - - // Look for a PCI node with interrupt-map - for node in fdt.find_nodes("/") { - { - if let Node::Pci(pci) = node { - if let Ok(interrupt_map) = pci.interrupt_map() { - assert!(!interrupt_map.is_empty()); - return; // Test passed if we found and parsed interrupt-map - } - } - } - } - - // If we get here, no interrupt-map was found - // println!("No interrupt-map found in any PCI node"); - } - - #[test] - fn test_interrupt_map_mask() { - let dtb_data = include_bytes!("../../../../dtb-file/src/dtb/qemu_pci.dtb"); - let fdt = Fdt::from_bytes(dtb_data).unwrap(); - - for node in fdt.find_nodes("/") { - { - if let Node::Pci(pci) = node { - if let Some(mask) = pci.interrupt_map_mask() { - // println!("Found interrupt-map-mask: {:?}", mask); - assert_eq!(mask.len(), 4, "PCI interrupt-map-mask should have 4 cells"); - return; // Test passed - } - } - } - } - - // println!("No interrupt-map-mask found in any PCI node"); - } - - #[test] - fn test_bus_range() { - let dtb_data = include_bytes!("../../../../dtb-file/src/dtb/qemu_pci.dtb"); - let fdt = Fdt::from_bytes(dtb_data).unwrap(); - - for node in fdt.find_nodes("/") { - { - if let Node::Pci(pci) = node { - if let Some(range) = pci.bus_range() { - // println!("Found bus-range: {}-{}", start, end); - assert!(range.start <= range.end, "Bus range start should be <= end"); - return; // Test passed - } - } - } - } - - // println!("No bus-range found in any PCI node"); - } - - #[test] - fn test_pci_properties() { - let dtb_data = include_bytes!("../../../../dtb-file/src/dtb/qemu_pci.dtb"); - let fdt = Fdt::from_bytes(dtb_data).unwrap(); - - for node in fdt.find_nodes("/") { - { - if let Node::Pci(pci) = node { - // Test address cells - assert_eq!(pci.address_cells(), 3, "PCI should use 3 address cells"); - - // Test interrupt cells - assert_eq!(pci.interrupt_cells(), 1, "PCI should use 1 interrupt cell"); - - // Test device type - if let Some(device_type) = pci.device_type() { - assert!(!device_type.is_empty()); - } - - // Test compatibles - let compatibles = pci.compatibles(); - if !compatibles.is_empty() { - // println!("Compatibles: {:?}", compatibles); - } - - return; // Test passed for first PCI node found - } - } - } - - panic!("No PCI nodes found for property testing"); - } -} diff --git a/fdt-parser/src/data.rs b/fdt-parser/src/data.rs deleted file mode 100644 index cb728c0..0000000 --- a/fdt-parser/src/data.rs +++ /dev/null @@ -1,277 +0,0 @@ -//! Low-level data access utilities for FDT parsing. -//! -//! This module provides raw data access primitives for reading and parsing -//! Device Tree Blob data structures. It handles byte-aligned access, buffer -//! management, and iterators for common data formats used in device trees. - -use core::{ - ffi::CStr, - ops::{Deref, Range}, -}; - -use crate::{base::Fdt, FdtError, Property, Token}; - -/// A raw byte slice view with position tracking for FDT data. -/// -/// `Raw` provides a window into the FDT data with the ability to track -/// the current position and create sub-ranges. -#[derive(Clone, Copy)] -pub struct Raw<'a> { - value: &'a [u8], - pos: usize, -} - -impl<'a> Raw<'a> { - /// Creates a new `Raw` view from a byte slice. - pub(crate) fn new(value: &'a [u8]) -> Self { - Raw { value, pos: 0 } - } - - /// Creates a new `Buffer` for sequential reading from this raw data. - pub fn buffer(&self) -> Buffer<'a> { - Buffer { - raw: *self, - iter: 0, - } - } - - /// Returns the underlying byte slice. - pub fn value(&self) -> &'a [u8] { - self.value - } - - /// Creates a new `Raw` starting at the specified offset from the current position. - pub fn begin_at(&self, offset: usize) -> Raw<'a> { - let pos = self.pos + offset; - Raw { - value: &self.value[offset..], - pos, - } - } - - /// Returns a sub-range of the data as a new `Raw`. - /// - /// # Errors - /// - /// Returns `FdtError::BufferTooSmall` if the range extends beyond the data. - pub fn get_range(&self, range: Range) -> Result, FdtError> { - let pos = self.pos + range.start; - let end = pos + range.len(); - if end <= self.value.len() { - Ok(Raw { - value: &self.value[range], - pos, - }) - } else { - Err(FdtError::BufferTooSmall { pos: end }) - } - } - - /// Returns the current position in the original data stream. - pub fn pos(&self) -> usize { - self.pos - } - - /// Returns the underlying byte slice as a reference. - pub fn as_ref(&self) -> &'a [u8] { - self.value - } -} - -impl<'a> Deref for Raw<'a> { - type Target = &'a [u8]; - - fn deref(&self) -> &Self::Target { - &self.value - } -} - -/// A sequential buffer reader for parsing FDT data structures. -/// -/// `Buffer` provides sequential read access with automatic position tracking, -/// supporting various data types and alignment operations required by the -/// Device Tree specification. -#[derive(Clone)] -pub struct Buffer<'a> { - raw: Raw<'a>, - iter: usize, -} - -impl<'a> Buffer<'a> { - /// Takes the specified number of bytes from the buffer. - /// - /// # Errors - /// - /// Returns `FdtError::BufferTooSmall` if insufficient bytes remain. - pub fn take(&mut self, size: usize) -> Result, FdtError> { - let start = self.iter; - let end = start + size; - if end <= self.raw.value.len() { - self.iter = end; - Ok(Raw { - value: &self.raw.value[start..end], - pos: self.pos(), - }) - } else { - Err(FdtError::BufferTooSmall { - pos: self.pos() + size, - }) - } - } - - pub(crate) fn pos(&self) -> usize { - self.raw.pos + self.iter - } - - /// Returns the remaining unread data as a `Raw`. - pub fn remain(&self) -> Raw<'a> { - Raw { - value: &self.raw.value[self.iter..], - pos: self.pos(), - } - } - - /// Reads a big-endian u32 value. - pub fn take_u32(&mut self) -> Result { - let bytes = self.take(4)?; - Ok(u32::from_be_bytes(bytes.as_ref().try_into().unwrap())) - } - - /// Reads a big-endian u64 value. - pub fn take_u64(&mut self) -> Result { - let bytes = self.take(8)?; - Ok(u64::from_be_bytes(bytes.as_ref().try_into().unwrap())) - } - - pub(crate) fn take_token(&mut self) -> Result { - let u = self.take_u32()?; - Ok(Token::from(u)) - } - - /// Reads a null-terminated string. - pub fn take_str(&mut self) -> Result<&'a str, FdtError> { - let remain = self.remain(); - if remain.is_empty() { - return Err(FdtError::BufferTooSmall { pos: self.iter }); - } - - let cs = CStr::from_bytes_until_nul(remain.as_ref()) - .map_err(|_| FdtError::FromBytesUntilNull)?; - - let s = cs.to_str()?; - - let str_len = cs.to_bytes_with_nul().len(); - self.iter += str_len; - - Ok(s) - } - - /// Skips bytes aligned to 4-byte boundary (FDT format requirement). - pub fn skip_4_aligned(&mut self, len: usize) -> Result<(), FdtError> { - self.take((len + 3) & !0x3)?; - Ok(()) - } - - /// Takes bytes aligned to 4-byte boundary. - pub fn take_aligned(&mut self, len: usize) -> Result, FdtError> { - let bytes = (len + 3) & !0x3; - self.take(bytes) - } - - /// Advances the position to the next 4-byte boundary. - pub fn take_to_aligned(&mut self) { - let remain = self.iter % 4; - if remain != 0 { - let add = 4 - remain; - if self.iter + add <= self.raw.value.len() { - self.iter += 4 - remain; - } else { - self.iter = self.raw.value.len(); - } - } - } - - /// Takes a value based on the cell size (1 = 4 bytes, 2 = 8 bytes). - /// - /// # Panics - /// - /// Panics if cell_size is not 1 or 2. - pub fn take_by_cell_size(&mut self, cell_size: u8) -> Option { - match cell_size { - 1 => self.take_u32().map(|s| s as _).ok(), - 2 => self.take_u64().ok(), - _ => panic!("invalid cell size {}", cell_size), - } - } - - /// Takes a property value from the buffer. - pub fn take_prop(&mut self, fdt: &Fdt<'a>) -> Result, FdtError> { - let len = self.take_u32()?; - let nameoff = self.take_u32()?; - let data = self.take_aligned(len as _)?; - Ok(Property { - name: fdt.get_str(nameoff as _)?, - data, - }) - } -} - -/// Iterator over u32 values in raw data. -pub struct U32Iter<'a> { - buffer: Buffer<'a>, -} - -impl<'a> U32Iter<'a> { - /// Creates a new u32 iterator from raw data. - pub fn new(raw: Raw<'a>) -> Self { - Self { - buffer: raw.buffer(), - } - } - - /// Reads two u32 values as a u64 (big-endian combination). - pub fn as_u64(&mut self) -> u64 { - let h = self.buffer.take_u32().unwrap(); - if let Ok(l) = self.buffer.take_u32() { - ((h as u64) << 32) + l as u64 - } else { - h as _ - } - } -} - -impl<'a> Iterator for U32Iter<'a> { - type Item = u32; - - fn next(&mut self) -> Option { - self.buffer.take_u32().ok() - } -} - -/// Iterator over 2D arrays of u32 values. -pub struct U32Iter2D<'a> { - reader: Buffer<'a>, - row_len: u8, -} - -impl<'a> U32Iter2D<'a> { - /// Creates a new 2D iterator with the specified row length (in u32 cells). - pub fn new(bytes: &Raw<'a>, row_len: u8) -> Self { - Self { - reader: bytes.buffer(), - row_len, - } - } -} - -impl<'a> Iterator for U32Iter2D<'a> { - type Item = U32Iter<'a>; - - fn next(&mut self) -> Option { - let bytes = self - .reader - .take(self.row_len as usize * size_of::()) - .ok()?; - Some(U32Iter::new(bytes)) - } -} diff --git a/fdt-parser/src/define.rs b/fdt-parser/src/define.rs deleted file mode 100644 index a89ea90..0000000 --- a/fdt-parser/src/define.rs +++ /dev/null @@ -1,225 +0,0 @@ -//! Common type definitions and constants for FDT parsing. -//! -//! This module defines the core data types, constants, and enumerations -//! used throughout the FDT parser, including the magic number, tokens, -//! status values, and device tree-specific structures. - -use core::fmt::{Debug, Display}; - -use crate::data::{Buffer, Raw, U32Iter}; - -/// The Device Tree Blob magic number (0xd00dfeed). -/// -/// This value must be present at the start of any valid Device Tree Blob. -pub const FDT_MAGIC: u32 = 0xd00dfeed; - -/// Token type for parsing FDT structure blocks. -/// -/// Tokens are 32-bit values that identify different elements in the -/// device tree structure block. -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -pub(crate) enum Token { - /// Begin node token (0x1) - BeginNode, - /// End node token (0x2) - EndNode, - /// Property token (0x3) - Prop, - /// No-op token (0x4) - Nop, - /// End token (0x9) - marks the end of the structure block - End, - /// Any other data (not a valid token) - Data, -} - -impl From for Token { - fn from(value: u32) -> Self { - match value { - 0x1 => Token::BeginNode, - 0x2 => Token::EndNode, - 0x3 => Token::Prop, - 0x4 => Token::Nop, - 0x9 => Token::End, - _ => Token::Data, - } - } -} - -/// Device node status indicating whether the node is enabled or disabled. -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -pub enum Status { - /// Node is enabled and operational ("okay") - Okay, - /// Node is disabled ("disabled") - Disabled, -} - -/// A memory reservation entry in the FDT. -/// -/// Memory reservations specify physical memory regions that must -/// not be overwritten by the device tree or bootloader. -#[derive(Clone, Copy)] -pub struct MemoryRegion { - /// Physical address of the reserved region - pub address: *mut u8, - /// Size of the reserved region in bytes - pub size: usize, -} - -impl Debug for MemoryRegion { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.write_fmt(format_args!( - "MemoryRegion {{ address: {:p}, size: {:#x} }}", - self.address, self.size - )) - } -} - -/// A phandle (pointer handle) for referencing nodes in the device tree. -/// -/// Phandles are unique integer identifiers assigned to nodes that need -/// to be referenced from other nodes. -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[repr(transparent)] -pub struct Phandle(u32); - -impl From for Phandle { - fn from(value: u32) -> Self { - Self(value) - } -} -impl Phandle { - /// Returns the phandle value as a usize. - pub fn as_usize(&self) -> usize { - self.0 as usize - } -} - -impl Display for Phandle { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!(f, "<{:#x}>", self.0) - } -} - -/// A register entry describing a memory-mapped region. -/// -/// The `reg` property contains one or more of these entries, each -/// describing a address range for a device's registers. -#[derive(Clone, Copy)] -pub struct FdtReg { - /// Parent bus address - pub address: u64, - /// Child bus address - pub child_bus_address: u64, - /// Size of the region (None if not specified) - pub size: Option, -} - -impl Debug for FdtReg { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.write_fmt(format_args!("<{:#x}", self.address))?; - if self.child_bus_address != self.address { - f.write_fmt(format_args!("({:#x})", self.child_bus_address))?; - } - f.write_fmt(format_args!(", "))?; - if let Some(s) = self.size { - f.write_fmt(format_args!("{:#x}>", s)) - } else { - f.write_str("None>") - } - } -} - -/// Range mapping child bus addresses to parent bus addresses. -/// -/// The `ranges` property uses these entries to describe how addresses -/// on one bus are translated to another bus. -#[derive(Clone)] -pub struct FdtRange<'a> { - data_child: Raw<'a>, - data_parent: Raw<'a>, - /// Size of range - pub size: u64, -} - -impl<'a> FdtRange<'a> { - /// Returns an iterator over the child bus address cells. - pub fn child_bus_address(&self) -> U32Iter<'a> { - U32Iter::new(self.data_child) - } - - /// Returns an iterator over the parent bus address cells. - pub fn parent_bus_address(&self) -> U32Iter<'a> { - U32Iter::new(self.data_parent) - } -} - -impl core::fmt::Debug for FdtRange<'_> { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.write_str("Range {{ child_bus_address: [ ")?; - for addr in self.child_bus_address() { - f.write_fmt(format_args!("{:#x} ", addr))?; - } - f.write_str("], parent_bus_address: [ ")?; - for addr in self.parent_bus_address() { - f.write_fmt(format_args!("{:#x} ", addr))?; - } - f.write_fmt(format_args!("], size: {:#x}", self.size)) - } -} - -/// A slice of range entries with associated cell size information. -#[derive(Clone)] -pub struct FdtRangeSilce<'a> { - address_cell: u8, - address_cell_parent: u8, - size_cell: u8, - reader: Buffer<'a>, -} - -impl<'a> FdtRangeSilce<'a> { - pub(crate) fn new( - address_cell: u8, - address_cell_parent: u8, - size_cell: u8, - raw: &Raw<'a>, - ) -> Self { - Self { - address_cell, - address_cell_parent, - size_cell, - reader: raw.buffer(), - } - } - - /// Returns an iterator over the range entries. - pub fn iter(&self) -> FdtRangeIter<'a> { - FdtRangeIter { s: self.clone() } - } -} - -/// Iterator over range entries. -#[derive(Clone)] -pub struct FdtRangeIter<'a> { - s: FdtRangeSilce<'a>, -} - -impl<'a> Iterator for FdtRangeIter<'a> { - type Item = FdtRange<'a>; - - fn next(&mut self) -> Option { - let child_address_bytes = self.s.address_cell as usize * size_of::(); - let data_child = self.s.reader.take(child_address_bytes).ok()?; - - let parent_address_bytes = self.s.address_cell_parent as usize * size_of::(); - let data_parent = self.s.reader.take(parent_address_bytes).ok()?; - - let size = self.s.reader.take_by_cell_size(self.s.size_cell)?; - Some(FdtRange { - size, - data_child, - data_parent, - }) - } -} diff --git a/fdt-parser/src/header.rs b/fdt-parser/src/header.rs deleted file mode 100644 index d339203..0000000 --- a/fdt-parser/src/header.rs +++ /dev/null @@ -1,114 +0,0 @@ -//! FDT header structure parsing. -//! -//! This module handles parsing the Device Tree Blob header, which contains -//! metadata about the structure and layout of the device tree data. - -use core::ptr::NonNull; - -use crate::FdtError; - -#[repr(align(4))] -struct AlignedHeader([u8; size_of::
()]); - -/// The FDT header structure. -/// -/// The header is located at the start of any Device Tree Blob and contains -/// information about the layout and version of the device tree. All multi-byte -/// fields are stored in big-endian byte order. -#[derive(Debug, Clone)] -pub struct Header { - /// FDT header magic number (0xd00dfeed) - pub magic: u32, - /// Total size in bytes of the FDT structure - pub totalsize: u32, - /// Offset in bytes from the start of the header to the structure block - pub off_dt_struct: u32, - /// Offset in bytes from the start of the header to the strings block - pub off_dt_strings: u32, - /// Offset in bytes from the start of the header to the memory reservation block - pub off_mem_rsvmap: u32, - /// FDT version - pub version: u32, - /// Last compatible FDT version - pub last_comp_version: u32, - /// System boot CPU ID - pub boot_cpuid_phys: u32, - /// Length in bytes of the strings block - pub size_dt_strings: u32, - /// Length in bytes of the struct block - pub size_dt_struct: u32, -} - -impl Header { - /// Read a header from a byte slice and return an owned `Header` whose - /// fields are converted from big-endian (on-disk) to host order. - /// - /// # Errors - /// - /// Returns `FdtError::BufferTooSmall` if the slice is too small. - /// Returns `FdtError::InvalidMagic` if the magic number is incorrect. - pub fn from_bytes(data: &[u8]) -> Result { - if data.len() < core::mem::size_of::
() { - return Err(FdtError::BufferTooSmall { - pos: core::mem::size_of::
(), - }); - } - let ptr = NonNull::new(data.as_ptr() as *mut u8).ok_or(FdtError::InvalidPtr)?; - unsafe { Self::from_ptr(ptr.as_ptr()) } - } - - /// Read a header from a raw pointer and return an owned `Header` whose - /// fields are converted from big-endian (on-disk) to host order. - /// - /// # Safety - /// - /// The caller must ensure that the pointer is valid and points to a - /// memory region of at least `size_of::
()` bytes that contains a - /// valid device tree blob. - pub unsafe fn from_ptr(ptr: *mut u8) -> Result { - if !(ptr as usize).is_multiple_of(core::mem::align_of::
()) { - // Pointer is not aligned, so we need to copy the data to an aligned - // buffer first. - let mut aligned = AlignedHeader([0u8; core::mem::size_of::
()]); - unsafe { - core::ptr::copy_nonoverlapping( - ptr, - aligned.0.as_mut_ptr(), - core::mem::size_of::
(), - ); - } - Self::from_aligned_ptr(aligned.0.as_mut_ptr()) - } else { - // Pointer is aligned, we can read directly from it. - Self::from_aligned_ptr(ptr) - } - } - - fn from_aligned_ptr(ptr: *mut u8) -> Result { - let ptr = NonNull::new(ptr).ok_or(FdtError::InvalidPtr)?; - - // SAFETY: caller provided a valid pointer to the beginning of a device - // tree blob. We read the raw header as it exists in memory (which is - // big-endian on-disk). Then convert each u32 field from big-endian to - // host order using `u32::from_be`. - let raw = unsafe { &*(ptr.cast::
().as_ptr()) }; - - let magic = u32::from_be(raw.magic); - if magic != crate::FDT_MAGIC { - return Err(FdtError::InvalidMagic(magic)); - } - - Ok(Header { - magic, - totalsize: u32::from_be(raw.totalsize), - off_dt_struct: u32::from_be(raw.off_dt_struct), - off_dt_strings: u32::from_be(raw.off_dt_strings), - off_mem_rsvmap: u32::from_be(raw.off_mem_rsvmap), - version: u32::from_be(raw.version), - last_comp_version: u32::from_be(raw.last_comp_version), - boot_cpuid_phys: u32::from_be(raw.boot_cpuid_phys), - size_dt_strings: u32::from_be(raw.size_dt_strings), - size_dt_struct: u32::from_be(raw.size_dt_struct), - }) - } -} diff --git a/fdt-parser/src/lib.rs b/fdt-parser/src/lib.rs deleted file mode 100644 index baa318d..0000000 --- a/fdt-parser/src/lib.rs +++ /dev/null @@ -1,130 +0,0 @@ -//! A `#![no_std]` Flattened Device Tree (FDT) parser for Rust. -//! -//! This crate provides a pure-Rust parser for Device Tree Blob (DTB) files -//! based on the devicetree-specification-v0.4. It supports both direct parsing -//! and a cached representation for efficient repeated lookups. -//! -//! # Features -//! -//! - `#![no_std]` compatible - suitable for bare-metal and embedded systems -//! - Two parsing modes: -//! - [`base`] - Direct parsing that walks the FDT structure -//! - [`cache`] - Cached representation with indexed nodes for faster lookups -//! - Zero-copy parsing where possible -//! - Comprehensive error handling -//! -//! # Example -//! -//! ```no_run -//! use fdt_parser::Fdt; -//! -//! # fn main() -> Result<(), Box> { -//! // Read DTB data from file or memory -//! let data = std::fs::read("path/to/device.dtb")?; -//! -//! // Parse the FDT -//! let fdt = Fdt::from_bytes(&data)?; -//! -//! // Get the root node -//! let root = fdt.get_node_by_path("/").unwrap(); -//! println!("Root node: {}", root.name()); -//! -//! // Iterate over all nodes -//! for node in fdt.all_nodes() { -//! println!("Node: {}", node.name()); -//! } -//! # Ok(()) -//! # } -//! ``` - -#![no_std] -#![deny(warnings, missing_docs)] - -extern crate alloc; - -/// Macro to unwrap `Option` values, returning `FdtError::NotFound` if `None`. -/// -/// # Variants -/// -/// - `none_ok!(expr)` - Returns `FdtError::NotFound` if `expr` is `None` -/// - `none_ok!(expr, err)` - Returns the specified error if `expr` is `None` -macro_rules! none_ok { - ($e:expr) => {{ - let Some(v) = $e else { - return Err(crate::FdtError::NotFound); - }; - v - }}; - ($e:expr, $err:expr) => {{ - let Some(v) = $e else { - return Err($err); - }; - v - }}; -} - -mod data; -mod define; -mod header; -mod property; - -pub mod base; -pub mod cache; - -use core::ffi::FromBytesUntilNulError; - -pub use cache::*; -pub use define::*; -pub use header::Header; -pub use property::Property; - -/// Errors that can occur during FDT parsing and traversal. -#[derive(thiserror::Error, Debug, Clone)] -pub enum FdtError { - /// A requested item (node, property, etc.) was not found - #[error("not found")] - NotFound, - /// The buffer is too small to contain the expected data at the given position - #[error("buffer too small at position {pos}")] - BufferTooSmall { - /// The position at which the buffer was found to be too small - pos: usize, - }, - /// The FDT magic number does not match the expected value - #[error("invalid magic number {0:#x} != {FDT_MAGIC:#x}")] - InvalidMagic(u32), - /// An invalid pointer was encountered during parsing - #[error("invalid pointer")] - InvalidPtr, - /// String data does not contain a null terminator - #[error("data provided does not contain a nul")] - FromBytesUntilNull, - /// Failed to parse data as UTF-8 - #[error("failed to parse UTF-8 string")] - Utf8Parse, - /// No alias was found for the requested path - #[error("no aliase found")] - NoAlias, - /// Memory allocation failed - #[error("system out of memory")] - NoMemory, - /// The specified node was not found - #[error("node `{0}` not found")] - NodeNotFound(&'static str), - /// The specified property was not found - #[error("property `{0}` not found")] - PropertyNotFound(&'static str), -} - -impl From for FdtError { - /// Converts a UTF-8 parsing error into `FdtError::Utf8Parse`. - fn from(_: core::str::Utf8Error) -> Self { - FdtError::Utf8Parse - } -} -impl From for FdtError { - /// Converts a C-string parsing error into `FdtError::FromBytesUntilNull`. - fn from(_: FromBytesUntilNulError) -> Self { - FdtError::FromBytesUntilNull - } -} diff --git a/fdt-parser/src/property.rs b/fdt-parser/src/property.rs deleted file mode 100644 index b10938c..0000000 --- a/fdt-parser/src/property.rs +++ /dev/null @@ -1,123 +0,0 @@ -//! Device tree property parsing and access. -//! -//! This module provides the `Property` type for accessing device tree -//! property values, with methods for interpreting the data in various formats. - -use core::{ffi::CStr, iter}; - -use crate::{ - base::Fdt, - data::{Buffer, Raw}, - FdtError, Token, -}; - -/// A device tree property. -/// -/// Properties are key-value pairs associated with device tree nodes. -/// Each property has a name and a value, where the value can be interpreted -/// in various ways depending on the property type. -#[derive(Clone)] -pub struct Property<'a> { - /// The property name - pub name: &'a str, - pub(crate) data: Raw<'a>, -} - -impl<'a> Property<'a> { - /// Returns the raw property value as a byte slice. - pub fn raw_value(&self) -> &'a [u8] { - self.data.value() - } - - /// Interprets the property value as a big-endian u32. - pub fn u32(&self) -> Result { - self.data.buffer().take_u32() - } - - /// Interprets the property value as a big-endian u64. - pub fn u64(&self) -> Result { - self.data.buffer().take_u64() - } - - /// Interprets the property value as a null-terminated string. - pub fn str(&self) -> Result<&'a str, FdtError> { - let res = CStr::from_bytes_until_nul(self.data.value())?.to_str()?; - Ok(res) - } - - /// Interprets the property value as a list of null-terminated strings. - pub fn str_list(&self) -> impl Iterator + 'a { - let mut value = self.data.buffer(); - iter::from_fn(move || value.take_str().ok()) - } - - /// Interprets the property value as a list of big-endian u32 values. - pub fn u32_list(&self) -> impl Iterator + 'a { - let mut value = self.data.buffer(); - iter::from_fn(move || value.take_u32().ok()) - } - - /// Interprets the property value as a list of big-endian u64 values. - pub fn u64_list(&self) -> impl Iterator + 'a { - let mut value = self.data.buffer(); - iter::from_fn(move || value.take_u64().ok()) - } -} - -impl core::fmt::Debug for Property<'_> { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!(f, "{} = [", self.name)?; - for v in self.u32_list() { - write!(f, "{:#x}, ", v)?; - } - write!(f, "]")?; - Ok(()) - } -} - -/// Iterator over properties in a device tree node. -pub(crate) struct PropIter<'a> { - fdt: Fdt<'a>, - reader: Buffer<'a>, - has_err: bool, -} - -impl<'a> PropIter<'a> { - pub fn new(fdt: Fdt<'a>, reader: Buffer<'a>) -> Self { - Self { - fdt, - reader, - has_err: false, - } - } - - fn try_next(&mut self) -> Result>, FdtError> { - loop { - match self.reader.take_token()? { - Token::Prop => break, - Token::Nop => {} - _ => return Ok(None), - } - } - let prop = self.reader.take_prop(&self.fdt)?; - Ok(Some(prop)) - } -} - -impl<'a> Iterator for PropIter<'a> { - type Item = Result, FdtError>; - - fn next(&mut self) -> Option { - if self.has_err { - return None; - } - match self.try_next() { - Ok(Some(prop)) => Some(Ok(prop)), - Ok(None) => None, - Err(e) => { - self.has_err = true; - Some(Err(e)) - } - } - } -} diff --git a/fdt-parser/tests/head.rs b/fdt-parser/tests/head.rs deleted file mode 100644 index a19dd4a..0000000 --- a/fdt-parser/tests/head.rs +++ /dev/null @@ -1,18 +0,0 @@ -#[cfg(test)] -mod test { - use dtb_file::{fdt_phytium, fdt_rpi_4b}; - use fdt_parser::*; - - #[test] - fn test_head() { - let header = Header::from_bytes(&fdt_rpi_4b()).unwrap(); - println!("{:#?}", header); - } - - #[test] - fn test_head_phytium() { - let raw = fdt_phytium(); - let header = Header::from_bytes(&raw).unwrap(); - println!("{:#?}", header); - } -} diff --git a/fdt-parser/tests/no_mem.rs b/fdt-parser/tests/no_mem.rs deleted file mode 100644 index 78d07ab..0000000 --- a/fdt-parser/tests/no_mem.rs +++ /dev/null @@ -1,339 +0,0 @@ -#[cfg(test)] -mod test { - use dtb_file::{fdt_3568, fdt_phytium, fdt_qemu, fdt_reserve, fdt_rpi_4b}; - use fdt_parser::base::*; - - #[test] - fn test_new() { - let raw = fdt_qemu(); - let ptr = raw.as_ptr() as *mut u8; - let fdt: Fdt<'static> = unsafe { Fdt::from_ptr(ptr).unwrap() }; - - println!("ver: {:#?}", fdt.header().version); - } - - #[test] - fn test_memory_reservation_blocks() { - // Test with custom DTB that has memory reservations - let raw = fdt_reserve(); - let ptr = raw.as_ptr() as *mut u8; - let fdt = unsafe { Fdt::from_ptr(ptr).unwrap() }; - - // Get memory reservation blocks - let rsv_result = fdt.memory_reservation_blocks(); - - let entries: Vec<_> = rsv_result.collect(); - - // Should have exactly 3 reservation blocks as defined in our DTS - assert_eq!( - entries.len(), - 3, - "Should have exactly 3 memory reservation blocks" - ); - - // Test the specific values we defined - let expected_reservations = [ - (0x40000000u64, 0x04000000u64), // 64MB at 1GB - (0x80000000u64, 0x00100000u64), // 1MB at 2GB - (0xA0000000u64, 0x00200000u64), // 2MB at 2.5GB - ]; - - for (i, &(expected_addr, expected_size)) in expected_reservations.iter().enumerate() { - assert_eq!( - entries[i].address as usize, expected_addr as usize, - "Reservation {} address mismatch: expected {:#x}, got {:#p}", - i, expected_addr, entries[i].address - ); - assert_eq!( - entries[i].size, expected_size as usize, - "Reservation {} size mismatch: expected {:#x}, got {:#x}", - i, expected_size, entries[i].size - ); - } - - // Test iterator behavior - iterate twice to ensure it works correctly - let rsv1: Vec<_> = fdt.memory_reservation_blocks().collect(); - let rsv2: Vec<_> = fdt.memory_reservation_blocks().collect(); - assert_eq!( - rsv1.len(), - rsv2.len(), - "Multiple iterations should yield same results" - ); - - for (entry1, entry2) in rsv1.iter().zip(rsv2.iter()) { - assert_eq!( - entry1.address, entry2.address, - "Addresses should match between iterations" - ); - assert_eq!( - entry1.size, entry2.size, - "Sizes should match between iterations" - ); - } - } - - #[test] - fn test_empty_memory_reservation_blocks() { - // Test with DTBs that have no memory reservations - let test_cases = [ - ("QEMU", fdt_qemu()), - ("Phytium", fdt_phytium()), - ("RK3568", fdt_3568()), - ]; - - for (name, raw) in test_cases { - let ptr = raw.as_ptr() as *mut u8; - let fdt = unsafe { Fdt::from_ptr(ptr).unwrap() }; - - let rsv_result = fdt.memory_reservation_blocks(); - - let entries: Vec<_> = rsv_result.collect(); - assert_eq!( - entries.len(), - 0, - "{} DTB should have no memory reservation blocks", - name - ); - } - } - - fn test_node<'a>() -> Option> { - let raw = fdt_rpi_4b(); - let fdt = unsafe { Fdt::from_ptr(raw.ptr()).unwrap() }; - fdt.all_nodes().next().and_then(|n| n.ok()) - } - - #[test] - fn test_send_node() { - let node = test_node(); - if let Some(node) = node { - println!("{:?}", node.name()); - } - } - - #[test] - fn test_all_nodes() { - env_logger::builder() - .is_test(true) - .filter_level(log::LevelFilter::Debug) - .init(); - let raw = fdt_reserve(); - let fdt = unsafe { Fdt::from_ptr(raw.ptr()).unwrap() }; - for node in fdt.all_nodes().flatten() { - println!( - "{}{} l{} parent={:?}", - match node.level { - 0 => "", - 1 => " ", - 2 => " ", - _ => " ", - }, - node.name(), - node.level(), - node.parent_name() - ); - } - } - - #[test] - fn test_property() { - let raw = fdt_rpi_4b(); - let fdt = unsafe { Fdt::from_ptr(raw.ptr()).unwrap() }; - for node in fdt.all_nodes().flatten() { - println!("{}:", node.name()); - for prop in node.properties().flatten() { - println!(" {:?}", prop); - } - } - } - - #[test] - fn test_str_list() { - let raw = fdt_rpi_4b(); - let fdt = unsafe { Fdt::from_ptr(raw.ptr()).unwrap() }; - let uart = fdt - .find_nodes("/soc/serial@7e201000") - .next() - .unwrap() - .unwrap(); - let caps = uart - .find_property("compatible") - .unwrap() - .str_list() - .collect::>(); - - let want = ["arm,pl011", "arm,primecell"]; - - for (i, cap) in caps.iter().enumerate() { - assert_eq!(*cap, want[i]); - } - } - #[test] - fn test_find_nodes() { - let raw = fdt_rpi_4b(); - let fdt = unsafe { Fdt::from_ptr(raw.ptr()).unwrap() }; - - let uart = fdt.find_nodes("/soc/serial"); - - let want = [ - "serial@7e201000", - "serial@7e215040", - "serial@7e201400", - "serial@7e201600", - "serial@7e201800", - "serial@7e201a00", - ]; - - for (act, want) in uart.zip(want.iter()) { - let act = act.unwrap(); - assert_eq!(act.name(), *want); - } - } - - #[test] - fn test_find_node2() { - let raw = fdt_rpi_4b(); - let fdt = Fdt::from_bytes(&raw).unwrap(); - let node = fdt - .find_nodes("/soc/serial@7e215040") - .next() - .unwrap() - .unwrap(); - assert_eq!(node.name(), "serial@7e215040"); - } - - #[test] - fn test_find_aliases() { - let raw = fdt_rpi_4b(); - let fdt = Fdt::from_bytes(&raw).unwrap(); - let path = fdt.find_aliase("serial0").unwrap(); - assert_eq!(path, "/soc/serial@7e215040"); - } - #[test] - fn test_find_node_aliases() { - let raw = fdt_rpi_4b(); - let fdt = Fdt::from_bytes(&raw).unwrap(); - let node = fdt.find_nodes("serial0").next().unwrap().unwrap(); - assert_eq!(node.name(), "serial@7e215040"); - } - - #[test] - fn test_chosen() { - let raw = fdt_rpi_4b(); - let fdt = Fdt::from_bytes(&raw).unwrap(); - let chosen = fdt.chosen().unwrap(); - let bootargs = chosen.bootargs().unwrap(); - assert_eq!( - bootargs, - "coherent_pool=1M 8250.nr_uarts=1 snd_bcm2835.enable_headphones=0" - ); - - let stdout = chosen.stdout().unwrap(); - assert_eq!(stdout.params, Some("115200n8")); - assert_eq!(stdout.name(), "serial@7e215040"); - } - - #[test] - fn test_reg() { - let raw = fdt_rpi_4b(); - let fdt = Fdt::from_bytes(&raw).unwrap(); - - let node = fdt - .find_nodes("/soc/serial@7e215040") - .next() - .unwrap() - .unwrap(); - - let reg = node.reg().unwrap().next().unwrap(); - - println!("reg: {:?}", reg); - - assert_eq!( - reg.address, 0xfe215040, - "want 0xfe215040, got {:#x}", - reg.address - ); - assert_eq!( - reg.child_bus_address, 0x7e215040, - "want 0x7e215040, got {:#x}", - reg.child_bus_address - ); - assert_eq!( - reg.size, - Some(0x40), - "want 0x40, got {:#x}", - reg.size.unwrap() - ); - } - - #[test] - fn test_memory() { - let raw = fdt_qemu(); - let fdt = Fdt::from_bytes(&raw).unwrap(); - - let node = fdt.memory().next().unwrap().unwrap(); - - let reg = node.reg().unwrap().next().unwrap(); - println!("memory reg: {:?}", reg); - } - - #[test] - fn test_reserved_memory() { - let raw = fdt_rpi_4b(); - let fdt = unsafe { Fdt::from_ptr(raw.ptr()).unwrap() }; - let ls = fdt - .reserved_memory_regions() - .unwrap() - .collect::, _>>() - .unwrap(); - - let want_names = ["linux,cma", "nvram@0", "nvram@1"]; - - for node in &ls { - println!("reserved memory node: {:?}", node); - } - - assert_eq!(ls.len(), want_names.len()); - for (i, node) in ls.iter().enumerate() { - assert_eq!(node.name(), want_names[i]); - } - } - - #[test] - fn test_debugcon() { - let raw = fdt_qemu(); - let fdt = Fdt::from_bytes(&raw).unwrap(); - let debugcon = fdt.chosen().unwrap().debugcon().unwrap(); - - match debugcon { - fdt_parser::base::DebugCon::Node(node) => { - println!("Found debugcon node: {:?}", node.name()); - } - fdt_parser::base::DebugCon::EarlyConInfo { name, mmio, params } => { - println!( - "Found earlycon info: name={}, mmio={:#x}, params={:?}", - name, mmio, params - ); - } - } - } - - #[test] - fn test_debugcon2() { - let raw = fdt_3568(); - let fdt = Fdt::from_bytes(&raw).unwrap(); - let debugcon = fdt.chosen().unwrap().debugcon().unwrap(); - - match debugcon { - fdt_parser::base::DebugCon::Node(node) => { - println!("Found debugcon node: {:?}", node.name()); - } - fdt_parser::base::DebugCon::EarlyConInfo { name, mmio, params } => { - println!( - "Found earlycon info: name={}, mmio={:#x}, params={:?}", - name, mmio, params - ); - } - } - } -} diff --git a/fdt-parser/tests/node.rs b/fdt-parser/tests/node.rs deleted file mode 100644 index 0493866..0000000 --- a/fdt-parser/tests/node.rs +++ /dev/null @@ -1,739 +0,0 @@ -#[cfg(test)] -mod test { - use dtb_file::{fdt_3568, fdt_phytium, fdt_qemu, fdt_reserve, fdt_rpi_4b}; - use fdt_parser::*; - use log::{debug, info}; - use std::sync::Once; - - fn init_logging() { - static INIT: Once = Once::new(); - INIT.call_once(|| { - let _ = env_logger::builder() - .is_test(true) - .filter_level(log::LevelFilter::Trace) - .try_init(); - }); - } - - #[test] - fn test_new() { - init_logging(); - let raw = fdt_qemu(); - let ptr = raw.as_ptr() as *mut u8; - let fdt = unsafe { Fdt::from_ptr(ptr).unwrap() }; - - info!("ver: {:#?}", fdt.header().version); - } - - #[test] - fn test_memory_reservation_blocks() { - init_logging(); - // Test with custom DTB that has memory reservations - let raw = fdt_reserve(); - let ptr = raw.as_ptr() as *mut u8; - let fdt = unsafe { Fdt::from_ptr(ptr).unwrap() }; - - // Get memory reservation blocks - let rsv_result = fdt.memory_reservation_blocks(); - - let entries = rsv_result; - - // Should have exactly 3 reservation blocks as defined in our DTS - assert_eq!( - entries.len(), - 3, - "Should have exactly 3 memory reservation blocks" - ); - - // Test the specific values we defined - let expected_reservations = [ - (0x40000000u64, 0x04000000u64), // 64MB at 1GB - (0x80000000u64, 0x00100000u64), // 1MB at 2GB - (0xA0000000u64, 0x00200000u64), // 2MB at 2.5GB - ]; - - for (i, &(expected_addr, expected_size)) in expected_reservations.iter().enumerate() { - assert_eq!( - entries[i].address as usize, expected_addr as usize, - "Reservation {} address mismatch: expected {:#x}, got {:#p}", - i, expected_addr, entries[i].address - ); - assert_eq!( - entries[i].size, expected_size as usize, - "Reservation {} size mismatch: expected {:#x}, got {:#x}", - i, expected_size, entries[i].size - ); - } - - // Test iterator behavior - iterate twice to ensure it works correctly - let rsv1: Vec<_> = fdt.memory_reservation_blocks(); - let rsv2: Vec<_> = fdt.memory_reservation_blocks(); - assert_eq!( - rsv1.len(), - rsv2.len(), - "Multiple iterations should yield same results" - ); - - for (entry1, entry2) in rsv1.iter().zip(rsv2.iter()) { - assert_eq!( - entry1.address, entry2.address, - "Addresses should match between iterations" - ); - assert_eq!( - entry1.size, entry2.size, - "Sizes should match between iterations" - ); - } - } - - #[test] - fn test_empty_memory_reservation_blocks() { - init_logging(); - // Test with DTBs that have no memory reservations - let test_cases = [ - ("QEMU", fdt_qemu()), - ("Phytium", fdt_phytium()), - ("RK3568", fdt_3568()), - ]; - - for (name, raw) in test_cases { - let ptr = raw.as_ptr() as *mut u8; - let fdt = unsafe { Fdt::from_ptr(ptr).unwrap() }; - - let rsv_result = fdt.memory_reservation_blocks(); - - let entries = rsv_result; - assert_eq!( - entries.len(), - 0, - "{} DTB should have no memory reservation blocks", - name - ); - } - } - - fn test_node() -> Option { - let raw = fdt_rpi_4b(); - let fdt = unsafe { Fdt::from_ptr(raw.ptr()).unwrap() }; - fdt.all_nodes().into_iter().next() - } - - #[test] - fn test_send_node() { - init_logging(); - let node = test_node(); - if let Some(node) = node { - info!("{:?}", node.name()); - } - } - - #[test] - fn test_all_nodes() { - init_logging(); - let raw = fdt_reserve(); - let fdt = unsafe { Fdt::from_ptr(raw.ptr()).unwrap() }; - for node in fdt.all_nodes() { - debug!( - "{}{} l{} parent={:?}", - match node.level() { - 0 => "", - 1 => " ", - 2 => " ", - _ => " ", - }, - node.full_path(), - node.level(), - node.parent().map(|n| n.name().to_string()) - ); - } - } - - #[test] - fn test_property() { - init_logging(); - let raw = fdt_rpi_4b(); - let fdt = unsafe { Fdt::from_ptr(raw.ptr()).unwrap() }; - for node in fdt.all_nodes() { - info!("{}:", node.name()); - for prop in node.properties() { - debug!(" {:?}", prop); - } - } - } - - #[test] - fn test_str_list() { - init_logging(); - let raw = fdt_rpi_4b(); - let fdt = unsafe { Fdt::from_ptr(raw.ptr()).unwrap() }; - let uart = fdt.find_nodes("/soc/serial@7e201000")[0].clone(); - let caps = uart.compatibles(); - - let want = ["arm,pl011", "arm,primecell"]; - - for (i, cap) in caps.iter().enumerate() { - assert_eq!(*cap, want[i]); - } - } - #[test] - fn test_find_nodes() { - init_logging(); - let raw = fdt_rpi_4b(); - let fdt = unsafe { Fdt::from_ptr(raw.ptr()).unwrap() }; - - let uart = fdt.find_nodes("/soc/serial"); - - let want = [ - "serial@7e201000", - "serial@7e215040", - "serial@7e201400", - "serial@7e201600", - "serial@7e201800", - "serial@7e201a00", - ]; - - for (act, &want) in uart.iter().zip(want.iter()) { - assert_eq!(act.name(), want); - } - } - - #[test] - fn test_find_node2() { - init_logging(); - let raw = fdt_rpi_4b(); - let fdt = Fdt::from_bytes(&raw).unwrap(); - let node = fdt.find_nodes("/soc/serial@7e215040")[0].clone(); - assert_eq!(node.name(), "serial@7e215040"); - } - - #[test] - fn test_find_aliases() { - init_logging(); - let raw = fdt_rpi_4b(); - let fdt = Fdt::from_bytes(&raw).unwrap(); - let path = fdt.find_aliase("serial0").unwrap(); - assert_eq!(path, "/soc/serial@7e215040"); - } - #[test] - fn test_find_node_aliases() { - init_logging(); - let raw = fdt_rpi_4b(); - let fdt = Fdt::from_bytes(&raw).unwrap(); - let node = fdt.find_nodes("serial0")[0].clone(); - assert_eq!(node.name(), "serial@7e215040"); - } - - #[test] - fn test_reg() { - init_logging(); - let raw = fdt_rpi_4b(); - let fdt = Fdt::from_bytes(&raw).unwrap(); - - let node = fdt.find_nodes("/soc/serial@7e215040")[0].clone(); - - let reg = node.reg().unwrap()[0]; - - let parent = node.parent().unwrap(); - if let Some(addr_cells_prop) = parent.find_property("#address-cells") { - debug!("parent #address-cells={}", addr_cells_prop.u32().unwrap()); - } - if let Some(size_cells_prop) = parent.find_property("#size-cells") { - debug!("parent #size-cells={}", size_cells_prop.u32().unwrap()); - } - if let Some(ranges) = parent.ranges() { - for (idx, range) in ranges.iter().enumerate() { - let child_cells = range.child_bus_address().collect::>(); - let parent_cells = range.parent_bus_address().collect::>(); - let child_addr = child_cells - .iter() - .fold(0u64, |acc, val| (acc << 32) | (*val as u64)); - let parent_addr = parent_cells - .iter() - .fold(0u64, |acc, val| (acc << 32) | (*val as u64)); - debug!( - "range[{idx}]: child_cells={:?} parent_cells={:?} child={:#x} parent={:#x} size={:#x}", - child_cells, parent_cells, child_addr, parent_addr, range.size - ); - } - } - - info!("reg: {:?}", reg); - - assert_eq!( - reg.address, 0xfe215040, - "want 0xfe215040, got {:#x}", - reg.address - ); - assert_eq!( - reg.child_bus_address, 0x7e215040, - "want 0x7e215040, got {:#x}", - reg.child_bus_address - ); - assert_eq!( - reg.size, - Some(0x40), - "want 0x40, got {:#x}", - reg.size.unwrap() - ); - } - - // #[test] - // fn test_memory() { - // let raw = fdt_qemu(); - // let fdt = Fdt::from_bytes(&raw).unwrap(); - - // let node = fdt.memory(); - - // for node in node { - // let node = node.unwrap(); - // println!("memory node: {:?}", node.name()); - // for reg in node.reg().unwrap().unwrap() { - // println!(" reg: {:?}", reg); - // } - - // for region in node.regions() { - // let region = region.unwrap(); - // println!(" region: {:?}", region); - // } - // } - // } - #[test] - fn test_find_compatible() { - init_logging(); - let raw = fdt_rpi_4b(); - let fdt = unsafe { Fdt::from_ptr(raw.ptr()).unwrap() }; - - let ls = fdt.find_compatible(&["arm,pl011", "arm,primecell"]); - - assert_eq!(ls[0].name(), "serial@7e201000"); - } - - #[test] - fn test_compatibles() { - init_logging(); - let raw = fdt_rpi_4b(); - let fdt = unsafe { Fdt::from_ptr(raw.ptr()).unwrap() }; - let mut ls = fdt.find_nodes("/soc/serial@7e201000"); - let uart = ls.pop().unwrap(); - let caps = uart.compatibles(); - - let want = ["arm,pl011", "arm,primecell"]; - - for (act, want) in caps.iter().zip(want.iter()) { - assert_eq!(act, *want); - } - } - - #[test] - fn test_interrupt() { - init_logging(); - let raw = fdt_rpi_4b(); - let fdt = Fdt::from_bytes(&raw).unwrap(); - let node = fdt.find_nodes("/soc/serial@7e215040")[0].clone(); - - let itr_ctrl = node.interrupt_parent().unwrap(); - info!("itr_ctrl: {:?}", itr_ctrl.name()); - let interrupt_cells = itr_ctrl.interrupt_cells().unwrap(); - assert_eq!(interrupt_cells, 3); - } - - #[test] - fn test_interrupt2() { - init_logging(); - let raw = fdt_rpi_4b(); - let fdt = Fdt::from_bytes(&raw).unwrap(); - - let node = fdt.find_compatible(&["brcm,bcm2711-hdmi0"])[0].clone(); - - let itr_ctrl_ph = node.interrupt_parent_phandle().unwrap(); - assert_eq!(itr_ctrl_ph, 0x2c.into()); - - let itr_ctrl = node.interrupt_parent().unwrap(); - assert_eq!(itr_ctrl.name(), "interrupt-controller@7ef00100"); - } - - #[test] - fn test_interrupts() { - init_logging(); - let raw = fdt_rpi_4b(); - let fdt = Fdt::from_bytes(&raw).unwrap(); - - let node = fdt.find_compatible(&["brcm,bcm2711-hdmi0"])[0].clone(); - let itr = node.interrupts().unwrap(); - let want_itrs = [0x0, 0x1, 0x2, 0x3, 0x4, 0x5]; - - for (i, itr) in itr.iter().enumerate() { - assert_eq!(itr[0], want_itrs[i]); - } - } - - #[test] - fn test_clocks() { - init_logging(); - let raw = fdt_rpi_4b(); - let fdt = Fdt::from_bytes(&raw).unwrap(); - - let node = fdt.find_nodes("/soc/serial@7e215040")[0].clone(); - let clocks = node.clocks().unwrap(); - assert!(!clocks.is_empty()); - let clock = &clocks[0]; - assert_eq!(clock.provider_name(), "aux@7e215000"); - } - - #[test] - fn test_clocks_cell_1() { - init_logging(); - let fdt = fdt_3568(); - - let fdt = Fdt::from_bytes(&fdt).unwrap(); - let node = fdt.find_nodes("/sdhci@fe310000")[0].clone(); - let clocks = node.clocks().unwrap(); - let clock = clocks[0].clone(); - - for clock in &clocks { - debug!("clock: {:?}", clock); - } - assert_eq!(clock.provider_name(), "clock-controller@fdd20000"); - } - - #[test] - fn test_clocks_cell_0() { - init_logging(); - let raw = fdt_phytium(); - - let fdt = Fdt::from_bytes(&raw).unwrap(); - - let node = fdt.find_nodes("/soc/uart@2800e000")[0].clone(); - let clocks = node.clocks().unwrap(); - - for clock in &clocks { - debug!("clock: {:?}", clock); - } - } - - #[test] - fn test_pcie() { - let raw = fdt_rpi_4b(); - let fdt = Fdt::from_bytes(&raw).unwrap(); - let node = fdt - .find_compatible(&["brcm,bcm2711-pcie"]) - .into_iter() - .next() - .unwrap(); - let regs = node.reg().unwrap(); - let reg = regs[0]; - println!("reg: {:?}", reg); - assert_eq!(reg.address, 0xfd500000); - assert_eq!(reg.size, Some(0x9310)); - } - - #[test] - fn test_pci2() { - let raw = fdt_phytium(); - let fdt = Fdt::from_bytes(&raw).unwrap(); - let node = fdt - .find_compatible(&["pci-host-ecam-generic"]) - .into_iter() - .next() - .unwrap(); - - let Node::Pci(pci) = node else { - panic!("Not a PCI node"); - }; - - let want = [ - PciRange { - space: PciSpace::IO, - bus_address: 0x0, - cpu_address: 0x50000000, - size: 0xf00000, - prefetchable: false, - }, - PciRange { - space: PciSpace::Memory32, - bus_address: 0x58000000, - cpu_address: 0x58000000, - size: 0x28000000, - prefetchable: false, - }, - PciRange { - space: PciSpace::Memory64, - bus_address: 0x1000000000, - cpu_address: 0x1000000000, - size: 0x1000000000, - prefetchable: false, - }, - ]; - - for (i, range) in pci.ranges().unwrap().iter().enumerate() { - assert_eq!(*range, want[i]); - } - } - - #[test] - fn test_pci_irq_map() { - let raw = fdt_phytium(); - let fdt = Fdt::from_bytes(&raw).unwrap(); - let pci = fdt - .find_compatible(&["pci-host-ecam-generic"]) - .into_iter() - .next() - .unwrap(); - - let Node::Pci(pci) = pci else { - panic!("Not a PCI node"); - }; - - let irq = pci.child_interrupts(0, 0, 0, 4).unwrap(); - assert!(!irq.irqs.is_empty()); - } - - #[test] - fn test_pci_irq_map2() { - let raw = fdt_qemu(); - let fdt = Fdt::from_bytes(&raw).unwrap(); - let pci = fdt - .find_compatible(&["pci-host-ecam-generic"]) - .into_iter() - .next() - .unwrap(); - - let Node::Pci(pci) = pci else { - panic!("Not a PCI node"); - }; - - let irq = pci.child_interrupts(0, 2, 0, 1).unwrap(); - - let want = [0, 5, 4]; - - for (got, want) in irq.irqs.iter().zip(want.iter()) { - assert_eq!(*got, *want); - } - } - - // #[test] - // fn test_debugcon() { - // let raw = fdt_qemu(); - // let fdt = Fdt::from_bytes(&raw).unwrap(); - // let debugcon = fdt.chosen().unwrap().debugcon().unwrap(); - - // match debugcon { - // Some(DebugCon::Node(node)) => { - // println!("Found debugcon node: {:?}", node.name()); - // } - // Some(DebugCon::EarlyConInfo { name, mmio, params }) => { - // println!("Found earlycon info: name={}, mmio={:#x}, params={:?}", name, mmio, params); - // } - // None => { - // println!("No debugcon found"); - // } - // } - // } - - // #[test] - // fn test_debugcon2() { - // let raw = fdt_3568(); - // let fdt = Fdt::from_bytes(&raw).unwrap(); - // let debugcon = fdt.chosen().unwrap().debugcon().unwrap(); - - // match debugcon { - // Some(DebugCon::Node(node)) => { - // println!("Found debugcon node: {:?}", node.name()); - // } - // Some(DebugCon::EarlyConInfo { name, mmio, params }) => { - // println!("Found earlycon info: name={}, mmio={:#x}, params={:?}", name, mmio, params); - // } - // None => { - // println!("No debugcon found"); - // } - // } - // } - - #[test] - fn test_parent_relationships_basic() { - let raw = fdt_reserve(); - let fdt = unsafe { fdt_parser::Fdt::from_ptr(raw.ptr()).unwrap() }; - - // Collect all nodes into Vec for easier lookup - let nodes = fdt.all_nodes(); - - // Test that root node has no parent - let root = nodes.iter().find(|n| n.full_path() == "/").unwrap(); - assert!(root.parent().is_none(), "Root node should have no parent"); - assert_eq!(root.level(), 0); - - // Test that first level nodes have root as parent - let chosen = nodes.iter().find(|n| n.full_path() == "/chosen").unwrap(); - assert_eq!(chosen.parent().unwrap().full_path(), "/"); - assert_eq!(chosen.level(), 1); - - let memory = nodes.iter().find(|n| n.full_path() == "/memory@0").unwrap(); - assert_eq!(memory.parent().unwrap().full_path(), "/"); - assert_eq!(memory.level(), 1); - - let cpus = nodes.iter().find(|n| n.full_path() == "/cpus").unwrap(); - assert_eq!(cpus.parent().unwrap().full_path(), "/"); - assert_eq!(cpus.level(), 1); - - let timer = nodes.iter().find(|n| n.full_path() == "/timer").unwrap(); - assert_eq!(timer.parent().unwrap().full_path(), "/"); - assert_eq!(timer.level(), 1); - - let serial = nodes - .iter() - .find(|n| n.full_path() == "/serial@1c28000") - .unwrap(); - assert_eq!(serial.parent().unwrap().full_path(), "/"); - assert_eq!(serial.level(), 1); - - // Test that second level nodes have correct parent - let cpu0 = nodes - .iter() - .find(|n| n.full_path() == "/cpus/cpu@0") - .unwrap(); - assert_eq!(cpu0.parent().unwrap().full_path(), "/cpus"); - assert_eq!(cpu0.level(), 2); - - let cpu1 = nodes - .iter() - .find(|n| n.full_path() == "/cpus/cpu@1") - .unwrap(); - assert_eq!(cpu1.parent().unwrap().full_path(), "/cpus"); - assert_eq!(cpu1.level(), 2); - } - - #[test] - fn test_parent_relationships_cache() { - let raw = fdt_reserve(); - let fdt = unsafe { fdt_parser::Fdt::from_ptr(raw.ptr()).unwrap() }; - - // Collect all nodes into Vec for easier lookup - let nodes = fdt.all_nodes(); - - // Test that root node has no parent - let root = nodes.iter().find(|n| n.full_path() == "/").unwrap(); - assert!(root.parent().is_none(), "Root node should have no parent"); - assert_eq!(root.level(), 0); - - // Test that first level nodes have root as parent - let chosen = nodes.iter().find(|n| n.full_path() == "/chosen").unwrap(); - assert_eq!(chosen.parent().unwrap().full_path(), "/"); - assert_eq!(chosen.level(), 1); - - let memory = nodes.iter().find(|n| n.full_path() == "/memory@0").unwrap(); - assert_eq!(memory.parent().unwrap().full_path(), "/"); - assert_eq!(memory.level(), 1); - - let cpus = nodes.iter().find(|n| n.full_path() == "/cpus").unwrap(); - assert_eq!(cpus.parent().unwrap().full_path(), "/"); - assert_eq!(cpus.level(), 1); - - let timer = nodes.iter().find(|n| n.full_path() == "/timer").unwrap(); - assert_eq!(timer.parent().unwrap().full_path(), "/"); - assert_eq!(timer.level(), 1); - - let serial = nodes - .iter() - .find(|n| n.full_path() == "/serial@1c28000") - .unwrap(); - assert_eq!(serial.parent().unwrap().full_path(), "/"); - assert_eq!(serial.level(), 1); - - // Test that second level nodes have correct parent - let cpu0 = nodes - .iter() - .find(|n| n.full_path() == "/cpus/cpu@0") - .unwrap(); - assert_eq!(cpu0.parent().unwrap().full_path(), "/cpus"); - assert_eq!(cpu0.level(), 2); - - let cpu1 = nodes - .iter() - .find(|n| n.full_path() == "/cpus/cpu@1") - .unwrap(); - assert_eq!(cpu1.parent().unwrap().full_path(), "/cpus"); - assert_eq!(cpu1.level(), 2); - } - - #[test] - fn test_parent_with_different_dtb() { - // Use only a smaller DTB file to test parent relationships to avoid performance issues - let test_cases = [("Test Reserve", fdt_reserve())]; - - for (name, raw) in test_cases { - let fdt = unsafe { fdt_parser::Fdt::from_ptr(raw.ptr()).unwrap() }; - - // Find root node - let nodes = fdt.all_nodes(); - let root_node = nodes.iter().find(|node| node.full_path() == "/").unwrap(); - - assert!( - root_node.parent().is_none(), - "{}: Root node should have no parent", - name - ); - assert_eq!( - root_node.level(), - 0, - "{}: Root node should be at level 0", - name - ); - - // Find a first level node - let first_level_node = nodes - .iter() - .find(|node| node.level() == 1 && node.full_path() != "/") - .unwrap(); - - assert_eq!( - first_level_node.parent().unwrap().full_path(), - "/", - "{}: First level child's parent should be root", - name - ); - assert_eq!( - first_level_node.level(), - 1, - "{}: First level child should be at level 1", - name - ); - } - } - - #[test] - fn test_parent_edge_cases() { - let raw = fdt_reserve(); - let fdt = unsafe { fdt_parser::Fdt::from_ptr(raw.ptr()).unwrap() }; - - // Test parent node consistency - let nodes = fdt.all_nodes(); - - for node in &nodes { - if let Some(parent) = node.parent() { - // Parent's level should be one less than current node - assert_eq!( - parent.level(), - node.level().saturating_sub(1), - "Parent level should be one less than child for node {}", - node.full_path() - ); - - // If not root node, parent should not be None - if node.level() > 0 { - assert!(parent.parent().is_some() || parent.level() == 0, - "Parent of non-root node should either have a parent or be root for node {}", - node.full_path()); - } - } else { - // Only root node should have no parent - assert_eq!( - node.level(), - 0, - "Only root node should have no parent, but node {} at level {} has none", - node.full_path(), - node.level() - ); - } - } - } -} diff --git a/fdt-raw/Cargo.toml b/fdt-raw/Cargo.toml index 7666ead..ed1177a 100644 --- a/fdt-raw/Cargo.toml +++ b/fdt-raw/Cargo.toml @@ -2,16 +2,12 @@ authors = ["周睿 "] categories = ["embedded", "no-std", "hardware-support"] description = "A low-level, no-std compatible library for parsing Flattened Device Tree (FDT) binary files" -documentation = "https://docs.rs/fdt-raw" edition = "2024" -exclude = [".git*", "*.md", "tests/"] keywords = ["device-tree", "dtb", "embedded", "no-std", "bare-metal"] license = "MIT OR Apache-2.0" name = "fdt-raw" -readme = "README.md" -homepage = "https://github.com/drivercraft/fdt-parser" repository = "https://github.com/drivercraft/fdt-parser" -version = "0.1.6" +version = "0.2.0" [dependencies] heapless = "0.9" @@ -19,12 +15,10 @@ log = "0.4" thiserror = {version = "2", default-features = false} [dev-dependencies] -dtb-file = { path = "../dtb-file" } +dtb-file.workspace = true env_logger = "0.11" [features] -default = [] -std = [] [package.metadata.docs.rs] all-features = true diff --git a/fdt-raw/src/data.rs b/fdt-raw/src/data.rs index 25d9807..4a3412a 100644 --- a/fdt-raw/src/data.rs +++ b/fdt-raw/src/data.rs @@ -10,13 +10,31 @@ use core::{ use crate::define::{FdtError, Token}; +/// Size of a 32-bit (4-byte) value in bytes. +/// Used frequently in FDT parsing for alignment and value sizes. +pub(crate) const U32_SIZE: usize = 4; + +/// Memory reservation entry size in bytes (address + size, each 8 bytes). +pub(crate) const MEM_RSV_ENTRY_SIZE: usize = 16; + /// A view into a byte slice with a specific range. /// /// `Bytes` provides a window into FDT data with range tracking and -/// convenience methods for creating readers and iterators. +/// convenience methods for creating readers and iterators. This allows +/// zero-copy parsing by maintaining references to the original data. +/// +/// # Examples +/// +/// ```ignore +/// let bytes = Bytes::new(&data); +/// let slice = bytes.slice(10..20); +/// let reader = slice.reader(); +/// ``` #[derive(Clone)] pub struct Bytes<'a> { + /// Reference to the complete original data buffer pub(crate) all: &'a [u8], + /// The active range within the original buffer range: Range, } @@ -30,6 +48,12 @@ impl Deref for Bytes<'_> { impl<'a> Bytes<'a> { /// Creates a new `Bytes` from the entire byte slice. + /// + /// # Examples + /// + /// ```ignore + /// let bytes = Bytes::new(&my_data); + /// ``` pub fn new(all: &'a [u8]) -> Self { Self { all, @@ -38,6 +62,10 @@ impl<'a> Bytes<'a> { } /// Creates a new `Bytes` from a subrange of the current data. + /// + /// # Panics + /// + /// Panics if `range.end` exceeds the current length. pub fn slice(&self, range: Range) -> Self { assert!(range.end <= self.len()); Self { @@ -65,6 +93,10 @@ impl<'a> Bytes<'a> { } /// Creates a reader starting at a specific position. + /// + /// # Panics + /// + /// Panics if `position` is greater than or equal to the current length. pub fn reader_at(&self, position: usize) -> Reader<'a> { assert!(position < self.len()); Reader { @@ -96,10 +128,21 @@ impl<'a> Bytes<'a> { /// Sequential reader for parsing FDT data structures. /// /// `Reader` provides sequential read access with position tracking for -/// parsing FDT binary format. +/// parsing FDT binary format. It maintains a current position and can +/// backtrack if needed. +/// +/// # Examples +/// +/// ```ignore +/// let mut reader = bytes.reader(); +/// let value = reader.read_u32()?; +/// let str = reader.read_cstr()?; +/// ``` #[derive(Clone)] pub struct Reader<'a> { + /// The byte slice being read from pub(crate) bytes: Bytes<'a>, + /// Current read position within the bytes pub(crate) iter: usize, } @@ -128,7 +171,7 @@ impl<'a> Reader<'a> { /// Reads a big-endian u32 value. pub fn read_u32(&mut self) -> Option { - let bytes = self.read_bytes(4)?; + let bytes = self.read_bytes(U32_SIZE)?; Some(u32::from_be_bytes(bytes.as_slice().try_into().unwrap())) } @@ -153,7 +196,7 @@ impl<'a> Reader<'a> { /// Reads a token from the FDT structure block. pub fn read_token(&mut self) -> Result { - let bytes = self.read_bytes(4).ok_or(FdtError::BufferTooSmall { + let bytes = self.read_bytes(U32_SIZE).ok_or(FdtError::BufferTooSmall { pos: self.position(), })?; Ok(u32::from_be_bytes(bytes.as_slice().try_into().unwrap()).into()) @@ -167,6 +210,18 @@ impl<'a> Reader<'a> { } /// Iterator over u32 values in FDT data. +/// +/// Reads big-endian u32 values sequentially from the underlying data. +/// Each iteration consumes 4 bytes. +/// +/// # Examples +/// +/// ```ignore +/// let mut iter = bytes.as_u32_iter(); +/// while let Some(value) = iter.next() { +/// println!("Value: {:#x}", value); +/// } +/// ``` #[derive(Clone)] pub struct U32Iter<'a> { /// The underlying reader for accessing FDT data @@ -177,12 +232,24 @@ impl Iterator for U32Iter<'_> { type Item = u32; fn next(&mut self) -> Option { - let bytes = self.reader.read_bytes(4)?; + let bytes = self.reader.read_bytes(U32_SIZE)?; Some(u32::from_be_bytes(bytes.as_slice().try_into().unwrap())) } } /// Iterator over null-terminated strings in FDT data. +/// +/// Reads null-terminated (C-style) strings sequentially from the underlying data. +/// Each iteration consumes the string content plus its null terminator. +/// +/// # Examples +/// +/// ```ignore +/// let mut iter = bytes.as_str_iter(); +/// while let Some(s) = iter.next() { +/// println!("String: {}", s); +/// } +/// ``` #[derive(Clone)] pub struct StrIter<'a> { /// The underlying reader for accessing FDT data diff --git a/fdt-raw/src/define.rs b/fdt-raw/src/define.rs index 4912185..6c11539 100644 --- a/fdt-raw/src/define.rs +++ b/fdt-raw/src/define.rs @@ -166,8 +166,8 @@ pub enum FdtError { #[error("data provided does not contain a nul")] FromBytesUntilNull, /// Failed to parse data as a UTF-8 string - #[error("failed to parse UTF-8 string")] - Utf8Parse, + #[error("{0}")] + Utf8Error(#[from] core::str::Utf8Error), /// The specified alias was not found in the /aliases node #[error("no aliase `{0}` found")] NoAlias(&'static str), @@ -182,11 +182,6 @@ pub enum FdtError { PropertyNotFound(&'static str), } -impl From for FdtError { - fn from(_: core::str::Utf8Error) -> Self { - FdtError::Utf8Parse - } -} impl From for FdtError { fn from(_: FromBytesUntilNulError) -> Self { FdtError::FromBytesUntilNull diff --git a/fdt-raw/src/fdt.rs b/fdt-raw/src/fdt.rs index 48d8f3e..118c07c 100644 --- a/fdt-raw/src/fdt.rs +++ b/fdt-raw/src/fdt.rs @@ -8,7 +8,8 @@ use core::fmt; use crate::{ - Chosen, FdtError, Memory, MemoryReservation, Node, data::Bytes, header::Header, iter::FdtIter, + Chosen, FdtError, Memory, MemoryReservation, Node, Property, VecRange, data, data::Bytes, + fmt_utils, header::Header, iter::FdtIter, }; /// Iterator over memory reservation entries. @@ -25,8 +26,8 @@ impl<'a> Iterator for MemoryReservationIter<'a> { type Item = MemoryReservation; fn next(&mut self) -> Option { - // Ensure we have enough data to read address and size (8 bytes each) - if self.offset + 16 > self.data.len() { + // Ensure we have enough data to read a complete entry + if self.offset + data::MEM_RSV_ENTRY_SIZE > self.data.len() { return None; } @@ -49,14 +50,6 @@ impl<'a> Iterator for MemoryReservationIter<'a> { } } -/// Helper function for writing indentation during formatting. -fn write_indent(f: &mut fmt::Formatter<'_>, count: usize, ch: &str) -> fmt::Result { - for _ in 0..count { - write!(f, "{}", ch)?; - } - Ok(()) -} - /// A parsed Flattened Device Tree (FDT). /// /// This is the main type for working with device tree blobs. It provides @@ -171,6 +164,69 @@ impl<'a> Fdt<'a> { found_node } + /// Find all direct children of a node at the given path. + /// + /// Returns an iterator over all direct child nodes (one level deeper) + /// of the node at the specified path. Returns `None` if the node is + /// not found. + /// + /// Only direct children are yielded — grandchildren and deeper + /// descendants are skipped. + /// + /// # Example + /// + /// ```ignore + /// // List all direct children of /soc + /// if let Some(children) = fdt.find_children_by_path("/soc") { + /// for child in children { + /// println!("{}", child.name()); + /// } + /// } + /// ``` + pub fn find_children_by_path(&self, path: &str) -> ChildrenIter<'a> { + let Some(path) = self.normalize_path(path) else { + return ChildrenIter { + node_iter: self.all_nodes(), + child_level: 0, + done: true, + }; + }; + let split = path.trim_matches('/').split('/'); + + let mut iter = self.all_nodes(); + let mut target_level = 0usize; + + for part in split { + if part.is_empty() { + // Root path "/" — skip the root node itself + iter.next(); + break; + } + let mut found = false; + for node in iter.by_ref() { + if node.name() == part { + found = true; + target_level = node.level(); + break; + } + } + if !found { + return ChildrenIter { + node_iter: self.all_nodes(), + child_level: 0, + done: true, + }; + } + } + + let child_level = target_level + 1; + ChildrenIter { + node_iter: iter, + child_level, + done: false, + } + } + /// Resolve an alias to its full path. /// /// Looks up the alias in the /aliases node and returns the corresponding @@ -213,81 +269,107 @@ impl<'a> Fdt<'a> { /// point (e.g., a parent node has no `ranges` property), the original /// address is returned. pub fn translate_address(&self, path: &'a str, address: u64) -> u64 { + let mut addresses = [address]; + self.translate_addresses(path, &mut addresses); + addresses[0] + } + + /// Translate multiple device addresses to CPU physical addresses in a single pass. + /// + /// This is more efficient than calling `translate_address` multiple times + /// for the same node path, because the tree is walked only once. Each + /// parent node's `ranges` property is looked up once and applied to all + /// addresses in the batch. + /// + /// # Arguments + /// + /// * `path` - Node path (absolute path starting with '/' or alias name) + /// * `addresses` - Mutable slice of device addresses to translate in place. + /// The addresses are modified with the translated CPU physical addresses. + /// + /// If translation fails for any address at any level, the original address + /// value is preserved for that address. + pub fn translate_addresses(&self, path: &'a str, addresses: &mut [u64]) { let path = match self.normalize_path(path) { Some(p) => p, - None => return address, + None => return, }; - // Split path into component parts - let path_parts: heapless::Vec<&str, 16> = path - .trim_matches('/') - .split('/') - .filter(|s| !s.is_empty()) - .collect(); - + let path_parts = Self::split_path(path); if path_parts.is_empty() { - return address; + return; } - let mut current_address = address; + self.translate_addresses_with_parts(&path_parts, addresses); + } + + /// Splits an absolute path into its component parts. + /// + /// Takes a path like "/soc/serial@0" and returns ["soc", "serial@0"]. + fn split_path(path: &str) -> heapless::Vec<&str, 16> { + path.trim_matches('/') + .split('/') + .filter(|s| !s.is_empty()) + .collect() + } + /// Performs batch address translation using pre-split path components. + /// + /// Walks up the tree from the deepest node, applying `ranges` at each level + /// to all addresses in the slice. Each parent node is looked up only once. + fn translate_addresses_with_parts(&self, path_parts: &[&str], addresses: &mut [u64]) { // Walk up from the deepest node, applying ranges at each level - // Note: We start from the second-to-last level (the target node itself is skipped) + // We start from the second-to-last level (the target node itself is skipped) for depth in (0..path_parts.len()).rev() { - // Build the path to the current parent level let parent_parts = &path_parts[..depth]; + if parent_parts.is_empty() { // Reached root node, no more translation needed break; } - // Find the parent node - let mut parent_path = heapless::String::<256>::new(); - parent_path.push('/').ok(); - for (i, part) in parent_parts.iter().enumerate() { - if i > 0 { - parent_path.push('/').ok(); - } - parent_path.push_str(part).ok(); - } - - let parent_node = match self.find_by_path(parent_path.as_str()) { - Some(node) => node, - None => continue, - }; + if let Some(parent_node) = self.find_node_by_parts(parent_parts) { + let ranges = match parent_node.ranges() { + Some(r) => r, + None => break, // No ranges property, stop translation + }; - // Get the parent's ranges property - let ranges = match parent_node.ranges() { - Some(r) => r, - None => { - // No ranges property, stop translation - break; + // Apply ranges to all addresses in batch + for addr in addresses.iter_mut() { + *addr = Self::apply_ranges_one(&ranges, *addr); } - }; + } + } + } - // Look for a matching translation rule in ranges - let mut found = false; - for range in ranges.iter() { - // Check if the address falls within this range - if current_address >= range.child_address - && current_address < range.child_address + range.length - { - // Calculate offset in child address space - let offset = current_address - range.child_address; - // Translate to parent address space - current_address = range.parent_address + offset; - found = true; - break; - } + /// Finds a node by its path component parts. + fn find_node_by_parts(&self, parts: &[&str]) -> Option> { + let mut path = heapless::String::<256>::new(); + path.push('/').ok(); + for (i, part) in parts.iter().enumerate() { + if i > 0 { + path.push('/').ok(); } + path.push_str(part).ok(); + } + self.find_by_path(path.as_str()) + } - if !found { - // No matching range found, keep current address and continue - // This typically means translation failed, but we try upper levels + /// Translates a single address using the given ranges. + /// + /// If the address falls within a range, it is translated. Otherwise, + /// the original address is returned unchanged. + fn apply_ranges_one(ranges: &VecRange<'_>, address: u64) -> u64 { + for range in ranges.iter() { + // Check if the address falls within this range + if address >= range.child_address && address < range.child_address + range.length { + let offset = address - range.child_address; + return range.parent_address + offset; } } - current_address + // No matching range found, return as-is + address } /// Returns an iterator over memory reservation entries. @@ -364,49 +446,108 @@ impl<'a> Iterator for ReservedMemoryIter<'a> { } } +/// Iterator over direct children of a specific node. +/// +/// Yields only nodes whose level equals `child_level`. Nodes deeper +/// than `child_level` (grandchildren) are skipped, and iteration stops +/// when leaving the parent's subtree (level < child_level). +pub struct ChildrenIter<'a> { + node_iter: FdtIter<'a>, + child_level: usize, + done: bool, +} + +impl<'a> Iterator for ChildrenIter<'a> { + type Item = Node<'a>; + + fn next(&mut self) -> Option { + if self.done { + return None; + } + for node in self.node_iter.by_ref() { + if node.level() == self.child_level { + return Some(node); + } + if node.level() < self.child_level { + // Left the parent's subtree + self.done = true; + return None; + } + // node.level() > self.child_level: grandchild, skip + } + None + } +} + impl fmt::Display for Fdt<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { writeln!(f, "/dts-v1/;")?; writeln!(f)?; - let mut prev_level = 0; + let mut state = DisplayState::new(); for node in self.all_nodes() { - let level = node.level(); + self.close_open_nodes(f, &mut state, node.level())?; + self.write_node(f, &node)?; + state.prev_level = node.level() + 1; + } - // Close nodes from the previous level - while prev_level > level { - prev_level -= 1; - write_indent(f, prev_level, " ")?; - writeln!(f, "}};\n")?; - } + self.close_all_nodes(f, &mut state) + } +} - write_indent(f, level, " ")?; - let name = if node.name().is_empty() { - "/" - } else { - node.name() - }; +/// State for tracking the display output during tree traversal. +struct DisplayState { + prev_level: usize, +} - // Print node header - writeln!(f, "{} {{", name)?; +impl DisplayState { + fn new() -> Self { + Self { prev_level: 0 } + } +} - // Print properties - for prop in node.properties() { - write_indent(f, level + 1, " ")?; - writeln!(f, "{};", prop)?; - } +impl Fdt<'_> { + /// Writes a single node in DTS format. + fn write_node(&self, f: &mut fmt::Formatter<'_>, node: &Node<'_>) -> fmt::Result { + fmt_utils::write_indent(f, node.level(), " ")?; + let name = Self::format_node_name(node.name()); + writeln!(f, "{} {{", name)?; - prev_level = level + 1; + for prop in node.properties() { + fmt_utils::write_indent(f, node.level() + 1, " ")?; + writeln!(f, "{};", prop)?; } + Ok(()) + } - // Close remaining nodes - while prev_level > 0 { - prev_level -= 1; - write_indent(f, prev_level, " ")?; + /// Formats a node name, replacing empty names with "/". + fn format_node_name(name: &str) -> &str { + if name.is_empty() { "/" } else { name } + } + + /// Closes nodes that are no longer open in the tree traversal. + fn close_open_nodes( + &self, + f: &mut fmt::Formatter<'_>, + state: &mut DisplayState, + current_level: usize, + ) -> fmt::Result { + while state.prev_level > current_level { + state.prev_level -= 1; + fmt_utils::write_indent(f, state.prev_level, " ")?; writeln!(f, "}};\n")?; } + Ok(()) + } + /// Closes all remaining open nodes at the end of output. + fn close_all_nodes(&self, f: &mut fmt::Formatter<'_>, state: &mut DisplayState) -> fmt::Result { + while state.prev_level > 0 { + state.prev_level -= 1; + fmt_utils::write_indent(f, state.prev_level, " ")?; + writeln!(f, "}};\n")?; + } Ok(()) } } @@ -418,53 +559,70 @@ impl fmt::Debug for Fdt<'_> { writeln!(f, "\tnodes:")?; for node in self.all_nodes() { - let level = node.level(); - // Base indentation is 2 tabs, plus 1 tab per level - write_indent(f, level + 2, "\t")?; + self.debug_node(f, &node)?; + } - let name = if node.name().is_empty() { - "/" - } else { - node.name() - }; + writeln!(f, "}}") + } +} + +impl Fdt<'_> { + /// Writes a node in debug format. + fn debug_node(&self, f: &mut fmt::Formatter<'_>, node: &Node<'_>) -> fmt::Result { + let level = node.level(); + fmt_utils::write_indent(f, level + 2, "\t")?; + + let name = Self::format_node_name(node.name()); + writeln!( + f, + "[{}] address_cells={}, size_cells={}", + name, node.address_cells, node.size_cells + )?; + + for prop in node.properties() { + self.debug_property(f, level, &prop)?; + } + Ok(()) + } - // Print node name and basic info - writeln!( + /// Writes a property in debug format. + fn debug_property( + &self, + f: &mut fmt::Formatter<'_>, + level: usize, + prop: &Property<'_>, + ) -> fmt::Result { + fmt_utils::write_indent(f, level + 3, "\t")?; + + match () { + () if prop.as_address_cells().is_some() => { + writeln!(f, "#address-cells: {}", prop.as_address_cells().unwrap())? + } + () if prop.as_size_cells().is_some() => { + writeln!(f, "#size-cells: {}", prop.as_size_cells().unwrap())? + } + () if prop.as_interrupt_cells().is_some() => writeln!( f, - "[{}] address_cells={}, size_cells={}", - name, node.address_cells, node.size_cells - )?; - - // Print properties - for prop in node.properties() { - write_indent(f, level + 3, "\t")?; - if let Some(v) = prop.as_address_cells() { - writeln!(f, "#address-cells: {}", v)?; - } else if let Some(v) = prop.as_size_cells() { - writeln!(f, "#size-cells: {}", v)?; - } else if let Some(v) = prop.as_interrupt_cells() { - writeln!(f, "#interrupt-cells: {}", v)?; - } else if let Some(s) = prop.as_status() { - writeln!(f, "status: {:?}", s)?; - } else if let Some(p) = prop.as_phandle() { - writeln!(f, "phandle: {}", p)?; - } else { - // Default handling for unknown properties - if prop.is_empty() { - writeln!(f, "{}", prop.name())?; - } else if let Some(s) = prop.as_str() { - writeln!(f, "{}: \"{}\"", prop.name(), s)?; - } else if prop.len() == 4 { - let v = u32::from_be_bytes(prop.data().as_slice().try_into().unwrap()); - writeln!(f, "{}: {:#x}", prop.name(), v)?; - } else { - writeln!(f, "{}: <{} bytes>", prop.name(), prop.len())?; - } - } + "#interrupt-cells: {}", + prop.as_interrupt_cells().unwrap() + )?, + () if prop.as_status().is_some() => { + writeln!(f, "status: {:?}", prop.as_status().unwrap())? + } + () if prop.as_phandle().is_some() => { + writeln!(f, "phandle: {}", prop.as_phandle().unwrap())? + } + () if prop.is_empty() => writeln!(f, "{}", prop.name())?, + () if prop.as_str().is_some() => { + writeln!(f, "{}: \"{}\"", prop.name(), prop.as_str().unwrap())? + } + () if prop.len() == 4 => { + let v = u32::from_be_bytes(prop.data().as_slice().try_into().unwrap()); + writeln!(f, "{}: {:#x}", prop.name(), v)? } + () => writeln!(f, "{}: <{} bytes>", prop.name(), prop.len())?, } - - writeln!(f, "}}") + Ok(()) } } @@ -476,7 +634,7 @@ mod tests { #[test] fn test_memory_reservation_iterator() { // Create simple test data: one memory reservation entry + terminator - let mut test_data = [0u8; 32]; + let mut test_data = [0u8; data::MEM_RSV_ENTRY_SIZE * 2]; // Address: 0x80000000, Size: 0x10000000 (256MB) test_data[0..8].copy_from_slice(&0x80000000u64.to_be_bytes()); @@ -499,7 +657,7 @@ mod tests { #[test] fn test_empty_memory_reservation_iterator() { // Only terminator - let mut test_data = [0u8; 16]; + let mut test_data = [0u8; data::MEM_RSV_ENTRY_SIZE]; test_data[0..8].copy_from_slice(&0u64.to_be_bytes()); test_data[8..16].copy_from_slice(&0u64.to_be_bytes()); diff --git a/fdt-raw/src/fmt_utils.rs b/fdt-raw/src/fmt_utils.rs new file mode 100644 index 0000000..9fe0ec6 --- /dev/null +++ b/fdt-raw/src/fmt_utils.rs @@ -0,0 +1,23 @@ +//! Formatting utilities for FDT display output. +//! +//! This module provides helper functions for formatting device tree +//! structures with proper indentation. + +use core::fmt; + +/// Writes indentation to a formatter. +/// +/// Repeats the specified character `count` times, used for +/// indentation when displaying device tree structures. +/// +/// # Arguments +/// +/// * `f` - The formatter to write to +/// * `count` - Number of times to repeat the character +/// * `ch` - The character to use for indentation +pub fn write_indent(f: &mut fmt::Formatter<'_>, count: usize, ch: &str) -> fmt::Result { + for _ in 0..count { + write!(f, "{}", ch)?; + } + Ok(()) +} diff --git a/fdt-raw/src/header.rs b/fdt-raw/src/header.rs index 54dc2db..bd09125 100644 --- a/fdt-raw/src/header.rs +++ b/fdt-raw/src/header.rs @@ -7,6 +7,7 @@ use core::ptr::NonNull; use crate::FdtError; +use crate::data::U32_SIZE; /// A 4-byte aligned buffer for header data. /// @@ -83,7 +84,7 @@ impl Header { /// Returns `FdtError::InvalidPtr` if the pointer is null, or /// `FdtError::InvalidMagic` if the magic number doesn't match. pub unsafe fn from_ptr(ptr: *mut u8) -> Result { - if !(ptr as usize).is_multiple_of(core::mem::align_of::
()) { + if !(ptr as usize).is_multiple_of(Self::alignment()) { // Pointer is not aligned, so we need to copy the data to an aligned // buffer first. let mut aligned = AlignedHeader([0u8; core::mem::size_of::
()]); @@ -137,4 +138,10 @@ impl Header { size_dt_struct: u32::from_be(raw.size_dt_struct), }) } + + /// Returns the required alignment for FDT structures. + #[inline] + pub const fn alignment() -> usize { + U32_SIZE + } } diff --git a/fdt-raw/src/iter.rs b/fdt-raw/src/iter.rs index 2090ff1..0a1f344 100644 --- a/fdt-raw/src/iter.rs +++ b/fdt-raw/src/iter.rs @@ -29,6 +29,8 @@ pub struct FdtIter<'a> { level: usize, /// Context stack, with the top being the current context context_stack: heapless::Vec, + /// Path stack tracking the current path components from root + path_stack: heapless::Vec<&'a str, 16>, } impl<'a> FdtIter<'a> { @@ -60,17 +62,27 @@ impl<'a> FdtIter<'a> { level: 0, finished: false, context_stack, + path_stack: heapless::Vec::new(), } } /// Returns the current context (top of the stack). + /// + /// # Safety + /// + /// The stack is never empty because a default context is pushed on + /// initialization in `FdtIter::new`. #[inline] fn current_context(&self) -> &NodeContext { - // The stack is never empty because we push a default context on initialization + // SAFETY: The stack is initialized with a default context and is never + // completely emptied during iteration. self.context_stack.last().unwrap() } /// Handles an error by logging it and terminating iteration. + /// + /// When an error occurs during FDT parsing, we log it and stop iteration + /// rather than panicking. This allows partial parsing and graceful degradation. fn handle_error(&mut self, err: FdtError) { error!("FDT parse error: {}", err); self.finished = true; @@ -103,6 +115,7 @@ impl<'a> Iterator for FdtIter<'a> { self.level -= 1; // Pop stack to restore parent node context self.context_stack.pop(); + self.path_stack.pop(); } // Continue loop to process next token } @@ -131,7 +144,7 @@ impl<'a> Iterator for FdtIter<'a> { ); // Read node name - match node_iter.read_node_name() { + match node_iter.read_node_name(&self.path_stack) { Ok(mut node) => { // Process node properties to get address-cells, size-cells match node_iter.process() { @@ -154,6 +167,10 @@ impl<'a> Iterator for FdtIter<'a> { // Has child nodes, update reader position self.reader = node_iter.reader().clone(); + // Push current node name onto path stack + if !node.name().is_empty() { + let _ = self.path_stack.push(node.name()); + } // Increase level (node has children) self.level += 1; } @@ -190,6 +207,7 @@ impl<'a> Iterator for FdtIter<'a> { self.level -= 1; // Pop stack to restore parent node context self.context_stack.pop(); + self.path_stack.pop(); } continue; } diff --git a/fdt-raw/src/lib.rs b/fdt-raw/src/lib.rs index b54c7a1..99ed732 100644 --- a/fdt-raw/src/lib.rs +++ b/fdt-raw/src/lib.rs @@ -47,6 +47,8 @@ mod header; mod iter; mod node; +mod fmt_utils; + pub use define::*; pub use fdt::Fdt; pub use header::Header; diff --git a/fdt-raw/src/node/mod.rs b/fdt-raw/src/node/mod.rs index 9137044..75db6ae 100644 --- a/fdt-raw/src/node/mod.rs +++ b/fdt-raw/src/node/mod.rs @@ -10,9 +10,10 @@ use core::ops::Deref; use core::{ffi::CStr, fmt::Debug}; use crate::Fdt; +use crate::fmt_utils; use crate::{ FdtError, Token, - data::{Bytes, Reader}, + data::{Bytes, Reader, U32_SIZE}, }; mod chosen; @@ -26,7 +27,13 @@ pub use prop::{PropIter, Property, RangeInfo, RegInfo, RegIter, VecRange}; /// Context inherited from a node's parent. /// /// Contains the `#address-cells` and `#size-cells` values that should -/// be used when parsing properties of the current node. +/// be used when parsing properties of the current node. These values +/// are inherited from the parent node unless overridden. +/// +/// # Default Values +/// +/// The root node defaults to `#address-cells = 2` and `#size-cells = 1` +/// per the Device Tree specification. #[derive(Clone)] pub(crate) struct NodeContext { /// Parent node's #address-cells (used for parsing current node's reg) @@ -61,6 +68,8 @@ pub struct NodeBase<'a> { pub size_cells: u8, /// Inherited context (contains parent's cells) context: NodeContext, + /// Path components from root to this node + path_components: heapless::Vec<&'a str, 16>, } impl<'a> NodeBase<'a> { @@ -140,27 +149,36 @@ impl<'a> NodeBase<'a> { .into_iter() .flat_map(|p| p.as_str_iter()) } -} -/// Helper function for writing indentation during formatting. -fn write_indent(f: &mut fmt::Formatter<'_>, count: usize, ch: &str) -> fmt::Result { - for _ in 0..count { - write!(f, "{}", ch)?; + /// Returns the full path of this node as a string. + /// + /// For the root node, returns "/". For other nodes, returns the + /// absolute path like "/soc/serial@0". + pub fn path(&self) -> heapless::String<256> { + let mut result = heapless::String::new(); + if self.path_components.is_empty() { + let _ = result.push('/'); + return result; + } + for component in &self.path_components { + let _ = result.push('/'); + let _ = result.push_str(component); + } + result } - Ok(()) } impl fmt::Display for NodeBase<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write_indent(f, self.level, " ")?; + fmt_utils::write_indent(f, self.level, " ")?; let name = if self.name.is_empty() { "/" } else { self.name }; writeln!(f, "{} {{", name)?; for prop in self.properties() { - write_indent(f, self.level + 1, " ")?; + fmt_utils::write_indent(f, self.level + 1, " ")?; writeln!(f, "{};", prop)?; } - write_indent(f, self.level, " ")?; + fmt_utils::write_indent(f, self.level, " ")?; write!(f, "}}") } } @@ -231,13 +249,16 @@ pub(crate) struct ParsedProps { } /// State of a single node iteration. +/// +/// Tracks the current state while parsing a single node's content. +/// Used internally by `OneNodeIter` to communicate with `FdtIter`. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub(crate) enum OneNodeState { - /// Currently processing the node + /// Currently processing the node (reading properties) Processing, - /// Encountered a child's BeginNode, needs to backtrack + /// Encountered a child's BeginNode token, needs to backtrack ChildBegin, - /// Encountered EndNode, current node processing complete + /// Encountered EndNode token, current node processing complete End, } @@ -246,13 +267,26 @@ pub(crate) enum OneNodeState { /// When encountering a child's BeginNode token, it backtracks and signals /// FdtIter to handle the child node. This allows FdtIter to maintain /// proper tree traversal state. +/// +/// # Implementation Notes +/// +/// This iterator is `pub(crate)` because it's an internal implementation +/// detail of the FDT parsing machinery. External consumers should use +/// `FdtIter` or `NodeBase::properties()` instead. pub(crate) struct OneNodeIter<'a> { + /// Reader for the node's property data reader: Reader<'a>, + /// Strings block for looking up property names strings: Bytes<'a>, + /// Current iteration state state: OneNodeState, + /// Depth level of this node in the tree level: usize, + /// Inherited context from parent (address_cells, size_cells) context: NodeContext, + /// Extracted properties (#address-cells, #size-cells) parsed_props: ParsedProps, + /// Reference to the containing FDT for path resolution fdt: Fdt<'a>, } @@ -281,13 +315,20 @@ impl<'a> OneNodeIter<'a> { &self.reader } - /// Returns the parsed properties. + /// Returns the parsed properties extracted from this node. pub fn parsed_props(&self) -> &ParsedProps { &self.parsed_props } /// Reads the node name (called after BeginNode token). - pub fn read_node_name(&mut self) -> Result, FdtError> { + /// + /// Reads the null-terminated node name and aligns to a 4-byte boundary. + /// Returns a partially-constructed `NodeBase` with default cell values + /// that will be updated by `process()`. + pub fn read_node_name( + &mut self, + parent_path: &heapless::Vec<&'a str, 16>, + ) -> Result, FdtError> { // Read null-terminated name string let name = self.read_cstr()?; @@ -296,6 +337,12 @@ impl<'a> OneNodeIter<'a> { let data = self.reader.remain(); + // Build path components: parent path + current node name + let mut path_components = parent_path.clone(); + if !name.is_empty() { + let _ = path_components.push(name); + } + Ok(NodeBase { name, data, @@ -306,10 +353,11 @@ impl<'a> OneNodeIter<'a> { size_cells: 1, context: self.context.clone(), _fdt: self.fdt.clone(), + path_components, }) } - /// Reads a null-terminated string. + /// Reads a null-terminated string from the current position. fn read_cstr(&mut self) -> Result<&'a str, FdtError> { let bytes = self.reader.remain(); let cstr = CStr::from_bytes_until_nul(bytes.as_slice())?; @@ -320,9 +368,12 @@ impl<'a> OneNodeIter<'a> { } /// Aligns the reader to a 4-byte boundary. + /// + /// FDT structures are 4-byte aligned, so after reading variable-length + /// data (like node names), we need to pad to the next 4-byte boundary. fn align4(&mut self) { let pos = self.reader.position(); - let aligned = (pos + 3) & !3; + let aligned = (pos + U32_SIZE - 1) & !(U32_SIZE - 1); let skip = aligned - pos; if skip > 0 { let _ = self.reader.read_bytes(skip); @@ -330,25 +381,39 @@ impl<'a> OneNodeIter<'a> { } /// Reads a property name from the strings block. + /// + /// Property names are stored as offsets into the strings block, + /// not inline with the property data. fn read_prop_name(&self, nameoff: u32) -> Result<&'a str, FdtError> { let bytes = self.strings.slice(nameoff as usize..self.strings.len()); let cstr = CStr::from_bytes_until_nul(bytes.as_slice())?; Ok(cstr.to_str()?) } - /// Reads a u32 from big-endian bytes. + /// Reads a u32 value from big-endian bytes at the given offset. fn read_u32_be(data: &[u8], offset: usize) -> u64 { - u32::from_be_bytes(data[offset..offset + 4].try_into().unwrap()) as u64 + u32::from_be_bytes(data[offset..offset + U32_SIZE].try_into().unwrap()) as u64 } - /// Processes node content, parsing key properties until child node or end. + /// Processes node content, parsing properties until child node or end. + /// + /// This is the core parsing loop for a node. It reads tokens sequentially: + /// - Properties are parsed and `#address-cells`/`#size-cells` are extracted + /// - Child nodes cause backtracking and return `ChildBegin` + /// - EndNode terminates processing and returns `End` + /// + /// # Returns + /// + /// - `Ok(OneNodeState::ChildBegin)` if a child node was found + /// - `Ok(OneNodeState::End)` if the node ended + /// - `Err(FdtError)` if parsing failed pub fn process(&mut self) -> Result { loop { let token = self.reader.read_token()?; match token { Token::BeginNode => { // Child node encountered, backtrack token and return - self.reader.backtrack(4); + self.reader.backtrack(U32_SIZE); self.state = OneNodeState::ChildBegin; return Ok(OneNodeState::ChildBegin); } diff --git a/fdt-raw/src/node/prop/mod.rs b/fdt-raw/src/node/prop/mod.rs index 49736b0..cf002c8 100644 --- a/fdt-raw/src/node/prop/mod.rs +++ b/fdt-raw/src/node/prop/mod.rs @@ -17,7 +17,7 @@ pub use reg::{RegInfo, RegIter}; use crate::{ FdtError, Phandle, Status, Token, - data::{Bytes, Reader, StrIter, U32Iter}, + data::{Bytes, Reader, StrIter, U32_SIZE, U32Iter}, }; /// A generic device tree property containing name and raw data. @@ -221,78 +221,109 @@ impl<'a> Property<'a> { impl fmt::Display for Property<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { if self.is_empty() { - write!(f, "{}", self.name()) - } else if let Some(v) = self.as_address_cells() { - write!(f, "#address-cells = <{:#x}>", v) - } else if let Some(v) = self.as_size_cells() { - write!(f, "#size-cells = <{:#x}>", v) - } else if let Some(v) = self.as_interrupt_cells() { - write!(f, "#interrupt-cells = <{:#x}>", v) - } else if self.name() == "reg" { - // reg property needs special handling, but we lack context info - // Display raw data - write!(f, "reg = ")?; - format_bytes(f, &self.data()) - } else if let Some(s) = self.as_status() { - write!(f, "status = \"{:?}\"", s) - } else if let Some(p) = self.as_phandle() { - write!(f, "phandle = {}", p) - } else if let Some(p) = self.as_interrupt_parent() { - write!(f, "interrupt-parent = {}", p) - } else if let Some(s) = self.as_device_type() { - write!(f, "device_type = \"{}\"", s) - } else if let Some(iter) = self.as_compatible() { - write!(f, "compatible = ")?; - let mut first = true; - for s in iter.clone() { - if !first { - write!(f, ", ")?; - } - write!(f, "\"{}\"", s)?; - first = false; - } - Ok(()) - } else if let Some(iter) = self.as_clock_names() { - write!(f, "clock-names = ")?; - let mut first = true; - for s in iter.clone() { - if !first { - write!(f, ", ")?; - } - write!(f, "\"{}\"", s)?; - first = false; - } - Ok(()) - } else if self.is_dma_coherent() { - write!(f, "dma-coherent") - } else if let Some(s) = self.as_str() { - // Check if there are multiple strings - if self.data().iter().filter(|&&b| b == 0).count() > 1 { - write!(f, "{} = ", self.name())?; - let mut first = true; - for s in self.as_str_iter() { - if !first { - write!(f, ", ")?; - } - write!(f, "\"{}\"", s)?; - first = false; - } - Ok(()) - } else { - write!(f, "{} = \"{}\"", self.name(), s) + return write!(f, "{}", self.name()); + } + + // Try typed formatters first + if let Some(result) = self.try_format_typed(f) { + return result; + } + + // Named properties with special handling + match self.name() { + "reg" => { + write!(f, "reg = ")?; + format_bytes(f, &self.data()) } - } else if self.len() == 4 { - // Single u32 + _ => self.format_generic(f), + } + } +} + +impl Property<'_> { + /// Attempts to format the property using its specific type formatter. + /// Returns `Some(result)` if a specific formatter was used, `None` otherwise. + fn try_format_typed(&self, f: &mut fmt::Formatter<'_>) -> Option { + if let Some(v) = self.as_address_cells() { + return Some(write!(f, "#address-cells = <{:#x}>", v)); + } + if let Some(v) = self.as_size_cells() { + return Some(write!(f, "#size-cells = <{:#x}>", v)); + } + if let Some(v) = self.as_interrupt_cells() { + return Some(write!(f, "#interrupt-cells = <{:#x}>", v)); + } + if let Some(s) = self.as_status() { + return Some(write!(f, "status = \"{:?}\"", s)); + } + if let Some(p) = self.as_phandle() { + return Some(write!(f, "phandle = {}", p)); + } + if let Some(p) = self.as_interrupt_parent() { + return Some(write!(f, "interrupt-parent = {}", p)); + } + if let Some(s) = self.as_device_type() { + return Some(write!(f, "device_type = \"{}\"", s)); + } + if let Some(iter) = self.as_compatible() { + return Some(format_string_list(f, "compatible", iter)); + } + if let Some(iter) = self.as_clock_names() { + return Some(format_string_list(f, "clock-names", iter)); + } + if self.is_dma_coherent() { + return Some(write!(f, "dma-coherent")); + } + None + } + + /// Formats the property as a generic value (string, number, or bytes). + fn format_generic(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // Check for multiple strings + if self.has_multiple_strings() { + return format_string_list(f, self.name(), self.as_str_iter()); + } + + // Try as single string + if let Some(s) = self.as_str() { + return write!(f, "{} = \"{}\"", self.name(), s); + } + + // Try as single u32 + if self.len() == 4 { let v = u32::from_be_bytes(self.data().as_slice().try_into().unwrap()); - write!(f, "{} = <{:#x}>", self.name(), v) - } else { - // Raw bytes - write!(f, "{} = ", self.name())?; - format_bytes(f, &self.data()) + return write!(f, "{} = <{:#x}>", self.name(), v); } + + // Default to raw bytes + write!(f, "{} = ", self.name())?; + format_bytes(f, &self.data()) + } + + /// Checks if the property data contains multiple null-terminated strings. + fn has_multiple_strings(&self) -> bool { + self.data().iter().filter(|&&b| b == 0).count() > 1 } } +/// Formats a list of strings as "name = "s1", "s2"". +fn format_string_list<'a>( + f: &mut fmt::Formatter<'_>, + name: &str, + iter: impl Iterator, +) -> fmt::Result { + write!(f, "{} = ", name)?; + let mut first = true; + for s in iter { + if !first { + write!(f, ", ")?; + } + write!(f, "\"{}\"", s)?; + first = false; + } + Ok(()) +} + /// Formats a byte array as DTS format. fn format_bytes(f: &mut fmt::Formatter<'_>, data: &[u8]) -> fmt::Result { if data.len().is_multiple_of(4) { @@ -324,10 +355,22 @@ fn format_bytes(f: &mut fmt::Formatter<'_>, data: &[u8]) -> fmt::Result { /// Property iterator. /// /// Iterates over properties within a node, parsing each property from the -/// device tree structure block. +/// device tree structure block. Properties are read sequentially until +/// a node boundary (BeginNode, EndNode, or End token) is encountered. +/// +/// # Examples +/// +/// ```ignore +/// for prop in node.properties() { +/// println!("{}: {}", prop.name(), prop.len()); +/// } +/// ``` pub struct PropIter<'a> { + /// Reader for the property data reader: Reader<'a>, + /// Strings block for resolving property names strings: Bytes<'a>, + /// Whether iteration has terminated (due to error or boundary) finished: bool, } @@ -363,7 +406,7 @@ impl<'a> PropIter<'a> { /// Aligns the reader to a 4-byte boundary. fn align4(&mut self) { let pos = self.reader.position(); - let aligned = (pos + 3) & !3; + let aligned = (pos + U32_SIZE - 1) & !(U32_SIZE - 1); let skip = aligned - pos; if skip > 0 { let _ = self.reader.read_bytes(skip); @@ -443,7 +486,7 @@ impl<'a> Iterator for PropIter<'a> { } Token::BeginNode | Token::EndNode | Token::End => { // Encountered node boundary, backtrack and terminate property iteration - self.reader.backtrack(4); + self.reader.backtrack(U32_SIZE); self.finished = true; return None; } diff --git a/fdt-raw/src/node/prop/reg.rs b/fdt-raw/src/node/prop/reg.rs index 66284e8..a8bfa29 100644 --- a/fdt-raw/src/node/prop/reg.rs +++ b/fdt-raw/src/node/prop/reg.rs @@ -2,6 +2,13 @@ //! //! This module provides types for parsing the `reg` property, which describes //! memory-mapped registers and address ranges for devices. +//! +//! The `reg` property format is: +//! ```text +//! reg = ; +//! ``` +//! where each address and size uses `#address-cells` and `#size-cells` +//! u32 values respectively, inherited from the parent node. use crate::data::Reader; @@ -9,17 +16,22 @@ use crate::data::Reader; /// /// Represents a single entry in a `reg` property, describing an address /// range for a device's registers or memory. +/// +/// # Fields +/// +/// * `address` - The base address of the register range or memory region +/// * `size` - The size of the range (may be `None` if `#size-cells` is 0) #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct RegInfo { - /// Base address + /// Base address of the register/memory range pub address: u64, - /// Region size (optional, as size can be 0) + /// Size of the range (None if #size-cells is 0) pub size: Option, } impl RegInfo { - /// Creates a new RegInfo. - pub fn new(address: u64, size: Option) -> Self { + /// Creates a new RegInfo with the given address and optional size. + pub const fn new(address: u64, size: Option) -> Self { Self { address, size } } } @@ -28,6 +40,21 @@ impl RegInfo { /// /// Iterates over entries in a `reg` property, parsing address and size /// values based on the parent node's #address-cells and #size-cells values. +/// +/// # Cell Values +/// +/// - `#address-cells` determines how many u32 values form each address +/// - `#size-cells` determines how many u32 values form each size +/// - Common values: 1 for 32-bit addresses, 2 for 64-bit addresses +/// +/// # Examples +/// +/// ```ignore +/// // Assuming #address-cells = 2, #size-cells = 1 +/// for reg in node.reg() { +/// println!("Address: {:#x}, Size: {:#x}", reg.address, reg.size.unwrap_or(0)); +/// } +/// ``` #[derive(Clone)] pub struct RegIter<'a> { reader: Reader<'a>, diff --git a/fdt-raw/tests/node.rs b/fdt-raw/tests/node.rs index f8b7a5d..6319aaa 100644 --- a/fdt-raw/tests/node.rs +++ b/fdt-raw/tests/node.rs @@ -551,13 +551,13 @@ fn test_memory_in_fdt(raw: &[u8], name: &str) { name, i, reg_info.address, reg_info.size ); - // Basic verification: address should be valid - if reg_info.size.is_some() && reg_info.size.unwrap() > 0 { - assert!( - reg_info.size.unwrap() > 0, - "Memory size should be positive, got {:?}", - reg_info.size - ); + // Basic verification: if size is present and positive, verify it + if let Some(size) = reg_info.size { + if size > 0 { + info!("[{}] Memory size is positive: {}", name, size); + } else { + info!("[{}] Memory size is 0", name); + } } } @@ -611,3 +611,332 @@ fn test_compatibles() { info!("compatible: {}", compatible); } } + +#[test] +fn test_node_path_root() { + init_logging(); + let raw = fdt_qemu(); + let fdt = Fdt::from_bytes(&raw).unwrap(); + + // The first node (root) should have path "/" + let root = fdt.all_nodes().next().unwrap(); + assert_eq!(root.name(), ""); + assert_eq!(root.path().as_str(), "/"); +} + +#[test] +fn test_node_path_all_nodes() { + init_logging(); + let raw = fdt_qemu(); + let fdt = Fdt::from_bytes(&raw).unwrap(); + + for node in fdt.all_nodes() { + let path = node.path(); + info!("node: {} -> path: {}", node.name(), path); + + // All paths must start with '/' + assert!( + path.starts_with('/'), + "Path should start with '/', got: {}", + path + ); + + // Root node special case + if node.name().is_empty() { + assert_eq!(path.as_str(), "/"); + } else { + // Non-root nodes: path should end with the node name + assert!( + path.ends_with(node.name()), + "Path '{}' should end with node name '{}'", + path, + node.name() + ); + // Path should not have double slashes + assert!( + !path.contains("//"), + "Path should not contain '//': {}", + path + ); + } + } +} + +#[test] +fn test_node_path_known_nodes() { + init_logging(); + let raw = fdt_qemu(); + let fdt = Fdt::from_bytes(&raw).unwrap(); + + // Collect all paths + let paths: Vec = fdt.all_nodes().map(|n| n.path().to_string()).collect(); + + // Verify known paths exist + let expected_paths = ["/", "/memory@40000000", "/chosen"]; + for expected in expected_paths { + assert!( + paths.iter().any(|p| p == expected), + "Expected path '{}' not found in: {:?}", + expected, + paths + ); + } +} + +#[test] +fn test_node_path_find_by_path_consistency() { + init_logging(); + let raw = fdt_qemu(); + let fdt = Fdt::from_bytes(&raw).unwrap(); + + // For each node, its path() should be findable via find_by_path + for node in fdt.all_nodes() { + let path = node.path(); + let found = fdt.find_by_path(path.as_str()); + assert!( + found.is_some(), + "Node with path '{}' (name='{}') should be findable via find_by_path", + path, + node.name() + ); + assert_eq!( + found.unwrap().name(), + node.name(), + "find_by_path('{}') returned node with wrong name", + path + ); + } +} + +#[test] +fn test_node_path_depth() { + init_logging(); + let raw = fdt_rpi_4b(); + let fdt = Fdt::from_bytes(&raw).unwrap(); + + for node in fdt.all_nodes() { + let path = node.path(); + let level = node.level(); + // Number of '/' in path should equal the level for non-root, or 1 for root + let slash_count = path.chars().filter(|&c| c == '/').count(); + if level == 0 { + assert_eq!( + slash_count, 1, + "Root path '{}' should have exactly one '/'", + path + ); + } else { + assert_eq!( + slash_count, level, + "Path '{}' at level {} should have {} slashes, got {}", + path, level, level, slash_count + ); + } + info!("level={} path={}", level, path); + } +} + +#[test] +fn test_find_children_by_path_root() { + init_logging(); + let raw = fdt_qemu(); + let fdt = Fdt::from_bytes(&raw).unwrap(); + + // Root "/" should have children + let children: Vec<_> = fdt.find_children_by_path("/").collect(); + assert!(!children.is_empty(), "Root should have children"); + + // All children should be at level 1 + for child in &children { + assert_eq!( + child.level(), + 1, + "Root child '{}' should be at level 1, got {}", + child.name(), + child.level() + ); + } + + // Known root children in QEMU DTB + let child_names: Vec<&str> = children.iter().map(|n| n.name()).collect(); + info!("Root children: {:?}", child_names); + assert!( + child_names.contains(&"memory@40000000"), + "Root should contain memory node, got {:?}", + child_names + ); + assert!( + child_names.contains(&"chosen"), + "Root should contain chosen node, got {:?}", + child_names + ); +} + +#[test] +fn test_find_children_by_path_nonroot() { + init_logging(); + let raw = fdt_qemu(); + let fdt = Fdt::from_bytes(&raw).unwrap(); + + // Find a node that is known to have children (e.g., a node with sub-nodes) + // In QEMU DTB, "platform-bus@c000000" or "apb-pclk" are common + // Let's use a node we know has children by scanning the tree + let mut parent_with_children: Option = None; + + for node in fdt.all_nodes() { + if node.level() == 1 { + let path = node.path(); + let children: Vec<_> = fdt.find_children_by_path(path.as_str()).collect(); + if !children.is_empty() { + info!( + "Found parent '{}' with {} children, first='{}'", + path, + children.len(), + children[0].name() + ); + parent_with_children = Some(path.to_string()); + break; + } + } + } + + assert!( + parent_with_children.is_some(), + "Should find at least one non-root node with children" + ); +} + +#[test] +fn test_find_children_by_path_leaf() { + init_logging(); + let raw = fdt_qemu(); + let fdt = Fdt::from_bytes(&raw).unwrap(); + + // "chosen" node typically has no child nodes + let children: Vec<_> = fdt.find_children_by_path("/chosen").collect(); + info!( + "Children of /chosen: {:?}", + children + .iter() + .map(|n: &fdt_raw::Node| n.name()) + .collect::>() + ); + + // Even if it has children, verify they are all at the correct level + let chosen = fdt.find_by_path("/chosen").unwrap(); + let expected_level = chosen.level() + 1; + for child in &children { + assert_eq!(child.level(), expected_level); + } +} + +#[test] +fn test_find_children_by_path_nonexistent() { + init_logging(); + let raw = fdt_qemu(); + let fdt = Fdt::from_bytes(&raw).unwrap(); + + // Non-existent path should return empty iterator + let result: Vec<_> = fdt.find_children_by_path("/nonexistent/path").collect(); + assert!( + result.is_empty(), + "Non-existent path should return empty iterator" + ); +} + +#[test] +fn test_find_children_by_path_no_grandchildren() { + init_logging(); + let raw = fdt_qemu(); + let fdt = Fdt::from_bytes(&raw).unwrap(); + + // Verify that find_children_by_path returns only direct children, + // not grandchildren or deeper descendants + let root_children: Vec<_> = fdt.find_children_by_path("/").collect(); + + // Count all descendants of root (all nodes except root) + let all_count = fdt.all_nodes().count(); + info!( + "Root has {} direct children, tree has {} total nodes", + root_children.len(), + all_count + ); + + // If the tree has more than level-1 nodes, direct children must be + // fewer than total nodes - 1 (excluding root itself) + assert!( + root_children.len() < all_count, + "Direct children ({}) should be fewer than total nodes ({})", + root_children.len(), + all_count + ); + + // Verify all children are exactly level 1 (direct children of root) + for child in &root_children { + assert_eq!( + child.level(), + 1, + "Child '{}' has level {}, expected 1", + child.name(), + child.level() + ); + } +} + +#[test] +fn test_find_children_by_path_consistency() { + init_logging(); + let raw = fdt_qemu(); + let fdt = Fdt::from_bytes(&raw).unwrap(); + + // For every node, verify that its children from find_children_by_path + // match the children we see in all_nodes() + let all_nodes: Vec<_> = fdt.all_nodes().collect(); + + for (i, node) in all_nodes.iter().enumerate() { + let path = node.path(); + let node_level = node.level(); + + // Collect direct children from all_nodes + let mut expected_children: Vec<&str> = Vec::new(); + for child in all_nodes.iter().skip(i + 1) { + if child.level() == node_level + 1 { + expected_children.push(child.name()); + } else if child.level() <= node_level { + break; // Left the subtree + } + // level > node_level + 1: grandchild, skip + } + + // Collect direct children from find_children_by_path + let actual_children: Vec = fdt + .find_children_by_path(path.as_str()) + .map(|n: fdt_raw::Node| n.name().to_string()) + .collect(); + + assert_eq!( + actual_children.len(), + expected_children.len(), + "Children count mismatch for '{}': got {:?}, expected {:?}", + path, + actual_children, + expected_children + ); + + for (k, (actual, expected)) in actual_children + .iter() + .zip(expected_children.iter()) + .enumerate() + { + assert_eq!( + actual.as_str(), + *expected, + "Child #{} of '{}': got '{}', expected '{}'", + k, + path, + actual, + expected + ); + } + } +} diff --git a/fdt-raw/tests/ranges.rs b/fdt-raw/tests/ranges.rs index 6c1f9a0..2c1c3af 100644 --- a/fdt-raw/tests/ranges.rs +++ b/fdt-raw/tests/ranges.rs @@ -55,3 +55,41 @@ fn test_reg() { reg.size.unwrap() ); } + +#[test] +fn test_translate_addresses_batch() { + let raw = fdt_rpi_4b(); + let fdt = Fdt::from_bytes(&raw).unwrap(); + + let path = "/soc/serial@7e215040"; + + // Single address — should match translate_address result + let single = fdt.translate_address(path, 0x7e215040); + assert_eq!(single, 0xfe215040); + + // Batch translation with multiple addresses from the same path + let mut addresses = [0x7e215040u64, 0x7e200000]; + let original = addresses; + fdt.translate_addresses(path, &mut addresses); + + assert_eq!( + addresses[0], 0xfe215040, + "batch[0]: want 0xfe215040, got {:#x}", + addresses[0] + ); + assert_eq!( + addresses[1], 0xfe200000, + "batch[1]: want 0xfe200000, got {:#x}", + addresses[1] + ); + + // Verify batch result matches individual calls + for (i, &addr) in original.iter().enumerate() { + let individual = fdt.translate_address(path, addr); + assert_eq!( + addresses[i], individual, + "batch[{}] ({:#x}) differs from individual ({:#x})", + i, addresses[i], individual + ); + } +}