petri/vm/openvmm/
modify.rs1use super::MANA_INSTANCE;
7use super::NIC_MAC_ADDRESS;
8use super::PetriVmConfigOpenVmm;
9use super::memdiff_disk_from_artifact;
10use crate::PetriVmgsResource;
11use crate::ProcessorTopology;
12use chipset_resources::battery::BatteryDeviceHandleX64;
13use chipset_resources::battery::HostBatteryUpdate;
14use fs_err::File;
15use gdma_resources::GdmaDeviceHandle;
16use gdma_resources::VportDefinition;
17use get_resources::ged::IgvmAttestTestConfig;
18use hvlite_defs::config::Config;
19use hvlite_defs::config::DeviceVtl;
20use hvlite_defs::config::LoadMode;
21use hvlite_defs::config::VpciDeviceConfig;
22use hvlite_defs::config::Vtl2BaseAddressType;
23use petri_artifacts_common::tags::IsTestVmgs;
24use petri_artifacts_common::tags::MachineArch;
25use petri_artifacts_common::tags::OsFlavor;
26use petri_artifacts_core::ResolvedArtifact;
27use tpm_resources::TpmDeviceHandle;
28use tpm_resources::TpmRegisterLayout;
29use vm_resource::IntoResource;
30use vmcore::non_volatile_store::resources::EphemeralNonVolatileStoreHandle;
31use vmgs_resources::VmgsResource;
32use vmotherboard::ChipsetDeviceHandle;
33use vtl2_settings_proto::Vtl2Settings;
34
35impl PetriVmConfigOpenVmm {
36 pub fn with_vmbus_redirect(mut self) -> Self {
38 self.config
39 .vmbus
40 .as_mut()
41 .expect("vmbus not configured")
42 .vtl2_redirect = true;
43
44 let Some(ged) = &mut self.ged else {
45 panic!("VMBus redirection is only supported for OpenHCL.")
46 };
47 ged.vmbus_redirection = true;
48
49 self
50 }
51
52 pub fn with_vtl0_alias_map(mut self) -> Self {
55 self.config
56 .hypervisor
57 .with_vtl2
58 .as_mut()
59 .expect("Not an openhcl config.")
60 .vtl0_alias_map = true;
61 self
62 }
63
64 pub fn with_tpm(mut self) -> Self {
66 if self.firmware.is_openhcl() {
67 self.ged.as_mut().unwrap().enable_tpm = true;
68 } else {
69 self.config.chipset_devices.push(ChipsetDeviceHandle {
70 name: "tpm".to_string(),
71 resource: TpmDeviceHandle {
72 ppi_store: EphemeralNonVolatileStoreHandle.into_resource(),
73 nvram_store: EphemeralNonVolatileStoreHandle.into_resource(),
74 refresh_tpm_seeds: false,
75 ak_cert_type: tpm_resources::TpmAkCertTypeResource::None,
76 register_layout: TpmRegisterLayout::IoPort,
77 guest_secret_key: None,
78 logger: None,
79 }
80 .into_resource(),
81 });
82 if let LoadMode::Uefi { enable_tpm, .. } = &mut self.config.load_mode {
83 *enable_tpm = true;
84 }
85 }
86
87 self
88 }
89
90 pub fn with_processor_topology(mut self, topology: ProcessorTopology) -> Self {
95 let ProcessorTopology {
96 vp_count,
97 enable_smt,
98 vps_per_socket,
99 apic_mode,
100 } = topology;
101 self.config.processor_topology.proc_count = vp_count;
102 self.config.processor_topology.enable_smt = enable_smt;
103 self.config.processor_topology.vps_per_socket = vps_per_socket;
104 self.config.processor_topology.arch = Some(match self.arch {
105 MachineArch::X86_64 => hvlite_defs::config::ArchTopologyConfig::X86(
106 hvlite_defs::config::X86TopologyConfig {
107 x2apic: match apic_mode {
108 None => hvlite_defs::config::X2ApicConfig::Auto,
109 Some(x) => match x {
110 crate::ApicMode::Xapic => {
111 hvlite_defs::config::X2ApicConfig::Unsupported
112 }
113 crate::ApicMode::X2apicSupported => {
114 hvlite_defs::config::X2ApicConfig::Supported
115 }
116 crate::ApicMode::X2apicEnabled => {
117 hvlite_defs::config::X2ApicConfig::Enabled
118 }
119 },
120 },
121 ..Default::default()
122 },
123 ),
124 MachineArch::Aarch64 => hvlite_defs::config::ArchTopologyConfig::Aarch64(
125 hvlite_defs::config::Aarch64TopologyConfig::default(),
126 ),
127 });
128 self
129 }
130
131 pub fn with_secure_boot(mut self) -> Self {
133 if !self.firmware.is_uefi() {
134 panic!("Secure boot is only supported for UEFI firmware.");
135 }
136
137 if self.firmware.is_openhcl() {
138 self.ged.as_mut().unwrap().secure_boot_enabled = true;
139 } else {
140 self.config.secure_boot_enabled = true;
141 }
142
143 match self.os_flavor() {
144 OsFlavor::Windows => self.with_windows_secure_boot_template(),
145 OsFlavor::Linux => self.with_uefi_ca_secure_boot_template(),
146 _ => panic!(
147 "Secure boot unsupported for OS flavor {:?}",
148 self.os_flavor()
149 ),
150 }
151 }
152
153 pub fn with_windows_secure_boot_template(mut self) -> Self {
155 if !self.firmware.is_uefi() {
156 panic!("Secure boot is only supported for UEFI firmware.");
157 }
158
159 if self.firmware.is_openhcl() {
160 self.ged.as_mut().unwrap().secure_boot_template =
161 get_resources::ged::GuestSecureBootTemplateType::MicrosoftWindows;
162 } else {
163 self.config.custom_uefi_vars = hyperv_secure_boot_templates::x64::microsoft_windows();
164 }
165
166 self
167 }
168
169 pub fn with_uefi_ca_secure_boot_template(mut self) -> Self {
171 if !self.firmware.is_uefi() {
172 panic!("Secure boot is only supported for UEFI firmware.");
173 }
174
175 if self.firmware.is_openhcl() {
176 self.ged.as_mut().unwrap().secure_boot_template =
177 get_resources::ged::GuestSecureBootTemplateType::MicrosoftUefiCertificateAuthoritiy;
178 } else {
179 self.config.custom_uefi_vars = hyperv_secure_boot_templates::x64::microsoft_uefi_ca();
180 }
181
182 self
183 }
184
185 pub fn with_battery(mut self) -> Self {
187 if self.firmware.is_openhcl() {
188 self.ged.as_mut().unwrap().enable_battery = true;
189 } else {
190 self.config.chipset_devices.push(ChipsetDeviceHandle {
191 name: "battery".to_string(),
192 resource: BatteryDeviceHandleX64 {
193 battery_status_recv: {
194 let (tx, rx) = mesh::channel();
195 tx.send(HostBatteryUpdate::default_present());
196 rx
197 },
198 }
199 .into_resource(),
200 });
201 if let LoadMode::Uefi { enable_battery, .. } = &mut self.config.load_mode {
202 *enable_battery = true;
203 }
204 }
205 self
206 }
207
208 pub fn with_tpm_state_persistence(mut self) -> Self {
210 if !self.firmware.is_openhcl() {
211 panic!("TPM state persistence is only supported for OpenHCL.")
212 };
213
214 let ged = self.ged.as_mut().expect("No GED to configure TPM");
215
216 ged.no_persistent_secrets = false;
219
220 self
221 }
222
223 pub fn with_igvm_attest_test_config(mut self, config: IgvmAttestTestConfig) -> Self {
225 if !self.firmware.is_openhcl() {
226 panic!("IGVM Attest test config is only supported for OpenHCL.")
227 };
228
229 let ged = self.ged.as_mut().expect("No GED to configure TPM");
230
231 ged.igvm_attest_test_config = Some(config);
232
233 self
234 }
235
236 pub fn with_nic(mut self) -> Self {
240 let endpoint =
241 net_backend_resources::consomme::ConsommeHandle { cidr: None }.into_resource();
242 if self.vtl2_settings.is_some() {
243 self.config.vpci_devices.push(VpciDeviceConfig {
244 vtl: DeviceVtl::Vtl2,
245 instance_id: MANA_INSTANCE,
246 resource: GdmaDeviceHandle {
247 vports: vec![VportDefinition {
248 mac_address: NIC_MAC_ADDRESS,
249 endpoint,
250 }],
251 }
252 .into_resource(),
253 });
254
255 self.vtl2_settings
256 .as_mut()
257 .unwrap()
258 .dynamic
259 .as_mut()
260 .unwrap()
261 .nic_devices
262 .push(vtl2_settings_proto::NicDeviceLegacy {
263 instance_id: MANA_INSTANCE.to_string(),
264 subordinate_instance_id: None,
265 max_sub_channels: None,
266 });
267 } else {
268 const NETVSP_INSTANCE: guid::Guid = guid::guid!("c6c46cc3-9302-4344-b206-aef65e5bd0a2");
269 self.config.vmbus_devices.push((
270 DeviceVtl::Vtl0,
271 netvsp_resources::NetvspHandle {
272 instance_id: NETVSP_INSTANCE,
273 mac_address: NIC_MAC_ADDRESS,
274 endpoint,
275 max_queues: None,
276 }
277 .into_resource(),
278 ));
279 }
280
281 self
282 }
283
284 pub fn with_uefi_frontpage(mut self, enable_frontpage: bool) -> Self {
289 match self.config.load_mode {
290 LoadMode::Uefi {
291 ref mut disable_frontpage,
292 ..
293 } => {
294 *disable_frontpage = !enable_frontpage;
295 }
296 LoadMode::Igvm { .. } => {
297 let ged = self.ged.as_mut().expect("no GED to configure DPS");
298 match ged.firmware {
299 get_resources::ged::GuestFirmwareConfig::Uefi {
300 ref mut disable_frontpage,
301 ..
302 } => {
303 *disable_frontpage = !enable_frontpage;
304 }
305 _ => {
306 panic!("not a UEFI boot");
307 }
308 }
309 }
310 _ => panic!("not a UEFI boot"),
311 }
312 self
313 }
314
315 pub fn with_default_boot_always_attempt(mut self, val: bool) -> Self {
317 match self.config.load_mode {
318 LoadMode::Uefi {
319 ref mut default_boot_always_attempt,
320 ..
321 } => {
322 *default_boot_always_attempt = val;
323 }
324 LoadMode::Igvm { .. } => {
325 let ged = self.ged.as_mut().expect("no GED to configure DPS");
326 match ged.firmware {
327 get_resources::ged::GuestFirmwareConfig::Uefi {
328 ref mut default_boot_always_attempt,
329 ..
330 } => {
331 *default_boot_always_attempt = val;
332 }
333 _ => {
334 panic!("not a UEFI boot");
335 }
336 }
337 }
338 _ => panic!("not a UEFI boot"),
339 }
340 self
341 }
342
343 pub fn with_vmgs<T: IsTestVmgs>(mut self, vmgs: PetriVmgsResource<T>) -> Self {
345 let vmgs = match vmgs {
346 PetriVmgsResource::Disk(disk) => {
347 VmgsResource::Disk(memdiff_disk_from_artifact(&disk.erase()).expect("open VMGS"))
348 }
349 PetriVmgsResource::ReprovisionOnFailure(disk) => VmgsResource::ReprovisionOnFailure(
350 memdiff_disk_from_artifact(&disk.erase()).expect("open VMGS"),
351 ),
352 PetriVmgsResource::Reprovision(disk) => VmgsResource::Reprovision(
353 memdiff_disk_from_artifact(&disk.erase()).expect("open VMGS"),
354 ),
355 PetriVmgsResource::Ephemeral => VmgsResource::Ephemeral,
356 };
357
358 if self.firmware.is_openhcl() {
359 self.ged.as_mut().unwrap().vmgs = vmgs;
360 } else {
361 self.config.vmgs = Some(vmgs);
362 }
363 self
364 }
365
366 pub fn with_openhcl_command_line(mut self, additional_cmdline: &str) -> Self {
368 if !self.firmware.is_openhcl() {
369 panic!("Not an OpenHCL firmware.");
370 }
371 let LoadMode::Igvm { cmdline, .. } = &mut self.config.load_mode else {
372 unreachable!()
373 };
374 cmdline.push(' ');
375 cmdline.push_str(additional_cmdline);
376 self
377 }
378
379 pub fn with_confidential_filtering(self) -> Self {
381 if !self.firmware.is_openhcl() {
382 panic!("Confidential filtering is only supported for OpenHCL");
383 }
384 self.with_openhcl_command_line(&format!(
385 "{}=1 {}=0",
386 underhill_confidentiality::OPENHCL_CONFIDENTIAL_ENV_VAR_NAME,
387 underhill_confidentiality::OPENHCL_CONFIDENTIAL_DEBUG_ENV_VAR_NAME
388 ))
389 }
390
391 pub fn with_custom_openhcl(mut self, artifact: ResolvedArtifact) -> Self {
393 let LoadMode::Igvm { file, .. } = &mut self.config.load_mode else {
394 panic!("Custom OpenHCL is only supported for OpenHCL firmware.")
395 };
396 *file = File::open(artifact)
397 .expect("Failed to open custom OpenHCL file")
398 .into();
399 self
400 }
401
402 pub fn with_custom_vtl2_settings(mut self, f: impl FnOnce(&mut Vtl2Settings)) -> Self {
406 f(self
407 .vtl2_settings
408 .as_mut()
409 .expect("Custom VTL 2 settings are only supported with OpenHCL."));
410 self
411 }
412
413 pub fn with_vtl2_relocation_mode(mut self, mode: Vtl2BaseAddressType) -> Self {
415 let LoadMode::Igvm {
416 vtl2_base_address, ..
417 } = &mut self.config.load_mode
418 else {
419 panic!("vtl2 relocation mode is only supported for OpenHCL firmware")
420 };
421 *vtl2_base_address = mode;
422 self
423 }
424
425 pub fn with_custom_config(mut self, f: impl FnOnce(&mut Config)) -> Self {
429 f(&mut self.config);
430 self
431 }
432
433 pub fn with_agent_file(mut self, name: &str, artifact: ResolvedArtifact) -> Self {
435 self.resources.agent_image.add_file(name, artifact);
436 self
437 }
438
439 pub fn with_openhcl_agent_file(mut self, name: &str, artifact: ResolvedArtifact) -> Self {
441 self.resources
442 .openhcl_agent_image
443 .as_mut()
444 .unwrap()
445 .add_file(name, artifact);
446 self
447 }
448
449 pub fn with_allow_early_vtl0_access(mut self, allow: bool) -> Self {
455 self.config
456 .hypervisor
457 .with_vtl2
458 .as_mut()
459 .unwrap()
460 .late_map_vtl0_memory =
461 (!allow).then_some(hvlite_defs::config::LateMapVtl0MemoryPolicy::InjectException);
462
463 self
464 }
465}