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