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