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 hyperv_ic_resources::shutdown::ShutdownIcHandle;
49use ide_resources::GuestMedia;
50use ide_resources::IdeDeviceConfig;
51use mesh_process::Mesh;
52use nvme_resources::NamespaceDefinition;
53use nvme_resources::NvmeControllerHandle;
54use openvmm_defs::config::Config;
55use openvmm_defs::config::DEFAULT_MMIO_GAPS_AARCH64;
56use openvmm_defs::config::DEFAULT_MMIO_GAPS_AARCH64_WITH_VTL2;
57use openvmm_defs::config::DEFAULT_MMIO_GAPS_X86;
58use openvmm_defs::config::DEFAULT_MMIO_GAPS_X86_WITH_VTL2;
59use openvmm_defs::config::DEFAULT_PCAT_BOOT_ORDER;
60use openvmm_defs::config::DEFAULT_PCIE_ECAM_BASE;
61use openvmm_defs::config::DeviceVtl;
62use openvmm_defs::config::HypervisorConfig;
63use openvmm_defs::config::LateMapVtl0MemoryPolicy;
64use openvmm_defs::config::LoadMode;
65use openvmm_defs::config::ProcessorTopologyConfig;
66use openvmm_defs::config::SerialInformation;
67use openvmm_defs::config::VmbusConfig;
68use openvmm_defs::config::VpciDeviceConfig;
69use openvmm_defs::config::Vtl2BaseAddressType;
70use openvmm_defs::config::Vtl2Config;
71use openvmm_helpers::disk::open_disk_type;
72use openvmm_pcat_locator::RomFileLocation;
73use pal_async::DefaultDriver;
74use pal_async::socket::PolledSocket;
75use pal_async::task::Spawn;
76use pal_async::task::Task;
77use petri_artifacts_common::tags::MachineArch;
78use petri_artifacts_core::ResolvedArtifact;
79use pipette_client::PIPETTE_VSOCK_PORT;
80use scsidisk_resources::SimpleScsiDiskHandle;
81use scsidisk_resources::SimpleScsiDvdHandle;
82use serial_16550_resources::ComPort;
83use serial_core::resources::DisconnectedSerialBackendHandle;
84use serial_socket::net::OpenSocketSerialConfig;
85use sparse_mmap::alloc_shared_memory;
86use storvsp_resources::ScsiControllerHandle;
87use storvsp_resources::ScsiDeviceAndPath;
88use storvsp_resources::ScsiPath;
89use tempfile::TempPath;
90use tpm_resources::TpmDeviceHandle;
91use tpm_resources::TpmRegisterLayout;
92use uidevices_resources::SynthVideoHandle;
93use unix_socket::UnixListener;
94use unix_socket::UnixStream;
95use video_core::SharedFramebufferHandle;
96use vm_manifest_builder::VmChipsetResult;
97use vm_manifest_builder::VmManifestBuilder;
98use vm_resource::IntoResource;
99use vm_resource::Resource;
100use vm_resource::kind::DiskHandleKind;
101use vm_resource::kind::SerialBackendHandle;
102use vm_resource::kind::VmbusDeviceHandleKind;
103use vmbus_serial_resources::VmbusSerialDeviceHandle;
104use vmbus_serial_resources::VmbusSerialPort;
105use vmcore::non_volatile_store::resources::EphemeralNonVolatileStoreHandle;
106use vmgs_resources::GuestStateEncryptionPolicy;
107use vmgs_resources::VmgsFileHandle;
108use vmotherboard::ChipsetDeviceHandle;
109use vtl2_settings_proto::Vtl2Settings;
110
111impl PetriVmConfigOpenVmm {
112 pub async fn new(
114 openvmm_path: &ResolvedArtifact,
115 petri_vm_config: PetriVmConfig,
116 resources: &PetriVmResources,
117 ) -> anyhow::Result<Self> {
118 let PetriVmConfig {
119 name: _,
120 arch,
121 host_log_levels,
122 firmware,
123 memory,
124 proc_topology,
125 agent_image,
126 openhcl_agent_image,
127 vmgs,
128 boot_device_type,
129 tpm: tpm_config,
130 guest_crash_disk,
131 } = petri_vm_config;
132
133 let PetriVmResources { driver, log_source } = resources;
134
135 let mesh = Mesh::new("petri_mesh".to_string())?;
136
137 let setup = PetriVmConfigSetupCore {
138 arch,
139 firmware: &firmware,
140 driver,
141 logger: log_source,
142 vmgs: &vmgs,
143 boot_device_type,
144 tpm_config: tpm_config.as_ref(),
145 mesh: &mesh,
146 openvmm_path,
147 };
148
149 let mut chipset = VmManifestBuilder::new(
150 match firmware {
151 Firmware::LinuxDirect { .. } => {
152 vm_manifest_builder::BaseChipsetType::HyperVGen2LinuxDirect
153 }
154 Firmware::OpenhclLinuxDirect { .. } => {
155 vm_manifest_builder::BaseChipsetType::HclHost
156 }
157 Firmware::OpenhclUefi { .. } => vm_manifest_builder::BaseChipsetType::HclHost,
158 Firmware::Pcat { .. } => vm_manifest_builder::BaseChipsetType::HypervGen1,
159 Firmware::Uefi { .. } => vm_manifest_builder::BaseChipsetType::HypervGen2Uefi,
160 Firmware::OpenhclPcat { .. } => todo!("OpenVMM OpenHCL PCAT"),
161 },
162 match arch {
163 MachineArch::X86_64 => vm_manifest_builder::MachineArch::X86_64,
164 MachineArch::Aarch64 => vm_manifest_builder::MachineArch::Aarch64,
165 },
166 );
167
168 let load_mode = setup.load_firmware()?;
169
170 let SerialData {
171 mut emulated_serial_config,
172 serial_tasks: log_stream_tasks,
173 linux_direct_serial_agent,
174 } = setup.configure_serial(log_source)?;
175
176 let (video_dev, framebuffer, framebuffer_view) = match setup.config_video()? {
177 Some((v, fb, fba)) => {
178 chipset = chipset.with_framebuffer();
179 (Some(v), Some(fb), Some(fba.view()?))
180 }
181 None => (None, None, None),
182 };
183
184 let mut ide_disks = Vec::new();
185 let mut vpci_devices = Vec::new();
186 let mut vmbus_devices = Vec::new();
187
188 let (firmware_event_send, firmware_event_recv) = mesh::mpsc_channel();
189
190 let make_vsock_listener = || -> anyhow::Result<(UnixListener, TempPath)> {
191 Ok(tempfile::Builder::new()
192 .make(|path| UnixListener::bind(path))?
193 .into_parts())
194 };
195
196 let (with_vtl2, vtl2_vmbus, ged, ged_send, mut vtl2_settings, vtl2_vsock_path) =
197 if firmware.is_openhcl() {
198 let (ged, ged_send) = setup.config_openhcl_vmbus_devices(
199 &mut emulated_serial_config,
200 &mut vmbus_devices,
201 &firmware_event_send,
202 framebuffer.is_some(),
203 )?;
204
205 let late_map_vtl0_memory = match load_mode {
206 LoadMode::Igvm {
207 vtl2_base_address: Vtl2BaseAddressType::Vtl2Allocate { .. },
208 ..
209 } => {
210 None
212 }
213 _ => Some(LateMapVtl0MemoryPolicy::InjectException),
214 };
215
216 let (vtl2_vsock_listener, vtl2_vsock_path) = make_vsock_listener()?;
217 (
218 Some(Vtl2Config {
219 vtl0_alias_map: false, late_map_vtl0_memory,
221 }),
222 Some(VmbusConfig {
223 vsock_listener: Some(vtl2_vsock_listener),
224 vsock_path: Some(vtl2_vsock_path.to_string_lossy().into_owned()),
225 vmbus_max_version: None,
226 vtl2_redirect: false,
227 #[cfg(windows)]
228 vmbusproxy_handle: None,
229 }),
230 Some(ged),
231 Some(ged_send),
232 Some(crate::vm::default_vtl2_settings()),
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 openvmm_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 => openvmm_defs::config::ArchTopologyConfig::X86(
371 openvmm_defs::config::X86TopologyConfig {
372 x2apic: match apic_mode {
373 None => openvmm_defs::config::X2ApicConfig::Auto,
374 Some(x) => match x {
375 crate::ApicMode::Xapic => {
376 openvmm_defs::config::X2ApicConfig::Unsupported
377 }
378 crate::ApicMode::X2apicSupported => {
379 openvmm_defs::config::X2ApicConfig::Supported
380 }
381 crate::ApicMode::X2apicEnabled => {
382 openvmm_defs::config::X2ApicConfig::Enabled
383 }
384 },
385 },
386 ..Default::default()
387 },
388 ),
389 MachineArch::Aarch64 => openvmm_defs::config::ArchTopologyConfig::Aarch64(
390 openvmm_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().await? {
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(openvmm_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 host_log_levels,
533 config,
534 boot_device_type,
535 mesh,
536
537 resources: PetriVmResourcesOpenVmm {
538 log_stream_tasks,
539 firmware_event_recv,
540 shutdown_ic_send,
541 kvp_ic_send,
542 ged_send,
543 pipette_listener,
544 vtl2_pipette_listener,
545 linux_direct_serial_agent,
546 driver: driver.clone(),
547 output_dir: log_source.output_dir().to_owned(),
548 agent_image,
549 openhcl_agent_image,
550 openvmm_path: openvmm_path.clone(),
551 vtl2_vsock_path,
552 _vmbus_vsock_path: vmbus_vsock_path,
553 },
554
555 openvmm_log_file: log_source.log_file("openvmm")?,
556
557 petri_vtl0_scsi,
558 ged,
559 framebuffer_view,
560
561 vtl2_settings,
562 })
563 }
564}
565
566struct PetriVmConfigSetupCore<'a> {
567 arch: MachineArch,
568 firmware: &'a Firmware,
569 driver: &'a DefaultDriver,
570 logger: &'a PetriLogSource,
571 vmgs: &'a PetriVmgsResource,
572 boot_device_type: BootDeviceType,
573 tpm_config: Option<&'a TpmConfig>,
574 mesh: &'a Mesh,
575 openvmm_path: &'a ResolvedArtifact,
576}
577
578struct SerialData {
579 emulated_serial_config: [Option<Resource<SerialBackendHandle>>; 4],
580 serial_tasks: Vec<Task<anyhow::Result<()>>>,
581 linux_direct_serial_agent: Option<LinuxDirectSerialAgent>,
582}
583
584enum BootDisk {
585 Scsi(ScsiDeviceAndPath),
586 Vpci(VpciDeviceConfig),
587 Ide(IdeDeviceConfig),
588}
589
590enum VideoDevice {
591 Vga(RomFileLocation),
592 Synth(DeviceVtl, Resource<VmbusDeviceHandleKind>),
593}
594
595impl PetriVmConfigSetupCore<'_> {
596 fn configure_serial(&self, logger: &PetriLogSource) -> anyhow::Result<SerialData> {
597 let mut serial_tasks = Vec::new();
598
599 let serial0_log_file = logger.log_file(match self.firmware {
600 Firmware::LinuxDirect { .. } | Firmware::OpenhclLinuxDirect { .. } => "linux",
601 Firmware::Pcat { .. } | Firmware::OpenhclPcat { .. } => "pcat",
602 Firmware::Uefi { .. } | Firmware::OpenhclUefi { .. } => "uefi",
603 })?;
604
605 let (serial0_host, serial0) = self
606 .create_serial_stream()
607 .context("failed to create serial0 stream")?;
608 let (serial0_read, serial0_write) = serial0_host.split();
609 let serial0_task = self.driver.spawn(
610 "serial0-console",
611 crate::log_task(serial0_log_file, serial0_read, "serial0-console"),
612 );
613 serial_tasks.push(serial0_task);
614
615 let serial2 = if self.firmware.is_openhcl() {
616 let (serial2_host, serial2) = self
617 .create_serial_stream()
618 .context("failed to create serial2 stream")?;
619 let serial2_task = self.driver.spawn(
620 "serial2-openhcl",
621 crate::log_task(logger.log_file("openhcl")?, serial2_host, "serial2-openhcl"),
622 );
623 serial_tasks.push(serial2_task);
624 serial2
625 } else {
626 None
627 };
628
629 if self.firmware.is_linux_direct() {
630 let (serial1_host, serial1) = self.create_serial_stream()?;
631 let (serial1_read, _serial1_write) = serial1_host.split();
632 let linux_direct_serial_agent =
633 LinuxDirectSerialAgent::new(serial1_read, serial0_write);
634 Ok(SerialData {
635 emulated_serial_config: [serial0, serial1, serial2, None],
636 serial_tasks,
637 linux_direct_serial_agent: Some(linux_direct_serial_agent),
638 })
639 } else {
640 Ok(SerialData {
641 emulated_serial_config: [serial0, None, serial2, None],
642 serial_tasks,
643 linux_direct_serial_agent: None,
644 })
645 }
646 }
647
648 fn create_serial_stream(
649 &self,
650 ) -> anyhow::Result<(
651 PolledSocket<UnixStream>,
652 Option<Resource<SerialBackendHandle>>,
653 )> {
654 let (host_side, guest_side) = UnixStream::pair()?;
655 let host_side = PolledSocket::new(self.driver, host_side)?;
656 let serial = OpenSocketSerialConfig::from(guest_side).into_resource();
657 Ok((host_side, Some(serial)))
658 }
659
660 fn load_firmware(&self) -> anyhow::Result<LoadMode> {
661 Ok(match (self.arch, &self.firmware) {
662 (MachineArch::X86_64, Firmware::LinuxDirect { kernel, initrd }) => {
663 let kernel = File::open(kernel.clone())
664 .context("Failed to open kernel")?
665 .into();
666 let initrd = File::open(initrd.clone())
667 .context("Failed to open initrd")?
668 .into();
669 LoadMode::Linux {
670 kernel,
671 initrd: Some(initrd),
672 cmdline: "console=ttyS0 debug panic=-1 rdinit=/bin/sh".into(),
673 custom_dsdt: None,
674 enable_serial: true,
675 }
676 }
677 (MachineArch::Aarch64, Firmware::LinuxDirect { kernel, initrd }) => {
678 let kernel = File::open(kernel.clone())
679 .context("Failed to open kernel")?
680 .into();
681 let initrd = File::open(initrd.clone())
682 .context("Failed to open initrd")?
683 .into();
684 LoadMode::Linux {
685 kernel,
686 initrd: Some(initrd),
687 cmdline: "console=ttyAMA0 earlycon debug panic=-1 rdinit=/bin/sh".into(),
688 custom_dsdt: None,
689 enable_serial: true,
690 }
691 }
692 (
693 MachineArch::X86_64,
694 Firmware::Pcat {
695 bios_firmware: firmware,
696 guest: _, svga_firmware: _, },
699 ) => {
700 let firmware = openvmm_pcat_locator::find_pcat_bios(firmware.get())
701 .context("Failed to load packaged PCAT binary")?;
702 LoadMode::Pcat {
703 firmware,
704 boot_order: DEFAULT_PCAT_BOOT_ORDER,
705 }
706 }
707 (
708 _,
709 Firmware::Uefi {
710 uefi_firmware: firmware,
711 guest: _, uefi_config:
713 UefiConfig {
714 secure_boot_enabled: _, secure_boot_template: _, disable_frontpage,
717 default_boot_always_attempt,
718 },
719 },
720 ) => {
721 let firmware = File::open(firmware.clone())
722 .context("Failed to open uefi firmware file")?
723 .into();
724 LoadMode::Uefi {
725 firmware,
726 enable_debugging: false,
727 enable_memory_protections: false,
728 disable_frontpage: *disable_frontpage,
729 enable_tpm: self.tpm_config.is_some(),
730 enable_battery: false,
731 enable_serial: true,
732 enable_vpci_boot: matches!(self.boot_device_type, BootDeviceType::Nvme),
733 uefi_console_mode: Some(openvmm_defs::config::UefiConsoleMode::Com1),
734 default_boot_always_attempt: *default_boot_always_attempt,
735 bios_guid: Guid::new_random(),
736 }
737 }
738 (
739 MachineArch::X86_64,
740 Firmware::OpenhclLinuxDirect {
741 igvm_path,
742 openhcl_config,
743 }
744 | Firmware::OpenhclUefi {
745 igvm_path,
746 guest: _, isolation: _, uefi_config: _, openhcl_config,
750 },
751 ) => {
752 let OpenHclConfig {
753 vtl2_nvme_boot: _, vmbus_redirect: _, custom_command_line: _,
756 log_levels: _,
757 vtl2_base_address_type,
758 modify_vtl2_settings: _, } = openhcl_config;
760
761 let mut cmdline = Some(openhcl_config.command_line());
762
763 append_cmdline(&mut cmdline, "panic=-1 reboot=triple");
764
765 let isolated = match self.firmware {
766 Firmware::OpenhclLinuxDirect { .. } => {
767 append_cmdline(
771 &mut cmdline,
772 "UNDERHILL_SERIAL_WAIT_FOR_RTS=1 UNDERHILL_CMDLINE_APPEND=\"rdinit=/bin/sh\"",
773 );
774 false
775 }
776 Firmware::OpenhclUefi { isolation, .. } if isolation.is_some() => true,
777 _ => false,
778 };
779
780 if let Firmware::OpenhclUefi {
786 uefi_config:
787 UefiConfig {
788 default_boot_always_attempt,
789 secure_boot_enabled,
790 ..
791 },
792 ..
793 } = self.firmware
794 {
795 if !isolated
796 && !secure_boot_enabled
797 && self.tpm_config.is_none()
798 && !default_boot_always_attempt
799 {
800 append_cmdline(&mut cmdline, "HCL_DEFAULT_BOOT_ALWAYS_ATTEMPT=0");
801 }
802 }
803
804 let vtl2_base_address = vtl2_base_address_type.unwrap_or_else(|| {
805 if isolated {
806 Vtl2BaseAddressType::File
809 } else {
810 Vtl2BaseAddressType::Absolute(2 * SIZE_1_GB)
815 }
816 });
817
818 let file = File::open(igvm_path.clone())
819 .context("failed to open openhcl firmware file")?
820 .into();
821 LoadMode::Igvm {
822 file,
823 cmdline: cmdline.unwrap_or_default(),
824 vtl2_base_address,
825 com_serial: Some(SerialInformation {
826 io_port: ComPort::Com3.io_port(),
827 irq: ComPort::Com3.irq().into(),
828 }),
829 }
830 }
831 (a, f) => anyhow::bail!("Unsupported firmware {f:?} for arch {a:?}"),
832 })
833 }
834
835 fn load_boot_disk(
836 &self,
837 vtl2_settings: Option<&mut Vtl2Settings>,
838 ) -> anyhow::Result<Option<BootDisk>> {
839 let emulate_storage_in_openhcl = matches!(
840 self.firmware,
841 Firmware::OpenhclUefi {
842 openhcl_config: OpenHclConfig {
843 vtl2_nvme_boot: true,
844 ..
845 },
846 ..
847 }
848 );
849 enum Media {
850 Disk(Resource<DiskHandleKind>),
851 Dvd(Resource<DiskHandleKind>),
852 }
853 let media = match &self.firmware {
854 Firmware::LinuxDirect { .. }
855 | Firmware::OpenhclLinuxDirect { .. }
856 | Firmware::Uefi {
857 guest: UefiGuest::None,
858 ..
859 }
860 | Firmware::OpenhclUefi {
861 guest: UefiGuest::None,
862 ..
863 } => return Ok(None),
864 Firmware::Pcat { guest, .. } | Firmware::OpenhclPcat { guest, .. } => {
865 let disk_path = guest.artifact();
866 match guest {
867 PcatGuest::Vhd(_) => Media::Disk(memdiff_disk(disk_path.as_ref())?),
868 PcatGuest::Iso(_) => Media::Dvd(open_disk_type(disk_path.as_ref(), true)?),
869 }
870 }
871 Firmware::Uefi { guest, .. } | Firmware::OpenhclUefi { guest, .. } => {
872 let disk_path = guest.artifact();
873 Media::Disk(memdiff_disk(
874 disk_path.expect("not uefi guest none").as_ref(),
875 )?)
876 }
877 };
878
879 if emulate_storage_in_openhcl {
880 match self.boot_device_type {
881 BootDeviceType::None => {}
882 BootDeviceType::Ide => todo!("support IDE emulation testing"),
883 BootDeviceType::Nvme => todo!("support NVMe emulation testing"),
884 BootDeviceType::Scsi => vtl2_settings
885 .expect("openhcl config should have vtl2settings")
886 .dynamic
887 .as_mut()
888 .unwrap()
889 .storage_controllers
890 .push(
891 Vtl2StorageControllerBuilder::new(ControllerType::Scsi)
892 .with_instance_id(PARAVISOR_BOOT_NVME_INSTANCE)
893 .add_lun(
894 Vtl2LunBuilder::disk()
895 .with_location(BOOT_NVME_LUN)
896 .with_physical_device(Vtl2StorageBackingDeviceBuilder::new(
897 ControllerType::Nvme,
898 PARAVISOR_BOOT_NVME_INSTANCE,
899 BOOT_NVME_NSID,
900 )),
901 )
902 .build(),
903 ),
904 }
905 match media {
906 Media::Dvd(_) => todo!("support DVD to VTL2"),
907 Media::Disk(disk) => Ok(Some(BootDisk::Vpci(VpciDeviceConfig {
908 vtl: DeviceVtl::Vtl2,
909 instance_id: PARAVISOR_BOOT_NVME_INSTANCE,
910 resource: NvmeControllerHandle {
911 subsystem_id: PARAVISOR_BOOT_NVME_INSTANCE,
912 max_io_queues: 64,
913 msix_count: 64,
914 namespaces: vec![NamespaceDefinition {
915 nsid: BOOT_NVME_NSID,
916 disk,
917 read_only: false,
918 }],
919 }
920 .into_resource(),
921 }))),
922 }
923 } else {
924 match self.boot_device_type {
925 BootDeviceType::None => Ok(None),
926 BootDeviceType::Ide => {
927 let guest_media = match media {
928 Media::Disk(disk_type) => GuestMedia::Disk {
929 read_only: false,
930 disk_parameters: None,
931 disk_type,
932 },
933 Media::Dvd(media) => GuestMedia::Dvd(
934 SimpleScsiDvdHandle {
935 media: Some(media),
936 requests: None,
937 }
938 .into_resource(),
939 ),
940 };
941 Ok(Some(BootDisk::Ide(IdeDeviceConfig {
942 path: ide_resources::IdePath {
943 channel: 0,
944 drive: 0,
945 },
946 guest_media,
947 })))
948 }
949 BootDeviceType::Scsi => {
950 let disk = match media {
951 Media::Disk(disk) => disk,
952 Media::Dvd(_) => todo!("support SCSI DVD boot disks"),
953 };
954 Ok(Some(BootDisk::Scsi(ScsiDeviceAndPath {
955 path: ScsiPath {
956 path: 0,
957 target: 0,
958 lun: 0,
959 },
960 device: SimpleScsiDiskHandle {
961 read_only: false,
962 parameters: Default::default(),
963 disk,
964 }
965 .into_resource(),
966 })))
967 }
968 BootDeviceType::Nvme => {
969 let disk = match media {
970 Media::Disk(disk) => disk,
971 Media::Dvd(_) => anyhow::bail!("dvd not supported on nvme"),
972 };
973 Ok(Some(BootDisk::Vpci(VpciDeviceConfig {
974 vtl: DeviceVtl::Vtl0,
975 instance_id: BOOT_NVME_INSTANCE,
976 resource: NvmeControllerHandle {
977 subsystem_id: BOOT_NVME_INSTANCE,
978 max_io_queues: 64,
979 msix_count: 64,
980 namespaces: vec![NamespaceDefinition {
981 nsid: BOOT_NVME_NSID,
982 disk,
983 read_only: false,
984 }],
985 }
986 .into_resource(),
987 })))
988 }
989 }
990 }
991 }
992
993 fn config_openhcl_vmbus_devices(
994 &self,
995 serial: &mut [Option<Resource<SerialBackendHandle>>],
996 devices: &mut impl Extend<(DeviceVtl, Resource<VmbusDeviceHandleKind>)>,
997 firmware_event_send: &mesh::Sender<FirmwareEvent>,
998 framebuffer: bool,
999 ) -> anyhow::Result<(
1000 get_resources::ged::GuestEmulationDeviceHandle,
1001 mesh::Sender<get_resources::ged::GuestEmulationRequest>,
1002 )> {
1003 let serial0 = serial[0].take();
1004 devices.extend([(
1005 DeviceVtl::Vtl2,
1006 VmbusSerialDeviceHandle {
1007 port: VmbusSerialPort::Com1,
1008 backend: serial0.unwrap_or_else(|| DisconnectedSerialBackendHandle.into_resource()),
1009 }
1010 .into_resource(),
1011 )]);
1012 let serial1 = serial[1].take();
1013 devices.extend([(
1014 DeviceVtl::Vtl2,
1015 VmbusSerialDeviceHandle {
1016 port: VmbusSerialPort::Com2,
1017 backend: serial1.unwrap_or_else(|| DisconnectedSerialBackendHandle.into_resource()),
1018 }
1019 .into_resource(),
1020 )]);
1021
1022 let crash = spawn_dump_handler(self.driver, self.logger).into_resource();
1023 devices.extend([(DeviceVtl::Vtl2, crash)]);
1024
1025 let (guest_request_send, guest_request_recv) = mesh::channel();
1026
1027 let (
1028 UefiConfig {
1029 secure_boot_enabled,
1030 secure_boot_template,
1031 disable_frontpage,
1032 default_boot_always_attempt,
1033 },
1034 OpenHclConfig { vmbus_redirect, .. },
1035 ) = match self.firmware {
1036 Firmware::OpenhclUefi {
1037 uefi_config,
1038 openhcl_config,
1039 ..
1040 } => (uefi_config, openhcl_config),
1041 Firmware::OpenhclLinuxDirect { openhcl_config, .. } => {
1042 (&UefiConfig::default(), openhcl_config)
1043 }
1044 _ => anyhow::bail!("not a supported openhcl firmware config"),
1045 };
1046
1047 let test_gsp_by_id = self
1048 .vmgs
1049 .disk()
1050 .is_some_and(|x| matches!(x.encryption_policy, GuestStateEncryptionPolicy::GspById(_)));
1051
1052 let ged = get_resources::ged::GuestEmulationDeviceHandle {
1054 firmware: get_resources::ged::GuestFirmwareConfig::Uefi {
1055 firmware_debug: false,
1056 disable_frontpage: *disable_frontpage,
1057 enable_vpci_boot: matches!(self.boot_device_type, BootDeviceType::Nvme),
1058 console_mode: get_resources::ged::UefiConsoleMode::COM1,
1059 default_boot_always_attempt: *default_boot_always_attempt,
1060 },
1061 com1: true,
1062 com2: true,
1063 vmbus_redirection: *vmbus_redirect,
1064 vtl2_settings: None, vmgs: memdiff_vmgs(self.vmgs)?,
1066 framebuffer: framebuffer.then(|| SharedFramebufferHandle.into_resource()),
1067 guest_request_recv,
1068 enable_tpm: self.tpm_config.is_some(),
1069 firmware_event_send: Some(firmware_event_send.clone()),
1070 secure_boot_enabled: *secure_boot_enabled,
1071 secure_boot_template: match secure_boot_template {
1072 Some(SecureBootTemplate::MicrosoftWindows) => {
1073 get_resources::ged::GuestSecureBootTemplateType::MicrosoftWindows
1074 }
1075 Some(SecureBootTemplate::MicrosoftUefiCertificateAuthority) => {
1076 get_resources::ged::GuestSecureBootTemplateType::MicrosoftUefiCertificateAuthority
1077 }
1078 None => get_resources::ged::GuestSecureBootTemplateType::None,
1079 },
1080 enable_battery: false,
1081 no_persistent_secrets: self.tpm_config.as_ref().is_some_and(|c| c.no_persistent_secrets),
1082 igvm_attest_test_config: None,
1083 test_gsp_by_id,
1084 efi_diagnostics_log_level: Default::default(), };
1086
1087 Ok((ged, guest_request_send))
1088 }
1089
1090 fn config_video(
1091 &self,
1092 ) -> anyhow::Result<Option<(VideoDevice, Framebuffer, FramebufferAccess)>> {
1093 if self.firmware.isolation().is_some() {
1094 return Ok(None);
1095 }
1096
1097 let video_dev = match self.firmware {
1098 Firmware::Pcat { svga_firmware, .. } | Firmware::OpenhclPcat { svga_firmware, .. } => {
1099 Some(VideoDevice::Vga(
1100 openvmm_pcat_locator::find_svga_bios(svga_firmware.get())
1101 .context("Failed to load VGA BIOS")?,
1102 ))
1103 }
1104 Firmware::Uefi { .. } | Firmware::OpenhclUefi { .. } => Some(VideoDevice::Synth(
1105 DeviceVtl::Vtl0,
1106 SynthVideoHandle {
1107 framebuffer: SharedFramebufferHandle.into_resource(),
1108 }
1109 .into_resource(),
1110 )),
1111 Firmware::OpenhclLinuxDirect { .. } | Firmware::LinuxDirect { .. } => None,
1112 };
1113
1114 Ok(if let Some(vdev) = video_dev {
1115 let vram = alloc_shared_memory(FRAMEBUFFER_SIZE).context("allocating framebuffer")?;
1116 let (fb, fba) = framebuffer::framebuffer(vram, FRAMEBUFFER_SIZE, 0)
1117 .context("creating framebuffer")?;
1118 Some((vdev, fb, fba))
1119 } else {
1120 None
1121 })
1122 }
1123
1124 async fn config_tpm(&self) -> anyhow::Result<Option<ChipsetDeviceHandle>> {
1125 if !self.firmware.is_openhcl()
1126 && let Some(TpmConfig {
1127 no_persistent_secrets,
1128 }) = self.tpm_config
1129 {
1130 let register_layout = match self.arch {
1131 MachineArch::X86_64 => TpmRegisterLayout::IoPort,
1132 MachineArch::Aarch64 => TpmRegisterLayout::Mmio,
1133 };
1134
1135 let (ppi_store, nvram_store) = if self.vmgs.disk().is_none() || *no_persistent_secrets {
1136 (
1137 EphemeralNonVolatileStoreHandle.into_resource(),
1138 EphemeralNonVolatileStoreHandle.into_resource(),
1139 )
1140 } else {
1141 (
1142 VmgsFileHandle::new(vmgs_format::FileId::TPM_PPI, true).into_resource(),
1143 VmgsFileHandle::new(vmgs_format::FileId::TPM_NVRAM, true).into_resource(),
1144 )
1145 };
1146
1147 Ok(Some(ChipsetDeviceHandle {
1148 name: "tpm".to_string(),
1149 resource: chipset_device_worker_defs::RemoteChipsetDeviceHandle {
1150 device: TpmDeviceHandle {
1151 ppi_store,
1152 nvram_store,
1153 refresh_tpm_seeds: false,
1154 ak_cert_type: tpm_resources::TpmAkCertTypeResource::None,
1155 register_layout,
1156 guest_secret_key: None,
1157 logger: None,
1158 is_confidential_vm: self.firmware.isolation().is_some(),
1159 bios_guid: Guid::ZERO,
1161 }
1162 .into_resource(),
1163 worker_host: self.make_device_worker("tpm").await?,
1164 }
1165 .into_resource(),
1166 }))
1167 } else {
1168 Ok(None)
1169 }
1170 }
1171
1172 async fn make_device_worker(&self, name: &str) -> anyhow::Result<mesh_worker::WorkerHost> {
1173 let (host, runner) = mesh_worker::worker_host();
1174 self.mesh
1175 .launch_host(
1176 mesh_process::ProcessConfig::new(name).process_name(self.openvmm_path),
1177 openvmm_defs::entrypoint::MeshHostParams { runner },
1178 )
1179 .await?;
1180 Ok(host)
1181 }
1182}
1183
1184fn spawn_dump_handler(driver: &DefaultDriver, logger: &PetriLogSource) -> GuestCrashDeviceHandle {
1185 let (send, mut recv) = mesh::channel();
1186 let handle = GuestCrashDeviceHandle {
1187 request_dump: send,
1188 max_dump_size: 256 * 1024 * 1024,
1189 };
1190 driver
1191 .spawn("openhcl-dump-handler", {
1192 let logger = logger.clone();
1193 let driver = driver.clone();
1194 async move {
1195 while let Some(rpc) = recv.next().await {
1196 rpc.handle_failable_sync(|done| {
1197 let (file, path) = logger.create_attachment("openhcl.core")?.into_parts();
1198 driver
1199 .spawn("crash-waiter", async move {
1200 let filename = path.file_name().unwrap().to_str().unwrap();
1201 if done.await.is_ok() {
1202 tracing::warn!(filename, "openhcl crash dump complete");
1203 } else {
1204 tracing::error!(
1205 filename,
1206 "openhcl crash dump incomplete, may be corrupted"
1207 );
1208 }
1209 })
1210 .detach();
1211 anyhow::Ok(file)
1212 })
1213 }
1214 }
1215 })
1216 .detach();
1217 handle
1218}