Skip to main content

petri/vm/openvmm/
modify.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Helpers to modify a [`PetriVmConfigOpenVmm`] from its defaults.
5
6// TODO: Delete all modification functions that are not backend-specific
7// from this file, add necessary settings to the backend-agnostic
8// `PetriVmConfig`, and add corresponding functions to `PetriVmBuilder`.
9
10use 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::PcieMmioRangeConfig;
29use openvmm_defs::config::PcieRootComplexConfig;
30use openvmm_defs::config::PcieRootPortConfig;
31use openvmm_defs::config::PcieSwitchConfig;
32use openvmm_defs::config::SmmuInstanceConfig;
33use openvmm_defs::config::VpciDeviceConfig;
34use openvmm_defs::config::Vtl2BaseAddressType;
35use vm_resource::IntoResource;
36use vmotherboard::ChipsetDeviceHandle;
37
38impl PetriVmConfigOpenVmm {
39    /// Enable the VTL0 alias map.
40    // TODO: Remove once #912 is fixed.
41    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    /// Enable the battery for the VM.
52    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    /// Set test config for the GED's IGVM attest request handler
75    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    /// Enable a synthnic for the VM.
88    ///
89    /// Uses a mana emulator and the paravisor if a paravisor is present.
90    pub fn with_nic(mut self) -> Self {
91        let endpoint = net_backend_resources::consomme::ConsommeHandle {
92            cidr: None,
93            ports: Vec::new(),
94        }
95        .into_resource();
96        if let Some(vtl2_settings) = self.runtime_config.vtl2_settings.as_mut() {
97            self.config.vpci_devices.push(VpciDeviceConfig {
98                vtl: DeviceVtl::Vtl2,
99                instance_id: MANA_INSTANCE,
100                resource: GdmaDeviceHandle {
101                    vports: vec![VportDefinition {
102                        mac_address: NIC_MAC_ADDRESS,
103                        endpoint,
104                    }],
105                }
106                .into_resource(),
107            });
108
109            vtl2_settings.dynamic.as_mut().unwrap().nic_devices.push(
110                vtl2_settings_proto::NicDeviceLegacy {
111                    instance_id: MANA_INSTANCE.to_string(),
112                    subordinate_instance_id: None,
113                    max_sub_channels: None,
114                },
115            );
116        } else {
117            const NETVSP_INSTANCE: Guid = guid::guid!("c6c46cc3-9302-4344-b206-aef65e5bd0a2");
118            self.config.vmbus_devices.push((
119                DeviceVtl::Vtl0,
120                netvsp_resources::NetvspHandle {
121                    instance_id: NETVSP_INSTANCE,
122                    mac_address: NIC_MAC_ADDRESS,
123                    endpoint,
124                    max_queues: None,
125                }
126                .into_resource(),
127            ));
128        }
129
130        self
131    }
132
133    /// Add a PCIe NIC to the VM using the MANA emulator.
134    pub fn with_pcie_nic(mut self, port_name: &str, mac_address: MacAddress) -> Self {
135        let endpoint = net_backend_resources::consomme::ConsommeHandle {
136            cidr: None,
137            ports: Vec::new(),
138        }
139        .into_resource();
140        self.config.pcie_devices.push(PcieDeviceConfig {
141            port_name: port_name.to_string(),
142            resource: GdmaDeviceHandle {
143                vports: vec![VportDefinition {
144                    mac_address,
145                    endpoint,
146                }],
147            }
148            .into_resource(),
149        });
150
151        self
152    }
153
154    /// Add a PCIe NVMe device to the VM using the NVMe emulator.
155    pub fn with_pcie_nvme(mut self, port_name: &str, subsystem_id: Guid) -> Self {
156        self.config.pcie_devices.push(PcieDeviceConfig {
157            port_name: port_name.to_string(),
158            resource: NvmeControllerHandle {
159                subsystem_id,
160                max_io_queues: 64,
161                msix_count: 64,
162                namespaces: vec![NamespaceDefinition {
163                    nsid: 1,
164                    disk: LayeredDiskHandle::single_layer(RamDiskLayerHandle {
165                        len: Some(1024 * 1024),
166                        sector_size: None,
167                    })
168                    .into_resource(),
169                    read_only: false,
170                }],
171                requests: None,
172            }
173            .into_resource(),
174        });
175
176        self
177    }
178
179    /// Enable a virtio-net NIC for the VM backed by Consomme.
180    ///
181    /// This exposes a virtio-net device on a PCIe root port, suitable for
182    /// guests running virtio drivers (e.g. Linux with UEFI boot).
183    pub fn with_virtio_nic(mut self, port_name: &str) -> Self {
184        let endpoint = net_backend_resources::consomme::ConsommeHandle {
185            cidr: None,
186            ports: Vec::new(),
187        }
188        .into_resource();
189
190        self.config.pcie_devices.push(PcieDeviceConfig {
191            port_name: port_name.to_string(),
192            resource: virtio_resources::VirtioPciDeviceHandle(
193                virtio_resources::net::VirtioNetHandle {
194                    max_queues: None,
195                    mac_address: NIC_MAC_ADDRESS,
196                    endpoint,
197                }
198                .into_resource(),
199            )
200            .into_resource(),
201        });
202
203        self
204    }
205
206    /// Load with the specified VTL2 relocation mode.
207    pub fn with_vtl2_relocation_mode(mut self, mode: Vtl2BaseAddressType) -> Self {
208        let LoadMode::Igvm {
209            vtl2_base_address, ..
210        } = &mut self.config.load_mode
211        else {
212            panic!("vtl2 relocation mode is only supported for OpenHCL firmware")
213        };
214        *vtl2_base_address = mode;
215        self
216    }
217
218    /// Use a file-backed memory region instead of anonymous RAM.
219    ///
220    /// The file at the given path will be created (or opened) and sized to
221    /// match the VM's configured memory. Guest memory is then backed by
222    /// this file, which persists across snapshot save/restore.
223    pub fn with_memory_backing_file(mut self, path: impl Into<std::path::PathBuf>) -> Self {
224        self.memory_backing_file = Some(path.into());
225        self
226    }
227
228    /// Use explicit hugetlb-backed guest memory.
229    pub fn with_hugepages(mut self, hugepage_size: Option<u64>) -> Self {
230        self.config.memory.hugepages = true;
231        self.config.memory.hugepage_size = hugepage_size;
232        self
233    }
234
235    /// Add a symmetric PCIe topology to the VM based on some basic scale factors
236    ///
237    /// All root ports are named according to their index within their parent
238    /// using the naming scheme `sXrcYrpZ`. For example, the third root port on
239    /// the fourth root complex in segment 0 would be named `s0rc3rp2`.
240    pub fn with_pcie_root_topology(
241        mut self,
242        segment_count: u64,
243        root_complex_per_segment: u64,
244        root_ports_per_root_complex: u64,
245    ) -> Self {
246        const LOW_MMIO_SIZE: u64 = 64 * 1024 * 1024; // 64 MB
247        const HIGH_MMIO_SIZE: u64 = 1024 * 1024 * 1024; // 1 GB
248
249        // Add the root complexes to the VM
250        for segment in 0..segment_count {
251            let bus_count_per_rc = 256 / root_complex_per_segment;
252            for rc_index_in_segment in 0..root_complex_per_segment {
253                let index = segment * root_complex_per_segment + rc_index_in_segment;
254                let name = format!("s{}rc{}", segment, rc_index_in_segment);
255
256                let start_bus = rc_index_in_segment * bus_count_per_rc;
257                let end_bus = start_bus + bus_count_per_rc - 1;
258
259                let ports = (0..root_ports_per_root_complex)
260                    .map(|i| PcieRootPortConfig {
261                        name: format!("s{}rc{}rp{}", segment, rc_index_in_segment, i),
262                        hotplug: true,
263                        acs_capabilities_supported: Some(0),
264                        cxl: false,
265                    })
266                    .collect();
267
268                self.config.pcie_root_complexes.push(PcieRootComplexConfig {
269                    index: index.try_into().unwrap(),
270                    name,
271                    segment: segment.try_into().unwrap(),
272                    start_bus: start_bus.try_into().unwrap(),
273                    end_bus: end_bus.try_into().unwrap(),
274                    low_mmio: PcieMmioRangeConfig::Dynamic {
275                        size: LOW_MMIO_SIZE,
276                    },
277                    high_mmio: PcieMmioRangeConfig::Dynamic {
278                        size: HIGH_MMIO_SIZE,
279                    },
280                    cxl: None,
281                    ports,
282                });
283            }
284        }
285
286        self
287    }
288
289    /// Add a PCIe switch to the VM.
290    pub fn with_pcie_switch(
291        mut self,
292        port_name: &str,
293        switch_name: &str,
294        port_count: u8,
295        hotplug: bool,
296    ) -> Self {
297        self.config.pcie_switches.push(PcieSwitchConfig {
298            name: switch_name.to_string(),
299            num_downstream_ports: port_count,
300            parent_port: port_name.to_string(),
301            hotplug,
302            acs_capabilities_supported: Some(0),
303        });
304        self
305    }
306
307    /// Enable SMMUv3 IOMMU on the specified root complexes (aarch64 only).
308    ///
309    /// Each name must match a root complex added via
310    /// [`with_pcie_root_topology`](Self::with_pcie_root_topology). The SMMU
311    /// provides stage 1 IOVA translation for devices behind those root
312    /// complexes.
313    pub fn with_smmu(mut self, rc_names: &[&str]) -> Self {
314        let arch = self
315            .config
316            .processor_topology
317            .arch
318            .as_mut()
319            .expect("arch topology not set");
320
321        match arch {
322            openvmm_defs::config::ArchTopologyConfig::Aarch64(aarch64) => {
323                aarch64.smmu = rc_names
324                    .iter()
325                    .map(|name| SmmuInstanceConfig {
326                        rc_name: name.to_string(),
327                    })
328                    .collect();
329            }
330            _ => panic!("SMMU is only supported on aarch64"),
331        }
332        self
333    }
334
335    /// This is intended for special one-off use cases. As soon as something
336    /// is needed in multiple tests we should consider making it a supported
337    /// pattern.
338    pub fn with_custom_config(mut self, f: impl FnOnce(&mut Config)) -> Self {
339        f(&mut self.config);
340        self
341    }
342
343    /// Specifies whether VTL2 should be allowed to access VTL0 memory before it
344    /// sets any VTL protections.
345    ///
346    /// This is needed just for the TMK VMM, and only until it gains support for
347    /// setting VTL protections.
348    pub fn with_allow_early_vtl0_access(mut self, allow: bool) -> Self {
349        self.config
350            .hypervisor
351            .with_vtl2
352            .as_mut()
353            .unwrap()
354            .late_map_vtl0_memory =
355            (!allow).then_some(openvmm_defs::config::LateMapVtl0MemoryPolicy::InjectException);
356
357        self
358    }
359}