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_CONFIDENTIAL_GUEST_SUPPORT: &str = "confidential-guest-support=";
24const KEY_HMAT: &str = "hmat=";
25const KEY_SPCR: &str = "spcr=";
26const KEY_AUX_RAM_SHARE: &str = "aux-ram-share=";
27const KEY_MEMORY_BACKEND: &str = "memory-backend=";
28const KEY_IGVM_CFG: &str = "igvm-cfg=";
29
30/// Supported `interleave-granularity=` values for `cxl-fmw`.
31#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Default, Arbitrary)]
32pub enum Granularity {
33    #[default]
34    G256,
35    G512,
36    G1k,
37    G2k,
38    G4k,
39    G8k,
40    G16k,
41}
42
43impl ToArg for Granularity {
44    fn to_arg(&self) -> &str {
45        match self {
46            Granularity::G256 => "256",
47            Granularity::G512 => "512",
48            Granularity::G1k => "1k",
49            Granularity::G2k => "2k",
50            Granularity::G4k => "4k",
51            Granularity::G8k => "8k",
52            Granularity::G16k => "16k",
53        }
54    }
55}
56
57impl FromStr for Granularity {
58    type Err = String;
59
60    fn from_str(s: &str) -> Result<Self, Self::Err> {
61        match s {
62            "256" => Ok(Self::G256),
63            "512" => Ok(Self::G512),
64            "1k" => Ok(Self::G1k),
65            "2k" => Ok(Self::G2k),
66            "4k" => Ok(Self::G4k),
67            "8k" => Ok(Self::G8k),
68            "16k" => Ok(Self::G16k),
69            other => Err(format!("invalid cxl-fmw interleave-granularity: {other}")),
70        }
71    }
72}
73
74/// A CXL fixed memory window definition for `-machine`.
75#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Default, Builder, Arbitrary)]
76pub struct CxlFmw {
77    targets: Vec<String>,
78    size: String,
79    interleave_granularity: Option<Granularity>,
80}
81
82/// Cache topology properties for `-machine smp-cache.*`.
83#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Default, Builder, Arbitrary)]
84pub struct SmpCache {
85    cache: String,
86    topology: String,
87}
88
89/// An SGX EPC section definition for `-machine sgx-epc.N.*`.
90#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Builder, Arbitrary)]
91pub struct SgxEpc {
92    memdev: ShellString,
93    node: usize,
94}
95
96/// Architecture-specific machine types accepted by this crate.
97#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Arbitrary)]
98pub enum MachineType {
99    X86_64(MachineTypeX86_64),
100    Aarch(MachineTypeAarch64),
101}
102
103impl ToArg for MachineType {
104    fn to_arg(&self) -> &str {
105        match self {
106            MachineType::X86_64(mt) => mt.to_arg(),
107            MachineType::Aarch(mt) => mt.to_arg(),
108        }
109    }
110}
111/// Select the emulated machine by name. Use ``-machine help`` to list
112/// available machines.
113///
114/// For architectures which aim to support live migration compatibility
115/// across releases, each release will introduce a new versioned machine
116/// type. For example, the 2.8.0 release introduced machine types
117/// "pc-i440fx-2.8" and "pc-q35-2.8" for the x86\_64/i686 architectures.
118///
119/// To allow live migration of guests from QEMU version 2.8.0, to QEMU
120/// version 2.9.0, the 2.9.0 version must support the "pc-i440fx-2.8"
121/// and "pc-q35-2.8" machines too. To allow users live migrating VMs to
122/// skip multiple intermediate releases when upgrading, new releases of
123/// QEMU will support machine types from many previous versions.
124#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Builder, Arbitrary)]
125pub struct Machine<T> {
126    /// The QEMU machine type name.
127    machine_type: T,
128
129    /// This is used to enable an accelerator. Depending on the target
130    /// architecture, kvm, xen, hvf, nvmm, whpx or tcg can be available.
131    /// By default, tcg is used. If there is more than one accelerator
132    /// specified, the next one is used if the previous one fails to
133    /// initialize.
134    accel: Option<Vec<AccelType>>,
135
136    /// Enables emulation of VMWare IO port, for vmmouse etc. auto says
137    //  to select the value based on accel and i8042. For accel=xen or
138    //  i8042=off the default is off otherwise the default is on.
139    vmport: Option<OnOffAuto>,
140
141    /// Include guest memory in a core dump. The default is on.
142    dump_guest_core: Option<OnOffDefaultOn>,
143
144    /// Enables or disables memory merge support. This feature, when
145    //  supported by the host, de-duplicates identical memory pages
146    //  among VMs instances (enabled by default).
147    mem_merge: Option<OnOffDefaultOn>,
148
149    /// Enables or disables AES key wrapping support on s390-ccw hosts.
150    /// This feature controls whether AES wrapping keys will be created
151    /// to allow execution of AES cryptographic functions. The default
152    /// is on.
153    aes_key_wrap: Option<OnOffDefaultOn>,
154
155    /// Enables or disables DEA key wrapping support on s390-ccw hosts.
156    /// This feature controls whether DEA wrapping keys will be created
157    /// to allow execution of DEA cryptographic functions. The default
158    /// is on.
159    dea_key_wrap: Option<OnOffDefaultOn>,
160
161    /// Enables or disables NVDIMM support. The default is off.
162    nvdimm: Option<OnOffDefaultOff>,
163
164    /// Memory encryption object to use. The default is none.
165    memory_encryption: Option<ShellString>,
166
167    /// Confidential guest support object to use. The default is none.
168    confidential_guest_support: Option<ShellString>,
169
170    /// Enables or disables ACPI Heterogeneous Memory Attribute Table
171    /// (HMAT) support. The default is off.
172    hmat: Option<OnOffDefaultOff>,
173
174    /// Enables or disables ACPI Serial Port Console Redirection Table
175    /// (SPCR) support. The default is on.
176    spcr: Option<OnOffDefaultOn>,
177
178    /// Allocate auxiliary guest RAM as an anonymous file that is
179    /// shareable with an external process.  This option applies to
180    /// memory allocated as a side effect of creating various devices.
181    /// It does not apply to memory-backend-objects, whether explicitly
182    /// specified on the command line, or implicitly created by the -m
183    /// command line option.  The default is off.
184    aux_ram_share: Option<OnOffDefaultOff>,
185
186    /// An alternative to legacy ``-mem-path`` and ``mem-prealloc`` options.
187    /// Allows to use a memory backend as main RAM.
188    memory_backend: Option<ShellString>,
189
190    /// CXL fixed memory windows.
191    cxl_fmw: Option<Vec<CxlFmw>>,
192
193    /// IGVM configuration object to use for initial guest state.
194    igvm_cfg: Option<ShellString>,
195
196    /// SGX EPC sections.
197    sgx_epc: Option<Vec<SgxEpc>>,
198}
199
200#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Builder, Arbitrary)]
201pub struct MachineX86_64 {
202    /// The x86_64-specific `-machine` payload.
203    pub m: Machine<MachineTypeX86_64>,
204}
205
206impl ToCommand for MachineX86_64 {
207    fn command(&self) -> String {
208        ARG_MACHINE.to_string()
209    }
210
211    fn to_args(&self) -> Vec<String> {
212        let mut args = vec![self.m.machine_type.to_arg().to_string()];
213
214        if let Some(accels) = &self.m.accel {
215            let accel_strs: Vec<&str> = accels.iter().map(|a| a.to_arg()).collect();
216            args.push(format!("{}{}", KEY_ACCEL, accel_strs.join(":")));
217        }
218        qao!(&self.m.vmport, args, KEY_VMPORT);
219        qao!(&self.m.dump_guest_core, args, KEY_DUMP_GUEST_CORE);
220        qao!(&self.m.mem_merge, args, KEY_MEM_MERGE);
221        qao!(&self.m.aes_key_wrap, args, KEY_AES_KEY_WRAP);
222        qao!(&self.m.dea_key_wrap, args, KEY_DEA_KEY_WRAP);
223        qao!(&self.m.nvdimm, args, KEY_NVDIMM);
224        if let Some(memory_encryption) = &self.m.memory_encryption {
225            args.push(format!("{}{}", KEY_MEMORY_ENCRYPTION, memory_encryption.as_ref()));
226        }
227        if let Some(confidential_guest_support) = &self.m.confidential_guest_support {
228            args.push(format!("{}{}", KEY_CONFIDENTIAL_GUEST_SUPPORT, confidential_guest_support.as_ref()));
229        }
230        qao!(&self.m.hmat, args, KEY_HMAT);
231        qao!(&self.m.spcr, args, KEY_SPCR);
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        push_cxl_fmw_args(&mut args, &self.m.cxl_fmw);
237        if let Some(igvm_cfg) = &self.m.igvm_cfg {
238            args.push(format!("{}{}", KEY_IGVM_CFG, igvm_cfg.as_ref()));
239        }
240        if let Some(sgx_epcs) = &self.m.sgx_epc {
241            for (idx, sgx_epc) in sgx_epcs.iter().enumerate() {
242                args.push(format!("sgx-epc.{}.memdev={}", idx, sgx_epc.memdev.as_ref()));
243                args.push(format!("sgx-epc.{}.node={}", idx, sgx_epc.node));
244            }
245        }
246
247        vec![args.join(DELIM_COMMA)]
248    }
249}
250
251impl FromStr for MachineX86_64 {
252    type Err = ShellStringError;
253
254    fn from_str(s: &str) -> Result<Self, Self::Err> {
255        parse_machine_x86_64(s).map_err(ShellStringError::new)
256    }
257}
258
259fn parse_machine_x86_64(s: &str) -> Result<MachineX86_64, String> {
260    let mut parts = s.split(DELIM_COMMA);
261    let first = parts.next().ok_or_else(|| "empty machine argument".to_string())?;
262
263    let mut machine_type = None;
264    let mut accel = None;
265    let mut vmport = None;
266    let mut dump_guest_core = None;
267    let mut mem_merge = None;
268    let mut aes_key_wrap = None;
269    let mut dea_key_wrap = None;
270    let mut nvdimm = None;
271    let mut memory_encryption = None;
272    let mut confidential_guest_support = None;
273    let mut hmat = None;
274    let mut spcr = None;
275    let mut aux_ram_share = None;
276    let mut memory_backend = None;
277    let mut cxl_fmw = std::collections::BTreeMap::<usize, CxlFmwParts>::new();
278    let mut igvm_cfg = None;
279    let mut sgx_epc = std::collections::BTreeMap::<usize, (Option<ShellString>, Option<usize>)>::new();
280
281    if let Some(value) = first.strip_prefix(KEY_TYPE) {
282        machine_type = Some(parse_machine_type(value)?);
283    } else if first.contains('=') {
284        parse_machine_option(
285            first,
286            &mut machine_type,
287            &mut accel,
288            &mut vmport,
289            &mut dump_guest_core,
290            &mut mem_merge,
291            &mut aes_key_wrap,
292            &mut dea_key_wrap,
293            &mut nvdimm,
294            &mut memory_encryption,
295            &mut confidential_guest_support,
296            &mut hmat,
297            &mut spcr,
298            &mut aux_ram_share,
299            &mut memory_backend,
300            &mut cxl_fmw,
301            &mut igvm_cfg,
302            &mut sgx_epc,
303        )?;
304    } else {
305        machine_type = Some(parse_machine_type(first)?);
306    }
307
308    for part in parts {
309        parse_machine_option(
310            part,
311            &mut machine_type,
312            &mut accel,
313            &mut vmport,
314            &mut dump_guest_core,
315            &mut mem_merge,
316            &mut aes_key_wrap,
317            &mut dea_key_wrap,
318            &mut nvdimm,
319            &mut memory_encryption,
320            &mut confidential_guest_support,
321            &mut hmat,
322            &mut spcr,
323            &mut aux_ram_share,
324            &mut memory_backend,
325            &mut cxl_fmw,
326            &mut igvm_cfg,
327            &mut sgx_epc,
328        )?;
329    }
330
331    let machine_type = machine_type.ok_or_else(|| "machine type is required".to_string())?;
332    let cxl_fmw = build_cxl_fmw(cxl_fmw)?;
333    let sgx_epc = build_sgx_epc(sgx_epc)?;
334
335    Ok(MachineX86_64 {
336        m: Machine {
337            machine_type,
338            accel,
339            vmport,
340            dump_guest_core,
341            mem_merge,
342            aes_key_wrap,
343            dea_key_wrap,
344            nvdimm,
345            memory_encryption,
346            confidential_guest_support,
347            hmat,
348            spcr,
349            aux_ram_share,
350            memory_backend,
351            cxl_fmw,
352            igvm_cfg,
353            sgx_epc,
354        },
355    })
356}
357
358#[allow(clippy::too_many_arguments)]
359fn parse_machine_option(
360    part: &str,
361    machine_type: &mut Option<MachineTypeX86_64>,
362    accel: &mut Option<Vec<AccelType>>,
363    vmport: &mut Option<OnOffAuto>,
364    dump_guest_core: &mut Option<OnOffDefaultOn>,
365    mem_merge: &mut Option<OnOffDefaultOn>,
366    aes_key_wrap: &mut Option<OnOffDefaultOn>,
367    dea_key_wrap: &mut Option<OnOffDefaultOn>,
368    nvdimm: &mut Option<OnOffDefaultOff>,
369    memory_encryption: &mut Option<ShellString>,
370    confidential_guest_support: &mut Option<ShellString>,
371    hmat: &mut Option<OnOffDefaultOff>,
372    spcr: &mut Option<OnOffDefaultOn>,
373    aux_ram_share: &mut Option<OnOffDefaultOff>,
374    memory_backend: &mut Option<ShellString>,
375    cxl_fmw: &mut std::collections::BTreeMap<usize, CxlFmwParts>,
376    igvm_cfg: &mut Option<ShellString>,
377    sgx_epc: &mut std::collections::BTreeMap<usize, (Option<ShellString>, Option<usize>)>,
378) -> Result<(), String> {
379    let (key, value) = part.split_once('=').ok_or_else(|| format!("invalid machine option: {part}"))?;
380    match key {
381        "type" => *machine_type = Some(parse_machine_type(value)?),
382        "accel" => {
383            let accels = value
384                .split(DELIM_COLON)
385                .map(|v| v.parse::<AccelType>().map_err(|_| format!("invalid accel value: {v}")))
386                .collect::<Result<Vec<_>, _>>()?;
387            *accel = Some(accels);
388        }
389        "vmport" => *vmport = Some(value.parse::<OnOffAuto>().map_err(|_| format!("invalid vmport value: {value}"))?),
390        "dump-guest-core" => *dump_guest_core = Some(value.parse::<OnOffDefaultOn>().map_err(|_| format!("invalid dump-guest-core value: {value}"))?),
391        "mem-merge" => *mem_merge = Some(value.parse::<OnOffDefaultOn>().map_err(|_| format!("invalid mem-merge value: {value}"))?),
392        "aes-key-wrap" => *aes_key_wrap = Some(value.parse::<OnOffDefaultOn>().map_err(|_| format!("invalid aes-key-wrap value: {value}"))?),
393        "dea-key-wrap" => *dea_key_wrap = Some(value.parse::<OnOffDefaultOn>().map_err(|_| format!("invalid dea-key-wrap value: {value}"))?),
394        "nvdimm" => *nvdimm = Some(value.parse::<OnOffDefaultOff>().map_err(|_| format!("invalid nvdimm value: {value}"))?),
395        "memory-encryption" => *memory_encryption = Some(ShellString::new(value)),
396        "confidential-guest-support" => *confidential_guest_support = Some(ShellString::new(value)),
397        "hmat" => *hmat = Some(value.parse::<OnOffDefaultOff>().map_err(|_| format!("invalid hmat value: {value}"))?),
398        "spcr" => *spcr = Some(value.parse::<OnOffDefaultOn>().map_err(|_| format!("invalid spcr value: {value}"))?),
399        "aux-ram-share" => *aux_ram_share = Some(value.parse::<OnOffDefaultOff>().map_err(|_| format!("invalid aux-ram-share value: {value}"))?),
400        "memory-backend" => *memory_backend = Some(ShellString::new(value)),
401        _ if key.starts_with("cxl-fmw.") => parse_cxl_fmw_option(key, value, cxl_fmw)?,
402        "igvm-cfg" => *igvm_cfg = Some(ShellString::new(value)),
403        _ if key.starts_with("sgx-epc.") => parse_sgx_epc_option(key, value, sgx_epc)?,
404        other => return Err(format!("unsupported machine option: {other}")),
405    }
406
407    Ok(())
408}
409
410fn parse_machine_type(value: &str) -> Result<MachineTypeX86_64, String> {
411    value.parse::<MachineTypeX86_64>()
412}
413
414#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Builder, Arbitrary)]
415pub struct MachineAarch64 {
416    pub m: Machine<MachineTypeAarch64>,
417}
418impl ToCommand for MachineAarch64 {
419    fn command(&self) -> String {
420        ARG_MACHINE.to_string()
421    }
422
423    fn to_args(&self) -> Vec<String> {
424        let mut args = vec![self.m.machine_type.to_arg().to_string()];
425
426        if let Some(accels) = &self.m.accel {
427            let accel_strs: Vec<&str> = accels.iter().map(|a| a.to_arg()).collect();
428            args.push(format!("{}{}", KEY_ACCEL, accel_strs.join(":")));
429        }
430        qao!(&self.m.dump_guest_core, args, KEY_DUMP_GUEST_CORE);
431        qao!(&self.m.mem_merge, args, KEY_MEM_MERGE);
432        qao!(&self.m.nvdimm, args, KEY_NVDIMM);
433        qao!(&self.m.spcr, args, KEY_SPCR);
434        if let Some(confidential_guest_support) = &self.m.confidential_guest_support {
435            args.push(format!("{}{}", KEY_CONFIDENTIAL_GUEST_SUPPORT, confidential_guest_support.as_ref()));
436        }
437        if let Some(memory_backend) = &self.m.memory_backend {
438            args.push(format!("{}{}", KEY_MEMORY_BACKEND, memory_backend.as_ref()));
439        }
440        push_cxl_fmw_args(&mut args, &self.m.cxl_fmw);
441        if let Some(igvm_cfg) = &self.m.igvm_cfg {
442            args.push(format!("{}{}", KEY_IGVM_CFG, igvm_cfg.as_ref()));
443        }
444        if let Some(sgx_epcs) = &self.m.sgx_epc {
445            for (idx, sgx_epc) in sgx_epcs.iter().enumerate() {
446                args.push(format!("sgx-epc.{}.memdev={}", idx, sgx_epc.memdev.as_ref()));
447                args.push(format!("sgx-epc.{}.node={}", idx, sgx_epc.node));
448            }
449        }
450
451        vec![args.join(DELIM_COMMA)]
452    }
453}
454impl FromStr for MachineAarch64 {
455    type Err = ShellStringError;
456
457    fn from_str(s: &str) -> Result<Self, Self::Err> {
458        parse_machine_aarch64(s).map_err(ShellStringError::new)
459    }
460}
461
462fn parse_machine_aarch64(s: &str) -> Result<MachineAarch64, String> {
463    let mut parts = s.split(DELIM_COMMA);
464    let first = parts.next().ok_or_else(|| "empty machine argument".to_string())?;
465
466    let mut machine_type = None;
467    let mut accel = None;
468    let mut dump_guest_core = None;
469    let mut mem_merge = None;
470    let mut nvdimm = None;
471    let mut spcr = None;
472    let mut confidential_guest_support = None;
473    let mut memory_backend = None;
474    let mut cxl_fmw = std::collections::BTreeMap::<usize, CxlFmwParts>::new();
475    let mut igvm_cfg = None;
476    let mut sgx_epc = std::collections::BTreeMap::<usize, (Option<ShellString>, Option<usize>)>::new();
477
478    if let Some(value) = first.strip_prefix(KEY_TYPE) {
479        machine_type = Some(parse_machine_type_aarch64(value)?);
480    } else if first.contains('=') {
481        parse_machine_aarch64_option(
482            first,
483            &mut machine_type,
484            &mut accel,
485            &mut dump_guest_core,
486            &mut mem_merge,
487            &mut nvdimm,
488            &mut spcr,
489            &mut confidential_guest_support,
490            &mut memory_backend,
491            &mut cxl_fmw,
492            &mut igvm_cfg,
493            &mut sgx_epc,
494        )?;
495    } else {
496        machine_type = Some(parse_machine_type_aarch64(first)?);
497    }
498
499    for part in parts {
500        parse_machine_aarch64_option(
501            part,
502            &mut machine_type,
503            &mut accel,
504            &mut dump_guest_core,
505            &mut mem_merge,
506            &mut nvdimm,
507            &mut spcr,
508            &mut confidential_guest_support,
509            &mut memory_backend,
510            &mut cxl_fmw,
511            &mut igvm_cfg,
512            &mut sgx_epc,
513        )?;
514    }
515
516    let machine_type = machine_type.ok_or_else(|| "machine type is required".to_string())?;
517    let cxl_fmw = build_cxl_fmw(cxl_fmw)?;
518    let sgx_epc = build_sgx_epc(sgx_epc)?;
519
520    Ok(MachineAarch64 {
521        m: Machine {
522            machine_type,
523            accel,
524            vmport: None,
525            dump_guest_core,
526            mem_merge,
527            aes_key_wrap: None,
528            dea_key_wrap: None,
529            nvdimm,
530            memory_encryption: None,
531            confidential_guest_support,
532            hmat: None,
533            spcr,
534            aux_ram_share: None,
535            memory_backend,
536            cxl_fmw,
537            igvm_cfg,
538            sgx_epc,
539        },
540    })
541}
542
543#[allow(clippy::too_many_arguments)]
544fn parse_machine_aarch64_option(
545    part: &str,
546    machine_type: &mut Option<MachineTypeAarch64>,
547    accel: &mut Option<Vec<AccelType>>,
548    dump_guest_core: &mut Option<OnOffDefaultOn>,
549    mem_merge: &mut Option<OnOffDefaultOn>,
550    nvdimm: &mut Option<OnOffDefaultOff>,
551    spcr: &mut Option<OnOffDefaultOn>,
552    confidential_guest_support: &mut Option<ShellString>,
553    memory_backend: &mut Option<ShellString>,
554    cxl_fmw: &mut std::collections::BTreeMap<usize, CxlFmwParts>,
555    igvm_cfg: &mut Option<ShellString>,
556    sgx_epc: &mut std::collections::BTreeMap<usize, (Option<ShellString>, Option<usize>)>,
557) -> Result<(), String> {
558    let (key, value) = part.split_once('=').ok_or_else(|| format!("invalid machine option: {part}"))?;
559    match key {
560        "type" => *machine_type = Some(parse_machine_type_aarch64(value)?),
561        "accel" => {
562            let accels = value
563                .split(DELIM_COLON)
564                .map(|v| v.parse::<AccelType>().map_err(|_| format!("invalid accel value: {v}")))
565                .collect::<Result<Vec<_>, _>>()?;
566            *accel = Some(accels);
567        }
568        "dump-guest-core" => *dump_guest_core = Some(value.parse::<OnOffDefaultOn>().map_err(|_| format!("invalid dump-guest-core value: {value}"))?),
569        "mem-merge" => *mem_merge = Some(value.parse::<OnOffDefaultOn>().map_err(|_| format!("invalid mem-merge value: {value}"))?),
570        "nvdimm" => *nvdimm = Some(value.parse::<OnOffDefaultOff>().map_err(|_| format!("invalid nvdimm value: {value}"))?),
571        "spcr" => *spcr = Some(value.parse::<OnOffDefaultOn>().map_err(|_| format!("invalid spcr value: {value}"))?),
572        "confidential-guest-support" => *confidential_guest_support = Some(ShellString::new(value)),
573        "memory-backend" => *memory_backend = Some(ShellString::new(value)),
574        _ if key.starts_with("cxl-fmw.") => parse_cxl_fmw_option(key, value, cxl_fmw)?,
575        "igvm-cfg" => *igvm_cfg = Some(ShellString::new(value)),
576        _ if key.starts_with("sgx-epc.") => parse_sgx_epc_option(key, value, sgx_epc)?,
577        other => return Err(format!("unsupported aarch64 machine option: {other}")),
578    }
579
580    Ok(())
581}
582
583#[derive(Debug, Default)]
584struct CxlFmwParts {
585    targets: std::collections::BTreeMap<usize, String>,
586    size: Option<String>,
587    interleave_granularity: Option<Granularity>,
588}
589
590fn push_cxl_fmw_args(args: &mut Vec<String>, cxl_fmw: &Option<Vec<CxlFmw>>) {
591    if let Some(cxl_fmws) = cxl_fmw {
592        for (idx, cxl_fmw) in cxl_fmws.iter().enumerate() {
593            for (target_idx, target) in cxl_fmw.targets.iter().enumerate() {
594                args.push(format!("cxl-fmw.{}.targets.{}={}", idx, target_idx, target));
595            }
596            args.push(format!("cxl-fmw.{}.size={}", idx, cxl_fmw.size));
597            if let Some(granularity) = &cxl_fmw.interleave_granularity {
598                args.push(format!("cxl-fmw.{}.interleave-granularity={}", idx, granularity.to_arg()));
599            }
600        }
601    }
602}
603
604fn parse_cxl_fmw_option(key: &str, value: &str, cxl_fmw: &mut std::collections::BTreeMap<usize, CxlFmwParts>) -> Result<(), String> {
605    let mut parts = key.split('.');
606    let prefix = parts.next();
607    let index = parts.next().ok_or_else(|| format!("invalid CXL FMW option: {key}"))?;
608    if prefix != Some("cxl-fmw") {
609        return Err(format!("invalid CXL FMW option: {key}"));
610    }
611
612    let index = index.parse::<usize>().map_err(|e| format!("invalid CXL FMW index: {e}"))?;
613    let entry = cxl_fmw.entry(index).or_default();
614
615    match parts.next() {
616        Some("targets") => {
617            let target_index = parts.next().ok_or_else(|| format!("invalid CXL FMW target option: {key}"))?;
618            if parts.next().is_some() {
619                return Err(format!("invalid CXL FMW target option: {key}"));
620            }
621            let target_index = target_index.parse::<usize>().map_err(|e| format!("invalid CXL FMW target index: {e}"))?;
622            entry.targets.insert(target_index, value.to_string());
623        }
624        Some("size") => {
625            if parts.next().is_some() {
626                return Err(format!("invalid CXL FMW size option: {key}"));
627            }
628            entry.size = Some(value.to_string());
629        }
630        Some("interleave-granularity") => {
631            if parts.next().is_some() {
632                return Err(format!("invalid CXL FMW interleave-granularity option: {key}"));
633            }
634            entry.interleave_granularity = Some(value.parse::<Granularity>()?);
635        }
636        Some(other) => return Err(format!("unsupported CXL FMW option: {other}")),
637        None => return Err(format!("invalid CXL FMW option: {key}")),
638    }
639
640    Ok(())
641}
642
643fn build_cxl_fmw(cxl_fmw: std::collections::BTreeMap<usize, CxlFmwParts>) -> Result<Option<Vec<CxlFmw>>, String> {
644    if cxl_fmw.is_empty() {
645        return Ok(None);
646    }
647
648    let mut windows = Vec::new();
649    for (index, parts) in cxl_fmw {
650        if parts.targets.is_empty() {
651            return Err(format!("cxl-fmw.{index} requires at least one target"));
652        }
653        windows.push(CxlFmw {
654            targets: parts.targets.into_values().collect(),
655            size: parts.size.ok_or_else(|| format!("cxl-fmw.{index} requires size"))?,
656            interleave_granularity: parts.interleave_granularity,
657        });
658    }
659    Ok(Some(windows))
660}
661
662fn parse_sgx_epc_option(key: &str, value: &str, sgx_epc: &mut std::collections::BTreeMap<usize, (Option<ShellString>, Option<usize>)>) -> Result<(), String> {
663    let mut parts = key.split('.');
664    let prefix = parts.next();
665    let index = parts.next().ok_or_else(|| format!("invalid SGX EPC option: {key}"))?;
666    let field = parts.next().ok_or_else(|| format!("invalid SGX EPC option: {key}"))?;
667    if prefix != Some("sgx-epc") || parts.next().is_some() {
668        return Err(format!("invalid SGX EPC option: {key}"));
669    }
670
671    let index = index.parse::<usize>().map_err(|e| format!("invalid SGX EPC index: {e}"))?;
672    let entry = sgx_epc.entry(index).or_default();
673    match field {
674        "memdev" => entry.0 = Some(ShellString::new(value)),
675        "node" => entry.1 = Some(value.parse::<usize>().map_err(|e| format!("invalid SGX EPC node: {e}"))?),
676        other => return Err(format!("unsupported SGX EPC option: {other}")),
677    }
678    Ok(())
679}
680
681fn build_sgx_epc(sgx_epc: std::collections::BTreeMap<usize, (Option<ShellString>, Option<usize>)>) -> Result<Option<Vec<SgxEpc>>, String> {
682    if sgx_epc.is_empty() {
683        return Ok(None);
684    }
685
686    let mut entries = Vec::new();
687    for (index, (memdev, node)) in sgx_epc {
688        entries.push(SgxEpc {
689            memdev: memdev.ok_or_else(|| format!("sgx-epc.{index} requires memdev"))?,
690            node: node.ok_or_else(|| format!("sgx-epc.{index} requires node"))?,
691        });
692    }
693    Ok(Some(entries))
694}
695
696fn parse_machine_type_aarch64(value: &str) -> Result<MachineTypeAarch64, String> {
697    value.parse::<MachineTypeAarch64>().map_err(|_| format!("{value} is not a supported aarch64 machine type"))
698}