crosvm/crosvm/
config.rs

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