Jaewan Kim | c6e023b | 2023-10-12 15:11:05 +0900 | [diff] [blame] | 1 | // Copyright 2023, The Android Open Source Project |
| 2 | // |
| 3 | // Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | // you may not use this file except in compliance with the License. |
| 5 | // You may obtain a copy of the License at |
| 6 | // |
| 7 | // http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | // |
| 9 | // Unless required by applicable law or agreed to in writing, software |
| 10 | // distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | // See the License for the specific language governing permissions and |
| 13 | // limitations under the License. |
| 14 | |
| 15 | //! Validate device assignment written in crosvm DT with VM DTBO, and apply it |
| 16 | //! to platform DT. |
| 17 | //! Declared in separated libs for adding unit tests, which requires libstd. |
| 18 | |
| 19 | #[cfg(test)] |
| 20 | extern crate alloc; |
| 21 | |
| 22 | use alloc::ffi::CString; |
| 23 | use alloc::fmt; |
| 24 | use alloc::vec; |
| 25 | use alloc::vec::Vec; |
| 26 | use core::ffi::CStr; |
| 27 | use core::iter::Iterator; |
| 28 | use core::mem; |
| 29 | use libfdt::{Fdt, FdtError, FdtNode}; |
| 30 | |
Jaewan Kim | c6e023b | 2023-10-12 15:11:05 +0900 | [diff] [blame] | 31 | // TODO(b/308694211): Use cstr! from vmbase instead. |
| 32 | macro_rules! cstr { |
| 33 | ($str:literal) => {{ |
Pierre-Clément Tosi | d701a0b | 2023-11-07 15:38:59 +0000 | [diff] [blame^] | 34 | const S: &str = concat!($str, "\0"); |
| 35 | const C: &::core::ffi::CStr = match ::core::ffi::CStr::from_bytes_with_nul(S.as_bytes()) { |
| 36 | Ok(v) => v, |
| 37 | Err(_) => panic!("string contains interior NUL"), |
| 38 | }; |
| 39 | C |
Jaewan Kim | c6e023b | 2023-10-12 15:11:05 +0900 | [diff] [blame] | 40 | }}; |
| 41 | } |
| 42 | |
| 43 | const FILTERED_VM_DTBO_PROP: [&CStr; 3] = [ |
Pierre-Clément Tosi | d701a0b | 2023-11-07 15:38:59 +0000 | [diff] [blame^] | 44 | cstr!("android,pvmfw,phy-reg"), |
| 45 | cstr!("android,pvmfw,phy-iommu"), |
| 46 | cstr!("android,pvmfw,phy-sid"), |
Jaewan Kim | c6e023b | 2023-10-12 15:11:05 +0900 | [diff] [blame] | 47 | ]; |
| 48 | |
Jaewan Kim | c6e023b | 2023-10-12 15:11:05 +0900 | [diff] [blame] | 49 | // TODO(b/277993056): Keep constants derived from platform.dts in one place. |
| 50 | const CELLS_PER_INTERRUPT: usize = 3; // from /intc node in platform.dts |
| 51 | |
| 52 | /// Errors in device assignment. |
| 53 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] |
| 54 | pub enum DeviceAssignmentError { |
| 55 | // Invalid VM DTBO |
| 56 | InvalidDtbo, |
| 57 | /// Invalid __symbols__ |
| 58 | InvalidSymbols, |
| 59 | /// Invalid <interrupts> |
| 60 | InvalidInterrupts, |
| 61 | /// Unsupported overlay target syntax. Only supports <target-path> with full path. |
| 62 | UnsupportedOverlayTarget, |
| 63 | /// Unexpected error from libfdt |
| 64 | UnexpectedFdtError(FdtError), |
| 65 | } |
| 66 | |
| 67 | impl From<FdtError> for DeviceAssignmentError { |
| 68 | fn from(e: FdtError) -> Self { |
| 69 | DeviceAssignmentError::UnexpectedFdtError(e) |
| 70 | } |
| 71 | } |
| 72 | |
| 73 | impl fmt::Display for DeviceAssignmentError { |
| 74 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| 75 | match self { |
| 76 | Self::InvalidDtbo => write!(f, "Invalid DTBO"), |
| 77 | Self::InvalidSymbols => write!( |
| 78 | f, |
| 79 | "Invalid property in /__symbols__. Must point to valid assignable device node." |
| 80 | ), |
| 81 | Self::InvalidInterrupts => write!(f, "Invalid <interrupts>"), |
| 82 | Self::UnsupportedOverlayTarget => { |
| 83 | write!(f, "Unsupported overlay target. Only supports 'target-path = \"/\"'") |
| 84 | } |
| 85 | Self::UnexpectedFdtError(e) => write!(f, "Unexpected Error from libfdt: {e}"), |
| 86 | } |
| 87 | } |
| 88 | } |
| 89 | |
| 90 | pub type Result<T> = core::result::Result<T, DeviceAssignmentError>; |
| 91 | |
| 92 | /// Represents VM DTBO |
| 93 | #[repr(transparent)] |
| 94 | pub struct VmDtbo(Fdt); |
| 95 | |
| 96 | impl VmDtbo { |
Jaewan Kim | c6e023b | 2023-10-12 15:11:05 +0900 | [diff] [blame] | 97 | /// Wraps a mutable slice containing a VM DTBO. |
| 98 | /// |
| 99 | /// Fails if the VM DTBO does not pass validation. |
| 100 | pub fn from_mut_slice(dtbo: &mut [u8]) -> Result<&mut Self> { |
| 101 | // This validates DTBO |
| 102 | let fdt = Fdt::from_mut_slice(dtbo)?; |
| 103 | // SAFETY: VmDtbo is a transparent wrapper around Fdt, so representation is the same. |
| 104 | Ok(unsafe { mem::transmute::<&mut Fdt, &mut Self>(fdt) }) |
| 105 | } |
| 106 | |
| 107 | // Locates device node path as if the given dtbo node path is assigned and VM DTBO is overlaid. |
| 108 | // For given dtbo node path, this concatenates <target-path> of the enclosing fragment and |
| 109 | // relative path from __overlay__ node. |
| 110 | // |
| 111 | // Here's an example with sample VM DTBO: |
| 112 | // / { |
| 113 | // fragment@rng { |
| 114 | // target-path = "/"; // Always 'target-path = "/"'. Disallows <target> or other path. |
| 115 | // __overlay__ { |
| 116 | // rng { ... }; // Actual device node is here. If overlaid, path would be "/rng" |
| 117 | // }; |
| 118 | // }; |
| 119 | // __symbols__ { // List of assignable devices |
| 120 | // // Each property describes an assigned device device information. |
| 121 | // // property name is the device label, and property value is the path in the VM DTBO. |
| 122 | // rng = "/fragment@rng/__overlay__/rng"; |
| 123 | // }; |
| 124 | // }; |
| 125 | // |
| 126 | // Then locate_overlay_target_path(cstr!("/fragment@rng/__overlay__/rng")) is Ok("/rng") |
| 127 | // |
| 128 | // Contrary to fdt_overlay_target_offset(), this API enforces overlay target property |
| 129 | // 'target-path = "/"', so the overlay doesn't modify and/or append platform DT's existing |
| 130 | // node and/or properties. The enforcement is for compatibility reason. |
| 131 | fn locate_overlay_target_path(&self, dtbo_node_path: &CStr) -> Result<CString> { |
| 132 | let dtbo_node_path_bytes = dtbo_node_path.to_bytes(); |
| 133 | if dtbo_node_path_bytes.first() != Some(&b'/') { |
| 134 | return Err(DeviceAssignmentError::UnsupportedOverlayTarget); |
| 135 | } |
| 136 | |
| 137 | let node = self.0.node(dtbo_node_path)?.ok_or(DeviceAssignmentError::InvalidSymbols)?; |
| 138 | |
| 139 | let fragment_node = node.supernode_at_depth(1)?; |
| 140 | let target_path = fragment_node |
Pierre-Clément Tosi | d701a0b | 2023-11-07 15:38:59 +0000 | [diff] [blame^] | 141 | .getprop_str(cstr!("target-path"))? |
Jaewan Kim | c6e023b | 2023-10-12 15:11:05 +0900 | [diff] [blame] | 142 | .ok_or(DeviceAssignmentError::InvalidDtbo)?; |
| 143 | if target_path != cstr!("/") { |
| 144 | return Err(DeviceAssignmentError::UnsupportedOverlayTarget); |
| 145 | } |
| 146 | |
| 147 | let mut components = dtbo_node_path_bytes |
| 148 | .split(|char| *char == b'/') |
| 149 | .filter(|&component| !component.is_empty()) |
| 150 | .skip(1); |
| 151 | let overlay_node_name = components.next(); |
Pierre-Clément Tosi | d701a0b | 2023-11-07 15:38:59 +0000 | [diff] [blame^] | 152 | if overlay_node_name != Some(b"__overlay__") { |
Jaewan Kim | c6e023b | 2023-10-12 15:11:05 +0900 | [diff] [blame] | 153 | return Err(DeviceAssignmentError::InvalidDtbo); |
| 154 | } |
| 155 | let mut overlaid_path = Vec::with_capacity(dtbo_node_path_bytes.len()); |
| 156 | for component in components { |
| 157 | overlaid_path.push(b'/'); |
| 158 | overlaid_path.extend_from_slice(component); |
| 159 | } |
| 160 | overlaid_path.push(b'\0'); |
| 161 | |
| 162 | Ok(CString::from_vec_with_nul(overlaid_path).unwrap()) |
| 163 | } |
| 164 | } |
| 165 | |
| 166 | impl AsRef<Fdt> for VmDtbo { |
| 167 | fn as_ref(&self) -> &Fdt { |
| 168 | &self.0 |
| 169 | } |
| 170 | } |
| 171 | |
| 172 | impl AsMut<Fdt> for VmDtbo { |
| 173 | fn as_mut(&mut self) -> &mut Fdt { |
| 174 | &mut self.0 |
| 175 | } |
| 176 | } |
| 177 | |
| 178 | /// Assigned device information parsed from crosvm DT. |
| 179 | /// Keeps everything in the owned data because underlying FDT will be reused for platform DT. |
| 180 | #[derive(Debug, Eq, PartialEq)] |
| 181 | struct AssignedDeviceInfo { |
| 182 | // Node path of assigned device (e.g. "/rng") |
| 183 | node_path: CString, |
| 184 | // DTBO node path of the assigned device (e.g. "/fragment@rng/__overlay__/rng") |
| 185 | dtbo_node_path: CString, |
| 186 | // <reg> property from the crosvm DT |
| 187 | reg: Vec<u8>, |
| 188 | // <interrupts> property from the crosvm DT |
| 189 | interrupts: Vec<u8>, |
| 190 | } |
| 191 | |
| 192 | impl AssignedDeviceInfo { |
| 193 | fn parse_interrupts(node: &FdtNode) -> Result<Vec<u8>> { |
| 194 | // Validation: Validate if interrupts cell numbers are multiple of #interrupt-cells. |
| 195 | // We can't know how many interrupts would exist. |
| 196 | let interrupts_cells = node |
Pierre-Clément Tosi | d701a0b | 2023-11-07 15:38:59 +0000 | [diff] [blame^] | 197 | .getprop_cells(cstr!("interrupts"))? |
Jaewan Kim | c6e023b | 2023-10-12 15:11:05 +0900 | [diff] [blame] | 198 | .ok_or(DeviceAssignmentError::InvalidInterrupts)? |
| 199 | .count(); |
| 200 | if interrupts_cells % CELLS_PER_INTERRUPT != 0 { |
| 201 | return Err(DeviceAssignmentError::InvalidInterrupts); |
| 202 | } |
| 203 | |
| 204 | // Once validated, keep the raw bytes so patch can be done with setprop() |
Pierre-Clément Tosi | d701a0b | 2023-11-07 15:38:59 +0000 | [diff] [blame^] | 205 | Ok(node.getprop(cstr!("interrupts")).unwrap().unwrap().into()) |
Jaewan Kim | c6e023b | 2023-10-12 15:11:05 +0900 | [diff] [blame] | 206 | } |
| 207 | |
| 208 | // TODO(b/277993056): Read and validate iommu |
| 209 | fn parse(fdt: &Fdt, vm_dtbo: &VmDtbo, dtbo_node_path: &CStr) -> Result<Option<Self>> { |
| 210 | let node_path = vm_dtbo.locate_overlay_target_path(dtbo_node_path)?; |
| 211 | |
| 212 | let Some(node) = fdt.node(&node_path)? else { return Ok(None) }; |
| 213 | |
| 214 | // TODO(b/277993056): Validate reg with HVC, and keep reg with FdtNode::reg() |
Pierre-Clément Tosi | d701a0b | 2023-11-07 15:38:59 +0000 | [diff] [blame^] | 215 | let reg = node.getprop(cstr!("reg")).unwrap().unwrap(); |
Jaewan Kim | c6e023b | 2023-10-12 15:11:05 +0900 | [diff] [blame] | 216 | |
| 217 | let interrupts = Self::parse_interrupts(&node)?; |
| 218 | |
| 219 | Ok(Some(Self { |
| 220 | node_path, |
| 221 | dtbo_node_path: dtbo_node_path.into(), |
| 222 | reg: reg.to_vec(), |
| 223 | interrupts: interrupts.to_vec(), |
| 224 | })) |
| 225 | } |
| 226 | |
| 227 | fn patch(&self, fdt: &mut Fdt) -> Result<()> { |
| 228 | let mut dst = fdt.node_mut(&self.node_path)?.unwrap(); |
Pierre-Clément Tosi | d701a0b | 2023-11-07 15:38:59 +0000 | [diff] [blame^] | 229 | dst.setprop(cstr!("reg"), &self.reg)?; |
| 230 | dst.setprop(cstr!("interrupts"), &self.interrupts)?; |
Jaewan Kim | c6e023b | 2023-10-12 15:11:05 +0900 | [diff] [blame] | 231 | // TODO(b/277993056): Read and patch iommu |
| 232 | Ok(()) |
| 233 | } |
| 234 | } |
| 235 | |
| 236 | #[derive(Debug, Default, Eq, PartialEq)] |
| 237 | pub struct DeviceAssignmentInfo { |
| 238 | assigned_devices: Vec<AssignedDeviceInfo>, |
| 239 | filtered_dtbo_paths: Vec<CString>, |
| 240 | } |
| 241 | |
| 242 | impl DeviceAssignmentInfo { |
| 243 | /// Parses fdt and vm_dtbo, and creates new DeviceAssignmentInfo |
| 244 | // TODO(b/277993056): Parse __local_fixups__ |
| 245 | // TODO(b/277993056): Parse __fixups__ |
| 246 | pub fn parse(fdt: &Fdt, vm_dtbo: &VmDtbo) -> Result<Option<Self>> { |
| 247 | let Some(symbols_node) = vm_dtbo.as_ref().symbols()? else { |
| 248 | // /__symbols__ should contain all assignable devices. |
| 249 | // If empty, then nothing can be assigned. |
| 250 | return Ok(None); |
| 251 | }; |
| 252 | |
| 253 | let mut assigned_devices = vec![]; |
| 254 | let mut filtered_dtbo_paths = vec![]; |
| 255 | for symbol_prop in symbols_node.properties()? { |
| 256 | let symbol_prop_value = symbol_prop.value()?; |
| 257 | let dtbo_node_path = CStr::from_bytes_with_nul(symbol_prop_value) |
| 258 | .or(Err(DeviceAssignmentError::InvalidSymbols))?; |
| 259 | let assigned_device = AssignedDeviceInfo::parse(fdt, vm_dtbo, dtbo_node_path)?; |
| 260 | if let Some(assigned_device) = assigned_device { |
| 261 | assigned_devices.push(assigned_device); |
| 262 | } else { |
| 263 | filtered_dtbo_paths.push(dtbo_node_path.into()); |
| 264 | } |
| 265 | } |
Pierre-Clément Tosi | d701a0b | 2023-11-07 15:38:59 +0000 | [diff] [blame^] | 266 | filtered_dtbo_paths.push(CString::new("/__symbols__").unwrap()); |
Jaewan Kim | c6e023b | 2023-10-12 15:11:05 +0900 | [diff] [blame] | 267 | |
| 268 | if assigned_devices.is_empty() { |
| 269 | return Ok(None); |
| 270 | } |
| 271 | Ok(Some(Self { assigned_devices, filtered_dtbo_paths })) |
| 272 | } |
| 273 | |
| 274 | /// Filters VM DTBO to only contain necessary information for booting pVM |
| 275 | /// In detail, this will remove followings by setting nop node / nop property. |
| 276 | /// - Removes unassigned devices |
| 277 | /// - Removes /__symbols__ node |
| 278 | // TODO(b/277993056): remove unused dependencies in VM DTBO. |
| 279 | // TODO(b/277993056): remove supernodes' properties. |
| 280 | // TODO(b/277993056): remove unused alises. |
| 281 | pub fn filter(&self, vm_dtbo: &mut VmDtbo) -> Result<()> { |
| 282 | let vm_dtbo = vm_dtbo.as_mut(); |
| 283 | |
| 284 | // Filters unused node in assigned devices |
| 285 | for filtered_dtbo_path in &self.filtered_dtbo_paths { |
| 286 | let node = vm_dtbo.node_mut(filtered_dtbo_path).unwrap().unwrap(); |
| 287 | node.nop()?; |
| 288 | } |
| 289 | |
| 290 | // Filters unused properties in assigned device node |
| 291 | for assigned_device in &self.assigned_devices { |
| 292 | let mut node = vm_dtbo.node_mut(&assigned_device.dtbo_node_path).unwrap().unwrap(); |
| 293 | for prop in FILTERED_VM_DTBO_PROP { |
| 294 | node.nop_property(prop)?; |
| 295 | } |
| 296 | } |
| 297 | Ok(()) |
| 298 | } |
| 299 | |
| 300 | pub fn patch(&self, fdt: &mut Fdt) -> Result<()> { |
| 301 | for device in &self.assigned_devices { |
| 302 | device.patch(fdt)? |
| 303 | } |
| 304 | Ok(()) |
| 305 | } |
| 306 | } |
| 307 | |
| 308 | #[cfg(test)] |
| 309 | mod tests { |
| 310 | use super::*; |
| 311 | use std::fs; |
| 312 | |
| 313 | const VM_DTBO_FILE_PATH: &str = "test_pvmfw_devices_vm_dtbo.dtbo"; |
| 314 | const VM_DTBO_WITHOUT_SYMBOLS_FILE_PATH: &str = |
| 315 | "test_pvmfw_devices_vm_dtbo_without_symbols.dtbo"; |
| 316 | const FDT_FILE_PATH: &str = "test_pvmfw_devices_with_rng.dtb"; |
| 317 | |
| 318 | fn into_fdt_prop(native_bytes: Vec<u32>) -> Vec<u8> { |
| 319 | let mut v = Vec::with_capacity(native_bytes.len() * 4); |
| 320 | for byte in native_bytes { |
| 321 | v.extend_from_slice(&byte.to_be_bytes()); |
| 322 | } |
| 323 | v |
| 324 | } |
| 325 | |
| 326 | #[test] |
| 327 | fn device_info_new_without_symbols() { |
| 328 | let mut fdt_data = fs::read(FDT_FILE_PATH).unwrap(); |
| 329 | let mut vm_dtbo_data = fs::read(VM_DTBO_WITHOUT_SYMBOLS_FILE_PATH).unwrap(); |
| 330 | let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap(); |
| 331 | let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap(); |
| 332 | |
| 333 | let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo).unwrap(); |
| 334 | assert_eq!(device_info, None); |
| 335 | } |
| 336 | |
| 337 | #[test] |
| 338 | fn device_info_assigned_info() { |
| 339 | let mut fdt_data = fs::read(FDT_FILE_PATH).unwrap(); |
| 340 | let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap(); |
| 341 | let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap(); |
| 342 | let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap(); |
| 343 | |
| 344 | let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo).unwrap().unwrap(); |
| 345 | |
| 346 | let expected = [AssignedDeviceInfo { |
| 347 | node_path: CString::new("/rng").unwrap(), |
| 348 | dtbo_node_path: cstr!("/fragment@rng/__overlay__/rng").into(), |
| 349 | reg: into_fdt_prop(vec![0x0, 0x9, 0x0, 0xFF]), |
| 350 | interrupts: into_fdt_prop(vec![0x0, 0xF, 0x4]), |
| 351 | }]; |
| 352 | |
| 353 | assert_eq!(device_info.assigned_devices, expected); |
| 354 | } |
| 355 | |
| 356 | #[test] |
| 357 | fn device_info_new_without_assigned_devices() { |
| 358 | let mut fdt_data: Vec<u8> = pvmfw_fdt_template::RAW.into(); |
| 359 | let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap(); |
| 360 | let fdt = Fdt::from_mut_slice(fdt_data.as_mut_slice()).unwrap(); |
| 361 | let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap(); |
| 362 | |
| 363 | let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo).unwrap(); |
| 364 | assert_eq!(device_info, None); |
| 365 | } |
| 366 | |
| 367 | #[test] |
| 368 | fn device_info_filter() { |
| 369 | let mut fdt_data = fs::read(FDT_FILE_PATH).unwrap(); |
| 370 | let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap(); |
| 371 | let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap(); |
| 372 | let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap(); |
| 373 | |
| 374 | let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo).unwrap().unwrap(); |
| 375 | device_info.filter(vm_dtbo).unwrap(); |
| 376 | |
| 377 | let vm_dtbo = vm_dtbo.as_mut(); |
| 378 | |
| 379 | let rng = vm_dtbo.node(cstr!("/fragment@rng/__overlay__/rng")).unwrap(); |
| 380 | assert_ne!(rng, None); |
| 381 | |
| 382 | let light = vm_dtbo.node(cstr!("/fragment@rng/__overlay__/light")).unwrap(); |
| 383 | assert_eq!(light, None); |
| 384 | |
| 385 | let symbols_node = vm_dtbo.symbols().unwrap(); |
| 386 | assert_eq!(symbols_node, None); |
| 387 | } |
| 388 | |
| 389 | #[test] |
| 390 | fn device_info_patch() { |
| 391 | let mut fdt_data = fs::read(FDT_FILE_PATH).unwrap(); |
| 392 | let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap(); |
| 393 | let mut data = vec![0_u8; fdt_data.len() + vm_dtbo_data.len()]; |
| 394 | let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap(); |
| 395 | let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap(); |
| 396 | let platform_dt = Fdt::create_empty_tree(data.as_mut_slice()).unwrap(); |
| 397 | |
| 398 | let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo).unwrap().unwrap(); |
| 399 | device_info.filter(vm_dtbo).unwrap(); |
| 400 | |
| 401 | // SAFETY: Damaged VM DTBO wouldn't be used after this unsafe block. |
| 402 | unsafe { |
| 403 | platform_dt.apply_overlay(vm_dtbo.as_mut()).unwrap(); |
| 404 | } |
| 405 | |
| 406 | let rng_node = platform_dt.node(cstr!("/rng")).unwrap().unwrap(); |
| 407 | let expected: Vec<(&CStr, Vec<u8>)> = vec![ |
| 408 | (cstr!("android,rng,ignore-gctrl-reset"), Vec::<u8>::new()), |
| 409 | (cstr!("compatible"), b"android,rng\0".to_vec()), |
| 410 | (cstr!("reg"), into_fdt_prop(vec![0x0, 0x9, 0x0, 0xFF])), |
| 411 | (cstr!("interrupts"), into_fdt_prop(vec![0x0, 0xF, 0x4])), |
| 412 | ]; |
| 413 | |
| 414 | for (prop, (prop_name, prop_value)) in rng_node.properties().unwrap().zip(expected) { |
| 415 | assert_eq!((prop.name(), prop.value()), (Ok(prop_name), Ok(prop_value.as_slice()))); |
| 416 | } |
| 417 | } |
| 418 | } |