blob: a2816c4a6e65318e0ba03358ad51a2646ae63fed [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
22use alloc::ffi::CString;
23use alloc::fmt;
24use alloc::vec;
25use alloc::vec::Vec;
26use core::ffi::CStr;
27use core::iter::Iterator;
28use core::mem;
29use libfdt::{Fdt, FdtError, FdtNode};
30
31// TODO(b/308694211): Move this to the vmbase
32macro_rules! const_cstr {
33 ($str:literal) => {{
34 #[allow(unused_unsafe)] // In case the macro is used within an unsafe block.
35 // SAFETY: Trailing null is guaranteed by concat!()
36 unsafe {
37 CStr::from_bytes_with_nul_unchecked(concat!($str, "\0").as_bytes())
38 }
39 }};
40}
41
42// TODO(b/308694211): Use cstr! from vmbase instead.
43macro_rules! cstr {
44 ($str:literal) => {{
45 CStr::from_bytes_with_nul(concat!($str, "\0").as_bytes()).unwrap()
46 }};
47}
48
49const FILTERED_VM_DTBO_PROP: [&CStr; 3] = [
50 const_cstr!("android,pvmfw,phy-reg"),
51 const_cstr!("android,pvmfw,phy-iommu"),
52 const_cstr!("android,pvmfw,phy-sid"),
53];
54
55const REG_PROP_NAME: &CStr = const_cstr!("reg");
56const INTERRUPTS_PROP_NAME: &CStr = const_cstr!("interrupts");
57// TODO(b/277993056): Keep constants derived from platform.dts in one place.
58const CELLS_PER_INTERRUPT: usize = 3; // from /intc node in platform.dts
59
60/// Errors in device assignment.
61#[derive(Clone, Copy, Debug, Eq, PartialEq)]
62pub enum DeviceAssignmentError {
63 // Invalid VM DTBO
64 InvalidDtbo,
65 /// Invalid __symbols__
66 InvalidSymbols,
67 /// Invalid <interrupts>
68 InvalidInterrupts,
69 /// Unsupported overlay target syntax. Only supports <target-path> with full path.
70 UnsupportedOverlayTarget,
71 /// Unexpected error from libfdt
72 UnexpectedFdtError(FdtError),
73}
74
75impl From<FdtError> for DeviceAssignmentError {
76 fn from(e: FdtError) -> Self {
77 DeviceAssignmentError::UnexpectedFdtError(e)
78 }
79}
80
81impl fmt::Display for DeviceAssignmentError {
82 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
83 match self {
84 Self::InvalidDtbo => write!(f, "Invalid DTBO"),
85 Self::InvalidSymbols => write!(
86 f,
87 "Invalid property in /__symbols__. Must point to valid assignable device node."
88 ),
89 Self::InvalidInterrupts => write!(f, "Invalid <interrupts>"),
90 Self::UnsupportedOverlayTarget => {
91 write!(f, "Unsupported overlay target. Only supports 'target-path = \"/\"'")
92 }
93 Self::UnexpectedFdtError(e) => write!(f, "Unexpected Error from libfdt: {e}"),
94 }
95 }
96}
97
98pub type Result<T> = core::result::Result<T, DeviceAssignmentError>;
99
100/// Represents VM DTBO
101#[repr(transparent)]
102pub struct VmDtbo(Fdt);
103
104impl VmDtbo {
105 const OVERLAY_NODE_NAME: &CStr = const_cstr!("__overlay__");
106 const TARGET_PATH_PROP: &CStr = const_cstr!("target-path");
107 const SYMBOLS_NODE_PATH: &CStr = const_cstr!("/__symbols__");
108
109 /// Wraps a mutable slice containing a VM DTBO.
110 ///
111 /// Fails if the VM DTBO does not pass validation.
112 pub fn from_mut_slice(dtbo: &mut [u8]) -> Result<&mut Self> {
113 // This validates DTBO
114 let fdt = Fdt::from_mut_slice(dtbo)?;
115 // SAFETY: VmDtbo is a transparent wrapper around Fdt, so representation is the same.
116 Ok(unsafe { mem::transmute::<&mut Fdt, &mut Self>(fdt) })
117 }
118
119 // Locates device node path as if the given dtbo node path is assigned and VM DTBO is overlaid.
120 // For given dtbo node path, this concatenates <target-path> of the enclosing fragment and
121 // relative path from __overlay__ node.
122 //
123 // Here's an example with sample VM DTBO:
124 // / {
125 // fragment@rng {
126 // target-path = "/"; // Always 'target-path = "/"'. Disallows <target> or other path.
127 // __overlay__ {
128 // rng { ... }; // Actual device node is here. If overlaid, path would be "/rng"
129 // };
130 // };
131 // __symbols__ { // List of assignable devices
132 // // Each property describes an assigned device device information.
133 // // property name is the device label, and property value is the path in the VM DTBO.
134 // rng = "/fragment@rng/__overlay__/rng";
135 // };
136 // };
137 //
138 // Then locate_overlay_target_path(cstr!("/fragment@rng/__overlay__/rng")) is Ok("/rng")
139 //
140 // Contrary to fdt_overlay_target_offset(), this API enforces overlay target property
141 // 'target-path = "/"', so the overlay doesn't modify and/or append platform DT's existing
142 // node and/or properties. The enforcement is for compatibility reason.
143 fn locate_overlay_target_path(&self, dtbo_node_path: &CStr) -> Result<CString> {
144 let dtbo_node_path_bytes = dtbo_node_path.to_bytes();
145 if dtbo_node_path_bytes.first() != Some(&b'/') {
146 return Err(DeviceAssignmentError::UnsupportedOverlayTarget);
147 }
148
149 let node = self.0.node(dtbo_node_path)?.ok_or(DeviceAssignmentError::InvalidSymbols)?;
150
151 let fragment_node = node.supernode_at_depth(1)?;
152 let target_path = fragment_node
153 .getprop_str(Self::TARGET_PATH_PROP)?
154 .ok_or(DeviceAssignmentError::InvalidDtbo)?;
155 if target_path != cstr!("/") {
156 return Err(DeviceAssignmentError::UnsupportedOverlayTarget);
157 }
158
159 let mut components = dtbo_node_path_bytes
160 .split(|char| *char == b'/')
161 .filter(|&component| !component.is_empty())
162 .skip(1);
163 let overlay_node_name = components.next();
164 if overlay_node_name != Some(Self::OVERLAY_NODE_NAME.to_bytes()) {
165 return Err(DeviceAssignmentError::InvalidDtbo);
166 }
167 let mut overlaid_path = Vec::with_capacity(dtbo_node_path_bytes.len());
168 for component in components {
169 overlaid_path.push(b'/');
170 overlaid_path.extend_from_slice(component);
171 }
172 overlaid_path.push(b'\0');
173
174 Ok(CString::from_vec_with_nul(overlaid_path).unwrap())
175 }
176}
177
178impl AsRef<Fdt> for VmDtbo {
179 fn as_ref(&self) -> &Fdt {
180 &self.0
181 }
182}
183
184impl AsMut<Fdt> for VmDtbo {
185 fn as_mut(&mut self) -> &mut Fdt {
186 &mut self.0
187 }
188}
189
190/// Assigned device information parsed from crosvm DT.
191/// Keeps everything in the owned data because underlying FDT will be reused for platform DT.
192#[derive(Debug, Eq, PartialEq)]
193struct AssignedDeviceInfo {
194 // Node path of assigned device (e.g. "/rng")
195 node_path: CString,
196 // DTBO node path of the assigned device (e.g. "/fragment@rng/__overlay__/rng")
197 dtbo_node_path: CString,
198 // <reg> property from the crosvm DT
199 reg: Vec<u8>,
200 // <interrupts> property from the crosvm DT
201 interrupts: Vec<u8>,
202}
203
204impl AssignedDeviceInfo {
205 fn parse_interrupts(node: &FdtNode) -> Result<Vec<u8>> {
206 // Validation: Validate if interrupts cell numbers are multiple of #interrupt-cells.
207 // We can't know how many interrupts would exist.
208 let interrupts_cells = node
209 .getprop_cells(INTERRUPTS_PROP_NAME)?
210 .ok_or(DeviceAssignmentError::InvalidInterrupts)?
211 .count();
212 if interrupts_cells % CELLS_PER_INTERRUPT != 0 {
213 return Err(DeviceAssignmentError::InvalidInterrupts);
214 }
215
216 // Once validated, keep the raw bytes so patch can be done with setprop()
217 Ok(node.getprop(INTERRUPTS_PROP_NAME).unwrap().unwrap().into())
218 }
219
220 // TODO(b/277993056): Read and validate iommu
221 fn parse(fdt: &Fdt, vm_dtbo: &VmDtbo, dtbo_node_path: &CStr) -> Result<Option<Self>> {
222 let node_path = vm_dtbo.locate_overlay_target_path(dtbo_node_path)?;
223
224 let Some(node) = fdt.node(&node_path)? else { return Ok(None) };
225
226 // TODO(b/277993056): Validate reg with HVC, and keep reg with FdtNode::reg()
227 let reg = node.getprop(REG_PROP_NAME).unwrap().unwrap();
228
229 let interrupts = Self::parse_interrupts(&node)?;
230
231 Ok(Some(Self {
232 node_path,
233 dtbo_node_path: dtbo_node_path.into(),
234 reg: reg.to_vec(),
235 interrupts: interrupts.to_vec(),
236 }))
237 }
238
239 fn patch(&self, fdt: &mut Fdt) -> Result<()> {
240 let mut dst = fdt.node_mut(&self.node_path)?.unwrap();
241 dst.setprop(REG_PROP_NAME, &self.reg)?;
242 dst.setprop(INTERRUPTS_PROP_NAME, &self.interrupts)?;
243 // TODO(b/277993056): Read and patch iommu
244 Ok(())
245 }
246}
247
248#[derive(Debug, Default, Eq, PartialEq)]
249pub struct DeviceAssignmentInfo {
250 assigned_devices: Vec<AssignedDeviceInfo>,
251 filtered_dtbo_paths: Vec<CString>,
252}
253
254impl DeviceAssignmentInfo {
255 /// Parses fdt and vm_dtbo, and creates new DeviceAssignmentInfo
256 // TODO(b/277993056): Parse __local_fixups__
257 // TODO(b/277993056): Parse __fixups__
258 pub fn parse(fdt: &Fdt, vm_dtbo: &VmDtbo) -> Result<Option<Self>> {
259 let Some(symbols_node) = vm_dtbo.as_ref().symbols()? else {
260 // /__symbols__ should contain all assignable devices.
261 // If empty, then nothing can be assigned.
262 return Ok(None);
263 };
264
265 let mut assigned_devices = vec![];
266 let mut filtered_dtbo_paths = vec![];
267 for symbol_prop in symbols_node.properties()? {
268 let symbol_prop_value = symbol_prop.value()?;
269 let dtbo_node_path = CStr::from_bytes_with_nul(symbol_prop_value)
270 .or(Err(DeviceAssignmentError::InvalidSymbols))?;
271 let assigned_device = AssignedDeviceInfo::parse(fdt, vm_dtbo, dtbo_node_path)?;
272 if let Some(assigned_device) = assigned_device {
273 assigned_devices.push(assigned_device);
274 } else {
275 filtered_dtbo_paths.push(dtbo_node_path.into());
276 }
277 }
278 filtered_dtbo_paths.push(VmDtbo::SYMBOLS_NODE_PATH.into());
279
280 if assigned_devices.is_empty() {
281 return Ok(None);
282 }
283 Ok(Some(Self { assigned_devices, filtered_dtbo_paths }))
284 }
285
286 /// Filters VM DTBO to only contain necessary information for booting pVM
287 /// In detail, this will remove followings by setting nop node / nop property.
288 /// - Removes unassigned devices
289 /// - Removes /__symbols__ node
290 // TODO(b/277993056): remove unused dependencies in VM DTBO.
291 // TODO(b/277993056): remove supernodes' properties.
292 // TODO(b/277993056): remove unused alises.
293 pub fn filter(&self, vm_dtbo: &mut VmDtbo) -> Result<()> {
294 let vm_dtbo = vm_dtbo.as_mut();
295
296 // Filters unused node in assigned devices
297 for filtered_dtbo_path in &self.filtered_dtbo_paths {
298 let node = vm_dtbo.node_mut(filtered_dtbo_path).unwrap().unwrap();
299 node.nop()?;
300 }
301
302 // Filters unused properties in assigned device node
303 for assigned_device in &self.assigned_devices {
304 let mut node = vm_dtbo.node_mut(&assigned_device.dtbo_node_path).unwrap().unwrap();
305 for prop in FILTERED_VM_DTBO_PROP {
306 node.nop_property(prop)?;
307 }
308 }
309 Ok(())
310 }
311
312 pub fn patch(&self, fdt: &mut Fdt) -> Result<()> {
313 for device in &self.assigned_devices {
314 device.patch(fdt)?
315 }
316 Ok(())
317 }
318}
319
320#[cfg(test)]
321mod tests {
322 use super::*;
323 use std::fs;
324
325 const VM_DTBO_FILE_PATH: &str = "test_pvmfw_devices_vm_dtbo.dtbo";
326 const VM_DTBO_WITHOUT_SYMBOLS_FILE_PATH: &str =
327 "test_pvmfw_devices_vm_dtbo_without_symbols.dtbo";
328 const FDT_FILE_PATH: &str = "test_pvmfw_devices_with_rng.dtb";
329
330 fn into_fdt_prop(native_bytes: Vec<u32>) -> Vec<u8> {
331 let mut v = Vec::with_capacity(native_bytes.len() * 4);
332 for byte in native_bytes {
333 v.extend_from_slice(&byte.to_be_bytes());
334 }
335 v
336 }
337
338 #[test]
339 fn device_info_new_without_symbols() {
340 let mut fdt_data = fs::read(FDT_FILE_PATH).unwrap();
341 let mut vm_dtbo_data = fs::read(VM_DTBO_WITHOUT_SYMBOLS_FILE_PATH).unwrap();
342 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
343 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
344
345 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo).unwrap();
346 assert_eq!(device_info, None);
347 }
348
349 #[test]
350 fn device_info_assigned_info() {
351 let mut fdt_data = fs::read(FDT_FILE_PATH).unwrap();
352 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
353 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
354 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
355
356 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo).unwrap().unwrap();
357
358 let expected = [AssignedDeviceInfo {
359 node_path: CString::new("/rng").unwrap(),
360 dtbo_node_path: cstr!("/fragment@rng/__overlay__/rng").into(),
361 reg: into_fdt_prop(vec![0x0, 0x9, 0x0, 0xFF]),
362 interrupts: into_fdt_prop(vec![0x0, 0xF, 0x4]),
363 }];
364
365 assert_eq!(device_info.assigned_devices, expected);
366 }
367
368 #[test]
369 fn device_info_new_without_assigned_devices() {
370 let mut fdt_data: Vec<u8> = pvmfw_fdt_template::RAW.into();
371 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
372 let fdt = Fdt::from_mut_slice(fdt_data.as_mut_slice()).unwrap();
373 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
374
375 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo).unwrap();
376 assert_eq!(device_info, None);
377 }
378
379 #[test]
380 fn device_info_filter() {
381 let mut fdt_data = fs::read(FDT_FILE_PATH).unwrap();
382 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
383 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
384 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
385
386 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo).unwrap().unwrap();
387 device_info.filter(vm_dtbo).unwrap();
388
389 let vm_dtbo = vm_dtbo.as_mut();
390
391 let rng = vm_dtbo.node(cstr!("/fragment@rng/__overlay__/rng")).unwrap();
392 assert_ne!(rng, None);
393
394 let light = vm_dtbo.node(cstr!("/fragment@rng/__overlay__/light")).unwrap();
395 assert_eq!(light, None);
396
397 let symbols_node = vm_dtbo.symbols().unwrap();
398 assert_eq!(symbols_node, None);
399 }
400
401 #[test]
402 fn device_info_patch() {
403 let mut fdt_data = fs::read(FDT_FILE_PATH).unwrap();
404 let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
405 let mut data = vec![0_u8; fdt_data.len() + vm_dtbo_data.len()];
406 let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
407 let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
408 let platform_dt = Fdt::create_empty_tree(data.as_mut_slice()).unwrap();
409
410 let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo).unwrap().unwrap();
411 device_info.filter(vm_dtbo).unwrap();
412
413 // SAFETY: Damaged VM DTBO wouldn't be used after this unsafe block.
414 unsafe {
415 platform_dt.apply_overlay(vm_dtbo.as_mut()).unwrap();
416 }
417
418 let rng_node = platform_dt.node(cstr!("/rng")).unwrap().unwrap();
419 let expected: Vec<(&CStr, Vec<u8>)> = vec![
420 (cstr!("android,rng,ignore-gctrl-reset"), Vec::<u8>::new()),
421 (cstr!("compatible"), b"android,rng\0".to_vec()),
422 (cstr!("reg"), into_fdt_prop(vec![0x0, 0x9, 0x0, 0xFF])),
423 (cstr!("interrupts"), into_fdt_prop(vec![0x0, 0xF, 0x4])),
424 ];
425
426 for (prop, (prop_name, prop_value)) in rng_node.properties().unwrap().zip(expected) {
427 assert_eq!((prop.name(), prop.value()), (Ok(prop_name), Ok(prop_value.as_slice())));
428 }
429 }
430}