petri/vm/openvmm/
start.rs1use super::PetriVmConfigOpenVmm;
7use super::PetriVmOpenVmm;
8use super::PetriVmResourcesOpenVmm;
9use crate::OpenvmmLogConfig;
10use crate::PetriLogFile;
11use crate::PetriVmRuntimeConfig;
12use crate::worker::Worker;
13use anyhow::Context;
14use mesh_process::Mesh;
15use mesh_process::ProcessConfig;
16use mesh_worker::WorkerHost;
17use openvmm_defs::config::DeviceVtl;
18use pal_async::pipe::PolledPipe;
19use pal_async::task::Spawn;
20use petri_artifacts_common::tags::MachineArch;
21use petri_artifacts_common::tags::OsFlavor;
22use std::collections::BTreeMap;
23use std::ffi::OsString;
24use std::io::Write;
25use std::sync::Arc;
26use vm_resource::IntoResource;
27
28impl PetriVmConfigOpenVmm {
29 async fn run_core(self) -> anyhow::Result<(PetriVmOpenVmm, PetriVmRuntimeConfig)> {
30 let Self {
31 runtime_config,
32 arch,
33 host_log_levels,
34 mut config,
35
36 mesh,
37
38 mut resources,
39
40 openvmm_log_file,
41
42 memory_backing_file,
43 requested_private_memory: _,
44
45 ged,
46 framebuffer_view,
47
48 pending_iommu,
49 } = self;
50
51 for (name, iommu_config) in &pending_iommu {
53 let rc = config
54 .pcie_root_complexes
55 .iter_mut()
56 .find(|rc| rc.name == *name)
57 .with_context(|| format!("IOMMU configured for unknown root complex '{name}'"))?;
58 rc.iommu = Some(iommu_config.clone());
59 }
60
61 let has_unsupported_pcie_save_restore_device = config
68 .pcie_devices
69 .iter()
70 .any(|device| matches!(device.resource.id(), "nvme" | "gdma"));
71 let supports_save_restore = !resources.properties.is_openhcl
72 && !resources.properties.is_pcat
73 && !matches!(arch, MachineArch::Aarch64)
74 && !resources.properties.using_vpci
75 && !has_unsupported_pcie_save_restore_device
76 && !resources.properties.use_virtio_vsock;
77
78 if let Some(mut ged) = ged {
80 ged.vtl2_settings = Some(prost::Message::encode_to_vec(
81 runtime_config.vtl2_settings.as_ref().unwrap(),
82 ));
83 config
84 .vmbus_devices
85 .push((DeviceVtl::Vtl2, ged.into_resource()));
86 }
87
88 tracing::debug!(?config, "OpenVMM config");
89
90 let log_env = match host_log_levels {
91 None | Some(OpenvmmLogConfig::TestDefault) => BTreeMap::<OsString, OsString>::from([
92 ("OPENVMM_LOG".into(), "debug,hyper_util=info".into()),
95 ("OPENVMM_SHOW_SPANS".into(), "true".into()),
96 ]),
97 Some(OpenvmmLogConfig::BuiltInDefault) => BTreeMap::new(),
98 Some(OpenvmmLogConfig::Custom(levels)) => levels
99 .iter()
100 .map(|(k, v)| (OsString::from(k), OsString::from(v)))
101 .collect::<BTreeMap<OsString, OsString>>(),
102 };
103
104 let (host, pid) = Self::openvmm_host(&mut resources, &mesh, openvmm_log_file, log_env)
105 .await
106 .context("failed to create host process")?;
107 let shared_memory = memory_backing_file
110 .as_ref()
111 .map(|mem_path| {
112 let total_mem_size: u64 = config
113 .numa
114 .nodes
115 .iter()
116 .filter_map(|n| n.mem.as_ref())
117 .map(|m| m.mem_size)
118 .sum();
119 openvmm_helpers::shared_memory::open_memory_backing_file(mem_path, total_mem_size)
120 })
121 .transpose()?;
122
123 for (node, mem) in config
128 .numa
129 .nodes
130 .iter()
131 .enumerate()
132 .filter_map(|(i, n)| n.mem.as_ref().map(|m| (i, m)))
133 {
134 tracing::info!(
135 node,
136 mem_size = mem.mem_size,
137 backing_mode = if mem.private_memory {
138 "private"
139 } else {
140 "shared"
141 },
142 transparent_hugepages = mem.transparent_hugepages,
143 hugepages = mem.hugepages,
144 "guest RAM backing"
145 );
146 }
147
148 let (worker, halt_notif) = Worker::launch(&host, config, shared_memory)
149 .await
150 .context("failed to launch vm worker")?;
151
152 let worker = Arc::new(worker);
153
154 let is_minimal = resources.properties.minimal_mode;
155
156 let tcp_pipette_port = match resources.tcp_pipette_port.take() {
161 Some(recv) => Some(
162 recv.await
163 .context("failed to receive TCP pipette port from consomme")?,
164 ),
165 None => None,
166 };
167
168 let mut vm = PetriVmOpenVmm::new(
169 super::runtime::PetriVmInner {
170 resources,
171 mesh,
172 worker,
173 framebuffer_view,
174 cidata_mounted: false,
175 tcp_pipette_port,
176 pid,
177 },
178 halt_notif,
179 );
180
181 tracing::info!("Resuming VM");
182 vm.resume().await?;
183
184 if supports_save_restore && !is_minimal {
186 tracing::info!("Testing save/restore");
187 vm.verify_save_restore().await?;
188 }
189
190 tracing::info!("VM ready");
191 Ok((vm, runtime_config))
192 }
193
194 pub async fn run(mut self) -> anyhow::Result<(PetriVmOpenVmm, PetriVmRuntimeConfig)> {
197 if self.resources.properties.using_vtl0_pipette
201 && matches!(self.resources.properties.os_flavor, OsFlavor::Windows)
202 && !self.resources.properties.is_isolated
203 && !self.resources.properties.no_vmbus
204 {
205 let mut imc_hive_file = tempfile::tempfile().context("failed to create temp file")?;
206 imc_hive_file
207 .write_all(include_bytes!("../../../guest-bootstrap/imc.hiv"))
208 .context("failed to write imc hive")?;
209
210 self.config.vmbus_devices.push((
211 DeviceVtl::Vtl0,
212 vmbfs_resources::VmbfsImcDeviceHandle {
213 file: imc_hive_file,
214 }
215 .into_resource(),
216 ));
217 }
218
219 let launch_via_serial = self.resources.linux_direct_serial_agent.is_some()
223 && self.resources.properties.using_vtl0_pipette;
224
225 let (mut vm, config) = self.run_core().await?;
227
228 if launch_via_serial {
229 vm.launch_linux_direct_pipette().await?;
230 }
231
232 Ok((vm, config))
233 }
234
235 async fn openvmm_host(
236 resources: &mut PetriVmResourcesOpenVmm,
237 mesh: &Mesh,
238 log_file: PetriLogFile,
239 vmm_env: BTreeMap<OsString, OsString>,
240 ) -> anyhow::Result<(WorkerHost, i32)> {
241 let (stderr_read, stderr_write) = pal::pipe_pair()?;
244 let task = resources.driver.spawn(
245 "serial log",
246 crate::log_task(
247 log_file,
248 PolledPipe::new(&resources.driver, stderr_read)
249 .context("failed to create polled pipe")?,
250 "openvmm stderr",
251 ),
252 );
253 resources.log_stream_tasks.push(task);
254
255 let (host, runner) = mesh_worker::worker_host();
256 let pid = mesh
257 .launch_host(
258 ProcessConfig::new("vmm")
259 .process_name(&resources.openvmm_path)
260 .stderr(Some(stderr_write))
261 .env(vmm_env),
262 openvmm_defs::entrypoint::MeshHostParams { runner },
263 )
264 .await?;
265 Ok((host, pid))
266 }
267}