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 clap::Parser;
17use pal_async::DefaultDriver;
18use pal_async::DefaultPool;
19use run::CommonState;
20use std::path::PathBuf;
21use tracing::level_filters::LevelFilter;
22use tracing_subscriber::fmt::format::FmtSpan;
23use tracing_subscriber::layer::SubscriberExt;
24use tracing_subscriber::util::SubscriberInitExt;
25
26fn main() -> anyhow::Result<()> {
27    tracing_subscriber::registry()
28        .with(
29            tracing_subscriber::fmt::layer()
30                .pretty()
31                .map_event_format(|e| e.with_source_location(false))
32                .fmt_fields(tracing_helpers::formatter::FieldFormatter)
33                .with_span_events(FmtSpan::NEW | FmtSpan::CLOSE),
34        )
35        .with(
36            tracing_subscriber::EnvFilter::builder()
37                .with_default_directive(LevelFilter::INFO.into())
38                .with_env_var("TMK_LOG")
39                .from_env_lossy(),
40        )
41        .init();
42
43    DefaultPool::run_with(do_main)
44}
45
46/// A simple VMM for loading and running test microkernels (TMKs).
47///
48/// This is used to test the underlying VMM infrastructure without the complexity
49/// of the full OpenVMM stack.
50///
51/// This can run either on a host or inside a paravisor environment.
52#[derive(Parser)]
53struct Options {
54    /// The hypervisor interface to use to run the TMK.
55    #[clap(long)]
56    hv: Option<HypervisorOpt>,
57    /// Disable offloads to the hypervisor. This disables WHP APIC emulation,
58    /// for example.
59    #[clap(long)]
60    disable_offloads: bool,
61    /// The path to the TMK binary.
62    #[clap(long)]
63    tmk: PathBuf,
64    /// List tests available in the TMK.
65    #[clap(long)]
66    list: bool,
67    /// Tests to run. Default is to run all tests.
68    #[clap(conflicts_with("list"))]
69    tests: Vec<String>,
70}
71
72#[derive(clap::ValueEnum, Copy, Clone)]
73enum HypervisorOpt {
74    /// Use KVM to run the TMK.
75    #[cfg(target_os = "linux")]
76    Kvm,
77    /// Use mshv to run the TMK.
78    #[cfg(all(target_os = "linux", guest_arch = "x86_64"))]
79    Mshv,
80    /// Use mshv-vtl to run the TMK; only supported inside a paravisor
81    /// environment.
82    #[cfg(target_os = "linux")]
83    MshvVtl,
84    /// Use WHP to run the TMK.
85    #[cfg(target_os = "windows")]
86    Whp,
87    /// Use Hypervisor.Framework to run the TMK.
88    #[cfg(target_os = "macos")]
89    Hvf,
90}
91
92async fn do_main(driver: DefaultDriver) -> anyhow::Result<()> {
93    let opts = Options::parse();
94
95    if opts.list {
96        let tmk = fs_err::File::open(&opts.tmk).context("failed to open TMK")?;
97        let tests = load::enumerate_tests(&tmk)?;
98        for test in tests {
99            println!("{}", test.name);
100        }
101        Ok(())
102    } else {
103        let hv = match opts.hv {
104            Some(hv) => hv,
105            None => choose_hypervisor()?,
106        };
107        let mut state = CommonState::new(driver, opts).await?;
108
109        state
110            .for_each_test(async |state, test| match hv {
111                #[cfg(target_os = "linux")]
112                HypervisorOpt::Kvm => state.run_host_vmm(virt_kvm::Kvm, test).await,
113                #[cfg(all(target_os = "linux", guest_arch = "x86_64"))]
114                HypervisorOpt::Mshv => state.run_host_vmm(virt_mshv::LinuxMshv, test).await,
115                #[cfg(target_os = "linux")]
116                HypervisorOpt::MshvVtl => {
117                    state
118                        .run_paravisor_vmm(virt::IsolationType::None, test)
119                        .await
120                }
121                #[cfg(windows)]
122                HypervisorOpt::Whp => state.run_host_vmm(virt_whp::Whp, test).await,
123                #[cfg(target_os = "macos")]
124                HypervisorOpt::Hvf => state.run_host_vmm(virt_hvf::HvfHypervisor, test).await,
125            })
126            .await
127    }
128}
129
130fn choose_hypervisor() -> anyhow::Result<HypervisorOpt> {
131    #[cfg(all(target_os = "linux", guest_arch = "x86_64"))]
132    {
133        if virt::Hypervisor::is_available(&virt_mshv::LinuxMshv)? {
134            return Ok(HypervisorOpt::Mshv);
135        }
136    }
137    #[cfg(target_os = "linux")]
138    {
139        if virt::Hypervisor::is_available(&virt_kvm::Kvm)? {
140            return Ok(HypervisorOpt::Kvm);
141        }
142    }
143    #[cfg(windows)]
144    {
145        if virt::Hypervisor::is_available(&virt_whp::Whp)? {
146            return Ok(HypervisorOpt::Whp);
147        }
148    }
149    #[cfg(target_os = "macos")]
150    {
151        if virt::Hypervisor::is_available(&virt_hvf::HvfHypervisor)? {
152            return Ok(HypervisorOpt::Hvf);
153        }
154    }
155
156    anyhow::bail!("no hypervisor available");
157}