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