blob: 3f84a8dfab353e1c626dcf71d1aec7c75439fcf8 [file] [log] [blame]
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001// 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)]
20extern crate alloc;
21
Jaewan Kim51ccfed2023-11-08 13:51:58 +090022use alloc::collections::{BTreeMap, BTreeSet};
Jaewan Kimc6e023b2023-10-12 15:11:05 +090023use alloc::ffi::CString;
24use alloc::fmt;
25use alloc::vec;
26use alloc::vec::Vec;
27use core::ffi::CStr;
28use core::iter::Iterator;
29use core::mem;
Jaewan Kim51ccfed2023-11-08 13:51:58 +090030use libfdt::{Fdt, FdtError, FdtNode, Phandle};
Jaewan Kimc6e023b2023-10-12 15:11:05 +090031
Jaewan Kimc6e023b2023-10-12 15:11:05 +090032// TODO(b/308694211): Use cstr! from vmbase instead.
33macro_rules! cstr {
34 ($str:literal) => {{
Pierre-Clément Tosid701a0b2023-11-07 15:38:59 +000035 const S: &str = concat!($str, "\0");
36 const C: &::core::ffi::CStr = match ::core::ffi::CStr::from_bytes_with_nul(S.as_bytes()) {
37 Ok(v) => v,
38 Err(_) => panic!("string contains interior NUL"),
39 };
40 C
Jaewan Kimc6e023b2023-10-12 15:11:05 +090041 }};
42}
43
Jaewan Kimc6e023b2023-10-12 15:11:05 +090044// TODO(b/277993056): Keep constants derived from platform.dts in one place.
45const CELLS_PER_INTERRUPT: usize = 3; // from /intc node in platform.dts
46
47/// Errors in device assignment.
48#[derive(Clone, Copy, Debug, Eq, PartialEq)]
49pub enum DeviceAssignmentError {
50 // Invalid VM DTBO
51 InvalidDtbo,
52 /// Invalid __symbols__
53 InvalidSymbols,
54 /// Invalid <interrupts>
55 InvalidInterrupts,
Jaewan Kim51ccfed2023-11-08 13:51:58 +090056 /// Invalid <iommus>
57 InvalidIommus,
58 /// Too many pvIOMMU
59 TooManyPvIommu,
60 /// Duplicated pvIOMMU IDs exist
61 DuplicatedPvIommuIds,
Jaewan Kimc6e023b2023-10-12 15:11:05 +090062 /// Unsupported overlay target syntax. Only supports <target-path> with full path.
63 UnsupportedOverlayTarget,
Jaewan Kim51ccfed2023-11-08 13:51:58 +090064 /// Internal error
65 Internal,
Jaewan Kimc6e023b2023-10-12 15:11:05 +090066 /// Unexpected error from libfdt
67 UnexpectedFdtError(FdtError),
68}
69
70impl From<FdtError> for DeviceAssignmentError {
71 fn from(e: FdtError) -> Self {
72 DeviceAssignmentError::UnexpectedFdtError(e)
73 }
74}
75
76impl fmt::Display for DeviceAssignmentError {
77 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
78 match self {
79 Self::InvalidDtbo => write!(f, "Invalid DTBO"),
80 Self::InvalidSymbols => write!(
81 f,
82 "Invalid property in /__symbols__. Must point to valid assignable device node."
83 ),
84 Self::InvalidInterrupts => write!(f, "Invalid <interrupts>"),
Jaewan Kim51ccfed2023-11-08 13:51:58 +090085 Self::InvalidIommus => write!(f, "Invalid <iommus>"),
86 Self::TooManyPvIommu => write!(
87 f,
88 "Too many pvIOMMU node. Insufficient pre-populated pvIOMMUs in platform DT"
89 ),
90 Self::DuplicatedPvIommuIds => {
91 write!(f, "Duplicated pvIOMMU IDs exist. IDs must unique")
92 }
Jaewan Kimc6e023b2023-10-12 15:11:05 +090093 Self::UnsupportedOverlayTarget => {
94 write!(f, "Unsupported overlay target. Only supports 'target-path = \"/\"'")
95 }
Jaewan Kim51ccfed2023-11-08 13:51:58 +090096 Self::Internal => write!(f, "Internal error"),
Jaewan Kimc6e023b2023-10-12 15:11:05 +090097 Self::UnexpectedFdtError(e) => write!(f, "Unexpected Error from libfdt: {e}"),
98 }
99 }
100}
101
102pub type Result<T> = core::result::Result<T, DeviceAssignmentError>;
103
104/// Represents VM DTBO
105#[repr(transparent)]
106pub struct VmDtbo(Fdt);
107
108impl VmDtbo {
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900109 /// Wraps a mutable slice containing a VM DTBO.
110 ///
111 /// Fails if the VM DTBO does not pass validation.
112 pub fn from_mut_slice(dtbo: &mut [u8]) -> Result<&mut Self> {
113 // This validates DTBO
114 let fdt = Fdt::from_mut_slice(dtbo)?;
115 // SAFETY: VmDtbo is a transparent wrapper around Fdt, so representation is the same.
116 Ok(unsafe { mem::transmute::<&mut Fdt, &mut Self>(fdt) })
117 }
118
119 // Locates device node path as if the given dtbo node path is assigned and VM DTBO is overlaid.
120 // For given dtbo node path, this concatenates <target-path> of the enclosing fragment and
121 // relative path from __overlay__ node.
122 //
123 // Here's an example with sample VM DTBO:
124 // / {
125 // fragment@rng {
126 // target-path = "/"; // Always 'target-path = "/"'. Disallows <target> or other path.
127 // __overlay__ {
128 // rng { ... }; // Actual device node is here. If overlaid, path would be "/rng"
129 // };
130 // };
131 // __symbols__ { // List of assignable devices
132 // // Each property describes an assigned device device information.
133 // // property name is the device label, and property value is the path in the VM DTBO.
134 // rng = "/fragment@rng/__overlay__/rng";
135 // };
136 // };
137 //
138 // Then locate_overlay_target_path(cstr!("/fragment@rng/__overlay__/rng")) is Ok("/rng")
139 //
140 // Contrary to fdt_overlay_target_offset(), this API enforces overlay target property
141 // 'target-path = "/"', so the overlay doesn't modify and/or append platform DT's existing
142 // node and/or properties. The enforcement is for compatibility reason.
143 fn locate_overlay_target_path(&self, dtbo_node_path: &CStr) -> Result<CString> {
144 let dtbo_node_path_bytes = dtbo_node_path.to_bytes();
145 if dtbo_node_path_bytes.first() != Some(&b'/') {
146 return Err(DeviceAssignmentError::UnsupportedOverlayTarget);
147 }
148
149 let node = self.0.node(dtbo_node_path)?.ok_or(DeviceAssignmentError::InvalidSymbols)?;
150
151 let fragment_node = node.supernode_at_depth(1)?;
152 let target_path = fragment_node
Pierre-Clément Tosid701a0b2023-11-07 15:38:59 +0000153 .getprop_str(cstr!("target-path"))?
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900154 .ok_or(DeviceAssignmentError::InvalidDtbo)?;
155 if target_path != cstr!("/") {
156 return Err(DeviceAssignmentError::UnsupportedOverlayTarget);
157 }
158
159 let mut components = dtbo_node_path_bytes
160 .split(|char| *char == b'/')
161 .filter(|&component| !component.is_empty())
162 .skip(1);
163 let overlay_node_name = components.next();
Pierre-Clément Tosid701a0b2023-11-07 15:38:59 +0000164 if overlay_node_name != Some(b"__overlay__") {
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900165 return Err(DeviceAssignmentError::InvalidDtbo);
166 }
167 let mut overlaid_path = Vec::with_capacity(dtbo_node_path_bytes.len());
168 for component in components {
169 overlaid_path.push(b'/');
170 overlaid_path.extend_from_slice(component);
171 }
172 overlaid_path.push(b'\0');
173
174 Ok(CString::from_vec_with_nul(overlaid_path).unwrap())
175 }
176}
177
178impl AsRef<Fdt> for VmDtbo {
179 fn as_ref(&self) -> &Fdt {
180 &self.0
181 }
182}
183
184impl AsMut<Fdt> for VmDtbo {
185 fn as_mut(&mut self) -> &mut Fdt {
186 &mut self.0
187 }
188}
189
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900190#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
191struct PvIommu {
192 // ID from pvIOMMU node
193 id: u32,
194}
195
196impl PvIommu {
197 fn parse(node: &FdtNode) -> Result<Self> {
198 let id = node.getprop_u32(cstr!("id"))?.ok_or(DeviceAssignmentError::InvalidIommus)?;
199 Ok(Self { id })
200 }
201}
202
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900203/// Assigned device information parsed from crosvm DT.
204/// Keeps everything in the owned data because underlying FDT will be reused for platform DT.
205#[derive(Debug, Eq, PartialEq)]
206struct AssignedDeviceInfo {
207 // Node path of assigned device (e.g. "/rng")
208 node_path: CString,
209 // DTBO node path of the assigned device (e.g. "/fragment@rng/__overlay__/rng")
210 dtbo_node_path: CString,
211 // <reg> property from the crosvm DT
212 reg: Vec<u8>,
213 // <interrupts> property from the crosvm DT
214 interrupts: Vec<u8>,
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900215 // Parsed <iommus> property from the crosvm DT.
216 iommus: Vec<PvIommu>,
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900217}
218
219impl AssignedDeviceInfo {
220 fn parse_interrupts(node: &FdtNode) -> Result<Vec<u8>> {
221 // Validation: Validate if interrupts cell numbers are multiple of #interrupt-cells.
222 // We can't know how many interrupts would exist.
223 let interrupts_cells = node
Pierre-Clément Tosid701a0b2023-11-07 15:38:59 +0000224 .getprop_cells(cstr!("interrupts"))?
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900225 .ok_or(DeviceAssignmentError::InvalidInterrupts)?
226 .count();
227 if interrupts_cells % CELLS_PER_INTERRUPT != 0 {
228 return Err(DeviceAssignmentError::InvalidInterrupts);
229 }
230
231 // Once validated, keep the raw bytes so patch can be done with setprop()
Pierre-Clément Tosid701a0b2023-11-07 15:38:59 +0000232 Ok(node.getprop(cstr!("interrupts")).unwrap().unwrap().into())
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900233 }
234
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900235 // TODO(b/277993056): Also validate /__local_fixups__ to ensure that <iommus> has phandle.
236 // TODO(b/277993056): Also keep vSID.
237 // TODO(b/277993056): Validate #iommu-cells values.
238 fn parse_iommus(node: &FdtNode, pviommus: &BTreeMap<Phandle, PvIommu>) -> Result<Vec<PvIommu>> {
239 let mut iommus = vec![];
240 let Some(cells) = node.getprop_cells(cstr!("iommus"))? else {
241 return Ok(iommus);
242 };
243 for cell in cells {
244 let phandle = Phandle::try_from(cell).or(Err(DeviceAssignmentError::InvalidIommus))?;
245 let pviommu = pviommus.get(&phandle).ok_or(DeviceAssignmentError::InvalidIommus)?;
246 iommus.push(*pviommu)
247 }
248 Ok(iommus)
249 }
250
251 fn parse(
252 fdt: &Fdt,
253 vm_dtbo: &VmDtbo,
254 dtbo_node_path: &CStr,
255 pviommus: &BTreeMap<Phandle, PvIommu>,
256 ) -> Result<Option<Self>> {
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900257 let node_path = vm_dtbo.locate_overlay_target_path(dtbo_node_path)?;
258
259 let Some(node) = fdt.node(&node_path)? else { return Ok(None) };
260
261 // TODO(b/277993056): Validate reg with HVC, and keep reg with FdtNode::reg()
Pierre-Clément Tosid701a0b2023-11-07 15:38:59 +0000262 let reg = node.getprop(cstr!("reg")).unwrap().unwrap();
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900263 let interrupts = Self::parse_interrupts(&node)?;
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900264 let iommus = Self::parse_iommus(&node, pviommus)?;
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900265 Ok(Some(Self {
266 node_path,
267 dtbo_node_path: dtbo_node_path.into(),
268 reg: reg.to_vec(),
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900269 interrupts,
270 iommus,
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900271 }))
272 }
273
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900274 fn patch(&self, fdt: &mut Fdt, pviommu_phandles: &BTreeMap<PvIommu, Phandle>) -> Result<()> {
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900275 let mut dst = fdt.node_mut(&self.node_path)?.unwrap();
Pierre-Clément Tosid701a0b2023-11-07 15:38:59 +0000276 dst.setprop(cstr!("reg"), &self.reg)?;
277 dst.setprop(cstr!("interrupts"), &self.interrupts)?;
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900278
279 let iommus: Vec<u8> = self
280 .iommus
281 .iter()
282 .flat_map(|pviommu| {
283 let phandle = pviommu_phandles.get(pviommu).unwrap();
284 u32::from(*phandle).to_be_bytes()
285 })
286 .collect();
287 dst.setprop(cstr!("iommus"), &iommus)?;
288
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900289 Ok(())
290 }
291}
292
293#[derive(Debug, Default, Eq, PartialEq)]
294pub struct DeviceAssignmentInfo {
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900295 pviommus: BTreeSet<PvIommu>,
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900296 assigned_devices: Vec<AssignedDeviceInfo>,
297 filtered_dtbo_paths: Vec<CString>,
298}
299
300impl DeviceAssignmentInfo {
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900301 const PVIOMMU_COMPATIBLE: &CStr = cstr!("pkvm,pviommu");
302
303 /// Parses pvIOMMUs in fdt
304 // Note: This will validate pvIOMMU ids' uniqueness, even when unassigned.
305 fn parse_pviommus(fdt: &Fdt) -> Result<BTreeMap<Phandle, PvIommu>> {
306 // TODO(b/277993056): Validated `<#iommu-cells>`.
307 let mut pviommus = BTreeMap::new();
308 for compatible in fdt.compatible_nodes(Self::PVIOMMU_COMPATIBLE)? {
309 let Some(phandle) = compatible.get_phandle()? else {
310 continue; // Skips unreachable pvIOMMU node
311 };
312 let pviommu = PvIommu::parse(&compatible)?;
313 if pviommus.insert(phandle, pviommu).is_some() {
314 return Err(FdtError::BadPhandle.into());
315 }
316 }
317 Ok(pviommus)
318 }
319
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900320 /// Parses fdt and vm_dtbo, and creates new DeviceAssignmentInfo
321 // TODO(b/277993056): Parse __local_fixups__
322 // TODO(b/277993056): Parse __fixups__
323 pub fn parse(fdt: &Fdt, vm_dtbo: &VmDtbo) -> Result<Option<Self>> {
324 let Some(symbols_node) = vm_dtbo.as_ref().symbols()? else {
325 // /__symbols__ should contain all assignable devices.
326 // If empty, then nothing can be assigned.
327 return Ok(None);
328 };
329
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900330 let pviommus = Self::parse_pviommus(fdt)?;
331 let unique_pviommus: BTreeSet<_> = pviommus.values().cloned().collect();
332 if pviommus.len() != unique_pviommus.len() {
333 return Err(DeviceAssignmentError::DuplicatedPvIommuIds);
334 }
335
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900336 let mut assigned_devices = vec![];
337 let mut filtered_dtbo_paths = vec![];
338 for symbol_prop in symbols_node.properties()? {
339 let symbol_prop_value = symbol_prop.value()?;
340 let dtbo_node_path = CStr::from_bytes_with_nul(symbol_prop_value)
341 .or(Err(DeviceAssignmentError::InvalidSymbols))?;
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900342 let assigned_device =
343 AssignedDeviceInfo::parse(fdt, vm_dtbo, dtbo_node_path, &pviommus)?;
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900344 if let Some(assigned_device) = assigned_device {
345 assigned_devices.push(assigned_device);
346 } else {
347 filtered_dtbo_paths.push(dtbo_node_path.into());
348 }
349 }
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900350 if assigned_devices.is_empty() {
351 return Ok(None);
352 }
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900353 filtered_dtbo_paths.push(CString::new("/__symbols__").unwrap());
354
355 Ok(Some(Self { pviommus: unique_pviommus, assigned_devices, filtered_dtbo_paths }))
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900356 }
357
358 /// Filters VM DTBO to only contain necessary information for booting pVM
359 /// In detail, this will remove followings by setting nop node / nop property.
360 /// - Removes unassigned devices
361 /// - Removes /__symbols__ node
362 // TODO(b/277993056): remove unused dependencies in VM DTBO.
363 // TODO(b/277993056): remove supernodes' properties.
364 // TODO(b/277993056): remove unused alises.
365 pub fn filter(&self, vm_dtbo: &mut VmDtbo) -> Result<()> {
366 let vm_dtbo = vm_dtbo.as_mut();
367
368 // Filters unused node in assigned devices
369 for filtered_dtbo_path in &self.filtered_dtbo_paths {
370 let node = vm_dtbo.node_mut(filtered_dtbo_path).unwrap().unwrap();
371 node.nop()?;
372 }
373
Pierre-Clément Tosifc3e8b52023-11-08 14:30:29 +0000374 // Filters pvmfw-specific properties in assigned device node.
375 const FILTERED_VM_DTBO_PROP: [&CStr; 3] = [
376 cstr!("android,pvmfw,phy-reg"),
377 cstr!("android,pvmfw,phy-iommu"),
378 cstr!("android,pvmfw,phy-sid"),
379 ];
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900380 for assigned_device in &self.assigned_devices {
381 let mut node = vm_dtbo.node_mut(&assigned_device.dtbo_node_path).unwrap().unwrap();
382 for prop in FILTERED_VM_DTBO_PROP {
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900383 match node.nop_property(prop) {
384 Err(FdtError::NotFound) => Ok(()), // allows not exists
385 other => other,
386 }?;
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900387 }
388 }
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900389
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900390 Ok(())
391 }
392
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900393 fn patch_pviommus(&self, fdt: &mut Fdt) -> Result<BTreeMap<PvIommu, Phandle>> {
394 let mut compatible = fdt.root_mut()?.next_compatible(Self::PVIOMMU_COMPATIBLE)?;
395 let mut pviommu_phandles = BTreeMap::new();
396
397 for pviommu in &self.pviommus {
398 let mut node = compatible.ok_or(DeviceAssignmentError::TooManyPvIommu)?;
399 let phandle = node.as_node().get_phandle()?.ok_or(DeviceAssignmentError::Internal)?;
400 node.setprop_inplace(cstr!("id"), &pviommu.id.to_be_bytes())?;
401 if pviommu_phandles.insert(*pviommu, phandle).is_some() {
402 return Err(DeviceAssignmentError::Internal);
403 }
404 compatible = node.next_compatible(Self::PVIOMMU_COMPATIBLE)?;
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900405 }
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900406
407 // Filters pre-populated but unassigned pvIOMMUs.
408 while let Some(filtered_pviommu) = compatible {
409 compatible = filtered_pviommu.delete_and_next_compatible(Self::PVIOMMU_COMPATIBLE)?;
410 }
411
412 Ok(pviommu_phandles)
413 }
414
415 pub fn patch(&self, fdt: &mut Fdt) -> Result<()> {
416 let pviommu_phandles = self.patch_pviommus(fdt)?;
417
418 // Patches assigned devices
419 for device in &self.assigned_devices {
420 device.patch(fdt, &pviommu_phandles)?;
421 }
422
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900423 Ok(())
424 }
425}
426
427#[cfg(test)]
428mod tests {
429 use super::*;
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900430 use alloc::collections::BTreeSet;
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900431 use std::fs;
432
433 const VM_DTBO_FILE_PATH: &str = "test_pvmfw_devices_vm_dtbo.dtbo";
434 const VM_DTBO_WITHOUT_SYMBOLS_FILE_PATH: &str =
435 "test_pvmfw_devices_vm_dtbo_without_symbols.dtbo";
436 const FDT_FILE_PATH: &str = "test_pvmfw_devices_with_rng.dtb";
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900437 const FDT_WITH_IOMMU_FILE_PATH: &str = "test_pvmfw_devices_with_rng_iommu.dtb";
438 const FDT_WITH_MULTIPLE_DEVICES_IOMMUS_FILE_PATH: &str =
439 "test_pvmfw_devices_with_multiple_devices_iommus.dtb";
440 const FDT_WITH_IOMMU_SHARING: &str = "test_pvmfw_devices_with_iommu_sharing.dtb";
441 const FDT_WITH_IOMMU_ID_CONFLICT: &str = "test_pvmfw_devices_with_iommu_id_conflict.dtb";
442
443 #[derive(Debug, Eq, PartialEq)]
444 struct AssignedDeviceNode {
445 path: CString,
446 reg: Vec<u8>,
447 interrupts: Vec<u8>,
448 iommus: Vec<u32>, // pvIOMMU ids
449 }
450
451 impl AssignedDeviceNode {
452 fn parse(fdt: &Fdt, path: &CStr) -> Result<Self> {
453 let Some(node) = fdt.node(path)? else {
454 return Err(FdtError::NotFound.into());
455 };
456
457 // TODO(b/277993056): Replace DeviceAssignmentError::Internal
458 let reg = node.getprop(cstr!("reg"))?.ok_or(DeviceAssignmentError::Internal)?;
459 let interrupts = node
460 .getprop(cstr!("interrupts"))?
461 .ok_or(DeviceAssignmentError::InvalidInterrupts)?;
462 let mut iommus = vec![];
463 if let Some(cells) = node.getprop_cells(cstr!("iommus"))? {
464 for cell in cells {
465 let phandle = Phandle::try_from(cell)?;
466 let pviommu = fdt
467 .node_with_phandle(phandle)?
468 .ok_or(DeviceAssignmentError::InvalidIommus)?;
469 let compatible = pviommu.getprop_str(cstr!("compatible"));
470 if compatible != Ok(Some(cstr!("pkvm,pviommu"))) {
471 return Err(DeviceAssignmentError::InvalidIommus);
472 }
473 let id = pviommu
474 .getprop_u32(cstr!("id"))?
475 .ok_or(DeviceAssignmentError::InvalidIommus)?;
476 iommus.push(id);
477 }
478 }
479 Ok(Self { path: path.into(), reg: reg.into(), interrupts: interrupts.into(), iommus })
480 }
481 }
482
483 fn collect_pviommus(fdt: &Fdt) -> Result<Vec<u32>> {
484 let mut pviommus = BTreeSet::new();
485 for pviommu in fdt.compatible_nodes(cstr!("pkvm,pviommu"))? {
486 if let Ok(Some(id)) = pviommu.getprop_u32(cstr!("id")) {
487 pviommus.insert(id);
488 }
489 }
490 Ok(pviommus.iter().cloned().collect())
491 }
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900492
493 fn into_fdt_prop(native_bytes: Vec<u32>) -> Vec<u8> {
494 let mut v = Vec::with_capacity(native_bytes.len() * 4);
495 for byte in native_bytes {
496 v.extend_from_slice(&byte.to_be_bytes());
497 }
498 v
499 }
500
501 #[test]
502 fn device_info_new_without_symbols() {
503 let mut fdt_data = fs::read(FDT_FILE_PATH).unwrap();
504 let mut vm_dtbo_data = fs::read(VM_DTBO_WITHOUT_SYMBOLS_FILE_PATH).unwrap();
505 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
506 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
507
508 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo).unwrap();
509 assert_eq!(device_info, None);
510 }
511
512 #[test]
513 fn device_info_assigned_info() {
514 let mut fdt_data = fs::read(FDT_FILE_PATH).unwrap();
515 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
516 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
517 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
518
519 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo).unwrap().unwrap();
520
521 let expected = [AssignedDeviceInfo {
522 node_path: CString::new("/rng").unwrap(),
523 dtbo_node_path: cstr!("/fragment@rng/__overlay__/rng").into(),
524 reg: into_fdt_prop(vec![0x0, 0x9, 0x0, 0xFF]),
525 interrupts: into_fdt_prop(vec![0x0, 0xF, 0x4]),
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900526 iommus: vec![],
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900527 }];
528
529 assert_eq!(device_info.assigned_devices, expected);
530 }
531
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900532 // TODO(b/311655051): Test with real once instead of empty FDT.
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900533 #[test]
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900534 fn device_info_new_with_empty_device_tree() {
535 let mut fdt_data = vec![0; pvmfw_fdt_template::RAW.len()];
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900536 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900537 let fdt = Fdt::create_empty_tree(&mut fdt_data).unwrap();
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900538 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
539
540 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo).unwrap();
541 assert_eq!(device_info, None);
542 }
543
544 #[test]
545 fn device_info_filter() {
546 let mut fdt_data = fs::read(FDT_FILE_PATH).unwrap();
547 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
548 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
549 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
550
551 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo).unwrap().unwrap();
552 device_info.filter(vm_dtbo).unwrap();
553
554 let vm_dtbo = vm_dtbo.as_mut();
555
556 let rng = vm_dtbo.node(cstr!("/fragment@rng/__overlay__/rng")).unwrap();
557 assert_ne!(rng, None);
558
559 let light = vm_dtbo.node(cstr!("/fragment@rng/__overlay__/light")).unwrap();
560 assert_eq!(light, None);
561
562 let symbols_node = vm_dtbo.symbols().unwrap();
563 assert_eq!(symbols_node, None);
564 }
565
566 #[test]
567 fn device_info_patch() {
568 let mut fdt_data = fs::read(FDT_FILE_PATH).unwrap();
569 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
570 let mut data = vec![0_u8; fdt_data.len() + vm_dtbo_data.len()];
571 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
572 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
573 let platform_dt = Fdt::create_empty_tree(data.as_mut_slice()).unwrap();
574
575 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo).unwrap().unwrap();
576 device_info.filter(vm_dtbo).unwrap();
577
578 // SAFETY: Damaged VM DTBO wouldn't be used after this unsafe block.
579 unsafe {
580 platform_dt.apply_overlay(vm_dtbo.as_mut()).unwrap();
581 }
Jaewan Kim0bd637d2023-11-10 13:09:41 +0900582 device_info.patch(platform_dt).unwrap();
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900583
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900584 // Note: Intentionally not using AssignedDeviceNode for matching all props.
Jaewan Kim0bd637d2023-11-10 13:09:41 +0900585 type FdtResult<T> = libfdt::Result<T>;
586 let expected: Vec<(FdtResult<&CStr>, FdtResult<Vec<u8>>)> = vec![
587 (Ok(cstr!("android,rng,ignore-gctrl-reset")), Ok(Vec::new())),
588 (Ok(cstr!("compatible")), Ok(Vec::from(*b"android,rng\0"))),
589 (Ok(cstr!("interrupts")), Ok(into_fdt_prop(vec![0x0, 0xF, 0x4]))),
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900590 (Ok(cstr!("iommus")), Ok(Vec::new())),
Jaewan Kim0bd637d2023-11-10 13:09:41 +0900591 (Ok(cstr!("reg")), Ok(into_fdt_prop(vec![0x0, 0x9, 0x0, 0xFF]))),
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900592 ];
593
Jaewan Kim0bd637d2023-11-10 13:09:41 +0900594 let rng_node = platform_dt.node(cstr!("/rng")).unwrap().unwrap();
595 let mut properties: Vec<_> = rng_node
596 .properties()
597 .unwrap()
598 .map(|prop| (prop.name(), prop.value().map(|x| x.into())))
599 .collect();
600 properties.sort_by(|a, b| {
601 let lhs = a.0.unwrap_or_default();
602 let rhs = b.0.unwrap_or_default();
603 lhs.partial_cmp(rhs).unwrap()
604 });
605
606 assert_eq!(properties, expected);
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900607 }
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900608
609 #[test]
610 fn device_info_overlay_iommu() {
611 let mut fdt_data = fs::read(FDT_WITH_IOMMU_FILE_PATH).unwrap();
612 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
613 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
614 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
615 let mut platform_dt_data = pvmfw_fdt_template::RAW.to_vec();
616 platform_dt_data.resize(pvmfw_fdt_template::RAW.len() * 2, 0);
617 let platform_dt = Fdt::from_mut_slice(&mut platform_dt_data).unwrap();
618 platform_dt.unpack().unwrap();
619
620 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo).unwrap().unwrap();
621 device_info.filter(vm_dtbo).unwrap();
622
623 // SAFETY: Damaged VM DTBO wouldn't be used after this unsafe block.
624 unsafe {
625 platform_dt.apply_overlay(vm_dtbo.as_mut()).unwrap();
626 }
627 device_info.patch(platform_dt).unwrap();
628
629 let expected = AssignedDeviceNode {
630 path: CString::new("/rng").unwrap(),
631 reg: into_fdt_prop(vec![0x0, 0x9, 0x0, 0xFF]),
632 interrupts: into_fdt_prop(vec![0x0, 0xF, 0x4]),
633 iommus: vec![0x4],
634 };
635
636 let node = AssignedDeviceNode::parse(platform_dt, &expected.path);
637 assert_eq!(node, Ok(expected));
638
639 let pviommus = collect_pviommus(platform_dt);
640 assert_eq!(pviommus, Ok(vec![0x4]));
641 }
642
643 #[test]
644 fn device_info_multiple_devices_iommus() {
645 let mut fdt_data = fs::read(FDT_WITH_MULTIPLE_DEVICES_IOMMUS_FILE_PATH).unwrap();
646 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
647 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
648 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
649 let mut platform_dt_data = pvmfw_fdt_template::RAW.to_vec();
650 platform_dt_data.resize(pvmfw_fdt_template::RAW.len() * 2, 0);
651 let platform_dt = Fdt::from_mut_slice(&mut platform_dt_data).unwrap();
652 platform_dt.unpack().unwrap();
653
654 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo).unwrap().unwrap();
655 device_info.filter(vm_dtbo).unwrap();
656
657 // SAFETY: Damaged VM DTBO wouldn't be used after this unsafe block.
658 unsafe {
659 platform_dt.apply_overlay(vm_dtbo.as_mut()).unwrap();
660 }
661 device_info.patch(platform_dt).unwrap();
662
663 let expected_devices = [
664 AssignedDeviceNode {
665 path: CString::new("/rng").unwrap(),
666 reg: into_fdt_prop(vec![0x0, 0x9, 0x0, 0xFF]),
667 interrupts: into_fdt_prop(vec![0x0, 0xF, 0x4]),
668 iommus: vec![0x4, 0x9],
669 },
670 AssignedDeviceNode {
671 path: CString::new("/light").unwrap(),
672 reg: into_fdt_prop(vec![0x100, 0x9]),
673 interrupts: into_fdt_prop(vec![0x0, 0xF, 0x5]),
674 iommus: vec![0x40, 0x50, 0x60],
675 },
676 ];
677
678 for expected in expected_devices {
679 let node = AssignedDeviceNode::parse(platform_dt, &expected.path);
680 assert_eq!(node, Ok(expected));
681 }
682 let pviommus = collect_pviommus(platform_dt);
683 assert_eq!(pviommus, Ok(vec![0x4, 0x9, 0x40, 0x50, 0x60]));
684 }
685
686 #[test]
687 fn device_info_iommu_sharing() {
688 let mut fdt_data = fs::read(FDT_WITH_IOMMU_SHARING).unwrap();
689 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
690 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
691 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
692 let mut platform_dt_data = pvmfw_fdt_template::RAW.to_vec();
693 platform_dt_data.resize(pvmfw_fdt_template::RAW.len() * 2, 0);
694 let platform_dt = Fdt::from_mut_slice(&mut platform_dt_data).unwrap();
695 platform_dt.unpack().unwrap();
696
697 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo).unwrap().unwrap();
698 device_info.filter(vm_dtbo).unwrap();
699
700 // SAFETY: Damaged VM DTBO wouldn't be used after this unsafe block.
701 unsafe {
702 platform_dt.apply_overlay(vm_dtbo.as_mut()).unwrap();
703 }
704 device_info.patch(platform_dt).unwrap();
705
706 let expected_devices = [
707 AssignedDeviceNode {
708 path: CString::new("/rng").unwrap(),
709 reg: into_fdt_prop(vec![0x0, 0x9, 0x0, 0xFF]),
710 interrupts: into_fdt_prop(vec![0x0, 0xF, 0x4]),
711 iommus: vec![0x4, 0x9],
712 },
713 AssignedDeviceNode {
714 path: CString::new("/light").unwrap(),
715 reg: into_fdt_prop(vec![0x100, 0x9]),
716 interrupts: into_fdt_prop(vec![0x0, 0xF, 0x5]),
717 iommus: vec![0x9, 0x40],
718 },
719 ];
720
721 for expected in expected_devices {
722 let node = AssignedDeviceNode::parse(platform_dt, &expected.path);
723 assert_eq!(node, Ok(expected));
724 }
725
726 let pviommus = collect_pviommus(platform_dt);
727 assert_eq!(pviommus, Ok(vec![0x4, 0x9, 0x40]));
728 }
729
730 #[test]
731 fn device_info_iommu_id_conflict() {
732 let mut fdt_data = fs::read(FDT_WITH_IOMMU_ID_CONFLICT).unwrap();
733 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
734 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
735 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
736
737 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo);
738
739 assert_eq!(device_info, Err(DeviceAssignmentError::DuplicatedPvIommuIds));
740 }
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900741}