Skip to main content

qemu_command_builder/args/
machine.rs

1use crate::parsers::ARG_MACHINE;
2use std::str::FromStr;
3
4use bon::Builder;
5use proptest_derive::Arbitrary;
6
7use crate::args::machine_type::{MachineTypeAarch64, MachineTypeX86_64};
8use crate::common::*;
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>,
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>,
154
155                                         /*
156                                           /// Define a CXL Fixed Memory Window (CFMW).
157                                           ///
158                                           /// Described in the CXL 2.0 ECN: CEDT CFMWS & QTG _DSM.
159                                           ///
160                                           /// They are regions of Host Physical Addresses (HPA) on a system which
161                                           /// may be interleaved across one or more CXL host bridges.  The system
162                                           /// software will assign particular devices into these windows and
163                                           /// configure the downstream Host-managed Device Memory (HDM) decoders
164                                           /// in root ports, switch ports and devices appropriately to meet the
165                                           /// interleave requirements before enabling the memory devices.
166                                           ///
167                                           /// ``targets.X=target`` provides the mapping to CXL host bridges
168                                           /// which may be identified by the id provided in the -device entry.
169                                           /// Multiple entries are needed to specify all the targets when
170                                           /// the fixed memory window represents interleaved memory. X is the
171                                           /// target index from 0.
172                                           ///
173                                           /// ``size=size`` sets the size of the CFMW. This must be a multiple of
174                                           /// 256MiB. The region will be aligned to 256MiB but the location is
175                                           /// platform and configuration dependent.
176                                           ///
177                                           /// ``interleave-granularity=granularity`` sets the granularity of
178                                           /// interleave. Default 256 (bytes). Only 256, 512, 1k, 2k,
179                                           /// 4k, 8k and 16k granularities supported.
180                                         // TOOD  cxl_fmw: Option<CxlFmw>,
181
182                                           /// Define cache properties for SMP system.
183                                           ///
184                                           /// ``cache=cachename`` specifies the cache that the properties will be
185                                           /// applied on. This field is the combination of cache level and cache
186                                           /// type. It supports ``l1d`` (L1 data cache), ``l1i`` (L1 instruction
187                                           /// cache), ``l2`` (L2 unified cache) and ``l3`` (L3 unified cache).
188                                           ///
189                                           /// ``topology=topologylevel`` sets the cache topology level. It accepts
190                                           /// CPU topology levels including ``core``, ``module``, ``cluster``, ``die``,
191                                           /// ``socket``, ``book``, ``drawer`` and a special value ``default``. If
192                                           /// ``default`` is set, then the cache topology will follow the architecture's
193                                           /// default cache topology model. If another topology level is set, the cache
194                                           /// will be shared at corresponding CPU topology level. For example,
195                                           /// ``topology=core`` makes the cache shared by all threads within a core.
196                                           /// The omitting cache will default to using the ``default`` level.
197                                           ///
198                                           /// The default cache topology model for an i386 PC machine is as follows:
199                                           /// ``l1d``, ``l1i``, and ``l2`` caches are per ``core``, while the ``l3``
200                                           /// cache is per ``die``.
201                                           */
202                                         // TODO   smp_cache: Option<Vec<SmpCache>>,
203}
204
205#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Builder, Arbitrary)]
206pub struct MachineX86_64 {
207    /// The x86_64-specific `-machine` payload.
208    pub m: Machine<MachineTypeX86_64>,
209}
210
211impl ToCommand for MachineX86_64 {
212    fn command(&self) -> String {
213        ARG_MACHINE.to_string()
214    }
215
216    fn to_args(&self) -> Vec<String> {
217        let mut args = vec![self.m.machine_type.to_arg().to_string()];
218
219        if let Some(accels) = &self.m.accel {
220            let accel_strs: Vec<&str> = accels.iter().map(|a| a.to_arg()).collect();
221            args.push(format!("{}{}", KEY_ACCEL, accel_strs.join(":")));
222        }
223        qao!(&self.m.vmport, args, KEY_VMPORT);
224        qao!(&self.m.dump_guest_core, args, KEY_DUMP_GUEST_CORE);
225        qao!(&self.m.mem_merge, args, KEY_MEM_MERGE);
226        qao!(&self.m.aes_key_wrap, args, KEY_AES_KEY_WRAP);
227        qao!(&self.m.dea_key_wrap, args, KEY_DEA_KEY_WRAP);
228        qao!(&self.m.nvdimm, args, KEY_NVDIMM);
229        if let Some(memory_encryption) = &self.m.memory_encryption {
230            args.push(format!("{}{}", KEY_MEMORY_ENCRYPTION, memory_encryption.as_ref()));
231        }
232        qao!(&self.m.hmat, args, KEY_HMAT);
233        qao!(&self.m.aux_ram_share, args, KEY_AUX_RAM_SHARE);
234        if let Some(memory_backend) = &self.m.memory_backend {
235            args.push(format!("{}{}", KEY_MEMORY_BACKEND, memory_backend.as_ref()));
236        }
237
238        /*
239        if let Some(cxl_fmw) = &self.m.cxl_fmw {
240            for (idx, target) in cxl_fmw.targets.iter().enumerate() {
241                args.push(format!("cxl-fmw.0.targets.{}={}", idx, target));
242            }
243            args.push(format!("cxl-fmw.0.size={}", cxl_fmw.size));
244            if let Some(granularity) = &cxl_fmw.interleave_granularity {
245                args.push(format!("cxl-fmw.0.interleave-granularity={}", granularity.to_arg()));
246            }
247        }
248        if let Some(smp_caches) = &self.m.smp_cache {
249            for (idx, smp_cache) in smp_caches.iter().enumerate() {
250                args.push(format!("smp-cache.{}.cache={}", idx, smp_cache.cache));
251                args.push(format!("smp-cache.{}.topology={}", idx, smp_cache.topology));
252            }
253        }
254         */
255        vec![args.join(DELIM_COMMA)]
256    }
257}
258
259impl FromStr for MachineX86_64 {
260    type Err = ShellStringError;
261
262    fn from_str(s: &str) -> Result<Self, Self::Err> {
263        parse_machine_x86_64(s).map_err(ShellStringError::new)
264    }
265}
266
267fn parse_machine_x86_64(s: &str) -> Result<MachineX86_64, String> {
268    let mut parts = s.split(DELIM_COMMA);
269    let first = parts.next().ok_or_else(|| "empty machine argument".to_string())?;
270
271    let mut machine_type = None;
272    let mut accel = None;
273    let mut vmport = None;
274    let mut dump_guest_core = None;
275    let mut mem_merge = None;
276    let mut aes_key_wrap = None;
277    let mut dea_key_wrap = None;
278    let mut nvdimm = None;
279    let mut memory_encryption = None;
280    let mut hmat = None;
281    let mut aux_ram_share = None;
282    let mut memory_backend = None;
283
284    if let Some(value) = first.strip_prefix(KEY_TYPE) {
285        machine_type = Some(parse_machine_type(value)?);
286    } else if first.contains('=') {
287        parse_machine_option(
288            first,
289            &mut machine_type,
290            &mut accel,
291            &mut vmport,
292            &mut dump_guest_core,
293            &mut mem_merge,
294            &mut aes_key_wrap,
295            &mut dea_key_wrap,
296            &mut nvdimm,
297            &mut memory_encryption,
298            &mut hmat,
299            &mut aux_ram_share,
300            &mut memory_backend,
301        )?;
302    } else {
303        machine_type = Some(parse_machine_type(first)?);
304    }
305
306    for part in parts {
307        parse_machine_option(
308            part,
309            &mut machine_type,
310            &mut accel,
311            &mut vmport,
312            &mut dump_guest_core,
313            &mut mem_merge,
314            &mut aes_key_wrap,
315            &mut dea_key_wrap,
316            &mut nvdimm,
317            &mut memory_encryption,
318            &mut hmat,
319            &mut aux_ram_share,
320            &mut memory_backend,
321        )?;
322    }
323
324    let machine_type = machine_type.ok_or_else(|| "machine type is required".to_string())?;
325
326    Ok(MachineX86_64 {
327        m: Machine {
328            machine_type,
329            accel,
330            vmport,
331            dump_guest_core,
332            mem_merge,
333            aes_key_wrap,
334            dea_key_wrap,
335            nvdimm,
336            memory_encryption,
337            hmat,
338            aux_ram_share,
339            memory_backend,
340        },
341    })
342}
343
344#[allow(clippy::too_many_arguments)]
345fn parse_machine_option(
346    part: &str,
347    machine_type: &mut Option<MachineTypeX86_64>,
348    accel: &mut Option<Vec<AccelType>>,
349    vmport: &mut Option<OnOffAuto>,
350    dump_guest_core: &mut Option<OnOffDefaultOn>,
351    mem_merge: &mut Option<OnOffDefaultOn>,
352    aes_key_wrap: &mut Option<OnOffDefaultOn>,
353    dea_key_wrap: &mut Option<OnOffDefaultOn>,
354    nvdimm: &mut Option<OnOffDefaultOff>,
355    memory_encryption: &mut Option<ShellString>,
356    hmat: &mut Option<OnOffDefaultOff>,
357    aux_ram_share: &mut Option<OnOffDefaultOff>,
358    memory_backend: &mut Option<ShellString>,
359) -> Result<(), String> {
360    let (key, value) = part.split_once('=').ok_or_else(|| format!("invalid machine option: {part}"))?;
361    match key {
362        "type" => *machine_type = Some(parse_machine_type(value)?),
363        "accel" => {
364            let accels = value
365                .split(DELIM_COLON)
366                .map(|v| v.parse::<AccelType>().map_err(|_| format!("invalid accel value: {v}")))
367                .collect::<Result<Vec<_>, _>>()?;
368            *accel = Some(accels);
369        }
370        "vmport" => *vmport = Some(value.parse::<OnOffAuto>().map_err(|_| format!("invalid vmport value: {value}"))?),
371        "dump-guest-core" => *dump_guest_core = Some(value.parse::<OnOffDefaultOn>().map_err(|_| format!("invalid dump-guest-core value: {value}"))?),
372        "mem-merge" => *mem_merge = Some(value.parse::<OnOffDefaultOn>().map_err(|_| format!("invalid mem-merge value: {value}"))?),
373        "aes-key-wrap" => *aes_key_wrap = Some(value.parse::<OnOffDefaultOn>().map_err(|_| format!("invalid aes-key-wrap value: {value}"))?),
374        "dea-key-wrap" => *dea_key_wrap = Some(value.parse::<OnOffDefaultOn>().map_err(|_| format!("invalid dea-key-wrap value: {value}"))?),
375        "nvdimm" => *nvdimm = Some(value.parse::<OnOffDefaultOff>().map_err(|_| format!("invalid nvdimm value: {value}"))?),
376        "memory-encryption" => *memory_encryption = Some(ShellString::new(value)),
377        "hmat" => *hmat = Some(value.parse::<OnOffDefaultOff>().map_err(|_| format!("invalid hmat value: {value}"))?),
378        "aux-ram-share" => *aux_ram_share = Some(value.parse::<OnOffDefaultOff>().map_err(|_| format!("invalid aux-ram-share value: {value}"))?),
379        "memory-backend" => *memory_backend = Some(ShellString::new(value)),
380        other => return Err(format!("unsupported machine option: {other}")),
381    }
382
383    Ok(())
384}
385
386fn parse_machine_type(value: &str) -> Result<MachineTypeX86_64, String> {
387    value.parse::<MachineTypeX86_64>()
388}
389
390#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Builder, Arbitrary)]
391pub struct MachineAarch64 {
392    pub m: Machine<MachineTypeAarch64>,
393}
394impl ToCommand for MachineAarch64 {
395    fn command(&self) -> String {
396        ARG_MACHINE.to_string()
397    }
398
399    fn to_args(&self) -> Vec<String> {
400        let mut args = vec![self.m.machine_type.to_arg().to_string()];
401
402        if let Some(accels) = &self.m.accel {
403            let accel_strs: Vec<&str> = accels.iter().map(|a| a.to_arg()).collect();
404            args.push(format!("{}{}", KEY_ACCEL, accel_strs.join(":")));
405        }
406        qao!(&self.m.dump_guest_core, args, KEY_DUMP_GUEST_CORE);
407        qao!(&self.m.mem_merge, args, KEY_MEM_MERGE);
408        qao!(&self.m.nvdimm, args, KEY_NVDIMM);
409        if let Some(memory_backend) = &self.m.memory_backend {
410            args.push(format!("{}{}", KEY_MEMORY_BACKEND, memory_backend.as_ref()));
411        }
412
413        vec![args.join(DELIM_COMMA)]
414    }
415}
416impl FromStr for MachineAarch64 {
417    type Err = ShellStringError;
418
419    fn from_str(s: &str) -> Result<Self, Self::Err> {
420        parse_machine_aarch64(s).map_err(ShellStringError::new)
421    }
422}
423
424fn parse_machine_aarch64(s: &str) -> Result<MachineAarch64, String> {
425    let mut parts = s.split(DELIM_COMMA);
426    let first = parts.next().ok_or_else(|| "empty machine argument".to_string())?;
427
428    let mut machine_type = None;
429    let mut accel = None;
430    let mut dump_guest_core = None;
431    let mut mem_merge = None;
432    let mut nvdimm = None;
433    let mut memory_backend = None;
434
435    if let Some(value) = first.strip_prefix(KEY_TYPE) {
436        machine_type = Some(parse_machine_type_aarch64(value)?);
437    } else if first.contains('=') {
438        parse_machine_aarch64_option(first, &mut machine_type, &mut accel, &mut dump_guest_core, &mut mem_merge, &mut nvdimm, &mut memory_backend)?;
439    } else {
440        machine_type = Some(parse_machine_type_aarch64(first)?);
441    }
442
443    for part in parts {
444        parse_machine_aarch64_option(part, &mut machine_type, &mut accel, &mut dump_guest_core, &mut mem_merge, &mut nvdimm, &mut memory_backend)?;
445    }
446
447    let machine_type = machine_type.ok_or_else(|| "machine type is required".to_string())?;
448
449    Ok(MachineAarch64 {
450        m: Machine {
451            machine_type,
452            accel,
453            vmport: None,
454            dump_guest_core,
455            mem_merge,
456            aes_key_wrap: None,
457            dea_key_wrap: None,
458            nvdimm,
459            memory_encryption: None,
460            hmat: None,
461            aux_ram_share: None,
462            memory_backend,
463        },
464    })
465}
466
467fn parse_machine_aarch64_option(
468    part: &str,
469    machine_type: &mut Option<MachineTypeAarch64>,
470    accel: &mut Option<Vec<AccelType>>,
471    dump_guest_core: &mut Option<OnOffDefaultOn>,
472    mem_merge: &mut Option<OnOffDefaultOn>,
473    nvdimm: &mut Option<OnOffDefaultOff>,
474    memory_backend: &mut Option<ShellString>,
475) -> Result<(), String> {
476    let (key, value) = part.split_once('=').ok_or_else(|| format!("invalid machine option: {part}"))?;
477    match key {
478        "type" => *machine_type = Some(parse_machine_type_aarch64(value)?),
479        "accel" => {
480            let accels = value
481                .split(DELIM_COLON)
482                .map(|v| v.parse::<AccelType>().map_err(|_| format!("invalid accel value: {v}")))
483                .collect::<Result<Vec<_>, _>>()?;
484            *accel = Some(accels);
485        }
486        "dump-guest-core" => *dump_guest_core = Some(value.parse::<OnOffDefaultOn>().map_err(|_| format!("invalid dump-guest-core value: {value}"))?),
487        "mem-merge" => *mem_merge = Some(value.parse::<OnOffDefaultOn>().map_err(|_| format!("invalid mem-merge value: {value}"))?),
488        "nvdimm" => *nvdimm = Some(value.parse::<OnOffDefaultOff>().map_err(|_| format!("invalid nvdimm value: {value}"))?),
489        "memory-backend" => *memory_backend = Some(ShellString::new(value)),
490        other => return Err(format!("unsupported aarch64 machine option: {other}")),
491    }
492
493    Ok(())
494}
495
496fn parse_machine_type_aarch64(value: &str) -> Result<MachineTypeAarch64, String> {
497    value.parse::<MachineTypeAarch64>().map_err(|_| format!("{value} is not a supported aarch64 machine type"))
498}