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 memory_range::MemoryRange;
22use net_backend_resources::mac_address::MacAddress;
23use nvme_resources::NamespaceDefinition;
24use nvme_resources::NvmeControllerHandle;
25use openvmm_defs::config::Config;
26use openvmm_defs::config::DeviceVtl;
27use openvmm_defs::config::LoadMode;
28use openvmm_defs::config::PcieDeviceConfig;
29use openvmm_defs::config::PcieRootComplexConfig;
30use openvmm_defs::config::PcieRootPortConfig;
31use openvmm_defs::config::PcieSwitchConfig;
32use openvmm_defs::config::VpciDeviceConfig;
33use openvmm_defs::config::Vtl2BaseAddressType;
34use vm_resource::IntoResource;
35use vmotherboard::ChipsetDeviceHandle;
36
37impl PetriVmConfigOpenVmm {
38    /// Enable the VTL0 alias map.
39    // TODO: Remove once #912 is fixed.
40    pub fn with_vtl0_alias_map(mut self) -> Self {
41        self.config
42            .hypervisor
43            .with_vtl2
44            .as_mut()
45            .expect("Not an openhcl config.")
46            .vtl0_alias_map = true;
47        self
48    }
49
50    /// Enable the battery for the VM.
51    pub fn with_battery(mut self) -> Self {
52        if self.resources.properties.is_openhcl {
53            self.ged.as_mut().unwrap().enable_battery = true;
54        } else {
55            self.config.chipset_devices.push(ChipsetDeviceHandle {
56                name: "battery".to_string(),
57                resource: BatteryDeviceHandleX64 {
58                    battery_status_recv: {
59                        let (tx, rx) = mesh::channel();
60                        tx.send(HostBatteryUpdate::default_present());
61                        rx
62                    },
63                }
64                .into_resource(),
65            });
66            if let LoadMode::Uefi { enable_battery, .. } = &mut self.config.load_mode {
67                *enable_battery = true;
68            }
69        }
70        self
71    }
72
73    /// Set test config for the GED's IGVM attest request handler
74    pub fn with_igvm_attest_test_config(mut self, config: IgvmAttestTestConfig) -> Self {
75        if !self.resources.properties.is_openhcl {
76            panic!("IGVM Attest test config is only supported for OpenHCL.")
77        };
78
79        let ged = self.ged.as_mut().expect("No GED to configure TPM");
80
81        ged.igvm_attest_test_config = Some(config);
82
83        self
84    }
85
86    /// Enable a synthnic for the VM.
87    ///
88    /// Uses a mana emulator and the paravisor if a paravisor is present.
89    pub fn with_nic(mut self) -> Self {
90        let endpoint =
91            net_backend_resources::consomme::ConsommeHandle { cidr: None }.into_resource();
92        if let Some(vtl2_settings) = self.runtime_config.vtl2_settings.as_mut() {
93            self.config.vpci_devices.push(VpciDeviceConfig {
94                vtl: DeviceVtl::Vtl2,
95                instance_id: MANA_INSTANCE,
96                resource: GdmaDeviceHandle {
97                    vports: vec![VportDefinition {
98                        mac_address: NIC_MAC_ADDRESS,
99                        endpoint,
100                    }],
101                }
102                .into_resource(),
103            });
104
105            vtl2_settings.dynamic.as_mut().unwrap().nic_devices.push(
106                vtl2_settings_proto::NicDeviceLegacy {
107                    instance_id: MANA_INSTANCE.to_string(),
108                    subordinate_instance_id: None,
109                    max_sub_channels: None,
110                },
111            );
112        } else {
113            const NETVSP_INSTANCE: Guid = guid::guid!("c6c46cc3-9302-4344-b206-aef65e5bd0a2");
114            self.config.vmbus_devices.push((
115                DeviceVtl::Vtl0,
116                netvsp_resources::NetvspHandle {
117                    instance_id: NETVSP_INSTANCE,
118                    mac_address: NIC_MAC_ADDRESS,
119                    endpoint,
120                    max_queues: None,
121                }
122                .into_resource(),
123            ));
124        }
125
126        self
127    }
128
129    /// Add a PCIe NIC to the VM using the MANA emulator.
130    pub fn with_pcie_nic(mut self, port_name: &str, mac_address: MacAddress) -> Self {
131        let endpoint =
132            net_backend_resources::consomme::ConsommeHandle { cidr: None }.into_resource();
133        self.config.pcie_devices.push(PcieDeviceConfig {
134            port_name: port_name.to_string(),
135            resource: GdmaDeviceHandle {
136                vports: vec![VportDefinition {
137                    mac_address,
138                    endpoint,
139                }],
140            }
141            .into_resource(),
142        });
143
144        self
145    }
146
147    /// Add a PCIe NVMe device to the VM using the NVMe emulator.
148    pub fn with_pcie_nvme(mut self, port_name: &str, subsystem_id: Guid) -> Self {
149        self.config.pcie_devices.push(PcieDeviceConfig {
150            port_name: port_name.to_string(),
151            resource: NvmeControllerHandle {
152                subsystem_id,
153                max_io_queues: 64,
154                msix_count: 64,
155                namespaces: vec![NamespaceDefinition {
156                    nsid: 1,
157                    disk: LayeredDiskHandle::single_layer(RamDiskLayerHandle {
158                        len: Some(1024 * 1024),
159                        sector_size: None,
160                    })
161                    .into_resource(),
162                    read_only: false,
163                }],
164                requests: None,
165            }
166            .into_resource(),
167        });
168
169        self
170    }
171
172    /// Enable a virtio-net NIC for the VM backed by Consomme.
173    ///
174    /// This exposes a virtio-net device on a PCIe root port, suitable for
175    /// guests running virtio drivers (e.g. Linux with UEFI boot).
176    pub fn with_virtio_nic(mut self, port_name: &str) -> Self {
177        let endpoint =
178            net_backend_resources::consomme::ConsommeHandle { cidr: None }.into_resource();
179
180        self.config.pcie_devices.push(PcieDeviceConfig {
181            port_name: port_name.to_string(),
182            resource: virtio_resources::VirtioPciDeviceHandle(
183                virtio_resources::net::VirtioNetHandle {
184                    max_queues: None,
185                    mac_address: NIC_MAC_ADDRESS,
186                    endpoint,
187                }
188                .into_resource(),
189            )
190            .into_resource(),
191        });
192
193        self
194    }
195
196    /// Load with the specified VTL2 relocation mode.
197    pub fn with_vtl2_relocation_mode(mut self, mode: Vtl2BaseAddressType) -> Self {
198        let LoadMode::Igvm {
199            vtl2_base_address, ..
200        } = &mut self.config.load_mode
201        else {
202            panic!("vtl2 relocation mode is only supported for OpenHCL firmware")
203        };
204        *vtl2_base_address = mode;
205        self
206    }
207
208    /// Use a file-backed memory region instead of anonymous RAM.
209    ///
210    /// The file at the given path will be created (or opened) and sized to
211    /// match the VM's configured memory. Guest memory is then backed by
212    /// this file, which persists across snapshot save/restore.
213    pub fn with_memory_backing_file(mut self, path: impl Into<std::path::PathBuf>) -> Self {
214        self.memory_backing_file = Some(path.into());
215        self
216    }
217
218    /// Add a symmetric PCIe topology to the VM based on some basic scale factors
219    ///
220    /// All root ports are named according to their index within their parent
221    /// using the naming scheme `sXrcYrpZ`. For example, the third root port on
222    /// the fourth root complex in segment 0 would be named `s0rc3rp2`.
223    pub fn with_pcie_root_topology(
224        mut self,
225        segment_count: u64,
226        root_complex_per_segment: u64,
227        root_ports_per_root_complex: u64,
228    ) -> Self {
229        const SINGLE_BUS_NUMBER_ECAM_SIZE: u64 = 1024 * 1024; // 1 MB
230        const FULL_SEGMENT_ECAM_SIZE: u64 = 256 * SINGLE_BUS_NUMBER_ECAM_SIZE; // 256 MB
231        const LOW_MMIO_SIZE: u64 = 64 * 1024 * 1024; // 64 MB
232        const HIGH_MMIO_SIZE: u64 = 1024 * 1024 * 1024; // 1 GB
233
234        // Allocate and configure the address space gaps
235        let ecam_size = segment_count * FULL_SEGMENT_ECAM_SIZE;
236        let low_mmio_size = segment_count * root_complex_per_segment * LOW_MMIO_SIZE;
237        let high_mmio_size = segment_count * root_complex_per_segment * HIGH_MMIO_SIZE;
238
239        let low_mmio_start = self.config.memory.mmio_gaps[0].start();
240        let high_mmio_end = self.config.memory.mmio_gaps[1].end();
241
242        let ecam_gap = MemoryRange::new(low_mmio_start - ecam_size..low_mmio_start);
243        let low_gap = MemoryRange::new(ecam_gap.start() - low_mmio_size..ecam_gap.start());
244        let high_gap = MemoryRange::new(high_mmio_end..high_mmio_end + high_mmio_size);
245
246        self.config.memory.pci_ecam_gaps.push(ecam_gap);
247        self.config.memory.pci_mmio_gaps.push(low_gap);
248        self.config.memory.pci_mmio_gaps.push(high_gap);
249
250        // Add the root complexes to the VM
251        for segment in 0..segment_count {
252            let bus_count_per_rc = 256 / root_complex_per_segment;
253            for rc_index_in_segment in 0..root_complex_per_segment {
254                let index = segment * root_complex_per_segment + rc_index_in_segment;
255                let name = format!("s{}rc{}", segment, rc_index_in_segment);
256
257                let start_bus = rc_index_in_segment * bus_count_per_rc;
258                let end_bus = start_bus + bus_count_per_rc - 1;
259
260                let ecam_range_start = ecam_gap.start()
261                    + segment * FULL_SEGMENT_ECAM_SIZE
262                    + start_bus * SINGLE_BUS_NUMBER_ECAM_SIZE;
263                let ecam_range_end =
264                    ecam_range_start + bus_count_per_rc * SINGLE_BUS_NUMBER_ECAM_SIZE;
265
266                let low_mmio_start = low_gap.start() + index * LOW_MMIO_SIZE;
267                let low_mmio_end = low_gap.start() + (index + 1) * LOW_MMIO_SIZE;
268                let high_mmio_start = high_gap.start() + index * HIGH_MMIO_SIZE;
269                let high_mmio_end = high_gap.start() + (index + 1) * HIGH_MMIO_SIZE;
270
271                let ports = (0..root_ports_per_root_complex)
272                    .map(|i| PcieRootPortConfig {
273                        name: format!("s{}rc{}rp{}", segment, rc_index_in_segment, i),
274                        hotplug: true,
275                    })
276                    .collect();
277
278                self.config.pcie_root_complexes.push(PcieRootComplexConfig {
279                    index: index.try_into().unwrap(),
280                    name,
281                    segment: segment.try_into().unwrap(),
282                    start_bus: start_bus.try_into().unwrap(),
283                    end_bus: end_bus.try_into().unwrap(),
284                    ecam_range: MemoryRange::new(ecam_range_start..ecam_range_end),
285                    low_mmio: MemoryRange::new(low_mmio_start..low_mmio_end),
286                    high_mmio: MemoryRange::new(high_mmio_start..high_mmio_end),
287                    ports,
288                });
289            }
290        }
291
292        self
293    }
294
295    /// Add a PCIe switch to the VM.
296    pub fn with_pcie_switch(
297        mut self,
298        port_name: &str,
299        switch_name: &str,
300        port_count: u8,
301        hotplug: bool,
302    ) -> Self {
303        self.config.pcie_switches.push(PcieSwitchConfig {
304            name: switch_name.to_string(),
305            num_downstream_ports: port_count,
306            parent_port: port_name.to_string(),
307            hotplug,
308        });
309        self
310    }
311
312    /// This is intended for special one-off use cases. As soon as something
313    /// is needed in multiple tests we should consider making it a supported
314    /// pattern.
315    pub fn with_custom_config(mut self, f: impl FnOnce(&mut Config)) -> Self {
316        f(&mut self.config);
317        self
318    }
319
320    /// Specifies whether VTL2 should be allowed to access VTL0 memory before it
321    /// sets any VTL protections.
322    ///
323    /// This is needed just for the TMK VMM, and only until it gains support for
324    /// setting VTL protections.
325    pub fn with_allow_early_vtl0_access(mut self, allow: bool) -> Self {
326        self.config
327            .hypervisor
328            .with_vtl2
329            .as_mut()
330            .unwrap()
331            .late_map_vtl0_memory =
332            (!allow).then_some(openvmm_defs::config::LateMapVtl0MemoryPolicy::InjectException);
333
334        self
335    }
336}