xref: /cloud-hypervisor/vmm/src/config.rs (revision eea9bcea38e0c5649f444c829f3a4f9c22aa486c)
1 // Copyright © 2019 Intel Corporation
2 //
3 // SPDX-License-Identifier: Apache-2.0
4 //
5 
6 use clap::ArgMatches;
7 use net_util::MacAddr;
8 use option_parser::{
9     ByteSized, IntegerList, OptionParser, OptionParserError, StringList, Toggle, Tuple,
10 };
11 use serde::{Deserialize, Serialize};
12 use std::collections::{BTreeSet, HashMap};
13 use std::convert::From;
14 use std::fmt;
15 use std::net::Ipv4Addr;
16 use std::path::PathBuf;
17 use std::result;
18 use std::str::FromStr;
19 use thiserror::Error;
20 use virtio_devices::{RateLimiterConfig, TokenBucketConfig};
21 
22 pub const DEFAULT_VCPUS: u8 = 1;
23 pub const DEFAULT_MEMORY_MB: u64 = 512;
24 
25 // When booting with PVH boot the maximum physical addressable size
26 // is a 46 bit address space even when the host supports with 5-level
27 // paging.
28 pub const DEFAULT_MAX_PHYS_BITS: u8 = 46;
29 
30 pub const DEFAULT_RNG_SOURCE: &str = "/dev/urandom";
31 pub const DEFAULT_NUM_QUEUES_VUNET: usize = 2;
32 pub const DEFAULT_QUEUE_SIZE_VUNET: u16 = 256;
33 pub const DEFAULT_NUM_QUEUES_VUBLK: usize = 1;
34 pub const DEFAULT_QUEUE_SIZE_VUBLK: u16 = 128;
35 
36 pub const DEFAULT_NUM_PCI_SEGMENTS: u16 = 1;
37 const MAX_NUM_PCI_SEGMENTS: u16 = 16;
38 
39 /// Errors associated with VM configuration parameters.
40 #[derive(Debug, Error)]
41 pub enum Error {
42     /// Filesystem tag is missing
43     ParseFsTagMissing,
44     /// Filesystem socket is missing
45     ParseFsSockMissing,
46     /// Missing persistent memory file parameter.
47     ParsePmemFileMissing,
48     /// Missing vsock socket path parameter.
49     ParseVsockSockMissing,
50     /// Missing vsock cid parameter.
51     ParseVsockCidMissing,
52     /// Missing restore source_url parameter.
53     ParseRestoreSourceUrlMissing,
54     /// Error parsing CPU options
55     ParseCpus(OptionParserError),
56     /// Invalid CPU features
57     InvalidCpuFeatures(String),
58     /// Error parsing memory options
59     ParseMemory(OptionParserError),
60     /// Error parsing memory zone options
61     ParseMemoryZone(OptionParserError),
62     /// Missing 'id' from memory zone
63     ParseMemoryZoneIdMissing,
64     /// Error parsing disk options
65     ParseDisk(OptionParserError),
66     /// Error parsing network options
67     ParseNetwork(OptionParserError),
68     /// Error parsing RNG options
69     ParseRng(OptionParserError),
70     /// Error parsing balloon options
71     ParseBalloon(OptionParserError),
72     /// Error parsing filesystem parameters
73     ParseFileSystem(OptionParserError),
74     /// Error parsing persistent memory parameters
75     ParsePersistentMemory(OptionParserError),
76     /// Failed parsing console
77     ParseConsole(OptionParserError),
78     /// No mode given for console
79     ParseConsoleInvalidModeGiven,
80     /// Failed parsing device parameters
81     ParseDevice(OptionParserError),
82     /// Missing path from device,
83     ParseDevicePathMissing,
84     /// Failed parsing vsock parameters
85     ParseVsock(OptionParserError),
86     /// Failed parsing restore parameters
87     ParseRestore(OptionParserError),
88     /// Failed parsing SGX EPC parameters
89     #[cfg(target_arch = "x86_64")]
90     ParseSgxEpc(OptionParserError),
91     /// Missing 'id' from SGX EPC section
92     #[cfg(target_arch = "x86_64")]
93     ParseSgxEpcIdMissing,
94     /// Failed parsing NUMA parameters
95     ParseNuma(OptionParserError),
96     /// Failed validating configuration
97     Validation(ValidationError),
98     #[cfg(feature = "tdx")]
99     /// Failed parsing TDX config
100     ParseTdx(OptionParserError),
101     #[cfg(feature = "tdx")]
102     /// No TDX firmware
103     FirmwarePathMissing,
104     /// Failed parsing userspace device
105     ParseUserDevice(OptionParserError),
106     /// Missing socket for userspace device
107     ParseUserDeviceSocketMissing,
108     /// Failed parsing platform parameters
109     ParsePlatform(OptionParserError),
110     /// Failed parsing vDPA device
111     ParseVdpa(OptionParserError),
112     /// Missing path for vDPA device
113     ParseVdpaPathMissing,
114 }
115 
116 #[derive(Debug, PartialEq, Eq, Error)]
117 pub enum ValidationError {
118     /// Both console and serial are tty.
119     DoubleTtyMode,
120     /// No kernel specified
121     KernelMissing,
122     /// Missing file value for console
123     ConsoleFileMissing,
124     /// Max is less than boot
125     CpusMaxLowerThanBoot,
126     /// Both socket and path specified
127     DiskSocketAndPath,
128     /// Using vhost user requires shared memory
129     VhostUserRequiresSharedMemory,
130     /// No socket provided for vhost_use
131     VhostUserMissingSocket,
132     /// Trying to use IOMMU without PCI
133     IommuUnsupported,
134     /// Trying to use VFIO without PCI
135     VfioUnsupported,
136     /// CPU topology count doesn't match max
137     CpuTopologyCount,
138     /// One part of the CPU topology was zero
139     CpuTopologyZeroPart,
140     #[cfg(target_arch = "aarch64")]
141     /// Dies per package must be 1
142     CpuTopologyDiesPerPackage,
143     /// Virtio needs a min of 2 queues
144     VnetQueueLowerThan2,
145     /// The input queue number for virtio_net must match the number of input fds
146     VnetQueueFdMismatch,
147     /// Using reserved fd
148     VnetReservedFd,
149     /// Hugepages not turned on
150     HugePageSizeWithoutHugePages,
151     /// Huge page size is not power of 2
152     InvalidHugePageSize(u64),
153     /// CPU Hotplug is not permitted with TDX
154     #[cfg(feature = "tdx")]
155     TdxNoCpuHotplug,
156     /// Missing firmware for TDX
157     #[cfg(feature = "tdx")]
158     TdxFirmwareMissing,
159     /// Insuffient vCPUs for queues
160     TooManyQueues,
161     /// Need shared memory for vfio-user
162     UserDevicesRequireSharedMemory,
163     /// Memory zone is reused across NUMA nodes
164     MemoryZoneReused(String, u32, u32),
165     /// Invalid number of PCI segments
166     InvalidNumPciSegments(u16),
167     /// Invalid PCI segment id
168     InvalidPciSegment(u16),
169     /// Balloon too big
170     BalloonLargerThanRam(u64, u64),
171     /// On a IOMMU segment but not behind IOMMU
172     OnIommuSegment(u16),
173     // On a IOMMU segment but IOMMU not suported
174     IommuNotSupportedOnSegment(u16),
175     // Identifier is not unique
176     IdentifierNotUnique(String),
177     /// Invalid identifier
178     InvalidIdentifier(String),
179     /// Placing the device behind a virtual IOMMU is not supported
180     IommuNotSupported,
181     /// Duplicated device path (device added twice)
182     DuplicateDevicePath(String),
183     /// Provided MTU is lower than what the VIRTIO specification expects
184     InvalidMtu(u16),
185 }
186 
187 type ValidationResult<T> = std::result::Result<T, ValidationError>;
188 
189 impl fmt::Display for ValidationError {
190     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
191         use self::ValidationError::*;
192         match self {
193             DoubleTtyMode => write!(f, "Console mode tty specified for both serial and console"),
194             KernelMissing => write!(f, "No kernel specified"),
195             ConsoleFileMissing => write!(f, "Path missing when using file console mode"),
196             CpusMaxLowerThanBoot => write!(f, "Max CPUs lower than boot CPUs"),
197             DiskSocketAndPath => write!(f, "Disk path and vhost socket both provided"),
198             VhostUserRequiresSharedMemory => {
199                 write!(f, "Using vhost-user requires using shared memory")
200             }
201             VhostUserMissingSocket => write!(f, "No socket provided when using vhost-user"),
202             IommuUnsupported => write!(f, "Using an IOMMU without PCI support is unsupported"),
203             VfioUnsupported => write!(f, "Using VFIO without PCI support is unsupported"),
204             CpuTopologyZeroPart => write!(f, "No part of the CPU topology can be zero"),
205             CpuTopologyCount => write!(
206                 f,
207                 "Product of CPU topology parts does not match maximum vCPUs"
208             ),
209             #[cfg(target_arch = "aarch64")]
210             CpuTopologyDiesPerPackage => write!(f, "Dies per package must be 1"),
211             VnetQueueLowerThan2 => write!(f, "Number of queues to virtio_net less than 2"),
212             VnetQueueFdMismatch => write!(
213                 f,
214                 "Number of queues to virtio_net does not match the number of input FDs"
215             ),
216             VnetReservedFd => write!(f, "Reserved fd number (<= 2)"),
217             HugePageSizeWithoutHugePages => {
218                 write!(f, "Huge page size specified but huge pages not enabled")
219             }
220             InvalidHugePageSize(s) => {
221                 write!(f, "Huge page size is not power of 2: {}", s)
222             }
223             #[cfg(feature = "tdx")]
224             TdxNoCpuHotplug => {
225                 write!(f, "CPU hotplug is not permitted with TDX")
226             }
227             #[cfg(feature = "tdx")]
228             TdxFirmwareMissing => {
229                 write!(f, "No TDX firmware specified")
230             }
231             TooManyQueues => {
232                 write!(f, "Number of vCPUs is insufficient for number of queues")
233             }
234             UserDevicesRequireSharedMemory => {
235                 write!(f, "Using user devices requires using shared memory")
236             }
237             MemoryZoneReused(s, u1, u2) => {
238                 write!(
239                     f,
240                     "Memory zone: {} belongs to multiple NUMA nodes {} and {}",
241                     s, u1, u2
242                 )
243             }
244             InvalidNumPciSegments(n) => {
245                 write!(
246                     f,
247                     "Number of PCI segments ({}) not in range of 1 to {}",
248                     n, MAX_NUM_PCI_SEGMENTS
249                 )
250             }
251             InvalidPciSegment(pci_segment) => {
252                 write!(f, "Invalid PCI segment id: {}", pci_segment)
253             }
254             BalloonLargerThanRam(balloon_size, ram_size) => {
255                 write!(
256                     f,
257                     "Ballon size ({}) greater than RAM ({})",
258                     balloon_size, ram_size
259                 )
260             }
261             OnIommuSegment(pci_segment) => {
262                 write!(
263                     f,
264                     "Device is on an IOMMU PCI segment ({}) but not placed behind IOMMU",
265                     pci_segment
266                 )
267             }
268             IommuNotSupportedOnSegment(pci_segment) => {
269                 write!(
270                     f,
271                     "Device is on an IOMMU PCI segment ({}) but does not support being placed behind IOMMU",
272                     pci_segment
273                 )
274             }
275             IdentifierNotUnique(s) => {
276                 write!(f, "Identifier {} is not unique", s)
277             }
278             InvalidIdentifier(s) => {
279                 write!(f, "Identifier {} is invalid", s)
280             }
281             IommuNotSupported => {
282                 write!(f, "Device does not support being placed behind IOMMU")
283             }
284             DuplicateDevicePath(p) => write!(f, "Duplicated device path: {}", p),
285             &InvalidMtu(mtu) => {
286                 write!(
287                     f,
288                     "Provided MTU {} is lower than 1280 (expected by VIRTIO specification)",
289                     mtu
290                 )
291             }
292         }
293     }
294 }
295 
296 impl fmt::Display for Error {
297     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
298         use self::Error::*;
299         match self {
300             ParseConsole(o) => write!(f, "Error parsing --console: {}", o),
301             ParseConsoleInvalidModeGiven => {
302                 write!(f, "Error parsing --console: invalid console mode given")
303             }
304             ParseCpus(o) => write!(f, "Error parsing --cpus: {}", o),
305             InvalidCpuFeatures(o) => write!(f, "Invalid feature in --cpus features list: {}", o),
306             ParseDevice(o) => write!(f, "Error parsing --device: {}", o),
307             ParseDevicePathMissing => write!(f, "Error parsing --device: path missing"),
308             ParseFileSystem(o) => write!(f, "Error parsing --fs: {}", o),
309             ParseFsSockMissing => write!(f, "Error parsing --fs: socket missing"),
310             ParseFsTagMissing => write!(f, "Error parsing --fs: tag missing"),
311             ParsePersistentMemory(o) => write!(f, "Error parsing --pmem: {}", o),
312             ParsePmemFileMissing => write!(f, "Error parsing --pmem: file missing"),
313             ParseVsock(o) => write!(f, "Error parsing --vsock: {}", o),
314             ParseVsockCidMissing => write!(f, "Error parsing --vsock: cid missing"),
315             ParseVsockSockMissing => write!(f, "Error parsing --vsock: socket missing"),
316             ParseMemory(o) => write!(f, "Error parsing --memory: {}", o),
317             ParseMemoryZone(o) => write!(f, "Error parsing --memory-zone: {}", o),
318             ParseMemoryZoneIdMissing => write!(f, "Error parsing --memory-zone: id missing"),
319             ParseNetwork(o) => write!(f, "Error parsing --net: {}", o),
320             ParseDisk(o) => write!(f, "Error parsing --disk: {}", o),
321             ParseRng(o) => write!(f, "Error parsing --rng: {}", o),
322             ParseBalloon(o) => write!(f, "Error parsing --balloon: {}", o),
323             ParseRestore(o) => write!(f, "Error parsing --restore: {}", o),
324             #[cfg(target_arch = "x86_64")]
325             ParseSgxEpc(o) => write!(f, "Error parsing --sgx-epc: {}", o),
326             #[cfg(target_arch = "x86_64")]
327             ParseSgxEpcIdMissing => write!(f, "Error parsing --sgx-epc: id missing"),
328             ParseNuma(o) => write!(f, "Error parsing --numa: {}", o),
329             ParseRestoreSourceUrlMissing => {
330                 write!(f, "Error parsing --restore: source_url missing")
331             }
332             ParseUserDeviceSocketMissing => {
333                 write!(f, "Error parsing --user-device: socket missing")
334             }
335             ParseUserDevice(o) => write!(f, "Error parsing --user-device: {}", o),
336             Validation(v) => write!(f, "Error validating configuration: {}", v),
337             #[cfg(feature = "tdx")]
338             ParseTdx(o) => write!(f, "Error parsing --tdx: {}", o),
339             #[cfg(feature = "tdx")]
340             FirmwarePathMissing => write!(f, "TDX firmware missing"),
341             ParsePlatform(o) => write!(f, "Error parsing --platform: {}", o),
342             ParseVdpa(o) => write!(f, "Error parsing --vdpa: {}", o),
343             ParseVdpaPathMissing => write!(f, "Error parsing --vdpa: path missing"),
344         }
345     }
346 }
347 
348 pub fn add_to_config<T>(items: &mut Option<Vec<T>>, item: T) {
349     if let Some(items) = items {
350         items.push(item);
351     } else {
352         *items = Some(vec![item]);
353     }
354 }
355 
356 pub type Result<T> = result::Result<T, Error>;
357 
358 pub struct VmParams<'a> {
359     pub cpus: &'a str,
360     pub memory: &'a str,
361     pub memory_zones: Option<Vec<&'a str>>,
362     pub firmware: Option<&'a str>,
363     pub kernel: Option<&'a str>,
364     pub initramfs: Option<&'a str>,
365     pub cmdline: Option<&'a str>,
366     pub disks: Option<Vec<&'a str>>,
367     pub net: Option<Vec<&'a str>>,
368     pub rng: &'a str,
369     pub balloon: Option<&'a str>,
370     pub fs: Option<Vec<&'a str>>,
371     pub pmem: Option<Vec<&'a str>>,
372     pub serial: &'a str,
373     pub console: &'a str,
374     pub devices: Option<Vec<&'a str>>,
375     pub user_devices: Option<Vec<&'a str>>,
376     pub vdpa: Option<Vec<&'a str>>,
377     pub vsock: Option<&'a str>,
378     #[cfg(target_arch = "x86_64")]
379     pub sgx_epc: Option<Vec<&'a str>>,
380     pub numa: Option<Vec<&'a str>>,
381     pub watchdog: bool,
382     #[cfg(feature = "guest_debug")]
383     pub gdb: bool,
384     pub platform: Option<&'a str>,
385 }
386 
387 impl<'a> VmParams<'a> {
388     pub fn from_arg_matches(args: &'a ArgMatches) -> Self {
389         // These .unwrap()s cannot fail as there is a default value defined
390         let cpus = args.value_of("cpus").unwrap();
391         let memory = args.value_of("memory").unwrap();
392         let memory_zones: Option<Vec<&str>> = args.values_of("memory-zone").map(|x| x.collect());
393         let rng = args.value_of("rng").unwrap();
394         let serial = args.value_of("serial").unwrap();
395         let firmware = args.value_of("firmware");
396         let kernel = args.value_of("kernel");
397         let initramfs = args.value_of("initramfs");
398         let cmdline = args.value_of("cmdline");
399         let disks: Option<Vec<&str>> = args.values_of("disk").map(|x| x.collect());
400         let net: Option<Vec<&str>> = args.values_of("net").map(|x| x.collect());
401         let console = args.value_of("console").unwrap();
402         let balloon = args.value_of("balloon");
403         let fs: Option<Vec<&str>> = args.values_of("fs").map(|x| x.collect());
404         let pmem: Option<Vec<&str>> = args.values_of("pmem").map(|x| x.collect());
405         let devices: Option<Vec<&str>> = args.values_of("device").map(|x| x.collect());
406         let user_devices: Option<Vec<&str>> = args.values_of("user-device").map(|x| x.collect());
407         let vdpa: Option<Vec<&str>> = args.values_of("vdpa").map(|x| x.collect());
408         let vsock: Option<&str> = args.value_of("vsock");
409         #[cfg(target_arch = "x86_64")]
410         let sgx_epc: Option<Vec<&str>> = args.values_of("sgx-epc").map(|x| x.collect());
411         let numa: Option<Vec<&str>> = args.values_of("numa").map(|x| x.collect());
412         let watchdog = args.is_present("watchdog");
413         let platform = args.value_of("platform");
414         #[cfg(feature = "guest_debug")]
415         let gdb = args.is_present("gdb");
416         VmParams {
417             cpus,
418             memory,
419             memory_zones,
420             firmware,
421             kernel,
422             initramfs,
423             cmdline,
424             disks,
425             net,
426             rng,
427             balloon,
428             fs,
429             pmem,
430             serial,
431             console,
432             devices,
433             user_devices,
434             vdpa,
435             vsock,
436             #[cfg(target_arch = "x86_64")]
437             sgx_epc,
438             numa,
439             watchdog,
440             #[cfg(feature = "guest_debug")]
441             gdb,
442             platform,
443         }
444     }
445 }
446 
447 #[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, Serialize)]
448 pub enum HotplugMethod {
449     Acpi,
450     VirtioMem,
451 }
452 
453 impl Default for HotplugMethod {
454     fn default() -> Self {
455         HotplugMethod::Acpi
456     }
457 }
458 
459 #[derive(Debug)]
460 pub enum ParseHotplugMethodError {
461     InvalidValue(String),
462 }
463 
464 impl FromStr for HotplugMethod {
465     type Err = ParseHotplugMethodError;
466 
467     fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
468         match s.to_lowercase().as_str() {
469             "acpi" => Ok(HotplugMethod::Acpi),
470             "virtio-mem" => Ok(HotplugMethod::VirtioMem),
471             _ => Err(ParseHotplugMethodError::InvalidValue(s.to_owned())),
472         }
473     }
474 }
475 
476 #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
477 pub struct CpuAffinity {
478     pub vcpu: u8,
479     pub host_cpus: Vec<u8>,
480 }
481 
482 #[derive(Clone, Debug, Default, PartialEq, Eq, Deserialize, Serialize)]
483 pub struct CpuFeatures {
484     #[cfg(target_arch = "x86_64")]
485     #[serde(default)]
486     pub amx: bool,
487 }
488 
489 pub enum CpuTopologyParseError {
490     InvalidValue(String),
491 }
492 
493 #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
494 pub struct CpuTopology {
495     pub threads_per_core: u8,
496     pub cores_per_die: u8,
497     pub dies_per_package: u8,
498     pub packages: u8,
499 }
500 
501 impl FromStr for CpuTopology {
502     type Err = CpuTopologyParseError;
503 
504     fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
505         let parts: Vec<&str> = s.split(':').collect();
506 
507         if parts.len() != 4 {
508             return Err(Self::Err::InvalidValue(s.to_owned()));
509         }
510 
511         let t = CpuTopology {
512             threads_per_core: parts[0]
513                 .parse()
514                 .map_err(|_| Self::Err::InvalidValue(s.to_owned()))?,
515             cores_per_die: parts[1]
516                 .parse()
517                 .map_err(|_| Self::Err::InvalidValue(s.to_owned()))?,
518             dies_per_package: parts[2]
519                 .parse()
520                 .map_err(|_| Self::Err::InvalidValue(s.to_owned()))?,
521             packages: parts[3]
522                 .parse()
523                 .map_err(|_| Self::Err::InvalidValue(s.to_owned()))?,
524         };
525 
526         Ok(t)
527     }
528 }
529 
530 fn default_cpuconfig_max_phys_bits() -> u8 {
531     DEFAULT_MAX_PHYS_BITS
532 }
533 
534 #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
535 pub struct CpusConfig {
536     pub boot_vcpus: u8,
537     pub max_vcpus: u8,
538     #[serde(default)]
539     pub topology: Option<CpuTopology>,
540     #[serde(default)]
541     pub kvm_hyperv: bool,
542     #[serde(default = "default_cpuconfig_max_phys_bits")]
543     pub max_phys_bits: u8,
544     #[serde(default)]
545     pub affinity: Option<Vec<CpuAffinity>>,
546     #[serde(default)]
547     pub features: CpuFeatures,
548 }
549 
550 impl CpusConfig {
551     pub fn parse(cpus: &str) -> Result<Self> {
552         let mut parser = OptionParser::new();
553         parser
554             .add("boot")
555             .add("max")
556             .add("topology")
557             .add("kvm_hyperv")
558             .add("max_phys_bits")
559             .add("affinity")
560             .add("features");
561         parser.parse(cpus).map_err(Error::ParseCpus)?;
562 
563         let boot_vcpus: u8 = parser
564             .convert("boot")
565             .map_err(Error::ParseCpus)?
566             .unwrap_or(DEFAULT_VCPUS);
567         let max_vcpus: u8 = parser
568             .convert("max")
569             .map_err(Error::ParseCpus)?
570             .unwrap_or(boot_vcpus);
571         let topology = parser.convert("topology").map_err(Error::ParseCpus)?;
572         let kvm_hyperv = parser
573             .convert::<Toggle>("kvm_hyperv")
574             .map_err(Error::ParseCpus)?
575             .unwrap_or(Toggle(false))
576             .0;
577         let max_phys_bits = parser
578             .convert::<u8>("max_phys_bits")
579             .map_err(Error::ParseCpus)?
580             .unwrap_or(DEFAULT_MAX_PHYS_BITS);
581         let affinity = parser
582             .convert::<Tuple<u8, Vec<u8>>>("affinity")
583             .map_err(Error::ParseCpus)?
584             .map(|v| {
585                 v.0.iter()
586                     .map(|(e1, e2)| CpuAffinity {
587                         vcpu: *e1,
588                         host_cpus: e2.clone(),
589                     })
590                     .collect()
591             });
592         let features_list = parser
593             .convert::<StringList>("features")
594             .map_err(Error::ParseCpus)?
595             .unwrap_or_default();
596         // Some ugliness here as the features being checked might be disabled
597         // at compile time causing the below allow and the need to specify the
598         // ref type in the match.
599         // The issue will go away once kvm_hyperv is moved under the features
600         // list as it will always be checked for.
601         #[allow(unused_mut)]
602         let mut features = CpuFeatures::default();
603         for s in features_list.0 {
604             match <std::string::String as AsRef<str>>::as_ref(&s) {
605                 #[cfg(target_arch = "x86_64")]
606                 "amx" => {
607                     features.amx = true;
608                     Ok(())
609                 }
610                 _ => Err(Error::InvalidCpuFeatures(s)),
611             }?;
612         }
613 
614         Ok(CpusConfig {
615             boot_vcpus,
616             max_vcpus,
617             topology,
618             kvm_hyperv,
619             max_phys_bits,
620             affinity,
621             features,
622         })
623     }
624 }
625 
626 impl Default for CpusConfig {
627     fn default() -> Self {
628         CpusConfig {
629             boot_vcpus: DEFAULT_VCPUS,
630             max_vcpus: DEFAULT_VCPUS,
631             topology: None,
632             kvm_hyperv: false,
633             max_phys_bits: DEFAULT_MAX_PHYS_BITS,
634             affinity: None,
635             features: CpuFeatures::default(),
636         }
637     }
638 }
639 
640 fn default_platformconfig_num_pci_segments() -> u16 {
641     DEFAULT_NUM_PCI_SEGMENTS
642 }
643 
644 #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
645 pub struct PlatformConfig {
646     #[serde(default = "default_platformconfig_num_pci_segments")]
647     pub num_pci_segments: u16,
648     #[serde(default)]
649     pub iommu_segments: Option<Vec<u16>>,
650     #[serde(default)]
651     pub serial_number: Option<String>,
652     #[serde(default)]
653     pub uuid: Option<String>,
654     #[serde(default)]
655     pub oem_strings: Option<Vec<String>>,
656     #[cfg(feature = "tdx")]
657     #[serde(default)]
658     pub tdx: bool,
659 }
660 
661 impl PlatformConfig {
662     pub fn parse(platform: &str) -> Result<Self> {
663         let mut parser = OptionParser::new();
664         parser
665             .add("num_pci_segments")
666             .add("iommu_segments")
667             .add("serial_number")
668             .add("uuid")
669             .add("oem_strings");
670         #[cfg(feature = "tdx")]
671         parser.add("tdx");
672         parser.parse(platform).map_err(Error::ParsePlatform)?;
673 
674         let num_pci_segments: u16 = parser
675             .convert("num_pci_segments")
676             .map_err(Error::ParsePlatform)?
677             .unwrap_or(DEFAULT_NUM_PCI_SEGMENTS);
678         let iommu_segments = parser
679             .convert::<IntegerList>("iommu_segments")
680             .map_err(Error::ParsePlatform)?
681             .map(|v| v.0.iter().map(|e| *e as u16).collect());
682         let serial_number = parser
683             .convert("serial_number")
684             .map_err(Error::ParsePlatform)?;
685         let uuid = parser.convert("uuid").map_err(Error::ParsePlatform)?;
686         let oem_strings = parser
687             .convert::<StringList>("oem_strings")
688             .map_err(Error::ParsePlatform)?
689             .map(|v| v.0);
690         #[cfg(feature = "tdx")]
691         let tdx = parser
692             .convert::<Toggle>("tdx")
693             .map_err(Error::ParsePlatform)?
694             .unwrap_or(Toggle(false))
695             .0;
696         Ok(PlatformConfig {
697             num_pci_segments,
698             iommu_segments,
699             serial_number,
700             uuid,
701             oem_strings,
702             #[cfg(feature = "tdx")]
703             tdx,
704         })
705     }
706 
707     pub fn validate(&self) -> ValidationResult<()> {
708         if self.num_pci_segments == 0 || self.num_pci_segments > MAX_NUM_PCI_SEGMENTS {
709             return Err(ValidationError::InvalidNumPciSegments(
710                 self.num_pci_segments,
711             ));
712         }
713 
714         if let Some(iommu_segments) = &self.iommu_segments {
715             for segment in iommu_segments {
716                 if *segment >= self.num_pci_segments {
717                     return Err(ValidationError::InvalidPciSegment(*segment));
718                 }
719             }
720         }
721 
722         Ok(())
723     }
724 }
725 
726 impl Default for PlatformConfig {
727     fn default() -> Self {
728         PlatformConfig {
729             num_pci_segments: DEFAULT_NUM_PCI_SEGMENTS,
730             iommu_segments: None,
731             serial_number: None,
732             uuid: None,
733             oem_strings: None,
734             #[cfg(feature = "tdx")]
735             tdx: false,
736         }
737     }
738 }
739 
740 #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
741 pub struct MemoryZoneConfig {
742     pub id: String,
743     pub size: u64,
744     #[serde(default)]
745     pub file: Option<PathBuf>,
746     #[serde(default)]
747     pub shared: bool,
748     #[serde(default)]
749     pub hugepages: bool,
750     #[serde(default)]
751     pub hugepage_size: Option<u64>,
752     #[serde(default)]
753     pub host_numa_node: Option<u32>,
754     #[serde(default)]
755     pub hotplug_size: Option<u64>,
756     #[serde(default)]
757     pub hotplugged_size: Option<u64>,
758     #[serde(default)]
759     pub prefault: bool,
760 }
761 
762 #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
763 pub struct MemoryConfig {
764     pub size: u64,
765     #[serde(default)]
766     pub mergeable: bool,
767     #[serde(default)]
768     pub hotplug_method: HotplugMethod,
769     #[serde(default)]
770     pub hotplug_size: Option<u64>,
771     #[serde(default)]
772     pub hotplugged_size: Option<u64>,
773     #[serde(default)]
774     pub shared: bool,
775     #[serde(default)]
776     pub hugepages: bool,
777     #[serde(default)]
778     pub hugepage_size: Option<u64>,
779     #[serde(default)]
780     pub prefault: bool,
781     #[serde(default)]
782     pub zones: Option<Vec<MemoryZoneConfig>>,
783 }
784 
785 impl MemoryConfig {
786     pub fn parse(memory: &str, memory_zones: Option<Vec<&str>>) -> Result<Self> {
787         let mut parser = OptionParser::new();
788         parser
789             .add("size")
790             .add("file")
791             .add("mergeable")
792             .add("hotplug_method")
793             .add("hotplug_size")
794             .add("hotplugged_size")
795             .add("shared")
796             .add("hugepages")
797             .add("hugepage_size")
798             .add("prefault");
799         parser.parse(memory).map_err(Error::ParseMemory)?;
800 
801         let size = parser
802             .convert::<ByteSized>("size")
803             .map_err(Error::ParseMemory)?
804             .unwrap_or(ByteSized(DEFAULT_MEMORY_MB << 20))
805             .0;
806         let mergeable = parser
807             .convert::<Toggle>("mergeable")
808             .map_err(Error::ParseMemory)?
809             .unwrap_or(Toggle(false))
810             .0;
811         let hotplug_method = parser
812             .convert("hotplug_method")
813             .map_err(Error::ParseMemory)?
814             .unwrap_or_default();
815         let hotplug_size = parser
816             .convert::<ByteSized>("hotplug_size")
817             .map_err(Error::ParseMemory)?
818             .map(|v| v.0);
819         let hotplugged_size = parser
820             .convert::<ByteSized>("hotplugged_size")
821             .map_err(Error::ParseMemory)?
822             .map(|v| v.0);
823         let shared = parser
824             .convert::<Toggle>("shared")
825             .map_err(Error::ParseMemory)?
826             .unwrap_or(Toggle(false))
827             .0;
828         let hugepages = parser
829             .convert::<Toggle>("hugepages")
830             .map_err(Error::ParseMemory)?
831             .unwrap_or(Toggle(false))
832             .0;
833         let hugepage_size = parser
834             .convert::<ByteSized>("hugepage_size")
835             .map_err(Error::ParseMemory)?
836             .map(|v| v.0);
837         let prefault = parser
838             .convert::<Toggle>("prefault")
839             .map_err(Error::ParseMemory)?
840             .unwrap_or(Toggle(false))
841             .0;
842 
843         let zones: Option<Vec<MemoryZoneConfig>> = if let Some(memory_zones) = &memory_zones {
844             let mut zones = Vec::new();
845             for memory_zone in memory_zones.iter() {
846                 let mut parser = OptionParser::new();
847                 parser
848                     .add("id")
849                     .add("size")
850                     .add("file")
851                     .add("shared")
852                     .add("hugepages")
853                     .add("hugepage_size")
854                     .add("host_numa_node")
855                     .add("hotplug_size")
856                     .add("hotplugged_size")
857                     .add("prefault");
858                 parser.parse(memory_zone).map_err(Error::ParseMemoryZone)?;
859 
860                 let id = parser.get("id").ok_or(Error::ParseMemoryZoneIdMissing)?;
861                 let size = parser
862                     .convert::<ByteSized>("size")
863                     .map_err(Error::ParseMemoryZone)?
864                     .unwrap_or(ByteSized(DEFAULT_MEMORY_MB << 20))
865                     .0;
866                 let file = parser.get("file").map(PathBuf::from);
867                 let shared = parser
868                     .convert::<Toggle>("shared")
869                     .map_err(Error::ParseMemoryZone)?
870                     .unwrap_or(Toggle(false))
871                     .0;
872                 let hugepages = parser
873                     .convert::<Toggle>("hugepages")
874                     .map_err(Error::ParseMemoryZone)?
875                     .unwrap_or(Toggle(false))
876                     .0;
877                 let hugepage_size = parser
878                     .convert::<ByteSized>("hugepage_size")
879                     .map_err(Error::ParseMemoryZone)?
880                     .map(|v| v.0);
881 
882                 let host_numa_node = parser
883                     .convert::<u32>("host_numa_node")
884                     .map_err(Error::ParseMemoryZone)?;
885                 let hotplug_size = parser
886                     .convert::<ByteSized>("hotplug_size")
887                     .map_err(Error::ParseMemoryZone)?
888                     .map(|v| v.0);
889                 let hotplugged_size = parser
890                     .convert::<ByteSized>("hotplugged_size")
891                     .map_err(Error::ParseMemoryZone)?
892                     .map(|v| v.0);
893                 let prefault = parser
894                     .convert::<Toggle>("prefault")
895                     .map_err(Error::ParseMemoryZone)?
896                     .unwrap_or(Toggle(false))
897                     .0;
898 
899                 zones.push(MemoryZoneConfig {
900                     id,
901                     size,
902                     file,
903                     shared,
904                     hugepages,
905                     hugepage_size,
906                     host_numa_node,
907                     hotplug_size,
908                     hotplugged_size,
909                     prefault,
910                 });
911             }
912             Some(zones)
913         } else {
914             None
915         };
916 
917         Ok(MemoryConfig {
918             size,
919             mergeable,
920             hotplug_method,
921             hotplug_size,
922             hotplugged_size,
923             shared,
924             hugepages,
925             hugepage_size,
926             prefault,
927             zones,
928         })
929     }
930 
931     pub fn total_size(&self) -> u64 {
932         let mut size = self.size;
933         if let Some(hotplugged_size) = self.hotplugged_size {
934             size += hotplugged_size;
935         }
936 
937         if let Some(zones) = &self.zones {
938             for zone in zones.iter() {
939                 size += zone.size;
940                 if let Some(hotplugged_size) = zone.hotplugged_size {
941                     size += hotplugged_size;
942                 }
943             }
944         }
945 
946         size
947     }
948 }
949 
950 impl Default for MemoryConfig {
951     fn default() -> Self {
952         MemoryConfig {
953             size: DEFAULT_MEMORY_MB << 20,
954             mergeable: false,
955             hotplug_method: HotplugMethod::Acpi,
956             hotplug_size: None,
957             hotplugged_size: None,
958             shared: false,
959             hugepages: false,
960             hugepage_size: None,
961             prefault: false,
962             zones: None,
963         }
964     }
965 }
966 
967 #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
968 pub struct KernelConfig {
969     pub path: PathBuf,
970 }
971 
972 #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
973 pub struct InitramfsConfig {
974     pub path: PathBuf,
975 }
976 
977 #[derive(Clone, Debug, Default, PartialEq, Eq, Deserialize, Serialize)]
978 pub struct CmdlineConfig {
979     pub args: String,
980 }
981 
982 impl CmdlineConfig {
983     pub fn parse(cmdline: Option<&str>) -> Result<Self> {
984         let args = cmdline
985             .map(std::string::ToString::to_string)
986             .unwrap_or_else(String::new);
987 
988         Ok(CmdlineConfig { args })
989     }
990 }
991 
992 #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
993 pub struct DiskConfig {
994     pub path: Option<PathBuf>,
995     #[serde(default)]
996     pub readonly: bool,
997     #[serde(default)]
998     pub direct: bool,
999     #[serde(default)]
1000     pub iommu: bool,
1001     #[serde(default = "default_diskconfig_num_queues")]
1002     pub num_queues: usize,
1003     #[serde(default = "default_diskconfig_queue_size")]
1004     pub queue_size: u16,
1005     #[serde(default)]
1006     pub vhost_user: bool,
1007     pub vhost_socket: Option<String>,
1008     #[serde(default)]
1009     pub rate_limiter_config: Option<RateLimiterConfig>,
1010     #[serde(default)]
1011     pub id: Option<String>,
1012     // For testing use only. Not exposed in API.
1013     #[serde(default)]
1014     pub disable_io_uring: bool,
1015     #[serde(default)]
1016     pub pci_segment: u16,
1017 }
1018 
1019 fn default_diskconfig_num_queues() -> usize {
1020     DEFAULT_NUM_QUEUES_VUBLK
1021 }
1022 
1023 fn default_diskconfig_queue_size() -> u16 {
1024     DEFAULT_QUEUE_SIZE_VUBLK
1025 }
1026 
1027 impl Default for DiskConfig {
1028     fn default() -> Self {
1029         Self {
1030             path: None,
1031             readonly: false,
1032             direct: false,
1033             iommu: false,
1034             num_queues: default_diskconfig_num_queues(),
1035             queue_size: default_diskconfig_queue_size(),
1036             vhost_user: false,
1037             vhost_socket: None,
1038             id: None,
1039             disable_io_uring: false,
1040             rate_limiter_config: None,
1041             pci_segment: 0,
1042         }
1043     }
1044 }
1045 
1046 impl DiskConfig {
1047     pub const SYNTAX: &'static str = "Disk parameters \
1048          \"path=<disk_image_path>,readonly=on|off,direct=on|off,iommu=on|off,\
1049          num_queues=<number_of_queues>,queue_size=<size_of_each_queue>,\
1050          vhost_user=on|off,socket=<vhost_user_socket_path>,\
1051          bw_size=<bytes>,bw_one_time_burst=<bytes>,bw_refill_time=<ms>,\
1052          ops_size=<io_ops>,ops_one_time_burst=<io_ops>,ops_refill_time=<ms>,\
1053          id=<device_id>,pci_segment=<segment_id>\"";
1054 
1055     pub fn parse(disk: &str) -> Result<Self> {
1056         let mut parser = OptionParser::new();
1057         parser
1058             .add("path")
1059             .add("readonly")
1060             .add("direct")
1061             .add("iommu")
1062             .add("queue_size")
1063             .add("num_queues")
1064             .add("vhost_user")
1065             .add("socket")
1066             .add("bw_size")
1067             .add("bw_one_time_burst")
1068             .add("bw_refill_time")
1069             .add("ops_size")
1070             .add("ops_one_time_burst")
1071             .add("ops_refill_time")
1072             .add("id")
1073             .add("_disable_io_uring")
1074             .add("pci_segment");
1075         parser.parse(disk).map_err(Error::ParseDisk)?;
1076 
1077         let path = parser.get("path").map(PathBuf::from);
1078         let readonly = parser
1079             .convert::<Toggle>("readonly")
1080             .map_err(Error::ParseDisk)?
1081             .unwrap_or(Toggle(false))
1082             .0;
1083         let direct = parser
1084             .convert::<Toggle>("direct")
1085             .map_err(Error::ParseDisk)?
1086             .unwrap_or(Toggle(false))
1087             .0;
1088         let iommu = parser
1089             .convert::<Toggle>("iommu")
1090             .map_err(Error::ParseDisk)?
1091             .unwrap_or(Toggle(false))
1092             .0;
1093         let queue_size = parser
1094             .convert("queue_size")
1095             .map_err(Error::ParseDisk)?
1096             .unwrap_or_else(default_diskconfig_queue_size);
1097         let num_queues = parser
1098             .convert("num_queues")
1099             .map_err(Error::ParseDisk)?
1100             .unwrap_or_else(default_diskconfig_num_queues);
1101         let vhost_user = parser
1102             .convert::<Toggle>("vhost_user")
1103             .map_err(Error::ParseDisk)?
1104             .unwrap_or(Toggle(false))
1105             .0;
1106         let vhost_socket = parser.get("socket");
1107         let id = parser.get("id");
1108         let disable_io_uring = parser
1109             .convert::<Toggle>("_disable_io_uring")
1110             .map_err(Error::ParseDisk)?
1111             .unwrap_or(Toggle(false))
1112             .0;
1113         let pci_segment = parser
1114             .convert("pci_segment")
1115             .map_err(Error::ParseDisk)?
1116             .unwrap_or_default();
1117         let bw_size = parser
1118             .convert("bw_size")
1119             .map_err(Error::ParseDisk)?
1120             .unwrap_or_default();
1121         let bw_one_time_burst = parser
1122             .convert("bw_one_time_burst")
1123             .map_err(Error::ParseDisk)?
1124             .unwrap_or_default();
1125         let bw_refill_time = parser
1126             .convert("bw_refill_time")
1127             .map_err(Error::ParseDisk)?
1128             .unwrap_or_default();
1129         let ops_size = parser
1130             .convert("ops_size")
1131             .map_err(Error::ParseDisk)?
1132             .unwrap_or_default();
1133         let ops_one_time_burst = parser
1134             .convert("ops_one_time_burst")
1135             .map_err(Error::ParseDisk)?
1136             .unwrap_or_default();
1137         let ops_refill_time = parser
1138             .convert("ops_refill_time")
1139             .map_err(Error::ParseDisk)?
1140             .unwrap_or_default();
1141         let bw_tb_config = if bw_size != 0 && bw_refill_time != 0 {
1142             Some(TokenBucketConfig {
1143                 size: bw_size,
1144                 one_time_burst: Some(bw_one_time_burst),
1145                 refill_time: bw_refill_time,
1146             })
1147         } else {
1148             None
1149         };
1150         let ops_tb_config = if ops_size != 0 && ops_refill_time != 0 {
1151             Some(TokenBucketConfig {
1152                 size: ops_size,
1153                 one_time_burst: Some(ops_one_time_burst),
1154                 refill_time: ops_refill_time,
1155             })
1156         } else {
1157             None
1158         };
1159         let rate_limiter_config = if bw_tb_config.is_some() || ops_tb_config.is_some() {
1160             Some(RateLimiterConfig {
1161                 bandwidth: bw_tb_config,
1162                 ops: ops_tb_config,
1163             })
1164         } else {
1165             None
1166         };
1167 
1168         Ok(DiskConfig {
1169             path,
1170             readonly,
1171             direct,
1172             iommu,
1173             num_queues,
1174             queue_size,
1175             vhost_user,
1176             vhost_socket,
1177             rate_limiter_config,
1178             id,
1179             disable_io_uring,
1180             pci_segment,
1181         })
1182     }
1183 
1184     pub fn validate(&self, vm_config: &VmConfig) -> ValidationResult<()> {
1185         if self.num_queues > vm_config.cpus.boot_vcpus as usize {
1186             return Err(ValidationError::TooManyQueues);
1187         }
1188 
1189         if self.vhost_user && self.iommu {
1190             return Err(ValidationError::IommuNotSupported);
1191         }
1192 
1193         if let Some(platform_config) = vm_config.platform.as_ref() {
1194             if self.pci_segment >= platform_config.num_pci_segments {
1195                 return Err(ValidationError::InvalidPciSegment(self.pci_segment));
1196             }
1197 
1198             if let Some(iommu_segments) = platform_config.iommu_segments.as_ref() {
1199                 if iommu_segments.contains(&self.pci_segment) && !self.iommu {
1200                     return Err(ValidationError::OnIommuSegment(self.pci_segment));
1201                 }
1202             }
1203         }
1204 
1205         Ok(())
1206     }
1207 }
1208 
1209 #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
1210 pub enum VhostMode {
1211     Client,
1212     Server,
1213 }
1214 
1215 impl Default for VhostMode {
1216     fn default() -> Self {
1217         VhostMode::Client
1218     }
1219 }
1220 
1221 #[derive(Debug)]
1222 pub enum ParseVhostModeError {
1223     InvalidValue(String),
1224 }
1225 
1226 impl FromStr for VhostMode {
1227     type Err = ParseVhostModeError;
1228 
1229     fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
1230         match s.to_lowercase().as_str() {
1231             "client" => Ok(VhostMode::Client),
1232             "server" => Ok(VhostMode::Server),
1233             _ => Err(ParseVhostModeError::InvalidValue(s.to_owned())),
1234         }
1235     }
1236 }
1237 
1238 #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
1239 pub struct NetConfig {
1240     #[serde(default = "default_netconfig_tap")]
1241     pub tap: Option<String>,
1242     #[serde(default = "default_netconfig_ip")]
1243     pub ip: Ipv4Addr,
1244     #[serde(default = "default_netconfig_mask")]
1245     pub mask: Ipv4Addr,
1246     #[serde(default = "default_netconfig_mac")]
1247     pub mac: MacAddr,
1248     #[serde(default)]
1249     pub host_mac: Option<MacAddr>,
1250     #[serde(default)]
1251     pub mtu: Option<u16>,
1252     #[serde(default)]
1253     pub iommu: bool,
1254     #[serde(default = "default_netconfig_num_queues")]
1255     pub num_queues: usize,
1256     #[serde(default = "default_netconfig_queue_size")]
1257     pub queue_size: u16,
1258     #[serde(default)]
1259     pub vhost_user: bool,
1260     pub vhost_socket: Option<String>,
1261     #[serde(default)]
1262     pub vhost_mode: VhostMode,
1263     #[serde(default)]
1264     pub id: Option<String>,
1265     #[serde(default)]
1266     pub fds: Option<Vec<i32>>,
1267     #[serde(default)]
1268     pub rate_limiter_config: Option<RateLimiterConfig>,
1269     #[serde(default)]
1270     pub pci_segment: u16,
1271 }
1272 
1273 fn default_netconfig_tap() -> Option<String> {
1274     None
1275 }
1276 
1277 fn default_netconfig_ip() -> Ipv4Addr {
1278     Ipv4Addr::new(192, 168, 249, 1)
1279 }
1280 
1281 fn default_netconfig_mask() -> Ipv4Addr {
1282     Ipv4Addr::new(255, 255, 255, 0)
1283 }
1284 
1285 fn default_netconfig_mac() -> MacAddr {
1286     MacAddr::local_random()
1287 }
1288 
1289 fn default_netconfig_num_queues() -> usize {
1290     DEFAULT_NUM_QUEUES_VUNET
1291 }
1292 
1293 fn default_netconfig_queue_size() -> u16 {
1294     DEFAULT_QUEUE_SIZE_VUNET
1295 }
1296 
1297 impl Default for NetConfig {
1298     fn default() -> Self {
1299         Self {
1300             tap: default_netconfig_tap(),
1301             ip: default_netconfig_ip(),
1302             mask: default_netconfig_mask(),
1303             mac: default_netconfig_mac(),
1304             host_mac: None,
1305             mtu: None,
1306             iommu: false,
1307             num_queues: default_netconfig_num_queues(),
1308             queue_size: default_netconfig_queue_size(),
1309             vhost_user: false,
1310             vhost_socket: None,
1311             vhost_mode: VhostMode::Client,
1312             id: None,
1313             fds: None,
1314             rate_limiter_config: None,
1315             pci_segment: 0,
1316         }
1317     }
1318 }
1319 
1320 impl NetConfig {
1321     pub const SYNTAX: &'static str = "Network parameters \
1322     \"tap=<if_name>,ip=<ip_addr>,mask=<net_mask>,mac=<mac_addr>,fd=<fd1,fd2...>,iommu=on|off,\
1323     num_queues=<number_of_queues>,queue_size=<size_of_each_queue>,id=<device_id>,\
1324     vhost_user=<vhost_user_enable>,socket=<vhost_user_socket_path>,vhost_mode=client|server,\
1325     bw_size=<bytes>,bw_one_time_burst=<bytes>,bw_refill_time=<ms>,\
1326     ops_size=<io_ops>,ops_one_time_burst=<io_ops>,ops_refill_time=<ms>,pci_segment=<segment_id>\"";
1327 
1328     pub fn parse(net: &str) -> Result<Self> {
1329         let mut parser = OptionParser::new();
1330 
1331         parser
1332             .add("tap")
1333             .add("ip")
1334             .add("mask")
1335             .add("mac")
1336             .add("host_mac")
1337             .add("mtu")
1338             .add("iommu")
1339             .add("queue_size")
1340             .add("num_queues")
1341             .add("vhost_user")
1342             .add("socket")
1343             .add("vhost_mode")
1344             .add("id")
1345             .add("fd")
1346             .add("bw_size")
1347             .add("bw_one_time_burst")
1348             .add("bw_refill_time")
1349             .add("ops_size")
1350             .add("ops_one_time_burst")
1351             .add("ops_refill_time")
1352             .add("pci_segment");
1353         parser.parse(net).map_err(Error::ParseNetwork)?;
1354 
1355         let tap = parser.get("tap");
1356         let ip = parser
1357             .convert("ip")
1358             .map_err(Error::ParseNetwork)?
1359             .unwrap_or_else(default_netconfig_ip);
1360         let mask = parser
1361             .convert("mask")
1362             .map_err(Error::ParseNetwork)?
1363             .unwrap_or_else(default_netconfig_mask);
1364         let mac = parser
1365             .convert("mac")
1366             .map_err(Error::ParseNetwork)?
1367             .unwrap_or_else(default_netconfig_mac);
1368         let host_mac = parser.convert("host_mac").map_err(Error::ParseNetwork)?;
1369         let mtu = parser.convert("mtu").map_err(Error::ParseNetwork)?;
1370         let iommu = parser
1371             .convert::<Toggle>("iommu")
1372             .map_err(Error::ParseNetwork)?
1373             .unwrap_or(Toggle(false))
1374             .0;
1375         let queue_size = parser
1376             .convert("queue_size")
1377             .map_err(Error::ParseNetwork)?
1378             .unwrap_or_else(default_netconfig_queue_size);
1379         let num_queues = parser
1380             .convert("num_queues")
1381             .map_err(Error::ParseNetwork)?
1382             .unwrap_or_else(default_netconfig_num_queues);
1383         let vhost_user = parser
1384             .convert::<Toggle>("vhost_user")
1385             .map_err(Error::ParseNetwork)?
1386             .unwrap_or(Toggle(false))
1387             .0;
1388         let vhost_socket = parser.get("socket");
1389         let vhost_mode = parser
1390             .convert("vhost_mode")
1391             .map_err(Error::ParseNetwork)?
1392             .unwrap_or_default();
1393         let id = parser.get("id");
1394         let fds = parser
1395             .convert::<IntegerList>("fd")
1396             .map_err(Error::ParseNetwork)?
1397             .map(|v| v.0.iter().map(|e| *e as i32).collect());
1398         let pci_segment = parser
1399             .convert("pci_segment")
1400             .map_err(Error::ParseNetwork)?
1401             .unwrap_or_default();
1402         let bw_size = parser
1403             .convert("bw_size")
1404             .map_err(Error::ParseDisk)?
1405             .unwrap_or_default();
1406         let bw_one_time_burst = parser
1407             .convert("bw_one_time_burst")
1408             .map_err(Error::ParseDisk)?
1409             .unwrap_or_default();
1410         let bw_refill_time = parser
1411             .convert("bw_refill_time")
1412             .map_err(Error::ParseDisk)?
1413             .unwrap_or_default();
1414         let ops_size = parser
1415             .convert("ops_size")
1416             .map_err(Error::ParseDisk)?
1417             .unwrap_or_default();
1418         let ops_one_time_burst = parser
1419             .convert("ops_one_time_burst")
1420             .map_err(Error::ParseDisk)?
1421             .unwrap_or_default();
1422         let ops_refill_time = parser
1423             .convert("ops_refill_time")
1424             .map_err(Error::ParseDisk)?
1425             .unwrap_or_default();
1426         let bw_tb_config = if bw_size != 0 && bw_refill_time != 0 {
1427             Some(TokenBucketConfig {
1428                 size: bw_size,
1429                 one_time_burst: Some(bw_one_time_burst),
1430                 refill_time: bw_refill_time,
1431             })
1432         } else {
1433             None
1434         };
1435         let ops_tb_config = if ops_size != 0 && ops_refill_time != 0 {
1436             Some(TokenBucketConfig {
1437                 size: ops_size,
1438                 one_time_burst: Some(ops_one_time_burst),
1439                 refill_time: ops_refill_time,
1440             })
1441         } else {
1442             None
1443         };
1444         let rate_limiter_config = if bw_tb_config.is_some() || ops_tb_config.is_some() {
1445             Some(RateLimiterConfig {
1446                 bandwidth: bw_tb_config,
1447                 ops: ops_tb_config,
1448             })
1449         } else {
1450             None
1451         };
1452 
1453         let config = NetConfig {
1454             tap,
1455             ip,
1456             mask,
1457             mac,
1458             host_mac,
1459             mtu,
1460             iommu,
1461             num_queues,
1462             queue_size,
1463             vhost_user,
1464             vhost_socket,
1465             vhost_mode,
1466             id,
1467             fds,
1468             rate_limiter_config,
1469             pci_segment,
1470         };
1471         Ok(config)
1472     }
1473 
1474     pub fn validate(&self, vm_config: &VmConfig) -> ValidationResult<()> {
1475         if self.num_queues < 2 {
1476             return Err(ValidationError::VnetQueueLowerThan2);
1477         }
1478 
1479         if self.fds.is_some() && self.fds.as_ref().unwrap().len() * 2 != self.num_queues {
1480             return Err(ValidationError::VnetQueueFdMismatch);
1481         }
1482 
1483         if let Some(fds) = self.fds.as_ref() {
1484             for fd in fds {
1485                 if *fd <= 2 {
1486                     return Err(ValidationError::VnetReservedFd);
1487                 }
1488             }
1489         }
1490 
1491         if (self.num_queues / 2) > vm_config.cpus.boot_vcpus as usize {
1492             return Err(ValidationError::TooManyQueues);
1493         }
1494 
1495         if self.vhost_user && self.iommu {
1496             return Err(ValidationError::IommuNotSupported);
1497         }
1498 
1499         if let Some(platform_config) = vm_config.platform.as_ref() {
1500             if self.pci_segment >= platform_config.num_pci_segments {
1501                 return Err(ValidationError::InvalidPciSegment(self.pci_segment));
1502             }
1503 
1504             if let Some(iommu_segments) = platform_config.iommu_segments.as_ref() {
1505                 if iommu_segments.contains(&self.pci_segment) && !self.iommu {
1506                     return Err(ValidationError::OnIommuSegment(self.pci_segment));
1507                 }
1508             }
1509         }
1510 
1511         if let Some(mtu) = self.mtu {
1512             if mtu < virtio_devices::net::MIN_MTU {
1513                 return Err(ValidationError::InvalidMtu(mtu));
1514             }
1515         }
1516 
1517         Ok(())
1518     }
1519 }
1520 
1521 #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
1522 pub struct RngConfig {
1523     pub src: PathBuf,
1524     #[serde(default)]
1525     pub iommu: bool,
1526 }
1527 
1528 impl RngConfig {
1529     pub fn parse(rng: &str) -> Result<Self> {
1530         let mut parser = OptionParser::new();
1531         parser.add("src").add("iommu");
1532         parser.parse(rng).map_err(Error::ParseRng)?;
1533 
1534         let src = PathBuf::from(
1535             parser
1536                 .get("src")
1537                 .unwrap_or_else(|| DEFAULT_RNG_SOURCE.to_owned()),
1538         );
1539         let iommu = parser
1540             .convert::<Toggle>("iommu")
1541             .map_err(Error::ParseRng)?
1542             .unwrap_or(Toggle(false))
1543             .0;
1544 
1545         Ok(RngConfig { src, iommu })
1546     }
1547 }
1548 
1549 impl Default for RngConfig {
1550     fn default() -> Self {
1551         RngConfig {
1552             src: PathBuf::from(DEFAULT_RNG_SOURCE),
1553             iommu: false,
1554         }
1555     }
1556 }
1557 
1558 #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
1559 pub struct BalloonConfig {
1560     pub size: u64,
1561     /// Option to deflate the balloon in case the guest is out of memory.
1562     #[serde(default)]
1563     pub deflate_on_oom: bool,
1564     /// Option to enable free page reporting from the guest.
1565     #[serde(default)]
1566     pub free_page_reporting: bool,
1567 }
1568 
1569 impl BalloonConfig {
1570     pub const SYNTAX: &'static str =
1571         "Balloon parameters \"size=<balloon_size>,deflate_on_oom=on|off,\
1572         free_page_reporting=on|off\"";
1573 
1574     pub fn parse(balloon: &str) -> Result<Self> {
1575         let mut parser = OptionParser::new();
1576         parser.add("size");
1577         parser.add("deflate_on_oom");
1578         parser.add("free_page_reporting");
1579         parser.parse(balloon).map_err(Error::ParseBalloon)?;
1580 
1581         let size = parser
1582             .convert::<ByteSized>("size")
1583             .map_err(Error::ParseBalloon)?
1584             .map(|v| v.0)
1585             .unwrap_or(0);
1586 
1587         let deflate_on_oom = parser
1588             .convert::<Toggle>("deflate_on_oom")
1589             .map_err(Error::ParseBalloon)?
1590             .unwrap_or(Toggle(false))
1591             .0;
1592 
1593         let free_page_reporting = parser
1594             .convert::<Toggle>("free_page_reporting")
1595             .map_err(Error::ParseBalloon)?
1596             .unwrap_or(Toggle(false))
1597             .0;
1598 
1599         Ok(BalloonConfig {
1600             size,
1601             deflate_on_oom,
1602             free_page_reporting,
1603         })
1604     }
1605 }
1606 
1607 #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
1608 pub struct FsConfig {
1609     pub tag: String,
1610     pub socket: PathBuf,
1611     #[serde(default = "default_fsconfig_num_queues")]
1612     pub num_queues: usize,
1613     #[serde(default = "default_fsconfig_queue_size")]
1614     pub queue_size: u16,
1615     #[serde(default)]
1616     pub id: Option<String>,
1617     #[serde(default)]
1618     pub pci_segment: u16,
1619 }
1620 
1621 fn default_fsconfig_num_queues() -> usize {
1622     1
1623 }
1624 
1625 fn default_fsconfig_queue_size() -> u16 {
1626     1024
1627 }
1628 
1629 impl Default for FsConfig {
1630     fn default() -> Self {
1631         Self {
1632             tag: "".to_owned(),
1633             socket: PathBuf::new(),
1634             num_queues: default_fsconfig_num_queues(),
1635             queue_size: default_fsconfig_queue_size(),
1636             id: None,
1637             pci_segment: 0,
1638         }
1639     }
1640 }
1641 
1642 impl FsConfig {
1643     pub const SYNTAX: &'static str = "virtio-fs parameters \
1644     \"tag=<tag_name>,socket=<socket_path>,num_queues=<number_of_queues>,\
1645     queue_size=<size_of_each_queue>,id=<device_id>,pci_segment=<segment_id>\"";
1646 
1647     pub fn parse(fs: &str) -> Result<Self> {
1648         let mut parser = OptionParser::new();
1649         parser
1650             .add("tag")
1651             .add("queue_size")
1652             .add("num_queues")
1653             .add("socket")
1654             .add("id")
1655             .add("pci_segment");
1656         parser.parse(fs).map_err(Error::ParseFileSystem)?;
1657 
1658         let tag = parser.get("tag").ok_or(Error::ParseFsTagMissing)?;
1659         let socket = PathBuf::from(parser.get("socket").ok_or(Error::ParseFsSockMissing)?);
1660 
1661         let queue_size = parser
1662             .convert("queue_size")
1663             .map_err(Error::ParseFileSystem)?
1664             .unwrap_or_else(default_fsconfig_queue_size);
1665         let num_queues = parser
1666             .convert("num_queues")
1667             .map_err(Error::ParseFileSystem)?
1668             .unwrap_or_else(default_fsconfig_num_queues);
1669 
1670         let id = parser.get("id");
1671 
1672         let pci_segment = parser
1673             .convert("pci_segment")
1674             .map_err(Error::ParseFileSystem)?
1675             .unwrap_or_default();
1676 
1677         Ok(FsConfig {
1678             tag,
1679             socket,
1680             num_queues,
1681             queue_size,
1682             id,
1683             pci_segment,
1684         })
1685     }
1686 
1687     pub fn validate(&self, vm_config: &VmConfig) -> ValidationResult<()> {
1688         if self.num_queues > vm_config.cpus.boot_vcpus as usize {
1689             return Err(ValidationError::TooManyQueues);
1690         }
1691 
1692         if let Some(platform_config) = vm_config.platform.as_ref() {
1693             if self.pci_segment >= platform_config.num_pci_segments {
1694                 return Err(ValidationError::InvalidPciSegment(self.pci_segment));
1695             }
1696 
1697             if let Some(iommu_segments) = platform_config.iommu_segments.as_ref() {
1698                 if iommu_segments.contains(&self.pci_segment) {
1699                     return Err(ValidationError::IommuNotSupportedOnSegment(
1700                         self.pci_segment,
1701                     ));
1702                 }
1703             }
1704         }
1705 
1706         Ok(())
1707     }
1708 }
1709 
1710 #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, Default)]
1711 pub struct PmemConfig {
1712     pub file: PathBuf,
1713     #[serde(default)]
1714     pub size: Option<u64>,
1715     #[serde(default)]
1716     pub iommu: bool,
1717     #[serde(default)]
1718     pub discard_writes: bool,
1719     #[serde(default)]
1720     pub id: Option<String>,
1721     #[serde(default)]
1722     pub pci_segment: u16,
1723 }
1724 
1725 impl PmemConfig {
1726     pub const SYNTAX: &'static str = "Persistent memory parameters \
1727     \"file=<backing_file_path>,size=<persistent_memory_size>,iommu=on|off,\
1728     discard_writes=on|off,id=<device_id>,pci_segment=<segment_id>\"";
1729     pub fn parse(pmem: &str) -> Result<Self> {
1730         let mut parser = OptionParser::new();
1731         parser
1732             .add("size")
1733             .add("file")
1734             .add("iommu")
1735             .add("discard_writes")
1736             .add("id")
1737             .add("pci_segment");
1738         parser.parse(pmem).map_err(Error::ParsePersistentMemory)?;
1739 
1740         let file = PathBuf::from(parser.get("file").ok_or(Error::ParsePmemFileMissing)?);
1741         let size = parser
1742             .convert::<ByteSized>("size")
1743             .map_err(Error::ParsePersistentMemory)?
1744             .map(|v| v.0);
1745         let iommu = parser
1746             .convert::<Toggle>("iommu")
1747             .map_err(Error::ParsePersistentMemory)?
1748             .unwrap_or(Toggle(false))
1749             .0;
1750         let discard_writes = parser
1751             .convert::<Toggle>("discard_writes")
1752             .map_err(Error::ParsePersistentMemory)?
1753             .unwrap_or(Toggle(false))
1754             .0;
1755         let id = parser.get("id");
1756         let pci_segment = parser
1757             .convert("pci_segment")
1758             .map_err(Error::ParsePersistentMemory)?
1759             .unwrap_or_default();
1760 
1761         Ok(PmemConfig {
1762             file,
1763             size,
1764             iommu,
1765             discard_writes,
1766             id,
1767             pci_segment,
1768         })
1769     }
1770 
1771     pub fn validate(&self, vm_config: &VmConfig) -> ValidationResult<()> {
1772         if let Some(platform_config) = vm_config.platform.as_ref() {
1773             if self.pci_segment >= platform_config.num_pci_segments {
1774                 return Err(ValidationError::InvalidPciSegment(self.pci_segment));
1775             }
1776 
1777             if let Some(iommu_segments) = platform_config.iommu_segments.as_ref() {
1778                 if iommu_segments.contains(&self.pci_segment) && !self.iommu {
1779                     return Err(ValidationError::OnIommuSegment(self.pci_segment));
1780                 }
1781             }
1782         }
1783 
1784         Ok(())
1785     }
1786 }
1787 
1788 #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
1789 pub enum ConsoleOutputMode {
1790     Off,
1791     Pty,
1792     Tty,
1793     File,
1794     Null,
1795 }
1796 
1797 #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
1798 pub struct ConsoleConfig {
1799     #[serde(default = "default_consoleconfig_file")]
1800     pub file: Option<PathBuf>,
1801     pub mode: ConsoleOutputMode,
1802     #[serde(default)]
1803     pub iommu: bool,
1804 }
1805 
1806 fn default_consoleconfig_file() -> Option<PathBuf> {
1807     None
1808 }
1809 
1810 impl ConsoleConfig {
1811     pub fn parse(console: &str) -> Result<Self> {
1812         let mut parser = OptionParser::new();
1813         parser
1814             .add_valueless("off")
1815             .add_valueless("pty")
1816             .add_valueless("tty")
1817             .add_valueless("null")
1818             .add("file")
1819             .add("iommu");
1820         parser.parse(console).map_err(Error::ParseConsole)?;
1821 
1822         let mut file: Option<PathBuf> = default_consoleconfig_file();
1823         let mut mode: ConsoleOutputMode = ConsoleOutputMode::Off;
1824 
1825         if parser.is_set("off") {
1826         } else if parser.is_set("pty") {
1827             mode = ConsoleOutputMode::Pty
1828         } else if parser.is_set("tty") {
1829             mode = ConsoleOutputMode::Tty
1830         } else if parser.is_set("null") {
1831             mode = ConsoleOutputMode::Null
1832         } else if parser.is_set("file") {
1833             mode = ConsoleOutputMode::File;
1834             file =
1835                 Some(PathBuf::from(parser.get("file").ok_or(
1836                     Error::Validation(ValidationError::ConsoleFileMissing),
1837                 )?));
1838         } else {
1839             return Err(Error::ParseConsoleInvalidModeGiven);
1840         }
1841         let iommu = parser
1842             .convert::<Toggle>("iommu")
1843             .map_err(Error::ParseConsole)?
1844             .unwrap_or(Toggle(false))
1845             .0;
1846 
1847         Ok(Self { file, mode, iommu })
1848     }
1849 
1850     pub fn default_serial() -> Self {
1851         ConsoleConfig {
1852             file: None,
1853             mode: ConsoleOutputMode::Null,
1854             iommu: false,
1855         }
1856     }
1857 
1858     pub fn default_console() -> Self {
1859         ConsoleConfig {
1860             file: None,
1861             mode: ConsoleOutputMode::Tty,
1862             iommu: false,
1863         }
1864     }
1865 }
1866 
1867 #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, Default)]
1868 pub struct DeviceConfig {
1869     pub path: PathBuf,
1870     #[serde(default)]
1871     pub iommu: bool,
1872     #[serde(default)]
1873     pub id: Option<String>,
1874     #[serde(default)]
1875     pub pci_segment: u16,
1876 }
1877 
1878 impl DeviceConfig {
1879     pub const SYNTAX: &'static str =
1880         "Direct device assignment parameters \"path=<device_path>,iommu=on|off,id=<device_id>,pci_segment=<segment_id>\"";
1881     pub fn parse(device: &str) -> Result<Self> {
1882         let mut parser = OptionParser::new();
1883         parser.add("path").add("id").add("iommu").add("pci_segment");
1884         parser.parse(device).map_err(Error::ParseDevice)?;
1885 
1886         let path = parser
1887             .get("path")
1888             .map(PathBuf::from)
1889             .ok_or(Error::ParseDevicePathMissing)?;
1890         let iommu = parser
1891             .convert::<Toggle>("iommu")
1892             .map_err(Error::ParseDevice)?
1893             .unwrap_or(Toggle(false))
1894             .0;
1895         let id = parser.get("id");
1896         let pci_segment = parser
1897             .convert::<u16>("pci_segment")
1898             .map_err(Error::ParseDevice)?
1899             .unwrap_or_default();
1900 
1901         Ok(DeviceConfig {
1902             path,
1903             iommu,
1904             id,
1905             pci_segment,
1906         })
1907     }
1908 
1909     pub fn validate(&self, vm_config: &VmConfig) -> ValidationResult<()> {
1910         if let Some(platform_config) = vm_config.platform.as_ref() {
1911             if self.pci_segment >= platform_config.num_pci_segments {
1912                 return Err(ValidationError::InvalidPciSegment(self.pci_segment));
1913             }
1914 
1915             if let Some(iommu_segments) = platform_config.iommu_segments.as_ref() {
1916                 if iommu_segments.contains(&self.pci_segment) && !self.iommu {
1917                     return Err(ValidationError::OnIommuSegment(self.pci_segment));
1918                 }
1919             }
1920         }
1921 
1922         Ok(())
1923     }
1924 }
1925 
1926 #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, Default)]
1927 pub struct UserDeviceConfig {
1928     pub socket: PathBuf,
1929     #[serde(default)]
1930     pub id: Option<String>,
1931     #[serde(default)]
1932     pub pci_segment: u16,
1933 }
1934 
1935 impl UserDeviceConfig {
1936     pub const SYNTAX: &'static str =
1937         "Userspace device socket=<socket_path>,id=<device_id>,pci_segment=<segment_id>\"";
1938     pub fn parse(user_device: &str) -> Result<Self> {
1939         let mut parser = OptionParser::new();
1940         parser.add("socket").add("id").add("pci_segment");
1941         parser.parse(user_device).map_err(Error::ParseUserDevice)?;
1942 
1943         let socket = parser
1944             .get("socket")
1945             .map(PathBuf::from)
1946             .ok_or(Error::ParseUserDeviceSocketMissing)?;
1947         let id = parser.get("id");
1948         let pci_segment = parser
1949             .convert::<u16>("pci_segment")
1950             .map_err(Error::ParseUserDevice)?
1951             .unwrap_or_default();
1952 
1953         Ok(UserDeviceConfig {
1954             socket,
1955             id,
1956             pci_segment,
1957         })
1958     }
1959 
1960     pub fn validate(&self, vm_config: &VmConfig) -> ValidationResult<()> {
1961         if let Some(platform_config) = vm_config.platform.as_ref() {
1962             if self.pci_segment >= platform_config.num_pci_segments {
1963                 return Err(ValidationError::InvalidPciSegment(self.pci_segment));
1964             }
1965 
1966             if let Some(iommu_segments) = platform_config.iommu_segments.as_ref() {
1967                 if iommu_segments.contains(&self.pci_segment) {
1968                     return Err(ValidationError::IommuNotSupportedOnSegment(
1969                         self.pci_segment,
1970                     ));
1971                 }
1972             }
1973         }
1974 
1975         Ok(())
1976     }
1977 }
1978 
1979 #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, Default)]
1980 pub struct VdpaConfig {
1981     pub path: PathBuf,
1982     #[serde(default = "default_vdpaconfig_num_queues")]
1983     pub num_queues: usize,
1984     #[serde(default)]
1985     pub iommu: bool,
1986     #[serde(default)]
1987     pub id: Option<String>,
1988     #[serde(default)]
1989     pub pci_segment: u16,
1990 }
1991 
1992 fn default_vdpaconfig_num_queues() -> usize {
1993     1
1994 }
1995 
1996 impl VdpaConfig {
1997     pub const SYNTAX: &'static str = "vDPA device \
1998         \"path=<device_path>,num_queues=<number_of_queues>,iommu=on|off,\
1999         id=<device_id>,pci_segment=<segment_id>\"";
2000     pub fn parse(vdpa: &str) -> Result<Self> {
2001         let mut parser = OptionParser::new();
2002         parser
2003             .add("path")
2004             .add("num_queues")
2005             .add("iommu")
2006             .add("id")
2007             .add("pci_segment");
2008         parser.parse(vdpa).map_err(Error::ParseVdpa)?;
2009 
2010         let path = parser
2011             .get("path")
2012             .map(PathBuf::from)
2013             .ok_or(Error::ParseVdpaPathMissing)?;
2014         let num_queues = parser
2015             .convert("num_queues")
2016             .map_err(Error::ParseVdpa)?
2017             .unwrap_or_else(default_vdpaconfig_num_queues);
2018         let iommu = parser
2019             .convert::<Toggle>("iommu")
2020             .map_err(Error::ParseVdpa)?
2021             .unwrap_or(Toggle(false))
2022             .0;
2023         let id = parser.get("id");
2024         let pci_segment = parser
2025             .convert("pci_segment")
2026             .map_err(Error::ParseVdpa)?
2027             .unwrap_or_default();
2028 
2029         Ok(VdpaConfig {
2030             path,
2031             num_queues,
2032             iommu,
2033             id,
2034             pci_segment,
2035         })
2036     }
2037 
2038     pub fn validate(&self, vm_config: &VmConfig) -> ValidationResult<()> {
2039         if let Some(platform_config) = vm_config.platform.as_ref() {
2040             if self.pci_segment >= platform_config.num_pci_segments {
2041                 return Err(ValidationError::InvalidPciSegment(self.pci_segment));
2042             }
2043 
2044             if let Some(iommu_segments) = platform_config.iommu_segments.as_ref() {
2045                 if iommu_segments.contains(&self.pci_segment) && !self.iommu {
2046                     return Err(ValidationError::OnIommuSegment(self.pci_segment));
2047                 }
2048             }
2049         }
2050 
2051         Ok(())
2052     }
2053 }
2054 
2055 #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, Default)]
2056 pub struct VsockConfig {
2057     pub cid: u64,
2058     pub socket: PathBuf,
2059     #[serde(default)]
2060     pub iommu: bool,
2061     #[serde(default)]
2062     pub id: Option<String>,
2063     #[serde(default)]
2064     pub pci_segment: u16,
2065 }
2066 
2067 impl VsockConfig {
2068     pub const SYNTAX: &'static str = "Virtio VSOCK parameters \
2069         \"cid=<context_id>,socket=<socket_path>,iommu=on|off,id=<device_id>,pci_segment=<segment_id>\"";
2070     pub fn parse(vsock: &str) -> Result<Self> {
2071         let mut parser = OptionParser::new();
2072         parser
2073             .add("socket")
2074             .add("cid")
2075             .add("iommu")
2076             .add("id")
2077             .add("pci_segment");
2078         parser.parse(vsock).map_err(Error::ParseVsock)?;
2079 
2080         let socket = parser
2081             .get("socket")
2082             .map(PathBuf::from)
2083             .ok_or(Error::ParseVsockSockMissing)?;
2084         let iommu = parser
2085             .convert::<Toggle>("iommu")
2086             .map_err(Error::ParseVsock)?
2087             .unwrap_or(Toggle(false))
2088             .0;
2089         let cid = parser
2090             .convert("cid")
2091             .map_err(Error::ParseVsock)?
2092             .ok_or(Error::ParseVsockCidMissing)?;
2093         let id = parser.get("id");
2094         let pci_segment = parser
2095             .convert("pci_segment")
2096             .map_err(Error::ParseVsock)?
2097             .unwrap_or_default();
2098 
2099         Ok(VsockConfig {
2100             cid,
2101             socket,
2102             iommu,
2103             id,
2104             pci_segment,
2105         })
2106     }
2107 
2108     pub fn validate(&self, vm_config: &VmConfig) -> ValidationResult<()> {
2109         if let Some(platform_config) = vm_config.platform.as_ref() {
2110             if self.pci_segment >= platform_config.num_pci_segments {
2111                 return Err(ValidationError::InvalidPciSegment(self.pci_segment));
2112             }
2113 
2114             if let Some(iommu_segments) = platform_config.iommu_segments.as_ref() {
2115                 if iommu_segments.contains(&self.pci_segment) && !self.iommu {
2116                     return Err(ValidationError::OnIommuSegment(self.pci_segment));
2117                 }
2118             }
2119         }
2120 
2121         Ok(())
2122     }
2123 }
2124 
2125 #[cfg(target_arch = "x86_64")]
2126 #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, Default)]
2127 pub struct SgxEpcConfig {
2128     pub id: String,
2129     #[serde(default)]
2130     pub size: u64,
2131     #[serde(default)]
2132     pub prefault: bool,
2133 }
2134 
2135 #[cfg(target_arch = "x86_64")]
2136 impl SgxEpcConfig {
2137     pub const SYNTAX: &'static str = "SGX EPC parameters \
2138         \"id=<epc_section_identifier>,size=<epc_section_size>,prefault=on|off\"";
2139     pub fn parse(sgx_epc: &str) -> Result<Self> {
2140         let mut parser = OptionParser::new();
2141         parser.add("id").add("size").add("prefault");
2142         parser.parse(sgx_epc).map_err(Error::ParseSgxEpc)?;
2143 
2144         let id = parser.get("id").ok_or(Error::ParseSgxEpcIdMissing)?;
2145         let size = parser
2146             .convert::<ByteSized>("size")
2147             .map_err(Error::ParseSgxEpc)?
2148             .unwrap_or(ByteSized(0))
2149             .0;
2150         let prefault = parser
2151             .convert::<Toggle>("prefault")
2152             .map_err(Error::ParseSgxEpc)?
2153             .unwrap_or(Toggle(false))
2154             .0;
2155 
2156         Ok(SgxEpcConfig { id, size, prefault })
2157     }
2158 }
2159 
2160 #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, Default)]
2161 pub struct NumaDistance {
2162     #[serde(default)]
2163     pub destination: u32,
2164     #[serde(default)]
2165     pub distance: u8,
2166 }
2167 
2168 #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, Default)]
2169 pub struct NumaConfig {
2170     #[serde(default)]
2171     pub guest_numa_id: u32,
2172     #[serde(default)]
2173     pub cpus: Option<Vec<u8>>,
2174     #[serde(default)]
2175     pub distances: Option<Vec<NumaDistance>>,
2176     #[serde(default)]
2177     pub memory_zones: Option<Vec<String>>,
2178     #[cfg(target_arch = "x86_64")]
2179     #[serde(default)]
2180     pub sgx_epc_sections: Option<Vec<String>>,
2181 }
2182 
2183 impl NumaConfig {
2184     pub const SYNTAX: &'static str = "Settings related to a given NUMA node \
2185         \"guest_numa_id=<node_id>,cpus=<cpus_id>,distances=<list_of_distances_to_destination_nodes>,\
2186         memory_zones=<list_of_memory_zones>,sgx_epc_sections=<list_of_sgx_epc_sections>\"";
2187     pub fn parse(numa: &str) -> Result<Self> {
2188         let mut parser = OptionParser::new();
2189         parser
2190             .add("guest_numa_id")
2191             .add("cpus")
2192             .add("distances")
2193             .add("memory_zones")
2194             .add("sgx_epc_sections");
2195         parser.parse(numa).map_err(Error::ParseNuma)?;
2196 
2197         let guest_numa_id = parser
2198             .convert::<u32>("guest_numa_id")
2199             .map_err(Error::ParseNuma)?
2200             .unwrap_or(0);
2201         let cpus = parser
2202             .convert::<IntegerList>("cpus")
2203             .map_err(Error::ParseNuma)?
2204             .map(|v| v.0.iter().map(|e| *e as u8).collect());
2205         let distances = parser
2206             .convert::<Tuple<u64, u64>>("distances")
2207             .map_err(Error::ParseNuma)?
2208             .map(|v| {
2209                 v.0.iter()
2210                     .map(|(e1, e2)| NumaDistance {
2211                         destination: *e1 as u32,
2212                         distance: *e2 as u8,
2213                     })
2214                     .collect()
2215             });
2216         let memory_zones = parser
2217             .convert::<StringList>("memory_zones")
2218             .map_err(Error::ParseNuma)?
2219             .map(|v| v.0);
2220         #[cfg(target_arch = "x86_64")]
2221         let sgx_epc_sections = parser
2222             .convert::<StringList>("sgx_epc_sections")
2223             .map_err(Error::ParseNuma)?
2224             .map(|v| v.0);
2225 
2226         Ok(NumaConfig {
2227             guest_numa_id,
2228             cpus,
2229             distances,
2230             memory_zones,
2231             #[cfg(target_arch = "x86_64")]
2232             sgx_epc_sections,
2233         })
2234     }
2235 }
2236 
2237 #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, Default)]
2238 pub struct RestoreConfig {
2239     pub source_url: PathBuf,
2240     #[serde(default)]
2241     pub prefault: bool,
2242 }
2243 
2244 impl RestoreConfig {
2245     pub const SYNTAX: &'static str = "Restore from a VM snapshot. \
2246         \nRestore parameters \"source_url=<source_url>,prefault=on|off\" \
2247         \n`source_url` should be a valid URL (e.g file:///foo/bar or tcp://192.168.1.10/foo) \
2248         \n`prefault` brings memory pages in when enabled (disabled by default)";
2249     pub fn parse(restore: &str) -> Result<Self> {
2250         let mut parser = OptionParser::new();
2251         parser.add("source_url").add("prefault");
2252         parser.parse(restore).map_err(Error::ParseRestore)?;
2253 
2254         let source_url = parser
2255             .get("source_url")
2256             .map(PathBuf::from)
2257             .ok_or(Error::ParseRestoreSourceUrlMissing)?;
2258         let prefault = parser
2259             .convert::<Toggle>("prefault")
2260             .map_err(Error::ParseRestore)?
2261             .unwrap_or(Toggle(false))
2262             .0;
2263 
2264         Ok(RestoreConfig {
2265             source_url,
2266             prefault,
2267         })
2268     }
2269 }
2270 
2271 #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
2272 pub struct PayloadConfig {
2273     #[serde(default)]
2274     pub firmware: Option<PathBuf>,
2275     #[serde(default)]
2276     pub kernel: Option<PathBuf>,
2277     #[serde(default)]
2278     pub cmdline: Option<String>,
2279     #[serde(default)]
2280     pub initramfs: Option<PathBuf>,
2281 }
2282 
2283 #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
2284 pub struct VmConfig {
2285     #[serde(default)]
2286     pub cpus: CpusConfig,
2287     #[serde(default)]
2288     pub memory: MemoryConfig,
2289     pub kernel: Option<KernelConfig>,
2290     #[serde(default)]
2291     pub initramfs: Option<InitramfsConfig>,
2292     #[serde(default)]
2293     pub cmdline: CmdlineConfig,
2294     #[serde(default)]
2295     pub payload: Option<PayloadConfig>,
2296     pub disks: Option<Vec<DiskConfig>>,
2297     pub net: Option<Vec<NetConfig>>,
2298     #[serde(default)]
2299     pub rng: RngConfig,
2300     pub balloon: Option<BalloonConfig>,
2301     pub fs: Option<Vec<FsConfig>>,
2302     pub pmem: Option<Vec<PmemConfig>>,
2303     #[serde(default = "ConsoleConfig::default_serial")]
2304     pub serial: ConsoleConfig,
2305     #[serde(default = "ConsoleConfig::default_console")]
2306     pub console: ConsoleConfig,
2307     pub devices: Option<Vec<DeviceConfig>>,
2308     pub user_devices: Option<Vec<UserDeviceConfig>>,
2309     pub vdpa: Option<Vec<VdpaConfig>>,
2310     pub vsock: Option<VsockConfig>,
2311     #[serde(default)]
2312     pub iommu: bool,
2313     #[cfg(target_arch = "x86_64")]
2314     pub sgx_epc: Option<Vec<SgxEpcConfig>>,
2315     pub numa: Option<Vec<NumaConfig>>,
2316     #[serde(default)]
2317     pub watchdog: bool,
2318     #[cfg(feature = "guest_debug")]
2319     pub gdb: bool,
2320     pub platform: Option<PlatformConfig>,
2321 }
2322 
2323 impl VmConfig {
2324     fn validate_identifier(
2325         id_list: &mut BTreeSet<String>,
2326         id: &Option<String>,
2327     ) -> ValidationResult<()> {
2328         if let Some(id) = id.as_ref() {
2329             if id.starts_with("__") {
2330                 return Err(ValidationError::InvalidIdentifier(id.clone()));
2331             }
2332 
2333             if !id_list.insert(id.clone()) {
2334                 return Err(ValidationError::IdentifierNotUnique(id.clone()));
2335             }
2336         }
2337 
2338         Ok(())
2339     }
2340 
2341     // Also enables virtio-iommu if the config needs it
2342     // Returns the list of unique identifiers provided through the
2343     // configuration.
2344     pub fn validate(&mut self) -> ValidationResult<BTreeSet<String>> {
2345         let mut id_list = BTreeSet::new();
2346 
2347         if self.kernel.is_some() {
2348             warn!("The \"VmConfig\" members \"kernel\", \"cmdline\" and \"initramfs\" are deprecated. Use \"payload\" member instead.");
2349             self.payload = Some(PayloadConfig {
2350                 kernel: self.kernel.take().map(|k| k.path),
2351                 cmdline: if self.cmdline.args.is_empty() {
2352                     None
2353                 } else {
2354                     Some(self.cmdline.args.drain(..).collect())
2355                 },
2356                 initramfs: self.initramfs.take().map(|i| i.path),
2357                 ..Default::default()
2358             })
2359         }
2360 
2361         self.payload
2362             .as_ref()
2363             .ok_or(ValidationError::KernelMissing)?;
2364 
2365         #[cfg(feature = "tdx")]
2366         {
2367             let tdx_enabled = self.platform.as_ref().map(|p| p.tdx).unwrap_or(false);
2368             // At this point we know payload isn't None.
2369             if tdx_enabled && self.payload.as_ref().unwrap().firmware.is_none() {
2370                 return Err(ValidationError::TdxFirmwareMissing);
2371             }
2372             if tdx_enabled && (self.cpus.max_vcpus != self.cpus.boot_vcpus) {
2373                 return Err(ValidationError::TdxNoCpuHotplug);
2374             }
2375         }
2376 
2377         if self.console.mode == ConsoleOutputMode::Tty && self.serial.mode == ConsoleOutputMode::Tty
2378         {
2379             return Err(ValidationError::DoubleTtyMode);
2380         }
2381 
2382         if self.console.mode == ConsoleOutputMode::File && self.console.file.is_none() {
2383             return Err(ValidationError::ConsoleFileMissing);
2384         }
2385 
2386         if self.serial.mode == ConsoleOutputMode::File && self.serial.file.is_none() {
2387             return Err(ValidationError::ConsoleFileMissing);
2388         }
2389 
2390         if self.cpus.max_vcpus < self.cpus.boot_vcpus {
2391             return Err(ValidationError::CpusMaxLowerThanBoot);
2392         }
2393 
2394         if let Some(disks) = &self.disks {
2395             for disk in disks {
2396                 if disk.vhost_socket.as_ref().and(disk.path.as_ref()).is_some() {
2397                     return Err(ValidationError::DiskSocketAndPath);
2398                 }
2399                 if disk.vhost_user && !self.memory.shared {
2400                     return Err(ValidationError::VhostUserRequiresSharedMemory);
2401                 }
2402                 if disk.vhost_user && disk.vhost_socket.is_none() {
2403                     return Err(ValidationError::VhostUserMissingSocket);
2404                 }
2405                 disk.validate(self)?;
2406                 self.iommu |= disk.iommu;
2407 
2408                 Self::validate_identifier(&mut id_list, &disk.id)?;
2409             }
2410         }
2411 
2412         if let Some(nets) = &self.net {
2413             for net in nets {
2414                 if net.vhost_user && !self.memory.shared {
2415                     return Err(ValidationError::VhostUserRequiresSharedMemory);
2416                 }
2417                 net.validate(self)?;
2418                 self.iommu |= net.iommu;
2419 
2420                 Self::validate_identifier(&mut id_list, &net.id)?;
2421             }
2422         }
2423 
2424         if let Some(fses) = &self.fs {
2425             if !fses.is_empty() && !self.memory.shared {
2426                 return Err(ValidationError::VhostUserRequiresSharedMemory);
2427             }
2428             for fs in fses {
2429                 fs.validate(self)?;
2430 
2431                 Self::validate_identifier(&mut id_list, &fs.id)?;
2432             }
2433         }
2434 
2435         if let Some(pmems) = &self.pmem {
2436             for pmem in pmems {
2437                 pmem.validate(self)?;
2438                 self.iommu |= pmem.iommu;
2439 
2440                 Self::validate_identifier(&mut id_list, &pmem.id)?;
2441             }
2442         }
2443 
2444         self.iommu |= self.rng.iommu;
2445         self.iommu |= self.console.iommu;
2446 
2447         if let Some(t) = &self.cpus.topology {
2448             if t.threads_per_core == 0
2449                 || t.cores_per_die == 0
2450                 || t.dies_per_package == 0
2451                 || t.packages == 0
2452             {
2453                 return Err(ValidationError::CpuTopologyZeroPart);
2454             }
2455 
2456             // The setting of dies doesen't apply on AArch64.
2457             // Only '1' value is accepted, so its impact on the vcpu topology
2458             // setting can be ignored.
2459             #[cfg(target_arch = "aarch64")]
2460             if t.dies_per_package != 1 {
2461                 return Err(ValidationError::CpuTopologyDiesPerPackage);
2462             }
2463 
2464             let total = t.threads_per_core * t.cores_per_die * t.dies_per_package * t.packages;
2465             if total != self.cpus.max_vcpus {
2466                 return Err(ValidationError::CpuTopologyCount);
2467             }
2468         }
2469 
2470         if let Some(hugepage_size) = &self.memory.hugepage_size {
2471             if !self.memory.hugepages {
2472                 return Err(ValidationError::HugePageSizeWithoutHugePages);
2473             }
2474             if !hugepage_size.is_power_of_two() {
2475                 return Err(ValidationError::InvalidHugePageSize(*hugepage_size));
2476             }
2477         }
2478 
2479         if let Some(user_devices) = &self.user_devices {
2480             if !user_devices.is_empty() && !self.memory.shared {
2481                 return Err(ValidationError::UserDevicesRequireSharedMemory);
2482             }
2483 
2484             for user_device in user_devices {
2485                 user_device.validate(self)?;
2486 
2487                 Self::validate_identifier(&mut id_list, &user_device.id)?;
2488             }
2489         }
2490 
2491         if let Some(vdpa_devices) = &self.vdpa {
2492             for vdpa_device in vdpa_devices {
2493                 vdpa_device.validate(self)?;
2494                 self.iommu |= vdpa_device.iommu;
2495 
2496                 Self::validate_identifier(&mut id_list, &vdpa_device.id)?;
2497             }
2498         }
2499 
2500         if let Some(balloon) = &self.balloon {
2501             let mut ram_size = self.memory.size;
2502 
2503             if let Some(zones) = &self.memory.zones {
2504                 for zone in zones {
2505                     ram_size += zone.size;
2506                 }
2507             }
2508 
2509             if balloon.size >= ram_size {
2510                 return Err(ValidationError::BalloonLargerThanRam(
2511                     balloon.size,
2512                     ram_size,
2513                 ));
2514             }
2515         }
2516 
2517         if let Some(devices) = &self.devices {
2518             let mut device_paths = BTreeSet::new();
2519             for device in devices {
2520                 if !device_paths.insert(device.path.to_string_lossy()) {
2521                     return Err(ValidationError::DuplicateDevicePath(
2522                         device.path.to_string_lossy().to_string(),
2523                     ));
2524                 }
2525 
2526                 device.validate(self)?;
2527                 self.iommu |= device.iommu;
2528 
2529                 Self::validate_identifier(&mut id_list, &device.id)?;
2530             }
2531         }
2532 
2533         if let Some(vsock) = &self.vsock {
2534             vsock.validate(self)?;
2535             self.iommu |= vsock.iommu;
2536 
2537             Self::validate_identifier(&mut id_list, &vsock.id)?;
2538         }
2539 
2540         if let Some(numa) = &self.numa {
2541             let mut used_numa_node_memory_zones = HashMap::new();
2542             for numa_node in numa.iter() {
2543                 for memory_zone in numa_node.memory_zones.clone().unwrap().iter() {
2544                     if !used_numa_node_memory_zones.contains_key(memory_zone) {
2545                         used_numa_node_memory_zones
2546                             .insert(memory_zone.to_string(), numa_node.guest_numa_id);
2547                     } else {
2548                         return Err(ValidationError::MemoryZoneReused(
2549                             memory_zone.to_string(),
2550                             *used_numa_node_memory_zones.get(memory_zone).unwrap(),
2551                             numa_node.guest_numa_id,
2552                         ));
2553                     }
2554                 }
2555             }
2556         }
2557 
2558         if let Some(zones) = &self.memory.zones {
2559             for zone in zones.iter() {
2560                 let id = zone.id.clone();
2561                 Self::validate_identifier(&mut id_list, &Some(id))?;
2562             }
2563         }
2564 
2565         #[cfg(target_arch = "x86_64")]
2566         if let Some(sgx_epcs) = &self.sgx_epc {
2567             for sgx_epc in sgx_epcs.iter() {
2568                 let id = sgx_epc.id.clone();
2569                 Self::validate_identifier(&mut id_list, &Some(id))?;
2570             }
2571         }
2572 
2573         self.platform.as_ref().map(|p| p.validate()).transpose()?;
2574         self.iommu |= self
2575             .platform
2576             .as_ref()
2577             .map(|p| p.iommu_segments.is_some())
2578             .unwrap_or_default();
2579 
2580         Ok(id_list)
2581     }
2582 
2583     pub fn parse(vm_params: VmParams) -> Result<Self> {
2584         let mut disks: Option<Vec<DiskConfig>> = None;
2585         if let Some(disk_list) = &vm_params.disks {
2586             let mut disk_config_list = Vec::new();
2587             for item in disk_list.iter() {
2588                 let disk_config = DiskConfig::parse(item)?;
2589                 disk_config_list.push(disk_config);
2590             }
2591             disks = Some(disk_config_list);
2592         }
2593 
2594         let mut net: Option<Vec<NetConfig>> = None;
2595         if let Some(net_list) = &vm_params.net {
2596             let mut net_config_list = Vec::new();
2597             for item in net_list.iter() {
2598                 let net_config = NetConfig::parse(item)?;
2599                 net_config_list.push(net_config);
2600             }
2601             net = Some(net_config_list);
2602         }
2603 
2604         let rng = RngConfig::parse(vm_params.rng)?;
2605 
2606         let mut balloon: Option<BalloonConfig> = None;
2607         if let Some(balloon_params) = &vm_params.balloon {
2608             balloon = Some(BalloonConfig::parse(balloon_params)?);
2609         }
2610 
2611         let mut fs: Option<Vec<FsConfig>> = None;
2612         if let Some(fs_list) = &vm_params.fs {
2613             let mut fs_config_list = Vec::new();
2614             for item in fs_list.iter() {
2615                 fs_config_list.push(FsConfig::parse(item)?);
2616             }
2617             fs = Some(fs_config_list);
2618         }
2619 
2620         let mut pmem: Option<Vec<PmemConfig>> = None;
2621         if let Some(pmem_list) = &vm_params.pmem {
2622             let mut pmem_config_list = Vec::new();
2623             for item in pmem_list.iter() {
2624                 let pmem_config = PmemConfig::parse(item)?;
2625                 pmem_config_list.push(pmem_config);
2626             }
2627             pmem = Some(pmem_config_list);
2628         }
2629 
2630         let console = ConsoleConfig::parse(vm_params.console)?;
2631         let serial = ConsoleConfig::parse(vm_params.serial)?;
2632 
2633         let mut devices: Option<Vec<DeviceConfig>> = None;
2634         if let Some(device_list) = &vm_params.devices {
2635             let mut device_config_list = Vec::new();
2636             for item in device_list.iter() {
2637                 let device_config = DeviceConfig::parse(item)?;
2638                 device_config_list.push(device_config);
2639             }
2640             devices = Some(device_config_list);
2641         }
2642 
2643         let mut user_devices: Option<Vec<UserDeviceConfig>> = None;
2644         if let Some(user_device_list) = &vm_params.user_devices {
2645             let mut user_device_config_list = Vec::new();
2646             for item in user_device_list.iter() {
2647                 let user_device_config = UserDeviceConfig::parse(item)?;
2648                 user_device_config_list.push(user_device_config);
2649             }
2650             user_devices = Some(user_device_config_list);
2651         }
2652 
2653         let mut vdpa: Option<Vec<VdpaConfig>> = None;
2654         if let Some(vdpa_list) = &vm_params.vdpa {
2655             let mut vdpa_config_list = Vec::new();
2656             for item in vdpa_list.iter() {
2657                 let vdpa_config = VdpaConfig::parse(item)?;
2658                 vdpa_config_list.push(vdpa_config);
2659             }
2660             vdpa = Some(vdpa_config_list);
2661         }
2662 
2663         let mut vsock: Option<VsockConfig> = None;
2664         if let Some(vs) = &vm_params.vsock {
2665             let vsock_config = VsockConfig::parse(vs)?;
2666             vsock = Some(vsock_config);
2667         }
2668 
2669         let platform = vm_params.platform.map(PlatformConfig::parse).transpose()?;
2670 
2671         #[cfg(target_arch = "x86_64")]
2672         let mut sgx_epc: Option<Vec<SgxEpcConfig>> = None;
2673         #[cfg(target_arch = "x86_64")]
2674         {
2675             if let Some(sgx_epc_list) = &vm_params.sgx_epc {
2676                 let mut sgx_epc_config_list = Vec::new();
2677                 for item in sgx_epc_list.iter() {
2678                     let sgx_epc_config = SgxEpcConfig::parse(item)?;
2679                     sgx_epc_config_list.push(sgx_epc_config);
2680                 }
2681                 sgx_epc = Some(sgx_epc_config_list);
2682             }
2683         }
2684 
2685         let mut numa: Option<Vec<NumaConfig>> = None;
2686         if let Some(numa_list) = &vm_params.numa {
2687             let mut numa_config_list = Vec::new();
2688             for item in numa_list.iter() {
2689                 let numa_config = NumaConfig::parse(item)?;
2690                 numa_config_list.push(numa_config);
2691             }
2692             numa = Some(numa_config_list);
2693         }
2694 
2695         let payload = if vm_params.kernel.is_some() || vm_params.firmware.is_some() {
2696             Some(PayloadConfig {
2697                 kernel: vm_params.kernel.map(PathBuf::from),
2698                 initramfs: vm_params.initramfs.map(PathBuf::from),
2699                 cmdline: vm_params.cmdline.map(|s| s.to_string()),
2700                 firmware: vm_params.firmware.map(PathBuf::from),
2701             })
2702         } else {
2703             None
2704         };
2705 
2706         #[cfg(feature = "guest_debug")]
2707         let gdb = vm_params.gdb;
2708 
2709         let mut config = VmConfig {
2710             cpus: CpusConfig::parse(vm_params.cpus)?,
2711             memory: MemoryConfig::parse(vm_params.memory, vm_params.memory_zones)?,
2712             kernel: None,
2713             initramfs: None,
2714             cmdline: CmdlineConfig::default(),
2715             payload,
2716             disks,
2717             net,
2718             rng,
2719             balloon,
2720             fs,
2721             pmem,
2722             serial,
2723             console,
2724             devices,
2725             user_devices,
2726             vdpa,
2727             vsock,
2728             iommu: false, // updated in VmConfig::validate()
2729             #[cfg(target_arch = "x86_64")]
2730             sgx_epc,
2731             numa,
2732             watchdog: vm_params.watchdog,
2733             #[cfg(feature = "guest_debug")]
2734             gdb,
2735             platform,
2736         };
2737         config.validate().map_err(Error::Validation)?;
2738         Ok(config)
2739     }
2740 
2741     #[cfg(feature = "tdx")]
2742     pub fn is_tdx_enabled(&self) -> bool {
2743         self.platform.as_ref().map(|p| p.tdx).unwrap_or(false)
2744     }
2745 }
2746 
2747 #[cfg(test)]
2748 mod tests {
2749     use super::*;
2750 
2751     #[test]
2752     fn test_cpu_parsing() -> Result<()> {
2753         assert_eq!(CpusConfig::parse("")?, CpusConfig::default());
2754 
2755         assert_eq!(
2756             CpusConfig::parse("boot=1")?,
2757             CpusConfig {
2758                 boot_vcpus: 1,
2759                 max_vcpus: 1,
2760                 ..Default::default()
2761             }
2762         );
2763         assert_eq!(
2764             CpusConfig::parse("boot=1,max=2")?,
2765             CpusConfig {
2766                 boot_vcpus: 1,
2767                 max_vcpus: 2,
2768                 ..Default::default()
2769             }
2770         );
2771         assert_eq!(
2772             CpusConfig::parse("boot=8,topology=2:2:1:2")?,
2773             CpusConfig {
2774                 boot_vcpus: 8,
2775                 max_vcpus: 8,
2776                 topology: Some(CpuTopology {
2777                     threads_per_core: 2,
2778                     cores_per_die: 2,
2779                     dies_per_package: 1,
2780                     packages: 2
2781                 }),
2782                 ..Default::default()
2783             }
2784         );
2785 
2786         assert!(CpusConfig::parse("boot=8,topology=2:2:1").is_err());
2787         assert!(CpusConfig::parse("boot=8,topology=2:2:1:x").is_err());
2788         assert_eq!(
2789             CpusConfig::parse("boot=1,kvm_hyperv=on")?,
2790             CpusConfig {
2791                 boot_vcpus: 1,
2792                 max_vcpus: 1,
2793                 kvm_hyperv: true,
2794                 ..Default::default()
2795             }
2796         );
2797         assert_eq!(
2798             CpusConfig::parse("boot=2,affinity=[0@[0,2],1@[1,3]]")?,
2799             CpusConfig {
2800                 boot_vcpus: 2,
2801                 max_vcpus: 2,
2802                 affinity: Some(vec![
2803                     CpuAffinity {
2804                         vcpu: 0,
2805                         host_cpus: vec![0, 2],
2806                     },
2807                     CpuAffinity {
2808                         vcpu: 1,
2809                         host_cpus: vec![1, 3],
2810                     }
2811                 ]),
2812                 ..Default::default()
2813             },
2814         );
2815 
2816         Ok(())
2817     }
2818 
2819     #[test]
2820     fn test_mem_parsing() -> Result<()> {
2821         assert_eq!(MemoryConfig::parse("", None)?, MemoryConfig::default());
2822         // Default string
2823         assert_eq!(
2824             MemoryConfig::parse("size=512M", None)?,
2825             MemoryConfig::default()
2826         );
2827         assert_eq!(
2828             MemoryConfig::parse("size=512M,mergeable=on", None)?,
2829             MemoryConfig {
2830                 size: 512 << 20,
2831                 mergeable: true,
2832                 ..Default::default()
2833             }
2834         );
2835         assert_eq!(
2836             MemoryConfig::parse("mergeable=on", None)?,
2837             MemoryConfig {
2838                 mergeable: true,
2839                 ..Default::default()
2840             }
2841         );
2842         assert_eq!(
2843             MemoryConfig::parse("size=1G,mergeable=off", None)?,
2844             MemoryConfig {
2845                 size: 1 << 30,
2846                 mergeable: false,
2847                 ..Default::default()
2848             }
2849         );
2850         assert_eq!(
2851             MemoryConfig::parse("hotplug_method=acpi", None)?,
2852             MemoryConfig {
2853                 ..Default::default()
2854             }
2855         );
2856         assert_eq!(
2857             MemoryConfig::parse("hotplug_method=acpi,hotplug_size=512M", None)?,
2858             MemoryConfig {
2859                 hotplug_size: Some(512 << 20),
2860                 ..Default::default()
2861             }
2862         );
2863         assert_eq!(
2864             MemoryConfig::parse("hotplug_method=virtio-mem,hotplug_size=512M", None)?,
2865             MemoryConfig {
2866                 hotplug_size: Some(512 << 20),
2867                 hotplug_method: HotplugMethod::VirtioMem,
2868                 ..Default::default()
2869             }
2870         );
2871         assert_eq!(
2872             MemoryConfig::parse("hugepages=on,size=1G,hugepage_size=2M", None)?,
2873             MemoryConfig {
2874                 hugepage_size: Some(2 << 20),
2875                 size: 1 << 30,
2876                 hugepages: true,
2877                 ..Default::default()
2878             }
2879         );
2880         Ok(())
2881     }
2882 
2883     #[test]
2884     fn test_disk_parsing() -> Result<()> {
2885         assert_eq!(
2886             DiskConfig::parse("path=/path/to_file")?,
2887             DiskConfig {
2888                 path: Some(PathBuf::from("/path/to_file")),
2889                 ..Default::default()
2890             }
2891         );
2892         assert_eq!(
2893             DiskConfig::parse("path=/path/to_file,id=mydisk0")?,
2894             DiskConfig {
2895                 path: Some(PathBuf::from("/path/to_file")),
2896                 id: Some("mydisk0".to_owned()),
2897                 ..Default::default()
2898             }
2899         );
2900         assert_eq!(
2901             DiskConfig::parse("vhost_user=true,socket=/tmp/sock")?,
2902             DiskConfig {
2903                 vhost_socket: Some(String::from("/tmp/sock")),
2904                 vhost_user: true,
2905                 ..Default::default()
2906             }
2907         );
2908         assert_eq!(
2909             DiskConfig::parse("path=/path/to_file,iommu=on")?,
2910             DiskConfig {
2911                 path: Some(PathBuf::from("/path/to_file")),
2912                 iommu: true,
2913                 ..Default::default()
2914             }
2915         );
2916         assert_eq!(
2917             DiskConfig::parse("path=/path/to_file,iommu=on,queue_size=256")?,
2918             DiskConfig {
2919                 path: Some(PathBuf::from("/path/to_file")),
2920                 iommu: true,
2921                 queue_size: 256,
2922                 ..Default::default()
2923             }
2924         );
2925         assert_eq!(
2926             DiskConfig::parse("path=/path/to_file,iommu=on,queue_size=256,num_queues=4")?,
2927             DiskConfig {
2928                 path: Some(PathBuf::from("/path/to_file")),
2929                 iommu: true,
2930                 queue_size: 256,
2931                 num_queues: 4,
2932                 ..Default::default()
2933             }
2934         );
2935         assert_eq!(
2936             DiskConfig::parse("path=/path/to_file,direct=on")?,
2937             DiskConfig {
2938                 path: Some(PathBuf::from("/path/to_file")),
2939                 direct: true,
2940                 ..Default::default()
2941             }
2942         );
2943         assert_eq!(
2944             DiskConfig::parse("path=/path/to_file")?,
2945             DiskConfig {
2946                 path: Some(PathBuf::from("/path/to_file")),
2947                 ..Default::default()
2948             }
2949         );
2950         assert_eq!(
2951             DiskConfig::parse("path=/path/to_file")?,
2952             DiskConfig {
2953                 path: Some(PathBuf::from("/path/to_file")),
2954                 ..Default::default()
2955             }
2956         );
2957 
2958         Ok(())
2959     }
2960 
2961     #[test]
2962     fn test_net_parsing() -> Result<()> {
2963         // mac address is random
2964         assert_eq!(
2965             NetConfig::parse("mac=de:ad:be:ef:12:34,host_mac=12:34:de:ad:be:ef")?,
2966             NetConfig {
2967                 mac: MacAddr::parse_str("de:ad:be:ef:12:34").unwrap(),
2968                 host_mac: Some(MacAddr::parse_str("12:34:de:ad:be:ef").unwrap()),
2969                 ..Default::default()
2970             }
2971         );
2972 
2973         assert_eq!(
2974             NetConfig::parse("mac=de:ad:be:ef:12:34,host_mac=12:34:de:ad:be:ef,id=mynet0")?,
2975             NetConfig {
2976                 mac: MacAddr::parse_str("de:ad:be:ef:12:34").unwrap(),
2977                 host_mac: Some(MacAddr::parse_str("12:34:de:ad:be:ef").unwrap()),
2978                 id: Some("mynet0".to_owned()),
2979                 ..Default::default()
2980             }
2981         );
2982 
2983         assert_eq!(
2984             NetConfig::parse(
2985                 "mac=de:ad:be:ef:12:34,host_mac=12:34:de:ad:be:ef,tap=tap0,ip=192.168.100.1,mask=255.255.255.128"
2986             )?,
2987             NetConfig {
2988                 mac: MacAddr::parse_str("de:ad:be:ef:12:34").unwrap(),
2989                 host_mac: Some(MacAddr::parse_str("12:34:de:ad:be:ef").unwrap()),
2990                 tap: Some("tap0".to_owned()),
2991                 ip: "192.168.100.1".parse().unwrap(),
2992                 mask: "255.255.255.128".parse().unwrap(),
2993                 ..Default::default()
2994             }
2995         );
2996 
2997         assert_eq!(
2998             NetConfig::parse(
2999                 "mac=de:ad:be:ef:12:34,host_mac=12:34:de:ad:be:ef,vhost_user=true,socket=/tmp/sock"
3000             )?,
3001             NetConfig {
3002                 mac: MacAddr::parse_str("de:ad:be:ef:12:34").unwrap(),
3003                 host_mac: Some(MacAddr::parse_str("12:34:de:ad:be:ef").unwrap()),
3004                 vhost_user: true,
3005                 vhost_socket: Some("/tmp/sock".to_owned()),
3006                 ..Default::default()
3007             }
3008         );
3009 
3010         assert_eq!(
3011             NetConfig::parse("mac=de:ad:be:ef:12:34,host_mac=12:34:de:ad:be:ef,num_queues=4,queue_size=1024,iommu=on")?,
3012             NetConfig {
3013                 mac: MacAddr::parse_str("de:ad:be:ef:12:34").unwrap(),
3014                 host_mac: Some(MacAddr::parse_str("12:34:de:ad:be:ef").unwrap()),
3015                 num_queues: 4,
3016                 queue_size: 1024,
3017                 iommu: true,
3018                 ..Default::default()
3019             }
3020         );
3021 
3022         assert_eq!(
3023             NetConfig::parse("mac=de:ad:be:ef:12:34,fd=[3,7],num_queues=4")?,
3024             NetConfig {
3025                 mac: MacAddr::parse_str("de:ad:be:ef:12:34").unwrap(),
3026                 fds: Some(vec![3, 7]),
3027                 num_queues: 4,
3028                 ..Default::default()
3029             }
3030         );
3031 
3032         Ok(())
3033     }
3034 
3035     #[test]
3036     fn test_parse_rng() -> Result<()> {
3037         assert_eq!(RngConfig::parse("")?, RngConfig::default());
3038         assert_eq!(
3039             RngConfig::parse("src=/dev/random")?,
3040             RngConfig {
3041                 src: PathBuf::from("/dev/random"),
3042                 ..Default::default()
3043             }
3044         );
3045         assert_eq!(
3046             RngConfig::parse("src=/dev/random,iommu=on")?,
3047             RngConfig {
3048                 src: PathBuf::from("/dev/random"),
3049                 iommu: true,
3050             }
3051         );
3052         assert_eq!(
3053             RngConfig::parse("iommu=on")?,
3054             RngConfig {
3055                 iommu: true,
3056                 ..Default::default()
3057             }
3058         );
3059         Ok(())
3060     }
3061 
3062     #[test]
3063     fn test_parse_fs() -> Result<()> {
3064         // "tag" and "socket" must be supplied
3065         assert!(FsConfig::parse("").is_err());
3066         assert!(FsConfig::parse("tag=mytag").is_err());
3067         assert!(FsConfig::parse("socket=/tmp/sock").is_err());
3068         assert_eq!(
3069             FsConfig::parse("tag=mytag,socket=/tmp/sock")?,
3070             FsConfig {
3071                 socket: PathBuf::from("/tmp/sock"),
3072                 tag: "mytag".to_owned(),
3073                 ..Default::default()
3074             }
3075         );
3076         assert_eq!(
3077             FsConfig::parse("tag=mytag,socket=/tmp/sock")?,
3078             FsConfig {
3079                 socket: PathBuf::from("/tmp/sock"),
3080                 tag: "mytag".to_owned(),
3081                 ..Default::default()
3082             }
3083         );
3084         assert_eq!(
3085             FsConfig::parse("tag=mytag,socket=/tmp/sock,num_queues=4,queue_size=1024")?,
3086             FsConfig {
3087                 socket: PathBuf::from("/tmp/sock"),
3088                 tag: "mytag".to_owned(),
3089                 num_queues: 4,
3090                 queue_size: 1024,
3091                 ..Default::default()
3092             }
3093         );
3094 
3095         Ok(())
3096     }
3097 
3098     #[test]
3099     fn test_pmem_parsing() -> Result<()> {
3100         // Must always give a file and size
3101         assert!(PmemConfig::parse("").is_err());
3102         assert!(PmemConfig::parse("size=128M").is_err());
3103         assert_eq!(
3104             PmemConfig::parse("file=/tmp/pmem,size=128M")?,
3105             PmemConfig {
3106                 file: PathBuf::from("/tmp/pmem"),
3107                 size: Some(128 << 20),
3108                 ..Default::default()
3109             }
3110         );
3111         assert_eq!(
3112             PmemConfig::parse("file=/tmp/pmem,size=128M,id=mypmem0")?,
3113             PmemConfig {
3114                 file: PathBuf::from("/tmp/pmem"),
3115                 size: Some(128 << 20),
3116                 id: Some("mypmem0".to_owned()),
3117                 ..Default::default()
3118             }
3119         );
3120         assert_eq!(
3121             PmemConfig::parse("file=/tmp/pmem,size=128M,iommu=on,discard_writes=on")?,
3122             PmemConfig {
3123                 file: PathBuf::from("/tmp/pmem"),
3124                 size: Some(128 << 20),
3125                 discard_writes: true,
3126                 iommu: true,
3127                 ..Default::default()
3128             }
3129         );
3130 
3131         Ok(())
3132     }
3133 
3134     #[test]
3135     fn test_console_parsing() -> Result<()> {
3136         assert!(ConsoleConfig::parse("").is_err());
3137         assert!(ConsoleConfig::parse("badmode").is_err());
3138         assert_eq!(
3139             ConsoleConfig::parse("off")?,
3140             ConsoleConfig {
3141                 mode: ConsoleOutputMode::Off,
3142                 iommu: false,
3143                 file: None,
3144             }
3145         );
3146         assert_eq!(
3147             ConsoleConfig::parse("pty")?,
3148             ConsoleConfig {
3149                 mode: ConsoleOutputMode::Pty,
3150                 iommu: false,
3151                 file: None,
3152             }
3153         );
3154         assert_eq!(
3155             ConsoleConfig::parse("tty")?,
3156             ConsoleConfig {
3157                 mode: ConsoleOutputMode::Tty,
3158                 iommu: false,
3159                 file: None,
3160             }
3161         );
3162         assert_eq!(
3163             ConsoleConfig::parse("null")?,
3164             ConsoleConfig {
3165                 mode: ConsoleOutputMode::Null,
3166                 iommu: false,
3167                 file: None,
3168             }
3169         );
3170         assert_eq!(
3171             ConsoleConfig::parse("file=/tmp/console")?,
3172             ConsoleConfig {
3173                 mode: ConsoleOutputMode::File,
3174                 iommu: false,
3175                 file: Some(PathBuf::from("/tmp/console"))
3176             }
3177         );
3178         assert_eq!(
3179             ConsoleConfig::parse("null,iommu=on")?,
3180             ConsoleConfig {
3181                 mode: ConsoleOutputMode::Null,
3182                 iommu: true,
3183                 file: None,
3184             }
3185         );
3186         assert_eq!(
3187             ConsoleConfig::parse("file=/tmp/console,iommu=on")?,
3188             ConsoleConfig {
3189                 mode: ConsoleOutputMode::File,
3190                 iommu: true,
3191                 file: Some(PathBuf::from("/tmp/console"))
3192             }
3193         );
3194         Ok(())
3195     }
3196 
3197     #[test]
3198     fn test_device_parsing() -> Result<()> {
3199         // Device must have a path provided
3200         assert!(DeviceConfig::parse("").is_err());
3201         assert_eq!(
3202             DeviceConfig::parse("path=/path/to/device")?,
3203             DeviceConfig {
3204                 path: PathBuf::from("/path/to/device"),
3205                 id: None,
3206                 iommu: false,
3207                 ..Default::default()
3208             }
3209         );
3210 
3211         assert_eq!(
3212             DeviceConfig::parse("path=/path/to/device,iommu=on")?,
3213             DeviceConfig {
3214                 path: PathBuf::from("/path/to/device"),
3215                 id: None,
3216                 iommu: true,
3217                 ..Default::default()
3218             }
3219         );
3220 
3221         assert_eq!(
3222             DeviceConfig::parse("path=/path/to/device,iommu=on,id=mydevice0")?,
3223             DeviceConfig {
3224                 path: PathBuf::from("/path/to/device"),
3225                 id: Some("mydevice0".to_owned()),
3226                 iommu: true,
3227                 ..Default::default()
3228             }
3229         );
3230 
3231         Ok(())
3232     }
3233 
3234     #[test]
3235     fn test_vdpa_parsing() -> Result<()> {
3236         // path is required
3237         assert!(VdpaConfig::parse("").is_err());
3238         assert_eq!(
3239             VdpaConfig::parse("path=/dev/vhost-vdpa")?,
3240             VdpaConfig {
3241                 path: PathBuf::from("/dev/vhost-vdpa"),
3242                 num_queues: 1,
3243                 id: None,
3244                 ..Default::default()
3245             }
3246         );
3247         assert_eq!(
3248             VdpaConfig::parse("path=/dev/vhost-vdpa,num_queues=2,id=my_vdpa")?,
3249             VdpaConfig {
3250                 path: PathBuf::from("/dev/vhost-vdpa"),
3251                 num_queues: 2,
3252                 id: Some("my_vdpa".to_owned()),
3253                 ..Default::default()
3254             }
3255         );
3256         Ok(())
3257     }
3258 
3259     #[test]
3260     fn test_vsock_parsing() -> Result<()> {
3261         // socket and cid is required
3262         assert!(VsockConfig::parse("").is_err());
3263         assert_eq!(
3264             VsockConfig::parse("socket=/tmp/sock,cid=1")?,
3265             VsockConfig {
3266                 cid: 1,
3267                 socket: PathBuf::from("/tmp/sock"),
3268                 iommu: false,
3269                 id: None,
3270                 ..Default::default()
3271             }
3272         );
3273         assert_eq!(
3274             VsockConfig::parse("socket=/tmp/sock,cid=1,iommu=on")?,
3275             VsockConfig {
3276                 cid: 1,
3277                 socket: PathBuf::from("/tmp/sock"),
3278                 iommu: true,
3279                 id: None,
3280                 ..Default::default()
3281             }
3282         );
3283         Ok(())
3284     }
3285 
3286     #[test]
3287     fn test_config_validation() {
3288         let mut valid_config = VmConfig {
3289             cpus: CpusConfig {
3290                 boot_vcpus: 1,
3291                 max_vcpus: 1,
3292                 ..Default::default()
3293             },
3294             memory: MemoryConfig {
3295                 size: 536_870_912,
3296                 mergeable: false,
3297                 hotplug_method: HotplugMethod::Acpi,
3298                 hotplug_size: None,
3299                 hotplugged_size: None,
3300                 shared: false,
3301                 hugepages: false,
3302                 hugepage_size: None,
3303                 prefault: false,
3304                 zones: None,
3305             },
3306             kernel: None,
3307             cmdline: CmdlineConfig {
3308                 args: String::default(),
3309             },
3310             initramfs: None,
3311             payload: Some(PayloadConfig {
3312                 kernel: Some(PathBuf::from("/path/to/kernel")),
3313                 ..Default::default()
3314             }),
3315             disks: None,
3316             net: None,
3317             rng: RngConfig {
3318                 src: PathBuf::from("/dev/urandom"),
3319                 iommu: false,
3320             },
3321             balloon: None,
3322             fs: None,
3323             pmem: None,
3324             serial: ConsoleConfig {
3325                 file: None,
3326                 mode: ConsoleOutputMode::Null,
3327                 iommu: false,
3328             },
3329             console: ConsoleConfig {
3330                 file: None,
3331                 mode: ConsoleOutputMode::Tty,
3332                 iommu: false,
3333             },
3334             devices: None,
3335             user_devices: None,
3336             vdpa: None,
3337             vsock: None,
3338             iommu: false,
3339             #[cfg(target_arch = "x86_64")]
3340             sgx_epc: None,
3341             numa: None,
3342             watchdog: false,
3343             #[cfg(feature = "guest_debug")]
3344             gdb: false,
3345             platform: None,
3346         };
3347 
3348         assert!(valid_config.validate().is_ok());
3349 
3350         let mut invalid_config = valid_config.clone();
3351         invalid_config.serial.mode = ConsoleOutputMode::Tty;
3352         invalid_config.console.mode = ConsoleOutputMode::Tty;
3353         assert_eq!(
3354             invalid_config.validate(),
3355             Err(ValidationError::DoubleTtyMode)
3356         );
3357 
3358         let mut invalid_config = valid_config.clone();
3359         invalid_config.payload = None;
3360         assert_eq!(
3361             invalid_config.validate(),
3362             Err(ValidationError::KernelMissing)
3363         );
3364 
3365         let mut invalid_config = valid_config.clone();
3366         invalid_config.serial.mode = ConsoleOutputMode::File;
3367         invalid_config.serial.file = None;
3368         assert_eq!(
3369             invalid_config.validate(),
3370             Err(ValidationError::ConsoleFileMissing)
3371         );
3372 
3373         let mut invalid_config = valid_config.clone();
3374         invalid_config.cpus.max_vcpus = 16;
3375         invalid_config.cpus.boot_vcpus = 32;
3376         assert_eq!(
3377             invalid_config.validate(),
3378             Err(ValidationError::CpusMaxLowerThanBoot)
3379         );
3380 
3381         let mut invalid_config = valid_config.clone();
3382         invalid_config.cpus.max_vcpus = 16;
3383         invalid_config.cpus.boot_vcpus = 16;
3384         invalid_config.cpus.topology = Some(CpuTopology {
3385             threads_per_core: 2,
3386             cores_per_die: 8,
3387             dies_per_package: 1,
3388             packages: 2,
3389         });
3390         assert_eq!(
3391             invalid_config.validate(),
3392             Err(ValidationError::CpuTopologyCount)
3393         );
3394 
3395         let mut invalid_config = valid_config.clone();
3396         invalid_config.disks = Some(vec![DiskConfig {
3397             vhost_socket: Some("/path/to/sock".to_owned()),
3398             path: Some(PathBuf::from("/path/to/image")),
3399             ..Default::default()
3400         }]);
3401         assert_eq!(
3402             invalid_config.validate(),
3403             Err(ValidationError::DiskSocketAndPath)
3404         );
3405 
3406         let mut invalid_config = valid_config.clone();
3407         invalid_config.memory.shared = true;
3408         invalid_config.disks = Some(vec![DiskConfig {
3409             vhost_user: true,
3410             ..Default::default()
3411         }]);
3412         assert_eq!(
3413             invalid_config.validate(),
3414             Err(ValidationError::VhostUserMissingSocket)
3415         );
3416 
3417         let mut invalid_config = valid_config.clone();
3418         invalid_config.disks = Some(vec![DiskConfig {
3419             vhost_user: true,
3420             vhost_socket: Some("/path/to/sock".to_owned()),
3421             ..Default::default()
3422         }]);
3423         assert_eq!(
3424             invalid_config.validate(),
3425             Err(ValidationError::VhostUserRequiresSharedMemory)
3426         );
3427 
3428         let mut still_valid_config = valid_config.clone();
3429         still_valid_config.disks = Some(vec![DiskConfig {
3430             vhost_user: true,
3431             vhost_socket: Some("/path/to/sock".to_owned()),
3432             ..Default::default()
3433         }]);
3434         still_valid_config.memory.shared = true;
3435         assert!(still_valid_config.validate().is_ok());
3436 
3437         let mut invalid_config = valid_config.clone();
3438         invalid_config.net = Some(vec![NetConfig {
3439             vhost_user: true,
3440             ..Default::default()
3441         }]);
3442         assert_eq!(
3443             invalid_config.validate(),
3444             Err(ValidationError::VhostUserRequiresSharedMemory)
3445         );
3446 
3447         let mut still_valid_config = valid_config.clone();
3448         still_valid_config.net = Some(vec![NetConfig {
3449             vhost_user: true,
3450             vhost_socket: Some("/path/to/sock".to_owned()),
3451             ..Default::default()
3452         }]);
3453         still_valid_config.memory.shared = true;
3454         assert!(still_valid_config.validate().is_ok());
3455 
3456         let mut invalid_config = valid_config.clone();
3457         invalid_config.net = Some(vec![NetConfig {
3458             fds: Some(vec![0]),
3459             ..Default::default()
3460         }]);
3461         assert_eq!(
3462             invalid_config.validate(),
3463             Err(ValidationError::VnetReservedFd)
3464         );
3465 
3466         let mut invalid_config = valid_config.clone();
3467         invalid_config.fs = Some(vec![FsConfig {
3468             ..Default::default()
3469         }]);
3470         assert_eq!(
3471             invalid_config.validate(),
3472             Err(ValidationError::VhostUserRequiresSharedMemory)
3473         );
3474 
3475         let mut still_valid_config = valid_config.clone();
3476         still_valid_config.memory.shared = true;
3477         assert!(still_valid_config.validate().is_ok());
3478 
3479         let mut still_valid_config = valid_config.clone();
3480         still_valid_config.memory.hugepages = true;
3481         assert!(still_valid_config.validate().is_ok());
3482 
3483         let mut still_valid_config = valid_config.clone();
3484         still_valid_config.memory.hugepages = true;
3485         still_valid_config.memory.hugepage_size = Some(2 << 20);
3486         assert!(still_valid_config.validate().is_ok());
3487 
3488         let mut invalid_config = valid_config.clone();
3489         invalid_config.memory.hugepages = false;
3490         invalid_config.memory.hugepage_size = Some(2 << 20);
3491         assert_eq!(
3492             invalid_config.validate(),
3493             Err(ValidationError::HugePageSizeWithoutHugePages)
3494         );
3495 
3496         let mut invalid_config = valid_config.clone();
3497         invalid_config.memory.hugepages = true;
3498         invalid_config.memory.hugepage_size = Some(3 << 20);
3499         assert_eq!(
3500             invalid_config.validate(),
3501             Err(ValidationError::InvalidHugePageSize(3 << 20))
3502         );
3503 
3504         let mut still_valid_config = valid_config.clone();
3505         still_valid_config.platform = Some(PlatformConfig {
3506             num_pci_segments: 16,
3507             ..Default::default()
3508         });
3509         assert!(still_valid_config.validate().is_ok());
3510 
3511         let mut invalid_config = valid_config.clone();
3512         invalid_config.platform = Some(PlatformConfig {
3513             num_pci_segments: 17,
3514             ..Default::default()
3515         });
3516         assert_eq!(
3517             invalid_config.validate(),
3518             Err(ValidationError::InvalidNumPciSegments(17))
3519         );
3520 
3521         let mut still_valid_config = valid_config.clone();
3522         still_valid_config.platform = Some(PlatformConfig {
3523             num_pci_segments: 16,
3524             iommu_segments: Some(vec![1, 2, 3]),
3525             ..Default::default()
3526         });
3527         assert!(still_valid_config.validate().is_ok());
3528 
3529         let mut invalid_config = valid_config.clone();
3530         invalid_config.platform = Some(PlatformConfig {
3531             num_pci_segments: 16,
3532             iommu_segments: Some(vec![17, 18]),
3533             ..Default::default()
3534         });
3535         assert_eq!(
3536             invalid_config.validate(),
3537             Err(ValidationError::InvalidPciSegment(17))
3538         );
3539 
3540         let mut still_valid_config = valid_config.clone();
3541         still_valid_config.platform = Some(PlatformConfig {
3542             num_pci_segments: 16,
3543             iommu_segments: Some(vec![1, 2, 3]),
3544             ..Default::default()
3545         });
3546         still_valid_config.disks = Some(vec![DiskConfig {
3547             iommu: true,
3548             pci_segment: 1,
3549             ..Default::default()
3550         }]);
3551         assert!(still_valid_config.validate().is_ok());
3552 
3553         let mut still_valid_config = valid_config.clone();
3554         still_valid_config.platform = Some(PlatformConfig {
3555             num_pci_segments: 16,
3556             iommu_segments: Some(vec![1, 2, 3]),
3557             ..Default::default()
3558         });
3559         still_valid_config.net = Some(vec![NetConfig {
3560             iommu: true,
3561             pci_segment: 1,
3562             ..Default::default()
3563         }]);
3564         assert!(still_valid_config.validate().is_ok());
3565 
3566         let mut still_valid_config = valid_config.clone();
3567         still_valid_config.platform = Some(PlatformConfig {
3568             num_pci_segments: 16,
3569             iommu_segments: Some(vec![1, 2, 3]),
3570             ..Default::default()
3571         });
3572         still_valid_config.pmem = Some(vec![PmemConfig {
3573             iommu: true,
3574             pci_segment: 1,
3575             ..Default::default()
3576         }]);
3577         assert!(still_valid_config.validate().is_ok());
3578 
3579         let mut still_valid_config = valid_config.clone();
3580         still_valid_config.platform = Some(PlatformConfig {
3581             num_pci_segments: 16,
3582             iommu_segments: Some(vec![1, 2, 3]),
3583             ..Default::default()
3584         });
3585         still_valid_config.devices = Some(vec![DeviceConfig {
3586             iommu: true,
3587             pci_segment: 1,
3588             ..Default::default()
3589         }]);
3590         assert!(still_valid_config.validate().is_ok());
3591 
3592         let mut still_valid_config = valid_config.clone();
3593         still_valid_config.platform = Some(PlatformConfig {
3594             num_pci_segments: 16,
3595             iommu_segments: Some(vec![1, 2, 3]),
3596             ..Default::default()
3597         });
3598         still_valid_config.vsock = Some(VsockConfig {
3599             iommu: true,
3600             pci_segment: 1,
3601             ..Default::default()
3602         });
3603         assert!(still_valid_config.validate().is_ok());
3604 
3605         let mut invalid_config = valid_config.clone();
3606         invalid_config.platform = Some(PlatformConfig {
3607             num_pci_segments: 16,
3608             iommu_segments: Some(vec![1, 2, 3]),
3609             ..Default::default()
3610         });
3611         invalid_config.disks = Some(vec![DiskConfig {
3612             iommu: false,
3613             pci_segment: 1,
3614             ..Default::default()
3615         }]);
3616         assert_eq!(
3617             invalid_config.validate(),
3618             Err(ValidationError::OnIommuSegment(1))
3619         );
3620 
3621         let mut invalid_config = valid_config.clone();
3622         invalid_config.platform = Some(PlatformConfig {
3623             num_pci_segments: 16,
3624             iommu_segments: Some(vec![1, 2, 3]),
3625             ..Default::default()
3626         });
3627         invalid_config.net = Some(vec![NetConfig {
3628             iommu: false,
3629             pci_segment: 1,
3630             ..Default::default()
3631         }]);
3632         assert_eq!(
3633             invalid_config.validate(),
3634             Err(ValidationError::OnIommuSegment(1))
3635         );
3636 
3637         let mut invalid_config = valid_config.clone();
3638         invalid_config.platform = Some(PlatformConfig {
3639             num_pci_segments: 16,
3640             iommu_segments: Some(vec![1, 2, 3]),
3641             ..Default::default()
3642         });
3643         invalid_config.pmem = Some(vec![PmemConfig {
3644             iommu: false,
3645             pci_segment: 1,
3646             ..Default::default()
3647         }]);
3648         assert_eq!(
3649             invalid_config.validate(),
3650             Err(ValidationError::OnIommuSegment(1))
3651         );
3652 
3653         let mut invalid_config = valid_config.clone();
3654         invalid_config.platform = Some(PlatformConfig {
3655             num_pci_segments: 16,
3656             iommu_segments: Some(vec![1, 2, 3]),
3657             ..Default::default()
3658         });
3659         invalid_config.devices = Some(vec![DeviceConfig {
3660             iommu: false,
3661             pci_segment: 1,
3662             ..Default::default()
3663         }]);
3664         assert_eq!(
3665             invalid_config.validate(),
3666             Err(ValidationError::OnIommuSegment(1))
3667         );
3668 
3669         let mut invalid_config = valid_config.clone();
3670         invalid_config.platform = Some(PlatformConfig {
3671             num_pci_segments: 16,
3672             iommu_segments: Some(vec![1, 2, 3]),
3673             ..Default::default()
3674         });
3675         invalid_config.vsock = Some(VsockConfig {
3676             iommu: false,
3677             pci_segment: 1,
3678             ..Default::default()
3679         });
3680         assert_eq!(
3681             invalid_config.validate(),
3682             Err(ValidationError::OnIommuSegment(1))
3683         );
3684 
3685         let mut invalid_config = valid_config.clone();
3686         invalid_config.memory.shared = true;
3687         invalid_config.platform = Some(PlatformConfig {
3688             num_pci_segments: 16,
3689             iommu_segments: Some(vec![1, 2, 3]),
3690             ..Default::default()
3691         });
3692         invalid_config.user_devices = Some(vec![UserDeviceConfig {
3693             pci_segment: 1,
3694             ..Default::default()
3695         }]);
3696         assert_eq!(
3697             invalid_config.validate(),
3698             Err(ValidationError::IommuNotSupportedOnSegment(1))
3699         );
3700 
3701         let mut invalid_config = valid_config.clone();
3702         invalid_config.platform = Some(PlatformConfig {
3703             num_pci_segments: 16,
3704             iommu_segments: Some(vec![1, 2, 3]),
3705             ..Default::default()
3706         });
3707         invalid_config.vdpa = Some(vec![VdpaConfig {
3708             pci_segment: 1,
3709             ..Default::default()
3710         }]);
3711         assert_eq!(
3712             invalid_config.validate(),
3713             Err(ValidationError::OnIommuSegment(1))
3714         );
3715 
3716         let mut invalid_config = valid_config.clone();
3717         invalid_config.memory.shared = true;
3718         invalid_config.platform = Some(PlatformConfig {
3719             num_pci_segments: 16,
3720             iommu_segments: Some(vec![1, 2, 3]),
3721             ..Default::default()
3722         });
3723         invalid_config.fs = Some(vec![FsConfig {
3724             pci_segment: 1,
3725             ..Default::default()
3726         }]);
3727         assert_eq!(
3728             invalid_config.validate(),
3729             Err(ValidationError::IommuNotSupportedOnSegment(1))
3730         );
3731 
3732         let mut still_valid_config = valid_config.clone();
3733         still_valid_config.devices = Some(vec![
3734             DeviceConfig {
3735                 path: "/device1".into(),
3736                 ..Default::default()
3737             },
3738             DeviceConfig {
3739                 path: "/device2".into(),
3740                 ..Default::default()
3741             },
3742         ]);
3743         assert!(still_valid_config.validate().is_ok());
3744 
3745         let mut invalid_config = valid_config;
3746         invalid_config.devices = Some(vec![
3747             DeviceConfig {
3748                 path: "/device1".into(),
3749                 ..Default::default()
3750             },
3751             DeviceConfig {
3752                 path: "/device1".into(),
3753                 ..Default::default()
3754             },
3755         ]);
3756         assert!(invalid_config.validate().is_err());
3757     }
3758 }
3759