1mod construct;
12mod modify;
13mod runtime;
14mod start;
15
16pub use runtime::OpenVmmFramebufferAccess;
17pub use runtime::OpenVmmInspector;
18pub use runtime::PetriVmOpenVmm;
19
20use crate::BootDeviceType;
21use crate::Firmware;
22use crate::OpenHclServicingFlags;
23use crate::OpenvmmLogConfig;
24use crate::PetriDiskType;
25use crate::PetriLogFile;
26use crate::PetriVmConfig;
27use crate::PetriVmResources;
28use crate::PetriVmRuntimeConfig;
29use crate::PetriVmgsDisk;
30use crate::PetriVmgsResource;
31use crate::PetriVmmBackend;
32use crate::VmmQuirks;
33use crate::disk_image::AgentImage;
34use crate::linux_direct_serial_agent::LinuxDirectSerialAgent;
35use anyhow::Context;
36use async_trait::async_trait;
37use disk_backend_resources::LayeredDiskHandle;
38use disk_backend_resources::layer::DiskLayerHandle;
39use disk_backend_resources::layer::RamDiskLayerHandle;
40use get_resources::ged::FirmwareEvent;
41use guid::Guid;
42use hyperv_ic_resources::shutdown::ShutdownRpc;
43use mesh::Receiver;
44use mesh::Sender;
45use net_backend_resources::mac_address::MacAddress;
46use openvmm_defs::config::Config;
47use openvmm_helpers::disk::open_disk_type;
48use pal_async::DefaultDriver;
49use pal_async::socket::PolledSocket;
50use pal_async::task::Task;
51use petri_artifacts_common::tags::GuestQuirksInner;
52use petri_artifacts_common::tags::MachineArch;
53use petri_artifacts_common::tags::OsFlavor;
54use petri_artifacts_core::ArtifactResolver;
55use petri_artifacts_core::ResolvedArtifact;
56use std::path::Path;
57use std::path::PathBuf;
58use std::sync::Arc;
59use std::time::Duration;
60use storvsp_resources::ScsiControllerHandle;
61use tempfile::TempPath;
62use unix_socket::UnixListener;
63use vm_resource::IntoResource;
64use vm_resource::Resource;
65use vm_resource::kind::DiskHandleKind;
66use vmgs_resources::VmgsDisk;
67use vmgs_resources::VmgsResource;
68use vtl2_settings_proto::Vtl2Settings;
69
70pub(crate) const SCSI_INSTANCE: Guid = guid::guid!("27b553e8-8b39-411b-a55f-839971a7884f");
72
73pub(crate) const PARAVISOR_BOOT_NVME_INSTANCE: Guid =
76 guid::guid!("92bc8346-718b-449a-8751-edbf3dcd27e4");
77
78pub(crate) const BOOT_NVME_INSTANCE: Guid = guid::guid!("e23a04e2-90f5-4852-bc9d-e7ac691b756c");
80
81const MANA_INSTANCE: Guid = guid::guid!("f9641cf4-d915-4743-a7d8-efa75db7b85a");
83
84pub(crate) const BOOT_NVME_NSID: u32 = 37;
86
87pub(crate) const BOOT_NVME_LUN: u32 = 1;
89
90pub const NIC_MAC_ADDRESS: MacAddress = MacAddress::new([0x00, 0x15, 0x5D, 0x12, 0x12, 0x12]);
92
93pub struct OpenVmmPetriBackend {
95 openvmm_path: ResolvedArtifact,
96}
97
98#[async_trait]
99impl PetriVmmBackend for OpenVmmPetriBackend {
100 type VmmConfig = PetriVmConfigOpenVmm;
101 type VmRuntime = PetriVmOpenVmm;
102
103 fn check_compat(firmware: &Firmware, arch: MachineArch) -> bool {
104 arch == MachineArch::host()
105 && !(firmware.is_openhcl() && (!cfg!(windows) || arch == MachineArch::Aarch64))
106 && !(firmware.is_pcat() && arch == MachineArch::Aarch64)
107 }
108
109 fn quirks(firmware: &Firmware) -> (GuestQuirksInner, VmmQuirks) {
110 (
111 firmware.quirks().openvmm,
112 VmmQuirks {
113 flaky_boot: firmware.is_pcat().then_some(Duration::from_secs(15)),
115 },
116 )
117 }
118
119 fn default_servicing_flags() -> OpenHclServicingFlags {
120 OpenHclServicingFlags {
121 enable_nvme_keepalive: true,
122 enable_mana_keepalive: true,
123 override_version_checks: false,
124 stop_timeout_hint_secs: None,
125 }
126 }
127
128 fn create_guest_dump_disk() -> anyhow::Result<
129 Option<(
130 Arc<TempPath>,
131 Box<dyn FnOnce() -> anyhow::Result<Box<dyn fatfs::ReadWriteSeek>>>,
132 )>,
133 > {
134 Ok(None) }
136
137 fn new(resolver: &ArtifactResolver<'_>) -> Self {
138 OpenVmmPetriBackend {
139 openvmm_path: resolver
140 .require(petri_artifacts_vmm_test::artifacts::OPENVMM_NATIVE)
141 .erase(),
142 }
143 }
144
145 async fn run(
146 self,
147 config: PetriVmConfig,
148 modify_vmm_config: Option<impl FnOnce(PetriVmConfigOpenVmm) -> PetriVmConfigOpenVmm + Send>,
149 resources: &PetriVmResources,
150 ) -> anyhow::Result<(Self::VmRuntime, PetriVmRuntimeConfig)> {
151 let mut config = PetriVmConfigOpenVmm::new(&self.openvmm_path, config, resources).await?;
152
153 if let Some(f) = modify_vmm_config {
154 config = f(config);
155 }
156
157 config.run().await
158 }
159}
160
161pub struct PetriVmConfigOpenVmm {
163 firmware: Firmware,
165 arch: MachineArch,
166 host_log_levels: Option<OpenvmmLogConfig>,
167 config: Config,
168 boot_device_type: BootDeviceType,
169
170 mesh: mesh_process::Mesh,
172
173 resources: PetriVmResourcesOpenVmm,
175
176 openvmm_log_file: PetriLogFile,
178
179 petri_vtl0_scsi: ScsiControllerHandle,
182
183 ged: Option<get_resources::ged::GuestEmulationDeviceHandle>,
184 framebuffer_view: Option<framebuffer::View>,
185
186 vtl2_settings: Option<Vtl2Settings>,
187}
188struct PetriVmResourcesOpenVmm {
190 log_stream_tasks: Vec<Task<anyhow::Result<()>>>,
191 firmware_event_recv: Receiver<FirmwareEvent>,
192 shutdown_ic_send: Sender<ShutdownRpc>,
193 kvp_ic_send: Sender<hyperv_ic_resources::kvp::KvpConnectRpc>,
194 ged_send: Option<Sender<get_resources::ged::GuestEmulationRequest>>,
195 pipette_listener: PolledSocket<UnixListener>,
196 vtl2_pipette_listener: Option<PolledSocket<UnixListener>>,
197 linux_direct_serial_agent: Option<LinuxDirectSerialAgent>,
198
199 driver: DefaultDriver,
201 agent_image: Option<AgentImage>,
202 openhcl_agent_image: Option<AgentImage>,
203 openvmm_path: ResolvedArtifact,
204 output_dir: PathBuf,
205
206 vtl2_vsock_path: Option<TempPath>,
208 _vmbus_vsock_path: TempPath,
209}
210
211impl PetriVmConfigOpenVmm {
212 pub fn os_flavor(&self) -> OsFlavor {
214 self.firmware.os_flavor()
215 }
216}
217
218fn memdiff_disk(path: &Path) -> anyhow::Result<Resource<DiskHandleKind>> {
219 let disk = open_disk_type(path, true)
220 .with_context(|| format!("failed to open disk: {}", path.display()))?;
221 Ok(LayeredDiskHandle {
222 layers: vec![
223 RamDiskLayerHandle { len: None }.into_resource().into(),
224 DiskLayerHandle(disk).into_resource().into(),
225 ],
226 }
227 .into_resource())
228}
229
230fn memdiff_vmgs(vmgs: &PetriVmgsResource) -> anyhow::Result<VmgsResource> {
231 let convert_disk = |disk: &PetriVmgsDisk| -> anyhow::Result<VmgsDisk> {
232 Ok(VmgsDisk {
233 disk: match &disk.disk {
234 PetriDiskType::Memory => LayeredDiskHandle::single_layer(RamDiskLayerHandle {
235 len: Some(vmgs_format::VMGS_DEFAULT_CAPACITY),
236 })
237 .into_resource(),
238 PetriDiskType::Differencing(path) => memdiff_disk(path)?,
239 PetriDiskType::Persistent(path) => open_disk_type(path, false)?,
240 },
241 encryption_policy: disk.encryption_policy,
242 })
243 };
244
245 Ok(match vmgs {
246 PetriVmgsResource::Disk(disk) => VmgsResource::Disk(convert_disk(disk)?),
247 PetriVmgsResource::ReprovisionOnFailure(disk) => {
248 VmgsResource::ReprovisionOnFailure(convert_disk(disk)?)
249 }
250 PetriVmgsResource::Reprovision(disk) => VmgsResource::Reprovision(convert_disk(disk)?),
251 PetriVmgsResource::Ephemeral => VmgsResource::Ephemeral,
252 })
253}