1use std::str::Utf8Error;
6
7use zerocopy::{BigEndian, FromBytes, Immutable, KnownLayout, U32};
8
9use crate::types::*;
10
11const FDT_MAGIC: u32 = 0xd00dfeed;
13
14const FDT_BEGIN_NODE: u32 = 0x00000001;
17
18const FDT_END_NODE: u32 = 0x00000002;
20
21const FDT_PROP: u32 = 0x00000003;
23
24const FDT_NOP: u32 = 0x00000004;
26
27const FDT_END: u32 = 0x00000009;
30
31#[derive(Debug)]
32pub enum ParseError {
33 InvalidMagicNumber,
34 UnsupportedVersion,
35 Utf8Error(Utf8Error),
36 MalformedStructure(String),
37 ZeroCopyError,
38}
39
40impl std::fmt::Display for ParseError {
41 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
42 match self {
43 ParseError::InvalidMagicNumber => write!(f, "Invalid magic number"),
44 ParseError::UnsupportedVersion => write!(f, "Unsupported version"),
45 ParseError::Utf8Error(e) => write!(f, "Utf8 error: {}", e),
46 ParseError::MalformedStructure(e) => write!(f, "Malformed structure: {}", e),
47 ParseError::ZeroCopyError => write!(f, "Zero copy error"),
48 }
49 }
50}
51
52impl std::error::Error for ParseError {
53 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
54 match self {
55 ParseError::Utf8Error(e) => Some(e),
56 _ => None,
57 }
58 }
59}
60
61pub fn parse_devicetree<'a>(data: &'a [u8]) -> Result<Devicetree<'a>, ParseError> {
63 let header = parse_item::<Header>(&data)?;
65 verify_header(&header)?;
66
67 let off_mem_rsvmap = header.off_mem_rsvmap.get() as usize;
69 let reserve_entries = parse_reserve_entries(&data[off_mem_rsvmap..])?;
70
71 let off_dt_strings = header.off_dt_strings.get() as usize;
73 let size_dt_strings = header.size_dt_strings.get() as usize;
74 let string_data = verify_slice(&data, off_dt_strings, size_dt_strings)?;
75
76 let off_dt_struct = header.off_dt_struct.get() as usize;
78 let size_dt_struct = header.size_dt_struct.get() as usize;
79 let structure_data = verify_slice(&data, off_dt_struct, size_dt_struct)?;
80 let mut struct_offset = 0;
81 let root_node = parse_structure_block(&structure_data, &mut struct_offset, &string_data)?;
84
85 let token = parse_item::<U32<BigEndian>>(&structure_data[struct_offset..])?.get();
87 if token != FDT_END {
88 return Err(ParseError::MalformedStructure(format!(
89 "Expected FDT_END token at the end of structure block, got 0x{:x}",
90 token
91 )));
92 }
93
94 Ok(Devicetree { header, reserve_entries, root_node })
95}
96
97fn verify_header(header: &Header) -> Result<(), ParseError> {
99 if header.magic.get() != FDT_MAGIC {
100 return Err(ParseError::InvalidMagicNumber);
101 }
102 if header.version.get() < 17 {
103 return Err(ParseError::UnsupportedVersion);
104 }
105
106 Ok(())
107}
108
109fn verify_slice(data: &[u8], offset: usize, size: usize) -> Result<&[u8], ParseError> {
113 if offset + size > data.len() {
114 return Err(ParseError::MalformedStructure(format!(
115 "Attempted to create slice from 0x{:x} to 0x{:x}, where total length was 0x{:x}",
116 offset,
117 offset + size,
118 data.len()
119 )));
120 }
121 Ok(&data[offset..offset + size])
122}
123
124fn parse_item<'a, T: FromBytes + KnownLayout + Immutable>(
128 data: &'a [u8],
129) -> Result<&'a T, ParseError> {
130 let token = T::ref_from_prefix(&data).map_err(|_| ParseError::ZeroCopyError)?;
131 Ok(token.0)
132}
133
134fn parse_item_and_increment_offset<'a, T: FromBytes + KnownLayout + Immutable>(
139 data: &'a [u8],
140 offset: &mut usize,
141) -> Result<&'a T, ParseError> {
142 let r = parse_item(data)?;
143 *offset += std::mem::size_of::<T>();
144
145 Ok(r)
146}
147
148fn parse_string<'a>(data: &'a [u8], offset: &mut usize) -> Result<&'a str, ParseError> {
156 let str_slice = &data[*offset..];
157 let null_index = str_slice.iter().position(|c| *c == 0).ok_or(ParseError::ZeroCopyError)?;
158 *offset += null_index + 1;
159 *offset = (*offset + 3) & !3;
161
162 std::str::from_utf8(&str_slice[..null_index]).map_err(|e| ParseError::Utf8Error(e))
163}
164
165fn parse_structure_block<'a>(
173 data: &'a [u8],
174 offset: &mut usize,
175 strings_block: &'a [u8],
176) -> Result<Node<'a>, ParseError> {
177 let token = parse_item_and_increment_offset::<U32<BigEndian>>(&data[*offset..], offset)?;
178 if *token != FDT_BEGIN_NODE {
179 return Err(ParseError::MalformedStructure(format!(
180 "Expected FDT_BEGIN_NODE token at offset 0x{:x}, got 0x{:x}",
181 offset, token
182 )));
183 }
184
185 let name = parse_string(&data, offset)?;
186 let mut node = Node::new(name);
187
188 loop {
189 let token =
190 parse_item_and_increment_offset::<U32<BigEndian>>(&data[*offset..], offset)?.get();
191 match token {
192 token if token == FDT_BEGIN_NODE => {
193 *offset -= std::mem::size_of_val(&token);
195
196 let child_node = parse_structure_block(data, offset, strings_block)?;
197 node.children.push(child_node);
198 }
199 token if token == FDT_PROP => {
200 let length =
201 parse_item_and_increment_offset::<U32<BigEndian>>(&data[*offset..], offset)?
202 .get() as usize;
203
204 let mut prop_name_offset =
205 parse_item_and_increment_offset::<U32<BigEndian>>(&data[*offset..], offset)?
206 .get() as usize;
207 let prop_name = parse_string(&strings_block, &mut prop_name_offset)?;
208
209 let value = &data[*offset..*offset + length];
211 *offset += length;
212 *offset = (*offset + 3) & !3;
213
214 node.properties.push(Property { name: prop_name, value });
215 }
216 token if token == FDT_END_NODE => {
217 return Ok(node);
218 }
219 token if token == FDT_NOP => {}
220 token if token == FDT_END => {
221 return Err(ParseError::MalformedStructure("FDT_END inside of node".to_string()));
222 }
223 token => {
224 return Err(ParseError::MalformedStructure(format!(
225 "Expected valid token at offset 0x{:x}, got 0x{:x}",
226 offset, token
227 )));
228 }
229 }
230 }
231}
232
233fn parse_reserve_entries<'a>(data: &'a [u8]) -> Result<&'a [ReserveEntry], ParseError> {
235 let mut num_entries = 0;
236 let mut offset = 0;
237 loop {
238 let entry: &ReserveEntry = ReserveEntry::ref_from_prefix(&data[offset..])
239 .map_err(|_| {
240 ParseError::MalformedStructure(
241 "Memory reservation map entry too small or misaligned.".to_string(),
242 )
243 })?
244 .0;
245
246 if entry.address.get() == 0 && entry.size.get() == 0 {
248 return <[ReserveEntry]>::ref_from_prefix_with_elems(data, num_entries)
249 .map(|s| s.0)
250 .map_err(|_| {
251 ParseError::MalformedStructure("Invalid reserve entry slice".to_string())
252 });
253 }
254
255 offset += std::mem::size_of::<ReserveEntry>();
256 num_entries += 1;
257 }
258}
259
260#[cfg(test)]
261mod test {
262 #[test]
263 fn test_vim3() {
264 let contents = std::fs::read("pkg/test-data/test.dtb").expect("failed to read file");
265 crate::parser::parse_devicetree(&contents).expect("failed to parse devicetree");
266 }
267}