1use super::PetriVmConfigOpenVmm;
8use super::PetriVmResourcesOpenVmm;
9use crate::Drive;
10use crate::EfiDiagnosticsLogLevel;
11use crate::Firmware;
12use crate::IgvmFirmwareSource;
13use crate::IsolationType;
14use crate::MemoryConfig;
15use crate::OpenHclConfig;
16use crate::PcieNvmeDrive;
17use crate::PcieVirtioBlkDrive;
18use crate::PetriLogSource;
19use crate::PetriVmConfig;
20use crate::PetriVmResources;
21use crate::PetriVmgsResource;
22use crate::ProcessorTopology;
23use crate::SIZE_1_MB;
24use crate::SecureBootTemplate;
25use crate::TpmConfig;
26use crate::UefiConfig;
27use crate::VmbusStorageController;
28use crate::VmbusStorageType;
29use crate::linux_direct_serial_agent::LinuxDirectSerialAgent;
30use crate::openvmm::memdiff_vmgs;
31use crate::openvmm::petri_disk_to_openvmm;
32use crate::vm::PetriVmProperties;
33use crate::vm::append_cmdline;
34use anyhow::Context;
35use framebuffer::FRAMEBUFFER_SIZE;
36use framebuffer::Framebuffer;
37use framebuffer::FramebufferAccess;
38use fs_err::File;
39use futures::StreamExt;
40use get_resources::crash::GuestCrashDeviceHandle;
41use get_resources::ged::FirmwareEvent;
42use guid::Guid;
43use hyperv_ic_resources::shutdown::ShutdownIcHandle;
44use ide_resources::GuestMedia;
45use ide_resources::IdeDeviceConfig;
46use mesh_process::Mesh;
47use nvme_resources::NamespaceDefinition;
48use nvme_resources::NvmeControllerHandle;
49use openvmm_defs::config::Config;
50use openvmm_defs::config::DEFAULT_ACPI_POWER_BUTTON_IRQ_X64;
51use openvmm_defs::config::DEFAULT_ACPI_POWER_BUTTON_SPI_AARCH64;
52use openvmm_defs::config::DEFAULT_PCAT_BOOT_ORDER;
53use openvmm_defs::config::DeviceVtl;
54use openvmm_defs::config::HypervisorConfig;
55use openvmm_defs::config::IgvmPersonality;
56use openvmm_defs::config::LateMapVtl0MemoryPolicy;
57use openvmm_defs::config::LoadMode;
58use openvmm_defs::config::NumaNode;
59use openvmm_defs::config::NumaTopology;
60use openvmm_defs::config::PcieDeviceConfig;
61use openvmm_defs::config::ProcessorTopologyConfig;
62use openvmm_defs::config::UartId;
63use openvmm_defs::config::VmbusConfig;
64use openvmm_defs::config::VpAssignment;
65use openvmm_defs::config::VpciDeviceConfig;
66use openvmm_defs::config::Vtl2BaseAddressType;
67use openvmm_defs::config::Vtl2Config;
68use openvmm_helpers::config::supports_acpi_power_button;
69use openvmm_pcat_locator::RomFileLocation;
70use pal_async::DefaultDriver;
71use pal_async::socket::PolledSocket;
72use pal_async::task::Spawn;
73use pal_async::task::Task;
74use petri_artifacts_common::tags::MachineArch;
75use petri_artifacts_core::ResolvedArtifact;
76use pipette_client::PIPETTE_PORT;
77use scsidisk_resources::SimpleScsiDiskHandle;
78use scsidisk_resources::SimpleScsiDvdHandle;
79use serial_16550_resources::ComPort;
80use serial_core::resources::DisconnectedSerialBackendHandle;
81use serial_socket::net::OpenSocketSerialConfig;
82use sparse_mmap::alloc_shared_memory;
83use std::collections::HashMap;
84use storvsp_resources::ScsiControllerHandle;
85use storvsp_resources::ScsiDeviceAndPath;
86use storvsp_resources::ScsiPath;
87use tempfile::TempPath;
88use tpm_resources::TpmDeviceHandle;
89use tpm_resources::TpmRegisterLayout;
90use tpm_vmgs::tpm_nvram_file_id;
91use uidevices_resources::SynthVideoHandle;
92use unix_socket::UnixListener;
93use unix_socket::UnixStream;
94use video_core::SharedFramebufferHandle;
95use virtio_resources::VirtioPciDeviceHandle;
96use virtio_resources::blk::VirtioBlkHandle;
97use virtio_resources::vsock::VirtioVsockHandle;
98#[cfg(target_os = "linux")]
99use virtio_resources::vsock::VirtioVsockVhostHandle;
100use vm_manifest_builder::VmChipsetResult;
101use vm_manifest_builder::VmManifestBuilder;
102use vm_resource::IntoResource;
103use vm_resource::Resource;
104use vm_resource::kind::SerialBackendHandle;
105use vm_resource::kind::VirtioDeviceHandle;
106use vm_resource::kind::VmbusDeviceHandleKind;
107use vmbus_serial_resources::VmbusSerialDeviceHandle;
108use vmbus_serial_resources::VmbusSerialPort;
109use vmcore::non_volatile_store::resources::EphemeralNonVolatileStoreHandle;
110use vmgs_resources::GuestStateEncryptionPolicy;
111use vmgs_resources::VmgsFileHandle;
112use vmotherboard::ChipsetDeviceHandle;
113
114impl PetriVmConfigOpenVmm {
115 pub async fn new(
117 openvmm_path: &ResolvedArtifact,
118 petri_vm_config: PetriVmConfig,
119 resources: &PetriVmResources,
120 properties: &PetriVmProperties,
121 ) -> anyhow::Result<Self> {
122 let PetriVmConfig {
123 name: _,
124 arch,
125 host_log_levels,
126 firmware,
127 hibernation_enabled,
128 ipmi_enabled,
129 memory,
130 proc_topology,
131 nested_virt_enabled,
132 vmgs,
133 tpm: tpm_config,
134 vmbus_storage_controllers,
135 pcie_nvme_drives,
136 pcie_virtio_blk_drives,
137 physical_nvme_devices,
138 guest_shares,
139 } = petri_vm_config;
140
141 if !physical_nvme_devices.is_empty() {
142 anyhow::bail!("Physical NVMe devices are only supported with the Hyper-V backend");
143 }
144
145 if matches!(
146 firmware.openhcl_firmware(),
147 Some(IgvmFirmwareSource::VmgsOrInBox)
148 ) {
149 anyhow::bail!("loading OpenHCL from VMGS is only supported by the Hyper-V backend");
150 }
151
152 tracing::debug!(?firmware, ?arch, "Petri VM firmware configuration");
153
154 let PetriVmResources {
155 driver,
156 log_source,
157 prebuilt_initrd,
158 } = resources;
159 anyhow::ensure!(
160 !properties.use_virtio_console
161 || !firmware.is_linux_direct()
162 || !properties.using_vtl0_pipette
163 || properties.external_pipette
164 || prebuilt_initrd.is_some(),
165 "virtio-console requires an initrd-owned Linux-direct Pipette"
166 );
167 #[cfg(target_os = "linux")]
168 let vhost_vsock_guest_cid = properties.vhost_vsock_guest_cid;
169 #[cfg(not(target_os = "linux"))]
170 let vhost_vsock_guest_cid: Option<u32> = None;
171
172 let mesh = Mesh::new("petri_mesh".to_string())?;
173
174 let setup = PetriVmConfigSetupCore {
175 arch,
176 firmware: &firmware,
177 hibernation_enabled,
178 ipmi_enabled,
179 driver,
180 logger: log_source,
181 vmgs: &vmgs,
182 tpm_config: tpm_config.as_ref(),
183 mesh: &mesh,
184 openvmm_path,
185 pipette_rdinit_param: prebuilt_initrd.as_ref().map(|x| x.rdinit_param.clone()),
186 external_pipette: properties.external_pipette,
187 enable_serial: properties.enable_serial,
188 use_virtio_console: properties.use_virtio_console,
189 use_virtio_vsock: properties.use_virtio_vsock,
190 no_vmbus: properties.no_vmbus,
191 no_hv: properties.no_hv,
192 };
193
194 let mut chipset = VmManifestBuilder::new(
195 match firmware {
196 Firmware::LinuxDirect { .. } => {
197 vm_manifest_builder::BaseChipsetType::HyperVGen2LinuxDirect
198 }
199 Firmware::IgvmLinuxDirect { .. } => {
200 vm_manifest_builder::BaseChipsetType::EnlightenedLinuxDirect
201 }
202 Firmware::OpenhclLinuxDirect { .. } => {
203 vm_manifest_builder::BaseChipsetType::HclHost
204 }
205 Firmware::OpenhclUefi { .. } => vm_manifest_builder::BaseChipsetType::HclHost,
206 Firmware::Pcat { .. } => vm_manifest_builder::BaseChipsetType::HypervGen1,
207 Firmware::Uefi { .. } => vm_manifest_builder::BaseChipsetType::HypervGen2Uefi,
208 Firmware::OpenhclPcat { .. } => todo!("OpenVMM OpenHCL PCAT"),
209 },
210 match arch {
211 MachineArch::X86_64 => vm_manifest_builder::MachineArch::X86_64,
212 MachineArch::Aarch64 => vm_manifest_builder::MachineArch::Aarch64,
213 },
214 );
215
216 let mut load_mode = setup.load_firmware()?;
217
218 if let (Some(prebuilt), LoadMode::Linux { initrd, .. }) = (prebuilt_initrd, &mut load_mode)
221 {
222 let file = std::fs::File::open(prebuilt.path.as_ref()).with_context(|| {
223 format!(
224 "failed to open prebuilt initrd at {}",
225 prebuilt.path.as_ref().display()
226 )
227 })?;
228 *initrd = Some(file);
229 }
230
231 let (emulated_serial_config, mut log_stream_tasks, linux_direct_serial_agent) =
232 if !properties.enable_serial {
233 ([None, None, None, None], Vec::new(), None)
235 } else {
236 let SerialData {
237 emulated_serial_config,
238 serial_tasks,
239 linux_direct_serial_agent,
240 } = setup.configure_serial(log_source)?;
241 (
242 emulated_serial_config,
243 serial_tasks,
244 linux_direct_serial_agent,
245 )
246 };
247 let mut emulated_serial_config = emulated_serial_config;
248 let mut private_virtio_devices = Vec::new();
249 if properties.use_virtio_console {
250 let (host, backend) = setup
251 .create_serial_stream()
252 .context("failed to create virtio-console stream")?;
253 log_stream_tasks.push(driver.spawn(
254 "virtio-console",
255 crate::log_task(log_source.log_file("hvc0")?, host, "virtio-console"),
256 ));
257 private_virtio_devices.push((
258 "petri-console-",
259 virtio_resources::console::VirtioConsoleHandle { backend }.into_resource(),
260 ));
261 }
262
263 let (video_dev, framebuffer, framebuffer_view) = match setup.config_video()? {
264 Some((v, fb, fba)) => {
265 chipset = chipset.with_framebuffer();
266 (Some(v), Some(fb), Some(fba.view()?))
267 }
268 None => (None, None, None),
269 };
270
271 if properties.no_vmbus {
272 chipset = chipset.without_vmbus();
273 }
274
275 let (ide_disks, storvsp_ide_handles) =
276 ide_controllers_to_openvmm(firmware.ide_controllers()).await?;
277 let (mut vmbus_devices, vpci_devices) =
278 vmbus_storage_controllers_to_openvmm(&vmbus_storage_controllers).await?;
279
280 let mut pcie_devices = Vec::new();
281 for PcieNvmeDrive {
282 port_name,
283 nsid,
284 drive: Drive { disk, .. },
285 } in pcie_nvme_drives
286 {
287 let disk = disk.ok_or_else(|| {
288 anyhow::anyhow!(
289 "missing disk for PCIe NVMe drive on port '{port_name}' (nsid {nsid})"
290 )
291 })?;
292 let disk = petri_disk_to_openvmm(&disk).await?;
293 pcie_devices.push(PcieDeviceConfig {
294 port_name,
295 resource: NvmeControllerHandle {
296 subsystem_id: Guid::new_random(),
297 max_io_queues: 64,
298 msix_count: 64,
299 namespaces: vec![NamespaceDefinition {
300 nsid,
301 read_only: false,
302 disk,
303 }],
304 requests: None,
305 }
306 .into_resource(),
307 });
308 }
309
310 for PcieVirtioBlkDrive {
311 port_name,
312 drive: Drive { disk, .. },
313 } in pcie_virtio_blk_drives
314 {
315 let disk = disk.ok_or_else(|| {
316 anyhow::anyhow!("missing disk for PCIe virtio-blk drive on port '{port_name}'")
317 })?;
318 let disk = petri_disk_to_openvmm(&disk).await?;
319 pcie_devices.push(PcieDeviceConfig {
320 port_name,
321 resource: VirtioPciDeviceHandle(
322 VirtioBlkHandle {
323 disk,
324 read_only: false,
325 serial: None,
326 }
327 .into_resource(),
328 )
329 .into_resource(),
330 });
331 }
332
333 if !storvsp_ide_handles.is_empty() {
334 anyhow::ensure!(
335 !properties.no_vmbus,
336 "IDE accelerator requires VMBus to be enabled"
337 );
338 vmbus_devices.extend(storvsp_ide_handles);
339 }
340
341 let (firmware_event_send, firmware_event_recv) = mesh::mpsc_channel();
342 let (ipmi_sel_event_send, ipmi_sel_event_recv) = mesh::mpsc_channel();
343
344 let make_vsock_listener = || -> anyhow::Result<(UnixListener, TempPath)> {
345 Ok(tempfile::Builder::new()
346 .make(|path| UnixListener::bind(path))?
347 .into_parts())
348 };
349
350 let (with_vtl2, vtl2_vmbus, ged, ged_send, vtl2_vsock_path) = if firmware.is_openhcl() {
351 let (ged, ged_send) = setup
352 .config_openhcl_vmbus_devices(
353 &mut emulated_serial_config,
354 &mut vmbus_devices,
355 &firmware_event_send,
356 &ipmi_sel_event_send,
357 framebuffer.is_some(),
358 )
359 .await?;
360
361 let late_map_vtl0_memory = match load_mode {
362 LoadMode::Igvm {
363 vtl2_base_address: Vtl2BaseAddressType::Vtl2Allocate { .. },
364 ..
365 } => {
366 None
368 }
369 _ => Some(LateMapVtl0MemoryPolicy::InjectException),
370 };
371
372 let (vtl2_vsock_listener, vtl2_vsock_path) = make_vsock_listener()?;
373 (
374 Some(Vtl2Config {
375 vtl0_alias_map: false, late_map_vtl0_memory,
377 }),
378 Some(VmbusConfig {
379 vsock_listener: Some(vtl2_vsock_listener),
380 vsock_path: Some(vtl2_vsock_path.to_string_lossy().into_owned()),
381 vmbus_max_version: None,
382 vtl2_redirect: false,
383 #[cfg(windows)]
384 vmbusproxy_handle: None,
385 }),
386 Some(ged),
387 Some(ged_send),
388 Some(vtl2_vsock_path),
389 )
390 } else {
391 (None, None, None, None, None)
392 };
393
394 if properties.enable_serial {
397 chipset = chipset.with_serial(emulated_serial_config);
398 if matches!(firmware, Firmware::LinuxDirect { .. })
402 && prebuilt_initrd.is_none()
403 && !properties.external_pipette
404 {
405 chipset = chipset.with_serial_wait_for_rts();
406 }
407 }
408
409 let vga_firmware = match video_dev {
411 Some(VideoDevice::Vga(firmware)) => Some(firmware),
412 Some(VideoDevice::Synth(vtl, resource)) => {
413 vmbus_devices.push((vtl, resource));
414 None
415 }
416 None => None,
417 };
418
419 let (shutdown_ic_send, kvp_ic_send) = if !properties.minimal_mode && !properties.no_vmbus {
421 let (shutdown_ic_send, shutdown_ic_recv) = mesh::channel();
422 vmbus_devices.push((
423 DeviceVtl::Vtl0,
424 ShutdownIcHandle {
425 recv: shutdown_ic_recv,
426 }
427 .into_resource(),
428 ));
429
430 let (kvp_ic_send, kvp_ic_recv) = mesh::channel();
431 vmbus_devices.push((
432 DeviceVtl::Vtl0,
433 hyperv_ic_resources::kvp::KvpIcHandle { recv: kvp_ic_recv }.into_resource(),
434 ));
435
436 vmbus_devices.push((
437 DeviceVtl::Vtl0,
438 hyperv_ic_resources::timesync::TimesyncIcHandle.into_resource(),
439 ));
440
441 (Some(shutdown_ic_send), Some(kvp_ic_send))
442 } else {
443 (None, None)
444 };
445
446 let (vsock_listener, vsock_path) = make_vsock_listener()?;
448 let mut vsock_listener = Some(vsock_listener);
449 let vsock_path_string = vsock_path.to_string_lossy();
450
451 if matches!(firmware, Firmware::Uefi { .. }) {
454 use firmware_uefi_resources::aarch64_secure_boot_templates;
455 use firmware_uefi_resources::x64_secure_boot_templates;
456
457 let uefi_cfg = firmware.uefi_config();
458 let base_template =
459 uefi_cfg
460 .and_then(|c| c.secure_boot_template)
461 .map(|template| match (arch, template) {
462 (MachineArch::X86_64, SecureBootTemplate::MicrosoftWindows) => {
463 x64_secure_boot_templates::microsoft_windows()
464 }
465 (
466 MachineArch::X86_64,
467 SecureBootTemplate::MicrosoftUefiCertificateAuthority,
468 ) => x64_secure_boot_templates::microsoft_uefi_ca(),
469 (MachineArch::Aarch64, SecureBootTemplate::MicrosoftWindows) => {
470 aarch64_secure_boot_templates::microsoft_windows()
471 }
472 (
473 MachineArch::Aarch64,
474 SecureBootTemplate::MicrosoftUefiCertificateAuthority,
475 ) => aarch64_secure_boot_templates::microsoft_uefi_ca(),
476 });
477 let custom_uefi_json = uefi_cfg
478 .and_then(|c| c.custom_uefi_json.clone())
479 .map(Into::into);
480 let secure_boot = uefi_cfg.is_some_and(|c| c.secure_boot_enabled);
481 let log_level = match uefi_cfg
482 .map(|c| c.efi_diagnostics_log_level)
483 .unwrap_or_default()
484 {
485 EfiDiagnosticsLogLevel::Default => {
486 firmware_uefi_resources::LogLevel::make_default()
487 }
488 EfiDiagnosticsLogLevel::Info => firmware_uefi_resources::LogLevel::make_info(),
489 EfiDiagnosticsLogLevel::Full => firmware_uefi_resources::LogLevel::make_full(),
490 };
491 let diagnostics_rate_limit = uefi_cfg.and_then(|c| c.efi_diagnostics_rate_limit);
492 let nvram_storage = if vmgs.disk().is_some() {
493 VmgsFileHandle::new(vmgs_format::FileId::BIOS_NVRAM, true).into_resource()
494 } else {
495 EphemeralNonVolatileStoreHandle.into_resource()
496 };
497 chipset = chipset.with_uefi(vm_manifest_builder::UefiManifest::new(
498 match arch {
499 MachineArch::X86_64 => vm_manifest_builder::MachineArch::X86_64,
500 MachineArch::Aarch64 => vm_manifest_builder::MachineArch::Aarch64,
501 },
502 base_template,
503 custom_uefi_json,
504 secure_boot,
505 log_level,
506 diagnostics_rate_limit,
507 nvram_storage,
508 None,
509 ));
510 }
511
512 let acpi_power_button_send =
513 if !properties.minimal_mode && supports_acpi_power_button(&load_mode) {
514 let (send, recv) = mesh::channel();
515 let irq = match arch {
516 MachineArch::X86_64 => DEFAULT_ACPI_POWER_BUTTON_IRQ_X64,
517 MachineArch::Aarch64 => DEFAULT_ACPI_POWER_BUTTON_SPI_AARCH64,
518 };
519 chipset = chipset.with_acpi_power_button(irq, recv);
520 Some(send)
521 } else {
522 None
523 };
524
525 let layout_config = chipset.layout_config();
526 let chipset = chipset
527 .build()
528 .context("failed to build chipset configuration")?;
529
530 let requested_private_memory = memory.private_memory;
533
534 let numa = {
535 let MemoryConfig {
536 startup_bytes,
537 dynamic_memory_range,
538 numa_mem_sizes,
539 private_memory,
540 transparent_hugepages,
541 } = memory;
542
543 if dynamic_memory_range.is_some() {
544 anyhow::bail!("dynamic memory not supported in OpenVMM");
545 }
546
547 let private_incompatible =
555 firmware.is_openhcl() || firmware.is_pcat() || vhost_vsock_guest_cid.is_some();
556 let private_memory = match private_memory {
557 Some(true) if private_incompatible => {
560 anyhow::bail!(
561 "private guest memory was explicitly requested but is \
562 not supported with this configuration (OpenHCL, \
563 PCAT/Gen1, and kernel vhost-vsock require shared memory)"
564 );
565 }
566 Some(explicit) => explicit,
567 None => !private_incompatible,
570 };
571
572 let make_mem = |size: u64| openvmm_defs::config::MemoryConfig {
578 mem_size: size,
579 prefetch_memory: false,
580 private_memory,
581 transparent_hugepages,
582 hugepages: false,
583 hugepage_size: None,
584 host_numa_node: None,
585 };
586
587 if let Some(sizes) = numa_mem_sizes {
588 NumaTopology {
589 nodes: sizes
590 .into_iter()
591 .map(|size| NumaNode {
592 mem: if size > 0 { Some(make_mem(size)) } else { None },
593 vps: VpAssignment::FromTopology,
594 })
595 .collect(),
596 distances: vec![],
597 }
598 } else {
599 NumaTopology {
600 nodes: vec![NumaNode {
601 mem: Some(make_mem(startup_bytes)),
602 vps: VpAssignment::FromTopology,
603 }],
604 distances: vec![],
605 }
606 }
607 };
608
609 let processor_topology = {
610 let ProcessorTopology {
611 vp_count,
612 enable_smt,
613 vps_per_socket,
614 apic_mode,
615 } = proc_topology;
616
617 ProcessorTopologyConfig {
618 proc_count: vp_count,
619 vps_per_socket,
620 enable_smt,
621 arch: Some(match arch {
622 MachineArch::X86_64 => openvmm_defs::config::ArchTopologyConfig::X86(
623 openvmm_defs::config::X86TopologyConfig {
624 x2apic: match apic_mode {
625 None => openvmm_defs::config::X2ApicConfig::Auto,
626 Some(x) => match x {
627 crate::ApicMode::Xapic => {
628 openvmm_defs::config::X2ApicConfig::Unsupported
629 }
630 crate::ApicMode::X2apicSupported => {
631 openvmm_defs::config::X2ApicConfig::Supported
632 }
633 crate::ApicMode::X2apicEnabled => {
634 openvmm_defs::config::X2ApicConfig::Enabled
635 }
636 },
637 },
638 ..Default::default()
639 },
640 ),
641 MachineArch::Aarch64 => openvmm_defs::config::ArchTopologyConfig::Aarch64(
642 openvmm_defs::config::Aarch64TopologyConfig::default(),
643 ),
644 }),
645 }
646 };
647
648 let vmgs = if firmware.is_openhcl() {
649 None
650 } else {
651 Some(memdiff_vmgs(&vmgs).await?)
652 };
653
654 let VmChipsetResult {
655 chipset,
656 mut chipset_devices,
657 pci_chipset_devices,
658 isa_dma_controller,
659 capabilities,
660 } = chipset;
661
662 if let Some(tpm) = setup.config_tpm().await? {
664 chipset_devices.push(tpm);
665 }
666
667 if properties.use_virtio_vsock {
669 let resource: Resource<VirtioDeviceHandle> = match vhost_vsock_guest_cid {
670 #[cfg(target_os = "linux")]
671 Some(guest_cid) => {
672 let vhost = std::fs::OpenOptions::new()
673 .read(true)
674 .write(true)
675 .open("/dev/vhost-vsock")
676 .context("failed to open /dev/vhost-vsock")?
677 .into();
678 vsock_listener = None;
681 VirtioVsockVhostHandle { vhost, guest_cid }.into_resource()
682 }
683 #[cfg(not(target_os = "linux"))]
684 Some(_) => unreachable!("kernel vhost-vsock is Linux-only"),
685 None => VirtioVsockHandle {
686 guest_cid: 0x3,
687 base_path: vsock_path_string.to_string(),
688 listener: vsock_listener.take().unwrap(),
689 }
690 .into_resource(),
691 };
692 private_virtio_devices.push(("petri-vsock-", resource));
693 }
694
695 let config = Config {
696 load_mode,
698 firmware_event_send: Some(firmware_event_send),
699
700 numa,
702 processor_topology,
703
704 chipset,
706 chipset_devices,
707 pci_chipset_devices,
708 isa_dma_controller,
709 chipset_capabilities: capabilities,
710 layout: layout_config,
711
712 hypervisor: HypervisorConfig {
714 with_hv: !properties.no_hv,
715 with_vtl2,
716 with_isolation: match firmware.isolation() {
717 Some(IsolationType::Vbs) => Some(openvmm_defs::config::IsolationType::Vbs),
718 Some(IsolationType::Snp) => {
719 Some(openvmm_defs::config::IsolationType::Snp { host_data: None })
720 }
721 None => None,
722 _ => anyhow::bail!("unsupported isolation type"),
723 },
724 nested_virt: nested_virt_enabled,
725 },
726 vmbus: if properties.no_vmbus {
727 None
728 } else {
729 Some(VmbusConfig {
730 vsock_listener,
733 vsock_path: (!properties.use_virtio_vsock)
734 .then(|| vsock_path_string.to_string()),
735 vmbus_max_version: None,
736 vtl2_redirect: firmware.openhcl_config().is_some_and(|c| c.vmbus_redirect),
737 #[cfg(windows)]
738 vmbusproxy_handle: None,
739 })
740 },
741 vtl2_vmbus,
742
743 floppy_disks: vec![],
745 ide_disks,
746 pcie_root_complexes: vec![],
747 pcie_ecam_below_4gb: false,
748 pcie_devices,
749 pcie_switches: vec![],
750 pcie_generic_initiators: vec![],
751 vpci_devices,
752 vmbus_devices,
753
754 framebuffer,
756 vga_firmware,
757
758 vmgs,
759
760 #[cfg(windows)]
762 kernel_vmnics: vec![],
763 input: mesh::Receiver::new(),
764 vtl2_gfx: false,
765 virtio_devices: vec![],
766 #[cfg(windows)]
767 vpci_resources: vec![],
768 debugger_rpc: None,
769 rtc_delta_milliseconds: 0,
770 };
771
772 let path = format!("{vsock_path_string}_{PIPETTE_PORT}");
774 let pipette_listener = PolledSocket::new(
775 driver,
776 UnixListener::bind(path).context("failed to bind to pipette listener")?,
777 )?;
778
779 let vtl2_pipette_listener = if let Some(vtl2_vmbus) = &config.vtl2_vmbus {
781 let path = vtl2_vmbus.vsock_path.as_ref().unwrap();
782 let path = format!("{path}_{PIPETTE_PORT}");
783 Some(PolledSocket::new(
784 driver,
785 UnixListener::bind(path).context("failed to bind to vtl2 pipette listener")?,
786 )?)
787 } else {
788 None
789 };
790
791 Ok(Self {
792 guest_shares,
793 private_virtio_devices,
794 runtime_config: firmware.into_runtime_config(vmbus_storage_controllers),
795 arch,
796 host_log_levels,
797 config,
798 skip_save_restore: false,
799 mesh,
800
801 resources: PetriVmResourcesOpenVmm {
802 log_stream_tasks,
803 firmware_event_recv,
804 ipmi_sel_event_recv,
805 shutdown_ic_send,
806 acpi_power_button_send,
807 kvp_ic_send,
808 ged_send,
809 pipette_listener,
810 vtl2_pipette_listener,
811 linux_direct_serial_agent,
812 tcp_pipette_port: None,
813 driver: driver.clone(),
814 output_dir: log_source.output_dir().to_owned(),
815 openvmm_path: openvmm_path.clone(),
816 vtl2_vsock_path,
817 _vsock_path: vsock_path,
818 properties: properties.clone(),
819 #[cfg(windows)]
820 _switch_ports: Vec::new(),
821 },
822
823 openvmm_log_file: log_source.log_file("openvmm")?,
824
825 memory_backing_file: None,
826 requested_private_memory,
827
828 ged,
829 framebuffer_view,
830
831 pending_iommu: Vec::new(),
832 })
833 }
834}
835
836struct PetriVmConfigSetupCore<'a> {
837 arch: MachineArch,
838 firmware: &'a Firmware,
839 hibernation_enabled: bool,
840 ipmi_enabled: bool,
841 driver: &'a DefaultDriver,
842 logger: &'a PetriLogSource,
843 vmgs: &'a PetriVmgsResource,
844 tpm_config: Option<&'a TpmConfig>,
845 mesh: &'a Mesh,
846 openvmm_path: &'a ResolvedArtifact,
847 pipette_rdinit_param: Option<String>,
848 external_pipette: bool,
849 enable_serial: bool,
850 use_virtio_console: bool,
851 use_virtio_vsock: bool,
852 no_vmbus: bool,
853 no_hv: bool,
854}
855
856struct SerialData {
857 emulated_serial_config: [Option<Resource<SerialBackendHandle>>; 4],
858 serial_tasks: Vec<Task<anyhow::Result<()>>>,
859 linux_direct_serial_agent: Option<LinuxDirectSerialAgent>,
860}
861
862enum VideoDevice {
863 Vga(RomFileLocation),
864 Synth(DeviceVtl, Resource<VmbusDeviceHandleKind>),
865}
866
867impl PetriVmConfigSetupCore<'_> {
868 fn configure_serial(&self, logger: &PetriLogSource) -> anyhow::Result<SerialData> {
869 let mut serial_tasks = Vec::new();
870
871 let serial0_log_file = logger.log_file(match self.firmware {
872 Firmware::LinuxDirect { .. }
873 | Firmware::IgvmLinuxDirect { .. }
874 | Firmware::OpenhclLinuxDirect { .. } => "linux",
875 Firmware::Pcat { .. } | Firmware::OpenhclPcat { .. } => "pcat",
876 Firmware::Uefi { .. } | Firmware::OpenhclUefi { .. } => "uefi",
877 })?;
878
879 let (serial0_host, serial0) = self
880 .create_serial_stream()
881 .context("failed to create serial0 stream")?;
882 let (serial0_read, serial0_write) = serial0_host.split();
883 let serial0_task = self.driver.spawn(
884 "serial0-console",
885 crate::log_task(serial0_log_file, serial0_read, "serial0-console"),
886 );
887 serial_tasks.push(serial0_task);
888
889 let serial2 = if self.firmware.is_openhcl() {
890 let (serial2_host, serial2) = self
891 .create_serial_stream()
892 .context("failed to create serial2 stream")?;
893 let serial2_task = self.driver.spawn(
894 "serial2-openhcl",
895 crate::log_task(logger.log_file("openhcl")?, serial2_host, "serial2-openhcl"),
896 );
897 serial_tasks.push(serial2_task);
898 Some(serial2)
899 } else {
900 None
901 };
902
903 if self.firmware.is_linux_direct()
904 && !self.firmware.is_isolated_without_paravisor()
905 && !self.external_pipette
906 && !self.use_virtio_console
907 && self.pipette_rdinit_param.is_none()
908 {
909 let (serial1_host, serial1) = self.create_serial_stream()?;
912 let (serial1_read, _serial1_write) = serial1_host.split();
913 let linux_direct_serial_agent =
914 LinuxDirectSerialAgent::new(serial1_read, serial0_write);
915 Ok(SerialData {
916 emulated_serial_config: [Some(serial0), Some(serial1), serial2, None],
917 serial_tasks,
918 linux_direct_serial_agent: Some(linux_direct_serial_agent),
919 })
920 } else {
921 Ok(SerialData {
922 emulated_serial_config: [Some(serial0), None, serial2, None],
923 serial_tasks,
924 linux_direct_serial_agent: None,
925 })
926 }
927 }
928
929 fn create_serial_stream(
930 &self,
931 ) -> anyhow::Result<(PolledSocket<UnixStream>, Resource<SerialBackendHandle>)> {
932 let (host_side, guest_side) = UnixStream::pair()?;
933 let host_side = PolledSocket::new(self.driver, host_side)?;
934 let serial = OpenSocketSerialConfig::from(guest_side).into_resource();
935 Ok((host_side, serial))
936 }
937
938 fn load_firmware(&self) -> anyhow::Result<LoadMode> {
939 let vsock_blacklist = crate::vm::linux_vsock_blacklist(self.use_virtio_vsock);
947
948 Ok(match (self.arch, &self.firmware) {
949 (MachineArch::X86_64, Firmware::IgvmLinuxDirect { igvm_path, .. }) => LoadMode::Igvm {
950 file: File::open(igvm_path.clone())
951 .context("failed to open Linux-direct IGVM")?
952 .into(),
953 cmdline: String::new(),
954 vtl2_base_address: Vtl2BaseAddressType::File,
955 com_serial: None,
956 personality: IgvmPersonality::LinuxDirect,
957 },
958 (arch, Firmware::LinuxDirect { kernel, initrd }) => {
959 let console = match (self.use_virtio_console, arch) {
960 (true, _) => "console=hvc0",
961 (false, MachineArch::X86_64) => "console=ttyS0",
962 (false, MachineArch::Aarch64) => "console=ttyAMA0 earlycon",
963 };
964 let kernel = File::open(kernel.clone())
965 .context("Failed to open kernel")?
966 .into();
967 let initrd = File::open(initrd.clone())
968 .context("Failed to open initrd")?
969 .into();
970
971 let rdinit = match self.pipette_rdinit_param.as_deref() {
972 Some(path) => format!(" rdinit={path}"),
973 None if self.external_pipette => String::new(),
974 None => " rdinit=/bin/sh".into(),
975 };
976
977 let serial_args = if self.enable_serial || self.use_virtio_console {
978 format!("{console} debug ")
979 } else {
980 String::new()
981 };
982
983 let cmdline = format!("{serial_args}panic=-1{rdinit} {vsock_blacklist}");
984
985 LoadMode::Linux {
986 kernel,
987 initrd: Some(initrd),
988 cmdline,
989 enable_serial: self.enable_serial,
990 isolation: openvmm_defs::config::LinuxIsolationConfig::None,
991 boot_mode: openvmm_defs::config::LinuxDirectBootMode::Acpi,
992 smbios: Default::default(),
993 }
994 }
995 (
996 MachineArch::X86_64,
997 Firmware::Pcat {
998 bios_firmware: firmware,
999 guest: _, svga_firmware: _, ide_controllers: _,
1002 },
1003 ) => {
1004 let firmware = openvmm_pcat_locator::find_pcat_bios(firmware.get())
1005 .context("Failed to load packaged PCAT binary")?;
1006 LoadMode::Pcat {
1007 firmware,
1008 boot_order: DEFAULT_PCAT_BOOT_ORDER,
1009 hibernation_enabled: self.hibernation_enabled,
1010 smbios: Box::new(openvmm_defs::config::SmbiosConfig::default()),
1011 }
1012 }
1013 (
1014 _,
1015 Firmware::Uefi {
1016 uefi_firmware: firmware,
1017 guest: _, uefi_config:
1019 UefiConfig {
1020 secure_boot_enabled: _, secure_boot_template: _, custom_uefi_json: _, disable_frontpage,
1024 default_boot_always_attempt,
1025 enable_vpci_boot,
1026 force_dma_bounce,
1027 efi_diagnostics_log_level: _, efi_diagnostics_rate_limit: _, },
1030 },
1031 ) => {
1032 anyhow::ensure!(
1033 !(self.no_hv && self.arch == MachineArch::X86_64),
1034 "x86_64 UEFI firmware requires Hyper-V enlightenments"
1035 );
1036 let firmware = File::open(firmware.clone())
1037 .context("Failed to open uefi firmware file")?
1038 .into();
1039 LoadMode::Uefi {
1040 firmware,
1041 enable_debugging: false,
1042 enable_memory_protections: false,
1043 disable_frontpage: *disable_frontpage,
1044 tpm_version: self.tpm_config.map(|c| c.version.into()),
1045 enable_battery: false,
1046 enable_serial: true,
1047 enable_vpci_boot: *enable_vpci_boot,
1048 uefi_console_mode: Some(openvmm_defs::config::UefiConsoleMode::Com1),
1049 default_boot_always_attempt: *default_boot_always_attempt,
1050 smbios: Box::new(openvmm_defs::config::SmbiosConfig {
1051 system: openvmm_defs::config::SmbiosSystemOverrides {
1052 uuid: Guid::new_random(),
1053 ..Default::default()
1054 },
1055 ..Default::default()
1056 }),
1057 enable_vmbus: !self.no_vmbus,
1058 force_dma_bounce: *force_dma_bounce,
1059 enable_hv: !self.no_hv,
1060 hibernation_enabled: self.hibernation_enabled,
1061 force_firmware_version: false,
1062 }
1063 }
1064 (
1065 MachineArch::X86_64,
1066 Firmware::OpenhclLinuxDirect {
1067 igvm_firmware: IgvmFirmwareSource::File(igvm_path),
1068 openhcl_config,
1069 }
1070 | Firmware::OpenhclUefi {
1071 igvm_firmware: IgvmFirmwareSource::File(igvm_path),
1072 guest: _, isolation: _, uefi_config: _, openhcl_config,
1076 },
1077 ) => {
1078 let OpenHclConfig {
1079 vmbus_redirect: _, enable_mana_keepalive: _,
1081 custom_command_line: _,
1082 log_levels: _,
1083 vtl2_base_address_type,
1084 vtl2_settings: _, } = openhcl_config;
1086
1087 let mut cmdline = Some(openhcl_config.command_line());
1088
1089 append_cmdline(&mut cmdline, "panic=-1 reboot=triple");
1090
1091 if matches!(self.firmware, Firmware::OpenhclLinuxDirect { .. }) {
1092 append_cmdline(
1096 &mut cmdline,
1097 format!(
1098 "UNDERHILL_SERIAL_WAIT_FOR_RTS=1 UNDERHILL_CMDLINE_APPEND=\"rdinit=/bin/sh {vsock_blacklist}\""
1099 ),
1100 );
1101 }
1102
1103 let isolation = self.firmware.isolation();
1104
1105 if let Firmware::OpenhclUefi {
1111 uefi_config:
1112 UefiConfig {
1113 default_boot_always_attempt,
1114 secure_boot_enabled,
1115 ..
1116 },
1117 ..
1118 } = self.firmware
1119 {
1120 if isolation.is_none()
1121 && !secure_boot_enabled
1122 && self.tpm_config.is_none()
1123 && !default_boot_always_attempt
1124 {
1125 append_cmdline(&mut cmdline, "HCL_DEFAULT_BOOT_ALWAYS_ATTEMPT=0");
1126 }
1127 }
1128
1129 if let Firmware::OpenhclUefi {
1132 uefi_config:
1133 UefiConfig {
1134 efi_diagnostics_rate_limit: Some(limit),
1135 ..
1136 },
1137 ..
1138 } = self.firmware
1139 {
1140 append_cmdline(
1141 &mut cmdline,
1142 format!("HCL_EFI_DIAGNOSTICS_RATE_LIMIT={limit}"),
1143 );
1144 }
1145
1146 let vtl2_base_address = vtl2_base_address_type.unwrap_or_else(|| {
1147 match isolation {
1148 #[cfg(not(debug_assertions))]
1151 Some(IsolationType::Vbs) => Vtl2BaseAddressType::Vtl2Allocate {
1152 size: Some(128 * 1024 * 1024),
1153 },
1154 Some(_) => Vtl2BaseAddressType::File,
1157 None => Vtl2BaseAddressType::Absolute(512 * SIZE_1_MB),
1162 }
1163 });
1164
1165 let file = File::open(igvm_path.get())
1166 .context("failed to open openhcl firmware file")?
1167 .into();
1168 LoadMode::Igvm {
1169 file,
1170 cmdline: cmdline.unwrap_or_default(),
1171 vtl2_base_address,
1172 com_serial: self.enable_serial.then_some(UartId::Com(ComPort::Com3)),
1173 personality: IgvmPersonality::Openhcl,
1174 }
1175 }
1176 (a, f) => anyhow::bail!("Unsupported firmware {f:?} for arch {a:?}"),
1177 })
1178 }
1179
1180 async fn config_openhcl_vmbus_devices(
1181 &self,
1182 serial: &mut [Option<Resource<SerialBackendHandle>>],
1183 devices: &mut impl Extend<(DeviceVtl, Resource<VmbusDeviceHandleKind>)>,
1184 firmware_event_send: &mesh::Sender<FirmwareEvent>,
1185 ipmi_sel_event_send: &mesh::Sender<get_resources::ged::IpmiSelEvent>,
1186 framebuffer: bool,
1187 ) -> anyhow::Result<(
1188 get_resources::ged::GuestEmulationDeviceHandle,
1189 mesh::Sender<get_resources::ged::GuestEmulationRequest>,
1190 )> {
1191 let serial0 = serial[0].take();
1192 devices.extend([(
1193 DeviceVtl::Vtl2,
1194 VmbusSerialDeviceHandle {
1195 port: VmbusSerialPort::Com1,
1196 backend: serial0.unwrap_or_else(|| DisconnectedSerialBackendHandle.into_resource()),
1197 }
1198 .into_resource(),
1199 )]);
1200 let serial1 = serial[1].take();
1201 devices.extend([(
1202 DeviceVtl::Vtl2,
1203 VmbusSerialDeviceHandle {
1204 port: VmbusSerialPort::Com2,
1205 backend: serial1.unwrap_or_else(|| DisconnectedSerialBackendHandle.into_resource()),
1206 }
1207 .into_resource(),
1208 )]);
1209
1210 let crash = spawn_dump_handler(self.driver, self.logger).into_resource();
1211 devices.extend([(DeviceVtl::Vtl2, crash)]);
1212
1213 let (guest_request_send, guest_request_recv) = mesh::channel();
1214
1215 let (
1216 UefiConfig {
1217 secure_boot_enabled,
1218 secure_boot_template,
1219 custom_uefi_json: _, disable_frontpage,
1221 default_boot_always_attempt,
1222 enable_vpci_boot,
1223 force_dma_bounce,
1224 efi_diagnostics_log_level,
1225 efi_diagnostics_rate_limit: _, },
1227 OpenHclConfig { vmbus_redirect, .. },
1228 ) = match self.firmware {
1229 Firmware::OpenhclUefi {
1230 uefi_config,
1231 openhcl_config,
1232 ..
1233 } => (uefi_config, openhcl_config),
1234 Firmware::OpenhclLinuxDirect { openhcl_config, .. } => {
1235 (&UefiConfig::default(), openhcl_config)
1236 }
1237 _ => anyhow::bail!("not a supported openhcl firmware config"),
1238 };
1239
1240 let test_gsp_by_id = matches!(
1241 self.vmgs.encryption_policy(),
1242 Some(GuestStateEncryptionPolicy::GspById(_))
1243 );
1244
1245 let ged = get_resources::ged::GuestEmulationDeviceHandle {
1247 firmware: get_resources::ged::GuestFirmwareConfig::Uefi {
1248 firmware_debug: false,
1249 enable_memory_protections: false,
1250 disable_frontpage: *disable_frontpage,
1251 enable_vpci_boot: *enable_vpci_boot,
1252 console_mode: get_resources::ged::UefiConsoleMode::COM1,
1253 default_boot_always_attempt: *default_boot_always_attempt,
1254 },
1255 com1: true,
1256 com2: true,
1257 serial_tx_only: false,
1258 vmbus_redirection: *vmbus_redirect,
1259 vtl2_settings: None, vmgs: memdiff_vmgs(self.vmgs).await?,
1261 framebuffer: framebuffer.then(|| SharedFramebufferHandle.into_resource()),
1262 guest_request_recv,
1263 tpm_version: self.tpm_config.map(|c| c.version.into() ),
1264 firmware_event_send: Some(firmware_event_send.clone()),
1265 ipmi_sel_event_send: Some(ipmi_sel_event_send.clone()),
1266 secure_boot_enabled: *secure_boot_enabled,
1267 secure_boot_template: match secure_boot_template {
1268 Some(SecureBootTemplate::MicrosoftWindows) => {
1269 get_resources::ged::GuestSecureBootTemplateType::MicrosoftWindows
1270 }
1271 Some(SecureBootTemplate::MicrosoftUefiCertificateAuthority) => {
1272 get_resources::ged::GuestSecureBootTemplateType::MicrosoftUefiCertificateAuthority
1273 }
1274 None => get_resources::ged::GuestSecureBootTemplateType::None,
1275 },
1276 enable_battery: false,
1277 enable_hibernation: self.hibernation_enabled,
1278 enable_ipmi: self.ipmi_enabled,
1279 no_persistent_secrets: self.tpm_config.as_ref().is_some_and(|c| c.no_persistent_secrets),
1280 igvm_attest_test_config: None,
1281 test_gsp_by_id,
1282 efi_diagnostics_log_level: match efi_diagnostics_log_level {
1283 EfiDiagnosticsLogLevel::Default => {
1284 get_resources::ged::EfiDiagnosticsLogLevelType::Default
1285 }
1286 EfiDiagnosticsLogLevel::Info => {
1287 get_resources::ged::EfiDiagnosticsLogLevelType::Info
1288 }
1289 EfiDiagnosticsLogLevel::Full => {
1290 get_resources::ged::EfiDiagnosticsLogLevelType::Full
1291 }
1292 },
1293 force_dma_bounce_enabled: *force_dma_bounce,
1294 smbios: Default::default(),
1295 };
1296
1297 Ok((ged, guest_request_send))
1298 }
1299
1300 fn config_video(
1301 &self,
1302 ) -> anyhow::Result<Option<(VideoDevice, Framebuffer, FramebufferAccess)>> {
1303 if self.firmware.isolation().is_some() {
1304 return Ok(None);
1305 }
1306
1307 let video_dev = match self.firmware {
1308 Firmware::Pcat { svga_firmware, .. } | Firmware::OpenhclPcat { svga_firmware, .. } => {
1309 Some(VideoDevice::Vga(
1310 openvmm_pcat_locator::find_svga_bios(svga_firmware.get())
1311 .context("Failed to load VGA BIOS")?,
1312 ))
1313 }
1314 Firmware::Uefi { .. } | Firmware::OpenhclUefi { .. } if !self.no_vmbus => {
1315 Some(VideoDevice::Synth(
1316 DeviceVtl::Vtl0,
1317 SynthVideoHandle {
1318 framebuffer: SharedFramebufferHandle.into_resource(),
1319 dirt_send: None,
1320 }
1321 .into_resource(),
1322 ))
1323 }
1324 Firmware::OpenhclLinuxDirect { .. }
1325 | Firmware::IgvmLinuxDirect { .. }
1326 | Firmware::LinuxDirect { .. }
1327 | Firmware::Uefi { .. }
1328 | Firmware::OpenhclUefi { .. } => None,
1329 };
1330
1331 Ok(if let Some(vdev) = video_dev {
1332 let vram =
1333 alloc_shared_memory(FRAMEBUFFER_SIZE, "vram").context("allocating framebuffer")?;
1334 let (fb, fba) = framebuffer::framebuffer(vram, FRAMEBUFFER_SIZE, 0)
1335 .context("creating framebuffer")?;
1336 Some((vdev, fb, fba))
1337 } else {
1338 None
1339 })
1340 }
1341
1342 async fn config_tpm(&self) -> anyhow::Result<Option<ChipsetDeviceHandle>> {
1343 if !self.firmware.is_openhcl()
1344 && let Some(&TpmConfig {
1345 no_persistent_secrets,
1346 version,
1347 ..
1348 }) = self.tpm_config
1349 {
1350 let register_layout = match self.arch {
1351 MachineArch::X86_64 => TpmRegisterLayout::IoPort,
1352 MachineArch::Aarch64 => TpmRegisterLayout::Mmio,
1353 };
1354
1355 let tpm_version = version.into();
1356
1357 let (ppi_store, nvram_store) = if self.vmgs.disk().is_none() || no_persistent_secrets {
1358 (
1359 EphemeralNonVolatileStoreHandle.into_resource(),
1360 EphemeralNonVolatileStoreHandle.into_resource(),
1361 )
1362 } else {
1363 (
1364 VmgsFileHandle::new(vmgs_format::FileId::TPM_PPI, true).into_resource(),
1365 VmgsFileHandle::new(tpm_nvram_file_id(tpm_version), true).into_resource(),
1366 )
1367 };
1368
1369 Ok(Some(ChipsetDeviceHandle {
1370 name: "tpm".to_string(),
1371 resource: chipset_device_worker_defs::RemoteChipsetDeviceHandle {
1372 device: TpmDeviceHandle {
1373 version: tpm_version,
1374 ppi_store,
1375 nvram_store,
1376 refresh_tpm_seeds: false,
1377 ak_cert_type: tpm_resources::TpmAkCertTypeResource::None,
1378 register_layout,
1379 guest_secret_key: None,
1380 logger: None,
1381 is_confidential_vm: self.firmware.isolation().is_some(),
1382 bios_guid: Guid::ZERO,
1384 nvram_size: None,
1385 }
1386 .into_resource(),
1387 worker_host: self.make_device_worker("tpm").await?,
1388 }
1389 .into_resource(),
1390 }))
1391 } else {
1392 Ok(None)
1393 }
1394 }
1395
1396 async fn make_device_worker(&self, name: &str) -> anyhow::Result<mesh_worker::WorkerHost> {
1397 let (host, runner) = mesh_worker::worker_host();
1398 self.mesh
1399 .launch_host(
1400 mesh_process::ProcessConfig::new(name).process_name(self.openvmm_path),
1401 openvmm_defs::entrypoint::MeshHostParams { runner },
1402 )
1403 .await?;
1404 Ok(host)
1405 }
1406}
1407
1408fn spawn_dump_handler(driver: &DefaultDriver, logger: &PetriLogSource) -> GuestCrashDeviceHandle {
1409 let (send, mut recv) = mesh::channel();
1410 let handle = GuestCrashDeviceHandle {
1411 request_dump: send,
1412 max_dump_size: 256 * 1024 * 1024,
1413 };
1414 driver
1415 .spawn("openhcl-dump-handler", {
1416 let logger = logger.clone();
1417 let driver = driver.clone();
1418 async move {
1419 while let Some(rpc) = recv.next().await {
1420 rpc.handle_failable_sync(|done| {
1421 let (file, path) = logger.create_attachment("openhcl.core")?.into_parts();
1422 driver
1423 .spawn("crash-waiter", async move {
1424 let filename = path.file_name().unwrap().to_str().unwrap();
1425 if done.await.is_ok() {
1426 tracing::warn!(filename, "openhcl crash dump complete");
1427 } else {
1428 tracing::error!(
1429 filename,
1430 "openhcl crash dump incomplete, may be corrupted"
1431 );
1432 }
1433 })
1434 .detach();
1435 anyhow::Ok(file)
1436 })
1437 }
1438 }
1439 })
1440 .detach();
1441 handle
1442}
1443
1444async fn ide_controllers_to_openvmm(
1447 ide_controllers: Option<&[[Option<Drive>; 2]; 2]>,
1448) -> anyhow::Result<(
1449 Vec<IdeDeviceConfig>,
1450 Vec<(DeviceVtl, Resource<VmbusDeviceHandleKind>)>,
1451)> {
1452 let mut ide_disks = Vec::new();
1453 let mut storvsp_ide_handles = Vec::new();
1454
1455 if let Some(ide_controllers) = ide_controllers {
1456 for (controller_number, controller) in ide_controllers.iter().enumerate() {
1457 for (controller_location, drive) in controller.iter().enumerate() {
1458 if let Some(drive) = drive {
1459 if let Some(disk) = &drive.disk {
1460 let storvsp_disk = if !drive.is_dvd {
1464 Some(petri_disk_to_openvmm(disk).await?)
1465 } else {
1466 None
1467 };
1468
1469 let disk = petri_disk_to_openvmm(disk).await?;
1470 let guest_media = if drive.is_dvd {
1471 GuestMedia::Dvd(
1472 SimpleScsiDvdHandle {
1473 media: Some(disk),
1474 requests: None,
1475 }
1476 .into_resource(),
1477 )
1478 } else {
1479 GuestMedia::Disk {
1480 disk_type: disk,
1481 read_only: false,
1482 }
1483 };
1484
1485 let channel = controller_number as u8;
1486 let device = controller_location as u8;
1487
1488 ide_disks.push(IdeDeviceConfig {
1489 path: ide_resources::IdePath {
1490 channel,
1491 drive: device,
1492 },
1493 guest_media,
1494 });
1495
1496 if let Some(storvsp_disk) = storvsp_disk {
1498 storvsp_ide_handles.push((
1499 DeviceVtl::Vtl0,
1500 storvsp_resources::StorvspIdeDeviceHandle {
1501 channel_id: channel,
1502 device_id: device,
1503 disk: SimpleScsiDiskHandle {
1504 disk: storvsp_disk,
1505 read_only: false,
1506 parameters: Default::default(),
1507 }
1508 .into_resource(),
1509 io_queue_depth: None,
1510 }
1511 .into_resource(),
1512 ));
1513 }
1514 }
1515 }
1516 }
1517 }
1518 }
1519
1520 Ok((ide_disks, storvsp_ide_handles))
1521}
1522
1523async fn vmbus_storage_controllers_to_openvmm(
1525 vmbus_storage_controllers: &HashMap<Guid, VmbusStorageController>,
1526) -> anyhow::Result<(
1527 Vec<(DeviceVtl, Resource<VmbusDeviceHandleKind>)>,
1528 Vec<VpciDeviceConfig>,
1529)> {
1530 let mut vmbus_devices = Vec::new();
1531 let mut vpci_devices = Vec::new();
1532
1533 for (instance_id, controller) in vmbus_storage_controllers {
1535 let vtl = match controller.target_vtl {
1536 crate::Vtl::Vtl0 => DeviceVtl::Vtl0,
1537 crate::Vtl::Vtl1 => DeviceVtl::Vtl1,
1538 crate::Vtl::Vtl2 => DeviceVtl::Vtl2,
1539 };
1540 match controller.controller_type {
1541 VmbusStorageType::Scsi => {
1542 let mut devices = Vec::new();
1543 for (lun, Drive { disk, is_dvd }) in &controller.drives {
1544 if !*is_dvd && let Some(disk) = disk {
1545 devices.push(ScsiDeviceAndPath {
1546 path: ScsiPath {
1547 path: 0,
1548 target: 0,
1549 lun: (*lun).try_into().expect("invalid scsi lun"),
1550 },
1551 device: SimpleScsiDiskHandle {
1552 disk: petri_disk_to_openvmm(disk).await?,
1553 read_only: false,
1554 parameters: Default::default(),
1555 }
1556 .into_resource(),
1557 });
1558 } else {
1559 todo!("dvd ({}) or empty ({})", *is_dvd, disk.is_none())
1560 }
1561 }
1562
1563 vmbus_devices.push((
1564 vtl,
1565 ScsiControllerHandle {
1566 instance_id: *instance_id,
1567 max_sub_channel_count: 1,
1568 io_queue_depth: None,
1569 devices,
1570 requests: None,
1571 poll_mode_queue_depth: None,
1572 }
1573 .into_resource(),
1574 ));
1575 }
1576 VmbusStorageType::Nvme => {
1577 let mut namespaces = Vec::new();
1578 for (nsid, Drive { disk, is_dvd }) in &controller.drives {
1579 if !*is_dvd && let Some(disk) = disk {
1580 namespaces.push(NamespaceDefinition {
1581 nsid: *nsid,
1582 read_only: false,
1583 disk: petri_disk_to_openvmm(disk).await?,
1584 });
1585 } else {
1586 todo!("dvd ({}) or empty ({})", *is_dvd, disk.is_none())
1587 }
1588 }
1589
1590 vpci_devices.push(VpciDeviceConfig {
1591 vtl,
1592 instance_id: *instance_id,
1593 resource: NvmeControllerHandle {
1594 subsystem_id: *instance_id,
1595 max_io_queues: 64,
1596 msix_count: 64,
1597 namespaces,
1598 requests: None,
1599 }
1600 .into_resource(),
1601 vnode: None,
1602 });
1603 }
1604 VmbusStorageType::VirtioBlk => {
1605 const VIRTIO_BLK_INSTANCE_ID_TEMPLATE: Guid = Guid {
1608 data1: 0,
1609 data2: 0x1234,
1610 data3: 0x5678,
1611 data4: [0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67, 0x89],
1612 };
1613 for (lun, Drive { disk, is_dvd }) in &controller.drives {
1614 if *is_dvd {
1615 anyhow::bail!("dvd not supported with virtio-blk");
1616 }
1617 let Some(disk) = disk else {
1618 anyhow::bail!("empty drive not supported with virtio-blk");
1619 };
1620 let mut drive_id = VIRTIO_BLK_INSTANCE_ID_TEMPLATE;
1621 drive_id.data1 = *lun;
1622 vpci_devices.push(VpciDeviceConfig {
1623 vtl,
1624 instance_id: drive_id,
1625 resource: VirtioPciDeviceHandle(
1626 VirtioBlkHandle {
1627 disk: petri_disk_to_openvmm(disk).await?,
1628 read_only: false,
1629 serial: None,
1630 }
1631 .into_resource(),
1632 )
1633 .into_resource(),
1634 vnode: None,
1635 });
1636 }
1637 }
1638 }
1639 }
1640
1641 Ok((vmbus_devices, vpci_devices))
1642}