blob: 19ace5f22adaa8c7fb0640fe970cf8ef164916a6 [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
186impl AsRef<Fdt> for VmDtbo {
187 fn as_ref(&self) -> &Fdt {
188 &self.0
189 }
190}
191
192impl AsMut<Fdt> for VmDtbo {
193 fn as_mut(&mut self) -> &mut Fdt {
194 &mut self.0
195 }
196}
197
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900198#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
199struct PvIommu {
200 // ID from pvIOMMU node
201 id: u32,
202}
203
204impl PvIommu {
205 fn parse(node: &FdtNode) -> Result<Self> {
Jaewan Kima9200492023-11-21 20:45:31 +0900206 let iommu_cells = node
207 .getprop_u32(cstr!("#iommu-cells"))?
208 .ok_or(DeviceAssignmentError::InvalidPvIommu)?;
209 // Ensures <#iommu-cells> = 1. It means that `<iommus>` entry contains pair of
210 // (pvIOMMU ID, vSID)
211 if iommu_cells != 1 {
212 return Err(DeviceAssignmentError::InvalidPvIommu);
213 }
214 let id = node.getprop_u32(cstr!("id"))?.ok_or(DeviceAssignmentError::InvalidPvIommu)?;
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900215 Ok(Self { id })
216 }
217}
218
Jaewan Kima9200492023-11-21 20:45:31 +0900219#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
220struct Vsid(u32);
221
Jaewan Kim52477ae2023-11-21 21:20:52 +0900222#[derive(Debug, Copy, Clone, Eq, PartialEq)]
223struct DeviceReg {
224 addr: u64,
225 size: u64,
226}
227
228impl TryFrom<Reg<u64>> for DeviceReg {
229 type Error = DeviceAssignmentError;
230
231 fn try_from(reg: Reg<u64>) -> Result<Self> {
232 Ok(Self { addr: reg.addr, size: reg.size.ok_or(DeviceAssignmentError::InvalidReg)? })
233 }
234}
235
236fn parse_node_reg(node: &FdtNode) -> Result<Vec<DeviceReg>> {
237 node.reg()?
238 .ok_or(DeviceAssignmentError::InvalidReg)?
239 .map(DeviceReg::try_from)
240 .collect::<Result<Vec<_>>>()
241}
242
243fn to_be_bytes(reg: &[DeviceReg]) -> Vec<u8> {
244 let mut reg_cells = vec![];
245 for x in reg {
246 reg_cells.extend_from_slice(&x.addr.to_be_bytes());
247 reg_cells.extend_from_slice(&x.size.to_be_bytes());
248 }
249 reg_cells
250}
251
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900252/// Assigned device information parsed from crosvm DT.
253/// Keeps everything in the owned data because underlying FDT will be reused for platform DT.
254#[derive(Debug, Eq, PartialEq)]
255struct AssignedDeviceInfo {
256 // Node path of assigned device (e.g. "/rng")
257 node_path: CString,
258 // DTBO node path of the assigned device (e.g. "/fragment@rng/__overlay__/rng")
259 dtbo_node_path: CString,
260 // <reg> property from the crosvm DT
Jaewan Kim52477ae2023-11-21 21:20:52 +0900261 reg: Vec<DeviceReg>,
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900262 // <interrupts> property from the crosvm DT
263 interrupts: Vec<u8>,
Jaewan Kima9200492023-11-21 20:45:31 +0900264 // Parsed <iommus> property from the crosvm DT. Tuple of PvIommu and vSID.
265 iommus: Vec<(PvIommu, Vsid)>,
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900266}
267
268impl AssignedDeviceInfo {
Jaewan Kim52477ae2023-11-21 21:20:52 +0900269 fn parse_reg(
270 node: &FdtNode,
271 hypervisor: &dyn DeviceAssigningHypervisor,
272 ) -> Result<Vec<DeviceReg>> {
273 let device_reg = parse_node_reg(node)?;
274 // TODO(b/277993056): Valid the result back with physical reg
275 for reg in &device_reg {
276 hypervisor.get_phys_mmio_token(reg.addr, reg.size).map_err(|e| {
277 let name = node.name();
278 error!("Failed to validate device <reg>, error={e:?}, name={name:?}, reg={reg:?}");
279 DeviceAssignmentError::InvalidReg
280 })?;
281 }
282 Ok(device_reg)
283 }
284
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900285 fn parse_interrupts(node: &FdtNode) -> Result<Vec<u8>> {
286 // Validation: Validate if interrupts cell numbers are multiple of #interrupt-cells.
287 // We can't know how many interrupts would exist.
288 let interrupts_cells = node
Pierre-Clément Tosid701a0b2023-11-07 15:38:59 +0000289 .getprop_cells(cstr!("interrupts"))?
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900290 .ok_or(DeviceAssignmentError::InvalidInterrupts)?
291 .count();
292 if interrupts_cells % CELLS_PER_INTERRUPT != 0 {
293 return Err(DeviceAssignmentError::InvalidInterrupts);
294 }
295
296 // Once validated, keep the raw bytes so patch can be done with setprop()
Pierre-Clément Tosid701a0b2023-11-07 15:38:59 +0000297 Ok(node.getprop(cstr!("interrupts")).unwrap().unwrap().into())
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900298 }
299
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900300 // TODO(b/277993056): Also validate /__local_fixups__ to ensure that <iommus> has phandle.
Jaewan Kima9200492023-11-21 20:45:31 +0900301 fn parse_iommus(
302 node: &FdtNode,
303 pviommus: &BTreeMap<Phandle, PvIommu>,
Jaewan Kim52477ae2023-11-21 21:20:52 +0900304 hypervisor: &dyn DeviceAssigningHypervisor,
Jaewan Kima9200492023-11-21 20:45:31 +0900305 ) -> Result<Vec<(PvIommu, Vsid)>> {
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900306 let mut iommus = vec![];
Jaewan Kima9200492023-11-21 20:45:31 +0900307 let Some(mut cells) = node.getprop_cells(cstr!("iommus"))? else {
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900308 return Ok(iommus);
309 };
Jaewan Kima9200492023-11-21 20:45:31 +0900310 while let Some(cell) = cells.next() {
311 // Parse pvIOMMU ID
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900312 let phandle = Phandle::try_from(cell).or(Err(DeviceAssignmentError::InvalidIommus))?;
313 let pviommu = pviommus.get(&phandle).ok_or(DeviceAssignmentError::InvalidIommus)?;
Jaewan Kima9200492023-11-21 20:45:31 +0900314
315 // Parse vSID
316 let Some(cell) = cells.next() else {
317 return Err(DeviceAssignmentError::InvalidIommus);
318 };
319 let vsid = Vsid(cell);
320
Jaewan Kim52477ae2023-11-21 21:20:52 +0900321 // TODO(b/277993056): Valid the result back with phys iommu id and sid..
322 hypervisor
323 .get_phys_iommu_token(pviommu.id.into(), vsid.0.into())
324 .map_err(|e| {
325 let name = node.name().unwrap_or_default();
326 error!("Failed to validate device <iommus>, error={e:?}, name={name:?}, pviommu={pviommu:?}, vsid={:?}", vsid.0);
327 DeviceAssignmentError::InvalidIommus
328 })?;
329
Jaewan Kima9200492023-11-21 20:45:31 +0900330 iommus.push((*pviommu, vsid));
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900331 }
332 Ok(iommus)
333 }
334
335 fn parse(
336 fdt: &Fdt,
337 vm_dtbo: &VmDtbo,
338 dtbo_node_path: &CStr,
339 pviommus: &BTreeMap<Phandle, PvIommu>,
Jaewan Kim52477ae2023-11-21 21:20:52 +0900340 hypervisor: &dyn DeviceAssigningHypervisor,
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900341 ) -> Result<Option<Self>> {
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900342 let node_path = vm_dtbo.locate_overlay_target_path(dtbo_node_path)?;
343
344 let Some(node) = fdt.node(&node_path)? else { return Ok(None) };
345
Jaewan Kim52477ae2023-11-21 21:20:52 +0900346 let reg = Self::parse_reg(&node, hypervisor)?;
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900347 let interrupts = Self::parse_interrupts(&node)?;
Jaewan Kim52477ae2023-11-21 21:20:52 +0900348 let iommus = Self::parse_iommus(&node, pviommus, hypervisor)?;
349 Ok(Some(Self { node_path, dtbo_node_path: dtbo_node_path.into(), reg, interrupts, iommus }))
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900350 }
351
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900352 fn patch(&self, fdt: &mut Fdt, pviommu_phandles: &BTreeMap<PvIommu, Phandle>) -> Result<()> {
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900353 let mut dst = fdt.node_mut(&self.node_path)?.unwrap();
Jaewan Kim52477ae2023-11-21 21:20:52 +0900354 dst.setprop(cstr!("reg"), &to_be_bytes(&self.reg))?;
Pierre-Clément Tosid701a0b2023-11-07 15:38:59 +0000355 dst.setprop(cstr!("interrupts"), &self.interrupts)?;
Jaewan Kima9200492023-11-21 20:45:31 +0900356 let mut iommus = Vec::with_capacity(8 * self.iommus.len());
357 for (pviommu, vsid) in &self.iommus {
358 let phandle = pviommu_phandles.get(pviommu).unwrap();
359 iommus.extend_from_slice(&u32::from(*phandle).to_be_bytes());
360 iommus.extend_from_slice(&vsid.0.to_be_bytes());
361 }
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900362 dst.setprop(cstr!("iommus"), &iommus)?;
363
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900364 Ok(())
365 }
366}
367
368#[derive(Debug, Default, Eq, PartialEq)]
369pub struct DeviceAssignmentInfo {
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900370 pviommus: BTreeSet<PvIommu>,
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900371 assigned_devices: Vec<AssignedDeviceInfo>,
372 filtered_dtbo_paths: Vec<CString>,
373}
374
375impl DeviceAssignmentInfo {
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900376 const PVIOMMU_COMPATIBLE: &CStr = cstr!("pkvm,pviommu");
377
378 /// Parses pvIOMMUs in fdt
379 // Note: This will validate pvIOMMU ids' uniqueness, even when unassigned.
380 fn parse_pviommus(fdt: &Fdt) -> Result<BTreeMap<Phandle, PvIommu>> {
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900381 let mut pviommus = BTreeMap::new();
382 for compatible in fdt.compatible_nodes(Self::PVIOMMU_COMPATIBLE)? {
383 let Some(phandle) = compatible.get_phandle()? else {
384 continue; // Skips unreachable pvIOMMU node
385 };
386 let pviommu = PvIommu::parse(&compatible)?;
387 if pviommus.insert(phandle, pviommu).is_some() {
388 return Err(FdtError::BadPhandle.into());
389 }
390 }
391 Ok(pviommus)
392 }
393
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900394 /// Parses fdt and vm_dtbo, and creates new DeviceAssignmentInfo
395 // TODO(b/277993056): Parse __local_fixups__
396 // TODO(b/277993056): Parse __fixups__
Jaewan Kim52477ae2023-11-21 21:20:52 +0900397 pub fn parse(
398 fdt: &Fdt,
399 vm_dtbo: &VmDtbo,
400 hypervisor: &dyn DeviceAssigningHypervisor,
401 ) -> Result<Option<Self>> {
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900402 let Some(symbols_node) = vm_dtbo.as_ref().symbols()? else {
403 // /__symbols__ should contain all assignable devices.
404 // If empty, then nothing can be assigned.
405 return Ok(None);
406 };
407
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900408 let pviommus = Self::parse_pviommus(fdt)?;
409 let unique_pviommus: BTreeSet<_> = pviommus.values().cloned().collect();
410 if pviommus.len() != unique_pviommus.len() {
411 return Err(DeviceAssignmentError::DuplicatedPvIommuIds);
412 }
413
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900414 let mut assigned_devices = vec![];
415 let mut filtered_dtbo_paths = vec![];
416 for symbol_prop in symbols_node.properties()? {
417 let symbol_prop_value = symbol_prop.value()?;
418 let dtbo_node_path = CStr::from_bytes_with_nul(symbol_prop_value)
419 .or(Err(DeviceAssignmentError::InvalidSymbols))?;
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900420 let assigned_device =
Jaewan Kim52477ae2023-11-21 21:20:52 +0900421 AssignedDeviceInfo::parse(fdt, vm_dtbo, dtbo_node_path, &pviommus, hypervisor)?;
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900422 if let Some(assigned_device) = assigned_device {
423 assigned_devices.push(assigned_device);
424 } else {
425 filtered_dtbo_paths.push(dtbo_node_path.into());
426 }
427 }
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900428 if assigned_devices.is_empty() {
429 return Ok(None);
430 }
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900431 filtered_dtbo_paths.push(CString::new("/__symbols__").unwrap());
432
433 Ok(Some(Self { pviommus: unique_pviommus, assigned_devices, filtered_dtbo_paths }))
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900434 }
435
436 /// Filters VM DTBO to only contain necessary information for booting pVM
437 /// In detail, this will remove followings by setting nop node / nop property.
438 /// - Removes unassigned devices
439 /// - Removes /__symbols__ node
440 // TODO(b/277993056): remove unused dependencies in VM DTBO.
441 // TODO(b/277993056): remove supernodes' properties.
442 // TODO(b/277993056): remove unused alises.
443 pub fn filter(&self, vm_dtbo: &mut VmDtbo) -> Result<()> {
444 let vm_dtbo = vm_dtbo.as_mut();
445
446 // Filters unused node in assigned devices
447 for filtered_dtbo_path in &self.filtered_dtbo_paths {
448 let node = vm_dtbo.node_mut(filtered_dtbo_path).unwrap().unwrap();
449 node.nop()?;
450 }
451
Pierre-Clément Tosifc3e8b52023-11-08 14:30:29 +0000452 // Filters pvmfw-specific properties in assigned device node.
453 const FILTERED_VM_DTBO_PROP: [&CStr; 3] = [
454 cstr!("android,pvmfw,phy-reg"),
455 cstr!("android,pvmfw,phy-iommu"),
456 cstr!("android,pvmfw,phy-sid"),
457 ];
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900458 for assigned_device in &self.assigned_devices {
459 let mut node = vm_dtbo.node_mut(&assigned_device.dtbo_node_path).unwrap().unwrap();
460 for prop in FILTERED_VM_DTBO_PROP {
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900461 match node.nop_property(prop) {
462 Err(FdtError::NotFound) => Ok(()), // allows not exists
463 other => other,
464 }?;
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900465 }
466 }
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900467
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900468 Ok(())
469 }
470
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900471 fn patch_pviommus(&self, fdt: &mut Fdt) -> Result<BTreeMap<PvIommu, Phandle>> {
472 let mut compatible = fdt.root_mut()?.next_compatible(Self::PVIOMMU_COMPATIBLE)?;
473 let mut pviommu_phandles = BTreeMap::new();
474
475 for pviommu in &self.pviommus {
476 let mut node = compatible.ok_or(DeviceAssignmentError::TooManyPvIommu)?;
477 let phandle = node.as_node().get_phandle()?.ok_or(DeviceAssignmentError::Internal)?;
478 node.setprop_inplace(cstr!("id"), &pviommu.id.to_be_bytes())?;
479 if pviommu_phandles.insert(*pviommu, phandle).is_some() {
480 return Err(DeviceAssignmentError::Internal);
481 }
482 compatible = node.next_compatible(Self::PVIOMMU_COMPATIBLE)?;
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900483 }
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900484
485 // Filters pre-populated but unassigned pvIOMMUs.
486 while let Some(filtered_pviommu) = compatible {
487 compatible = filtered_pviommu.delete_and_next_compatible(Self::PVIOMMU_COMPATIBLE)?;
488 }
489
490 Ok(pviommu_phandles)
491 }
492
493 pub fn patch(&self, fdt: &mut Fdt) -> Result<()> {
494 let pviommu_phandles = self.patch_pviommus(fdt)?;
495
496 // Patches assigned devices
497 for device in &self.assigned_devices {
498 device.patch(fdt, &pviommu_phandles)?;
499 }
500
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900501 Ok(())
502 }
503}
504
505#[cfg(test)]
506mod tests {
507 use super::*;
Jaewan Kim52477ae2023-11-21 21:20:52 +0900508 use alloc::collections::{BTreeMap, BTreeSet};
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900509 use std::fs;
510
511 const VM_DTBO_FILE_PATH: &str = "test_pvmfw_devices_vm_dtbo.dtbo";
512 const VM_DTBO_WITHOUT_SYMBOLS_FILE_PATH: &str =
513 "test_pvmfw_devices_vm_dtbo_without_symbols.dtbo";
Jaewan Kima67e36a2023-11-29 16:50:23 +0900514 const FDT_WITHOUT_IOMMUS_FILE_PATH: &str = "test_pvmfw_devices_without_iommus.dtb";
Jaewan Kim52477ae2023-11-21 21:20:52 +0900515 const FDT_WITHOUT_DEVICE_FILE_PATH: &str = "test_pvmfw_devices_without_device.dtb";
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900516 const FDT_FILE_PATH: &str = "test_pvmfw_devices_with_rng.dtb";
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900517 const FDT_WITH_MULTIPLE_DEVICES_IOMMUS_FILE_PATH: &str =
518 "test_pvmfw_devices_with_multiple_devices_iommus.dtb";
519 const FDT_WITH_IOMMU_SHARING: &str = "test_pvmfw_devices_with_iommu_sharing.dtb";
520 const FDT_WITH_IOMMU_ID_CONFLICT: &str = "test_pvmfw_devices_with_iommu_id_conflict.dtb";
521
Jaewan Kim52477ae2023-11-21 21:20:52 +0900522 #[derive(Debug, Default)]
523 struct MockHypervisor {
524 mmio_tokens: BTreeMap<(u64, u64), u64>,
525 iommu_tokens: BTreeMap<(u64, u64), (u64, u64)>,
526 }
527
528 impl DeviceAssigningHypervisor for MockHypervisor {
529 fn get_phys_mmio_token(&self, base_ipa: u64, size: u64) -> hyp::Result<u64> {
530 Ok(*self.mmio_tokens.get(&(base_ipa, size)).ok_or(hyp::Error::KvmError(
531 hyp::KvmError::InvalidParameter,
532 0xc6000012, /* VENDOR_HYP_KVM_DEV_REQ_MMIO_FUNC_ID */
533 ))?)
534 }
535
536 fn get_phys_iommu_token(&self, pviommu_id: u64, vsid: u64) -> hyp::Result<(u64, u64)> {
537 Ok(*self.iommu_tokens.get(&(pviommu_id, vsid)).ok_or(hyp::Error::KvmError(
538 hyp::KvmError::InvalidParameter,
539 0xc6000013, /* VENDOR_HYP_KVM_DEV_REQ_DMA_FUNC_ID */
540 ))?)
541 }
542 }
543
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900544 #[derive(Debug, Eq, PartialEq)]
545 struct AssignedDeviceNode {
546 path: CString,
547 reg: Vec<u8>,
548 interrupts: Vec<u8>,
Jaewan Kima67e36a2023-11-29 16:50:23 +0900549 iommus: Vec<u32>, // pvIOMMU id and vSID
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900550 }
551
552 impl AssignedDeviceNode {
553 fn parse(fdt: &Fdt, path: &CStr) -> Result<Self> {
554 let Some(node) = fdt.node(path)? else {
555 return Err(FdtError::NotFound.into());
556 };
557
Jaewan Kim52477ae2023-11-21 21:20:52 +0900558 let reg = node.getprop(cstr!("reg"))?.ok_or(DeviceAssignmentError::InvalidReg)?;
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900559 let interrupts = node
560 .getprop(cstr!("interrupts"))?
561 .ok_or(DeviceAssignmentError::InvalidInterrupts)?;
562 let mut iommus = vec![];
Jaewan Kima9200492023-11-21 20:45:31 +0900563 if let Some(mut cells) = node.getprop_cells(cstr!("iommus"))? {
564 while let Some(pviommu_id) = cells.next() {
565 // pvIOMMU id
566 let phandle = Phandle::try_from(pviommu_id)?;
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900567 let pviommu = fdt
568 .node_with_phandle(phandle)?
569 .ok_or(DeviceAssignmentError::InvalidIommus)?;
570 let compatible = pviommu.getprop_str(cstr!("compatible"));
571 if compatible != Ok(Some(cstr!("pkvm,pviommu"))) {
572 return Err(DeviceAssignmentError::InvalidIommus);
573 }
574 let id = pviommu
575 .getprop_u32(cstr!("id"))?
576 .ok_or(DeviceAssignmentError::InvalidIommus)?;
577 iommus.push(id);
Jaewan Kima9200492023-11-21 20:45:31 +0900578
579 // vSID
580 let Some(vsid) = cells.next() else {
581 return Err(DeviceAssignmentError::InvalidIommus);
582 };
583 iommus.push(vsid);
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900584 }
585 }
586 Ok(Self { path: path.into(), reg: reg.into(), interrupts: interrupts.into(), iommus })
587 }
588 }
589
590 fn collect_pviommus(fdt: &Fdt) -> Result<Vec<u32>> {
591 let mut pviommus = BTreeSet::new();
592 for pviommu in fdt.compatible_nodes(cstr!("pkvm,pviommu"))? {
593 if let Ok(Some(id)) = pviommu.getprop_u32(cstr!("id")) {
594 pviommus.insert(id);
595 }
596 }
597 Ok(pviommus.iter().cloned().collect())
598 }
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900599
600 fn into_fdt_prop(native_bytes: Vec<u32>) -> Vec<u8> {
601 let mut v = Vec::with_capacity(native_bytes.len() * 4);
602 for byte in native_bytes {
603 v.extend_from_slice(&byte.to_be_bytes());
604 }
605 v
606 }
607
Jaewan Kim52477ae2023-11-21 21:20:52 +0900608 impl From<[u64; 2]> for DeviceReg {
609 fn from(fdt_cells: [u64; 2]) -> Self {
610 DeviceReg { addr: fdt_cells[0], size: fdt_cells[1] }
611 }
612 }
613
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900614 #[test]
615 fn device_info_new_without_symbols() {
616 let mut fdt_data = fs::read(FDT_FILE_PATH).unwrap();
617 let mut vm_dtbo_data = fs::read(VM_DTBO_WITHOUT_SYMBOLS_FILE_PATH).unwrap();
618 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
619 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
620
Jaewan Kim52477ae2023-11-21 21:20:52 +0900621 let hypervisor: MockHypervisor = Default::default();
622 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor).unwrap();
623 assert_eq!(device_info, None);
624 }
625
626 #[test]
627 fn device_info_new_without_device() {
628 let mut fdt_data = fs::read(FDT_WITHOUT_DEVICE_FILE_PATH).unwrap();
629 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
630 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
631 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
632
633 let hypervisor: MockHypervisor = Default::default();
634 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor).unwrap();
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900635 assert_eq!(device_info, None);
636 }
637
638 #[test]
Jaewan Kima67e36a2023-11-29 16:50:23 +0900639 fn device_info_assigned_info_without_iommus() {
640 let mut fdt_data = fs::read(FDT_WITHOUT_IOMMUS_FILE_PATH).unwrap();
641 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
642 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
643 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
644
Jaewan Kim52477ae2023-11-21 21:20:52 +0900645 let hypervisor = MockHypervisor {
646 mmio_tokens: [((0x9, 0xFF), 0x300)].into(),
647 iommu_tokens: BTreeMap::new(),
648 };
649 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor).unwrap().unwrap();
Jaewan Kima67e36a2023-11-29 16:50:23 +0900650
651 let expected = [AssignedDeviceInfo {
652 node_path: CString::new("/backlight").unwrap(),
653 dtbo_node_path: cstr!("/fragment@backlight/__overlay__/backlight").into(),
Jaewan Kim52477ae2023-11-21 21:20:52 +0900654 reg: vec![[0x9, 0xFF].into()],
Jaewan Kima67e36a2023-11-29 16:50:23 +0900655 interrupts: into_fdt_prop(vec![0x0, 0xF, 0x4]),
656 iommus: vec![],
657 }];
658
659 assert_eq!(device_info.assigned_devices, expected);
660 }
661
662 #[test]
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900663 fn device_info_assigned_info() {
664 let mut fdt_data = fs::read(FDT_FILE_PATH).unwrap();
665 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
666 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
667 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
668
Jaewan Kim52477ae2023-11-21 21:20:52 +0900669 let hypervisor = MockHypervisor {
670 mmio_tokens: [((0x9, 0xFF), 0x12F00000)].into(),
671 iommu_tokens: [((0x4, 0xFF0), (0x12E40000, 0x3))].into(),
672 };
673 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor).unwrap().unwrap();
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900674
675 let expected = [AssignedDeviceInfo {
676 node_path: CString::new("/rng").unwrap(),
677 dtbo_node_path: cstr!("/fragment@rng/__overlay__/rng").into(),
Jaewan Kim52477ae2023-11-21 21:20:52 +0900678 reg: vec![[0x9, 0xFF].into()],
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900679 interrupts: into_fdt_prop(vec![0x0, 0xF, 0x4]),
Jaewan Kima67e36a2023-11-29 16:50:23 +0900680 iommus: vec![(PvIommu { id: 0x4 }, Vsid(0xFF0))],
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900681 }];
682
683 assert_eq!(device_info.assigned_devices, expected);
684 }
685
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900686 // TODO(b/311655051): Test with real once instead of empty FDT.
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900687 #[test]
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900688 fn device_info_new_with_empty_device_tree() {
689 let mut fdt_data = vec![0; pvmfw_fdt_template::RAW.len()];
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900690 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900691 let fdt = Fdt::create_empty_tree(&mut fdt_data).unwrap();
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900692 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
693
Jaewan Kim52477ae2023-11-21 21:20:52 +0900694 let hypervisor: MockHypervisor = Default::default();
695 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor).unwrap();
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900696 assert_eq!(device_info, None);
697 }
698
699 #[test]
700 fn device_info_filter() {
701 let mut fdt_data = fs::read(FDT_FILE_PATH).unwrap();
702 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
703 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
704 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
705
Jaewan Kim52477ae2023-11-21 21:20:52 +0900706 let hypervisor = MockHypervisor {
707 mmio_tokens: [((0x9, 0xFF), 0x12F00000)].into(),
708 iommu_tokens: [((0x4, 0xFF0), (0x12E40000, 0x3))].into(),
709 };
710 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor).unwrap().unwrap();
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900711 device_info.filter(vm_dtbo).unwrap();
712
713 let vm_dtbo = vm_dtbo.as_mut();
714
715 let rng = vm_dtbo.node(cstr!("/fragment@rng/__overlay__/rng")).unwrap();
716 assert_ne!(rng, None);
717
718 let light = vm_dtbo.node(cstr!("/fragment@rng/__overlay__/light")).unwrap();
719 assert_eq!(light, None);
720
Jaewan Kima67e36a2023-11-29 16:50:23 +0900721 let led = vm_dtbo.node(cstr!("/fragment@led/__overlay__/led")).unwrap();
722 assert_eq!(led, None);
723
724 let backlight = vm_dtbo.node(cstr!("/fragment@backlight/__overlay__/backlight")).unwrap();
725 assert_eq!(backlight, None);
726
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900727 let symbols_node = vm_dtbo.symbols().unwrap();
728 assert_eq!(symbols_node, None);
729 }
730
731 #[test]
732 fn device_info_patch() {
Jaewan Kima67e36a2023-11-29 16:50:23 +0900733 let mut fdt_data = fs::read(FDT_WITHOUT_IOMMUS_FILE_PATH).unwrap();
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900734 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
735 let mut data = vec![0_u8; fdt_data.len() + vm_dtbo_data.len()];
736 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
737 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
738 let platform_dt = Fdt::create_empty_tree(data.as_mut_slice()).unwrap();
739
Jaewan Kim52477ae2023-11-21 21:20:52 +0900740 let hypervisor = MockHypervisor {
741 mmio_tokens: [((0x9, 0xFF), 0x300)].into(),
742 iommu_tokens: BTreeMap::new(),
743 };
744 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor).unwrap().unwrap();
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900745 device_info.filter(vm_dtbo).unwrap();
746
747 // SAFETY: Damaged VM DTBO wouldn't be used after this unsafe block.
748 unsafe {
749 platform_dt.apply_overlay(vm_dtbo.as_mut()).unwrap();
750 }
Jaewan Kim0bd637d2023-11-10 13:09:41 +0900751 device_info.patch(platform_dt).unwrap();
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900752
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900753 // Note: Intentionally not using AssignedDeviceNode for matching all props.
Jaewan Kim0bd637d2023-11-10 13:09:41 +0900754 type FdtResult<T> = libfdt::Result<T>;
755 let expected: Vec<(FdtResult<&CStr>, FdtResult<Vec<u8>>)> = vec![
Jaewan Kima67e36a2023-11-29 16:50:23 +0900756 (Ok(cstr!("android,backlight,ignore-gctrl-reset")), Ok(Vec::new())),
757 (Ok(cstr!("compatible")), Ok(Vec::from(*b"android,backlight\0"))),
Jaewan Kim0bd637d2023-11-10 13:09:41 +0900758 (Ok(cstr!("interrupts")), Ok(into_fdt_prop(vec![0x0, 0xF, 0x4]))),
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900759 (Ok(cstr!("iommus")), Ok(Vec::new())),
Jaewan Kim0bd637d2023-11-10 13:09:41 +0900760 (Ok(cstr!("reg")), Ok(into_fdt_prop(vec![0x0, 0x9, 0x0, 0xFF]))),
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900761 ];
762
Jaewan Kima67e36a2023-11-29 16:50:23 +0900763 let rng_node = platform_dt.node(cstr!("/backlight")).unwrap().unwrap();
Jaewan Kim0bd637d2023-11-10 13:09:41 +0900764 let mut properties: Vec<_> = rng_node
765 .properties()
766 .unwrap()
767 .map(|prop| (prop.name(), prop.value().map(|x| x.into())))
768 .collect();
769 properties.sort_by(|a, b| {
770 let lhs = a.0.unwrap_or_default();
771 let rhs = b.0.unwrap_or_default();
772 lhs.partial_cmp(rhs).unwrap()
773 });
774
775 assert_eq!(properties, expected);
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900776 }
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900777
778 #[test]
779 fn device_info_overlay_iommu() {
Jaewan Kima67e36a2023-11-29 16:50:23 +0900780 let mut fdt_data = fs::read(FDT_FILE_PATH).unwrap();
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900781 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
782 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
783 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
784 let mut platform_dt_data = pvmfw_fdt_template::RAW.to_vec();
785 platform_dt_data.resize(pvmfw_fdt_template::RAW.len() * 2, 0);
786 let platform_dt = Fdt::from_mut_slice(&mut platform_dt_data).unwrap();
787 platform_dt.unpack().unwrap();
788
Jaewan Kim52477ae2023-11-21 21:20:52 +0900789 let hypervisor = MockHypervisor {
790 mmio_tokens: [((0x9, 0xFF), 0x12F00000)].into(),
791 iommu_tokens: [((0x4, 0xFF0), (0x12E40000, 0x3))].into(),
792 };
793 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor).unwrap().unwrap();
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900794 device_info.filter(vm_dtbo).unwrap();
795
796 // SAFETY: Damaged VM DTBO wouldn't be used after this unsafe block.
797 unsafe {
798 platform_dt.apply_overlay(vm_dtbo.as_mut()).unwrap();
799 }
800 device_info.patch(platform_dt).unwrap();
801
802 let expected = AssignedDeviceNode {
803 path: CString::new("/rng").unwrap(),
804 reg: into_fdt_prop(vec![0x0, 0x9, 0x0, 0xFF]),
805 interrupts: into_fdt_prop(vec![0x0, 0xF, 0x4]),
Jaewan Kima9200492023-11-21 20:45:31 +0900806 iommus: vec![0x4, 0xFF0],
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900807 };
808
809 let node = AssignedDeviceNode::parse(platform_dt, &expected.path);
810 assert_eq!(node, Ok(expected));
811
812 let pviommus = collect_pviommus(platform_dt);
813 assert_eq!(pviommus, Ok(vec![0x4]));
814 }
815
816 #[test]
817 fn device_info_multiple_devices_iommus() {
818 let mut fdt_data = fs::read(FDT_WITH_MULTIPLE_DEVICES_IOMMUS_FILE_PATH).unwrap();
819 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
820 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
821 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
822 let mut platform_dt_data = pvmfw_fdt_template::RAW.to_vec();
823 platform_dt_data.resize(pvmfw_fdt_template::RAW.len() * 2, 0);
824 let platform_dt = Fdt::from_mut_slice(&mut platform_dt_data).unwrap();
825 platform_dt.unpack().unwrap();
826
Jaewan Kim52477ae2023-11-21 21:20:52 +0900827 let hypervisor = MockHypervisor {
828 mmio_tokens: [
829 ((0x9, 0xFF), 0x12F00000),
830 ((0x100, 0x1000), 0xF00000),
831 ((0x200, 0x1000), 0xF10000),
832 ]
833 .into(),
834 iommu_tokens: [
835 ((0x4, 0xFF0), (0x12E40000, 3)),
836 ((0x40, 0xFFA), (0x40000, 0x4)),
837 ((0x50, 0xFFB), (0x50000, 0x5)),
838 ]
839 .into(),
840 };
841 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor).unwrap().unwrap();
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900842 device_info.filter(vm_dtbo).unwrap();
843
844 // SAFETY: Damaged VM DTBO wouldn't be used after this unsafe block.
845 unsafe {
846 platform_dt.apply_overlay(vm_dtbo.as_mut()).unwrap();
847 }
848 device_info.patch(platform_dt).unwrap();
849
850 let expected_devices = [
851 AssignedDeviceNode {
852 path: CString::new("/rng").unwrap(),
853 reg: into_fdt_prop(vec![0x0, 0x9, 0x0, 0xFF]),
854 interrupts: into_fdt_prop(vec![0x0, 0xF, 0x4]),
Jaewan Kima67e36a2023-11-29 16:50:23 +0900855 iommus: vec![0x4, 0xFF0],
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900856 },
857 AssignedDeviceNode {
858 path: CString::new("/light").unwrap(),
Jaewan Kim52477ae2023-11-21 21:20:52 +0900859 reg: into_fdt_prop(vec![0x0, 0x100, 0x0, 0x1000, 0x0, 0x200, 0x0, 0x1000]),
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900860 interrupts: into_fdt_prop(vec![0x0, 0xF, 0x5]),
Jaewan Kima67e36a2023-11-29 16:50:23 +0900861 iommus: vec![0x40, 0xFFA, 0x50, 0xFFB],
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900862 },
863 ];
864
865 for expected in expected_devices {
866 let node = AssignedDeviceNode::parse(platform_dt, &expected.path);
867 assert_eq!(node, Ok(expected));
868 }
869 let pviommus = collect_pviommus(platform_dt);
Jaewan Kima67e36a2023-11-29 16:50:23 +0900870 assert_eq!(pviommus, Ok(vec![0x4, 0x40, 0x50]));
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900871 }
872
873 #[test]
874 fn device_info_iommu_sharing() {
875 let mut fdt_data = fs::read(FDT_WITH_IOMMU_SHARING).unwrap();
876 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
877 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
878 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
879 let mut platform_dt_data = pvmfw_fdt_template::RAW.to_vec();
880 platform_dt_data.resize(pvmfw_fdt_template::RAW.len() * 2, 0);
881 let platform_dt = Fdt::from_mut_slice(&mut platform_dt_data).unwrap();
882 platform_dt.unpack().unwrap();
883
Jaewan Kim52477ae2023-11-21 21:20:52 +0900884 let hypervisor = MockHypervisor {
885 mmio_tokens: [((0x9, 0xFF), 0x12F00000), ((0x100, 0x9), 0x12000000)].into(),
886 iommu_tokens: [((0x4, 0xFF0), (0x12E40000, 3))].into(),
887 };
888 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor).unwrap().unwrap();
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900889 device_info.filter(vm_dtbo).unwrap();
890
891 // SAFETY: Damaged VM DTBO wouldn't be used after this unsafe block.
892 unsafe {
893 platform_dt.apply_overlay(vm_dtbo.as_mut()).unwrap();
894 }
895 device_info.patch(platform_dt).unwrap();
896
897 let expected_devices = [
898 AssignedDeviceNode {
899 path: CString::new("/rng").unwrap(),
900 reg: into_fdt_prop(vec![0x0, 0x9, 0x0, 0xFF]),
901 interrupts: into_fdt_prop(vec![0x0, 0xF, 0x4]),
Jaewan Kima67e36a2023-11-29 16:50:23 +0900902 iommus: vec![0x4, 0xFF0],
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900903 },
904 AssignedDeviceNode {
Jaewan Kima67e36a2023-11-29 16:50:23 +0900905 path: CString::new("/led").unwrap(),
Jaewan Kim52477ae2023-11-21 21:20:52 +0900906 reg: into_fdt_prop(vec![0x0, 0x100, 0x0, 0x9]),
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900907 interrupts: into_fdt_prop(vec![0x0, 0xF, 0x5]),
Jaewan Kima67e36a2023-11-29 16:50:23 +0900908 iommus: vec![0x4, 0xFF0],
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900909 },
910 ];
911
912 for expected in expected_devices {
913 let node = AssignedDeviceNode::parse(platform_dt, &expected.path);
914 assert_eq!(node, Ok(expected));
915 }
916
917 let pviommus = collect_pviommus(platform_dt);
Jaewan Kima67e36a2023-11-29 16:50:23 +0900918 assert_eq!(pviommus, Ok(vec![0x4]));
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900919 }
920
921 #[test]
922 fn device_info_iommu_id_conflict() {
923 let mut fdt_data = fs::read(FDT_WITH_IOMMU_ID_CONFLICT).unwrap();
924 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
925 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
926 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
927
Jaewan Kim52477ae2023-11-21 21:20:52 +0900928 let hypervisor = MockHypervisor {
929 mmio_tokens: [((0x9, 0xFF), 0x12F00000)].into(),
930 iommu_tokens: [((0x4, 0xFF0), (0x12E40000, 0x3))].into(),
931 };
932 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor);
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900933
934 assert_eq!(device_info, Err(DeviceAssignmentError::DuplicatedPvIommuIds));
935 }
Jaewan Kim52477ae2023-11-21 21:20:52 +0900936
937 #[test]
938 fn device_info_invalid_reg() {
939 let mut fdt_data = fs::read(FDT_FILE_PATH).unwrap();
940 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
941 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
942 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
943
944 let hypervisor = MockHypervisor {
945 mmio_tokens: BTreeMap::new(),
946 iommu_tokens: [((0x4, 0xFF0), (0x12E40000, 0x3))].into(),
947 };
948 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor);
949
950 assert_eq!(device_info, Err(DeviceAssignmentError::InvalidReg));
951 }
952
953 #[test]
954 fn device_info_invalid_iommus() {
955 let mut fdt_data = fs::read(FDT_FILE_PATH).unwrap();
956 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
957 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
958 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
959
960 let hypervisor = MockHypervisor {
961 mmio_tokens: [((0x9, 0xFF), 0x12F00000)].into(),
962 iommu_tokens: BTreeMap::new(),
963 };
964 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor);
965
966 assert_eq!(device_info, Err(DeviceAssignmentError::InvalidIommus));
967 }
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900968}