From 2f5c2e9b6eb3740eccea7156466be9a39dc445ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Tue, 10 Feb 2026 10:29:57 +0800 Subject: [PATCH 01/39] Remove unused property parsing module and associated tests - Deleted the `property.rs` module which provided functionality for parsing and accessing device tree properties. - Removed tests related to device tree headers and memory reservations that relied on the deleted module. - Updated `Cargo.toml` to reflect the removal of the module and incremented the version to 0.2.0. --- CLAUDE.md | 83 -- Cargo.toml | 8 +- demo_find_all.pdb | Bin 1347584 -> 0 bytes dtb-tool/Cargo.toml | 18 - dtb-tool/src/main.rs | 61 -- example_all_nodes.rs | 61 -- fdt-edit/Cargo.toml | 31 - fdt-edit/LLMs.txt | 481 ------------ fdt-edit/README.md | 233 ------ fdt-edit/examples/fdt_debug_demo.rs | 26 - fdt-edit/src/ctx.rs | 160 ---- fdt-edit/src/encode.rs | 225 ------ fdt-edit/src/fdt.rs | 640 --------------- fdt-edit/src/lib.rs | 61 -- fdt-edit/src/node/clock.rs | 233 ------ fdt-edit/src/node/display.rs | 468 ----------- fdt-edit/src/node/gerneric.rs | 315 -------- fdt-edit/src/node/interrupt_controller.rs | 71 -- fdt-edit/src/node/iter.rs | 272 ------- fdt-edit/src/node/memory.rs | 111 --- fdt-edit/src/node/mod.rs | 395 ---------- fdt-edit/src/node/pci.rs | 382 --------- fdt-edit/src/prop/mod.rs | 157 ---- fdt-edit/tests/clock.rs | 205 ----- fdt-edit/tests/display_debug.rs | 271 ------- fdt-edit/tests/edit.rs | 171 ---- fdt-edit/tests/find2.rs | 67 -- fdt-edit/tests/irq.rs | 172 ---- fdt-edit/tests/memory.rs | 85 -- fdt-edit/tests/pci.rs | 182 ----- fdt-edit/tests/range.rs | 145 ---- fdt-edit/tests/remove_node.rs | 208 ----- fdt-parser/.gitignore | 6 - fdt-parser/Cargo.toml | 23 - fdt-parser/LICENSE | 21 - fdt-parser/README.md | 162 ---- fdt-parser/examples/bcm2711.rs | 41 - fdt-parser/examples/phytium.rs | 28 - fdt-parser/src/base/fdt.rs | 657 ---------------- fdt-parser/src/base/mod.rs | 11 - fdt-parser/src/base/node/chosen.rs | 200 ----- .../src/base/node/interrupt_controller.rs | 56 -- fdt-parser/src/base/node/memory.rs | 77 -- fdt-parser/src/base/node/mod.rs | 600 -------------- fdt-parser/src/cache/fdt.rs | 261 ------- fdt-parser/src/cache/mod.rs | 53 -- fdt-parser/src/cache/node/chosen.rs | 180 ----- fdt-parser/src/cache/node/clock.rs | 143 ---- .../src/cache/node/interrupt_controller.rs | 40 - fdt-parser/src/cache/node/memory.rs | 45 -- fdt-parser/src/cache/node/mod.rs | 380 --------- fdt-parser/src/cache/node/pci.rs | 485 ------------ fdt-parser/src/data.rs | 277 ------- fdt-parser/src/define.rs | 225 ------ fdt-parser/src/header.rs | 114 --- fdt-parser/src/lib.rs | 130 --- fdt-parser/src/property.rs | 123 --- fdt-parser/tests/head.rs | 18 - fdt-parser/tests/no_mem.rs | 339 -------- fdt-parser/tests/node.rs | 739 ------------------ fdt-raw/Cargo.toml | 9 +- 61 files changed, 8 insertions(+), 11433 deletions(-) delete mode 100644 CLAUDE.md delete mode 100644 demo_find_all.pdb delete mode 100644 dtb-tool/Cargo.toml delete mode 100644 dtb-tool/src/main.rs delete mode 100644 example_all_nodes.rs delete mode 100644 fdt-edit/Cargo.toml delete mode 100644 fdt-edit/LLMs.txt delete mode 100644 fdt-edit/README.md delete mode 100644 fdt-edit/examples/fdt_debug_demo.rs delete mode 100644 fdt-edit/src/ctx.rs delete mode 100644 fdt-edit/src/encode.rs delete mode 100644 fdt-edit/src/fdt.rs delete mode 100644 fdt-edit/src/lib.rs delete mode 100644 fdt-edit/src/node/clock.rs delete mode 100644 fdt-edit/src/node/display.rs delete mode 100644 fdt-edit/src/node/gerneric.rs delete mode 100644 fdt-edit/src/node/interrupt_controller.rs delete mode 100644 fdt-edit/src/node/iter.rs delete mode 100644 fdt-edit/src/node/memory.rs delete mode 100644 fdt-edit/src/node/mod.rs delete mode 100644 fdt-edit/src/node/pci.rs delete mode 100644 fdt-edit/src/prop/mod.rs delete mode 100644 fdt-edit/tests/clock.rs delete mode 100644 fdt-edit/tests/display_debug.rs delete mode 100644 fdt-edit/tests/edit.rs delete mode 100644 fdt-edit/tests/find2.rs delete mode 100644 fdt-edit/tests/irq.rs delete mode 100644 fdt-edit/tests/memory.rs delete mode 100644 fdt-edit/tests/pci.rs delete mode 100644 fdt-edit/tests/range.rs delete mode 100644 fdt-edit/tests/remove_node.rs delete mode 100644 fdt-parser/.gitignore delete mode 100644 fdt-parser/Cargo.toml delete mode 100644 fdt-parser/LICENSE delete mode 100644 fdt-parser/README.md delete mode 100644 fdt-parser/examples/bcm2711.rs delete mode 100644 fdt-parser/examples/phytium.rs delete mode 100644 fdt-parser/src/base/fdt.rs delete mode 100644 fdt-parser/src/base/mod.rs delete mode 100644 fdt-parser/src/base/node/chosen.rs delete mode 100644 fdt-parser/src/base/node/interrupt_controller.rs delete mode 100644 fdt-parser/src/base/node/memory.rs delete mode 100644 fdt-parser/src/base/node/mod.rs delete mode 100644 fdt-parser/src/cache/fdt.rs delete mode 100644 fdt-parser/src/cache/mod.rs delete mode 100644 fdt-parser/src/cache/node/chosen.rs delete mode 100644 fdt-parser/src/cache/node/clock.rs delete mode 100644 fdt-parser/src/cache/node/interrupt_controller.rs delete mode 100644 fdt-parser/src/cache/node/memory.rs delete mode 100644 fdt-parser/src/cache/node/mod.rs delete mode 100644 fdt-parser/src/cache/node/pci.rs delete mode 100644 fdt-parser/src/data.rs delete mode 100644 fdt-parser/src/define.rs delete mode 100644 fdt-parser/src/header.rs delete mode 100644 fdt-parser/src/lib.rs delete mode 100644 fdt-parser/src/property.rs delete mode 100644 fdt-parser/tests/head.rs delete mode 100644 fdt-parser/tests/no_mem.rs delete mode 100644 fdt-parser/tests/node.rs 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..8a94a0c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,3 +1,7 @@ [workspace] -resolver = "2" -members = ["fdt-parser", "dtb-tool", "dtb-file", "fdt-raw", "fdt-edit"] +members = ["dtb-file", "fdt-raw"] +resolver = "3" + + +[workspace.dependencies] +dtb-file = { path = "dtb-file" } \ No newline at end of file diff --git a/demo_find_all.pdb b/demo_find_all.pdb deleted file mode 100644 index 92e07efea2ccc16b94d9b8f1fb388eb144956339..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1347584 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

