1use super::BOOT_NVME_LUN;
8use super::BOOT_NVME_NSID;
9use super::PARAVISOR_BOOT_NVME_INSTANCE;
10use super::PetriVmConfigOpenVmm;
11use super::PetriVmResourcesOpenVmm;
12use super::SCSI_INSTANCE;
13use super::memdiff_disk;
14use crate::BootDeviceType;
15use crate::Firmware;
16use crate::IsolationType;
17use crate::MemoryConfig;
18use crate::OpenHclConfig;
19use crate::PcatGuest;
20use crate::PetriLogSource;
21use crate::PetriVmConfig;
22use crate::PetriVmResources;
23use crate::PetriVmgsResource;
24use crate::ProcessorTopology;
25use crate::SIZE_1_GB;
26use crate::SecureBootTemplate;
27use crate::UefiConfig;
28use crate::UefiGuest;
29use crate::linux_direct_serial_agent::LinuxDirectSerialAgent;
30use crate::openvmm::BOOT_NVME_INSTANCE;
31use crate::openvmm::memdiff_vmgs;
32use crate::vm::append_cmdline;
33use crate::vtl2_settings::ControllerType;
34use crate::vtl2_settings::Vtl2LunBuilder;
35use crate::vtl2_settings::Vtl2StorageBackingDeviceBuilder;
36use crate::vtl2_settings::Vtl2StorageControllerBuilder;
37use anyhow::Context;
38use framebuffer::FRAMEBUFFER_SIZE;
39use framebuffer::Framebuffer;
40use framebuffer::FramebufferAccess;
41use fs_err::File;
42use futures::StreamExt;
43use get_resources::crash::GuestCrashDeviceHandle;
44use get_resources::ged::FirmwareEvent;
45use guid::Guid;
46use hvlite_defs::config::Config;
47use hvlite_defs::config::DEFAULT_MMIO_GAPS_AARCH64;
48use hvlite_defs::config::DEFAULT_MMIO_GAPS_AARCH64_WITH_VTL2;
49use hvlite_defs::config::DEFAULT_MMIO_GAPS_X86;
50use hvlite_defs::config::DEFAULT_MMIO_GAPS_X86_WITH_VTL2;
51use hvlite_defs::config::DEFAULT_PCAT_BOOT_ORDER;
52use hvlite_defs::config::DEFAULT_PCIE_ECAM_BASE;
53use hvlite_defs::config::DeviceVtl;
54use hvlite_defs::config::HypervisorConfig;
55use hvlite_defs::config::LateMapVtl0MemoryPolicy;
56use hvlite_defs::config::LoadMode;
57use hvlite_defs::config::ProcessorTopologyConfig;
58use hvlite_defs::config::SerialInformation;
59use hvlite_defs::config::VmbusConfig;
60use hvlite_defs::config::VpciDeviceConfig;
61use hvlite_defs::config::Vtl2BaseAddressType;
62use hvlite_defs::config::Vtl2Config;
63use hvlite_helpers::disk::open_disk_type;
64use hvlite_pcat_locator::RomFileLocation;
65use hyperv_ic_resources::shutdown::ShutdownIcHandle;
66use ide_resources::GuestMedia;
67use ide_resources::IdeDeviceConfig;
68use nvme_resources::NamespaceDefinition;
69use nvme_resources::NvmeControllerHandle;
70use pal_async::DefaultDriver;
71use pal_async::socket::PolledSocket;
72use pal_async::task::Spawn;
73use pal_async::task::Task;
74use petri_artifacts_common::tags::MachineArch;
75use petri_artifacts_core::ResolvedArtifact;
76use pipette_client::PIPETTE_VSOCK_PORT;
77use scsidisk_resources::SimpleScsiDiskHandle;
78use scsidisk_resources::SimpleScsiDvdHandle;
79use serial_16550_resources::ComPort;
80use serial_core::resources::DisconnectedSerialBackendHandle;
81use serial_socket::net::OpenSocketSerialConfig;
82use sparse_mmap::alloc_shared_memory;
83use storvsp_resources::ScsiControllerHandle;
84use storvsp_resources::ScsiDeviceAndPath;
85use storvsp_resources::ScsiPath;
86use tempfile::TempPath;
87use uidevices_resources::SynthVideoHandle;
88use unix_socket::UnixListener;
89use unix_socket::UnixStream;
90use video_core::SharedFramebufferHandle;
91use vm_manifest_builder::VmManifestBuilder;
92use vm_resource::IntoResource;
93use vm_resource::Resource;
94use vm_resource::kind::DiskHandleKind;
95use vm_resource::kind::SerialBackendHandle;
96use vm_resource::kind::VmbusDeviceHandleKind;
97use vmbus_serial_resources::VmbusSerialDeviceHandle;
98use vmbus_serial_resources::VmbusSerialPort;
99use vmgs_resources::GuestStateEncryptionPolicy;
100use vtl2_settings_proto::Vtl2Settings;
101
102impl PetriVmConfigOpenVmm {
103 pub fn new(
105 openvmm_path: &ResolvedArtifact,
106 petri_vm_config: PetriVmConfig,
107 resources: &PetriVmResources,
108 ) -> anyhow::Result<Self> {
109 let PetriVmConfig {
110 name: _,
111 arch,
112 firmware,
113 memory,
114 proc_topology,
115 agent_image,
116 openhcl_agent_image,
117 vmgs,
118 boot_device_type,
119 tpm_state_persistence,
120 } = petri_vm_config;
121
122 let PetriVmResources { driver, log_source } = resources;
123
124 let setup = PetriVmConfigSetupCore {
125 arch,
126 firmware: &firmware,
127 driver,
128 logger: log_source,
129 vmgs: &vmgs,
130 boot_device_type,
131 };
132
133 let mut chipset = VmManifestBuilder::new(
134 match firmware {
135 Firmware::LinuxDirect { .. } => {
136 vm_manifest_builder::BaseChipsetType::HyperVGen2LinuxDirect
137 }
138 Firmware::OpenhclLinuxDirect { .. } => {
139 vm_manifest_builder::BaseChipsetType::HclHost
140 }
141 Firmware::OpenhclUefi { .. } => vm_manifest_builder::BaseChipsetType::HclHost,
142 Firmware::Pcat { .. } => vm_manifest_builder::BaseChipsetType::HypervGen1,
143 Firmware::Uefi { .. } => vm_manifest_builder::BaseChipsetType::HypervGen2Uefi,
144 Firmware::OpenhclPcat { .. } => todo!("OpenVMM OpenHCL PCAT"),
145 },
146 match arch {
147 MachineArch::X86_64 => vm_manifest_builder::MachineArch::X86_64,
148 MachineArch::Aarch64 => vm_manifest_builder::MachineArch::Aarch64,
149 },
150 );
151
152 let load_mode = setup.load_firmware()?;
153
154 let SerialData {
155 mut emulated_serial_config,
156 serial_tasks: log_stream_tasks,
157 linux_direct_serial_agent,
158 } = setup.configure_serial(log_source)?;
159
160 let (video_dev, framebuffer, framebuffer_view) = match setup.config_video()? {
161 Some((v, fb, fba)) => {
162 chipset = chipset.with_framebuffer();
163 (Some(v), Some(fb), Some(fba.view()?))
164 }
165 None => (None, None, None),
166 };
167
168 let mut devices = Vec::new();
169
170 let (firmware_event_send, firmware_event_recv) = mesh::mpsc_channel();
171
172 let make_vsock_listener = || -> anyhow::Result<(UnixListener, TempPath)> {
173 Ok(tempfile::Builder::new()
174 .make(|path| UnixListener::bind(path))?
175 .into_parts())
176 };
177
178 let (with_vtl2, vtl2_vmbus, ged, ged_send, mut vtl2_settings, vtl2_vsock_path) =
179 if firmware.is_openhcl() {
180 let (ged, ged_send) = setup.config_openhcl_vmbus_devices(
181 &mut emulated_serial_config,
182 &mut devices,
183 &firmware_event_send,
184 framebuffer.is_some(),
185 tpm_state_persistence,
186 )?;
187
188 let late_map_vtl0_memory = match load_mode {
189 LoadMode::Igvm {
190 vtl2_base_address: Vtl2BaseAddressType::Vtl2Allocate { .. },
191 ..
192 } => {
193 None
195 }
196 _ => Some(LateMapVtl0MemoryPolicy::InjectException),
197 };
198
199 let (vtl2_vsock_listener, vtl2_vsock_path) = make_vsock_listener()?;
200 (
201 Some(Vtl2Config {
202 vtl0_alias_map: false, late_map_vtl0_memory,
204 }),
205 Some(VmbusConfig {
206 vsock_listener: Some(vtl2_vsock_listener),
207 vsock_path: Some(vtl2_vsock_path.to_string_lossy().into_owned()),
208 vmbus_max_version: None,
209 vtl2_redirect: false,
210 #[cfg(windows)]
211 vmbusproxy_handle: None,
212 }),
213 Some(ged),
214 Some(ged_send),
215 Some(Vtl2Settings {
217 version: vtl2_settings_proto::vtl2_settings_base::Version::V1.into(),
218 dynamic: Some(Default::default()),
219 fixed: Some(Default::default()),
220 namespace_settings: Default::default(),
221 }),
222 Some(vtl2_vsock_path),
223 )
224 } else {
225 (None, None, None, None, None, None)
226 };
227
228 setup.load_boot_disk(&mut devices, vtl2_settings.as_mut())?;
229
230 chipset = chipset.with_serial(emulated_serial_config);
233 if matches!(firmware, Firmware::LinuxDirect { .. }) {
237 chipset = chipset.with_serial_wait_for_rts();
238 }
239
240 let mut vmbus_devices = Vec::new();
242 let mut ide_disks = Vec::new();
243 let floppy_disks = Vec::new();
244 let mut vpci_devices = Vec::new();
245 for d in devices {
246 match d {
247 Device::Vmbus(vtl, resource) => vmbus_devices.push((vtl, resource)),
248 Device::Vpci(c) => vpci_devices.push(c),
249 Device::Ide(c) => ide_disks.push(c),
250 }
251 }
252
253 let vga_firmware = match video_dev {
255 Some(VideoDevice::Vga(firmware)) => Some(firmware),
256 Some(VideoDevice::Synth(vtl, resource)) => {
257 vmbus_devices.push((vtl, resource));
258 None
259 }
260 None => None,
261 };
262
263 let (shutdown_ic_send, shutdown_ic_recv) = mesh::channel();
265 vmbus_devices.push((
266 DeviceVtl::Vtl0,
267 ShutdownIcHandle {
268 recv: shutdown_ic_recv,
269 }
270 .into_resource(),
271 ));
272
273 let (kvp_ic_send, kvp_ic_recv) = mesh::channel();
275 vmbus_devices.push((
276 DeviceVtl::Vtl0,
277 hyperv_ic_resources::kvp::KvpIcHandle { recv: kvp_ic_recv }.into_resource(),
278 ));
279
280 vmbus_devices.push((
282 DeviceVtl::Vtl0,
283 hyperv_ic_resources::timesync::TimesyncIcHandle.into_resource(),
284 ));
285
286 let (vmbus_vsock_listener, vmbus_vsock_path) = make_vsock_listener()?;
288
289 let chipset = chipset
290 .build()
291 .context("failed to build chipset configuration")?;
292
293 let memory = {
294 let MemoryConfig {
295 startup_bytes,
296 dynamic_memory_range,
297 } = memory;
298
299 if dynamic_memory_range.is_some() {
300 anyhow::bail!("dynamic memory not supported in OpenVMM");
301 }
302
303 hvlite_defs::config::MemoryConfig {
304 mem_size: startup_bytes,
305 mmio_gaps: if firmware.is_openhcl() {
306 match arch {
307 MachineArch::X86_64 => DEFAULT_MMIO_GAPS_X86_WITH_VTL2.into(),
308 MachineArch::Aarch64 => DEFAULT_MMIO_GAPS_AARCH64_WITH_VTL2.into(),
309 }
310 } else {
311 match arch {
312 MachineArch::X86_64 => DEFAULT_MMIO_GAPS_X86.into(),
313 MachineArch::Aarch64 => DEFAULT_MMIO_GAPS_AARCH64.into(),
314 }
315 },
316 prefetch_memory: false,
317 pcie_ecam_base: DEFAULT_PCIE_ECAM_BASE,
318 }
319 };
320
321 let processor_topology = {
322 let ProcessorTopology {
323 vp_count,
324 enable_smt,
325 vps_per_socket,
326 apic_mode,
327 } = proc_topology;
328
329 ProcessorTopologyConfig {
330 proc_count: vp_count,
331 vps_per_socket,
332 enable_smt,
333 arch: Some(match arch {
334 MachineArch::X86_64 => hvlite_defs::config::ArchTopologyConfig::X86(
335 hvlite_defs::config::X86TopologyConfig {
336 x2apic: match apic_mode {
337 None => hvlite_defs::config::X2ApicConfig::Auto,
338 Some(x) => match x {
339 crate::ApicMode::Xapic => {
340 hvlite_defs::config::X2ApicConfig::Unsupported
341 }
342 crate::ApicMode::X2apicSupported => {
343 hvlite_defs::config::X2ApicConfig::Supported
344 }
345 crate::ApicMode::X2apicEnabled => {
346 hvlite_defs::config::X2ApicConfig::Enabled
347 }
348 },
349 },
350 ..Default::default()
351 },
352 ),
353 MachineArch::Aarch64 => hvlite_defs::config::ArchTopologyConfig::Aarch64(
354 hvlite_defs::config::Aarch64TopologyConfig::default(),
355 ),
356 }),
357 }
358 };
359
360 let (secure_boot_enabled, custom_uefi_vars) = firmware.uefi_config().map_or_else(
361 || (false, Default::default()),
362 |c| {
363 (
364 c.secure_boot_enabled,
365 match (arch, c.secure_boot_template) {
366 (MachineArch::X86_64, Some(SecureBootTemplate::MicrosoftWindows)) => {
367 hyperv_secure_boot_templates::x64::microsoft_windows()
368 }
369 (
370 MachineArch::X86_64,
371 Some(SecureBootTemplate::MicrosoftUefiCertificateAuthority),
372 ) => hyperv_secure_boot_templates::x64::microsoft_uefi_ca(),
373 (MachineArch::Aarch64, Some(SecureBootTemplate::MicrosoftWindows)) => {
374 hyperv_secure_boot_templates::aarch64::microsoft_windows()
375 }
376 (
377 MachineArch::Aarch64,
378 Some(SecureBootTemplate::MicrosoftUefiCertificateAuthority),
379 ) => hyperv_secure_boot_templates::aarch64::microsoft_uefi_ca(),
380 (_, None) => Default::default(),
381 },
382 )
383 },
384 );
385
386 let vmgs = if firmware.is_openhcl() {
387 None
388 } else {
389 Some(memdiff_vmgs(&vmgs)?)
390 };
391
392 let config = Config {
393 load_mode,
395 firmware_event_send: Some(firmware_event_send),
396
397 memory,
399 processor_topology,
400
401 chipset: chipset.chipset,
403 chipset_devices: chipset.chipset_devices,
404
405 hypervisor: HypervisorConfig {
407 with_hv: true,
408 user_mode_hv_enlightenments: false,
409 user_mode_apic: false,
410 with_vtl2,
411 with_isolation: match firmware.isolation() {
412 Some(IsolationType::Vbs) => Some(hvlite_defs::config::IsolationType::Vbs),
413 None => None,
414 _ => anyhow::bail!("unsupported isolation type"),
415 },
416 },
417 vmbus: Some(VmbusConfig {
418 vsock_listener: Some(vmbus_vsock_listener),
419 vsock_path: Some(vmbus_vsock_path.to_string_lossy().into_owned()),
420 vmbus_max_version: None,
421 vtl2_redirect: firmware.openhcl_config().is_some_and(|c| c.vmbus_redirect),
422 #[cfg(windows)]
423 vmbusproxy_handle: None,
424 }),
425 vtl2_vmbus,
426
427 floppy_disks,
429 ide_disks,
430 pcie_root_complexes: vec![],
431 pcie_devices: vec![],
432 vpci_devices,
433 vmbus_devices,
434
435 framebuffer,
437 vga_firmware,
438
439 secure_boot_enabled,
440 custom_uefi_vars,
441 vmgs,
442
443 automatic_guest_reset: false,
445
446 #[cfg(windows)]
448 kernel_vmnics: vec![],
449 input: mesh::Receiver::new(),
450 vtl2_gfx: false,
451 virtio_console_pci: false,
452 virtio_serial: None,
453 virtio_devices: vec![],
454 #[cfg(windows)]
455 vpci_resources: vec![],
456 debugger_rpc: None,
457 generation_id_recv: None,
458 rtc_delta_milliseconds: 0,
459 };
460
461 let path = config.vmbus.as_ref().unwrap().vsock_path.as_ref().unwrap();
463 let path = format!("{path}_{PIPETTE_VSOCK_PORT}");
464 let pipette_listener = PolledSocket::new(
465 driver,
466 UnixListener::bind(path).context("failed to bind to pipette listener")?,
467 )?;
468
469 let vtl2_pipette_listener = if let Some(vtl2_vmbus) = &config.vtl2_vmbus {
471 let path = vtl2_vmbus.vsock_path.as_ref().unwrap();
472 let path = format!("{path}_{PIPETTE_VSOCK_PORT}");
473 Some(PolledSocket::new(
474 driver,
475 UnixListener::bind(path).context("failed to bind to vtl2 pipette listener")?,
476 )?)
477 } else {
478 None
479 };
480
481 Ok(Self {
482 firmware,
483 arch,
484 config,
485 boot_device_type,
486
487 resources: PetriVmResourcesOpenVmm {
488 log_stream_tasks,
489 firmware_event_recv,
490 shutdown_ic_send,
491 kvp_ic_send,
492 ged_send,
493 pipette_listener,
494 vtl2_pipette_listener,
495 linux_direct_serial_agent,
496 driver: driver.clone(),
497 output_dir: log_source.output_dir().to_owned(),
498 agent_image,
499 openhcl_agent_image,
500 openvmm_path: openvmm_path.clone(),
501 vtl2_vsock_path,
502 _vmbus_vsock_path: vmbus_vsock_path,
503 vtl2_settings,
504 },
505
506 openvmm_log_file: log_source.log_file("openvmm")?,
507
508 ged,
509 framebuffer_view,
510 })
511 }
512}
513
514struct PetriVmConfigSetupCore<'a> {
515 arch: MachineArch,
516 firmware: &'a Firmware,
517 driver: &'a DefaultDriver,
518 logger: &'a PetriLogSource,
519 vmgs: &'a PetriVmgsResource,
520 boot_device_type: BootDeviceType,
521}
522
523struct SerialData {
524 emulated_serial_config: [Option<Resource<SerialBackendHandle>>; 4],
525 serial_tasks: Vec<Task<anyhow::Result<()>>>,
526 linux_direct_serial_agent: Option<LinuxDirectSerialAgent>,
527}
528
529enum Device {
530 Vmbus(DeviceVtl, Resource<VmbusDeviceHandleKind>),
531 Vpci(VpciDeviceConfig),
532 Ide(IdeDeviceConfig),
533}
534
535enum VideoDevice {
536 Vga(RomFileLocation),
537 Synth(DeviceVtl, Resource<VmbusDeviceHandleKind>),
538}
539
540impl PetriVmConfigSetupCore<'_> {
541 fn configure_serial(&self, logger: &PetriLogSource) -> anyhow::Result<SerialData> {
542 let mut serial_tasks = Vec::new();
543
544 let serial0_log_file = logger.log_file(match self.firmware {
545 Firmware::LinuxDirect { .. } | Firmware::OpenhclLinuxDirect { .. } => "linux",
546 Firmware::Pcat { .. } | Firmware::OpenhclPcat { .. } => "pcat",
547 Firmware::Uefi { .. } | Firmware::OpenhclUefi { .. } => "uefi",
548 })?;
549
550 let (serial0_host, serial0) = self
551 .create_serial_stream()
552 .context("failed to create serial0 stream")?;
553 let (serial0_read, serial0_write) = serial0_host.split();
554 let serial0_task = self.driver.spawn(
555 "serial0-console",
556 crate::log_task(serial0_log_file, serial0_read, "serial0-console"),
557 );
558 serial_tasks.push(serial0_task);
559
560 let serial2 = if self.firmware.is_openhcl() {
561 let (serial2_host, serial2) = self
562 .create_serial_stream()
563 .context("failed to create serial2 stream")?;
564 let serial2_task = self.driver.spawn(
565 "serial2-openhcl",
566 crate::log_task(logger.log_file("openhcl")?, serial2_host, "serial2-openhcl"),
567 );
568 serial_tasks.push(serial2_task);
569 serial2
570 } else {
571 None
572 };
573
574 if self.firmware.is_linux_direct() {
575 let (serial1_host, serial1) = self.create_serial_stream()?;
576 let (serial1_read, _serial1_write) = serial1_host.split();
577 let linux_direct_serial_agent =
578 LinuxDirectSerialAgent::new(serial1_read, serial0_write);
579 Ok(SerialData {
580 emulated_serial_config: [serial0, serial1, serial2, None],
581 serial_tasks,
582 linux_direct_serial_agent: Some(linux_direct_serial_agent),
583 })
584 } else {
585 Ok(SerialData {
586 emulated_serial_config: [serial0, None, serial2, None],
587 serial_tasks,
588 linux_direct_serial_agent: None,
589 })
590 }
591 }
592
593 fn create_serial_stream(
594 &self,
595 ) -> anyhow::Result<(
596 PolledSocket<UnixStream>,
597 Option<Resource<SerialBackendHandle>>,
598 )> {
599 let (host_side, guest_side) = UnixStream::pair()?;
600 let host_side = PolledSocket::new(self.driver, host_side)?;
601 let serial = OpenSocketSerialConfig::from(guest_side).into_resource();
602 Ok((host_side, Some(serial)))
603 }
604
605 fn load_firmware(&self) -> anyhow::Result<LoadMode> {
606 Ok(match (self.arch, &self.firmware) {
607 (MachineArch::X86_64, Firmware::LinuxDirect { kernel, initrd }) => {
608 let kernel = File::open(kernel.clone())
609 .context("Failed to open kernel")?
610 .into();
611 let initrd = File::open(initrd.clone())
612 .context("Failed to open initrd")?
613 .into();
614 LoadMode::Linux {
615 kernel,
616 initrd: Some(initrd),
617 cmdline: "console=ttyS0 debug panic=-1 rdinit=/bin/sh".into(),
618 custom_dsdt: None,
619 enable_serial: true,
620 }
621 }
622 (MachineArch::Aarch64, Firmware::LinuxDirect { kernel, initrd }) => {
623 let kernel = File::open(kernel.clone())
624 .context("Failed to open kernel")?
625 .into();
626 let initrd = File::open(initrd.clone())
627 .context("Failed to open initrd")?
628 .into();
629 LoadMode::Linux {
630 kernel,
631 initrd: Some(initrd),
632 cmdline: "console=ttyAMA0 earlycon debug panic=-1 rdinit=/bin/sh".into(),
633 custom_dsdt: None,
634 enable_serial: true,
635 }
636 }
637 (
638 MachineArch::X86_64,
639 Firmware::Pcat {
640 bios_firmware: firmware,
641 guest: _, svga_firmware: _, },
644 ) => {
645 let firmware = hvlite_pcat_locator::find_pcat_bios(firmware.get())
646 .context("Failed to load packaged PCAT binary")?;
647 LoadMode::Pcat {
648 firmware,
649 boot_order: DEFAULT_PCAT_BOOT_ORDER,
650 }
651 }
652 (
653 _,
654 Firmware::Uefi {
655 uefi_firmware: firmware,
656 guest: _, uefi_config:
658 UefiConfig {
659 secure_boot_enabled: _, secure_boot_template: _, disable_frontpage,
662 default_boot_always_attempt,
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: false,
675 enable_battery: false,
676 enable_serial: true,
677 enable_vpci_boot: matches!(self.boot_device_type, BootDeviceType::Nvme),
678 uefi_console_mode: Some(hvlite_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 vtl2_nvme_boot: _, vmbus_redirect: _, command_line: _,
701 log_levels: _,
702 vtl2_base_address_type,
703 } = 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 let vtl2_base_address = vtl2_base_address_type.unwrap_or_else(|| {
725 if isolated {
726 Vtl2BaseAddressType::File
729 } else {
730 Vtl2BaseAddressType::Absolute(2 * SIZE_1_GB)
735 }
736 });
737
738 let file = File::open(igvm_path.clone())
739 .context("failed to open openhcl firmware file")?
740 .into();
741 LoadMode::Igvm {
742 file,
743 cmdline: cmdline.unwrap_or_default(),
744 vtl2_base_address,
745 com_serial: Some(SerialInformation {
746 io_port: ComPort::Com3.io_port(),
747 irq: ComPort::Com3.irq().into(),
748 }),
749 }
750 }
751 (a, f) => anyhow::bail!("Unsupported firmware {f:?} for arch {a:?}"),
752 })
753 }
754
755 fn load_boot_disk(
756 &self,
757 devices: &mut impl Extend<Device>,
758 vtl2_settings: Option<&mut Vtl2Settings>,
759 ) -> anyhow::Result<()> {
760 let emulate_storage_in_openhcl = matches!(
761 self.firmware,
762 Firmware::OpenhclUefi {
763 openhcl_config: OpenHclConfig {
764 vtl2_nvme_boot: true,
765 ..
766 },
767 ..
768 }
769 );
770 enum Media {
771 Disk(Resource<DiskHandleKind>),
772 Dvd(Resource<DiskHandleKind>),
773 }
774 let media = match &self.firmware {
775 Firmware::LinuxDirect { .. }
776 | Firmware::OpenhclLinuxDirect { .. }
777 | Firmware::Uefi {
778 guest: UefiGuest::None,
779 ..
780 }
781 | Firmware::OpenhclUefi {
782 guest: UefiGuest::None,
783 ..
784 } => return Ok(()),
785 Firmware::Pcat { guest, .. } | Firmware::OpenhclPcat { guest, .. } => {
786 let disk_path = guest.artifact();
787 match guest {
788 PcatGuest::Vhd(_) => Media::Disk(memdiff_disk(disk_path.as_ref())?),
789 PcatGuest::Iso(_) => Media::Dvd(open_disk_type(disk_path.as_ref(), true)?),
790 }
791 }
792 Firmware::Uefi { guest, .. } | Firmware::OpenhclUefi { guest, .. } => {
793 let disk_path = guest.artifact();
794 Media::Disk(memdiff_disk(
795 disk_path.expect("not uefi guest none").as_ref(),
796 )?)
797 }
798 };
799
800 if emulate_storage_in_openhcl {
801 match media {
802 Media::Dvd(_) => todo!("support DVD to VTL2"),
803 Media::Disk(disk) => {
804 devices.extend([Device::Vpci(VpciDeviceConfig {
805 vtl: DeviceVtl::Vtl2,
806 instance_id: PARAVISOR_BOOT_NVME_INSTANCE,
807 resource: NvmeControllerHandle {
808 subsystem_id: PARAVISOR_BOOT_NVME_INSTANCE,
809 max_io_queues: 64,
810 msix_count: 64,
811 namespaces: vec![NamespaceDefinition {
812 nsid: BOOT_NVME_NSID,
813 disk,
814 read_only: false,
815 }],
816 }
817 .into_resource(),
818 })]);
819 }
820 }
821 match self.boot_device_type {
822 BootDeviceType::None => {}
823 BootDeviceType::Ide => todo!("support IDE emulation testing"),
824 BootDeviceType::Nvme => todo!("support NVMe emulation testing"),
825 BootDeviceType::Scsi => vtl2_settings
826 .expect("openhcl config should have vtl2settings")
827 .dynamic
828 .as_mut()
829 .unwrap()
830 .storage_controllers
831 .push(
832 Vtl2StorageControllerBuilder::scsi()
833 .with_instance_id(SCSI_INSTANCE)
834 .add_lun(
835 Vtl2LunBuilder::disk()
836 .with_location(BOOT_NVME_LUN)
837 .with_physical_device(Vtl2StorageBackingDeviceBuilder::new(
838 ControllerType::Nvme,
839 PARAVISOR_BOOT_NVME_INSTANCE,
840 BOOT_NVME_NSID,
841 )),
842 )
843 .build(),
844 ),
845 }
846 } else {
847 match self.boot_device_type {
848 BootDeviceType::None => {}
849 BootDeviceType::Ide => {
850 let guest_media = match media {
851 Media::Disk(disk_type) => GuestMedia::Disk {
852 read_only: false,
853 disk_parameters: None,
854 disk_type,
855 },
856 Media::Dvd(media) => GuestMedia::Dvd(
857 SimpleScsiDvdHandle {
858 media: Some(media),
859 requests: None,
860 }
861 .into_resource(),
862 ),
863 };
864 devices.extend([Device::Ide(IdeDeviceConfig {
865 path: ide_resources::IdePath {
866 channel: 0,
867 drive: 0,
868 },
869 guest_media,
870 })]);
871 }
872 BootDeviceType::Scsi => {
873 let disk = match media {
874 Media::Disk(disk) => disk,
875 Media::Dvd(_) => todo!("support SCSI DVD boot disks"),
876 };
877 devices.extend([Device::Vmbus(
878 DeviceVtl::Vtl0,
879 ScsiControllerHandle {
880 instance_id: SCSI_INSTANCE,
881 max_sub_channel_count: 1,
882 io_queue_depth: None,
883 devices: vec![ScsiDeviceAndPath {
884 path: ScsiPath {
885 path: 0,
886 target: 0,
887 lun: 0,
888 },
889 device: SimpleScsiDiskHandle {
890 read_only: false,
891 parameters: Default::default(),
892 disk,
893 }
894 .into_resource(),
895 }],
896 requests: None,
897 poll_mode_queue_depth: None,
898 }
899 .into_resource(),
900 )]);
901 }
902 BootDeviceType::Nvme => {
903 let disk = match media {
904 Media::Disk(disk) => disk,
905 Media::Dvd(_) => anyhow::bail!("dvd not supported on nvme"),
906 };
907 devices.extend([Device::Vpci(VpciDeviceConfig {
908 vtl: DeviceVtl::Vtl0,
909 instance_id: BOOT_NVME_INSTANCE,
910 resource: NvmeControllerHandle {
911 subsystem_id: BOOT_NVME_INSTANCE,
912 max_io_queues: 64,
913 msix_count: 64,
914 namespaces: vec![NamespaceDefinition {
915 nsid: BOOT_NVME_NSID,
916 disk,
917 read_only: false,
918 }],
919 }
920 .into_resource(),
921 })]);
922 }
923 }
924 }
925
926 Ok(())
927 }
928
929 fn config_openhcl_vmbus_devices(
930 &self,
931 serial: &mut [Option<Resource<SerialBackendHandle>>],
932 devices: &mut impl Extend<Device>,
933 firmware_event_send: &mesh::Sender<FirmwareEvent>,
934 framebuffer: bool,
935 tpm_state_persistence: bool,
936 ) -> anyhow::Result<(
937 get_resources::ged::GuestEmulationDeviceHandle,
938 mesh::Sender<get_resources::ged::GuestEmulationRequest>,
939 )> {
940 let serial0 = serial[0].take();
941 devices.extend([Device::Vmbus(
942 DeviceVtl::Vtl2,
943 VmbusSerialDeviceHandle {
944 port: VmbusSerialPort::Com1,
945 backend: serial0.unwrap_or_else(|| DisconnectedSerialBackendHandle.into_resource()),
946 }
947 .into_resource(),
948 )]);
949 let serial1 = serial[1].take();
950 devices.extend([Device::Vmbus(
951 DeviceVtl::Vtl2,
952 VmbusSerialDeviceHandle {
953 port: VmbusSerialPort::Com2,
954 backend: serial1.unwrap_or_else(|| DisconnectedSerialBackendHandle.into_resource()),
955 }
956 .into_resource(),
957 )]);
958
959 let gel = get_resources::gel::GuestEmulationLogHandle.into_resource();
960
961 let crash = spawn_dump_handler(self.driver, self.logger).into_resource();
962
963 devices.extend([
964 Device::Vmbus(DeviceVtl::Vtl2, crash),
965 Device::Vmbus(DeviceVtl::Vtl2, gel),
966 ]);
967
968 let (guest_request_send, guest_request_recv) = mesh::channel();
969
970 let (
971 UefiConfig {
972 secure_boot_enabled,
973 secure_boot_template,
974 disable_frontpage,
975 default_boot_always_attempt,
976 },
977 OpenHclConfig { vmbus_redirect, .. },
978 ) = match self.firmware {
979 Firmware::OpenhclUefi {
980 uefi_config,
981 openhcl_config,
982 ..
983 } => (uefi_config, openhcl_config),
984 Firmware::OpenhclLinuxDirect { openhcl_config, .. } => {
985 (&UefiConfig::default(), openhcl_config)
986 }
987 _ => anyhow::bail!("not a supported openhcl firmware config"),
988 };
989
990 let test_gsp_by_id = self
991 .vmgs
992 .disk()
993 .is_some_and(|x| matches!(x.encryption_policy, GuestStateEncryptionPolicy::GspById(_)));
994
995 let ged = get_resources::ged::GuestEmulationDeviceHandle {
997 firmware: get_resources::ged::GuestFirmwareConfig::Uefi {
998 firmware_debug: false,
999 disable_frontpage: *disable_frontpage,
1000 enable_vpci_boot: matches!(self.boot_device_type, BootDeviceType::Nvme),
1001 console_mode: get_resources::ged::UefiConsoleMode::COM1,
1002 default_boot_always_attempt: *default_boot_always_attempt,
1003 },
1004 com1: true,
1005 com2: true,
1006 vmbus_redirection: *vmbus_redirect,
1007 vtl2_settings: None, vmgs: memdiff_vmgs(self.vmgs)?,
1009 framebuffer: framebuffer.then(|| SharedFramebufferHandle.into_resource()),
1010 guest_request_recv,
1011 enable_tpm: false,
1012 firmware_event_send: Some(firmware_event_send.clone()),
1013 secure_boot_enabled: *secure_boot_enabled,
1014 secure_boot_template: match secure_boot_template {
1015 Some(SecureBootTemplate::MicrosoftWindows) => {
1016 get_resources::ged::GuestSecureBootTemplateType::MicrosoftWindows
1017 }
1018 Some(SecureBootTemplate::MicrosoftUefiCertificateAuthority) => {
1019 get_resources::ged::GuestSecureBootTemplateType::MicrosoftUefiCertificateAuthority
1020 }
1021 None => get_resources::ged::GuestSecureBootTemplateType::None,
1022 },
1023 enable_battery: false,
1024 no_persistent_secrets: !tpm_state_persistence,
1025 igvm_attest_test_config: None,
1026 test_gsp_by_id,
1027 };
1028
1029 Ok((ged, guest_request_send))
1030 }
1031
1032 fn config_video(
1033 &self,
1034 ) -> anyhow::Result<Option<(VideoDevice, Framebuffer, FramebufferAccess)>> {
1035 if self.firmware.isolation().is_some() {
1036 return Ok(None);
1037 }
1038
1039 let video_dev = match self.firmware {
1040 Firmware::Pcat { svga_firmware, .. } | Firmware::OpenhclPcat { svga_firmware, .. } => {
1041 Some(VideoDevice::Vga(
1042 hvlite_pcat_locator::find_svga_bios(svga_firmware.get())
1043 .context("Failed to load VGA BIOS")?,
1044 ))
1045 }
1046 Firmware::Uefi { .. } | Firmware::OpenhclUefi { .. } => Some(VideoDevice::Synth(
1047 DeviceVtl::Vtl0,
1048 SynthVideoHandle {
1049 framebuffer: SharedFramebufferHandle.into_resource(),
1050 }
1051 .into_resource(),
1052 )),
1053 Firmware::OpenhclLinuxDirect { .. } | Firmware::LinuxDirect { .. } => None,
1054 };
1055
1056 Ok(if let Some(vdev) = video_dev {
1057 let vram = alloc_shared_memory(FRAMEBUFFER_SIZE).context("allocating framebuffer")?;
1058 let (fb, fba) = framebuffer::framebuffer(vram, FRAMEBUFFER_SIZE, 0)
1059 .context("creating framebuffer")?;
1060 Some((vdev, fb, fba))
1061 } else {
1062 None
1063 })
1064 }
1065}
1066
1067fn spawn_dump_handler(driver: &DefaultDriver, logger: &PetriLogSource) -> GuestCrashDeviceHandle {
1068 let (send, mut recv) = mesh::channel();
1069 let handle = GuestCrashDeviceHandle {
1070 request_dump: send,
1071 max_dump_size: 256 * 1024 * 1024,
1072 };
1073 driver
1074 .spawn("openhcl-dump-handler", {
1075 let logger = logger.clone();
1076 let driver = driver.clone();
1077 async move {
1078 while let Some(rpc) = recv.next().await {
1079 rpc.handle_failable_sync(|done| {
1080 let (file, path) = logger.create_attachment("openhcl.core")?.into_parts();
1081 driver
1082 .spawn("crash-waiter", async move {
1083 let filename = path.file_name().unwrap().to_str().unwrap();
1084 if done.await.is_ok() {
1085 tracing::warn!(filename, "openhcl crash dump complete");
1086 } else {
1087 tracing::error!(
1088 filename,
1089 "openhcl crash dump incomplete, may be corrupted"
1090 );
1091 }
1092 })
1093 .detach();
1094 anyhow::Ok(file)
1095 })
1096 }
1097 }
1098 })
1099 .detach();
1100 handle
1101}