1use super::PetriVmConfigOpenVmm;
8use super::PetriVmResourcesOpenVmm;
9use crate::Drive;
10use crate::EfiDiagnosticsLogLevel;
11use crate::Firmware;
12use crate::IsolationType;
13use crate::MemoryConfig;
14use crate::OpenHclConfig;
15use crate::PcieNvmeDrive;
16use crate::PetriLogSource;
17use crate::PetriVmConfig;
18use crate::PetriVmResources;
19use crate::PetriVmgsResource;
20use crate::ProcessorTopology;
21use crate::SecureBootTemplate;
22use crate::TpmConfig;
23use crate::UefiConfig;
24use crate::VmbusStorageType;
25use crate::linux_direct_serial_agent::LinuxDirectSerialAgent;
26
27use crate::SIZE_1_MB;
28use crate::VmbusStorageController;
29use crate::openvmm::memdiff_vmgs;
30use crate::openvmm::petri_disk_to_openvmm;
31use crate::vm::PetriVmProperties;
32use crate::vm::append_cmdline;
33use anyhow::Context;
34use framebuffer::FRAMEBUFFER_SIZE;
35use framebuffer::Framebuffer;
36use framebuffer::FramebufferAccess;
37use fs_err::File;
38use futures::StreamExt;
39use get_resources::crash::GuestCrashDeviceHandle;
40use get_resources::ged::FirmwareEvent;
41use guid::Guid;
42use hyperv_ic_resources::shutdown::ShutdownIcHandle;
43use ide_resources::GuestMedia;
44use ide_resources::IdeDeviceConfig;
45use mesh_process::Mesh;
46use nvme_resources::NamespaceDefinition;
47use nvme_resources::NvmeControllerHandle;
48use openvmm_defs::config::Config;
49use openvmm_defs::config::DEFAULT_PCAT_BOOT_ORDER;
50use openvmm_defs::config::DeviceVtl;
51use openvmm_defs::config::HypervisorConfig;
52use openvmm_defs::config::LateMapVtl0MemoryPolicy;
53use openvmm_defs::config::LoadMode;
54use openvmm_defs::config::PcieDeviceConfig;
55use openvmm_defs::config::ProcessorTopologyConfig;
56use openvmm_defs::config::SerialInformation;
57use openvmm_defs::config::VmbusConfig;
58use openvmm_defs::config::VpciDeviceConfig;
59use openvmm_defs::config::Vtl2BaseAddressType;
60use openvmm_defs::config::Vtl2Config;
61use openvmm_pcat_locator::RomFileLocation;
62use pal_async::DefaultDriver;
63use pal_async::socket::PolledSocket;
64use pal_async::task::Spawn;
65use pal_async::task::Task;
66use petri_artifacts_common::tags::MachineArch;
67use petri_artifacts_core::ResolvedArtifact;
68use pipette_client::PIPETTE_VSOCK_PORT;
69use scsidisk_resources::SimpleScsiDiskHandle;
70use scsidisk_resources::SimpleScsiDvdHandle;
71use serial_16550_resources::ComPort;
72use serial_core::resources::DisconnectedSerialBackendHandle;
73use serial_socket::net::OpenSocketSerialConfig;
74use sparse_mmap::alloc_shared_memory;
75use std::collections::HashMap;
76use storvsp_resources::ScsiControllerHandle;
77use storvsp_resources::ScsiDeviceAndPath;
78use storvsp_resources::ScsiPath;
79use tempfile::TempPath;
80use tpm_resources::TpmDeviceHandle;
81use tpm_resources::TpmRegisterLayout;
82use uidevices_resources::SynthVideoHandle;
83use unix_socket::UnixListener;
84use unix_socket::UnixStream;
85use video_core::SharedFramebufferHandle;
86use virtio_resources::VirtioPciDeviceHandle;
87use virtio_resources::blk::VirtioBlkHandle;
88use virtio_resources::vsock::VirtioVsockHandle;
89use vm_manifest_builder::VmChipsetResult;
90use vm_manifest_builder::VmManifestBuilder;
91use vm_resource::IntoResource;
92use vm_resource::Resource;
93use vm_resource::kind::SerialBackendHandle;
94use vm_resource::kind::VmbusDeviceHandleKind;
95use vmbus_serial_resources::VmbusSerialDeviceHandle;
96use vmbus_serial_resources::VmbusSerialPort;
97use vmcore::non_volatile_store::resources::EphemeralNonVolatileStoreHandle;
98use vmgs_resources::GuestStateEncryptionPolicy;
99use vmgs_resources::VmgsFileHandle;
100use vmotherboard::ChipsetDeviceHandle;
101
102impl PetriVmConfigOpenVmm {
103 pub async fn new(
105 openvmm_path: &ResolvedArtifact,
106 petri_vm_config: PetriVmConfig,
107 resources: &PetriVmResources,
108 properties: PetriVmProperties,
109 ) -> anyhow::Result<Self> {
110 let PetriVmConfig {
111 name: _,
112 arch,
113 host_log_levels,
114 firmware,
115 memory,
116 proc_topology,
117 vmgs,
118 tpm: tpm_config,
119 vmbus_storage_controllers,
120 pcie_nvme_drives,
121 } = petri_vm_config;
122
123 tracing::debug!(?firmware, ?arch, "Petri VM firmware configuration");
124
125 let PetriVmResources { driver, log_source } = resources;
126
127 let mesh = Mesh::new("petri_mesh".to_string())?;
128
129 let setup = PetriVmConfigSetupCore {
130 arch,
131 firmware: &firmware,
132 driver,
133 logger: log_source,
134 vmgs: &vmgs,
135 tpm_config: tpm_config.as_ref(),
136 mesh: &mesh,
137 openvmm_path,
138 uses_pipette_as_init: properties.uses_pipette_as_init,
139 enable_serial: properties.enable_serial,
140 use_virtio_vsock: properties.use_virtio_vsock,
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 mut load_mode = setup.load_firmware()?;
163
164 if properties.uses_pipette_as_init {
168 if let LoadMode::Linux { initrd, .. } = &mut load_mode {
169 let prebuilt = properties
170 .prebuilt_initrd
171 .as_ref()
172 .expect("uses_pipette_as_init requires prebuilt_initrd");
173 let file = std::fs::File::open(prebuilt).with_context(|| {
174 format!("failed to open prebuilt initrd at {}", prebuilt.display())
175 })?;
176 *initrd = Some(file);
177 }
178 }
179
180 let (emulated_serial_config, log_stream_tasks, linux_direct_serial_agent) =
181 if !properties.enable_serial {
182 ([None, None, None, None], Vec::new(), None)
184 } else {
185 let SerialData {
186 emulated_serial_config,
187 serial_tasks,
188 linux_direct_serial_agent,
189 } = setup.configure_serial(log_source)?;
190 (
191 emulated_serial_config,
192 serial_tasks,
193 linux_direct_serial_agent,
194 )
195 };
196 let mut emulated_serial_config = emulated_serial_config;
197
198 let (video_dev, framebuffer, framebuffer_view) = match setup.config_video()? {
199 Some((v, fb, fba)) => {
200 chipset = chipset.with_framebuffer();
201 (Some(v), Some(fb), Some(fba.view()?))
202 }
203 None => (None, None, None),
204 };
205
206 let ide_disks = ide_controllers_to_openvmm(firmware.ide_controllers()).await?;
207 let (mut vmbus_devices, vpci_devices) =
208 vmbus_storage_controllers_to_openvmm(&vmbus_storage_controllers).await?;
209
210 let mut pcie_devices = Vec::new();
211 for PcieNvmeDrive {
212 port_name,
213 nsid,
214 drive: Drive { disk, .. },
215 } in pcie_nvme_drives
216 {
217 let disk = disk.ok_or_else(|| {
218 anyhow::anyhow!(
219 "missing disk for PCIe NVMe drive on port '{port_name}' (nsid {nsid})"
220 )
221 })?;
222 let disk = petri_disk_to_openvmm(&disk).await?;
223 pcie_devices.push(PcieDeviceConfig {
224 port_name,
225 resource: NvmeControllerHandle {
226 subsystem_id: guid::guid!("a1b2c3d4-e5f6-7890-abcd-ef0123456789"),
227 max_io_queues: 64,
228 msix_count: 64,
229 namespaces: vec![NamespaceDefinition {
230 nsid,
231 read_only: false,
232 disk,
233 }],
234 requests: None,
235 }
236 .into_resource(),
237 });
238 }
239
240 let (firmware_event_send, firmware_event_recv) = mesh::mpsc_channel();
241
242 let make_vsock_listener = || -> anyhow::Result<(UnixListener, TempPath)> {
243 Ok(tempfile::Builder::new()
244 .make(|path| UnixListener::bind(path))?
245 .into_parts())
246 };
247
248 let (with_vtl2, vtl2_vmbus, ged, ged_send, vtl2_vsock_path) = if firmware.is_openhcl() {
249 let (ged, ged_send) = setup
250 .config_openhcl_vmbus_devices(
251 &mut emulated_serial_config,
252 &mut vmbus_devices,
253 &firmware_event_send,
254 framebuffer.is_some(),
255 )
256 .await?;
257
258 let late_map_vtl0_memory = match load_mode {
259 LoadMode::Igvm {
260 vtl2_base_address: Vtl2BaseAddressType::Vtl2Allocate { .. },
261 ..
262 } => {
263 None
265 }
266 _ => Some(LateMapVtl0MemoryPolicy::InjectException),
267 };
268
269 let (vtl2_vsock_listener, vtl2_vsock_path) = make_vsock_listener()?;
270 (
271 Some(Vtl2Config {
272 vtl0_alias_map: false, late_map_vtl0_memory,
274 }),
275 Some(VmbusConfig {
276 vsock_listener: Some(vtl2_vsock_listener),
277 vsock_path: Some(vtl2_vsock_path.to_string_lossy().into_owned()),
278 vmbus_max_version: None,
279 vtl2_redirect: false,
280 #[cfg(windows)]
281 vmbusproxy_handle: None,
282 }),
283 Some(ged),
284 Some(ged_send),
285 Some(vtl2_vsock_path),
286 )
287 } else {
288 (None, None, None, None, None)
289 };
290
291 if properties.enable_serial {
294 chipset = chipset.with_serial(emulated_serial_config);
295 if matches!(firmware, Firmware::LinuxDirect { .. }) && !properties.uses_pipette_as_init
299 {
300 chipset = chipset.with_serial_wait_for_rts();
301 }
302 }
303
304 let vga_firmware = match video_dev {
306 Some(VideoDevice::Vga(firmware)) => Some(firmware),
307 Some(VideoDevice::Synth(vtl, resource)) => {
308 vmbus_devices.push((vtl, resource));
309 None
310 }
311 None => None,
312 };
313
314 let (shutdown_ic_send, kvp_ic_send) = if !properties.minimal_mode {
316 let (shutdown_ic_send, shutdown_ic_recv) = mesh::channel();
317 vmbus_devices.push((
318 DeviceVtl::Vtl0,
319 ShutdownIcHandle {
320 recv: shutdown_ic_recv,
321 }
322 .into_resource(),
323 ));
324
325 let (kvp_ic_send, kvp_ic_recv) = mesh::channel();
326 vmbus_devices.push((
327 DeviceVtl::Vtl0,
328 hyperv_ic_resources::kvp::KvpIcHandle { recv: kvp_ic_recv }.into_resource(),
329 ));
330
331 vmbus_devices.push((
332 DeviceVtl::Vtl0,
333 hyperv_ic_resources::timesync::TimesyncIcHandle.into_resource(),
334 ));
335
336 (shutdown_ic_send, kvp_ic_send)
337 } else {
338 let (shutdown_ic_send, _) = mesh::channel();
343 let (kvp_ic_send, _) = mesh::channel();
344 (shutdown_ic_send, kvp_ic_send)
345 };
346
347 let (vsock_listener, vsock_path) = make_vsock_listener()?;
349 let mut vsock_listener = Some(vsock_listener);
350 let vsock_path_string = vsock_path.to_string_lossy();
351
352 let layout_config = chipset.layout_config();
353 let chipset = chipset
354 .build()
355 .context("failed to build chipset configuration")?;
356
357 let memory = {
358 let MemoryConfig {
359 startup_bytes,
360 dynamic_memory_range,
361 numa_mem_sizes,
362 } = memory;
363
364 if dynamic_memory_range.is_some() {
365 anyhow::bail!("dynamic memory not supported in OpenVMM");
366 }
367
368 let mem_size = if let Some(ref sizes) = numa_mem_sizes {
369 sizes
370 .iter()
371 .try_fold(0u64, |acc, &s| acc.checked_add(s))
372 .context("numa memory sizes overflow")?
373 } else {
374 startup_bytes
375 };
376
377 openvmm_defs::config::MemoryConfig {
378 mem_size,
379 prefetch_memory: false,
380 private_memory: false,
381 transparent_hugepages: false,
382 hugepages: false,
383 hugepage_size: None,
384 numa_mem_sizes,
385 }
386 };
387
388 let processor_topology = {
389 let ProcessorTopology {
390 vp_count,
391 enable_smt,
392 vps_per_socket,
393 apic_mode,
394 } = proc_topology;
395
396 ProcessorTopologyConfig {
397 proc_count: vp_count,
398 vps_per_socket,
399 enable_smt,
400 arch: Some(match arch {
401 MachineArch::X86_64 => openvmm_defs::config::ArchTopologyConfig::X86(
402 openvmm_defs::config::X86TopologyConfig {
403 x2apic: match apic_mode {
404 None => openvmm_defs::config::X2ApicConfig::Auto,
405 Some(x) => match x {
406 crate::ApicMode::Xapic => {
407 openvmm_defs::config::X2ApicConfig::Unsupported
408 }
409 crate::ApicMode::X2apicSupported => {
410 openvmm_defs::config::X2ApicConfig::Supported
411 }
412 crate::ApicMode::X2apicEnabled => {
413 openvmm_defs::config::X2ApicConfig::Enabled
414 }
415 },
416 },
417 ..Default::default()
418 },
419 ),
420 MachineArch::Aarch64 => openvmm_defs::config::ArchTopologyConfig::Aarch64(
421 openvmm_defs::config::Aarch64TopologyConfig::default(),
422 ),
423 }),
424 }
425 };
426
427 let (secure_boot_enabled, custom_uefi_vars) = firmware.uefi_config().map_or_else(
428 || (false, Default::default()),
429 |c| {
430 (
431 c.secure_boot_enabled,
432 match (arch, c.secure_boot_template) {
433 (MachineArch::X86_64, Some(SecureBootTemplate::MicrosoftWindows)) => {
434 hyperv_secure_boot_templates::x64::microsoft_windows()
435 }
436 (
437 MachineArch::X86_64,
438 Some(SecureBootTemplate::MicrosoftUefiCertificateAuthority),
439 ) => hyperv_secure_boot_templates::x64::microsoft_uefi_ca(),
440 (MachineArch::Aarch64, Some(SecureBootTemplate::MicrosoftWindows)) => {
441 hyperv_secure_boot_templates::aarch64::microsoft_windows()
442 }
443 (
444 MachineArch::Aarch64,
445 Some(SecureBootTemplate::MicrosoftUefiCertificateAuthority),
446 ) => hyperv_secure_boot_templates::aarch64::microsoft_uefi_ca(),
447 (_, None) => Default::default(),
448 },
449 )
450 },
451 );
452
453 let vmgs = if firmware.is_openhcl() {
454 None
455 } else {
456 Some(memdiff_vmgs(&vmgs).await?)
457 };
458
459 let VmChipsetResult {
460 chipset,
461 mut chipset_devices,
462 pci_chipset_devices,
463 capabilities,
464 } = chipset;
465
466 if let Some(tpm) = setup.config_tpm().await? {
468 chipset_devices.push(tpm);
469 }
470
471 if properties.use_virtio_vsock {
473 pcie_devices.push(PcieDeviceConfig {
474 port_name: "s0rc0rp0".to_string(),
475 resource: VirtioPciDeviceHandle(
476 VirtioVsockHandle {
477 guest_cid: 0x3,
478 base_path: vsock_path_string.to_string(),
479 listener: vsock_listener.take().unwrap(),
480 }
481 .into_resource(),
482 )
483 .into_resource(),
484 });
485 }
486
487 let config = Config {
488 load_mode,
490 firmware_event_send: Some(firmware_event_send),
491
492 memory,
494 processor_topology,
495
496 chipset,
498 chipset_devices,
499 pci_chipset_devices,
500 chipset_capabilities: capabilities,
501 layout: layout_config,
502
503 hypervisor: HypervisorConfig {
505 with_hv: true,
506 with_vtl2,
507 with_isolation: match firmware.isolation() {
508 Some(IsolationType::Vbs) => Some(openvmm_defs::config::IsolationType::Vbs),
509 None => None,
510 _ => anyhow::bail!("unsupported isolation type"),
511 },
512 },
513 vmbus: Some(VmbusConfig {
514 vsock_listener,
517 vsock_path: (!properties.use_virtio_vsock).then(|| vsock_path_string.to_string()),
518 vmbus_max_version: None,
519 vtl2_redirect: firmware.openhcl_config().is_some_and(|c| c.vmbus_redirect),
520 #[cfg(windows)]
521 vmbusproxy_handle: None,
522 }),
523 vtl2_vmbus,
524
525 floppy_disks: vec![],
527 ide_disks,
528 pcie_root_complexes: vec![],
529 pcie_devices,
530 pcie_switches: vec![],
531 vpci_devices,
532 vmbus_devices,
533
534 framebuffer,
536 vga_firmware,
537
538 secure_boot_enabled,
539 custom_uefi_vars,
540 vmgs,
541
542 automatic_guest_reset: false,
544
545 #[cfg(windows)]
547 kernel_vmnics: vec![],
548 input: mesh::Receiver::new(),
549 vtl2_gfx: false,
550 virtio_devices: vec![],
551 #[cfg(windows)]
552 vpci_resources: vec![],
553 debugger_rpc: None,
554 generation_id_recv: None,
555 rtc_delta_milliseconds: 0,
556 efi_diagnostics_log_level: match firmware
557 .uefi_config()
558 .map(|c| c.efi_diagnostics_log_level)
559 .unwrap_or_default()
560 {
561 EfiDiagnosticsLogLevel::Default => {
562 openvmm_defs::config::EfiDiagnosticsLogLevelType::Default
563 }
564 EfiDiagnosticsLogLevel::Info => {
565 openvmm_defs::config::EfiDiagnosticsLogLevelType::Info
566 }
567 EfiDiagnosticsLogLevel::Full => {
568 openvmm_defs::config::EfiDiagnosticsLogLevelType::Full
569 }
570 },
571 };
572
573 let path = format!("{vsock_path_string}_{PIPETTE_VSOCK_PORT}");
575 let pipette_listener = PolledSocket::new(
576 driver,
577 UnixListener::bind(path).context("failed to bind to pipette listener")?,
578 )?;
579
580 let vtl2_pipette_listener = if let Some(vtl2_vmbus) = &config.vtl2_vmbus {
582 let path = vtl2_vmbus.vsock_path.as_ref().unwrap();
583 let path = format!("{path}_{PIPETTE_VSOCK_PORT}");
584 Some(PolledSocket::new(
585 driver,
586 UnixListener::bind(path).context("failed to bind to vtl2 pipette listener")?,
587 )?)
588 } else {
589 None
590 };
591
592 Ok(Self {
593 runtime_config: firmware.into_runtime_config(vmbus_storage_controllers),
594 arch,
595 host_log_levels,
596 config,
597 mesh,
598
599 resources: PetriVmResourcesOpenVmm {
600 log_stream_tasks,
601 firmware_event_recv,
602 shutdown_ic_send,
603 kvp_ic_send,
604 ged_send,
605 pipette_listener,
606 vtl2_pipette_listener,
607 linux_direct_serial_agent,
608 driver: driver.clone(),
609 output_dir: log_source.output_dir().to_owned(),
610 openvmm_path: openvmm_path.clone(),
611 vtl2_vsock_path,
612 _vsock_path: vsock_path,
613 properties,
614 },
615
616 openvmm_log_file: log_source.log_file("openvmm")?,
617
618 memory_backing_file: None,
619
620 ged,
621 framebuffer_view,
622 })
623 }
624}
625
626struct PetriVmConfigSetupCore<'a> {
627 arch: MachineArch,
628 firmware: &'a Firmware,
629 driver: &'a DefaultDriver,
630 logger: &'a PetriLogSource,
631 vmgs: &'a PetriVmgsResource,
632 tpm_config: Option<&'a TpmConfig>,
633 mesh: &'a Mesh,
634 openvmm_path: &'a ResolvedArtifact,
635 uses_pipette_as_init: bool,
636 enable_serial: bool,
637 use_virtio_vsock: bool,
638}
639
640struct SerialData {
641 emulated_serial_config: [Option<Resource<SerialBackendHandle>>; 4],
642 serial_tasks: Vec<Task<anyhow::Result<()>>>,
643 linux_direct_serial_agent: Option<LinuxDirectSerialAgent>,
644}
645
646enum VideoDevice {
647 Vga(RomFileLocation),
648 Synth(DeviceVtl, Resource<VmbusDeviceHandleKind>),
649}
650
651impl PetriVmConfigSetupCore<'_> {
652 fn configure_serial(&self, logger: &PetriLogSource) -> anyhow::Result<SerialData> {
653 let mut serial_tasks = Vec::new();
654
655 let serial0_log_file = logger.log_file(match self.firmware {
656 Firmware::LinuxDirect { .. } | Firmware::OpenhclLinuxDirect { .. } => "linux",
657 Firmware::Pcat { .. } | Firmware::OpenhclPcat { .. } => "pcat",
658 Firmware::Uefi { .. } | Firmware::OpenhclUefi { .. } => "uefi",
659 })?;
660
661 let (serial0_host, serial0) = self
662 .create_serial_stream()
663 .context("failed to create serial0 stream")?;
664 let (serial0_read, serial0_write) = serial0_host.split();
665 let serial0_task = self.driver.spawn(
666 "serial0-console",
667 crate::log_task(serial0_log_file, serial0_read, "serial0-console"),
668 );
669 serial_tasks.push(serial0_task);
670
671 let serial2 = if self.firmware.is_openhcl() {
672 let (serial2_host, serial2) = self
673 .create_serial_stream()
674 .context("failed to create serial2 stream")?;
675 let serial2_task = self.driver.spawn(
676 "serial2-openhcl",
677 crate::log_task(logger.log_file("openhcl")?, serial2_host, "serial2-openhcl"),
678 );
679 serial_tasks.push(serial2_task);
680 serial2
681 } else {
682 None
683 };
684
685 if self.firmware.is_linux_direct() && !self.uses_pipette_as_init {
686 let (serial1_host, serial1) = self.create_serial_stream()?;
689 let (serial1_read, _serial1_write) = serial1_host.split();
690 let linux_direct_serial_agent =
691 LinuxDirectSerialAgent::new(serial1_read, serial0_write);
692 Ok(SerialData {
693 emulated_serial_config: [serial0, serial1, serial2, None],
694 serial_tasks,
695 linux_direct_serial_agent: Some(linux_direct_serial_agent),
696 })
697 } else {
698 Ok(SerialData {
699 emulated_serial_config: [serial0, None, serial2, None],
700 serial_tasks,
701 linux_direct_serial_agent: None,
702 })
703 }
704 }
705
706 fn create_serial_stream(
707 &self,
708 ) -> anyhow::Result<(
709 PolledSocket<UnixStream>,
710 Option<Resource<SerialBackendHandle>>,
711 )> {
712 let (host_side, guest_side) = UnixStream::pair()?;
713 let host_side = PolledSocket::new(self.driver, host_side)?;
714 let serial = OpenSocketSerialConfig::from(guest_side).into_resource();
715 Ok((host_side, Some(serial)))
716 }
717
718 fn load_firmware(&self) -> anyhow::Result<LoadMode> {
719 const VIRTIO_VSOCK_BLACKLIST: &str = "initcall_blacklist=virtio_vsock_init";
727 let vsock_blacklist = if self.use_virtio_vsock {
728 "initcall_blacklist=hv_sock_init"
729 } else {
730 VIRTIO_VSOCK_BLACKLIST
731 };
732
733 Ok(match (self.arch, &self.firmware) {
734 (arch, Firmware::LinuxDirect { kernel, initrd }) => {
735 let console = match arch {
736 MachineArch::X86_64 => "console=ttyS0",
737 MachineArch::Aarch64 => "console=ttyAMA0 earlycon",
738 };
739 let kernel = File::open(kernel.clone())
740 .context("Failed to open kernel")?
741 .into();
742 let initrd = File::open(initrd.clone())
743 .context("Failed to open initrd")?
744 .into();
745
746 let init = if self.uses_pipette_as_init {
747 "/pipette"
748 } else {
749 "/bin/sh"
750 };
751
752 let serial_args = if self.enable_serial {
753 format!("{console} debug ")
754 } else {
755 String::new()
756 };
757
758 let cmdline = format!("{serial_args}panic=-1 rdinit={init} {vsock_blacklist}");
759
760 LoadMode::Linux {
761 kernel,
762 initrd: Some(initrd),
763 cmdline,
764 custom_dsdt: None,
765 enable_serial: self.enable_serial,
766 boot_mode: openvmm_defs::config::LinuxDirectBootMode::Acpi,
767 }
768 }
769 (
770 MachineArch::X86_64,
771 Firmware::Pcat {
772 bios_firmware: firmware,
773 guest: _, svga_firmware: _, ide_controllers: _,
776 },
777 ) => {
778 let firmware = openvmm_pcat_locator::find_pcat_bios(firmware.get())
779 .context("Failed to load packaged PCAT binary")?;
780 LoadMode::Pcat {
781 firmware,
782 boot_order: DEFAULT_PCAT_BOOT_ORDER,
783 }
784 }
785 (
786 _,
787 Firmware::Uefi {
788 uefi_firmware: firmware,
789 guest: _, uefi_config:
791 UefiConfig {
792 secure_boot_enabled: _, secure_boot_template: _, disable_frontpage,
795 default_boot_always_attempt,
796 enable_vpci_boot,
797 efi_diagnostics_log_level: _, },
799 },
800 ) => {
801 let firmware = File::open(firmware.clone())
802 .context("Failed to open uefi firmware file")?
803 .into();
804 LoadMode::Uefi {
805 firmware,
806 enable_debugging: false,
807 enable_memory_protections: false,
808 disable_frontpage: *disable_frontpage,
809 enable_tpm: self.tpm_config.is_some(),
810 enable_battery: false,
811 enable_serial: true,
812 enable_vpci_boot: *enable_vpci_boot,
813 uefi_console_mode: Some(openvmm_defs::config::UefiConsoleMode::Com1),
814 default_boot_always_attempt: *default_boot_always_attempt,
815 bios_guid: Guid::new_random(),
816 }
817 }
818 (
819 MachineArch::X86_64,
820 Firmware::OpenhclLinuxDirect {
821 igvm_path,
822 openhcl_config,
823 }
824 | Firmware::OpenhclUefi {
825 igvm_path,
826 guest: _, isolation: _, uefi_config: _, openhcl_config,
830 },
831 ) => {
832 let OpenHclConfig {
833 vmbus_redirect: _, custom_command_line: _,
835 log_levels: _,
836 vtl2_base_address_type,
837 vtl2_settings: _, } = openhcl_config;
839
840 let mut cmdline = Some(openhcl_config.command_line());
841
842 append_cmdline(&mut cmdline, "panic=-1 reboot=triple");
843
844 let isolated = match self.firmware {
845 Firmware::OpenhclLinuxDirect { .. } => {
846 append_cmdline(
850 &mut cmdline,
851 format!(
852 "UNDERHILL_SERIAL_WAIT_FOR_RTS=1 UNDERHILL_CMDLINE_APPEND=\"rdinit=/bin/sh {vsock_blacklist}\""
853 ),
854 );
855 false
856 }
857 Firmware::OpenhclUefi { isolation, .. } if isolation.is_some() => true,
858 _ => false,
859 };
860
861 if let Firmware::OpenhclUefi {
867 uefi_config:
868 UefiConfig {
869 default_boot_always_attempt,
870 secure_boot_enabled,
871 ..
872 },
873 ..
874 } = self.firmware
875 {
876 if !isolated
877 && !secure_boot_enabled
878 && self.tpm_config.is_none()
879 && !default_boot_always_attempt
880 {
881 append_cmdline(&mut cmdline, "HCL_DEFAULT_BOOT_ALWAYS_ATTEMPT=0");
882 }
883 }
884
885 let vtl2_base_address = vtl2_base_address_type.unwrap_or_else(|| {
886 if isolated {
887 Vtl2BaseAddressType::File
890 } else {
891 Vtl2BaseAddressType::Absolute(512 * SIZE_1_MB)
896 }
897 });
898
899 let file = File::open(igvm_path.clone())
900 .context("failed to open openhcl firmware file")?
901 .into();
902 LoadMode::Igvm {
903 file,
904 cmdline: cmdline.unwrap_or_default(),
905 vtl2_base_address,
906 com_serial: Some(SerialInformation {
907 io_port: ComPort::Com3.io_port(),
908 irq: ComPort::Com3.irq().into(),
909 }),
910 }
911 }
912 (a, f) => anyhow::bail!("Unsupported firmware {f:?} for arch {a:?}"),
913 })
914 }
915
916 async fn config_openhcl_vmbus_devices(
917 &self,
918 serial: &mut [Option<Resource<SerialBackendHandle>>],
919 devices: &mut impl Extend<(DeviceVtl, Resource<VmbusDeviceHandleKind>)>,
920 firmware_event_send: &mesh::Sender<FirmwareEvent>,
921 framebuffer: bool,
922 ) -> anyhow::Result<(
923 get_resources::ged::GuestEmulationDeviceHandle,
924 mesh::Sender<get_resources::ged::GuestEmulationRequest>,
925 )> {
926 let serial0 = serial[0].take();
927 devices.extend([(
928 DeviceVtl::Vtl2,
929 VmbusSerialDeviceHandle {
930 port: VmbusSerialPort::Com1,
931 backend: serial0.unwrap_or_else(|| DisconnectedSerialBackendHandle.into_resource()),
932 }
933 .into_resource(),
934 )]);
935 let serial1 = serial[1].take();
936 devices.extend([(
937 DeviceVtl::Vtl2,
938 VmbusSerialDeviceHandle {
939 port: VmbusSerialPort::Com2,
940 backend: serial1.unwrap_or_else(|| DisconnectedSerialBackendHandle.into_resource()),
941 }
942 .into_resource(),
943 )]);
944
945 let crash = spawn_dump_handler(self.driver, self.logger).into_resource();
946 devices.extend([(DeviceVtl::Vtl2, crash)]);
947
948 let (guest_request_send, guest_request_recv) = mesh::channel();
949
950 let (
951 UefiConfig {
952 secure_boot_enabled,
953 secure_boot_template,
954 disable_frontpage,
955 default_boot_always_attempt,
956 enable_vpci_boot,
957 efi_diagnostics_log_level,
958 },
959 OpenHclConfig { vmbus_redirect, .. },
960 ) = match self.firmware {
961 Firmware::OpenhclUefi {
962 uefi_config,
963 openhcl_config,
964 ..
965 } => (uefi_config, openhcl_config),
966 Firmware::OpenhclLinuxDirect { openhcl_config, .. } => {
967 (&UefiConfig::default(), openhcl_config)
968 }
969 _ => anyhow::bail!("not a supported openhcl firmware config"),
970 };
971
972 let test_gsp_by_id = matches!(
973 self.vmgs.encryption_policy(),
974 Some(GuestStateEncryptionPolicy::GspById(_))
975 );
976
977 let ged = get_resources::ged::GuestEmulationDeviceHandle {
979 firmware: get_resources::ged::GuestFirmwareConfig::Uefi {
980 firmware_debug: false,
981 disable_frontpage: *disable_frontpage,
982 enable_vpci_boot: *enable_vpci_boot,
983 console_mode: get_resources::ged::UefiConsoleMode::COM1,
984 default_boot_always_attempt: *default_boot_always_attempt,
985 },
986 com1: true,
987 com2: true,
988 serial_tx_only: false,
989 vmbus_redirection: *vmbus_redirect,
990 vtl2_settings: None, vmgs: memdiff_vmgs(self.vmgs).await?,
992 framebuffer: framebuffer.then(|| SharedFramebufferHandle.into_resource()),
993 guest_request_recv,
994 enable_tpm: self.tpm_config.is_some(),
995 firmware_event_send: Some(firmware_event_send.clone()),
996 secure_boot_enabled: *secure_boot_enabled,
997 secure_boot_template: match secure_boot_template {
998 Some(SecureBootTemplate::MicrosoftWindows) => {
999 get_resources::ged::GuestSecureBootTemplateType::MicrosoftWindows
1000 }
1001 Some(SecureBootTemplate::MicrosoftUefiCertificateAuthority) => {
1002 get_resources::ged::GuestSecureBootTemplateType::MicrosoftUefiCertificateAuthority
1003 }
1004 None => get_resources::ged::GuestSecureBootTemplateType::None,
1005 },
1006 enable_battery: false,
1007 no_persistent_secrets: self.tpm_config.as_ref().is_some_and(|c| c.no_persistent_secrets),
1008 igvm_attest_test_config: None,
1009 test_gsp_by_id,
1010 efi_diagnostics_log_level: match efi_diagnostics_log_level {
1011 EfiDiagnosticsLogLevel::Default => {
1012 get_resources::ged::EfiDiagnosticsLogLevelType::Default
1013 }
1014 EfiDiagnosticsLogLevel::Info => {
1015 get_resources::ged::EfiDiagnosticsLogLevelType::Info
1016 }
1017 EfiDiagnosticsLogLevel::Full => {
1018 get_resources::ged::EfiDiagnosticsLogLevelType::Full
1019 }
1020 },
1021 hv_sint_enabled: false,
1022 };
1023
1024 Ok((ged, guest_request_send))
1025 }
1026
1027 fn config_video(
1028 &self,
1029 ) -> anyhow::Result<Option<(VideoDevice, Framebuffer, FramebufferAccess)>> {
1030 if self.firmware.isolation().is_some() {
1031 return Ok(None);
1032 }
1033
1034 let video_dev = match self.firmware {
1035 Firmware::Pcat { svga_firmware, .. } | Firmware::OpenhclPcat { svga_firmware, .. } => {
1036 Some(VideoDevice::Vga(
1037 openvmm_pcat_locator::find_svga_bios(svga_firmware.get())
1038 .context("Failed to load VGA BIOS")?,
1039 ))
1040 }
1041 Firmware::Uefi { .. } | Firmware::OpenhclUefi { .. } => Some(VideoDevice::Synth(
1042 DeviceVtl::Vtl0,
1043 SynthVideoHandle {
1044 framebuffer: SharedFramebufferHandle.into_resource(),
1045 }
1046 .into_resource(),
1047 )),
1048 Firmware::OpenhclLinuxDirect { .. } | Firmware::LinuxDirect { .. } => None,
1049 };
1050
1051 Ok(if let Some(vdev) = video_dev {
1052 let vram =
1053 alloc_shared_memory(FRAMEBUFFER_SIZE, "vram").context("allocating framebuffer")?;
1054 let (fb, fba) = framebuffer::framebuffer(vram, FRAMEBUFFER_SIZE, 0)
1055 .context("creating framebuffer")?;
1056 Some((vdev, fb, fba))
1057 } else {
1058 None
1059 })
1060 }
1061
1062 async fn config_tpm(&self) -> anyhow::Result<Option<ChipsetDeviceHandle>> {
1063 if !self.firmware.is_openhcl()
1064 && let Some(TpmConfig {
1065 no_persistent_secrets,
1066 }) = self.tpm_config
1067 {
1068 let register_layout = match self.arch {
1069 MachineArch::X86_64 => TpmRegisterLayout::IoPort,
1070 MachineArch::Aarch64 => TpmRegisterLayout::Mmio,
1071 };
1072
1073 let (ppi_store, nvram_store) = if self.vmgs.disk().is_none() || *no_persistent_secrets {
1074 (
1075 EphemeralNonVolatileStoreHandle.into_resource(),
1076 EphemeralNonVolatileStoreHandle.into_resource(),
1077 )
1078 } else {
1079 (
1080 VmgsFileHandle::new(vmgs_format::FileId::TPM_PPI, true).into_resource(),
1081 VmgsFileHandle::new(vmgs_format::FileId::TPM_NVRAM, true).into_resource(),
1082 )
1083 };
1084
1085 Ok(Some(ChipsetDeviceHandle {
1086 name: "tpm".to_string(),
1087 resource: chipset_device_worker_defs::RemoteChipsetDeviceHandle {
1088 device: TpmDeviceHandle {
1089 ppi_store,
1090 nvram_store,
1091 refresh_tpm_seeds: false,
1092 ak_cert_type: tpm_resources::TpmAkCertTypeResource::None,
1093 register_layout,
1094 guest_secret_key: None,
1095 logger: None,
1096 is_confidential_vm: self.firmware.isolation().is_some(),
1097 bios_guid: Guid::ZERO,
1099 nvram_size: None,
1100 }
1101 .into_resource(),
1102 worker_host: self.make_device_worker("tpm").await?,
1103 }
1104 .into_resource(),
1105 }))
1106 } else {
1107 Ok(None)
1108 }
1109 }
1110
1111 async fn make_device_worker(&self, name: &str) -> anyhow::Result<mesh_worker::WorkerHost> {
1112 let (host, runner) = mesh_worker::worker_host();
1113 self.mesh
1114 .launch_host(
1115 mesh_process::ProcessConfig::new(name).process_name(self.openvmm_path),
1116 openvmm_defs::entrypoint::MeshHostParams { runner },
1117 )
1118 .await?;
1119 Ok(host)
1120 }
1121}
1122
1123fn spawn_dump_handler(driver: &DefaultDriver, logger: &PetriLogSource) -> GuestCrashDeviceHandle {
1124 let (send, mut recv) = mesh::channel();
1125 let handle = GuestCrashDeviceHandle {
1126 request_dump: send,
1127 max_dump_size: 256 * 1024 * 1024,
1128 };
1129 driver
1130 .spawn("openhcl-dump-handler", {
1131 let logger = logger.clone();
1132 let driver = driver.clone();
1133 async move {
1134 while let Some(rpc) = recv.next().await {
1135 rpc.handle_failable_sync(|done| {
1136 let (file, path) = logger.create_attachment("openhcl.core")?.into_parts();
1137 driver
1138 .spawn("crash-waiter", async move {
1139 let filename = path.file_name().unwrap().to_str().unwrap();
1140 if done.await.is_ok() {
1141 tracing::warn!(filename, "openhcl crash dump complete");
1142 } else {
1143 tracing::error!(
1144 filename,
1145 "openhcl crash dump incomplete, may be corrupted"
1146 );
1147 }
1148 })
1149 .detach();
1150 anyhow::Ok(file)
1151 })
1152 }
1153 }
1154 })
1155 .detach();
1156 handle
1157}
1158
1159async fn ide_controllers_to_openvmm(
1161 ide_controllers: Option<&[[Option<Drive>; 2]; 2]>,
1162) -> anyhow::Result<Vec<IdeDeviceConfig>> {
1163 let mut ide_disks = Vec::new();
1164
1165 if let Some(ide_controllers) = ide_controllers {
1166 for (controller_number, controller) in ide_controllers.iter().enumerate() {
1167 for (controller_location, drive) in controller.iter().enumerate() {
1168 if let Some(drive) = drive {
1169 if let Some(disk) = &drive.disk {
1170 let disk = petri_disk_to_openvmm(disk).await?;
1171 let guest_media = if drive.is_dvd {
1172 GuestMedia::Dvd(
1173 SimpleScsiDvdHandle {
1174 media: Some(disk),
1175 requests: None,
1176 }
1177 .into_resource(),
1178 )
1179 } else {
1180 GuestMedia::Disk {
1181 disk_type: disk,
1182 read_only: false,
1183 disk_parameters: None,
1184 }
1185 };
1186
1187 ide_disks.push(IdeDeviceConfig {
1188 path: ide_resources::IdePath {
1189 channel: controller_number as u8,
1190 drive: controller_location as u8,
1191 },
1192 guest_media,
1193 });
1194 }
1195 }
1196 }
1197 }
1198 }
1199
1200 Ok(ide_disks)
1201}
1202
1203async fn vmbus_storage_controllers_to_openvmm(
1205 vmbus_storage_controllers: &HashMap<Guid, VmbusStorageController>,
1206) -> anyhow::Result<(
1207 Vec<(DeviceVtl, Resource<VmbusDeviceHandleKind>)>,
1208 Vec<VpciDeviceConfig>,
1209)> {
1210 let mut vmbus_devices = Vec::new();
1211 let mut vpci_devices = Vec::new();
1212
1213 for (instance_id, controller) in vmbus_storage_controllers {
1215 let vtl = match controller.target_vtl {
1216 crate::Vtl::Vtl0 => DeviceVtl::Vtl0,
1217 crate::Vtl::Vtl1 => DeviceVtl::Vtl1,
1218 crate::Vtl::Vtl2 => DeviceVtl::Vtl2,
1219 };
1220 match controller.controller_type {
1221 VmbusStorageType::Scsi => {
1222 let mut devices = Vec::new();
1223 for (lun, Drive { disk, is_dvd }) in &controller.drives {
1224 if !*is_dvd && let Some(disk) = disk {
1225 devices.push(ScsiDeviceAndPath {
1226 path: ScsiPath {
1227 path: 0,
1228 target: 0,
1229 lun: (*lun).try_into().expect("invalid scsi lun"),
1230 },
1231 device: SimpleScsiDiskHandle {
1232 disk: petri_disk_to_openvmm(disk).await?,
1233 read_only: false,
1234 parameters: Default::default(),
1235 }
1236 .into_resource(),
1237 });
1238 } else {
1239 todo!("dvd ({}) or empty ({})", *is_dvd, disk.is_none())
1240 }
1241 }
1242
1243 vmbus_devices.push((
1244 vtl,
1245 ScsiControllerHandle {
1246 instance_id: *instance_id,
1247 max_sub_channel_count: 1,
1248 io_queue_depth: None,
1249 devices,
1250 requests: None,
1251 poll_mode_queue_depth: None,
1252 }
1253 .into_resource(),
1254 ));
1255 }
1256 VmbusStorageType::Nvme => {
1257 let mut namespaces = Vec::new();
1258 for (nsid, Drive { disk, is_dvd }) in &controller.drives {
1259 if !*is_dvd && let Some(disk) = disk {
1260 namespaces.push(NamespaceDefinition {
1261 nsid: *nsid,
1262 read_only: false,
1263 disk: petri_disk_to_openvmm(disk).await?,
1264 });
1265 } else {
1266 todo!("dvd ({}) or empty ({})", *is_dvd, disk.is_none())
1267 }
1268 }
1269
1270 vpci_devices.push(VpciDeviceConfig {
1271 vtl,
1272 instance_id: *instance_id,
1273 resource: NvmeControllerHandle {
1274 subsystem_id: *instance_id,
1275 max_io_queues: 64,
1276 msix_count: 64,
1277 namespaces,
1278 requests: None,
1279 }
1280 .into_resource(),
1281 });
1282 }
1283 VmbusStorageType::VirtioBlk => {
1284 const VIRTIO_BLK_INSTANCE_ID_TEMPLATE: Guid = Guid {
1287 data1: 0,
1288 data2: 0x1234,
1289 data3: 0x5678,
1290 data4: [0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67, 0x89],
1291 };
1292 for (lun, Drive { disk, is_dvd }) in &controller.drives {
1293 if *is_dvd {
1294 anyhow::bail!("dvd not supported with virtio-blk");
1295 }
1296 let Some(disk) = disk else {
1297 anyhow::bail!("empty drive not supported with virtio-blk");
1298 };
1299 let mut drive_id = VIRTIO_BLK_INSTANCE_ID_TEMPLATE;
1300 drive_id.data1 = *lun;
1301 vpci_devices.push(VpciDeviceConfig {
1302 vtl,
1303 instance_id: drive_id,
1304 resource: VirtioPciDeviceHandle(
1305 VirtioBlkHandle {
1306 disk: petri_disk_to_openvmm(disk).await?,
1307 read_only: false,
1308 }
1309 .into_resource(),
1310 )
1311 .into_resource(),
1312 });
1313 }
1314 }
1315 }
1316 }
1317
1318 Ok((vmbus_devices, vpci_devices))
1319}