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_pcie_nic(mut self, port_name: &str, mac_address: MacAddress) -> Self {
163 let endpoint = net_backend_resources::consomme::ConsommeHandle {
164 cidr: None,
165 ports: Vec::new(),
166 recv: None,
167 }
168 .into_resource();
169 self.config.pcie_devices.push(PcieDeviceConfig {
170 port_name: port_name.to_string(),
171 resource: GdmaDeviceHandle {
172 vports: vec![VportDefinition {
173 mac_address,
174 endpoint,
175 }],
176 }
177 .into_resource(),
178 });
179
180 self
181 }
182
183 pub fn with_pcie_nvme(mut self, port_name: &str, subsystem_id: Guid) -> Self {
185 self.config.pcie_devices.push(PcieDeviceConfig {
186 port_name: port_name.to_string(),
187 resource: NvmeControllerHandle {
188 subsystem_id,
189 max_io_queues: 64,
190 msix_count: 64,
191 namespaces: vec![NamespaceDefinition {
192 nsid: 1,
193 disk: LayeredDiskHandle::single_layer(RamDiskLayerHandle {
194 len: Some(1024 * 1024),
195 sector_size: None,
196 })
197 .into_resource(),
198 read_only: false,
199 }],
200 requests: None,
201 }
202 .into_resource(),
203 });
204
205 self
206 }
207
208 pub fn with_virtio_nic(mut self, port_name: &str, mac_address: MacAddress) -> Self {
213 let endpoint = net_backend_resources::consomme::ConsommeHandle {
214 cidr: None,
215 ports: Vec::new(),
216 recv: None,
217 }
218 .into_resource();
219
220 self.config.pcie_devices.push(PcieDeviceConfig {
221 port_name: port_name.to_string(),
222 resource: virtio_resources::VirtioPciDeviceHandle(
223 virtio_resources::net::VirtioNetHandle {
224 max_queues: None,
225 mac_address,
226 endpoint,
227 }
228 .into_resource(),
229 )
230 .into_resource(),
231 });
232
233 self
234 }
235
236 pub fn with_tcp_pipette_nic(mut self, port_name: &str, mac_address: MacAddress) -> Self {
244 let (port_send, port_recv) = mesh::oneshot();
245 let endpoint = net_backend_resources::consomme::ConsommeHandle {
246 cidr: None,
247 ports: vec![net_backend_resources::consomme::HostPortConfig {
248 protocol: net_backend_resources::consomme::HostPortProtocol::Tcp,
249 host_address: Some(net_backend_resources::consomme::HostIpAddress::Ipv4(
250 std::net::Ipv4Addr::LOCALHOST,
251 )),
252 host_port: net_backend_resources::consomme::HostPort::Dynamic(port_send),
253 guest_port: pipette_client::PIPETTE_PORT as u16,
254 }],
255 recv: None,
256 }
257 .into_resource();
258 self.config.pcie_devices.push(PcieDeviceConfig {
259 port_name: port_name.to_string(),
260 resource: virtio_resources::VirtioPciDeviceHandle(
261 virtio_resources::net::VirtioNetHandle {
262 max_queues: None,
263 mac_address,
264 endpoint,
265 }
266 .into_resource(),
267 )
268 .into_resource(),
269 });
270 self.resources.tcp_pipette_port = Some(port_recv);
271 self
272 }
273
274 pub fn with_nested_virt(mut self) -> Self {
276 self.config.hypervisor.nested_virt = true;
277 self
278 }
279
280 #[cfg(windows)]
296 pub fn with_dio_nic(mut self, switch_id: Option<Guid>) -> Self {
297 let switch_port_id = vmswitch::kernel::SwitchPortId {
298 switch: switch_id.unwrap_or(vmswitch::hcn::DEFAULT_SWITCH),
299 port: Guid::new_random(),
300 };
301 let _ = vmswitch::hcn::Network::open(&switch_port_id.switch)
302 .unwrap_or_else(|e| panic!("could not find switch {}: {e}", switch_port_id.switch));
303 let switch_port = vmswitch::kernel::SwitchPort::new(&switch_port_id)
304 .expect("failed to create vmswitch DIO port");
305 self.resources._switch_ports.push(switch_port);
306
307 let endpoint = net_backend_resources::dio::WindowsDirectIoHandle {
308 switch_port_id: net_backend_resources::dio::SwitchPortId {
309 switch: switch_port_id.switch,
310 port: switch_port_id.port,
311 },
312 }
313 .into_resource();
314
315 if let Some(vtl2_settings) = self.runtime_config.vtl2_settings.as_mut() {
316 self.config.vpci_devices.push(VpciDeviceConfig {
317 vtl: DeviceVtl::Vtl2,
318 instance_id: MANA_INSTANCE,
319 resource: GdmaDeviceHandle {
320 vports: vec![VportDefinition {
321 mac_address: NIC_MAC_ADDRESS,
322 endpoint,
323 }],
324 }
325 .into_resource(),
326 vnode: None,
327 });
328
329 vtl2_settings.dynamic.as_mut().unwrap().nic_devices.push(
330 vtl2_settings_proto::NicDeviceLegacy {
331 instance_id: MANA_INSTANCE.to_string(),
332 subordinate_instance_id: None,
333 max_sub_channels: None,
334 },
335 );
336 } else {
337 const NETVSP_DIO_INSTANCE: Guid = guid::guid!("d1ff4c5a-1b3c-4f0d-8e10-1b9d8b1d1cee");
338 self.config.vmbus_devices.push((
339 DeviceVtl::Vtl0,
340 netvsp_resources::NetvspHandle {
341 instance_id: NETVSP_DIO_INSTANCE,
342 mac_address: NIC_MAC_ADDRESS,
343 endpoint,
344 max_queues: None,
345 }
346 .into_resource(),
347 ));
348 }
349
350 self
351 }
352
353 pub fn with_vtl2_relocation_mode(mut self, mode: Vtl2BaseAddressType) -> Self {
355 let LoadMode::Igvm {
356 vtl2_base_address, ..
357 } = &mut self.config.load_mode
358 else {
359 panic!("vtl2 relocation mode is only supported for OpenHCL firmware")
360 };
361 *vtl2_base_address = mode;
362 self
363 }
364
365 pub fn with_memory_backing_file(mut self, path: impl Into<std::path::PathBuf>) -> Self {
377 assert_ne!(
378 self.requested_private_memory,
379 Some(true),
380 "with_memory_backing_file forces shared memory, which conflicts with \
381 the explicitly requested private memory"
382 );
383 self.memory_backing_file = Some(path.into());
384 for node in &mut self.config.numa.nodes {
385 if let Some(mem) = &mut node.mem {
386 mem.private_memory = false;
387 }
388 }
389 self
390 }
391
392 pub fn with_hugepages(mut self, hugepage_size: Option<u64>) -> Self {
400 assert_ne!(
401 self.requested_private_memory,
402 Some(true),
403 "with_hugepages forces shared memory, which conflicts with the \
404 explicitly requested private memory"
405 );
406 for node in &mut self.config.numa.nodes {
407 if let Some(mem) = &mut node.mem {
408 mem.hugepages = true;
409 mem.hugepage_size = hugepage_size;
410 mem.private_memory = false;
411 }
412 }
413 self
414 }
415
416 pub fn with_pcie_root_topology(
428 mut self,
429 segment_count: u64,
430 root_complex_per_segment: u64,
431 root_ports_per_root_complex: u64,
432 ) -> Self {
433 const LOW_MMIO_SIZE: u64 = 64 * 1024 * 1024; const HIGH_MMIO_SIZE: u64 = 1024 * 1024 * 1024; let segment_base = self
441 .config
442 .pcie_root_complexes
443 .iter()
444 .map(|rc| u64::from(rc.segment) + 1)
445 .max()
446 .unwrap_or(0);
447 let index_base = self.config.pcie_root_complexes.len() as u64;
448
449 for segment_offset in 0..segment_count {
451 let segment = segment_base + segment_offset;
452 let bus_count_per_rc = 256 / root_complex_per_segment;
453 for rc_index_in_segment in 0..root_complex_per_segment {
454 let index =
455 index_base + segment_offset * root_complex_per_segment + rc_index_in_segment;
456 let name = format!("s{}rc{}", segment, rc_index_in_segment);
457
458 let start_bus = rc_index_in_segment * bus_count_per_rc;
459 let end_bus = start_bus + bus_count_per_rc - 1;
460
461 let ports = (0..root_ports_per_root_complex)
462 .map(|i| PciePortConfig {
463 name: format!("s{}rc{}rp{}", segment, rc_index_in_segment, i),
464 devfn: None,
465 hotplug: true,
466 acs_capabilities_supported: Some(0),
467 cxl: false,
468 pasid: false,
469 })
470 .collect();
471
472 self.config.pcie_root_complexes.push(PcieRootComplexConfig {
473 index: index.try_into().unwrap(),
474 name,
475 segment: segment.try_into().unwrap(),
476 start_bus: start_bus.try_into().unwrap(),
477 end_bus: end_bus.try_into().unwrap(),
478 low_mmio: PcieMmioRangeConfig::Dynamic {
479 size: LOW_MMIO_SIZE,
480 },
481 high_mmio: PcieMmioRangeConfig::Dynamic {
482 size: HIGH_MMIO_SIZE,
483 },
484 cxl: None,
485 ports,
486 iommu: None,
487 vnode: None,
488 preserve_bars: false,
489 });
490 }
491 }
492
493 self
494 }
495
496 pub fn with_pcie_switch(
498 mut self,
499 port_name: &str,
500 switch_name: &str,
501 port_count: u8,
502 hotplug: bool,
503 ) -> Self {
504 self.config.pcie_switches.push(PcieSwitchConfig {
505 name: switch_name.to_string(),
506 parent_port: port_name.to_string(),
507 ports: (0..port_count)
508 .map(|i| PciePortConfig {
509 name: format!("{switch_name}-downstream-{i}"),
510 devfn: None,
511 hotplug,
512 acs_capabilities_supported: Some(0),
513 cxl: false,
514 pasid: false,
515 })
516 .collect(),
517 });
518 self
519 }
520
521 pub fn with_smmu(mut self, rc_names: &[&str]) -> Self {
528 for name in rc_names {
529 self.pending_iommu.push((
530 name.to_string(),
531 PcieIommuConfig::Smmu {
532 accel: false,
533 oas: openvmm_defs::config::SmmuOas::Auto,
534 },
535 ));
536 }
537 self
538 }
539
540 pub fn with_smmu_accel(mut self, rc_names: &[&str]) -> Self {
549 for name in rc_names {
550 self.pending_iommu.push((
551 name.to_string(),
552 PcieIommuConfig::Smmu {
553 accel: true,
554 oas: openvmm_defs::config::SmmuOas::Auto,
555 },
556 ));
557 }
558 self
559 }
560
561 pub fn with_amd_iommu(mut self, rc_names: &[&str]) -> Self {
570 for name in rc_names {
571 self.pending_iommu
572 .push((name.to_string(), PcieIommuConfig::AmdVi));
573 }
574 self
575 }
576
577 pub fn with_intel_vtd(mut self, rc_names: &[&str]) -> Self {
586 for name in rc_names {
587 self.pending_iommu
588 .push((name.to_string(), PcieIommuConfig::IntelVtd));
589 }
590 self
591 }
592
593 pub fn with_custom_config(mut self, f: impl FnOnce(&mut Config)) -> Self {
597 f(&mut self.config);
598 self
599 }
600
601 pub fn with_allow_early_vtl0_access(mut self, allow: bool) -> Self {
607 self.config
608 .hypervisor
609 .with_vtl2
610 .as_mut()
611 .unwrap()
612 .late_map_vtl0_memory =
613 (!allow).then_some(openvmm_defs::config::LateMapVtl0MemoryPolicy::InjectException);
614
615 self
616 }
617}