1use super::PetriVmConfigOpenVmm;
8use super::PetriVmResourcesOpenVmm;
9use crate::Drive;
10use crate::EfiDiagnosticsLogLevel;
11use crate::Firmware;
12use crate::IsolationType;
13use crate::MemoryConfig;
14use crate::OpenHclConfig;
15use crate::PcieNvmeDrive;
16use crate::PcieVirtioBlkDrive;
17use crate::PetriLogSource;
18use crate::PetriVmConfig;
19use crate::PetriVmResources;
20use crate::PetriVmgsResource;
21use crate::ProcessorTopology;
22use crate::SecureBootTemplate;
23use crate::TpmConfig;
24use crate::UefiConfig;
25use crate::VmbusStorageType;
26use crate::linux_direct_serial_agent::LinuxDirectSerialAgent;
27
28use crate::SIZE_1_MB;
29use crate::VmbusStorageController;
30use crate::openvmm::memdiff_vmgs;
31use crate::openvmm::petri_disk_to_openvmm;
32use crate::vm::PetriVmProperties;
33use crate::vm::append_cmdline;
34use anyhow::Context;
35use framebuffer::FRAMEBUFFER_SIZE;
36use framebuffer::Framebuffer;
37use framebuffer::FramebufferAccess;
38use fs_err::File;
39use futures::StreamExt;
40use get_resources::crash::GuestCrashDeviceHandle;
41use get_resources::ged::FirmwareEvent;
42use guid::Guid;
43use hyperv_ic_resources::shutdown::ShutdownIcHandle;
44use ide_resources::GuestMedia;
45use ide_resources::IdeDeviceConfig;
46use mesh_process::Mesh;
47use nvme_resources::NamespaceDefinition;
48use nvme_resources::NvmeControllerHandle;
49use openvmm_defs::config::Config;
50use openvmm_defs::config::DEFAULT_PCAT_BOOT_ORDER;
51use openvmm_defs::config::DeviceVtl;
52use openvmm_defs::config::HypervisorConfig;
53use openvmm_defs::config::LateMapVtl0MemoryPolicy;
54use openvmm_defs::config::LoadMode;
55use openvmm_defs::config::NumaNode;
56use openvmm_defs::config::NumaTopology;
57use openvmm_defs::config::PcieDeviceConfig;
58use openvmm_defs::config::ProcessorTopologyConfig;
59use openvmm_defs::config::SerialInformation;
60use openvmm_defs::config::VmbusConfig;
61use openvmm_defs::config::VpAssignment;
62use openvmm_defs::config::VpciDeviceConfig;
63use openvmm_defs::config::Vtl2BaseAddressType;
64use openvmm_defs::config::Vtl2Config;
65use openvmm_pcat_locator::RomFileLocation;
66use pal_async::DefaultDriver;
67use pal_async::socket::PolledSocket;
68use pal_async::task::Spawn;
69use pal_async::task::Task;
70use petri_artifacts_common::tags::MachineArch;
71use petri_artifacts_core::ResolvedArtifact;
72use pipette_client::PIPETTE_PORT;
73use scsidisk_resources::SimpleScsiDiskHandle;
74use scsidisk_resources::SimpleScsiDvdHandle;
75use serial_16550_resources::ComPort;
76use serial_core::resources::DisconnectedSerialBackendHandle;
77use serial_socket::net::OpenSocketSerialConfig;
78use sparse_mmap::alloc_shared_memory;
79use std::collections::HashMap;
80use storvsp_resources::ScsiControllerHandle;
81use storvsp_resources::ScsiDeviceAndPath;
82use storvsp_resources::ScsiPath;
83use tempfile::TempPath;
84use tpm_resources::TpmDeviceHandle;
85use tpm_resources::TpmRegisterLayout;
86use uidevices_resources::SynthVideoHandle;
87use unix_socket::UnixListener;
88use unix_socket::UnixStream;
89use video_core::SharedFramebufferHandle;
90use virtio_resources::VirtioPciDeviceHandle;
91use virtio_resources::blk::VirtioBlkHandle;
92use virtio_resources::vsock::VirtioVsockHandle;
93#[cfg(target_os = "linux")]
94use virtio_resources::vsock::VirtioVsockVhostHandle;
95use vm_manifest_builder::VmChipsetResult;
96use vm_manifest_builder::VmManifestBuilder;
97use vm_resource::IntoResource;
98use vm_resource::Resource;
99use vm_resource::kind::SerialBackendHandle;
100use vm_resource::kind::VirtioDeviceHandle;
101use vm_resource::kind::VmbusDeviceHandleKind;
102use vmbus_serial_resources::VmbusSerialDeviceHandle;
103use vmbus_serial_resources::VmbusSerialPort;
104use vmcore::non_volatile_store::resources::EphemeralNonVolatileStoreHandle;
105use vmgs_resources::GuestStateEncryptionPolicy;
106use vmgs_resources::VmgsFileHandle;
107use vmotherboard::ChipsetDeviceHandle;
108
109impl PetriVmConfigOpenVmm {
110 pub async fn new(
112 openvmm_path: &ResolvedArtifact,
113 petri_vm_config: PetriVmConfig,
114 resources: &PetriVmResources,
115 properties: PetriVmProperties,
116 ) -> anyhow::Result<Self> {
117 let PetriVmConfig {
118 name: _,
119 arch,
120 host_log_levels,
121 firmware,
122 memory,
123 proc_topology,
124 vmgs,
125 tpm: tpm_config,
126 vmbus_storage_controllers,
127 pcie_nvme_drives,
128 pcie_virtio_blk_drives,
129 physical_nvme_devices,
130 } = petri_vm_config;
131
132 if !physical_nvme_devices.is_empty() {
133 anyhow::bail!("Physical NVMe devices are only supported with the Hyper-V backend");
134 }
135
136 tracing::debug!(?firmware, ?arch, "Petri VM firmware configuration");
137
138 let PetriVmResources { driver, log_source } = resources;
139 #[cfg(target_os = "linux")]
140 let vhost_vsock_guest_cid = properties.vhost_vsock_guest_cid;
141 #[cfg(not(target_os = "linux"))]
142 let vhost_vsock_guest_cid: Option<u32> = None;
143
144 let mesh = Mesh::new("petri_mesh".to_string())?;
145
146 let setup = PetriVmConfigSetupCore {
147 arch,
148 firmware: &firmware,
149 driver,
150 logger: log_source,
151 vmgs: &vmgs,
152 tpm_config: tpm_config.as_ref(),
153 mesh: &mesh,
154 openvmm_path,
155 uses_pipette_as_init: properties.uses_pipette_as_init,
156 enable_serial: properties.enable_serial,
157 use_virtio_vsock: properties.use_virtio_vsock,
158 no_vmbus: properties.no_vmbus,
159 };
160
161 let mut chipset = VmManifestBuilder::new(
162 match firmware {
163 Firmware::LinuxDirect { .. } => {
164 vm_manifest_builder::BaseChipsetType::HyperVGen2LinuxDirect
165 }
166 Firmware::OpenhclLinuxDirect { .. } => {
167 vm_manifest_builder::BaseChipsetType::HclHost
168 }
169 Firmware::OpenhclUefi { .. } => vm_manifest_builder::BaseChipsetType::HclHost,
170 Firmware::Pcat { .. } => vm_manifest_builder::BaseChipsetType::HypervGen1,
171 Firmware::Uefi { .. } => vm_manifest_builder::BaseChipsetType::HypervGen2Uefi,
172 Firmware::OpenhclPcat { .. } => todo!("OpenVMM OpenHCL PCAT"),
173 },
174 match arch {
175 MachineArch::X86_64 => vm_manifest_builder::MachineArch::X86_64,
176 MachineArch::Aarch64 => vm_manifest_builder::MachineArch::Aarch64,
177 },
178 );
179
180 let mut load_mode = setup.load_firmware()?;
181
182 if properties.uses_pipette_as_init {
186 if let LoadMode::Linux { initrd, .. } = &mut load_mode {
187 let prebuilt = properties
188 .prebuilt_initrd
189 .as_ref()
190 .expect("uses_pipette_as_init requires prebuilt_initrd");
191 let file = std::fs::File::open(prebuilt).with_context(|| {
192 format!("failed to open prebuilt initrd at {}", prebuilt.display())
193 })?;
194 *initrd = Some(file);
195 }
196 }
197
198 let (emulated_serial_config, log_stream_tasks, linux_direct_serial_agent) =
199 if !properties.enable_serial {
200 ([None, None, None, None], Vec::new(), None)
202 } else {
203 let SerialData {
204 emulated_serial_config,
205 serial_tasks,
206 linux_direct_serial_agent,
207 } = setup.configure_serial(log_source)?;
208 (
209 emulated_serial_config,
210 serial_tasks,
211 linux_direct_serial_agent,
212 )
213 };
214 let mut emulated_serial_config = emulated_serial_config;
215
216 let (video_dev, framebuffer, framebuffer_view) = match setup.config_video()? {
217 Some((v, fb, fba)) => {
218 chipset = chipset.with_framebuffer();
219 (Some(v), Some(fb), Some(fba.view()?))
220 }
221 None => (None, None, None),
222 };
223
224 if properties.no_vmbus {
225 chipset = chipset.without_vmbus();
226 }
227
228 let (ide_disks, storvsp_ide_handles) =
229 ide_controllers_to_openvmm(firmware.ide_controllers()).await?;
230 let (mut vmbus_devices, vpci_devices) =
231 vmbus_storage_controllers_to_openvmm(&vmbus_storage_controllers).await?;
232
233 let mut pcie_devices = Vec::new();
234 for PcieNvmeDrive {
235 port_name,
236 nsid,
237 drive: Drive { disk, .. },
238 } in pcie_nvme_drives
239 {
240 let disk = disk.ok_or_else(|| {
241 anyhow::anyhow!(
242 "missing disk for PCIe NVMe drive on port '{port_name}' (nsid {nsid})"
243 )
244 })?;
245 let disk = petri_disk_to_openvmm(&disk).await?;
246 pcie_devices.push(PcieDeviceConfig {
247 port_name,
248 resource: NvmeControllerHandle {
249 subsystem_id: Guid::new_random(),
250 max_io_queues: 64,
251 msix_count: 64,
252 namespaces: vec![NamespaceDefinition {
253 nsid,
254 read_only: false,
255 disk,
256 }],
257 requests: None,
258 }
259 .into_resource(),
260 });
261 }
262
263 for PcieVirtioBlkDrive {
264 port_name,
265 drive: Drive { disk, .. },
266 } in pcie_virtio_blk_drives
267 {
268 let disk = disk.ok_or_else(|| {
269 anyhow::anyhow!("missing disk for PCIe virtio-blk drive on port '{port_name}'")
270 })?;
271 let disk = petri_disk_to_openvmm(&disk).await?;
272 pcie_devices.push(PcieDeviceConfig {
273 port_name,
274 resource: VirtioPciDeviceHandle(
275 VirtioBlkHandle {
276 disk,
277 read_only: false,
278 }
279 .into_resource(),
280 )
281 .into_resource(),
282 });
283 }
284
285 if !storvsp_ide_handles.is_empty() {
286 anyhow::ensure!(
287 !properties.no_vmbus,
288 "IDE accelerator requires VMBus to be enabled"
289 );
290 vmbus_devices.extend(storvsp_ide_handles);
291 }
292
293 let (firmware_event_send, firmware_event_recv) = mesh::mpsc_channel();
294
295 let make_vsock_listener = || -> anyhow::Result<(UnixListener, TempPath)> {
296 Ok(tempfile::Builder::new()
297 .make(|path| UnixListener::bind(path))?
298 .into_parts())
299 };
300
301 let (with_vtl2, vtl2_vmbus, ged, ged_send, vtl2_vsock_path) = if firmware.is_openhcl() {
302 let (ged, ged_send) = setup
303 .config_openhcl_vmbus_devices(
304 &mut emulated_serial_config,
305 &mut vmbus_devices,
306 &firmware_event_send,
307 framebuffer.is_some(),
308 )
309 .await?;
310
311 let late_map_vtl0_memory = match load_mode {
312 LoadMode::Igvm {
313 vtl2_base_address: Vtl2BaseAddressType::Vtl2Allocate { .. },
314 ..
315 } => {
316 None
318 }
319 _ => Some(LateMapVtl0MemoryPolicy::InjectException),
320 };
321
322 let (vtl2_vsock_listener, vtl2_vsock_path) = make_vsock_listener()?;
323 (
324 Some(Vtl2Config {
325 vtl0_alias_map: false, late_map_vtl0_memory,
327 }),
328 Some(VmbusConfig {
329 vsock_listener: Some(vtl2_vsock_listener),
330 vsock_path: Some(vtl2_vsock_path.to_string_lossy().into_owned()),
331 vmbus_max_version: None,
332 vtl2_redirect: false,
333 #[cfg(windows)]
334 vmbusproxy_handle: None,
335 }),
336 Some(ged),
337 Some(ged_send),
338 Some(vtl2_vsock_path),
339 )
340 } else {
341 (None, None, None, None, None)
342 };
343
344 if properties.enable_serial {
347 chipset = chipset.with_serial(emulated_serial_config);
348 if matches!(firmware, Firmware::LinuxDirect { .. }) && !properties.uses_pipette_as_init
352 {
353 chipset = chipset.with_serial_wait_for_rts();
354 }
355 }
356
357 let vga_firmware = match video_dev {
359 Some(VideoDevice::Vga(firmware)) => Some(firmware),
360 Some(VideoDevice::Synth(vtl, resource)) => {
361 vmbus_devices.push((vtl, resource));
362 None
363 }
364 None => None,
365 };
366
367 let (shutdown_ic_send, kvp_ic_send) = if !properties.minimal_mode && !properties.no_vmbus {
369 let (shutdown_ic_send, shutdown_ic_recv) = mesh::channel();
370 vmbus_devices.push((
371 DeviceVtl::Vtl0,
372 ShutdownIcHandle {
373 recv: shutdown_ic_recv,
374 }
375 .into_resource(),
376 ));
377
378 let (kvp_ic_send, kvp_ic_recv) = mesh::channel();
379 vmbus_devices.push((
380 DeviceVtl::Vtl0,
381 hyperv_ic_resources::kvp::KvpIcHandle { recv: kvp_ic_recv }.into_resource(),
382 ));
383
384 vmbus_devices.push((
385 DeviceVtl::Vtl0,
386 hyperv_ic_resources::timesync::TimesyncIcHandle.into_resource(),
387 ));
388
389 (Some(shutdown_ic_send), Some(kvp_ic_send))
390 } else {
391 (None, None)
392 };
393
394 let (vsock_listener, vsock_path) = make_vsock_listener()?;
396 let mut vsock_listener = Some(vsock_listener);
397 let vsock_path_string = vsock_path.to_string_lossy();
398
399 if matches!(firmware, Firmware::Uefi { .. }) {
402 use firmware_uefi_resources::aarch64_secure_boot_templates;
403 use firmware_uefi_resources::x64_secure_boot_templates;
404
405 let uefi_cfg = firmware.uefi_config();
406 let base_template_json =
407 uefi_cfg
408 .and_then(|c| c.secure_boot_template)
409 .map(|template| match (arch, template) {
410 (MachineArch::X86_64, SecureBootTemplate::MicrosoftWindows) => {
411 x64_secure_boot_templates::microsoft_windows()
412 }
413 (
414 MachineArch::X86_64,
415 SecureBootTemplate::MicrosoftUefiCertificateAuthority,
416 ) => x64_secure_boot_templates::microsoft_uefi_ca(),
417 (MachineArch::Aarch64, SecureBootTemplate::MicrosoftWindows) => {
418 aarch64_secure_boot_templates::microsoft_windows()
419 }
420 (
421 MachineArch::Aarch64,
422 SecureBootTemplate::MicrosoftUefiCertificateAuthority,
423 ) => aarch64_secure_boot_templates::microsoft_uefi_ca(),
424 });
425 let custom_uefi_json = uefi_cfg
426 .and_then(|c| c.custom_uefi_json.clone())
427 .map(Into::into);
428 let secure_boot = uefi_cfg.is_some_and(|c| c.secure_boot_enabled);
429 let log_level = match uefi_cfg
430 .map(|c| c.efi_diagnostics_log_level)
431 .unwrap_or_default()
432 {
433 EfiDiagnosticsLogLevel::Default => {
434 firmware_uefi_resources::LogLevel::make_default()
435 }
436 EfiDiagnosticsLogLevel::Info => firmware_uefi_resources::LogLevel::make_info(),
437 EfiDiagnosticsLogLevel::Full => firmware_uefi_resources::LogLevel::make_full(),
438 };
439 let diagnostics_rate_limit = uefi_cfg.and_then(|c| c.efi_diagnostics_rate_limit);
440 let nvram_storage = if vmgs.disk().is_some() {
441 VmgsFileHandle::new(vmgs_format::FileId::BIOS_NVRAM, true).into_resource()
442 } else {
443 EphemeralNonVolatileStoreHandle.into_resource()
444 };
445 chipset = chipset.with_uefi(vm_manifest_builder::UefiManifest::new(
446 match arch {
447 MachineArch::X86_64 => vm_manifest_builder::MachineArch::X86_64,
448 MachineArch::Aarch64 => vm_manifest_builder::MachineArch::Aarch64,
449 },
450 base_template_json,
451 custom_uefi_json,
452 secure_boot,
453 log_level,
454 diagnostics_rate_limit,
455 nvram_storage,
456 None,
457 ));
458 }
459
460 let layout_config = chipset.layout_config();
461 let chipset = chipset
462 .build()
463 .context("failed to build chipset configuration")?;
464
465 let requested_private_memory = memory.private_memory;
468
469 let numa = {
470 let MemoryConfig {
471 startup_bytes,
472 dynamic_memory_range,
473 numa_mem_sizes,
474 private_memory,
475 transparent_hugepages,
476 } = memory;
477
478 if dynamic_memory_range.is_some() {
479 anyhow::bail!("dynamic memory not supported in OpenVMM");
480 }
481
482 let private_incompatible =
490 firmware.is_openhcl() || firmware.is_pcat() || vhost_vsock_guest_cid.is_some();
491 let private_memory = match private_memory {
492 Some(true) if private_incompatible => {
495 anyhow::bail!(
496 "private guest memory was explicitly requested but is \
497 not supported with this configuration (OpenHCL, \
498 PCAT/Gen1, and kernel vhost-vsock require shared memory)"
499 );
500 }
501 Some(explicit) => explicit,
502 None => !private_incompatible,
505 };
506
507 let make_mem = |size: u64| openvmm_defs::config::MemoryConfig {
513 mem_size: size,
514 prefetch_memory: false,
515 private_memory,
516 transparent_hugepages,
517 hugepages: false,
518 hugepage_size: None,
519 host_numa_node: None,
520 };
521
522 if let Some(sizes) = numa_mem_sizes {
523 NumaTopology {
524 nodes: sizes
525 .into_iter()
526 .map(|size| NumaNode {
527 mem: if size > 0 { Some(make_mem(size)) } else { None },
528 vps: VpAssignment::FromTopology,
529 })
530 .collect(),
531 distances: vec![],
532 }
533 } else {
534 NumaTopology {
535 nodes: vec![NumaNode {
536 mem: Some(make_mem(startup_bytes)),
537 vps: VpAssignment::FromTopology,
538 }],
539 distances: vec![],
540 }
541 }
542 };
543
544 let processor_topology = {
545 let ProcessorTopology {
546 vp_count,
547 enable_smt,
548 vps_per_socket,
549 apic_mode,
550 } = proc_topology;
551
552 ProcessorTopologyConfig {
553 proc_count: vp_count,
554 vps_per_socket,
555 enable_smt,
556 arch: Some(match arch {
557 MachineArch::X86_64 => openvmm_defs::config::ArchTopologyConfig::X86(
558 openvmm_defs::config::X86TopologyConfig {
559 x2apic: match apic_mode {
560 None => openvmm_defs::config::X2ApicConfig::Auto,
561 Some(x) => match x {
562 crate::ApicMode::Xapic => {
563 openvmm_defs::config::X2ApicConfig::Unsupported
564 }
565 crate::ApicMode::X2apicSupported => {
566 openvmm_defs::config::X2ApicConfig::Supported
567 }
568 crate::ApicMode::X2apicEnabled => {
569 openvmm_defs::config::X2ApicConfig::Enabled
570 }
571 },
572 },
573 ..Default::default()
574 },
575 ),
576 MachineArch::Aarch64 => openvmm_defs::config::ArchTopologyConfig::Aarch64(
577 openvmm_defs::config::Aarch64TopologyConfig::default(),
578 ),
579 }),
580 }
581 };
582
583 let vmgs = if firmware.is_openhcl() {
584 None
585 } else {
586 Some(memdiff_vmgs(&vmgs).await?)
587 };
588
589 let VmChipsetResult {
590 chipset,
591 mut chipset_devices,
592 pci_chipset_devices,
593 isa_dma_controller,
594 capabilities,
595 } = chipset;
596
597 if let Some(tpm) = setup.config_tpm().await? {
599 chipset_devices.push(tpm);
600 }
601
602 if properties.use_virtio_vsock {
606 let vsock_port = (0..)
607 .map(|i| format!("s0rc0rp{i}"))
608 .find(|name| !pcie_devices.iter().any(|d| d.port_name == *name))
609 .unwrap();
610 let resource: Resource<VirtioDeviceHandle> = match vhost_vsock_guest_cid {
611 #[cfg(target_os = "linux")]
612 Some(guest_cid) => {
613 let vhost = std::fs::OpenOptions::new()
614 .read(true)
615 .write(true)
616 .open("/dev/vhost-vsock")
617 .context("failed to open /dev/vhost-vsock")?
618 .into();
619 vsock_listener = None;
622 VirtioVsockVhostHandle { vhost, guest_cid }.into_resource()
623 }
624 #[cfg(not(target_os = "linux"))]
625 Some(_) => unreachable!("kernel vhost-vsock is Linux-only"),
626 None => VirtioVsockHandle {
627 guest_cid: 0x3,
628 base_path: vsock_path_string.to_string(),
629 listener: vsock_listener.take().unwrap(),
630 }
631 .into_resource(),
632 };
633 pcie_devices.push(PcieDeviceConfig {
634 port_name: vsock_port,
635 resource: VirtioPciDeviceHandle(resource).into_resource(),
636 });
637 }
638
639 let config = Config {
640 load_mode,
642 firmware_event_send: Some(firmware_event_send),
643
644 numa,
646 processor_topology,
647
648 chipset,
650 chipset_devices,
651 pci_chipset_devices,
652 isa_dma_controller,
653 chipset_capabilities: capabilities,
654 layout: layout_config,
655
656 hypervisor: HypervisorConfig {
658 with_hv: true,
659 with_vtl2,
660 with_isolation: match firmware.isolation() {
661 Some(IsolationType::Vbs) => Some(openvmm_defs::config::IsolationType::Vbs),
662 None => None,
663 _ => anyhow::bail!("unsupported isolation type"),
664 },
665 nested_virt: false,
666 },
667 vmbus: if properties.no_vmbus {
668 None
669 } else {
670 Some(VmbusConfig {
671 vsock_listener,
674 vsock_path: (!properties.use_virtio_vsock)
675 .then(|| vsock_path_string.to_string()),
676 vmbus_max_version: None,
677 vtl2_redirect: firmware.openhcl_config().is_some_and(|c| c.vmbus_redirect),
678 #[cfg(windows)]
679 vmbusproxy_handle: None,
680 })
681 },
682 vtl2_vmbus,
683
684 floppy_disks: vec![],
686 ide_disks,
687 pcie_root_complexes: vec![],
688 pcie_devices,
689 pcie_switches: vec![],
690 pcie_generic_initiators: vec![],
691 vpci_devices,
692 vmbus_devices,
693
694 framebuffer,
696 vga_firmware,
697
698 vmgs,
699
700 #[cfg(windows)]
702 kernel_vmnics: vec![],
703 input: mesh::Receiver::new(),
704 vtl2_gfx: false,
705 virtio_devices: vec![],
706 #[cfg(windows)]
707 vpci_resources: vec![],
708 debugger_rpc: None,
709 rtc_delta_milliseconds: 0,
710 };
711
712 let path = format!("{vsock_path_string}_{PIPETTE_PORT}");
714 let pipette_listener = PolledSocket::new(
715 driver,
716 UnixListener::bind(path).context("failed to bind to pipette listener")?,
717 )?;
718
719 let vtl2_pipette_listener = if let Some(vtl2_vmbus) = &config.vtl2_vmbus {
721 let path = vtl2_vmbus.vsock_path.as_ref().unwrap();
722 let path = format!("{path}_{PIPETTE_PORT}");
723 Some(PolledSocket::new(
724 driver,
725 UnixListener::bind(path).context("failed to bind to vtl2 pipette listener")?,
726 )?)
727 } else {
728 None
729 };
730
731 Ok(Self {
732 runtime_config: firmware.into_runtime_config(vmbus_storage_controllers),
733 arch,
734 host_log_levels,
735 config,
736 mesh,
737
738 resources: PetriVmResourcesOpenVmm {
739 log_stream_tasks,
740 firmware_event_recv,
741 shutdown_ic_send,
742 kvp_ic_send,
743 ged_send,
744 pipette_listener,
745 vtl2_pipette_listener,
746 linux_direct_serial_agent,
747 tcp_pipette_port: None,
748 driver: driver.clone(),
749 output_dir: log_source.output_dir().to_owned(),
750 openvmm_path: openvmm_path.clone(),
751 vtl2_vsock_path,
752 _vsock_path: vsock_path,
753 properties,
754 #[cfg(windows)]
755 _switch_ports: Vec::new(),
756 },
757
758 openvmm_log_file: log_source.log_file("openvmm")?,
759
760 memory_backing_file: None,
761 requested_private_memory,
762
763 ged,
764 framebuffer_view,
765
766 pending_iommu: Vec::new(),
767 })
768 }
769}
770
771struct PetriVmConfigSetupCore<'a> {
772 arch: MachineArch,
773 firmware: &'a Firmware,
774 driver: &'a DefaultDriver,
775 logger: &'a PetriLogSource,
776 vmgs: &'a PetriVmgsResource,
777 tpm_config: Option<&'a TpmConfig>,
778 mesh: &'a Mesh,
779 openvmm_path: &'a ResolvedArtifact,
780 uses_pipette_as_init: bool,
781 enable_serial: bool,
782 use_virtio_vsock: bool,
783 no_vmbus: bool,
784}
785
786struct SerialData {
787 emulated_serial_config: [Option<Resource<SerialBackendHandle>>; 4],
788 serial_tasks: Vec<Task<anyhow::Result<()>>>,
789 linux_direct_serial_agent: Option<LinuxDirectSerialAgent>,
790}
791
792enum VideoDevice {
793 Vga(RomFileLocation),
794 Synth(DeviceVtl, Resource<VmbusDeviceHandleKind>),
795}
796
797impl PetriVmConfigSetupCore<'_> {
798 fn configure_serial(&self, logger: &PetriLogSource) -> anyhow::Result<SerialData> {
799 let mut serial_tasks = Vec::new();
800
801 let serial0_log_file = logger.log_file(match self.firmware {
802 Firmware::LinuxDirect { .. } | Firmware::OpenhclLinuxDirect { .. } => "linux",
803 Firmware::Pcat { .. } | Firmware::OpenhclPcat { .. } => "pcat",
804 Firmware::Uefi { .. } | Firmware::OpenhclUefi { .. } => "uefi",
805 })?;
806
807 let (serial0_host, serial0) = self
808 .create_serial_stream()
809 .context("failed to create serial0 stream")?;
810 let (serial0_read, serial0_write) = serial0_host.split();
811 let serial0_task = self.driver.spawn(
812 "serial0-console",
813 crate::log_task(serial0_log_file, serial0_read, "serial0-console"),
814 );
815 serial_tasks.push(serial0_task);
816
817 let serial2 = if self.firmware.is_openhcl() {
818 let (serial2_host, serial2) = self
819 .create_serial_stream()
820 .context("failed to create serial2 stream")?;
821 let serial2_task = self.driver.spawn(
822 "serial2-openhcl",
823 crate::log_task(logger.log_file("openhcl")?, serial2_host, "serial2-openhcl"),
824 );
825 serial_tasks.push(serial2_task);
826 serial2
827 } else {
828 None
829 };
830
831 if self.firmware.is_linux_direct() && !self.uses_pipette_as_init {
832 let (serial1_host, serial1) = self.create_serial_stream()?;
835 let (serial1_read, _serial1_write) = serial1_host.split();
836 let linux_direct_serial_agent =
837 LinuxDirectSerialAgent::new(serial1_read, serial0_write);
838 Ok(SerialData {
839 emulated_serial_config: [serial0, serial1, serial2, None],
840 serial_tasks,
841 linux_direct_serial_agent: Some(linux_direct_serial_agent),
842 })
843 } else {
844 Ok(SerialData {
845 emulated_serial_config: [serial0, None, serial2, None],
846 serial_tasks,
847 linux_direct_serial_agent: None,
848 })
849 }
850 }
851
852 fn create_serial_stream(
853 &self,
854 ) -> anyhow::Result<(
855 PolledSocket<UnixStream>,
856 Option<Resource<SerialBackendHandle>>,
857 )> {
858 let (host_side, guest_side) = UnixStream::pair()?;
859 let host_side = PolledSocket::new(self.driver, host_side)?;
860 let serial = OpenSocketSerialConfig::from(guest_side).into_resource();
861 Ok((host_side, Some(serial)))
862 }
863
864 fn load_firmware(&self) -> anyhow::Result<LoadMode> {
865 const VIRTIO_VSOCK_BLACKLIST: &str = "initcall_blacklist=virtio_vsock_init";
873 let vsock_blacklist = if self.use_virtio_vsock {
874 "initcall_blacklist=hv_sock_init"
875 } else {
876 VIRTIO_VSOCK_BLACKLIST
877 };
878
879 Ok(match (self.arch, &self.firmware) {
880 (arch, Firmware::LinuxDirect { kernel, initrd }) => {
881 let console = match arch {
882 MachineArch::X86_64 => "console=ttyS0",
883 MachineArch::Aarch64 => "console=ttyAMA0 earlycon",
884 };
885 let kernel = File::open(kernel.clone())
886 .context("Failed to open kernel")?
887 .into();
888 let initrd = File::open(initrd.clone())
889 .context("Failed to open initrd")?
890 .into();
891
892 let init = if self.uses_pipette_as_init {
893 "/pipette"
894 } else {
895 "/bin/sh"
896 };
897
898 let serial_args = if self.enable_serial {
899 format!("{console} debug ")
900 } else {
901 String::new()
902 };
903
904 let cmdline = format!("{serial_args}panic=-1 rdinit={init} {vsock_blacklist}");
905
906 LoadMode::Linux {
907 kernel,
908 initrd: Some(initrd),
909 cmdline,
910 enable_serial: self.enable_serial,
911 boot_mode: openvmm_defs::config::LinuxDirectBootMode::Acpi,
912 }
913 }
914 (
915 MachineArch::X86_64,
916 Firmware::Pcat {
917 bios_firmware: firmware,
918 guest: _, svga_firmware: _, ide_controllers: _,
921 },
922 ) => {
923 let firmware = openvmm_pcat_locator::find_pcat_bios(firmware.get())
924 .context("Failed to load packaged PCAT binary")?;
925 LoadMode::Pcat {
926 firmware,
927 boot_order: DEFAULT_PCAT_BOOT_ORDER,
928 }
929 }
930 (
931 _,
932 Firmware::Uefi {
933 uefi_firmware: firmware,
934 guest: _, uefi_config:
936 UefiConfig {
937 secure_boot_enabled: _, secure_boot_template: _, custom_uefi_json: _, disable_frontpage,
941 default_boot_always_attempt,
942 enable_vpci_boot,
943 force_dma_bounce,
944 efi_diagnostics_log_level: _, efi_diagnostics_rate_limit: _, },
947 },
948 ) => {
949 let firmware = File::open(firmware.clone())
950 .context("Failed to open uefi firmware file")?
951 .into();
952 LoadMode::Uefi {
953 firmware,
954 enable_debugging: false,
955 enable_memory_protections: false,
956 disable_frontpage: *disable_frontpage,
957 enable_tpm: self.tpm_config.is_some(),
958 enable_battery: false,
959 enable_serial: true,
960 enable_vpci_boot: *enable_vpci_boot,
961 uefi_console_mode: Some(openvmm_defs::config::UefiConsoleMode::Com1),
962 default_boot_always_attempt: *default_boot_always_attempt,
963 bios_guid: Guid::new_random(),
964 enable_vmbus: !self.no_vmbus,
965 force_dma_bounce: *force_dma_bounce,
966 }
967 }
968 (
969 MachineArch::X86_64,
970 Firmware::OpenhclLinuxDirect {
971 igvm_path,
972 openhcl_config,
973 }
974 | Firmware::OpenhclUefi {
975 igvm_path,
976 guest: _, isolation: _, uefi_config: _, openhcl_config,
980 },
981 ) => {
982 let OpenHclConfig {
983 vmbus_redirect: _, custom_command_line: _,
985 log_levels: _,
986 vtl2_base_address_type,
987 vtl2_settings: _, } = openhcl_config;
989
990 let mut cmdline = Some(openhcl_config.command_line());
991
992 append_cmdline(&mut cmdline, "panic=-1 reboot=triple");
993
994 let isolated = match self.firmware {
995 Firmware::OpenhclLinuxDirect { .. } => {
996 append_cmdline(
1000 &mut cmdline,
1001 format!(
1002 "UNDERHILL_SERIAL_WAIT_FOR_RTS=1 UNDERHILL_CMDLINE_APPEND=\"rdinit=/bin/sh {vsock_blacklist}\""
1003 ),
1004 );
1005 false
1006 }
1007 Firmware::OpenhclUefi { isolation, .. } if isolation.is_some() => true,
1008 _ => false,
1009 };
1010
1011 if let Firmware::OpenhclUefi {
1017 uefi_config:
1018 UefiConfig {
1019 default_boot_always_attempt,
1020 secure_boot_enabled,
1021 ..
1022 },
1023 ..
1024 } = self.firmware
1025 {
1026 if !isolated
1027 && !secure_boot_enabled
1028 && self.tpm_config.is_none()
1029 && !default_boot_always_attempt
1030 {
1031 append_cmdline(&mut cmdline, "HCL_DEFAULT_BOOT_ALWAYS_ATTEMPT=0");
1032 }
1033 }
1034
1035 if let Firmware::OpenhclUefi {
1038 uefi_config:
1039 UefiConfig {
1040 efi_diagnostics_rate_limit: Some(limit),
1041 ..
1042 },
1043 ..
1044 } = self.firmware
1045 {
1046 append_cmdline(
1047 &mut cmdline,
1048 format!("HCL_EFI_DIAGNOSTICS_RATE_LIMIT={limit}"),
1049 );
1050 }
1051
1052 let vtl2_base_address = vtl2_base_address_type.unwrap_or_else(|| {
1053 if isolated {
1054 Vtl2BaseAddressType::File
1057 } else {
1058 Vtl2BaseAddressType::Absolute(512 * SIZE_1_MB)
1063 }
1064 });
1065
1066 let file = File::open(igvm_path.clone())
1067 .context("failed to open openhcl firmware file")?
1068 .into();
1069 LoadMode::Igvm {
1070 file,
1071 cmdline: cmdline.unwrap_or_default(),
1072 vtl2_base_address,
1073 com_serial: Some(SerialInformation {
1074 io_port: ComPort::Com3.io_port(),
1075 irq: ComPort::Com3.irq().into(),
1076 }),
1077 }
1078 }
1079 (a, f) => anyhow::bail!("Unsupported firmware {f:?} for arch {a:?}"),
1080 })
1081 }
1082
1083 async fn config_openhcl_vmbus_devices(
1084 &self,
1085 serial: &mut [Option<Resource<SerialBackendHandle>>],
1086 devices: &mut impl Extend<(DeviceVtl, Resource<VmbusDeviceHandleKind>)>,
1087 firmware_event_send: &mesh::Sender<FirmwareEvent>,
1088 framebuffer: bool,
1089 ) -> anyhow::Result<(
1090 get_resources::ged::GuestEmulationDeviceHandle,
1091 mesh::Sender<get_resources::ged::GuestEmulationRequest>,
1092 )> {
1093 let serial0 = serial[0].take();
1094 devices.extend([(
1095 DeviceVtl::Vtl2,
1096 VmbusSerialDeviceHandle {
1097 port: VmbusSerialPort::Com1,
1098 backend: serial0.unwrap_or_else(|| DisconnectedSerialBackendHandle.into_resource()),
1099 }
1100 .into_resource(),
1101 )]);
1102 let serial1 = serial[1].take();
1103 devices.extend([(
1104 DeviceVtl::Vtl2,
1105 VmbusSerialDeviceHandle {
1106 port: VmbusSerialPort::Com2,
1107 backend: serial1.unwrap_or_else(|| DisconnectedSerialBackendHandle.into_resource()),
1108 }
1109 .into_resource(),
1110 )]);
1111
1112 let crash = spawn_dump_handler(self.driver, self.logger).into_resource();
1113 devices.extend([(DeviceVtl::Vtl2, crash)]);
1114
1115 let (guest_request_send, guest_request_recv) = mesh::channel();
1116
1117 let (
1118 UefiConfig {
1119 secure_boot_enabled,
1120 secure_boot_template,
1121 custom_uefi_json: _, disable_frontpage,
1123 default_boot_always_attempt,
1124 enable_vpci_boot,
1125 force_dma_bounce,
1126 efi_diagnostics_log_level,
1127 efi_diagnostics_rate_limit: _, },
1129 OpenHclConfig { vmbus_redirect, .. },
1130 ) = match self.firmware {
1131 Firmware::OpenhclUefi {
1132 uefi_config,
1133 openhcl_config,
1134 ..
1135 } => (uefi_config, openhcl_config),
1136 Firmware::OpenhclLinuxDirect { openhcl_config, .. } => {
1137 (&UefiConfig::default(), openhcl_config)
1138 }
1139 _ => anyhow::bail!("not a supported openhcl firmware config"),
1140 };
1141
1142 let test_gsp_by_id = matches!(
1143 self.vmgs.encryption_policy(),
1144 Some(GuestStateEncryptionPolicy::GspById(_))
1145 );
1146
1147 let ged = get_resources::ged::GuestEmulationDeviceHandle {
1149 firmware: get_resources::ged::GuestFirmwareConfig::Uefi {
1150 firmware_debug: false,
1151 disable_frontpage: *disable_frontpage,
1152 enable_vpci_boot: *enable_vpci_boot,
1153 console_mode: get_resources::ged::UefiConsoleMode::COM1,
1154 default_boot_always_attempt: *default_boot_always_attempt,
1155 },
1156 com1: true,
1157 com2: true,
1158 serial_tx_only: false,
1159 vmbus_redirection: *vmbus_redirect,
1160 vtl2_settings: None, vmgs: memdiff_vmgs(self.vmgs).await?,
1162 framebuffer: framebuffer.then(|| SharedFramebufferHandle.into_resource()),
1163 guest_request_recv,
1164 enable_tpm: self.tpm_config.is_some(),
1165 firmware_event_send: Some(firmware_event_send.clone()),
1166 secure_boot_enabled: *secure_boot_enabled,
1167 secure_boot_template: match secure_boot_template {
1168 Some(SecureBootTemplate::MicrosoftWindows) => {
1169 get_resources::ged::GuestSecureBootTemplateType::MicrosoftWindows
1170 }
1171 Some(SecureBootTemplate::MicrosoftUefiCertificateAuthority) => {
1172 get_resources::ged::GuestSecureBootTemplateType::MicrosoftUefiCertificateAuthority
1173 }
1174 None => get_resources::ged::GuestSecureBootTemplateType::None,
1175 },
1176 enable_battery: false,
1177 no_persistent_secrets: self.tpm_config.as_ref().is_some_and(|c| c.no_persistent_secrets),
1178 igvm_attest_test_config: None,
1179 test_gsp_by_id,
1180 efi_diagnostics_log_level: match efi_diagnostics_log_level {
1181 EfiDiagnosticsLogLevel::Default => {
1182 get_resources::ged::EfiDiagnosticsLogLevelType::Default
1183 }
1184 EfiDiagnosticsLogLevel::Info => {
1185 get_resources::ged::EfiDiagnosticsLogLevelType::Info
1186 }
1187 EfiDiagnosticsLogLevel::Full => {
1188 get_resources::ged::EfiDiagnosticsLogLevelType::Full
1189 }
1190 },
1191 force_dma_bounce_enabled: *force_dma_bounce,
1192 };
1193
1194 Ok((ged, guest_request_send))
1195 }
1196
1197 fn config_video(
1198 &self,
1199 ) -> anyhow::Result<Option<(VideoDevice, Framebuffer, FramebufferAccess)>> {
1200 if self.firmware.isolation().is_some() {
1201 return Ok(None);
1202 }
1203
1204 let video_dev = match self.firmware {
1205 Firmware::Pcat { svga_firmware, .. } | Firmware::OpenhclPcat { svga_firmware, .. } => {
1206 Some(VideoDevice::Vga(
1207 openvmm_pcat_locator::find_svga_bios(svga_firmware.get())
1208 .context("Failed to load VGA BIOS")?,
1209 ))
1210 }
1211 Firmware::Uefi { .. } | Firmware::OpenhclUefi { .. } if !self.no_vmbus => {
1212 Some(VideoDevice::Synth(
1213 DeviceVtl::Vtl0,
1214 SynthVideoHandle {
1215 framebuffer: SharedFramebufferHandle.into_resource(),
1216 dirt_send: None,
1217 }
1218 .into_resource(),
1219 ))
1220 }
1221 Firmware::OpenhclLinuxDirect { .. }
1222 | Firmware::LinuxDirect { .. }
1223 | Firmware::Uefi { .. }
1224 | Firmware::OpenhclUefi { .. } => None,
1225 };
1226
1227 Ok(if let Some(vdev) = video_dev {
1228 let vram =
1229 alloc_shared_memory(FRAMEBUFFER_SIZE, "vram").context("allocating framebuffer")?;
1230 let (fb, fba) = framebuffer::framebuffer(vram, FRAMEBUFFER_SIZE, 0)
1231 .context("creating framebuffer")?;
1232 Some((vdev, fb, fba))
1233 } else {
1234 None
1235 })
1236 }
1237
1238 async fn config_tpm(&self) -> anyhow::Result<Option<ChipsetDeviceHandle>> {
1239 if !self.firmware.is_openhcl()
1240 && let Some(TpmConfig {
1241 no_persistent_secrets,
1242 ..
1243 }) = self.tpm_config
1244 {
1245 let register_layout = match self.arch {
1246 MachineArch::X86_64 => TpmRegisterLayout::IoPort,
1247 MachineArch::Aarch64 => TpmRegisterLayout::Mmio,
1248 };
1249
1250 let (ppi_store, nvram_store) = if self.vmgs.disk().is_none() || *no_persistent_secrets {
1251 (
1252 EphemeralNonVolatileStoreHandle.into_resource(),
1253 EphemeralNonVolatileStoreHandle.into_resource(),
1254 )
1255 } else {
1256 (
1257 VmgsFileHandle::new(vmgs_format::FileId::TPM_PPI, true).into_resource(),
1258 VmgsFileHandle::new(vmgs_format::FileId::TPM_NVRAM, true).into_resource(),
1259 )
1260 };
1261
1262 Ok(Some(ChipsetDeviceHandle {
1263 name: "tpm".to_string(),
1264 resource: chipset_device_worker_defs::RemoteChipsetDeviceHandle {
1265 device: TpmDeviceHandle {
1266 ppi_store,
1267 nvram_store,
1268 refresh_tpm_seeds: false,
1269 ak_cert_type: tpm_resources::TpmAkCertTypeResource::None,
1270 register_layout,
1271 guest_secret_key: None,
1272 logger: None,
1273 is_confidential_vm: self.firmware.isolation().is_some(),
1274 bios_guid: Guid::ZERO,
1276 nvram_size: None,
1277 }
1278 .into_resource(),
1279 worker_host: self.make_device_worker("tpm").await?,
1280 }
1281 .into_resource(),
1282 }))
1283 } else {
1284 Ok(None)
1285 }
1286 }
1287
1288 async fn make_device_worker(&self, name: &str) -> anyhow::Result<mesh_worker::WorkerHost> {
1289 let (host, runner) = mesh_worker::worker_host();
1290 self.mesh
1291 .launch_host(
1292 mesh_process::ProcessConfig::new(name).process_name(self.openvmm_path),
1293 openvmm_defs::entrypoint::MeshHostParams { runner },
1294 )
1295 .await?;
1296 Ok(host)
1297 }
1298}
1299
1300fn spawn_dump_handler(driver: &DefaultDriver, logger: &PetriLogSource) -> GuestCrashDeviceHandle {
1301 let (send, mut recv) = mesh::channel();
1302 let handle = GuestCrashDeviceHandle {
1303 request_dump: send,
1304 max_dump_size: 256 * 1024 * 1024,
1305 };
1306 driver
1307 .spawn("openhcl-dump-handler", {
1308 let logger = logger.clone();
1309 let driver = driver.clone();
1310 async move {
1311 while let Some(rpc) = recv.next().await {
1312 rpc.handle_failable_sync(|done| {
1313 let (file, path) = logger.create_attachment("openhcl.core")?.into_parts();
1314 driver
1315 .spawn("crash-waiter", async move {
1316 let filename = path.file_name().unwrap().to_str().unwrap();
1317 if done.await.is_ok() {
1318 tracing::warn!(filename, "openhcl crash dump complete");
1319 } else {
1320 tracing::error!(
1321 filename,
1322 "openhcl crash dump incomplete, may be corrupted"
1323 );
1324 }
1325 })
1326 .detach();
1327 anyhow::Ok(file)
1328 })
1329 }
1330 }
1331 })
1332 .detach();
1333 handle
1334}
1335
1336async fn ide_controllers_to_openvmm(
1339 ide_controllers: Option<&[[Option<Drive>; 2]; 2]>,
1340) -> anyhow::Result<(
1341 Vec<IdeDeviceConfig>,
1342 Vec<(DeviceVtl, Resource<VmbusDeviceHandleKind>)>,
1343)> {
1344 let mut ide_disks = Vec::new();
1345 let mut storvsp_ide_handles = Vec::new();
1346
1347 if let Some(ide_controllers) = ide_controllers {
1348 for (controller_number, controller) in ide_controllers.iter().enumerate() {
1349 for (controller_location, drive) in controller.iter().enumerate() {
1350 if let Some(drive) = drive {
1351 if let Some(disk) = &drive.disk {
1352 let storvsp_disk = if !drive.is_dvd {
1356 Some(petri_disk_to_openvmm(disk).await?)
1357 } else {
1358 None
1359 };
1360
1361 let disk = petri_disk_to_openvmm(disk).await?;
1362 let guest_media = if drive.is_dvd {
1363 GuestMedia::Dvd(
1364 SimpleScsiDvdHandle {
1365 media: Some(disk),
1366 requests: None,
1367 }
1368 .into_resource(),
1369 )
1370 } else {
1371 GuestMedia::Disk {
1372 disk_type: disk,
1373 read_only: false,
1374 }
1375 };
1376
1377 let channel = controller_number as u8;
1378 let device = controller_location as u8;
1379
1380 ide_disks.push(IdeDeviceConfig {
1381 path: ide_resources::IdePath {
1382 channel,
1383 drive: device,
1384 },
1385 guest_media,
1386 });
1387
1388 if let Some(storvsp_disk) = storvsp_disk {
1390 storvsp_ide_handles.push((
1391 DeviceVtl::Vtl0,
1392 storvsp_resources::StorvspIdeDeviceHandle {
1393 channel_id: channel,
1394 device_id: device,
1395 disk: SimpleScsiDiskHandle {
1396 disk: storvsp_disk,
1397 read_only: false,
1398 parameters: Default::default(),
1399 }
1400 .into_resource(),
1401 io_queue_depth: None,
1402 }
1403 .into_resource(),
1404 ));
1405 }
1406 }
1407 }
1408 }
1409 }
1410 }
1411
1412 Ok((ide_disks, storvsp_ide_handles))
1413}
1414
1415async fn vmbus_storage_controllers_to_openvmm(
1417 vmbus_storage_controllers: &HashMap<Guid, VmbusStorageController>,
1418) -> anyhow::Result<(
1419 Vec<(DeviceVtl, Resource<VmbusDeviceHandleKind>)>,
1420 Vec<VpciDeviceConfig>,
1421)> {
1422 let mut vmbus_devices = Vec::new();
1423 let mut vpci_devices = Vec::new();
1424
1425 for (instance_id, controller) in vmbus_storage_controllers {
1427 let vtl = match controller.target_vtl {
1428 crate::Vtl::Vtl0 => DeviceVtl::Vtl0,
1429 crate::Vtl::Vtl1 => DeviceVtl::Vtl1,
1430 crate::Vtl::Vtl2 => DeviceVtl::Vtl2,
1431 };
1432 match controller.controller_type {
1433 VmbusStorageType::Scsi => {
1434 let mut devices = Vec::new();
1435 for (lun, Drive { disk, is_dvd }) in &controller.drives {
1436 if !*is_dvd && let Some(disk) = disk {
1437 devices.push(ScsiDeviceAndPath {
1438 path: ScsiPath {
1439 path: 0,
1440 target: 0,
1441 lun: (*lun).try_into().expect("invalid scsi lun"),
1442 },
1443 device: SimpleScsiDiskHandle {
1444 disk: petri_disk_to_openvmm(disk).await?,
1445 read_only: false,
1446 parameters: Default::default(),
1447 }
1448 .into_resource(),
1449 });
1450 } else {
1451 todo!("dvd ({}) or empty ({})", *is_dvd, disk.is_none())
1452 }
1453 }
1454
1455 vmbus_devices.push((
1456 vtl,
1457 ScsiControllerHandle {
1458 instance_id: *instance_id,
1459 max_sub_channel_count: 1,
1460 io_queue_depth: None,
1461 devices,
1462 requests: None,
1463 poll_mode_queue_depth: None,
1464 }
1465 .into_resource(),
1466 ));
1467 }
1468 VmbusStorageType::Nvme => {
1469 let mut namespaces = Vec::new();
1470 for (nsid, Drive { disk, is_dvd }) in &controller.drives {
1471 if !*is_dvd && let Some(disk) = disk {
1472 namespaces.push(NamespaceDefinition {
1473 nsid: *nsid,
1474 read_only: false,
1475 disk: petri_disk_to_openvmm(disk).await?,
1476 });
1477 } else {
1478 todo!("dvd ({}) or empty ({})", *is_dvd, disk.is_none())
1479 }
1480 }
1481
1482 vpci_devices.push(VpciDeviceConfig {
1483 vtl,
1484 instance_id: *instance_id,
1485 resource: NvmeControllerHandle {
1486 subsystem_id: *instance_id,
1487 max_io_queues: 64,
1488 msix_count: 64,
1489 namespaces,
1490 requests: None,
1491 }
1492 .into_resource(),
1493 vnode: None,
1494 });
1495 }
1496 VmbusStorageType::VirtioBlk => {
1497 const VIRTIO_BLK_INSTANCE_ID_TEMPLATE: Guid = Guid {
1500 data1: 0,
1501 data2: 0x1234,
1502 data3: 0x5678,
1503 data4: [0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67, 0x89],
1504 };
1505 for (lun, Drive { disk, is_dvd }) in &controller.drives {
1506 if *is_dvd {
1507 anyhow::bail!("dvd not supported with virtio-blk");
1508 }
1509 let Some(disk) = disk else {
1510 anyhow::bail!("empty drive not supported with virtio-blk");
1511 };
1512 let mut drive_id = VIRTIO_BLK_INSTANCE_ID_TEMPLATE;
1513 drive_id.data1 = *lun;
1514 vpci_devices.push(VpciDeviceConfig {
1515 vtl,
1516 instance_id: drive_id,
1517 resource: VirtioPciDeviceHandle(
1518 VirtioBlkHandle {
1519 disk: petri_disk_to_openvmm(disk).await?,
1520 read_only: false,
1521 }
1522 .into_resource(),
1523 )
1524 .into_resource(),
1525 vnode: None,
1526 });
1527 }
1528 }
1529 }
1530 }
1531
1532 Ok((vmbus_devices, vpci_devices))
1533}