blob: 9c3e566779cd75a269d0ecaeaf11b2537bcddbb5 [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 Kimc6e023b2023-10-12 15:11:05 +090076 /// Unsupported overlay target syntax. Only supports <target-path> with full path.
77 UnsupportedOverlayTarget,
Jaewan Kim19b984f2023-12-04 15:16:50 +090078 /// Unsupported PhysIommu,
79 UnsupportedPhysIommu,
80 /// Unsupported (pvIOMMU id, vSID) duplication. Currently the pair should be unique.
81 UnsupportedPvIommusDuplication,
82 /// Unsupported (IOMMU token, SID) duplication. Currently the pair should be unique.
83 UnsupportedIommusDuplication,
Jaewan Kim51ccfed2023-11-08 13:51:58 +090084 /// Internal error
85 Internal,
Jaewan Kimc6e023b2023-10-12 15:11:05 +090086 /// Unexpected error from libfdt
87 UnexpectedFdtError(FdtError),
88}
89
90impl From<FdtError> for DeviceAssignmentError {
91 fn from(e: FdtError) -> Self {
92 DeviceAssignmentError::UnexpectedFdtError(e)
93 }
94}
95
96impl fmt::Display for DeviceAssignmentError {
97 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
98 match self {
99 Self::InvalidDtbo => write!(f, "Invalid DTBO"),
100 Self::InvalidSymbols => write!(
101 f,
102 "Invalid property in /__symbols__. Must point to valid assignable device node."
103 ),
Jaewan Kim19b984f2023-12-04 15:16:50 +0900104 Self::MalformedReg => write!(f, "Malformed <reg>. Can't parse"),
105 Self::InvalidReg => write!(f, "Invalid <reg>. Failed to validate with hypervisor"),
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900106 Self::InvalidInterrupts => write!(f, "Invalid <interrupts>"),
Jaewan Kim19b984f2023-12-04 15:16:50 +0900107 Self::MalformedIommus => write!(f, "Malformed <iommus>. Can't parse."),
108 Self::InvalidIommus => {
109 write!(f, "Invalid <iommus>. Failed to validate with hypervisor")
110 }
111 Self::InvalidPhysIommu => write!(f, "Invalid phys IOMMU node"),
Jaewan Kima9200492023-11-21 20:45:31 +0900112 Self::InvalidPvIommu => write!(f, "Invalid pvIOMMU node"),
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900113 Self::TooManyPvIommu => write!(
114 f,
115 "Too many pvIOMMU node. Insufficient pre-populated pvIOMMUs in platform DT"
116 ),
Jaewan Kim19b984f2023-12-04 15:16:50 +0900117 Self::DuplicatedIommuIds => {
118 write!(f, "Duplicated IOMMU IDs exist. IDs must unique among iommu node")
119 }
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900120 Self::DuplicatedPvIommuIds => {
Jaewan Kim19b984f2023-12-04 15:16:50 +0900121 write!(f, "Duplicated pvIOMMU IDs exist. IDs must unique among iommu node")
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900122 }
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900123 Self::UnsupportedOverlayTarget => {
124 write!(f, "Unsupported overlay target. Only supports 'target-path = \"/\"'")
125 }
Jaewan Kim19b984f2023-12-04 15:16:50 +0900126 Self::UnsupportedPhysIommu => {
127 write!(f, "Unsupported Phys IOMMU. Currently only supports #iommu-cells = <1>")
128 }
129 Self::UnsupportedPvIommusDuplication => {
130 write!(f, "Unsupported (pvIOMMU id, vSID) duplication. Currently the pair should be unique.")
131 }
132 Self::UnsupportedIommusDuplication => {
133 write!(f, "Unsupported (IOMMU token, SID) duplication. Currently the pair should be unique.")
134 }
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900135 Self::Internal => write!(f, "Internal error"),
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900136 Self::UnexpectedFdtError(e) => write!(f, "Unexpected Error from libfdt: {e}"),
137 }
138 }
139}
140
141pub type Result<T> = core::result::Result<T, DeviceAssignmentError>;
142
143/// Represents VM DTBO
144#[repr(transparent)]
145pub struct VmDtbo(Fdt);
146
147impl VmDtbo {
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900148 /// Wraps a mutable slice containing a VM DTBO.
149 ///
150 /// Fails if the VM DTBO does not pass validation.
151 pub fn from_mut_slice(dtbo: &mut [u8]) -> Result<&mut Self> {
152 // This validates DTBO
153 let fdt = Fdt::from_mut_slice(dtbo)?;
154 // SAFETY: VmDtbo is a transparent wrapper around Fdt, so representation is the same.
155 Ok(unsafe { mem::transmute::<&mut Fdt, &mut Self>(fdt) })
156 }
157
158 // Locates device node path as if the given dtbo node path is assigned and VM DTBO is overlaid.
159 // For given dtbo node path, this concatenates <target-path> of the enclosing fragment and
160 // relative path from __overlay__ node.
161 //
162 // Here's an example with sample VM DTBO:
163 // / {
164 // fragment@rng {
165 // target-path = "/"; // Always 'target-path = "/"'. Disallows <target> or other path.
166 // __overlay__ {
167 // rng { ... }; // Actual device node is here. If overlaid, path would be "/rng"
168 // };
169 // };
Jaewan Kim80ef9fa2024-02-25 16:08:14 +0000170 // __symbols__ { // Contains list of assignable devices
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900171 // rng = "/fragment@rng/__overlay__/rng";
172 // };
173 // };
174 //
175 // Then locate_overlay_target_path(cstr!("/fragment@rng/__overlay__/rng")) is Ok("/rng")
176 //
177 // Contrary to fdt_overlay_target_offset(), this API enforces overlay target property
178 // 'target-path = "/"', so the overlay doesn't modify and/or append platform DT's existing
179 // node and/or properties. The enforcement is for compatibility reason.
Jaewan Kim19b984f2023-12-04 15:16:50 +0900180 fn locate_overlay_target_path(
181 &self,
182 dtbo_node_path: &CStr,
183 dtbo_node: &FdtNode,
184 ) -> Result<CString> {
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900185 let dtbo_node_path_bytes = dtbo_node_path.to_bytes();
186 if dtbo_node_path_bytes.first() != Some(&b'/') {
187 return Err(DeviceAssignmentError::UnsupportedOverlayTarget);
188 }
189
Jaewan Kim19b984f2023-12-04 15:16:50 +0900190 let fragment_node = dtbo_node.supernode_at_depth(1)?;
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900191 let target_path = fragment_node
Pierre-Clément Tosid701a0b2023-11-07 15:38:59 +0000192 .getprop_str(cstr!("target-path"))?
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900193 .ok_or(DeviceAssignmentError::InvalidDtbo)?;
194 if target_path != cstr!("/") {
195 return Err(DeviceAssignmentError::UnsupportedOverlayTarget);
196 }
197
198 let mut components = dtbo_node_path_bytes
199 .split(|char| *char == b'/')
200 .filter(|&component| !component.is_empty())
201 .skip(1);
202 let overlay_node_name = components.next();
Pierre-Clément Tosid701a0b2023-11-07 15:38:59 +0000203 if overlay_node_name != Some(b"__overlay__") {
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900204 return Err(DeviceAssignmentError::InvalidDtbo);
205 }
206 let mut overlaid_path = Vec::with_capacity(dtbo_node_path_bytes.len());
207 for component in components {
208 overlaid_path.push(b'/');
209 overlaid_path.extend_from_slice(component);
210 }
211 overlaid_path.push(b'\0');
212
213 Ok(CString::from_vec_with_nul(overlaid_path).unwrap())
214 }
Jaewan Kim19b984f2023-12-04 15:16:50 +0900215
216 fn parse_physical_iommus(physical_node: &FdtNode) -> Result<BTreeMap<Phandle, PhysIommu>> {
217 let mut phys_iommus = BTreeMap::new();
218 for (node, _) in physical_node.descendants() {
219 let Some(phandle) = node.get_phandle()? else {
220 continue; // Skips unreachable IOMMU node
221 };
222 let Some(iommu) = PhysIommu::parse(&node)? else {
223 continue; // Skip if not a PhysIommu.
224 };
225 if phys_iommus.insert(phandle, iommu).is_some() {
226 return Err(FdtError::BadPhandle.into());
227 }
228 }
229 Self::validate_physical_iommus(&phys_iommus)?;
230 Ok(phys_iommus)
231 }
232
233 fn validate_physical_iommus(phys_iommus: &BTreeMap<Phandle, PhysIommu>) -> Result<()> {
234 let unique_iommus: BTreeSet<_> = phys_iommus.values().cloned().collect();
235 if phys_iommus.len() != unique_iommus.len() {
236 return Err(DeviceAssignmentError::DuplicatedIommuIds);
237 }
238 Ok(())
239 }
240
241 fn validate_physical_devices(
242 physical_devices: &BTreeMap<Phandle, PhysicalDeviceInfo>,
243 ) -> Result<()> {
244 // Only need to validate iommus because <reg> will be validated together with PV <reg>
245 // see: DeviceAssignmentInfo::validate_all_regs().
246 let mut all_iommus = BTreeSet::new();
247 for physical_device in physical_devices.values() {
248 for iommu in &physical_device.iommus {
249 if !all_iommus.insert(iommu) {
250 error!("Unsupported phys IOMMU duplication found, <iommus> = {iommu:?}");
251 return Err(DeviceAssignmentError::UnsupportedIommusDuplication);
252 }
253 }
254 }
255 Ok(())
256 }
257
258 fn parse_physical_devices_with_iommus(
259 physical_node: &FdtNode,
260 phys_iommus: &BTreeMap<Phandle, PhysIommu>,
261 ) -> Result<BTreeMap<Phandle, PhysicalDeviceInfo>> {
262 let mut physical_devices = BTreeMap::new();
263 for (node, _) in physical_node.descendants() {
264 let Some(info) = PhysicalDeviceInfo::parse(&node, phys_iommus)? else {
265 continue;
266 };
267 if physical_devices.insert(info.target, info).is_some() {
268 return Err(DeviceAssignmentError::InvalidDtbo);
269 }
270 }
271 Self::validate_physical_devices(&physical_devices)?;
272 Ok(physical_devices)
273 }
274
275 /// Parses Physical devices in VM DTBO
276 fn parse_physical_devices(&self) -> Result<BTreeMap<Phandle, PhysicalDeviceInfo>> {
277 let Some(physical_node) = self.as_ref().node(cstr!("/host"))? else {
278 return Ok(BTreeMap::new());
279 };
280
281 let phys_iommus = Self::parse_physical_iommus(&physical_node)?;
282 Self::parse_physical_devices_with_iommus(&physical_node, &phys_iommus)
283 }
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900284}
285
Jaewan Kimc39974e2023-12-02 01:13:30 +0900286fn is_overlayable_node(dtbo_path: &CStr) -> bool {
287 dtbo_path
288 .to_bytes()
289 .split(|char| *char == b'/')
290 .filter(|&component| !component.is_empty())
291 .nth(1)
292 .map_or(false, |name| name == b"__overlay__")
293}
294
Jaewan Kimc730ebf2024-02-22 10:34:55 +0900295fn filter_dangling_symbols(fdt: &mut Fdt) -> Result<()> {
296 if let Some(symbols) = fdt.symbols()? {
297 let mut removed = vec![];
298 for prop in symbols.properties()? {
299 let path = CStr::from_bytes_with_nul(prop.value()?)
300 .map_err(|_| DeviceAssignmentError::Internal)?;
301 if fdt.node(path)?.is_none() {
302 let name = prop.name()?;
303 removed.push(CString::from(name));
304 }
305 }
306
307 let mut symbols = fdt.symbols_mut()?.unwrap();
308 for name in removed {
309 symbols.nop_property(&name)?;
310 }
311 }
312 Ok(())
313}
314
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900315impl AsRef<Fdt> for VmDtbo {
316 fn as_ref(&self) -> &Fdt {
317 &self.0
318 }
319}
320
321impl AsMut<Fdt> for VmDtbo {
322 fn as_mut(&mut self) -> &mut Fdt {
323 &mut self.0
324 }
325}
326
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900327#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
328struct PvIommu {
329 // ID from pvIOMMU node
330 id: u32,
331}
332
333impl PvIommu {
334 fn parse(node: &FdtNode) -> Result<Self> {
Jaewan Kima9200492023-11-21 20:45:31 +0900335 let iommu_cells = node
336 .getprop_u32(cstr!("#iommu-cells"))?
337 .ok_or(DeviceAssignmentError::InvalidPvIommu)?;
Jaewan Kim19b984f2023-12-04 15:16:50 +0900338 // Ensures #iommu-cells = <1>. It means that `<iommus>` entry contains pair of
Jaewan Kima9200492023-11-21 20:45:31 +0900339 // (pvIOMMU ID, vSID)
340 if iommu_cells != 1 {
341 return Err(DeviceAssignmentError::InvalidPvIommu);
342 }
343 let id = node.getprop_u32(cstr!("id"))?.ok_or(DeviceAssignmentError::InvalidPvIommu)?;
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900344 Ok(Self { id })
345 }
346}
347
Jaewan Kima9200492023-11-21 20:45:31 +0900348#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
349struct Vsid(u32);
350
Jaewan Kim19b984f2023-12-04 15:16:50 +0900351#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
352struct Sid(u64);
353
354impl From<u32> for Sid {
355 fn from(sid: u32) -> Self {
356 Self(sid.into())
357 }
358}
359
360#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
Jaewan Kim52477ae2023-11-21 21:20:52 +0900361struct DeviceReg {
362 addr: u64,
363 size: u64,
364}
365
366impl TryFrom<Reg<u64>> for DeviceReg {
367 type Error = DeviceAssignmentError;
368
369 fn try_from(reg: Reg<u64>) -> Result<Self> {
Jaewan Kim19b984f2023-12-04 15:16:50 +0900370 Ok(Self { addr: reg.addr, size: reg.size.ok_or(DeviceAssignmentError::MalformedReg)? })
Jaewan Kim52477ae2023-11-21 21:20:52 +0900371 }
372}
373
374fn parse_node_reg(node: &FdtNode) -> Result<Vec<DeviceReg>> {
375 node.reg()?
Jaewan Kim19b984f2023-12-04 15:16:50 +0900376 .ok_or(DeviceAssignmentError::MalformedReg)?
Jaewan Kim52477ae2023-11-21 21:20:52 +0900377 .map(DeviceReg::try_from)
378 .collect::<Result<Vec<_>>>()
379}
380
381fn to_be_bytes(reg: &[DeviceReg]) -> Vec<u8> {
382 let mut reg_cells = vec![];
383 for x in reg {
384 reg_cells.extend_from_slice(&x.addr.to_be_bytes());
385 reg_cells.extend_from_slice(&x.size.to_be_bytes());
386 }
387 reg_cells
388}
389
Jaewan Kim19b984f2023-12-04 15:16:50 +0900390#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
391struct PhysIommu {
392 token: u64,
393}
394
395impl PhysIommu {
396 fn parse(node: &FdtNode) -> Result<Option<Self>> {
397 let Some(token) = node.getprop_u64(cstr!("android,pvmfw,token"))? else {
398 return Ok(None);
399 };
400 let Some(iommu_cells) = node.getprop_u32(cstr!("#iommu-cells"))? else {
401 return Err(DeviceAssignmentError::InvalidPhysIommu);
402 };
403 // Currently only supports #iommu-cells = <1>.
404 // In that case `<iommus>` entry contains pair of (pIOMMU phandle, Sid token)
405 if iommu_cells != 1 {
406 return Err(DeviceAssignmentError::UnsupportedPhysIommu);
407 }
408 Ok(Some(Self { token }))
409 }
410}
411
412#[derive(Debug)]
413struct PhysicalDeviceInfo {
414 target: Phandle,
415 reg: Vec<DeviceReg>,
416 iommus: Vec<(PhysIommu, Sid)>,
417}
418
419impl PhysicalDeviceInfo {
420 fn parse_iommus(
421 node: &FdtNode,
422 phys_iommus: &BTreeMap<Phandle, PhysIommu>,
423 ) -> Result<Vec<(PhysIommu, Sid)>> {
424 let mut iommus = vec![];
425 let Some(mut cells) = node.getprop_cells(cstr!("iommus"))? else {
426 return Ok(iommus);
427 };
428 while let Some(cell) = cells.next() {
429 // Parse pIOMMU ID
430 let phandle =
431 Phandle::try_from(cell).or(Err(DeviceAssignmentError::MalformedIommus))?;
432 let iommu = phys_iommus.get(&phandle).ok_or(DeviceAssignmentError::MalformedIommus)?;
433
434 // Parse Sid
435 let Some(cell) = cells.next() else {
436 return Err(DeviceAssignmentError::MalformedIommus);
437 };
438
439 iommus.push((*iommu, Sid::from(cell)));
440 }
441 Ok(iommus)
442 }
443
444 fn parse(node: &FdtNode, phys_iommus: &BTreeMap<Phandle, PhysIommu>) -> Result<Option<Self>> {
445 let Some(phandle) = node.getprop_u32(cstr!("android,pvmfw,target"))? else {
446 return Ok(None);
447 };
448 let target = Phandle::try_from(phandle)?;
449 let reg = parse_node_reg(node)?;
450 let iommus = Self::parse_iommus(node, phys_iommus)?;
451 Ok(Some(Self { target, reg, iommus }))
452 }
453}
454
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900455/// Assigned device information parsed from crosvm DT.
456/// Keeps everything in the owned data because underlying FDT will be reused for platform DT.
457#[derive(Debug, Eq, PartialEq)]
458struct AssignedDeviceInfo {
459 // Node path of assigned device (e.g. "/rng")
460 node_path: CString,
461 // DTBO node path of the assigned device (e.g. "/fragment@rng/__overlay__/rng")
462 dtbo_node_path: CString,
463 // <reg> property from the crosvm DT
Jaewan Kim52477ae2023-11-21 21:20:52 +0900464 reg: Vec<DeviceReg>,
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900465 // <interrupts> property from the crosvm DT
466 interrupts: Vec<u8>,
Jaewan Kima9200492023-11-21 20:45:31 +0900467 // Parsed <iommus> property from the crosvm DT. Tuple of PvIommu and vSID.
468 iommus: Vec<(PvIommu, Vsid)>,
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900469}
470
471impl AssignedDeviceInfo {
Jaewan Kim19b984f2023-12-04 15:16:50 +0900472 fn validate_reg(
473 device_reg: &[DeviceReg],
474 physical_device_reg: &[DeviceReg],
Jaewan Kim52477ae2023-11-21 21:20:52 +0900475 hypervisor: &dyn DeviceAssigningHypervisor,
Jaewan Kim19b984f2023-12-04 15:16:50 +0900476 ) -> Result<()> {
477 if device_reg.len() != physical_device_reg.len() {
478 return Err(DeviceAssignmentError::InvalidReg);
479 }
480 // PV reg and physical reg should have 1:1 match in order.
481 for (reg, phys_reg) in device_reg.iter().zip(physical_device_reg.iter()) {
482 let addr = hypervisor.get_phys_mmio_token(reg.addr, reg.size).map_err(|e| {
483 error!("Failed to validate device <reg>, error={e:?}, reg={reg:x?}");
Jaewan Kim52477ae2023-11-21 21:20:52 +0900484 DeviceAssignmentError::InvalidReg
485 })?;
Jaewan Kim19b984f2023-12-04 15:16:50 +0900486 // Only check address because hypervisor guaranatees size match when success.
487 if phys_reg.addr != addr {
488 error!("Failed to validate device <reg>. No matching phys reg for reg={reg:x?}");
489 return Err(DeviceAssignmentError::InvalidReg);
490 }
Jaewan Kim52477ae2023-11-21 21:20:52 +0900491 }
Jaewan Kim19b984f2023-12-04 15:16:50 +0900492 Ok(())
Jaewan Kim52477ae2023-11-21 21:20:52 +0900493 }
494
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900495 fn parse_interrupts(node: &FdtNode) -> Result<Vec<u8>> {
496 // Validation: Validate if interrupts cell numbers are multiple of #interrupt-cells.
497 // We can't know how many interrupts would exist.
498 let interrupts_cells = node
Pierre-Clément Tosid701a0b2023-11-07 15:38:59 +0000499 .getprop_cells(cstr!("interrupts"))?
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900500 .ok_or(DeviceAssignmentError::InvalidInterrupts)?
501 .count();
502 if interrupts_cells % CELLS_PER_INTERRUPT != 0 {
503 return Err(DeviceAssignmentError::InvalidInterrupts);
504 }
505
506 // Once validated, keep the raw bytes so patch can be done with setprop()
Pierre-Clément Tosid701a0b2023-11-07 15:38:59 +0000507 Ok(node.getprop(cstr!("interrupts")).unwrap().unwrap().into())
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900508 }
509
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900510 // TODO(b/277993056): Also validate /__local_fixups__ to ensure that <iommus> has phandle.
Jaewan Kima9200492023-11-21 20:45:31 +0900511 fn parse_iommus(
512 node: &FdtNode,
513 pviommus: &BTreeMap<Phandle, PvIommu>,
514 ) -> Result<Vec<(PvIommu, Vsid)>> {
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900515 let mut iommus = vec![];
Jaewan Kima9200492023-11-21 20:45:31 +0900516 let Some(mut cells) = node.getprop_cells(cstr!("iommus"))? else {
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900517 return Ok(iommus);
518 };
Jaewan Kima9200492023-11-21 20:45:31 +0900519 while let Some(cell) = cells.next() {
520 // Parse pvIOMMU ID
Jaewan Kim19b984f2023-12-04 15:16:50 +0900521 let phandle =
522 Phandle::try_from(cell).or(Err(DeviceAssignmentError::MalformedIommus))?;
523 let pviommu = pviommus.get(&phandle).ok_or(DeviceAssignmentError::MalformedIommus)?;
Jaewan Kima9200492023-11-21 20:45:31 +0900524
525 // Parse vSID
526 let Some(cell) = cells.next() else {
Jaewan Kim19b984f2023-12-04 15:16:50 +0900527 return Err(DeviceAssignmentError::MalformedIommus);
Jaewan Kima9200492023-11-21 20:45:31 +0900528 };
529 let vsid = Vsid(cell);
530
531 iommus.push((*pviommu, vsid));
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900532 }
533 Ok(iommus)
534 }
535
Jaewan Kim19b984f2023-12-04 15:16:50 +0900536 fn validate_iommus(
537 iommus: &[(PvIommu, Vsid)],
538 physical_device_iommu: &[(PhysIommu, Sid)],
539 hypervisor: &dyn DeviceAssigningHypervisor,
540 ) -> Result<()> {
541 if iommus.len() != physical_device_iommu.len() {
542 return Err(DeviceAssignmentError::InvalidIommus);
543 }
544 // pvIOMMU can be reordered, and hypervisor may not guarantee 1:1 mapping.
545 // So we need to mark what's matched or not.
546 let mut physical_device_iommu = physical_device_iommu.to_vec();
547 for (pviommu, vsid) in iommus {
548 let (id, sid) = hypervisor.get_phys_iommu_token(pviommu.id.into(), vsid.0.into())
549 .map_err(|e| {
550 error!("Failed to validate device <iommus>, error={e:?}, pviommu={pviommu:?}, vsid={vsid:?}");
551 DeviceAssignmentError::InvalidIommus
552 })?;
553
554 let pos = physical_device_iommu
555 .iter()
556 .position(|(phys_iommu, phys_sid)| (phys_iommu.token, phys_sid.0) == (id, sid));
557 match pos {
558 Some(pos) => physical_device_iommu.remove(pos),
559 None => {
560 error!("Failed to validate device <iommus>. No matching phys iommu or duplicated mapping for pviommu={pviommu:?}, vsid={vsid:?}");
561 return Err(DeviceAssignmentError::InvalidIommus);
562 }
563 };
564 }
565 Ok(())
566 }
567
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900568 fn parse(
569 fdt: &Fdt,
570 vm_dtbo: &VmDtbo,
571 dtbo_node_path: &CStr,
Jaewan Kim19b984f2023-12-04 15:16:50 +0900572 physical_devices: &BTreeMap<Phandle, PhysicalDeviceInfo>,
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900573 pviommus: &BTreeMap<Phandle, PvIommu>,
Jaewan Kim52477ae2023-11-21 21:20:52 +0900574 hypervisor: &dyn DeviceAssigningHypervisor,
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900575 ) -> Result<Option<Self>> {
Jaewan Kim19b984f2023-12-04 15:16:50 +0900576 let dtbo_node =
577 vm_dtbo.as_ref().node(dtbo_node_path)?.ok_or(DeviceAssignmentError::InvalidSymbols)?;
578 let node_path = vm_dtbo.locate_overlay_target_path(dtbo_node_path, &dtbo_node)?;
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900579
580 let Some(node) = fdt.node(&node_path)? else { return Ok(None) };
581
Jaewan Kim80ef9fa2024-02-25 16:08:14 +0000582 // Currently can only assign devices backed by physical devices.
Jaewan Kim19b984f2023-12-04 15:16:50 +0900583 let phandle = dtbo_node.get_phandle()?.ok_or(DeviceAssignmentError::InvalidDtbo)?;
Jaewan Kim80ef9fa2024-02-25 16:08:14 +0000584 let Some(physical_device) = physical_devices.get(&phandle) else {
585 // If labeled DT node isn't backed by physical device node, then just return None.
586 // It's not an error because such node can be a dependency of assignable device nodes.
587 return Ok(None);
588 };
Jaewan Kim19b984f2023-12-04 15:16:50 +0900589
590 let reg = parse_node_reg(&node)?;
591 Self::validate_reg(&reg, &physical_device.reg, hypervisor)?;
592
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900593 let interrupts = Self::parse_interrupts(&node)?;
Jaewan Kim19b984f2023-12-04 15:16:50 +0900594
595 let iommus = Self::parse_iommus(&node, pviommus)?;
596 Self::validate_iommus(&iommus, &physical_device.iommus, hypervisor)?;
597
Jaewan Kim52477ae2023-11-21 21:20:52 +0900598 Ok(Some(Self { node_path, dtbo_node_path: dtbo_node_path.into(), reg, interrupts, iommus }))
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900599 }
600
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900601 fn patch(&self, fdt: &mut Fdt, pviommu_phandles: &BTreeMap<PvIommu, Phandle>) -> Result<()> {
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900602 let mut dst = fdt.node_mut(&self.node_path)?.unwrap();
Jaewan Kim52477ae2023-11-21 21:20:52 +0900603 dst.setprop(cstr!("reg"), &to_be_bytes(&self.reg))?;
Pierre-Clément Tosid701a0b2023-11-07 15:38:59 +0000604 dst.setprop(cstr!("interrupts"), &self.interrupts)?;
Jaewan Kima9200492023-11-21 20:45:31 +0900605 let mut iommus = Vec::with_capacity(8 * self.iommus.len());
606 for (pviommu, vsid) in &self.iommus {
607 let phandle = pviommu_phandles.get(pviommu).unwrap();
608 iommus.extend_from_slice(&u32::from(*phandle).to_be_bytes());
609 iommus.extend_from_slice(&vsid.0.to_be_bytes());
610 }
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900611 dst.setprop(cstr!("iommus"), &iommus)?;
612
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900613 Ok(())
614 }
615}
616
617#[derive(Debug, Default, Eq, PartialEq)]
618pub struct DeviceAssignmentInfo {
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900619 pviommus: BTreeSet<PvIommu>,
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900620 assigned_devices: Vec<AssignedDeviceInfo>,
621 filtered_dtbo_paths: Vec<CString>,
622}
623
624impl DeviceAssignmentInfo {
Chris Wailes9d09f572024-01-16 13:31:02 -0800625 const PVIOMMU_COMPATIBLE: &'static CStr = cstr!("pkvm,pviommu");
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900626
627 /// Parses pvIOMMUs in fdt
628 // Note: This will validate pvIOMMU ids' uniqueness, even when unassigned.
629 fn parse_pviommus(fdt: &Fdt) -> Result<BTreeMap<Phandle, PvIommu>> {
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900630 let mut pviommus = BTreeMap::new();
631 for compatible in fdt.compatible_nodes(Self::PVIOMMU_COMPATIBLE)? {
632 let Some(phandle) = compatible.get_phandle()? else {
633 continue; // Skips unreachable pvIOMMU node
634 };
635 let pviommu = PvIommu::parse(&compatible)?;
636 if pviommus.insert(phandle, pviommu).is_some() {
637 return Err(FdtError::BadPhandle.into());
638 }
639 }
640 Ok(pviommus)
641 }
642
Jaewan Kim19b984f2023-12-04 15:16:50 +0900643 fn validate_pviommu_topology(assigned_devices: &[AssignedDeviceInfo]) -> Result<()> {
644 let mut all_iommus = BTreeSet::new();
645 for assigned_device in assigned_devices {
646 for iommu in &assigned_device.iommus {
647 if !all_iommus.insert(iommu) {
648 error!("Unsupported pvIOMMU duplication found, <iommus> = {iommu:?}");
649 return Err(DeviceAssignmentError::UnsupportedPvIommusDuplication);
650 }
651 }
652 }
653 Ok(())
654 }
655
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900656 /// Parses fdt and vm_dtbo, and creates new DeviceAssignmentInfo
657 // TODO(b/277993056): Parse __local_fixups__
658 // TODO(b/277993056): Parse __fixups__
Jaewan Kim52477ae2023-11-21 21:20:52 +0900659 pub fn parse(
660 fdt: &Fdt,
661 vm_dtbo: &VmDtbo,
662 hypervisor: &dyn DeviceAssigningHypervisor,
663 ) -> Result<Option<Self>> {
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900664 let Some(symbols_node) = vm_dtbo.as_ref().symbols()? else {
665 // /__symbols__ should contain all assignable devices.
666 // If empty, then nothing can be assigned.
667 return Ok(None);
668 };
669
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900670 let pviommus = Self::parse_pviommus(fdt)?;
671 let unique_pviommus: BTreeSet<_> = pviommus.values().cloned().collect();
672 if pviommus.len() != unique_pviommus.len() {
673 return Err(DeviceAssignmentError::DuplicatedPvIommuIds);
674 }
675
Jaewan Kim19b984f2023-12-04 15:16:50 +0900676 let physical_devices = vm_dtbo.parse_physical_devices()?;
677
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900678 let mut assigned_devices = vec![];
679 let mut filtered_dtbo_paths = vec![];
680 for symbol_prop in symbols_node.properties()? {
681 let symbol_prop_value = symbol_prop.value()?;
682 let dtbo_node_path = CStr::from_bytes_with_nul(symbol_prop_value)
683 .or(Err(DeviceAssignmentError::InvalidSymbols))?;
Jaewan Kimc39974e2023-12-02 01:13:30 +0900684 if !is_overlayable_node(dtbo_node_path) {
685 continue;
686 }
Jaewan Kim19b984f2023-12-04 15:16:50 +0900687 let assigned_device = AssignedDeviceInfo::parse(
688 fdt,
689 vm_dtbo,
690 dtbo_node_path,
691 &physical_devices,
692 &pviommus,
693 hypervisor,
694 )?;
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900695 if let Some(assigned_device) = assigned_device {
696 assigned_devices.push(assigned_device);
697 } else {
698 filtered_dtbo_paths.push(dtbo_node_path.into());
699 }
700 }
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900701 if assigned_devices.is_empty() {
702 return Ok(None);
703 }
Jaewan Kimc39974e2023-12-02 01:13:30 +0900704
Jaewan Kim19b984f2023-12-04 15:16:50 +0900705 Self::validate_pviommu_topology(&assigned_devices)?;
706
Jaewan Kimc39974e2023-12-02 01:13:30 +0900707 // Clean up any nodes that wouldn't be overlaid but may contain reference to filtered nodes.
708 // Otherwise, `fdt_apply_overlay()` would fail because of missing phandle reference.
Jaewan Kimc39974e2023-12-02 01:13:30 +0900709 // TODO(b/277993056): Also filter other unused nodes/props in __local_fixups__
710 filtered_dtbo_paths.push(CString::new("/__local_fixups__/host").unwrap());
711
712 // Note: Any node without __overlay__ will be ignored by fdt_apply_overlay,
713 // so doesn't need to be filtered.
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900714
715 Ok(Some(Self { pviommus: unique_pviommus, assigned_devices, filtered_dtbo_paths }))
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900716 }
717
718 /// Filters VM DTBO to only contain necessary information for booting pVM
719 /// In detail, this will remove followings by setting nop node / nop property.
720 /// - Removes unassigned devices
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900721 // TODO(b/277993056): remove unused dependencies in VM DTBO.
722 // TODO(b/277993056): remove supernodes' properties.
723 // TODO(b/277993056): remove unused alises.
724 pub fn filter(&self, vm_dtbo: &mut VmDtbo) -> Result<()> {
725 let vm_dtbo = vm_dtbo.as_mut();
726
727 // Filters unused node in assigned devices
728 for filtered_dtbo_path in &self.filtered_dtbo_paths {
729 let node = vm_dtbo.node_mut(filtered_dtbo_path).unwrap().unwrap();
730 node.nop()?;
731 }
732
Jaewan Kim371f6c82024-02-24 01:33:37 +0900733 filter_dangling_symbols(vm_dtbo)
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900734 }
735
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900736 fn patch_pviommus(&self, fdt: &mut Fdt) -> Result<BTreeMap<PvIommu, Phandle>> {
Pierre-Clément Tosi244efea2024-02-16 14:48:14 +0000737 let mut compatible = fdt.root_mut().next_compatible(Self::PVIOMMU_COMPATIBLE)?;
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900738 let mut pviommu_phandles = BTreeMap::new();
739
740 for pviommu in &self.pviommus {
741 let mut node = compatible.ok_or(DeviceAssignmentError::TooManyPvIommu)?;
742 let phandle = node.as_node().get_phandle()?.ok_or(DeviceAssignmentError::Internal)?;
743 node.setprop_inplace(cstr!("id"), &pviommu.id.to_be_bytes())?;
744 if pviommu_phandles.insert(*pviommu, phandle).is_some() {
745 return Err(DeviceAssignmentError::Internal);
746 }
747 compatible = node.next_compatible(Self::PVIOMMU_COMPATIBLE)?;
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900748 }
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900749
750 // Filters pre-populated but unassigned pvIOMMUs.
751 while let Some(filtered_pviommu) = compatible {
752 compatible = filtered_pviommu.delete_and_next_compatible(Self::PVIOMMU_COMPATIBLE)?;
753 }
754
755 Ok(pviommu_phandles)
756 }
757
758 pub fn patch(&self, fdt: &mut Fdt) -> Result<()> {
759 let pviommu_phandles = self.patch_pviommus(fdt)?;
760
761 // Patches assigned devices
762 for device in &self.assigned_devices {
763 device.patch(fdt, &pviommu_phandles)?;
764 }
765
Jaewan Kimc730ebf2024-02-22 10:34:55 +0900766 // Removes any dangling references in __symbols__ (e.g. removed pvIOMMUs)
767 filter_dangling_symbols(fdt)
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900768 }
769}
770
771#[cfg(test)]
772mod tests {
773 use super::*;
Jaewan Kim52477ae2023-11-21 21:20:52 +0900774 use alloc::collections::{BTreeMap, BTreeSet};
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900775 use std::fs;
776
777 const VM_DTBO_FILE_PATH: &str = "test_pvmfw_devices_vm_dtbo.dtbo";
778 const VM_DTBO_WITHOUT_SYMBOLS_FILE_PATH: &str =
779 "test_pvmfw_devices_vm_dtbo_without_symbols.dtbo";
Jaewan Kim19b984f2023-12-04 15:16:50 +0900780 const VM_DTBO_WITH_DUPLICATED_IOMMUS_FILE_PATH: &str =
781 "test_pvmfw_devices_vm_dtbo_with_duplicated_iommus.dtbo";
Jaewan Kima67e36a2023-11-29 16:50:23 +0900782 const FDT_WITHOUT_IOMMUS_FILE_PATH: &str = "test_pvmfw_devices_without_iommus.dtb";
Jaewan Kim52477ae2023-11-21 21:20:52 +0900783 const FDT_WITHOUT_DEVICE_FILE_PATH: &str = "test_pvmfw_devices_without_device.dtb";
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900784 const FDT_FILE_PATH: &str = "test_pvmfw_devices_with_rng.dtb";
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900785 const FDT_WITH_MULTIPLE_DEVICES_IOMMUS_FILE_PATH: &str =
786 "test_pvmfw_devices_with_multiple_devices_iommus.dtb";
787 const FDT_WITH_IOMMU_SHARING: &str = "test_pvmfw_devices_with_iommu_sharing.dtb";
788 const FDT_WITH_IOMMU_ID_CONFLICT: &str = "test_pvmfw_devices_with_iommu_id_conflict.dtb";
Jaewan Kim19b984f2023-12-04 15:16:50 +0900789 const FDT_WITH_DUPLICATED_PVIOMMUS_FILE_PATH: &str =
790 "test_pvmfw_devices_with_duplicated_pviommus.dtb";
791 const FDT_WITH_MULTIPLE_REG_IOMMU_FILE_PATH: &str =
792 "test_pvmfw_devices_with_multiple_reg_iommus.dtb";
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900793
Jaewan Kim52477ae2023-11-21 21:20:52 +0900794 #[derive(Debug, Default)]
795 struct MockHypervisor {
796 mmio_tokens: BTreeMap<(u64, u64), u64>,
797 iommu_tokens: BTreeMap<(u64, u64), (u64, u64)>,
798 }
799
800 impl DeviceAssigningHypervisor for MockHypervisor {
801 fn get_phys_mmio_token(&self, base_ipa: u64, size: u64) -> hyp::Result<u64> {
802 Ok(*self.mmio_tokens.get(&(base_ipa, size)).ok_or(hyp::Error::KvmError(
803 hyp::KvmError::InvalidParameter,
804 0xc6000012, /* VENDOR_HYP_KVM_DEV_REQ_MMIO_FUNC_ID */
805 ))?)
806 }
807
808 fn get_phys_iommu_token(&self, pviommu_id: u64, vsid: u64) -> hyp::Result<(u64, u64)> {
809 Ok(*self.iommu_tokens.get(&(pviommu_id, vsid)).ok_or(hyp::Error::KvmError(
810 hyp::KvmError::InvalidParameter,
811 0xc6000013, /* VENDOR_HYP_KVM_DEV_REQ_DMA_FUNC_ID */
812 ))?)
813 }
814 }
815
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900816 #[derive(Debug, Eq, PartialEq)]
817 struct AssignedDeviceNode {
818 path: CString,
819 reg: Vec<u8>,
820 interrupts: Vec<u8>,
Jaewan Kima67e36a2023-11-29 16:50:23 +0900821 iommus: Vec<u32>, // pvIOMMU id and vSID
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900822 }
823
824 impl AssignedDeviceNode {
825 fn parse(fdt: &Fdt, path: &CStr) -> Result<Self> {
826 let Some(node) = fdt.node(path)? else {
827 return Err(FdtError::NotFound.into());
828 };
829
Jaewan Kim19b984f2023-12-04 15:16:50 +0900830 let reg = node.getprop(cstr!("reg"))?.ok_or(DeviceAssignmentError::MalformedReg)?;
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900831 let interrupts = node
832 .getprop(cstr!("interrupts"))?
833 .ok_or(DeviceAssignmentError::InvalidInterrupts)?;
834 let mut iommus = vec![];
Jaewan Kima9200492023-11-21 20:45:31 +0900835 if let Some(mut cells) = node.getprop_cells(cstr!("iommus"))? {
836 while let Some(pviommu_id) = cells.next() {
837 // pvIOMMU id
838 let phandle = Phandle::try_from(pviommu_id)?;
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900839 let pviommu = fdt
840 .node_with_phandle(phandle)?
Jaewan Kim19b984f2023-12-04 15:16:50 +0900841 .ok_or(DeviceAssignmentError::MalformedIommus)?;
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900842 let compatible = pviommu.getprop_str(cstr!("compatible"));
843 if compatible != Ok(Some(cstr!("pkvm,pviommu"))) {
Jaewan Kim19b984f2023-12-04 15:16:50 +0900844 return Err(DeviceAssignmentError::MalformedIommus);
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900845 }
846 let id = pviommu
847 .getprop_u32(cstr!("id"))?
Jaewan Kim19b984f2023-12-04 15:16:50 +0900848 .ok_or(DeviceAssignmentError::MalformedIommus)?;
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900849 iommus.push(id);
Jaewan Kima9200492023-11-21 20:45:31 +0900850
851 // vSID
852 let Some(vsid) = cells.next() else {
Jaewan Kim19b984f2023-12-04 15:16:50 +0900853 return Err(DeviceAssignmentError::MalformedIommus);
Jaewan Kima9200492023-11-21 20:45:31 +0900854 };
855 iommus.push(vsid);
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900856 }
857 }
858 Ok(Self { path: path.into(), reg: reg.into(), interrupts: interrupts.into(), iommus })
859 }
860 }
861
862 fn collect_pviommus(fdt: &Fdt) -> Result<Vec<u32>> {
863 let mut pviommus = BTreeSet::new();
864 for pviommu in fdt.compatible_nodes(cstr!("pkvm,pviommu"))? {
865 if let Ok(Some(id)) = pviommu.getprop_u32(cstr!("id")) {
866 pviommus.insert(id);
867 }
868 }
869 Ok(pviommus.iter().cloned().collect())
870 }
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900871
872 fn into_fdt_prop(native_bytes: Vec<u32>) -> Vec<u8> {
873 let mut v = Vec::with_capacity(native_bytes.len() * 4);
874 for byte in native_bytes {
875 v.extend_from_slice(&byte.to_be_bytes());
876 }
877 v
878 }
879
Jaewan Kim52477ae2023-11-21 21:20:52 +0900880 impl From<[u64; 2]> for DeviceReg {
881 fn from(fdt_cells: [u64; 2]) -> Self {
882 DeviceReg { addr: fdt_cells[0], size: fdt_cells[1] }
883 }
884 }
885
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900886 #[test]
887 fn device_info_new_without_symbols() {
888 let mut fdt_data = fs::read(FDT_FILE_PATH).unwrap();
889 let mut vm_dtbo_data = fs::read(VM_DTBO_WITHOUT_SYMBOLS_FILE_PATH).unwrap();
890 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
891 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
892
Jaewan Kim52477ae2023-11-21 21:20:52 +0900893 let hypervisor: MockHypervisor = Default::default();
894 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor).unwrap();
895 assert_eq!(device_info, None);
896 }
897
898 #[test]
899 fn device_info_new_without_device() {
900 let mut fdt_data = fs::read(FDT_WITHOUT_DEVICE_FILE_PATH).unwrap();
901 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
902 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
903 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
904
905 let hypervisor: MockHypervisor = Default::default();
906 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor).unwrap();
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900907 assert_eq!(device_info, None);
908 }
909
910 #[test]
Jaewan Kima67e36a2023-11-29 16:50:23 +0900911 fn device_info_assigned_info_without_iommus() {
912 let mut fdt_data = fs::read(FDT_WITHOUT_IOMMUS_FILE_PATH).unwrap();
913 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
914 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
915 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
916
Jaewan Kim52477ae2023-11-21 21:20:52 +0900917 let hypervisor = MockHypervisor {
918 mmio_tokens: [((0x9, 0xFF), 0x300)].into(),
919 iommu_tokens: BTreeMap::new(),
920 };
921 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor).unwrap().unwrap();
Jaewan Kima67e36a2023-11-29 16:50:23 +0900922
923 let expected = [AssignedDeviceInfo {
Jaewan Kimc39974e2023-12-02 01:13:30 +0900924 node_path: CString::new("/bus0/backlight").unwrap(),
Jaewan Kima232ed02024-02-25 16:08:14 +0000925 dtbo_node_path: cstr!("/fragment@0/__overlay__/bus0/backlight").into(),
Jaewan Kim52477ae2023-11-21 21:20:52 +0900926 reg: vec![[0x9, 0xFF].into()],
Jaewan Kima67e36a2023-11-29 16:50:23 +0900927 interrupts: into_fdt_prop(vec![0x0, 0xF, 0x4]),
928 iommus: vec![],
929 }];
930
931 assert_eq!(device_info.assigned_devices, expected);
932 }
933
934 #[test]
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900935 fn device_info_assigned_info() {
936 let mut fdt_data = fs::read(FDT_FILE_PATH).unwrap();
937 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
938 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
939 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
940
Jaewan Kim52477ae2023-11-21 21:20:52 +0900941 let hypervisor = MockHypervisor {
942 mmio_tokens: [((0x9, 0xFF), 0x12F00000)].into(),
943 iommu_tokens: [((0x4, 0xFF0), (0x12E40000, 0x3))].into(),
944 };
945 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor).unwrap().unwrap();
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900946
947 let expected = [AssignedDeviceInfo {
948 node_path: CString::new("/rng").unwrap(),
Jaewan Kima232ed02024-02-25 16:08:14 +0000949 dtbo_node_path: cstr!("/fragment@0/__overlay__/rng").into(),
Jaewan Kim52477ae2023-11-21 21:20:52 +0900950 reg: vec![[0x9, 0xFF].into()],
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900951 interrupts: into_fdt_prop(vec![0x0, 0xF, 0x4]),
Jaewan Kima67e36a2023-11-29 16:50:23 +0900952 iommus: vec![(PvIommu { id: 0x4 }, Vsid(0xFF0))],
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900953 }];
954
955 assert_eq!(device_info.assigned_devices, expected);
956 }
957
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900958 #[test]
959 fn device_info_filter() {
960 let mut fdt_data = fs::read(FDT_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
Jaewan Kim52477ae2023-11-21 21:20:52 +0900965 let hypervisor = MockHypervisor {
966 mmio_tokens: [((0x9, 0xFF), 0x12F00000)].into(),
967 iommu_tokens: [((0x4, 0xFF0), (0x12E40000, 0x3))].into(),
968 };
969 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor).unwrap().unwrap();
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900970 device_info.filter(vm_dtbo).unwrap();
971
972 let vm_dtbo = vm_dtbo.as_mut();
973
Jaewan Kim371f6c82024-02-24 01:33:37 +0900974 let symbols = vm_dtbo.symbols().unwrap().unwrap();
975
Jaewan Kima232ed02024-02-25 16:08:14 +0000976 let rng = vm_dtbo.node(cstr!("/fragment@0/__overlay__/rng")).unwrap();
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900977 assert_ne!(rng, None);
Jaewan Kim371f6c82024-02-24 01:33:37 +0900978 let rng_symbol = symbols.getprop_str(cstr!("rng")).unwrap();
Jaewan Kima232ed02024-02-25 16:08:14 +0000979 assert_eq!(Some(cstr!("/fragment@0/__overlay__/rng")), rng_symbol);
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900980
Jaewan Kima232ed02024-02-25 16:08:14 +0000981 let light = vm_dtbo.node(cstr!("/fragment@0/__overlay__/light")).unwrap();
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900982 assert_eq!(light, None);
Jaewan Kim371f6c82024-02-24 01:33:37 +0900983 let light_symbol = symbols.getprop_str(cstr!("light")).unwrap();
984 assert_eq!(None, light_symbol);
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900985
Jaewan Kima232ed02024-02-25 16:08:14 +0000986 let led = vm_dtbo.node(cstr!("/fragment@0/__overlay__/led")).unwrap();
Jaewan Kima67e36a2023-11-29 16:50:23 +0900987 assert_eq!(led, None);
Jaewan Kim371f6c82024-02-24 01:33:37 +0900988 let led_symbol = symbols.getprop_str(cstr!("led")).unwrap();
989 assert_eq!(None, led_symbol);
Jaewan Kima67e36a2023-11-29 16:50:23 +0900990
Jaewan Kima232ed02024-02-25 16:08:14 +0000991 let backlight = vm_dtbo.node(cstr!("/fragment@0/__overlay__/bus0/backlight")).unwrap();
Jaewan Kima67e36a2023-11-29 16:50:23 +0900992 assert_eq!(backlight, None);
Jaewan Kim371f6c82024-02-24 01:33:37 +0900993 let backlight_symbol = symbols.getprop_str(cstr!("backlight")).unwrap();
994 assert_eq!(None, backlight_symbol);
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900995 }
996
997 #[test]
998 fn device_info_patch() {
Jaewan Kima67e36a2023-11-29 16:50:23 +0900999 let mut fdt_data = fs::read(FDT_WITHOUT_IOMMUS_FILE_PATH).unwrap();
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001000 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1001 let mut data = vec![0_u8; fdt_data.len() + vm_dtbo_data.len()];
1002 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1003 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1004 let platform_dt = Fdt::create_empty_tree(data.as_mut_slice()).unwrap();
1005
Jaewan Kim52477ae2023-11-21 21:20:52 +09001006 let hypervisor = MockHypervisor {
1007 mmio_tokens: [((0x9, 0xFF), 0x300)].into(),
1008 iommu_tokens: BTreeMap::new(),
1009 };
1010 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor).unwrap().unwrap();
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001011 device_info.filter(vm_dtbo).unwrap();
1012
1013 // SAFETY: Damaged VM DTBO wouldn't be used after this unsafe block.
1014 unsafe {
1015 platform_dt.apply_overlay(vm_dtbo.as_mut()).unwrap();
1016 }
Jaewan Kim0bd637d2023-11-10 13:09:41 +09001017 device_info.patch(platform_dt).unwrap();
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001018
Jaewan Kimc39974e2023-12-02 01:13:30 +09001019 let rng_node = platform_dt.node(cstr!("/bus0/backlight")).unwrap().unwrap();
1020 let phandle = rng_node.getprop_u32(cstr!("phandle")).unwrap();
1021 assert_ne!(None, phandle);
1022
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001023 // Note: Intentionally not using AssignedDeviceNode for matching all props.
Jaewan Kim0bd637d2023-11-10 13:09:41 +09001024 type FdtResult<T> = libfdt::Result<T>;
1025 let expected: Vec<(FdtResult<&CStr>, FdtResult<Vec<u8>>)> = vec![
Jaewan Kima67e36a2023-11-29 16:50:23 +09001026 (Ok(cstr!("android,backlight,ignore-gctrl-reset")), Ok(Vec::new())),
1027 (Ok(cstr!("compatible")), Ok(Vec::from(*b"android,backlight\0"))),
Jaewan Kim0bd637d2023-11-10 13:09:41 +09001028 (Ok(cstr!("interrupts")), Ok(into_fdt_prop(vec![0x0, 0xF, 0x4]))),
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001029 (Ok(cstr!("iommus")), Ok(Vec::new())),
Jaewan Kimc39974e2023-12-02 01:13:30 +09001030 (Ok(cstr!("phandle")), Ok(into_fdt_prop(vec![phandle.unwrap()]))),
Jaewan Kim0bd637d2023-11-10 13:09:41 +09001031 (Ok(cstr!("reg")), Ok(into_fdt_prop(vec![0x0, 0x9, 0x0, 0xFF]))),
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001032 ];
1033
Jaewan Kim0bd637d2023-11-10 13:09:41 +09001034 let mut properties: Vec<_> = rng_node
1035 .properties()
1036 .unwrap()
1037 .map(|prop| (prop.name(), prop.value().map(|x| x.into())))
1038 .collect();
1039 properties.sort_by(|a, b| {
1040 let lhs = a.0.unwrap_or_default();
1041 let rhs = b.0.unwrap_or_default();
1042 lhs.partial_cmp(rhs).unwrap()
1043 });
1044
1045 assert_eq!(properties, expected);
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001046 }
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001047
1048 #[test]
Jaewan Kimc730ebf2024-02-22 10:34:55 +09001049 fn device_info_patch_no_pviommus() {
1050 let mut fdt_data = fs::read(FDT_WITHOUT_IOMMUS_FILE_PATH).unwrap();
1051 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1052 let mut data = vec![0_u8; fdt_data.len() + vm_dtbo_data.len()];
1053 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1054 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1055 let platform_dt = Fdt::create_empty_tree(data.as_mut_slice()).unwrap();
1056
1057 let hypervisor = MockHypervisor {
1058 mmio_tokens: [((0x9, 0xFF), 0x300)].into(),
1059 iommu_tokens: BTreeMap::new(),
1060 };
1061 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor).unwrap().unwrap();
1062 device_info.filter(vm_dtbo).unwrap();
1063
1064 // SAFETY: Damaged VM DTBO wouldn't be used after this unsafe block.
1065 unsafe {
1066 platform_dt.apply_overlay(vm_dtbo.as_mut()).unwrap();
1067 }
1068 device_info.patch(platform_dt).unwrap();
1069
1070 let compatible = platform_dt.root().next_compatible(cstr!("pkvm,pviommu")).unwrap();
1071 assert_eq!(None, compatible);
1072
1073 if let Some(symbols) = platform_dt.symbols().unwrap() {
1074 for prop in symbols.properties().unwrap() {
1075 let path = CStr::from_bytes_with_nul(prop.value().unwrap()).unwrap();
1076 assert_ne!(None, platform_dt.node(path).unwrap());
1077 }
1078 }
1079 }
1080
1081 #[test]
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001082 fn device_info_overlay_iommu() {
Jaewan Kima67e36a2023-11-29 16:50:23 +09001083 let mut fdt_data = fs::read(FDT_FILE_PATH).unwrap();
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001084 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1085 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1086 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1087 let mut platform_dt_data = pvmfw_fdt_template::RAW.to_vec();
1088 platform_dt_data.resize(pvmfw_fdt_template::RAW.len() * 2, 0);
1089 let platform_dt = Fdt::from_mut_slice(&mut platform_dt_data).unwrap();
1090 platform_dt.unpack().unwrap();
1091
Jaewan Kim52477ae2023-11-21 21:20:52 +09001092 let hypervisor = MockHypervisor {
1093 mmio_tokens: [((0x9, 0xFF), 0x12F00000)].into(),
1094 iommu_tokens: [((0x4, 0xFF0), (0x12E40000, 0x3))].into(),
1095 };
1096 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor).unwrap().unwrap();
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001097 device_info.filter(vm_dtbo).unwrap();
1098
1099 // SAFETY: Damaged VM DTBO wouldn't be used after this unsafe block.
1100 unsafe {
1101 platform_dt.apply_overlay(vm_dtbo.as_mut()).unwrap();
1102 }
1103 device_info.patch(platform_dt).unwrap();
1104
1105 let expected = AssignedDeviceNode {
1106 path: CString::new("/rng").unwrap(),
1107 reg: into_fdt_prop(vec![0x0, 0x9, 0x0, 0xFF]),
1108 interrupts: into_fdt_prop(vec![0x0, 0xF, 0x4]),
Jaewan Kima9200492023-11-21 20:45:31 +09001109 iommus: vec![0x4, 0xFF0],
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001110 };
1111
1112 let node = AssignedDeviceNode::parse(platform_dt, &expected.path);
1113 assert_eq!(node, Ok(expected));
1114
1115 let pviommus = collect_pviommus(platform_dt);
1116 assert_eq!(pviommus, Ok(vec![0x4]));
1117 }
1118
1119 #[test]
1120 fn device_info_multiple_devices_iommus() {
1121 let mut fdt_data = fs::read(FDT_WITH_MULTIPLE_DEVICES_IOMMUS_FILE_PATH).unwrap();
1122 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1123 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1124 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1125 let mut platform_dt_data = pvmfw_fdt_template::RAW.to_vec();
1126 platform_dt_data.resize(pvmfw_fdt_template::RAW.len() * 2, 0);
1127 let platform_dt = Fdt::from_mut_slice(&mut platform_dt_data).unwrap();
1128 platform_dt.unpack().unwrap();
1129
Jaewan Kim52477ae2023-11-21 21:20:52 +09001130 let hypervisor = MockHypervisor {
1131 mmio_tokens: [
1132 ((0x9, 0xFF), 0x12F00000),
Jaewan Kim19b984f2023-12-04 15:16:50 +09001133 ((0x10000, 0x1000), 0xF00000),
1134 ((0x20000, 0x1000), 0xF10000),
Jaewan Kim52477ae2023-11-21 21:20:52 +09001135 ]
1136 .into(),
1137 iommu_tokens: [
1138 ((0x4, 0xFF0), (0x12E40000, 3)),
1139 ((0x40, 0xFFA), (0x40000, 0x4)),
1140 ((0x50, 0xFFB), (0x50000, 0x5)),
1141 ]
1142 .into(),
1143 };
1144 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor).unwrap().unwrap();
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001145 device_info.filter(vm_dtbo).unwrap();
1146
1147 // SAFETY: Damaged VM DTBO wouldn't be used after this unsafe block.
1148 unsafe {
1149 platform_dt.apply_overlay(vm_dtbo.as_mut()).unwrap();
1150 }
1151 device_info.patch(platform_dt).unwrap();
1152
1153 let expected_devices = [
1154 AssignedDeviceNode {
1155 path: CString::new("/rng").unwrap(),
1156 reg: into_fdt_prop(vec![0x0, 0x9, 0x0, 0xFF]),
1157 interrupts: into_fdt_prop(vec![0x0, 0xF, 0x4]),
Jaewan Kima67e36a2023-11-29 16:50:23 +09001158 iommus: vec![0x4, 0xFF0],
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001159 },
1160 AssignedDeviceNode {
1161 path: CString::new("/light").unwrap(),
Jaewan Kim19b984f2023-12-04 15:16:50 +09001162 reg: into_fdt_prop(vec![0x0, 0x10000, 0x0, 0x1000, 0x0, 0x20000, 0x0, 0x1000]),
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001163 interrupts: into_fdt_prop(vec![0x0, 0xF, 0x5]),
Jaewan Kima67e36a2023-11-29 16:50:23 +09001164 iommus: vec![0x40, 0xFFA, 0x50, 0xFFB],
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001165 },
1166 ];
1167
1168 for expected in expected_devices {
1169 let node = AssignedDeviceNode::parse(platform_dt, &expected.path);
1170 assert_eq!(node, Ok(expected));
1171 }
1172 let pviommus = collect_pviommus(platform_dt);
Jaewan Kima67e36a2023-11-29 16:50:23 +09001173 assert_eq!(pviommus, Ok(vec![0x4, 0x40, 0x50]));
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001174 }
1175
1176 #[test]
1177 fn device_info_iommu_sharing() {
1178 let mut fdt_data = fs::read(FDT_WITH_IOMMU_SHARING).unwrap();
1179 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1180 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1181 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1182 let mut platform_dt_data = pvmfw_fdt_template::RAW.to_vec();
1183 platform_dt_data.resize(pvmfw_fdt_template::RAW.len() * 2, 0);
1184 let platform_dt = Fdt::from_mut_slice(&mut platform_dt_data).unwrap();
1185 platform_dt.unpack().unwrap();
1186
Jaewan Kim52477ae2023-11-21 21:20:52 +09001187 let hypervisor = MockHypervisor {
Jaewan Kim19b984f2023-12-04 15:16:50 +09001188 mmio_tokens: [((0x9, 0xFF), 0x12F00000), ((0x1000, 0x9), 0x12000000)].into(),
1189 iommu_tokens: [((0x4, 0xFF0), (0x12E40000, 3)), ((0x4, 0xFF1), (0x12E40000, 9))].into(),
Jaewan Kim52477ae2023-11-21 21:20:52 +09001190 };
1191 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor).unwrap().unwrap();
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001192 device_info.filter(vm_dtbo).unwrap();
1193
1194 // SAFETY: Damaged VM DTBO wouldn't be used after this unsafe block.
1195 unsafe {
1196 platform_dt.apply_overlay(vm_dtbo.as_mut()).unwrap();
1197 }
1198 device_info.patch(platform_dt).unwrap();
1199
1200 let expected_devices = [
1201 AssignedDeviceNode {
1202 path: CString::new("/rng").unwrap(),
1203 reg: into_fdt_prop(vec![0x0, 0x9, 0x0, 0xFF]),
1204 interrupts: into_fdt_prop(vec![0x0, 0xF, 0x4]),
Jaewan Kima67e36a2023-11-29 16:50:23 +09001205 iommus: vec![0x4, 0xFF0],
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001206 },
1207 AssignedDeviceNode {
Jaewan Kima67e36a2023-11-29 16:50:23 +09001208 path: CString::new("/led").unwrap(),
Jaewan Kim19b984f2023-12-04 15:16:50 +09001209 reg: into_fdt_prop(vec![0x0, 0x1000, 0x0, 0x9]),
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001210 interrupts: into_fdt_prop(vec![0x0, 0xF, 0x5]),
Jaewan Kim19b984f2023-12-04 15:16:50 +09001211 iommus: vec![0x4, 0xFF1],
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001212 },
1213 ];
1214
1215 for expected in expected_devices {
1216 let node = AssignedDeviceNode::parse(platform_dt, &expected.path);
1217 assert_eq!(node, Ok(expected));
1218 }
1219
1220 let pviommus = collect_pviommus(platform_dt);
Jaewan Kima67e36a2023-11-29 16:50:23 +09001221 assert_eq!(pviommus, Ok(vec![0x4]));
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001222 }
1223
1224 #[test]
1225 fn device_info_iommu_id_conflict() {
1226 let mut fdt_data = fs::read(FDT_WITH_IOMMU_ID_CONFLICT).unwrap();
1227 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1228 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1229 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1230
Jaewan Kim52477ae2023-11-21 21:20:52 +09001231 let hypervisor = MockHypervisor {
Jaewan Kim19b984f2023-12-04 15:16:50 +09001232 mmio_tokens: [((0x9, 0xFF), 0x300)].into(),
Jaewan Kim52477ae2023-11-21 21:20:52 +09001233 iommu_tokens: [((0x4, 0xFF0), (0x12E40000, 0x3))].into(),
1234 };
1235 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor);
Jaewan Kim51ccfed2023-11-08 13:51:58 +09001236
1237 assert_eq!(device_info, Err(DeviceAssignmentError::DuplicatedPvIommuIds));
1238 }
Jaewan Kim52477ae2023-11-21 21:20:52 +09001239
1240 #[test]
1241 fn device_info_invalid_reg() {
1242 let mut fdt_data = fs::read(FDT_FILE_PATH).unwrap();
1243 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1244 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1245 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1246
1247 let hypervisor = MockHypervisor {
1248 mmio_tokens: BTreeMap::new(),
1249 iommu_tokens: [((0x4, 0xFF0), (0x12E40000, 0x3))].into(),
1250 };
1251 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor);
1252
1253 assert_eq!(device_info, Err(DeviceAssignmentError::InvalidReg));
1254 }
1255
1256 #[test]
Jaewan Kim19b984f2023-12-04 15:16:50 +09001257 fn device_info_invalid_reg_out_of_order() {
1258 let mut fdt_data = fs::read(FDT_WITH_MULTIPLE_REG_IOMMU_FILE_PATH).unwrap();
1259 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1260 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1261 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1262
1263 let hypervisor = MockHypervisor {
1264 mmio_tokens: [((0xF000, 0x1000), 0xF10000), ((0xF100, 0x1000), 0xF00000)].into(),
1265 iommu_tokens: [((0xFF0, 0xF0), (0x40000, 0x4)), ((0xFF1, 0xF1), (0x50000, 0x5))].into(),
1266 };
1267 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor);
1268
1269 assert_eq!(device_info, Err(DeviceAssignmentError::InvalidReg));
1270 }
1271
1272 #[test]
Jaewan Kim52477ae2023-11-21 21:20:52 +09001273 fn device_info_invalid_iommus() {
1274 let mut fdt_data = fs::read(FDT_FILE_PATH).unwrap();
1275 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1276 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1277 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1278
1279 let hypervisor = MockHypervisor {
1280 mmio_tokens: [((0x9, 0xFF), 0x12F00000)].into(),
1281 iommu_tokens: BTreeMap::new(),
1282 };
1283 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor);
1284
1285 assert_eq!(device_info, Err(DeviceAssignmentError::InvalidIommus));
1286 }
Jaewan Kim19b984f2023-12-04 15:16:50 +09001287
1288 #[test]
1289 fn device_info_duplicated_pv_iommus() {
1290 let mut fdt_data = fs::read(FDT_WITH_DUPLICATED_PVIOMMUS_FILE_PATH).unwrap();
1291 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1292 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1293 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1294
1295 let hypervisor = MockHypervisor {
1296 mmio_tokens: [((0x10000, 0x1000), 0xF00000), ((0x20000, 0xFF), 0xF10000)].into(),
1297 iommu_tokens: [((0xFF, 0xF), (0x40000, 0x4))].into(),
1298 };
1299 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor);
1300
1301 assert_eq!(device_info, Err(DeviceAssignmentError::DuplicatedPvIommuIds));
1302 }
1303
1304 #[test]
1305 fn device_info_duplicated_iommus() {
1306 let mut fdt_data = fs::read(FDT_FILE_PATH).unwrap();
1307 let mut vm_dtbo_data = fs::read(VM_DTBO_WITH_DUPLICATED_IOMMUS_FILE_PATH).unwrap();
1308 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1309 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1310
1311 let hypervisor = MockHypervisor {
1312 mmio_tokens: [((0x10000, 0x1000), 0xF00000), ((0x20000, 0xFF), 0xF10000)].into(),
1313 iommu_tokens: [((0xFF, 0xF), (0x40000, 0x4))].into(),
1314 };
1315 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor);
1316
1317 assert_eq!(device_info, Err(DeviceAssignmentError::UnsupportedIommusDuplication));
1318 }
1319
1320 #[test]
1321 fn device_info_duplicated_iommu_mapping() {
1322 let mut fdt_data = fs::read(FDT_WITH_MULTIPLE_REG_IOMMU_FILE_PATH).unwrap();
1323 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
1324 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
1325 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
1326
1327 let hypervisor = MockHypervisor {
1328 mmio_tokens: [((0xF000, 0x1000), 0xF00000), ((0xF100, 0x1000), 0xF10000)].into(),
1329 iommu_tokens: [((0xFF0, 0xF0), (0x40000, 0x4)), ((0xFF1, 0xF1), (0x40000, 0x4))].into(),
1330 };
1331 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor);
1332
1333 assert_eq!(device_info, Err(DeviceAssignmentError::InvalidIommus));
1334 }
Jaewan Kimc6e023b2023-10-12 15:11:05 +09001335}