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