blob: 60bf21ca9d653b12be93f5593c7d020c46fd8e5d [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 Kim51ccfed2023-11-08 13:51:58 +090030use libfdt::{Fdt, FdtError, FdtNode, Phandle};
Jaewan Kimc6e023b2023-10-12 15:11:05 +090031
Jaewan Kimc6e023b2023-10-12 15:11:05 +090032// TODO(b/308694211): Use cstr! from vmbase instead.
33macro_rules! cstr {
34 ($str:literal) => {{
Pierre-Clément Tosid701a0b2023-11-07 15:38:59 +000035 const S: &str = concat!($str, "\0");
36 const C: &::core::ffi::CStr = match ::core::ffi::CStr::from_bytes_with_nul(S.as_bytes()) {
37 Ok(v) => v,
38 Err(_) => panic!("string contains interior NUL"),
39 };
40 C
Jaewan Kimc6e023b2023-10-12 15:11:05 +090041 }};
42}
43
Jaewan Kimc6e023b2023-10-12 15:11:05 +090044// TODO(b/277993056): Keep constants derived from platform.dts in one place.
45const CELLS_PER_INTERRUPT: usize = 3; // from /intc node in platform.dts
46
47/// Errors in device assignment.
48#[derive(Clone, Copy, Debug, Eq, PartialEq)]
49pub enum DeviceAssignmentError {
50 // Invalid VM DTBO
51 InvalidDtbo,
52 /// Invalid __symbols__
53 InvalidSymbols,
54 /// Invalid <interrupts>
55 InvalidInterrupts,
Jaewan Kim51ccfed2023-11-08 13:51:58 +090056 /// Invalid <iommus>
57 InvalidIommus,
Jaewan Kima9200492023-11-21 20:45:31 +090058 /// Invalid pvIOMMU node
59 InvalidPvIommu,
Jaewan Kim51ccfed2023-11-08 13:51:58 +090060 /// Too many pvIOMMU
61 TooManyPvIommu,
62 /// Duplicated pvIOMMU IDs exist
63 DuplicatedPvIommuIds,
Jaewan Kimc6e023b2023-10-12 15:11:05 +090064 /// Unsupported overlay target syntax. Only supports <target-path> with full path.
65 UnsupportedOverlayTarget,
Jaewan Kim51ccfed2023-11-08 13:51:58 +090066 /// Internal error
67 Internal,
Jaewan Kimc6e023b2023-10-12 15:11:05 +090068 /// Unexpected error from libfdt
69 UnexpectedFdtError(FdtError),
70}
71
72impl From<FdtError> for DeviceAssignmentError {
73 fn from(e: FdtError) -> Self {
74 DeviceAssignmentError::UnexpectedFdtError(e)
75 }
76}
77
78impl fmt::Display for DeviceAssignmentError {
79 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
80 match self {
81 Self::InvalidDtbo => write!(f, "Invalid DTBO"),
82 Self::InvalidSymbols => write!(
83 f,
84 "Invalid property in /__symbols__. Must point to valid assignable device node."
85 ),
86 Self::InvalidInterrupts => write!(f, "Invalid <interrupts>"),
Jaewan Kim51ccfed2023-11-08 13:51:58 +090087 Self::InvalidIommus => write!(f, "Invalid <iommus>"),
Jaewan Kima9200492023-11-21 20:45:31 +090088 Self::InvalidPvIommu => write!(f, "Invalid pvIOMMU node"),
Jaewan Kim51ccfed2023-11-08 13:51:58 +090089 Self::TooManyPvIommu => write!(
90 f,
91 "Too many pvIOMMU node. Insufficient pre-populated pvIOMMUs in platform DT"
92 ),
93 Self::DuplicatedPvIommuIds => {
94 write!(f, "Duplicated pvIOMMU IDs exist. IDs must unique")
95 }
Jaewan Kimc6e023b2023-10-12 15:11:05 +090096 Self::UnsupportedOverlayTarget => {
97 write!(f, "Unsupported overlay target. Only supports 'target-path = \"/\"'")
98 }
Jaewan Kim51ccfed2023-11-08 13:51:58 +090099 Self::Internal => write!(f, "Internal error"),
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900100 Self::UnexpectedFdtError(e) => write!(f, "Unexpected Error from libfdt: {e}"),
101 }
102 }
103}
104
105pub type Result<T> = core::result::Result<T, DeviceAssignmentError>;
106
107/// Represents VM DTBO
108#[repr(transparent)]
109pub struct VmDtbo(Fdt);
110
111impl VmDtbo {
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900112 /// Wraps a mutable slice containing a VM DTBO.
113 ///
114 /// Fails if the VM DTBO does not pass validation.
115 pub fn from_mut_slice(dtbo: &mut [u8]) -> Result<&mut Self> {
116 // This validates DTBO
117 let fdt = Fdt::from_mut_slice(dtbo)?;
118 // SAFETY: VmDtbo is a transparent wrapper around Fdt, so representation is the same.
119 Ok(unsafe { mem::transmute::<&mut Fdt, &mut Self>(fdt) })
120 }
121
122 // Locates device node path as if the given dtbo node path is assigned and VM DTBO is overlaid.
123 // For given dtbo node path, this concatenates <target-path> of the enclosing fragment and
124 // relative path from __overlay__ node.
125 //
126 // Here's an example with sample VM DTBO:
127 // / {
128 // fragment@rng {
129 // target-path = "/"; // Always 'target-path = "/"'. Disallows <target> or other path.
130 // __overlay__ {
131 // rng { ... }; // Actual device node is here. If overlaid, path would be "/rng"
132 // };
133 // };
134 // __symbols__ { // List of assignable devices
135 // // Each property describes an assigned device device information.
136 // // property name is the device label, and property value is the path in the VM DTBO.
137 // rng = "/fragment@rng/__overlay__/rng";
138 // };
139 // };
140 //
141 // Then locate_overlay_target_path(cstr!("/fragment@rng/__overlay__/rng")) is Ok("/rng")
142 //
143 // Contrary to fdt_overlay_target_offset(), this API enforces overlay target property
144 // 'target-path = "/"', so the overlay doesn't modify and/or append platform DT's existing
145 // node and/or properties. The enforcement is for compatibility reason.
146 fn locate_overlay_target_path(&self, dtbo_node_path: &CStr) -> Result<CString> {
147 let dtbo_node_path_bytes = dtbo_node_path.to_bytes();
148 if dtbo_node_path_bytes.first() != Some(&b'/') {
149 return Err(DeviceAssignmentError::UnsupportedOverlayTarget);
150 }
151
152 let node = self.0.node(dtbo_node_path)?.ok_or(DeviceAssignmentError::InvalidSymbols)?;
153
154 let fragment_node = node.supernode_at_depth(1)?;
155 let target_path = fragment_node
Pierre-Clément Tosid701a0b2023-11-07 15:38:59 +0000156 .getprop_str(cstr!("target-path"))?
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900157 .ok_or(DeviceAssignmentError::InvalidDtbo)?;
158 if target_path != cstr!("/") {
159 return Err(DeviceAssignmentError::UnsupportedOverlayTarget);
160 }
161
162 let mut components = dtbo_node_path_bytes
163 .split(|char| *char == b'/')
164 .filter(|&component| !component.is_empty())
165 .skip(1);
166 let overlay_node_name = components.next();
Pierre-Clément Tosid701a0b2023-11-07 15:38:59 +0000167 if overlay_node_name != Some(b"__overlay__") {
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900168 return Err(DeviceAssignmentError::InvalidDtbo);
169 }
170 let mut overlaid_path = Vec::with_capacity(dtbo_node_path_bytes.len());
171 for component in components {
172 overlaid_path.push(b'/');
173 overlaid_path.extend_from_slice(component);
174 }
175 overlaid_path.push(b'\0');
176
177 Ok(CString::from_vec_with_nul(overlaid_path).unwrap())
178 }
179}
180
181impl AsRef<Fdt> for VmDtbo {
182 fn as_ref(&self) -> &Fdt {
183 &self.0
184 }
185}
186
187impl AsMut<Fdt> for VmDtbo {
188 fn as_mut(&mut self) -> &mut Fdt {
189 &mut self.0
190 }
191}
192
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900193#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
194struct PvIommu {
195 // ID from pvIOMMU node
196 id: u32,
197}
198
199impl PvIommu {
200 fn parse(node: &FdtNode) -> Result<Self> {
Jaewan Kima9200492023-11-21 20:45:31 +0900201 let iommu_cells = node
202 .getprop_u32(cstr!("#iommu-cells"))?
203 .ok_or(DeviceAssignmentError::InvalidPvIommu)?;
204 // Ensures <#iommu-cells> = 1. It means that `<iommus>` entry contains pair of
205 // (pvIOMMU ID, vSID)
206 if iommu_cells != 1 {
207 return Err(DeviceAssignmentError::InvalidPvIommu);
208 }
209 let id = node.getprop_u32(cstr!("id"))?.ok_or(DeviceAssignmentError::InvalidPvIommu)?;
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900210 Ok(Self { id })
211 }
212}
213
Jaewan Kima9200492023-11-21 20:45:31 +0900214#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
215struct Vsid(u32);
216
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900217/// Assigned device information parsed from crosvm DT.
218/// Keeps everything in the owned data because underlying FDT will be reused for platform DT.
219#[derive(Debug, Eq, PartialEq)]
220struct AssignedDeviceInfo {
221 // Node path of assigned device (e.g. "/rng")
222 node_path: CString,
223 // DTBO node path of the assigned device (e.g. "/fragment@rng/__overlay__/rng")
224 dtbo_node_path: CString,
225 // <reg> property from the crosvm DT
226 reg: Vec<u8>,
227 // <interrupts> property from the crosvm DT
228 interrupts: Vec<u8>,
Jaewan Kima9200492023-11-21 20:45:31 +0900229 // Parsed <iommus> property from the crosvm DT. Tuple of PvIommu and vSID.
230 iommus: Vec<(PvIommu, Vsid)>,
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900231}
232
233impl AssignedDeviceInfo {
234 fn parse_interrupts(node: &FdtNode) -> Result<Vec<u8>> {
235 // Validation: Validate if interrupts cell numbers are multiple of #interrupt-cells.
236 // We can't know how many interrupts would exist.
237 let interrupts_cells = node
Pierre-Clément Tosid701a0b2023-11-07 15:38:59 +0000238 .getprop_cells(cstr!("interrupts"))?
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900239 .ok_or(DeviceAssignmentError::InvalidInterrupts)?
240 .count();
241 if interrupts_cells % CELLS_PER_INTERRUPT != 0 {
242 return Err(DeviceAssignmentError::InvalidInterrupts);
243 }
244
245 // Once validated, keep the raw bytes so patch can be done with setprop()
Pierre-Clément Tosid701a0b2023-11-07 15:38:59 +0000246 Ok(node.getprop(cstr!("interrupts")).unwrap().unwrap().into())
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900247 }
248
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900249 // TODO(b/277993056): Also validate /__local_fixups__ to ensure that <iommus> has phandle.
Jaewan Kima9200492023-11-21 20:45:31 +0900250 fn parse_iommus(
251 node: &FdtNode,
252 pviommus: &BTreeMap<Phandle, PvIommu>,
253 ) -> Result<Vec<(PvIommu, Vsid)>> {
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900254 let mut iommus = vec![];
Jaewan Kima9200492023-11-21 20:45:31 +0900255 let Some(mut cells) = node.getprop_cells(cstr!("iommus"))? else {
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900256 return Ok(iommus);
257 };
Jaewan Kima9200492023-11-21 20:45:31 +0900258 while let Some(cell) = cells.next() {
259 // Parse pvIOMMU ID
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900260 let phandle = Phandle::try_from(cell).or(Err(DeviceAssignmentError::InvalidIommus))?;
261 let pviommu = pviommus.get(&phandle).ok_or(DeviceAssignmentError::InvalidIommus)?;
Jaewan Kima9200492023-11-21 20:45:31 +0900262
263 // Parse vSID
264 let Some(cell) = cells.next() else {
265 return Err(DeviceAssignmentError::InvalidIommus);
266 };
267 let vsid = Vsid(cell);
268
269 iommus.push((*pviommu, vsid));
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900270 }
271 Ok(iommus)
272 }
273
274 fn parse(
275 fdt: &Fdt,
276 vm_dtbo: &VmDtbo,
277 dtbo_node_path: &CStr,
278 pviommus: &BTreeMap<Phandle, PvIommu>,
279 ) -> Result<Option<Self>> {
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900280 let node_path = vm_dtbo.locate_overlay_target_path(dtbo_node_path)?;
281
282 let Some(node) = fdt.node(&node_path)? else { return Ok(None) };
283
284 // TODO(b/277993056): Validate reg with HVC, and keep reg with FdtNode::reg()
Pierre-Clément Tosid701a0b2023-11-07 15:38:59 +0000285 let reg = node.getprop(cstr!("reg")).unwrap().unwrap();
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900286 let interrupts = Self::parse_interrupts(&node)?;
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900287 let iommus = Self::parse_iommus(&node, pviommus)?;
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900288 Ok(Some(Self {
289 node_path,
290 dtbo_node_path: dtbo_node_path.into(),
291 reg: reg.to_vec(),
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900292 interrupts,
293 iommus,
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900294 }))
295 }
296
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900297 fn patch(&self, fdt: &mut Fdt, pviommu_phandles: &BTreeMap<PvIommu, Phandle>) -> Result<()> {
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900298 let mut dst = fdt.node_mut(&self.node_path)?.unwrap();
Pierre-Clément Tosid701a0b2023-11-07 15:38:59 +0000299 dst.setprop(cstr!("reg"), &self.reg)?;
300 dst.setprop(cstr!("interrupts"), &self.interrupts)?;
Jaewan Kima9200492023-11-21 20:45:31 +0900301 let mut iommus = Vec::with_capacity(8 * self.iommus.len());
302 for (pviommu, vsid) in &self.iommus {
303 let phandle = pviommu_phandles.get(pviommu).unwrap();
304 iommus.extend_from_slice(&u32::from(*phandle).to_be_bytes());
305 iommus.extend_from_slice(&vsid.0.to_be_bytes());
306 }
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900307 dst.setprop(cstr!("iommus"), &iommus)?;
308
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900309 Ok(())
310 }
311}
312
313#[derive(Debug, Default, Eq, PartialEq)]
314pub struct DeviceAssignmentInfo {
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900315 pviommus: BTreeSet<PvIommu>,
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900316 assigned_devices: Vec<AssignedDeviceInfo>,
317 filtered_dtbo_paths: Vec<CString>,
318}
319
320impl DeviceAssignmentInfo {
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900321 const PVIOMMU_COMPATIBLE: &CStr = cstr!("pkvm,pviommu");
322
323 /// Parses pvIOMMUs in fdt
324 // Note: This will validate pvIOMMU ids' uniqueness, even when unassigned.
325 fn parse_pviommus(fdt: &Fdt) -> Result<BTreeMap<Phandle, PvIommu>> {
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900326 let mut pviommus = BTreeMap::new();
327 for compatible in fdt.compatible_nodes(Self::PVIOMMU_COMPATIBLE)? {
328 let Some(phandle) = compatible.get_phandle()? else {
329 continue; // Skips unreachable pvIOMMU node
330 };
331 let pviommu = PvIommu::parse(&compatible)?;
332 if pviommus.insert(phandle, pviommu).is_some() {
333 return Err(FdtError::BadPhandle.into());
334 }
335 }
336 Ok(pviommus)
337 }
338
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900339 /// Parses fdt and vm_dtbo, and creates new DeviceAssignmentInfo
340 // TODO(b/277993056): Parse __local_fixups__
341 // TODO(b/277993056): Parse __fixups__
342 pub fn parse(fdt: &Fdt, vm_dtbo: &VmDtbo) -> Result<Option<Self>> {
343 let Some(symbols_node) = vm_dtbo.as_ref().symbols()? else {
344 // /__symbols__ should contain all assignable devices.
345 // If empty, then nothing can be assigned.
346 return Ok(None);
347 };
348
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900349 let pviommus = Self::parse_pviommus(fdt)?;
350 let unique_pviommus: BTreeSet<_> = pviommus.values().cloned().collect();
351 if pviommus.len() != unique_pviommus.len() {
352 return Err(DeviceAssignmentError::DuplicatedPvIommuIds);
353 }
354
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900355 let mut assigned_devices = vec![];
356 let mut filtered_dtbo_paths = vec![];
357 for symbol_prop in symbols_node.properties()? {
358 let symbol_prop_value = symbol_prop.value()?;
359 let dtbo_node_path = CStr::from_bytes_with_nul(symbol_prop_value)
360 .or(Err(DeviceAssignmentError::InvalidSymbols))?;
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900361 let assigned_device =
362 AssignedDeviceInfo::parse(fdt, vm_dtbo, dtbo_node_path, &pviommus)?;
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900363 if let Some(assigned_device) = assigned_device {
364 assigned_devices.push(assigned_device);
365 } else {
366 filtered_dtbo_paths.push(dtbo_node_path.into());
367 }
368 }
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900369 if assigned_devices.is_empty() {
370 return Ok(None);
371 }
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900372 filtered_dtbo_paths.push(CString::new("/__symbols__").unwrap());
373
374 Ok(Some(Self { pviommus: unique_pviommus, assigned_devices, filtered_dtbo_paths }))
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900375 }
376
377 /// Filters VM DTBO to only contain necessary information for booting pVM
378 /// In detail, this will remove followings by setting nop node / nop property.
379 /// - Removes unassigned devices
380 /// - Removes /__symbols__ node
381 // TODO(b/277993056): remove unused dependencies in VM DTBO.
382 // TODO(b/277993056): remove supernodes' properties.
383 // TODO(b/277993056): remove unused alises.
384 pub fn filter(&self, vm_dtbo: &mut VmDtbo) -> Result<()> {
385 let vm_dtbo = vm_dtbo.as_mut();
386
387 // Filters unused node in assigned devices
388 for filtered_dtbo_path in &self.filtered_dtbo_paths {
389 let node = vm_dtbo.node_mut(filtered_dtbo_path).unwrap().unwrap();
390 node.nop()?;
391 }
392
Pierre-Clément Tosifc3e8b52023-11-08 14:30:29 +0000393 // Filters pvmfw-specific properties in assigned device node.
394 const FILTERED_VM_DTBO_PROP: [&CStr; 3] = [
395 cstr!("android,pvmfw,phy-reg"),
396 cstr!("android,pvmfw,phy-iommu"),
397 cstr!("android,pvmfw,phy-sid"),
398 ];
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900399 for assigned_device in &self.assigned_devices {
400 let mut node = vm_dtbo.node_mut(&assigned_device.dtbo_node_path).unwrap().unwrap();
401 for prop in FILTERED_VM_DTBO_PROP {
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900402 match node.nop_property(prop) {
403 Err(FdtError::NotFound) => Ok(()), // allows not exists
404 other => other,
405 }?;
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900406 }
407 }
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900408
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900409 Ok(())
410 }
411
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900412 fn patch_pviommus(&self, fdt: &mut Fdt) -> Result<BTreeMap<PvIommu, Phandle>> {
413 let mut compatible = fdt.root_mut()?.next_compatible(Self::PVIOMMU_COMPATIBLE)?;
414 let mut pviommu_phandles = BTreeMap::new();
415
416 for pviommu in &self.pviommus {
417 let mut node = compatible.ok_or(DeviceAssignmentError::TooManyPvIommu)?;
418 let phandle = node.as_node().get_phandle()?.ok_or(DeviceAssignmentError::Internal)?;
419 node.setprop_inplace(cstr!("id"), &pviommu.id.to_be_bytes())?;
420 if pviommu_phandles.insert(*pviommu, phandle).is_some() {
421 return Err(DeviceAssignmentError::Internal);
422 }
423 compatible = node.next_compatible(Self::PVIOMMU_COMPATIBLE)?;
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900424 }
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900425
426 // Filters pre-populated but unassigned pvIOMMUs.
427 while let Some(filtered_pviommu) = compatible {
428 compatible = filtered_pviommu.delete_and_next_compatible(Self::PVIOMMU_COMPATIBLE)?;
429 }
430
431 Ok(pviommu_phandles)
432 }
433
434 pub fn patch(&self, fdt: &mut Fdt) -> Result<()> {
435 let pviommu_phandles = self.patch_pviommus(fdt)?;
436
437 // Patches assigned devices
438 for device in &self.assigned_devices {
439 device.patch(fdt, &pviommu_phandles)?;
440 }
441
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900442 Ok(())
443 }
444}
445
446#[cfg(test)]
447mod tests {
448 use super::*;
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900449 use alloc::collections::BTreeSet;
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900450 use std::fs;
451
452 const VM_DTBO_FILE_PATH: &str = "test_pvmfw_devices_vm_dtbo.dtbo";
453 const VM_DTBO_WITHOUT_SYMBOLS_FILE_PATH: &str =
454 "test_pvmfw_devices_vm_dtbo_without_symbols.dtbo";
455 const FDT_FILE_PATH: &str = "test_pvmfw_devices_with_rng.dtb";
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900456 const FDT_WITH_IOMMU_FILE_PATH: &str = "test_pvmfw_devices_with_rng_iommu.dtb";
457 const FDT_WITH_MULTIPLE_DEVICES_IOMMUS_FILE_PATH: &str =
458 "test_pvmfw_devices_with_multiple_devices_iommus.dtb";
459 const FDT_WITH_IOMMU_SHARING: &str = "test_pvmfw_devices_with_iommu_sharing.dtb";
460 const FDT_WITH_IOMMU_ID_CONFLICT: &str = "test_pvmfw_devices_with_iommu_id_conflict.dtb";
461
462 #[derive(Debug, Eq, PartialEq)]
463 struct AssignedDeviceNode {
464 path: CString,
465 reg: Vec<u8>,
466 interrupts: Vec<u8>,
467 iommus: Vec<u32>, // pvIOMMU ids
468 }
469
470 impl AssignedDeviceNode {
471 fn parse(fdt: &Fdt, path: &CStr) -> Result<Self> {
472 let Some(node) = fdt.node(path)? else {
473 return Err(FdtError::NotFound.into());
474 };
475
476 // TODO(b/277993056): Replace DeviceAssignmentError::Internal
477 let reg = node.getprop(cstr!("reg"))?.ok_or(DeviceAssignmentError::Internal)?;
478 let interrupts = node
479 .getprop(cstr!("interrupts"))?
480 .ok_or(DeviceAssignmentError::InvalidInterrupts)?;
481 let mut iommus = vec![];
Jaewan Kima9200492023-11-21 20:45:31 +0900482 if let Some(mut cells) = node.getprop_cells(cstr!("iommus"))? {
483 while let Some(pviommu_id) = cells.next() {
484 // pvIOMMU id
485 let phandle = Phandle::try_from(pviommu_id)?;
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900486 let pviommu = fdt
487 .node_with_phandle(phandle)?
488 .ok_or(DeviceAssignmentError::InvalidIommus)?;
489 let compatible = pviommu.getprop_str(cstr!("compatible"));
490 if compatible != Ok(Some(cstr!("pkvm,pviommu"))) {
491 return Err(DeviceAssignmentError::InvalidIommus);
492 }
493 let id = pviommu
494 .getprop_u32(cstr!("id"))?
495 .ok_or(DeviceAssignmentError::InvalidIommus)?;
496 iommus.push(id);
Jaewan Kima9200492023-11-21 20:45:31 +0900497
498 // vSID
499 let Some(vsid) = cells.next() else {
500 return Err(DeviceAssignmentError::InvalidIommus);
501 };
502 iommus.push(vsid);
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900503 }
504 }
505 Ok(Self { path: path.into(), reg: reg.into(), interrupts: interrupts.into(), iommus })
506 }
507 }
508
509 fn collect_pviommus(fdt: &Fdt) -> Result<Vec<u32>> {
510 let mut pviommus = BTreeSet::new();
511 for pviommu in fdt.compatible_nodes(cstr!("pkvm,pviommu"))? {
512 if let Ok(Some(id)) = pviommu.getprop_u32(cstr!("id")) {
513 pviommus.insert(id);
514 }
515 }
516 Ok(pviommus.iter().cloned().collect())
517 }
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900518
519 fn into_fdt_prop(native_bytes: Vec<u32>) -> Vec<u8> {
520 let mut v = Vec::with_capacity(native_bytes.len() * 4);
521 for byte in native_bytes {
522 v.extend_from_slice(&byte.to_be_bytes());
523 }
524 v
525 }
526
527 #[test]
528 fn device_info_new_without_symbols() {
529 let mut fdt_data = fs::read(FDT_FILE_PATH).unwrap();
530 let mut vm_dtbo_data = fs::read(VM_DTBO_WITHOUT_SYMBOLS_FILE_PATH).unwrap();
531 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
532 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
533
534 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo).unwrap();
535 assert_eq!(device_info, None);
536 }
537
538 #[test]
539 fn device_info_assigned_info() {
540 let mut fdt_data = fs::read(FDT_FILE_PATH).unwrap();
541 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
542 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
543 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
544
545 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo).unwrap().unwrap();
546
547 let expected = [AssignedDeviceInfo {
548 node_path: CString::new("/rng").unwrap(),
549 dtbo_node_path: cstr!("/fragment@rng/__overlay__/rng").into(),
550 reg: into_fdt_prop(vec![0x0, 0x9, 0x0, 0xFF]),
551 interrupts: into_fdt_prop(vec![0x0, 0xF, 0x4]),
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900552 iommus: vec![],
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900553 }];
554
555 assert_eq!(device_info.assigned_devices, expected);
556 }
557
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900558 // TODO(b/311655051): Test with real once instead of empty FDT.
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900559 #[test]
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900560 fn device_info_new_with_empty_device_tree() {
561 let mut fdt_data = vec![0; pvmfw_fdt_template::RAW.len()];
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900562 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900563 let fdt = Fdt::create_empty_tree(&mut fdt_data).unwrap();
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900564 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
565
566 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo).unwrap();
567 assert_eq!(device_info, None);
568 }
569
570 #[test]
571 fn device_info_filter() {
572 let mut fdt_data = fs::read(FDT_FILE_PATH).unwrap();
573 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
574 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
575 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
576
577 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo).unwrap().unwrap();
578 device_info.filter(vm_dtbo).unwrap();
579
580 let vm_dtbo = vm_dtbo.as_mut();
581
582 let rng = vm_dtbo.node(cstr!("/fragment@rng/__overlay__/rng")).unwrap();
583 assert_ne!(rng, None);
584
585 let light = vm_dtbo.node(cstr!("/fragment@rng/__overlay__/light")).unwrap();
586 assert_eq!(light, None);
587
588 let symbols_node = vm_dtbo.symbols().unwrap();
589 assert_eq!(symbols_node, None);
590 }
591
592 #[test]
593 fn device_info_patch() {
594 let mut fdt_data = fs::read(FDT_FILE_PATH).unwrap();
595 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
596 let mut data = vec![0_u8; fdt_data.len() + vm_dtbo_data.len()];
597 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
598 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
599 let platform_dt = Fdt::create_empty_tree(data.as_mut_slice()).unwrap();
600
601 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo).unwrap().unwrap();
602 device_info.filter(vm_dtbo).unwrap();
603
604 // SAFETY: Damaged VM DTBO wouldn't be used after this unsafe block.
605 unsafe {
606 platform_dt.apply_overlay(vm_dtbo.as_mut()).unwrap();
607 }
Jaewan Kim0bd637d2023-11-10 13:09:41 +0900608 device_info.patch(platform_dt).unwrap();
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900609
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900610 // Note: Intentionally not using AssignedDeviceNode for matching all props.
Jaewan Kim0bd637d2023-11-10 13:09:41 +0900611 type FdtResult<T> = libfdt::Result<T>;
612 let expected: Vec<(FdtResult<&CStr>, FdtResult<Vec<u8>>)> = vec![
613 (Ok(cstr!("android,rng,ignore-gctrl-reset")), Ok(Vec::new())),
614 (Ok(cstr!("compatible")), Ok(Vec::from(*b"android,rng\0"))),
615 (Ok(cstr!("interrupts")), Ok(into_fdt_prop(vec![0x0, 0xF, 0x4]))),
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900616 (Ok(cstr!("iommus")), Ok(Vec::new())),
Jaewan Kim0bd637d2023-11-10 13:09:41 +0900617 (Ok(cstr!("reg")), Ok(into_fdt_prop(vec![0x0, 0x9, 0x0, 0xFF]))),
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900618 ];
619
Jaewan Kim0bd637d2023-11-10 13:09:41 +0900620 let rng_node = platform_dt.node(cstr!("/rng")).unwrap().unwrap();
621 let mut properties: Vec<_> = rng_node
622 .properties()
623 .unwrap()
624 .map(|prop| (prop.name(), prop.value().map(|x| x.into())))
625 .collect();
626 properties.sort_by(|a, b| {
627 let lhs = a.0.unwrap_or_default();
628 let rhs = b.0.unwrap_or_default();
629 lhs.partial_cmp(rhs).unwrap()
630 });
631
632 assert_eq!(properties, expected);
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900633 }
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900634
635 #[test]
636 fn device_info_overlay_iommu() {
637 let mut fdt_data = fs::read(FDT_WITH_IOMMU_FILE_PATH).unwrap();
638 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
639 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
640 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
641 let mut platform_dt_data = pvmfw_fdt_template::RAW.to_vec();
642 platform_dt_data.resize(pvmfw_fdt_template::RAW.len() * 2, 0);
643 let platform_dt = Fdt::from_mut_slice(&mut platform_dt_data).unwrap();
644 platform_dt.unpack().unwrap();
645
646 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo).unwrap().unwrap();
647 device_info.filter(vm_dtbo).unwrap();
648
649 // SAFETY: Damaged VM DTBO wouldn't be used after this unsafe block.
650 unsafe {
651 platform_dt.apply_overlay(vm_dtbo.as_mut()).unwrap();
652 }
653 device_info.patch(platform_dt).unwrap();
654
655 let expected = AssignedDeviceNode {
656 path: CString::new("/rng").unwrap(),
657 reg: into_fdt_prop(vec![0x0, 0x9, 0x0, 0xFF]),
658 interrupts: into_fdt_prop(vec![0x0, 0xF, 0x4]),
Jaewan Kima9200492023-11-21 20:45:31 +0900659 iommus: vec![0x4, 0xFF0],
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900660 };
661
662 let node = AssignedDeviceNode::parse(platform_dt, &expected.path);
663 assert_eq!(node, Ok(expected));
664
665 let pviommus = collect_pviommus(platform_dt);
666 assert_eq!(pviommus, Ok(vec![0x4]));
667 }
668
669 #[test]
670 fn device_info_multiple_devices_iommus() {
671 let mut fdt_data = fs::read(FDT_WITH_MULTIPLE_DEVICES_IOMMUS_FILE_PATH).unwrap();
672 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
673 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
674 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
675 let mut platform_dt_data = pvmfw_fdt_template::RAW.to_vec();
676 platform_dt_data.resize(pvmfw_fdt_template::RAW.len() * 2, 0);
677 let platform_dt = Fdt::from_mut_slice(&mut platform_dt_data).unwrap();
678 platform_dt.unpack().unwrap();
679
680 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo).unwrap().unwrap();
681 device_info.filter(vm_dtbo).unwrap();
682
683 // SAFETY: Damaged VM DTBO wouldn't be used after this unsafe block.
684 unsafe {
685 platform_dt.apply_overlay(vm_dtbo.as_mut()).unwrap();
686 }
687 device_info.patch(platform_dt).unwrap();
688
689 let expected_devices = [
690 AssignedDeviceNode {
691 path: CString::new("/rng").unwrap(),
692 reg: into_fdt_prop(vec![0x0, 0x9, 0x0, 0xFF]),
693 interrupts: into_fdt_prop(vec![0x0, 0xF, 0x4]),
Jaewan Kima9200492023-11-21 20:45:31 +0900694 iommus: vec![0x4, 0xFF0, 0x9, 0xFF1],
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900695 },
696 AssignedDeviceNode {
697 path: CString::new("/light").unwrap(),
698 reg: into_fdt_prop(vec![0x100, 0x9]),
699 interrupts: into_fdt_prop(vec![0x0, 0xF, 0x5]),
Jaewan Kima9200492023-11-21 20:45:31 +0900700 iommus: vec![0x40, 0xFFA, 0x50, 0xFFB, 0x60, 0xFFC],
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900701 },
702 ];
703
704 for expected in expected_devices {
705 let node = AssignedDeviceNode::parse(platform_dt, &expected.path);
706 assert_eq!(node, Ok(expected));
707 }
708 let pviommus = collect_pviommus(platform_dt);
709 assert_eq!(pviommus, Ok(vec![0x4, 0x9, 0x40, 0x50, 0x60]));
710 }
711
712 #[test]
713 fn device_info_iommu_sharing() {
714 let mut fdt_data = fs::read(FDT_WITH_IOMMU_SHARING).unwrap();
715 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
716 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
717 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
718 let mut platform_dt_data = pvmfw_fdt_template::RAW.to_vec();
719 platform_dt_data.resize(pvmfw_fdt_template::RAW.len() * 2, 0);
720 let platform_dt = Fdt::from_mut_slice(&mut platform_dt_data).unwrap();
721 platform_dt.unpack().unwrap();
722
723 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo).unwrap().unwrap();
724 device_info.filter(vm_dtbo).unwrap();
725
726 // SAFETY: Damaged VM DTBO wouldn't be used after this unsafe block.
727 unsafe {
728 platform_dt.apply_overlay(vm_dtbo.as_mut()).unwrap();
729 }
730 device_info.patch(platform_dt).unwrap();
731
732 let expected_devices = [
733 AssignedDeviceNode {
734 path: CString::new("/rng").unwrap(),
735 reg: into_fdt_prop(vec![0x0, 0x9, 0x0, 0xFF]),
736 interrupts: into_fdt_prop(vec![0x0, 0xF, 0x4]),
Jaewan Kima9200492023-11-21 20:45:31 +0900737 iommus: vec![0x4, 0xFF0, 0x9, 0xFF1],
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900738 },
739 AssignedDeviceNode {
740 path: CString::new("/light").unwrap(),
741 reg: into_fdt_prop(vec![0x100, 0x9]),
742 interrupts: into_fdt_prop(vec![0x0, 0xF, 0x5]),
Jaewan Kima9200492023-11-21 20:45:31 +0900743 iommus: vec![0x9, 0xFF1, 0x40, 0xFFA],
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900744 },
745 ];
746
747 for expected in expected_devices {
748 let node = AssignedDeviceNode::parse(platform_dt, &expected.path);
749 assert_eq!(node, Ok(expected));
750 }
751
752 let pviommus = collect_pviommus(platform_dt);
753 assert_eq!(pviommus, Ok(vec![0x4, 0x9, 0x40]));
754 }
755
756 #[test]
757 fn device_info_iommu_id_conflict() {
758 let mut fdt_data = fs::read(FDT_WITH_IOMMU_ID_CONFLICT).unwrap();
759 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
760 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
761 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
762
763 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo);
764
765 assert_eq!(device_info, Err(DeviceAssignmentError::DuplicatedPvIommuIds));
766 }
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900767}