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