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