blob: 6c3dcb8c3c6f9cf1ca304b54541a61669dcb32c3 [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;
Pierre-Clément Tosi49e26ce2024-03-12 16:31:50 +000030use core::ops::Range;
Jaewan Kim8f6f4662023-12-12 17:38:47 +090031use libfdt::{Fdt, FdtError, FdtNode, FdtNodeMut, Phandle, Reg};
Jaewan Kim52477ae2023-11-21 21:20:52 +090032use log::error;
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +010033use log::warn;
Pierre-Clément Tosia9b345f2024-04-27 01:01:42 +010034// TODO(b/308694211): Use vmbase::hyp::{DeviceAssigningHypervisor, Error} proper for tests.
35#[cfg(not(test))]
36use vmbase::hyp::DeviceAssigningHypervisor;
Jaewan Kim8f6f4662023-12-12 17:38:47 +090037use zerocopy::byteorder::big_endian::U32;
38use zerocopy::FromBytes as _;
Jaewan Kimc6e023b2023-10-12 15:11:05 +090039
Jaewan Kimc6e023b2023-10-12 15:11:05 +090040// TODO(b/308694211): Use cstr! from vmbase instead.
41macro_rules! cstr {
42 ($str:literal) => {{
Pierre-Clément Tosid701a0b2023-11-07 15:38:59 +000043 const S: &str = concat!($str, "\0");
44 const C: &::core::ffi::CStr = match ::core::ffi::CStr::from_bytes_with_nul(S.as_bytes()) {
45 Ok(v) => v,
46 Err(_) => panic!("string contains interior NUL"),
47 };
48 C
Jaewan Kimc6e023b2023-10-12 15:11:05 +090049 }};
50}
51
Jaewan Kimc6e023b2023-10-12 15:11:05 +090052// TODO(b/277993056): Keep constants derived from platform.dts in one place.
53const CELLS_PER_INTERRUPT: usize = 3; // from /intc node in platform.dts
54
55/// Errors in device assignment.
56#[derive(Clone, Copy, Debug, Eq, PartialEq)]
57pub enum DeviceAssignmentError {
Jaewan Kim52477ae2023-11-21 21:20:52 +090058 /// Invalid VM DTBO
Jaewan Kimc6e023b2023-10-12 15:11:05 +090059 InvalidDtbo,
60 /// Invalid __symbols__
61 InvalidSymbols,
Jaewan Kim19b984f2023-12-04 15:16:50 +090062 /// Malformed <reg>. Can't parse.
63 MalformedReg,
Pierre-Clément Tosif4d8a0d2024-10-25 17:14:15 +010064 /// Missing physical <reg> of assigned device.
65 MissingReg(u64, u64),
66 /// Extra <reg> of assigned device.
67 ExtraReg(u64, u64),
Pierre-Clément Tosi8b78bc32024-03-13 17:37:07 +000068 /// Invalid virtual <reg> of assigned device.
Pierre-Clément Tosif4d8a0d2024-10-25 17:14:15 +010069 InvalidReg(u64),
70 /// Token for <reg> of assigned device does not match expected value.
71 InvalidRegToken(u64, u64),
Jaewan Kimc6e023b2023-10-12 15:11:05 +090072 /// Invalid <interrupts>
73 InvalidInterrupts,
Jaewan Kim19b984f2023-12-04 15:16:50 +090074 /// Malformed <iommus>
75 MalformedIommus,
Jaewan Kim51ccfed2023-11-08 13:51:58 +090076 /// Invalid <iommus>
77 InvalidIommus,
Jaewan Kim19b984f2023-12-04 15:16:50 +090078 /// Invalid phys IOMMU node
79 InvalidPhysIommu,
Jaewan Kima9200492023-11-21 20:45:31 +090080 /// Invalid pvIOMMU node
81 InvalidPvIommu,
Jaewan Kim51ccfed2023-11-08 13:51:58 +090082 /// Too many pvIOMMU
83 TooManyPvIommu,
Jaewan Kim19b984f2023-12-04 15:16:50 +090084 /// Duplicated phys IOMMU IDs exist
85 DuplicatedIommuIds,
Jaewan Kim51ccfed2023-11-08 13:51:58 +090086 /// Duplicated pvIOMMU IDs exist
87 DuplicatedPvIommuIds,
Jaewan Kimf8abbb52023-12-12 22:11:39 +090088 /// Unsupported path format. Only supports full path.
89 UnsupportedPathFormat,
Jaewan Kimc6e023b2023-10-12 15:11:05 +090090 /// Unsupported overlay target syntax. Only supports <target-path> with full path.
91 UnsupportedOverlayTarget,
Jaewan Kim19b984f2023-12-04 15:16:50 +090092 /// Unsupported PhysIommu,
93 UnsupportedPhysIommu,
94 /// Unsupported (pvIOMMU id, vSID) duplication. Currently the pair should be unique.
95 UnsupportedPvIommusDuplication,
96 /// Unsupported (IOMMU token, SID) duplication. Currently the pair should be unique.
97 UnsupportedIommusDuplication,
Jaewan Kim51ccfed2023-11-08 13:51:58 +090098 /// Internal error
99 Internal,
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900100 /// Unexpected error from libfdt
101 UnexpectedFdtError(FdtError),
102}
103
104impl From<FdtError> for DeviceAssignmentError {
105 fn from(e: FdtError) -> Self {
106 DeviceAssignmentError::UnexpectedFdtError(e)
107 }
108}
109
110impl fmt::Display for DeviceAssignmentError {
111 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
112 match self {
113 Self::InvalidDtbo => write!(f, "Invalid DTBO"),
114 Self::InvalidSymbols => write!(
115 f,
116 "Invalid property in /__symbols__. Must point to valid assignable device node."
117 ),
Jaewan Kim19b984f2023-12-04 15:16:50 +0900118 Self::MalformedReg => write!(f, "Malformed <reg>. Can't parse"),
Pierre-Clément Tosif4d8a0d2024-10-25 17:14:15 +0100119 Self::MissingReg(addr, size) => {
120 write!(f, "Missing physical MMIO region: addr:{addr:#x}), size:{size:#x}")
Pierre-Clément Tosi8b78bc32024-03-13 17:37:07 +0000121 }
Pierre-Clément Tosif4d8a0d2024-10-25 17:14:15 +0100122 Self::ExtraReg(addr, size) => {
123 write!(f, "Unexpected extra MMIO region: addr:{addr:#x}), size:{size:#x}")
124 }
125 Self::InvalidReg(addr) => {
126 write!(f, "Invalid guest MMIO granule (addr: {addr:#x})")
127 }
128 Self::InvalidRegToken(token, expected) => {
129 write!(f, "Unexpected MMIO token ({token:#x}), should be {expected:#x}")
Pierre-Clément Tosi8b78bc32024-03-13 17:37:07 +0000130 }
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900131 Self::InvalidInterrupts => write!(f, "Invalid <interrupts>"),
Jaewan Kim19b984f2023-12-04 15:16:50 +0900132 Self::MalformedIommus => write!(f, "Malformed <iommus>. Can't parse."),
133 Self::InvalidIommus => {
134 write!(f, "Invalid <iommus>. Failed to validate with hypervisor")
135 }
136 Self::InvalidPhysIommu => write!(f, "Invalid phys IOMMU node"),
Jaewan Kima9200492023-11-21 20:45:31 +0900137 Self::InvalidPvIommu => write!(f, "Invalid pvIOMMU node"),
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900138 Self::TooManyPvIommu => write!(
139 f,
140 "Too many pvIOMMU node. Insufficient pre-populated pvIOMMUs in platform DT"
141 ),
Jaewan Kim19b984f2023-12-04 15:16:50 +0900142 Self::DuplicatedIommuIds => {
143 write!(f, "Duplicated IOMMU IDs exist. IDs must unique among iommu node")
144 }
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900145 Self::DuplicatedPvIommuIds => {
Jaewan Kim19b984f2023-12-04 15:16:50 +0900146 write!(f, "Duplicated pvIOMMU IDs exist. IDs must unique among iommu node")
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900147 }
Jaewan Kimf8abbb52023-12-12 22:11:39 +0900148 Self::UnsupportedPathFormat => {
149 write!(f, "Unsupported UnsupportedPathFormat. Only supports full path")
150 }
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900151 Self::UnsupportedOverlayTarget => {
152 write!(f, "Unsupported overlay target. Only supports 'target-path = \"/\"'")
153 }
Jaewan Kim19b984f2023-12-04 15:16:50 +0900154 Self::UnsupportedPhysIommu => {
155 write!(f, "Unsupported Phys IOMMU. Currently only supports #iommu-cells = <1>")
156 }
157 Self::UnsupportedPvIommusDuplication => {
158 write!(f, "Unsupported (pvIOMMU id, vSID) duplication. Currently the pair should be unique.")
159 }
160 Self::UnsupportedIommusDuplication => {
161 write!(f, "Unsupported (IOMMU token, SID) duplication. Currently the pair should be unique.")
162 }
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900163 Self::Internal => write!(f, "Internal error"),
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900164 Self::UnexpectedFdtError(e) => write!(f, "Unexpected Error from libfdt: {e}"),
165 }
166 }
167}
168
169pub type Result<T> = core::result::Result<T, DeviceAssignmentError>;
170
Jaewan Kimf8abbb52023-12-12 22:11:39 +0900171#[derive(Clone, Default, Ord, PartialOrd, Eq, PartialEq)]
172pub struct DtPathTokens<'a> {
173 tokens: Vec<&'a [u8]>,
174}
175
176impl<'a> fmt::Debug for DtPathTokens<'a> {
177 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
178 let mut list = f.debug_list();
179 for token in &self.tokens {
180 let mut bytes = token.to_vec();
181 bytes.push(b'\0');
182 match CString::from_vec_with_nul(bytes) {
183 Ok(string) => list.entry(&string),
184 Err(_) => list.entry(token),
185 };
186 }
187 list.finish()
188 }
189}
190
191impl<'a> DtPathTokens<'a> {
192 fn new(path: &'a CStr) -> Result<Self> {
193 if path.to_bytes().first() != Some(&b'/') {
194 return Err(DeviceAssignmentError::UnsupportedPathFormat);
195 }
196 let tokens: Vec<_> = path
197 .to_bytes()
198 .split(|char| *char == b'/')
199 .filter(|&component| !component.is_empty())
200 .collect();
201 Ok(Self { tokens })
202 }
203
204 fn to_overlay_target_path(&self) -> Result<Self> {
205 if !self.is_overlayable_node() {
206 return Err(DeviceAssignmentError::InvalidDtbo);
207 }
208 Ok(Self { tokens: self.tokens.as_slice()[2..].to_vec() })
209 }
210
211 fn to_cstring(&self) -> CString {
212 if self.tokens.is_empty() {
213 return CString::new(*b"/\0").unwrap();
214 }
215
216 let size = self.tokens.iter().fold(0, |sum, token| sum + token.len() + 1);
217 let mut path = Vec::with_capacity(size + 1);
218 for token in &self.tokens {
219 path.push(b'/');
220 path.extend_from_slice(token);
221 }
222 path.push(b'\0');
223
224 CString::from_vec_with_nul(path).unwrap()
225 }
226
227 fn is_overlayable_node(&self) -> bool {
228 self.tokens.get(1) == Some(&&b"__overlay__"[..])
229 }
230}
231
Jaewan Kim8f6f4662023-12-12 17:38:47 +0900232#[derive(Debug, Eq, PartialEq)]
233enum DeviceTreeChildrenMask {
234 Partial(Vec<DeviceTreeMask>),
235 All,
236}
237
238#[derive(Eq, PartialEq)]
239struct DeviceTreeMask {
240 name_bytes: Vec<u8>,
241 children: DeviceTreeChildrenMask,
242}
243
244impl fmt::Debug for DeviceTreeMask {
245 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
246 let name_bytes = [self.name_bytes.as_slice(), b"\0"].concat();
247
248 f.debug_struct("DeviceTreeMask")
249 .field("name", &CStr::from_bytes_with_nul(&name_bytes).unwrap())
250 .field("children", &self.children)
251 .finish()
252 }
253}
254
255impl DeviceTreeMask {
256 fn new() -> Self {
257 Self { name_bytes: b"/".to_vec(), children: DeviceTreeChildrenMask::Partial(Vec::new()) }
258 }
259
260 fn mask_internal(&mut self, path: &DtPathTokens, leaf_mask: DeviceTreeChildrenMask) -> bool {
261 let mut iter = self;
262 let mut newly_masked = false;
263 'next_token: for path_token in &path.tokens {
264 let DeviceTreeChildrenMask::Partial(ref mut children) = &mut iter.children else {
265 return false;
266 };
267
268 // Note: Can't use iterator for 'get or insert'. (a.k.a. polonius Rust)
269 #[allow(clippy::needless_range_loop)]
270 for i in 0..children.len() {
271 if children[i].name_bytes.as_slice() == *path_token {
272 iter = &mut children[i];
273 newly_masked = false;
274 continue 'next_token;
275 }
276 }
277 let child = Self {
278 name_bytes: path_token.to_vec(),
279 children: DeviceTreeChildrenMask::Partial(Vec::new()),
280 };
281 children.push(child);
282 newly_masked = true;
283 iter = children.last_mut().unwrap()
284 }
285 iter.children = leaf_mask;
286 newly_masked
287 }
288
289 fn mask(&mut self, path: &DtPathTokens) -> bool {
290 self.mask_internal(path, DeviceTreeChildrenMask::Partial(Vec::new()))
291 }
292
293 fn mask_all(&mut self, path: &DtPathTokens) {
294 self.mask_internal(path, DeviceTreeChildrenMask::All);
295 }
296}
297
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900298/// Represents VM DTBO
299#[repr(transparent)]
300pub struct VmDtbo(Fdt);
301
302impl VmDtbo {
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900303 /// Wraps a mutable slice containing a VM DTBO.
304 ///
305 /// Fails if the VM DTBO does not pass validation.
306 pub fn from_mut_slice(dtbo: &mut [u8]) -> Result<&mut Self> {
307 // This validates DTBO
308 let fdt = Fdt::from_mut_slice(dtbo)?;
309 // SAFETY: VmDtbo is a transparent wrapper around Fdt, so representation is the same.
310 Ok(unsafe { mem::transmute::<&mut Fdt, &mut Self>(fdt) })
311 }
312
313 // Locates device node path as if the given dtbo node path is assigned and VM DTBO is overlaid.
314 // For given dtbo node path, this concatenates <target-path> of the enclosing fragment and
315 // relative path from __overlay__ node.
316 //
317 // Here's an example with sample VM DTBO:
318 // / {
319 // fragment@rng {
320 // target-path = "/"; // Always 'target-path = "/"'. Disallows <target> or other path.
321 // __overlay__ {
322 // rng { ... }; // Actual device node is here. If overlaid, path would be "/rng"
323 // };
324 // };
Jaewan Kim80ef9fa2024-02-25 16:08:14 +0000325 // __symbols__ { // Contains list of assignable devices
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900326 // rng = "/fragment@rng/__overlay__/rng";
327 // };
328 // };
329 //
330 // Then locate_overlay_target_path(cstr!("/fragment@rng/__overlay__/rng")) is Ok("/rng")
331 //
332 // Contrary to fdt_overlay_target_offset(), this API enforces overlay target property
333 // 'target-path = "/"', so the overlay doesn't modify and/or append platform DT's existing
334 // node and/or properties. The enforcement is for compatibility reason.
Jaewan Kim19b984f2023-12-04 15:16:50 +0900335 fn locate_overlay_target_path(
336 &self,
Jaewan Kimf8abbb52023-12-12 22:11:39 +0900337 dtbo_node_path: &DtPathTokens,
Jaewan Kim19b984f2023-12-04 15:16:50 +0900338 dtbo_node: &FdtNode,
339 ) -> Result<CString> {
Jaewan Kim19b984f2023-12-04 15:16:50 +0900340 let fragment_node = dtbo_node.supernode_at_depth(1)?;
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900341 let target_path = fragment_node
Pierre-Clément Tosid701a0b2023-11-07 15:38:59 +0000342 .getprop_str(cstr!("target-path"))?
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900343 .ok_or(DeviceAssignmentError::InvalidDtbo)?;
344 if target_path != cstr!("/") {
345 return Err(DeviceAssignmentError::UnsupportedOverlayTarget);
346 }
347
Jaewan Kimf8abbb52023-12-12 22:11:39 +0900348 let overlaid_path = dtbo_node_path.to_overlay_target_path()?;
349 Ok(overlaid_path.to_cstring())
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900350 }
Jaewan Kim19b984f2023-12-04 15:16:50 +0900351
352 fn parse_physical_iommus(physical_node: &FdtNode) -> Result<BTreeMap<Phandle, PhysIommu>> {
353 let mut phys_iommus = BTreeMap::new();
354 for (node, _) in physical_node.descendants() {
355 let Some(phandle) = node.get_phandle()? else {
356 continue; // Skips unreachable IOMMU node
357 };
358 let Some(iommu) = PhysIommu::parse(&node)? else {
359 continue; // Skip if not a PhysIommu.
360 };
361 if phys_iommus.insert(phandle, iommu).is_some() {
362 return Err(FdtError::BadPhandle.into());
363 }
364 }
365 Self::validate_physical_iommus(&phys_iommus)?;
366 Ok(phys_iommus)
367 }
368
369 fn validate_physical_iommus(phys_iommus: &BTreeMap<Phandle, PhysIommu>) -> Result<()> {
370 let unique_iommus: BTreeSet<_> = phys_iommus.values().cloned().collect();
371 if phys_iommus.len() != unique_iommus.len() {
372 return Err(DeviceAssignmentError::DuplicatedIommuIds);
373 }
374 Ok(())
375 }
376
377 fn validate_physical_devices(
378 physical_devices: &BTreeMap<Phandle, PhysicalDeviceInfo>,
379 ) -> Result<()> {
380 // Only need to validate iommus because <reg> will be validated together with PV <reg>
381 // see: DeviceAssignmentInfo::validate_all_regs().
382 let mut all_iommus = BTreeSet::new();
383 for physical_device in physical_devices.values() {
384 for iommu in &physical_device.iommus {
385 if !all_iommus.insert(iommu) {
386 error!("Unsupported phys IOMMU duplication found, <iommus> = {iommu:?}");
387 return Err(DeviceAssignmentError::UnsupportedIommusDuplication);
388 }
389 }
390 }
391 Ok(())
392 }
393
394 fn parse_physical_devices_with_iommus(
395 physical_node: &FdtNode,
396 phys_iommus: &BTreeMap<Phandle, PhysIommu>,
397 ) -> Result<BTreeMap<Phandle, PhysicalDeviceInfo>> {
398 let mut physical_devices = BTreeMap::new();
399 for (node, _) in physical_node.descendants() {
400 let Some(info) = PhysicalDeviceInfo::parse(&node, phys_iommus)? else {
401 continue;
402 };
403 if physical_devices.insert(info.target, info).is_some() {
404 return Err(DeviceAssignmentError::InvalidDtbo);
405 }
406 }
407 Self::validate_physical_devices(&physical_devices)?;
408 Ok(physical_devices)
409 }
410
411 /// Parses Physical devices in VM DTBO
412 fn parse_physical_devices(&self) -> Result<BTreeMap<Phandle, PhysicalDeviceInfo>> {
413 let Some(physical_node) = self.as_ref().node(cstr!("/host"))? else {
414 return Ok(BTreeMap::new());
415 };
416
417 let phys_iommus = Self::parse_physical_iommus(&physical_node)?;
418 Self::parse_physical_devices_with_iommus(&physical_node, &phys_iommus)
419 }
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900420
Jaewan Kimf8abbb52023-12-12 22:11:39 +0900421 fn node(&self, path: &DtPathTokens) -> Result<Option<FdtNode>> {
422 let mut node = self.as_ref().root();
423 for token in &path.tokens {
424 let Some(subnode) = node.subnode_with_name_bytes(token)? else {
425 return Ok(None);
426 };
427 node = subnode;
428 }
429 Ok(Some(node))
430 }
Jaewan Kim8f6f4662023-12-12 17:38:47 +0900431
432 fn collect_overlayable_nodes_with_phandle(&self) -> Result<BTreeMap<Phandle, DtPathTokens>> {
433 let mut paths = BTreeMap::new();
434 let mut path: DtPathTokens = Default::default();
435 let root = self.as_ref().root();
436 for (node, depth) in root.descendants() {
437 path.tokens.truncate(depth - 1);
438 path.tokens.push(node.name()?.to_bytes());
439 if !path.is_overlayable_node() {
440 continue;
441 }
442 if let Some(phandle) = node.get_phandle()? {
443 paths.insert(phandle, path.clone());
444 }
445 }
446 Ok(paths)
447 }
448
449 fn collect_phandle_references_from_overlayable_nodes(
450 &self,
451 ) -> Result<BTreeMap<DtPathTokens, Vec<Phandle>>> {
452 const CELL_SIZE: usize = core::mem::size_of::<u32>();
453
454 let vm_dtbo = self.as_ref();
455
456 let mut phandle_map = BTreeMap::new();
457 let Some(local_fixups) = vm_dtbo.node(cstr!("/__local_fixups__"))? else {
458 return Ok(phandle_map);
459 };
460
461 let mut path: DtPathTokens = Default::default();
462 for (fixup_node, depth) in local_fixups.descendants() {
463 let node_name = fixup_node.name()?;
464 path.tokens.truncate(depth - 1);
465 path.tokens.push(node_name.to_bytes());
466 if path.tokens.len() != depth {
467 return Err(DeviceAssignmentError::Internal);
468 }
469 if !path.is_overlayable_node() {
470 continue;
471 }
472 let target_node = self.node(&path)?.ok_or(DeviceAssignmentError::InvalidDtbo)?;
473
474 let mut phandles = vec![];
475 for fixup_prop in fixup_node.properties()? {
476 let target_prop = target_node
477 .getprop(fixup_prop.name()?)
478 .or(Err(DeviceAssignmentError::InvalidDtbo))?
479 .ok_or(DeviceAssignmentError::InvalidDtbo)?;
480 let fixup_prop_values = fixup_prop.value()?;
481 if fixup_prop_values.is_empty() || fixup_prop_values.len() % CELL_SIZE != 0 {
482 return Err(DeviceAssignmentError::InvalidDtbo);
483 }
484
485 for fixup_prop_cell in fixup_prop_values.chunks(CELL_SIZE) {
486 let phandle_offset: usize = u32::from_be_bytes(
487 fixup_prop_cell.try_into().or(Err(DeviceAssignmentError::InvalidDtbo))?,
488 )
489 .try_into()
490 .or(Err(DeviceAssignmentError::InvalidDtbo))?;
491 if phandle_offset % CELL_SIZE != 0 {
492 return Err(DeviceAssignmentError::InvalidDtbo);
493 }
494 let phandle_value = target_prop
495 .get(phandle_offset..phandle_offset + CELL_SIZE)
496 .ok_or(DeviceAssignmentError::InvalidDtbo)?;
497 let phandle: Phandle = U32::ref_from(phandle_value)
498 .unwrap()
499 .get()
500 .try_into()
501 .or(Err(DeviceAssignmentError::InvalidDtbo))?;
502
503 phandles.push(phandle);
504 }
505 }
506 if !phandles.is_empty() {
507 phandle_map.insert(path.clone(), phandles);
508 }
509 }
510
511 Ok(phandle_map)
512 }
513
514 fn build_mask(&self, assigned_devices: Vec<DtPathTokens>) -> Result<DeviceTreeMask> {
515 if assigned_devices.is_empty() {
516 return Err(DeviceAssignmentError::Internal);
517 }
518
519 let dependencies = self.collect_phandle_references_from_overlayable_nodes()?;
520 let paths = self.collect_overlayable_nodes_with_phandle()?;
521
522 let mut mask = DeviceTreeMask::new();
523 let mut stack = assigned_devices;
524 while let Some(path) = stack.pop() {
525 if !mask.mask(&path) {
526 continue;
527 }
528 let Some(dst_phandles) = dependencies.get(&path) else {
529 continue;
530 };
531 for dst_phandle in dst_phandles {
532 let dst_path = paths.get(dst_phandle).ok_or(DeviceAssignmentError::Internal)?;
533 stack.push(dst_path.clone());
534 }
535 }
536
537 Ok(mask)
538 }
Jaewan Kimc39974e2023-12-02 01:13:30 +0900539}
540
Jaewan Kimc730ebf2024-02-22 10:34:55 +0900541fn filter_dangling_symbols(fdt: &mut Fdt) -> Result<()> {
542 if let Some(symbols) = fdt.symbols()? {
543 let mut removed = vec![];
544 for prop in symbols.properties()? {
545 let path = CStr::from_bytes_with_nul(prop.value()?)
546 .map_err(|_| DeviceAssignmentError::Internal)?;
547 if fdt.node(path)?.is_none() {
548 let name = prop.name()?;
549 removed.push(CString::from(name));
550 }
551 }
552
553 let mut symbols = fdt.symbols_mut()?.unwrap();
554 for name in removed {
555 symbols.nop_property(&name)?;
556 }
557 }
558 Ok(())
559}
560
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900561impl AsRef<Fdt> for VmDtbo {
562 fn as_ref(&self) -> &Fdt {
563 &self.0
564 }
565}
566
567impl AsMut<Fdt> for VmDtbo {
568 fn as_mut(&mut self) -> &mut Fdt {
569 &mut self.0
570 }
571}
572
Jaewan Kim8f6f4662023-12-12 17:38:47 +0900573// Filter any node that isn't masked by DeviceTreeMask.
574fn filter_with_mask(anchor: FdtNodeMut, mask: &DeviceTreeMask) -> Result<()> {
575 let mut stack = vec![mask];
576 let mut iter = anchor.next_node(0)?;
577 while let Some((node, depth)) = iter {
578 stack.truncate(depth);
579 let parent_mask = stack.last().unwrap();
580 let DeviceTreeChildrenMask::Partial(parent_mask_children) = &parent_mask.children else {
581 // Shouldn't happen. We only step-in if parent has DeviceTreeChildrenMask::Partial.
582 return Err(DeviceAssignmentError::Internal);
583 };
584
585 let name = node.as_node().name()?.to_bytes();
586 let mask = parent_mask_children.iter().find(|child_mask| child_mask.name_bytes == name);
587 if let Some(masked) = mask {
588 if let DeviceTreeChildrenMask::Partial(_) = &masked.children {
589 // This node is partially masked. Stepping-in.
590 stack.push(masked);
591 iter = node.next_node(depth)?;
592 } else {
593 // This node is fully masked. Stepping-out.
594 iter = node.next_node_skip_subnodes(depth)?;
595 }
596 } else {
597 // This node isn't masked.
598 iter = node.delete_and_next_node(depth)?;
599 }
600 }
601
602 Ok(())
603}
604
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900605#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
606struct PvIommu {
607 // ID from pvIOMMU node
608 id: u32,
609}
610
611impl PvIommu {
612 fn parse(node: &FdtNode) -> Result<Self> {
Jaewan Kima9200492023-11-21 20:45:31 +0900613 let iommu_cells = node
614 .getprop_u32(cstr!("#iommu-cells"))?
615 .ok_or(DeviceAssignmentError::InvalidPvIommu)?;
Jaewan Kim19b984f2023-12-04 15:16:50 +0900616 // Ensures #iommu-cells = <1>. It means that `<iommus>` entry contains pair of
Jaewan Kima9200492023-11-21 20:45:31 +0900617 // (pvIOMMU ID, vSID)
618 if iommu_cells != 1 {
619 return Err(DeviceAssignmentError::InvalidPvIommu);
620 }
621 let id = node.getprop_u32(cstr!("id"))?.ok_or(DeviceAssignmentError::InvalidPvIommu)?;
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900622 Ok(Self { id })
623 }
624}
625
Jaewan Kima9200492023-11-21 20:45:31 +0900626#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
627struct Vsid(u32);
628
Jaewan Kim19b984f2023-12-04 15:16:50 +0900629#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
630struct Sid(u64);
631
632impl From<u32> for Sid {
633 fn from(sid: u32) -> Self {
634 Self(sid.into())
635 }
636}
637
638#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
Jaewan Kim52477ae2023-11-21 21:20:52 +0900639struct DeviceReg {
640 addr: u64,
641 size: u64,
642}
643
Pierre-Clément Tosi49e26ce2024-03-12 16:31:50 +0000644impl DeviceReg {
645 pub fn overlaps(&self, range: &Range<u64>) -> bool {
646 self.addr < range.end && range.start < self.addr.checked_add(self.size).unwrap()
647 }
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +0100648
649 pub fn is_aligned(&self, granule: u64) -> bool {
650 self.addr % granule == 0 && self.size % granule == 0
651 }
Pierre-Clément Tosi49e26ce2024-03-12 16:31:50 +0000652}
653
Jaewan Kim52477ae2023-11-21 21:20:52 +0900654impl TryFrom<Reg<u64>> for DeviceReg {
655 type Error = DeviceAssignmentError;
656
657 fn try_from(reg: Reg<u64>) -> Result<Self> {
Jaewan Kim19b984f2023-12-04 15:16:50 +0900658 Ok(Self { addr: reg.addr, size: reg.size.ok_or(DeviceAssignmentError::MalformedReg)? })
Jaewan Kim52477ae2023-11-21 21:20:52 +0900659 }
660}
661
662fn parse_node_reg(node: &FdtNode) -> Result<Vec<DeviceReg>> {
663 node.reg()?
Jaewan Kim19b984f2023-12-04 15:16:50 +0900664 .ok_or(DeviceAssignmentError::MalformedReg)?
Jaewan Kim52477ae2023-11-21 21:20:52 +0900665 .map(DeviceReg::try_from)
666 .collect::<Result<Vec<_>>>()
667}
668
669fn to_be_bytes(reg: &[DeviceReg]) -> Vec<u8> {
670 let mut reg_cells = vec![];
671 for x in reg {
672 reg_cells.extend_from_slice(&x.addr.to_be_bytes());
673 reg_cells.extend_from_slice(&x.size.to_be_bytes());
674 }
675 reg_cells
676}
677
Jaewan Kim19b984f2023-12-04 15:16:50 +0900678#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
679struct PhysIommu {
680 token: u64,
681}
682
683impl PhysIommu {
684 fn parse(node: &FdtNode) -> Result<Option<Self>> {
685 let Some(token) = node.getprop_u64(cstr!("android,pvmfw,token"))? else {
686 return Ok(None);
687 };
688 let Some(iommu_cells) = node.getprop_u32(cstr!("#iommu-cells"))? else {
689 return Err(DeviceAssignmentError::InvalidPhysIommu);
690 };
691 // Currently only supports #iommu-cells = <1>.
692 // In that case `<iommus>` entry contains pair of (pIOMMU phandle, Sid token)
693 if iommu_cells != 1 {
694 return Err(DeviceAssignmentError::UnsupportedPhysIommu);
695 }
696 Ok(Some(Self { token }))
697 }
698}
699
700#[derive(Debug)]
701struct PhysicalDeviceInfo {
702 target: Phandle,
703 reg: Vec<DeviceReg>,
704 iommus: Vec<(PhysIommu, Sid)>,
705}
706
707impl PhysicalDeviceInfo {
708 fn parse_iommus(
709 node: &FdtNode,
710 phys_iommus: &BTreeMap<Phandle, PhysIommu>,
711 ) -> Result<Vec<(PhysIommu, Sid)>> {
712 let mut iommus = vec![];
713 let Some(mut cells) = node.getprop_cells(cstr!("iommus"))? else {
714 return Ok(iommus);
715 };
716 while let Some(cell) = cells.next() {
717 // Parse pIOMMU ID
718 let phandle =
719 Phandle::try_from(cell).or(Err(DeviceAssignmentError::MalformedIommus))?;
720 let iommu = phys_iommus.get(&phandle).ok_or(DeviceAssignmentError::MalformedIommus)?;
721
722 // Parse Sid
723 let Some(cell) = cells.next() else {
724 return Err(DeviceAssignmentError::MalformedIommus);
725 };
726
727 iommus.push((*iommu, Sid::from(cell)));
728 }
729 Ok(iommus)
730 }
731
732 fn parse(node: &FdtNode, phys_iommus: &BTreeMap<Phandle, PhysIommu>) -> Result<Option<Self>> {
733 let Some(phandle) = node.getprop_u32(cstr!("android,pvmfw,target"))? else {
734 return Ok(None);
735 };
736 let target = Phandle::try_from(phandle)?;
737 let reg = parse_node_reg(node)?;
738 let iommus = Self::parse_iommus(node, phys_iommus)?;
739 Ok(Some(Self { target, reg, iommus }))
740 }
741}
742
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900743/// Assigned device information parsed from crosvm DT.
744/// Keeps everything in the owned data because underlying FDT will be reused for platform DT.
745#[derive(Debug, Eq, PartialEq)]
746struct AssignedDeviceInfo {
747 // Node path of assigned device (e.g. "/rng")
748 node_path: CString,
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900749 // <reg> property from the crosvm DT
Jaewan Kim52477ae2023-11-21 21:20:52 +0900750 reg: Vec<DeviceReg>,
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900751 // <interrupts> property from the crosvm DT
752 interrupts: Vec<u8>,
Jaewan Kima9200492023-11-21 20:45:31 +0900753 // Parsed <iommus> property from the crosvm DT. Tuple of PvIommu and vSID.
754 iommus: Vec<(PvIommu, Vsid)>,
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900755}
756
757impl AssignedDeviceInfo {
Jaewan Kim19b984f2023-12-04 15:16:50 +0900758 fn validate_reg(
759 device_reg: &[DeviceReg],
760 physical_device_reg: &[DeviceReg],
Jaewan Kim52477ae2023-11-21 21:20:52 +0900761 hypervisor: &dyn DeviceAssigningHypervisor,
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +0100762 granule: usize,
Jaewan Kim19b984f2023-12-04 15:16:50 +0900763 ) -> Result<()> {
Pierre-Clément Tosi8b78bc32024-03-13 17:37:07 +0000764 let mut virt_regs = device_reg.iter();
765 let mut phys_regs = physical_device_reg.iter();
Pierre-Clément Tosi49e26ce2024-03-12 16:31:50 +0000766 // TODO(b/308694211): Move this constant to vmbase::layout once vmbase is std-compatible.
767 const PVMFW_RANGE: Range<u64> = 0x7fc0_0000..0x8000_0000;
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +0100768
Jaewan Kim19b984f2023-12-04 15:16:50 +0900769 // PV reg and physical reg should have 1:1 match in order.
Pierre-Clément Tosi8b78bc32024-03-13 17:37:07 +0000770 for (reg, phys_reg) in virt_regs.by_ref().zip(phys_regs.by_ref()) {
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +0100771 if !reg.is_aligned(granule.try_into().unwrap()) {
772 let DeviceReg { addr, size } = reg;
773 warn!("Assigned region ({addr:#x}, {size:#x}) not aligned to {granule:#x}");
774 // TODO(ptosi): Fix our test data so that we can return Err(...);
775 }
Pierre-Clément Tosi49e26ce2024-03-12 16:31:50 +0000776 if reg.overlaps(&PVMFW_RANGE) {
Pierre-Clément Tosif4d8a0d2024-10-25 17:14:15 +0100777 return Err(DeviceAssignmentError::InvalidReg(reg.addr));
Pierre-Clément Tosi49e26ce2024-03-12 16:31:50 +0000778 }
Pierre-Clément Tosif4d8a0d2024-10-25 17:14:15 +0100779 let expected_token = phys_reg.addr;
Pierre-Clément Tosi49e26ce2024-03-12 16:31:50 +0000780 // If this call returns successfully, hyp has mapped the MMIO region at `reg`.
Pierre-Clément Tosif4d8a0d2024-10-25 17:14:15 +0100781 let token = hypervisor.get_phys_mmio_token(reg.addr, reg.size).map_err(|e| {
Pierre-Clément Tosi08d6e3f2024-03-13 18:22:16 +0000782 error!("Hypervisor error while requesting MMIO token: {e}");
Pierre-Clément Tosif4d8a0d2024-10-25 17:14:15 +0100783 DeviceAssignmentError::InvalidReg(reg.addr)
Jaewan Kim52477ae2023-11-21 21:20:52 +0900784 })?;
Pierre-Clément Tosi49e26ce2024-03-12 16:31:50 +0000785 // Only check address because hypervisor guarantees size match when success.
Pierre-Clément Tosif4d8a0d2024-10-25 17:14:15 +0100786 if token != expected_token {
787 return Err(DeviceAssignmentError::InvalidRegToken(token, expected_token));
Jaewan Kim19b984f2023-12-04 15:16:50 +0900788 }
Jaewan Kim52477ae2023-11-21 21:20:52 +0900789 }
Pierre-Clément Tosi8b78bc32024-03-13 17:37:07 +0000790
791 if let Some(DeviceReg { addr, size }) = virt_regs.next() {
Pierre-Clément Tosif4d8a0d2024-10-25 17:14:15 +0100792 return Err(DeviceAssignmentError::ExtraReg(*addr, *size));
Pierre-Clément Tosi8b78bc32024-03-13 17:37:07 +0000793 }
794
795 if let Some(DeviceReg { addr, size }) = phys_regs.next() {
Pierre-Clément Tosif4d8a0d2024-10-25 17:14:15 +0100796 return Err(DeviceAssignmentError::MissingReg(*addr, *size));
Pierre-Clément Tosi8b78bc32024-03-13 17:37:07 +0000797 }
798
Jaewan Kim19b984f2023-12-04 15:16:50 +0900799 Ok(())
Jaewan Kim52477ae2023-11-21 21:20:52 +0900800 }
801
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900802 fn parse_interrupts(node: &FdtNode) -> Result<Vec<u8>> {
803 // Validation: Validate if interrupts cell numbers are multiple of #interrupt-cells.
804 // We can't know how many interrupts would exist.
805 let interrupts_cells = node
Pierre-Clément Tosid701a0b2023-11-07 15:38:59 +0000806 .getprop_cells(cstr!("interrupts"))?
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900807 .ok_or(DeviceAssignmentError::InvalidInterrupts)?
808 .count();
809 if interrupts_cells % CELLS_PER_INTERRUPT != 0 {
810 return Err(DeviceAssignmentError::InvalidInterrupts);
811 }
812
813 // Once validated, keep the raw bytes so patch can be done with setprop()
Pierre-Clément Tosid701a0b2023-11-07 15:38:59 +0000814 Ok(node.getprop(cstr!("interrupts")).unwrap().unwrap().into())
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900815 }
816
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900817 // TODO(b/277993056): Also validate /__local_fixups__ to ensure that <iommus> has phandle.
Jaewan Kima9200492023-11-21 20:45:31 +0900818 fn parse_iommus(
819 node: &FdtNode,
820 pviommus: &BTreeMap<Phandle, PvIommu>,
821 ) -> Result<Vec<(PvIommu, Vsid)>> {
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900822 let mut iommus = vec![];
Jaewan Kima9200492023-11-21 20:45:31 +0900823 let Some(mut cells) = node.getprop_cells(cstr!("iommus"))? else {
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900824 return Ok(iommus);
825 };
Jaewan Kima9200492023-11-21 20:45:31 +0900826 while let Some(cell) = cells.next() {
827 // Parse pvIOMMU ID
Jaewan Kim19b984f2023-12-04 15:16:50 +0900828 let phandle =
829 Phandle::try_from(cell).or(Err(DeviceAssignmentError::MalformedIommus))?;
830 let pviommu = pviommus.get(&phandle).ok_or(DeviceAssignmentError::MalformedIommus)?;
Jaewan Kima9200492023-11-21 20:45:31 +0900831
832 // Parse vSID
833 let Some(cell) = cells.next() else {
Jaewan Kim19b984f2023-12-04 15:16:50 +0900834 return Err(DeviceAssignmentError::MalformedIommus);
Jaewan Kima9200492023-11-21 20:45:31 +0900835 };
836 let vsid = Vsid(cell);
837
838 iommus.push((*pviommu, vsid));
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900839 }
840 Ok(iommus)
841 }
842
Jaewan Kim19b984f2023-12-04 15:16:50 +0900843 fn validate_iommus(
844 iommus: &[(PvIommu, Vsid)],
845 physical_device_iommu: &[(PhysIommu, Sid)],
846 hypervisor: &dyn DeviceAssigningHypervisor,
847 ) -> Result<()> {
848 if iommus.len() != physical_device_iommu.len() {
849 return Err(DeviceAssignmentError::InvalidIommus);
850 }
851 // pvIOMMU can be reordered, and hypervisor may not guarantee 1:1 mapping.
852 // So we need to mark what's matched or not.
853 let mut physical_device_iommu = physical_device_iommu.to_vec();
854 for (pviommu, vsid) in iommus {
Pierre-Clément Tosi08d6e3f2024-03-13 18:22:16 +0000855 let (id, sid) =
856 hypervisor.get_phys_iommu_token(pviommu.id.into(), vsid.0.into()).map_err(|e| {
857 error!("Hypervisor error while requesting IOMMU token ({pviommu:?}, {vsid:?}): {e}");
858 DeviceAssignmentError::InvalidIommus
859 })?;
Jaewan Kim19b984f2023-12-04 15:16:50 +0900860
861 let pos = physical_device_iommu
862 .iter()
863 .position(|(phys_iommu, phys_sid)| (phys_iommu.token, phys_sid.0) == (id, sid));
864 match pos {
865 Some(pos) => physical_device_iommu.remove(pos),
866 None => {
867 error!("Failed to validate device <iommus>. No matching phys iommu or duplicated mapping for pviommu={pviommu:?}, vsid={vsid:?}");
868 return Err(DeviceAssignmentError::InvalidIommus);
869 }
870 };
871 }
872 Ok(())
873 }
874
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900875 fn parse(
876 fdt: &Fdt,
877 vm_dtbo: &VmDtbo,
Jaewan Kimf8abbb52023-12-12 22:11:39 +0900878 dtbo_node_path: &DtPathTokens,
Jaewan Kim19b984f2023-12-04 15:16:50 +0900879 physical_devices: &BTreeMap<Phandle, PhysicalDeviceInfo>,
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900880 pviommus: &BTreeMap<Phandle, PvIommu>,
Jaewan Kim52477ae2023-11-21 21:20:52 +0900881 hypervisor: &dyn DeviceAssigningHypervisor,
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +0100882 granule: usize,
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900883 ) -> Result<Option<Self>> {
Jaewan Kim19b984f2023-12-04 15:16:50 +0900884 let dtbo_node =
Jaewan Kimf8abbb52023-12-12 22:11:39 +0900885 vm_dtbo.node(dtbo_node_path)?.ok_or(DeviceAssignmentError::InvalidSymbols)?;
Jaewan Kim19b984f2023-12-04 15:16:50 +0900886 let node_path = vm_dtbo.locate_overlay_target_path(dtbo_node_path, &dtbo_node)?;
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900887
888 let Some(node) = fdt.node(&node_path)? else { return Ok(None) };
889
Jaewan Kim80ef9fa2024-02-25 16:08:14 +0000890 // Currently can only assign devices backed by physical devices.
Jaewan Kim19b984f2023-12-04 15:16:50 +0900891 let phandle = dtbo_node.get_phandle()?.ok_or(DeviceAssignmentError::InvalidDtbo)?;
Jaewan Kim80ef9fa2024-02-25 16:08:14 +0000892 let Some(physical_device) = physical_devices.get(&phandle) else {
893 // If labeled DT node isn't backed by physical device node, then just return None.
894 // It's not an error because such node can be a dependency of assignable device nodes.
895 return Ok(None);
896 };
Jaewan Kim19b984f2023-12-04 15:16:50 +0900897
898 let reg = parse_node_reg(&node)?;
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +0100899 Self::validate_reg(&reg, &physical_device.reg, hypervisor, granule)?;
Jaewan Kim19b984f2023-12-04 15:16:50 +0900900
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900901 let interrupts = Self::parse_interrupts(&node)?;
Jaewan Kim19b984f2023-12-04 15:16:50 +0900902
903 let iommus = Self::parse_iommus(&node, pviommus)?;
904 Self::validate_iommus(&iommus, &physical_device.iommus, hypervisor)?;
905
Jaewan Kimf8abbb52023-12-12 22:11:39 +0900906 Ok(Some(Self { node_path, reg, interrupts, iommus }))
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900907 }
908
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900909 fn patch(&self, fdt: &mut Fdt, pviommu_phandles: &BTreeMap<PvIommu, Phandle>) -> Result<()> {
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900910 let mut dst = fdt.node_mut(&self.node_path)?.unwrap();
Jaewan Kim52477ae2023-11-21 21:20:52 +0900911 dst.setprop(cstr!("reg"), &to_be_bytes(&self.reg))?;
Pierre-Clément Tosid701a0b2023-11-07 15:38:59 +0000912 dst.setprop(cstr!("interrupts"), &self.interrupts)?;
Jaewan Kima9200492023-11-21 20:45:31 +0900913 let mut iommus = Vec::with_capacity(8 * self.iommus.len());
914 for (pviommu, vsid) in &self.iommus {
915 let phandle = pviommu_phandles.get(pviommu).unwrap();
916 iommus.extend_from_slice(&u32::from(*phandle).to_be_bytes());
917 iommus.extend_from_slice(&vsid.0.to_be_bytes());
918 }
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900919 dst.setprop(cstr!("iommus"), &iommus)?;
920
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900921 Ok(())
922 }
923}
924
Jaewan Kim8f6f4662023-12-12 17:38:47 +0900925#[derive(Debug, Eq, PartialEq)]
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900926pub struct DeviceAssignmentInfo {
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900927 pviommus: BTreeSet<PvIommu>,
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900928 assigned_devices: Vec<AssignedDeviceInfo>,
Jaewan Kim8f6f4662023-12-12 17:38:47 +0900929 vm_dtbo_mask: DeviceTreeMask,
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900930}
931
932impl DeviceAssignmentInfo {
Chris Wailes9d09f572024-01-16 13:31:02 -0800933 const PVIOMMU_COMPATIBLE: &'static CStr = cstr!("pkvm,pviommu");
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900934
935 /// Parses pvIOMMUs in fdt
936 // Note: This will validate pvIOMMU ids' uniqueness, even when unassigned.
937 fn parse_pviommus(fdt: &Fdt) -> Result<BTreeMap<Phandle, PvIommu>> {
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900938 let mut pviommus = BTreeMap::new();
939 for compatible in fdt.compatible_nodes(Self::PVIOMMU_COMPATIBLE)? {
940 let Some(phandle) = compatible.get_phandle()? else {
941 continue; // Skips unreachable pvIOMMU node
942 };
943 let pviommu = PvIommu::parse(&compatible)?;
944 if pviommus.insert(phandle, pviommu).is_some() {
945 return Err(FdtError::BadPhandle.into());
946 }
947 }
948 Ok(pviommus)
949 }
950
Jaewan Kim19b984f2023-12-04 15:16:50 +0900951 fn validate_pviommu_topology(assigned_devices: &[AssignedDeviceInfo]) -> Result<()> {
952 let mut all_iommus = BTreeSet::new();
953 for assigned_device in assigned_devices {
954 for iommu in &assigned_device.iommus {
955 if !all_iommus.insert(iommu) {
956 error!("Unsupported pvIOMMU duplication found, <iommus> = {iommu:?}");
957 return Err(DeviceAssignmentError::UnsupportedPvIommusDuplication);
958 }
959 }
960 }
961 Ok(())
962 }
963
Pierre-Clément Tosie5cca922024-04-30 17:54:08 +0100964 // TODO(b/308694211): Remove this workaround for visibility once using
965 // vmbase::hyp::DeviceAssigningHypervisor for tests.
966 #[cfg(test)]
967 fn parse(
968 fdt: &Fdt,
969 vm_dtbo: &VmDtbo,
970 hypervisor: &dyn DeviceAssigningHypervisor,
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +0100971 granule: usize,
Pierre-Clément Tosie5cca922024-04-30 17:54:08 +0100972 ) -> Result<Option<Self>> {
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +0100973 Self::internal_parse(fdt, vm_dtbo, hypervisor, granule)
Pierre-Clément Tosie5cca922024-04-30 17:54:08 +0100974 }
975
976 #[cfg(not(test))]
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900977 /// Parses fdt and vm_dtbo, and creates new DeviceAssignmentInfo
978 // TODO(b/277993056): Parse __local_fixups__
979 // TODO(b/277993056): Parse __fixups__
Jaewan Kim52477ae2023-11-21 21:20:52 +0900980 pub fn parse(
981 fdt: &Fdt,
982 vm_dtbo: &VmDtbo,
983 hypervisor: &dyn DeviceAssigningHypervisor,
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +0100984 granule: usize,
Jaewan Kim52477ae2023-11-21 21:20:52 +0900985 ) -> Result<Option<Self>> {
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +0100986 Self::internal_parse(fdt, vm_dtbo, hypervisor, granule)
Pierre-Clément Tosie5cca922024-04-30 17:54:08 +0100987 }
988
989 fn internal_parse(
990 fdt: &Fdt,
991 vm_dtbo: &VmDtbo,
992 hypervisor: &dyn DeviceAssigningHypervisor,
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +0100993 granule: usize,
Pierre-Clément Tosie5cca922024-04-30 17:54:08 +0100994 ) -> Result<Option<Self>> {
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900995 let Some(symbols_node) = vm_dtbo.as_ref().symbols()? else {
996 // /__symbols__ should contain all assignable devices.
997 // If empty, then nothing can be assigned.
998 return Ok(None);
999 };
1000
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001001 let pviommus = Self::parse_pviommus(fdt)?;
1002 let unique_pviommus: BTreeSet<_> = pviommus.values().cloned().collect();
1003 if pviommus.len() != unique_pviommus.len() {
1004 return Err(DeviceAssignmentError::DuplicatedPvIommuIds);
1005 }
1006
Jaewan Kim19b984f2023-12-04 15:16:50 +09001007 let physical_devices = vm_dtbo.parse_physical_devices()?;
1008
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001009 let mut assigned_devices = vec![];
Jaewan Kim8f6f4662023-12-12 17:38:47 +09001010 let mut assigned_device_paths = vec![];
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001011 for symbol_prop in symbols_node.properties()? {
1012 let symbol_prop_value = symbol_prop.value()?;
1013 let dtbo_node_path = CStr::from_bytes_with_nul(symbol_prop_value)
1014 .or(Err(DeviceAssignmentError::InvalidSymbols))?;
Jaewan Kimf8abbb52023-12-12 22:11:39 +09001015 let dtbo_node_path = DtPathTokens::new(dtbo_node_path)?;
1016 if !dtbo_node_path.is_overlayable_node() {
Jaewan Kimc39974e2023-12-02 01:13:30 +09001017 continue;
1018 }
Jaewan Kim19b984f2023-12-04 15:16:50 +09001019 let assigned_device = AssignedDeviceInfo::parse(
1020 fdt,
1021 vm_dtbo,
Jaewan Kimf8abbb52023-12-12 22:11:39 +09001022 &dtbo_node_path,
Jaewan Kim19b984f2023-12-04 15:16:50 +09001023 &physical_devices,
1024 &pviommus,
1025 hypervisor,
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +01001026 granule,
Jaewan Kim19b984f2023-12-04 15:16:50 +09001027 )?;
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001028 if let Some(assigned_device) = assigned_device {
1029 assigned_devices.push(assigned_device);
Jaewan Kim8f6f4662023-12-12 17:38:47 +09001030 assigned_device_paths.push(dtbo_node_path);
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001031 }
1032 }
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001033 if assigned_devices.is_empty() {
1034 return Ok(None);
1035 }
Jaewan Kimc39974e2023-12-02 01:13:30 +09001036
Jaewan Kim19b984f2023-12-04 15:16:50 +09001037 Self::validate_pviommu_topology(&assigned_devices)?;
1038
Jaewan Kim8f6f4662023-12-12 17:38:47 +09001039 let mut vm_dtbo_mask = vm_dtbo.build_mask(assigned_device_paths)?;
1040 vm_dtbo_mask.mask_all(&DtPathTokens::new(cstr!("/__local_fixups__"))?);
1041 vm_dtbo_mask.mask_all(&DtPathTokens::new(cstr!("/__symbols__"))?);
Jaewan Kimc39974e2023-12-02 01:13:30 +09001042
1043 // Note: Any node without __overlay__ will be ignored by fdt_apply_overlay,
1044 // so doesn't need to be filtered.
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001045
Jaewan Kim8f6f4662023-12-12 17:38:47 +09001046 Ok(Some(Self { pviommus: unique_pviommus, assigned_devices, vm_dtbo_mask }))
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001047 }
1048
1049 /// Filters VM DTBO to only contain necessary information for booting pVM
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001050 pub fn filter(&self, vm_dtbo: &mut VmDtbo) -> Result<()> {
1051 let vm_dtbo = vm_dtbo.as_mut();
1052
Jaewan Kim8f6f4662023-12-12 17:38:47 +09001053 // Filter unused references in /__local_fixups__
1054 if let Some(local_fixups) = vm_dtbo.node_mut(cstr!("/__local_fixups__"))? {
1055 filter_with_mask(local_fixups, &self.vm_dtbo_mask)?;
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001056 }
1057
Jaewan Kim8f6f4662023-12-12 17:38:47 +09001058 // Filter unused nodes in rest of tree
1059 let root = vm_dtbo.root_mut();
1060 filter_with_mask(root, &self.vm_dtbo_mask)?;
1061
Jaewan Kim371f6c82024-02-24 01:33:37 +09001062 filter_dangling_symbols(vm_dtbo)
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001063 }
1064
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001065 fn patch_pviommus(&self, fdt: &mut Fdt) -> Result<BTreeMap<PvIommu, Phandle>> {
Pierre-Clément Tosi244efea2024-02-16 14:48:14 +00001066 let mut compatible = fdt.root_mut().next_compatible(Self::PVIOMMU_COMPATIBLE)?;
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001067 let mut pviommu_phandles = BTreeMap::new();
1068
1069 for pviommu in &self.pviommus {
1070 let mut node = compatible.ok_or(DeviceAssignmentError::TooManyPvIommu)?;
1071 let phandle = node.as_node().get_phandle()?.ok_or(DeviceAssignmentError::Internal)?;
1072 node.setprop_inplace(cstr!("id"), &pviommu.id.to_be_bytes())?;
1073 if pviommu_phandles.insert(*pviommu, phandle).is_some() {
1074 return Err(DeviceAssignmentError::Internal);
1075 }
1076 compatible = node.next_compatible(Self::PVIOMMU_COMPATIBLE)?;
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001077 }
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001078
1079 // Filters pre-populated but unassigned pvIOMMUs.
1080 while let Some(filtered_pviommu) = compatible {
1081 compatible = filtered_pviommu.delete_and_next_compatible(Self::PVIOMMU_COMPATIBLE)?;
1082 }
1083
1084 Ok(pviommu_phandles)
1085 }
1086
1087 pub fn patch(&self, fdt: &mut Fdt) -> Result<()> {
1088 let pviommu_phandles = self.patch_pviommus(fdt)?;
1089
1090 // Patches assigned devices
1091 for device in &self.assigned_devices {
1092 device.patch(fdt, &pviommu_phandles)?;
1093 }
1094
Jaewan Kimc730ebf2024-02-22 10:34:55 +09001095 // Removes any dangling references in __symbols__ (e.g. removed pvIOMMUs)
1096 filter_dangling_symbols(fdt)
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001097 }
1098}
1099
Jaewan Kim50246682024-03-11 23:18:54 +09001100/// Cleans device trees not to contain any pre-populated nodes/props for device assignment.
1101pub fn clean(fdt: &mut Fdt) -> Result<()> {
1102 let mut compatible = fdt.root_mut().next_compatible(cstr!("pkvm,pviommu"))?;
1103 // Filters pre-populated
1104 while let Some(filtered_pviommu) = compatible {
1105 compatible = filtered_pviommu.delete_and_next_compatible(cstr!("pkvm,pviommu"))?;
1106 }
1107
1108 // Removes any dangling references in __symbols__ (e.g. removed pvIOMMUs)
1109 filter_dangling_symbols(fdt)
1110}
1111
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001112#[cfg(test)]
Pierre-Clément Tosie5cca922024-04-30 17:54:08 +01001113#[derive(Clone, Copy, Debug)]
1114enum MockHypervisorError {
1115 FailedGetPhysMmioToken,
1116 FailedGetPhysIommuToken,
1117}
1118
1119#[cfg(test)]
1120type MockHypervisorResult<T> = core::result::Result<T, MockHypervisorError>;
1121
1122#[cfg(test)]
1123impl fmt::Display for MockHypervisorError {
1124 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1125 match self {
1126 MockHypervisorError::FailedGetPhysMmioToken => {
1127 write!(f, "Failed to get physical MMIO token")
1128 }
1129 MockHypervisorError::FailedGetPhysIommuToken => {
1130 write!(f, "Failed to get physical IOMMU token")
1131 }
1132 }
1133 }
1134}
1135
1136#[cfg(test)]
1137trait DeviceAssigningHypervisor {
1138 /// Returns MMIO token.
1139 fn get_phys_mmio_token(&self, base_ipa: u64, size: u64) -> MockHypervisorResult<u64>;
1140
1141 /// Returns DMA token as a tuple of (phys_iommu_id, phys_sid).
1142 fn get_phys_iommu_token(&self, pviommu_id: u64, vsid: u64) -> MockHypervisorResult<(u64, u64)>;
1143}
1144
1145#[cfg(test)]
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001146mod tests {
1147 use super::*;
Jaewan Kim52477ae2023-11-21 21:20:52 +09001148 use alloc::collections::{BTreeMap, BTreeSet};
Jaewan Kim8f6f4662023-12-12 17:38:47 +09001149 use dts::Dts;
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001150 use std::fs;
Jaewan Kim8f6f4662023-12-12 17:38:47 +09001151 use std::path::Path;
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001152
1153 const VM_DTBO_FILE_PATH: &str = "test_pvmfw_devices_vm_dtbo.dtbo";
1154 const VM_DTBO_WITHOUT_SYMBOLS_FILE_PATH: &str =
1155 "test_pvmfw_devices_vm_dtbo_without_symbols.dtbo";
Jaewan Kim19b984f2023-12-04 15:16:50 +09001156 const VM_DTBO_WITH_DUPLICATED_IOMMUS_FILE_PATH: &str =
1157 "test_pvmfw_devices_vm_dtbo_with_duplicated_iommus.dtbo";
Jaewan Kim8f6f4662023-12-12 17:38:47 +09001158 const VM_DTBO_WITH_DEPENDENCIES_FILE_PATH: &str =
1159 "test_pvmfw_devices_vm_dtbo_with_dependencies.dtbo";
Jaewan Kima67e36a2023-11-29 16:50:23 +09001160 const FDT_WITHOUT_IOMMUS_FILE_PATH: &str = "test_pvmfw_devices_without_iommus.dtb";
Jaewan Kim52477ae2023-11-21 21:20:52 +09001161 const FDT_WITHOUT_DEVICE_FILE_PATH: &str = "test_pvmfw_devices_without_device.dtb";
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001162 const FDT_FILE_PATH: &str = "test_pvmfw_devices_with_rng.dtb";
Pierre-Clément Tosi49e26ce2024-03-12 16:31:50 +00001163 const FDT_WITH_DEVICE_OVERLAPPING_PVMFW: &str = "test_pvmfw_devices_overlapping_pvmfw.dtb";
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001164 const FDT_WITH_MULTIPLE_DEVICES_IOMMUS_FILE_PATH: &str =
1165 "test_pvmfw_devices_with_multiple_devices_iommus.dtb";
1166 const FDT_WITH_IOMMU_SHARING: &str = "test_pvmfw_devices_with_iommu_sharing.dtb";
1167 const FDT_WITH_IOMMU_ID_CONFLICT: &str = "test_pvmfw_devices_with_iommu_id_conflict.dtb";
Jaewan Kim19b984f2023-12-04 15:16:50 +09001168 const FDT_WITH_DUPLICATED_PVIOMMUS_FILE_PATH: &str =
1169 "test_pvmfw_devices_with_duplicated_pviommus.dtb";
1170 const FDT_WITH_MULTIPLE_REG_IOMMU_FILE_PATH: &str =
1171 "test_pvmfw_devices_with_multiple_reg_iommus.dtb";
Jaewan Kim8f6f4662023-12-12 17:38:47 +09001172 const FDT_WITH_DEPENDENCY_FILE_PATH: &str = "test_pvmfw_devices_with_dependency.dtb";
1173 const FDT_WITH_MULTIPLE_DEPENDENCIES_FILE_PATH: &str =
1174 "test_pvmfw_devices_with_multiple_dependencies.dtb";
1175 const FDT_WITH_DEPENDENCY_LOOP_FILE_PATH: &str = "test_pvmfw_devices_with_dependency_loop.dtb";
1176
1177 const EXPECTED_FDT_WITH_DEPENDENCY_FILE_PATH: &str = "expected_dt_with_dependency.dtb";
1178 const EXPECTED_FDT_WITH_MULTIPLE_DEPENDENCIES_FILE_PATH: &str =
1179 "expected_dt_with_multiple_dependencies.dtb";
1180 const EXPECTED_FDT_WITH_DEPENDENCY_LOOP_FILE_PATH: &str =
1181 "expected_dt_with_dependency_loop.dtb";
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001182
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +01001183 // TODO(b/308694211): Use vmbase::SIZE_4KB.
1184 const SIZE_4KB: usize = 4 << 10;
1185
Jaewan Kim52477ae2023-11-21 21:20:52 +09001186 #[derive(Debug, Default)]
1187 struct MockHypervisor {
1188 mmio_tokens: BTreeMap<(u64, u64), u64>,
1189 iommu_tokens: BTreeMap<(u64, u64), (u64, u64)>,
1190 }
1191
1192 impl DeviceAssigningHypervisor for MockHypervisor {
Pierre-Clément Tosie5cca922024-04-30 17:54:08 +01001193 fn get_phys_mmio_token(&self, base_ipa: u64, size: u64) -> MockHypervisorResult<u64> {
1194 let token = self.mmio_tokens.get(&(base_ipa, size));
1195
1196 Ok(*token.ok_or(MockHypervisorError::FailedGetPhysMmioToken)?)
Jaewan Kim52477ae2023-11-21 21:20:52 +09001197 }
1198
Pierre-Clément Tosie5cca922024-04-30 17:54:08 +01001199 fn get_phys_iommu_token(
1200 &self,
1201 pviommu_id: u64,
1202 vsid: u64,
1203 ) -> MockHypervisorResult<(u64, u64)> {
1204 let token = self.iommu_tokens.get(&(pviommu_id, vsid));
1205
1206 Ok(*token.ok_or(MockHypervisorError::FailedGetPhysIommuToken)?)
Jaewan Kim52477ae2023-11-21 21:20:52 +09001207 }
1208 }
1209
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001210 #[derive(Debug, Eq, PartialEq)]
1211 struct AssignedDeviceNode {
1212 path: CString,
1213 reg: Vec<u8>,
1214 interrupts: Vec<u8>,
Jaewan Kima67e36a2023-11-29 16:50:23 +09001215 iommus: Vec<u32>, // pvIOMMU id and vSID
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001216 }
1217
1218 impl AssignedDeviceNode {
1219 fn parse(fdt: &Fdt, path: &CStr) -> Result<Self> {
1220 let Some(node) = fdt.node(path)? else {
1221 return Err(FdtError::NotFound.into());
1222 };
1223
Jaewan Kim19b984f2023-12-04 15:16:50 +09001224 let reg = node.getprop(cstr!("reg"))?.ok_or(DeviceAssignmentError::MalformedReg)?;
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001225 let interrupts = node
1226 .getprop(cstr!("interrupts"))?
1227 .ok_or(DeviceAssignmentError::InvalidInterrupts)?;
1228 let mut iommus = vec![];
Jaewan Kima9200492023-11-21 20:45:31 +09001229 if let Some(mut cells) = node.getprop_cells(cstr!("iommus"))? {
1230 while let Some(pviommu_id) = cells.next() {
1231 // pvIOMMU id
1232 let phandle = Phandle::try_from(pviommu_id)?;
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001233 let pviommu = fdt
1234 .node_with_phandle(phandle)?
Jaewan Kim19b984f2023-12-04 15:16:50 +09001235 .ok_or(DeviceAssignmentError::MalformedIommus)?;
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001236 let compatible = pviommu.getprop_str(cstr!("compatible"));
1237 if compatible != Ok(Some(cstr!("pkvm,pviommu"))) {
Jaewan Kim19b984f2023-12-04 15:16:50 +09001238 return Err(DeviceAssignmentError::MalformedIommus);
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001239 }
1240 let id = pviommu
1241 .getprop_u32(cstr!("id"))?
Jaewan Kim19b984f2023-12-04 15:16:50 +09001242 .ok_or(DeviceAssignmentError::MalformedIommus)?;
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001243 iommus.push(id);
Jaewan Kima9200492023-11-21 20:45:31 +09001244
1245 // vSID
1246 let Some(vsid) = cells.next() else {
Jaewan Kim19b984f2023-12-04 15:16:50 +09001247 return Err(DeviceAssignmentError::MalformedIommus);
Jaewan Kima9200492023-11-21 20:45:31 +09001248 };
1249 iommus.push(vsid);
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001250 }
1251 }
1252 Ok(Self { path: path.into(), reg: reg.into(), interrupts: interrupts.into(), iommus })
1253 }
1254 }
1255
1256 fn collect_pviommus(fdt: &Fdt) -> Result<Vec<u32>> {
1257 let mut pviommus = BTreeSet::new();
1258 for pviommu in fdt.compatible_nodes(cstr!("pkvm,pviommu"))? {
1259 if let Ok(Some(id)) = pviommu.getprop_u32(cstr!("id")) {
1260 pviommus.insert(id);
1261 }
1262 }
1263 Ok(pviommus.iter().cloned().collect())
1264 }
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001265
1266 fn into_fdt_prop(native_bytes: Vec<u32>) -> Vec<u8> {
1267 let mut v = Vec::with_capacity(native_bytes.len() * 4);
1268 for byte in native_bytes {
1269 v.extend_from_slice(&byte.to_be_bytes());
1270 }
1271 v
1272 }
1273
Jaewan Kim52477ae2023-11-21 21:20:52 +09001274 impl From<[u64; 2]> for DeviceReg {
1275 fn from(fdt_cells: [u64; 2]) -> Self {
1276 DeviceReg { addr: fdt_cells[0], size: fdt_cells[1] }
1277 }
1278 }
1279
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +01001280 // TODO(ptosi): Add tests with varying HYP_GRANULE values.
1281
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001282 #[test]
1283 fn device_info_new_without_symbols() {
1284 let mut fdt_data = fs::read(FDT_FILE_PATH).unwrap();
1285 let mut vm_dtbo_data = fs::read(VM_DTBO_WITHOUT_SYMBOLS_FILE_PATH).unwrap();
1286 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1287 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1288
Jaewan Kim52477ae2023-11-21 21:20:52 +09001289 let hypervisor: MockHypervisor = Default::default();
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +01001290 const HYP_GRANULE: usize = SIZE_4KB;
1291 let device_info =
1292 DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor, HYP_GRANULE).unwrap();
Jaewan Kim52477ae2023-11-21 21:20:52 +09001293 assert_eq!(device_info, None);
1294 }
1295
1296 #[test]
1297 fn device_info_new_without_device() {
1298 let mut fdt_data = fs::read(FDT_WITHOUT_DEVICE_FILE_PATH).unwrap();
1299 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1300 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1301 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1302
1303 let hypervisor: MockHypervisor = Default::default();
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +01001304 const HYP_GRANULE: usize = SIZE_4KB;
1305 let device_info =
1306 DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor, HYP_GRANULE).unwrap();
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001307 assert_eq!(device_info, None);
1308 }
1309
1310 #[test]
Jaewan Kima67e36a2023-11-29 16:50:23 +09001311 fn device_info_assigned_info_without_iommus() {
1312 let mut fdt_data = fs::read(FDT_WITHOUT_IOMMUS_FILE_PATH).unwrap();
1313 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1314 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1315 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1316
Jaewan Kim52477ae2023-11-21 21:20:52 +09001317 let hypervisor = MockHypervisor {
1318 mmio_tokens: [((0x9, 0xFF), 0x300)].into(),
1319 iommu_tokens: BTreeMap::new(),
1320 };
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +01001321 const HYP_GRANULE: usize = SIZE_4KB;
1322 let device_info =
1323 DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor, HYP_GRANULE).unwrap().unwrap();
Jaewan Kima67e36a2023-11-29 16:50:23 +09001324
1325 let expected = [AssignedDeviceInfo {
Jaewan Kimc39974e2023-12-02 01:13:30 +09001326 node_path: CString::new("/bus0/backlight").unwrap(),
Jaewan Kim52477ae2023-11-21 21:20:52 +09001327 reg: vec![[0x9, 0xFF].into()],
Jaewan Kima67e36a2023-11-29 16:50:23 +09001328 interrupts: into_fdt_prop(vec![0x0, 0xF, 0x4]),
1329 iommus: vec![],
1330 }];
1331
1332 assert_eq!(device_info.assigned_devices, expected);
1333 }
1334
1335 #[test]
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001336 fn device_info_assigned_info() {
1337 let mut fdt_data = fs::read(FDT_FILE_PATH).unwrap();
1338 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1339 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1340 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1341
Jaewan Kim52477ae2023-11-21 21:20:52 +09001342 let hypervisor = MockHypervisor {
1343 mmio_tokens: [((0x9, 0xFF), 0x12F00000)].into(),
1344 iommu_tokens: [((0x4, 0xFF0), (0x12E40000, 0x3))].into(),
1345 };
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +01001346 const HYP_GRANULE: usize = SIZE_4KB;
1347 let device_info =
1348 DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor, HYP_GRANULE).unwrap().unwrap();
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001349
1350 let expected = [AssignedDeviceInfo {
1351 node_path: CString::new("/rng").unwrap(),
Jaewan Kim52477ae2023-11-21 21:20:52 +09001352 reg: vec![[0x9, 0xFF].into()],
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001353 interrupts: into_fdt_prop(vec![0x0, 0xF, 0x4]),
Jaewan Kima67e36a2023-11-29 16:50:23 +09001354 iommus: vec![(PvIommu { id: 0x4 }, Vsid(0xFF0))],
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001355 }];
1356
1357 assert_eq!(device_info.assigned_devices, expected);
1358 }
1359
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001360 #[test]
1361 fn device_info_filter() {
1362 let mut fdt_data = fs::read(FDT_FILE_PATH).unwrap();
1363 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1364 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1365 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1366
Jaewan Kim52477ae2023-11-21 21:20:52 +09001367 let hypervisor = MockHypervisor {
1368 mmio_tokens: [((0x9, 0xFF), 0x12F00000)].into(),
1369 iommu_tokens: [((0x4, 0xFF0), (0x12E40000, 0x3))].into(),
1370 };
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +01001371 const HYP_GRANULE: usize = SIZE_4KB;
1372 let device_info =
1373 DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor, HYP_GRANULE).unwrap().unwrap();
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001374 device_info.filter(vm_dtbo).unwrap();
1375
1376 let vm_dtbo = vm_dtbo.as_mut();
1377
Jaewan Kim371f6c82024-02-24 01:33:37 +09001378 let symbols = vm_dtbo.symbols().unwrap().unwrap();
1379
Jaewan Kima232ed02024-02-25 16:08:14 +00001380 let rng = vm_dtbo.node(cstr!("/fragment@0/__overlay__/rng")).unwrap();
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001381 assert_ne!(rng, None);
Jaewan Kim371f6c82024-02-24 01:33:37 +09001382 let rng_symbol = symbols.getprop_str(cstr!("rng")).unwrap();
Jaewan Kima232ed02024-02-25 16:08:14 +00001383 assert_eq!(Some(cstr!("/fragment@0/__overlay__/rng")), rng_symbol);
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001384
Jaewan Kima232ed02024-02-25 16:08:14 +00001385 let light = vm_dtbo.node(cstr!("/fragment@0/__overlay__/light")).unwrap();
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001386 assert_eq!(light, None);
Jaewan Kim371f6c82024-02-24 01:33:37 +09001387 let light_symbol = symbols.getprop_str(cstr!("light")).unwrap();
1388 assert_eq!(None, light_symbol);
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001389
Jaewan Kima232ed02024-02-25 16:08:14 +00001390 let led = vm_dtbo.node(cstr!("/fragment@0/__overlay__/led")).unwrap();
Jaewan Kima67e36a2023-11-29 16:50:23 +09001391 assert_eq!(led, None);
Jaewan Kim371f6c82024-02-24 01:33:37 +09001392 let led_symbol = symbols.getprop_str(cstr!("led")).unwrap();
1393 assert_eq!(None, led_symbol);
Jaewan Kima67e36a2023-11-29 16:50:23 +09001394
Jaewan Kima232ed02024-02-25 16:08:14 +00001395 let backlight = vm_dtbo.node(cstr!("/fragment@0/__overlay__/bus0/backlight")).unwrap();
Jaewan Kima67e36a2023-11-29 16:50:23 +09001396 assert_eq!(backlight, None);
Jaewan Kim371f6c82024-02-24 01:33:37 +09001397 let backlight_symbol = symbols.getprop_str(cstr!("backlight")).unwrap();
1398 assert_eq!(None, backlight_symbol);
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001399 }
1400
1401 #[test]
1402 fn device_info_patch() {
Jaewan Kima67e36a2023-11-29 16:50:23 +09001403 let mut fdt_data = fs::read(FDT_WITHOUT_IOMMUS_FILE_PATH).unwrap();
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001404 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1405 let mut data = vec![0_u8; fdt_data.len() + vm_dtbo_data.len()];
1406 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1407 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1408 let platform_dt = Fdt::create_empty_tree(data.as_mut_slice()).unwrap();
1409
Jaewan Kim52477ae2023-11-21 21:20:52 +09001410 let hypervisor = MockHypervisor {
1411 mmio_tokens: [((0x9, 0xFF), 0x300)].into(),
1412 iommu_tokens: BTreeMap::new(),
1413 };
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +01001414 const HYP_GRANULE: usize = SIZE_4KB;
1415 let device_info =
1416 DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor, HYP_GRANULE).unwrap().unwrap();
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001417 device_info.filter(vm_dtbo).unwrap();
1418
1419 // SAFETY: Damaged VM DTBO wouldn't be used after this unsafe block.
1420 unsafe {
1421 platform_dt.apply_overlay(vm_dtbo.as_mut()).unwrap();
1422 }
Jaewan Kim0bd637d2023-11-10 13:09:41 +09001423 device_info.patch(platform_dt).unwrap();
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001424
Jaewan Kimc39974e2023-12-02 01:13:30 +09001425 let rng_node = platform_dt.node(cstr!("/bus0/backlight")).unwrap().unwrap();
1426 let phandle = rng_node.getprop_u32(cstr!("phandle")).unwrap();
1427 assert_ne!(None, phandle);
1428
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001429 // Note: Intentionally not using AssignedDeviceNode for matching all props.
Jaewan Kim0bd637d2023-11-10 13:09:41 +09001430 type FdtResult<T> = libfdt::Result<T>;
1431 let expected: Vec<(FdtResult<&CStr>, FdtResult<Vec<u8>>)> = vec![
Jaewan Kima67e36a2023-11-29 16:50:23 +09001432 (Ok(cstr!("android,backlight,ignore-gctrl-reset")), Ok(Vec::new())),
1433 (Ok(cstr!("compatible")), Ok(Vec::from(*b"android,backlight\0"))),
Jaewan Kim0bd637d2023-11-10 13:09:41 +09001434 (Ok(cstr!("interrupts")), Ok(into_fdt_prop(vec![0x0, 0xF, 0x4]))),
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001435 (Ok(cstr!("iommus")), Ok(Vec::new())),
Jaewan Kimc39974e2023-12-02 01:13:30 +09001436 (Ok(cstr!("phandle")), Ok(into_fdt_prop(vec![phandle.unwrap()]))),
Jaewan Kim0bd637d2023-11-10 13:09:41 +09001437 (Ok(cstr!("reg")), Ok(into_fdt_prop(vec![0x0, 0x9, 0x0, 0xFF]))),
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001438 ];
1439
Jaewan Kim0bd637d2023-11-10 13:09:41 +09001440 let mut properties: Vec<_> = rng_node
1441 .properties()
1442 .unwrap()
1443 .map(|prop| (prop.name(), prop.value().map(|x| x.into())))
1444 .collect();
1445 properties.sort_by(|a, b| {
1446 let lhs = a.0.unwrap_or_default();
1447 let rhs = b.0.unwrap_or_default();
1448 lhs.partial_cmp(rhs).unwrap()
1449 });
1450
1451 assert_eq!(properties, expected);
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001452 }
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001453
1454 #[test]
Jaewan Kimc730ebf2024-02-22 10:34:55 +09001455 fn device_info_patch_no_pviommus() {
1456 let mut fdt_data = fs::read(FDT_WITHOUT_IOMMUS_FILE_PATH).unwrap();
1457 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1458 let mut data = vec![0_u8; fdt_data.len() + vm_dtbo_data.len()];
1459 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1460 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1461 let platform_dt = Fdt::create_empty_tree(data.as_mut_slice()).unwrap();
1462
1463 let hypervisor = MockHypervisor {
1464 mmio_tokens: [((0x9, 0xFF), 0x300)].into(),
1465 iommu_tokens: BTreeMap::new(),
1466 };
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +01001467 const HYP_GRANULE: usize = SIZE_4KB;
1468 let device_info =
1469 DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor, HYP_GRANULE).unwrap().unwrap();
Jaewan Kimc730ebf2024-02-22 10:34:55 +09001470 device_info.filter(vm_dtbo).unwrap();
1471
1472 // SAFETY: Damaged VM DTBO wouldn't be used after this unsafe block.
1473 unsafe {
1474 platform_dt.apply_overlay(vm_dtbo.as_mut()).unwrap();
1475 }
1476 device_info.patch(platform_dt).unwrap();
1477
1478 let compatible = platform_dt.root().next_compatible(cstr!("pkvm,pviommu")).unwrap();
1479 assert_eq!(None, compatible);
1480
1481 if let Some(symbols) = platform_dt.symbols().unwrap() {
1482 for prop in symbols.properties().unwrap() {
1483 let path = CStr::from_bytes_with_nul(prop.value().unwrap()).unwrap();
1484 assert_ne!(None, platform_dt.node(path).unwrap());
1485 }
1486 }
1487 }
1488
1489 #[test]
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001490 fn device_info_overlay_iommu() {
Jaewan Kima67e36a2023-11-29 16:50:23 +09001491 let mut fdt_data = fs::read(FDT_FILE_PATH).unwrap();
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001492 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1493 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1494 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1495 let mut platform_dt_data = pvmfw_fdt_template::RAW.to_vec();
1496 platform_dt_data.resize(pvmfw_fdt_template::RAW.len() * 2, 0);
1497 let platform_dt = Fdt::from_mut_slice(&mut platform_dt_data).unwrap();
1498 platform_dt.unpack().unwrap();
1499
Jaewan Kim52477ae2023-11-21 21:20:52 +09001500 let hypervisor = MockHypervisor {
1501 mmio_tokens: [((0x9, 0xFF), 0x12F00000)].into(),
1502 iommu_tokens: [((0x4, 0xFF0), (0x12E40000, 0x3))].into(),
1503 };
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +01001504 const HYP_GRANULE: usize = SIZE_4KB;
1505 let device_info =
1506 DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor, HYP_GRANULE).unwrap().unwrap();
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001507 device_info.filter(vm_dtbo).unwrap();
1508
1509 // SAFETY: Damaged VM DTBO wouldn't be used after this unsafe block.
1510 unsafe {
1511 platform_dt.apply_overlay(vm_dtbo.as_mut()).unwrap();
1512 }
1513 device_info.patch(platform_dt).unwrap();
1514
1515 let expected = AssignedDeviceNode {
1516 path: CString::new("/rng").unwrap(),
1517 reg: into_fdt_prop(vec![0x0, 0x9, 0x0, 0xFF]),
1518 interrupts: into_fdt_prop(vec![0x0, 0xF, 0x4]),
Jaewan Kima9200492023-11-21 20:45:31 +09001519 iommus: vec![0x4, 0xFF0],
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001520 };
1521
1522 let node = AssignedDeviceNode::parse(platform_dt, &expected.path);
1523 assert_eq!(node, Ok(expected));
1524
1525 let pviommus = collect_pviommus(platform_dt);
1526 assert_eq!(pviommus, Ok(vec![0x4]));
1527 }
1528
1529 #[test]
1530 fn device_info_multiple_devices_iommus() {
1531 let mut fdt_data = fs::read(FDT_WITH_MULTIPLE_DEVICES_IOMMUS_FILE_PATH).unwrap();
1532 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1533 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1534 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1535 let mut platform_dt_data = pvmfw_fdt_template::RAW.to_vec();
1536 platform_dt_data.resize(pvmfw_fdt_template::RAW.len() * 2, 0);
1537 let platform_dt = Fdt::from_mut_slice(&mut platform_dt_data).unwrap();
1538 platform_dt.unpack().unwrap();
1539
Jaewan Kim52477ae2023-11-21 21:20:52 +09001540 let hypervisor = MockHypervisor {
1541 mmio_tokens: [
1542 ((0x9, 0xFF), 0x12F00000),
Jaewan Kim19b984f2023-12-04 15:16:50 +09001543 ((0x10000, 0x1000), 0xF00000),
1544 ((0x20000, 0x1000), 0xF10000),
Jaewan Kim52477ae2023-11-21 21:20:52 +09001545 ]
1546 .into(),
1547 iommu_tokens: [
1548 ((0x4, 0xFF0), (0x12E40000, 3)),
1549 ((0x40, 0xFFA), (0x40000, 0x4)),
1550 ((0x50, 0xFFB), (0x50000, 0x5)),
1551 ]
1552 .into(),
1553 };
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +01001554 const HYP_GRANULE: usize = SIZE_4KB;
1555 let device_info =
1556 DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor, HYP_GRANULE).unwrap().unwrap();
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001557 device_info.filter(vm_dtbo).unwrap();
1558
1559 // SAFETY: Damaged VM DTBO wouldn't be used after this unsafe block.
1560 unsafe {
1561 platform_dt.apply_overlay(vm_dtbo.as_mut()).unwrap();
1562 }
1563 device_info.patch(platform_dt).unwrap();
1564
1565 let expected_devices = [
1566 AssignedDeviceNode {
1567 path: CString::new("/rng").unwrap(),
1568 reg: into_fdt_prop(vec![0x0, 0x9, 0x0, 0xFF]),
1569 interrupts: into_fdt_prop(vec![0x0, 0xF, 0x4]),
Jaewan Kima67e36a2023-11-29 16:50:23 +09001570 iommus: vec![0x4, 0xFF0],
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001571 },
1572 AssignedDeviceNode {
1573 path: CString::new("/light").unwrap(),
Jaewan Kim19b984f2023-12-04 15:16:50 +09001574 reg: into_fdt_prop(vec![0x0, 0x10000, 0x0, 0x1000, 0x0, 0x20000, 0x0, 0x1000]),
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001575 interrupts: into_fdt_prop(vec![0x0, 0xF, 0x5]),
Jaewan Kima67e36a2023-11-29 16:50:23 +09001576 iommus: vec![0x40, 0xFFA, 0x50, 0xFFB],
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001577 },
1578 ];
1579
1580 for expected in expected_devices {
1581 let node = AssignedDeviceNode::parse(platform_dt, &expected.path);
1582 assert_eq!(node, Ok(expected));
1583 }
1584 let pviommus = collect_pviommus(platform_dt);
Jaewan Kima67e36a2023-11-29 16:50:23 +09001585 assert_eq!(pviommus, Ok(vec![0x4, 0x40, 0x50]));
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001586 }
1587
1588 #[test]
1589 fn device_info_iommu_sharing() {
1590 let mut fdt_data = fs::read(FDT_WITH_IOMMU_SHARING).unwrap();
1591 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1592 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1593 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1594 let mut platform_dt_data = pvmfw_fdt_template::RAW.to_vec();
1595 platform_dt_data.resize(pvmfw_fdt_template::RAW.len() * 2, 0);
1596 let platform_dt = Fdt::from_mut_slice(&mut platform_dt_data).unwrap();
1597 platform_dt.unpack().unwrap();
1598
Jaewan Kim52477ae2023-11-21 21:20:52 +09001599 let hypervisor = MockHypervisor {
Jaewan Kim19b984f2023-12-04 15:16:50 +09001600 mmio_tokens: [((0x9, 0xFF), 0x12F00000), ((0x1000, 0x9), 0x12000000)].into(),
1601 iommu_tokens: [((0x4, 0xFF0), (0x12E40000, 3)), ((0x4, 0xFF1), (0x12E40000, 9))].into(),
Jaewan Kim52477ae2023-11-21 21:20:52 +09001602 };
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +01001603 const HYP_GRANULE: usize = SIZE_4KB;
1604 let device_info =
1605 DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor, HYP_GRANULE).unwrap().unwrap();
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001606 device_info.filter(vm_dtbo).unwrap();
1607
1608 // SAFETY: Damaged VM DTBO wouldn't be used after this unsafe block.
1609 unsafe {
1610 platform_dt.apply_overlay(vm_dtbo.as_mut()).unwrap();
1611 }
1612 device_info.patch(platform_dt).unwrap();
1613
1614 let expected_devices = [
1615 AssignedDeviceNode {
1616 path: CString::new("/rng").unwrap(),
1617 reg: into_fdt_prop(vec![0x0, 0x9, 0x0, 0xFF]),
1618 interrupts: into_fdt_prop(vec![0x0, 0xF, 0x4]),
Jaewan Kima67e36a2023-11-29 16:50:23 +09001619 iommus: vec![0x4, 0xFF0],
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001620 },
1621 AssignedDeviceNode {
Jaewan Kima67e36a2023-11-29 16:50:23 +09001622 path: CString::new("/led").unwrap(),
Jaewan Kim19b984f2023-12-04 15:16:50 +09001623 reg: into_fdt_prop(vec![0x0, 0x1000, 0x0, 0x9]),
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001624 interrupts: into_fdt_prop(vec![0x0, 0xF, 0x5]),
Jaewan Kim19b984f2023-12-04 15:16:50 +09001625 iommus: vec![0x4, 0xFF1],
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001626 },
1627 ];
1628
1629 for expected in expected_devices {
1630 let node = AssignedDeviceNode::parse(platform_dt, &expected.path);
1631 assert_eq!(node, Ok(expected));
1632 }
1633
1634 let pviommus = collect_pviommus(platform_dt);
Jaewan Kima67e36a2023-11-29 16:50:23 +09001635 assert_eq!(pviommus, Ok(vec![0x4]));
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001636 }
1637
1638 #[test]
1639 fn device_info_iommu_id_conflict() {
1640 let mut fdt_data = fs::read(FDT_WITH_IOMMU_ID_CONFLICT).unwrap();
1641 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1642 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1643 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1644
Jaewan Kim52477ae2023-11-21 21:20:52 +09001645 let hypervisor = MockHypervisor {
Jaewan Kim19b984f2023-12-04 15:16:50 +09001646 mmio_tokens: [((0x9, 0xFF), 0x300)].into(),
Jaewan Kim52477ae2023-11-21 21:20:52 +09001647 iommu_tokens: [((0x4, 0xFF0), (0x12E40000, 0x3))].into(),
1648 };
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +01001649 const HYP_GRANULE: usize = SIZE_4KB;
1650 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor, HYP_GRANULE);
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001651
1652 assert_eq!(device_info, Err(DeviceAssignmentError::DuplicatedPvIommuIds));
1653 }
Jaewan Kim52477ae2023-11-21 21:20:52 +09001654
1655 #[test]
1656 fn device_info_invalid_reg() {
1657 let mut fdt_data = fs::read(FDT_FILE_PATH).unwrap();
1658 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1659 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1660 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1661
1662 let hypervisor = MockHypervisor {
1663 mmio_tokens: BTreeMap::new(),
1664 iommu_tokens: [((0x4, 0xFF0), (0x12E40000, 0x3))].into(),
1665 };
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +01001666 const HYP_GRANULE: usize = SIZE_4KB;
1667 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor, HYP_GRANULE);
Jaewan Kim52477ae2023-11-21 21:20:52 +09001668
Pierre-Clément Tosif4d8a0d2024-10-25 17:14:15 +01001669 assert_eq!(device_info, Err(DeviceAssignmentError::InvalidReg(0x9)));
Jaewan Kim52477ae2023-11-21 21:20:52 +09001670 }
1671
1672 #[test]
Jaewan Kim19b984f2023-12-04 15:16:50 +09001673 fn device_info_invalid_reg_out_of_order() {
1674 let mut fdt_data = fs::read(FDT_WITH_MULTIPLE_REG_IOMMU_FILE_PATH).unwrap();
1675 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1676 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1677 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1678
1679 let hypervisor = MockHypervisor {
1680 mmio_tokens: [((0xF000, 0x1000), 0xF10000), ((0xF100, 0x1000), 0xF00000)].into(),
1681 iommu_tokens: [((0xFF0, 0xF0), (0x40000, 0x4)), ((0xFF1, 0xF1), (0x50000, 0x5))].into(),
1682 };
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +01001683 const HYP_GRANULE: usize = SIZE_4KB;
1684 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor, HYP_GRANULE);
Jaewan Kim19b984f2023-12-04 15:16:50 +09001685
Pierre-Clément Tosif4d8a0d2024-10-25 17:14:15 +01001686 assert_eq!(device_info, Err(DeviceAssignmentError::InvalidRegToken(0xF10000, 0xF00000)));
Jaewan Kim19b984f2023-12-04 15:16:50 +09001687 }
1688
1689 #[test]
Jaewan Kim52477ae2023-11-21 21:20:52 +09001690 fn device_info_invalid_iommus() {
1691 let mut fdt_data = fs::read(FDT_FILE_PATH).unwrap();
1692 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1693 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1694 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1695
1696 let hypervisor = MockHypervisor {
1697 mmio_tokens: [((0x9, 0xFF), 0x12F00000)].into(),
1698 iommu_tokens: BTreeMap::new(),
1699 };
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +01001700 const HYP_GRANULE: usize = SIZE_4KB;
1701 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor, HYP_GRANULE);
Jaewan Kim52477ae2023-11-21 21:20:52 +09001702
1703 assert_eq!(device_info, Err(DeviceAssignmentError::InvalidIommus));
1704 }
Jaewan Kim19b984f2023-12-04 15:16:50 +09001705
1706 #[test]
1707 fn device_info_duplicated_pv_iommus() {
1708 let mut fdt_data = fs::read(FDT_WITH_DUPLICATED_PVIOMMUS_FILE_PATH).unwrap();
1709 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1710 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1711 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1712
1713 let hypervisor = MockHypervisor {
1714 mmio_tokens: [((0x10000, 0x1000), 0xF00000), ((0x20000, 0xFF), 0xF10000)].into(),
1715 iommu_tokens: [((0xFF, 0xF), (0x40000, 0x4))].into(),
1716 };
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +01001717 const HYP_GRANULE: usize = SIZE_4KB;
1718 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor, HYP_GRANULE);
Jaewan Kim19b984f2023-12-04 15:16:50 +09001719
1720 assert_eq!(device_info, Err(DeviceAssignmentError::DuplicatedPvIommuIds));
1721 }
1722
1723 #[test]
1724 fn device_info_duplicated_iommus() {
1725 let mut fdt_data = fs::read(FDT_FILE_PATH).unwrap();
1726 let mut vm_dtbo_data = fs::read(VM_DTBO_WITH_DUPLICATED_IOMMUS_FILE_PATH).unwrap();
1727 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1728 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1729
1730 let hypervisor = MockHypervisor {
1731 mmio_tokens: [((0x10000, 0x1000), 0xF00000), ((0x20000, 0xFF), 0xF10000)].into(),
1732 iommu_tokens: [((0xFF, 0xF), (0x40000, 0x4))].into(),
1733 };
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +01001734 const HYP_GRANULE: usize = SIZE_4KB;
1735 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor, HYP_GRANULE);
Jaewan Kim19b984f2023-12-04 15:16:50 +09001736
1737 assert_eq!(device_info, Err(DeviceAssignmentError::UnsupportedIommusDuplication));
1738 }
1739
1740 #[test]
1741 fn device_info_duplicated_iommu_mapping() {
1742 let mut fdt_data = fs::read(FDT_WITH_MULTIPLE_REG_IOMMU_FILE_PATH).unwrap();
1743 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1744 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1745 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1746
1747 let hypervisor = MockHypervisor {
1748 mmio_tokens: [((0xF000, 0x1000), 0xF00000), ((0xF100, 0x1000), 0xF10000)].into(),
1749 iommu_tokens: [((0xFF0, 0xF0), (0x40000, 0x4)), ((0xFF1, 0xF1), (0x40000, 0x4))].into(),
1750 };
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +01001751 const HYP_GRANULE: usize = SIZE_4KB;
1752 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor, HYP_GRANULE);
Jaewan Kim19b984f2023-12-04 15:16:50 +09001753
1754 assert_eq!(device_info, Err(DeviceAssignmentError::InvalidIommus));
1755 }
Jaewan Kim50246682024-03-11 23:18:54 +09001756
1757 #[test]
Pierre-Clément Tosi49e26ce2024-03-12 16:31:50 +00001758 fn device_info_overlaps_pvmfw() {
1759 let mut fdt_data = fs::read(FDT_WITH_DEVICE_OVERLAPPING_PVMFW).unwrap();
1760 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1761 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1762 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1763
1764 let hypervisor = MockHypervisor {
1765 mmio_tokens: [((0x7fee0000, 0x1000), 0xF00000)].into(),
1766 iommu_tokens: [((0xFF, 0xF), (0x40000, 0x4))].into(),
1767 };
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +01001768 const HYP_GRANULE: usize = SIZE_4KB;
1769 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor, HYP_GRANULE);
Pierre-Clément Tosi49e26ce2024-03-12 16:31:50 +00001770
Pierre-Clément Tosif4d8a0d2024-10-25 17:14:15 +01001771 assert_eq!(device_info, Err(DeviceAssignmentError::InvalidReg(0x7fee0000)));
Pierre-Clément Tosi49e26ce2024-03-12 16:31:50 +00001772 }
1773
1774 #[test]
Jaewan Kim50246682024-03-11 23:18:54 +09001775 fn device_assignment_clean() {
1776 let mut platform_dt_data = pvmfw_fdt_template::RAW.to_vec();
1777 let platform_dt = Fdt::from_mut_slice(&mut platform_dt_data).unwrap();
1778
1779 let compatible = platform_dt.root().next_compatible(cstr!("pkvm,pviommu"));
1780 assert_ne!(None, compatible.unwrap());
1781
1782 clean(platform_dt).unwrap();
1783
1784 let compatible = platform_dt.root().next_compatible(cstr!("pkvm,pviommu"));
1785 assert_eq!(Ok(None), compatible);
1786 }
Jaewan Kim8f6f4662023-12-12 17:38:47 +09001787
1788 #[test]
1789 fn device_info_dependency() {
1790 let mut fdt_data = fs::read(FDT_WITH_DEPENDENCY_FILE_PATH).unwrap();
1791 let mut vm_dtbo_data = fs::read(VM_DTBO_WITH_DEPENDENCIES_FILE_PATH).unwrap();
1792 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1793 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1794 let mut platform_dt_data = pvmfw_fdt_template::RAW.to_vec();
1795 platform_dt_data.resize(pvmfw_fdt_template::RAW.len() * 2, 0);
1796 let platform_dt = Fdt::from_mut_slice(&mut platform_dt_data).unwrap();
1797 platform_dt.unpack().unwrap();
1798
1799 let hypervisor = MockHypervisor {
1800 mmio_tokens: [((0xFF000, 0x1), 0xF000)].into(),
1801 iommu_tokens: Default::default(),
1802 };
1803
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +01001804 const HYP_GRANULE: usize = SIZE_4KB;
1805 let device_info =
1806 DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor, HYP_GRANULE).unwrap().unwrap();
Jaewan Kim8f6f4662023-12-12 17:38:47 +09001807 device_info.filter(vm_dtbo).unwrap();
1808
1809 // SAFETY: Damaged VM DTBO wouldn't be used after this unsafe block.
1810 unsafe {
1811 platform_dt.apply_overlay(vm_dtbo.as_mut()).unwrap();
1812 }
1813 device_info.patch(platform_dt).unwrap();
1814
1815 let expected = Dts::from_dtb(Path::new(EXPECTED_FDT_WITH_DEPENDENCY_FILE_PATH)).unwrap();
1816 let platform_dt = Dts::from_fdt(platform_dt).unwrap();
1817
1818 assert_eq!(expected, platform_dt);
1819 }
1820
1821 #[test]
1822 fn device_info_multiple_dependencies() {
1823 let mut fdt_data = fs::read(FDT_WITH_MULTIPLE_DEPENDENCIES_FILE_PATH).unwrap();
1824 let mut vm_dtbo_data = fs::read(VM_DTBO_WITH_DEPENDENCIES_FILE_PATH).unwrap();
1825 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1826 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1827 let mut platform_dt_data = pvmfw_fdt_template::RAW.to_vec();
1828 platform_dt_data.resize(pvmfw_fdt_template::RAW.len() * 2, 0);
1829 let platform_dt = Fdt::from_mut_slice(&mut platform_dt_data).unwrap();
1830 platform_dt.unpack().unwrap();
1831
1832 let hypervisor = MockHypervisor {
1833 mmio_tokens: [((0xFF000, 0x1), 0xF000), ((0xFF100, 0x1), 0xF100)].into(),
1834 iommu_tokens: Default::default(),
1835 };
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +01001836 const HYP_GRANULE: usize = SIZE_4KB;
1837 let device_info =
1838 DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor, HYP_GRANULE).unwrap().unwrap();
Jaewan Kim8f6f4662023-12-12 17:38:47 +09001839 device_info.filter(vm_dtbo).unwrap();
1840
1841 // SAFETY: Damaged VM DTBO wouldn't be used after this unsafe block.
1842 unsafe {
1843 platform_dt.apply_overlay(vm_dtbo.as_mut()).unwrap();
1844 }
1845 device_info.patch(platform_dt).unwrap();
1846
1847 let expected =
1848 Dts::from_dtb(Path::new(EXPECTED_FDT_WITH_MULTIPLE_DEPENDENCIES_FILE_PATH)).unwrap();
1849 let platform_dt = Dts::from_fdt(platform_dt).unwrap();
1850
1851 assert_eq!(expected, platform_dt);
1852 }
1853
1854 #[test]
1855 fn device_info_dependency_loop() {
1856 let mut fdt_data = fs::read(FDT_WITH_DEPENDENCY_LOOP_FILE_PATH).unwrap();
1857 let mut vm_dtbo_data = fs::read(VM_DTBO_WITH_DEPENDENCIES_FILE_PATH).unwrap();
1858 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1859 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1860 let mut platform_dt_data = pvmfw_fdt_template::RAW.to_vec();
1861 platform_dt_data.resize(pvmfw_fdt_template::RAW.len() * 2, 0);
1862 let platform_dt = Fdt::from_mut_slice(&mut platform_dt_data).unwrap();
1863 platform_dt.unpack().unwrap();
1864
1865 let hypervisor = MockHypervisor {
1866 mmio_tokens: [((0xFF200, 0x1), 0xF200)].into(),
1867 iommu_tokens: Default::default(),
1868 };
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +01001869 const HYP_GRANULE: usize = SIZE_4KB;
1870 let device_info =
1871 DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor, HYP_GRANULE).unwrap().unwrap();
Jaewan Kim8f6f4662023-12-12 17:38:47 +09001872 device_info.filter(vm_dtbo).unwrap();
1873
1874 // SAFETY: Damaged VM DTBO wouldn't be used after this unsafe block.
1875 unsafe {
1876 platform_dt.apply_overlay(vm_dtbo.as_mut()).unwrap();
1877 }
1878 device_info.patch(platform_dt).unwrap();
1879
1880 let expected =
1881 Dts::from_dtb(Path::new(EXPECTED_FDT_WITH_DEPENDENCY_LOOP_FILE_PATH)).unwrap();
1882 let platform_dt = Dts::from_fdt(platform_dt).unwrap();
1883
1884 assert_eq!(expected, platform_dt);
1885 }
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001886}