1use super::PetriVmConfigOpenVmm;
7use super::PetriVmOpenVmm;
8use super::PetriVmResourcesOpenVmm;
9use crate::BootDeviceType;
10use crate::Firmware;
11use crate::OpenvmmLogConfig;
12use crate::PetriLogFile;
13use crate::PetriVmRuntimeConfig;
14use crate::worker::Worker;
15use anyhow::Context;
16use disk_backend_resources::FileDiskHandle;
17use guid::Guid;
18use mesh_process::Mesh;
19use mesh_process::ProcessConfig;
20use mesh_worker::WorkerHost;
21use openvmm_defs::config::DeviceVtl;
22use pal_async::pipe::PolledPipe;
23use pal_async::task::Spawn;
24use petri_artifacts_common::tags::MachineArch;
25use petri_artifacts_common::tags::OsFlavor;
26use scsidisk_resources::SimpleScsiDiskHandle;
27use std::collections::BTreeMap;
28use std::ffi::OsString;
29use std::io::Write;
30use std::sync::Arc;
31use storvsp_resources::ScsiControllerHandle;
32use storvsp_resources::ScsiDeviceAndPath;
33use storvsp_resources::ScsiPath;
34use vm_resource::IntoResource;
35
36impl PetriVmConfigOpenVmm {
37 async fn run_core(self) -> anyhow::Result<(PetriVmOpenVmm, PetriVmRuntimeConfig)> {
38 let Self {
39 firmware,
40 arch,
41 host_log_levels,
42 mut config,
43 boot_device_type,
44
45 mesh,
46
47 mut resources,
48
49 openvmm_log_file,
50
51 petri_vtl0_scsi,
52 ged,
53 framebuffer_view,
54
55 mut vtl2_settings,
56 } = self;
57
58 tracing::debug!(?firmware, ?arch, "Petri VM firmware configuration");
59
60 let has_pcie = !config.pcie_root_complexes.is_empty();
61
62 let supports_save_restore = !firmware.is_openhcl()
68 && !matches!(firmware, Firmware::Pcat { .. })
69 && !matches!(arch, MachineArch::Aarch64)
70 && !matches!(boot_device_type, BootDeviceType::Nvme)
71 && !has_pcie;
72
73 if firmware.is_openhcl() {
74 const UH_CIDATA_SCSI_INSTANCE: Guid =
76 guid::guid!("766e96f8-2ceb-437e-afe3-a93169e48a7c");
77
78 if let Some(openhcl_agent_disk) = resources
79 .openhcl_agent_image
80 .as_ref()
81 .unwrap()
82 .build()
83 .context("failed to build agent image")?
84 {
85 config.vmbus_devices.push((
86 DeviceVtl::Vtl2,
87 ScsiControllerHandle {
88 instance_id: UH_CIDATA_SCSI_INSTANCE,
89 max_sub_channel_count: 1,
90 io_queue_depth: None,
91 devices: vec![ScsiDeviceAndPath {
92 path: ScsiPath {
93 path: 0,
94 target: 0,
95 lun: crate::vm::PETRI_VTL0_SCSI_BOOT_LUN,
96 },
97 device: SimpleScsiDiskHandle {
98 read_only: true,
99 parameters: Default::default(),
100 disk: FileDiskHandle(openhcl_agent_disk.into_file())
101 .into_resource(),
102 }
103 .into_resource(),
104 }],
105 requests: None,
106 poll_mode_queue_depth: None,
107 }
108 .into_resource(),
109 ));
110 }
111 }
112
113 if !petri_vtl0_scsi.devices.is_empty() {
115 config
116 .vmbus_devices
117 .push((DeviceVtl::Vtl0, petri_vtl0_scsi.into_resource()));
118 }
119
120 if let Some(f) = firmware
122 .into_openhcl_config()
123 .and_then(|c| c.modify_vtl2_settings)
124 {
125 f.0(vtl2_settings.as_mut().unwrap())
126 };
127
128 if let Some(mut ged) = ged {
130 ged.vtl2_settings = Some(prost::Message::encode_to_vec(
131 vtl2_settings.as_ref().unwrap(),
132 ));
133 config
134 .vmbus_devices
135 .push((DeviceVtl::Vtl2, ged.into_resource()));
136 }
137
138 tracing::debug!(?config, "OpenVMM config");
139
140 let log_env = match host_log_levels {
141 None | Some(OpenvmmLogConfig::TestDefault) => BTreeMap::<OsString, OsString>::from([
142 ("OPENVMM_LOG".into(), "debug".into()),
143 ("OPENVMM_SHOW_SPANS".into(), "true".into()),
144 ]),
145 Some(OpenvmmLogConfig::BuiltInDefault) => BTreeMap::new(),
146 Some(OpenvmmLogConfig::Custom(levels)) => levels
147 .iter()
148 .map(|(k, v)| (OsString::from(k), OsString::from(v)))
149 .collect::<BTreeMap<OsString, OsString>>(),
150 };
151
152 let host = Self::openvmm_host(&mut resources, &mesh, openvmm_log_file, log_env)
153 .await
154 .context("failed to create host process")?;
155 let (worker, halt_notif) = Worker::launch(&host, config)
156 .await
157 .context("failed to launch vm worker")?;
158
159 let worker = Arc::new(worker);
160
161 let mut vm = PetriVmOpenVmm::new(
162 super::runtime::PetriVmInner {
163 resources,
164 mesh,
165 worker,
166 framebuffer_view,
167 },
168 halt_notif,
169 );
170
171 tracing::info!("Resuming VM");
172 vm.resume().await?;
173
174 if supports_save_restore {
176 tracing::info!("Testing save/restore");
177 vm.verify_save_restore().await?;
178 }
179
180 tracing::info!("VM ready");
181 Ok((vm, PetriVmRuntimeConfig { vtl2_settings }))
182 }
183
184 pub async fn run(mut self) -> anyhow::Result<(PetriVmOpenVmm, PetriVmRuntimeConfig)> {
187 let launch_linux_direct_pipette = if let Some(agent_image) = &self.resources.agent_image {
188 if let Some(agent_disk) = agent_image.build().context("failed to build agent image")? {
190 self.petri_vtl0_scsi.devices.push(ScsiDeviceAndPath {
191 path: ScsiPath {
192 path: 0,
193 target: 0,
194 lun: crate::vm::PETRI_VTL0_SCSI_PIPETTE_LUN,
195 },
196 device: SimpleScsiDiskHandle {
197 read_only: true,
198 parameters: Default::default(),
199 disk: FileDiskHandle(agent_disk.into_file()).into_resource(),
200 }
201 .into_resource(),
202 });
203 }
204
205 if matches!(self.firmware.os_flavor(), OsFlavor::Windows)
206 && self.firmware.isolation().is_none()
207 {
208 let mut imc_hive_file =
211 tempfile::tempfile().context("failed to create temp file")?;
212 imc_hive_file
213 .write_all(include_bytes!("../../../guest-bootstrap/imc.hiv"))
214 .context("failed to write imc hive")?;
215
216 self.config.vmbus_devices.push((
218 DeviceVtl::Vtl0,
219 vmbfs_resources::VmbfsImcDeviceHandle {
220 file: imc_hive_file,
221 }
222 .into_resource(),
223 ));
224 }
225
226 self.firmware.is_linux_direct() && agent_image.contains_pipette()
227 } else {
228 false
229 };
230
231 let (mut vm, config) = self.run_core().await?;
233
234 if launch_linux_direct_pipette {
235 vm.launch_linux_direct_pipette().await?;
236 }
237
238 Ok((vm, config))
239 }
240
241 async fn openvmm_host(
242 resources: &mut PetriVmResourcesOpenVmm,
243 mesh: &Mesh,
244 log_file: PetriLogFile,
245 vmm_env: BTreeMap<OsString, OsString>,
246 ) -> anyhow::Result<WorkerHost> {
247 let (stderr_read, stderr_write) = pal::pipe_pair()?;
250 let task = resources.driver.spawn(
251 "serial log",
252 crate::log_task(
253 log_file,
254 PolledPipe::new(&resources.driver, stderr_read)
255 .context("failed to create polled pipe")?,
256 "openvmm stderr",
257 ),
258 );
259 resources.log_stream_tasks.push(task);
260
261 let (host, runner) = mesh_worker::worker_host();
262 mesh.launch_host(
263 ProcessConfig::new("vmm")
264 .process_name(&resources.openvmm_path)
265 .stderr(Some(stderr_write))
266 .env(vmm_env.into_iter()),
267 openvmm_defs::entrypoint::MeshHostParams { runner },
268 )
269 .await?;
270 Ok(host)
271 }
272}