n5dpYFt?q$(=JmrnbNA7n( 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

;iqULGS!}EzYHvBqGq3A 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̵L*U6WE06Ftx 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

&lCF_UjXegY3= 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 G;Qs-2XLXkV 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 deleted file mode 100644 index ab57a7a..0000000 --- a/fdt-edit/Cargo.toml +++ /dev/null @@ -1,31 +0,0 @@ -[package] -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" - -[dependencies] -enum_dispatch = "0.3.13" -fdt-raw = {version = "0.1.0", path = "../fdt-raw"} -log = "0.4" - -[dev-dependencies] -dtb-file = {path = "../dtb-file"} -env_logger = "0.11" - -[features] -default = [] -std = [] - -[package.metadata.docs.rs] -all-features = true -targets = ["x86_64-unknown-linux-gnu", "aarch64-unknown-none-softfloat", "riscv64gc-unknown-none-elf"] 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 deleted file mode 100644 index 02a4f4d..0000000 --- a/fdt-edit/src/encode.rs +++ /dev/null @@ -1,225 +0,0 @@ -//! FDT encoding module. -//! -//! This module handles serialization of the `Fdt` structure into the -//! DTB (Device Tree Blob) binary format. - -use alloc::{string::String, vec::Vec}; -use core::ops::Deref; -use fdt_raw::{FDT_MAGIC, Token}; - -use crate::{Fdt, Node}; - -/// FDT binary data container. -/// -/// Wraps the encoded DTB data and provides access to the underlying bytes. -#[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() - } -} - -impl Deref for FdtData { - type Target = [u8]; - - fn deref(&self) -> &Self::Target { - unsafe { - core::slice::from_raw_parts( - self.0.as_ptr() as *const u8, - self.0.len() * core::mem::size_of::(), - ) - } - } -} - -impl AsRef<[u8]> for FdtData { - fn as_ref(&self) -> &[u8] { - self - } -} - -/// 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. -pub struct FdtEncoder<'a> { - fdt: &'a Fdt, - struct_data: Vec, - strings_data: Vec, - string_offsets: Vec<(String, u32)>, -} - -impl<'a> FdtEncoder<'a> { - /// Creates a new encoder for the given FDT. - pub fn new(fdt: &'a Fdt) -> Self { - Self { - fdt, - struct_data: Vec::new(), - strings_data: Vec::new(), - string_offsets: Vec::new(), - } - } - - /// 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 { - return *offset; - } - } - - let offset = self.strings_data.len() as u32; - self.strings_data.extend_from_slice(s.as_bytes()); - self.strings_data.push(0); // null terminator - self.string_offsets.push((s.into(), offset)); - offset - } - - /// Writes a BEGIN_NODE token and node name. - fn write_begin_node(&mut self, name: &str) { - let begin_token: u32 = Token::BeginNode.into(); - self.struct_data.push(begin_token.to_be()); - - let name_bytes = name.as_bytes(); - let name_len = name_bytes.len() + 1; // +1 for null - let aligned_len = (name_len + 3) & !3; - - let mut name_buf = vec![0u8; aligned_len]; - name_buf[..name_bytes.len()].copy_from_slice(name_bytes); - - for chunk in name_buf.chunks(4) { - let word = u32::from_ne_bytes(chunk.try_into().unwrap()); - self.struct_data.push(word); - } - } - - /// Writes an 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()); - - self.struct_data.push((data.len() as u32).to_be()); - - let nameoff = self.get_or_add_string(name); - self.struct_data.push(nameoff.to_be()); - - if !data.is_empty() { - let aligned_len = (data.len() + 3) & !3; - let mut data_buf = vec![0u8; aligned_len]; - data_buf[..data.len()].copy_from_slice(data); - - for chunk in data_buf.chunks(4) { - let word = u32::from_ne_bytes(chunk.try_into().unwrap()); - self.struct_data.push(word); - } - } - } - - /// 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()); - - // Add 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()); - - // Write all properties (using raw data directly) - for prop in node.properties() { - self.write_property(prop.name(), &prop.data); - } - - // Recursively encode child nodes - for child in node.children() { - self.encode_node(child); - } - - // Write END_NODE - self.write_end_node(); - } - - /// Generates the final FDT binary data. - fn finalize(self) -> FdtData { - let memory_reservations = &self.fdt.memory_reservations; - let boot_cpuid_phys = self.fdt.boot_cpuid_phys; - - let header_size = 40u32; // 10 * 4 bytes - let mem_rsv_size = ((memory_reservations.len() + 1) * 16) as u32; - let struct_size = (self.struct_data.len() * 4) as u32; - let strings_size = self.strings_data.len() as u32; - - let off_mem_rsvmap = header_size; - let off_dt_struct = off_mem_rsvmap + mem_rsv_size; - let off_dt_strings = off_dt_struct + struct_size; - let totalsize = off_dt_strings + strings_size; - let totalsize_aligned = (totalsize + 3) & !3; - - let mut data = Vec::with_capacity(totalsize_aligned as usize / 4); - - // Header - data.push(FDT_MAGIC.to_be()); - data.push(totalsize_aligned.to_be()); - data.push(off_dt_struct.to_be()); - data.push(off_dt_strings.to_be()); - data.push(off_mem_rsvmap.to_be()); - data.push(17u32.to_be()); // version - data.push(16u32.to_be()); // last_comp_version - data.push(boot_cpuid_phys.to_be()); - data.push(strings_size.to_be()); - data.push(struct_size.to_be()); - - // Memory reservation block - for rsv in memory_reservations { - let addr_hi = (rsv.address >> 32) as u32; - let addr_lo = rsv.address as u32; - let size_hi = (rsv.size >> 32) as u32; - let size_lo = rsv.size as u32; - data.push(addr_hi.to_be()); - data.push(addr_lo.to_be()); - data.push(size_hi.to_be()); - data.push(size_lo.to_be()); - } - // Terminator - data.push(0); - data.push(0); - data.push(0); - data.push(0); - - // Struct block - data.extend_from_slice(&self.struct_data); - - // Strings block - let strings_aligned_len = (self.strings_data.len() + 3) & !3; - let mut strings_buf = vec![0u8; strings_aligned_len]; - strings_buf[..self.strings_data.len()].copy_from_slice(&self.strings_data); - - for chunk in strings_buf.chunks(4) { - let word = u32::from_ne_bytes(chunk.try_into().unwrap()); - data.push(word); - } - - FdtData(data) - } -} diff --git a/fdt-edit/src/fdt.rs b/fdt-edit/src/fdt.rs deleted file mode 100644 index 5107372..0000000 --- a/fdt-edit/src/fdt.rs +++ /dev/null @@ -1,640 +0,0 @@ -//! Editable Flattened Device Tree (FDT) structure. -//! -//! 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. - -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}, -}; - -/// 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. -#[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, -} - -impl Default for Fdt { - fn default() -> Self { - Self::new() - } -} - -impl Fdt { - /// Creates a new empty FDT. - pub fn new() -> Self { - Self { - boot_cpuid_phys: 0, - memory_reservations: Vec::new(), - root: Node::new(""), - 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) - } - - /// 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) - } - - /// 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(), - }; - - // Build node tree using a stack to track parent nodes - let mut node_stack: Vec = Vec::new(); - - for raw_node in raw_fdt.all_nodes() { - let level = raw_node.level(); - let node = Node::from(&raw_node); - - // 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; - } - } - - node_stack.push(node); - } - - // 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; - } - } - - // Build phandle cache - fdt.rebuild_phandle_cache(); - - Ok(fdt) - } - - /// 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, "/"); - } - - /// 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 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) - }; - self.build_phandle_cache_recursive(child, &child_path); - } - } - - /// Normalizes a path: resolves aliases or ensures leading '/'. - 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 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)); - } - } - 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() - } - - /// Returns the root node (mutable reference). - pub fn root_mut<'a>(&'a mut self) -> NodeMut<'a> { - self.get_by_path_mut("/").unwrap() - } - - /// 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; - } - } - - // Rebuild phandle cache - self.rebuild_phandle_cache(); - - Ok(()) - } - - /// 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)?; - - // Find target node and apply overlay - let target_path_owned = target_path.to_string(); - - // Apply overlay to target node - self.apply_overlay_to_target(&target_path_owned, overlay_node)?; - - Ok(()) - } - - /// 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()); - } - - // 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()); - } - } - - Err(FdtError::NotFound) - } - - /// 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)?; - - // Merge overlay properties and child nodes - Self::merge_nodes(target.node, overlay_node); - - Ok(()) - } - - /// 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 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()); - } - } - - Ok(()) - } - - /// 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()); - } - - // 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); - } else { - // Add new child node - target.add_child(source_child.clone()); - } - } - } - - /// 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(); - } - - Ok(()) - } - - /// 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); - } - } - - /// 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) - } - - /// Returns a depth-first iterator over all nodes. - pub fn all_nodes(&self) -> impl Iterator> + '_ { - NodeIter::new(&self.root) - } - - /// Returns a mutable depth-first iterator over all nodes. - pub fn all_nodes_mut(&mut self) -> impl Iterator> + '_ { - NodeIterMut::new(&mut self.root) - } - - /// 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 - } - }) - } - - /// 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 - } - }) - } - - /// 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 - } - }) - } - - /// Finds nodes with matching compatible strings. - 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; - }; - - for comp in ls { - if compatible.contains(&comp) { - results.push(node_ref); - break; - } - } - } - results - } - - /// Serializes the FDT to binary DTB 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() - } - } -} - -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)?; - } - - 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())?; - } - 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(()) - } -} diff --git a/fdt-edit/src/lib.rs b/fdt-edit/src/lib.rs deleted file mode 100644 index 72478ac..0000000 --- a/fdt-edit/src/lib.rs +++ /dev/null @@ -1,61 +0,0 @@ -//! 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 node::*; -pub use prop::{Phandle, Property, RangesEntry, RegInfo, Status}; 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 deleted file mode 100644 index 48f6b79..0000000 --- a/fdt-edit/src/node/mod.rs +++ /dev/null @@ -1,395 +0,0 @@ -//! 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>), -} - -/// 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. -#[derive(Clone)] -pub struct Node { - /// Node name (without path) - pub name: String, - /// Property list (maintains original order) - pub(crate) 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) - name_cache: BTreeMap, -} - -impl Node { - /// Creates a new node with the given name. - pub fn new(name: &str) -> Self { - Self { - name: name.to_string(), - properties: Vec::new(), - prop_cache: BTreeMap::new(), - children: Vec::new(), - name_cache: BTreeMap::new(), - } - } - - /// Returns the node's name. - pub fn name(&self) -> &str { - &self.name - } - - /// Returns an iterator over the node's properties. - pub fn properties(&self) -> impl Iterator { - self.properties.iter() - } - - /// Returns a slice of the node's children. - pub fn children(&self) -> &[Node] { - &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. - /// - /// Updates the name cache for fast lookups. - pub fn add_child(&mut self, child: Node) { - let index = self.children.len(); - self.name_cache.insert(child.name.clone(), index); - self.children.push(child); - } - - /// Adds a property to this node. - /// - /// Updates the property cache for fast lookups. - pub fn add_property(&mut self, prop: Property) { - let name = prop.name.clone(); - let index = self.properties.len(); - self.prop_cache.insert(name, index); - self.properties.push(prop); - } - - /// Gets a child node 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) - } - - /// Gets a mutable reference to a child node by name. - /// - /// 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); - } - - // 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) - } - - /// 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?; - - let removed = self.children.remove(idx); - self.rebuild_name_cache(); - Some(removed) - } - - /// Sets a property, adding it if it doesn't exist or updating if it does. - pub fn set_property(&mut self, prop: Property) { - let name = prop.name.clone(); - if let Some(&idx) = self.prop_cache.get(&name) { - // Update existing property - self.properties[idx] = prop; - } else { - // Add new property - let idx = self.properties.len(); - self.prop_cache.insert(name, idx); - self.properties.push(prop); - } - } - - /// Gets a property by name. - pub fn get_property(&self, name: &str) -> Option<&Property> { - self.prop_cache.get(name).map(|&idx| &self.properties[idx]) - } - - /// Gets a mutable reference to a property by name. - pub fn get_property_mut(&mut self, name: &str) -> Option<&mut Property> { - self.prop_cache - .get(name) - .map(|&idx| &mut self.properties[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; - } - } - Some(prop) - } else { - None - } - } - - /// Returns the `#address-cells` property value. - pub fn address_cells(&self) -> Option { - self.get_property("#address-cells") - .and_then(|prop| prop.get_u32()) - } - - /// Returns the `#size-cells` property value. - pub fn size_cells(&self) -> Option { - self.get_property("#size-cells") - .and_then(|prop| prop.get_u32()) - } - - /// Returns the `phandle` property value. - pub fn phandle(&self) -> Option { - self.get_property("phandle") - .and_then(|prop| prop.get_u32()) - .map(Phandle::from) - } - - /// Returns the `interrupt-parent` property value. - pub fn interrupt_parent(&self) -> Option { - self.get_property("interrupt-parent") - .and_then(|prop| prop.get_u32()) - .map(Phandle::from) - } - - /// Returns the `status` property value. - pub fn status(&self) -> Option { - let prop = self.get_property("status")?; - let s = prop.as_str()?; - match s { - "okay" => Some(Status::Okay), - "disabled" => Some(Status::Disabled), - _ => None, - } - } - - /// Parses the `ranges` property for address translation. - /// - /// Returns a vector of range entries mapping child bus addresses to parent bus addresses. - pub fn ranges(&self, parent_address_cells: u32) -> Option> { - let prop = self.get_property("ranges")?; - 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)) = ( - reader.read_cells(child_address_cells), - reader.read_cells(parent_addr_cells), - reader.read_cells(size_cells), - ) { - entries.push(RangesEntry { - child_bus_address: child_addr, - parent_bus_address: parent_addr, - length: size, - }); - } - - 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> { - let prop = self.get_property("compatible")?; - Some(prop.as_str_iter()) - } - - /// Returns an iterator over all compatible strings. - pub fn compatibles(&self) -> impl Iterator { - self.get_property("compatible") - .map(|prop| prop.as_str_iter()) - .into_iter() - .flatten() - } - - /// Returns the `device_type` property value. - pub fn device_type(&self) -> Option<&str> { - let prop = self.get_property("device_type")?; - 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); - } - - 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) - } - } - - /// 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) - } - } - } -} - -impl From<&fdt_raw::Node<'_>> for Node { - fn from(raw: &fdt_raw::Node<'_>) -> Self { - let mut new_node = Node::new(raw.name()); - // Copy properties - for raw_prop in raw.properties() { - let prop = Property::from(&raw_prop); - new_node.set_property(prop); - } - new_node - } -} diff --git a/fdt-edit/src/node/pci.rs b/fdt-edit/src/node/pci.rs deleted file mode 100644 index 2223d2f..0000000 --- a/fdt-edit/src/node/pci.rs +++ /dev/null @@ -1,382 +0,0 @@ -use core::ops::{Deref, Range}; - -use alloc::vec::Vec; -use fdt_raw::{FdtError, Phandle, data::U32Iter}; -use log::debug; - -use crate::node::gerneric::NodeRefGen; - -/// PCI address space types. -#[derive(Clone, Debug, PartialEq)] -pub enum PciSpace { - /// I/O space - IO, - /// 32-bit memory space - Memory32, - /// 64-bit memory space - Memory64, -} - -/// PCI address range entry. -/// -/// Represents a range of addresses in PCI address space with mapping to CPU address space. -#[derive(Clone, Debug, PartialEq)] -pub struct PciRange { - /// The PCI address space type - pub space: PciSpace, - /// Address on the PCI bus - pub bus_address: u64, - /// Address in CPU physical address space - pub cpu_address: u64, - /// Size of the range in bytes - pub size: u64, - /// Whether the memory region is prefetchable - pub prefetchable: bool, -} - -/// PCI interrupt mapping entry. -/// -/// Represents a mapping from PCI device interrupts to parent interrupt controller inputs. -#[derive(Clone, Debug)] -pub struct PciInterruptMap { - /// Child device address (masked) - pub child_address: Vec, - /// Child device IRQ (masked) - pub child_irq: Vec, - /// Phandle of the interrupt parent controller - pub interrupt_parent: Phandle, - /// Parent controller IRQ inputs - pub parent_irq: Vec, -} - -/// PCI interrupt information. -/// -/// Contains the resolved interrupt information for a PCI device. -#[derive(Clone, Debug, PartialEq)] -pub struct PciInterruptInfo { - /// List of IRQ numbers - 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>, -} - -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 }) - } else { - Err(node) - } - } - - /// Returns the `#interrupt-cells` property value. - /// - /// Defaults to 1 for PCI devices if not specified. - pub fn interrupt_cells(&self) -> u32 { - self.find_property("#interrupt-cells") - .and_then(|prop| prop.get_u32()) - .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.find_property("interrupt-map-mask") - .map(|prop| prop.get_u32_iter()) - } - - /// 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()?; - - Some(start..end) - }) - } - - /// Get the ranges property for address translation - pub fn ranges(&self) -> Option> { - let prop = self.find_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; - - 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()?; - - // Parse parent bus address (使用父节点的 #address-cells) - let parent_addr = data.read_cells(parent_addr_cells)?; - - // Parse size (使用当前节点的 #size-cells) - let size = data.read_cells(size_cells)?; - - // Extract PCI address space and prefetchable from child_addr[0] - let (space, prefetchable) = self.decode_pci_address_space(pci_hi); - - 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 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 - pub fn interrupt_map(&self) -> Result, FdtError> { - let prop = self - .find_property("interrupt-map") - .ok_or(FdtError::NotFound)?; - - // Convert mask and data to Vec for indexed access - let mask: Vec = self - .interrupt_map_mask() - .ok_or(FdtError::NotFound)? - .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_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 - } - } - - // Parse child IRQ - let mut child_irq = Vec::with_capacity(child_irq_cells); - for _ in 0..child_irq_cells { - match data.read_u32() { - Some(v) => child_irq.push(v), - None => return Ok(mappings), - } - } - - // Parse interrupt parent 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 - 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; - - let irq_cells = irq_parent - .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) - (0, 3) - }; - - // Skip parent address cells - for _ in 0..parent_addr_cells { - if data.read_u32().is_none() { - return Ok(mappings); - } - } - - // Parse parent IRQ - let mut parent_irq = Vec::with_capacity(parent_irq_cells); - for _ in 0..parent_irq_cells { - match data.read_u32() { - Some(v) => parent_irq.push(v), - None => return Ok(mappings), - } - } - - // Apply mask to child address and IRQ - let masked_address: Vec = child_address - .iter() - .enumerate() - .map(|(i, value)| { - let mask_value = mask.get(i).copied().unwrap_or(0xffff_ffff); - value & mask_value - }) - .collect(); - let masked_irq: Vec = child_irq - .iter() - .enumerate() - .map(|(i, value)| { - let mask_value = mask - .get(child_addr_cells + i) - .copied() - .unwrap_or(0xffff_ffff); - value & mask_value - }) - .collect(); - - mappings.push(PciInterruptMap { - child_address: masked_address, - child_irq: masked_irq, - interrupt_parent, - parent_irq, - }); - } - } -} - -impl<'a> Deref for NodeRefPci<'a> { - type Target = NodeRefGen<'a>; - - fn deref(&self) -> &Self::Target { - &self.node - } -} diff --git a/fdt-edit/src/prop/mod.rs b/fdt-edit/src/prop/mod.rs deleted file mode 100644 index 3b9a0fb..0000000 --- a/fdt-edit/src/prop/mod.rs +++ /dev/null @@ -1,157 +0,0 @@ -//! Device tree property representation and manipulation. -//! -//! This module provides the `Property` type which represents a mutable device tree -//! property with a name and data, along with methods for accessing and modifying -//! various property data formats. - -use core::ffi::CStr; - -use alloc::{ - string::{String, ToString}, - vec::Vec, -}; - -use fdt_raw::data::{Bytes, Reader, StrIter, U32Iter}; -// Re-export from fdt_raw -pub use fdt_raw::{Phandle, RegInfo, Status}; - -/// A mutable device tree property. -/// -/// Represents a property with a name and raw data. Provides methods for -/// accessing and modifying the data in various formats (u32, u64, strings, etc.). -#[derive(Clone)] -pub struct Property { - /// Property name - pub name: String, - /// Raw property data - pub data: Vec, -} - -impl Property { - /// Creates a new property with the given name and data. - pub fn new(name: &str, data: Vec) -> Self { - Self { - name: name.to_string(), - data, - } - } - - /// Returns the property name. - pub fn name(&self) -> &str { - &self.name - } - - /// Returns the property data as a big-endian u32. - /// - /// Returns None if the data is not exactly 4 bytes. - pub fn get_u32(&self) -> Option { - if self.data.len() != 4 { - return None; - } - Some(u32::from_be_bytes([ - self.data[0], - self.data[1], - self.data[2], - self.data[3], - ])) - } - - /// Sets the property data from a list of u32 values (as big-endian). - pub fn set_u32_ls(&mut self, values: &[u32]) { - self.data.clear(); - for &value in values { - self.data.extend_from_slice(&value.to_be_bytes()); - } - } - - /// Returns an iterator over u32 values in the property data. - pub fn get_u32_iter(&self) -> U32Iter<'_> { - Bytes::new(&self.data).as_u32_iter() - } - - /// Returns the property data as a big-endian u64. - /// - /// Returns None if the data is not exactly 8 bytes. - pub fn get_u64(&self) -> Option { - if self.data.len() != 8 { - return None; - } - Some(u64::from_be_bytes([ - self.data[0], - self.data[1], - self.data[2], - self.data[3], - self.data[4], - self.data[5], - self.data[6], - self.data[7], - ])) - } - - /// Sets the property data from a u64 value (as big-endian). - pub fn set_u64(&mut self, value: u64) { - self.data = value.to_be_bytes().to_vec(); - } - - /// Returns the property data as a null-terminated string. - /// - /// Returns None if the data is not a valid null-terminated UTF-8 string. - pub fn as_str(&self) -> Option<&str> { - CStr::from_bytes_with_nul(&self.data) - .ok() - .and_then(|cstr| cstr.to_str().ok()) - } - - /// Sets the property data from a string value. - /// - /// The string will be null-terminated. - pub fn set_string(&mut self, value: &str) { - let mut bytes = value.as_bytes().to_vec(); - bytes.push(0); // Null-terminate - self.data = bytes; - } - - /// Returns an iterator over null-terminated strings in the property data. - pub fn as_str_iter(&self) -> StrIter<'_> { - Bytes::new(&self.data).as_str_iter() - } - - /// Sets the property data from a list of string values. - /// - /// Each string will be null-terminated. - pub fn set_string_ls(&mut self, values: &[&str]) { - self.data.clear(); - for &value in values { - self.data.extend_from_slice(value.as_bytes()); - self.data.push(0); // Null-terminate each string - } - } - - /// Returns a reader for accessing the property data. - pub fn as_reader(&self) -> Reader<'_> { - Bytes::new(&self.data).reader() - } -} - -impl From<&fdt_raw::Property<'_>> for Property { - fn from(value: &fdt_raw::Property<'_>) -> Self { - Self { - name: value.name().to_string(), - data: value.as_slice().to_vec(), - } - } -} - -/// Ranges entry information for address translation. -/// -/// Represents a single entry in a `ranges` property, mapping a child bus -/// address range to a parent bus address range. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct RangesEntry { - /// Child bus address - pub child_bus_address: u64, - /// Parent bus address - pub parent_bus_address: u64, - /// Length of the region - pub length: u64, -} diff --git a/fdt-edit/tests/clock.rs b/fdt-edit/tests/clock.rs deleted file mode 100644 index e967cab..0000000 --- a/fdt-edit/tests/clock.rs +++ /dev/null @@ -1,205 +0,0 @@ -#![cfg(unix)] - -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 - ); - } - } - 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); - } - - match &clock.kind { - ClockType::Fixed(fixed) => { - println!( - " Fixed clock: freq={}Hz accuracy={:?}", - fixed.frequency, fixed.accuracy - ); - } - ClockType::Normal => { - println!(" Clock provider"); - } - } - } - } - } - - #[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 - ); - // 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())); - } - } - } - } - } - - #[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" - ); - } - } - } - } - } -} 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/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 deleted file mode 100644 index e423990..0000000 --- a/fdt-edit/tests/pci.rs +++ /dev/null @@ -1,182 +0,0 @@ -#[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(); - }); - } - - #[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()); - } - } - } - - // 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 - } - } - } - - // 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); - } - - return; // Test passed for first PCI node found - } - } - } - - 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_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 irq = pci.child_interrupts(0, 0, 0, 4).unwrap(); - - assert!(!irq.irqs.is_empty()); - } - - #[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 NodeRef::Pci(pci) = node_ref 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); - } - } -} diff --git a/fdt-edit/tests/range.rs b/fdt-edit/tests/range.rs deleted file mode 100644 index 25b17f8..0000000 --- a/fdt-edit/tests/range.rs +++ /dev/null @@ -1,145 +0,0 @@ -#[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() - ); - } - - #[test] - fn test_set_regs_with_ranges_conversion() { - init_logging(); - let raw = fdt_rpi_4b(); - let mut fdt = Fdt::from_bytes(&raw).unwrap(); - - // 获取可变节点引用 - 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(); - - // 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); - - // 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, - }]); - } - - // 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); - - 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" - ); - } -} 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..6d76aae 100644 --- a/fdt-raw/Cargo.toml +++ b/fdt-raw/Cargo.toml @@ -4,14 +4,11 @@ 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 +16,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 From 17ceab47760056e0a768164e24efec1d8aa61abf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Tue, 10 Feb 2026 10:52:39 +0800 Subject: [PATCH 02/39] feat: Enhance FDT parsing and formatting utilities - Added new constants for size calculations in FDT parsing (`U32_SIZE`, `MEM_RSV_ENTRY_SIZE`). - Improved `Bytes` struct with additional documentation and examples for clarity. - Refactored `Fdt` struct methods to utilize new utility functions for path splitting and address translation. - Introduced `fmt_utils` module for shared formatting functions, improving code organization and readability. - Updated `Property` formatting to handle various types more gracefully, including a new method for typed formatting. - Enhanced error handling in iterators to allow for graceful degradation during parsing. - Added comprehensive documentation to several structs and methods to improve maintainability and usability. --- .claude/settings.local.json | 3 +- fdt-raw/src/data.rs | 77 ++++++++- fdt-raw/src/define.rs | 9 +- fdt-raw/src/fdt.rs | 305 ++++++++++++++++++++--------------- fdt-raw/src/fmt_utils.rs | 23 +++ fdt-raw/src/header.rs | 9 +- fdt-raw/src/iter.rs | 11 +- fdt-raw/src/lib.rs | 2 + fdt-raw/src/node/mod.rs | 82 +++++++--- fdt-raw/src/node/prop/mod.rs | 183 +++++++++++++-------- fdt-raw/src/node/prop/reg.rs | 35 +++- 11 files changed, 495 insertions(+), 244 deletions(-) create mode 100644 fdt-raw/src/fmt_utils.rs diff --git a/.claude/settings.local.json b/.claude/settings.local.json index aa06865..2befd39 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -10,7 +10,8 @@ "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:*)" ], "deny": [], "ask": [] 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..23baa31 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, 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 @@ -218,76 +211,79 @@ impl<'a> Fdt<'a> { None => return address, }; - // 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; } - let mut current_address = address; + self.translate_address_with_parts(&path_parts, address) + } + /// 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 address translation using pre-split path components. + /// + /// Walks up the tree from the deepest node, applying `ranges` at each level. + fn translate_address_with_parts(&self, path_parts: &[&str], mut address: u64) -> 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(); + if let Some(parent_node) = self.find_node_by_parts(parent_parts) { + address = match self.apply_ranges_translation(parent_node, address) { + Some(translated) => translated, + None => break, // No ranges property, stop translation + }; } + } - let parent_node = match self.find_by_path(parent_path.as_str()) { - Some(node) => node, - None => continue, - }; - - // Get the parent's ranges property - let ranges = match parent_node.ranges() { - Some(r) => r, - None => { - // No ranges property, stop translation - break; - } - }; + address + } - // 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 + /// Applies a single level of address translation using a node's ranges property. + /// + /// Returns `Some(translated_address)` if the address falls within a range, + /// or `None` if the node has no ranges property. + fn apply_ranges_translation(&self, node: Node<'a>, address: u64) -> Option { + let ranges = node.ranges()?; + + 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 Some(range.parent_address + offset); } } - current_address + // No matching range found, but we continue to try upper levels + Some(address) } /// Returns an iterator over memory reservation entries. @@ -369,44 +365,74 @@ impl fmt::Display for Fdt<'_> { 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(()) + } + + /// Formats a node name, replacing empty names with "/". + fn format_node_name(name: &str) -> &str { + if name.is_empty() { "/" } else { name } + } - // Close remaining nodes - while prev_level > 0 { - prev_level -= 1; - write_indent(f, prev_level, " ")?; + /// 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,56 +444,71 @@ 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")?; - - let name = if node.name().is_empty() { - "/" - } else { - node.name() - }; - - // Print node name and basic info - 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())?; - } - } - } + self.debug_node(f, &node)?; } 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(()) + } + + /// 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, "#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())?, + } + Ok(()) + } +} + #[cfg(test)] mod tests { use super::*; @@ -476,7 +517,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 +540,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..ea55775 100644 --- a/fdt-raw/src/header.rs +++ b/fdt-raw/src/header.rs @@ -6,6 +6,7 @@ use core::ptr::NonNull; +use crate::data::U32_SIZE; use crate::FdtError; /// 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..68214b3 100644 --- a/fdt-raw/src/iter.rs +++ b/fdt-raw/src/iter.rs @@ -64,13 +64,22 @@ impl<'a> FdtIter<'a> { } /// 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; 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..83e671c 100644 --- a/fdt-raw/src/node/mod.rs +++ b/fdt-raw/src/node/mod.rs @@ -9,10 +9,11 @@ use core::fmt; use core::ops::Deref; use core::{ffi::CStr, fmt::Debug}; +use crate::fmt_utils; use crate::Fdt; 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) @@ -142,25 +149,17 @@ impl<'a> NodeBase<'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(()) -} - 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 +230,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 +248,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,12 +296,16 @@ 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). + /// + /// 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) -> Result, FdtError> { // Read null-terminated name string let name = self.read_cstr()?; @@ -309,7 +328,7 @@ impl<'a> OneNodeIter<'a> { }) } - /// 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 +339,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 +352,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..4bbb3b4 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, U32Iter, U32_SIZE}, }; /// 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>, From d522ce5af399eec91df084d44ca58c1dc2ea545b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Tue, 10 Feb 2026 12:01:54 +0800 Subject: [PATCH 03/39] feat: Add path tracking to FDT nodes and iterators --- fdt-raw/src/iter.rs | 11 +++- fdt-raw/src/node/mod.rs | 33 ++++++++++- fdt-raw/tests/node.rs | 127 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 168 insertions(+), 3 deletions(-) diff --git a/fdt-raw/src/iter.rs b/fdt-raw/src/iter.rs index 68214b3..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,6 +62,7 @@ impl<'a> FdtIter<'a> { level: 0, finished: false, context_stack, + path_stack: heapless::Vec::new(), } } @@ -112,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 } @@ -140,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() { @@ -163,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; } @@ -199,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/node/mod.rs b/fdt-raw/src/node/mod.rs index 83e671c..75db6ae 100644 --- a/fdt-raw/src/node/mod.rs +++ b/fdt-raw/src/node/mod.rs @@ -9,8 +9,8 @@ use core::fmt; use core::ops::Deref; use core::{ffi::CStr, fmt::Debug}; -use crate::fmt_utils; use crate::Fdt; +use crate::fmt_utils; use crate::{ FdtError, Token, data::{Bytes, Reader, U32_SIZE}, @@ -68,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> { @@ -147,6 +149,23 @@ impl<'a> NodeBase<'a> { .into_iter() .flat_map(|p| p.as_str_iter()) } + + /// 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 + } } impl fmt::Display for NodeBase<'_> { @@ -306,7 +325,10 @@ impl<'a> OneNodeIter<'a> { /// 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) -> Result, FdtError> { + 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()?; @@ -315,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, @@ -325,6 +353,7 @@ impl<'a> OneNodeIter<'a> { size_cells: 1, context: self.context.clone(), _fdt: self.fdt.clone(), + path_components, }) } diff --git a/fdt-raw/tests/node.rs b/fdt-raw/tests/node.rs index f8b7a5d..b9e5b5c 100644 --- a/fdt-raw/tests/node.rs +++ b/fdt-raw/tests/node.rs @@ -611,3 +611,130 @@ 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); + } +} From f3d6395633a9a760d97d2de2c524b640c3fd876b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Tue, 10 Feb 2026 12:05:50 +0800 Subject: [PATCH 04/39] feat: Add batch address translation method to FDT parser --- fdt-raw/src/fdt.rs | 92 ++++++++++++++++++++++++++++------------- fdt-raw/tests/ranges.rs | 38 +++++++++++++++++ 2 files changed, 101 insertions(+), 29 deletions(-) diff --git a/fdt-raw/src/fdt.rs b/fdt-raw/src/fdt.rs index 23baa31..07aea0a 100644 --- a/fdt-raw/src/fdt.rs +++ b/fdt-raw/src/fdt.rs @@ -8,8 +8,8 @@ use core::fmt; use crate::{ - Chosen, FdtError, Memory, MemoryReservation, Node, Property, data, data::Bytes, fmt_utils, - 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. @@ -206,17 +206,51 @@ 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_inner(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` - Slice of device addresses from the node's `reg` property + /// + /// # Returns + /// + /// A `heapless::Vec` of translated CPU physical addresses, in the same + /// order as the input. If translation fails, the original addresses are + /// preserved. + pub fn translate_addresses( + &self, + path: &'a str, + addresses: &[u64], + ) -> heapless::Vec { + let mut result: heapless::Vec = addresses.iter().copied().collect(); + self.translate_addresses_inner(path, &mut result); + result + } + + /// Inner implementation for batch address translation. + fn translate_addresses_inner(&self, path: &'a str, addresses: &mut [u64]) { let path = match self.normalize_path(path) { Some(p) => p, - None => return address, + None => return, }; let path_parts = Self::split_path(path); if path_parts.is_empty() { - return address; + return; } - self.translate_address_with_parts(&path_parts, address) + self.translate_addresses_with_parts(&path_parts, addresses); } /// Splits an absolute path into its component parts. @@ -229,10 +263,11 @@ impl<'a> Fdt<'a> { .collect() } - /// Performs address translation using pre-split path components. + /// Performs batch address translation using pre-split path components. /// - /// Walks up the tree from the deepest node, applying `ranges` at each level. - fn translate_address_with_parts(&self, path_parts: &[&str], mut address: u64) -> u64 { + /// 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 // We start from the second-to-last level (the target node itself is skipped) for depth in (0..path_parts.len()).rev() { @@ -244,14 +279,17 @@ impl<'a> Fdt<'a> { } if let Some(parent_node) = self.find_node_by_parts(parent_parts) { - address = match self.apply_ranges_translation(parent_node, address) { - Some(translated) => translated, + let ranges = match parent_node.ranges() { + Some(r) => r, None => break, // No ranges property, stop translation }; + + // Apply ranges to all addresses in batch + for addr in addresses.iter_mut() { + *addr = Self::apply_ranges_one(&ranges, *addr); + } } } - - address } /// Finds a node by its path component parts. @@ -267,23 +305,21 @@ impl<'a> Fdt<'a> { self.find_by_path(path.as_str()) } - /// Applies a single level of address translation using a node's ranges property. + /// Translates a single address using the given ranges. /// - /// Returns `Some(translated_address)` if the address falls within a range, - /// or `None` if the node has no ranges property. - fn apply_ranges_translation(&self, node: Node<'a>, address: u64) -> Option { - let ranges = node.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 Some(range.parent_address + offset); + return range.parent_address + offset; } } - // No matching range found, but we continue to try upper levels - Some(address) + // No matching range found, return as-is + address } /// Returns an iterator over memory reservation entries. @@ -423,11 +459,7 @@ impl Fdt<'_> { } /// Closes all remaining open nodes at the end of output. - fn close_all_nodes( - &self, - f: &mut fmt::Formatter<'_>, - state: &mut DisplayState, - ) -> fmt::Result { + 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, " ")?; @@ -486,9 +518,11 @@ impl Fdt<'_> { () 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, "#interrupt-cells: {}", prop.as_interrupt_cells().unwrap())? - } + () if prop.as_interrupt_cells().is_some() => writeln!( + f, + "#interrupt-cells: {}", + prop.as_interrupt_cells().unwrap() + )?, () if prop.as_status().is_some() => { writeln!(f, "status: {:?}", prop.as_status().unwrap())? } diff --git a/fdt-raw/tests/ranges.rs b/fdt-raw/tests/ranges.rs index 6c1f9a0..218bb24 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 addresses = &[0x7e215040u64, 0x7e200000]; + let result: heapless::Vec = fdt.translate_addresses(path, addresses); + + assert_eq!(result.len(), 2); + assert_eq!( + result[0], 0xfe215040, + "batch[0]: want 0xfe215040, got {:#x}", + result[0] + ); + assert_eq!( + result[1], 0xfe200000, + "batch[1]: want 0xfe200000, got {:#x}", + result[1] + ); + + // Verify batch result matches individual calls + for (i, &addr) in addresses.iter().enumerate() { + let individual = fdt.translate_address(path, addr); + assert_eq!( + result[i], individual, + "batch[{}] ({:#x}) differs from individual ({:#x})", + i, result[i], individual + ); + } +} From 9cfa0010d9f0043c09f4976c81e7d1e460d0db47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Tue, 10 Feb 2026 12:24:27 +0800 Subject: [PATCH 05/39] feat: Refactor address translation methods for improved batch processing --- fdt-raw/src/fdt.rs | 25 ++++++------------------- fdt-raw/tests/ranges.rs | 20 ++++++++++---------- 2 files changed, 16 insertions(+), 29 deletions(-) diff --git a/fdt-raw/src/fdt.rs b/fdt-raw/src/fdt.rs index 07aea0a..9a11385 100644 --- a/fdt-raw/src/fdt.rs +++ b/fdt-raw/src/fdt.rs @@ -207,7 +207,7 @@ impl<'a> Fdt<'a> { /// address is returned. pub fn translate_address(&self, path: &'a str, address: u64) -> u64 { let mut addresses = [address]; - self.translate_addresses_inner(path, &mut addresses); + self.translate_addresses(path, &mut addresses); addresses[0] } @@ -221,25 +221,12 @@ impl<'a> Fdt<'a> { /// # Arguments /// /// * `path` - Node path (absolute path starting with '/' or alias name) - /// * `addresses` - Slice of device addresses from the node's `reg` property + /// * `addresses` - Mutable slice of device addresses to translate in place. + /// The addresses are modified with the translated CPU physical addresses. /// - /// # Returns - /// - /// A `heapless::Vec` of translated CPU physical addresses, in the same - /// order as the input. If translation fails, the original addresses are - /// preserved. - pub fn translate_addresses( - &self, - path: &'a str, - addresses: &[u64], - ) -> heapless::Vec { - let mut result: heapless::Vec = addresses.iter().copied().collect(); - self.translate_addresses_inner(path, &mut result); - result - } - - /// Inner implementation for batch address translation. - fn translate_addresses_inner(&self, path: &'a str, addresses: &mut [u64]) { + /// 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, diff --git a/fdt-raw/tests/ranges.rs b/fdt-raw/tests/ranges.rs index 218bb24..ee57c7c 100644 --- a/fdt-raw/tests/ranges.rs +++ b/fdt-raw/tests/ranges.rs @@ -68,28 +68,28 @@ fn test_translate_addresses_batch() { assert_eq!(single, 0xfe215040); // Batch translation with multiple addresses from the same path - let addresses = &[0x7e215040u64, 0x7e200000]; - let result: heapless::Vec = fdt.translate_addresses(path, addresses); + let mut addresses = [0x7e215040u64, 0x7e200000]; + let original = addresses.clone(); + fdt.translate_addresses(path, &mut addresses); - assert_eq!(result.len(), 2); assert_eq!( - result[0], 0xfe215040, + addresses[0], 0xfe215040, "batch[0]: want 0xfe215040, got {:#x}", - result[0] + addresses[0] ); assert_eq!( - result[1], 0xfe200000, + addresses[1], 0xfe200000, "batch[1]: want 0xfe200000, got {:#x}", - result[1] + addresses[1] ); // Verify batch result matches individual calls - for (i, &addr) in addresses.iter().enumerate() { + for (i, &addr) in original.iter().enumerate() { let individual = fdt.translate_address(path, addr); assert_eq!( - result[i], individual, + addresses[i], individual, "batch[{}] ({:#x}) differs from individual ({:#x})", - i, result[i], individual + i, addresses[i], individual ); } } From e0a2f8315190aa80860064e50d90982238f98af8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Tue, 10 Feb 2026 12:34:53 +0800 Subject: [PATCH 06/39] feat: Add method to find direct children of a node by path and corresponding tests --- fdt-raw/src/fdt.rs | 86 +++++++++++++++++ fdt-raw/tests/node.rs | 220 ++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 296 insertions(+), 10 deletions(-) diff --git a/fdt-raw/src/fdt.rs b/fdt-raw/src/fdt.rs index 9a11385..9d0c121 100644 --- a/fdt-raw/src/fdt.rs +++ b/fdt-raw/src/fdt.rs @@ -164,6 +164,59 @@ 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) -> Option> + 'a> { + let path = self.normalize_path(path)?; + 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 None; + } + } + + let child_level = target_level + 1; + Some(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 @@ -383,6 +436,39 @@ 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). +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/;")?; diff --git a/fdt-raw/tests/node.rs b/fdt-raw/tests/node.rs index b9e5b5c..41353e3 100644 --- a/fdt-raw/tests/node.rs +++ b/fdt-raw/tests/node.rs @@ -669,17 +669,10 @@ fn test_node_path_known_nodes() { let fdt = Fdt::from_bytes(&raw).unwrap(); // Collect all paths - let paths: Vec = fdt - .all_nodes() - .map(|n| n.path().to_string()) - .collect(); + let paths: Vec = fdt.all_nodes().map(|n| n.path().to_string()).collect(); // Verify known paths exist - let expected_paths = [ - "/", - "/memory@40000000", - "/chosen", - ]; + let expected_paths = ["/", "/memory@40000000", "/chosen"]; for expected in expected_paths { assert!( paths.iter().any(|p| p == expected), @@ -727,7 +720,11 @@ fn test_node_path_depth() { // 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); + assert_eq!( + slash_count, 1, + "Root path '{}' should have exactly one '/'", + path + ); } else { assert_eq!( slash_count, level, @@ -738,3 +735,206 @@ fn test_node_path_depth() { 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("/").unwrap().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(); + if let Some(children_iter) = fdt.find_children_by_path(path.as_str()) { + let children: Vec<_> = children_iter.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").unwrap().collect(); + info!( + "Children of /chosen: {:?}", + children.iter().map(|n| 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 None + let result = fdt.find_children_by_path("/nonexistent/path"); + assert!(result.is_none(), "Non-existent path should return None"); +} + +#[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("/").unwrap().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 j in (i + 1)..all_nodes.len() { + let child = &all_nodes[j]; + 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()) + .unwrap() + .map(|n| 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 + ); + } + } +} From 4c8a8a06a08bd7f0c34c429b83a4ee9d7097d32d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Tue, 10 Feb 2026 14:15:06 +0800 Subject: [PATCH 07/39] feat: Add fdt-edit module with Node and Property structures for device tree manipulation --- Cargo.toml | 5 +- fdt-edit/Cargo.toml | 28 ++++ fdt-edit/src/lib.rs | 10 ++ fdt-edit/src/node/mod.rs | 353 +++++++++++++++++++++++++++++++++++++++ fdt-edit/src/prop/mod.rs | 157 +++++++++++++++++ fdt-raw/Cargo.toml | 1 - 6 files changed, 550 insertions(+), 4 deletions(-) create mode 100644 fdt-edit/Cargo.toml create mode 100644 fdt-edit/src/lib.rs create mode 100644 fdt-edit/src/node/mod.rs create mode 100644 fdt-edit/src/prop/mod.rs diff --git a/Cargo.toml b/Cargo.toml index 8a94a0c..b433063 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,6 @@ [workspace] -members = ["dtb-file", "fdt-raw"] +members = ["dtb-file", "fdt-edit", "fdt-raw"] resolver = "3" - [workspace.dependencies] -dtb-file = { path = "dtb-file" } \ No newline at end of file +dtb-file = {path = "dtb-file"} diff --git a/fdt-edit/Cargo.toml b/fdt-edit/Cargo.toml new file mode 100644 index 0000000..8891f76 --- /dev/null +++ b/fdt-edit/Cargo.toml @@ -0,0 +1,28 @@ +[package] +authors = ["周睿 "] +categories = ["embedded", "no-std", "hardware-support"] +description = "A high-level library for creating, editing, and encoding Flattened Device Tree (FDT) structures" +edition = "2024" +homepage = "https://github.com/drivercraft/fdt-parser" +keywords = ["device-tree", "dtb", "embedded", "no-std", "editor"] +license = "MIT OR Apache-2.0" +name = "fdt-edit" +repository = "https://github.com/drivercraft/fdt-parser" +version = "0.2.0" + +[dependencies] +enum_dispatch = "0.3.13" +fdt-raw = {version = "0.2", path = "../fdt-raw"} +log = "0.4" + +[dev-dependencies] +dtb-file.workspace = true +env_logger = "0.11" + +[features] +default = [] +std = [] + +[package.metadata.docs.rs] +all-features = true +targets = ["x86_64-unknown-linux-gnu", "aarch64-unknown-none-softfloat", "riscv64gc-unknown-none-elf"] diff --git a/fdt-edit/src/lib.rs b/fdt-edit/src/lib.rs new file mode 100644 index 0000000..cf9aa1b --- /dev/null +++ b/fdt-edit/src/lib.rs @@ -0,0 +1,10 @@ +#![no_std] + +#[macro_use] +extern crate alloc; + +mod node; +mod prop; + +pub use node::*; +pub use prop::*; diff --git a/fdt-edit/src/node/mod.rs b/fdt-edit/src/node/mod.rs new file mode 100644 index 0000000..02d6bc7 --- /dev/null +++ b/fdt-edit/src/node/mod.rs @@ -0,0 +1,353 @@ +use alloc::{collections::btree_map::BTreeMap, string::String, vec::Vec}; +use fdt_raw::{Phandle, Status}; + +use crate::{Property, RangesEntry}; + +/// 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. +#[derive(Clone)] +pub struct Node { + /// Node name (without path) + pub name: String, + /// Property list (maintains original order) + properties: Vec, + /// Property name to index mapping (for fast lookup) + prop_cache: BTreeMap, + /// Child nodes + children: Vec, + /// Child name to index mapping (for fast lookup) + name_cache: BTreeMap, +} + +impl Node { + /// Creates a new node with the given name. + pub fn new(name: &str) -> Self { + Self { + name: name.into(), + properties: Vec::new(), + prop_cache: BTreeMap::new(), + children: Vec::new(), + name_cache: BTreeMap::new(), + } + } + + /// Returns the node's name. + pub fn name(&self) -> &str { + &self.name + } + + /// Returns an iterator over the node's properties. + pub fn properties(&self) -> &[Property] { + &self.properties + } + + /// Returns a slice of the node's children. + pub fn children(&self) -> &[Node] { + &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. + /// + /// Updates the name cache for fast lookups. + pub fn add_child(&mut self, child: Node) { + let index = self.children.len(); + self.name_cache.insert(child.name.clone(), index); + self.children.push(child); + } + + /// Adds a property to this node. + /// + /// Updates the property cache for fast lookups. + pub fn add_property(&mut self, prop: Property) { + let name = prop.name.clone(); + let index = self.properties.len(); + self.prop_cache.insert(name, index); + self.properties.push(prop); + } + + /// Gets a child node 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) + } + + /// Gets a mutable reference to a child node by name. + /// + /// 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); + } + + // 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) + } + + /// 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?; + + let removed = self.children.remove(idx); + self.rebuild_name_cache(); + Some(removed) + } + + /// Sets a property, adding it if it doesn't exist or updating if it does. + pub fn set_property(&mut self, prop: Property) { + let name = prop.name.clone(); + if let Some(&idx) = self.prop_cache.get(&name) { + // Update existing property + self.properties[idx] = prop; + } else { + // Add new property + let idx = self.properties.len(); + self.prop_cache.insert(name, idx); + self.properties.push(prop); + } + } + + /// Gets a property by name. + pub fn get_property(&self, name: &str) -> Option<&Property> { + self.prop_cache.get(name).map(|&idx| &self.properties[idx]) + } + + /// Gets a mutable reference to a property by name. + pub fn get_property_mut(&mut self, name: &str) -> Option<&mut Property> { + self.prop_cache + .get(name) + .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) { + // Rebuild indices (need to update subsequent indices after removal) + let prop = self.properties.remove(idx); + self.rebuild_prop_cache(); + Some(prop) + } else { + None + } + } + + /// Returns the `#address-cells` property value. + pub fn address_cells(&self) -> Option { + self.get_property("#address-cells") + .and_then(|prop| prop.get_u32()) + } + + /// Returns the `#size-cells` property value. + pub fn size_cells(&self) -> Option { + self.get_property("#size-cells") + .and_then(|prop| prop.get_u32()) + } + + /// Returns the `phandle` property value. + pub fn phandle(&self) -> Option { + self.get_property("phandle") + .and_then(|prop| prop.get_u32()) + .map(Phandle::from) + } + + /// Returns the `interrupt-parent` property value. + pub fn interrupt_parent(&self) -> Option { + self.get_property("interrupt-parent") + .and_then(|prop| prop.get_u32()) + .map(Phandle::from) + } + + /// Returns the `status` property value. + pub fn status(&self) -> Option { + let prop = self.get_property("status")?; + let s = prop.as_str()?; + match s { + "okay" => Some(Status::Okay), + "disabled" => Some(Status::Disabled), + _ => None, + } + } + + /// Parses the `ranges` property for address translation. + /// + /// Returns a vector of range entries mapping child bus addresses to parent bus addresses. + pub fn ranges(&self, parent_address_cells: u32) -> Option> { + let prop = self.get_property("ranges")?; + 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)) = ( + reader.read_cells(child_address_cells), + reader.read_cells(parent_addr_cells), + reader.read_cells(size_cells), + ) { + entries.push(RangesEntry { + child_bus_address: child_addr, + parent_bus_address: parent_addr, + length: size, + }); + } + + 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> { + let prop = self.get_property("compatible")?; + Some(prop.as_str_iter()) + } + + /// Returns an iterator over all compatible strings. + pub fn compatibles(&self) -> impl Iterator { + self.get_property("compatible") + .map(|prop| prop.as_str_iter()) + .into_iter() + .flatten() + } + + /// Returns the `device_type` property value. + pub fn device_type(&self) -> Option<&str> { + let prop = self.get_property("device_type")?; + 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); + } + + 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) + } + } + + /// 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) + } + } + } +} + +impl From<&fdt_raw::Node<'_>> for Node { + fn from(raw: &fdt_raw::Node<'_>) -> Self { + let mut new_node = Node::new(raw.name()); + // Copy properties + for raw_prop in raw.properties() { + let prop = Property::from(&raw_prop); + new_node.set_property(prop); + } + new_node + } +} diff --git a/fdt-edit/src/prop/mod.rs b/fdt-edit/src/prop/mod.rs new file mode 100644 index 0000000..02fc943 --- /dev/null +++ b/fdt-edit/src/prop/mod.rs @@ -0,0 +1,157 @@ +//! Device tree property representation and manipulation. +//! +//! This module provides the `Property` type which represents a mutable device tree +//! property with a name and data, along with methods for accessing and modifying +//! various property data formats. + +use core::ffi::CStr; + +use alloc::{ + string::{String, ToString}, + vec::Vec, +}; + +use fdt_raw::data::{Bytes, StrIter, U32Iter}; +// Re-export from fdt_raw +pub use fdt_raw::{Phandle, RegInfo, Status, data::Reader}; + +/// A mutable device tree property. +/// +/// Represents a property with a name and raw data. Provides methods for +/// accessing and modifying the data in various formats (u32, u64, strings, etc.). +#[derive(Clone)] +pub struct Property { + /// Property name + pub name: String, + /// Raw property data + pub data: Vec, +} + +impl Property { + /// Creates a new property with the given name and data. + pub fn new(name: &str, data: Vec) -> Self { + Self { + name: name.to_string(), + data, + } + } + + /// Returns the property name. + pub fn name(&self) -> &str { + &self.name + } + + /// Returns the property data as a big-endian u32. + /// + /// Returns None if the data is not exactly 4 bytes. + pub fn get_u32(&self) -> Option { + if self.data.len() != 4 { + return None; + } + Some(u32::from_be_bytes([ + self.data[0], + self.data[1], + self.data[2], + self.data[3], + ])) + } + + /// Sets the property data from a list of u32 values (as big-endian). + pub fn set_u32_ls(&mut self, values: &[u32]) { + self.data.clear(); + for &value in values { + self.data.extend_from_slice(&value.to_be_bytes()); + } + } + + /// Returns an iterator over u32 values in the property data. + pub fn get_u32_iter(&self) -> U32Iter<'_> { + Bytes::new(&self.data).as_u32_iter() + } + + /// Returns the property data as a big-endian u64. + /// + /// Returns None if the data is not exactly 8 bytes. + pub fn get_u64(&self) -> Option { + if self.data.len() != 8 { + return None; + } + Some(u64::from_be_bytes([ + self.data[0], + self.data[1], + self.data[2], + self.data[3], + self.data[4], + self.data[5], + self.data[6], + self.data[7], + ])) + } + + /// Sets the property data from a u64 value (as big-endian). + pub fn set_u64(&mut self, value: u64) { + self.data = value.to_be_bytes().to_vec(); + } + + /// Returns the property data as a null-terminated string. + /// + /// Returns None if the data is not a valid null-terminated UTF-8 string. + pub fn as_str(&self) -> Option<&str> { + CStr::from_bytes_with_nul(&self.data) + .ok() + .and_then(|cstr| cstr.to_str().ok()) + } + + /// Sets the property data from a string value. + /// + /// The string will be null-terminated. + pub fn set_string(&mut self, value: &str) { + let mut bytes = value.as_bytes().to_vec(); + bytes.push(0); // Null-terminate + self.data = bytes; + } + + /// Returns an iterator over null-terminated strings in the property data. + pub fn as_str_iter(&self) -> StrIter<'_> { + Bytes::new(&self.data).as_str_iter() + } + + /// Sets the property data from a list of string values. + /// + /// Each string will be null-terminated. + pub fn set_string_ls(&mut self, values: &[&str]) { + self.data.clear(); + for &value in values { + self.data.extend_from_slice(value.as_bytes()); + self.data.push(0); // Null-terminate each string + } + } + + /// Returns a reader for accessing the property data. + pub fn as_reader(&self) -> Reader<'_> { + Bytes::new(&self.data).reader() + } +} + +impl From<&fdt_raw::Property<'_>> for Property { + fn from(value: &fdt_raw::Property<'_>) -> Self { + Self { + name: value.name().to_string(), + data: value.as_slice().to_vec(), + } + } +} + +/// Ranges entry information for address translation. +/// +/// Represents a single entry in a `ranges` property, mapping a child bus +/// address range to a parent bus address range. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct RangesEntry { + /// Child bus address + pub child_bus_address: u64, + /// Parent bus address + pub parent_bus_address: u64, + /// Length of the region + pub length: u64, +} diff --git a/fdt-raw/Cargo.toml b/fdt-raw/Cargo.toml index 6d76aae..ed1177a 100644 --- a/fdt-raw/Cargo.toml +++ b/fdt-raw/Cargo.toml @@ -2,7 +2,6 @@ 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" keywords = ["device-tree", "dtb", "embedded", "no-std", "bare-metal"] license = "MIT OR Apache-2.0" From 4558d09f09501e48d23f048bfab57e9ef91284ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Tue, 10 Feb 2026 14:38:01 +0800 Subject: [PATCH 08/39] feat: Implement editable FDT structure with node iteration and testing --- fdt-edit/src/fdt.rs | 127 ++++++++++++++++++++++++++++++++++ fdt-edit/src/lib.rs | 5 ++ fdt-edit/src/node/mod.rs | 24 ++++++- fdt-edit/src/node_iter/mod.rs | 92 ++++++++++++++++++++++++ fdt-edit/src/prop/mod.rs | 2 +- fdt-edit/tests/fdt.rs | 12 ++++ 6 files changed, 259 insertions(+), 3 deletions(-) create mode 100644 fdt-edit/src/fdt.rs create mode 100644 fdt-edit/src/node_iter/mod.rs create mode 100644 fdt-edit/tests/fdt.rs diff --git a/fdt-edit/src/fdt.rs b/fdt-edit/src/fdt.rs new file mode 100644 index 0000000..82c0ec4 --- /dev/null +++ b/fdt-edit/src/fdt.rs @@ -0,0 +1,127 @@ +//! Editable Flattened Device Tree (FDT) structure. +//! +//! 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. + +use alloc::{ + collections::BTreeMap, + string::{String, ToString}, + vec::Vec, +}; + +use crate::node_iter::*; +use crate::{FdtError, Phandle}; + +pub use fdt_raw::MemoryReservation; + +use crate::Node; + +/// 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. +#[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, +} + +impl Default for Fdt { + fn default() -> Self { + Self::new() + } +} + +impl Fdt { + /// Creates a new empty FDT. + pub fn new() -> Self { + Self { + boot_cpuid_phys: 0, + memory_reservations: Vec::new(), + root: Node::new(""), + 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) + } + + /// 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) + } + + /// 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(), + }; + + // Build node tree using a stack to track parent nodes + let mut node_stack: Vec = Vec::new(); + + for raw_node in raw_fdt.all_nodes() { + let level = raw_node.level(); + let node = Node::from(&raw_node); + if let Some(phandle) = node.phandle() { + fdt.phandle_cache + .insert(phandle, raw_node.path().to_string()); + } + + // 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; + } + } + + node_stack.push(node); + } + + // 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; + } + } + Ok(fdt) + } + + pub fn all_raw_nodes(&self) -> impl Iterator { + NodeIter::new(&self.root) + } + + pub fn all_raw_nodes_mut(&mut self) -> impl Iterator { + NodeIterMut::new(&mut self.root) + } +} diff --git a/fdt-edit/src/lib.rs b/fdt-edit/src/lib.rs index cf9aa1b..abcd389 100644 --- a/fdt-edit/src/lib.rs +++ b/fdt-edit/src/lib.rs @@ -3,8 +3,13 @@ #[macro_use] extern crate alloc; +mod fdt; mod node; +mod node_iter; mod prop; +pub use fdt_raw::{FdtError, Phandle, RegInfo, Status, data::Reader}; + +pub use fdt::*; pub use node::*; pub use prop::*; diff --git a/fdt-edit/src/node/mod.rs b/fdt-edit/src/node/mod.rs index 02d6bc7..afb0c7d 100644 --- a/fdt-edit/src/node/mod.rs +++ b/fdt-edit/src/node/mod.rs @@ -1,3 +1,5 @@ +use core::fmt::{Debug, Display}; + use alloc::{collections::btree_map::BTreeMap, string::String, vec::Vec}; use fdt_raw::{Phandle, Status}; @@ -50,8 +52,8 @@ impl Node { } /// Returns a mutable iterator over the node's children. - pub fn children_mut(&mut self) -> impl Iterator { - self.children.iter_mut() + pub fn children_mut(&mut self) -> &mut [Node] { + &mut self.children } /// Adds a child node to this node. @@ -351,3 +353,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_iter/mod.rs b/fdt-edit/src/node_iter/mod.rs new file mode 100644 index 0000000..2fca076 --- /dev/null +++ b/fdt-edit/src/node_iter/mod.rs @@ -0,0 +1,92 @@ +use alloc::vec::Vec; + +use crate::Node; + +struct StackEntry<'a> { + node: &'a Node, + child_index: usize, +} + +pub(crate) struct NodeIter<'a> { + stack: Vec>, +} + +impl<'a> NodeIter<'a> { + pub fn new(root: &'a Node) -> Self { + Self { + stack: vec![StackEntry { + node: root, + child_index: 0, + }], + } + } +} + +impl<'a> Iterator for NodeIter<'a> { + type Item = &'a Node; + + fn next(&mut self) -> Option { + while let Some(top) = self.stack.last_mut() { + if top.child_index < top.node.children().len() { + let child = &top.node.children()[top.child_index]; + top.child_index += 1; + self.stack.push(StackEntry { + node: child, + child_index: 0, + }); + return Some(child); + } else { + self.stack.pop(); + } + } + None + } +} + +struct StackEntryMut { + node: *mut Node, + child_index: usize, +} + +unsafe impl Send for StackEntryMut {} + +pub(crate) struct NodeIterMut<'a> { + stack: Vec, + _marker: core::marker::PhantomData<&'a mut Node>, +} + +impl<'a> NodeIterMut<'a> { + pub fn new(root: &'a mut Node) -> Self { + Self { + stack: vec![StackEntryMut { + node: root as *mut Node, + child_index: 0, + }], + _marker: core::marker::PhantomData, + } + } +} + +impl<'a> Iterator for NodeIterMut<'a> { + type Item = &'a mut Node; + + fn next(&mut self) -> Option { + while let Some(top) = self.stack.last_mut() { + unsafe { + let node = &mut *top.node; + if top.child_index < node.children().len() { + let child_ptr = &mut node.children_mut()[top.child_index] as *mut Node; + top.child_index += 1; + self.stack.push(StackEntryMut { + node: child_ptr, + child_index: 0, + }); + return Some(&mut *child_ptr); + } else { + self.stack.pop(); + } + } + } + None + } +} diff --git a/fdt-edit/src/prop/mod.rs b/fdt-edit/src/prop/mod.rs index 02fc943..6a50d71 100644 --- a/fdt-edit/src/prop/mod.rs +++ b/fdt-edit/src/prop/mod.rs @@ -13,7 +13,7 @@ use alloc::{ use fdt_raw::data::{Bytes, StrIter, U32Iter}; // Re-export from fdt_raw -pub use fdt_raw::{Phandle, RegInfo, Status, data::Reader}; +use crate::Reader; /// A mutable device tree property. /// diff --git a/fdt-edit/tests/fdt.rs b/fdt-edit/tests/fdt.rs new file mode 100644 index 0000000..0fabfdb --- /dev/null +++ b/fdt-edit/tests/fdt.rs @@ -0,0 +1,12 @@ +use dtb_file::*; +use fdt_edit::*; + +#[test] +fn test_all_node() { + // Test memory node detection using phytium DTB + let raw_data = fdt_phytium(); + let fdt = Fdt::from_bytes(&raw_data).unwrap(); + for node in fdt.all_raw_nodes() { + println!("Node: {:?}", node); + } +} From 988f4f15c73e7c3042a422c78a787cb77e6f54aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Tue, 10 Feb 2026 15:59:01 +0800 Subject: [PATCH 09/39] feat: Refactor node iteration with new NodeRef and NodeRefMut structures --- fdt-edit/src/fdt.rs | 12 +++- fdt-edit/src/lib.rs | 1 + fdt-edit/src/node_iter/base.rs | 43 ++++++++++++ fdt-edit/src/node_iter/iter_ref.rs | 102 +++++++++++++++++++++++++++++ fdt-edit/src/node_iter/mod.rs | 91 ++----------------------- fdt-edit/tests/fdt.rs | 14 +++- 6 files changed, 172 insertions(+), 91 deletions(-) create mode 100644 fdt-edit/src/node_iter/base.rs create mode 100644 fdt-edit/src/node_iter/iter_ref.rs diff --git a/fdt-edit/src/fdt.rs b/fdt-edit/src/fdt.rs index 82c0ec4..42ad297 100644 --- a/fdt-edit/src/fdt.rs +++ b/fdt-edit/src/fdt.rs @@ -118,10 +118,18 @@ impl Fdt { } pub fn all_raw_nodes(&self) -> impl Iterator { - NodeIter::new(&self.root) + self.all_nodes().map(|node_ref| node_ref.node) } pub fn all_raw_nodes_mut(&mut self) -> impl Iterator { - NodeIterMut::new(&mut self.root) + self.all_nodes_mut().map(|node_ref| node_ref.node) + } + + pub fn all_nodes(&self) -> impl Iterator> { + NodeRefIter::new(&self.root) + } + + pub fn all_nodes_mut(&mut self) -> impl Iterator> { + NodeRefIterMut::new(&mut self.root) } } diff --git a/fdt-edit/src/lib.rs b/fdt-edit/src/lib.rs index abcd389..fabb088 100644 --- a/fdt-edit/src/lib.rs +++ b/fdt-edit/src/lib.rs @@ -13,3 +13,4 @@ pub use fdt_raw::{FdtError, Phandle, RegInfo, Status, data::Reader}; pub use fdt::*; pub use node::*; pub use prop::*; +pub use node_iter::*; diff --git a/fdt-edit/src/node_iter/base.rs b/fdt-edit/src/node_iter/base.rs new file mode 100644 index 0000000..79ed75e --- /dev/null +++ b/fdt-edit/src/node_iter/base.rs @@ -0,0 +1,43 @@ +use core::fmt::Display; + +use crate::Node; + +pub struct NodeRef<'a> { + pub(crate) meta: NodeIterMeta, + pub(crate) node: &'a Node, +} + +pub struct NodeRefMut<'a> { + pub(crate) meta: NodeIterMeta, + pub(crate) node: &'a mut Node, +} + +#[derive(Clone)] +pub(crate) struct NodeIterMeta { + pub(crate) level: usize, + pub(crate) address_cells: usize, + pub(crate) size_cells: usize, +} + +impl NodeIterMeta { + fn write_indent(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + for _ in 0..self.level { + write!(f, " ")?; // Indent based on level + } + Ok(()) + } +} + +impl Display for NodeRef<'_> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + self.meta.write_indent(f)?; + + writeln!(f, "{}", self.node.name)?; + for prop in self.node.properties() { + self.meta.write_indent(f)?; + // writeln!(f, " {} = ", prop.name())?; + } + + Ok(()) + } +} diff --git a/fdt-edit/src/node_iter/iter_ref.rs b/fdt-edit/src/node_iter/iter_ref.rs new file mode 100644 index 0000000..c620803 --- /dev/null +++ b/fdt-edit/src/node_iter/iter_ref.rs @@ -0,0 +1,102 @@ +use alloc::vec::Vec; + +use crate::{NodeIterMeta, NodeRef, NodeRefMut}; + +use super::Node; + +struct StackEntry<'a> { + node: *mut Node, + child_index: usize, + meta: NodeIterMeta, + _marker: core::marker::PhantomData<&'a Node>, +} + +unsafe impl<'a> Send for NodeRefIter<'a> {} + +pub(crate) struct NodeRefIter<'a> { + stack: Vec>, +} + +impl<'a> NodeRefIter<'a> { + pub fn new(root: &'a Node) -> Self { + Self { + stack: vec![StackEntry { + node: root as *const Node as usize as *mut Node, + child_index: 0, + meta: NodeIterMeta { + level: 0, + address_cells: 2, // Default value, can be overridden by root node properties + size_cells: 1, // Default value, can be overridden by root node properties + }, + _marker: core::marker::PhantomData, + }], + } + } + + fn next_raw(&mut self) -> Option<(*mut Node, NodeIterMeta)> { + while let Some(top) = self.stack.last_mut() { + unsafe { + let node = &*top.node; + + if top.child_index < node.children().len() { + let child = &node.children()[top.child_index]; + let child_ptr = child as *const Node as usize as *mut Node; + top.child_index += 1; + + // Update meta information based on the current node's properties + let child_meta = NodeIterMeta { + level: top.meta.level + 1, + address_cells: top.meta.address_cells, // This should be updated based on the child's properties if it has #address-cells + size_cells: top.meta.size_cells, // This should be updated based on the child's properties if it has #size-cells + }; + + self.stack.push(StackEntry { + node: child_ptr, + child_index: 0, + meta: child_meta.clone(), + _marker: core::marker::PhantomData, + }); + + return Some((child_ptr, child_meta)); + } else { + self.stack.pop(); + } + } + } + None + } +} + +impl<'a> Iterator for NodeRefIter<'a> { + type Item = NodeRef<'a>; + + fn next(&mut self) -> Option { + self.next_raw().map(|(node_ptr, meta)| NodeRef { + node: unsafe { &*node_ptr }, + meta, + }) + } +} + +pub(crate) struct NodeRefIterMut<'a> { + inner: NodeRefIter<'a>, +} + +impl<'a> NodeRefIterMut<'a> { + pub fn new(root: &'a mut Node) -> Self { + Self { + inner: NodeRefIter::new(root), + } + } +} + +impl<'a> Iterator for NodeRefIterMut<'a> { + type Item = NodeRefMut<'a>; + + fn next(&mut self) -> Option { + self.inner.next_raw().map(|(node_ptr, meta)| NodeRefMut { + node: unsafe { &mut *node_ptr }, + meta, + }) + } +} diff --git a/fdt-edit/src/node_iter/mod.rs b/fdt-edit/src/node_iter/mod.rs index 2fca076..fa532b9 100644 --- a/fdt-edit/src/node_iter/mod.rs +++ b/fdt-edit/src/node_iter/mod.rs @@ -2,91 +2,8 @@ use alloc::vec::Vec; use crate::Node; -struct StackEntry<'a> { - node: &'a Node, - child_index: usize, -} +mod iter_ref; +mod base; -pub(crate) struct NodeIter<'a> { - stack: Vec>, -} - -impl<'a> NodeIter<'a> { - pub fn new(root: &'a Node) -> Self { - Self { - stack: vec![StackEntry { - node: root, - child_index: 0, - }], - } - } -} - -impl<'a> Iterator for NodeIter<'a> { - type Item = &'a Node; - - fn next(&mut self) -> Option { - while let Some(top) = self.stack.last_mut() { - if top.child_index < top.node.children().len() { - let child = &top.node.children()[top.child_index]; - top.child_index += 1; - self.stack.push(StackEntry { - node: child, - child_index: 0, - }); - return Some(child); - } else { - self.stack.pop(); - } - } - None - } -} - -struct StackEntryMut { - node: *mut Node, - child_index: usize, -} - -unsafe impl Send for StackEntryMut {} - -pub(crate) struct NodeIterMut<'a> { - stack: Vec, - _marker: core::marker::PhantomData<&'a mut Node>, -} - -impl<'a> NodeIterMut<'a> { - pub fn new(root: &'a mut Node) -> Self { - Self { - stack: vec![StackEntryMut { - node: root as *mut Node, - child_index: 0, - }], - _marker: core::marker::PhantomData, - } - } -} - -impl<'a> Iterator for NodeIterMut<'a> { - type Item = &'a mut Node; - - fn next(&mut self) -> Option { - while let Some(top) = self.stack.last_mut() { - unsafe { - let node = &mut *top.node; - if top.child_index < node.children().len() { - let child_ptr = &mut node.children_mut()[top.child_index] as *mut Node; - top.child_index += 1; - self.stack.push(StackEntryMut { - node: child_ptr, - child_index: 0, - }); - return Some(&mut *child_ptr); - } else { - self.stack.pop(); - } - } - } - None - } -} +pub use iter_ref::*; +pub use base::*; diff --git a/fdt-edit/tests/fdt.rs b/fdt-edit/tests/fdt.rs index 0fabfdb..f3bc102 100644 --- a/fdt-edit/tests/fdt.rs +++ b/fdt-edit/tests/fdt.rs @@ -1,12 +1,22 @@ use dtb_file::*; use fdt_edit::*; +#[test] +fn test_all_raw_node() { + // Test memory node detection using phytium DTB + let raw_data = fdt_phytium(); + let mut fdt = Fdt::from_bytes(&raw_data).unwrap(); + for node in fdt.all_raw_nodes_mut() { + println!("{:?}", node); + } +} + #[test] fn test_all_node() { // Test memory node detection using phytium DTB let raw_data = fdt_phytium(); let fdt = Fdt::from_bytes(&raw_data).unwrap(); - for node in fdt.all_raw_nodes() { - println!("Node: {:?}", node); + for node in fdt.all_nodes() { + println!("{}", node); } } From fa4b74290f65091b03e6c30b495c402c2634a33f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Tue, 10 Feb 2026 16:11:57 +0800 Subject: [PATCH 10/39] feat: Update property formatting in NodeRef display implementation --- fdt-edit/src/node_iter/base.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/fdt-edit/src/node_iter/base.rs b/fdt-edit/src/node_iter/base.rs index 79ed75e..3327bc6 100644 --- a/fdt-edit/src/node_iter/base.rs +++ b/fdt-edit/src/node_iter/base.rs @@ -35,7 +35,16 @@ impl Display for NodeRef<'_> { writeln!(f, "{}", self.node.name)?; for prop in self.node.properties() { self.meta.write_indent(f)?; - // writeln!(f, " {} = ", prop.name())?; + write!(f, " {} = ", prop.name())?; + + if let Some(str) = prop.as_str() { + writeln!(f, "\"{}\";", str)?; + } else { + for cell in prop.get_u32_iter() { + write!(f, "{:#x} ", cell)?; + } + writeln!(f, ";")?; + } } Ok(()) From baeb699008cccaf338f57f6614941b961352bdd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Tue, 10 Feb 2026 17:01:30 +0800 Subject: [PATCH 11/39] feat: Refactor Node handling with NodeGeneric and update NodeRef/NodeRefMut implementations --- fdt-edit/src/fdt.rs | 9 +-- fdt-edit/src/node_iter/base.rs | 27 +++++--- fdt-edit/src/node_iter/iter_ref.rs | 56 ++++++++++------ fdt-edit/src/node_iter/mod.rs | 101 ++++++++++++++++++++++++++++- 4 files changed, 157 insertions(+), 36 deletions(-) diff --git a/fdt-edit/src/fdt.rs b/fdt-edit/src/fdt.rs index 42ad297..d7789a8 100644 --- a/fdt-edit/src/fdt.rs +++ b/fdt-edit/src/fdt.rs @@ -118,18 +118,19 @@ impl Fdt { } pub fn all_raw_nodes(&self) -> impl Iterator { - self.all_nodes().map(|node_ref| node_ref.node) + self.all_nodes().map(|node_ref| node_ref._as_raw()) } pub fn all_raw_nodes_mut(&mut self) -> impl Iterator { - self.all_nodes_mut().map(|node_ref| node_ref.node) + self.all_nodes_mut() + .map(|mut node_ref| node_ref._as_raw_mut()) } - pub fn all_nodes(&self) -> impl Iterator> { + pub fn all_nodes(&self) -> impl Iterator> + '_ { NodeRefIter::new(&self.root) } - pub fn all_nodes_mut(&mut self) -> impl Iterator> { + pub fn all_nodes_mut(&mut self) -> impl Iterator> + '_ { NodeRefIterMut::new(&mut self.root) } } diff --git a/fdt-edit/src/node_iter/base.rs b/fdt-edit/src/node_iter/base.rs index 3327bc6..d7ba256 100644 --- a/fdt-edit/src/node_iter/base.rs +++ b/fdt-edit/src/node_iter/base.rs @@ -2,16 +2,27 @@ use core::fmt::Display; use crate::Node; -pub struct NodeRef<'a> { +pub struct NodeGeneric { pub(crate) meta: NodeIterMeta, - pub(crate) node: &'a Node, + pub(crate) node: *mut Node, } -pub struct NodeRefMut<'a> { - pub(crate) meta: NodeIterMeta, - pub(crate) node: &'a mut Node, +impl NodeGeneric { + pub(crate) fn new(node: *mut Node, meta: NodeIterMeta) -> Self { + Self { node, meta } + } + + pub fn as_node<'a>(&self) -> &'a Node { + unsafe { &*self.node } + } + + pub fn as_node_mut<'a>(&mut self) -> &'a mut Node { + unsafe { &mut *self.node } + } } +unsafe impl Send for NodeGeneric {} + #[derive(Clone)] pub(crate) struct NodeIterMeta { pub(crate) level: usize, @@ -28,12 +39,12 @@ impl NodeIterMeta { } } -impl Display for NodeRef<'_> { +impl Display for NodeGeneric { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { self.meta.write_indent(f)?; - writeln!(f, "{}", self.node.name)?; - for prop in self.node.properties() { + writeln!(f, "{}", self.as_node().name)?; + for prop in self.as_node().properties() { self.meta.write_indent(f)?; write!(f, " {} = ", prop.name())?; diff --git a/fdt-edit/src/node_iter/iter_ref.rs b/fdt-edit/src/node_iter/iter_ref.rs index c620803..edf1182 100644 --- a/fdt-edit/src/node_iter/iter_ref.rs +++ b/fdt-edit/src/node_iter/iter_ref.rs @@ -1,24 +1,26 @@ +use core::fmt::Display; + use alloc::vec::Vec; +use fdt_raw::NodeBase; -use crate::{NodeIterMeta, NodeRef, NodeRefMut}; +use crate::{NodeGeneric, NodeIterMeta, NodeKind, NodeRef, NodeRefMut}; use super::Node; -struct StackEntry<'a> { +struct StackEntry { node: *mut Node, child_index: usize, meta: NodeIterMeta, - _marker: core::marker::PhantomData<&'a Node>, } -unsafe impl<'a> Send for NodeRefIter<'a> {} +unsafe impl Send for StackEntry {} -pub(crate) struct NodeRefIter<'a> { - stack: Vec>, +pub(crate) struct NodeIter { + stack: Vec, } -impl<'a> NodeRefIter<'a> { - pub fn new(root: &'a Node) -> Self { +impl NodeIter { + pub fn new(root: &Node) -> Self { Self { stack: vec![StackEntry { node: root as *const Node as usize as *mut Node, @@ -28,7 +30,6 @@ impl<'a> NodeRefIter<'a> { address_cells: 2, // Default value, can be overridden by root node properties size_cells: 1, // Default value, can be overridden by root node properties }, - _marker: core::marker::PhantomData, }], } } @@ -54,7 +55,6 @@ impl<'a> NodeRefIter<'a> { node: child_ptr, child_index: 0, meta: child_meta.clone(), - _marker: core::marker::PhantomData, }); return Some((child_ptr, child_meta)); @@ -67,25 +67,40 @@ impl<'a> NodeRefIter<'a> { } } +pub(crate) struct NodeRefIter<'a> { + iter: NodeIter, + _marker: core::marker::PhantomData<&'a ()>, +} + +impl<'a> NodeRefIter<'a> { + pub fn new(root: &Node) -> Self { + Self { + iter: NodeIter::new(root), + _marker: core::marker::PhantomData, + } + } +} + impl<'a> Iterator for NodeRefIter<'a> { type Item = NodeRef<'a>; fn next(&mut self) -> Option { - self.next_raw().map(|(node_ptr, meta)| NodeRef { - node: unsafe { &*node_ptr }, - meta, - }) + self.iter + .next_raw() + .map(|(node_ptr, meta)| NodeRef::new(node_ptr, meta)) } } pub(crate) struct NodeRefIterMut<'a> { - inner: NodeRefIter<'a>, + iter: NodeIter, + _marker: core::marker::PhantomData<&'a ()>, } impl<'a> NodeRefIterMut<'a> { - pub fn new(root: &'a mut Node) -> Self { + pub fn new(root: &Node) -> Self { Self { - inner: NodeRefIter::new(root), + iter: NodeIter::new(root), + _marker: core::marker::PhantomData, } } } @@ -94,9 +109,8 @@ impl<'a> Iterator for NodeRefIterMut<'a> { type Item = NodeRefMut<'a>; fn next(&mut self) -> Option { - self.inner.next_raw().map(|(node_ptr, meta)| NodeRefMut { - node: unsafe { &mut *node_ptr }, - meta, - }) + self.iter + .next_raw() + .map(|(node_ptr, meta)| NodeRefMut::new(node_ptr, meta)) } } diff --git a/fdt-edit/src/node_iter/mod.rs b/fdt-edit/src/node_iter/mod.rs index fa532b9..e74ac20 100644 --- a/fdt-edit/src/node_iter/mod.rs +++ b/fdt-edit/src/node_iter/mod.rs @@ -1,9 +1,104 @@ -use alloc::vec::Vec; +use core::{ + fmt::Display, + ops::{Deref, DerefMut}, +}; use crate::Node; -mod iter_ref; mod base; +mod iter_ref; -pub use iter_ref::*; pub use base::*; +pub(crate) use iter_ref::*; + +pub enum NodeKind { + Generic(NodeGeneric), +} + +impl NodeKind { + pub(crate) fn new(node: *mut Node, meta: NodeIterMeta) -> Self { + NodeKind::Generic(NodeGeneric::new(node, meta)) + } + + pub(crate) fn _as_raw<'a>(&self) -> &'a Node { + match self { + NodeKind::Generic(generic) => generic.as_node(), + } + } + + pub(crate) fn _as_raw_mut<'a>(&mut self) -> &'a mut Node { + match self { + NodeKind::Generic(generic) => generic.as_node_mut(), + } + } + + pub(crate) fn _fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + NodeKind::Generic(generic) => generic.fmt(f), + } + } +} + +pub struct NodeRef<'a> { + inner: NodeKind, + _marker: core::marker::PhantomData<&'a ()>, +} + +impl<'a> NodeRef<'a> { + pub(crate) fn new(node: *mut Node, meta: NodeIterMeta) -> Self { + Self { + inner: NodeKind::new(node, meta), + _marker: core::marker::PhantomData, + } + } + + pub fn as_raw(&self) -> &Node { + self.inner._as_raw() + } +} + +impl<'a> Deref for NodeRef<'a> { + type Target = NodeKind; + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl Display for NodeRef<'_> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + self.inner._fmt(f) + } +} + +pub struct NodeRefMut<'a> { + inner: NodeKind, + _marker: core::marker::PhantomData<&'a ()>, +} + +impl<'a> NodeRefMut<'a> { + pub(crate) fn new(node: *mut Node, meta: NodeIterMeta) -> Self { + Self { + inner: NodeKind::new(node, meta), + _marker: core::marker::PhantomData, + } + } +} + +impl<'a> Deref for NodeRefMut<'a> { + type Target = NodeKind; + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl<'a> DerefMut for NodeRefMut<'a> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner + } +} + +impl Display for NodeRefMut<'_> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + self.inner._fmt(f) + } +} From 5c11a700113c524ae7181e8f27c166c8252ed6b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Tue, 10 Feb 2026 17:05:18 +0800 Subject: [PATCH 12/39] feat: Refactor Node iteration and enhance NodeKind with NodeOp trait --- fdt-edit/src/fdt.rs | 2 +- fdt-edit/src/node_iter/iter_ref.rs | 5 +---- fdt-edit/src/node_iter/mod.rs | 30 +++++++++++++++++++++--------- 3 files changed, 23 insertions(+), 14 deletions(-) diff --git a/fdt-edit/src/fdt.rs b/fdt-edit/src/fdt.rs index d7789a8..ab9fee1 100644 --- a/fdt-edit/src/fdt.rs +++ b/fdt-edit/src/fdt.rs @@ -131,6 +131,6 @@ impl Fdt { } pub fn all_nodes_mut(&mut self) -> impl Iterator> + '_ { - NodeRefIterMut::new(&mut self.root) + NodeRefIterMut::new(&self.root) } } diff --git a/fdt-edit/src/node_iter/iter_ref.rs b/fdt-edit/src/node_iter/iter_ref.rs index edf1182..0684764 100644 --- a/fdt-edit/src/node_iter/iter_ref.rs +++ b/fdt-edit/src/node_iter/iter_ref.rs @@ -1,9 +1,6 @@ -use core::fmt::Display; - use alloc::vec::Vec; -use fdt_raw::NodeBase; -use crate::{NodeGeneric, NodeIterMeta, NodeKind, NodeRef, NodeRefMut}; +use crate::{NodeIterMeta, NodeRef, NodeRefMut}; use super::Node; diff --git a/fdt-edit/src/node_iter/mod.rs b/fdt-edit/src/node_iter/mod.rs index e74ac20..b0ebd5a 100644 --- a/fdt-edit/src/node_iter/mod.rs +++ b/fdt-edit/src/node_iter/mod.rs @@ -9,33 +9,45 @@ mod base; mod iter_ref; pub use base::*; +use enum_dispatch::enum_dispatch; pub(crate) use iter_ref::*; +#[enum_dispatch(NodeOp)] pub enum NodeKind { Generic(NodeGeneric), } +#[enum_dispatch] +pub(crate) trait NodeOp { + fn as_generic(&self) -> &NodeGeneric; + fn as_generic_mut(&mut self) -> &mut NodeGeneric; +} + impl NodeKind { pub(crate) fn new(node: *mut Node, meta: NodeIterMeta) -> Self { NodeKind::Generic(NodeGeneric::new(node, meta)) } pub(crate) fn _as_raw<'a>(&self) -> &'a Node { - match self { - NodeKind::Generic(generic) => generic.as_node(), - } + self.as_generic().as_node() } pub(crate) fn _as_raw_mut<'a>(&mut self) -> &'a mut Node { - match self { - NodeKind::Generic(generic) => generic.as_node_mut(), - } + self.as_generic_mut().as_node_mut() } pub(crate) fn _fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - match self { - NodeKind::Generic(generic) => generic.fmt(f), - } + self.as_generic().fmt(f) + } +} + +impl NodeOp for NodeGeneric { + fn as_generic(&self) -> &NodeGeneric { + self + } + + fn as_generic_mut(&mut self) -> &mut NodeGeneric { + self } } From 57992b4aac64fc9f9baae78e871fc35556d33f65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Tue, 10 Feb 2026 17:14:19 +0800 Subject: [PATCH 13/39] feat: Enhance compatible property formatting in NodeGeneric display implementation --- fdt-edit/src/node_iter/base.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/fdt-edit/src/node_iter/base.rs b/fdt-edit/src/node_iter/base.rs index d7ba256..99040db 100644 --- a/fdt-edit/src/node_iter/base.rs +++ b/fdt-edit/src/node_iter/base.rs @@ -48,6 +48,18 @@ impl Display for NodeGeneric { self.meta.write_indent(f)?; write!(f, " {} = ", prop.name())?; + if prop.name() == "compatible" { + write!(f, "[")?; + for (i, str) in prop.as_str_iter().enumerate() { + write!(f, "\"{}\"", str)?; + if i != prop.as_str_iter().count() - 1 { + write!(f, ", ")?; + } + } + writeln!(f, "]")?; + continue; + } + if let Some(str) = prop.as_str() { writeln!(f, "\"{}\";", str)?; } else { From 503d26c5039c280c9223be93c46f25396a6e195f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Tue, 10 Feb 2026 17:28:14 +0800 Subject: [PATCH 14/39] feat: Add NodeMemory struct and enhance NodeKind with memory node support --- fdt-edit/src/lib.rs | 4 +-- fdt-edit/src/node_iter/base.rs | 16 ++++++++++-- fdt-edit/src/node_iter/iter_ref.rs | 8 +++--- fdt-edit/src/node_iter/memory.rs | 41 ++++++++++++++++++++++++++++++ fdt-edit/src/node_iter/mod.rs | 23 ++++++++++++----- 5 files changed, 77 insertions(+), 15 deletions(-) create mode 100644 fdt-edit/src/node_iter/memory.rs diff --git a/fdt-edit/src/lib.rs b/fdt-edit/src/lib.rs index fabb088..6da3ba0 100644 --- a/fdt-edit/src/lib.rs +++ b/fdt-edit/src/lib.rs @@ -8,9 +8,9 @@ mod node; mod node_iter; mod prop; -pub use fdt_raw::{FdtError, Phandle, RegInfo, Status, data::Reader}; +pub use fdt_raw::{FdtError, MemoryRegion, Phandle, RegInfo, Status, data::Reader}; pub use fdt::*; pub use node::*; -pub use prop::*; pub use node_iter::*; +pub use prop::*; diff --git a/fdt-edit/src/node_iter/base.rs b/fdt-edit/src/node_iter/base.rs index 99040db..831fa02 100644 --- a/fdt-edit/src/node_iter/base.rs +++ b/fdt-edit/src/node_iter/base.rs @@ -19,6 +19,18 @@ impl NodeGeneric { pub fn as_node_mut<'a>(&mut self) -> &'a mut Node { unsafe { &mut *self.node } } + + pub fn name(&self) -> &str { + self.as_node().name() + } + + pub fn properties(&self) -> &[crate::Property] { + self.as_node().properties() + } + + pub fn get_property(&self, name: &str) -> Option<&crate::Property> { + self.as_node().get_property(name) + } } unsafe impl Send for NodeGeneric {} @@ -26,8 +38,8 @@ unsafe impl Send for NodeGeneric {} #[derive(Clone)] pub(crate) struct NodeIterMeta { pub(crate) level: usize, - pub(crate) address_cells: usize, - pub(crate) size_cells: usize, + pub(crate) parent_address_cells: usize, + pub(crate) parent_size_cells: usize, } impl NodeIterMeta { diff --git a/fdt-edit/src/node_iter/iter_ref.rs b/fdt-edit/src/node_iter/iter_ref.rs index 0684764..3c1df5c 100644 --- a/fdt-edit/src/node_iter/iter_ref.rs +++ b/fdt-edit/src/node_iter/iter_ref.rs @@ -24,8 +24,8 @@ impl NodeIter { child_index: 0, meta: NodeIterMeta { level: 0, - address_cells: 2, // Default value, can be overridden by root node properties - size_cells: 1, // Default value, can be overridden by root node properties + parent_address_cells: 2, // Default value, can be overridden by root node properties + parent_size_cells: 1, // Default value, can be overridden by root node properties }, }], } @@ -44,8 +44,8 @@ impl NodeIter { // Update meta information based on the current node's properties let child_meta = NodeIterMeta { level: top.meta.level + 1, - address_cells: top.meta.address_cells, // This should be updated based on the child's properties if it has #address-cells - size_cells: top.meta.size_cells, // This should be updated based on the child's properties if it has #size-cells + parent_address_cells: top.meta.parent_address_cells, // This should be updated based on the child's properties if it has #address-cells + parent_size_cells: top.meta.parent_size_cells, // This should be updated based on the child's properties if it has #size-cells }; self.stack.push(StackEntry { diff --git a/fdt-edit/src/node_iter/memory.rs b/fdt-edit/src/node_iter/memory.rs new file mode 100644 index 0000000..25b6389 --- /dev/null +++ b/fdt-edit/src/node_iter/memory.rs @@ -0,0 +1,41 @@ +use alloc::vec::Vec; +use fdt_raw::MemoryRegion; + +use crate::{NodeGeneric, NodeOp}; + +pub struct NodeMemory { + pub(crate) base: NodeGeneric, +} + +impl NodeMemory { + pub(crate) fn try_new(node: NodeGeneric) -> Result { + if is_memory_node(&node) { + Ok(Self { base: node }) + } else { + Err(node) + } + } +} + +impl NodeOp for NodeMemory { + fn as_generic(&self) -> &NodeGeneric { + &self.base + } + + fn as_generic_mut(&mut self) -> &mut NodeGeneric { + &mut self.base + } +} + +/// Check if node is a memory node +fn is_memory_node(node: &NodeGeneric) -> bool { + // Check if device_type property is "memory" + if let Some(device_type) = node.as_node().device_type() + && device_type == "memory" + { + return true; + } + + // Or node name starts with "memory" + node.as_node().name().starts_with("memory") +} diff --git a/fdt-edit/src/node_iter/mod.rs b/fdt-edit/src/node_iter/mod.rs index b0ebd5a..b2f5a61 100644 --- a/fdt-edit/src/node_iter/mod.rs +++ b/fdt-edit/src/node_iter/mod.rs @@ -7,25 +7,38 @@ use crate::Node; mod base; mod iter_ref; +mod memory; pub use base::*; use enum_dispatch::enum_dispatch; pub(crate) use iter_ref::*; +pub use memory::*; #[enum_dispatch(NodeOp)] pub enum NodeKind { Generic(NodeGeneric), + Memory(NodeMemory), } #[enum_dispatch] pub(crate) trait NodeOp { fn as_generic(&self) -> &NodeGeneric; fn as_generic_mut(&mut self) -> &mut NodeGeneric; + fn _display(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + self.as_generic().fmt(f) + } } impl NodeKind { pub(crate) fn new(node: *mut Node, meta: NodeIterMeta) -> Self { - NodeKind::Generic(NodeGeneric::new(node, meta)) + let generic = NodeGeneric::new(node, meta.clone()); + + let generic = match NodeMemory::try_new(generic) { + Ok(mem) => return NodeKind::Memory(mem), + Err(generic) => generic, + }; + + NodeKind::Generic(generic) } pub(crate) fn _as_raw<'a>(&self) -> &'a Node { @@ -35,10 +48,6 @@ impl NodeKind { pub(crate) fn _as_raw_mut<'a>(&mut self) -> &'a mut Node { self.as_generic_mut().as_node_mut() } - - pub(crate) fn _fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - self.as_generic().fmt(f) - } } impl NodeOp for NodeGeneric { @@ -78,7 +87,7 @@ impl<'a> Deref for NodeRef<'a> { impl Display for NodeRef<'_> { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - self.inner._fmt(f) + self.inner._display(f) } } @@ -111,6 +120,6 @@ impl<'a> DerefMut for NodeRefMut<'a> { impl Display for NodeRefMut<'_> { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - self.inner._fmt(f) + self.inner._display(f) } } From 2aa1f93637e5fb551e0a8d594d583a9c7f227e74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Tue, 10 Feb 2026 17:36:38 +0800 Subject: [PATCH 15/39] feat: Enhance NodeIterMeta to support parent path tracking and update NodeIter for path construction --- fdt-edit/src/node_iter/base.rs | 25 ++++++++++++++++++++++--- fdt-edit/src/node_iter/iter_ref.rs | 13 +++++++------ 2 files changed, 29 insertions(+), 9 deletions(-) diff --git a/fdt-edit/src/node_iter/base.rs b/fdt-edit/src/node_iter/base.rs index 831fa02..4ed5c89 100644 --- a/fdt-edit/src/node_iter/base.rs +++ b/fdt-edit/src/node_iter/base.rs @@ -1,3 +1,4 @@ +use alloc::{string::String, vec::Vec}; use core::fmt::Display; use crate::Node; @@ -31,6 +32,25 @@ impl NodeGeneric { pub fn get_property(&self, name: &str) -> Option<&crate::Property> { self.as_node().get_property(name) } + + pub fn parent_path(&self) -> &[String] { + &self.meta.parent_path + } + + pub fn parent_path_string(&self) -> String { + if self.meta.parent_path.is_empty() { + return String::from("/"); + } + format!("/{}", self.meta.parent_path.join("/")) + } + + pub fn path(&self) -> String { + let parent = self.parent_path_string(); + if parent == "/" { + return format!("/{}", self.name()); + } + format!("{}/{}", parent, self.name()) + } } unsafe impl Send for NodeGeneric {} @@ -38,8 +58,7 @@ unsafe impl Send for NodeGeneric {} #[derive(Clone)] pub(crate) struct NodeIterMeta { pub(crate) level: usize, - pub(crate) parent_address_cells: usize, - pub(crate) parent_size_cells: usize, + pub(crate) parent_path: Vec, } impl NodeIterMeta { @@ -55,7 +74,7 @@ impl Display for NodeGeneric { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { self.meta.write_indent(f)?; - writeln!(f, "{}", self.as_node().name)?; + writeln!(f, "{}", self.path())?; for prop in self.as_node().properties() { self.meta.write_indent(f)?; write!(f, " {} = ", prop.name())?; diff --git a/fdt-edit/src/node_iter/iter_ref.rs b/fdt-edit/src/node_iter/iter_ref.rs index 3c1df5c..47e71c2 100644 --- a/fdt-edit/src/node_iter/iter_ref.rs +++ b/fdt-edit/src/node_iter/iter_ref.rs @@ -1,4 +1,4 @@ -use alloc::vec::Vec; +use alloc::{vec, vec::Vec}; use crate::{NodeIterMeta, NodeRef, NodeRefMut}; @@ -24,8 +24,7 @@ impl NodeIter { child_index: 0, meta: NodeIterMeta { level: 0, - parent_address_cells: 2, // Default value, can be overridden by root node properties - parent_size_cells: 1, // Default value, can be overridden by root node properties + parent_path: vec![], }, }], } @@ -41,11 +40,13 @@ impl NodeIter { let child_ptr = child as *const Node as usize as *mut Node; top.child_index += 1; - // Update meta information based on the current node's properties + // Build the parent path for the child node + let mut parent_path = top.meta.parent_path.clone(); + parent_path.push(node.name().into()); + let child_meta = NodeIterMeta { level: top.meta.level + 1, - parent_address_cells: top.meta.parent_address_cells, // This should be updated based on the child's properties if it has #address-cells - parent_size_cells: top.meta.parent_size_cells, // This should be updated based on the child's properties if it has #size-cells + parent_path, }; self.stack.push(StackEntry { From c366cdd5a21fc202b781e30ad488edb0746aa286 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Tue, 10 Feb 2026 17:43:29 +0800 Subject: [PATCH 16/39] feat: Add path retrieval method to NodeKind and implement exact path lookup in Fdt --- fdt-edit/src/fdt.rs | 13 +++++++++++ fdt-edit/src/node_iter/mod.rs | 5 ++++ fdt-edit/tests/fdt.rs | 44 +++++++++++++++++++++++++++++++++++ 3 files changed, 62 insertions(+) diff --git a/fdt-edit/src/fdt.rs b/fdt-edit/src/fdt.rs index ab9fee1..f2b8847 100644 --- a/fdt-edit/src/fdt.rs +++ b/fdt-edit/src/fdt.rs @@ -133,4 +133,17 @@ impl Fdt { pub fn all_nodes_mut(&mut self) -> impl Iterator> + '_ { NodeRefIterMut::new(&self.root) } + + fn get_by_path_exact(&self, path: &str) -> Option> { + self.all_nodes().find(|node| node.path().as_str() == path) + } + + // /// Resolves an alias to its full path. + // /// + // /// 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_exact("/aliases")?; + + // } } diff --git a/fdt-edit/src/node_iter/mod.rs b/fdt-edit/src/node_iter/mod.rs index b2f5a61..3a285b7 100644 --- a/fdt-edit/src/node_iter/mod.rs +++ b/fdt-edit/src/node_iter/mod.rs @@ -9,6 +9,7 @@ mod base; mod iter_ref; mod memory; +use alloc::string::String; pub use base::*; use enum_dispatch::enum_dispatch; pub(crate) use iter_ref::*; @@ -48,6 +49,10 @@ impl NodeKind { pub(crate) fn _as_raw_mut<'a>(&mut self) -> &'a mut Node { self.as_generic_mut().as_node_mut() } + + pub fn path(&self) -> String { + self.as_generic().path() + } } impl NodeOp for NodeGeneric { diff --git a/fdt-edit/tests/fdt.rs b/fdt-edit/tests/fdt.rs index f3bc102..b580033 100644 --- a/fdt-edit/tests/fdt.rs +++ b/fdt-edit/tests/fdt.rs @@ -1,5 +1,18 @@ +use std::sync::Once; + use dtb_file::*; 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_all_raw_node() { @@ -20,3 +33,34 @@ fn test_all_node() { println!("{}", node); } } + +#[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() + ); +} From 208fe9c299a9cd3d4ba49362794f95387bb6697d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Tue, 10 Feb 2026 20:24:03 +0800 Subject: [PATCH 17/39] Refactor device tree node iteration and viewing - Removed obsolete node iteration and memory handling code from `node_iter` module. - Introduced `NodeView` and `NodeViewMut` for safe, typed access to device tree nodes. - Implemented visitor traits (`Visit` and `VisitMut`) for traversing device tree nodes. - Updated tests to validate new node iteration and classification logic. - Enhanced path lookup functionality to ensure correct resolution of node paths. --- fdt-edit/Cargo.toml | 1 - fdt-edit/src/fdt.rs | 320 ++++++++++++---- fdt-edit/src/lib.rs | 9 +- fdt-edit/src/node/mod.rs | 203 ++++------ fdt-edit/src/node_iter/base.rs | 106 ------ fdt-edit/src/node_iter/iter_ref.rs | 114 ------ fdt-edit/src/node_iter/memory.rs | 41 -- fdt-edit/src/node_iter/mod.rs | 130 ------- fdt-edit/src/view.rs | 591 +++++++++++++++++++++++++++++ fdt-edit/src/visit.rs | 169 +++++++++ fdt-edit/tests/fdt.rs | 165 +++++--- 11 files changed, 1206 insertions(+), 643 deletions(-) delete mode 100644 fdt-edit/src/node_iter/base.rs delete mode 100644 fdt-edit/src/node_iter/iter_ref.rs delete mode 100644 fdt-edit/src/node_iter/memory.rs delete mode 100644 fdt-edit/src/node_iter/mod.rs create mode 100644 fdt-edit/src/view.rs create mode 100644 fdt-edit/src/visit.rs diff --git a/fdt-edit/Cargo.toml b/fdt-edit/Cargo.toml index 8891f76..d1b7d39 100644 --- a/fdt-edit/Cargo.toml +++ b/fdt-edit/Cargo.toml @@ -11,7 +11,6 @@ repository = "https://github.com/drivercraft/fdt-parser" version = "0.2.0" [dependencies] -enum_dispatch = "0.3.13" fdt-raw = {version = "0.2", path = "../fdt-raw"} log = "0.4" diff --git a/fdt-edit/src/fdt.rs b/fdt-edit/src/fdt.rs index f2b8847..02ec1fa 100644 --- a/fdt-edit/src/fdt.rs +++ b/fdt-edit/src/fdt.rs @@ -3,36 +3,35 @@ //! 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, - string::{String, ToString}, - vec::Vec, -}; +use alloc::{collections::BTreeMap, string::String, vec, vec::Vec}; -use crate::node_iter::*; -use crate::{FdtError, Phandle}; +use crate::{FdtError, Node, NodeId, Phandle}; pub use fdt_raw::MemoryReservation; -use crate::Node; - /// 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, + /// 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 { @@ -42,16 +41,202 @@ 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, + root: root_id, + next_id: 1, phandle_cache: BTreeMap::new(), } } + /// 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 + } + + /// Returns the root node ID. + pub fn root_id(&self) -> NodeId { + self.root + } + + /// Returns a reference to the node with the given ID. + pub fn node(&self, id: NodeId) -> Option<&Node> { + self.nodes.get(&id) + } + + /// 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) + } + + /// Returns the total number of nodes in the tree. + pub fn node_count(&self) -> usize { + self.nodes.len() + } + + /// 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, mut node: Node) -> NodeId { + node.parent = Some(parent); + let name = node.name.clone(); + let id = self.alloc_node(node); + + if let Some(parent_node) = self.nodes.get_mut(&parent) { + parent_node.add_child(&name, id); + } + + // 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); + } + + id + } + + /// 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 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 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); + } + } + } + + /// 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, + }; + 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); + } + } + + /// Looks up a node by its full path (e.g. "/soc/uart@10000"), + /// returning its `NodeId`. + /// + /// The root node is matched by "/" or "". + pub fn get_by_path_id(&self, path: &str) -> Option { + let normalized = path.trim_start_matches('/'); + if normalized.is_empty() { + return Some(self.root); + } + + let mut current = self.root; + for part in normalized.split('/') { + let node = self.nodes.get(¤t)?; + current = node.get_child(part)?; + } + Some(current) + } + + /// 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() + } + + /// 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; + loop { + let node = match self.nodes.get(&cur) { + Some(n) => n, + None => break, + }; + if cur == self.root { + break; + } + parts.push(&node.name); + match node.parent { + Some(p) => cur = p, + None => break, + } + } + parts.reverse(); + if parts.is_empty() { + return String::from("/"); + } + format!("/{}", parts.join("/")) + } + + /// 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 + } + + let parts: Vec<&str> = normalized.split('/').collect(); + let child_name = *parts.last()?; + + // 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)?; + } + + self.remove_node(parent_id, child_name) + } + + /// 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], + } + } + /// Parses an FDT from raw byte data. pub fn from_bytes(data: &[u8]) -> Result { let raw_fdt = fdt_raw::Fdt::from_bytes(data)?; @@ -76,74 +261,77 @@ impl Fdt { let mut fdt = Fdt { boot_cpuid_phys: header.boot_cpuid_phys, memory_reservations: raw_fdt.memory_reservations().collect(), - root: Node::new(""), + nodes: BTreeMap::new(), + root: 0, + next_id: 0, phandle_cache: BTreeMap::new(), }; - // Build node tree using a stack to track parent nodes - let mut node_stack: Vec = Vec::new(); + // 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(); for raw_node in raw_fdt.all_nodes() { let level = raw_node.level(); let node = Node::from(&raw_node); - if let Some(phandle) = node.phandle() { - fdt.phandle_cache - .insert(phandle, raw_node.path().to_string()); + let node_name = node.name.clone(); + + // Allocate the node in the arena + let node_id = fdt.alloc_node(node); + + // Update phandle cache + if let Some(phandle) = fdt.nodes.get(&node_id).and_then(|n| n.phandle()) { + fdt.phandle_cache.insert(phandle, node_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); + // 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 { - // This is the root node - fdt.root = child; + break; } } - node_stack.push(node); - } - - // Pop all remaining nodes - while let Some(child) = node_stack.pop() { - if let Some(parent) = node_stack.last_mut() { - parent.add_child(child); + if let Some(&(parent_id, _)) = id_stack.last() { + // Set parent link + if let Some(n) = fdt.nodes.get_mut(&node_id) { + n.parent = Some(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 { // This is the root node - fdt.root = child; + fdt.root = node_id; } - } - Ok(fdt) - } - pub fn all_raw_nodes(&self) -> impl Iterator { - self.all_nodes().map(|node_ref| node_ref._as_raw()) - } + id_stack.push((node_id, level)); + } - pub fn all_raw_nodes_mut(&mut self) -> impl Iterator { - self.all_nodes_mut() - .map(|mut node_ref| node_ref._as_raw_mut()) + Ok(fdt) } +} - pub fn all_nodes(&self) -> impl Iterator> + '_ { - NodeRefIter::new(&self.root) - } +/// Depth-first iterator over all node IDs in the tree. +pub struct NodeDfsIter<'a> { + fdt: &'a Fdt, + stack: Vec, +} - pub fn all_nodes_mut(&mut self) -> impl Iterator> + '_ { - NodeRefIterMut::new(&self.root) - } +impl<'a> Iterator for NodeDfsIter<'a> { + type Item = NodeId; - fn get_by_path_exact(&self, path: &str) -> Option> { - self.all_nodes().find(|node| node.path().as_str() == path) + 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); + } + } + Some(id) } - - // /// Resolves an alias to its full path. - // /// - // /// 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_exact("/aliases")?; - - // } } diff --git a/fdt-edit/src/lib.rs b/fdt-edit/src/lib.rs index 6da3ba0..9289a53 100644 --- a/fdt-edit/src/lib.rs +++ b/fdt-edit/src/lib.rs @@ -5,12 +5,17 @@ extern crate alloc; mod fdt; mod node; -mod node_iter; mod prop; +mod view; +mod visit; 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 fdt::*; pub use node::*; -pub use node_iter::*; pub use prop::*; +pub use view::*; +pub use visit::*; diff --git a/fdt-edit/src/node/mod.rs b/fdt-edit/src/node/mod.rs index afb0c7d..fd0a28c 100644 --- a/fdt-edit/src/node/mod.rs +++ b/fdt-edit/src/node/mod.rs @@ -3,24 +3,27 @@ use core::fmt::{Debug, Display}; use alloc::{collections::btree_map::BTreeMap, string::String, vec::Vec}; use fdt_raw::{Phandle, Status}; -use crate::{Property, RangesEntry}; +use crate::{NodeId, Property, RangesEntry}; /// 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, + /// Parent node ID (None for root) + pub parent: Option, /// Property list (maintains original order) properties: Vec, /// Property name to index mapping (for fast lookup) prop_cache: BTreeMap, - /// Child nodes - children: Vec, - /// Child name to index mapping (for fast lookup) + /// 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, } @@ -29,6 +32,7 @@ impl Node { pub fn new(name: &str) -> Self { Self { name: name.into(), + parent: None, properties: Vec::new(), prop_cache: BTreeMap::new(), children: Vec::new(), @@ -46,23 +50,18 @@ impl Node { &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) -> &mut [Node] { - &mut self.children - } - - /// 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. @@ -75,53 +74,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. @@ -162,7 +153,6 @@ impl Node { /// 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) { - // Rebuild indices (need to update subsequent indices after removal) let prop = self.properties.remove(idx); self.rebuild_prop_cache(); Some(prop) @@ -216,11 +206,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)) = ( @@ -238,14 +225,6 @@ 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> { let prop = self.get_property("compatible")?; @@ -266,86 +245,32 @@ 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()) } } 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); diff --git a/fdt-edit/src/node_iter/base.rs b/fdt-edit/src/node_iter/base.rs deleted file mode 100644 index 4ed5c89..0000000 --- a/fdt-edit/src/node_iter/base.rs +++ /dev/null @@ -1,106 +0,0 @@ -use alloc::{string::String, vec::Vec}; -use core::fmt::Display; - -use crate::Node; - -pub struct NodeGeneric { - pub(crate) meta: NodeIterMeta, - pub(crate) node: *mut Node, -} - -impl NodeGeneric { - pub(crate) fn new(node: *mut Node, meta: NodeIterMeta) -> Self { - Self { node, meta } - } - - pub fn as_node<'a>(&self) -> &'a Node { - unsafe { &*self.node } - } - - pub fn as_node_mut<'a>(&mut self) -> &'a mut Node { - unsafe { &mut *self.node } - } - - pub fn name(&self) -> &str { - self.as_node().name() - } - - pub fn properties(&self) -> &[crate::Property] { - self.as_node().properties() - } - - pub fn get_property(&self, name: &str) -> Option<&crate::Property> { - self.as_node().get_property(name) - } - - pub fn parent_path(&self) -> &[String] { - &self.meta.parent_path - } - - pub fn parent_path_string(&self) -> String { - if self.meta.parent_path.is_empty() { - return String::from("/"); - } - format!("/{}", self.meta.parent_path.join("/")) - } - - pub fn path(&self) -> String { - let parent = self.parent_path_string(); - if parent == "/" { - return format!("/{}", self.name()); - } - format!("{}/{}", parent, self.name()) - } -} - -unsafe impl Send for NodeGeneric {} - -#[derive(Clone)] -pub(crate) struct NodeIterMeta { - pub(crate) level: usize, - pub(crate) parent_path: Vec, -} - -impl NodeIterMeta { - fn write_indent(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - for _ in 0..self.level { - write!(f, " ")?; // Indent based on level - } - Ok(()) - } -} - -impl Display for NodeGeneric { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - self.meta.write_indent(f)?; - - writeln!(f, "{}", self.path())?; - for prop in self.as_node().properties() { - self.meta.write_indent(f)?; - write!(f, " {} = ", prop.name())?; - - if prop.name() == "compatible" { - write!(f, "[")?; - for (i, str) in prop.as_str_iter().enumerate() { - write!(f, "\"{}\"", str)?; - if i != prop.as_str_iter().count() - 1 { - write!(f, ", ")?; - } - } - writeln!(f, "]")?; - continue; - } - - if let Some(str) = prop.as_str() { - writeln!(f, "\"{}\";", str)?; - } else { - for cell in prop.get_u32_iter() { - write!(f, "{:#x} ", cell)?; - } - writeln!(f, ";")?; - } - } - - Ok(()) - } -} diff --git a/fdt-edit/src/node_iter/iter_ref.rs b/fdt-edit/src/node_iter/iter_ref.rs deleted file mode 100644 index 47e71c2..0000000 --- a/fdt-edit/src/node_iter/iter_ref.rs +++ /dev/null @@ -1,114 +0,0 @@ -use alloc::{vec, vec::Vec}; - -use crate::{NodeIterMeta, NodeRef, NodeRefMut}; - -use super::Node; - -struct StackEntry { - node: *mut Node, - child_index: usize, - meta: NodeIterMeta, -} - -unsafe impl Send for StackEntry {} - -pub(crate) struct NodeIter { - stack: Vec, -} - -impl NodeIter { - pub fn new(root: &Node) -> Self { - Self { - stack: vec![StackEntry { - node: root as *const Node as usize as *mut Node, - child_index: 0, - meta: NodeIterMeta { - level: 0, - parent_path: vec![], - }, - }], - } - } - - fn next_raw(&mut self) -> Option<(*mut Node, NodeIterMeta)> { - while let Some(top) = self.stack.last_mut() { - unsafe { - let node = &*top.node; - - if top.child_index < node.children().len() { - let child = &node.children()[top.child_index]; - let child_ptr = child as *const Node as usize as *mut Node; - top.child_index += 1; - - // Build the parent path for the child node - let mut parent_path = top.meta.parent_path.clone(); - parent_path.push(node.name().into()); - - let child_meta = NodeIterMeta { - level: top.meta.level + 1, - parent_path, - }; - - self.stack.push(StackEntry { - node: child_ptr, - child_index: 0, - meta: child_meta.clone(), - }); - - return Some((child_ptr, child_meta)); - } else { - self.stack.pop(); - } - } - } - None - } -} - -pub(crate) struct NodeRefIter<'a> { - iter: NodeIter, - _marker: core::marker::PhantomData<&'a ()>, -} - -impl<'a> NodeRefIter<'a> { - pub fn new(root: &Node) -> Self { - Self { - iter: NodeIter::new(root), - _marker: core::marker::PhantomData, - } - } -} - -impl<'a> Iterator for NodeRefIter<'a> { - type Item = NodeRef<'a>; - - fn next(&mut self) -> Option { - self.iter - .next_raw() - .map(|(node_ptr, meta)| NodeRef::new(node_ptr, meta)) - } -} - -pub(crate) struct NodeRefIterMut<'a> { - iter: NodeIter, - _marker: core::marker::PhantomData<&'a ()>, -} - -impl<'a> NodeRefIterMut<'a> { - pub fn new(root: &Node) -> Self { - Self { - iter: NodeIter::new(root), - _marker: core::marker::PhantomData, - } - } -} - -impl<'a> Iterator for NodeRefIterMut<'a> { - type Item = NodeRefMut<'a>; - - fn next(&mut self) -> Option { - self.iter - .next_raw() - .map(|(node_ptr, meta)| NodeRefMut::new(node_ptr, meta)) - } -} diff --git a/fdt-edit/src/node_iter/memory.rs b/fdt-edit/src/node_iter/memory.rs deleted file mode 100644 index 25b6389..0000000 --- a/fdt-edit/src/node_iter/memory.rs +++ /dev/null @@ -1,41 +0,0 @@ -use alloc::vec::Vec; -use fdt_raw::MemoryRegion; - -use crate::{NodeGeneric, NodeOp}; - -pub struct NodeMemory { - pub(crate) base: NodeGeneric, -} - -impl NodeMemory { - pub(crate) fn try_new(node: NodeGeneric) -> Result { - if is_memory_node(&node) { - Ok(Self { base: node }) - } else { - Err(node) - } - } -} - -impl NodeOp for NodeMemory { - fn as_generic(&self) -> &NodeGeneric { - &self.base - } - - fn as_generic_mut(&mut self) -> &mut NodeGeneric { - &mut self.base - } -} - -/// Check if node is a memory node -fn is_memory_node(node: &NodeGeneric) -> bool { - // Check if device_type property is "memory" - if let Some(device_type) = node.as_node().device_type() - && device_type == "memory" - { - return true; - } - - // Or node name starts with "memory" - node.as_node().name().starts_with("memory") -} diff --git a/fdt-edit/src/node_iter/mod.rs b/fdt-edit/src/node_iter/mod.rs deleted file mode 100644 index 3a285b7..0000000 --- a/fdt-edit/src/node_iter/mod.rs +++ /dev/null @@ -1,130 +0,0 @@ -use core::{ - fmt::Display, - ops::{Deref, DerefMut}, -}; - -use crate::Node; - -mod base; -mod iter_ref; -mod memory; - -use alloc::string::String; -pub use base::*; -use enum_dispatch::enum_dispatch; -pub(crate) use iter_ref::*; -pub use memory::*; - -#[enum_dispatch(NodeOp)] -pub enum NodeKind { - Generic(NodeGeneric), - Memory(NodeMemory), -} - -#[enum_dispatch] -pub(crate) trait NodeOp { - fn as_generic(&self) -> &NodeGeneric; - fn as_generic_mut(&mut self) -> &mut NodeGeneric; - fn _display(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - self.as_generic().fmt(f) - } -} - -impl NodeKind { - pub(crate) fn new(node: *mut Node, meta: NodeIterMeta) -> Self { - let generic = NodeGeneric::new(node, meta.clone()); - - let generic = match NodeMemory::try_new(generic) { - Ok(mem) => return NodeKind::Memory(mem), - Err(generic) => generic, - }; - - NodeKind::Generic(generic) - } - - pub(crate) fn _as_raw<'a>(&self) -> &'a Node { - self.as_generic().as_node() - } - - pub(crate) fn _as_raw_mut<'a>(&mut self) -> &'a mut Node { - self.as_generic_mut().as_node_mut() - } - - pub fn path(&self) -> String { - self.as_generic().path() - } -} - -impl NodeOp for NodeGeneric { - fn as_generic(&self) -> &NodeGeneric { - self - } - - fn as_generic_mut(&mut self) -> &mut NodeGeneric { - self - } -} - -pub struct NodeRef<'a> { - inner: NodeKind, - _marker: core::marker::PhantomData<&'a ()>, -} - -impl<'a> NodeRef<'a> { - pub(crate) fn new(node: *mut Node, meta: NodeIterMeta) -> Self { - Self { - inner: NodeKind::new(node, meta), - _marker: core::marker::PhantomData, - } - } - - pub fn as_raw(&self) -> &Node { - self.inner._as_raw() - } -} - -impl<'a> Deref for NodeRef<'a> { - type Target = NodeKind; - fn deref(&self) -> &Self::Target { - &self.inner - } -} - -impl Display for NodeRef<'_> { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - self.inner._display(f) - } -} - -pub struct NodeRefMut<'a> { - inner: NodeKind, - _marker: core::marker::PhantomData<&'a ()>, -} - -impl<'a> NodeRefMut<'a> { - pub(crate) fn new(node: *mut Node, meta: NodeIterMeta) -> Self { - Self { - inner: NodeKind::new(node, meta), - _marker: core::marker::PhantomData, - } - } -} - -impl<'a> Deref for NodeRefMut<'a> { - type Target = NodeKind; - fn deref(&self) -> &Self::Target { - &self.inner - } -} - -impl<'a> DerefMut for NodeRefMut<'a> { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.inner - } -} - -impl Display for NodeRefMut<'_> { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - self.inner._display(f) - } -} diff --git a/fdt-edit/src/view.rs b/fdt-edit/src/view.rs new file mode 100644 index 0000000..c41ed58 --- /dev/null +++ b/fdt-edit/src/view.rs @@ -0,0 +1,591 @@ +//! 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`. + +use core::ops::Deref; + +use alloc::{string::String, vec::Vec}; +use fdt_raw::{MemoryRegion, Phandle, Status}; + +use crate::{Fdt, Node, NodeId, Property, RangesEntry}; + +// --------------------------------------------------------------------------- +// 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 struct NodeView<'a> { + fdt: &'a Fdt, + id: NodeId, +} + +impl<'a> NodeView<'a> { + /// Creates a new `NodeView`. + pub(crate) fn new(fdt: &'a Fdt, id: NodeId) -> Self { + Self { fdt, id } + } + + /// 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") + } + + /// Returns the `Fdt` arena this view belongs to. + pub fn fdt(&self) -> &'a Fdt { + self.fdt + } + + // -- delegation to Node -- + + /// Node name (without path). + pub fn name(&self) -> &'a str { + self.as_node().name() + } + + /// Property list. + pub fn properties(&self) -> &'a [Property] { + self.as_node().properties() + } + + /// Get a property by name. + pub fn get_property(&self, name: &str) -> Option<&'a Property> { + self.as_node().get_property(name) + } + + /// Child node IDs. + pub fn child_ids(&self) -> &'a [NodeId] { + self.as_node().children() + } + + /// Iterator over child `NodeView`s. + pub fn children(&self) -> impl Iterator> + 'a { + let fdt = self.fdt; + self.as_node() + .children() + .iter() + .map(move |&child_id| NodeView::new(fdt, child_id)) + } + + /// Get a child view by name. + pub fn get_child(&self, name: &str) -> Option> { + let child_id = self.as_node().get_child(name)?; + Some(NodeView::new(self.fdt, child_id)) + } + + /// Parent view, if any. + pub fn parent(&self) -> Option> { + self.as_node() + .parent + .map(|pid| NodeView::new(self.fdt, pid)) + } + + /// Full path string (e.g. "/soc/uart@10000"). + pub fn path(&self) -> String { + self.fdt.path_of(self.id) + } + + // -- DT shortcut methods (delegated to Node) -- + + pub fn address_cells(&self) -> Option { + self.as_node().address_cells() + } + pub fn size_cells(&self) -> Option { + self.as_node().size_cells() + } + pub fn phandle(&self) -> Option { + self.as_node().phandle() + } + pub fn interrupt_parent(&self) -> Option { + self.as_node().interrupt_parent() + } + pub fn status(&self) -> Option { + self.as_node().status() + } + pub fn compatible(&self) -> Option> { + self.as_node().compatible() + } + pub fn compatibles(&self) -> impl Iterator { + self.as_node().compatibles() + } + pub fn device_type(&self) -> Option<&'a str> { + self.as_node().device_type() + } + pub fn ranges(&self, parent_address_cells: u32) -> Option> { + self.as_node().ranges(parent_address_cells) + } + + // -- classification -- + + /// Classify this node into a typed view. + pub fn classify(&self) -> NodeType<'a> { + let node = self.as_node(); + if node.is_interrupt_controller() { + NodeType::InterruptController(IntcNodeView { inner: *self }) + } else if node.is_memory() { + NodeType::Memory(MemoryNodeView { inner: *self }) + } else { + NodeType::Generic(GenericNodeView { inner: *self }) + } + } + + /// Try to view as a memory node. + pub fn as_memory(&self) -> Option> { + if self.as_node().is_memory() { + Some(MemoryNodeView { inner: *self }) + } else { + None + } + } + + /// Try to view as an interrupt controller node. + pub fn as_intc(&self) -> Option> { + if self.as_node().is_interrupt_controller() { + Some(IntcNodeView { inner: *self }) + } else { + None + } + } +} + +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.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(()) + } +} + +// --------------------------------------------------------------------------- +// NodeViewMut — mutable view +// --------------------------------------------------------------------------- + +/// A mutable view of a node in the device tree. +/// +/// Holds an exclusive `&mut Fdt` reference and a `NodeId`. +pub struct NodeViewMut<'a> { + fdt: &'a mut Fdt, + id: NodeId, +} + +impl<'a> NodeViewMut<'a> { + /// Creates a new `NodeViewMut`. + pub(crate) fn new(fdt: &'a mut Fdt, id: NodeId) -> Self { + Self { fdt, id } + } + + /// Returns the underlying `NodeId`. + pub fn id(&self) -> NodeId { + self.id + } + + /// Immutable access to the underlying `Node`. + pub fn as_node(&self) -> &Node { + self.fdt + .node(self.id) + .expect("NodeViewMut references a valid node") + } + + /// Mutable access to the underlying `Node`. + pub fn as_node_mut(&mut self) -> &mut Node { + self.fdt + .node_mut(self.id) + .expect("NodeViewMut references a valid node") + } + + /// Node name. + pub fn name(&self) -> &str { + self.as_node().name() + } + + /// Full path string. + pub fn path(&self) -> String { + self.fdt.path_of(self.id) + } + + // -- property mutation -- + + /// Set a property (add or update). + pub fn set_property(&mut self, prop: Property) { + self.as_node_mut().set_property(prop); + } + + /// Remove a property by name. + pub fn remove_property(&mut self, name: &str) -> Option { + self.as_node_mut().remove_property(name) + } + + /// Get a property by name. + pub fn get_property(&self, name: &str) -> Option<&Property> { + self.as_node().get_property(name) + } + + // -- child mutation -- + + /// Add a child node, returning the new child's ID. + pub fn add_child(&mut self, node: Node) -> NodeId { + self.fdt.add_node(self.id, node) + } + + /// Remove a child node by name. + pub fn remove_child(&mut self, name: &str) -> Option { + self.fdt.remove_node(self.id, name) + } + + // -- classification (consumes self because &mut Fdt cannot be copied) -- + + /// Classify this node into a typed mutable view. + pub fn classify(self) -> NodeTypeMut<'a> { + let is_intc = self + .fdt + .node(self.id) + .map(|n| n.is_interrupt_controller()) + .unwrap_or(false); + let is_mem = self + .fdt + .node(self.id) + .map(|n| n.is_memory()) + .unwrap_or(false); + + if is_intc { + NodeTypeMut::InterruptController(IntcNodeViewMut { inner: self }) + } else if is_mem { + NodeTypeMut::Memory(MemoryNodeViewMut { inner: self }) + } else { + NodeTypeMut::Generic(GenericNodeViewMut { inner: self }) + } + } +} + +// --------------------------------------------------------------------------- +// NodeType — classified immutable view enum +// --------------------------------------------------------------------------- + +/// Typed node view enum, allowing pattern matching by node kind. +pub enum NodeType<'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 generic node (no special classification). + Generic(GenericNodeView<'a>), +} + +impl<'a> NodeType<'a> { + /// Returns the inner `NodeView` regardless of variant. + pub fn as_view(&self) -> &NodeView<'a> { + match self { + NodeType::Memory(v) => &v.inner, + NodeType::InterruptController(v) => &v.inner, + NodeType::Generic(v) => &v.inner, + } + } +} + +// --------------------------------------------------------------------------- +// NodeTypeMut — classified mutable view enum +// --------------------------------------------------------------------------- + +/// Typed mutable node view enum. +pub enum NodeTypeMut<'a> { + Memory(MemoryNodeViewMut<'a>), + InterruptController(IntcNodeViewMut<'a>), + Generic(GenericNodeViewMut<'a>), +} + +impl<'a> NodeTypeMut<'a> { + /// Returns the inner node ID regardless of variant. + pub fn id(&self) -> NodeId { + match self { + NodeTypeMut::Memory(v) => v.inner.id, + NodeTypeMut::InterruptController(v) => v.inner.id, + NodeTypeMut::Generic(v) => v.inner.id, + } + } +} + +// --------------------------------------------------------------------------- +// MemoryNodeView +// --------------------------------------------------------------------------- + +/// Specialized view for memory nodes. +/// +/// Provides methods for parsing `reg` into memory regions. +#[derive(Clone, Copy)] +pub struct MemoryNodeView<'a> { + inner: NodeView<'a>, +} + +impl<'a> Deref for MemoryNodeView<'a> { + type Target = NodeView<'a>; + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl<'a> MemoryNodeView<'a> { + /// Iterates over memory regions parsed from the `reg` property. + /// + /// Uses the parent node's `#address-cells` and `#size-cells` to decode. + pub fn regions(&self) -> Vec { + let node = self.inner.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(); + + let mut reader = reg.as_reader(); + let mut regions = Vec::new(); + + while let (Some(address), Some(size)) = + (reader.read_cells(addr_cells), reader.read_cells(size_cells)) + { + regions.push(MemoryRegion { address, size }); + } + + regions + } + + /// Total size across all memory regions. + pub fn total_size(&self) -> u64 { + self.regions().iter().map(|r| r.size).sum() + } + + /// Returns (address_cells, size_cells) from the parent node (defaults: 2, 1). + fn parent_cells(&self) -> (usize, usize) { + if let Some(parent) = self.inner.parent() { + let ac = parent.address_cells().unwrap_or(2) as usize; + let sc = parent.size_cells().unwrap_or(1) as usize; + (ac, sc) + } else { + (2, 1) + } + } +} + +// --------------------------------------------------------------------------- +// IntcNodeView +// --------------------------------------------------------------------------- + +/// Specialized view for interrupt controller nodes. +#[derive(Clone, Copy)] +pub struct IntcNodeView<'a> { + inner: NodeView<'a>, +} + +impl<'a> Deref for IntcNodeView<'a> { + type Target = NodeView<'a>; + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl<'a> IntcNodeView<'a> { + /// Returns the `#interrupt-cells` property value. + pub fn interrupt_cells(&self) -> Option { + self.inner.as_node().interrupt_cells() + } + + /// This is always `true` for `IntcNodeView` (type-level guarantee). + pub fn is_interrupt_controller(&self) -> bool { + true + } +} + +// --------------------------------------------------------------------------- +// GenericNodeView +// --------------------------------------------------------------------------- + +/// A generic node view with no extra specialization. +#[derive(Clone, Copy)] +pub struct GenericNodeView<'a> { + inner: NodeView<'a>, +} + +impl<'a> Deref for GenericNodeView<'a> { + type Target = NodeView<'a>; + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +// --------------------------------------------------------------------------- +// Mutable specialized views +// --------------------------------------------------------------------------- + +/// Mutable view for memory nodes. +pub struct MemoryNodeViewMut<'a> { + inner: NodeViewMut<'a>, +} + +impl<'a> MemoryNodeViewMut<'a> { + /// Access underlying mutable view. + pub fn as_view_mut(&mut self) -> &mut NodeViewMut<'a> { + &mut self.inner + } + + /// Access underlying immutable node. + pub fn as_node(&self) -> &Node { + self.inner.as_node() + } + + pub fn id(&self) -> NodeId { + self.inner.id + } +} + +/// Mutable view for interrupt controller nodes. +pub struct IntcNodeViewMut<'a> { + inner: NodeViewMut<'a>, +} + +impl<'a> IntcNodeViewMut<'a> { + pub fn as_view_mut(&mut self) -> &mut NodeViewMut<'a> { + &mut self.inner + } + + pub fn as_node(&self) -> &Node { + self.inner.as_node() + } + + pub fn id(&self) -> NodeId { + self.inner.id + } + + pub fn interrupt_cells(&self) -> Option { + self.inner.as_node().interrupt_cells() + } +} + +/// Mutable view for generic nodes. +pub struct GenericNodeViewMut<'a> { + inner: NodeViewMut<'a>, +} + +impl<'a> GenericNodeViewMut<'a> { + pub fn as_view_mut(&mut self) -> &mut NodeViewMut<'a> { + &mut self.inner + } + + pub fn as_node(&self) -> &Node { + self.inner.as_node() + } + + pub fn id(&self) -> NodeId { + self.inner.id + } +} + +// --------------------------------------------------------------------------- +// Fdt convenience methods returning views +// --------------------------------------------------------------------------- + +impl Fdt { + /// Returns a `NodeView` for the root node. + pub fn root(&self) -> NodeView<'_> { + NodeView::new(self, self.root_id()) + } + + /// Returns a `NodeView` for the given node ID, if it exists. + pub fn view(&self, id: NodeId) -> Option> { + if self.node(id).is_some() { + Some(NodeView::new(self, id)) + } else { + None + } + } + + /// Returns a `NodeViewMut` for the given node ID, if it exists. + pub fn view_mut(&mut self, id: NodeId) -> Option> { + if self.node(id).is_some() { + Some(NodeViewMut::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> { + if self.node(id).is_some() { + Some(NodeViewMut::new(self, id).classify()) + } else { + None + } + } + + /// 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()) + } + + /// 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(NodeViewMut::new(self, id).classify()) + } + + /// 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()) + } + + /// 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(NodeViewMut::new(self, id).classify()) + } + + /// Returns a depth-first iterator over `NodeView`s. + pub fn iter_raw_nodes(&self) -> impl Iterator> { + self.iter_node_ids().map(move |id| NodeView::new(self, id)) + } + + /// Returns a depth-first iterator over classified `NodeType`s. + pub fn all_nodes(&self) -> impl Iterator> { + self.iter_raw_nodes().map(|v| v.classify()) + } +} diff --git a/fdt-edit/src/visit.rs b/fdt-edit/src/visit.rs new file mode 100644 index 0000000..5da6599 --- /dev/null +++ b/fdt-edit/src/visit.rs @@ -0,0 +1,169 @@ +//! Visitor traits for traversing device tree nodes. +//! +//! Inspired by `toml_edit::visit` / `visit_mut`. The `Visit` trait provides +//! immutable traversal with `NodeView`, while `VisitMut` provides mutable +//! traversal using `(&mut Fdt, NodeId)` pairs to avoid aliasing issues. + +use alloc::vec::Vec; + +use crate::{Fdt, NodeId, NodeView, Property}; + +// --------------------------------------------------------------------------- +// Visit — immutable traversal +// --------------------------------------------------------------------------- + +/// Immutable visitor trait for device tree traversal. +/// +/// Override individual methods to customize behavior. Default implementations +/// recurse through the tree structure. +/// +/// # Example +/// +/// ```ignore +/// struct NodeCounter { count: usize } +/// +/// impl<'a> Visit<'a> for NodeCounter { +/// fn visit_node(&mut self, node: NodeView<'a>) { +/// self.count += 1; +/// visit_node(self, node); // recurse children +/// } +/// } +/// ``` +pub trait Visit<'a> { + /// Called for every node. Default: classifies and dispatches to typed methods. + fn visit_node(&mut self, node: NodeView<'a>) { + visit_node(self, node); + } + + /// Called for memory nodes. Default: visits properties and recurses children. + fn visit_memory_node(&mut self, node: NodeView<'a>) { + visit_node_children(self, node); + } + + /// Called for interrupt controller nodes. Default: visits properties and recurses children. + fn visit_intc_node(&mut self, node: NodeView<'a>) { + visit_node_children(self, node); + } + + /// Called for generic nodes. Default: visits properties and recurses children. + fn visit_generic_node(&mut self, node: NodeView<'a>) { + visit_node_children(self, node); + } + + /// Called for each property. Default: no-op. + fn visit_property(&mut self, _node: NodeView<'a>, _prop: &'a Property) {} +} + +/// Default `visit_node` implementation: classifies and dispatches. +pub fn visit_node<'a, V: Visit<'a> + ?Sized>(v: &mut V, node: NodeView<'a>) { + let n = node.as_node(); + if n.is_interrupt_controller() { + v.visit_intc_node(node); + } else if n.is_memory() { + v.visit_memory_node(node); + } else { + v.visit_generic_node(node); + } +} + +/// Visits properties then recurses into children (used by default typed methods). +pub fn visit_node_children<'a, V: Visit<'a> + ?Sized>(v: &mut V, node: NodeView<'a>) { + for prop in node.properties() { + v.visit_property(node, prop); + } + for child in node.children() { + v.visit_node(child); + } +} + +// --------------------------------------------------------------------------- +// VisitMut — mutable traversal +// --------------------------------------------------------------------------- + +/// Mutable visitor trait for device tree traversal. +/// +/// Takes `(&mut Fdt, NodeId)` pairs instead of view types to avoid +/// `&mut` aliasing. The default implementations clone children lists +/// before recursing to avoid borrow conflicts. +pub trait VisitMut { + /// Called for every node. Default: classifies and dispatches. + fn visit_node_mut(&mut self, fdt: &mut Fdt, id: NodeId) { + visit_node_mut(self, fdt, id); + } + + /// Called for memory nodes. + fn visit_memory_node_mut(&mut self, fdt: &mut Fdt, id: NodeId) { + visit_node_children_mut(self, fdt, id); + } + + /// Called for interrupt controller nodes. + fn visit_intc_node_mut(&mut self, fdt: &mut Fdt, id: NodeId) { + visit_node_children_mut(self, fdt, id); + } + + /// Called for generic nodes. + fn visit_generic_node_mut(&mut self, fdt: &mut Fdt, id: NodeId) { + visit_node_children_mut(self, fdt, id); + } + + /// Called for each property. + fn visit_property_mut(&mut self, _fdt: &mut Fdt, _node_id: NodeId, _prop_name: &str) {} +} + +/// Default `visit_node_mut`: classifies and dispatches. +pub fn visit_node_mut(v: &mut V, fdt: &mut Fdt, id: NodeId) { + let (is_intc, is_mem) = { + match fdt.node(id) { + Some(n) => (n.is_interrupt_controller(), n.is_memory()), + None => return, + } + }; + + if is_intc { + v.visit_intc_node_mut(fdt, id); + } else if is_mem { + v.visit_memory_node_mut(fdt, id); + } else { + v.visit_generic_node_mut(fdt, id); + } +} + +/// Visits properties then recurses children (mutable default). +pub fn visit_node_children_mut(v: &mut V, fdt: &mut Fdt, id: NodeId) { + // Clone property names to avoid borrow conflict + let prop_names: Vec<_> = match fdt.node(id) { + Some(n) => n.properties().iter().map(|p| p.name.clone()).collect(), + None => return, + }; + + for name in &prop_names { + v.visit_property_mut(fdt, id, name); + } + + // Clone children list to avoid borrow conflict + let children: Vec = match fdt.node(id) { + Some(n) => n.children().to_vec(), + None => return, + }; + + for child_id in children { + v.visit_node_mut(fdt, child_id); + } +} + +// --------------------------------------------------------------------------- +// Convenience entry points on Fdt +// --------------------------------------------------------------------------- + +impl Fdt { + /// Run an immutable visitor starting from the root. + pub fn visit<'a, V: Visit<'a>>(&'a self, visitor: &mut V) { + visitor.visit_node(self.root()); + } + + /// Run a mutable visitor starting from the root. + pub fn visit_mut(&mut self, visitor: &mut V) { + let root = self.root_id(); + visitor.visit_node_mut(self, root); + } +} diff --git a/fdt-edit/tests/fdt.rs b/fdt-edit/tests/fdt.rs index b580033..3630394 100644 --- a/fdt-edit/tests/fdt.rs +++ b/fdt-edit/tests/fdt.rs @@ -1,66 +1,143 @@ -use std::sync::Once; - use dtb_file::*; 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_all_raw_node() { - // Test memory node detection using phytium DTB +fn test_iter_nodes() { let raw_data = fdt_phytium(); - let mut fdt = Fdt::from_bytes(&raw_data).unwrap(); - for node in fdt.all_raw_nodes_mut() { - println!("{:?}", node); + 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_all_node() { - // Test memory node detection using phytium DTB +fn test_node_classify() { let raw_data = fdt_phytium(); let fdt = Fdt::from_bytes(&raw_data).unwrap(); - for node in fdt.all_nodes() { - println!("{}", node); + + let mut memory_count = 0; + let mut intc_count = 0; + let mut generic_count = 0; + + for view in fdt.all_nodes() { + match view.classify() { + 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_reg() { - init_logging(); - let raw = fdt_rpi_4b(); - let fdt = Fdt::from_bytes(&raw).unwrap(); +fn test_visit_trait() { + struct Counter { + nodes: usize, + memory: usize, + intc: usize, + } - let node = fdt.get_by_path("/soc/serial@7e215040").unwrap(); + impl<'a> Visit<'a> for Counter { + fn visit_memory_node(&mut self, node: NodeView<'a>) { + self.memory += 1; + self.nodes += 1; + visit_node_children(self, node); + } - let reg = node.regs().unwrap()[0]; + fn visit_intc_node(&mut self, node: NodeView<'a>) { + self.intc += 1; + self.nodes += 1; + visit_node_children(self, node); + } - info!("reg: {:#x?}", reg); + fn visit_generic_node(&mut self, node: NodeView<'a>) { + self.nodes += 1; + visit_node_children(self, node); + } + } - assert_eq!( - reg.address, 0xfe215040, - "want 0xfe215040, got {:#x}", - reg.address - ); + let raw_data = fdt_phytium(); + let fdt = Fdt::from_bytes(&raw_data).unwrap(); - 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() + let mut counter = Counter { + nodes: 0, + memory: 0, + intc: 0, + }; + fdt.visit(&mut counter); + + println!( + "Visit: total={} memory={} intc={}", + counter.nodes, counter.memory, counter.intc ); + assert_eq!(counter.nodes, fdt.node_count()); +} + +#[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.as_view().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.as_view().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); + } } From e57f345e95f225d9fffe4abe91ebe8a3b563ca92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Tue, 10 Feb 2026 20:28:09 +0800 Subject: [PATCH 18/39] feat: Add methods to NodeType for accessing node properties and enhance test for node classification --- fdt-edit/src/view.rs | 26 ++++++++++++++++++++++++++ fdt-edit/tests/fdt.rs | 2 +- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/fdt-edit/src/view.rs b/fdt-edit/src/view.rs index c41ed58..fd1826c 100644 --- a/fdt-edit/src/view.rs +++ b/fdt-edit/src/view.rs @@ -314,6 +314,32 @@ impl<'a> NodeType<'a> { NodeType::Generic(v) => &v.inner, } } + + /// 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() + } + + /// 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() + } +} + +impl core::fmt::Display for NodeType<'_> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{}", self.as_view()) + } } // --------------------------------------------------------------------------- diff --git a/fdt-edit/tests/fdt.rs b/fdt-edit/tests/fdt.rs index 3630394..33ad613 100644 --- a/fdt-edit/tests/fdt.rs +++ b/fdt-edit/tests/fdt.rs @@ -24,7 +24,7 @@ fn test_node_classify() { let mut generic_count = 0; for view in fdt.all_nodes() { - match view.classify() { + match view { NodeType::Memory(mem) => { memory_count += 1; let regions = mem.regions(); From aae30850ea904383d40a39a633dc64d0357d726b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Tue, 10 Feb 2026 20:38:02 +0800 Subject: [PATCH 19/39] feat: Add enum_dispatch dependency and implement NodeView for safe access to device tree nodes --- fdt-edit/Cargo.toml | 1 + fdt-edit/src/lib.rs | 3 +-- fdt-edit/src/node/mod.rs | 2 ++ fdt-edit/src/{ => node}/view.rs | 48 ++++++++++++++++++++------------- 4 files changed, 33 insertions(+), 21 deletions(-) rename fdt-edit/src/{ => node}/view.rs (96%) diff --git a/fdt-edit/Cargo.toml b/fdt-edit/Cargo.toml index d1b7d39..dbb0be9 100644 --- a/fdt-edit/Cargo.toml +++ b/fdt-edit/Cargo.toml @@ -13,6 +13,7 @@ version = "0.2.0" [dependencies] fdt-raw = {version = "0.2", path = "../fdt-raw"} log = "0.4" +enum_dispatch = "0.3" [dev-dependencies] dtb-file.workspace = true diff --git a/fdt-edit/src/lib.rs b/fdt-edit/src/lib.rs index 9289a53..d5d384c 100644 --- a/fdt-edit/src/lib.rs +++ b/fdt-edit/src/lib.rs @@ -6,7 +6,6 @@ extern crate alloc; mod fdt; mod node; mod prop; -mod view; mod visit; pub use fdt_raw::{FdtError, MemoryRegion, Phandle, RegInfo, Status, data::Reader}; @@ -15,7 +14,7 @@ pub use fdt_raw::{FdtError, MemoryRegion, Phandle, RegInfo, Status, data::Reader pub type NodeId = usize; pub use fdt::*; +pub use node::view::*; pub use node::*; pub use prop::*; -pub use view::*; pub use visit::*; diff --git a/fdt-edit/src/node/mod.rs b/fdt-edit/src/node/mod.rs index fd0a28c..68eed44 100644 --- a/fdt-edit/src/node/mod.rs +++ b/fdt-edit/src/node/mod.rs @@ -5,6 +5,8 @@ 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 node IDs. diff --git a/fdt-edit/src/view.rs b/fdt-edit/src/node/view.rs similarity index 96% rename from fdt-edit/src/view.rs rename to fdt-edit/src/node/view.rs index fd1826c..425bb65 100644 --- a/fdt-edit/src/view.rs +++ b/fdt-edit/src/node/view.rs @@ -4,13 +4,22 @@ //! `Fdt` arena. `NodeType` and `NodeTypeMut` enums allow dispatching to //! type-specialized views such as `MemoryNodeView` and `IntcNodeView`. -use core::ops::Deref; +use core::{fmt::Display, ops::Deref}; use alloc::{string::String, vec::Vec}; +use enum_dispatch::enum_dispatch; use fdt_raw::{MemoryRegion, Phandle, Status}; use crate::{Fdt, Node, NodeId, Property, RangesEntry}; +#[enum_dispatch] +pub(crate) trait ViewOp { + fn as_view(&self) -> NodeView<'_>; + fn display(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + self.as_view().fmt(f) + } +} + // --------------------------------------------------------------------------- // NodeView — immutable view // --------------------------------------------------------------------------- @@ -140,24 +149,6 @@ impl<'a> NodeView<'a> { NodeType::Generic(GenericNodeView { inner: *self }) } } - - /// Try to view as a memory node. - pub fn as_memory(&self) -> Option> { - if self.as_node().is_memory() { - Some(MemoryNodeView { inner: *self }) - } else { - None - } - } - - /// Try to view as an interrupt controller node. - pub fn as_intc(&self) -> Option> { - if self.as_node().is_interrupt_controller() { - Some(IntcNodeView { inner: *self }) - } else { - None - } - } } impl core::fmt::Display for NodeView<'_> { @@ -295,6 +286,7 @@ impl<'a> NodeViewMut<'a> { // NodeType — classified immutable view enum // --------------------------------------------------------------------------- +#[enum_dispatch(ViewOp)] /// Typed node view enum, allowing pattern matching by node kind. pub enum NodeType<'a> { /// A memory node (`device_type = "memory"` or name starts with "memory"). @@ -376,6 +368,12 @@ pub struct MemoryNodeView<'a> { inner: NodeView<'a>, } +impl ViewOp for MemoryNodeView<'_> { + fn as_view(&self) -> NodeView<'_> { + self.inner + } +} + impl<'a> Deref for MemoryNodeView<'a> { type Target = NodeView<'a>; fn deref(&self) -> &Self::Target { @@ -436,6 +434,12 @@ pub struct IntcNodeView<'a> { inner: NodeView<'a>, } +impl<'a> ViewOp for IntcNodeView<'a> { + fn as_view(&self) -> NodeView<'a> { + self.inner + } +} + impl<'a> Deref for IntcNodeView<'a> { type Target = NodeView<'a>; fn deref(&self) -> &Self::Target { @@ -465,6 +469,12 @@ pub struct GenericNodeView<'a> { inner: NodeView<'a>, } +impl ViewOp for GenericNodeView<'_> { + fn as_view(&self) -> NodeView<'_> { + self.inner + } +} + impl<'a> Deref for GenericNodeView<'a> { type Target = NodeView<'a>; fn deref(&self) -> &Self::Target { From 4a2413eef3c207e4118c9884d8ce76e4bbbac61e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Tue, 10 Feb 2026 21:40:08 +0800 Subject: [PATCH 20/39] Implement specialized node views for device tree nodes - Added `NodeGeneric` and `NodeGenericMut` for generic node views. - Introduced `IntcNodeView` and `IntcNodeViewMut` for interrupt controller nodes. - Created `MemoryNodeView` and `MemoryNodeViewMut` for memory nodes, including methods for parsing memory regions. - Refactored `NodeView` and `NodeType` to support new specialized views. - Removed the visitor traits and related implementations for traversing device tree nodes. - Updated tests to reflect changes in node view access and removed obsolete visitor tests. --- fdt-edit/src/lib.rs | 2 - fdt-edit/src/node/view.rs | 627 ------------------------------ fdt-edit/src/node/view/generic.rs | 57 +++ fdt-edit/src/node/view/intc.rs | 85 ++++ fdt-edit/src/node/view/memory.rs | 122 ++++++ fdt-edit/src/node/view/mod.rs | 293 ++++++++++++++ fdt-edit/src/visit.rs | 169 -------- fdt-edit/tests/fdt.rs | 48 +-- 8 files changed, 559 insertions(+), 844 deletions(-) delete mode 100644 fdt-edit/src/node/view.rs create mode 100644 fdt-edit/src/node/view/generic.rs create mode 100644 fdt-edit/src/node/view/intc.rs create mode 100644 fdt-edit/src/node/view/memory.rs create mode 100644 fdt-edit/src/node/view/mod.rs delete mode 100644 fdt-edit/src/visit.rs diff --git a/fdt-edit/src/lib.rs b/fdt-edit/src/lib.rs index d5d384c..58f0b4f 100644 --- a/fdt-edit/src/lib.rs +++ b/fdt-edit/src/lib.rs @@ -6,7 +6,6 @@ extern crate alloc; mod fdt; mod node; mod prop; -mod visit; pub use fdt_raw::{FdtError, MemoryRegion, Phandle, RegInfo, Status, data::Reader}; @@ -17,4 +16,3 @@ pub use fdt::*; pub use node::view::*; pub use node::*; pub use prop::*; -pub use visit::*; diff --git a/fdt-edit/src/node/view.rs b/fdt-edit/src/node/view.rs deleted file mode 100644 index 425bb65..0000000 --- a/fdt-edit/src/node/view.rs +++ /dev/null @@ -1,627 +0,0 @@ -//! 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`. - -use core::{fmt::Display, ops::Deref}; - -use alloc::{string::String, vec::Vec}; -use enum_dispatch::enum_dispatch; -use fdt_raw::{MemoryRegion, Phandle, Status}; - -use crate::{Fdt, Node, NodeId, Property, RangesEntry}; - -#[enum_dispatch] -pub(crate) trait ViewOp { - fn as_view(&self) -> NodeView<'_>; - fn display(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - self.as_view().fmt(f) - } -} - -// --------------------------------------------------------------------------- -// 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 struct NodeView<'a> { - fdt: &'a Fdt, - id: NodeId, -} - -impl<'a> NodeView<'a> { - /// Creates a new `NodeView`. - pub(crate) fn new(fdt: &'a Fdt, id: NodeId) -> Self { - Self { fdt, id } - } - - /// 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") - } - - /// Returns the `Fdt` arena this view belongs to. - pub fn fdt(&self) -> &'a Fdt { - self.fdt - } - - // -- delegation to Node -- - - /// Node name (without path). - pub fn name(&self) -> &'a str { - self.as_node().name() - } - - /// Property list. - pub fn properties(&self) -> &'a [Property] { - self.as_node().properties() - } - - /// Get a property by name. - pub fn get_property(&self, name: &str) -> Option<&'a Property> { - self.as_node().get_property(name) - } - - /// Child node IDs. - pub fn child_ids(&self) -> &'a [NodeId] { - self.as_node().children() - } - - /// Iterator over child `NodeView`s. - pub fn children(&self) -> impl Iterator> + 'a { - let fdt = self.fdt; - self.as_node() - .children() - .iter() - .map(move |&child_id| NodeView::new(fdt, child_id)) - } - - /// Get a child view by name. - pub fn get_child(&self, name: &str) -> Option> { - let child_id = self.as_node().get_child(name)?; - Some(NodeView::new(self.fdt, child_id)) - } - - /// Parent view, if any. - pub fn parent(&self) -> Option> { - self.as_node() - .parent - .map(|pid| NodeView::new(self.fdt, pid)) - } - - /// Full path string (e.g. "/soc/uart@10000"). - pub fn path(&self) -> String { - self.fdt.path_of(self.id) - } - - // -- DT shortcut methods (delegated to Node) -- - - pub fn address_cells(&self) -> Option { - self.as_node().address_cells() - } - pub fn size_cells(&self) -> Option { - self.as_node().size_cells() - } - pub fn phandle(&self) -> Option { - self.as_node().phandle() - } - pub fn interrupt_parent(&self) -> Option { - self.as_node().interrupt_parent() - } - pub fn status(&self) -> Option { - self.as_node().status() - } - pub fn compatible(&self) -> Option> { - self.as_node().compatible() - } - pub fn compatibles(&self) -> impl Iterator { - self.as_node().compatibles() - } - pub fn device_type(&self) -> Option<&'a str> { - self.as_node().device_type() - } - pub fn ranges(&self, parent_address_cells: u32) -> Option> { - self.as_node().ranges(parent_address_cells) - } - - // -- classification -- - - /// Classify this node into a typed view. - pub fn classify(&self) -> NodeType<'a> { - let node = self.as_node(); - if node.is_interrupt_controller() { - NodeType::InterruptController(IntcNodeView { inner: *self }) - } else if node.is_memory() { - NodeType::Memory(MemoryNodeView { inner: *self }) - } else { - NodeType::Generic(GenericNodeView { 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.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(()) - } -} - -// --------------------------------------------------------------------------- -// NodeViewMut — mutable view -// --------------------------------------------------------------------------- - -/// A mutable view of a node in the device tree. -/// -/// Holds an exclusive `&mut Fdt` reference and a `NodeId`. -pub struct NodeViewMut<'a> { - fdt: &'a mut Fdt, - id: NodeId, -} - -impl<'a> NodeViewMut<'a> { - /// Creates a new `NodeViewMut`. - pub(crate) fn new(fdt: &'a mut Fdt, id: NodeId) -> Self { - Self { fdt, id } - } - - /// Returns the underlying `NodeId`. - pub fn id(&self) -> NodeId { - self.id - } - - /// Immutable access to the underlying `Node`. - pub fn as_node(&self) -> &Node { - self.fdt - .node(self.id) - .expect("NodeViewMut references a valid node") - } - - /// Mutable access to the underlying `Node`. - pub fn as_node_mut(&mut self) -> &mut Node { - self.fdt - .node_mut(self.id) - .expect("NodeViewMut references a valid node") - } - - /// Node name. - pub fn name(&self) -> &str { - self.as_node().name() - } - - /// Full path string. - pub fn path(&self) -> String { - self.fdt.path_of(self.id) - } - - // -- property mutation -- - - /// Set a property (add or update). - pub fn set_property(&mut self, prop: Property) { - self.as_node_mut().set_property(prop); - } - - /// Remove a property by name. - pub fn remove_property(&mut self, name: &str) -> Option { - self.as_node_mut().remove_property(name) - } - - /// Get a property by name. - pub fn get_property(&self, name: &str) -> Option<&Property> { - self.as_node().get_property(name) - } - - // -- child mutation -- - - /// Add a child node, returning the new child's ID. - pub fn add_child(&mut self, node: Node) -> NodeId { - self.fdt.add_node(self.id, node) - } - - /// Remove a child node by name. - pub fn remove_child(&mut self, name: &str) -> Option { - self.fdt.remove_node(self.id, name) - } - - // -- classification (consumes self because &mut Fdt cannot be copied) -- - - /// Classify this node into a typed mutable view. - pub fn classify(self) -> NodeTypeMut<'a> { - let is_intc = self - .fdt - .node(self.id) - .map(|n| n.is_interrupt_controller()) - .unwrap_or(false); - let is_mem = self - .fdt - .node(self.id) - .map(|n| n.is_memory()) - .unwrap_or(false); - - if is_intc { - NodeTypeMut::InterruptController(IntcNodeViewMut { inner: self }) - } else if is_mem { - NodeTypeMut::Memory(MemoryNodeViewMut { inner: self }) - } else { - NodeTypeMut::Generic(GenericNodeViewMut { inner: self }) - } - } -} - -// --------------------------------------------------------------------------- -// NodeType — classified immutable view enum -// --------------------------------------------------------------------------- - -#[enum_dispatch(ViewOp)] -/// Typed node view enum, allowing pattern matching by node kind. -pub enum NodeType<'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 generic node (no special classification). - Generic(GenericNodeView<'a>), -} - -impl<'a> NodeType<'a> { - /// Returns the inner `NodeView` regardless of variant. - pub fn as_view(&self) -> &NodeView<'a> { - match self { - NodeType::Memory(v) => &v.inner, - NodeType::InterruptController(v) => &v.inner, - NodeType::Generic(v) => &v.inner, - } - } - - /// 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() - } - - /// 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() - } -} - -impl core::fmt::Display for NodeType<'_> { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!(f, "{}", self.as_view()) - } -} - -// --------------------------------------------------------------------------- -// NodeTypeMut — classified mutable view enum -// --------------------------------------------------------------------------- - -/// Typed mutable node view enum. -pub enum NodeTypeMut<'a> { - Memory(MemoryNodeViewMut<'a>), - InterruptController(IntcNodeViewMut<'a>), - Generic(GenericNodeViewMut<'a>), -} - -impl<'a> NodeTypeMut<'a> { - /// Returns the inner node ID regardless of variant. - pub fn id(&self) -> NodeId { - match self { - NodeTypeMut::Memory(v) => v.inner.id, - NodeTypeMut::InterruptController(v) => v.inner.id, - NodeTypeMut::Generic(v) => v.inner.id, - } - } -} - -// --------------------------------------------------------------------------- -// MemoryNodeView -// --------------------------------------------------------------------------- - -/// Specialized view for memory nodes. -/// -/// Provides methods for parsing `reg` into memory regions. -#[derive(Clone, Copy)] -pub struct MemoryNodeView<'a> { - inner: NodeView<'a>, -} - -impl ViewOp for MemoryNodeView<'_> { - fn as_view(&self) -> NodeView<'_> { - self.inner - } -} - -impl<'a> Deref for MemoryNodeView<'a> { - type Target = NodeView<'a>; - fn deref(&self) -> &Self::Target { - &self.inner - } -} - -impl<'a> MemoryNodeView<'a> { - /// Iterates over memory regions parsed from the `reg` property. - /// - /// Uses the parent node's `#address-cells` and `#size-cells` to decode. - pub fn regions(&self) -> Vec { - let node = self.inner.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(); - - let mut reader = reg.as_reader(); - let mut regions = Vec::new(); - - while let (Some(address), Some(size)) = - (reader.read_cells(addr_cells), reader.read_cells(size_cells)) - { - regions.push(MemoryRegion { address, size }); - } - - regions - } - - /// Total size across all memory regions. - pub fn total_size(&self) -> u64 { - self.regions().iter().map(|r| r.size).sum() - } - - /// Returns (address_cells, size_cells) from the parent node (defaults: 2, 1). - fn parent_cells(&self) -> (usize, usize) { - if let Some(parent) = self.inner.parent() { - let ac = parent.address_cells().unwrap_or(2) as usize; - let sc = parent.size_cells().unwrap_or(1) as usize; - (ac, sc) - } else { - (2, 1) - } - } -} - -// --------------------------------------------------------------------------- -// IntcNodeView -// --------------------------------------------------------------------------- - -/// Specialized view for interrupt controller nodes. -#[derive(Clone, Copy)] -pub struct IntcNodeView<'a> { - inner: NodeView<'a>, -} - -impl<'a> ViewOp for IntcNodeView<'a> { - fn as_view(&self) -> NodeView<'a> { - self.inner - } -} - -impl<'a> Deref for IntcNodeView<'a> { - type Target = NodeView<'a>; - fn deref(&self) -> &Self::Target { - &self.inner - } -} - -impl<'a> IntcNodeView<'a> { - /// Returns the `#interrupt-cells` property value. - pub fn interrupt_cells(&self) -> Option { - self.inner.as_node().interrupt_cells() - } - - /// This is always `true` for `IntcNodeView` (type-level guarantee). - pub fn is_interrupt_controller(&self) -> bool { - true - } -} - -// --------------------------------------------------------------------------- -// GenericNodeView -// --------------------------------------------------------------------------- - -/// A generic node view with no extra specialization. -#[derive(Clone, Copy)] -pub struct GenericNodeView<'a> { - inner: NodeView<'a>, -} - -impl ViewOp for GenericNodeView<'_> { - fn as_view(&self) -> NodeView<'_> { - self.inner - } -} - -impl<'a> Deref for GenericNodeView<'a> { - type Target = NodeView<'a>; - fn deref(&self) -> &Self::Target { - &self.inner - } -} - -// --------------------------------------------------------------------------- -// Mutable specialized views -// --------------------------------------------------------------------------- - -/// Mutable view for memory nodes. -pub struct MemoryNodeViewMut<'a> { - inner: NodeViewMut<'a>, -} - -impl<'a> MemoryNodeViewMut<'a> { - /// Access underlying mutable view. - pub fn as_view_mut(&mut self) -> &mut NodeViewMut<'a> { - &mut self.inner - } - - /// Access underlying immutable node. - pub fn as_node(&self) -> &Node { - self.inner.as_node() - } - - pub fn id(&self) -> NodeId { - self.inner.id - } -} - -/// Mutable view for interrupt controller nodes. -pub struct IntcNodeViewMut<'a> { - inner: NodeViewMut<'a>, -} - -impl<'a> IntcNodeViewMut<'a> { - pub fn as_view_mut(&mut self) -> &mut NodeViewMut<'a> { - &mut self.inner - } - - pub fn as_node(&self) -> &Node { - self.inner.as_node() - } - - pub fn id(&self) -> NodeId { - self.inner.id - } - - pub fn interrupt_cells(&self) -> Option { - self.inner.as_node().interrupt_cells() - } -} - -/// Mutable view for generic nodes. -pub struct GenericNodeViewMut<'a> { - inner: NodeViewMut<'a>, -} - -impl<'a> GenericNodeViewMut<'a> { - pub fn as_view_mut(&mut self) -> &mut NodeViewMut<'a> { - &mut self.inner - } - - pub fn as_node(&self) -> &Node { - self.inner.as_node() - } - - pub fn id(&self) -> NodeId { - self.inner.id - } -} - -// --------------------------------------------------------------------------- -// Fdt convenience methods returning views -// --------------------------------------------------------------------------- - -impl Fdt { - /// Returns a `NodeView` for the root node. - pub fn root(&self) -> NodeView<'_> { - NodeView::new(self, self.root_id()) - } - - /// Returns a `NodeView` for the given node ID, if it exists. - pub fn view(&self, id: NodeId) -> Option> { - if self.node(id).is_some() { - Some(NodeView::new(self, id)) - } else { - None - } - } - - /// Returns a `NodeViewMut` for the given node ID, if it exists. - pub fn view_mut(&mut self, id: NodeId) -> Option> { - if self.node(id).is_some() { - Some(NodeViewMut::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> { - if self.node(id).is_some() { - Some(NodeViewMut::new(self, id).classify()) - } else { - None - } - } - - /// 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()) - } - - /// 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(NodeViewMut::new(self, id).classify()) - } - - /// 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()) - } - - /// 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(NodeViewMut::new(self, id).classify()) - } - - /// Returns a depth-first iterator over `NodeView`s. - pub fn iter_raw_nodes(&self) -> impl Iterator> { - self.iter_node_ids().map(move |id| NodeView::new(self, id)) - } - - /// Returns a depth-first iterator over classified `NodeType`s. - pub fn all_nodes(&self) -> impl Iterator> { - self.iter_raw_nodes().map(|v| v.classify()) - } -} diff --git a/fdt-edit/src/node/view/generic.rs b/fdt-edit/src/node/view/generic.rs new file mode 100644 index 0000000..05a8133 --- /dev/null +++ b/fdt-edit/src/node/view/generic.rs @@ -0,0 +1,57 @@ +//! Generic node view specialization. + +use alloc::string::String; + +use super::NodeView; +use crate::{NodeId, 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() + } +} + +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> NodeGenericMut<'a> { + pub fn id(&self) -> NodeId { + self.inner.id() + } + + pub fn path(&self) -> String { + self.inner.path() + } +} diff --git a/fdt-edit/src/node/view/intc.rs b/fdt-edit/src/node/view/intc.rs new file mode 100644 index 0000000..da417f8 --- /dev/null +++ b/fdt-edit/src/node/view/intc.rs @@ -0,0 +1,85 @@ +//! Interrupt controller node view specialization. + +use core::ops::Deref; + +use super::NodeView; +use crate::{NodeGeneric, NodeGenericMut, 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> 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..149fa3f --- /dev/null +++ b/fdt-edit/src/node/view/memory.rs @@ -0,0 +1,122 @@ +//! Memory node view specialization. + +use core::ops::Deref; + +use alloc::vec::Vec; +use fdt_raw::MemoryRegion; + +use super::NodeView; +use crate::{Node, NodeGeneric, NodeGenericMut, NodeId, 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 `#address-cells` and `#size-cells` to decode. + pub fn regions(&self) -> Vec { + let node = self.as_view().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(); + + let mut reader = reg.as_reader(); + let mut regions = Vec::new(); + + while let (Some(address), Some(size)) = + (reader.read_cells(addr_cells), reader.read_cells(size_cells)) + { + regions.push(MemoryRegion { address, size }); + } + + regions + } + + /// Total size across all memory regions. + pub fn total_size(&self) -> u64 { + self.regions().iter().map(|r| r.size).sum() + } + + /// Returns (address_cells, size_cells) from the parent node (defaults: 2, 1). + fn parent_cells(&self) -> (usize, usize) { + if let Some(parent) = self.as_view().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) + } + } +} + +// --------------------------------------------------------------------------- +// 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> 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..36b21db --- /dev/null +++ b/fdt-edit/src/node/view/mod.rs @@ -0,0 +1,293 @@ +//! 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 generic; +mod intc; +mod memory; + +use core::fmt::Display; + +use alloc::{string::String, vec::Vec}; +use enum_dispatch::enum_dispatch; + +use crate::{Fdt, Node, NodeId}; + +// Re-export specialized view types +pub use generic::{NodeGeneric, NodeGenericMut}; +pub use intc::{IntcNodeView, IntcNodeViewMut}; +pub use memory::{MemoryNodeView, MemoryNodeViewMut}; + +#[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) + } +} + +// --------------------------------------------------------------------------- +// 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.as_node() + .parent + .map(|pid| NodeView::new(self.fdt(), pid).classify()) + } + + pub fn parent_mut(&mut self) -> Option> { + let parent = self.as_node().parent?; + 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() + } + + fn classify(&self) -> NodeType<'a> { + 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 }) + } + + fn classify_mut(&mut self) -> NodeTypeMut<'a> { + 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 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 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() + } +} + +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> { + Memory(MemoryNodeViewMut<'a>), + InterruptController(IntcNodeViewMut<'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() + } +} + +// --------------------------------------------------------------------------- +// 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()) + } + + /// 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()) + } + + /// 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()) + } + + /// 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()) + } + + /// 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()) + } + + /// 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)) + } + + /// Returns a depth-first iterator over classified `NodeType`s. + pub fn all_nodes(&self) -> impl Iterator> { + self.iter_raw_nodes().map(|v| v.classify()) + } +} diff --git a/fdt-edit/src/visit.rs b/fdt-edit/src/visit.rs deleted file mode 100644 index 5da6599..0000000 --- a/fdt-edit/src/visit.rs +++ /dev/null @@ -1,169 +0,0 @@ -//! Visitor traits for traversing device tree nodes. -//! -//! Inspired by `toml_edit::visit` / `visit_mut`. The `Visit` trait provides -//! immutable traversal with `NodeView`, while `VisitMut` provides mutable -//! traversal using `(&mut Fdt, NodeId)` pairs to avoid aliasing issues. - -use alloc::vec::Vec; - -use crate::{Fdt, NodeId, NodeView, Property}; - -// --------------------------------------------------------------------------- -// Visit — immutable traversal -// --------------------------------------------------------------------------- - -/// Immutable visitor trait for device tree traversal. -/// -/// Override individual methods to customize behavior. Default implementations -/// recurse through the tree structure. -/// -/// # Example -/// -/// ```ignore -/// struct NodeCounter { count: usize } -/// -/// impl<'a> Visit<'a> for NodeCounter { -/// fn visit_node(&mut self, node: NodeView<'a>) { -/// self.count += 1; -/// visit_node(self, node); // recurse children -/// } -/// } -/// ``` -pub trait Visit<'a> { - /// Called for every node. Default: classifies and dispatches to typed methods. - fn visit_node(&mut self, node: NodeView<'a>) { - visit_node(self, node); - } - - /// Called for memory nodes. Default: visits properties and recurses children. - fn visit_memory_node(&mut self, node: NodeView<'a>) { - visit_node_children(self, node); - } - - /// Called for interrupt controller nodes. Default: visits properties and recurses children. - fn visit_intc_node(&mut self, node: NodeView<'a>) { - visit_node_children(self, node); - } - - /// Called for generic nodes. Default: visits properties and recurses children. - fn visit_generic_node(&mut self, node: NodeView<'a>) { - visit_node_children(self, node); - } - - /// Called for each property. Default: no-op. - fn visit_property(&mut self, _node: NodeView<'a>, _prop: &'a Property) {} -} - -/// Default `visit_node` implementation: classifies and dispatches. -pub fn visit_node<'a, V: Visit<'a> + ?Sized>(v: &mut V, node: NodeView<'a>) { - let n = node.as_node(); - if n.is_interrupt_controller() { - v.visit_intc_node(node); - } else if n.is_memory() { - v.visit_memory_node(node); - } else { - v.visit_generic_node(node); - } -} - -/// Visits properties then recurses into children (used by default typed methods). -pub fn visit_node_children<'a, V: Visit<'a> + ?Sized>(v: &mut V, node: NodeView<'a>) { - for prop in node.properties() { - v.visit_property(node, prop); - } - for child in node.children() { - v.visit_node(child); - } -} - -// --------------------------------------------------------------------------- -// VisitMut — mutable traversal -// --------------------------------------------------------------------------- - -/// Mutable visitor trait for device tree traversal. -/// -/// Takes `(&mut Fdt, NodeId)` pairs instead of view types to avoid -/// `&mut` aliasing. The default implementations clone children lists -/// before recursing to avoid borrow conflicts. -pub trait VisitMut { - /// Called for every node. Default: classifies and dispatches. - fn visit_node_mut(&mut self, fdt: &mut Fdt, id: NodeId) { - visit_node_mut(self, fdt, id); - } - - /// Called for memory nodes. - fn visit_memory_node_mut(&mut self, fdt: &mut Fdt, id: NodeId) { - visit_node_children_mut(self, fdt, id); - } - - /// Called for interrupt controller nodes. - fn visit_intc_node_mut(&mut self, fdt: &mut Fdt, id: NodeId) { - visit_node_children_mut(self, fdt, id); - } - - /// Called for generic nodes. - fn visit_generic_node_mut(&mut self, fdt: &mut Fdt, id: NodeId) { - visit_node_children_mut(self, fdt, id); - } - - /// Called for each property. - fn visit_property_mut(&mut self, _fdt: &mut Fdt, _node_id: NodeId, _prop_name: &str) {} -} - -/// Default `visit_node_mut`: classifies and dispatches. -pub fn visit_node_mut(v: &mut V, fdt: &mut Fdt, id: NodeId) { - let (is_intc, is_mem) = { - match fdt.node(id) { - Some(n) => (n.is_interrupt_controller(), n.is_memory()), - None => return, - } - }; - - if is_intc { - v.visit_intc_node_mut(fdt, id); - } else if is_mem { - v.visit_memory_node_mut(fdt, id); - } else { - v.visit_generic_node_mut(fdt, id); - } -} - -/// Visits properties then recurses children (mutable default). -pub fn visit_node_children_mut(v: &mut V, fdt: &mut Fdt, id: NodeId) { - // Clone property names to avoid borrow conflict - let prop_names: Vec<_> = match fdt.node(id) { - Some(n) => n.properties().iter().map(|p| p.name.clone()).collect(), - None => return, - }; - - for name in &prop_names { - v.visit_property_mut(fdt, id, name); - } - - // Clone children list to avoid borrow conflict - let children: Vec = match fdt.node(id) { - Some(n) => n.children().to_vec(), - None => return, - }; - - for child_id in children { - v.visit_node_mut(fdt, child_id); - } -} - -// --------------------------------------------------------------------------- -// Convenience entry points on Fdt -// --------------------------------------------------------------------------- - -impl Fdt { - /// Run an immutable visitor starting from the root. - pub fn visit<'a, V: Visit<'a>>(&'a self, visitor: &mut V) { - visitor.visit_node(self.root()); - } - - /// Run a mutable visitor starting from the root. - pub fn visit_mut(&mut self, visitor: &mut V) { - let root = self.root_id(); - visitor.visit_node_mut(self, root); - } -} diff --git a/fdt-edit/tests/fdt.rs b/fdt-edit/tests/fdt.rs index 33ad613..a136eed 100644 --- a/fdt-edit/tests/fdt.rs +++ b/fdt-edit/tests/fdt.rs @@ -59,50 +59,6 @@ fn test_node_classify() { assert!(generic_count > 0, "phytium DTB should have generic nodes"); } -#[test] -fn test_visit_trait() { - struct Counter { - nodes: usize, - memory: usize, - intc: usize, - } - - impl<'a> Visit<'a> for Counter { - fn visit_memory_node(&mut self, node: NodeView<'a>) { - self.memory += 1; - self.nodes += 1; - visit_node_children(self, node); - } - - fn visit_intc_node(&mut self, node: NodeView<'a>) { - self.intc += 1; - self.nodes += 1; - visit_node_children(self, node); - } - - fn visit_generic_node(&mut self, node: NodeView<'a>) { - self.nodes += 1; - visit_node_children(self, node); - } - } - - let raw_data = fdt_phytium(); - let fdt = Fdt::from_bytes(&raw_data).unwrap(); - - let mut counter = Counter { - nodes: 0, - memory: 0, - intc: 0, - }; - fdt.visit(&mut counter); - - println!( - "Visit: total={} memory={} intc={}", - counter.nodes, counter.memory, counter.intc - ); - assert_eq!(counter.nodes, fdt.node_count()); -} - #[test] fn test_path_lookup() { let raw_data = fdt_phytium(); @@ -110,7 +66,7 @@ fn test_path_lookup() { // Root should always be found let root = fdt.get_by_path("/").unwrap(); - assert_eq!(root.as_view().id(), fdt.root_id()); + 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() { @@ -129,7 +85,7 @@ fn test_path_lookup() { for view in fdt.all_nodes() { let path = view.path(); let typed = fdt.get_by_path(&path).unwrap(); - assert_eq!(typed.as_view().id(), view.id()); + assert_eq!(typed.id(), view.id()); } } From e7c179349adfbe9a7ad0f579951adacb620d06d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Tue, 10 Feb 2026 21:44:03 +0800 Subject: [PATCH 21/39] fix: Adjust imports in memory and header modules for consistency --- fdt-edit/src/node/view/memory.rs | 2 +- fdt-raw/src/header.rs | 2 +- fdt-raw/src/node/prop/mod.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/fdt-edit/src/node/view/memory.rs b/fdt-edit/src/node/view/memory.rs index 149fa3f..0319fd2 100644 --- a/fdt-edit/src/node/view/memory.rs +++ b/fdt-edit/src/node/view/memory.rs @@ -6,7 +6,7 @@ use alloc::vec::Vec; use fdt_raw::MemoryRegion; use super::NodeView; -use crate::{Node, NodeGeneric, NodeGenericMut, NodeId, ViewOp}; +use crate::{NodeGeneric, NodeGenericMut, ViewOp}; // --------------------------------------------------------------------------- // MemoryNodeView diff --git a/fdt-raw/src/header.rs b/fdt-raw/src/header.rs index ea55775..bd09125 100644 --- a/fdt-raw/src/header.rs +++ b/fdt-raw/src/header.rs @@ -6,8 +6,8 @@ use core::ptr::NonNull; -use crate::data::U32_SIZE; use crate::FdtError; +use crate::data::U32_SIZE; /// A 4-byte aligned buffer for header data. /// diff --git a/fdt-raw/src/node/prop/mod.rs b/fdt-raw/src/node/prop/mod.rs index 4bbb3b4..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, U32_SIZE}, + data::{Bytes, Reader, StrIter, U32_SIZE, U32Iter}, }; /// A generic device tree property containing name and raw data. From 2abdb11925c14863a3bb823e2b0f7342b9f8f8f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Tue, 10 Feb 2026 21:57:31 +0800 Subject: [PATCH 22/39] feat: Enhance Fdt with alias resolution and path normalization methods --- fdt-edit/src/fdt.rs | 63 +++++++++++++++++++++++++++++++++-- fdt-edit/src/node/view/mod.rs | 38 ++------------------- 2 files changed, 62 insertions(+), 39 deletions(-) diff --git a/fdt-edit/src/fdt.rs b/fdt-edit/src/fdt.rs index 02ec1fa..db4146a 100644 --- a/fdt-edit/src/fdt.rs +++ b/fdt-edit/src/fdt.rs @@ -7,9 +7,13 @@ //! All nodes are stored in a flat `BTreeMap` arena. Child //! relationships are represented as `Vec` inside each `Node`. -use alloc::{collections::BTreeMap, string::String, vec, vec::Vec}; +use alloc::{ + collections::BTreeMap, + string::{String, ToString}, + vec::{self, Vec}, +}; -use crate::{FdtError, Node, NodeId, Phandle}; +use crate::{FdtError, Node, NodeId, NodeType, NodeTypeMut, NodeView, Phandle}; pub use fdt_raw::MemoryReservation; @@ -158,12 +162,31 @@ impl Fdt { } } + 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 { + // 尝试解析别名 + self.resolve_alias(path).map(|s| s.to_string()) + } + } + /// Looks up a node by its full path (e.g. "/soc/uart@10000"), /// returning its `NodeId`. /// /// The root node is matched by "/" or "". pub fn get_by_path_id(&self, path: &str) -> Option { - let normalized = path.trim_start_matches('/'); + let normalized_path = self.normalize_path(path)?; + let normalized = normalized_path.trim_start_matches('/'); if normalized.is_empty() { return Some(self.root); } @@ -313,6 +336,40 @@ impl Fdt { Ok(fdt) } + + /// 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()) + } + + /// 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()) + } + + /// 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()) + } + + /// 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()) + } + + /// 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)) + } + + /// Returns a depth-first iterator over classified `NodeType`s. + pub fn all_nodes(&self) -> impl Iterator> { + self.iter_raw_nodes().map(|v| v.classify()) + } } /// Depth-first iterator over all node IDs in the tree. diff --git a/fdt-edit/src/node/view/mod.rs b/fdt-edit/src/node/view/mod.rs index 36b21db..e939bd0 100644 --- a/fdt-edit/src/node/view/mod.rs +++ b/fdt-edit/src/node/view/mod.rs @@ -112,7 +112,7 @@ impl<'a> NodeView<'a> { self.as_node().size_cells() } - fn classify(&self) -> NodeType<'a> { + pub(crate) fn classify(&self) -> NodeType<'a> { if let Some(node) = MemoryNodeView::try_from_view(*self) { return NodeType::Memory(node); } @@ -124,7 +124,7 @@ impl<'a> NodeView<'a> { NodeType::Generic(NodeGeneric { inner: *self }) } - fn classify_mut(&mut self) -> NodeTypeMut<'a> { + pub(crate) fn classify_mut(&mut self) -> NodeTypeMut<'a> { if let Some(node) = MemoryNodeViewMut::try_from_view(*self) { return NodeTypeMut::Memory(node); } @@ -256,38 +256,4 @@ impl Fdt { pub fn view_typed_mut(&mut self, id: NodeId) -> Option> { self.view(id).map(|mut v| v.classify_mut()) } - - /// 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()) - } - - /// 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()) - } - - /// 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()) - } - - /// 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()) - } - - /// 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)) - } - - /// Returns a depth-first iterator over classified `NodeType`s. - pub fn all_nodes(&self) -> impl Iterator> { - self.iter_raw_nodes().map(|v| v.classify()) - } } From 764c8e765602971a6e5add297e202394d60d3ea9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Tue, 10 Feb 2026 22:24:03 +0800 Subject: [PATCH 23/39] feat: Implement address translation for memory regions and add tests --- fdt-edit/src/node/view/memory.rs | 41 ++++----------- fdt-edit/src/node/view/mod.rs | 86 +++++++++++++++++++++++++++++++- fdt-edit/tests/range.rs | 20 ++++++++ 3 files changed, 115 insertions(+), 32 deletions(-) create mode 100644 fdt-edit/tests/range.rs diff --git a/fdt-edit/src/node/view/memory.rs b/fdt-edit/src/node/view/memory.rs index 0319fd2..5db7135 100644 --- a/fdt-edit/src/node/view/memory.rs +++ b/fdt-edit/src/node/view/memory.rs @@ -47,44 +47,23 @@ impl<'a> MemoryNodeView<'a> { /// Iterates over memory regions parsed from the `reg` property. /// - /// Uses the parent node's `#address-cells` and `#size-cells` to decode. + /// Uses the parent node's `ranges` for address translation, converting + /// bus addresses to CPU physical addresses. pub fn regions(&self) -> Vec { - let node = self.as_view().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(); - - let mut reader = reg.as_reader(); - let mut regions = Vec::new(); - - while let (Some(address), Some(size)) = - (reader.read_cells(addr_cells), reader.read_cells(size_cells)) - { - regions.push(MemoryRegion { address, size }); - } - - regions + // 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() } - - /// Returns (address_cells, size_cells) from the parent node (defaults: 2, 1). - fn parent_cells(&self) -> (usize, usize) { - if let Some(parent) = self.as_view().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) - } - } } // --------------------------------------------------------------------------- diff --git a/fdt-edit/src/node/view/mod.rs b/fdt-edit/src/node/view/mod.rs index e939bd0..2e933fc 100644 --- a/fdt-edit/src/node/view/mod.rs +++ b/fdt-edit/src/node/view/mod.rs @@ -14,7 +14,7 @@ use core::fmt::Display; use alloc::{string::String, vec::Vec}; use enum_dispatch::enum_dispatch; -use crate::{Fdt, Node, NodeId}; +use crate::{Fdt, Node, NodeId, RangesEntry}; // Re-export specialized view types pub use generic::{NodeGeneric, NodeGenericMut}; @@ -112,6 +112,78 @@ impl<'a> NodeView<'a> { 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) + }) + } + pub(crate) fn classify(&self) -> NodeType<'a> { if let Some(node) = MemoryNodeView::try_from_view(*self) { return NodeType::Memory(node); @@ -206,6 +278,11 @@ impl<'a> NodeType<'a> { 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<'_> { @@ -257,3 +334,10 @@ impl Fdt { self.view(id).map(|mut v| v.classify_mut()) } } + +#[derive(Clone, Copy, Debug)] +pub struct RegFixed { + pub address: u64, + pub child_bus_address: u64, + pub size: Option, +} diff --git a/fdt-edit/tests/range.rs b/fdt-edit/tests/range.rs new file mode 100644 index 0000000..5fe65b9 --- /dev/null +++ b/fdt-edit/tests/range.rs @@ -0,0 +1,20 @@ +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"); +} From eaf8e4749157b2fa03869b7e3e2f629d9172ac9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Tue, 10 Feb 2026 22:34:36 +0800 Subject: [PATCH 24/39] feat: Implement address translation for device tree node registers and add corresponding tests --- fdt-edit/src/fdt.rs | 2 +- fdt-edit/src/node/view/generic.rs | 13 ++++- fdt-edit/src/node/view/intc.rs | 4 ++ fdt-edit/src/node/view/memory.rs | 4 ++ fdt-edit/src/node/view/mod.rs | 79 ++++++++++++++++++++++++++++++- fdt-edit/tests/range.rs | 59 +++++++++++++++++++++++ 6 files changed, 156 insertions(+), 5 deletions(-) diff --git a/fdt-edit/src/fdt.rs b/fdt-edit/src/fdt.rs index db4146a..756d896 100644 --- a/fdt-edit/src/fdt.rs +++ b/fdt-edit/src/fdt.rs @@ -10,7 +10,7 @@ use alloc::{ collections::BTreeMap, string::{String, ToString}, - vec::{self, Vec}, + vec::Vec, }; use crate::{FdtError, Node, NodeId, NodeType, NodeTypeMut, NodeView, Phandle}; diff --git a/fdt-edit/src/node/view/generic.rs b/fdt-edit/src/node/view/generic.rs index 05a8133..475ec1d 100644 --- a/fdt-edit/src/node/view/generic.rs +++ b/fdt-edit/src/node/view/generic.rs @@ -1,9 +1,10 @@ //! Generic node view specialization. -use alloc::string::String; +use alloc::{string::String, vec::Vec}; +use fdt_raw::RegInfo; use super::NodeView; -use crate::{NodeId, ViewOp}; +use crate::{NodeId, RegFixed, ViewOp}; // --------------------------------------------------------------------------- // GenericNodeView @@ -23,6 +24,10 @@ impl<'a> NodeGeneric<'a> { pub fn path(&self) -> String { self.inner.path() } + + pub fn regs(&self) -> Vec { + self.inner.regs() + } } impl<'a> ViewOp<'a> for NodeGeneric<'a> { @@ -54,4 +59,8 @@ impl<'a> NodeGenericMut<'a> { pub fn path(&self) -> String { self.inner.path() } + + pub fn set_regs(&mut self, regs: &[RegInfo]) { + self.inner.set_regs(regs); + } } diff --git a/fdt-edit/src/node/view/intc.rs b/fdt-edit/src/node/view/intc.rs index da417f8..ade3908 100644 --- a/fdt-edit/src/node/view/intc.rs +++ b/fdt-edit/src/node/view/intc.rs @@ -82,4 +82,8 @@ impl<'a> IntcNodeViewMut<'a> { None } } + + pub fn set_regs(&mut self, regs: &[fdt_raw::RegInfo]) { + self.inner.set_regs(regs); + } } diff --git a/fdt-edit/src/node/view/memory.rs b/fdt-edit/src/node/view/memory.rs index 5db7135..511d25e 100644 --- a/fdt-edit/src/node/view/memory.rs +++ b/fdt-edit/src/node/view/memory.rs @@ -98,4 +98,8 @@ impl<'a> MemoryNodeViewMut<'a> { None } } + + pub fn set_regs(&mut self, regs: &[fdt_raw::RegInfo]) { + self.inner.set_regs(regs); + } } diff --git a/fdt-edit/src/node/view/mod.rs b/fdt-edit/src/node/view/mod.rs index 2e933fc..92a8f35 100644 --- a/fdt-edit/src/node/view/mod.rs +++ b/fdt-edit/src/node/view/mod.rs @@ -14,7 +14,7 @@ use core::fmt::Display; use alloc::{string::String, vec::Vec}; use enum_dispatch::enum_dispatch; -use crate::{Fdt, Node, NodeId, RangesEntry}; +use crate::{Fdt, Node, NodeId, Property, RangesEntry}; // Re-export specialized view types pub use generic::{NodeGeneric, NodeGenericMut}; @@ -177,13 +177,80 @@ impl<'a> NodeView<'a> { 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() + 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) = MemoryNodeView::try_from_view(*self) { return NodeType::Memory(node); @@ -308,6 +375,14 @@ impl<'a> NodeTypeMut<'a> { 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); + } } // --------------------------------------------------------------------------- diff --git a/fdt-edit/tests/range.rs b/fdt-edit/tests/range.rs index 5fe65b9..dd0da56 100644 --- a/fdt-edit/tests/range.rs +++ b/fdt-edit/tests/range.rs @@ -18,3 +18,62 @@ fn test_reg_address_translation() { 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() { + 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(); + node.set_regs(&[RegInfo { + address: new_cpu_address, + size: Some(new_size), + }]); + } + + // 重新读取验证 + let node = fdt.get_by_path("/soc/serial@7e215040").unwrap(); + let updated_regs = node.regs(); + let updated_reg = &updated_regs[0]; + + // 验证:读取回来的 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)); +} + +#[test] +fn test_set_regs_roundtrip() { + let raw = fdt_rpi_4b(); + let mut fdt = Fdt::from_bytes(&raw).unwrap(); + + // 获取原始 reg 信息 + let original_reg = { + let node = fdt.get_by_path("/soc/serial@7e215040").unwrap(); + node.regs()[0] + }; + + // 使用相同的 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); +} From efa6aa80a91991a5ab2b3563497aecc38eee9457 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Tue, 10 Feb 2026 22:35:14 +0800 Subject: [PATCH 25/39] fix: Remove unused set_regs method from IntcNodeViewMut and MemoryNodeViewMut --- fdt-edit/src/node/view/intc.rs | 4 ---- fdt-edit/src/node/view/memory.rs | 6 +----- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/fdt-edit/src/node/view/intc.rs b/fdt-edit/src/node/view/intc.rs index ade3908..da417f8 100644 --- a/fdt-edit/src/node/view/intc.rs +++ b/fdt-edit/src/node/view/intc.rs @@ -82,8 +82,4 @@ impl<'a> IntcNodeViewMut<'a> { None } } - - pub fn set_regs(&mut self, regs: &[fdt_raw::RegInfo]) { - self.inner.set_regs(regs); - } } diff --git a/fdt-edit/src/node/view/memory.rs b/fdt-edit/src/node/view/memory.rs index 511d25e..3ccd860 100644 --- a/fdt-edit/src/node/view/memory.rs +++ b/fdt-edit/src/node/view/memory.rs @@ -54,7 +54,7 @@ impl<'a> MemoryNodeView<'a> { let regs = self.as_view().regs(); regs.into_iter() .map(|r| MemoryRegion { - address: r.address, // Use the CPU-translated address + address: r.address, // Use the CPU-translated address size: r.size.unwrap_or(0), }) .collect() @@ -98,8 +98,4 @@ impl<'a> MemoryNodeViewMut<'a> { None } } - - pub fn set_regs(&mut self, regs: &[fdt_raw::RegInfo]) { - self.inner.set_regs(regs); - } } From e6c555bac8fc96b369f64c6d50e9c154e7a3aea6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Tue, 10 Feb 2026 22:36:16 +0800 Subject: [PATCH 26/39] fix: Refactor path_of method to use while let for cleaner code and add dead code allowance for parent_mut method --- .claude/settings.local.json | 3 ++- fdt-edit/src/fdt.rs | 6 +----- fdt-edit/src/node/view/mod.rs | 1 + 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 2befd39..4cc8fc6 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -11,7 +11,8 @@ "mcp__web-search-prime__webSearchPrime", "WebFetch(domain:www.devicetree.org)", "Bash(RUST_BACKTRACE=1 cargo test -p fdt-parser -- test_pci2 --nocapture)", - "Bash(cargo check:*)" + "Bash(cargo check:*)", + "Bash(cargo clippy:*)" ], "deny": [], "ask": [] diff --git a/fdt-edit/src/fdt.rs b/fdt-edit/src/fdt.rs index 756d896..5b481bf 100644 --- a/fdt-edit/src/fdt.rs +++ b/fdt-edit/src/fdt.rs @@ -208,11 +208,7 @@ impl Fdt { pub fn path_of(&self, id: NodeId) -> String { let mut parts: Vec<&str> = Vec::new(); let mut cur = id; - loop { - let node = match self.nodes.get(&cur) { - Some(n) => n, - None => break, - }; + while let Some(node) = self.nodes.get(&cur) { if cur == self.root { break; } diff --git a/fdt-edit/src/node/view/mod.rs b/fdt-edit/src/node/view/mod.rs index 92a8f35..8197c8f 100644 --- a/fdt-edit/src/node/view/mod.rs +++ b/fdt-edit/src/node/view/mod.rs @@ -97,6 +97,7 @@ impl<'a> NodeView<'a> { .map(|pid| NodeView::new(self.fdt(), pid).classify()) } + #[allow(dead_code)] pub fn parent_mut(&mut self) -> Option> { let parent = self.as_node().parent?; let mut parent_view = NodeView::new(self.fdt(), parent); From 9a1888634e26808f130f2684c9e1ee002ed3a23f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Tue, 10 Feb 2026 23:12:55 +0800 Subject: [PATCH 27/39] feat: Add ViewMutOp trait and implement for NodeGenericMut; enhance child node addition methods --- fdt-edit/src/node/view/generic.rs | 20 +++++++++++++++++++- fdt-edit/src/node/view/mod.rs | 4 ++++ fdt-raw/tests/ranges.rs | 2 +- 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/fdt-edit/src/node/view/generic.rs b/fdt-edit/src/node/view/generic.rs index 475ec1d..1df0526 100644 --- a/fdt-edit/src/node/view/generic.rs +++ b/fdt-edit/src/node/view/generic.rs @@ -4,7 +4,7 @@ use alloc::{string::String, vec::Vec}; use fdt_raw::RegInfo; use super::NodeView; -use crate::{NodeId, RegFixed, ViewOp}; +use crate::{Node, NodeId, RegFixed, ViewMutOp, ViewOp}; // --------------------------------------------------------------------------- // GenericNodeView @@ -51,6 +51,12 @@ impl<'a> ViewOp<'a> for NodeGenericMut<'a> { } } +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() @@ -63,4 +69,16 @@ impl<'a> NodeGenericMut<'a> { 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 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/mod.rs b/fdt-edit/src/node/view/mod.rs index 8197c8f..51c02a9 100644 --- a/fdt-edit/src/node/view/mod.rs +++ b/fdt-edit/src/node/view/mod.rs @@ -29,6 +29,10 @@ pub(crate) trait ViewOp<'a> { } } +pub trait ViewMutOp<'a> { + fn new(node: NodeGenericMut<'a>) -> Self; +} + // --------------------------------------------------------------------------- // NodeView — immutable view // --------------------------------------------------------------------------- diff --git a/fdt-raw/tests/ranges.rs b/fdt-raw/tests/ranges.rs index ee57c7c..2c1c3af 100644 --- a/fdt-raw/tests/ranges.rs +++ b/fdt-raw/tests/ranges.rs @@ -69,7 +69,7 @@ fn test_translate_addresses_batch() { // Batch translation with multiple addresses from the same path let mut addresses = [0x7e215040u64, 0x7e200000]; - let original = addresses.clone(); + let original = addresses; fdt.translate_addresses(path, &mut addresses); assert_eq!( From 658a182d9091080aac472a6a0799ba4b29eaaef2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Tue, 10 Feb 2026 23:19:43 +0800 Subject: [PATCH 28/39] feat: Enhance ViewMutOp trait with new implementations for IntcNodeViewMut and MemoryNodeViewMut; adjust visibility of add_child method in NodeGenericMut --- fdt-edit/src/node/view/generic.rs | 2 +- fdt-edit/src/node/view/intc.rs | 13 ++++++++++++- fdt-edit/src/node/view/memory.rs | 11 ++++++++++- fdt-edit/src/node/view/mod.rs | 12 +++++++++++- 4 files changed, 34 insertions(+), 4 deletions(-) diff --git a/fdt-edit/src/node/view/generic.rs b/fdt-edit/src/node/view/generic.rs index 1df0526..f2feb6b 100644 --- a/fdt-edit/src/node/view/generic.rs +++ b/fdt-edit/src/node/view/generic.rs @@ -77,7 +77,7 @@ impl<'a> NodeGenericMut<'a> { NodeGenericMut { inner: new_view } } - pub fn add_child>(&mut self, name: &str) -> T { + 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 index da417f8..7cbcd36 100644 --- a/fdt-edit/src/node/view/intc.rs +++ b/fdt-edit/src/node/view/intc.rs @@ -2,8 +2,10 @@ use core::ops::Deref; +use alloc::vec::Vec; + use super::NodeView; -use crate::{NodeGeneric, NodeGenericMut, ViewOp}; +use crate::{NodeGeneric, NodeGenericMut, Property, ViewMutOp, ViewOp}; // --------------------------------------------------------------------------- // IntcNodeView @@ -65,6 +67,15 @@ impl<'a> ViewOp<'a> for IntcNodeViewMut<'a> { } } +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 { diff --git a/fdt-edit/src/node/view/memory.rs b/fdt-edit/src/node/view/memory.rs index 3ccd860..110a845 100644 --- a/fdt-edit/src/node/view/memory.rs +++ b/fdt-edit/src/node/view/memory.rs @@ -6,7 +6,7 @@ use alloc::vec::Vec; use fdt_raw::MemoryRegion; use super::NodeView; -use crate::{NodeGeneric, NodeGenericMut, ViewOp}; +use crate::{NodeGeneric, NodeGenericMut, Property, ViewMutOp, ViewOp}; // --------------------------------------------------------------------------- // MemoryNodeView @@ -88,6 +88,15 @@ impl<'a> ViewOp<'a> for MemoryNodeViewMut<'a> { } } +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() { diff --git a/fdt-edit/src/node/view/mod.rs b/fdt-edit/src/node/view/mod.rs index 51c02a9..4af92ab 100644 --- a/fdt-edit/src/node/view/mod.rs +++ b/fdt-edit/src/node/view/mod.rs @@ -29,7 +29,7 @@ pub(crate) trait ViewOp<'a> { } } -pub trait ViewMutOp<'a> { +pub(crate) trait ViewMutOp<'a> { fn new(node: NodeGenericMut<'a>) -> Self; } @@ -415,6 +415,16 @@ impl Fdt { } } +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, From f114f635c04d97448a2aacf65c455c1008fb0e41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Tue, 10 Feb 2026 23:24:49 +0800 Subject: [PATCH 29/39] feat: Add root_mut method to Fdt for mutable access to the root node --- fdt-edit/src/fdt.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/fdt-edit/src/fdt.rs b/fdt-edit/src/fdt.rs index 5b481bf..b3526a5 100644 --- a/fdt-edit/src/fdt.rs +++ b/fdt-edit/src/fdt.rs @@ -366,6 +366,10 @@ impl Fdt { pub fn all_nodes(&self) -> impl Iterator> { self.iter_raw_nodes().map(|v| v.classify()) } + + pub fn root_mut(&mut self) -> NodeTypeMut<'_> { + self.view_typed_mut(self.root).unwrap() + } } /// Depth-first iterator over all node IDs in the tree. From 93c0d2a8e792783cb2eb10a118d7da07c3490aab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Tue, 10 Feb 2026 23:40:12 +0800 Subject: [PATCH 30/39] feat: Implement parent mapping in Fdt and update node structure for parent access --- fdt-edit/src/fdt.rs | 23 ++++++++++++++++------- fdt-edit/src/node/mod.rs | 3 --- fdt-edit/src/node/view/mod.rs | 6 +++--- 3 files changed, 19 insertions(+), 13 deletions(-) diff --git a/fdt-edit/src/fdt.rs b/fdt-edit/src/fdt.rs index b3526a5..b281b69 100644 --- a/fdt-edit/src/fdt.rs +++ b/fdt-edit/src/fdt.rs @@ -30,6 +30,8 @@ pub struct Fdt { pub memory_reservations: Vec, /// 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 @@ -54,6 +56,7 @@ impl Fdt { boot_cpuid_phys: 0, memory_reservations: Vec::new(), nodes, + parent_map: BTreeMap::new(), root: root_id, next_id: 1, phandle_cache: BTreeMap::new(), @@ -73,6 +76,11 @@ impl Fdt { self.root } + /// 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() + } + /// Returns a reference to the node with the given ID. pub fn node(&self, id: NodeId) -> Option<&Node> { self.nodes.get(&id) @@ -92,10 +100,10 @@ impl Fdt { /// /// 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, mut node: Node) -> NodeId { - node.parent = Some(parent); + 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); @@ -131,6 +139,8 @@ impl Fdt { /// 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); @@ -213,8 +223,8 @@ impl Fdt { break; } parts.push(&node.name); - match node.parent { - Some(p) => cur = p, + match self.parent_map.get(&cur) { + Some(&p) => cur = p, None => break, } } @@ -281,6 +291,7 @@ impl 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(), @@ -315,9 +326,7 @@ impl Fdt { if let Some(&(parent_id, _)) = id_stack.last() { // Set parent link - if let Some(n) = fdt.nodes.get_mut(&node_id) { - n.parent = Some(parent_id); - } + 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); diff --git a/fdt-edit/src/node/mod.rs b/fdt-edit/src/node/mod.rs index 68eed44..fbac607 100644 --- a/fdt-edit/src/node/mod.rs +++ b/fdt-edit/src/node/mod.rs @@ -16,8 +16,6 @@ pub(crate) mod view; pub struct Node { /// Node name (without path) pub name: String, - /// Parent node ID (None for root) - pub parent: Option, /// Property list (maintains original order) properties: Vec, /// Property name to index mapping (for fast lookup) @@ -34,7 +32,6 @@ impl Node { pub fn new(name: &str) -> Self { Self { name: name.into(), - parent: None, properties: Vec::new(), prop_cache: BTreeMap::new(), children: Vec::new(), diff --git a/fdt-edit/src/node/view/mod.rs b/fdt-edit/src/node/view/mod.rs index 4af92ab..c1ad0e0 100644 --- a/fdt-edit/src/node/view/mod.rs +++ b/fdt-edit/src/node/view/mod.rs @@ -96,14 +96,14 @@ impl<'a> NodeView<'a> { } pub fn parent(&self) -> Option> { - self.as_node() - .parent + 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.as_node().parent?; + 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) From 5fc9b37548c7f19b3a3f23b0d8862d8ecb0e3952 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Wed, 11 Feb 2026 00:01:31 +0800 Subject: [PATCH 31/39] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=20FDT=20?= =?UTF-8?q?=E7=BC=96=E7=A0=81=E6=A8=A1=E5=9D=97=E5=8F=8A=E7=9B=B8=E5=85=B3?= =?UTF-8?q?=E6=B5=8B=E8=AF=95=E7=94=A8=E4=BE=8B=EF=BC=8C=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E5=B0=86=20FDT=20=E7=BB=93=E6=9E=84=E5=BA=8F=E5=88=97=E5=8C=96?= =?UTF-8?q?=E4=B8=BA=20DTB=20=E6=A0=BC=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .claude/settings.local.json | 5 +- fdt-edit/src/encode.rs | 225 ++++++++++++++++++++++++++++++++++ fdt-edit/src/fdt.rs | 7 +- fdt-edit/src/lib.rs | 2 + fdt-edit/tests/encode.rs | 233 ++++++++++++++++++++++++++++++++++++ 5 files changed, 470 insertions(+), 2 deletions(-) create mode 100644 fdt-edit/src/encode.rs create mode 100644 fdt-edit/tests/encode.rs diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 4cc8fc6..b76feb2 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -12,7 +12,10 @@ "WebFetch(domain:www.devicetree.org)", "Bash(RUST_BACKTRACE=1 cargo test -p fdt-parser -- test_pci2 --nocapture)", "Bash(cargo check:*)", - "Bash(cargo clippy:*)" + "Bash(cargo clippy:*)", + "mcp__zread__get_repo_structure", + "Bash(find:*)", + "mcp__zread__read_file" ], "deny": [], "ask": [] diff --git a/fdt-edit/src/encode.rs b/fdt-edit/src/encode.rs new file mode 100644 index 0000000..a5329e0 --- /dev/null +++ b/fdt-edit/src/encode.rs @@ -0,0 +1,225 @@ +//! FDT 编码模块 +//! +//! 将 Fdt 结构序列化为 DTB 二进制格式 + +use alloc::{string::String, vec::Vec}; +use core::ops::Deref; + +use fdt_raw::{FDT_MAGIC, Token}; + +use crate::{Fdt, NodeId}; + +/// FDT 二进制数据 +#[derive(Clone, Debug)] +pub struct FdtData(Vec); + +impl FdtData { + /// 获取数据长度(字节) + pub fn len(&self) -> usize { + self.0.len() * 4 + } + + /// 数据是否为空 + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } +} + +impl Deref for FdtData { + type Target = [u8]; + + fn deref(&self) -> &Self::Target { + unsafe { + core::slice::from_raw_parts( + self.0.as_ptr() as *const u8, + self.0.len() * core::mem::size_of::(), + ) + } + } +} + +impl AsRef<[u8]> for FdtData { + fn as_ref(&self) -> &[u8] { + self + } +} + +/// FDT 编码器 +pub struct FdtEncoder<'a> { + fdt: &'a Fdt, + struct_data: Vec, + strings_data: Vec, + string_offsets: Vec<(String, u32)>, +} + +impl<'a> FdtEncoder<'a> { + /// 创建新的编码器 + pub fn new(fdt: &'a Fdt) -> Self { + Self { + fdt, + struct_data: Vec::new(), + strings_data: Vec::new(), + string_offsets: Vec::new(), + } + } + + /// 获取或添加字符串,返回偏移量 + fn get_or_add_string(&mut self, s: &str) -> u32 { + for (existing, offset) in &self.string_offsets { + if existing == s { + return *offset; + } + } + + let offset = self.strings_data.len() as u32; + self.strings_data.extend_from_slice(s.as_bytes()); + self.strings_data.push(0); // null terminator + self.string_offsets.push((s.into(), offset)); + offset + } + + /// 写入 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()); + + let name_bytes = name.as_bytes(); + let name_len = name_bytes.len() + 1; // +1 for null + let aligned_len = (name_len + 3) & !3; + + let mut name_buf = vec![0u8; aligned_len]; + name_buf[..name_bytes.len()].copy_from_slice(name_bytes); + + for chunk in name_buf.chunks(4) { + let word = u32::from_ne_bytes(chunk.try_into().unwrap()); + self.struct_data.push(word); + } + } + + /// 写入 END_NODE token + fn write_end_node(&mut self) { + let end_token: u32 = Token::EndNode.into(); + self.struct_data.push(end_token.to_be()); + } + + /// 写入属性 + fn write_property(&mut self, name: &str, data: &[u8]) { + let prop_token: u32 = Token::Prop.into(); + self.struct_data.push(prop_token.to_be()); + + self.struct_data.push((data.len() as u32).to_be()); + + let nameoff = self.get_or_add_string(name); + self.struct_data.push(nameoff.to_be()); + + if !data.is_empty() { + let aligned_len = (data.len() + 3) & !3; + let mut data_buf = vec![0u8; aligned_len]; + data_buf[..data.len()].copy_from_slice(data); + + for chunk in data_buf.chunks(4) { + let word = u32::from_ne_bytes(chunk.try_into().unwrap()); + self.struct_data.push(word); + } + } + } + + /// 执行编码 + pub fn encode(mut self) -> FdtData { + // 从根节点开始递归编码节点树 + self.encode_node(self.fdt.root_id()); + + // 添加 END token + let token: u32 = Token::End.into(); + self.struct_data.push(token.to_be()); + + self.finalize() + } + + /// 递归编码节点及其子节点(适配 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); + + // 写入所有属性 + for prop in node.properties() { + self.write_property(&prop.name, &prop.data); + } + + // 递归编码子节点 + for &child_id in node.children() { + self.encode_node(child_id); + } + + // 写入 END_NODE + self.write_end_node(); + } + + /// 生成最终 FDT 数据 + fn finalize(self) -> FdtData { + let memory_reservations = &self.fdt.memory_reservations; + let boot_cpuid_phys = self.fdt.boot_cpuid_phys; + + let header_size = 40u32; // 10 * 4 bytes + let mem_rsv_size = ((memory_reservations.len() + 1) * 16) as u32; + let struct_size = (self.struct_data.len() * 4) as u32; + let strings_size = self.strings_data.len() as u32; + + let off_mem_rsvmap = header_size; + let off_dt_struct = off_mem_rsvmap + mem_rsv_size; + let off_dt_strings = off_dt_struct + struct_size; + let totalsize = off_dt_strings + strings_size; + let totalsize_aligned = (totalsize + 3) & !3; + + let mut data = Vec::with_capacity(totalsize_aligned as usize / 4); + + // Header + data.push(FDT_MAGIC.to_be()); + data.push(totalsize_aligned.to_be()); + data.push(off_dt_struct.to_be()); + data.push(off_dt_strings.to_be()); + data.push(off_mem_rsvmap.to_be()); + data.push(17u32.to_be()); // version + data.push(16u32.to_be()); // last_comp_version + data.push(boot_cpuid_phys.to_be()); + data.push(strings_size.to_be()); + data.push(struct_size.to_be()); + + // Memory reservation block + for rsv in memory_reservations { + let addr_hi = (rsv.address >> 32) as u32; + let addr_lo = rsv.address as u32; + let size_hi = (rsv.size >> 32) as u32; + let size_lo = rsv.size as u32; + data.push(addr_hi.to_be()); + data.push(addr_lo.to_be()); + data.push(size_hi.to_be()); + data.push(size_lo.to_be()); + } + // Terminator + data.push(0); + data.push(0); + data.push(0); + data.push(0); + + // Struct block + data.extend_from_slice(&self.struct_data); + + // Strings block + let strings_aligned_len = (self.strings_data.len() + 3) & !3; + let mut strings_buf = vec![0u8; strings_aligned_len]; + strings_buf[..self.strings_data.len()].copy_from_slice(&self.strings_data); + + for chunk in strings_buf.chunks(4) { + let word = u32::from_ne_bytes(chunk.try_into().unwrap()); + data.push(word); + } + + FdtData(data) + } +} diff --git a/fdt-edit/src/fdt.rs b/fdt-edit/src/fdt.rs index b281b69..8924959 100644 --- a/fdt-edit/src/fdt.rs +++ b/fdt-edit/src/fdt.rs @@ -13,7 +13,7 @@ use alloc::{ vec::Vec, }; -use crate::{FdtError, Node, NodeId, NodeType, NodeTypeMut, NodeView, Phandle}; +use crate::{FdtData, FdtEncoder, FdtError, Node, NodeId, NodeType, NodeTypeMut, NodeView, Phandle}; pub use fdt_raw::MemoryReservation; @@ -379,6 +379,11 @@ impl Fdt { pub fn root_mut(&mut self) -> NodeTypeMut<'_> { self.view_typed_mut(self.root).unwrap() } + + /// Encodes the FDT to DTB binary format. + pub fn encode(&self) -> FdtData { + FdtEncoder::new(self).encode() + } } /// Depth-first iterator over all node IDs in the tree. diff --git a/fdt-edit/src/lib.rs b/fdt-edit/src/lib.rs index 58f0b4f..932075a 100644 --- a/fdt-edit/src/lib.rs +++ b/fdt-edit/src/lib.rs @@ -3,6 +3,7 @@ #[macro_use] extern crate alloc; +mod encode; mod fdt; mod node; mod prop; @@ -12,6 +13,7 @@ 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::*; diff --git a/fdt-edit/tests/encode.rs b/fdt-edit/tests/encode.rs new file mode 100644 index 0000000..96be699 --- /dev/null +++ b/fdt-edit/tests/encode.rs @@ -0,0 +1,233 @@ +//! 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"]); +} From c36b5a2453d20980f72eaec8e21abfcd967e2ab5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Wed, 11 Feb 2026 15:27:20 +0800 Subject: [PATCH 32/39] =?UTF-8?q?feat:=20=E4=BF=AE=E6=94=B9=20find=5Fchild?= =?UTF-8?q?ren=5Fby=5Fpath=20=E6=96=B9=E6=B3=95=E4=BB=A5=E8=BF=94=E5=9B=9E?= =?UTF-8?q?=20ChildrenIter=EF=BC=8C=E4=BC=98=E5=8C=96=E8=B7=AF=E5=BE=84?= =?UTF-8?q?=E5=A4=84=E7=90=86=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fdt-raw/src/fdt.rs | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/fdt-raw/src/fdt.rs b/fdt-raw/src/fdt.rs index 9d0c121..118c07c 100644 --- a/fdt-raw/src/fdt.rs +++ b/fdt-raw/src/fdt.rs @@ -183,8 +183,14 @@ impl<'a> Fdt<'a> { /// } /// } /// ``` - pub fn find_children_by_path(&self, path: &str) -> Option> + 'a> { - let path = self.normalize_path(path)?; + 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(); @@ -205,16 +211,20 @@ impl<'a> Fdt<'a> { } } if !found { - return None; + return ChildrenIter { + node_iter: self.all_nodes(), + child_level: 0, + done: true, + }; } } let child_level = target_level + 1; - Some(ChildrenIter { + ChildrenIter { node_iter: iter, child_level, done: false, - }) + } } /// Resolve an alias to its full path. @@ -441,7 +451,7 @@ impl<'a> Iterator for ReservedMemoryIter<'a> { /// 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). -struct ChildrenIter<'a> { +pub struct ChildrenIter<'a> { node_iter: FdtIter<'a>, child_level: usize, done: bool, From 32b0fc62069612278bc7588522964bd5caf62888 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Fri, 13 Feb 2026 22:53:02 +0800 Subject: [PATCH 33/39] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=20find=5Fchild?= =?UTF-8?q?ren=5Fby=5Fpath=20=E6=96=B9=E6=B3=95=EF=BC=8C=E6=94=B9=E8=BF=9B?= =?UTF-8?q?=E8=BF=94=E5=9B=9E=E5=80=BC=E5=A4=84=E7=90=86=E5=92=8C=E6=97=A5?= =?UTF-8?q?=E5=BF=97=E8=AE=B0=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fdt-raw/tests/node.rs | 56 ++++++++++++++++++++----------------------- 1 file changed, 26 insertions(+), 30 deletions(-) diff --git a/fdt-raw/tests/node.rs b/fdt-raw/tests/node.rs index 41353e3..343c2db 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); + } } } @@ -743,7 +743,7 @@ fn test_find_children_by_path_root() { let fdt = Fdt::from_bytes(&raw).unwrap(); // Root "/" should have children - let children: Vec<_> = fdt.find_children_by_path("/").unwrap().collect(); + let children: Vec<_> = fdt.find_children_by_path("/").collect(); assert!(!children.is_empty(), "Root should have children"); // All children should be at level 1 @@ -786,18 +786,16 @@ fn test_find_children_by_path_nonroot() { for node in fdt.all_nodes() { if node.level() == 1 { let path = node.path(); - if let Some(children_iter) = fdt.find_children_by_path(path.as_str()) { - let children: Vec<_> = children_iter.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; - } + 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; } } } @@ -815,10 +813,10 @@ fn test_find_children_by_path_leaf() { let fdt = Fdt::from_bytes(&raw).unwrap(); // "chosen" node typically has no child nodes - let children: Vec<_> = fdt.find_children_by_path("/chosen").unwrap().collect(); + let children: Vec<_> = fdt.find_children_by_path("/chosen").collect(); info!( "Children of /chosen: {:?}", - children.iter().map(|n| n.name()).collect::>() + children.iter().map(|n: &fdt_raw::Node| n.name()).collect::>() ); // Even if it has children, verify they are all at the correct level @@ -835,9 +833,9 @@ fn test_find_children_by_path_nonexistent() { let raw = fdt_qemu(); let fdt = Fdt::from_bytes(&raw).unwrap(); - // Non-existent path should return None - let result = fdt.find_children_by_path("/nonexistent/path"); - assert!(result.is_none(), "Non-existent path should return None"); + // 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] @@ -848,7 +846,7 @@ fn test_find_children_by_path_no_grandchildren() { // Verify that find_children_by_path returns only direct children, // not grandchildren or deeper descendants - let root_children: Vec<_> = fdt.find_children_by_path("/").unwrap().collect(); + 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(); @@ -895,8 +893,7 @@ fn test_find_children_by_path_consistency() { // Collect direct children from all_nodes let mut expected_children: Vec<&str> = Vec::new(); - for j in (i + 1)..all_nodes.len() { - let child = &all_nodes[j]; + 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 { @@ -908,8 +905,7 @@ fn test_find_children_by_path_consistency() { // Collect direct children from find_children_by_path let actual_children: Vec = fdt .find_children_by_path(path.as_str()) - .unwrap() - .map(|n| n.name().to_string()) + .map(|n: fdt_raw::Node| n.name().to_string()) .collect(); assert_eq!( From d3ce51db9fe36452ce138e960a58db5f741e72b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Fri, 13 Feb 2026 23:20:40 +0800 Subject: [PATCH 34/39] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=20FDT=20?= =?UTF-8?q?=E9=87=8D=E5=BB=BA=E6=B5=8B=E8=AF=95=EF=BC=8C=E9=AA=8C=E8=AF=81?= =?UTF-8?q?=E8=A7=A3=E6=9E=90=E5=92=8C=E7=BC=96=E7=A0=81=E7=9A=84=E6=AD=A3?= =?UTF-8?q?=E7=A1=AE=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fdt-edit/tests/rebuild.rs | 125 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 fdt-edit/tests/rebuild.rs diff --git a/fdt-edit/tests/rebuild.rs b/fdt-edit/tests/rebuild.rs new file mode 100644 index 0000000..d2df2e4 --- /dev/null +++ b/fdt-edit/tests/rebuild.rs @@ -0,0 +1,125 @@ +//! 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); +} From 00c81fb0176395d4821c3d88f2e6dc147d141a8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Fri, 13 Feb 2026 23:50:32 +0800 Subject: [PATCH 35/39] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E6=97=B6?= =?UTF-8?q?=E9=92=9F=E5=92=8CPCI=E8=8A=82=E7=82=B9=E8=A7=86=E5=9B=BE?= =?UTF-8?q?=E6=A8=A1=E5=9D=97=E5=8F=8A=E7=9B=B8=E5=85=B3=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=EF=BC=8C=E5=A2=9E=E5=BC=BA=E8=AE=BE=E5=A4=87=E6=A0=91=E8=A7=A3?= =?UTF-8?q?=E6=9E=90=E8=83=BD=E5=8A=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fdt-edit/src/fdt.rs | 22 ++ fdt-edit/src/node/mod.rs | 10 + fdt-edit/src/node/view/clock.rs | 230 ++++++++++++++++ fdt-edit/src/node/view/mod.rs | 26 ++ fdt-edit/src/node/view/pci.rs | 454 ++++++++++++++++++++++++++++++++ fdt-edit/tests/clock.rs | 84 ++++++ fdt-edit/tests/fdt.rs | 14 + fdt-edit/tests/pci.rs | 193 ++++++++++++++ 8 files changed, 1033 insertions(+) create mode 100644 fdt-edit/src/node/view/clock.rs create mode 100644 fdt-edit/src/node/view/pci.rs create mode 100644 fdt-edit/tests/clock.rs create mode 100644 fdt-edit/tests/pci.rs diff --git a/fdt-edit/src/fdt.rs b/fdt-edit/src/fdt.rs index 8924959..cde2f70 100644 --- a/fdt-edit/src/fdt.rs +++ b/fdt-edit/src/fdt.rs @@ -380,6 +380,28 @@ impl Fdt { self.view_typed_mut(self.root).unwrap() } + /// Finds nodes with matching compatible strings. + pub fn find_compatible(&self, compatible: &[&str]) -> Vec> { + let mut results = Vec::new(); + for node_ref in self.all_nodes() { + let compatibles = node_ref.as_node().compatibles(); + let mut found = false; + + for comp in compatibles { + if compatible.contains(&comp) { + results.push(node_ref); + found = true; + break; + } + } + + if found { + continue; + } + } + results + } + /// Encodes the FDT to DTB binary format. pub fn encode(&self) -> FdtData { FdtEncoder::new(self).encode() diff --git a/fdt-edit/src/node/mod.rs b/fdt-edit/src/node/mod.rs index fbac607..8f45f68 100644 --- a/fdt-edit/src/node/mod.rs +++ b/fdt-edit/src/node/mod.rs @@ -264,6 +264,16 @@ impl Node { 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 { 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/mod.rs b/fdt-edit/src/node/view/mod.rs index c1ad0e0..e8dc169 100644 --- a/fdt-edit/src/node/view/mod.rs +++ b/fdt-edit/src/node/view/mod.rs @@ -5,9 +5,11 @@ //! 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; @@ -17,9 +19,11 @@ 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> { @@ -257,6 +261,14 @@ impl<'a> NodeView<'a> { } 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); } @@ -269,6 +281,14 @@ impl<'a> NodeView<'a> { } 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); } @@ -318,10 +338,14 @@ impl core::fmt::Display for NodeView<'_> { #[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>), } @@ -370,8 +394,10 @@ impl core::fmt::Display for NodeType<'_> { /// 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>), } diff --git a/fdt-edit/src/node/view/pci.rs b/fdt-edit/src/node/view/pci.rs new file mode 100644 index 0000000..db69095 --- /dev/null +++ b/fdt-edit/src/node/view/pci.rs @@ -0,0 +1,454 @@ +//! PCI node view specialization. + +use core::ops::{Deref, Range}; + +use alloc::vec::Vec; +use fdt_raw::{FdtError, Phandle}; + +use super::NodeView; +use crate::{NodeGeneric, NodeGenericMut, Property, ViewMutOp, ViewOp}; + +// --------------------------------------------------------------------------- +// PCI types +// --------------------------------------------------------------------------- + +/// PCI address space types. +#[derive(Clone, Debug, PartialEq)] +pub enum PciSpace { + /// I/O space + IO, + /// 32-bit memory space + Memory32, + /// 64-bit memory space + Memory64, +} + +/// PCI address range entry. +/// +/// Represents a range of addresses in PCI address space with mapping to CPU address space. +#[derive(Clone, Debug, PartialEq)] +pub struct PciRange { + /// The PCI address space type + pub space: PciSpace, + /// Address on the PCI bus + pub bus_address: u64, + /// Address in CPU physical address space + pub cpu_address: u64, + /// Size of the range in bytes + pub size: u64, + /// Whether the memory region is prefetchable + pub prefetchable: bool, +} + +/// PCI interrupt mapping entry. +/// +/// Represents a mapping from PCI device interrupts to parent interrupt controller inputs. +#[derive(Clone, Debug)] +pub struct PciInterruptMap { + /// Child device address (masked) + pub child_address: Vec, + /// Child device IRQ (masked) + pub child_irq: Vec, + /// Phandle of the interrupt parent controller + pub interrupt_parent: Phandle, + /// Parent controller IRQ inputs + pub parent_irq: Vec, +} + +/// PCI interrupt information. +/// +/// Contains the resolved interrupt information for a PCI device. +#[derive(Clone, Debug, PartialEq)] +pub struct PciInterruptInfo { + /// List of IRQ numbers + pub irqs: Vec, +} + +// --------------------------------------------------------------------------- +// PciNodeView +// --------------------------------------------------------------------------- + +/// Specialized view for PCI bridge nodes. +#[derive(Clone, Copy)] +pub struct PciNodeView<'a> { + pub(super) inner: NodeGeneric<'a>, +} + +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 { + None + } + } + + /// Returns the `#interrupt-cells` property value. + /// + /// Defaults to 1 for PCI devices if not specified. + pub fn interrupt_cells(&self) -> u32 { + self.as_view() + .as_node() + .get_property("#interrupt-cells") + .and_then(|prop| prop.get_u32()) + .unwrap_or(1) + } + + /// 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. + pub fn bus_range(&self) -> Option> { + 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) + }) + } + + /// 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. + pub fn ranges(&self) -> Option> { + 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 + + // 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 as 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) + // 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 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 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); + + ranges.push(PciRange { + space, + bus_address, + cpu_address: parent_addr, + size, + prefetchable, + }); + } + + Some(ranges) + } + + /// 解析 interrupt-map 属性 + pub fn interrupt_map(&self) -> Result, FdtError> { + let prop = self + .as_view() + .as_node() + .get_property("interrupt-map") + .ok_or(FdtError::NotFound)?; + + // 将 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(); + + // 计算每个条目的大小 + // 格式: + let child_addr_cells = self.as_view().address_cells().unwrap_or(3) as usize; + let child_irq_cells = self.interrupt_cells() as usize; + + loop { + // 解析子地址 + 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), // 数据结束 + } + } + + // 解析子 IRQ + let mut child_irq = Vec::with_capacity(child_irq_cells); + for _ in 0..child_irq_cells { + match data.read_u32() { + Some(v) => child_irq.push(v), + None => return Ok(mappings), + } + } + + // 解析中断父 phandle + let interrupt_parent_raw = match data.read_u32() { + Some(v) => v, + None => return Ok(mappings), + }; + let interrupt_parent = Phandle::from(interrupt_parent_raw); + + // 通过 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.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; + (addr_cells, irq_cells) + } else { + // 默认值:address_cells=0, interrupt_cells=3 (GIC 格式) + (0, 3) + }; + + // 跳过父地址 cells + for _ in 0..parent_addr_cells { + if data.read_u32().is_none() { + return Ok(mappings); + } + } + + // 解析父 IRQ + let mut parent_irq = Vec::with_capacity(parent_irq_cells); + for _ in 0..parent_irq_cells { + match data.read_u32() { + Some(v) => parent_irq.push(v), + None => return Ok(mappings), + } + } + + // 应用 mask 到子地址和 IRQ + let masked_address: Vec = child_address + .iter() + .enumerate() + .map(|(i, value)| { + let mask_value = mask.get(i).copied().unwrap_or(0xffff_ffff); + value & mask_value + }) + .collect(); + let masked_irq: Vec = child_irq + .iter() + .enumerate() + .map(|(i, value)| { + let mask_value = mask + .get(child_addr_cells + i) + .copied() + .unwrap_or(0xffff_ffff); + value & mask_value + }) + .collect(); + + mappings.push(PciInterruptMap { + child_address: masked_address, + child_irq: masked_irq, + interrupt_parent, + parent_irq, + }); + } + } + + /// 获取 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(0).take(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(0).take(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 PciNodeViewMut<'a> { + type Target = NodeGenericMut<'a>; + + fn deref(&self) -> &Self::Target { + &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/tests/clock.rs b/fdt-edit/tests/clock.rs new file mode 100644 index 0000000..ec4971d --- /dev/null +++ b/fdt-edit/tests/clock.rs @@ -0,0 +1,84 @@ +//! Clock node view tests. + +use dtb_file::*; +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!("Total clock nodes: {}", clock_count); + // 飞腾 DTB 应该有时钟节点 + assert!(clock_count > 0, "phytium DTB should have clock nodes"); +} + +#[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_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" + ); + + if let Some(ref name) = fixed.name { + println!(" Name: {}", name); + } + + if let Some(accuracy) = fixed.accuracy { + println!(" Accuracy: {} ppb", accuracy); + } + } + } + } +} diff --git a/fdt-edit/tests/fdt.rs b/fdt-edit/tests/fdt.rs index a136eed..2c9658e 100644 --- a/fdt-edit/tests/fdt.rs +++ b/fdt-edit/tests/fdt.rs @@ -25,6 +25,20 @@ fn test_node_classify() { 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(); diff --git a/fdt-edit/tests/pci.rs b/fdt-edit/tests/pci.rs new file mode 100644 index 0000000..49e8710 --- /dev/null +++ b/fdt-edit/tests/pci.rs @@ -0,0 +1,193 @@ +//! PCI node view tests. + +use std::sync::Once; + +use dtb_file::*; +use fdt_edit::{Fdt, NodeType, PciSpace, PciRange}; + +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() + ); + } + } + + 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"); + } + } + } +} + +#[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 + ); + } + } + } +} + +#[test] +fn test_pci_interrupt_map_mask() { + 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(mask) = pci.interrupt_map_mask() { + println!("PCI {} interrupt-map-mask: {:?}", pci.path(), mask); + assert!(!mask.is_empty(), "interrupt-map-mask should not be empty"); + } + } + } +} + +#[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(); + + let NodeType::Pci(pci) = node_ref 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() { + 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 NodeType::Pci(pci) = node_ref 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); + } +} From 41ca1e9ffd3a0a194d0e87fbfb70fdced3bb3bee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Sat, 14 Feb 2026 00:31:04 +0800 Subject: [PATCH 36/39] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=E6=A0=BC=E5=BC=8F=EF=BC=8C=E8=B0=83=E6=95=B4=E5=A4=9A?= =?UTF-8?q?=E4=B8=AA=E6=B5=8B=E8=AF=95=E6=96=87=E4=BB=B6=E4=B8=AD=E7=9A=84?= =?UTF-8?q?=E6=89=93=E5=8D=B0=E8=AF=AD=E5=8F=A5=E5=92=8C=E5=B1=9E=E6=80=A7?= =?UTF-8?q?=E8=AE=BE=E7=BD=AE=E7=9A=84=E6=A0=BC=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fdt-edit/src/fdt.rs | 4 ++- fdt-edit/tests/clock.rs | 12 ++------ fdt-edit/tests/encode.rs | 61 ++++++++++++++++++++++++++++++--------- fdt-edit/tests/pci.rs | 14 ++++----- fdt-edit/tests/range.rs | 10 +++++-- fdt-edit/tests/rebuild.rs | 8 +++-- fdt-raw/tests/node.rs | 10 +++++-- 7 files changed, 81 insertions(+), 38 deletions(-) diff --git a/fdt-edit/src/fdt.rs b/fdt-edit/src/fdt.rs index cde2f70..e18b350 100644 --- a/fdt-edit/src/fdt.rs +++ b/fdt-edit/src/fdt.rs @@ -13,7 +13,9 @@ use alloc::{ vec::Vec, }; -use crate::{FdtData, FdtEncoder, FdtError, Node, NodeId, NodeType, NodeTypeMut, NodeView, Phandle}; +use crate::{ + FdtData, FdtEncoder, FdtError, Node, NodeId, NodeType, NodeTypeMut, NodeView, Phandle, +}; pub use fdt_raw::MemoryReservation; diff --git a/fdt-edit/tests/clock.rs b/fdt-edit/tests/clock.rs index ec4971d..f5575c8 100644 --- a/fdt-edit/tests/clock.rs +++ b/fdt-edit/tests/clock.rs @@ -34,11 +34,7 @@ fn test_clock_output_names() { if let NodeType::Clock(clock) = node { let names = clock.clock_output_names(); if !names.is_empty() { - println!( - "Clock {} has output names: {:?}", - clock.path(), - names - ); + println!("Clock {} has output names: {:?}", clock.path(), names); // Test output_name method if let Some(first_name) = clock.output_name(0) { @@ -59,11 +55,7 @@ fn test_fixed_clock() { 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 - ); + println!("Fixed clock: {} freq={}Hz", clock.path(), fixed.frequency); // Fixed clock should have a frequency assert!( diff --git a/fdt-edit/tests/encode.rs b/fdt-edit/tests/encode.rs index 96be699..6e71ea0 100644 --- a/fdt-edit/tests/encode.rs +++ b/fdt-edit/tests/encode.rs @@ -28,8 +28,14 @@ fn test_encode_with_properties() { // 添加一些属性到根节点 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( + "#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); @@ -46,7 +52,10 @@ fn test_encode_with_properties() { // 验证属性 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")); + assert_eq!( + node_ref.get_property("model").unwrap().as_str(), + Some("Test Device") + ); } /// 测试带有子节点的 FDT 编码 @@ -57,8 +66,14 @@ fn test_encode_with_children() { // 添加子节点 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])); + 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"); @@ -104,8 +119,15 @@ fn test_parse_and_encode() { 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!( + 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); } @@ -114,7 +136,11 @@ fn test_parse_and_encode() { 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); + assert!( + reparsed_id.is_some(), + "path {} not found in reparsed FDT", + path + ); } } @@ -167,7 +193,10 @@ fn test_encode_with_reserve_dtb() { let reparsed = Fdt::from_bytes(&encoded).unwrap(); // 验证保留区被正确编码 - assert_eq!(original.memory_reservations.len(), reparsed.memory_reservations.len()); + assert_eq!( + original.memory_reservations.len(), + reparsed.memory_reservations.len() + ); } /// 测试节点属性完整性 @@ -180,10 +209,16 @@ fn test_encode_properties_integrity() { let node = fdt.node_mut(root_id).unwrap(); // u32 属性 - node.set_property(crate::Property::new("prop-u32", 0x12345678u32.to_be_bytes().to_vec())); + 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-u64", + 0x1234567890ABCDEFu64.to_be_bytes().to_vec(), + )); // 字符串属性 node.set_property(crate::Property::new("prop-string", { @@ -201,8 +236,8 @@ fn test_encode_properties_integrity() { // reg 属性 { let v = vec![ - 0x00, 0x10, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, - 0x00, 0x30, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, + 0x00, 0x10, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x40, + 0x00, 0x00, ]; node.set_property(crate::Property::new("reg", v)); } diff --git a/fdt-edit/tests/pci.rs b/fdt-edit/tests/pci.rs index 49e8710..cc0d573 100644 --- a/fdt-edit/tests/pci.rs +++ b/fdt-edit/tests/pci.rs @@ -3,7 +3,7 @@ use std::sync::Once; use dtb_file::*; -use fdt_edit::{Fdt, NodeType, PciSpace, PciRange}; +use fdt_edit::{Fdt, NodeType, PciRange, PciSpace}; fn init_logging() { static INIT: Once = Once::new(); @@ -45,11 +45,7 @@ fn test_pci_ranges() { 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() - ); + println!("PCI {} has {} ranges:", pci.path(), ranges.len()); for range in &ranges { let space_name = match range.space { @@ -60,7 +56,11 @@ fn test_pci_ranges() { println!( " {}: bus={:#x} cpu={:#x} size={:#x} prefetch={}", - space_name, range.bus_address, range.cpu_address, range.size, range.prefetchable + space_name, + range.bus_address, + range.cpu_address, + range.size, + range.prefetchable ); } diff --git a/fdt-edit/tests/range.rs b/fdt-edit/tests/range.rs index dd0da56..9c10277 100644 --- a/fdt-edit/tests/range.rs +++ b/fdt-edit/tests/range.rs @@ -15,7 +15,10 @@ fn test_reg_address_translation() { 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.child_bus_address, 0x7e215040, + "bus address should be 0x7e215040" + ); assert_eq!(reg.size, Some(0x40), "size should be 0x40"); } @@ -74,6 +77,9 @@ fn test_set_regs_roundtrip() { }; assert_eq!(roundtrip_reg.address, original_reg.address); - assert_eq!(roundtrip_reg.child_bus_address, original_reg.child_bus_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 index d2df2e4..e3e8cae 100644 --- a/fdt-edit/tests/rebuild.rs +++ b/fdt-edit/tests/rebuild.rs @@ -59,8 +59,8 @@ fn test_rebuild_single(case: &DtbTestCase) { // 1. 获取原始 DTB 数据 let raw_data = (case.loader)(); - let original = Fdt::from_bytes(&raw_data) - .unwrap_or_else(|_| panic!("Failed to parse {}", case.name)); + let original = + Fdt::from_bytes(&raw_data).unwrap_or_else(|_| panic!("Failed to parse {}", case.name)); // 2. 编码 let encoded = original.encode(); @@ -118,7 +118,9 @@ fn test_rebuild_single(case: &DtbTestCase) { assert!( diff_status.success(), "DTS files differ for {}: run 'diff {} {}' to see details", - case.name, orig_dts_path, enc_dts_path + case.name, + orig_dts_path, + enc_dts_path ); println!("Rebuild test PASSED: {}", case.name); diff --git a/fdt-raw/tests/node.rs b/fdt-raw/tests/node.rs index 343c2db..6319aaa 100644 --- a/fdt-raw/tests/node.rs +++ b/fdt-raw/tests/node.rs @@ -816,7 +816,10 @@ fn test_find_children_by_path_leaf() { let children: Vec<_> = fdt.find_children_by_path("/chosen").collect(); info!( "Children of /chosen: {:?}", - children.iter().map(|n: &fdt_raw::Node| n.name()).collect::>() + children + .iter() + .map(|n: &fdt_raw::Node| n.name()) + .collect::>() ); // Even if it has children, verify they are all at the correct level @@ -835,7 +838,10 @@ fn test_find_children_by_path_nonexistent() { // 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"); + assert!( + result.is_empty(), + "Non-existent path should return empty iterator" + ); } #[test] From 5c59d34ee285ea8f18c965871fbc90946b6aa3cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Sat, 14 Feb 2026 00:33:32 +0800 Subject: [PATCH 37/39] =?UTF-8?q?feat:=20=E7=AE=80=E5=8C=96=20Clippy=20?= =?UTF-8?q?=E5=92=8C=E6=9E=84=E5=BB=BA=E5=91=BD=E4=BB=A4=EF=BC=8C=E7=A7=BB?= =?UTF-8?q?=E9=99=A4=E4=B8=8D=E5=BF=85=E8=A6=81=E7=9A=84=20manifest-path?= =?UTF-8?q?=20=E5=92=8C=E5=8C=85=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/check.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index a92cb82..30956c9 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -23,6 +23,6 @@ jobs: - 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 --target ${{ matrix.targets }} --all-features -- -A clippy::new_without_default - name: Build - run: cargo build -p fdt-parser --target ${{ matrix.targets }} --all-features + run: cargo build --target ${{ matrix.targets }} --all-features From 7ae499e57e3d55fc522e5f0b1f1ea08978375760 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Sat, 14 Feb 2026 00:33:45 +0800 Subject: [PATCH 38/39] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=20PciNodeView?= =?UTF-8?q?=20=E4=B8=AD=E7=9A=84=E5=9C=B0=E5=9D=80=E5=92=8C=E4=B8=AD?= =?UTF-8?q?=E6=96=AD=E5=A1=AB=E5=85=85=E9=80=BB=E8=BE=91=EF=BC=8C=E4=BD=BF?= =?UTF-8?q?=E7=94=A8=E6=9B=B4=E6=B8=85=E6=99=B0=E7=9A=84=E5=87=BD=E6=95=B0?= =?UTF-8?q?=E6=9B=BF=E4=BB=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fdt-edit/src/node/view/pci.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/fdt-edit/src/node/view/pci.rs b/fdt-edit/src/node/view/pci.rs index db69095..2b7cbc5 100644 --- a/fdt-edit/src/node/view/pci.rs +++ b/fdt-edit/src/node/view/pci.rs @@ -165,7 +165,7 @@ impl<'a> PciNodeView<'a> { let parent_addr_cells = if let Some(parent) = self.as_view().parent() { parent.as_view().address_cells().unwrap_or(2) as usize } else { - 2 as usize + 2_usize }; let size_cells = self.as_view().size_cells().unwrap_or(2) as usize; @@ -361,7 +361,7 @@ impl<'a> PciNodeView<'a> { // 如果 encoded_address 比 child_addr_cells 短,填充 0 let remaining = child_addr_cells.saturating_sub(encoded_address.len()); - masked_child_address.extend(core::iter::repeat(0).take(remaining)); + 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); @@ -377,7 +377,7 @@ impl<'a> PciNodeView<'a> { // 如果 encoded_irq 比 child_irq_cells 短,填充 0 let remaining_irq = child_irq_cells.saturating_sub(encoded_irq.len()); - masked_child_irq.extend(core::iter::repeat(0).take(remaining_irq)); + masked_child_irq.extend(core::iter::repeat_n(0, remaining_irq)); // 在 interrupt-map 中查找匹配的条目 for mapping in &interrupt_map { From e67e88a53e6e7debf6736f273aca700de51cd929 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Sat, 14 Feb 2026 00:36:48 +0800 Subject: [PATCH 39/39] =?UTF-8?q?feat:=20=E7=A7=BB=E9=99=A4=20CI=20?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E4=B8=AD=E7=9A=84=E7=9B=AE=E6=A0=87=E6=9E=B6?= =?UTF-8?q?=E6=9E=84=EF=BC=8C=E7=AE=80=E5=8C=96=E6=9E=84=E5=BB=BA=E5=92=8C?= =?UTF-8?q?=E6=A3=80=E6=9F=A5=E6=AD=A5=E9=AA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/check.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 30956c9..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 --target ${{ matrix.targets }} --all-features -- -A clippy::new_without_default + run: cargo clippy --all-features - name: Build - run: cargo build --target ${{ matrix.targets }} --all-features + run: cargo build --all-features

+(f*e@w3* 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%0DD*eccRWqJ zal}+w169{6@ac;7ZBc&e+&)W9Pc!lgF~P^04^}Rrrr@N3-5IKu#o@#%UP;DflKyeX9L{Ik_kTOMuINk9=HtGH*M<22*3U^d@&lpTJGf> z4MLiCQAT?6n97KcM^5i@(w7H1^`CFTjToR{-yDkTJ!SKs-lI39i}pB^)0T}-xGUI)@IksN>)*;2{c}$+>{T%j`&-L-xvn{L-^%c9)w* z>nWoDr7&$a%rs^(DVPj_4&ho5?!okow@)JeSCo%xJIU^Vb74Rcr}k^x@jF(r+Au9} zT6v^T<4e^>lI?MqOMHHuKr4Zu1ADEXAY}crrw_qoO?DMI=nZHNb07G_NHR;8j;e}JH;JBoYWPP zjwc=UUthT6pMj!8nw7&Ko^0ZFI6O6}=z&HY~Wwg*~F}v%H+Ij?!l5ji#E@ zjmR6FMB_rlQC)?goLJm&ucc64?GYyV%oIaNZ94M``BD6i24$r6)6ASSFPgWJuBrr03MJ@c5Yr4jr1|vmU=Jp&&W*9!(^T6rWAO-ii!1*+-$TYYMWGl>CIIjXcM1Q zhv~j55%DCm?1pvht}JSA)c(@B0Sfs7*Hf=hu&PlNkK~702%@oRuCnzM>Cxt8;N|x$%A;NJlsC7yj zX3fY)rBCbc{mBYppwOV{AY5&K7T68K5%!=2ogeS(P-*POSl1oz89V68V2xFqG8E%n zUpUz8EH-_pw3ao%b6`APuAe|3A29hqlr$w8jkNc-uOkHaK>*Nx)1oR6)fT;>C)2EQ%2H92g-Lp z!cRXe`&qjaYaM#MJN&$N_|&QHa7E4ou#?At(Srv+*gG8fs&3VSxnFbMo>8Yq_fnyI z9G`2lmzlBDK&D^V{-bC=G@c~ek6oc+Ds4m$fdLxdRO_kF(@4`#)nWPq)CasdFA<1#OF*dLj<-WR?H(QCqG6#WXxNcNu%h6f^!X6|fR)uM_; zAhOw1PMvNtndV|EK7^9JzX$lV+MG6A_Sa%93nz<2sen)Y++Tsi^W_{&duh?fJva8y zb(0lzQz#7+R@a(sXnWmN?(_x-1d|-~%Q07;YPVw{6F07xSw${xn|ihh9F=`qj>Yn| zd;iW+*eVZA2Nol+xyUT%X_)@1tB+<r@>5*Mg=rdLDkW5GaY^N$C|9z;tW0|{_%$R zG!J#7sd`Owlsk|Y)rFf+(ZXdPMDr@LkHk+?`!CcnMJ@1dSMgxFFge4~J-JVVwkbP! zZTKeG84Yl2aA$a+5>`pmTHK}sJ1N)sR-j!DRQr)8`<#xU)IU=HK)k8lot$F%?jOTd z-n{$AMhR5jRyE>HZ4*ntTa3z^kG_y({~mO-xw*NT$KH<`mS1g8bCAbP;MGt%(cwf7 zGF-VHC563*(vWVC&Pz|nq&^EDf4#mFUsJi zSw}`P6P)?CwED?yM zm4U%nIw9i_9WQqr4~)S;SPUyn#^`wH6XK2N9I>h`LP6g!*ifR^M-Le^D9&hzijMWr zccG&e^atXb;y@fLiY4(3k_X!3#0B+(Okx*j*Mhy zq{UjSw_GlIj@DXeM1avEi9ePO=%N7nSiNbmVNh(8-e5#o;!>19sd?S&E&qX7(~ORe z5{IAB6QLFjCei|?&F6w zw1U`W>WH0Yk&#pyC?)3@BEyDURr#t zsD0adbNia;20icHc%g9h>4Afjx`L{4;d0O7@4WEz1p?uH@FUM<_3BVy&Qxa;muA* zbd)q~jbtnK(nm&$??+Hyi`igGqrLc~VE-R`Zvq%sRsD~@i7W*XB9wg#6|gJ~@4cCM zGmov4ZnRKJXiHlmklEYPGznR{07BR+vV=_v3PcvIiW)=&5g`f)vPjq$1p_D`E<`~< zME!lvz4zR?Gs$}?OM>73-%&a<`OLlV-gECg_ndRjITwZX^kS0B5fSrCcrwzOfI}nw zTzCbG0yMd!wCRk*zdR5-p3CR`T&`H~iQ1>Ae)L?Z#w#i3KSr&#mLiaAYp3#c0FnWh z@nHlAwbj)+ATz3x|>=m_;;!qH)(w`Zkt!G@p;r`Z`h7nbvDMVXo<6xXXuI+HNunsjTRF zg`yYB3U-aWtdgJfZ(q6QlI;2FPotPD3g+@;m=pCP<7PZ3pGY``M824jYl-*D-YKds zFE08a=LC7(*49oDaWA;}M4S;1<<Iq|^3zwgb{&jC>88 zM0BP&LN&-|{Ia%I{Is@~ek&y;lF(uJZvp-zDQiC5Zp213 zm4__&nOx6d3&cEfiJXng3acq-jaGr3k2=L-5^{l8j75o^3wa4|Lu|4v8wcsaw63Y# zc9?|BI}GDeV{?XL1}QaG$+KvN8bb>Tdr4m)i3dVVvQD}1I}RNpka~t;MXRyzAsPp}ikqEm3=PNv{xiXP30De4*5 z9IMuvd`3cT?v5)tj0~iu5lXlur~0lP_p^z(Pj&G87-!B85~CE~9BzWFP|vN-m(368 zGhR|mWmBDD;f+=)WKnUjLY*8=Q2|pG3j$0akq;{g!4MUZ8ZLw^%WD4_Y~+7QE% zzMsfMUF=6RS9qVQ!RPQfE%~q$C&u_enJeTtV~MKmZ)9FNyO1f0$>X&m(~-I-Kx_jv z^YeB-UUYH=OsIUOXiv91wWnB9N9s$mIkRHvVrZzYD|cnB&5YQ#54R0TaaQanr7dQ} zXYroGbN~(4yUg@Fv?;at5~Qg#3a;kI{0u}f*Mmq7OOM!xW+=MVU_yk?ffC)KQe^+{ zr`r*25UYe%E~xPVC@6PwG5>_B*9fD3v9SQUV)n)JG0%>BZZw;W#@x8j?-!_b+Wq$t zct*@s+(X`e$QQSWncc=d!ZMU|1+3%SsCsu3JPlUtX>$kYfwMP)I+;6ufR zEaSwSWCE73l6+XRA=kya_AsKiRNP2YB+4q2OD68;eAHNkn3ySKK~3hrH%LC-R#*(Z z5@vC-#KVn=Y;;XHgoF3HJh?NDb`c{j`DMR6gOzmAw#yjhJeQWs7$rWBQBfLEx0r}| zF>n++=D9iSq_p1lPSFQ!1(Vn`v_YqFt6x!n8j|t!mFYwa1C` z0Qw~x-zyHM`S-ksiQk9#V$yy??HuXMn}4?szvcbrkV3~)>8+2w6qKcUo>J{?ZBYC` z+Skao=?OIf`^*sAW-vFO*V?qSW3I>%Ye?i!!7xV+OXqOH!v|Qcnn#irmssN4N!x|8 zk%{_wzmOM38SejWLM}SBxdSJw@=##@%%kQU)<_;z)c-40|83;|5d09n# z6$SQD3SKAs5EPH;{E<33OE8=vHdR@C{Y*>HIqg{9PDTqbFC}Q{vYy+$fm&-`nO(Pn zszV-*Y^}{`=Tv&5=EcPyX!gf!KNh#Wyc6{c8CTG2Z$*c^oVSM7Jv0`VdEdZMs$FI0 z1#yw_Q!ctVgn_~;Cx#_#TxW|W*twkV#;3f-BG#_={Od_7hlEtHBkF^Y2ch(0_&L$vw*)F{x>$lCPQJ@5H^{QS0Bv0mxOb+)c87&h2ugqFMR{Gvw1fTOShl3om@QW zqLK70lAXI}Qe8vi1x{3W&nVj;3vR*7MRUut^Aj6Z1;&Ku_kfabhZ` z4I9Krxxv|%lNd$<6}~@?Y<9r z3`*-_I9niu;Q z&Tca2z|^op&As%;b~fzDPGjE}8GdlK4uWCv7d$^v$VYuA=VJ>a8$QkX-m3&339%z2 zStk(kqs-eyK5%klWFg}fyja`;oAk-H&3vNLZ%f)fWTVFQ(m3iTzD{i?nG7;^qckS9&qJqh2$GuYK@PNn=*tnn~N zZF6{hhn!ow@~}ss@6FWy z;bL2ew-DAH>VHz&ckeLu1H+%1^>eCAtN~XYW{zkK_;ALG5vygp#K&thmxmZ^GnXC* zi0vR=I$v*sWv8?u?g{WUiDUPRLYsrtJqnK*$?J)R6z{@osc{%9BJT-zSLmKUybsDOL<>fQaYfSS>kUQ}_)(y9vvssn9I=3+??>eo)ZwRMT$? zhmYj2rcNG*QL0#%zTc!h`SWNknQ~dncE2&eGFMEW)50xaJ$YoxBM`nNGCn?a@0Grww7cY`vaZHLC~-vCc(=r3x|pvWDMG~Nyd0YtoM?3 zyo37tku3Wn<^zG^WWch{=5ZNv-jno(E{V=~R(q;_(AnA<&$B#^0F$X~}Ud%+LpAXTV##Daow*pq%MQZbZ*deTd}G4!bD6f=G%gQl@A z-yr0d$&;hx;ZZ*wIlB%^k)OvD)rP@6%lbj~yXvOA5}zU%9MBYcS1`=&I`wmvWR z1Zz`6TEW?e>~F(3`Z?KR(k?)#$rPM;v}la;fIMpyv07%l6z{wB=m+oVklX9qT5@&y zJZx47taw1pg#p+A#C-g};;Ul2z907sF+ZQk*_k38X#QOFrTbGO?KkQ?(>Jz{5w{u^ zd_UxQrg;(-qa#f$t60L1<#65rEj5;P;xzGCzDpWo28(TbL5lNwzeySUja~7=UXv@p zo{4ujkK3@^lx#QYkMVk$C#L4CytZ%lEh*!bSUUlFT^znEONDnuCgUezRn56YFPTZ=Jm*lJ8;B1! z_%5`FB+K-kya?H+Ga+#gGDav4IXz?NVBf=TpCe-$UR+n>(Tew#pS{6fS>&&fd1wv2cGM!Wq{}8L?(W-y_ zPn6Al_8oC;>2@yey`rsC_@X$Tu$ksEaNnamCDM9VwA};<2MHtTM5CZxR@I$GawLOx zV6X5WiVo`%ELbml7PZqh?UAs*!T}kg4vwLd*h%61V#AUmXiEH6%$s+`Ge)wNz~@3N zSY>{(v~N=1xU&8aBWudF?E5OW-75(~joJ{Po?dmLS-ov{mW( zd{5ro%MAm&KTHZ^GzM<@v1}%p&HK@!3&%EUOsyKnADaH10oGoM>zkr}U%i?6Y$*nC z{$Wfe&C=6thZpk=Ol5U3Ue!^ccS1ig!Z)4 zLi3@jU(Jb358N9m+MDo&tRuXq z&66{DT8mrXAIY$CzF1%7$*kGwCYb+jzvtmVVb6SwOlKN zFDXXHanhQ}VJLhD;U6(tS#7Fc{aaMdO&x0@jTBm6HRN0lpIv$i)xLKZuWR`MKM`sI_$HyTv5;c*Vju9pO}#6fJy7T^8O zNc#(W_caQ2Oi&y2ITtHq09?7IOyHU91D%-v%<~ zcz960kdWPpmR)y&8W1f}j3)DLES7T%2^BNL^71;vp0E(t&rMma)VMl%H|4k0+Ol6V z$4uCy<08wxBZ43@J0*uS>E>gFIUqG5_XEihjKZjI=iY zuh=Vb$Pmp$9W3j7GM~*rLSq`%7;|Y>yVALkkh72>|KobhPZ@a5I*4uj9)8NR#nmdD zzH_>~^NY_Oo)j{Kw0QCJmKf!+8>l3)nl+MIBRrlbs4=d=IJ3^9Y!*-oDN0zzMqvtA ztt0BPKI^h&$^!G?!TZX_ISMGSQv_>C)&<*&4xaibP7x(e4sk4+fNNMGAA^ZWy0BH!cy=)k;SY|r*ms%N zKV#4j^Z#&>utpNCj>d}V40Tt}k)t@}m8LR|<)L2E?@33ZHIp)JI5-9$DSryf*Vg*l zU;=az7^rbFR7ga##i*wY=vmdKzLeSWTT8AB>#ZL|Dbg?s9V(f~Cg7fp zS*_`5@*nB>CEbaIH*rDxK2+waV!3aN@|YYYwI!Dbzirc%n>O);2d`5%AI-ys266ec zxQQH&svBE|yAY3dv*1yv+2Yf5(yD%bpIpRRXe`u_$7n?FiNr#Z1 z==L2n)-J+zD(U%I4>9Xej~dJRbd8};+gRyg|HI?7jNH4O`CQWFb)0Zg#0kS81U_82 zClb$3#9U+&$P|hRH(5mZJ)Y0BYSp%LvF?Yl<%|$(1xp;Z6*#4}lV<@Q<`xU^H7|Nz z0y#C{?LA$`Rd+qD_JYLd#8qdKQ1S|~n4N$*5o1Mj;#xIMH7^CpYj%bIid-2VhrFh3 zfW( zY(u*p)-F(f9EF(6s0*7_1|}{zl+jet_Dy?zV`d#m4H{>)YX)m)AI20~ehfl2eE5i2 zZ`q1Qc!^{FAWuNTD|$HlLlzK6MAVngMEcL5`9rZ7Q6mr|X zYY-s}zcYB<=U{f4!XIbD2OWXnh(su*-O=EBb6+>(deN*Cg$sQ$fqV@#SS6mE6Bul3ri*4QNnkIWL4QTJdF%{C-#jGaK7Nv z`2yH|J58f_0_Vmar1Bz;knK3+D&`rzs(ob89h%!zuJ>+io-B7G9RN(W@`kb<6g`qA zo|6XStnv{~A6lkU46|pI;^w=BO^?@-CB z{TXzn9G$kookrg%jR!*@LCPnr1Ij{__c3|!;*XPz4ikrM}%%tM=-3P)s*hmJFS z6>xrL{oZBIgxNVRob8-g`EA8`CQbv3g)Ghn5r~tCN9DN=%{O$_sup~}@Z*3N2epT= z^~*IX=XJFq14E~nD({dvqO+s5vtw#~DVDGIOvPKIl*?y`jxWy;X-m=SagwrR;k1;z zqmbqR@q0LA$+@0~6GS2$)2&~v8ENMk-d@wp{e6|!kaQzqpO+yvU{Ew5m4}RYDHNBk zYHWey)skE`kw*fxSTdeR;EUMZE$epmJU9?bzT?=^Nh83#NwL2(kp#f7{1 zcs!p)ngeXOj++o?MC>!!w+)X&ko+yrQj_+FV@w~-V-HIIhYIaw#skYudrq>Ncp{q1 zc{BV8j3Z;#22(~m+X>OlgBCYnhI zeJ=KK@kP1uCR zt>-e_pc4u_@dnp+i*^RVSWz$P$Q&qS!=PthS|&3D=fGr}#{kL@|Hm z%$jX92k`*vRE?Bz`PB=cDml7UHc36 zZDx)=O>3>A4Xaz1_0&A~=u8sRmW~Na;BpB!NMwBTG7h#ntT5iY`p;##Tvji1kRdt7 zNcS)|5dAA*GNcEozJu|i?^)aPVcEgSP#o4sAsx_qCOS_P`4(vdCQpCxDwae4|H8X5 zAz*RviY^FLCwAKy1^W_-u&c$5EiKE}=dAw&>hxaBm@sYV_TZko6XHfYV69{~W72yyHZk^A$(*DHz z%*kd>o{;++PEqqGH5R@TS-apSd;~1o(zOXQ<3#oR`XT=LiJ?9%bl#y9wFSeu5Z~|FbRG6c zzTe2=n8+sM*=RE1BYy+buL~4C2EL+rt1oRHf@_K`oKl{UCeU0;!$;XAFg0@XI@;