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
44 ged,
45 framebuffer_view,
46
47 pending_iommu,
48 } = self;
49
50 for (name, iommu_config) in &pending_iommu {
52 let rc = config
53 .pcie_root_complexes
54 .iter_mut()
55 .find(|rc| rc.name == *name)
56 .with_context(|| format!("IOMMU configured for unknown root complex '{name}'"))?;
57 rc.iommu = Some(iommu_config.clone());
58 }
59
60 let has_unsupported_pcie_save_restore_device = config
67 .pcie_devices
68 .iter()
69 .any(|device| matches!(device.resource.id(), "nvme" | "gdma"));
70 let supports_save_restore = !resources.properties.is_openhcl
71 && !resources.properties.is_pcat
72 && !matches!(arch, MachineArch::Aarch64)
73 && !resources.properties.using_vpci
74 && !has_unsupported_pcie_save_restore_device
75 && !resources.properties.use_virtio_vsock;
76
77 if let Some(mut ged) = ged {
79 ged.vtl2_settings = Some(prost::Message::encode_to_vec(
80 runtime_config.vtl2_settings.as_ref().unwrap(),
81 ));
82 config
83 .vmbus_devices
84 .push((DeviceVtl::Vtl2, ged.into_resource()));
85 }
86
87 tracing::debug!(?config, "OpenVMM config");
88
89 let log_env = match host_log_levels {
90 None | Some(OpenvmmLogConfig::TestDefault) => BTreeMap::<OsString, OsString>::from([
91 ("OPENVMM_LOG".into(), "debug".into()),
92 ("OPENVMM_SHOW_SPANS".into(), "true".into()),
93 ]),
94 Some(OpenvmmLogConfig::BuiltInDefault) => BTreeMap::new(),
95 Some(OpenvmmLogConfig::Custom(levels)) => levels
96 .iter()
97 .map(|(k, v)| (OsString::from(k), OsString::from(v)))
98 .collect::<BTreeMap<OsString, OsString>>(),
99 };
100
101 let (host, pid) = Self::openvmm_host(&mut resources, &mesh, openvmm_log_file, log_env)
102 .await
103 .context("failed to create host process")?;
104 let shared_memory = memory_backing_file
107 .as_ref()
108 .map(|mem_path| {
109 let total_mem_size: u64 = config
110 .numa
111 .nodes
112 .iter()
113 .filter_map(|n| n.mem.as_ref())
114 .map(|m| m.mem_size)
115 .sum();
116 openvmm_helpers::shared_memory::open_memory_backing_file(mem_path, total_mem_size)
117 })
118 .transpose()?;
119
120 let (worker, halt_notif) = Worker::launch(&host, config, shared_memory)
121 .await
122 .context("failed to launch vm worker")?;
123
124 let worker = Arc::new(worker);
125
126 let is_minimal = resources.properties.minimal_mode;
127
128 let tcp_pipette_port = match resources.tcp_pipette_port.take() {
133 Some(recv) => Some(
134 recv.await
135 .context("failed to receive TCP pipette port from consomme")?,
136 ),
137 None => None,
138 };
139
140 let mut vm = PetriVmOpenVmm::new(
141 super::runtime::PetriVmInner {
142 resources,
143 mesh,
144 worker,
145 framebuffer_view,
146 cidata_mounted: false,
147 tcp_pipette_port,
148 pid,
149 },
150 halt_notif,
151 );
152
153 tracing::info!("Resuming VM");
154 vm.resume().await?;
155
156 if supports_save_restore && !is_minimal {
158 tracing::info!("Testing save/restore");
159 vm.verify_save_restore().await?;
160 }
161
162 tracing::info!("VM ready");
163 Ok((vm, runtime_config))
164 }
165
166 pub async fn run(mut self) -> anyhow::Result<(PetriVmOpenVmm, PetriVmRuntimeConfig)> {
169 if self.resources.properties.using_vtl0_pipette
173 && matches!(self.resources.properties.os_flavor, OsFlavor::Windows)
174 && !self.resources.properties.is_isolated
175 && !self.resources.properties.no_vmbus
176 {
177 let mut imc_hive_file = tempfile::tempfile().context("failed to create temp file")?;
178 imc_hive_file
179 .write_all(include_bytes!("../../../guest-bootstrap/imc.hiv"))
180 .context("failed to write imc hive")?;
181
182 self.config.vmbus_devices.push((
183 DeviceVtl::Vtl0,
184 vmbfs_resources::VmbfsImcDeviceHandle {
185 file: imc_hive_file,
186 }
187 .into_resource(),
188 ));
189 }
190
191 let launch_via_serial = self.resources.linux_direct_serial_agent.is_some()
195 && self.resources.properties.using_vtl0_pipette;
196
197 let (mut vm, config) = self.run_core().await?;
199
200 if launch_via_serial {
201 vm.launch_linux_direct_pipette().await?;
202 }
203
204 Ok((vm, config))
205 }
206
207 async fn openvmm_host(
208 resources: &mut PetriVmResourcesOpenVmm,
209 mesh: &Mesh,
210 log_file: PetriLogFile,
211 vmm_env: BTreeMap<OsString, OsString>,
212 ) -> anyhow::Result<(WorkerHost, i32)> {
213 let (stderr_read, stderr_write) = pal::pipe_pair()?;
216 let task = resources.driver.spawn(
217 "serial log",
218 crate::log_task(
219 log_file,
220 PolledPipe::new(&resources.driver, stderr_read)
221 .context("failed to create polled pipe")?,
222 "openvmm stderr",
223 ),
224 );
225 resources.log_stream_tasks.push(task);
226
227 let (host, runner) = mesh_worker::worker_host();
228 let pid = mesh
229 .launch_host(
230 ProcessConfig::new("vmm")
231 .process_name(&resources.openvmm_path)
232 .stderr(Some(stderr_write))
233 .env(vmm_env),
234 openvmm_defs::entrypoint::MeshHostParams { runner },
235 )
236 .await?;
237 Ok((host, pid))
238 }
239}