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