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::PetriDiskType;
23use crate::PetriLogFile;
24use crate::PetriVmConfig;
25use crate::PetriVmResources;
26use crate::PetriVmgsDisk;
27use crate::PetriVmgsResource;
28use crate::PetriVmmBackend;
29use crate::VmmQuirks;
30use crate::disk_image::AgentImage;
31use crate::linux_direct_serial_agent::LinuxDirectSerialAgent;
32use anyhow::Context;
33use async_trait::async_trait;
34use disk_backend_resources::LayeredDiskHandle;
35use disk_backend_resources::layer::DiskLayerHandle;
36use disk_backend_resources::layer::RamDiskLayerHandle;
37use get_resources::ged::FirmwareEvent;
38use guid::Guid;
39use hvlite_defs::config::Config;
40use hvlite_helpers::disk::open_disk_type;
41use hyperv_ic_resources::shutdown::ShutdownRpc;
42use mesh::Receiver;
43use mesh::Sender;
44use net_backend_resources::mac_address::MacAddress;
45use pal_async::DefaultDriver;
46use pal_async::socket::PolledSocket;
47use pal_async::task::Task;
48use petri_artifacts_common::tags::GuestQuirksInner;
49use petri_artifacts_common::tags::MachineArch;
50use petri_artifacts_common::tags::OsFlavor;
51use petri_artifacts_core::ArtifactResolver;
52use petri_artifacts_core::ResolvedArtifact;
53use std::path::Path;
54use std::path::PathBuf;
55use std::time::Duration;
56use tempfile::TempPath;
57use unix_socket::UnixListener;
58use vm_resource::IntoResource;
59use vm_resource::Resource;
60use vm_resource::kind::DiskHandleKind;
61use vmgs_resources::VmgsDisk;
62use vmgs_resources::VmgsResource;
63use vtl2_settings_proto::Vtl2Settings;
64
65pub(crate) const SCSI_INSTANCE: Guid = guid::guid!("27b553e8-8b39-411b-a55f-839971a7884f");
67
68pub(crate) const PARAVISOR_BOOT_NVME_INSTANCE: Guid =
71 guid::guid!("92bc8346-718b-449a-8751-edbf3dcd27e4");
72
73pub(crate) const BOOT_NVME_INSTANCE: Guid = guid::guid!("e23a04e2-90f5-4852-bc9d-e7ac691b756c");
75
76const MANA_INSTANCE: Guid = guid::guid!("f9641cf4-d915-4743-a7d8-efa75db7b85a");
78
79pub(crate) const BOOT_NVME_NSID: u32 = 37;
81
82pub(crate) const BOOT_NVME_LUN: u32 = 1;
84
85pub const NIC_MAC_ADDRESS: MacAddress = MacAddress::new([0x00, 0x15, 0x5D, 0x12, 0x12, 0x12]);
87
88pub struct OpenVmmPetriBackend {
90 openvmm_path: ResolvedArtifact,
91}
92
93#[async_trait]
94impl PetriVmmBackend for OpenVmmPetriBackend {
95 type VmmConfig = PetriVmConfigOpenVmm;
96 type VmRuntime = PetriVmOpenVmm;
97
98 fn check_compat(firmware: &Firmware, arch: MachineArch) -> bool {
99 arch == MachineArch::host()
100 && !(firmware.is_openhcl() && (!cfg!(windows) || arch == MachineArch::Aarch64))
101 && !(firmware.is_pcat() && arch == MachineArch::Aarch64)
102 }
103
104 fn quirks(firmware: &Firmware) -> (GuestQuirksInner, VmmQuirks) {
105 (
106 firmware.quirks().openvmm,
107 VmmQuirks {
108 flaky_boot: firmware.is_pcat().then_some(Duration::from_secs(15)),
110 },
111 )
112 }
113
114 fn new(resolver: &ArtifactResolver<'_>) -> Self {
115 OpenVmmPetriBackend {
116 openvmm_path: resolver
117 .require(petri_artifacts_vmm_test::artifacts::OPENVMM_NATIVE)
118 .erase(),
119 }
120 }
121
122 async fn run(
123 self,
124 config: PetriVmConfig,
125 modify_vmm_config: Option<impl FnOnce(PetriVmConfigOpenVmm) -> PetriVmConfigOpenVmm + Send>,
126 resources: &PetriVmResources,
127 ) -> anyhow::Result<Self::VmRuntime> {
128 let mut config = PetriVmConfigOpenVmm::new(&self.openvmm_path, config, resources)?;
129
130 if let Some(f) = modify_vmm_config {
131 config = f(config);
132 }
133
134 config.run().await
135 }
136}
137
138pub struct PetriVmConfigOpenVmm {
140 firmware: Firmware,
142 arch: MachineArch,
143 config: Config,
144 boot_device_type: BootDeviceType,
145
146 resources: PetriVmResourcesOpenVmm,
148
149 openvmm_log_file: PetriLogFile,
151
152 ged: Option<get_resources::ged::GuestEmulationDeviceHandle>,
154 framebuffer_view: Option<framebuffer::View>,
155}
156struct PetriVmResourcesOpenVmm {
158 log_stream_tasks: Vec<Task<anyhow::Result<()>>>,
159 firmware_event_recv: Receiver<FirmwareEvent>,
160 shutdown_ic_send: Sender<ShutdownRpc>,
161 kvp_ic_send: Sender<hyperv_ic_resources::kvp::KvpConnectRpc>,
162 ged_send: Option<Sender<get_resources::ged::GuestEmulationRequest>>,
163 pipette_listener: PolledSocket<UnixListener>,
164 vtl2_pipette_listener: Option<PolledSocket<UnixListener>>,
165 linux_direct_serial_agent: Option<LinuxDirectSerialAgent>,
166
167 driver: DefaultDriver,
169 agent_image: Option<AgentImage>,
170 openhcl_agent_image: Option<AgentImage>,
171 openvmm_path: ResolvedArtifact,
172 output_dir: PathBuf,
173
174 vtl2_vsock_path: Option<TempPath>,
176 _vmbus_vsock_path: TempPath,
177
178 vtl2_settings: Option<Vtl2Settings>,
179}
180
181impl PetriVmConfigOpenVmm {
182 pub fn os_flavor(&self) -> OsFlavor {
184 self.firmware.os_flavor()
185 }
186}
187
188fn memdiff_disk(path: &Path) -> anyhow::Result<Resource<DiskHandleKind>> {
189 let disk = open_disk_type(path, true)
190 .with_context(|| format!("failed to open disk: {}", path.display()))?;
191 Ok(LayeredDiskHandle {
192 layers: vec![
193 RamDiskLayerHandle { len: None }.into_resource().into(),
194 DiskLayerHandle(disk).into_resource().into(),
195 ],
196 }
197 .into_resource())
198}
199
200fn memdiff_vmgs(vmgs: &PetriVmgsResource) -> anyhow::Result<VmgsResource> {
201 let convert_disk = |disk: &PetriVmgsDisk| -> anyhow::Result<VmgsDisk> {
202 Ok(VmgsDisk {
203 disk: match &disk.disk {
204 PetriDiskType::Memory => LayeredDiskHandle::single_layer(RamDiskLayerHandle {
205 len: Some(vmgs_format::VMGS_DEFAULT_CAPACITY),
206 })
207 .into_resource(),
208 PetriDiskType::Differencing(path) => memdiff_disk(path)?,
209 PetriDiskType::Persistent(path) => open_disk_type(path, false)?,
210 },
211 encryption_policy: disk.encryption_policy,
212 })
213 };
214
215 Ok(match vmgs {
216 PetriVmgsResource::Disk(disk) => VmgsResource::Disk(convert_disk(disk)?),
217 PetriVmgsResource::ReprovisionOnFailure(disk) => {
218 VmgsResource::ReprovisionOnFailure(convert_disk(disk)?)
219 }
220 PetriVmgsResource::Reprovision(disk) => VmgsResource::Reprovision(convert_disk(disk)?),
221 PetriVmgsResource::Ephemeral => VmgsResource::Ephemeral,
222 })
223}