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 with_vtl0_alias_map(mut self) -> Self {
42 self.config
43 .hypervisor
44 .with_vtl2
45 .as_mut()
46 .expect("Not an openhcl config.")
47 .vtl0_alias_map = true;
48 self
49 }
50
51 pub fn with_battery(mut self) -> Self {
53 if self.resources.properties.is_openhcl {
54 self.ged.as_mut().unwrap().enable_battery = true;
55 } else {
56 self.config.chipset_devices.push(ChipsetDeviceHandle {
57 name: "battery".to_string(),
58 resource: BatteryDeviceHandleX64 {
59 battery_status_recv: {
60 let (tx, rx) = mesh::channel();
61 tx.send(HostBatteryUpdate::default_present());
62 rx
63 },
64 }
65 .into_resource(),
66 });
67 if let LoadMode::Uefi { enable_battery, .. } = &mut self.config.load_mode {
68 *enable_battery = true;
69 }
70 }
71 self
72 }
73
74 pub fn with_igvm_attest_test_config(mut self, config: IgvmAttestTestConfig) -> Self {
76 if !self.resources.properties.is_openhcl {
77 panic!("IGVM Attest test config is only supported for OpenHCL.")
78 };
79
80 let ged = self.ged.as_mut().expect("No GED to configure TPM");
81
82 ged.igvm_attest_test_config = Some(config);
83
84 self
85 }
86
87 pub fn with_smbios(mut self, f: impl FnOnce(&mut smbios_defs::SmbiosConfig)) -> Self {
96 if self.resources.properties.is_openhcl {
97 let ged = self.ged.as_mut().expect("OpenHCL config must have a GED.");
98 f(&mut ged.smbios);
99 } else {
100 let smbios = match &mut self.config.load_mode {
101 LoadMode::Linux { smbios, .. }
102 | LoadMode::Uefi { smbios, .. }
103 | LoadMode::Pcat { smbios, .. } => &mut **smbios,
104 LoadMode::Igvm { .. } | LoadMode::None => {
105 panic!("SMBIOS configuration is not supported for this load mode.")
106 }
107 };
108 f(smbios);
109 }
110 self
111 }
112
113 pub fn with_nic(mut self) -> Self {
117 let endpoint = net_backend_resources::consomme::ConsommeHandle {
118 cidr: None,
119 ports: Vec::new(),
120 recv: None,
121 }
122 .into_resource();
123 if let Some(vtl2_settings) = self.runtime_config.vtl2_settings.as_mut() {
124 self.config.vpci_devices.push(VpciDeviceConfig {
125 vtl: DeviceVtl::Vtl2,
126 instance_id: MANA_INSTANCE,
127 resource: GdmaDeviceHandle {
128 vports: vec![VportDefinition {
129 mac_address: NIC_MAC_ADDRESS,
130 endpoint,
131 }],
132 }
133 .into_resource(),
134 vnode: None,
135 });
136
137 vtl2_settings.dynamic.as_mut().unwrap().nic_devices.push(
138 vtl2_settings_proto::NicDeviceLegacy {
139 instance_id: MANA_INSTANCE.to_string(),
140 subordinate_instance_id: None,
141 max_sub_channels: None,
142 },
143 );
144 } else {
145 const NETVSP_INSTANCE: Guid = guid::guid!("c6c46cc3-9302-4344-b206-aef65e5bd0a2");
146 self.config.vmbus_devices.push((
147 DeviceVtl::Vtl0,
148 netvsp_resources::NetvspHandle {
149 instance_id: NETVSP_INSTANCE,
150 mac_address: NIC_MAC_ADDRESS,
151 endpoint,
152 max_queues: None,
153 }
154 .into_resource(),
155 ));
156 }
157
158 self
159 }
160
161 pub fn with_mana_vf(self, instance_id: Guid, mac_address: MacAddress) -> Self {
163 self.with_mana_vf_vports(instance_id, [mac_address])
164 }
165
166 pub fn with_mana_vf_vports(
168 mut self,
169 instance_id: Guid,
170 mac_addresses: impl IntoIterator<Item = MacAddress>,
171 ) -> Self {
172 let vtl2_settings = self
173 .runtime_config
174 .vtl2_settings
175 .as_mut()
176 .expect("a VTL2 MANA VF requires OpenHCL firmware");
177
178 let vports = mac_addresses
179 .into_iter()
180 .map(|mac_address| VportDefinition {
181 mac_address,
182 endpoint: net_backend_resources::consomme::ConsommeHandle {
183 cidr: None,
184 ports: Vec::new(),
185 recv: None,
186 }
187 .into_resource(),
188 })
189 .collect::<Vec<_>>();
190
191 assert!(!vports.is_empty(), "a MANA VF requires at least one vport");
192
193 self.config.vpci_devices.push(VpciDeviceConfig {
194 vtl: DeviceVtl::Vtl2,
195 instance_id,
196 resource: GdmaDeviceHandle { vports }.into_resource(),
197 vnode: None,
198 });
199 vtl2_settings.dynamic.as_mut().unwrap().nic_devices.push(
200 vtl2_settings_proto::NicDeviceLegacy {
201 instance_id: instance_id.to_string(),
202 subordinate_instance_id: None,
203 max_sub_channels: None,
204 },
205 );
206
207 self
208 }
209
210 pub fn with_pcie_nic(mut self, port_name: &str, mac_address: MacAddress) -> Self {
212 let endpoint = net_backend_resources::consomme::ConsommeHandle {
213 cidr: None,
214 ports: Vec::new(),
215 recv: None,
216 }
217 .into_resource();
218 self.config.pcie_devices.push(PcieDeviceConfig {
219 port_name: port_name.to_string(),
220 resource: GdmaDeviceHandle {
221 vports: vec![VportDefinition {
222 mac_address,
223 endpoint,
224 }],
225 }
226 .into_resource(),
227 });
228
229 self
230 }
231
232 pub fn with_pcie_nvme(mut self, port_name: &str, subsystem_id: Guid) -> Self {
234 self.config.pcie_devices.push(PcieDeviceConfig {
235 port_name: port_name.to_string(),
236 resource: NvmeControllerHandle {
237 subsystem_id,
238 max_io_queues: 64,
239 msix_count: 64,
240 namespaces: vec![NamespaceDefinition {
241 nsid: 1,
242 disk: LayeredDiskHandle::single_layer(RamDiskLayerHandle {
243 len: Some(1024 * 1024),
244 sector_size: None,
245 })
246 .into_resource(),
247 read_only: false,
248 }],
249 requests: None,
250 }
251 .into_resource(),
252 });
253
254 self
255 }
256
257 pub fn with_virtio_nic(mut self, port_name: &str, mac_address: MacAddress) -> Self {
262 let endpoint = net_backend_resources::consomme::ConsommeHandle {
263 cidr: None,
264 ports: Vec::new(),
265 recv: None,
266 }
267 .into_resource();
268
269 self.config.pcie_devices.push(PcieDeviceConfig {
270 port_name: port_name.to_string(),
271 resource: virtio_resources::VirtioPciDeviceHandle(
272 virtio_resources::net::VirtioNetHandle {
273 max_queues: None,
274 mac_address,
275 endpoint,
276 }
277 .into_resource(),
278 )
279 .into_resource(),
280 });
281
282 self
283 }
284
285 pub fn with_tcp_pipette_nic(mut self, port_name: &str, mac_address: MacAddress) -> Self {
293 let (port_send, port_recv) = mesh::oneshot();
294 let endpoint = net_backend_resources::consomme::ConsommeHandle {
295 cidr: None,
296 ports: vec![net_backend_resources::consomme::HostPortConfig {
297 protocol: net_backend_resources::consomme::HostPortProtocol::Tcp,
298 host_address: Some(net_backend_resources::consomme::HostIpAddress::Ipv4(
299 std::net::Ipv4Addr::LOCALHOST,
300 )),
301 host_port: net_backend_resources::consomme::HostPort::Dynamic(port_send),
302 guest_port: pipette_client::PIPETTE_PORT as u16,
303 }],
304 recv: None,
305 }
306 .into_resource();
307 self.config.pcie_devices.push(PcieDeviceConfig {
308 port_name: port_name.to_string(),
309 resource: virtio_resources::VirtioPciDeviceHandle(
310 virtio_resources::net::VirtioNetHandle {
311 max_queues: None,
312 mac_address,
313 endpoint,
314 }
315 .into_resource(),
316 )
317 .into_resource(),
318 });
319 self.resources.tcp_pipette_port = Some(port_recv);
320 self
321 }
322
323 pub fn with_nested_virt(mut self) -> Self {
325 self.config.hypervisor.nested_virt = true;
326 self
327 }
328
329 #[cfg(windows)]
345 pub fn with_dio_nic(mut self, switch_id: Option<Guid>) -> Self {
346 let switch_port_id = vmswitch::kernel::SwitchPortId {
347 switch: switch_id.unwrap_or(vmswitch::hcn::DEFAULT_SWITCH),
348 port: Guid::new_random(),
349 };
350 let _ = vmswitch::hcn::Network::open(&switch_port_id.switch)
351 .unwrap_or_else(|e| panic!("could not find switch {}: {e}", switch_port_id.switch));
352 let switch_port = vmswitch::kernel::SwitchPort::new(&switch_port_id)
353 .expect("failed to create vmswitch DIO port");
354 self.resources._switch_ports.push(switch_port);
355
356 let endpoint = net_backend_resources::dio::WindowsDirectIoHandle {
357 switch_port_id: net_backend_resources::dio::SwitchPortId {
358 switch: switch_port_id.switch,
359 port: switch_port_id.port,
360 },
361 }
362 .into_resource();
363
364 if let Some(vtl2_settings) = self.runtime_config.vtl2_settings.as_mut() {
365 self.config.vpci_devices.push(VpciDeviceConfig {
366 vtl: DeviceVtl::Vtl2,
367 instance_id: MANA_INSTANCE,
368 resource: GdmaDeviceHandle {
369 vports: vec![VportDefinition {
370 mac_address: NIC_MAC_ADDRESS,
371 endpoint,
372 }],
373 }
374 .into_resource(),
375 vnode: None,
376 });
377
378 vtl2_settings.dynamic.as_mut().unwrap().nic_devices.push(
379 vtl2_settings_proto::NicDeviceLegacy {
380 instance_id: MANA_INSTANCE.to_string(),
381 subordinate_instance_id: None,
382 max_sub_channels: None,
383 },
384 );
385 } else {
386 const NETVSP_DIO_INSTANCE: Guid = guid::guid!("d1ff4c5a-1b3c-4f0d-8e10-1b9d8b1d1cee");
387 self.config.vmbus_devices.push((
388 DeviceVtl::Vtl0,
389 netvsp_resources::NetvspHandle {
390 instance_id: NETVSP_DIO_INSTANCE,
391 mac_address: NIC_MAC_ADDRESS,
392 endpoint,
393 max_queues: None,
394 }
395 .into_resource(),
396 ));
397 }
398
399 self
400 }
401
402 pub fn with_vtl2_relocation_mode(mut self, mode: Vtl2BaseAddressType) -> Self {
404 let LoadMode::Igvm {
405 vtl2_base_address, ..
406 } = &mut self.config.load_mode
407 else {
408 panic!("vtl2 relocation mode is only supported for OpenHCL firmware")
409 };
410 *vtl2_base_address = mode;
411 self
412 }
413
414 pub fn with_memory_backing_file(mut self, path: impl Into<std::path::PathBuf>) -> Self {
426 assert_ne!(
427 self.requested_private_memory,
428 Some(true),
429 "with_memory_backing_file forces shared memory, which conflicts with \
430 the explicitly requested private memory"
431 );
432 self.memory_backing_file = Some(path.into());
433 for node in &mut self.config.numa.nodes {
434 if let Some(mem) = &mut node.mem {
435 mem.private_memory = false;
436 }
437 }
438 self
439 }
440
441 pub fn with_hugepages(mut self, hugepage_size: Option<u64>) -> Self {
449 assert_ne!(
450 self.requested_private_memory,
451 Some(true),
452 "with_hugepages forces shared memory, which conflicts with the \
453 explicitly requested private memory"
454 );
455 for node in &mut self.config.numa.nodes {
456 if let Some(mem) = &mut node.mem {
457 mem.hugepages = true;
458 mem.hugepage_size = hugepage_size;
459 mem.private_memory = false;
460 }
461 }
462 self
463 }
464
465 pub fn with_pcie_root_topology(
477 mut self,
478 segment_count: u64,
479 root_complex_per_segment: u64,
480 root_ports_per_root_complex: u64,
481 ) -> Self {
482 const LOW_MMIO_SIZE: u64 = 64 * 1024 * 1024; const HIGH_MMIO_SIZE: u64 = 1024 * 1024 * 1024; let segment_base = self
490 .config
491 .pcie_root_complexes
492 .iter()
493 .map(|rc| u64::from(rc.segment) + 1)
494 .max()
495 .unwrap_or(0);
496 let index_base = self.config.pcie_root_complexes.len() as u64;
497
498 for segment_offset in 0..segment_count {
500 let segment = segment_base + segment_offset;
501 let bus_count_per_rc = 256 / root_complex_per_segment;
502 for rc_index_in_segment in 0..root_complex_per_segment {
503 let index =
504 index_base + segment_offset * root_complex_per_segment + rc_index_in_segment;
505 let name = format!("s{}rc{}", segment, rc_index_in_segment);
506
507 let start_bus = rc_index_in_segment * bus_count_per_rc;
508 let end_bus = start_bus + bus_count_per_rc - 1;
509
510 let ports = (0..root_ports_per_root_complex)
511 .map(|i| PciePortConfig {
512 name: format!("s{}rc{}rp{}", segment, rc_index_in_segment, i),
513 devfn: None,
514 hotplug: true,
515 acs_capabilities_supported: Some(0),
516 cxl: false,
517 pasid: false,
518 })
519 .collect();
520
521 self.config.pcie_root_complexes.push(PcieRootComplexConfig {
522 index: index.try_into().unwrap(),
523 name,
524 segment: segment.try_into().unwrap(),
525 start_bus: start_bus.try_into().unwrap(),
526 end_bus: end_bus.try_into().unwrap(),
527 low_mmio: PcieMmioRangeConfig::Dynamic {
528 size: LOW_MMIO_SIZE,
529 },
530 high_mmio: PcieMmioRangeConfig::Dynamic {
531 size: HIGH_MMIO_SIZE,
532 },
533 cxl: None,
534 ports,
535 iommu: None,
536 vnode: None,
537 preserve_bars: false,
538 });
539 }
540 }
541
542 self
543 }
544
545 pub fn with_pcie_switch(
547 mut self,
548 port_name: &str,
549 switch_name: &str,
550 port_count: u8,
551 hotplug: bool,
552 ) -> Self {
553 self.config.pcie_switches.push(PcieSwitchConfig {
554 name: switch_name.to_string(),
555 parent_port: port_name.to_string(),
556 ports: (0..port_count)
557 .map(|i| PciePortConfig {
558 name: format!("{switch_name}-downstream-{i}"),
559 devfn: None,
560 hotplug,
561 acs_capabilities_supported: Some(0),
562 cxl: false,
563 pasid: false,
564 })
565 .collect(),
566 });
567 self
568 }
569
570 pub fn with_smmu(mut self, rc_names: &[&str]) -> Self {
577 for name in rc_names {
578 self.pending_iommu.push((
579 name.to_string(),
580 PcieIommuConfig::Smmu {
581 accel: false,
582 oas: openvmm_defs::config::SmmuOas::Auto,
583 },
584 ));
585 }
586 self
587 }
588
589 pub fn with_smmu_accel(mut self, rc_names: &[&str]) -> Self {
598 for name in rc_names {
599 self.pending_iommu.push((
600 name.to_string(),
601 PcieIommuConfig::Smmu {
602 accel: true,
603 oas: openvmm_defs::config::SmmuOas::Auto,
604 },
605 ));
606 }
607 self
608 }
609
610 pub fn with_amd_iommu(mut self, rc_names: &[&str]) -> Self {
619 for name in rc_names {
620 self.pending_iommu
621 .push((name.to_string(), PcieIommuConfig::AmdVi));
622 }
623 self
624 }
625
626 pub fn with_intel_vtd(mut self, rc_names: &[&str]) -> Self {
635 for name in rc_names {
636 self.pending_iommu
637 .push((name.to_string(), PcieIommuConfig::IntelVtd));
638 }
639 self
640 }
641
642 pub fn with_custom_config(mut self, f: impl FnOnce(&mut Config)) -> Self {
646 f(&mut self.config);
647 self
648 }
649
650 pub fn with_allow_early_vtl0_access(mut self, allow: bool) -> Self {
656 self.config
657 .hypervisor
658 .with_vtl2
659 .as_mut()
660 .unwrap()
661 .late_map_vtl0_memory =
662 (!allow).then_some(openvmm_defs::config::LateMapVtl0MemoryPolicy::InjectException);
663
664 self
665 }
666}