petri/vm/openvmm/
start.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Methods to start a [`PetriVmConfigOpenVmm`] and produce a running [`PetriVmOpenVmm`].
5
6use super::PetriVmConfigOpenVmm;
7use super::PetriVmOpenVmm;
8use super::PetriVmResourcesOpenVmm;
9use crate::OpenvmmLogConfig;
10use crate::PetriLogFile;
11use crate::PetriVmRuntimeConfig;
12use crate::worker::Worker;
13use anyhow::Context;
14use mesh_process::Mesh;
15use mesh_process::ProcessConfig;
16use mesh_worker::WorkerHost;
17use openvmm_defs::config::DeviceVtl;
18use pal_async::pipe::PolledPipe;
19use pal_async::task::Spawn;
20use petri_artifacts_common::tags::MachineArch;
21use petri_artifacts_common::tags::OsFlavor;
22use std::collections::BTreeMap;
23use std::ffi::OsString;
24use std::io::Write;
25use std::sync::Arc;
26use vm_resource::IntoResource;
27
28impl PetriVmConfigOpenVmm {
29    async fn run_core(self) -> anyhow::Result<(PetriVmOpenVmm, PetriVmRuntimeConfig)> {
30        let Self {
31            runtime_config,
32            arch,
33            host_log_levels,
34            mut config,
35
36            mesh,
37
38            mut resources,
39
40            openvmm_log_file,
41
42            ged,
43            framebuffer_view,
44        } = self;
45
46        let has_pcie = !config.pcie_root_complexes.is_empty();
47
48        // TODO: OpenHCL needs virt_whp support
49        // TODO: PCAT needs vga device support
50        // TODO: arm64 is broken?
51        // TODO: VPCI and NVMe don't support save/restore
52        // TODO: PCIe emulators don't support save/restore yet
53        let supports_save_restore = !resources.properties.is_openhcl
54            && !resources.properties.is_pcat
55            && !matches!(arch, MachineArch::Aarch64)
56            && !resources.properties.using_vpci
57            && !has_pcie;
58
59        // Add the GED and VTL 2 settings.
60        if let Some(mut ged) = ged {
61            ged.vtl2_settings = Some(prost::Message::encode_to_vec(
62                runtime_config.vtl2_settings.as_ref().unwrap(),
63            ));
64            config
65                .vmbus_devices
66                .push((DeviceVtl::Vtl2, ged.into_resource()));
67        }
68
69        tracing::debug!(?config, "OpenVMM config");
70
71        let log_env = match host_log_levels {
72            None | Some(OpenvmmLogConfig::TestDefault) => BTreeMap::<OsString, OsString>::from([
73                ("OPENVMM_LOG".into(), "debug".into()),
74                ("OPENVMM_SHOW_SPANS".into(), "true".into()),
75            ]),
76            Some(OpenvmmLogConfig::BuiltInDefault) => BTreeMap::new(),
77            Some(OpenvmmLogConfig::Custom(levels)) => levels
78                .iter()
79                .map(|(k, v)| (OsString::from(k), OsString::from(v)))
80                .collect::<BTreeMap<OsString, OsString>>(),
81        };
82
83        let host = Self::openvmm_host(&mut resources, &mesh, openvmm_log_file, log_env)
84            .await
85            .context("failed to create host process")?;
86        let (worker, halt_notif) = Worker::launch(&host, config)
87            .await
88            .context("failed to launch vm worker")?;
89
90        let worker = Arc::new(worker);
91
92        let mut vm = PetriVmOpenVmm::new(
93            super::runtime::PetriVmInner {
94                resources,
95                mesh,
96                worker,
97                framebuffer_view,
98            },
99            halt_notif,
100        );
101
102        tracing::info!("Resuming VM");
103        vm.resume().await?;
104
105        // Run basic save/restore test if it is supported
106        if supports_save_restore {
107            tracing::info!("Testing save/restore");
108            vm.verify_save_restore().await?;
109        }
110
111        tracing::info!("VM ready");
112        Ok((vm, runtime_config))
113    }
114
115    /// Run the VM, configuring pipette to automatically start if it is
116    /// included in the config
117    pub async fn run(mut self) -> anyhow::Result<(PetriVmOpenVmm, PetriVmRuntimeConfig)> {
118        let launch_linux_direct_pipette = if self.resources.properties.using_vtl0_pipette {
119            if matches!(self.resources.properties.os_flavor, OsFlavor::Windows)
120                && !self.resources.properties.is_isolated
121            {
122                // Make a file for the IMC hive. It's not guaranteed to be at a fixed
123                // location at runtime.
124                let mut imc_hive_file =
125                    tempfile::tempfile().context("failed to create temp file")?;
126                imc_hive_file
127                    .write_all(include_bytes!("../../../guest-bootstrap/imc.hiv"))
128                    .context("failed to write imc hive")?;
129
130                // Add the IMC device.
131                self.config.vmbus_devices.push((
132                    DeviceVtl::Vtl0,
133                    vmbfs_resources::VmbfsImcDeviceHandle {
134                        file: imc_hive_file,
135                    }
136                    .into_resource(),
137                ));
138            }
139
140            self.resources.properties.is_linux_direct
141        } else {
142            false
143        };
144
145        // Start the VM.
146        let (mut vm, config) = self.run_core().await?;
147
148        if launch_linux_direct_pipette {
149            vm.launch_linux_direct_pipette().await?;
150        }
151
152        Ok((vm, config))
153    }
154
155    async fn openvmm_host(
156        resources: &mut PetriVmResourcesOpenVmm,
157        mesh: &Mesh,
158        log_file: PetriLogFile,
159        vmm_env: BTreeMap<OsString, OsString>,
160    ) -> anyhow::Result<WorkerHost> {
161        // Copy the child's stderr to this process's, since internally this is
162        // wrapped by the test harness.
163        let (stderr_read, stderr_write) = pal::pipe_pair()?;
164        let task = resources.driver.spawn(
165            "serial log",
166            crate::log_task(
167                log_file,
168                PolledPipe::new(&resources.driver, stderr_read)
169                    .context("failed to create polled pipe")?,
170                "openvmm stderr",
171            ),
172        );
173        resources.log_stream_tasks.push(task);
174
175        let (host, runner) = mesh_worker::worker_host();
176        mesh.launch_host(
177            ProcessConfig::new("vmm")
178                .process_name(&resources.openvmm_path)
179                .stderr(Some(stderr_write))
180                .env(vmm_env.into_iter()),
181            openvmm_defs::entrypoint::MeshHostParams { runner },
182        )
183        .await?;
184        Ok(host)
185    }
186}