blob: da9462b8e2bbe45f32a8cb64ebf3b94fdc08993f [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),
Pierre-Clément Tosi7fb437f2024-10-29 09:54:42 +000072 /// Invalid virtual <reg> size of assigned device.
73 InvalidRegSize(u64, u64),
Jaewan Kimc6e023b2023-10-12 15:11:05 +090074 /// Invalid <interrupts>
75 InvalidInterrupts,
Jaewan Kim19b984f2023-12-04 15:16:50 +090076 /// Malformed <iommus>
77 MalformedIommus,
Jaewan Kim51ccfed2023-11-08 13:51:58 +090078 /// Invalid <iommus>
79 InvalidIommus,
Jaewan Kim19b984f2023-12-04 15:16:50 +090080 /// Invalid phys IOMMU node
81 InvalidPhysIommu,
Jaewan Kima9200492023-11-21 20:45:31 +090082 /// Invalid pvIOMMU node
83 InvalidPvIommu,
Jaewan Kim51ccfed2023-11-08 13:51:58 +090084 /// Too many pvIOMMU
85 TooManyPvIommu,
Jaewan Kim19b984f2023-12-04 15:16:50 +090086 /// Duplicated phys IOMMU IDs exist
87 DuplicatedIommuIds,
Jaewan Kim51ccfed2023-11-08 13:51:58 +090088 /// Duplicated pvIOMMU IDs exist
89 DuplicatedPvIommuIds,
Jaewan Kimf8abbb52023-12-12 22:11:39 +090090 /// Unsupported path format. Only supports full path.
91 UnsupportedPathFormat,
Jaewan Kimc6e023b2023-10-12 15:11:05 +090092 /// Unsupported overlay target syntax. Only supports <target-path> with full path.
93 UnsupportedOverlayTarget,
Jaewan Kim19b984f2023-12-04 15:16:50 +090094 /// Unsupported PhysIommu,
95 UnsupportedPhysIommu,
96 /// Unsupported (pvIOMMU id, vSID) duplication. Currently the pair should be unique.
97 UnsupportedPvIommusDuplication,
98 /// Unsupported (IOMMU token, SID) duplication. Currently the pair should be unique.
99 UnsupportedIommusDuplication,
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900100 /// Internal error
101 Internal,
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900102 /// Unexpected error from libfdt
103 UnexpectedFdtError(FdtError),
104}
105
106impl From<FdtError> for DeviceAssignmentError {
107 fn from(e: FdtError) -> Self {
108 DeviceAssignmentError::UnexpectedFdtError(e)
109 }
110}
111
112impl fmt::Display for DeviceAssignmentError {
113 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
114 match self {
115 Self::InvalidDtbo => write!(f, "Invalid DTBO"),
116 Self::InvalidSymbols => write!(
117 f,
118 "Invalid property in /__symbols__. Must point to valid assignable device node."
119 ),
Jaewan Kim19b984f2023-12-04 15:16:50 +0900120 Self::MalformedReg => write!(f, "Malformed <reg>. Can't parse"),
Pierre-Clément Tosif4d8a0d2024-10-25 17:14:15 +0100121 Self::MissingReg(addr, size) => {
122 write!(f, "Missing physical MMIO region: addr:{addr:#x}), size:{size:#x}")
Pierre-Clément Tosi8b78bc32024-03-13 17:37:07 +0000123 }
Pierre-Clément Tosif4d8a0d2024-10-25 17:14:15 +0100124 Self::ExtraReg(addr, size) => {
125 write!(f, "Unexpected extra MMIO region: addr:{addr:#x}), size:{size:#x}")
126 }
127 Self::InvalidReg(addr) => {
128 write!(f, "Invalid guest MMIO granule (addr: {addr:#x})")
129 }
Pierre-Clément Tosi7fb437f2024-10-29 09:54:42 +0000130 Self::InvalidRegSize(size, expected) => {
131 write!(f, "Unexpected MMIO size ({size:#x}), should be {expected:#x}")
132 }
Pierre-Clément Tosif4d8a0d2024-10-25 17:14:15 +0100133 Self::InvalidRegToken(token, expected) => {
134 write!(f, "Unexpected MMIO token ({token:#x}), should be {expected:#x}")
Pierre-Clément Tosi8b78bc32024-03-13 17:37:07 +0000135 }
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900136 Self::InvalidInterrupts => write!(f, "Invalid <interrupts>"),
Jaewan Kim19b984f2023-12-04 15:16:50 +0900137 Self::MalformedIommus => write!(f, "Malformed <iommus>. Can't parse."),
138 Self::InvalidIommus => {
139 write!(f, "Invalid <iommus>. Failed to validate with hypervisor")
140 }
141 Self::InvalidPhysIommu => write!(f, "Invalid phys IOMMU node"),
Jaewan Kima9200492023-11-21 20:45:31 +0900142 Self::InvalidPvIommu => write!(f, "Invalid pvIOMMU node"),
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900143 Self::TooManyPvIommu => write!(
144 f,
145 "Too many pvIOMMU node. Insufficient pre-populated pvIOMMUs in platform DT"
146 ),
Jaewan Kim19b984f2023-12-04 15:16:50 +0900147 Self::DuplicatedIommuIds => {
148 write!(f, "Duplicated IOMMU IDs exist. IDs must unique among iommu node")
149 }
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900150 Self::DuplicatedPvIommuIds => {
Jaewan Kim19b984f2023-12-04 15:16:50 +0900151 write!(f, "Duplicated pvIOMMU IDs exist. IDs must unique among iommu node")
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900152 }
Jaewan Kimf8abbb52023-12-12 22:11:39 +0900153 Self::UnsupportedPathFormat => {
154 write!(f, "Unsupported UnsupportedPathFormat. Only supports full path")
155 }
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900156 Self::UnsupportedOverlayTarget => {
157 write!(f, "Unsupported overlay target. Only supports 'target-path = \"/\"'")
158 }
Jaewan Kim19b984f2023-12-04 15:16:50 +0900159 Self::UnsupportedPhysIommu => {
160 write!(f, "Unsupported Phys IOMMU. Currently only supports #iommu-cells = <1>")
161 }
162 Self::UnsupportedPvIommusDuplication => {
163 write!(f, "Unsupported (pvIOMMU id, vSID) duplication. Currently the pair should be unique.")
164 }
165 Self::UnsupportedIommusDuplication => {
166 write!(f, "Unsupported (IOMMU token, SID) duplication. Currently the pair should be unique.")
167 }
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900168 Self::Internal => write!(f, "Internal error"),
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900169 Self::UnexpectedFdtError(e) => write!(f, "Unexpected Error from libfdt: {e}"),
170 }
171 }
172}
173
174pub type Result<T> = core::result::Result<T, DeviceAssignmentError>;
175
Jaewan Kimf8abbb52023-12-12 22:11:39 +0900176#[derive(Clone, Default, Ord, PartialOrd, Eq, PartialEq)]
177pub struct DtPathTokens<'a> {
178 tokens: Vec<&'a [u8]>,
179}
180
181impl<'a> fmt::Debug for DtPathTokens<'a> {
182 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
183 let mut list = f.debug_list();
184 for token in &self.tokens {
185 let mut bytes = token.to_vec();
186 bytes.push(b'\0');
187 match CString::from_vec_with_nul(bytes) {
188 Ok(string) => list.entry(&string),
189 Err(_) => list.entry(token),
190 };
191 }
192 list.finish()
193 }
194}
195
196impl<'a> DtPathTokens<'a> {
197 fn new(path: &'a CStr) -> Result<Self> {
198 if path.to_bytes().first() != Some(&b'/') {
199 return Err(DeviceAssignmentError::UnsupportedPathFormat);
200 }
201 let tokens: Vec<_> = path
202 .to_bytes()
203 .split(|char| *char == b'/')
204 .filter(|&component| !component.is_empty())
205 .collect();
206 Ok(Self { tokens })
207 }
208
209 fn to_overlay_target_path(&self) -> Result<Self> {
210 if !self.is_overlayable_node() {
211 return Err(DeviceAssignmentError::InvalidDtbo);
212 }
213 Ok(Self { tokens: self.tokens.as_slice()[2..].to_vec() })
214 }
215
216 fn to_cstring(&self) -> CString {
217 if self.tokens.is_empty() {
218 return CString::new(*b"/\0").unwrap();
219 }
220
221 let size = self.tokens.iter().fold(0, |sum, token| sum + token.len() + 1);
222 let mut path = Vec::with_capacity(size + 1);
223 for token in &self.tokens {
224 path.push(b'/');
225 path.extend_from_slice(token);
226 }
227 path.push(b'\0');
228
229 CString::from_vec_with_nul(path).unwrap()
230 }
231
232 fn is_overlayable_node(&self) -> bool {
233 self.tokens.get(1) == Some(&&b"__overlay__"[..])
234 }
235}
236
Jaewan Kim8f6f4662023-12-12 17:38:47 +0900237#[derive(Debug, Eq, PartialEq)]
238enum DeviceTreeChildrenMask {
239 Partial(Vec<DeviceTreeMask>),
240 All,
241}
242
243#[derive(Eq, PartialEq)]
244struct DeviceTreeMask {
245 name_bytes: Vec<u8>,
246 children: DeviceTreeChildrenMask,
247}
248
249impl fmt::Debug for DeviceTreeMask {
250 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
251 let name_bytes = [self.name_bytes.as_slice(), b"\0"].concat();
252
253 f.debug_struct("DeviceTreeMask")
254 .field("name", &CStr::from_bytes_with_nul(&name_bytes).unwrap())
255 .field("children", &self.children)
256 .finish()
257 }
258}
259
260impl DeviceTreeMask {
261 fn new() -> Self {
262 Self { name_bytes: b"/".to_vec(), children: DeviceTreeChildrenMask::Partial(Vec::new()) }
263 }
264
265 fn mask_internal(&mut self, path: &DtPathTokens, leaf_mask: DeviceTreeChildrenMask) -> bool {
266 let mut iter = self;
267 let mut newly_masked = false;
268 'next_token: for path_token in &path.tokens {
269 let DeviceTreeChildrenMask::Partial(ref mut children) = &mut iter.children else {
270 return false;
271 };
272
273 // Note: Can't use iterator for 'get or insert'. (a.k.a. polonius Rust)
274 #[allow(clippy::needless_range_loop)]
275 for i in 0..children.len() {
276 if children[i].name_bytes.as_slice() == *path_token {
277 iter = &mut children[i];
278 newly_masked = false;
279 continue 'next_token;
280 }
281 }
282 let child = Self {
283 name_bytes: path_token.to_vec(),
284 children: DeviceTreeChildrenMask::Partial(Vec::new()),
285 };
286 children.push(child);
287 newly_masked = true;
288 iter = children.last_mut().unwrap()
289 }
290 iter.children = leaf_mask;
291 newly_masked
292 }
293
294 fn mask(&mut self, path: &DtPathTokens) -> bool {
295 self.mask_internal(path, DeviceTreeChildrenMask::Partial(Vec::new()))
296 }
297
298 fn mask_all(&mut self, path: &DtPathTokens) {
299 self.mask_internal(path, DeviceTreeChildrenMask::All);
300 }
301}
302
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900303/// Represents VM DTBO
304#[repr(transparent)]
305pub struct VmDtbo(Fdt);
306
307impl VmDtbo {
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900308 /// Wraps a mutable slice containing a VM DTBO.
309 ///
310 /// Fails if the VM DTBO does not pass validation.
311 pub fn from_mut_slice(dtbo: &mut [u8]) -> Result<&mut Self> {
312 // This validates DTBO
313 let fdt = Fdt::from_mut_slice(dtbo)?;
314 // SAFETY: VmDtbo is a transparent wrapper around Fdt, so representation is the same.
315 Ok(unsafe { mem::transmute::<&mut Fdt, &mut Self>(fdt) })
316 }
317
318 // Locates device node path as if the given dtbo node path is assigned and VM DTBO is overlaid.
319 // For given dtbo node path, this concatenates <target-path> of the enclosing fragment and
320 // relative path from __overlay__ node.
321 //
322 // Here's an example with sample VM DTBO:
323 // / {
324 // fragment@rng {
325 // target-path = "/"; // Always 'target-path = "/"'. Disallows <target> or other path.
326 // __overlay__ {
327 // rng { ... }; // Actual device node is here. If overlaid, path would be "/rng"
328 // };
329 // };
Jaewan Kim80ef9fa2024-02-25 16:08:14 +0000330 // __symbols__ { // Contains list of assignable devices
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900331 // rng = "/fragment@rng/__overlay__/rng";
332 // };
333 // };
334 //
335 // Then locate_overlay_target_path(cstr!("/fragment@rng/__overlay__/rng")) is Ok("/rng")
336 //
337 // Contrary to fdt_overlay_target_offset(), this API enforces overlay target property
338 // 'target-path = "/"', so the overlay doesn't modify and/or append platform DT's existing
339 // node and/or properties. The enforcement is for compatibility reason.
Jaewan Kim19b984f2023-12-04 15:16:50 +0900340 fn locate_overlay_target_path(
341 &self,
Jaewan Kimf8abbb52023-12-12 22:11:39 +0900342 dtbo_node_path: &DtPathTokens,
Jaewan Kim19b984f2023-12-04 15:16:50 +0900343 dtbo_node: &FdtNode,
344 ) -> Result<CString> {
Jaewan Kim19b984f2023-12-04 15:16:50 +0900345 let fragment_node = dtbo_node.supernode_at_depth(1)?;
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900346 let target_path = fragment_node
Pierre-Clément Tosid701a0b2023-11-07 15:38:59 +0000347 .getprop_str(cstr!("target-path"))?
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900348 .ok_or(DeviceAssignmentError::InvalidDtbo)?;
349 if target_path != cstr!("/") {
350 return Err(DeviceAssignmentError::UnsupportedOverlayTarget);
351 }
352
Jaewan Kimf8abbb52023-12-12 22:11:39 +0900353 let overlaid_path = dtbo_node_path.to_overlay_target_path()?;
354 Ok(overlaid_path.to_cstring())
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900355 }
Jaewan Kim19b984f2023-12-04 15:16:50 +0900356
357 fn parse_physical_iommus(physical_node: &FdtNode) -> Result<BTreeMap<Phandle, PhysIommu>> {
358 let mut phys_iommus = BTreeMap::new();
359 for (node, _) in physical_node.descendants() {
360 let Some(phandle) = node.get_phandle()? else {
361 continue; // Skips unreachable IOMMU node
362 };
363 let Some(iommu) = PhysIommu::parse(&node)? else {
364 continue; // Skip if not a PhysIommu.
365 };
366 if phys_iommus.insert(phandle, iommu).is_some() {
367 return Err(FdtError::BadPhandle.into());
368 }
369 }
370 Self::validate_physical_iommus(&phys_iommus)?;
371 Ok(phys_iommus)
372 }
373
374 fn validate_physical_iommus(phys_iommus: &BTreeMap<Phandle, PhysIommu>) -> Result<()> {
375 let unique_iommus: BTreeSet<_> = phys_iommus.values().cloned().collect();
376 if phys_iommus.len() != unique_iommus.len() {
377 return Err(DeviceAssignmentError::DuplicatedIommuIds);
378 }
379 Ok(())
380 }
381
382 fn validate_physical_devices(
383 physical_devices: &BTreeMap<Phandle, PhysicalDeviceInfo>,
384 ) -> Result<()> {
385 // Only need to validate iommus because <reg> will be validated together with PV <reg>
386 // see: DeviceAssignmentInfo::validate_all_regs().
387 let mut all_iommus = BTreeSet::new();
388 for physical_device in physical_devices.values() {
389 for iommu in &physical_device.iommus {
390 if !all_iommus.insert(iommu) {
391 error!("Unsupported phys IOMMU duplication found, <iommus> = {iommu:?}");
392 return Err(DeviceAssignmentError::UnsupportedIommusDuplication);
393 }
394 }
395 }
396 Ok(())
397 }
398
399 fn parse_physical_devices_with_iommus(
400 physical_node: &FdtNode,
401 phys_iommus: &BTreeMap<Phandle, PhysIommu>,
402 ) -> Result<BTreeMap<Phandle, PhysicalDeviceInfo>> {
403 let mut physical_devices = BTreeMap::new();
404 for (node, _) in physical_node.descendants() {
405 let Some(info) = PhysicalDeviceInfo::parse(&node, phys_iommus)? else {
406 continue;
407 };
408 if physical_devices.insert(info.target, info).is_some() {
409 return Err(DeviceAssignmentError::InvalidDtbo);
410 }
411 }
412 Self::validate_physical_devices(&physical_devices)?;
413 Ok(physical_devices)
414 }
415
416 /// Parses Physical devices in VM DTBO
417 fn parse_physical_devices(&self) -> Result<BTreeMap<Phandle, PhysicalDeviceInfo>> {
418 let Some(physical_node) = self.as_ref().node(cstr!("/host"))? else {
419 return Ok(BTreeMap::new());
420 };
421
422 let phys_iommus = Self::parse_physical_iommus(&physical_node)?;
423 Self::parse_physical_devices_with_iommus(&physical_node, &phys_iommus)
424 }
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900425
Jaewan Kimf8abbb52023-12-12 22:11:39 +0900426 fn node(&self, path: &DtPathTokens) -> Result<Option<FdtNode>> {
427 let mut node = self.as_ref().root();
428 for token in &path.tokens {
429 let Some(subnode) = node.subnode_with_name_bytes(token)? else {
430 return Ok(None);
431 };
432 node = subnode;
433 }
434 Ok(Some(node))
435 }
Jaewan Kim8f6f4662023-12-12 17:38:47 +0900436
437 fn collect_overlayable_nodes_with_phandle(&self) -> Result<BTreeMap<Phandle, DtPathTokens>> {
438 let mut paths = BTreeMap::new();
439 let mut path: DtPathTokens = Default::default();
440 let root = self.as_ref().root();
441 for (node, depth) in root.descendants() {
442 path.tokens.truncate(depth - 1);
443 path.tokens.push(node.name()?.to_bytes());
444 if !path.is_overlayable_node() {
445 continue;
446 }
447 if let Some(phandle) = node.get_phandle()? {
448 paths.insert(phandle, path.clone());
449 }
450 }
451 Ok(paths)
452 }
453
454 fn collect_phandle_references_from_overlayable_nodes(
455 &self,
456 ) -> Result<BTreeMap<DtPathTokens, Vec<Phandle>>> {
457 const CELL_SIZE: usize = core::mem::size_of::<u32>();
458
459 let vm_dtbo = self.as_ref();
460
461 let mut phandle_map = BTreeMap::new();
462 let Some(local_fixups) = vm_dtbo.node(cstr!("/__local_fixups__"))? else {
463 return Ok(phandle_map);
464 };
465
466 let mut path: DtPathTokens = Default::default();
467 for (fixup_node, depth) in local_fixups.descendants() {
468 let node_name = fixup_node.name()?;
469 path.tokens.truncate(depth - 1);
470 path.tokens.push(node_name.to_bytes());
471 if path.tokens.len() != depth {
472 return Err(DeviceAssignmentError::Internal);
473 }
474 if !path.is_overlayable_node() {
475 continue;
476 }
477 let target_node = self.node(&path)?.ok_or(DeviceAssignmentError::InvalidDtbo)?;
478
479 let mut phandles = vec![];
480 for fixup_prop in fixup_node.properties()? {
481 let target_prop = target_node
482 .getprop(fixup_prop.name()?)
483 .or(Err(DeviceAssignmentError::InvalidDtbo))?
484 .ok_or(DeviceAssignmentError::InvalidDtbo)?;
485 let fixup_prop_values = fixup_prop.value()?;
486 if fixup_prop_values.is_empty() || fixup_prop_values.len() % CELL_SIZE != 0 {
487 return Err(DeviceAssignmentError::InvalidDtbo);
488 }
489
490 for fixup_prop_cell in fixup_prop_values.chunks(CELL_SIZE) {
491 let phandle_offset: usize = u32::from_be_bytes(
492 fixup_prop_cell.try_into().or(Err(DeviceAssignmentError::InvalidDtbo))?,
493 )
494 .try_into()
495 .or(Err(DeviceAssignmentError::InvalidDtbo))?;
496 if phandle_offset % CELL_SIZE != 0 {
497 return Err(DeviceAssignmentError::InvalidDtbo);
498 }
499 let phandle_value = target_prop
500 .get(phandle_offset..phandle_offset + CELL_SIZE)
501 .ok_or(DeviceAssignmentError::InvalidDtbo)?;
502 let phandle: Phandle = U32::ref_from(phandle_value)
503 .unwrap()
504 .get()
505 .try_into()
506 .or(Err(DeviceAssignmentError::InvalidDtbo))?;
507
508 phandles.push(phandle);
509 }
510 }
511 if !phandles.is_empty() {
512 phandle_map.insert(path.clone(), phandles);
513 }
514 }
515
516 Ok(phandle_map)
517 }
518
519 fn build_mask(&self, assigned_devices: Vec<DtPathTokens>) -> Result<DeviceTreeMask> {
520 if assigned_devices.is_empty() {
521 return Err(DeviceAssignmentError::Internal);
522 }
523
524 let dependencies = self.collect_phandle_references_from_overlayable_nodes()?;
525 let paths = self.collect_overlayable_nodes_with_phandle()?;
526
527 let mut mask = DeviceTreeMask::new();
528 let mut stack = assigned_devices;
529 while let Some(path) = stack.pop() {
530 if !mask.mask(&path) {
531 continue;
532 }
533 let Some(dst_phandles) = dependencies.get(&path) else {
534 continue;
535 };
536 for dst_phandle in dst_phandles {
537 let dst_path = paths.get(dst_phandle).ok_or(DeviceAssignmentError::Internal)?;
538 stack.push(dst_path.clone());
539 }
540 }
541
542 Ok(mask)
543 }
Jaewan Kimc39974e2023-12-02 01:13:30 +0900544}
545
Jaewan Kimc730ebf2024-02-22 10:34:55 +0900546fn filter_dangling_symbols(fdt: &mut Fdt) -> Result<()> {
547 if let Some(symbols) = fdt.symbols()? {
548 let mut removed = vec![];
549 for prop in symbols.properties()? {
550 let path = CStr::from_bytes_with_nul(prop.value()?)
551 .map_err(|_| DeviceAssignmentError::Internal)?;
552 if fdt.node(path)?.is_none() {
553 let name = prop.name()?;
554 removed.push(CString::from(name));
555 }
556 }
557
558 let mut symbols = fdt.symbols_mut()?.unwrap();
559 for name in removed {
560 symbols.nop_property(&name)?;
561 }
562 }
563 Ok(())
564}
565
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900566impl AsRef<Fdt> for VmDtbo {
567 fn as_ref(&self) -> &Fdt {
568 &self.0
569 }
570}
571
572impl AsMut<Fdt> for VmDtbo {
573 fn as_mut(&mut self) -> &mut Fdt {
574 &mut self.0
575 }
576}
577
Jaewan Kim8f6f4662023-12-12 17:38:47 +0900578// Filter any node that isn't masked by DeviceTreeMask.
579fn filter_with_mask(anchor: FdtNodeMut, mask: &DeviceTreeMask) -> Result<()> {
580 let mut stack = vec![mask];
581 let mut iter = anchor.next_node(0)?;
582 while let Some((node, depth)) = iter {
583 stack.truncate(depth);
584 let parent_mask = stack.last().unwrap();
585 let DeviceTreeChildrenMask::Partial(parent_mask_children) = &parent_mask.children else {
586 // Shouldn't happen. We only step-in if parent has DeviceTreeChildrenMask::Partial.
587 return Err(DeviceAssignmentError::Internal);
588 };
589
590 let name = node.as_node().name()?.to_bytes();
591 let mask = parent_mask_children.iter().find(|child_mask| child_mask.name_bytes == name);
592 if let Some(masked) = mask {
593 if let DeviceTreeChildrenMask::Partial(_) = &masked.children {
594 // This node is partially masked. Stepping-in.
595 stack.push(masked);
596 iter = node.next_node(depth)?;
597 } else {
598 // This node is fully masked. Stepping-out.
599 iter = node.next_node_skip_subnodes(depth)?;
600 }
601 } else {
602 // This node isn't masked.
603 iter = node.delete_and_next_node(depth)?;
604 }
605 }
606
607 Ok(())
608}
609
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900610#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
611struct PvIommu {
612 // ID from pvIOMMU node
613 id: u32,
614}
615
616impl PvIommu {
617 fn parse(node: &FdtNode) -> Result<Self> {
Jaewan Kima9200492023-11-21 20:45:31 +0900618 let iommu_cells = node
619 .getprop_u32(cstr!("#iommu-cells"))?
620 .ok_or(DeviceAssignmentError::InvalidPvIommu)?;
Jaewan Kim19b984f2023-12-04 15:16:50 +0900621 // Ensures #iommu-cells = <1>. It means that `<iommus>` entry contains pair of
Jaewan Kima9200492023-11-21 20:45:31 +0900622 // (pvIOMMU ID, vSID)
623 if iommu_cells != 1 {
624 return Err(DeviceAssignmentError::InvalidPvIommu);
625 }
626 let id = node.getprop_u32(cstr!("id"))?.ok_or(DeviceAssignmentError::InvalidPvIommu)?;
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900627 Ok(Self { id })
628 }
629}
630
Jaewan Kima9200492023-11-21 20:45:31 +0900631#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
632struct Vsid(u32);
633
Jaewan Kim19b984f2023-12-04 15:16:50 +0900634#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
635struct Sid(u64);
636
637impl From<u32> for Sid {
638 fn from(sid: u32) -> Self {
639 Self(sid.into())
640 }
641}
642
643#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
Jaewan Kim52477ae2023-11-21 21:20:52 +0900644struct DeviceReg {
645 addr: u64,
646 size: u64,
647}
648
Pierre-Clément Tosi49e26ce2024-03-12 16:31:50 +0000649impl DeviceReg {
650 pub fn overlaps(&self, range: &Range<u64>) -> bool {
651 self.addr < range.end && range.start < self.addr.checked_add(self.size).unwrap()
652 }
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +0100653
654 pub fn is_aligned(&self, granule: u64) -> bool {
655 self.addr % granule == 0 && self.size % granule == 0
656 }
Pierre-Clément Tosi49e26ce2024-03-12 16:31:50 +0000657}
658
Jaewan Kim52477ae2023-11-21 21:20:52 +0900659impl TryFrom<Reg<u64>> for DeviceReg {
660 type Error = DeviceAssignmentError;
661
662 fn try_from(reg: Reg<u64>) -> Result<Self> {
Jaewan Kim19b984f2023-12-04 15:16:50 +0900663 Ok(Self { addr: reg.addr, size: reg.size.ok_or(DeviceAssignmentError::MalformedReg)? })
Jaewan Kim52477ae2023-11-21 21:20:52 +0900664 }
665}
666
667fn parse_node_reg(node: &FdtNode) -> Result<Vec<DeviceReg>> {
668 node.reg()?
Jaewan Kim19b984f2023-12-04 15:16:50 +0900669 .ok_or(DeviceAssignmentError::MalformedReg)?
Jaewan Kim52477ae2023-11-21 21:20:52 +0900670 .map(DeviceReg::try_from)
671 .collect::<Result<Vec<_>>>()
672}
673
674fn to_be_bytes(reg: &[DeviceReg]) -> Vec<u8> {
675 let mut reg_cells = vec![];
676 for x in reg {
677 reg_cells.extend_from_slice(&x.addr.to_be_bytes());
678 reg_cells.extend_from_slice(&x.size.to_be_bytes());
679 }
680 reg_cells
681}
682
Jaewan Kim19b984f2023-12-04 15:16:50 +0900683#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
684struct PhysIommu {
685 token: u64,
686}
687
688impl PhysIommu {
689 fn parse(node: &FdtNode) -> Result<Option<Self>> {
690 let Some(token) = node.getprop_u64(cstr!("android,pvmfw,token"))? else {
691 return Ok(None);
692 };
693 let Some(iommu_cells) = node.getprop_u32(cstr!("#iommu-cells"))? else {
694 return Err(DeviceAssignmentError::InvalidPhysIommu);
695 };
696 // Currently only supports #iommu-cells = <1>.
697 // In that case `<iommus>` entry contains pair of (pIOMMU phandle, Sid token)
698 if iommu_cells != 1 {
699 return Err(DeviceAssignmentError::UnsupportedPhysIommu);
700 }
701 Ok(Some(Self { token }))
702 }
703}
704
705#[derive(Debug)]
706struct PhysicalDeviceInfo {
707 target: Phandle,
708 reg: Vec<DeviceReg>,
709 iommus: Vec<(PhysIommu, Sid)>,
710}
711
712impl PhysicalDeviceInfo {
713 fn parse_iommus(
714 node: &FdtNode,
715 phys_iommus: &BTreeMap<Phandle, PhysIommu>,
716 ) -> Result<Vec<(PhysIommu, Sid)>> {
717 let mut iommus = vec![];
718 let Some(mut cells) = node.getprop_cells(cstr!("iommus"))? else {
719 return Ok(iommus);
720 };
721 while let Some(cell) = cells.next() {
722 // Parse pIOMMU ID
723 let phandle =
724 Phandle::try_from(cell).or(Err(DeviceAssignmentError::MalformedIommus))?;
725 let iommu = phys_iommus.get(&phandle).ok_or(DeviceAssignmentError::MalformedIommus)?;
726
727 // Parse Sid
728 let Some(cell) = cells.next() else {
729 return Err(DeviceAssignmentError::MalformedIommus);
730 };
731
732 iommus.push((*iommu, Sid::from(cell)));
733 }
734 Ok(iommus)
735 }
736
737 fn parse(node: &FdtNode, phys_iommus: &BTreeMap<Phandle, PhysIommu>) -> Result<Option<Self>> {
738 let Some(phandle) = node.getprop_u32(cstr!("android,pvmfw,target"))? else {
739 return Ok(None);
740 };
741 let target = Phandle::try_from(phandle)?;
742 let reg = parse_node_reg(node)?;
743 let iommus = Self::parse_iommus(node, phys_iommus)?;
744 Ok(Some(Self { target, reg, iommus }))
745 }
746}
747
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900748/// Assigned device information parsed from crosvm DT.
749/// Keeps everything in the owned data because underlying FDT will be reused for platform DT.
750#[derive(Debug, Eq, PartialEq)]
751struct AssignedDeviceInfo {
752 // Node path of assigned device (e.g. "/rng")
753 node_path: CString,
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900754 // <reg> property from the crosvm DT
Jaewan Kim52477ae2023-11-21 21:20:52 +0900755 reg: Vec<DeviceReg>,
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900756 // <interrupts> property from the crosvm DT
757 interrupts: Vec<u8>,
Jaewan Kima9200492023-11-21 20:45:31 +0900758 // Parsed <iommus> property from the crosvm DT. Tuple of PvIommu and vSID.
759 iommus: Vec<(PvIommu, Vsid)>,
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900760}
761
762impl AssignedDeviceInfo {
Jaewan Kim19b984f2023-12-04 15:16:50 +0900763 fn validate_reg(
764 device_reg: &[DeviceReg],
765 physical_device_reg: &[DeviceReg],
Jaewan Kim52477ae2023-11-21 21:20:52 +0900766 hypervisor: &dyn DeviceAssigningHypervisor,
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +0100767 granule: usize,
Jaewan Kim19b984f2023-12-04 15:16:50 +0900768 ) -> Result<()> {
Pierre-Clément Tosi8b78bc32024-03-13 17:37:07 +0000769 let mut virt_regs = device_reg.iter();
770 let mut phys_regs = physical_device_reg.iter();
Pierre-Clément Tosi49e26ce2024-03-12 16:31:50 +0000771 // TODO(b/308694211): Move this constant to vmbase::layout once vmbase is std-compatible.
772 const PVMFW_RANGE: Range<u64> = 0x7fc0_0000..0x8000_0000;
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +0100773
Jaewan Kim19b984f2023-12-04 15:16:50 +0900774 // PV reg and physical reg should have 1:1 match in order.
Pierre-Clément Tosi8b78bc32024-03-13 17:37:07 +0000775 for (reg, phys_reg) in virt_regs.by_ref().zip(phys_regs.by_ref()) {
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +0100776 if !reg.is_aligned(granule.try_into().unwrap()) {
777 let DeviceReg { addr, size } = reg;
778 warn!("Assigned region ({addr:#x}, {size:#x}) not aligned to {granule:#x}");
779 // TODO(ptosi): Fix our test data so that we can return Err(...);
780 }
Pierre-Clément Tosi49e26ce2024-03-12 16:31:50 +0000781 if reg.overlaps(&PVMFW_RANGE) {
Pierre-Clément Tosif4d8a0d2024-10-25 17:14:15 +0100782 return Err(DeviceAssignmentError::InvalidReg(reg.addr));
Pierre-Clément Tosi49e26ce2024-03-12 16:31:50 +0000783 }
Pierre-Clément Tosi7fb437f2024-10-29 09:54:42 +0000784 if reg.size != phys_reg.size {
785 return Err(DeviceAssignmentError::InvalidRegSize(reg.size, phys_reg.size));
786 }
Pierre-Clément Tosif4d8a0d2024-10-25 17:14:15 +0100787 let expected_token = phys_reg.addr;
Pierre-Clément Tosi49e26ce2024-03-12 16:31:50 +0000788 // If this call returns successfully, hyp has mapped the MMIO region at `reg`.
Pierre-Clément Tosif4d8a0d2024-10-25 17:14:15 +0100789 let token = hypervisor.get_phys_mmio_token(reg.addr, reg.size).map_err(|e| {
Pierre-Clément Tosi08d6e3f2024-03-13 18:22:16 +0000790 error!("Hypervisor error while requesting MMIO token: {e}");
Pierre-Clément Tosif4d8a0d2024-10-25 17:14:15 +0100791 DeviceAssignmentError::InvalidReg(reg.addr)
Jaewan Kim52477ae2023-11-21 21:20:52 +0900792 })?;
Pierre-Clément Tosif4d8a0d2024-10-25 17:14:15 +0100793 if token != expected_token {
794 return Err(DeviceAssignmentError::InvalidRegToken(token, expected_token));
Jaewan Kim19b984f2023-12-04 15:16:50 +0900795 }
Jaewan Kim52477ae2023-11-21 21:20:52 +0900796 }
Pierre-Clément Tosi8b78bc32024-03-13 17:37:07 +0000797
798 if let Some(DeviceReg { addr, size }) = virt_regs.next() {
Pierre-Clément Tosif4d8a0d2024-10-25 17:14:15 +0100799 return Err(DeviceAssignmentError::ExtraReg(*addr, *size));
Pierre-Clément Tosi8b78bc32024-03-13 17:37:07 +0000800 }
801
802 if let Some(DeviceReg { addr, size }) = phys_regs.next() {
Pierre-Clément Tosif4d8a0d2024-10-25 17:14:15 +0100803 return Err(DeviceAssignmentError::MissingReg(*addr, *size));
Pierre-Clément Tosi8b78bc32024-03-13 17:37:07 +0000804 }
805
Jaewan Kim19b984f2023-12-04 15:16:50 +0900806 Ok(())
Jaewan Kim52477ae2023-11-21 21:20:52 +0900807 }
808
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900809 fn parse_interrupts(node: &FdtNode) -> Result<Vec<u8>> {
810 // Validation: Validate if interrupts cell numbers are multiple of #interrupt-cells.
811 // We can't know how many interrupts would exist.
812 let interrupts_cells = node
Pierre-Clément Tosid701a0b2023-11-07 15:38:59 +0000813 .getprop_cells(cstr!("interrupts"))?
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900814 .ok_or(DeviceAssignmentError::InvalidInterrupts)?
815 .count();
816 if interrupts_cells % CELLS_PER_INTERRUPT != 0 {
817 return Err(DeviceAssignmentError::InvalidInterrupts);
818 }
819
820 // Once validated, keep the raw bytes so patch can be done with setprop()
Pierre-Clément Tosid701a0b2023-11-07 15:38:59 +0000821 Ok(node.getprop(cstr!("interrupts")).unwrap().unwrap().into())
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900822 }
823
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900824 // TODO(b/277993056): Also validate /__local_fixups__ to ensure that <iommus> has phandle.
Jaewan Kima9200492023-11-21 20:45:31 +0900825 fn parse_iommus(
826 node: &FdtNode,
827 pviommus: &BTreeMap<Phandle, PvIommu>,
828 ) -> Result<Vec<(PvIommu, Vsid)>> {
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900829 let mut iommus = vec![];
Jaewan Kima9200492023-11-21 20:45:31 +0900830 let Some(mut cells) = node.getprop_cells(cstr!("iommus"))? else {
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900831 return Ok(iommus);
832 };
Jaewan Kima9200492023-11-21 20:45:31 +0900833 while let Some(cell) = cells.next() {
834 // Parse pvIOMMU ID
Jaewan Kim19b984f2023-12-04 15:16:50 +0900835 let phandle =
836 Phandle::try_from(cell).or(Err(DeviceAssignmentError::MalformedIommus))?;
837 let pviommu = pviommus.get(&phandle).ok_or(DeviceAssignmentError::MalformedIommus)?;
Jaewan Kima9200492023-11-21 20:45:31 +0900838
839 // Parse vSID
840 let Some(cell) = cells.next() else {
Jaewan Kim19b984f2023-12-04 15:16:50 +0900841 return Err(DeviceAssignmentError::MalformedIommus);
Jaewan Kima9200492023-11-21 20:45:31 +0900842 };
843 let vsid = Vsid(cell);
844
845 iommus.push((*pviommu, vsid));
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900846 }
847 Ok(iommus)
848 }
849
Jaewan Kim19b984f2023-12-04 15:16:50 +0900850 fn validate_iommus(
851 iommus: &[(PvIommu, Vsid)],
852 physical_device_iommu: &[(PhysIommu, Sid)],
853 hypervisor: &dyn DeviceAssigningHypervisor,
854 ) -> Result<()> {
855 if iommus.len() != physical_device_iommu.len() {
856 return Err(DeviceAssignmentError::InvalidIommus);
857 }
858 // pvIOMMU can be reordered, and hypervisor may not guarantee 1:1 mapping.
859 // So we need to mark what's matched or not.
860 let mut physical_device_iommu = physical_device_iommu.to_vec();
861 for (pviommu, vsid) in iommus {
Pierre-Clément Tosi08d6e3f2024-03-13 18:22:16 +0000862 let (id, sid) =
863 hypervisor.get_phys_iommu_token(pviommu.id.into(), vsid.0.into()).map_err(|e| {
864 error!("Hypervisor error while requesting IOMMU token ({pviommu:?}, {vsid:?}): {e}");
865 DeviceAssignmentError::InvalidIommus
866 })?;
Jaewan Kim19b984f2023-12-04 15:16:50 +0900867
868 let pos = physical_device_iommu
869 .iter()
870 .position(|(phys_iommu, phys_sid)| (phys_iommu.token, phys_sid.0) == (id, sid));
871 match pos {
872 Some(pos) => physical_device_iommu.remove(pos),
873 None => {
874 error!("Failed to validate device <iommus>. No matching phys iommu or duplicated mapping for pviommu={pviommu:?}, vsid={vsid:?}");
875 return Err(DeviceAssignmentError::InvalidIommus);
876 }
877 };
878 }
879 Ok(())
880 }
881
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900882 fn parse(
883 fdt: &Fdt,
884 vm_dtbo: &VmDtbo,
Jaewan Kimf8abbb52023-12-12 22:11:39 +0900885 dtbo_node_path: &DtPathTokens,
Jaewan Kim19b984f2023-12-04 15:16:50 +0900886 physical_devices: &BTreeMap<Phandle, PhysicalDeviceInfo>,
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900887 pviommus: &BTreeMap<Phandle, PvIommu>,
Jaewan Kim52477ae2023-11-21 21:20:52 +0900888 hypervisor: &dyn DeviceAssigningHypervisor,
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +0100889 granule: usize,
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900890 ) -> Result<Option<Self>> {
Jaewan Kim19b984f2023-12-04 15:16:50 +0900891 let dtbo_node =
Jaewan Kimf8abbb52023-12-12 22:11:39 +0900892 vm_dtbo.node(dtbo_node_path)?.ok_or(DeviceAssignmentError::InvalidSymbols)?;
Jaewan Kim19b984f2023-12-04 15:16:50 +0900893 let node_path = vm_dtbo.locate_overlay_target_path(dtbo_node_path, &dtbo_node)?;
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900894
895 let Some(node) = fdt.node(&node_path)? else { return Ok(None) };
896
Jaewan Kim80ef9fa2024-02-25 16:08:14 +0000897 // Currently can only assign devices backed by physical devices.
Jaewan Kim19b984f2023-12-04 15:16:50 +0900898 let phandle = dtbo_node.get_phandle()?.ok_or(DeviceAssignmentError::InvalidDtbo)?;
Jaewan Kim80ef9fa2024-02-25 16:08:14 +0000899 let Some(physical_device) = physical_devices.get(&phandle) else {
900 // If labeled DT node isn't backed by physical device node, then just return None.
901 // It's not an error because such node can be a dependency of assignable device nodes.
902 return Ok(None);
903 };
Jaewan Kim19b984f2023-12-04 15:16:50 +0900904
905 let reg = parse_node_reg(&node)?;
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +0100906 Self::validate_reg(&reg, &physical_device.reg, hypervisor, granule)?;
Jaewan Kim19b984f2023-12-04 15:16:50 +0900907
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900908 let interrupts = Self::parse_interrupts(&node)?;
Jaewan Kim19b984f2023-12-04 15:16:50 +0900909
910 let iommus = Self::parse_iommus(&node, pviommus)?;
911 Self::validate_iommus(&iommus, &physical_device.iommus, hypervisor)?;
912
Jaewan Kimf8abbb52023-12-12 22:11:39 +0900913 Ok(Some(Self { node_path, reg, interrupts, iommus }))
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900914 }
915
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900916 fn patch(&self, fdt: &mut Fdt, pviommu_phandles: &BTreeMap<PvIommu, Phandle>) -> Result<()> {
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900917 let mut dst = fdt.node_mut(&self.node_path)?.unwrap();
Jaewan Kim52477ae2023-11-21 21:20:52 +0900918 dst.setprop(cstr!("reg"), &to_be_bytes(&self.reg))?;
Pierre-Clément Tosid701a0b2023-11-07 15:38:59 +0000919 dst.setprop(cstr!("interrupts"), &self.interrupts)?;
Jaewan Kima9200492023-11-21 20:45:31 +0900920 let mut iommus = Vec::with_capacity(8 * self.iommus.len());
921 for (pviommu, vsid) in &self.iommus {
922 let phandle = pviommu_phandles.get(pviommu).unwrap();
923 iommus.extend_from_slice(&u32::from(*phandle).to_be_bytes());
924 iommus.extend_from_slice(&vsid.0.to_be_bytes());
925 }
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900926 dst.setprop(cstr!("iommus"), &iommus)?;
927
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900928 Ok(())
929 }
930}
931
Jaewan Kim8f6f4662023-12-12 17:38:47 +0900932#[derive(Debug, Eq, PartialEq)]
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900933pub struct DeviceAssignmentInfo {
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900934 pviommus: BTreeSet<PvIommu>,
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900935 assigned_devices: Vec<AssignedDeviceInfo>,
Jaewan Kim8f6f4662023-12-12 17:38:47 +0900936 vm_dtbo_mask: DeviceTreeMask,
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900937}
938
939impl DeviceAssignmentInfo {
Chris Wailes9d09f572024-01-16 13:31:02 -0800940 const PVIOMMU_COMPATIBLE: &'static CStr = cstr!("pkvm,pviommu");
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900941
942 /// Parses pvIOMMUs in fdt
943 // Note: This will validate pvIOMMU ids' uniqueness, even when unassigned.
944 fn parse_pviommus(fdt: &Fdt) -> Result<BTreeMap<Phandle, PvIommu>> {
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900945 let mut pviommus = BTreeMap::new();
946 for compatible in fdt.compatible_nodes(Self::PVIOMMU_COMPATIBLE)? {
947 let Some(phandle) = compatible.get_phandle()? else {
948 continue; // Skips unreachable pvIOMMU node
949 };
950 let pviommu = PvIommu::parse(&compatible)?;
951 if pviommus.insert(phandle, pviommu).is_some() {
952 return Err(FdtError::BadPhandle.into());
953 }
954 }
955 Ok(pviommus)
956 }
957
Jaewan Kim19b984f2023-12-04 15:16:50 +0900958 fn validate_pviommu_topology(assigned_devices: &[AssignedDeviceInfo]) -> Result<()> {
959 let mut all_iommus = BTreeSet::new();
960 for assigned_device in assigned_devices {
961 for iommu in &assigned_device.iommus {
962 if !all_iommus.insert(iommu) {
963 error!("Unsupported pvIOMMU duplication found, <iommus> = {iommu:?}");
964 return Err(DeviceAssignmentError::UnsupportedPvIommusDuplication);
965 }
966 }
967 }
968 Ok(())
969 }
970
Pierre-Clément Tosie5cca922024-04-30 17:54:08 +0100971 // TODO(b/308694211): Remove this workaround for visibility once using
972 // vmbase::hyp::DeviceAssigningHypervisor for tests.
973 #[cfg(test)]
974 fn parse(
975 fdt: &Fdt,
976 vm_dtbo: &VmDtbo,
977 hypervisor: &dyn DeviceAssigningHypervisor,
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +0100978 granule: usize,
Pierre-Clément Tosie5cca922024-04-30 17:54:08 +0100979 ) -> Result<Option<Self>> {
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +0100980 Self::internal_parse(fdt, vm_dtbo, hypervisor, granule)
Pierre-Clément Tosie5cca922024-04-30 17:54:08 +0100981 }
982
983 #[cfg(not(test))]
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900984 /// Parses fdt and vm_dtbo, and creates new DeviceAssignmentInfo
985 // TODO(b/277993056): Parse __local_fixups__
986 // TODO(b/277993056): Parse __fixups__
Jaewan Kim52477ae2023-11-21 21:20:52 +0900987 pub fn parse(
988 fdt: &Fdt,
989 vm_dtbo: &VmDtbo,
990 hypervisor: &dyn DeviceAssigningHypervisor,
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +0100991 granule: usize,
Jaewan Kim52477ae2023-11-21 21:20:52 +0900992 ) -> Result<Option<Self>> {
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +0100993 Self::internal_parse(fdt, vm_dtbo, hypervisor, granule)
Pierre-Clément Tosie5cca922024-04-30 17:54:08 +0100994 }
995
996 fn internal_parse(
997 fdt: &Fdt,
998 vm_dtbo: &VmDtbo,
999 hypervisor: &dyn DeviceAssigningHypervisor,
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +01001000 granule: usize,
Pierre-Clément Tosie5cca922024-04-30 17:54:08 +01001001 ) -> Result<Option<Self>> {
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001002 let Some(symbols_node) = vm_dtbo.as_ref().symbols()? else {
1003 // /__symbols__ should contain all assignable devices.
1004 // If empty, then nothing can be assigned.
1005 return Ok(None);
1006 };
1007
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001008 let pviommus = Self::parse_pviommus(fdt)?;
1009 let unique_pviommus: BTreeSet<_> = pviommus.values().cloned().collect();
1010 if pviommus.len() != unique_pviommus.len() {
1011 return Err(DeviceAssignmentError::DuplicatedPvIommuIds);
1012 }
1013
Jaewan Kim19b984f2023-12-04 15:16:50 +09001014 let physical_devices = vm_dtbo.parse_physical_devices()?;
1015
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001016 let mut assigned_devices = vec![];
Jaewan Kim8f6f4662023-12-12 17:38:47 +09001017 let mut assigned_device_paths = vec![];
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001018 for symbol_prop in symbols_node.properties()? {
1019 let symbol_prop_value = symbol_prop.value()?;
1020 let dtbo_node_path = CStr::from_bytes_with_nul(symbol_prop_value)
1021 .or(Err(DeviceAssignmentError::InvalidSymbols))?;
Jaewan Kimf8abbb52023-12-12 22:11:39 +09001022 let dtbo_node_path = DtPathTokens::new(dtbo_node_path)?;
1023 if !dtbo_node_path.is_overlayable_node() {
Jaewan Kimc39974e2023-12-02 01:13:30 +09001024 continue;
1025 }
Jaewan Kim19b984f2023-12-04 15:16:50 +09001026 let assigned_device = AssignedDeviceInfo::parse(
1027 fdt,
1028 vm_dtbo,
Jaewan Kimf8abbb52023-12-12 22:11:39 +09001029 &dtbo_node_path,
Jaewan Kim19b984f2023-12-04 15:16:50 +09001030 &physical_devices,
1031 &pviommus,
1032 hypervisor,
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +01001033 granule,
Jaewan Kim19b984f2023-12-04 15:16:50 +09001034 )?;
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001035 if let Some(assigned_device) = assigned_device {
1036 assigned_devices.push(assigned_device);
Jaewan Kim8f6f4662023-12-12 17:38:47 +09001037 assigned_device_paths.push(dtbo_node_path);
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001038 }
1039 }
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001040 if assigned_devices.is_empty() {
1041 return Ok(None);
1042 }
Jaewan Kimc39974e2023-12-02 01:13:30 +09001043
Jaewan Kim19b984f2023-12-04 15:16:50 +09001044 Self::validate_pviommu_topology(&assigned_devices)?;
1045
Jaewan Kim8f6f4662023-12-12 17:38:47 +09001046 let mut vm_dtbo_mask = vm_dtbo.build_mask(assigned_device_paths)?;
1047 vm_dtbo_mask.mask_all(&DtPathTokens::new(cstr!("/__local_fixups__"))?);
1048 vm_dtbo_mask.mask_all(&DtPathTokens::new(cstr!("/__symbols__"))?);
Jaewan Kimc39974e2023-12-02 01:13:30 +09001049
1050 // Note: Any node without __overlay__ will be ignored by fdt_apply_overlay,
1051 // so doesn't need to be filtered.
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001052
Jaewan Kim8f6f4662023-12-12 17:38:47 +09001053 Ok(Some(Self { pviommus: unique_pviommus, assigned_devices, vm_dtbo_mask }))
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001054 }
1055
1056 /// Filters VM DTBO to only contain necessary information for booting pVM
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001057 pub fn filter(&self, vm_dtbo: &mut VmDtbo) -> Result<()> {
1058 let vm_dtbo = vm_dtbo.as_mut();
1059
Jaewan Kim8f6f4662023-12-12 17:38:47 +09001060 // Filter unused references in /__local_fixups__
1061 if let Some(local_fixups) = vm_dtbo.node_mut(cstr!("/__local_fixups__"))? {
1062 filter_with_mask(local_fixups, &self.vm_dtbo_mask)?;
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001063 }
1064
Jaewan Kim8f6f4662023-12-12 17:38:47 +09001065 // Filter unused nodes in rest of tree
1066 let root = vm_dtbo.root_mut();
1067 filter_with_mask(root, &self.vm_dtbo_mask)?;
1068
Jaewan Kim371f6c82024-02-24 01:33:37 +09001069 filter_dangling_symbols(vm_dtbo)
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001070 }
1071
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001072 fn patch_pviommus(&self, fdt: &mut Fdt) -> Result<BTreeMap<PvIommu, Phandle>> {
Pierre-Clément Tosi244efea2024-02-16 14:48:14 +00001073 let mut compatible = fdt.root_mut().next_compatible(Self::PVIOMMU_COMPATIBLE)?;
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001074 let mut pviommu_phandles = BTreeMap::new();
1075
1076 for pviommu in &self.pviommus {
1077 let mut node = compatible.ok_or(DeviceAssignmentError::TooManyPvIommu)?;
1078 let phandle = node.as_node().get_phandle()?.ok_or(DeviceAssignmentError::Internal)?;
1079 node.setprop_inplace(cstr!("id"), &pviommu.id.to_be_bytes())?;
1080 if pviommu_phandles.insert(*pviommu, phandle).is_some() {
1081 return Err(DeviceAssignmentError::Internal);
1082 }
1083 compatible = node.next_compatible(Self::PVIOMMU_COMPATIBLE)?;
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001084 }
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001085
1086 // Filters pre-populated but unassigned pvIOMMUs.
1087 while let Some(filtered_pviommu) = compatible {
1088 compatible = filtered_pviommu.delete_and_next_compatible(Self::PVIOMMU_COMPATIBLE)?;
1089 }
1090
1091 Ok(pviommu_phandles)
1092 }
1093
1094 pub fn patch(&self, fdt: &mut Fdt) -> Result<()> {
1095 let pviommu_phandles = self.patch_pviommus(fdt)?;
1096
1097 // Patches assigned devices
1098 for device in &self.assigned_devices {
1099 device.patch(fdt, &pviommu_phandles)?;
1100 }
1101
Jaewan Kimc730ebf2024-02-22 10:34:55 +09001102 // Removes any dangling references in __symbols__ (e.g. removed pvIOMMUs)
1103 filter_dangling_symbols(fdt)
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001104 }
1105}
1106
Jaewan Kim50246682024-03-11 23:18:54 +09001107/// Cleans device trees not to contain any pre-populated nodes/props for device assignment.
1108pub fn clean(fdt: &mut Fdt) -> Result<()> {
1109 let mut compatible = fdt.root_mut().next_compatible(cstr!("pkvm,pviommu"))?;
1110 // Filters pre-populated
1111 while let Some(filtered_pviommu) = compatible {
1112 compatible = filtered_pviommu.delete_and_next_compatible(cstr!("pkvm,pviommu"))?;
1113 }
1114
1115 // Removes any dangling references in __symbols__ (e.g. removed pvIOMMUs)
1116 filter_dangling_symbols(fdt)
1117}
1118
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001119#[cfg(test)]
Pierre-Clément Tosie5cca922024-04-30 17:54:08 +01001120#[derive(Clone, Copy, Debug)]
1121enum MockHypervisorError {
1122 FailedGetPhysMmioToken,
1123 FailedGetPhysIommuToken,
1124}
1125
1126#[cfg(test)]
1127type MockHypervisorResult<T> = core::result::Result<T, MockHypervisorError>;
1128
1129#[cfg(test)]
1130impl fmt::Display for MockHypervisorError {
1131 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1132 match self {
1133 MockHypervisorError::FailedGetPhysMmioToken => {
1134 write!(f, "Failed to get physical MMIO token")
1135 }
1136 MockHypervisorError::FailedGetPhysIommuToken => {
1137 write!(f, "Failed to get physical IOMMU token")
1138 }
1139 }
1140 }
1141}
1142
1143#[cfg(test)]
1144trait DeviceAssigningHypervisor {
1145 /// Returns MMIO token.
1146 fn get_phys_mmio_token(&self, base_ipa: u64, size: u64) -> MockHypervisorResult<u64>;
1147
1148 /// Returns DMA token as a tuple of (phys_iommu_id, phys_sid).
1149 fn get_phys_iommu_token(&self, pviommu_id: u64, vsid: u64) -> MockHypervisorResult<(u64, u64)>;
1150}
1151
1152#[cfg(test)]
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001153mod tests {
1154 use super::*;
Jaewan Kim52477ae2023-11-21 21:20:52 +09001155 use alloc::collections::{BTreeMap, BTreeSet};
Jaewan Kim8f6f4662023-12-12 17:38:47 +09001156 use dts::Dts;
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001157 use std::fs;
Jaewan Kim8f6f4662023-12-12 17:38:47 +09001158 use std::path::Path;
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001159
1160 const VM_DTBO_FILE_PATH: &str = "test_pvmfw_devices_vm_dtbo.dtbo";
1161 const VM_DTBO_WITHOUT_SYMBOLS_FILE_PATH: &str =
1162 "test_pvmfw_devices_vm_dtbo_without_symbols.dtbo";
Jaewan Kim19b984f2023-12-04 15:16:50 +09001163 const VM_DTBO_WITH_DUPLICATED_IOMMUS_FILE_PATH: &str =
1164 "test_pvmfw_devices_vm_dtbo_with_duplicated_iommus.dtbo";
Jaewan Kim8f6f4662023-12-12 17:38:47 +09001165 const VM_DTBO_WITH_DEPENDENCIES_FILE_PATH: &str =
1166 "test_pvmfw_devices_vm_dtbo_with_dependencies.dtbo";
Jaewan Kima67e36a2023-11-29 16:50:23 +09001167 const FDT_WITHOUT_IOMMUS_FILE_PATH: &str = "test_pvmfw_devices_without_iommus.dtb";
Jaewan Kim52477ae2023-11-21 21:20:52 +09001168 const FDT_WITHOUT_DEVICE_FILE_PATH: &str = "test_pvmfw_devices_without_device.dtb";
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001169 const FDT_FILE_PATH: &str = "test_pvmfw_devices_with_rng.dtb";
Pierre-Clément Tosi49e26ce2024-03-12 16:31:50 +00001170 const FDT_WITH_DEVICE_OVERLAPPING_PVMFW: &str = "test_pvmfw_devices_overlapping_pvmfw.dtb";
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001171 const FDT_WITH_MULTIPLE_DEVICES_IOMMUS_FILE_PATH: &str =
1172 "test_pvmfw_devices_with_multiple_devices_iommus.dtb";
1173 const FDT_WITH_IOMMU_SHARING: &str = "test_pvmfw_devices_with_iommu_sharing.dtb";
1174 const FDT_WITH_IOMMU_ID_CONFLICT: &str = "test_pvmfw_devices_with_iommu_id_conflict.dtb";
Jaewan Kim19b984f2023-12-04 15:16:50 +09001175 const FDT_WITH_DUPLICATED_PVIOMMUS_FILE_PATH: &str =
1176 "test_pvmfw_devices_with_duplicated_pviommus.dtb";
1177 const FDT_WITH_MULTIPLE_REG_IOMMU_FILE_PATH: &str =
1178 "test_pvmfw_devices_with_multiple_reg_iommus.dtb";
Jaewan Kim8f6f4662023-12-12 17:38:47 +09001179 const FDT_WITH_DEPENDENCY_FILE_PATH: &str = "test_pvmfw_devices_with_dependency.dtb";
1180 const FDT_WITH_MULTIPLE_DEPENDENCIES_FILE_PATH: &str =
1181 "test_pvmfw_devices_with_multiple_dependencies.dtb";
1182 const FDT_WITH_DEPENDENCY_LOOP_FILE_PATH: &str = "test_pvmfw_devices_with_dependency_loop.dtb";
1183
1184 const EXPECTED_FDT_WITH_DEPENDENCY_FILE_PATH: &str = "expected_dt_with_dependency.dtb";
1185 const EXPECTED_FDT_WITH_MULTIPLE_DEPENDENCIES_FILE_PATH: &str =
1186 "expected_dt_with_multiple_dependencies.dtb";
1187 const EXPECTED_FDT_WITH_DEPENDENCY_LOOP_FILE_PATH: &str =
1188 "expected_dt_with_dependency_loop.dtb";
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001189
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +01001190 // TODO(b/308694211): Use vmbase::SIZE_4KB.
1191 const SIZE_4KB: usize = 4 << 10;
1192
Jaewan Kim52477ae2023-11-21 21:20:52 +09001193 #[derive(Debug, Default)]
1194 struct MockHypervisor {
1195 mmio_tokens: BTreeMap<(u64, u64), u64>,
1196 iommu_tokens: BTreeMap<(u64, u64), (u64, u64)>,
1197 }
1198
Pierre-Clément Tosi5ce6c6f2024-10-29 10:53:07 +00001199 impl MockHypervisor {
1200 // TODO(ptosi): Improve these tests to cover multi-page devices.
1201 fn get_mmio_token(&self, addr: u64) -> Option<&u64> {
1202 // We currently only have single (or sub-) page MMIO test data so can ignore sizes.
1203 let key = self.mmio_tokens.keys().find(|(virt, _)| *virt == addr)?;
1204 self.mmio_tokens.get(key)
1205 }
1206 }
1207
Jaewan Kim52477ae2023-11-21 21:20:52 +09001208 impl DeviceAssigningHypervisor for MockHypervisor {
Pierre-Clément Tosi5ce6c6f2024-10-29 10:53:07 +00001209 fn get_phys_mmio_token(&self, base_ipa: u64, _size: u64) -> MockHypervisorResult<u64> {
1210 let token = self.get_mmio_token(base_ipa);
Pierre-Clément Tosie5cca922024-04-30 17:54:08 +01001211
1212 Ok(*token.ok_or(MockHypervisorError::FailedGetPhysMmioToken)?)
Jaewan Kim52477ae2023-11-21 21:20:52 +09001213 }
1214
Pierre-Clément Tosie5cca922024-04-30 17:54:08 +01001215 fn get_phys_iommu_token(
1216 &self,
1217 pviommu_id: u64,
1218 vsid: u64,
1219 ) -> MockHypervisorResult<(u64, u64)> {
1220 let token = self.iommu_tokens.get(&(pviommu_id, vsid));
1221
1222 Ok(*token.ok_or(MockHypervisorError::FailedGetPhysIommuToken)?)
Jaewan Kim52477ae2023-11-21 21:20:52 +09001223 }
1224 }
1225
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001226 #[derive(Debug, Eq, PartialEq)]
1227 struct AssignedDeviceNode {
1228 path: CString,
1229 reg: Vec<u8>,
1230 interrupts: Vec<u8>,
Jaewan Kima67e36a2023-11-29 16:50:23 +09001231 iommus: Vec<u32>, // pvIOMMU id and vSID
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001232 }
1233
1234 impl AssignedDeviceNode {
1235 fn parse(fdt: &Fdt, path: &CStr) -> Result<Self> {
1236 let Some(node) = fdt.node(path)? else {
1237 return Err(FdtError::NotFound.into());
1238 };
1239
Jaewan Kim19b984f2023-12-04 15:16:50 +09001240 let reg = node.getprop(cstr!("reg"))?.ok_or(DeviceAssignmentError::MalformedReg)?;
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001241 let interrupts = node
1242 .getprop(cstr!("interrupts"))?
1243 .ok_or(DeviceAssignmentError::InvalidInterrupts)?;
1244 let mut iommus = vec![];
Jaewan Kima9200492023-11-21 20:45:31 +09001245 if let Some(mut cells) = node.getprop_cells(cstr!("iommus"))? {
1246 while let Some(pviommu_id) = cells.next() {
1247 // pvIOMMU id
1248 let phandle = Phandle::try_from(pviommu_id)?;
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001249 let pviommu = fdt
1250 .node_with_phandle(phandle)?
Jaewan Kim19b984f2023-12-04 15:16:50 +09001251 .ok_or(DeviceAssignmentError::MalformedIommus)?;
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001252 let compatible = pviommu.getprop_str(cstr!("compatible"));
1253 if compatible != Ok(Some(cstr!("pkvm,pviommu"))) {
Jaewan Kim19b984f2023-12-04 15:16:50 +09001254 return Err(DeviceAssignmentError::MalformedIommus);
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001255 }
1256 let id = pviommu
1257 .getprop_u32(cstr!("id"))?
Jaewan Kim19b984f2023-12-04 15:16:50 +09001258 .ok_or(DeviceAssignmentError::MalformedIommus)?;
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001259 iommus.push(id);
Jaewan Kima9200492023-11-21 20:45:31 +09001260
1261 // vSID
1262 let Some(vsid) = cells.next() else {
Jaewan Kim19b984f2023-12-04 15:16:50 +09001263 return Err(DeviceAssignmentError::MalformedIommus);
Jaewan Kima9200492023-11-21 20:45:31 +09001264 };
1265 iommus.push(vsid);
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001266 }
1267 }
1268 Ok(Self { path: path.into(), reg: reg.into(), interrupts: interrupts.into(), iommus })
1269 }
1270 }
1271
1272 fn collect_pviommus(fdt: &Fdt) -> Result<Vec<u32>> {
1273 let mut pviommus = BTreeSet::new();
1274 for pviommu in fdt.compatible_nodes(cstr!("pkvm,pviommu"))? {
1275 if let Ok(Some(id)) = pviommu.getprop_u32(cstr!("id")) {
1276 pviommus.insert(id);
1277 }
1278 }
1279 Ok(pviommus.iter().cloned().collect())
1280 }
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001281
1282 fn into_fdt_prop(native_bytes: Vec<u32>) -> Vec<u8> {
1283 let mut v = Vec::with_capacity(native_bytes.len() * 4);
1284 for byte in native_bytes {
1285 v.extend_from_slice(&byte.to_be_bytes());
1286 }
1287 v
1288 }
1289
Jaewan Kim52477ae2023-11-21 21:20:52 +09001290 impl From<[u64; 2]> for DeviceReg {
1291 fn from(fdt_cells: [u64; 2]) -> Self {
1292 DeviceReg { addr: fdt_cells[0], size: fdt_cells[1] }
1293 }
1294 }
1295
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +01001296 // TODO(ptosi): Add tests with varying HYP_GRANULE values.
1297
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001298 #[test]
1299 fn device_info_new_without_symbols() {
1300 let mut fdt_data = fs::read(FDT_FILE_PATH).unwrap();
1301 let mut vm_dtbo_data = fs::read(VM_DTBO_WITHOUT_SYMBOLS_FILE_PATH).unwrap();
1302 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1303 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1304
Jaewan Kim52477ae2023-11-21 21:20:52 +09001305 let hypervisor: MockHypervisor = Default::default();
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +01001306 const HYP_GRANULE: usize = SIZE_4KB;
1307 let device_info =
1308 DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor, HYP_GRANULE).unwrap();
Jaewan Kim52477ae2023-11-21 21:20:52 +09001309 assert_eq!(device_info, None);
1310 }
1311
1312 #[test]
1313 fn device_info_new_without_device() {
1314 let mut fdt_data = fs::read(FDT_WITHOUT_DEVICE_FILE_PATH).unwrap();
1315 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1316 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1317 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1318
1319 let hypervisor: MockHypervisor = Default::default();
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +01001320 const HYP_GRANULE: usize = SIZE_4KB;
1321 let device_info =
1322 DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor, HYP_GRANULE).unwrap();
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001323 assert_eq!(device_info, None);
1324 }
1325
1326 #[test]
Jaewan Kima67e36a2023-11-29 16:50:23 +09001327 fn device_info_assigned_info_without_iommus() {
1328 let mut fdt_data = fs::read(FDT_WITHOUT_IOMMUS_FILE_PATH).unwrap();
1329 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1330 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1331 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1332
Jaewan Kim52477ae2023-11-21 21:20:52 +09001333 let hypervisor = MockHypervisor {
1334 mmio_tokens: [((0x9, 0xFF), 0x300)].into(),
1335 iommu_tokens: BTreeMap::new(),
1336 };
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +01001337 const HYP_GRANULE: usize = SIZE_4KB;
1338 let device_info =
1339 DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor, HYP_GRANULE).unwrap().unwrap();
Jaewan Kima67e36a2023-11-29 16:50:23 +09001340
1341 let expected = [AssignedDeviceInfo {
Jaewan Kimc39974e2023-12-02 01:13:30 +09001342 node_path: CString::new("/bus0/backlight").unwrap(),
Jaewan Kim52477ae2023-11-21 21:20:52 +09001343 reg: vec![[0x9, 0xFF].into()],
Jaewan Kima67e36a2023-11-29 16:50:23 +09001344 interrupts: into_fdt_prop(vec![0x0, 0xF, 0x4]),
1345 iommus: vec![],
1346 }];
1347
1348 assert_eq!(device_info.assigned_devices, expected);
1349 }
1350
1351 #[test]
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001352 fn device_info_assigned_info() {
1353 let mut fdt_data = fs::read(FDT_FILE_PATH).unwrap();
1354 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1355 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1356 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1357
Jaewan Kim52477ae2023-11-21 21:20:52 +09001358 let hypervisor = MockHypervisor {
1359 mmio_tokens: [((0x9, 0xFF), 0x12F00000)].into(),
1360 iommu_tokens: [((0x4, 0xFF0), (0x12E40000, 0x3))].into(),
1361 };
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +01001362 const HYP_GRANULE: usize = SIZE_4KB;
1363 let device_info =
1364 DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor, HYP_GRANULE).unwrap().unwrap();
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001365
1366 let expected = [AssignedDeviceInfo {
1367 node_path: CString::new("/rng").unwrap(),
Jaewan Kim52477ae2023-11-21 21:20:52 +09001368 reg: vec![[0x9, 0xFF].into()],
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001369 interrupts: into_fdt_prop(vec![0x0, 0xF, 0x4]),
Jaewan Kima67e36a2023-11-29 16:50:23 +09001370 iommus: vec![(PvIommu { id: 0x4 }, Vsid(0xFF0))],
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001371 }];
1372
1373 assert_eq!(device_info.assigned_devices, expected);
1374 }
1375
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001376 #[test]
1377 fn device_info_filter() {
1378 let mut fdt_data = fs::read(FDT_FILE_PATH).unwrap();
1379 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1380 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1381 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1382
Jaewan Kim52477ae2023-11-21 21:20:52 +09001383 let hypervisor = MockHypervisor {
1384 mmio_tokens: [((0x9, 0xFF), 0x12F00000)].into(),
1385 iommu_tokens: [((0x4, 0xFF0), (0x12E40000, 0x3))].into(),
1386 };
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +01001387 const HYP_GRANULE: usize = SIZE_4KB;
1388 let device_info =
1389 DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor, HYP_GRANULE).unwrap().unwrap();
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001390 device_info.filter(vm_dtbo).unwrap();
1391
1392 let vm_dtbo = vm_dtbo.as_mut();
1393
Jaewan Kim371f6c82024-02-24 01:33:37 +09001394 let symbols = vm_dtbo.symbols().unwrap().unwrap();
1395
Jaewan Kima232ed02024-02-25 16:08:14 +00001396 let rng = vm_dtbo.node(cstr!("/fragment@0/__overlay__/rng")).unwrap();
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001397 assert_ne!(rng, None);
Jaewan Kim371f6c82024-02-24 01:33:37 +09001398 let rng_symbol = symbols.getprop_str(cstr!("rng")).unwrap();
Jaewan Kima232ed02024-02-25 16:08:14 +00001399 assert_eq!(Some(cstr!("/fragment@0/__overlay__/rng")), rng_symbol);
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001400
Jaewan Kima232ed02024-02-25 16:08:14 +00001401 let light = vm_dtbo.node(cstr!("/fragment@0/__overlay__/light")).unwrap();
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001402 assert_eq!(light, None);
Jaewan Kim371f6c82024-02-24 01:33:37 +09001403 let light_symbol = symbols.getprop_str(cstr!("light")).unwrap();
1404 assert_eq!(None, light_symbol);
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001405
Jaewan Kima232ed02024-02-25 16:08:14 +00001406 let led = vm_dtbo.node(cstr!("/fragment@0/__overlay__/led")).unwrap();
Jaewan Kima67e36a2023-11-29 16:50:23 +09001407 assert_eq!(led, None);
Jaewan Kim371f6c82024-02-24 01:33:37 +09001408 let led_symbol = symbols.getprop_str(cstr!("led")).unwrap();
1409 assert_eq!(None, led_symbol);
Jaewan Kima67e36a2023-11-29 16:50:23 +09001410
Jaewan Kima232ed02024-02-25 16:08:14 +00001411 let backlight = vm_dtbo.node(cstr!("/fragment@0/__overlay__/bus0/backlight")).unwrap();
Jaewan Kima67e36a2023-11-29 16:50:23 +09001412 assert_eq!(backlight, None);
Jaewan Kim371f6c82024-02-24 01:33:37 +09001413 let backlight_symbol = symbols.getprop_str(cstr!("backlight")).unwrap();
1414 assert_eq!(None, backlight_symbol);
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001415 }
1416
1417 #[test]
1418 fn device_info_patch() {
Jaewan Kima67e36a2023-11-29 16:50:23 +09001419 let mut fdt_data = fs::read(FDT_WITHOUT_IOMMUS_FILE_PATH).unwrap();
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001420 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1421 let mut data = vec![0_u8; fdt_data.len() + vm_dtbo_data.len()];
1422 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1423 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1424 let platform_dt = Fdt::create_empty_tree(data.as_mut_slice()).unwrap();
1425
Jaewan Kim52477ae2023-11-21 21:20:52 +09001426 let hypervisor = MockHypervisor {
1427 mmio_tokens: [((0x9, 0xFF), 0x300)].into(),
1428 iommu_tokens: BTreeMap::new(),
1429 };
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +01001430 const HYP_GRANULE: usize = SIZE_4KB;
1431 let device_info =
1432 DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor, HYP_GRANULE).unwrap().unwrap();
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001433 device_info.filter(vm_dtbo).unwrap();
1434
1435 // SAFETY: Damaged VM DTBO wouldn't be used after this unsafe block.
1436 unsafe {
1437 platform_dt.apply_overlay(vm_dtbo.as_mut()).unwrap();
1438 }
Jaewan Kim0bd637d2023-11-10 13:09:41 +09001439 device_info.patch(platform_dt).unwrap();
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001440
Jaewan Kimc39974e2023-12-02 01:13:30 +09001441 let rng_node = platform_dt.node(cstr!("/bus0/backlight")).unwrap().unwrap();
1442 let phandle = rng_node.getprop_u32(cstr!("phandle")).unwrap();
1443 assert_ne!(None, phandle);
1444
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001445 // Note: Intentionally not using AssignedDeviceNode for matching all props.
Jaewan Kim0bd637d2023-11-10 13:09:41 +09001446 type FdtResult<T> = libfdt::Result<T>;
1447 let expected: Vec<(FdtResult<&CStr>, FdtResult<Vec<u8>>)> = vec![
Jaewan Kima67e36a2023-11-29 16:50:23 +09001448 (Ok(cstr!("android,backlight,ignore-gctrl-reset")), Ok(Vec::new())),
1449 (Ok(cstr!("compatible")), Ok(Vec::from(*b"android,backlight\0"))),
Jaewan Kim0bd637d2023-11-10 13:09:41 +09001450 (Ok(cstr!("interrupts")), Ok(into_fdt_prop(vec![0x0, 0xF, 0x4]))),
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001451 (Ok(cstr!("iommus")), Ok(Vec::new())),
Jaewan Kimc39974e2023-12-02 01:13:30 +09001452 (Ok(cstr!("phandle")), Ok(into_fdt_prop(vec![phandle.unwrap()]))),
Jaewan Kim0bd637d2023-11-10 13:09:41 +09001453 (Ok(cstr!("reg")), Ok(into_fdt_prop(vec![0x0, 0x9, 0x0, 0xFF]))),
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001454 ];
1455
Jaewan Kim0bd637d2023-11-10 13:09:41 +09001456 let mut properties: Vec<_> = rng_node
1457 .properties()
1458 .unwrap()
1459 .map(|prop| (prop.name(), prop.value().map(|x| x.into())))
1460 .collect();
1461 properties.sort_by(|a, b| {
1462 let lhs = a.0.unwrap_or_default();
1463 let rhs = b.0.unwrap_or_default();
1464 lhs.partial_cmp(rhs).unwrap()
1465 });
1466
1467 assert_eq!(properties, expected);
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001468 }
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001469
1470 #[test]
Jaewan Kimc730ebf2024-02-22 10:34:55 +09001471 fn device_info_patch_no_pviommus() {
1472 let mut fdt_data = fs::read(FDT_WITHOUT_IOMMUS_FILE_PATH).unwrap();
1473 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1474 let mut data = vec![0_u8; fdt_data.len() + vm_dtbo_data.len()];
1475 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1476 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1477 let platform_dt = Fdt::create_empty_tree(data.as_mut_slice()).unwrap();
1478
1479 let hypervisor = MockHypervisor {
1480 mmio_tokens: [((0x9, 0xFF), 0x300)].into(),
1481 iommu_tokens: BTreeMap::new(),
1482 };
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +01001483 const HYP_GRANULE: usize = SIZE_4KB;
1484 let device_info =
1485 DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor, HYP_GRANULE).unwrap().unwrap();
Jaewan Kimc730ebf2024-02-22 10:34:55 +09001486 device_info.filter(vm_dtbo).unwrap();
1487
1488 // SAFETY: Damaged VM DTBO wouldn't be used after this unsafe block.
1489 unsafe {
1490 platform_dt.apply_overlay(vm_dtbo.as_mut()).unwrap();
1491 }
1492 device_info.patch(platform_dt).unwrap();
1493
1494 let compatible = platform_dt.root().next_compatible(cstr!("pkvm,pviommu")).unwrap();
1495 assert_eq!(None, compatible);
1496
1497 if let Some(symbols) = platform_dt.symbols().unwrap() {
1498 for prop in symbols.properties().unwrap() {
1499 let path = CStr::from_bytes_with_nul(prop.value().unwrap()).unwrap();
1500 assert_ne!(None, platform_dt.node(path).unwrap());
1501 }
1502 }
1503 }
1504
1505 #[test]
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001506 fn device_info_overlay_iommu() {
Jaewan Kima67e36a2023-11-29 16:50:23 +09001507 let mut fdt_data = fs::read(FDT_FILE_PATH).unwrap();
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001508 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1509 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1510 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1511 let mut platform_dt_data = pvmfw_fdt_template::RAW.to_vec();
1512 platform_dt_data.resize(pvmfw_fdt_template::RAW.len() * 2, 0);
1513 let platform_dt = Fdt::from_mut_slice(&mut platform_dt_data).unwrap();
1514 platform_dt.unpack().unwrap();
1515
Jaewan Kim52477ae2023-11-21 21:20:52 +09001516 let hypervisor = MockHypervisor {
1517 mmio_tokens: [((0x9, 0xFF), 0x12F00000)].into(),
1518 iommu_tokens: [((0x4, 0xFF0), (0x12E40000, 0x3))].into(),
1519 };
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +01001520 const HYP_GRANULE: usize = SIZE_4KB;
1521 let device_info =
1522 DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor, HYP_GRANULE).unwrap().unwrap();
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001523 device_info.filter(vm_dtbo).unwrap();
1524
1525 // SAFETY: Damaged VM DTBO wouldn't be used after this unsafe block.
1526 unsafe {
1527 platform_dt.apply_overlay(vm_dtbo.as_mut()).unwrap();
1528 }
1529 device_info.patch(platform_dt).unwrap();
1530
1531 let expected = AssignedDeviceNode {
1532 path: CString::new("/rng").unwrap(),
1533 reg: into_fdt_prop(vec![0x0, 0x9, 0x0, 0xFF]),
1534 interrupts: into_fdt_prop(vec![0x0, 0xF, 0x4]),
Jaewan Kima9200492023-11-21 20:45:31 +09001535 iommus: vec![0x4, 0xFF0],
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001536 };
1537
1538 let node = AssignedDeviceNode::parse(platform_dt, &expected.path);
1539 assert_eq!(node, Ok(expected));
1540
1541 let pviommus = collect_pviommus(platform_dt);
1542 assert_eq!(pviommus, Ok(vec![0x4]));
1543 }
1544
1545 #[test]
1546 fn device_info_multiple_devices_iommus() {
1547 let mut fdt_data = fs::read(FDT_WITH_MULTIPLE_DEVICES_IOMMUS_FILE_PATH).unwrap();
1548 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1549 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1550 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1551 let mut platform_dt_data = pvmfw_fdt_template::RAW.to_vec();
1552 platform_dt_data.resize(pvmfw_fdt_template::RAW.len() * 2, 0);
1553 let platform_dt = Fdt::from_mut_slice(&mut platform_dt_data).unwrap();
1554 platform_dt.unpack().unwrap();
1555
Jaewan Kim52477ae2023-11-21 21:20:52 +09001556 let hypervisor = MockHypervisor {
1557 mmio_tokens: [
1558 ((0x9, 0xFF), 0x12F00000),
Jaewan Kim19b984f2023-12-04 15:16:50 +09001559 ((0x10000, 0x1000), 0xF00000),
1560 ((0x20000, 0x1000), 0xF10000),
Jaewan Kim52477ae2023-11-21 21:20:52 +09001561 ]
1562 .into(),
1563 iommu_tokens: [
1564 ((0x4, 0xFF0), (0x12E40000, 3)),
1565 ((0x40, 0xFFA), (0x40000, 0x4)),
1566 ((0x50, 0xFFB), (0x50000, 0x5)),
1567 ]
1568 .into(),
1569 };
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +01001570 const HYP_GRANULE: usize = SIZE_4KB;
1571 let device_info =
1572 DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor, HYP_GRANULE).unwrap().unwrap();
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001573 device_info.filter(vm_dtbo).unwrap();
1574
1575 // SAFETY: Damaged VM DTBO wouldn't be used after this unsafe block.
1576 unsafe {
1577 platform_dt.apply_overlay(vm_dtbo.as_mut()).unwrap();
1578 }
1579 device_info.patch(platform_dt).unwrap();
1580
1581 let expected_devices = [
1582 AssignedDeviceNode {
1583 path: CString::new("/rng").unwrap(),
1584 reg: into_fdt_prop(vec![0x0, 0x9, 0x0, 0xFF]),
1585 interrupts: into_fdt_prop(vec![0x0, 0xF, 0x4]),
Jaewan Kima67e36a2023-11-29 16:50:23 +09001586 iommus: vec![0x4, 0xFF0],
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001587 },
1588 AssignedDeviceNode {
1589 path: CString::new("/light").unwrap(),
Jaewan Kim19b984f2023-12-04 15:16:50 +09001590 reg: into_fdt_prop(vec![0x0, 0x10000, 0x0, 0x1000, 0x0, 0x20000, 0x0, 0x1000]),
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001591 interrupts: into_fdt_prop(vec![0x0, 0xF, 0x5]),
Jaewan Kima67e36a2023-11-29 16:50:23 +09001592 iommus: vec![0x40, 0xFFA, 0x50, 0xFFB],
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001593 },
1594 ];
1595
1596 for expected in expected_devices {
1597 let node = AssignedDeviceNode::parse(platform_dt, &expected.path);
1598 assert_eq!(node, Ok(expected));
1599 }
1600 let pviommus = collect_pviommus(platform_dt);
Jaewan Kima67e36a2023-11-29 16:50:23 +09001601 assert_eq!(pviommus, Ok(vec![0x4, 0x40, 0x50]));
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001602 }
1603
1604 #[test]
1605 fn device_info_iommu_sharing() {
1606 let mut fdt_data = fs::read(FDT_WITH_IOMMU_SHARING).unwrap();
1607 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1608 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1609 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1610 let mut platform_dt_data = pvmfw_fdt_template::RAW.to_vec();
1611 platform_dt_data.resize(pvmfw_fdt_template::RAW.len() * 2, 0);
1612 let platform_dt = Fdt::from_mut_slice(&mut platform_dt_data).unwrap();
1613 platform_dt.unpack().unwrap();
1614
Jaewan Kim52477ae2023-11-21 21:20:52 +09001615 let hypervisor = MockHypervisor {
Jaewan Kim19b984f2023-12-04 15:16:50 +09001616 mmio_tokens: [((0x9, 0xFF), 0x12F00000), ((0x1000, 0x9), 0x12000000)].into(),
1617 iommu_tokens: [((0x4, 0xFF0), (0x12E40000, 3)), ((0x4, 0xFF1), (0x12E40000, 9))].into(),
Jaewan Kim52477ae2023-11-21 21:20:52 +09001618 };
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +01001619 const HYP_GRANULE: usize = SIZE_4KB;
1620 let device_info =
1621 DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor, HYP_GRANULE).unwrap().unwrap();
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001622 device_info.filter(vm_dtbo).unwrap();
1623
1624 // SAFETY: Damaged VM DTBO wouldn't be used after this unsafe block.
1625 unsafe {
1626 platform_dt.apply_overlay(vm_dtbo.as_mut()).unwrap();
1627 }
1628 device_info.patch(platform_dt).unwrap();
1629
1630 let expected_devices = [
1631 AssignedDeviceNode {
1632 path: CString::new("/rng").unwrap(),
1633 reg: into_fdt_prop(vec![0x0, 0x9, 0x0, 0xFF]),
1634 interrupts: into_fdt_prop(vec![0x0, 0xF, 0x4]),
Jaewan Kima67e36a2023-11-29 16:50:23 +09001635 iommus: vec![0x4, 0xFF0],
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001636 },
1637 AssignedDeviceNode {
Jaewan Kima67e36a2023-11-29 16:50:23 +09001638 path: CString::new("/led").unwrap(),
Jaewan Kim19b984f2023-12-04 15:16:50 +09001639 reg: into_fdt_prop(vec![0x0, 0x1000, 0x0, 0x9]),
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001640 interrupts: into_fdt_prop(vec![0x0, 0xF, 0x5]),
Jaewan Kim19b984f2023-12-04 15:16:50 +09001641 iommus: vec![0x4, 0xFF1],
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001642 },
1643 ];
1644
1645 for expected in expected_devices {
1646 let node = AssignedDeviceNode::parse(platform_dt, &expected.path);
1647 assert_eq!(node, Ok(expected));
1648 }
1649
1650 let pviommus = collect_pviommus(platform_dt);
Jaewan Kima67e36a2023-11-29 16:50:23 +09001651 assert_eq!(pviommus, Ok(vec![0x4]));
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001652 }
1653
1654 #[test]
1655 fn device_info_iommu_id_conflict() {
1656 let mut fdt_data = fs::read(FDT_WITH_IOMMU_ID_CONFLICT).unwrap();
1657 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1658 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1659 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1660
Jaewan Kim52477ae2023-11-21 21:20:52 +09001661 let hypervisor = MockHypervisor {
Jaewan Kim19b984f2023-12-04 15:16:50 +09001662 mmio_tokens: [((0x9, 0xFF), 0x300)].into(),
Jaewan Kim52477ae2023-11-21 21:20:52 +09001663 iommu_tokens: [((0x4, 0xFF0), (0x12E40000, 0x3))].into(),
1664 };
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +01001665 const HYP_GRANULE: usize = SIZE_4KB;
1666 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor, HYP_GRANULE);
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001667
1668 assert_eq!(device_info, Err(DeviceAssignmentError::DuplicatedPvIommuIds));
1669 }
Jaewan Kim52477ae2023-11-21 21:20:52 +09001670
1671 #[test]
1672 fn device_info_invalid_reg() {
1673 let mut fdt_data = fs::read(FDT_FILE_PATH).unwrap();
1674 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1675 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1676 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1677
1678 let hypervisor = MockHypervisor {
1679 mmio_tokens: BTreeMap::new(),
1680 iommu_tokens: [((0x4, 0xFF0), (0x12E40000, 0x3))].into(),
1681 };
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +01001682 const HYP_GRANULE: usize = SIZE_4KB;
1683 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor, HYP_GRANULE);
Jaewan Kim52477ae2023-11-21 21:20:52 +09001684
Pierre-Clément Tosif4d8a0d2024-10-25 17:14:15 +01001685 assert_eq!(device_info, Err(DeviceAssignmentError::InvalidReg(0x9)));
Jaewan Kim52477ae2023-11-21 21:20:52 +09001686 }
1687
1688 #[test]
Jaewan Kim19b984f2023-12-04 15:16:50 +09001689 fn device_info_invalid_reg_out_of_order() {
1690 let mut fdt_data = fs::read(FDT_WITH_MULTIPLE_REG_IOMMU_FILE_PATH).unwrap();
1691 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1692 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1693 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1694
1695 let hypervisor = MockHypervisor {
1696 mmio_tokens: [((0xF000, 0x1000), 0xF10000), ((0xF100, 0x1000), 0xF00000)].into(),
1697 iommu_tokens: [((0xFF0, 0xF0), (0x40000, 0x4)), ((0xFF1, 0xF1), (0x50000, 0x5))].into(),
1698 };
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +01001699 const HYP_GRANULE: usize = SIZE_4KB;
1700 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor, HYP_GRANULE);
Jaewan Kim19b984f2023-12-04 15:16:50 +09001701
Pierre-Clément Tosif4d8a0d2024-10-25 17:14:15 +01001702 assert_eq!(device_info, Err(DeviceAssignmentError::InvalidRegToken(0xF10000, 0xF00000)));
Jaewan Kim19b984f2023-12-04 15:16:50 +09001703 }
1704
1705 #[test]
Jaewan Kim52477ae2023-11-21 21:20:52 +09001706 fn device_info_invalid_iommus() {
1707 let mut fdt_data = fs::read(FDT_FILE_PATH).unwrap();
1708 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1709 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1710 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1711
1712 let hypervisor = MockHypervisor {
1713 mmio_tokens: [((0x9, 0xFF), 0x12F00000)].into(),
1714 iommu_tokens: BTreeMap::new(),
1715 };
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +01001716 const HYP_GRANULE: usize = SIZE_4KB;
1717 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor, HYP_GRANULE);
Jaewan Kim52477ae2023-11-21 21:20:52 +09001718
1719 assert_eq!(device_info, Err(DeviceAssignmentError::InvalidIommus));
1720 }
Jaewan Kim19b984f2023-12-04 15:16:50 +09001721
1722 #[test]
1723 fn device_info_duplicated_pv_iommus() {
1724 let mut fdt_data = fs::read(FDT_WITH_DUPLICATED_PVIOMMUS_FILE_PATH).unwrap();
1725 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1726 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1727 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1728
1729 let hypervisor = MockHypervisor {
1730 mmio_tokens: [((0x10000, 0x1000), 0xF00000), ((0x20000, 0xFF), 0xF10000)].into(),
1731 iommu_tokens: [((0xFF, 0xF), (0x40000, 0x4))].into(),
1732 };
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +01001733 const HYP_GRANULE: usize = SIZE_4KB;
1734 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor, HYP_GRANULE);
Jaewan Kim19b984f2023-12-04 15:16:50 +09001735
1736 assert_eq!(device_info, Err(DeviceAssignmentError::DuplicatedPvIommuIds));
1737 }
1738
1739 #[test]
1740 fn device_info_duplicated_iommus() {
1741 let mut fdt_data = fs::read(FDT_FILE_PATH).unwrap();
1742 let mut vm_dtbo_data = fs::read(VM_DTBO_WITH_DUPLICATED_IOMMUS_FILE_PATH).unwrap();
1743 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1744 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1745
1746 let hypervisor = MockHypervisor {
1747 mmio_tokens: [((0x10000, 0x1000), 0xF00000), ((0x20000, 0xFF), 0xF10000)].into(),
1748 iommu_tokens: [((0xFF, 0xF), (0x40000, 0x4))].into(),
1749 };
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +01001750 const HYP_GRANULE: usize = SIZE_4KB;
1751 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor, HYP_GRANULE);
Jaewan Kim19b984f2023-12-04 15:16:50 +09001752
1753 assert_eq!(device_info, Err(DeviceAssignmentError::UnsupportedIommusDuplication));
1754 }
1755
1756 #[test]
1757 fn device_info_duplicated_iommu_mapping() {
1758 let mut fdt_data = fs::read(FDT_WITH_MULTIPLE_REG_IOMMU_FILE_PATH).unwrap();
1759 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1760 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1761 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1762
1763 let hypervisor = MockHypervisor {
1764 mmio_tokens: [((0xF000, 0x1000), 0xF00000), ((0xF100, 0x1000), 0xF10000)].into(),
1765 iommu_tokens: [((0xFF0, 0xF0), (0x40000, 0x4)), ((0xFF1, 0xF1), (0x40000, 0x4))].into(),
1766 };
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +01001767 const HYP_GRANULE: usize = SIZE_4KB;
1768 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor, HYP_GRANULE);
Jaewan Kim19b984f2023-12-04 15:16:50 +09001769
1770 assert_eq!(device_info, Err(DeviceAssignmentError::InvalidIommus));
1771 }
Jaewan Kim50246682024-03-11 23:18:54 +09001772
1773 #[test]
Pierre-Clément Tosi49e26ce2024-03-12 16:31:50 +00001774 fn device_info_overlaps_pvmfw() {
1775 let mut fdt_data = fs::read(FDT_WITH_DEVICE_OVERLAPPING_PVMFW).unwrap();
1776 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1777 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1778 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1779
1780 let hypervisor = MockHypervisor {
1781 mmio_tokens: [((0x7fee0000, 0x1000), 0xF00000)].into(),
1782 iommu_tokens: [((0xFF, 0xF), (0x40000, 0x4))].into(),
1783 };
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +01001784 const HYP_GRANULE: usize = SIZE_4KB;
1785 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor, HYP_GRANULE);
Pierre-Clément Tosi49e26ce2024-03-12 16:31:50 +00001786
Pierre-Clément Tosif4d8a0d2024-10-25 17:14:15 +01001787 assert_eq!(device_info, Err(DeviceAssignmentError::InvalidReg(0x7fee0000)));
Pierre-Clément Tosi49e26ce2024-03-12 16:31:50 +00001788 }
1789
1790 #[test]
Jaewan Kim50246682024-03-11 23:18:54 +09001791 fn device_assignment_clean() {
1792 let mut platform_dt_data = pvmfw_fdt_template::RAW.to_vec();
1793 let platform_dt = Fdt::from_mut_slice(&mut platform_dt_data).unwrap();
1794
1795 let compatible = platform_dt.root().next_compatible(cstr!("pkvm,pviommu"));
1796 assert_ne!(None, compatible.unwrap());
1797
1798 clean(platform_dt).unwrap();
1799
1800 let compatible = platform_dt.root().next_compatible(cstr!("pkvm,pviommu"));
1801 assert_eq!(Ok(None), compatible);
1802 }
Jaewan Kim8f6f4662023-12-12 17:38:47 +09001803
1804 #[test]
1805 fn device_info_dependency() {
1806 let mut fdt_data = fs::read(FDT_WITH_DEPENDENCY_FILE_PATH).unwrap();
1807 let mut vm_dtbo_data = fs::read(VM_DTBO_WITH_DEPENDENCIES_FILE_PATH).unwrap();
1808 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1809 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1810 let mut platform_dt_data = pvmfw_fdt_template::RAW.to_vec();
1811 platform_dt_data.resize(pvmfw_fdt_template::RAW.len() * 2, 0);
1812 let platform_dt = Fdt::from_mut_slice(&mut platform_dt_data).unwrap();
1813 platform_dt.unpack().unwrap();
1814
1815 let hypervisor = MockHypervisor {
1816 mmio_tokens: [((0xFF000, 0x1), 0xF000)].into(),
1817 iommu_tokens: Default::default(),
1818 };
1819
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +01001820 const HYP_GRANULE: usize = SIZE_4KB;
1821 let device_info =
1822 DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor, HYP_GRANULE).unwrap().unwrap();
Jaewan Kim8f6f4662023-12-12 17:38:47 +09001823 device_info.filter(vm_dtbo).unwrap();
1824
1825 // SAFETY: Damaged VM DTBO wouldn't be used after this unsafe block.
1826 unsafe {
1827 platform_dt.apply_overlay(vm_dtbo.as_mut()).unwrap();
1828 }
1829 device_info.patch(platform_dt).unwrap();
1830
1831 let expected = Dts::from_dtb(Path::new(EXPECTED_FDT_WITH_DEPENDENCY_FILE_PATH)).unwrap();
1832 let platform_dt = Dts::from_fdt(platform_dt).unwrap();
1833
1834 assert_eq!(expected, platform_dt);
1835 }
1836
1837 #[test]
1838 fn device_info_multiple_dependencies() {
1839 let mut fdt_data = fs::read(FDT_WITH_MULTIPLE_DEPENDENCIES_FILE_PATH).unwrap();
1840 let mut vm_dtbo_data = fs::read(VM_DTBO_WITH_DEPENDENCIES_FILE_PATH).unwrap();
1841 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1842 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1843 let mut platform_dt_data = pvmfw_fdt_template::RAW.to_vec();
1844 platform_dt_data.resize(pvmfw_fdt_template::RAW.len() * 2, 0);
1845 let platform_dt = Fdt::from_mut_slice(&mut platform_dt_data).unwrap();
1846 platform_dt.unpack().unwrap();
1847
1848 let hypervisor = MockHypervisor {
1849 mmio_tokens: [((0xFF000, 0x1), 0xF000), ((0xFF100, 0x1), 0xF100)].into(),
1850 iommu_tokens: Default::default(),
1851 };
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +01001852 const HYP_GRANULE: usize = SIZE_4KB;
1853 let device_info =
1854 DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor, HYP_GRANULE).unwrap().unwrap();
Jaewan Kim8f6f4662023-12-12 17:38:47 +09001855 device_info.filter(vm_dtbo).unwrap();
1856
1857 // SAFETY: Damaged VM DTBO wouldn't be used after this unsafe block.
1858 unsafe {
1859 platform_dt.apply_overlay(vm_dtbo.as_mut()).unwrap();
1860 }
1861 device_info.patch(platform_dt).unwrap();
1862
1863 let expected =
1864 Dts::from_dtb(Path::new(EXPECTED_FDT_WITH_MULTIPLE_DEPENDENCIES_FILE_PATH)).unwrap();
1865 let platform_dt = Dts::from_fdt(platform_dt).unwrap();
1866
1867 assert_eq!(expected, platform_dt);
1868 }
1869
1870 #[test]
1871 fn device_info_dependency_loop() {
1872 let mut fdt_data = fs::read(FDT_WITH_DEPENDENCY_LOOP_FILE_PATH).unwrap();
1873 let mut vm_dtbo_data = fs::read(VM_DTBO_WITH_DEPENDENCIES_FILE_PATH).unwrap();
1874 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1875 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1876 let mut platform_dt_data = pvmfw_fdt_template::RAW.to_vec();
1877 platform_dt_data.resize(pvmfw_fdt_template::RAW.len() * 2, 0);
1878 let platform_dt = Fdt::from_mut_slice(&mut platform_dt_data).unwrap();
1879 platform_dt.unpack().unwrap();
1880
1881 let hypervisor = MockHypervisor {
1882 mmio_tokens: [((0xFF200, 0x1), 0xF200)].into(),
1883 iommu_tokens: Default::default(),
1884 };
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +01001885 const HYP_GRANULE: usize = SIZE_4KB;
1886 let device_info =
1887 DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor, HYP_GRANULE).unwrap().unwrap();
Jaewan Kim8f6f4662023-12-12 17:38:47 +09001888 device_info.filter(vm_dtbo).unwrap();
1889
1890 // SAFETY: Damaged VM DTBO wouldn't be used after this unsafe block.
1891 unsafe {
1892 platform_dt.apply_overlay(vm_dtbo.as_mut()).unwrap();
1893 }
1894 device_info.patch(platform_dt).unwrap();
1895
1896 let expected =
1897 Dts::from_dtb(Path::new(EXPECTED_FDT_WITH_DEPENDENCY_LOOP_FILE_PATH)).unwrap();
1898 let platform_dt = Dts::from_fdt(platform_dt).unwrap();
1899
1900 assert_eq!(expected, platform_dt);
1901 }
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001902}