• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2022 The ChromiumOS Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #[cfg(target_arch = "x86_64")]
6 use std::arch::x86_64::__cpuid;
7 #[cfg(target_arch = "x86_64")]
8 use std::arch::x86_64::__cpuid_count;
9 use std::collections::BTreeMap;
10 use std::path::PathBuf;
11 use std::str::FromStr;
12 use std::time::Duration;
13 
14 use arch::set_default_serial_parameters;
15 use arch::CpuSet;
16 use arch::FdtPosition;
17 use arch::PciConfig;
18 use arch::Pstore;
19 #[cfg(target_arch = "x86_64")]
20 use arch::SmbiosOptions;
21 #[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
22 use arch::SveConfig;
23 use arch::VcpuAffinity;
24 use base::debug;
25 use base::pagesize;
26 use cros_async::ExecutorKind;
27 use devices::serial_device::SerialHardware;
28 use devices::serial_device::SerialParameters;
29 use devices::virtio::block::DiskOption;
30 #[cfg(any(feature = "video-decoder", feature = "video-encoder"))]
31 use devices::virtio::device_constants::video::VideoDeviceConfig;
32 #[cfg(feature = "gpu")]
33 use devices::virtio::gpu::GpuParameters;
34 use devices::virtio::scsi::ScsiOption;
35 #[cfg(feature = "audio")]
36 use devices::virtio::snd::parameters::Parameters as SndParameters;
37 #[cfg(all(windows, feature = "gpu"))]
38 use devices::virtio::vhost::user::device::gpu::sys::windows::GpuBackendConfig;
39 #[cfg(all(windows, feature = "gpu"))]
40 use devices::virtio::vhost::user::device::gpu::sys::windows::GpuVmmConfig;
41 #[cfg(all(windows, feature = "gpu"))]
42 use devices::virtio::vhost::user::device::gpu::sys::windows::InputEventSplitConfig;
43 #[cfg(all(windows, feature = "gpu"))]
44 use devices::virtio::vhost::user::device::gpu::sys::windows::WindowProcedureThreadSplitConfig;
45 #[cfg(all(windows, feature = "audio"))]
46 use devices::virtio::vhost::user::device::snd::sys::windows::SndSplitConfig;
47 use devices::virtio::vsock::VsockConfig;
48 use devices::virtio::DeviceType;
49 #[cfg(feature = "net")]
50 use devices::virtio::NetParameters;
51 use devices::FwCfgParameters;
52 use devices::PciAddress;
53 use devices::PflashParameters;
54 use devices::StubPciParameters;
55 #[cfg(target_arch = "x86_64")]
56 use hypervisor::CpuHybridType;
57 use hypervisor::ProtectionType;
58 use jail::JailConfig;
59 use resources::AddressRange;
60 use serde::Deserialize;
61 use serde::Deserializer;
62 use serde::Serialize;
63 use serde_keyvalue::FromKeyValues;
64 use vm_control::BatteryType;
65 use vm_memory::FileBackedMappingParameters;
66 #[cfg(target_arch = "x86_64")]
67 use x86_64::check_host_hybrid_support;
68 #[cfg(target_arch = "x86_64")]
69 use x86_64::CpuIdCall;
70 
71 pub(crate) use super::sys::HypervisorKind;
72 #[cfg(any(target_os = "android", target_os = "linux"))]
73 use crate::crosvm::sys::config::SharedDir;
74 
75 cfg_if::cfg_if! {
76     if #[cfg(any(target_os = "android", target_os = "linux"))] {
77         #[cfg(feature = "gpu")]
78         use crate::crosvm::sys::GpuRenderServerParameters;
79 
80         #[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
81         static VHOST_SCMI_PATH: &str = "/dev/vhost-scmi";
82     } else if #[cfg(windows)] {
83         use base::{Event, Tube};
84     }
85 }
86 
87 // by default, if enabled, the balloon WS features will use 4 bins.
88 #[cfg(feature = "balloon")]
89 const VIRTIO_BALLOON_WS_DEFAULT_NUM_BINS: u8 = 4;
90 
91 /// Indicates the location and kind of executable kernel for a VM.
92 #[allow(dead_code)]
93 #[derive(Debug, Serialize, Deserialize)]
94 pub enum Executable {
95     /// An executable intended to be run as a BIOS directly.
96     Bios(PathBuf),
97     /// A elf linux kernel, loaded and executed by crosvm.
98     Kernel(PathBuf),
99     /// Path to a plugin executable that is forked by crosvm.
100     Plugin(PathBuf),
101 }
102 
103 #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, FromKeyValues)]
104 #[serde(deny_unknown_fields, rename_all = "kebab-case")]
105 pub enum IrqChipKind {
106     /// All interrupt controllers are emulated in the kernel.
107     Kernel,
108     /// APIC is emulated in the kernel.  All other interrupt controllers are in userspace.
109     Split,
110     /// All interrupt controllers are emulated in userspace.
111     Userspace,
112 }
113 
114 /// The core types in hybrid architecture.
115 #[cfg(target_arch = "x86_64")]
116 #[derive(Debug, PartialEq, Eq, Deserialize, Serialize)]
117 #[serde(deny_unknown_fields, rename_all = "kebab-case")]
118 pub struct CpuCoreType {
119     /// Intel Atom.
120     pub atom: CpuSet,
121     /// Intel Core.
122     pub core: CpuSet,
123 }
124 
125 #[derive(Debug, Default, PartialEq, Eq, Deserialize, Serialize, FromKeyValues)]
126 #[serde(deny_unknown_fields, rename_all = "kebab-case")]
127 pub struct CpuOptions {
128     /// Number of CPU cores.
129     #[serde(default)]
130     pub num_cores: Option<usize>,
131     /// Vector of CPU ids to be grouped into the same cluster.
132     #[serde(default)]
133     pub clusters: Vec<CpuSet>,
134     /// Core Type of CPUs.
135     #[cfg(target_arch = "x86_64")]
136     pub core_types: Option<CpuCoreType>,
137     /// Select which CPU to boot from.
138     #[serde(default)]
139     pub boot_cpu: Option<usize>,
140     /// Vector of CPU ids to be grouped into the same freq domain.
141     #[serde(default)]
142     pub freq_domains: Vec<CpuSet>,
143     /// Scalable Vector Extension.
144     #[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
145     pub sve: Option<SveConfig>,
146 }
147 
148 /// Device tree overlay configuration.
149 #[derive(Debug, Default, Serialize, Deserialize, FromKeyValues)]
150 #[serde(deny_unknown_fields, rename_all = "kebab-case")]
151 pub struct DtboOption {
152     /// Overlay file to apply to the base device tree.
153     pub path: PathBuf,
154     /// Whether to only apply device tree nodes which belong to a VFIO device.
155     #[serde(rename = "filter", default)]
156     pub filter_devs: bool,
157 }
158 
159 #[derive(Debug, Default, Deserialize, Serialize, FromKeyValues, PartialEq, Eq)]
160 #[serde(deny_unknown_fields, rename_all = "kebab-case")]
161 pub struct MemOptions {
162     /// Amount of guest memory in MiB.
163     #[serde(default)]
164     pub size: Option<u64>,
165 }
166 
deserialize_swap_interval<'de, D: Deserializer<'de>>( deserializer: D, ) -> Result<Option<Duration>, D::Error>167 fn deserialize_swap_interval<'de, D: Deserializer<'de>>(
168     deserializer: D,
169 ) -> Result<Option<Duration>, D::Error> {
170     let ms = Option::<u64>::deserialize(deserializer)?;
171     match ms {
172         None => Ok(None),
173         Some(ms) => Ok(Some(Duration::from_millis(ms))),
174     }
175 }
176 
177 #[derive(
178     Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, serde_keyvalue::FromKeyValues,
179 )]
180 #[serde(deny_unknown_fields, rename_all = "kebab-case")]
181 pub struct PmemOption {
182     /// Path to the diks image.
183     pub path: PathBuf,
184     /// Whether the disk is read-only.
185     #[serde(default)]
186     pub ro: bool,
187     /// If set, add a kernel command line option making this the root device. Can only be set once.
188     #[serde(default)]
189     pub root: bool,
190     /// Experimental option to specify the size in bytes of an anonymous virtual memory area that
191     /// will be created to back this device.
192     #[serde(default)]
193     pub vma_size: Option<u64>,
194     /// Experimental option to specify interval for periodic swap out of memory mapping
195     #[serde(
196         default,
197         deserialize_with = "deserialize_swap_interval",
198         rename = "swap-interval-ms"
199     )]
200     pub swap_interval: Option<Duration>,
201 }
202 
203 #[derive(Serialize, Deserialize, FromKeyValues)]
204 #[serde(deny_unknown_fields, rename_all = "kebab-case")]
205 pub struct VhostUserOption {
206     pub socket: PathBuf,
207 
208     /// Maximum number of entries per queue (default: 32768)
209     pub max_queue_size: Option<u16>,
210 }
211 
212 #[derive(Serialize, Deserialize, FromKeyValues)]
213 #[serde(deny_unknown_fields, rename_all = "kebab-case")]
214 pub struct VhostUserFrontendOption {
215     /// Device type
216     #[serde(rename = "type")]
217     pub type_: devices::virtio::DeviceType,
218 
219     /// Path to the vhost-user backend socket to connect to
220     pub socket: PathBuf,
221 
222     /// Maximum number of entries per queue (default: 32768)
223     pub max_queue_size: Option<u16>,
224 
225     /// Preferred PCI address
226     pub pci_address: Option<PciAddress>,
227 }
228 
229 #[derive(Serialize, Deserialize, FromKeyValues)]
230 #[serde(deny_unknown_fields, rename_all = "kebab-case")]
231 pub struct VhostUserFsOption {
232     #[serde(alias = "socket")]
233     pub socket_path: Option<PathBuf>,
234     /// File descriptor of connected socket
235     pub socket_fd: Option<u32>,
236     pub tag: Option<String>,
237 
238     /// Maximum number of entries per queue (default: 32768)
239     pub max_queue_size: Option<u16>,
240 }
241 
parse_vhost_user_fs_option(param: &str) -> Result<VhostUserFsOption, String>242 pub fn parse_vhost_user_fs_option(param: &str) -> Result<VhostUserFsOption, String> {
243     // Allow the previous `--vhost-user-fs /path/to/socket:fs-tag` format for compatibility.
244     // This will unfortunately prevent parsing of valid comma-separated FromKeyValues options that
245     // contain a ":" character (e.g. in a socket filename), but those were not supported in the old
246     // format either, so we can live with it until the deprecated format is removed.
247     // TODO(b/218223240): Remove support for the deprecated format (and use `FromKeyValues`
248     // directly instead of `from_str_fn`) once enough time has passed.
249     if param.contains(':') {
250         // (socket:tag)
251         let mut components = param.split(':');
252         let socket = PathBuf::from(
253             components
254                 .next()
255                 .ok_or("missing socket path for `vhost-user-fs`")?,
256         );
257         let tag = components
258             .next()
259             .ok_or("missing tag for `vhost-user-fs`")?
260             .to_owned();
261 
262         log::warn!(
263             "`--vhost-user-fs` with colon-separated options is deprecated; \
264             please use `--vhost-user-fs {},tag={}` instead",
265             socket.display(),
266             tag,
267         );
268 
269         Ok(VhostUserFsOption {
270             socket_path: Some(socket),
271             tag: Some(tag),
272             max_queue_size: None,
273             socket_fd: None,
274         })
275     } else {
276         from_key_values::<VhostUserFsOption>(param)
277     }
278 }
279 
280 pub const DEFAULT_TOUCH_DEVICE_HEIGHT: u32 = 1024;
281 pub const DEFAULT_TOUCH_DEVICE_WIDTH: u32 = 1280;
282 
283 #[derive(Serialize, Deserialize, Debug, FromKeyValues)]
284 #[serde(deny_unknown_fields, rename_all = "kebab-case")]
285 pub struct TouchDeviceOption {
286     pub path: PathBuf,
287     pub width: Option<u32>,
288     pub height: Option<u32>,
289     pub name: Option<String>,
290 }
291 
292 /// Try to parse a colon-separated touch device option.
293 ///
294 /// The expected format is "PATH:WIDTH:HEIGHT:NAME", with all fields except PATH being optional.
parse_touch_device_option_legacy(s: &str) -> Option<TouchDeviceOption>295 fn parse_touch_device_option_legacy(s: &str) -> Option<TouchDeviceOption> {
296     let mut it = s.split(':');
297     let path = PathBuf::from(it.next()?.to_owned());
298     let width = if let Some(width) = it.next() {
299         Some(width.trim().parse().ok()?)
300     } else {
301         None
302     };
303     let height = if let Some(height) = it.next() {
304         Some(height.trim().parse().ok()?)
305     } else {
306         None
307     };
308     let name = it.next().map(|name| name.trim().to_string());
309     if it.next().is_some() {
310         return None;
311     }
312 
313     Some(TouchDeviceOption {
314         path,
315         width,
316         height,
317         name,
318     })
319 }
320 
321 /// Parse virtio-input touch device options from a string.
322 ///
323 /// This function only exists to enable the use of the deprecated colon-separated form
324 /// ("PATH:WIDTH:HEIGHT:NAME"); once the deprecation period is over, this function should be removed
325 /// in favor of using the derived `FromKeyValues` function directly.
parse_touch_device_option(s: &str) -> Result<TouchDeviceOption, String>326 pub fn parse_touch_device_option(s: &str) -> Result<TouchDeviceOption, String> {
327     if s.contains(':') {
328         if let Some(touch_spec) = parse_touch_device_option_legacy(s) {
329             log::warn!(
330                 "colon-separated touch device options are deprecated; \
331                 please use --input instead"
332             );
333             return Ok(touch_spec);
334         }
335     }
336 
337     from_key_values::<TouchDeviceOption>(s)
338 }
339 
340 /// virtio-input device configuration
341 #[derive(Serialize, Deserialize, Debug, FromKeyValues, Eq, PartialEq)]
342 #[serde(deny_unknown_fields, rename_all = "kebab-case")]
343 pub enum InputDeviceOption {
344     Evdev {
345         path: PathBuf,
346     },
347     Keyboard {
348         path: PathBuf,
349     },
350     Mouse {
351         path: PathBuf,
352     },
353     MultiTouch {
354         path: PathBuf,
355         width: Option<u32>,
356         height: Option<u32>,
357         name: Option<String>,
358     },
359     Rotary {
360         path: PathBuf,
361     },
362     SingleTouch {
363         path: PathBuf,
364         width: Option<u32>,
365         height: Option<u32>,
366         name: Option<String>,
367     },
368     Switches {
369         path: PathBuf,
370     },
371     Trackpad {
372         path: PathBuf,
373         width: Option<u32>,
374         height: Option<u32>,
375         name: Option<String>,
376     },
377     MultiTouchTrackpad {
378         path: PathBuf,
379         width: Option<u32>,
380         height: Option<u32>,
381         name: Option<String>,
382     },
383     #[serde(rename_all = "kebab-case")]
384     Custom {
385         path: PathBuf,
386         config_path: PathBuf,
387     },
388 }
389 
parse_hex_or_decimal(maybe_hex_string: &str) -> Result<u64, String>390 fn parse_hex_or_decimal(maybe_hex_string: &str) -> Result<u64, String> {
391     // Parse string starting with 0x as hex and others as numbers.
392     if let Some(hex_string) = maybe_hex_string.strip_prefix("0x") {
393         u64::from_str_radix(hex_string, 16)
394     } else if let Some(hex_string) = maybe_hex_string.strip_prefix("0X") {
395         u64::from_str_radix(hex_string, 16)
396     } else {
397         u64::from_str(maybe_hex_string)
398     }
399     .map_err(|e| format!("invalid numeric value {}: {}", maybe_hex_string, e))
400 }
401 
parse_mmio_address_range(s: &str) -> Result<Vec<AddressRange>, String>402 pub fn parse_mmio_address_range(s: &str) -> Result<Vec<AddressRange>, String> {
403     s.split(",")
404         .map(|s| {
405             let r: Vec<&str> = s.split("-").collect();
406             if r.len() != 2 {
407                 return Err(invalid_value_err(s, "invalid range"));
408             }
409             let parse = |s: &str| -> Result<u64, String> {
410                 match parse_hex_or_decimal(s) {
411                     Ok(v) => Ok(v),
412                     Err(_) => Err(invalid_value_err(s, "expected u64 value")),
413                 }
414             };
415             Ok(AddressRange {
416                 start: parse(r[0])?,
417                 end: parse(r[1])?,
418             })
419         })
420         .collect()
421 }
422 
validate_serial_parameters(params: &SerialParameters) -> Result<(), String>423 pub fn validate_serial_parameters(params: &SerialParameters) -> Result<(), String> {
424     if params.stdin && params.input.is_some() {
425         return Err("Cannot specify both stdin and input options".to_string());
426     }
427     if params.num < 1 {
428         return Err(invalid_value_err(
429             params.num.to_string(),
430             "Serial port num must be at least 1",
431         ));
432     }
433 
434     if params.hardware == SerialHardware::Serial && params.num > 4 {
435         return Err(invalid_value_err(
436             format!("{}", params.num),
437             "Serial port num must be 4 or less",
438         ));
439     }
440 
441     if params.pci_address.is_some() && params.hardware != SerialHardware::VirtioConsole {
442         return Err(invalid_value_err(
443             params.pci_address.unwrap().to_string(),
444             "Providing serial PCI address is only supported for virtio-console hardware type",
445         ));
446     }
447 
448     Ok(())
449 }
450 
parse_serial_options(s: &str) -> Result<SerialParameters, String>451 pub fn parse_serial_options(s: &str) -> Result<SerialParameters, String> {
452     let params: SerialParameters = from_key_values(s)?;
453 
454     validate_serial_parameters(&params)?;
455 
456     Ok(params)
457 }
458 
parse_bus_id_addr(v: &str) -> Result<(u8, u8, u16, u16), String>459 pub fn parse_bus_id_addr(v: &str) -> Result<(u8, u8, u16, u16), String> {
460     debug!("parse_bus_id_addr: {}", v);
461     let mut ids = v.split(':');
462     let errorre = move |item| move |e| format!("{}: {}", item, e);
463     match (ids.next(), ids.next(), ids.next(), ids.next()) {
464         (Some(bus_id), Some(addr), Some(vid), Some(pid)) => {
465             let bus_id = bus_id.parse::<u8>().map_err(errorre("bus_id"))?;
466             let addr = addr.parse::<u8>().map_err(errorre("addr"))?;
467             let vid = u16::from_str_radix(vid, 16).map_err(errorre("vid"))?;
468             let pid = u16::from_str_radix(pid, 16).map_err(errorre("pid"))?;
469             Ok((bus_id, addr, vid, pid))
470         }
471         _ => Err(String::from("BUS_ID:ADDR:BUS_NUM:DEV_NUM")),
472     }
473 }
474 
invalid_value_err<T: AsRef<str>, S: ToString>(value: T, expected: S) -> String475 pub fn invalid_value_err<T: AsRef<str>, S: ToString>(value: T, expected: S) -> String {
476     format!("invalid value {}: {}", value.as_ref(), expected.to_string())
477 }
478 
479 #[derive(Debug, Serialize, Deserialize, FromKeyValues)]
480 #[serde(deny_unknown_fields, rename_all = "kebab-case")]
481 pub struct BatteryConfig {
482     #[serde(rename = "type", default)]
483     pub type_: BatteryType,
484 }
485 
parse_cpu_btreemap_u32(s: &str) -> Result<BTreeMap<usize, u32>, String>486 pub fn parse_cpu_btreemap_u32(s: &str) -> Result<BTreeMap<usize, u32>, String> {
487     let mut parsed_btreemap: BTreeMap<usize, u32> = BTreeMap::default();
488     for cpu_pair in s.split(',') {
489         let assignment: Vec<&str> = cpu_pair.split('=').collect();
490         if assignment.len() != 2 {
491             return Err(invalid_value_err(
492                 cpu_pair,
493                 "Invalid CPU pair syntax, missing '='",
494             ));
495         }
496         let cpu = assignment[0].parse().map_err(|_| {
497             invalid_value_err(assignment[0], "CPU index must be a non-negative integer")
498         })?;
499         let val = assignment[1].parse().map_err(|_| {
500             invalid_value_err(assignment[1], "CPU property must be a non-negative integer")
501         })?;
502         if parsed_btreemap.insert(cpu, val).is_some() {
503             return Err(invalid_value_err(cpu_pair, "CPU index must be unique"));
504         }
505     }
506     Ok(parsed_btreemap)
507 }
508 
509 #[cfg(all(
510     any(target_arch = "arm", target_arch = "aarch64"),
511     any(target_os = "android", target_os = "linux")
512 ))]
parse_cpu_frequencies(s: &str) -> Result<BTreeMap<usize, Vec<u32>>, String>513 pub fn parse_cpu_frequencies(s: &str) -> Result<BTreeMap<usize, Vec<u32>>, String> {
514     let mut cpu_frequencies: BTreeMap<usize, Vec<u32>> = BTreeMap::default();
515     for cpufreq_assigns in s.split(';') {
516         let assignment: Vec<&str> = cpufreq_assigns.split('=').collect();
517         if assignment.len() != 2 {
518             return Err(invalid_value_err(
519                 cpufreq_assigns,
520                 "invalid CPU freq syntax",
521             ));
522         }
523         let cpu = assignment[0].parse().map_err(|_| {
524             invalid_value_err(assignment[0], "CPU index must be a non-negative integer")
525         })?;
526         let freqs = assignment[1]
527             .split(',')
528             .map(|x| x.parse::<u32>().unwrap())
529             .collect::<Vec<_>>();
530         if cpu_frequencies.insert(cpu, freqs).is_some() {
531             return Err(invalid_value_err(
532                 cpufreq_assigns,
533                 "CPU index must be unique",
534             ));
535         }
536     }
537     Ok(cpu_frequencies)
538 }
539 
from_key_values<'a, T: Deserialize<'a>>(value: &'a str) -> Result<T, String>540 pub fn from_key_values<'a, T: Deserialize<'a>>(value: &'a str) -> Result<T, String> {
541     serde_keyvalue::from_key_values(value).map_err(|e| e.to_string())
542 }
543 
544 /// Parse a list of guest to host CPU mappings.
545 ///
546 /// Each mapping consists of a single guest CPU index mapped to one or more host CPUs in the form
547 /// accepted by `CpuSet::from_str`:
548 ///
549 ///  `<GUEST-CPU>=<HOST-CPU-SET>[:<GUEST-CPU>=<HOST-CPU-SET>[:...]]`
parse_cpu_affinity(s: &str) -> Result<VcpuAffinity, String>550 pub fn parse_cpu_affinity(s: &str) -> Result<VcpuAffinity, String> {
551     if s.contains('=') {
552         let mut affinity_map = BTreeMap::new();
553         for cpu_pair in s.split(':') {
554             let assignment: Vec<&str> = cpu_pair.split('=').collect();
555             if assignment.len() != 2 {
556                 return Err(invalid_value_err(
557                     cpu_pair,
558                     "invalid VCPU assignment syntax",
559                 ));
560             }
561             let guest_cpu = assignment[0].parse().map_err(|_| {
562                 invalid_value_err(assignment[0], "CPU index must be a non-negative integer")
563             })?;
564             let host_cpu_set = CpuSet::from_str(assignment[1])?;
565             if affinity_map.insert(guest_cpu, host_cpu_set).is_some() {
566                 return Err(invalid_value_err(cpu_pair, "VCPU index must be unique"));
567             }
568         }
569         Ok(VcpuAffinity::PerVcpu(affinity_map))
570     } else {
571         Ok(VcpuAffinity::Global(CpuSet::from_str(s)?))
572     }
573 }
574 
executable_is_plugin(executable: &Option<Executable>) -> bool575 pub fn executable_is_plugin(executable: &Option<Executable>) -> bool {
576     matches!(executable, Some(Executable::Plugin(_)))
577 }
578 
parse_pflash_parameters(s: &str) -> Result<PflashParameters, String>579 pub fn parse_pflash_parameters(s: &str) -> Result<PflashParameters, String> {
580     let pflash_parameters: PflashParameters = from_key_values(s)?;
581 
582     Ok(pflash_parameters)
583 }
584 
585 // BTreeMaps serialize fine, as long as their keys are trivial types. A tuple does not
586 // work, hence the need to convert to/from a vector form.
587 mod serde_serial_params {
588     use std::iter::FromIterator;
589 
590     use serde::Deserializer;
591     use serde::Serializer;
592 
593     use super::*;
594 
serialize<S>( params: &BTreeMap<(SerialHardware, u8), SerialParameters>, ser: S, ) -> Result<S::Ok, S::Error> where S: Serializer,595     pub fn serialize<S>(
596         params: &BTreeMap<(SerialHardware, u8), SerialParameters>,
597         ser: S,
598     ) -> Result<S::Ok, S::Error>
599     where
600         S: Serializer,
601     {
602         let v: Vec<(&(SerialHardware, u8), &SerialParameters)> = params.iter().collect();
603         serde::Serialize::serialize(&v, ser)
604     }
605 
deserialize<'a, D>( de: D, ) -> Result<BTreeMap<(SerialHardware, u8), SerialParameters>, D::Error> where D: Deserializer<'a>,606     pub fn deserialize<'a, D>(
607         de: D,
608     ) -> Result<BTreeMap<(SerialHardware, u8), SerialParameters>, D::Error>
609     where
610         D: Deserializer<'a>,
611     {
612         let params: Vec<((SerialHardware, u8), SerialParameters)> =
613             serde::Deserialize::deserialize(de)?;
614         Ok(BTreeMap::from_iter(params))
615     }
616 }
617 
618 /// Aggregate of all configurable options for a running VM.
619 #[derive(Serialize, Deserialize)]
620 #[remain::sorted]
621 pub struct Config {
622     #[cfg(all(target_arch = "x86_64", unix))]
623     pub ac_adapter: bool,
624     pub acpi_tables: Vec<PathBuf>,
625     #[cfg(feature = "android_display")]
626     pub android_display_service: Option<String>,
627     pub android_fstab: Option<PathBuf>,
628     pub async_executor: Option<ExecutorKind>,
629     #[cfg(feature = "balloon")]
630     pub balloon: bool,
631     #[cfg(feature = "balloon")]
632     pub balloon_bias: i64,
633     #[cfg(feature = "balloon")]
634     pub balloon_control: Option<PathBuf>,
635     #[cfg(feature = "balloon")]
636     pub balloon_page_reporting: bool,
637     #[cfg(feature = "balloon")]
638     pub balloon_ws_num_bins: u8,
639     #[cfg(feature = "balloon")]
640     pub balloon_ws_reporting: bool,
641     pub battery_config: Option<BatteryConfig>,
642     #[cfg(windows)]
643     pub block_control_tube: Vec<Tube>,
644     #[cfg(windows)]
645     pub block_vhost_user_tube: Vec<Tube>,
646     #[cfg(any(target_os = "android", target_os = "linux"))]
647     pub boost_uclamp: bool,
648     pub boot_cpu: usize,
649     #[cfg(target_arch = "x86_64")]
650     pub break_linux_pci_config_io: bool,
651     #[cfg(windows)]
652     pub broker_shutdown_event: Option<Event>,
653     #[cfg(target_arch = "x86_64")]
654     pub bus_lock_ratelimit: u64,
655     #[cfg(any(target_os = "android", target_os = "linux"))]
656     pub coiommu_param: Option<devices::CoIommuParameters>,
657     pub core_scheduling: bool,
658     pub cpu_capacity: BTreeMap<usize, u32>, // CPU index -> capacity
659     pub cpu_clusters: Vec<CpuSet>,
660     pub cpu_freq_domains: Vec<CpuSet>,
661     #[cfg(all(
662         any(target_arch = "arm", target_arch = "aarch64"),
663         any(target_os = "android", target_os = "linux")
664     ))]
665     pub cpu_frequencies_khz: BTreeMap<usize, Vec<u32>>, // CPU index -> frequencies
666     #[cfg(all(
667         any(target_arch = "arm", target_arch = "aarch64"),
668         any(target_os = "android", target_os = "linux")
669     ))]
670     pub cpu_ipc_ratio: BTreeMap<usize, u32>, // CPU index -> IPC Ratio
671     #[cfg(feature = "crash-report")]
672     pub crash_pipe_name: Option<String>,
673     #[cfg(feature = "crash-report")]
674     pub crash_report_uuid: Option<String>,
675     pub delay_rt: bool,
676     pub device_tree_overlay: Vec<DtboOption>,
677     pub disable_virtio_intx: bool,
678     pub disks: Vec<DiskOption>,
679     pub display_input_height: Option<u32>,
680     pub display_input_width: Option<u32>,
681     pub display_window_keyboard: bool,
682     pub display_window_mouse: bool,
683     pub dump_device_tree_blob: Option<PathBuf>,
684     pub dynamic_power_coefficient: BTreeMap<usize, u32>,
685     pub enable_fw_cfg: bool,
686     pub enable_hwp: bool,
687     pub executable_path: Option<Executable>,
688     #[cfg(windows)]
689     pub exit_stats: bool,
690     pub fdt_position: Option<FdtPosition>,
691     pub file_backed_mappings_mmio: Vec<FileBackedMappingParameters>,
692     pub file_backed_mappings_ram: Vec<FileBackedMappingParameters>,
693     pub force_calibrated_tsc_leaf: bool,
694     pub force_s2idle: bool,
695     pub fw_cfg_parameters: Vec<FwCfgParameters>,
696     #[cfg(feature = "gdb")]
697     pub gdb: Option<u32>,
698     #[cfg(all(windows, feature = "gpu"))]
699     pub gpu_backend_config: Option<GpuBackendConfig>,
700     #[cfg(all(unix, feature = "gpu"))]
701     pub gpu_cgroup_path: Option<PathBuf>,
702     #[cfg(feature = "gpu")]
703     pub gpu_parameters: Option<GpuParameters>,
704     #[cfg(all(unix, feature = "gpu"))]
705     pub gpu_render_server_parameters: Option<GpuRenderServerParameters>,
706     #[cfg(all(unix, feature = "gpu"))]
707     pub gpu_server_cgroup_path: Option<PathBuf>,
708     #[cfg(all(windows, feature = "gpu"))]
709     pub gpu_vmm_config: Option<GpuVmmConfig>,
710     pub host_cpu_topology: bool,
711     #[cfg(windows)]
712     pub host_guid: Option<String>,
713     pub hugepages: bool,
714     pub hypervisor: Option<HypervisorKind>,
715     #[cfg(feature = "balloon")]
716     pub init_memory: Option<u64>,
717     pub initrd_path: Option<PathBuf>,
718     #[cfg(all(windows, feature = "gpu"))]
719     pub input_event_split_config: Option<InputEventSplitConfig>,
720     pub irq_chip: Option<IrqChipKind>,
721     pub itmt: bool,
722     pub jail_config: Option<JailConfig>,
723     #[cfg(windows)]
724     pub kernel_log_file: Option<String>,
725     #[cfg(any(target_os = "android", target_os = "linux"))]
726     pub lock_guest_memory: bool,
727     #[cfg(windows)]
728     pub log_file: Option<String>,
729     #[cfg(windows)]
730     pub logs_directory: Option<String>,
731     #[cfg(all(feature = "media", feature = "video-decoder"))]
732     pub media_decoder: Vec<VideoDeviceConfig>,
733     pub memory: Option<u64>,
734     pub memory_file: Option<PathBuf>,
735     pub mmio_address_ranges: Vec<AddressRange>,
736     #[cfg(target_arch = "aarch64")]
737     pub mte: bool,
738     pub name: Option<String>,
739     #[cfg(feature = "net")]
740     pub net: Vec<NetParameters>,
741     #[cfg(windows)]
742     pub net_vhost_user_tube: Option<Tube>,
743     pub no_i8042: bool,
744     pub no_pmu: bool,
745     pub no_rtc: bool,
746     pub no_smt: bool,
747     pub params: Vec<String>,
748     pub pci_config: PciConfig,
749     #[cfg(feature = "pci-hotplug")]
750     pub pci_hotplug_slots: Option<u8>,
751     pub per_vm_core_scheduling: bool,
752     pub pflash_parameters: Option<PflashParameters>,
753     #[cfg(feature = "plugin")]
754     pub plugin_gid_maps: Vec<crate::crosvm::plugin::GidMap>,
755     #[cfg(feature = "plugin")]
756     pub plugin_mounts: Vec<crate::crosvm::plugin::BindMount>,
757     pub plugin_root: Option<PathBuf>,
758     #[cfg(any(target_os = "android", target_os = "linux"))]
759     pub pmem_ext2: Vec<crate::crosvm::sys::config::PmemExt2Option>,
760     pub pmems: Vec<PmemOption>,
761     #[cfg(feature = "process-invariants")]
762     pub process_invariants_data_handle: Option<u64>,
763     #[cfg(feature = "process-invariants")]
764     pub process_invariants_data_size: Option<usize>,
765     #[cfg(windows)]
766     pub product_channel: Option<String>,
767     #[cfg(windows)]
768     pub product_name: Option<String>,
769     #[cfg(windows)]
770     pub product_version: Option<String>,
771     pub protection_type: ProtectionType,
772     pub pstore: Option<Pstore>,
773     #[cfg(feature = "pvclock")]
774     pub pvclock: bool,
775     /// Must be `Some` iff `protection_type == ProtectionType::UnprotectedWithFirmware`.
776     pub pvm_fw: Option<PathBuf>,
777     pub restore_path: Option<PathBuf>,
778     pub rng: bool,
779     pub rt_cpus: CpuSet,
780     pub scsis: Vec<ScsiOption>,
781     #[serde(with = "serde_serial_params")]
782     pub serial_parameters: BTreeMap<(SerialHardware, u8), SerialParameters>,
783     #[cfg(windows)]
784     pub service_pipe_name: Option<String>,
785     #[cfg(any(target_os = "android", target_os = "linux"))]
786     #[serde(skip)]
787     pub shared_dirs: Vec<SharedDir>,
788     #[cfg(feature = "media")]
789     pub simple_media_device: bool,
790     #[cfg(any(feature = "slirp-ring-capture", feature = "slirp-debug"))]
791     pub slirp_capture_file: Option<String>,
792     #[cfg(target_arch = "x86_64")]
793     pub smbios: SmbiosOptions,
794     #[cfg(all(windows, feature = "audio"))]
795     pub snd_split_configs: Vec<SndSplitConfig>,
796     pub socket_path: Option<PathBuf>,
797     #[cfg(feature = "audio")]
798     pub sound: Option<PathBuf>,
799     pub stub_pci_devices: Vec<StubPciParameters>,
800     pub suspended: bool,
801     #[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
802     pub sve: Option<SveConfig>,
803     pub swap_dir: Option<PathBuf>,
804     pub swiotlb: Option<u64>,
805     #[cfg(target_os = "android")]
806     pub task_profiles: Vec<String>,
807     #[cfg(any(target_os = "android", target_os = "linux"))]
808     pub unmap_guest_memory_on_fork: bool,
809     pub usb: bool,
810     #[cfg(any(target_os = "android", target_os = "linux"))]
811     #[cfg(feature = "media")]
812     pub v4l2_proxy: Vec<PathBuf>,
813     pub vcpu_affinity: Option<VcpuAffinity>,
814     pub vcpu_cgroup_path: Option<PathBuf>,
815     pub vcpu_count: Option<usize>,
816     #[cfg(target_arch = "x86_64")]
817     pub vcpu_hybrid_type: BTreeMap<usize, CpuHybridType>, // CPU index -> hybrid type
818     #[cfg(any(target_os = "android", target_os = "linux"))]
819     pub vfio: Vec<super::sys::config::VfioOption>,
820     #[cfg(any(target_os = "android", target_os = "linux"))]
821     pub vfio_isolate_hotplug: bool,
822     #[cfg(any(target_os = "android", target_os = "linux"))]
823     #[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
824     pub vhost_scmi: bool,
825     #[cfg(any(target_os = "android", target_os = "linux"))]
826     #[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
827     pub vhost_scmi_device: PathBuf,
828     pub vhost_user: Vec<VhostUserFrontendOption>,
829     pub vhost_user_connect_timeout_ms: Option<u64>,
830     pub vhost_user_fs: Vec<VhostUserFsOption>,
831     #[cfg(feature = "video-decoder")]
832     pub video_dec: Vec<VideoDeviceConfig>,
833     #[cfg(feature = "video-encoder")]
834     pub video_enc: Vec<VideoDeviceConfig>,
835     #[cfg(all(
836         any(target_arch = "arm", target_arch = "aarch64"),
837         any(target_os = "android", target_os = "linux")
838     ))]
839     pub virt_cpufreq: bool,
840     pub virt_cpufreq_v2: bool,
841     pub virtio_input: Vec<InputDeviceOption>,
842     #[cfg(feature = "audio")]
843     #[serde(skip)]
844     pub virtio_snds: Vec<SndParameters>,
845     pub vsock: Option<VsockConfig>,
846     #[cfg(feature = "vtpm")]
847     pub vtpm_proxy: bool,
848     pub wayland_socket_paths: BTreeMap<String, PathBuf>,
849     #[cfg(all(windows, feature = "gpu"))]
850     pub window_procedure_thread_split_config: Option<WindowProcedureThreadSplitConfig>,
851     pub x_display: Option<String>,
852 }
853 
854 impl Default for Config {
default() -> Config855     fn default() -> Config {
856         Config {
857             #[cfg(all(target_arch = "x86_64", unix))]
858             ac_adapter: false,
859             acpi_tables: Vec::new(),
860             #[cfg(feature = "android_display")]
861             android_display_service: None,
862             android_fstab: None,
863             async_executor: None,
864             #[cfg(feature = "balloon")]
865             balloon: true,
866             #[cfg(feature = "balloon")]
867             balloon_bias: 0,
868             #[cfg(feature = "balloon")]
869             balloon_control: None,
870             #[cfg(feature = "balloon")]
871             balloon_page_reporting: false,
872             #[cfg(feature = "balloon")]
873             balloon_ws_num_bins: VIRTIO_BALLOON_WS_DEFAULT_NUM_BINS,
874             #[cfg(feature = "balloon")]
875             balloon_ws_reporting: false,
876             battery_config: None,
877             boot_cpu: 0,
878             #[cfg(windows)]
879             block_control_tube: Vec::new(),
880             #[cfg(windows)]
881             block_vhost_user_tube: Vec::new(),
882             #[cfg(target_arch = "x86_64")]
883             break_linux_pci_config_io: false,
884             #[cfg(windows)]
885             broker_shutdown_event: None,
886             #[cfg(target_arch = "x86_64")]
887             bus_lock_ratelimit: 0,
888             #[cfg(any(target_os = "android", target_os = "linux"))]
889             coiommu_param: None,
890             core_scheduling: true,
891             #[cfg(feature = "crash-report")]
892             crash_pipe_name: None,
893             #[cfg(feature = "crash-report")]
894             crash_report_uuid: None,
895             cpu_capacity: BTreeMap::new(),
896             cpu_clusters: Vec::new(),
897             #[cfg(all(
898                 any(target_arch = "arm", target_arch = "aarch64"),
899                 any(target_os = "android", target_os = "linux")
900             ))]
901             cpu_frequencies_khz: BTreeMap::new(),
902             cpu_freq_domains: Vec::new(),
903             #[cfg(all(
904                 any(target_arch = "arm", target_arch = "aarch64"),
905                 any(target_os = "android", target_os = "linux")
906             ))]
907             cpu_ipc_ratio: BTreeMap::new(),
908             delay_rt: false,
909             device_tree_overlay: Vec::new(),
910             disks: Vec::new(),
911             disable_virtio_intx: false,
912             display_input_height: None,
913             display_input_width: None,
914             display_window_keyboard: false,
915             display_window_mouse: false,
916             dump_device_tree_blob: None,
917             dynamic_power_coefficient: BTreeMap::new(),
918             enable_fw_cfg: false,
919             enable_hwp: false,
920             executable_path: None,
921             #[cfg(windows)]
922             exit_stats: false,
923             fdt_position: None,
924             file_backed_mappings_mmio: Vec::new(),
925             file_backed_mappings_ram: Vec::new(),
926             force_calibrated_tsc_leaf: false,
927             force_s2idle: false,
928             fw_cfg_parameters: Vec::new(),
929             #[cfg(feature = "gdb")]
930             gdb: None,
931             #[cfg(all(windows, feature = "gpu"))]
932             gpu_backend_config: None,
933             #[cfg(feature = "gpu")]
934             gpu_parameters: None,
935             #[cfg(all(unix, feature = "gpu"))]
936             gpu_render_server_parameters: None,
937             #[cfg(all(unix, feature = "gpu"))]
938             gpu_cgroup_path: None,
939             #[cfg(all(unix, feature = "gpu"))]
940             gpu_server_cgroup_path: None,
941             #[cfg(all(windows, feature = "gpu"))]
942             gpu_vmm_config: None,
943             host_cpu_topology: false,
944             #[cfg(windows)]
945             host_guid: None,
946             #[cfg(windows)]
947             product_version: None,
948             #[cfg(windows)]
949             product_channel: None,
950             hugepages: false,
951             hypervisor: None,
952             #[cfg(feature = "balloon")]
953             init_memory: None,
954             initrd_path: None,
955             #[cfg(all(windows, feature = "gpu"))]
956             input_event_split_config: None,
957             irq_chip: None,
958             itmt: false,
959             jail_config: if !cfg!(feature = "default-no-sandbox") {
960                 Some(Default::default())
961             } else {
962                 None
963             },
964             #[cfg(windows)]
965             kernel_log_file: None,
966             #[cfg(any(target_os = "android", target_os = "linux"))]
967             lock_guest_memory: false,
968             #[cfg(windows)]
969             log_file: None,
970             #[cfg(windows)]
971             logs_directory: None,
972             #[cfg(any(target_os = "android", target_os = "linux"))]
973             boost_uclamp: false,
974             #[cfg(all(feature = "media", feature = "video-decoder"))]
975             media_decoder: Default::default(),
976             memory: None,
977             memory_file: None,
978             mmio_address_ranges: Vec::new(),
979             #[cfg(target_arch = "aarch64")]
980             mte: false,
981             name: None,
982             #[cfg(feature = "net")]
983             net: Vec::new(),
984             #[cfg(windows)]
985             net_vhost_user_tube: None,
986             no_i8042: false,
987             no_pmu: false,
988             no_rtc: false,
989             no_smt: false,
990             params: Vec::new(),
991             pci_config: Default::default(),
992             #[cfg(feature = "pci-hotplug")]
993             pci_hotplug_slots: None,
994             per_vm_core_scheduling: false,
995             pflash_parameters: None,
996             #[cfg(feature = "plugin")]
997             plugin_gid_maps: Vec::new(),
998             #[cfg(feature = "plugin")]
999             plugin_mounts: Vec::new(),
1000             plugin_root: None,
1001             #[cfg(any(target_os = "android", target_os = "linux"))]
1002             pmem_ext2: Vec::new(),
1003             pmems: Vec::new(),
1004             #[cfg(feature = "process-invariants")]
1005             process_invariants_data_handle: None,
1006             #[cfg(feature = "process-invariants")]
1007             process_invariants_data_size: None,
1008             #[cfg(windows)]
1009             product_name: None,
1010             protection_type: ProtectionType::Unprotected,
1011             pstore: None,
1012             #[cfg(feature = "pvclock")]
1013             pvclock: false,
1014             pvm_fw: None,
1015             restore_path: None,
1016             rng: true,
1017             rt_cpus: Default::default(),
1018             serial_parameters: BTreeMap::new(),
1019             scsis: Vec::new(),
1020             #[cfg(windows)]
1021             service_pipe_name: None,
1022             #[cfg(any(target_os = "android", target_os = "linux"))]
1023             shared_dirs: Vec::new(),
1024             #[cfg(feature = "media")]
1025             simple_media_device: Default::default(),
1026             #[cfg(any(feature = "slirp-ring-capture", feature = "slirp-debug"))]
1027             slirp_capture_file: None,
1028             #[cfg(target_arch = "x86_64")]
1029             smbios: SmbiosOptions::default(),
1030             #[cfg(all(windows, feature = "audio"))]
1031             snd_split_configs: Vec::new(),
1032             socket_path: None,
1033             #[cfg(feature = "audio")]
1034             sound: None,
1035             stub_pci_devices: Vec::new(),
1036             suspended: false,
1037             #[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
1038             sve: None,
1039             swap_dir: None,
1040             swiotlb: None,
1041             #[cfg(target_os = "android")]
1042             task_profiles: Vec::new(),
1043             #[cfg(any(target_os = "android", target_os = "linux"))]
1044             unmap_guest_memory_on_fork: false,
1045             usb: true,
1046             vcpu_affinity: None,
1047             vcpu_cgroup_path: None,
1048             vcpu_count: None,
1049             #[cfg(target_arch = "x86_64")]
1050             vcpu_hybrid_type: BTreeMap::new(),
1051             #[cfg(any(target_os = "android", target_os = "linux"))]
1052             vfio: Vec::new(),
1053             #[cfg(any(target_os = "android", target_os = "linux"))]
1054             vfio_isolate_hotplug: false,
1055             #[cfg(any(target_os = "android", target_os = "linux"))]
1056             #[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
1057             vhost_scmi: false,
1058             #[cfg(any(target_os = "android", target_os = "linux"))]
1059             #[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
1060             vhost_scmi_device: PathBuf::from(VHOST_SCMI_PATH),
1061             vhost_user: Vec::new(),
1062             vhost_user_connect_timeout_ms: None,
1063             vhost_user_fs: Vec::new(),
1064             vsock: None,
1065             #[cfg(feature = "video-decoder")]
1066             video_dec: Vec::new(),
1067             #[cfg(feature = "video-encoder")]
1068             video_enc: Vec::new(),
1069             #[cfg(all(
1070                 any(target_arch = "arm", target_arch = "aarch64"),
1071                 any(target_os = "android", target_os = "linux")
1072             ))]
1073             virt_cpufreq: false,
1074             virt_cpufreq_v2: false,
1075             virtio_input: Vec::new(),
1076             #[cfg(feature = "audio")]
1077             virtio_snds: Vec::new(),
1078             #[cfg(any(target_os = "android", target_os = "linux"))]
1079             #[cfg(feature = "media")]
1080             v4l2_proxy: Vec::new(),
1081             #[cfg(feature = "vtpm")]
1082             vtpm_proxy: false,
1083             wayland_socket_paths: BTreeMap::new(),
1084             #[cfg(windows)]
1085             window_procedure_thread_split_config: None,
1086             x_display: None,
1087         }
1088     }
1089 }
1090 
validate_config(cfg: &mut Config) -> std::result::Result<(), String>1091 pub fn validate_config(cfg: &mut Config) -> std::result::Result<(), String> {
1092     if cfg.executable_path.is_none() {
1093         return Err("Executable is not specified".to_string());
1094     }
1095 
1096     if cfg.plugin_root.is_some() && !executable_is_plugin(&cfg.executable_path) {
1097         return Err("`plugin-root` requires `plugin`".to_string());
1098     }
1099 
1100     #[cfg(feature = "gpu")]
1101     {
1102         crate::crosvm::gpu_config::validate_gpu_config(cfg)?;
1103     }
1104     #[cfg(feature = "gdb")]
1105     if cfg.gdb.is_some() && cfg.vcpu_count.unwrap_or(1) != 1 {
1106         return Err("`gdb` requires the number of vCPU to be 1".to_string());
1107     }
1108     if cfg.host_cpu_topology {
1109         if cfg.no_smt {
1110             return Err(
1111                 "`host-cpu-topology` cannot be set at the same time as `no_smt`, since \
1112                 the smt of the Guest is the same as that of the Host when \
1113                 `host-cpu-topology` is set."
1114                     .to_string(),
1115             );
1116         }
1117 
1118         let pcpu_count =
1119             base::number_of_logical_cores().expect("Could not read number of logical cores");
1120         if let Some(vcpu_count) = cfg.vcpu_count {
1121             if pcpu_count != vcpu_count {
1122                 return Err(format!(
1123                     "`host-cpu-topology` requires the count of vCPUs({}) to equal the \
1124                             count of CPUs({}) on host.",
1125                     vcpu_count, pcpu_count
1126                 ));
1127             }
1128         } else {
1129             cfg.vcpu_count = Some(pcpu_count);
1130         }
1131 
1132         match &cfg.vcpu_affinity {
1133             None => {
1134                 let mut affinity_map = BTreeMap::new();
1135                 for cpu_id in 0..cfg.vcpu_count.unwrap() {
1136                     affinity_map.insert(cpu_id, CpuSet::new([cpu_id]));
1137                 }
1138                 cfg.vcpu_affinity = Some(VcpuAffinity::PerVcpu(affinity_map));
1139             }
1140             _ => {
1141                 return Err(
1142                     "`host-cpu-topology` requires not to set `cpu-affinity` at the same time"
1143                         .to_string(),
1144                 );
1145             }
1146         }
1147 
1148         if !cfg.cpu_capacity.is_empty() {
1149             return Err(
1150                 "`host-cpu-topology` requires not to set `cpu-capacity` at the same time"
1151                     .to_string(),
1152             );
1153         }
1154 
1155         if !cfg.cpu_clusters.is_empty() {
1156             return Err(
1157                 "`host-cpu-topology` requires not to set `cpu clusters` at the same time"
1158                     .to_string(),
1159             );
1160         }
1161     }
1162 
1163     if cfg.boot_cpu >= cfg.vcpu_count.unwrap_or(1) {
1164         log::warn!("boot_cpu selection cannot be higher than vCPUs available, defaulting to 0");
1165         cfg.boot_cpu = 0;
1166     }
1167 
1168     #[cfg(all(
1169         any(target_arch = "arm", target_arch = "aarch64"),
1170         any(target_os = "android", target_os = "linux")
1171     ))]
1172     if !cfg.cpu_frequencies_khz.is_empty() {
1173         if !cfg.virt_cpufreq_v2 {
1174             return Err("`cpu-frequencies` requires `virt-cpufreq-upstream`".to_string());
1175         }
1176 
1177         if cfg.host_cpu_topology {
1178             return Err(
1179                 "`host-cpu-topology` cannot be used with 'cpu-frequencies` at the same time"
1180                     .to_string(),
1181             );
1182         }
1183     }
1184 
1185     #[cfg(all(
1186         any(target_arch = "arm", target_arch = "aarch64"),
1187         any(target_os = "android", target_os = "linux")
1188     ))]
1189     if cfg.virt_cpufreq {
1190         if !cfg.host_cpu_topology && (cfg.vcpu_affinity.is_none() || cfg.cpu_capacity.is_empty()) {
1191             return Err("`virt-cpufreq` requires 'host-cpu-topology' enabled or \
1192                        have vcpu_affinity and cpu_capacity configured"
1193                 .to_string());
1194         }
1195     }
1196     #[cfg(target_arch = "x86_64")]
1197     if !cfg.vcpu_hybrid_type.is_empty() {
1198         if cfg.host_cpu_topology {
1199             return Err("`core-types` cannot be set with `host-cpu-topology`.".to_string());
1200         }
1201         check_host_hybrid_support(&CpuIdCall::new(__cpuid_count, __cpuid))
1202             .map_err(|e| format!("the cpu doesn't support `core-types`: {}", e))?;
1203         if cfg.vcpu_hybrid_type.len() != cfg.vcpu_count.unwrap_or(1) {
1204             return Err("`core-types` must be set for all virtual CPUs".to_string());
1205         }
1206         for cpu_id in 0..cfg.vcpu_count.unwrap_or(1) {
1207             if !cfg.vcpu_hybrid_type.contains_key(&cpu_id) {
1208                 return Err("`core-types` must be set for all virtual CPUs".to_string());
1209             }
1210         }
1211     }
1212     #[cfg(target_arch = "x86_64")]
1213     if cfg.enable_hwp && !cfg.host_cpu_topology {
1214         return Err("setting `enable-hwp` requires `host-cpu-topology` is set.".to_string());
1215     }
1216     #[cfg(target_arch = "x86_64")]
1217     if cfg.itmt {
1218         use std::collections::BTreeSet;
1219         // ITMT only works on the case each vCPU is 1:1 mapping to a pCPU.
1220         // `host-cpu-topology` has already set this 1:1 mapping. If no
1221         // `host-cpu-topology`, we need check the cpu affinity setting.
1222         if !cfg.host_cpu_topology {
1223             // only VcpuAffinity::PerVcpu supports setting cpu affinity
1224             // for each vCPU.
1225             if let Some(VcpuAffinity::PerVcpu(v)) = &cfg.vcpu_affinity {
1226                 // ITMT allows more pCPUs than vCPUs.
1227                 if v.len() != cfg.vcpu_count.unwrap_or(1) {
1228                     return Err("`itmt` requires affinity to be set for every vCPU.".to_string());
1229                 }
1230 
1231                 let mut pcpu_set = BTreeSet::new();
1232                 for cpus in v.values() {
1233                     if cpus.len() != 1 {
1234                         return Err(
1235                             "`itmt` requires affinity to be set 1 pCPU for 1 vCPU.".to_owned()
1236                         );
1237                     }
1238                     // Ensure that each vCPU corresponds to a different pCPU to avoid pCPU sharing,
1239                     // otherwise it will seriously affect the ITMT scheduling optimization effect.
1240                     if !pcpu_set.insert(cpus[0]) {
1241                         return Err(
1242                             "`cpu_host` requires affinity to be set different pVPU for each vCPU."
1243                                 .to_owned(),
1244                         );
1245                     }
1246                 }
1247             } else {
1248                 return Err("`itmt` requires affinity to be set for every vCPU.".to_string());
1249             }
1250         }
1251         if !cfg.enable_hwp {
1252             return Err("setting `itmt` requires `enable-hwp` is set.".to_string());
1253         }
1254     }
1255 
1256     #[cfg(feature = "balloon")]
1257     {
1258         if !cfg.balloon && cfg.balloon_control.is_some() {
1259             return Err("'balloon-control' requires enabled balloon".to_string());
1260         }
1261 
1262         if !cfg.balloon && cfg.balloon_page_reporting {
1263             return Err("'balloon_page_reporting' requires enabled balloon".to_string());
1264         }
1265     }
1266 
1267     #[cfg(any(target_os = "android", target_os = "linux"))]
1268     if cfg.lock_guest_memory && cfg.jail_config.is_none() {
1269         return Err("'lock-guest-memory' and 'disable-sandbox' are mutually exclusive".to_string());
1270     }
1271 
1272     // TODO(b/253386409): Vmm-swap only support sandboxed devices until vmm-swap use
1273     // `devices::Suspendable` to suspend devices.
1274     #[cfg(feature = "swap")]
1275     if cfg.swap_dir.is_some() && cfg.jail_config.is_none() {
1276         return Err("'swap' and 'disable-sandbox' are mutually exclusive".to_string());
1277     }
1278 
1279     set_default_serial_parameters(
1280         &mut cfg.serial_parameters,
1281         cfg.vhost_user
1282             .iter()
1283             .any(|opt| opt.type_ == DeviceType::Console),
1284     );
1285 
1286     for mapping in cfg
1287         .file_backed_mappings_mmio
1288         .iter_mut()
1289         .chain(cfg.file_backed_mappings_ram.iter_mut())
1290     {
1291         validate_file_backed_mapping(mapping)?;
1292     }
1293 
1294     for pmem in cfg.pmems.iter() {
1295         validate_pmem(pmem)?;
1296     }
1297 
1298     // Validate platform specific things
1299     super::sys::config::validate_config(cfg)
1300 }
1301 
validate_file_backed_mapping(mapping: &mut FileBackedMappingParameters) -> Result<(), String>1302 fn validate_file_backed_mapping(mapping: &mut FileBackedMappingParameters) -> Result<(), String> {
1303     let pagesize_mask = pagesize() as u64 - 1;
1304     let aligned_address = mapping.address & !pagesize_mask;
1305     let aligned_size =
1306         ((mapping.address + mapping.size + pagesize_mask) & !pagesize_mask) - aligned_address;
1307 
1308     if mapping.align {
1309         mapping.address = aligned_address;
1310         mapping.size = aligned_size;
1311     } else if aligned_address != mapping.address || aligned_size != mapping.size {
1312         return Err(
1313             "--file-backed-mapping addr and size parameters must be page size aligned".to_string(),
1314         );
1315     }
1316 
1317     Ok(())
1318 }
1319 
validate_pmem(pmem: &PmemOption) -> Result<(), String>1320 fn validate_pmem(pmem: &PmemOption) -> Result<(), String> {
1321     if (pmem.swap_interval.is_some() && pmem.vma_size.is_none())
1322         || (pmem.swap_interval.is_none() && pmem.vma_size.is_some())
1323     {
1324         return Err(
1325             "--pmem vma-size and swap-interval parameters must be specified together".to_string(),
1326         );
1327     }
1328 
1329     if pmem.ro && pmem.swap_interval.is_some() {
1330         return Err(
1331             "--pmem swap-interval parameter can only be set for writable pmem device".to_string(),
1332         );
1333     }
1334 
1335     Ok(())
1336 }
1337 
1338 #[cfg(test)]
1339 #[allow(clippy::needless_update)]
1340 mod tests {
1341     use argh::FromArgs;
1342     use devices::PciClassCode;
1343     use devices::StubPciParameters;
1344     #[cfg(target_arch = "x86_64")]
1345     use uuid::uuid;
1346 
1347     use super::*;
1348 
config_from_args(args: &[&str]) -> Config1349     fn config_from_args(args: &[&str]) -> Config {
1350         crate::crosvm::cmdline::RunCommand::from_args(&[], args)
1351             .unwrap()
1352             .try_into()
1353             .unwrap()
1354     }
1355 
1356     #[test]
parse_cpu_opts()1357     fn parse_cpu_opts() {
1358         let res: CpuOptions = from_key_values("").unwrap();
1359         assert_eq!(res, CpuOptions::default());
1360 
1361         // num_cores
1362         let res: CpuOptions = from_key_values("12").unwrap();
1363         assert_eq!(
1364             res,
1365             CpuOptions {
1366                 num_cores: Some(12),
1367                 ..Default::default()
1368             }
1369         );
1370 
1371         let res: CpuOptions = from_key_values("num-cores=16").unwrap();
1372         assert_eq!(
1373             res,
1374             CpuOptions {
1375                 num_cores: Some(16),
1376                 ..Default::default()
1377             }
1378         );
1379 
1380         // clusters
1381         let res: CpuOptions = from_key_values("clusters=[[0],[1],[2],[3]]").unwrap();
1382         assert_eq!(
1383             res,
1384             CpuOptions {
1385                 clusters: vec![
1386                     CpuSet::new([0]),
1387                     CpuSet::new([1]),
1388                     CpuSet::new([2]),
1389                     CpuSet::new([3])
1390                 ],
1391                 ..Default::default()
1392             }
1393         );
1394 
1395         let res: CpuOptions = from_key_values("clusters=[[0-3]]").unwrap();
1396         assert_eq!(
1397             res,
1398             CpuOptions {
1399                 clusters: vec![CpuSet::new([0, 1, 2, 3])],
1400                 ..Default::default()
1401             }
1402         );
1403 
1404         let res: CpuOptions = from_key_values("clusters=[[0,2],[1,3],[4-7,12]]").unwrap();
1405         assert_eq!(
1406             res,
1407             CpuOptions {
1408                 clusters: vec![
1409                     CpuSet::new([0, 2]),
1410                     CpuSet::new([1, 3]),
1411                     CpuSet::new([4, 5, 6, 7, 12])
1412                 ],
1413                 ..Default::default()
1414             }
1415         );
1416 
1417         #[cfg(target_arch = "x86_64")]
1418         {
1419             let res: CpuOptions = from_key_values("core-types=[atom=[1,3-7],core=[0,2]]").unwrap();
1420             assert_eq!(
1421                 res,
1422                 CpuOptions {
1423                     core_types: Some(CpuCoreType {
1424                         atom: CpuSet::new([1, 3, 4, 5, 6, 7]),
1425                         core: CpuSet::new([0, 2])
1426                     }),
1427                     ..Default::default()
1428                 }
1429             );
1430         }
1431 
1432         // All together
1433         let res: CpuOptions = from_key_values("16,clusters=[[0],[4-6],[7]]").unwrap();
1434         assert_eq!(
1435             res,
1436             CpuOptions {
1437                 num_cores: Some(16),
1438                 clusters: vec![CpuSet::new([0]), CpuSet::new([4, 5, 6]), CpuSet::new([7])],
1439                 ..Default::default()
1440             }
1441         );
1442 
1443         let res: CpuOptions = from_key_values("clusters=[[0-7],[30-31]],num-cores=32").unwrap();
1444         assert_eq!(
1445             res,
1446             CpuOptions {
1447                 num_cores: Some(32),
1448                 clusters: vec![CpuSet::new([0, 1, 2, 3, 4, 5, 6, 7]), CpuSet::new([30, 31])],
1449                 ..Default::default()
1450             }
1451         );
1452     }
1453 
1454     #[test]
parse_cpu_set_single()1455     fn parse_cpu_set_single() {
1456         assert_eq!(
1457             CpuSet::from_str("123").expect("parse failed"),
1458             CpuSet::new([123])
1459         );
1460     }
1461 
1462     #[test]
parse_cpu_set_list()1463     fn parse_cpu_set_list() {
1464         assert_eq!(
1465             CpuSet::from_str("0,1,2,3").expect("parse failed"),
1466             CpuSet::new([0, 1, 2, 3])
1467         );
1468     }
1469 
1470     #[test]
parse_cpu_set_range()1471     fn parse_cpu_set_range() {
1472         assert_eq!(
1473             CpuSet::from_str("0-3").expect("parse failed"),
1474             CpuSet::new([0, 1, 2, 3])
1475         );
1476     }
1477 
1478     #[test]
parse_cpu_set_list_of_ranges()1479     fn parse_cpu_set_list_of_ranges() {
1480         assert_eq!(
1481             CpuSet::from_str("3-4,7-9,18").expect("parse failed"),
1482             CpuSet::new([3, 4, 7, 8, 9, 18])
1483         );
1484     }
1485 
1486     #[test]
parse_cpu_set_repeated()1487     fn parse_cpu_set_repeated() {
1488         // For now, allow duplicates - they will be handled gracefully by the vec to cpu_set_t
1489         // conversion.
1490         assert_eq!(
1491             CpuSet::from_str("1,1,1").expect("parse failed"),
1492             CpuSet::new([1, 1, 1])
1493         );
1494     }
1495 
1496     #[test]
parse_cpu_set_negative()1497     fn parse_cpu_set_negative() {
1498         // Negative CPU numbers are not allowed.
1499         CpuSet::from_str("-3").expect_err("parse should have failed");
1500     }
1501 
1502     #[test]
parse_cpu_set_reverse_range()1503     fn parse_cpu_set_reverse_range() {
1504         // Ranges must be from low to high.
1505         CpuSet::from_str("5-2").expect_err("parse should have failed");
1506     }
1507 
1508     #[test]
parse_cpu_set_open_range()1509     fn parse_cpu_set_open_range() {
1510         CpuSet::from_str("3-").expect_err("parse should have failed");
1511     }
1512 
1513     #[test]
parse_cpu_set_extra_comma()1514     fn parse_cpu_set_extra_comma() {
1515         CpuSet::from_str("0,1,2,").expect_err("parse should have failed");
1516     }
1517 
1518     #[test]
parse_cpu_affinity_global()1519     fn parse_cpu_affinity_global() {
1520         assert_eq!(
1521             parse_cpu_affinity("0,5-7,9").expect("parse failed"),
1522             VcpuAffinity::Global(CpuSet::new([0, 5, 6, 7, 9])),
1523         );
1524     }
1525 
1526     #[test]
parse_cpu_affinity_per_vcpu_one_to_one()1527     fn parse_cpu_affinity_per_vcpu_one_to_one() {
1528         let mut expected_map = BTreeMap::new();
1529         expected_map.insert(0, CpuSet::new([0]));
1530         expected_map.insert(1, CpuSet::new([1]));
1531         expected_map.insert(2, CpuSet::new([2]));
1532         expected_map.insert(3, CpuSet::new([3]));
1533         assert_eq!(
1534             parse_cpu_affinity("0=0:1=1:2=2:3=3").expect("parse failed"),
1535             VcpuAffinity::PerVcpu(expected_map),
1536         );
1537     }
1538 
1539     #[test]
parse_cpu_affinity_per_vcpu_sets()1540     fn parse_cpu_affinity_per_vcpu_sets() {
1541         let mut expected_map = BTreeMap::new();
1542         expected_map.insert(0, CpuSet::new([0, 1, 2]));
1543         expected_map.insert(1, CpuSet::new([3, 4, 5]));
1544         expected_map.insert(2, CpuSet::new([6, 7, 8]));
1545         assert_eq!(
1546             parse_cpu_affinity("0=0,1,2:1=3-5:2=6,7-8").expect("parse failed"),
1547             VcpuAffinity::PerVcpu(expected_map),
1548         );
1549     }
1550 
1551     #[test]
parse_mem_opts()1552     fn parse_mem_opts() {
1553         let res: MemOptions = from_key_values("").unwrap();
1554         assert_eq!(res.size, None);
1555 
1556         let res: MemOptions = from_key_values("1024").unwrap();
1557         assert_eq!(res.size, Some(1024));
1558 
1559         let res: MemOptions = from_key_values("size=0x4000").unwrap();
1560         assert_eq!(res.size, Some(16384));
1561     }
1562 
1563     #[test]
parse_serial_vaild()1564     fn parse_serial_vaild() {
1565         parse_serial_options("type=syslog,num=1,console=true,stdin=true")
1566             .expect("parse should have succeded");
1567     }
1568 
1569     #[test]
parse_serial_virtio_console_vaild()1570     fn parse_serial_virtio_console_vaild() {
1571         parse_serial_options("type=syslog,num=5,console=true,stdin=true,hardware=virtio-console")
1572             .expect("parse should have succeded");
1573     }
1574 
1575     #[test]
parse_serial_valid_no_num()1576     fn parse_serial_valid_no_num() {
1577         parse_serial_options("type=syslog").expect("parse should have succeded");
1578     }
1579 
1580     #[test]
parse_serial_equals_in_value()1581     fn parse_serial_equals_in_value() {
1582         let parsed = parse_serial_options("type=syslog,path=foo=bar==.log")
1583             .expect("parse should have succeded");
1584         assert_eq!(parsed.path, Some(PathBuf::from("foo=bar==.log")));
1585     }
1586 
1587     #[test]
parse_serial_invalid_type()1588     fn parse_serial_invalid_type() {
1589         parse_serial_options("type=wormhole,num=1").expect_err("parse should have failed");
1590     }
1591 
1592     #[test]
parse_serial_invalid_num_upper()1593     fn parse_serial_invalid_num_upper() {
1594         parse_serial_options("type=syslog,num=5").expect_err("parse should have failed");
1595     }
1596 
1597     #[test]
parse_serial_invalid_num_lower()1598     fn parse_serial_invalid_num_lower() {
1599         parse_serial_options("type=syslog,num=0").expect_err("parse should have failed");
1600     }
1601 
1602     #[test]
parse_serial_virtio_console_invalid_num_lower()1603     fn parse_serial_virtio_console_invalid_num_lower() {
1604         parse_serial_options("type=syslog,hardware=virtio-console,num=0")
1605             .expect_err("parse should have failed");
1606     }
1607 
1608     #[test]
parse_serial_invalid_num_string()1609     fn parse_serial_invalid_num_string() {
1610         parse_serial_options("type=syslog,num=number3").expect_err("parse should have failed");
1611     }
1612 
1613     #[test]
parse_serial_invalid_option()1614     fn parse_serial_invalid_option() {
1615         parse_serial_options("type=syslog,speed=lightspeed").expect_err("parse should have failed");
1616     }
1617 
1618     #[test]
parse_serial_invalid_two_stdin()1619     fn parse_serial_invalid_two_stdin() {
1620         assert!(TryInto::<Config>::try_into(
1621             crate::crosvm::cmdline::RunCommand::from_args(
1622                 &[],
1623                 &[
1624                     "--serial",
1625                     "num=1,type=stdout,stdin=true",
1626                     "--serial",
1627                     "num=2,type=stdout,stdin=true"
1628                 ]
1629             )
1630             .unwrap()
1631         )
1632         .is_err())
1633     }
1634 
1635     #[test]
parse_serial_pci_address_valid_for_virtio()1636     fn parse_serial_pci_address_valid_for_virtio() {
1637         let parsed =
1638             parse_serial_options("type=syslog,hardware=virtio-console,pci-address=00:0e.0")
1639                 .expect("parse should have succeded");
1640         assert_eq!(
1641             parsed.pci_address,
1642             Some(PciAddress {
1643                 bus: 0,
1644                 dev: 14,
1645                 func: 0
1646             })
1647         );
1648     }
1649 
1650     #[test]
parse_serial_pci_address_valid_for_legacy_virtio()1651     fn parse_serial_pci_address_valid_for_legacy_virtio() {
1652         let parsed =
1653             parse_serial_options("type=syslog,hardware=legacy-virtio-console,pci-address=00:0e.0")
1654                 .expect("parse should have succeded");
1655         assert_eq!(
1656             parsed.pci_address,
1657             Some(PciAddress {
1658                 bus: 0,
1659                 dev: 14,
1660                 func: 0
1661             })
1662         );
1663     }
1664 
1665     #[test]
parse_serial_pci_address_failed_for_serial()1666     fn parse_serial_pci_address_failed_for_serial() {
1667         parse_serial_options("type=syslog,hardware=serial,pci-address=00:0e.0")
1668             .expect_err("expected pci-address error for serial hardware");
1669     }
1670 
1671     #[test]
parse_serial_pci_address_failed_for_debugcon()1672     fn parse_serial_pci_address_failed_for_debugcon() {
1673         parse_serial_options("type=syslog,hardware=debugcon,pci-address=00:0e.0")
1674             .expect_err("expected pci-address error for debugcon hardware");
1675     }
1676 
1677     #[test]
parse_battery_valid()1678     fn parse_battery_valid() {
1679         let bat_config: BatteryConfig = from_key_values("type=goldfish").unwrap();
1680         assert_eq!(bat_config.type_, BatteryType::Goldfish);
1681     }
1682 
1683     #[test]
parse_battery_valid_no_type()1684     fn parse_battery_valid_no_type() {
1685         let bat_config: BatteryConfig = from_key_values("").unwrap();
1686         assert_eq!(bat_config.type_, BatteryType::Goldfish);
1687     }
1688 
1689     #[test]
parse_battery_invalid_parameter()1690     fn parse_battery_invalid_parameter() {
1691         from_key_values::<BatteryConfig>("tyep=goldfish").expect_err("parse should have failed");
1692     }
1693 
1694     #[test]
parse_battery_invalid_type_value()1695     fn parse_battery_invalid_type_value() {
1696         from_key_values::<BatteryConfig>("type=xxx").expect_err("parse should have failed");
1697     }
1698 
1699     #[test]
parse_irqchip_kernel()1700     fn parse_irqchip_kernel() {
1701         let cfg = TryInto::<Config>::try_into(
1702             crate::crosvm::cmdline::RunCommand::from_args(
1703                 &[],
1704                 &["--irqchip", "kernel", "/dev/null"],
1705             )
1706             .unwrap(),
1707         )
1708         .unwrap();
1709 
1710         assert_eq!(cfg.irq_chip, Some(IrqChipKind::Kernel));
1711     }
1712 
1713     #[test]
parse_irqchip_split()1714     fn parse_irqchip_split() {
1715         let cfg = TryInto::<Config>::try_into(
1716             crate::crosvm::cmdline::RunCommand::from_args(
1717                 &[],
1718                 &["--irqchip", "split", "/dev/null"],
1719             )
1720             .unwrap(),
1721         )
1722         .unwrap();
1723 
1724         assert_eq!(cfg.irq_chip, Some(IrqChipKind::Split));
1725     }
1726 
1727     #[test]
parse_irqchip_userspace()1728     fn parse_irqchip_userspace() {
1729         let cfg = TryInto::<Config>::try_into(
1730             crate::crosvm::cmdline::RunCommand::from_args(
1731                 &[],
1732                 &["--irqchip", "userspace", "/dev/null"],
1733             )
1734             .unwrap(),
1735         )
1736         .unwrap();
1737 
1738         assert_eq!(cfg.irq_chip, Some(IrqChipKind::Userspace));
1739     }
1740 
1741     #[test]
parse_stub_pci()1742     fn parse_stub_pci() {
1743         let params = from_key_values::<StubPciParameters>("0000:01:02.3,vendor=0xfffe,device=0xfffd,class=0xffc1c2,subsystem_vendor=0xfffc,subsystem_device=0xfffb,revision=0xa").unwrap();
1744         assert_eq!(params.address.bus, 1);
1745         assert_eq!(params.address.dev, 2);
1746         assert_eq!(params.address.func, 3);
1747         assert_eq!(params.vendor, 0xfffe);
1748         assert_eq!(params.device, 0xfffd);
1749         assert_eq!(params.class.class as u8, PciClassCode::Other as u8);
1750         assert_eq!(params.class.subclass, 0xc1);
1751         assert_eq!(params.class.programming_interface, 0xc2);
1752         assert_eq!(params.subsystem_vendor, 0xfffc);
1753         assert_eq!(params.subsystem_device, 0xfffb);
1754         assert_eq!(params.revision, 0xa);
1755     }
1756 
1757     #[test]
parse_file_backed_mapping_valid()1758     fn parse_file_backed_mapping_valid() {
1759         let params = from_key_values::<FileBackedMappingParameters>(
1760             "addr=0x1000,size=0x2000,path=/dev/mem,offset=0x3000,rw,sync",
1761         )
1762         .unwrap();
1763         assert_eq!(params.address, 0x1000);
1764         assert_eq!(params.size, 0x2000);
1765         assert_eq!(params.path, PathBuf::from("/dev/mem"));
1766         assert_eq!(params.offset, 0x3000);
1767         assert!(params.writable);
1768         assert!(params.sync);
1769     }
1770 
1771     #[test]
parse_file_backed_mapping_incomplete()1772     fn parse_file_backed_mapping_incomplete() {
1773         assert!(
1774             from_key_values::<FileBackedMappingParameters>("addr=0x1000,size=0x2000")
1775                 .unwrap_err()
1776                 .contains("missing field `path`")
1777         );
1778         assert!(
1779             from_key_values::<FileBackedMappingParameters>("size=0x2000,path=/dev/mem")
1780                 .unwrap_err()
1781                 .contains("missing field `addr`")
1782         );
1783         assert!(
1784             from_key_values::<FileBackedMappingParameters>("addr=0x1000,path=/dev/mem")
1785                 .unwrap_err()
1786                 .contains("missing field `size`")
1787         );
1788     }
1789 
1790     #[test]
parse_file_backed_mapping_unaligned_addr()1791     fn parse_file_backed_mapping_unaligned_addr() {
1792         let mut params =
1793             from_key_values::<FileBackedMappingParameters>("addr=0x1001,size=0x2000,path=/dev/mem")
1794                 .unwrap();
1795         assert!(validate_file_backed_mapping(&mut params)
1796             .unwrap_err()
1797             .contains("aligned"));
1798     }
1799     #[test]
parse_file_backed_mapping_unaligned_size()1800     fn parse_file_backed_mapping_unaligned_size() {
1801         let mut params =
1802             from_key_values::<FileBackedMappingParameters>("addr=0x1000,size=0x2001,path=/dev/mem")
1803                 .unwrap();
1804         assert!(validate_file_backed_mapping(&mut params)
1805             .unwrap_err()
1806             .contains("aligned"));
1807     }
1808 
1809     #[test]
parse_file_backed_mapping_align()1810     fn parse_file_backed_mapping_align() {
1811         let addr = pagesize() as u64 * 3 + 42;
1812         let size = pagesize() as u64 - 0xf;
1813         let mut params = from_key_values::<FileBackedMappingParameters>(&format!(
1814             "addr={addr},size={size},path=/dev/mem,align",
1815         ))
1816         .unwrap();
1817         assert_eq!(params.address, addr);
1818         assert_eq!(params.size, size);
1819         validate_file_backed_mapping(&mut params).unwrap();
1820         assert_eq!(params.address, pagesize() as u64 * 3);
1821         assert_eq!(params.size, pagesize() as u64 * 2);
1822     }
1823 
1824     #[test]
parse_fw_cfg_valid_path()1825     fn parse_fw_cfg_valid_path() {
1826         let cfg = TryInto::<Config>::try_into(
1827             crate::crosvm::cmdline::RunCommand::from_args(
1828                 &[],
1829                 &["--fw-cfg", "name=bar,path=data.bin", "/dev/null"],
1830             )
1831             .unwrap(),
1832         )
1833         .unwrap();
1834 
1835         assert_eq!(cfg.fw_cfg_parameters.len(), 1);
1836         assert_eq!(cfg.fw_cfg_parameters[0].name, "bar".to_string());
1837         assert_eq!(cfg.fw_cfg_parameters[0].string, None);
1838         assert_eq!(cfg.fw_cfg_parameters[0].path, Some("data.bin".into()));
1839     }
1840 
1841     #[test]
parse_fw_cfg_valid_string()1842     fn parse_fw_cfg_valid_string() {
1843         let cfg = TryInto::<Config>::try_into(
1844             crate::crosvm::cmdline::RunCommand::from_args(
1845                 &[],
1846                 &["--fw-cfg", "name=bar,string=foo", "/dev/null"],
1847             )
1848             .unwrap(),
1849         )
1850         .unwrap();
1851 
1852         assert_eq!(cfg.fw_cfg_parameters.len(), 1);
1853         assert_eq!(cfg.fw_cfg_parameters[0].name, "bar".to_string());
1854         assert_eq!(cfg.fw_cfg_parameters[0].string, Some("foo".to_string()));
1855         assert_eq!(cfg.fw_cfg_parameters[0].path, None);
1856     }
1857 
1858     #[test]
parse_dtbo()1859     fn parse_dtbo() {
1860         let cfg: Config = crate::crosvm::cmdline::RunCommand::from_args(
1861             &[],
1862             &[
1863                 "--device-tree-overlay",
1864                 "/path/to/dtbo1",
1865                 "--device-tree-overlay",
1866                 "/path/to/dtbo2",
1867                 "/dev/null",
1868             ],
1869         )
1870         .unwrap()
1871         .try_into()
1872         .unwrap();
1873 
1874         assert_eq!(cfg.device_tree_overlay.len(), 2);
1875         for (opt, p) in cfg
1876             .device_tree_overlay
1877             .into_iter()
1878             .zip(["/path/to/dtbo1", "/path/to/dtbo2"])
1879         {
1880             assert_eq!(opt.path, PathBuf::from(p));
1881             assert!(!opt.filter_devs);
1882         }
1883     }
1884 
1885     #[test]
1886     #[cfg(any(target_os = "android", target_os = "linux"))]
parse_dtbo_filtered()1887     fn parse_dtbo_filtered() {
1888         let cfg: Config = crate::crosvm::cmdline::RunCommand::from_args(
1889             &[],
1890             &[
1891                 "--vfio",
1892                 "/path/to/dev,dt-symbol=mydev",
1893                 "--device-tree-overlay",
1894                 "/path/to/dtbo1,filter",
1895                 "--device-tree-overlay",
1896                 "/path/to/dtbo2,filter",
1897                 "/dev/null",
1898             ],
1899         )
1900         .unwrap()
1901         .try_into()
1902         .unwrap();
1903 
1904         assert_eq!(cfg.device_tree_overlay.len(), 2);
1905         for (opt, p) in cfg
1906             .device_tree_overlay
1907             .into_iter()
1908             .zip(["/path/to/dtbo1", "/path/to/dtbo2"])
1909         {
1910             assert_eq!(opt.path, PathBuf::from(p));
1911             assert!(opt.filter_devs);
1912         }
1913 
1914         assert!(TryInto::<Config>::try_into(
1915             crate::crosvm::cmdline::RunCommand::from_args(
1916                 &[],
1917                 &["--device-tree-overlay", "/path/to/dtbo,filter", "/dev/null"],
1918             )
1919             .unwrap(),
1920         )
1921         .is_err());
1922     }
1923 
1924     #[test]
parse_fw_cfg_invalid_no_name()1925     fn parse_fw_cfg_invalid_no_name() {
1926         assert!(
1927             crate::crosvm::cmdline::RunCommand::from_args(&[], &["--fw-cfg", "string=foo",])
1928                 .is_err()
1929         );
1930     }
1931 
1932     #[cfg(any(feature = "video-decoder", feature = "video-encoder"))]
1933     #[test]
parse_video()1934     fn parse_video() {
1935         use devices::virtio::device_constants::video::VideoBackendType;
1936 
1937         #[cfg(feature = "libvda")]
1938         {
1939             let params: VideoDeviceConfig = from_key_values("libvda").unwrap();
1940             assert_eq!(params.backend, VideoBackendType::Libvda);
1941 
1942             let params: VideoDeviceConfig = from_key_values("libvda-vd").unwrap();
1943             assert_eq!(params.backend, VideoBackendType::LibvdaVd);
1944         }
1945 
1946         #[cfg(feature = "ffmpeg")]
1947         {
1948             let params: VideoDeviceConfig = from_key_values("ffmpeg").unwrap();
1949             assert_eq!(params.backend, VideoBackendType::Ffmpeg);
1950         }
1951 
1952         #[cfg(feature = "vaapi")]
1953         {
1954             let params: VideoDeviceConfig = from_key_values("vaapi").unwrap();
1955             assert_eq!(params.backend, VideoBackendType::Vaapi);
1956         }
1957     }
1958 
1959     #[test]
parse_vhost_user_option()1960     fn parse_vhost_user_option() {
1961         let opt: VhostUserOption = from_key_values("/10mm").unwrap();
1962         assert_eq!(opt.socket.to_str(), Some("/10mm"));
1963         assert_eq!(opt.max_queue_size, None);
1964 
1965         let opt: VhostUserOption = from_key_values("/10mm,max-queue-size=256").unwrap();
1966         assert_eq!(opt.socket.to_str(), Some("/10mm"));
1967         assert_eq!(opt.max_queue_size, Some(256));
1968     }
1969 
1970     #[test]
parse_vhost_user_option_all_device_types()1971     fn parse_vhost_user_option_all_device_types() {
1972         fn test_device_type(type_string: &str, type_: DeviceType) {
1973             let vhost_user_arg = format!("{},socket=sock", type_string);
1974 
1975             let cfg = TryInto::<Config>::try_into(
1976                 crate::crosvm::cmdline::RunCommand::from_args(
1977                     &[],
1978                     &["--vhost-user", &vhost_user_arg, "/dev/null"],
1979                 )
1980                 .unwrap(),
1981             )
1982             .unwrap();
1983 
1984             assert_eq!(cfg.vhost_user.len(), 1);
1985             let vu = &cfg.vhost_user[0];
1986             assert_eq!(vu.type_, type_);
1987         }
1988 
1989         test_device_type("net", DeviceType::Net);
1990         test_device_type("block", DeviceType::Block);
1991         test_device_type("console", DeviceType::Console);
1992         test_device_type("rng", DeviceType::Rng);
1993         test_device_type("balloon", DeviceType::Balloon);
1994         test_device_type("scsi", DeviceType::Scsi);
1995         test_device_type("9p", DeviceType::P9);
1996         test_device_type("gpu", DeviceType::Gpu);
1997         test_device_type("input", DeviceType::Input);
1998         test_device_type("vsock", DeviceType::Vsock);
1999         test_device_type("iommu", DeviceType::Iommu);
2000         test_device_type("sound", DeviceType::Sound);
2001         test_device_type("fs", DeviceType::Fs);
2002         test_device_type("pmem", DeviceType::Pmem);
2003         test_device_type("mac80211-hwsim", DeviceType::Mac80211HwSim);
2004         test_device_type("video-encoder", DeviceType::VideoEncoder);
2005         test_device_type("video-decoder", DeviceType::VideoDecoder);
2006         test_device_type("scmi", DeviceType::Scmi);
2007         test_device_type("wl", DeviceType::Wl);
2008         test_device_type("tpm", DeviceType::Tpm);
2009         test_device_type("pvclock", DeviceType::Pvclock);
2010     }
2011 
2012     #[test]
parse_vhost_user_fs_deprecated()2013     fn parse_vhost_user_fs_deprecated() {
2014         let cfg = TryInto::<Config>::try_into(
2015             crate::crosvm::cmdline::RunCommand::from_args(
2016                 &[],
2017                 &["--vhost-user-fs", "my_socket:my_tag", "/dev/null"],
2018             )
2019             .unwrap(),
2020         )
2021         .unwrap();
2022 
2023         assert_eq!(cfg.vhost_user_fs.len(), 1);
2024         let fs = &cfg.vhost_user_fs[0];
2025         let socket = fs.socket_path.as_ref().unwrap();
2026         assert_eq!(socket.to_str(), Some("my_socket"));
2027         assert_eq!(fs.tag, Some("my_tag".to_string()));
2028         assert_eq!(fs.max_queue_size, None);
2029         assert_eq!(fs.socket_fd, None);
2030     }
2031 
2032     #[test]
parse_vhost_user_fs()2033     fn parse_vhost_user_fs() {
2034         let cfg = TryInto::<Config>::try_into(
2035             crate::crosvm::cmdline::RunCommand::from_args(
2036                 &[],
2037                 &["--vhost-user-fs", "my_socket,tag=my_tag", "/dev/null"],
2038             )
2039             .unwrap(),
2040         )
2041         .unwrap();
2042 
2043         assert_eq!(cfg.vhost_user_fs.len(), 1);
2044         let fs = &cfg.vhost_user_fs[0];
2045         let socket = fs.socket_path.as_ref().unwrap();
2046         assert_eq!(socket.to_str(), Some("my_socket"));
2047         assert_eq!(fs.tag, Some("my_tag".to_string()));
2048         assert_eq!(fs.max_queue_size, None);
2049     }
2050 
2051     #[test]
parse_vhost_user_fs_explict_socket()2052     fn parse_vhost_user_fs_explict_socket() {
2053         let cfg = TryInto::<Config>::try_into(
2054             crate::crosvm::cmdline::RunCommand::from_args(
2055                 &[],
2056                 &[
2057                     "--vhost-user-fs",
2058                     "socket=my_socket,tag=my_tag",
2059                     "/dev/null",
2060                 ],
2061             )
2062             .unwrap(),
2063         )
2064         .unwrap();
2065 
2066         assert_eq!(cfg.vhost_user_fs.len(), 1);
2067         let fs = &cfg.vhost_user_fs[0];
2068         let socket = fs.socket_path.as_ref().unwrap();
2069         assert_eq!(socket.to_str(), Some("my_socket"));
2070         assert_eq!(fs.tag, Some("my_tag".to_string()));
2071         assert_eq!(fs.max_queue_size, None);
2072     }
2073 
2074     #[test]
parse_vhost_user_fs_max_queue_size()2075     fn parse_vhost_user_fs_max_queue_size() {
2076         let cfg = TryInto::<Config>::try_into(
2077             crate::crosvm::cmdline::RunCommand::from_args(
2078                 &[],
2079                 &[
2080                     "--vhost-user-fs",
2081                     "my_socket,tag=my_tag,max-queue-size=256",
2082                     "/dev/null",
2083                 ],
2084             )
2085             .unwrap(),
2086         )
2087         .unwrap();
2088 
2089         assert_eq!(cfg.vhost_user_fs.len(), 1);
2090         let fs = &cfg.vhost_user_fs[0];
2091         let socket = fs.socket_path.as_ref().unwrap();
2092         assert_eq!(socket.to_str(), Some("my_socket"));
2093         assert_eq!(fs.tag, Some("my_tag".to_string()));
2094         assert_eq!(fs.max_queue_size, Some(256));
2095     }
2096 
2097     #[test]
parse_vhost_user_fs_no_tag()2098     fn parse_vhost_user_fs_no_tag() {
2099         let cfg = TryInto::<Config>::try_into(
2100             crate::crosvm::cmdline::RunCommand::from_args(
2101                 &[],
2102                 &["--vhost-user-fs", "my_socket", "/dev/null"],
2103             )
2104             .unwrap(),
2105         )
2106         .unwrap();
2107 
2108         assert_eq!(cfg.vhost_user_fs.len(), 1);
2109         let fs = &cfg.vhost_user_fs[0];
2110         let socket = fs.socket_path.as_ref().unwrap();
2111         assert_eq!(socket.to_str(), Some("my_socket"));
2112         assert_eq!(fs.tag, None);
2113         assert_eq!(fs.max_queue_size, None);
2114     }
2115 
2116     #[test]
parse_vhost_user_fs_socket_fd()2117     fn parse_vhost_user_fs_socket_fd() {
2118         let cfg = TryInto::<Config>::try_into(
2119             crate::crosvm::cmdline::RunCommand::from_args(
2120                 &[],
2121                 &[
2122                     "--vhost-user-fs",
2123                     "tag=my_tag,max-queue-size=256,socket-fd=1234",
2124                     "/dev/null",
2125                 ],
2126             )
2127             .unwrap(),
2128         )
2129         .unwrap();
2130 
2131         assert_eq!(cfg.vhost_user_fs.len(), 1);
2132         let fs = &cfg.vhost_user_fs[0];
2133         assert!(fs.socket_path.is_none());
2134         assert_eq!(fs.tag, Some("my_tag".to_string()));
2135         assert_eq!(fs.max_queue_size, Some(256));
2136         assert_eq!(fs.socket_fd.unwrap(), 1234_u32);
2137     }
2138 
2139     #[cfg(target_arch = "x86_64")]
2140     #[test]
parse_smbios_uuid()2141     fn parse_smbios_uuid() {
2142         let opt: SmbiosOptions =
2143             from_key_values("uuid=12e474af-2cc1-49d1-b0e5-d03a3e03ca03").unwrap();
2144         assert_eq!(
2145             opt.uuid,
2146             Some(uuid!("12e474af-2cc1-49d1-b0e5-d03a3e03ca03"))
2147         );
2148 
2149         from_key_values::<SmbiosOptions>("uuid=zzzz").expect_err("expected error parsing uuid");
2150     }
2151 
2152     #[test]
parse_touch_legacy()2153     fn parse_touch_legacy() {
2154         let cfg = TryInto::<Config>::try_into(
2155             crate::crosvm::cmdline::RunCommand::from_args(
2156                 &[],
2157                 &["--multi-touch", "my_socket:867:5309", "bzImage"],
2158             )
2159             .unwrap(),
2160         )
2161         .unwrap();
2162 
2163         assert_eq!(cfg.virtio_input.len(), 1);
2164         let multi_touch = cfg
2165             .virtio_input
2166             .iter()
2167             .find(|input| matches!(input, InputDeviceOption::MultiTouch { .. }))
2168             .unwrap();
2169         assert_eq!(
2170             *multi_touch,
2171             InputDeviceOption::MultiTouch {
2172                 path: PathBuf::from("my_socket"),
2173                 width: Some(867),
2174                 height: Some(5309),
2175                 name: None
2176             }
2177         );
2178     }
2179 
2180     #[test]
parse_touch()2181     fn parse_touch() {
2182         let cfg = TryInto::<Config>::try_into(
2183             crate::crosvm::cmdline::RunCommand::from_args(
2184                 &[],
2185                 &["--multi-touch", r"C:\path,width=867,height=5309", "bzImage"],
2186             )
2187             .unwrap(),
2188         )
2189         .unwrap();
2190 
2191         assert_eq!(cfg.virtio_input.len(), 1);
2192         let multi_touch = cfg
2193             .virtio_input
2194             .iter()
2195             .find(|input| matches!(input, InputDeviceOption::MultiTouch { .. }))
2196             .unwrap();
2197         assert_eq!(
2198             *multi_touch,
2199             InputDeviceOption::MultiTouch {
2200                 path: PathBuf::from(r"C:\path"),
2201                 width: Some(867),
2202                 height: Some(5309),
2203                 name: None
2204             }
2205         );
2206     }
2207 
2208     #[test]
single_touch_spec_and_track_pad_spec_default_size()2209     fn single_touch_spec_and_track_pad_spec_default_size() {
2210         let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
2211             &[],
2212             &[
2213                 "--single-touch",
2214                 "/dev/single-touch-test",
2215                 "--trackpad",
2216                 "/dev/single-touch-test",
2217                 "/dev/null",
2218             ],
2219         )
2220         .unwrap()
2221         .try_into()
2222         .unwrap();
2223 
2224         let single_touch = config
2225             .virtio_input
2226             .iter()
2227             .find(|input| matches!(input, InputDeviceOption::SingleTouch { .. }))
2228             .unwrap();
2229         let trackpad = config
2230             .virtio_input
2231             .iter()
2232             .find(|input| matches!(input, InputDeviceOption::Trackpad { .. }))
2233             .unwrap();
2234 
2235         assert_eq!(
2236             *single_touch,
2237             InputDeviceOption::SingleTouch {
2238                 path: PathBuf::from("/dev/single-touch-test"),
2239                 width: None,
2240                 height: None,
2241                 name: None
2242             }
2243         );
2244         assert_eq!(
2245             *trackpad,
2246             InputDeviceOption::Trackpad {
2247                 path: PathBuf::from("/dev/single-touch-test"),
2248                 width: None,
2249                 height: None,
2250                 name: None
2251             }
2252         );
2253     }
2254 
2255     #[cfg(feature = "gpu")]
2256     #[test]
single_touch_spec_default_size_from_gpu()2257     fn single_touch_spec_default_size_from_gpu() {
2258         let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
2259             &[],
2260             &[
2261                 "--single-touch",
2262                 "/dev/single-touch-test",
2263                 "--gpu",
2264                 "width=1024,height=768",
2265                 "/dev/null",
2266             ],
2267         )
2268         .unwrap()
2269         .try_into()
2270         .unwrap();
2271 
2272         let single_touch = config
2273             .virtio_input
2274             .iter()
2275             .find(|input| matches!(input, InputDeviceOption::SingleTouch { .. }))
2276             .unwrap();
2277         assert_eq!(
2278             *single_touch,
2279             InputDeviceOption::SingleTouch {
2280                 path: PathBuf::from("/dev/single-touch-test"),
2281                 width: None,
2282                 height: None,
2283                 name: None
2284             }
2285         );
2286 
2287         assert_eq!(config.display_input_width, Some(1024));
2288         assert_eq!(config.display_input_height, Some(768));
2289     }
2290 
2291     #[test]
single_touch_spec_and_track_pad_spec_with_size()2292     fn single_touch_spec_and_track_pad_spec_with_size() {
2293         let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
2294             &[],
2295             &[
2296                 "--single-touch",
2297                 "/dev/single-touch-test:12345:54321",
2298                 "--trackpad",
2299                 "/dev/single-touch-test:5678:9876",
2300                 "/dev/null",
2301             ],
2302         )
2303         .unwrap()
2304         .try_into()
2305         .unwrap();
2306 
2307         let single_touch = config
2308             .virtio_input
2309             .iter()
2310             .find(|input| matches!(input, InputDeviceOption::SingleTouch { .. }))
2311             .unwrap();
2312         let trackpad = config
2313             .virtio_input
2314             .iter()
2315             .find(|input| matches!(input, InputDeviceOption::Trackpad { .. }))
2316             .unwrap();
2317 
2318         assert_eq!(
2319             *single_touch,
2320             InputDeviceOption::SingleTouch {
2321                 path: PathBuf::from("/dev/single-touch-test"),
2322                 width: Some(12345),
2323                 height: Some(54321),
2324                 name: None
2325             }
2326         );
2327         assert_eq!(
2328             *trackpad,
2329             InputDeviceOption::Trackpad {
2330                 path: PathBuf::from("/dev/single-touch-test"),
2331                 width: Some(5678),
2332                 height: Some(9876),
2333                 name: None
2334             }
2335         );
2336     }
2337 
2338     #[cfg(feature = "gpu")]
2339     #[test]
single_touch_spec_with_size_independent_from_gpu()2340     fn single_touch_spec_with_size_independent_from_gpu() {
2341         let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
2342             &[],
2343             &[
2344                 "--single-touch",
2345                 "/dev/single-touch-test:12345:54321",
2346                 "--gpu",
2347                 "width=1024,height=768",
2348                 "/dev/null",
2349             ],
2350         )
2351         .unwrap()
2352         .try_into()
2353         .unwrap();
2354 
2355         let single_touch = config
2356             .virtio_input
2357             .iter()
2358             .find(|input| matches!(input, InputDeviceOption::SingleTouch { .. }))
2359             .unwrap();
2360 
2361         assert_eq!(
2362             *single_touch,
2363             InputDeviceOption::SingleTouch {
2364                 path: PathBuf::from("/dev/single-touch-test"),
2365                 width: Some(12345),
2366                 height: Some(54321),
2367                 name: None
2368             }
2369         );
2370 
2371         assert_eq!(config.display_input_width, Some(1024));
2372         assert_eq!(config.display_input_height, Some(768));
2373     }
2374 
2375     #[test]
virtio_switches()2376     fn virtio_switches() {
2377         let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
2378             &[],
2379             &["--switches", "/dev/switches-test", "/dev/null"],
2380         )
2381         .unwrap()
2382         .try_into()
2383         .unwrap();
2384 
2385         let switches = config
2386             .virtio_input
2387             .iter()
2388             .find(|input| matches!(input, InputDeviceOption::Switches { .. }))
2389             .unwrap();
2390 
2391         assert_eq!(
2392             *switches,
2393             InputDeviceOption::Switches {
2394                 path: PathBuf::from("/dev/switches-test")
2395             }
2396         );
2397     }
2398 
2399     #[test]
virtio_rotary()2400     fn virtio_rotary() {
2401         let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
2402             &[],
2403             &["--rotary", "/dev/rotary-test", "/dev/null"],
2404         )
2405         .unwrap()
2406         .try_into()
2407         .unwrap();
2408 
2409         let rotary = config
2410             .virtio_input
2411             .iter()
2412             .find(|input| matches!(input, InputDeviceOption::Rotary { .. }))
2413             .unwrap();
2414 
2415         assert_eq!(
2416             *rotary,
2417             InputDeviceOption::Rotary {
2418                 path: PathBuf::from("/dev/rotary-test")
2419             }
2420         );
2421     }
2422 
2423     #[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
2424     #[test]
parse_pci_cam()2425     fn parse_pci_cam() {
2426         assert_eq!(
2427             config_from_args(&["--pci", "cam=[start=0x123]", "/dev/null"]).pci_config,
2428             PciConfig {
2429                 cam: Some(arch::MemoryRegionConfig {
2430                     start: 0x123,
2431                     size: None,
2432                 }),
2433                 ..PciConfig::default()
2434             }
2435         );
2436         assert_eq!(
2437             config_from_args(&["--pci", "cam=[start=0x123,size=0x456]", "/dev/null"]).pci_config,
2438             PciConfig {
2439                 cam: Some(arch::MemoryRegionConfig {
2440                     start: 0x123,
2441                     size: Some(0x456),
2442                 }),
2443                 ..PciConfig::default()
2444             },
2445         );
2446     }
2447 
2448     #[cfg(target_arch = "x86_64")]
2449     #[test]
parse_pci_ecam()2450     fn parse_pci_ecam() {
2451         assert_eq!(
2452             config_from_args(&["--pci", "ecam=[start=0x123]", "/dev/null"]).pci_config,
2453             PciConfig {
2454                 ecam: Some(arch::MemoryRegionConfig {
2455                     start: 0x123,
2456                     size: None,
2457                 }),
2458                 ..PciConfig::default()
2459             }
2460         );
2461         assert_eq!(
2462             config_from_args(&["--pci", "ecam=[start=0x123,size=0x456]", "/dev/null"]).pci_config,
2463             PciConfig {
2464                 ecam: Some(arch::MemoryRegionConfig {
2465                     start: 0x123,
2466                     size: Some(0x456),
2467                 }),
2468                 ..PciConfig::default()
2469             },
2470         );
2471     }
2472 
2473     #[test]
parse_pci_mem()2474     fn parse_pci_mem() {
2475         assert_eq!(
2476             config_from_args(&["--pci", "mem=[start=0x123]", "/dev/null"]).pci_config,
2477             PciConfig {
2478                 mem: Some(arch::MemoryRegionConfig {
2479                     start: 0x123,
2480                     size: None,
2481                 }),
2482                 ..PciConfig::default()
2483             }
2484         );
2485         assert_eq!(
2486             config_from_args(&["--pci", "mem=[start=0x123,size=0x456]", "/dev/null"]).pci_config,
2487             PciConfig {
2488                 mem: Some(arch::MemoryRegionConfig {
2489                     start: 0x123,
2490                     size: Some(0x456),
2491                 }),
2492                 ..PciConfig::default()
2493             },
2494         );
2495     }
2496 
2497     #[test]
parse_pmem_options_missing_path()2498     fn parse_pmem_options_missing_path() {
2499         assert!(from_key_values::<PmemOption>("")
2500             .unwrap_err()
2501             .contains("missing field `path`"));
2502     }
2503 
2504     #[test]
parse_pmem_options_default_values()2505     fn parse_pmem_options_default_values() {
2506         let pmem = from_key_values::<PmemOption>("/path/to/disk.img").unwrap();
2507         assert_eq!(
2508             pmem,
2509             PmemOption {
2510                 path: "/path/to/disk.img".into(),
2511                 ro: false,
2512                 root: false,
2513                 vma_size: None,
2514                 swap_interval: None,
2515             }
2516         );
2517     }
2518 
2519     #[test]
parse_pmem_options_virtual_swap()2520     fn parse_pmem_options_virtual_swap() {
2521         let pmem =
2522             from_key_values::<PmemOption>("virtual_path,vma-size=12345,swap-interval-ms=1000")
2523                 .unwrap();
2524         assert_eq!(
2525             pmem,
2526             PmemOption {
2527                 path: "virtual_path".into(),
2528                 ro: false,
2529                 root: false,
2530                 vma_size: Some(12345),
2531                 swap_interval: Some(Duration::new(1, 0)),
2532             }
2533         );
2534     }
2535 
2536     #[test]
validate_pmem_missing_virtual_swap_param()2537     fn validate_pmem_missing_virtual_swap_param() {
2538         let pmem = from_key_values::<PmemOption>("virtual_path,swap-interval-ms=1000").unwrap();
2539         assert!(validate_pmem(&pmem)
2540             .unwrap_err()
2541             .contains("vma-size and swap-interval parameters must be specified together"));
2542     }
2543 
2544     #[test]
validate_pmem_read_only_virtual_swap()2545     fn validate_pmem_read_only_virtual_swap() {
2546         let pmem = from_key_values::<PmemOption>(
2547             "virtual_path,ro=true,vma-size=12345,swap-interval-ms=1000",
2548         )
2549         .unwrap();
2550         assert!(validate_pmem(&pmem)
2551             .unwrap_err()
2552             .contains("swap-interval parameter can only be set for writable pmem device"));
2553     }
2554 }
2555