blob: a0a5f550fe2e0d62af5169e9fe53bd5cf5169dbc [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 Tosia9b345f2024-04-27 01:01:42 +010033// TODO(b/308694211): Use vmbase::hyp::{DeviceAssigningHypervisor, Error} proper for tests.
34#[cfg(not(test))]
35use vmbase::hyp::DeviceAssigningHypervisor;
Jaewan Kim8f6f4662023-12-12 17:38:47 +090036use zerocopy::byteorder::big_endian::U32;
37use zerocopy::FromBytes as _;
Jaewan Kimc6e023b2023-10-12 15:11:05 +090038
Jaewan Kimc6e023b2023-10-12 15:11:05 +090039// TODO(b/308694211): Use cstr! from vmbase instead.
40macro_rules! cstr {
41 ($str:literal) => {{
Pierre-Clément Tosid701a0b2023-11-07 15:38:59 +000042 const S: &str = concat!($str, "\0");
43 const C: &::core::ffi::CStr = match ::core::ffi::CStr::from_bytes_with_nul(S.as_bytes()) {
44 Ok(v) => v,
45 Err(_) => panic!("string contains interior NUL"),
46 };
47 C
Jaewan Kimc6e023b2023-10-12 15:11:05 +090048 }};
49}
50
Jaewan Kimc6e023b2023-10-12 15:11:05 +090051// TODO(b/277993056): Keep constants derived from platform.dts in one place.
52const CELLS_PER_INTERRUPT: usize = 3; // from /intc node in platform.dts
53
54/// Errors in device assignment.
55#[derive(Clone, Copy, Debug, Eq, PartialEq)]
56pub enum DeviceAssignmentError {
Jaewan Kim52477ae2023-11-21 21:20:52 +090057 /// Invalid VM DTBO
Jaewan Kimc6e023b2023-10-12 15:11:05 +090058 InvalidDtbo,
59 /// Invalid __symbols__
60 InvalidSymbols,
Jaewan Kim19b984f2023-12-04 15:16:50 +090061 /// Malformed <reg>. Can't parse.
62 MalformedReg,
Pierre-Clément Tosif4d8a0d2024-10-25 17:14:15 +010063 /// Missing physical <reg> of assigned device.
64 MissingReg(u64, u64),
65 /// Extra <reg> of assigned device.
66 ExtraReg(u64, u64),
Pierre-Clément Tosi8b78bc32024-03-13 17:37:07 +000067 /// Invalid virtual <reg> of assigned device.
Pierre-Clément Tosif4d8a0d2024-10-25 17:14:15 +010068 InvalidReg(u64),
69 /// Token for <reg> of assigned device does not match expected value.
70 InvalidRegToken(u64, u64),
Jaewan Kimc6e023b2023-10-12 15:11:05 +090071 /// Invalid <interrupts>
72 InvalidInterrupts,
Jaewan Kim19b984f2023-12-04 15:16:50 +090073 /// Malformed <iommus>
74 MalformedIommus,
Jaewan Kim51ccfed2023-11-08 13:51:58 +090075 /// Invalid <iommus>
76 InvalidIommus,
Jaewan Kim19b984f2023-12-04 15:16:50 +090077 /// Invalid phys IOMMU node
78 InvalidPhysIommu,
Jaewan Kima9200492023-11-21 20:45:31 +090079 /// Invalid pvIOMMU node
80 InvalidPvIommu,
Jaewan Kim51ccfed2023-11-08 13:51:58 +090081 /// Too many pvIOMMU
82 TooManyPvIommu,
Jaewan Kim19b984f2023-12-04 15:16:50 +090083 /// Duplicated phys IOMMU IDs exist
84 DuplicatedIommuIds,
Jaewan Kim51ccfed2023-11-08 13:51:58 +090085 /// Duplicated pvIOMMU IDs exist
86 DuplicatedPvIommuIds,
Jaewan Kimf8abbb52023-12-12 22:11:39 +090087 /// Unsupported path format. Only supports full path.
88 UnsupportedPathFormat,
Jaewan Kimc6e023b2023-10-12 15:11:05 +090089 /// Unsupported overlay target syntax. Only supports <target-path> with full path.
90 UnsupportedOverlayTarget,
Jaewan Kim19b984f2023-12-04 15:16:50 +090091 /// Unsupported PhysIommu,
92 UnsupportedPhysIommu,
93 /// Unsupported (pvIOMMU id, vSID) duplication. Currently the pair should be unique.
94 UnsupportedPvIommusDuplication,
95 /// Unsupported (IOMMU token, SID) duplication. Currently the pair should be unique.
96 UnsupportedIommusDuplication,
Jaewan Kim51ccfed2023-11-08 13:51:58 +090097 /// Internal error
98 Internal,
Jaewan Kimc6e023b2023-10-12 15:11:05 +090099 /// Unexpected error from libfdt
100 UnexpectedFdtError(FdtError),
101}
102
103impl From<FdtError> for DeviceAssignmentError {
104 fn from(e: FdtError) -> Self {
105 DeviceAssignmentError::UnexpectedFdtError(e)
106 }
107}
108
109impl fmt::Display for DeviceAssignmentError {
110 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
111 match self {
112 Self::InvalidDtbo => write!(f, "Invalid DTBO"),
113 Self::InvalidSymbols => write!(
114 f,
115 "Invalid property in /__symbols__. Must point to valid assignable device node."
116 ),
Jaewan Kim19b984f2023-12-04 15:16:50 +0900117 Self::MalformedReg => write!(f, "Malformed <reg>. Can't parse"),
Pierre-Clément Tosif4d8a0d2024-10-25 17:14:15 +0100118 Self::MissingReg(addr, size) => {
119 write!(f, "Missing physical MMIO region: addr:{addr:#x}), size:{size:#x}")
Pierre-Clément Tosi8b78bc32024-03-13 17:37:07 +0000120 }
Pierre-Clément Tosif4d8a0d2024-10-25 17:14:15 +0100121 Self::ExtraReg(addr, size) => {
122 write!(f, "Unexpected extra MMIO region: addr:{addr:#x}), size:{size:#x}")
123 }
124 Self::InvalidReg(addr) => {
125 write!(f, "Invalid guest MMIO granule (addr: {addr:#x})")
126 }
127 Self::InvalidRegToken(token, expected) => {
128 write!(f, "Unexpected MMIO token ({token:#x}), should be {expected:#x}")
Pierre-Clément Tosi8b78bc32024-03-13 17:37:07 +0000129 }
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900130 Self::InvalidInterrupts => write!(f, "Invalid <interrupts>"),
Jaewan Kim19b984f2023-12-04 15:16:50 +0900131 Self::MalformedIommus => write!(f, "Malformed <iommus>. Can't parse."),
132 Self::InvalidIommus => {
133 write!(f, "Invalid <iommus>. Failed to validate with hypervisor")
134 }
135 Self::InvalidPhysIommu => write!(f, "Invalid phys IOMMU node"),
Jaewan Kima9200492023-11-21 20:45:31 +0900136 Self::InvalidPvIommu => write!(f, "Invalid pvIOMMU node"),
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900137 Self::TooManyPvIommu => write!(
138 f,
139 "Too many pvIOMMU node. Insufficient pre-populated pvIOMMUs in platform DT"
140 ),
Jaewan Kim19b984f2023-12-04 15:16:50 +0900141 Self::DuplicatedIommuIds => {
142 write!(f, "Duplicated IOMMU IDs exist. IDs must unique among iommu node")
143 }
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900144 Self::DuplicatedPvIommuIds => {
Jaewan Kim19b984f2023-12-04 15:16:50 +0900145 write!(f, "Duplicated pvIOMMU IDs exist. IDs must unique among iommu node")
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900146 }
Jaewan Kimf8abbb52023-12-12 22:11:39 +0900147 Self::UnsupportedPathFormat => {
148 write!(f, "Unsupported UnsupportedPathFormat. Only supports full path")
149 }
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900150 Self::UnsupportedOverlayTarget => {
151 write!(f, "Unsupported overlay target. Only supports 'target-path = \"/\"'")
152 }
Jaewan Kim19b984f2023-12-04 15:16:50 +0900153 Self::UnsupportedPhysIommu => {
154 write!(f, "Unsupported Phys IOMMU. Currently only supports #iommu-cells = <1>")
155 }
156 Self::UnsupportedPvIommusDuplication => {
157 write!(f, "Unsupported (pvIOMMU id, vSID) duplication. Currently the pair should be unique.")
158 }
159 Self::UnsupportedIommusDuplication => {
160 write!(f, "Unsupported (IOMMU token, SID) duplication. Currently the pair should be unique.")
161 }
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900162 Self::Internal => write!(f, "Internal error"),
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900163 Self::UnexpectedFdtError(e) => write!(f, "Unexpected Error from libfdt: {e}"),
164 }
165 }
166}
167
168pub type Result<T> = core::result::Result<T, DeviceAssignmentError>;
169
Jaewan Kimf8abbb52023-12-12 22:11:39 +0900170#[derive(Clone, Default, Ord, PartialOrd, Eq, PartialEq)]
171pub struct DtPathTokens<'a> {
172 tokens: Vec<&'a [u8]>,
173}
174
175impl<'a> fmt::Debug for DtPathTokens<'a> {
176 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
177 let mut list = f.debug_list();
178 for token in &self.tokens {
179 let mut bytes = token.to_vec();
180 bytes.push(b'\0');
181 match CString::from_vec_with_nul(bytes) {
182 Ok(string) => list.entry(&string),
183 Err(_) => list.entry(token),
184 };
185 }
186 list.finish()
187 }
188}
189
190impl<'a> DtPathTokens<'a> {
191 fn new(path: &'a CStr) -> Result<Self> {
192 if path.to_bytes().first() != Some(&b'/') {
193 return Err(DeviceAssignmentError::UnsupportedPathFormat);
194 }
195 let tokens: Vec<_> = path
196 .to_bytes()
197 .split(|char| *char == b'/')
198 .filter(|&component| !component.is_empty())
199 .collect();
200 Ok(Self { tokens })
201 }
202
203 fn to_overlay_target_path(&self) -> Result<Self> {
204 if !self.is_overlayable_node() {
205 return Err(DeviceAssignmentError::InvalidDtbo);
206 }
207 Ok(Self { tokens: self.tokens.as_slice()[2..].to_vec() })
208 }
209
210 fn to_cstring(&self) -> CString {
211 if self.tokens.is_empty() {
212 return CString::new(*b"/\0").unwrap();
213 }
214
215 let size = self.tokens.iter().fold(0, |sum, token| sum + token.len() + 1);
216 let mut path = Vec::with_capacity(size + 1);
217 for token in &self.tokens {
218 path.push(b'/');
219 path.extend_from_slice(token);
220 }
221 path.push(b'\0');
222
223 CString::from_vec_with_nul(path).unwrap()
224 }
225
226 fn is_overlayable_node(&self) -> bool {
227 self.tokens.get(1) == Some(&&b"__overlay__"[..])
228 }
229}
230
Jaewan Kim8f6f4662023-12-12 17:38:47 +0900231#[derive(Debug, Eq, PartialEq)]
232enum DeviceTreeChildrenMask {
233 Partial(Vec<DeviceTreeMask>),
234 All,
235}
236
237#[derive(Eq, PartialEq)]
238struct DeviceTreeMask {
239 name_bytes: Vec<u8>,
240 children: DeviceTreeChildrenMask,
241}
242
243impl fmt::Debug for DeviceTreeMask {
244 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
245 let name_bytes = [self.name_bytes.as_slice(), b"\0"].concat();
246
247 f.debug_struct("DeviceTreeMask")
248 .field("name", &CStr::from_bytes_with_nul(&name_bytes).unwrap())
249 .field("children", &self.children)
250 .finish()
251 }
252}
253
254impl DeviceTreeMask {
255 fn new() -> Self {
256 Self { name_bytes: b"/".to_vec(), children: DeviceTreeChildrenMask::Partial(Vec::new()) }
257 }
258
259 fn mask_internal(&mut self, path: &DtPathTokens, leaf_mask: DeviceTreeChildrenMask) -> bool {
260 let mut iter = self;
261 let mut newly_masked = false;
262 'next_token: for path_token in &path.tokens {
263 let DeviceTreeChildrenMask::Partial(ref mut children) = &mut iter.children else {
264 return false;
265 };
266
267 // Note: Can't use iterator for 'get or insert'. (a.k.a. polonius Rust)
268 #[allow(clippy::needless_range_loop)]
269 for i in 0..children.len() {
270 if children[i].name_bytes.as_slice() == *path_token {
271 iter = &mut children[i];
272 newly_masked = false;
273 continue 'next_token;
274 }
275 }
276 let child = Self {
277 name_bytes: path_token.to_vec(),
278 children: DeviceTreeChildrenMask::Partial(Vec::new()),
279 };
280 children.push(child);
281 newly_masked = true;
282 iter = children.last_mut().unwrap()
283 }
284 iter.children = leaf_mask;
285 newly_masked
286 }
287
288 fn mask(&mut self, path: &DtPathTokens) -> bool {
289 self.mask_internal(path, DeviceTreeChildrenMask::Partial(Vec::new()))
290 }
291
292 fn mask_all(&mut self, path: &DtPathTokens) {
293 self.mask_internal(path, DeviceTreeChildrenMask::All);
294 }
295}
296
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900297/// Represents VM DTBO
298#[repr(transparent)]
299pub struct VmDtbo(Fdt);
300
301impl VmDtbo {
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900302 /// Wraps a mutable slice containing a VM DTBO.
303 ///
304 /// Fails if the VM DTBO does not pass validation.
305 pub fn from_mut_slice(dtbo: &mut [u8]) -> Result<&mut Self> {
306 // This validates DTBO
307 let fdt = Fdt::from_mut_slice(dtbo)?;
308 // SAFETY: VmDtbo is a transparent wrapper around Fdt, so representation is the same.
309 Ok(unsafe { mem::transmute::<&mut Fdt, &mut Self>(fdt) })
310 }
311
312 // Locates device node path as if the given dtbo node path is assigned and VM DTBO is overlaid.
313 // For given dtbo node path, this concatenates <target-path> of the enclosing fragment and
314 // relative path from __overlay__ node.
315 //
316 // Here's an example with sample VM DTBO:
317 // / {
318 // fragment@rng {
319 // target-path = "/"; // Always 'target-path = "/"'. Disallows <target> or other path.
320 // __overlay__ {
321 // rng { ... }; // Actual device node is here. If overlaid, path would be "/rng"
322 // };
323 // };
Jaewan Kim80ef9fa2024-02-25 16:08:14 +0000324 // __symbols__ { // Contains list of assignable devices
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900325 // rng = "/fragment@rng/__overlay__/rng";
326 // };
327 // };
328 //
329 // Then locate_overlay_target_path(cstr!("/fragment@rng/__overlay__/rng")) is Ok("/rng")
330 //
331 // Contrary to fdt_overlay_target_offset(), this API enforces overlay target property
332 // 'target-path = "/"', so the overlay doesn't modify and/or append platform DT's existing
333 // node and/or properties. The enforcement is for compatibility reason.
Jaewan Kim19b984f2023-12-04 15:16:50 +0900334 fn locate_overlay_target_path(
335 &self,
Jaewan Kimf8abbb52023-12-12 22:11:39 +0900336 dtbo_node_path: &DtPathTokens,
Jaewan Kim19b984f2023-12-04 15:16:50 +0900337 dtbo_node: &FdtNode,
338 ) -> Result<CString> {
Jaewan Kim19b984f2023-12-04 15:16:50 +0900339 let fragment_node = dtbo_node.supernode_at_depth(1)?;
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900340 let target_path = fragment_node
Pierre-Clément Tosid701a0b2023-11-07 15:38:59 +0000341 .getprop_str(cstr!("target-path"))?
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900342 .ok_or(DeviceAssignmentError::InvalidDtbo)?;
343 if target_path != cstr!("/") {
344 return Err(DeviceAssignmentError::UnsupportedOverlayTarget);
345 }
346
Jaewan Kimf8abbb52023-12-12 22:11:39 +0900347 let overlaid_path = dtbo_node_path.to_overlay_target_path()?;
348 Ok(overlaid_path.to_cstring())
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900349 }
Jaewan Kim19b984f2023-12-04 15:16:50 +0900350
351 fn parse_physical_iommus(physical_node: &FdtNode) -> Result<BTreeMap<Phandle, PhysIommu>> {
352 let mut phys_iommus = BTreeMap::new();
353 for (node, _) in physical_node.descendants() {
354 let Some(phandle) = node.get_phandle()? else {
355 continue; // Skips unreachable IOMMU node
356 };
357 let Some(iommu) = PhysIommu::parse(&node)? else {
358 continue; // Skip if not a PhysIommu.
359 };
360 if phys_iommus.insert(phandle, iommu).is_some() {
361 return Err(FdtError::BadPhandle.into());
362 }
363 }
364 Self::validate_physical_iommus(&phys_iommus)?;
365 Ok(phys_iommus)
366 }
367
368 fn validate_physical_iommus(phys_iommus: &BTreeMap<Phandle, PhysIommu>) -> Result<()> {
369 let unique_iommus: BTreeSet<_> = phys_iommus.values().cloned().collect();
370 if phys_iommus.len() != unique_iommus.len() {
371 return Err(DeviceAssignmentError::DuplicatedIommuIds);
372 }
373 Ok(())
374 }
375
376 fn validate_physical_devices(
377 physical_devices: &BTreeMap<Phandle, PhysicalDeviceInfo>,
378 ) -> Result<()> {
379 // Only need to validate iommus because <reg> will be validated together with PV <reg>
380 // see: DeviceAssignmentInfo::validate_all_regs().
381 let mut all_iommus = BTreeSet::new();
382 for physical_device in physical_devices.values() {
383 for iommu in &physical_device.iommus {
384 if !all_iommus.insert(iommu) {
385 error!("Unsupported phys IOMMU duplication found, <iommus> = {iommu:?}");
386 return Err(DeviceAssignmentError::UnsupportedIommusDuplication);
387 }
388 }
389 }
390 Ok(())
391 }
392
393 fn parse_physical_devices_with_iommus(
394 physical_node: &FdtNode,
395 phys_iommus: &BTreeMap<Phandle, PhysIommu>,
396 ) -> Result<BTreeMap<Phandle, PhysicalDeviceInfo>> {
397 let mut physical_devices = BTreeMap::new();
398 for (node, _) in physical_node.descendants() {
399 let Some(info) = PhysicalDeviceInfo::parse(&node, phys_iommus)? else {
400 continue;
401 };
402 if physical_devices.insert(info.target, info).is_some() {
403 return Err(DeviceAssignmentError::InvalidDtbo);
404 }
405 }
406 Self::validate_physical_devices(&physical_devices)?;
407 Ok(physical_devices)
408 }
409
410 /// Parses Physical devices in VM DTBO
411 fn parse_physical_devices(&self) -> Result<BTreeMap<Phandle, PhysicalDeviceInfo>> {
412 let Some(physical_node) = self.as_ref().node(cstr!("/host"))? else {
413 return Ok(BTreeMap::new());
414 };
415
416 let phys_iommus = Self::parse_physical_iommus(&physical_node)?;
417 Self::parse_physical_devices_with_iommus(&physical_node, &phys_iommus)
418 }
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900419
Jaewan Kimf8abbb52023-12-12 22:11:39 +0900420 fn node(&self, path: &DtPathTokens) -> Result<Option<FdtNode>> {
421 let mut node = self.as_ref().root();
422 for token in &path.tokens {
423 let Some(subnode) = node.subnode_with_name_bytes(token)? else {
424 return Ok(None);
425 };
426 node = subnode;
427 }
428 Ok(Some(node))
429 }
Jaewan Kim8f6f4662023-12-12 17:38:47 +0900430
431 fn collect_overlayable_nodes_with_phandle(&self) -> Result<BTreeMap<Phandle, DtPathTokens>> {
432 let mut paths = BTreeMap::new();
433 let mut path: DtPathTokens = Default::default();
434 let root = self.as_ref().root();
435 for (node, depth) in root.descendants() {
436 path.tokens.truncate(depth - 1);
437 path.tokens.push(node.name()?.to_bytes());
438 if !path.is_overlayable_node() {
439 continue;
440 }
441 if let Some(phandle) = node.get_phandle()? {
442 paths.insert(phandle, path.clone());
443 }
444 }
445 Ok(paths)
446 }
447
448 fn collect_phandle_references_from_overlayable_nodes(
449 &self,
450 ) -> Result<BTreeMap<DtPathTokens, Vec<Phandle>>> {
451 const CELL_SIZE: usize = core::mem::size_of::<u32>();
452
453 let vm_dtbo = self.as_ref();
454
455 let mut phandle_map = BTreeMap::new();
456 let Some(local_fixups) = vm_dtbo.node(cstr!("/__local_fixups__"))? else {
457 return Ok(phandle_map);
458 };
459
460 let mut path: DtPathTokens = Default::default();
461 for (fixup_node, depth) in local_fixups.descendants() {
462 let node_name = fixup_node.name()?;
463 path.tokens.truncate(depth - 1);
464 path.tokens.push(node_name.to_bytes());
465 if path.tokens.len() != depth {
466 return Err(DeviceAssignmentError::Internal);
467 }
468 if !path.is_overlayable_node() {
469 continue;
470 }
471 let target_node = self.node(&path)?.ok_or(DeviceAssignmentError::InvalidDtbo)?;
472
473 let mut phandles = vec![];
474 for fixup_prop in fixup_node.properties()? {
475 let target_prop = target_node
476 .getprop(fixup_prop.name()?)
477 .or(Err(DeviceAssignmentError::InvalidDtbo))?
478 .ok_or(DeviceAssignmentError::InvalidDtbo)?;
479 let fixup_prop_values = fixup_prop.value()?;
480 if fixup_prop_values.is_empty() || fixup_prop_values.len() % CELL_SIZE != 0 {
481 return Err(DeviceAssignmentError::InvalidDtbo);
482 }
483
484 for fixup_prop_cell in fixup_prop_values.chunks(CELL_SIZE) {
485 let phandle_offset: usize = u32::from_be_bytes(
486 fixup_prop_cell.try_into().or(Err(DeviceAssignmentError::InvalidDtbo))?,
487 )
488 .try_into()
489 .or(Err(DeviceAssignmentError::InvalidDtbo))?;
490 if phandle_offset % CELL_SIZE != 0 {
491 return Err(DeviceAssignmentError::InvalidDtbo);
492 }
493 let phandle_value = target_prop
494 .get(phandle_offset..phandle_offset + CELL_SIZE)
495 .ok_or(DeviceAssignmentError::InvalidDtbo)?;
496 let phandle: Phandle = U32::ref_from(phandle_value)
497 .unwrap()
498 .get()
499 .try_into()
500 .or(Err(DeviceAssignmentError::InvalidDtbo))?;
501
502 phandles.push(phandle);
503 }
504 }
505 if !phandles.is_empty() {
506 phandle_map.insert(path.clone(), phandles);
507 }
508 }
509
510 Ok(phandle_map)
511 }
512
513 fn build_mask(&self, assigned_devices: Vec<DtPathTokens>) -> Result<DeviceTreeMask> {
514 if assigned_devices.is_empty() {
515 return Err(DeviceAssignmentError::Internal);
516 }
517
518 let dependencies = self.collect_phandle_references_from_overlayable_nodes()?;
519 let paths = self.collect_overlayable_nodes_with_phandle()?;
520
521 let mut mask = DeviceTreeMask::new();
522 let mut stack = assigned_devices;
523 while let Some(path) = stack.pop() {
524 if !mask.mask(&path) {
525 continue;
526 }
527 let Some(dst_phandles) = dependencies.get(&path) else {
528 continue;
529 };
530 for dst_phandle in dst_phandles {
531 let dst_path = paths.get(dst_phandle).ok_or(DeviceAssignmentError::Internal)?;
532 stack.push(dst_path.clone());
533 }
534 }
535
536 Ok(mask)
537 }
Jaewan Kimc39974e2023-12-02 01:13:30 +0900538}
539
Jaewan Kimc730ebf2024-02-22 10:34:55 +0900540fn filter_dangling_symbols(fdt: &mut Fdt) -> Result<()> {
541 if let Some(symbols) = fdt.symbols()? {
542 let mut removed = vec![];
543 for prop in symbols.properties()? {
544 let path = CStr::from_bytes_with_nul(prop.value()?)
545 .map_err(|_| DeviceAssignmentError::Internal)?;
546 if fdt.node(path)?.is_none() {
547 let name = prop.name()?;
548 removed.push(CString::from(name));
549 }
550 }
551
552 let mut symbols = fdt.symbols_mut()?.unwrap();
553 for name in removed {
554 symbols.nop_property(&name)?;
555 }
556 }
557 Ok(())
558}
559
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900560impl AsRef<Fdt> for VmDtbo {
561 fn as_ref(&self) -> &Fdt {
562 &self.0
563 }
564}
565
566impl AsMut<Fdt> for VmDtbo {
567 fn as_mut(&mut self) -> &mut Fdt {
568 &mut self.0
569 }
570}
571
Jaewan Kim8f6f4662023-12-12 17:38:47 +0900572// Filter any node that isn't masked by DeviceTreeMask.
573fn filter_with_mask(anchor: FdtNodeMut, mask: &DeviceTreeMask) -> Result<()> {
574 let mut stack = vec![mask];
575 let mut iter = anchor.next_node(0)?;
576 while let Some((node, depth)) = iter {
577 stack.truncate(depth);
578 let parent_mask = stack.last().unwrap();
579 let DeviceTreeChildrenMask::Partial(parent_mask_children) = &parent_mask.children else {
580 // Shouldn't happen. We only step-in if parent has DeviceTreeChildrenMask::Partial.
581 return Err(DeviceAssignmentError::Internal);
582 };
583
584 let name = node.as_node().name()?.to_bytes();
585 let mask = parent_mask_children.iter().find(|child_mask| child_mask.name_bytes == name);
586 if let Some(masked) = mask {
587 if let DeviceTreeChildrenMask::Partial(_) = &masked.children {
588 // This node is partially masked. Stepping-in.
589 stack.push(masked);
590 iter = node.next_node(depth)?;
591 } else {
592 // This node is fully masked. Stepping-out.
593 iter = node.next_node_skip_subnodes(depth)?;
594 }
595 } else {
596 // This node isn't masked.
597 iter = node.delete_and_next_node(depth)?;
598 }
599 }
600
601 Ok(())
602}
603
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900604#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
605struct PvIommu {
606 // ID from pvIOMMU node
607 id: u32,
608}
609
610impl PvIommu {
611 fn parse(node: &FdtNode) -> Result<Self> {
Jaewan Kima9200492023-11-21 20:45:31 +0900612 let iommu_cells = node
613 .getprop_u32(cstr!("#iommu-cells"))?
614 .ok_or(DeviceAssignmentError::InvalidPvIommu)?;
Jaewan Kim19b984f2023-12-04 15:16:50 +0900615 // Ensures #iommu-cells = <1>. It means that `<iommus>` entry contains pair of
Jaewan Kima9200492023-11-21 20:45:31 +0900616 // (pvIOMMU ID, vSID)
617 if iommu_cells != 1 {
618 return Err(DeviceAssignmentError::InvalidPvIommu);
619 }
620 let id = node.getprop_u32(cstr!("id"))?.ok_or(DeviceAssignmentError::InvalidPvIommu)?;
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900621 Ok(Self { id })
622 }
623}
624
Jaewan Kima9200492023-11-21 20:45:31 +0900625#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
626struct Vsid(u32);
627
Jaewan Kim19b984f2023-12-04 15:16:50 +0900628#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
629struct Sid(u64);
630
631impl From<u32> for Sid {
632 fn from(sid: u32) -> Self {
633 Self(sid.into())
634 }
635}
636
637#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
Jaewan Kim52477ae2023-11-21 21:20:52 +0900638struct DeviceReg {
639 addr: u64,
640 size: u64,
641}
642
Pierre-Clément Tosi49e26ce2024-03-12 16:31:50 +0000643impl DeviceReg {
644 pub fn overlaps(&self, range: &Range<u64>) -> bool {
645 self.addr < range.end && range.start < self.addr.checked_add(self.size).unwrap()
646 }
647}
648
Jaewan Kim52477ae2023-11-21 21:20:52 +0900649impl TryFrom<Reg<u64>> for DeviceReg {
650 type Error = DeviceAssignmentError;
651
652 fn try_from(reg: Reg<u64>) -> Result<Self> {
Jaewan Kim19b984f2023-12-04 15:16:50 +0900653 Ok(Self { addr: reg.addr, size: reg.size.ok_or(DeviceAssignmentError::MalformedReg)? })
Jaewan Kim52477ae2023-11-21 21:20:52 +0900654 }
655}
656
657fn parse_node_reg(node: &FdtNode) -> Result<Vec<DeviceReg>> {
658 node.reg()?
Jaewan Kim19b984f2023-12-04 15:16:50 +0900659 .ok_or(DeviceAssignmentError::MalformedReg)?
Jaewan Kim52477ae2023-11-21 21:20:52 +0900660 .map(DeviceReg::try_from)
661 .collect::<Result<Vec<_>>>()
662}
663
664fn to_be_bytes(reg: &[DeviceReg]) -> Vec<u8> {
665 let mut reg_cells = vec![];
666 for x in reg {
667 reg_cells.extend_from_slice(&x.addr.to_be_bytes());
668 reg_cells.extend_from_slice(&x.size.to_be_bytes());
669 }
670 reg_cells
671}
672
Jaewan Kim19b984f2023-12-04 15:16:50 +0900673#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
674struct PhysIommu {
675 token: u64,
676}
677
678impl PhysIommu {
679 fn parse(node: &FdtNode) -> Result<Option<Self>> {
680 let Some(token) = node.getprop_u64(cstr!("android,pvmfw,token"))? else {
681 return Ok(None);
682 };
683 let Some(iommu_cells) = node.getprop_u32(cstr!("#iommu-cells"))? else {
684 return Err(DeviceAssignmentError::InvalidPhysIommu);
685 };
686 // Currently only supports #iommu-cells = <1>.
687 // In that case `<iommus>` entry contains pair of (pIOMMU phandle, Sid token)
688 if iommu_cells != 1 {
689 return Err(DeviceAssignmentError::UnsupportedPhysIommu);
690 }
691 Ok(Some(Self { token }))
692 }
693}
694
695#[derive(Debug)]
696struct PhysicalDeviceInfo {
697 target: Phandle,
698 reg: Vec<DeviceReg>,
699 iommus: Vec<(PhysIommu, Sid)>,
700}
701
702impl PhysicalDeviceInfo {
703 fn parse_iommus(
704 node: &FdtNode,
705 phys_iommus: &BTreeMap<Phandle, PhysIommu>,
706 ) -> Result<Vec<(PhysIommu, Sid)>> {
707 let mut iommus = vec![];
708 let Some(mut cells) = node.getprop_cells(cstr!("iommus"))? else {
709 return Ok(iommus);
710 };
711 while let Some(cell) = cells.next() {
712 // Parse pIOMMU ID
713 let phandle =
714 Phandle::try_from(cell).or(Err(DeviceAssignmentError::MalformedIommus))?;
715 let iommu = phys_iommus.get(&phandle).ok_or(DeviceAssignmentError::MalformedIommus)?;
716
717 // Parse Sid
718 let Some(cell) = cells.next() else {
719 return Err(DeviceAssignmentError::MalformedIommus);
720 };
721
722 iommus.push((*iommu, Sid::from(cell)));
723 }
724 Ok(iommus)
725 }
726
727 fn parse(node: &FdtNode, phys_iommus: &BTreeMap<Phandle, PhysIommu>) -> Result<Option<Self>> {
728 let Some(phandle) = node.getprop_u32(cstr!("android,pvmfw,target"))? else {
729 return Ok(None);
730 };
731 let target = Phandle::try_from(phandle)?;
732 let reg = parse_node_reg(node)?;
733 let iommus = Self::parse_iommus(node, phys_iommus)?;
734 Ok(Some(Self { target, reg, iommus }))
735 }
736}
737
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900738/// Assigned device information parsed from crosvm DT.
739/// Keeps everything in the owned data because underlying FDT will be reused for platform DT.
740#[derive(Debug, Eq, PartialEq)]
741struct AssignedDeviceInfo {
742 // Node path of assigned device (e.g. "/rng")
743 node_path: CString,
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900744 // <reg> property from the crosvm DT
Jaewan Kim52477ae2023-11-21 21:20:52 +0900745 reg: Vec<DeviceReg>,
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900746 // <interrupts> property from the crosvm DT
747 interrupts: Vec<u8>,
Jaewan Kima9200492023-11-21 20:45:31 +0900748 // Parsed <iommus> property from the crosvm DT. Tuple of PvIommu and vSID.
749 iommus: Vec<(PvIommu, Vsid)>,
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900750}
751
752impl AssignedDeviceInfo {
Jaewan Kim19b984f2023-12-04 15:16:50 +0900753 fn validate_reg(
754 device_reg: &[DeviceReg],
755 physical_device_reg: &[DeviceReg],
Jaewan Kim52477ae2023-11-21 21:20:52 +0900756 hypervisor: &dyn DeviceAssigningHypervisor,
Jaewan Kim19b984f2023-12-04 15:16:50 +0900757 ) -> Result<()> {
Pierre-Clément Tosi8b78bc32024-03-13 17:37:07 +0000758 let mut virt_regs = device_reg.iter();
759 let mut phys_regs = physical_device_reg.iter();
Pierre-Clément Tosi49e26ce2024-03-12 16:31:50 +0000760 // TODO(b/308694211): Move this constant to vmbase::layout once vmbase is std-compatible.
761 const PVMFW_RANGE: Range<u64> = 0x7fc0_0000..0x8000_0000;
Jaewan Kim19b984f2023-12-04 15:16:50 +0900762 // PV reg and physical reg should have 1:1 match in order.
Pierre-Clément Tosi8b78bc32024-03-13 17:37:07 +0000763 for (reg, phys_reg) in virt_regs.by_ref().zip(phys_regs.by_ref()) {
Pierre-Clément Tosi49e26ce2024-03-12 16:31:50 +0000764 if reg.overlaps(&PVMFW_RANGE) {
Pierre-Clément Tosif4d8a0d2024-10-25 17:14:15 +0100765 return Err(DeviceAssignmentError::InvalidReg(reg.addr));
Pierre-Clément Tosi49e26ce2024-03-12 16:31:50 +0000766 }
Pierre-Clément Tosif4d8a0d2024-10-25 17:14:15 +0100767 let expected_token = phys_reg.addr;
Pierre-Clément Tosi49e26ce2024-03-12 16:31:50 +0000768 // If this call returns successfully, hyp has mapped the MMIO region at `reg`.
Pierre-Clément Tosif4d8a0d2024-10-25 17:14:15 +0100769 let token = hypervisor.get_phys_mmio_token(reg.addr, reg.size).map_err(|e| {
Pierre-Clément Tosi08d6e3f2024-03-13 18:22:16 +0000770 error!("Hypervisor error while requesting MMIO token: {e}");
Pierre-Clément Tosif4d8a0d2024-10-25 17:14:15 +0100771 DeviceAssignmentError::InvalidReg(reg.addr)
Jaewan Kim52477ae2023-11-21 21:20:52 +0900772 })?;
Pierre-Clément Tosi49e26ce2024-03-12 16:31:50 +0000773 // Only check address because hypervisor guarantees size match when success.
Pierre-Clément Tosif4d8a0d2024-10-25 17:14:15 +0100774 if token != expected_token {
775 return Err(DeviceAssignmentError::InvalidRegToken(token, expected_token));
Jaewan Kim19b984f2023-12-04 15:16:50 +0900776 }
Jaewan Kim52477ae2023-11-21 21:20:52 +0900777 }
Pierre-Clément Tosi8b78bc32024-03-13 17:37:07 +0000778
779 if let Some(DeviceReg { addr, size }) = virt_regs.next() {
Pierre-Clément Tosif4d8a0d2024-10-25 17:14:15 +0100780 return Err(DeviceAssignmentError::ExtraReg(*addr, *size));
Pierre-Clément Tosi8b78bc32024-03-13 17:37:07 +0000781 }
782
783 if let Some(DeviceReg { addr, size }) = phys_regs.next() {
Pierre-Clément Tosif4d8a0d2024-10-25 17:14:15 +0100784 return Err(DeviceAssignmentError::MissingReg(*addr, *size));
Pierre-Clément Tosi8b78bc32024-03-13 17:37:07 +0000785 }
786
Jaewan Kim19b984f2023-12-04 15:16:50 +0900787 Ok(())
Jaewan Kim52477ae2023-11-21 21:20:52 +0900788 }
789
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900790 fn parse_interrupts(node: &FdtNode) -> Result<Vec<u8>> {
791 // Validation: Validate if interrupts cell numbers are multiple of #interrupt-cells.
792 // We can't know how many interrupts would exist.
793 let interrupts_cells = node
Pierre-Clément Tosid701a0b2023-11-07 15:38:59 +0000794 .getprop_cells(cstr!("interrupts"))?
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900795 .ok_or(DeviceAssignmentError::InvalidInterrupts)?
796 .count();
797 if interrupts_cells % CELLS_PER_INTERRUPT != 0 {
798 return Err(DeviceAssignmentError::InvalidInterrupts);
799 }
800
801 // Once validated, keep the raw bytes so patch can be done with setprop()
Pierre-Clément Tosid701a0b2023-11-07 15:38:59 +0000802 Ok(node.getprop(cstr!("interrupts")).unwrap().unwrap().into())
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900803 }
804
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900805 // TODO(b/277993056): Also validate /__local_fixups__ to ensure that <iommus> has phandle.
Jaewan Kima9200492023-11-21 20:45:31 +0900806 fn parse_iommus(
807 node: &FdtNode,
808 pviommus: &BTreeMap<Phandle, PvIommu>,
809 ) -> Result<Vec<(PvIommu, Vsid)>> {
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900810 let mut iommus = vec![];
Jaewan Kima9200492023-11-21 20:45:31 +0900811 let Some(mut cells) = node.getprop_cells(cstr!("iommus"))? else {
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900812 return Ok(iommus);
813 };
Jaewan Kima9200492023-11-21 20:45:31 +0900814 while let Some(cell) = cells.next() {
815 // Parse pvIOMMU ID
Jaewan Kim19b984f2023-12-04 15:16:50 +0900816 let phandle =
817 Phandle::try_from(cell).or(Err(DeviceAssignmentError::MalformedIommus))?;
818 let pviommu = pviommus.get(&phandle).ok_or(DeviceAssignmentError::MalformedIommus)?;
Jaewan Kima9200492023-11-21 20:45:31 +0900819
820 // Parse vSID
821 let Some(cell) = cells.next() else {
Jaewan Kim19b984f2023-12-04 15:16:50 +0900822 return Err(DeviceAssignmentError::MalformedIommus);
Jaewan Kima9200492023-11-21 20:45:31 +0900823 };
824 let vsid = Vsid(cell);
825
826 iommus.push((*pviommu, vsid));
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900827 }
828 Ok(iommus)
829 }
830
Jaewan Kim19b984f2023-12-04 15:16:50 +0900831 fn validate_iommus(
832 iommus: &[(PvIommu, Vsid)],
833 physical_device_iommu: &[(PhysIommu, Sid)],
834 hypervisor: &dyn DeviceAssigningHypervisor,
835 ) -> Result<()> {
836 if iommus.len() != physical_device_iommu.len() {
837 return Err(DeviceAssignmentError::InvalidIommus);
838 }
839 // pvIOMMU can be reordered, and hypervisor may not guarantee 1:1 mapping.
840 // So we need to mark what's matched or not.
841 let mut physical_device_iommu = physical_device_iommu.to_vec();
842 for (pviommu, vsid) in iommus {
Pierre-Clément Tosi08d6e3f2024-03-13 18:22:16 +0000843 let (id, sid) =
844 hypervisor.get_phys_iommu_token(pviommu.id.into(), vsid.0.into()).map_err(|e| {
845 error!("Hypervisor error while requesting IOMMU token ({pviommu:?}, {vsid:?}): {e}");
846 DeviceAssignmentError::InvalidIommus
847 })?;
Jaewan Kim19b984f2023-12-04 15:16:50 +0900848
849 let pos = physical_device_iommu
850 .iter()
851 .position(|(phys_iommu, phys_sid)| (phys_iommu.token, phys_sid.0) == (id, sid));
852 match pos {
853 Some(pos) => physical_device_iommu.remove(pos),
854 None => {
855 error!("Failed to validate device <iommus>. No matching phys iommu or duplicated mapping for pviommu={pviommu:?}, vsid={vsid:?}");
856 return Err(DeviceAssignmentError::InvalidIommus);
857 }
858 };
859 }
860 Ok(())
861 }
862
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900863 fn parse(
864 fdt: &Fdt,
865 vm_dtbo: &VmDtbo,
Jaewan Kimf8abbb52023-12-12 22:11:39 +0900866 dtbo_node_path: &DtPathTokens,
Jaewan Kim19b984f2023-12-04 15:16:50 +0900867 physical_devices: &BTreeMap<Phandle, PhysicalDeviceInfo>,
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900868 pviommus: &BTreeMap<Phandle, PvIommu>,
Jaewan Kim52477ae2023-11-21 21:20:52 +0900869 hypervisor: &dyn DeviceAssigningHypervisor,
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900870 ) -> Result<Option<Self>> {
Jaewan Kim19b984f2023-12-04 15:16:50 +0900871 let dtbo_node =
Jaewan Kimf8abbb52023-12-12 22:11:39 +0900872 vm_dtbo.node(dtbo_node_path)?.ok_or(DeviceAssignmentError::InvalidSymbols)?;
Jaewan Kim19b984f2023-12-04 15:16:50 +0900873 let node_path = vm_dtbo.locate_overlay_target_path(dtbo_node_path, &dtbo_node)?;
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900874
875 let Some(node) = fdt.node(&node_path)? else { return Ok(None) };
876
Jaewan Kim80ef9fa2024-02-25 16:08:14 +0000877 // Currently can only assign devices backed by physical devices.
Jaewan Kim19b984f2023-12-04 15:16:50 +0900878 let phandle = dtbo_node.get_phandle()?.ok_or(DeviceAssignmentError::InvalidDtbo)?;
Jaewan Kim80ef9fa2024-02-25 16:08:14 +0000879 let Some(physical_device) = physical_devices.get(&phandle) else {
880 // If labeled DT node isn't backed by physical device node, then just return None.
881 // It's not an error because such node can be a dependency of assignable device nodes.
882 return Ok(None);
883 };
Jaewan Kim19b984f2023-12-04 15:16:50 +0900884
885 let reg = parse_node_reg(&node)?;
886 Self::validate_reg(&reg, &physical_device.reg, hypervisor)?;
887
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900888 let interrupts = Self::parse_interrupts(&node)?;
Jaewan Kim19b984f2023-12-04 15:16:50 +0900889
890 let iommus = Self::parse_iommus(&node, pviommus)?;
891 Self::validate_iommus(&iommus, &physical_device.iommus, hypervisor)?;
892
Jaewan Kimf8abbb52023-12-12 22:11:39 +0900893 Ok(Some(Self { node_path, reg, interrupts, iommus }))
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900894 }
895
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900896 fn patch(&self, fdt: &mut Fdt, pviommu_phandles: &BTreeMap<PvIommu, Phandle>) -> Result<()> {
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900897 let mut dst = fdt.node_mut(&self.node_path)?.unwrap();
Jaewan Kim52477ae2023-11-21 21:20:52 +0900898 dst.setprop(cstr!("reg"), &to_be_bytes(&self.reg))?;
Pierre-Clément Tosid701a0b2023-11-07 15:38:59 +0000899 dst.setprop(cstr!("interrupts"), &self.interrupts)?;
Jaewan Kima9200492023-11-21 20:45:31 +0900900 let mut iommus = Vec::with_capacity(8 * self.iommus.len());
901 for (pviommu, vsid) in &self.iommus {
902 let phandle = pviommu_phandles.get(pviommu).unwrap();
903 iommus.extend_from_slice(&u32::from(*phandle).to_be_bytes());
904 iommus.extend_from_slice(&vsid.0.to_be_bytes());
905 }
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900906 dst.setprop(cstr!("iommus"), &iommus)?;
907
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900908 Ok(())
909 }
910}
911
Jaewan Kim8f6f4662023-12-12 17:38:47 +0900912#[derive(Debug, Eq, PartialEq)]
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900913pub struct DeviceAssignmentInfo {
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900914 pviommus: BTreeSet<PvIommu>,
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900915 assigned_devices: Vec<AssignedDeviceInfo>,
Jaewan Kim8f6f4662023-12-12 17:38:47 +0900916 vm_dtbo_mask: DeviceTreeMask,
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900917}
918
919impl DeviceAssignmentInfo {
Chris Wailes9d09f572024-01-16 13:31:02 -0800920 const PVIOMMU_COMPATIBLE: &'static CStr = cstr!("pkvm,pviommu");
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900921
922 /// Parses pvIOMMUs in fdt
923 // Note: This will validate pvIOMMU ids' uniqueness, even when unassigned.
924 fn parse_pviommus(fdt: &Fdt) -> Result<BTreeMap<Phandle, PvIommu>> {
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900925 let mut pviommus = BTreeMap::new();
926 for compatible in fdt.compatible_nodes(Self::PVIOMMU_COMPATIBLE)? {
927 let Some(phandle) = compatible.get_phandle()? else {
928 continue; // Skips unreachable pvIOMMU node
929 };
930 let pviommu = PvIommu::parse(&compatible)?;
931 if pviommus.insert(phandle, pviommu).is_some() {
932 return Err(FdtError::BadPhandle.into());
933 }
934 }
935 Ok(pviommus)
936 }
937
Jaewan Kim19b984f2023-12-04 15:16:50 +0900938 fn validate_pviommu_topology(assigned_devices: &[AssignedDeviceInfo]) -> Result<()> {
939 let mut all_iommus = BTreeSet::new();
940 for assigned_device in assigned_devices {
941 for iommu in &assigned_device.iommus {
942 if !all_iommus.insert(iommu) {
943 error!("Unsupported pvIOMMU duplication found, <iommus> = {iommu:?}");
944 return Err(DeviceAssignmentError::UnsupportedPvIommusDuplication);
945 }
946 }
947 }
948 Ok(())
949 }
950
Pierre-Clément Tosie5cca922024-04-30 17:54:08 +0100951 // TODO(b/308694211): Remove this workaround for visibility once using
952 // vmbase::hyp::DeviceAssigningHypervisor for tests.
953 #[cfg(test)]
954 fn parse(
955 fdt: &Fdt,
956 vm_dtbo: &VmDtbo,
957 hypervisor: &dyn DeviceAssigningHypervisor,
958 ) -> Result<Option<Self>> {
959 Self::internal_parse(fdt, vm_dtbo, hypervisor)
960 }
961
962 #[cfg(not(test))]
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900963 /// Parses fdt and vm_dtbo, and creates new DeviceAssignmentInfo
964 // TODO(b/277993056): Parse __local_fixups__
965 // TODO(b/277993056): Parse __fixups__
Jaewan Kim52477ae2023-11-21 21:20:52 +0900966 pub fn parse(
967 fdt: &Fdt,
968 vm_dtbo: &VmDtbo,
969 hypervisor: &dyn DeviceAssigningHypervisor,
970 ) -> Result<Option<Self>> {
Pierre-Clément Tosie5cca922024-04-30 17:54:08 +0100971 Self::internal_parse(fdt, vm_dtbo, hypervisor)
972 }
973
974 fn internal_parse(
975 fdt: &Fdt,
976 vm_dtbo: &VmDtbo,
977 hypervisor: &dyn DeviceAssigningHypervisor,
978 ) -> Result<Option<Self>> {
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900979 let Some(symbols_node) = vm_dtbo.as_ref().symbols()? else {
980 // /__symbols__ should contain all assignable devices.
981 // If empty, then nothing can be assigned.
982 return Ok(None);
983 };
984
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900985 let pviommus = Self::parse_pviommus(fdt)?;
986 let unique_pviommus: BTreeSet<_> = pviommus.values().cloned().collect();
987 if pviommus.len() != unique_pviommus.len() {
988 return Err(DeviceAssignmentError::DuplicatedPvIommuIds);
989 }
990
Jaewan Kim19b984f2023-12-04 15:16:50 +0900991 let physical_devices = vm_dtbo.parse_physical_devices()?;
992
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900993 let mut assigned_devices = vec![];
Jaewan Kim8f6f4662023-12-12 17:38:47 +0900994 let mut assigned_device_paths = vec![];
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900995 for symbol_prop in symbols_node.properties()? {
996 let symbol_prop_value = symbol_prop.value()?;
997 let dtbo_node_path = CStr::from_bytes_with_nul(symbol_prop_value)
998 .or(Err(DeviceAssignmentError::InvalidSymbols))?;
Jaewan Kimf8abbb52023-12-12 22:11:39 +0900999 let dtbo_node_path = DtPathTokens::new(dtbo_node_path)?;
1000 if !dtbo_node_path.is_overlayable_node() {
Jaewan Kimc39974e2023-12-02 01:13:30 +09001001 continue;
1002 }
Jaewan Kim19b984f2023-12-04 15:16:50 +09001003 let assigned_device = AssignedDeviceInfo::parse(
1004 fdt,
1005 vm_dtbo,
Jaewan Kimf8abbb52023-12-12 22:11:39 +09001006 &dtbo_node_path,
Jaewan Kim19b984f2023-12-04 15:16:50 +09001007 &physical_devices,
1008 &pviommus,
1009 hypervisor,
1010 )?;
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001011 if let Some(assigned_device) = assigned_device {
1012 assigned_devices.push(assigned_device);
Jaewan Kim8f6f4662023-12-12 17:38:47 +09001013 assigned_device_paths.push(dtbo_node_path);
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001014 }
1015 }
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001016 if assigned_devices.is_empty() {
1017 return Ok(None);
1018 }
Jaewan Kimc39974e2023-12-02 01:13:30 +09001019
Jaewan Kim19b984f2023-12-04 15:16:50 +09001020 Self::validate_pviommu_topology(&assigned_devices)?;
1021
Jaewan Kim8f6f4662023-12-12 17:38:47 +09001022 let mut vm_dtbo_mask = vm_dtbo.build_mask(assigned_device_paths)?;
1023 vm_dtbo_mask.mask_all(&DtPathTokens::new(cstr!("/__local_fixups__"))?);
1024 vm_dtbo_mask.mask_all(&DtPathTokens::new(cstr!("/__symbols__"))?);
Jaewan Kimc39974e2023-12-02 01:13:30 +09001025
1026 // Note: Any node without __overlay__ will be ignored by fdt_apply_overlay,
1027 // so doesn't need to be filtered.
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001028
Jaewan Kim8f6f4662023-12-12 17:38:47 +09001029 Ok(Some(Self { pviommus: unique_pviommus, assigned_devices, vm_dtbo_mask }))
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001030 }
1031
1032 /// Filters VM DTBO to only contain necessary information for booting pVM
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001033 pub fn filter(&self, vm_dtbo: &mut VmDtbo) -> Result<()> {
1034 let vm_dtbo = vm_dtbo.as_mut();
1035
Jaewan Kim8f6f4662023-12-12 17:38:47 +09001036 // Filter unused references in /__local_fixups__
1037 if let Some(local_fixups) = vm_dtbo.node_mut(cstr!("/__local_fixups__"))? {
1038 filter_with_mask(local_fixups, &self.vm_dtbo_mask)?;
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001039 }
1040
Jaewan Kim8f6f4662023-12-12 17:38:47 +09001041 // Filter unused nodes in rest of tree
1042 let root = vm_dtbo.root_mut();
1043 filter_with_mask(root, &self.vm_dtbo_mask)?;
1044
Jaewan Kim371f6c82024-02-24 01:33:37 +09001045 filter_dangling_symbols(vm_dtbo)
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001046 }
1047
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001048 fn patch_pviommus(&self, fdt: &mut Fdt) -> Result<BTreeMap<PvIommu, Phandle>> {
Pierre-Clément Tosi244efea2024-02-16 14:48:14 +00001049 let mut compatible = fdt.root_mut().next_compatible(Self::PVIOMMU_COMPATIBLE)?;
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001050 let mut pviommu_phandles = BTreeMap::new();
1051
1052 for pviommu in &self.pviommus {
1053 let mut node = compatible.ok_or(DeviceAssignmentError::TooManyPvIommu)?;
1054 let phandle = node.as_node().get_phandle()?.ok_or(DeviceAssignmentError::Internal)?;
1055 node.setprop_inplace(cstr!("id"), &pviommu.id.to_be_bytes())?;
1056 if pviommu_phandles.insert(*pviommu, phandle).is_some() {
1057 return Err(DeviceAssignmentError::Internal);
1058 }
1059 compatible = node.next_compatible(Self::PVIOMMU_COMPATIBLE)?;
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001060 }
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001061
1062 // Filters pre-populated but unassigned pvIOMMUs.
1063 while let Some(filtered_pviommu) = compatible {
1064 compatible = filtered_pviommu.delete_and_next_compatible(Self::PVIOMMU_COMPATIBLE)?;
1065 }
1066
1067 Ok(pviommu_phandles)
1068 }
1069
1070 pub fn patch(&self, fdt: &mut Fdt) -> Result<()> {
1071 let pviommu_phandles = self.patch_pviommus(fdt)?;
1072
1073 // Patches assigned devices
1074 for device in &self.assigned_devices {
1075 device.patch(fdt, &pviommu_phandles)?;
1076 }
1077
Jaewan Kimc730ebf2024-02-22 10:34:55 +09001078 // Removes any dangling references in __symbols__ (e.g. removed pvIOMMUs)
1079 filter_dangling_symbols(fdt)
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001080 }
1081}
1082
Jaewan Kim50246682024-03-11 23:18:54 +09001083/// Cleans device trees not to contain any pre-populated nodes/props for device assignment.
1084pub fn clean(fdt: &mut Fdt) -> Result<()> {
1085 let mut compatible = fdt.root_mut().next_compatible(cstr!("pkvm,pviommu"))?;
1086 // Filters pre-populated
1087 while let Some(filtered_pviommu) = compatible {
1088 compatible = filtered_pviommu.delete_and_next_compatible(cstr!("pkvm,pviommu"))?;
1089 }
1090
1091 // Removes any dangling references in __symbols__ (e.g. removed pvIOMMUs)
1092 filter_dangling_symbols(fdt)
1093}
1094
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001095#[cfg(test)]
Pierre-Clément Tosie5cca922024-04-30 17:54:08 +01001096#[derive(Clone, Copy, Debug)]
1097enum MockHypervisorError {
1098 FailedGetPhysMmioToken,
1099 FailedGetPhysIommuToken,
1100}
1101
1102#[cfg(test)]
1103type MockHypervisorResult<T> = core::result::Result<T, MockHypervisorError>;
1104
1105#[cfg(test)]
1106impl fmt::Display for MockHypervisorError {
1107 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1108 match self {
1109 MockHypervisorError::FailedGetPhysMmioToken => {
1110 write!(f, "Failed to get physical MMIO token")
1111 }
1112 MockHypervisorError::FailedGetPhysIommuToken => {
1113 write!(f, "Failed to get physical IOMMU token")
1114 }
1115 }
1116 }
1117}
1118
1119#[cfg(test)]
1120trait DeviceAssigningHypervisor {
1121 /// Returns MMIO token.
1122 fn get_phys_mmio_token(&self, base_ipa: u64, size: u64) -> MockHypervisorResult<u64>;
1123
1124 /// Returns DMA token as a tuple of (phys_iommu_id, phys_sid).
1125 fn get_phys_iommu_token(&self, pviommu_id: u64, vsid: u64) -> MockHypervisorResult<(u64, u64)>;
1126}
1127
1128#[cfg(test)]
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001129mod tests {
1130 use super::*;
Jaewan Kim52477ae2023-11-21 21:20:52 +09001131 use alloc::collections::{BTreeMap, BTreeSet};
Jaewan Kim8f6f4662023-12-12 17:38:47 +09001132 use dts::Dts;
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001133 use std::fs;
Jaewan Kim8f6f4662023-12-12 17:38:47 +09001134 use std::path::Path;
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001135
1136 const VM_DTBO_FILE_PATH: &str = "test_pvmfw_devices_vm_dtbo.dtbo";
1137 const VM_DTBO_WITHOUT_SYMBOLS_FILE_PATH: &str =
1138 "test_pvmfw_devices_vm_dtbo_without_symbols.dtbo";
Jaewan Kim19b984f2023-12-04 15:16:50 +09001139 const VM_DTBO_WITH_DUPLICATED_IOMMUS_FILE_PATH: &str =
1140 "test_pvmfw_devices_vm_dtbo_with_duplicated_iommus.dtbo";
Jaewan Kim8f6f4662023-12-12 17:38:47 +09001141 const VM_DTBO_WITH_DEPENDENCIES_FILE_PATH: &str =
1142 "test_pvmfw_devices_vm_dtbo_with_dependencies.dtbo";
Jaewan Kima67e36a2023-11-29 16:50:23 +09001143 const FDT_WITHOUT_IOMMUS_FILE_PATH: &str = "test_pvmfw_devices_without_iommus.dtb";
Jaewan Kim52477ae2023-11-21 21:20:52 +09001144 const FDT_WITHOUT_DEVICE_FILE_PATH: &str = "test_pvmfw_devices_without_device.dtb";
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001145 const FDT_FILE_PATH: &str = "test_pvmfw_devices_with_rng.dtb";
Pierre-Clément Tosi49e26ce2024-03-12 16:31:50 +00001146 const FDT_WITH_DEVICE_OVERLAPPING_PVMFW: &str = "test_pvmfw_devices_overlapping_pvmfw.dtb";
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001147 const FDT_WITH_MULTIPLE_DEVICES_IOMMUS_FILE_PATH: &str =
1148 "test_pvmfw_devices_with_multiple_devices_iommus.dtb";
1149 const FDT_WITH_IOMMU_SHARING: &str = "test_pvmfw_devices_with_iommu_sharing.dtb";
1150 const FDT_WITH_IOMMU_ID_CONFLICT: &str = "test_pvmfw_devices_with_iommu_id_conflict.dtb";
Jaewan Kim19b984f2023-12-04 15:16:50 +09001151 const FDT_WITH_DUPLICATED_PVIOMMUS_FILE_PATH: &str =
1152 "test_pvmfw_devices_with_duplicated_pviommus.dtb";
1153 const FDT_WITH_MULTIPLE_REG_IOMMU_FILE_PATH: &str =
1154 "test_pvmfw_devices_with_multiple_reg_iommus.dtb";
Jaewan Kim8f6f4662023-12-12 17:38:47 +09001155 const FDT_WITH_DEPENDENCY_FILE_PATH: &str = "test_pvmfw_devices_with_dependency.dtb";
1156 const FDT_WITH_MULTIPLE_DEPENDENCIES_FILE_PATH: &str =
1157 "test_pvmfw_devices_with_multiple_dependencies.dtb";
1158 const FDT_WITH_DEPENDENCY_LOOP_FILE_PATH: &str = "test_pvmfw_devices_with_dependency_loop.dtb";
1159
1160 const EXPECTED_FDT_WITH_DEPENDENCY_FILE_PATH: &str = "expected_dt_with_dependency.dtb";
1161 const EXPECTED_FDT_WITH_MULTIPLE_DEPENDENCIES_FILE_PATH: &str =
1162 "expected_dt_with_multiple_dependencies.dtb";
1163 const EXPECTED_FDT_WITH_DEPENDENCY_LOOP_FILE_PATH: &str =
1164 "expected_dt_with_dependency_loop.dtb";
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001165
Jaewan Kim52477ae2023-11-21 21:20:52 +09001166 #[derive(Debug, Default)]
1167 struct MockHypervisor {
1168 mmio_tokens: BTreeMap<(u64, u64), u64>,
1169 iommu_tokens: BTreeMap<(u64, u64), (u64, u64)>,
1170 }
1171
1172 impl DeviceAssigningHypervisor for MockHypervisor {
Pierre-Clément Tosie5cca922024-04-30 17:54:08 +01001173 fn get_phys_mmio_token(&self, base_ipa: u64, size: u64) -> MockHypervisorResult<u64> {
1174 let token = self.mmio_tokens.get(&(base_ipa, size));
1175
1176 Ok(*token.ok_or(MockHypervisorError::FailedGetPhysMmioToken)?)
Jaewan Kim52477ae2023-11-21 21:20:52 +09001177 }
1178
Pierre-Clément Tosie5cca922024-04-30 17:54:08 +01001179 fn get_phys_iommu_token(
1180 &self,
1181 pviommu_id: u64,
1182 vsid: u64,
1183 ) -> MockHypervisorResult<(u64, u64)> {
1184 let token = self.iommu_tokens.get(&(pviommu_id, vsid));
1185
1186 Ok(*token.ok_or(MockHypervisorError::FailedGetPhysIommuToken)?)
Jaewan Kim52477ae2023-11-21 21:20:52 +09001187 }
1188 }
1189
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001190 #[derive(Debug, Eq, PartialEq)]
1191 struct AssignedDeviceNode {
1192 path: CString,
1193 reg: Vec<u8>,
1194 interrupts: Vec<u8>,
Jaewan Kima67e36a2023-11-29 16:50:23 +09001195 iommus: Vec<u32>, // pvIOMMU id and vSID
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001196 }
1197
1198 impl AssignedDeviceNode {
1199 fn parse(fdt: &Fdt, path: &CStr) -> Result<Self> {
1200 let Some(node) = fdt.node(path)? else {
1201 return Err(FdtError::NotFound.into());
1202 };
1203
Jaewan Kim19b984f2023-12-04 15:16:50 +09001204 let reg = node.getprop(cstr!("reg"))?.ok_or(DeviceAssignmentError::MalformedReg)?;
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001205 let interrupts = node
1206 .getprop(cstr!("interrupts"))?
1207 .ok_or(DeviceAssignmentError::InvalidInterrupts)?;
1208 let mut iommus = vec![];
Jaewan Kima9200492023-11-21 20:45:31 +09001209 if let Some(mut cells) = node.getprop_cells(cstr!("iommus"))? {
1210 while let Some(pviommu_id) = cells.next() {
1211 // pvIOMMU id
1212 let phandle = Phandle::try_from(pviommu_id)?;
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001213 let pviommu = fdt
1214 .node_with_phandle(phandle)?
Jaewan Kim19b984f2023-12-04 15:16:50 +09001215 .ok_or(DeviceAssignmentError::MalformedIommus)?;
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001216 let compatible = pviommu.getprop_str(cstr!("compatible"));
1217 if compatible != Ok(Some(cstr!("pkvm,pviommu"))) {
Jaewan Kim19b984f2023-12-04 15:16:50 +09001218 return Err(DeviceAssignmentError::MalformedIommus);
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001219 }
1220 let id = pviommu
1221 .getprop_u32(cstr!("id"))?
Jaewan Kim19b984f2023-12-04 15:16:50 +09001222 .ok_or(DeviceAssignmentError::MalformedIommus)?;
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001223 iommus.push(id);
Jaewan Kima9200492023-11-21 20:45:31 +09001224
1225 // vSID
1226 let Some(vsid) = cells.next() else {
Jaewan Kim19b984f2023-12-04 15:16:50 +09001227 return Err(DeviceAssignmentError::MalformedIommus);
Jaewan Kima9200492023-11-21 20:45:31 +09001228 };
1229 iommus.push(vsid);
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001230 }
1231 }
1232 Ok(Self { path: path.into(), reg: reg.into(), interrupts: interrupts.into(), iommus })
1233 }
1234 }
1235
1236 fn collect_pviommus(fdt: &Fdt) -> Result<Vec<u32>> {
1237 let mut pviommus = BTreeSet::new();
1238 for pviommu in fdt.compatible_nodes(cstr!("pkvm,pviommu"))? {
1239 if let Ok(Some(id)) = pviommu.getprop_u32(cstr!("id")) {
1240 pviommus.insert(id);
1241 }
1242 }
1243 Ok(pviommus.iter().cloned().collect())
1244 }
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001245
1246 fn into_fdt_prop(native_bytes: Vec<u32>) -> Vec<u8> {
1247 let mut v = Vec::with_capacity(native_bytes.len() * 4);
1248 for byte in native_bytes {
1249 v.extend_from_slice(&byte.to_be_bytes());
1250 }
1251 v
1252 }
1253
Jaewan Kim52477ae2023-11-21 21:20:52 +09001254 impl From<[u64; 2]> for DeviceReg {
1255 fn from(fdt_cells: [u64; 2]) -> Self {
1256 DeviceReg { addr: fdt_cells[0], size: fdt_cells[1] }
1257 }
1258 }
1259
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001260 #[test]
1261 fn device_info_new_without_symbols() {
1262 let mut fdt_data = fs::read(FDT_FILE_PATH).unwrap();
1263 let mut vm_dtbo_data = fs::read(VM_DTBO_WITHOUT_SYMBOLS_FILE_PATH).unwrap();
1264 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1265 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1266
Jaewan Kim52477ae2023-11-21 21:20:52 +09001267 let hypervisor: MockHypervisor = Default::default();
1268 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor).unwrap();
1269 assert_eq!(device_info, None);
1270 }
1271
1272 #[test]
1273 fn device_info_new_without_device() {
1274 let mut fdt_data = fs::read(FDT_WITHOUT_DEVICE_FILE_PATH).unwrap();
1275 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1276 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1277 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1278
1279 let hypervisor: MockHypervisor = Default::default();
1280 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor).unwrap();
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001281 assert_eq!(device_info, None);
1282 }
1283
1284 #[test]
Jaewan Kima67e36a2023-11-29 16:50:23 +09001285 fn device_info_assigned_info_without_iommus() {
1286 let mut fdt_data = fs::read(FDT_WITHOUT_IOMMUS_FILE_PATH).unwrap();
1287 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1288 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1289 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1290
Jaewan Kim52477ae2023-11-21 21:20:52 +09001291 let hypervisor = MockHypervisor {
1292 mmio_tokens: [((0x9, 0xFF), 0x300)].into(),
1293 iommu_tokens: BTreeMap::new(),
1294 };
1295 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor).unwrap().unwrap();
Jaewan Kima67e36a2023-11-29 16:50:23 +09001296
1297 let expected = [AssignedDeviceInfo {
Jaewan Kimc39974e2023-12-02 01:13:30 +09001298 node_path: CString::new("/bus0/backlight").unwrap(),
Jaewan Kim52477ae2023-11-21 21:20:52 +09001299 reg: vec![[0x9, 0xFF].into()],
Jaewan Kima67e36a2023-11-29 16:50:23 +09001300 interrupts: into_fdt_prop(vec![0x0, 0xF, 0x4]),
1301 iommus: vec![],
1302 }];
1303
1304 assert_eq!(device_info.assigned_devices, expected);
1305 }
1306
1307 #[test]
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001308 fn device_info_assigned_info() {
1309 let mut fdt_data = fs::read(FDT_FILE_PATH).unwrap();
1310 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1311 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1312 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1313
Jaewan Kim52477ae2023-11-21 21:20:52 +09001314 let hypervisor = MockHypervisor {
1315 mmio_tokens: [((0x9, 0xFF), 0x12F00000)].into(),
1316 iommu_tokens: [((0x4, 0xFF0), (0x12E40000, 0x3))].into(),
1317 };
1318 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor).unwrap().unwrap();
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001319
1320 let expected = [AssignedDeviceInfo {
1321 node_path: CString::new("/rng").unwrap(),
Jaewan Kim52477ae2023-11-21 21:20:52 +09001322 reg: vec![[0x9, 0xFF].into()],
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001323 interrupts: into_fdt_prop(vec![0x0, 0xF, 0x4]),
Jaewan Kima67e36a2023-11-29 16:50:23 +09001324 iommus: vec![(PvIommu { id: 0x4 }, Vsid(0xFF0))],
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001325 }];
1326
1327 assert_eq!(device_info.assigned_devices, expected);
1328 }
1329
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001330 #[test]
1331 fn device_info_filter() {
1332 let mut fdt_data = fs::read(FDT_FILE_PATH).unwrap();
1333 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1334 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1335 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1336
Jaewan Kim52477ae2023-11-21 21:20:52 +09001337 let hypervisor = MockHypervisor {
1338 mmio_tokens: [((0x9, 0xFF), 0x12F00000)].into(),
1339 iommu_tokens: [((0x4, 0xFF0), (0x12E40000, 0x3))].into(),
1340 };
1341 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor).unwrap().unwrap();
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001342 device_info.filter(vm_dtbo).unwrap();
1343
1344 let vm_dtbo = vm_dtbo.as_mut();
1345
Jaewan Kim371f6c82024-02-24 01:33:37 +09001346 let symbols = vm_dtbo.symbols().unwrap().unwrap();
1347
Jaewan Kima232ed02024-02-25 16:08:14 +00001348 let rng = vm_dtbo.node(cstr!("/fragment@0/__overlay__/rng")).unwrap();
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001349 assert_ne!(rng, None);
Jaewan Kim371f6c82024-02-24 01:33:37 +09001350 let rng_symbol = symbols.getprop_str(cstr!("rng")).unwrap();
Jaewan Kima232ed02024-02-25 16:08:14 +00001351 assert_eq!(Some(cstr!("/fragment@0/__overlay__/rng")), rng_symbol);
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001352
Jaewan Kima232ed02024-02-25 16:08:14 +00001353 let light = vm_dtbo.node(cstr!("/fragment@0/__overlay__/light")).unwrap();
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001354 assert_eq!(light, None);
Jaewan Kim371f6c82024-02-24 01:33:37 +09001355 let light_symbol = symbols.getprop_str(cstr!("light")).unwrap();
1356 assert_eq!(None, light_symbol);
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001357
Jaewan Kima232ed02024-02-25 16:08:14 +00001358 let led = vm_dtbo.node(cstr!("/fragment@0/__overlay__/led")).unwrap();
Jaewan Kima67e36a2023-11-29 16:50:23 +09001359 assert_eq!(led, None);
Jaewan Kim371f6c82024-02-24 01:33:37 +09001360 let led_symbol = symbols.getprop_str(cstr!("led")).unwrap();
1361 assert_eq!(None, led_symbol);
Jaewan Kima67e36a2023-11-29 16:50:23 +09001362
Jaewan Kima232ed02024-02-25 16:08:14 +00001363 let backlight = vm_dtbo.node(cstr!("/fragment@0/__overlay__/bus0/backlight")).unwrap();
Jaewan Kima67e36a2023-11-29 16:50:23 +09001364 assert_eq!(backlight, None);
Jaewan Kim371f6c82024-02-24 01:33:37 +09001365 let backlight_symbol = symbols.getprop_str(cstr!("backlight")).unwrap();
1366 assert_eq!(None, backlight_symbol);
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001367 }
1368
1369 #[test]
1370 fn device_info_patch() {
Jaewan Kima67e36a2023-11-29 16:50:23 +09001371 let mut fdt_data = fs::read(FDT_WITHOUT_IOMMUS_FILE_PATH).unwrap();
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001372 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1373 let mut data = vec![0_u8; fdt_data.len() + vm_dtbo_data.len()];
1374 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1375 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1376 let platform_dt = Fdt::create_empty_tree(data.as_mut_slice()).unwrap();
1377
Jaewan Kim52477ae2023-11-21 21:20:52 +09001378 let hypervisor = MockHypervisor {
1379 mmio_tokens: [((0x9, 0xFF), 0x300)].into(),
1380 iommu_tokens: BTreeMap::new(),
1381 };
1382 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor).unwrap().unwrap();
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001383 device_info.filter(vm_dtbo).unwrap();
1384
1385 // SAFETY: Damaged VM DTBO wouldn't be used after this unsafe block.
1386 unsafe {
1387 platform_dt.apply_overlay(vm_dtbo.as_mut()).unwrap();
1388 }
Jaewan Kim0bd637d2023-11-10 13:09:41 +09001389 device_info.patch(platform_dt).unwrap();
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001390
Jaewan Kimc39974e2023-12-02 01:13:30 +09001391 let rng_node = platform_dt.node(cstr!("/bus0/backlight")).unwrap().unwrap();
1392 let phandle = rng_node.getprop_u32(cstr!("phandle")).unwrap();
1393 assert_ne!(None, phandle);
1394
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001395 // Note: Intentionally not using AssignedDeviceNode for matching all props.
Jaewan Kim0bd637d2023-11-10 13:09:41 +09001396 type FdtResult<T> = libfdt::Result<T>;
1397 let expected: Vec<(FdtResult<&CStr>, FdtResult<Vec<u8>>)> = vec![
Jaewan Kima67e36a2023-11-29 16:50:23 +09001398 (Ok(cstr!("android,backlight,ignore-gctrl-reset")), Ok(Vec::new())),
1399 (Ok(cstr!("compatible")), Ok(Vec::from(*b"android,backlight\0"))),
Jaewan Kim0bd637d2023-11-10 13:09:41 +09001400 (Ok(cstr!("interrupts")), Ok(into_fdt_prop(vec![0x0, 0xF, 0x4]))),
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001401 (Ok(cstr!("iommus")), Ok(Vec::new())),
Jaewan Kimc39974e2023-12-02 01:13:30 +09001402 (Ok(cstr!("phandle")), Ok(into_fdt_prop(vec![phandle.unwrap()]))),
Jaewan Kim0bd637d2023-11-10 13:09:41 +09001403 (Ok(cstr!("reg")), Ok(into_fdt_prop(vec![0x0, 0x9, 0x0, 0xFF]))),
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001404 ];
1405
Jaewan Kim0bd637d2023-11-10 13:09:41 +09001406 let mut properties: Vec<_> = rng_node
1407 .properties()
1408 .unwrap()
1409 .map(|prop| (prop.name(), prop.value().map(|x| x.into())))
1410 .collect();
1411 properties.sort_by(|a, b| {
1412 let lhs = a.0.unwrap_or_default();
1413 let rhs = b.0.unwrap_or_default();
1414 lhs.partial_cmp(rhs).unwrap()
1415 });
1416
1417 assert_eq!(properties, expected);
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001418 }
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001419
1420 #[test]
Jaewan Kimc730ebf2024-02-22 10:34:55 +09001421 fn device_info_patch_no_pviommus() {
1422 let mut fdt_data = fs::read(FDT_WITHOUT_IOMMUS_FILE_PATH).unwrap();
1423 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1424 let mut data = vec![0_u8; fdt_data.len() + vm_dtbo_data.len()];
1425 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1426 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1427 let platform_dt = Fdt::create_empty_tree(data.as_mut_slice()).unwrap();
1428
1429 let hypervisor = MockHypervisor {
1430 mmio_tokens: [((0x9, 0xFF), 0x300)].into(),
1431 iommu_tokens: BTreeMap::new(),
1432 };
1433 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor).unwrap().unwrap();
1434 device_info.filter(vm_dtbo).unwrap();
1435
1436 // SAFETY: Damaged VM DTBO wouldn't be used after this unsafe block.
1437 unsafe {
1438 platform_dt.apply_overlay(vm_dtbo.as_mut()).unwrap();
1439 }
1440 device_info.patch(platform_dt).unwrap();
1441
1442 let compatible = platform_dt.root().next_compatible(cstr!("pkvm,pviommu")).unwrap();
1443 assert_eq!(None, compatible);
1444
1445 if let Some(symbols) = platform_dt.symbols().unwrap() {
1446 for prop in symbols.properties().unwrap() {
1447 let path = CStr::from_bytes_with_nul(prop.value().unwrap()).unwrap();
1448 assert_ne!(None, platform_dt.node(path).unwrap());
1449 }
1450 }
1451 }
1452
1453 #[test]
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001454 fn device_info_overlay_iommu() {
Jaewan Kima67e36a2023-11-29 16:50:23 +09001455 let mut fdt_data = fs::read(FDT_FILE_PATH).unwrap();
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001456 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1457 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1458 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1459 let mut platform_dt_data = pvmfw_fdt_template::RAW.to_vec();
1460 platform_dt_data.resize(pvmfw_fdt_template::RAW.len() * 2, 0);
1461 let platform_dt = Fdt::from_mut_slice(&mut platform_dt_data).unwrap();
1462 platform_dt.unpack().unwrap();
1463
Jaewan Kim52477ae2023-11-21 21:20:52 +09001464 let hypervisor = MockHypervisor {
1465 mmio_tokens: [((0x9, 0xFF), 0x12F00000)].into(),
1466 iommu_tokens: [((0x4, 0xFF0), (0x12E40000, 0x3))].into(),
1467 };
1468 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor).unwrap().unwrap();
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001469 device_info.filter(vm_dtbo).unwrap();
1470
1471 // SAFETY: Damaged VM DTBO wouldn't be used after this unsafe block.
1472 unsafe {
1473 platform_dt.apply_overlay(vm_dtbo.as_mut()).unwrap();
1474 }
1475 device_info.patch(platform_dt).unwrap();
1476
1477 let expected = AssignedDeviceNode {
1478 path: CString::new("/rng").unwrap(),
1479 reg: into_fdt_prop(vec![0x0, 0x9, 0x0, 0xFF]),
1480 interrupts: into_fdt_prop(vec![0x0, 0xF, 0x4]),
Jaewan Kima9200492023-11-21 20:45:31 +09001481 iommus: vec![0x4, 0xFF0],
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001482 };
1483
1484 let node = AssignedDeviceNode::parse(platform_dt, &expected.path);
1485 assert_eq!(node, Ok(expected));
1486
1487 let pviommus = collect_pviommus(platform_dt);
1488 assert_eq!(pviommus, Ok(vec![0x4]));
1489 }
1490
1491 #[test]
1492 fn device_info_multiple_devices_iommus() {
1493 let mut fdt_data = fs::read(FDT_WITH_MULTIPLE_DEVICES_IOMMUS_FILE_PATH).unwrap();
1494 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1495 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1496 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1497 let mut platform_dt_data = pvmfw_fdt_template::RAW.to_vec();
1498 platform_dt_data.resize(pvmfw_fdt_template::RAW.len() * 2, 0);
1499 let platform_dt = Fdt::from_mut_slice(&mut platform_dt_data).unwrap();
1500 platform_dt.unpack().unwrap();
1501
Jaewan Kim52477ae2023-11-21 21:20:52 +09001502 let hypervisor = MockHypervisor {
1503 mmio_tokens: [
1504 ((0x9, 0xFF), 0x12F00000),
Jaewan Kim19b984f2023-12-04 15:16:50 +09001505 ((0x10000, 0x1000), 0xF00000),
1506 ((0x20000, 0x1000), 0xF10000),
Jaewan Kim52477ae2023-11-21 21:20:52 +09001507 ]
1508 .into(),
1509 iommu_tokens: [
1510 ((0x4, 0xFF0), (0x12E40000, 3)),
1511 ((0x40, 0xFFA), (0x40000, 0x4)),
1512 ((0x50, 0xFFB), (0x50000, 0x5)),
1513 ]
1514 .into(),
1515 };
1516 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor).unwrap().unwrap();
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001517 device_info.filter(vm_dtbo).unwrap();
1518
1519 // SAFETY: Damaged VM DTBO wouldn't be used after this unsafe block.
1520 unsafe {
1521 platform_dt.apply_overlay(vm_dtbo.as_mut()).unwrap();
1522 }
1523 device_info.patch(platform_dt).unwrap();
1524
1525 let expected_devices = [
1526 AssignedDeviceNode {
1527 path: CString::new("/rng").unwrap(),
1528 reg: into_fdt_prop(vec![0x0, 0x9, 0x0, 0xFF]),
1529 interrupts: into_fdt_prop(vec![0x0, 0xF, 0x4]),
Jaewan Kima67e36a2023-11-29 16:50:23 +09001530 iommus: vec![0x4, 0xFF0],
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001531 },
1532 AssignedDeviceNode {
1533 path: CString::new("/light").unwrap(),
Jaewan Kim19b984f2023-12-04 15:16:50 +09001534 reg: into_fdt_prop(vec![0x0, 0x10000, 0x0, 0x1000, 0x0, 0x20000, 0x0, 0x1000]),
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001535 interrupts: into_fdt_prop(vec![0x0, 0xF, 0x5]),
Jaewan Kima67e36a2023-11-29 16:50:23 +09001536 iommus: vec![0x40, 0xFFA, 0x50, 0xFFB],
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001537 },
1538 ];
1539
1540 for expected in expected_devices {
1541 let node = AssignedDeviceNode::parse(platform_dt, &expected.path);
1542 assert_eq!(node, Ok(expected));
1543 }
1544 let pviommus = collect_pviommus(platform_dt);
Jaewan Kima67e36a2023-11-29 16:50:23 +09001545 assert_eq!(pviommus, Ok(vec![0x4, 0x40, 0x50]));
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001546 }
1547
1548 #[test]
1549 fn device_info_iommu_sharing() {
1550 let mut fdt_data = fs::read(FDT_WITH_IOMMU_SHARING).unwrap();
1551 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1552 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1553 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1554 let mut platform_dt_data = pvmfw_fdt_template::RAW.to_vec();
1555 platform_dt_data.resize(pvmfw_fdt_template::RAW.len() * 2, 0);
1556 let platform_dt = Fdt::from_mut_slice(&mut platform_dt_data).unwrap();
1557 platform_dt.unpack().unwrap();
1558
Jaewan Kim52477ae2023-11-21 21:20:52 +09001559 let hypervisor = MockHypervisor {
Jaewan Kim19b984f2023-12-04 15:16:50 +09001560 mmio_tokens: [((0x9, 0xFF), 0x12F00000), ((0x1000, 0x9), 0x12000000)].into(),
1561 iommu_tokens: [((0x4, 0xFF0), (0x12E40000, 3)), ((0x4, 0xFF1), (0x12E40000, 9))].into(),
Jaewan Kim52477ae2023-11-21 21:20:52 +09001562 };
1563 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor).unwrap().unwrap();
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001564 device_info.filter(vm_dtbo).unwrap();
1565
1566 // SAFETY: Damaged VM DTBO wouldn't be used after this unsafe block.
1567 unsafe {
1568 platform_dt.apply_overlay(vm_dtbo.as_mut()).unwrap();
1569 }
1570 device_info.patch(platform_dt).unwrap();
1571
1572 let expected_devices = [
1573 AssignedDeviceNode {
1574 path: CString::new("/rng").unwrap(),
1575 reg: into_fdt_prop(vec![0x0, 0x9, 0x0, 0xFF]),
1576 interrupts: into_fdt_prop(vec![0x0, 0xF, 0x4]),
Jaewan Kima67e36a2023-11-29 16:50:23 +09001577 iommus: vec![0x4, 0xFF0],
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001578 },
1579 AssignedDeviceNode {
Jaewan Kima67e36a2023-11-29 16:50:23 +09001580 path: CString::new("/led").unwrap(),
Jaewan Kim19b984f2023-12-04 15:16:50 +09001581 reg: into_fdt_prop(vec![0x0, 0x1000, 0x0, 0x9]),
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001582 interrupts: into_fdt_prop(vec![0x0, 0xF, 0x5]),
Jaewan Kim19b984f2023-12-04 15:16:50 +09001583 iommus: vec![0x4, 0xFF1],
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001584 },
1585 ];
1586
1587 for expected in expected_devices {
1588 let node = AssignedDeviceNode::parse(platform_dt, &expected.path);
1589 assert_eq!(node, Ok(expected));
1590 }
1591
1592 let pviommus = collect_pviommus(platform_dt);
Jaewan Kima67e36a2023-11-29 16:50:23 +09001593 assert_eq!(pviommus, Ok(vec![0x4]));
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001594 }
1595
1596 #[test]
1597 fn device_info_iommu_id_conflict() {
1598 let mut fdt_data = fs::read(FDT_WITH_IOMMU_ID_CONFLICT).unwrap();
1599 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1600 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1601 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1602
Jaewan Kim52477ae2023-11-21 21:20:52 +09001603 let hypervisor = MockHypervisor {
Jaewan Kim19b984f2023-12-04 15:16:50 +09001604 mmio_tokens: [((0x9, 0xFF), 0x300)].into(),
Jaewan Kim52477ae2023-11-21 21:20:52 +09001605 iommu_tokens: [((0x4, 0xFF0), (0x12E40000, 0x3))].into(),
1606 };
1607 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor);
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001608
1609 assert_eq!(device_info, Err(DeviceAssignmentError::DuplicatedPvIommuIds));
1610 }
Jaewan Kim52477ae2023-11-21 21:20:52 +09001611
1612 #[test]
1613 fn device_info_invalid_reg() {
1614 let mut fdt_data = fs::read(FDT_FILE_PATH).unwrap();
1615 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1616 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1617 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1618
1619 let hypervisor = MockHypervisor {
1620 mmio_tokens: BTreeMap::new(),
1621 iommu_tokens: [((0x4, 0xFF0), (0x12E40000, 0x3))].into(),
1622 };
1623 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor);
1624
Pierre-Clément Tosif4d8a0d2024-10-25 17:14:15 +01001625 assert_eq!(device_info, Err(DeviceAssignmentError::InvalidReg(0x9)));
Jaewan Kim52477ae2023-11-21 21:20:52 +09001626 }
1627
1628 #[test]
Jaewan Kim19b984f2023-12-04 15:16:50 +09001629 fn device_info_invalid_reg_out_of_order() {
1630 let mut fdt_data = fs::read(FDT_WITH_MULTIPLE_REG_IOMMU_FILE_PATH).unwrap();
1631 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1632 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1633 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1634
1635 let hypervisor = MockHypervisor {
1636 mmio_tokens: [((0xF000, 0x1000), 0xF10000), ((0xF100, 0x1000), 0xF00000)].into(),
1637 iommu_tokens: [((0xFF0, 0xF0), (0x40000, 0x4)), ((0xFF1, 0xF1), (0x50000, 0x5))].into(),
1638 };
1639 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor);
1640
Pierre-Clément Tosif4d8a0d2024-10-25 17:14:15 +01001641 assert_eq!(device_info, Err(DeviceAssignmentError::InvalidRegToken(0xF10000, 0xF00000)));
Jaewan Kim19b984f2023-12-04 15:16:50 +09001642 }
1643
1644 #[test]
Jaewan Kim52477ae2023-11-21 21:20:52 +09001645 fn device_info_invalid_iommus() {
1646 let mut fdt_data = fs::read(FDT_FILE_PATH).unwrap();
1647 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1648 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1649 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1650
1651 let hypervisor = MockHypervisor {
1652 mmio_tokens: [((0x9, 0xFF), 0x12F00000)].into(),
1653 iommu_tokens: BTreeMap::new(),
1654 };
1655 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor);
1656
1657 assert_eq!(device_info, Err(DeviceAssignmentError::InvalidIommus));
1658 }
Jaewan Kim19b984f2023-12-04 15:16:50 +09001659
1660 #[test]
1661 fn device_info_duplicated_pv_iommus() {
1662 let mut fdt_data = fs::read(FDT_WITH_DUPLICATED_PVIOMMUS_FILE_PATH).unwrap();
1663 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1664 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1665 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1666
1667 let hypervisor = MockHypervisor {
1668 mmio_tokens: [((0x10000, 0x1000), 0xF00000), ((0x20000, 0xFF), 0xF10000)].into(),
1669 iommu_tokens: [((0xFF, 0xF), (0x40000, 0x4))].into(),
1670 };
1671 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor);
1672
1673 assert_eq!(device_info, Err(DeviceAssignmentError::DuplicatedPvIommuIds));
1674 }
1675
1676 #[test]
1677 fn device_info_duplicated_iommus() {
1678 let mut fdt_data = fs::read(FDT_FILE_PATH).unwrap();
1679 let mut vm_dtbo_data = fs::read(VM_DTBO_WITH_DUPLICATED_IOMMUS_FILE_PATH).unwrap();
1680 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1681 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1682
1683 let hypervisor = MockHypervisor {
1684 mmio_tokens: [((0x10000, 0x1000), 0xF00000), ((0x20000, 0xFF), 0xF10000)].into(),
1685 iommu_tokens: [((0xFF, 0xF), (0x40000, 0x4))].into(),
1686 };
1687 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor);
1688
1689 assert_eq!(device_info, Err(DeviceAssignmentError::UnsupportedIommusDuplication));
1690 }
1691
1692 #[test]
1693 fn device_info_duplicated_iommu_mapping() {
1694 let mut fdt_data = fs::read(FDT_WITH_MULTIPLE_REG_IOMMU_FILE_PATH).unwrap();
1695 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1696 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1697 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1698
1699 let hypervisor = MockHypervisor {
1700 mmio_tokens: [((0xF000, 0x1000), 0xF00000), ((0xF100, 0x1000), 0xF10000)].into(),
1701 iommu_tokens: [((0xFF0, 0xF0), (0x40000, 0x4)), ((0xFF1, 0xF1), (0x40000, 0x4))].into(),
1702 };
1703 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor);
1704
1705 assert_eq!(device_info, Err(DeviceAssignmentError::InvalidIommus));
1706 }
Jaewan Kim50246682024-03-11 23:18:54 +09001707
1708 #[test]
Pierre-Clément Tosi49e26ce2024-03-12 16:31:50 +00001709 fn device_info_overlaps_pvmfw() {
1710 let mut fdt_data = fs::read(FDT_WITH_DEVICE_OVERLAPPING_PVMFW).unwrap();
1711 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1712 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1713 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1714
1715 let hypervisor = MockHypervisor {
1716 mmio_tokens: [((0x7fee0000, 0x1000), 0xF00000)].into(),
1717 iommu_tokens: [((0xFF, 0xF), (0x40000, 0x4))].into(),
1718 };
1719 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor);
1720
Pierre-Clément Tosif4d8a0d2024-10-25 17:14:15 +01001721 assert_eq!(device_info, Err(DeviceAssignmentError::InvalidReg(0x7fee0000)));
Pierre-Clément Tosi49e26ce2024-03-12 16:31:50 +00001722 }
1723
1724 #[test]
Jaewan Kim50246682024-03-11 23:18:54 +09001725 fn device_assignment_clean() {
1726 let mut platform_dt_data = pvmfw_fdt_template::RAW.to_vec();
1727 let platform_dt = Fdt::from_mut_slice(&mut platform_dt_data).unwrap();
1728
1729 let compatible = platform_dt.root().next_compatible(cstr!("pkvm,pviommu"));
1730 assert_ne!(None, compatible.unwrap());
1731
1732 clean(platform_dt).unwrap();
1733
1734 let compatible = platform_dt.root().next_compatible(cstr!("pkvm,pviommu"));
1735 assert_eq!(Ok(None), compatible);
1736 }
Jaewan Kim8f6f4662023-12-12 17:38:47 +09001737
1738 #[test]
1739 fn device_info_dependency() {
1740 let mut fdt_data = fs::read(FDT_WITH_DEPENDENCY_FILE_PATH).unwrap();
1741 let mut vm_dtbo_data = fs::read(VM_DTBO_WITH_DEPENDENCIES_FILE_PATH).unwrap();
1742 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1743 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1744 let mut platform_dt_data = pvmfw_fdt_template::RAW.to_vec();
1745 platform_dt_data.resize(pvmfw_fdt_template::RAW.len() * 2, 0);
1746 let platform_dt = Fdt::from_mut_slice(&mut platform_dt_data).unwrap();
1747 platform_dt.unpack().unwrap();
1748
1749 let hypervisor = MockHypervisor {
1750 mmio_tokens: [((0xFF000, 0x1), 0xF000)].into(),
1751 iommu_tokens: Default::default(),
1752 };
1753
1754 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor).unwrap().unwrap();
1755 device_info.filter(vm_dtbo).unwrap();
1756
1757 // SAFETY: Damaged VM DTBO wouldn't be used after this unsafe block.
1758 unsafe {
1759 platform_dt.apply_overlay(vm_dtbo.as_mut()).unwrap();
1760 }
1761 device_info.patch(platform_dt).unwrap();
1762
1763 let expected = Dts::from_dtb(Path::new(EXPECTED_FDT_WITH_DEPENDENCY_FILE_PATH)).unwrap();
1764 let platform_dt = Dts::from_fdt(platform_dt).unwrap();
1765
1766 assert_eq!(expected, platform_dt);
1767 }
1768
1769 #[test]
1770 fn device_info_multiple_dependencies() {
1771 let mut fdt_data = fs::read(FDT_WITH_MULTIPLE_DEPENDENCIES_FILE_PATH).unwrap();
1772 let mut vm_dtbo_data = fs::read(VM_DTBO_WITH_DEPENDENCIES_FILE_PATH).unwrap();
1773 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1774 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1775 let mut platform_dt_data = pvmfw_fdt_template::RAW.to_vec();
1776 platform_dt_data.resize(pvmfw_fdt_template::RAW.len() * 2, 0);
1777 let platform_dt = Fdt::from_mut_slice(&mut platform_dt_data).unwrap();
1778 platform_dt.unpack().unwrap();
1779
1780 let hypervisor = MockHypervisor {
1781 mmio_tokens: [((0xFF000, 0x1), 0xF000), ((0xFF100, 0x1), 0xF100)].into(),
1782 iommu_tokens: Default::default(),
1783 };
1784 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor).unwrap().unwrap();
1785 device_info.filter(vm_dtbo).unwrap();
1786
1787 // SAFETY: Damaged VM DTBO wouldn't be used after this unsafe block.
1788 unsafe {
1789 platform_dt.apply_overlay(vm_dtbo.as_mut()).unwrap();
1790 }
1791 device_info.patch(platform_dt).unwrap();
1792
1793 let expected =
1794 Dts::from_dtb(Path::new(EXPECTED_FDT_WITH_MULTIPLE_DEPENDENCIES_FILE_PATH)).unwrap();
1795 let platform_dt = Dts::from_fdt(platform_dt).unwrap();
1796
1797 assert_eq!(expected, platform_dt);
1798 }
1799
1800 #[test]
1801 fn device_info_dependency_loop() {
1802 let mut fdt_data = fs::read(FDT_WITH_DEPENDENCY_LOOP_FILE_PATH).unwrap();
1803 let mut vm_dtbo_data = fs::read(VM_DTBO_WITH_DEPENDENCIES_FILE_PATH).unwrap();
1804 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1805 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1806 let mut platform_dt_data = pvmfw_fdt_template::RAW.to_vec();
1807 platform_dt_data.resize(pvmfw_fdt_template::RAW.len() * 2, 0);
1808 let platform_dt = Fdt::from_mut_slice(&mut platform_dt_data).unwrap();
1809 platform_dt.unpack().unwrap();
1810
1811 let hypervisor = MockHypervisor {
1812 mmio_tokens: [((0xFF200, 0x1), 0xF200)].into(),
1813 iommu_tokens: Default::default(),
1814 };
1815 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor).unwrap().unwrap();
1816 device_info.filter(vm_dtbo).unwrap();
1817
1818 // SAFETY: Damaged VM DTBO wouldn't be used after this unsafe block.
1819 unsafe {
1820 platform_dt.apply_overlay(vm_dtbo.as_mut()).unwrap();
1821 }
1822 device_info.patch(platform_dt).unwrap();
1823
1824 let expected =
1825 Dts::from_dtb(Path::new(EXPECTED_FDT_WITH_DEPENDENCY_LOOP_FILE_PATH)).unwrap();
1826 let platform_dt = Dts::from_fdt(platform_dt).unwrap();
1827
1828 assert_eq!(expected, platform_dt);
1829 }
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001830}