1use super::MANA_INSTANCE;
11use super::NIC_MAC_ADDRESS;
12use super::PetriVmConfigOpenVmm;
13use chipset_resources::battery::BatteryDeviceHandleX64;
14use chipset_resources::battery::HostBatteryUpdate;
15use disk_backend_resources::LayeredDiskHandle;
16use disk_backend_resources::layer::RamDiskLayerHandle;
17use gdma_resources::GdmaDeviceHandle;
18use gdma_resources::VportDefinition;
19use get_resources::ged::IgvmAttestTestConfig;
20use guid::Guid;
21use net_backend_resources::mac_address::MacAddress;
22use nvme_resources::NamespaceDefinition;
23use nvme_resources::NvmeControllerHandle;
24use openvmm_defs::config::Config;
25use openvmm_defs::config::DeviceVtl;
26use openvmm_defs::config::LoadMode;
27use openvmm_defs::config::PcieDeviceConfig;
28use openvmm_defs::config::PcieIommuConfig;
29use openvmm_defs::config::PcieMmioRangeConfig;
30use openvmm_defs::config::PciePortConfig;
31use openvmm_defs::config::PcieRootComplexConfig;
32use openvmm_defs::config::PcieSwitchConfig;
33use openvmm_defs::config::VpciDeviceConfig;
34use openvmm_defs::config::Vtl2BaseAddressType;
35use vm_resource::IntoResource;
36use vmotherboard::ChipsetDeviceHandle;
37
38impl PetriVmConfigOpenVmm {
39 pub fn without_save_restore(mut self) -> Self {
41 self.skip_save_restore = true;
42 self
43 }
44
45 pub fn with_vtl0_alias_map(mut self) -> Self {
48 self.config
49 .hypervisor
50 .with_vtl2
51 .as_mut()
52 .expect("Not an openhcl config.")
53 .vtl0_alias_map = true;
54 self
55 }
56
57 pub fn with_battery(mut self) -> Self {
59 if self.resources.properties.is_openhcl {
60 self.ged.as_mut().unwrap().enable_battery = true;
61 } else {
62 self.config.chipset_devices.push(ChipsetDeviceHandle {
63 name: "battery".to_string(),
64 resource: BatteryDeviceHandleX64 {
65 battery_status_recv: {
66 let (tx, rx) = mesh::channel();
67 tx.send(HostBatteryUpdate::default_present());
68 rx
69 },
70 }
71 .into_resource(),
72 });
73 if let LoadMode::Uefi { enable_battery, .. } = &mut self.config.load_mode {
74 *enable_battery = true;
75 }
76 }
77 self
78 }
79
80 pub fn with_igvm_attest_test_config(mut self, config: IgvmAttestTestConfig) -> Self {
82 if !self.resources.properties.is_openhcl {
83 panic!("IGVM Attest test config is only supported for OpenHCL.")
84 };
85
86 let ged = self.ged.as_mut().expect("No GED to configure TPM");
87
88 ged.igvm_attest_test_config = Some(config);
89
90 self
91 }
92
93 pub fn with_smbios(mut self, f: impl FnOnce(&mut smbios_defs::SmbiosConfig)) -> Self {
102 if self.resources.properties.is_openhcl {
103 let ged = self.ged.as_mut().expect("OpenHCL config must have a GED.");
104 f(&mut ged.smbios);
105 } else {
106 let smbios = match &mut self.config.load_mode {
107 LoadMode::Linux { smbios, .. }
108 | LoadMode::Uefi { smbios, .. }
109 | LoadMode::Pcat { smbios, .. } => &mut **smbios,
110 LoadMode::Igvm { .. } | LoadMode::None => {
111 panic!("SMBIOS configuration is not supported for this load mode.")
112 }
113 };
114 f(smbios);
115 }
116 self
117 }
118
119 pub fn with_nic(mut self) -> Self {
123 let endpoint = net_backend_resources::consomme::ConsommeHandle {
124 cidr: None,
125 params: Default::default(),
126 ports: Vec::new(),
127 internal_dns: true,
128 recv: None,
129 }
130 .into_resource();
131 if let Some(vtl2_settings) = self.runtime_config.vtl2_settings.as_mut() {
132 self.config.vpci_devices.push(VpciDeviceConfig {
133 vtl: DeviceVtl::Vtl2,
134 instance_id: MANA_INSTANCE,
135 resource: GdmaDeviceHandle {
136 vports: vec![VportDefinition {
137 mac_address: NIC_MAC_ADDRESS,
138 endpoint,
139 }],
140 }
141 .into_resource(),
142 vnode: None,
143 });
144
145 vtl2_settings.dynamic.as_mut().unwrap().nic_devices.push(
146 vtl2_settings_proto::NicDeviceLegacy {
147 instance_id: MANA_INSTANCE.to_string(),
148 subordinate_instance_id: None,
149 max_sub_channels: None,
150 },
151 );
152 } else {
153 const NETVSP_INSTANCE: Guid = guid::guid!("c6c46cc3-9302-4344-b206-aef65e5bd0a2");
154 self.config.vmbus_devices.push((
155 DeviceVtl::Vtl0,
156 netvsp_resources::NetvspHandle {
157 instance_id: NETVSP_INSTANCE,
158 mac_address: NIC_MAC_ADDRESS,
159 endpoint,
160 max_queues: None,
161 }
162 .into_resource(),
163 ));
164 }
165
166 self
167 }
168
169 pub fn with_mana_vf(self, instance_id: Guid, mac_address: MacAddress) -> Self {
171 self.with_mana_vf_vports(instance_id, [mac_address])
172 }
173
174 pub fn with_mana_vf_vports(
176 mut self,
177 instance_id: Guid,
178 mac_addresses: impl IntoIterator<Item = MacAddress>,
179 ) -> Self {
180 let vtl2_settings = self
181 .runtime_config
182 .vtl2_settings
183 .as_mut()
184 .expect("a VTL2 MANA VF requires OpenHCL firmware");
185
186 let vports = mac_addresses
187 .into_iter()
188 .map(|mac_address| VportDefinition {
189 mac_address,
190 endpoint: net_backend_resources::consomme::ConsommeHandle {
191 cidr: None,
192 params: Default::default(),
193 ports: Vec::new(),
194 internal_dns: true,
195 recv: None,
196 }
197 .into_resource(),
198 })
199 .collect::<Vec<_>>();
200
201 assert!(!vports.is_empty(), "a MANA VF requires at least one vport");
202
203 self.config.vpci_devices.push(VpciDeviceConfig {
204 vtl: DeviceVtl::Vtl2,
205 instance_id,
206 resource: GdmaDeviceHandle { vports }.into_resource(),
207 vnode: None,
208 });
209 vtl2_settings.dynamic.as_mut().unwrap().nic_devices.push(
210 vtl2_settings_proto::NicDeviceLegacy {
211 instance_id: instance_id.to_string(),
212 subordinate_instance_id: None,
213 max_sub_channels: None,
214 },
215 );
216
217 self
218 }
219
220 pub fn with_pcie_nic(mut self, port_name: &str, mac_address: MacAddress) -> Self {
222 let endpoint = net_backend_resources::consomme::ConsommeHandle {
223 cidr: None,
224 params: Default::default(),
225 ports: Vec::new(),
226 internal_dns: true,
227 recv: None,
228 }
229 .into_resource();
230 self.config.pcie_devices.push(PcieDeviceConfig {
231 port_name: port_name.to_string(),
232 resource: GdmaDeviceHandle {
233 vports: vec![VportDefinition {
234 mac_address,
235 endpoint,
236 }],
237 }
238 .into_resource(),
239 });
240
241 self
242 }
243
244 pub fn with_pcie_nvme(mut self, port_name: &str, subsystem_id: Guid) -> Self {
246 self.config.pcie_devices.push(PcieDeviceConfig {
247 port_name: port_name.to_string(),
248 resource: NvmeControllerHandle {
249 subsystem_id,
250 max_io_queues: 64,
251 msix_count: 64,
252 namespaces: vec![NamespaceDefinition {
253 nsid: 1,
254 disk: LayeredDiskHandle::single_layer(RamDiskLayerHandle {
255 len: Some(1024 * 1024),
256 sector_size: None,
257 })
258 .into_resource(),
259 read_only: false,
260 }],
261 requests: None,
262 }
263 .into_resource(),
264 });
265
266 self
267 }
268
269 pub fn with_virtio_nic(self, port_name: &str, mac_address: MacAddress) -> Self {
274 self.with_virtio_nic_rx_gso(port_name, mac_address, false)
275 }
276
277 pub fn with_virtio_nic_rx_gso(
279 mut self,
280 port_name: &str,
281 mac_address: MacAddress,
282 enable_rx_gso: bool,
283 ) -> Self {
284 let endpoint = net_backend_resources::consomme::ConsommeHandle {
285 cidr: None,
286 params: Default::default(),
287 ports: Vec::new(),
288 internal_dns: true,
289 recv: None,
290 }
291 .into_resource();
292
293 self.config.pcie_devices.push(PcieDeviceConfig {
294 port_name: port_name.to_string(),
295 resource: virtio_resources::VirtioPciDeviceHandle(
296 virtio_resources::net::VirtioNetHandle {
297 max_queues: None,
298 enable_rx_gso,
299 mac_address,
300 endpoint,
301 }
302 .into_resource(),
303 )
304 .into_resource(),
305 });
306
307 self
308 }
309
310 pub fn with_tcp_pipette_nic(mut self, port_name: &str, mac_address: MacAddress) -> Self {
318 let (port_send, port_recv) = mesh::oneshot();
319 let endpoint = net_backend_resources::consomme::ConsommeHandle {
320 cidr: None,
321 params: Default::default(),
322 ports: vec![net_backend_resources::consomme::HostPortConfig {
323 protocol: net_backend_resources::consomme::HostPortProtocol::Tcp,
324 host_address: Some(net_backend_resources::consomme::HostIpAddress::Ipv4(
325 std::net::Ipv4Addr::LOCALHOST,
326 )),
327 host_port: net_backend_resources::consomme::HostPort::Dynamic(port_send),
328 guest_port: pipette_client::PIPETTE_PORT as u16,
329 }],
330 internal_dns: true,
331 recv: None,
332 }
333 .into_resource();
334 self.config.pcie_devices.push(PcieDeviceConfig {
335 port_name: port_name.to_string(),
336 resource: virtio_resources::VirtioPciDeviceHandle(
337 virtio_resources::net::VirtioNetHandle {
338 max_queues: None,
339 enable_rx_gso: false,
340 mac_address,
341 endpoint,
342 }
343 .into_resource(),
344 )
345 .into_resource(),
346 });
347 self.resources.tcp_pipette_port = Some(port_recv);
348 self
349 }
350
351 #[cfg(windows)]
367 pub fn with_dio_nic(mut self, switch_id: Option<Guid>) -> Self {
368 let switch_port_id = vmswitch::kernel::SwitchPortId {
369 switch: switch_id.unwrap_or(vmswitch::hcn::DEFAULT_SWITCH),
370 port: Guid::new_random(),
371 };
372 let _ = vmswitch::hcn::Network::open(&switch_port_id.switch)
373 .unwrap_or_else(|e| panic!("could not find switch {}: {e}", switch_port_id.switch));
374 let switch_port = vmswitch::kernel::SwitchPort::new(&switch_port_id)
375 .expect("failed to create vmswitch DIO port");
376 self.resources._switch_ports.push(switch_port);
377
378 let endpoint = net_backend_resources::dio::WindowsDirectIoHandle {
379 switch_port_id: net_backend_resources::dio::SwitchPortId {
380 switch: switch_port_id.switch,
381 port: switch_port_id.port,
382 },
383 }
384 .into_resource();
385
386 if let Some(vtl2_settings) = self.runtime_config.vtl2_settings.as_mut() {
387 self.config.vpci_devices.push(VpciDeviceConfig {
388 vtl: DeviceVtl::Vtl2,
389 instance_id: MANA_INSTANCE,
390 resource: GdmaDeviceHandle {
391 vports: vec![VportDefinition {
392 mac_address: NIC_MAC_ADDRESS,
393 endpoint,
394 }],
395 }
396 .into_resource(),
397 vnode: None,
398 });
399
400 vtl2_settings.dynamic.as_mut().unwrap().nic_devices.push(
401 vtl2_settings_proto::NicDeviceLegacy {
402 instance_id: MANA_INSTANCE.to_string(),
403 subordinate_instance_id: None,
404 max_sub_channels: None,
405 },
406 );
407 } else {
408 const NETVSP_DIO_INSTANCE: Guid = guid::guid!("d1ff4c5a-1b3c-4f0d-8e10-1b9d8b1d1cee");
409 self.config.vmbus_devices.push((
410 DeviceVtl::Vtl0,
411 netvsp_resources::NetvspHandle {
412 instance_id: NETVSP_DIO_INSTANCE,
413 mac_address: NIC_MAC_ADDRESS,
414 endpoint,
415 max_queues: None,
416 }
417 .into_resource(),
418 ));
419 }
420
421 self
422 }
423
424 pub fn with_vtl2_relocation_mode(mut self, mode: Vtl2BaseAddressType) -> Self {
426 let LoadMode::Igvm {
427 vtl2_base_address, ..
428 } = &mut self.config.load_mode
429 else {
430 panic!("vtl2 relocation mode is only supported for OpenHCL firmware")
431 };
432 *vtl2_base_address = mode;
433 self
434 }
435
436 pub fn with_memory_backing_file(mut self, path: impl Into<std::path::PathBuf>) -> Self {
448 assert_ne!(
449 self.requested_private_memory,
450 Some(true),
451 "with_memory_backing_file forces shared memory, which conflicts with \
452 the explicitly requested private memory"
453 );
454 self.memory_backing_file = Some(path.into());
455 for node in &mut self.config.numa.nodes {
456 if let Some(mem) = &mut node.mem {
457 mem.private_memory = false;
458 }
459 }
460 self
461 }
462
463 pub fn with_hugepages(mut self, hugepage_size: Option<u64>) -> Self {
471 assert_ne!(
472 self.requested_private_memory,
473 Some(true),
474 "with_hugepages forces shared memory, which conflicts with the \
475 explicitly requested private memory"
476 );
477 for node in &mut self.config.numa.nodes {
478 if let Some(mem) = &mut node.mem {
479 mem.hugepages = true;
480 mem.hugepage_size = hugepage_size;
481 mem.private_memory = false;
482 }
483 }
484 self
485 }
486
487 pub fn with_pcie_root_topology(
499 self,
500 segment_count: u64,
501 root_complex_per_segment: u64,
502 root_ports_per_root_complex: u64,
503 ) -> Self {
504 if segment_count == 0 {
505 return self;
506 }
507 let segment_base = self
512 .config
513 .pcie_root_complexes
514 .iter()
515 .map(|rc| u64::from(rc.segment) + 1)
516 .max()
517 .unwrap_or(0);
518 self.with_pcie_root_topology_at(
519 segment_base,
520 segment_count,
521 root_complex_per_segment,
522 root_ports_per_root_complex,
523 )
524 }
525
526 pub fn with_pcie_root_topology_at(
529 mut self,
530 segment_base: u64,
531 segment_count: u64,
532 root_complex_per_segment: u64,
533 root_ports_per_root_complex: u64,
534 ) -> Self {
535 let segment_end = segment_base
536 .checked_add(segment_count)
537 .filter(|end| *end <= u16::MAX as u64 + 1)
538 .expect("PCI segment range exceeds u16");
539 assert!(
540 !self
541 .config
542 .pcie_root_complexes
543 .iter()
544 .any(|rc| (segment_base..segment_end).contains(&u64::from(rc.segment))),
545 "PCI segment range overlaps an existing root complex"
546 );
547 assert!(
548 (1..=256).contains(&root_complex_per_segment),
549 "PCI topology requires between 1 and 256 root complexes per segment"
550 );
551 const LOW_MMIO_SIZE: u64 = 64 * 1024 * 1024;
552 const HIGH_MMIO_SIZE: u64 = 1024 * 1024 * 1024;
553 let index_base = self.config.pcie_root_complexes.len() as u64;
554
555 for segment_offset in 0..segment_count {
557 let segment = segment_base + segment_offset;
558 let bus_count_per_rc = 256 / root_complex_per_segment;
559 for rc_index_in_segment in 0..root_complex_per_segment {
560 let index =
561 index_base + segment_offset * root_complex_per_segment + rc_index_in_segment;
562 let name = format!("s{}rc{}", segment, rc_index_in_segment);
563
564 let start_bus = rc_index_in_segment * bus_count_per_rc;
565 let end_bus = start_bus + bus_count_per_rc - 1;
566
567 let ports = (0..root_ports_per_root_complex)
568 .map(|i| PciePortConfig {
569 name: format!("s{}rc{}rp{}", segment, rc_index_in_segment, i),
570 devfn: None,
571 hotplug: true,
572 acs_capabilities_supported: Some(0),
573 cxl: false,
574 pasid: false,
575 })
576 .collect();
577
578 self.config.pcie_root_complexes.push(PcieRootComplexConfig {
579 index: index.try_into().unwrap(),
580 name,
581 segment: segment.try_into().unwrap(),
582 start_bus: start_bus.try_into().unwrap(),
583 end_bus: end_bus.try_into().unwrap(),
584 legacy_pci_config_io: false,
585 low_mmio: PcieMmioRangeConfig::Dynamic {
586 size: LOW_MMIO_SIZE,
587 },
588 high_mmio: PcieMmioRangeConfig::Dynamic {
589 size: HIGH_MMIO_SIZE,
590 },
591 cxl: None,
592 ports,
593 iommu: None,
594 vnode: None,
595 preserve_bars: false,
596 });
597 }
598 }
599
600 self
601 }
602
603 pub(super) fn with_private_virtio_devices(mut self) -> Self {
604 if self.guest_shares.is_empty() && self.private_virtio_devices.is_empty() {
605 return self;
606 }
607 if self.config.pcie_root_complexes.is_empty() {
608 self = self.with_pcie_root_topology(1, 1, 0);
609 }
610 let root_index = self
612 .config
613 .pcie_root_complexes
614 .iter()
615 .position(|rc| rc.segment != 0)
616 .unwrap_or(0);
617 let mut port_names = self
618 .config
619 .pcie_root_complexes
620 .iter()
621 .flat_map(|rc| &rc.ports)
622 .chain(self.config.pcie_switches.iter().flat_map(|s| &s.ports))
623 .map(|p| p.name.clone())
624 .chain(self.config.pcie_devices.iter().map(|d| d.port_name.clone()))
625 .collect::<std::collections::HashSet<_>>();
626 let mut shares = std::mem::take(&mut self.guest_shares)
627 .into_iter()
628 .collect::<Vec<_>>();
629 shares.sort_by(|a, b| a.0.cmp(&b.0));
630 for (tag, share) in shares {
631 let root_path = share
632 .host_path
633 .into_os_string()
634 .into_string()
635 .expect("guest share path must be UTF-8");
636 self.private_virtio_devices.push((
637 "petri-share-",
638 virtio_resources::p9::VirtioPlan9Handle {
639 tag,
640 root_path,
641 debug: false,
642 }
643 .into_resource(),
644 ));
645 }
646 for (prefix, resource) in std::mem::take(&mut self.private_virtio_devices) {
647 let port_name = (0..)
648 .map(|i| format!("{prefix}{i}"))
649 .find(|name| port_names.insert(name.clone()))
650 .unwrap();
651 self.config.pcie_root_complexes[root_index]
652 .ports
653 .push(PciePortConfig {
654 name: port_name.clone(),
655 devfn: None,
656 hotplug: false,
657 acs_capabilities_supported: Some(0),
658 cxl: false,
659 pasid: false,
660 });
661 self.config.pcie_devices.push(PcieDeviceConfig {
662 port_name,
663 resource: virtio_resources::VirtioPciDeviceHandle(resource).into_resource(),
664 });
665 }
666 self
667 }
668
669 pub fn with_pcie_switch(
671 mut self,
672 port_name: &str,
673 switch_name: &str,
674 port_count: u8,
675 hotplug: bool,
676 ) -> Self {
677 self.config.pcie_switches.push(PcieSwitchConfig {
678 name: switch_name.to_string(),
679 parent_port: port_name.to_string(),
680 ports: (0..port_count)
681 .map(|i| PciePortConfig {
682 name: format!("{switch_name}-downstream-{i}"),
683 devfn: None,
684 hotplug,
685 acs_capabilities_supported: Some(0),
686 cxl: false,
687 pasid: false,
688 })
689 .collect(),
690 });
691 self
692 }
693
694 pub fn with_smmu(mut self, rc_names: &[&str]) -> Self {
701 for name in rc_names {
702 self.pending_iommu.push((
703 name.to_string(),
704 PcieIommuConfig::Smmu {
705 accel: false,
706 oas: openvmm_defs::config::SmmuOas::Auto,
707 ssidsize: openvmm_defs::config::SmmuSsidSize::Auto,
708 },
709 ));
710 }
711 self
712 }
713
714 pub fn with_smmu_accel(mut self, rc_names: &[&str]) -> Self {
723 for name in rc_names {
724 self.pending_iommu.push((
725 name.to_string(),
726 PcieIommuConfig::Smmu {
727 accel: true,
728 oas: openvmm_defs::config::SmmuOas::Auto,
729 ssidsize: openvmm_defs::config::SmmuSsidSize::Auto,
730 },
731 ));
732 }
733 self
734 }
735
736 pub fn with_amd_iommu(mut self, rc_names: &[&str]) -> Self {
745 for name in rc_names {
746 self.pending_iommu
747 .push((name.to_string(), PcieIommuConfig::AmdVi));
748 }
749 self
750 }
751
752 pub fn with_intel_vtd(mut self, rc_names: &[&str]) -> Self {
761 for name in rc_names {
762 self.pending_iommu
763 .push((name.to_string(), PcieIommuConfig::IntelVtd));
764 }
765 self
766 }
767
768 pub fn with_custom_config(mut self, f: impl FnOnce(&mut Config)) -> Self {
772 f(&mut self.config);
773 self
774 }
775
776 pub fn with_allow_early_vtl0_access(mut self, allow: bool) -> Self {
782 self.config
783 .hypervisor
784 .with_vtl2
785 .as_mut()
786 .unwrap()
787 .late_map_vtl0_memory =
788 (!allow).then_some(openvmm_defs::config::LateMapVtl0MemoryPolicy::InjectException);
789
790 self
791 }
792}