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