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