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 } = self;
47
48 let has_unsupported_pcie_save_restore_device = config
55 .pcie_devices
56 .iter()
57 .any(|device| matches!(device.resource.id(), "nvme" | "gdma"));
58 let supports_save_restore = !resources.properties.is_openhcl
59 && !resources.properties.is_pcat
60 && !matches!(arch, MachineArch::Aarch64)
61 && !resources.properties.using_vpci
62 && !has_unsupported_pcie_save_restore_device
63 && !resources.properties.use_virtio_vsock;
64
65 if let Some(mut ged) = ged {
67 ged.vtl2_settings = Some(prost::Message::encode_to_vec(
68 runtime_config.vtl2_settings.as_ref().unwrap(),
69 ));
70 config
71 .vmbus_devices
72 .push((DeviceVtl::Vtl2, ged.into_resource()));
73 }
74
75 tracing::debug!(?config, "OpenVMM config");
76
77 let log_env = match host_log_levels {
78 None | Some(OpenvmmLogConfig::TestDefault) => BTreeMap::<OsString, OsString>::from([
79 ("OPENVMM_LOG".into(), "debug".into()),
80 ("OPENVMM_SHOW_SPANS".into(), "true".into()),
81 ]),
82 Some(OpenvmmLogConfig::BuiltInDefault) => BTreeMap::new(),
83 Some(OpenvmmLogConfig::Custom(levels)) => levels
84 .iter()
85 .map(|(k, v)| (OsString::from(k), OsString::from(v)))
86 .collect::<BTreeMap<OsString, OsString>>(),
87 };
88
89 let (host, pid) = Self::openvmm_host(&mut resources, &mesh, openvmm_log_file, log_env)
90 .await
91 .context("failed to create host process")?;
92 let shared_memory = memory_backing_file
95 .as_ref()
96 .map(|mem_path| {
97 openvmm_helpers::shared_memory::open_memory_backing_file(
98 mem_path,
99 config.memory.mem_size,
100 )
101 })
102 .transpose()?;
103
104 let (worker, halt_notif) = Worker::launch(&host, config, shared_memory)
105 .await
106 .context("failed to launch vm worker")?;
107
108 let worker = Arc::new(worker);
109
110 let is_minimal = resources.properties.minimal_mode;
111
112 let mut vm = PetriVmOpenVmm::new(
113 super::runtime::PetriVmInner {
114 resources,
115 mesh,
116 worker,
117 framebuffer_view,
118 cidata_mounted: false,
119 pid,
120 },
121 halt_notif,
122 );
123
124 tracing::info!("Resuming VM");
125 vm.resume().await?;
126
127 if supports_save_restore && !is_minimal {
129 tracing::info!("Testing save/restore");
130 vm.verify_save_restore().await?;
131 }
132
133 tracing::info!("VM ready");
134 Ok((vm, runtime_config))
135 }
136
137 pub async fn run(mut self) -> anyhow::Result<(PetriVmOpenVmm, PetriVmRuntimeConfig)> {
140 if self.resources.properties.using_vtl0_pipette
142 && matches!(self.resources.properties.os_flavor, OsFlavor::Windows)
143 && !self.resources.properties.is_isolated
144 {
145 let mut imc_hive_file = tempfile::tempfile().context("failed to create temp file")?;
146 imc_hive_file
147 .write_all(include_bytes!("../../../guest-bootstrap/imc.hiv"))
148 .context("failed to write imc hive")?;
149
150 self.config.vmbus_devices.push((
151 DeviceVtl::Vtl0,
152 vmbfs_resources::VmbfsImcDeviceHandle {
153 file: imc_hive_file,
154 }
155 .into_resource(),
156 ));
157 }
158
159 let launch_via_serial = self.resources.linux_direct_serial_agent.is_some()
163 && self.resources.properties.using_vtl0_pipette;
164
165 let (mut vm, config) = self.run_core().await?;
167
168 if launch_via_serial {
169 vm.launch_linux_direct_pipette().await?;
170 }
171
172 Ok((vm, config))
173 }
174
175 async fn openvmm_host(
176 resources: &mut PetriVmResourcesOpenVmm,
177 mesh: &Mesh,
178 log_file: PetriLogFile,
179 vmm_env: BTreeMap<OsString, OsString>,
180 ) -> anyhow::Result<(WorkerHost, i32)> {
181 let (stderr_read, stderr_write) = pal::pipe_pair()?;
184 let task = resources.driver.spawn(
185 "serial log",
186 crate::log_task(
187 log_file,
188 PolledPipe::new(&resources.driver, stderr_read)
189 .context("failed to create polled pipe")?,
190 "openvmm stderr",
191 ),
192 );
193 resources.log_stream_tasks.push(task);
194
195 let (host, runner) = mesh_worker::worker_host();
196 let pid = mesh
197 .launch_host(
198 ProcessConfig::new("vmm")
199 .process_name(&resources.openvmm_path)
200 .stderr(Some(stderr_write))
201 .env(vmm_env),
202 openvmm_defs::entrypoint::MeshHostParams { runner },
203 )
204 .await?;
205 Ok((host, pid))
206 }
207}