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