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