1use super::BOOT_NVME_INSTANCE;
8use super::BOOT_NVME_LUN;
9use super::BOOT_NVME_NSID;
10use super::PetriVmArtifactsOpenVmm;
11use super::PetriVmConfigOpenVmm;
12use super::PetriVmResourcesOpenVmm;
13use super::SCSI_INSTANCE;
14use super::memdiff_disk_from_artifact;
15use crate::Firmware;
16use crate::IsolationType;
17use crate::PcatGuest;
18use crate::PetriLogSource;
19use crate::PetriTestParams;
20use crate::ProcessorTopology;
21use crate::SIZE_1_GB;
22use crate::UefiGuest;
23use crate::linux_direct_serial_agent::LinuxDirectSerialAgent;
24use crate::openhcl_diag::OpenHclDiagHandler;
25use anyhow::Context;
26use disk_backend_resources::LayeredDiskHandle;
27use disk_backend_resources::layer::RamDiskLayerHandle;
28use framebuffer::FRAMEBUFFER_SIZE;
29use framebuffer::Framebuffer;
30use framebuffer::FramebufferAccess;
31use fs_err::File;
32use futures::StreamExt;
33use get_resources::crash::GuestCrashDeviceHandle;
34use get_resources::ged::FirmwareEvent;
35use guid::Guid;
36use hvlite_defs::config::Config;
37use hvlite_defs::config::DEFAULT_MMIO_GAPS_AARCH64;
38use hvlite_defs::config::DEFAULT_MMIO_GAPS_AARCH64_WITH_VTL2;
39use hvlite_defs::config::DEFAULT_MMIO_GAPS_X86;
40use hvlite_defs::config::DEFAULT_MMIO_GAPS_X86_WITH_VTL2;
41use hvlite_defs::config::DEFAULT_PCAT_BOOT_ORDER;
42use hvlite_defs::config::DeviceVtl;
43use hvlite_defs::config::HypervisorConfig;
44use hvlite_defs::config::LateMapVtl0MemoryPolicy;
45use hvlite_defs::config::LoadMode;
46use hvlite_defs::config::MemoryConfig;
47use hvlite_defs::config::ProcessorTopologyConfig;
48use hvlite_defs::config::SerialInformation;
49use hvlite_defs::config::VmbusConfig;
50use hvlite_defs::config::VpciDeviceConfig;
51use hvlite_defs::config::Vtl2BaseAddressType;
52use hvlite_defs::config::Vtl2Config;
53use hvlite_helpers::disk::open_disk_type;
54use hvlite_pcat_locator::RomFileLocation;
55use hyperv_ic_resources::shutdown::ShutdownIcHandle;
56use ide_resources::GuestMedia;
57use ide_resources::IdeDeviceConfig;
58use nvme_resources::NamespaceDefinition;
59use nvme_resources::NvmeControllerHandle;
60use pal_async::DefaultDriver;
61use pal_async::socket::PolledSocket;
62use pal_async::task::Spawn;
63use pal_async::task::Task;
64use petri_artifacts_common::tags::MachineArch;
65use pipette_client::PIPETTE_VSOCK_PORT;
66use scsidisk_resources::SimpleScsiDiskHandle;
67use scsidisk_resources::SimpleScsiDvdHandle;
68use serial_16550_resources::ComPort;
69use serial_core::resources::DisconnectedSerialBackendHandle;
70use serial_socket::net::OpenSocketSerialConfig;
71use sparse_mmap::alloc_shared_memory;
72use std::fmt::Write as _;
73use storvsp_resources::ScsiControllerHandle;
74use storvsp_resources::ScsiDeviceAndPath;
75use storvsp_resources::ScsiPath;
76use tempfile::TempPath;
77use uidevices_resources::SynthVideoHandle;
78use unix_socket::UnixListener;
79use unix_socket::UnixStream;
80use video_core::SharedFramebufferHandle;
81use vm_manifest_builder::VmManifestBuilder;
82use vm_resource::IntoResource;
83use vm_resource::Resource;
84use vm_resource::kind::SerialBackendHandle;
85use vm_resource::kind::VmbusDeviceHandleKind;
86use vmbus_serial_resources::VmbusSerialDeviceHandle;
87use vmbus_serial_resources::VmbusSerialPort;
88use vmgs_resources::VmgsResource;
89use vtl2_settings_proto::Vtl2Settings;
90
91impl PetriVmConfigOpenVmm {
92 pub fn new(
94 params: &PetriTestParams<'_>,
95 artifacts: PetriVmArtifactsOpenVmm,
96 driver: &DefaultDriver,
97 ) -> anyhow::Result<Self> {
98 let PetriVmArtifactsOpenVmm {
99 firmware,
100 arch,
101 agent_image,
102 openhcl_agent_image,
103 openvmm_path,
104 } = artifacts;
105
106 let setup = PetriVmConfigSetupCore {
107 arch,
108 firmware: &firmware,
109 driver,
110 logger: params.logger,
111 };
112
113 let mut chipset = VmManifestBuilder::new(
114 match firmware {
115 Firmware::LinuxDirect { .. } => {
116 vm_manifest_builder::BaseChipsetType::HyperVGen2LinuxDirect
117 }
118 Firmware::OpenhclLinuxDirect { .. } => {
119 vm_manifest_builder::BaseChipsetType::HclHost
120 }
121 Firmware::OpenhclUefi { .. } => vm_manifest_builder::BaseChipsetType::HclHost,
122 Firmware::Pcat { .. } => vm_manifest_builder::BaseChipsetType::HypervGen1,
123 Firmware::Uefi { .. } => vm_manifest_builder::BaseChipsetType::HypervGen2Uefi,
124 },
125 match arch {
126 MachineArch::X86_64 => vm_manifest_builder::MachineArch::X86_64,
127 MachineArch::Aarch64 => vm_manifest_builder::MachineArch::Aarch64,
128 },
129 );
130
131 let load_mode = setup.load_firmware()?;
132
133 let SerialData {
134 mut emulated_serial_config,
135 serial_tasks: log_stream_tasks,
136 linux_direct_serial_agent,
137 } = setup.configure_serial(params.logger)?;
138
139 let (video_dev, framebuffer, framebuffer_access) = match setup.config_video()? {
140 Some((v, fb, fba)) => {
141 chipset = chipset.with_framebuffer();
142 (Some(v), Some(fb), Some(fba))
143 }
144 None => (None, None, None),
145 };
146
147 let mut devices = Vec::new();
148
149 let (firmware_event_send, firmware_event_recv) = mesh::mpsc_channel();
150
151 let make_vsock_listener = || -> anyhow::Result<(UnixListener, TempPath)> {
152 Ok(tempfile::Builder::new()
153 .make(|path| UnixListener::bind(path))?
154 .into_parts())
155 };
156
157 let (
158 with_vtl2,
159 vtl2_vmbus,
160 openhcl_diag_handler,
161 ged,
162 ged_send,
163 mut vtl2_settings,
164 vtl2_vsock_path,
165 ) = if firmware.is_openhcl() {
166 let (ged, ged_send) = setup.config_openhcl_vmbus_devices(
167 &mut emulated_serial_config,
168 &mut devices,
169 &firmware_event_send,
170 framebuffer.is_some(),
171 )?;
172 let (vtl2_vsock_listener, vtl2_vsock_path) = make_vsock_listener()?;
173 (
174 Some(Vtl2Config {
175 vtl0_alias_map: false, late_map_vtl0_memory: Some(LateMapVtl0MemoryPolicy::InjectException),
177 }),
178 Some(VmbusConfig {
179 vsock_listener: Some(vtl2_vsock_listener),
180 vsock_path: Some(vtl2_vsock_path.to_string_lossy().into_owned()),
181 vmbus_max_version: None,
182 vtl2_redirect: false,
183 #[cfg(windows)]
184 vmbusproxy_handle: None,
185 }),
186 Some(OpenHclDiagHandler::new(
187 diag_client::DiagClient::from_hybrid_vsock(driver.clone(), &vtl2_vsock_path),
188 )),
189 Some(ged),
190 Some(ged_send),
191 Some(Vtl2Settings {
193 version: vtl2_settings_proto::vtl2_settings_base::Version::V1.into(),
194 dynamic: Some(Default::default()),
195 fixed: Some(Default::default()),
196 namespace_settings: Default::default(),
197 }),
198 Some(vtl2_vsock_path),
199 )
200 } else {
201 (None, None, None, None, None, None, None)
202 };
203
204 setup.load_boot_disk(&mut devices, vtl2_settings.as_mut())?;
205 let expected_boot_event = firmware.expected_boot_event();
206
207 chipset = chipset.with_serial(emulated_serial_config);
210 if matches!(firmware, Firmware::LinuxDirect { .. }) {
214 chipset = chipset.with_serial_wait_for_rts();
215 }
216
217 let mut vmbus_devices = Vec::new();
219 let mut ide_disks = Vec::new();
220 let floppy_disks = Vec::new();
221 let mut vpci_devices = Vec::new();
222 for d in devices {
223 match d {
224 Device::Vmbus(vtl, resource) => vmbus_devices.push((vtl, resource)),
225 Device::Vpci(c) => vpci_devices.push(c),
226 Device::Ide(c) => ide_disks.push(c),
227 }
228 }
229
230 let vga_firmware = match video_dev {
232 Some(VideoDevice::Vga(firmware)) => Some(firmware),
233 Some(VideoDevice::Synth(vtl, resource)) => {
234 vmbus_devices.push((vtl, resource));
235 None
236 }
237 None => None,
238 };
239
240 let (shutdown_ic_send, shutdown_ic_recv) = mesh::channel();
242 vmbus_devices.push((
243 DeviceVtl::Vtl0,
244 ShutdownIcHandle {
245 recv: shutdown_ic_recv,
246 }
247 .into_resource(),
248 ));
249
250 let (kvp_ic_send, kvp_ic_recv) = mesh::channel();
252 vmbus_devices.push((
253 DeviceVtl::Vtl0,
254 hyperv_ic_resources::kvp::KvpIcHandle { recv: kvp_ic_recv }.into_resource(),
255 ));
256
257 vmbus_devices.push((
259 DeviceVtl::Vtl0,
260 hyperv_ic_resources::timesync::TimesyncIcHandle.into_resource(),
261 ));
262
263 let (vmbus_vsock_listener, vmbus_vsock_path) = make_vsock_listener()?;
265
266 let chipset = chipset
267 .build()
268 .context("failed to build chipset configuration")?;
269
270 let config = Config {
271 load_mode,
273 firmware_event_send: Some(firmware_event_send),
274
275 memory: MemoryConfig {
277 mem_size: if firmware.is_openhcl() {
278 4 * SIZE_1_GB
279 } else {
280 SIZE_1_GB
281 },
282 mmio_gaps: if firmware.is_openhcl() {
283 match arch {
284 MachineArch::X86_64 => DEFAULT_MMIO_GAPS_X86_WITH_VTL2.into(),
285 MachineArch::Aarch64 => DEFAULT_MMIO_GAPS_AARCH64_WITH_VTL2.into(),
286 }
287 } else {
288 match arch {
289 MachineArch::X86_64 => DEFAULT_MMIO_GAPS_X86.into(),
290 MachineArch::Aarch64 => DEFAULT_MMIO_GAPS_AARCH64.into(),
291 }
292 },
293 prefetch_memory: false,
294 },
295 processor_topology: ProcessorTopologyConfig {
296 proc_count: 2,
297 vps_per_socket: None,
298 enable_smt: None,
299 arch: None,
300 },
301
302 chipset: chipset.chipset,
304 chipset_devices: chipset.chipset_devices,
305
306 hypervisor: HypervisorConfig {
308 with_hv: true,
309 user_mode_hv_enlightenments: false,
310 user_mode_apic: false,
311 with_vtl2,
312 with_isolation: match firmware.isolation() {
313 Some(IsolationType::Vbs) => Some(hvlite_defs::config::IsolationType::Vbs),
314 None => None,
315 _ => anyhow::bail!("unsupported isolation type"),
316 },
317 },
318 vmbus: Some(VmbusConfig {
319 vsock_listener: Some(vmbus_vsock_listener),
320 vsock_path: Some(vmbus_vsock_path.to_string_lossy().into_owned()),
321 vmbus_max_version: None,
322 vtl2_redirect: false,
323 #[cfg(windows)]
324 vmbusproxy_handle: None,
325 }),
326 vtl2_vmbus,
327
328 floppy_disks,
330 ide_disks,
331 vpci_devices,
332 vmbus_devices,
333
334 framebuffer,
336 vga_firmware,
337
338 custom_uefi_vars: Default::default(),
340
341 #[cfg(windows)]
343 kernel_vmnics: vec![],
344 input: mesh::Receiver::new(),
345 vtl2_gfx: false,
346 virtio_console_pci: false,
347 virtio_serial: None,
348 virtio_devices: vec![],
349 #[cfg(windows)]
350 vpci_resources: vec![],
351 vmgs: if firmware.is_openhcl() {
352 None
353 } else {
354 Some(VmgsResource::Ephemeral)
355 },
356 secure_boot_enabled: false,
357 debugger_rpc: None,
358 generation_id_recv: None,
359 rtc_delta_milliseconds: 0,
360 };
361
362 let path = config.vmbus.as_ref().unwrap().vsock_path.as_ref().unwrap();
364 let path = format!("{path}_{PIPETTE_VSOCK_PORT}");
365 let pipette_listener = PolledSocket::new(
366 driver,
367 UnixListener::bind(path).context("failed to bind to pipette listener")?,
368 )?;
369
370 let vtl2_pipette_listener = if let Some(vtl2_vmbus) = &config.vtl2_vmbus {
372 let path = vtl2_vmbus.vsock_path.as_ref().unwrap();
373 let path = format!("{path}_{PIPETTE_VSOCK_PORT}");
374 Some(PolledSocket::new(
375 driver,
376 UnixListener::bind(path).context("failed to bind to vtl2 pipette listener")?,
377 )?)
378 } else {
379 None
380 };
381
382 Ok(Self {
383 firmware,
384 arch,
385 config,
386
387 resources: PetriVmResourcesOpenVmm {
388 log_stream_tasks,
389 firmware_event_recv,
390 shutdown_ic_send,
391 kvp_ic_send,
392 expected_boot_event,
393 ged_send,
394 pipette_listener,
395 vtl2_pipette_listener,
396 openhcl_diag_handler,
397 linux_direct_serial_agent,
398 driver: driver.clone(),
399 output_dir: params.output_dir.to_owned(),
400 agent_image,
401 openhcl_agent_image,
402 openvmm_path,
403 log_source: params.logger.clone(),
404 vtl2_vsock_path,
405 _vmbus_vsock_path: vmbus_vsock_path,
406 },
407
408 openvmm_log_file: params.logger.log_file("openvmm")?,
409
410 ged,
411 vtl2_settings,
412 framebuffer_access,
413 }
414 .with_processor_topology(ProcessorTopology::default()))
415 }
416}
417
418struct PetriVmConfigSetupCore<'a> {
419 arch: MachineArch,
420 firmware: &'a Firmware,
421 driver: &'a DefaultDriver,
422 logger: &'a PetriLogSource,
423}
424
425struct SerialData {
426 emulated_serial_config: [Option<Resource<SerialBackendHandle>>; 4],
427 serial_tasks: Vec<Task<anyhow::Result<()>>>,
428 linux_direct_serial_agent: Option<LinuxDirectSerialAgent>,
429}
430
431enum Device {
432 Vmbus(DeviceVtl, Resource<VmbusDeviceHandleKind>),
433 Vpci(VpciDeviceConfig),
434 Ide(IdeDeviceConfig),
435}
436
437enum VideoDevice {
438 Vga(RomFileLocation),
439 Synth(DeviceVtl, Resource<VmbusDeviceHandleKind>),
440}
441
442impl PetriVmConfigSetupCore<'_> {
443 fn configure_serial(&self, logger: &PetriLogSource) -> anyhow::Result<SerialData> {
444 let mut serial_tasks = Vec::new();
445
446 let serial0_log_file = logger.log_file(match self.firmware {
447 Firmware::LinuxDirect { .. } | Firmware::OpenhclLinuxDirect { .. } => "linux",
448 Firmware::Pcat { .. } => "pcat",
449 Firmware::Uefi { .. } | Firmware::OpenhclUefi { .. } => "uefi",
450 })?;
451
452 let (serial0_host, serial0) = self
453 .create_serial_stream()
454 .context("failed to create serial0 stream")?;
455 let (serial0_read, serial0_write) = serial0_host.split();
456 let serial0_task = self.driver.spawn(
457 "serial0-console",
458 crate::log_stream(serial0_log_file, serial0_read),
459 );
460 serial_tasks.push(serial0_task);
461
462 let serial2 = if self.firmware.is_openhcl() {
463 let (serial2_host, serial2) = self
464 .create_serial_stream()
465 .context("failed to create serial2 stream")?;
466 let serial2_task = self.driver.spawn(
467 "serial2-openhcl",
468 crate::log_stream(logger.log_file("openhcl")?, serial2_host),
469 );
470 serial_tasks.push(serial2_task);
471 serial2
472 } else {
473 None
474 };
475
476 if self.firmware.is_linux_direct() {
477 let (serial1_host, serial1) = self.create_serial_stream()?;
478 let (serial1_read, _serial1_write) = serial1_host.split();
479 let linux_direct_serial_agent =
480 LinuxDirectSerialAgent::new(serial1_read, serial0_write);
481 Ok(SerialData {
482 emulated_serial_config: [serial0, serial1, serial2, None],
483 serial_tasks,
484 linux_direct_serial_agent: Some(linux_direct_serial_agent),
485 })
486 } else {
487 Ok(SerialData {
488 emulated_serial_config: [serial0, None, serial2, None],
489 serial_tasks,
490 linux_direct_serial_agent: None,
491 })
492 }
493 }
494
495 fn create_serial_stream(
496 &self,
497 ) -> anyhow::Result<(
498 PolledSocket<UnixStream>,
499 Option<Resource<SerialBackendHandle>>,
500 )> {
501 let (host_side, guest_side) = UnixStream::pair()?;
502 let host_side = PolledSocket::new(self.driver, host_side)?;
503 let serial = OpenSocketSerialConfig::from(guest_side).into_resource();
504 Ok((host_side, Some(serial)))
505 }
506
507 fn load_firmware(&self) -> anyhow::Result<LoadMode> {
508 let openhcl_tracing =
510 if let Ok(x) = std::env::var("OPENVMM_LOG").or_else(|_| std::env::var("HVLITE_LOG")) {
511 format!("OPENVMM_LOG={x}")
512 } else {
513 "OPENVMM_LOG=debug".to_owned()
514 };
515 let openhcl_show_spans = if let Ok(x) = std::env::var("OPENVMM_SHOW_SPANS") {
516 format!("OPENVMM_SHOW_SPANS={x}")
517 } else {
518 "OPENVMM_SHOW_SPANS=true".to_owned()
519 };
520
521 Ok(match (self.arch, &self.firmware) {
522 (MachineArch::X86_64, Firmware::LinuxDirect { kernel, initrd }) => {
523 let kernel = File::open(kernel.clone())
524 .context("Failed to open kernel")?
525 .into();
526 let initrd = File::open(initrd.clone())
527 .context("Failed to open initrd")?
528 .into();
529 LoadMode::Linux {
530 kernel,
531 initrd: Some(initrd),
532 cmdline: "console=ttyS0 debug panic=-1 rdinit=/bin/sh".into(),
533 custom_dsdt: None,
534 enable_serial: true,
535 }
536 }
537 (MachineArch::Aarch64, Firmware::LinuxDirect { kernel, initrd }) => {
538 let kernel = File::open(kernel.clone())
539 .context("Failed to open kernel")?
540 .into();
541 let initrd = File::open(initrd.clone())
542 .context("Failed to open initrd")?
543 .into();
544 LoadMode::Linux {
545 kernel,
546 initrd: Some(initrd),
547 cmdline: "console=ttyAMA0 earlycon debug panic=-1 rdinit=/bin/sh".into(),
548 custom_dsdt: None,
549 enable_serial: true,
550 }
551 }
552 (
553 MachineArch::X86_64,
554 Firmware::Pcat {
555 bios_firmware: firmware,
556 ..
557 },
558 ) => {
559 let firmware = hvlite_pcat_locator::find_pcat_bios(firmware.get())
560 .context("Failed to load packaged PCAT binary")?;
561 LoadMode::Pcat {
562 firmware,
563 boot_order: DEFAULT_PCAT_BOOT_ORDER,
564 }
565 }
566 (
567 _,
568 Firmware::Uefi {
569 uefi_firmware: firmware,
570 ..
571 },
572 ) => {
573 let firmware = File::open(firmware.clone())
574 .context("Failed to open uefi firmware file")?
575 .into();
576 LoadMode::Uefi {
577 firmware,
578 enable_debugging: false,
579 enable_memory_protections: false,
580 disable_frontpage: true,
581 enable_tpm: false,
582 enable_battery: false,
583 enable_serial: true,
584 enable_vpci_boot: false,
585 uefi_console_mode: Some(hvlite_defs::config::UefiConsoleMode::Com1),
586 default_boot_always_attempt: false,
587 }
588 }
589 (
590 MachineArch::X86_64,
591 Firmware::OpenhclLinuxDirect { igvm_path, .. }
592 | Firmware::OpenhclUefi { igvm_path, .. },
593 ) => {
594 let mut cmdline =
595 format!("panic=-1 reboot=triple {openhcl_tracing} {openhcl_show_spans}");
596
597 let isolated = match self.firmware {
598 Firmware::OpenhclLinuxDirect { .. } => {
599 write!(cmdline, " UNDERHILL_SERIAL_WAIT_FOR_RTS=1 UNDERHILL_CMDLINE_APPEND=\"rdinit=/bin/sh\"").unwrap();
603 false
604 }
605 Firmware::OpenhclUefi { isolation, .. } if isolation.is_some() => true,
606 _ => false,
607 };
608 let file = File::open(igvm_path.clone())
609 .context("failed to open openhcl firmware file")?
610 .into();
611 LoadMode::Igvm {
612 file,
613 cmdline,
614 vtl2_base_address: if isolated {
615 Vtl2BaseAddressType::File
618 } else {
619 Vtl2BaseAddressType::Absolute(2 * SIZE_1_GB)
624 },
625 com_serial: Some(SerialInformation {
626 io_port: ComPort::Com3.io_port(),
627 irq: ComPort::Com3.irq().into(),
628 }),
629 }
630 }
631 (a, f) => anyhow::bail!("Unsupported firmware {f:?} for arch {a:?}"),
632 })
633 }
634
635 fn load_boot_disk(
636 &self,
637 devices: &mut impl Extend<Device>,
638 vtl2_settings: Option<&mut Vtl2Settings>,
639 ) -> anyhow::Result<()> {
640 match &self.firmware {
641 Firmware::LinuxDirect { .. } | Firmware::OpenhclLinuxDirect { .. } => {
642 }
644 Firmware::Uefi {
645 guest: UefiGuest::None,
646 ..
647 }
648 | Firmware::OpenhclUefi {
649 guest: UefiGuest::None,
650 ..
651 } => {
652 }
654 Firmware::Pcat { guest, .. } => {
655 let disk_path = guest.artifact();
656 let guest_media = match guest {
657 PcatGuest::Vhd(_) => GuestMedia::Disk {
658 read_only: false,
659 disk_parameters: None,
660 disk_type: memdiff_disk_from_artifact(disk_path)?,
661 },
662 PcatGuest::Iso(_) => GuestMedia::Dvd(
663 SimpleScsiDvdHandle {
664 media: Some(open_disk_type(disk_path.as_ref(), true)?),
665 requests: None,
666 }
667 .into_resource(),
668 ),
669 };
670 devices.extend([Device::Ide(IdeDeviceConfig {
671 path: ide_resources::IdePath {
672 channel: 0,
673 drive: 0,
674 },
675 guest_media,
676 })]);
677 }
678 Firmware::Uefi { guest, .. }
679 | Firmware::OpenhclUefi {
680 guest,
681 vtl2_nvme_boot: false,
682 ..
683 } => {
684 let disk_path = guest.artifact();
685 devices.extend([Device::Vmbus(
686 DeviceVtl::Vtl0,
687 ScsiControllerHandle {
688 instance_id: SCSI_INSTANCE,
689 max_sub_channel_count: 1,
690 io_queue_depth: None,
691 devices: vec![ScsiDeviceAndPath {
692 path: ScsiPath {
693 path: 0,
694 target: 0,
695 lun: 0,
696 },
697 device: SimpleScsiDiskHandle {
698 read_only: false,
699 parameters: Default::default(),
700 disk: memdiff_disk_from_artifact(
701 disk_path.expect("not uefi guest none"),
702 )?,
703 }
704 .into_resource(),
705 }],
706 requests: None,
707 }
708 .into_resource(),
709 )]);
710 }
711 Firmware::OpenhclUefi {
712 guest,
713 vtl2_nvme_boot: true,
714 ..
715 } => {
716 let disk_path = guest.artifact();
717 devices.extend([Device::Vpci(VpciDeviceConfig {
718 vtl: DeviceVtl::Vtl2,
719 instance_id: BOOT_NVME_INSTANCE,
720 resource: NvmeControllerHandle {
721 subsystem_id: BOOT_NVME_INSTANCE,
722 max_io_queues: 64,
723 msix_count: 64,
724 namespaces: vec![NamespaceDefinition {
725 nsid: BOOT_NVME_NSID,
726 disk: memdiff_disk_from_artifact(
727 disk_path.expect("not uefi guest none"),
728 )?,
729 read_only: false,
730 }],
731 }
732 .into_resource(),
733 })]);
734 vtl2_settings
735 .expect("openhcl config should have vtl2settings")
736 .dynamic
737 .as_mut()
738 .unwrap()
739 .storage_controllers
740 .push(vtl2_settings_proto::StorageController {
741 instance_id: SCSI_INSTANCE.to_string(),
742 protocol: vtl2_settings_proto::storage_controller::StorageProtocol::Scsi
743 .into(),
744 luns: vec![vtl2_settings_proto::Lun {
745 location: BOOT_NVME_LUN,
746 device_id: Guid::new_random().to_string(),
747 vendor_id: "OpenVMM".to_string(),
748 product_id: "Disk".to_string(),
749 product_revision_level: "1.0".to_string(),
750 serial_number: "0".to_string(),
751 model_number: "1".to_string(),
752 physical_devices: Some(vtl2_settings_proto::PhysicalDevices {
753 r#type: vtl2_settings_proto::physical_devices::BackingType::Single
754 .into(),
755 device: Some(vtl2_settings_proto::PhysicalDevice {
756 device_type:
757 vtl2_settings_proto::physical_device::DeviceType::Nvme
758 .into(),
759 device_path: BOOT_NVME_INSTANCE.to_string(),
760 sub_device_path: BOOT_NVME_NSID,
761 }),
762 devices: Vec::new(),
763 }),
764 ..Default::default()
765 }],
766 io_queue_depth: None,
767 });
768 }
769 }
770
771 Ok(())
772 }
773
774 fn config_openhcl_vmbus_devices(
775 &self,
776 serial: &mut [Option<Resource<SerialBackendHandle>>],
777 devices: &mut impl Extend<Device>,
778 firmware_event_send: &mesh::Sender<FirmwareEvent>,
779 framebuffer: bool,
780 ) -> anyhow::Result<(
781 get_resources::ged::GuestEmulationDeviceHandle,
782 mesh::Sender<get_resources::ged::GuestEmulationRequest>,
783 )> {
784 let serial0 = serial[0].take();
785 devices.extend([Device::Vmbus(
786 DeviceVtl::Vtl2,
787 VmbusSerialDeviceHandle {
788 port: VmbusSerialPort::Com1,
789 backend: serial0.unwrap_or_else(|| DisconnectedSerialBackendHandle.into_resource()),
790 }
791 .into_resource(),
792 )]);
793 let serial1 = serial[1].take();
794 devices.extend([Device::Vmbus(
795 DeviceVtl::Vtl2,
796 VmbusSerialDeviceHandle {
797 port: VmbusSerialPort::Com2,
798 backend: serial1.unwrap_or_else(|| DisconnectedSerialBackendHandle.into_resource()),
799 }
800 .into_resource(),
801 )]);
802
803 let gel = get_resources::gel::GuestEmulationLogHandle.into_resource();
804
805 let crash = spawn_dump_handler(self.driver, self.logger).into_resource();
806
807 devices.extend([
808 Device::Vmbus(DeviceVtl::Vtl2, crash),
809 Device::Vmbus(DeviceVtl::Vtl2, gel),
810 ]);
811
812 let (guest_request_send, guest_request_recv) = mesh::channel();
813
814 let ged = get_resources::ged::GuestEmulationDeviceHandle {
816 firmware: get_resources::ged::GuestFirmwareConfig::Uefi {
817 firmware_debug: false,
818 disable_frontpage: true,
819 enable_vpci_boot: false,
820 console_mode: get_resources::ged::UefiConsoleMode::COM1,
821 default_boot_always_attempt: false,
822 },
823 com1: true,
824 com2: true,
825 vmbus_redirection: false,
826 vtl2_settings: None, vmgs: VmgsResource::Disk(
828 LayeredDiskHandle::single_layer(RamDiskLayerHandle {
829 len: Some(vmgs_format::VMGS_DEFAULT_CAPACITY),
830 })
831 .into_resource(),
832 ),
833 framebuffer: framebuffer.then(|| SharedFramebufferHandle.into_resource()),
834 guest_request_recv,
835 enable_tpm: false,
836 firmware_event_send: Some(firmware_event_send.clone()),
837 secure_boot_enabled: false,
838 secure_boot_template: get_resources::ged::GuestSecureBootTemplateType::None,
839 enable_battery: false,
840 no_persistent_secrets: true,
841 igvm_attest_test_config: None,
842 };
843
844 Ok((ged, guest_request_send))
845 }
846
847 fn config_video(
848 &self,
849 ) -> anyhow::Result<Option<(VideoDevice, Framebuffer, FramebufferAccess)>> {
850 if self.firmware.isolation().is_some() {
851 return Ok(None);
852 }
853
854 let video_dev = match self.firmware {
855 Firmware::Pcat { svga_firmware, .. } => Some(VideoDevice::Vga(
856 hvlite_pcat_locator::find_svga_bios(svga_firmware.get())
857 .context("Failed to load VGA BIOS")?,
858 )),
859 Firmware::Uefi { .. } | Firmware::OpenhclUefi { .. } => Some(VideoDevice::Synth(
860 DeviceVtl::Vtl0,
861 SynthVideoHandle {
862 framebuffer: SharedFramebufferHandle.into_resource(),
863 }
864 .into_resource(),
865 )),
866 Firmware::OpenhclLinuxDirect { .. } | Firmware::LinuxDirect { .. } => None,
867 };
868
869 Ok(if let Some(vdev) = video_dev {
870 let vram = alloc_shared_memory(FRAMEBUFFER_SIZE).context("allocating framebuffer")?;
871 let (fb, fba) = framebuffer::framebuffer(vram, FRAMEBUFFER_SIZE, 0)
872 .context("creating framebuffer")?;
873 Some((vdev, fb, fba))
874 } else {
875 None
876 })
877 }
878}
879
880fn spawn_dump_handler(driver: &DefaultDriver, logger: &PetriLogSource) -> GuestCrashDeviceHandle {
881 let (send, mut recv) = mesh::channel();
882 let handle = GuestCrashDeviceHandle {
883 request_dump: send,
884 max_dump_size: 256 * 1024 * 1024,
885 };
886 driver
887 .spawn("openhcl-dump-handler", {
888 let logger = logger.clone();
889 let driver = driver.clone();
890 async move {
891 while let Some(rpc) = recv.next().await {
892 rpc.handle_failable_sync(|done| {
893 let (file, path) = logger.create_attachment("openhcl.core")?.into_parts();
894 driver
895 .spawn("crash-waiter", async move {
896 let filename = path.file_name().unwrap().to_str().unwrap();
897 if done.await.is_ok() {
898 tracing::warn!(filename, "openhcl crash dump complete");
899 } else {
900 tracing::error!(
901 filename,
902 "openhcl crash dump incomplete, may be corrupted"
903 );
904 }
905 })
906 .detach();
907 anyhow::Ok(file)
908 })
909 }
910 }
911 })
912 .detach();
913 handle
914}