blob: 8d4d840319dc0a60635a9df885a22ae0248750e2 [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 Kim52477ae2023-11-21 21:20:52 +090056 /// Invalid <reg>
57 InvalidReg,
Jaewan Kimc6e023b2023-10-12 15:11:05 +090058 /// Invalid <interrupts>
59 InvalidInterrupts,
Jaewan Kim51ccfed2023-11-08 13:51:58 +090060 /// Invalid <iommus>
61 InvalidIommus,
Jaewan Kima9200492023-11-21 20:45:31 +090062 /// Invalid pvIOMMU node
63 InvalidPvIommu,
Jaewan Kim51ccfed2023-11-08 13:51:58 +090064 /// Too many pvIOMMU
65 TooManyPvIommu,
66 /// Duplicated pvIOMMU IDs exist
67 DuplicatedPvIommuIds,
Jaewan Kimc6e023b2023-10-12 15:11:05 +090068 /// Unsupported overlay target syntax. Only supports <target-path> with full path.
69 UnsupportedOverlayTarget,
Jaewan Kim51ccfed2023-11-08 13:51:58 +090070 /// Internal error
71 Internal,
Jaewan Kimc6e023b2023-10-12 15:11:05 +090072 /// Unexpected error from libfdt
73 UnexpectedFdtError(FdtError),
74}
75
76impl From<FdtError> for DeviceAssignmentError {
77 fn from(e: FdtError) -> Self {
78 DeviceAssignmentError::UnexpectedFdtError(e)
79 }
80}
81
82impl fmt::Display for DeviceAssignmentError {
83 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
84 match self {
85 Self::InvalidDtbo => write!(f, "Invalid DTBO"),
86 Self::InvalidSymbols => write!(
87 f,
88 "Invalid property in /__symbols__. Must point to valid assignable device node."
89 ),
Jaewan Kim52477ae2023-11-21 21:20:52 +090090 Self::InvalidReg => write!(f, "Invalid <reg>"),
Jaewan Kimc6e023b2023-10-12 15:11:05 +090091 Self::InvalidInterrupts => write!(f, "Invalid <interrupts>"),
Jaewan Kim51ccfed2023-11-08 13:51:58 +090092 Self::InvalidIommus => write!(f, "Invalid <iommus>"),
Jaewan Kima9200492023-11-21 20:45:31 +090093 Self::InvalidPvIommu => write!(f, "Invalid pvIOMMU node"),
Jaewan Kim51ccfed2023-11-08 13:51:58 +090094 Self::TooManyPvIommu => write!(
95 f,
96 "Too many pvIOMMU node. Insufficient pre-populated pvIOMMUs in platform DT"
97 ),
98 Self::DuplicatedPvIommuIds => {
99 write!(f, "Duplicated pvIOMMU IDs exist. IDs must unique")
100 }
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900101 Self::UnsupportedOverlayTarget => {
102 write!(f, "Unsupported overlay target. Only supports 'target-path = \"/\"'")
103 }
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900104 Self::Internal => write!(f, "Internal error"),
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900105 Self::UnexpectedFdtError(e) => write!(f, "Unexpected Error from libfdt: {e}"),
106 }
107 }
108}
109
110pub type Result<T> = core::result::Result<T, DeviceAssignmentError>;
111
112/// Represents VM DTBO
113#[repr(transparent)]
114pub struct VmDtbo(Fdt);
115
116impl VmDtbo {
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900117 /// Wraps a mutable slice containing a VM DTBO.
118 ///
119 /// Fails if the VM DTBO does not pass validation.
120 pub fn from_mut_slice(dtbo: &mut [u8]) -> Result<&mut Self> {
121 // This validates DTBO
122 let fdt = Fdt::from_mut_slice(dtbo)?;
123 // SAFETY: VmDtbo is a transparent wrapper around Fdt, so representation is the same.
124 Ok(unsafe { mem::transmute::<&mut Fdt, &mut Self>(fdt) })
125 }
126
127 // Locates device node path as if the given dtbo node path is assigned and VM DTBO is overlaid.
128 // For given dtbo node path, this concatenates <target-path> of the enclosing fragment and
129 // relative path from __overlay__ node.
130 //
131 // Here's an example with sample VM DTBO:
132 // / {
133 // fragment@rng {
134 // target-path = "/"; // Always 'target-path = "/"'. Disallows <target> or other path.
135 // __overlay__ {
136 // rng { ... }; // Actual device node is here. If overlaid, path would be "/rng"
137 // };
138 // };
139 // __symbols__ { // List of assignable devices
140 // // Each property describes an assigned device device information.
141 // // property name is the device label, and property value is the path in the VM DTBO.
142 // rng = "/fragment@rng/__overlay__/rng";
143 // };
144 // };
145 //
146 // Then locate_overlay_target_path(cstr!("/fragment@rng/__overlay__/rng")) is Ok("/rng")
147 //
148 // Contrary to fdt_overlay_target_offset(), this API enforces overlay target property
149 // 'target-path = "/"', so the overlay doesn't modify and/or append platform DT's existing
150 // node and/or properties. The enforcement is for compatibility reason.
151 fn locate_overlay_target_path(&self, dtbo_node_path: &CStr) -> Result<CString> {
152 let dtbo_node_path_bytes = dtbo_node_path.to_bytes();
153 if dtbo_node_path_bytes.first() != Some(&b'/') {
154 return Err(DeviceAssignmentError::UnsupportedOverlayTarget);
155 }
156
157 let node = self.0.node(dtbo_node_path)?.ok_or(DeviceAssignmentError::InvalidSymbols)?;
158
159 let fragment_node = node.supernode_at_depth(1)?;
160 let target_path = fragment_node
Pierre-Clément Tosid701a0b2023-11-07 15:38:59 +0000161 .getprop_str(cstr!("target-path"))?
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900162 .ok_or(DeviceAssignmentError::InvalidDtbo)?;
163 if target_path != cstr!("/") {
164 return Err(DeviceAssignmentError::UnsupportedOverlayTarget);
165 }
166
167 let mut components = dtbo_node_path_bytes
168 .split(|char| *char == b'/')
169 .filter(|&component| !component.is_empty())
170 .skip(1);
171 let overlay_node_name = components.next();
Pierre-Clément Tosid701a0b2023-11-07 15:38:59 +0000172 if overlay_node_name != Some(b"__overlay__") {
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900173 return Err(DeviceAssignmentError::InvalidDtbo);
174 }
175 let mut overlaid_path = Vec::with_capacity(dtbo_node_path_bytes.len());
176 for component in components {
177 overlaid_path.push(b'/');
178 overlaid_path.extend_from_slice(component);
179 }
180 overlaid_path.push(b'\0');
181
182 Ok(CString::from_vec_with_nul(overlaid_path).unwrap())
183 }
184}
185
Jaewan Kimc39974e2023-12-02 01:13:30 +0900186fn is_overlayable_node(dtbo_path: &CStr) -> bool {
187 dtbo_path
188 .to_bytes()
189 .split(|char| *char == b'/')
190 .filter(|&component| !component.is_empty())
191 .nth(1)
192 .map_or(false, |name| name == b"__overlay__")
193}
194
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900195impl AsRef<Fdt> for VmDtbo {
196 fn as_ref(&self) -> &Fdt {
197 &self.0
198 }
199}
200
201impl AsMut<Fdt> for VmDtbo {
202 fn as_mut(&mut self) -> &mut Fdt {
203 &mut self.0
204 }
205}
206
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900207#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
208struct PvIommu {
209 // ID from pvIOMMU node
210 id: u32,
211}
212
213impl PvIommu {
214 fn parse(node: &FdtNode) -> Result<Self> {
Jaewan Kima9200492023-11-21 20:45:31 +0900215 let iommu_cells = node
216 .getprop_u32(cstr!("#iommu-cells"))?
217 .ok_or(DeviceAssignmentError::InvalidPvIommu)?;
218 // Ensures <#iommu-cells> = 1. It means that `<iommus>` entry contains pair of
219 // (pvIOMMU ID, vSID)
220 if iommu_cells != 1 {
221 return Err(DeviceAssignmentError::InvalidPvIommu);
222 }
223 let id = node.getprop_u32(cstr!("id"))?.ok_or(DeviceAssignmentError::InvalidPvIommu)?;
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900224 Ok(Self { id })
225 }
226}
227
Jaewan Kima9200492023-11-21 20:45:31 +0900228#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
229struct Vsid(u32);
230
Jaewan Kim52477ae2023-11-21 21:20:52 +0900231#[derive(Debug, Copy, Clone, Eq, PartialEq)]
232struct DeviceReg {
233 addr: u64,
234 size: u64,
235}
236
237impl TryFrom<Reg<u64>> for DeviceReg {
238 type Error = DeviceAssignmentError;
239
240 fn try_from(reg: Reg<u64>) -> Result<Self> {
241 Ok(Self { addr: reg.addr, size: reg.size.ok_or(DeviceAssignmentError::InvalidReg)? })
242 }
243}
244
245fn parse_node_reg(node: &FdtNode) -> Result<Vec<DeviceReg>> {
246 node.reg()?
247 .ok_or(DeviceAssignmentError::InvalidReg)?
248 .map(DeviceReg::try_from)
249 .collect::<Result<Vec<_>>>()
250}
251
252fn to_be_bytes(reg: &[DeviceReg]) -> Vec<u8> {
253 let mut reg_cells = vec![];
254 for x in reg {
255 reg_cells.extend_from_slice(&x.addr.to_be_bytes());
256 reg_cells.extend_from_slice(&x.size.to_be_bytes());
257 }
258 reg_cells
259}
260
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900261/// Assigned device information parsed from crosvm DT.
262/// Keeps everything in the owned data because underlying FDT will be reused for platform DT.
263#[derive(Debug, Eq, PartialEq)]
264struct AssignedDeviceInfo {
265 // Node path of assigned device (e.g. "/rng")
266 node_path: CString,
267 // DTBO node path of the assigned device (e.g. "/fragment@rng/__overlay__/rng")
268 dtbo_node_path: CString,
269 // <reg> property from the crosvm DT
Jaewan Kim52477ae2023-11-21 21:20:52 +0900270 reg: Vec<DeviceReg>,
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900271 // <interrupts> property from the crosvm DT
272 interrupts: Vec<u8>,
Jaewan Kima9200492023-11-21 20:45:31 +0900273 // Parsed <iommus> property from the crosvm DT. Tuple of PvIommu and vSID.
274 iommus: Vec<(PvIommu, Vsid)>,
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900275}
276
277impl AssignedDeviceInfo {
Jaewan Kim52477ae2023-11-21 21:20:52 +0900278 fn parse_reg(
279 node: &FdtNode,
280 hypervisor: &dyn DeviceAssigningHypervisor,
281 ) -> Result<Vec<DeviceReg>> {
282 let device_reg = parse_node_reg(node)?;
283 // TODO(b/277993056): Valid the result back with physical reg
284 for reg in &device_reg {
285 hypervisor.get_phys_mmio_token(reg.addr, reg.size).map_err(|e| {
286 let name = node.name();
287 error!("Failed to validate device <reg>, error={e:?}, name={name:?}, reg={reg:?}");
288 DeviceAssignmentError::InvalidReg
289 })?;
290 }
291 Ok(device_reg)
292 }
293
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900294 fn parse_interrupts(node: &FdtNode) -> Result<Vec<u8>> {
295 // Validation: Validate if interrupts cell numbers are multiple of #interrupt-cells.
296 // We can't know how many interrupts would exist.
297 let interrupts_cells = node
Pierre-Clément Tosid701a0b2023-11-07 15:38:59 +0000298 .getprop_cells(cstr!("interrupts"))?
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900299 .ok_or(DeviceAssignmentError::InvalidInterrupts)?
300 .count();
301 if interrupts_cells % CELLS_PER_INTERRUPT != 0 {
302 return Err(DeviceAssignmentError::InvalidInterrupts);
303 }
304
305 // Once validated, keep the raw bytes so patch can be done with setprop()
Pierre-Clément Tosid701a0b2023-11-07 15:38:59 +0000306 Ok(node.getprop(cstr!("interrupts")).unwrap().unwrap().into())
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900307 }
308
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900309 // TODO(b/277993056): Also validate /__local_fixups__ to ensure that <iommus> has phandle.
Jaewan Kima9200492023-11-21 20:45:31 +0900310 fn parse_iommus(
311 node: &FdtNode,
312 pviommus: &BTreeMap<Phandle, PvIommu>,
Jaewan Kim52477ae2023-11-21 21:20:52 +0900313 hypervisor: &dyn DeviceAssigningHypervisor,
Jaewan Kima9200492023-11-21 20:45:31 +0900314 ) -> Result<Vec<(PvIommu, Vsid)>> {
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900315 let mut iommus = vec![];
Jaewan Kima9200492023-11-21 20:45:31 +0900316 let Some(mut cells) = node.getprop_cells(cstr!("iommus"))? else {
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900317 return Ok(iommus);
318 };
Jaewan Kima9200492023-11-21 20:45:31 +0900319 while let Some(cell) = cells.next() {
320 // Parse pvIOMMU ID
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900321 let phandle = Phandle::try_from(cell).or(Err(DeviceAssignmentError::InvalidIommus))?;
322 let pviommu = pviommus.get(&phandle).ok_or(DeviceAssignmentError::InvalidIommus)?;
Jaewan Kima9200492023-11-21 20:45:31 +0900323
324 // Parse vSID
325 let Some(cell) = cells.next() else {
326 return Err(DeviceAssignmentError::InvalidIommus);
327 };
328 let vsid = Vsid(cell);
329
Jaewan Kim52477ae2023-11-21 21:20:52 +0900330 // TODO(b/277993056): Valid the result back with phys iommu id and sid..
331 hypervisor
332 .get_phys_iommu_token(pviommu.id.into(), vsid.0.into())
333 .map_err(|e| {
334 let name = node.name().unwrap_or_default();
335 error!("Failed to validate device <iommus>, error={e:?}, name={name:?}, pviommu={pviommu:?}, vsid={:?}", vsid.0);
336 DeviceAssignmentError::InvalidIommus
337 })?;
338
Jaewan Kima9200492023-11-21 20:45:31 +0900339 iommus.push((*pviommu, vsid));
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900340 }
341 Ok(iommus)
342 }
343
344 fn parse(
345 fdt: &Fdt,
346 vm_dtbo: &VmDtbo,
347 dtbo_node_path: &CStr,
348 pviommus: &BTreeMap<Phandle, PvIommu>,
Jaewan Kim52477ae2023-11-21 21:20:52 +0900349 hypervisor: &dyn DeviceAssigningHypervisor,
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900350 ) -> Result<Option<Self>> {
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900351 let node_path = vm_dtbo.locate_overlay_target_path(dtbo_node_path)?;
352
353 let Some(node) = fdt.node(&node_path)? else { return Ok(None) };
354
Jaewan Kim52477ae2023-11-21 21:20:52 +0900355 let reg = Self::parse_reg(&node, hypervisor)?;
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900356 let interrupts = Self::parse_interrupts(&node)?;
Jaewan Kim52477ae2023-11-21 21:20:52 +0900357 let iommus = Self::parse_iommus(&node, pviommus, hypervisor)?;
358 Ok(Some(Self { node_path, dtbo_node_path: dtbo_node_path.into(), reg, interrupts, iommus }))
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900359 }
360
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900361 fn patch(&self, fdt: &mut Fdt, pviommu_phandles: &BTreeMap<PvIommu, Phandle>) -> Result<()> {
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900362 let mut dst = fdt.node_mut(&self.node_path)?.unwrap();
Jaewan Kim52477ae2023-11-21 21:20:52 +0900363 dst.setprop(cstr!("reg"), &to_be_bytes(&self.reg))?;
Pierre-Clément Tosid701a0b2023-11-07 15:38:59 +0000364 dst.setprop(cstr!("interrupts"), &self.interrupts)?;
Jaewan Kima9200492023-11-21 20:45:31 +0900365 let mut iommus = Vec::with_capacity(8 * self.iommus.len());
366 for (pviommu, vsid) in &self.iommus {
367 let phandle = pviommu_phandles.get(pviommu).unwrap();
368 iommus.extend_from_slice(&u32::from(*phandle).to_be_bytes());
369 iommus.extend_from_slice(&vsid.0.to_be_bytes());
370 }
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900371 dst.setprop(cstr!("iommus"), &iommus)?;
372
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900373 Ok(())
374 }
375}
376
377#[derive(Debug, Default, Eq, PartialEq)]
378pub struct DeviceAssignmentInfo {
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900379 pviommus: BTreeSet<PvIommu>,
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900380 assigned_devices: Vec<AssignedDeviceInfo>,
381 filtered_dtbo_paths: Vec<CString>,
382}
383
384impl DeviceAssignmentInfo {
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900385 const PVIOMMU_COMPATIBLE: &CStr = cstr!("pkvm,pviommu");
386
387 /// Parses pvIOMMUs in fdt
388 // Note: This will validate pvIOMMU ids' uniqueness, even when unassigned.
389 fn parse_pviommus(fdt: &Fdt) -> Result<BTreeMap<Phandle, PvIommu>> {
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900390 let mut pviommus = BTreeMap::new();
391 for compatible in fdt.compatible_nodes(Self::PVIOMMU_COMPATIBLE)? {
392 let Some(phandle) = compatible.get_phandle()? else {
393 continue; // Skips unreachable pvIOMMU node
394 };
395 let pviommu = PvIommu::parse(&compatible)?;
396 if pviommus.insert(phandle, pviommu).is_some() {
397 return Err(FdtError::BadPhandle.into());
398 }
399 }
400 Ok(pviommus)
401 }
402
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900403 /// Parses fdt and vm_dtbo, and creates new DeviceAssignmentInfo
404 // TODO(b/277993056): Parse __local_fixups__
405 // TODO(b/277993056): Parse __fixups__
Jaewan Kim52477ae2023-11-21 21:20:52 +0900406 pub fn parse(
407 fdt: &Fdt,
408 vm_dtbo: &VmDtbo,
409 hypervisor: &dyn DeviceAssigningHypervisor,
410 ) -> Result<Option<Self>> {
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900411 let Some(symbols_node) = vm_dtbo.as_ref().symbols()? else {
412 // /__symbols__ should contain all assignable devices.
413 // If empty, then nothing can be assigned.
414 return Ok(None);
415 };
416
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900417 let pviommus = Self::parse_pviommus(fdt)?;
418 let unique_pviommus: BTreeSet<_> = pviommus.values().cloned().collect();
419 if pviommus.len() != unique_pviommus.len() {
420 return Err(DeviceAssignmentError::DuplicatedPvIommuIds);
421 }
422
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900423 let mut assigned_devices = vec![];
424 let mut filtered_dtbo_paths = vec![];
425 for symbol_prop in symbols_node.properties()? {
426 let symbol_prop_value = symbol_prop.value()?;
427 let dtbo_node_path = CStr::from_bytes_with_nul(symbol_prop_value)
428 .or(Err(DeviceAssignmentError::InvalidSymbols))?;
Jaewan Kimc39974e2023-12-02 01:13:30 +0900429 if !is_overlayable_node(dtbo_node_path) {
430 continue;
431 }
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900432 let assigned_device =
Jaewan Kim52477ae2023-11-21 21:20:52 +0900433 AssignedDeviceInfo::parse(fdt, vm_dtbo, dtbo_node_path, &pviommus, hypervisor)?;
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900434 if let Some(assigned_device) = assigned_device {
435 assigned_devices.push(assigned_device);
436 } else {
437 filtered_dtbo_paths.push(dtbo_node_path.into());
438 }
439 }
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900440 if assigned_devices.is_empty() {
441 return Ok(None);
442 }
Jaewan Kimc39974e2023-12-02 01:13:30 +0900443
444 // Clean up any nodes that wouldn't be overlaid but may contain reference to filtered nodes.
445 // Otherwise, `fdt_apply_overlay()` would fail because of missing phandle reference.
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900446 filtered_dtbo_paths.push(CString::new("/__symbols__").unwrap());
Jaewan Kimc39974e2023-12-02 01:13:30 +0900447 // TODO(b/277993056): Also filter other unused nodes/props in __local_fixups__
448 filtered_dtbo_paths.push(CString::new("/__local_fixups__/host").unwrap());
449
450 // Note: Any node without __overlay__ will be ignored by fdt_apply_overlay,
451 // so doesn't need to be filtered.
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900452
453 Ok(Some(Self { pviommus: unique_pviommus, assigned_devices, filtered_dtbo_paths }))
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900454 }
455
456 /// Filters VM DTBO to only contain necessary information for booting pVM
457 /// In detail, this will remove followings by setting nop node / nop property.
458 /// - Removes unassigned devices
459 /// - Removes /__symbols__ node
460 // TODO(b/277993056): remove unused dependencies in VM DTBO.
461 // TODO(b/277993056): remove supernodes' properties.
462 // TODO(b/277993056): remove unused alises.
463 pub fn filter(&self, vm_dtbo: &mut VmDtbo) -> Result<()> {
464 let vm_dtbo = vm_dtbo.as_mut();
465
466 // Filters unused node in assigned devices
467 for filtered_dtbo_path in &self.filtered_dtbo_paths {
468 let node = vm_dtbo.node_mut(filtered_dtbo_path).unwrap().unwrap();
469 node.nop()?;
470 }
471
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900472 Ok(())
473 }
474
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900475 fn patch_pviommus(&self, fdt: &mut Fdt) -> Result<BTreeMap<PvIommu, Phandle>> {
476 let mut compatible = fdt.root_mut()?.next_compatible(Self::PVIOMMU_COMPATIBLE)?;
477 let mut pviommu_phandles = BTreeMap::new();
478
479 for pviommu in &self.pviommus {
480 let mut node = compatible.ok_or(DeviceAssignmentError::TooManyPvIommu)?;
481 let phandle = node.as_node().get_phandle()?.ok_or(DeviceAssignmentError::Internal)?;
482 node.setprop_inplace(cstr!("id"), &pviommu.id.to_be_bytes())?;
483 if pviommu_phandles.insert(*pviommu, phandle).is_some() {
484 return Err(DeviceAssignmentError::Internal);
485 }
486 compatible = node.next_compatible(Self::PVIOMMU_COMPATIBLE)?;
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900487 }
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900488
489 // Filters pre-populated but unassigned pvIOMMUs.
490 while let Some(filtered_pviommu) = compatible {
491 compatible = filtered_pviommu.delete_and_next_compatible(Self::PVIOMMU_COMPATIBLE)?;
492 }
493
494 Ok(pviommu_phandles)
495 }
496
497 pub fn patch(&self, fdt: &mut Fdt) -> Result<()> {
498 let pviommu_phandles = self.patch_pviommus(fdt)?;
499
500 // Patches assigned devices
501 for device in &self.assigned_devices {
502 device.patch(fdt, &pviommu_phandles)?;
503 }
504
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900505 Ok(())
506 }
507}
508
509#[cfg(test)]
510mod tests {
511 use super::*;
Jaewan Kim52477ae2023-11-21 21:20:52 +0900512 use alloc::collections::{BTreeMap, BTreeSet};
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900513 use std::fs;
514
515 const VM_DTBO_FILE_PATH: &str = "test_pvmfw_devices_vm_dtbo.dtbo";
516 const VM_DTBO_WITHOUT_SYMBOLS_FILE_PATH: &str =
517 "test_pvmfw_devices_vm_dtbo_without_symbols.dtbo";
Jaewan Kima67e36a2023-11-29 16:50:23 +0900518 const FDT_WITHOUT_IOMMUS_FILE_PATH: &str = "test_pvmfw_devices_without_iommus.dtb";
Jaewan Kim52477ae2023-11-21 21:20:52 +0900519 const FDT_WITHOUT_DEVICE_FILE_PATH: &str = "test_pvmfw_devices_without_device.dtb";
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900520 const FDT_FILE_PATH: &str = "test_pvmfw_devices_with_rng.dtb";
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900521 const FDT_WITH_MULTIPLE_DEVICES_IOMMUS_FILE_PATH: &str =
522 "test_pvmfw_devices_with_multiple_devices_iommus.dtb";
523 const FDT_WITH_IOMMU_SHARING: &str = "test_pvmfw_devices_with_iommu_sharing.dtb";
524 const FDT_WITH_IOMMU_ID_CONFLICT: &str = "test_pvmfw_devices_with_iommu_id_conflict.dtb";
525
Jaewan Kim52477ae2023-11-21 21:20:52 +0900526 #[derive(Debug, Default)]
527 struct MockHypervisor {
528 mmio_tokens: BTreeMap<(u64, u64), u64>,
529 iommu_tokens: BTreeMap<(u64, u64), (u64, u64)>,
530 }
531
532 impl DeviceAssigningHypervisor for MockHypervisor {
533 fn get_phys_mmio_token(&self, base_ipa: u64, size: u64) -> hyp::Result<u64> {
534 Ok(*self.mmio_tokens.get(&(base_ipa, size)).ok_or(hyp::Error::KvmError(
535 hyp::KvmError::InvalidParameter,
536 0xc6000012, /* VENDOR_HYP_KVM_DEV_REQ_MMIO_FUNC_ID */
537 ))?)
538 }
539
540 fn get_phys_iommu_token(&self, pviommu_id: u64, vsid: u64) -> hyp::Result<(u64, u64)> {
541 Ok(*self.iommu_tokens.get(&(pviommu_id, vsid)).ok_or(hyp::Error::KvmError(
542 hyp::KvmError::InvalidParameter,
543 0xc6000013, /* VENDOR_HYP_KVM_DEV_REQ_DMA_FUNC_ID */
544 ))?)
545 }
546 }
547
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900548 #[derive(Debug, Eq, PartialEq)]
549 struct AssignedDeviceNode {
550 path: CString,
551 reg: Vec<u8>,
552 interrupts: Vec<u8>,
Jaewan Kima67e36a2023-11-29 16:50:23 +0900553 iommus: Vec<u32>, // pvIOMMU id and vSID
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900554 }
555
556 impl AssignedDeviceNode {
557 fn parse(fdt: &Fdt, path: &CStr) -> Result<Self> {
558 let Some(node) = fdt.node(path)? else {
559 return Err(FdtError::NotFound.into());
560 };
561
Jaewan Kim52477ae2023-11-21 21:20:52 +0900562 let reg = node.getprop(cstr!("reg"))?.ok_or(DeviceAssignmentError::InvalidReg)?;
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900563 let interrupts = node
564 .getprop(cstr!("interrupts"))?
565 .ok_or(DeviceAssignmentError::InvalidInterrupts)?;
566 let mut iommus = vec![];
Jaewan Kima9200492023-11-21 20:45:31 +0900567 if let Some(mut cells) = node.getprop_cells(cstr!("iommus"))? {
568 while let Some(pviommu_id) = cells.next() {
569 // pvIOMMU id
570 let phandle = Phandle::try_from(pviommu_id)?;
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900571 let pviommu = fdt
572 .node_with_phandle(phandle)?
573 .ok_or(DeviceAssignmentError::InvalidIommus)?;
574 let compatible = pviommu.getprop_str(cstr!("compatible"));
575 if compatible != Ok(Some(cstr!("pkvm,pviommu"))) {
576 return Err(DeviceAssignmentError::InvalidIommus);
577 }
578 let id = pviommu
579 .getprop_u32(cstr!("id"))?
580 .ok_or(DeviceAssignmentError::InvalidIommus)?;
581 iommus.push(id);
Jaewan Kima9200492023-11-21 20:45:31 +0900582
583 // vSID
584 let Some(vsid) = cells.next() else {
585 return Err(DeviceAssignmentError::InvalidIommus);
586 };
587 iommus.push(vsid);
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900588 }
589 }
590 Ok(Self { path: path.into(), reg: reg.into(), interrupts: interrupts.into(), iommus })
591 }
592 }
593
594 fn collect_pviommus(fdt: &Fdt) -> Result<Vec<u32>> {
595 let mut pviommus = BTreeSet::new();
596 for pviommu in fdt.compatible_nodes(cstr!("pkvm,pviommu"))? {
597 if let Ok(Some(id)) = pviommu.getprop_u32(cstr!("id")) {
598 pviommus.insert(id);
599 }
600 }
601 Ok(pviommus.iter().cloned().collect())
602 }
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900603
604 fn into_fdt_prop(native_bytes: Vec<u32>) -> Vec<u8> {
605 let mut v = Vec::with_capacity(native_bytes.len() * 4);
606 for byte in native_bytes {
607 v.extend_from_slice(&byte.to_be_bytes());
608 }
609 v
610 }
611
Jaewan Kim52477ae2023-11-21 21:20:52 +0900612 impl From<[u64; 2]> for DeviceReg {
613 fn from(fdt_cells: [u64; 2]) -> Self {
614 DeviceReg { addr: fdt_cells[0], size: fdt_cells[1] }
615 }
616 }
617
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900618 #[test]
619 fn device_info_new_without_symbols() {
620 let mut fdt_data = fs::read(FDT_FILE_PATH).unwrap();
621 let mut vm_dtbo_data = fs::read(VM_DTBO_WITHOUT_SYMBOLS_FILE_PATH).unwrap();
622 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
623 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
624
Jaewan Kim52477ae2023-11-21 21:20:52 +0900625 let hypervisor: MockHypervisor = Default::default();
626 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor).unwrap();
627 assert_eq!(device_info, None);
628 }
629
630 #[test]
631 fn device_info_new_without_device() {
632 let mut fdt_data = fs::read(FDT_WITHOUT_DEVICE_FILE_PATH).unwrap();
633 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
634 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
635 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
636
637 let hypervisor: MockHypervisor = Default::default();
638 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor).unwrap();
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900639 assert_eq!(device_info, None);
640 }
641
642 #[test]
Jaewan Kima67e36a2023-11-29 16:50:23 +0900643 fn device_info_assigned_info_without_iommus() {
644 let mut fdt_data = fs::read(FDT_WITHOUT_IOMMUS_FILE_PATH).unwrap();
645 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
646 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
647 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
648
Jaewan Kim52477ae2023-11-21 21:20:52 +0900649 let hypervisor = MockHypervisor {
650 mmio_tokens: [((0x9, 0xFF), 0x300)].into(),
651 iommu_tokens: BTreeMap::new(),
652 };
653 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor).unwrap().unwrap();
Jaewan Kima67e36a2023-11-29 16:50:23 +0900654
655 let expected = [AssignedDeviceInfo {
Jaewan Kimc39974e2023-12-02 01:13:30 +0900656 node_path: CString::new("/bus0/backlight").unwrap(),
657 dtbo_node_path: cstr!("/fragment@backlight/__overlay__/bus0/backlight").into(),
Jaewan Kim52477ae2023-11-21 21:20:52 +0900658 reg: vec![[0x9, 0xFF].into()],
Jaewan Kima67e36a2023-11-29 16:50:23 +0900659 interrupts: into_fdt_prop(vec![0x0, 0xF, 0x4]),
660 iommus: vec![],
661 }];
662
663 assert_eq!(device_info.assigned_devices, expected);
664 }
665
666 #[test]
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900667 fn device_info_assigned_info() {
668 let mut fdt_data = fs::read(FDT_FILE_PATH).unwrap();
669 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
670 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
671 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
672
Jaewan Kim52477ae2023-11-21 21:20:52 +0900673 let hypervisor = MockHypervisor {
674 mmio_tokens: [((0x9, 0xFF), 0x12F00000)].into(),
675 iommu_tokens: [((0x4, 0xFF0), (0x12E40000, 0x3))].into(),
676 };
677 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor).unwrap().unwrap();
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900678
679 let expected = [AssignedDeviceInfo {
680 node_path: CString::new("/rng").unwrap(),
681 dtbo_node_path: cstr!("/fragment@rng/__overlay__/rng").into(),
Jaewan Kim52477ae2023-11-21 21:20:52 +0900682 reg: vec![[0x9, 0xFF].into()],
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900683 interrupts: into_fdt_prop(vec![0x0, 0xF, 0x4]),
Jaewan Kima67e36a2023-11-29 16:50:23 +0900684 iommus: vec![(PvIommu { id: 0x4 }, Vsid(0xFF0))],
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900685 }];
686
687 assert_eq!(device_info.assigned_devices, expected);
688 }
689
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900690 // TODO(b/311655051): Test with real once instead of empty FDT.
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900691 #[test]
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900692 fn device_info_new_with_empty_device_tree() {
693 let mut fdt_data = vec![0; pvmfw_fdt_template::RAW.len()];
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900694 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900695 let fdt = Fdt::create_empty_tree(&mut fdt_data).unwrap();
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900696 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
697
Jaewan Kim52477ae2023-11-21 21:20:52 +0900698 let hypervisor: MockHypervisor = Default::default();
699 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor).unwrap();
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900700 assert_eq!(device_info, None);
701 }
702
703 #[test]
704 fn device_info_filter() {
705 let mut fdt_data = fs::read(FDT_FILE_PATH).unwrap();
706 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
707 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
708 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
709
Jaewan Kim52477ae2023-11-21 21:20:52 +0900710 let hypervisor = MockHypervisor {
711 mmio_tokens: [((0x9, 0xFF), 0x12F00000)].into(),
712 iommu_tokens: [((0x4, 0xFF0), (0x12E40000, 0x3))].into(),
713 };
714 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor).unwrap().unwrap();
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900715 device_info.filter(vm_dtbo).unwrap();
716
717 let vm_dtbo = vm_dtbo.as_mut();
718
719 let rng = vm_dtbo.node(cstr!("/fragment@rng/__overlay__/rng")).unwrap();
720 assert_ne!(rng, None);
721
722 let light = vm_dtbo.node(cstr!("/fragment@rng/__overlay__/light")).unwrap();
723 assert_eq!(light, None);
724
Jaewan Kima67e36a2023-11-29 16:50:23 +0900725 let led = vm_dtbo.node(cstr!("/fragment@led/__overlay__/led")).unwrap();
726 assert_eq!(led, None);
727
Jaewan Kimc39974e2023-12-02 01:13:30 +0900728 let backlight =
729 vm_dtbo.node(cstr!("/fragment@backlight/__overlay__/bus0/backlight")).unwrap();
Jaewan Kima67e36a2023-11-29 16:50:23 +0900730 assert_eq!(backlight, None);
731
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900732 let symbols_node = vm_dtbo.symbols().unwrap();
733 assert_eq!(symbols_node, None);
734 }
735
736 #[test]
737 fn device_info_patch() {
Jaewan Kima67e36a2023-11-29 16:50:23 +0900738 let mut fdt_data = fs::read(FDT_WITHOUT_IOMMUS_FILE_PATH).unwrap();
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900739 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
740 let mut data = vec![0_u8; fdt_data.len() + vm_dtbo_data.len()];
741 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
742 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
743 let platform_dt = Fdt::create_empty_tree(data.as_mut_slice()).unwrap();
744
Jaewan Kim52477ae2023-11-21 21:20:52 +0900745 let hypervisor = MockHypervisor {
746 mmio_tokens: [((0x9, 0xFF), 0x300)].into(),
747 iommu_tokens: BTreeMap::new(),
748 };
749 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor).unwrap().unwrap();
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900750 device_info.filter(vm_dtbo).unwrap();
751
752 // SAFETY: Damaged VM DTBO wouldn't be used after this unsafe block.
753 unsafe {
754 platform_dt.apply_overlay(vm_dtbo.as_mut()).unwrap();
755 }
Jaewan Kim0bd637d2023-11-10 13:09:41 +0900756 device_info.patch(platform_dt).unwrap();
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900757
Jaewan Kimc39974e2023-12-02 01:13:30 +0900758 let rng_node = platform_dt.node(cstr!("/bus0/backlight")).unwrap().unwrap();
759 let phandle = rng_node.getprop_u32(cstr!("phandle")).unwrap();
760 assert_ne!(None, phandle);
761
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900762 // Note: Intentionally not using AssignedDeviceNode for matching all props.
Jaewan Kim0bd637d2023-11-10 13:09:41 +0900763 type FdtResult<T> = libfdt::Result<T>;
764 let expected: Vec<(FdtResult<&CStr>, FdtResult<Vec<u8>>)> = vec![
Jaewan Kima67e36a2023-11-29 16:50:23 +0900765 (Ok(cstr!("android,backlight,ignore-gctrl-reset")), Ok(Vec::new())),
766 (Ok(cstr!("compatible")), Ok(Vec::from(*b"android,backlight\0"))),
Jaewan Kim0bd637d2023-11-10 13:09:41 +0900767 (Ok(cstr!("interrupts")), Ok(into_fdt_prop(vec![0x0, 0xF, 0x4]))),
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900768 (Ok(cstr!("iommus")), Ok(Vec::new())),
Jaewan Kimc39974e2023-12-02 01:13:30 +0900769 (Ok(cstr!("phandle")), Ok(into_fdt_prop(vec![phandle.unwrap()]))),
Jaewan Kim0bd637d2023-11-10 13:09:41 +0900770 (Ok(cstr!("reg")), Ok(into_fdt_prop(vec![0x0, 0x9, 0x0, 0xFF]))),
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900771 ];
772
Jaewan Kim0bd637d2023-11-10 13:09:41 +0900773 let mut properties: Vec<_> = rng_node
774 .properties()
775 .unwrap()
776 .map(|prop| (prop.name(), prop.value().map(|x| x.into())))
777 .collect();
778 properties.sort_by(|a, b| {
779 let lhs = a.0.unwrap_or_default();
780 let rhs = b.0.unwrap_or_default();
781 lhs.partial_cmp(rhs).unwrap()
782 });
783
784 assert_eq!(properties, expected);
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900785 }
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900786
787 #[test]
788 fn device_info_overlay_iommu() {
Jaewan Kima67e36a2023-11-29 16:50:23 +0900789 let mut fdt_data = fs::read(FDT_FILE_PATH).unwrap();
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900790 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
791 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
792 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
793 let mut platform_dt_data = pvmfw_fdt_template::RAW.to_vec();
794 platform_dt_data.resize(pvmfw_fdt_template::RAW.len() * 2, 0);
795 let platform_dt = Fdt::from_mut_slice(&mut platform_dt_data).unwrap();
796 platform_dt.unpack().unwrap();
797
Jaewan Kim52477ae2023-11-21 21:20:52 +0900798 let hypervisor = MockHypervisor {
799 mmio_tokens: [((0x9, 0xFF), 0x12F00000)].into(),
800 iommu_tokens: [((0x4, 0xFF0), (0x12E40000, 0x3))].into(),
801 };
802 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor).unwrap().unwrap();
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900803 device_info.filter(vm_dtbo).unwrap();
804
805 // SAFETY: Damaged VM DTBO wouldn't be used after this unsafe block.
806 unsafe {
807 platform_dt.apply_overlay(vm_dtbo.as_mut()).unwrap();
808 }
809 device_info.patch(platform_dt).unwrap();
810
811 let expected = AssignedDeviceNode {
812 path: CString::new("/rng").unwrap(),
813 reg: into_fdt_prop(vec![0x0, 0x9, 0x0, 0xFF]),
814 interrupts: into_fdt_prop(vec![0x0, 0xF, 0x4]),
Jaewan Kima9200492023-11-21 20:45:31 +0900815 iommus: vec![0x4, 0xFF0],
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900816 };
817
818 let node = AssignedDeviceNode::parse(platform_dt, &expected.path);
819 assert_eq!(node, Ok(expected));
820
821 let pviommus = collect_pviommus(platform_dt);
822 assert_eq!(pviommus, Ok(vec![0x4]));
823 }
824
825 #[test]
826 fn device_info_multiple_devices_iommus() {
827 let mut fdt_data = fs::read(FDT_WITH_MULTIPLE_DEVICES_IOMMUS_FILE_PATH).unwrap();
828 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
829 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
830 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
831 let mut platform_dt_data = pvmfw_fdt_template::RAW.to_vec();
832 platform_dt_data.resize(pvmfw_fdt_template::RAW.len() * 2, 0);
833 let platform_dt = Fdt::from_mut_slice(&mut platform_dt_data).unwrap();
834 platform_dt.unpack().unwrap();
835
Jaewan Kim52477ae2023-11-21 21:20:52 +0900836 let hypervisor = MockHypervisor {
837 mmio_tokens: [
838 ((0x9, 0xFF), 0x12F00000),
839 ((0x100, 0x1000), 0xF00000),
840 ((0x200, 0x1000), 0xF10000),
841 ]
842 .into(),
843 iommu_tokens: [
844 ((0x4, 0xFF0), (0x12E40000, 3)),
845 ((0x40, 0xFFA), (0x40000, 0x4)),
846 ((0x50, 0xFFB), (0x50000, 0x5)),
847 ]
848 .into(),
849 };
850 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor).unwrap().unwrap();
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900851 device_info.filter(vm_dtbo).unwrap();
852
853 // SAFETY: Damaged VM DTBO wouldn't be used after this unsafe block.
854 unsafe {
855 platform_dt.apply_overlay(vm_dtbo.as_mut()).unwrap();
856 }
857 device_info.patch(platform_dt).unwrap();
858
859 let expected_devices = [
860 AssignedDeviceNode {
861 path: CString::new("/rng").unwrap(),
862 reg: into_fdt_prop(vec![0x0, 0x9, 0x0, 0xFF]),
863 interrupts: into_fdt_prop(vec![0x0, 0xF, 0x4]),
Jaewan Kima67e36a2023-11-29 16:50:23 +0900864 iommus: vec![0x4, 0xFF0],
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900865 },
866 AssignedDeviceNode {
867 path: CString::new("/light").unwrap(),
Jaewan Kim52477ae2023-11-21 21:20:52 +0900868 reg: into_fdt_prop(vec![0x0, 0x100, 0x0, 0x1000, 0x0, 0x200, 0x0, 0x1000]),
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900869 interrupts: into_fdt_prop(vec![0x0, 0xF, 0x5]),
Jaewan Kima67e36a2023-11-29 16:50:23 +0900870 iommus: vec![0x40, 0xFFA, 0x50, 0xFFB],
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900871 },
872 ];
873
874 for expected in expected_devices {
875 let node = AssignedDeviceNode::parse(platform_dt, &expected.path);
876 assert_eq!(node, Ok(expected));
877 }
878 let pviommus = collect_pviommus(platform_dt);
Jaewan Kima67e36a2023-11-29 16:50:23 +0900879 assert_eq!(pviommus, Ok(vec![0x4, 0x40, 0x50]));
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900880 }
881
882 #[test]
883 fn device_info_iommu_sharing() {
884 let mut fdt_data = fs::read(FDT_WITH_IOMMU_SHARING).unwrap();
885 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
886 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
887 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
888 let mut platform_dt_data = pvmfw_fdt_template::RAW.to_vec();
889 platform_dt_data.resize(pvmfw_fdt_template::RAW.len() * 2, 0);
890 let platform_dt = Fdt::from_mut_slice(&mut platform_dt_data).unwrap();
891 platform_dt.unpack().unwrap();
892
Jaewan Kim52477ae2023-11-21 21:20:52 +0900893 let hypervisor = MockHypervisor {
894 mmio_tokens: [((0x9, 0xFF), 0x12F00000), ((0x100, 0x9), 0x12000000)].into(),
895 iommu_tokens: [((0x4, 0xFF0), (0x12E40000, 3))].into(),
896 };
897 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor).unwrap().unwrap();
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900898 device_info.filter(vm_dtbo).unwrap();
899
900 // SAFETY: Damaged VM DTBO wouldn't be used after this unsafe block.
901 unsafe {
902 platform_dt.apply_overlay(vm_dtbo.as_mut()).unwrap();
903 }
904 device_info.patch(platform_dt).unwrap();
905
906 let expected_devices = [
907 AssignedDeviceNode {
908 path: CString::new("/rng").unwrap(),
909 reg: into_fdt_prop(vec![0x0, 0x9, 0x0, 0xFF]),
910 interrupts: into_fdt_prop(vec![0x0, 0xF, 0x4]),
Jaewan Kima67e36a2023-11-29 16:50:23 +0900911 iommus: vec![0x4, 0xFF0],
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900912 },
913 AssignedDeviceNode {
Jaewan Kima67e36a2023-11-29 16:50:23 +0900914 path: CString::new("/led").unwrap(),
Jaewan Kim52477ae2023-11-21 21:20:52 +0900915 reg: into_fdt_prop(vec![0x0, 0x100, 0x0, 0x9]),
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900916 interrupts: into_fdt_prop(vec![0x0, 0xF, 0x5]),
Jaewan Kima67e36a2023-11-29 16:50:23 +0900917 iommus: vec![0x4, 0xFF0],
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900918 },
919 ];
920
921 for expected in expected_devices {
922 let node = AssignedDeviceNode::parse(platform_dt, &expected.path);
923 assert_eq!(node, Ok(expected));
924 }
925
926 let pviommus = collect_pviommus(platform_dt);
Jaewan Kima67e36a2023-11-29 16:50:23 +0900927 assert_eq!(pviommus, Ok(vec![0x4]));
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900928 }
929
930 #[test]
931 fn device_info_iommu_id_conflict() {
932 let mut fdt_data = fs::read(FDT_WITH_IOMMU_ID_CONFLICT).unwrap();
933 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
934 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
935 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
936
Jaewan Kim52477ae2023-11-21 21:20:52 +0900937 let hypervisor = MockHypervisor {
938 mmio_tokens: [((0x9, 0xFF), 0x12F00000)].into(),
939 iommu_tokens: [((0x4, 0xFF0), (0x12E40000, 0x3))].into(),
940 };
941 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor);
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900942
943 assert_eq!(device_info, Err(DeviceAssignmentError::DuplicatedPvIommuIds));
944 }
Jaewan Kim52477ae2023-11-21 21:20:52 +0900945
946 #[test]
947 fn device_info_invalid_reg() {
948 let mut fdt_data = fs::read(FDT_FILE_PATH).unwrap();
949 let mut vm_dtbo_data = fs::read(VM_DTBO_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
953 let hypervisor = MockHypervisor {
954 mmio_tokens: BTreeMap::new(),
955 iommu_tokens: [((0x4, 0xFF0), (0x12E40000, 0x3))].into(),
956 };
957 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor);
958
959 assert_eq!(device_info, Err(DeviceAssignmentError::InvalidReg));
960 }
961
962 #[test]
963 fn device_info_invalid_iommus() {
964 let mut fdt_data = fs::read(FDT_FILE_PATH).unwrap();
965 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
966 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
967 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
968
969 let hypervisor = MockHypervisor {
970 mmio_tokens: [((0x9, 0xFF), 0x12F00000)].into(),
971 iommu_tokens: BTreeMap::new(),
972 };
973 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor);
974
975 assert_eq!(device_info, Err(DeviceAssignmentError::InvalidIommus));
976 }
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900977}