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::SIZE_1_MB;
26use crate::VmbusStorageController;
27use crate::openvmm::memdiff_vmgs;
28use crate::openvmm::petri_disk_to_openvmm;
29use crate::vm::PetriVmProperties;
30use crate::vm::append_cmdline;
31use anyhow::Context;
32use framebuffer::FRAMEBUFFER_SIZE;
33use framebuffer::Framebuffer;
34use framebuffer::FramebufferAccess;
35use fs_err::File;
36use futures::StreamExt;
37use get_resources::crash::GuestCrashDeviceHandle;
38use get_resources::ged::FirmwareEvent;
39use guid::Guid;
40use hyperv_ic_resources::shutdown::ShutdownIcHandle;
41use ide_resources::GuestMedia;
42use ide_resources::IdeDeviceConfig;
43use mesh_process::Mesh;
44use nvme_resources::NamespaceDefinition;
45use nvme_resources::NvmeControllerHandle;
46use openvmm_defs::config::Config;
47use openvmm_defs::config::DEFAULT_MMIO_GAPS_AARCH64;
48use openvmm_defs::config::DEFAULT_MMIO_GAPS_AARCH64_WITH_VTL2;
49use openvmm_defs::config::DEFAULT_MMIO_GAPS_X86;
50use openvmm_defs::config::DEFAULT_MMIO_GAPS_X86_WITH_VTL2;
51use openvmm_defs::config::DEFAULT_PCAT_BOOT_ORDER;
52use openvmm_defs::config::DEFAULT_PCIE_ECAM_BASE;
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 vm_manifest_builder::VmChipsetResult;
89use vm_manifest_builder::VmManifestBuilder;
90use vm_resource::IntoResource;
91use vm_resource::Resource;
92use vm_resource::kind::SerialBackendHandle;
93use vm_resource::kind::VmbusDeviceHandleKind;
94use vmbus_serial_resources::VmbusSerialDeviceHandle;
95use vmbus_serial_resources::VmbusSerialPort;
96use vmcore::non_volatile_store::resources::EphemeralNonVolatileStoreHandle;
97use vmgs_resources::GuestStateEncryptionPolicy;
98use vmgs_resources::VmgsFileHandle;
99use vmotherboard::ChipsetDeviceHandle;
100
101impl PetriVmConfigOpenVmm {
102 pub async fn new(
104 openvmm_path: &ResolvedArtifact,
105 petri_vm_config: PetriVmConfig,
106 resources: &PetriVmResources,
107 properties: PetriVmProperties,
108 ) -> anyhow::Result<Self> {
109 let PetriVmConfig {
110 name: _,
111 arch,
112 host_log_levels,
113 firmware,
114 memory,
115 proc_topology,
116 vmgs,
117 tpm: tpm_config,
118 vmbus_storage_controllers,
119 } = petri_vm_config;
120
121 tracing::debug!(?firmware, ?arch, "Petri VM firmware configuration");
122
123 let PetriVmResources { driver, log_source } = resources;
124
125 let mesh = Mesh::new("petri_mesh".to_string())?;
126
127 let setup = PetriVmConfigSetupCore {
128 arch,
129 firmware: &firmware,
130 driver,
131 logger: log_source,
132 vmgs: &vmgs,
133 tpm_config: tpm_config.as_ref(),
134 mesh: &mesh,
135 openvmm_path,
136 };
137
138 let mut chipset = VmManifestBuilder::new(
139 match firmware {
140 Firmware::LinuxDirect { .. } => {
141 vm_manifest_builder::BaseChipsetType::HyperVGen2LinuxDirect
142 }
143 Firmware::OpenhclLinuxDirect { .. } => {
144 vm_manifest_builder::BaseChipsetType::HclHost
145 }
146 Firmware::OpenhclUefi { .. } => vm_manifest_builder::BaseChipsetType::HclHost,
147 Firmware::Pcat { .. } => vm_manifest_builder::BaseChipsetType::HypervGen1,
148 Firmware::Uefi { .. } => vm_manifest_builder::BaseChipsetType::HypervGen2Uefi,
149 Firmware::OpenhclPcat { .. } => todo!("OpenVMM OpenHCL PCAT"),
150 },
151 match arch {
152 MachineArch::X86_64 => vm_manifest_builder::MachineArch::X86_64,
153 MachineArch::Aarch64 => vm_manifest_builder::MachineArch::Aarch64,
154 },
155 );
156
157 let load_mode = setup.load_firmware()?;
158
159 let SerialData {
160 mut emulated_serial_config,
161 serial_tasks: log_stream_tasks,
162 linux_direct_serial_agent,
163 } = setup.configure_serial(log_source)?;
164
165 let (video_dev, framebuffer, framebuffer_view) = match setup.config_video()? {
166 Some((v, fb, fba)) => {
167 chipset = chipset.with_framebuffer();
168 (Some(v), Some(fb), Some(fba.view()?))
169 }
170 None => (None, None, None),
171 };
172
173 let ide_disks = ide_controllers_to_openvmm(firmware.ide_controllers())?;
174 let (mut vmbus_devices, vpci_devices) =
175 vmbus_storage_controllers_to_openvmm(&vmbus_storage_controllers)?;
176
177 let (firmware_event_send, firmware_event_recv) = mesh::mpsc_channel();
178
179 let make_vsock_listener = || -> anyhow::Result<(UnixListener, TempPath)> {
180 Ok(tempfile::Builder::new()
181 .make(|path| UnixListener::bind(path))?
182 .into_parts())
183 };
184
185 let (with_vtl2, vtl2_vmbus, ged, ged_send, vtl2_vsock_path) = if firmware.is_openhcl() {
186 let (ged, ged_send) = setup.config_openhcl_vmbus_devices(
187 &mut emulated_serial_config,
188 &mut vmbus_devices,
189 &firmware_event_send,
190 framebuffer.is_some(),
191 )?;
192
193 let late_map_vtl0_memory = match load_mode {
194 LoadMode::Igvm {
195 vtl2_base_address: Vtl2BaseAddressType::Vtl2Allocate { .. },
196 ..
197 } => {
198 None
200 }
201 _ => Some(LateMapVtl0MemoryPolicy::InjectException),
202 };
203
204 let (vtl2_vsock_listener, vtl2_vsock_path) = make_vsock_listener()?;
205 (
206 Some(Vtl2Config {
207 vtl0_alias_map: false, late_map_vtl0_memory,
209 }),
210 Some(VmbusConfig {
211 vsock_listener: Some(vtl2_vsock_listener),
212 vsock_path: Some(vtl2_vsock_path.to_string_lossy().into_owned()),
213 vmbus_max_version: None,
214 vtl2_redirect: false,
215 #[cfg(windows)]
216 vmbusproxy_handle: None,
217 }),
218 Some(ged),
219 Some(ged_send),
220 Some(vtl2_vsock_path),
221 )
222 } else {
223 (None, None, None, None, None)
224 };
225
226 chipset = chipset.with_serial(emulated_serial_config);
229 if matches!(firmware, Firmware::LinuxDirect { .. }) {
233 chipset = chipset.with_serial_wait_for_rts();
234 }
235
236 let vga_firmware = match video_dev {
238 Some(VideoDevice::Vga(firmware)) => Some(firmware),
239 Some(VideoDevice::Synth(vtl, resource)) => {
240 vmbus_devices.push((vtl, resource));
241 None
242 }
243 None => None,
244 };
245
246 let (shutdown_ic_send, shutdown_ic_recv) = mesh::channel();
248 vmbus_devices.push((
249 DeviceVtl::Vtl0,
250 ShutdownIcHandle {
251 recv: shutdown_ic_recv,
252 }
253 .into_resource(),
254 ));
255
256 let (kvp_ic_send, kvp_ic_recv) = mesh::channel();
258 vmbus_devices.push((
259 DeviceVtl::Vtl0,
260 hyperv_ic_resources::kvp::KvpIcHandle { recv: kvp_ic_recv }.into_resource(),
261 ));
262
263 vmbus_devices.push((
265 DeviceVtl::Vtl0,
266 hyperv_ic_resources::timesync::TimesyncIcHandle.into_resource(),
267 ));
268
269 let (vmbus_vsock_listener, vmbus_vsock_path) = make_vsock_listener()?;
271
272 let chipset = chipset
273 .build()
274 .context("failed to build chipset configuration")?;
275
276 let memory = {
277 let MemoryConfig {
278 startup_bytes,
279 dynamic_memory_range,
280 } = memory;
281
282 if dynamic_memory_range.is_some() {
283 anyhow::bail!("dynamic memory not supported in OpenVMM");
284 }
285
286 openvmm_defs::config::MemoryConfig {
287 mem_size: startup_bytes,
288 mmio_gaps: if firmware.is_openhcl() {
289 match arch {
290 MachineArch::X86_64 => DEFAULT_MMIO_GAPS_X86_WITH_VTL2.into(),
291 MachineArch::Aarch64 => DEFAULT_MMIO_GAPS_AARCH64_WITH_VTL2.into(),
292 }
293 } else {
294 match arch {
295 MachineArch::X86_64 => DEFAULT_MMIO_GAPS_X86.into(),
296 MachineArch::Aarch64 => DEFAULT_MMIO_GAPS_AARCH64.into(),
297 }
298 },
299 prefetch_memory: false,
300 pcie_ecam_base: DEFAULT_PCIE_ECAM_BASE,
301 }
302 };
303
304 let processor_topology = {
305 let ProcessorTopology {
306 vp_count,
307 enable_smt,
308 vps_per_socket,
309 apic_mode,
310 } = proc_topology;
311
312 ProcessorTopologyConfig {
313 proc_count: vp_count,
314 vps_per_socket,
315 enable_smt,
316 arch: Some(match arch {
317 MachineArch::X86_64 => openvmm_defs::config::ArchTopologyConfig::X86(
318 openvmm_defs::config::X86TopologyConfig {
319 x2apic: match apic_mode {
320 None => openvmm_defs::config::X2ApicConfig::Auto,
321 Some(x) => match x {
322 crate::ApicMode::Xapic => {
323 openvmm_defs::config::X2ApicConfig::Unsupported
324 }
325 crate::ApicMode::X2apicSupported => {
326 openvmm_defs::config::X2ApicConfig::Supported
327 }
328 crate::ApicMode::X2apicEnabled => {
329 openvmm_defs::config::X2ApicConfig::Enabled
330 }
331 },
332 },
333 ..Default::default()
334 },
335 ),
336 MachineArch::Aarch64 => openvmm_defs::config::ArchTopologyConfig::Aarch64(
337 openvmm_defs::config::Aarch64TopologyConfig::default(),
338 ),
339 }),
340 }
341 };
342
343 let (secure_boot_enabled, custom_uefi_vars) = firmware.uefi_config().map_or_else(
344 || (false, Default::default()),
345 |c| {
346 (
347 c.secure_boot_enabled,
348 match (arch, c.secure_boot_template) {
349 (MachineArch::X86_64, Some(SecureBootTemplate::MicrosoftWindows)) => {
350 hyperv_secure_boot_templates::x64::microsoft_windows()
351 }
352 (
353 MachineArch::X86_64,
354 Some(SecureBootTemplate::MicrosoftUefiCertificateAuthority),
355 ) => hyperv_secure_boot_templates::x64::microsoft_uefi_ca(),
356 (MachineArch::Aarch64, Some(SecureBootTemplate::MicrosoftWindows)) => {
357 hyperv_secure_boot_templates::aarch64::microsoft_windows()
358 }
359 (
360 MachineArch::Aarch64,
361 Some(SecureBootTemplate::MicrosoftUefiCertificateAuthority),
362 ) => hyperv_secure_boot_templates::aarch64::microsoft_uefi_ca(),
363 (_, None) => Default::default(),
364 },
365 )
366 },
367 );
368
369 let vmgs = if firmware.is_openhcl() {
370 None
371 } else {
372 Some(memdiff_vmgs(&vmgs)?)
373 };
374
375 let VmChipsetResult {
376 chipset,
377 mut chipset_devices,
378 } = chipset;
379
380 if let Some(tpm) = setup.config_tpm().await? {
382 chipset_devices.push(tpm);
383 }
384
385 let config = Config {
386 load_mode,
388 firmware_event_send: Some(firmware_event_send),
389
390 memory,
392 processor_topology,
393
394 chipset,
396 chipset_devices,
397
398 hypervisor: HypervisorConfig {
400 with_hv: true,
401 user_mode_hv_enlightenments: false,
402 user_mode_apic: false,
403 with_vtl2,
404 with_isolation: match firmware.isolation() {
405 Some(IsolationType::Vbs) => Some(openvmm_defs::config::IsolationType::Vbs),
406 None => None,
407 _ => anyhow::bail!("unsupported isolation type"),
408 },
409 },
410 vmbus: Some(VmbusConfig {
411 vsock_listener: Some(vmbus_vsock_listener),
412 vsock_path: Some(vmbus_vsock_path.to_string_lossy().into_owned()),
413 vmbus_max_version: None,
414 vtl2_redirect: firmware.openhcl_config().is_some_and(|c| c.vmbus_redirect),
415 #[cfg(windows)]
416 vmbusproxy_handle: None,
417 }),
418 vtl2_vmbus,
419
420 floppy_disks: vec![],
422 ide_disks,
423 pcie_root_complexes: vec![],
424 pcie_devices: vec![],
425 pcie_switches: vec![],
426 vpci_devices,
427 vmbus_devices,
428
429 framebuffer,
431 vga_firmware,
432
433 secure_boot_enabled,
434 custom_uefi_vars,
435 vmgs,
436
437 automatic_guest_reset: false,
439
440 #[cfg(windows)]
442 kernel_vmnics: vec![],
443 input: mesh::Receiver::new(),
444 vtl2_gfx: false,
445 virtio_console_pci: false,
446 virtio_serial: None,
447 virtio_devices: vec![],
448 #[cfg(windows)]
449 vpci_resources: vec![],
450 debugger_rpc: None,
451 generation_id_recv: None,
452 rtc_delta_milliseconds: 0,
453 efi_diagnostics_log_level: Default::default(), };
455
456 let path = config.vmbus.as_ref().unwrap().vsock_path.as_ref().unwrap();
458 let path = format!("{path}_{PIPETTE_VSOCK_PORT}");
459 let pipette_listener = PolledSocket::new(
460 driver,
461 UnixListener::bind(path).context("failed to bind to pipette listener")?,
462 )?;
463
464 let vtl2_pipette_listener = if let Some(vtl2_vmbus) = &config.vtl2_vmbus {
466 let path = vtl2_vmbus.vsock_path.as_ref().unwrap();
467 let path = format!("{path}_{PIPETTE_VSOCK_PORT}");
468 Some(PolledSocket::new(
469 driver,
470 UnixListener::bind(path).context("failed to bind to vtl2 pipette listener")?,
471 )?)
472 } else {
473 None
474 };
475
476 Ok(Self {
477 runtime_config: firmware.into_runtime_config(vmbus_storage_controllers),
478 arch,
479 host_log_levels,
480 config,
481 mesh,
482
483 resources: PetriVmResourcesOpenVmm {
484 log_stream_tasks,
485 firmware_event_recv,
486 shutdown_ic_send,
487 kvp_ic_send,
488 ged_send,
489 pipette_listener,
490 vtl2_pipette_listener,
491 linux_direct_serial_agent,
492 driver: driver.clone(),
493 output_dir: log_source.output_dir().to_owned(),
494 openvmm_path: openvmm_path.clone(),
495 vtl2_vsock_path,
496 _vmbus_vsock_path: vmbus_vsock_path,
497 properties,
498 },
499
500 openvmm_log_file: log_source.log_file("openvmm")?,
501
502 ged,
503 framebuffer_view,
504 })
505 }
506}
507
508struct PetriVmConfigSetupCore<'a> {
509 arch: MachineArch,
510 firmware: &'a Firmware,
511 driver: &'a DefaultDriver,
512 logger: &'a PetriLogSource,
513 vmgs: &'a PetriVmgsResource,
514 tpm_config: Option<&'a TpmConfig>,
515 mesh: &'a Mesh,
516 openvmm_path: &'a ResolvedArtifact,
517}
518
519struct SerialData {
520 emulated_serial_config: [Option<Resource<SerialBackendHandle>>; 4],
521 serial_tasks: Vec<Task<anyhow::Result<()>>>,
522 linux_direct_serial_agent: Option<LinuxDirectSerialAgent>,
523}
524
525enum VideoDevice {
526 Vga(RomFileLocation),
527 Synth(DeviceVtl, Resource<VmbusDeviceHandleKind>),
528}
529
530impl PetriVmConfigSetupCore<'_> {
531 fn configure_serial(&self, logger: &PetriLogSource) -> anyhow::Result<SerialData> {
532 let mut serial_tasks = Vec::new();
533
534 let serial0_log_file = logger.log_file(match self.firmware {
535 Firmware::LinuxDirect { .. } | Firmware::OpenhclLinuxDirect { .. } => "linux",
536 Firmware::Pcat { .. } | Firmware::OpenhclPcat { .. } => "pcat",
537 Firmware::Uefi { .. } | Firmware::OpenhclUefi { .. } => "uefi",
538 })?;
539
540 let (serial0_host, serial0) = self
541 .create_serial_stream()
542 .context("failed to create serial0 stream")?;
543 let (serial0_read, serial0_write) = serial0_host.split();
544 let serial0_task = self.driver.spawn(
545 "serial0-console",
546 crate::log_task(serial0_log_file, serial0_read, "serial0-console"),
547 );
548 serial_tasks.push(serial0_task);
549
550 let serial2 = if self.firmware.is_openhcl() {
551 let (serial2_host, serial2) = self
552 .create_serial_stream()
553 .context("failed to create serial2 stream")?;
554 let serial2_task = self.driver.spawn(
555 "serial2-openhcl",
556 crate::log_task(logger.log_file("openhcl")?, serial2_host, "serial2-openhcl"),
557 );
558 serial_tasks.push(serial2_task);
559 serial2
560 } else {
561 None
562 };
563
564 if self.firmware.is_linux_direct() {
565 let (serial1_host, serial1) = self.create_serial_stream()?;
566 let (serial1_read, _serial1_write) = serial1_host.split();
567 let linux_direct_serial_agent =
568 LinuxDirectSerialAgent::new(serial1_read, serial0_write);
569 Ok(SerialData {
570 emulated_serial_config: [serial0, serial1, serial2, None],
571 serial_tasks,
572 linux_direct_serial_agent: Some(linux_direct_serial_agent),
573 })
574 } else {
575 Ok(SerialData {
576 emulated_serial_config: [serial0, None, serial2, None],
577 serial_tasks,
578 linux_direct_serial_agent: None,
579 })
580 }
581 }
582
583 fn create_serial_stream(
584 &self,
585 ) -> anyhow::Result<(
586 PolledSocket<UnixStream>,
587 Option<Resource<SerialBackendHandle>>,
588 )> {
589 let (host_side, guest_side) = UnixStream::pair()?;
590 let host_side = PolledSocket::new(self.driver, host_side)?;
591 let serial = OpenSocketSerialConfig::from(guest_side).into_resource();
592 Ok((host_side, Some(serial)))
593 }
594
595 fn load_firmware(&self) -> anyhow::Result<LoadMode> {
596 Ok(match (self.arch, &self.firmware) {
597 (MachineArch::X86_64, Firmware::LinuxDirect { kernel, initrd }) => {
598 let kernel = File::open(kernel.clone())
599 .context("Failed to open kernel")?
600 .into();
601 let initrd = File::open(initrd.clone())
602 .context("Failed to open initrd")?
603 .into();
604 LoadMode::Linux {
605 kernel,
606 initrd: Some(initrd),
607 cmdline: "console=ttyS0 debug panic=-1 rdinit=/bin/sh".into(),
608 custom_dsdt: None,
609 enable_serial: true,
610 }
611 }
612 (MachineArch::Aarch64, Firmware::LinuxDirect { kernel, initrd }) => {
613 let kernel = File::open(kernel.clone())
614 .context("Failed to open kernel")?
615 .into();
616 let initrd = File::open(initrd.clone())
617 .context("Failed to open initrd")?
618 .into();
619 LoadMode::Linux {
620 kernel,
621 initrd: Some(initrd),
622 cmdline: "console=ttyAMA0 earlycon debug panic=-1 rdinit=/bin/sh".into(),
623 custom_dsdt: None,
624 enable_serial: true,
625 }
626 }
627 (
628 MachineArch::X86_64,
629 Firmware::Pcat {
630 bios_firmware: firmware,
631 guest: _, svga_firmware: _, ide_controllers: _,
634 },
635 ) => {
636 let firmware = openvmm_pcat_locator::find_pcat_bios(firmware.get())
637 .context("Failed to load packaged PCAT binary")?;
638 LoadMode::Pcat {
639 firmware,
640 boot_order: DEFAULT_PCAT_BOOT_ORDER,
641 }
642 }
643 (
644 _,
645 Firmware::Uefi {
646 uefi_firmware: firmware,
647 guest: _, uefi_config:
649 UefiConfig {
650 secure_boot_enabled: _, secure_boot_template: _, disable_frontpage,
653 default_boot_always_attempt,
654 enable_vpci_boot,
655 },
656 },
657 ) => {
658 let firmware = File::open(firmware.clone())
659 .context("Failed to open uefi firmware file")?
660 .into();
661 LoadMode::Uefi {
662 firmware,
663 enable_debugging: false,
664 enable_memory_protections: false,
665 disable_frontpage: *disable_frontpage,
666 enable_tpm: self.tpm_config.is_some(),
667 enable_battery: false,
668 enable_serial: true,
669 enable_vpci_boot: *enable_vpci_boot,
670 uefi_console_mode: Some(openvmm_defs::config::UefiConsoleMode::Com1),
671 default_boot_always_attempt: *default_boot_always_attempt,
672 bios_guid: Guid::new_random(),
673 }
674 }
675 (
676 MachineArch::X86_64,
677 Firmware::OpenhclLinuxDirect {
678 igvm_path,
679 openhcl_config,
680 }
681 | Firmware::OpenhclUefi {
682 igvm_path,
683 guest: _, isolation: _, uefi_config: _, openhcl_config,
687 },
688 ) => {
689 let OpenHclConfig {
690 vmbus_redirect: _, custom_command_line: _,
692 log_levels: _,
693 vtl2_base_address_type,
694 vtl2_settings: _, } = openhcl_config;
696
697 let mut cmdline = Some(openhcl_config.command_line());
698
699 append_cmdline(&mut cmdline, "panic=-1 reboot=triple");
700
701 let isolated = match self.firmware {
702 Firmware::OpenhclLinuxDirect { .. } => {
703 append_cmdline(
707 &mut cmdline,
708 "UNDERHILL_SERIAL_WAIT_FOR_RTS=1 UNDERHILL_CMDLINE_APPEND=\"rdinit=/bin/sh\"",
709 );
710 false
711 }
712 Firmware::OpenhclUefi { isolation, .. } if isolation.is_some() => true,
713 _ => false,
714 };
715
716 if let Firmware::OpenhclUefi {
722 uefi_config:
723 UefiConfig {
724 default_boot_always_attempt,
725 secure_boot_enabled,
726 ..
727 },
728 ..
729 } = self.firmware
730 {
731 if !isolated
732 && !secure_boot_enabled
733 && self.tpm_config.is_none()
734 && !default_boot_always_attempt
735 {
736 append_cmdline(&mut cmdline, "HCL_DEFAULT_BOOT_ALWAYS_ATTEMPT=0");
737 }
738 }
739
740 let vtl2_base_address = vtl2_base_address_type.unwrap_or_else(|| {
741 if isolated {
742 Vtl2BaseAddressType::File
745 } else {
746 Vtl2BaseAddressType::Absolute(512 * SIZE_1_MB)
751 }
752 });
753
754 let file = File::open(igvm_path.clone())
755 .context("failed to open openhcl firmware file")?
756 .into();
757 LoadMode::Igvm {
758 file,
759 cmdline: cmdline.unwrap_or_default(),
760 vtl2_base_address,
761 com_serial: Some(SerialInformation {
762 io_port: ComPort::Com3.io_port(),
763 irq: ComPort::Com3.irq().into(),
764 }),
765 }
766 }
767 (a, f) => anyhow::bail!("Unsupported firmware {f:?} for arch {a:?}"),
768 })
769 }
770
771 fn config_openhcl_vmbus_devices(
772 &self,
773 serial: &mut [Option<Resource<SerialBackendHandle>>],
774 devices: &mut impl Extend<(DeviceVtl, Resource<VmbusDeviceHandleKind>)>,
775 firmware_event_send: &mesh::Sender<FirmwareEvent>,
776 framebuffer: bool,
777 ) -> anyhow::Result<(
778 get_resources::ged::GuestEmulationDeviceHandle,
779 mesh::Sender<get_resources::ged::GuestEmulationRequest>,
780 )> {
781 let serial0 = serial[0].take();
782 devices.extend([(
783 DeviceVtl::Vtl2,
784 VmbusSerialDeviceHandle {
785 port: VmbusSerialPort::Com1,
786 backend: serial0.unwrap_or_else(|| DisconnectedSerialBackendHandle.into_resource()),
787 }
788 .into_resource(),
789 )]);
790 let serial1 = serial[1].take();
791 devices.extend([(
792 DeviceVtl::Vtl2,
793 VmbusSerialDeviceHandle {
794 port: VmbusSerialPort::Com2,
795 backend: serial1.unwrap_or_else(|| DisconnectedSerialBackendHandle.into_resource()),
796 }
797 .into_resource(),
798 )]);
799
800 let crash = spawn_dump_handler(self.driver, self.logger).into_resource();
801 devices.extend([(DeviceVtl::Vtl2, crash)]);
802
803 let (guest_request_send, guest_request_recv) = mesh::channel();
804
805 let (
806 UefiConfig {
807 secure_boot_enabled,
808 secure_boot_template,
809 disable_frontpage,
810 default_boot_always_attempt,
811 enable_vpci_boot,
812 },
813 OpenHclConfig { vmbus_redirect, .. },
814 ) = match self.firmware {
815 Firmware::OpenhclUefi {
816 uefi_config,
817 openhcl_config,
818 ..
819 } => (uefi_config, openhcl_config),
820 Firmware::OpenhclLinuxDirect { openhcl_config, .. } => {
821 (&UefiConfig::default(), openhcl_config)
822 }
823 _ => anyhow::bail!("not a supported openhcl firmware config"),
824 };
825
826 let test_gsp_by_id = self
827 .vmgs
828 .disk()
829 .is_some_and(|x| matches!(x.encryption_policy, GuestStateEncryptionPolicy::GspById(_)));
830
831 let ged = get_resources::ged::GuestEmulationDeviceHandle {
833 firmware: get_resources::ged::GuestFirmwareConfig::Uefi {
834 firmware_debug: false,
835 disable_frontpage: *disable_frontpage,
836 enable_vpci_boot: *enable_vpci_boot,
837 console_mode: get_resources::ged::UefiConsoleMode::COM1,
838 default_boot_always_attempt: *default_boot_always_attempt,
839 },
840 com1: true,
841 com2: true,
842 serial_tx_only: false,
843 vmbus_redirection: *vmbus_redirect,
844 vtl2_settings: None, vmgs: memdiff_vmgs(self.vmgs)?,
846 framebuffer: framebuffer.then(|| SharedFramebufferHandle.into_resource()),
847 guest_request_recv,
848 enable_tpm: self.tpm_config.is_some(),
849 firmware_event_send: Some(firmware_event_send.clone()),
850 secure_boot_enabled: *secure_boot_enabled,
851 secure_boot_template: match secure_boot_template {
852 Some(SecureBootTemplate::MicrosoftWindows) => {
853 get_resources::ged::GuestSecureBootTemplateType::MicrosoftWindows
854 }
855 Some(SecureBootTemplate::MicrosoftUefiCertificateAuthority) => {
856 get_resources::ged::GuestSecureBootTemplateType::MicrosoftUefiCertificateAuthority
857 }
858 None => get_resources::ged::GuestSecureBootTemplateType::None,
859 },
860 enable_battery: false,
861 no_persistent_secrets: self.tpm_config.as_ref().is_some_and(|c| c.no_persistent_secrets),
862 igvm_attest_test_config: None,
863 test_gsp_by_id,
864 efi_diagnostics_log_level: Default::default(), hv_sint_enabled: false,
866 };
867
868 Ok((ged, guest_request_send))
869 }
870
871 fn config_video(
872 &self,
873 ) -> anyhow::Result<Option<(VideoDevice, Framebuffer, FramebufferAccess)>> {
874 if self.firmware.isolation().is_some() {
875 return Ok(None);
876 }
877
878 let video_dev = match self.firmware {
879 Firmware::Pcat { svga_firmware, .. } | Firmware::OpenhclPcat { svga_firmware, .. } => {
880 Some(VideoDevice::Vga(
881 openvmm_pcat_locator::find_svga_bios(svga_firmware.get())
882 .context("Failed to load VGA BIOS")?,
883 ))
884 }
885 Firmware::Uefi { .. } | Firmware::OpenhclUefi { .. } => Some(VideoDevice::Synth(
886 DeviceVtl::Vtl0,
887 SynthVideoHandle {
888 framebuffer: SharedFramebufferHandle.into_resource(),
889 }
890 .into_resource(),
891 )),
892 Firmware::OpenhclLinuxDirect { .. } | Firmware::LinuxDirect { .. } => None,
893 };
894
895 Ok(if let Some(vdev) = video_dev {
896 let vram = alloc_shared_memory(FRAMEBUFFER_SIZE).context("allocating framebuffer")?;
897 let (fb, fba) = framebuffer::framebuffer(vram, FRAMEBUFFER_SIZE, 0)
898 .context("creating framebuffer")?;
899 Some((vdev, fb, fba))
900 } else {
901 None
902 })
903 }
904
905 async fn config_tpm(&self) -> anyhow::Result<Option<ChipsetDeviceHandle>> {
906 if !self.firmware.is_openhcl()
907 && let Some(TpmConfig {
908 no_persistent_secrets,
909 }) = self.tpm_config
910 {
911 let register_layout = match self.arch {
912 MachineArch::X86_64 => TpmRegisterLayout::IoPort,
913 MachineArch::Aarch64 => TpmRegisterLayout::Mmio,
914 };
915
916 let (ppi_store, nvram_store) = if self.vmgs.disk().is_none() || *no_persistent_secrets {
917 (
918 EphemeralNonVolatileStoreHandle.into_resource(),
919 EphemeralNonVolatileStoreHandle.into_resource(),
920 )
921 } else {
922 (
923 VmgsFileHandle::new(vmgs_format::FileId::TPM_PPI, true).into_resource(),
924 VmgsFileHandle::new(vmgs_format::FileId::TPM_NVRAM, true).into_resource(),
925 )
926 };
927
928 Ok(Some(ChipsetDeviceHandle {
929 name: "tpm".to_string(),
930 resource: chipset_device_worker_defs::RemoteChipsetDeviceHandle {
931 device: TpmDeviceHandle {
932 ppi_store,
933 nvram_store,
934 refresh_tpm_seeds: false,
935 ak_cert_type: tpm_resources::TpmAkCertTypeResource::None,
936 register_layout,
937 guest_secret_key: None,
938 logger: None,
939 is_confidential_vm: self.firmware.isolation().is_some(),
940 bios_guid: Guid::ZERO,
942 }
943 .into_resource(),
944 worker_host: self.make_device_worker("tpm").await?,
945 }
946 .into_resource(),
947 }))
948 } else {
949 Ok(None)
950 }
951 }
952
953 async fn make_device_worker(&self, name: &str) -> anyhow::Result<mesh_worker::WorkerHost> {
954 let (host, runner) = mesh_worker::worker_host();
955 self.mesh
956 .launch_host(
957 mesh_process::ProcessConfig::new(name).process_name(self.openvmm_path),
958 openvmm_defs::entrypoint::MeshHostParams { runner },
959 )
960 .await?;
961 Ok(host)
962 }
963}
964
965fn spawn_dump_handler(driver: &DefaultDriver, logger: &PetriLogSource) -> GuestCrashDeviceHandle {
966 let (send, mut recv) = mesh::channel();
967 let handle = GuestCrashDeviceHandle {
968 request_dump: send,
969 max_dump_size: 256 * 1024 * 1024,
970 };
971 driver
972 .spawn("openhcl-dump-handler", {
973 let logger = logger.clone();
974 let driver = driver.clone();
975 async move {
976 while let Some(rpc) = recv.next().await {
977 rpc.handle_failable_sync(|done| {
978 let (file, path) = logger.create_attachment("openhcl.core")?.into_parts();
979 driver
980 .spawn("crash-waiter", async move {
981 let filename = path.file_name().unwrap().to_str().unwrap();
982 if done.await.is_ok() {
983 tracing::warn!(filename, "openhcl crash dump complete");
984 } else {
985 tracing::error!(
986 filename,
987 "openhcl crash dump incomplete, may be corrupted"
988 );
989 }
990 })
991 .detach();
992 anyhow::Ok(file)
993 })
994 }
995 }
996 })
997 .detach();
998 handle
999}
1000
1001fn ide_controllers_to_openvmm(
1003 ide_controllers: Option<&[[Option<Drive>; 2]; 2]>,
1004) -> anyhow::Result<Vec<IdeDeviceConfig>> {
1005 let mut ide_disks = Vec::new();
1006
1007 if let Some(ide_controllers) = ide_controllers {
1008 for (controller_number, controller) in ide_controllers.iter().enumerate() {
1009 for (controller_location, drive) in controller.iter().enumerate() {
1010 if let Some(drive) = drive {
1011 if let Some(disk) = &drive.disk {
1012 let disk = petri_disk_to_openvmm(disk)?;
1013 let guest_media = if drive.is_dvd {
1014 GuestMedia::Dvd(
1015 SimpleScsiDvdHandle {
1016 media: Some(disk),
1017 requests: None,
1018 }
1019 .into_resource(),
1020 )
1021 } else {
1022 GuestMedia::Disk {
1023 disk_type: disk,
1024 read_only: false,
1025 disk_parameters: None,
1026 }
1027 };
1028
1029 ide_disks.push(IdeDeviceConfig {
1030 path: ide_resources::IdePath {
1031 channel: controller_number as u8,
1032 drive: controller_location as u8,
1033 },
1034 guest_media,
1035 });
1036 }
1037 }
1038 }
1039 }
1040 }
1041
1042 Ok(ide_disks)
1043}
1044
1045fn vmbus_storage_controllers_to_openvmm(
1047 vmbus_storage_controllers: &HashMap<Guid, VmbusStorageController>,
1048) -> anyhow::Result<(
1049 Vec<(DeviceVtl, Resource<VmbusDeviceHandleKind>)>,
1050 Vec<VpciDeviceConfig>,
1051)> {
1052 let mut vmbus_devices = Vec::new();
1053 let mut vpci_devices = Vec::new();
1054
1055 for (instance_id, controller) in vmbus_storage_controllers {
1057 let vtl = match controller.target_vtl {
1058 crate::Vtl::Vtl0 => DeviceVtl::Vtl0,
1059 crate::Vtl::Vtl1 => DeviceVtl::Vtl1,
1060 crate::Vtl::Vtl2 => DeviceVtl::Vtl2,
1061 };
1062 match controller.controller_type {
1063 VmbusStorageType::Scsi => {
1064 let mut devices = Vec::new();
1065 for (lun, Drive { disk, is_dvd }) in &controller.drives {
1066 if !*is_dvd && let Some(disk) = disk {
1067 devices.push(ScsiDeviceAndPath {
1068 path: ScsiPath {
1069 path: 0,
1070 target: 0,
1071 lun: (*lun).try_into().expect("invalid scsi lun"),
1072 },
1073 device: SimpleScsiDiskHandle {
1074 disk: petri_disk_to_openvmm(disk)?,
1075 read_only: false,
1076 parameters: Default::default(),
1077 }
1078 .into_resource(),
1079 });
1080 } else {
1081 todo!("dvd ({}) or empty ({})", *is_dvd, disk.is_none())
1082 }
1083 }
1084
1085 vmbus_devices.push((
1086 vtl,
1087 ScsiControllerHandle {
1088 instance_id: *instance_id,
1089 max_sub_channel_count: 1,
1090 io_queue_depth: None,
1091 devices,
1092 requests: None,
1093 poll_mode_queue_depth: None,
1094 }
1095 .into_resource(),
1096 ));
1097 }
1098 VmbusStorageType::Nvme => {
1099 let mut namespaces = Vec::new();
1100 for (nsid, Drive { disk, is_dvd }) in &controller.drives {
1101 if !*is_dvd && let Some(disk) = disk {
1102 namespaces.push(NamespaceDefinition {
1103 nsid: *nsid,
1104 read_only: false,
1105 disk: petri_disk_to_openvmm(disk)?,
1106 });
1107 } else {
1108 todo!("dvd ({}) or empty ({})", *is_dvd, disk.is_none())
1109 }
1110 }
1111
1112 vpci_devices.push(VpciDeviceConfig {
1113 vtl,
1114 instance_id: *instance_id,
1115 resource: NvmeControllerHandle {
1116 subsystem_id: *instance_id,
1117 max_io_queues: 64,
1118 msix_count: 64,
1119 namespaces,
1120 requests: None,
1121 }
1122 .into_resource(),
1123 });
1124 }
1125 }
1126 }
1127
1128 Ok((vmbus_devices, vpci_devices))
1129}