Skip to main content

petri/vm/openvmm/
construct.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Contains [`PetriVmConfigOpenVmm::new`], which builds a [`PetriVmConfigOpenVmm`] with all
5//! default settings for a given [`Firmware`] and [`MachineArch`].
6
7use 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::PetriLogSource;
17use crate::PetriVmConfig;
18use crate::PetriVmResources;
19use crate::PetriVmgsResource;
20use crate::ProcessorTopology;
21use crate::SecureBootTemplate;
22use crate::TpmConfig;
23use crate::UefiConfig;
24use crate::VmbusStorageType;
25use crate::linux_direct_serial_agent::LinuxDirectSerialAgent;
26
27use crate::SIZE_1_MB;
28use crate::VmbusStorageController;
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::PcieDeviceConfig;
55use openvmm_defs::config::ProcessorTopologyConfig;
56use openvmm_defs::config::SerialInformation;
57use openvmm_defs::config::VmbusConfig;
58use openvmm_defs::config::VpciDeviceConfig;
59use openvmm_defs::config::Vtl2BaseAddressType;
60use openvmm_defs::config::Vtl2Config;
61use openvmm_pcat_locator::RomFileLocation;
62use pal_async::DefaultDriver;
63use pal_async::socket::PolledSocket;
64use pal_async::task::Spawn;
65use pal_async::task::Task;
66use petri_artifacts_common::tags::MachineArch;
67use petri_artifacts_core::ResolvedArtifact;
68use pipette_client::PIPETTE_VSOCK_PORT;
69use scsidisk_resources::SimpleScsiDiskHandle;
70use scsidisk_resources::SimpleScsiDvdHandle;
71use serial_16550_resources::ComPort;
72use serial_core::resources::DisconnectedSerialBackendHandle;
73use serial_socket::net::OpenSocketSerialConfig;
74use sparse_mmap::alloc_shared_memory;
75use std::collections::HashMap;
76use storvsp_resources::ScsiControllerHandle;
77use storvsp_resources::ScsiDeviceAndPath;
78use storvsp_resources::ScsiPath;
79use tempfile::TempPath;
80use tpm_resources::TpmDeviceHandle;
81use tpm_resources::TpmRegisterLayout;
82use uidevices_resources::SynthVideoHandle;
83use unix_socket::UnixListener;
84use unix_socket::UnixStream;
85use video_core::SharedFramebufferHandle;
86use virtio_resources::VirtioPciDeviceHandle;
87use virtio_resources::blk::VirtioBlkHandle;
88use virtio_resources::vsock::VirtioVsockHandle;
89use vm_manifest_builder::VmChipsetResult;
90use vm_manifest_builder::VmManifestBuilder;
91use vm_resource::IntoResource;
92use vm_resource::Resource;
93use vm_resource::kind::SerialBackendHandle;
94use vm_resource::kind::VmbusDeviceHandleKind;
95use vmbus_serial_resources::VmbusSerialDeviceHandle;
96use vmbus_serial_resources::VmbusSerialPort;
97use vmcore::non_volatile_store::resources::EphemeralNonVolatileStoreHandle;
98use vmgs_resources::GuestStateEncryptionPolicy;
99use vmgs_resources::VmgsFileHandle;
100use vmotherboard::ChipsetDeviceHandle;
101
102impl PetriVmConfigOpenVmm {
103    /// Create a new VM configuration.
104    pub async fn new(
105        openvmm_path: &ResolvedArtifact,
106        petri_vm_config: PetriVmConfig,
107        resources: &PetriVmResources,
108        properties: PetriVmProperties,
109    ) -> anyhow::Result<Self> {
110        let PetriVmConfig {
111            name: _,
112            arch,
113            host_log_levels,
114            firmware,
115            memory,
116            proc_topology,
117            vmgs,
118            tpm: tpm_config,
119            vmbus_storage_controllers,
120            pcie_nvme_drives,
121        } = petri_vm_config;
122
123        tracing::debug!(?firmware, ?arch, "Petri VM firmware configuration");
124
125        let PetriVmResources { driver, log_source } = resources;
126
127        let mesh = Mesh::new("petri_mesh".to_string())?;
128
129        let setup = PetriVmConfigSetupCore {
130            arch,
131            firmware: &firmware,
132            driver,
133            logger: log_source,
134            vmgs: &vmgs,
135            tpm_config: tpm_config.as_ref(),
136            mesh: &mesh,
137            openvmm_path,
138            uses_pipette_as_init: properties.uses_pipette_as_init,
139            enable_serial: properties.enable_serial,
140            use_virtio_vsock: properties.use_virtio_vsock,
141        };
142
143        let mut chipset = VmManifestBuilder::new(
144            match firmware {
145                Firmware::LinuxDirect { .. } => {
146                    vm_manifest_builder::BaseChipsetType::HyperVGen2LinuxDirect
147                }
148                Firmware::OpenhclLinuxDirect { .. } => {
149                    vm_manifest_builder::BaseChipsetType::HclHost
150                }
151                Firmware::OpenhclUefi { .. } => vm_manifest_builder::BaseChipsetType::HclHost,
152                Firmware::Pcat { .. } => vm_manifest_builder::BaseChipsetType::HypervGen1,
153                Firmware::Uefi { .. } => vm_manifest_builder::BaseChipsetType::HypervGen2Uefi,
154                Firmware::OpenhclPcat { .. } => todo!("OpenVMM OpenHCL PCAT"),
155            },
156            match arch {
157                MachineArch::X86_64 => vm_manifest_builder::MachineArch::X86_64,
158                MachineArch::Aarch64 => vm_manifest_builder::MachineArch::Aarch64,
159            },
160        );
161
162        let mut load_mode = setup.load_firmware()?;
163
164        // If using pipette-as-init, replace the initrd with the pre-built
165        // one that has pipette injected. run_core() guarantees that
166        // prebuilt_initrd is set when uses_pipette_as_init is true.
167        if properties.uses_pipette_as_init {
168            if let LoadMode::Linux { initrd, .. } = &mut load_mode {
169                let prebuilt = properties
170                    .prebuilt_initrd
171                    .as_ref()
172                    .expect("uses_pipette_as_init requires prebuilt_initrd");
173                let file = std::fs::File::open(prebuilt).with_context(|| {
174                    format!("failed to open prebuilt initrd at {}", prebuilt.display())
175                })?;
176                *initrd = Some(file);
177            }
178        }
179
180        let (emulated_serial_config, log_stream_tasks, linux_direct_serial_agent) =
181            if !properties.enable_serial {
182                // No emulated serial backends (OpenHCL VMBus serial stubs may still exist)
183                ([None, None, None, None], Vec::new(), None)
184            } else {
185                let SerialData {
186                    emulated_serial_config,
187                    serial_tasks,
188                    linux_direct_serial_agent,
189                } = setup.configure_serial(log_source)?;
190                (
191                    emulated_serial_config,
192                    serial_tasks,
193                    linux_direct_serial_agent,
194                )
195            };
196        let mut emulated_serial_config = emulated_serial_config;
197
198        let (video_dev, framebuffer, framebuffer_view) = match setup.config_video()? {
199            Some((v, fb, fba)) => {
200                chipset = chipset.with_framebuffer();
201                (Some(v), Some(fb), Some(fba.view()?))
202            }
203            None => (None, None, None),
204        };
205
206        let ide_disks = ide_controllers_to_openvmm(firmware.ide_controllers()).await?;
207        let (mut vmbus_devices, vpci_devices) =
208            vmbus_storage_controllers_to_openvmm(&vmbus_storage_controllers).await?;
209
210        let mut pcie_devices = Vec::new();
211        for PcieNvmeDrive {
212            port_name,
213            nsid,
214            drive: Drive { disk, .. },
215        } in pcie_nvme_drives
216        {
217            let disk = disk.ok_or_else(|| {
218                anyhow::anyhow!(
219                    "missing disk for PCIe NVMe drive on port '{port_name}' (nsid {nsid})"
220                )
221            })?;
222            let disk = petri_disk_to_openvmm(&disk).await?;
223            pcie_devices.push(PcieDeviceConfig {
224                port_name,
225                resource: NvmeControllerHandle {
226                    subsystem_id: guid::guid!("a1b2c3d4-e5f6-7890-abcd-ef0123456789"),
227                    max_io_queues: 64,
228                    msix_count: 64,
229                    namespaces: vec![NamespaceDefinition {
230                        nsid,
231                        read_only: false,
232                        disk,
233                    }],
234                    requests: None,
235                }
236                .into_resource(),
237            });
238        }
239
240        let (firmware_event_send, firmware_event_recv) = mesh::mpsc_channel();
241
242        let make_vsock_listener = || -> anyhow::Result<(UnixListener, TempPath)> {
243            Ok(tempfile::Builder::new()
244                .make(|path| UnixListener::bind(path))?
245                .into_parts())
246        };
247
248        let (with_vtl2, vtl2_vmbus, ged, ged_send, vtl2_vsock_path) = if firmware.is_openhcl() {
249            let (ged, ged_send) = setup
250                .config_openhcl_vmbus_devices(
251                    &mut emulated_serial_config,
252                    &mut vmbus_devices,
253                    &firmware_event_send,
254                    framebuffer.is_some(),
255                )
256                .await?;
257
258            let late_map_vtl0_memory = match load_mode {
259                LoadMode::Igvm {
260                    vtl2_base_address: Vtl2BaseAddressType::Vtl2Allocate { .. },
261                    ..
262                } => {
263                    // Late Map VTL0 memory not supported when test supplies Vtl2Allocate
264                    None
265                }
266                _ => Some(LateMapVtl0MemoryPolicy::InjectException),
267            };
268
269            let (vtl2_vsock_listener, vtl2_vsock_path) = make_vsock_listener()?;
270            (
271                Some(Vtl2Config {
272                    vtl0_alias_map: false, // TODO: enable when OpenVMM supports it for DMA
273                    late_map_vtl0_memory,
274                }),
275                Some(VmbusConfig {
276                    vsock_listener: Some(vtl2_vsock_listener),
277                    vsock_path: Some(vtl2_vsock_path.to_string_lossy().into_owned()),
278                    vmbus_max_version: None,
279                    vtl2_redirect: false,
280                    #[cfg(windows)]
281                    vmbusproxy_handle: None,
282                }),
283                Some(ged),
284                Some(ged_send),
285                Some(vtl2_vsock_path),
286            )
287        } else {
288            (None, None, None, None, None)
289        };
290
291        // Configure the serial ports now that they have been updated by the
292        // OpenHCL configuration.
293        if properties.enable_serial {
294            chipset = chipset.with_serial(emulated_serial_config);
295            // Set so that we don't pull serial data until the guest is
296            // ready. Otherwise, Linux will drop the input serial data
297            // on the floor during boot.
298            if matches!(firmware, Firmware::LinuxDirect { .. }) && !properties.uses_pipette_as_init
299            {
300                chipset = chipset.with_serial_wait_for_rts();
301            }
302        }
303
304        // Extract video configuration
305        let vga_firmware = match video_dev {
306            Some(VideoDevice::Vga(firmware)) => Some(firmware),
307            Some(VideoDevice::Synth(vtl, resource)) => {
308                vmbus_devices.push((vtl, resource));
309                None
310            }
311            None => None,
312        };
313
314        // Add default VMBus devices (skipped in minimal mode).
315        let (shutdown_ic_send, kvp_ic_send) = if !properties.minimal_mode {
316            let (shutdown_ic_send, shutdown_ic_recv) = mesh::channel();
317            vmbus_devices.push((
318                DeviceVtl::Vtl0,
319                ShutdownIcHandle {
320                    recv: shutdown_ic_recv,
321                }
322                .into_resource(),
323            ));
324
325            let (kvp_ic_send, kvp_ic_recv) = mesh::channel();
326            vmbus_devices.push((
327                DeviceVtl::Vtl0,
328                hyperv_ic_resources::kvp::KvpIcHandle { recv: kvp_ic_recv }.into_resource(),
329            ));
330
331            vmbus_devices.push((
332                DeviceVtl::Vtl0,
333                hyperv_ic_resources::timesync::TimesyncIcHandle.into_resource(),
334            ));
335
336            (shutdown_ic_send, kvp_ic_send)
337        } else {
338            // Minimal mode: no ICs. Create dummy senders so the fields
339            // are populated (calls to send_enlightened_shutdown will fail
340            // with a channel error, which is fine — minimal VMs shut down
341            // via reboot(2) directly).
342            let (shutdown_ic_send, _) = mesh::channel();
343            let (kvp_ic_send, _) = mesh::channel();
344            (shutdown_ic_send, kvp_ic_send)
345        };
346
347        // Make a vmbus or virtio vsock path for pipette connections
348        let (vsock_listener, vsock_path) = make_vsock_listener()?;
349        let mut vsock_listener = Some(vsock_listener);
350        let vsock_path_string = vsock_path.to_string_lossy();
351
352        let layout_config = chipset.layout_config();
353        let chipset = chipset
354            .build()
355            .context("failed to build chipset configuration")?;
356
357        let memory = {
358            let MemoryConfig {
359                startup_bytes,
360                dynamic_memory_range,
361                numa_mem_sizes,
362            } = memory;
363
364            if dynamic_memory_range.is_some() {
365                anyhow::bail!("dynamic memory not supported in OpenVMM");
366            }
367
368            let mem_size = if let Some(ref sizes) = numa_mem_sizes {
369                sizes
370                    .iter()
371                    .try_fold(0u64, |acc, &s| acc.checked_add(s))
372                    .context("numa memory sizes overflow")?
373            } else {
374                startup_bytes
375            };
376
377            openvmm_defs::config::MemoryConfig {
378                mem_size,
379                prefetch_memory: false,
380                private_memory: false,
381                transparent_hugepages: false,
382                hugepages: false,
383                hugepage_size: None,
384                numa_mem_sizes,
385            }
386        };
387
388        let processor_topology = {
389            let ProcessorTopology {
390                vp_count,
391                enable_smt,
392                vps_per_socket,
393                apic_mode,
394            } = proc_topology;
395
396            ProcessorTopologyConfig {
397                proc_count: vp_count,
398                vps_per_socket,
399                enable_smt,
400                arch: Some(match arch {
401                    MachineArch::X86_64 => openvmm_defs::config::ArchTopologyConfig::X86(
402                        openvmm_defs::config::X86TopologyConfig {
403                            x2apic: match apic_mode {
404                                None => openvmm_defs::config::X2ApicConfig::Auto,
405                                Some(x) => match x {
406                                    crate::ApicMode::Xapic => {
407                                        openvmm_defs::config::X2ApicConfig::Unsupported
408                                    }
409                                    crate::ApicMode::X2apicSupported => {
410                                        openvmm_defs::config::X2ApicConfig::Supported
411                                    }
412                                    crate::ApicMode::X2apicEnabled => {
413                                        openvmm_defs::config::X2ApicConfig::Enabled
414                                    }
415                                },
416                            },
417                            ..Default::default()
418                        },
419                    ),
420                    MachineArch::Aarch64 => openvmm_defs::config::ArchTopologyConfig::Aarch64(
421                        openvmm_defs::config::Aarch64TopologyConfig::default(),
422                    ),
423                }),
424            }
425        };
426
427        let (secure_boot_enabled, custom_uefi_vars) = firmware.uefi_config().map_or_else(
428            || (false, Default::default()),
429            |c| {
430                (
431                    c.secure_boot_enabled,
432                    match (arch, c.secure_boot_template) {
433                        (MachineArch::X86_64, Some(SecureBootTemplate::MicrosoftWindows)) => {
434                            hyperv_secure_boot_templates::x64::microsoft_windows()
435                        }
436                        (
437                            MachineArch::X86_64,
438                            Some(SecureBootTemplate::MicrosoftUefiCertificateAuthority),
439                        ) => hyperv_secure_boot_templates::x64::microsoft_uefi_ca(),
440                        (MachineArch::Aarch64, Some(SecureBootTemplate::MicrosoftWindows)) => {
441                            hyperv_secure_boot_templates::aarch64::microsoft_windows()
442                        }
443                        (
444                            MachineArch::Aarch64,
445                            Some(SecureBootTemplate::MicrosoftUefiCertificateAuthority),
446                        ) => hyperv_secure_boot_templates::aarch64::microsoft_uefi_ca(),
447                        (_, None) => Default::default(),
448                    },
449                )
450            },
451        );
452
453        let vmgs = if firmware.is_openhcl() {
454            None
455        } else {
456            Some(memdiff_vmgs(&vmgs).await?)
457        };
458
459        let VmChipsetResult {
460            chipset,
461            mut chipset_devices,
462            pci_chipset_devices,
463            capabilities,
464        } = chipset;
465
466        // Add the TPM
467        if let Some(tpm) = setup.config_tpm().await? {
468            chipset_devices.push(tpm);
469        }
470
471        // Set up virtio-vsock if enabled.
472        if properties.use_virtio_vsock {
473            pcie_devices.push(PcieDeviceConfig {
474                port_name: "s0rc0rp0".to_string(),
475                resource: VirtioPciDeviceHandle(
476                    VirtioVsockHandle {
477                        guest_cid: 0x3,
478                        base_path: vsock_path_string.to_string(),
479                        listener: vsock_listener.take().unwrap(),
480                    }
481                    .into_resource(),
482                )
483                .into_resource(),
484            });
485        }
486
487        let config = Config {
488            // Firmware
489            load_mode,
490            firmware_event_send: Some(firmware_event_send),
491
492            // CPU and RAM
493            memory,
494            processor_topology,
495
496            // Base chipset
497            chipset,
498            chipset_devices,
499            pci_chipset_devices,
500            chipset_capabilities: capabilities,
501            layout: layout_config,
502
503            // Basic virtualization device support
504            hypervisor: HypervisorConfig {
505                with_hv: true,
506                with_vtl2,
507                with_isolation: match firmware.isolation() {
508                    Some(IsolationType::Vbs) => Some(openvmm_defs::config::IsolationType::Vbs),
509                    None => None,
510                    _ => anyhow::bail!("unsupported isolation type"),
511                },
512            },
513            vmbus: Some(VmbusConfig {
514                // If virtio vsock is enabled, the vsock_listener will have already been taken and
515                // is now None.
516                vsock_listener,
517                vsock_path: (!properties.use_virtio_vsock).then(|| vsock_path_string.to_string()),
518                vmbus_max_version: None,
519                vtl2_redirect: firmware.openhcl_config().is_some_and(|c| c.vmbus_redirect),
520                #[cfg(windows)]
521                vmbusproxy_handle: None,
522            }),
523            vtl2_vmbus,
524
525            // Devices
526            floppy_disks: vec![],
527            ide_disks,
528            pcie_root_complexes: vec![],
529            pcie_devices,
530            pcie_switches: vec![],
531            vpci_devices,
532            vmbus_devices,
533
534            // Video support
535            framebuffer,
536            vga_firmware,
537
538            secure_boot_enabled,
539            custom_uefi_vars,
540            vmgs,
541
542            // Don't automatically reset the guest by default
543            automatic_guest_reset: false,
544
545            // Disabled for VMM tests by default
546            #[cfg(windows)]
547            kernel_vmnics: vec![],
548            input: mesh::Receiver::new(),
549            vtl2_gfx: false,
550            virtio_devices: vec![],
551            #[cfg(windows)]
552            vpci_resources: vec![],
553            debugger_rpc: None,
554            generation_id_recv: None,
555            rtc_delta_milliseconds: 0,
556            efi_diagnostics_log_level: match firmware
557                .uefi_config()
558                .map(|c| c.efi_diagnostics_log_level)
559                .unwrap_or_default()
560            {
561                EfiDiagnosticsLogLevel::Default => {
562                    openvmm_defs::config::EfiDiagnosticsLogLevelType::Default
563                }
564                EfiDiagnosticsLogLevel::Info => {
565                    openvmm_defs::config::EfiDiagnosticsLogLevelType::Info
566                }
567                EfiDiagnosticsLogLevel::Full => {
568                    openvmm_defs::config::EfiDiagnosticsLogLevelType::Full
569                }
570            },
571        };
572
573        // Make the pipette connection listener.
574        let path = format!("{vsock_path_string}_{PIPETTE_VSOCK_PORT}");
575        let pipette_listener = PolledSocket::new(
576            driver,
577            UnixListener::bind(path).context("failed to bind to pipette listener")?,
578        )?;
579
580        // Make the vtl2 pipette connection listener.
581        let vtl2_pipette_listener = if let Some(vtl2_vmbus) = &config.vtl2_vmbus {
582            let path = vtl2_vmbus.vsock_path.as_ref().unwrap();
583            let path = format!("{path}_{PIPETTE_VSOCK_PORT}");
584            Some(PolledSocket::new(
585                driver,
586                UnixListener::bind(path).context("failed to bind to vtl2 pipette listener")?,
587            )?)
588        } else {
589            None
590        };
591
592        Ok(Self {
593            runtime_config: firmware.into_runtime_config(vmbus_storage_controllers),
594            arch,
595            host_log_levels,
596            config,
597            mesh,
598
599            resources: PetriVmResourcesOpenVmm {
600                log_stream_tasks,
601                firmware_event_recv,
602                shutdown_ic_send,
603                kvp_ic_send,
604                ged_send,
605                pipette_listener,
606                vtl2_pipette_listener,
607                linux_direct_serial_agent,
608                driver: driver.clone(),
609                output_dir: log_source.output_dir().to_owned(),
610                openvmm_path: openvmm_path.clone(),
611                vtl2_vsock_path,
612                _vsock_path: vsock_path,
613                properties,
614            },
615
616            openvmm_log_file: log_source.log_file("openvmm")?,
617
618            memory_backing_file: None,
619
620            ged,
621            framebuffer_view,
622        })
623    }
624}
625
626struct PetriVmConfigSetupCore<'a> {
627    arch: MachineArch,
628    firmware: &'a Firmware,
629    driver: &'a DefaultDriver,
630    logger: &'a PetriLogSource,
631    vmgs: &'a PetriVmgsResource,
632    tpm_config: Option<&'a TpmConfig>,
633    mesh: &'a Mesh,
634    openvmm_path: &'a ResolvedArtifact,
635    uses_pipette_as_init: bool,
636    enable_serial: bool,
637    use_virtio_vsock: bool,
638}
639
640struct SerialData {
641    emulated_serial_config: [Option<Resource<SerialBackendHandle>>; 4],
642    serial_tasks: Vec<Task<anyhow::Result<()>>>,
643    linux_direct_serial_agent: Option<LinuxDirectSerialAgent>,
644}
645
646enum VideoDevice {
647    Vga(RomFileLocation),
648    Synth(DeviceVtl, Resource<VmbusDeviceHandleKind>),
649}
650
651impl PetriVmConfigSetupCore<'_> {
652    fn configure_serial(&self, logger: &PetriLogSource) -> anyhow::Result<SerialData> {
653        let mut serial_tasks = Vec::new();
654
655        let serial0_log_file = logger.log_file(match self.firmware {
656            Firmware::LinuxDirect { .. } | Firmware::OpenhclLinuxDirect { .. } => "linux",
657            Firmware::Pcat { .. } | Firmware::OpenhclPcat { .. } => "pcat",
658            Firmware::Uefi { .. } | Firmware::OpenhclUefi { .. } => "uefi",
659        })?;
660
661        let (serial0_host, serial0) = self
662            .create_serial_stream()
663            .context("failed to create serial0 stream")?;
664        let (serial0_read, serial0_write) = serial0_host.split();
665        let serial0_task = self.driver.spawn(
666            "serial0-console",
667            crate::log_task(serial0_log_file, serial0_read, "serial0-console"),
668        );
669        serial_tasks.push(serial0_task);
670
671        let serial2 = if self.firmware.is_openhcl() {
672            let (serial2_host, serial2) = self
673                .create_serial_stream()
674                .context("failed to create serial2 stream")?;
675            let serial2_task = self.driver.spawn(
676                "serial2-openhcl",
677                crate::log_task(logger.log_file("openhcl")?, serial2_host, "serial2-openhcl"),
678            );
679            serial_tasks.push(serial2_task);
680            serial2
681        } else {
682            None
683        };
684
685        if self.firmware.is_linux_direct() && !self.uses_pipette_as_init {
686            // Non-pipette-as-init Linux direct: create serial1 and a serial
687            // agent so we can send shell commands to launch pipette.
688            let (serial1_host, serial1) = self.create_serial_stream()?;
689            let (serial1_read, _serial1_write) = serial1_host.split();
690            let linux_direct_serial_agent =
691                LinuxDirectSerialAgent::new(serial1_read, serial0_write);
692            Ok(SerialData {
693                emulated_serial_config: [serial0, serial1, serial2, None],
694                serial_tasks,
695                linux_direct_serial_agent: Some(linux_direct_serial_agent),
696            })
697        } else {
698            Ok(SerialData {
699                emulated_serial_config: [serial0, None, serial2, None],
700                serial_tasks,
701                linux_direct_serial_agent: None,
702            })
703        }
704    }
705
706    fn create_serial_stream(
707        &self,
708    ) -> anyhow::Result<(
709        PolledSocket<UnixStream>,
710        Option<Resource<SerialBackendHandle>>,
711    )> {
712        let (host_side, guest_side) = UnixStream::pair()?;
713        let host_side = PolledSocket::new(self.driver, host_side)?;
714        let serial = OpenSocketSerialConfig::from(guest_side).into_resource();
715        Ok((host_side, Some(serial)))
716    }
717
718    fn load_firmware(&self) -> anyhow::Result<LoadMode> {
719        // The test kernel has both CONFIG_VIRTIO_VSOCK=y and
720        // CONFIG_HYPERV_VSOCKETS=y built in. The kernel only allows one G2H
721        // vsock transport, and virtio_vsock_init runs first, claiming the
722        // slot. This causes hv_sock registration to fail with -EBUSY,
723        // breaking pipette's AF_VSOCK connection. Blacklist either
724        // virtio_vsock_init or hv_sock_init depending on which vsock transport
725        // is being used.
726        const VIRTIO_VSOCK_BLACKLIST: &str = "initcall_blacklist=virtio_vsock_init";
727        let vsock_blacklist = if self.use_virtio_vsock {
728            "initcall_blacklist=hv_sock_init"
729        } else {
730            VIRTIO_VSOCK_BLACKLIST
731        };
732
733        Ok(match (self.arch, &self.firmware) {
734            (arch, Firmware::LinuxDirect { kernel, initrd }) => {
735                let console = match arch {
736                    MachineArch::X86_64 => "console=ttyS0",
737                    MachineArch::Aarch64 => "console=ttyAMA0 earlycon",
738                };
739                let kernel = File::open(kernel.clone())
740                    .context("Failed to open kernel")?
741                    .into();
742                let initrd = File::open(initrd.clone())
743                    .context("Failed to open initrd")?
744                    .into();
745
746                let init = if self.uses_pipette_as_init {
747                    "/pipette"
748                } else {
749                    "/bin/sh"
750                };
751
752                let serial_args = if self.enable_serial {
753                    format!("{console} debug ")
754                } else {
755                    String::new()
756                };
757
758                let cmdline = format!("{serial_args}panic=-1 rdinit={init} {vsock_blacklist}");
759
760                LoadMode::Linux {
761                    kernel,
762                    initrd: Some(initrd),
763                    cmdline,
764                    custom_dsdt: None,
765                    enable_serial: self.enable_serial,
766                    boot_mode: openvmm_defs::config::LinuxDirectBootMode::Acpi,
767                }
768            }
769            (
770                MachineArch::X86_64,
771                Firmware::Pcat {
772                    bios_firmware: firmware,
773                    guest: _,         // load_boot_disk
774                    svga_firmware: _, // config_video
775                    ide_controllers: _,
776                },
777            ) => {
778                let firmware = openvmm_pcat_locator::find_pcat_bios(firmware.get())
779                    .context("Failed to load packaged PCAT binary")?;
780                LoadMode::Pcat {
781                    firmware,
782                    boot_order: DEFAULT_PCAT_BOOT_ORDER,
783                }
784            }
785            (
786                _,
787                Firmware::Uefi {
788                    uefi_firmware: firmware,
789                    guest: _, // load_boot_disk
790                    uefi_config:
791                        UefiConfig {
792                            secure_boot_enabled: _,  // new
793                            secure_boot_template: _, // new
794                            disable_frontpage,
795                            default_boot_always_attempt,
796                            enable_vpci_boot,
797                            efi_diagnostics_log_level: _, // applied to top-level Config below
798                        },
799                },
800            ) => {
801                let firmware = File::open(firmware.clone())
802                    .context("Failed to open uefi firmware file")?
803                    .into();
804                LoadMode::Uefi {
805                    firmware,
806                    enable_debugging: false,
807                    enable_memory_protections: false,
808                    disable_frontpage: *disable_frontpage,
809                    enable_tpm: self.tpm_config.is_some(),
810                    enable_battery: false,
811                    enable_serial: true,
812                    enable_vpci_boot: *enable_vpci_boot,
813                    uefi_console_mode: Some(openvmm_defs::config::UefiConsoleMode::Com1),
814                    default_boot_always_attempt: *default_boot_always_attempt,
815                    bios_guid: Guid::new_random(),
816                }
817            }
818            (
819                MachineArch::X86_64,
820                Firmware::OpenhclLinuxDirect {
821                    igvm_path,
822                    openhcl_config,
823                }
824                | Firmware::OpenhclUefi {
825                    igvm_path,
826                    guest: _,       // load_boot_disk
827                    isolation: _,   // new via Firmware::isolation
828                    uefi_config: _, // config_openhcl_vmbus_devices
829                    openhcl_config,
830                },
831            ) => {
832                let OpenHclConfig {
833                    vmbus_redirect: _, // config_openhcl_vmbus_devices
834                    custom_command_line: _,
835                    log_levels: _,
836                    vtl2_base_address_type,
837                    vtl2_settings: _, // run_core
838                } = openhcl_config;
839
840                let mut cmdline = Some(openhcl_config.command_line());
841
842                append_cmdline(&mut cmdline, "panic=-1 reboot=triple");
843
844                let isolated = match self.firmware {
845                    Firmware::OpenhclLinuxDirect { .. } => {
846                        // Set UNDERHILL_SERIAL_WAIT_FOR_RTS=1 so that we don't pull serial data
847                        // until the guest is ready. Otherwise, Linux will drop the input serial
848                        // data on the floor during boot.
849                        append_cmdline(
850                            &mut cmdline,
851                            format!(
852                                "UNDERHILL_SERIAL_WAIT_FOR_RTS=1 UNDERHILL_CMDLINE_APPEND=\"rdinit=/bin/sh {vsock_blacklist}\""
853                            ),
854                        );
855                        false
856                    }
857                    Firmware::OpenhclUefi { isolation, .. } if isolation.is_some() => true,
858                    _ => false,
859                };
860
861                // For certain configurations, we need to override the override
862                // in new_underhill_vm.
863                //
864                // TODO: remove this (and OpenHCL override) once host changes
865                // are saturated.
866                if let Firmware::OpenhclUefi {
867                    uefi_config:
868                        UefiConfig {
869                            default_boot_always_attempt,
870                            secure_boot_enabled,
871                            ..
872                        },
873                    ..
874                } = self.firmware
875                {
876                    if !isolated
877                        && !secure_boot_enabled
878                        && self.tpm_config.is_none()
879                        && !default_boot_always_attempt
880                    {
881                        append_cmdline(&mut cmdline, "HCL_DEFAULT_BOOT_ALWAYS_ATTEMPT=0");
882                    }
883                }
884
885                let vtl2_base_address = vtl2_base_address_type.unwrap_or_else(|| {
886                    if isolated {
887                        // Isolated VMs must load at the location specified by
888                        // the file, as they do not support relocation.
889                        Vtl2BaseAddressType::File
890                    } else {
891                        // By default, utilize IGVM relocation and tell OpenVMM
892                        // to place VTL2 at 512MB. This tests both relocation
893                        // support in OpenVMM, and relocation support within
894                        // OpenHCL.
895                        Vtl2BaseAddressType::Absolute(512 * SIZE_1_MB)
896                    }
897                });
898
899                let file = File::open(igvm_path.clone())
900                    .context("failed to open openhcl firmware file")?
901                    .into();
902                LoadMode::Igvm {
903                    file,
904                    cmdline: cmdline.unwrap_or_default(),
905                    vtl2_base_address,
906                    com_serial: Some(SerialInformation {
907                        io_port: ComPort::Com3.io_port(),
908                        irq: ComPort::Com3.irq().into(),
909                    }),
910                }
911            }
912            (a, f) => anyhow::bail!("Unsupported firmware {f:?} for arch {a:?}"),
913        })
914    }
915
916    async fn config_openhcl_vmbus_devices(
917        &self,
918        serial: &mut [Option<Resource<SerialBackendHandle>>],
919        devices: &mut impl Extend<(DeviceVtl, Resource<VmbusDeviceHandleKind>)>,
920        firmware_event_send: &mesh::Sender<FirmwareEvent>,
921        framebuffer: bool,
922    ) -> anyhow::Result<(
923        get_resources::ged::GuestEmulationDeviceHandle,
924        mesh::Sender<get_resources::ged::GuestEmulationRequest>,
925    )> {
926        let serial0 = serial[0].take();
927        devices.extend([(
928            DeviceVtl::Vtl2,
929            VmbusSerialDeviceHandle {
930                port: VmbusSerialPort::Com1,
931                backend: serial0.unwrap_or_else(|| DisconnectedSerialBackendHandle.into_resource()),
932            }
933            .into_resource(),
934        )]);
935        let serial1 = serial[1].take();
936        devices.extend([(
937            DeviceVtl::Vtl2,
938            VmbusSerialDeviceHandle {
939                port: VmbusSerialPort::Com2,
940                backend: serial1.unwrap_or_else(|| DisconnectedSerialBackendHandle.into_resource()),
941            }
942            .into_resource(),
943        )]);
944
945        let crash = spawn_dump_handler(self.driver, self.logger).into_resource();
946        devices.extend([(DeviceVtl::Vtl2, crash)]);
947
948        let (guest_request_send, guest_request_recv) = mesh::channel();
949
950        let (
951            UefiConfig {
952                secure_boot_enabled,
953                secure_boot_template,
954                disable_frontpage,
955                default_boot_always_attempt,
956                enable_vpci_boot,
957                efi_diagnostics_log_level,
958            },
959            OpenHclConfig { vmbus_redirect, .. },
960        ) = match self.firmware {
961            Firmware::OpenhclUefi {
962                uefi_config,
963                openhcl_config,
964                ..
965            } => (uefi_config, openhcl_config),
966            Firmware::OpenhclLinuxDirect { openhcl_config, .. } => {
967                (&UefiConfig::default(), openhcl_config)
968            }
969            _ => anyhow::bail!("not a supported openhcl firmware config"),
970        };
971
972        let test_gsp_by_id = matches!(
973            self.vmgs.encryption_policy(),
974            Some(GuestStateEncryptionPolicy::GspById(_))
975        );
976
977        // Save the GED handle to add later after configuration is complete.
978        let ged = get_resources::ged::GuestEmulationDeviceHandle {
979            firmware: get_resources::ged::GuestFirmwareConfig::Uefi {
980                firmware_debug: false,
981                disable_frontpage: *disable_frontpage,
982                enable_vpci_boot: *enable_vpci_boot,
983                console_mode: get_resources::ged::UefiConsoleMode::COM1,
984                default_boot_always_attempt: *default_boot_always_attempt,
985            },
986            com1: true,
987            com2: true,
988            serial_tx_only: false,
989            vmbus_redirection: *vmbus_redirect,
990            vtl2_settings: None, // Will be added at startup to allow tests to modify
991            vmgs: memdiff_vmgs(self.vmgs).await?,
992            framebuffer: framebuffer.then(|| SharedFramebufferHandle.into_resource()),
993            guest_request_recv,
994            enable_tpm: self.tpm_config.is_some(),
995            firmware_event_send: Some(firmware_event_send.clone()),
996            secure_boot_enabled: *secure_boot_enabled,
997            secure_boot_template: match secure_boot_template {
998                Some(SecureBootTemplate::MicrosoftWindows) => {
999                    get_resources::ged::GuestSecureBootTemplateType::MicrosoftWindows
1000                }
1001                Some(SecureBootTemplate::MicrosoftUefiCertificateAuthority) => {
1002                    get_resources::ged::GuestSecureBootTemplateType::MicrosoftUefiCertificateAuthority
1003                }
1004                None => get_resources::ged::GuestSecureBootTemplateType::None,
1005            },
1006            enable_battery: false,
1007            no_persistent_secrets: self.tpm_config.as_ref().is_some_and(|c| c.no_persistent_secrets),
1008            igvm_attest_test_config: None,
1009            test_gsp_by_id,
1010            efi_diagnostics_log_level: match efi_diagnostics_log_level {
1011                EfiDiagnosticsLogLevel::Default => {
1012                    get_resources::ged::EfiDiagnosticsLogLevelType::Default
1013                }
1014                EfiDiagnosticsLogLevel::Info => {
1015                    get_resources::ged::EfiDiagnosticsLogLevelType::Info
1016                }
1017                EfiDiagnosticsLogLevel::Full => {
1018                    get_resources::ged::EfiDiagnosticsLogLevelType::Full
1019                }
1020            },
1021            hv_sint_enabled: false,
1022        };
1023
1024        Ok((ged, guest_request_send))
1025    }
1026
1027    fn config_video(
1028        &self,
1029    ) -> anyhow::Result<Option<(VideoDevice, Framebuffer, FramebufferAccess)>> {
1030        if self.firmware.isolation().is_some() {
1031            return Ok(None);
1032        }
1033
1034        let video_dev = match self.firmware {
1035            Firmware::Pcat { svga_firmware, .. } | Firmware::OpenhclPcat { svga_firmware, .. } => {
1036                Some(VideoDevice::Vga(
1037                    openvmm_pcat_locator::find_svga_bios(svga_firmware.get())
1038                        .context("Failed to load VGA BIOS")?,
1039                ))
1040            }
1041            Firmware::Uefi { .. } | Firmware::OpenhclUefi { .. } => Some(VideoDevice::Synth(
1042                DeviceVtl::Vtl0,
1043                SynthVideoHandle {
1044                    framebuffer: SharedFramebufferHandle.into_resource(),
1045                }
1046                .into_resource(),
1047            )),
1048            Firmware::OpenhclLinuxDirect { .. } | Firmware::LinuxDirect { .. } => None,
1049        };
1050
1051        Ok(if let Some(vdev) = video_dev {
1052            let vram =
1053                alloc_shared_memory(FRAMEBUFFER_SIZE, "vram").context("allocating framebuffer")?;
1054            let (fb, fba) = framebuffer::framebuffer(vram, FRAMEBUFFER_SIZE, 0)
1055                .context("creating framebuffer")?;
1056            Some((vdev, fb, fba))
1057        } else {
1058            None
1059        })
1060    }
1061
1062    async fn config_tpm(&self) -> anyhow::Result<Option<ChipsetDeviceHandle>> {
1063        if !self.firmware.is_openhcl()
1064            && let Some(TpmConfig {
1065                no_persistent_secrets,
1066            }) = self.tpm_config
1067        {
1068            let register_layout = match self.arch {
1069                MachineArch::X86_64 => TpmRegisterLayout::IoPort,
1070                MachineArch::Aarch64 => TpmRegisterLayout::Mmio,
1071            };
1072
1073            let (ppi_store, nvram_store) = if self.vmgs.disk().is_none() || *no_persistent_secrets {
1074                (
1075                    EphemeralNonVolatileStoreHandle.into_resource(),
1076                    EphemeralNonVolatileStoreHandle.into_resource(),
1077                )
1078            } else {
1079                (
1080                    VmgsFileHandle::new(vmgs_format::FileId::TPM_PPI, true).into_resource(),
1081                    VmgsFileHandle::new(vmgs_format::FileId::TPM_NVRAM, true).into_resource(),
1082                )
1083            };
1084
1085            Ok(Some(ChipsetDeviceHandle {
1086                name: "tpm".to_string(),
1087                resource: chipset_device_worker_defs::RemoteChipsetDeviceHandle {
1088                    device: TpmDeviceHandle {
1089                        ppi_store,
1090                        nvram_store,
1091                        refresh_tpm_seeds: false,
1092                        ak_cert_type: tpm_resources::TpmAkCertTypeResource::None,
1093                        register_layout,
1094                        guest_secret_key: None,
1095                        logger: None,
1096                        is_confidential_vm: self.firmware.isolation().is_some(),
1097                        // TODO: generate an actual BIOS GUID and put it here
1098                        bios_guid: Guid::ZERO,
1099                        nvram_size: None,
1100                    }
1101                    .into_resource(),
1102                    worker_host: self.make_device_worker("tpm").await?,
1103                }
1104                .into_resource(),
1105            }))
1106        } else {
1107            Ok(None)
1108        }
1109    }
1110
1111    async fn make_device_worker(&self, name: &str) -> anyhow::Result<mesh_worker::WorkerHost> {
1112        let (host, runner) = mesh_worker::worker_host();
1113        self.mesh
1114            .launch_host(
1115                mesh_process::ProcessConfig::new(name).process_name(self.openvmm_path),
1116                openvmm_defs::entrypoint::MeshHostParams { runner },
1117            )
1118            .await?;
1119        Ok(host)
1120    }
1121}
1122
1123fn spawn_dump_handler(driver: &DefaultDriver, logger: &PetriLogSource) -> GuestCrashDeviceHandle {
1124    let (send, mut recv) = mesh::channel();
1125    let handle = GuestCrashDeviceHandle {
1126        request_dump: send,
1127        max_dump_size: 256 * 1024 * 1024,
1128    };
1129    driver
1130        .spawn("openhcl-dump-handler", {
1131            let logger = logger.clone();
1132            let driver = driver.clone();
1133            async move {
1134                while let Some(rpc) = recv.next().await {
1135                    rpc.handle_failable_sync(|done| {
1136                        let (file, path) = logger.create_attachment("openhcl.core")?.into_parts();
1137                        driver
1138                            .spawn("crash-waiter", async move {
1139                                let filename = path.file_name().unwrap().to_str().unwrap();
1140                                if done.await.is_ok() {
1141                                    tracing::warn!(filename, "openhcl crash dump complete");
1142                                } else {
1143                                    tracing::error!(
1144                                        filename,
1145                                        "openhcl crash dump incomplete, may be corrupted"
1146                                    );
1147                                }
1148                            })
1149                            .detach();
1150                        anyhow::Ok(file)
1151                    })
1152                }
1153            }
1154        })
1155        .detach();
1156    handle
1157}
1158
1159/// Convert the generic IDE configuration to OpenVMM IDE disks.
1160async fn ide_controllers_to_openvmm(
1161    ide_controllers: Option<&[[Option<Drive>; 2]; 2]>,
1162) -> anyhow::Result<Vec<IdeDeviceConfig>> {
1163    let mut ide_disks = Vec::new();
1164
1165    if let Some(ide_controllers) = ide_controllers {
1166        for (controller_number, controller) in ide_controllers.iter().enumerate() {
1167            for (controller_location, drive) in controller.iter().enumerate() {
1168                if let Some(drive) = drive {
1169                    if let Some(disk) = &drive.disk {
1170                        let disk = petri_disk_to_openvmm(disk).await?;
1171                        let guest_media = if drive.is_dvd {
1172                            GuestMedia::Dvd(
1173                                SimpleScsiDvdHandle {
1174                                    media: Some(disk),
1175                                    requests: None,
1176                                }
1177                                .into_resource(),
1178                            )
1179                        } else {
1180                            GuestMedia::Disk {
1181                                disk_type: disk,
1182                                read_only: false,
1183                                disk_parameters: None,
1184                            }
1185                        };
1186
1187                        ide_disks.push(IdeDeviceConfig {
1188                            path: ide_resources::IdePath {
1189                                channel: controller_number as u8,
1190                                drive: controller_location as u8,
1191                            },
1192                            guest_media,
1193                        });
1194                    }
1195                }
1196            }
1197        }
1198    }
1199
1200    Ok(ide_disks)
1201}
1202
1203/// Convert the generic VMBUS storage configuration to OpenVMM VMBUS and VPCI devices.
1204async fn vmbus_storage_controllers_to_openvmm(
1205    vmbus_storage_controllers: &HashMap<Guid, VmbusStorageController>,
1206) -> anyhow::Result<(
1207    Vec<(DeviceVtl, Resource<VmbusDeviceHandleKind>)>,
1208    Vec<VpciDeviceConfig>,
1209)> {
1210    let mut vmbus_devices = Vec::new();
1211    let mut vpci_devices = Vec::new();
1212
1213    // Add VMBus storage
1214    for (instance_id, controller) in vmbus_storage_controllers {
1215        let vtl = match controller.target_vtl {
1216            crate::Vtl::Vtl0 => DeviceVtl::Vtl0,
1217            crate::Vtl::Vtl1 => DeviceVtl::Vtl1,
1218            crate::Vtl::Vtl2 => DeviceVtl::Vtl2,
1219        };
1220        match controller.controller_type {
1221            VmbusStorageType::Scsi => {
1222                let mut devices = Vec::new();
1223                for (lun, Drive { disk, is_dvd }) in &controller.drives {
1224                    if !*is_dvd && let Some(disk) = disk {
1225                        devices.push(ScsiDeviceAndPath {
1226                            path: ScsiPath {
1227                                path: 0,
1228                                target: 0,
1229                                lun: (*lun).try_into().expect("invalid scsi lun"),
1230                            },
1231                            device: SimpleScsiDiskHandle {
1232                                disk: petri_disk_to_openvmm(disk).await?,
1233                                read_only: false,
1234                                parameters: Default::default(),
1235                            }
1236                            .into_resource(),
1237                        });
1238                    } else {
1239                        todo!("dvd ({}) or empty ({})", *is_dvd, disk.is_none())
1240                    }
1241                }
1242
1243                vmbus_devices.push((
1244                    vtl,
1245                    ScsiControllerHandle {
1246                        instance_id: *instance_id,
1247                        max_sub_channel_count: 1,
1248                        io_queue_depth: None,
1249                        devices,
1250                        requests: None,
1251                        poll_mode_queue_depth: None,
1252                    }
1253                    .into_resource(),
1254                ));
1255            }
1256            VmbusStorageType::Nvme => {
1257                let mut namespaces = Vec::new();
1258                for (nsid, Drive { disk, is_dvd }) in &controller.drives {
1259                    if !*is_dvd && let Some(disk) = disk {
1260                        namespaces.push(NamespaceDefinition {
1261                            nsid: *nsid,
1262                            read_only: false,
1263                            disk: petri_disk_to_openvmm(disk).await?,
1264                        });
1265                    } else {
1266                        todo!("dvd ({}) or empty ({})", *is_dvd, disk.is_none())
1267                    }
1268                }
1269
1270                vpci_devices.push(VpciDeviceConfig {
1271                    vtl,
1272                    instance_id: *instance_id,
1273                    resource: NvmeControllerHandle {
1274                        subsystem_id: *instance_id,
1275                        max_io_queues: 64,
1276                        msix_count: 64,
1277                        namespaces,
1278                        requests: None,
1279                    }
1280                    .into_resource(),
1281                });
1282            }
1283            VmbusStorageType::VirtioBlk => {
1284                // Each virtio-blk drive needs a unique VPCI instance ID.
1285                // Use a fixed template GUID with data1 set to the LUN.
1286                const VIRTIO_BLK_INSTANCE_ID_TEMPLATE: Guid = Guid {
1287                    data1: 0,
1288                    data2: 0x1234,
1289                    data3: 0x5678,
1290                    data4: [0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67, 0x89],
1291                };
1292                for (lun, Drive { disk, is_dvd }) in &controller.drives {
1293                    if *is_dvd {
1294                        anyhow::bail!("dvd not supported with virtio-blk");
1295                    }
1296                    let Some(disk) = disk else {
1297                        anyhow::bail!("empty drive not supported with virtio-blk");
1298                    };
1299                    let mut drive_id = VIRTIO_BLK_INSTANCE_ID_TEMPLATE;
1300                    drive_id.data1 = *lun;
1301                    vpci_devices.push(VpciDeviceConfig {
1302                        vtl,
1303                        instance_id: drive_id,
1304                        resource: VirtioPciDeviceHandle(
1305                            VirtioBlkHandle {
1306                                disk: petri_disk_to_openvmm(disk).await?,
1307                                read_only: false,
1308                            }
1309                            .into_resource(),
1310                        )
1311                        .into_resource(),
1312                    });
1313                }
1314            }
1315        }
1316    }
1317
1318    Ok((vmbus_devices, vpci_devices))
1319}