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