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