blob: 14f1fe5cb8146589069048cb1ee57e75e81fb466 [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";
Jaewan Kima67e36a2023-11-29 16:50:23 +0900455 const FDT_WITHOUT_IOMMUS_FILE_PATH: &str = "test_pvmfw_devices_without_iommus.dtb";
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900456 const FDT_FILE_PATH: &str = "test_pvmfw_devices_with_rng.dtb";
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900457 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>,
Jaewan Kima67e36a2023-11-29 16:50:23 +0900467 iommus: Vec<u32>, // pvIOMMU id and vSID
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900468 }
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]
Jaewan Kima67e36a2023-11-29 16:50:23 +0900539 fn device_info_assigned_info_without_iommus() {
540 let mut fdt_data = fs::read(FDT_WITHOUT_IOMMUS_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("/backlight").unwrap(),
549 dtbo_node_path: cstr!("/fragment@backlight/__overlay__/backlight").into(),
550 reg: into_fdt_prop(vec![0x0, 0x9, 0x0, 0xFF]),
551 interrupts: into_fdt_prop(vec![0x0, 0xF, 0x4]),
552 iommus: vec![],
553 }];
554
555 assert_eq!(device_info.assigned_devices, expected);
556 }
557
558 #[test]
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900559 fn device_info_assigned_info() {
560 let mut fdt_data = fs::read(FDT_FILE_PATH).unwrap();
561 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
562 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
563 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
564
565 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo).unwrap().unwrap();
566
567 let expected = [AssignedDeviceInfo {
568 node_path: CString::new("/rng").unwrap(),
569 dtbo_node_path: cstr!("/fragment@rng/__overlay__/rng").into(),
570 reg: into_fdt_prop(vec![0x0, 0x9, 0x0, 0xFF]),
571 interrupts: into_fdt_prop(vec![0x0, 0xF, 0x4]),
Jaewan Kima67e36a2023-11-29 16:50:23 +0900572 iommus: vec![(PvIommu { id: 0x4 }, Vsid(0xFF0))],
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900573 }];
574
575 assert_eq!(device_info.assigned_devices, expected);
576 }
577
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900578 // TODO(b/311655051): Test with real once instead of empty FDT.
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900579 #[test]
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900580 fn device_info_new_with_empty_device_tree() {
581 let mut fdt_data = vec![0; pvmfw_fdt_template::RAW.len()];
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900582 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900583 let fdt = Fdt::create_empty_tree(&mut fdt_data).unwrap();
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900584 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
585
586 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo).unwrap();
587 assert_eq!(device_info, None);
588 }
589
590 #[test]
591 fn device_info_filter() {
592 let mut fdt_data = fs::read(FDT_FILE_PATH).unwrap();
593 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
594 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
595 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
596
597 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo).unwrap().unwrap();
598 device_info.filter(vm_dtbo).unwrap();
599
600 let vm_dtbo = vm_dtbo.as_mut();
601
602 let rng = vm_dtbo.node(cstr!("/fragment@rng/__overlay__/rng")).unwrap();
603 assert_ne!(rng, None);
604
605 let light = vm_dtbo.node(cstr!("/fragment@rng/__overlay__/light")).unwrap();
606 assert_eq!(light, None);
607
Jaewan Kima67e36a2023-11-29 16:50:23 +0900608 let led = vm_dtbo.node(cstr!("/fragment@led/__overlay__/led")).unwrap();
609 assert_eq!(led, None);
610
611 let backlight = vm_dtbo.node(cstr!("/fragment@backlight/__overlay__/backlight")).unwrap();
612 assert_eq!(backlight, None);
613
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900614 let symbols_node = vm_dtbo.symbols().unwrap();
615 assert_eq!(symbols_node, None);
616 }
617
618 #[test]
619 fn device_info_patch() {
Jaewan Kima67e36a2023-11-29 16:50:23 +0900620 let mut fdt_data = fs::read(FDT_WITHOUT_IOMMUS_FILE_PATH).unwrap();
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900621 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
622 let mut data = vec![0_u8; fdt_data.len() + vm_dtbo_data.len()];
623 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
624 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
625 let platform_dt = Fdt::create_empty_tree(data.as_mut_slice()).unwrap();
626
627 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo).unwrap().unwrap();
628 device_info.filter(vm_dtbo).unwrap();
629
630 // SAFETY: Damaged VM DTBO wouldn't be used after this unsafe block.
631 unsafe {
632 platform_dt.apply_overlay(vm_dtbo.as_mut()).unwrap();
633 }
Jaewan Kim0bd637d2023-11-10 13:09:41 +0900634 device_info.patch(platform_dt).unwrap();
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900635
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900636 // Note: Intentionally not using AssignedDeviceNode for matching all props.
Jaewan Kim0bd637d2023-11-10 13:09:41 +0900637 type FdtResult<T> = libfdt::Result<T>;
638 let expected: Vec<(FdtResult<&CStr>, FdtResult<Vec<u8>>)> = vec![
Jaewan Kima67e36a2023-11-29 16:50:23 +0900639 (Ok(cstr!("android,backlight,ignore-gctrl-reset")), Ok(Vec::new())),
640 (Ok(cstr!("compatible")), Ok(Vec::from(*b"android,backlight\0"))),
Jaewan Kim0bd637d2023-11-10 13:09:41 +0900641 (Ok(cstr!("interrupts")), Ok(into_fdt_prop(vec![0x0, 0xF, 0x4]))),
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900642 (Ok(cstr!("iommus")), Ok(Vec::new())),
Jaewan Kim0bd637d2023-11-10 13:09:41 +0900643 (Ok(cstr!("reg")), Ok(into_fdt_prop(vec![0x0, 0x9, 0x0, 0xFF]))),
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900644 ];
645
Jaewan Kima67e36a2023-11-29 16:50:23 +0900646 let rng_node = platform_dt.node(cstr!("/backlight")).unwrap().unwrap();
Jaewan Kim0bd637d2023-11-10 13:09:41 +0900647 let mut properties: Vec<_> = rng_node
648 .properties()
649 .unwrap()
650 .map(|prop| (prop.name(), prop.value().map(|x| x.into())))
651 .collect();
652 properties.sort_by(|a, b| {
653 let lhs = a.0.unwrap_or_default();
654 let rhs = b.0.unwrap_or_default();
655 lhs.partial_cmp(rhs).unwrap()
656 });
657
658 assert_eq!(properties, expected);
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900659 }
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900660
661 #[test]
662 fn device_info_overlay_iommu() {
Jaewan Kima67e36a2023-11-29 16:50:23 +0900663 let mut fdt_data = fs::read(FDT_FILE_PATH).unwrap();
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900664 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
665 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
666 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
667 let mut platform_dt_data = pvmfw_fdt_template::RAW.to_vec();
668 platform_dt_data.resize(pvmfw_fdt_template::RAW.len() * 2, 0);
669 let platform_dt = Fdt::from_mut_slice(&mut platform_dt_data).unwrap();
670 platform_dt.unpack().unwrap();
671
672 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo).unwrap().unwrap();
673 device_info.filter(vm_dtbo).unwrap();
674
675 // SAFETY: Damaged VM DTBO wouldn't be used after this unsafe block.
676 unsafe {
677 platform_dt.apply_overlay(vm_dtbo.as_mut()).unwrap();
678 }
679 device_info.patch(platform_dt).unwrap();
680
681 let expected = AssignedDeviceNode {
682 path: CString::new("/rng").unwrap(),
683 reg: into_fdt_prop(vec![0x0, 0x9, 0x0, 0xFF]),
684 interrupts: into_fdt_prop(vec![0x0, 0xF, 0x4]),
Jaewan Kima9200492023-11-21 20:45:31 +0900685 iommus: vec![0x4, 0xFF0],
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900686 };
687
688 let node = AssignedDeviceNode::parse(platform_dt, &expected.path);
689 assert_eq!(node, Ok(expected));
690
691 let pviommus = collect_pviommus(platform_dt);
692 assert_eq!(pviommus, Ok(vec![0x4]));
693 }
694
695 #[test]
696 fn device_info_multiple_devices_iommus() {
697 let mut fdt_data = fs::read(FDT_WITH_MULTIPLE_DEVICES_IOMMUS_FILE_PATH).unwrap();
698 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
699 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
700 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
701 let mut platform_dt_data = pvmfw_fdt_template::RAW.to_vec();
702 platform_dt_data.resize(pvmfw_fdt_template::RAW.len() * 2, 0);
703 let platform_dt = Fdt::from_mut_slice(&mut platform_dt_data).unwrap();
704 platform_dt.unpack().unwrap();
705
706 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo).unwrap().unwrap();
707 device_info.filter(vm_dtbo).unwrap();
708
709 // SAFETY: Damaged VM DTBO wouldn't be used after this unsafe block.
710 unsafe {
711 platform_dt.apply_overlay(vm_dtbo.as_mut()).unwrap();
712 }
713 device_info.patch(platform_dt).unwrap();
714
715 let expected_devices = [
716 AssignedDeviceNode {
717 path: CString::new("/rng").unwrap(),
718 reg: into_fdt_prop(vec![0x0, 0x9, 0x0, 0xFF]),
719 interrupts: into_fdt_prop(vec![0x0, 0xF, 0x4]),
Jaewan Kima67e36a2023-11-29 16:50:23 +0900720 iommus: vec![0x4, 0xFF0],
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900721 },
722 AssignedDeviceNode {
723 path: CString::new("/light").unwrap(),
724 reg: into_fdt_prop(vec![0x100, 0x9]),
725 interrupts: into_fdt_prop(vec![0x0, 0xF, 0x5]),
Jaewan Kima67e36a2023-11-29 16:50:23 +0900726 iommus: vec![0x40, 0xFFA, 0x50, 0xFFB],
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900727 },
728 ];
729
730 for expected in expected_devices {
731 let node = AssignedDeviceNode::parse(platform_dt, &expected.path);
732 assert_eq!(node, Ok(expected));
733 }
734 let pviommus = collect_pviommus(platform_dt);
Jaewan Kima67e36a2023-11-29 16:50:23 +0900735 assert_eq!(pviommus, Ok(vec![0x4, 0x40, 0x50]));
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900736 }
737
738 #[test]
739 fn device_info_iommu_sharing() {
740 let mut fdt_data = fs::read(FDT_WITH_IOMMU_SHARING).unwrap();
741 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
742 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
743 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
744 let mut platform_dt_data = pvmfw_fdt_template::RAW.to_vec();
745 platform_dt_data.resize(pvmfw_fdt_template::RAW.len() * 2, 0);
746 let platform_dt = Fdt::from_mut_slice(&mut platform_dt_data).unwrap();
747 platform_dt.unpack().unwrap();
748
749 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo).unwrap().unwrap();
750 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 }
756 device_info.patch(platform_dt).unwrap();
757
758 let expected_devices = [
759 AssignedDeviceNode {
760 path: CString::new("/rng").unwrap(),
761 reg: into_fdt_prop(vec![0x0, 0x9, 0x0, 0xFF]),
762 interrupts: into_fdt_prop(vec![0x0, 0xF, 0x4]),
Jaewan Kima67e36a2023-11-29 16:50:23 +0900763 iommus: vec![0x4, 0xFF0],
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900764 },
765 AssignedDeviceNode {
Jaewan Kima67e36a2023-11-29 16:50:23 +0900766 path: CString::new("/led").unwrap(),
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900767 reg: into_fdt_prop(vec![0x100, 0x9]),
768 interrupts: into_fdt_prop(vec![0x0, 0xF, 0x5]),
Jaewan Kima67e36a2023-11-29 16:50:23 +0900769 iommus: vec![0x4, 0xFF0],
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900770 },
771 ];
772
773 for expected in expected_devices {
774 let node = AssignedDeviceNode::parse(platform_dt, &expected.path);
775 assert_eq!(node, Ok(expected));
776 }
777
778 let pviommus = collect_pviommus(platform_dt);
Jaewan Kima67e36a2023-11-29 16:50:23 +0900779 assert_eq!(pviommus, Ok(vec![0x4]));
Jaewan Kim51ccfed2023-11-08 13:51:58 +0900780 }
781
782 #[test]
783 fn device_info_iommu_id_conflict() {
784 let mut fdt_data = fs::read(FDT_WITH_IOMMU_ID_CONFLICT).unwrap();
785 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
786 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
787 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
788
789 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo);
790
791 assert_eq!(device_info, Err(DeviceAssignmentError::DuplicatedPvIommuIds));
792 }
Jaewan Kimc6e023b2023-10-12 15:11:05 +0900793}