Skip to main content

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            memory_backing_file,
43
44            ged,
45            framebuffer_view,
46        } = self;
47
48        // TODO: OpenHCL needs virt_whp support
49        // TODO: PCAT needs vga device support
50        // TODO: arm64 is broken?
51        // TODO: VPCI and some PCIe endpoints (NVMe/GDMA) don't support
52        // TODO: virtio vsock doesn't support save/restore yet
53        // save/restore yet.
54        let has_unsupported_pcie_save_restore_device = config
55            .pcie_devices
56            .iter()
57            .any(|device| matches!(device.resource.id(), "nvme" | "gdma"));
58        let supports_save_restore = !resources.properties.is_openhcl
59            && !resources.properties.is_pcat
60            && !matches!(arch, MachineArch::Aarch64)
61            && !resources.properties.using_vpci
62            && !has_unsupported_pcie_save_restore_device
63            && !resources.properties.use_virtio_vsock;
64
65        // Add the GED and VTL 2 settings.
66        if let Some(mut ged) = ged {
67            ged.vtl2_settings = Some(prost::Message::encode_to_vec(
68                runtime_config.vtl2_settings.as_ref().unwrap(),
69            ));
70            config
71                .vmbus_devices
72                .push((DeviceVtl::Vtl2, ged.into_resource()));
73        }
74
75        tracing::debug!(?config, "OpenVMM config");
76
77        let log_env = match host_log_levels {
78            None | Some(OpenvmmLogConfig::TestDefault) => BTreeMap::<OsString, OsString>::from([
79                ("OPENVMM_LOG".into(), "debug".into()),
80                ("OPENVMM_SHOW_SPANS".into(), "true".into()),
81            ]),
82            Some(OpenvmmLogConfig::BuiltInDefault) => BTreeMap::new(),
83            Some(OpenvmmLogConfig::Custom(levels)) => levels
84                .iter()
85                .map(|(k, v)| (OsString::from(k), OsString::from(v)))
86                .collect::<BTreeMap<OsString, OsString>>(),
87        };
88
89        let (host, pid) = Self::openvmm_host(&mut resources, &mesh, openvmm_log_file, log_env)
90            .await
91            .context("failed to create host process")?;
92        // If a memory backing file was requested, open/create it and size
93        // it to match the configured guest RAM.
94        let shared_memory = memory_backing_file
95            .as_ref()
96            .map(|mem_path| {
97                openvmm_helpers::shared_memory::open_memory_backing_file(
98                    mem_path,
99                    config.memory.mem_size,
100                )
101            })
102            .transpose()?;
103
104        let (worker, halt_notif) = Worker::launch(&host, config, shared_memory)
105            .await
106            .context("failed to launch vm worker")?;
107
108        let worker = Arc::new(worker);
109
110        let is_minimal = resources.properties.minimal_mode;
111
112        let mut vm = PetriVmOpenVmm::new(
113            super::runtime::PetriVmInner {
114                resources,
115                mesh,
116                worker,
117                framebuffer_view,
118                cidata_mounted: false,
119                pid,
120            },
121            halt_notif,
122        );
123
124        tracing::info!("Resuming VM");
125        vm.resume().await?;
126
127        // Run basic save/restore test if it is supported
128        if supports_save_restore && !is_minimal {
129            tracing::info!("Testing save/restore");
130            vm.verify_save_restore().await?;
131        }
132
133        tracing::info!("VM ready");
134        Ok((vm, runtime_config))
135    }
136
137    /// Run the VM, configuring pipette to automatically start if it is
138    /// included in the config
139    pub async fn run(mut self) -> anyhow::Result<(PetriVmOpenVmm, PetriVmRuntimeConfig)> {
140        // Set up the IMC hive for Windows guests that use pipette in VTL0.
141        if self.resources.properties.using_vtl0_pipette
142            && matches!(self.resources.properties.os_flavor, OsFlavor::Windows)
143            && !self.resources.properties.is_isolated
144        {
145            let mut imc_hive_file = tempfile::tempfile().context("failed to create temp file")?;
146            imc_hive_file
147                .write_all(include_bytes!("../../../guest-bootstrap/imc.hiv"))
148                .context("failed to write imc hive")?;
149
150            self.config.vmbus_devices.push((
151                DeviceVtl::Vtl0,
152                vmbfs_resources::VmbfsImcDeviceHandle {
153                    file: imc_hive_file,
154                }
155                .into_resource(),
156            ));
157        }
158
159        // On non-pipette-as-init Linux direct, launch pipette via the serial
160        // agent. (When pipette is PID 1, it auto-starts on boot and the
161        // serial agent is not present.)
162        let launch_via_serial = self.resources.linux_direct_serial_agent.is_some()
163            && self.resources.properties.using_vtl0_pipette;
164
165        // Start the VM.
166        let (mut vm, config) = self.run_core().await?;
167
168        if launch_via_serial {
169            vm.launch_linux_direct_pipette().await?;
170        }
171
172        Ok((vm, config))
173    }
174
175    async fn openvmm_host(
176        resources: &mut PetriVmResourcesOpenVmm,
177        mesh: &Mesh,
178        log_file: PetriLogFile,
179        vmm_env: BTreeMap<OsString, OsString>,
180    ) -> anyhow::Result<(WorkerHost, i32)> {
181        // Copy the child's stderr to this process's, since internally this is
182        // wrapped by the test harness.
183        let (stderr_read, stderr_write) = pal::pipe_pair()?;
184        let task = resources.driver.spawn(
185            "serial log",
186            crate::log_task(
187                log_file,
188                PolledPipe::new(&resources.driver, stderr_read)
189                    .context("failed to create polled pipe")?,
190                "openvmm stderr",
191            ),
192        );
193        resources.log_stream_tasks.push(task);
194
195        let (host, runner) = mesh_worker::worker_host();
196        let pid = mesh
197            .launch_host(
198                ProcessConfig::new("vmm")
199                    .process_name(&resources.openvmm_path)
200                    .stderr(Some(stderr_write))
201                    .env(vmm_env),
202                openvmm_defs::entrypoint::MeshHostParams { runner },
203            )
204            .await?;
205        Ok((host, pid))
206    }
207}