Skip to main content

tmk_vmm/
main.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! A simple VMM for loading and running test microkernels (TMKs) but does not
5//! support general-purpose VMs.
6//!
7//! This is used to test the underlying VMM infrastructure without the complexity
8//! of the full OpenVMM stack.
9
10mod host_vmm;
11mod load;
12mod paravisor_vmm;
13mod run;
14
15use anyhow::Context;
16use anyhow::Result;
17use clap::Parser;
18use pal_async::DefaultDriver;
19use pal_async::DefaultPool;
20use run::CommonState;
21use std::path::PathBuf;
22use tracing::level_filters::LevelFilter;
23use tracing_subscriber::fmt::format::FmtSpan;
24use tracing_subscriber::layer::SubscriberExt;
25use tracing_subscriber::util::SubscriberInitExt;
26
27fn main() -> Result<()> {
28    tracing_subscriber::registry()
29        .with(
30            tracing_subscriber::fmt::layer()
31                .pretty()
32                .map_event_format(|e| e.with_source_location(false))
33                .fmt_fields(tracing_helpers::formatter::FieldFormatter)
34                .with_span_events(FmtSpan::NEW | FmtSpan::CLOSE),
35        )
36        .with(
37            tracing_subscriber::EnvFilter::builder()
38                .with_default_directive(LevelFilter::INFO.into())
39                .with_env_var("TMK_LOG")
40                .from_env_lossy(),
41        )
42        .init();
43
44    DefaultPool::run_with(do_main)
45}
46
47/// A simple VMM for loading and running test microkernels (TMKs).
48///
49/// This is used to test the underlying VMM infrastructure without the complexity
50/// of the full OpenVMM stack.
51///
52/// This can run either on a host or inside a paravisor environment.
53#[derive(Parser)]
54struct Options {
55    /// The hypervisor interface to use to run the TMK.
56    #[clap(long)]
57    hv: Option<HypervisorOpt>,
58    /// Disable offloads to the hypervisor. This disables WHP APIC emulation,
59    /// for example.
60    #[clap(long)]
61    disable_offloads: bool,
62    /// The path to the TMK binary.
63    #[clap(long)]
64    tmk: PathBuf,
65    /// List tests available in the TMK.
66    #[clap(long)]
67    list: bool,
68    /// Tests to run. Default is to run all tests.
69    #[clap(conflicts_with("list"))]
70    tests: Vec<String>,
71}
72
73#[derive(clap::ValueEnum, Copy, Clone)]
74enum HypervisorOpt {
75    /// Use KVM to run the TMK.
76    #[cfg(target_os = "linux")]
77    Kvm,
78    /// Use mshv to run the TMK.
79    #[cfg(all(target_os = "linux", guest_arch = "x86_64"))]
80    Mshv,
81    /// Use mshv-vtl to run the TMK; only supported inside a paravisor
82    /// environment.
83    #[cfg(target_os = "linux")]
84    MshvVtl,
85    /// Use WHP to run the TMK.
86    #[cfg(target_os = "windows")]
87    Whp,
88    /// Use Hypervisor.Framework to run the TMK.
89    #[cfg(target_os = "macos")]
90    Hvf,
91    /// Use mshv-vtl to run the TMK inside a CCA realm.
92    #[cfg(all(target_os = "linux", guest_arch = "aarch64"))]
93    Cca,
94}
95
96impl Options {
97    fn finalize(mut self) -> Result<Self> {
98        let hv = match self.hv {
99            Some(hv) => hv,
100            None => choose_hypervisor()?,
101        };
102
103        self.hv = Some(hv);
104
105        Ok(self)
106    }
107}
108
109async fn do_main(driver: DefaultDriver) -> Result<()> {
110    let opts = Options::parse();
111
112    if opts.list {
113        let tmk = fs_err::File::open(&opts.tmk).context("failed to open TMK")?;
114        let tests = load::enumerate_tests(&tmk)?;
115        for test in tests {
116            println!("{}", test.name);
117        }
118        Ok(())
119    } else {
120        let opts = opts.finalize()?;
121        let hv = opts.hv.expect("hv must have a finalized value");
122        let mut state = CommonState::new(driver, opts).await?;
123
124        state
125            .for_each_test(async |state, test| match hv {
126                #[cfg(target_os = "linux")]
127                HypervisorOpt::Kvm => state.run_host_vmm(virt_kvm::Kvm::new()?, test).await,
128                #[cfg(all(target_os = "linux", guest_arch = "x86_64"))]
129                HypervisorOpt::Mshv => state.run_host_vmm(virt_mshv::LinuxMshv::new()?, test).await,
130                #[cfg(target_os = "linux")]
131                HypervisorOpt::MshvVtl => {
132                    state
133                        .run_paravisor_vmm(virt::IsolationType::None, test)
134                        .await
135                }
136                #[cfg(all(target_os = "linux", guest_arch = "aarch64"))]
137                HypervisorOpt::Cca => {
138                    state
139                        .run_paravisor_vmm(virt::IsolationType::Cca, test)
140                        .await
141                }
142                #[cfg(windows)]
143                HypervisorOpt::Whp => {
144                    state
145                        .run_host_vmm(
146                            virt_whp::Whp {
147                                user_mode_apic: state.state.opts.disable_offloads,
148                                offload_enlightenments: !state.state.opts.disable_offloads,
149                            },
150                            test,
151                        )
152                        .await
153                }
154                #[cfg(target_os = "macos")]
155                HypervisorOpt::Hvf => state.run_host_vmm(virt_hvf::HvfHypervisor, test).await,
156            })
157            .await
158    }
159}
160
161fn choose_hypervisor() -> Result<HypervisorOpt> {
162    #[cfg(all(target_os = "linux", guest_arch = "x86_64"))]
163    {
164        if virt_mshv::is_available()? {
165            return Ok(HypervisorOpt::Mshv);
166        }
167    }
168    #[cfg(target_os = "linux")]
169    {
170        if virt_kvm::is_available()? {
171            return Ok(HypervisorOpt::Kvm);
172        }
173    }
174    #[cfg(windows)]
175    {
176        if virt_whp::is_available()? {
177            return Ok(HypervisorOpt::Whp);
178        }
179    }
180    #[cfg(target_os = "macos")]
181    {
182        return Ok(HypervisorOpt::Hvf);
183    }
184
185    #[expect(clippy::allow_attributes)]
186    #[allow(unreachable_code, reason = "unreachable on some targets")]
187    {
188        anyhow::bail!("no hypervisor available");
189    }
190}