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