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::BOOT_NVME_LUN;
8use super::BOOT_NVME_NSID;
9use super::PARAVISOR_BOOT_NVME_INSTANCE;
10use super::PetriVmConfigOpenVmm;
11use super::PetriVmResourcesOpenVmm;
12use super::SCSI_INSTANCE;
13use super::memdiff_disk;
14use crate::BootDeviceType;
15use crate::Firmware;
16use crate::IsolationType;
17use crate::MemoryConfig;
18use crate::OpenHclConfig;
19use crate::PcatGuest;
20use crate::PetriLogSource;
21use crate::PetriVmConfig;
22use crate::PetriVmResources;
23use crate::PetriVmgsResource;
24use crate::ProcessorTopology;
25use crate::SIZE_1_GB;
26use crate::SecureBootTemplate;
27use crate::UefiConfig;
28use crate::UefiGuest;
29use crate::linux_direct_serial_agent::LinuxDirectSerialAgent;
30use crate::openvmm::BOOT_NVME_INSTANCE;
31use crate::openvmm::memdiff_vmgs;
32use crate::vm::append_cmdline;
33use crate::vtl2_settings::ControllerType;
34use crate::vtl2_settings::Vtl2LunBuilder;
35use crate::vtl2_settings::Vtl2StorageBackingDeviceBuilder;
36use crate::vtl2_settings::Vtl2StorageControllerBuilder;
37use anyhow::Context;
38use framebuffer::FRAMEBUFFER_SIZE;
39use framebuffer::Framebuffer;
40use framebuffer::FramebufferAccess;
41use fs_err::File;
42use futures::StreamExt;
43use get_resources::crash::GuestCrashDeviceHandle;
44use get_resources::ged::FirmwareEvent;
45use guid::Guid;
46use hvlite_defs::config::Config;
47use hvlite_defs::config::DEFAULT_MMIO_GAPS_AARCH64;
48use hvlite_defs::config::DEFAULT_MMIO_GAPS_AARCH64_WITH_VTL2;
49use hvlite_defs::config::DEFAULT_MMIO_GAPS_X86;
50use hvlite_defs::config::DEFAULT_MMIO_GAPS_X86_WITH_VTL2;
51use hvlite_defs::config::DEFAULT_PCAT_BOOT_ORDER;
52use hvlite_defs::config::DEFAULT_PCIE_ECAM_BASE;
53use hvlite_defs::config::DeviceVtl;
54use hvlite_defs::config::HypervisorConfig;
55use hvlite_defs::config::LateMapVtl0MemoryPolicy;
56use hvlite_defs::config::LoadMode;
57use hvlite_defs::config::ProcessorTopologyConfig;
58use hvlite_defs::config::SerialInformation;
59use hvlite_defs::config::VmbusConfig;
60use hvlite_defs::config::VpciDeviceConfig;
61use hvlite_defs::config::Vtl2BaseAddressType;
62use hvlite_defs::config::Vtl2Config;
63use hvlite_helpers::disk::open_disk_type;
64use hvlite_pcat_locator::RomFileLocation;
65use hyperv_ic_resources::shutdown::ShutdownIcHandle;
66use ide_resources::GuestMedia;
67use ide_resources::IdeDeviceConfig;
68use nvme_resources::NamespaceDefinition;
69use nvme_resources::NvmeControllerHandle;
70use pal_async::DefaultDriver;
71use pal_async::socket::PolledSocket;
72use pal_async::task::Spawn;
73use pal_async::task::Task;
74use petri_artifacts_common::tags::MachineArch;
75use petri_artifacts_core::ResolvedArtifact;
76use pipette_client::PIPETTE_VSOCK_PORT;
77use scsidisk_resources::SimpleScsiDiskHandle;
78use scsidisk_resources::SimpleScsiDvdHandle;
79use serial_16550_resources::ComPort;
80use serial_core::resources::DisconnectedSerialBackendHandle;
81use serial_socket::net::OpenSocketSerialConfig;
82use sparse_mmap::alloc_shared_memory;
83use storvsp_resources::ScsiControllerHandle;
84use storvsp_resources::ScsiDeviceAndPath;
85use storvsp_resources::ScsiPath;
86use tempfile::TempPath;
87use uidevices_resources::SynthVideoHandle;
88use unix_socket::UnixListener;
89use unix_socket::UnixStream;
90use video_core::SharedFramebufferHandle;
91use vm_manifest_builder::VmManifestBuilder;
92use vm_resource::IntoResource;
93use vm_resource::Resource;
94use vm_resource::kind::DiskHandleKind;
95use vm_resource::kind::SerialBackendHandle;
96use vm_resource::kind::VmbusDeviceHandleKind;
97use vmbus_serial_resources::VmbusSerialDeviceHandle;
98use vmbus_serial_resources::VmbusSerialPort;
99use vmgs_resources::GuestStateEncryptionPolicy;
100use vtl2_settings_proto::Vtl2Settings;
101
102impl PetriVmConfigOpenVmm {
103    /// Create a new VM configuration.
104    pub fn new(
105        openvmm_path: &ResolvedArtifact,
106        petri_vm_config: PetriVmConfig,
107        resources: &PetriVmResources,
108    ) -> anyhow::Result<Self> {
109        let PetriVmConfig {
110            name: _,
111            arch,
112            firmware,
113            memory,
114            proc_topology,
115            agent_image,
116            openhcl_agent_image,
117            vmgs,
118            boot_device_type,
119            tpm_state_persistence,
120        } = petri_vm_config;
121
122        let PetriVmResources { driver, log_source } = resources;
123
124        let setup = PetriVmConfigSetupCore {
125            arch,
126            firmware: &firmware,
127            driver,
128            logger: log_source,
129            vmgs: &vmgs,
130            boot_device_type,
131        };
132
133        let mut chipset = VmManifestBuilder::new(
134            match firmware {
135                Firmware::LinuxDirect { .. } => {
136                    vm_manifest_builder::BaseChipsetType::HyperVGen2LinuxDirect
137                }
138                Firmware::OpenhclLinuxDirect { .. } => {
139                    vm_manifest_builder::BaseChipsetType::HclHost
140                }
141                Firmware::OpenhclUefi { .. } => vm_manifest_builder::BaseChipsetType::HclHost,
142                Firmware::Pcat { .. } => vm_manifest_builder::BaseChipsetType::HypervGen1,
143                Firmware::Uefi { .. } => vm_manifest_builder::BaseChipsetType::HypervGen2Uefi,
144                Firmware::OpenhclPcat { .. } => todo!("OpenVMM OpenHCL PCAT"),
145            },
146            match arch {
147                MachineArch::X86_64 => vm_manifest_builder::MachineArch::X86_64,
148                MachineArch::Aarch64 => vm_manifest_builder::MachineArch::Aarch64,
149            },
150        );
151
152        let load_mode = setup.load_firmware()?;
153
154        let SerialData {
155            mut emulated_serial_config,
156            serial_tasks: log_stream_tasks,
157            linux_direct_serial_agent,
158        } = setup.configure_serial(log_source)?;
159
160        let (video_dev, framebuffer, framebuffer_view) = match setup.config_video()? {
161            Some((v, fb, fba)) => {
162                chipset = chipset.with_framebuffer();
163                (Some(v), Some(fb), Some(fba.view()?))
164            }
165            None => (None, None, None),
166        };
167
168        let mut devices = Vec::new();
169
170        let (firmware_event_send, firmware_event_recv) = mesh::mpsc_channel();
171
172        let make_vsock_listener = || -> anyhow::Result<(UnixListener, TempPath)> {
173            Ok(tempfile::Builder::new()
174                .make(|path| UnixListener::bind(path))?
175                .into_parts())
176        };
177
178        let (with_vtl2, vtl2_vmbus, ged, ged_send, mut vtl2_settings, vtl2_vsock_path) =
179            if firmware.is_openhcl() {
180                let (ged, ged_send) = setup.config_openhcl_vmbus_devices(
181                    &mut emulated_serial_config,
182                    &mut devices,
183                    &firmware_event_send,
184                    framebuffer.is_some(),
185                    tpm_state_persistence,
186                )?;
187
188                let late_map_vtl0_memory = match load_mode {
189                    LoadMode::Igvm {
190                        vtl2_base_address: Vtl2BaseAddressType::Vtl2Allocate { .. },
191                        ..
192                    } => {
193                        // Late Map VTL0 memory not supported when test supplies Vtl2Allocate
194                        None
195                    }
196                    _ => Some(LateMapVtl0MemoryPolicy::InjectException),
197                };
198
199                let (vtl2_vsock_listener, vtl2_vsock_path) = make_vsock_listener()?;
200                (
201                    Some(Vtl2Config {
202                        vtl0_alias_map: false, // TODO: enable when OpenVMM supports it for DMA
203                        late_map_vtl0_memory,
204                    }),
205                    Some(VmbusConfig {
206                        vsock_listener: Some(vtl2_vsock_listener),
207                        vsock_path: Some(vtl2_vsock_path.to_string_lossy().into_owned()),
208                        vmbus_max_version: None,
209                        vtl2_redirect: false,
210                        #[cfg(windows)]
211                        vmbusproxy_handle: None,
212                    }),
213                    Some(ged),
214                    Some(ged_send),
215                    // Basic sane default
216                    Some(Vtl2Settings {
217                        version: vtl2_settings_proto::vtl2_settings_base::Version::V1.into(),
218                        dynamic: Some(Default::default()),
219                        fixed: Some(Default::default()),
220                        namespace_settings: Default::default(),
221                    }),
222                    Some(vtl2_vsock_path),
223                )
224            } else {
225                (None, None, None, None, None, None)
226            };
227
228        setup.load_boot_disk(&mut devices, vtl2_settings.as_mut())?;
229
230        // Configure the serial ports now that they have been updated by the
231        // OpenHCL configuration.
232        chipset = chipset.with_serial(emulated_serial_config);
233        // Set so that we don't pull serial data until the guest is
234        // ready. Otherwise, Linux will drop the input serial data
235        // on the floor during boot.
236        if matches!(firmware, Firmware::LinuxDirect { .. }) {
237            chipset = chipset.with_serial_wait_for_rts();
238        }
239
240        // Partition the devices by type.
241        let mut vmbus_devices = Vec::new();
242        let mut ide_disks = Vec::new();
243        let floppy_disks = Vec::new();
244        let mut vpci_devices = Vec::new();
245        for d in devices {
246            match d {
247                Device::Vmbus(vtl, resource) => vmbus_devices.push((vtl, resource)),
248                Device::Vpci(c) => vpci_devices.push(c),
249                Device::Ide(c) => ide_disks.push(c),
250            }
251        }
252
253        // Extract video configuration
254        let vga_firmware = match video_dev {
255            Some(VideoDevice::Vga(firmware)) => Some(firmware),
256            Some(VideoDevice::Synth(vtl, resource)) => {
257                vmbus_devices.push((vtl, resource));
258                None
259            }
260            None => None,
261        };
262
263        // Add the Hyper-V Shutdown IC
264        let (shutdown_ic_send, shutdown_ic_recv) = mesh::channel();
265        vmbus_devices.push((
266            DeviceVtl::Vtl0,
267            ShutdownIcHandle {
268                recv: shutdown_ic_recv,
269            }
270            .into_resource(),
271        ));
272
273        // Add the Hyper-V KVP IC
274        let (kvp_ic_send, kvp_ic_recv) = mesh::channel();
275        vmbus_devices.push((
276            DeviceVtl::Vtl0,
277            hyperv_ic_resources::kvp::KvpIcHandle { recv: kvp_ic_recv }.into_resource(),
278        ));
279
280        // Add the Hyper-V timesync IC
281        vmbus_devices.push((
282            DeviceVtl::Vtl0,
283            hyperv_ic_resources::timesync::TimesyncIcHandle.into_resource(),
284        ));
285
286        // Make a vmbus vsock path for pipette connections
287        let (vmbus_vsock_listener, vmbus_vsock_path) = make_vsock_listener()?;
288
289        let chipset = chipset
290            .build()
291            .context("failed to build chipset configuration")?;
292
293        let memory = {
294            let MemoryConfig {
295                startup_bytes,
296                dynamic_memory_range,
297            } = memory;
298
299            if dynamic_memory_range.is_some() {
300                anyhow::bail!("dynamic memory not supported in OpenVMM");
301            }
302
303            hvlite_defs::config::MemoryConfig {
304                mem_size: startup_bytes,
305                mmio_gaps: if firmware.is_openhcl() {
306                    match arch {
307                        MachineArch::X86_64 => DEFAULT_MMIO_GAPS_X86_WITH_VTL2.into(),
308                        MachineArch::Aarch64 => DEFAULT_MMIO_GAPS_AARCH64_WITH_VTL2.into(),
309                    }
310                } else {
311                    match arch {
312                        MachineArch::X86_64 => DEFAULT_MMIO_GAPS_X86.into(),
313                        MachineArch::Aarch64 => DEFAULT_MMIO_GAPS_AARCH64.into(),
314                    }
315                },
316                prefetch_memory: false,
317                pcie_ecam_base: DEFAULT_PCIE_ECAM_BASE,
318            }
319        };
320
321        let processor_topology = {
322            let ProcessorTopology {
323                vp_count,
324                enable_smt,
325                vps_per_socket,
326                apic_mode,
327            } = proc_topology;
328
329            ProcessorTopologyConfig {
330                proc_count: vp_count,
331                vps_per_socket,
332                enable_smt,
333                arch: Some(match arch {
334                    MachineArch::X86_64 => hvlite_defs::config::ArchTopologyConfig::X86(
335                        hvlite_defs::config::X86TopologyConfig {
336                            x2apic: match apic_mode {
337                                None => hvlite_defs::config::X2ApicConfig::Auto,
338                                Some(x) => match x {
339                                    crate::ApicMode::Xapic => {
340                                        hvlite_defs::config::X2ApicConfig::Unsupported
341                                    }
342                                    crate::ApicMode::X2apicSupported => {
343                                        hvlite_defs::config::X2ApicConfig::Supported
344                                    }
345                                    crate::ApicMode::X2apicEnabled => {
346                                        hvlite_defs::config::X2ApicConfig::Enabled
347                                    }
348                                },
349                            },
350                            ..Default::default()
351                        },
352                    ),
353                    MachineArch::Aarch64 => hvlite_defs::config::ArchTopologyConfig::Aarch64(
354                        hvlite_defs::config::Aarch64TopologyConfig::default(),
355                    ),
356                }),
357            }
358        };
359
360        let (secure_boot_enabled, custom_uefi_vars) = firmware.uefi_config().map_or_else(
361            || (false, Default::default()),
362            |c| {
363                (
364                    c.secure_boot_enabled,
365                    match (arch, c.secure_boot_template) {
366                        (MachineArch::X86_64, Some(SecureBootTemplate::MicrosoftWindows)) => {
367                            hyperv_secure_boot_templates::x64::microsoft_windows()
368                        }
369                        (
370                            MachineArch::X86_64,
371                            Some(SecureBootTemplate::MicrosoftUefiCertificateAuthority),
372                        ) => hyperv_secure_boot_templates::x64::microsoft_uefi_ca(),
373                        (MachineArch::Aarch64, Some(SecureBootTemplate::MicrosoftWindows)) => {
374                            hyperv_secure_boot_templates::aarch64::microsoft_windows()
375                        }
376                        (
377                            MachineArch::Aarch64,
378                            Some(SecureBootTemplate::MicrosoftUefiCertificateAuthority),
379                        ) => hyperv_secure_boot_templates::aarch64::microsoft_uefi_ca(),
380                        (_, None) => Default::default(),
381                    },
382                )
383            },
384        );
385
386        let vmgs = if firmware.is_openhcl() {
387            None
388        } else {
389            Some(memdiff_vmgs(&vmgs)?)
390        };
391
392        let config = Config {
393            // Firmware
394            load_mode,
395            firmware_event_send: Some(firmware_event_send),
396
397            // CPU and RAM
398            memory,
399            processor_topology,
400
401            // Base chipset
402            chipset: chipset.chipset,
403            chipset_devices: chipset.chipset_devices,
404
405            // Basic virtualization device support
406            hypervisor: HypervisorConfig {
407                with_hv: true,
408                user_mode_hv_enlightenments: false,
409                user_mode_apic: false,
410                with_vtl2,
411                with_isolation: match firmware.isolation() {
412                    Some(IsolationType::Vbs) => Some(hvlite_defs::config::IsolationType::Vbs),
413                    None => None,
414                    _ => anyhow::bail!("unsupported isolation type"),
415                },
416            },
417            vmbus: Some(VmbusConfig {
418                vsock_listener: Some(vmbus_vsock_listener),
419                vsock_path: Some(vmbus_vsock_path.to_string_lossy().into_owned()),
420                vmbus_max_version: None,
421                vtl2_redirect: firmware.openhcl_config().is_some_and(|c| c.vmbus_redirect),
422                #[cfg(windows)]
423                vmbusproxy_handle: None,
424            }),
425            vtl2_vmbus,
426
427            // Devices
428            floppy_disks,
429            ide_disks,
430            pcie_root_complexes: vec![],
431            pcie_devices: vec![],
432            vpci_devices,
433            vmbus_devices,
434
435            // Video support
436            framebuffer,
437            vga_firmware,
438
439            secure_boot_enabled,
440            custom_uefi_vars,
441            vmgs,
442
443            // Don't automatically reset the guest by default
444            automatic_guest_reset: false,
445
446            // Disabled for VMM tests by default
447            #[cfg(windows)]
448            kernel_vmnics: vec![],
449            input: mesh::Receiver::new(),
450            vtl2_gfx: false,
451            virtio_console_pci: false,
452            virtio_serial: None,
453            virtio_devices: vec![],
454            #[cfg(windows)]
455            vpci_resources: vec![],
456            debugger_rpc: None,
457            generation_id_recv: None,
458            rtc_delta_milliseconds: 0,
459        };
460
461        // Make the pipette connection listener.
462        let path = config.vmbus.as_ref().unwrap().vsock_path.as_ref().unwrap();
463        let path = format!("{path}_{PIPETTE_VSOCK_PORT}");
464        let pipette_listener = PolledSocket::new(
465            driver,
466            UnixListener::bind(path).context("failed to bind to pipette listener")?,
467        )?;
468
469        // Make the vtl2 pipette connection listener.
470        let vtl2_pipette_listener = if let Some(vtl2_vmbus) = &config.vtl2_vmbus {
471            let path = vtl2_vmbus.vsock_path.as_ref().unwrap();
472            let path = format!("{path}_{PIPETTE_VSOCK_PORT}");
473            Some(PolledSocket::new(
474                driver,
475                UnixListener::bind(path).context("failed to bind to vtl2 pipette listener")?,
476            )?)
477        } else {
478            None
479        };
480
481        Ok(Self {
482            firmware,
483            arch,
484            config,
485            boot_device_type,
486
487            resources: PetriVmResourcesOpenVmm {
488                log_stream_tasks,
489                firmware_event_recv,
490                shutdown_ic_send,
491                kvp_ic_send,
492                ged_send,
493                pipette_listener,
494                vtl2_pipette_listener,
495                linux_direct_serial_agent,
496                driver: driver.clone(),
497                output_dir: log_source.output_dir().to_owned(),
498                agent_image,
499                openhcl_agent_image,
500                openvmm_path: openvmm_path.clone(),
501                vtl2_vsock_path,
502                _vmbus_vsock_path: vmbus_vsock_path,
503                vtl2_settings,
504            },
505
506            openvmm_log_file: log_source.log_file("openvmm")?,
507
508            ged,
509            framebuffer_view,
510        })
511    }
512}
513
514struct PetriVmConfigSetupCore<'a> {
515    arch: MachineArch,
516    firmware: &'a Firmware,
517    driver: &'a DefaultDriver,
518    logger: &'a PetriLogSource,
519    vmgs: &'a PetriVmgsResource,
520    boot_device_type: BootDeviceType,
521}
522
523struct SerialData {
524    emulated_serial_config: [Option<Resource<SerialBackendHandle>>; 4],
525    serial_tasks: Vec<Task<anyhow::Result<()>>>,
526    linux_direct_serial_agent: Option<LinuxDirectSerialAgent>,
527}
528
529enum Device {
530    Vmbus(DeviceVtl, Resource<VmbusDeviceHandleKind>),
531    Vpci(VpciDeviceConfig),
532    Ide(IdeDeviceConfig),
533}
534
535enum VideoDevice {
536    Vga(RomFileLocation),
537    Synth(DeviceVtl, Resource<VmbusDeviceHandleKind>),
538}
539
540impl PetriVmConfigSetupCore<'_> {
541    fn configure_serial(&self, logger: &PetriLogSource) -> anyhow::Result<SerialData> {
542        let mut serial_tasks = Vec::new();
543
544        let serial0_log_file = logger.log_file(match self.firmware {
545            Firmware::LinuxDirect { .. } | Firmware::OpenhclLinuxDirect { .. } => "linux",
546            Firmware::Pcat { .. } | Firmware::OpenhclPcat { .. } => "pcat",
547            Firmware::Uefi { .. } | Firmware::OpenhclUefi { .. } => "uefi",
548        })?;
549
550        let (serial0_host, serial0) = self
551            .create_serial_stream()
552            .context("failed to create serial0 stream")?;
553        let (serial0_read, serial0_write) = serial0_host.split();
554        let serial0_task = self.driver.spawn(
555            "serial0-console",
556            crate::log_task(serial0_log_file, serial0_read, "serial0-console"),
557        );
558        serial_tasks.push(serial0_task);
559
560        let serial2 = if self.firmware.is_openhcl() {
561            let (serial2_host, serial2) = self
562                .create_serial_stream()
563                .context("failed to create serial2 stream")?;
564            let serial2_task = self.driver.spawn(
565                "serial2-openhcl",
566                crate::log_task(logger.log_file("openhcl")?, serial2_host, "serial2-openhcl"),
567            );
568            serial_tasks.push(serial2_task);
569            serial2
570        } else {
571            None
572        };
573
574        if self.firmware.is_linux_direct() {
575            let (serial1_host, serial1) = self.create_serial_stream()?;
576            let (serial1_read, _serial1_write) = serial1_host.split();
577            let linux_direct_serial_agent =
578                LinuxDirectSerialAgent::new(serial1_read, serial0_write);
579            Ok(SerialData {
580                emulated_serial_config: [serial0, serial1, serial2, None],
581                serial_tasks,
582                linux_direct_serial_agent: Some(linux_direct_serial_agent),
583            })
584        } else {
585            Ok(SerialData {
586                emulated_serial_config: [serial0, None, serial2, None],
587                serial_tasks,
588                linux_direct_serial_agent: None,
589            })
590        }
591    }
592
593    fn create_serial_stream(
594        &self,
595    ) -> anyhow::Result<(
596        PolledSocket<UnixStream>,
597        Option<Resource<SerialBackendHandle>>,
598    )> {
599        let (host_side, guest_side) = UnixStream::pair()?;
600        let host_side = PolledSocket::new(self.driver, host_side)?;
601        let serial = OpenSocketSerialConfig::from(guest_side).into_resource();
602        Ok((host_side, Some(serial)))
603    }
604
605    fn load_firmware(&self) -> anyhow::Result<LoadMode> {
606        Ok(match (self.arch, &self.firmware) {
607            (MachineArch::X86_64, Firmware::LinuxDirect { kernel, initrd }) => {
608                let kernel = File::open(kernel.clone())
609                    .context("Failed to open kernel")?
610                    .into();
611                let initrd = File::open(initrd.clone())
612                    .context("Failed to open initrd")?
613                    .into();
614                LoadMode::Linux {
615                    kernel,
616                    initrd: Some(initrd),
617                    cmdline: "console=ttyS0 debug panic=-1 rdinit=/bin/sh".into(),
618                    custom_dsdt: None,
619                    enable_serial: true,
620                }
621            }
622            (MachineArch::Aarch64, Firmware::LinuxDirect { kernel, initrd }) => {
623                let kernel = File::open(kernel.clone())
624                    .context("Failed to open kernel")?
625                    .into();
626                let initrd = File::open(initrd.clone())
627                    .context("Failed to open initrd")?
628                    .into();
629                LoadMode::Linux {
630                    kernel,
631                    initrd: Some(initrd),
632                    cmdline: "console=ttyAMA0 earlycon debug panic=-1 rdinit=/bin/sh".into(),
633                    custom_dsdt: None,
634                    enable_serial: true,
635                }
636            }
637            (
638                MachineArch::X86_64,
639                Firmware::Pcat {
640                    bios_firmware: firmware,
641                    guest: _,         // load_boot_disk
642                    svga_firmware: _, // config_video
643                },
644            ) => {
645                let firmware = hvlite_pcat_locator::find_pcat_bios(firmware.get())
646                    .context("Failed to load packaged PCAT binary")?;
647                LoadMode::Pcat {
648                    firmware,
649                    boot_order: DEFAULT_PCAT_BOOT_ORDER,
650                }
651            }
652            (
653                _,
654                Firmware::Uefi {
655                    uefi_firmware: firmware,
656                    guest: _, // load_boot_disk
657                    uefi_config:
658                        UefiConfig {
659                            secure_boot_enabled: _,  // new
660                            secure_boot_template: _, // new
661                            disable_frontpage,
662                            default_boot_always_attempt,
663                        },
664                },
665            ) => {
666                let firmware = File::open(firmware.clone())
667                    .context("Failed to open uefi firmware file")?
668                    .into();
669                LoadMode::Uefi {
670                    firmware,
671                    enable_debugging: false,
672                    enable_memory_protections: false,
673                    disable_frontpage: *disable_frontpage,
674                    enable_tpm: false,
675                    enable_battery: false,
676                    enable_serial: true,
677                    enable_vpci_boot: matches!(self.boot_device_type, BootDeviceType::Nvme),
678                    uefi_console_mode: Some(hvlite_defs::config::UefiConsoleMode::Com1),
679                    default_boot_always_attempt: *default_boot_always_attempt,
680                    bios_guid: Guid::new_random(),
681                }
682            }
683            (
684                MachineArch::X86_64,
685                Firmware::OpenhclLinuxDirect {
686                    igvm_path,
687                    openhcl_config,
688                }
689                | Firmware::OpenhclUefi {
690                    igvm_path,
691                    guest: _,       // load_boot_disk
692                    isolation: _,   // new via Firmware::isolation
693                    uefi_config: _, // config_openhcl_vmbus_devices
694                    openhcl_config,
695                },
696            ) => {
697                let OpenHclConfig {
698                    vtl2_nvme_boot: _, // load_boot_disk
699                    vmbus_redirect: _, // config_openhcl_vmbus_devices
700                    command_line: _,
701                    log_levels: _,
702                    vtl2_base_address_type,
703                } = openhcl_config;
704
705                let mut cmdline = Some(openhcl_config.command_line());
706
707                append_cmdline(&mut cmdline, "panic=-1 reboot=triple");
708
709                let isolated = match self.firmware {
710                    Firmware::OpenhclLinuxDirect { .. } => {
711                        // Set UNDERHILL_SERIAL_WAIT_FOR_RTS=1 so that we don't pull serial data
712                        // until the guest is ready. Otherwise, Linux will drop the input serial
713                        // data on the floor during boot.
714                        append_cmdline(
715                            &mut cmdline,
716                            "UNDERHILL_SERIAL_WAIT_FOR_RTS=1 UNDERHILL_CMDLINE_APPEND=\"rdinit=/bin/sh\"",
717                        );
718                        false
719                    }
720                    Firmware::OpenhclUefi { isolation, .. } if isolation.is_some() => true,
721                    _ => false,
722                };
723
724                let vtl2_base_address = vtl2_base_address_type.unwrap_or_else(|| {
725                    if isolated {
726                        // Isolated VMs must load at the location specified by
727                        // the file, as they do not support relocation.
728                        Vtl2BaseAddressType::File
729                    } else {
730                        // By default, utilize IGVM relocation and tell OpenVMM
731                        // to place VTL2 at 2GB. This tests both relocation
732                        // support in OpenVMM, and relocation support within
733                        // OpenHCL.
734                        Vtl2BaseAddressType::Absolute(2 * SIZE_1_GB)
735                    }
736                });
737
738                let file = File::open(igvm_path.clone())
739                    .context("failed to open openhcl firmware file")?
740                    .into();
741                LoadMode::Igvm {
742                    file,
743                    cmdline: cmdline.unwrap_or_default(),
744                    vtl2_base_address,
745                    com_serial: Some(SerialInformation {
746                        io_port: ComPort::Com3.io_port(),
747                        irq: ComPort::Com3.irq().into(),
748                    }),
749                }
750            }
751            (a, f) => anyhow::bail!("Unsupported firmware {f:?} for arch {a:?}"),
752        })
753    }
754
755    fn load_boot_disk(
756        &self,
757        devices: &mut impl Extend<Device>,
758        vtl2_settings: Option<&mut Vtl2Settings>,
759    ) -> anyhow::Result<()> {
760        let emulate_storage_in_openhcl = matches!(
761            self.firmware,
762            Firmware::OpenhclUefi {
763                openhcl_config: OpenHclConfig {
764                    vtl2_nvme_boot: true,
765                    ..
766                },
767                ..
768            }
769        );
770        enum Media {
771            Disk(Resource<DiskHandleKind>),
772            Dvd(Resource<DiskHandleKind>),
773        }
774        let media = match &self.firmware {
775            Firmware::LinuxDirect { .. }
776            | Firmware::OpenhclLinuxDirect { .. }
777            | Firmware::Uefi {
778                guest: UefiGuest::None,
779                ..
780            }
781            | Firmware::OpenhclUefi {
782                guest: UefiGuest::None,
783                ..
784            } => return Ok(()),
785            Firmware::Pcat { guest, .. } | Firmware::OpenhclPcat { guest, .. } => {
786                let disk_path = guest.artifact();
787                match guest {
788                    PcatGuest::Vhd(_) => Media::Disk(memdiff_disk(disk_path.as_ref())?),
789                    PcatGuest::Iso(_) => Media::Dvd(open_disk_type(disk_path.as_ref(), true)?),
790                }
791            }
792            Firmware::Uefi { guest, .. } | Firmware::OpenhclUefi { guest, .. } => {
793                let disk_path = guest.artifact();
794                Media::Disk(memdiff_disk(
795                    disk_path.expect("not uefi guest none").as_ref(),
796                )?)
797            }
798        };
799
800        if emulate_storage_in_openhcl {
801            match media {
802                Media::Dvd(_) => todo!("support DVD to VTL2"),
803                Media::Disk(disk) => {
804                    devices.extend([Device::Vpci(VpciDeviceConfig {
805                        vtl: DeviceVtl::Vtl2,
806                        instance_id: PARAVISOR_BOOT_NVME_INSTANCE,
807                        resource: NvmeControllerHandle {
808                            subsystem_id: PARAVISOR_BOOT_NVME_INSTANCE,
809                            max_io_queues: 64,
810                            msix_count: 64,
811                            namespaces: vec![NamespaceDefinition {
812                                nsid: BOOT_NVME_NSID,
813                                disk,
814                                read_only: false,
815                            }],
816                        }
817                        .into_resource(),
818                    })]);
819                }
820            }
821            match self.boot_device_type {
822                BootDeviceType::None => {}
823                BootDeviceType::Ide => todo!("support IDE emulation testing"),
824                BootDeviceType::Nvme => todo!("support NVMe emulation testing"),
825                BootDeviceType::Scsi => vtl2_settings
826                    .expect("openhcl config should have vtl2settings")
827                    .dynamic
828                    .as_mut()
829                    .unwrap()
830                    .storage_controllers
831                    .push(
832                        Vtl2StorageControllerBuilder::scsi()
833                            .with_instance_id(SCSI_INSTANCE)
834                            .add_lun(
835                                Vtl2LunBuilder::disk()
836                                    .with_location(BOOT_NVME_LUN)
837                                    .with_physical_device(Vtl2StorageBackingDeviceBuilder::new(
838                                        ControllerType::Nvme,
839                                        PARAVISOR_BOOT_NVME_INSTANCE,
840                                        BOOT_NVME_NSID,
841                                    )),
842                            )
843                            .build(),
844                    ),
845            }
846        } else {
847            match self.boot_device_type {
848                BootDeviceType::None => {}
849                BootDeviceType::Ide => {
850                    let guest_media = match media {
851                        Media::Disk(disk_type) => GuestMedia::Disk {
852                            read_only: false,
853                            disk_parameters: None,
854                            disk_type,
855                        },
856                        Media::Dvd(media) => GuestMedia::Dvd(
857                            SimpleScsiDvdHandle {
858                                media: Some(media),
859                                requests: None,
860                            }
861                            .into_resource(),
862                        ),
863                    };
864                    devices.extend([Device::Ide(IdeDeviceConfig {
865                        path: ide_resources::IdePath {
866                            channel: 0,
867                            drive: 0,
868                        },
869                        guest_media,
870                    })]);
871                }
872                BootDeviceType::Scsi => {
873                    let disk = match media {
874                        Media::Disk(disk) => disk,
875                        Media::Dvd(_) => todo!("support SCSI DVD boot disks"),
876                    };
877                    devices.extend([Device::Vmbus(
878                        DeviceVtl::Vtl0,
879                        ScsiControllerHandle {
880                            instance_id: SCSI_INSTANCE,
881                            max_sub_channel_count: 1,
882                            io_queue_depth: None,
883                            devices: vec![ScsiDeviceAndPath {
884                                path: ScsiPath {
885                                    path: 0,
886                                    target: 0,
887                                    lun: 0,
888                                },
889                                device: SimpleScsiDiskHandle {
890                                    read_only: false,
891                                    parameters: Default::default(),
892                                    disk,
893                                }
894                                .into_resource(),
895                            }],
896                            requests: None,
897                            poll_mode_queue_depth: None,
898                        }
899                        .into_resource(),
900                    )]);
901                }
902                BootDeviceType::Nvme => {
903                    let disk = match media {
904                        Media::Disk(disk) => disk,
905                        Media::Dvd(_) => anyhow::bail!("dvd not supported on nvme"),
906                    };
907                    devices.extend([Device::Vpci(VpciDeviceConfig {
908                        vtl: DeviceVtl::Vtl0,
909                        instance_id: BOOT_NVME_INSTANCE,
910                        resource: NvmeControllerHandle {
911                            subsystem_id: BOOT_NVME_INSTANCE,
912                            max_io_queues: 64,
913                            msix_count: 64,
914                            namespaces: vec![NamespaceDefinition {
915                                nsid: BOOT_NVME_NSID,
916                                disk,
917                                read_only: false,
918                            }],
919                        }
920                        .into_resource(),
921                    })]);
922                }
923            }
924        }
925
926        Ok(())
927    }
928
929    fn config_openhcl_vmbus_devices(
930        &self,
931        serial: &mut [Option<Resource<SerialBackendHandle>>],
932        devices: &mut impl Extend<Device>,
933        firmware_event_send: &mesh::Sender<FirmwareEvent>,
934        framebuffer: bool,
935        tpm_state_persistence: bool,
936    ) -> anyhow::Result<(
937        get_resources::ged::GuestEmulationDeviceHandle,
938        mesh::Sender<get_resources::ged::GuestEmulationRequest>,
939    )> {
940        let serial0 = serial[0].take();
941        devices.extend([Device::Vmbus(
942            DeviceVtl::Vtl2,
943            VmbusSerialDeviceHandle {
944                port: VmbusSerialPort::Com1,
945                backend: serial0.unwrap_or_else(|| DisconnectedSerialBackendHandle.into_resource()),
946            }
947            .into_resource(),
948        )]);
949        let serial1 = serial[1].take();
950        devices.extend([Device::Vmbus(
951            DeviceVtl::Vtl2,
952            VmbusSerialDeviceHandle {
953                port: VmbusSerialPort::Com2,
954                backend: serial1.unwrap_or_else(|| DisconnectedSerialBackendHandle.into_resource()),
955            }
956            .into_resource(),
957        )]);
958
959        let gel = get_resources::gel::GuestEmulationLogHandle.into_resource();
960
961        let crash = spawn_dump_handler(self.driver, self.logger).into_resource();
962
963        devices.extend([
964            Device::Vmbus(DeviceVtl::Vtl2, crash),
965            Device::Vmbus(DeviceVtl::Vtl2, gel),
966        ]);
967
968        let (guest_request_send, guest_request_recv) = mesh::channel();
969
970        let (
971            UefiConfig {
972                secure_boot_enabled,
973                secure_boot_template,
974                disable_frontpage,
975                default_boot_always_attempt,
976            },
977            OpenHclConfig { vmbus_redirect, .. },
978        ) = match self.firmware {
979            Firmware::OpenhclUefi {
980                uefi_config,
981                openhcl_config,
982                ..
983            } => (uefi_config, openhcl_config),
984            Firmware::OpenhclLinuxDirect { openhcl_config, .. } => {
985                (&UefiConfig::default(), openhcl_config)
986            }
987            _ => anyhow::bail!("not a supported openhcl firmware config"),
988        };
989
990        let test_gsp_by_id = self
991            .vmgs
992            .disk()
993            .is_some_and(|x| matches!(x.encryption_policy, GuestStateEncryptionPolicy::GspById(_)));
994
995        // Save the GED handle to add later after configuration is complete.
996        let ged = get_resources::ged::GuestEmulationDeviceHandle {
997            firmware: get_resources::ged::GuestFirmwareConfig::Uefi {
998                firmware_debug: false,
999                disable_frontpage: *disable_frontpage,
1000                enable_vpci_boot: matches!(self.boot_device_type, BootDeviceType::Nvme),
1001                console_mode: get_resources::ged::UefiConsoleMode::COM1,
1002                default_boot_always_attempt: *default_boot_always_attempt,
1003            },
1004            com1: true,
1005            com2: true,
1006            vmbus_redirection: *vmbus_redirect,
1007            vtl2_settings: None, // Will be added at startup to allow tests to modify
1008            vmgs: memdiff_vmgs(self.vmgs)?,
1009            framebuffer: framebuffer.then(|| SharedFramebufferHandle.into_resource()),
1010            guest_request_recv,
1011            enable_tpm: false,
1012            firmware_event_send: Some(firmware_event_send.clone()),
1013            secure_boot_enabled: *secure_boot_enabled,
1014            secure_boot_template: match secure_boot_template {
1015                Some(SecureBootTemplate::MicrosoftWindows) => {
1016                    get_resources::ged::GuestSecureBootTemplateType::MicrosoftWindows
1017                }
1018                Some(SecureBootTemplate::MicrosoftUefiCertificateAuthority) => {
1019                    get_resources::ged::GuestSecureBootTemplateType::MicrosoftUefiCertificateAuthority
1020                }
1021                None => get_resources::ged::GuestSecureBootTemplateType::None,
1022            },
1023            enable_battery: false,
1024            no_persistent_secrets: !tpm_state_persistence,
1025            igvm_attest_test_config: None,
1026            test_gsp_by_id,
1027        };
1028
1029        Ok((ged, guest_request_send))
1030    }
1031
1032    fn config_video(
1033        &self,
1034    ) -> anyhow::Result<Option<(VideoDevice, Framebuffer, FramebufferAccess)>> {
1035        if self.firmware.isolation().is_some() {
1036            return Ok(None);
1037        }
1038
1039        let video_dev = match self.firmware {
1040            Firmware::Pcat { svga_firmware, .. } | Firmware::OpenhclPcat { svga_firmware, .. } => {
1041                Some(VideoDevice::Vga(
1042                    hvlite_pcat_locator::find_svga_bios(svga_firmware.get())
1043                        .context("Failed to load VGA BIOS")?,
1044                ))
1045            }
1046            Firmware::Uefi { .. } | Firmware::OpenhclUefi { .. } => Some(VideoDevice::Synth(
1047                DeviceVtl::Vtl0,
1048                SynthVideoHandle {
1049                    framebuffer: SharedFramebufferHandle.into_resource(),
1050                }
1051                .into_resource(),
1052            )),
1053            Firmware::OpenhclLinuxDirect { .. } | Firmware::LinuxDirect { .. } => None,
1054        };
1055
1056        Ok(if let Some(vdev) = video_dev {
1057            let vram = alloc_shared_memory(FRAMEBUFFER_SIZE).context("allocating framebuffer")?;
1058            let (fb, fba) = framebuffer::framebuffer(vram, FRAMEBUFFER_SIZE, 0)
1059                .context("creating framebuffer")?;
1060            Some((vdev, fb, fba))
1061        } else {
1062            None
1063        })
1064    }
1065}
1066
1067fn spawn_dump_handler(driver: &DefaultDriver, logger: &PetriLogSource) -> GuestCrashDeviceHandle {
1068    let (send, mut recv) = mesh::channel();
1069    let handle = GuestCrashDeviceHandle {
1070        request_dump: send,
1071        max_dump_size: 256 * 1024 * 1024,
1072    };
1073    driver
1074        .spawn("openhcl-dump-handler", {
1075            let logger = logger.clone();
1076            let driver = driver.clone();
1077            async move {
1078                while let Some(rpc) = recv.next().await {
1079                    rpc.handle_failable_sync(|done| {
1080                        let (file, path) = logger.create_attachment("openhcl.core")?.into_parts();
1081                        driver
1082                            .spawn("crash-waiter", async move {
1083                                let filename = path.file_name().unwrap().to_str().unwrap();
1084                                if done.await.is_ok() {
1085                                    tracing::warn!(filename, "openhcl crash dump complete");
1086                                } else {
1087                                    tracing::error!(
1088                                        filename,
1089                                        "openhcl crash dump incomplete, may be corrupted"
1090                                    );
1091                                }
1092                            })
1093                            .detach();
1094                        anyhow::Ok(file)
1095                    })
1096                }
1097            }
1098        })
1099        .detach();
1100    handle
1101}