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_nic(mut self) -> Self {
91 let endpoint = net_backend_resources::consomme::ConsommeHandle {
92 cidr: None,
93 ports: Vec::new(),
94 recv: None,
95 }
96 .into_resource();
97 if let Some(vtl2_settings) = self.runtime_config.vtl2_settings.as_mut() {
98 self.config.vpci_devices.push(VpciDeviceConfig {
99 vtl: DeviceVtl::Vtl2,
100 instance_id: MANA_INSTANCE,
101 resource: GdmaDeviceHandle {
102 vports: vec![VportDefinition {
103 mac_address: NIC_MAC_ADDRESS,
104 endpoint,
105 }],
106 }
107 .into_resource(),
108 vnode: None,
109 });
110
111 vtl2_settings.dynamic.as_mut().unwrap().nic_devices.push(
112 vtl2_settings_proto::NicDeviceLegacy {
113 instance_id: MANA_INSTANCE.to_string(),
114 subordinate_instance_id: None,
115 max_sub_channels: None,
116 },
117 );
118 } else {
119 const NETVSP_INSTANCE: Guid = guid::guid!("c6c46cc3-9302-4344-b206-aef65e5bd0a2");
120 self.config.vmbus_devices.push((
121 DeviceVtl::Vtl0,
122 netvsp_resources::NetvspHandle {
123 instance_id: NETVSP_INSTANCE,
124 mac_address: NIC_MAC_ADDRESS,
125 endpoint,
126 max_queues: None,
127 }
128 .into_resource(),
129 ));
130 }
131
132 self
133 }
134
135 pub fn with_pcie_nic(mut self, port_name: &str, mac_address: MacAddress) -> Self {
137 let endpoint = net_backend_resources::consomme::ConsommeHandle {
138 cidr: None,
139 ports: Vec::new(),
140 recv: None,
141 }
142 .into_resource();
143 self.config.pcie_devices.push(PcieDeviceConfig {
144 port_name: port_name.to_string(),
145 resource: GdmaDeviceHandle {
146 vports: vec![VportDefinition {
147 mac_address,
148 endpoint,
149 }],
150 }
151 .into_resource(),
152 });
153
154 self
155 }
156
157 pub fn with_pcie_nvme(mut self, port_name: &str, subsystem_id: Guid) -> Self {
159 self.config.pcie_devices.push(PcieDeviceConfig {
160 port_name: port_name.to_string(),
161 resource: NvmeControllerHandle {
162 subsystem_id,
163 max_io_queues: 64,
164 msix_count: 64,
165 namespaces: vec![NamespaceDefinition {
166 nsid: 1,
167 disk: LayeredDiskHandle::single_layer(RamDiskLayerHandle {
168 len: Some(1024 * 1024),
169 sector_size: None,
170 })
171 .into_resource(),
172 read_only: false,
173 }],
174 requests: None,
175 }
176 .into_resource(),
177 });
178
179 self
180 }
181
182 pub fn with_virtio_nic(mut self, port_name: &str) -> Self {
187 let endpoint = net_backend_resources::consomme::ConsommeHandle {
188 cidr: None,
189 ports: Vec::new(),
190 recv: None,
191 }
192 .into_resource();
193
194 self.config.pcie_devices.push(PcieDeviceConfig {
195 port_name: port_name.to_string(),
196 resource: virtio_resources::VirtioPciDeviceHandle(
197 virtio_resources::net::VirtioNetHandle {
198 max_queues: None,
199 mac_address: NIC_MAC_ADDRESS,
200 endpoint,
201 }
202 .into_resource(),
203 )
204 .into_resource(),
205 });
206
207 self
208 }
209
210 pub fn with_tcp_pipette_nic(mut self, port_name: &str) -> Self {
218 let (port_send, port_recv) = mesh::oneshot();
219 let endpoint = net_backend_resources::consomme::ConsommeHandle {
220 cidr: None,
221 ports: vec![net_backend_resources::consomme::HostPortConfig {
222 protocol: net_backend_resources::consomme::HostPortProtocol::Tcp,
223 host_address: Some(net_backend_resources::consomme::HostIpAddress::Ipv4(
224 std::net::Ipv4Addr::LOCALHOST,
225 )),
226 host_port: net_backend_resources::consomme::HostPort::Dynamic(port_send),
227 guest_port: pipette_client::PIPETTE_PORT as u16,
228 }],
229 recv: None,
230 }
231 .into_resource();
232 self.config.pcie_devices.push(PcieDeviceConfig {
233 port_name: port_name.to_string(),
234 resource: virtio_resources::VirtioPciDeviceHandle(
235 virtio_resources::net::VirtioNetHandle {
236 max_queues: None,
237 mac_address: NIC_MAC_ADDRESS,
238 endpoint,
239 }
240 .into_resource(),
241 )
242 .into_resource(),
243 });
244 self.resources.tcp_pipette_port = Some(port_recv);
245 self
246 }
247
248 #[cfg(windows)]
264 pub fn with_dio_nic(mut self, switch_id: Option<Guid>) -> Self {
265 let switch_port_id = vmswitch::kernel::SwitchPortId {
266 switch: switch_id.unwrap_or(vmswitch::hcn::DEFAULT_SWITCH),
267 port: Guid::new_random(),
268 };
269 let _ = vmswitch::hcn::Network::open(&switch_port_id.switch)
270 .unwrap_or_else(|e| panic!("could not find switch {}: {e}", switch_port_id.switch));
271 let switch_port = vmswitch::kernel::SwitchPort::new(&switch_port_id)
272 .expect("failed to create vmswitch DIO port");
273 self.resources._switch_ports.push(switch_port);
274
275 let endpoint = net_backend_resources::dio::WindowsDirectIoHandle {
276 switch_port_id: net_backend_resources::dio::SwitchPortId {
277 switch: switch_port_id.switch,
278 port: switch_port_id.port,
279 },
280 }
281 .into_resource();
282
283 if let Some(vtl2_settings) = self.runtime_config.vtl2_settings.as_mut() {
284 self.config.vpci_devices.push(VpciDeviceConfig {
285 vtl: DeviceVtl::Vtl2,
286 instance_id: MANA_INSTANCE,
287 resource: GdmaDeviceHandle {
288 vports: vec![VportDefinition {
289 mac_address: NIC_MAC_ADDRESS,
290 endpoint,
291 }],
292 }
293 .into_resource(),
294 vnode: None,
295 });
296
297 vtl2_settings.dynamic.as_mut().unwrap().nic_devices.push(
298 vtl2_settings_proto::NicDeviceLegacy {
299 instance_id: MANA_INSTANCE.to_string(),
300 subordinate_instance_id: None,
301 max_sub_channels: None,
302 },
303 );
304 } else {
305 const NETVSP_DIO_INSTANCE: Guid = guid::guid!("d1ff4c5a-1b3c-4f0d-8e10-1b9d8b1d1cee");
306 self.config.vmbus_devices.push((
307 DeviceVtl::Vtl0,
308 netvsp_resources::NetvspHandle {
309 instance_id: NETVSP_DIO_INSTANCE,
310 mac_address: NIC_MAC_ADDRESS,
311 endpoint,
312 max_queues: None,
313 }
314 .into_resource(),
315 ));
316 }
317
318 self
319 }
320
321 pub fn with_vtl2_relocation_mode(mut self, mode: Vtl2BaseAddressType) -> Self {
323 let LoadMode::Igvm {
324 vtl2_base_address, ..
325 } = &mut self.config.load_mode
326 else {
327 panic!("vtl2 relocation mode is only supported for OpenHCL firmware")
328 };
329 *vtl2_base_address = mode;
330 self
331 }
332
333 pub fn with_memory_backing_file(mut self, path: impl Into<std::path::PathBuf>) -> Self {
339 self.memory_backing_file = Some(path.into());
340 self
341 }
342
343 pub fn with_hugepages(mut self, hugepage_size: Option<u64>) -> Self {
345 for node in &mut self.config.numa.nodes {
346 if let Some(mem) = &mut node.mem {
347 mem.hugepages = true;
348 mem.hugepage_size = hugepage_size;
349 }
350 }
351 self
352 }
353
354 pub fn with_pcie_root_topology(
360 mut self,
361 segment_count: u64,
362 root_complex_per_segment: u64,
363 root_ports_per_root_complex: u64,
364 ) -> Self {
365 const LOW_MMIO_SIZE: u64 = 64 * 1024 * 1024; const HIGH_MMIO_SIZE: u64 = 1024 * 1024 * 1024; for segment in 0..segment_count {
370 let bus_count_per_rc = 256 / root_complex_per_segment;
371 for rc_index_in_segment in 0..root_complex_per_segment {
372 let index = segment * root_complex_per_segment + rc_index_in_segment;
373 let name = format!("s{}rc{}", segment, rc_index_in_segment);
374
375 let start_bus = rc_index_in_segment * bus_count_per_rc;
376 let end_bus = start_bus + bus_count_per_rc - 1;
377
378 let ports = (0..root_ports_per_root_complex)
379 .map(|i| PciePortConfig {
380 name: format!("s{}rc{}rp{}", segment, rc_index_in_segment, i),
381 devfn: None,
382 hotplug: true,
383 acs_capabilities_supported: Some(0),
384 cxl: false,
385 })
386 .collect();
387
388 self.config.pcie_root_complexes.push(PcieRootComplexConfig {
389 index: index.try_into().unwrap(),
390 name,
391 segment: segment.try_into().unwrap(),
392 start_bus: start_bus.try_into().unwrap(),
393 end_bus: end_bus.try_into().unwrap(),
394 low_mmio: PcieMmioRangeConfig::Dynamic {
395 size: LOW_MMIO_SIZE,
396 },
397 high_mmio: PcieMmioRangeConfig::Dynamic {
398 size: HIGH_MMIO_SIZE,
399 },
400 cxl: None,
401 ports,
402 iommu: None,
403 vnode: None,
404 preserve_bars: false,
405 });
406 }
407 }
408
409 self
410 }
411
412 pub fn with_pcie_switch(
414 mut self,
415 port_name: &str,
416 switch_name: &str,
417 port_count: u8,
418 hotplug: bool,
419 ) -> Self {
420 self.config.pcie_switches.push(PcieSwitchConfig {
421 name: switch_name.to_string(),
422 parent_port: port_name.to_string(),
423 ports: (0..port_count)
424 .map(|i| PciePortConfig {
425 name: format!("{switch_name}-downstream-{i}"),
426 devfn: None,
427 hotplug,
428 acs_capabilities_supported: Some(0),
429 cxl: false,
430 })
431 .collect(),
432 });
433 self
434 }
435
436 pub fn with_smmu(mut self, rc_names: &[&str]) -> Self {
443 for name in rc_names {
444 self.pending_iommu
445 .push((name.to_string(), PcieIommuConfig::Smmu));
446 }
447 self
448 }
449
450 pub fn with_amd_iommu(mut self, rc_names: &[&str]) -> Self {
459 for name in rc_names {
460 self.pending_iommu
461 .push((name.to_string(), PcieIommuConfig::AmdVi));
462 }
463 self
464 }
465
466 pub fn with_intel_vtd(mut self, rc_names: &[&str]) -> Self {
475 for name in rc_names {
476 self.pending_iommu
477 .push((name.to_string(), PcieIommuConfig::IntelVtd));
478 }
479 self
480 }
481
482 pub fn with_custom_config(mut self, f: impl FnOnce(&mut Config)) -> Self {
486 f(&mut self.config);
487 self
488 }
489
490 pub fn with_allow_early_vtl0_access(mut self, allow: bool) -> Self {
496 self.config
497 .hypervisor
498 .with_vtl2
499 .as_mut()
500 .unwrap()
501 .late_map_vtl0_memory =
502 (!allow).then_some(openvmm_defs::config::LateMapVtl0MemoryPolicy::InjectException);
503
504 self
505 }
506}