blob: edfe000d0854bda610e35a9c6ba19d0dafb96bdb [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;
Jaewan Kim52477ae2023-11-21 21:20:52 +090030use hyp::DeviceAssigningHypervisor;
31use libfdt::{Fdt, FdtError, FdtNode, Phandle, Reg};
32use log::error;
Jaewan Kimc6e023b2023-10-12 15:11:05 +090033
Jaewan Kimc6e023b2023-10-12 15:11:05 +090034// TODO(b/308694211): Use cstr! from vmbase instead.
35macro_rules! cstr {
36 ($str:literal) => {{
Pierre-Clément Tosid701a0b2023-11-07 15:38:59 +000037 const S: &str = concat!($str, "\0");
38 const C: &::core::ffi::CStr = match ::core::ffi::CStr::from_bytes_with_nul(S.as_bytes()) {
39 Ok(v) => v,
40 Err(_) => panic!("string contains interior NUL"),
41 };
42 C
Jaewan Kimc6e023b2023-10-12 15:11:05 +090043 }};
44}
45
Jaewan Kimc6e023b2023-10-12 15:11:05 +090046// TODO(b/277993056): Keep constants derived from platform.dts in one place.
47const CELLS_PER_INTERRUPT: usize = 3; // from /intc node in platform.dts
48
49/// Errors in device assignment.
50#[derive(Clone, Copy, Debug, Eq, PartialEq)]
51pub enum DeviceAssignmentError {
Jaewan Kim52477ae2023-11-21 21:20:52 +090052 /// Invalid VM DTBO
Jaewan Kimc6e023b2023-10-12 15:11:05 +090053 InvalidDtbo,
54 /// Invalid __symbols__
55 InvalidSymbols,
Jaewan Kim19b984f2023-12-04 15:16:50 +090056 /// Malformed <reg>. Can't parse.
57 MalformedReg,
Pierre-Clément Tosi8b78bc32024-03-13 17:37:07 +000058 /// Invalid physical <reg> of assigned device.
59 InvalidPhysReg(u64, u64),
60 /// Invalid virtual <reg> of assigned device.
61 InvalidReg(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 Tosi8b78bc32024-03-13 17:37:07 +0000109 Self::InvalidReg(addr, size) => {
110 write!(f, "Invalid guest MMIO region (addr: {addr:#x}, size: {size:#x})")
111 }
112 Self::InvalidPhysReg(addr, size) => {
113 write!(f, "Invalid physical MMIO region (addr: {addr:#x}, size: {size:#x})")
114 }
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900115 Self::InvalidInterrupts => write!(f, "Invalid <interrupts>"),
Jaewan Kim19b984f2023-12-04 15:16:50 +0900116 Self::MalformedIommus => write!(f, "Malformed <iommus>. Can't parse."),
117 Self::InvalidIommus => {
118 write!(f, "Invalid <iommus>. Failed to validate with hypervisor")
119 }
120 Self::InvalidPhysIommu => write!(f, "Invalid phys IOMMU node"),
Jaewan Kima9200492023-11-21 20:45:31 +0900121 Self::InvalidPvIommu => write!(f, "Invalid pvIOMMU node"),
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900122 Self::TooManyPvIommu => write!(
123 f,
124 "Too many pvIOMMU node. Insufficient pre-populated pvIOMMUs in platform DT"
125 ),
Jaewan Kim19b984f2023-12-04 15:16:50 +0900126 Self::DuplicatedIommuIds => {
127 write!(f, "Duplicated IOMMU IDs exist. IDs must unique among iommu node")
128 }
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900129 Self::DuplicatedPvIommuIds => {
Jaewan Kim19b984f2023-12-04 15:16:50 +0900130 write!(f, "Duplicated pvIOMMU IDs exist. IDs must unique among iommu node")
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900131 }
Jaewan Kimf8abbb52023-12-12 22:11:39 +0900132 Self::UnsupportedPathFormat => {
133 write!(f, "Unsupported UnsupportedPathFormat. Only supports full path")
134 }
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900135 Self::UnsupportedOverlayTarget => {
136 write!(f, "Unsupported overlay target. Only supports 'target-path = \"/\"'")
137 }
Jaewan Kim19b984f2023-12-04 15:16:50 +0900138 Self::UnsupportedPhysIommu => {
139 write!(f, "Unsupported Phys IOMMU. Currently only supports #iommu-cells = <1>")
140 }
141 Self::UnsupportedPvIommusDuplication => {
142 write!(f, "Unsupported (pvIOMMU id, vSID) duplication. Currently the pair should be unique.")
143 }
144 Self::UnsupportedIommusDuplication => {
145 write!(f, "Unsupported (IOMMU token, SID) duplication. Currently the pair should be unique.")
146 }
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900147 Self::Internal => write!(f, "Internal error"),
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900148 Self::UnexpectedFdtError(e) => write!(f, "Unexpected Error from libfdt: {e}"),
149 }
150 }
151}
152
153pub type Result<T> = core::result::Result<T, DeviceAssignmentError>;
154
Jaewan Kimf8abbb52023-12-12 22:11:39 +0900155#[derive(Clone, Default, Ord, PartialOrd, Eq, PartialEq)]
156pub struct DtPathTokens<'a> {
157 tokens: Vec<&'a [u8]>,
158}
159
160impl<'a> fmt::Debug for DtPathTokens<'a> {
161 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
162 let mut list = f.debug_list();
163 for token in &self.tokens {
164 let mut bytes = token.to_vec();
165 bytes.push(b'\0');
166 match CString::from_vec_with_nul(bytes) {
167 Ok(string) => list.entry(&string),
168 Err(_) => list.entry(token),
169 };
170 }
171 list.finish()
172 }
173}
174
175impl<'a> DtPathTokens<'a> {
176 fn new(path: &'a CStr) -> Result<Self> {
177 if path.to_bytes().first() != Some(&b'/') {
178 return Err(DeviceAssignmentError::UnsupportedPathFormat);
179 }
180 let tokens: Vec<_> = path
181 .to_bytes()
182 .split(|char| *char == b'/')
183 .filter(|&component| !component.is_empty())
184 .collect();
185 Ok(Self { tokens })
186 }
187
188 fn to_overlay_target_path(&self) -> Result<Self> {
189 if !self.is_overlayable_node() {
190 return Err(DeviceAssignmentError::InvalidDtbo);
191 }
192 Ok(Self { tokens: self.tokens.as_slice()[2..].to_vec() })
193 }
194
195 fn to_cstring(&self) -> CString {
196 if self.tokens.is_empty() {
197 return CString::new(*b"/\0").unwrap();
198 }
199
200 let size = self.tokens.iter().fold(0, |sum, token| sum + token.len() + 1);
201 let mut path = Vec::with_capacity(size + 1);
202 for token in &self.tokens {
203 path.push(b'/');
204 path.extend_from_slice(token);
205 }
206 path.push(b'\0');
207
208 CString::from_vec_with_nul(path).unwrap()
209 }
210
211 fn is_overlayable_node(&self) -> bool {
212 self.tokens.get(1) == Some(&&b"__overlay__"[..])
213 }
214}
215
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900216/// Represents VM DTBO
217#[repr(transparent)]
218pub struct VmDtbo(Fdt);
219
220impl VmDtbo {
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900221 /// Wraps a mutable slice containing a VM DTBO.
222 ///
223 /// Fails if the VM DTBO does not pass validation.
224 pub fn from_mut_slice(dtbo: &mut [u8]) -> Result<&mut Self> {
225 // This validates DTBO
226 let fdt = Fdt::from_mut_slice(dtbo)?;
227 // SAFETY: VmDtbo is a transparent wrapper around Fdt, so representation is the same.
228 Ok(unsafe { mem::transmute::<&mut Fdt, &mut Self>(fdt) })
229 }
230
231 // Locates device node path as if the given dtbo node path is assigned and VM DTBO is overlaid.
232 // For given dtbo node path, this concatenates <target-path> of the enclosing fragment and
233 // relative path from __overlay__ node.
234 //
235 // Here's an example with sample VM DTBO:
236 // / {
237 // fragment@rng {
238 // target-path = "/"; // Always 'target-path = "/"'. Disallows <target> or other path.
239 // __overlay__ {
240 // rng { ... }; // Actual device node is here. If overlaid, path would be "/rng"
241 // };
242 // };
Jaewan Kim80ef9fa2024-02-25 16:08:14 +0000243 // __symbols__ { // Contains list of assignable devices
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900244 // rng = "/fragment@rng/__overlay__/rng";
245 // };
246 // };
247 //
248 // Then locate_overlay_target_path(cstr!("/fragment@rng/__overlay__/rng")) is Ok("/rng")
249 //
250 // Contrary to fdt_overlay_target_offset(), this API enforces overlay target property
251 // 'target-path = "/"', so the overlay doesn't modify and/or append platform DT's existing
252 // node and/or properties. The enforcement is for compatibility reason.
Jaewan Kim19b984f2023-12-04 15:16:50 +0900253 fn locate_overlay_target_path(
254 &self,
Jaewan Kimf8abbb52023-12-12 22:11:39 +0900255 dtbo_node_path: &DtPathTokens,
Jaewan Kim19b984f2023-12-04 15:16:50 +0900256 dtbo_node: &FdtNode,
257 ) -> Result<CString> {
Jaewan Kim19b984f2023-12-04 15:16:50 +0900258 let fragment_node = dtbo_node.supernode_at_depth(1)?;
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900259 let target_path = fragment_node
Pierre-Clément Tosid701a0b2023-11-07 15:38:59 +0000260 .getprop_str(cstr!("target-path"))?
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900261 .ok_or(DeviceAssignmentError::InvalidDtbo)?;
262 if target_path != cstr!("/") {
263 return Err(DeviceAssignmentError::UnsupportedOverlayTarget);
264 }
265
Jaewan Kimf8abbb52023-12-12 22:11:39 +0900266 let overlaid_path = dtbo_node_path.to_overlay_target_path()?;
267 Ok(overlaid_path.to_cstring())
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900268 }
Jaewan Kim19b984f2023-12-04 15:16:50 +0900269
270 fn parse_physical_iommus(physical_node: &FdtNode) -> Result<BTreeMap<Phandle, PhysIommu>> {
271 let mut phys_iommus = BTreeMap::new();
272 for (node, _) in physical_node.descendants() {
273 let Some(phandle) = node.get_phandle()? else {
274 continue; // Skips unreachable IOMMU node
275 };
276 let Some(iommu) = PhysIommu::parse(&node)? else {
277 continue; // Skip if not a PhysIommu.
278 };
279 if phys_iommus.insert(phandle, iommu).is_some() {
280 return Err(FdtError::BadPhandle.into());
281 }
282 }
283 Self::validate_physical_iommus(&phys_iommus)?;
284 Ok(phys_iommus)
285 }
286
287 fn validate_physical_iommus(phys_iommus: &BTreeMap<Phandle, PhysIommu>) -> Result<()> {
288 let unique_iommus: BTreeSet<_> = phys_iommus.values().cloned().collect();
289 if phys_iommus.len() != unique_iommus.len() {
290 return Err(DeviceAssignmentError::DuplicatedIommuIds);
291 }
292 Ok(())
293 }
294
295 fn validate_physical_devices(
296 physical_devices: &BTreeMap<Phandle, PhysicalDeviceInfo>,
297 ) -> Result<()> {
298 // Only need to validate iommus because <reg> will be validated together with PV <reg>
299 // see: DeviceAssignmentInfo::validate_all_regs().
300 let mut all_iommus = BTreeSet::new();
301 for physical_device in physical_devices.values() {
302 for iommu in &physical_device.iommus {
303 if !all_iommus.insert(iommu) {
304 error!("Unsupported phys IOMMU duplication found, <iommus> = {iommu:?}");
305 return Err(DeviceAssignmentError::UnsupportedIommusDuplication);
306 }
307 }
308 }
309 Ok(())
310 }
311
312 fn parse_physical_devices_with_iommus(
313 physical_node: &FdtNode,
314 phys_iommus: &BTreeMap<Phandle, PhysIommu>,
315 ) -> Result<BTreeMap<Phandle, PhysicalDeviceInfo>> {
316 let mut physical_devices = BTreeMap::new();
317 for (node, _) in physical_node.descendants() {
318 let Some(info) = PhysicalDeviceInfo::parse(&node, phys_iommus)? else {
319 continue;
320 };
321 if physical_devices.insert(info.target, info).is_some() {
322 return Err(DeviceAssignmentError::InvalidDtbo);
323 }
324 }
325 Self::validate_physical_devices(&physical_devices)?;
326 Ok(physical_devices)
327 }
328
329 /// Parses Physical devices in VM DTBO
330 fn parse_physical_devices(&self) -> Result<BTreeMap<Phandle, PhysicalDeviceInfo>> {
331 let Some(physical_node) = self.as_ref().node(cstr!("/host"))? else {
332 return Ok(BTreeMap::new());
333 };
334
335 let phys_iommus = Self::parse_physical_iommus(&physical_node)?;
336 Self::parse_physical_devices_with_iommus(&physical_node, &phys_iommus)
337 }
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900338
Jaewan Kimf8abbb52023-12-12 22:11:39 +0900339 fn node(&self, path: &DtPathTokens) -> Result<Option<FdtNode>> {
340 let mut node = self.as_ref().root();
341 for token in &path.tokens {
342 let Some(subnode) = node.subnode_with_name_bytes(token)? else {
343 return Ok(None);
344 };
345 node = subnode;
346 }
347 Ok(Some(node))
348 }
Jaewan Kimc39974e2023-12-02 01:13:30 +0900349}
350
Jaewan Kimc730ebf2024-02-22 10:34:55 +0900351fn filter_dangling_symbols(fdt: &mut Fdt) -> Result<()> {
352 if let Some(symbols) = fdt.symbols()? {
353 let mut removed = vec![];
354 for prop in symbols.properties()? {
355 let path = CStr::from_bytes_with_nul(prop.value()?)
356 .map_err(|_| DeviceAssignmentError::Internal)?;
357 if fdt.node(path)?.is_none() {
358 let name = prop.name()?;
359 removed.push(CString::from(name));
360 }
361 }
362
363 let mut symbols = fdt.symbols_mut()?.unwrap();
364 for name in removed {
365 symbols.nop_property(&name)?;
366 }
367 }
368 Ok(())
369}
370
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900371impl AsRef<Fdt> for VmDtbo {
372 fn as_ref(&self) -> &Fdt {
373 &self.0
374 }
375}
376
377impl AsMut<Fdt> for VmDtbo {
378 fn as_mut(&mut self) -> &mut Fdt {
379 &mut self.0
380 }
381}
382
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900383#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
384struct PvIommu {
385 // ID from pvIOMMU node
386 id: u32,
387}
388
389impl PvIommu {
390 fn parse(node: &FdtNode) -> Result<Self> {
Jaewan Kima9200492023-11-21 20:45:31 +0900391 let iommu_cells = node
392 .getprop_u32(cstr!("#iommu-cells"))?
393 .ok_or(DeviceAssignmentError::InvalidPvIommu)?;
Jaewan Kim19b984f2023-12-04 15:16:50 +0900394 // Ensures #iommu-cells = <1>. It means that `<iommus>` entry contains pair of
Jaewan Kima9200492023-11-21 20:45:31 +0900395 // (pvIOMMU ID, vSID)
396 if iommu_cells != 1 {
397 return Err(DeviceAssignmentError::InvalidPvIommu);
398 }
399 let id = node.getprop_u32(cstr!("id"))?.ok_or(DeviceAssignmentError::InvalidPvIommu)?;
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900400 Ok(Self { id })
401 }
402}
403
Jaewan Kima9200492023-11-21 20:45:31 +0900404#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
405struct Vsid(u32);
406
Jaewan Kim19b984f2023-12-04 15:16:50 +0900407#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
408struct Sid(u64);
409
410impl From<u32> for Sid {
411 fn from(sid: u32) -> Self {
412 Self(sid.into())
413 }
414}
415
416#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
Jaewan Kim52477ae2023-11-21 21:20:52 +0900417struct DeviceReg {
418 addr: u64,
419 size: u64,
420}
421
422impl TryFrom<Reg<u64>> for DeviceReg {
423 type Error = DeviceAssignmentError;
424
425 fn try_from(reg: Reg<u64>) -> Result<Self> {
Jaewan Kim19b984f2023-12-04 15:16:50 +0900426 Ok(Self { addr: reg.addr, size: reg.size.ok_or(DeviceAssignmentError::MalformedReg)? })
Jaewan Kim52477ae2023-11-21 21:20:52 +0900427 }
428}
429
430fn parse_node_reg(node: &FdtNode) -> Result<Vec<DeviceReg>> {
431 node.reg()?
Jaewan Kim19b984f2023-12-04 15:16:50 +0900432 .ok_or(DeviceAssignmentError::MalformedReg)?
Jaewan Kim52477ae2023-11-21 21:20:52 +0900433 .map(DeviceReg::try_from)
434 .collect::<Result<Vec<_>>>()
435}
436
437fn to_be_bytes(reg: &[DeviceReg]) -> Vec<u8> {
438 let mut reg_cells = vec![];
439 for x in reg {
440 reg_cells.extend_from_slice(&x.addr.to_be_bytes());
441 reg_cells.extend_from_slice(&x.size.to_be_bytes());
442 }
443 reg_cells
444}
445
Jaewan Kim19b984f2023-12-04 15:16:50 +0900446#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
447struct PhysIommu {
448 token: u64,
449}
450
451impl PhysIommu {
452 fn parse(node: &FdtNode) -> Result<Option<Self>> {
453 let Some(token) = node.getprop_u64(cstr!("android,pvmfw,token"))? else {
454 return Ok(None);
455 };
456 let Some(iommu_cells) = node.getprop_u32(cstr!("#iommu-cells"))? else {
457 return Err(DeviceAssignmentError::InvalidPhysIommu);
458 };
459 // Currently only supports #iommu-cells = <1>.
460 // In that case `<iommus>` entry contains pair of (pIOMMU phandle, Sid token)
461 if iommu_cells != 1 {
462 return Err(DeviceAssignmentError::UnsupportedPhysIommu);
463 }
464 Ok(Some(Self { token }))
465 }
466}
467
468#[derive(Debug)]
469struct PhysicalDeviceInfo {
470 target: Phandle,
471 reg: Vec<DeviceReg>,
472 iommus: Vec<(PhysIommu, Sid)>,
473}
474
475impl PhysicalDeviceInfo {
476 fn parse_iommus(
477 node: &FdtNode,
478 phys_iommus: &BTreeMap<Phandle, PhysIommu>,
479 ) -> Result<Vec<(PhysIommu, Sid)>> {
480 let mut iommus = vec![];
481 let Some(mut cells) = node.getprop_cells(cstr!("iommus"))? else {
482 return Ok(iommus);
483 };
484 while let Some(cell) = cells.next() {
485 // Parse pIOMMU ID
486 let phandle =
487 Phandle::try_from(cell).or(Err(DeviceAssignmentError::MalformedIommus))?;
488 let iommu = phys_iommus.get(&phandle).ok_or(DeviceAssignmentError::MalformedIommus)?;
489
490 // Parse Sid
491 let Some(cell) = cells.next() else {
492 return Err(DeviceAssignmentError::MalformedIommus);
493 };
494
495 iommus.push((*iommu, Sid::from(cell)));
496 }
497 Ok(iommus)
498 }
499
500 fn parse(node: &FdtNode, phys_iommus: &BTreeMap<Phandle, PhysIommu>) -> Result<Option<Self>> {
501 let Some(phandle) = node.getprop_u32(cstr!("android,pvmfw,target"))? else {
502 return Ok(None);
503 };
504 let target = Phandle::try_from(phandle)?;
505 let reg = parse_node_reg(node)?;
506 let iommus = Self::parse_iommus(node, phys_iommus)?;
507 Ok(Some(Self { target, reg, iommus }))
508 }
509}
510
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900511/// Assigned device information parsed from crosvm DT.
512/// Keeps everything in the owned data because underlying FDT will be reused for platform DT.
513#[derive(Debug, Eq, PartialEq)]
514struct AssignedDeviceInfo {
515 // Node path of assigned device (e.g. "/rng")
516 node_path: CString,
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900517 // <reg> property from the crosvm DT
Jaewan Kim52477ae2023-11-21 21:20:52 +0900518 reg: Vec<DeviceReg>,
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900519 // <interrupts> property from the crosvm DT
520 interrupts: Vec<u8>,
Jaewan Kima9200492023-11-21 20:45:31 +0900521 // Parsed <iommus> property from the crosvm DT. Tuple of PvIommu and vSID.
522 iommus: Vec<(PvIommu, Vsid)>,
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900523}
524
525impl AssignedDeviceInfo {
Jaewan Kim19b984f2023-12-04 15:16:50 +0900526 fn validate_reg(
527 device_reg: &[DeviceReg],
528 physical_device_reg: &[DeviceReg],
Jaewan Kim52477ae2023-11-21 21:20:52 +0900529 hypervisor: &dyn DeviceAssigningHypervisor,
Jaewan Kim19b984f2023-12-04 15:16:50 +0900530 ) -> Result<()> {
Pierre-Clément Tosi8b78bc32024-03-13 17:37:07 +0000531 let mut virt_regs = device_reg.iter();
532 let mut phys_regs = physical_device_reg.iter();
Jaewan Kim19b984f2023-12-04 15:16:50 +0900533 // PV reg and physical reg should have 1:1 match in order.
Pierre-Clément Tosi8b78bc32024-03-13 17:37:07 +0000534 for (reg, phys_reg) in virt_regs.by_ref().zip(phys_regs.by_ref()) {
Jaewan Kim19b984f2023-12-04 15:16:50 +0900535 let addr = hypervisor.get_phys_mmio_token(reg.addr, reg.size).map_err(|e| {
Pierre-Clément Tosi08d6e3f2024-03-13 18:22:16 +0000536 error!("Hypervisor error while requesting MMIO token: {e}");
Pierre-Clément Tosi8b78bc32024-03-13 17:37:07 +0000537 DeviceAssignmentError::InvalidReg(reg.addr, reg.size)
Jaewan Kim52477ae2023-11-21 21:20:52 +0900538 })?;
Jaewan Kim19b984f2023-12-04 15:16:50 +0900539 // Only check address because hypervisor guaranatees size match when success.
540 if phys_reg.addr != addr {
Pierre-Clément Tosi8b78bc32024-03-13 17:37:07 +0000541 error!("Assigned device {reg:x?} has unexpected physical address");
542 return Err(DeviceAssignmentError::InvalidPhysReg(addr, reg.size));
Jaewan Kim19b984f2023-12-04 15:16:50 +0900543 }
Jaewan Kim52477ae2023-11-21 21:20:52 +0900544 }
Pierre-Clément Tosi8b78bc32024-03-13 17:37:07 +0000545
546 if let Some(DeviceReg { addr, size }) = virt_regs.next() {
547 return Err(DeviceAssignmentError::InvalidReg(*addr, *size));
548 }
549
550 if let Some(DeviceReg { addr, size }) = phys_regs.next() {
551 return Err(DeviceAssignmentError::InvalidPhysReg(*addr, *size));
552 }
553
Jaewan Kim19b984f2023-12-04 15:16:50 +0900554 Ok(())
Jaewan Kim52477ae2023-11-21 21:20:52 +0900555 }
556
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900557 fn parse_interrupts(node: &FdtNode) -> Result<Vec<u8>> {
558 // Validation: Validate if interrupts cell numbers are multiple of #interrupt-cells.
559 // We can't know how many interrupts would exist.
560 let interrupts_cells = node
Pierre-Clément Tosid701a0b2023-11-07 15:38:59 +0000561 .getprop_cells(cstr!("interrupts"))?
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900562 .ok_or(DeviceAssignmentError::InvalidInterrupts)?
563 .count();
564 if interrupts_cells % CELLS_PER_INTERRUPT != 0 {
565 return Err(DeviceAssignmentError::InvalidInterrupts);
566 }
567
568 // Once validated, keep the raw bytes so patch can be done with setprop()
Pierre-Clément Tosid701a0b2023-11-07 15:38:59 +0000569 Ok(node.getprop(cstr!("interrupts")).unwrap().unwrap().into())
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900570 }
571
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900572 // TODO(b/277993056): Also validate /__local_fixups__ to ensure that <iommus> has phandle.
Jaewan Kima9200492023-11-21 20:45:31 +0900573 fn parse_iommus(
574 node: &FdtNode,
575 pviommus: &BTreeMap<Phandle, PvIommu>,
576 ) -> Result<Vec<(PvIommu, Vsid)>> {
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900577 let mut iommus = vec![];
Jaewan Kima9200492023-11-21 20:45:31 +0900578 let Some(mut cells) = node.getprop_cells(cstr!("iommus"))? else {
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900579 return Ok(iommus);
580 };
Jaewan Kima9200492023-11-21 20:45:31 +0900581 while let Some(cell) = cells.next() {
582 // Parse pvIOMMU ID
Jaewan Kim19b984f2023-12-04 15:16:50 +0900583 let phandle =
584 Phandle::try_from(cell).or(Err(DeviceAssignmentError::MalformedIommus))?;
585 let pviommu = pviommus.get(&phandle).ok_or(DeviceAssignmentError::MalformedIommus)?;
Jaewan Kima9200492023-11-21 20:45:31 +0900586
587 // Parse vSID
588 let Some(cell) = cells.next() else {
Jaewan Kim19b984f2023-12-04 15:16:50 +0900589 return Err(DeviceAssignmentError::MalformedIommus);
Jaewan Kima9200492023-11-21 20:45:31 +0900590 };
591 let vsid = Vsid(cell);
592
593 iommus.push((*pviommu, vsid));
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900594 }
595 Ok(iommus)
596 }
597
Jaewan Kim19b984f2023-12-04 15:16:50 +0900598 fn validate_iommus(
599 iommus: &[(PvIommu, Vsid)],
600 physical_device_iommu: &[(PhysIommu, Sid)],
601 hypervisor: &dyn DeviceAssigningHypervisor,
602 ) -> Result<()> {
603 if iommus.len() != physical_device_iommu.len() {
604 return Err(DeviceAssignmentError::InvalidIommus);
605 }
606 // pvIOMMU can be reordered, and hypervisor may not guarantee 1:1 mapping.
607 // So we need to mark what's matched or not.
608 let mut physical_device_iommu = physical_device_iommu.to_vec();
609 for (pviommu, vsid) in iommus {
Pierre-Clément Tosi08d6e3f2024-03-13 18:22:16 +0000610 let (id, sid) =
611 hypervisor.get_phys_iommu_token(pviommu.id.into(), vsid.0.into()).map_err(|e| {
612 error!("Hypervisor error while requesting IOMMU token ({pviommu:?}, {vsid:?}): {e}");
613 DeviceAssignmentError::InvalidIommus
614 })?;
Jaewan Kim19b984f2023-12-04 15:16:50 +0900615
616 let pos = physical_device_iommu
617 .iter()
618 .position(|(phys_iommu, phys_sid)| (phys_iommu.token, phys_sid.0) == (id, sid));
619 match pos {
620 Some(pos) => physical_device_iommu.remove(pos),
621 None => {
622 error!("Failed to validate device <iommus>. No matching phys iommu or duplicated mapping for pviommu={pviommu:?}, vsid={vsid:?}");
623 return Err(DeviceAssignmentError::InvalidIommus);
624 }
625 };
626 }
627 Ok(())
628 }
629
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900630 fn parse(
631 fdt: &Fdt,
632 vm_dtbo: &VmDtbo,
Jaewan Kimf8abbb52023-12-12 22:11:39 +0900633 dtbo_node_path: &DtPathTokens,
Jaewan Kim19b984f2023-12-04 15:16:50 +0900634 physical_devices: &BTreeMap<Phandle, PhysicalDeviceInfo>,
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900635 pviommus: &BTreeMap<Phandle, PvIommu>,
Jaewan Kim52477ae2023-11-21 21:20:52 +0900636 hypervisor: &dyn DeviceAssigningHypervisor,
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900637 ) -> Result<Option<Self>> {
Jaewan Kim19b984f2023-12-04 15:16:50 +0900638 let dtbo_node =
Jaewan Kimf8abbb52023-12-12 22:11:39 +0900639 vm_dtbo.node(dtbo_node_path)?.ok_or(DeviceAssignmentError::InvalidSymbols)?;
Jaewan Kim19b984f2023-12-04 15:16:50 +0900640 let node_path = vm_dtbo.locate_overlay_target_path(dtbo_node_path, &dtbo_node)?;
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900641
642 let Some(node) = fdt.node(&node_path)? else { return Ok(None) };
643
Jaewan Kim80ef9fa2024-02-25 16:08:14 +0000644 // Currently can only assign devices backed by physical devices.
Jaewan Kim19b984f2023-12-04 15:16:50 +0900645 let phandle = dtbo_node.get_phandle()?.ok_or(DeviceAssignmentError::InvalidDtbo)?;
Jaewan Kim80ef9fa2024-02-25 16:08:14 +0000646 let Some(physical_device) = physical_devices.get(&phandle) else {
647 // If labeled DT node isn't backed by physical device node, then just return None.
648 // It's not an error because such node can be a dependency of assignable device nodes.
649 return Ok(None);
650 };
Jaewan Kim19b984f2023-12-04 15:16:50 +0900651
652 let reg = parse_node_reg(&node)?;
653 Self::validate_reg(&reg, &physical_device.reg, hypervisor)?;
654
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900655 let interrupts = Self::parse_interrupts(&node)?;
Jaewan Kim19b984f2023-12-04 15:16:50 +0900656
657 let iommus = Self::parse_iommus(&node, pviommus)?;
658 Self::validate_iommus(&iommus, &physical_device.iommus, hypervisor)?;
659
Jaewan Kimf8abbb52023-12-12 22:11:39 +0900660 Ok(Some(Self { node_path, reg, interrupts, iommus }))
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900661 }
662
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900663 fn patch(&self, fdt: &mut Fdt, pviommu_phandles: &BTreeMap<PvIommu, Phandle>) -> Result<()> {
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900664 let mut dst = fdt.node_mut(&self.node_path)?.unwrap();
Jaewan Kim52477ae2023-11-21 21:20:52 +0900665 dst.setprop(cstr!("reg"), &to_be_bytes(&self.reg))?;
Pierre-Clément Tosid701a0b2023-11-07 15:38:59 +0000666 dst.setprop(cstr!("interrupts"), &self.interrupts)?;
Jaewan Kima9200492023-11-21 20:45:31 +0900667 let mut iommus = Vec::with_capacity(8 * self.iommus.len());
668 for (pviommu, vsid) in &self.iommus {
669 let phandle = pviommu_phandles.get(pviommu).unwrap();
670 iommus.extend_from_slice(&u32::from(*phandle).to_be_bytes());
671 iommus.extend_from_slice(&vsid.0.to_be_bytes());
672 }
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900673 dst.setprop(cstr!("iommus"), &iommus)?;
674
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900675 Ok(())
676 }
677}
678
679#[derive(Debug, Default, Eq, PartialEq)]
680pub struct DeviceAssignmentInfo {
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900681 pviommus: BTreeSet<PvIommu>,
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900682 assigned_devices: Vec<AssignedDeviceInfo>,
683 filtered_dtbo_paths: Vec<CString>,
684}
685
686impl DeviceAssignmentInfo {
Chris Wailes9d09f572024-01-16 13:31:02 -0800687 const PVIOMMU_COMPATIBLE: &'static CStr = cstr!("pkvm,pviommu");
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900688
689 /// Parses pvIOMMUs in fdt
690 // Note: This will validate pvIOMMU ids' uniqueness, even when unassigned.
691 fn parse_pviommus(fdt: &Fdt) -> Result<BTreeMap<Phandle, PvIommu>> {
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900692 let mut pviommus = BTreeMap::new();
693 for compatible in fdt.compatible_nodes(Self::PVIOMMU_COMPATIBLE)? {
694 let Some(phandle) = compatible.get_phandle()? else {
695 continue; // Skips unreachable pvIOMMU node
696 };
697 let pviommu = PvIommu::parse(&compatible)?;
698 if pviommus.insert(phandle, pviommu).is_some() {
699 return Err(FdtError::BadPhandle.into());
700 }
701 }
702 Ok(pviommus)
703 }
704
Jaewan Kim19b984f2023-12-04 15:16:50 +0900705 fn validate_pviommu_topology(assigned_devices: &[AssignedDeviceInfo]) -> Result<()> {
706 let mut all_iommus = BTreeSet::new();
707 for assigned_device in assigned_devices {
708 for iommu in &assigned_device.iommus {
709 if !all_iommus.insert(iommu) {
710 error!("Unsupported pvIOMMU duplication found, <iommus> = {iommu:?}");
711 return Err(DeviceAssignmentError::UnsupportedPvIommusDuplication);
712 }
713 }
714 }
715 Ok(())
716 }
717
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900718 /// Parses fdt and vm_dtbo, and creates new DeviceAssignmentInfo
719 // TODO(b/277993056): Parse __local_fixups__
720 // TODO(b/277993056): Parse __fixups__
Jaewan Kim52477ae2023-11-21 21:20:52 +0900721 pub fn parse(
722 fdt: &Fdt,
723 vm_dtbo: &VmDtbo,
724 hypervisor: &dyn DeviceAssigningHypervisor,
725 ) -> Result<Option<Self>> {
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900726 let Some(symbols_node) = vm_dtbo.as_ref().symbols()? else {
727 // /__symbols__ should contain all assignable devices.
728 // If empty, then nothing can be assigned.
729 return Ok(None);
730 };
731
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900732 let pviommus = Self::parse_pviommus(fdt)?;
733 let unique_pviommus: BTreeSet<_> = pviommus.values().cloned().collect();
734 if pviommus.len() != unique_pviommus.len() {
735 return Err(DeviceAssignmentError::DuplicatedPvIommuIds);
736 }
737
Jaewan Kim19b984f2023-12-04 15:16:50 +0900738 let physical_devices = vm_dtbo.parse_physical_devices()?;
739
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900740 let mut assigned_devices = vec![];
741 let mut filtered_dtbo_paths = vec![];
742 for symbol_prop in symbols_node.properties()? {
743 let symbol_prop_value = symbol_prop.value()?;
744 let dtbo_node_path = CStr::from_bytes_with_nul(symbol_prop_value)
745 .or(Err(DeviceAssignmentError::InvalidSymbols))?;
Jaewan Kimf8abbb52023-12-12 22:11:39 +0900746 let dtbo_node_path = DtPathTokens::new(dtbo_node_path)?;
747 if !dtbo_node_path.is_overlayable_node() {
Jaewan Kimc39974e2023-12-02 01:13:30 +0900748 continue;
749 }
Jaewan Kim19b984f2023-12-04 15:16:50 +0900750 let assigned_device = AssignedDeviceInfo::parse(
751 fdt,
752 vm_dtbo,
Jaewan Kimf8abbb52023-12-12 22:11:39 +0900753 &dtbo_node_path,
Jaewan Kim19b984f2023-12-04 15:16:50 +0900754 &physical_devices,
755 &pviommus,
756 hypervisor,
757 )?;
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900758 if let Some(assigned_device) = assigned_device {
759 assigned_devices.push(assigned_device);
760 } else {
Jaewan Kimf8abbb52023-12-12 22:11:39 +0900761 filtered_dtbo_paths.push(dtbo_node_path.to_cstring());
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900762 }
763 }
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900764 if assigned_devices.is_empty() {
765 return Ok(None);
766 }
Jaewan Kimc39974e2023-12-02 01:13:30 +0900767
Jaewan Kim19b984f2023-12-04 15:16:50 +0900768 Self::validate_pviommu_topology(&assigned_devices)?;
769
Jaewan Kimc39974e2023-12-02 01:13:30 +0900770 // Clean up any nodes that wouldn't be overlaid but may contain reference to filtered nodes.
771 // Otherwise, `fdt_apply_overlay()` would fail because of missing phandle reference.
Jaewan Kimc39974e2023-12-02 01:13:30 +0900772 // TODO(b/277993056): Also filter other unused nodes/props in __local_fixups__
773 filtered_dtbo_paths.push(CString::new("/__local_fixups__/host").unwrap());
774
775 // Note: Any node without __overlay__ will be ignored by fdt_apply_overlay,
776 // so doesn't need to be filtered.
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900777
778 Ok(Some(Self { pviommus: unique_pviommus, assigned_devices, filtered_dtbo_paths }))
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900779 }
780
781 /// Filters VM DTBO to only contain necessary information for booting pVM
782 /// In detail, this will remove followings by setting nop node / nop property.
783 /// - Removes unassigned devices
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900784 // TODO(b/277993056): remove unused dependencies in VM DTBO.
785 // TODO(b/277993056): remove supernodes' properties.
786 // TODO(b/277993056): remove unused alises.
787 pub fn filter(&self, vm_dtbo: &mut VmDtbo) -> Result<()> {
788 let vm_dtbo = vm_dtbo.as_mut();
789
790 // Filters unused node in assigned devices
791 for filtered_dtbo_path in &self.filtered_dtbo_paths {
792 let node = vm_dtbo.node_mut(filtered_dtbo_path).unwrap().unwrap();
793 node.nop()?;
794 }
795
Jaewan Kim371f6c82024-02-24 01:33:37 +0900796 filter_dangling_symbols(vm_dtbo)
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900797 }
798
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900799 fn patch_pviommus(&self, fdt: &mut Fdt) -> Result<BTreeMap<PvIommu, Phandle>> {
Pierre-Clément Tosi244efea2024-02-16 14:48:14 +0000800 let mut compatible = fdt.root_mut().next_compatible(Self::PVIOMMU_COMPATIBLE)?;
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900801 let mut pviommu_phandles = BTreeMap::new();
802
803 for pviommu in &self.pviommus {
804 let mut node = compatible.ok_or(DeviceAssignmentError::TooManyPvIommu)?;
805 let phandle = node.as_node().get_phandle()?.ok_or(DeviceAssignmentError::Internal)?;
806 node.setprop_inplace(cstr!("id"), &pviommu.id.to_be_bytes())?;
807 if pviommu_phandles.insert(*pviommu, phandle).is_some() {
808 return Err(DeviceAssignmentError::Internal);
809 }
810 compatible = node.next_compatible(Self::PVIOMMU_COMPATIBLE)?;
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900811 }
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900812
813 // Filters pre-populated but unassigned pvIOMMUs.
814 while let Some(filtered_pviommu) = compatible {
815 compatible = filtered_pviommu.delete_and_next_compatible(Self::PVIOMMU_COMPATIBLE)?;
816 }
817
818 Ok(pviommu_phandles)
819 }
820
821 pub fn patch(&self, fdt: &mut Fdt) -> Result<()> {
822 let pviommu_phandles = self.patch_pviommus(fdt)?;
823
824 // Patches assigned devices
825 for device in &self.assigned_devices {
826 device.patch(fdt, &pviommu_phandles)?;
827 }
828
Jaewan Kimc730ebf2024-02-22 10:34:55 +0900829 // Removes any dangling references in __symbols__ (e.g. removed pvIOMMUs)
830 filter_dangling_symbols(fdt)
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900831 }
832}
833
Jaewan Kim50246682024-03-11 23:18:54 +0900834/// Cleans device trees not to contain any pre-populated nodes/props for device assignment.
835pub fn clean(fdt: &mut Fdt) -> Result<()> {
836 let mut compatible = fdt.root_mut().next_compatible(cstr!("pkvm,pviommu"))?;
837 // Filters pre-populated
838 while let Some(filtered_pviommu) = compatible {
839 compatible = filtered_pviommu.delete_and_next_compatible(cstr!("pkvm,pviommu"))?;
840 }
841
842 // Removes any dangling references in __symbols__ (e.g. removed pvIOMMUs)
843 filter_dangling_symbols(fdt)
844}
845
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900846#[cfg(test)]
847mod tests {
848 use super::*;
Jaewan Kim52477ae2023-11-21 21:20:52 +0900849 use alloc::collections::{BTreeMap, BTreeSet};
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900850 use std::fs;
851
852 const VM_DTBO_FILE_PATH: &str = "test_pvmfw_devices_vm_dtbo.dtbo";
853 const VM_DTBO_WITHOUT_SYMBOLS_FILE_PATH: &str =
854 "test_pvmfw_devices_vm_dtbo_without_symbols.dtbo";
Jaewan Kim19b984f2023-12-04 15:16:50 +0900855 const VM_DTBO_WITH_DUPLICATED_IOMMUS_FILE_PATH: &str =
856 "test_pvmfw_devices_vm_dtbo_with_duplicated_iommus.dtbo";
Jaewan Kima67e36a2023-11-29 16:50:23 +0900857 const FDT_WITHOUT_IOMMUS_FILE_PATH: &str = "test_pvmfw_devices_without_iommus.dtb";
Jaewan Kim52477ae2023-11-21 21:20:52 +0900858 const FDT_WITHOUT_DEVICE_FILE_PATH: &str = "test_pvmfw_devices_without_device.dtb";
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900859 const FDT_FILE_PATH: &str = "test_pvmfw_devices_with_rng.dtb";
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900860 const FDT_WITH_MULTIPLE_DEVICES_IOMMUS_FILE_PATH: &str =
861 "test_pvmfw_devices_with_multiple_devices_iommus.dtb";
862 const FDT_WITH_IOMMU_SHARING: &str = "test_pvmfw_devices_with_iommu_sharing.dtb";
863 const FDT_WITH_IOMMU_ID_CONFLICT: &str = "test_pvmfw_devices_with_iommu_id_conflict.dtb";
Jaewan Kim19b984f2023-12-04 15:16:50 +0900864 const FDT_WITH_DUPLICATED_PVIOMMUS_FILE_PATH: &str =
865 "test_pvmfw_devices_with_duplicated_pviommus.dtb";
866 const FDT_WITH_MULTIPLE_REG_IOMMU_FILE_PATH: &str =
867 "test_pvmfw_devices_with_multiple_reg_iommus.dtb";
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900868
Jaewan Kim52477ae2023-11-21 21:20:52 +0900869 #[derive(Debug, Default)]
870 struct MockHypervisor {
871 mmio_tokens: BTreeMap<(u64, u64), u64>,
872 iommu_tokens: BTreeMap<(u64, u64), (u64, u64)>,
873 }
874
875 impl DeviceAssigningHypervisor for MockHypervisor {
876 fn get_phys_mmio_token(&self, base_ipa: u64, size: u64) -> hyp::Result<u64> {
877 Ok(*self.mmio_tokens.get(&(base_ipa, size)).ok_or(hyp::Error::KvmError(
878 hyp::KvmError::InvalidParameter,
879 0xc6000012, /* VENDOR_HYP_KVM_DEV_REQ_MMIO_FUNC_ID */
880 ))?)
881 }
882
883 fn get_phys_iommu_token(&self, pviommu_id: u64, vsid: u64) -> hyp::Result<(u64, u64)> {
884 Ok(*self.iommu_tokens.get(&(pviommu_id, vsid)).ok_or(hyp::Error::KvmError(
885 hyp::KvmError::InvalidParameter,
886 0xc6000013, /* VENDOR_HYP_KVM_DEV_REQ_DMA_FUNC_ID */
887 ))?)
888 }
889 }
890
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900891 #[derive(Debug, Eq, PartialEq)]
892 struct AssignedDeviceNode {
893 path: CString,
894 reg: Vec<u8>,
895 interrupts: Vec<u8>,
Jaewan Kima67e36a2023-11-29 16:50:23 +0900896 iommus: Vec<u32>, // pvIOMMU id and vSID
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900897 }
898
899 impl AssignedDeviceNode {
900 fn parse(fdt: &Fdt, path: &CStr) -> Result<Self> {
901 let Some(node) = fdt.node(path)? else {
902 return Err(FdtError::NotFound.into());
903 };
904
Jaewan Kim19b984f2023-12-04 15:16:50 +0900905 let reg = node.getprop(cstr!("reg"))?.ok_or(DeviceAssignmentError::MalformedReg)?;
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900906 let interrupts = node
907 .getprop(cstr!("interrupts"))?
908 .ok_or(DeviceAssignmentError::InvalidInterrupts)?;
909 let mut iommus = vec![];
Jaewan Kima9200492023-11-21 20:45:31 +0900910 if let Some(mut cells) = node.getprop_cells(cstr!("iommus"))? {
911 while let Some(pviommu_id) = cells.next() {
912 // pvIOMMU id
913 let phandle = Phandle::try_from(pviommu_id)?;
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900914 let pviommu = fdt
915 .node_with_phandle(phandle)?
Jaewan Kim19b984f2023-12-04 15:16:50 +0900916 .ok_or(DeviceAssignmentError::MalformedIommus)?;
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900917 let compatible = pviommu.getprop_str(cstr!("compatible"));
918 if compatible != Ok(Some(cstr!("pkvm,pviommu"))) {
Jaewan Kim19b984f2023-12-04 15:16:50 +0900919 return Err(DeviceAssignmentError::MalformedIommus);
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900920 }
921 let id = pviommu
922 .getprop_u32(cstr!("id"))?
Jaewan Kim19b984f2023-12-04 15:16:50 +0900923 .ok_or(DeviceAssignmentError::MalformedIommus)?;
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900924 iommus.push(id);
Jaewan Kima9200492023-11-21 20:45:31 +0900925
926 // vSID
927 let Some(vsid) = cells.next() else {
Jaewan Kim19b984f2023-12-04 15:16:50 +0900928 return Err(DeviceAssignmentError::MalformedIommus);
Jaewan Kima9200492023-11-21 20:45:31 +0900929 };
930 iommus.push(vsid);
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900931 }
932 }
933 Ok(Self { path: path.into(), reg: reg.into(), interrupts: interrupts.into(), iommus })
934 }
935 }
936
937 fn collect_pviommus(fdt: &Fdt) -> Result<Vec<u32>> {
938 let mut pviommus = BTreeSet::new();
939 for pviommu in fdt.compatible_nodes(cstr!("pkvm,pviommu"))? {
940 if let Ok(Some(id)) = pviommu.getprop_u32(cstr!("id")) {
941 pviommus.insert(id);
942 }
943 }
944 Ok(pviommus.iter().cloned().collect())
945 }
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900946
947 fn into_fdt_prop(native_bytes: Vec<u32>) -> Vec<u8> {
948 let mut v = Vec::with_capacity(native_bytes.len() * 4);
949 for byte in native_bytes {
950 v.extend_from_slice(&byte.to_be_bytes());
951 }
952 v
953 }
954
Jaewan Kim52477ae2023-11-21 21:20:52 +0900955 impl From<[u64; 2]> for DeviceReg {
956 fn from(fdt_cells: [u64; 2]) -> Self {
957 DeviceReg { addr: fdt_cells[0], size: fdt_cells[1] }
958 }
959 }
960
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900961 #[test]
962 fn device_info_new_without_symbols() {
963 let mut fdt_data = fs::read(FDT_FILE_PATH).unwrap();
964 let mut vm_dtbo_data = fs::read(VM_DTBO_WITHOUT_SYMBOLS_FILE_PATH).unwrap();
965 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
966 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
967
Jaewan Kim52477ae2023-11-21 21:20:52 +0900968 let hypervisor: MockHypervisor = Default::default();
969 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor).unwrap();
970 assert_eq!(device_info, None);
971 }
972
973 #[test]
974 fn device_info_new_without_device() {
975 let mut fdt_data = fs::read(FDT_WITHOUT_DEVICE_FILE_PATH).unwrap();
976 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
977 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
978 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
979
980 let hypervisor: MockHypervisor = Default::default();
981 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor).unwrap();
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900982 assert_eq!(device_info, None);
983 }
984
985 #[test]
Jaewan Kima67e36a2023-11-29 16:50:23 +0900986 fn device_info_assigned_info_without_iommus() {
987 let mut fdt_data = fs::read(FDT_WITHOUT_IOMMUS_FILE_PATH).unwrap();
988 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
989 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
990 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
991
Jaewan Kim52477ae2023-11-21 21:20:52 +0900992 let hypervisor = MockHypervisor {
993 mmio_tokens: [((0x9, 0xFF), 0x300)].into(),
994 iommu_tokens: BTreeMap::new(),
995 };
996 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor).unwrap().unwrap();
Jaewan Kima67e36a2023-11-29 16:50:23 +0900997
998 let expected = [AssignedDeviceInfo {
Jaewan Kimc39974e2023-12-02 01:13:30 +0900999 node_path: CString::new("/bus0/backlight").unwrap(),
Jaewan Kim52477ae2023-11-21 21:20:52 +09001000 reg: vec![[0x9, 0xFF].into()],
Jaewan Kima67e36a2023-11-29 16:50:23 +09001001 interrupts: into_fdt_prop(vec![0x0, 0xF, 0x4]),
1002 iommus: vec![],
1003 }];
1004
1005 assert_eq!(device_info.assigned_devices, expected);
1006 }
1007
1008 #[test]
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001009 fn device_info_assigned_info() {
1010 let mut fdt_data = fs::read(FDT_FILE_PATH).unwrap();
1011 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1012 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1013 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1014
Jaewan Kim52477ae2023-11-21 21:20:52 +09001015 let hypervisor = MockHypervisor {
1016 mmio_tokens: [((0x9, 0xFF), 0x12F00000)].into(),
1017 iommu_tokens: [((0x4, 0xFF0), (0x12E40000, 0x3))].into(),
1018 };
1019 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor).unwrap().unwrap();
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001020
1021 let expected = [AssignedDeviceInfo {
1022 node_path: CString::new("/rng").unwrap(),
Jaewan Kim52477ae2023-11-21 21:20:52 +09001023 reg: vec![[0x9, 0xFF].into()],
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001024 interrupts: into_fdt_prop(vec![0x0, 0xF, 0x4]),
Jaewan Kima67e36a2023-11-29 16:50:23 +09001025 iommus: vec![(PvIommu { id: 0x4 }, Vsid(0xFF0))],
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001026 }];
1027
1028 assert_eq!(device_info.assigned_devices, expected);
1029 }
1030
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001031 #[test]
1032 fn device_info_filter() {
1033 let mut fdt_data = fs::read(FDT_FILE_PATH).unwrap();
1034 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1035 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1036 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1037
Jaewan Kim52477ae2023-11-21 21:20:52 +09001038 let hypervisor = MockHypervisor {
1039 mmio_tokens: [((0x9, 0xFF), 0x12F00000)].into(),
1040 iommu_tokens: [((0x4, 0xFF0), (0x12E40000, 0x3))].into(),
1041 };
1042 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor).unwrap().unwrap();
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001043 device_info.filter(vm_dtbo).unwrap();
1044
1045 let vm_dtbo = vm_dtbo.as_mut();
1046
Jaewan Kim371f6c82024-02-24 01:33:37 +09001047 let symbols = vm_dtbo.symbols().unwrap().unwrap();
1048
Jaewan Kima232ed02024-02-25 16:08:14 +00001049 let rng = vm_dtbo.node(cstr!("/fragment@0/__overlay__/rng")).unwrap();
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001050 assert_ne!(rng, None);
Jaewan Kim371f6c82024-02-24 01:33:37 +09001051 let rng_symbol = symbols.getprop_str(cstr!("rng")).unwrap();
Jaewan Kima232ed02024-02-25 16:08:14 +00001052 assert_eq!(Some(cstr!("/fragment@0/__overlay__/rng")), rng_symbol);
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001053
Jaewan Kima232ed02024-02-25 16:08:14 +00001054 let light = vm_dtbo.node(cstr!("/fragment@0/__overlay__/light")).unwrap();
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001055 assert_eq!(light, None);
Jaewan Kim371f6c82024-02-24 01:33:37 +09001056 let light_symbol = symbols.getprop_str(cstr!("light")).unwrap();
1057 assert_eq!(None, light_symbol);
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001058
Jaewan Kima232ed02024-02-25 16:08:14 +00001059 let led = vm_dtbo.node(cstr!("/fragment@0/__overlay__/led")).unwrap();
Jaewan Kima67e36a2023-11-29 16:50:23 +09001060 assert_eq!(led, None);
Jaewan Kim371f6c82024-02-24 01:33:37 +09001061 let led_symbol = symbols.getprop_str(cstr!("led")).unwrap();
1062 assert_eq!(None, led_symbol);
Jaewan Kima67e36a2023-11-29 16:50:23 +09001063
Jaewan Kima232ed02024-02-25 16:08:14 +00001064 let backlight = vm_dtbo.node(cstr!("/fragment@0/__overlay__/bus0/backlight")).unwrap();
Jaewan Kima67e36a2023-11-29 16:50:23 +09001065 assert_eq!(backlight, None);
Jaewan Kim371f6c82024-02-24 01:33:37 +09001066 let backlight_symbol = symbols.getprop_str(cstr!("backlight")).unwrap();
1067 assert_eq!(None, backlight_symbol);
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001068 }
1069
1070 #[test]
1071 fn device_info_patch() {
Jaewan Kima67e36a2023-11-29 16:50:23 +09001072 let mut fdt_data = fs::read(FDT_WITHOUT_IOMMUS_FILE_PATH).unwrap();
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001073 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1074 let mut data = vec![0_u8; fdt_data.len() + vm_dtbo_data.len()];
1075 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1076 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1077 let platform_dt = Fdt::create_empty_tree(data.as_mut_slice()).unwrap();
1078
Jaewan Kim52477ae2023-11-21 21:20:52 +09001079 let hypervisor = MockHypervisor {
1080 mmio_tokens: [((0x9, 0xFF), 0x300)].into(),
1081 iommu_tokens: BTreeMap::new(),
1082 };
1083 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor).unwrap().unwrap();
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001084 device_info.filter(vm_dtbo).unwrap();
1085
1086 // SAFETY: Damaged VM DTBO wouldn't be used after this unsafe block.
1087 unsafe {
1088 platform_dt.apply_overlay(vm_dtbo.as_mut()).unwrap();
1089 }
Jaewan Kim0bd637d2023-11-10 13:09:41 +09001090 device_info.patch(platform_dt).unwrap();
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001091
Jaewan Kimc39974e2023-12-02 01:13:30 +09001092 let rng_node = platform_dt.node(cstr!("/bus0/backlight")).unwrap().unwrap();
1093 let phandle = rng_node.getprop_u32(cstr!("phandle")).unwrap();
1094 assert_ne!(None, phandle);
1095
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001096 // Note: Intentionally not using AssignedDeviceNode for matching all props.
Jaewan Kim0bd637d2023-11-10 13:09:41 +09001097 type FdtResult<T> = libfdt::Result<T>;
1098 let expected: Vec<(FdtResult<&CStr>, FdtResult<Vec<u8>>)> = vec![
Jaewan Kima67e36a2023-11-29 16:50:23 +09001099 (Ok(cstr!("android,backlight,ignore-gctrl-reset")), Ok(Vec::new())),
1100 (Ok(cstr!("compatible")), Ok(Vec::from(*b"android,backlight\0"))),
Jaewan Kim0bd637d2023-11-10 13:09:41 +09001101 (Ok(cstr!("interrupts")), Ok(into_fdt_prop(vec![0x0, 0xF, 0x4]))),
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001102 (Ok(cstr!("iommus")), Ok(Vec::new())),
Jaewan Kimc39974e2023-12-02 01:13:30 +09001103 (Ok(cstr!("phandle")), Ok(into_fdt_prop(vec![phandle.unwrap()]))),
Jaewan Kim0bd637d2023-11-10 13:09:41 +09001104 (Ok(cstr!("reg")), Ok(into_fdt_prop(vec![0x0, 0x9, 0x0, 0xFF]))),
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001105 ];
1106
Jaewan Kim0bd637d2023-11-10 13:09:41 +09001107 let mut properties: Vec<_> = rng_node
1108 .properties()
1109 .unwrap()
1110 .map(|prop| (prop.name(), prop.value().map(|x| x.into())))
1111 .collect();
1112 properties.sort_by(|a, b| {
1113 let lhs = a.0.unwrap_or_default();
1114 let rhs = b.0.unwrap_or_default();
1115 lhs.partial_cmp(rhs).unwrap()
1116 });
1117
1118 assert_eq!(properties, expected);
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001119 }
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001120
1121 #[test]
Jaewan Kimc730ebf2024-02-22 10:34:55 +09001122 fn device_info_patch_no_pviommus() {
1123 let mut fdt_data = fs::read(FDT_WITHOUT_IOMMUS_FILE_PATH).unwrap();
1124 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1125 let mut data = vec![0_u8; fdt_data.len() + vm_dtbo_data.len()];
1126 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1127 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1128 let platform_dt = Fdt::create_empty_tree(data.as_mut_slice()).unwrap();
1129
1130 let hypervisor = MockHypervisor {
1131 mmio_tokens: [((0x9, 0xFF), 0x300)].into(),
1132 iommu_tokens: BTreeMap::new(),
1133 };
1134 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor).unwrap().unwrap();
1135 device_info.filter(vm_dtbo).unwrap();
1136
1137 // SAFETY: Damaged VM DTBO wouldn't be used after this unsafe block.
1138 unsafe {
1139 platform_dt.apply_overlay(vm_dtbo.as_mut()).unwrap();
1140 }
1141 device_info.patch(platform_dt).unwrap();
1142
1143 let compatible = platform_dt.root().next_compatible(cstr!("pkvm,pviommu")).unwrap();
1144 assert_eq!(None, compatible);
1145
1146 if let Some(symbols) = platform_dt.symbols().unwrap() {
1147 for prop in symbols.properties().unwrap() {
1148 let path = CStr::from_bytes_with_nul(prop.value().unwrap()).unwrap();
1149 assert_ne!(None, platform_dt.node(path).unwrap());
1150 }
1151 }
1152 }
1153
1154 #[test]
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001155 fn device_info_overlay_iommu() {
Jaewan Kima67e36a2023-11-29 16:50:23 +09001156 let mut fdt_data = fs::read(FDT_FILE_PATH).unwrap();
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001157 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1158 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1159 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1160 let mut platform_dt_data = pvmfw_fdt_template::RAW.to_vec();
1161 platform_dt_data.resize(pvmfw_fdt_template::RAW.len() * 2, 0);
1162 let platform_dt = Fdt::from_mut_slice(&mut platform_dt_data).unwrap();
1163 platform_dt.unpack().unwrap();
1164
Jaewan Kim52477ae2023-11-21 21:20:52 +09001165 let hypervisor = MockHypervisor {
1166 mmio_tokens: [((0x9, 0xFF), 0x12F00000)].into(),
1167 iommu_tokens: [((0x4, 0xFF0), (0x12E40000, 0x3))].into(),
1168 };
1169 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor).unwrap().unwrap();
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001170 device_info.filter(vm_dtbo).unwrap();
1171
1172 // SAFETY: Damaged VM DTBO wouldn't be used after this unsafe block.
1173 unsafe {
1174 platform_dt.apply_overlay(vm_dtbo.as_mut()).unwrap();
1175 }
1176 device_info.patch(platform_dt).unwrap();
1177
1178 let expected = AssignedDeviceNode {
1179 path: CString::new("/rng").unwrap(),
1180 reg: into_fdt_prop(vec![0x0, 0x9, 0x0, 0xFF]),
1181 interrupts: into_fdt_prop(vec![0x0, 0xF, 0x4]),
Jaewan Kima9200492023-11-21 20:45:31 +09001182 iommus: vec![0x4, 0xFF0],
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001183 };
1184
1185 let node = AssignedDeviceNode::parse(platform_dt, &expected.path);
1186 assert_eq!(node, Ok(expected));
1187
1188 let pviommus = collect_pviommus(platform_dt);
1189 assert_eq!(pviommus, Ok(vec![0x4]));
1190 }
1191
1192 #[test]
1193 fn device_info_multiple_devices_iommus() {
1194 let mut fdt_data = fs::read(FDT_WITH_MULTIPLE_DEVICES_IOMMUS_FILE_PATH).unwrap();
1195 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1196 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1197 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1198 let mut platform_dt_data = pvmfw_fdt_template::RAW.to_vec();
1199 platform_dt_data.resize(pvmfw_fdt_template::RAW.len() * 2, 0);
1200 let platform_dt = Fdt::from_mut_slice(&mut platform_dt_data).unwrap();
1201 platform_dt.unpack().unwrap();
1202
Jaewan Kim52477ae2023-11-21 21:20:52 +09001203 let hypervisor = MockHypervisor {
1204 mmio_tokens: [
1205 ((0x9, 0xFF), 0x12F00000),
Jaewan Kim19b984f2023-12-04 15:16:50 +09001206 ((0x10000, 0x1000), 0xF00000),
1207 ((0x20000, 0x1000), 0xF10000),
Jaewan Kim52477ae2023-11-21 21:20:52 +09001208 ]
1209 .into(),
1210 iommu_tokens: [
1211 ((0x4, 0xFF0), (0x12E40000, 3)),
1212 ((0x40, 0xFFA), (0x40000, 0x4)),
1213 ((0x50, 0xFFB), (0x50000, 0x5)),
1214 ]
1215 .into(),
1216 };
1217 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor).unwrap().unwrap();
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001218 device_info.filter(vm_dtbo).unwrap();
1219
1220 // SAFETY: Damaged VM DTBO wouldn't be used after this unsafe block.
1221 unsafe {
1222 platform_dt.apply_overlay(vm_dtbo.as_mut()).unwrap();
1223 }
1224 device_info.patch(platform_dt).unwrap();
1225
1226 let expected_devices = [
1227 AssignedDeviceNode {
1228 path: CString::new("/rng").unwrap(),
1229 reg: into_fdt_prop(vec![0x0, 0x9, 0x0, 0xFF]),
1230 interrupts: into_fdt_prop(vec![0x0, 0xF, 0x4]),
Jaewan Kima67e36a2023-11-29 16:50:23 +09001231 iommus: vec![0x4, 0xFF0],
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001232 },
1233 AssignedDeviceNode {
1234 path: CString::new("/light").unwrap(),
Jaewan Kim19b984f2023-12-04 15:16:50 +09001235 reg: into_fdt_prop(vec![0x0, 0x10000, 0x0, 0x1000, 0x0, 0x20000, 0x0, 0x1000]),
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001236 interrupts: into_fdt_prop(vec![0x0, 0xF, 0x5]),
Jaewan Kima67e36a2023-11-29 16:50:23 +09001237 iommus: vec![0x40, 0xFFA, 0x50, 0xFFB],
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001238 },
1239 ];
1240
1241 for expected in expected_devices {
1242 let node = AssignedDeviceNode::parse(platform_dt, &expected.path);
1243 assert_eq!(node, Ok(expected));
1244 }
1245 let pviommus = collect_pviommus(platform_dt);
Jaewan Kima67e36a2023-11-29 16:50:23 +09001246 assert_eq!(pviommus, Ok(vec![0x4, 0x40, 0x50]));
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001247 }
1248
1249 #[test]
1250 fn device_info_iommu_sharing() {
1251 let mut fdt_data = fs::read(FDT_WITH_IOMMU_SHARING).unwrap();
1252 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1253 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1254 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1255 let mut platform_dt_data = pvmfw_fdt_template::RAW.to_vec();
1256 platform_dt_data.resize(pvmfw_fdt_template::RAW.len() * 2, 0);
1257 let platform_dt = Fdt::from_mut_slice(&mut platform_dt_data).unwrap();
1258 platform_dt.unpack().unwrap();
1259
Jaewan Kim52477ae2023-11-21 21:20:52 +09001260 let hypervisor = MockHypervisor {
Jaewan Kim19b984f2023-12-04 15:16:50 +09001261 mmio_tokens: [((0x9, 0xFF), 0x12F00000), ((0x1000, 0x9), 0x12000000)].into(),
1262 iommu_tokens: [((0x4, 0xFF0), (0x12E40000, 3)), ((0x4, 0xFF1), (0x12E40000, 9))].into(),
Jaewan Kim52477ae2023-11-21 21:20:52 +09001263 };
1264 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor).unwrap().unwrap();
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001265 device_info.filter(vm_dtbo).unwrap();
1266
1267 // SAFETY: Damaged VM DTBO wouldn't be used after this unsafe block.
1268 unsafe {
1269 platform_dt.apply_overlay(vm_dtbo.as_mut()).unwrap();
1270 }
1271 device_info.patch(platform_dt).unwrap();
1272
1273 let expected_devices = [
1274 AssignedDeviceNode {
1275 path: CString::new("/rng").unwrap(),
1276 reg: into_fdt_prop(vec![0x0, 0x9, 0x0, 0xFF]),
1277 interrupts: into_fdt_prop(vec![0x0, 0xF, 0x4]),
Jaewan Kima67e36a2023-11-29 16:50:23 +09001278 iommus: vec![0x4, 0xFF0],
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001279 },
1280 AssignedDeviceNode {
Jaewan Kima67e36a2023-11-29 16:50:23 +09001281 path: CString::new("/led").unwrap(),
Jaewan Kim19b984f2023-12-04 15:16:50 +09001282 reg: into_fdt_prop(vec![0x0, 0x1000, 0x0, 0x9]),
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001283 interrupts: into_fdt_prop(vec![0x0, 0xF, 0x5]),
Jaewan Kim19b984f2023-12-04 15:16:50 +09001284 iommus: vec![0x4, 0xFF1],
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001285 },
1286 ];
1287
1288 for expected in expected_devices {
1289 let node = AssignedDeviceNode::parse(platform_dt, &expected.path);
1290 assert_eq!(node, Ok(expected));
1291 }
1292
1293 let pviommus = collect_pviommus(platform_dt);
Jaewan Kima67e36a2023-11-29 16:50:23 +09001294 assert_eq!(pviommus, Ok(vec![0x4]));
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001295 }
1296
1297 #[test]
1298 fn device_info_iommu_id_conflict() {
1299 let mut fdt_data = fs::read(FDT_WITH_IOMMU_ID_CONFLICT).unwrap();
1300 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1301 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1302 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1303
Jaewan Kim52477ae2023-11-21 21:20:52 +09001304 let hypervisor = MockHypervisor {
Jaewan Kim19b984f2023-12-04 15:16:50 +09001305 mmio_tokens: [((0x9, 0xFF), 0x300)].into(),
Jaewan Kim52477ae2023-11-21 21:20:52 +09001306 iommu_tokens: [((0x4, 0xFF0), (0x12E40000, 0x3))].into(),
1307 };
1308 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor);
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001309
1310 assert_eq!(device_info, Err(DeviceAssignmentError::DuplicatedPvIommuIds));
1311 }
Jaewan Kim52477ae2023-11-21 21:20:52 +09001312
1313 #[test]
1314 fn device_info_invalid_reg() {
1315 let mut fdt_data = fs::read(FDT_FILE_PATH).unwrap();
1316 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1317 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1318 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1319
1320 let hypervisor = MockHypervisor {
1321 mmio_tokens: BTreeMap::new(),
1322 iommu_tokens: [((0x4, 0xFF0), (0x12E40000, 0x3))].into(),
1323 };
1324 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor);
1325
Pierre-Clément Tosi8b78bc32024-03-13 17:37:07 +00001326 assert_eq!(device_info, Err(DeviceAssignmentError::InvalidReg(0x9, 0xFF)));
Jaewan Kim52477ae2023-11-21 21:20:52 +09001327 }
1328
1329 #[test]
Jaewan Kim19b984f2023-12-04 15:16:50 +09001330 fn device_info_invalid_reg_out_of_order() {
1331 let mut fdt_data = fs::read(FDT_WITH_MULTIPLE_REG_IOMMU_FILE_PATH).unwrap();
1332 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1333 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1334 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1335
1336 let hypervisor = MockHypervisor {
1337 mmio_tokens: [((0xF000, 0x1000), 0xF10000), ((0xF100, 0x1000), 0xF00000)].into(),
1338 iommu_tokens: [((0xFF0, 0xF0), (0x40000, 0x4)), ((0xFF1, 0xF1), (0x50000, 0x5))].into(),
1339 };
1340 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor);
1341
Pierre-Clément Tosi8b78bc32024-03-13 17:37:07 +00001342 assert_eq!(device_info, Err(DeviceAssignmentError::InvalidPhysReg(0xF10000, 0x1000)));
Jaewan Kim19b984f2023-12-04 15:16:50 +09001343 }
1344
1345 #[test]
Jaewan Kim52477ae2023-11-21 21:20:52 +09001346 fn device_info_invalid_iommus() {
1347 let mut fdt_data = fs::read(FDT_FILE_PATH).unwrap();
1348 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1349 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1350 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1351
1352 let hypervisor = MockHypervisor {
1353 mmio_tokens: [((0x9, 0xFF), 0x12F00000)].into(),
1354 iommu_tokens: BTreeMap::new(),
1355 };
1356 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor);
1357
1358 assert_eq!(device_info, Err(DeviceAssignmentError::InvalidIommus));
1359 }
Jaewan Kim19b984f2023-12-04 15:16:50 +09001360
1361 #[test]
1362 fn device_info_duplicated_pv_iommus() {
1363 let mut fdt_data = fs::read(FDT_WITH_DUPLICATED_PVIOMMUS_FILE_PATH).unwrap();
1364 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1365 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1366 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1367
1368 let hypervisor = MockHypervisor {
1369 mmio_tokens: [((0x10000, 0x1000), 0xF00000), ((0x20000, 0xFF), 0xF10000)].into(),
1370 iommu_tokens: [((0xFF, 0xF), (0x40000, 0x4))].into(),
1371 };
1372 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor);
1373
1374 assert_eq!(device_info, Err(DeviceAssignmentError::DuplicatedPvIommuIds));
1375 }
1376
1377 #[test]
1378 fn device_info_duplicated_iommus() {
1379 let mut fdt_data = fs::read(FDT_FILE_PATH).unwrap();
1380 let mut vm_dtbo_data = fs::read(VM_DTBO_WITH_DUPLICATED_IOMMUS_FILE_PATH).unwrap();
1381 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1382 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1383
1384 let hypervisor = MockHypervisor {
1385 mmio_tokens: [((0x10000, 0x1000), 0xF00000), ((0x20000, 0xFF), 0xF10000)].into(),
1386 iommu_tokens: [((0xFF, 0xF), (0x40000, 0x4))].into(),
1387 };
1388 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor);
1389
1390 assert_eq!(device_info, Err(DeviceAssignmentError::UnsupportedIommusDuplication));
1391 }
1392
1393 #[test]
1394 fn device_info_duplicated_iommu_mapping() {
1395 let mut fdt_data = fs::read(FDT_WITH_MULTIPLE_REG_IOMMU_FILE_PATH).unwrap();
1396 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1397 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1398 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1399
1400 let hypervisor = MockHypervisor {
1401 mmio_tokens: [((0xF000, 0x1000), 0xF00000), ((0xF100, 0x1000), 0xF10000)].into(),
1402 iommu_tokens: [((0xFF0, 0xF0), (0x40000, 0x4)), ((0xFF1, 0xF1), (0x40000, 0x4))].into(),
1403 };
1404 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor);
1405
1406 assert_eq!(device_info, Err(DeviceAssignmentError::InvalidIommus));
1407 }
Jaewan Kim50246682024-03-11 23:18:54 +09001408
1409 #[test]
1410 fn device_assignment_clean() {
1411 let mut platform_dt_data = pvmfw_fdt_template::RAW.to_vec();
1412 let platform_dt = Fdt::from_mut_slice(&mut platform_dt_data).unwrap();
1413
1414 let compatible = platform_dt.root().next_compatible(cstr!("pkvm,pviommu"));
1415 assert_ne!(None, compatible.unwrap());
1416
1417 clean(platform_dt).unwrap();
1418
1419 let compatible = platform_dt.root().next_compatible(cstr!("pkvm,pviommu"));
1420 assert_eq!(Ok(None), compatible);
1421 }
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001422}