Skip to content

Commit 9bfa979

Browse files
authored
Merge pull request #340 from Dstack-TEE/qemu-version
dstack-mr: Add qemu_version in VmConfig
2 parents 98d7d65 + 4b4f55b commit 9bfa979

File tree

9 files changed

+208
-17
lines changed

9 files changed

+208
-17
lines changed

dstack-mr/src/acpi.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,12 @@ impl Machine<'_> {
8585
} else {
8686
machine.push_str(",smm=off");
8787
}
88-
if self.pic {
88+
89+
let vopt = self
90+
.versioned_options()
91+
.context("Failed to get versioned options")?;
92+
93+
if vopt.pic {
8994
machine.push_str(",pic=on");
9095
} else {
9196
machine.push_str(",pic=off");
@@ -148,8 +153,13 @@ impl Machine<'_> {
148153

149154
debug!("qemu command: {cmd:?}");
150155

156+
let ver = vopt.version;
151157
// Execute the command and capture output
152158
let output = cmd
159+
.env(
160+
"QEMU_ACPI_COMPAT_VER",
161+
format!("{}.{}.{}", ver.0, ver.1, ver.2),
162+
)
153163
.output()
154164
.context("failed to execute dstack-acpi-tables")?;
155165

dstack-mr/src/machine.rs

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use crate::tdvf::Tdvf;
66
use crate::util::debug_print_log;
77
use crate::{kernel, TdxMeasurements};
88
use crate::{measure_log, measure_sha384};
9-
use anyhow::{Context, Result};
9+
use anyhow::{bail, Context, Result};
1010
use fs_err as fs;
1111
use log::debug;
1212

@@ -18,8 +18,9 @@ pub struct Machine<'a> {
1818
pub kernel: &'a str,
1919
pub initrd: &'a str,
2020
pub kernel_cmdline: &'a str,
21-
pub two_pass_add_pages: bool,
22-
pub pic: bool,
21+
pub two_pass_add_pages: Option<bool>,
22+
pub pic: Option<bool>,
23+
pub qemu_version: Option<String>,
2324
#[builder(default = false)]
2425
pub smm: bool,
2526
pub pci_hole64_size: Option<u64>,
@@ -30,6 +31,53 @@ pub struct Machine<'a> {
3031
pub root_verity: bool,
3132
}
3233

34+
fn parse_version_tuple(v: &str) -> Result<(u32, u32, u32)> {
35+
let parts: Vec<u32> = v
36+
.split('.')
37+
.map(|p| p.parse::<u32>().context("Invalid version number"))
38+
.collect::<Result<Vec<_>, _>>()?;
39+
if parts.len() != 3 {
40+
bail!(
41+
"Version string must have exactly 3 parts (major.minor.patch), got {}",
42+
parts.len()
43+
);
44+
}
45+
Ok((parts[0], parts[1], parts[2]))
46+
}
47+
48+
impl Machine<'_> {
49+
pub fn versioned_options(&self) -> Result<VersionedOptions> {
50+
let version = match &self.qemu_version {
51+
Some(v) => Some(parse_version_tuple(v).context("Failed to parse QEMU version")?),
52+
None => None,
53+
};
54+
let default_pic;
55+
let default_two_pass;
56+
let version = version.unwrap_or((9, 1, 0));
57+
if version < (8, 0, 0) {
58+
bail!("Unsupported QEMU version: {version:?}");
59+
}
60+
if ((8, 0, 0)..(9, 0, 0)).contains(&version) {
61+
default_pic = true;
62+
default_two_pass = true;
63+
} else {
64+
default_pic = false;
65+
default_two_pass = false;
66+
};
67+
Ok(VersionedOptions {
68+
version,
69+
pic: self.pic.unwrap_or(default_pic),
70+
two_pass_add_pages: self.two_pass_add_pages.unwrap_or(default_two_pass),
71+
})
72+
}
73+
}
74+
75+
pub struct VersionedOptions {
76+
pub version: (u32, u32, u32),
77+
pub pic: bool,
78+
pub two_pass_add_pages: bool,
79+
}
80+
3381
impl Machine<'_> {
3482
pub fn measure(&self) -> Result<TdxMeasurements> {
3583
debug!("measuring machine: {self:#?}");

dstack-mr/src/tdvf.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,10 @@ impl<'a> Tdvf<'a> {
223223
}
224224

225225
pub fn mrtd(&self, machine: &Machine) -> Result<Vec<u8>> {
226-
self.compute_mrtd(if machine.two_pass_add_pages {
226+
let opts = machine
227+
.versioned_options()
228+
.context("Failed to get versioned options")?;
229+
self.compute_mrtd(if opts.two_pass_add_pages {
227230
PageAddOrder::TwoPass
228231
} else {
229232
PageAddOrder::SinglePass

dstack-types/src/lib.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -138,10 +138,9 @@ pub struct VmConfig {
138138
pub cpu_count: u32,
139139
pub memory_size: u64,
140140
// https://github.com/intel-staging/qemu-tdx/issues/1
141-
#[serde(default)]
142-
pub qemu_single_pass_add_pages: bool,
143-
#[serde(default)]
144-
pub pic: bool,
141+
pub qemu_single_pass_add_pages: Option<bool>,
142+
pub pic: Option<bool>,
143+
pub qemu_version: Option<String>,
145144
#[serde(default)]
146145
pub pci_hole64_size: u64,
147146
#[serde(default)]
@@ -152,6 +151,7 @@ pub struct VmConfig {
152151
pub num_nvswitches: u32,
153152
#[serde(default)]
154153
pub hotplug_off: bool,
154+
pub image: Option<String>,
155155
}
156156

157157
#[derive(Serialize, Deserialize, Debug, Clone)]

kms/src/main_service.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -268,8 +268,9 @@ impl RpcHandler {
268268
.kernel_cmdline(&kernel_cmdline)
269269
.root_verity(true)
270270
.hotplug_off(vm_config.hotplug_off)
271-
.two_pass_add_pages(vm_config.qemu_single_pass_add_pages)
272-
.pic(vm_config.pic)
271+
.maybe_two_pass_add_pages(vm_config.qemu_single_pass_add_pages)
272+
.maybe_pic(vm_config.pic)
273+
.maybe_qemu_version(vm_config.qemu_version.clone())
273274
.maybe_pci_hole64_size(if vm_config.pci_hole64_size > 0 {
274275
Some(vm_config.pci_hole64_size)
275276
} else {

vmm/src/app.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -513,11 +513,13 @@ impl App {
513513
memory_size: manifest.memory as u64 * 1024 * 1024,
514514
qemu_single_pass_add_pages: cfg.cvm.qemu_single_pass_add_pages,
515515
pic: cfg.cvm.qemu_pic,
516+
qemu_version: cfg.cvm.qemu_version.clone(),
516517
pci_hole64_size: cfg.cvm.qemu_pci_hole64_size,
517518
hugepages: manifest.hugepages,
518519
num_gpus: gpus.gpus.len() as u32,
519520
num_nvswitches: gpus.bridges.len() as u32,
520521
hotplug_off: cfg.cvm.qemu_hotplug_off,
522+
image: Some(manifest.image.clone()),
521523
})?;
522524
json!({
523525
"kms_urls": kms_urls,

vmm/src/config.rs

Lines changed: 128 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//
33
// SPDX-License-Identifier: Apache-2.0
44

5-
use std::{net::IpAddr, path::PathBuf, str::FromStr};
5+
use std::{net::IpAddr, path::PathBuf, process::Command, str::FromStr};
66

77
use anyhow::{bail, Context, Result};
88
use load_config::load_config;
@@ -11,9 +11,69 @@ use rocket::figment::Figment;
1111
use serde::{Deserialize, Serialize};
1212

1313
use lspci::{lspci_filtered, Device};
14-
use tracing::info;
14+
use tracing::{info, warn};
1515

1616
pub const DEFAULT_CONFIG: &str = include_str!("../vmm.toml");
17+
18+
fn detect_qemu_version(qemu_path: &PathBuf) -> Result<String> {
19+
let output = Command::new(qemu_path)
20+
.arg("--version")
21+
.output()
22+
.context("Failed to execute qemu --version")?;
23+
24+
if !output.status.success() {
25+
bail!("QEMU version command failed with status: {}", output.status);
26+
}
27+
28+
let version_output =
29+
String::from_utf8(output.stdout).context("QEMU version output is not valid UTF-8")?;
30+
31+
parse_qemu_version_from_output(&version_output)
32+
.context("Could not parse QEMU version from output")
33+
}
34+
35+
fn parse_qemu_version_from_output(output: &str) -> Result<String> {
36+
// Parse version from output like:
37+
// "QEMU emulator version 8.2.2 (Debian 2:8.2.2+ds-0ubuntu1.4+tdx1.0)"
38+
// "QEMU emulator version 9.1.0"
39+
let version = output
40+
.lines()
41+
.next()
42+
.and_then(|line| {
43+
let words: Vec<&str> = line.split_whitespace().collect();
44+
45+
// First try: Look for "version" keyword and get the next word (only if it looks like a version)
46+
if let Some(version_idx) = words.iter().position(|&word| word == "version") {
47+
if let Some(next_word) = words.get(version_idx + 1) {
48+
// Only use the word after "version" if it looks like a version number
49+
if next_word.chars().next().is_some_and(|c| c.is_ascii_digit())
50+
&& (next_word.contains('.')
51+
|| next_word.chars().all(|c| c.is_ascii_digit() || c == '-'))
52+
{
53+
return Some(*next_word);
54+
}
55+
}
56+
}
57+
58+
// Fallback: find first word that looks like a version number
59+
words
60+
.iter()
61+
.find(|word| {
62+
// Check if word starts with digit and contains dots (version-like)
63+
word.chars().next().is_some_and(|c| c.is_ascii_digit())
64+
&& (word.contains('.')
65+
|| word.chars().all(|c| c.is_ascii_digit() || c == '-'))
66+
})
67+
.copied()
68+
})
69+
.context("Could not parse QEMU version from output")?;
70+
71+
// Extract just the version number (e.g., "8.2.2" from "8.2.2+ds-0ubuntu1.4+tdx1.0")
72+
let clean_version = version.split('+').next().unwrap_or(version).to_string();
73+
74+
Ok(clean_version)
75+
}
76+
1777
pub fn load_config_figment(config_file: Option<&str>) -> Figment {
1878
load_config("vmm", DEFAULT_CONFIG, config_file, false)
1979
}
@@ -127,9 +187,11 @@ pub struct CvmConfig {
127187
pub use_mrconfigid: bool,
128188

129189
/// QEMU single pass add page
130-
pub qemu_single_pass_add_pages: bool,
190+
pub qemu_single_pass_add_pages: Option<bool>,
131191
/// QEMU pic
132-
pub qemu_pic: bool,
192+
pub qemu_pic: Option<bool>,
193+
/// QEMU qemu_version
194+
pub qemu_version: Option<String>,
133195
/// QEMU pci_hole64_size
134196
pub qemu_pci_hole64_size: u64,
135197
/// QEMU hotplug_off
@@ -361,7 +423,69 @@ impl Config {
361423
}
362424
}
363425
info!("QEMU path: {}", me.cvm.qemu_path.display());
426+
427+
// Detect QEMU version if not already set
428+
match &me.cvm.qemu_version {
429+
None => match detect_qemu_version(&me.cvm.qemu_path) {
430+
Ok(version) => {
431+
info!("Detected QEMU version: {version}");
432+
me.cvm.qemu_version = Some(version);
433+
}
434+
Err(e) => {
435+
warn!("Failed to detect QEMU version: {e}");
436+
// Continue without version - the system will use defaults
437+
}
438+
},
439+
Some(version) => info!("Configured QEMU version: {version}"),
440+
}
364441
}
365442
Ok(me)
366443
}
367444
}
445+
446+
#[cfg(test)]
447+
mod tests {
448+
use super::*;
449+
450+
#[test]
451+
fn test_parse_qemu_version_debian_format() {
452+
let output = "QEMU emulator version 8.2.2 (Debian 2:8.2.2+ds-0ubuntu1.4+tdx1.0)\nCopyright (c) 2003-2023 Fabrice Bellard and the QEMU Project developers";
453+
let version = parse_qemu_version_from_output(output).unwrap();
454+
assert_eq!(version, "8.2.2");
455+
}
456+
457+
#[test]
458+
fn test_parse_qemu_version_simple_format() {
459+
let output = "QEMU emulator version 9.1.0\nCopyright (c) 2003-2024 Fabrice Bellard and the QEMU Project developers";
460+
let version = parse_qemu_version_from_output(output).unwrap();
461+
assert_eq!(version, "9.1.0");
462+
}
463+
464+
#[test]
465+
fn test_parse_qemu_version_old_debian_format() {
466+
let output = "QEMU emulator version 8.2.2 (Debian 1:8.2.2+ds-0ubuntu1.2)\nCopyright (c) 2003-2023 Fabrice Bellard and the QEMU Project developers";
467+
let version = parse_qemu_version_from_output(output).unwrap();
468+
assert_eq!(version, "8.2.2");
469+
}
470+
471+
#[test]
472+
fn test_parse_qemu_version_with_rc() {
473+
let output = "QEMU emulator version 9.0.0-rc1\nCopyright (c) 2003-2024 Fabrice Bellard and the QEMU Project developers";
474+
let version = parse_qemu_version_from_output(output).unwrap();
475+
assert_eq!(version, "9.0.0-rc1");
476+
}
477+
478+
#[test]
479+
fn test_parse_qemu_version_fallback() {
480+
let output = "Some unusual format 8.1.5 with version info";
481+
let version = parse_qemu_version_from_output(output).unwrap();
482+
assert_eq!(version, "8.1.5");
483+
}
484+
485+
#[test]
486+
fn test_parse_qemu_version_invalid() {
487+
let output = "No version information here";
488+
let result = parse_qemu_version_from_output(output);
489+
assert!(result.is_err());
490+
}
491+
}

vmm/src/one_shot.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,11 +261,13 @@ Compose file content (first 200 chars):
261261
memory_size: manifest.memory as u64 * 1024 * 1024,
262262
qemu_single_pass_add_pages: config.cvm.qemu_single_pass_add_pages,
263263
pic: config.cvm.qemu_pic,
264+
qemu_version: config.cvm.qemu_version.clone(),
264265
pci_hole64_size: config.cvm.qemu_pci_hole64_size,
265266
hugepages: manifest.hugepages,
266267
num_gpus: manifest.gpus.as_ref().map_or(0, |g| g.gpus.len() as u32),
267268
num_nvswitches: manifest.gpus.as_ref().map_or(0, |g| g.bridges.len() as u32),
268269
hotplug_off: config.cvm.qemu_hotplug_off,
270+
image: Some(manifest.image.clone()),
269271
})?
270272
});
271273
let sys_config_path = vm_work_dir.shared_dir().join(".sys-config.json");

vmm/vmm.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,9 @@ user = ""
3030
use_mrconfigid = true
3131

3232
# QEMU flags
33-
qemu_single_pass_add_pages = false
34-
qemu_pic = true
33+
#qemu_single_pass_add_pages = false
34+
#qemu_pic = true
35+
#qemu_version = ""
3536
qemu_pci_hole64_size = 0
3637
qemu_hotplug_off = false
3738

0 commit comments

Comments
 (0)