blob: c3ccf969737be21932dd22a9c7118f68bfa4a5cc [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 Kim52477ae2023-11-21 21:20:52 +090031use hyp::DeviceAssigningHypervisor;
32use libfdt::{Fdt, FdtError, FdtNode, Phandle, Reg};
33use log::error;
Jaewan Kimc6e023b2023-10-12 15:11:05 +090034
Jaewan Kimc6e023b2023-10-12 15:11:05 +090035// TODO(b/308694211): Use cstr! from vmbase instead.
36macro_rules! cstr {
37 ($str:literal) => {{
Pierre-Clément Tosid701a0b2023-11-07 15:38:59 +000038 const S: &str = concat!($str, "\0");
39 const C: &::core::ffi::CStr = match ::core::ffi::CStr::from_bytes_with_nul(S.as_bytes()) {
40 Ok(v) => v,
41 Err(_) => panic!("string contains interior NUL"),
42 };
43 C
Jaewan Kimc6e023b2023-10-12 15:11:05 +090044 }};
45}
46
Jaewan Kimc6e023b2023-10-12 15:11:05 +090047// TODO(b/277993056): Keep constants derived from platform.dts in one place.
48const CELLS_PER_INTERRUPT: usize = 3; // from /intc node in platform.dts
49
50/// Errors in device assignment.
51#[derive(Clone, Copy, Debug, Eq, PartialEq)]
52pub enum DeviceAssignmentError {
Jaewan Kim52477ae2023-11-21 21:20:52 +090053 /// Invalid VM DTBO
Jaewan Kimc6e023b2023-10-12 15:11:05 +090054 InvalidDtbo,
55 /// Invalid __symbols__
56 InvalidSymbols,
Jaewan Kim19b984f2023-12-04 15:16:50 +090057 /// Malformed <reg>. Can't parse.
58 MalformedReg,
Pierre-Clément Tosi8b78bc32024-03-13 17:37:07 +000059 /// Invalid physical <reg> of assigned device.
60 InvalidPhysReg(u64, u64),
61 /// Invalid virtual <reg> of assigned device.
62 InvalidReg(u64, u64),
Jaewan Kimc6e023b2023-10-12 15:11:05 +090063 /// Invalid <interrupts>
64 InvalidInterrupts,
Jaewan Kim19b984f2023-12-04 15:16:50 +090065 /// Malformed <iommus>
66 MalformedIommus,
Jaewan Kim51ccfed2023-11-08 13:51:58 +090067 /// Invalid <iommus>
68 InvalidIommus,
Jaewan Kim19b984f2023-12-04 15:16:50 +090069 /// Invalid phys IOMMU node
70 InvalidPhysIommu,
Jaewan Kima9200492023-11-21 20:45:31 +090071 /// Invalid pvIOMMU node
72 InvalidPvIommu,
Jaewan Kim51ccfed2023-11-08 13:51:58 +090073 /// Too many pvIOMMU
74 TooManyPvIommu,
Jaewan Kim19b984f2023-12-04 15:16:50 +090075 /// Duplicated phys IOMMU IDs exist
76 DuplicatedIommuIds,
Jaewan Kim51ccfed2023-11-08 13:51:58 +090077 /// Duplicated pvIOMMU IDs exist
78 DuplicatedPvIommuIds,
Jaewan Kimf8abbb52023-12-12 22:11:39 +090079 /// Unsupported path format. Only supports full path.
80 UnsupportedPathFormat,
Jaewan Kimc6e023b2023-10-12 15:11:05 +090081 /// Unsupported overlay target syntax. Only supports <target-path> with full path.
82 UnsupportedOverlayTarget,
Jaewan Kim19b984f2023-12-04 15:16:50 +090083 /// Unsupported PhysIommu,
84 UnsupportedPhysIommu,
85 /// Unsupported (pvIOMMU id, vSID) duplication. Currently the pair should be unique.
86 UnsupportedPvIommusDuplication,
87 /// Unsupported (IOMMU token, SID) duplication. Currently the pair should be unique.
88 UnsupportedIommusDuplication,
Jaewan Kim51ccfed2023-11-08 13:51:58 +090089 /// Internal error
90 Internal,
Jaewan Kimc6e023b2023-10-12 15:11:05 +090091 /// Unexpected error from libfdt
92 UnexpectedFdtError(FdtError),
93}
94
95impl From<FdtError> for DeviceAssignmentError {
96 fn from(e: FdtError) -> Self {
97 DeviceAssignmentError::UnexpectedFdtError(e)
98 }
99}
100
101impl fmt::Display for DeviceAssignmentError {
102 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
103 match self {
104 Self::InvalidDtbo => write!(f, "Invalid DTBO"),
105 Self::InvalidSymbols => write!(
106 f,
107 "Invalid property in /__symbols__. Must point to valid assignable device node."
108 ),
Jaewan Kim19b984f2023-12-04 15:16:50 +0900109 Self::MalformedReg => write!(f, "Malformed <reg>. Can't parse"),
Pierre-Clément Tosi8b78bc32024-03-13 17:37:07 +0000110 Self::InvalidReg(addr, size) => {
111 write!(f, "Invalid guest MMIO region (addr: {addr:#x}, size: {size:#x})")
112 }
113 Self::InvalidPhysReg(addr, size) => {
114 write!(f, "Invalid physical MMIO region (addr: {addr:#x}, size: {size:#x})")
115 }
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900116 Self::InvalidInterrupts => write!(f, "Invalid <interrupts>"),
Jaewan Kim19b984f2023-12-04 15:16:50 +0900117 Self::MalformedIommus => write!(f, "Malformed <iommus>. Can't parse."),
118 Self::InvalidIommus => {
119 write!(f, "Invalid <iommus>. Failed to validate with hypervisor")
120 }
121 Self::InvalidPhysIommu => write!(f, "Invalid phys IOMMU node"),
Jaewan Kima9200492023-11-21 20:45:31 +0900122 Self::InvalidPvIommu => write!(f, "Invalid pvIOMMU node"),
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900123 Self::TooManyPvIommu => write!(
124 f,
125 "Too many pvIOMMU node. Insufficient pre-populated pvIOMMUs in platform DT"
126 ),
Jaewan Kim19b984f2023-12-04 15:16:50 +0900127 Self::DuplicatedIommuIds => {
128 write!(f, "Duplicated IOMMU IDs exist. IDs must unique among iommu node")
129 }
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900130 Self::DuplicatedPvIommuIds => {
Jaewan Kim19b984f2023-12-04 15:16:50 +0900131 write!(f, "Duplicated pvIOMMU IDs exist. IDs must unique among iommu node")
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900132 }
Jaewan Kimf8abbb52023-12-12 22:11:39 +0900133 Self::UnsupportedPathFormat => {
134 write!(f, "Unsupported UnsupportedPathFormat. Only supports full path")
135 }
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900136 Self::UnsupportedOverlayTarget => {
137 write!(f, "Unsupported overlay target. Only supports 'target-path = \"/\"'")
138 }
Jaewan Kim19b984f2023-12-04 15:16:50 +0900139 Self::UnsupportedPhysIommu => {
140 write!(f, "Unsupported Phys IOMMU. Currently only supports #iommu-cells = <1>")
141 }
142 Self::UnsupportedPvIommusDuplication => {
143 write!(f, "Unsupported (pvIOMMU id, vSID) duplication. Currently the pair should be unique.")
144 }
145 Self::UnsupportedIommusDuplication => {
146 write!(f, "Unsupported (IOMMU token, SID) duplication. Currently the pair should be unique.")
147 }
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900148 Self::Internal => write!(f, "Internal error"),
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900149 Self::UnexpectedFdtError(e) => write!(f, "Unexpected Error from libfdt: {e}"),
150 }
151 }
152}
153
154pub type Result<T> = core::result::Result<T, DeviceAssignmentError>;
155
Jaewan Kimf8abbb52023-12-12 22:11:39 +0900156#[derive(Clone, Default, Ord, PartialOrd, Eq, PartialEq)]
157pub struct DtPathTokens<'a> {
158 tokens: Vec<&'a [u8]>,
159}
160
161impl<'a> fmt::Debug for DtPathTokens<'a> {
162 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
163 let mut list = f.debug_list();
164 for token in &self.tokens {
165 let mut bytes = token.to_vec();
166 bytes.push(b'\0');
167 match CString::from_vec_with_nul(bytes) {
168 Ok(string) => list.entry(&string),
169 Err(_) => list.entry(token),
170 };
171 }
172 list.finish()
173 }
174}
175
176impl<'a> DtPathTokens<'a> {
177 fn new(path: &'a CStr) -> Result<Self> {
178 if path.to_bytes().first() != Some(&b'/') {
179 return Err(DeviceAssignmentError::UnsupportedPathFormat);
180 }
181 let tokens: Vec<_> = path
182 .to_bytes()
183 .split(|char| *char == b'/')
184 .filter(|&component| !component.is_empty())
185 .collect();
186 Ok(Self { tokens })
187 }
188
189 fn to_overlay_target_path(&self) -> Result<Self> {
190 if !self.is_overlayable_node() {
191 return Err(DeviceAssignmentError::InvalidDtbo);
192 }
193 Ok(Self { tokens: self.tokens.as_slice()[2..].to_vec() })
194 }
195
196 fn to_cstring(&self) -> CString {
197 if self.tokens.is_empty() {
198 return CString::new(*b"/\0").unwrap();
199 }
200
201 let size = self.tokens.iter().fold(0, |sum, token| sum + token.len() + 1);
202 let mut path = Vec::with_capacity(size + 1);
203 for token in &self.tokens {
204 path.push(b'/');
205 path.extend_from_slice(token);
206 }
207 path.push(b'\0');
208
209 CString::from_vec_with_nul(path).unwrap()
210 }
211
212 fn is_overlayable_node(&self) -> bool {
213 self.tokens.get(1) == Some(&&b"__overlay__"[..])
214 }
215}
216
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900217/// Represents VM DTBO
218#[repr(transparent)]
219pub struct VmDtbo(Fdt);
220
221impl VmDtbo {
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900222 /// Wraps a mutable slice containing a VM DTBO.
223 ///
224 /// Fails if the VM DTBO does not pass validation.
225 pub fn from_mut_slice(dtbo: &mut [u8]) -> Result<&mut Self> {
226 // This validates DTBO
227 let fdt = Fdt::from_mut_slice(dtbo)?;
228 // SAFETY: VmDtbo is a transparent wrapper around Fdt, so representation is the same.
229 Ok(unsafe { mem::transmute::<&mut Fdt, &mut Self>(fdt) })
230 }
231
232 // Locates device node path as if the given dtbo node path is assigned and VM DTBO is overlaid.
233 // For given dtbo node path, this concatenates <target-path> of the enclosing fragment and
234 // relative path from __overlay__ node.
235 //
236 // Here's an example with sample VM DTBO:
237 // / {
238 // fragment@rng {
239 // target-path = "/"; // Always 'target-path = "/"'. Disallows <target> or other path.
240 // __overlay__ {
241 // rng { ... }; // Actual device node is here. If overlaid, path would be "/rng"
242 // };
243 // };
Jaewan Kim80ef9fa2024-02-25 16:08:14 +0000244 // __symbols__ { // Contains list of assignable devices
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900245 // rng = "/fragment@rng/__overlay__/rng";
246 // };
247 // };
248 //
249 // Then locate_overlay_target_path(cstr!("/fragment@rng/__overlay__/rng")) is Ok("/rng")
250 //
251 // Contrary to fdt_overlay_target_offset(), this API enforces overlay target property
252 // 'target-path = "/"', so the overlay doesn't modify and/or append platform DT's existing
253 // node and/or properties. The enforcement is for compatibility reason.
Jaewan Kim19b984f2023-12-04 15:16:50 +0900254 fn locate_overlay_target_path(
255 &self,
Jaewan Kimf8abbb52023-12-12 22:11:39 +0900256 dtbo_node_path: &DtPathTokens,
Jaewan Kim19b984f2023-12-04 15:16:50 +0900257 dtbo_node: &FdtNode,
258 ) -> Result<CString> {
Jaewan Kim19b984f2023-12-04 15:16:50 +0900259 let fragment_node = dtbo_node.supernode_at_depth(1)?;
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900260 let target_path = fragment_node
Pierre-Clément Tosid701a0b2023-11-07 15:38:59 +0000261 .getprop_str(cstr!("target-path"))?
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900262 .ok_or(DeviceAssignmentError::InvalidDtbo)?;
263 if target_path != cstr!("/") {
264 return Err(DeviceAssignmentError::UnsupportedOverlayTarget);
265 }
266
Jaewan Kimf8abbb52023-12-12 22:11:39 +0900267 let overlaid_path = dtbo_node_path.to_overlay_target_path()?;
268 Ok(overlaid_path.to_cstring())
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900269 }
Jaewan Kim19b984f2023-12-04 15:16:50 +0900270
271 fn parse_physical_iommus(physical_node: &FdtNode) -> Result<BTreeMap<Phandle, PhysIommu>> {
272 let mut phys_iommus = BTreeMap::new();
273 for (node, _) in physical_node.descendants() {
274 let Some(phandle) = node.get_phandle()? else {
275 continue; // Skips unreachable IOMMU node
276 };
277 let Some(iommu) = PhysIommu::parse(&node)? else {
278 continue; // Skip if not a PhysIommu.
279 };
280 if phys_iommus.insert(phandle, iommu).is_some() {
281 return Err(FdtError::BadPhandle.into());
282 }
283 }
284 Self::validate_physical_iommus(&phys_iommus)?;
285 Ok(phys_iommus)
286 }
287
288 fn validate_physical_iommus(phys_iommus: &BTreeMap<Phandle, PhysIommu>) -> Result<()> {
289 let unique_iommus: BTreeSet<_> = phys_iommus.values().cloned().collect();
290 if phys_iommus.len() != unique_iommus.len() {
291 return Err(DeviceAssignmentError::DuplicatedIommuIds);
292 }
293 Ok(())
294 }
295
296 fn validate_physical_devices(
297 physical_devices: &BTreeMap<Phandle, PhysicalDeviceInfo>,
298 ) -> Result<()> {
299 // Only need to validate iommus because <reg> will be validated together with PV <reg>
300 // see: DeviceAssignmentInfo::validate_all_regs().
301 let mut all_iommus = BTreeSet::new();
302 for physical_device in physical_devices.values() {
303 for iommu in &physical_device.iommus {
304 if !all_iommus.insert(iommu) {
305 error!("Unsupported phys IOMMU duplication found, <iommus> = {iommu:?}");
306 return Err(DeviceAssignmentError::UnsupportedIommusDuplication);
307 }
308 }
309 }
310 Ok(())
311 }
312
313 fn parse_physical_devices_with_iommus(
314 physical_node: &FdtNode,
315 phys_iommus: &BTreeMap<Phandle, PhysIommu>,
316 ) -> Result<BTreeMap<Phandle, PhysicalDeviceInfo>> {
317 let mut physical_devices = BTreeMap::new();
318 for (node, _) in physical_node.descendants() {
319 let Some(info) = PhysicalDeviceInfo::parse(&node, phys_iommus)? else {
320 continue;
321 };
322 if physical_devices.insert(info.target, info).is_some() {
323 return Err(DeviceAssignmentError::InvalidDtbo);
324 }
325 }
326 Self::validate_physical_devices(&physical_devices)?;
327 Ok(physical_devices)
328 }
329
330 /// Parses Physical devices in VM DTBO
331 fn parse_physical_devices(&self) -> Result<BTreeMap<Phandle, PhysicalDeviceInfo>> {
332 let Some(physical_node) = self.as_ref().node(cstr!("/host"))? else {
333 return Ok(BTreeMap::new());
334 };
335
336 let phys_iommus = Self::parse_physical_iommus(&physical_node)?;
337 Self::parse_physical_devices_with_iommus(&physical_node, &phys_iommus)
338 }
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900339
Jaewan Kimf8abbb52023-12-12 22:11:39 +0900340 fn node(&self, path: &DtPathTokens) -> Result<Option<FdtNode>> {
341 let mut node = self.as_ref().root();
342 for token in &path.tokens {
343 let Some(subnode) = node.subnode_with_name_bytes(token)? else {
344 return Ok(None);
345 };
346 node = subnode;
347 }
348 Ok(Some(node))
349 }
Jaewan Kimc39974e2023-12-02 01:13:30 +0900350}
351
Jaewan Kimc730ebf2024-02-22 10:34:55 +0900352fn filter_dangling_symbols(fdt: &mut Fdt) -> Result<()> {
353 if let Some(symbols) = fdt.symbols()? {
354 let mut removed = vec![];
355 for prop in symbols.properties()? {
356 let path = CStr::from_bytes_with_nul(prop.value()?)
357 .map_err(|_| DeviceAssignmentError::Internal)?;
358 if fdt.node(path)?.is_none() {
359 let name = prop.name()?;
360 removed.push(CString::from(name));
361 }
362 }
363
364 let mut symbols = fdt.symbols_mut()?.unwrap();
365 for name in removed {
366 symbols.nop_property(&name)?;
367 }
368 }
369 Ok(())
370}
371
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900372impl AsRef<Fdt> for VmDtbo {
373 fn as_ref(&self) -> &Fdt {
374 &self.0
375 }
376}
377
378impl AsMut<Fdt> for VmDtbo {
379 fn as_mut(&mut self) -> &mut Fdt {
380 &mut self.0
381 }
382}
383
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900384#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
385struct PvIommu {
386 // ID from pvIOMMU node
387 id: u32,
388}
389
390impl PvIommu {
391 fn parse(node: &FdtNode) -> Result<Self> {
Jaewan Kima9200492023-11-21 20:45:31 +0900392 let iommu_cells = node
393 .getprop_u32(cstr!("#iommu-cells"))?
394 .ok_or(DeviceAssignmentError::InvalidPvIommu)?;
Jaewan Kim19b984f2023-12-04 15:16:50 +0900395 // Ensures #iommu-cells = <1>. It means that `<iommus>` entry contains pair of
Jaewan Kima9200492023-11-21 20:45:31 +0900396 // (pvIOMMU ID, vSID)
397 if iommu_cells != 1 {
398 return Err(DeviceAssignmentError::InvalidPvIommu);
399 }
400 let id = node.getprop_u32(cstr!("id"))?.ok_or(DeviceAssignmentError::InvalidPvIommu)?;
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900401 Ok(Self { id })
402 }
403}
404
Jaewan Kima9200492023-11-21 20:45:31 +0900405#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
406struct Vsid(u32);
407
Jaewan Kim19b984f2023-12-04 15:16:50 +0900408#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
409struct Sid(u64);
410
411impl From<u32> for Sid {
412 fn from(sid: u32) -> Self {
413 Self(sid.into())
414 }
415}
416
417#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
Jaewan Kim52477ae2023-11-21 21:20:52 +0900418struct DeviceReg {
419 addr: u64,
420 size: u64,
421}
422
Pierre-Clément Tosi49e26ce2024-03-12 16:31:50 +0000423impl DeviceReg {
424 pub fn overlaps(&self, range: &Range<u64>) -> bool {
425 self.addr < range.end && range.start < self.addr.checked_add(self.size).unwrap()
426 }
427}
428
Jaewan Kim52477ae2023-11-21 21:20:52 +0900429impl TryFrom<Reg<u64>> for DeviceReg {
430 type Error = DeviceAssignmentError;
431
432 fn try_from(reg: Reg<u64>) -> Result<Self> {
Jaewan Kim19b984f2023-12-04 15:16:50 +0900433 Ok(Self { addr: reg.addr, size: reg.size.ok_or(DeviceAssignmentError::MalformedReg)? })
Jaewan Kim52477ae2023-11-21 21:20:52 +0900434 }
435}
436
437fn parse_node_reg(node: &FdtNode) -> Result<Vec<DeviceReg>> {
438 node.reg()?
Jaewan Kim19b984f2023-12-04 15:16:50 +0900439 .ok_or(DeviceAssignmentError::MalformedReg)?
Jaewan Kim52477ae2023-11-21 21:20:52 +0900440 .map(DeviceReg::try_from)
441 .collect::<Result<Vec<_>>>()
442}
443
444fn to_be_bytes(reg: &[DeviceReg]) -> Vec<u8> {
445 let mut reg_cells = vec![];
446 for x in reg {
447 reg_cells.extend_from_slice(&x.addr.to_be_bytes());
448 reg_cells.extend_from_slice(&x.size.to_be_bytes());
449 }
450 reg_cells
451}
452
Jaewan Kim19b984f2023-12-04 15:16:50 +0900453#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
454struct PhysIommu {
455 token: u64,
456}
457
458impl PhysIommu {
459 fn parse(node: &FdtNode) -> Result<Option<Self>> {
460 let Some(token) = node.getprop_u64(cstr!("android,pvmfw,token"))? else {
461 return Ok(None);
462 };
463 let Some(iommu_cells) = node.getprop_u32(cstr!("#iommu-cells"))? else {
464 return Err(DeviceAssignmentError::InvalidPhysIommu);
465 };
466 // Currently only supports #iommu-cells = <1>.
467 // In that case `<iommus>` entry contains pair of (pIOMMU phandle, Sid token)
468 if iommu_cells != 1 {
469 return Err(DeviceAssignmentError::UnsupportedPhysIommu);
470 }
471 Ok(Some(Self { token }))
472 }
473}
474
475#[derive(Debug)]
476struct PhysicalDeviceInfo {
477 target: Phandle,
478 reg: Vec<DeviceReg>,
479 iommus: Vec<(PhysIommu, Sid)>,
480}
481
482impl PhysicalDeviceInfo {
483 fn parse_iommus(
484 node: &FdtNode,
485 phys_iommus: &BTreeMap<Phandle, PhysIommu>,
486 ) -> Result<Vec<(PhysIommu, Sid)>> {
487 let mut iommus = vec![];
488 let Some(mut cells) = node.getprop_cells(cstr!("iommus"))? else {
489 return Ok(iommus);
490 };
491 while let Some(cell) = cells.next() {
492 // Parse pIOMMU ID
493 let phandle =
494 Phandle::try_from(cell).or(Err(DeviceAssignmentError::MalformedIommus))?;
495 let iommu = phys_iommus.get(&phandle).ok_or(DeviceAssignmentError::MalformedIommus)?;
496
497 // Parse Sid
498 let Some(cell) = cells.next() else {
499 return Err(DeviceAssignmentError::MalformedIommus);
500 };
501
502 iommus.push((*iommu, Sid::from(cell)));
503 }
504 Ok(iommus)
505 }
506
507 fn parse(node: &FdtNode, phys_iommus: &BTreeMap<Phandle, PhysIommu>) -> Result<Option<Self>> {
508 let Some(phandle) = node.getprop_u32(cstr!("android,pvmfw,target"))? else {
509 return Ok(None);
510 };
511 let target = Phandle::try_from(phandle)?;
512 let reg = parse_node_reg(node)?;
513 let iommus = Self::parse_iommus(node, phys_iommus)?;
514 Ok(Some(Self { target, reg, iommus }))
515 }
516}
517
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900518/// Assigned device information parsed from crosvm DT.
519/// Keeps everything in the owned data because underlying FDT will be reused for platform DT.
520#[derive(Debug, Eq, PartialEq)]
521struct AssignedDeviceInfo {
522 // Node path of assigned device (e.g. "/rng")
523 node_path: CString,
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900524 // <reg> property from the crosvm DT
Jaewan Kim52477ae2023-11-21 21:20:52 +0900525 reg: Vec<DeviceReg>,
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900526 // <interrupts> property from the crosvm DT
527 interrupts: Vec<u8>,
Jaewan Kima9200492023-11-21 20:45:31 +0900528 // Parsed <iommus> property from the crosvm DT. Tuple of PvIommu and vSID.
529 iommus: Vec<(PvIommu, Vsid)>,
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900530}
531
532impl AssignedDeviceInfo {
Jaewan Kim19b984f2023-12-04 15:16:50 +0900533 fn validate_reg(
534 device_reg: &[DeviceReg],
535 physical_device_reg: &[DeviceReg],
Jaewan Kim52477ae2023-11-21 21:20:52 +0900536 hypervisor: &dyn DeviceAssigningHypervisor,
Jaewan Kim19b984f2023-12-04 15:16:50 +0900537 ) -> Result<()> {
Pierre-Clément Tosi8b78bc32024-03-13 17:37:07 +0000538 let mut virt_regs = device_reg.iter();
539 let mut phys_regs = physical_device_reg.iter();
Pierre-Clément Tosi49e26ce2024-03-12 16:31:50 +0000540 // TODO(b/308694211): Move this constant to vmbase::layout once vmbase is std-compatible.
541 const PVMFW_RANGE: Range<u64> = 0x7fc0_0000..0x8000_0000;
Jaewan Kim19b984f2023-12-04 15:16:50 +0900542 // PV reg and physical reg should have 1:1 match in order.
Pierre-Clément Tosi8b78bc32024-03-13 17:37:07 +0000543 for (reg, phys_reg) in virt_regs.by_ref().zip(phys_regs.by_ref()) {
Pierre-Clément Tosi49e26ce2024-03-12 16:31:50 +0000544 if reg.overlaps(&PVMFW_RANGE) {
545 return Err(DeviceAssignmentError::InvalidReg(reg.addr, reg.size));
546 }
547 // If this call returns successfully, hyp has mapped the MMIO region at `reg`.
Jaewan Kim19b984f2023-12-04 15:16:50 +0900548 let addr = hypervisor.get_phys_mmio_token(reg.addr, reg.size).map_err(|e| {
Pierre-Clément Tosi08d6e3f2024-03-13 18:22:16 +0000549 error!("Hypervisor error while requesting MMIO token: {e}");
Pierre-Clément Tosi8b78bc32024-03-13 17:37:07 +0000550 DeviceAssignmentError::InvalidReg(reg.addr, reg.size)
Jaewan Kim52477ae2023-11-21 21:20:52 +0900551 })?;
Pierre-Clément Tosi49e26ce2024-03-12 16:31:50 +0000552 // Only check address because hypervisor guarantees size match when success.
Jaewan Kim19b984f2023-12-04 15:16:50 +0900553 if phys_reg.addr != addr {
Pierre-Clément Tosi8b78bc32024-03-13 17:37:07 +0000554 error!("Assigned device {reg:x?} has unexpected physical address");
555 return Err(DeviceAssignmentError::InvalidPhysReg(addr, reg.size));
Jaewan Kim19b984f2023-12-04 15:16:50 +0900556 }
Jaewan Kim52477ae2023-11-21 21:20:52 +0900557 }
Pierre-Clément Tosi8b78bc32024-03-13 17:37:07 +0000558
559 if let Some(DeviceReg { addr, size }) = virt_regs.next() {
560 return Err(DeviceAssignmentError::InvalidReg(*addr, *size));
561 }
562
563 if let Some(DeviceReg { addr, size }) = phys_regs.next() {
564 return Err(DeviceAssignmentError::InvalidPhysReg(*addr, *size));
565 }
566
Jaewan Kim19b984f2023-12-04 15:16:50 +0900567 Ok(())
Jaewan Kim52477ae2023-11-21 21:20:52 +0900568 }
569
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900570 fn parse_interrupts(node: &FdtNode) -> Result<Vec<u8>> {
571 // Validation: Validate if interrupts cell numbers are multiple of #interrupt-cells.
572 // We can't know how many interrupts would exist.
573 let interrupts_cells = node
Pierre-Clément Tosid701a0b2023-11-07 15:38:59 +0000574 .getprop_cells(cstr!("interrupts"))?
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900575 .ok_or(DeviceAssignmentError::InvalidInterrupts)?
576 .count();
577 if interrupts_cells % CELLS_PER_INTERRUPT != 0 {
578 return Err(DeviceAssignmentError::InvalidInterrupts);
579 }
580
581 // Once validated, keep the raw bytes so patch can be done with setprop()
Pierre-Clément Tosid701a0b2023-11-07 15:38:59 +0000582 Ok(node.getprop(cstr!("interrupts")).unwrap().unwrap().into())
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900583 }
584
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900585 // TODO(b/277993056): Also validate /__local_fixups__ to ensure that <iommus> has phandle.
Jaewan Kima9200492023-11-21 20:45:31 +0900586 fn parse_iommus(
587 node: &FdtNode,
588 pviommus: &BTreeMap<Phandle, PvIommu>,
589 ) -> Result<Vec<(PvIommu, Vsid)>> {
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900590 let mut iommus = vec![];
Jaewan Kima9200492023-11-21 20:45:31 +0900591 let Some(mut cells) = node.getprop_cells(cstr!("iommus"))? else {
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900592 return Ok(iommus);
593 };
Jaewan Kima9200492023-11-21 20:45:31 +0900594 while let Some(cell) = cells.next() {
595 // Parse pvIOMMU ID
Jaewan Kim19b984f2023-12-04 15:16:50 +0900596 let phandle =
597 Phandle::try_from(cell).or(Err(DeviceAssignmentError::MalformedIommus))?;
598 let pviommu = pviommus.get(&phandle).ok_or(DeviceAssignmentError::MalformedIommus)?;
Jaewan Kima9200492023-11-21 20:45:31 +0900599
600 // Parse vSID
601 let Some(cell) = cells.next() else {
Jaewan Kim19b984f2023-12-04 15:16:50 +0900602 return Err(DeviceAssignmentError::MalformedIommus);
Jaewan Kima9200492023-11-21 20:45:31 +0900603 };
604 let vsid = Vsid(cell);
605
606 iommus.push((*pviommu, vsid));
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900607 }
608 Ok(iommus)
609 }
610
Jaewan Kim19b984f2023-12-04 15:16:50 +0900611 fn validate_iommus(
612 iommus: &[(PvIommu, Vsid)],
613 physical_device_iommu: &[(PhysIommu, Sid)],
614 hypervisor: &dyn DeviceAssigningHypervisor,
615 ) -> Result<()> {
616 if iommus.len() != physical_device_iommu.len() {
617 return Err(DeviceAssignmentError::InvalidIommus);
618 }
619 // pvIOMMU can be reordered, and hypervisor may not guarantee 1:1 mapping.
620 // So we need to mark what's matched or not.
621 let mut physical_device_iommu = physical_device_iommu.to_vec();
622 for (pviommu, vsid) in iommus {
Pierre-Clément Tosi08d6e3f2024-03-13 18:22:16 +0000623 let (id, sid) =
624 hypervisor.get_phys_iommu_token(pviommu.id.into(), vsid.0.into()).map_err(|e| {
625 error!("Hypervisor error while requesting IOMMU token ({pviommu:?}, {vsid:?}): {e}");
626 DeviceAssignmentError::InvalidIommus
627 })?;
Jaewan Kim19b984f2023-12-04 15:16:50 +0900628
629 let pos = physical_device_iommu
630 .iter()
631 .position(|(phys_iommu, phys_sid)| (phys_iommu.token, phys_sid.0) == (id, sid));
632 match pos {
633 Some(pos) => physical_device_iommu.remove(pos),
634 None => {
635 error!("Failed to validate device <iommus>. No matching phys iommu or duplicated mapping for pviommu={pviommu:?}, vsid={vsid:?}");
636 return Err(DeviceAssignmentError::InvalidIommus);
637 }
638 };
639 }
640 Ok(())
641 }
642
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900643 fn parse(
644 fdt: &Fdt,
645 vm_dtbo: &VmDtbo,
Jaewan Kimf8abbb52023-12-12 22:11:39 +0900646 dtbo_node_path: &DtPathTokens,
Jaewan Kim19b984f2023-12-04 15:16:50 +0900647 physical_devices: &BTreeMap<Phandle, PhysicalDeviceInfo>,
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900648 pviommus: &BTreeMap<Phandle, PvIommu>,
Jaewan Kim52477ae2023-11-21 21:20:52 +0900649 hypervisor: &dyn DeviceAssigningHypervisor,
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900650 ) -> Result<Option<Self>> {
Jaewan Kim19b984f2023-12-04 15:16:50 +0900651 let dtbo_node =
Jaewan Kimf8abbb52023-12-12 22:11:39 +0900652 vm_dtbo.node(dtbo_node_path)?.ok_or(DeviceAssignmentError::InvalidSymbols)?;
Jaewan Kim19b984f2023-12-04 15:16:50 +0900653 let node_path = vm_dtbo.locate_overlay_target_path(dtbo_node_path, &dtbo_node)?;
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900654
655 let Some(node) = fdt.node(&node_path)? else { return Ok(None) };
656
Jaewan Kim80ef9fa2024-02-25 16:08:14 +0000657 // Currently can only assign devices backed by physical devices.
Jaewan Kim19b984f2023-12-04 15:16:50 +0900658 let phandle = dtbo_node.get_phandle()?.ok_or(DeviceAssignmentError::InvalidDtbo)?;
Jaewan Kim80ef9fa2024-02-25 16:08:14 +0000659 let Some(physical_device) = physical_devices.get(&phandle) else {
660 // If labeled DT node isn't backed by physical device node, then just return None.
661 // It's not an error because such node can be a dependency of assignable device nodes.
662 return Ok(None);
663 };
Jaewan Kim19b984f2023-12-04 15:16:50 +0900664
665 let reg = parse_node_reg(&node)?;
666 Self::validate_reg(&reg, &physical_device.reg, hypervisor)?;
667
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900668 let interrupts = Self::parse_interrupts(&node)?;
Jaewan Kim19b984f2023-12-04 15:16:50 +0900669
670 let iommus = Self::parse_iommus(&node, pviommus)?;
671 Self::validate_iommus(&iommus, &physical_device.iommus, hypervisor)?;
672
Jaewan Kimf8abbb52023-12-12 22:11:39 +0900673 Ok(Some(Self { node_path, reg, interrupts, iommus }))
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900674 }
675
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900676 fn patch(&self, fdt: &mut Fdt, pviommu_phandles: &BTreeMap<PvIommu, Phandle>) -> Result<()> {
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900677 let mut dst = fdt.node_mut(&self.node_path)?.unwrap();
Jaewan Kim52477ae2023-11-21 21:20:52 +0900678 dst.setprop(cstr!("reg"), &to_be_bytes(&self.reg))?;
Pierre-Clément Tosid701a0b2023-11-07 15:38:59 +0000679 dst.setprop(cstr!("interrupts"), &self.interrupts)?;
Jaewan Kima9200492023-11-21 20:45:31 +0900680 let mut iommus = Vec::with_capacity(8 * self.iommus.len());
681 for (pviommu, vsid) in &self.iommus {
682 let phandle = pviommu_phandles.get(pviommu).unwrap();
683 iommus.extend_from_slice(&u32::from(*phandle).to_be_bytes());
684 iommus.extend_from_slice(&vsid.0.to_be_bytes());
685 }
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900686 dst.setprop(cstr!("iommus"), &iommus)?;
687
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900688 Ok(())
689 }
690}
691
692#[derive(Debug, Default, Eq, PartialEq)]
693pub struct DeviceAssignmentInfo {
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900694 pviommus: BTreeSet<PvIommu>,
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900695 assigned_devices: Vec<AssignedDeviceInfo>,
696 filtered_dtbo_paths: Vec<CString>,
697}
698
699impl DeviceAssignmentInfo {
Chris Wailes9d09f572024-01-16 13:31:02 -0800700 const PVIOMMU_COMPATIBLE: &'static CStr = cstr!("pkvm,pviommu");
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900701
702 /// Parses pvIOMMUs in fdt
703 // Note: This will validate pvIOMMU ids' uniqueness, even when unassigned.
704 fn parse_pviommus(fdt: &Fdt) -> Result<BTreeMap<Phandle, PvIommu>> {
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900705 let mut pviommus = BTreeMap::new();
706 for compatible in fdt.compatible_nodes(Self::PVIOMMU_COMPATIBLE)? {
707 let Some(phandle) = compatible.get_phandle()? else {
708 continue; // Skips unreachable pvIOMMU node
709 };
710 let pviommu = PvIommu::parse(&compatible)?;
711 if pviommus.insert(phandle, pviommu).is_some() {
712 return Err(FdtError::BadPhandle.into());
713 }
714 }
715 Ok(pviommus)
716 }
717
Jaewan Kim19b984f2023-12-04 15:16:50 +0900718 fn validate_pviommu_topology(assigned_devices: &[AssignedDeviceInfo]) -> Result<()> {
719 let mut all_iommus = BTreeSet::new();
720 for assigned_device in assigned_devices {
721 for iommu in &assigned_device.iommus {
722 if !all_iommus.insert(iommu) {
723 error!("Unsupported pvIOMMU duplication found, <iommus> = {iommu:?}");
724 return Err(DeviceAssignmentError::UnsupportedPvIommusDuplication);
725 }
726 }
727 }
728 Ok(())
729 }
730
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900731 /// Parses fdt and vm_dtbo, and creates new DeviceAssignmentInfo
732 // TODO(b/277993056): Parse __local_fixups__
733 // TODO(b/277993056): Parse __fixups__
Jaewan Kim52477ae2023-11-21 21:20:52 +0900734 pub fn parse(
735 fdt: &Fdt,
736 vm_dtbo: &VmDtbo,
737 hypervisor: &dyn DeviceAssigningHypervisor,
738 ) -> Result<Option<Self>> {
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900739 let Some(symbols_node) = vm_dtbo.as_ref().symbols()? else {
740 // /__symbols__ should contain all assignable devices.
741 // If empty, then nothing can be assigned.
742 return Ok(None);
743 };
744
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900745 let pviommus = Self::parse_pviommus(fdt)?;
746 let unique_pviommus: BTreeSet<_> = pviommus.values().cloned().collect();
747 if pviommus.len() != unique_pviommus.len() {
748 return Err(DeviceAssignmentError::DuplicatedPvIommuIds);
749 }
750
Jaewan Kim19b984f2023-12-04 15:16:50 +0900751 let physical_devices = vm_dtbo.parse_physical_devices()?;
752
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900753 let mut assigned_devices = vec![];
754 let mut filtered_dtbo_paths = vec![];
755 for symbol_prop in symbols_node.properties()? {
756 let symbol_prop_value = symbol_prop.value()?;
757 let dtbo_node_path = CStr::from_bytes_with_nul(symbol_prop_value)
758 .or(Err(DeviceAssignmentError::InvalidSymbols))?;
Jaewan Kimf8abbb52023-12-12 22:11:39 +0900759 let dtbo_node_path = DtPathTokens::new(dtbo_node_path)?;
760 if !dtbo_node_path.is_overlayable_node() {
Jaewan Kimc39974e2023-12-02 01:13:30 +0900761 continue;
762 }
Jaewan Kim19b984f2023-12-04 15:16:50 +0900763 let assigned_device = AssignedDeviceInfo::parse(
764 fdt,
765 vm_dtbo,
Jaewan Kimf8abbb52023-12-12 22:11:39 +0900766 &dtbo_node_path,
Jaewan Kim19b984f2023-12-04 15:16:50 +0900767 &physical_devices,
768 &pviommus,
769 hypervisor,
770 )?;
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900771 if let Some(assigned_device) = assigned_device {
772 assigned_devices.push(assigned_device);
773 } else {
Jaewan Kimf8abbb52023-12-12 22:11:39 +0900774 filtered_dtbo_paths.push(dtbo_node_path.to_cstring());
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900775 }
776 }
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900777 if assigned_devices.is_empty() {
778 return Ok(None);
779 }
Jaewan Kimc39974e2023-12-02 01:13:30 +0900780
Jaewan Kim19b984f2023-12-04 15:16:50 +0900781 Self::validate_pviommu_topology(&assigned_devices)?;
782
Jaewan Kimc39974e2023-12-02 01:13:30 +0900783 // Clean up any nodes that wouldn't be overlaid but may contain reference to filtered nodes.
784 // Otherwise, `fdt_apply_overlay()` would fail because of missing phandle reference.
Jaewan Kimc39974e2023-12-02 01:13:30 +0900785 // TODO(b/277993056): Also filter other unused nodes/props in __local_fixups__
786 filtered_dtbo_paths.push(CString::new("/__local_fixups__/host").unwrap());
787
788 // Note: Any node without __overlay__ will be ignored by fdt_apply_overlay,
789 // so doesn't need to be filtered.
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900790
791 Ok(Some(Self { pviommus: unique_pviommus, assigned_devices, filtered_dtbo_paths }))
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900792 }
793
794 /// Filters VM DTBO to only contain necessary information for booting pVM
795 /// In detail, this will remove followings by setting nop node / nop property.
796 /// - Removes unassigned devices
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900797 // TODO(b/277993056): remove unused dependencies in VM DTBO.
798 // TODO(b/277993056): remove supernodes' properties.
799 // TODO(b/277993056): remove unused alises.
800 pub fn filter(&self, vm_dtbo: &mut VmDtbo) -> Result<()> {
801 let vm_dtbo = vm_dtbo.as_mut();
802
803 // Filters unused node in assigned devices
804 for filtered_dtbo_path in &self.filtered_dtbo_paths {
805 let node = vm_dtbo.node_mut(filtered_dtbo_path).unwrap().unwrap();
806 node.nop()?;
807 }
808
Jaewan Kim371f6c82024-02-24 01:33:37 +0900809 filter_dangling_symbols(vm_dtbo)
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900810 }
811
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900812 fn patch_pviommus(&self, fdt: &mut Fdt) -> Result<BTreeMap<PvIommu, Phandle>> {
Pierre-Clément Tosi244efea2024-02-16 14:48:14 +0000813 let mut compatible = fdt.root_mut().next_compatible(Self::PVIOMMU_COMPATIBLE)?;
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900814 let mut pviommu_phandles = BTreeMap::new();
815
816 for pviommu in &self.pviommus {
817 let mut node = compatible.ok_or(DeviceAssignmentError::TooManyPvIommu)?;
818 let phandle = node.as_node().get_phandle()?.ok_or(DeviceAssignmentError::Internal)?;
819 node.setprop_inplace(cstr!("id"), &pviommu.id.to_be_bytes())?;
820 if pviommu_phandles.insert(*pviommu, phandle).is_some() {
821 return Err(DeviceAssignmentError::Internal);
822 }
823 compatible = node.next_compatible(Self::PVIOMMU_COMPATIBLE)?;
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900824 }
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900825
826 // Filters pre-populated but unassigned pvIOMMUs.
827 while let Some(filtered_pviommu) = compatible {
828 compatible = filtered_pviommu.delete_and_next_compatible(Self::PVIOMMU_COMPATIBLE)?;
829 }
830
831 Ok(pviommu_phandles)
832 }
833
834 pub fn patch(&self, fdt: &mut Fdt) -> Result<()> {
835 let pviommu_phandles = self.patch_pviommus(fdt)?;
836
837 // Patches assigned devices
838 for device in &self.assigned_devices {
839 device.patch(fdt, &pviommu_phandles)?;
840 }
841
Jaewan Kimc730ebf2024-02-22 10:34:55 +0900842 // Removes any dangling references in __symbols__ (e.g. removed pvIOMMUs)
843 filter_dangling_symbols(fdt)
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900844 }
845}
846
Jaewan Kim50246682024-03-11 23:18:54 +0900847/// Cleans device trees not to contain any pre-populated nodes/props for device assignment.
848pub fn clean(fdt: &mut Fdt) -> Result<()> {
849 let mut compatible = fdt.root_mut().next_compatible(cstr!("pkvm,pviommu"))?;
850 // Filters pre-populated
851 while let Some(filtered_pviommu) = compatible {
852 compatible = filtered_pviommu.delete_and_next_compatible(cstr!("pkvm,pviommu"))?;
853 }
854
855 // Removes any dangling references in __symbols__ (e.g. removed pvIOMMUs)
856 filter_dangling_symbols(fdt)
857}
858
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900859#[cfg(test)]
860mod tests {
861 use super::*;
Jaewan Kim52477ae2023-11-21 21:20:52 +0900862 use alloc::collections::{BTreeMap, BTreeSet};
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900863 use std::fs;
864
865 const VM_DTBO_FILE_PATH: &str = "test_pvmfw_devices_vm_dtbo.dtbo";
866 const VM_DTBO_WITHOUT_SYMBOLS_FILE_PATH: &str =
867 "test_pvmfw_devices_vm_dtbo_without_symbols.dtbo";
Jaewan Kim19b984f2023-12-04 15:16:50 +0900868 const VM_DTBO_WITH_DUPLICATED_IOMMUS_FILE_PATH: &str =
869 "test_pvmfw_devices_vm_dtbo_with_duplicated_iommus.dtbo";
Jaewan Kima67e36a2023-11-29 16:50:23 +0900870 const FDT_WITHOUT_IOMMUS_FILE_PATH: &str = "test_pvmfw_devices_without_iommus.dtb";
Jaewan Kim52477ae2023-11-21 21:20:52 +0900871 const FDT_WITHOUT_DEVICE_FILE_PATH: &str = "test_pvmfw_devices_without_device.dtb";
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900872 const FDT_FILE_PATH: &str = "test_pvmfw_devices_with_rng.dtb";
Pierre-Clément Tosi49e26ce2024-03-12 16:31:50 +0000873 const FDT_WITH_DEVICE_OVERLAPPING_PVMFW: &str = "test_pvmfw_devices_overlapping_pvmfw.dtb";
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900874 const FDT_WITH_MULTIPLE_DEVICES_IOMMUS_FILE_PATH: &str =
875 "test_pvmfw_devices_with_multiple_devices_iommus.dtb";
876 const FDT_WITH_IOMMU_SHARING: &str = "test_pvmfw_devices_with_iommu_sharing.dtb";
877 const FDT_WITH_IOMMU_ID_CONFLICT: &str = "test_pvmfw_devices_with_iommu_id_conflict.dtb";
Jaewan Kim19b984f2023-12-04 15:16:50 +0900878 const FDT_WITH_DUPLICATED_PVIOMMUS_FILE_PATH: &str =
879 "test_pvmfw_devices_with_duplicated_pviommus.dtb";
880 const FDT_WITH_MULTIPLE_REG_IOMMU_FILE_PATH: &str =
881 "test_pvmfw_devices_with_multiple_reg_iommus.dtb";
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900882
Jaewan Kim52477ae2023-11-21 21:20:52 +0900883 #[derive(Debug, Default)]
884 struct MockHypervisor {
885 mmio_tokens: BTreeMap<(u64, u64), u64>,
886 iommu_tokens: BTreeMap<(u64, u64), (u64, u64)>,
887 }
888
889 impl DeviceAssigningHypervisor for MockHypervisor {
890 fn get_phys_mmio_token(&self, base_ipa: u64, size: u64) -> hyp::Result<u64> {
891 Ok(*self.mmio_tokens.get(&(base_ipa, size)).ok_or(hyp::Error::KvmError(
892 hyp::KvmError::InvalidParameter,
893 0xc6000012, /* VENDOR_HYP_KVM_DEV_REQ_MMIO_FUNC_ID */
894 ))?)
895 }
896
897 fn get_phys_iommu_token(&self, pviommu_id: u64, vsid: u64) -> hyp::Result<(u64, u64)> {
898 Ok(*self.iommu_tokens.get(&(pviommu_id, vsid)).ok_or(hyp::Error::KvmError(
899 hyp::KvmError::InvalidParameter,
900 0xc6000013, /* VENDOR_HYP_KVM_DEV_REQ_DMA_FUNC_ID */
901 ))?)
902 }
903 }
904
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900905 #[derive(Debug, Eq, PartialEq)]
906 struct AssignedDeviceNode {
907 path: CString,
908 reg: Vec<u8>,
909 interrupts: Vec<u8>,
Jaewan Kima67e36a2023-11-29 16:50:23 +0900910 iommus: Vec<u32>, // pvIOMMU id and vSID
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900911 }
912
913 impl AssignedDeviceNode {
914 fn parse(fdt: &Fdt, path: &CStr) -> Result<Self> {
915 let Some(node) = fdt.node(path)? else {
916 return Err(FdtError::NotFound.into());
917 };
918
Jaewan Kim19b984f2023-12-04 15:16:50 +0900919 let reg = node.getprop(cstr!("reg"))?.ok_or(DeviceAssignmentError::MalformedReg)?;
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900920 let interrupts = node
921 .getprop(cstr!("interrupts"))?
922 .ok_or(DeviceAssignmentError::InvalidInterrupts)?;
923 let mut iommus = vec![];
Jaewan Kima9200492023-11-21 20:45:31 +0900924 if let Some(mut cells) = node.getprop_cells(cstr!("iommus"))? {
925 while let Some(pviommu_id) = cells.next() {
926 // pvIOMMU id
927 let phandle = Phandle::try_from(pviommu_id)?;
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900928 let pviommu = fdt
929 .node_with_phandle(phandle)?
Jaewan Kim19b984f2023-12-04 15:16:50 +0900930 .ok_or(DeviceAssignmentError::MalformedIommus)?;
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900931 let compatible = pviommu.getprop_str(cstr!("compatible"));
932 if compatible != Ok(Some(cstr!("pkvm,pviommu"))) {
Jaewan Kim19b984f2023-12-04 15:16:50 +0900933 return Err(DeviceAssignmentError::MalformedIommus);
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900934 }
935 let id = pviommu
936 .getprop_u32(cstr!("id"))?
Jaewan Kim19b984f2023-12-04 15:16:50 +0900937 .ok_or(DeviceAssignmentError::MalformedIommus)?;
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900938 iommus.push(id);
Jaewan Kima9200492023-11-21 20:45:31 +0900939
940 // vSID
941 let Some(vsid) = cells.next() else {
Jaewan Kim19b984f2023-12-04 15:16:50 +0900942 return Err(DeviceAssignmentError::MalformedIommus);
Jaewan Kima9200492023-11-21 20:45:31 +0900943 };
944 iommus.push(vsid);
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900945 }
946 }
947 Ok(Self { path: path.into(), reg: reg.into(), interrupts: interrupts.into(), iommus })
948 }
949 }
950
951 fn collect_pviommus(fdt: &Fdt) -> Result<Vec<u32>> {
952 let mut pviommus = BTreeSet::new();
953 for pviommu in fdt.compatible_nodes(cstr!("pkvm,pviommu"))? {
954 if let Ok(Some(id)) = pviommu.getprop_u32(cstr!("id")) {
955 pviommus.insert(id);
956 }
957 }
958 Ok(pviommus.iter().cloned().collect())
959 }
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900960
961 fn into_fdt_prop(native_bytes: Vec<u32>) -> Vec<u8> {
962 let mut v = Vec::with_capacity(native_bytes.len() * 4);
963 for byte in native_bytes {
964 v.extend_from_slice(&byte.to_be_bytes());
965 }
966 v
967 }
968
Jaewan Kim52477ae2023-11-21 21:20:52 +0900969 impl From<[u64; 2]> for DeviceReg {
970 fn from(fdt_cells: [u64; 2]) -> Self {
971 DeviceReg { addr: fdt_cells[0], size: fdt_cells[1] }
972 }
973 }
974
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900975 #[test]
976 fn device_info_new_without_symbols() {
977 let mut fdt_data = fs::read(FDT_FILE_PATH).unwrap();
978 let mut vm_dtbo_data = fs::read(VM_DTBO_WITHOUT_SYMBOLS_FILE_PATH).unwrap();
979 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
980 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
981
Jaewan Kim52477ae2023-11-21 21:20:52 +0900982 let hypervisor: MockHypervisor = Default::default();
983 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor).unwrap();
984 assert_eq!(device_info, None);
985 }
986
987 #[test]
988 fn device_info_new_without_device() {
989 let mut fdt_data = fs::read(FDT_WITHOUT_DEVICE_FILE_PATH).unwrap();
990 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
991 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
992 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
993
994 let hypervisor: MockHypervisor = Default::default();
995 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor).unwrap();
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900996 assert_eq!(device_info, None);
997 }
998
999 #[test]
Jaewan Kima67e36a2023-11-29 16:50:23 +09001000 fn device_info_assigned_info_without_iommus() {
1001 let mut fdt_data = fs::read(FDT_WITHOUT_IOMMUS_FILE_PATH).unwrap();
1002 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1003 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1004 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1005
Jaewan Kim52477ae2023-11-21 21:20:52 +09001006 let hypervisor = MockHypervisor {
1007 mmio_tokens: [((0x9, 0xFF), 0x300)].into(),
1008 iommu_tokens: BTreeMap::new(),
1009 };
1010 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor).unwrap().unwrap();
Jaewan Kima67e36a2023-11-29 16:50:23 +09001011
1012 let expected = [AssignedDeviceInfo {
Jaewan Kimc39974e2023-12-02 01:13:30 +09001013 node_path: CString::new("/bus0/backlight").unwrap(),
Jaewan Kim52477ae2023-11-21 21:20:52 +09001014 reg: vec![[0x9, 0xFF].into()],
Jaewan Kima67e36a2023-11-29 16:50:23 +09001015 interrupts: into_fdt_prop(vec![0x0, 0xF, 0x4]),
1016 iommus: vec![],
1017 }];
1018
1019 assert_eq!(device_info.assigned_devices, expected);
1020 }
1021
1022 #[test]
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001023 fn device_info_assigned_info() {
1024 let mut fdt_data = fs::read(FDT_FILE_PATH).unwrap();
1025 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1026 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1027 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1028
Jaewan Kim52477ae2023-11-21 21:20:52 +09001029 let hypervisor = MockHypervisor {
1030 mmio_tokens: [((0x9, 0xFF), 0x12F00000)].into(),
1031 iommu_tokens: [((0x4, 0xFF0), (0x12E40000, 0x3))].into(),
1032 };
1033 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor).unwrap().unwrap();
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001034
1035 let expected = [AssignedDeviceInfo {
1036 node_path: CString::new("/rng").unwrap(),
Jaewan Kim52477ae2023-11-21 21:20:52 +09001037 reg: vec![[0x9, 0xFF].into()],
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001038 interrupts: into_fdt_prop(vec![0x0, 0xF, 0x4]),
Jaewan Kima67e36a2023-11-29 16:50:23 +09001039 iommus: vec![(PvIommu { id: 0x4 }, Vsid(0xFF0))],
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001040 }];
1041
1042 assert_eq!(device_info.assigned_devices, expected);
1043 }
1044
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001045 #[test]
1046 fn device_info_filter() {
1047 let mut fdt_data = fs::read(FDT_FILE_PATH).unwrap();
1048 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1049 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1050 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1051
Jaewan Kim52477ae2023-11-21 21:20:52 +09001052 let hypervisor = MockHypervisor {
1053 mmio_tokens: [((0x9, 0xFF), 0x12F00000)].into(),
1054 iommu_tokens: [((0x4, 0xFF0), (0x12E40000, 0x3))].into(),
1055 };
1056 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor).unwrap().unwrap();
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001057 device_info.filter(vm_dtbo).unwrap();
1058
1059 let vm_dtbo = vm_dtbo.as_mut();
1060
Jaewan Kim371f6c82024-02-24 01:33:37 +09001061 let symbols = vm_dtbo.symbols().unwrap().unwrap();
1062
Jaewan Kima232ed02024-02-25 16:08:14 +00001063 let rng = vm_dtbo.node(cstr!("/fragment@0/__overlay__/rng")).unwrap();
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001064 assert_ne!(rng, None);
Jaewan Kim371f6c82024-02-24 01:33:37 +09001065 let rng_symbol = symbols.getprop_str(cstr!("rng")).unwrap();
Jaewan Kima232ed02024-02-25 16:08:14 +00001066 assert_eq!(Some(cstr!("/fragment@0/__overlay__/rng")), rng_symbol);
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001067
Jaewan Kima232ed02024-02-25 16:08:14 +00001068 let light = vm_dtbo.node(cstr!("/fragment@0/__overlay__/light")).unwrap();
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001069 assert_eq!(light, None);
Jaewan Kim371f6c82024-02-24 01:33:37 +09001070 let light_symbol = symbols.getprop_str(cstr!("light")).unwrap();
1071 assert_eq!(None, light_symbol);
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001072
Jaewan Kima232ed02024-02-25 16:08:14 +00001073 let led = vm_dtbo.node(cstr!("/fragment@0/__overlay__/led")).unwrap();
Jaewan Kima67e36a2023-11-29 16:50:23 +09001074 assert_eq!(led, None);
Jaewan Kim371f6c82024-02-24 01:33:37 +09001075 let led_symbol = symbols.getprop_str(cstr!("led")).unwrap();
1076 assert_eq!(None, led_symbol);
Jaewan Kima67e36a2023-11-29 16:50:23 +09001077
Jaewan Kima232ed02024-02-25 16:08:14 +00001078 let backlight = vm_dtbo.node(cstr!("/fragment@0/__overlay__/bus0/backlight")).unwrap();
Jaewan Kima67e36a2023-11-29 16:50:23 +09001079 assert_eq!(backlight, None);
Jaewan Kim371f6c82024-02-24 01:33:37 +09001080 let backlight_symbol = symbols.getprop_str(cstr!("backlight")).unwrap();
1081 assert_eq!(None, backlight_symbol);
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001082 }
1083
1084 #[test]
1085 fn device_info_patch() {
Jaewan Kima67e36a2023-11-29 16:50:23 +09001086 let mut fdt_data = fs::read(FDT_WITHOUT_IOMMUS_FILE_PATH).unwrap();
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001087 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1088 let mut data = vec![0_u8; fdt_data.len() + vm_dtbo_data.len()];
1089 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1090 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1091 let platform_dt = Fdt::create_empty_tree(data.as_mut_slice()).unwrap();
1092
Jaewan Kim52477ae2023-11-21 21:20:52 +09001093 let hypervisor = MockHypervisor {
1094 mmio_tokens: [((0x9, 0xFF), 0x300)].into(),
1095 iommu_tokens: BTreeMap::new(),
1096 };
1097 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor).unwrap().unwrap();
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001098 device_info.filter(vm_dtbo).unwrap();
1099
1100 // SAFETY: Damaged VM DTBO wouldn't be used after this unsafe block.
1101 unsafe {
1102 platform_dt.apply_overlay(vm_dtbo.as_mut()).unwrap();
1103 }
Jaewan Kim0bd637d2023-11-10 13:09:41 +09001104 device_info.patch(platform_dt).unwrap();
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001105
Jaewan Kimc39974e2023-12-02 01:13:30 +09001106 let rng_node = platform_dt.node(cstr!("/bus0/backlight")).unwrap().unwrap();
1107 let phandle = rng_node.getprop_u32(cstr!("phandle")).unwrap();
1108 assert_ne!(None, phandle);
1109
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001110 // Note: Intentionally not using AssignedDeviceNode for matching all props.
Jaewan Kim0bd637d2023-11-10 13:09:41 +09001111 type FdtResult<T> = libfdt::Result<T>;
1112 let expected: Vec<(FdtResult<&CStr>, FdtResult<Vec<u8>>)> = vec![
Jaewan Kima67e36a2023-11-29 16:50:23 +09001113 (Ok(cstr!("android,backlight,ignore-gctrl-reset")), Ok(Vec::new())),
1114 (Ok(cstr!("compatible")), Ok(Vec::from(*b"android,backlight\0"))),
Jaewan Kim0bd637d2023-11-10 13:09:41 +09001115 (Ok(cstr!("interrupts")), Ok(into_fdt_prop(vec![0x0, 0xF, 0x4]))),
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001116 (Ok(cstr!("iommus")), Ok(Vec::new())),
Jaewan Kimc39974e2023-12-02 01:13:30 +09001117 (Ok(cstr!("phandle")), Ok(into_fdt_prop(vec![phandle.unwrap()]))),
Jaewan Kim0bd637d2023-11-10 13:09:41 +09001118 (Ok(cstr!("reg")), Ok(into_fdt_prop(vec![0x0, 0x9, 0x0, 0xFF]))),
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001119 ];
1120
Jaewan Kim0bd637d2023-11-10 13:09:41 +09001121 let mut properties: Vec<_> = rng_node
1122 .properties()
1123 .unwrap()
1124 .map(|prop| (prop.name(), prop.value().map(|x| x.into())))
1125 .collect();
1126 properties.sort_by(|a, b| {
1127 let lhs = a.0.unwrap_or_default();
1128 let rhs = b.0.unwrap_or_default();
1129 lhs.partial_cmp(rhs).unwrap()
1130 });
1131
1132 assert_eq!(properties, expected);
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001133 }
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001134
1135 #[test]
Jaewan Kimc730ebf2024-02-22 10:34:55 +09001136 fn device_info_patch_no_pviommus() {
1137 let mut fdt_data = fs::read(FDT_WITHOUT_IOMMUS_FILE_PATH).unwrap();
1138 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1139 let mut data = vec![0_u8; fdt_data.len() + vm_dtbo_data.len()];
1140 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1141 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1142 let platform_dt = Fdt::create_empty_tree(data.as_mut_slice()).unwrap();
1143
1144 let hypervisor = MockHypervisor {
1145 mmio_tokens: [((0x9, 0xFF), 0x300)].into(),
1146 iommu_tokens: BTreeMap::new(),
1147 };
1148 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor).unwrap().unwrap();
1149 device_info.filter(vm_dtbo).unwrap();
1150
1151 // SAFETY: Damaged VM DTBO wouldn't be used after this unsafe block.
1152 unsafe {
1153 platform_dt.apply_overlay(vm_dtbo.as_mut()).unwrap();
1154 }
1155 device_info.patch(platform_dt).unwrap();
1156
1157 let compatible = platform_dt.root().next_compatible(cstr!("pkvm,pviommu")).unwrap();
1158 assert_eq!(None, compatible);
1159
1160 if let Some(symbols) = platform_dt.symbols().unwrap() {
1161 for prop in symbols.properties().unwrap() {
1162 let path = CStr::from_bytes_with_nul(prop.value().unwrap()).unwrap();
1163 assert_ne!(None, platform_dt.node(path).unwrap());
1164 }
1165 }
1166 }
1167
1168 #[test]
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001169 fn device_info_overlay_iommu() {
Jaewan Kima67e36a2023-11-29 16:50:23 +09001170 let mut fdt_data = fs::read(FDT_FILE_PATH).unwrap();
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001171 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1172 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1173 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1174 let mut platform_dt_data = pvmfw_fdt_template::RAW.to_vec();
1175 platform_dt_data.resize(pvmfw_fdt_template::RAW.len() * 2, 0);
1176 let platform_dt = Fdt::from_mut_slice(&mut platform_dt_data).unwrap();
1177 platform_dt.unpack().unwrap();
1178
Jaewan Kim52477ae2023-11-21 21:20:52 +09001179 let hypervisor = MockHypervisor {
1180 mmio_tokens: [((0x9, 0xFF), 0x12F00000)].into(),
1181 iommu_tokens: [((0x4, 0xFF0), (0x12E40000, 0x3))].into(),
1182 };
1183 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor).unwrap().unwrap();
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001184 device_info.filter(vm_dtbo).unwrap();
1185
1186 // SAFETY: Damaged VM DTBO wouldn't be used after this unsafe block.
1187 unsafe {
1188 platform_dt.apply_overlay(vm_dtbo.as_mut()).unwrap();
1189 }
1190 device_info.patch(platform_dt).unwrap();
1191
1192 let expected = AssignedDeviceNode {
1193 path: CString::new("/rng").unwrap(),
1194 reg: into_fdt_prop(vec![0x0, 0x9, 0x0, 0xFF]),
1195 interrupts: into_fdt_prop(vec![0x0, 0xF, 0x4]),
Jaewan Kima9200492023-11-21 20:45:31 +09001196 iommus: vec![0x4, 0xFF0],
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001197 };
1198
1199 let node = AssignedDeviceNode::parse(platform_dt, &expected.path);
1200 assert_eq!(node, Ok(expected));
1201
1202 let pviommus = collect_pviommus(platform_dt);
1203 assert_eq!(pviommus, Ok(vec![0x4]));
1204 }
1205
1206 #[test]
1207 fn device_info_multiple_devices_iommus() {
1208 let mut fdt_data = fs::read(FDT_WITH_MULTIPLE_DEVICES_IOMMUS_FILE_PATH).unwrap();
1209 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1210 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1211 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1212 let mut platform_dt_data = pvmfw_fdt_template::RAW.to_vec();
1213 platform_dt_data.resize(pvmfw_fdt_template::RAW.len() * 2, 0);
1214 let platform_dt = Fdt::from_mut_slice(&mut platform_dt_data).unwrap();
1215 platform_dt.unpack().unwrap();
1216
Jaewan Kim52477ae2023-11-21 21:20:52 +09001217 let hypervisor = MockHypervisor {
1218 mmio_tokens: [
1219 ((0x9, 0xFF), 0x12F00000),
Jaewan Kim19b984f2023-12-04 15:16:50 +09001220 ((0x10000, 0x1000), 0xF00000),
1221 ((0x20000, 0x1000), 0xF10000),
Jaewan Kim52477ae2023-11-21 21:20:52 +09001222 ]
1223 .into(),
1224 iommu_tokens: [
1225 ((0x4, 0xFF0), (0x12E40000, 3)),
1226 ((0x40, 0xFFA), (0x40000, 0x4)),
1227 ((0x50, 0xFFB), (0x50000, 0x5)),
1228 ]
1229 .into(),
1230 };
1231 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor).unwrap().unwrap();
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001232 device_info.filter(vm_dtbo).unwrap();
1233
1234 // SAFETY: Damaged VM DTBO wouldn't be used after this unsafe block.
1235 unsafe {
1236 platform_dt.apply_overlay(vm_dtbo.as_mut()).unwrap();
1237 }
1238 device_info.patch(platform_dt).unwrap();
1239
1240 let expected_devices = [
1241 AssignedDeviceNode {
1242 path: CString::new("/rng").unwrap(),
1243 reg: into_fdt_prop(vec![0x0, 0x9, 0x0, 0xFF]),
1244 interrupts: into_fdt_prop(vec![0x0, 0xF, 0x4]),
Jaewan Kima67e36a2023-11-29 16:50:23 +09001245 iommus: vec![0x4, 0xFF0],
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001246 },
1247 AssignedDeviceNode {
1248 path: CString::new("/light").unwrap(),
Jaewan Kim19b984f2023-12-04 15:16:50 +09001249 reg: into_fdt_prop(vec![0x0, 0x10000, 0x0, 0x1000, 0x0, 0x20000, 0x0, 0x1000]),
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001250 interrupts: into_fdt_prop(vec![0x0, 0xF, 0x5]),
Jaewan Kima67e36a2023-11-29 16:50:23 +09001251 iommus: vec![0x40, 0xFFA, 0x50, 0xFFB],
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001252 },
1253 ];
1254
1255 for expected in expected_devices {
1256 let node = AssignedDeviceNode::parse(platform_dt, &expected.path);
1257 assert_eq!(node, Ok(expected));
1258 }
1259 let pviommus = collect_pviommus(platform_dt);
Jaewan Kima67e36a2023-11-29 16:50:23 +09001260 assert_eq!(pviommus, Ok(vec![0x4, 0x40, 0x50]));
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001261 }
1262
1263 #[test]
1264 fn device_info_iommu_sharing() {
1265 let mut fdt_data = fs::read(FDT_WITH_IOMMU_SHARING).unwrap();
1266 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1267 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1268 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1269 let mut platform_dt_data = pvmfw_fdt_template::RAW.to_vec();
1270 platform_dt_data.resize(pvmfw_fdt_template::RAW.len() * 2, 0);
1271 let platform_dt = Fdt::from_mut_slice(&mut platform_dt_data).unwrap();
1272 platform_dt.unpack().unwrap();
1273
Jaewan Kim52477ae2023-11-21 21:20:52 +09001274 let hypervisor = MockHypervisor {
Jaewan Kim19b984f2023-12-04 15:16:50 +09001275 mmio_tokens: [((0x9, 0xFF), 0x12F00000), ((0x1000, 0x9), 0x12000000)].into(),
1276 iommu_tokens: [((0x4, 0xFF0), (0x12E40000, 3)), ((0x4, 0xFF1), (0x12E40000, 9))].into(),
Jaewan Kim52477ae2023-11-21 21:20:52 +09001277 };
1278 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor).unwrap().unwrap();
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001279 device_info.filter(vm_dtbo).unwrap();
1280
1281 // SAFETY: Damaged VM DTBO wouldn't be used after this unsafe block.
1282 unsafe {
1283 platform_dt.apply_overlay(vm_dtbo.as_mut()).unwrap();
1284 }
1285 device_info.patch(platform_dt).unwrap();
1286
1287 let expected_devices = [
1288 AssignedDeviceNode {
1289 path: CString::new("/rng").unwrap(),
1290 reg: into_fdt_prop(vec![0x0, 0x9, 0x0, 0xFF]),
1291 interrupts: into_fdt_prop(vec![0x0, 0xF, 0x4]),
Jaewan Kima67e36a2023-11-29 16:50:23 +09001292 iommus: vec![0x4, 0xFF0],
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001293 },
1294 AssignedDeviceNode {
Jaewan Kima67e36a2023-11-29 16:50:23 +09001295 path: CString::new("/led").unwrap(),
Jaewan Kim19b984f2023-12-04 15:16:50 +09001296 reg: into_fdt_prop(vec![0x0, 0x1000, 0x0, 0x9]),
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001297 interrupts: into_fdt_prop(vec![0x0, 0xF, 0x5]),
Jaewan Kim19b984f2023-12-04 15:16:50 +09001298 iommus: vec![0x4, 0xFF1],
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001299 },
1300 ];
1301
1302 for expected in expected_devices {
1303 let node = AssignedDeviceNode::parse(platform_dt, &expected.path);
1304 assert_eq!(node, Ok(expected));
1305 }
1306
1307 let pviommus = collect_pviommus(platform_dt);
Jaewan Kima67e36a2023-11-29 16:50:23 +09001308 assert_eq!(pviommus, Ok(vec![0x4]));
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001309 }
1310
1311 #[test]
1312 fn device_info_iommu_id_conflict() {
1313 let mut fdt_data = fs::read(FDT_WITH_IOMMU_ID_CONFLICT).unwrap();
1314 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1315 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1316 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1317
Jaewan Kim52477ae2023-11-21 21:20:52 +09001318 let hypervisor = MockHypervisor {
Jaewan Kim19b984f2023-12-04 15:16:50 +09001319 mmio_tokens: [((0x9, 0xFF), 0x300)].into(),
Jaewan Kim52477ae2023-11-21 21:20:52 +09001320 iommu_tokens: [((0x4, 0xFF0), (0x12E40000, 0x3))].into(),
1321 };
1322 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor);
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001323
1324 assert_eq!(device_info, Err(DeviceAssignmentError::DuplicatedPvIommuIds));
1325 }
Jaewan Kim52477ae2023-11-21 21:20:52 +09001326
1327 #[test]
1328 fn device_info_invalid_reg() {
1329 let mut fdt_data = fs::read(FDT_FILE_PATH).unwrap();
1330 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1331 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1332 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1333
1334 let hypervisor = MockHypervisor {
1335 mmio_tokens: BTreeMap::new(),
1336 iommu_tokens: [((0x4, 0xFF0), (0x12E40000, 0x3))].into(),
1337 };
1338 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor);
1339
Pierre-Clément Tosi8b78bc32024-03-13 17:37:07 +00001340 assert_eq!(device_info, Err(DeviceAssignmentError::InvalidReg(0x9, 0xFF)));
Jaewan Kim52477ae2023-11-21 21:20:52 +09001341 }
1342
1343 #[test]
Jaewan Kim19b984f2023-12-04 15:16:50 +09001344 fn device_info_invalid_reg_out_of_order() {
1345 let mut fdt_data = fs::read(FDT_WITH_MULTIPLE_REG_IOMMU_FILE_PATH).unwrap();
1346 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1347 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1348 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1349
1350 let hypervisor = MockHypervisor {
1351 mmio_tokens: [((0xF000, 0x1000), 0xF10000), ((0xF100, 0x1000), 0xF00000)].into(),
1352 iommu_tokens: [((0xFF0, 0xF0), (0x40000, 0x4)), ((0xFF1, 0xF1), (0x50000, 0x5))].into(),
1353 };
1354 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor);
1355
Pierre-Clément Tosi8b78bc32024-03-13 17:37:07 +00001356 assert_eq!(device_info, Err(DeviceAssignmentError::InvalidPhysReg(0xF10000, 0x1000)));
Jaewan Kim19b984f2023-12-04 15:16:50 +09001357 }
1358
1359 #[test]
Jaewan Kim52477ae2023-11-21 21:20:52 +09001360 fn device_info_invalid_iommus() {
1361 let mut fdt_data = fs::read(FDT_FILE_PATH).unwrap();
1362 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1363 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1364 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1365
1366 let hypervisor = MockHypervisor {
1367 mmio_tokens: [((0x9, 0xFF), 0x12F00000)].into(),
1368 iommu_tokens: BTreeMap::new(),
1369 };
1370 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor);
1371
1372 assert_eq!(device_info, Err(DeviceAssignmentError::InvalidIommus));
1373 }
Jaewan Kim19b984f2023-12-04 15:16:50 +09001374
1375 #[test]
1376 fn device_info_duplicated_pv_iommus() {
1377 let mut fdt_data = fs::read(FDT_WITH_DUPLICATED_PVIOMMUS_FILE_PATH).unwrap();
1378 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1379 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1380 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1381
1382 let hypervisor = MockHypervisor {
1383 mmio_tokens: [((0x10000, 0x1000), 0xF00000), ((0x20000, 0xFF), 0xF10000)].into(),
1384 iommu_tokens: [((0xFF, 0xF), (0x40000, 0x4))].into(),
1385 };
1386 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor);
1387
1388 assert_eq!(device_info, Err(DeviceAssignmentError::DuplicatedPvIommuIds));
1389 }
1390
1391 #[test]
1392 fn device_info_duplicated_iommus() {
1393 let mut fdt_data = fs::read(FDT_FILE_PATH).unwrap();
1394 let mut vm_dtbo_data = fs::read(VM_DTBO_WITH_DUPLICATED_IOMMUS_FILE_PATH).unwrap();
1395 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1396 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1397
1398 let hypervisor = MockHypervisor {
1399 mmio_tokens: [((0x10000, 0x1000), 0xF00000), ((0x20000, 0xFF), 0xF10000)].into(),
1400 iommu_tokens: [((0xFF, 0xF), (0x40000, 0x4))].into(),
1401 };
1402 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor);
1403
1404 assert_eq!(device_info, Err(DeviceAssignmentError::UnsupportedIommusDuplication));
1405 }
1406
1407 #[test]
1408 fn device_info_duplicated_iommu_mapping() {
1409 let mut fdt_data = fs::read(FDT_WITH_MULTIPLE_REG_IOMMU_FILE_PATH).unwrap();
1410 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1411 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1412 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1413
1414 let hypervisor = MockHypervisor {
1415 mmio_tokens: [((0xF000, 0x1000), 0xF00000), ((0xF100, 0x1000), 0xF10000)].into(),
1416 iommu_tokens: [((0xFF0, 0xF0), (0x40000, 0x4)), ((0xFF1, 0xF1), (0x40000, 0x4))].into(),
1417 };
1418 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor);
1419
1420 assert_eq!(device_info, Err(DeviceAssignmentError::InvalidIommus));
1421 }
Jaewan Kim50246682024-03-11 23:18:54 +09001422
1423 #[test]
Pierre-Clément Tosi49e26ce2024-03-12 16:31:50 +00001424 fn device_info_overlaps_pvmfw() {
1425 let mut fdt_data = fs::read(FDT_WITH_DEVICE_OVERLAPPING_PVMFW).unwrap();
1426 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1427 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1428 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1429
1430 let hypervisor = MockHypervisor {
1431 mmio_tokens: [((0x7fee0000, 0x1000), 0xF00000)].into(),
1432 iommu_tokens: [((0xFF, 0xF), (0x40000, 0x4))].into(),
1433 };
1434 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor);
1435
1436 assert_eq!(device_info, Err(DeviceAssignmentError::InvalidReg(0x7fee0000, 0x1000)));
1437 }
1438
1439 #[test]
Jaewan Kim50246682024-03-11 23:18:54 +09001440 fn device_assignment_clean() {
1441 let mut platform_dt_data = pvmfw_fdt_template::RAW.to_vec();
1442 let platform_dt = Fdt::from_mut_slice(&mut platform_dt_data).unwrap();
1443
1444 let compatible = platform_dt.root().next_compatible(cstr!("pkvm,pviommu"));
1445 assert_ne!(None, compatible.unwrap());
1446
1447 clean(platform_dt).unwrap();
1448
1449 let compatible = platform_dt.root().next_compatible(cstr!("pkvm,pviommu"));
1450 assert_eq!(Ok(None), compatible);
1451 }
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001452}