Skip to main content

qemu_command_builder/
machine.rs

1use crate::parsers::ARG_MACHINE;
2use std::str::FromStr;
3
4use bon::Builder;
5use proptest_derive::Arbitrary;
6
7use crate::common::*;
8use crate::machine_type::{MachineTypeAarch64, MachineTypeX86_64};
9use crate::parsers::{DELIM_COLON, DELIM_COMMA};
10use crate::qao;
11use crate::shell_string::{ShellString, ShellStringError};
12use crate::to_command::{ToArg, ToCommand};
13
14const KEY_ACCEL: &str = "accel=";
15const KEY_TYPE: &str = "type=";
16const KEY_VMPORT: &str = "vmport=";
17const KEY_DUMP_GUEST_CORE: &str = "dump-guest-core=";
18const KEY_MEM_MERGE: &str = "mem-merge=";
19const KEY_AES_KEY_WRAP: &str = "aes-key-wrap=";
20const KEY_DEA_KEY_WRAP: &str = "dea-key-wrap=";
21const KEY_NVDIMM: &str = "nvdimm=";
22const KEY_MEMORY_ENCRYPTION: &str = "memory-encryption=";
23const KEY_HMAT: &str = "hmat=";
24const KEY_AUX_RAM_SHARE: &str = "aux-ram-share=";
25const KEY_MEMORY_BACKEND: &str = "memory-backend=";
26
27/// Supported `interleave-granularity=` values for `cxl-fmw`.
28#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Default, Arbitrary)]
29pub enum Granularity {
30    #[default]
31    G256,
32    G512,
33    G1k,
34    G2k,
35    G4k,
36    G8k,
37    G16k,
38}
39
40impl ToArg for Granularity {
41    fn to_arg(&self) -> &str {
42        match self {
43            Granularity::G256 => "256",
44            Granularity::G512 => "512",
45            Granularity::G1k => "1k",
46            Granularity::G2k => "2k",
47            Granularity::G4k => "4k",
48            Granularity::G8k => "8k",
49            Granularity::G16k => "16k",
50        }
51    }
52}
53/// A CXL fixed memory window definition for `-machine`.
54#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Default, Builder, Arbitrary)]
55pub struct CxlFmw {
56    targets: Vec<String>,
57    size: String,
58    interleave_granularity: Option<Granularity>,
59}
60
61/// Cache topology properties for `-machine smp-cache.*`.
62#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Default, Builder, Arbitrary)]
63pub struct SmpCache {
64    cache: String,
65    topology: String,
66}
67
68/// Architecture-specific machine types accepted by this crate.
69#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Arbitrary)]
70pub enum MachineType {
71    X86_64(MachineTypeX86_64),
72    Aarch(MachineTypeAarch64),
73}
74
75impl ToArg for MachineType {
76    fn to_arg(&self) -> &str {
77        match self {
78            MachineType::X86_64(mt) => mt.to_arg(),
79            MachineType::Aarch(mt) => mt.to_arg(),
80        }
81    }
82}
83/// Select the emulated machine by name. Use ``-machine help`` to list
84/// available machines.
85///
86/// For architectures which aim to support live migration compatibility
87/// across releases, each release will introduce a new versioned machine
88/// type. For example, the 2.8.0 release introduced machine types
89/// "pc-i440fx-2.8" and "pc-q35-2.8" for the x86\_64/i686 architectures.
90///
91/// To allow live migration of guests from QEMU version 2.8.0, to QEMU
92/// version 2.9.0, the 2.9.0 version must support the "pc-i440fx-2.8"
93/// and "pc-q35-2.8" machines too. To allow users live migrating VMs to
94/// skip multiple intermediate releases when upgrading, new releases of
95/// QEMU will support machine types from many previous versions.
96#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Builder, Arbitrary)]
97pub struct Machine<T> {
98    /// The QEMU machine type name.
99    machine_type: T,
100
101    /// This is used to enable an accelerator. Depending on the target
102    /// architecture, kvm, xen, hvf, nvmm, whpx or tcg can be available.
103    /// By default, tcg is used. If there is more than one accelerator
104    /// specified, the next one is used if the previous one fails to
105    /// initialize.
106    accel: Option<Vec<AccelType>>,
107
108    /// Enables emulation of VMWare IO port, for vmmouse etc. auto says
109    //  to select the value based on accel and i8042. For accel=xen or
110    //  i8042=off the default is off otherwise the default is on.
111    vmport: Option<OnOffAuto>,
112
113    /// Include guest memory in a core dump. The default is on.
114    dump_guest_core: Option<OnOffDefaultOn>,
115
116    /// Enables or disables memory merge support. This feature, when
117    //  supported by the host, de-duplicates identical memory pages
118    //  among VMs instances (enabled by default).
119    mem_merge: Option<OnOffDefaultOn>,
120
121    /// Enables or disables AES key wrapping support on s390-ccw hosts.
122    /// This feature controls whether AES wrapping keys will be created
123    /// to allow execution of AES cryptographic functions. The default
124    /// is on.
125    aes_key_wrap: Option<OnOffDefaultOn>,
126
127    /// Enables or disables DEA key wrapping support on s390-ccw hosts.
128    /// This feature controls whether DEA wrapping keys will be created
129    /// to allow execution of DEA cryptographic functions. The default
130    /// is on.
131    dea_key_wrap: Option<OnOffDefaultOn>,
132
133    /// Enables or disables NVDIMM support. The default is off.
134    nvdimm: Option<OnOffDefaultOff>,
135
136    /// Memory encryption object to use. The default is none.
137    memory_encryption: Option<ShellString>, // TODO find out actual values
138
139    /// Enables or disables ACPI Heterogeneous Memory Attribute Table
140    /// (HMAT) support. The default is off.
141    hmat: Option<OnOffDefaultOff>,
142
143    /// Allocate auxiliary guest RAM as an anonymous file that is
144    /// shareable with an external process.  This option applies to
145    /// memory allocated as a side effect of creating various devices.
146    /// It does not apply to memory-backend-objects, whether explicitly
147    /// specified on the command line, or implicitly created by the -m
148    /// command line option.  The default is off.
149    aux_ram_share: Option<OnOffDefaultOff>,
150
151    /// An alternative to legacy ``-mem-path`` and ``mem-prealloc`` options.
152    /// Allows to use a memory backend as main RAM.
153    memory_backend: Option<ShellString>, // TODO find out actual values
154                                         /*
155                                           /// Define a CXL Fixed Memory Window (CFMW).
156                                           ///
157                                           /// Described in the CXL 2.0 ECN: CEDT CFMWS & QTG _DSM.
158                                           ///
159                                           /// They are regions of Host Physical Addresses (HPA) on a system which
160                                           /// may be interleaved across one or more CXL host bridges.  The system
161                                           /// software will assign particular devices into these windows and
162                                           /// configure the downstream Host-managed Device Memory (HDM) decoders
163                                           /// in root ports, switch ports and devices appropriately to meet the
164                                           /// interleave requirements before enabling the memory devices.
165                                           ///
166                                           /// ``targets.X=target`` provides the mapping to CXL host bridges
167                                           /// which may be identified by the id provided in the -device entry.
168                                           /// Multiple entries are needed to specify all the targets when
169                                           /// the fixed memory window represents interleaved memory. X is the
170                                           /// target index from 0.
171                                           ///
172                                           /// ``size=size`` sets the size of the CFMW. This must be a multiple of
173                                           /// 256MiB. The region will be aligned to 256MiB but the location is
174                                           /// platform and configuration dependent.
175                                           ///
176                                           /// ``interleave-granularity=granularity`` sets the granularity of
177                                           /// interleave. Default 256 (bytes). Only 256, 512, 1k, 2k,
178                                           /// 4k, 8k and 16k granularities supported.
179                                         // TOOD  cxl_fmw: Option<CxlFmw>,
180
181                                           /// Define cache properties for SMP system.
182                                           ///
183                                           /// ``cache=cachename`` specifies the cache that the properties will be
184                                           /// applied on. This field is the combination of cache level and cache
185                                           /// type. It supports ``l1d`` (L1 data cache), ``l1i`` (L1 instruction
186                                           /// cache), ``l2`` (L2 unified cache) and ``l3`` (L3 unified cache).
187                                           ///
188                                           /// ``topology=topologylevel`` sets the cache topology level. It accepts
189                                           /// CPU topology levels including ``core``, ``module``, ``cluster``, ``die``,
190                                           /// ``socket``, ``book``, ``drawer`` and a special value ``default``. If
191                                           /// ``default`` is set, then the cache topology will follow the architecture's
192                                           /// default cache topology model. If another topology level is set, the cache
193                                           /// will be shared at corresponding CPU topology level. For example,
194                                           /// ``topology=core`` makes the cache shared by all threads within a core.
195                                           /// The omitting cache will default to using the ``default`` level.
196                                           ///
197                                           /// The default cache topology model for an i386 PC machine is as follows:
198                                           /// ``l1d``, ``l1i``, and ``l2`` caches are per ``core``, while the ``l3``
199                                           /// cache is per ``die``.
200                                           */
201                                         // TODO   smp_cache: Option<Vec<SmpCache>>,
202}
203
204#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Builder, Arbitrary)]
205pub struct MachineX86_64 {
206    /// The x86_64-specific `-machine` payload.
207    pub m: Machine<MachineTypeX86_64>,
208}
209
210impl ToCommand for MachineX86_64 {
211    fn command(&self) -> String {
212        ARG_MACHINE.to_string()
213    }
214
215    fn to_args(&self) -> Vec<String> {
216        let mut args = vec![self.m.machine_type.to_arg().to_string()];
217
218        if let Some(accels) = &self.m.accel {
219            let accel_strs: Vec<&str> = accels.iter().map(|a| a.to_arg()).collect();
220            args.push(format!("{}{}", KEY_ACCEL, accel_strs.join(":")));
221        }
222        qao!(&self.m.vmport, args, KEY_VMPORT);
223        qao!(&self.m.dump_guest_core, args, KEY_DUMP_GUEST_CORE);
224        qao!(&self.m.mem_merge, args, KEY_MEM_MERGE);
225        qao!(&self.m.aes_key_wrap, args, KEY_AES_KEY_WRAP);
226        qao!(&self.m.dea_key_wrap, args, KEY_DEA_KEY_WRAP);
227        qao!(&self.m.nvdimm, args, KEY_NVDIMM);
228        if let Some(memory_encryption) = &self.m.memory_encryption {
229            args.push(format!("{}{}", KEY_MEMORY_ENCRYPTION, memory_encryption.as_ref()));
230        }
231        qao!(&self.m.hmat, args, KEY_HMAT);
232        qao!(&self.m.aux_ram_share, args, KEY_AUX_RAM_SHARE);
233        if let Some(memory_backend) = &self.m.memory_backend {
234            args.push(format!("{}{}", KEY_MEMORY_BACKEND, memory_backend.as_ref()));
235        }
236
237        /*
238        if let Some(cxl_fmw) = &self.m.cxl_fmw {
239            for (idx, target) in cxl_fmw.targets.iter().enumerate() {
240                args.push(format!("cxl-fmw.0.targets.{}={}", idx, target));
241            }
242            args.push(format!("cxl-fmw.0.size={}", cxl_fmw.size));
243            if let Some(granularity) = &cxl_fmw.interleave_granularity {
244                args.push(format!("cxl-fmw.0.interleave-granularity={}", granularity.to_arg()));
245            }
246        }
247        if let Some(smp_caches) = &self.m.smp_cache {
248            for (idx, smp_cache) in smp_caches.iter().enumerate() {
249                args.push(format!("smp-cache.{}.cache={}", idx, smp_cache.cache));
250                args.push(format!("smp-cache.{}.topology={}", idx, smp_cache.topology));
251            }
252        }
253         */
254        vec![args.join(DELIM_COMMA)]
255    }
256}
257
258impl FromStr for MachineX86_64 {
259    type Err = ShellStringError;
260
261    fn from_str(s: &str) -> Result<Self, Self::Err> {
262        parse_machine_x86_64(s).map_err(ShellStringError::new)
263    }
264}
265
266fn parse_machine_x86_64(s: &str) -> Result<MachineX86_64, String> {
267    let mut parts = s.split(DELIM_COMMA);
268    let first = parts.next().ok_or_else(|| "empty machine argument".to_string())?;
269
270    let mut machine_type = None;
271    let mut accel = None;
272    let mut vmport = None;
273    let mut dump_guest_core = None;
274    let mut mem_merge = None;
275    let mut aes_key_wrap = None;
276    let mut dea_key_wrap = None;
277    let mut nvdimm = None;
278    let mut memory_encryption = None;
279    let mut hmat = None;
280    let mut aux_ram_share = None;
281    let mut memory_backend = None;
282
283    if let Some(value) = first.strip_prefix(KEY_TYPE) {
284        machine_type = Some(parse_machine_type(value)?);
285    } else if first.contains('=') {
286        parse_machine_option(
287            first,
288            &mut machine_type,
289            &mut accel,
290            &mut vmport,
291            &mut dump_guest_core,
292            &mut mem_merge,
293            &mut aes_key_wrap,
294            &mut dea_key_wrap,
295            &mut nvdimm,
296            &mut memory_encryption,
297            &mut hmat,
298            &mut aux_ram_share,
299            &mut memory_backend,
300        )?;
301    } else {
302        machine_type = Some(parse_machine_type(first)?);
303    }
304
305    for part in parts {
306        parse_machine_option(
307            part,
308            &mut machine_type,
309            &mut accel,
310            &mut vmport,
311            &mut dump_guest_core,
312            &mut mem_merge,
313            &mut aes_key_wrap,
314            &mut dea_key_wrap,
315            &mut nvdimm,
316            &mut memory_encryption,
317            &mut hmat,
318            &mut aux_ram_share,
319            &mut memory_backend,
320        )?;
321    }
322
323    let machine_type = machine_type.ok_or_else(|| "machine type is required".to_string())?;
324
325    Ok(MachineX86_64 {
326        m: Machine {
327            machine_type,
328            accel,
329            vmport,
330            dump_guest_core,
331            mem_merge,
332            aes_key_wrap,
333            dea_key_wrap,
334            nvdimm,
335            memory_encryption,
336            hmat,
337            aux_ram_share,
338            memory_backend,
339        },
340    })
341}
342
343#[allow(clippy::too_many_arguments)]
344fn parse_machine_option(
345    part: &str,
346    machine_type: &mut Option<MachineTypeX86_64>,
347    accel: &mut Option<Vec<AccelType>>,
348    vmport: &mut Option<OnOffAuto>,
349    dump_guest_core: &mut Option<OnOffDefaultOn>,
350    mem_merge: &mut Option<OnOffDefaultOn>,
351    aes_key_wrap: &mut Option<OnOffDefaultOn>,
352    dea_key_wrap: &mut Option<OnOffDefaultOn>,
353    nvdimm: &mut Option<OnOffDefaultOff>,
354    memory_encryption: &mut Option<ShellString>,
355    hmat: &mut Option<OnOffDefaultOff>,
356    aux_ram_share: &mut Option<OnOffDefaultOff>,
357    memory_backend: &mut Option<ShellString>,
358) -> Result<(), String> {
359    let (key, value) = part.split_once('=').ok_or_else(|| format!("invalid machine option: {part}"))?;
360    match key {
361        "type" => *machine_type = Some(parse_machine_type(value)?),
362        "accel" => {
363            let accels = value
364                .split(DELIM_COLON)
365                .map(|v| v.parse::<AccelType>().map_err(|_| format!("invalid accel value: {v}")))
366                .collect::<Result<Vec<_>, _>>()?;
367            *accel = Some(accels);
368        }
369        "vmport" => *vmport = Some(value.parse::<OnOffAuto>().map_err(|_| format!("invalid vmport value: {value}"))?),
370        "dump-guest-core" => *dump_guest_core = Some(value.parse::<OnOffDefaultOn>().map_err(|_| format!("invalid dump-guest-core value: {value}"))?),
371        "mem-merge" => *mem_merge = Some(value.parse::<OnOffDefaultOn>().map_err(|_| format!("invalid mem-merge value: {value}"))?),
372        "aes-key-wrap" => *aes_key_wrap = Some(value.parse::<OnOffDefaultOn>().map_err(|_| format!("invalid aes-key-wrap value: {value}"))?),
373        "dea-key-wrap" => *dea_key_wrap = Some(value.parse::<OnOffDefaultOn>().map_err(|_| format!("invalid dea-key-wrap value: {value}"))?),
374        "nvdimm" => *nvdimm = Some(value.parse::<OnOffDefaultOff>().map_err(|_| format!("invalid nvdimm value: {value}"))?),
375        "memory-encryption" => *memory_encryption = Some(ShellString::new(value)),
376        "hmat" => *hmat = Some(value.parse::<OnOffDefaultOff>().map_err(|_| format!("invalid hmat value: {value}"))?),
377        "aux-ram-share" => *aux_ram_share = Some(value.parse::<OnOffDefaultOff>().map_err(|_| format!("invalid aux-ram-share value: {value}"))?),
378        "memory-backend" => *memory_backend = Some(ShellString::new(value)),
379        other => return Err(format!("unsupported machine option: {other}")),
380    }
381
382    Ok(())
383}
384
385fn parse_machine_type(value: &str) -> Result<MachineTypeX86_64, String> {
386    value.parse::<MachineTypeX86_64>()
387}
388
389#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Builder, Arbitrary)]
390pub struct MachineAarch64 {
391    pub m: Machine<MachineTypeAarch64>,
392}
393impl ToCommand for MachineAarch64 {
394    fn command(&self) -> String {
395        ARG_MACHINE.to_string()
396    }
397
398    fn to_args(&self) -> Vec<String> {
399        let mut args = vec![self.m.machine_type.to_arg().to_string()];
400
401        if let Some(accels) = &self.m.accel {
402            let accel_strs: Vec<&str> = accels.iter().map(|a| a.to_arg()).collect();
403            args.push(format!("{}{}", KEY_ACCEL, accel_strs.join(":")));
404        }
405        qao!(&self.m.dump_guest_core, args, KEY_DUMP_GUEST_CORE);
406        qao!(&self.m.mem_merge, args, KEY_MEM_MERGE);
407        qao!(&self.m.nvdimm, args, KEY_NVDIMM);
408        if let Some(memory_backend) = &self.m.memory_backend {
409            args.push(format!("{}{}", KEY_MEMORY_BACKEND, memory_backend.as_ref()));
410        }
411
412        vec![args.join(DELIM_COMMA)]
413    }
414}
415impl FromStr for MachineAarch64 {
416    type Err = ShellStringError;
417
418    fn from_str(s: &str) -> Result<Self, Self::Err> {
419        parse_machine_aarch64(s).map_err(ShellStringError::new)
420    }
421}
422
423fn parse_machine_aarch64(s: &str) -> Result<MachineAarch64, String> {
424    let mut parts = s.split(DELIM_COMMA);
425    let first = parts.next().ok_or_else(|| "empty machine argument".to_string())?;
426
427    let mut machine_type = None;
428    let mut accel = None;
429    let mut dump_guest_core = None;
430    let mut mem_merge = None;
431    let mut nvdimm = None;
432    let mut memory_backend = None;
433
434    if let Some(value) = first.strip_prefix(KEY_TYPE) {
435        machine_type = Some(parse_machine_type_aarch64(value)?);
436    } else if first.contains('=') {
437        parse_machine_aarch64_option(first, &mut machine_type, &mut accel, &mut dump_guest_core, &mut mem_merge, &mut nvdimm, &mut memory_backend)?;
438    } else {
439        machine_type = Some(parse_machine_type_aarch64(first)?);
440    }
441
442    for part in parts {
443        parse_machine_aarch64_option(part, &mut machine_type, &mut accel, &mut dump_guest_core, &mut mem_merge, &mut nvdimm, &mut memory_backend)?;
444    }
445
446    let machine_type = machine_type.ok_or_else(|| "machine type is required".to_string())?;
447
448    Ok(MachineAarch64 {
449        m: Machine {
450            machine_type,
451            accel,
452            vmport: None,
453            dump_guest_core,
454            mem_merge,
455            aes_key_wrap: None,
456            dea_key_wrap: None,
457            nvdimm,
458            memory_encryption: None,
459            hmat: None,
460            aux_ram_share: None,
461            memory_backend,
462        },
463    })
464}
465
466fn parse_machine_aarch64_option(
467    part: &str,
468    machine_type: &mut Option<MachineTypeAarch64>,
469    accel: &mut Option<Vec<AccelType>>,
470    dump_guest_core: &mut Option<OnOffDefaultOn>,
471    mem_merge: &mut Option<OnOffDefaultOn>,
472    nvdimm: &mut Option<OnOffDefaultOff>,
473    memory_backend: &mut Option<ShellString>,
474) -> Result<(), String> {
475    let (key, value) = part.split_once('=').ok_or_else(|| format!("invalid machine option: {part}"))?;
476    match key {
477        "type" => *machine_type = Some(parse_machine_type_aarch64(value)?),
478        "accel" => {
479            let accels = value
480                .split(DELIM_COLON)
481                .map(|v| v.parse::<AccelType>().map_err(|_| format!("invalid accel value: {v}")))
482                .collect::<Result<Vec<_>, _>>()?;
483            *accel = Some(accels);
484        }
485        "dump-guest-core" => *dump_guest_core = Some(value.parse::<OnOffDefaultOn>().map_err(|_| format!("invalid dump-guest-core value: {value}"))?),
486        "mem-merge" => *mem_merge = Some(value.parse::<OnOffDefaultOn>().map_err(|_| format!("invalid mem-merge value: {value}"))?),
487        "nvdimm" => *nvdimm = Some(value.parse::<OnOffDefaultOff>().map_err(|_| format!("invalid nvdimm value: {value}"))?),
488        "memory-backend" => *memory_backend = Some(ShellString::new(value)),
489        other => return Err(format!("unsupported aarch64 machine option: {other}")),
490    }
491
492    Ok(())
493}
494
495fn parse_machine_type_aarch64(value: &str) -> Result<MachineTypeAarch64, String> {
496    value.parse::<MachineTypeAarch64>().map_err(|_| format!("{value} is not a supported aarch64 machine type"))
497}