├── examples ├── bcm2709-rpi-2-b.dtb └── dump.rs ├── Cargo.toml └── src ├── util.rs └── lib.rs /examples/bcm2709-rpi-2-b.dtb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mbr/device_tree-rs/HEAD/examples/bcm2709-rpi-2-b.dtb -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "device_tree" 3 | version = "1.0.3" 4 | authors = ["Marc Brinkmann "] 5 | license = "MIT" 6 | description = "Reads and parses Linux device tree images" 7 | repository = "https://github.com/mbr/device_tree-rs" 8 | documentation = "https://mbr.github.io/device_tree-rs/device_tree/" 9 | 10 | [features] 11 | string-dedup = [] # Requires std 12 | -------------------------------------------------------------------------------- /examples/dump.rs: -------------------------------------------------------------------------------- 1 | extern crate device_tree; 2 | 3 | use std::fs; 4 | use std::io::Read; 5 | use std::io::Write; 6 | 7 | fn main() { 8 | // read file into memory 9 | let mut input = fs::File::open("sample.dtb").unwrap(); 10 | let mut buf = Vec::new(); 11 | input.read_to_end(&mut buf).unwrap(); 12 | 13 | let dt = device_tree::DeviceTree::load(buf.as_slice()).unwrap(); 14 | println!("{:?}", dt); 15 | 16 | let dtb = dt.store().unwrap(); 17 | let mut output = fs::OpenOptions::new() 18 | .write(true) 19 | .create(true) 20 | .open("output.dtb") 21 | .unwrap(); 22 | output.write_all(&dtb).unwrap(); 23 | } 24 | -------------------------------------------------------------------------------- /src/util.rs: -------------------------------------------------------------------------------- 1 | pub use core::{convert, fmt, option, result, str}; 2 | 3 | #[inline] 4 | pub fn align(val: usize, to: usize) -> usize { 5 | val + (to - (val % to)) % to 6 | } 7 | 8 | #[derive(Debug)] 9 | pub enum SliceReadError { 10 | UnexpectedEndOfInput, 11 | } 12 | 13 | pub type SliceReadResult = Result; 14 | 15 | pub trait SliceRead { 16 | fn read_be_u32(&self, pos: usize) -> SliceReadResult; 17 | fn read_be_u64(&self, pos: usize) -> SliceReadResult; 18 | fn read_bstring0(&self, pos: usize) -> SliceReadResult<&[u8]>; 19 | fn subslice(&self, start: usize, len: usize) -> SliceReadResult<&[u8]>; 20 | } 21 | 22 | impl<'a> SliceRead for &'a [u8] { 23 | fn read_be_u32(&self, pos: usize) -> SliceReadResult { 24 | // check size is valid 25 | if !(pos + 4 <= self.len()) { 26 | return Err(SliceReadError::UnexpectedEndOfInput); 27 | } 28 | 29 | Ok( 30 | (self[pos] as u32) << 24 | (self[pos + 1] as u32) << 16 | (self[pos + 2] as u32) << 8 31 | | (self[pos + 3] as u32), 32 | ) 33 | } 34 | 35 | fn read_be_u64(&self, pos: usize) -> SliceReadResult { 36 | // check size is valid 37 | if !(pos + 8 <= self.len()) { 38 | return Err(SliceReadError::UnexpectedEndOfInput); 39 | } 40 | 41 | Ok( 42 | (self[pos] as u64) << 56 | (self[pos + 1] as u64) << 48 | (self[pos + 2] as u64) << 40 43 | | (self[pos + 3] as u64) << 32 | (self[pos + 4] as u64) << 24 44 | | (self[pos + 5] as u64) << 16 | (self[pos + 6] as u64) << 8 45 | | (self[pos + 7] as u64), 46 | ) 47 | } 48 | 49 | fn read_bstring0(&self, pos: usize) -> SliceReadResult<&[u8]> { 50 | let mut cur = pos; 51 | while cur < self.len() { 52 | if self[cur] == 0 { 53 | return Ok(&self[pos..cur]); 54 | } 55 | 56 | cur += 1; 57 | } 58 | 59 | Err(SliceReadError::UnexpectedEndOfInput) 60 | } 61 | 62 | fn subslice(&self, start: usize, end: usize) -> SliceReadResult<&[u8]> { 63 | if !(end < self.len()) { 64 | return Err(SliceReadError::UnexpectedEndOfInput); 65 | } 66 | 67 | Ok(&self[start..end]) 68 | } 69 | } 70 | 71 | #[derive(Debug)] 72 | pub enum VecWriteError { 73 | NonContiguousWrite, 74 | UnalignedWrite, 75 | } 76 | 77 | pub type VecWriteResult = Result<(), VecWriteError>; 78 | 79 | pub trait VecWrite { 80 | fn write_be_u32(&mut self, pos: usize, val: u32) -> VecWriteResult; 81 | fn write_be_u64(&mut self, pos: usize, val: u64) -> VecWriteResult; 82 | fn write_bstring0(&mut self, val: &str) -> VecWriteResult; 83 | fn pad(&mut self, alignment: usize) -> VecWriteResult; 84 | } 85 | 86 | impl VecWrite for Vec { 87 | fn write_be_u32(&mut self, pos: usize, val: u32) -> VecWriteResult { 88 | if pos % 4 != 0 { 89 | return Err(VecWriteError::UnalignedWrite); 90 | } 91 | if pos > self.len() { 92 | return Err(VecWriteError::NonContiguousWrite); 93 | } 94 | if pos + 4 > self.len() { 95 | for _ in 0..(pos + 4 - self.len()) { 96 | self.push(0); 97 | } 98 | } 99 | assert!(pos + 3 < self.len()); 100 | self[pos] = ((val >> 24) & 0xff) as u8; 101 | self[pos + 1] = ((val >> 16) & 0xff) as u8; 102 | self[pos + 2] = ((val >> 8) & 0xff) as u8; 103 | self[pos + 3] = (val & 0xff) as u8; 104 | Ok(()) 105 | } 106 | 107 | fn write_be_u64(&mut self, pos: usize, val: u64) -> VecWriteResult { 108 | if pos % 8 != 0 { 109 | return Err(VecWriteError::UnalignedWrite); 110 | } 111 | if pos > self.len() { 112 | return Err(VecWriteError::NonContiguousWrite); 113 | } 114 | if pos > self.len() - 8 { 115 | for _ in 0..(pos + 8 - self.len()) { 116 | self.push(0); 117 | } 118 | } 119 | assert!(pos + 7 < self.len()); 120 | self[pos] = ((val >> 56) & 0xff) as u8; 121 | self[pos + 1] = ((val >> 48) & 0xff) as u8; 122 | self[pos + 2] = ((val >> 40) & 0xff) as u8; 123 | self[pos + 3] = ((val >> 32) & 0xff) as u8; 124 | self[pos + 4] = ((val >> 24) & 0xff) as u8; 125 | self[pos + 5] = ((val >> 16) & 0xff) as u8; 126 | self[pos + 6] = ((val >> 8) & 0xff) as u8; 127 | self[pos + 7] = (val & 0xff) as u8; 128 | Ok(()) 129 | } 130 | 131 | fn write_bstring0(&mut self, val: &str) -> VecWriteResult { 132 | for b in val.bytes() { 133 | self.push(b); 134 | } 135 | self.push(0); 136 | Ok(()) 137 | } 138 | 139 | fn pad(&mut self, alignment: usize) -> VecWriteResult { 140 | let misalignment = self.len() % alignment; 141 | if misalignment > 0 { 142 | for _ in 0..(alignment - misalignment) { 143 | self.push(0); 144 | } 145 | } 146 | Ok(()) 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Parse flattened linux device trees 2 | //! 3 | //! Device trees are used to describe a lot of hardware, especially in the ARM 4 | //! embedded world and are also used to boot Linux on these device. A device 5 | //! tree describes addresses and other attributes for many parts on these 6 | //! boards 7 | //! 8 | //! This library allows parsing the so-called flattened device trees, which 9 | //! are the compiled binary forms of these trees. 10 | //! 11 | //! To read more about device trees, check out 12 | //! [the kernel docs](https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/plain/Documentation/devicetree/booting-without-of.txt?id=HEAD). 13 | //! Some example device trees 14 | //! to try out are [the Raspberry Pi ones] 15 | //! (https://github.com/raspberrypi/firmware/tree/master/boot). 16 | //! 17 | //! The library does not use `std`, just `core`. 18 | //! 19 | //! # Examples 20 | //! 21 | //! ```ignore 22 | //! fn main() { 23 | //! // read file into memory 24 | //! let mut input = fs::File::open("sample.dtb").unwrap(); 25 | //! let mut buf = Vec::new(); 26 | //! input.read_to_end(&mut buf).unwrap(); 27 | //! 28 | //! let dt = device_tree::DeviceTree::load(buf.as_slice ()).unwrap(); 29 | //! println!("{:?}", dt); 30 | //! } 31 | //! ``` 32 | 33 | extern crate core; 34 | 35 | pub mod util; 36 | 37 | use core::str; 38 | use util::{align, SliceRead, SliceReadError, VecWrite, VecWriteError}; 39 | 40 | const MAGIC_NUMBER: u32 = 0xd00dfeed; 41 | const SUPPORTED_VERSION: u32 = 17; 42 | const COMPAT_VERSION: u32 = 16; 43 | const OF_DT_BEGIN_NODE: u32 = 0x00000001; 44 | const OF_DT_END_NODE: u32 = 0x00000002; 45 | const OF_DT_PROP: u32 = 0x00000003; 46 | const OF_DT_END: u32 = 0x00000009; 47 | 48 | /// An error describe parsing problems when creating device trees. 49 | #[derive(Debug)] 50 | pub enum DeviceTreeError { 51 | /// The magic number `MAGIC_NUMBER` was not found at the start of the 52 | /// structure. 53 | InvalidMagicNumber, 54 | 55 | /// An offset or size found inside the device tree is outside of what was 56 | /// supplied to `load()`. 57 | SizeMismatch, 58 | 59 | /// Failed to read data from slice. 60 | SliceReadError(SliceReadError), 61 | 62 | /// The data format was not as expected at the given position 63 | ParseError(usize), 64 | 65 | /// While trying to convert a string that was supposed to be ASCII, invalid 66 | /// utf8 sequences were encounted 67 | Utf8Error, 68 | 69 | /// The device tree version is not supported by this library. 70 | VersionNotSupported, 71 | 72 | /// The device tree structure could not be serialized to DTB 73 | VecWriteError(VecWriteError), 74 | } 75 | 76 | /// Device tree structure. 77 | #[derive(Debug, PartialEq)] 78 | pub struct DeviceTree { 79 | /// Version, as indicated by version header 80 | pub version: u32, 81 | 82 | /// The number of the CPU the system boots from 83 | pub boot_cpuid_phys: u32, 84 | 85 | /// A list of tuples of `(offset, length)`, indicating reserved memory 86 | // regions. 87 | pub reserved: Vec<(u64, u64)>, 88 | 89 | /// The root node. 90 | pub root: Node, 91 | } 92 | 93 | /// A single node in the device tree. 94 | #[derive(Debug, PartialEq)] 95 | pub struct Node { 96 | /// The name of the node, as it appears in the node path. 97 | pub name: String, 98 | 99 | /// A list of node properties, `(key, value)`. 100 | pub props: Vec<(String, Vec)>, 101 | 102 | /// Child nodes of this node. 103 | pub children: Vec, 104 | } 105 | 106 | #[derive(Debug)] 107 | pub enum PropError { 108 | NotFound, 109 | Utf8Error, 110 | Missing0, 111 | SliceReadError(SliceReadError), 112 | } 113 | 114 | impl From for DeviceTreeError { 115 | fn from(e: SliceReadError) -> DeviceTreeError { 116 | DeviceTreeError::SliceReadError(e) 117 | } 118 | } 119 | 120 | impl From for DeviceTreeError { 121 | fn from(e: VecWriteError) -> DeviceTreeError { 122 | DeviceTreeError::VecWriteError(e) 123 | } 124 | } 125 | 126 | impl From for DeviceTreeError { 127 | fn from(_: str::Utf8Error) -> DeviceTreeError { 128 | DeviceTreeError::Utf8Error 129 | } 130 | } 131 | 132 | #[cfg(feature = "string-dedup")] 133 | mod advancedstringtable { 134 | use std::collections::HashMap; 135 | 136 | pub struct StringTable { 137 | pub buffer: Vec, 138 | index: HashMap, 139 | } 140 | 141 | impl StringTable { 142 | pub fn new() -> StringTable { 143 | StringTable { 144 | buffer: Vec::new(), 145 | index: HashMap::new(), 146 | } 147 | } 148 | 149 | pub fn add_string(&mut self, val: &str) -> u32 { 150 | if let Some(offset) = self.index.get(val) { 151 | return *offset; 152 | } 153 | let offset = self.buffer.len() as u32; 154 | self.buffer.extend(val.bytes()); 155 | self.buffer.push(0); 156 | self.index.insert(val.to_string(), offset); 157 | offset 158 | } 159 | } 160 | } 161 | 162 | #[cfg(not(feature = "string-dedup"))] 163 | mod stringtable { 164 | pub struct StringTable { 165 | pub buffer: Vec, 166 | } 167 | 168 | impl StringTable { 169 | pub fn new() -> StringTable { 170 | StringTable { buffer: Vec::new() } 171 | } 172 | 173 | pub fn add_string(&mut self, val: &str) -> u32 { 174 | let offset = self.buffer.len(); 175 | self.buffer.extend(val.bytes()); 176 | self.buffer.push(0); 177 | offset as u32 178 | } 179 | } 180 | } 181 | 182 | #[cfg(not(feature = "string-dedup"))] 183 | use stringtable::StringTable; 184 | 185 | #[cfg(feature = "string-dedup")] 186 | use advancedstringtable::StringTable; 187 | 188 | impl DeviceTree { 189 | //! Load a device tree from a memory buffer. 190 | pub fn load(buffer: &[u8]) -> Result { 191 | // 0 magic_number: u32, 192 | 193 | // 4 totalsize: u32, 194 | // 8 off_dt_struct: u32, 195 | // 12 off_dt_strings: u32, 196 | // 16 off_mem_rsvmap: u32, 197 | // 20 version: u32, 198 | // 24 last_comp_version: u32, 199 | 200 | // // version 2 fields 201 | // 28 boot_cpuid_phys: u32, 202 | 203 | // // version 3 fields 204 | // 32 size_dt_strings: u32, 205 | 206 | // // version 17 fields 207 | // 36 size_dt_struct: u32, 208 | 209 | if try!(buffer.read_be_u32(0)) != MAGIC_NUMBER { 210 | return Err(DeviceTreeError::InvalidMagicNumber); 211 | } 212 | 213 | // check total size 214 | if try!(buffer.read_be_u32(4)) as usize != buffer.len() { 215 | return Err(DeviceTreeError::SizeMismatch); 216 | } 217 | 218 | // check version 219 | let version = try!(buffer.read_be_u32(20)); 220 | if version != SUPPORTED_VERSION { 221 | return Err(DeviceTreeError::VersionNotSupported); 222 | } 223 | 224 | let off_dt_struct = try!(buffer.read_be_u32(8)) as usize; 225 | let off_dt_strings = try!(buffer.read_be_u32(12)) as usize; 226 | let off_mem_rsvmap = try!(buffer.read_be_u32(16)) as usize; 227 | let boot_cpuid_phys = try!(buffer.read_be_u32(28)); 228 | 229 | // load reserved memory list 230 | let mut reserved = Vec::new(); 231 | let mut pos = off_mem_rsvmap; 232 | 233 | loop { 234 | let offset = try!(buffer.read_be_u64(pos)); 235 | pos += 8; 236 | let size = try!(buffer.read_be_u64(pos)); 237 | pos += 8; 238 | 239 | reserved.push((offset, size)); 240 | 241 | if size == 0 { 242 | break; 243 | } 244 | } 245 | 246 | let (_, root) = try!(Node::load(buffer, off_dt_struct, off_dt_strings)); 247 | 248 | Ok(DeviceTree { 249 | version: version, 250 | boot_cpuid_phys: boot_cpuid_phys, 251 | reserved: reserved, 252 | root: root, 253 | }) 254 | } 255 | 256 | pub fn find<'a>(&'a self, path: &str) -> Option<&'a Node> { 257 | // we only find root nodes on the device tree 258 | if !path.starts_with('/') { 259 | return None; 260 | } 261 | 262 | self.root.find(&path[1..]) 263 | } 264 | 265 | pub fn store<'a>(&'a self) -> Result, DeviceTreeError> { 266 | let mut dtb = Vec::new(); 267 | let mut strings = StringTable::new(); 268 | 269 | // Magic 270 | let len = dtb.len(); 271 | try!(dtb.write_be_u32(len, MAGIC_NUMBER)); 272 | 273 | let size_off = dtb.len(); 274 | try!(dtb.write_be_u32(size_off, 0)); // Fill in size later 275 | let off_dt_struct = dtb.len(); 276 | try!(dtb.write_be_u32(off_dt_struct, 0)); // Fill in off_dt_struct later 277 | let off_dt_strings = dtb.len(); 278 | try!(dtb.write_be_u32(off_dt_strings, 0)); // Fill in off_dt_strings later 279 | let off_mem_rsvmap = dtb.len(); 280 | try!(dtb.write_be_u32(off_mem_rsvmap, 0)); // Fill in off_mem_rsvmap later 281 | 282 | // Version 283 | let len = dtb.len(); 284 | try!(dtb.write_be_u32(len, SUPPORTED_VERSION)); 285 | // Last comp version 286 | let len = dtb.len(); 287 | try!(dtb.write_be_u32(len, COMPAT_VERSION)); 288 | // boot_cpuid_phys 289 | let len = dtb.len(); 290 | try!(dtb.write_be_u32(len, self.boot_cpuid_phys)); 291 | 292 | let off_size_strings = dtb.len(); 293 | try!(dtb.write_be_u32(off_size_strings, 0)); // Fill in size_dt_strings later 294 | let off_size_struct = dtb.len(); 295 | try!(dtb.write_be_u32(off_size_struct, 0)); // Fill in size_dt_struct later 296 | 297 | // Memory Reservation Block 298 | try!(dtb.pad(8)); 299 | let len = dtb.len(); 300 | try!(dtb.write_be_u32(off_mem_rsvmap, len as u32)); 301 | for reservation in self.reserved.iter() { 302 | // address 303 | let len = dtb.len(); 304 | try!(dtb.write_be_u64(len, reservation.0)); 305 | // size 306 | let len = dtb.len(); 307 | try!(dtb.write_be_u64(len, reservation.1)); 308 | } 309 | 310 | // Structure Block 311 | try!(dtb.pad(4)); 312 | let structure_start = dtb.len(); 313 | try!(dtb.write_be_u32(off_dt_struct, structure_start as u32)); 314 | try!(self.root.store(&mut dtb, &mut strings)); 315 | 316 | try!(dtb.pad(4)); 317 | let len = dtb.len(); 318 | try!(dtb.write_be_u32(len, OF_DT_END)); 319 | 320 | let len = dtb.len(); 321 | try!(dtb.write_be_u32(off_size_struct, (len - structure_start) as u32)); 322 | try!(dtb.write_be_u32(off_size_strings, strings.buffer.len() as u32)); 323 | 324 | // Strings Block 325 | try!(dtb.pad(4)); 326 | let len = dtb.len(); 327 | try!(dtb.write_be_u32(off_dt_strings, len as u32)); 328 | dtb.extend_from_slice(&strings.buffer); 329 | 330 | let len = dtb.len(); 331 | try!(dtb.write_be_u32(size_off, len as u32)); 332 | 333 | Ok(dtb) 334 | } 335 | } 336 | 337 | impl Node { 338 | fn load( 339 | buffer: &[u8], 340 | start: usize, 341 | off_dt_strings: usize, 342 | ) -> Result<(usize, Node), DeviceTreeError> { 343 | // check for DT_BEGIN_NODE 344 | if try!(buffer.read_be_u32(start)) != OF_DT_BEGIN_NODE { 345 | return Err(DeviceTreeError::ParseError(start)); 346 | } 347 | 348 | let raw_name = try!(buffer.read_bstring0(start + 4)); 349 | 350 | // read all the props 351 | let mut pos = align(start + 4 + raw_name.len() + 1, 4); 352 | 353 | let mut props = Vec::new(); 354 | 355 | while try!(buffer.read_be_u32(pos)) == OF_DT_PROP { 356 | let val_size = try!(buffer.read_be_u32(pos + 4)) as usize; 357 | let name_offset = try!(buffer.read_be_u32(pos + 8)) as usize; 358 | 359 | // get value slice 360 | let val_start = pos + 12; 361 | let val_end = val_start + val_size; 362 | let val = try!(buffer.subslice(val_start, val_end)); 363 | 364 | // lookup name in strings table 365 | let prop_name = try!(buffer.read_bstring0(off_dt_strings + name_offset)); 366 | 367 | props.push((try!(str::from_utf8(prop_name)).to_owned(), val.to_owned())); 368 | 369 | pos = align(val_end, 4); 370 | } 371 | 372 | // finally, parse children 373 | let mut children = Vec::new(); 374 | 375 | while try!(buffer.read_be_u32(pos)) == OF_DT_BEGIN_NODE { 376 | let (new_pos, child_node) = try!(Node::load(buffer, pos, off_dt_strings)); 377 | pos = new_pos; 378 | 379 | children.push(child_node); 380 | } 381 | 382 | if try!(buffer.read_be_u32(pos)) != OF_DT_END_NODE { 383 | return Err(DeviceTreeError::ParseError(pos)); 384 | } 385 | 386 | pos += 4; 387 | 388 | Ok(( 389 | pos, 390 | Node { 391 | name: try!(str::from_utf8(raw_name)).to_owned(), 392 | props: props, 393 | children: children, 394 | }, 395 | )) 396 | } 397 | 398 | pub fn find<'a>(&'a self, path: &str) -> Option<&'a Node> { 399 | if path == "" { 400 | return Some(self); 401 | } 402 | 403 | match path.find('/') { 404 | Some(idx) => { 405 | // find should return the proper index, so we're safe to 406 | // use indexing here 407 | let (l, r) = path.split_at(idx); 408 | 409 | // we know that the first char of slashed is a '/' 410 | let subpath = &r[1..]; 411 | 412 | for child in self.children.iter() { 413 | if child.name == l { 414 | return child.find(subpath); 415 | } 416 | } 417 | 418 | // no matching child found 419 | None 420 | } 421 | None => self.children.iter().find(|n| n.name == path), 422 | } 423 | } 424 | 425 | pub fn has_prop(&self, name: &str) -> bool { 426 | if let Some(_) = self.prop_raw(name) { 427 | true 428 | } else { 429 | false 430 | } 431 | } 432 | 433 | pub fn prop_str<'a>(&'a self, name: &str) -> Result<&'a str, PropError> { 434 | let raw = try!(self.prop_raw(name).ok_or(PropError::NotFound)); 435 | 436 | let l = raw.len(); 437 | if l < 1 || raw[l - 1] != 0 { 438 | return Err(PropError::Missing0); 439 | } 440 | 441 | Ok(try!(str::from_utf8(&raw[..(l - 1)]))) 442 | } 443 | 444 | pub fn prop_raw<'a>(&'a self, name: &str) -> Option<&'a Vec> { 445 | for &(ref key, ref val) in self.props.iter() { 446 | if key == name { 447 | return Some(val); 448 | } 449 | } 450 | None 451 | } 452 | 453 | pub fn prop_u64(&self, name: &str) -> Result { 454 | let raw = try!(self.prop_raw(name).ok_or(PropError::NotFound)); 455 | 456 | Ok(try!(raw.as_slice().read_be_u64(0))) 457 | } 458 | 459 | pub fn prop_u32(&self, name: &str) -> Result { 460 | let raw = try!(self.prop_raw(name).ok_or(PropError::NotFound)); 461 | 462 | Ok(try!(raw.as_slice().read_be_u32(0))) 463 | } 464 | 465 | pub fn store( 466 | &self, 467 | structure: &mut Vec, 468 | strings: &mut StringTable, 469 | ) -> Result<(), DeviceTreeError> { 470 | try!(structure.pad(4)); 471 | let len = structure.len(); 472 | try!(structure.write_be_u32(len, OF_DT_BEGIN_NODE)); 473 | 474 | try!(structure.write_bstring0(&self.name)); 475 | for prop in self.props.iter() { 476 | try!(structure.pad(4)); 477 | let len = structure.len(); 478 | try!(structure.write_be_u32(len, OF_DT_PROP)); 479 | 480 | // Write property value length 481 | try!(structure.pad(4)); 482 | let len = structure.len(); 483 | try!(structure.write_be_u32(len, prop.1.len() as u32)); 484 | 485 | // Write name offset 486 | try!(structure.pad(4)); 487 | let len = structure.len(); 488 | try!(structure.write_be_u32(len, strings.add_string(&prop.0))); 489 | 490 | // Store the property value 491 | structure.extend_from_slice(&prop.1); 492 | } 493 | 494 | // Recurse on children 495 | for child in self.children.iter() { 496 | try!(child.store(structure, strings)); 497 | } 498 | 499 | try!(structure.pad(4)); 500 | let len = structure.len(); 501 | try!(structure.write_be_u32(len, OF_DT_END_NODE)); 502 | Ok(()) 503 | } 504 | } 505 | 506 | impl From for PropError { 507 | fn from(_: str::Utf8Error) -> PropError { 508 | PropError::Utf8Error 509 | } 510 | } 511 | 512 | impl From for PropError { 513 | fn from(e: SliceReadError) -> PropError { 514 | PropError::SliceReadError(e) 515 | } 516 | } 517 | 518 | #[cfg(test)] 519 | mod test { 520 | use std::fs; 521 | use std::io::{Read, Write}; 522 | 523 | use super::*; 524 | 525 | #[test] 526 | fn roundtrip() { 527 | // read file into memory 528 | let buf = include_bytes!("../examples/bcm2709-rpi-2-b.dtb"); 529 | let original_fdt = DeviceTree::load(buf).unwrap(); 530 | 531 | let dtb = original_fdt.store().unwrap(); 532 | let mut output = fs::OpenOptions::new() 533 | .write(true) 534 | .create(true) 535 | .open("output.dtb") 536 | .unwrap(); 537 | output.write_all(&dtb).unwrap(); 538 | 539 | let mut input = fs::File::open("output.dtb").unwrap(); 540 | let mut buf = Vec::new(); 541 | input.read_to_end(&mut buf).unwrap(); 542 | let generated_fdt = DeviceTree::load(buf.as_slice()).unwrap(); 543 | 544 | assert!(original_fdt == generated_fdt); 545 | } 546 | } 547 | --------------------------------------------------------------------------------