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#[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#[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#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Default, Builder, Arbitrary)]
63pub struct SmpCache {
64 cache: String,
65 topology: String,
66}
67
68#[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#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Builder, Arbitrary)]
97pub struct Machine<T> {
98 machine_type: T,
100
101 accel: Option<Vec<AccelType>>,
107
108 vmport: Option<OnOffAuto>,
112
113 dump_guest_core: Option<OnOffDefaultOn>,
115
116 mem_merge: Option<OnOffDefaultOn>,
120
121 aes_key_wrap: Option<OnOffDefaultOn>,
126
127 dea_key_wrap: Option<OnOffDefaultOn>,
132
133 nvdimm: Option<OnOffDefaultOff>,
135
136 memory_encryption: Option<ShellString>,
138
139 hmat: Option<OnOffDefaultOff>,
142
143 aux_ram_share: Option<OnOffDefaultOff>,
150
151 memory_backend: Option<ShellString>,
154
155 }
204
205#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Builder, Arbitrary)]
206pub struct MachineX86_64 {
207 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 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}