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