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