1mod 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#[derive(Parser)]
54struct Options {
55 #[clap(long)]
57 hv: Option<HypervisorOpt>,
58 #[clap(long)]
61 disable_offloads: bool,
62 #[clap(long)]
64 tmk: PathBuf,
65 #[clap(long)]
67 list: bool,
68 #[clap(conflicts_with("list"))]
70 tests: Vec<String>,
71}
72
73#[derive(clap::ValueEnum, Copy, Clone)]
74enum HypervisorOpt {
75 #[cfg(target_os = "linux")]
77 Kvm,
78 #[cfg(all(target_os = "linux", guest_arch = "x86_64"))]
80 Mshv,
81 #[cfg(target_os = "linux")]
84 MshvVtl,
85 #[cfg(target_os = "windows")]
87 Whp,
88 #[cfg(target_os = "macos")]
90 Hvf,
91 #[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}