blob: ba13fa323fcf39a280e997cd54cdd1abbde27f1a [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;
Per Larsen7ec45d32024-11-02 00:56:46 +000031// TODO(b/308694211): Use hypervisor_backends::{DeviceAssigningHypervisor, Error} proper for tests.
32#[cfg(not(test))]
33use hypervisor_backends::DeviceAssigningHypervisor;
Jaewan Kim8f6f4662023-12-12 17:38:47 +090034use libfdt::{Fdt, FdtError, FdtNode, FdtNodeMut, Phandle, Reg};
Jaewan Kim52477ae2023-11-21 21:20:52 +090035use log::error;
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +010036use log::warn;
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/277993056): Keep constants derived from platform.dts in one place.
41const CELLS_PER_INTERRUPT: usize = 3; // from /intc node in platform.dts
42
43/// Errors in device assignment.
44#[derive(Clone, Copy, Debug, Eq, PartialEq)]
45pub enum DeviceAssignmentError {
Jaewan Kim52477ae2023-11-21 21:20:52 +090046 /// Invalid VM DTBO
Jaewan Kimc6e023b2023-10-12 15:11:05 +090047 InvalidDtbo,
48 /// Invalid __symbols__
49 InvalidSymbols,
Jaewan Kim19b984f2023-12-04 15:16:50 +090050 /// Malformed <reg>. Can't parse.
51 MalformedReg,
Pierre-Clément Tosif4d8a0d2024-10-25 17:14:15 +010052 /// Missing physical <reg> of assigned device.
53 MissingReg(u64, u64),
54 /// Extra <reg> of assigned device.
55 ExtraReg(u64, u64),
Pierre-Clément Tosi8b78bc32024-03-13 17:37:07 +000056 /// Invalid virtual <reg> of assigned device.
Pierre-Clément Tosif4d8a0d2024-10-25 17:14:15 +010057 InvalidReg(u64),
58 /// Token for <reg> of assigned device does not match expected value.
59 InvalidRegToken(u64, u64),
Pierre-Clément Tosi7fb437f2024-10-29 09:54:42 +000060 /// Invalid virtual <reg> size of assigned device.
61 InvalidRegSize(u64, u64),
Jaewan Kimc6e023b2023-10-12 15:11:05 +090062 /// Invalid <interrupts>
63 InvalidInterrupts,
Jaewan Kim19b984f2023-12-04 15:16:50 +090064 /// Malformed <iommus>
65 MalformedIommus,
Jaewan Kim51ccfed2023-11-08 13:51:58 +090066 /// Invalid <iommus>
67 InvalidIommus,
Jaewan Kim19b984f2023-12-04 15:16:50 +090068 /// Invalid phys IOMMU node
69 InvalidPhysIommu,
Jaewan Kima9200492023-11-21 20:45:31 +090070 /// Invalid pvIOMMU node
71 InvalidPvIommu,
Jaewan Kim51ccfed2023-11-08 13:51:58 +090072 /// Too many pvIOMMU
73 TooManyPvIommu,
Jaewan Kim19b984f2023-12-04 15:16:50 +090074 /// Duplicated phys IOMMU IDs exist
75 DuplicatedIommuIds,
Jaewan Kim51ccfed2023-11-08 13:51:58 +090076 /// Duplicated pvIOMMU IDs exist
77 DuplicatedPvIommuIds,
Jaewan Kimf8abbb52023-12-12 22:11:39 +090078 /// Unsupported path format. Only supports full path.
79 UnsupportedPathFormat,
Jaewan Kimc6e023b2023-10-12 15:11:05 +090080 /// Unsupported overlay target syntax. Only supports <target-path> with full path.
81 UnsupportedOverlayTarget,
Jaewan Kim19b984f2023-12-04 15:16:50 +090082 /// Unsupported PhysIommu,
83 UnsupportedPhysIommu,
84 /// Unsupported (pvIOMMU id, vSID) duplication. Currently the pair should be unique.
85 UnsupportedPvIommusDuplication,
86 /// Unsupported (IOMMU token, SID) duplication. Currently the pair should be unique.
87 UnsupportedIommusDuplication,
Jaewan Kim51ccfed2023-11-08 13:51:58 +090088 /// Internal error
89 Internal,
Jaewan Kimc6e023b2023-10-12 15:11:05 +090090 /// Unexpected error from libfdt
91 UnexpectedFdtError(FdtError),
92}
93
94impl From<FdtError> for DeviceAssignmentError {
95 fn from(e: FdtError) -> Self {
96 DeviceAssignmentError::UnexpectedFdtError(e)
97 }
98}
99
100impl fmt::Display for DeviceAssignmentError {
101 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
102 match self {
103 Self::InvalidDtbo => write!(f, "Invalid DTBO"),
104 Self::InvalidSymbols => write!(
105 f,
106 "Invalid property in /__symbols__. Must point to valid assignable device node."
107 ),
Jaewan Kim19b984f2023-12-04 15:16:50 +0900108 Self::MalformedReg => write!(f, "Malformed <reg>. Can't parse"),
Pierre-Clément Tosif4d8a0d2024-10-25 17:14:15 +0100109 Self::MissingReg(addr, size) => {
110 write!(f, "Missing physical MMIO region: addr:{addr:#x}), size:{size:#x}")
Pierre-Clément Tosi8b78bc32024-03-13 17:37:07 +0000111 }
Pierre-Clément Tosif4d8a0d2024-10-25 17:14:15 +0100112 Self::ExtraReg(addr, size) => {
113 write!(f, "Unexpected extra MMIO region: addr:{addr:#x}), size:{size:#x}")
114 }
115 Self::InvalidReg(addr) => {
116 write!(f, "Invalid guest MMIO granule (addr: {addr:#x})")
117 }
Pierre-Clément Tosi7fb437f2024-10-29 09:54:42 +0000118 Self::InvalidRegSize(size, expected) => {
119 write!(f, "Unexpected MMIO size ({size:#x}), should be {expected:#x}")
120 }
Pierre-Clément Tosif4d8a0d2024-10-25 17:14:15 +0100121 Self::InvalidRegToken(token, expected) => {
122 write!(f, "Unexpected MMIO token ({token:#x}), should be {expected:#x}")
Pierre-Clément Tosi8b78bc32024-03-13 17:37:07 +0000123 }
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900124 Self::InvalidInterrupts => write!(f, "Invalid <interrupts>"),
Jaewan Kim19b984f2023-12-04 15:16:50 +0900125 Self::MalformedIommus => write!(f, "Malformed <iommus>. Can't parse."),
126 Self::InvalidIommus => {
127 write!(f, "Invalid <iommus>. Failed to validate with hypervisor")
128 }
129 Self::InvalidPhysIommu => write!(f, "Invalid phys IOMMU node"),
Jaewan Kima9200492023-11-21 20:45:31 +0900130 Self::InvalidPvIommu => write!(f, "Invalid pvIOMMU node"),
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900131 Self::TooManyPvIommu => write!(
132 f,
133 "Too many pvIOMMU node. Insufficient pre-populated pvIOMMUs in platform DT"
134 ),
Jaewan Kim19b984f2023-12-04 15:16:50 +0900135 Self::DuplicatedIommuIds => {
136 write!(f, "Duplicated IOMMU IDs exist. IDs must unique among iommu node")
137 }
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900138 Self::DuplicatedPvIommuIds => {
Jaewan Kim19b984f2023-12-04 15:16:50 +0900139 write!(f, "Duplicated pvIOMMU IDs exist. IDs must unique among iommu node")
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900140 }
Jaewan Kimf8abbb52023-12-12 22:11:39 +0900141 Self::UnsupportedPathFormat => {
142 write!(f, "Unsupported UnsupportedPathFormat. Only supports full path")
143 }
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900144 Self::UnsupportedOverlayTarget => {
145 write!(f, "Unsupported overlay target. Only supports 'target-path = \"/\"'")
146 }
Jaewan Kim19b984f2023-12-04 15:16:50 +0900147 Self::UnsupportedPhysIommu => {
148 write!(f, "Unsupported Phys IOMMU. Currently only supports #iommu-cells = <1>")
149 }
150 Self::UnsupportedPvIommusDuplication => {
151 write!(f, "Unsupported (pvIOMMU id, vSID) duplication. Currently the pair should be unique.")
152 }
153 Self::UnsupportedIommusDuplication => {
154 write!(f, "Unsupported (IOMMU token, SID) duplication. Currently the pair should be unique.")
155 }
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900156 Self::Internal => write!(f, "Internal error"),
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900157 Self::UnexpectedFdtError(e) => write!(f, "Unexpected Error from libfdt: {e}"),
158 }
159 }
160}
161
162pub type Result<T> = core::result::Result<T, DeviceAssignmentError>;
163
Jaewan Kimf8abbb52023-12-12 22:11:39 +0900164#[derive(Clone, Default, Ord, PartialOrd, Eq, PartialEq)]
165pub struct DtPathTokens<'a> {
166 tokens: Vec<&'a [u8]>,
167}
168
Chris Wailes52358e92025-01-27 17:04:40 -0800169impl fmt::Debug for DtPathTokens<'_> {
Jaewan Kimf8abbb52023-12-12 22:11:39 +0900170 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
171 let mut list = f.debug_list();
172 for token in &self.tokens {
173 let mut bytes = token.to_vec();
174 bytes.push(b'\0');
175 match CString::from_vec_with_nul(bytes) {
176 Ok(string) => list.entry(&string),
177 Err(_) => list.entry(token),
178 };
179 }
180 list.finish()
181 }
182}
183
184impl<'a> DtPathTokens<'a> {
185 fn new(path: &'a CStr) -> Result<Self> {
186 if path.to_bytes().first() != Some(&b'/') {
187 return Err(DeviceAssignmentError::UnsupportedPathFormat);
188 }
189 let tokens: Vec<_> = path
190 .to_bytes()
191 .split(|char| *char == b'/')
192 .filter(|&component| !component.is_empty())
193 .collect();
194 Ok(Self { tokens })
195 }
196
197 fn to_overlay_target_path(&self) -> Result<Self> {
198 if !self.is_overlayable_node() {
199 return Err(DeviceAssignmentError::InvalidDtbo);
200 }
201 Ok(Self { tokens: self.tokens.as_slice()[2..].to_vec() })
202 }
203
204 fn to_cstring(&self) -> CString {
205 if self.tokens.is_empty() {
206 return CString::new(*b"/\0").unwrap();
207 }
208
209 let size = self.tokens.iter().fold(0, |sum, token| sum + token.len() + 1);
210 let mut path = Vec::with_capacity(size + 1);
211 for token in &self.tokens {
212 path.push(b'/');
213 path.extend_from_slice(token);
214 }
215 path.push(b'\0');
216
217 CString::from_vec_with_nul(path).unwrap()
218 }
219
220 fn is_overlayable_node(&self) -> bool {
221 self.tokens.get(1) == Some(&&b"__overlay__"[..])
222 }
223}
224
Jaewan Kim8f6f4662023-12-12 17:38:47 +0900225#[derive(Debug, Eq, PartialEq)]
226enum DeviceTreeChildrenMask {
227 Partial(Vec<DeviceTreeMask>),
228 All,
229}
230
231#[derive(Eq, PartialEq)]
232struct DeviceTreeMask {
233 name_bytes: Vec<u8>,
234 children: DeviceTreeChildrenMask,
235}
236
237impl fmt::Debug for DeviceTreeMask {
238 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
239 let name_bytes = [self.name_bytes.as_slice(), b"\0"].concat();
240
241 f.debug_struct("DeviceTreeMask")
242 .field("name", &CStr::from_bytes_with_nul(&name_bytes).unwrap())
243 .field("children", &self.children)
244 .finish()
245 }
246}
247
248impl DeviceTreeMask {
249 fn new() -> Self {
250 Self { name_bytes: b"/".to_vec(), children: DeviceTreeChildrenMask::Partial(Vec::new()) }
251 }
252
253 fn mask_internal(&mut self, path: &DtPathTokens, leaf_mask: DeviceTreeChildrenMask) -> bool {
254 let mut iter = self;
255 let mut newly_masked = false;
256 'next_token: for path_token in &path.tokens {
257 let DeviceTreeChildrenMask::Partial(ref mut children) = &mut iter.children else {
258 return false;
259 };
260
261 // Note: Can't use iterator for 'get or insert'. (a.k.a. polonius Rust)
262 #[allow(clippy::needless_range_loop)]
263 for i in 0..children.len() {
264 if children[i].name_bytes.as_slice() == *path_token {
265 iter = &mut children[i];
266 newly_masked = false;
267 continue 'next_token;
268 }
269 }
270 let child = Self {
271 name_bytes: path_token.to_vec(),
272 children: DeviceTreeChildrenMask::Partial(Vec::new()),
273 };
274 children.push(child);
275 newly_masked = true;
276 iter = children.last_mut().unwrap()
277 }
278 iter.children = leaf_mask;
279 newly_masked
280 }
281
282 fn mask(&mut self, path: &DtPathTokens) -> bool {
283 self.mask_internal(path, DeviceTreeChildrenMask::Partial(Vec::new()))
284 }
285
286 fn mask_all(&mut self, path: &DtPathTokens) {
287 self.mask_internal(path, DeviceTreeChildrenMask::All);
288 }
289}
290
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900291/// Represents VM DTBO
292#[repr(transparent)]
293pub struct VmDtbo(Fdt);
294
295impl VmDtbo {
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900296 /// Wraps a mutable slice containing a VM DTBO.
297 ///
298 /// Fails if the VM DTBO does not pass validation.
299 pub fn from_mut_slice(dtbo: &mut [u8]) -> Result<&mut Self> {
300 // This validates DTBO
301 let fdt = Fdt::from_mut_slice(dtbo)?;
302 // SAFETY: VmDtbo is a transparent wrapper around Fdt, so representation is the same.
303 Ok(unsafe { mem::transmute::<&mut Fdt, &mut Self>(fdt) })
304 }
305
306 // Locates device node path as if the given dtbo node path is assigned and VM DTBO is overlaid.
307 // For given dtbo node path, this concatenates <target-path> of the enclosing fragment and
308 // relative path from __overlay__ node.
309 //
310 // Here's an example with sample VM DTBO:
311 // / {
312 // fragment@rng {
313 // target-path = "/"; // Always 'target-path = "/"'. Disallows <target> or other path.
314 // __overlay__ {
315 // rng { ... }; // Actual device node is here. If overlaid, path would be "/rng"
316 // };
317 // };
Jaewan Kim80ef9fa2024-02-25 16:08:14 +0000318 // __symbols__ { // Contains list of assignable devices
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900319 // rng = "/fragment@rng/__overlay__/rng";
320 // };
321 // };
322 //
Alan Stokesf46a17c2025-01-05 15:50:18 +0000323 // Then locate_overlay_target_path(c"/fragment@rng/__overlay__/rng") is Ok("/rng")
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900324 //
325 // Contrary to fdt_overlay_target_offset(), this API enforces overlay target property
326 // 'target-path = "/"', so the overlay doesn't modify and/or append platform DT's existing
327 // node and/or properties. The enforcement is for compatibility reason.
Jaewan Kim19b984f2023-12-04 15:16:50 +0900328 fn locate_overlay_target_path(
329 &self,
Jaewan Kimf8abbb52023-12-12 22:11:39 +0900330 dtbo_node_path: &DtPathTokens,
Jaewan Kim19b984f2023-12-04 15:16:50 +0900331 dtbo_node: &FdtNode,
332 ) -> Result<CString> {
Jaewan Kim19b984f2023-12-04 15:16:50 +0900333 let fragment_node = dtbo_node.supernode_at_depth(1)?;
Alan Stokesf46a17c2025-01-05 15:50:18 +0000334 let target_path =
335 fragment_node.getprop_str(c"target-path")?.ok_or(DeviceAssignmentError::InvalidDtbo)?;
336 if target_path != c"/" {
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900337 return Err(DeviceAssignmentError::UnsupportedOverlayTarget);
338 }
339
Jaewan Kimf8abbb52023-12-12 22:11:39 +0900340 let overlaid_path = dtbo_node_path.to_overlay_target_path()?;
341 Ok(overlaid_path.to_cstring())
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900342 }
Jaewan Kim19b984f2023-12-04 15:16:50 +0900343
344 fn parse_physical_iommus(physical_node: &FdtNode) -> Result<BTreeMap<Phandle, PhysIommu>> {
345 let mut phys_iommus = BTreeMap::new();
346 for (node, _) in physical_node.descendants() {
347 let Some(phandle) = node.get_phandle()? else {
348 continue; // Skips unreachable IOMMU node
349 };
350 let Some(iommu) = PhysIommu::parse(&node)? else {
351 continue; // Skip if not a PhysIommu.
352 };
353 if phys_iommus.insert(phandle, iommu).is_some() {
354 return Err(FdtError::BadPhandle.into());
355 }
356 }
357 Self::validate_physical_iommus(&phys_iommus)?;
358 Ok(phys_iommus)
359 }
360
361 fn validate_physical_iommus(phys_iommus: &BTreeMap<Phandle, PhysIommu>) -> Result<()> {
362 let unique_iommus: BTreeSet<_> = phys_iommus.values().cloned().collect();
363 if phys_iommus.len() != unique_iommus.len() {
364 return Err(DeviceAssignmentError::DuplicatedIommuIds);
365 }
366 Ok(())
367 }
368
369 fn validate_physical_devices(
370 physical_devices: &BTreeMap<Phandle, PhysicalDeviceInfo>,
371 ) -> Result<()> {
372 // Only need to validate iommus because <reg> will be validated together with PV <reg>
373 // see: DeviceAssignmentInfo::validate_all_regs().
374 let mut all_iommus = BTreeSet::new();
375 for physical_device in physical_devices.values() {
Sreenad Menon84599282025-01-27 20:47:52 -0800376 if let Some(iommus) = &physical_device.iommus {
377 for iommu in iommus {
378 if !all_iommus.insert(iommu) {
379 error!("Unsupported phys IOMMU duplication found, <iommus> = {iommu:?}");
380 return Err(DeviceAssignmentError::UnsupportedIommusDuplication);
381 }
Jaewan Kim19b984f2023-12-04 15:16:50 +0900382 }
383 }
384 }
385 Ok(())
386 }
387
388 fn parse_physical_devices_with_iommus(
389 physical_node: &FdtNode,
390 phys_iommus: &BTreeMap<Phandle, PhysIommu>,
391 ) -> Result<BTreeMap<Phandle, PhysicalDeviceInfo>> {
392 let mut physical_devices = BTreeMap::new();
393 for (node, _) in physical_node.descendants() {
394 let Some(info) = PhysicalDeviceInfo::parse(&node, phys_iommus)? else {
395 continue;
396 };
397 if physical_devices.insert(info.target, info).is_some() {
398 return Err(DeviceAssignmentError::InvalidDtbo);
399 }
400 }
401 Self::validate_physical_devices(&physical_devices)?;
402 Ok(physical_devices)
403 }
404
405 /// Parses Physical devices in VM DTBO
406 fn parse_physical_devices(&self) -> Result<BTreeMap<Phandle, PhysicalDeviceInfo>> {
Alan Stokesf46a17c2025-01-05 15:50:18 +0000407 let Some(physical_node) = self.as_ref().node(c"/host")? else {
Jaewan Kim19b984f2023-12-04 15:16:50 +0900408 return Ok(BTreeMap::new());
409 };
410
411 let phys_iommus = Self::parse_physical_iommus(&physical_node)?;
412 Self::parse_physical_devices_with_iommus(&physical_node, &phys_iommus)
413 }
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900414
Jaewan Kimf8abbb52023-12-12 22:11:39 +0900415 fn node(&self, path: &DtPathTokens) -> Result<Option<FdtNode>> {
416 let mut node = self.as_ref().root();
417 for token in &path.tokens {
418 let Some(subnode) = node.subnode_with_name_bytes(token)? else {
419 return Ok(None);
420 };
421 node = subnode;
422 }
423 Ok(Some(node))
424 }
Jaewan Kim8f6f4662023-12-12 17:38:47 +0900425
426 fn collect_overlayable_nodes_with_phandle(&self) -> Result<BTreeMap<Phandle, DtPathTokens>> {
427 let mut paths = BTreeMap::new();
428 let mut path: DtPathTokens = Default::default();
429 let root = self.as_ref().root();
430 for (node, depth) in root.descendants() {
431 path.tokens.truncate(depth - 1);
432 path.tokens.push(node.name()?.to_bytes());
433 if !path.is_overlayable_node() {
434 continue;
435 }
436 if let Some(phandle) = node.get_phandle()? {
437 paths.insert(phandle, path.clone());
438 }
439 }
440 Ok(paths)
441 }
442
443 fn collect_phandle_references_from_overlayable_nodes(
444 &self,
445 ) -> Result<BTreeMap<DtPathTokens, Vec<Phandle>>> {
446 const CELL_SIZE: usize = core::mem::size_of::<u32>();
447
448 let vm_dtbo = self.as_ref();
449
450 let mut phandle_map = BTreeMap::new();
Alan Stokesf46a17c2025-01-05 15:50:18 +0000451 let Some(local_fixups) = vm_dtbo.node(c"/__local_fixups__")? else {
Jaewan Kim8f6f4662023-12-12 17:38:47 +0900452 return Ok(phandle_map);
453 };
454
455 let mut path: DtPathTokens = Default::default();
456 for (fixup_node, depth) in local_fixups.descendants() {
457 let node_name = fixup_node.name()?;
458 path.tokens.truncate(depth - 1);
459 path.tokens.push(node_name.to_bytes());
460 if path.tokens.len() != depth {
461 return Err(DeviceAssignmentError::Internal);
462 }
463 if !path.is_overlayable_node() {
464 continue;
465 }
466 let target_node = self.node(&path)?.ok_or(DeviceAssignmentError::InvalidDtbo)?;
467
468 let mut phandles = vec![];
469 for fixup_prop in fixup_node.properties()? {
470 let target_prop = target_node
471 .getprop(fixup_prop.name()?)
472 .or(Err(DeviceAssignmentError::InvalidDtbo))?
473 .ok_or(DeviceAssignmentError::InvalidDtbo)?;
474 let fixup_prop_values = fixup_prop.value()?;
475 if fixup_prop_values.is_empty() || fixup_prop_values.len() % CELL_SIZE != 0 {
476 return Err(DeviceAssignmentError::InvalidDtbo);
477 }
478
479 for fixup_prop_cell in fixup_prop_values.chunks(CELL_SIZE) {
480 let phandle_offset: usize = u32::from_be_bytes(
481 fixup_prop_cell.try_into().or(Err(DeviceAssignmentError::InvalidDtbo))?,
482 )
483 .try_into()
484 .or(Err(DeviceAssignmentError::InvalidDtbo))?;
485 if phandle_offset % CELL_SIZE != 0 {
486 return Err(DeviceAssignmentError::InvalidDtbo);
487 }
488 let phandle_value = target_prop
489 .get(phandle_offset..phandle_offset + CELL_SIZE)
490 .ok_or(DeviceAssignmentError::InvalidDtbo)?;
491 let phandle: Phandle = U32::ref_from(phandle_value)
492 .unwrap()
493 .get()
494 .try_into()
495 .or(Err(DeviceAssignmentError::InvalidDtbo))?;
496
497 phandles.push(phandle);
498 }
499 }
500 if !phandles.is_empty() {
501 phandle_map.insert(path.clone(), phandles);
502 }
503 }
504
505 Ok(phandle_map)
506 }
507
508 fn build_mask(&self, assigned_devices: Vec<DtPathTokens>) -> Result<DeviceTreeMask> {
509 if assigned_devices.is_empty() {
510 return Err(DeviceAssignmentError::Internal);
511 }
512
513 let dependencies = self.collect_phandle_references_from_overlayable_nodes()?;
514 let paths = self.collect_overlayable_nodes_with_phandle()?;
515
516 let mut mask = DeviceTreeMask::new();
517 let mut stack = assigned_devices;
518 while let Some(path) = stack.pop() {
519 if !mask.mask(&path) {
520 continue;
521 }
522 let Some(dst_phandles) = dependencies.get(&path) else {
523 continue;
524 };
525 for dst_phandle in dst_phandles {
526 let dst_path = paths.get(dst_phandle).ok_or(DeviceAssignmentError::Internal)?;
527 stack.push(dst_path.clone());
528 }
529 }
530
531 Ok(mask)
532 }
Jaewan Kimc39974e2023-12-02 01:13:30 +0900533}
534
Jaewan Kimc730ebf2024-02-22 10:34:55 +0900535fn filter_dangling_symbols(fdt: &mut Fdt) -> Result<()> {
536 if let Some(symbols) = fdt.symbols()? {
537 let mut removed = vec![];
538 for prop in symbols.properties()? {
539 let path = CStr::from_bytes_with_nul(prop.value()?)
540 .map_err(|_| DeviceAssignmentError::Internal)?;
541 if fdt.node(path)?.is_none() {
542 let name = prop.name()?;
543 removed.push(CString::from(name));
544 }
545 }
546
547 let mut symbols = fdt.symbols_mut()?.unwrap();
548 for name in removed {
549 symbols.nop_property(&name)?;
550 }
551 }
552 Ok(())
553}
554
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900555impl AsRef<Fdt> for VmDtbo {
556 fn as_ref(&self) -> &Fdt {
557 &self.0
558 }
559}
560
561impl AsMut<Fdt> for VmDtbo {
562 fn as_mut(&mut self) -> &mut Fdt {
563 &mut self.0
564 }
565}
566
Jaewan Kim8f6f4662023-12-12 17:38:47 +0900567// Filter any node that isn't masked by DeviceTreeMask.
568fn filter_with_mask(anchor: FdtNodeMut, mask: &DeviceTreeMask) -> Result<()> {
569 let mut stack = vec![mask];
570 let mut iter = anchor.next_node(0)?;
571 while let Some((node, depth)) = iter {
572 stack.truncate(depth);
573 let parent_mask = stack.last().unwrap();
574 let DeviceTreeChildrenMask::Partial(parent_mask_children) = &parent_mask.children else {
575 // Shouldn't happen. We only step-in if parent has DeviceTreeChildrenMask::Partial.
576 return Err(DeviceAssignmentError::Internal);
577 };
578
579 let name = node.as_node().name()?.to_bytes();
580 let mask = parent_mask_children.iter().find(|child_mask| child_mask.name_bytes == name);
581 if let Some(masked) = mask {
582 if let DeviceTreeChildrenMask::Partial(_) = &masked.children {
583 // This node is partially masked. Stepping-in.
584 stack.push(masked);
585 iter = node.next_node(depth)?;
586 } else {
587 // This node is fully masked. Stepping-out.
588 iter = node.next_node_skip_subnodes(depth)?;
589 }
590 } else {
591 // This node isn't masked.
592 iter = node.delete_and_next_node(depth)?;
593 }
594 }
595
596 Ok(())
597}
598
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900599#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
600struct PvIommu {
601 // ID from pvIOMMU node
602 id: u32,
603}
604
605impl PvIommu {
606 fn parse(node: &FdtNode) -> Result<Self> {
Alan Stokesf46a17c2025-01-05 15:50:18 +0000607 let iommu_cells =
608 node.getprop_u32(c"#iommu-cells")?.ok_or(DeviceAssignmentError::InvalidPvIommu)?;
Jaewan Kim19b984f2023-12-04 15:16:50 +0900609 // Ensures #iommu-cells = <1>. It means that `<iommus>` entry contains pair of
Jaewan Kima9200492023-11-21 20:45:31 +0900610 // (pvIOMMU ID, vSID)
611 if iommu_cells != 1 {
612 return Err(DeviceAssignmentError::InvalidPvIommu);
613 }
Alan Stokesf46a17c2025-01-05 15:50:18 +0000614 let id = node.getprop_u32(c"id")?.ok_or(DeviceAssignmentError::InvalidPvIommu)?;
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900615 Ok(Self { id })
616 }
617}
618
Jaewan Kima9200492023-11-21 20:45:31 +0900619#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
620struct Vsid(u32);
621
Jaewan Kim19b984f2023-12-04 15:16:50 +0900622#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
623struct Sid(u64);
624
625impl From<u32> for Sid {
626 fn from(sid: u32) -> Self {
627 Self(sid.into())
628 }
629}
630
631#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
Jaewan Kim52477ae2023-11-21 21:20:52 +0900632struct DeviceReg {
633 addr: u64,
634 size: u64,
635}
636
Pierre-Clément Tosi49e26ce2024-03-12 16:31:50 +0000637impl DeviceReg {
638 pub fn overlaps(&self, range: &Range<u64>) -> bool {
639 self.addr < range.end && range.start < self.addr.checked_add(self.size).unwrap()
640 }
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +0100641
642 pub fn is_aligned(&self, granule: u64) -> bool {
643 self.addr % granule == 0 && self.size % granule == 0
644 }
Pierre-Clément Tosi49e26ce2024-03-12 16:31:50 +0000645}
646
Jaewan Kim52477ae2023-11-21 21:20:52 +0900647impl TryFrom<Reg<u64>> for DeviceReg {
648 type Error = DeviceAssignmentError;
649
650 fn try_from(reg: Reg<u64>) -> Result<Self> {
Jaewan Kim19b984f2023-12-04 15:16:50 +0900651 Ok(Self { addr: reg.addr, size: reg.size.ok_or(DeviceAssignmentError::MalformedReg)? })
Jaewan Kim52477ae2023-11-21 21:20:52 +0900652 }
653}
654
655fn parse_node_reg(node: &FdtNode) -> Result<Vec<DeviceReg>> {
656 node.reg()?
Jaewan Kim19b984f2023-12-04 15:16:50 +0900657 .ok_or(DeviceAssignmentError::MalformedReg)?
Jaewan Kim52477ae2023-11-21 21:20:52 +0900658 .map(DeviceReg::try_from)
659 .collect::<Result<Vec<_>>>()
660}
661
662fn to_be_bytes(reg: &[DeviceReg]) -> Vec<u8> {
663 let mut reg_cells = vec![];
664 for x in reg {
665 reg_cells.extend_from_slice(&x.addr.to_be_bytes());
666 reg_cells.extend_from_slice(&x.size.to_be_bytes());
667 }
668 reg_cells
669}
670
Jaewan Kim19b984f2023-12-04 15:16:50 +0900671#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
672struct PhysIommu {
673 token: u64,
674}
675
676impl PhysIommu {
677 fn parse(node: &FdtNode) -> Result<Option<Self>> {
Alan Stokesf46a17c2025-01-05 15:50:18 +0000678 let Some(token) = node.getprop_u64(c"android,pvmfw,token")? else {
Jaewan Kim19b984f2023-12-04 15:16:50 +0900679 return Ok(None);
680 };
Alan Stokesf46a17c2025-01-05 15:50:18 +0000681 let Some(iommu_cells) = node.getprop_u32(c"#iommu-cells")? else {
Jaewan Kim19b984f2023-12-04 15:16:50 +0900682 return Err(DeviceAssignmentError::InvalidPhysIommu);
683 };
684 // Currently only supports #iommu-cells = <1>.
685 // In that case `<iommus>` entry contains pair of (pIOMMU phandle, Sid token)
686 if iommu_cells != 1 {
687 return Err(DeviceAssignmentError::UnsupportedPhysIommu);
688 }
689 Ok(Some(Self { token }))
690 }
691}
692
693#[derive(Debug)]
694struct PhysicalDeviceInfo {
695 target: Phandle,
696 reg: Vec<DeviceReg>,
Sreenad Menon84599282025-01-27 20:47:52 -0800697 iommus: Option<Vec<(PhysIommu, Sid)>>,
Jaewan Kim19b984f2023-12-04 15:16:50 +0900698}
699
700impl PhysicalDeviceInfo {
701 fn parse_iommus(
702 node: &FdtNode,
703 phys_iommus: &BTreeMap<Phandle, PhysIommu>,
Sreenad Menon84599282025-01-27 20:47:52 -0800704 ) -> Result<Option<Vec<(PhysIommu, Sid)>>> {
Jaewan Kim19b984f2023-12-04 15:16:50 +0900705 let mut iommus = vec![];
Alan Stokesf46a17c2025-01-05 15:50:18 +0000706 let Some(mut cells) = node.getprop_cells(c"iommus")? else {
Sreenad Menon84599282025-01-27 20:47:52 -0800707 return Ok(None);
Jaewan Kim19b984f2023-12-04 15:16:50 +0900708 };
709 while let Some(cell) = cells.next() {
710 // Parse pIOMMU ID
711 let phandle =
712 Phandle::try_from(cell).or(Err(DeviceAssignmentError::MalformedIommus))?;
713 let iommu = phys_iommus.get(&phandle).ok_or(DeviceAssignmentError::MalformedIommus)?;
714
715 // Parse Sid
716 let Some(cell) = cells.next() else {
717 return Err(DeviceAssignmentError::MalformedIommus);
718 };
719
720 iommus.push((*iommu, Sid::from(cell)));
721 }
Sreenad Menon84599282025-01-27 20:47:52 -0800722 Ok(Some(iommus))
Jaewan Kim19b984f2023-12-04 15:16:50 +0900723 }
724
725 fn parse(node: &FdtNode, phys_iommus: &BTreeMap<Phandle, PhysIommu>) -> Result<Option<Self>> {
Alan Stokesf46a17c2025-01-05 15:50:18 +0000726 let Some(phandle) = node.getprop_u32(c"android,pvmfw,target")? else {
Jaewan Kim19b984f2023-12-04 15:16:50 +0900727 return Ok(None);
728 };
729 let target = Phandle::try_from(phandle)?;
730 let reg = parse_node_reg(node)?;
731 let iommus = Self::parse_iommus(node, phys_iommus)?;
732 Ok(Some(Self { target, reg, iommus }))
733 }
734}
735
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900736/// Assigned device information parsed from crosvm DT.
737/// Keeps everything in the owned data because underlying FDT will be reused for platform DT.
738#[derive(Debug, Eq, PartialEq)]
739struct AssignedDeviceInfo {
740 // Node path of assigned device (e.g. "/rng")
741 node_path: CString,
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900742 // <reg> property from the crosvm DT
Jaewan Kim52477ae2023-11-21 21:20:52 +0900743 reg: Vec<DeviceReg>,
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900744 // <interrupts> property from the crosvm DT
Sreenad Menon474da802025-01-27 21:22:46 -0800745 interrupts: Option<Vec<u8>>,
Jaewan Kima9200492023-11-21 20:45:31 +0900746 // Parsed <iommus> property from the crosvm DT. Tuple of PvIommu and vSID.
Sreenad Menon84599282025-01-27 20:47:52 -0800747 iommus: Option<Vec<(PvIommu, Vsid)>>,
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900748}
749
750impl AssignedDeviceInfo {
Jaewan Kim19b984f2023-12-04 15:16:50 +0900751 fn validate_reg(
752 device_reg: &[DeviceReg],
753 physical_device_reg: &[DeviceReg],
Jaewan Kim52477ae2023-11-21 21:20:52 +0900754 hypervisor: &dyn DeviceAssigningHypervisor,
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +0100755 granule: usize,
Jaewan Kim19b984f2023-12-04 15:16:50 +0900756 ) -> Result<()> {
Pierre-Clément Tosi8b78bc32024-03-13 17:37:07 +0000757 let mut virt_regs = device_reg.iter();
758 let mut phys_regs = physical_device_reg.iter();
Pierre-Clément Tosi49e26ce2024-03-12 16:31:50 +0000759 // TODO(b/308694211): Move this constant to vmbase::layout once vmbase is std-compatible.
760 const PVMFW_RANGE: Range<u64> = 0x7fc0_0000..0x8000_0000;
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +0100761
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 Tosieacdd0f2024-10-22 16:31:01 +0100764 if !reg.is_aligned(granule.try_into().unwrap()) {
765 let DeviceReg { addr, size } = reg;
766 warn!("Assigned region ({addr:#x}, {size:#x}) not aligned to {granule:#x}");
767 // TODO(ptosi): Fix our test data so that we can return Err(...);
768 }
Pierre-Clément Tosi49e26ce2024-03-12 16:31:50 +0000769 if reg.overlaps(&PVMFW_RANGE) {
Pierre-Clément Tosif4d8a0d2024-10-25 17:14:15 +0100770 return Err(DeviceAssignmentError::InvalidReg(reg.addr));
Pierre-Clément Tosi49e26ce2024-03-12 16:31:50 +0000771 }
Pierre-Clément Tosi7fb437f2024-10-29 09:54:42 +0000772 if reg.size != phys_reg.size {
773 return Err(DeviceAssignmentError::InvalidRegSize(reg.size, phys_reg.size));
774 }
Mostafa Saleh646a31b2024-10-22 12:55:59 +0000775 for offset in (0..reg.size).step_by(granule) {
776 let expected_token = phys_reg.addr + offset;
777 // If this call returns successfully, hyp has mapped the MMIO granule.
778 let token = hypervisor.get_phys_mmio_token(reg.addr + offset).map_err(|e| {
779 error!("Hypervisor error while requesting MMIO token: {e}");
780 DeviceAssignmentError::InvalidReg(reg.addr)
781 })?;
782 if token != expected_token {
783 return Err(DeviceAssignmentError::InvalidRegToken(token, expected_token));
784 }
Jaewan Kim19b984f2023-12-04 15:16:50 +0900785 }
Jaewan Kim52477ae2023-11-21 21:20:52 +0900786 }
Pierre-Clément Tosi8b78bc32024-03-13 17:37:07 +0000787
788 if let Some(DeviceReg { addr, size }) = virt_regs.next() {
Pierre-Clément Tosif4d8a0d2024-10-25 17:14:15 +0100789 return Err(DeviceAssignmentError::ExtraReg(*addr, *size));
Pierre-Clément Tosi8b78bc32024-03-13 17:37:07 +0000790 }
791
792 if let Some(DeviceReg { addr, size }) = phys_regs.next() {
Pierre-Clément Tosif4d8a0d2024-10-25 17:14:15 +0100793 return Err(DeviceAssignmentError::MissingReg(*addr, *size));
Pierre-Clément Tosi8b78bc32024-03-13 17:37:07 +0000794 }
795
Jaewan Kim19b984f2023-12-04 15:16:50 +0900796 Ok(())
Jaewan Kim52477ae2023-11-21 21:20:52 +0900797 }
798
Sreenad Menon474da802025-01-27 21:22:46 -0800799 fn parse_interrupts(node: &FdtNode) -> Result<Option<Vec<u8>>> {
800 let Some(cells) = node.getprop_cells(c"interrupts")? else {
801 return Ok(None);
802 };
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900803 // Validation: Validate if interrupts cell numbers are multiple of #interrupt-cells.
804 // We can't know how many interrupts would exist.
Sreenad Menon474da802025-01-27 21:22:46 -0800805 if cells.count() % CELLS_PER_INTERRUPT != 0 {
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900806 return Err(DeviceAssignmentError::InvalidInterrupts);
807 }
808
809 // Once validated, keep the raw bytes so patch can be done with setprop()
Sreenad Menon474da802025-01-27 21:22:46 -0800810 Ok(Some(node.getprop(c"interrupts").unwrap().unwrap().into()))
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900811 }
812
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900813 // TODO(b/277993056): Also validate /__local_fixups__ to ensure that <iommus> has phandle.
Jaewan Kima9200492023-11-21 20:45:31 +0900814 fn parse_iommus(
815 node: &FdtNode,
816 pviommus: &BTreeMap<Phandle, PvIommu>,
817 ) -> Result<Vec<(PvIommu, Vsid)>> {
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900818 let mut iommus = vec![];
Alan Stokesf46a17c2025-01-05 15:50:18 +0000819 let Some(mut cells) = node.getprop_cells(c"iommus")? else {
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900820 return Ok(iommus);
821 };
Jaewan Kima9200492023-11-21 20:45:31 +0900822 while let Some(cell) = cells.next() {
823 // Parse pvIOMMU ID
Jaewan Kim19b984f2023-12-04 15:16:50 +0900824 let phandle =
825 Phandle::try_from(cell).or(Err(DeviceAssignmentError::MalformedIommus))?;
826 let pviommu = pviommus.get(&phandle).ok_or(DeviceAssignmentError::MalformedIommus)?;
Jaewan Kima9200492023-11-21 20:45:31 +0900827
828 // Parse vSID
829 let Some(cell) = cells.next() else {
Jaewan Kim19b984f2023-12-04 15:16:50 +0900830 return Err(DeviceAssignmentError::MalformedIommus);
Jaewan Kima9200492023-11-21 20:45:31 +0900831 };
832 let vsid = Vsid(cell);
833
834 iommus.push((*pviommu, vsid));
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900835 }
836 Ok(iommus)
837 }
838
Jaewan Kim19b984f2023-12-04 15:16:50 +0900839 fn validate_iommus(
840 iommus: &[(PvIommu, Vsid)],
841 physical_device_iommu: &[(PhysIommu, Sid)],
842 hypervisor: &dyn DeviceAssigningHypervisor,
843 ) -> Result<()> {
844 if iommus.len() != physical_device_iommu.len() {
845 return Err(DeviceAssignmentError::InvalidIommus);
846 }
847 // pvIOMMU can be reordered, and hypervisor may not guarantee 1:1 mapping.
848 // So we need to mark what's matched or not.
849 let mut physical_device_iommu = physical_device_iommu.to_vec();
850 for (pviommu, vsid) in iommus {
Pierre-Clément Tosi08d6e3f2024-03-13 18:22:16 +0000851 let (id, sid) =
852 hypervisor.get_phys_iommu_token(pviommu.id.into(), vsid.0.into()).map_err(|e| {
853 error!("Hypervisor error while requesting IOMMU token ({pviommu:?}, {vsid:?}): {e}");
854 DeviceAssignmentError::InvalidIommus
855 })?;
Jaewan Kim19b984f2023-12-04 15:16:50 +0900856
857 let pos = physical_device_iommu
858 .iter()
859 .position(|(phys_iommu, phys_sid)| (phys_iommu.token, phys_sid.0) == (id, sid));
860 match pos {
861 Some(pos) => physical_device_iommu.remove(pos),
862 None => {
863 error!("Failed to validate device <iommus>. No matching phys iommu or duplicated mapping for pviommu={pviommu:?}, vsid={vsid:?}");
864 return Err(DeviceAssignmentError::InvalidIommus);
865 }
866 };
867 }
868 Ok(())
869 }
870
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900871 fn parse(
872 fdt: &Fdt,
873 vm_dtbo: &VmDtbo,
Jaewan Kimf8abbb52023-12-12 22:11:39 +0900874 dtbo_node_path: &DtPathTokens,
Jaewan Kim19b984f2023-12-04 15:16:50 +0900875 physical_devices: &BTreeMap<Phandle, PhysicalDeviceInfo>,
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900876 pviommus: &BTreeMap<Phandle, PvIommu>,
Jaewan Kim52477ae2023-11-21 21:20:52 +0900877 hypervisor: &dyn DeviceAssigningHypervisor,
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +0100878 granule: usize,
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900879 ) -> Result<Option<Self>> {
Jaewan Kim19b984f2023-12-04 15:16:50 +0900880 let dtbo_node =
Jaewan Kimf8abbb52023-12-12 22:11:39 +0900881 vm_dtbo.node(dtbo_node_path)?.ok_or(DeviceAssignmentError::InvalidSymbols)?;
Jaewan Kim19b984f2023-12-04 15:16:50 +0900882 let node_path = vm_dtbo.locate_overlay_target_path(dtbo_node_path, &dtbo_node)?;
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900883
884 let Some(node) = fdt.node(&node_path)? else { return Ok(None) };
885
Jaewan Kim80ef9fa2024-02-25 16:08:14 +0000886 // Currently can only assign devices backed by physical devices.
Jaewan Kim19b984f2023-12-04 15:16:50 +0900887 let phandle = dtbo_node.get_phandle()?.ok_or(DeviceAssignmentError::InvalidDtbo)?;
Jaewan Kim80ef9fa2024-02-25 16:08:14 +0000888 let Some(physical_device) = physical_devices.get(&phandle) else {
889 // If labeled DT node isn't backed by physical device node, then just return None.
890 // It's not an error because such node can be a dependency of assignable device nodes.
891 return Ok(None);
892 };
Jaewan Kim19b984f2023-12-04 15:16:50 +0900893
894 let reg = parse_node_reg(&node)?;
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +0100895 Self::validate_reg(&reg, &physical_device.reg, hypervisor, granule)?;
Jaewan Kim19b984f2023-12-04 15:16:50 +0900896
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900897 let interrupts = Self::parse_interrupts(&node)?;
Jaewan Kim19b984f2023-12-04 15:16:50 +0900898
Sreenad Menon84599282025-01-27 20:47:52 -0800899 // Ignore <iommus> if no pvIOMMUs are expected based on the VM DTBO, possibly
900 // because physical IOMMUs are being assigned directly.
901 let iommus = if let Some(iommus) = &physical_device.iommus {
902 let parsed_iommus = Self::parse_iommus(&node, pviommus)?;
903 Self::validate_iommus(&parsed_iommus, iommus, hypervisor)?;
904 Some(parsed_iommus)
905 } else {
906 // TODO: Detect misconfigured iommus in input DT.
907 None
908 };
Jaewan Kim19b984f2023-12-04 15:16:50 +0900909
Jaewan Kimf8abbb52023-12-12 22:11:39 +0900910 Ok(Some(Self { node_path, reg, interrupts, iommus }))
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900911 }
912
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900913 fn patch(&self, fdt: &mut Fdt, pviommu_phandles: &BTreeMap<PvIommu, Phandle>) -> Result<()> {
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900914 let mut dst = fdt.node_mut(&self.node_path)?.unwrap();
Alan Stokesf46a17c2025-01-05 15:50:18 +0000915 dst.setprop(c"reg", &to_be_bytes(&self.reg))?;
Sreenad Menon474da802025-01-27 21:22:46 -0800916 if let Some(interrupts) = &self.interrupts {
917 dst.setprop(c"interrupts", interrupts)?;
918 } else {
919 dst.nop_property(c"interrupts")?;
920 }
Sreenad Menon84599282025-01-27 20:47:52 -0800921
922 if let Some(iommus) = &self.iommus {
923 let mut iommus_vec = Vec::with_capacity(8 * iommus.len());
924 for (pviommu, vsid) in iommus {
925 let phandle = pviommu_phandles.get(pviommu).unwrap();
926 iommus_vec.extend_from_slice(&u32::from(*phandle).to_be_bytes());
927 iommus_vec.extend_from_slice(&vsid.0.to_be_bytes());
928 }
929 dst.setprop(c"iommus", &iommus_vec)?;
Jaewan Kima9200492023-11-21 20:45:31 +0900930 }
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900931
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900932 Ok(())
933 }
934}
935
Jaewan Kim8f6f4662023-12-12 17:38:47 +0900936#[derive(Debug, Eq, PartialEq)]
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900937pub struct DeviceAssignmentInfo {
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900938 pviommus: BTreeSet<PvIommu>,
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900939 assigned_devices: Vec<AssignedDeviceInfo>,
Jaewan Kim8f6f4662023-12-12 17:38:47 +0900940 vm_dtbo_mask: DeviceTreeMask,
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900941}
942
943impl DeviceAssignmentInfo {
Alan Stokesf46a17c2025-01-05 15:50:18 +0000944 const PVIOMMU_COMPATIBLE: &'static CStr = c"pkvm,pviommu";
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900945
946 /// Parses pvIOMMUs in fdt
947 // Note: This will validate pvIOMMU ids' uniqueness, even when unassigned.
948 fn parse_pviommus(fdt: &Fdt) -> Result<BTreeMap<Phandle, PvIommu>> {
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900949 let mut pviommus = BTreeMap::new();
950 for compatible in fdt.compatible_nodes(Self::PVIOMMU_COMPATIBLE)? {
951 let Some(phandle) = compatible.get_phandle()? else {
952 continue; // Skips unreachable pvIOMMU node
953 };
954 let pviommu = PvIommu::parse(&compatible)?;
955 if pviommus.insert(phandle, pviommu).is_some() {
956 return Err(FdtError::BadPhandle.into());
957 }
958 }
959 Ok(pviommus)
960 }
961
Jaewan Kim19b984f2023-12-04 15:16:50 +0900962 fn validate_pviommu_topology(assigned_devices: &[AssignedDeviceInfo]) -> Result<()> {
963 let mut all_iommus = BTreeSet::new();
964 for assigned_device in assigned_devices {
Sreenad Menon84599282025-01-27 20:47:52 -0800965 if let Some(iommus) = &assigned_device.iommus {
966 for iommu in iommus {
967 if !all_iommus.insert(iommu) {
968 error!("Unsupported pvIOMMU duplication found, <iommus> = {iommu:?}");
969 return Err(DeviceAssignmentError::UnsupportedPvIommusDuplication);
970 }
Jaewan Kim19b984f2023-12-04 15:16:50 +0900971 }
972 }
973 }
974 Ok(())
975 }
976
Pierre-Clément Tosie5cca922024-04-30 17:54:08 +0100977 // TODO(b/308694211): Remove this workaround for visibility once using
978 // vmbase::hyp::DeviceAssigningHypervisor for tests.
979 #[cfg(test)]
980 fn parse(
981 fdt: &Fdt,
982 vm_dtbo: &VmDtbo,
983 hypervisor: &dyn DeviceAssigningHypervisor,
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +0100984 granule: usize,
Pierre-Clément Tosie5cca922024-04-30 17:54:08 +0100985 ) -> Result<Option<Self>> {
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +0100986 Self::internal_parse(fdt, vm_dtbo, hypervisor, granule)
Pierre-Clément Tosie5cca922024-04-30 17:54:08 +0100987 }
988
989 #[cfg(not(test))]
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900990 /// Parses fdt and vm_dtbo, and creates new DeviceAssignmentInfo
991 // TODO(b/277993056): Parse __local_fixups__
992 // TODO(b/277993056): Parse __fixups__
Jaewan Kim52477ae2023-11-21 21:20:52 +0900993 pub fn parse(
994 fdt: &Fdt,
995 vm_dtbo: &VmDtbo,
996 hypervisor: &dyn DeviceAssigningHypervisor,
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +0100997 granule: usize,
Jaewan Kim52477ae2023-11-21 21:20:52 +0900998 ) -> Result<Option<Self>> {
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +0100999 Self::internal_parse(fdt, vm_dtbo, hypervisor, granule)
Pierre-Clément Tosie5cca922024-04-30 17:54:08 +01001000 }
1001
1002 fn internal_parse(
1003 fdt: &Fdt,
1004 vm_dtbo: &VmDtbo,
1005 hypervisor: &dyn DeviceAssigningHypervisor,
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +01001006 granule: usize,
Pierre-Clément Tosie5cca922024-04-30 17:54:08 +01001007 ) -> Result<Option<Self>> {
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001008 let Some(symbols_node) = vm_dtbo.as_ref().symbols()? else {
1009 // /__symbols__ should contain all assignable devices.
1010 // If empty, then nothing can be assigned.
1011 return Ok(None);
1012 };
1013
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001014 let pviommus = Self::parse_pviommus(fdt)?;
1015 let unique_pviommus: BTreeSet<_> = pviommus.values().cloned().collect();
1016 if pviommus.len() != unique_pviommus.len() {
1017 return Err(DeviceAssignmentError::DuplicatedPvIommuIds);
1018 }
1019
Jaewan Kim19b984f2023-12-04 15:16:50 +09001020 let physical_devices = vm_dtbo.parse_physical_devices()?;
1021
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001022 let mut assigned_devices = vec![];
Jaewan Kim8f6f4662023-12-12 17:38:47 +09001023 let mut assigned_device_paths = vec![];
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001024 for symbol_prop in symbols_node.properties()? {
1025 let symbol_prop_value = symbol_prop.value()?;
1026 let dtbo_node_path = CStr::from_bytes_with_nul(symbol_prop_value)
1027 .or(Err(DeviceAssignmentError::InvalidSymbols))?;
Jaewan Kimf8abbb52023-12-12 22:11:39 +09001028 let dtbo_node_path = DtPathTokens::new(dtbo_node_path)?;
1029 if !dtbo_node_path.is_overlayable_node() {
Jaewan Kimc39974e2023-12-02 01:13:30 +09001030 continue;
1031 }
Jaewan Kim19b984f2023-12-04 15:16:50 +09001032 let assigned_device = AssignedDeviceInfo::parse(
1033 fdt,
1034 vm_dtbo,
Jaewan Kimf8abbb52023-12-12 22:11:39 +09001035 &dtbo_node_path,
Jaewan Kim19b984f2023-12-04 15:16:50 +09001036 &physical_devices,
1037 &pviommus,
1038 hypervisor,
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +01001039 granule,
Jaewan Kim19b984f2023-12-04 15:16:50 +09001040 )?;
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001041 if let Some(assigned_device) = assigned_device {
1042 assigned_devices.push(assigned_device);
Jaewan Kim8f6f4662023-12-12 17:38:47 +09001043 assigned_device_paths.push(dtbo_node_path);
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001044 }
1045 }
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001046 if assigned_devices.is_empty() {
1047 return Ok(None);
1048 }
Jaewan Kimc39974e2023-12-02 01:13:30 +09001049
Jaewan Kim19b984f2023-12-04 15:16:50 +09001050 Self::validate_pviommu_topology(&assigned_devices)?;
1051
Jaewan Kim8f6f4662023-12-12 17:38:47 +09001052 let mut vm_dtbo_mask = vm_dtbo.build_mask(assigned_device_paths)?;
Alan Stokesf46a17c2025-01-05 15:50:18 +00001053 vm_dtbo_mask.mask_all(&DtPathTokens::new(c"/__local_fixups__")?);
1054 vm_dtbo_mask.mask_all(&DtPathTokens::new(c"/__symbols__")?);
Jaewan Kimc39974e2023-12-02 01:13:30 +09001055
1056 // Note: Any node without __overlay__ will be ignored by fdt_apply_overlay,
1057 // so doesn't need to be filtered.
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001058
Jaewan Kim8f6f4662023-12-12 17:38:47 +09001059 Ok(Some(Self { pviommus: unique_pviommus, assigned_devices, vm_dtbo_mask }))
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001060 }
1061
1062 /// Filters VM DTBO to only contain necessary information for booting pVM
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001063 pub fn filter(&self, vm_dtbo: &mut VmDtbo) -> Result<()> {
1064 let vm_dtbo = vm_dtbo.as_mut();
1065
Jaewan Kim8f6f4662023-12-12 17:38:47 +09001066 // Filter unused references in /__local_fixups__
Alan Stokesf46a17c2025-01-05 15:50:18 +00001067 if let Some(local_fixups) = vm_dtbo.node_mut(c"/__local_fixups__")? {
Jaewan Kim8f6f4662023-12-12 17:38:47 +09001068 filter_with_mask(local_fixups, &self.vm_dtbo_mask)?;
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001069 }
1070
Jaewan Kim8f6f4662023-12-12 17:38:47 +09001071 // Filter unused nodes in rest of tree
1072 let root = vm_dtbo.root_mut();
1073 filter_with_mask(root, &self.vm_dtbo_mask)?;
1074
Jaewan Kim371f6c82024-02-24 01:33:37 +09001075 filter_dangling_symbols(vm_dtbo)
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001076 }
1077
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001078 fn patch_pviommus(&self, fdt: &mut Fdt) -> Result<BTreeMap<PvIommu, Phandle>> {
Pierre-Clément Tosi244efea2024-02-16 14:48:14 +00001079 let mut compatible = fdt.root_mut().next_compatible(Self::PVIOMMU_COMPATIBLE)?;
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001080 let mut pviommu_phandles = BTreeMap::new();
1081
1082 for pviommu in &self.pviommus {
1083 let mut node = compatible.ok_or(DeviceAssignmentError::TooManyPvIommu)?;
1084 let phandle = node.as_node().get_phandle()?.ok_or(DeviceAssignmentError::Internal)?;
Alan Stokesf46a17c2025-01-05 15:50:18 +00001085 node.setprop_inplace(c"id", &pviommu.id.to_be_bytes())?;
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001086 if pviommu_phandles.insert(*pviommu, phandle).is_some() {
1087 return Err(DeviceAssignmentError::Internal);
1088 }
1089 compatible = node.next_compatible(Self::PVIOMMU_COMPATIBLE)?;
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001090 }
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001091
1092 // Filters pre-populated but unassigned pvIOMMUs.
1093 while let Some(filtered_pviommu) = compatible {
1094 compatible = filtered_pviommu.delete_and_next_compatible(Self::PVIOMMU_COMPATIBLE)?;
1095 }
1096
1097 Ok(pviommu_phandles)
1098 }
1099
1100 pub fn patch(&self, fdt: &mut Fdt) -> Result<()> {
1101 let pviommu_phandles = self.patch_pviommus(fdt)?;
1102
1103 // Patches assigned devices
1104 for device in &self.assigned_devices {
1105 device.patch(fdt, &pviommu_phandles)?;
1106 }
1107
Jaewan Kimc730ebf2024-02-22 10:34:55 +09001108 // Removes any dangling references in __symbols__ (e.g. removed pvIOMMUs)
1109 filter_dangling_symbols(fdt)
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001110 }
1111}
1112
Jaewan Kim50246682024-03-11 23:18:54 +09001113/// Cleans device trees not to contain any pre-populated nodes/props for device assignment.
1114pub fn clean(fdt: &mut Fdt) -> Result<()> {
Alan Stokesf46a17c2025-01-05 15:50:18 +00001115 let mut compatible = fdt.root_mut().next_compatible(c"pkvm,pviommu")?;
Jaewan Kim50246682024-03-11 23:18:54 +09001116 // Filters pre-populated
1117 while let Some(filtered_pviommu) = compatible {
Alan Stokesf46a17c2025-01-05 15:50:18 +00001118 compatible = filtered_pviommu.delete_and_next_compatible(c"pkvm,pviommu")?;
Jaewan Kim50246682024-03-11 23:18:54 +09001119 }
1120
1121 // Removes any dangling references in __symbols__ (e.g. removed pvIOMMUs)
1122 filter_dangling_symbols(fdt)
1123}
1124
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001125#[cfg(test)]
Pierre-Clément Tosie5cca922024-04-30 17:54:08 +01001126#[derive(Clone, Copy, Debug)]
1127enum MockHypervisorError {
1128 FailedGetPhysMmioToken,
1129 FailedGetPhysIommuToken,
1130}
1131
1132#[cfg(test)]
1133type MockHypervisorResult<T> = core::result::Result<T, MockHypervisorError>;
1134
1135#[cfg(test)]
1136impl fmt::Display for MockHypervisorError {
1137 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1138 match self {
1139 MockHypervisorError::FailedGetPhysMmioToken => {
1140 write!(f, "Failed to get physical MMIO token")
1141 }
1142 MockHypervisorError::FailedGetPhysIommuToken => {
1143 write!(f, "Failed to get physical IOMMU token")
1144 }
1145 }
1146 }
1147}
1148
1149#[cfg(test)]
1150trait DeviceAssigningHypervisor {
1151 /// Returns MMIO token.
Mostafa Saleh646a31b2024-10-22 12:55:59 +00001152 fn get_phys_mmio_token(&self, base_ipa: u64) -> MockHypervisorResult<u64>;
Pierre-Clément Tosie5cca922024-04-30 17:54:08 +01001153
1154 /// Returns DMA token as a tuple of (phys_iommu_id, phys_sid).
1155 fn get_phys_iommu_token(&self, pviommu_id: u64, vsid: u64) -> MockHypervisorResult<(u64, u64)>;
1156}
1157
1158#[cfg(test)]
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001159mod tests {
1160 use super::*;
Jaewan Kim52477ae2023-11-21 21:20:52 +09001161 use alloc::collections::{BTreeMap, BTreeSet};
Jaewan Kim8f6f4662023-12-12 17:38:47 +09001162 use dts::Dts;
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001163 use std::fs;
Jaewan Kim8f6f4662023-12-12 17:38:47 +09001164 use std::path::Path;
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001165
1166 const VM_DTBO_FILE_PATH: &str = "test_pvmfw_devices_vm_dtbo.dtbo";
1167 const VM_DTBO_WITHOUT_SYMBOLS_FILE_PATH: &str =
1168 "test_pvmfw_devices_vm_dtbo_without_symbols.dtbo";
Jaewan Kim19b984f2023-12-04 15:16:50 +09001169 const VM_DTBO_WITH_DUPLICATED_IOMMUS_FILE_PATH: &str =
1170 "test_pvmfw_devices_vm_dtbo_with_duplicated_iommus.dtbo";
Jaewan Kim8f6f4662023-12-12 17:38:47 +09001171 const VM_DTBO_WITH_DEPENDENCIES_FILE_PATH: &str =
1172 "test_pvmfw_devices_vm_dtbo_with_dependencies.dtbo";
Jaewan Kima67e36a2023-11-29 16:50:23 +09001173 const FDT_WITHOUT_IOMMUS_FILE_PATH: &str = "test_pvmfw_devices_without_iommus.dtb";
Jaewan Kim52477ae2023-11-21 21:20:52 +09001174 const FDT_WITHOUT_DEVICE_FILE_PATH: &str = "test_pvmfw_devices_without_device.dtb";
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001175 const FDT_FILE_PATH: &str = "test_pvmfw_devices_with_rng.dtb";
Pierre-Clément Tosi49e26ce2024-03-12 16:31:50 +00001176 const FDT_WITH_DEVICE_OVERLAPPING_PVMFW: &str = "test_pvmfw_devices_overlapping_pvmfw.dtb";
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001177 const FDT_WITH_MULTIPLE_DEVICES_IOMMUS_FILE_PATH: &str =
1178 "test_pvmfw_devices_with_multiple_devices_iommus.dtb";
1179 const FDT_WITH_IOMMU_SHARING: &str = "test_pvmfw_devices_with_iommu_sharing.dtb";
1180 const FDT_WITH_IOMMU_ID_CONFLICT: &str = "test_pvmfw_devices_with_iommu_id_conflict.dtb";
Jaewan Kim19b984f2023-12-04 15:16:50 +09001181 const FDT_WITH_DUPLICATED_PVIOMMUS_FILE_PATH: &str =
1182 "test_pvmfw_devices_with_duplicated_pviommus.dtb";
1183 const FDT_WITH_MULTIPLE_REG_IOMMU_FILE_PATH: &str =
1184 "test_pvmfw_devices_with_multiple_reg_iommus.dtb";
Jaewan Kim8f6f4662023-12-12 17:38:47 +09001185 const FDT_WITH_DEPENDENCY_FILE_PATH: &str = "test_pvmfw_devices_with_dependency.dtb";
1186 const FDT_WITH_MULTIPLE_DEPENDENCIES_FILE_PATH: &str =
1187 "test_pvmfw_devices_with_multiple_dependencies.dtb";
1188 const FDT_WITH_DEPENDENCY_LOOP_FILE_PATH: &str = "test_pvmfw_devices_with_dependency_loop.dtb";
1189
1190 const EXPECTED_FDT_WITH_DEPENDENCY_FILE_PATH: &str = "expected_dt_with_dependency.dtb";
1191 const EXPECTED_FDT_WITH_MULTIPLE_DEPENDENCIES_FILE_PATH: &str =
1192 "expected_dt_with_multiple_dependencies.dtb";
1193 const EXPECTED_FDT_WITH_DEPENDENCY_LOOP_FILE_PATH: &str =
1194 "expected_dt_with_dependency_loop.dtb";
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001195
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +01001196 // TODO(b/308694211): Use vmbase::SIZE_4KB.
1197 const SIZE_4KB: usize = 4 << 10;
1198
Jaewan Kim52477ae2023-11-21 21:20:52 +09001199 #[derive(Debug, Default)]
1200 struct MockHypervisor {
1201 mmio_tokens: BTreeMap<(u64, u64), u64>,
1202 iommu_tokens: BTreeMap<(u64, u64), (u64, u64)>,
1203 }
1204
Pierre-Clément Tosi5ce6c6f2024-10-29 10:53:07 +00001205 impl MockHypervisor {
1206 // TODO(ptosi): Improve these tests to cover multi-page devices.
1207 fn get_mmio_token(&self, addr: u64) -> Option<&u64> {
1208 // We currently only have single (or sub-) page MMIO test data so can ignore sizes.
1209 let key = self.mmio_tokens.keys().find(|(virt, _)| *virt == addr)?;
1210 self.mmio_tokens.get(key)
1211 }
1212 }
1213
Jaewan Kim52477ae2023-11-21 21:20:52 +09001214 impl DeviceAssigningHypervisor for MockHypervisor {
Mostafa Saleh646a31b2024-10-22 12:55:59 +00001215 fn get_phys_mmio_token(&self, base_ipa: u64) -> MockHypervisorResult<u64> {
Pierre-Clément Tosi5ce6c6f2024-10-29 10:53:07 +00001216 let token = self.get_mmio_token(base_ipa);
Pierre-Clément Tosie5cca922024-04-30 17:54:08 +01001217
1218 Ok(*token.ok_or(MockHypervisorError::FailedGetPhysMmioToken)?)
Jaewan Kim52477ae2023-11-21 21:20:52 +09001219 }
1220
Pierre-Clément Tosie5cca922024-04-30 17:54:08 +01001221 fn get_phys_iommu_token(
1222 &self,
1223 pviommu_id: u64,
1224 vsid: u64,
1225 ) -> MockHypervisorResult<(u64, u64)> {
1226 let token = self.iommu_tokens.get(&(pviommu_id, vsid));
1227
1228 Ok(*token.ok_or(MockHypervisorError::FailedGetPhysIommuToken)?)
Jaewan Kim52477ae2023-11-21 21:20:52 +09001229 }
1230 }
1231
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001232 #[derive(Debug, Eq, PartialEq)]
1233 struct AssignedDeviceNode {
1234 path: CString,
1235 reg: Vec<u8>,
1236 interrupts: Vec<u8>,
Jaewan Kima67e36a2023-11-29 16:50:23 +09001237 iommus: Vec<u32>, // pvIOMMU id and vSID
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001238 }
1239
1240 impl AssignedDeviceNode {
1241 fn parse(fdt: &Fdt, path: &CStr) -> Result<Self> {
1242 let Some(node) = fdt.node(path)? else {
1243 return Err(FdtError::NotFound.into());
1244 };
1245
Alan Stokesf46a17c2025-01-05 15:50:18 +00001246 let reg = node.getprop(c"reg")?.ok_or(DeviceAssignmentError::MalformedReg)?;
1247 let interrupts =
1248 node.getprop(c"interrupts")?.ok_or(DeviceAssignmentError::InvalidInterrupts)?;
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001249 let mut iommus = vec![];
Alan Stokesf46a17c2025-01-05 15:50:18 +00001250 if let Some(mut cells) = node.getprop_cells(c"iommus")? {
Jaewan Kima9200492023-11-21 20:45:31 +09001251 while let Some(pviommu_id) = cells.next() {
1252 // pvIOMMU id
1253 let phandle = Phandle::try_from(pviommu_id)?;
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001254 let pviommu = fdt
1255 .node_with_phandle(phandle)?
Jaewan Kim19b984f2023-12-04 15:16:50 +09001256 .ok_or(DeviceAssignmentError::MalformedIommus)?;
Alan Stokesf46a17c2025-01-05 15:50:18 +00001257 let compatible = pviommu.getprop_str(c"compatible");
1258 if compatible != Ok(Some(c"pkvm,pviommu")) {
Jaewan Kim19b984f2023-12-04 15:16:50 +09001259 return Err(DeviceAssignmentError::MalformedIommus);
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001260 }
1261 let id = pviommu
Alan Stokesf46a17c2025-01-05 15:50:18 +00001262 .getprop_u32(c"id")?
Jaewan Kim19b984f2023-12-04 15:16:50 +09001263 .ok_or(DeviceAssignmentError::MalformedIommus)?;
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001264 iommus.push(id);
Jaewan Kima9200492023-11-21 20:45:31 +09001265
1266 // vSID
1267 let Some(vsid) = cells.next() else {
Jaewan Kim19b984f2023-12-04 15:16:50 +09001268 return Err(DeviceAssignmentError::MalformedIommus);
Jaewan Kima9200492023-11-21 20:45:31 +09001269 };
1270 iommus.push(vsid);
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001271 }
1272 }
1273 Ok(Self { path: path.into(), reg: reg.into(), interrupts: interrupts.into(), iommus })
1274 }
1275 }
1276
1277 fn collect_pviommus(fdt: &Fdt) -> Result<Vec<u32>> {
1278 let mut pviommus = BTreeSet::new();
Alan Stokesf46a17c2025-01-05 15:50:18 +00001279 for pviommu in fdt.compatible_nodes(c"pkvm,pviommu")? {
1280 if let Ok(Some(id)) = pviommu.getprop_u32(c"id") {
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001281 pviommus.insert(id);
1282 }
1283 }
1284 Ok(pviommus.iter().cloned().collect())
1285 }
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001286
1287 fn into_fdt_prop(native_bytes: Vec<u32>) -> Vec<u8> {
1288 let mut v = Vec::with_capacity(native_bytes.len() * 4);
1289 for byte in native_bytes {
1290 v.extend_from_slice(&byte.to_be_bytes());
1291 }
1292 v
1293 }
1294
Jaewan Kim52477ae2023-11-21 21:20:52 +09001295 impl From<[u64; 2]> for DeviceReg {
1296 fn from(fdt_cells: [u64; 2]) -> Self {
1297 DeviceReg { addr: fdt_cells[0], size: fdt_cells[1] }
1298 }
1299 }
1300
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +01001301 // TODO(ptosi): Add tests with varying HYP_GRANULE values.
1302
Sreenad Menon84599282025-01-27 20:47:52 -08001303 // TODO(ptosi): Add tests with iommus.is_none()
1304
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001305 #[test]
1306 fn device_info_new_without_symbols() {
1307 let mut fdt_data = fs::read(FDT_FILE_PATH).unwrap();
1308 let mut vm_dtbo_data = fs::read(VM_DTBO_WITHOUT_SYMBOLS_FILE_PATH).unwrap();
1309 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1310 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1311
Jaewan Kim52477ae2023-11-21 21:20:52 +09001312 let hypervisor: MockHypervisor = Default::default();
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +01001313 const HYP_GRANULE: usize = SIZE_4KB;
1314 let device_info =
1315 DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor, HYP_GRANULE).unwrap();
Jaewan Kim52477ae2023-11-21 21:20:52 +09001316 assert_eq!(device_info, None);
1317 }
1318
1319 #[test]
1320 fn device_info_new_without_device() {
1321 let mut fdt_data = fs::read(FDT_WITHOUT_DEVICE_FILE_PATH).unwrap();
1322 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1323 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1324 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1325
1326 let hypervisor: MockHypervisor = Default::default();
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +01001327 const HYP_GRANULE: usize = SIZE_4KB;
1328 let device_info =
1329 DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor, HYP_GRANULE).unwrap();
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001330 assert_eq!(device_info, None);
1331 }
1332
1333 #[test]
Jaewan Kima67e36a2023-11-29 16:50:23 +09001334 fn device_info_assigned_info_without_iommus() {
1335 let mut fdt_data = fs::read(FDT_WITHOUT_IOMMUS_FILE_PATH).unwrap();
1336 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1337 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1338 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1339
Jaewan Kim52477ae2023-11-21 21:20:52 +09001340 let hypervisor = MockHypervisor {
1341 mmio_tokens: [((0x9, 0xFF), 0x300)].into(),
1342 iommu_tokens: BTreeMap::new(),
1343 };
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +01001344 const HYP_GRANULE: usize = SIZE_4KB;
1345 let device_info =
1346 DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor, HYP_GRANULE).unwrap().unwrap();
Jaewan Kima67e36a2023-11-29 16:50:23 +09001347
1348 let expected = [AssignedDeviceInfo {
Jaewan Kimc39974e2023-12-02 01:13:30 +09001349 node_path: CString::new("/bus0/backlight").unwrap(),
Jaewan Kim52477ae2023-11-21 21:20:52 +09001350 reg: vec![[0x9, 0xFF].into()],
Sreenad Menon474da802025-01-27 21:22:46 -08001351 interrupts: Some(into_fdt_prop(vec![0x0, 0xF, 0x4])),
Frederick Mayle4d27e7e2025-03-21 14:57:26 -07001352 iommus: None,
Jaewan Kima67e36a2023-11-29 16:50:23 +09001353 }];
1354
1355 assert_eq!(device_info.assigned_devices, expected);
1356 }
1357
1358 #[test]
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001359 fn device_info_assigned_info() {
1360 let mut fdt_data = fs::read(FDT_FILE_PATH).unwrap();
1361 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1362 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1363 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1364
Jaewan Kim52477ae2023-11-21 21:20:52 +09001365 let hypervisor = MockHypervisor {
1366 mmio_tokens: [((0x9, 0xFF), 0x12F00000)].into(),
1367 iommu_tokens: [((0x4, 0xFF0), (0x12E40000, 0x3))].into(),
1368 };
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +01001369 const HYP_GRANULE: usize = SIZE_4KB;
1370 let device_info =
1371 DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor, HYP_GRANULE).unwrap().unwrap();
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001372
1373 let expected = [AssignedDeviceInfo {
1374 node_path: CString::new("/rng").unwrap(),
Jaewan Kim52477ae2023-11-21 21:20:52 +09001375 reg: vec![[0x9, 0xFF].into()],
Sreenad Menon474da802025-01-27 21:22:46 -08001376 interrupts: Some(into_fdt_prop(vec![0x0, 0xF, 0x4])),
Sreenad Menon84599282025-01-27 20:47:52 -08001377 iommus: Some(vec![(PvIommu { id: 0x4 }, Vsid(0xFF0))]),
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001378 }];
1379
1380 assert_eq!(device_info.assigned_devices, expected);
1381 }
1382
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001383 #[test]
1384 fn device_info_filter() {
1385 let mut fdt_data = fs::read(FDT_FILE_PATH).unwrap();
1386 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1387 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1388 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1389
Jaewan Kim52477ae2023-11-21 21:20:52 +09001390 let hypervisor = MockHypervisor {
1391 mmio_tokens: [((0x9, 0xFF), 0x12F00000)].into(),
1392 iommu_tokens: [((0x4, 0xFF0), (0x12E40000, 0x3))].into(),
1393 };
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +01001394 const HYP_GRANULE: usize = SIZE_4KB;
1395 let device_info =
1396 DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor, HYP_GRANULE).unwrap().unwrap();
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001397 device_info.filter(vm_dtbo).unwrap();
1398
1399 let vm_dtbo = vm_dtbo.as_mut();
1400
Jaewan Kim371f6c82024-02-24 01:33:37 +09001401 let symbols = vm_dtbo.symbols().unwrap().unwrap();
1402
Alan Stokesf46a17c2025-01-05 15:50:18 +00001403 let rng = vm_dtbo.node(c"/fragment@0/__overlay__/rng").unwrap();
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001404 assert_ne!(rng, None);
Alan Stokesf46a17c2025-01-05 15:50:18 +00001405 let rng_symbol = symbols.getprop_str(c"rng").unwrap();
1406 assert_eq!(Some(c"/fragment@0/__overlay__/rng"), rng_symbol);
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001407
Alan Stokesf46a17c2025-01-05 15:50:18 +00001408 let light = vm_dtbo.node(c"/fragment@0/__overlay__/light").unwrap();
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001409 assert_eq!(light, None);
Alan Stokesf46a17c2025-01-05 15:50:18 +00001410 let light_symbol = symbols.getprop_str(c"light").unwrap();
Jaewan Kim371f6c82024-02-24 01:33:37 +09001411 assert_eq!(None, light_symbol);
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001412
Alan Stokesf46a17c2025-01-05 15:50:18 +00001413 let led = vm_dtbo.node(c"/fragment@0/__overlay__/led").unwrap();
Jaewan Kima67e36a2023-11-29 16:50:23 +09001414 assert_eq!(led, None);
Alan Stokesf46a17c2025-01-05 15:50:18 +00001415 let led_symbol = symbols.getprop_str(c"led").unwrap();
Jaewan Kim371f6c82024-02-24 01:33:37 +09001416 assert_eq!(None, led_symbol);
Jaewan Kima67e36a2023-11-29 16:50:23 +09001417
Alan Stokesf46a17c2025-01-05 15:50:18 +00001418 let backlight = vm_dtbo.node(c"/fragment@0/__overlay__/bus0/backlight").unwrap();
Jaewan Kima67e36a2023-11-29 16:50:23 +09001419 assert_eq!(backlight, None);
Alan Stokesf46a17c2025-01-05 15:50:18 +00001420 let backlight_symbol = symbols.getprop_str(c"backlight").unwrap();
Jaewan Kim371f6c82024-02-24 01:33:37 +09001421 assert_eq!(None, backlight_symbol);
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001422 }
1423
1424 #[test]
1425 fn device_info_patch() {
Jaewan Kima67e36a2023-11-29 16:50:23 +09001426 let mut fdt_data = fs::read(FDT_WITHOUT_IOMMUS_FILE_PATH).unwrap();
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001427 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1428 let mut data = vec![0_u8; fdt_data.len() + vm_dtbo_data.len()];
1429 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1430 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1431 let platform_dt = Fdt::create_empty_tree(data.as_mut_slice()).unwrap();
1432
Jaewan Kim52477ae2023-11-21 21:20:52 +09001433 let hypervisor = MockHypervisor {
1434 mmio_tokens: [((0x9, 0xFF), 0x300)].into(),
1435 iommu_tokens: BTreeMap::new(),
1436 };
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +01001437 const HYP_GRANULE: usize = SIZE_4KB;
1438 let device_info =
1439 DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor, HYP_GRANULE).unwrap().unwrap();
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001440 device_info.filter(vm_dtbo).unwrap();
1441
1442 // SAFETY: Damaged VM DTBO wouldn't be used after this unsafe block.
1443 unsafe {
1444 platform_dt.apply_overlay(vm_dtbo.as_mut()).unwrap();
1445 }
Jaewan Kim0bd637d2023-11-10 13:09:41 +09001446 device_info.patch(platform_dt).unwrap();
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001447
Alan Stokesf46a17c2025-01-05 15:50:18 +00001448 let rng_node = platform_dt.node(c"/bus0/backlight").unwrap().unwrap();
1449 let phandle = rng_node.getprop_u32(c"phandle").unwrap();
Jaewan Kimc39974e2023-12-02 01:13:30 +09001450 assert_ne!(None, phandle);
1451
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001452 // Note: Intentionally not using AssignedDeviceNode for matching all props.
Jaewan Kim0bd637d2023-11-10 13:09:41 +09001453 type FdtResult<T> = libfdt::Result<T>;
1454 let expected: Vec<(FdtResult<&CStr>, FdtResult<Vec<u8>>)> = vec![
Alan Stokesf46a17c2025-01-05 15:50:18 +00001455 (Ok(c"android,backlight,ignore-gctrl-reset"), Ok(Vec::new())),
1456 (Ok(c"compatible"), Ok(Vec::from(*b"android,backlight\0"))),
1457 (Ok(c"interrupts"), Ok(into_fdt_prop(vec![0x0, 0xF, 0x4]))),
Alan Stokesf46a17c2025-01-05 15:50:18 +00001458 (Ok(c"phandle"), Ok(into_fdt_prop(vec![phandle.unwrap()]))),
1459 (Ok(c"reg"), Ok(into_fdt_prop(vec![0x0, 0x9, 0x0, 0xFF]))),
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001460 ];
1461
Jaewan Kim0bd637d2023-11-10 13:09:41 +09001462 let mut properties: Vec<_> = rng_node
1463 .properties()
1464 .unwrap()
1465 .map(|prop| (prop.name(), prop.value().map(|x| x.into())))
1466 .collect();
1467 properties.sort_by(|a, b| {
1468 let lhs = a.0.unwrap_or_default();
1469 let rhs = b.0.unwrap_or_default();
1470 lhs.partial_cmp(rhs).unwrap()
1471 });
1472
1473 assert_eq!(properties, expected);
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001474 }
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001475
1476 #[test]
Jaewan Kimc730ebf2024-02-22 10:34:55 +09001477 fn device_info_patch_no_pviommus() {
1478 let mut fdt_data = fs::read(FDT_WITHOUT_IOMMUS_FILE_PATH).unwrap();
1479 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1480 let mut data = vec![0_u8; fdt_data.len() + vm_dtbo_data.len()];
1481 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1482 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1483 let platform_dt = Fdt::create_empty_tree(data.as_mut_slice()).unwrap();
1484
1485 let hypervisor = MockHypervisor {
1486 mmio_tokens: [((0x9, 0xFF), 0x300)].into(),
1487 iommu_tokens: BTreeMap::new(),
1488 };
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +01001489 const HYP_GRANULE: usize = SIZE_4KB;
1490 let device_info =
1491 DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor, HYP_GRANULE).unwrap().unwrap();
Jaewan Kimc730ebf2024-02-22 10:34:55 +09001492 device_info.filter(vm_dtbo).unwrap();
1493
1494 // SAFETY: Damaged VM DTBO wouldn't be used after this unsafe block.
1495 unsafe {
1496 platform_dt.apply_overlay(vm_dtbo.as_mut()).unwrap();
1497 }
1498 device_info.patch(platform_dt).unwrap();
1499
Alan Stokesf46a17c2025-01-05 15:50:18 +00001500 let compatible = platform_dt.root().next_compatible(c"pkvm,pviommu").unwrap();
Jaewan Kimc730ebf2024-02-22 10:34:55 +09001501 assert_eq!(None, compatible);
1502
1503 if let Some(symbols) = platform_dt.symbols().unwrap() {
1504 for prop in symbols.properties().unwrap() {
1505 let path = CStr::from_bytes_with_nul(prop.value().unwrap()).unwrap();
1506 assert_ne!(None, platform_dt.node(path).unwrap());
1507 }
1508 }
1509 }
1510
1511 #[test]
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001512 fn device_info_overlay_iommu() {
Jaewan Kima67e36a2023-11-29 16:50:23 +09001513 let mut fdt_data = fs::read(FDT_FILE_PATH).unwrap();
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001514 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1515 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1516 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1517 let mut platform_dt_data = pvmfw_fdt_template::RAW.to_vec();
1518 platform_dt_data.resize(pvmfw_fdt_template::RAW.len() * 2, 0);
1519 let platform_dt = Fdt::from_mut_slice(&mut platform_dt_data).unwrap();
1520 platform_dt.unpack().unwrap();
1521
Jaewan Kim52477ae2023-11-21 21:20:52 +09001522 let hypervisor = MockHypervisor {
1523 mmio_tokens: [((0x9, 0xFF), 0x12F00000)].into(),
1524 iommu_tokens: [((0x4, 0xFF0), (0x12E40000, 0x3))].into(),
1525 };
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +01001526 const HYP_GRANULE: usize = SIZE_4KB;
1527 let device_info =
1528 DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor, HYP_GRANULE).unwrap().unwrap();
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001529 device_info.filter(vm_dtbo).unwrap();
1530
1531 // SAFETY: Damaged VM DTBO wouldn't be used after this unsafe block.
1532 unsafe {
1533 platform_dt.apply_overlay(vm_dtbo.as_mut()).unwrap();
1534 }
1535 device_info.patch(platform_dt).unwrap();
1536
1537 let expected = AssignedDeviceNode {
1538 path: CString::new("/rng").unwrap(),
1539 reg: into_fdt_prop(vec![0x0, 0x9, 0x0, 0xFF]),
1540 interrupts: into_fdt_prop(vec![0x0, 0xF, 0x4]),
Jaewan Kima9200492023-11-21 20:45:31 +09001541 iommus: vec![0x4, 0xFF0],
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001542 };
1543
1544 let node = AssignedDeviceNode::parse(platform_dt, &expected.path);
1545 assert_eq!(node, Ok(expected));
1546
1547 let pviommus = collect_pviommus(platform_dt);
1548 assert_eq!(pviommus, Ok(vec![0x4]));
1549 }
1550
1551 #[test]
1552 fn device_info_multiple_devices_iommus() {
1553 let mut fdt_data = fs::read(FDT_WITH_MULTIPLE_DEVICES_IOMMUS_FILE_PATH).unwrap();
1554 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1555 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1556 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1557 let mut platform_dt_data = pvmfw_fdt_template::RAW.to_vec();
1558 platform_dt_data.resize(pvmfw_fdt_template::RAW.len() * 2, 0);
1559 let platform_dt = Fdt::from_mut_slice(&mut platform_dt_data).unwrap();
1560 platform_dt.unpack().unwrap();
1561
Jaewan Kim52477ae2023-11-21 21:20:52 +09001562 let hypervisor = MockHypervisor {
1563 mmio_tokens: [
1564 ((0x9, 0xFF), 0x12F00000),
Jaewan Kim19b984f2023-12-04 15:16:50 +09001565 ((0x10000, 0x1000), 0xF00000),
1566 ((0x20000, 0x1000), 0xF10000),
Jaewan Kim52477ae2023-11-21 21:20:52 +09001567 ]
1568 .into(),
1569 iommu_tokens: [
1570 ((0x4, 0xFF0), (0x12E40000, 3)),
1571 ((0x40, 0xFFA), (0x40000, 0x4)),
1572 ((0x50, 0xFFB), (0x50000, 0x5)),
1573 ]
1574 .into(),
1575 };
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +01001576 const HYP_GRANULE: usize = SIZE_4KB;
1577 let device_info =
1578 DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor, HYP_GRANULE).unwrap().unwrap();
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001579 device_info.filter(vm_dtbo).unwrap();
1580
1581 // SAFETY: Damaged VM DTBO wouldn't be used after this unsafe block.
1582 unsafe {
1583 platform_dt.apply_overlay(vm_dtbo.as_mut()).unwrap();
1584 }
1585 device_info.patch(platform_dt).unwrap();
1586
1587 let expected_devices = [
1588 AssignedDeviceNode {
1589 path: CString::new("/rng").unwrap(),
1590 reg: into_fdt_prop(vec![0x0, 0x9, 0x0, 0xFF]),
1591 interrupts: into_fdt_prop(vec![0x0, 0xF, 0x4]),
Jaewan Kima67e36a2023-11-29 16:50:23 +09001592 iommus: vec![0x4, 0xFF0],
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001593 },
1594 AssignedDeviceNode {
1595 path: CString::new("/light").unwrap(),
Jaewan Kim19b984f2023-12-04 15:16:50 +09001596 reg: into_fdt_prop(vec![0x0, 0x10000, 0x0, 0x1000, 0x0, 0x20000, 0x0, 0x1000]),
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001597 interrupts: into_fdt_prop(vec![0x0, 0xF, 0x5]),
Jaewan Kima67e36a2023-11-29 16:50:23 +09001598 iommus: vec![0x40, 0xFFA, 0x50, 0xFFB],
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001599 },
1600 ];
1601
1602 for expected in expected_devices {
1603 let node = AssignedDeviceNode::parse(platform_dt, &expected.path);
1604 assert_eq!(node, Ok(expected));
1605 }
1606 let pviommus = collect_pviommus(platform_dt);
Jaewan Kima67e36a2023-11-29 16:50:23 +09001607 assert_eq!(pviommus, Ok(vec![0x4, 0x40, 0x50]));
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001608 }
1609
1610 #[test]
1611 fn device_info_iommu_sharing() {
1612 let mut fdt_data = fs::read(FDT_WITH_IOMMU_SHARING).unwrap();
1613 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1614 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1615 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1616 let mut platform_dt_data = pvmfw_fdt_template::RAW.to_vec();
1617 platform_dt_data.resize(pvmfw_fdt_template::RAW.len() * 2, 0);
1618 let platform_dt = Fdt::from_mut_slice(&mut platform_dt_data).unwrap();
1619 platform_dt.unpack().unwrap();
1620
Jaewan Kim52477ae2023-11-21 21:20:52 +09001621 let hypervisor = MockHypervisor {
Jaewan Kim19b984f2023-12-04 15:16:50 +09001622 mmio_tokens: [((0x9, 0xFF), 0x12F00000), ((0x1000, 0x9), 0x12000000)].into(),
1623 iommu_tokens: [((0x4, 0xFF0), (0x12E40000, 3)), ((0x4, 0xFF1), (0x12E40000, 9))].into(),
Jaewan Kim52477ae2023-11-21 21:20:52 +09001624 };
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +01001625 const HYP_GRANULE: usize = SIZE_4KB;
1626 let device_info =
1627 DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor, HYP_GRANULE).unwrap().unwrap();
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001628 device_info.filter(vm_dtbo).unwrap();
1629
1630 // SAFETY: Damaged VM DTBO wouldn't be used after this unsafe block.
1631 unsafe {
1632 platform_dt.apply_overlay(vm_dtbo.as_mut()).unwrap();
1633 }
1634 device_info.patch(platform_dt).unwrap();
1635
1636 let expected_devices = [
1637 AssignedDeviceNode {
1638 path: CString::new("/rng").unwrap(),
1639 reg: into_fdt_prop(vec![0x0, 0x9, 0x0, 0xFF]),
1640 interrupts: into_fdt_prop(vec![0x0, 0xF, 0x4]),
Jaewan Kima67e36a2023-11-29 16:50:23 +09001641 iommus: vec![0x4, 0xFF0],
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001642 },
1643 AssignedDeviceNode {
Jaewan Kima67e36a2023-11-29 16:50:23 +09001644 path: CString::new("/led").unwrap(),
Jaewan Kim19b984f2023-12-04 15:16:50 +09001645 reg: into_fdt_prop(vec![0x0, 0x1000, 0x0, 0x9]),
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001646 interrupts: into_fdt_prop(vec![0x0, 0xF, 0x5]),
Jaewan Kim19b984f2023-12-04 15:16:50 +09001647 iommus: vec![0x4, 0xFF1],
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001648 },
1649 ];
1650
1651 for expected in expected_devices {
1652 let node = AssignedDeviceNode::parse(platform_dt, &expected.path);
1653 assert_eq!(node, Ok(expected));
1654 }
1655
1656 let pviommus = collect_pviommus(platform_dt);
Jaewan Kima67e36a2023-11-29 16:50:23 +09001657 assert_eq!(pviommus, Ok(vec![0x4]));
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001658 }
1659
1660 #[test]
1661 fn device_info_iommu_id_conflict() {
1662 let mut fdt_data = fs::read(FDT_WITH_IOMMU_ID_CONFLICT).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
Jaewan Kim52477ae2023-11-21 21:20:52 +09001667 let hypervisor = MockHypervisor {
Jaewan Kim19b984f2023-12-04 15:16:50 +09001668 mmio_tokens: [((0x9, 0xFF), 0x300)].into(),
Jaewan Kim52477ae2023-11-21 21:20:52 +09001669 iommu_tokens: [((0x4, 0xFF0), (0x12E40000, 0x3))].into(),
1670 };
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +01001671 const HYP_GRANULE: usize = SIZE_4KB;
1672 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor, HYP_GRANULE);
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001673
1674 assert_eq!(device_info, Err(DeviceAssignmentError::DuplicatedPvIommuIds));
1675 }
Jaewan Kim52477ae2023-11-21 21:20:52 +09001676
1677 #[test]
1678 fn device_info_invalid_reg() {
1679 let mut fdt_data = fs::read(FDT_FILE_PATH).unwrap();
1680 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1681 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1682 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1683
1684 let hypervisor = MockHypervisor {
1685 mmio_tokens: BTreeMap::new(),
1686 iommu_tokens: [((0x4, 0xFF0), (0x12E40000, 0x3))].into(),
1687 };
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +01001688 const HYP_GRANULE: usize = SIZE_4KB;
1689 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor, HYP_GRANULE);
Jaewan Kim52477ae2023-11-21 21:20:52 +09001690
Pierre-Clément Tosif4d8a0d2024-10-25 17:14:15 +01001691 assert_eq!(device_info, Err(DeviceAssignmentError::InvalidReg(0x9)));
Jaewan Kim52477ae2023-11-21 21:20:52 +09001692 }
1693
1694 #[test]
Jaewan Kim19b984f2023-12-04 15:16:50 +09001695 fn device_info_invalid_reg_out_of_order() {
1696 let mut fdt_data = fs::read(FDT_WITH_MULTIPLE_REG_IOMMU_FILE_PATH).unwrap();
1697 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1698 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1699 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1700
1701 let hypervisor = MockHypervisor {
1702 mmio_tokens: [((0xF000, 0x1000), 0xF10000), ((0xF100, 0x1000), 0xF00000)].into(),
1703 iommu_tokens: [((0xFF0, 0xF0), (0x40000, 0x4)), ((0xFF1, 0xF1), (0x50000, 0x5))].into(),
1704 };
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +01001705 const HYP_GRANULE: usize = SIZE_4KB;
1706 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor, HYP_GRANULE);
Jaewan Kim19b984f2023-12-04 15:16:50 +09001707
Pierre-Clément Tosif4d8a0d2024-10-25 17:14:15 +01001708 assert_eq!(device_info, Err(DeviceAssignmentError::InvalidRegToken(0xF10000, 0xF00000)));
Jaewan Kim19b984f2023-12-04 15:16:50 +09001709 }
1710
1711 #[test]
Jaewan Kim52477ae2023-11-21 21:20:52 +09001712 fn device_info_invalid_iommus() {
1713 let mut fdt_data = fs::read(FDT_FILE_PATH).unwrap();
1714 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1715 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1716 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1717
1718 let hypervisor = MockHypervisor {
1719 mmio_tokens: [((0x9, 0xFF), 0x12F00000)].into(),
1720 iommu_tokens: BTreeMap::new(),
1721 };
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +01001722 const HYP_GRANULE: usize = SIZE_4KB;
1723 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor, HYP_GRANULE);
Jaewan Kim52477ae2023-11-21 21:20:52 +09001724
1725 assert_eq!(device_info, Err(DeviceAssignmentError::InvalidIommus));
1726 }
Jaewan Kim19b984f2023-12-04 15:16:50 +09001727
1728 #[test]
1729 fn device_info_duplicated_pv_iommus() {
1730 let mut fdt_data = fs::read(FDT_WITH_DUPLICATED_PVIOMMUS_FILE_PATH).unwrap();
1731 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1732 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1733 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1734
1735 let hypervisor = MockHypervisor {
1736 mmio_tokens: [((0x10000, 0x1000), 0xF00000), ((0x20000, 0xFF), 0xF10000)].into(),
1737 iommu_tokens: [((0xFF, 0xF), (0x40000, 0x4))].into(),
1738 };
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +01001739 const HYP_GRANULE: usize = SIZE_4KB;
1740 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor, HYP_GRANULE);
Jaewan Kim19b984f2023-12-04 15:16:50 +09001741
1742 assert_eq!(device_info, Err(DeviceAssignmentError::DuplicatedPvIommuIds));
1743 }
1744
1745 #[test]
1746 fn device_info_duplicated_iommus() {
1747 let mut fdt_data = fs::read(FDT_FILE_PATH).unwrap();
1748 let mut vm_dtbo_data = fs::read(VM_DTBO_WITH_DUPLICATED_IOMMUS_FILE_PATH).unwrap();
1749 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1750 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1751
1752 let hypervisor = MockHypervisor {
1753 mmio_tokens: [((0x10000, 0x1000), 0xF00000), ((0x20000, 0xFF), 0xF10000)].into(),
1754 iommu_tokens: [((0xFF, 0xF), (0x40000, 0x4))].into(),
1755 };
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +01001756 const HYP_GRANULE: usize = SIZE_4KB;
1757 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor, HYP_GRANULE);
Jaewan Kim19b984f2023-12-04 15:16:50 +09001758
1759 assert_eq!(device_info, Err(DeviceAssignmentError::UnsupportedIommusDuplication));
1760 }
1761
1762 #[test]
1763 fn device_info_duplicated_iommu_mapping() {
1764 let mut fdt_data = fs::read(FDT_WITH_MULTIPLE_REG_IOMMU_FILE_PATH).unwrap();
1765 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1766 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1767 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1768
1769 let hypervisor = MockHypervisor {
1770 mmio_tokens: [((0xF000, 0x1000), 0xF00000), ((0xF100, 0x1000), 0xF10000)].into(),
1771 iommu_tokens: [((0xFF0, 0xF0), (0x40000, 0x4)), ((0xFF1, 0xF1), (0x40000, 0x4))].into(),
1772 };
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +01001773 const HYP_GRANULE: usize = SIZE_4KB;
1774 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor, HYP_GRANULE);
Jaewan Kim19b984f2023-12-04 15:16:50 +09001775
1776 assert_eq!(device_info, Err(DeviceAssignmentError::InvalidIommus));
1777 }
Jaewan Kim50246682024-03-11 23:18:54 +09001778
1779 #[test]
Pierre-Clément Tosi49e26ce2024-03-12 16:31:50 +00001780 fn device_info_overlaps_pvmfw() {
1781 let mut fdt_data = fs::read(FDT_WITH_DEVICE_OVERLAPPING_PVMFW).unwrap();
1782 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1783 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1784 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1785
1786 let hypervisor = MockHypervisor {
1787 mmio_tokens: [((0x7fee0000, 0x1000), 0xF00000)].into(),
1788 iommu_tokens: [((0xFF, 0xF), (0x40000, 0x4))].into(),
1789 };
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +01001790 const HYP_GRANULE: usize = SIZE_4KB;
1791 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor, HYP_GRANULE);
Pierre-Clément Tosi49e26ce2024-03-12 16:31:50 +00001792
Pierre-Clément Tosif4d8a0d2024-10-25 17:14:15 +01001793 assert_eq!(device_info, Err(DeviceAssignmentError::InvalidReg(0x7fee0000)));
Pierre-Clément Tosi49e26ce2024-03-12 16:31:50 +00001794 }
1795
1796 #[test]
Jaewan Kim50246682024-03-11 23:18:54 +09001797 fn device_assignment_clean() {
1798 let mut platform_dt_data = pvmfw_fdt_template::RAW.to_vec();
1799 let platform_dt = Fdt::from_mut_slice(&mut platform_dt_data).unwrap();
1800
Alan Stokesf46a17c2025-01-05 15:50:18 +00001801 let compatible = platform_dt.root().next_compatible(c"pkvm,pviommu");
Jaewan Kim50246682024-03-11 23:18:54 +09001802 assert_ne!(None, compatible.unwrap());
1803
1804 clean(platform_dt).unwrap();
1805
Alan Stokesf46a17c2025-01-05 15:50:18 +00001806 let compatible = platform_dt.root().next_compatible(c"pkvm,pviommu");
Jaewan Kim50246682024-03-11 23:18:54 +09001807 assert_eq!(Ok(None), compatible);
1808 }
Jaewan Kim8f6f4662023-12-12 17:38:47 +09001809
1810 #[test]
1811 fn device_info_dependency() {
1812 let mut fdt_data = fs::read(FDT_WITH_DEPENDENCY_FILE_PATH).unwrap();
1813 let mut vm_dtbo_data = fs::read(VM_DTBO_WITH_DEPENDENCIES_FILE_PATH).unwrap();
1814 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1815 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1816 let mut platform_dt_data = pvmfw_fdt_template::RAW.to_vec();
1817 platform_dt_data.resize(pvmfw_fdt_template::RAW.len() * 2, 0);
1818 let platform_dt = Fdt::from_mut_slice(&mut platform_dt_data).unwrap();
1819 platform_dt.unpack().unwrap();
1820
1821 let hypervisor = MockHypervisor {
1822 mmio_tokens: [((0xFF000, 0x1), 0xF000)].into(),
1823 iommu_tokens: Default::default(),
1824 };
1825
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +01001826 const HYP_GRANULE: usize = SIZE_4KB;
1827 let device_info =
1828 DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor, HYP_GRANULE).unwrap().unwrap();
Jaewan Kim8f6f4662023-12-12 17:38:47 +09001829 device_info.filter(vm_dtbo).unwrap();
1830
1831 // SAFETY: Damaged VM DTBO wouldn't be used after this unsafe block.
1832 unsafe {
1833 platform_dt.apply_overlay(vm_dtbo.as_mut()).unwrap();
1834 }
1835 device_info.patch(platform_dt).unwrap();
1836
1837 let expected = Dts::from_dtb(Path::new(EXPECTED_FDT_WITH_DEPENDENCY_FILE_PATH)).unwrap();
1838 let platform_dt = Dts::from_fdt(platform_dt).unwrap();
1839
1840 assert_eq!(expected, platform_dt);
1841 }
1842
1843 #[test]
1844 fn device_info_multiple_dependencies() {
1845 let mut fdt_data = fs::read(FDT_WITH_MULTIPLE_DEPENDENCIES_FILE_PATH).unwrap();
1846 let mut vm_dtbo_data = fs::read(VM_DTBO_WITH_DEPENDENCIES_FILE_PATH).unwrap();
1847 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1848 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1849 let mut platform_dt_data = pvmfw_fdt_template::RAW.to_vec();
1850 platform_dt_data.resize(pvmfw_fdt_template::RAW.len() * 2, 0);
1851 let platform_dt = Fdt::from_mut_slice(&mut platform_dt_data).unwrap();
1852 platform_dt.unpack().unwrap();
1853
1854 let hypervisor = MockHypervisor {
1855 mmio_tokens: [((0xFF000, 0x1), 0xF000), ((0xFF100, 0x1), 0xF100)].into(),
1856 iommu_tokens: Default::default(),
1857 };
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +01001858 const HYP_GRANULE: usize = SIZE_4KB;
1859 let device_info =
1860 DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor, HYP_GRANULE).unwrap().unwrap();
Jaewan Kim8f6f4662023-12-12 17:38:47 +09001861 device_info.filter(vm_dtbo).unwrap();
1862
1863 // SAFETY: Damaged VM DTBO wouldn't be used after this unsafe block.
1864 unsafe {
1865 platform_dt.apply_overlay(vm_dtbo.as_mut()).unwrap();
1866 }
1867 device_info.patch(platform_dt).unwrap();
1868
1869 let expected =
1870 Dts::from_dtb(Path::new(EXPECTED_FDT_WITH_MULTIPLE_DEPENDENCIES_FILE_PATH)).unwrap();
1871 let platform_dt = Dts::from_fdt(platform_dt).unwrap();
1872
1873 assert_eq!(expected, platform_dt);
1874 }
1875
1876 #[test]
1877 fn device_info_dependency_loop() {
1878 let mut fdt_data = fs::read(FDT_WITH_DEPENDENCY_LOOP_FILE_PATH).unwrap();
1879 let mut vm_dtbo_data = fs::read(VM_DTBO_WITH_DEPENDENCIES_FILE_PATH).unwrap();
1880 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1881 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1882 let mut platform_dt_data = pvmfw_fdt_template::RAW.to_vec();
1883 platform_dt_data.resize(pvmfw_fdt_template::RAW.len() * 2, 0);
1884 let platform_dt = Fdt::from_mut_slice(&mut platform_dt_data).unwrap();
1885 platform_dt.unpack().unwrap();
1886
1887 let hypervisor = MockHypervisor {
1888 mmio_tokens: [((0xFF200, 0x1), 0xF200)].into(),
1889 iommu_tokens: Default::default(),
1890 };
Pierre-Clément Tosieacdd0f2024-10-22 16:31:01 +01001891 const HYP_GRANULE: usize = SIZE_4KB;
1892 let device_info =
1893 DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor, HYP_GRANULE).unwrap().unwrap();
Jaewan Kim8f6f4662023-12-12 17:38:47 +09001894 device_info.filter(vm_dtbo).unwrap();
1895
1896 // SAFETY: Damaged VM DTBO wouldn't be used after this unsafe block.
1897 unsafe {
1898 platform_dt.apply_overlay(vm_dtbo.as_mut()).unwrap();
1899 }
1900 device_info.patch(platform_dt).unwrap();
1901
1902 let expected =
1903 Dts::from_dtb(Path::new(EXPECTED_FDT_WITH_DEPENDENCY_LOOP_FILE_PATH)).unwrap();
1904 let platform_dt = Dts::from_fdt(platform_dt).unwrap();
1905
1906 assert_eq!(expected, platform_dt);
1907 }
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001908}