blob: 2bd1bec3adee7e0536ada02a87d233138ea7a126 [file] [log] [blame]
Elie Kheirallahea5dd522024-11-26 22:16:01 +00001// Copyright 2024, 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//! Integration test for VMs on device.
16
17use android_system_virtualizationservice::{
18 aidl::android::system::virtualizationservice::{
19 CpuTopology::CpuTopology, DiskImage::DiskImage, VirtualMachineConfig::VirtualMachineConfig,
20 VirtualMachineRawConfig::VirtualMachineRawConfig,
21 },
22 binder::{ParcelFileDescriptor, ProcessState},
23};
24use anyhow::anyhow;
25use anyhow::Context;
26use anyhow::Error;
27use log::error;
28use log::info;
29use std::fs::read_to_string;
30use std::fs::File;
31use std::io::Write;
32use std::process::Command;
33use vmclient::VmInstance;
34
35const VMBASE_EXAMPLE_KERNEL_PATH: &str = "vmbase_example_kernel.bin";
36const TEST_DISK_IMAGE_PATH: &str = "test_disk.img";
37const EMPTY_DISK_IMAGE_PATH: &str = "empty_disk.img";
38const GOLDEN_DEVICE_TREE: &str = "./goldens/dt_dump_golden.dts";
39const GOLDEN_DEVICE_TREE_PROTECTED: &str = "./goldens/dt_dump_protected_golden.dts";
40
41/// Runs an unprotected VM and validates it against a golden device tree.
42#[test]
43fn test_device_tree_compat() -> Result<(), Error> {
44 run_test(false, GOLDEN_DEVICE_TREE)
45}
46
47/// Runs a protected VM and validates it against a golden device tree.
48#[test]
49fn test_device_tree_protected_compat() -> Result<(), Error> {
50 run_test(true, GOLDEN_DEVICE_TREE_PROTECTED)
51}
52
53fn run_test(protected: bool, golden_dt: &str) -> Result<(), Error> {
54 let kernel = Some(open_payload(VMBASE_EXAMPLE_KERNEL_PATH)?);
55 android_logger::init_once(
56 android_logger::Config::default()
57 .with_tag("backcompat")
58 .with_max_level(log::LevelFilter::Debug),
59 );
60
61 // We need to start the thread pool for Binder to work properly, especially link_to_death.
62 ProcessState::start_thread_pool();
63
64 let virtmgr =
65 vmclient::VirtualizationService::new().context("Failed to spawn VirtualizationService")?;
66 let service = virtmgr.connect().context("Failed to connect to VirtualizationService")?;
67
68 // Make file for test disk image.
69 let mut test_image = File::options()
70 .create(true)
71 .read(true)
72 .write(true)
73 .truncate(true)
74 .open(TEST_DISK_IMAGE_PATH)
75 .with_context(|| format!("Failed to open test disk image {}", TEST_DISK_IMAGE_PATH))?;
76 // Write 4 sectors worth of 4-byte numbers counting up.
77 for i in 0u32..512 {
78 test_image.write_all(&i.to_le_bytes())?;
79 }
80 let test_image = ParcelFileDescriptor::new(test_image);
81 let disk_image = DiskImage { image: Some(test_image), writable: false, partitions: vec![] };
82
83 // Make file for empty test disk image.
84 let empty_image = File::options()
85 .create(true)
86 .read(true)
87 .write(true)
88 .truncate(true)
89 .open(EMPTY_DISK_IMAGE_PATH)
90 .with_context(|| format!("Failed to open empty disk image {}", EMPTY_DISK_IMAGE_PATH))?;
91 let empty_image = ParcelFileDescriptor::new(empty_image);
92 let empty_disk_image =
93 DiskImage { image: Some(empty_image), writable: false, partitions: vec![] };
94
95 let config = VirtualMachineConfig::RawConfig(VirtualMachineRawConfig {
96 name: String::from("VmBaseTest"),
97 kernel,
98 disks: vec![disk_image, empty_disk_image],
99 protectedVm: protected,
100 memoryMib: 300,
101 cpuTopology: CpuTopology::ONE_CPU,
102 platformVersion: "~1.0".to_string(),
103 ..Default::default()
104 });
105
106 let dump_dt = File::options()
107 .create(true)
108 .read(true)
109 .write(true)
110 .truncate(true)
111 .open("dump_dt.dtb")
112 .with_context(|| "Failed to open device tree dump file dump_dt.dtb")?;
113 let vm = VmInstance::create(
114 service.as_ref(),
115 &config,
116 None,
117 /* consoleIn */ None,
118 None,
119 Some(dump_dt),
Elie Kheirallahea5dd522024-11-26 22:16:01 +0000120 )
121 .context("Failed to create VM")?;
Jaewan Kim6dcf3082025-01-10 16:30:27 +0900122 vm.start(None).context("Failed to start VM")?;
Elie Kheirallahea5dd522024-11-26 22:16:01 +0000123 info!("Started example VM.");
124
125 // Wait for VM to finish
126 let _ = vm.wait_for_death();
127
128 if !Command::new("./dtc_static")
129 .arg("-I")
130 .arg("dts")
131 .arg("-O")
132 .arg("dtb")
133 .arg("-qqq")
134 .arg("-f")
135 .arg("-s")
136 .arg("-o")
137 .arg("dump_dt_golden.dtb")
138 .arg(golden_dt)
139 .output()?
140 .status
141 .success()
142 {
143 return Err(anyhow!("failed to execute dtc"));
144 }
145 let dtcompare_res = Command::new("./dtcompare")
146 .arg("--dt1")
147 .arg("dump_dt_golden.dtb")
148 .arg("--dt2")
149 .arg("dump_dt.dtb")
150 .arg("--ignore-path-value")
151 .arg("/chosen/kaslr-seed")
152 .arg("--ignore-path-value")
153 .arg("/chosen/rng-seed")
Elie Kheirallahb8b921b2025-01-21 17:15:54 +0000154 // TODO: b/391420337 Investigate if bootargs may mutate VM
155 .arg("--ignore-path-value")
156 .arg("/chosen/bootargs")
157 .arg("--ignore-path-value")
158 .arg("/config/kernel-size")
Elie Kheirallahea5dd522024-11-26 22:16:01 +0000159 .arg("--ignore-path-value")
160 .arg("/avf/untrusted/instance-id")
161 .arg("--ignore-path-value")
162 .arg("/chosen/linuxinitrd-start")
163 .arg("--ignore-path-value")
164 .arg("/chosen/linuxinitrd-end")
165 .arg("--ignore-path-value")
166 .arg("/avf/secretkeeper_public_key")
167 .arg("--ignore-path")
168 .arg("/avf/name")
169 .output()
170 .context("failed to execute dtcompare")?;
171 if !dtcompare_res.status.success() {
172 if !Command::new("./dtc_static")
173 .arg("-I")
174 .arg("dtb")
175 .arg("-O")
176 .arg("dts")
177 .arg("-qqq")
178 .arg("-f")
179 .arg("-s")
180 .arg("-o")
181 .arg("dump_dt_failed.dts")
182 .arg("dump_dt.dtb")
183 .output()?
184 .status
185 .success()
186 {
187 return Err(anyhow!("failed to execute dtc"));
188 }
189 let dt2 = read_to_string("dump_dt_failed.dts")?;
190 error!(
191 "Device tree 2 does not match golden DT.\n
192 Device Tree 2: {}",
193 dt2
194 );
195 return Err(anyhow!(
196 "stdout: {:?}\n stderr: {:?}",
197 dtcompare_res.stdout,
198 dtcompare_res.stderr
199 ));
200 }
201
202 Ok(())
203}
204
205fn open_payload(path: &str) -> Result<ParcelFileDescriptor, Error> {
206 let file = File::open(path).with_context(|| format!("Failed to open VM image {path}"))?;
207 Ok(ParcelFileDescriptor::new(file))
208}