blob: fb00e72674934d7bafbfc1e891a8a7b189e671bf [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,
58 /// Invalid <reg>. Failed to validate with HVC.
Jaewan Kim52477ae2023-11-21 21:20:52 +090059 InvalidReg,
Jaewan Kimc6e023b2023-10-12 15:11:05 +090060 /// Invalid <interrupts>
61 InvalidInterrupts,
Jaewan Kim19b984f2023-12-04 15:16:50 +090062 /// Malformed <iommus>
63 MalformedIommus,
Jaewan Kim51ccfed2023-11-08 13:51:58 +090064 /// Invalid <iommus>
65 InvalidIommus,
Jaewan Kim19b984f2023-12-04 15:16:50 +090066 /// Invalid phys IOMMU node
67 InvalidPhysIommu,
Jaewan Kima9200492023-11-21 20:45:31 +090068 /// Invalid pvIOMMU node
69 InvalidPvIommu,
Jaewan Kim51ccfed2023-11-08 13:51:58 +090070 /// Too many pvIOMMU
71 TooManyPvIommu,
Jaewan Kim19b984f2023-12-04 15:16:50 +090072 /// Duplicated phys IOMMU IDs exist
73 DuplicatedIommuIds,
Jaewan Kim51ccfed2023-11-08 13:51:58 +090074 /// Duplicated pvIOMMU IDs exist
75 DuplicatedPvIommuIds,
Jaewan Kimf8abbb52023-12-12 22:11:39 +090076 /// Unsupported path format. Only supports full path.
77 UnsupportedPathFormat,
Jaewan Kimc6e023b2023-10-12 15:11:05 +090078 /// Unsupported overlay target syntax. Only supports <target-path> with full path.
79 UnsupportedOverlayTarget,
Jaewan Kim19b984f2023-12-04 15:16:50 +090080 /// Unsupported PhysIommu,
81 UnsupportedPhysIommu,
82 /// Unsupported (pvIOMMU id, vSID) duplication. Currently the pair should be unique.
83 UnsupportedPvIommusDuplication,
84 /// Unsupported (IOMMU token, SID) duplication. Currently the pair should be unique.
85 UnsupportedIommusDuplication,
Jaewan Kim51ccfed2023-11-08 13:51:58 +090086 /// Internal error
87 Internal,
Jaewan Kimc6e023b2023-10-12 15:11:05 +090088 /// Unexpected error from libfdt
89 UnexpectedFdtError(FdtError),
90}
91
92impl From<FdtError> for DeviceAssignmentError {
93 fn from(e: FdtError) -> Self {
94 DeviceAssignmentError::UnexpectedFdtError(e)
95 }
96}
97
98impl fmt::Display for DeviceAssignmentError {
99 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
100 match self {
101 Self::InvalidDtbo => write!(f, "Invalid DTBO"),
102 Self::InvalidSymbols => write!(
103 f,
104 "Invalid property in /__symbols__. Must point to valid assignable device node."
105 ),
Jaewan Kim19b984f2023-12-04 15:16:50 +0900106 Self::MalformedReg => write!(f, "Malformed <reg>. Can't parse"),
107 Self::InvalidReg => write!(f, "Invalid <reg>. Failed to validate with hypervisor"),
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900108 Self::InvalidInterrupts => write!(f, "Invalid <interrupts>"),
Jaewan Kim19b984f2023-12-04 15:16:50 +0900109 Self::MalformedIommus => write!(f, "Malformed <iommus>. Can't parse."),
110 Self::InvalidIommus => {
111 write!(f, "Invalid <iommus>. Failed to validate with hypervisor")
112 }
113 Self::InvalidPhysIommu => write!(f, "Invalid phys IOMMU node"),
Jaewan Kima9200492023-11-21 20:45:31 +0900114 Self::InvalidPvIommu => write!(f, "Invalid pvIOMMU node"),
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900115 Self::TooManyPvIommu => write!(
116 f,
117 "Too many pvIOMMU node. Insufficient pre-populated pvIOMMUs in platform DT"
118 ),
Jaewan Kim19b984f2023-12-04 15:16:50 +0900119 Self::DuplicatedIommuIds => {
120 write!(f, "Duplicated IOMMU IDs exist. IDs must unique among iommu node")
121 }
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900122 Self::DuplicatedPvIommuIds => {
Jaewan Kim19b984f2023-12-04 15:16:50 +0900123 write!(f, "Duplicated pvIOMMU IDs exist. IDs must unique among iommu node")
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900124 }
Jaewan Kimf8abbb52023-12-12 22:11:39 +0900125 Self::UnsupportedPathFormat => {
126 write!(f, "Unsupported UnsupportedPathFormat. Only supports full path")
127 }
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900128 Self::UnsupportedOverlayTarget => {
129 write!(f, "Unsupported overlay target. Only supports 'target-path = \"/\"'")
130 }
Jaewan Kim19b984f2023-12-04 15:16:50 +0900131 Self::UnsupportedPhysIommu => {
132 write!(f, "Unsupported Phys IOMMU. Currently only supports #iommu-cells = <1>")
133 }
134 Self::UnsupportedPvIommusDuplication => {
135 write!(f, "Unsupported (pvIOMMU id, vSID) duplication. Currently the pair should be unique.")
136 }
137 Self::UnsupportedIommusDuplication => {
138 write!(f, "Unsupported (IOMMU token, SID) duplication. Currently the pair should be unique.")
139 }
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900140 Self::Internal => write!(f, "Internal error"),
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900141 Self::UnexpectedFdtError(e) => write!(f, "Unexpected Error from libfdt: {e}"),
142 }
143 }
144}
145
146pub type Result<T> = core::result::Result<T, DeviceAssignmentError>;
147
Jaewan Kimf8abbb52023-12-12 22:11:39 +0900148#[derive(Clone, Default, Ord, PartialOrd, Eq, PartialEq)]
149pub struct DtPathTokens<'a> {
150 tokens: Vec<&'a [u8]>,
151}
152
153impl<'a> fmt::Debug for DtPathTokens<'a> {
154 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
155 let mut list = f.debug_list();
156 for token in &self.tokens {
157 let mut bytes = token.to_vec();
158 bytes.push(b'\0');
159 match CString::from_vec_with_nul(bytes) {
160 Ok(string) => list.entry(&string),
161 Err(_) => list.entry(token),
162 };
163 }
164 list.finish()
165 }
166}
167
168impl<'a> DtPathTokens<'a> {
169 fn new(path: &'a CStr) -> Result<Self> {
170 if path.to_bytes().first() != Some(&b'/') {
171 return Err(DeviceAssignmentError::UnsupportedPathFormat);
172 }
173 let tokens: Vec<_> = path
174 .to_bytes()
175 .split(|char| *char == b'/')
176 .filter(|&component| !component.is_empty())
177 .collect();
178 Ok(Self { tokens })
179 }
180
181 fn to_overlay_target_path(&self) -> Result<Self> {
182 if !self.is_overlayable_node() {
183 return Err(DeviceAssignmentError::InvalidDtbo);
184 }
185 Ok(Self { tokens: self.tokens.as_slice()[2..].to_vec() })
186 }
187
188 fn to_cstring(&self) -> CString {
189 if self.tokens.is_empty() {
190 return CString::new(*b"/\0").unwrap();
191 }
192
193 let size = self.tokens.iter().fold(0, |sum, token| sum + token.len() + 1);
194 let mut path = Vec::with_capacity(size + 1);
195 for token in &self.tokens {
196 path.push(b'/');
197 path.extend_from_slice(token);
198 }
199 path.push(b'\0');
200
201 CString::from_vec_with_nul(path).unwrap()
202 }
203
204 fn is_overlayable_node(&self) -> bool {
205 self.tokens.get(1) == Some(&&b"__overlay__"[..])
206 }
207}
208
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900209/// Represents VM DTBO
210#[repr(transparent)]
211pub struct VmDtbo(Fdt);
212
213impl VmDtbo {
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900214 /// Wraps a mutable slice containing a VM DTBO.
215 ///
216 /// Fails if the VM DTBO does not pass validation.
217 pub fn from_mut_slice(dtbo: &mut [u8]) -> Result<&mut Self> {
218 // This validates DTBO
219 let fdt = Fdt::from_mut_slice(dtbo)?;
220 // SAFETY: VmDtbo is a transparent wrapper around Fdt, so representation is the same.
221 Ok(unsafe { mem::transmute::<&mut Fdt, &mut Self>(fdt) })
222 }
223
224 // Locates device node path as if the given dtbo node path is assigned and VM DTBO is overlaid.
225 // For given dtbo node path, this concatenates <target-path> of the enclosing fragment and
226 // relative path from __overlay__ node.
227 //
228 // Here's an example with sample VM DTBO:
229 // / {
230 // fragment@rng {
231 // target-path = "/"; // Always 'target-path = "/"'. Disallows <target> or other path.
232 // __overlay__ {
233 // rng { ... }; // Actual device node is here. If overlaid, path would be "/rng"
234 // };
235 // };
Jaewan Kim80ef9fa2024-02-25 16:08:14 +0000236 // __symbols__ { // Contains list of assignable devices
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900237 // rng = "/fragment@rng/__overlay__/rng";
238 // };
239 // };
240 //
241 // Then locate_overlay_target_path(cstr!("/fragment@rng/__overlay__/rng")) is Ok("/rng")
242 //
243 // Contrary to fdt_overlay_target_offset(), this API enforces overlay target property
244 // 'target-path = "/"', so the overlay doesn't modify and/or append platform DT's existing
245 // node and/or properties. The enforcement is for compatibility reason.
Jaewan Kim19b984f2023-12-04 15:16:50 +0900246 fn locate_overlay_target_path(
247 &self,
Jaewan Kimf8abbb52023-12-12 22:11:39 +0900248 dtbo_node_path: &DtPathTokens,
Jaewan Kim19b984f2023-12-04 15:16:50 +0900249 dtbo_node: &FdtNode,
250 ) -> Result<CString> {
Jaewan Kim19b984f2023-12-04 15:16:50 +0900251 let fragment_node = dtbo_node.supernode_at_depth(1)?;
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900252 let target_path = fragment_node
Pierre-Clément Tosid701a0b2023-11-07 15:38:59 +0000253 .getprop_str(cstr!("target-path"))?
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900254 .ok_or(DeviceAssignmentError::InvalidDtbo)?;
255 if target_path != cstr!("/") {
256 return Err(DeviceAssignmentError::UnsupportedOverlayTarget);
257 }
258
Jaewan Kimf8abbb52023-12-12 22:11:39 +0900259 let overlaid_path = dtbo_node_path.to_overlay_target_path()?;
260 Ok(overlaid_path.to_cstring())
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900261 }
Jaewan Kim19b984f2023-12-04 15:16:50 +0900262
263 fn parse_physical_iommus(physical_node: &FdtNode) -> Result<BTreeMap<Phandle, PhysIommu>> {
264 let mut phys_iommus = BTreeMap::new();
265 for (node, _) in physical_node.descendants() {
266 let Some(phandle) = node.get_phandle()? else {
267 continue; // Skips unreachable IOMMU node
268 };
269 let Some(iommu) = PhysIommu::parse(&node)? else {
270 continue; // Skip if not a PhysIommu.
271 };
272 if phys_iommus.insert(phandle, iommu).is_some() {
273 return Err(FdtError::BadPhandle.into());
274 }
275 }
276 Self::validate_physical_iommus(&phys_iommus)?;
277 Ok(phys_iommus)
278 }
279
280 fn validate_physical_iommus(phys_iommus: &BTreeMap<Phandle, PhysIommu>) -> Result<()> {
281 let unique_iommus: BTreeSet<_> = phys_iommus.values().cloned().collect();
282 if phys_iommus.len() != unique_iommus.len() {
283 return Err(DeviceAssignmentError::DuplicatedIommuIds);
284 }
285 Ok(())
286 }
287
288 fn validate_physical_devices(
289 physical_devices: &BTreeMap<Phandle, PhysicalDeviceInfo>,
290 ) -> Result<()> {
291 // Only need to validate iommus because <reg> will be validated together with PV <reg>
292 // see: DeviceAssignmentInfo::validate_all_regs().
293 let mut all_iommus = BTreeSet::new();
294 for physical_device in physical_devices.values() {
295 for iommu in &physical_device.iommus {
296 if !all_iommus.insert(iommu) {
297 error!("Unsupported phys IOMMU duplication found, <iommus> = {iommu:?}");
298 return Err(DeviceAssignmentError::UnsupportedIommusDuplication);
299 }
300 }
301 }
302 Ok(())
303 }
304
305 fn parse_physical_devices_with_iommus(
306 physical_node: &FdtNode,
307 phys_iommus: &BTreeMap<Phandle, PhysIommu>,
308 ) -> Result<BTreeMap<Phandle, PhysicalDeviceInfo>> {
309 let mut physical_devices = BTreeMap::new();
310 for (node, _) in physical_node.descendants() {
311 let Some(info) = PhysicalDeviceInfo::parse(&node, phys_iommus)? else {
312 continue;
313 };
314 if physical_devices.insert(info.target, info).is_some() {
315 return Err(DeviceAssignmentError::InvalidDtbo);
316 }
317 }
318 Self::validate_physical_devices(&physical_devices)?;
319 Ok(physical_devices)
320 }
321
322 /// Parses Physical devices in VM DTBO
323 fn parse_physical_devices(&self) -> Result<BTreeMap<Phandle, PhysicalDeviceInfo>> {
324 let Some(physical_node) = self.as_ref().node(cstr!("/host"))? else {
325 return Ok(BTreeMap::new());
326 };
327
328 let phys_iommus = Self::parse_physical_iommus(&physical_node)?;
329 Self::parse_physical_devices_with_iommus(&physical_node, &phys_iommus)
330 }
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900331
Jaewan Kimf8abbb52023-12-12 22:11:39 +0900332 fn node(&self, path: &DtPathTokens) -> Result<Option<FdtNode>> {
333 let mut node = self.as_ref().root();
334 for token in &path.tokens {
335 let Some(subnode) = node.subnode_with_name_bytes(token)? else {
336 return Ok(None);
337 };
338 node = subnode;
339 }
340 Ok(Some(node))
341 }
Jaewan Kimc39974e2023-12-02 01:13:30 +0900342}
343
Jaewan Kimc730ebf2024-02-22 10:34:55 +0900344fn filter_dangling_symbols(fdt: &mut Fdt) -> Result<()> {
345 if let Some(symbols) = fdt.symbols()? {
346 let mut removed = vec![];
347 for prop in symbols.properties()? {
348 let path = CStr::from_bytes_with_nul(prop.value()?)
349 .map_err(|_| DeviceAssignmentError::Internal)?;
350 if fdt.node(path)?.is_none() {
351 let name = prop.name()?;
352 removed.push(CString::from(name));
353 }
354 }
355
356 let mut symbols = fdt.symbols_mut()?.unwrap();
357 for name in removed {
358 symbols.nop_property(&name)?;
359 }
360 }
361 Ok(())
362}
363
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900364impl AsRef<Fdt> for VmDtbo {
365 fn as_ref(&self) -> &Fdt {
366 &self.0
367 }
368}
369
370impl AsMut<Fdt> for VmDtbo {
371 fn as_mut(&mut self) -> &mut Fdt {
372 &mut self.0
373 }
374}
375
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900376#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
377struct PvIommu {
378 // ID from pvIOMMU node
379 id: u32,
380}
381
382impl PvIommu {
383 fn parse(node: &FdtNode) -> Result<Self> {
Jaewan Kima9200492023-11-21 20:45:31 +0900384 let iommu_cells = node
385 .getprop_u32(cstr!("#iommu-cells"))?
386 .ok_or(DeviceAssignmentError::InvalidPvIommu)?;
Jaewan Kim19b984f2023-12-04 15:16:50 +0900387 // Ensures #iommu-cells = <1>. It means that `<iommus>` entry contains pair of
Jaewan Kima9200492023-11-21 20:45:31 +0900388 // (pvIOMMU ID, vSID)
389 if iommu_cells != 1 {
390 return Err(DeviceAssignmentError::InvalidPvIommu);
391 }
392 let id = node.getprop_u32(cstr!("id"))?.ok_or(DeviceAssignmentError::InvalidPvIommu)?;
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900393 Ok(Self { id })
394 }
395}
396
Jaewan Kima9200492023-11-21 20:45:31 +0900397#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
398struct Vsid(u32);
399
Jaewan Kim19b984f2023-12-04 15:16:50 +0900400#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
401struct Sid(u64);
402
403impl From<u32> for Sid {
404 fn from(sid: u32) -> Self {
405 Self(sid.into())
406 }
407}
408
409#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
Jaewan Kim52477ae2023-11-21 21:20:52 +0900410struct DeviceReg {
411 addr: u64,
412 size: u64,
413}
414
415impl TryFrom<Reg<u64>> for DeviceReg {
416 type Error = DeviceAssignmentError;
417
418 fn try_from(reg: Reg<u64>) -> Result<Self> {
Jaewan Kim19b984f2023-12-04 15:16:50 +0900419 Ok(Self { addr: reg.addr, size: reg.size.ok_or(DeviceAssignmentError::MalformedReg)? })
Jaewan Kim52477ae2023-11-21 21:20:52 +0900420 }
421}
422
423fn parse_node_reg(node: &FdtNode) -> Result<Vec<DeviceReg>> {
424 node.reg()?
Jaewan Kim19b984f2023-12-04 15:16:50 +0900425 .ok_or(DeviceAssignmentError::MalformedReg)?
Jaewan Kim52477ae2023-11-21 21:20:52 +0900426 .map(DeviceReg::try_from)
427 .collect::<Result<Vec<_>>>()
428}
429
430fn to_be_bytes(reg: &[DeviceReg]) -> Vec<u8> {
431 let mut reg_cells = vec![];
432 for x in reg {
433 reg_cells.extend_from_slice(&x.addr.to_be_bytes());
434 reg_cells.extend_from_slice(&x.size.to_be_bytes());
435 }
436 reg_cells
437}
438
Jaewan Kim19b984f2023-12-04 15:16:50 +0900439#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
440struct PhysIommu {
441 token: u64,
442}
443
444impl PhysIommu {
445 fn parse(node: &FdtNode) -> Result<Option<Self>> {
446 let Some(token) = node.getprop_u64(cstr!("android,pvmfw,token"))? else {
447 return Ok(None);
448 };
449 let Some(iommu_cells) = node.getprop_u32(cstr!("#iommu-cells"))? else {
450 return Err(DeviceAssignmentError::InvalidPhysIommu);
451 };
452 // Currently only supports #iommu-cells = <1>.
453 // In that case `<iommus>` entry contains pair of (pIOMMU phandle, Sid token)
454 if iommu_cells != 1 {
455 return Err(DeviceAssignmentError::UnsupportedPhysIommu);
456 }
457 Ok(Some(Self { token }))
458 }
459}
460
461#[derive(Debug)]
462struct PhysicalDeviceInfo {
463 target: Phandle,
464 reg: Vec<DeviceReg>,
465 iommus: Vec<(PhysIommu, Sid)>,
466}
467
468impl PhysicalDeviceInfo {
469 fn parse_iommus(
470 node: &FdtNode,
471 phys_iommus: &BTreeMap<Phandle, PhysIommu>,
472 ) -> Result<Vec<(PhysIommu, Sid)>> {
473 let mut iommus = vec![];
474 let Some(mut cells) = node.getprop_cells(cstr!("iommus"))? else {
475 return Ok(iommus);
476 };
477 while let Some(cell) = cells.next() {
478 // Parse pIOMMU ID
479 let phandle =
480 Phandle::try_from(cell).or(Err(DeviceAssignmentError::MalformedIommus))?;
481 let iommu = phys_iommus.get(&phandle).ok_or(DeviceAssignmentError::MalformedIommus)?;
482
483 // Parse Sid
484 let Some(cell) = cells.next() else {
485 return Err(DeviceAssignmentError::MalformedIommus);
486 };
487
488 iommus.push((*iommu, Sid::from(cell)));
489 }
490 Ok(iommus)
491 }
492
493 fn parse(node: &FdtNode, phys_iommus: &BTreeMap<Phandle, PhysIommu>) -> Result<Option<Self>> {
494 let Some(phandle) = node.getprop_u32(cstr!("android,pvmfw,target"))? else {
495 return Ok(None);
496 };
497 let target = Phandle::try_from(phandle)?;
498 let reg = parse_node_reg(node)?;
499 let iommus = Self::parse_iommus(node, phys_iommus)?;
500 Ok(Some(Self { target, reg, iommus }))
501 }
502}
503
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900504/// Assigned device information parsed from crosvm DT.
505/// Keeps everything in the owned data because underlying FDT will be reused for platform DT.
506#[derive(Debug, Eq, PartialEq)]
507struct AssignedDeviceInfo {
508 // Node path of assigned device (e.g. "/rng")
509 node_path: CString,
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900510 // <reg> property from the crosvm DT
Jaewan Kim52477ae2023-11-21 21:20:52 +0900511 reg: Vec<DeviceReg>,
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900512 // <interrupts> property from the crosvm DT
513 interrupts: Vec<u8>,
Jaewan Kima9200492023-11-21 20:45:31 +0900514 // Parsed <iommus> property from the crosvm DT. Tuple of PvIommu and vSID.
515 iommus: Vec<(PvIommu, Vsid)>,
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900516}
517
518impl AssignedDeviceInfo {
Jaewan Kim19b984f2023-12-04 15:16:50 +0900519 fn validate_reg(
520 device_reg: &[DeviceReg],
521 physical_device_reg: &[DeviceReg],
Jaewan Kim52477ae2023-11-21 21:20:52 +0900522 hypervisor: &dyn DeviceAssigningHypervisor,
Jaewan Kim19b984f2023-12-04 15:16:50 +0900523 ) -> Result<()> {
524 if device_reg.len() != physical_device_reg.len() {
525 return Err(DeviceAssignmentError::InvalidReg);
526 }
527 // PV reg and physical reg should have 1:1 match in order.
528 for (reg, phys_reg) in device_reg.iter().zip(physical_device_reg.iter()) {
529 let addr = hypervisor.get_phys_mmio_token(reg.addr, reg.size).map_err(|e| {
Pierre-Clément Tosi08d6e3f2024-03-13 18:22:16 +0000530 error!("Hypervisor error while requesting MMIO token: {e}");
Jaewan Kim52477ae2023-11-21 21:20:52 +0900531 DeviceAssignmentError::InvalidReg
532 })?;
Jaewan Kim19b984f2023-12-04 15:16:50 +0900533 // Only check address because hypervisor guaranatees size match when success.
534 if phys_reg.addr != addr {
535 error!("Failed to validate device <reg>. No matching phys reg for reg={reg:x?}");
536 return Err(DeviceAssignmentError::InvalidReg);
537 }
Jaewan Kim52477ae2023-11-21 21:20:52 +0900538 }
Jaewan Kim19b984f2023-12-04 15:16:50 +0900539 Ok(())
Jaewan Kim52477ae2023-11-21 21:20:52 +0900540 }
541
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900542 fn parse_interrupts(node: &FdtNode) -> Result<Vec<u8>> {
543 // Validation: Validate if interrupts cell numbers are multiple of #interrupt-cells.
544 // We can't know how many interrupts would exist.
545 let interrupts_cells = node
Pierre-Clément Tosid701a0b2023-11-07 15:38:59 +0000546 .getprop_cells(cstr!("interrupts"))?
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900547 .ok_or(DeviceAssignmentError::InvalidInterrupts)?
548 .count();
549 if interrupts_cells % CELLS_PER_INTERRUPT != 0 {
550 return Err(DeviceAssignmentError::InvalidInterrupts);
551 }
552
553 // Once validated, keep the raw bytes so patch can be done with setprop()
Pierre-Clément Tosid701a0b2023-11-07 15:38:59 +0000554 Ok(node.getprop(cstr!("interrupts")).unwrap().unwrap().into())
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900555 }
556
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900557 // TODO(b/277993056): Also validate /__local_fixups__ to ensure that <iommus> has phandle.
Jaewan Kima9200492023-11-21 20:45:31 +0900558 fn parse_iommus(
559 node: &FdtNode,
560 pviommus: &BTreeMap<Phandle, PvIommu>,
561 ) -> Result<Vec<(PvIommu, Vsid)>> {
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900562 let mut iommus = vec![];
Jaewan Kima9200492023-11-21 20:45:31 +0900563 let Some(mut cells) = node.getprop_cells(cstr!("iommus"))? else {
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900564 return Ok(iommus);
565 };
Jaewan Kima9200492023-11-21 20:45:31 +0900566 while let Some(cell) = cells.next() {
567 // Parse pvIOMMU ID
Jaewan Kim19b984f2023-12-04 15:16:50 +0900568 let phandle =
569 Phandle::try_from(cell).or(Err(DeviceAssignmentError::MalformedIommus))?;
570 let pviommu = pviommus.get(&phandle).ok_or(DeviceAssignmentError::MalformedIommus)?;
Jaewan Kima9200492023-11-21 20:45:31 +0900571
572 // Parse vSID
573 let Some(cell) = cells.next() else {
Jaewan Kim19b984f2023-12-04 15:16:50 +0900574 return Err(DeviceAssignmentError::MalformedIommus);
Jaewan Kima9200492023-11-21 20:45:31 +0900575 };
576 let vsid = Vsid(cell);
577
578 iommus.push((*pviommu, vsid));
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900579 }
580 Ok(iommus)
581 }
582
Jaewan Kim19b984f2023-12-04 15:16:50 +0900583 fn validate_iommus(
584 iommus: &[(PvIommu, Vsid)],
585 physical_device_iommu: &[(PhysIommu, Sid)],
586 hypervisor: &dyn DeviceAssigningHypervisor,
587 ) -> Result<()> {
588 if iommus.len() != physical_device_iommu.len() {
589 return Err(DeviceAssignmentError::InvalidIommus);
590 }
591 // pvIOMMU can be reordered, and hypervisor may not guarantee 1:1 mapping.
592 // So we need to mark what's matched or not.
593 let mut physical_device_iommu = physical_device_iommu.to_vec();
594 for (pviommu, vsid) in iommus {
Pierre-Clément Tosi08d6e3f2024-03-13 18:22:16 +0000595 let (id, sid) =
596 hypervisor.get_phys_iommu_token(pviommu.id.into(), vsid.0.into()).map_err(|e| {
597 error!("Hypervisor error while requesting IOMMU token ({pviommu:?}, {vsid:?}): {e}");
598 DeviceAssignmentError::InvalidIommus
599 })?;
Jaewan Kim19b984f2023-12-04 15:16:50 +0900600
601 let pos = physical_device_iommu
602 .iter()
603 .position(|(phys_iommu, phys_sid)| (phys_iommu.token, phys_sid.0) == (id, sid));
604 match pos {
605 Some(pos) => physical_device_iommu.remove(pos),
606 None => {
607 error!("Failed to validate device <iommus>. No matching phys iommu or duplicated mapping for pviommu={pviommu:?}, vsid={vsid:?}");
608 return Err(DeviceAssignmentError::InvalidIommus);
609 }
610 };
611 }
612 Ok(())
613 }
614
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900615 fn parse(
616 fdt: &Fdt,
617 vm_dtbo: &VmDtbo,
Jaewan Kimf8abbb52023-12-12 22:11:39 +0900618 dtbo_node_path: &DtPathTokens,
Jaewan Kim19b984f2023-12-04 15:16:50 +0900619 physical_devices: &BTreeMap<Phandle, PhysicalDeviceInfo>,
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900620 pviommus: &BTreeMap<Phandle, PvIommu>,
Jaewan Kim52477ae2023-11-21 21:20:52 +0900621 hypervisor: &dyn DeviceAssigningHypervisor,
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900622 ) -> Result<Option<Self>> {
Jaewan Kim19b984f2023-12-04 15:16:50 +0900623 let dtbo_node =
Jaewan Kimf8abbb52023-12-12 22:11:39 +0900624 vm_dtbo.node(dtbo_node_path)?.ok_or(DeviceAssignmentError::InvalidSymbols)?;
Jaewan Kim19b984f2023-12-04 15:16:50 +0900625 let node_path = vm_dtbo.locate_overlay_target_path(dtbo_node_path, &dtbo_node)?;
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900626
627 let Some(node) = fdt.node(&node_path)? else { return Ok(None) };
628
Jaewan Kim80ef9fa2024-02-25 16:08:14 +0000629 // Currently can only assign devices backed by physical devices.
Jaewan Kim19b984f2023-12-04 15:16:50 +0900630 let phandle = dtbo_node.get_phandle()?.ok_or(DeviceAssignmentError::InvalidDtbo)?;
Jaewan Kim80ef9fa2024-02-25 16:08:14 +0000631 let Some(physical_device) = physical_devices.get(&phandle) else {
632 // If labeled DT node isn't backed by physical device node, then just return None.
633 // It's not an error because such node can be a dependency of assignable device nodes.
634 return Ok(None);
635 };
Jaewan Kim19b984f2023-12-04 15:16:50 +0900636
637 let reg = parse_node_reg(&node)?;
638 Self::validate_reg(&reg, &physical_device.reg, hypervisor)?;
639
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900640 let interrupts = Self::parse_interrupts(&node)?;
Jaewan Kim19b984f2023-12-04 15:16:50 +0900641
642 let iommus = Self::parse_iommus(&node, pviommus)?;
643 Self::validate_iommus(&iommus, &physical_device.iommus, hypervisor)?;
644
Jaewan Kimf8abbb52023-12-12 22:11:39 +0900645 Ok(Some(Self { node_path, reg, interrupts, iommus }))
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900646 }
647
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900648 fn patch(&self, fdt: &mut Fdt, pviommu_phandles: &BTreeMap<PvIommu, Phandle>) -> Result<()> {
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900649 let mut dst = fdt.node_mut(&self.node_path)?.unwrap();
Jaewan Kim52477ae2023-11-21 21:20:52 +0900650 dst.setprop(cstr!("reg"), &to_be_bytes(&self.reg))?;
Pierre-Clément Tosid701a0b2023-11-07 15:38:59 +0000651 dst.setprop(cstr!("interrupts"), &self.interrupts)?;
Jaewan Kima9200492023-11-21 20:45:31 +0900652 let mut iommus = Vec::with_capacity(8 * self.iommus.len());
653 for (pviommu, vsid) in &self.iommus {
654 let phandle = pviommu_phandles.get(pviommu).unwrap();
655 iommus.extend_from_slice(&u32::from(*phandle).to_be_bytes());
656 iommus.extend_from_slice(&vsid.0.to_be_bytes());
657 }
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900658 dst.setprop(cstr!("iommus"), &iommus)?;
659
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900660 Ok(())
661 }
662}
663
664#[derive(Debug, Default, Eq, PartialEq)]
665pub struct DeviceAssignmentInfo {
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900666 pviommus: BTreeSet<PvIommu>,
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900667 assigned_devices: Vec<AssignedDeviceInfo>,
668 filtered_dtbo_paths: Vec<CString>,
669}
670
671impl DeviceAssignmentInfo {
Chris Wailes9d09f572024-01-16 13:31:02 -0800672 const PVIOMMU_COMPATIBLE: &'static CStr = cstr!("pkvm,pviommu");
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900673
674 /// Parses pvIOMMUs in fdt
675 // Note: This will validate pvIOMMU ids' uniqueness, even when unassigned.
676 fn parse_pviommus(fdt: &Fdt) -> Result<BTreeMap<Phandle, PvIommu>> {
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900677 let mut pviommus = BTreeMap::new();
678 for compatible in fdt.compatible_nodes(Self::PVIOMMU_COMPATIBLE)? {
679 let Some(phandle) = compatible.get_phandle()? else {
680 continue; // Skips unreachable pvIOMMU node
681 };
682 let pviommu = PvIommu::parse(&compatible)?;
683 if pviommus.insert(phandle, pviommu).is_some() {
684 return Err(FdtError::BadPhandle.into());
685 }
686 }
687 Ok(pviommus)
688 }
689
Jaewan Kim19b984f2023-12-04 15:16:50 +0900690 fn validate_pviommu_topology(assigned_devices: &[AssignedDeviceInfo]) -> Result<()> {
691 let mut all_iommus = BTreeSet::new();
692 for assigned_device in assigned_devices {
693 for iommu in &assigned_device.iommus {
694 if !all_iommus.insert(iommu) {
695 error!("Unsupported pvIOMMU duplication found, <iommus> = {iommu:?}");
696 return Err(DeviceAssignmentError::UnsupportedPvIommusDuplication);
697 }
698 }
699 }
700 Ok(())
701 }
702
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900703 /// Parses fdt and vm_dtbo, and creates new DeviceAssignmentInfo
704 // TODO(b/277993056): Parse __local_fixups__
705 // TODO(b/277993056): Parse __fixups__
Jaewan Kim52477ae2023-11-21 21:20:52 +0900706 pub fn parse(
707 fdt: &Fdt,
708 vm_dtbo: &VmDtbo,
709 hypervisor: &dyn DeviceAssigningHypervisor,
710 ) -> Result<Option<Self>> {
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900711 let Some(symbols_node) = vm_dtbo.as_ref().symbols()? else {
712 // /__symbols__ should contain all assignable devices.
713 // If empty, then nothing can be assigned.
714 return Ok(None);
715 };
716
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900717 let pviommus = Self::parse_pviommus(fdt)?;
718 let unique_pviommus: BTreeSet<_> = pviommus.values().cloned().collect();
719 if pviommus.len() != unique_pviommus.len() {
720 return Err(DeviceAssignmentError::DuplicatedPvIommuIds);
721 }
722
Jaewan Kim19b984f2023-12-04 15:16:50 +0900723 let physical_devices = vm_dtbo.parse_physical_devices()?;
724
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900725 let mut assigned_devices = vec![];
726 let mut filtered_dtbo_paths = vec![];
727 for symbol_prop in symbols_node.properties()? {
728 let symbol_prop_value = symbol_prop.value()?;
729 let dtbo_node_path = CStr::from_bytes_with_nul(symbol_prop_value)
730 .or(Err(DeviceAssignmentError::InvalidSymbols))?;
Jaewan Kimf8abbb52023-12-12 22:11:39 +0900731 let dtbo_node_path = DtPathTokens::new(dtbo_node_path)?;
732 if !dtbo_node_path.is_overlayable_node() {
Jaewan Kimc39974e2023-12-02 01:13:30 +0900733 continue;
734 }
Jaewan Kim19b984f2023-12-04 15:16:50 +0900735 let assigned_device = AssignedDeviceInfo::parse(
736 fdt,
737 vm_dtbo,
Jaewan Kimf8abbb52023-12-12 22:11:39 +0900738 &dtbo_node_path,
Jaewan Kim19b984f2023-12-04 15:16:50 +0900739 &physical_devices,
740 &pviommus,
741 hypervisor,
742 )?;
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900743 if let Some(assigned_device) = assigned_device {
744 assigned_devices.push(assigned_device);
745 } else {
Jaewan Kimf8abbb52023-12-12 22:11:39 +0900746 filtered_dtbo_paths.push(dtbo_node_path.to_cstring());
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900747 }
748 }
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900749 if assigned_devices.is_empty() {
750 return Ok(None);
751 }
Jaewan Kimc39974e2023-12-02 01:13:30 +0900752
Jaewan Kim19b984f2023-12-04 15:16:50 +0900753 Self::validate_pviommu_topology(&assigned_devices)?;
754
Jaewan Kimc39974e2023-12-02 01:13:30 +0900755 // Clean up any nodes that wouldn't be overlaid but may contain reference to filtered nodes.
756 // Otherwise, `fdt_apply_overlay()` would fail because of missing phandle reference.
Jaewan Kimc39974e2023-12-02 01:13:30 +0900757 // TODO(b/277993056): Also filter other unused nodes/props in __local_fixups__
758 filtered_dtbo_paths.push(CString::new("/__local_fixups__/host").unwrap());
759
760 // Note: Any node without __overlay__ will be ignored by fdt_apply_overlay,
761 // so doesn't need to be filtered.
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900762
763 Ok(Some(Self { pviommus: unique_pviommus, assigned_devices, filtered_dtbo_paths }))
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900764 }
765
766 /// Filters VM DTBO to only contain necessary information for booting pVM
767 /// In detail, this will remove followings by setting nop node / nop property.
768 /// - Removes unassigned devices
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900769 // TODO(b/277993056): remove unused dependencies in VM DTBO.
770 // TODO(b/277993056): remove supernodes' properties.
771 // TODO(b/277993056): remove unused alises.
772 pub fn filter(&self, vm_dtbo: &mut VmDtbo) -> Result<()> {
773 let vm_dtbo = vm_dtbo.as_mut();
774
775 // Filters unused node in assigned devices
776 for filtered_dtbo_path in &self.filtered_dtbo_paths {
777 let node = vm_dtbo.node_mut(filtered_dtbo_path).unwrap().unwrap();
778 node.nop()?;
779 }
780
Jaewan Kim371f6c82024-02-24 01:33:37 +0900781 filter_dangling_symbols(vm_dtbo)
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900782 }
783
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900784 fn patch_pviommus(&self, fdt: &mut Fdt) -> Result<BTreeMap<PvIommu, Phandle>> {
Pierre-Clément Tosi244efea2024-02-16 14:48:14 +0000785 let mut compatible = fdt.root_mut().next_compatible(Self::PVIOMMU_COMPATIBLE)?;
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900786 let mut pviommu_phandles = BTreeMap::new();
787
788 for pviommu in &self.pviommus {
789 let mut node = compatible.ok_or(DeviceAssignmentError::TooManyPvIommu)?;
790 let phandle = node.as_node().get_phandle()?.ok_or(DeviceAssignmentError::Internal)?;
791 node.setprop_inplace(cstr!("id"), &pviommu.id.to_be_bytes())?;
792 if pviommu_phandles.insert(*pviommu, phandle).is_some() {
793 return Err(DeviceAssignmentError::Internal);
794 }
795 compatible = node.next_compatible(Self::PVIOMMU_COMPATIBLE)?;
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900796 }
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900797
798 // Filters pre-populated but unassigned pvIOMMUs.
799 while let Some(filtered_pviommu) = compatible {
800 compatible = filtered_pviommu.delete_and_next_compatible(Self::PVIOMMU_COMPATIBLE)?;
801 }
802
803 Ok(pviommu_phandles)
804 }
805
806 pub fn patch(&self, fdt: &mut Fdt) -> Result<()> {
807 let pviommu_phandles = self.patch_pviommus(fdt)?;
808
809 // Patches assigned devices
810 for device in &self.assigned_devices {
811 device.patch(fdt, &pviommu_phandles)?;
812 }
813
Jaewan Kimc730ebf2024-02-22 10:34:55 +0900814 // Removes any dangling references in __symbols__ (e.g. removed pvIOMMUs)
815 filter_dangling_symbols(fdt)
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900816 }
817}
818
Jaewan Kim50246682024-03-11 23:18:54 +0900819/// Cleans device trees not to contain any pre-populated nodes/props for device assignment.
820pub fn clean(fdt: &mut Fdt) -> Result<()> {
821 let mut compatible = fdt.root_mut().next_compatible(cstr!("pkvm,pviommu"))?;
822 // Filters pre-populated
823 while let Some(filtered_pviommu) = compatible {
824 compatible = filtered_pviommu.delete_and_next_compatible(cstr!("pkvm,pviommu"))?;
825 }
826
827 // Removes any dangling references in __symbols__ (e.g. removed pvIOMMUs)
828 filter_dangling_symbols(fdt)
829}
830
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900831#[cfg(test)]
832mod tests {
833 use super::*;
Jaewan Kim52477ae2023-11-21 21:20:52 +0900834 use alloc::collections::{BTreeMap, BTreeSet};
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900835 use std::fs;
836
837 const VM_DTBO_FILE_PATH: &str = "test_pvmfw_devices_vm_dtbo.dtbo";
838 const VM_DTBO_WITHOUT_SYMBOLS_FILE_PATH: &str =
839 "test_pvmfw_devices_vm_dtbo_without_symbols.dtbo";
Jaewan Kim19b984f2023-12-04 15:16:50 +0900840 const VM_DTBO_WITH_DUPLICATED_IOMMUS_FILE_PATH: &str =
841 "test_pvmfw_devices_vm_dtbo_with_duplicated_iommus.dtbo";
Jaewan Kima67e36a2023-11-29 16:50:23 +0900842 const FDT_WITHOUT_IOMMUS_FILE_PATH: &str = "test_pvmfw_devices_without_iommus.dtb";
Jaewan Kim52477ae2023-11-21 21:20:52 +0900843 const FDT_WITHOUT_DEVICE_FILE_PATH: &str = "test_pvmfw_devices_without_device.dtb";
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900844 const FDT_FILE_PATH: &str = "test_pvmfw_devices_with_rng.dtb";
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900845 const FDT_WITH_MULTIPLE_DEVICES_IOMMUS_FILE_PATH: &str =
846 "test_pvmfw_devices_with_multiple_devices_iommus.dtb";
847 const FDT_WITH_IOMMU_SHARING: &str = "test_pvmfw_devices_with_iommu_sharing.dtb";
848 const FDT_WITH_IOMMU_ID_CONFLICT: &str = "test_pvmfw_devices_with_iommu_id_conflict.dtb";
Jaewan Kim19b984f2023-12-04 15:16:50 +0900849 const FDT_WITH_DUPLICATED_PVIOMMUS_FILE_PATH: &str =
850 "test_pvmfw_devices_with_duplicated_pviommus.dtb";
851 const FDT_WITH_MULTIPLE_REG_IOMMU_FILE_PATH: &str =
852 "test_pvmfw_devices_with_multiple_reg_iommus.dtb";
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900853
Jaewan Kim52477ae2023-11-21 21:20:52 +0900854 #[derive(Debug, Default)]
855 struct MockHypervisor {
856 mmio_tokens: BTreeMap<(u64, u64), u64>,
857 iommu_tokens: BTreeMap<(u64, u64), (u64, u64)>,
858 }
859
860 impl DeviceAssigningHypervisor for MockHypervisor {
861 fn get_phys_mmio_token(&self, base_ipa: u64, size: u64) -> hyp::Result<u64> {
862 Ok(*self.mmio_tokens.get(&(base_ipa, size)).ok_or(hyp::Error::KvmError(
863 hyp::KvmError::InvalidParameter,
864 0xc6000012, /* VENDOR_HYP_KVM_DEV_REQ_MMIO_FUNC_ID */
865 ))?)
866 }
867
868 fn get_phys_iommu_token(&self, pviommu_id: u64, vsid: u64) -> hyp::Result<(u64, u64)> {
869 Ok(*self.iommu_tokens.get(&(pviommu_id, vsid)).ok_or(hyp::Error::KvmError(
870 hyp::KvmError::InvalidParameter,
871 0xc6000013, /* VENDOR_HYP_KVM_DEV_REQ_DMA_FUNC_ID */
872 ))?)
873 }
874 }
875
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900876 #[derive(Debug, Eq, PartialEq)]
877 struct AssignedDeviceNode {
878 path: CString,
879 reg: Vec<u8>,
880 interrupts: Vec<u8>,
Jaewan Kima67e36a2023-11-29 16:50:23 +0900881 iommus: Vec<u32>, // pvIOMMU id and vSID
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900882 }
883
884 impl AssignedDeviceNode {
885 fn parse(fdt: &Fdt, path: &CStr) -> Result<Self> {
886 let Some(node) = fdt.node(path)? else {
887 return Err(FdtError::NotFound.into());
888 };
889
Jaewan Kim19b984f2023-12-04 15:16:50 +0900890 let reg = node.getprop(cstr!("reg"))?.ok_or(DeviceAssignmentError::MalformedReg)?;
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900891 let interrupts = node
892 .getprop(cstr!("interrupts"))?
893 .ok_or(DeviceAssignmentError::InvalidInterrupts)?;
894 let mut iommus = vec![];
Jaewan Kima9200492023-11-21 20:45:31 +0900895 if let Some(mut cells) = node.getprop_cells(cstr!("iommus"))? {
896 while let Some(pviommu_id) = cells.next() {
897 // pvIOMMU id
898 let phandle = Phandle::try_from(pviommu_id)?;
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900899 let pviommu = fdt
900 .node_with_phandle(phandle)?
Jaewan Kim19b984f2023-12-04 15:16:50 +0900901 .ok_or(DeviceAssignmentError::MalformedIommus)?;
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900902 let compatible = pviommu.getprop_str(cstr!("compatible"));
903 if compatible != Ok(Some(cstr!("pkvm,pviommu"))) {
Jaewan Kim19b984f2023-12-04 15:16:50 +0900904 return Err(DeviceAssignmentError::MalformedIommus);
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900905 }
906 let id = pviommu
907 .getprop_u32(cstr!("id"))?
Jaewan Kim19b984f2023-12-04 15:16:50 +0900908 .ok_or(DeviceAssignmentError::MalformedIommus)?;
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900909 iommus.push(id);
Jaewan Kima9200492023-11-21 20:45:31 +0900910
911 // vSID
912 let Some(vsid) = cells.next() else {
Jaewan Kim19b984f2023-12-04 15:16:50 +0900913 return Err(DeviceAssignmentError::MalformedIommus);
Jaewan Kima9200492023-11-21 20:45:31 +0900914 };
915 iommus.push(vsid);
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900916 }
917 }
918 Ok(Self { path: path.into(), reg: reg.into(), interrupts: interrupts.into(), iommus })
919 }
920 }
921
922 fn collect_pviommus(fdt: &Fdt) -> Result<Vec<u32>> {
923 let mut pviommus = BTreeSet::new();
924 for pviommu in fdt.compatible_nodes(cstr!("pkvm,pviommu"))? {
925 if let Ok(Some(id)) = pviommu.getprop_u32(cstr!("id")) {
926 pviommus.insert(id);
927 }
928 }
929 Ok(pviommus.iter().cloned().collect())
930 }
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900931
932 fn into_fdt_prop(native_bytes: Vec<u32>) -> Vec<u8> {
933 let mut v = Vec::with_capacity(native_bytes.len() * 4);
934 for byte in native_bytes {
935 v.extend_from_slice(&byte.to_be_bytes());
936 }
937 v
938 }
939
Jaewan Kim52477ae2023-11-21 21:20:52 +0900940 impl From<[u64; 2]> for DeviceReg {
941 fn from(fdt_cells: [u64; 2]) -> Self {
942 DeviceReg { addr: fdt_cells[0], size: fdt_cells[1] }
943 }
944 }
945
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900946 #[test]
947 fn device_info_new_without_symbols() {
948 let mut fdt_data = fs::read(FDT_FILE_PATH).unwrap();
949 let mut vm_dtbo_data = fs::read(VM_DTBO_WITHOUT_SYMBOLS_FILE_PATH).unwrap();
950 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
951 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
952
Jaewan Kim52477ae2023-11-21 21:20:52 +0900953 let hypervisor: MockHypervisor = Default::default();
954 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor).unwrap();
955 assert_eq!(device_info, None);
956 }
957
958 #[test]
959 fn device_info_new_without_device() {
960 let mut fdt_data = fs::read(FDT_WITHOUT_DEVICE_FILE_PATH).unwrap();
961 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
962 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
963 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
964
965 let hypervisor: MockHypervisor = Default::default();
966 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor).unwrap();
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900967 assert_eq!(device_info, None);
968 }
969
970 #[test]
Jaewan Kima67e36a2023-11-29 16:50:23 +0900971 fn device_info_assigned_info_without_iommus() {
972 let mut fdt_data = fs::read(FDT_WITHOUT_IOMMUS_FILE_PATH).unwrap();
973 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
974 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
975 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
976
Jaewan Kim52477ae2023-11-21 21:20:52 +0900977 let hypervisor = MockHypervisor {
978 mmio_tokens: [((0x9, 0xFF), 0x300)].into(),
979 iommu_tokens: BTreeMap::new(),
980 };
981 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor).unwrap().unwrap();
Jaewan Kima67e36a2023-11-29 16:50:23 +0900982
983 let expected = [AssignedDeviceInfo {
Jaewan Kimc39974e2023-12-02 01:13:30 +0900984 node_path: CString::new("/bus0/backlight").unwrap(),
Jaewan Kim52477ae2023-11-21 21:20:52 +0900985 reg: vec![[0x9, 0xFF].into()],
Jaewan Kima67e36a2023-11-29 16:50:23 +0900986 interrupts: into_fdt_prop(vec![0x0, 0xF, 0x4]),
987 iommus: vec![],
988 }];
989
990 assert_eq!(device_info.assigned_devices, expected);
991 }
992
993 #[test]
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900994 fn device_info_assigned_info() {
995 let mut fdt_data = fs::read(FDT_FILE_PATH).unwrap();
996 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
997 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
998 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
999
Jaewan Kim52477ae2023-11-21 21:20:52 +09001000 let hypervisor = MockHypervisor {
1001 mmio_tokens: [((0x9, 0xFF), 0x12F00000)].into(),
1002 iommu_tokens: [((0x4, 0xFF0), (0x12E40000, 0x3))].into(),
1003 };
1004 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor).unwrap().unwrap();
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001005
1006 let expected = [AssignedDeviceInfo {
1007 node_path: CString::new("/rng").unwrap(),
Jaewan Kim52477ae2023-11-21 21:20:52 +09001008 reg: vec![[0x9, 0xFF].into()],
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001009 interrupts: into_fdt_prop(vec![0x0, 0xF, 0x4]),
Jaewan Kima67e36a2023-11-29 16:50:23 +09001010 iommus: vec![(PvIommu { id: 0x4 }, Vsid(0xFF0))],
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001011 }];
1012
1013 assert_eq!(device_info.assigned_devices, expected);
1014 }
1015
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001016 #[test]
1017 fn device_info_filter() {
1018 let mut fdt_data = fs::read(FDT_FILE_PATH).unwrap();
1019 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1020 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1021 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1022
Jaewan Kim52477ae2023-11-21 21:20:52 +09001023 let hypervisor = MockHypervisor {
1024 mmio_tokens: [((0x9, 0xFF), 0x12F00000)].into(),
1025 iommu_tokens: [((0x4, 0xFF0), (0x12E40000, 0x3))].into(),
1026 };
1027 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor).unwrap().unwrap();
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001028 device_info.filter(vm_dtbo).unwrap();
1029
1030 let vm_dtbo = vm_dtbo.as_mut();
1031
Jaewan Kim371f6c82024-02-24 01:33:37 +09001032 let symbols = vm_dtbo.symbols().unwrap().unwrap();
1033
Jaewan Kima232ed02024-02-25 16:08:14 +00001034 let rng = vm_dtbo.node(cstr!("/fragment@0/__overlay__/rng")).unwrap();
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001035 assert_ne!(rng, None);
Jaewan Kim371f6c82024-02-24 01:33:37 +09001036 let rng_symbol = symbols.getprop_str(cstr!("rng")).unwrap();
Jaewan Kima232ed02024-02-25 16:08:14 +00001037 assert_eq!(Some(cstr!("/fragment@0/__overlay__/rng")), rng_symbol);
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001038
Jaewan Kima232ed02024-02-25 16:08:14 +00001039 let light = vm_dtbo.node(cstr!("/fragment@0/__overlay__/light")).unwrap();
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001040 assert_eq!(light, None);
Jaewan Kim371f6c82024-02-24 01:33:37 +09001041 let light_symbol = symbols.getprop_str(cstr!("light")).unwrap();
1042 assert_eq!(None, light_symbol);
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001043
Jaewan Kima232ed02024-02-25 16:08:14 +00001044 let led = vm_dtbo.node(cstr!("/fragment@0/__overlay__/led")).unwrap();
Jaewan Kima67e36a2023-11-29 16:50:23 +09001045 assert_eq!(led, None);
Jaewan Kim371f6c82024-02-24 01:33:37 +09001046 let led_symbol = symbols.getprop_str(cstr!("led")).unwrap();
1047 assert_eq!(None, led_symbol);
Jaewan Kima67e36a2023-11-29 16:50:23 +09001048
Jaewan Kima232ed02024-02-25 16:08:14 +00001049 let backlight = vm_dtbo.node(cstr!("/fragment@0/__overlay__/bus0/backlight")).unwrap();
Jaewan Kima67e36a2023-11-29 16:50:23 +09001050 assert_eq!(backlight, None);
Jaewan Kim371f6c82024-02-24 01:33:37 +09001051 let backlight_symbol = symbols.getprop_str(cstr!("backlight")).unwrap();
1052 assert_eq!(None, backlight_symbol);
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001053 }
1054
1055 #[test]
1056 fn device_info_patch() {
Jaewan Kima67e36a2023-11-29 16:50:23 +09001057 let mut fdt_data = fs::read(FDT_WITHOUT_IOMMUS_FILE_PATH).unwrap();
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001058 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1059 let mut data = vec![0_u8; fdt_data.len() + vm_dtbo_data.len()];
1060 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1061 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1062 let platform_dt = Fdt::create_empty_tree(data.as_mut_slice()).unwrap();
1063
Jaewan Kim52477ae2023-11-21 21:20:52 +09001064 let hypervisor = MockHypervisor {
1065 mmio_tokens: [((0x9, 0xFF), 0x300)].into(),
1066 iommu_tokens: BTreeMap::new(),
1067 };
1068 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor).unwrap().unwrap();
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001069 device_info.filter(vm_dtbo).unwrap();
1070
1071 // SAFETY: Damaged VM DTBO wouldn't be used after this unsafe block.
1072 unsafe {
1073 platform_dt.apply_overlay(vm_dtbo.as_mut()).unwrap();
1074 }
Jaewan Kim0bd637d2023-11-10 13:09:41 +09001075 device_info.patch(platform_dt).unwrap();
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001076
Jaewan Kimc39974e2023-12-02 01:13:30 +09001077 let rng_node = platform_dt.node(cstr!("/bus0/backlight")).unwrap().unwrap();
1078 let phandle = rng_node.getprop_u32(cstr!("phandle")).unwrap();
1079 assert_ne!(None, phandle);
1080
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001081 // Note: Intentionally not using AssignedDeviceNode for matching all props.
Jaewan Kim0bd637d2023-11-10 13:09:41 +09001082 type FdtResult<T> = libfdt::Result<T>;
1083 let expected: Vec<(FdtResult<&CStr>, FdtResult<Vec<u8>>)> = vec![
Jaewan Kima67e36a2023-11-29 16:50:23 +09001084 (Ok(cstr!("android,backlight,ignore-gctrl-reset")), Ok(Vec::new())),
1085 (Ok(cstr!("compatible")), Ok(Vec::from(*b"android,backlight\0"))),
Jaewan Kim0bd637d2023-11-10 13:09:41 +09001086 (Ok(cstr!("interrupts")), Ok(into_fdt_prop(vec![0x0, 0xF, 0x4]))),
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001087 (Ok(cstr!("iommus")), Ok(Vec::new())),
Jaewan Kimc39974e2023-12-02 01:13:30 +09001088 (Ok(cstr!("phandle")), Ok(into_fdt_prop(vec![phandle.unwrap()]))),
Jaewan Kim0bd637d2023-11-10 13:09:41 +09001089 (Ok(cstr!("reg")), Ok(into_fdt_prop(vec![0x0, 0x9, 0x0, 0xFF]))),
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001090 ];
1091
Jaewan Kim0bd637d2023-11-10 13:09:41 +09001092 let mut properties: Vec<_> = rng_node
1093 .properties()
1094 .unwrap()
1095 .map(|prop| (prop.name(), prop.value().map(|x| x.into())))
1096 .collect();
1097 properties.sort_by(|a, b| {
1098 let lhs = a.0.unwrap_or_default();
1099 let rhs = b.0.unwrap_or_default();
1100 lhs.partial_cmp(rhs).unwrap()
1101 });
1102
1103 assert_eq!(properties, expected);
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001104 }
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001105
1106 #[test]
Jaewan Kimc730ebf2024-02-22 10:34:55 +09001107 fn device_info_patch_no_pviommus() {
1108 let mut fdt_data = fs::read(FDT_WITHOUT_IOMMUS_FILE_PATH).unwrap();
1109 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1110 let mut data = vec![0_u8; fdt_data.len() + vm_dtbo_data.len()];
1111 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1112 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1113 let platform_dt = Fdt::create_empty_tree(data.as_mut_slice()).unwrap();
1114
1115 let hypervisor = MockHypervisor {
1116 mmio_tokens: [((0x9, 0xFF), 0x300)].into(),
1117 iommu_tokens: BTreeMap::new(),
1118 };
1119 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor).unwrap().unwrap();
1120 device_info.filter(vm_dtbo).unwrap();
1121
1122 // SAFETY: Damaged VM DTBO wouldn't be used after this unsafe block.
1123 unsafe {
1124 platform_dt.apply_overlay(vm_dtbo.as_mut()).unwrap();
1125 }
1126 device_info.patch(platform_dt).unwrap();
1127
1128 let compatible = platform_dt.root().next_compatible(cstr!("pkvm,pviommu")).unwrap();
1129 assert_eq!(None, compatible);
1130
1131 if let Some(symbols) = platform_dt.symbols().unwrap() {
1132 for prop in symbols.properties().unwrap() {
1133 let path = CStr::from_bytes_with_nul(prop.value().unwrap()).unwrap();
1134 assert_ne!(None, platform_dt.node(path).unwrap());
1135 }
1136 }
1137 }
1138
1139 #[test]
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001140 fn device_info_overlay_iommu() {
Jaewan Kima67e36a2023-11-29 16:50:23 +09001141 let mut fdt_data = fs::read(FDT_FILE_PATH).unwrap();
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001142 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1143 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1144 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1145 let mut platform_dt_data = pvmfw_fdt_template::RAW.to_vec();
1146 platform_dt_data.resize(pvmfw_fdt_template::RAW.len() * 2, 0);
1147 let platform_dt = Fdt::from_mut_slice(&mut platform_dt_data).unwrap();
1148 platform_dt.unpack().unwrap();
1149
Jaewan Kim52477ae2023-11-21 21:20:52 +09001150 let hypervisor = MockHypervisor {
1151 mmio_tokens: [((0x9, 0xFF), 0x12F00000)].into(),
1152 iommu_tokens: [((0x4, 0xFF0), (0x12E40000, 0x3))].into(),
1153 };
1154 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor).unwrap().unwrap();
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001155 device_info.filter(vm_dtbo).unwrap();
1156
1157 // SAFETY: Damaged VM DTBO wouldn't be used after this unsafe block.
1158 unsafe {
1159 platform_dt.apply_overlay(vm_dtbo.as_mut()).unwrap();
1160 }
1161 device_info.patch(platform_dt).unwrap();
1162
1163 let expected = AssignedDeviceNode {
1164 path: CString::new("/rng").unwrap(),
1165 reg: into_fdt_prop(vec![0x0, 0x9, 0x0, 0xFF]),
1166 interrupts: into_fdt_prop(vec![0x0, 0xF, 0x4]),
Jaewan Kima9200492023-11-21 20:45:31 +09001167 iommus: vec![0x4, 0xFF0],
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001168 };
1169
1170 let node = AssignedDeviceNode::parse(platform_dt, &expected.path);
1171 assert_eq!(node, Ok(expected));
1172
1173 let pviommus = collect_pviommus(platform_dt);
1174 assert_eq!(pviommus, Ok(vec![0x4]));
1175 }
1176
1177 #[test]
1178 fn device_info_multiple_devices_iommus() {
1179 let mut fdt_data = fs::read(FDT_WITH_MULTIPLE_DEVICES_IOMMUS_FILE_PATH).unwrap();
1180 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1181 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1182 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1183 let mut platform_dt_data = pvmfw_fdt_template::RAW.to_vec();
1184 platform_dt_data.resize(pvmfw_fdt_template::RAW.len() * 2, 0);
1185 let platform_dt = Fdt::from_mut_slice(&mut platform_dt_data).unwrap();
1186 platform_dt.unpack().unwrap();
1187
Jaewan Kim52477ae2023-11-21 21:20:52 +09001188 let hypervisor = MockHypervisor {
1189 mmio_tokens: [
1190 ((0x9, 0xFF), 0x12F00000),
Jaewan Kim19b984f2023-12-04 15:16:50 +09001191 ((0x10000, 0x1000), 0xF00000),
1192 ((0x20000, 0x1000), 0xF10000),
Jaewan Kim52477ae2023-11-21 21:20:52 +09001193 ]
1194 .into(),
1195 iommu_tokens: [
1196 ((0x4, 0xFF0), (0x12E40000, 3)),
1197 ((0x40, 0xFFA), (0x40000, 0x4)),
1198 ((0x50, 0xFFB), (0x50000, 0x5)),
1199 ]
1200 .into(),
1201 };
1202 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor).unwrap().unwrap();
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001203 device_info.filter(vm_dtbo).unwrap();
1204
1205 // SAFETY: Damaged VM DTBO wouldn't be used after this unsafe block.
1206 unsafe {
1207 platform_dt.apply_overlay(vm_dtbo.as_mut()).unwrap();
1208 }
1209 device_info.patch(platform_dt).unwrap();
1210
1211 let expected_devices = [
1212 AssignedDeviceNode {
1213 path: CString::new("/rng").unwrap(),
1214 reg: into_fdt_prop(vec![0x0, 0x9, 0x0, 0xFF]),
1215 interrupts: into_fdt_prop(vec![0x0, 0xF, 0x4]),
Jaewan Kima67e36a2023-11-29 16:50:23 +09001216 iommus: vec![0x4, 0xFF0],
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001217 },
1218 AssignedDeviceNode {
1219 path: CString::new("/light").unwrap(),
Jaewan Kim19b984f2023-12-04 15:16:50 +09001220 reg: into_fdt_prop(vec![0x0, 0x10000, 0x0, 0x1000, 0x0, 0x20000, 0x0, 0x1000]),
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001221 interrupts: into_fdt_prop(vec![0x0, 0xF, 0x5]),
Jaewan Kima67e36a2023-11-29 16:50:23 +09001222 iommus: vec![0x40, 0xFFA, 0x50, 0xFFB],
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001223 },
1224 ];
1225
1226 for expected in expected_devices {
1227 let node = AssignedDeviceNode::parse(platform_dt, &expected.path);
1228 assert_eq!(node, Ok(expected));
1229 }
1230 let pviommus = collect_pviommus(platform_dt);
Jaewan Kima67e36a2023-11-29 16:50:23 +09001231 assert_eq!(pviommus, Ok(vec![0x4, 0x40, 0x50]));
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001232 }
1233
1234 #[test]
1235 fn device_info_iommu_sharing() {
1236 let mut fdt_data = fs::read(FDT_WITH_IOMMU_SHARING).unwrap();
1237 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1238 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1239 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1240 let mut platform_dt_data = pvmfw_fdt_template::RAW.to_vec();
1241 platform_dt_data.resize(pvmfw_fdt_template::RAW.len() * 2, 0);
1242 let platform_dt = Fdt::from_mut_slice(&mut platform_dt_data).unwrap();
1243 platform_dt.unpack().unwrap();
1244
Jaewan Kim52477ae2023-11-21 21:20:52 +09001245 let hypervisor = MockHypervisor {
Jaewan Kim19b984f2023-12-04 15:16:50 +09001246 mmio_tokens: [((0x9, 0xFF), 0x12F00000), ((0x1000, 0x9), 0x12000000)].into(),
1247 iommu_tokens: [((0x4, 0xFF0), (0x12E40000, 3)), ((0x4, 0xFF1), (0x12E40000, 9))].into(),
Jaewan Kim52477ae2023-11-21 21:20:52 +09001248 };
1249 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor).unwrap().unwrap();
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001250 device_info.filter(vm_dtbo).unwrap();
1251
1252 // SAFETY: Damaged VM DTBO wouldn't be used after this unsafe block.
1253 unsafe {
1254 platform_dt.apply_overlay(vm_dtbo.as_mut()).unwrap();
1255 }
1256 device_info.patch(platform_dt).unwrap();
1257
1258 let expected_devices = [
1259 AssignedDeviceNode {
1260 path: CString::new("/rng").unwrap(),
1261 reg: into_fdt_prop(vec![0x0, 0x9, 0x0, 0xFF]),
1262 interrupts: into_fdt_prop(vec![0x0, 0xF, 0x4]),
Jaewan Kima67e36a2023-11-29 16:50:23 +09001263 iommus: vec![0x4, 0xFF0],
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001264 },
1265 AssignedDeviceNode {
Jaewan Kima67e36a2023-11-29 16:50:23 +09001266 path: CString::new("/led").unwrap(),
Jaewan Kim19b984f2023-12-04 15:16:50 +09001267 reg: into_fdt_prop(vec![0x0, 0x1000, 0x0, 0x9]),
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001268 interrupts: into_fdt_prop(vec![0x0, 0xF, 0x5]),
Jaewan Kim19b984f2023-12-04 15:16:50 +09001269 iommus: vec![0x4, 0xFF1],
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001270 },
1271 ];
1272
1273 for expected in expected_devices {
1274 let node = AssignedDeviceNode::parse(platform_dt, &expected.path);
1275 assert_eq!(node, Ok(expected));
1276 }
1277
1278 let pviommus = collect_pviommus(platform_dt);
Jaewan Kima67e36a2023-11-29 16:50:23 +09001279 assert_eq!(pviommus, Ok(vec![0x4]));
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001280 }
1281
1282 #[test]
1283 fn device_info_iommu_id_conflict() {
1284 let mut fdt_data = fs::read(FDT_WITH_IOMMU_ID_CONFLICT).unwrap();
1285 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1286 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1287 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1288
Jaewan Kim52477ae2023-11-21 21:20:52 +09001289 let hypervisor = MockHypervisor {
Jaewan Kim19b984f2023-12-04 15:16:50 +09001290 mmio_tokens: [((0x9, 0xFF), 0x300)].into(),
Jaewan Kim52477ae2023-11-21 21:20:52 +09001291 iommu_tokens: [((0x4, 0xFF0), (0x12E40000, 0x3))].into(),
1292 };
1293 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor);
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001294
1295 assert_eq!(device_info, Err(DeviceAssignmentError::DuplicatedPvIommuIds));
1296 }
Jaewan Kim52477ae2023-11-21 21:20:52 +09001297
1298 #[test]
1299 fn device_info_invalid_reg() {
1300 let mut fdt_data = fs::read(FDT_FILE_PATH).unwrap();
1301 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1302 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1303 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1304
1305 let hypervisor = MockHypervisor {
1306 mmio_tokens: BTreeMap::new(),
1307 iommu_tokens: [((0x4, 0xFF0), (0x12E40000, 0x3))].into(),
1308 };
1309 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor);
1310
1311 assert_eq!(device_info, Err(DeviceAssignmentError::InvalidReg));
1312 }
1313
1314 #[test]
Jaewan Kim19b984f2023-12-04 15:16:50 +09001315 fn device_info_invalid_reg_out_of_order() {
1316 let mut fdt_data = fs::read(FDT_WITH_MULTIPLE_REG_IOMMU_FILE_PATH).unwrap();
1317 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1318 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1319 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1320
1321 let hypervisor = MockHypervisor {
1322 mmio_tokens: [((0xF000, 0x1000), 0xF10000), ((0xF100, 0x1000), 0xF00000)].into(),
1323 iommu_tokens: [((0xFF0, 0xF0), (0x40000, 0x4)), ((0xFF1, 0xF1), (0x50000, 0x5))].into(),
1324 };
1325 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor);
1326
1327 assert_eq!(device_info, Err(DeviceAssignmentError::InvalidReg));
1328 }
1329
1330 #[test]
Jaewan Kim52477ae2023-11-21 21:20:52 +09001331 fn device_info_invalid_iommus() {
1332 let mut fdt_data = fs::read(FDT_FILE_PATH).unwrap();
1333 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1334 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1335 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1336
1337 let hypervisor = MockHypervisor {
1338 mmio_tokens: [((0x9, 0xFF), 0x12F00000)].into(),
1339 iommu_tokens: BTreeMap::new(),
1340 };
1341 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor);
1342
1343 assert_eq!(device_info, Err(DeviceAssignmentError::InvalidIommus));
1344 }
Jaewan Kim19b984f2023-12-04 15:16:50 +09001345
1346 #[test]
1347 fn device_info_duplicated_pv_iommus() {
1348 let mut fdt_data = fs::read(FDT_WITH_DUPLICATED_PVIOMMUS_FILE_PATH).unwrap();
1349 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1350 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1351 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1352
1353 let hypervisor = MockHypervisor {
1354 mmio_tokens: [((0x10000, 0x1000), 0xF00000), ((0x20000, 0xFF), 0xF10000)].into(),
1355 iommu_tokens: [((0xFF, 0xF), (0x40000, 0x4))].into(),
1356 };
1357 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor);
1358
1359 assert_eq!(device_info, Err(DeviceAssignmentError::DuplicatedPvIommuIds));
1360 }
1361
1362 #[test]
1363 fn device_info_duplicated_iommus() {
1364 let mut fdt_data = fs::read(FDT_FILE_PATH).unwrap();
1365 let mut vm_dtbo_data = fs::read(VM_DTBO_WITH_DUPLICATED_IOMMUS_FILE_PATH).unwrap();
1366 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1367 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1368
1369 let hypervisor = MockHypervisor {
1370 mmio_tokens: [((0x10000, 0x1000), 0xF00000), ((0x20000, 0xFF), 0xF10000)].into(),
1371 iommu_tokens: [((0xFF, 0xF), (0x40000, 0x4))].into(),
1372 };
1373 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor);
1374
1375 assert_eq!(device_info, Err(DeviceAssignmentError::UnsupportedIommusDuplication));
1376 }
1377
1378 #[test]
1379 fn device_info_duplicated_iommu_mapping() {
1380 let mut fdt_data = fs::read(FDT_WITH_MULTIPLE_REG_IOMMU_FILE_PATH).unwrap();
1381 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1382 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1383 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1384
1385 let hypervisor = MockHypervisor {
1386 mmio_tokens: [((0xF000, 0x1000), 0xF00000), ((0xF100, 0x1000), 0xF10000)].into(),
1387 iommu_tokens: [((0xFF0, 0xF0), (0x40000, 0x4)), ((0xFF1, 0xF1), (0x40000, 0x4))].into(),
1388 };
1389 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor);
1390
1391 assert_eq!(device_info, Err(DeviceAssignmentError::InvalidIommus));
1392 }
Jaewan Kim50246682024-03-11 23:18:54 +09001393
1394 #[test]
1395 fn device_assignment_clean() {
1396 let mut platform_dt_data = pvmfw_fdt_template::RAW.to_vec();
1397 let platform_dt = Fdt::from_mut_slice(&mut platform_dt_data).unwrap();
1398
1399 let compatible = platform_dt.root().next_compatible(cstr!("pkvm,pviommu"));
1400 assert_ne!(None, compatible.unwrap());
1401
1402 clean(platform_dt).unwrap();
1403
1404 let compatible = platform_dt.root().next_compatible(cstr!("pkvm,pviommu"));
1405 assert_eq!(Ok(None), compatible);
1406 }
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001407}