microsoft/openvmm

Public

mirrored from https://github.com/microsoft/openvmmAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
8bd6d8fd5fcd4e4efdee54f2c1fd165a93cfb9c3

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

petri/src/vm/construct.rs

993lines · modecode

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Contains [`PetriVmConfig::new`], which builds a [`PetriVmConfig`] with all
5//! default settings for a given [`Firmware`] and [`MachineArch`].
6
7use crate::linux_direct_serial_agent::LinuxDirectSerialAgent;
8use crate::openhcl_diag::OpenHclDiagHandler;
9use crate::tracing::trace_attachment;
10use crate::vm::PetriVmResources;
11use crate::Firmware;
12use crate::PcatGuest;
13use crate::PetriVmConfig;
14use crate::UefiGuest;
15use crate::BOOT_NVME_INSTANCE;
16use crate::BOOT_NVME_LUN;
17use crate::BOOT_NVME_NSID;
18use crate::SCSI_INSTANCE;
19use crate::SIZE_1_GB;
20use anyhow::Context;
21use disk_backend_resources::RamDiffDiskHandle;
22use framebuffer::Framebuffer;
23use framebuffer::FramebufferAccess;
24use framebuffer::FRAMEBUFFER_SIZE;
25use fs_err::File;
26use futures::io::BufReader;
27use futures::AsyncBufReadExt;
28use futures::AsyncRead;
29use futures::AsyncReadExt;
30use get_resources::ged::FirmwareEvent;
31use guid::Guid;
32use hvlite_defs::config::Config;
33use hvlite_defs::config::DeviceVtl;
34use hvlite_defs::config::HypervisorConfig;
35use hvlite_defs::config::LateMapVtl0MemoryPolicy;
36use hvlite_defs::config::LoadMode;
37use hvlite_defs::config::MemoryConfig;
38use hvlite_defs::config::ProcessorTopologyConfig;
39use hvlite_defs::config::SerialInformation;
40use hvlite_defs::config::VmbusConfig;
41use hvlite_defs::config::VpciDeviceConfig;
42use hvlite_defs::config::Vtl2BaseAddressType;
43use hvlite_defs::config::Vtl2Config;
44use hvlite_defs::config::DEFAULT_MMIO_GAPS;
45use hvlite_defs::config::DEFAULT_MMIO_GAPS_WITH_VTL2;
46use hvlite_defs::config::DEFAULT_PCAT_BOOT_ORDER;
47use hvlite_helpers::crash_dump::spawn_dump_handler;
48use hvlite_helpers::disk::open_disk_type;
49use hvlite_pcat_locator::RomFileLocation;
50use hyperv_ic_resources::shutdown::ShutdownIcHandle;
51use ide_resources::GuestMedia;
52use ide_resources::IdeDeviceConfig;
53use nvme_resources::NamespaceDefinition;
54use nvme_resources::NvmeControllerHandle;
55use pal_async::socket::PolledSocket;
56use pal_async::task::Spawn;
57use pal_async::task::Task;
58use pal_async::DefaultDriver;
59use petri_artifacts_common::artifacts as common_artifacts;
60use petri_artifacts_common::tags::MachineArch;
61use petri_artifacts_core::AsArtifactHandle;
62use petri_artifacts_core::TestArtifacts;
63use petri_artifacts_vmm_test::artifacts as hvlite_artifacts;
64use pipette_client::PIPETTE_VSOCK_PORT;
65use scsidisk_resources::SimpleScsiDiskHandle;
66use scsidisk_resources::SimpleScsiDvdHandle;
67use serial_16550_resources::ComPort;
68use serial_core::resources::DisconnectedSerialBackendHandle;
69use serial_socket::unix::OpenUnixStreamSerialConfig;
70use sparse_mmap::alloc_shared_memory;
71use std::fmt::Write as _;
72use std::io::Write;
73use std::path::PathBuf;
74use std::sync::Arc;
75use storvsp_resources::ScsiControllerHandle;
76use storvsp_resources::ScsiDeviceAndPath;
77use storvsp_resources::ScsiPath;
78use uidevices_resources::SynthVideoHandle;
79use unix_socket::UnixListener;
80use unix_socket::UnixStream;
81use video_core::SharedFramebufferHandle;
82use vm_manifest_builder::VmManifestBuilder;
83use vm_resource::kind::SerialBackendHandle;
84use vm_resource::kind::VmbusDeviceHandleKind;
85use vm_resource::IntoResource;
86use vm_resource::Resource;
87use vmbus_serial_resources::VmbusSerialDeviceHandle;
88use vmbus_serial_resources::VmbusSerialPort;
89use vtl2_settings_proto::Vtl2Settings;
90
91impl PetriVmConfig {
92 /// Create a new VM configuration.
93 pub fn new(
94 firmware: Firmware,
95 arch: MachineArch,
96 resolver: TestArtifacts,
97 driver: &DefaultDriver,
98 ) -> anyhow::Result<Self> {
99 // Use the current thread name for the test name, both cargo-test and
100 // cargo-nextest set this.
101 // FUTURE: If we ever want to use petri outside a testing context this
102 // will need to be revisited.
103 let current_thread = std::thread::current();
104 let test_name = current_thread.name().context("no thread name configured")?;
105 if test_name.is_empty() {
106 anyhow::bail!("thread name is empty");
107 }
108 if test_name == "main" {
109 anyhow::bail!("thread name is 'main', not running from test thread");
110 }
111 // Windows paths can't include colons, replace them.
112 let test_name = test_name.replace("::", "__");
113 let setup = PetriVmConfigSetupCore {
114 test_name: &test_name,
115 arch,
116 firmware: &firmware,
117 resolver: &resolver,
118 driver,
119 };
120
121 let TestLogFiles {
122 output_dir,
123 hvlite_file,
124 guest_file,
125 petri_file,
126 openhcl_file,
127 } = setup
128 .create_log_files()
129 .context("failed to create test log files")?;
130
131 crate::tracing::try_init_tracing(petri_file.into())?;
132
133 let mut chipset = VmManifestBuilder::new(
134 match firmware {
135 Firmware::LinuxDirect {} => {
136 vm_manifest_builder::BaseChipsetType::HyperVGen2LinuxDirect
137 }
138 Firmware::OpenhclLinuxDirect { .. } => {
139 vm_manifest_builder::BaseChipsetType::HclHost
140 }
141 Firmware::OpenhclUefi { .. } => vm_manifest_builder::BaseChipsetType::HclHost,
142 Firmware::Pcat { .. } => vm_manifest_builder::BaseChipsetType::HypervGen1,
143 Firmware::Uefi { .. } => vm_manifest_builder::BaseChipsetType::HypervGen2Uefi,
144 },
145 match arch {
146 MachineArch::X86_64 => vm_manifest_builder::MachineArch::X86_64,
147 MachineArch::Aarch64 => vm_manifest_builder::MachineArch::Aarch64,
148 },
149 );
150
151 let load_mode = setup.load_firmware()?;
152
153 let SerialData {
154 mut emulated_serial_config,
155 serial_tasks,
156 linux_direct_serial_agent,
157 } = setup.configure_serial(guest_file, openhcl_file)?;
158
159 let (video_dev, framebuffer, framebuffer_access) = match setup.config_video()? {
160 Some((v, fb, fba)) => {
161 chipset = chipset.with_framebuffer();
162 (Some(v), Some(fb), Some(fba))
163 }
164 None => (None, None, None),
165 };
166
167 let mut devices = Vec::new();
168
169 let (firmware_event_send, firmware_event_recv) = mesh::mpsc_channel();
170
171 let (with_vtl2, vtl2_vmbus, openhcl_diag_handler, ged, ged_send, mut vtl2_settings) =
172 if firmware.is_openhcl() {
173 let (ged, ged_send) = setup.config_openhcl_vmbus_devices(
174 &mut emulated_serial_config,
175 &mut devices,
176 &firmware_event_send,
177 framebuffer.is_some(),
178 )?;
179 let (vtl2_vsock_listener, vtl2_vsock_path) =
180 tempfile_helpers::with_temp_path(|path| UnixListener::bind(path))?;
181 let ged_send = Arc::new(ged_send);
182 (
183 Some(Vtl2Config {
184 vtl0_alias_map: false, // TODO: enable when OpenVMM supports it for DMA
185 late_map_vtl0_memory: Some(LateMapVtl0MemoryPolicy::InjectException),
186 vtl2_emulates_apic: false,
187 }),
188 Some(VmbusConfig {
189 vsock_listener: Some(vtl2_vsock_listener),
190 vsock_path: Some(vtl2_vsock_path.to_string_lossy().into_owned()),
191 vmbus_max_version: None,
192 vtl2_redirect: false,
193 #[cfg(windows)]
194 vmbusproxy_handle: None,
195 }),
196 Some(OpenHclDiagHandler {
197 ged_send: ged_send.clone(),
198 vtl2_vsock_path,
199 }),
200 Some(ged),
201 Some(ged_send),
202 // Basic sane default
203 Some(Vtl2Settings {
204 version: vtl2_settings_proto::vtl2_settings_base::Version::V1.into(),
205 dynamic: Some(Default::default()),
206 fixed: Some(Default::default()),
207 namespace_settings: Default::default(),
208 }),
209 )
210 } else {
211 (None, None, None, None, None, None)
212 };
213
214 setup.load_boot_disk(&mut devices, vtl2_settings.as_mut())?;
215 let expected_boot_event = setup.get_expected_boot_event();
216
217 // Configure the serial ports now that they have been updated by the
218 // OpenHCL configuration.
219 chipset = chipset.with_serial(emulated_serial_config);
220 // Set so that we don't pull serial data until the guest is
221 // ready. Otherwise, Linux will drop the input serial data
222 // on the floor during boot.
223 if matches!(firmware, Firmware::LinuxDirect { .. }) {
224 chipset = chipset.with_serial_wait_for_rts();
225 }
226
227 // Partition the devices by type.
228 let mut vmbus_devices = Vec::new();
229 let mut ide_disks = Vec::new();
230 let floppy_disks = Vec::new();
231 let mut vpci_devices = Vec::new();
232 for d in devices {
233 match d {
234 Device::Vmbus(vtl, resource) => vmbus_devices.push((vtl, resource)),
235 Device::Vpci(c) => vpci_devices.push(c),
236 Device::Ide(c) => ide_disks.push(c),
237 }
238 }
239
240 // Extract video configuration
241 let vga_firmware = match video_dev {
242 Some(VideoDevice::Vga(firmware)) => Some(firmware),
243 Some(VideoDevice::Synth(vtl, resource)) => {
244 vmbus_devices.push((vtl, resource));
245 None
246 }
247 None => None,
248 };
249
250 // Add the Hyper-V Shutdown IC
251 let (shutdown_ic_send, shutdown_ic_recv) = mesh::channel();
252 vmbus_devices.push((
253 DeviceVtl::Vtl0,
254 ShutdownIcHandle {
255 recv: shutdown_ic_recv,
256 }
257 .into_resource(),
258 ));
259
260 // Make a vmbus vsock path for pipette connections
261 let (vmbus_vsock_listener, vmbus_vsock_path) =
262 tempfile_helpers::with_temp_path(|path| UnixListener::bind(path))?;
263
264 let chipset = chipset
265 .build()
266 .context("failed to build chipset configuration")?;
267
268 let config = Config {
269 // Firmware
270 load_mode,
271 firmware_event_send: Some(firmware_event_send),
272
273 // CPU and RAM
274 memory: MemoryConfig {
275 mem_size: if firmware.is_openhcl() {
276 4 * SIZE_1_GB
277 } else {
278 SIZE_1_GB
279 },
280 mmio_gaps: if firmware.is_openhcl() {
281 DEFAULT_MMIO_GAPS_WITH_VTL2.into()
282 } else {
283 DEFAULT_MMIO_GAPS.into()
284 },
285 prefetch_memory: false,
286 },
287 processor_topology: ProcessorTopologyConfig {
288 proc_count: 2,
289 vps_per_socket: None,
290 enable_smt: None,
291 arch: Default::default(),
292 },
293
294 // Base chipset
295 chipset: chipset.chipset,
296 chipset_devices: chipset.chipset_devices,
297
298 // Basic virtualization device support
299 hypervisor: HypervisorConfig {
300 with_hv: true,
301 user_mode_hv_enlightenments: false,
302 user_mode_apic: false,
303 with_vtl2,
304 with_isolation: firmware.isolation(),
305 },
306 vmbus: Some(VmbusConfig {
307 vsock_listener: Some(vmbus_vsock_listener),
308 vsock_path: Some(vmbus_vsock_path.to_string_lossy().into_owned()),
309 vmbus_max_version: None,
310 vtl2_redirect: false,
311 #[cfg(windows)]
312 vmbusproxy_handle: None,
313 }),
314 vtl2_vmbus,
315
316 // Devices
317 floppy_disks,
318 ide_disks,
319 vpci_devices,
320 vmbus_devices,
321
322 // Video support
323 framebuffer,
324 vga_firmware,
325
326 // Reasonable defaults
327 custom_uefi_vars: Default::default(),
328
329 // Disabled for VMM tests by default
330 #[cfg(windows)]
331 kernel_vmnics: vec![],
332 input: mesh::MpscReceiver::new(),
333 vtl2_gfx: false,
334 virtio_console_pci: false,
335 virtio_serial: None,
336 virtio_devices: vec![],
337 #[cfg(windows)]
338 vpci_resources: vec![],
339 vmgs_file: None,
340 secure_boot_enabled: false,
341 debugger_rpc: None,
342 generation_id_recv: None,
343 };
344
345 // Make the pipette connection listener.
346 let path = config.vmbus.as_ref().unwrap().vsock_path.as_ref().unwrap();
347 let path = format!("{path}_{PIPETTE_VSOCK_PORT}");
348 let pipette_listener = PolledSocket::new(
349 driver,
350 UnixListener::bind(path).context("failed to bind to pipette listener")?,
351 )?;
352
353 // Make the vtl2 pipette connection listener.
354 let vtl2_pipette_listener = if let Some(vtl2_vmbus) = &config.vtl2_vmbus {
355 let path = vtl2_vmbus.vsock_path.as_ref().unwrap();
356 let path = format!("{path}_{PIPETTE_VSOCK_PORT}");
357 Some(PolledSocket::new(
358 driver,
359 UnixListener::bind(path).context("failed to bind to vtl2 pipette listener")?,
360 )?)
361 } else {
362 None
363 };
364
365 Ok(Self {
366 firmware,
367 arch,
368 config,
369
370 resources: PetriVmResources {
371 serial_tasks,
372 firmware_event_recv,
373 shutdown_ic_send,
374 expected_boot_event,
375 ged_send,
376 pipette_listener,
377 vtl2_pipette_listener,
378 openhcl_diag_handler,
379 linux_direct_serial_agent,
380 driver: driver.clone(),
381 resolver,
382 output_dir,
383 },
384
385 hvlite_log_file: hvlite_file,
386
387 ged,
388 vtl2_settings,
389 framebuffer_access,
390 })
391 }
392}
393
394struct PetriVmConfigSetupCore<'a> {
395 test_name: &'a str,
396 arch: MachineArch,
397 firmware: &'a Firmware,
398 resolver: &'a TestArtifacts,
399 driver: &'a DefaultDriver,
400}
401
402struct SerialData {
403 emulated_serial_config: [Option<Resource<SerialBackendHandle>>; 4],
404 serial_tasks: Vec<Task<anyhow::Result<()>>>,
405 linux_direct_serial_agent: Option<LinuxDirectSerialAgent>,
406}
407
408struct TestLogFiles {
409 output_dir: PathBuf,
410 hvlite_file: File,
411 guest_file: File,
412 petri_file: File,
413 openhcl_file: Option<File>,
414}
415
416enum LogTarget {
417 Linux,
418 Uefi,
419 Pcat,
420 Openhcl,
421}
422
423enum Device {
424 Vmbus(DeviceVtl, Resource<VmbusDeviceHandleKind>),
425 Vpci(VpciDeviceConfig),
426 Ide(IdeDeviceConfig),
427}
428
429enum VideoDevice {
430 Vga(RomFileLocation),
431 Synth(DeviceVtl, Resource<VmbusDeviceHandleKind>),
432}
433
434impl PetriVmConfigSetupCore<'_> {
435 fn create_log_files(&self) -> anyhow::Result<TestLogFiles> {
436 // DEVNOTE: This function runs before tracing is set up.
437
438 let test_log_dir = self.resolver.resolve(common_artifacts::TEST_LOG_DIRECTORY);
439 let output_dir = test_log_dir.join(self.test_name);
440 if output_dir.exists() {
441 std::fs::remove_dir_all(&output_dir)?;
442 }
443 std::fs::create_dir_all(&output_dir)?;
444
445 // NOTE: Due to a WSL + Windows defender bug, .txt extensions take forever to create within WSL
446 // when cross compiling. Name them .log which works around it.
447 let hvlite_file = File::create(output_dir.join("hvlite.log"))?;
448 let guest_file = File::create(output_dir.join("guest.log"))?;
449 let petri_file = File::create(output_dir.join("petri.log"))?;
450 let openhcl_file = if self.firmware.is_openhcl() {
451 Some(File::create(output_dir.join("openhcl.log"))?)
452 } else {
453 None
454 };
455
456 for attachment in [&hvlite_file, &guest_file, &petri_file]
457 .into_iter()
458 .chain(openhcl_file.as_ref())
459 {
460 trace_attachment(attachment.path());
461 }
462
463 Ok(TestLogFiles {
464 output_dir,
465 hvlite_file,
466 guest_file,
467 petri_file,
468 openhcl_file,
469 })
470 }
471
472 fn configure_serial(
473 &self,
474 guest_file: File,
475 openhcl_file: Option<File>,
476 ) -> anyhow::Result<SerialData> {
477 let mut serial_tasks = Vec::new();
478
479 let serial0_log_target = match self.firmware {
480 Firmware::LinuxDirect { .. } | Firmware::OpenhclLinuxDirect { .. } => LogTarget::Linux,
481 Firmware::Pcat { .. } => LogTarget::Pcat,
482 Firmware::Uefi { .. } | Firmware::OpenhclUefi { .. } => LogTarget::Uefi,
483 };
484
485 let (serial0_host, serial0) = self
486 .create_serial_stream()
487 .context("failed to create serial0 stream")?;
488 let (serial0_read, serial0_write) = serial0_host.split();
489 let serial0_task = self
490 .spawn_serial_task(
491 "serial0-console",
492 serial0_log_target,
493 serial0_read,
494 guest_file,
495 )
496 .context("failed to spawn serial0 task")?;
497 serial_tasks.push(serial0_task);
498
499 let serial2 = if self.firmware.is_openhcl() {
500 let (serial2_host, serial2) = self
501 .create_serial_stream()
502 .context("failed to create serial2 stream")?;
503 let serial2_task = self
504 .spawn_serial_task(
505 "serial2-openhcl",
506 LogTarget::Openhcl,
507 serial2_host,
508 openhcl_file.unwrap(),
509 )
510 .context("failed to spawn serial2 task")?;
511 serial_tasks.push(serial2_task);
512 serial2
513 } else {
514 None
515 };
516
517 if self.firmware.is_linux_direct() {
518 let (serial1_host, serial1) = self.create_serial_stream()?;
519 let (serial1_read, _serial1_write) = serial1_host.split();
520 let linux_direct_serial_agent =
521 LinuxDirectSerialAgent::new(serial1_read, serial0_write);
522 Ok(SerialData {
523 emulated_serial_config: [serial0, serial1, serial2, None],
524 serial_tasks,
525 linux_direct_serial_agent: Some(linux_direct_serial_agent),
526 })
527 } else {
528 Ok(SerialData {
529 emulated_serial_config: [serial0, None, serial2, None],
530 serial_tasks,
531 linux_direct_serial_agent: None,
532 })
533 }
534 }
535
536 fn create_serial_stream(
537 &self,
538 ) -> anyhow::Result<(
539 PolledSocket<UnixStream>,
540 Option<Resource<SerialBackendHandle>>,
541 )> {
542 let (host_side, guest_side) = UnixStream::pair()?;
543 let host_side = PolledSocket::new(self.driver, host_side)?;
544 let serial = OpenUnixStreamSerialConfig::from(guest_side).into_resource();
545 Ok((host_side, Some(serial)))
546 }
547
548 fn spawn_serial_task(
549 &self,
550 task_name: &str,
551 log_target: LogTarget,
552 reader: impl AsyncRead + Unpin + Send + 'static,
553 mut file: File,
554 ) -> anyhow::Result<Task<anyhow::Result<()>>> {
555 Ok(self.driver.spawn(task_name, async move {
556 let mut buf = Vec::new();
557 let mut reader = BufReader::new(reader);
558 loop {
559 buf.clear();
560 let n = (&mut reader).take(256).read_until(b'\n', &mut buf).await?;
561 if n == 0 {
562 break;
563 }
564
565 let string_buf = String::from_utf8_lossy(&buf);
566 let string_buf_trimmed = string_buf.trim_end();
567 // tracing's target needs to be a const, can't just pass in a string
568 match log_target {
569 LogTarget::Linux => {
570 tracing::info!(target: crate::tracing::LINUX_TARGET, "{}", string_buf_trimmed)
571 }
572 LogTarget::Uefi => {
573 tracing::info!(target: crate::tracing::UEFI_TARGET, "{}", string_buf_trimmed)
574 }
575 LogTarget::Pcat => {
576 tracing::info!(target: crate::tracing::PCAT_TARGET, "{}", string_buf_trimmed)
577 }
578 LogTarget::Openhcl => {
579 tracing::info!(target: crate::tracing::OPENHCL_TARGET, "{}", string_buf_trimmed)
580 }
581 }
582
583 file.write_all(&buf)?;
584 }
585 Ok(())
586 }))
587 }
588
589 fn load_firmware(&self) -> anyhow::Result<LoadMode> {
590 // Forward OPENVMM_LOG to OpenHCL if it's set.
591 let openhcl_tracing =
592 if let Ok(x) = std::env::var("OPENVMM_LOG").or_else(|_| std::env::var("HVLITE_LOG")) {
593 format!("OPENVMM_LOG={x}")
594 } else {
595 "OPENVMM_LOG=debug".to_owned()
596 };
597
598 Ok(match (self.arch, &self.firmware) {
599 (MachineArch::X86_64, Firmware::LinuxDirect { .. }) => {
600 let kernel = File::open(
601 self.resolver
602 .resolve(hvlite_artifacts::loadable::LINUX_DIRECT_TEST_KERNEL_X64),
603 )
604 .context("Failed to open kernel")?
605 .into();
606 let initrd = File::open(
607 self.resolver
608 .resolve(hvlite_artifacts::loadable::LINUX_DIRECT_TEST_INITRD_X64),
609 )
610 .context("Failed to open initrd")?
611 .into();
612 LoadMode::Linux {
613 kernel,
614 initrd: Some(initrd),
615 cmdline: "console=ttyS0 debug panic=-1 rdinit=/bin/sh".into(),
616 custom_dsdt: None,
617 enable_serial: true,
618 }
619 }
620 (MachineArch::Aarch64, Firmware::LinuxDirect { .. }) => {
621 let kernel = File::open(
622 self.resolver
623 .resolve(hvlite_artifacts::loadable::LINUX_DIRECT_TEST_KERNEL_AARCH64),
624 )
625 .context("Failed to open kernel")?
626 .into();
627 let initrd = File::open(
628 self.resolver
629 .resolve(hvlite_artifacts::loadable::LINUX_DIRECT_TEST_INITRD_AARCH64),
630 )
631 .context("Failed to open initrd")?
632 .into();
633 LoadMode::Linux {
634 kernel,
635 initrd: Some(initrd),
636 cmdline: "console=ttyAMA0 earlycon debug panic=-1 rdinit=/bin/sh".into(),
637 custom_dsdt: None,
638 enable_serial: true,
639 }
640 }
641 (MachineArch::X86_64, Firmware::Pcat { .. }) => {
642 let firmware = hvlite_pcat_locator::find_pcat_bios(
643 self.resolver
644 .try_resolve(hvlite_artifacts::loadable::PCAT_FIRMWARE_X64)
645 .as_deref(),
646 )
647 .context("Failed to load packaged PCAT binary")?;
648 LoadMode::Pcat {
649 firmware,
650 boot_order: DEFAULT_PCAT_BOOT_ORDER,
651 }
652 }
653 (_, Firmware::Uefi { .. }) => {
654 let firmware = File::open(self.resolver.resolve(match self.arch {
655 MachineArch::X86_64 => hvlite_artifacts::loadable::UEFI_FIRMWARE_X64.erase(),
656 MachineArch::Aarch64 => {
657 hvlite_artifacts::loadable::UEFI_FIRMWARE_AARCH64.erase()
658 }
659 }))
660 .context("Failed to open uefi firmware file")?
661 .into();
662 LoadMode::Uefi {
663 firmware,
664 enable_debugging: false,
665 enable_memory_protections: false,
666 disable_frontpage: true,
667 enable_tpm: false,
668 enable_battery: false,
669 enable_serial: true,
670 enable_vpci_boot: false,
671 uefi_console_mode: Some(hvlite_defs::config::UefiConsoleMode::Com1),
672 }
673 }
674 (
675 MachineArch::X86_64,
676 Firmware::OpenhclLinuxDirect { .. } | Firmware::OpenhclUefi { .. },
677 ) => {
678 let mut cmdline = format!("panic=-1 reboot=triple {openhcl_tracing}");
679
680 let (igvm_artifact, isolated) = match self.firmware {
681 Firmware::OpenhclLinuxDirect { .. } => {
682 // Set UNDERHILL_SERIAL_WAIT_FOR_RTS=1 so that we don't pull serial data
683 // until the guest is ready. Otherwise, Linux will drop the input serial
684 // data on the floor during boot.
685 write!(cmdline, " UNDERHILL_SERIAL_WAIT_FOR_RTS=1 UNDERHILL_CMDLINE_APPEND=\"rdinit=/bin/sh\"").unwrap();
686 (
687 hvlite_artifacts::openhcl_igvm::LATEST_LINUX_DIRECT_TEST_X64.erase(),
688 false,
689 )
690 }
691 Firmware::OpenhclUefi { isolation, .. } if isolation.is_some() => {
692 (hvlite_artifacts::openhcl_igvm::LATEST_CVM_X64.erase(), true)
693 }
694 _ => (
695 hvlite_artifacts::openhcl_igvm::LATEST_STANDARD_X64.erase(),
696 false,
697 ),
698 };
699 let path = self.resolver.resolve(igvm_artifact);
700 let file = File::open(path)
701 .context("failed to open openhcl firmware file")?
702 .into();
703 LoadMode::Igvm {
704 file,
705 cmdline,
706 vtl2_base_address: if isolated {
707 // Isolated VMs must load at the location specified by
708 // the file, as they do not support relocation.
709 Vtl2BaseAddressType::File
710 } else {
711 // By default, utilize IGVM relocation and tell hvlite
712 // to place VTL2 at 2GB. This tests both relocation
713 // support in hvlite, and relocation support within
714 // underhill.
715 Vtl2BaseAddressType::Absolute(2 * SIZE_1_GB)
716 },
717 com_serial: Some(SerialInformation {
718 io_port: ComPort::Com3.io_port(),
719 irq: ComPort::Com3.irq().into(),
720 }),
721 }
722 }
723 (a, f) => anyhow::bail!("Unsupported firmware {f:?} for arch {a:?}"),
724 })
725 }
726
727 fn load_boot_disk(
728 &self,
729 devices: &mut impl Extend<Device>,
730 vtl2_settings: Option<&mut Vtl2Settings>,
731 ) -> anyhow::Result<()> {
732 match &self.firmware {
733 Firmware::LinuxDirect { .. } | Firmware::OpenhclLinuxDirect { .. } => {
734 // Nothing to do, everything is contained in LoadMode
735 }
736 Firmware::Uefi {
737 guest: UefiGuest::None,
738 }
739 | Firmware::OpenhclUefi {
740 guest: UefiGuest::None,
741 ..
742 } => {
743 // Nothing to do, no guest
744 }
745 Firmware::Pcat { guest } => {
746 let path = self.resolver.resolve(guest.artifact());
747 let inner_disk = open_disk_type(&path, true)?;
748 let guest_media = match guest {
749 PcatGuest::Vhd(_) => GuestMedia::Disk {
750 read_only: false,
751 disk_parameters: None,
752 disk_type: (RamDiffDiskHandle { lower: inner_disk }).into_resource(),
753 },
754 PcatGuest::Iso(_) => GuestMedia::Dvd(
755 SimpleScsiDvdHandle {
756 media: Some(inner_disk),
757 requests: None,
758 }
759 .into_resource(),
760 ),
761 };
762 devices.extend([Device::Ide(IdeDeviceConfig {
763 path: ide_resources::IdePath {
764 channel: 0,
765 drive: 0,
766 },
767 guest_media,
768 })]);
769 }
770 Firmware::Uefi { guest }
771 | Firmware::OpenhclUefi {
772 guest,
773 vtl2_nvme_boot: false,
774 ..
775 } => {
776 let path = self.resolver.resolve(guest.artifact());
777 devices.extend([Device::Vmbus(
778 DeviceVtl::Vtl0,
779 ScsiControllerHandle {
780 instance_id: SCSI_INSTANCE,
781 max_sub_channel_count: 1,
782 io_queue_depth: None,
783 devices: vec![ScsiDeviceAndPath {
784 path: ScsiPath {
785 path: 0,
786 target: 0,
787 lun: 0,
788 },
789 device: SimpleScsiDiskHandle {
790 read_only: false,
791 parameters: Default::default(),
792 disk: RamDiffDiskHandle {
793 lower: open_disk_type(&path, true)?,
794 }
795 .into_resource(),
796 }
797 .into_resource(),
798 }],
799 requests: None,
800 }
801 .into_resource(),
802 )]);
803 }
804 Firmware::OpenhclUefi {
805 guest,
806 vtl2_nvme_boot: true,
807 ..
808 } => {
809 let path = self.resolver.resolve(guest.artifact());
810 devices.extend([Device::Vpci(VpciDeviceConfig {
811 vtl: DeviceVtl::Vtl2,
812 instance_id: BOOT_NVME_INSTANCE,
813 resource: NvmeControllerHandle {
814 subsystem_id: BOOT_NVME_INSTANCE,
815 max_io_queues: 64,
816 msix_count: 64,
817 namespaces: vec![NamespaceDefinition {
818 nsid: BOOT_NVME_NSID,
819 disk: RamDiffDiskHandle {
820 lower: open_disk_type(&path, true)?,
821 }
822 .into_resource(),
823 read_only: false,
824 }],
825 }
826 .into_resource(),
827 })]);
828 vtl2_settings
829 .expect("openhcl config should have vtl2settings")
830 .dynamic
831 .as_mut()
832 .unwrap()
833 .storage_controllers
834 .push(vtl2_settings_proto::StorageController {
835 instance_id: SCSI_INSTANCE.to_string(),
836 protocol: vtl2_settings_proto::storage_controller::StorageProtocol::Scsi
837 .into(),
838 luns: vec![vtl2_settings_proto::Lun {
839 location: BOOT_NVME_LUN,
840 device_id: Guid::new_random().to_string(),
841 vendor_id: "HvLite".to_string(),
842 product_id: "Disk".to_string(),
843 product_revision_level: "1.0".to_string(),
844 serial_number: "0".to_string(),
845 model_number: "1".to_string(),
846 physical_devices: Some(vtl2_settings_proto::PhysicalDevices {
847 r#type: vtl2_settings_proto::physical_devices::BackingType::Single
848 .into(),
849 device: Some(vtl2_settings_proto::PhysicalDevice {
850 device_type:
851 vtl2_settings_proto::physical_device::DeviceType::Nvme
852 .into(),
853 device_path: BOOT_NVME_INSTANCE.to_string(),
854 sub_device_path: BOOT_NVME_NSID,
855 }),
856 devices: Vec::new(),
857 }),
858 ..Default::default()
859 }],
860 io_queue_depth: None,
861 });
862 }
863 }
864
865 Ok(())
866 }
867
868 fn config_openhcl_vmbus_devices(
869 &self,
870 serial: &mut [Option<Resource<SerialBackendHandle>>],
871 devices: &mut impl Extend<Device>,
872 firmware_event_send: &mesh::MpscSender<FirmwareEvent>,
873 framebuffer: bool,
874 ) -> anyhow::Result<(
875 get_resources::ged::GuestEmulationDeviceHandle,
876 mesh::Sender<get_resources::ged::GuestEmulationRequest>,
877 )> {
878 let serial0 = serial[0].take();
879 devices.extend([Device::Vmbus(
880 DeviceVtl::Vtl2,
881 VmbusSerialDeviceHandle {
882 port: VmbusSerialPort::Com1,
883 backend: serial0.unwrap_or_else(|| DisconnectedSerialBackendHandle.into_resource()),
884 }
885 .into_resource(),
886 )]);
887 let serial1 = serial[1].take();
888 devices.extend([Device::Vmbus(
889 DeviceVtl::Vtl2,
890 VmbusSerialDeviceHandle {
891 port: VmbusSerialPort::Com2,
892 backend: serial1.unwrap_or_else(|| DisconnectedSerialBackendHandle.into_resource()),
893 }
894 .into_resource(),
895 )]);
896
897 let gel = get_resources::gel::GuestEmulationLogHandle.into_resource();
898
899 let (crash, task) = spawn_dump_handler(
900 self.driver,
901 self.resolver
902 .resolve(hvlite_artifacts::OPENHCL_DUMP_DIRECTORY),
903 None,
904 );
905 task.detach();
906
907 devices.extend([
908 Device::Vmbus(DeviceVtl::Vtl2, crash),
909 Device::Vmbus(DeviceVtl::Vtl2, gel),
910 ]);
911
912 let (guest_request_send, guest_request_recv) = mesh::channel();
913
914 // Save the GED handle to add later after configuration is complete.
915 let ged = get_resources::ged::GuestEmulationDeviceHandle {
916 firmware: get_resources::ged::GuestFirmwareConfig::Uefi {
917 firmware_debug: false,
918 disable_frontpage: true,
919 enable_vpci_boot: false,
920 console_mode: get_resources::ged::UefiConsoleMode::COM1,
921 },
922 com1: true,
923 com2: true,
924 vmbus_redirection: false,
925 vtl2_settings: None, // Will be added at startup to allow tests to modify
926 framebuffer: framebuffer.then(|| SharedFramebufferHandle.into_resource()),
927 guest_request_recv,
928 enable_tpm: false,
929 firmware_event_send: Some(firmware_event_send.clone()),
930 secure_boot_enabled: false,
931 secure_boot_template: get_resources::ged::GuestSecureBootTemplateType::None,
932 enable_battery: false,
933 };
934
935 Ok((ged, guest_request_send))
936 }
937
938 fn config_video(
939 &self,
940 ) -> anyhow::Result<Option<(VideoDevice, Framebuffer, FramebufferAccess)>> {
941 if self.firmware.isolation().is_some() {
942 return Ok(None);
943 }
944
945 let video_dev = match self.firmware {
946 Firmware::Pcat { .. } => Some(VideoDevice::Vga(
947 hvlite_pcat_locator::find_svga_bios(
948 self.resolver
949 .try_resolve(hvlite_artifacts::loadable::SVGA_FIRMWARE_X64)
950 .as_deref(),
951 )
952 .context("Failed to load VGA BIOS")?,
953 )),
954 Firmware::Uefi { .. } | Firmware::OpenhclUefi { .. } => Some(VideoDevice::Synth(
955 DeviceVtl::Vtl0,
956 SynthVideoHandle {
957 framebuffer: SharedFramebufferHandle.into_resource(),
958 }
959 .into_resource(),
960 )),
961 Firmware::OpenhclLinuxDirect { .. } | Firmware::LinuxDirect { .. } => None,
962 };
963
964 Ok(if let Some(vdev) = video_dev {
965 let vram = alloc_shared_memory(FRAMEBUFFER_SIZE).context("allocating framebuffer")?;
966 let (fb, fba) = framebuffer::framebuffer(vram, FRAMEBUFFER_SIZE, 0)
967 .context("creating framebuffer")?;
968 Some((vdev, fb, fba))
969 } else {
970 None
971 })
972 }
973
974 fn get_expected_boot_event(&self) -> Option<FirmwareEvent> {
975 match &self.firmware {
976 Firmware::LinuxDirect { .. } | Firmware::OpenhclLinuxDirect { .. } => None,
977 Firmware::Pcat { .. } => {
978 // TODO: Handle older PCAT versions that don't fire the event
979 Some(FirmwareEvent::BootAttempt)
980 }
981 Firmware::Uefi {
982 guest: UefiGuest::None,
983 }
984 | Firmware::OpenhclUefi {
985 guest: UefiGuest::None,
986 ..
987 } => Some(FirmwareEvent::NoBootDevice),
988 Firmware::Uefi { .. } | Firmware::OpenhclUefi { .. } => {
989 Some(FirmwareEvent::BootSuccess)
990 }
991 }
992 }
993}
994