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