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