1use 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 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 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, 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 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 chipset = chipset.with_serial(emulated_serial_config);
282 if matches!(firmware, Firmware::LinuxDirect { .. }) {
286 chipset = chipset.with_serial_wait_for_rts();
287 }
288
289 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 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 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 vmbus_devices.push((
318 DeviceVtl::Vtl0,
319 hyperv_ic_resources::timesync::TimesyncIcHandle.into_resource(),
320 ));
321
322 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 if let Some(tpm) = setup.config_tpm() {
435 chipset_devices.push(tpm);
436 }
437
438 let config = Config {
439 load_mode,
441 firmware_event_send: Some(firmware_event_send),
442
443 memory,
445 processor_topology,
446
447 chipset,
449 chipset_devices,
450
451 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 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 framebuffer,
484 vga_firmware,
485
486 secure_boot_enabled,
487 custom_uefi_vars,
488 vmgs,
489
490 automatic_guest_reset: false,
492
493 #[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(), };
508
509 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 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: _, svga_firmware: _, },
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: _, uefi_config:
708 UefiConfig {
709 secure_boot_enabled: _, secure_boot_template: _, 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: _, isolation: _, uefi_config: _, openhcl_config,
745 },
746 ) => {
747 let OpenHclConfig {
748 vtl2_nvme_boot: _, vmbus_redirect: _, 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 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 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 Vtl2BaseAddressType::File
803 } else {
804 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 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, 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(), };
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 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}