blob: bf0afa6143cc7788f69310222fb9dd7b615ff5a7 [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),
Chaitanya Cheemala (xWF)3da8a162025-01-21 08:57:09 -0800120 None,
Elie Kheirallahea5dd522024-11-26 22:16:01 +0000121 )
122 .context("Failed to create VM")?;
Chaitanya Cheemala (xWF)3da8a162025-01-21 08:57:09 -0800123 vm.start().context("Failed to start VM")?;
Elie Kheirallahea5dd522024-11-26 22:16:01 +0000124 info!("Started example VM.");
125
126 // Wait for VM to finish
127 let _ = vm.wait_for_death();
128
129 if !Command::new("./dtc_static")
130 .arg("-I")
131 .arg("dts")
132 .arg("-O")
133 .arg("dtb")
134 .arg("-qqq")
135 .arg("-f")
136 .arg("-s")
137 .arg("-o")
138 .arg("dump_dt_golden.dtb")
139 .arg(golden_dt)
140 .output()?
141 .status
142 .success()
143 {
144 return Err(anyhow!("failed to execute dtc"));
145 }
146 let dtcompare_res = Command::new("./dtcompare")
147 .arg("--dt1")
148 .arg("dump_dt_golden.dtb")
149 .arg("--dt2")
150 .arg("dump_dt.dtb")
151 .arg("--ignore-path-value")
152 .arg("/chosen/kaslr-seed")
153 .arg("--ignore-path-value")
154 .arg("/chosen/rng-seed")
Elie Kheirallahb8b921b2025-01-21 17:15:54 +0000155 // TODO: b/391420337 Investigate if bootargs may mutate VM
156 .arg("--ignore-path-value")
157 .arg("/chosen/bootargs")
158 .arg("--ignore-path-value")
159 .arg("/config/kernel-size")
Elie Kheirallahea5dd522024-11-26 22:16:01 +0000160 .arg("--ignore-path-value")
161 .arg("/avf/untrusted/instance-id")
162 .arg("--ignore-path-value")
163 .arg("/chosen/linuxinitrd-start")
164 .arg("--ignore-path-value")
165 .arg("/chosen/linuxinitrd-end")
166 .arg("--ignore-path-value")
167 .arg("/avf/secretkeeper_public_key")
168 .arg("--ignore-path")
169 .arg("/avf/name")
170 .output()
171 .context("failed to execute dtcompare")?;
172 if !dtcompare_res.status.success() {
173 if !Command::new("./dtc_static")
174 .arg("-I")
175 .arg("dtb")
176 .arg("-O")
177 .arg("dts")
178 .arg("-qqq")
179 .arg("-f")
180 .arg("-s")
181 .arg("-o")
182 .arg("dump_dt_failed.dts")
183 .arg("dump_dt.dtb")
184 .output()?
185 .status
186 .success()
187 {
188 return Err(anyhow!("failed to execute dtc"));
189 }
190 let dt2 = read_to_string("dump_dt_failed.dts")?;
191 error!(
192 "Device tree 2 does not match golden DT.\n
193 Device Tree 2: {}",
194 dt2
195 );
196 return Err(anyhow!(
197 "stdout: {:?}\n stderr: {:?}",
198 dtcompare_res.stdout,
199 dtcompare_res.stderr
200 ));
201 }
202
203 Ok(())
204}
205
206fn open_payload(path: &str) -> Result<ParcelFileDescriptor, Error> {
207 let file = File::open(path).with_context(|| format!("Failed to open VM image {path}"))?;
208 Ok(ParcelFileDescriptor::new(file))
209}